summaryrefslogtreecommitdiffstats
path: root/mailnews
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews')
-rw-r--r--mailnews/addrbook/content/abAddressBookNameDialog.js72
-rw-r--r--mailnews/addrbook/content/abAddressBookNameDialog.xul26
-rw-r--r--mailnews/addrbook/content/abDragDrop.js424
-rw-r--r--mailnews/addrbook/content/abEditCardDialog.xul17
-rw-r--r--mailnews/addrbook/content/abMailListDialog.js613
-rw-r--r--mailnews/addrbook/content/abNewCardDialog.xul33
-rw-r--r--mailnews/addrbook/content/abResultsPane.js507
-rw-r--r--mailnews/addrbook/content/abResultsPaneOverlay.xul90
-rw-r--r--mailnews/addrbook/content/addrbookWidgets.xml439
-rw-r--r--mailnews/addrbook/content/print.css94
-rw-r--r--mailnews/addrbook/moz.build9
-rw-r--r--mailnews/addrbook/prefs/content/pref-directory-add.js394
-rw-r--r--mailnews/addrbook/prefs/content/pref-directory-add.xul152
-rw-r--r--mailnews/addrbook/prefs/content/pref-editdirectories.js142
-rw-r--r--mailnews/addrbook/prefs/content/pref-editdirectories.xul43
-rw-r--r--mailnews/addrbook/public/moz.build47
-rw-r--r--mailnews/addrbook/public/nsAbBaseCID.h445
-rw-r--r--mailnews/addrbook/public/nsIAbAddressCollector.idl58
-rw-r--r--mailnews/addrbook/public/nsIAbAutoCompleteResult.idl36
-rw-r--r--mailnews/addrbook/public/nsIAbBooleanExpression.idl122
-rw-r--r--mailnews/addrbook/public/nsIAbCard.idl358
-rw-r--r--mailnews/addrbook/public/nsIAbCollection.idl92
-rw-r--r--mailnews/addrbook/public/nsIAbDirFactory.idl35
-rw-r--r--mailnews/addrbook/public/nsIAbDirFactoryService.idl28
-rw-r--r--mailnews/addrbook/public/nsIAbDirSearchListener.idl15
-rw-r--r--mailnews/addrbook/public/nsIAbDirectory.idl296
-rw-r--r--mailnews/addrbook/public/nsIAbDirectoryQuery.idl164
-rw-r--r--mailnews/addrbook/public/nsIAbDirectoryQueryProxy.idl14
-rw-r--r--mailnews/addrbook/public/nsIAbDirectorySearch.idl53
-rw-r--r--mailnews/addrbook/public/nsIAbItem.idl90
-rw-r--r--mailnews/addrbook/public/nsIAbLDAPAttributeMap.idl194
-rw-r--r--mailnews/addrbook/public/nsIAbLDAPCard.idl56
-rw-r--r--mailnews/addrbook/public/nsIAbLDAPDirectory.idl112
-rw-r--r--mailnews/addrbook/public/nsIAbLDAPReplicationData.idl68
-rw-r--r--mailnews/addrbook/public/nsIAbLDAPReplicationQuery.idl67
-rw-r--r--mailnews/addrbook/public/nsIAbLDAPReplicationService.idl32
-rw-r--r--mailnews/addrbook/public/nsIAbLDIFService.idl40
-rw-r--r--mailnews/addrbook/public/nsIAbListener.idl90
-rw-r--r--mailnews/addrbook/public/nsIAbMDBDirectory.idl71
-rw-r--r--mailnews/addrbook/public/nsIAbManager.idl190
-rw-r--r--mailnews/addrbook/public/nsIAbView.idl109
-rw-r--r--mailnews/addrbook/public/nsIAddbookUrl.idl19
-rw-r--r--mailnews/addrbook/public/nsIAddrDBAnnouncer.idl35
-rw-r--r--mailnews/addrbook/public/nsIAddrDBListener.idl36
-rw-r--r--mailnews/addrbook/public/nsIAddrDatabase.idl311
-rw-r--r--mailnews/addrbook/public/nsIMsgVCardService.idl29
-rw-r--r--mailnews/addrbook/src/moz.build93
-rw-r--r--mailnews/addrbook/src/nsAbAddressCollector.cpp331
-rw-r--r--mailnews/addrbook/src/nsAbAddressCollector.h44
-rw-r--r--mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js58
-rw-r--r--mailnews/addrbook/src/nsAbAutoCompleteSearch.js466
-rw-r--r--mailnews/addrbook/src/nsAbBSDirectory.cpp322
-rw-r--r--mailnews/addrbook/src/nsAbBSDirectory.h50
-rw-r--r--mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp246
-rw-r--r--mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h45
-rw-r--r--mailnews/addrbook/src/nsAbBooleanExpression.cpp132
-rw-r--r--mailnews/addrbook/src/nsAbBooleanExpression.h43
-rw-r--r--mailnews/addrbook/src/nsAbCardProperty.cpp1193
-rw-r--r--mailnews/addrbook/src/nsAbCardProperty.h59
-rw-r--r--mailnews/addrbook/src/nsAbContentHandler.cpp184
-rw-r--r--mailnews/addrbook/src/nsAbContentHandler.h26
-rw-r--r--mailnews/addrbook/src/nsAbDirFactoryService.cpp54
-rw-r--r--mailnews/addrbook/src/nsAbDirFactoryService.h23
-rw-r--r--mailnews/addrbook/src/nsAbDirProperty.cpp593
-rw-r--r--mailnews/addrbook/src/nsAbDirProperty.h70
-rw-r--r--mailnews/addrbook/src/nsAbDirectoryQuery.cpp528
-rw-r--r--mailnews/addrbook/src/nsAbDirectoryQuery.h113
-rw-r--r--mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp33
-rw-r--r--mailnews/addrbook/src/nsAbDirectoryQueryProxy.h27
-rw-r--r--mailnews/addrbook/src/nsAbLDAPAttributeMap.js247
-rw-r--r--mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js325
-rw-r--r--mailnews/addrbook/src/nsAbLDAPCard.cpp297
-rw-r--r--mailnews/addrbook/src/nsAbLDAPCard.h30
-rw-r--r--mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp541
-rw-r--r--mailnews/addrbook/src/nsAbLDAPChangeLogData.h57
-rw-r--r--mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp180
-rw-r--r--mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h28
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirFactory.cpp79
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirFactory.h23
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectory.cpp949
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectory.h75
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp371
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectoryModify.h30
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp609
-rw-r--r--mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h44
-rw-r--r--mailnews/addrbook/src/nsAbLDAPListenerBase.cpp358
-rw-r--r--mailnews/addrbook/src/nsAbLDAPListenerBase.h54
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationData.cpp489
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationData.h66
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp152
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationQuery.h43
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationService.cpp136
-rw-r--r--mailnews/addrbook/src/nsAbLDAPReplicationService.h33
-rw-r--r--mailnews/addrbook/src/nsAbLDIFService.cpp868
-rw-r--r--mailnews/addrbook/src/nsAbLDIFService.h37
-rw-r--r--mailnews/addrbook/src/nsAbMDBCard.cpp55
-rw-r--r--mailnews/addrbook/src/nsAbMDBCard.h26
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirFactory.cpp117
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirFactory.h24
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirProperty.cpp143
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirProperty.h40
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirectory.cpp1123
-rw-r--r--mailnews/addrbook/src/nsAbMDBDirectory.h104
-rw-r--r--mailnews/addrbook/src/nsAbManager.cpp1418
-rw-r--r--mailnews/addrbook/src/nsAbManager.h70
-rw-r--r--mailnews/addrbook/src/nsAbOSXCard.h47
-rw-r--r--mailnews/addrbook/src/nsAbOSXCard.mm401
-rw-r--r--mailnews/addrbook/src/nsAbOSXDirFactory.cpp50
-rw-r--r--mailnews/addrbook/src/nsAbOSXDirFactory.h21
-rw-r--r--mailnews/addrbook/src/nsAbOSXDirectory.h124
-rw-r--r--mailnews/addrbook/src/nsAbOSXDirectory.mm1374
-rw-r--r--mailnews/addrbook/src/nsAbOSXUtils.h36
-rw-r--r--mailnews/addrbook/src/nsAbOSXUtils.mm117
-rw-r--r--mailnews/addrbook/src/nsAbOutlookDirFactory.cpp87
-rw-r--r--mailnews/addrbook/src/nsAbOutlookDirFactory.h22
-rw-r--r--mailnews/addrbook/src/nsAbOutlookDirectory.cpp1539
-rw-r--r--mailnews/addrbook/src/nsAbOutlookDirectory.h152
-rw-r--r--mailnews/addrbook/src/nsAbQueryStringToExpression.cpp337
-rw-r--r--mailnews/addrbook/src/nsAbQueryStringToExpression.h49
-rw-r--r--mailnews/addrbook/src/nsAbUtils.h140
-rw-r--r--mailnews/addrbook/src/nsAbView.cpp1453
-rw-r--r--mailnews/addrbook/src/nsAbView.h83
-rw-r--r--mailnews/addrbook/src/nsAbWinHelper.cpp1003
-rw-r--r--mailnews/addrbook/src/nsAbWinHelper.h156
-rw-r--r--mailnews/addrbook/src/nsAddbookProtocolHandler.cpp327
-rw-r--r--mailnews/addrbook/src/nsAddbookProtocolHandler.h45
-rw-r--r--mailnews/addrbook/src/nsAddbookUrl.cpp306
-rw-r--r--mailnews/addrbook/src/nsAddbookUrl.h39
-rw-r--r--mailnews/addrbook/src/nsAddrDatabase.cpp3334
-rw-r--r--mailnews/addrbook/src/nsAddrDatabase.h438
-rw-r--r--mailnews/addrbook/src/nsAddrbook.manifest12
-rw-r--r--mailnews/addrbook/src/nsDirPrefs.cpp1452
-rw-r--r--mailnews/addrbook/src/nsDirPrefs.h86
-rw-r--r--mailnews/addrbook/src/nsMapiAddressBook.cpp147
-rw-r--r--mailnews/addrbook/src/nsMapiAddressBook.h54
-rw-r--r--mailnews/addrbook/src/nsMsgVCardService.cpp77
-rw-r--r--mailnews/addrbook/src/nsMsgVCardService.h24
-rw-r--r--mailnews/addrbook/src/nsVCard.cpp1571
-rw-r--r--mailnews/addrbook/src/nsVCard.h64
-rw-r--r--mailnews/addrbook/src/nsVCardObj.cpp1330
-rw-r--r--mailnews/addrbook/src/nsVCardObj.h396
-rw-r--r--mailnews/addrbook/src/nsWabAddressBook.cpp127
-rw-r--r--mailnews/addrbook/src/nsWabAddressBook.h57
-rw-r--r--mailnews/base/content/charsetList.css5
-rw-r--r--mailnews/base/content/charsetList.xml69
-rw-r--r--mailnews/base/content/dateFormat.js225
-rw-r--r--mailnews/base/content/folderProps.js387
-rw-r--r--mailnews/base/content/folderProps.xul216
-rw-r--r--mailnews/base/content/folderWidgets.xml829
-rw-r--r--mailnews/base/content/jsTreeView.js235
-rw-r--r--mailnews/base/content/junkCommands.js456
-rw-r--r--mailnews/base/content/junkLog.js33
-rw-r--r--mailnews/base/content/junkLog.xul46
-rw-r--r--mailnews/base/content/junkMailInfo.xul35
-rw-r--r--mailnews/base/content/markByDate.js133
-rw-r--r--mailnews/base/content/markByDate.xul34
-rw-r--r--mailnews/base/content/msgAccountCentral.js341
-rw-r--r--mailnews/base/content/msgAccountCentral.xul246
-rw-r--r--mailnews/base/content/msgFolderPickerOverlay.js99
-rw-r--r--mailnews/base/content/msgPrintEngine.js209
-rw-r--r--mailnews/base/content/msgSynchronize.js199
-rw-r--r--mailnews/base/content/msgSynchronize.xul43
-rw-r--r--mailnews/base/content/newFolderDialog.js85
-rw-r--r--mailnews/base/content/newFolderDialog.xul57
-rw-r--r--mailnews/base/content/newmailalert.css35
-rw-r--r--mailnews/base/content/newmailalert.js194
-rw-r--r--mailnews/base/content/newmailalert.xul35
-rw-r--r--mailnews/base/content/renameFolderDialog.js47
-rw-r--r--mailnews/base/content/renameFolderDialog.xul23
-rw-r--r--mailnews/base/content/retention.js45
-rw-r--r--mailnews/base/content/shareglue.js20
-rw-r--r--mailnews/base/content/shutdownWindow.js99
-rw-r--r--mailnews/base/content/shutdownWindow.xul31
-rw-r--r--mailnews/base/content/virtualFolderListDialog.js136
-rw-r--r--mailnews/base/content/virtualFolderListDialog.xul91
-rw-r--r--mailnews/base/content/virtualFolderProperties.js266
-rw-r--r--mailnews/base/content/virtualFolderProperties.xul103
-rw-r--r--mailnews/base/ispdata/README3
-rw-r--r--mailnews/base/ispdata/aol.rdf84
-rw-r--r--mailnews/base/ispdata/gmail.rdf127
-rw-r--r--mailnews/base/ispdata/movemail.rdf42
-rw-r--r--mailnews/base/ispdata/moz.build8
-rw-r--r--mailnews/base/moz.build13
-rw-r--r--mailnews/base/prefs/content/AccountManager.js1604
-rw-r--r--mailnews/base/prefs/content/AccountManager.xul87
-rw-r--r--mailnews/base/prefs/content/AccountWizard.js988
-rw-r--r--mailnews/base/prefs/content/AccountWizard.xul371
-rw-r--r--mailnews/base/prefs/content/SmtpServerEdit.js46
-rw-r--r--mailnews/base/prefs/content/SmtpServerEdit.xul28
-rw-r--r--mailnews/base/prefs/content/accountUtils.js462
-rw-r--r--mailnews/base/prefs/content/accountcreation/MyBadCertHandler.js41
-rw-r--r--mailnews/base/prefs/content/accountcreation/accountConfig.js261
-rw-r--r--mailnews/base/prefs/content/accountcreation/createInBackend.js337
-rw-r--r--mailnews/base/prefs/content/accountcreation/emailWizard.js1976
-rw-r--r--mailnews/base/prefs/content/accountcreation/emailWizard.xul497
-rw-r--r--mailnews/base/prefs/content/accountcreation/fetchConfig.js237
-rw-r--r--mailnews/base/prefs/content/accountcreation/fetchhttp.js264
-rw-r--r--mailnews/base/prefs/content/accountcreation/guessConfig.js1192
-rw-r--r--mailnews/base/prefs/content/accountcreation/readFromXML.js243
-rw-r--r--mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js205
-rw-r--r--mailnews/base/prefs/content/accountcreation/util.js304
-rw-r--r--mailnews/base/prefs/content/accountcreation/verifyConfig.js353
-rw-r--r--mailnews/base/prefs/content/am-addressing.js79
-rw-r--r--mailnews/base/prefs/content/am-addressing.xul18
-rw-r--r--mailnews/base/prefs/content/am-addressingOverlay.xul135
-rw-r--r--mailnews/base/prefs/content/am-archiveoptions.js69
-rw-r--r--mailnews/base/prefs/content/am-archiveoptions.xul99
-rw-r--r--mailnews/base/prefs/content/am-copies.js471
-rw-r--r--mailnews/base/prefs/content/am-copies.xul21
-rw-r--r--mailnews/base/prefs/content/am-copiesOverlay.xul311
-rw-r--r--mailnews/base/prefs/content/am-help.js76
-rw-r--r--mailnews/base/prefs/content/am-identities-list.js180
-rw-r--r--mailnews/base/prefs/content/am-identities-list.xul67
-rw-r--r--mailnews/base/prefs/content/am-identity-edit.js405
-rw-r--r--mailnews/base/prefs/content/am-identity-edit.xul154
-rw-r--r--mailnews/base/prefs/content/am-junk.js296
-rw-r--r--mailnews/base/prefs/content/am-junk.xul232
-rw-r--r--mailnews/base/prefs/content/am-main.js55
-rw-r--r--mailnews/base/prefs/content/am-main.xul147
-rw-r--r--mailnews/base/prefs/content/am-offline.js351
-rw-r--r--mailnews/base/prefs/content/am-offline.xul158
-rw-r--r--mailnews/base/prefs/content/am-prefs.js114
-rw-r--r--mailnews/base/prefs/content/am-server-advanced.js157
-rw-r--r--mailnews/base/prefs/content/am-server-advanced.xul146
-rw-r--r--mailnews/base/prefs/content/am-server-top.xul13
-rw-r--r--mailnews/base/prefs/content/am-server.js404
-rw-r--r--mailnews/base/prefs/content/am-server.xul470
-rw-r--r--mailnews/base/prefs/content/am-serverwithnoidentities.js34
-rw-r--r--mailnews/base/prefs/content/am-serverwithnoidentities.xul77
-rw-r--r--mailnews/base/prefs/content/am-smtp.js263
-rw-r--r--mailnews/base/prefs/content/am-smtp.xul112
-rw-r--r--mailnews/base/prefs/content/amUtils.js205
-rw-r--r--mailnews/base/prefs/content/aw-accname.js73
-rw-r--r--mailnews/base/prefs/content/aw-accounttype.js114
-rw-r--r--mailnews/base/prefs/content/aw-done.js215
-rw-r--r--mailnews/base/prefs/content/aw-identity.js212
-rw-r--r--mailnews/base/prefs/content/aw-incoming.js176
-rw-r--r--mailnews/base/prefs/content/aw-outgoing.js151
-rw-r--r--mailnews/base/prefs/content/ispUtils.js166
-rw-r--r--mailnews/base/prefs/content/removeAccount.js148
-rw-r--r--mailnews/base/prefs/content/removeAccount.xul82
-rw-r--r--mailnews/base/prefs/content/smtpEditOverlay.js184
-rw-r--r--mailnews/base/prefs/content/smtpEditOverlay.xul126
-rw-r--r--mailnews/base/public/MailNewsTypes.h39
-rw-r--r--mailnews/base/public/MailNewsTypes2.idl93
-rw-r--r--mailnews/base/public/moz.build77
-rw-r--r--mailnews/base/public/mozINewMailListener.idl22
-rw-r--r--mailnews/base/public/mozINewMailNotificationService.idl58
-rw-r--r--mailnews/base/public/msgCore.h201
-rw-r--r--mailnews/base/public/msgIOAuth2Module.idl59
-rw-r--r--mailnews/base/public/nsICopyMessageListener.idl24
-rw-r--r--mailnews/base/public/nsICopyMsgStreamListener.idl18
-rw-r--r--mailnews/base/public/nsIFolderListener.idl63
-rw-r--r--mailnews/base/public/nsIFolderLookupService.idl35
-rw-r--r--mailnews/base/public/nsIIncomingServerListener.idl33
-rw-r--r--mailnews/base/public/nsIMapiRegistry.idl50
-rw-r--r--mailnews/base/public/nsIMessenger.idl141
-rw-r--r--mailnews/base/public/nsIMessengerMigrator.idl15
-rw-r--r--mailnews/base/public/nsIMessengerOSIntegration.idl14
-rw-r--r--mailnews/base/public/nsIMessengerWindowService.idl17
-rw-r--r--mailnews/base/public/nsIMsgAccount.idl88
-rw-r--r--mailnews/base/public/nsIMsgAccountManager.idl240
-rw-r--r--mailnews/base/public/nsIMsgAsyncPrompter.idl63
-rw-r--r--mailnews/base/public/nsIMsgBiffManager.idl19
-rw-r--r--mailnews/base/public/nsIMsgContentPolicy.idl36
-rw-r--r--mailnews/base/public/nsIMsgCopyService.idl105
-rw-r--r--mailnews/base/public/nsIMsgCopyServiceListener.idl56
-rw-r--r--mailnews/base/public/nsIMsgCustomColumnHandler.idl42
-rw-r--r--mailnews/base/public/nsIMsgDBView.idl527
-rw-r--r--mailnews/base/public/nsIMsgFolder.idl853
-rw-r--r--mailnews/base/public/nsIMsgFolderCache.idl21
-rw-r--r--mailnews/base/public/nsIMsgFolderCacheElement.idl19
-rw-r--r--mailnews/base/public/nsIMsgFolderCompactor.idl46
-rw-r--r--mailnews/base/public/nsIMsgFolderListener.idl212
-rw-r--r--mailnews/base/public/nsIMsgFolderNotificationService.idl98
-rw-r--r--mailnews/base/public/nsIMsgHdr.idl104
-rw-r--r--mailnews/base/public/nsIMsgIdentity.idl250
-rw-r--r--mailnews/base/public/nsIMsgIncomingServer.idl590
-rw-r--r--mailnews/base/public/nsIMsgKeyArray.idl54
-rw-r--r--mailnews/base/public/nsIMsgMailNewsUrl.idl201
-rw-r--r--mailnews/base/public/nsIMsgMailSession.idl79
-rw-r--r--mailnews/base/public/nsIMsgMdnGenerator.idl81
-rw-r--r--mailnews/base/public/nsIMsgMessageService.idl259
-rw-r--r--mailnews/base/public/nsIMsgOfflineManager.idl23
-rw-r--r--mailnews/base/public/nsIMsgPluggableStore.idl330
-rw-r--r--mailnews/base/public/nsIMsgPrintEngine.idl40
-rw-r--r--mailnews/base/public/nsIMsgProgress.idl40
-rw-r--r--mailnews/base/public/nsIMsgProtocolInfo.idl99
-rw-r--r--mailnews/base/public/nsIMsgPurgeService.idl14
-rw-r--r--mailnews/base/public/nsIMsgRDFDataSource.idl16
-rw-r--r--mailnews/base/public/nsIMsgShutdown.idl69
-rw-r--r--mailnews/base/public/nsIMsgStatusFeedback.idl19
-rw-r--r--mailnews/base/public/nsIMsgTagService.idl67
-rw-r--r--mailnews/base/public/nsIMsgThread.idl36
-rw-r--r--mailnews/base/public/nsIMsgUserFeedbackListener.idl28
-rw-r--r--mailnews/base/public/nsIMsgWindow.idl98
-rw-r--r--mailnews/base/public/nsIMsgWindowData.idl23
-rw-r--r--mailnews/base/public/nsISpamSettings.idl97
-rw-r--r--mailnews/base/public/nsIStatusBarBiffManager.idl13
-rw-r--r--mailnews/base/public/nsIStopwatch.idl44
-rw-r--r--mailnews/base/public/nsISubscribableServer.idl76
-rw-r--r--mailnews/base/public/nsIUrlListener.idl32
-rw-r--r--mailnews/base/public/nsMsgBaseCID.h536
-rw-r--r--mailnews/base/public/nsMsgFolderFlags.idl115
-rw-r--r--mailnews/base/public/nsMsgGroupnameFlags.h49
-rw-r--r--mailnews/base/public/nsMsgHeaderMasks.h53
-rw-r--r--mailnews/base/public/nsMsgLocalFolderHdrs.h39
-rw-r--r--mailnews/base/public/nsMsgMessageFlags.idl173
-rw-r--r--mailnews/base/search/content/CustomHeaders.js203
-rw-r--r--mailnews/base/search/content/CustomHeaders.xul57
-rw-r--r--mailnews/base/search/content/FilterEditor.js856
-rw-r--r--mailnews/base/search/content/FilterEditor.xul125
-rw-r--r--mailnews/base/search/content/searchTermOverlay.js536
-rw-r--r--mailnews/base/search/content/searchTermOverlay.xul66
-rw-r--r--mailnews/base/search/content/searchWidgets.xml740
-rw-r--r--mailnews/base/search/content/viewLog.js36
-rw-r--r--mailnews/base/search/content/viewLog.xul49
-rw-r--r--mailnews/base/search/public/moz.build38
-rw-r--r--mailnews/base/search/public/nsIMsgFilter.idl142
-rw-r--r--mailnews/base/search/public/nsIMsgFilterCustomAction.idl90
-rw-r--r--mailnews/base/search/public/nsIMsgFilterHitNotify.idl27
-rw-r--r--mailnews/base/search/public/nsIMsgFilterList.idl108
-rw-r--r--mailnews/base/search/public/nsIMsgFilterPlugin.idl350
-rw-r--r--mailnews/base/search/public/nsIMsgFilterService.idl95
-rw-r--r--mailnews/base/search/public/nsIMsgOperationListener.idl17
-rw-r--r--mailnews/base/search/public/nsIMsgSearchAdapter.idl42
-rw-r--r--mailnews/base/search/public/nsIMsgSearchCustomTerm.idl79
-rw-r--r--mailnews/base/search/public/nsIMsgSearchNotify.idl31
-rw-r--r--mailnews/base/search/public/nsIMsgSearchScopeTerm.idl20
-rw-r--r--mailnews/base/search/public/nsIMsgSearchSession.idl147
-rw-r--r--mailnews/base/search/public/nsIMsgSearchTerm.idl156
-rw-r--r--mailnews/base/search/public/nsIMsgSearchValidityManager.idl26
-rw-r--r--mailnews/base/search/public/nsIMsgSearchValidityTable.idl50
-rw-r--r--mailnews/base/search/public/nsIMsgSearchValue.idl36
-rw-r--r--mailnews/base/search/public/nsIMsgTraitService.idl174
-rw-r--r--mailnews/base/search/public/nsMsgBodyHandler.h112
-rw-r--r--mailnews/base/search/public/nsMsgFilterCore.idl63
-rw-r--r--mailnews/base/search/public/nsMsgResultElement.h40
-rw-r--r--mailnews/base/search/public/nsMsgSearchAdapter.h218
-rw-r--r--mailnews/base/search/public/nsMsgSearchBoolExpression.h107
-rw-r--r--mailnews/base/search/public/nsMsgSearchCore.idl222
-rw-r--r--mailnews/base/search/public/nsMsgSearchScopeTerm.h45
-rw-r--r--mailnews/base/search/public/nsMsgSearchTerm.h85
-rw-r--r--mailnews/base/search/src/Bogofilter.sfd14
-rw-r--r--mailnews/base/search/src/DSPAM.sfd14
-rw-r--r--mailnews/base/search/src/Habeas.sfd8
-rw-r--r--mailnews/base/search/src/POPFile.sfd14
-rw-r--r--mailnews/base/search/src/SpamAssassin.sfd14
-rw-r--r--mailnews/base/search/src/SpamCatcher.sfd14
-rw-r--r--mailnews/base/search/src/SpamPal.sfd14
-rw-r--r--mailnews/base/search/src/moz.build33
-rw-r--r--mailnews/base/search/src/nsMsgBodyHandler.cpp487
-rw-r--r--mailnews/base/search/src/nsMsgFilter.cpp1057
-rw-r--r--mailnews/base/search/src/nsMsgFilter.h103
-rw-r--r--mailnews/base/search/src/nsMsgFilterList.cpp1198
-rw-r--r--mailnews/base/search/src/nsMsgFilterList.h75
-rw-r--r--mailnews/base/search/src/nsMsgFilterService.cpp1216
-rw-r--r--mailnews/base/search/src/nsMsgFilterService.h46
-rw-r--r--mailnews/base/search/src/nsMsgImapSearch.cpp1004
-rw-r--r--mailnews/base/search/src/nsMsgLocalSearch.cpp1022
-rw-r--r--mailnews/base/search/src/nsMsgLocalSearch.h104
-rw-r--r--mailnews/base/search/src/nsMsgSearchAdapter.cpp1332
-rw-r--r--mailnews/base/search/src/nsMsgSearchImap.h37
-rw-r--r--mailnews/base/search/src/nsMsgSearchNews.cpp511
-rw-r--r--mailnews/base/search/src/nsMsgSearchNews.h49
-rw-r--r--mailnews/base/search/src/nsMsgSearchSession.cpp675
-rw-r--r--mailnews/base/search/src/nsMsgSearchSession.h98
-rw-r--r--mailnews/base/search/src/nsMsgSearchTerm.cpp2088
-rw-r--r--mailnews/base/search/src/nsMsgSearchValue.cpp117
-rw-r--r--mailnews/base/search/src/nsMsgSearchValue.h26
-rw-r--r--mailnews/base/search/src/nsMsgTraitService.js239
-rw-r--r--mailnews/base/search/src/nsMsgTraitService.manifest2
-rw-r--r--mailnews/base/src/MailNewsDLF.cpp101
-rw-r--r--mailnews/base/src/MailNewsDLF.h38
-rw-r--r--mailnews/base/src/MailnewsLoadContextInfo.cpp55
-rw-r--r--mailnews/base/src/MailnewsLoadContextInfo.h32
-rw-r--r--mailnews/base/src/folderLookupService.js99
-rw-r--r--mailnews/base/src/moz.build85
-rw-r--r--mailnews/base/src/msgAsyncPrompter.js126
-rw-r--r--mailnews/base/src/msgBase.manifest14
-rw-r--r--mailnews/base/src/msgOAuth2Module.js150
-rw-r--r--mailnews/base/src/newMailNotificationService.js377
-rw-r--r--mailnews/base/src/nsCidProtocolHandler.cpp73
-rw-r--r--mailnews/base/src/nsCidProtocolHandler.h24
-rw-r--r--mailnews/base/src/nsCopyMessageStreamListener.cpp145
-rw-r--r--mailnews/base/src/nsCopyMessageStreamListener.h37
-rw-r--r--mailnews/base/src/nsMailDirProvider.cpp206
-rw-r--r--mailnews/base/src/nsMailDirProvider.h43
-rw-r--r--mailnews/base/src/nsMailDirServiceDefs.h31
-rw-r--r--mailnews/base/src/nsMailNewsCommandLineHandler.js170
-rw-r--r--mailnews/base/src/nsMessenger.cpp3072
-rw-r--r--mailnews/base/src/nsMessenger.h102
-rw-r--r--mailnews/base/src/nsMessengerBootstrap.cpp83
-rw-r--r--mailnews/base/src/nsMessengerBootstrap.h31
-rw-r--r--mailnews/base/src/nsMessengerContentHandler.cpp81
-rw-r--r--mailnews/base/src/nsMessengerContentHandler.h20
-rw-r--r--mailnews/base/src/nsMessengerOSXIntegration.h63
-rw-r--r--mailnews/base/src/nsMessengerOSXIntegration.mm708
-rw-r--r--mailnews/base/src/nsMessengerUnixIntegration.cpp766
-rw-r--r--mailnews/base/src/nsMessengerUnixIntegration.h63
-rw-r--r--mailnews/base/src/nsMessengerWinIntegration.cpp1184
-rw-r--r--mailnews/base/src/nsMessengerWinIntegration.h104
-rw-r--r--mailnews/base/src/nsMsgAccount.cpp435
-rw-r--r--mailnews/base/src/nsMsgAccount.h38
-rw-r--r--mailnews/base/src/nsMsgAccountManager.cpp3707
-rw-r--r--mailnews/base/src/nsMsgAccountManager.h226
-rw-r--r--mailnews/base/src/nsMsgAccountManagerDS.cpp1183
-rw-r--r--mailnews/base/src/nsMsgAccountManagerDS.h142
-rw-r--r--mailnews/base/src/nsMsgBiffManager.cpp372
-rw-r--r--mailnews/base/src/nsMsgBiffManager.h55
-rw-r--r--mailnews/base/src/nsMsgContentPolicy.cpp1076
-rw-r--r--mailnews/base/src/nsMsgContentPolicy.h93
-rw-r--r--mailnews/base/src/nsMsgCopyService.cpp708
-rw-r--r--mailnews/base/src/nsMsgCopyService.h94
-rw-r--r--mailnews/base/src/nsMsgDBView.cpp8095
-rw-r--r--mailnews/base/src/nsMsgDBView.h513
-rw-r--r--mailnews/base/src/nsMsgFolderCache.cpp376
-rw-r--r--mailnews/base/src/nsMsgFolderCache.h50
-rw-r--r--mailnews/base/src/nsMsgFolderCacheElement.cpp163
-rw-r--r--mailnews/base/src/nsMsgFolderCacheElement.h35
-rw-r--r--mailnews/base/src/nsMsgFolderCompactor.cpp1352
-rw-r--r--mailnews/base/src/nsMsgFolderCompactor.h115
-rw-r--r--mailnews/base/src/nsMsgFolderDataSource.cpp2475
-rw-r--r--mailnews/base/src/nsMsgFolderDataSource.h356
-rw-r--r--mailnews/base/src/nsMsgFolderNotificationService.cpp172
-rw-r--r--mailnews/base/src/nsMsgFolderNotificationService.h46
-rw-r--r--mailnews/base/src/nsMsgGroupThread.cpp856
-rw-r--r--mailnews/base/src/nsMsgGroupThread.h87
-rw-r--r--mailnews/base/src/nsMsgGroupView.cpp1024
-rw-r--r--mailnews/base/src/nsMsgGroupView.h74
-rw-r--r--mailnews/base/src/nsMsgMailSession.cpp761
-rw-r--r--mailnews/base/src/nsMsgMailSession.h106
-rw-r--r--mailnews/base/src/nsMsgOfflineManager.cpp399
-rw-r--r--mailnews/base/src/nsMsgOfflineManager.h85
-rw-r--r--mailnews/base/src/nsMsgPrintEngine.cpp741
-rw-r--r--mailnews/base/src/nsMsgPrintEngine.h91
-rw-r--r--mailnews/base/src/nsMsgProgress.cpp262
-rw-r--r--mailnews/base/src/nsMsgProgress.h47
-rw-r--r--mailnews/base/src/nsMsgPurgeService.cpp486
-rw-r--r--mailnews/base/src/nsMsgPurgeService.h55
-rw-r--r--mailnews/base/src/nsMsgQuickSearchDBView.cpp882
-rw-r--r--mailnews/base/src/nsMsgQuickSearchDBView.h87
-rw-r--r--mailnews/base/src/nsMsgRDFDataSource.cpp371
-rw-r--r--mailnews/base/src/nsMsgRDFDataSource.h66
-rw-r--r--mailnews/base/src/nsMsgRDFUtils.cpp82
-rw-r--r--mailnews/base/src/nsMsgRDFUtils.h104
-rw-r--r--mailnews/base/src/nsMsgSearchDBView.cpp1433
-rw-r--r--mailnews/base/src/nsMsgSearchDBView.h146
-rw-r--r--mailnews/base/src/nsMsgServiceProvider.cpp139
-rw-r--r--mailnews/base/src/nsMsgServiceProvider.h34
-rw-r--r--mailnews/base/src/nsMsgSpecialViews.cpp179
-rw-r--r--mailnews/base/src/nsMsgSpecialViews.h71
-rw-r--r--mailnews/base/src/nsMsgStatusFeedback.cpp303
-rw-r--r--mailnews/base/src/nsMsgStatusFeedback.h50
-rw-r--r--mailnews/base/src/nsMsgTagService.cpp560
-rw-r--r--mailnews/base/src/nsMsgTagService.h57
-rw-r--r--mailnews/base/src/nsMsgThreadedDBView.cpp983
-rw-r--r--mailnews/base/src/nsMsgThreadedDBView.h51
-rw-r--r--mailnews/base/src/nsMsgWindow.cpp534
-rw-r--r--mailnews/base/src/nsMsgWindow.h61
-rw-r--r--mailnews/base/src/nsMsgXFViewThread.cpp481
-rw-r--r--mailnews/base/src/nsMsgXFViewThread.h54
-rw-r--r--mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp515
-rw-r--r--mailnews/base/src/nsMsgXFVirtualFolderDBView.h61
-rw-r--r--mailnews/base/src/nsSpamSettings.cpp892
-rw-r--r--mailnews/base/src/nsSpamSettings.h71
-rw-r--r--mailnews/base/src/nsStatusBarBiffManager.cpp238
-rw-r--r--mailnews/base/src/nsStatusBarBiffManager.h44
-rw-r--r--mailnews/base/src/nsSubscribableServer.cpp805
-rw-r--r--mailnews/base/src/nsSubscribableServer.h80
-rw-r--r--mailnews/base/src/nsSubscribeDataSource.cpp673
-rw-r--r--mailnews/base/src/nsSubscribeDataSource.h50
-rw-r--r--mailnews/base/src/virtualFolderWrapper.js255
-rw-r--r--mailnews/base/util/ABQueryUtils.jsm130
-rw-r--r--mailnews/base/util/IOUtils.js136
-rw-r--r--mailnews/base/util/JXON.js180
-rw-r--r--mailnews/base/util/OAuth2.jsm234
-rw-r--r--mailnews/base/util/OAuth2Providers.jsm77
-rw-r--r--mailnews/base/util/ServiceList.h40
-rw-r--r--mailnews/base/util/Services.cpp106
-rw-r--r--mailnews/base/util/Services.h26
-rw-r--r--mailnews/base/util/StringBundle.js189
-rw-r--r--mailnews/base/util/errUtils.js309
-rw-r--r--mailnews/base/util/folderUtils.jsm234
-rw-r--r--mailnews/base/util/hostnameUtils.jsm341
-rw-r--r--mailnews/base/util/iteratorUtils.jsm166
-rw-r--r--mailnews/base/util/jsTreeSelection.js654
-rw-r--r--mailnews/base/util/mailServices.js73
-rw-r--r--mailnews/base/util/mailnewsMigrator.js203
-rw-r--r--mailnews/base/util/moz.build82
-rw-r--r--mailnews/base/util/msgDBCacheManager.js175
-rw-r--r--mailnews/base/util/nsImapMoveCoalescer.cpp233
-rw-r--r--mailnews/base/util/nsImapMoveCoalescer.h73
-rw-r--r--mailnews/base/util/nsMsgCompressIStream.cpp227
-rw-r--r--mailnews/base/util/nsMsgCompressIStream.h35
-rw-r--r--mailnews/base/util/nsMsgCompressOStream.cpp145
-rw-r--r--mailnews/base/util/nsMsgCompressOStream.h28
-rw-r--r--mailnews/base/util/nsMsgDBFolder.cpp6063
-rw-r--r--mailnews/base/util/nsMsgDBFolder.h291
-rw-r--r--mailnews/base/util/nsMsgDBFolderAtomList.h26
-rw-r--r--mailnews/base/util/nsMsgFileStream.cpp196
-rw-r--r--mailnews/base/util/nsMsgFileStream.h33
-rw-r--r--mailnews/base/util/nsMsgI18N.cpp479
-rw-r--r--mailnews/base/util/nsMsgI18N.h198
-rw-r--r--mailnews/base/util/nsMsgIdentity.cpp669
-rw-r--r--mailnews/base/util/nsMsgIdentity.h97
-rw-r--r--mailnews/base/util/nsMsgIncomingServer.cpp2332
-rw-r--r--mailnews/base/util/nsMsgIncomingServer.h101
-rw-r--r--mailnews/base/util/nsMsgKeyArray.cpp77
-rw-r--r--mailnews/base/util/nsMsgKeyArray.h33
-rw-r--r--mailnews/base/util/nsMsgKeySet.cpp1520
-rw-r--r--mailnews/base/util/nsMsgKeySet.h108
-rw-r--r--mailnews/base/util/nsMsgLineBuffer.cpp439
-rw-r--r--mailnews/base/util/nsMsgLineBuffer.h110
-rw-r--r--mailnews/base/util/nsMsgMailNewsUrl.cpp1060
-rw-r--r--mailnews/base/util/nsMsgMailNewsUrl.h80
-rw-r--r--mailnews/base/util/nsMsgProtocol.cpp1550
-rw-r--r--mailnews/base/util/nsMsgProtocol.h233
-rw-r--r--mailnews/base/util/nsMsgReadStateTxn.cpp66
-rw-r--r--mailnews/base/util/nsMsgReadStateTxn.h48
-rw-r--r--mailnews/base/util/nsMsgTxn.cpp294
-rw-r--r--mailnews/base/util/nsMsgTxn.h67
-rw-r--r--mailnews/base/util/nsMsgUtils.cpp2520
-rw-r--r--mailnews/base/util/nsMsgUtils.h589
-rw-r--r--mailnews/base/util/nsStopwatch.cpp183
-rw-r--r--mailnews/base/util/nsStopwatch.h47
-rw-r--r--mailnews/base/util/templateUtils.js90
-rw-r--r--mailnews/base/util/traceHelper.js113
-rw-r--r--mailnews/build/moz.build63
-rw-r--r--mailnews/build/newmail.icobin0 -> 318 bytes
-rw-r--r--mailnews/build/nsMailModule.cpp1392
-rw-r--r--mailnews/compose/content/askSendFormat.js58
-rw-r--r--mailnews/compose/content/askSendFormat.xul46
-rw-r--r--mailnews/compose/content/mailComposeEditorOverlay.xul157
-rw-r--r--mailnews/compose/content/menulistCompactBindings.xml22
-rw-r--r--mailnews/compose/content/sendProgress.js171
-rw-r--r--mailnews/compose/content/sendProgress.xul50
-rw-r--r--mailnews/compose/moz.build10
-rw-r--r--mailnews/compose/public/moz.build35
-rw-r--r--mailnews/compose/public/nsIMsgAttachment.idl124
-rw-r--r--mailnews/compose/public/nsIMsgAttachmentHandler.idl45
-rw-r--r--mailnews/compose/public/nsIMsgCompFields.idl97
-rw-r--r--mailnews/compose/public/nsIMsgCompUtils.idl14
-rw-r--r--mailnews/compose/public/nsIMsgCompose.idl307
-rw-r--r--mailnews/compose/public/nsIMsgComposeParams.idl81
-rw-r--r--mailnews/compose/public/nsIMsgComposeProgressParams.idl16
-rw-r--r--mailnews/compose/public/nsIMsgComposeSecure.idl25
-rw-r--r--mailnews/compose/public/nsIMsgComposeService.idl147
-rw-r--r--mailnews/compose/public/nsIMsgQuote.idl35
-rw-r--r--mailnews/compose/public/nsIMsgQuotingOutputStreamListener.idl16
-rw-r--r--mailnews/compose/public/nsIMsgSend.idl405
-rw-r--r--mailnews/compose/public/nsIMsgSendLater.idl66
-rw-r--r--mailnews/compose/public/nsIMsgSendLaterListener.idl86
-rw-r--r--mailnews/compose/public/nsIMsgSendListener.idl57
-rw-r--r--mailnews/compose/public/nsIMsgSendReport.idl47
-rw-r--r--mailnews/compose/public/nsISmtpServer.idl131
-rw-r--r--mailnews/compose/public/nsISmtpService.idl134
-rw-r--r--mailnews/compose/public/nsISmtpUrl.idl110
-rw-r--r--mailnews/compose/public/nsIURLFetcher.idl38
-rw-r--r--mailnews/compose/public/nsMsgAttachmentData.h115
-rw-r--r--mailnews/compose/public/nsMsgCompCID.h247
-rw-r--r--mailnews/compose/src/moz.build55
-rw-r--r--mailnews/compose/src/nsComposeStrings.cpp116
-rw-r--r--mailnews/compose/src/nsComposeStrings.h77
-rw-r--r--mailnews/compose/src/nsMsgAppleCodes.h106
-rw-r--r--mailnews/compose/src/nsMsgAppleDouble.h207
-rw-r--r--mailnews/compose/src/nsMsgAppleDoubleEncode.cpp266
-rw-r--r--mailnews/compose/src/nsMsgAppleEncode.cpp703
-rw-r--r--mailnews/compose/src/nsMsgAttachment.cpp262
-rw-r--r--mailnews/compose/src/nsMsgAttachment.h41
-rw-r--r--mailnews/compose/src/nsMsgAttachmentHandler.cpp1384
-rw-r--r--mailnews/compose/src/nsMsgAttachmentHandler.h184
-rw-r--r--mailnews/compose/src/nsMsgCompFields.cpp692
-rw-r--r--mailnews/compose/src/nsMsgCompFields.h172
-rw-r--r--mailnews/compose/src/nsMsgCompUtils.cpp1803
-rw-r--r--mailnews/compose/src/nsMsgCompUtils.h143
-rw-r--r--mailnews/compose/src/nsMsgCompose.cpp6057
-rw-r--r--mailnews/compose/src/nsMsgCompose.h246
-rw-r--r--mailnews/compose/src/nsMsgComposeContentHandler.cpp125
-rw-r--r--mailnews/compose/src/nsMsgComposeContentHandler.h20
-rw-r--r--mailnews/compose/src/nsMsgComposeParams.cpp170
-rw-r--r--mailnews/compose/src/nsMsgComposeParams.h30
-rw-r--r--mailnews/compose/src/nsMsgComposeProgressParams.cpp46
-rw-r--r--mailnews/compose/src/nsMsgComposeProgressParams.h20
-rw-r--r--mailnews/compose/src/nsMsgComposeService.cpp1479
-rw-r--r--mailnews/compose/src/nsMsgComposeService.h68
-rw-r--r--mailnews/compose/src/nsMsgCopy.cpp553
-rw-r--r--mailnews/compose/src/nsMsgCopy.h120
-rw-r--r--mailnews/compose/src/nsMsgPrompts.cpp115
-rw-r--r--mailnews/compose/src/nsMsgPrompts.h22
-rw-r--r--mailnews/compose/src/nsMsgQuote.cpp233
-rw-r--r--mailnews/compose/src/nsMsgQuote.h52
-rw-r--r--mailnews/compose/src/nsMsgSend.cpp5219
-rw-r--r--mailnews/compose/src/nsMsgSend.h405
-rw-r--r--mailnews/compose/src/nsMsgSendLater.cpp1559
-rw-r--r--mailnews/compose/src/nsMsgSendLater.h144
-rw-r--r--mailnews/compose/src/nsMsgSendPart.cpp779
-rw-r--r--mailnews/compose/src/nsMsgSendPart.h101
-rw-r--r--mailnews/compose/src/nsMsgSendReport.cpp437
-rw-r--r--mailnews/compose/src/nsMsgSendReport.h46
-rw-r--r--mailnews/compose/src/nsSMTPProtocolHandler.js62
-rw-r--r--mailnews/compose/src/nsSMTPProtocolHandler.manifest4
-rw-r--r--mailnews/compose/src/nsSmtpProtocol.cpp2277
-rw-r--r--mailnews/compose/src/nsSmtpProtocol.h247
-rw-r--r--mailnews/compose/src/nsSmtpServer.cpp689
-rw-r--r--mailnews/compose/src/nsSmtpServer.h48
-rw-r--r--mailnews/compose/src/nsSmtpService.cpp775
-rw-r--r--mailnews/compose/src/nsSmtpService.h63
-rw-r--r--mailnews/compose/src/nsSmtpUrl.cpp802
-rw-r--r--mailnews/compose/src/nsSmtpUrl.h100
-rw-r--r--mailnews/compose/src/nsURLFetcher.cpp530
-rw-r--r--mailnews/compose/src/nsURLFetcher.h101
-rw-r--r--mailnews/db/gloda/components/glautocomp.js544
-rw-r--r--mailnews/db/gloda/components/gloda.manifest5
-rw-r--r--mailnews/db/gloda/components/jsmimeemitter.js493
-rw-r--r--mailnews/db/gloda/components/moz.build11
-rw-r--r--mailnews/db/gloda/content/glodacomplete.css94
-rw-r--r--mailnews/db/gloda/content/glodacomplete.xml644
-rw-r--r--mailnews/db/gloda/content/overlay.js6
-rw-r--r--mailnews/db/gloda/content/thunderbirdOverlay.xul9
-rw-r--r--mailnews/db/gloda/jar.mn11
-rw-r--r--mailnews/db/gloda/modules/collection.js772
-rw-r--r--mailnews/db/gloda/modules/connotent.js273
-rw-r--r--mailnews/db/gloda/modules/databind.js194
-rw-r--r--mailnews/db/gloda/modules/datamodel.js907
-rw-r--r--mailnews/db/gloda/modules/datastore.js3989
-rw-r--r--mailnews/db/gloda/modules/dbview.js178
-rw-r--r--mailnews/db/gloda/modules/everybody.js50
-rw-r--r--mailnews/db/gloda/modules/explattr.js193
-rw-r--r--mailnews/db/gloda/modules/facet.js582
-rw-r--r--mailnews/db/gloda/modules/fundattr.js907
-rw-r--r--mailnews/db/gloda/modules/gloda.js2283
-rw-r--r--mailnews/db/gloda/modules/index_ab.js287
-rw-r--r--mailnews/db/gloda/modules/index_msg.js3334
-rw-r--r--mailnews/db/gloda/modules/indexer.js1409
-rw-r--r--mailnews/db/gloda/modules/log4moz.js932
-rw-r--r--mailnews/db/gloda/modules/mimeTypeCategories.js204
-rw-r--r--mailnews/db/gloda/modules/mimemsg.js719
-rw-r--r--mailnews/db/gloda/modules/moz.build32
-rw-r--r--mailnews/db/gloda/modules/msg_search.js346
-rw-r--r--mailnews/db/gloda/modules/noun_freetag.js93
-rw-r--r--mailnews/db/gloda/modules/noun_mimetype.js365
-rw-r--r--mailnews/db/gloda/modules/noun_tag.js95
-rw-r--r--mailnews/db/gloda/modules/public.js36
-rw-r--r--mailnews/db/gloda/modules/query.js618
-rw-r--r--mailnews/db/gloda/modules/suffixtree.js340
-rw-r--r--mailnews/db/gloda/modules/utils.js155
-rw-r--r--mailnews/db/gloda/moz.build11
-rw-r--r--mailnews/db/moz.build10
-rw-r--r--mailnews/db/msgdb/moz.build9
-rw-r--r--mailnews/db/msgdb/public/moz.build27
-rw-r--r--mailnews/db/msgdb/public/nsDBFolderInfo.h135
-rw-r--r--mailnews/db/msgdb/public/nsIDBChangeAnnouncer.idl33
-rw-r--r--mailnews/db/msgdb/public/nsIDBChangeListener.idl115
-rw-r--r--mailnews/db/msgdb/public/nsIDBFolderInfo.idl108
-rw-r--r--mailnews/db/msgdb/public/nsIMsgDatabase.idl570
-rw-r--r--mailnews/db/msgdb/public/nsIMsgOfflineImapOperation.idl53
-rw-r--r--mailnews/db/msgdb/public/nsINewsDatabase.idl18
-rw-r--r--mailnews/db/msgdb/public/nsImapMailDatabase.h52
-rw-r--r--mailnews/db/msgdb/public/nsMailDatabase.h67
-rw-r--r--mailnews/db/msgdb/public/nsMsgDBCID.h63
-rw-r--r--mailnews/db/msgdb/public/nsMsgDatabase.h462
-rw-r--r--mailnews/db/msgdb/public/nsMsgHdr.h96
-rw-r--r--mailnews/db/msgdb/public/nsMsgThread.h63
-rw-r--r--mailnews/db/msgdb/public/nsNewsDatabase.h57
-rw-r--r--mailnews/db/msgdb/src/moz.build18
-rw-r--r--mailnews/db/msgdb/src/nsDBFolderInfo.cpp977
-rw-r--r--mailnews/db/msgdb/src/nsImapMailDatabase.cpp249
-rw-r--r--mailnews/db/msgdb/src/nsMailDatabase.cpp443
-rw-r--r--mailnews/db/msgdb/src/nsMsgDatabase.cpp5914
-rw-r--r--mailnews/db/msgdb/src/nsMsgHdr.cpp1097
-rw-r--r--mailnews/db/msgdb/src/nsMsgOfflineImapOperation.cpp378
-rw-r--r--mailnews/db/msgdb/src/nsMsgOfflineImapOperation.h55
-rw-r--r--mailnews/db/msgdb/src/nsMsgThread.cpp1180
-rw-r--r--mailnews/db/msgdb/src/nsNewsDatabase.cpp360
-rw-r--r--mailnews/extensions/bayesian-spam-filter/moz.build6
-rw-r--r--mailnews/extensions/bayesian-spam-filter/src/moz.build11
-rw-r--r--mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.cpp2758
-rw-r--r--mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.h404
-rw-r--r--mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilterCID.h22
-rw-r--r--mailnews/extensions/bayesian-spam-filter/src/nsIncompleteGamma.h259
-rw-r--r--mailnews/extensions/dsn/content/am-dsn.js36
-rw-r--r--mailnews/extensions/dsn/content/am-dsn.xul57
-rw-r--r--mailnews/extensions/dsn/content/dsn.js9
-rw-r--r--mailnews/extensions/dsn/jar.mn9
-rw-r--r--mailnews/extensions/dsn/moz.build15
-rw-r--r--mailnews/extensions/dsn/src/dsn-service.js24
-rw-r--r--mailnews/extensions/dsn/src/dsn-service.manifest3
-rw-r--r--mailnews/extensions/fts3/data/README5
-rw-r--r--mailnews/extensions/fts3/data/generate_table.py264
-rw-r--r--mailnews/extensions/fts3/data/nfkc.txt5786
-rw-r--r--mailnews/extensions/fts3/data/nfkc_cf.txt5376
-rw-r--r--mailnews/extensions/fts3/public/moz.build11
-rw-r--r--mailnews/extensions/fts3/public/nsIFts3Tokenizer.idl15
-rw-r--r--mailnews/extensions/fts3/src/Normalize.c1929
-rw-r--r--mailnews/extensions/fts3/src/README.mozilla3
-rw-r--r--mailnews/extensions/fts3/src/fts3_porter.c1150
-rw-r--r--mailnews/extensions/fts3/src/fts3_tokenizer.h148
-rw-r--r--mailnews/extensions/fts3/src/moz.build18
-rw-r--r--mailnews/extensions/fts3/src/nsFts3Tokenizer.cpp72
-rw-r--r--mailnews/extensions/fts3/src/nsFts3Tokenizer.h26
-rw-r--r--mailnews/extensions/fts3/src/nsFts3TokenizerCID.h16
-rw-r--r--mailnews/extensions/fts3/src/nsGlodaRankerFunction.cpp145
-rw-r--r--mailnews/extensions/fts3/src/nsGlodaRankerFunction.h25
-rw-r--r--mailnews/extensions/mailviews/content/mailViews.dat22
-rw-r--r--mailnews/extensions/mailviews/content/moz.build8
-rw-r--r--mailnews/extensions/mailviews/public/moz.build12
-rw-r--r--mailnews/extensions/mailviews/public/nsIMsgMailView.idl35
-rw-r--r--mailnews/extensions/mailviews/public/nsIMsgMailViewList.idl28
-rw-r--r--mailnews/extensions/mailviews/src/moz.build11
-rw-r--r--mailnews/extensions/mailviews/src/nsMsgMailViewList.cpp312
-rw-r--r--mailnews/extensions/mailviews/src/nsMsgMailViewList.h61
-rw-r--r--mailnews/extensions/mailviews/src/nsMsgMailViewsCID.h17
-rw-r--r--mailnews/extensions/mdn/content/am-mdn.js155
-rw-r--r--mailnews/extensions/mdn/content/am-mdn.xul136
-rw-r--r--mailnews/extensions/mdn/content/mdn.js23
-rw-r--r--mailnews/extensions/mdn/jar.mn7
-rw-r--r--mailnews/extensions/mdn/moz.build12
-rw-r--r--mailnews/extensions/mdn/src/mdn-service.js24
-rw-r--r--mailnews/extensions/mdn/src/mdn-service.manifest3
-rw-r--r--mailnews/extensions/mdn/src/moz.build16
-rw-r--r--mailnews/extensions/mdn/src/nsMsgMdnCID.h22
-rw-r--r--mailnews/extensions/mdn/src/nsMsgMdnGenerator.cpp1139
-rw-r--r--mailnews/extensions/mdn/src/nsMsgMdnGenerator.h90
-rw-r--r--mailnews/extensions/moz.build19
-rw-r--r--mailnews/extensions/newsblog/content/Feed.js620
-rw-r--r--mailnews/extensions/newsblog/content/FeedItem.js494
-rw-r--r--mailnews/extensions/newsblog/content/FeedUtils.jsm1609
-rw-r--r--mailnews/extensions/newsblog/content/am-newsblog.js63
-rw-r--r--mailnews/extensions/newsblog/content/am-newsblog.xul155
-rw-r--r--mailnews/extensions/newsblog/content/feed-parser.js1080
-rw-r--r--mailnews/extensions/newsblog/content/feed-subscriptions.js2703
-rw-r--r--mailnews/extensions/newsblog/content/feed-subscriptions.xul234
-rw-r--r--mailnews/extensions/newsblog/content/feedAccountWizard.js45
-rw-r--r--mailnews/extensions/newsblog/content/feedAccountWizard.xul79
-rw-r--r--mailnews/extensions/newsblog/content/newsblogOverlay.js363
-rw-r--r--mailnews/extensions/newsblog/jar.mn16
-rw-r--r--mailnews/extensions/newsblog/js/newsblog.js99
-rw-r--r--mailnews/extensions/newsblog/js/newsblog.manifest5
-rw-r--r--mailnews/extensions/newsblog/moz.build18
-rw-r--r--mailnews/extensions/newsblog/rss.rdf43
-rw-r--r--mailnews/extensions/offline-startup/js/offlineStartup.js170
-rw-r--r--mailnews/extensions/offline-startup/js/offlineStartup.manifest3
-rw-r--r--mailnews/extensions/offline-startup/moz.build10
-rw-r--r--mailnews/extensions/smime/content/am-smime.js478
-rw-r--r--mailnews/extensions/smime/content/am-smime.xul26
-rw-r--r--mailnews/extensions/smime/content/am-smimeIdentityEditOverlay.xul39
-rw-r--r--mailnews/extensions/smime/content/am-smimeOverlay.xul102
-rw-r--r--mailnews/extensions/smime/content/certFetchingStatus.js265
-rw-r--r--mailnews/extensions/smime/content/certFetchingStatus.xul24
-rw-r--r--mailnews/extensions/smime/content/certpicker.js73
-rw-r--r--mailnews/extensions/smime/content/certpicker.xul38
-rw-r--r--mailnews/extensions/smime/content/msgCompSMIMEOverlay.js357
-rw-r--r--mailnews/extensions/smime/content/msgCompSMIMEOverlay.xul85
-rw-r--r--mailnews/extensions/smime/content/msgCompSecurityInfo.js244
-rw-r--r--mailnews/extensions/smime/content/msgCompSecurityInfo.xul68
-rw-r--r--mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.js264
-rw-r--r--mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.xul29
-rw-r--r--mailnews/extensions/smime/content/msgReadSMIMEOverlay.js102
-rw-r--r--mailnews/extensions/smime/content/msgReadSMIMEOverlay.xul34
-rw-r--r--mailnews/extensions/smime/content/msgReadSecurityInfo.js232
-rw-r--r--mailnews/extensions/smime/content/msgReadSecurityInfo.xul68
-rw-r--r--mailnews/extensions/smime/content/smime.js14
-rw-r--r--mailnews/extensions/smime/jar.mn30
-rw-r--r--mailnews/extensions/smime/moz.build15
-rw-r--r--mailnews/extensions/smime/public/moz.build15
-rw-r--r--mailnews/extensions/smime/public/nsICertPickDialogs.idl30
-rw-r--r--mailnews/extensions/smime/public/nsIEncryptedSMIMEURIsSrvc.idl24
-rw-r--r--mailnews/extensions/smime/public/nsIMsgSMIMECompFields.idl18
-rw-r--r--mailnews/extensions/smime/public/nsIMsgSMIMEHeaderSink.idl29
-rw-r--r--mailnews/extensions/smime/public/nsISMimeJSHelper.idl73
-rw-r--r--mailnews/extensions/smime/public/nsIUserCertPicker.idl28
-rw-r--r--mailnews/extensions/smime/src/moz.build23
-rw-r--r--mailnews/extensions/smime/src/nsCertPicker.cpp471
-rw-r--r--mailnews/extensions/smime/src/nsCertPicker.h36
-rw-r--r--mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp36
-rw-r--r--mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h25
-rw-r--r--mailnews/extensions/smime/src/nsMsgComposeSecure.cpp1200
-rw-r--r--mailnews/extensions/smime/src/nsMsgComposeSecure.h106
-rw-r--r--mailnews/extensions/smime/src/nsMsgSMIMECID.h42
-rw-r--r--mailnews/extensions/smime/src/nsSMimeJSHelper.cpp335
-rw-r--r--mailnews/extensions/smime/src/nsSMimeJSHelper.h26
-rw-r--r--mailnews/extensions/smime/src/smime-service.js24
-rw-r--r--mailnews/extensions/smime/src/smime-service.manifest3
-rw-r--r--mailnews/imap/public/moz.build32
-rw-r--r--mailnews/imap/public/nsIAutoSyncFolderStrategy.idl23
-rw-r--r--mailnews/imap/public/nsIAutoSyncManager.idl194
-rw-r--r--mailnews/imap/public/nsIAutoSyncMsgStrategy.idl35
-rw-r--r--mailnews/imap/public/nsIAutoSyncState.idl130
-rw-r--r--mailnews/imap/public/nsIIMAPHostSessionList.h100
-rw-r--r--mailnews/imap/public/nsIImapFlagAndUidState.idl74
-rw-r--r--mailnews/imap/public/nsIImapHeaderXferInfo.idl23
-rw-r--r--mailnews/imap/public/nsIImapIncomingServer.idl105
-rw-r--r--mailnews/imap/public/nsIImapMailFolderSink.idl103
-rw-r--r--mailnews/imap/public/nsIImapMessageSink.idl80
-rw-r--r--mailnews/imap/public/nsIImapMockChannel.idl49
-rw-r--r--mailnews/imap/public/nsIImapProtocol.idl71
-rw-r--r--mailnews/imap/public/nsIImapProtocolSink.idl32
-rw-r--r--mailnews/imap/public/nsIImapServerSink.idl169
-rw-r--r--mailnews/imap/public/nsIImapService.idl258
-rw-r--r--mailnews/imap/public/nsIImapUrl.idl207
-rw-r--r--mailnews/imap/public/nsIMailboxSpec.idl43
-rw-r--r--mailnews/imap/public/nsIMsgImapMailFolder.idl215
-rw-r--r--mailnews/imap/public/nsMsgImapCID.h59
-rw-r--r--mailnews/imap/src/moz.build33
-rw-r--r--mailnews/imap/src/nsAutoSyncManager.cpp1412
-rw-r--r--mailnews/imap/src/nsAutoSyncManager.h265
-rw-r--r--mailnews/imap/src/nsAutoSyncState.cpp765
-rw-r--r--mailnews/imap/src/nsAutoSyncState.h107
-rw-r--r--mailnews/imap/src/nsIMAPBodyShell.cpp1333
-rw-r--r--mailnews/imap/src/nsIMAPBodyShell.h361
-rw-r--r--mailnews/imap/src/nsIMAPGenericParser.cpp484
-rw-r--r--mailnews/imap/src/nsIMAPGenericParser.h76
-rw-r--r--mailnews/imap/src/nsIMAPHostSessionList.cpp701
-rw-r--r--mailnews/imap/src/nsIMAPHostSessionList.h135
-rw-r--r--mailnews/imap/src/nsIMAPNamespace.cpp651
-rw-r--r--mailnews/imap/src/nsIMAPNamespace.h87
-rw-r--r--mailnews/imap/src/nsImapCore.h190
-rw-r--r--mailnews/imap/src/nsImapFlagAndUidState.cpp321
-rw-r--r--mailnews/imap/src/nsImapFlagAndUidState.h55
-rw-r--r--mailnews/imap/src/nsImapIncomingServer.cpp3382
-rw-r--r--mailnews/imap/src/nsImapIncomingServer.h137
-rw-r--r--mailnews/imap/src/nsImapMailFolder.cpp9837
-rw-r--r--mailnews/imap/src/nsImapMailFolder.h550
-rw-r--r--mailnews/imap/src/nsImapOfflineSync.cpp1290
-rw-r--r--mailnews/imap/src/nsImapOfflineSync.h92
-rw-r--r--mailnews/imap/src/nsImapProtocol.cpp10093
-rw-r--r--mailnews/imap/src/nsImapProtocol.h772
-rw-r--r--mailnews/imap/src/nsImapSearchResults.cpp92
-rw-r--r--mailnews/imap/src/nsImapSearchResults.h42
-rw-r--r--mailnews/imap/src/nsImapServerResponseParser.cpp3483
-rw-r--r--mailnews/imap/src/nsImapServerResponseParser.h272
-rw-r--r--mailnews/imap/src/nsImapService.cpp3400
-rw-r--r--mailnews/imap/src/nsImapService.h123
-rw-r--r--mailnews/imap/src/nsImapStringBundle.cpp42
-rw-r--r--mailnews/imap/src/nsImapStringBundle.h17
-rw-r--r--mailnews/imap/src/nsImapUndoTxn.cpp751
-rw-r--r--mailnews/imap/src/nsImapUndoTxn.h92
-rw-r--r--mailnews/imap/src/nsImapUrl.cpp1561
-rw-r--r--mailnews/imap/src/nsImapUrl.h133
-rw-r--r--mailnews/imap/src/nsImapUtils.cpp373
-rw-r--r--mailnews/imap/src/nsImapUtils.h77
-rw-r--r--mailnews/imap/src/nsSyncRunnableHelpers.cpp601
-rw-r--r--mailnews/imap/src/nsSyncRunnableHelpers.h151
-rw-r--r--mailnews/import/applemail/src/moz.build15
-rw-r--r--mailnews/import/applemail/src/nsAppleMailImport.cpp623
-rw-r--r--mailnews/import/applemail/src/nsAppleMailImport.h78
-rw-r--r--mailnews/import/applemail/src/nsEmlxHelperUtils.h55
-rw-r--r--mailnews/import/applemail/src/nsEmlxHelperUtils.mm240
-rw-r--r--mailnews/import/becky/src/moz.build16
-rw-r--r--mailnews/import/becky/src/nsBeckyAddressBooks.cpp383
-rw-r--r--mailnews/import/becky/src/nsBeckyAddressBooks.h35
-rw-r--r--mailnews/import/becky/src/nsBeckyFilters.cpp800
-rw-r--r--mailnews/import/becky/src/nsBeckyFilters.h77
-rw-r--r--mailnews/import/becky/src/nsBeckyImport.cpp168
-rw-r--r--mailnews/import/becky/src/nsBeckyImport.h36
-rw-r--r--mailnews/import/becky/src/nsBeckyMail.cpp641
-rw-r--r--mailnews/import/becky/src/nsBeckyMail.h45
-rw-r--r--mailnews/import/becky/src/nsBeckySettings.cpp471
-rw-r--r--mailnews/import/becky/src/nsBeckySettings.h52
-rw-r--r--mailnews/import/becky/src/nsBeckyStringBundle.cpp74
-rw-r--r--mailnews/import/becky/src/nsBeckyStringBundle.h33
-rw-r--r--mailnews/import/becky/src/nsBeckyUtils.cpp334
-rw-r--r--mailnews/import/becky/src/nsBeckyUtils.h37
-rw-r--r--mailnews/import/build/moz.build61
-rw-r--r--mailnews/import/build/nsImportModule.cpp194
-rw-r--r--mailnews/import/content/fieldMapImport.js186
-rw-r--r--mailnews/import/content/fieldMapImport.xul68
-rw-r--r--mailnews/import/content/import-test.html36
-rw-r--r--mailnews/import/content/importDialog.js1066
-rw-r--r--mailnews/import/content/importDialog.xul143
-rw-r--r--mailnews/import/outlook/src/MapiApi.cpp1937
-rw-r--r--mailnews/import/outlook/src/MapiApi.h265
-rw-r--r--mailnews/import/outlook/src/MapiDbgLog.h40
-rw-r--r--mailnews/import/outlook/src/MapiMessage.cpp1473
-rw-r--r--mailnews/import/outlook/src/MapiMessage.h271
-rw-r--r--mailnews/import/outlook/src/MapiMimeTypes.cpp96
-rw-r--r--mailnews/import/outlook/src/MapiMimeTypes.h31
-rw-r--r--mailnews/import/outlook/src/MapiTagStrs.cpp1070
-rw-r--r--mailnews/import/outlook/src/OutlookDebugLog.h24
-rw-r--r--mailnews/import/outlook/src/moz.build24
-rw-r--r--mailnews/import/outlook/src/nsOutlookCompose.cpp814
-rw-r--r--mailnews/import/outlook/src/nsOutlookCompose.h66
-rw-r--r--mailnews/import/outlook/src/nsOutlookImport.cpp589
-rw-r--r--mailnews/import/outlook/src/nsOutlookImport.h44
-rw-r--r--mailnews/import/outlook/src/nsOutlookMail.cpp863
-rw-r--r--mailnews/import/outlook/src/nsOutlookMail.h54
-rw-r--r--mailnews/import/outlook/src/nsOutlookSettings.cpp567
-rw-r--r--mailnews/import/outlook/src/nsOutlookSettings.h29
-rw-r--r--mailnews/import/outlook/src/nsOutlookStringBundle.cpp71
-rw-r--r--mailnews/import/outlook/src/nsOutlookStringBundle.h38
-rw-r--r--mailnews/import/outlook/src/rtfDecoder.cpp520
-rw-r--r--mailnews/import/outlook/src/rtfDecoder.h22
-rw-r--r--mailnews/import/outlook/src/rtfMailDecoder.cpp79
-rw-r--r--mailnews/import/outlook/src/rtfMailDecoder.h41
-rw-r--r--mailnews/import/public/moz.build21
-rw-r--r--mailnews/import/public/nsIImportABDescriptor.idl70
-rw-r--r--mailnews/import/public/nsIImportAddressBooks.idl153
-rw-r--r--mailnews/import/public/nsIImportFieldMap.idl72
-rw-r--r--mailnews/import/public/nsIImportFilters.idl32
-rw-r--r--mailnews/import/public/nsIImportGeneric.idl89
-rw-r--r--mailnews/import/public/nsIImportMail.idl98
-rw-r--r--mailnews/import/public/nsIImportMailboxDescriptor.idl46
-rw-r--r--mailnews/import/public/nsIImportMimeEncode.idl42
-rw-r--r--mailnews/import/public/nsIImportModule.idl32
-rw-r--r--mailnews/import/public/nsIImportService.idl59
-rw-r--r--mailnews/import/public/nsIImportSettings.idl39
-rw-r--r--mailnews/import/src/ImportCharSet.cpp58
-rw-r--r--mailnews/import/src/ImportCharSet.h175
-rw-r--r--mailnews/import/src/ImportDebug.h22
-rw-r--r--mailnews/import/src/ImportOutFile.cpp299
-rw-r--r--mailnews/import/src/ImportOutFile.h94
-rw-r--r--mailnews/import/src/ImportTranslate.cpp105
-rw-r--r--mailnews/import/src/ImportTranslate.h23
-rw-r--r--mailnews/import/src/moz.build25
-rw-r--r--mailnews/import/src/nsImportABDescriptor.cpp32
-rw-r--r--mailnews/import/src/nsImportABDescriptor.h103
-rw-r--r--mailnews/import/src/nsImportAddressBooks.cpp894
-rw-r--r--mailnews/import/src/nsImportEmbeddedImageData.cpp64
-rw-r--r--mailnews/import/src/nsImportEmbeddedImageData.h32
-rw-r--r--mailnews/import/src/nsImportEncodeScan.cpp374
-rw-r--r--mailnews/import/src/nsImportEncodeScan.h39
-rw-r--r--mailnews/import/src/nsImportFieldMap.cpp384
-rw-r--r--mailnews/import/src/nsImportFieldMap.h46
-rw-r--r--mailnews/import/src/nsImportMail.cpp1208
-rw-r--r--mailnews/import/src/nsImportMailboxDescriptor.cpp39
-rw-r--r--mailnews/import/src/nsImportMailboxDescriptor.h63
-rw-r--r--mailnews/import/src/nsImportMimeEncode.cpp411
-rw-r--r--mailnews/import/src/nsImportMimeEncode.h73
-rw-r--r--mailnews/import/src/nsImportScanFile.cpp172
-rw-r--r--mailnews/import/src/nsImportScanFile.h54
-rw-r--r--mailnews/import/src/nsImportService.cpp583
-rw-r--r--mailnews/import/src/nsImportService.h96
-rw-r--r--mailnews/import/src/nsImportStringBundle.cpp80
-rw-r--r--mailnews/import/src/nsImportStringBundle.h48
-rw-r--r--mailnews/import/src/nsImportTranslator.cpp296
-rw-r--r--mailnews/import/src/nsImportTranslator.h66
-rw-r--r--mailnews/import/text/src/TextDebugLog.h21
-rw-r--r--mailnews/import/text/src/moz.build16
-rw-r--r--mailnews/import/text/src/nsTextAddress.cpp470
-rw-r--r--mailnews/import/text/src/nsTextAddress.h57
-rw-r--r--mailnews/import/text/src/nsTextImport.cpp714
-rw-r--r--mailnews/import/text/src/nsTextImport.h39
-rw-r--r--mailnews/import/vcard/src/moz.build20
-rw-r--r--mailnews/import/vcard/src/nsVCardAddress.cpp139
-rw-r--r--mailnews/import/vcard/src/nsVCardAddress.h40
-rw-r--r--mailnews/import/vcard/src/nsVCardImport.cpp398
-rw-r--r--mailnews/import/vcard/src/nsVCardImport.h38
-rw-r--r--mailnews/import/winlivemail/WMDebugLog.h20
-rw-r--r--mailnews/import/winlivemail/moz.build14
-rw-r--r--mailnews/import/winlivemail/nsWMImport.cpp248
-rw-r--r--mailnews/import/winlivemail/nsWMImport.h38
-rw-r--r--mailnews/import/winlivemail/nsWMSettings.cpp758
-rw-r--r--mailnews/import/winlivemail/nsWMSettings.h22
-rw-r--r--mailnews/import/winlivemail/nsWMStringBundle.cpp71
-rw-r--r--mailnews/import/winlivemail/nsWMStringBundle.h38
-rw-r--r--mailnews/import/winlivemail/nsWMUtils.cpp164
-rw-r--r--mailnews/import/winlivemail/nsWMUtils.h27
-rw-r--r--mailnews/intl/charsetData.properties120
-rw-r--r--mailnews/intl/charsetalias.properties99
-rw-r--r--mailnews/intl/jar.mn6
-rw-r--r--mailnews/intl/moz.build35
-rw-r--r--mailnews/intl/nsCharsetAlias.cpp93
-rw-r--r--mailnews/intl/nsCharsetAlias.h25
-rw-r--r--mailnews/intl/nsCharsetConverterManager.cpp352
-rw-r--r--mailnews/intl/nsCharsetConverterManager.h30
-rw-r--r--mailnews/intl/nsCommUConvCID.h26
-rw-r--r--mailnews/intl/nsICharsetConverterManager.idl108
-rw-r--r--mailnews/intl/nsMUTF7ToUnicode.cpp14
-rw-r--r--mailnews/intl/nsMUTF7ToUnicode.h31
-rw-r--r--mailnews/intl/nsUTF7ToUnicode.cpp228
-rw-r--r--mailnews/intl/nsUTF7ToUnicode.h72
-rw-r--r--mailnews/intl/nsUnicodeToMUTF7.cpp14
-rw-r--r--mailnews/intl/nsUnicodeToMUTF7.h31
-rw-r--r--mailnews/intl/nsUnicodeToUTF7.cpp298
-rw-r--r--mailnews/intl/nsUnicodeToUTF7.h78
-rw-r--r--mailnews/jar.mn138
-rw-r--r--mailnews/jsaccount/modules/JSAccountUtils.jsm285
-rw-r--r--mailnews/jsaccount/modules/JaBaseUrl.jsm81
-rw-r--r--mailnews/jsaccount/moz.build15
-rw-r--r--mailnews/jsaccount/public/moz.build17
-rw-r--r--mailnews/jsaccount/public/msgIDelegateList.idl19
-rw-r--r--mailnews/jsaccount/public/msgIOverride.idl42
-rw-r--r--mailnews/jsaccount/public/msgJsAccountCID.h35
-rw-r--r--mailnews/jsaccount/readme.html56
-rw-r--r--mailnews/jsaccount/src/DelegateList.cpp33
-rw-r--r--mailnews/jsaccount/src/DelegateList.h52
-rw-r--r--mailnews/jsaccount/src/JaAbDirectory.cpp98
-rw-r--r--mailnews/jsaccount/src/JaAbDirectory.h89
-rw-r--r--mailnews/jsaccount/src/JaCompose.cpp103
-rw-r--r--mailnews/jsaccount/src/JaCompose.h92
-rw-r--r--mailnews/jsaccount/src/JaIncomingServer.cpp109
-rw-r--r--mailnews/jsaccount/src/JaIncomingServer.h94
-rw-r--r--mailnews/jsaccount/src/JaMsgFolder.cpp207
-rw-r--r--mailnews/jsaccount/src/JaMsgFolder.h126
-rw-r--r--mailnews/jsaccount/src/JaSend.cpp102
-rw-r--r--mailnews/jsaccount/src/JaSend.h96
-rw-r--r--mailnews/jsaccount/src/JaUrl.cpp228
-rw-r--r--mailnews/jsaccount/src/JaUrl.h118
-rw-r--r--mailnews/jsaccount/src/moz.build28
-rw-r--r--mailnews/local/public/moz.build31
-rw-r--r--mailnews/local/public/nsILocalMailIncomingServer.idl24
-rw-r--r--mailnews/local/public/nsIMailboxService.idl34
-rw-r--r--mailnews/local/public/nsIMailboxUrl.idl59
-rw-r--r--mailnews/local/public/nsIMovemailIncomingServer.idl11
-rw-r--r--mailnews/local/public/nsIMovemailService.idl25
-rw-r--r--mailnews/local/public/nsIMsgLocalMailFolder.idl122
-rw-r--r--mailnews/local/public/nsIMsgParseMailMsgState.idl48
-rw-r--r--mailnews/local/public/nsINewsBlogFeedDownloader.idl41
-rw-r--r--mailnews/local/public/nsINoIncomingServer.idl16
-rw-r--r--mailnews/local/public/nsINoneService.idl11
-rw-r--r--mailnews/local/public/nsIPop3IncomingServer.idl38
-rw-r--r--mailnews/local/public/nsIPop3Protocol.idl25
-rw-r--r--mailnews/local/public/nsIPop3Service.idl128
-rw-r--r--mailnews/local/public/nsIPop3Sink.idl53
-rw-r--r--mailnews/local/public/nsIPop3URL.idl19
-rw-r--r--mailnews/local/public/nsIRssIncomingServer.idl16
-rw-r--r--mailnews/local/public/nsIRssService.idl10
-rw-r--r--mailnews/local/public/nsMsgLocalCID.h227
-rw-r--r--mailnews/local/src/moz.build36
-rw-r--r--mailnews/local/src/nsLocalMailFolder.cpp3815
-rw-r--r--mailnews/local/src/nsLocalMailFolder.h279
-rw-r--r--mailnews/local/src/nsLocalUndoTxn.cpp560
-rw-r--r--mailnews/local/src/nsLocalUndoTxn.h86
-rw-r--r--mailnews/local/src/nsLocalUtils.cpp244
-rw-r--r--mailnews/local/src/nsLocalUtils.h30
-rw-r--r--mailnews/local/src/nsMailboxProtocol.cpp721
-rw-r--r--mailnews/local/src/nsMailboxProtocol.h125
-rw-r--r--mailnews/local/src/nsMailboxServer.cpp33
-rw-r--r--mailnews/local/src/nsMailboxServer.h22
-rw-r--r--mailnews/local/src/nsMailboxService.cpp677
-rw-r--r--mailnews/local/src/nsMailboxService.h57
-rw-r--r--mailnews/local/src/nsMailboxUrl.cpp556
-rw-r--r--mailnews/local/src/nsMailboxUrl.h110
-rw-r--r--mailnews/local/src/nsMovemailIncomingServer.cpp178
-rw-r--r--mailnews/local/src/nsMovemailIncomingServer.h40
-rw-r--r--mailnews/local/src/nsMovemailService.cpp694
-rw-r--r--mailnews/local/src/nsMovemailService.h32
-rw-r--r--mailnews/local/src/nsMsgBrkMBoxStore.cpp1131
-rw-r--r--mailnews/local/src/nsMsgBrkMBoxStore.h50
-rw-r--r--mailnews/local/src/nsMsgLocalStoreUtils.cpp345
-rw-r--r--mailnews/local/src/nsMsgLocalStoreUtils.h49
-rw-r--r--mailnews/local/src/nsMsgMaildirStore.cpp1466
-rw-r--r--mailnews/local/src/nsMsgMaildirStore.h38
-rw-r--r--mailnews/local/src/nsNoIncomingServer.cpp206
-rw-r--r--mailnews/local/src/nsNoIncomingServer.h41
-rw-r--r--mailnews/local/src/nsNoneService.cpp168
-rw-r--r--mailnews/local/src/nsNoneService.h28
-rw-r--r--mailnews/local/src/nsParseMailbox.cpp2632
-rw-r--r--mailnews/local/src/nsParseMailbox.h272
-rw-r--r--mailnews/local/src/nsPop3IncomingServer.cpp744
-rw-r--r--mailnews/local/src/nsPop3IncomingServer.h57
-rw-r--r--mailnews/local/src/nsPop3Protocol.cpp4172
-rw-r--r--mailnews/local/src/nsPop3Protocol.h402
-rw-r--r--mailnews/local/src/nsPop3Service.cpp711
-rw-r--r--mailnews/local/src/nsPop3Service.h52
-rw-r--r--mailnews/local/src/nsPop3Sink.cpp1026
-rw-r--r--mailnews/local/src/nsPop3Sink.h78
-rw-r--r--mailnews/local/src/nsPop3URL.cpp63
-rw-r--r--mailnews/local/src/nsPop3URL.h30
-rw-r--r--mailnews/local/src/nsRssIncomingServer.cpp260
-rw-r--r--mailnews/local/src/nsRssIncomingServer.h43
-rw-r--r--mailnews/local/src/nsRssService.cpp130
-rw-r--r--mailnews/local/src/nsRssService.h25
-rw-r--r--mailnews/mailnews.js964
-rw-r--r--mailnews/mailnews.mozbuild16
-rw-r--r--mailnews/mapi/mapiDll/Makefile.in6
-rw-r--r--mailnews/mapi/mapiDll/Mapi32.DEF21
-rw-r--r--mailnews/mapi/mapiDll/MapiDll.cpp480
-rw-r--r--mailnews/mapi/mapiDll/module.ver7
-rw-r--r--mailnews/mapi/mapiDll/moz.build24
-rw-r--r--mailnews/mapi/mapihook/build/Makefile.in36
-rw-r--r--mailnews/mapi/mapihook/build/MapiProxy.def13
-rw-r--r--mailnews/mapi/mapihook/build/module.ver6
-rw-r--r--mailnews/mapi/mapihook/build/moz.build22
-rw-r--r--mailnews/mapi/mapihook/build/msgMapi.idl91
-rw-r--r--mailnews/mapi/mapihook/moz.build11
-rw-r--r--mailnews/mapi/mapihook/public/moz.build11
-rw-r--r--mailnews/mapi/mapihook/public/nsIMapiSupport.idl44
-rw-r--r--mailnews/mapi/mapihook/src/Makefile.in6
-rw-r--r--mailnews/mapi/mapihook/src/Registry.cpp291
-rw-r--r--mailnews/mapi/mapihook/src/Registry.h23
-rw-r--r--mailnews/mapi/mapihook/src/moz.build25
-rw-r--r--mailnews/mapi/mapihook/src/msgMapiFactory.cpp85
-rw-r--r--mailnews/mapi/mapihook/src/msgMapiFactory.h39
-rw-r--r--mailnews/mapi/mapihook/src/msgMapiHook.cpp748
-rw-r--r--mailnews/mapi/mapihook/src/msgMapiHook.h32
-rw-r--r--mailnews/mapi/mapihook/src/msgMapiImp.cpp884
-rw-r--r--mailnews/mapi/mapihook/src/msgMapiImp.h75
-rw-r--r--mailnews/mapi/mapihook/src/msgMapiMain.cpp306
-rw-r--r--mailnews/mapi/mapihook/src/msgMapiMain.h86
-rw-r--r--mailnews/mapi/mapihook/src/msgMapiSupport.cpp151
-rw-r--r--mailnews/mapi/mapihook/src/msgMapiSupport.h34
-rw-r--r--mailnews/mime/cthandlers/glue/mimexpcom.cpp132
-rw-r--r--mailnews/mime/cthandlers/glue/mimexpcom.h93
-rw-r--r--mailnews/mime/cthandlers/glue/moz.build18
-rw-r--r--mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp60
-rw-r--r--mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h47
-rw-r--r--mailnews/mime/cthandlers/moz.build12
-rw-r--r--mailnews/mime/cthandlers/pgpmime/moz.build20
-rw-r--r--mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp634
-rw-r--r--mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h69
-rw-r--r--mailnews/mime/cthandlers/vcard/mimevcrd.cpp378
-rw-r--r--mailnews/mime/cthandlers/vcard/mimevcrd.h33
-rw-r--r--mailnews/mime/cthandlers/vcard/moz.build14
-rw-r--r--mailnews/mime/emitters/moz.build21
-rw-r--r--mailnews/mime/emitters/nsEmitterUtils.cpp66
-rw-r--r--mailnews/mime/emitters/nsEmitterUtils.h14
-rw-r--r--mailnews/mime/emitters/nsMimeBaseEmitter.cpp1091
-rw-r--r--mailnews/mime/emitters/nsMimeBaseEmitter.h147
-rw-r--r--mailnews/mime/emitters/nsMimeEmitterCID.h51
-rw-r--r--mailnews/mime/emitters/nsMimeHtmlEmitter.cpp543
-rw-r--r--mailnews/mime/emitters/nsMimeHtmlEmitter.h64
-rw-r--r--mailnews/mime/emitters/nsMimePlainEmitter.cpp64
-rw-r--r--mailnews/mime/emitters/nsMimePlainEmitter.h31
-rw-r--r--mailnews/mime/emitters/nsMimeRawEmitter.cpp34
-rw-r--r--mailnews/mime/emitters/nsMimeRawEmitter.h29
-rw-r--r--mailnews/mime/emitters/nsMimeRebuffer.cpp50
-rw-r--r--mailnews/mime/emitters/nsMimeRebuffer.h29
-rw-r--r--mailnews/mime/emitters/nsMimeXmlEmitter.cpp184
-rw-r--r--mailnews/mime/emitters/nsMimeXmlEmitter.h47
-rw-r--r--mailnews/mime/jsmime/LICENSE19
-rw-r--r--mailnews/mime/jsmime/README.md59
-rw-r--r--mailnews/mime/jsmime/jsmime.js3348
-rw-r--r--mailnews/mime/moz.build15
-rw-r--r--mailnews/mime/public/MimeEncoder.h44
-rw-r--r--mailnews/mime/public/MimeHeaderParser.h179
-rw-r--r--mailnews/mime/public/moz.build36
-rw-r--r--mailnews/mime/public/msgIStructuredHeaders.idl209
-rw-r--r--mailnews/mime/public/nsICMSDecoder.idl29
-rw-r--r--mailnews/mime/public/nsICMSEncoder.idl30
-rw-r--r--mailnews/mime/public/nsICMSMessage.idl41
-rw-r--r--mailnews/mime/public/nsICMSMessage2.idl67
-rw-r--r--mailnews/mime/public/nsICMSMessageErrors.idl35
-rw-r--r--mailnews/mime/public/nsICMSSecureMessage.idl42
-rw-r--r--mailnews/mime/public/nsIMimeContentTypeHandler.h65
-rw-r--r--mailnews/mime/public/nsIMimeConverter.idl75
-rw-r--r--mailnews/mime/public/nsIMimeEmitter.idl81
-rw-r--r--mailnews/mime/public/nsIMimeHeaders.idl41
-rw-r--r--mailnews/mime/public/nsIMimeMiscStatus.idl76
-rw-r--r--mailnews/mime/public/nsIMimeObjectClassAccess.h49
-rw-r--r--mailnews/mime/public/nsIMimeStreamConverter.idl93
-rw-r--r--mailnews/mime/public/nsIMsgHeaderParser.idl244
-rw-r--r--mailnews/mime/public/nsIPgpMimeProxy.idl69
-rw-r--r--mailnews/mime/public/nsISimpleMimeConverter.idl22
-rw-r--r--mailnews/mime/public/nsMailHeaders.h90
-rw-r--r--mailnews/mime/public/nsMsgMimeCID.h33
-rw-r--r--mailnews/mime/src/MimeHeaderParser.cpp248
-rw-r--r--mailnews/mime/src/comi18n.cpp108
-rw-r--r--mailnews/mime/src/comi18n.h42
-rw-r--r--mailnews/mime/src/extraMimeParsers.jsm29
-rw-r--r--mailnews/mime/src/jsmime.jsm90
-rw-r--r--mailnews/mime/src/mime.def7
-rw-r--r--mailnews/mime/src/mimeJSComponents.js515
-rw-r--r--mailnews/mime/src/mimeParser.jsm258
-rw-r--r--mailnews/mime/src/mimeTextHTMLParsed.cpp150
-rw-r--r--mailnews/mime/src/mimeTextHTMLParsed.h28
-rw-r--r--mailnews/mime/src/mimebuf.cpp249
-rw-r--r--mailnews/mime/src/mimebuf.h39
-rw-r--r--mailnews/mime/src/mimecms.cpp797
-rw-r--r--mailnews/mime/src/mimecms.h36
-rw-r--r--mailnews/mime/src/mimecom.cpp74
-rw-r--r--mailnews/mime/src/mimecom.h38
-rw-r--r--mailnews/mime/src/mimecont.cpp218
-rw-r--r--mailnews/mime/src/mimecont.h43
-rw-r--r--mailnews/mime/src/mimecryp.cpp562
-rw-r--r--mailnews/mime/src/mimecryp.h140
-rw-r--r--mailnews/mime/src/mimecth.cpp51
-rw-r--r--mailnews/mime/src/mimecth.h135
-rw-r--r--mailnews/mime/src/mimedrft.cpp2084
-rw-r--r--mailnews/mime/src/mimeebod.cpp509
-rw-r--r--mailnews/mime/src/mimeebod.h37
-rw-r--r--mailnews/mime/src/mimeenc.cpp1107
-rw-r--r--mailnews/mime/src/mimeeobj.cpp246
-rw-r--r--mailnews/mime/src/mimeeobj.h50
-rw-r--r--mailnews/mime/src/mimefilt.cpp399
-rw-r--r--mailnews/mime/src/mimehdrs.cpp888
-rw-r--r--mailnews/mime/src/mimehdrs.h88
-rw-r--r--mailnews/mime/src/mimei.cpp1910
-rw-r--r--mailnews/mime/src/mimei.h410
-rw-r--r--mailnews/mime/src/mimeiimg.cpp249
-rw-r--r--mailnews/mime/src/mimeiimg.h35
-rw-r--r--mailnews/mime/src/mimeleaf.cpp220
-rw-r--r--mailnews/mime/src/mimeleaf.h59
-rw-r--r--mailnews/mime/src/mimemalt.cpp580
-rw-r--r--mailnews/mime/src/mimemalt.h48
-rw-r--r--mailnews/mime/src/mimemapl.cpp189
-rw-r--r--mailnews/mime/src/mimemapl.h32
-rw-r--r--mailnews/mime/src/mimemcms.cpp494
-rw-r--r--mailnews/mime/src/mimemcms.h35
-rw-r--r--mailnews/mime/src/mimemdig.cpp24
-rw-r--r--mailnews/mime/src/mimemdig.h33
-rw-r--r--mailnews/mime/src/mimemmix.cpp21
-rw-r--r--mailnews/mime/src/mimemmix.h32
-rw-r--r--mailnews/mime/src/mimemoz2.cpp2212
-rw-r--r--mailnews/mime/src/mimemoz2.h196
-rw-r--r--mailnews/mime/src/mimempar.cpp21
-rw-r--r--mailnews/mime/src/mimempar.h32
-rw-r--r--mailnews/mime/src/mimemrel.cpp1199
-rw-r--r--mailnews/mime/src/mimemrel.h66
-rw-r--r--mailnews/mime/src/mimemsg.cpp965
-rw-r--r--mailnews/mime/src/mimemsg.h37
-rw-r--r--mailnews/mime/src/mimemsig.cpp783
-rw-r--r--mailnews/mime/src/mimemsig.h136
-rw-r--r--mailnews/mime/src/mimemult.cpp753
-rw-r--r--mailnews/mime/src/mimemult.h102
-rw-r--r--mailnews/mime/src/mimeobj.cpp327
-rw-r--r--mailnews/mime/src/mimeobj.h186
-rw-r--r--mailnews/mime/src/mimepbuf.cpp296
-rw-r--r--mailnews/mime/src/mimepbuf.h64
-rw-r--r--mailnews/mime/src/mimesun.cpp342
-rw-r--r--mailnews/mime/src/mimesun.h59
-rw-r--r--mailnews/mime/src/mimetenr.cpp28
-rw-r--r--mailnews/mime/src/mimetenr.h32
-rw-r--r--mailnews/mime/src/mimetext.cpp544
-rw-r--r--mailnews/mime/src/mimetext.h82
-rw-r--r--mailnews/mime/src/mimethpl.cpp165
-rw-r--r--mailnews/mime/src/mimethpl.h35
-rw-r--r--mailnews/mime/src/mimethsa.cpp143
-rw-r--r--mailnews/mime/src/mimethsa.h28
-rw-r--r--mailnews/mime/src/mimethtm.cpp254
-rw-r--r--mailnews/mime/src/mimethtm.h35
-rw-r--r--mailnews/mime/src/mimetpfl.cpp630
-rw-r--r--mailnews/mime/src/mimetpfl.h52
-rw-r--r--mailnews/mime/src/mimetpla.cpp451
-rw-r--r--mailnews/mime/src/mimetpla.h39
-rw-r--r--mailnews/mime/src/mimetric.cpp353
-rw-r--r--mailnews/mime/src/mimetric.h33
-rw-r--r--mailnews/mime/src/mimeunty.cpp588
-rw-r--r--mailnews/mime/src/mimeunty.h70
-rw-r--r--mailnews/mime/src/modlmime.h398
-rw-r--r--mailnews/mime/src/modmimee.h56
-rw-r--r--mailnews/mime/src/moz.build92
-rw-r--r--mailnews/mime/src/msgMime.manifest9
-rw-r--r--mailnews/mime/src/nsCMS.cpp1048
-rw-r--r--mailnews/mime/src/nsCMS.h108
-rw-r--r--mailnews/mime/src/nsCMSSecureMessage.cpp363
-rw-r--r--mailnews/mime/src/nsCMSSecureMessage.h37
-rw-r--r--mailnews/mime/src/nsMimeObjectClassAccess.cpp97
-rw-r--r--mailnews/mime/src/nsMimeObjectClassAccess.h52
-rw-r--r--mailnews/mime/src/nsMimeStringResources.h40
-rw-r--r--mailnews/mime/src/nsSimpleMimeConverterStub.cpp209
-rw-r--r--mailnews/mime/src/nsSimpleMimeConverterStub.h13
-rw-r--r--mailnews/mime/src/nsStreamConverter.cpp1157
-rw-r--r--mailnews/mime/src/nsStreamConverter.h88
-rw-r--r--mailnews/moz.build61
-rw-r--r--mailnews/news/content/downloadheaders.js85
-rw-r--r--mailnews/news/content/downloadheaders.xul49
-rw-r--r--mailnews/news/moz.build9
-rw-r--r--mailnews/news/public/moz.build24
-rw-r--r--mailnews/news/public/nsIMsgNewsFolder.idl132
-rw-r--r--mailnews/news/public/nsIMsgOfflineNewsState.idl23
-rw-r--r--mailnews/news/public/nsINNTPArticleList.idl17
-rw-r--r--mailnews/news/public/nsINNTPNewsgroupList.idl93
-rw-r--r--mailnews/news/public/nsINNTPNewsgroupPost.idl54
-rw-r--r--mailnews/news/public/nsINNTPProtocol.idl31
-rw-r--r--mailnews/news/public/nsINewsDownloadDialogArgs.idl19
-rw-r--r--mailnews/news/public/nsINntpIncomingServer.idl152
-rw-r--r--mailnews/news/public/nsINntpService.idl50
-rw-r--r--mailnews/news/public/nsINntpUrl.idl99
-rw-r--r--mailnews/news/public/nsMsgNewsCID.h117
-rw-r--r--mailnews/news/src/moz.build27
-rw-r--r--mailnews/news/src/nntpCore.h163
-rw-r--r--mailnews/news/src/nsNNTPArticleList.cpp104
-rw-r--r--mailnews/news/src/nsNNTPArticleList.h40
-rw-r--r--mailnews/news/src/nsNNTPNewsgroupList.cpp1332
-rw-r--r--mailnews/news/src/nsNNTPNewsgroupList.h124
-rw-r--r--mailnews/news/src/nsNNTPNewsgroupPost.cpp94
-rw-r--r--mailnews/news/src/nsNNTPNewsgroupPost.h61
-rw-r--r--mailnews/news/src/nsNNTPProtocol.cpp4747
-rw-r--r--mailnews/news/src/nsNNTPProtocol.h509
-rw-r--r--mailnews/news/src/nsNewsAutoCompleteSearch.js141
-rw-r--r--mailnews/news/src/nsNewsAutoCompleteSearch.manifest2
-rw-r--r--mailnews/news/src/nsNewsDownloadDialogArgs.cpp91
-rw-r--r--mailnews/news/src/nsNewsDownloadDialogArgs.h30
-rw-r--r--mailnews/news/src/nsNewsDownloader.cpp586
-rw-r--r--mailnews/news/src/nsNewsDownloader.h126
-rw-r--r--mailnews/news/src/nsNewsFolder.cpp1864
-rw-r--r--mailnews/news/src/nsNewsFolder.h147
-rw-r--r--mailnews/news/src/nsNewsUtils.cpp62
-rw-r--r--mailnews/news/src/nsNewsUtils.h33
-rw-r--r--mailnews/news/src/nsNntpIncomingServer.cpp2162
-rw-r--r--mailnews/news/src/nsNntpIncomingServer.h142
-rw-r--r--mailnews/news/src/nsNntpMockChannel.cpp353
-rw-r--r--mailnews/news/src/nsNntpMockChannel.h65
-rw-r--r--mailnews/news/src/nsNntpService.cpp1751
-rw-r--r--mailnews/news/src/nsNntpService.h76
-rw-r--r--mailnews/news/src/nsNntpUrl.cpp578
-rw-r--r--mailnews/news/src/nsNntpUrl.h64
1289 files changed, 411781 insertions, 0 deletions
diff --git a/mailnews/addrbook/content/abAddressBookNameDialog.js b/mailnews/addrbook/content/abAddressBookNameDialog.js
new file mode 100644
index 000000000..a62659cc3
--- /dev/null
+++ b/mailnews/addrbook/content/abAddressBookNameDialog.js
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gOkButton;
+var gNameInput;
+var gDirectory = null;
+
+var kPersonalAddressbookURI = "moz-abmdbdirectory://abook.mab";
+var kCollectedAddressbookURI = "moz-abmdbdirectory://history.mab";
+var kAllDirectoryRoot = "moz-abdirectory://";
+var kPABDirectory = 2; // defined in nsDirPrefs.h
+
+function abNameOnLoad()
+{
+ // Get the document elements.
+ gOkButton = document.documentElement.getButton('accept');
+ gNameInput = document.getElementById('name');
+
+ // look in arguments[0] for parameters to see if we have a directory or not
+ if ("arguments" in window && window.arguments[0] &&
+ "selectedDirectory" in window.arguments[0]) {
+ gDirectory = window.arguments[0].selectedDirectory;
+ gNameInput.value = gDirectory.dirName;
+ }
+
+ // Work out the window title (if we have a directory specified, then it's a
+ // rename).
+ var bundle = document.getElementById("bundle_addressBook");
+
+ if (gDirectory) {
+ let oldListName = gDirectory.dirName;
+ document.title = bundle.getFormattedString("addressBookTitleEdit", [oldListName]);
+ } else {
+ document.title = bundle.getString("addressBookTitleNew");
+ }
+
+ if (gDirectory &&
+ (gDirectory.URI == kCollectedAddressbookURI ||
+ gDirectory.URI == kPersonalAddressbookURI ||
+ gDirectory.URI == kAllDirectoryRoot + "?")) {
+ // Address book name is not editable, therefore disable the field and
+ // only have an ok button that doesn't do anything.
+ gNameInput.readOnly = true;
+ document.documentElement.buttons = "accept";
+ document.documentElement.removeAttribute("ondialogaccept");
+ } else {
+ gNameInput.focus();
+ abNameDoOkEnabling();
+ }
+}
+
+function abNameOKButton()
+{
+ var newName = gNameInput.value.trim();
+
+ // Either create a new directory or update an existing one depending on what
+ // we were given when we started.
+ if (gDirectory)
+ gDirectory.dirName = newName;
+ else
+ MailServices.ab.newAddressBook(newName, "", kPABDirectory);
+
+ return true;
+}
+
+function abNameDoOkEnabling()
+{
+ gOkButton.disabled = gNameInput.value.trim() == "";
+}
diff --git a/mailnews/addrbook/content/abAddressBookNameDialog.xul b/mailnews/addrbook/content/abAddressBookNameDialog.xul
new file mode 100644
index 000000000..f707cd597
--- /dev/null
+++ b/mailnews/addrbook/content/abAddressBookNameDialog.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/addressbook/abAddressBookNameDialog.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 36em;"
+ onload="abNameOnLoad();"
+ ondialogaccept="return abNameOKButton();">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_addressBook"
+ src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+ </stringbundleset>
+
+ <script type="application/javascript" src="chrome://messenger/content/addressbook/abAddressBookNameDialog.js"/>
+
+ <hbox align="center">
+ <label control="name" value="&name.label;" accesskey="&name.accesskey;"/>
+ <textbox id="name" oninput="abNameDoOkEnabling();" flex="1"/>
+ </hbox>
+</dialog>
diff --git a/mailnews/addrbook/content/abDragDrop.js b/mailnews/addrbook/content/abDragDrop.js
new file mode 100644
index 000000000..6160ec530
--- /dev/null
+++ b/mailnews/addrbook/content/abDragDrop.js
@@ -0,0 +1,424 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+
+// Returns the load context for the current window
+function getLoadContext() {
+ return window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext);
+}
+
+var abFlavorDataProvider = {
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIFlavorDataProvider]),
+
+ getFlavorData: function(aTransferable, aFlavor, aData, aDataLen)
+ {
+ if (aFlavor == "application/x-moz-file-promise")
+ {
+ var primitive = {};
+ aTransferable.getTransferData("text/vcard", primitive, {});
+ var vCard = primitive.value.QueryInterface(Components.interfaces.nsISupportsString).data;
+ aTransferable.getTransferData("application/x-moz-file-promise-dest-filename", primitive, {});
+ var leafName = primitive.value.QueryInterface(Components.interfaces.nsISupportsString).data;
+ aTransferable.getTransferData("application/x-moz-file-promise-dir", primitive, {});
+ var localFile = primitive.value.QueryInterface(Components.interfaces.nsIFile).clone();
+ localFile.append(leafName);
+
+ var ofStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
+ ofStream.init(localFile, -1, -1, 0);
+ var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"].createInstance(Components.interfaces.nsIConverterOutputStream);
+ converter.init(ofStream, null, 0, 0);
+ converter.writeString(vCard);
+ converter.close();
+
+ aData.value = localFile;
+ }
+ }
+};
+
+var abResultsPaneObserver = {
+ onDragStart: function (aEvent, aXferData, aDragAction)
+ {
+ var selectedRows = GetSelectedRows();
+
+ if (!selectedRows)
+ return;
+
+ var selectedAddresses = GetSelectedAddresses();
+
+ aXferData.data = new TransferData();
+ aXferData.data.addDataForFlavour("moz/abcard", selectedRows);
+ aXferData.data.addDataForFlavour("text/x-moz-address", selectedAddresses);
+ aXferData.data.addDataForFlavour("text/unicode", selectedAddresses);
+
+ let srcDirectory = getSelectedDirectory();
+ // The default allowable actions are copy, move and link, so we need
+ // to restrict them here.
+ if (!srcDirectory.readOnly)
+ // Only allow copy & move from read-write directories.
+ aDragAction.action = Components.interfaces.
+ nsIDragService.DRAGDROP_ACTION_COPY |
+ Components.interfaces.
+ nsIDragService.DRAGDROP_ACTION_MOVE;
+ else
+ // Only allow copy from read-only directories.
+ aDragAction.action = Components.interfaces.
+ nsIDragService.DRAGDROP_ACTION_COPY;
+
+ var card = GetSelectedCard();
+ if (card && card.displayName) {
+ let vCard = card.translateTo("vcard");
+ aXferData.data.addDataForFlavour("text/vcard", decodeURIComponent(vCard));
+ aXferData.data.addDataForFlavour("application/x-moz-file-promise-dest-filename", card.displayName + ".vcf");
+ aXferData.data.addDataForFlavour("application/x-moz-file-promise-url", "data:text/vcard," + vCard);
+ aXferData.data.addDataForFlavour("application/x-moz-file-promise", abFlavorDataProvider);
+ }
+ },
+
+ onDrop: function (aEvent, aXferData, aDragSession)
+ {
+ },
+
+ onDragExit: function (aEvent, aDragSession)
+ {
+ },
+
+ onDragOver: function (aEvent, aFlavour, aDragSession)
+ {
+ },
+
+ getSupportedFlavours: function ()
+ {
+ return null;
+ }
+};
+
+
+var dragService = Components.classes["@mozilla.org/widget/dragservice;1"]
+ .getService(Components.interfaces.nsIDragService);
+
+var abDirTreeObserver = {
+ /**
+ * canDrop - determine if the tree will accept the dropping of a item
+ * onto it.
+ *
+ * Note 1: We don't allow duplicate mailing list names, therefore copy
+ * is not allowed for mailing lists.
+ * Note 2: Mailing lists currently really need a card in the parent
+ * address book, therefore only moving to an address book is allowed.
+ *
+ * The possibilities:
+ *
+ * anything -> same place = Not allowed
+ * anything -> read only directory = Not allowed
+ * mailing list -> mailing list = Not allowed
+ * (we currently do not support recursive lists)
+ * address book card -> different address book = MOVE or COPY
+ * address book card -> mailing list = COPY only
+ * (cards currently have to exist outside list for list to work correctly)
+ * mailing list -> different address book = MOVE only
+ * (lists currently need to have unique names)
+ * card in mailing list -> parent mailing list = Not allowed
+ * card in mailing list -> other mailing list = MOVE or COPY
+ * card in mailing list -> other address book = MOVE or COPY
+ * read only directory item -> anywhere = COPY only
+ */
+ canDrop: function(index, orientation, dataTransfer)
+ {
+ if (orientation != Components.interfaces.nsITreeView.DROP_ON)
+ return false;
+ if (!dataTransfer.types.includes("moz/abcard")) {
+ return false;
+ }
+
+ let targetURI = gDirectoryTreeView.getDirectoryAtIndex(index).URI;
+
+ let srcURI = getSelectedDirectoryURI();
+
+ // We cannot allow copy/move to "All Address Books".
+ if (targetURI == kAllDirectoryRoot + "?")
+ return false;
+
+ // The same place case
+ if (targetURI == srcURI)
+ return false;
+
+ // determine if we dragging from a mailing list on a directory x to the parent (directory x).
+ // if so, don't allow the drop
+ if (srcURI.startsWith(targetURI))
+ return false;
+
+ // check if we can write to the target directory
+ // e.g. LDAP is readonly currently
+ var targetDirectory = GetDirectoryFromURI(targetURI);
+
+ if (targetDirectory.readOnly)
+ return false;
+
+ var dragSession = dragService.getCurrentSession();
+ if (!dragSession)
+ return false;
+
+ // XXX Due to bug 373125/bug 349044 we can't specify a default action,
+ // so we default to move and this means that the user would have to press
+ // ctrl to copy which most users don't realise.
+ //
+ // If target directory is a mailing list, then only allow copies.
+ // if (targetDirectory.isMailList &&
+ // dragSession.dragAction != Components.interfaces.
+ // nsIDragService.DRAGDROP_ACTION_COPY)
+ //return false;
+
+ var srcDirectory = GetDirectoryFromURI(srcURI);
+
+ // Only allow copy from read-only directories.
+ if (srcDirectory.readOnly &&
+ dragSession.dragAction != Components.interfaces.
+ nsIDragService.DRAGDROP_ACTION_COPY)
+ return false;
+
+ // Go through the cards checking to see if one of them is a mailing list
+ // (if we are attempting a copy) - we can't copy mailing lists as
+ // that would give us duplicate names which isn't allowed at the
+ // moment.
+ var draggingMailList = false;
+
+ // The data contains the a string of "selected rows", eg.: "1,2".
+ var rows = dataTransfer.getData("moz/abcard").split(",").map(j => parseInt(j, 10));
+
+ for (var j = 0; j < rows.length; j++)
+ {
+ if (gAbView.getCardFromRow(rows[j]).isMailList)
+ {
+ draggingMailList = true;
+ break;
+ }
+ }
+
+ // The rest of the cases - allow cards for copy or move, but only allow
+ // move of mailing lists if we're not going into another mailing list.
+ if (draggingMailList &&
+ (targetDirectory.isMailList ||
+ dragSession.dragAction == Components.interfaces.
+ nsIDragService.DRAGDROP_ACTION_COPY))
+ {
+ return false;
+ }
+
+ dragSession.canDrop = true;
+ return true;
+ },
+
+ /**
+ * onDrop - we don't need to check again for correctness as the
+ * tree view calls canDrop just before calling onDrop.
+ *
+ */
+ onDrop: function(index, orientation, dataTransfer)
+ {
+ var dragSession = dragService.getCurrentSession();
+ if (!dragSession)
+ return;
+ if (!dataTransfer.types.includes("moz/abcard")) {
+ return;
+ }
+
+ let targetURI = gDirectoryTreeView.getDirectoryAtIndex(index).URI;
+ let srcURI = getSelectedDirectoryURI();
+
+ // The data contains the a string of "selected rows", eg.: "1,2".
+ var rows = dataTransfer.getData("moz/abcard").split(",").map(j => parseInt(j, 10));
+ var numrows = rows.length;
+
+ var result;
+ // needToCopyCard is used for whether or not we should be creating
+ // copies of the cards in a mailing list in a different address book
+ // - it's not for if we are moving or not.
+ var needToCopyCard = true;
+ if (srcURI.length > targetURI.length) {
+ result = srcURI.split(targetURI);
+ if (result[0] != srcURI) {
+ // src directory is a mailing list on target directory, no need to copy card
+ needToCopyCard = false;
+ }
+ }
+ else {
+ result = targetURI.split(srcURI);
+ if (result[0] != targetURI) {
+ // target directory is a mailing list on src directory, no need to copy card
+ needToCopyCard = false;
+ }
+ }
+
+ // if we still think we have to copy the card,
+ // check if srcURI and targetURI are mailing lists on same directory
+ // if so, we don't have to copy the card
+ if (needToCopyCard) {
+ var targetParentURI = GetParentDirectoryFromMailingListURI(targetURI);
+ if (targetParentURI && (targetParentURI == GetParentDirectoryFromMailingListURI(srcURI)))
+ needToCopyCard = false;
+ }
+
+ var directory = GetDirectoryFromURI(targetURI);
+
+ // Only move if we are not transferring to a mail list
+ var actionIsMoving = (dragSession.dragAction & dragSession.DRAGDROP_ACTION_MOVE) && !directory.isMailList;
+
+ let cardsToCopy = [];
+ for (let j = 0; j < numrows; j++) {
+ cardsToCopy.push(gAbView.getCardFromRow(rows[j]));
+ }
+ for (let card of cardsToCopy) {
+ if (card.isMailList) {
+ // This check ensures we haven't slipped through by mistake
+ if (needToCopyCard && actionIsMoving) {
+ directory.addMailList(GetDirectoryFromURI(card.mailListURI));
+ }
+ } else {
+ let srcDirectory = null;
+ if (srcURI == (kAllDirectoryRoot + "?") && actionIsMoving) {
+ let dirId = card.directoryId.substring(0, card.directoryId.indexOf("&"));
+ srcDirectory = MailServices.ab.getDirectoryFromId(dirId);
+ }
+
+ directory.dropCard(card, needToCopyCard);
+
+ // This is true only if srcURI is "All ABs" and action is moving.
+ if (srcDirectory) {
+ let cardArray =
+ Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ cardArray.appendElement(card, false);
+ srcDirectory.deleteCards(cardArray);
+ }
+ }
+ }
+
+ var cardsTransferredText;
+
+ // If we are moving, but not moving to a directory, then delete the
+ // selected cards and display the appropriate text
+ if (actionIsMoving && srcURI != (kAllDirectoryRoot + "?")) {
+ // If we have moved the cards, then delete them as well.
+ gAbView.deleteSelectedCards();
+ }
+
+ if (actionIsMoving) {
+ cardsTransferredText = PluralForm.get(numrows,
+ gAddressBookBundle.getFormattedString("contactsMoved", [numrows]));
+ } else {
+ cardsTransferredText = PluralForm.get(numrows,
+ gAddressBookBundle.getFormattedString("contactsCopied", [numrows]));
+ }
+
+ if (srcURI == kAllDirectoryRoot + "?") {
+ SetAbView(srcURI);
+ }
+
+ document.getElementById("statusText").label = cardsTransferredText;
+ },
+
+ onToggleOpenState: function()
+ {
+ },
+
+ onCycleHeader: function(colID, elt)
+ {
+ },
+
+ onCycleCell: function(row, colID)
+ {
+ },
+
+ onSelectionChanged: function()
+ {
+ },
+
+ onPerformAction: function(action)
+ {
+ },
+
+ onPerformActionOnRow: function(action, row)
+ {
+ },
+
+ onPerformActionOnCell: function(action, row, colID)
+ {
+ }
+}
+
+function DragAddressOverTargetControl(event)
+{
+ var dragSession = gDragService.getCurrentSession();
+
+ if (!dragSession.isDataFlavorSupported("text/x-moz-address"))
+ return;
+
+ var trans = Components.classes["@mozilla.org/widget/transferable;1"]
+ .createInstance(Components.interfaces.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor("text/x-moz-address");
+
+ var canDrop = true;
+
+ for ( var i = 0; i < dragSession.numDropItems; ++i )
+ {
+ dragSession.getData ( trans, i );
+ var dataObj = new Object();
+ var bestFlavor = new Object();
+ var len = new Object();
+ try
+ {
+ trans.getAnyTransferData ( bestFlavor, dataObj, len );
+ }
+ catch (ex)
+ {
+ canDrop = false;
+ break;
+ }
+ }
+ dragSession.canDrop = canDrop;
+}
+
+function DropAddressOverTargetControl(event)
+{
+ var dragSession = gDragService.getCurrentSession();
+
+ var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
+ trans.addDataFlavor("text/x-moz-address");
+
+ for ( var i = 0; i < dragSession.numDropItems; ++i )
+ {
+ dragSession.getData ( trans, i );
+ var dataObj = new Object();
+ var bestFlavor = new Object();
+ var len = new Object();
+
+ // Ensure we catch any empty data that may have slipped through
+ try
+ {
+ trans.getAnyTransferData ( bestFlavor, dataObj, len);
+ }
+ catch (ex)
+ {
+ continue;
+ }
+
+ if ( dataObj )
+ dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
+ if ( !dataObj )
+ continue;
+
+ // pull the address out of the data object
+ var address = dataObj.data.substring(0, len.value);
+ if (!address)
+ continue;
+
+ DropRecipient(address);
+ }
+}
diff --git a/mailnews/addrbook/content/abEditCardDialog.xul b/mailnews/addrbook/content/abEditCardDialog.xul
new file mode 100644
index 000000000..0a21f3b83
--- /dev/null
+++ b/mailnews/addrbook/content/abEditCardDialog.xul
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/addressbook/abCardOverlay.xul"?>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="abcardWindow"
+ onload="OnLoadEditCard()"
+ ondialogaccept="return EditCardOKButton();">
+
+ <stringbundleset id="stringbundleset"/>
+ <vbox id="editcard"/>
+</dialog>
diff --git a/mailnews/addrbook/content/abMailListDialog.js b/mailnews/addrbook/content/abMailListDialog.js
new file mode 100644
index 000000000..ee94d39b7
--- /dev/null
+++ b/mailnews/addrbook/content/abMailListDialog.js
@@ -0,0 +1,613 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+top.MAX_RECIPIENTS = 1;
+var inputElementType = "";
+
+var gListCard;
+var gEditList;
+var oldListName = "";
+var gLoadListeners = [];
+var gSaveListeners = [];
+
+try
+{
+ var gDragService = Components.classes["@mozilla.org/widget/dragservice;1"]
+ .getService(Components.interfaces.nsIDragService);
+}
+catch (e)
+{
+}
+
+// Returns the load context for the current window
+function getLoadContext() {
+ return window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext);
+}
+
+function awHandleKeyPress(element, event)
+{
+ // allow dialog to close on enter if focused textbox has no value
+ if (element.value != "" && event.keyCode == KeyEvent.DOM_VK_RETURN) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+}
+
+function mailingListExists(listname)
+{
+ if (MailServices.ab.mailListNameExists(listname))
+ {
+ Services.prompt.alert(window,
+ gAddressBookBundle.getString("mailListNameExistsTitle"),
+ gAddressBookBundle.getString("mailListNameExistsMessage"));
+ return true;
+ }
+ return false;
+}
+
+function GetListValue(mailList, doAdd)
+{
+ var listname = document.getElementById("ListName").value.trim();
+
+ if (listname.length == 0)
+ {
+ var alertText = gAddressBookBundle.getString("emptyListName");
+ alert(alertText);
+ return false;
+ }
+ else
+ {
+ var canonicalNewListName = listname.toLowerCase();
+ var canonicalOldListName = oldListName.toLowerCase();
+ if (doAdd)
+ {
+ if (mailingListExists(canonicalNewListName))
+ return false;
+ }
+ else if (canonicalOldListName != canonicalNewListName)
+ {
+ if (mailingListExists(canonicalNewListName))
+ return false;
+ }
+ }
+
+ mailList.isMailList = true;
+ mailList.dirName = listname;
+ mailList.listNickName = document.getElementById('ListNickName').value;
+ mailList.description = document.getElementById('ListDescription').value;
+
+ var oldTotal = mailList.addressLists.length;
+ var i = 1;
+ var pos = 0;
+ var inputField, fieldValue, cardproperty;
+ while ((inputField = awGetInputElement(i)))
+ {
+
+ fieldValue = inputField.value;
+
+ if (doAdd || (!doAdd && pos >= oldTotal))
+ cardproperty = Components.classes["@mozilla.org/addressbook/cardproperty;1"].createInstance();
+ else
+ cardproperty = mailList.addressLists.queryElementAt(pos, Components.interfaces.nsIAbCard);
+
+ if (fieldValue == "")
+ {
+ if (!doAdd && cardproperty)
+ try
+ {
+ mailList.addressLists.removeElementAt(pos);
+ --oldTotal;
+ }
+ catch(ex)
+ {
+ // Ignore attempting to remove an item
+ // at a position greater than the number
+ // of elements in the addressLists attribute
+ }
+ }
+ else if (cardproperty)
+ {
+ cardproperty = cardproperty.QueryInterface(Components.interfaces.nsIAbCard);
+ if (cardproperty)
+ {
+ let addrObjects = MailServices.headerParser
+ .makeFromDisplayAddress(fieldValue, {});
+ for (let j = 0; j < addrObjects.length; j++)
+ {
+ if (j > 0)
+ {
+ cardproperty = Components.classes["@mozilla.org/addressbook/cardproperty;1"].createInstance();
+ cardproperty = cardproperty.QueryInterface(Components.interfaces.nsIAbCard);
+ }
+ cardproperty.primaryEmail = addrObjects[j].email;
+ cardproperty.displayName = addrObjects[j].name || addrObjects[j].email;
+
+ if (doAdd || (doAdd == false && pos >= oldTotal))
+ mailList.addressLists.appendElement(cardproperty, false);
+ }
+ pos++;
+ }
+ }
+ i++;
+ }
+
+ --i;
+
+ if (doAdd == false && i < oldTotal)
+ {
+ for (var j = i; j < oldTotal; j++)
+ mailList.addressLists.removeElementAt(j);
+ }
+ return true;
+}
+
+function MailListOKButton()
+{
+ var popup = document.getElementById('abPopup');
+ if (popup)
+ {
+ var uri = popup.getAttribute('value');
+
+ // FIX ME - hack to avoid crashing if no ab selected because of blank option bug from template
+ // should be able to just remove this if we are not seeing blank lines in the ab popup
+ if (!uri)
+ return false; // don't close window
+ // -----
+
+ //Add mailing list to database
+ var mailList = Components.classes["@mozilla.org/addressbook/directoryproperty;1"].createInstance();
+ mailList = mailList.QueryInterface(Components.interfaces.nsIAbDirectory);
+
+ if (GetListValue(mailList, true))
+ {
+ var parentDirectory = GetDirectoryFromURI(uri);
+ mailList = parentDirectory.addMailList(mailList);
+ NotifySaveListeners(mailList);
+ }
+ else
+ return false;
+ }
+
+ return true; // close the window
+}
+
+function OnLoadNewMailList()
+{
+ var selectedAB = null;
+
+ InitCommonJS();
+
+ if ("arguments" in window && window.arguments[0])
+ {
+ var abURI = window.arguments[0].selectedAB;
+ if (abURI && abURI != kAllDirectoryRoot + "?") {
+ var directory = GetDirectoryFromURI(abURI);
+ if (directory.isMailList) {
+ var parentURI = GetParentDirectoryFromMailingListURI(abURI);
+ if (parentURI) {
+ selectedAB = parentURI;
+ }
+ }
+ else if (directory.readOnly) {
+ selectedAB = kPersonalAddressbookURI;
+ }
+ else {
+ selectedAB = abURI;
+ }
+ }
+ }
+
+ if (!selectedAB)
+ selectedAB = kPersonalAddressbookURI;
+
+ // set popup with address book names
+ var abPopup = document.getElementById('abPopup');
+ abPopup.value = selectedAB;
+
+ AppendNewRowAndSetFocus();
+ awFitDummyRows(1);
+
+ document.addEventListener("keypress", awDocumentKeyPress, true);
+
+ // focus on first name
+ var listName = document.getElementById('ListName');
+ if (listName)
+ setTimeout( function(firstTextBox) { firstTextBox.focus(); }, 0, listName );
+
+ NotifyLoadListeners(directory);
+}
+
+function EditListOKButton()
+{
+ //edit mailing list in database
+ if (GetListValue(gEditList, false))
+ {
+ if (gListCard) {
+ // modify the list card (for the results pane) from the mailing list
+ gListCard.displayName = gEditList.dirName;
+ gListCard.lastName = gEditList.dirName;
+ gListCard.setProperty("NickName", gEditList.listNickName);
+ gListCard.setProperty("Notes", gEditList.description);
+ }
+
+ NotifySaveListeners(gEditList);
+ gEditList.editMailListToDatabase(gListCard);
+
+ window.arguments[0].refresh = true;
+ return true; // close the window
+ }
+
+ return false;
+}
+
+function OnLoadEditList()
+{
+ InitCommonJS();
+
+ gListCard = window.arguments[0].abCard;
+ var listUri = window.arguments[0].listURI;
+
+ gEditList = GetDirectoryFromURI(listUri);
+
+ document.getElementById('ListName').value = gEditList.dirName;
+ document.getElementById('ListNickName').value = gEditList.listNickName;
+ document.getElementById('ListDescription').value = gEditList.description;
+ oldListName = gEditList.dirName;
+
+ document.title = gAddressBookBundle.getFormattedString("mailingListTitleEdit", [oldListName]);
+
+ if (gEditList.addressLists)
+ {
+ let total = gEditList.addressLists.length;
+ if (total)
+ {
+ let listbox = document.getElementById('addressingWidget');
+ let newListBoxNode = listbox.cloneNode(false);
+ let templateNode = listbox.querySelector("listitem");
+
+ top.MAX_RECIPIENTS = 0;
+ for (let i = 0; i < total; i++)
+ {
+ let card = gEditList.addressLists.queryElementAt(i, Components.interfaces.nsIAbCard);
+ let address = MailServices.headerParser.makeMailboxObject(
+ card.displayName, card.primaryEmail).toString();
+ SetInputValue(address, newListBoxNode, templateNode);
+ }
+ listbox.parentNode.replaceChild(newListBoxNode, listbox);
+ }
+ }
+
+ // Is this directory read-only? If so, we now need to set all the fields to
+ // read-only.
+ if (gEditList.readOnly) {
+ const kMailListFields = [ 'ListName', 'ListNickName', 'ListDescription' ];
+
+ for (let i = 0; i < kMailListFields.length; ++i)
+ document.getElementById(kMailListFields[i]).readOnly = true;
+
+ document.documentElement.buttons = "accept";
+ document.documentElement.removeAttribute("ondialogaccept");
+
+ // Getting a sane read-only implementation for the addressing widget would
+ // basically need a separate dialog. Given I'm not sure about the future of
+ // the mailing list dialog in its current state, let's just disable it
+ // completely.
+ document.getElementById("addressingWidget").disabled = true;
+ }
+
+ document.addEventListener("keypress", awDocumentKeyPress, true);
+
+ // workaround for bug 118337 - for mailing lists that have more rows than fits inside
+ // the display, the value of the textbox inside the new row isn't inherited into the input -
+ // the first row then appears to be duplicated at the end although it is actually empty.
+ // see awAppendNewRow which copies first row and clears it
+ setTimeout(AppendLastRow, 0);
+ NotifyLoadListeners(gEditList);
+}
+
+function AppendLastRow()
+{
+ AppendNewRowAndSetFocus();
+ awFitDummyRows(1);
+
+ // focus on first name
+ var listName = document.getElementById('ListName');
+ if (listName)
+ listName.focus();
+}
+
+function AppendNewRowAndSetFocus()
+{
+ var lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+ if (lastInput && lastInput.value)
+ awAppendNewRow(true);
+ else
+ awSetFocus(top.MAX_RECIPIENTS, lastInput);
+}
+
+function SetInputValue(inputValue, parentNode, templateNode)
+{
+ top.MAX_RECIPIENTS++;
+
+ var newNode = templateNode.cloneNode(true);
+ parentNode.appendChild(newNode); // we need to insert the new node before we set the value of the select element!
+
+ var input = newNode.getElementsByTagName(awInputElementName());
+ if (input && input.length == 1)
+ {
+ //We need to set the value using both setAttribute and .value else we will
+ // lose the content when the field is not visible. See bug 37435
+ input[0].setAttribute("value", inputValue);
+ input[0].value = inputValue;
+ input[0].setAttribute("id", "addressCol1#" + top.MAX_RECIPIENTS);
+ }
+}
+
+function awNotAnEmptyArea(event)
+{
+ //This is temporary until i figure out how to ensure to always having an empty space after the last row
+
+ var lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+ if (lastInput && lastInput.value)
+ awAppendNewRow(false);
+
+ event.stopPropagation();
+}
+
+function awClickEmptySpace(target, setFocus)
+{
+ if (target == null ||
+ (target.localName != "listboxbody" &&
+ target.localName != "listcell" &&
+ target.localName != "listitem"))
+ return;
+
+ var lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+
+ if (lastInput && lastInput.value)
+ awAppendNewRow(setFocus);
+ else
+ if (setFocus)
+ awSetFocus(top.MAX_RECIPIENTS, lastInput);
+}
+
+function awReturnHit(inputElement)
+{
+ var row = awGetRowByInputElement(inputElement);
+ if (inputElement.value)
+ {
+ var nextInput = awGetInputElement(row+1);
+ if (!nextInput)
+ awAppendNewRow(true);
+ else
+ awSetFocus(row+1, nextInput);
+ }
+}
+
+function awDeleteRow(rowToDelete)
+{
+ /* When we delete a row, we must reset the id of others row in order to not break the sequence */
+ var maxRecipients = top.MAX_RECIPIENTS;
+ awRemoveRow(rowToDelete);
+
+ var numberOfCols = awGetNumberOfCols();
+ for (var row = rowToDelete + 1; row <= maxRecipients; row ++)
+ for (var col = 1; col <= numberOfCols; col++)
+ awGetElementByCol(row, col).setAttribute("id", "addressCol" + (col) + "#" + (row-1));
+
+ awTestRowSequence();
+}
+
+function awInputChanged(inputElement)
+{
+// AutoCompleteAddress(inputElement);
+
+ //Do we need to add a new row?
+ var lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+ if (lastInput && lastInput.value && !top.doNotCreateANewRow)
+ awAppendNewRow(false);
+ top.doNotCreateANewRow = false;
+}
+
+function awInputElementName()
+{
+ if (inputElementType == "")
+ inputElementType = document.getElementById("addressCol1#1").localName;
+ return inputElementType;
+}
+
+function awAppendNewRow(setFocus)
+{
+ var body = document.getElementById("addressingWidget");
+ var listitem1 = awGetListItem(1);
+
+ if (body && listitem1)
+ {
+ var nextDummy = awGetNextDummyRow();
+ var newNode = listitem1.cloneNode(true);
+ if (nextDummy)
+ body.replaceChild(newNode, nextDummy);
+ else
+ body.appendChild(newNode);
+
+ top.MAX_RECIPIENTS++;
+
+ var input = newNode.getElementsByTagName(awInputElementName());
+ if (input && input.length == 1)
+ {
+ input[0].setAttribute("value", "");
+ input[0].setAttribute("id", "addressCol1#" + top.MAX_RECIPIENTS);
+
+ if (input[0].getAttribute('focused') != '')
+ input[0].removeAttribute('focused');
+ }
+ // focus on new input widget
+ if (setFocus && input )
+ awSetFocus(top.MAX_RECIPIENTS, input[0]);
+ }
+}
+
+
+// functions for accessing the elements in the addressing widget
+
+function awGetInputElement(row)
+{
+ return document.getElementById("addressCol1#" + row);
+}
+
+
+function _awSetFocus()
+{
+ var listbox = document.getElementById('addressingWidget');
+ try
+ {
+ var theNewRow = awGetListItem(top.awRow);
+
+ listbox.ensureElementIsVisible(theNewRow);
+ top.awInputElement.focus();
+ }
+ catch(ex)
+ {
+ top.awFocusRetry ++;
+ if (top.awFocusRetry < 8)
+ {
+ dump("_awSetFocus failed, try it again...\n");
+ setTimeout(_awSetFocus, 0);
+ }
+ else
+ dump("_awSetFocus failed, forget about it!\n");
+ }
+}
+
+function awTabFromRecipient(element, event)
+{
+ //If we are the last element in the listbox, we don't want to create a new row.
+ if (element == awGetInputElement(top.MAX_RECIPIENTS))
+ top.doNotCreateANewRow = true;
+}
+
+function DragOverAddressListTree(event)
+{
+ var validFlavor = false;
+ var dragSession = gDragService.getCurrentSession();
+
+ // XXX add support for other flavors here
+ if (dragSession.isDataFlavorSupported("text/x-moz-address")) {
+ dragSession.canDrop = true;
+ }
+}
+
+function DropOnAddressListTree(event)
+{
+ let dragSession = gDragService.getCurrentSession();
+ let trans;
+
+ try {
+ trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor("text/x-moz-address");
+ }
+ catch (ex) {
+ return;
+ }
+
+ for (let i = 0; i < dragSession.numDropItems; ++i)
+ {
+ dragSession.getData(trans, i);
+ let dataObj = new Object();
+ let bestFlavor = new Object();
+ let len = new Object();
+ trans.getAnyTransferData(bestFlavor, dataObj, len);
+ if (dataObj)
+ dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
+ if (!dataObj)
+ continue;
+
+ // pull the URL out of the data object
+ let address = dataObj.data.substring(0, len.value);
+ if (!address)
+ continue;
+
+ DropListAddress(event.target, address);
+ }
+}
+
+function DropListAddress(target, address)
+{
+ // Set focus on a new available, visible row.
+ awClickEmptySpace(target, true);
+ if (top.MAX_RECIPIENTS == 0)
+ top.MAX_RECIPIENTS = 1;
+
+ // Break apart the MIME-ready header address into individual addressees to
+ // add to the dialog.
+ let addresses = {}, names = {}, fullNames = {};
+ MailServices.headerParser.parseHeadersWithArray(address, addresses, names,
+ fullNames);
+ for (let full of fullNames.value)
+ {
+ let lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+ lastInput.value = full;
+ awAppendNewRow(true);
+ }
+}
+
+/* Allows extensions to register a listener function for
+ * when a mailing list is loaded. The listener function
+ * should take two parameters - the first being the
+ * mailing list being loaded, the second one being the
+ * current window document.
+ */
+function RegisterLoadListener(aListener)
+{
+ gLoadListeners.push(aListener);
+}
+
+/* Allows extensions to unload a load listener function.
+ */
+function UnregisterLoadListener(aListener)
+{
+ var fIndex = gLoadListeners.indexOf(aListener);
+ if (fIndex != -1)
+ gLoadListeners.splice(fIndex, 1);
+}
+
+/* Allows extensions to register a listener function for
+ * when a mailing list is saved. Like a load listener,
+ * the save listener should take two parameters: the first
+ * being a copy of the mailing list that is being saved,
+ * and the second being the current window document.
+ */
+function RegisterSaveListener(aListener)
+{
+ gSaveListeners.push(aListener);
+}
+
+/* Allows extensions to unload a save listener function.
+ */
+function UnregisterSaveListener(aListener)
+{
+ var fIndex = gSaveListeners.indexOf(aListener);
+ if (fIndex != -1)
+ gSaveListeners.splice(fIndex, 1);
+}
+
+/* Notifies all load listeners.
+ */
+function NotifyLoadListeners(aMailingList)
+{
+ for (let i = 0; i < gLoadListeners.length; i++)
+ gLoadListeners[i](aMailingList, document);
+}
+
+/* Notifies all save listeners.
+ */
+function NotifySaveListeners(aMailingList)
+{
+ for (let i = 0; i < gSaveListeners.length; i++)
+ gSaveListeners[i](aMailingList, document);
+}
+
diff --git a/mailnews/addrbook/content/abNewCardDialog.xul b/mailnews/addrbook/content/abNewCardDialog.xul
new file mode 100644
index 000000000..4f4d1a49b
--- /dev/null
+++ b/mailnews/addrbook/content/abNewCardDialog.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/addressbook/abCardOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/addressbook/abNewCardDialog.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="abcardWindow"
+ onload="OnLoadNewCard()"
+ ondialogaccept="return NewCardOKButton();">
+
+ <stringbundleset id="stringbundleset"/>
+
+ <hbox align="center">
+
+ <label id="abPopupLabel" control="abPopup" value="&chooseAddressBook.label;" accesskey="&chooseAddressBook.accesskey;"/>
+
+ <menulist id="abPopup">
+ <menupopup id="abPopup-menupopup" class="addrbooksPopup" writable="true"/>
+ </menulist>
+
+ </hbox>
+
+ <spacer style="height:1em"/>
+
+ <vbox id="editcard"/>
+
+</dialog>
diff --git a/mailnews/addrbook/content/abResultsPane.js b/mailnews/addrbook/content/abResultsPane.js
new file mode 100644
index 000000000..9a31f701e
--- /dev/null
+++ b/mailnews/addrbook/content/abResultsPane.js
@@ -0,0 +1,507 @@
+/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ; js-indent-level: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Use of items in this file require:
+ *
+ * getSelectedDirectoryURI()
+ * returns the URI of the selected directory
+ * AbResultsPaneDoubleClick(card)
+ * Is called when the results pane is double-clicked, with the clicked card.
+ * AbEditCard(card)
+ * Is called when a card is to be edited, with the card as the parameter.
+ *
+ * The following function is only required if ResultsPaneController is used:
+ *
+ * goSetMenuValue()
+ * Core function in globalOverlay.js
+ */
+
+// List/card selections in the results pane.
+var kNothingSelected = 0;
+var kListsAndCards = 1;
+var kMultipleListsOnly = 2;
+var kSingleListOnly = 3;
+var kCardsOnly = 4;
+
+// Global Variables
+
+// gAbView holds an object with an nsIAbView interface
+var gAbView = null;
+// Holds a reference to the "abResultsTree" document element. Initially
+// set up by SetAbView.
+var gAbResultsTree = null;
+
+function SetAbView(aURI)
+{
+ // If we don't have a URI, just clear the view and leave everything else
+ // alone.
+ if (!aURI) {
+ gAbView.clearView();
+ return;
+ }
+
+ // If we do have a URI, we want to allow updating the review even if the
+ // URI is the same, as the search results may be different.
+
+ var sortColumn = kDefaultSortColumn;
+ var sortDirection = kDefaultAscending;
+
+ if (!gAbResultsTree) {
+ gAbResultsTree = document.getElementById("abResultsTree");
+ gAbResultsTree.controllers.appendController(ResultsPaneController);
+ }
+
+ if (gAbView) {
+ sortColumn = gAbView.sortColumn;
+ sortDirection = gAbView.sortDirection;
+ }
+ else {
+ if (gAbResultsTree.hasAttribute("sortCol"))
+ sortColumn = gAbResultsTree.getAttribute("sortCol");
+ var sortColumnNode = document.getElementById(sortColumn);
+ if (sortColumnNode && sortColumnNode.hasAttribute("sortDirection"))
+ sortDirection = sortColumnNode.getAttribute("sortDirection");
+ }
+
+ var directory = GetDirectoryFromURI(aURI);
+
+ if (!gAbView)
+ gAbView = Components.classes["@mozilla.org/addressbook/abview;1"]
+ .createInstance(Components.interfaces.nsIAbView);
+
+ var actualSortColumn = gAbView.setView(directory, GetAbViewListener(),
+ sortColumn, sortDirection);
+
+ gAbResultsTree.treeBoxObject.view =
+ gAbView.QueryInterface(Components.interfaces.nsITreeView);
+
+ UpdateSortIndicators(actualSortColumn, sortDirection);
+
+ // If the selected address book is LDAP and the search box is empty,
+ // inform the user of the empty results pane.
+ let abResultsTree = document.getElementById("abResultsTree");
+ let cardViewOuterBox = document.getElementById("CardViewOuterBox");
+ let blankResultsPaneMessageBox = document.getElementById("blankResultsPaneMessageBox");
+ if (aURI.startsWith("moz-abldapdirectory://") && !aURI.includes("?")) {
+ if (abResultsTree)
+ abResultsTree.hidden = true;
+ if (cardViewOuterBox)
+ cardViewOuterBox.hidden = true;
+ if (blankResultsPaneMessageBox)
+ blankResultsPaneMessageBox.hidden = false;
+ } else {
+ if (abResultsTree)
+ abResultsTree.hidden = false;
+ if (cardViewOuterBox)
+ cardViewOuterBox.hidden = false;
+ if (blankResultsPaneMessageBox)
+ blankResultsPaneMessageBox.hidden = true;
+ }
+}
+
+function CloseAbView()
+{
+ if (gAbView)
+ gAbView.clearView();
+}
+
+function GetOneOrMoreCardsSelected()
+{
+ return (gAbView && (gAbView.selection.getRangeCount() > 0));
+}
+
+function GetSelectedAddresses()
+{
+ return GetAddressesForCards(GetSelectedAbCards());
+}
+
+function GetNumSelectedCards()
+{
+ try {
+ return gAbView.selection.count;
+ }
+ catch (ex) {
+ }
+
+ // if something went wrong, return 0 for the count.
+ return 0;
+}
+
+function GetSelectedCardTypes()
+{
+ var cards = GetSelectedAbCards();
+ if (!cards) {
+ Components.utils.reportError("ERROR: GetSelectedCardTypes: |cards| is null.");
+ return kNothingSelected; // no view
+ }
+ var count = cards.length;
+ if (count == 0)
+ return kNothingSelected; // nothing selected
+
+ var mailingListCnt = 0;
+ var cardCnt = 0;
+ for (let i = 0; i < count; i++) {
+ // We can assume no values from GetSelectedAbCards will be null.
+ if (cards[i].isMailList)
+ mailingListCnt++;
+ else
+ cardCnt++;
+ }
+
+ return (mailingListCnt == 0) ? kCardsOnly :
+ (cardCnt > 0) ? kListsAndCards :
+ (mailingListCnt == 1) ? kSingleListOnly :
+ kMultipleListsOnly;
+}
+
+// NOTE, will return -1 if more than one card selected, or no cards selected.
+function GetSelectedCardIndex()
+{
+ if (!gAbView)
+ return -1;
+
+ var treeSelection = gAbView.selection;
+ if (treeSelection.getRangeCount() == 1) {
+ var start = new Object;
+ var end = new Object;
+ treeSelection.getRangeAt(0, start, end);
+ if (start.value == end.value)
+ return start.value;
+ }
+
+ return -1;
+}
+
+// NOTE, returns the card if exactly one card is selected, null otherwise
+function GetSelectedCard()
+{
+ var index = GetSelectedCardIndex();
+ return (index == -1) ? null : gAbView.getCardFromRow(index);
+}
+
+/**
+ * Return a (possibly empty) list of cards
+ *
+ * It pushes only non-null/empty element, if any, into the returned list.
+ */
+function GetSelectedAbCards()
+{
+ var abView = gAbView;
+
+ // if sidebar is open, and addressbook panel is open and focused,
+ // then use the ab view from sidebar (gCurFrame is from sidebarOverlay.js)
+ if (document.getElementById("sidebar-box")) {
+ const abPanelUrl =
+ "chrome://messenger/content/addressbook/addressbook-panel.xul";
+ if (gCurFrame &&
+ gCurFrame.getAttribute("src") == abPanelUrl &&
+ document.commandDispatcher.focusedWindow == gCurFrame.contentDocument.defaultView)
+ abView = gCurFrame.contentDocument.defaultView.gAbView;
+ }
+
+ if (!abView)
+ return [];
+
+ let cards = [];
+ var count = abView.selection.getRangeCount();
+ var current = 0;
+ for (let i = 0; i < count; ++i) {
+ let start = {};
+ let end = {};
+
+ abView.selection.getRangeAt(i, start, end);
+
+ for (let j = start.value; j <= end.value; ++j) {
+ // avoid inserting null element into the list. GetRangeAt() may be buggy.
+ let tmp = abView.getCardFromRow(j);
+ if (tmp) {
+ cards.push(tmp);
+ }
+ }
+ }
+ return cards;
+}
+
+// XXX todo
+// an optimization might be to make this return
+// the selected ranges, which would be faster
+// when the user does large selections, but for now, let's keep it simple.
+function GetSelectedRows()
+{
+ var selectedRows = "";
+
+ if (!gAbView)
+ return selectedRows;
+
+ var rangeCount = gAbView.selection.getRangeCount();
+ for (let i = 0; i < rangeCount; ++i) {
+ var start = new Object;
+ var end = new Object;
+ gAbView.selection.getRangeAt(i, start, end);
+ for (let j = start.value;j <= end.value; ++j) {
+ if (selectedRows)
+ selectedRows += ",";
+ selectedRows += j;
+ }
+ }
+
+ return selectedRows;
+}
+
+function AbSwapFirstNameLastName()
+{
+ if (gAbView)
+ gAbView.swapFirstNameLastName();
+}
+
+function AbEditSelectedCard()
+{
+ AbEditCard(GetSelectedCard());
+}
+
+function AbResultsPaneOnClick(event)
+{
+ // we only care about button 0 (left click) events
+ if (event.button != 0) return;
+
+ // all we need to worry about here is double clicks
+ // and column header clicks.
+ //
+ // we get in here for clicks on the "treecol" (headers)
+ // and the "scrollbarbutton" (scrollbar buttons)
+ // we don't want those events to cause a "double click"
+
+ var t = event.originalTarget;
+
+ if (t.localName == "treecol") {
+ var sortDirection;
+ var currentDirection = t.getAttribute("sortDirection");
+
+ // Revert the sort order. If none is set, use Ascending.
+ sortDirection = currentDirection == kDefaultAscending ?
+ kDefaultDescending : kDefaultAscending;
+
+ SortAndUpdateIndicators(t.id, sortDirection);
+ }
+ else if (t.localName == "treechildren") {
+ // figure out what row the click was in
+ var row = gAbResultsTree.treeBoxObject.getRowAt(event.clientX,
+ event.clientY);
+ if (row == -1)
+ return;
+
+ if (event.detail == 2)
+ AbResultsPaneDoubleClick(gAbView.getCardFromRow(row));
+ }
+}
+
+function AbSortAscending()
+{
+ var sortColumn = gAbResultsTree.getAttribute("sortCol");
+ SortAndUpdateIndicators(sortColumn, kDefaultAscending);
+}
+
+function AbSortDescending()
+{
+ var sortColumn = gAbResultsTree.getAttribute("sortCol");
+ SortAndUpdateIndicators(sortColumn, kDefaultDescending);
+}
+
+function SortResultPane(sortColumn)
+{
+ var sortDirection = kDefaultAscending;
+ if (gAbView)
+ sortDirection = gAbView.sortDirection;
+
+ SortAndUpdateIndicators(sortColumn, sortDirection);
+}
+
+function SortAndUpdateIndicators(sortColumn, sortDirection)
+{
+ UpdateSortIndicators(sortColumn, sortDirection);
+
+ if (gAbView)
+ gAbView.sortBy(sortColumn, sortDirection);
+}
+
+function UpdateSortIndicators(colID, sortDirection)
+{
+ var sortedColumn = null;
+
+ // set the sort indicator on the column we are sorted by
+ if (colID) {
+ sortedColumn = document.getElementById(colID);
+ if (sortedColumn) {
+ sortedColumn.setAttribute("sortDirection",sortDirection);
+ gAbResultsTree.setAttribute("sortCol", colID);
+ }
+ }
+
+ // remove the sort indicator from all the columns
+ // except the one we are sorted by
+ var currCol = gAbResultsTree.firstChild.firstChild;
+ while (currCol) {
+ if (currCol != sortedColumn && currCol.localName == "treecol")
+ currCol.removeAttribute("sortDirection");
+ currCol = currCol.nextSibling;
+ }
+}
+
+function InvalidateResultsPane()
+{
+ if (gAbResultsTree)
+ gAbResultsTree.treeBoxObject.invalidate();
+}
+
+// Controller object for Results Pane
+var ResultsPaneController =
+{
+ supportsCommand: function(command)
+ {
+ switch (command) {
+ case "cmd_selectAll":
+ case "cmd_delete":
+ case "button_delete":
+ case "cmd_printcardpreview":
+ case "cmd_printcard":
+ case "cmd_properties":
+ case "cmd_newlist":
+ case "cmd_newCard":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(command)
+ {
+ switch (command) {
+ case "cmd_selectAll":
+ return true;
+ case "cmd_delete":
+ case "button_delete":
+ var numSelected;
+ var enabled = false;
+ if (gAbView && gAbView.selection) {
+ if (gAbView.directory)
+ enabled = !gAbView.directory.readOnly;
+ numSelected = gAbView.selection.count;
+ }
+ else
+ numSelected = 0;
+
+ // fix me, don't update on isCommandEnabled
+ if (command == "cmd_delete") {
+ switch (GetSelectedCardTypes()) {
+ case kSingleListOnly:
+ goSetMenuValue(command, "valueList");
+ break;
+ case kMultipleListsOnly:
+ goSetMenuValue(command, "valueLists");
+ break;
+ case kListsAndCards:
+ goSetMenuValue(command, "valueItems");
+ break;
+ case kCardsOnly:
+ default:
+ if (numSelected < 2)
+ goSetMenuValue(command, "valueCard");
+ else
+ goSetMenuValue(command, "valueCards");
+ break;
+ }
+ }
+ return (enabled && (numSelected > 0));
+ case "cmd_printcardpreview":
+ case "cmd_printcard":
+ return (GetNumSelectedCards() > 0);
+ case "cmd_properties":
+ // Temporary fix for SeaMonkey (see bug 1318852).
+ // goSetLabelAccesskeyTooltiptext() is only defined in mail/.
+ // This will be removed in due course and therefore the
+ // block wasn't indented.
+ if (typeof goSetLabelAccesskeyTooltiptext == "function") {
+ let labelAttr = "valueGeneric";
+ let accKeyAttr = "valueGenericAccessKey";
+ let tooltipTextAttr = "valueGenericTooltipText";
+ switch (GetSelectedCardTypes()) {
+ // Set cmd_properties UI according to the type of the selected item(s),
+ // even with multiple selections for which cmd_properties is
+ // not yet available and hence disabled.
+ case kMultipleListsOnly:
+ case kSingleListOnly:
+ labelAttr = "valueMailingList";
+ accKeyAttr = "valueMailingListAccessKey";
+ tooltipTextAttr = "valueMailingListTooltipText";
+ break;
+ case kCardsOnly:
+ labelAttr = "valueContact";
+ accKeyAttr = "valueContactAccessKey";
+ tooltipTextAttr = "valueContactTooltipText";
+ break;
+ case kListsAndCards:
+ default:
+ //use generic set of attributes declared above
+ break;
+ }
+ // This code is shared between main AB and composition's contacts sidebar.
+ // Note that in composition, there's no cmd_properties-button (yet);
+ // the resulting dump() should be ignored.
+ goSetLabelAccesskeyTooltiptext("cmd_properties-button", null, null,
+ tooltipTextAttr);
+ goSetLabelAccesskeyTooltiptext("cmd_properties-contextMenu",
+ labelAttr, accKeyAttr);
+ goSetLabelAccesskeyTooltiptext("cmd_properties-menu",
+ labelAttr, accKeyAttr);
+ }
+ // While "Edit Contact" dialogue is still modal (bug 115904, bug 135126),
+ // only enable "Properties" button for single selection; then fix bug 119999.
+ return (GetNumSelectedCards() == 1);
+ case "cmd_newlist":
+ case "cmd_newCard":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(command)
+ {
+ switch (command) {
+ case "cmd_selectAll":
+ if (gAbView)
+ gAbView.selectAll();
+ break;
+ case "cmd_delete":
+ case "button_delete":
+ AbDelete();
+ break;
+ case "cmd_properties":
+ AbEditSelectedCard();
+ break;
+ case "cmd_newlist":
+ AbNewList();
+ break;
+ case "cmd_newCard":
+ AbNewCard();
+ break;
+ }
+ },
+
+ onEvent: function(event)
+ {
+ // on blur events set the menu item texts back to the normal values
+ if (event == "blur")
+ goSetMenuValue("cmd_delete", "valueDefault");
+ }
+};
+
+function SelectFirstCard()
+{
+ if (gAbView && gAbView.selection && (gAbView.selection.count > 0))
+ gAbView.selection.select(0);
+}
diff --git a/mailnews/addrbook/content/abResultsPaneOverlay.xul b/mailnews/addrbook/content/abResultsPaneOverlay.xul
new file mode 100644
index 000000000..6e6d9dbc9
--- /dev/null
+++ b/mailnews/addrbook/content/abResultsPaneOverlay.xul
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/addressbook/abResultsPane.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/addressbook/abResultsPaneOverlay.dtd">
+
+<overlay
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/javascript" src="chrome://messenger/content/addressbook/abResultsPane.js"/>
+<script type="application/javascript" src="chrome://global/content/nsDragAndDrop.js"/>
+<script type="application/javascript" src="chrome://messenger/content/addressbook/abDragDrop.js"/>
+
+<tree id="abResultsTree" flex="1" enableColumnDrag="true" class="plain focusring"
+ onclick="AbResultsPaneOnClick(event);"
+ onselect="this.view.selectionChanged(); document.commandDispatcher.updateCommands('addrbook-select');"
+ sortCol="GeneratedName"
+ persist="sortCol height">
+
+ <treecols id="abResultsTreeCols">
+ <!-- these column ids must match up to the mork column names, except for GeneratedName, see nsIAddrDatabase.idl -->
+ <treecol id="GeneratedName"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&GeneratedName.label;" primary="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="PrimaryEmail"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&PrimaryEmail.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="_AimScreenName"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&ScreenName.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="Company"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&Company.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="NickName"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&NickName.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="SecondEmail"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&SecondEmail.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="Department"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&Department.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="JobTitle"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&JobTitle.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="CellularNumber"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&CellularNumber.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="PagerNumber"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&PagerNumber.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="FaxNumber"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&FaxNumber.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="HomePhone"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&HomePhone.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="WorkPhone"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&WorkPhone.label;"/>
+
+ <!-- LOCALIZATION NOTE: _PhoneticName may be enabled for Japanese builds. -->
+ <!--
+ <splitter class="tree-splitter"/>
+ <treecol id="_PhoneticName"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&_PhoneticName.label;" hidden="true"/>
+ -->
+
+ </treecols>
+ <treechildren ondragstart="nsDragAndDrop.startDrag(event, abResultsPaneObserver);"/>
+</tree>
+
+</overlay>
+
diff --git a/mailnews/addrbook/content/addrbookWidgets.xml b/mailnews/addrbook/content/addrbookWidgets.xml
new file mode 100644
index 000000000..2481d5cd9
--- /dev/null
+++ b/mailnews/addrbook/content/addrbookWidgets.xml
@@ -0,0 +1,439 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="addrbookBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="addrbooks-menupopup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation implements="nsIAbListener">
+ <!-- A cache of nsIAbDirectory objects. -->
+ <field name="_directories">[]</field>
+
+ <!-- Represents the nsIAbDirectory attribute used as the value of the
+ parent menulist. Defaults to URI but can be e.g. dirPrefId -->
+ <field name="_value">this.getAttribute("value") || "URI"</field>
+
+ <constructor>
+ <![CDATA[
+ Components.utils.import("resource:///modules/mailServices.js");
+ // Init the address book cache.
+ const nsIAbDirectory = Components.interfaces.nsIAbDirectory;
+ let directories = MailServices.ab.directories;
+ while (directories && directories.hasMoreElements()) {
+ var ab = directories.getNext();
+ if (ab instanceof nsIAbDirectory && this._matches(ab))
+ this._directories.push(ab);
+ }
+
+ this._directories.sort(this._compare);
+
+ // Now create menuitems for all displayed directories.
+ var menulist = this.parentNode;
+ var value = this._value;
+ this._directories.forEach(function (ab) {
+ menulist.appendItem(ab.dirName, ab[value]);
+ });
+ if (this.hasAttribute("none")) {
+ // Create a dummy menuitem representing no selection.
+ this._directories.unshift(null);
+ menulist.insertItemAt(0, this.getAttribute("none"), "");
+ }
+
+ // Attempt to select the persisted or otherwise first directory.
+ menulist.value = menulist.value;
+ if (!menulist.selectedItem && this.hasChildNodes())
+ menulist.selectedIndex = 0;
+
+ const nsIAbListener = Components.interfaces.nsIAbListener;
+ // Add a listener so we can update correctly if the list should change
+ MailServices.ab.addAddressBookListener(this,
+ nsIAbListener.itemAdded |
+ nsIAbListener.directoryRemoved |
+ nsIAbListener.itemChanged);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ Components.utils.import("resource:///modules/mailServices.js");
+ MailServices.ab.removeAddressBookListener(this);
+
+ // Empty out anything in the list.
+ while (this.hasChildNodes())
+ this.lastChild.remove();
+ ]]>
+ </destructor>
+
+ <!-- nsIAbListener methods -->
+ <method name="onItemAdded">
+ <parameter name="aParentDir"/>
+ <parameter name="aItem"/>
+ <body><![CDATA[
+ // Are we interested in this new directory?
+ if (aItem instanceof Components.interfaces.nsIAbDirectory &&
+ !aItem.isMailList && this._matches(aItem)) {
+ this._directories.push(aItem);
+ this._directories.sort(this._compare);
+ // Insert the new menuitem at the position to which it was sorted.
+ this.parentNode.insertItemAt(this._directories.indexOf(aItem),
+ aItem.dirName, aItem[this._value]);
+ }
+ ]]></body>
+ </method>
+
+ <method name="onItemRemoved">
+ <parameter name="aParentDir"/>
+ <parameter name="aItem"/>
+ <body><![CDATA[
+ if (aItem instanceof Components.interfaces.nsIAbDirectory &&
+ !aItem.isMailList) {
+ // Find the item in the list to remove
+ // We can't use indexOf here because we need loose equality
+ for (var index = this._directories.length; --index >= 0; )
+ if (this._directories[index] == aItem)
+ break;
+ if (index != -1)
+ // Are we removing the selected directory?
+ if (this.parentNode.selectedItem ==
+ this.removeChild(this.childNodes[index]))
+ // If so, try to select the first directory, if available.
+ if (this.hasChildNodes())
+ this.firstChild.doCommand();
+ else
+ this.parentNode.selectedItem = null;
+ }
+ ]]></body>
+ </method>
+
+ <method name="onItemPropertyChanged">
+ <parameter name="aItem"/>
+ <parameter name="aProperty"/>
+ <parameter name="aOldValue"/>
+ <parameter name="aNewValue"/>
+ <body><![CDATA[
+ if (aItem instanceof Components.interfaces.nsIAbDirectory &&
+ !aItem.isMailList) {
+ // Find the item in the list to rename.
+ // We can't use indexOf here because we need loose equality
+ for (var oldIndex = this._directories.length; --oldIndex >= 0; )
+ if (this._directories[oldIndex] == aItem)
+ break;
+ if (oldIndex != -1) {
+ // Cache the matching item so that we can use indexOf next time.
+ aItem = this._directories[oldIndex];
+ var child = this.childNodes[oldIndex];
+ child.label = aItem.dirName;
+ this._directories.sort(this._compare);
+ // Reorder the menuitems if renaming changed the directory index.
+ var newIndex = this._directories.indexOf(aItem);
+ if (newIndex < oldIndex)
+ this.insertBefore(child, this.childNodes[newIndex]);
+ else if (newIndex > oldIndex)
+ this.insertBefore(child, this.childNodes[newIndex].nextSibling);
+ }
+ }
+ ]]></body>
+ </method>
+
+ <!-- Private methods -->
+ <!-- Tests to see whether this directory should display in the list. -->
+ <method name="_matches">
+ <parameter name="ab"/>
+ <body><![CDATA[
+ // This condition is used for instance when creating cards
+ if (this.getAttribute("writable") == "true" && ab.readOnly)
+ return false;
+
+ // This condition is used for instance when creating mailing lists
+ if (this.getAttribute("supportsmaillists") == "true" &&
+ !ab.supportsMailingLists)
+ return false;
+
+ return this.getAttribute(ab.isRemote ? "localonly" : "remoteonly") != "true";
+ ]]></body>
+ </method>
+
+ <!-- Used to sort directories in order -->
+ <method name="_compare">
+ <parameter name="a"/>
+ <parameter name="b"/>
+ <body><![CDATA[
+ // Null at the very top.
+ if (!a)
+ return -1;
+
+ if (!b)
+ return 1;
+
+ // Personal at the top.
+ const kPersonalAddressbookURI = "moz-abmdbdirectory://abook.mab";
+ if (a.URI == kPersonalAddressbookURI)
+ return -1;
+
+ if (b.URI == kPersonalAddressbookURI)
+ return 1;
+
+ // Collected at the bottom.
+ const kCollectedAddressbookURI = "moz-abmdbdirectory://history.mab";
+ if (a.URI == kCollectedAddressbookURI)
+ return 1;
+
+ if (b.URI == kCollectedAddressbookURI)
+ return -1;
+
+ // Sort books of the same type by name.
+ if (a.dirType == b.dirType)
+ return a.dirName.localeCompare(b.dirName);
+
+ // If one of the dirTypes is PAB and the other is something else,
+ // then the other will go below the one of type PAB.
+ const PABDirectory = 2;
+ if (a.dirType == PABDirectory)
+ return -1;
+
+ if (b.dirType == PABDirectory)
+ return 1;
+
+ // Sort anything else by the dir type.
+ return a.dirType - b.dirType;
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="map-list"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation>
+ <property name="mapURL" readonly="true">
+ <getter><![CDATA[
+ return this._createMapItURL();
+ ]]></getter>
+ </property>
+
+ <constructor>
+ <![CDATA[
+ this._setWidgetDisabled(true);
+ ]]>
+ </constructor>
+
+ <!--
+ Initializes the necessary address data from an addressbook card.
+ @param aCard A nsIAbCard to get the address data from.
+ @param aAddrPrefix A prefix of the card properties to use. Use "Home" or "Work".
+ -->
+ <method name="initMapAddressFromCard">
+ <parameter name="aCard"/>
+ <parameter name="aAddrPrefix"/>
+ <body><![CDATA[
+ let mapItURLFormat = this._getMapURLPref(0);
+ let doNotShowMap = !mapItURLFormat || !aAddrPrefix || !aCard;
+ this._setWidgetDisabled(doNotShowMap);
+ if (doNotShowMap)
+ return;
+
+ this.setAttribute("map_address1", aCard.getProperty(aAddrPrefix + "Address"));
+ this.setAttribute("map_address2", aCard.getProperty(aAddrPrefix + "Address2"));
+ this.setAttribute("map_city" , aCard.getProperty(aAddrPrefix + "City"));
+ this.setAttribute("map_state" , aCard.getProperty(aAddrPrefix + "State"));
+ this.setAttribute("map_zip" , aCard.getProperty(aAddrPrefix + "ZipCode"));
+ this.setAttribute("map_country" , aCard.getProperty(aAddrPrefix + "Country"));
+ ]]></body>
+ </method>
+
+ <!--
+ Initializes the necessary address data from passed in values.
+ -->
+ <method name="initMapAddress">
+ <parameter name="aAddr1"/>
+ <parameter name="aAddr2"/>
+ <parameter name="aCity"/>
+ <parameter name="aState"/>
+ <parameter name="aZip"/>
+ <parameter name="aCountry"/>
+ <body><![CDATA[
+ let mapItURLFormat = this._getMapURLPref(0);
+ let doNotShowMap = !mapItURLFormat || !(aAddr1 + aAddr2 + aCity + aState + aZip + aCountry);
+ this._setWidgetDisabled(doNotShowMap);
+ if (doNotShowMap)
+ return;
+
+ this.setAttribute("map_address1", aAddr1);
+ this.setAttribute("map_address2", aAddr2);
+ this.setAttribute("map_city" , aCity);
+ this.setAttribute("map_state" , aState);
+ this.setAttribute("map_zip" , aZip);
+ this.setAttribute("map_country" , aCountry);
+ ]]></body>
+ </method>
+
+ <!--
+ Sets the disabled/enabled state of the parent widget (e.g. a button).
+ -->
+ <method name="_setWidgetDisabled">
+ <parameter name="aDisabled"/>
+ <body><![CDATA[
+ this.parentNode.disabled = aDisabled;
+ ]]></body>
+ </method>
+
+ <!--
+ Returns the Map service URL from localized pref. Returns null if there
+ is none at the given index.
+ @param aIndex The index of the service to return. 0 is the default service.
+ -->
+ <method name="_getMapURLPref">
+ <parameter name="aIndex"/>
+ <body><![CDATA[
+ let url = null;
+ if (!aIndex) {
+ url = Services.prefs.getComplexValue("mail.addr_book.mapit_url.format",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } else {
+ try {
+ url = Services.prefs.getComplexValue("mail.addr_book.mapit_url." + aIndex + ".format",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (e) { }
+ }
+
+ return url;
+ ]]></body>
+ </method>
+
+ <!--
+ Builds menuitem elements representing map services defined in prefs
+ and attaches them to the specified button.
+ -->
+ <method name="_listMapServices">
+ <body><![CDATA[
+ let index = 1;
+ let itemFound = true;
+ let defaultFound = false;
+ const kUserIndex = 100;
+ let aMapList = this;
+ while (aMapList.hasChildNodes()) {
+ aMapList.lastChild.remove();
+ }
+
+ let defaultUrl = this._getMapURLPref(0);
+
+ // Creates the menuitem with supplied data.
+ function addMapService(aUrl, aName) {
+ let item = document.createElement("menuitem");
+ item.setAttribute("url", aUrl);
+ item.setAttribute("label", aName);
+ item.setAttribute("type", "radio");
+ item.setAttribute("name", "mapit_service");
+ if (aUrl == defaultUrl)
+ item.setAttribute("checked", "true");
+ aMapList.appendChild(item);
+ }
+
+ // Generates a useful generic name by cutting out only the host address.
+ function generateName(aUrl) {
+ return new URL(aUrl).hostname;
+ }
+
+ // Add all defined map services as menuitems.
+ while (itemFound) {
+ let urlName;
+ let urlTemplate = this._getMapURLPref(index);
+ if (!urlTemplate) {
+ itemFound = false;
+ } else {
+ // Name is not mandatory, generate one if not found.
+ try {
+ urlName = Services.prefs.getComplexValue("mail.addr_book.mapit_url." + index + ".name",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (e) {
+ urlName = generateName(urlTemplate);
+ }
+ }
+ if (itemFound) {
+ addMapService(urlTemplate, urlName);
+ index++;
+ if (urlTemplate == defaultUrl)
+ defaultFound = true;
+ } else if (index < kUserIndex) {
+ // After iterating the base region provided urls, check for user defined ones.
+ index = kUserIndex;
+ itemFound = true;
+ }
+ }
+ if (!defaultFound) {
+ // If user had put a customized map URL into mail.addr_book.mapit_url.format
+ // preserve it as a new map service named with the URL.
+ // 'index' now points to the first unused entry in prefs.
+ let defaultName = generateName(defaultUrl);
+ addMapService(defaultUrl, defaultName);
+ Services.prefs.setCharPref("mail.addr_book.mapit_url." + index + ".format",
+ defaultUrl);
+ Services.prefs.setCharPref("mail.addr_book.mapit_url." + index + ".name",
+ defaultName);
+ }
+ ]]></body>
+ </method>
+
+ <!--
+ Save user selected mapping service.
+ @param aItem The chosen menuitem with map service.
+ -->
+ <method name="_chooseMapService">
+ <parameter name="aItem"/>
+ <body><![CDATA[
+ // Save selected URL as the default.
+ let defaultUrl = Components.classes["@mozilla.org/pref-localizedstring;1"]
+ .createInstance(Components.interfaces.nsIPrefLocalizedString);
+ defaultUrl.data = aItem.getAttribute("url");
+ Services.prefs.setComplexValue("mail.addr_book.mapit_url.format",
+ Components.interfaces.nsIPrefLocalizedString, defaultUrl);
+ ]]></body>
+ </method>
+
+ <!--
+ Generate map URL in the href attribute.
+ -->
+ <method name="_createMapItURL">
+ <body><![CDATA[
+ let urlFormat = this._getMapURLPref(0);
+ if (!urlFormat)
+ return null;
+
+ let address1 = this.getAttribute("map_address1");
+ let address2 = this.getAttribute("map_address2");
+ let city = this.getAttribute("map_city");
+ let state = this.getAttribute("map_state");
+ let zip = this.getAttribute("map_zip");
+ let country = this.getAttribute("map_country");
+
+ urlFormat = urlFormat.replace("@A1", encodeURIComponent(address1));
+ urlFormat = urlFormat.replace("@A2", encodeURIComponent(address2));
+ urlFormat = urlFormat.replace("@CI", encodeURIComponent(city));
+ urlFormat = urlFormat.replace("@ST", encodeURIComponent(state));
+ urlFormat = urlFormat.replace("@ZI", encodeURIComponent(zip));
+ urlFormat = urlFormat.replace("@CO", encodeURIComponent(country));
+
+ return urlFormat;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ this._chooseMapService(event.target);
+ event.stopPropagation();
+ ]]>
+ </handler>
+ <handler event="popupshowing">
+ <![CDATA[
+ this._listMapServices();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/mailnews/addrbook/content/print.css b/mailnews/addrbook/content/print.css
new file mode 100644
index 000000000..0f6965379
--- /dev/null
+++ b/mailnews/addrbook/content/print.css
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+directory {
+ display: block;
+}
+
+section {
+ display:block;
+ margin: 15px 20px;
+}
+
+sectiontitle {
+ display:block;
+ font-weight:bold;
+ font-family:verdana;
+ font-size:small;
+}
+
+labelrow {
+ display:block;
+}
+
+label {
+ display:inline;
+ font-family:verdana;
+ font-size:small;
+}
+
+GeneratedName {
+ display:block;
+ font-weight:bold;
+ font-family:verdana;
+ margin-top: 20px;
+ margin-bottom: 3px;
+ margin-inline-end: 10px;
+ margin-inline-start: 10px;
+}
+
+FirstName, LastName,
+HomeAddress, HomeAddress2, HomeCountry,
+WorkAddress, WorkAddress2, WorkCountry,
+JobTitle, Department, Company, Notes {
+ display: block;
+ font-family: verdana;
+ font-size: small;
+}
+
+DisplayName, NickName,
+WorkPhone, HomePhone, FaxNumber, PagerNumber, CellularNumber,
+HomeCity, HomeState, HomeZipCode,
+WorkCity, WorkState, WorkZipCode,
+Custom1, Custom2, Custom3, Custom4 {
+ display: inline;
+ font-family: verdana;
+ font-size: small;
+}
+
+PrimaryEmail, SecondEmail,
+WebPage1, WebPage2 {
+ display: block;
+ font-family: verdana;
+ font-size: small;
+ text-decoration: underline;
+ color: #666666;
+}
+
+separator {
+ display: block;
+ border-bottom: 1px solid #000000;
+ margin-top: 0px;
+ margin-bottom: 0px;
+ margin-inline-end: 0px;
+ margin-inline-start: 10px;
+}
+
+table {
+ display: table;
+}
+
+tr {
+ display: table-row;
+}
+
+td {
+ display: table-cell;
+ vertical-align: top;
+}
+
+PreferMailFormat {
+ display: none;
+}
+
diff --git a/mailnews/addrbook/moz.build b/mailnews/addrbook/moz.build
new file mode 100644
index 000000000..27dcb8746
--- /dev/null
+++ b/mailnews/addrbook/moz.build
@@ -0,0 +1,9 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'public',
+ 'src',
+]
diff --git a/mailnews/addrbook/prefs/content/pref-directory-add.js b/mailnews/addrbook/prefs/content/pref-directory-add.js
new file mode 100644
index 000000000..011b8aed9
--- /dev/null
+++ b/mailnews/addrbook/prefs/content/pref-directory-add.js
@@ -0,0 +1,394 @@
+/* -*- Mode: Java; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+
+var gCurrentDirectory = null;
+var gReplicationBundle = null;
+var gReplicationService =
+ Components.classes["@mozilla.org/addressbook/ldap-replication-service;1"].
+ getService(Components.interfaces.nsIAbLDAPReplicationService);
+var gReplicationCancelled = false;
+var gProgressText;
+var gProgressMeter;
+var gDownloadInProgress = false;
+
+var kDefaultMaxHits = 100;
+var kDefaultLDAPPort = 389;
+var kDefaultSecureLDAPPort = 636;
+var kLDAPDirectory = 0; // defined in nsDirPrefs.h
+
+var ldapOfflineObserver = {
+ observe: function(subject, topic, state)
+ {
+ // sanity checks
+ if (topic != "network:offline-status-changed") return;
+ setDownloadOfflineOnlineState(state == "offline");
+ }
+}
+
+function Startup()
+{
+ gReplicationBundle = document.getElementById("bundle_replication");
+
+ document.getElementById("download").label =
+ gReplicationBundle.getString("downloadButton");
+ document.getElementById("download").accessKey =
+ gReplicationBundle.getString("downloadButton.accesskey");
+
+ if ("arguments" in window && window.arguments[0]) {
+ gCurrentDirectory = window.arguments[0].selectedDirectory;
+ try {
+ fillSettings();
+ } catch (ex) {
+ dump("pref-directory-add.js:Startup(): fillSettings() exception: "
+ + ex + "\n");
+ }
+
+ let oldListName = gCurrentDirectory.dirName;
+ document.title = gReplicationBundle.getFormattedString("directoryTitleEdit", [oldListName]);
+
+ // Only set up the download button for online/offline status toggling
+ // if the pref isn't locked to disable the button.
+ if (!Services.prefs.prefIsLocked(gCurrentDirectory.dirPrefId +
+ ".disable_button_download")) {
+ // Now connect to the offline/online observer
+ Services.obs.addObserver(ldapOfflineObserver,
+ "network:offline-status-changed", false);
+
+ // Now set the initial offline/online state and update the state
+ setDownloadOfflineOnlineState(Services.io.offline);
+ }
+ } else {
+ document.title = gReplicationBundle.getString("directoryTitleNew");
+ fillDefaultSettings();
+ // Don't add observer here as it doesn't make any sense.
+ }
+}
+
+function onUnload()
+{
+ if ("arguments" in window &&
+ window.arguments[0] &&
+ !Services.prefs.prefIsLocked(gCurrentDirectory.dirPrefId +
+ ".disable_button_download")) {
+ // Remove the observer that we put in on dialog startup
+ Services.obs.removeObserver(ldapOfflineObserver,
+ "network:offline-status-changed");
+ }
+}
+
+var progressListener = {
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_START) {
+ // start the spinning
+ gProgressMeter.setAttribute("mode", "undetermined");
+ gProgressText.value = gReplicationBundle.getString(aStatus ?
+ "replicationStarted" :
+ "changesStarted");
+ gDownloadInProgress = true;
+ document.getElementById("download").label =
+ gReplicationBundle.getString("cancelDownloadButton");
+ document.getElementById("download").accessKey =
+ gReplicationBundle.getString("cancelDownloadButton.accesskey");
+ }
+
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) {
+ EndDownload(aStatus);
+ }
+ },
+ onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
+ {
+ gProgressText.value = gReplicationBundle.getFormattedString("currentCount",
+ [aCurSelfProgress]);
+ },
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ },
+ onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ },
+ onSecurityChange: function(aWebProgress, aRequest, state)
+ {
+ },
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Components.interfaces.nsIWebProgressListener) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+};
+
+function DownloadNow()
+{
+ if (!gDownloadInProgress) {
+ gProgressText = document.getElementById("replicationProgressText");
+ gProgressMeter = document.getElementById("replicationProgressMeter");
+
+ gProgressText.hidden = false;
+ gProgressMeter.hidden = false;
+ gReplicationCancelled = false;
+
+ try {
+ if (gCurrentDirectory instanceof Components.interfaces.nsIAbLDAPDirectory)
+ gReplicationService.startReplication(gCurrentDirectory,
+ progressListener);
+ else
+ EndDownload(false);
+ }
+ catch (ex) {
+ EndDownload(false);
+ }
+ } else {
+ gReplicationCancelled = true;
+ try {
+ gReplicationService.cancelReplication(gCurrentDirectory.dirPrefId);
+ }
+ catch (ex) {
+ // XXX todo
+ // perhaps replication hasn't started yet? This can happen if you hit cancel after attempting to replication when offline
+ dump("unexpected failure while cancelling. ex=" + ex + "\n");
+ }
+ }
+}
+
+function EndDownload(aStatus)
+{
+ document.getElementById("download").label =
+ gReplicationBundle.getString("downloadButton");
+ document.getElementById("download").accessKey =
+ gReplicationBundle.getString("downloadButton.accesskey");
+
+ // stop the spinning
+ gProgressMeter.setAttribute("mode", "normal");
+ gProgressMeter.setAttribute("value", "100");
+ gProgressMeter.hidden = true;
+
+ gDownloadInProgress = false;
+ gProgressText.value =
+ gReplicationBundle.getString(aStatus ? "replicationSucceeded" :
+ gReplicationCancelled ? "replicationCancelled" :
+ "replicationFailed");
+}
+
+// fill the settings panel with the data from the preferences.
+//
+function fillSettings()
+{
+ document.getElementById("description").value = gCurrentDirectory.dirName;
+
+ if (gCurrentDirectory instanceof Components.interfaces.nsIAbLDAPDirectory) {
+ var ldapUrl = gCurrentDirectory.lDAPURL;
+
+ document.getElementById("results").value = gCurrentDirectory.maxHits;
+ document.getElementById("login").value = gCurrentDirectory.authDn;
+ document.getElementById("hostname").value = ldapUrl.host;
+ document.getElementById("basedn").value = ldapUrl.dn;
+ document.getElementById("search").value = ldapUrl.filter;
+
+ var sub = document.getElementById("sub");
+ switch(ldapUrl.scope) {
+ case Components.interfaces.nsILDAPURL.SCOPE_ONELEVEL:
+ sub.radioGroup.selectedItem = document.getElementById("one");
+ break;
+ default:
+ sub.radioGroup.selectedItem = sub;
+ break;
+ }
+
+ var sasl = document.getElementById("saslMechanism");
+ switch (gCurrentDirectory.saslMechanism) {
+ case "GSSAPI":
+ sasl.selectedItem = document.getElementById("GSSAPI");
+ break;
+ default:
+ sasl.selectedItem = document.getElementById("Simple");
+ break;
+ }
+
+ var secure = ldapUrl.options & ldapUrl.OPT_SECURE
+ if (secure)
+ document.getElementById("secure").setAttribute("checked", "true");
+
+ if (ldapUrl.port == -1)
+ document.getElementById("port").value =
+ (secure ? kDefaultSecureLDAPPort : kDefaultLDAPPort);
+ else
+ document.getElementById("port").value = ldapUrl.port;
+ }
+
+ // check if any of the preferences for this server are locked.
+ //If they are locked disable them
+ DisableUriFields(gCurrentDirectory.dirPrefId + ".uri");
+ DisableElementIfPrefIsLocked(gCurrentDirectory.dirPrefId + ".description", "description");
+ DisableElementIfPrefIsLocked(gCurrentDirectory.dirPrefId + ".disable_button_download", "download");
+ DisableElementIfPrefIsLocked(gCurrentDirectory.dirPrefId + ".maxHits", "results");
+ DisableElementIfPrefIsLocked(gCurrentDirectory.dirPrefId + ".auth.dn", "login");
+}
+
+function DisableElementIfPrefIsLocked(aPrefName, aElementId)
+{
+ if (Services.prefs.prefIsLocked(aPrefName))
+ document.getElementById(aElementId).setAttribute('disabled', true);
+}
+
+// disables all the text fields corresponding to the .uri pref.
+function DisableUriFields(aPrefName)
+{
+ if (Services.prefs.prefIsLocked(aPrefName)) {
+ let lockedElements = document.querySelectorAll('[disableiflocked="true"]');
+ for (let i = 0; i < lockedElements.length; i++)
+ lockedElements[i].setAttribute('disabled', 'true');
+ }
+}
+
+function onSecure()
+{
+ document.getElementById("port").value =
+ document.getElementById("secure").checked ? kDefaultSecureLDAPPort :
+ kDefaultLDAPPort;
+}
+
+function fillDefaultSettings()
+{
+ document.getElementById("port").value = kDefaultLDAPPort;
+ document.getElementById("results").value = kDefaultMaxHits;
+ var sub = document.getElementById("sub");
+ sub.radioGroup.selectedItem = sub;
+
+ // Disable the download button and add some text indicating why.
+ document.getElementById("download").disabled = true;
+ document.getElementById("downloadWarningMsg").hidden = false;
+ document.getElementById("downloadWarningMsg").textContent = document.
+ getElementById("bundle_addressBook").
+ getString("abReplicationSaveSettings");
+}
+
+function hasCharacters(number)
+{
+ var re = /[0-9]/g;
+ var num = number.match(re);
+ if(num && (num.length == number.length))
+ return false;
+ else
+ return true;
+}
+
+function onAccept()
+{
+ try {
+ var pref_string_content = "";
+ var pref_string_title = "";
+
+ var description = document.getElementById("description").value;
+ var hostname = cleanUpHostName(document.getElementById("hostname").value);
+ var port = document.getElementById("port").value;
+ var secure = document.getElementById("secure");
+ var results = document.getElementById("results").value;
+ var errorValue = null;
+ var saslMechanism = "";
+ if ((!description) || (description.trim() == ""))
+ errorValue = "invalidName";
+ else if (!isLegalHostNameOrIP(hostname))
+ errorValue = "invalidHostname";
+ // XXX write isValidDn and call it on the dn string here?
+ else if (port && hasCharacters(port))
+ errorValue = "invalidPortNumber";
+ else if (results && hasCharacters(results))
+ errorValue = "invalidResults";
+ if (!errorValue) {
+ // XXX Due to the LDAP c-sdk pass a dummy url to the IO service, then
+ // update the parts (bug 473351).
+ let ldapUrl = Services.io.newURI(
+ (secure.checked ? "ldaps://" : "ldap://") + "localhost/dc=???", null, null)
+ .QueryInterface(Components.interfaces.nsILDAPURL);
+
+ ldapUrl.host = hostname;
+ ldapUrl.port = port ? port :
+ (secure.checked ? kDefaultSecureLDAPPort :
+ kDefaultLDAPPort);
+ ldapUrl.dn = document.getElementById("basedn").value;
+ ldapUrl.scope = document.getElementById("one").selected ?
+ Components.interfaces.nsILDAPURL.SCOPE_ONELEVEL :
+ Components.interfaces.nsILDAPURL.SCOPE_SUBTREE;
+
+ ldapUrl.filter = document.getElementById("search").value;
+ if (document.getElementById("GSSAPI").selected) {
+ saslMechanism = "GSSAPI";
+ }
+
+ // check if we are modifying an existing directory or adding a new directory
+ if (gCurrentDirectory) {
+ gCurrentDirectory.dirName = description;
+ gCurrentDirectory.lDAPURL = ldapUrl.QueryInterface(Components.interfaces.nsILDAPURL);
+ window.opener.gNewServerString = gCurrentDirectory.dirPrefId;
+ }
+ else { // adding a new directory
+ window.opener.gNewServerString =
+ MailServices.ab.newAddressBook(description, ldapUrl.spec, kLDAPDirectory);
+ }
+
+ // XXX This is really annoying - both new/modify Address Book don't
+ // give us back the new directory we just created - so go find it from
+ // rdf so we can set a few final things up on it.
+ var targetURI = "moz-abldapdirectory://" + window.opener.gNewServerString;
+ var theDirectory =
+ MailServices.ab.getDirectory(targetURI)
+ .QueryInterface(Components.interfaces.nsIAbLDAPDirectory);
+
+ theDirectory.maxHits = results;
+ theDirectory.authDn = document.getElementById("login").value;
+ theDirectory.saslMechanism = saslMechanism;
+
+ window.opener.gNewServer = description;
+ // set window.opener.gUpdate to true so that LDAP Directory Servers
+ // dialog gets updated
+ window.opener.gUpdate = true;
+ } else {
+ var addressBookBundle = document.getElementById("bundle_addressBook");
+
+ Services.prompt.alert(window,
+ document.title,
+ addressBookBundle.getString(errorValue));
+ return false;
+ }
+ } catch (outer) {
+ dump("Internal error in pref-directory-add.js:onAccept() " + outer + "\n");
+ }
+ return true;
+}
+
+function onCancel()
+{
+ window.opener.gUpdate = false;
+}
+
+
+// called by Help button in platform overlay
+function doHelpButton()
+{
+ openHelp("mail-ldap-properties");
+}
+
+// Sets the download button state for offline or online.
+// This function should only be called for ldap edit dialogs.
+function setDownloadOfflineOnlineState(isOffline)
+{
+ if (isOffline)
+ {
+ // Disable the download button and add some text indicating why.
+ document.getElementById("downloadWarningMsg").textContent = document.
+ getElementById("bundle_addressBook").
+ getString("abReplicationOfflineWarning");
+ }
+ document.getElementById("downloadWarningMsg").hidden = !isOffline;
+ document.getElementById("download").disabled = isOffline;
+}
diff --git a/mailnews/addrbook/prefs/content/pref-directory-add.xul b/mailnews/addrbook/prefs/content/pref-directory-add.xul
new file mode 100644
index 000000000..33fe0cd31
--- /dev/null
+++ b/mailnews/addrbook/prefs/content/pref-directory-add.xul
@@ -0,0 +1,152 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/addressbook/pref-directory-add.dtd">
+
+<dialog id="addDirectory"
+ style="width: &newDirectoryWidth;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="Startup();"
+ onunload="onUnload();"
+ buttons="accept,cancel"
+ ondialogaccept="return onAccept();"
+ ondialogcancel="return onCancel();">
+
+ <script type="application/javascript" src="chrome://messenger/content/addressbook/pref-directory-add.js"/>
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+ <stringbundle id="bundle_replication" src="chrome://messenger/locale/addressbook/replicationProgress.properties"/>
+
+ <keyset id="keyset"/>
+ <vbox id="editDirectory">
+
+ <tabbox style="margin:5px">
+ <tabs id="directoryTabBox">
+ <tab label="&General.tab;"/>
+ <tab label="&Offline.tab;"/>
+ <tab label="&Advanced.tab;"/>
+ </tabs>
+
+ <tabpanels id="directoryTabPanels" flex="1">
+ <vbox>
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ <column/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <label value="&directoryName.label;" accesskey="&directoryName.accesskey;"
+ control="description"/>
+ <textbox id="description" flex="1"/>
+ <spacer flex="1"/>
+ </row>
+ <row align="center">
+ <label value="&directoryHostname.label;" accesskey="&directoryHostname.accesskey;"
+ control="hostname"/>
+ <textbox id="hostname" flex="1" disableiflocked="true" class="uri-element"/>
+ <spacer flex="1"/>
+ </row>
+ <row align="center">
+ <label value="&directoryBaseDN.label;"
+ accesskey="&directoryBaseDN.accesskey;"
+ control="basedn"/>
+ <vbox>
+ <textbox id="basedn" disableiflocked="true" class="uri-element"/>
+ </vbox>
+ <button label="&findButton.label;"
+ accesskey="&findButton.accesskey;" disabled="true"/>
+ </row>
+ <row align="center">
+ <label value="&portNumber.label;"
+ accesskey="&portNumber.accesskey;"
+ control="port"/>
+ <hbox>
+ <textbox id="port" type="number" size="5" min="1"
+ max="65535" hidespinbuttons="true"
+ disableiflocked="true"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label value="&directoryLogin.label;"
+ accesskey="&directoryLogin.accesskey;"
+ control="login"/>
+ <textbox id="login" flex="1" class="uri-element"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ <checkbox id="secure" label="&directorySecure.label;"
+ accesskey="&directorySecure.accesskey;"
+ oncommand="onSecure();" disableiflocked="true"/>
+ </vbox>
+ <vbox>
+ <description>&offlineText.label;</description>
+ <separator/>
+ <hbox>
+ <button id="download" oncommand="DownloadNow();"/>
+ <spacer flex="1"/>
+ </hbox>
+ <description id="downloadWarningMsg" hidden="true" class="error"/>
+ <description id="replicationProgressText" hidden="true"/>
+
+ <progressmeter id="replicationProgressMeter" mode="normal" value="0" hidden="true"/>
+ </vbox>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <label value="&return.label;"
+ accesskey="&return.accesskey;"
+ control="results"/>
+ <hbox align="center">
+ <textbox id="results" type="number" size="10" min="1"
+ max="2147483647" increment="10"/>
+ <label value="&results.label;"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label value="&scope.label;" control="scope" accesskey="&scope.accesskey;"/>
+ <radiogroup id="scope" orient="horizontal">
+ <radio id="one" value="1" label="&scopeOneLevel.label;"
+ disableiflocked="true" accesskey="&scopeOneLevel.accesskey;"/>
+ <radio id="sub" value="2" label="&scopeSubtree.label;"
+ disableiflocked="true" accesskey="&scopeSubtree.accesskey;"/>
+ </radiogroup>
+ </row>
+ <row>
+ <label value="&searchFilter.label;"
+ accesskey="&searchFilter.accesskey;"
+ control="search"/>
+ <textbox id="search" multiline="true" flex="1" disableiflocked="true"/>
+ </row>
+ <row align="center">
+ <label value="&saslMechanism.label;" control="saslMechanism" accesskey="&saslMechanism.accesskey;"/>
+ <menulist id="saslMechanism">
+ <menupopup>
+ <menuitem id="Simple" value=""
+ label="&saslOff.label;"
+ accesskey="&saslOff.accesskey;"/>
+ <menuitem id="GSSAPI" value="GSSAPI"
+ label="&saslGSSAPI.label;"
+ accesskey="&saslGSSAPI.accesskey;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </tabpanels>
+ </tabbox>
+ </vbox>
+
+</dialog>
+
diff --git a/mailnews/addrbook/prefs/content/pref-editdirectories.js b/mailnews/addrbook/prefs/content/pref-editdirectories.js
new file mode 100644
index 000000000..dfb3e0559
--- /dev/null
+++ b/mailnews/addrbook/prefs/content/pref-editdirectories.js
@@ -0,0 +1,142 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+// Listener to refresh the list items if something changes. In all these
+// cases we just rebuild the list as it is easier than searching/adding in the
+// correct places an would be an infrequent operation.
+var gAddressBookAbListener = {
+ onItemAdded: function(parentDir, item) {
+ if (item instanceof Components.interfaces.nsIAbDirectory) {
+ fillDirectoryList();
+ }
+ },
+ onItemRemoved: function(parentDir, item) {
+ if (item instanceof Components.interfaces.nsIAbDirectory) {
+ fillDirectoryList();
+ }
+ },
+ onItemPropertyChanged: function(item, property, oldValue, newValue) {
+ if (item instanceof Components.interfaces.nsIAbDirectory) {
+ fillDirectoryList();
+ }
+ }
+};
+
+function onInitEditDirectories()
+{
+ // For AbDeleteDirectory in abCommon.js
+ gAddressBookBundle = document.getElementById("bundle_addressBook");
+
+ // If the pref is locked disable the "Add" button
+ if (Services.prefs.prefIsLocked("ldap_2.disable_button_add"))
+ document.getElementById("addButton").setAttribute('disabled', true);
+
+ // Fill out the directory list
+ fillDirectoryList();
+
+ const nsIAbListener = Components.interfaces.nsIAbListener;
+ // Add a listener so we can update correctly if the list should change
+ MailServices.ab.addAddressBookListener(gAddressBookAbListener,
+ nsIAbListener.itemAdded |
+ nsIAbListener.directoryRemoved |
+ nsIAbListener.itemChanged);
+}
+
+function onUninitEditDirectories()
+{
+ MailServices.ab.removeAddressBookListener(gAddressBookAbListener);
+}
+
+function fillDirectoryList()
+{
+ var abList = document.getElementById("directoriesList");
+
+ // Empty out anything in the list
+ while (abList.hasChildNodes())
+ abList.lastChild.remove();
+
+ // Init the address book list
+ let directories = MailServices.ab.directories;
+ let holdingArray = [];
+ while (directories && directories.hasMoreElements()) {
+ let ab = directories.getNext();
+ if (ab instanceof Components.interfaces.nsIAbDirectory && ab.isRemote)
+ holdingArray.push(ab);
+ }
+
+ holdingArray.sort(function (a, b) { return a.dirName.localeCompare(b.dirName); });
+
+ holdingArray.forEach(function (ab) {
+ var item = document.createElement('listitem');
+ item.setAttribute("label", ab.dirName);
+ item.setAttribute("value", ab.URI);
+
+ abList.appendChild(item);
+ });
+}
+
+function selectDirectory()
+{
+ var abList = document.getElementById("directoriesList");
+ var editButton = document.getElementById("editButton");
+ var removeButton = document.getElementById("removeButton");
+
+ if (abList && abList.selectedItem) {
+ editButton.removeAttribute("disabled");
+
+ // If the disable delete button pref for the selected directory is set,
+ // disable the delete button for that directory.
+ let disable = false;
+ let ab = MailServices.ab.getDirectory(abList.value);
+ try {
+ disable = Services.prefs.getBoolPref(ab.dirPrefId + ".disable_delete");
+ }
+ catch(ex){
+ // If this preference is not set, it's ok.
+ }
+ if (disable)
+ removeButton.setAttribute("disabled", true);
+ else
+ removeButton.removeAttribute("disabled");
+ }
+ else {
+ editButton.setAttribute("disabled", true);
+ removeButton.setAttribute("disabled", true);
+ }
+}
+
+function dblClickDirectory(event)
+{
+ // We only care about left click events.
+ if (event.button != 0)
+ return;
+
+ editDirectory();
+}
+
+function editDirectory()
+{
+ var abList = document.getElementById("directoriesList");
+
+ if (abList && abList.selectedItem) {
+ let abURI = abList.value;
+ let ab = MailServices.ab.getDirectory(abURI);
+
+ window.openDialog(ab.propertiesChromeURI, "editDirectory",
+ "chrome,modal=yes,resizable=no",
+ { selectedDirectory: ab });
+ }
+}
+
+function removeDirectory()
+{
+ var abList = document.getElementById("directoriesList");
+
+ if (abList && abList.selectedItem)
+ AbDeleteDirectory(abList.value);
+}
diff --git a/mailnews/addrbook/prefs/content/pref-editdirectories.xul b/mailnews/addrbook/prefs/content/pref-editdirectories.xul
new file mode 100644
index 000000000..43ca17a95
--- /dev/null
+++ b/mailnews/addrbook/prefs/content/pref-editdirectories.xul
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/addressbook/pref-directory.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ id="editDirectories"
+ title="&pref.ldap.window.title;"
+ buttons="accept"
+ onload="onInitEditDirectories();"
+ onunload="onUninitEditDirectories();">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/addressbook/abCommon.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/addressbook/pref-editdirectories.js"/>
+
+ <stringbundle id="bundle_addressBook"
+ src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+
+ <label value="&directoriesText.label;"
+ accesskey="&directoriesText.accesskey;" control="directoriesList"/>
+ <hbox flex="1">
+ <listbox id="directoriesList" flex="1" onselect="selectDirectory();"
+ ondblclick="dblClickDirectory(event);"/>
+ <vbox>
+ <button id="addButton" label="&addDirectory.label;"
+ accesskey="&addDirectory.accesskey;"
+ oncommand="AbNewLDAPDirectory();"/>
+ <button id="editButton" label="&editDirectory.label;"
+ accesskey="&editDirectory.accesskey;" disabled="true"
+ oncommand="editDirectory();"/>
+ <button id="removeButton" label="&deleteDirectory.label;"
+ accesskey="&deleteDirectory.accesskey;" disabled="true"
+ oncommand="removeDirectory();"/>
+ </vbox>
+ </hbox>
+</dialog>
diff --git a/mailnews/addrbook/public/moz.build b/mailnews/addrbook/public/moz.build
new file mode 100644
index 000000000..5925ccc8a
--- /dev/null
+++ b/mailnews/addrbook/public/moz.build
@@ -0,0 +1,47 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIAbAddressCollector.idl',
+ 'nsIAbAutoCompleteResult.idl',
+ 'nsIAbBooleanExpression.idl',
+ 'nsIAbCard.idl',
+ 'nsIAbCollection.idl',
+ 'nsIAbDirectory.idl',
+ 'nsIAbDirectoryQuery.idl',
+ 'nsIAbDirectoryQueryProxy.idl',
+ 'nsIAbDirectorySearch.idl',
+ 'nsIAbDirFactory.idl',
+ 'nsIAbDirFactoryService.idl',
+ 'nsIAbDirSearchListener.idl',
+ 'nsIAbItem.idl',
+ 'nsIAbLDAPAttributeMap.idl',
+ 'nsIAbLDIFService.idl',
+ 'nsIAbListener.idl',
+ 'nsIAbManager.idl',
+ 'nsIAbMDBDirectory.idl',
+ 'nsIAbView.idl',
+ 'nsIAddbookUrl.idl',
+ 'nsIAddrDatabase.idl',
+ 'nsIAddrDBAnnouncer.idl',
+ 'nsIAddrDBListener.idl',
+ 'nsIMsgVCardService.idl',
+]
+
+if CONFIG['MOZ_LDAP_XPCOM']:
+ XPIDL_SOURCES += [
+ 'nsIAbLDAPCard.idl',
+ 'nsIAbLDAPDirectory.idl',
+ 'nsIAbLDAPReplicationData.idl',
+ 'nsIAbLDAPReplicationQuery.idl',
+ 'nsIAbLDAPReplicationService.idl',
+ ]
+
+XPIDL_MODULE = 'addrbook'
+
+EXPORTS += [
+ 'nsAbBaseCID.h',
+]
+
diff --git a/mailnews/addrbook/public/nsAbBaseCID.h b/mailnews/addrbook/public/nsAbBaseCID.h
new file mode 100644
index 000000000..0dcc630c5
--- /dev/null
+++ b/mailnews/addrbook/public/nsAbBaseCID.h
@@ -0,0 +1,445 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbBaseCID_h__
+#define nsAbBaseCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+//
+// The start of the contract ID for address book directory factories.
+//
+#define NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX \
+ "@mozilla.org/addressbook/directory-factory;1?name="
+
+//
+// The start of the contract ID for address book directory types
+//
+#define NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX \
+ "@mozilla.org/addressbook/directory;1?type="
+
+//
+// nsAbManager
+//
+#define NS_ABMANAGER_CONTRACTID \
+ "@mozilla.org/abmanager;1"
+
+#define NS_ABMANAGERSTARTUPHANDLER_CONTRACTID \
+ "@mozilla.org/commandlinehandler/general-startup;1?type=addressbook"
+
+#define NS_ABMANAGER_CID \
+{ /* {ad81b321-8a8a-42ca-a508-fe659de84586} */ \
+ 0xad81b321, 0x8a8a, 0x42ca, \
+ {0xa5, 0x08, 0xfe, 0x65, 0x9d, 0x8e, 0x45, 0x86} \
+}
+
+//
+// nsAbContentHandler
+//
+#define NS_ABCONTENTHANDLER_CID \
+{ /* {a72ad552-0484-4b5f-8d45-2d79158d22e3} */ \
+ 0xa72ad552, 0x0484, 0x4b5f, \
+ {0x8d, 0x45, 0x2d, 0x79, 0x15, 0x8d, 0x22, 0xe3} \
+}
+
+
+//
+// nsAbBSDirectory - the root address book
+//
+#define NS_ABDIRECTORY_CONTRACTID \
+ NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX "moz-abdirectory"
+
+#define NS_ABDIRECTORY_CID \
+{ /* {012D3C24-1DD2-11B2-BA79-B4AD359FC461}*/ \
+ 0x012D3C24, 0x1DD2, 0x11B2, \
+ {0xBA, 0x79, 0xB4, 0xAD, 0x35, 0x9F, 0xC4, 0x61} \
+}
+
+
+//
+// nsAddressBookDB
+//
+#define NS_ADDRDATABASE_CONTRACTID \
+ "@mozilla.org/addressbook/carddatabase;1"
+
+#define NS_ADDRDATABASE_CID \
+{ /* 63187917-1D19-11d3-A302-001083003D0C */ \
+ 0x63187917, 0x1d19, 0x11d3, \
+ {0xa3, 0x2, 0x0, 0x10, 0x83, 0x0, 0x3d, 0xc} \
+}
+
+//
+// nsAbCardProperty
+//
+#define NS_ABCARDPROPERTY_CONTRACTID \
+ "@mozilla.org/addressbook/cardproperty;1"
+#define NS_ABCARDPROPERTY_CID \
+{ /* 2B722171-2CEA-11d3-9E0B-00A0C92B5F0D */ \
+ 0x2b722171, 0x2cea, 0x11d3, \
+ {0x9e, 0xb, 0x0, 0xa0, 0xc9, 0x2b, 0x5f, 0xd} \
+}
+
+//
+// nsAbDirProperty
+//
+#define NS_ABDIRPROPERTY_CONTRACTID \
+ "@mozilla.org/addressbook/directoryproperty;1"
+#define NS_ABDIRPROPERTY_CID \
+{ /* 6FD8EC67-3965-11d3-A316-001083003D0C */ \
+ 0x6fd8ec67, 0x3965, 0x11d3, \
+ {0xa3, 0x16, 0x0, 0x10, 0x83, 0x0, 0x3d, 0xc} \
+}
+
+//
+// nsAbDirectoryProperties
+//
+#define NS_ABDIRECTORYPROPERTIES_CONTRACTID \
+ "@mozilla.org/addressbook/properties;1"
+#define NS_ABDIRECTORYPROPERTIES_CID \
+{ /* 8b00a972-1dd2-11b2-9d9c-9c377a9c3dba */ \
+ 0x8b00a972, 0x1dd2, 0x11b2, \
+ {0x9d, 0x9c, 0x9c, 0x37, 0x7a, 0x9c, 0x3d, 0xba} \
+}
+
+//
+// nsAbAddressCollector
+//
+#define NS_ABADDRESSCOLLECTOR_CONTRACTID \
+ "@mozilla.org/addressbook/services/addressCollector;1"
+#define NS_ABADDRESSCOLLECTOR_CID \
+{ /* e7702d5a-99d8-4648-bab7-919ea29f30b6 */ \
+ 0xe7702d5a, 0x99d8, 0x4648, \
+ {0xba, 0xb7, 0x91, 0x9e, 0xa2, 0x9f, 0x30, 0xb6} \
+}
+
+//
+// addbook URL
+//
+#define NS_ADDBOOKURL_CONTRACTID \
+ "@mozilla.org/addressbook/services/url;1?type=addbook"
+
+#define NS_ADDBOOKURL_CID \
+{ /* ff04c8e6-501e-11d3-a527-0060b0fc0444 */ \
+ 0xff04c8e6, 0x501e, 0x11d3, \
+ {0xa5, 0x27, 0x0, 0x60, 0xb0, 0xfc, 0x4, 0x44} \
+}
+
+//
+// addbook Protocol Handler
+//
+#define NS_ADDBOOK_HANDLER_CONTRACTID \
+ "@mozilla.org/addressbook/services/addbook;1"
+#define NS_ADDBOOK_HANDLER_CID \
+{ /* ff04c8e6-501e-11d3-ffcc-0060b0fc0444 */ \
+ 0xff04c8e6, 0x501e, 0x11d3, \
+ {0xff, 0xcc, 0x0, 0x60, 0xb0, 0xfc, 0x4, 0x44} \
+}
+
+//
+// directory factory service
+//
+#define NS_ABDIRFACTORYSERVICE_CONTRACTID \
+ "@mozilla.org/addressbook/directory-factory-service;1"
+
+#define NS_ABDIRFACTORYSERVICE_CID \
+{ /* {F8B212F2-742B-4A48-B7A0-4C44D4DDB121}*/ \
+ 0xF8B212F2, 0x742B, 0x4A48, \
+ {0xB7, 0xA0, 0x4C, 0x44, 0xD4, 0xDD, 0xB1, 0x21} \
+}
+
+//
+// mdb directory factory
+//
+#define NS_ABMDBDIRECTORY "moz-abmdbdirectory"
+
+#define NS_ABMDBDIRFACTORY_CONTRACTID \
+ NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX NS_ABMDBDIRECTORY
+
+#define NS_ABMDBDIRFACTORY_CID \
+{ /* {E1CB9C8A-722D-43E4-9D7B-7CCAE4B0338A}*/ \
+ 0xE1CB9C8A, 0x722D, 0x43E4, \
+ {0x9D, 0x7B, 0x7C, 0xCA, 0xE4, 0xB0, 0x33, 0x8A} \
+}
+
+//
+// nsAbMDBDirectory
+//
+#define NS_ABMDBDIRECTORY_CONTRACTID \
+ NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX NS_ABMDBDIRECTORY
+
+#define NS_ABMDBDIRECTORY_CID \
+{ /* {e618f894-1dd1-11b2-889c-9aaefaa90dde}*/ \
+ 0xe618f894, 0x1dd1, 0x11b2, \
+ {0x88, 0x9c, 0x9a, 0xae, 0xfa, 0xa9, 0x0d, 0xde} \
+}
+
+//
+// nsAbMDBCard
+//
+#define NS_ABMDBCARD_CONTRACTID \
+ "@mozilla.org/addressbook/moz-abmdbcard;1"
+
+#define NS_ABMDBCARD_CID \
+{ /* {f578a5d2-1dd1-11b2-8841-f45cc5e765f8} */ \
+ 0xf578a5d2, 0x1dd1, 0x11b2, \
+ {0x88, 0x41, 0xf4, 0x5c, 0xc5, 0xe7, 0x65, 0xf8} \
+}
+
+#ifdef XP_WIN
+//
+// nsAbOutlookDirectory
+//
+#define NS_ABOUTLOOKDIRECTORY_CONTRACTID \
+ NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX "moz-aboutlookdirectory"
+
+#define NS_ABOUTLOOKDIRECTORY_CID \
+{ /* {9cc57822-0599-4c47-a399-1c6fa185a05c}*/ \
+ 0x9cc57822, 0x0599, 0x4c47, \
+ {0xa3, 0x99, 0x1c, 0x6f, 0xa1, 0x85, 0xa0, 0x5c} \
+}
+
+//
+// Outlook directory factory
+//
+#define NS_ABOUTLOOKDIRFACTORY_CONTRACTID \
+ NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX "moz-aboutlookdirectory"
+
+#define NS_ABOUTLOOKDIRFACTORY_CID \
+{ /* {558ccc0f-2681-4dac-a066-debd8d26faf6}*/ \
+ 0x558ccc0f, 0x2681, 0x4dac, \
+ {0xa0, 0x66, 0xde, 0xbd, 0x8d, 0x26, 0xfa, 0xf6} \
+}
+#endif
+
+//
+// Addressbook Query support
+//
+
+#define NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID \
+ "@mozilla.org/addressbook/directory/query-arguments;1"
+
+#define NS_ABDIRECTORYQUERYARGUMENTS_CID \
+{ /* {f7dc2aeb-8e62-4750-965c-24b9e09ed8d2} */ \
+ 0xf7dc2aeb, 0x8e62, 0x4750, \
+ { 0x96, 0x5c, 0x24, 0xb9, 0xe0, 0x9e, 0xd8, 0xd2 } \
+}
+
+
+#define NS_BOOLEANCONDITIONSTRING_CONTRACTID \
+ "@mozilla.org/boolean-expression/condition-string;1"
+
+#define NS_BOOLEANCONDITIONSTRING_CID \
+{ /* {ca1944a9-527e-4c77-895d-d0466dd41cf5} */ \
+ 0xca1944a9, 0x527e, 0x4c77, \
+ { 0x89, 0x5d, 0xd0, 0x46, 0x6d, 0xd4, 0x1c, 0xf5 } \
+}
+
+
+#define NS_BOOLEANEXPRESSION_CONTRACTID \
+ "@mozilla.org/boolean-expression/n-peer;1"
+
+#define NS_BOOLEANEXPRESSION_CID \
+{ /* {2c2e75c8-6f56-4a50-af1c-72af5d0e8d41} */ \
+ 0x2c2e75c8, 0x6f56, 0x4a50, \
+ { 0xaf, 0x1c, 0x72, 0xaf, 0x5d, 0x0e, 0x8d, 0x41 } \
+}
+
+#define NS_ABDIRECTORYQUERYPROXY_CONTRACTID \
+ "@mozilla.org/addressbook/directory-query/proxy;1"
+
+#define NS_ABDIRECTORYQUERYPROXY_CID \
+{ /* {E162E335-541B-43B4-AAEA-FE591E240CAF}*/ \
+ 0xE162E335, 0x541B, 0x43B4, \
+ {0xAA, 0xEA, 0xFE, 0x59, 0x1E, 0x24, 0x0C, 0xAF} \
+}
+
+// nsAbLDAPDirectory
+//
+#define NS_ABLDAPDIRECTORY_CONTRACTID \
+ NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX "moz-abldapdirectory"
+
+#define NS_ABLDAPDIRECTORY_CID \
+{ /* {783E2777-66D7-4826-9E4B-8AB58C228A52}*/ \
+ 0x783E2777, 0x66D7, 0x4826, \
+ {0x9E, 0x4B, 0x8A, 0xB5, 0x8C, 0x22, 0x8A, 0x52} \
+}
+
+// nsAbLDAPDirectoryQuery
+//
+#define NS_ABLDAPDIRECTORYQUERY_CONTRACTID \
+ "@mozilla.org/addressbook/ldap-directory-query;1"
+
+#define NS_ABLDAPDIRECTORYQUERY_CID \
+{ /* {783E2777-66D7-4826-9E4B-8AB58C228A53}*/ \
+ 0x783E2777, 0x66D7, 0x4826, \
+ {0x9E, 0x4B, 0x8A, 0xB5, 0x8C, 0x22, 0x8A, 0x53} \
+}
+
+//
+// nsAbLDAPCard
+//
+#define NS_ABLDAPCARD_CONTRACTID \
+ "@mozilla.org/addressbook/moz-abldapcard"
+
+#define NS_ABLDAPCARD_CID \
+{ /* {10307B01-EBD6-465F-B972-1630410F70E6}*/ \
+ 0x10307B01, 0xEBD6, 0x465F, \
+ {0xB9, 0x72, 0x16, 0x30, 0x41, 0x0F, 0x70, 0xE6} \
+}
+
+//
+// LDAP directory factory
+//
+#define NS_ABLDAPDIRFACTORY_CONTRACTID \
+ NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX "moz-abldapdirectory"
+
+#define NS_ABLDAPDIRFACTORY_CID \
+{ /* {8e3701af-8828-426c-84ac-124825c778f8} */ \
+ 0x8e3701af, 0x8828, 0x426c, \
+ {0x84, 0xac, 0x12, 0x48, 0x25, 0xc7, 0x78, 0xf8} \
+}
+
+//
+// LDAP autocomplete directory factory
+//
+#define NS_ABLDAPACDIRFACTORY_CONTRACTID \
+ NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX "ldap"
+#define NS_ABLDAPSACDIRFACTORY_CONTRACTID \
+ NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX "ldaps"
+
+// nsAbLDAPAutoCompFormatter
+
+// 4e276d6d-9981-46b4-9070-92f344ac5f5a
+//
+#define NS_ABLDAPAUTOCOMPFORMATTER_CID \
+{ 0x4e276d6d, 0x9981, 0x46b4, \
+ { 0x90, 0x70, 0x92, 0xf3, 0x44, 0xac, 0x5f, 0x5a }}
+
+#define NS_ABLDAPAUTOCOMPFORMATTER_CONTRACTID \
+ "@mozilla.org/ldap-autocomplete-formatter;1?type=addrbook"
+
+
+// nsAbLDAPReplicationService
+//
+// {ece81280-2639-11d6-b791-00b0d06e5f27}
+//
+#define NS_ABLDAP_REPLICATIONSERVICE_CID \
+ {0xece81280, 0x2639, 0x11d6, \
+ { 0xb7, 0x91, 0x00, 0xb0, 0xd0, 0x6e, 0x5f, 0x27 }}
+
+#define NS_ABLDAP_REPLICATIONSERVICE_CONTRACTID \
+ "@mozilla.org/addressbook/ldap-replication-service;1"
+
+// nsAbLDAPReplicationQuery
+//
+// {5414fff0-263b-11d6-b791-00b0d06e5f27}
+//
+#define NS_ABLDAP_REPLICATIONQUERY_CID \
+ {0x5414fff0, 0x263b, 0x11d6, \
+ { 0xb7, 0x91, 0x00, 0xb0, 0xd0, 0x6e, 0x5f, 0x27 }}
+
+#define NS_ABLDAP_REPLICATIONQUERY_CONTRACTID \
+ "@mozilla.org/addressbook/ldap-replication-query;1"
+
+
+// nsAbLDAPChangeLogQuery
+//
+// {63E11D51-3C9B-11d6-B7B9-00B0D06E5F27}
+#define NS_ABLDAP_CHANGELOGQUERY_CID \
+ {0x63e11d51, 0x3c9b, 0x11d6, \
+ { 0xb7, 0xb9, 0x0, 0xb0, 0xd0, 0x6e, 0x5f, 0x27 }}
+
+#define NS_ABLDAP_CHANGELOGQUERY_CONTRACTID \
+ "@mozilla.org/addressbook/ldap-changelog-query;1"
+
+// nsAbLDAPProcessReplicationData
+//
+// {5414fff1-263b-11d6-b791-00b0d06e5f27}
+//
+#define NS_ABLDAP_PROCESSREPLICATIONDATA_CID \
+ {0x5414fff1, 0x263b, 0x11d6, \
+ { 0xb7, 0x91, 0x00, 0xb0, 0xd0, 0x6e, 0x5f, 0x27 }}
+
+#define NS_ABLDAP_PROCESSREPLICATIONDATA_CONTRACTID \
+ "@mozilla.org/addressbook/ldap-process-replication-data;1"
+
+
+// nsAbLDAPProcessChangeLogData
+//
+// {63E11D52-3C9B-11d6-B7B9-00B0D06E5F27}
+#define NS_ABLDAP_PROCESSCHANGELOGDATA_CID \
+ {0x63e11d52, 0x3c9b, 0x11d6, \
+ {0xb7, 0xb9, 0x0, 0xb0, 0xd0, 0x6e, 0x5f, 0x27 }}
+
+#define NS_ABLDAP_PROCESSCHANGELOGDATA_CONTRACTID \
+ "@mozilla.org/addressbook/ldap-process-changelog-data;1"
+
+// nsABView
+
+#define NS_ABVIEW_CID \
+{ 0xc5eb5d6a, 0x1dd1, 0x11b2, \
+ { 0xa0, 0x25, 0x94, 0xd1, 0x18, 0x1f, 0xc5, 0x9c }}
+
+#define NS_ABVIEW_CONTRACTID \
+ "@mozilla.org/addressbook/abview;1"
+
+#ifdef XP_MACOSX
+//
+// nsAbOSXDirectory
+//
+#define NS_ABOSXDIRECTORY_PREFIX "moz-abosxdirectory"
+#define NS_ABOSXCARD_PREFIX "moz-abosxcard"
+
+#define NS_ABOSXDIRECTORY_CONTRACTID \
+ NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX NS_ABOSXDIRECTORY_PREFIX
+
+#define NS_ABOSXDIRECTORY_CID \
+{ /* {83781cc6-c682-11d6-bdeb-0005024967b8}*/ \
+ 0x83781cc6, 0xc682, 0x11d6, \
+ {0xbd, 0xeb, 0x00, 0x05, 0x02, 0x49, 0x67, 0xb8} \
+}
+
+//
+// nsAbOSXCard
+//
+#define NS_ABOSXCARD_CONTRACTID \
+ NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX NS_ABOSXCARD_PREFIX
+
+#define NS_ABOSXCARD_CID \
+{ /* {89bbf582-c682-11d6-bc9d-0005024967b8}*/ \
+ 0x89bbf582, 0xc682, 0x11d6, \
+ {0xbc, 0x9d, 0x00, 0x05, 0x02, 0x49, 0x67, 0xb8} \
+}
+
+//
+// OS X directory factory
+//
+#define NS_ABOSXDIRFACTORY_CONTRACTID \
+ NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX NS_ABOSXDIRECTORY_PREFIX
+
+#define NS_ABOSXDIRFACTORY_CID \
+{ /* {90efe2fe-c682-11d6-9c83-0005024967b8}*/ \
+ 0x90efe2fe, 0xc682, 0x11d6, \
+ {0x9c, 0x83, 0x00, 0x05, 0x02, 0x49, 0x67, 0xb8} \
+}
+#endif
+
+#define NS_MSGVCARDSERVICE_CID \
+{ 0x3c4ac0da, 0x2cda, 0x4018, \
+ { 0x95, 0x51, 0xe1, 0x58, 0xb2, 0xe1, 0x22, 0xd3 }}
+
+#define NS_MSGVCARDSERVICE_CONTRACTID \
+ "@mozilla.org/addressbook/msgvcardservice;1"
+
+#define NS_ABLDIFSERVICE_CID \
+{ 0xdb6f46da, 0x8de3, 0x478d, \
+ { 0xb5, 0x39, 0x80, 0x13, 0x98, 0x65, 0x6c, 0xf6 }}
+
+#define NS_ABLDIFSERVICE_CONTRACTID \
+ "@mozilla.org/addressbook/abldifservice;1"
+
+#endif // nsAbBaseCID_h__
diff --git a/mailnews/addrbook/public/nsIAbAddressCollector.idl b/mailnews/addrbook/public/nsIAbAddressCollector.idl
new file mode 100644
index 000000000..3d5071a6b
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbAddressCollector.idl
@@ -0,0 +1,58 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAbCard;
+
+/**
+ * nsIAbAddressCollector is the interface to the address collecter service.
+ * It will save and update the supplied addresses into the address book
+ * specified by the "mail.collect_addressbook" pref.
+ */
+[scriptable, uuid(069d3fba-37d4-4158-b401-a8efaeea0b66)]
+interface nsIAbAddressCollector : nsISupports {
+ /**
+ * Collects email addresses into the address book.
+ * If a card already exists for the email, the first/last/display names
+ * will be updated if they are supplied alongside the address.
+ * If a card does not exist for the email it will be created if aCreateCard
+ * is true.
+ *
+ * @param aAddresses The list of emails (in standard header format)
+ * to collect into the address book.
+ * @param aCreateCard Set to true if a card should be created if the
+ * email address doesn't exist.
+ * @param aSendFormat The send format to save for the card. See
+ * nsIAbPreferMailFormat for values. If updating a card
+ * this value will only be changed if the current value
+ * for the card is "unknown".
+ */
+ void collectAddress(in AUTF8String aAddresses, in boolean aCreateCard,
+ in unsigned long aSendFormat);
+
+ /**
+ * Collects a single name and email address into the address book.
+ * By default, it saves the address without checking for an existing one.
+ * See collectAddress for the general implementation.
+ *
+ * @param aEmail The email address to collect.
+ * @param aDisplayName The display name associated with the email address.
+ * @param aCreateCard Set to true if a card should be created if the
+ * email address doesn't exist (ignored if
+ * aSkipCheckExisting is true).
+ * @param aSendFormat The send format to save for the card. See
+ * nsIAbPreferMailFormat for values. If updating a card
+ * this value will only be changed if the current value
+ * for the card is "unknown".
+ * @param aSkipCheckExisting Optional parameter, if this is set then the
+ * implementation will skip checking for an
+ * existing card, and just create a new card.
+ */
+ void collectSingleAddress(in AUTF8String aEmail, in AUTF8String aDisplayName,
+ in boolean aCreateCard,
+ in unsigned long aSendFormat,
+ [optional] in boolean aSkipCheckExisting);
+};
diff --git a/mailnews/addrbook/public/nsIAbAutoCompleteResult.idl b/mailnews/addrbook/public/nsIAbAutoCompleteResult.idl
new file mode 100644
index 000000000..f54290acc
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbAutoCompleteResult.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAutoCompleteResult.idl"
+
+interface nsIAbCard;
+
+/**
+ * This interface is used to extend the nsIAutoCompleteResult interface to
+ * provide extra facilities for obtaining more details of the results of
+ * an address book search.
+ */
+[scriptable, uuid(c0d35623-f719-4e43-ae24-573e393f87f9)]
+interface nsIAbAutoCompleteResult : nsIAutoCompleteResult {
+ /**
+ * Get the card from the result at the given index
+ */
+ nsIAbCard getCardAt(in long index);
+
+ /**
+ * Gets the email to use for the card within the result at the given index.
+ * This is the email that was matched against for the card where there are
+ * multiple email addresses on a card.
+ *
+ * @param index Index of the autocomplete result to return the value for.
+ * @result The email address to use from the card.
+ */
+ AString getEmailToUse(in long index);
+
+ /**
+ * The template used to build the query for this search. Optional.
+ */
+ attribute AString modelQuery;
+};
diff --git a/mailnews/addrbook/public/nsIAbBooleanExpression.idl b/mailnews/addrbook/public/nsIAbBooleanExpression.idl
new file mode 100644
index 000000000..aeef67b46
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbBooleanExpression.idl
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+typedef long nsAbBooleanConditionType;
+
+/**
+ * Condition types
+ *
+ * Constants defining the types of condition
+ * to obtain a boolean result of TRUE or FALSE
+ *
+ */
+[scriptable, uuid(F51387B1-5AEF-4A1C-830E-7CD3B02366CE)]
+interface nsIAbBooleanConditionTypes
+{
+ const long Exists = 0;
+ const long DoesNotExist = 1;
+ const long Contains = 2;
+ const long DoesNotContain = 3;
+ const long Is = 4;
+ const long IsNot = 5;
+ const long BeginsWith = 6;
+ const long EndsWith = 7;
+ const long LessThan = 8;
+ const long GreaterThan = 9;
+ const long SoundsLike = 10;
+ const long RegExp = 11;
+};
+
+
+typedef long nsAbBooleanOperationType;
+
+/*
+ * Operation types
+ *
+ * Constants defining the boolean operation that
+ * should be performed between two boolean expressions
+ *
+ */
+[uuid(9bdd2e51-2be4-49a4-a558-36d1a812231a)]
+interface nsIAbBooleanOperationTypes
+{
+ const long AND = 0;
+ const long OR = 1;
+ const long NOT = 2;
+};
+
+
+/**
+ * String condition
+ *
+ * A string condition represents a leaf node in a
+ * boolean expression tree and represents
+ * test which will return TRUE or FALSE
+ *
+ * Condition is an expression which is a
+ * leaf node in a boolean expression tree
+ *
+ */
+[scriptable, uuid(C3869D72-CFD0-45F0-A0EC-3F67D83C7110)]
+interface nsIAbBooleanConditionString : nsISupports
+{
+ /**
+ * The condition for how the a value
+ * should be compared
+ *
+ */
+ attribute nsAbBooleanConditionType condition;
+
+ /**
+ * The lhs of the condition
+ *
+ * Represents a property name which
+ * should be evaluated to obtain the
+ * lhs.
+ *
+ */
+ attribute string name;
+
+ /**
+ * The rhs of the condition
+ *
+ * <name> [condition] value
+ *
+ */
+ attribute wstring value;
+};
+
+/**
+ * N Boolean expression type
+ *
+ * Supports Unary Binary and N boolean expressions
+ *
+ * An operation represents a node in a boolean
+ * expression tree which may contain one or more
+ * child conditions or expressions
+ *
+ */
+[scriptable, uuid(223a9462-1aeb-4c1f-b069-5fc6278989b2)]
+interface nsIAbBooleanExpression: nsISupports
+{
+ /**
+ * The boolean operation to be applied to
+ * results of all evaluated expressions
+ *
+ */
+ attribute nsAbBooleanOperationType operation;
+
+ /**
+ * List of peer expressions
+ *
+ * e1 [op] e2 [op] .... en
+ *
+ */
+ attribute nsIArray expressions;
+};
+
diff --git a/mailnews/addrbook/public/nsIAbCard.idl b/mailnews/addrbook/public/nsIAbCard.idl
new file mode 100644
index 000000000..ccd1c4c43
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbCard.idl
@@ -0,0 +1,358 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAbItem.idl"
+
+interface nsISimpleEnumerator;
+interface nsIVariant;
+
+[scriptable, uuid(97448252-F189-11d4-A422-001083003D0C)]
+interface nsIAbPreferMailFormat {
+ const unsigned long unknown = 0;
+ const unsigned long plaintext = 1;
+ const unsigned long html = 2;
+};
+
+/**
+ * An interface representing an address book card.
+ *
+ * The UUID of a card is a composition of a directory ID and a per-directory ID.
+ * The per-directory ID is reflected in the localId property. If either of these
+ * properties change, the UUID will change correspondingly.
+ *
+ * None of these IDs will be reflected in the property collection. Neither
+ * nsIAbCard::properties, nsIAbCard::deleteProperty, nor any of the property
+ * getters and setters are able to interact with these properties.
+ *
+ * Fundamentally, a card is a collection of properties. Modifying a property in
+ * some way on a card does not change the backend used to store the card; the
+ * directory is required to do make the changes here.
+ *
+ * The following are the core properties that are used:
+ * - Names:
+ * - FirstName, LastName
+ * - PhoneticFirstName, PhoneticLastName
+ * - DisplayName, NickName
+ * - SpouseName, FamilyName
+ * - PrimaryEmail, SecondEmail
+ * - Home Contact:
+ * - HomeAddress, HomeAddress2, HomeCity, HomeState, HomeZipCode, HomeCountry
+ * - HomePhone, HomePhoneType
+ * - Work contact. Same as home, but with `Work' instead of `Home'
+ * - Other Contact:
+ * - FaxNumber, FaxNumberType
+ * - PagerNumber, PagerNumberType
+ * - CellularNumber, CellularNumberType
+ * - JobTitle, Department, Company
+ * - _AimScreenName
+ * - Dates:
+ * - AnniversaryYear, AnniversaryMonth, AnniversaryDay
+ * - BirthYear, BirthMonth, BirthDay
+ * - WebPage1 (work), WebPage2 (home)
+ * - Custom1, Custom2, Custom3, Custom4
+ * - Notes
+ * - Integral properties:
+ * - LastModifiedDate
+ * - PopularityIndex
+ * - PreferMailFormat (see nsIAbPreferMailFormat)
+ * - Photo properties:
+ * - PhotoName
+ * - PhotoType
+ * - PhotoURI
+ *
+ * The contract id for the standard implementation is
+ * <tt>\@mozilla.org/addressbook/cardproperty;1</tt>.
+ */
+[scriptable, uuid(9bddf024-5178-4097-894e-d84b4ddde101)]
+interface nsIAbCard : nsIAbItem {
+ /**
+ * The UUID for the nsIAbDirectory containing this card.
+ *
+ * The directory considered to contain this card is the directory which
+ * produced this card (e.g., through nsIAbDirectory::getCardForProperty) or
+ * the last directory to modify this card, if another directory did so. If the
+ * last directory to modify this card deleted it, then this card is considered
+ * unassociated.
+ *
+ * If this card is not associated with a directory, this string will be empty.
+ *
+ * There is no standardized way to associate a card with multiple directories.
+ *
+ * Consumers of this interface outside of directory implementations SHOULD
+ * NOT, in general, modify this property.
+ */
+ attribute AUTF8String directoryId;
+
+ /**
+ * The per-directory ID of this card.
+ *
+ * This property is the second part of the tuple logically representing a card
+ * UUID. It shares many requirements with that of nsIAbItem::uuid. In
+ * particular:
+ * - It MUST be unique (within the scope of its directory).
+ * - The empty string MUST only be used to indicate that it has not yet been
+ * assigned a localId.
+ * - It is STRONGLY RECOMMENDED that this id is consistent across sessions and
+ * that, should the card be deleted, its ids will not be reused.
+ * - The format of localId is left undefined.
+ *
+ * As long as directoryId is not changed, this property SHOULD NOT be changed.
+ * If directoryId is changed, the new directory MAY choose to reuse the same
+ * localId if reasonable. However, consumers MUST NOT assume that two cards
+ * with different directoryIds but the same localId are logically the same
+ * card.
+ *
+ * Similar to directoryId, consumers of cards outside of directory
+ * implementations SHOULD NOT, in general, modify this property.
+ */
+ attribute AUTF8String localId;
+
+ /**
+ * A list of all the properties that this card has as an enumerator, whose
+ * members are all nsIProperty objects.
+ */
+ readonly attribute nsISimpleEnumerator properties;
+
+ /**
+ * Returns a property for the given name.
+ *
+ * @param name The case-sensitive name of the property to get.
+ * @param defaultValue The value to return if the property does not exist.
+ * @exception NS_ERROR_NOT_AVAILABLE if the named property does not exist.
+ * @exception NS_ERROR_CANNOT_CONVERT_DATA if the property cannot be converted
+ * to the desired type.
+ */
+ nsIVariant getProperty(in AUTF8String name, in nsIVariant defaultValue);
+ /**
+ * @{
+ * Returns a property for the given name. Javascript callers should NOT use these,
+ * but use getProperty instead. XPConnect will do the type conversion automagically.
+ *
+ * These functions convert values in the same manner as the default
+ * implementation of nsIVariant. Of particular note is that boolean variables
+ * are converted to integers as in C/C++ (true is a non-zero value), so that
+ * false will be converted to a string of "0" and not "false."
+ *
+ *
+ * @param name The case-sensitive name of the property to get.
+ * @exception NS_ERROR_NOT_AVAILABLE if the named property does not exist.
+ * @exception NS_ERROR_CANNOT_CONVERT_DATA if the property cannot be converted
+ * to the desired type.
+ */
+ AString getPropertyAsAString(in string name);
+ AUTF8String getPropertyAsAUTF8String(in string name);
+ unsigned long getPropertyAsUint32(in string name);
+ boolean getPropertyAsBool(in string name);
+
+ /** @} */
+
+ /**
+ * Assigns the given to value to the property of the given name.
+ *
+ * Should the property exist, its value will be overwritten. An
+ * implementation may impose additional semantic constraints for certain
+ * properties. However, such constraints might not be checked by this method.
+ *
+ * @warning A value MUST be convertible to a string; if this convention is not
+ * followed, consumers of cards may fail unpredictably or return incorrect
+ * results.
+ *
+ * @param name The case-sensitive name of the property to set.
+ * @param value The new value of the property.
+ */
+ void setProperty(in AUTF8String name, in nsIVariant value);
+
+ /**
+ * @{
+ * Sets a property for the given name. Javascript callers should NOT use these,
+ * but use setProperty instead. XPConnect will do the type conversion automagically.
+ *
+ * These functions convert values in the same manner as the default
+ * implementation of nsIVariant.
+ */
+ void setPropertyAsAString(in string name, in AString value);
+ void setPropertyAsAUTF8String(in string name, in AUTF8String value);
+ void setPropertyAsUint32(in string name, in unsigned long value);
+ void setPropertyAsBool(in string name, in boolean value);
+
+ /** @} */
+
+ /**
+ * Deletes the property with the given name.
+ *
+ * Some properties may not be deleted. However, the implementation will not
+ * check this constraint at this method. If such a property is deleted, an
+ * error may be thrown when the card is modified at the database level.
+ *
+ * @param name The case-sensitive name of the property to set.
+ */
+ void deleteProperty(in AUTF8String name);
+
+ /**
+ * @{
+ * These properties are shorthand for getProperty and setProperty.
+ */
+ attribute AString firstName;
+ attribute AString lastName;
+ attribute AString displayName;
+ attribute AString primaryEmail;
+ /** @} */
+
+ /**
+ * Determines whether or not a card has the supplied email address in either
+ * of its PrimaryEmail or SecondEmail attributes.
+ *
+ * Note: This function is likely to be temporary whilst we work out proper
+ * APIs for multi-valued attributes in bug 118665.
+ *
+ * @param aEmailAddress The email address to attempt to match against.
+ * @return True if aEmailAddress matches any of the email
+ * addresses stored in the card.
+ */
+ boolean hasEmailAddress(in AUTF8String aEmailAddress);
+
+ /**
+ * Translates a card into a specific format.
+ * The following types are supported:
+ * - base64xml
+ * - xml
+ * - vcard
+ *
+ * @param aType The type of item to translate the card into.
+ * @return A string containing the translated card.
+ * @exception NS_ERROR_ILLEGAL_VALUE if we do not recognize the type.
+ */
+ AUTF8String translateTo(in AUTF8String aType);
+
+ /**
+ * Translates a card from the specified format
+ */
+ //void translateFrom(in AUTF8String aType, in AUTF8String aData);
+
+ /**
+ * Generate a phonetic name from the card, using the firstName and lastName
+ * values.
+ *
+ * @param aLastNameFirst Set to True to put the last name before the first.
+ * @return A string containing the generated phonetic name.
+ */
+ AString generatePhoneticName(in boolean aLastNameFirst);
+
+ /**
+ * Generate a chat name from the card, containing the value of the
+ * first non-empty chat field.
+ *
+ * @return A string containing the generated chat name.
+ */
+ AString generateChatName();
+
+ /**
+ * This function will copy all values from one card to another.
+ *
+ * @param srcCard The source card to copy values from.
+ */
+ void copy(in nsIAbCard aSrcCard);
+
+ /**
+ * Returns true if this card is equal to the other card.
+ *
+ * The default implementation defines equal as this card pointing to the
+ * same object as @arg aCard; another implementation defines it as equality of
+ * properties and values.
+ *
+ * @warning The exact nature of equality is still undefined, and actual
+ * results may not match theoretical results. Most notably, the code
+ * <tt>a.equals(b) == b.equals(a)</tt> might not return true. In
+ * particular, calling equals on cards from different address books
+ * may return inaccurate results.
+ *
+ *
+ * @return Equality, as defined above.
+ * @param aCard The card to compare against.
+ */
+ boolean equals(in nsIAbCard aCard);
+
+ // PROPERTIES TO BE DELETED AS PART OF REWRITE
+
+ attribute boolean isMailList;
+ /**
+ * If isMailList is true then mailListURI
+ * will contain the URI of the associated
+ * mail list
+ */
+ attribute string mailListURI;
+};
+
+%{C++
+// A nice list of properties for the benefit of C++ clients
+#define kFirstNameProperty "FirstName"
+#define kLastNameProperty "LastName"
+#define kDisplayNameProperty "DisplayName"
+#define kNicknameProperty "NickName"
+#define kPriEmailProperty "PrimaryEmail"
+#define kPreferMailFormatProperty "PreferMailFormat"
+#define kLastModifiedDateProperty "LastModifiedDate"
+#define kPopularityIndexProperty "PopularityIndex"
+
+#define kPhoneticFirstNameProperty "PhoneticFirstName"
+#define kPhoneticLastNameProperty "PhoneticLastName"
+#define kSpouseNameProperty "SpouseName"
+#define kFamilyNameProperty "FamilyName"
+#define k2ndEmailProperty "SecondEmail"
+
+#define kHomeAddressProperty "HomeAddress"
+#define kHomeAddress2Property "HomeAddress2"
+#define kHomeCityProperty "HomeCity"
+#define kHomeStateProperty "HomeState"
+#define kHomeZipCodeProperty "HomeZipCode"
+#define kHomeCountryProperty "HomeCountry"
+#define kHomeWebPageProperty "WebPage2"
+
+#define kWorkAddressProperty "WorkAddress"
+#define kWorkAddress2Property "WorkAddress2"
+#define kWorkCityProperty "WorkCity"
+#define kWorkStateProperty "WorkState"
+#define kWorkZipCodeProperty "WorkZipCode"
+#define kWorkCountryProperty "WorkCountry"
+#define kWorkWebPageProperty "WebPage1"
+
+#define kHomePhoneProperty "HomePhone"
+#define kHomePhoneTypeProperty "HomePhoneType"
+#define kWorkPhoneProperty "WorkPhone"
+#define kWorkPhoneTypeProperty "WorkPhoneType"
+#define kFaxProperty "FaxNumber"
+#define kFaxTypeProperty "FaxNumberType"
+#define kPagerTypeProperty "PagerNumberType"
+#define kPagerProperty "PagerNumber"
+#define kCellularProperty "CellularNumber"
+#define kCellularTypeProperty "CellularNumberType"
+
+#define kJobTitleProperty "JobTitle"
+#define kDepartmentProperty "Department"
+#define kCompanyProperty "Company"
+#define kScreenNameProperty "_AimScreenName"
+#define kCustom1Property "Custom1"
+#define kCustom2Property "Custom2"
+#define kCustom3Property "Custom3"
+#define kCustom4Property "Custom4"
+#define kNotesProperty "Notes"
+
+#define kGtalkProperty "_GoogleTalk"
+#define kAIMProperty "_AimScreenName"
+#define kYahooProperty "_Yahoo"
+#define kSkypeProperty "_Skype"
+#define kQQProperty "_QQ"
+#define kMSNProperty "_MSN"
+#define kICQProperty "_ICQ"
+#define kXMPPProperty "_JabberId"
+#define kIRCProperty "_IRC"
+
+#define kAnniversaryYearProperty "AnniversaryYear"
+#define kAnniversaryMonthProperty "AnniversaryMonth"
+#define kAnniversaryDayProperty "AnniversaryDay"
+#define kBirthYearProperty "BirthYear"
+#define kBirthMonthProperty "BirthMonth"
+#define kBirthDayProperty "BirthDay"
+%}
diff --git a/mailnews/addrbook/public/nsIAbCollection.idl b/mailnews/addrbook/public/nsIAbCollection.idl
new file mode 100644
index 000000000..1efbcce16
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbCollection.idl
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAbItem.idl"
+
+interface nsIAbCard;
+interface nsISimpleEnumerator;
+
+/**
+ * A collection of address book items.
+ */
+[scriptable, uuid(70f6123f-e06b-4101-9750-4ce73b38134b)]
+interface nsIAbCollection : nsIAbItem {
+
+ /**
+ * Returns true if this collection is read-only.
+ */
+ readonly attribute boolean readOnly;
+
+ /**
+ * Returns true if this collection is accessed over a network connection.
+ */
+ readonly attribute boolean isRemote;
+
+ /**
+ * Returns true if this collection is accessed over a secure connection.
+ *
+ * If isRemote returns false, then this value MUST be false as well.
+ */
+ readonly attribute boolean isSecure;
+
+ /**
+ * Returns an address book card for the specified email address if found.
+ *
+ * If there are multiple cards with the given email address, this method will
+ * return one of these cards in an implementation-defined manner.
+ *
+ * Matching is performed in a case-insensitive manner.
+ *
+ * This method performs a synchronous operation. If the collection cannot do
+ * the search in such a manner, then it should throw NS_ERROR_NOT_IMPLEMENTED.
+ *
+ * @param emailAddress The email address to find in any of the email address
+ * fields. If emailAddress is empty, the database won't
+ * be searched and the function will return as if no card
+ * was found.
+ * @return An nsIAbCard if one was found, else returns NULL.
+ * @exception NS_ERROR_NOT_IMPLEMENTED If the collection cannot do this.
+ */
+ nsIAbCard cardForEmailAddress(in AUTF8String emailAddress);
+
+ /**
+ * Returns an address book card for the specified property if found.
+ *
+ * If there are multiple cards with the given value for the property, this
+ * method will return one of these cards in an implementation-defined manner.
+ *
+ * This method performs a synchronous operation. If the collection cannot do
+ * the search in such a manner, then it should throw NS_ERROR_NOT_IMPLEMENTED.
+ *
+ * If the property is not natively a string, it can still be searched for
+ * using the string-encoded value of the property, e.g. "0". See
+ * nsIAbCard::getPropertyAsAUTF8String for more information. Empty values will
+ * return no match, to prevent spurious results.
+ *
+ * @param aProperty The property to look for.
+ * @param aValue The value to search for.
+ * @param aCaseSensitive True if matching should be done case-sensitively.
+ * @result An nsIAbCard if one was found, else returns NULL.
+ * @exception NS_ERROR_NOT_IMPLEMENTED If the collection cannot do this.
+ */
+ nsIAbCard getCardFromProperty(in string aProperty, in AUTF8String aValue,
+ in boolean aCaseSensitive);
+
+ /**
+ * Returns all address book cards with a specific property matching value
+ *
+ * This function is almost identical to getCardFromProperty, with the
+ * exception of returning all cards rather than just the first.
+ *
+ * @param aProperty The property to look for.
+ * @param aValue The value to search for.
+ * @param aCaseSensitive True if matching should be done case-sensitively.
+ * @result A nsISimpleEnumerator that holds nsIAbCard
+ * instances.
+ */
+ nsISimpleEnumerator getCardsFromProperty(in string aProperty,
+ in AUTF8String aValue,
+ in boolean aCaseSensitive);
+};
diff --git a/mailnews/addrbook/public/nsIAbDirFactory.idl b/mailnews/addrbook/public/nsIAbDirFactory.idl
new file mode 100644
index 000000000..3be1f6394
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbDirFactory.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+
+interface nsIAbDirectory;
+
+[scriptable, uuid(ad61b4fc-d8d8-40b2-b924-4c10f28a8a17)]
+interface nsIAbDirFactory : nsISupports
+{
+ /**
+ * Get a top level address book directory and sub directories, given some
+ * properties.
+ *
+ * @param aDirName Name of the address book
+ *
+ * @param aURI URI of the address book
+ *
+ * @param aPrefName Pref name for the preferences of the address book
+ *
+ * @return Enumeration of nsIAbDirectory interfaces
+ */
+ nsISimpleEnumerator getDirectories(in AString aDirName, in ACString aURI,
+ in ACString aPrefName);
+
+ /**
+ * Delete a top level address book directory
+ *
+ */
+ void deleteDirectory (in nsIAbDirectory directory);
+};
+
diff --git a/mailnews/addrbook/public/nsIAbDirFactoryService.idl b/mailnews/addrbook/public/nsIAbDirFactoryService.idl
new file mode 100644
index 000000000..8276d17ba
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbDirFactoryService.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+
+interface nsIAbDirFactory;
+
+[scriptable, uuid(154a951b-a310-400c-b98f-d769cc5d575f)]
+interface nsIAbDirFactoryService : nsISupports
+{
+ /**
+ * Obtain a directory factory component given a uri representing an address
+ * book. The scheme is extracted from the uri and contract id is generated
+ * of the form:
+ * @mozilla.org/addressbook/directory-factory;1?name=<scheme>
+ *
+ * This id is used to instantiate a registered component which implemented
+ * the nsIAbDirFactory interface.
+ *
+ * @param aURI The uri which contains the scheme that defines what directory
+ * factory instance is returned
+ */
+ nsIAbDirFactory getDirFactory(in ACString aURI);
+};
+
diff --git a/mailnews/addrbook/public/nsIAbDirSearchListener.idl b/mailnews/addrbook/public/nsIAbDirSearchListener.idl
new file mode 100644
index 000000000..2e8455c79
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbDirSearchListener.idl
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAbCard;
+
+[scriptable, uuid(eafe2488-4efb-4ac8-a6b4-7756eb1650a3)]
+interface nsIAbDirSearchListener : nsISupports {
+ void onSearchFinished(in long aResult, in AString aErrorMsg);
+
+ void onSearchFoundCard(in nsIAbCard aCard);
+};
diff --git a/mailnews/addrbook/public/nsIAbDirectory.idl b/mailnews/addrbook/public/nsIAbDirectory.idl
new file mode 100644
index 000000000..e3e52beec
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbDirectory.idl
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAbCollection.idl"
+#include "nsIAbCard.idl"
+
+interface nsISimpleEnumerator;
+interface nsIArray;
+interface nsIMutableArray;
+
+/* moz-abdirectory:// is the URI to access nsAbBSDirectory,
+ * which is the root directory for all types of address books
+ * this is used to get all address book directories. */
+
+%{C++
+#define kAllDirectoryRoot "moz-abdirectory://"
+
+#define kPersonalAddressbook "abook.mab"
+#define kPersonalAddressbookUri "moz-abmdbdirectory://abook.mab"
+#define kCollectedAddressbook "history.mab"
+#define kCollectedAddressbookUri "moz-abmdbdirectory://history.mab"
+
+#define kABFileName_PreviousSuffix ".na2" /* final v2 address book format */
+#define kABFileName_PreviousSuffixLen 4
+#define kABFileName_CurrentSuffix ".mab" /* v3 address book extension */
+%}
+
+/**
+ * A top-level address book directory.
+ *
+ * Please note that in order to be properly instantiated by nsIAbManager, every
+ * type of nsIAbDirectory must have a contract ID of the form:
+ *
+ * @mozilla.org/addressbook/directory;1?type=<AB URI Scheme>
+ *
+ * Where AB URI Scheme does not include the ://. For example, for the Mork-based
+ * address book, the scheme is "moz-abmdbdirectory", so the contract ID for
+ * the Mork-based address book type is:
+ *
+ * @mozilla.org/addressbook/directory;1?type=moz-abmdbdirectory
+ *
+ * The UUID of an nsIAbDirectory is its preference ID and its name, concatenated
+ * together.
+ */
+[scriptable, uuid(72dc868b-db5b-4daa-b6c6-071be4a05d02)]
+interface nsIAbDirectory : nsIAbCollection {
+
+ /**
+ * The chrome URI to use for bringing up a dialog to edit this directory.
+ * When opening the dialog, use a JS argument of
+ * {selectedDirectory: thisdir} where thisdir is this directory that you just
+ * got the chrome URI from.
+ */
+ readonly attribute ACString propertiesChromeURI;
+
+ /**
+ * The description of the directory. If this directory is not a mailing list,
+ * then setting this attribute will send round a "DirName" update via
+ * nsIAddrBookSession.
+ */
+ attribute AString dirName;
+
+ // XXX This should really be replaced by a QI or something better
+ readonly attribute long dirType;
+
+ // eliminated a bit more.
+
+ // The filename for address books within this directory.
+ readonly attribute ACString fileName;
+
+ // The URI of the address book
+ readonly attribute ACString URI;
+
+ // The position of the directory on the display.
+ readonly attribute long position;
+
+ // will be used for LDAP replication
+ attribute unsigned long lastModifiedDate;
+
+ // Defines whether this directory is a mail
+ // list or not
+ attribute boolean isMailList;
+
+ // Get the children directories
+ readonly attribute nsISimpleEnumerator childNodes;
+
+ /**
+ * Get the cards associated with the directory. This will return the cards
+ * associated with the mailing lists too.
+ */
+ readonly attribute nsISimpleEnumerator childCards;
+
+ /**
+ * Returns true if this directory represents a query - i.e. the rdf resource
+ * was something like moz-abmdbdirectory://abook.mab?....
+ */
+ readonly attribute boolean isQuery;
+
+ /**
+ * Initializes a directory, pointing to a particular
+ * URI
+ */
+ void init(in string aURI);
+
+ // Deletes either a mailing list or a top
+ // level directory, which also updates the
+ // preferences
+ void deleteDirectory(in nsIAbDirectory directory);
+
+ // Check if directory contains card
+ // If the implementation is asynchronous the card
+ // may not yet have arrived. If it is in the process
+ // of obtaining cards the method will throw an
+ // NS_ERROR_NOT_AVAILABLE exception if the card
+ // cannot be found.
+ boolean hasCard(in nsIAbCard cards);
+
+ // Check if directory contains directory
+ boolean hasDirectory(in nsIAbDirectory dir);
+
+ // Check if directory contains a mailinglist by name
+ boolean hasMailListWithName(in wstring aName);
+
+ /**
+ * Adds a card to the database.
+ *
+ * This card does not need to be of the same type as the database, e.g., one
+ * can add an nsIAbLDAPCard to an nsIAbMDBDirectory.
+ *
+ * @return "Real" card (eg nsIAbLDAPCard) that can be used for some
+ * extra functions.
+ */
+ nsIAbCard addCard(in nsIAbCard card);
+
+ /**
+ * Modifies a card in the database to match that supplied.
+ */
+ void modifyCard(in nsIAbCard modifiedCard);
+
+ /**
+ * Deletes the array of cards from the database.
+ *
+ * @param aCards The cards to delete from the database.
+ */
+ void deleteCards(in nsIArray aCards);
+
+ void dropCard(in nsIAbCard card, in boolean needToCopyCard);
+
+ /**
+ * Whether or not the directory should be searched when doing autocomplete,
+ * (currently by using GetChildCards); LDAP does not support this in online
+ * mode, so that should return false; additionally any other directory types
+ * that also do not support GetChildCards should return false.
+ *
+ * @param aIdentity An optional parameter detailing the identity key (see
+ * nsIMsgAccountManager) that this autocomplete is being
+ * run against.
+ * @return True if this directory should/can be used during
+ * local autocomplete.
+ */
+ boolean useForAutocomplete(in ACString aIdentityKey);
+
+ /**
+ * Does this directory support mailing lists? Note that in the case
+ * this directory is a mailing list and nested mailing lists are not
+ * supported, this will return false rather than true which the parent
+ * directory might.
+ */
+ readonly attribute boolean supportsMailingLists;
+
+ /**
+ * This attribute serves two purposes
+ * 1. If this directory is not a mail list, directories are stored here
+ * 2. If it is a mail list card entries are stored here
+ *
+ * @note This is a *live* array and not a static copy
+ */
+ attribute nsIMutableArray addressLists;
+
+ // Specific to a directory which stores mail lists
+
+ /**
+ * Creates a new mailing list in the directory. Currently only supported
+ * for top-level directories.
+ *
+ * @param list The new mailing list to add.
+ * @return The mailing list directory added, which may have been modified.
+ */
+ nsIAbDirectory addMailList(in nsIAbDirectory list);
+
+ /**
+ * Nick Name of the mailing list. This attribute is only really used when
+ * the nsIAbDirectory represents a mailing list.
+ */
+ attribute AString listNickName;
+
+ /**
+ * Description of the mailing list. This attribute is only really used when
+ * the nsIAbDirectory represents a mailing list.
+ */
+ attribute AString description;
+
+ /**
+ * Edits an existing mailing list (specified as listCard) into its parent
+ * directory. You should call this function on the resource with the same
+ * uri as the listCard.
+ *
+ * @param listCard A nsIAbCard version of the mailing list with the new
+ * values.
+ */
+ void editMailListToDatabase(in nsIAbCard listCard);
+
+ // Copies mail list properties from the srcList
+ void copyMailList(in nsIAbDirectory srcList);
+
+ /**
+ * Only creates a top level address book
+ * which is stored in the preferences
+ *
+ * Need to change to factory based approach
+ * to create new address books
+ *
+ * This method should become redundant or
+ * be only associated with card folders
+ *
+ * The parameters are the same as for
+ * nsIAbManager::newAddressBook
+ */
+ ACString createNewDirectory(in AString aDirName, in ACString aURI,
+ in unsigned long aType, in ACString aPrefName);
+
+ /* create a directory by passing the display name and address book uri */
+ void createDirectoryByURI(in AString displayName, in ACString aURI);
+
+ /**
+ * The id of the directory used in prefs e.g. "ldap_2.servers.pab"
+ * Setting this will cause directoryPrefs to be updated.
+ */
+ attribute ACString dirPrefId;
+
+ /**
+ * @name getXXXValue
+ *
+ * Helper functions to get different types of pref, but return a default
+ * value if a pref value was not obtained.
+ *
+ * @param aName The name of the pref within the branch dirPrefId to
+ * get a value from.
+ *
+ * @param aDefaultValue The default value to return if getting the pref fails
+ * or the pref is not present.
+ *
+ * @return The value of the pref or the default value.
+ *
+ * @exception NS_ERROR_NOT_INITIALIZED if the pref branch couldn't
+ * be obtained (e.g. dirPrefId isn't set).
+ */
+ //@{
+ long getIntValue(in string aName, in long aDefaultValue);
+ boolean getBoolValue(in string aName, in boolean aDefaultValue);
+ ACString getStringValue(in string aName, in ACString aDefaultValue);
+ AUTF8String getLocalizedStringValue(in string aName, in AUTF8String aDefaultValue);
+ //@}
+
+ /**
+ * The following attributes are read from an nsIAbDirectory via the above methods:
+ *
+ * HidesRecipients (Boolean)
+ * If true, and this nsIAbDirectory is a mailing list, then when sending mail to
+ * this list, recipients addresses will be hidden from one another by sending
+ * via BCC.
+ */
+
+ /**
+ * @name setXXXValue
+ *
+ * Helper functions to set different types of pref values.
+ *
+ * @param aName The name of the pref within the branch dirPrefId to
+ * get a value from.
+ *
+ * @param aValue The value to set the pref to.
+ *
+ * @exception NS_ERROR_NOT_INITIALIZED if the pref branch couldn't
+ * be obtained (e.g. dirPrefId isn't set).
+ */
+ //@{
+ void setIntValue(in string aName, in long aValue);
+ void setBoolValue(in string aName, in boolean aValue);
+ void setStringValue(in string aName, in ACString aValue);
+ void setLocalizedStringValue(in string aName, in AUTF8String aValue);
+ //@}
+
+};
diff --git a/mailnews/addrbook/public/nsIAbDirectoryQuery.idl b/mailnews/addrbook/public/nsIAbDirectoryQuery.idl
new file mode 100644
index 000000000..11a82d926
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbDirectoryQuery.idl
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+
+interface nsIAbDirSearchListener;
+interface nsIAbCard;
+interface nsIAbDirectory;
+
+/**
+ * The arguments for a query.
+ *
+ * Contains an expression for perform matches
+ * and an array of properties which should be
+ * returned if a match is found from the expression
+ *
+ */
+[scriptable, uuid(03af3018-2590-4f4c-a88c-1fff6595ef05)]
+interface nsIAbDirectoryQueryArguments : nsISupports
+{
+ /**
+ * Defines the boolean expression for
+ * the matching of cards
+ *
+ */
+ attribute nsISupports expression;
+
+ /**
+ * Defines if sub directories should be
+ * queried
+ *
+ */
+ attribute boolean querySubDirectories;
+
+ /**
+ * A parameter which can be used to pass in data specific to a particular
+ * type of addressbook.
+ */
+ attribute nsISupports typeSpecificArg;
+
+ /**
+ * A custom search filter which user wants to use in LDAP query.
+ */
+ attribute AUTF8String filter;
+};
+
+
+[scriptable, uuid(3A6E0C0C-1DD2-11B2-B23D-EA3A8CCB333C)]
+interface nsIAbDirectoryQueryPropertyValue : nsISupports
+{
+ /**
+ * The property which should be matched
+ *
+ * For example 'primaryEmail' or 'homePhone'
+ * for card properties.
+ *
+ * Two further properties are defined that
+ * do not exist as properties on a card.
+ *
+ * 'card:nsIAbCard' which represents the interface
+ * of a card component
+ *
+ */
+ readonly attribute string name;
+
+ /**
+ * The value of the property
+ *
+ */
+ readonly attribute wstring value;
+
+ /**
+ * The value of the property
+ * as an interface
+ *
+ * Only valid if the corresponding
+ * property name is related to an
+ * interface instead of a wstring
+ *
+ */
+ readonly attribute nsISupports valueISupports;
+};
+
+[scriptable, uuid(516e7ffa-69bc-41db-a493-dfb4895832f3)]
+interface nsIAbDirectoryQueryResultListener : nsISupports
+{
+ /**
+ * Called when a match is found. May be called from a different thread to
+ * the one that initiates the query.
+ *
+ * @param aCard An individual result associated returned from a query
+ */
+ void onQueryFoundCard(in nsIAbCard aCard);
+
+ /**
+ * List of defined query results
+ *
+ */
+ const long queryResultMatch = 0;
+ const long queryResultComplete = 1;
+ const long queryResultStopped = 2;
+ const long queryResultError = 3;
+
+ /**
+ * Called when a query has finished. May be called from a different thread
+ * to the one that initiates the query.
+ *
+ * @param aResult A result code from the list above.
+ *
+ * @param aErrorCode An error code specific to the type of query.
+ */
+ void onQueryResult(in long aResult, in long aErrorCode);
+};
+
+[scriptable, uuid(60b5961c-ce61-47b3-aa99-6d865f734dee)]
+interface nsIAbDirectoryQuery : nsISupports
+{
+ /**
+ * Initiates a query on a directory and sub-directories for properties
+ * on cards
+ *
+ * @param aDirectory A directory that the query may get extra details
+ * from.
+ *
+ * @param aArguments The properties and values to match value could of
+ * type nsIAbDirectoryQueryMatchItem for matches other
+ * than ?contains?
+ *
+ * @param aListener The listener which will obtain individual query
+ * results.
+ *
+ * @param aResultLimit Limits the number of results returned to a maximum
+ * value.
+ *
+ * @param aTimeOut The maximum length of time for the query
+ *
+ * @return A context id for the query
+ */
+ long doQuery(in nsIAbDirectory aDirectory,
+ in nsIAbDirectoryQueryArguments aArguments,
+ in nsIAbDirSearchListener aListener,
+ in long aResultLimit,
+ in long aTimeOut);
+
+ /**
+ * Stops an existing query operation if
+ * query operation is asynchronous
+ *
+ * The nsIAbDirectoryQueryResultListener will
+ * be notified when query has stopped
+ *
+ * It is implementation specific if notification
+ * synchronous or asynchronous
+ *
+ * @param contextID
+ * The unique number returned from
+ * the doQuery methods
+ *
+ */
+ void stopQuery (in long contextID);
+};
diff --git a/mailnews/addrbook/public/nsIAbDirectoryQueryProxy.idl b/mailnews/addrbook/public/nsIAbDirectoryQueryProxy.idl
new file mode 100644
index 000000000..68e2923cd
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbDirectoryQueryProxy.idl
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsIAbDirectoryQuery.idl"
+
+[scriptable, uuid(b8034849-1e98-4d39-819c-15ba61a7434f)]
+interface nsIAbDirectoryQueryProxy : nsIAbDirectoryQuery
+{
+ void initiate();
+};
+
diff --git a/mailnews/addrbook/public/nsIAbDirectorySearch.idl b/mailnews/addrbook/public/nsIAbDirectorySearch.idl
new file mode 100644
index 000000000..818684499
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbDirectorySearch.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+
+/**
+ * Searching of cards on a directory.
+ *
+ * The search data is defined in the query
+ * section of the directory URI, for example
+ *
+ * moz-abldapdirectory://ldap1.holland/dc=sun,dc=com?<query>
+ *
+ * If no search data is defined then the methods
+ * will return immediately with no error.
+ */
+[scriptable, uuid(ABF26047-37E3-44FD-A28A-6D37A1B9CCB3)]
+interface nsIAbDirectorySearch : nsISupports
+{
+ /**
+ * Starts a search on the directory.
+ *
+ * If a search is already being performed
+ * it is stopped.
+ *
+ * The results from a search, cards, will
+ * returned by informing the address book
+ * session that a new card has been added
+ * to the directory.
+ *
+ * The nsIAbDirectoryQuery implementation
+ * of the directory component (or a proxy)
+ * may be used as an implementation for
+ * this specialization of query.
+ *
+ * This method is semantically equivalent
+ * to the nsIAbDirectory.getChildCards
+ * method when there is search criteria
+ * defined in the directory uri.
+ *
+ */
+ void startSearch ();
+
+ /**
+ * Stops a search on the directory.
+ *
+ */
+ void stopSearch ();
+};
+
diff --git a/mailnews/addrbook/public/nsIAbItem.idl b/mailnews/addrbook/public/nsIAbItem.idl
new file mode 100644
index 000000000..adda32d96
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbItem.idl
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgHeaderParser;
+interface nsIStringBundle;
+
+/**
+ * A containable item for address books.
+ */
+[scriptable, uuid(bb691a55-cbfe-4cf8-974a-e18cfa845a73)]
+interface nsIAbItem : nsISupports {
+ /**
+ * A universally-unique identifier for this item.
+ *
+ * If this item cannot be associated with a UUID for some reason, it MUST
+ * return the empty string. The empty string MUST NOT be a valid UUID for any
+ * item. Under no circumstances may this function throw an error.
+ *
+ * It is STRONGLY RECOMMENDED that implementations guarantee that this UUID
+ * will not change between two different sessions of the application and that,
+ * if this item is deleted, the UUID will not be reused.
+ *
+ * The format of the UUID for a generic nsIAbItem is purposefully left
+ * undefined, although any item contained by an nsIAbDirectory SHOULD use
+ * nsIAbManager::generateUUID to generate the UUID.
+ */
+ readonly attribute AUTF8String uuid;
+
+ /**
+ * @{
+ * These constants reflect the possible values of the
+ * mail.addr_book.lastnamefirst preferences. They are intended to be used in
+ * generateName, defined below.
+ */
+ const unsigned long GENERATE_DISPLAY_NAME = 0;
+ const unsigned long GENERATE_LAST_FIRST_ORDER = 1;
+ const unsigned long GENERATE_FIRST_LAST_ORDER = 2;
+ /** @} */
+
+ /**
+ * Generate a name from the item for display purposes.
+ *
+ * If this item is an nsIAbCard, then it will use the aGenerateFormat option
+ * to determine the string to return.
+ * If this item is not an nsIAbCard, then the aGenerateFormat option may be
+ * ignored, and the displayName of the item returned.
+ *
+ * @param aGenerateFormat The format to generate as per the GENERATE_*
+ * constants above.
+ * @param aBundle An optional parameter that is a pointer to a string
+ * bundle that holds:
+ * chrome://messenger/locale/addressbook/addressBook.properties
+ * If this bundle is not supplied, then the function
+ * will obtain the bundle itself. If cached by the
+ * caller and supplied to this function, then
+ * performance will be improved over many calls.
+ * @return A string containing the generated name.
+ */
+ AString generateName(in long aGenerateFormat,
+ [optional] in nsIStringBundle aBundle);
+
+ /**
+ * Generate a formatted email address from the card, that can be used for
+ * sending emails.
+ *
+ * @param aExpandList If this card is a list, and this parameter is set
+ * to true, then the list will be expanded to include
+ * the emails of the cards within the list.
+ * @param aGroupMailLists If this card (or the items within this card) is a
+ * list, and this is set to true, then the list will
+ * be expanded in the RFC 2822 group format
+ * "displayname : email1 ; email2 ; etc".
+ * @param aHeaderParser An optional parameter pointing to the
+ * nsIMsgHeaderParser service. If this is not supplied
+ * the function will obtain the service itself. If
+ * cached by the called and supplied to this function,
+ * then performance will be improved over many calls.
+ * @return A string containing a comma-separated list of
+ * formatted addresses.
+ */
+ //AString generateFormattedEmail(in boolean aExpandList,
+ // in boolean aAsGroupMailLists,
+ // [optional] in nsIMsgHeaderParser aHeaderParser);
+
+};
+
diff --git a/mailnews/addrbook/public/nsIAbLDAPAttributeMap.idl b/mailnews/addrbook/public/nsIAbLDAPAttributeMap.idl
new file mode 100644
index 000000000..8b07f68af
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbLDAPAttributeMap.idl
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsILDAPMessage;
+interface nsIAbCard;
+
+/**
+ * A mapping between addressbook properties and ldap attributes.
+ *
+ * Each addressbook property can map to one or more attributes. If
+ * there is no entry in preferences for a field, the getters generally
+ * return null; empty strings are passed through as usual. The intent is
+ * that properties with a non-zero number of attributes can be overridden for
+ * a specific server by supplying a zero-length string. For this to work,
+ * most callers are likely to want to check for both success and a
+ * non-empty string.
+ *
+ * Note that the one exception to this pattern is getAttributes, which
+ * throws NS_ERROR_FAILURE for non-existent property entries, since
+ * XPConnect doesn't like returning null arrays.
+ *
+ * Note that each LDAP attribute can map to at most one addressbook
+ * property. The checkState method is a useful tool in enforcing
+ * this. Failure to enforce it may make it impossible to guarantee
+ * that getProperty will do something consistent and reasonable.
+ *
+ * Maybe someday once we support ldap autoconfig stuff (ie
+ * draft-joslin-config-schema-11.txt), we can simplify this and other
+ * code and only allow a property to map to a single attribute.
+ */
+[scriptable, uuid(fa019fd1-7f3d-417a-8957-154cca0240be)]
+interface nsIAbLDAPAttributeMap : nsISupports
+{
+ /**
+ * Get all the LDAP attributes associated with a given property
+ * name, in order of precedence (highest to lowest).
+ *
+ * @param aProperty the address book property to return attrs for
+ *
+ * @return a comma-separated list of attributes, null if no entry is
+ * present
+ */
+ ACString getAttributeList(in ACString aProperty);
+
+ /**
+ * Get all the LDAP attributes associated with a given property name, in
+ * order of precedence (highest to lowest).
+ *
+ * @param aProperty the address book property to return attrs for
+ *
+ * @return an array of attributes
+ *
+ * @exception NS_ERROR_FAILURE if there is no entry for this property
+ */
+ void getAttributes(in ACString aProperty, out unsigned long aCount,
+ [retval, array, size_is(aCount)] out string aAttrs);
+
+ /**
+ * Get the first (canonical) LDAP attribute associated with a given property
+ * name
+ *
+ * @param aProperty the address book property to return attrs for
+ *
+ * @return the first attribute associated with a given property,
+ * null if there is no entry for this property
+ */
+ ACString getFirstAttribute(in ACString aProperty);
+
+ /**
+ * Set an existing mapping to the comma-separated list of attributes.
+ *
+ * @param aProperty the mozilla addressbook property name
+ *
+ * @param aAttributeList a comma-separated list of attributes in
+ * order of precedence from high to low
+ *
+ * @param aAllowInconsistencies allow changes that would result in
+ * a map with an LDAP attribute associated
+ * with more than one property. Useful for
+ * doing a bunch of sets at once, and
+ * calling checkState at the end.
+ *
+ * @exception NS_ERROR_FAILURE making this change would result in a map
+ * with an LDAP attribute pointing to more
+ * than one property
+ */
+ void setAttributeList(in ACString aProperty, in ACString aAttributeList,
+ in boolean allowInconsistencies);
+
+ /**
+ * Find the Mozilla addressbook property name that this attribute should
+ * map to.
+ *
+ * @return the addressbook property name, null if it's not used in the map
+ */
+ ACString getProperty(in ACString aAttribute);
+
+ /**
+ * Get all attributes that may be used in an addressbook card via this
+ * property map (used for passing to to an LDAP search when you want
+ * everything that could be in a card returned).
+ *
+ * @return a comma-separated list of attribute names
+ *
+ * @exception NS_ERROR_FAILURE there are no attributes in this property map
+ */
+ ACString getAllCardAttributes();
+
+ /**
+ * Get all properties that may be used in an addressbook card via this
+ * property map.
+ *
+ * @return an array of properties
+ *
+ * @exception NS_ERROR_FAILURE there are no attributes in this property map
+ */
+ void getAllCardProperties(out unsigned long aCount,
+ [retval, array, size_is(aCount)] out string aProps);
+
+ /**
+ * Check that no LDAP attributes are listed in more than one property.
+ *
+ * @exception NS_ERROR_FAILURE one or more LDAP attributes are listed
+ * multiple times. The object is now in an
+ * inconsistent state, and should be either
+ * manually repaired or discarded.
+ */
+ void checkState();
+
+ /* These last two methods are really just for the convenience of the caller
+ * and to avoid tons of unnecessary crossing of the XPConnect boundary.
+ */
+
+ /**
+ * Set any attributes specified in the given prefbranch on this object.
+ *
+ * @param aPrefBranchName the pref branch containing all the
+ * property names
+ *
+ * @exception NS_ERROR_FAILURE one or more LDAP attributes are listed
+ * multiple times. The object is now in an
+ * inconsistent state, and should be either
+ * manually repaired or discarded.
+ */
+ void setFromPrefs(in ACString aPrefBranchName);
+
+ /**
+ * Set the properties on an addressbook card from the given LDAP message
+ * using the map in this object.
+ *
+ * @param aCard is the card object whose values are to be set
+ * @param aMessage is the LDAP message to get the values from
+ *
+ * @exception NS_ERROR_FAILURE is thrown if no addressbook properties
+ * are found in the message
+ */
+ void setCardPropertiesFromLDAPMessage(in nsILDAPMessage aMessage,
+ in nsIAbCard aCard);
+};
+
+/**
+ * The nsIAbLDAPAttributeMapService is used to build and hold a cache
+ * of maps.
+ */
+[scriptable, uuid(12e2d589-3c2a-48e4-8c82-b1e6464a0dfd)]
+interface nsIAbLDAPAttributeMapService : nsISupports
+{
+ /**
+ * Accessor to construct or return a cached copy of the attribute
+ * map for a given preference branch. The map is constructed by
+ * first taking the default map (as specified by the
+ * "ldap_2.servers.default.attrmap" prefbranch), and then having any
+ * preferences specified by aPrefBranchName override the defaults.
+ * LDIF import and export code should use the default map.
+ *
+ * @return the requested map
+ *
+ * @exception NS_ERROR_FAILURE error constructing the map;
+ * possibly because of a failure
+ * from checkState()
+ */
+ nsIAbLDAPAttributeMap getMapForPrefBranch(in ACString aPrefBranchName);
+};
+
+
+%{C++
+// test whether one of the getters has actually found an attribute
+#define ATTRMAP_FOUND_ATTR(rv, str) (NS_SUCCEEDED(rv) && !(str).IsEmpty())
+%}
diff --git a/mailnews/addrbook/public/nsIAbLDAPCard.idl b/mailnews/addrbook/public/nsIAbLDAPCard.idl
new file mode 100644
index 000000000..c1e444291
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbLDAPCard.idl
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAbLDAPAttributeMap;
+interface nsILDAPModification;
+interface nsILDAPMessage;
+interface nsIArray;
+
+[scriptable, uuid(2831b3b0-30ef-4070-8ad3-90ae04980e11)]
+interface nsIAbLDAPCard : nsISupports
+{
+ /**
+ * Returns the required information for an LDAP update message.
+ *
+ * @param aAttrMap The map between LDAP attributes and card properties
+ * @param aClassCount The number of objectClass values
+ * @param aClasses The objectClass values that the card needs to have
+ * @param updateType This should be one of:
+ * nsILDAPModification::MOD_ADD
+ * nsILDAPModification::MOD_REPLACE
+ *
+ * @return Returns an array of modifications required to
+ * add or replace the card in the ldap directory.
+ */
+ nsIArray getLDAPMessageInfo(in nsIAbLDAPAttributeMap aAttrMap,
+ in unsigned long aClassCount,
+ [array, size_is(aClassCount)] in string aClasses,
+ in long updateType);
+
+ /**
+ * Builds a relative distinguished name (RDN) with the given set of
+ * attributes.
+ *
+ * @param aAttrMap The map between LDAP attributes and card properties
+ * @param aAttrCount The number of attributes to use for the RDN
+ * @param aAttributes The name of the attributes to use for the RDN
+ *
+ */
+ ACString buildRdn(in nsIAbLDAPAttributeMap aAttrMap,
+ in unsigned long aAttrCount,
+ [array, size_is(aAttrCount)] in string aAttributes);
+
+ /**
+ * Stores meta-properties from a raw LDAP search result.
+ *
+ * @param aMessage The LDAP search result message.
+ *
+ */
+ void setMetaProperties(in nsILDAPMessage aMessage);
+
+ attribute ACString dn;
+};
diff --git a/mailnews/addrbook/public/nsIAbLDAPDirectory.idl b/mailnews/addrbook/public/nsIAbLDAPDirectory.idl
new file mode 100644
index 000000000..1c95ff623
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbLDAPDirectory.idl
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMutableArray;
+interface nsIFile;
+interface nsIAddrDatabase;
+interface nsIAbLDAPAttributeMap;
+interface nsILDAPURL;
+
+%{C++
+#define kLDAPDirectoryRoot "moz-abldapdirectory://"
+#define kLDAPDirectoryRootLen 22
+%}
+
+/**
+ * XXX This should really inherit from nsIAbDirectory, and some day it will.
+ * But for now, doing that complicates implementation.
+ */
+[scriptable, uuid(90dde295-e354-4d58-Add8-f9b29a95942d)]
+interface nsIAbLDAPDirectory : nsISupports
+{
+ /**
+ * If set, these arrays of nsILDAPControls are passed through to the
+ * nsILDAPOperation that searchExt is called on.
+ */
+ attribute nsIMutableArray searchServerControls;
+ attribute nsIMutableArray searchClientControls;
+
+ /**
+ * The Replication File Name to use.
+ */
+ attribute ACString replicationFileName;
+
+ /**
+ * The version of LDAP protocol in use.
+ */
+ attribute unsigned long protocolVersion;
+
+ /**
+ * The SASL mechanism to use to authenticate to the LDAP server
+ * If this is an empty string, then a simple bind will be performed
+ * A non-zero string is assumed to be the name of the SASL mechanism.
+ * Currently the only supported mechanism is GSSAPI
+ */
+ attribute ACString saslMechanism;
+
+ /**
+ * The AuthDN to use to access the server.
+ */
+ attribute AUTF8String authDn;
+
+ /**
+ * The maximum number of matches that the server will return per a search.
+ */
+ attribute long maxHits;
+
+ /**
+ * The Last Change Number used for replication.
+ */
+ attribute long lastChangeNumber;
+
+ /**
+ * The LDAP server's scoping of the lastChangeNumber.
+ */
+ attribute ACString dataVersion;
+
+ /**
+ * The attribute map that is associated with this directory's server.
+ */
+ readonly attribute nsIAbLDAPAttributeMap attributeMap;
+
+ /**
+ * The LDAP URL for this directory. Note that this differs from
+ * nsIAbDirectory::URI. This attribute will give you a true ldap
+ * url, e.g. ldap://localhost:389/ whereas the uri will give you the
+ * directories rdf uri, e.g. moz-abldapdirectory://<pref base name>/.
+ */
+ attribute nsILDAPURL lDAPURL;
+
+ /**
+ * The replication (offline) file that this database uses.
+ */
+ readonly attribute nsIFile replicationFile;
+
+ /**
+ * A database that is set up for the replication file.
+ */
+ readonly attribute nsIAddrDatabase replicationDatabase;
+
+ /**
+ * The LDAP attributes used to build the Relative Distinguished Name
+ * of new cards, in the form of a comma separated list.
+ *
+ * The default is to use the common name (cn) attribute.
+ */
+ attribute ACString rdnAttributes;
+
+ /**
+ * The LDAP objectClass values added to cards when they are created/added,
+ * in the form of a comma separated list.
+ *
+ * The default is to use the following classes:
+ * top,person,organizationalPerson,inetOrgPerson,mozillaAbPersonAlpha
+ */
+ attribute ACString objectClasses;
+
+};
+
diff --git a/mailnews/addrbook/public/nsIAbLDAPReplicationData.idl b/mailnews/addrbook/public/nsIAbLDAPReplicationData.idl
new file mode 100644
index 000000000..4b811cf2a
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbLDAPReplicationData.idl
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAbLDAPDirectory;
+interface nsILDAPConnection;
+interface nsILDAPURL;
+interface nsIAbLDAPReplicationQuery;
+interface nsIWebProgressListener;
+
+/**
+ * this service does replication of an LDAP directory to a local Mork AB Database.
+ */
+[scriptable, uuid(e628bbc9-8793-4f0b-bce4-990d399b1fca)]
+interface nsIAbLDAPProcessReplicationData : nsISupports
+{
+ /**
+ * readonly attribute giving the current replication state
+ */
+ readonly attribute int32_t replicationState;
+
+ /**
+ * replication states
+ */
+ const long kIdle = 0;
+ const long kAnonymousBinding = 1;
+ const long kAuthenticatedBinding = 2;
+ const long kSyncServerBinding = 3;
+ const long kSearchingAuthDN = 4;
+ const long kDecidingProtocol = 5;
+ const long kAuthenticating = 6;
+ const long kReplicatingAll = 7;
+ const long kSearchingRootDSE = 8;
+ const long kFindingChanges = 9;
+ const long kReplicatingChanges = 10;
+ const long kReplicationDone = 11;
+
+ /**
+ * readonly attribute giving the current protocol used
+ */
+ readonly attribute int32_t protocolUsed ;
+
+ /**
+ * replication protocols
+ */
+ const long kDefaultDownloadAll = 0;
+ const long kChangeLogProtocol = 1;
+ const long kLCUPProtocol = 2;
+ const long kLastUpdatedTimeStampMethod = 3;
+
+ /**
+ * this method initializes the implementation
+ */
+ void init(in nsIAbLDAPDirectory directory,
+ in nsILDAPConnection connection,
+ in nsILDAPURL url,
+ in nsIAbLDAPReplicationQuery query,
+ in nsIWebProgressListener progressListener);
+
+ /**
+ * this method a aborts the ongoing processing
+ */
+ void abort();
+};
+
diff --git a/mailnews/addrbook/public/nsIAbLDAPReplicationQuery.idl b/mailnews/addrbook/public/nsIAbLDAPReplicationQuery.idl
new file mode 100644
index 000000000..087e81bdd
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbLDAPReplicationQuery.idl
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIWebProgressListener;
+interface nsILDAPURL;
+interface nsILDAPConnection;
+interface nsILDAPOperation;
+interface nsIAbLDAPDirectory;
+
+/**
+ * this interface provides methods to perform LDAP Replication Queries
+ */
+[scriptable, uuid(460a739c-a8c1-4f24-b705-c89d136ab9f5)]
+interface nsIAbLDAPReplicationQuery : nsISupports
+{
+ /**
+ * initialize for the query
+ */
+ void init(in nsIAbLDAPDirectory aDirectory,
+ in nsIWebProgressListener aProgressListener);
+
+ /**
+ * Starts an LDAP query to do replication as needed
+ */
+ void doReplicationQuery();
+
+ /**
+ * Cancels the currently executing query
+ */
+ void cancelQuery();
+
+ /**
+ * this method is the callback when query is done, failed or successful
+ */
+ void done(in boolean aSuccess);
+};
+
+// XXX This interface currently isn't implemented as it didn't work.
+// Bug 311632 should fix it
+[scriptable, uuid(126202D1-4460-11d6-B7C2-00B0D06E5F27)]
+interface nsIAbLDAPChangeLogQuery : nsISupports
+{
+ /**
+ * Starts an LDAP query to find auth DN
+ */
+ void queryAuthDN(in AUTF8String aValueUsedToFindDn);
+
+ /**
+ * Starts an LDAP query to search server's Root DSE
+ */
+ void queryRootDSE();
+
+ /**
+ * Starts an LDAP ChangeLog query to find changelog entries
+ */
+ void queryChangeLog(in AUTF8String aChangeLogDN, in int32_t aLastChangeNo);
+
+ /**
+ * Starts an LDAP query to find changed entries
+ */
+ void queryChangedEntries(in AUTF8String aChangedEntryDN);
+};
+
diff --git a/mailnews/addrbook/public/nsIAbLDAPReplicationService.idl b/mailnews/addrbook/public/nsIAbLDAPReplicationService.idl
new file mode 100644
index 000000000..b58001538
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbLDAPReplicationService.idl
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIWebProgressListener;
+interface nsIAbLDAPDirectory;
+
+/**
+ * this service does replication of an LDAP directory to a local Mork AB Database.
+ */
+[scriptable, uuid(3f499c70-5ceb-4b91-8b7f-62c366859383)]
+interface nsIAbLDAPReplicationService: nsISupports {
+
+ /**
+ * Start Replication of given LDAP directory represented by the URI
+ */
+ void startReplication(in nsIAbLDAPDirectory aDirectory,
+ in nsIWebProgressListener progressListener);
+
+ /**
+ * Cancel Replication of given LDAP directory represented by the URI
+ */
+ void cancelReplication(in nsIAbLDAPDirectory aDirectory);
+
+ /**
+ * callback when replication is done, failure or success
+ */
+ void done(in boolean aSuccess);
+};
+
diff --git a/mailnews/addrbook/public/nsIAbLDIFService.idl b/mailnews/addrbook/public/nsIAbLDIFService.idl
new file mode 100644
index 000000000..74643ccbd
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbLDIFService.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIAddrDatabase;
+
+[scriptable, uuid(7afaa95f-0b1c-4d8a-a65f-bb5073ed6d39)]
+interface nsIAbLDIFService : nsISupports {
+
+ /**
+ * Determine if a file is likely to be an LDIF file based on field
+ * names that commonly appear in LDIF files.
+ *
+ * @param aSrc The file to examine
+ *
+ * @return true if the file appears to be of LDIF type,
+ * false otherwise
+ */
+ boolean isLDIFFile(in nsIFile aSrc);
+
+ /**
+ * Imports a file into the specified address book.
+ *
+ * @param aDb The address book to import addresses into.
+ *
+ * @param aSrc The file to import addresses from.
+ *
+ * @param aStoreLocAsHome Stores the address as a home rather than work
+ * address.
+ *
+ * @param aProgress May be null, but if a pointer is supplied,
+ * then it will be updated regularly with the
+ * current position of reading from the file.
+ *
+ */
+ void importLDIFFile(in nsIAddrDatabase aDb, in nsIFile aSrc, in boolean aStoreLocAsHome, inout unsigned long aProgress);
+};
diff --git a/mailnews/addrbook/public/nsIAbListener.idl b/mailnews/addrbook/public/nsIAbListener.idl
new file mode 100644
index 000000000..cc9761abf
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbListener.idl
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+typedef unsigned long abListenerNotifyFlagValue;
+
+/**
+ * nsIAbListener
+ *
+ * Implement this interface to receive notifications of address book
+ * items being added, removed or changed with loaded address books.
+ *
+ * Subscribe to events by using nsIAbManager.
+ */
+[scriptable, uuid(b3ca8745-2dad-4032-ae2f-0b8622f32697)]
+interface nsIAbListener : nsISupports {
+ /**
+ * These flags are used when registering the listener with nsIAbManager to
+ * specify when to receive notifications of address book updates.
+ */
+
+ /**
+ * An address book, mailing list or card is added.
+ */
+ const abListenerNotifyFlagValue itemAdded = 0x1;
+ /**
+ * A mailing list or card is removed from an address book.
+ */
+ const abListenerNotifyFlagValue directoryItemRemoved = 0x2;
+ /**
+ * An address book is removed
+ */
+ const abListenerNotifyFlagValue directoryRemoved = 0x4;
+ /**
+ * An address book, mailing list or card is changed.
+ */
+ const abListenerNotifyFlagValue itemChanged = 0x8;
+ /**
+ * All of the above notifications are to be received.
+ */
+ const abListenerNotifyFlagValue all = 0xFFFFFFFF;
+
+ /**
+ * Called when an address book item (book, card or list) is added
+ *
+ * @param parentDir The parent of the item being added.
+ *
+ * @param item The item being added to the database (a
+ * directory or card).
+ *
+ */
+ void onItemAdded(in nsISupports parentDir, in nsISupports item);
+
+ /**
+ * Called when an address book, mailing list or card is removed. This
+ * is partially configurable when setting up the listener via
+ * nsIAddrBookSession
+ *
+ * @param parentDir The parent of the item being removed, this
+ * may be an empty directory in the case of a
+ * top level address book.
+ *
+ * @param item The item being removed from the database.
+ *
+ */
+ void onItemRemoved(in nsISupports parentDir, in nsISupports item);
+
+ /**
+ * Called when an address book item is changed. Note the current
+ * implementation means that property is either the literal string "DirName"
+ * or null, with oldValue and newValue being specified if the property is
+ * "DirName" otherwise they are null.
+ *
+ * @param item The item being updated (a directory or a
+ * card).
+ *
+ * @param property The property of the item being changed.
+ *
+ * @param oldValue The old value of the item property being
+ * changed if it is known, null otherwise.
+ *
+ * @param newValue The new value of the item property being
+ * changed.
+ *
+ */
+ void onItemPropertyChanged(in nsISupports item, in string property, in wstring oldValue, in wstring newValue);
+};
diff --git a/mailnews/addrbook/public/nsIAbMDBDirectory.idl b/mailnews/addrbook/public/nsIAbMDBDirectory.idl
new file mode 100644
index 000000000..200fcd8ee
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbMDBDirectory.idl
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIAbDirectory;
+interface nsIAbCard;
+interface nsIAddrDatabase;
+
+%{C++
+#define kMDBDirectoryRoot "moz-abmdbdirectory://"
+#define kMDBDirectoryRootLen 21
+%}
+
+[scriptable, uuid(744072be-1ba0-46bc-af24-46e22567a2ea)]
+interface nsIAbMDBDirectory : nsISupports {
+
+ // Creates a directory component from the
+ // uriName, adds it to its children and returns
+ // the component
+ nsIAbDirectory addDirectory(in string uriName);
+
+ /**
+ * Supplies a nsIFile point to the database file for this directory
+ *
+ * @exception NS_ERROR_NOT_INITIALIZED If there is no filename preference
+ * present or it is empty
+ */
+ readonly attribute nsIFile databaseFile;
+
+ /**
+ * Supplies a nsIAddrDatabase that uses the databaseFile. See also
+ * databaseFile for possible exceptions.
+ */
+ readonly attribute nsIAddrDatabase database;
+
+ // Mail list specific
+ //
+
+ // Removes all elements from the addressLists
+ // property
+ [noscript] void removeElementsFromAddressList();
+
+ // Specific to a directory which stores mail lists
+ //
+
+ // Adds a directory to the addressLists attribute
+ void addMailListToDirectory(in nsIAbDirectory mailList);
+
+ // Specific to a directory which is a mail list
+ //
+
+ // Copies mail list properties from the srcList
+ void copyDBMailList(in nsIAbMDBDirectory srcListDB);
+
+ // Adds a card to the addressList attribute
+ void addAddressToList(in nsIAbCard card);
+
+ // Removes items from the addressLists member
+ void removeEmailAddressAt(in unsigned long aIndex);
+
+ attribute unsigned long dbRowID;
+
+ // Empty implementation, called by the data base
+ [noscript] void notifyDirItemAdded(in nsISupports item);
+
+ [noscript] void clearDatabase();
+};
diff --git a/mailnews/addrbook/public/nsIAbManager.idl b/mailnews/addrbook/public/nsIAbManager.idl
new file mode 100644
index 000000000..49a585544
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbManager.idl
@@ -0,0 +1,190 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIAbListener.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIAbDirectory;
+interface nsIAbCard;
+interface nsIAbDirectoryProperties;
+interface nsIFile;
+interface nsISimpleEnumerator;
+interface nsIAbBooleanExpression;
+
+/**
+ * nsIAbManager is an interface to the main address book mananger
+ * via the contract id "@mozilla.org/abmanager;1"
+ *
+ * It contains the main functions to create and delete address books as well
+ * as some helper functions.
+ */
+[scriptable, uuid(ea0d8b3d-a549-4874-82d8-3a82cee2a3f1)]
+interface nsIAbManager : nsISupports
+{
+ /**
+ * Returns an enumerator containing all the top-level directories
+ * (non-recursive)
+ */
+ readonly attribute nsISimpleEnumerator directories;
+
+ /**
+ * Returns the directory that represents the supplied URI.
+ *
+ * @param aURI The URI of the address book to find.
+ * @return The found address book.
+ */
+ nsIAbDirectory getDirectory(in ACString aURI);
+
+ /**
+ * Returns the directory that has the supplied dirPrefId.
+ *
+ * @param aDirPrefId The dirPrefId of the directory.
+ * @return The found AB directory.
+ */
+ nsIAbDirectory getDirectoryFromId(in ACString aDirPrefId);
+
+ /**
+ * Creates a new address book.
+ *
+ * @param aDirName The description of the address book.
+ * @param aURI The URI for the address book. This is specific to each
+ * type of address book.
+ * @param aType The type of the address book (see nsDirPrefs.h)
+ * @param aPrefName Overrides the default of ldap_2.servers.<aDirName>
+ * (note that the caller must ensure its uniqueness).
+ */
+ ACString newAddressBook(in AString aDirName, in ACString aURI,
+ in unsigned long aType,
+ [optional] in ACString aPrefName);
+
+ /**
+ * Deletes an address book.
+ *
+ * @param aURI The URI for the address book. This is specific to each
+ * type of address book.
+ */
+ void deleteAddressBook(in ACString aURI);
+
+ /**
+ * Exports an address book, it will provide a dialog to the user for the
+ * location to save the file to and will then save the address book to media.
+ *
+ * @param aParentWin Parent Window for the file save dialog to use.
+ * @param aDirectory The directory to export.
+ */
+ void exportAddressBook(in mozIDOMWindowProxy aParentWin, in nsIAbDirectory aDirectory);
+
+ /**
+ * Adds a nsIAbListener to receive notifications of address book updates
+ * according to the specified notifyFlags.
+ *
+ * @param aListener The listener that is to receive updates.
+ * @param aNotifyFlags A bitwise-or of abListenerNotifyFlagValue items
+ * specifying which notifications to receive. See
+ * nsIAbListener for possible values.
+ */
+ void addAddressBookListener(in nsIAbListener aListener,
+ in abListenerNotifyFlagValue aNotifyFlags);
+
+ /**
+ * Removes a nsIAbListener from receive notifications of address book
+ * updates.
+ *
+ * @param aListener The listener that is to no longer receive updates.
+ */
+ void removeAddressBookListener(in nsIAbListener aListener);
+
+ /**
+ * Call to notify the registered listeners when a property on an item has
+ * changed.
+ *
+ * @param aItem The items that has changed (e.g. an nsIAbDirectory)
+ * @param aProperty The property that has changed (e.g. DirName)
+ * @param aOldValue The old value of the property.
+ * @param aNewValue The new value of the property.
+ */
+ void notifyItemPropertyChanged(in nsISupports aItem,
+ in string aProperty,
+ in wstring aOldValue,
+ in wstring aNewValue);
+
+ /**
+ * Call to notify the registered listeners when a directory item is added.
+ *
+ * @param aParentDirectory The parent directory of the item that has been
+ * added.
+ * @param aItem The item that has been added.
+ */
+ void notifyDirectoryItemAdded(in nsIAbDirectory aParentDirectory,
+ in nsISupports aItem);
+
+ /**
+ * Call to notify the registered listeners when a directory item is removed.
+ *
+ * @param aParentDirectory The parent directory of the item that has been
+ * removed.
+ * @param aItem The item that has been removed.
+ */
+ void notifyDirectoryItemDeleted(in nsIAbDirectory aParentDirectory,
+ in nsISupports aItem);
+
+ /**
+ * Call to notify the registered listeners when a directory is removed.
+ *
+ * @param aParentDirectory The parent directory of the directory that has
+ * been removed.
+ * @param aDirectory The directory that has been removed.
+ */
+ void notifyDirectoryDeleted(in nsIAbDirectory aParentDirectory,
+ in nsISupports aDirectory);
+
+ /**
+ * Returns the user profile directory. NOTE: this should not be used
+ * as it may go away soon.
+ */
+ readonly attribute nsIFile userProfileDirectory;
+
+ /**
+ * Finds out if the mailing list name exists in any *mork/MDB* based
+ * address book
+ *
+ * @param aName The name of the list to try and find.
+ *
+ * @return True if the name exists.
+ */
+ boolean mailListNameExists(in wstring name);
+
+ /**
+ * Translates an escaped vcard string into a nsIAbCard.
+ *
+ * @param escapedVCardStr The string containing the vcard.
+ *
+ * @return A card containing the translated vcard data.
+ */
+ nsIAbCard escapedVCardToAbCard(in string escapedVCardStr);
+
+ /**
+ * Generates a UUID from a (directory ID, local ID) tuple.
+ *
+ * Use of this method is preferred in such cases, since it is designed to work
+ * with other methods of this interface.
+ *
+ * @param directoryId The directory ID.
+ * @param localId The per-directory ID.
+ * @return A string to use for the UUID.
+ */
+ AUTF8String generateUUID(in AUTF8String directoryId, in AUTF8String localId);
+
+
+ /**
+ * A utility function that converts an nsIAbDirectory query string to an
+ * nsIAbBooleanExpression.
+ *
+ * @param aQueryString The nsIAbDirectory query string
+ * @return an nsIAbBooleanExpression for the query string
+ */
+ nsIAbBooleanExpression convertQueryStringToExpression(in AUTF8String aQueryString);
+};
diff --git a/mailnews/addrbook/public/nsIAbView.idl b/mailnews/addrbook/public/nsIAbView.idl
new file mode 100644
index 000000000..cd591a1e5
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAbView.idl
@@ -0,0 +1,109 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAbCard;
+interface nsIAbDirectory;
+interface nsIArray;
+
+/// Define a class using this interface to listen to updates from nsIAbView.
+[scriptable, uuid(79ad5d6e-1dd2-11b2-addd-f547dab50d75)]
+interface nsIAbViewListener : nsISupports
+{
+ /// Called when the selection is changed in the tree
+ void onSelectionChanged();
+
+ /// Called when the total count of cards is changed.
+ void onCountChanged(in long total);
+};
+
+/**
+ * This interface and its associated nsAbView object provides an interface
+ * to allow a tree to be associated with an address book, and the results
+ * to be displayed in that tree.
+ *
+ * If you wish for the tree to display the results of a different address
+ * book, then call setView again. There is no need to delete and recreate the
+ * nsAbView object. If you wish to clear the view, then just call clearView.
+ */
+[scriptable, uuid(45e2fa9f-0b59-4090-a2fa-fb7042cf64a2)]
+interface nsIAbView : nsISupports
+{
+ /**
+ * Sets up the nsIAbView to look at the specified directory. This may be
+ * called multiple times.
+ *
+ * @param aDirectory The directory to search, this may be a directory
+ * with a query string.
+ * @param aViewListener An optional listener.
+ * @param aSortColumn The column to sort by. See the xul element with
+ * id abResultsTreeCols for possible values.
+ * @param aSortDirection The sort direction to use ("ascending"/"descending")
+ * @return The actual sortColumn (various switching of apps
+ * could cause the persisted sortColumn to be bogus).
+ */
+ AString setView(in nsIAbDirectory aAddressBook,
+ in nsIAbViewListener aAbViewListener,
+ in AString aSortColumn,
+ in AString aSortDirection);
+
+ /**
+ * Clears the view and releases any locally held copies of the address book
+ * directory. This should be called when the view is no longer required, e.g.
+ * on unload.
+ */
+ void clearView();
+
+ /**
+ * Sorts the tree by the specified parameters.
+ *
+ * @param aSortColumn The column to sort by. See the xul element with
+ * id abResultsTreeCols for possible values.
+ * @param aSortDirection The sort direction to use ("ascending"/"descending")
+ * @param aResort The function DOES optimize for the case when sortColumn
+ * and sortDirection is identical since the last call.
+ * If an unconditional resort is needed, set this to true.
+ */
+ void sortBy(in wstring aSortColumn, in wstring aSortDirection,
+ [optional] in boolean aResort);
+
+ /// Returns the current sort column
+ readonly attribute AString sortColumn;
+
+ /// Returns the current sort direction
+ readonly attribute AString sortDirection;
+
+ /**
+ * Returns the current directory that this view is hooked up to. May be
+ * null if no directory has been set.
+ */
+ readonly attribute nsIAbDirectory directory;
+
+ /**
+ * Returns the card associated with the given row.
+ *
+ * @param aRow The row from which to return the card.
+ * @return A card associated with the row, or null if row is not valid.
+ */
+ nsIAbCard getCardFromRow(in long aRow);
+
+ /// Selects all rows in the view.
+ void selectAll();
+
+ /// Deletes all the selected cards (no prompts are given).
+ void deleteSelectedCards();
+
+ /**
+ * Swaps the first and last name order, and updates the appropriate
+ * preference.
+ */
+ void swapFirstNameLastName();
+
+ /**
+ * Returns an array of the currently selected addresses.
+ */
+ readonly attribute nsIArray selectedAddresses;
+};
diff --git a/mailnews/addrbook/public/nsIAddbookUrl.idl b/mailnews/addrbook/public/nsIAddbookUrl.idl
new file mode 100644
index 000000000..5f2676dde
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAddbookUrl.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURI.idl"
+
+[scriptable, uuid(6EB9D874-01AA-11d4-8FBE-000064657374)]
+interface nsIAddbookUrlOperation
+{
+ const long InvalidUrl = 0;
+ const long PrintAddressBook = 1;
+ const long AddVCard = 2;
+};
+
+[uuid(5f965083-e866-4bfb-ba40-13c344395798)]
+interface nsIAddbookUrl : nsIURI {
+ readonly attribute long addbookOperation;
+};
diff --git a/mailnews/addrbook/public/nsIAddrDBAnnouncer.idl b/mailnews/addrbook/public/nsIAddrDBAnnouncer.idl
new file mode 100644
index 000000000..735ca64cb
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAddrDBAnnouncer.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIAbCard.idl"
+#include "nsIAbDirectory.idl"
+
+interface nsIAddrDBListener;
+
+[scriptable, uuid(166b19a1-1235-4613-9601-816dedc48c9e)]
+interface nsIAddrDBAnnouncer : nsISupports {
+
+ void addListener(in nsIAddrDBListener listener);
+ void removeListener(in nsIAddrDBListener listener);
+
+ void notifyCardAttribChange(in unsigned long abCode);
+
+ /**
+ * Notify all the listeners of the database about an event performed
+ * on a card entry.
+ *
+ * @param aAbCode The code to indicate the type of event
+ * (see nsAddrDatabase.h AB_NOTIFY_CODE for values).
+ * @param aCard The card entry on which the event occurred.
+ * @param aParent The parent of card entry. This is set by
+ * object which performs the operation.
+ */
+ void notifyCardEntryChange(in unsigned long aAbCode,
+ in nsIAbCard aCard,
+ in nsIAbDirectory aParent);
+
+ void notifyAnnouncerGoingAway();
+};
diff --git a/mailnews/addrbook/public/nsIAddrDBListener.idl b/mailnews/addrbook/public/nsIAddrDBListener.idl
new file mode 100644
index 000000000..f407313e2
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAddrDBListener.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+#include "nsIAbCard.idl"
+#include "nsIAbDirectory.idl"
+
+interface nsIAddrDBAnnouncer;
+
+[scriptable, uuid(5d7e5a7a-1ac9-46dc-abfd-758c98be26e9)]
+interface nsIAddrDBListener : nsISupports {
+
+ void onCardAttribChange(in unsigned long abCode);
+
+ /**
+ * Handle the card entry change event.
+ *
+ * @param aAbCode The code to indicate the type of event
+ * (see nsAddrDatabase.h AB_NOTIFY_CODE for values).
+ * @param aCard The card entry on which the event occurred.
+ * @param aParent The parent of card entry.
+ * If set to null, the event can be ignored.
+ * This happens during import & sync operations when
+ * listeners of a database need not be notified about
+ * card entry changes.
+ */
+ void onCardEntryChange (in unsigned long aAbCode,
+ in nsIAbCard aCard,
+ in nsIAbDirectory aParent);
+
+ void onListEntryChange (in unsigned long abCode,
+ in nsIAbDirectory list);
+ void onAnnouncerGoingAway();
+
+};
diff --git a/mailnews/addrbook/public/nsIAddrDatabase.idl b/mailnews/addrbook/public/nsIAddrDatabase.idl
new file mode 100644
index 000000000..e387b260f
--- /dev/null
+++ b/mailnews/addrbook/public/nsIAddrDatabase.idl
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAddrDBAnnouncer.idl"
+#include "nsIAbCard.idl"
+#include "nsIAbDirectory.idl"
+
+interface nsIFile;
+interface nsIMdbTableRowCursor;
+interface nsIMdbEnv;
+interface nsIMdbRow;
+interface nsIArray;
+interface nsISimpleEnumerator;
+
+%{C++
+// this is the prefix we for attributes that are specific
+// to the mozilla addressbook, and weren't in 4.x and aren't specified in
+// RFC 2789. used when exporting and import LDIF
+// see nsTextAddress.cpp, nsAddressBook.cpp
+#define MOZ_AB_LDIF_PREFIX "mozilla"
+
+// note, GeneratedName is not a real column
+// if you change any of this, make sure to change
+// Get / Set CardValue in nsAbCardProperty.cpp
+#define kPhoneticNameColumn "_PhoneticName"
+#define kAddressCharSetColumn "AddrCharSet"
+#define kMailListName "ListName"
+#define kMailListNickName "ListNickName"
+#define kMailListDescription "ListDescription"
+#define kMailListTotalAddresses "ListTotalAddresses"
+// not shown in the UI
+#define kLowerPriEmailColumn "LowercasePrimaryEmail"
+#define kLower2ndEmailColumn "LowercaseSecondEmail"
+
+// Palm Integration
+#define CARD_ATTRIB_PALMID "PalmRecId"
+#define CARD_ATTRIB_DISPLAY "DisplayName"
+
+%}
+
+[scriptable, uuid(20d4c6c3-0460-403e-aa9c-813654641566)]
+interface nsAddrDBCommitType
+{
+ const long kLargeCommit = 1;
+ const long kSessionCommit = 2;
+ const long kCompressCommit = 3;
+};
+
+[scriptable, uuid(c54973e4-d251-4b93-a0d0-81a616225061)]
+interface nsIAddrDatabase : nsIAddrDBAnnouncer {
+
+ /**
+ * Path to the address book database that this instance represents.
+ */
+ attribute nsIFile dbPath;
+ nsIAddrDatabase open(in nsIFile dbFile, in boolean create, in boolean upgrading);
+
+ void close(in boolean forceCommit);
+
+ /**
+ * Open the MDB database synchronously creating it if required. If
+ * successful, this routine will set up the m_mdbStore and m_mdbEnv of the
+ * database object so other database calls can work.
+ *
+ * @param dbName The location of the database file
+ * to open.
+ * @param create If set to true, will create the
+ * database file if it does not
+ * already exist.
+ * @exception NS_ERROR_FILE_NOT_FOUND The file was not found at the
+ * specified location (and create was
+ * false).
+ * @exception NS_ERROR_FILE_ACCESS_DENIED The file could not be opened as
+ * access was denied.
+ */
+ void openMDB(in nsIFile dbName, in boolean create);
+ void closeMDB(in boolean commit);
+
+ void commit(in unsigned long commitType);
+ void forceClosed();
+
+ /**
+ * Create a new card and add to the database
+ *
+ * @param aNewCard the card to be added
+ * @param aNotify if set to true, all the listeners of the
+ * database will be notified.
+ * @param aParent parent directory or mailing list to which the
+ * card is added. If set to null, listeners of the
+ * database will not be notified of card creation.
+ */
+ void createNewCardAndAddToDB(in nsIAbCard aNewCard, in boolean aNotify, in nsIAbDirectory aParent);
+
+ void createNewListCardAndAddToDB(in nsIAbDirectory list, in unsigned long listRowID, in nsIAbCard newCard, in boolean aNotify);
+
+ /**
+ * Create a new mailing list and add to the database
+ *
+ * @param aNewList the mailing list to be added.
+ * @param aNotify if set to true, all the listeners of the
+ * database will be notified.
+ * @param aParent parent directory to which the mailing list
+ * is added. If set to null, listeners of the database
+ * will not be notified of mailing list creation.
+ */
+ void createMailListAndAddToDB(in nsIAbDirectory aNewList, in boolean aNotify, in nsIAbDirectory aParent);
+
+ /**
+ * Enumerate the cards in the directory. The enumerator will return the
+ * cards associated with mailing lists too.
+ *
+ * @param directory the directory of which to enumerate the cards.
+ * @return an enumerator.
+ */
+ nsISimpleEnumerator enumerateCards(in nsIAbDirectory directory);
+
+ /**
+ * Enumerate the cards associated with the mailing lists in the directory.
+ *
+ * @param directory the directory of which to enumerate the cards.
+ * @return an enumerator.
+ */
+ nsISimpleEnumerator enumerateListAddresses(in nsIAbDirectory directory);
+
+ void getMailingListsFromDB(in nsIAbDirectory parentDir);
+
+ /**
+ * Delete a card from the database.
+ *
+ * @param aCard the card to be deleted.
+ * @param aNotify if set to true, all the listeners of the
+ * database will be notified.
+ * @param aParent parent directory from which the card
+ * is to be deleted. If set to null, listeners of
+ * the database will not be notified of card deletion.
+ */
+ void deleteCard(in nsIAbCard aCard, in boolean aNotify, in nsIAbDirectory aParent);
+
+ /**
+ * Edit a card in the database.
+ *
+ * @param aCard the card to be edited.
+ * @param aNotify if set to true, all the listeners of the
+ * database will be notified.
+ * @param aParent parent directory in which the card
+ * is to be edited. If set to null, listeners of
+ * the database will not be notified of card entry
+ * change.
+ */
+ void editCard(in nsIAbCard aCard, in boolean aNotify, in nsIAbDirectory aParent);
+ boolean containsCard(in nsIAbCard card);
+ /**
+ * Deletes a mailing list from the directory
+ *
+ * @param aMailList The nsIAbDirectory implementation of the mailing
+ * list that is to be deleted.
+ * @param aParent The parent of the mailing list that is being
+ * deleted. If this is supplied, then a notification
+ * of card entry change in the database will be made.
+ */
+ void deleteMailList(in nsIAbDirectory aMailList,
+ [optional] in nsIAbDirectory aParent);
+ void editMailList(in nsIAbDirectory mailList, in nsIAbCard listCard, in boolean aNotify);
+ boolean containsMailList(in nsIAbDirectory mailList);
+ void deleteCardFromMailList(in nsIAbDirectory mailList, in nsIAbCard card, in boolean aNotify);
+
+ /**
+ * Gets the first card which matches the attribute/value pair supplied.
+ *
+ * @param aDirectory The current nsIAbDirectory associated with this
+ * instance of the database.
+ * @param aName The attribute to look up the value in.
+ * @param aUTF8Value The value to look up in UTF8 format.
+ * @param aCaseInsensitive Set to true for case-insenstive matching.
+ * @result Returns an nsIAbCard if one is found, otherwise
+ * NULL.
+ */
+ nsIAbCard getCardFromAttribute(in nsIAbDirectory aDirectory, in string aName,
+ in AUTF8String aUTF8Value,
+ in boolean aCaseInsensitive);
+
+ /**
+ * Gets all cards which matches the attribute/value pair supplied.
+ *
+ * @param aDirectory The current nsIAbDirectory associated with this
+ * instance of the database.
+ * @param aName The attribute to look up the value in.
+ * @param aUTF8Value The value to look up in UTF8 format.
+ * @param aCaseInsensitive Set to true for case-insenstive matching.
+ * @result Returns an nsISimpleEnumerator of nsIAbCard
+ * instances.
+ */
+ nsISimpleEnumerator getCardsFromAttribute(in nsIAbDirectory aDirectory,
+ in string aName,
+ in AUTF8String uUTF8Value,
+ in boolean aCaseInsensitive);
+
+ boolean findMailListbyUnicodeName(in wstring listName);
+
+ void getCardCount(out uint32_t count);
+
+ [noscript] readonly attribute nsIMdbRow newRow;
+ [noscript] readonly attribute nsIMdbRow newListRow;
+ [noscript] void addCardRowToDB(in nsIMdbRow newRow);
+ [noscript] void addLdifListMember(in nsIMdbRow row, in string value);
+ [noscript] void addFirstName(in nsIMdbRow row, in string value);
+ [noscript] void addLastName(in nsIMdbRow row, in string value);
+ [noscript] void addPhoneticFirstName(in nsIMdbRow row, in string value);
+ [noscript] void addPhoneticLastName(in nsIMdbRow row, in string value);
+ [noscript] void addDisplayName(in nsIMdbRow row, in string value);
+ [noscript] void addNickName(in nsIMdbRow row, in string value);
+ [noscript] void addPrimaryEmail(in nsIMdbRow row, in string value);
+ [noscript] void add2ndEmail(in nsIMdbRow row, in string value);
+ [noscript] void addWorkPhone(in nsIMdbRow row, in string value);
+ [noscript] void addHomePhone(in nsIMdbRow row, in string value);
+ [noscript] void addFaxNumber(in nsIMdbRow row, in string value);
+ [noscript] void addPagerNumber(in nsIMdbRow row, in string value);
+ [noscript] void addCellularNumber(in nsIMdbRow row, in string value);
+ [noscript] void addWorkPhoneType(in nsIMdbRow row, in string value);
+ [noscript] void addHomePhoneType(in nsIMdbRow row, in string value);
+ [noscript] void addFaxNumberType(in nsIMdbRow row, in string value);
+ [noscript] void addPagerNumberType(in nsIMdbRow row, in string value);
+ [noscript] void addCellularNumberType(in nsIMdbRow row, in string value);
+ [noscript] void addHomeAddress(in nsIMdbRow row, in string value);
+ [noscript] void addHomeAddress2(in nsIMdbRow row, in string value);
+ [noscript] void addHomeCity(in nsIMdbRow row, in string value);
+ [noscript] void addHomeState(in nsIMdbRow row, in string value);
+ [noscript] void addHomeZipCode(in nsIMdbRow row, in string value);
+ [noscript] void addHomeCountry(in nsIMdbRow row, in string value);
+ [noscript] void addWorkAddress(in nsIMdbRow row, in string value);
+ [noscript] void addWorkAddress2(in nsIMdbRow row, in string value);
+ [noscript] void addWorkCity(in nsIMdbRow row, in string value);
+ [noscript] void addWorkState(in nsIMdbRow row, in string value);
+ [noscript] void addWorkZipCode(in nsIMdbRow row, in string value);
+ [noscript] void addWorkCountry(in nsIMdbRow row, in string value);
+ [noscript] void addJobTitle(in nsIMdbRow row, in string value);
+ [noscript] void addDepartment(in nsIMdbRow row, in string value);
+ [noscript] void addCompany(in nsIMdbRow row, in string value);
+ [noscript] void addAimScreenName(in nsIMdbRow row, in string value);
+ [noscript] void addAnniversaryYear(in nsIMdbRow row, in string value);
+ [noscript] void addAnniversaryMonth(in nsIMdbRow row, in string value);
+ [noscript] void addAnniversaryDay(in nsIMdbRow row, in string value);
+ [noscript] void addSpouseName(in nsIMdbRow row, in string value);
+ [noscript] void addFamilyName(in nsIMdbRow row, in string value);
+ [noscript] void addDefaultAddress(in nsIMdbRow row, in string value);
+ [noscript] void addCategory(in nsIMdbRow row, in string value);
+ [noscript] void addWebPage1(in nsIMdbRow row, in string value);
+ [noscript] void addWebPage2(in nsIMdbRow row, in string value);
+ [noscript] void addBirthYear(in nsIMdbRow row, in string value);
+ [noscript] void addBirthMonth(in nsIMdbRow row, in string value);
+ [noscript] void addBirthDay(in nsIMdbRow row, in string value);
+ [noscript] void addCustom1(in nsIMdbRow row, in string value);
+ [noscript] void addCustom2(in nsIMdbRow row, in string value);
+ [noscript] void addCustom3(in nsIMdbRow row, in string value);
+ [noscript] void addCustom4(in nsIMdbRow row, in string value);
+ [noscript] void addNotes(in nsIMdbRow row, in string value);
+ [noscript] void addPreferMailFormat(in nsIMdbRow row, in unsigned long value);
+ [noscript] void addPopularityIndex(in nsIMdbRow row, in unsigned long value);
+
+ [noscript] void addListName(in nsIMdbRow row, in string value);
+ [noscript] void addListNickName(in nsIMdbRow row, in string value);
+ [noscript] void addListDescription(in nsIMdbRow row, in string value);
+ [noscript] void addListDirNode(in nsIMdbRow listRow);
+
+ /**
+ * use for getting and setting generic string attributes
+ * like _AimScreenName
+ */
+ void setCardValue(in nsIAbCard card, in string name, in wstring value, in boolean notify);
+ wstring getCardValue(in nsIAbCard card, in string name);
+
+ /**
+ * Returns an array of the deleted cards currently stored in the mork file.
+ */
+ readonly attribute nsIArray deletedCardList;
+
+ /**
+ * Returns the count of the deleted card currently stored in the mork file.
+ */
+ readonly attribute unsigned long deletedCardCount;
+
+ /**
+ * Add the column representing the card to the mailing list row
+ * in the database.
+ *
+ * @param aPCard the card to be added.
+ * @param aPListRow the row to which the column will be added.
+ * @param aPos the position of the card in the mailing list.
+ * @param aPNewCard a pointer to hold the new card added to the row.
+ * @param aInMailingList If set to true, the card is already present
+ * in the mailing list
+ * @param aParent parent mailing list to which the card
+ * is added. If set to null, listeners of the
+ * database will not be notified of card creation.
+ * @param aRoot If the card is created while creating a new mailing
+ * list, its set to the parent addressbook.
+ * Set to null in other case.
+ */
+ void AddListCardColumnsToRow(in nsIAbCard aPCard,
+ in nsIMdbRow aPListRow,
+ in unsigned long aPos,
+ out nsIAbCard aPNewCard,
+ in boolean aInMailingList,
+ in nsIAbDirectory aParent,
+ in nsIAbDirectory aRoot);
+ void InitCardFromRow(in nsIAbCard aNewCard,in nsIMdbRow aCardRow);
+ void SetListAddressTotal(in nsIMdbRow aListRow, in uint32_t aTotal);
+ nsIMdbRow FindRowByCard(in nsIAbCard aCard);
+};
diff --git a/mailnews/addrbook/public/nsIMsgVCardService.idl b/mailnews/addrbook/public/nsIMsgVCardService.idl
new file mode 100644
index 000000000..e3e3411ca
--- /dev/null
+++ b/mailnews/addrbook/public/nsIMsgVCardService.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include "nsVCardObj.h"
+%}
+
+[ptr] native VObject_ptr(VObject);
+[ptr] native VObjectIterator_ptr(VObjectIterator);
+[ptr] native const_char_ptr(const char);
+
+[uuid(8b6ae917-676d-4f1f-bbad-2ecc9be0d9b1)]
+interface nsIMsgVCardService : nsISupports {
+ [noscript, notxpcom] void cleanVObject(in VObject_ptr o);
+ [noscript, notxpcom] VObject_ptr nextVObjectInList(in VObject_ptr o);
+ [noscript, notxpcom] VObject_ptr parse_MIME(in string input, in unsigned long len);
+ [noscript, notxpcom] charPtr fakeCString(in VObject_ptr o);
+ [noscript, notxpcom] VObject_ptr isAPropertyOf(in VObject_ptr o, in string id);
+ [noscript, notxpcom] charPtr writeMemoryVObjects(in string s, out long len, in VObject_ptr list, in boolean expandSpaces);
+ [noscript, notxpcom] VObject_ptr nextVObject(in VObjectIterator_ptr i);
+ [noscript, notxpcom] void initPropIterator(in VObjectIterator_ptr i, in VObject_ptr o);
+ [noscript, notxpcom] long moreIteration(in VObjectIterator_ptr i);
+ [noscript, notxpcom] const_char_ptr vObjectName(in VObject_ptr o);
+ [noscript, notxpcom] charPtr vObjectAnyValue(in VObject_ptr o);
+};
diff --git a/mailnews/addrbook/src/moz.build b/mailnews/addrbook/src/moz.build
new file mode 100644
index 000000000..648958cf2
--- /dev/null
+++ b/mailnews/addrbook/src/moz.build
@@ -0,0 +1,93 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'nsAbDirProperty.h',
+ 'nsDirPrefs.h',
+ 'nsVCardObj.h',
+]
+
+SOURCES += [
+ 'nsAbAddressCollector.cpp',
+ 'nsAbBooleanExpression.cpp',
+ 'nsAbBSDirectory.cpp',
+ 'nsAbCardProperty.cpp',
+ 'nsAbContentHandler.cpp',
+ 'nsAbDirectoryQuery.cpp',
+ 'nsAbDirectoryQueryProxy.cpp',
+ 'nsAbDirFactoryService.cpp',
+ 'nsAbDirProperty.cpp',
+ 'nsAbLDIFService.cpp',
+ 'nsAbManager.cpp',
+ 'nsAbMDBCard.cpp',
+ 'nsAbMDBDirectory.cpp',
+ 'nsAbMDBDirFactory.cpp',
+ 'nsAbMDBDirProperty.cpp',
+ 'nsAbQueryStringToExpression.cpp',
+ 'nsAbView.cpp',
+ 'nsAddbookProtocolHandler.cpp',
+ 'nsAddbookUrl.cpp',
+ 'nsAddrDatabase.cpp',
+ 'nsDirPrefs.cpp',
+ 'nsMsgVCardService.cpp',
+ 'nsVCard.cpp',
+ 'nsVCardObj.cpp',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['MOZ_MAPI_SUPPORT']:
+ SOURCES += [
+ 'nsAbOutlookDirectory.cpp',
+ 'nsAbOutlookDirFactory.cpp',
+ 'nsAbWinHelper.cpp',
+ 'nsMapiAddressBook.cpp',
+ 'nsWabAddressBook.cpp',
+ ]
+
+if CONFIG['OS_ARCH'] == 'Darwin':
+ SOURCES += [
+ 'nsAbOSXDirFactory.cpp',
+ ]
+
+ SOURCES += [
+ 'nsAbOSXCard.mm',
+ 'nsAbOSXDirectory.mm',
+ 'nsAbOSXUtils.mm',
+ ]
+
+if CONFIG['MOZ_LDAP_XPCOM']:
+ SOURCES += [
+ 'nsAbBoolExprToLDAPFilter.cpp',
+ 'nsAbLDAPCard.cpp',
+ 'nsAbLDAPDirectory.cpp',
+ 'nsAbLDAPDirectoryModify.cpp',
+ 'nsAbLDAPDirectoryQuery.cpp',
+ 'nsAbLDAPDirFactory.cpp',
+ 'nsAbLDAPListenerBase.cpp',
+ 'nsAbLDAPReplicationData.cpp',
+ 'nsAbLDAPReplicationQuery.cpp',
+ 'nsAbLDAPReplicationService.cpp',
+ ]
+ # XXX These files are not being built as they don't work. Bug 311632 should
+ # fix them.
+ # nsAbLDAPChangeLogQuery.cpp
+ # nsAbLDAPChangeLogData.cpp
+
+ EXTRA_COMPONENTS += [
+ 'nsAbLDAPAutoCompleteSearch.js',
+ ]
+
+ DEFINES['MOZ_LDAP_XPCOM'] = True
+
+EXTRA_COMPONENTS += [
+ 'nsAbAutoCompleteMyDomain.js',
+ 'nsAbAutoCompleteSearch.js',
+ 'nsAbLDAPAttributeMap.js',
+]
+
+EXTRA_PP_COMPONENTS += [
+ 'nsAddrbook.manifest',
+]
+
+FINAL_LIBRARY = 'mail'
diff --git a/mailnews/addrbook/src/nsAbAddressCollector.cpp b/mailnews/addrbook/src/nsAbAddressCollector.cpp
new file mode 100644
index 000000000..60f359601
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbAddressCollector.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // for pre-compiled headers
+#include "nsISimpleEnumerator.h"
+
+#include "nsIAbCard.h"
+#include "nsAbBaseCID.h"
+#include "nsAbAddressCollector.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsStringGlue.h"
+#include "prmem.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIAbManager.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+using namespace mozilla::mailnews;
+
+NS_IMPL_ISUPPORTS(nsAbAddressCollector, nsIAbAddressCollector, nsIObserver)
+
+#define PREF_MAIL_COLLECT_ADDRESSBOOK "mail.collect_addressbook"
+
+nsAbAddressCollector::nsAbAddressCollector()
+{
+}
+
+nsAbAddressCollector::~nsAbAddressCollector()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ pPrefBranchInt->RemoveObserver(PREF_MAIL_COLLECT_ADDRESSBOOK, this);
+}
+
+/**
+ * Returns the first card found with the specified email address. This
+ * returns an already addrefed pointer to the card if the card is found.
+ */
+already_AddRefed<nsIAbCard>
+nsAbAddressCollector::GetCardForAddress(const nsACString &aEmailAddress,
+ nsIAbDirectory **aDirectory)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = abManager->GetDirectories(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ bool hasMore;
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIAbDirectory> directory;
+ nsCOMPtr<nsIAbCard> result;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ directory = do_QueryInterface(supports, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ // Some implementations may return NS_ERROR_NOT_IMPLEMENTED here,
+ // so just catch the value and continue.
+ if (NS_FAILED(directory->CardForEmailAddress(aEmailAddress,
+ getter_AddRefs(result))))
+ {
+ continue;
+ }
+
+ if (result)
+ {
+ if (aDirectory)
+ directory.forget(aDirectory);
+ return result.forget();
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsAbAddressCollector::CollectAddress(const nsACString &aAddresses,
+ bool aCreateCard,
+ uint32_t aSendFormat)
+{
+ // If we've not got a valid directory, no point in going any further
+ if (!mDirectory)
+ return NS_OK;
+
+ // note that we're now setting the whole recipient list,
+ // not just the pretty name of the first recipient.
+ nsTArray<nsCString> names;
+ nsTArray<nsCString> addresses;
+ ExtractAllAddresses(EncodedHeader(aAddresses),
+ UTF16ArrayAdapter<>(names), UTF16ArrayAdapter<>(addresses));
+ uint32_t numAddresses = names.Length();
+
+ for (uint32_t i = 0; i < numAddresses; i++)
+ {
+ // Don't allow collection of addresses with no email address, it makes
+ // no sense. Whilst we should never get here in most normal cases, we
+ // should still be careful.
+ if (addresses[i].IsEmpty())
+ continue;
+
+ CollectSingleAddress(addresses[i], names[i], aCreateCard, aSendFormat,
+ false);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbAddressCollector::CollectSingleAddress(const nsACString &aEmail,
+ const nsACString &aDisplayName,
+ bool aCreateCard,
+ uint32_t aSendFormat,
+ bool aSkipCheckExisting)
+{
+ if (!mDirectory)
+ return NS_OK;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbDirectory> originDirectory;
+ nsCOMPtr<nsIAbCard> card = (!aSkipCheckExisting) ?
+ GetCardForAddress(aEmail, getter_AddRefs(originDirectory)) : nullptr;
+
+ if (!card && (aCreateCard || aSkipCheckExisting))
+ {
+ card = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && card)
+ {
+ // Set up the fields for the new card.
+ SetNamesForCard(card, aDisplayName);
+ AutoCollectScreenName(card, aEmail);
+
+ if (NS_SUCCEEDED(card->SetPrimaryEmail(NS_ConvertUTF8toUTF16(aEmail))))
+ {
+ card->SetPropertyAsUint32(kPreferMailFormatProperty, aSendFormat);
+
+ nsCOMPtr<nsIAbCard> addedCard;
+ rv = mDirectory->AddCard(card, getter_AddRefs(addedCard));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to add card");
+ }
+ }
+ }
+ else if (card && originDirectory)
+ {
+ // It could be that the origin directory is read-only, so don't try and
+ // write to it if it is.
+ bool readOnly;
+ rv = originDirectory->GetReadOnly(&readOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (readOnly)
+ return NS_OK;
+
+ // address is already in the AB, so update the names
+ bool modifiedCard = false;
+
+ nsString displayName;
+ card->GetDisplayName(displayName);
+ // If we already have a display name, don't set the names on the card.
+ if (displayName.IsEmpty() && !aDisplayName.IsEmpty())
+ modifiedCard = SetNamesForCard(card, aDisplayName);
+
+ if (aSendFormat != nsIAbPreferMailFormat::unknown)
+ {
+ uint32_t currentFormat;
+ rv = card->GetPropertyAsUint32(kPreferMailFormatProperty,
+ &currentFormat);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get preferred mail format");
+
+ // we only want to update the AB if the current format is unknown
+ if (currentFormat == nsIAbPreferMailFormat::unknown &&
+ NS_SUCCEEDED(card->SetPropertyAsUint32(kPreferMailFormatProperty,
+ aSendFormat)))
+ modifiedCard = true;
+ }
+
+ if (modifiedCard)
+ originDirectory->ModifyCard(card);
+ }
+
+ return NS_OK;
+}
+
+// Works out the screen name to put on the card for some well-known addresses
+void
+nsAbAddressCollector::AutoCollectScreenName(nsIAbCard *aCard,
+ const nsACString &aEmail)
+{
+ if (!aCard)
+ return;
+
+ int32_t atPos = aEmail.FindChar('@');
+ if (atPos == -1)
+ return;
+
+ const nsACString& domain = Substring(aEmail, atPos + 1);
+
+ if (domain.IsEmpty())
+ return;
+ // username in
+ // username@aol.com (America Online)
+ // username@cs.com (Compuserve)
+ // username@netscape.net (Netscape webmail)
+ // are all AIM screennames. autocollect that info.
+ if (domain.Equals("aol.com") || domain.Equals("cs.com") ||
+ domain.Equals("netscape.net"))
+ aCard->SetPropertyAsAUTF8String(kScreenNameProperty, Substring(aEmail, 0, atPos));
+ else if (domain.Equals("gmail.com") || domain.Equals("googlemail.com"))
+ aCard->SetPropertyAsAUTF8String(kGtalkProperty, Substring(aEmail, 0, atPos));
+}
+
+// Returns true if the card was modified successfully.
+bool
+nsAbAddressCollector::SetNamesForCard(nsIAbCard *aSenderCard,
+ const nsACString &aFullName)
+{
+ nsCString firstName;
+ nsCString lastName;
+ bool modifiedCard = false;
+
+ if (NS_SUCCEEDED(aSenderCard->SetDisplayName(NS_ConvertUTF8toUTF16(aFullName))))
+ modifiedCard = true;
+
+ // Now split up the full name.
+ SplitFullName(nsCString(aFullName), firstName, lastName);
+
+ if (!firstName.IsEmpty() &&
+ NS_SUCCEEDED(aSenderCard->SetFirstName(NS_ConvertUTF8toUTF16(firstName))))
+ modifiedCard = true;
+
+ if (!lastName.IsEmpty() &&
+ NS_SUCCEEDED(aSenderCard->SetLastName(NS_ConvertUTF8toUTF16(lastName))))
+ modifiedCard = true;
+
+ if (modifiedCard)
+ aSenderCard->SetPropertyAsBool("PreferDisplayName", false);
+
+ return modifiedCard;
+}
+
+// Splits the first and last name based on the space between them.
+void
+nsAbAddressCollector::SplitFullName(const nsCString &aFullName, nsCString &aFirstName,
+ nsCString &aLastName)
+{
+ int index = aFullName.RFindChar(' ');
+ if (index != -1)
+ {
+ aLastName = Substring(aFullName, index + 1);
+ aFirstName = Substring(aFullName, 0, index);
+ }
+}
+
+// Observes the collected address book pref in case it changes.
+NS_IMETHODIMP
+nsAbAddressCollector::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ if (!prefBranch) {
+ NS_ASSERTION(prefBranch, "failed to get prefs");
+ return NS_OK;
+ }
+
+ SetUpAbFromPrefs(prefBranch);
+ return NS_OK;
+}
+
+// Initialises the collector with the required items.
+nsresult
+nsAbAddressCollector::Init(void)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = prefBranch->AddObserver(PREF_MAIL_COLLECT_ADDRESSBOOK, this, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetUpAbFromPrefs(prefBranch);
+ return NS_OK;
+}
+
+// Performs the necessary changes to set up the collector for the specified
+// collected address book.
+void
+nsAbAddressCollector::SetUpAbFromPrefs(nsIPrefBranch *aPrefBranch)
+{
+ nsCString abURI;
+ aPrefBranch->GetCharPref(PREF_MAIL_COLLECT_ADDRESSBOOK,
+ getter_Copies(abURI));
+
+ if (abURI.IsEmpty())
+ abURI.AssignLiteral(kPersonalAddressbookUri);
+
+ if (abURI == mABURI)
+ return;
+
+ mDirectory = nullptr;
+ mABURI = abURI;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = abManager->GetDirectory(mABURI, getter_AddRefs(mDirectory));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ bool readOnly;
+ rv = mDirectory->GetReadOnly(&readOnly);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // If the directory is read-only, we can't write to it, so just blank it out
+ // here, and warn because we shouldn't hit this (UI is wrong).
+ if (readOnly)
+ {
+ NS_ERROR("Address Collection book preferences is set to a read-only book. "
+ "Address collection will not take place.");
+ mDirectory = nullptr;
+ }
+}
diff --git a/mailnews/addrbook/src/nsAbAddressCollector.h b/mailnews/addrbook/src/nsAbAddressCollector.h
new file mode 100644
index 000000000..7ef2236b8
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbAddressCollector.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsAbAddressCollector_H_
+#define _nsAbAddressCollector_H_
+
+#include "nsIAbAddressCollector.h"
+#include "nsCOMPtr.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbCard.h"
+#include "nsIObserver.h"
+#include "nsStringGlue.h"
+
+class nsIPrefBranch;
+
+class nsAbAddressCollector : public nsIAbAddressCollector,
+ public nsIObserver
+{
+public:
+ nsAbAddressCollector();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABADDRESSCOLLECTOR
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+private:
+ virtual ~nsAbAddressCollector();
+ already_AddRefed<nsIAbCard> GetCardForAddress(const nsACString &aEmailAddress,
+ nsIAbDirectory **aDirectory);
+ void AutoCollectScreenName(nsIAbCard *aCard, const nsACString &aEmail);
+ bool SetNamesForCard(nsIAbCard *aSenderCard, const nsACString &aFullName);
+ void SplitFullName(const nsCString &aFullName, nsCString &aFirstName,
+ nsCString &aLastName);
+ void SetUpAbFromPrefs(nsIPrefBranch *aPrefBranch);
+ nsCOMPtr <nsIAbDirectory> mDirectory;
+ nsCString mABURI;
+};
+
+#endif // _nsAbAddressCollector_H_
+
diff --git a/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js b/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js
new file mode 100644
index 000000000..1ace4ca50
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function nsAbAutoCompleteMyDomain() {}
+
+nsAbAutoCompleteMyDomain.prototype = {
+ classID: Components.ID("{5b259db2-e451-4de9-8a6f-cfba91402973}"),
+ QueryInterface: XPCOMUtils.generateQI([
+ Components.interfaces.nsIAutoCompleteSearch]),
+
+ cachedIdKey: "",
+ cachedIdentity: null,
+
+ applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+
+ startSearch: function(aString, aSearchParam, aResult, aListener) {
+ let params = aSearchParam ? JSON.parse(aSearchParam) : {};
+ let applicable = ("type" in params) && this.applicableHeaders.has(params.type);
+ const ACR = Components.interfaces.nsIAutoCompleteResult;
+ var address = null;
+ if (applicable && aString && !aString.includes(",")) {
+ if (("idKey" in params) && (params.idKey != this.cachedIdKey)) {
+ this.cachedIdentity = MailServices.accounts.getIdentity(params.idKey);
+ this.cachedIdKey = params.idKey;
+ }
+ if (this.cachedIdentity.autocompleteToMyDomain)
+ address = aString.includes("@") ? aString :
+ this.cachedIdentity.email.replace(/[^@]*/, aString);
+ }
+
+ var result = {
+ searchString: aString,
+ searchResult: address ? ACR.RESULT_SUCCESS : ACR.RESULT_FAILURE,
+ defaultIndex: -1,
+ errorDescription: null,
+ matchCount: address ? 1 : 0,
+ getValueAt: function() { return address; },
+ getLabelAt: function() { return this.getValueAt(); },
+ getCommentAt: function() { return null; },
+ getStyleAt: function() { return "default-match"; },
+ getImageAt: function() { return null; },
+ getFinalCompleteValueAt: function(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+ removeValueAt: function() {}
+ };
+ aListener.onSearchResult(this, result);
+ },
+
+ stopSearch: function() {}
+};
+
+var components = [nsAbAutoCompleteMyDomain];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/addrbook/src/nsAbAutoCompleteSearch.js b/mailnews/addrbook/src/nsAbAutoCompleteSearch.js
new file mode 100644
index 000000000..c6c9b8db8
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbAutoCompleteSearch.js
@@ -0,0 +1,466 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/ABQueryUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var ACR = Components.interfaces.nsIAutoCompleteResult;
+var nsIAbAutoCompleteResult = Components.interfaces.nsIAbAutoCompleteResult;
+
+function nsAbAutoCompleteResult(aSearchString) {
+ // Can't create this in the prototype as we'd get the same array for
+ // all instances
+ this._searchResults = []; // final results
+ this.searchString = aSearchString;
+ this._collectedValues = new Map(); // temporary unsorted results
+ // Get model query from pref; this will return mail.addr_book.autocompletequery.format.phonetic
+ // if mail.addr_book.show_phonetic_fields == true
+ this.modelQuery = getModelQuery("mail.addr_book.autocompletequery.format");
+ // check if the currently active model query has been modified by user
+ this._modelQueryHasUserValue = modelQueryHasUserValue("mail.addr_book.autocompletequery.format");
+}
+
+nsAbAutoCompleteResult.prototype = {
+ _searchResults: null,
+
+ // nsIAutoCompleteResult
+
+ modelQuery: null,
+ searchString: null,
+ searchResult: ACR.RESULT_NOMATCH,
+ defaultIndex: -1,
+ errorDescription: null,
+
+ get matchCount() {
+ return this._searchResults.length;
+ },
+
+ getValueAt: function getValueAt(aIndex) {
+ return this._searchResults[aIndex].value;
+ },
+
+ getLabelAt: function getLabelAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ getCommentAt: function getCommentAt(aIndex) {
+ return this._searchResults[aIndex].comment;
+ },
+
+ getStyleAt: function getStyleAt(aIndex) {
+ return "local-abook";
+ },
+
+ getImageAt: function getImageAt(aIndex) {
+ return "";
+ },
+
+ getFinalCompleteValueAt: function(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ removeValueAt: function removeValueAt(aRowIndex, aRemoveFromDB) {
+ },
+
+ // nsIAbAutoCompleteResult
+
+ getCardAt: function getCardAt(aIndex) {
+ return this._searchResults[aIndex].card;
+ },
+
+ getEmailToUse: function getEmailToUse(aIndex) {
+ return this._searchResults[aIndex].emailToUse;
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([ACR, nsIAbAutoCompleteResult])
+}
+
+function nsAbAutoCompleteSearch() {}
+
+nsAbAutoCompleteSearch.prototype = {
+ // For component registration
+ classID: Components.ID("2f946df9-114c-41fe-8899-81f10daf4f0c"),
+
+ // This is set from a preference,
+ // 0 = no comment column, 1 = name of address book this card came from
+ // Other numbers currently unused (hence default to zero)
+ _commentColumn: 0,
+ _parser: MailServices.headerParser,
+ _abManager: MailServices.ab,
+ applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+
+ // Private methods
+
+ /**
+ * Returns the popularity index for a given card. This takes account of a
+ * translation bug whereby Thunderbird 2 stores its values in mork as
+ * hexadecimal, and Thunderbird 3 stores as decimal.
+ *
+ * @param aDirectory The directory that the card is in.
+ * @param aCard The card to return the popularity index for.
+ */
+ _getPopularityIndex: function _getPopularityIndex(aDirectory, aCard) {
+ let popularityValue = aCard.getProperty("PopularityIndex", "0");
+ let popularityIndex = parseInt(popularityValue);
+
+ // If we haven't parsed it the first time round, parse it as hexadecimal
+ // and repair so that we don't have to keep repairing.
+ if (isNaN(popularityIndex)) {
+ popularityIndex = parseInt(popularityValue, 16);
+
+ // If its still NaN, just give up, we shouldn't ever get here.
+ if (isNaN(popularityIndex))
+ popularityIndex = 0;
+
+ // Now store this change so that we're not changing it each time around.
+ if (!aDirectory.readOnly) {
+ aCard.setProperty("PopularityIndex", popularityIndex);
+ try {
+ aDirectory.modifyCard(aCard);
+ }
+ catch (ex) {
+ Components.utils.reportError(ex);
+ }
+ }
+ }
+ return popularityIndex;
+ },
+
+ /**
+ * Gets the score of the (full) address, given the search input. We want
+ * results that match the beginning of a "word" in the result to score better
+ * than a result that matches only in the middle of the word.
+ *
+ * @param aCard - the card whose score is being decided
+ * @param aAddress - full lower-cased address, including display name and address
+ * @param aSearchString - search string provided by user
+ * @return a score; a higher score is better than a lower one
+ */
+ _getScore: function(aCard, aAddress, aSearchString) {
+ const BEST = 100;
+
+ // We will firstly check if the search term provided by the user
+ // is the nick name for the card or at least in the beginning of it.
+ let nick = aCard.getProperty("NickName", "").toLocaleLowerCase();
+ aSearchString = aSearchString.toLocaleLowerCase();
+ if (nick == aSearchString)
+ return BEST + 1;
+ if (nick.indexOf(aSearchString) == 0)
+ return BEST;
+
+ // We'll do this case-insensitively and ignore the domain.
+ let atIdx = aAddress.lastIndexOf("@");
+ if (atIdx != -1) // mail lists don't have an @
+ aAddress = aAddress.substr(0, atIdx);
+ let idx = aAddress.indexOf(aSearchString);
+ if (idx == 0)
+ return BEST;
+ if (idx == -1)
+ return 0;
+
+ // We want to treat firstname, lastname and word boundary(ish) parts of
+ // the email address the same. E.g. for "John Doe (:xx) <jd.who@example.com>"
+ // all of these should score the same: "John", "Doe", "xx",
+ // ":xx", "jd", "who".
+ let prevCh = aAddress.charAt(idx - 1);
+ if (/[ :."'(\-_<&]/.test(prevCh))
+ return BEST;
+
+ // The match was inside a word -> we don't care about the position.
+ return 0;
+ },
+
+ /**
+ * Searches cards in the given directory. If a card is matched (and isn't
+ * a mailing list) then the function will add a result for each email address
+ * that exists.
+ *
+ * @param searchQuery The boolean search query to use.
+ * @param directory An nsIAbDirectory to search.
+ * @param result The result element to append results to.
+ */
+ _searchCards: function(searchQuery, directory, result) {
+ let childCards;
+ try {
+ childCards = this._abManager.getDirectory(directory.URI + searchQuery).childCards;
+ } catch (e) {
+ Components.utils.reportError("Error running addressbook query '" + searchQuery + "': " + e);
+ return;
+ }
+
+ // Cache this values to save going through xpconnect each time
+ var commentColumn = this._commentColumn == 1 ? directory.dirName : "";
+
+ // Now iterate through all the cards.
+ while (childCards.hasMoreElements()) {
+ var card = childCards.getNext();
+
+ if (card instanceof Components.interfaces.nsIAbCard) {
+ if (card.isMailList)
+ this._addToResult(commentColumn, directory, card, "", true, result);
+ else {
+ let email = card.primaryEmail;
+ if (email)
+ this._addToResult(commentColumn, directory, card, email, true, result);
+
+ email = card.getProperty("SecondEmail", "");
+ if (email)
+ this._addToResult(commentColumn, directory, card, email, false, result);
+ }
+ }
+ }
+ },
+
+ /**
+ * Checks the parent card and email address of an autocomplete results entry
+ * from a previous result against the search parameters to see if that entry
+ * should still be included in the narrowed-down result.
+ *
+ * @param aCard The card to check.
+ * @param aEmailToUse The email address to check against.
+ * @param aSearchWords Array of words in the multi word search string.
+ * @return True if the card matches the search parameters, false
+ * otherwise.
+ */
+ _checkEntry: function _checkEntry(aCard, aEmailToUse, aSearchWords) {
+ // Joining values of many fields in a single string so that a single
+ // search query can be fired on all of them at once. Separating them
+ // using spaces so that field1=> "abc" and field2=> "def" on joining
+ // shouldn't return true on search for "bcd".
+ // Note: This should be constructed from model query pref using
+ // getModelQuery("mail.addr_book.autocompletequery.format")
+ // but for now we hard-code the default value equivalent of the pref here
+ // or else bail out before and reconstruct the full c++ query if the pref
+ // has been customized (modelQueryHasUserValue), so that we won't get here.
+ let cumulativeFieldText = aCard.displayName + " " +
+ aCard.firstName + " " +
+ aCard.lastName + " " +
+ aEmailToUse + " " +
+ aCard.getProperty("NickName", "");
+ if (aCard.isMailList)
+ cumulativeFieldText += " " + aCard.getProperty("Notes", "");
+ cumulativeFieldText = cumulativeFieldText.toLocaleLowerCase();
+
+ return aSearchWords.every(String.prototype.includes,
+ cumulativeFieldText);
+ },
+
+ /**
+ * Checks to see if an emailAddress (name/address) is a duplicate of an
+ * existing entry already in the results. If the emailAddress is found, it
+ * will remove the existing element if the popularity of the new card is
+ * higher than the previous card.
+ *
+ * @param directory The directory that the card is in.
+ * @param card The card that could be a duplicate.
+ * @param lcEmailAddress The emailAddress (name/address combination) to check
+ * for duplicates against. Lowercased.
+ * @param currentResults The current results list.
+ */
+ _checkDuplicate: function (directory, card, lcEmailAddress, currentResults) {
+ let existingResult = currentResults._collectedValues.get(lcEmailAddress);
+ if (!existingResult)
+ return false;
+
+ let popIndex = this._getPopularityIndex(directory, card);
+ // It's a duplicate, is the new one more popular?
+ if (popIndex > existingResult.popularity) {
+ // Yes it is, so delete this element, return false and allow
+ // _addToResult to sort the new element into the correct place.
+ currentResults._collectedValues.delete(lcEmailAddress);
+ return false;
+ }
+ // Not more popular, but still a duplicate. Return true and _addToResult
+ // will just forget about it.
+ return true;
+ },
+
+ /**
+ * Adds a card to the results list if it isn't a duplicate. The function will
+ * order the results by popularity.
+ *
+ * @param commentColumn The text to be displayed in the comment column
+ * (if any).
+ * @param directory The directory that the card is in.
+ * @param card The card being added to the results.
+ * @param emailToUse The email address from the card that should be used
+ * for this result.
+ * @param isPrimaryEmail Is the emailToUse the primary email? Set to true if
+ * it is the case. For mailing lists set it to true.
+ * @param result The result to add the new entry to.
+ */
+ _addToResult: function(commentColumn, directory, card,
+ emailToUse, isPrimaryEmail, result) {
+ let mbox = this._parser.makeMailboxObject(card.displayName,
+ card.isMailList ? card.getProperty("Notes", "") || card.displayName :
+ emailToUse);
+ if (!mbox.email)
+ return;
+
+ let emailAddress = mbox.toString();
+ let lcEmailAddress = emailAddress.toLocaleLowerCase();
+
+ // If it is a duplicate, then just return and don't add it. The
+ // _checkDuplicate function deals with it all for us.
+ if (this._checkDuplicate(directory, card, lcEmailAddress, result))
+ return;
+
+ result._collectedValues.set(lcEmailAddress, {
+ value: emailAddress,
+ comment: commentColumn,
+ card: card,
+ isPrimaryEmail: isPrimaryEmail,
+ emailToUse: emailToUse,
+ popularity: this._getPopularityIndex(directory, card),
+ score: this._getScore(card, lcEmailAddress, result.searchString)
+ });
+ },
+
+ // nsIAutoCompleteSearch
+
+ /**
+ * Starts a search based on the given parameters.
+ *
+ * @see nsIAutoCompleteSearch for parameter details.
+ *
+ * It is expected that aSearchParam contains the identity (if any) to use
+ * for determining if an address book should be autocompleted against.
+ */
+ startSearch: function startSearch(aSearchString, aSearchParam,
+ aPreviousResult, aListener) {
+ let params = aSearchParam ? JSON.parse(aSearchParam) : {};
+ var result = new nsAbAutoCompleteResult(aSearchString);
+ if (("type" in params) && !this.applicableHeaders.has(params.type)) {
+ result.searchResult = ACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, result);
+ return;
+ }
+
+ let fullString = aSearchString && aSearchString.trim().toLocaleLowerCase();
+
+ // If the search string is empty, or contains a comma, or the user
+ // hasn't enabled autocomplete, then just return no matches or the
+ // result ignored.
+ // The comma check is so that we don't autocomplete against the user
+ // entering multiple addresses.
+ if (!fullString || aSearchString.includes(",")) {
+ result.searchResult = ACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, result);
+ return;
+ }
+
+ // Array of all the terms from the fullString search query
+ // (separated on the basis of spaces or exact terms on the
+ // basis of quotes).
+ let searchWords = getSearchTokens(fullString);
+
+ // Find out about the comment column
+ try {
+ this._commentColumn = Services.prefs.getIntPref("mail.autoComplete.commentColumn");
+ } catch(e) { }
+
+ if (aPreviousResult instanceof nsIAbAutoCompleteResult &&
+ aSearchString.startsWith(aPreviousResult.searchString) &&
+ aPreviousResult.searchResult == ACR.RESULT_SUCCESS &&
+ !result._modelQueryHasUserValue &&
+ result.modelQuery == aPreviousResult.modelQuery) {
+ // We have successful previous matches, and model query has not changed since
+ // previous search, therefore just iterate through the list of previous result
+ // entries and reduce as appropriate (via _checkEntry function).
+ // Test for model query change is required: when reverting back from custom to
+ // default query, result._modelQueryHasUserValue==false, but we must bail out.
+ // Todo: However, if autocomplete model query has been customized, we fall
+ // back to using the full query again instead of reducing result list in js;
+ // The full query might be less performant as it's fired against entire AB,
+ // so we should try morphing the query for js. We can't use the _checkEntry
+ // js query yet because it is hardcoded (mimic default model query).
+ // At least we now allow users to customize their autocomplete model query...
+ for (let i = 0; i < aPreviousResult.matchCount; ++i) {
+ let card = aPreviousResult.getCardAt(i);
+ let email = aPreviousResult.getEmailToUse(i);
+ if (this._checkEntry(card, email, searchWords)) {
+ // Add matches into the results array. We re-sort as needed later.
+ result._searchResults.push({
+ value: aPreviousResult.getValueAt(i),
+ comment: aPreviousResult.getCommentAt(i),
+ card: card,
+ isPrimaryEmail: (card.primaryEmail == email),
+ emailToUse: email,
+ popularity: parseInt(card.getProperty("PopularityIndex", "0")),
+ score: this._getScore(card,
+ aPreviousResult.getValueAt(i).toLocaleLowerCase(),
+ fullString)
+ });
+ }
+ }
+ }
+ else
+ {
+ // Construct the search query from pref; using a query means we can
+ // optimise on running the search through c++ which is better for string
+ // comparisons (_checkEntry is relatively slow).
+ // When user's fullstring search expression is a multiword query, search
+ // for each word separately so that each result contains all the words
+ // from the fullstring in the fields of the addressbook card
+ // (see bug 558931 for explanations).
+ // Use helper method to split up search query to multi-word search
+ // query against multiple fields.
+ let searchWords = getSearchTokens(fullString);
+ let searchQuery = generateQueryURI(result.modelQuery, searchWords);
+
+ // Now do the searching
+ let allABs = this._abManager.directories;
+
+ // We're not going to bother searching sub-directories, currently the
+ // architecture forces all cards that are in mailing lists to be in ABs as
+ // well, therefore by searching sub-directories (aka mailing lists) we're
+ // just going to find duplicates.
+ while (allABs.hasMoreElements()) {
+ let dir = allABs.getNext();
+ if (dir instanceof Components.interfaces.nsIAbDirectory &&
+ dir.useForAutocomplete(("idKey" in params) ? params.idKey : null)) {
+ this._searchCards(searchQuery, dir, result);
+ }
+ }
+
+ result._searchResults = [...result._collectedValues.values()];
+ }
+
+ // Sort the results. Scoring may have changed so do it even if this is
+ // just filtered previous results.
+ result._searchResults.sort(function(a, b) {
+ // Order by 1) descending score, then 2) descending popularity,
+ // then 3) primary email before secondary for the same card, then
+ // 4) by emails sorted alphabetically.
+ return (b.score - a.score) ||
+ (b.popularity - a.popularity) ||
+ ((a.card == b.card && a.isPrimaryEmail) ? -1 : 0) ||
+ a.value.localeCompare(b.value);
+ });
+
+ if (result.matchCount) {
+ result.searchResult = ACR.RESULT_SUCCESS;
+ result.defaultIndex = 0;
+ }
+
+ aListener.onSearchResult(this, result);
+ },
+
+ stopSearch: function stopSearch() {
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces
+ .nsIAutoCompleteSearch])
+};
+
+// Module
+
+var components = [nsAbAutoCompleteSearch];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/addrbook/src/nsAbBSDirectory.cpp b/mailnews/addrbook/src/nsAbBSDirectory.cpp
new file mode 100644
index 000000000..446963bb2
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBSDirectory.cpp
@@ -0,0 +1,322 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbBSDirectory.h"
+
+#include "nsDirPrefs.h"
+#include "nsAbBaseCID.h"
+#include "nsAddrDatabase.h"
+#include "nsIAbManager.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsServiceManagerUtils.h"
+#include "nsAbDirFactoryService.h"
+#include "nsAbMDBDirFactory.h"
+#include "nsArrayEnumerator.h"
+
+#include "nsCRTGlue.h"
+
+nsAbBSDirectory::nsAbBSDirectory()
+: mInitialized(false)
+, mServers(13)
+{
+}
+
+nsAbBSDirectory::~nsAbBSDirectory()
+{
+}
+
+NS_IMETHODIMP nsAbBSDirectory::Init(const char *aURI)
+{
+ mURI = aURI;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsAbBSDirectory, nsAbDirProperty)
+
+nsresult nsAbBSDirectory::CreateDirectoriesFromFactory(const nsACString &aURI,
+ DIR_Server *aServer,
+ bool aNotify)
+{
+ nsresult rv;
+
+ // Get the directory factory service
+ nsCOMPtr<nsIAbDirFactoryService> dirFactoryService =
+ do_GetService(NS_ABDIRFACTORYSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ // Get the directory factory from the URI
+ nsCOMPtr<nsIAbDirFactory> dirFactory;
+ rv = dirFactoryService->GetDirFactory(aURI, getter_AddRefs(dirFactory));
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ // Create the directories
+ nsCOMPtr<nsISimpleEnumerator> newDirEnumerator;
+ rv = dirFactory->GetDirectories(NS_ConvertUTF8toUTF16(aServer->description),
+ aURI,
+ nsDependentCString(aServer->prefName),
+ getter_AddRefs(newDirEnumerator));
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ // Enumerate through the directories adding them
+ // to the sub directories array
+ bool hasMore;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+
+ while (NS_SUCCEEDED(newDirEnumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> newDirSupports;
+ rv = newDirEnumerator->GetNext(getter_AddRefs(newDirSupports));
+ if(NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIAbDirectory> childDir = do_QueryInterface(newDirSupports, &rv);
+ if(NS_FAILED(rv))
+ continue;
+
+ // Define a relationship between the preference
+ // entry and the directory
+ mServers.Put(childDir, aServer);
+
+ mSubDirectories.AppendObject(childDir);
+
+ if (aNotify && abManager)
+ abManager->NotifyDirectoryItemAdded(this, childDir);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::GetChildNodes(nsISimpleEnumerator* *aResult)
+{
+ nsresult rv = EnsureInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewArrayEnumerator(aResult, mSubDirectories);
+}
+
+nsresult nsAbBSDirectory::EnsureInitialized()
+{
+ if (mInitialized)
+ return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbDirFactoryService> dirFactoryService =
+ do_GetService(NS_ABDIRFACTORYSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ nsTArray<DIR_Server*> *directories = DIR_GetDirectories();
+ if (!directories)
+ return NS_ERROR_FAILURE;
+
+ int32_t count = directories->Length();
+ for (int32_t i = 0; i < count; i++)
+ {
+ DIR_Server *server = directories->ElementAt(i);
+
+ // if this is a 4.x, local .na2 addressbook (PABDirectory)
+ // we must skip it.
+ // mozilla can't handle 4.x .na2 addressbooks
+ // note, the filename might be na2 for 4.x LDAP directories
+ // (we used the .na2 file for replication), and we don't want to skip
+ // those. see bug #127007
+ uint32_t fileNameLen = strlen(server->fileName);
+ if (((fileNameLen > kABFileName_PreviousSuffixLen) &&
+ strcmp(server->fileName + fileNameLen - kABFileName_PreviousSuffixLen,
+ kABFileName_PreviousSuffix) == 0) &&
+ (server->dirType == PABDirectory))
+ continue;
+
+ // Set the uri property
+ nsAutoCString URI (server->uri);
+ // This is in case the uri is never set
+ // in the nsDirPref.cpp code.
+ if (!server->uri)
+ {
+ URI = NS_LITERAL_CSTRING(kMDBDirectoryRoot);
+ URI += nsDependentCString(server->fileName);
+ }
+
+ /*
+ * Check that we are not converting from a
+ * a 4.x address book file e.g. pab.na2
+ * check if the URI ends with ".na2"
+ */
+ if (StringEndsWith(URI, NS_LITERAL_CSTRING(kABFileName_PreviousSuffix)))
+ URI.Replace(kMDBDirectoryRootLen, URI.Length() - kMDBDirectoryRootLen, server->fileName);
+
+ // Create the directories
+ rv = CreateDirectoriesFromFactory(URI, server, false /* notify */);
+
+ // If we failed, this could be because something has set a pref for us
+ // which is now broke (e.g. no factory present). So just ignore this one
+ // and move on.
+ if (NS_FAILED(rv))
+ NS_WARNING("CreateDirectoriesFromFactory failed - Invalid factory?");
+ }
+
+ mInitialized = true;
+ // sort directories by position...
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::CreateNewDirectory(const nsAString &aDirName,
+ const nsACString &aURI,
+ uint32_t aType,
+ const nsACString &aPrefName,
+ nsACString &aResult)
+{
+ nsresult rv = EnsureInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /*
+ * TODO
+ * This procedure is still MDB specific
+ * due to the dependence on the current
+ * nsDirPref.cpp code
+ */
+
+ nsCString URI(aURI);
+
+ /*
+ * The creation of the address book in the preferences
+ * is very MDB implementation specific.
+ * If the fileName attribute is null then it will
+ * create an appropriate file name.
+ * Somehow have to resolve this issue so that it
+ * is more general.
+ *
+ */
+ DIR_Server* server = nullptr;
+ rv = DIR_AddNewAddressBook(aDirName, EmptyCString(), URI,
+ (DirectoryType)aType, aPrefName, &server);
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ if (aType == PABDirectory) {
+ // Add the URI property
+ URI.AssignLiteral(kMDBDirectoryRoot);
+ URI.Append(nsDependentCString(server->fileName));
+ }
+
+ aResult.Assign(server->prefName);
+
+ rv = CreateDirectoriesFromFactory(URI, server, true /* notify */);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::CreateDirectoryByURI(const nsAString &aDisplayName,
+ const nsACString &aURI)
+{
+ nsresult rv = EnsureInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString fileName;
+ if (StringBeginsWith(aURI, NS_LITERAL_CSTRING(kMDBDirectoryRoot)))
+ fileName = Substring(aURI, kMDBDirectoryRootLen);
+
+ DIR_Server * server = nullptr;
+ rv = DIR_AddNewAddressBook(aDisplayName, fileName, aURI,
+ PABDirectory, EmptyCString(), &server);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = CreateDirectoriesFromFactory(aURI, server, true /* notify */);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::DeleteDirectory(nsIAbDirectory *directory)
+{
+ NS_ENSURE_ARG_POINTER(directory);
+
+ nsresult rv = EnsureInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DIR_Server *server = nullptr;
+ mServers.Get(directory, &server);
+
+ if (!server)
+ return NS_ERROR_FAILURE;
+
+ struct GetDirectories
+ {
+ explicit GetDirectories(DIR_Server* aServer) : mServer(aServer) { }
+
+ nsCOMArray<nsIAbDirectory> directories;
+ DIR_Server* mServer;
+ };
+ GetDirectories getDirectories(server);
+ for (auto iter = mServers.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.UserData() == getDirectories.mServer) {
+ nsCOMPtr<nsIAbDirectory> abDir = do_QueryInterface(iter.Key());
+ getDirectories.directories.AppendObject(abDir);
+ }
+ }
+
+ DIR_DeleteServerFromList(server);
+
+ nsCOMPtr<nsIAbDirFactoryService> dirFactoryService =
+ do_GetService(NS_ABDIRFACTORYSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ uint32_t count = getDirectories.directories.Count();
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID);
+
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbDirectory> d = getDirectories.directories[i];
+
+ mServers.Remove(d);
+ mSubDirectories.RemoveObject(d);
+
+ if (abManager)
+ abManager->NotifyDirectoryDeleted(this, d);
+
+ nsCString uri;
+ rv = d->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirFactory> dirFactory;
+ rv = dirFactoryService->GetDirFactory(uri, getter_AddRefs(dirFactory));
+ if (NS_FAILED(rv))
+ continue;
+
+ rv = dirFactory->DeleteDirectory(d);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::HasDirectory(nsIAbDirectory *dir, bool *hasDir)
+{
+ if (!hasDir)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = EnsureInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DIR_Server *dirServer = nullptr;
+ mServers.Get(dir, &dirServer);
+ return DIR_ContainsServer(dirServer, hasDir);
+}
+
+NS_IMETHODIMP nsAbBSDirectory::UseForAutocomplete(const nsACString &aIdentityKey,
+ bool *aResult)
+{
+ // For the "root" directory (kAllDirectoryRoot) always return true so that
+ // we can search sub directories that may or may not be local.
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbBSDirectory::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
diff --git a/mailnews/addrbook/src/nsAbBSDirectory.h b/mailnews/addrbook/src/nsAbBSDirectory.h
new file mode 100644
index 000000000..bc550dbf5
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBSDirectory.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsAbBSDirectory_h__
+#define nsAbBSDirectory_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbDirProperty.h"
+
+#include "nsDataHashtable.h"
+#include "nsCOMArray.h"
+
+class nsAbBSDirectory : public nsAbDirProperty
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsAbBSDirectory();
+
+ // nsIAbDirectory methods
+ NS_IMETHOD Init(const char *aURI) override;
+ NS_IMETHOD GetChildNodes(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD CreateNewDirectory(const nsAString &aDirName,
+ const nsACString &aURI,
+ uint32_t aType,
+ const nsACString &aPrefName,
+ nsACString &aResult) override;
+ NS_IMETHOD CreateDirectoryByURI(const nsAString &aDisplayName,
+ const nsACString &aURI) override;
+ NS_IMETHOD DeleteDirectory(nsIAbDirectory *directory) override;
+ NS_IMETHOD HasDirectory(nsIAbDirectory *dir, bool *hasDir) override;
+ NS_IMETHOD UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult) override;
+ NS_IMETHOD GetURI(nsACString &aURI) override;
+
+protected:
+ virtual ~nsAbBSDirectory();
+ nsresult EnsureInitialized();
+ nsresult CreateDirectoriesFromFactory(const nsACString &aURI,
+ DIR_Server* aServer, bool aNotify);
+
+protected:
+ bool mInitialized;
+ nsCOMArray<nsIAbDirectory> mSubDirectories;
+ nsDataHashtable<nsISupportsHashKey, DIR_Server*> mServers;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp
new file mode 100644
index 000000000..679ee792d
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsAbBoolExprToLDAPFilter.h"
+#include "nsStringGlue.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+
+const int nsAbBoolExprToLDAPFilter::TRANSLATE_CARD_PROPERTY = 1 << 0 ;
+const int nsAbBoolExprToLDAPFilter::ALLOW_NON_CONVERTABLE_CARD_PROPERTY = 1 << 1 ;
+
+nsresult nsAbBoolExprToLDAPFilter::Convert (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanExpression* expression,
+ nsCString& filter,
+ int flags)
+{
+ nsCString f;
+ nsresult rv = FilterExpression (map, expression, f, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ filter = f;
+ return rv;
+}
+
+nsresult nsAbBoolExprToLDAPFilter::FilterExpression (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanExpression* expression,
+ nsCString& filter,
+ int flags)
+{
+ nsCOMPtr<nsIArray> childExpressions;
+ nsresult rv = expression->GetExpressions(getter_AddRefs(childExpressions));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ rv = childExpressions->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count == 0)
+ return NS_OK;
+
+ nsAbBooleanOperationType operation;
+ rv = expression->GetOperation(&operation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /*
+ * 3rd party query integration with Mozilla is achieved
+ * by calling nsAbLDAPDirectoryQuery::DoQuery(). Thus
+ * we can arrive here with a query asking for all the
+ * ldap attributes using the card:nsIAbCard interface.
+ *
+ * So we need to check that we are not creating a condition
+ * filter against this expression otherwise we will end up with an invalid
+ * filter equal to "(|)".
+ */
+
+ if (count == 1 )
+ {
+ nsCOMPtr<nsIAbBooleanConditionString>
+ childCondition(do_QueryElementAt(childExpressions, 1, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString name;
+ rv = childCondition->GetName (getter_Copies (name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if(name.Equals("card:nsIAbCard"))
+ return NS_OK;
+ }
+ }
+
+ filter.AppendLiteral("(");
+ switch (operation)
+ {
+ case nsIAbBooleanOperationTypes::AND:
+ filter.AppendLiteral("&");
+ rv = FilterExpressions (map, childExpressions, filter, flags);
+ break;
+ case nsIAbBooleanOperationTypes::OR:
+ filter.AppendLiteral("|");
+ rv = FilterExpressions (map, childExpressions, filter, flags);
+ break;
+ case nsIAbBooleanOperationTypes::NOT:
+ if (count > 1)
+ return NS_ERROR_FAILURE;
+ filter.AppendLiteral("!");
+ rv = FilterExpressions (map, childExpressions, filter, flags);
+ break;
+ default:
+ break;
+ }
+ filter.AppendLiteral(")");
+
+ return rv;
+}
+
+nsresult nsAbBoolExprToLDAPFilter::FilterExpressions (
+ nsIAbLDAPAttributeMap *map,
+ nsIArray* expressions,
+ nsCString& filter,
+ int flags)
+{
+ uint32_t count;
+ nsresult rv = expressions->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanConditionString> childCondition;
+ nsCOMPtr<nsIAbBooleanExpression> childExpression;
+ for (uint32_t i = 0; i < count; i++)
+ {
+ childCondition = do_QueryElementAt(expressions, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = FilterCondition (map, childCondition, filter, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+
+ childExpression = do_QueryElementAt(expressions, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = FilterExpression (map, childExpression, filter, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsAbBoolExprToLDAPFilter::FilterCondition (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanConditionString* condition,
+ nsCString& filter,
+ int flags)
+{
+ nsCString name;
+ nsresult rv = condition->GetName(getter_Copies (name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ldapAttr(name);
+ if (flags & TRANSLATE_CARD_PROPERTY)
+ {
+ rv = map->GetFirstAttribute (name, ldapAttr);
+ if (!(flags & ALLOW_NON_CONVERTABLE_CARD_PROPERTY) &&
+ !ATTRMAP_FOUND_ATTR(rv, ldapAttr))
+ return NS_OK;
+ }
+
+ nsAbBooleanConditionType conditionType;
+ rv = condition->GetCondition(&conditionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString value;
+ rv = condition->GetValue (getter_Copies (value));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ConvertUTF16toUTF8 vUTF8 (value);
+
+ switch (conditionType)
+ {
+ case nsIAbBooleanConditionTypes::DoesNotExist:
+ filter.AppendLiteral("(!(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=*))");
+ break;
+ case nsIAbBooleanConditionTypes::Exists:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=*)");
+ break;
+ case nsIAbBooleanConditionTypes::Contains:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.Append("=*");
+ filter.Append(vUTF8);
+ filter.AppendLiteral("*)");
+ break;
+ case nsIAbBooleanConditionTypes::DoesNotContain:
+ filter.AppendLiteral("(!(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=*");
+ filter.Append(vUTF8);
+ filter.AppendLiteral("*))");
+ break;
+ case nsIAbBooleanConditionTypes::Is:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral(")");
+ break;
+ case nsIAbBooleanConditionTypes::IsNot:
+ filter.AppendLiteral("(!(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral("))");
+ break;
+ case nsIAbBooleanConditionTypes::BeginsWith:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral("*)");
+ break;
+ case nsIAbBooleanConditionTypes::EndsWith:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("=*");
+ filter.Append(vUTF8);
+ filter.AppendLiteral(")");
+ break;
+ case nsIAbBooleanConditionTypes::LessThan:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("<=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral(")");
+ break;
+ case nsIAbBooleanConditionTypes::GreaterThan:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral(">=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral(")");
+ break;
+ case nsIAbBooleanConditionTypes::SoundsLike:
+ filter.AppendLiteral("(");
+ filter.Append(ldapAttr);
+ filter.AppendLiteral("~=");
+ filter.Append(vUTF8);
+ filter.AppendLiteral(")");
+ break;
+ case nsIAbBooleanConditionTypes::RegExp:
+ break;
+ default:
+ break;
+ }
+
+ return rv;
+}
+
diff --git a/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h
new file mode 100644
index 000000000..5ea892595
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBooleanExpressionToLDAPFilter_h__
+#define nsBooleanExpressionToLDAPFilter_h__
+
+#include "nsIAbBooleanExpression.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+
+class nsIAbLDAPAttributeMap;
+
+class nsAbBoolExprToLDAPFilter
+{
+public:
+ static const int TRANSLATE_CARD_PROPERTY ;
+ static const int ALLOW_NON_CONVERTABLE_CARD_PROPERTY ;
+
+ static nsresult Convert (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanExpression* expression,
+ nsCString& filter,
+ int flags = TRANSLATE_CARD_PROPERTY);
+
+protected:
+ static nsresult FilterExpression (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanExpression* expression,
+ nsCString& filter,
+ int flags);
+ static nsresult FilterExpressions (
+ nsIAbLDAPAttributeMap* map,
+ nsIArray* expressions,
+ nsCString& filter,
+ int flags);
+ static nsresult FilterCondition (
+ nsIAbLDAPAttributeMap* map,
+ nsIAbBooleanConditionString* condition,
+ nsCString& filter,
+ int flags);
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbBooleanExpression.cpp b/mailnews/addrbook/src/nsAbBooleanExpression.cpp
new file mode 100644
index 000000000..a1a39c1fa
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBooleanExpression.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbBooleanExpression.h"
+#include "nsComponentManagerUtils.h"
+
+NS_IMPL_ISUPPORTS(nsAbBooleanConditionString, nsIAbBooleanConditionString)
+
+nsAbBooleanConditionString::nsAbBooleanConditionString() :
+ mCondition (nsIAbBooleanConditionTypes::Exists)
+{
+}
+
+nsAbBooleanConditionString::~nsAbBooleanConditionString()
+{
+}
+
+/* attribute nsAbBooleanConditionType condition; */
+NS_IMETHODIMP nsAbBooleanConditionString::GetCondition(nsAbBooleanConditionType *aCondition)
+{
+ if (!aCondition)
+ return NS_ERROR_NULL_POINTER;
+
+ *aCondition = mCondition;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbBooleanConditionString::SetCondition(nsAbBooleanConditionType aCondition)
+{
+ mCondition = aCondition;
+
+ return NS_OK;
+}
+
+/* attribute string name; */
+NS_IMETHODIMP nsAbBooleanConditionString::GetName(char** aName)
+{
+ if (!aName)
+ return NS_ERROR_NULL_POINTER;
+
+ *aName = mName.IsEmpty() ? 0 : ToNewCString(mName);
+
+ return NS_OK;
+
+}
+NS_IMETHODIMP nsAbBooleanConditionString::SetName(const char* aName)
+{
+ if (!aName)
+ return NS_ERROR_NULL_POINTER;
+
+ mName = aName;
+
+ return NS_OK;
+}
+
+/* attribute wstring value; */
+NS_IMETHODIMP nsAbBooleanConditionString::GetValue(char16_t** aValue)
+{
+ if (!aValue)
+ return NS_ERROR_NULL_POINTER;
+
+ *aValue = ToNewUnicode(mValue);
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbBooleanConditionString::SetValue(const char16_t * aValue)
+{
+ if (!aValue)
+ return NS_ERROR_NULL_POINTER;
+
+ mValue = aValue;
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsAbBooleanExpression, nsIAbBooleanExpression)
+
+nsAbBooleanExpression::nsAbBooleanExpression() :
+ mOperation (nsIAbBooleanOperationTypes::AND)
+{
+}
+
+nsAbBooleanExpression::~nsAbBooleanExpression()
+{
+}
+
+/* attribute nsAbBooleanOperationType operation; */
+NS_IMETHODIMP nsAbBooleanExpression::GetOperation(nsAbBooleanOperationType *aOperation)
+{
+ if (!aOperation)
+ return NS_ERROR_NULL_POINTER;
+
+ *aOperation = mOperation;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbBooleanExpression::SetOperation(nsAbBooleanOperationType aOperation)
+{
+ mOperation = aOperation;
+
+ return NS_OK;
+}
+
+/* attribute nsIArray expressions; */
+NS_IMETHODIMP nsAbBooleanExpression::GetExpressions(nsIArray **aExpressions)
+{
+ if (!aExpressions)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!mExpressions)
+ {
+ mExpressions = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ if (!mExpressions)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_ADDREF(*aExpressions = mExpressions);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbBooleanExpression::SetExpressions(nsIArray *aExpressions)
+{
+ if (!aExpressions)
+ return NS_ERROR_NULL_POINTER;
+
+ mExpressions = aExpressions;
+
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbBooleanExpression.h b/mailnews/addrbook/src/nsAbBooleanExpression.h
new file mode 100644
index 000000000..697caf5f5
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbBooleanExpression.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbBooleanExpression_h__
+#define nsAbBooleanExpression_h__
+
+#include "nsIAbBooleanExpression.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIArray.h"
+
+class nsAbBooleanConditionString : public nsIAbBooleanConditionString
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABBOOLEANCONDITIONSTRING
+
+ nsAbBooleanConditionString();
+
+protected:
+ virtual ~nsAbBooleanConditionString();
+ nsAbBooleanConditionType mCondition;
+ nsCString mName;
+ nsString mValue;
+};
+
+class nsAbBooleanExpression: public nsIAbBooleanExpression
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABBOOLEANEXPRESSION
+
+ nsAbBooleanExpression();
+
+protected:
+ virtual ~nsAbBooleanExpression();
+ nsAbBooleanOperationType mOperation;
+ nsCOMPtr<nsIArray> mExpressions;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbCardProperty.cpp b/mailnews/addrbook/src/nsAbCardProperty.cpp
new file mode 100644
index 000000000..aa4b51db1
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbCardProperty.cpp
@@ -0,0 +1,1193 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbCardProperty.h"
+#include "nsAbBaseCID.h"
+#include "nsIPrefService.h"
+#include "nsIAbDirectory.h"
+#include "plbase64.h"
+#include "nsIStringBundle.h"
+#include "plstr.h"
+#include "nsMsgUtils.h"
+#include "nsINetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsVCardObj.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsIAbManager.h"
+
+#include "nsVariant.h"
+#include "nsIProperty.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "prmem.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+using namespace mozilla;
+
+#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst"
+
+const char sAddrbookProperties[] = "chrome://messenger/locale/addressbook/addressBook.properties";
+
+enum EAppendType {
+ eAppendLine,
+ eAppendLabel,
+ eAppendCityStateZip
+};
+
+struct AppendItem {
+ const char *mColumn;
+ const char* mLabel;
+ EAppendType mAppendType;
+};
+
+static const AppendItem NAME_ATTRS_ARRAY[] = {
+ {kDisplayNameProperty, "propertyDisplayName", eAppendLabel},
+ {kNicknameProperty, "propertyNickname", eAppendLabel},
+ {kPriEmailProperty, "", eAppendLine},
+#ifndef MOZ_THUNDERBIRD
+ {k2ndEmailProperty, "", eAppendLine},
+ {kScreenNameProperty, "propertyScreenName", eAppendLabel}
+#else
+ {k2ndEmailProperty, "", eAppendLine}
+#endif
+};
+
+static const AppendItem PHONE_ATTRS_ARRAY[] = {
+ {kWorkPhoneProperty, "propertyWork", eAppendLabel},
+ {kHomePhoneProperty, "propertyHome", eAppendLabel},
+ {kFaxProperty, "propertyFax", eAppendLabel},
+ {kPagerProperty, "propertyPager", eAppendLabel},
+ {kCellularProperty, "propertyCellular", eAppendLabel}
+};
+
+static const AppendItem HOME_ATTRS_ARRAY[] = {
+ {kHomeAddressProperty, "", eAppendLine},
+ {kHomeAddress2Property, "", eAppendLine},
+ {kHomeCityProperty, "", eAppendCityStateZip},
+ {kHomeCountryProperty, "", eAppendLine},
+ {kHomeWebPageProperty, "", eAppendLine}
+};
+
+static const AppendItem WORK_ATTRS_ARRAY[] = {
+ {kJobTitleProperty, "", eAppendLine},
+ {kDepartmentProperty, "", eAppendLine},
+ {kCompanyProperty, "", eAppendLine},
+ {kWorkAddressProperty, "", eAppendLine},
+ {kWorkAddress2Property, "", eAppendLine},
+ {kWorkCityProperty, "", eAppendCityStateZip},
+ {kWorkCountryProperty, "", eAppendLine},
+ {kWorkWebPageProperty, "", eAppendLine}
+};
+
+static const AppendItem CUSTOM_ATTRS_ARRAY[] = {
+ {kCustom1Property, "propertyCustom1", eAppendLabel},
+ {kCustom2Property, "propertyCustom2", eAppendLabel},
+ {kCustom3Property, "propertyCustom3", eAppendLabel},
+ {kCustom4Property, "propertyCustom4", eAppendLabel},
+ {kNotesProperty, "", eAppendLine}
+};
+
+#ifdef MOZ_THUNDERBIRD
+
+static const AppendItem CHAT_ATTRS_ARRAY[] = {
+ {kGtalkProperty, "propertyGtalk", eAppendLabel},
+ {kAIMProperty, "propertyAIM", eAppendLabel},
+ {kYahooProperty, "propertyYahoo", eAppendLabel},
+ {kSkypeProperty, "propertySkype", eAppendLabel},
+ {kQQProperty, "propertyQQ", eAppendLabel},
+ {kMSNProperty, "propertyMSN", eAppendLabel},
+ {kICQProperty, "propertyICQ", eAppendLabel},
+ {kXMPPProperty, "propertyXMPP", eAppendLabel},
+ {kIRCProperty, "propertyIRC", eAppendLabel}
+};
+#endif
+
+nsAbCardProperty::nsAbCardProperty()
+ : m_IsMailList(false)
+{
+ // Initialize some default properties
+ SetPropertyAsUint32(kPreferMailFormatProperty, nsIAbPreferMailFormat::unknown);
+ SetPropertyAsUint32(kPopularityIndexProperty, 0);
+ // Uninitialized...
+ SetPropertyAsUint32(kLastModifiedDateProperty, 0);
+}
+
+nsAbCardProperty::~nsAbCardProperty(void)
+{
+}
+
+NS_IMPL_ISUPPORTS(nsAbCardProperty, nsIAbCard, nsIAbItem)
+
+NS_IMETHODIMP nsAbCardProperty::GetUuid(nsACString &uuid)
+{
+ // If we have indeterminate sub-ids, return an empty uuid.
+ if (m_directoryId.Equals("") || m_localId.Equals(""))
+ {
+ uuid.Truncate();
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> manager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return manager->GenerateUUID(m_directoryId, m_localId, uuid);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetDirectoryId(nsACString &dirId)
+{
+ dirId = m_directoryId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetDirectoryId(const nsACString &aDirId)
+{
+ m_directoryId = aDirId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetLocalId(nsACString &localId)
+{
+ localId = m_localId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetLocalId(const nsACString &aLocalId)
+{
+ m_localId = aLocalId;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsAbCardProperty::GetIsMailList(bool *aIsMailList)
+{
+ *aIsMailList = m_IsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetIsMailList(bool aIsMailList)
+{
+ m_IsMailList = aIsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetMailListURI(char **aMailListURI)
+{
+ if (aMailListURI)
+ {
+ *aMailListURI = ToNewCString(m_MailListURI);
+ return (*aMailListURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetMailListURI(const char *aMailListURI)
+{
+ if (aMailListURI)
+ {
+ m_MailListURI = aMailListURI;
+ return NS_OK;
+ }
+ else
+ return NS_ERROR_NULL_POINTER;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Property bag portion of nsAbCardProperty
+///////////////////////////////////////////////////////////////////////////////
+
+class nsAbSimpleProperty final : public nsIProperty {
+public:
+ nsAbSimpleProperty(const nsACString& aName, nsIVariant* aValue)
+ : mName(aName), mValue(aValue)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROPERTY
+protected:
+ ~nsAbSimpleProperty() {}
+ nsCString mName;
+ nsCOMPtr<nsIVariant> mValue;
+};
+
+NS_IMPL_ISUPPORTS(nsAbSimpleProperty, nsIProperty)
+
+NS_IMETHODIMP
+nsAbSimpleProperty::GetName(nsAString& aName)
+{
+ aName.Assign(NS_ConvertUTF8toUTF16(mName));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbSimpleProperty::GetValue(nsIVariant* *aValue)
+{
+ NS_IF_ADDREF(*aValue = mValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetProperties(nsISimpleEnumerator **props)
+{
+ nsCOMArray<nsIProperty> propertyArray(m_properties.Count());
+ for (auto iter = m_properties.Iter(); !iter.Done(); iter.Next()) {
+ propertyArray.AppendObject(new nsAbSimpleProperty(iter.Key(),
+ iter.UserData()));
+ }
+ return NS_NewArrayEnumerator(props, propertyArray);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetProperty(const nsACString &name,
+ nsIVariant *defaultValue,
+ nsIVariant **value)
+{
+ if (!m_properties.Get(name, value))
+ NS_ADDREF(*value = defaultValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsAString(const char *name, nsAString &value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ?
+ variant->GetAsAString(value) : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsAUTF8String(const char *name, nsACString &value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ?
+ variant->GetAsAUTF8String(value) : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsUint32(const char *name, uint32_t *value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ?
+ variant->GetAsUint32(value) : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsBool(const char *name, bool *value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ?
+ variant->GetAsBool(value) : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetProperty(const nsACString &name, nsIVariant *value)
+{
+ m_properties.Put(name, value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsAString(const char *name, const nsAString &value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsAString(value);
+ m_properties.Put(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsAUTF8String(const char *name, const nsACString &value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsAUTF8String(value);
+ m_properties.Put(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsUint32(const char *name, uint32_t value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsUint32(value);
+ m_properties.Put(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsBool(const char *name, bool value)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsBool(value);
+ m_properties.Put(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::DeleteProperty(const nsACString &name)
+{
+ m_properties.Remove(name);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetFirstName(nsAString &aString)
+{
+ nsresult rv = GetPropertyAsAString(kFirstNameProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetFirstName(const nsAString &aString)
+{
+ return SetPropertyAsAString(kFirstNameProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetLastName(nsAString &aString)
+{
+ nsresult rv = GetPropertyAsAString(kLastNameProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetLastName(const nsAString &aString)
+{
+ return SetPropertyAsAString(kLastNameProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetDisplayName(nsAString &aString)
+{
+ nsresult rv = GetPropertyAsAString(kDisplayNameProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetDisplayName(const nsAString &aString)
+{
+ return SetPropertyAsAString(kDisplayNameProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPrimaryEmail(nsAString &aString)
+{
+ nsresult rv = GetPropertyAsAString(kPriEmailProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPrimaryEmail(const nsAString &aString)
+{
+ return SetPropertyAsAString(kPriEmailProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::HasEmailAddress(const nsACString &aEmailAddress,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = false;
+
+ nsCString emailAddress;
+ nsresult rv = GetPropertyAsAUTF8String(kPriEmailProperty, emailAddress);
+ if (rv != NS_ERROR_NOT_AVAILABLE &&
+ emailAddress.Equals(aEmailAddress, nsCaseInsensitiveCStringComparator()))
+ {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ rv = GetPropertyAsAUTF8String(k2ndEmailProperty, emailAddress);
+ if (rv != NS_ERROR_NOT_AVAILABLE &&
+ emailAddress.Equals(aEmailAddress, nsCaseInsensitiveCStringComparator()))
+ *aResult = true;
+
+ return NS_OK;
+}
+
+// This function may be overridden by derived classes for
+// nsAb*Card specific implementations.
+NS_IMETHODIMP nsAbCardProperty::Copy(nsIAbCard* srcCard)
+{
+ NS_ENSURE_ARG_POINTER(srcCard);
+
+ nsCOMPtr<nsISimpleEnumerator> properties;
+ nsresult rv = srcCard->GetProperties(getter_AddRefs(properties));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ nsCOMPtr<nsISupports> result;
+ while (NS_SUCCEEDED(rv = properties->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = properties->GetNext(getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIProperty> property = do_QueryInterface(result, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString name;
+ property->GetName(name);
+ nsCOMPtr<nsIVariant> value;
+ property->GetValue(getter_AddRefs(value));
+
+ SetProperty(NS_ConvertUTF16toUTF8(name), value);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isMailList;
+ srcCard->GetIsMailList(&isMailList);
+ SetIsMailList(isMailList);
+
+ nsCString mailListURI;
+ srcCard->GetMailListURI(getter_Copies(mailListURI));
+ SetMailListURI(mailListURI.get());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::Equals(nsIAbCard *card, bool *result)
+{
+ *result = (card == this);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The following methods are other views of a card
+////////////////////////////////////////////////////////////////////////////////
+
+// XXX: Use the category manager instead of this file to implement these
+NS_IMETHODIMP nsAbCardProperty::TranslateTo(const nsACString &type, nsACString &result)
+{
+ if (type.EqualsLiteral("base64xml"))
+ return ConvertToBase64EncodedXML(result);
+ else if (type.EqualsLiteral("xml"))
+ {
+ nsString utf16String;
+ nsresult rv = ConvertToXMLPrintData(utf16String);
+ NS_ENSURE_SUCCESS(rv, rv);
+ result = NS_ConvertUTF16toUTF8(utf16String);
+ return NS_OK;
+ }
+ else if (type.EqualsLiteral("vcard"))
+ return ConvertToEscapedVCard(result);
+
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+//
+static VObject* myAddPropValue(VObject *o, const char *propName, const char16_t *propValue, bool *aCardHasData)
+{
+ if (aCardHasData)
+ *aCardHasData = true;
+ return addPropValue(o, propName, NS_ConvertUTF16toUTF8(propValue).get());
+}
+
+nsresult nsAbCardProperty::ConvertToEscapedVCard(nsACString &aResult)
+{
+ nsString str;
+ nsresult rv;
+ bool vCardHasData = false;
+ VObject* vObj = newVObject(VCCardProp);
+ VObject* t;
+
+ // [comment from 4.x]
+ // Big flame coming....so Vobject is not designed at all to work with an array of
+ // attribute values. It wants you to have all of the attributes easily available. You
+ // cannot add one attribute at a time as you find them to the vobject. Why? Because
+ // it creates a property for a particular type like phone number and then that property
+ // has multiple values. This implementation is not pretty. I can hear my algos prof
+ // yelling from here.....I have to do a linear search through my attributes array for
+ // EACH vcard property we want to set. *sigh* One day I will have time to come back
+ // to this function and remedy this O(m*n) function where n = # attribute values and
+ // m = # of vcard properties....
+
+ (void)GetDisplayName(str);
+ if (!str.IsEmpty()) {
+ myAddPropValue(vObj, VCFullNameProp, str.get(), &vCardHasData);
+ }
+
+ (void)GetLastName(str);
+ if (!str.IsEmpty()) {
+ t = isAPropertyOf(vObj, VCNameProp);
+ if (!t)
+ t = addProp(vObj, VCNameProp);
+ myAddPropValue(t, VCFamilyNameProp, str.get(), &vCardHasData);
+ }
+
+ (void)GetFirstName(str);
+ if (!str.IsEmpty()) {
+ t = isAPropertyOf(vObj, VCNameProp);
+ if (!t)
+ t = addProp(vObj, VCNameProp);
+ myAddPropValue(t, VCGivenNameProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kCompanyProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCOrgProp);
+ if (!t)
+ t = addProp(vObj, VCOrgProp);
+ myAddPropValue(t, VCOrgNameProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kDepartmentProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCOrgProp);
+ if (!t)
+ t = addProp(vObj, VCOrgProp);
+ myAddPropValue(t, VCOrgUnitProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkAddress2Property, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCPostalBoxProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkAddressProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCStreetAddressProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkCityProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCCityProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkStateProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCRegionProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkZipCodeProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCPostalCodeProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkCountryProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (!t)
+ t = addProp(vObj, VCAdrProp);
+ myAddPropValue(t, VCCountryNameProp, str.get(), &vCardHasData);
+ }
+ else
+ {
+ // only add this if VCAdrProp already exists
+ t = isAPropertyOf(vObj, VCAdrProp);
+ if (t)
+ {
+ addProp(t, VCDomesticProp);
+ }
+ }
+
+ (void)GetPrimaryEmail(str);
+ if (!str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCEmailAddressProp, str.get(), &vCardHasData);
+ addProp(t, VCInternetProp);
+ }
+
+ rv = GetPropertyAsAString(kJobTitleProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ myAddPropValue(vObj, VCTitleProp, str.get(), &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkPhoneProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData);
+ addProp(t, VCWorkProp);
+ }
+
+ rv = GetPropertyAsAString(kFaxProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData);
+ addProp(t, VCFaxProp);
+ }
+
+ rv = GetPropertyAsAString(kPagerProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData);
+ addProp(t, VCPagerProp);
+ }
+
+ rv = GetPropertyAsAString(kHomePhoneProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData);
+ addProp(t, VCHomeProp);
+ }
+
+ rv = GetPropertyAsAString(kCellularProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData);
+ addProp(t, VCCellularProp);
+ }
+
+ rv = GetPropertyAsAString(kNotesProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ myAddPropValue(vObj, VCNoteProp, str.get(), &vCardHasData);
+ }
+
+ uint32_t format;
+ rv = GetPropertyAsUint32(kPreferMailFormatProperty, &format);
+ if (NS_SUCCEEDED(rv) && format == nsIAbPreferMailFormat::html) {
+ myAddPropValue(vObj, VCUseHTML, u"TRUE", &vCardHasData);
+ }
+ else if (NS_SUCCEEDED(rv) && format == nsIAbPreferMailFormat::plaintext) {
+ myAddPropValue(vObj, VCUseHTML, u"FALSE", &vCardHasData);
+ }
+
+ rv = GetPropertyAsAString(kWorkWebPageProperty, str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty())
+ {
+ myAddPropValue(vObj, VCURLProp, str.get(), &vCardHasData);
+ }
+
+ myAddPropValue(vObj, VCVersionProp, u"2.1", nullptr);
+
+ if (!vCardHasData) {
+ aResult.Truncate();
+ cleanVObject(vObj);
+ return NS_OK;
+ }
+
+ int len = 0;
+ char *vCard = writeMemVObject(0, &len, vObj);
+ if (vObj)
+ cleanVObject(vObj);
+
+ nsCString escResult;
+ MsgEscapeString(nsDependentCString(vCard), nsINetUtil::ESCAPE_URL_PATH, escResult);
+ aResult = escResult;
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::ConvertToBase64EncodedXML(nsACString &result)
+{
+ nsresult rv;
+ nsString xmlStr;
+
+ xmlStr.AppendLiteral("<?xml version=\"1.0\"?>\n"
+ "<?xml-stylesheet type=\"text/css\" href=\"chrome://messagebody/content/addressbook/print.css\"?>\n"
+ "<directory>\n");
+
+ // Get Address Book string and set it as title of XML document
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ if (stringBundleService) {
+ rv = stringBundleService->CreateBundle(sAddrbookProperties, getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsString addrBook;
+ rv = bundle->GetStringFromName(u"addressBook", getter_Copies(addrBook));
+ if (NS_SUCCEEDED(rv)) {
+ xmlStr.AppendLiteral("<title xmlns=\"http://www.w3.org/1999/xhtml\">");
+ xmlStr.Append(addrBook);
+ xmlStr.AppendLiteral("</title>\n");
+ }
+ }
+ }
+
+ nsString xmlSubstr;
+ rv = ConvertToXMLPrintData(xmlSubstr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ xmlStr.Append(xmlSubstr);
+ xmlStr.AppendLiteral("</directory>\n");
+
+ char *tmpRes = PL_Base64Encode(NS_ConvertUTF16toUTF8(xmlStr).get(), 0, nullptr);
+ result.Assign(tmpRes);
+ PR_Free(tmpRes);
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::ConvertToXMLPrintData(nsAString &aXMLSubstr)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t generatedNameFormat;
+ rv = prefBranch->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &generatedNameFormat);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = stringBundleService->CreateBundle(sAddrbookProperties, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString generatedName;
+ rv = GenerateName(generatedNameFormat, bundle, generatedName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<mozITXTToHTMLConv> conv = do_CreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString xmlStr;
+ xmlStr.SetLength(4096); // to reduce allocations. should be enough for most cards
+ xmlStr.AssignLiteral("<GeneratedName>\n");
+
+ // use ScanTXT to convert < > & to safe values.
+ nsString safeText;
+ if (!generatedName.IsEmpty()) {
+ rv = conv->ScanTXT(generatedName.get(), mozITXTToHTMLConv::kEntities,
+ getter_Copies(safeText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (safeText.IsEmpty()) {
+ nsAutoString primaryEmail;
+ GetPrimaryEmail(primaryEmail);
+
+ // use ScanTXT to convert < > & to safe values.
+ rv = conv->ScanTXT(primaryEmail.get(), mozITXTToHTMLConv::kEntities,
+ getter_Copies(safeText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ xmlStr.Append(safeText);
+
+ xmlStr.AppendLiteral("</GeneratedName>\n"
+ "<table><tr><td>");
+
+ rv = AppendSection(NAME_ATTRS_ARRAY, sizeof(NAME_ATTRS_ARRAY)/sizeof(AppendItem), EmptyString(), bundle, conv, xmlStr);
+
+ xmlStr.AppendLiteral("</td></tr><tr><td>");
+
+ rv = AppendSection(PHONE_ATTRS_ARRAY, sizeof(PHONE_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingPhone"), bundle, conv, xmlStr);
+
+ if (!m_IsMailList) {
+ rv = AppendSection(CUSTOM_ATTRS_ARRAY, sizeof(CUSTOM_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingOther"), bundle, conv, xmlStr);
+#ifdef MOZ_THUNDERBIRD
+ rv = AppendSection(CHAT_ATTRS_ARRAY, sizeof(CHAT_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingChat"), bundle, conv, xmlStr);
+#endif
+ }
+ else {
+ rv = AppendSection(CUSTOM_ATTRS_ARRAY, sizeof(CUSTOM_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingDescription"),
+ bundle, conv, xmlStr);
+
+ xmlStr.AppendLiteral("<section><sectiontitle>");
+
+ nsString headingAddresses;
+ rv = bundle->GetStringFromName(u"headingAddresses", getter_Copies(headingAddresses));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ xmlStr.Append(headingAddresses);
+ xmlStr.AppendLiteral("</sectiontitle>");
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIAbDirectory> mailList = nullptr;
+ rv = abManager->GetDirectory(m_MailListURI, getter_AddRefs(mailList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> addresses;
+ rv = mailList->GetAddressLists(getter_AddRefs(addresses));
+ if (addresses) {
+ uint32_t total = 0;
+ addresses->GetLength(&total);
+ if (total) {
+ uint32_t i;
+ nsAutoString displayName;
+ nsAutoString primaryEmail;
+ for (i = 0; i < total; i++) {
+ nsCOMPtr <nsIAbCard> listCard = do_QueryElementAt(addresses, i, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ xmlStr.AppendLiteral("<PrimaryEmail>\n");
+
+ rv = listCard->GetDisplayName(displayName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // use ScanTXT to convert < > & to safe values.
+ nsString safeText;
+ rv = conv->ScanTXT(displayName.get(), mozITXTToHTMLConv::kEntities,
+ getter_Copies(safeText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ xmlStr.Append(safeText);
+
+ xmlStr.AppendLiteral(" &lt;");
+
+ listCard->GetPrimaryEmail(primaryEmail);
+
+ // use ScanTXT to convert < > & to safe values.
+ rv = conv->ScanTXT(primaryEmail.get(), mozITXTToHTMLConv::kEntities,
+ getter_Copies(safeText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ xmlStr.Append(safeText);
+
+ xmlStr.AppendLiteral("&gt;</PrimaryEmail>\n");
+ }
+ }
+ }
+ xmlStr.AppendLiteral("</section>");
+ }
+
+ xmlStr.AppendLiteral("</td><td>");
+
+ rv = AppendSection(HOME_ATTRS_ARRAY, sizeof(HOME_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingHome"), bundle, conv, xmlStr);
+ rv = AppendSection(WORK_ATTRS_ARRAY, sizeof(WORK_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingWork"), bundle, conv, xmlStr);
+
+ xmlStr.AppendLiteral("</td></tr></table>");
+
+ aXMLSubstr = xmlStr;
+
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::AppendSection(const AppendItem *aArray, int16_t aCount, const nsString& aHeading,
+ nsIStringBundle *aBundle,
+ mozITXTToHTMLConv *aConv,
+ nsString &aResult)
+{
+ nsresult rv = NS_OK;
+
+ aResult.AppendLiteral("<section>");
+
+ nsString attrValue;
+ bool sectionIsEmpty = true;
+
+ int16_t i = 0;
+ for (i=0;i<aCount;i++) {
+ rv = GetPropertyAsAString(aArray[i].mColumn, attrValue);
+ if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty())
+ sectionIsEmpty = false;
+ }
+
+ if (!sectionIsEmpty && !aHeading.IsEmpty()) {
+ nsString heading;
+ rv = aBundle->GetStringFromName(aHeading.get(), getter_Copies(heading));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.AppendLiteral("<sectiontitle>");
+ aResult.Append(heading);
+ aResult.AppendLiteral("</sectiontitle>");
+ }
+
+ for (i=0;i<aCount;i++) {
+ switch (aArray[i].mAppendType) {
+ case eAppendLine:
+ rv = AppendLine(aArray[i], aConv, aResult);
+ break;
+ case eAppendLabel:
+ rv = AppendLabel(aArray[i], aBundle, aConv, aResult);
+ break;
+ case eAppendCityStateZip:
+ rv = AppendCityStateZip(aArray[i], aBundle, aConv, aResult);
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("append item failed");
+ break;
+ }
+ }
+ aResult.AppendLiteral("</section>");
+
+ return rv;
+}
+
+nsresult nsAbCardProperty::AppendLine(const AppendItem &aItem,
+ mozITXTToHTMLConv *aConv,
+ nsString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(aConv);
+
+ nsString attrValue;
+ nsresult rv = GetPropertyAsAString(aItem.mColumn, attrValue);
+
+ if (NS_FAILED(rv) || attrValue.IsEmpty())
+ return NS_OK;
+
+ aResult.Append(char16_t('<'));
+ aResult.Append(NS_ConvertUTF8toUTF16(aItem.mColumn));
+ aResult.Append(char16_t('>'));
+
+ // use ScanTXT to convert < > & to safe values.
+ nsString safeText;
+ rv = aConv->ScanTXT(attrValue.get(), mozITXTToHTMLConv::kEntities, getter_Copies(safeText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ aResult.Append(safeText);
+
+ aResult.AppendLiteral("</");
+ aResult.Append(NS_ConvertUTF8toUTF16(aItem.mColumn));
+ aResult.Append(char16_t('>'));
+
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::AppendLabel(const AppendItem &aItem,
+ nsIStringBundle *aBundle,
+ mozITXTToHTMLConv *aConv,
+ nsString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+
+ nsresult rv;
+ nsString label, attrValue;
+
+ rv = GetPropertyAsAString(aItem.mColumn, attrValue);
+
+ if (NS_FAILED(rv) || attrValue.IsEmpty())
+ return NS_OK;
+
+ rv = aBundle->GetStringFromName(NS_ConvertUTF8toUTF16(aItem.mLabel).get(), getter_Copies(label));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.AppendLiteral("<labelrow><label>");
+
+ aResult.Append(label);
+ aResult.AppendLiteral(": </label>");
+
+ rv = AppendLine(aItem, aConv, aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ aResult.AppendLiteral("</labelrow>");
+
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::AppendCityStateZip(const AppendItem &aItem,
+ nsIStringBundle *aBundle,
+ mozITXTToHTMLConv *aConv,
+ nsString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+
+ nsresult rv;
+ AppendItem item;
+ const char *statePropName, *zipPropName;
+
+ if (strcmp(aItem.mColumn, kHomeCityProperty) == 0) {
+ statePropName = kHomeStateProperty;
+ zipPropName = kHomeZipCodeProperty;
+ }
+ else {
+ statePropName = kWorkStateProperty;
+ zipPropName = kWorkZipCodeProperty;
+ }
+
+ nsAutoString cityResult, stateResult, zipResult;
+
+ rv = AppendLine(aItem, aConv, cityResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ item.mColumn = statePropName;
+ item.mLabel = "";
+
+ rv = AppendLine(item, aConv, stateResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ item.mColumn = zipPropName;
+
+ rv = AppendLine(item, aConv, zipResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString formattedString;
+
+ if (!cityResult.IsEmpty() && !stateResult.IsEmpty() && !zipResult.IsEmpty()) {
+ const char16_t *formatStrings[] = { cityResult.get(), stateResult.get(), zipResult.get() };
+ rv = aBundle->FormatStringFromName(u"cityAndStateAndZip", formatStrings, ArrayLength(formatStrings), getter_Copies(formattedString));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else if (!cityResult.IsEmpty() && !stateResult.IsEmpty() && zipResult.IsEmpty()) {
+ const char16_t *formatStrings[] = { cityResult.get(), stateResult.get() };
+ rv = aBundle->FormatStringFromName(u"cityAndStateNoZip", formatStrings, ArrayLength(formatStrings), getter_Copies(formattedString));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else if ((!cityResult.IsEmpty() && stateResult.IsEmpty() && !zipResult.IsEmpty()) ||
+ (cityResult.IsEmpty() && !stateResult.IsEmpty() && !zipResult.IsEmpty())) {
+ const char16_t *formatStrings[] = { cityResult.IsEmpty() ? stateResult.get() : cityResult.get(), zipResult.get() };
+ rv = aBundle->FormatStringFromName(u"cityOrStateAndZip", formatStrings, ArrayLength(formatStrings), getter_Copies(formattedString));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else {
+ if (!cityResult.IsEmpty())
+ formattedString = cityResult;
+ else if (!stateResult.IsEmpty())
+ formattedString = stateResult;
+ else
+ formattedString = zipResult;
+ }
+
+ aResult.Append(formattedString);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GenerateName(int32_t aGenerateFormat,
+ nsIStringBundle* aBundle,
+ nsAString &aResult)
+{
+ aResult.Truncate();
+
+ // Cache the first and last names
+ nsAutoString firstName, lastName;
+ GetFirstName(firstName);
+ GetLastName(lastName);
+
+ // No need to check for aBundle present straight away, only do that if we're
+ // actually going to use it.
+ if (aGenerateFormat == GENERATE_DISPLAY_NAME)
+ GetDisplayName(aResult);
+ else if (lastName.IsEmpty())
+ aResult = firstName;
+ else if (firstName.IsEmpty())
+ aResult = lastName;
+ else {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundle> bundle(aBundle);
+ if (!bundle) {
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED);
+
+ rv = stringBundleService->CreateBundle(sAddrbookProperties,
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsString result;
+
+ if (aGenerateFormat == GENERATE_LAST_FIRST_ORDER) {
+ const char16_t *stringParams[2] = {lastName.get(), firstName.get()};
+
+ rv = bundle->FormatStringFromName(u"lastFirstFormat",
+ stringParams, 2, getter_Copies(result));
+ }
+ else {
+ const char16_t *stringParams[2] = {firstName.get(), lastName.get()};
+
+ rv = bundle->FormatStringFromName(u"firstLastFormat",
+ stringParams, 2, getter_Copies(result));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.Assign(result);
+ }
+
+ if (aResult.IsEmpty())
+ {
+ // The normal names have failed, does this card have a company name? If so,
+ // use that instead, because that is likely to be more meaningful than an
+ // email address.
+ //
+ // If this errors, the string isn't found and we'll fall into the next
+ // check.
+ (void) GetPropertyAsAString(kCompanyProperty, aResult);
+ }
+
+ if (aResult.IsEmpty())
+ {
+ // see bug #211078
+ // if there is no generated name at this point
+ // use the userid from the email address
+ // it is better than nothing.
+ GetPrimaryEmail(aResult);
+ int32_t index = aResult.FindChar('@');
+ if (index != -1)
+ aResult.SetLength(index);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GeneratePhoneticName(bool aLastNameFirst,
+ nsAString &aResult)
+{
+ nsAutoString firstName, lastName;
+ GetPropertyAsAString(kPhoneticFirstNameProperty, firstName);
+ GetPropertyAsAString(kPhoneticLastNameProperty, lastName);
+
+ if (aLastNameFirst)
+ {
+ aResult = lastName;
+ aResult += firstName;
+ }
+ else
+ {
+ aResult = firstName;
+ aResult += lastName;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GenerateChatName(nsAString &aResult)
+{
+ aResult.Truncate();
+
+#define CHECK_CHAT_PROPERTY(aProtocol) \
+ if (NS_SUCCEEDED(GetPropertyAsAString(k##aProtocol##Property, aResult)) && \
+ !aResult.IsEmpty()) \
+ return NS_OK
+ CHECK_CHAT_PROPERTY(Gtalk);
+ CHECK_CHAT_PROPERTY(AIM);
+ CHECK_CHAT_PROPERTY(Yahoo);
+ CHECK_CHAT_PROPERTY(Skype);
+ CHECK_CHAT_PROPERTY(QQ);
+ CHECK_CHAT_PROPERTY(MSN);
+ CHECK_CHAT_PROPERTY(ICQ);
+ CHECK_CHAT_PROPERTY(XMPP);
+ CHECK_CHAT_PROPERTY(IRC);
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbCardProperty.h b/mailnews/addrbook/src/nsAbCardProperty.h
new file mode 100644
index 000000000..46ba50079
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbCardProperty.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/********************************************************************************************************
+
+ Interface for representing Address Book Person Card Property
+
+*********************************************************************************************************/
+
+#ifndef nsAbCardProperty_h__
+#define nsAbCardProperty_h__
+
+#include "nsIAbCard.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+
+#include "nsInterfaceHashtable.h"
+#include "nsIVariant.h"
+
+class nsIStringBundle;
+class mozITXTToHTMLConv;
+struct AppendItem;
+
+ /*
+ * Address Book Card Property
+ */
+
+class nsAbCardProperty: public nsIAbCard
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABCARD
+ NS_DECL_NSIABITEM
+
+ nsAbCardProperty();
+
+protected:
+ virtual ~nsAbCardProperty();
+ bool m_IsMailList;
+ nsCString m_MailListURI;
+
+ // Store most of the properties here
+ nsInterfaceHashtable<nsCStringHashKey, nsIVariant> m_properties;
+
+ nsCString m_directoryId, m_localId;
+private:
+ nsresult AppendSection(const AppendItem *aArray, int16_t aCount, const nsString& aHeading, nsIStringBundle *aBundle, mozITXTToHTMLConv *aConv, nsString &aResult);
+ nsresult AppendLine(const AppendItem &aItem, mozITXTToHTMLConv *aConv, nsString &aResult);
+ nsresult AppendLabel(const AppendItem &aItem, nsIStringBundle *aBundle, mozITXTToHTMLConv *aConv, nsString &aResult);
+ nsresult AppendCityStateZip(const AppendItem &aItem, nsIStringBundle *aBundle, mozITXTToHTMLConv *aConv, nsString &aResult);
+
+ nsresult ConvertToBase64EncodedXML(nsACString &result);
+ nsresult ConvertToXMLPrintData(nsAString &result);
+ nsresult ConvertToEscapedVCard(nsACString &result);
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbContentHandler.cpp b/mailnews/addrbook/src/nsAbContentHandler.cpp
new file mode 100644
index 000000000..3ee00148c
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbContentHandler.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbContentHandler.h"
+#include "nsAbBaseCID.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsNullPrincipal.h"
+#include "nsISupportsPrimitives.h"
+#include "plstr.h"
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgVCardService.h"
+#include "nsIAbCard.h"
+#include "nsIAbManager.h"
+#include "nsVCard.h"
+#include "nsIChannel.h"
+//
+// nsAbContentHandler
+//
+nsAbContentHandler::nsAbContentHandler()
+{
+}
+
+nsAbContentHandler::~nsAbContentHandler()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsAbContentHandler, nsIContentHandler,
+ nsIStreamLoaderObserver)
+
+NS_IMETHODIMP
+nsAbContentHandler::HandleContent(const char *aContentType,
+ nsIInterfaceRequestor *aWindowContext,
+ nsIRequest *request)
+{
+ NS_ENSURE_ARG_POINTER(request);
+
+ nsresult rv = NS_OK;
+
+ // First of all, get the content type and make sure it is a content type we know how to handle!
+ if (PL_strcasecmp(aContentType, "application/x-addvcard") == 0) {
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (!aChannel) return NS_ERROR_FAILURE;
+
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (uri)
+ {
+ nsAutoCString path;
+ rv = uri->GetPath(path);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ const char *startOfVCard = strstr(path.get(), "add?vcard=");
+ if (startOfVCard)
+ {
+ nsCString unescapedData;
+
+ // XXX todo, explain why we is escaped twice
+ MsgUnescapeString(nsDependentCString(startOfVCard + strlen("add?vcard=")),
+ 0, unescapedData);
+
+ if (!aWindowContext)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow = do_GetInterface(aWindowContext);
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow = nsPIDOMWindowOuter::From(domWindow);
+ parentWindow = parentWindow->GetOuterWindow();
+ NS_ENSURE_ARG_POINTER(parentWindow);
+
+ nsCOMPtr<nsIAbManager> ab =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIAbCard> cardFromVCard;
+ rv = ab->EscapedVCardToAbCard(unescapedData.get(),
+ getter_AddRefs(cardFromVCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ifptr->SetData(cardFromVCard);
+ ifptr->SetDataIID(&NS_GET_IID(nsIAbCard));
+
+ nsCOMPtr<nsPIDOMWindowOuter> dialogWindow;
+
+ rv = parentWindow->OpenDialog(
+ NS_LITERAL_STRING("chrome://messenger/content/addressbook/abNewCardDialog.xul"),
+ EmptyString(),
+ NS_LITERAL_STRING("chrome,resizable=no,titlebar,modal,centerscreen"),
+ ifptr, getter_AddRefs(dialogWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = NS_OK;
+ }
+ }
+ else if (PL_strcasecmp(aContentType, "text/x-vcard") == 0) {
+ // create a vcard stream listener that can parse the data stream
+ // and bring up the appropriate UI
+
+ // (1) cancel the current load operation. We'll restart it
+ request->Cancel(NS_ERROR_ABORT);
+ // get the url we were trying to open
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
+
+ rv = channel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create a stream loader to handle the v-card data
+ nsCOMPtr<nsIStreamLoader> streamLoader;
+ rv = NS_NewStreamLoader(getter_AddRefs(streamLoader),
+ uri,
+ this,
+ nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ }
+ else // The content-type was not application/x-addvcard...
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAbContentHandler::OnStreamComplete(nsIStreamLoader *aLoader,
+ nsISupports *aContext, nsresult aStatus,
+ uint32_t datalen, const uint8_t *data)
+{
+ NS_ENSURE_ARG_POINTER(aContext);
+ NS_ENSURE_SUCCESS(aStatus, aStatus); // don't process the vcard if we got a status error
+ nsresult rv = NS_OK;
+
+ // take our vCard string and open up an address book window based on it
+ nsCOMPtr<nsIMsgVCardService> vCardService = do_GetService(NS_MSGVCARDSERVICE_CONTRACTID);
+ if (vCardService)
+ {
+ nsAutoPtr<VObject> vObj(vCardService->Parse_MIME((const char *)data, datalen));
+ if (vObj)
+ {
+ int32_t len = 0;
+ nsCString vCard;
+ vCard.Adopt(vCardService->WriteMemoryVObjects(0, &len, vObj, false));
+
+ nsCOMPtr<nsIAbManager> ab =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIAbCard> cardFromVCard;
+ rv = ab->EscapedVCardToAbCard(vCard.get(),
+ getter_AddRefs(cardFromVCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow = do_GetInterface(aContext);
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow = nsPIDOMWindowOuter::From(domWindow);
+ parentWindow = parentWindow->GetOuterWindow();
+ NS_ENSURE_ARG_POINTER(parentWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> dialogWindow;
+ rv = parentWindow->OpenDialog(
+ NS_LITERAL_STRING("chrome://messenger/content/addressbook/abNewCardDialog.xul"),
+ EmptyString(),
+ NS_LITERAL_STRING("chrome,resizable=no,titlebar,modal,centerscreen"),
+ cardFromVCard, getter_AddRefs(dialogWindow));
+ }
+ }
+
+ return rv;
+}
diff --git a/mailnews/addrbook/src/nsAbContentHandler.h b/mailnews/addrbook/src/nsAbContentHandler.h
new file mode 100644
index 000000000..e5405ae0e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbContentHandler.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsAbContentHandler_h
+#define __nsAbContentHandler_h
+
+#include "nsIStreamLoader.h"
+#include "nsIContentHandler.h"
+
+class nsAbContentHandler : public nsIContentHandler,
+ public nsIStreamLoaderObserver
+{
+public:
+ nsAbContentHandler();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICONTENTHANDLER
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+private:
+ virtual ~nsAbContentHandler();
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbDirFactoryService.cpp b/mailnews/addrbook/src/nsAbDirFactoryService.cpp
new file mode 100644
index 000000000..4ddf8e635
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirFactoryService.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsMemory.h"
+#include "nsStringGlue.h"
+#include "plstr.h"
+
+#include "nsAbBaseCID.h"
+#include "nsAbDirFactoryService.h"
+#include "nsIAbDirFactory.h"
+#include "mozilla/Services.h"
+
+NS_IMPL_ISUPPORTS(nsAbDirFactoryService, nsIAbDirFactoryService)
+
+nsAbDirFactoryService::nsAbDirFactoryService()
+{
+}
+
+nsAbDirFactoryService::~nsAbDirFactoryService()
+{
+}
+
+/* nsIAbDirFactory getDirFactory (in string uri); */
+NS_IMETHODIMP
+nsAbDirFactoryService::GetDirFactory(const nsACString &aURI,
+ nsIAbDirFactory** aDirFactory)
+{
+ NS_ENSURE_ARG_POINTER(aDirFactory);
+
+ nsresult rv;
+
+ // Obtain the network IO service
+ nsCOMPtr<nsIIOService> nsService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(nsService, NS_ERROR_UNEXPECTED);
+
+ // Extract the scheme
+ nsAutoCString scheme;
+ rv = nsService->ExtractScheme(aURI, scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Try to find a factory using the component manager.
+ nsAutoCString contractID;
+ contractID.AssignLiteral(NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX);
+ contractID.Append(scheme);
+
+ return CallCreateInstance(contractID.get(), aDirFactory);
+}
diff --git a/mailnews/addrbook/src/nsAbDirFactoryService.h b/mailnews/addrbook/src/nsAbDirFactoryService.h
new file mode 100644
index 000000000..77396fbaa
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirFactoryService.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbDirFactoryService_h__
+#define nsAbDirFactoryService_h__
+
+#include "nsIAbDirFactoryService.h"
+
+class nsAbDirFactoryService : public nsIAbDirFactoryService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRFACTORYSERVICE
+
+ nsAbDirFactoryService();
+
+private:
+ virtual ~nsAbDirFactoryService();
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbDirProperty.cpp b/mailnews/addrbook/src/nsAbDirProperty.cpp
new file mode 100644
index 000000000..fbae06220
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirProperty.cpp
@@ -0,0 +1,593 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbDirProperty.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbCard.h"
+#include "nsDirPrefs.h"
+#include "nsIPrefService.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "prmem.h"
+#include "nsIAbManager.h"
+#include "nsArrayUtils.h"
+
+// From nsDirPrefs
+#define kDefaultPosition 1
+
+nsAbDirProperty::nsAbDirProperty(void)
+ : m_LastModifiedDate(0),
+ mIsValidURI(false),
+ mIsQueryURI(false)
+{
+ m_IsMailList = false;
+}
+
+nsAbDirProperty::~nsAbDirProperty(void)
+{
+#if 0
+ // this code causes a regression #138647
+ // don't turn it on until you figure it out
+ if (m_AddressList) {
+ uint32_t count;
+ nsresult rv;
+ rv = m_AddressList->GetLength(&count);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Count failed");
+ int32_t i;
+ for (i = count - 1; i >= 0; i--)
+ m_AddressList->RemoveElementAt(i);
+ }
+#endif
+}
+
+NS_IMPL_ISUPPORTS(nsAbDirProperty, nsIAbDirectory, nsISupportsWeakReference,
+ nsIAbCollection, nsIAbItem)
+
+NS_IMETHODIMP nsAbDirProperty::GetUuid(nsACString &uuid)
+{
+ // XXX: not all directories have a dirPrefId...
+ nsresult rv = GetDirPrefId(uuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uuid.Append('&');
+ nsString dirName;
+ GetDirName(dirName);
+ uuid.Append(NS_ConvertUTF16toUTF8(dirName));
+ return rv;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GenerateName(int32_t aGenerateFormat,
+ nsIStringBundle *aBundle,
+ nsAString &name)
+{
+ return GetDirName(name);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetPropertiesChromeURI(nsACString &aResult)
+{
+ aResult.AssignLiteral("chrome://messenger/content/addressbook/abAddressBookNameDialog.xul");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDirName(nsAString &aDirName)
+{
+ if (m_DirPrefId.IsEmpty())
+ {
+ aDirName = m_ListDirName;
+ return NS_OK;
+ }
+
+ nsCString dirName;
+ nsresult rv = GetLocalizedStringValue("description", EmptyCString(), dirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // In TB 2 only some prefs had chrome:// URIs. We had code in place that would
+ // only get the localized string pref for the particular address books that
+ // were built-in.
+ // Additionally, nsIPrefBranch::getComplexValue will only get a non-user-set,
+ // non-locked pref value if it is a chrome:// URI and will get the string
+ // value at that chrome URI. This breaks extensions/autoconfig that want to
+ // set default pref values and allow users to change directory names.
+ //
+ // Now we have to support this, and so if for whatever reason we fail to get
+ // the localized version, then we try and get the non-localized version
+ // instead. If the string value is empty, then we'll just get the empty value
+ // back here.
+ if (dirName.IsEmpty())
+ {
+ rv = GetStringValue("description", EmptyCString(), dirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ CopyUTF8toUTF16(dirName, aDirName);
+ return NS_OK;
+}
+
+// XXX Although mailing lists could use the NotifyItemPropertyChanged
+// mechanism here, it requires some rework on how we write/save data
+// relating to mailing lists, so we're just using the old method of a
+// local variable to store the mailing list name.
+NS_IMETHODIMP nsAbDirProperty::SetDirName(const nsAString &aDirName)
+{
+ if (m_DirPrefId.IsEmpty())
+ {
+ m_ListDirName = aDirName;
+ return NS_OK;
+ }
+
+ // Store the old value.
+ nsString oldDirName;
+ nsresult rv = GetDirName(oldDirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Save the new value
+ rv = SetLocalizedStringValue("description", NS_ConvertUTF16toUTF8(aDirName));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv))
+ // We inherit from nsIAbDirectory, so this static cast should be safe.
+ abManager->NotifyItemPropertyChanged(static_cast<nsIAbDirectory*>(this),
+ "DirName", oldDirName.get(),
+ nsString(aDirName).get());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDirType(int32_t *aDirType)
+{
+ return GetIntValue("dirType", LDAPDirectory, aDirType);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetFileName(nsACString &aFileName)
+{
+ return GetStringValue("filename", EmptyCString(), aFileName);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetURI(nsACString &aURI)
+{
+ // XXX Should we complete this for Mailing Lists?
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetPosition(int32_t *aPosition)
+{
+ return GetIntValue("position", kDefaultPosition, aPosition);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetLastModifiedDate(uint32_t *aLastModifiedDate)
+{
+ NS_ENSURE_ARG_POINTER(aLastModifiedDate);
+ *aLastModifiedDate = m_LastModifiedDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetLastModifiedDate(uint32_t aLastModifiedDate)
+{
+ if (aLastModifiedDate)
+ {
+ m_LastModifiedDate = aLastModifiedDate;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetListNickName(nsAString &aListNickName)
+{
+ aListNickName = m_ListNickName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetListNickName(const nsAString &aListNickName)
+{
+ m_ListNickName = aListNickName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDescription(nsAString &aDescription)
+{
+ aDescription = m_Description;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetDescription(const nsAString &aDescription)
+{
+ m_Description = aDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsMailList(bool *aIsMailList)
+{
+ *aIsMailList = m_IsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetIsMailList(bool aIsMailList)
+{
+ m_IsMailList = aIsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetAddressLists(nsIMutableArray * *aAddressLists)
+{
+ if (!m_AddressList)
+ {
+ nsresult rv;
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aAddressLists = m_AddressList;
+ NS_ADDREF(*aAddressLists);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetAddressLists(nsIMutableArray * aAddressLists)
+{
+ m_AddressList = aAddressLists;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::CopyMailList(nsIAbDirectory* srcList)
+{
+ SetIsMailList(true);
+
+ nsString str;
+ srcList->GetDirName(str);
+ SetDirName(str);
+ srcList->GetListNickName(str);
+ SetListNickName(str);
+ srcList->GetDescription(str);
+ SetDescription(str);
+
+ nsCOMPtr<nsIMutableArray> pAddressLists;
+ srcList->GetAddressLists(getter_AddRefs(pAddressLists));
+ SetAddressLists(pAddressLists);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsQuery(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // Mailing lists are not queries by default, individual directory types
+ // will override this.
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::Init(const char *aURI)
+{
+ mURINoQuery = aURI;
+ mURI = aURI;
+ mIsValidURI = true;
+
+ int32_t searchCharLocation = mURINoQuery.FindChar('?');
+ if (searchCharLocation >= 0)
+ {
+ mQueryString = Substring(mURINoQuery, searchCharLocation + 1);
+ mURINoQuery.SetLength(searchCharLocation);
+ mIsQueryURI = true;
+ }
+
+ return NS_OK;
+}
+
+// nsIAbDirectory NOT IMPLEMENTED methods
+NS_IMETHODIMP
+nsAbDirProperty::GetChildNodes(nsISimpleEnumerator **childList)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::GetChildCards(nsISimpleEnumerator **childCards)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::DeleteDirectory(nsIAbDirectory *directory)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::HasCard(nsIAbCard *cards, bool *hasCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::HasDirectory(nsIAbDirectory *dir, bool *hasDir)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::HasMailListWithName(const char16_t *aName, bool *aHasList)
+{
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ENSURE_ARG_POINTER(aHasList);
+
+ *aHasList = false;
+
+ bool supportsLists = false;
+ nsresult rv = GetSupportsMailingLists(&supportsLists);
+ if (NS_FAILED(rv) || !supportsLists)
+ return NS_OK;
+
+ if (m_IsMailList)
+ return NS_OK;
+
+ nsCOMPtr<nsIMutableArray> addressLists;
+ rv = GetAddressLists(getter_AddRefs(addressLists));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t listCount = 0;
+ rv = addressLists->GetLength(&listCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < listCount; i++)
+ {
+ nsCOMPtr<nsIAbDirectory> listDir(do_QueryElementAt(addressLists, i, &rv));
+ if (NS_SUCCEEDED(rv) && listDir)
+ {
+ nsAutoString listName;
+ rv = listDir->GetDirName(listName);
+ if (NS_SUCCEEDED(rv) && listName.Equals(aName))
+ {
+ *aHasList = true;
+ return NS_OK;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::CreateNewDirectory(const nsAString &aDirName,
+ const nsACString &aURI,
+ uint32_t aType,
+ const nsACString &aPrefName,
+ nsACString &aResult)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsAbDirProperty::CreateDirectoryByURI(const nsAString &aDisplayName,
+ const nsACString &aURI)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::AddMailList(nsIAbDirectory *list, nsIAbDirectory **addedList)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::EditMailListToDatabase(nsIAbCard *listCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::AddCard(nsIAbCard *childCard, nsIAbCard **addedCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::ModifyCard(nsIAbCard *aModifiedCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::DeleteCards(nsIArray *cards)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::DropCard(nsIAbCard *childCard, bool needToCopyCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::CardForEmailAddress(const nsACString &aEmailAddress,
+ nsIAbCard ** aAbCard)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::GetCardFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsIAbCard **result)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsAbDirProperty::GetCardsFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsISimpleEnumerator **result)
+{ return NS_ERROR_NOT_IMPLEMENTED; }
+
+
+NS_IMETHODIMP nsAbDirProperty::GetSupportsMailingLists(bool *aSupportsMailingsLists)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsMailingsLists);
+ // We don't currently support nested mailing lists, so only return true if
+ // we're not a mailing list.
+ *aSupportsMailingsLists = !m_IsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetReadOnly(bool *aReadOnly)
+{
+ NS_ENSURE_ARG_POINTER(aReadOnly);
+ // Default is that we are writable. Any implementation that is read-only must
+ // override this method.
+ *aReadOnly = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsRemote(bool *aIsRemote)
+{
+ NS_ENSURE_ARG_POINTER(aIsRemote);
+ *aIsRemote = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsSecure(bool *aIsSecure)
+{
+ NS_ENSURE_ARG_POINTER(aIsSecure);
+ *aIsSecure = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::UseForAutocomplete(const nsACString &aIdentityKey,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // Is local autocomplete enabled?
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return prefBranch->GetBoolPref("mail.enable_autocomplete", aResult);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDirPrefId(nsACString &aDirPrefId)
+{
+ aDirPrefId = m_DirPrefId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetDirPrefId(const nsACString &aDirPrefId)
+{
+ if (!m_DirPrefId.Equals(aDirPrefId))
+ {
+ m_DirPrefId.Assign(aDirPrefId);
+ // Clear the directory pref branch so that it is re-initialized next
+ // time its required.
+ m_DirectoryPrefs = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult nsAbDirProperty::InitDirectoryPrefs()
+{
+ if (m_DirPrefId.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString realPrefId(m_DirPrefId);
+ realPrefId.Append('.');
+
+ return prefService->GetBranch(realPrefId.get(), getter_AddRefs(m_DirectoryPrefs));
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIntValue(const char *aName,
+ int32_t aDefaultValue,
+ int32_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (NS_FAILED(m_DirectoryPrefs->GetIntPref(aName, aResult)))
+ *aResult = aDefaultValue;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetBoolValue(const char *aName,
+ bool aDefaultValue,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (NS_FAILED(m_DirectoryPrefs->GetBoolPref(aName, aResult)))
+ *aResult = aDefaultValue;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetStringValue(const char *aName,
+ const nsACString &aDefaultValue,
+ nsACString &aResult)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString value;
+
+ /* unfortunately, there may be some prefs out there which look like (null) */
+ if (NS_SUCCEEDED(m_DirectoryPrefs->GetCharPref(aName, getter_Copies(value))) &&
+ !value.EqualsLiteral("(null"))
+ aResult = value;
+ else
+ aResult = aDefaultValue;
+
+ return NS_OK;
+}
+/*
+ * Get localized unicode string pref from properties file, convert into an
+ * UTF8 string since address book prefs store as UTF8 strings. So far there
+ * are 2 default prefs stored in addressbook.properties.
+ * "ldap_2.servers.pab.description"
+ * "ldap_2.servers.history.description"
+ */
+NS_IMETHODIMP nsAbDirProperty::GetLocalizedStringValue(const char *aName,
+ const nsACString &aDefaultValue,
+ nsACString &aResult)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsString wvalue;
+ nsCOMPtr<nsIPrefLocalizedString> locStr;
+
+ nsresult rv = m_DirectoryPrefs->GetComplexValue(aName,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(locStr));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = locStr->ToString(getter_Copies(wvalue));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (wvalue.IsEmpty())
+ aResult = aDefaultValue;
+ else
+ CopyUTF16toUTF8(wvalue, aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetIntValue(const char *aName,
+ int32_t aValue)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return m_DirectoryPrefs->SetIntPref(aName, aValue);
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetBoolValue(const char *aName,
+ bool aValue)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return m_DirectoryPrefs->SetBoolPref(aName, aValue);
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetStringValue(const char *aName,
+ const nsACString &aValue)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return m_DirectoryPrefs->SetCharPref(aName, nsCString(aValue).get());
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetLocalizedStringValue(const char *aName,
+ const nsACString &aValue)
+{
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefLocalizedString> locStr(
+ do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = locStr->SetData(NS_ConvertUTF8toUTF16(aValue).get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return m_DirectoryPrefs->SetComplexValue(aName,
+ NS_GET_IID(nsIPrefLocalizedString),
+ locStr);
+}
diff --git a/mailnews/addrbook/src/nsAbDirProperty.h b/mailnews/addrbook/src/nsAbDirProperty.h
new file mode 100644
index 000000000..5e15a15fc
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirProperty.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/********************************************************************************************************
+
+ Interface for representing Address Book Directory
+
+*********************************************************************************************************/
+
+#ifndef nsAbDirProperty_h__
+#define nsAbDirProperty_h__
+
+#include "nsIAbDirectory.h" /* include the interface we are going to support */
+#include "nsCOMPtr.h"
+#include "nsDirPrefs.h"
+#include "nsStringGlue.h"
+#include "nsIPrefBranch.h"
+#include "nsIMutableArray.h"
+#include "nsWeakReference.h"
+
+ /*
+ * Address Book Directory
+ */
+
+class nsAbDirProperty: public nsIAbDirectory,
+ public nsSupportsWeakReference
+{
+public:
+ nsAbDirProperty(void);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABITEM
+ NS_DECL_NSIABCOLLECTION
+ NS_DECL_NSIABDIRECTORY
+
+protected:
+ virtual ~nsAbDirProperty(void);
+
+ /**
+ * Initialise the directory prefs for this branch
+ */
+ nsresult InitDirectoryPrefs();
+
+ uint32_t m_LastModifiedDate;
+
+ nsString m_ListDirName;
+ nsString m_ListName;
+ nsString m_ListNickName;
+ nsString m_Description;
+ bool m_IsMailList;
+
+ nsCString mURI;
+ nsCString mQueryString;
+ nsCString mURINoQuery;
+ bool mIsValidURI;
+ bool mIsQueryURI;
+
+
+ /*
+ * Note that any derived implementations should ensure that this item
+ * (m_DirPrefId) is correctly initialised correctly
+ */
+ nsCString m_DirPrefId; // ie,"ldap_2.servers.pab"
+
+ nsCOMPtr<nsIPrefBranch> m_DirectoryPrefs;
+ nsCOMPtr<nsIMutableArray> m_AddressList;
+};
+#endif
diff --git a/mailnews/addrbook/src/nsAbDirectoryQuery.cpp b/mailnews/addrbook/src/nsAbDirectoryQuery.cpp
new file mode 100644
index 000000000..9c9826ac8
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirectoryQuery.cpp
@@ -0,0 +1,528 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbDirectoryQuery.h"
+#include "nsAbDirectoryQueryProxy.h"
+#include "nsAbUtils.h"
+#include "nsAbBooleanExpression.h"
+#include "nsArrayUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsStringGlue.h"
+#include "nsUnicharUtils.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsISimpleEnumerator.h"
+#include "nsMsgUtils.h"
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQuerySimpleBooleanExpression, nsIAbBooleanExpression)
+
+nsAbDirectoryQuerySimpleBooleanExpression::nsAbDirectoryQuerySimpleBooleanExpression() :
+ mOperation (nsIAbBooleanOperationTypes::AND)
+{
+}
+
+nsAbDirectoryQuerySimpleBooleanExpression::~nsAbDirectoryQuerySimpleBooleanExpression()
+{
+}
+
+/* attribute nsAbBooleanOperationType operation; */
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::GetOperation(nsAbBooleanOperationType *aOperation)
+{
+ if (!aOperation)
+ return NS_ERROR_NULL_POINTER;
+
+ *aOperation = mOperation;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::SetOperation(nsAbBooleanOperationType aOperation)
+{
+ if (aOperation != nsIAbBooleanOperationTypes::AND &&
+ aOperation != nsIAbBooleanOperationTypes::OR)
+ return NS_ERROR_FAILURE;
+
+ mOperation = aOperation;
+
+ return NS_OK;
+}
+
+/* attribute nsIArray expressions; */
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::GetExpressions(nsIArray **aExpressions)
+{
+ if (!aExpressions)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!mExpressions)
+ {
+ mExpressions = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ if (!mExpressions)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_ADDREF(*aExpressions = mExpressions);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::SetExpressions(nsIArray *aExpressions)
+{
+ if (!aExpressions)
+ return NS_ERROR_NULL_POINTER;
+
+ // Ensure all the items are of the right type.
+ nsresult rv;
+ uint32_t count;
+ rv = aExpressions->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanConditionString> queryExpression;
+
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ queryExpression = do_QueryElementAt(aExpressions, i, &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Values ok, so we can just save and return.
+ mExpressions = aExpressions;
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQueryArguments, nsIAbDirectoryQueryArguments)
+
+nsAbDirectoryQueryArguments::nsAbDirectoryQueryArguments() :
+ mQuerySubDirectories(true)
+{
+}
+
+nsAbDirectoryQueryArguments::~nsAbDirectoryQueryArguments()
+{
+}
+
+/* attribute nsISupports matchItems; */
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetExpression(nsISupports** aExpression)
+{
+ if (!aExpression)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_IF_ADDREF(*aExpression = mExpression);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetExpression(nsISupports* aExpression)
+{
+ mExpression = aExpression;
+ return NS_OK;
+}
+
+/* attribute boolean querySubDirectories; */
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetQuerySubDirectories(bool* aQuerySubDirectories)
+{
+ NS_ENSURE_ARG_POINTER(aQuerySubDirectories);
+ *aQuerySubDirectories = mQuerySubDirectories;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetQuerySubDirectories(bool aQuerySubDirectories)
+{
+ mQuerySubDirectories = aQuerySubDirectories;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetTypeSpecificArg(nsISupports** aArg)
+{
+ NS_ENSURE_ARG_POINTER(aArg);
+
+ NS_IF_ADDREF(*aArg = mTypeSpecificArg);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetTypeSpecificArg(nsISupports* aArg)
+{
+ mTypeSpecificArg = aArg;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetFilter(nsACString & aFilter)
+{
+ aFilter.Assign(mFilter);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetFilter(const nsACString & aFilter)
+{
+ mFilter.Assign(aFilter);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQueryPropertyValue, nsIAbDirectoryQueryPropertyValue)
+
+nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue()
+{
+}
+
+nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue(const char* aName,
+ const char16_t* aValue)
+{
+ mName = aName;
+ mValue = aValue;
+}
+
+nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue(const char* aName,
+ nsISupports* aValueISupports)
+{
+ mName = aName;
+ mValueISupports = aValueISupports;
+}
+
+nsAbDirectoryQueryPropertyValue::~nsAbDirectoryQueryPropertyValue()
+{
+}
+
+/* read only attribute string name; */
+NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetName(char* *aName)
+{
+ *aName = mName.IsEmpty() ? 0 : ToNewCString(mName);
+
+ return NS_OK;
+}
+
+/* read only attribute wstring value; */
+NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetValue(char16_t* *aValue)
+{
+ *aValue = ToNewUnicode(mValue);
+ if (!(*aValue))
+ return NS_ERROR_OUT_OF_MEMORY;
+ else
+ return NS_OK;
+}
+
+/* readonly attribute nsISupports valueISupports; */
+NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetValueISupports(nsISupports* *aValueISupports)
+{
+ if (!mValueISupports)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_IF_ADDREF(*aValueISupports = mValueISupports);
+ return NS_OK;
+}
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsAbDirectoryQuery, nsIAbDirectoryQuery)
+
+nsAbDirectoryQuery::nsAbDirectoryQuery()
+{
+}
+
+nsAbDirectoryQuery::~nsAbDirectoryQuery()
+{
+}
+
+NS_IMETHODIMP nsAbDirectoryQuery::DoQuery(nsIAbDirectory *aDirectory,
+ nsIAbDirectoryQueryArguments* arguments,
+ nsIAbDirSearchListener* listener,
+ int32_t resultLimit, int32_t timeOut,
+ int32_t* _retval)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ nsCOMPtr<nsISupports> supportsExpression;
+ nsresult rv = arguments->GetExpression(getter_AddRefs(supportsExpression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> expression(do_QueryInterface(supportsExpression, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool doSubDirectories;
+ rv = arguments->GetQuerySubDirectories(&doSubDirectories);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = query(aDirectory, expression, listener, doSubDirectories, &resultLimit);
+
+ rv = NS_FAILED(rv) ? queryError(listener) : queryFinished(listener);
+
+ *_retval = 0;
+ return rv;
+}
+
+/* void stopQuery (in long contextID); */
+NS_IMETHODIMP nsAbDirectoryQuery::StopQuery(int32_t contextID)
+{
+ return NS_OK;
+}
+
+
+nsresult nsAbDirectoryQuery::query(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories,
+ int32_t* resultLimit)
+{
+ if (*resultLimit == 0)
+ return NS_OK;
+
+ nsresult rv = queryCards(directory, expression, listener, resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (doSubDirectories && resultLimit != 0)
+ {
+ rv = queryChildren(directory, expression, listener, doSubDirectories,
+ resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+nsresult nsAbDirectoryQuery::queryChildren(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories,
+ int32_t* resultLimit)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsISimpleEnumerator> subDirectories;
+ rv = directory->GetChildNodes(getter_AddRefs(subDirectories));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(rv = subDirectories->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = subDirectories->GetNext (getter_AddRefs (item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> subDirectory(do_QueryInterface(item, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = query(subDirectory, expression, listener, doSubDirectories, resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ }
+ return NS_OK;
+}
+
+nsresult nsAbDirectoryQuery::queryCards(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ int32_t* resultLimit)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsISimpleEnumerator> cards;
+ rv = directory->GetChildCards(getter_AddRefs(cards));
+ if (NS_FAILED(rv))
+ {
+ if (rv != NS_ERROR_NOT_IMPLEMENTED)
+ NS_ENSURE_SUCCESS(rv, rv);
+ else
+ return NS_OK;
+ }
+
+ if (!cards)
+ return NS_OK;
+
+ bool more;
+ while (NS_SUCCEEDED(cards->HasMoreElements(&more)) && more)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = cards->GetNext(getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card(do_QueryInterface(item, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = matchCard (card, expression, listener, resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*resultLimit == 0)
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAbDirectoryQuery::matchCard(nsIAbCard* card,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ int32_t* resultLimit)
+{
+ bool matchFound = false;
+ nsresult rv = matchCardExpression(card, expression, &matchFound);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matchFound)
+ {
+ (*resultLimit)--;
+ rv = queryMatch(card, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+nsresult nsAbDirectoryQuery::matchCardExpression(nsIAbCard* card,
+ nsIAbBooleanExpression* expression,
+ bool* result)
+{
+ nsAbBooleanOperationType operation;
+ nsresult rv = expression->GetOperation (&operation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIArray> childExpressions;
+ rv = expression->GetExpressions (getter_AddRefs (childExpressions));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ rv = childExpressions->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (operation == nsIAbBooleanOperationTypes::NOT &&
+ count > 1)
+ return NS_ERROR_FAILURE;
+
+ bool value = *result = false;
+ nsCOMPtr<nsIAbBooleanConditionString> childCondition;
+ nsCOMPtr<nsIAbBooleanExpression> childExpression;
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ childCondition = do_QueryElementAt(childExpressions, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = matchCardCondition (card, childCondition, &value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ childExpression = do_QueryElementAt(childExpressions, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = matchCardExpression (card, childExpression, &value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ return NS_ERROR_FAILURE;
+ }
+ if (operation == nsIAbBooleanOperationTypes::OR && value)
+ break;
+ else if (operation == nsIAbBooleanOperationTypes::AND && !value)
+ break;
+ else if (operation == nsIAbBooleanOperationTypes::NOT)
+ value = !value;
+ }
+ *result = value;
+
+ return NS_OK;
+}
+
+nsresult nsAbDirectoryQuery::matchCardCondition(nsIAbCard* card,
+ nsIAbBooleanConditionString* condition,
+ bool* matchFound)
+{
+ nsAbBooleanConditionType conditionType;
+ nsresult rv = condition->GetCondition (&conditionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString name;
+ rv = condition->GetName (getter_Copies (name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (name.Equals ("card:nsIAbCard"))
+ {
+ *matchFound = (conditionType == nsIAbBooleanConditionTypes::Exists);
+ return NS_OK;
+ }
+
+ nsString matchValue;
+ rv = condition->GetValue (getter_Copies (matchValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (name.EqualsLiteral("IsMailList"))
+ {
+ bool isMailList;
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only equals is supported.
+ if (conditionType != nsIAbBooleanConditionTypes::Is)
+ return NS_ERROR_FAILURE;
+
+ *matchFound = isMailList ? matchValue.EqualsLiteral("TRUE") :
+ matchValue.EqualsLiteral("FALSE");
+ return NS_OK;
+ }
+
+ nsString value;
+ (void)card->GetPropertyAsAString(name.get(), value);
+
+ if (value.IsEmpty())
+ {
+ *matchFound = (conditionType == nsIAbBooleanConditionTypes::DoesNotExist) ?
+ true : false;
+ return NS_OK;
+ }
+
+ /* TODO
+ * What about allowing choice between case insensitive
+ * and case sensitive comparisons?
+ *
+ */
+ switch (conditionType)
+ {
+ case nsIAbBooleanConditionTypes::Exists:
+ *matchFound = true;
+ break;
+ case nsIAbBooleanConditionTypes::Contains:
+ *matchFound = CaseInsensitiveFindInReadable(matchValue, value);
+ break;
+ case nsIAbBooleanConditionTypes::DoesNotContain:
+ *matchFound = !CaseInsensitiveFindInReadable(matchValue, value);
+ break;
+ case nsIAbBooleanConditionTypes::Is:
+ *matchFound = value.Equals(matchValue, nsCaseInsensitiveStringComparator());
+ break;
+ case nsIAbBooleanConditionTypes::IsNot:
+ *matchFound = !value.Equals(matchValue, nsCaseInsensitiveStringComparator());
+ break;
+ case nsIAbBooleanConditionTypes::BeginsWith:
+ *matchFound = StringBeginsWith(value, matchValue, nsCaseInsensitiveStringComparator());
+ break;
+ case nsIAbBooleanConditionTypes::LessThan:
+ *matchFound = Compare(value, matchValue, nsCaseInsensitiveStringComparator()) < 0;
+ break;
+ case nsIAbBooleanConditionTypes::GreaterThan:
+ *matchFound = Compare(value, matchValue, nsCaseInsensitiveStringComparator()) > 0;
+ break;
+ case nsIAbBooleanConditionTypes::EndsWith:
+ *matchFound = StringEndsWith(value, matchValue, nsCaseInsensitiveStringComparator());
+ break;
+ case nsIAbBooleanConditionTypes::SoundsLike:
+ case nsIAbBooleanConditionTypes::RegExp:
+ *matchFound = false;
+ break;
+ default:
+ *matchFound = false;
+ }
+
+ return rv;
+}
+
+nsresult nsAbDirectoryQuery::queryMatch(nsIAbCard* card,
+ nsIAbDirSearchListener* listener)
+{
+ return listener->OnSearchFoundCard(card);
+}
+
+nsresult nsAbDirectoryQuery::queryFinished(nsIAbDirSearchListener* listener)
+{
+ return listener->OnSearchFinished(nsIAbDirectoryQueryResultListener::queryResultComplete, EmptyString());
+}
+
+nsresult nsAbDirectoryQuery::queryError(nsIAbDirSearchListener* listener)
+{
+ return listener->OnSearchFinished(nsIAbDirectoryQueryResultListener::queryResultError, EmptyString());
+}
diff --git a/mailnews/addrbook/src/nsAbDirectoryQuery.h b/mailnews/addrbook/src/nsAbDirectoryQuery.h
new file mode 100644
index 000000000..99aa943aa
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirectoryQuery.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbDirectoryQuery_h__
+#define nsAbDirectoryQuery_h__
+
+#include "nsIAbDirectoryQuery.h"
+#include "nsIAbDirectory.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIArray.h"
+#include "nsIAbBooleanExpression.h"
+
+class nsAbDirectoryQuerySimpleBooleanExpression : public nsIAbBooleanExpression
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABBOOLEANEXPRESSION
+
+ nsAbDirectoryQuerySimpleBooleanExpression();
+
+private:
+ virtual ~nsAbDirectoryQuerySimpleBooleanExpression();
+
+public:
+ nsCOMPtr<nsIArray> mExpressions;
+ nsAbBooleanOperationType mOperation;
+};
+
+
+class nsAbDirectoryQueryArguments : public nsIAbDirectoryQueryArguments
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERYARGUMENTS
+
+ nsAbDirectoryQueryArguments();
+
+private:
+ virtual ~nsAbDirectoryQueryArguments();
+
+protected:
+ nsCOMPtr<nsISupports> mExpression;
+ nsCOMPtr<nsISupports> mTypeSpecificArg;
+ bool mQuerySubDirectories;
+ nsCString mFilter;
+};
+
+
+class nsAbDirectoryQueryPropertyValue : public nsIAbDirectoryQueryPropertyValue
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERYPROPERTYVALUE
+
+ nsAbDirectoryQueryPropertyValue();
+ nsAbDirectoryQueryPropertyValue(const char* aName,
+ const char16_t* aValue);
+ nsAbDirectoryQueryPropertyValue(const char* aName,
+ nsISupports* aValueISupports);
+
+protected:
+ virtual ~nsAbDirectoryQueryPropertyValue();
+ nsCString mName;
+ nsString mValue;
+ nsCOMPtr<nsISupports> mValueISupports;
+};
+
+
+class nsAbDirectoryQuery : public nsIAbDirectoryQuery
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERY
+
+ nsAbDirectoryQuery();
+
+protected:
+ virtual ~nsAbDirectoryQuery();
+ nsresult query(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories,
+ int32_t* resultLimit);
+ nsresult queryChildren(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories,
+ int32_t* resultLimit);
+ nsresult queryCards(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ int32_t* resultLimit);
+ nsresult matchCard(nsIAbCard* card,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ int32_t* resultLimit);
+ nsresult matchCardExpression(nsIAbCard* card,
+ nsIAbBooleanExpression* expression,
+ bool* result);
+ nsresult matchCardCondition(nsIAbCard* card,
+ nsIAbBooleanConditionString* condition,
+ bool* matchFound);
+
+ nsresult queryMatch (nsIAbCard* card,
+ nsIAbDirSearchListener* listener);
+ nsresult queryFinished(nsIAbDirSearchListener* listener);
+ nsresult queryError(nsIAbDirSearchListener* listener);
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp
new file mode 100644
index 000000000..553f3b903
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbDirectoryQuery.h"
+#include "nsAbDirectoryQueryProxy.h"
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQueryProxy, nsIAbDirectoryQueryProxy, nsIAbDirectoryQuery)
+
+nsAbDirectoryQueryProxy::nsAbDirectoryQueryProxy() :
+ mInitiated (false)
+{
+}
+
+nsAbDirectoryQueryProxy::~nsAbDirectoryQueryProxy()
+{
+}
+
+/* void initiate (in nsIAbDirectory directory); */
+NS_IMETHODIMP nsAbDirectoryQueryProxy::Initiate()
+{
+ if (mInitiated)
+ return NS_OK;
+
+ mDirectoryQuery = new nsAbDirectoryQuery();
+
+ mInitiated = true;
+
+ return NS_OK;
+}
+
+
diff --git a/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h
new file mode 100644
index 000000000..89a323c75
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbDirectoryQueryProxy_h__
+#define nsAbDirectoryQueryProxy_h__
+
+#include "nsIAbDirectoryQueryProxy.h"
+#include "nsCOMPtr.h"
+
+class nsAbDirectoryQueryProxy : public nsIAbDirectoryQueryProxy
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIABDIRECTORYQUERY(mDirectoryQuery->)
+ NS_DECL_NSIABDIRECTORYQUERYPROXY
+
+ nsAbDirectoryQueryProxy();
+
+protected:
+ virtual ~nsAbDirectoryQueryProxy();
+ bool mInitiated;
+ nsCOMPtr<nsIAbDirectoryQuery> mDirectoryQuery;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbLDAPAttributeMap.js b/mailnews/addrbook/src/nsAbLDAPAttributeMap.js
new file mode 100644
index 000000000..8ff2406d2
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPAttributeMap.js
@@ -0,0 +1,247 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var NS_ABLDAPATTRIBUTEMAP_CID = Components.ID(
+ "{127b341a-bdda-4270-85e1-edff569a9b85}");
+var NS_ABLDAPATTRIBUTEMAPSERVICE_CID = Components.ID(
+ "{4ed7d5e1-8800-40da-9e78-c4f509d7ac5e}");
+
+function nsAbLDAPAttributeMap() {
+ this.mPropertyMap = {};
+ this.mAttrMap = {};
+}
+
+nsAbLDAPAttributeMap.prototype = {
+ classID: NS_ABLDAPATTRIBUTEMAP_CID,
+
+ getAttributeList: function getAttributeList(aProperty) {
+
+ if (!(aProperty in this.mPropertyMap)) {
+ return null;
+ }
+
+ // return the joined list
+ return this.mPropertyMap[aProperty].join(",");
+ },
+
+ getAttributes: function getAttributes(aProperty, aCount, aAttrs) {
+
+ // fail if no entry for this
+ if (!(aProperty in this.mPropertyMap)) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ aAttrs = this.mPropertyMap[aProperty];
+ aCount = aAttrs.length;
+ return aAttrs;
+ },
+
+ getFirstAttribute: function getFirstAttribute(aProperty) {
+
+ // fail if no entry for this
+ if (!(aProperty in this.mPropertyMap)) {
+ return null;
+ }
+
+ return this.mPropertyMap[aProperty][0];
+ },
+
+ setAttributeList: function setAttributeList(aProperty, aAttributeList,
+ aAllowInconsistencies) {
+
+ var attrs = aAttributeList.split(",");
+
+ // check to make sure this call won't allow multiple mappings to be
+ // created, if requested
+ if (!aAllowInconsistencies) {
+ for (var attr of attrs) {
+ if (attr in this.mAttrMap && this.mAttrMap[attr] != aProperty) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ // delete any attr mappings created by the existing property map entry
+ if (aProperty in this.mPropertyMap) {
+ for (attr of this.mPropertyMap[aProperty]) {
+ delete this.mAttrMap[attr];
+ }
+ }
+
+ // add these attrs to the attrmap
+ for (attr of attrs) {
+ this.mAttrMap[attr] = aProperty;
+ }
+
+ // add them to the property map
+ this.mPropertyMap[aProperty] = attrs;
+ },
+
+ getProperty: function getProperty(aAttribute) {
+
+ if (!(aAttribute in this.mAttrMap)) {
+ return null;
+ }
+
+ return this.mAttrMap[aAttribute];
+ },
+
+ getAllCardAttributes: function getAllCardAttributes() {
+ var attrs = [];
+ for (var prop in this.mPropertyMap) {
+ let attrArray = this.mPropertyMap[prop];
+ attrs = attrs.concat(attrArray);
+ }
+
+ if (!attrs.length) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ return attrs.join(",");
+ },
+
+ getAllCardProperties: function getAllCardProperties(aCount) {
+
+ var props = [];
+ for (var prop in this.mPropertyMap) {
+ props.push(prop);
+ }
+
+ aCount.value = props.length;
+ return props;
+ },
+
+ setFromPrefs: function setFromPrefs(aPrefBranchName) {
+ // get the right pref branch
+ let branch = Services.prefs.getBranch(aPrefBranchName + ".");
+
+ // get the list of children
+ var childCount = {};
+ var children = branch.getChildList("", childCount);
+
+ // do the actual sets
+ for (var child of children) {
+ this.setAttributeList(child, branch.getCharPref(child), true);
+ }
+
+ // ensure that everything is kosher
+ this.checkState();
+ },
+
+ setCardPropertiesFromLDAPMessage: function
+ setCardPropertiesFromLDAPMessage(aMessage, aCard) {
+
+ var cardValueWasSet = false;
+
+ var msgAttrCount = {};
+ var msgAttrs = aMessage.getAttributes(msgAttrCount);
+
+ // downcase the array for comparison
+ function toLower(a) { return a.toLowerCase(); }
+ msgAttrs = msgAttrs.map(toLower);
+
+ // deal with each addressbook property
+ for (var prop in this.mPropertyMap) {
+
+ // go through the list of possible attrs in precedence order
+ for (var attr of this.mPropertyMap[prop]) {
+
+ attr = attr.toLowerCase();
+
+ // find the first attr that exists in this message
+ if (msgAttrs.indexOf(attr) != -1) {
+
+ try {
+ var values = aMessage.getValues(attr, {});
+ // strip out the optional label from the labeledURI
+ if (attr == "labeleduri" && values[0]) {
+ var index = values[0].indexOf(" ");
+ if (index != -1)
+ values[0] = values[0].substring(0, index);
+ }
+ aCard.setProperty(prop, values[0]);
+
+ cardValueWasSet = true;
+ break;
+ } catch (ex) {
+ // ignore any errors getting message values or setting card values
+ }
+ }
+ }
+ }
+
+ if (!cardValueWasSet) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ return;
+ },
+
+ checkState: function checkState() {
+
+ var attrsSeen = [];
+
+ for (var prop in this.mPropertyMap) {
+ let attrArray = this.mPropertyMap[prop];
+ for (var attr of attrArray) {
+
+ // multiple attributes that mapped to the empty string are permitted
+ if (!attr.length) {
+ continue;
+ }
+
+ // if we've seen this before, there's a problem
+ if (attrsSeen.indexOf(attr) != -1) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ // remember that we've seen it now
+ attrsSeen.push(attr);
+ }
+ }
+
+ return;
+ },
+
+ QueryInterface: XPCOMUtils
+ .generateQI([Components.interfaces.nsIAbLDAPAttributeMap])
+}
+
+function nsAbLDAPAttributeMapService() {
+}
+
+nsAbLDAPAttributeMapService.prototype = {
+
+ classID: NS_ABLDAPATTRIBUTEMAPSERVICE_CID,
+
+ mAttrMaps: {},
+
+ getMapForPrefBranch: function getMapForPrefBranch(aPrefBranchName) {
+
+ // if we've already got this map, return it
+ if (aPrefBranchName in this.mAttrMaps) {
+ return this.mAttrMaps[aPrefBranchName];
+ }
+
+ // otherwise, try and create it
+ var attrMap = new nsAbLDAPAttributeMap();
+ attrMap.setFromPrefs("ldap_2.servers.default.attrmap");
+ attrMap.setFromPrefs(aPrefBranchName + ".attrmap");
+
+ // cache
+ this.mAttrMaps[aPrefBranchName] = attrMap;
+
+ // and return
+ return attrMap;
+ },
+
+ QueryInterface: XPCOMUtils
+ .generateQI([Components.interfaces.nsIAbLDAPAttributeMapService])
+}
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsAbLDAPAttributeMap, nsAbLDAPAttributeMapService]);
+
diff --git a/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js b/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js
new file mode 100644
index 000000000..f75a57f0a
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js
@@ -0,0 +1,325 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var ACR = Components.interfaces.nsIAutoCompleteResult;
+var nsIAbAutoCompleteResult = Components.interfaces.nsIAbAutoCompleteResult;
+var nsIAbDirectoryQueryResultListener =
+ Components.interfaces.nsIAbDirectoryQueryResultListener;
+
+// nsAbLDAPAutoCompleteResult
+// Derived from nsIAbAutoCompleteResult, provides a LDAP specific result
+// implementation.
+
+function nsAbLDAPAutoCompleteResult(aSearchString) {
+ // Can't create this in the prototype as we'd get the same array for
+ // all instances
+ this._searchResults = [];
+ this.searchString = aSearchString;
+}
+
+nsAbLDAPAutoCompleteResult.prototype = {
+ _searchResults: null,
+ _commentColumn: "",
+
+ // nsIAutoCompleteResult
+
+ searchString: null,
+ searchResult: ACR.RESULT_NOMATCH,
+ defaultIndex: -1,
+ errorDescription: null,
+
+ get matchCount() {
+ return this._searchResults.length;
+ },
+
+ getLabelAt: function getLabelAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ getValueAt: function getValueAt(aIndex) {
+ return this._searchResults[aIndex].value;
+ },
+
+ getCommentAt: function getCommentAt(aIndex) {
+ return this._commentColumn;
+ },
+
+ getStyleAt: function getStyleAt(aIndex) {
+ return this.searchResult == ACR.RESULT_FAILURE ? "remote-err" :
+ "remote-abook";
+ },
+
+ getImageAt: function getImageAt(aIndex) {
+ return "";
+ },
+
+ getFinalCompleteValueAt: function(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ removeValueAt: function removeValueAt(aRowIndex, aRemoveFromDB) {
+ },
+
+ // nsIAbAutoCompleteResult
+
+ getCardAt: function getCardAt(aIndex) {
+ return this._searchResults[aIndex].card;
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([ACR, nsIAbAutoCompleteResult])
+}
+
+function nsAbLDAPAutoCompleteSearch() {
+ Services.obs.addObserver(this, "quit-application", false);
+ this._timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+}
+
+nsAbLDAPAutoCompleteSearch.prototype = {
+ // For component registration
+ classID: Components.ID("227e6482-fe9f-441f-9b7d-7b60375e7449"),
+
+ // A short-lived LDAP directory cache.
+ // To avoid recreating components as the user completes, we maintain the most
+ // recently used address book, nsAbLDAPDirectoryQuery and search context.
+ // However the cache is discarded if it has not been used for a minute.
+ // This is done to avoid problems with LDAP sessions timing out and hanging.
+ _query: null,
+ _book: null,
+ _attributes: null,
+ _context: -1,
+ _timer: null,
+
+ // The current search result.
+ _result: null,
+ // The listener to pass back results to.
+ _listener: null,
+
+ _parser: MailServices.headerParser,
+
+ applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+
+ // Private methods
+
+ _checkDuplicate: function _checkDuplicate(card, emailAddress) {
+ var lcEmailAddress = emailAddress.toLocaleLowerCase();
+
+ return this._result._searchResults.some(function(result) {
+ return result.value.toLocaleLowerCase() == lcEmailAddress;
+ });
+ },
+
+ _addToResult: function(card) {
+ let mbox = this._parser.makeMailboxObject(card.displayName,
+ card.isMailList ? card.getProperty("Notes", "") || card.displayName :
+ card.primaryEmail);
+ if (!mbox.email)
+ return;
+
+ let emailAddress = mbox.toString();
+
+ // If it is a duplicate, then just return and don't add it. The
+ // _checkDuplicate function deals with it all for us.
+ if (this._checkDuplicate(card, emailAddress))
+ return;
+
+ // Find out where to insert the card.
+ var insertPosition = 0;
+
+ // Next sort on full address
+ while (insertPosition < this._result._searchResults.length &&
+ emailAddress > this._result._searchResults[insertPosition].value)
+ ++insertPosition;
+
+ this._result._searchResults.splice(insertPosition, 0, {
+ value: emailAddress,
+ card: card,
+ });
+ },
+
+ // nsIObserver
+
+ observe: function observer(subject, topic, data) {
+ if (topic == "quit-application") {
+ Services.obs.removeObserver(this, "quit-application");
+ } else if (topic != "timer-callback") {
+ return;
+ }
+
+ // Force the individual query items to null, so that the memory
+ // gets collected straight away.
+ this.stopSearch();
+ this._book = null;
+ this._context = -1;
+ this._query = null;
+ this._attributes = null;
+ },
+
+ // nsIAutoCompleteSearch
+
+ startSearch: function startSearch(aSearchString, aParam,
+ aPreviousResult, aListener) {
+ let params = JSON.parse(aParam) || {};
+ let applicable = !("type" in params) || this.applicableHeaders.has(params.type);
+
+ this._result = new nsAbLDAPAutoCompleteResult(aSearchString);
+ aSearchString = aSearchString.toLocaleLowerCase();
+
+ // If the search string isn't value, or contains a comma, or the user
+ // hasn't enabled autocomplete, then just return no matches / or the
+ // result ignored.
+ // The comma check is so that we don't autocomplete against the user
+ // entering multiple addresses.
+ if (!applicable || !aSearchString || aSearchString.includes(",")) {
+ this._result.searchResult = ACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, this._result);
+ return;
+ }
+
+ // The rules here: If the current identity has a directoryServer set, then
+ // use that, otherwise, try the global preference instead.
+ var acDirURI = null;
+ var identity;
+
+ if ("idKey" in params) {
+ try {
+ identity = MailServices.accounts.getIdentity(params.idKey);
+ }
+ catch(ex) {
+ Components.utils.reportError("Couldn't get specified identity, " +
+ "falling back to global settings");
+ }
+ }
+
+ // Does the current identity override the global preference?
+ if (identity && identity.overrideGlobalPref)
+ acDirURI = identity.directoryServer;
+ else {
+ // Try the global one
+ if (Services.prefs.getBoolPref("ldap_2.autoComplete.useDirectory"))
+ acDirURI = Services.prefs.getCharPref("ldap_2.autoComplete.directoryServer");
+ }
+
+ if (!acDirURI || Services.io.offline) {
+ // No directory to search or we are offline, send a no match and return.
+ aListener.onSearchResult(this, this._result);
+ return;
+ }
+
+ this.stopSearch();
+
+ // If we don't already have a cached query for this URI, build a new one.
+ acDirURI = "moz-abldapdirectory://" + acDirURI;
+ if (!this._book || this._book.URI != acDirURI) {
+ this._query =
+ Components.classes["@mozilla.org/addressbook/ldap-directory-query;1"]
+ .createInstance(Components.interfaces.nsIAbDirectoryQuery);
+ this._book = MailServices.ab.getDirectory(acDirURI)
+ .QueryInterface(Components.interfaces.nsIAbLDAPDirectory);
+
+ // Create a minimal map just for the display name and primary email.
+ this._attributes =
+ Components.classes["@mozilla.org/addressbook/ldap-attribute-map;1"]
+ .createInstance(Components.interfaces.nsIAbLDAPAttributeMap);
+ this._attributes.setAttributeList("DisplayName",
+ this._book.attributeMap.getAttributeList("DisplayName", {}), true);
+ this._attributes.setAttributeList("PrimaryEmail",
+ this._book.attributeMap.getAttributeList("PrimaryEmail", {}), true);
+ }
+
+ this._result._commentColumn = this._book.dirName;
+ this._listener = aListener;
+ this._timer.init(this, 60000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+
+ var args =
+ Components.classes["@mozilla.org/addressbook/directory/query-arguments;1"]
+ .createInstance(Components.interfaces.nsIAbDirectoryQueryArguments);
+
+ var filterTemplate = this._book.getStringValue("autoComplete.filterTemplate", "");
+
+ // Use default value when preference is not set or it contains empty string
+ if (!filterTemplate)
+ filterTemplate = "(|(cn=%v1*%v2-*)(mail=%v1*%v2-*)(sn=%v1*%v2-*))";
+
+ // Create filter from filter template and search string
+ var ldapSvc = Components.classes["@mozilla.org/network/ldap-service;1"]
+ .getService(Components.interfaces.nsILDAPService);
+ var filter = ldapSvc.createFilter(1024, filterTemplate, "", "", "", aSearchString);
+ if (!filter)
+ throw new Error("Filter string is empty, check if filterTemplate variable is valid in prefs.js.");
+ args.typeSpecificArg = this._attributes;
+ args.querySubDirectories = true;
+ args.filter = filter;
+
+ // Start the actual search
+ this._context =
+ this._query.doQuery(this._book, args, this, this._book.maxHits, 0);
+ },
+
+ stopSearch: function stopSearch() {
+ if (this._listener) {
+ this._query.stopQuery(this._context);
+ this._listener = null;
+ }
+ },
+
+ // nsIAbDirSearchListener
+
+ onSearchFinished: function onSearchFinished(aResult, aErrorMsg) {
+ if (!this._listener)
+ return;
+
+ if (aResult == nsIAbDirectoryQueryResultListener.queryResultComplete) {
+ if (this._result.matchCount) {
+ this._result.searchResult = ACR.RESULT_SUCCESS;
+ this._result.defaultIndex = 0;
+ }
+ else
+ this._result.searchResult = ACR.RESULT_NOMATCH;
+ }
+ else if (aResult == nsIAbDirectoryQueryResultListener.queryResultError) {
+ this._result.searchResult = ACR.RESULT_FAILURE;
+ this._result.defaultIndex = 0;
+ }
+ // const long queryResultStopped = 2;
+ // const long queryResultError = 3;
+ this._listener.onSearchResult(this, this._result);
+ this._listener = null;
+ },
+
+ onSearchFoundCard: function onSearchFoundCard(aCard) {
+ if (!this._listener)
+ return;
+
+ this._addToResult(aCard);
+
+ /* XXX autocomplete doesn't expect you to rearrange while searching
+ if (this._result.matchCount)
+ this._result.searchResult = ACR.RESULT_SUCCESS_ONGOING;
+ else
+ this._result.searchResult = ACR.RESULT_NOMATCH_ONGOING;
+
+ this._listener.onSearchResult(this, this._result);
+ */
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver,
+ Components.interfaces
+ .nsIAutoCompleteSearch,
+ Components.interfaces
+ .nsIAbDirSearchListener])
+};
+
+// Module
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsAbLDAPAutoCompleteSearch]);
diff --git a/mailnews/addrbook/src/nsAbLDAPCard.cpp b/mailnews/addrbook/src/nsAbLDAPCard.cpp
new file mode 100644
index 000000000..6dcfdedbb
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPCard.cpp
@@ -0,0 +1,297 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPCard.h"
+#include "nsIMutableArray.h"
+#include "nsCOMPtr.h"
+#include "nsILDAPModification.h"
+#include "nsILDAPBERValue.h"
+#include "nsILDAPMessage.h"
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsAbBaseCID.h"
+#include "nsAbUtils.h"
+#include "nsILDAPErrors.h"
+
+#include <stdio.h>
+
+#define kDNColumn "DN"
+
+nsAbLDAPCard::nsAbLDAPCard()
+{
+}
+
+nsAbLDAPCard::~nsAbLDAPCard()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbLDAPCard, nsAbCardProperty, nsIAbLDAPCard)
+
+/* Retrieves the changes to the LDAP card and stores them in an LDAP
+ * update message.
+ *
+ * Calling this method changes the LDAP card, it updates the
+ * meta-properties (m_*) to reflect what the LDAP contents will be once
+ * the update has been performed. This allows you to do multiple (successful)
+ * consecutive edits on a card in a search result. If the meta-properties
+ * were not updated, incorrect assuptions would be made about what object
+ * classes to add, or what attributes to clear.
+ *
+ * XXX: We need to take care when integrating this code with the asynchronous
+ * update dialogs, as the current code in nsAbLDAPDirectory has a problem
+ * when an update fails: the modified card still gets stored and shown to
+ * the user instead of being discarded. There is one especially tricky case:
+ * when you do an update on a card which changes its DN, you have two
+ * operations (rename, then update the other attributes). If the rename
+ * operation succeeds and not the update of the attributes, you are
+ * "somewhere in between" the original card and the updated card.
+*/
+NS_IMETHODIMP nsAbLDAPCard::GetLDAPMessageInfo(
+ nsIAbLDAPAttributeMap *aAttributeMap,
+ const uint32_t aClassCount,
+ const char **aClasses,
+ int32_t aType,
+ nsIArray **aLDAPAddMessageInfo)
+{
+ NS_ENSURE_ARG_POINTER(aAttributeMap);
+ NS_ENSURE_ARG_POINTER(aClasses);
+ NS_ENSURE_ARG_POINTER(aLDAPAddMessageInfo);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> modArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add any missing object classes. We never remove any object
+ // classes: if an entry has additional object classes, it's probably
+ // for a good reason.
+ nsAutoCString oclass;
+ for (uint32_t i = 0; i < aClassCount; ++i)
+ {
+ oclass.Assign(nsDependentCString(aClasses[i]));
+ ToLowerCase(oclass);
+
+ if (!m_objectClass.Contains(oclass))
+ {
+ m_objectClass.AppendElement(oclass);
+ printf("LDAP : adding objectClass %s\n", oclass.get());
+ }
+ }
+
+ nsCOMPtr<nsILDAPModification> mod =
+ do_CreateInstance("@mozilla.org/network/ldap-modification;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> values =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < m_objectClass.Length(); ++i)
+ {
+ nsCOMPtr<nsILDAPBERValue> value =
+ do_CreateInstance("@mozilla.org/network/ldap-ber-value;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = value->SetFromUTF8(m_objectClass.ElementAt(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = values->AppendElement(value, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mod->SetUpModification(aType, NS_LITERAL_CSTRING("objectClass"), values);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ modArray->AppendElement(mod, false);
+
+ // Add card properties
+ CharPtrArrayGuard props;
+ rv = aAttributeMap->GetAllCardProperties(props.GetSizeAddr(),
+ props.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString attr;
+ nsCString propvalue;
+ for (uint32_t i = 0; i < props.GetSize(); ++i)
+ {
+ // Skip some attributes that don't map to LDAP.
+ //
+ // BirthYear : by default this is mapped to 'birthyear',
+ // which is not part of mozillaAbPersonAlpha
+ //
+ // LastModifiedDate : by default this is mapped to 'modifytimestamp',
+ // which cannot be modified
+ //
+ // PreferMailFormat : by default this is mapped to 'mozillaUseHtmlMail',
+ // which is a boolean, not plaintext/html/unknown
+ if (!strcmp(props[i], kBirthYearProperty) ||
+ !strcmp(props[i], kLastModifiedDateProperty) ||
+ !strcmp(props[i], kPreferMailFormatProperty))
+ continue;
+
+ rv = aAttributeMap->GetFirstAttribute(nsDependentCString(props[i]),
+ attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ToLowerCase(attr);
+
+ // If the property is not mapped to an attribute, skip it.
+ if (attr.IsEmpty())
+ continue;
+
+ nsCOMPtr<nsILDAPModification> mod =
+ do_CreateInstance("@mozilla.org/network/ldap-modification;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ size_t index = m_attributes.IndexOf(attr);
+
+ rv = GetPropertyAsAUTF8String(props[i], propvalue);
+
+ if (NS_SUCCEEDED(rv) &&!propvalue.IsEmpty())
+ {
+ // If the new value is not empty, add/update it
+ nsCOMPtr<nsILDAPBERValue> value =
+ do_CreateInstance("@mozilla.org/network/ldap-ber-value;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = value->SetFromUTF8(propvalue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mod->SetUpModificationOneValue(aType, attr, value);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ printf("LDAP : setting attribute %s (%s) to '%s'\n", attr.get(),
+ props[i], propvalue.get());
+ modArray->AppendElement(mod, false);
+ if (index != m_attributes.NoIndex)
+ m_attributes.AppendElement(attr);
+
+ }
+ else if (aType == nsILDAPModification::MOD_REPLACE &&
+ index != m_attributes.NoIndex)
+ {
+ // If the new value is empty, we are performing an update
+ // and the attribute was previously set, clear it
+ nsCOMPtr<nsIMutableArray> novalues =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mod->SetUpModification(aType, attr, novalues);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ printf("LDAP : removing attribute %s (%s)\n", attr.get(), props[i]);
+ modArray->AppendElement(mod, false);
+ m_attributes.RemoveElementAt(index);
+ }
+ }
+
+ NS_ADDREF(*aLDAPAddMessageInfo = modArray);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPCard::BuildRdn(nsIAbLDAPAttributeMap *aAttributeMap,
+ const uint32_t aAttrCount,
+ const char **aAttributes,
+ nsACString &aRdn)
+{
+ NS_ENSURE_ARG_POINTER(aAttributeMap);
+ NS_ENSURE_ARG_POINTER(aAttributes);
+
+ nsresult rv;
+ nsCString attr;
+ nsAutoCString prop;
+ nsCString propvalue;
+
+ aRdn.Truncate();
+ for (uint32_t i = 0; i < aAttrCount; ++i)
+ {
+ attr.Assign(nsDependentCString(aAttributes[i]));
+
+ // Lookup the property corresponding to the attribute
+ rv = aAttributeMap->GetProperty(attr, prop);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the property value
+ rv = GetPropertyAsAUTF8String(prop.get(), propvalue);
+
+ // XXX The case where an attribute needed to build the Relative
+ // Distinguished Name is not set needs to be handled by the caller,
+ // so as to let the user know what is missing.
+ if (NS_FAILED(rv) || propvalue.IsEmpty())
+ {
+ NS_ERROR("nsAbLDAPCard::BuildRdn: a required attribute is not set");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ aRdn.Append(attr);
+ aRdn.AppendLiteral("=");
+ aRdn.Append(propvalue);
+ if (i < aAttrCount - 1)
+ aRdn.AppendLiteral("+");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPCard::GetDn(nsACString &aDN)
+{
+ return GetPropertyAsAUTF8String(kDNColumn, aDN);
+}
+
+NS_IMETHODIMP nsAbLDAPCard::SetDn(const nsACString &aDN)
+{
+ SetLocalId(aDN);
+ return SetPropertyAsAUTF8String(kDNColumn, aDN);
+}
+
+NS_IMETHODIMP nsAbLDAPCard::SetMetaProperties(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ // Get DN
+ nsAutoCString dn;
+ nsresult rv = aMessage->GetDn(dn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetDn(dn);
+
+ // Get the list of set attributes
+ CharPtrArrayGuard attrs;
+ rv = aMessage->GetAttributes(attrs.GetSizeAddr(), attrs.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString attr;
+ m_attributes.Clear();
+ for (uint32_t i = 0; i < attrs.GetSize(); ++i)
+ {
+ attr.Assign(nsDependentCString(attrs[i]));
+ ToLowerCase(attr);
+ m_attributes.AppendElement(attr);
+ }
+
+ // Get the objectClass values
+ m_objectClass.Clear();
+ PRUnicharPtrArrayGuard vals;
+ rv = aMessage->GetValues("objectClass", vals.GetSizeAddr(),
+ vals.GetArrayAddr());
+
+ // objectClass is not always included in search result entries and
+ // nsILDAPMessage::GetValues returns NS_ERROR_LDAP_DECODING_ERROR if the
+ // requested attribute doesn't exist.
+ if (rv == NS_ERROR_LDAP_DECODING_ERROR)
+ return NS_OK;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString oclass;
+ for (uint32_t i = 0; i < vals.GetSize(); ++i)
+ {
+ oclass.Assign(NS_LossyConvertUTF16toASCII(nsDependentString(vals[i])));
+ ToLowerCase(oclass);
+ m_objectClass.AppendElement(oclass);
+ }
+
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPCard.h b/mailnews/addrbook/src/nsAbLDAPCard.h
new file mode 100644
index 000000000..ef11b50e4
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPCard.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPCard_h__
+#define nsAbLDAPCard_h__
+
+#include "nsAbCardProperty.h"
+#include "nsIAbLDAPCard.h"
+#include "nsTArray.h"
+
+class nsIMutableArray;
+
+class nsAbLDAPCard : public nsAbCardProperty,
+ public nsIAbLDAPCard
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIABLDAPCARD
+
+ nsAbLDAPCard();
+
+protected:
+ virtual ~nsAbLDAPCard();
+ nsTArray<nsCString> m_attributes;
+ nsTArray<nsCString> m_objectClass;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp b/mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp
new file mode 100644
index 000000000..acec2e539
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp
@@ -0,0 +1,541 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPChangeLogData.h"
+#include "nsAbLDAPChangeLogQuery.h"
+#include "nsILDAPMessage.h"
+#include "nsIAddrBookSession.h"
+#include "nsAbBaseCID.h"
+#include "nsAbUtils.h"
+#include "nsAbMDBCard.h"
+#include "nsAbLDAPCard.h"
+#include "nsIAuthPrompt.h"
+#include "nsIStringBundle.h"
+#include "nsIWindowWatcher.h"
+#include "nsUnicharUtils.h"
+#include "plstr.h"
+#include "nsILDAPErrors.h"
+#include "prmem.h"
+#include "mozilla/Services.h"
+
+// Defined here since to be used
+// only locally to this file.
+enum UpdateOp {
+ NO_OP,
+ ENTRY_ADD,
+ ENTRY_DELETE,
+ ENTRY_MODIFY
+};
+
+nsAbLDAPProcessChangeLogData::nsAbLDAPProcessChangeLogData()
+: mUseChangeLog(false),
+ mChangeLogEntriesCount(0),
+ mEntriesAddedQueryCount(0)
+{
+ mRootDSEEntry.firstChangeNumber = 0;
+ mRootDSEEntry.lastChangeNumber = 0;
+}
+
+nsAbLDAPProcessChangeLogData::~nsAbLDAPProcessChangeLogData()
+{
+
+}
+
+NS_IMETHODIMP nsAbLDAPProcessChangeLogData::Init(nsIAbLDAPReplicationQuery * query, nsIWebProgressListener *progressListener)
+{
+ NS_ENSURE_ARG_POINTER(query);
+
+ // Here we are assuming that the caller will pass a nsAbLDAPChangeLogQuery object,
+ // an implementation derived from the implementation of nsIAbLDAPReplicationQuery.
+ nsresult rv = NS_OK;
+ mChangeLogQuery = do_QueryInterface(query, &rv);
+ if(NS_FAILED(rv))
+ return rv;
+
+ // Call the parent's Init now.
+ return nsAbLDAPProcessReplicationData::Init(query, progressListener);
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnLDAPBind(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t errCode;
+
+ nsresult rv = aMessage->GetErrorCode(&errCode);
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+
+ if(errCode != nsILDAPErrors::SUCCESS) {
+ Done(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ switch(mState) {
+ case kAnonymousBinding :
+ rv = GetAuthData();
+ if(NS_SUCCEEDED(rv))
+ rv = mChangeLogQuery->QueryAuthDN(mAuthUserID);
+ if(NS_SUCCEEDED(rv))
+ mState = kSearchingAuthDN;
+ break;
+ case kAuthenticatedBinding :
+ rv = mChangeLogQuery->QueryRootDSE();
+ if(NS_SUCCEEDED(rv))
+ mState = kSearchingRootDSE;
+ break;
+ } //end of switch
+
+ if(NS_FAILED(rv))
+ Abort();
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnLDAPSearchEntry(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+
+ switch(mState)
+ {
+ case kSearchingAuthDN :
+ {
+ nsAutoCString authDN;
+ rv = aMessage->GetDn(authDN);
+ if(NS_SUCCEEDED(rv) && !authDN.IsEmpty())
+ mAuthDN = authDN.get();
+ }
+ break;
+ case kSearchingRootDSE:
+ rv = ParseRootDSEEntry(aMessage);
+ break;
+ case kFindingChanges:
+ rv = ParseChangeLogEntries(aMessage);
+ break;
+ // Fall through since we only add (for updates we delete and add)
+ case kReplicatingChanges:
+ case kReplicatingAll :
+ return nsAbLDAPProcessReplicationData::OnLDAPSearchEntry(aMessage);
+ }
+
+ if(NS_FAILED(rv))
+ Abort();
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnLDAPSearchResult(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t errorCode;
+
+ nsresult rv = aMessage->GetErrorCode(&errorCode);
+
+ if(NS_SUCCEEDED(rv))
+ {
+ if(errorCode == nsILDAPErrors::SUCCESS || errorCode == nsILDAPErrors::SIZELIMIT_EXCEEDED) {
+ switch(mState) {
+ case kSearchingAuthDN :
+ rv = OnSearchAuthDNDone();
+ break;
+ case kSearchingRootDSE:
+ {
+ // Before starting the changeLog check the DB file, if its not there or bogus
+ // we need to create a new one and set to all.
+ nsCOMPtr<nsIAddrBookSession> abSession = do_GetService(NS_ADDRBOOKSESSION_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ break;
+ nsCOMPtr<nsIFile> dbPath;
+ rv = abSession->GetUserProfileDirectory(getter_AddRefs(dbPath));
+ if (NS_FAILED(rv))
+ break;
+
+ nsAutoCString fileName;
+ rv = mDirectory->GetReplicationFileName(fileName);
+ if (NS_FAILED(rv))
+ break;
+
+ rv = dbPath->AppendNative(fileName);
+ if (NS_FAILED(rv))
+ break;
+
+ bool fileExists;
+ rv = dbPath->Exists(&fileExists);
+ if (NS_FAILED(rv))
+ break;
+
+ int64_t fileSize;
+ rv = dbPath->GetFileSize(&fileSize);
+ if(NS_FAILED(rv))
+ break;
+
+ if (!fileExists || !fileSize)
+ mUseChangeLog = false;
+
+ // Open / create the AB here since it calls Done,
+ // just return from here.
+ if (mUseChangeLog)
+ rv = OpenABForReplicatedDir(false);
+ else
+ rv = OpenABForReplicatedDir(true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Now start the appropriate query
+ rv = OnSearchRootDSEDone();
+ break;
+ }
+ case kFindingChanges:
+ rv = OnFindingChangesDone();
+ // If success we return from here since
+ // this changes state to kReplicatingChanges
+ // and it falls thru into the if clause below.
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ break;
+ case kReplicatingAll :
+ return nsAbLDAPProcessReplicationData::OnLDAPSearchResult(aMessage);
+ } // end of switch
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+ // If one of the changed entry in changelog is not found,
+ // continue with replicating the next one.
+ if(mState == kReplicatingChanges)
+ rv = OnReplicatingChangeDone();
+ } // end of outer if
+
+ if(NS_FAILED(rv))
+ Abort();
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::GetAuthData()
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (!wwatch)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAuthPrompt> dialog;
+ nsresult rv = wwatch->GetNewAuthPrompter(0, getter_AddRefs(dialog));
+ if (NS_FAILED(rv))
+ return rv;
+ if (!dialog)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsILDAPURL> url;
+ rv = mQuery->GetReplicationURL(getter_AddRefs(url));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString serverUri;
+ rv = url->GetSpec(serverUri);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle));
+ if (NS_FAILED (rv))
+ return rv ;
+
+ nsString title;
+ rv = bundle->GetStringFromName(u"AuthDlgTitle", getter_Copies(title));
+ if (NS_FAILED (rv))
+ return rv ;
+
+ nsString desc;
+ rv = bundle->GetStringFromName(u"AuthDlgDesc", getter_Copies(desc));
+ if (NS_FAILED (rv))
+ return rv ;
+
+ nsString username;
+ nsString password;
+ bool btnResult = false;
+ rv = dialog->PromptUsernameAndPassword(title, desc,
+ NS_ConvertUTF8toUTF16(serverUri).get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ getter_Copies(username), getter_Copies(password),
+ &btnResult);
+ if(NS_SUCCEEDED(rv) && btnResult) {
+ CopyUTF16toUTF8(username, mAuthUserID);
+ CopyUTF16toUTF8(password, mAuthPswd);
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnSearchAuthDNDone()
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsILDAPURL> url;
+ nsresult rv = mQuery->GetReplicationURL(getter_AddRefs(url));
+ if(NS_SUCCEEDED(rv))
+ rv = mQuery->ConnectToLDAPServer(url, mAuthDN);
+ if(NS_SUCCEEDED(rv)) {
+ mState = kAuthenticatedBinding;
+ rv = mDirectory->SetAuthDn(mAuthDN);
+ }
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::ParseRootDSEEntry(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Populate the RootDSEChangeLogEntry
+ CharPtrArrayGuard attrs;
+ nsresult rv = aMessage->GetAttributes(attrs.GetSizeAddr(), attrs.GetArrayAddr());
+ // No attributes
+ if(NS_FAILED(rv))
+ return rv;
+
+ for(int32_t i=attrs.GetSize()-1; i >= 0; i--) {
+ PRUnicharPtrArrayGuard vals;
+ rv = aMessage->GetValues(attrs.GetArray()[i], vals.GetSizeAddr(), vals.GetArrayAddr());
+ if(NS_FAILED(rv))
+ continue;
+ if(vals.GetSize()) {
+ if (!PL_strcasecmp(attrs[i], "changelog"))
+ CopyUTF16toUTF8(vals[0], mRootDSEEntry.changeLogDN);
+ if (!PL_strcasecmp(attrs[i], "firstChangeNumber"))
+ mRootDSEEntry.firstChangeNumber = atol(NS_LossyConvertUTF16toASCII(vals[0]).get());
+ if (!PL_strcasecmp(attrs[i], "lastChangeNumber"))
+ mRootDSEEntry.lastChangeNumber = atol(NS_LossyConvertUTF16toASCII(vals[0]).get());
+ if (!PL_strcasecmp(attrs[i], "dataVersion"))
+ CopyUTF16toUTF8(vals[0], mRootDSEEntry.dataVersion);
+ }
+ }
+
+ int32_t lastChangeNumber;
+ mDirectory->GetLastChangeNumber(&lastChangeNumber);
+
+ if ((mRootDSEEntry.lastChangeNumber > 0) &&
+ (lastChangeNumber < mRootDSEEntry.lastChangeNumber) &&
+ (lastChangeNumber > mRootDSEEntry.firstChangeNumber))
+ mUseChangeLog = true;
+
+ if (mRootDSEEntry.lastChangeNumber &&
+ (lastChangeNumber == mRootDSEEntry.lastChangeNumber)) {
+ Done(true); // We are up to date no need to replicate, db not open yet so call Done
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnSearchRootDSEDone()
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+
+ if(mUseChangeLog) {
+ rv = mChangeLogQuery->QueryChangeLog(mRootDSEEntry.changeLogDN, mRootDSEEntry.lastChangeNumber);
+ if (NS_FAILED(rv))
+ return rv;
+ mState = kFindingChanges;
+ if(mListener)
+ mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, false);
+ }
+ else {
+ rv = mQuery->QueryAllEntries();
+ if (NS_FAILED(rv))
+ return rv;
+ mState = kReplicatingAll;
+ if(mListener)
+ mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, true);
+ }
+
+ rv = mDirectory->SetLastChangeNumber(mRootDSEEntry.lastChangeNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDirectory->SetDataVersion(mRootDSEEntry.dataVersion);
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::ParseChangeLogEntries(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Populate the RootDSEChangeLogEntry
+ CharPtrArrayGuard attrs;
+ nsresult rv = aMessage->GetAttributes(attrs.GetSizeAddr(), attrs.GetArrayAddr());
+ // No attributes
+ if(NS_FAILED(rv))
+ return rv;
+
+ nsAutoString targetDN;
+ UpdateOp operation = NO_OP;
+ for(int32_t i = attrs.GetSize()-1; i >= 0; i--) {
+ PRUnicharPtrArrayGuard vals;
+ rv = aMessage->GetValues(attrs.GetArray()[i], vals.GetSizeAddr(), vals.GetArrayAddr());
+ if(NS_FAILED(rv))
+ continue;
+ if(vals.GetSize()) {
+ if (!PL_strcasecmp(attrs[i], "targetdn"))
+ targetDN = vals[0];
+ if (!PL_strcasecmp(attrs[i], "changetype")) {
+ if (!Compare(nsDependentString(vals[0]), NS_LITERAL_STRING("add"), nsCaseInsensitiveStringComparator()))
+ operation = ENTRY_ADD;
+ if (!Compare(nsDependentString(vals[0]), NS_LITERAL_STRING("modify"), nsCaseInsensitiveStringComparator()))
+ operation = ENTRY_MODIFY;
+ if (!Compare(nsDependentString(vals[0]), NS_LITERAL_STRING("delete"), nsCaseInsensitiveStringComparator()))
+ operation = ENTRY_DELETE;
+ }
+ }
+ }
+
+ mChangeLogEntriesCount++;
+ if(!(mChangeLogEntriesCount % 10)) { // Inform the listener every 10 entries
+ mListener->OnProgressChange(nullptr,nullptr,mChangeLogEntriesCount, -1, mChangeLogEntriesCount, -1);
+ // In case if the LDAP Connection thread is starved and causes problem
+ // uncomment this one and try.
+ // PR_Sleep(PR_INTERVAL_NO_WAIT); // give others a chance
+ }
+
+#ifdef DEBUG_rdayal
+ printf ("ChangeLog Replication : Updated Entry : %s for OpType : %u\n",
+ NS_ConvertUTF16toUTF8(targetDN).get(), operation);
+#endif
+
+ switch(operation) {
+ case ENTRY_ADD:
+ // Add the DN to the add list if not already in the list
+ if(!(mEntriesToAdd.IndexOf(targetDN) >= 0))
+ mEntriesToAdd.AppendString(targetDN);
+ break;
+ case ENTRY_DELETE:
+ // Do not check the return here since delete may fail if
+ // entry deleted in changelog does not exist in DB
+ // for e.g if the user specifies a filter, so go next entry
+ DeleteCard(targetDN);
+ break;
+ case ENTRY_MODIFY:
+ // For modify, delete the entry from DB and add updated entry
+ // we do this since we cannot access the changes attribs of changelog
+ rv = DeleteCard(targetDN);
+ if (NS_SUCCEEDED(rv))
+ if(!(mEntriesToAdd.IndexOf(targetDN) >= 0))
+ mEntriesToAdd.AppendString(targetDN);
+ break;
+ default:
+ // Should not come here, would come here only
+ // if the entry is not a changeLog entry
+ NS_WARNING("nsAbLDAPProcessChangeLogData::ParseChangeLogEntries"
+ "Not an changelog entry");
+ }
+
+ // Go ahead processing the next entry, a modify or delete DB operation
+ // can 'correctly' fail if the entry is not present in the DB,
+ // e.g. in case a filter is specified.
+ return NS_OK;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnFindingChangesDone()
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+#ifdef DEBUG_rdayal
+ printf ("ChangeLog Replication : Finding Changes Done \n");
+#endif
+
+ nsresult rv = NS_OK;
+
+ // No entries to add/update (for updates too we delete and add) entries,
+ // we took care of deletes in ParseChangeLogEntries, all Done!
+ mEntriesAddedQueryCount = mEntriesToAdd.Count();
+ if(mEntriesAddedQueryCount <= 0) {
+ if(mReplicationDB && mDBOpen) {
+ // Close the DB, no need to commit since we have not made
+ // any changes yet to the DB.
+ rv = mReplicationDB->Close(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB Close(no commit) on Success failed");
+ mDBOpen = false;
+ // Once are done with the replication file, delete the backup file
+ if(mBackupReplicationFile) {
+ rv = mBackupReplicationFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication BackupFile Remove on Success failed");
+ }
+ }
+ Done(true);
+ return NS_OK;
+ }
+
+ // Decrement the count first to get the correct array element
+ mEntriesAddedQueryCount--;
+ rv = mChangeLogQuery->QueryChangedEntries(NS_ConvertUTF16toUTF8(*(mEntriesToAdd[mEntriesAddedQueryCount])));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if(mListener && NS_SUCCEEDED(rv))
+ mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, true);
+
+ mState = kReplicatingChanges;
+ return rv;
+}
+
+nsresult nsAbLDAPProcessChangeLogData::OnReplicatingChangeDone()
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+
+ if(!mEntriesAddedQueryCount)
+ {
+ if(mReplicationDB && mDBOpen) {
+ rv = mReplicationDB->Close(true); // Commit and close the DB
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB Close (commit) on Success failed");
+ mDBOpen = false;
+ }
+ // Once we done with the replication file, delete the backup file.
+ if(mBackupReplicationFile) {
+ rv = mBackupReplicationFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication BackupFile Remove on Success failed");
+ }
+ Done(true); // All data is received
+ return NS_OK;
+ }
+
+ // Remove the entry already added from the list and query the next one.
+ if(mEntriesAddedQueryCount < mEntriesToAdd.Count() && mEntriesAddedQueryCount >= 0)
+ mEntriesToAdd.RemoveStringAt(mEntriesAddedQueryCount);
+ mEntriesAddedQueryCount--;
+ rv = mChangeLogQuery->QueryChangedEntries(NS_ConvertUTF16toUTF8(*(mEntriesToAdd[mEntriesAddedQueryCount])));
+
+ return rv;
+}
+
diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogData.h b/mailnews/addrbook/src/nsAbLDAPChangeLogData.h
new file mode 100644
index 000000000..9300e34ec
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPChangeLogData.h
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPChangeLogData_h__
+#define nsAbLDAPChangeLogData_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbLDAPReplicationData.h"
+#include "nsAbLDAPChangeLogQuery.h"
+
+typedef struct {
+ nsCString changeLogDN;
+ int32_t firstChangeNumber;
+ int32_t lastChangeNumber;
+ nsCString dataVersion;
+} RootDSEChangeLogEntry;
+
+class nsAbLDAPProcessChangeLogData : public nsAbLDAPProcessReplicationData
+{
+public :
+
+ nsAbLDAPProcessChangeLogData();
+
+ NS_IMETHOD Init(nsIAbLDAPReplicationQuery * query, nsIWebProgressListener *progressListener);
+
+protected :
+ ~nsAbLDAPProcessChangeLogData();
+
+ nsCOMPtr <nsIAbLDAPChangeLogQuery> mChangeLogQuery;
+
+ nsresult OnLDAPBind(nsILDAPMessage *aMessage);
+ nsresult OnLDAPSearchEntry(nsILDAPMessage *aMessage) override;
+ nsresult OnLDAPSearchResult(nsILDAPMessage *aMessage) override;
+
+ nsresult ParseChangeLogEntries(nsILDAPMessage *aMessage);
+ nsresult ParseRootDSEEntry(nsILDAPMessage *aMessage);
+
+ nsresult GetAuthData(); // displays username and password prompt
+ nsCString mAuthUserID; // user id of the user making the connection
+
+ nsresult OnSearchAuthDNDone();
+ nsresult OnSearchRootDSEDone();
+ nsresult OnFindingChangesDone();
+ nsresult OnReplicatingChangeDone();
+
+ RootDSEChangeLogEntry mRootDSEEntry;
+ bool mUseChangeLog;
+ int32_t mChangeLogEntriesCount;
+
+ int32_t mEntriesAddedQueryCount;
+ nsStringArray mEntriesToAdd;
+};
+
+
+#endif // nsAbLDAPChangeLogData_h__
+
diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp
new file mode 100644
index 000000000..1bdf474d6
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsCOMPtr.h"
+#include "nsAbLDAPChangeLogQuery.h"
+#include "nsAbLDAPReplicationService.h"
+#include "nsAbLDAPChangeLogData.h"
+#include "nsAbUtils.h"
+#include "prprf.h"
+#include "nsDirPrefs.h"
+#include "nsAbBaseCID.h"
+
+
+// The tables below were originally in nsAbLDAPProperties.cpp, which has since
+// gone away.
+static const char * sChangeLogRootDSEAttribs[] =
+{
+ "changelog",
+ "firstChangeNumber",
+ "lastChangeNumber",
+ "dataVersion"
+};
+static const char * sChangeLogEntryAttribs[] =
+{
+ "targetdn",
+ "changetype"
+};
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbLDAPChangeLogQuery, nsAbLDAPReplicationQuery, nsIAbLDAPChangeLogQuery)
+
+nsAbLDAPChangeLogQuery::nsAbLDAPChangeLogQuery()
+{
+}
+
+nsAbLDAPChangeLogQuery::~nsAbLDAPChangeLogQuery()
+{
+
+}
+
+// this is to be defined only till this is not hooked to SSL to get authDN and authPswd
+#define USE_AUTHDLG
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::Init(const nsACString & aPrefName, nsIWebProgressListener *aProgressListener)
+{
+ if(aPrefName.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ mDirPrefName = aPrefName;
+
+ nsresult rv = InitLDAPData();
+ if(NS_FAILED(rv))
+ return rv;
+
+ // create the ChangeLog Data Processor
+ mDataProcessor = do_CreateInstance(NS_ABLDAP_PROCESSCHANGELOGDATA_CONTRACTID, &rv);
+ if(NS_FAILED(rv))
+ return rv;
+
+ // 'this' initialized
+ mInitialized = true;
+
+ return mDataProcessor->Init(this, aProgressListener);
+}
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::DoReplicationQuery()
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+#ifdef USE_AUTHDLG
+ return ConnectToLDAPServer(mURL, EmptyCString());
+#else
+ mDataProcessor->PopulateAuthData();
+ return ConnectToLDAPServer(mURL, mAuthDN);
+#endif
+}
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryAuthDN(const nsACString & aValueUsedToFindDn)
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
+ nsresult rv = mDirectory->GetAttributeMap(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString filter;
+ rv = attrMap->GetFirstAttribute(NS_LITERAL_CSTRING("PrimaryEmail"), filter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ filter += '=';
+ filter += aValueUsedToFindDn;
+
+ nsAutoCString dn;
+ rv = mURL->GetDn(dn);
+ if(NS_FAILED(rv))
+ return rv;
+
+ rv = CreateNewLDAPOperation();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX We really should be using LDAP_NO_ATTRS here once its exposed via
+ // the XPCOM layer of the directory code.
+ return mOperation->SearchExt(dn, nsILDAPURL::SCOPE_SUBTREE, filter,
+ 0, nullptr,
+ 0, 0);
+}
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryRootDSE()
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = CreateNewLDAPOperation();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mOperation->SearchExt(EmptyCString(), nsILDAPURL::SCOPE_BASE,
+ NS_LITERAL_CSTRING("objectclass=*"),
+ sizeof(sChangeLogRootDSEAttribs),
+ sChangeLogRootDSEAttribs, 0, 0);
+}
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryChangeLog(const nsACString & aChangeLogDN, int32_t aLastChangeNo)
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+ if (aChangeLogDN.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ int32_t lastChangeNumber;
+ nsresult rv = mDirectory->GetLastChangeNumber(&lastChangeNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make sure that the filter here just have one condition
+ // and should not be enclosed in enclosing brackets.
+ // also condition '>' doesnot work, it should be '>='/
+ nsAutoCString filter (NS_LITERAL_CSTRING("changenumber>="));
+ filter.AppendInt(lastChangeNumber + 1);
+
+ rv = CreateNewLDAPOperation();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mOperation->SearchExt(aChangeLogDN, nsILDAPURL::SCOPE_ONELEVEL, filter,
+ sizeof(sChangeLogEntryAttribs),
+ sChangeLogEntryAttribs, 0, 0);
+}
+
+NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryChangedEntries(const nsACString & aChangedEntryDN)
+{
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+ if(aChangedEntryDN.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ nsAutoCString urlFilter;
+ nsresult rv = mURL->GetFilter(urlFilter);
+ if(NS_FAILED(rv))
+ return rv;
+
+ int32_t scope;
+ rv = mURL->GetScope(&scope);
+ if(NS_FAILED(rv))
+ return rv;
+
+ CharPtrArrayGuard attributes;
+ rv = mURL->GetAttributes(attributes.GetSizeAddr(), attributes.GetArrayAddr());
+ if(NS_FAILED(rv))
+ return rv;
+
+ rv = CreateNewLDAPOperation();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mOperation->SearchExt(aChangedEntryDN, scope, urlFilter,
+ attributes.GetSize(), attributes.GetArray(),
+ 0, 0);
+}
+
diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h
new file mode 100644
index 000000000..9652f470e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPChangeLogQuery_h__
+#define nsAbLDAPChangeLogQuery_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbLDAPReplicationQuery.h"
+#include "nsStringGlue.h"
+
+class nsAbLDAPChangeLogQuery : public nsIAbLDAPChangeLogQuery,
+ public nsAbLDAPReplicationQuery
+{
+public :
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABLDAPCHANGELOGQUERY
+
+ nsAbLDAPChangeLogQuery();
+ virtual ~nsAbLDAPChangeLogQuery();
+
+ NS_IMETHOD DoReplicationQuery() override;
+ NS_IMETHOD Init(const nsACString & aPrefName, nsIWebProgressListener *aProgressListener);
+};
+
+#endif // nsAbLDAPChangeLogQuery_h__
diff --git a/mailnews/addrbook/src/nsAbLDAPDirFactory.cpp b/mailnews/addrbook/src/nsAbLDAPDirFactory.cpp
new file mode 100644
index 000000000..299f2a953
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirFactory.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPDirFactory.h"
+#include "nsAbUtils.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsIAbManager.h"
+#include "nsIAbDirectory.h"
+#include "nsAbLDAPDirectory.h"
+
+#include "nsEnumeratorUtils.h"
+#include "nsAbBaseCID.h"
+
+NS_IMPL_ISUPPORTS(nsAbLDAPDirFactory, nsIAbDirFactory)
+
+nsAbLDAPDirFactory::nsAbLDAPDirFactory()
+{
+}
+
+nsAbLDAPDirFactory::~nsAbLDAPDirFactory()
+{
+}
+
+NS_IMETHODIMP
+nsAbLDAPDirFactory::GetDirectories(const nsAString &aDirName,
+ const nsACString &aURI,
+ const nsACString &aPrefName,
+ nsISimpleEnumerator **aDirectories)
+{
+ NS_ENSURE_ARG_POINTER(aDirectories);
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ if (Substring(aURI, 0, 5).EqualsLiteral("ldap:") ||
+ Substring(aURI, 0, 6).EqualsLiteral("ldaps:")) {
+ /*
+ * If the URI starts with ldap: or ldaps:
+ * then this directory is an LDAP directory.
+ *
+ * We don't want to use the ldap:// or ldaps:// URI
+ * as the URI because the ldap:// or ldaps:// URI
+ * will contain the hostname, basedn, port, etc.
+ * so if those attributes changed, we'll run into the
+ * the same problem that we hit with changing username / hostname
+ * for mail servers. To solve this problem, we add an extra
+ * level of indirection. The URI that we generate
+ * (the bridge URI) will be moz-abldapdirectory://<prefName>
+ * and when we need the hostname, basedn, port, etc,
+ * we'll use the <prefName> to get the necessary prefs.
+ * note, <prefName> does not change.
+ */
+ nsAutoCString bridgeURI;
+ bridgeURI = NS_LITERAL_CSTRING(kLDAPDirectoryRoot);
+ bridgeURI += aPrefName;
+ rv = abManager->GetDirectory(bridgeURI, getter_AddRefs(directory));
+ }
+ else {
+ rv = abManager->GetDirectory(aURI, getter_AddRefs(directory));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewSingletonEnumerator(aDirectories, directory);
+}
+
+/* void deleteDirectory (in nsIAbDirectory directory); */
+NS_IMETHODIMP
+nsAbLDAPDirFactory::DeleteDirectory(nsIAbDirectory *directory)
+{
+ // No actual deletion - as the LDAP Address Book is not physically
+ // created in the corresponding CreateDirectory() unlike the Personal
+ // Address Books. But we still need to return NS_OK from here.
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPDirFactory.h b/mailnews/addrbook/src/nsAbLDAPDirFactory.h
new file mode 100644
index 000000000..9285fbc05
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirFactory.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPDirFactory_h__
+#define nsAbLDAPDirFactory_h__
+
+#include "nsIAbDirFactory.h"
+
+class nsAbLDAPDirFactory : public nsIAbDirFactory
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRFACTORY
+
+ nsAbLDAPDirFactory();
+
+private:
+ virtual ~nsAbLDAPDirFactory();
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectory.cpp b/mailnews/addrbook/src/nsAbLDAPDirectory.cpp
new file mode 100644
index 000000000..480de0564
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectory.cpp
@@ -0,0 +1,949 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPDirectory.h"
+
+#include "nsAbQueryStringToExpression.h"
+
+#include "nsAbBaseCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsIIOService.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsIAbManager.h"
+#include "nsIAddrDatabase.h"
+#include "nsILDAPURL.h"
+#include "nsILDAPConnection.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsILDAPModification.h"
+#include "nsILDAPService.h"
+#include "nsIAbLDAPCard.h"
+#include "nsAbUtils.h"
+#include "nsArrayUtils.h"
+#include "nsIPrefService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+#define kDefaultMaxHits 100
+
+using namespace mozilla;
+
+nsAbLDAPDirectory::nsAbLDAPDirectory() :
+ nsAbDirProperty(),
+ mPerformingQuery(false),
+ mContext(0),
+ mLock("nsAbLDAPDirectory.mLock")
+{
+}
+
+nsAbLDAPDirectory::~nsAbLDAPDirectory()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbLDAPDirectory, nsAbDirProperty,
+ nsISupportsWeakReference, nsIAbDirSearchListener,
+ nsIAbLDAPDirectory)
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetPropertiesChromeURI(nsACString &aResult)
+{
+ aResult.AssignLiteral("chrome://messenger/content/addressbook/pref-directory-add.xul");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::Init(const char* aURI)
+{
+ // We need to ensure that the m_DirPrefId is initialized properly
+ nsAutoCString uri(aURI);
+
+ // Find the first ? (of the search params) if there is one.
+ // We know we can start at the end of the moz-abldapdirectory:// because
+ // that's the URI we should have been passed.
+ int32_t searchCharLocation = uri.FindChar('?', kLDAPDirectoryRootLen);
+
+ if (searchCharLocation == -1)
+ m_DirPrefId = Substring(uri, kLDAPDirectoryRootLen);
+ else
+ m_DirPrefId = Substring(uri, kLDAPDirectoryRootLen, searchCharLocation - kLDAPDirectoryRootLen);
+
+ return nsAbDirProperty::Init(aURI);
+}
+
+nsresult nsAbLDAPDirectory::Initiate()
+{
+ return NS_OK;
+}
+
+/*
+ *
+ * nsIAbDirectory methods
+ *
+ */
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetChildNodes(nsISimpleEnumerator* *aResult)
+{
+ return NS_NewEmptyEnumerator(aResult);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetChildCards(nsISimpleEnumerator** result)
+{
+ nsresult rv;
+
+ // when offline, we need to get the child cards for the local, replicated mdb directory
+ bool offline;
+ nsCOMPtr <nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+ rv = ioService->GetOffline(&offline);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (offline) {
+ nsCString fileName;
+ rv = GetReplicationFileName(fileName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if there is no fileName, bail out now.
+ if (fileName.IsEmpty())
+ return NS_OK;
+
+ // perform the same query, but on the local directory
+ nsAutoCString localDirectoryURI(NS_LITERAL_CSTRING(kMDBDirectoryRoot));
+ localDirectoryURI.Append(fileName);
+ if (mIsQueryURI)
+ {
+ localDirectoryURI.AppendLiteral("?");
+ localDirectoryURI.Append(mQueryString);
+ }
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(localDirectoryURI,
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->GetChildCards(result);
+ }
+ else {
+ // Start the search
+ rv = StartSearch();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewEmptyEnumerator(result);
+ }
+
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetIsQuery(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mIsQueryURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::HasCard(nsIAbCard* card, bool* hasCard)
+{
+ nsresult rv = Initiate ();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Enter lock
+ MutexAutoLock lock (mLock);
+
+ *hasCard = mCache.Get(card, nullptr);
+ if (!*hasCard && mPerformingQuery)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetLDAPURL(nsILDAPURL** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // Rather than using GetURI here we call GetStringValue directly so
+ // we can handle the case where the URI isn't specified (see comments
+ // below)
+ nsAutoCString URI;
+ nsresult rv = GetStringValue("uri", EmptyCString(), URI);
+ if (NS_FAILED(rv) || URI.IsEmpty())
+ {
+ /*
+ * A recent change in Mozilla now means that the LDAP Address Book
+ * URI is based on the unique preference name value i.e.
+ * [moz-abldapdirectory://prefName]
+ * Prior to this valid change it was based on the actual uri i.e.
+ * [moz-abldapdirectory://host:port/basedn]
+ * Basing the resource on the prefName allows these attributes to
+ * change.
+ *
+ * But the uri value was also the means by which third-party
+ * products could integrate with Mozilla's LDAP Address Books
+ * without necessarily having an entry in the preferences file
+ * or more importantly needing to be able to change the
+ * preferences entries. Thus to set the URI Spec now, it is
+ * only necessary to read the uri pref entry, while in the
+ * case where it is not a preference, we need to replace the
+ * "moz-abldapdirectory".
+ */
+ URI = mURINoQuery;
+ if (StringBeginsWith(URI, NS_LITERAL_CSTRING(kLDAPDirectoryRoot)))
+ URI.Replace(0, kLDAPDirectoryRootLen, NS_LITERAL_CSTRING("ldap://"));
+ }
+
+ nsCOMPtr<nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIURI> result;
+ rv = ioService->NewURI(URI, nullptr, nullptr, getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(result, aResult);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetLDAPURL(nsILDAPURL *aUrl)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+
+ nsAutoCString oldUrl;
+ // Note, it doesn't matter if GetStringValue fails - we'll just send an
+ // update if its blank (i.e. old value not set).
+ GetStringValue("uri", EmptyCString(), oldUrl);
+
+ // Actually set the new value.
+ nsCString tempLDAPURL;
+ nsresult rv = aUrl->GetSpec(tempLDAPURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetStringValue("uri", tempLDAPURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now we need to send an update which will ensure our indicators and
+ // listeners get updated correctly.
+
+ // See if they both start with ldaps: or ldap:
+ bool newIsNotSecure = StringHead(tempLDAPURL, 5).Equals("ldap:");
+
+ if (oldUrl.IsEmpty() ||
+ StringHead(oldUrl, 5).Equals("ldap:") != newIsNotSecure)
+ {
+ // They don't so its time to send round an update.
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We inherit from nsIAbDirectory, so this static cast should be safe.
+ abManager->NotifyItemPropertyChanged(static_cast<nsIAbDirectory*>(this),
+ "IsSecure",
+ (newIsNotSecure ? u"true" : u"false"),
+ (newIsNotSecure ? u"false" : u"true"));
+ }
+
+ return NS_OK;
+}
+
+/*
+ *
+ * nsIAbDirectorySearch methods
+ *
+ */
+
+NS_IMETHODIMP nsAbLDAPDirectory::StartSearch ()
+{
+ if (!mIsQueryURI || mQueryString.IsEmpty())
+ return NS_OK;
+
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = StopSearch();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> expression;
+ rv = nsAbQueryStringToExpression::Convert(mQueryString,
+ getter_AddRefs(expression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetExpression(expression);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetQuerySubDirectories(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the max hits to return
+ int32_t maxHits;
+ rv = GetMaxHits(&maxHits);
+ if (NS_FAILED(rv))
+ maxHits = kDefaultMaxHits;
+
+ // get the appropriate ldap attribute map, and pass it in via the
+ // TypeSpecificArgument
+ nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
+ rv = GetAttributeMap(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> typeSpecificArg = do_QueryInterface(attrMap, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetTypeSpecificArg(attrMap);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mDirectoryQuery)
+ {
+ mDirectoryQuery = do_CreateInstance(NS_ABLDAPDIRECTORYQUERY_CONTRACTID,
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Perform the query
+ rv = mDirectoryQuery->DoQuery(this, arguments, this, maxHits, 0, &mContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Enter lock
+ MutexAutoLock lock(mLock);
+ mPerformingQuery = true;
+ mCache.Clear();
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::StopSearch ()
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Enter lock
+ {
+ MutexAutoLock lockGuard(mLock);
+ if (!mPerformingQuery)
+ return NS_OK;
+ mPerformingQuery = false;
+ }
+ // Exit lock
+
+ if (!mDirectoryQuery)
+ return NS_ERROR_NULL_POINTER;
+
+ return mDirectoryQuery->StopQuery(mContext);
+}
+
+/*
+ *
+ * nsAbDirSearchListenerContext methods
+ *
+ */
+NS_IMETHODIMP nsAbLDAPDirectory::OnSearchFinished(int32_t aResult, const nsAString &aErrorMessage)
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+ mPerformingQuery = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::OnSearchFoundCard(nsIAbCard* card)
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Enter lock
+ {
+ MutexAutoLock lock(mLock);
+ mCache.Put(card, card);
+ }
+ // Exit lock
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if(NS_SUCCEEDED(rv))
+ abManager->NotifyDirectoryItemAdded(this, card);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetSupportsMailingLists(bool *aSupportsMailingsLists)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsMailingsLists);
+ *aSupportsMailingsLists = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetReadOnly(bool *aReadOnly)
+{
+ NS_ENSURE_ARG_POINTER(aReadOnly);
+
+ *aReadOnly = true;
+
+#ifdef MOZ_EXPERIMENTAL_WRITEABLE_LDAP
+ bool readOnly;
+ nsresult rv = GetBoolValue("readonly", false, &readOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (readOnly)
+ return NS_OK;
+
+ // when online, we'll allow writing as well
+ bool offline;
+ nsCOMPtr <nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+
+ rv = ioService->GetOffline(&offline);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!offline)
+ *aReadOnly = false;
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetIsRemote(bool *aIsRemote)
+{
+ NS_ENSURE_ARG_POINTER(aIsRemote);
+ *aIsRemote = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetIsSecure(bool *aIsSecure)
+{
+ NS_ENSURE_ARG_POINTER(aIsSecure);
+
+ nsAutoCString URI;
+ nsresult rv = GetStringValue("uri", EmptyCString(), URI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // to determine if this is a secure directory, check if the uri is ldaps:// or not
+ *aIsSecure = (strncmp(URI.get(), "ldaps:", 6) == 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::UseForAutocomplete(const nsACString &aIdentityKey,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // Set this to false by default to make the code easier below.
+ *aResult = false;
+
+ nsresult rv;
+ bool offline = false;
+ nsCOMPtr <nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+
+ rv = ioService->GetOffline(&offline);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we're online, then don't allow search during local autocomplete - must
+ // use the separate LDAP autocomplete session due to the current interfaces
+ if (!offline)
+ return NS_OK;
+
+ // Is the use directory pref set for autocompletion?
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool useDirectory = false;
+ rv = prefs->GetBoolPref("ldap_2.autoComplete.useDirectory", &useDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // No need to search if not set up globally for LDAP autocompletion and we've
+ // not been given an identity.
+ if (!useDirectory && aIdentityKey.IsEmpty())
+ return NS_OK;
+
+ nsCString prefName;
+ if (!aIdentityKey.IsEmpty())
+ {
+ // If we have an identity string, try and find out the required directory
+ // server.
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+
+ // If we failed, just return, we can't do much about this.
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = accountManager->GetIdentity(aIdentityKey, getter_AddRefs(identity));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool overrideGlobalPref = false;
+ identity->GetOverrideGlobalPref(&overrideGlobalPref);
+ if (overrideGlobalPref)
+ identity->GetDirectoryServer(prefName);
+ }
+ }
+
+ // If the preference name is still empty but useDirectory is false, then
+ // the global one is not available, nor is the overriden one.
+ if (prefName.IsEmpty() && !useDirectory)
+ return NS_OK;
+ }
+
+ // If we failed to get the identity preference, or the pref name is empty
+ // try the global preference.
+ if (prefName.IsEmpty())
+ {
+ nsresult rv = prefs->GetCharPref("ldap_2.autoComplete.directoryServer",
+ getter_Copies(prefName));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // Now see if the pref name matches our pref id.
+ if (prefName.Equals(m_DirPrefId))
+ {
+ // Yes it does, one last check - does the replication file exist?
+ nsresult rv;
+ nsCOMPtr<nsIFile> databaseFile;
+ // If we can't get the file, then there is no database to use
+ if (NS_FAILED(GetReplicationFile(getter_AddRefs(databaseFile))))
+ return NS_OK;
+
+ bool exists;
+ rv = databaseFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = exists;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetSearchClientControls(nsIMutableArray **aControls)
+{
+ NS_IF_ADDREF(*aControls = mSearchClientControls);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetSearchClientControls(nsIMutableArray *aControls)
+{
+ mSearchClientControls = aControls;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetSearchServerControls(nsIMutableArray **aControls)
+{
+ NS_IF_ADDREF(*aControls = mSearchServerControls);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetSearchServerControls(nsIMutableArray *aControls)
+{
+ mSearchServerControls = aControls;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetProtocolVersion(uint32_t *aProtocolVersion)
+{
+ nsAutoCString versionString;
+
+ nsresult rv = GetStringValue("protocolVersion", NS_LITERAL_CSTRING("3"), versionString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aProtocolVersion = versionString.EqualsLiteral("3") ?
+ (uint32_t)nsILDAPConnection::VERSION3 :
+ (uint32_t)nsILDAPConnection::VERSION2;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetProtocolVersion(uint32_t aProtocolVersion)
+{
+ // XXX We should cancel any existing LDAP connections here and
+ // be ready to re-initialise them with the new auth details.
+ return SetStringValue("protocolVersion",
+ aProtocolVersion == nsILDAPConnection::VERSION3 ?
+ NS_LITERAL_CSTRING("3") : NS_LITERAL_CSTRING("2"));
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetMaxHits(int32_t *aMaxHits)
+{
+ return GetIntValue("maxHits", kDefaultMaxHits, aMaxHits);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetMaxHits(int32_t aMaxHits)
+{
+ return SetIntValue("maxHits", aMaxHits);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetReplicationFileName(nsACString &aReplicationFileName)
+{
+ return GetStringValue("filename", EmptyCString(), aReplicationFileName);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetReplicationFileName(const nsACString &aReplicationFileName)
+{
+ return SetStringValue("filename", aReplicationFileName);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetAuthDn(nsACString &aAuthDn)
+{
+ return GetStringValue("auth.dn", EmptyCString(), aAuthDn);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetAuthDn(const nsACString &aAuthDn)
+{
+ // XXX We should cancel any existing LDAP connections here and
+ // be ready to re-initialise them with the new auth details.
+ return SetStringValue("auth.dn", aAuthDn);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetSaslMechanism(nsACString &aSaslMechanism)
+{
+ return GetStringValue("auth.saslmech", EmptyCString(), aSaslMechanism);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetSaslMechanism(const nsACString &aSaslMechanism)
+{
+ return SetStringValue("auth.saslmech", aSaslMechanism);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetLastChangeNumber(int32_t *aLastChangeNumber)
+{
+ return GetIntValue("lastChangeNumber", -1, aLastChangeNumber);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetLastChangeNumber(int32_t aLastChangeNumber)
+{
+ return SetIntValue("lastChangeNumber", aLastChangeNumber);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetDataVersion(nsACString &aDataVersion)
+{
+ return GetStringValue("dataVersion", EmptyCString(), aDataVersion);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetDataVersion(const nsACString &aDataVersion)
+{
+ return SetStringValue("dataVersion", aDataVersion);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetAttributeMap(nsIAbLDAPAttributeMap **aAttributeMap)
+{
+ NS_ENSURE_ARG_POINTER(aAttributeMap);
+
+ nsresult rv;
+ nsCOMPtr<nsIAbLDAPAttributeMapService> mapSvc =
+ do_GetService("@mozilla.org/addressbook/ldap-attribute-map-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mapSvc->GetMapForPrefBranch(m_DirPrefId, aAttributeMap);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetReplicationFile(nsIFile **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCString fileName;
+ nsresult rv = GetStringValue("filename", EmptyCString(), fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fileName.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIFile> profileDir;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = profileDir->AppendNative(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = profileDir);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetReplicationDatabase(nsIAddrDatabase **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> databaseFile;
+ rv = GetReplicationFile(getter_AddRefs(databaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory =
+ do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return addrDBFactory->Open(databaseFile, false /* no create */, true,
+ aResult);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::AddCard(nsIAbCard *aUpdatedCard,
+ nsIAbCard **aAddedCard)
+{
+ NS_ENSURE_ARG_POINTER(aUpdatedCard);
+ NS_ENSURE_ARG_POINTER(aAddedCard);
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
+ nsresult rv = GetAttributeMap(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a new LDAP card
+ nsCOMPtr<nsIAbLDAPCard> card =
+ do_CreateInstance(NS_ABLDAPCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy over the card data
+ nsCOMPtr<nsIAbCard> copyToCard = do_QueryInterface(card, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = copyToCard->Copy(aUpdatedCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Retrieve preferences
+ nsAutoCString prefString;
+ rv = GetRdnAttributes(prefString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CharPtrArrayGuard rdnAttrs;
+ rv = SplitStringList(prefString, rdnAttrs.GetSizeAddr(),
+ rdnAttrs.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetObjectClasses(prefString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CharPtrArrayGuard objClass;
+ rv = SplitStringList(prefString, objClass.GetSizeAddr(),
+ objClass.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Process updates
+ nsCOMPtr<nsIArray> modArray;
+ rv = card->GetLDAPMessageInfo(attrMap, objClass.GetSize(), objClass.GetArray(),
+ nsILDAPModification::MOD_ADD, getter_AddRefs(modArray));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // For new cards, the base DN is the search base DN
+ nsCOMPtr<nsILDAPURL> currentUrl;
+ rv = GetLDAPURL(getter_AddRefs(currentUrl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString baseDN;
+ rv = currentUrl->GetDn(baseDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Calculate DN
+ nsAutoCString cardDN;
+ rv = card->BuildRdn(attrMap, rdnAttrs.GetSize(), rdnAttrs.GetArray(),
+ cardDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+ cardDN.AppendLiteral(",");
+ cardDN.Append(baseDN);
+
+ rv = card->SetDn(cardDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+ copyToCard->SetDirectoryId(ourUuid);
+
+ // Launch query
+ rv = DoModify(this, nsILDAPModification::MOD_ADD, cardDN, modArray,
+ EmptyCString(), EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aAddedCard = copyToCard);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::DeleteCards(nsIArray *aCards)
+{
+ uint32_t cardCount;
+ uint32_t i;
+ nsAutoCString cardDN;
+
+ nsresult rv = aCards->GetLength(&cardCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (i = 0; i < cardCount; ++i)
+ {
+ nsCOMPtr<nsIAbLDAPCard> card(do_QueryElementAt(aCards, i, &rv));
+ if (NS_FAILED(rv))
+ {
+ NS_WARNING("Wrong type of card passed to nsAbLDAPDirectory::DeleteCards");
+ break;
+ }
+
+ // Set up the search ldap url - this is mURL
+ rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = card->GetDn(cardDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> realCard(do_QueryInterface(card));
+ realCard->SetDirectoryId(EmptyCString());
+
+ // Launch query
+ rv = DoModify(this, nsILDAPModification::MOD_DELETE, cardDN, nullptr,
+ EmptyCString(), EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::ModifyCard(nsIAbCard *aUpdatedCard)
+{
+ NS_ENSURE_ARG_POINTER(aUpdatedCard);
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
+ nsresult rv = GetAttributeMap(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the LDAP card
+ nsCOMPtr<nsIAbLDAPCard> card = do_QueryInterface(aUpdatedCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Retrieve preferences
+ nsAutoCString prefString;
+ rv = GetObjectClasses(prefString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CharPtrArrayGuard objClass;
+ rv = SplitStringList(prefString, objClass.GetSizeAddr(),
+ objClass.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Process updates
+ nsCOMPtr<nsIArray> modArray;
+ rv = card->GetLDAPMessageInfo(attrMap, objClass.GetSize(), objClass.GetArray(),
+ nsILDAPModification::MOD_REPLACE, getter_AddRefs(modArray));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get current DN
+ nsAutoCString oldDN;
+ rv = card->GetDn(oldDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILDAPService> ldapSvc = do_GetService(
+ "@mozilla.org/network/ldap-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Retrieve base DN and RDN attributes
+ nsAutoCString baseDN;
+ nsAutoCString oldRDN;
+ CharPtrArrayGuard rdnAttrs;
+ rv = ldapSvc->ParseDn(oldDN.get(), oldRDN, baseDN,
+ rdnAttrs.GetSizeAddr(), rdnAttrs.GetArrayAddr());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Calculate new RDN and check whether it has changed
+ nsAutoCString newRDN;
+ rv = card->BuildRdn(attrMap, rdnAttrs.GetSize(), rdnAttrs.GetArray(),
+ newRDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (newRDN.Equals(oldRDN))
+ {
+ // Launch query
+ rv = DoModify(this, nsILDAPModification::MOD_REPLACE, oldDN, modArray,
+ EmptyCString(), EmptyCString());
+ }
+ else
+ {
+ // Build and store the new DN
+ nsAutoCString newDN(newRDN);
+ newDN.AppendLiteral(",");
+ newDN.Append(baseDN);
+
+ rv = card->SetDn(newDN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Launch query
+ rv = DoModify(this, nsILDAPModification::MOD_REPLACE, oldDN, modArray,
+ newRDN, baseDN);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetRdnAttributes(nsACString &aRdnAttributes)
+{
+ return GetStringValue("rdnAttributes", NS_LITERAL_CSTRING("cn"),
+ aRdnAttributes);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetRdnAttributes(const nsACString &aRdnAttributes)
+{
+ return SetStringValue("rdnAttributes", aRdnAttributes);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::GetObjectClasses(nsACString &aObjectClasses)
+{
+ return GetStringValue("objectClasses", NS_LITERAL_CSTRING(
+ "top,person,organizationalPerson,inetOrgPerson,mozillaAbPersonAlpha"),
+ aObjectClasses);
+}
+
+NS_IMETHODIMP nsAbLDAPDirectory::SetObjectClasses(const nsACString &aObjectClasses)
+{
+ return SetStringValue("objectClasses", aObjectClasses);
+}
+
+nsresult nsAbLDAPDirectory::SplitStringList(
+ const nsACString& aString,
+ uint32_t *aCount,
+ char ***aValues)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+ NS_ENSURE_ARG_POINTER(aValues);
+
+ nsTArray<nsCString> strarr;
+ ParseString(aString, ',', strarr);
+
+ char **cArray = nullptr;
+ if (!(cArray = static_cast<char **>(moz_xmalloc(
+ strarr.Length() * sizeof(char *)))))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ for (uint32_t i = 0; i < strarr.Length(); ++i)
+ {
+ if (!(cArray[i] = ToNewCString(strarr[i])))
+ {
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(strarr.Length(), cArray);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ *aCount = strarr.Length();
+ *aValues = cArray;
+ return NS_OK;
+}
+
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectory.h b/mailnews/addrbook/src/nsAbLDAPDirectory.h
new file mode 100644
index 000000000..6e5279a97
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectory.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPDirectory_h__
+#define nsAbLDAPDirectory_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbDirProperty.h"
+#include "nsAbLDAPDirectoryModify.h"
+#include "nsIAbDirectoryQuery.h"
+#include "nsIAbDirectorySearch.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsIAbLDAPDirectory.h"
+#include "nsIMutableArray.h"
+#include "nsInterfaceHashtable.h"
+#include "mozilla/Mutex.h"
+
+class nsAbLDAPDirectory :
+ public nsAbDirProperty, // nsIAbDirectory
+ public nsAbLDAPDirectoryModify,
+ public nsIAbDirectorySearch,
+ public nsIAbLDAPDirectory,
+ public nsIAbDirSearchListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsAbLDAPDirectory();
+
+ NS_IMETHOD Init(const char *aUri) override;
+
+ // nsIAbDirectory methods
+ NS_IMETHOD GetPropertiesChromeURI(nsACString &aResult) override;
+ NS_IMETHOD GetURI(nsACString &aURI) override;
+ NS_IMETHOD GetChildNodes(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD GetChildCards(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD GetIsQuery(bool *aResult) override;
+ NS_IMETHOD HasCard(nsIAbCard *cards, bool *hasCard) override;
+ NS_IMETHOD GetSupportsMailingLists(bool *aSupportsMailingsLists) override;
+ NS_IMETHOD GetReadOnly(bool *aReadOnly) override;
+ NS_IMETHOD GetIsRemote(bool *aIsRemote) override;
+ NS_IMETHOD GetIsSecure(bool *aIsRemote) override;
+ NS_IMETHOD UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult) override;
+ NS_IMETHOD AddCard(nsIAbCard *aChildCard, nsIAbCard **aAddedCard) override;
+ NS_IMETHOD ModifyCard(nsIAbCard *aModifiedCard) override;
+ NS_IMETHOD DeleteCards(nsIArray *aCards) override;
+
+ // nsIAbDirectorySearch methods
+ NS_DECL_NSIABDIRECTORYSEARCH
+ NS_DECL_NSIABLDAPDIRECTORY
+ NS_DECL_NSIABDIRSEARCHLISTENER
+
+protected:
+ virtual ~nsAbLDAPDirectory();
+ nsresult Initiate();
+
+ nsresult SplitStringList(const nsACString& aString,
+ uint32_t *aCount,
+ char ***aValues);
+
+ bool mPerformingQuery;
+ int32_t mContext;
+ int32_t mMaxHits;
+
+ nsInterfaceHashtable<nsISupportsHashKey, nsIAbCard> mCache;
+
+ mozilla::Mutex mLock;
+ nsCOMPtr<nsIAbDirectoryQuery> mDirectoryQuery;
+ nsCOMPtr<nsIMutableArray> mSearchServerControls;
+ nsCOMPtr<nsIMutableArray> mSearchClientControls;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp
new file mode 100644
index 000000000..8322f6974
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp
@@ -0,0 +1,371 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPDirectoryModify.h"
+#include "nsILDAPMessage.h"
+#include "nsILDAPConnection.h"
+#include "nsILDAPErrors.h"
+#include "nsILDAPModification.h"
+#include "nsIAbLDAPDirectory.h"
+#include "nsIMutableArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include <stdio.h>
+
+using namespace mozilla;
+
+class nsAbModifyLDAPMessageListener : public nsAbLDAPListenerBase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsAbModifyLDAPMessageListener(const int32_t type,
+ const nsACString &cardDN,
+ nsIArray* modArray,
+ const nsACString &newRDN,
+ const nsACString &newBaseDN,
+ nsILDAPURL* directoryUrl,
+ nsILDAPConnection* connection,
+ nsIMutableArray* serverSearchControls,
+ nsIMutableArray* clientSearchControls,
+ const nsACString &login,
+ const int32_t timeOut = 0);
+ // nsILDAPMessageListener
+ NS_IMETHOD OnLDAPMessage(nsILDAPMessage *aMessage) override;
+
+protected:
+ virtual ~nsAbModifyLDAPMessageListener();
+
+ nsresult Cancel();
+ virtual void InitFailed(bool aCancelled = false) override;
+ virtual nsresult DoTask() override;
+ nsresult DoMainTask();
+ nsresult OnLDAPMessageModifyResult(nsILDAPMessage *aMessage);
+ nsresult OnLDAPMessageRenameResult(nsILDAPMessage *aMessage);
+
+ int32_t mType;
+ nsCString mCardDN;
+ nsCOMPtr<nsIArray> mModification;
+ nsCString mNewRDN;
+ nsCString mNewBaseDN;
+
+ bool mFinished;
+ bool mCanceled;
+ bool mFlagRename;
+
+ nsCOMPtr<nsILDAPOperation> mModifyOperation;
+ nsCOMPtr<nsIMutableArray> mServerSearchControls;
+ nsCOMPtr<nsIMutableArray> mClientSearchControls;
+};
+
+
+NS_IMPL_ISUPPORTS(nsAbModifyLDAPMessageListener, nsILDAPMessageListener)
+
+nsAbModifyLDAPMessageListener::nsAbModifyLDAPMessageListener(
+ const int32_t type,
+ const nsACString &cardDN,
+ nsIArray* modArray,
+ const nsACString &newRDN,
+ const nsACString &newBaseDN,
+ nsILDAPURL* directoryUrl,
+ nsILDAPConnection* connection,
+ nsIMutableArray* serverSearchControls,
+ nsIMutableArray* clientSearchControls,
+ const nsACString &login,
+ const int32_t timeOut) :
+ nsAbLDAPListenerBase(directoryUrl, connection, login, timeOut),
+ mType(type),
+ mCardDN(cardDN),
+ mModification(modArray),
+ mNewRDN(newRDN),
+ mNewBaseDN(newBaseDN),
+ mFinished(false),
+ mCanceled(false),
+ mFlagRename(false),
+ mServerSearchControls(serverSearchControls),
+ mClientSearchControls(clientSearchControls)
+{
+ if (mType == nsILDAPModification::MOD_REPLACE &&
+ !mNewRDN.IsEmpty() && !mNewBaseDN.IsEmpty())
+ mFlagRename = true;
+}
+
+nsAbModifyLDAPMessageListener::~nsAbModifyLDAPMessageListener ()
+{
+}
+
+nsresult nsAbModifyLDAPMessageListener::Cancel ()
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+ if (mFinished || mCanceled)
+ return NS_OK;
+
+ mCanceled = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbModifyLDAPMessageListener::OnLDAPMessage(nsILDAPMessage *aMessage)
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t messageType;
+ rv = aMessage->GetType(&messageType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool cancelOperation = false;
+
+ // Enter lock
+ {
+ MutexAutoLock lock (mLock);
+
+ if (mFinished)
+ return NS_OK;
+
+ // for these messages, no matter the outcome, we're done
+ if ((messageType == nsILDAPMessage::RES_ADD) ||
+ (messageType == nsILDAPMessage::RES_DELETE) ||
+ (messageType == nsILDAPMessage::RES_MODIFY))
+ mFinished = true;
+ else if (mCanceled)
+ {
+ mFinished = true;
+ cancelOperation = true;
+ }
+ }
+ // Leave lock
+
+ // nsCOMPtr<nsIAbDirectoryQueryResult> queryResult;
+ if (!cancelOperation)
+ {
+ switch (messageType)
+ {
+ case nsILDAPMessage::RES_BIND:
+ rv = OnLDAPMessageBind(aMessage);
+ if (NS_FAILED(rv))
+ // We know the bind failed and hence the message has an error, so we
+ // can just call ModifyResult with the message and that'll sort it out
+ // for us.
+ rv = OnLDAPMessageModifyResult(aMessage);
+ break;
+ case nsILDAPMessage::RES_ADD:
+ case nsILDAPMessage::RES_MODIFY:
+ case nsILDAPMessage::RES_DELETE:
+ rv = OnLDAPMessageModifyResult(aMessage);
+ break;
+ case nsILDAPMessage::RES_MODDN:
+ mFlagRename = false;
+ rv = OnLDAPMessageRenameResult(aMessage);
+ if (NS_FAILED(rv))
+ // Rename failed, so we stop here
+ mFinished = true;
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ if (mModifyOperation)
+ rv = mModifyOperation->AbandonExt();
+
+ // reset because we might re-use this listener...except don't do this
+ // until the search is done, so we'll ignore results from a previous
+ // search.
+ mCanceled = mFinished = false;
+ }
+
+ return rv;
+}
+
+void nsAbModifyLDAPMessageListener::InitFailed(bool aCancelled)
+{
+ // XXX Just cancel the operation for now
+ // we'll need to review this when we've got the proper listeners in place.
+ Cancel();
+}
+
+nsresult nsAbModifyLDAPMessageListener::DoTask()
+{
+ nsresult rv;
+ mCanceled = mFinished = false;
+
+ mModifyOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mModifyOperation->Init (mConnection, this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX do we need the search controls?
+ rv = mModifyOperation->SetServerControls(mServerSearchControls);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mModifyOperation->SetClientControls(mClientSearchControls);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFlagRename)
+ return mModifyOperation->Rename(mCardDN, mNewRDN, mNewBaseDN, true);
+
+ switch (mType)
+ {
+ case nsILDAPModification::MOD_ADD:
+ return mModifyOperation->AddExt(mCardDN, mModification);
+ case nsILDAPModification::MOD_DELETE:
+ return mModifyOperation->DeleteExt(mCardDN);
+ case nsILDAPModification::MOD_REPLACE:
+ return mModifyOperation->ModifyExt(mCardDN, mModification);
+ default:
+ NS_ERROR("Bad LDAP modification requested");
+ return NS_ERROR_UNEXPECTED;
+ }
+}
+
+nsresult nsAbModifyLDAPMessageListener::OnLDAPMessageModifyResult(nsILDAPMessage *aMessage)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ int32_t errCode;
+ rv = aMessage->GetErrorCode(&errCode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (errCode != nsILDAPErrors::SUCCESS)
+ {
+ nsAutoCString errMessage;
+ rv = aMessage->GetErrorMessage(errMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ printf("LDAP modification failed (code: %i, message: %s)\n",
+ errCode, errMessage.get());
+ return NS_ERROR_FAILURE;
+ }
+
+ printf("LDAP modification succeeded\n");
+ return NS_OK;
+}
+
+nsresult nsAbModifyLDAPMessageListener::OnLDAPMessageRenameResult(nsILDAPMessage *aMessage)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ int32_t errCode;
+ rv = aMessage->GetErrorCode(&errCode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (errCode != nsILDAPErrors::SUCCESS)
+ {
+ nsAutoCString errMessage;
+ rv = aMessage->GetErrorMessage(errMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ printf("LDAP rename failed (code: %i, message: %s)\n",
+ errCode, errMessage.get());
+ return NS_ERROR_FAILURE;
+ }
+
+ // Rename succeeded, now update the card DN and
+ // process the main task
+ mCardDN.Assign(mNewRDN);
+ mCardDN.AppendLiteral(",");
+ mCardDN.Append(mNewBaseDN);
+
+ printf("LDAP rename succeeded\n");
+ return DoTask();
+}
+
+nsAbLDAPDirectoryModify::nsAbLDAPDirectoryModify()
+{
+}
+
+nsAbLDAPDirectoryModify::~nsAbLDAPDirectoryModify()
+{
+}
+
+nsresult nsAbLDAPDirectoryModify::DoModify(nsIAbLDAPDirectory *directory,
+ const int32_t &updateType,
+ const nsACString &cardDN,
+ nsIArray* modArray,
+ const nsACString &newRDN,
+ const nsACString &newBaseDN)
+{
+ NS_ENSURE_ARG_POINTER(directory);
+ // modArray may be null in the delete operation case.
+ if (!modArray &&
+ (updateType == nsILDAPModification::MOD_ADD ||
+ updateType == nsILDAPModification::MOD_REPLACE))
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ // it's an error if we don't have a dn
+ if (cardDN.IsEmpty())
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsILDAPURL> currentUrl;
+ rv = directory->GetLDAPURL(getter_AddRefs(currentUrl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the ldap connection
+ nsCOMPtr<nsILDAPConnection> ldapConnection =
+ do_CreateInstance(NS_LDAPCONNECTION_CONTRACTID, &rv);
+
+ nsCOMPtr<nsIMutableArray> serverSearchControls;
+ rv = directory->GetSearchServerControls(getter_AddRefs(serverSearchControls));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> clientSearchControls;
+ rv = directory->GetSearchClientControls(getter_AddRefs(clientSearchControls));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /*
+ // XXX we need to fix how this all works - specifically, see the first patch
+ // on bug 124553 for how the query equivalent did this
+ // too soon? Do we need a new listener?
+ if (alreadyInitialized)
+ {
+ nsAbQueryLDAPMessageListener *msgListener =
+ NS_STATIC_CAST(nsAbQueryLDAPMessageListener *,
+ NS_STATIC_CAST(nsILDAPMessageListener *, mListener.get()));
+ if (msgListener)
+ {
+ msgListener->mUrl = url;
+ return msgListener->DoSearch();
+ }
+ }*/
+
+ nsCString login;
+ rv = directory->GetAuthDn(login);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t protocolVersion;
+ rv = directory->GetProtocolVersion(&protocolVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initiate LDAP message listener
+ nsAbModifyLDAPMessageListener* _messageListener =
+ new nsAbModifyLDAPMessageListener(updateType, cardDN, modArray,
+ newRDN, newBaseDN,
+ currentUrl,
+ ldapConnection,
+ serverSearchControls,
+ clientSearchControls,
+ login,
+ 0);
+ if (_messageListener == NULL)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Now lets initialize the LDAP connection properly. We'll kick
+ // off the bind operation in the callback function, |OnLDAPInit()|.
+ return ldapConnection->Init(currentUrl, login,
+ _messageListener, nullptr, protocolVersion);
+}
+
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryModify.h b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.h
new file mode 100644
index 000000000..c1a2f591f
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPDirectoryModify_h__
+#define nsAbLDAPDirectoryModify_h__
+
+#include "nsAbLDAPListenerBase.h"
+#include "nsIAbLDAPDirectory.h"
+#include "nsIArray.h"
+
+class nsILDAPURL;
+
+class nsAbLDAPDirectoryModify
+{
+public:
+ nsAbLDAPDirectoryModify();
+ virtual ~nsAbLDAPDirectoryModify();
+
+protected:
+ nsresult DoModify(nsIAbLDAPDirectory *directory,
+ const int32_t &aUpdateType,
+ const nsACString &aCardDN,
+ nsIArray* modArray,
+ const nsACString &aNewRDN,
+ const nsACString &aNewBaseDN);
+};
+
+#endif // nsAbLDAPDirectoryModify_h__
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp
new file mode 100644
index 000000000..18964a380
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp
@@ -0,0 +1,609 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPDirectoryQuery.h"
+#include "nsAbBoolExprToLDAPFilter.h"
+#include "nsILDAPMessage.h"
+#include "nsILDAPErrors.h"
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsIAbLDAPCard.h"
+#include "nsAbUtils.h"
+#include "nsAbBaseCID.h"
+#include "nsStringGlue.h"
+#include "prprf.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsAbLDAPDirectory.h"
+#include "nsAbLDAPListenerBase.h"
+#include "nsXPCOMCIDInternal.h"
+
+using namespace mozilla;
+
+// nsAbLDAPListenerBase inherits nsILDAPMessageListener
+class nsAbQueryLDAPMessageListener : public nsAbLDAPListenerBase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // Note that the directoryUrl is the details of the ldap directory
+ // without any search params or return attributes specified. The searchUrl
+ // therefore has the search params and return attributes specified.
+ // nsAbQueryLDAPMessageListener(nsIAbDirectoryQuery* directoryQuery,
+ nsAbQueryLDAPMessageListener(nsIAbDirectoryQueryResultListener* resultListener,
+ nsILDAPURL* directoryUrl,
+ nsILDAPURL* searchUrl,
+ nsILDAPConnection* connection,
+ nsIAbDirectoryQueryArguments* queryArguments,
+ nsIMutableArray* serverSearchControls,
+ nsIMutableArray* clientSearchControls,
+ const nsACString &login,
+ const nsACString &mechanism,
+ const int32_t resultLimit = -1,
+ const int32_t timeOut = 0);
+
+ // nsILDAPMessageListener
+ NS_IMETHOD OnLDAPMessage(nsILDAPMessage *aMessage) override;
+
+protected:
+ virtual ~nsAbQueryLDAPMessageListener ();
+ nsresult OnLDAPMessageSearchEntry(nsILDAPMessage *aMessage);
+ nsresult OnLDAPMessageSearchResult(nsILDAPMessage *aMessage);
+
+ friend class nsAbLDAPDirectoryQuery;
+
+ nsresult Cancel();
+ virtual nsresult DoTask() override;
+ virtual void InitFailed(bool aCancelled = false) override;
+
+ nsCOMPtr<nsILDAPURL> mSearchUrl;
+ nsIAbDirectoryQueryResultListener *mResultListener;
+ int32_t mContextID;
+ nsCOMPtr<nsIAbDirectoryQueryArguments> mQueryArguments;
+ int32_t mResultLimit;
+
+ bool mFinished;
+ bool mCanceled;
+ bool mWaitingForPrevQueryToFinish;
+
+ nsCOMPtr<nsIMutableArray> mServerSearchControls;
+ nsCOMPtr<nsIMutableArray> mClientSearchControls;
+};
+
+
+NS_IMPL_ISUPPORTS(nsAbQueryLDAPMessageListener, nsILDAPMessageListener)
+
+nsAbQueryLDAPMessageListener::nsAbQueryLDAPMessageListener(
+ nsIAbDirectoryQueryResultListener *resultListener,
+ nsILDAPURL* directoryUrl,
+ nsILDAPURL* searchUrl,
+ nsILDAPConnection* connection,
+ nsIAbDirectoryQueryArguments* queryArguments,
+ nsIMutableArray* serverSearchControls,
+ nsIMutableArray* clientSearchControls,
+ const nsACString &login,
+ const nsACString &mechanism,
+ const int32_t resultLimit,
+ const int32_t timeOut) :
+ nsAbLDAPListenerBase(directoryUrl, connection, login, timeOut),
+ mSearchUrl(searchUrl),
+ mResultListener(resultListener),
+ mQueryArguments(queryArguments),
+ mResultLimit(resultLimit),
+ mFinished(false),
+ mCanceled(false),
+ mWaitingForPrevQueryToFinish(false),
+ mServerSearchControls(serverSearchControls),
+ mClientSearchControls(clientSearchControls)
+{
+ mSaslMechanism.Assign(mechanism);
+}
+
+nsAbQueryLDAPMessageListener::~nsAbQueryLDAPMessageListener ()
+{
+}
+
+nsresult nsAbQueryLDAPMessageListener::Cancel ()
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+ if (mFinished || mCanceled)
+ return NS_OK;
+
+ mCanceled = true;
+ if (!mFinished)
+ mWaitingForPrevQueryToFinish = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbQueryLDAPMessageListener::OnLDAPMessage(nsILDAPMessage *aMessage)
+{
+ nsresult rv = Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t messageType;
+ rv = aMessage->GetType(&messageType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool cancelOperation = false;
+
+ // Enter lock
+ {
+ MutexAutoLock lock (mLock);
+
+ if (mFinished)
+ return NS_OK;
+
+ if (messageType == nsILDAPMessage::RES_SEARCH_RESULT)
+ mFinished = true;
+ else if (mCanceled)
+ {
+ mFinished = true;
+ cancelOperation = true;
+ }
+ }
+ // Leave lock
+
+ if (!mResultListener)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!cancelOperation)
+ {
+ switch (messageType)
+ {
+ case nsILDAPMessage::RES_BIND:
+ rv = OnLDAPMessageBind(aMessage);
+ if (NS_FAILED(rv))
+ // We know the bind failed and hence the message has an error, so we
+ // can just call SearchResult with the message and that'll sort it out
+ // for us.
+ rv = OnLDAPMessageSearchResult(aMessage);
+ break;
+ case nsILDAPMessage::RES_SEARCH_ENTRY:
+ if (!mFinished && !mWaitingForPrevQueryToFinish)
+ rv = OnLDAPMessageSearchEntry(aMessage);
+ break;
+ case nsILDAPMessage::RES_SEARCH_RESULT:
+ mWaitingForPrevQueryToFinish = false;
+ rv = OnLDAPMessageSearchResult(aMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ if (mOperation)
+ rv = mOperation->AbandonExt();
+
+ rv = mResultListener->OnQueryResult(
+ nsIAbDirectoryQueryResultListener::queryResultStopped, 0);
+
+ // reset because we might re-use this listener...except don't do this
+ // until the search is done, so we'll ignore results from a previous
+ // search.
+ if (messageType == nsILDAPMessage::RES_SEARCH_RESULT)
+ mCanceled = mFinished = false;
+ }
+
+ return rv;
+}
+
+nsresult nsAbQueryLDAPMessageListener::DoTask()
+{
+ nsresult rv;
+ mCanceled = mFinished = false;
+
+ mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOperation->Init(mConnection, this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString dn;
+ rv = mSearchUrl->GetDn(dn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t scope;
+ rv = mSearchUrl->GetScope(&scope);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString filter;
+ rv = mSearchUrl->GetFilter(filter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString attributes;
+ rv = mSearchUrl->GetAttributes(attributes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOperation->SetServerControls(mServerSearchControls);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOperation->SetClientControls(mClientSearchControls);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mOperation->SearchExt(dn, scope, filter, attributes, mTimeOut,
+ mResultLimit);
+}
+
+void nsAbQueryLDAPMessageListener::InitFailed(bool aCancelled)
+{
+ if (!mResultListener)
+ return;
+
+ // In the !aCancelled case we know there was an error, but we won't be
+ // able to translate it, so just return an error code of zero.
+ mResultListener->OnQueryResult(
+ aCancelled ? nsIAbDirectoryQueryResultListener::queryResultStopped :
+ nsIAbDirectoryQueryResultListener::queryResultError, 0);
+}
+
+nsresult nsAbQueryLDAPMessageListener::OnLDAPMessageSearchEntry(nsILDAPMessage *aMessage)
+{
+ nsresult rv;
+
+ if (!mResultListener)
+ return NS_ERROR_NULL_POINTER;
+
+ // the map for translating between LDAP attrs <-> addrbook fields
+ nsCOMPtr<nsISupports> iSupportsMap;
+ rv = mQueryArguments->GetTypeSpecificArg(getter_AddRefs(iSupportsMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> map = do_QueryInterface(iSupportsMap, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card = do_CreateInstance(NS_ABLDAPCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = map->SetCardPropertiesFromLDAPMessage(aMessage, card);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbLDAPCard> ldapCard = do_QueryInterface(card, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ldapCard->SetMetaProperties(aMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mResultListener->OnQueryFoundCard(card);
+}
+
+nsresult nsAbQueryLDAPMessageListener::OnLDAPMessageSearchResult(nsILDAPMessage *aMessage)
+{
+ int32_t errorCode;
+ nsresult rv = aMessage->GetErrorCode(&errorCode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (errorCode == nsILDAPErrors::SUCCESS || errorCode == nsILDAPErrors::SIZELIMIT_EXCEEDED)
+ return mResultListener->OnQueryResult(
+ nsIAbDirectoryQueryResultListener::queryResultComplete, 0);
+
+ return mResultListener->OnQueryResult(
+ nsIAbDirectoryQueryResultListener::queryResultError, errorCode);
+}
+
+// nsAbLDAPDirectoryQuery
+
+NS_IMPL_ISUPPORTS(nsAbLDAPDirectoryQuery, nsIAbDirectoryQuery,
+ nsIAbDirectoryQueryResultListener)
+
+nsAbLDAPDirectoryQuery::nsAbLDAPDirectoryQuery() :
+ mInitialized(false)
+{
+}
+
+nsAbLDAPDirectoryQuery::~nsAbLDAPDirectoryQuery()
+{
+}
+
+NS_IMETHODIMP nsAbLDAPDirectoryQuery::DoQuery(nsIAbDirectory *aDirectory,
+ nsIAbDirectoryQueryArguments* aArguments,
+ nsIAbDirSearchListener* aListener,
+ int32_t aResultLimit,
+ int32_t aTimeOut,
+ int32_t* _retval)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ NS_ENSURE_ARG_POINTER(aArguments);
+
+ mListeners.AppendObject(aListener);
+
+ // Ensure existing query is stopped. Context id doesn't matter here
+ nsresult rv = StopQuery(0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitialized = true;
+
+ // Get the current directory as LDAP specific
+ nsCOMPtr<nsIAbLDAPDirectory> directory(do_QueryInterface(aDirectory, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We also need the current URL to check as well...
+ nsCOMPtr<nsILDAPURL> currentUrl;
+ rv = directory->GetLDAPURL(getter_AddRefs(currentUrl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString login;
+ rv = directory->GetAuthDn(login);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString saslMechanism;
+ rv = directory->GetSaslMechanism(saslMechanism);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t protocolVersion;
+ rv = directory->GetProtocolVersion(&protocolVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // To do:
+ // Ensure query is stopped
+ // If connection params have changed re-create connection
+ // else reuse existing connection
+
+ bool redoConnection = false;
+
+ if (!mConnection || !mDirectoryUrl)
+ {
+ mDirectoryUrl = currentUrl;
+ aDirectory->GetUuid(mDirectoryId);
+ mCurrentLogin = login;
+ mCurrentMechanism = saslMechanism;
+ mCurrentProtocolVersion = protocolVersion;
+ redoConnection = true;
+ }
+ else
+ {
+ bool equal;
+ rv = mDirectoryUrl->Equals(currentUrl, &equal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!equal)
+ {
+ mDirectoryUrl = currentUrl;
+ aDirectory->GetUuid(mDirectoryId);
+ mCurrentLogin = login;
+ mCurrentMechanism = saslMechanism;
+ mCurrentProtocolVersion = protocolVersion;
+ redoConnection = true;
+ }
+ else
+ {
+ // Has login or version changed?
+ if (login != mCurrentLogin ||
+ saslMechanism != mCurrentMechanism ||
+ protocolVersion != mCurrentProtocolVersion)
+ {
+ redoConnection = true;
+ mCurrentLogin = login;
+ mCurrentMechanism = saslMechanism;
+ mCurrentProtocolVersion = protocolVersion;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = mDirectoryUrl->Clone(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILDAPURL> url(do_QueryInterface(uri, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get/Set the return attributes
+ nsCOMPtr<nsISupports> iSupportsMap;
+ rv = aArguments->GetTypeSpecificArg(getter_AddRefs(iSupportsMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> map = do_QueryInterface(iSupportsMap, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Require all attributes that are mapped to card properties
+ nsAutoCString returnAttributes;
+ rv = map->GetAllCardAttributes(returnAttributes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->SetAttributes(returnAttributes);
+ // Now do the error check
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Also require the objectClass attribute, it is used by
+ // nsAbLDAPCard::SetMetaProperties
+ rv = url->AddAttribute(NS_LITERAL_CSTRING("objectClass"));
+
+ nsAutoCString filter;
+
+ // Get filter from arguments if set:
+ rv = aArguments->GetFilter(filter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (filter.IsEmpty()) {
+ // Get the filter
+ nsCOMPtr<nsISupports> supportsExpression;
+ rv = aArguments->GetExpression(getter_AddRefs(supportsExpression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> expression(do_QueryInterface(supportsExpression, &rv));
+
+ // figure out how we map attribute names to addressbook fields for this
+ // query
+ rv = nsAbBoolExprToLDAPFilter::Convert(map, expression, filter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ /*
+ * Mozilla itself cannot arrive here with a blank filter
+ * as the nsAbLDAPDirectory::StartSearch() disallows it.
+ * But 3rd party LDAP query integration with Mozilla begins
+ * in this method.
+ *
+ * Default the filter string if blank, otherwise it gets
+ * set to (objectclass=*) which returns everything. Set
+ * the default to (objectclass=inetorgperson) as this
+ * is the most appropriate default objectclass which is
+ * central to the makeup of the mozilla ldap address book
+ * entries.
+ */
+ if (filter.IsEmpty())
+ {
+ filter.AssignLiteral("(objectclass=inetorgperson)");
+ }
+
+ // get the directoryFilter from the directory url and merge it with the user's
+ // search filter
+ nsAutoCString urlFilter;
+ rv = mDirectoryUrl->GetFilter(urlFilter);
+
+ // if urlFilter is unset (or set to the default "objectclass=*"), there's
+ // no need to AND in an empty search term, so leave prefix and suffix empty
+
+ nsAutoCString searchFilter;
+ if (urlFilter.Length() && !urlFilter.EqualsLiteral("(objectclass=*)"))
+ {
+ // if urlFilter isn't parenthesized, we need to add in parens so that
+ // the filter works as a term to &
+ //
+ if (urlFilter[0] != '(')
+ {
+ searchFilter = NS_LITERAL_CSTRING("(&(");
+ searchFilter.Append(urlFilter);
+ searchFilter.AppendLiteral(")");
+ }
+ else
+ {
+ searchFilter = NS_LITERAL_CSTRING("(&");
+ searchFilter.Append(urlFilter);
+ }
+
+ searchFilter += filter;
+ searchFilter += ')';
+ }
+ else
+ searchFilter = filter;
+
+ rv = url->SetFilter(searchFilter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now formulate the search string
+
+ // Get the scope
+ int32_t scope;
+ bool doSubDirectories;
+ rv = aArguments->GetQuerySubDirectories (&doSubDirectories);
+ NS_ENSURE_SUCCESS(rv, rv);
+ scope = doSubDirectories ? nsILDAPURL::SCOPE_SUBTREE :
+ nsILDAPURL::SCOPE_ONELEVEL;
+
+ rv = url->SetScope(scope);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // too soon? Do we need a new listener?
+ // If we already have a connection, and don't need to re-do it, give it the
+ // new search details and go for it...
+ if (!redoConnection)
+ {
+ nsAbQueryLDAPMessageListener *msgListener =
+ static_cast<nsAbQueryLDAPMessageListener *>(static_cast<nsILDAPMessageListener *>(mListener.get()));
+ if (msgListener)
+ {
+ // Ensure the urls are correct
+ msgListener->mDirectoryUrl = mDirectoryUrl;
+ msgListener->mSearchUrl = url;
+ // Also ensure we set the correct result limit
+ msgListener->mResultLimit = aResultLimit;
+ return msgListener->DoTask();
+ }
+ }
+
+ nsCOMPtr<nsIAbLDAPDirectory> abLDAPDir = do_QueryInterface(aDirectory, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> serverSearchControls;
+ rv = abLDAPDir->GetSearchServerControls(getter_AddRefs(serverSearchControls));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> clientSearchControls;
+ rv = abLDAPDir->GetSearchClientControls(getter_AddRefs(clientSearchControls));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the new connection (which cause the old one to be dropped if necessary)
+ mConnection = do_CreateInstance(NS_LDAPCONNECTION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectoryQueryResultListener> resultListener =
+ do_QueryInterface((nsIAbDirectoryQuery*)this, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initiate LDAP message listener
+ nsAbQueryLDAPMessageListener* _messageListener =
+ new nsAbQueryLDAPMessageListener(resultListener, mDirectoryUrl, url,
+ mConnection, aArguments,
+ serverSearchControls, clientSearchControls,
+ mCurrentLogin, mCurrentMechanism,
+ aResultLimit, aTimeOut);
+ if (_messageListener == NULL)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mListener = _messageListener;
+ *_retval = 1;
+
+ // Now lets initialize the LDAP connection properly. We'll kick
+ // off the bind operation in the callback function, |OnLDAPInit()|.
+ rv = mConnection->Init(mDirectoryUrl, mCurrentLogin,
+ mListener, nullptr, mCurrentProtocolVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+/* void stopQuery (in long contextID); */
+NS_IMETHODIMP nsAbLDAPDirectoryQuery::StopQuery(int32_t contextID)
+{
+ mInitialized = true;
+
+ if (!mListener)
+ return NS_OK;
+
+ nsAbQueryLDAPMessageListener *listener =
+ static_cast<nsAbQueryLDAPMessageListener *>(static_cast<nsILDAPMessageListener *>(mListener.get()));
+ if (listener)
+ return listener->Cancel();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectoryQuery::OnQueryFoundCard(nsIAbCard *aCard)
+{
+ aCard->SetDirectoryId(mDirectoryId);
+
+ for (int32_t i = 0; i < mListeners.Count(); ++i)
+ mListeners[i]->OnSearchFoundCard(aCard);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPDirectoryQuery::OnQueryResult(int32_t aResult,
+ int32_t aErrorCode)
+{
+ uint32_t count = mListeners.Count();
+
+ // XXX: Temporary fix for crasher needs reviewing as part of bug 135231.
+ // Temporarily add a reference to ourselves, in case the only thing
+ // keeping us alive is the link with the listener.
+ NS_ADDREF_THIS();
+
+ for (int32_t i = count - 1; i >= 0; --i)
+ {
+ mListeners[i]->OnSearchFinished(aResult, EmptyString());
+ mListeners.RemoveObjectAt(i);
+ }
+
+ NS_RELEASE_THIS();
+
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h
new file mode 100644
index 000000000..2c11c2ab9
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPDirectoryQuery_h__
+#define nsAbLDAPDirectoryQuery_h__
+
+#include "nsIAbDirectoryQuery.h"
+#include "nsILDAPConnection.h"
+#include "nsILDAPMessageListener.h"
+#include "nsILDAPURL.h"
+#include "nsWeakReference.h"
+
+#include "nsStringGlue.h"
+#include "nsCOMArray.h"
+
+class nsAbLDAPDirectoryQuery : public nsIAbDirectoryQuery,
+ public nsIAbDirectoryQueryResultListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERY
+ NS_DECL_NSIABDIRECTORYQUERYRESULTLISTENER
+
+ nsAbLDAPDirectoryQuery();
+
+protected:
+ nsCOMPtr<nsILDAPMessageListener> mListener;
+
+private:
+ virtual ~nsAbLDAPDirectoryQuery();
+ nsCOMPtr<nsILDAPConnection> mConnection;
+ nsCOMPtr<nsILDAPURL> mDirectoryUrl;
+ nsCString mDirectoryId;
+ nsCOMArray<nsIAbDirSearchListener> mListeners;
+ nsCString mCurrentLogin;
+ nsCString mCurrentMechanism;
+ uint32_t mCurrentProtocolVersion;
+
+ bool mInitialized;
+};
+
+#endif // nsAbLDAPDirectoryQuery_h__
diff --git a/mailnews/addrbook/src/nsAbLDAPListenerBase.cpp b/mailnews/addrbook/src/nsAbLDAPListenerBase.cpp
new file mode 100644
index 000000000..e5465a7f5
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPListenerBase.cpp
@@ -0,0 +1,358 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbLDAPListenerBase.h"
+#include "nsIWindowWatcher.h"
+#include "nsIWindowMediator.h"
+#include "mozIDOMWindow.h"
+#include "nsIAuthPrompt.h"
+#include "nsIStringBundle.h"
+#include "nsILDAPMessage.h"
+#include "nsILDAPErrors.h"
+#include "nsILoginManager.h"
+#include "nsILoginInfo.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+nsAbLDAPListenerBase::nsAbLDAPListenerBase(nsILDAPURL* url,
+ nsILDAPConnection* connection,
+ const nsACString &login,
+ const int32_t timeOut) :
+ mDirectoryUrl(url), mConnection(connection), mLogin(login),
+ mTimeOut(timeOut), mBound(false), mInitialized(false),
+ mLock("nsAbLDAPListenerBase.mLock")
+{
+}
+
+nsAbLDAPListenerBase::~nsAbLDAPListenerBase()
+{
+}
+
+nsresult nsAbLDAPListenerBase::Initiate()
+{
+ if (!mConnection || !mDirectoryUrl)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mInitialized)
+ return NS_OK;
+
+ mInitialized = true;
+
+ return NS_OK;
+}
+
+// If something fails in this function, we must call InitFailed() so that the
+// derived class (and listener) knows to cancel what its doing as there is
+// a problem.
+NS_IMETHODIMP nsAbLDAPListenerBase::OnLDAPInit(nsILDAPConnection *aConn, nsresult aStatus)
+{
+ if (!mConnection || !mDirectoryUrl)
+ {
+ InitFailed();
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsresult rv;
+ nsString passwd;
+
+ // Make sure that the Init() worked properly
+ if (NS_FAILED(aStatus))
+ {
+ InitFailed();
+ return NS_OK;
+ }
+
+ // If mLogin is set, we're expected to use it to get a password.
+ //
+ if (!mLogin.IsEmpty() && !mSaslMechanism.EqualsLiteral("GSSAPI"))
+ {
+ // get the string bundle service
+ //
+ nsCOMPtr<nsIStringBundleService> stringBundleSvc =
+ mozilla::services::GetStringBundleService();
+ if (!stringBundleSvc)
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():"
+ " error getting string bundle service");
+ InitFailed();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // get the LDAP string bundle
+ //
+ nsCOMPtr<nsIStringBundle> ldapBundle;
+ rv = stringBundleSvc->CreateBundle("chrome://mozldap/locale/ldap.properties",
+ getter_AddRefs(ldapBundle));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit(): error creating string"
+ "bundle chrome://mozldap/locale/ldap.properties");
+ InitFailed();
+ return rv;
+ }
+
+ // get the title for the authentication prompt
+ //
+ nsString authPromptTitle;
+ rv = ldapBundle->GetStringFromName(u"authPromptTitle",
+ getter_Copies(authPromptTitle));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit(): error getting"
+ "'authPromptTitle' string from bundle "
+ "chrome://mozldap/locale/ldap.properties");
+ InitFailed();
+ return rv;
+ }
+
+ // get the host name for the auth prompt
+ //
+ nsAutoCString host;
+ rv = mDirectoryUrl->GetAsciiHost(host);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit(): error getting ascii host"
+ "name from directory url");
+ InitFailed();
+ return rv;
+ }
+
+ // hostTemp is only necessary to work around a code-generation
+ // bug in egcs 1.1.2 (the version of gcc that comes with Red Hat 6.2),
+ // which is the default compiler for Mozilla on linux at the moment.
+ //
+ NS_ConvertASCIItoUTF16 hostTemp(host);
+ const char16_t *hostArray[1] = { hostTemp.get() };
+
+ // format the hostname into the authprompt text string
+ //
+ nsString authPromptText;
+ rv = ldapBundle->FormatStringFromName(u"authPromptText",
+ hostArray,
+ sizeof(hostArray) / sizeof(const char16_t *),
+ getter_Copies(authPromptText));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():"
+ "error getting 'authPromptText' string from bundle "
+ "chrome://mozldap/locale/ldap.properties");
+ InitFailed();
+ return rv;
+ }
+
+ // get the window mediator service, so we can get an auth prompter
+ //
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():"
+ " couldn't get window mediator service.");
+ InitFailed();
+ return rv;
+ }
+
+ // get the addressbook window, as it will be used to parent the auth
+ // prompter dialog
+ //
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ rv = windowMediator->GetMostRecentWindow(nullptr,
+ getter_AddRefs(window));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():"
+ " error getting most recent window");
+ InitFailed();
+ return rv;
+ }
+
+ // get the window watcher service, so we can get an auth prompter
+ //
+ nsCOMPtr<nsIWindowWatcher> windowWatcherSvc =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():"
+ " couldn't get window watcher service.");
+ InitFailed();
+ return rv;
+ }
+
+ // get the auth prompter itself
+ //
+ nsCOMPtr<nsIAuthPrompt> authPrompter;
+ rv = windowWatcherSvc->GetNewAuthPrompter(window,
+ getter_AddRefs(authPrompter));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit():"
+ " error getting auth prompter");
+ InitFailed();
+ return rv;
+ }
+
+ // get authentication password, prompting the user if necessary
+ //
+ // we're going to use the URL spec of the server as the "realm" for
+ // wallet to remember the password by / for.
+
+ // Get the specification
+ nsCString spec;
+ rv = mDirectoryUrl->GetSpec(spec);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit():"
+ " error getting directory url spec");
+ InitFailed();
+ return rv;
+ }
+
+ bool status;
+ rv = authPrompter->PromptPassword(authPromptTitle.get(),
+ authPromptText.get(),
+ NS_ConvertUTF8toUTF16(spec).get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ getter_Copies(passwd),
+ &status);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to prompt for"
+ " password");
+ InitFailed();
+ return rv;
+ }
+ else if (!status)
+ {
+ InitFailed(true);
+ return NS_OK;
+ }
+ }
+
+ // Initiate the LDAP operation
+ mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to create ldap operation");
+ InitFailed();
+ return rv;
+ }
+
+ rv = mOperation->Init(mConnection, this, nullptr);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to Initialise operation");
+ InitFailed();
+ return rv;
+ }
+
+ // Try non-password mechanisms first
+ if (mSaslMechanism.EqualsLiteral("GSSAPI"))
+ {
+ nsAutoCString service;
+ rv = mDirectoryUrl->GetAsciiHost(service);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ service.Insert(NS_LITERAL_CSTRING("ldap@"), 0);
+
+ nsCOMPtr<nsIAuthModule> authModule =
+ do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sasl-gssapi", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOperation->SaslBind(service, mSaslMechanism, authModule);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): "
+ "failed to perform GSSAPI bind");
+ mOperation = nullptr; // Break Listener -> Operation -> Listener ref cycle
+ InitFailed();
+ }
+ return rv;
+ }
+
+ // Bind
+ rv = mOperation->SimpleBind(NS_ConvertUTF16toUTF8(passwd));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to perform bind operation");
+ mOperation = nullptr; // Break Listener->Operation->Listener reference cycle
+ InitFailed();
+ }
+ return rv;
+}
+
+nsresult nsAbLDAPListenerBase::OnLDAPMessageBind(nsILDAPMessage *aMessage)
+{
+ if (mBound)
+ return NS_OK;
+
+ // see whether the bind actually succeeded
+ //
+ int32_t errCode;
+ nsresult rv = aMessage->GetErrorCode(&errCode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (errCode != nsILDAPErrors::SUCCESS)
+ {
+ // if the login failed, tell the wallet to forget this password
+ //
+ if (errCode == nsILDAPErrors::INAPPROPRIATE_AUTH ||
+ errCode == nsILDAPErrors::INVALID_CREDENTIALS)
+ {
+ // Login failed, so try again - but first remove the existing login(s)
+ // so that the user gets prompted. This may not be the best way of doing
+ // things, we need to review that later.
+
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString spec;
+ rv = mDirectoryUrl->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString prePath;
+ rv = mDirectoryUrl->GetPrePath(prePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ nsILoginInfo** logins;
+
+ rv = loginMgr->FindLogins(&count, NS_ConvertUTF8toUTF16(prePath),
+ EmptyString(),
+ NS_ConvertUTF8toUTF16(spec), &logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Typically there should only be one-login stored for this url, however,
+ // just in case there isn't.
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ rv = loginMgr->RemoveLogin(logins[i]);
+ if (NS_FAILED(rv))
+ {
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins);
+ return rv;
+ }
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins);
+
+ // XXX We should probably pop up an error dialog telling
+ // the user that the login failed here, rather than just bringing
+ // up the password dialog again, which is what calling OnLDAPInit()
+ // does.
+ return OnLDAPInit(nullptr, NS_OK);
+ }
+
+ // Don't know how to handle this, so use the message error code in
+ // the failure return value so we hopefully get it back to the UI.
+ return NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_LDAP, errCode);
+ }
+
+ mBound = true;
+ return DoTask();
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPListenerBase.h b/mailnews/addrbook/src/nsAbLDAPListenerBase.h
new file mode 100644
index 000000000..aa292df71
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPListenerBase.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbLDAPListenerBase_h__
+#define nsAbLDAPListenerBase_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsILDAPMessageListener.h"
+#include "nsILDAPURL.h"
+#include "nsILDAPConnection.h"
+#include "nsILDAPOperation.h"
+#include "nsStringGlue.h"
+#include "mozilla/Mutex.h"
+
+class nsAbLDAPListenerBase : public nsILDAPMessageListener
+{
+public:
+ // Note that the directoryUrl is the details of the ldap directory
+ // without any search params or attributes specified.
+ nsAbLDAPListenerBase(nsILDAPURL* directoryUrl = nullptr,
+ nsILDAPConnection* connection = nullptr,
+ const nsACString &login = EmptyCString(),
+ const int32_t timeOut = 0);
+ virtual ~nsAbLDAPListenerBase();
+
+ NS_IMETHOD OnLDAPInit(nsILDAPConnection *aConn, nsresult aStatus) override;
+
+protected:
+ nsresult OnLDAPMessageBind(nsILDAPMessage *aMessage);
+
+ nsresult Initiate();
+
+ // Called if an LDAP initialization fails.
+ virtual void InitFailed(bool aCancelled = false) = 0;
+
+ // Called to start off the required task after a bind.
+ virtual nsresult DoTask() = 0;
+
+ nsCOMPtr<nsILDAPURL> mDirectoryUrl;
+ nsCOMPtr<nsILDAPOperation> mOperation; // current ldap op
+ nsILDAPConnection* mConnection;
+ nsCString mLogin;
+ nsCString mSaslMechanism;
+ int32_t mTimeOut;
+ bool mBound;
+ bool mInitialized;
+
+ mozilla::Mutex mLock;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationData.cpp b/mailnews/addrbook/src/nsAbLDAPReplicationData.cpp
new file mode 100644
index 000000000..faa8cdd23
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationData.cpp
@@ -0,0 +1,489 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsILDAPMessage.h"
+#include "nsAbLDAPReplicationData.h"
+#include "nsIAbCard.h"
+#include "nsAbBaseCID.h"
+#include "nsAbUtils.h"
+#include "nsAbLDAPReplicationQuery.h"
+#include "nsILDAPErrors.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+
+// once bug # 101252 gets fixed, this should be reverted back to be non threadsafe
+// implementation is not really thread safe since each object should exist
+// independently along with its related independent nsAbLDAPReplicationQuery object.
+NS_IMPL_ISUPPORTS(nsAbLDAPProcessReplicationData, nsIAbLDAPProcessReplicationData, nsILDAPMessageListener)
+
+nsAbLDAPProcessReplicationData::nsAbLDAPProcessReplicationData() :
+ nsAbLDAPListenerBase(),
+ mState(kIdle),
+ mProtocol(-1),
+ mCount(0),
+ mDBOpen(false),
+ mInitialized(false)
+{
+}
+
+nsAbLDAPProcessReplicationData::~nsAbLDAPProcessReplicationData()
+{
+ /* destructor code */
+ if(mDBOpen && mReplicationDB)
+ mReplicationDB->Close(false);
+}
+
+NS_IMETHODIMP nsAbLDAPProcessReplicationData::Init(
+ nsIAbLDAPDirectory *aDirectory,
+ nsILDAPConnection *aConnection,
+ nsILDAPURL* aURL,
+ nsIAbLDAPReplicationQuery *aQuery,
+ nsIWebProgressListener *aProgressListener)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+ NS_ENSURE_ARG_POINTER(aConnection);
+ NS_ENSURE_ARG_POINTER(aURL);
+ NS_ENSURE_ARG_POINTER(aQuery);
+
+ mDirectory = aDirectory;
+ mConnection = aConnection;
+ mDirectoryUrl = aURL;
+ mQuery = aQuery;
+
+ mListener = aProgressListener;
+
+ nsresult rv = mDirectory->GetAttributeMap(getter_AddRefs(mAttrMap));
+ if (NS_FAILED(rv)) {
+ mQuery = nullptr;
+ return rv;
+ }
+
+ rv = mDirectory->GetAuthDn(mLogin);
+ if (NS_FAILED(rv)) {
+ mQuery = nullptr;
+ return rv;
+ }
+
+ rv = mDirectory->GetSaslMechanism(mSaslMechanism);
+ if (NS_FAILED(rv)) {
+ mQuery = nullptr;
+ return rv;
+ }
+
+ mInitialized = true;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPProcessReplicationData::GetReplicationState(int32_t *aReplicationState)
+{
+ NS_ENSURE_ARG_POINTER(aReplicationState);
+ *aReplicationState = mState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPProcessReplicationData::GetProtocolUsed(int32_t *aProtocolUsed)
+{
+ NS_ENSURE_ARG_POINTER(aProtocolUsed);
+ *aProtocolUsed = mProtocol;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbLDAPProcessReplicationData::OnLDAPMessage(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t messageType;
+ nsresult rv = aMessage->GetType(&messageType);
+ if (NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+
+ switch (messageType)
+ {
+ case nsILDAPMessage::RES_BIND:
+ rv = OnLDAPMessageBind(aMessage);
+ if (NS_FAILED(rv))
+ rv = Abort();
+ break;
+ case nsILDAPMessage::RES_SEARCH_ENTRY:
+ rv = OnLDAPSearchEntry(aMessage);
+ break;
+ case nsILDAPMessage::RES_SEARCH_RESULT:
+ rv = OnLDAPSearchResult(aMessage);
+ break;
+ default:
+ // for messageTypes we do not handle return NS_OK to LDAP and move ahead.
+ rv = NS_OK;
+ break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPProcessReplicationData::Abort()
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+
+ if (mState != kIdle && mOperation) {
+ rv = mOperation->AbandonExt();
+ if (NS_SUCCEEDED(rv))
+ mState = kIdle;
+ }
+
+ if (mReplicationDB && mDBOpen) {
+ // force close since we need to delete the file.
+ mReplicationDB->ForceClosed();
+ mDBOpen = false;
+
+ // delete the unsaved replication file
+ if (mReplicationFile) {
+ rv = mReplicationFile->Remove(false);
+ if (NS_SUCCEEDED(rv) && mDirectory) {
+ nsAutoCString fileName;
+ rv = mDirectory->GetReplicationFileName(fileName);
+ // now put back the backed up replicated file if aborted
+ if (NS_SUCCEEDED(rv) && mBackupReplicationFile)
+ rv = mBackupReplicationFile->MoveToNative(nullptr, fileName);
+ }
+ }
+ }
+
+ Done(false);
+
+ return rv;
+}
+
+nsresult nsAbLDAPProcessReplicationData::DoTask()
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = OpenABForReplicatedDir(true);
+ if (NS_FAILED(rv))
+ // do not call done here since it is called by OpenABForReplicationDir
+ return rv;
+
+ mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOperation->Init(mConnection, this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the relevant attributes associated with the directory server url
+ nsAutoCString urlFilter;
+ rv = mDirectoryUrl->GetFilter(urlFilter);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString dn;
+ rv = mDirectoryUrl->GetDn(dn);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (dn.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ int32_t scope;
+ rv = mDirectoryUrl->GetScope(&scope);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString attributes;
+ rv = mDirectoryUrl->GetAttributes(attributes);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mState = kReplicatingAll;
+
+ if (mListener && NS_SUCCEEDED(rv))
+ // XXX Cast from bool to nsresult
+ mListener->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_START,
+ static_cast<nsresult>(true));
+
+ return mOperation->SearchExt(dn, scope, urlFilter, attributes, 0, 0);
+}
+
+void nsAbLDAPProcessReplicationData::InitFailed(bool aCancelled)
+{
+ // Just call Done() which will ensure everything is tidied up nicely.
+ Done(false);
+}
+
+nsresult nsAbLDAPProcessReplicationData::OnLDAPSearchEntry(nsILDAPMessage *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+ // since this runs on the main thread and is single threaded, this will
+ // take care of entries returned by LDAP Connection thread after Abort.
+ if (!mReplicationDB || !mDBOpen)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+
+ // Although we would may naturally create an nsIAbLDAPCard here, we don't
+ // need to as we are writing this straight to the database, so just create
+ // the database version instead.
+ nsCOMPtr<nsIAbCard> newCard(do_CreateInstance(NS_ABMDBCARD_CONTRACTID,
+ &rv));
+ if (NS_FAILED(rv)) {
+ Abort();
+ return rv;
+ }
+
+ rv = mAttrMap->SetCardPropertiesFromLDAPMessage(aMessage, newCard);
+ if (NS_FAILED(rv))
+ {
+ NS_WARNING("nsAbLDAPProcessReplicationData::OnLDAPSearchEntry"
+ "No card properties could be set");
+ // if some entries are bogus for us, continue with next one
+ return NS_OK;
+ }
+
+ rv = mReplicationDB->CreateNewCardAndAddToDB(newCard, false, nullptr);
+ if(NS_FAILED(rv)) {
+ Abort();
+ return rv;
+ }
+
+ // now set the attribute for the DN of the entry in the card in the DB
+ nsAutoCString authDN;
+ rv = aMessage->GetDn(authDN);
+ if(NS_SUCCEEDED(rv) && !authDN.IsEmpty())
+ {
+ newCard->SetPropertyAsAUTF8String("_DN", authDN);
+ }
+
+ rv = mReplicationDB->EditCard(newCard, false, nullptr);
+ if(NS_FAILED(rv)) {
+ Abort();
+ return rv;
+ }
+
+
+ mCount ++;
+
+ if (mListener && !(mCount % 10)) // inform the listener every 10 entries
+ {
+ mListener->OnProgressChange(nullptr,nullptr,mCount, -1, mCount, -1);
+ // in case if the LDAP Connection thread is starved and causes problem
+ // uncomment this one and try.
+ // PR_Sleep(PR_INTERVAL_NO_WAIT); // give others a chance
+ }
+
+ return rv;
+}
+
+
+nsresult nsAbLDAPProcessReplicationData::OnLDAPSearchResult(nsILDAPMessage *aMessage)
+{
+#ifdef DEBUG_rdayal
+ printf("LDAP Replication : Got Results for Completion");
+#endif
+
+ NS_ENSURE_ARG_POINTER(aMessage);
+ if(!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t errorCode;
+ nsresult rv = aMessage->GetErrorCode(&errorCode);
+
+ if(NS_SUCCEEDED(rv)) {
+ // We are done with the LDAP search for all entries.
+ if(errorCode == nsILDAPErrors::SUCCESS || errorCode == nsILDAPErrors::SIZELIMIT_EXCEEDED) {
+ Done(true);
+ if(mReplicationDB && mDBOpen) {
+ rv = mReplicationDB->Close(true);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB Close on Success failed");
+ mDBOpen = false;
+ // once we have saved the new replication file, delete the backup file
+ if(mBackupReplicationFile)
+ {
+ rv = mBackupReplicationFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication BackupFile Remove on Success failed");
+ }
+ }
+ return NS_OK;
+ }
+ }
+
+ // in case if GetErrorCode returned error or errorCode is not SUCCESS / SIZELIMIT_EXCEEDED
+ if(mReplicationDB && mDBOpen) {
+ // if error result is returned close the DB without saving ???
+ // should we commit anyway ??? whatever is returned is not lost then !!
+ rv = mReplicationDB->ForceClosed(); // force close since we need to delete the file.
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB ForceClosed on Failure failed");
+ mDBOpen = false;
+ // if error result is returned remove the replicated file
+ if(mReplicationFile) {
+ rv = mReplicationFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication File Remove on Failure failed");
+ if(NS_SUCCEEDED(rv)) {
+ // now put back the backed up replicated file
+ if(mBackupReplicationFile && mDirectory)
+ {
+ nsAutoCString fileName;
+ rv = mDirectory->GetReplicationFileName(fileName);
+ if (NS_SUCCEEDED(rv) && !fileName.IsEmpty())
+ {
+ rv = mBackupReplicationFile->MoveToNative(nullptr, fileName);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Replication Backup File Move back on Failure failed");
+ }
+ }
+ }
+ }
+ Done(false);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAbLDAPProcessReplicationData::OpenABForReplicatedDir(bool aCreate)
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mDirectory->GetReplicationFile(getter_AddRefs(mReplicationFile));
+ if (NS_FAILED(rv))
+ {
+ Done(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString fileName;
+ rv = mReplicationFile->GetNativeLeafName(fileName);
+ if (NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+
+ // if the AB DB already exists backup existing one,
+ // in case if the user cancels or Abort put back the backed up file
+ bool fileExists;
+ rv = mReplicationFile->Exists(&fileExists);
+ if(NS_SUCCEEDED(rv) && fileExists) {
+ // create the backup file object same as the Replication file object.
+ // we create a backup file here since we need to cleanup the existing file
+ // for create and then commit so instead of deleting existing cards we just
+ // clone the existing one for a much better performance - for Download All.
+ // And also important in case if replication fails we donot lose user's existing
+ // replicated data for both Download all and Changelog.
+ nsCOMPtr<nsIFile> clone;
+ rv = mReplicationFile->Clone(getter_AddRefs(clone));
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+ mBackupReplicationFile = do_QueryInterface(clone, &rv);
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+ rv = mBackupReplicationFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0777);
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+ nsAutoString backupFileLeafName;
+ rv = mBackupReplicationFile->GetLeafName(backupFileLeafName);
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+ // remove the newly created unique backup file so that move and copy succeeds.
+ rv = mBackupReplicationFile->Remove(false);
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+
+ if(aCreate) {
+ // set backup file to existing replication file for move
+ mBackupReplicationFile->SetNativeLeafName(fileName);
+
+ rv = mBackupReplicationFile->MoveTo(nullptr, backupFileLeafName);
+ // set the backup file leaf name now
+ if (NS_SUCCEEDED(rv))
+ mBackupReplicationFile->SetLeafName(backupFileLeafName);
+ }
+ else {
+ // set backup file to existing replication file for copy
+ mBackupReplicationFile->SetNativeLeafName(fileName);
+
+ // specify the parent here specifically,
+ // passing nullptr to copy to the same dir actually renames existing file
+ // instead of making another copy of the existing file.
+ nsCOMPtr<nsIFile> parent;
+ rv = mBackupReplicationFile->GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv))
+ rv = mBackupReplicationFile->CopyTo(parent, backupFileLeafName);
+ // set the backup file leaf name now
+ if (NS_SUCCEEDED(rv))
+ mBackupReplicationFile->SetLeafName(backupFileLeafName);
+ }
+ if(NS_FAILED(rv)) {
+ Done(false);
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory =
+ do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+ if(NS_FAILED(rv)) {
+ if (mBackupReplicationFile)
+ mBackupReplicationFile->Remove(false);
+ Done(false);
+ return rv;
+ }
+
+ rv = addrDBFactory->Open(mReplicationFile, aCreate, true, getter_AddRefs(mReplicationDB));
+ if(NS_FAILED(rv)) {
+ Done(false);
+ if (mBackupReplicationFile)
+ mBackupReplicationFile->Remove(false);
+ return rv;
+ }
+
+ mDBOpen = true; // replication DB is now Open
+ return rv;
+}
+
+void nsAbLDAPProcessReplicationData::Done(bool aSuccess)
+{
+ if (!mInitialized)
+ return;
+
+ mState = kReplicationDone;
+
+ if (mQuery)
+ mQuery->Done(aSuccess);
+
+ if (mListener)
+ // XXX Cast from bool to nsresult
+ mListener->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP,
+ static_cast<nsresult>(aSuccess));
+
+ // since this is called when all is done here, either on success,
+ // failure or abort release the query now.
+ mQuery = nullptr;
+}
+
+nsresult nsAbLDAPProcessReplicationData::DeleteCard(nsString & aDn)
+{
+ nsCOMPtr<nsIAbCard> cardToDelete;
+ mReplicationDB->GetCardFromAttribute(nullptr, "_DN", NS_ConvertUTF16toUTF8(aDn),
+ false, getter_AddRefs(cardToDelete));
+ return mReplicationDB->DeleteCard(cardToDelete, false, nullptr);
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationData.h b/mailnews/addrbook/src/nsAbLDAPReplicationData.h
new file mode 100644
index 000000000..546ce659e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationData.h
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsAbLDAPReplicationData_h__
+#define nsAbLDAPReplicationData_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIAbLDAPReplicationData.h"
+#include "nsIWebProgressListener.h"
+#include "nsIAbLDAPReplicationQuery.h"
+#include "nsAbLDAPListenerBase.h"
+#include "nsIAddrDatabase.h"
+#include "nsIFile.h"
+#include "nsDirPrefs.h"
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsIAbLDAPDirectory.h"
+#include "nsStringGlue.h"
+
+class nsAbLDAPProcessReplicationData : public nsIAbLDAPProcessReplicationData,
+ public nsAbLDAPListenerBase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABLDAPPROCESSREPLICATIONDATA
+
+ nsAbLDAPProcessReplicationData();
+
+ // nsILDAPMessageListener
+ NS_IMETHOD OnLDAPMessage(nsILDAPMessage *aMessage) override;
+
+protected:
+ virtual ~nsAbLDAPProcessReplicationData();
+ virtual nsresult DoTask() override;
+ virtual void InitFailed(bool aCancelled = false) override;
+
+ // pointer to the interfaces used by this object
+ nsCOMPtr<nsIWebProgressListener> mListener;
+ // pointer to the query to call back to once we've finished
+ nsCOMPtr<nsIAbLDAPReplicationQuery> mQuery;
+
+ nsCOMPtr<nsIAddrDatabase> mReplicationDB;
+ nsCOMPtr <nsIFile> mReplicationFile;
+ nsCOMPtr <nsIFile> mBackupReplicationFile;
+
+ // state of processing, protocol used and count of results
+ int32_t mState;
+ int32_t mProtocol;
+ int32_t mCount;
+ bool mDBOpen;
+ bool mInitialized;
+
+ nsCOMPtr<nsIAbLDAPDirectory> mDirectory;
+ nsCOMPtr<nsIAbLDAPAttributeMap> mAttrMap; // maps ab properties to ldap attrs
+
+ virtual nsresult OnLDAPSearchEntry(nsILDAPMessage *aMessage);
+ virtual nsresult OnLDAPSearchResult(nsILDAPMessage *aMessage);
+
+ nsresult OpenABForReplicatedDir(bool bCreate);
+ nsresult DeleteCard(nsString & aDn);
+ void Done(bool aSuccess);
+};
+
+
+#endif // nsAbLDAPReplicationData_h__
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp
new file mode 100644
index 000000000..cab9825ed
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsCOMPtr.h"
+#include "nsAbLDAPReplicationQuery.h"
+#include "nsAbLDAPReplicationService.h"
+#include "nsAbLDAPReplicationData.h"
+#include "nsAbBaseCID.h"
+#include "nsAbUtils.h"
+#include "nsDirPrefs.h"
+#include "prmem.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+
+NS_IMPL_ISUPPORTS(nsAbLDAPReplicationQuery,
+ nsIAbLDAPReplicationQuery)
+
+nsAbLDAPReplicationQuery::nsAbLDAPReplicationQuery()
+ : mInitialized(false)
+{
+}
+
+nsresult nsAbLDAPReplicationQuery::InitLDAPData()
+{
+ nsAutoCString fileName;
+ nsresult rv = mDirectory->GetReplicationFileName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // this is done here to take care of the problem related to bug # 99124.
+ // earlier versions of Mozilla could have the fileName associated with the directory
+ // to be abook.mab which is the profile's personal addressbook. If the pref points to
+ // it, calls nsDirPrefs to generate a new server filename.
+ if (fileName.IsEmpty() || fileName.EqualsLiteral(kPersonalAddressbook))
+ {
+ // Ensure fileName is empty for DIR_GenerateAbFileName to work
+ // correctly.
+ fileName.Truncate();
+
+ nsCOMPtr<nsIAbDirectory> standardDir(do_QueryInterface(mDirectory, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString dirPrefId;
+ rv = standardDir->GetDirPrefId(dirPrefId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX This should be replaced by a local function at some stage.
+ // For now we'll continue using the nsDirPrefs version.
+ DIR_Server* server = DIR_GetServerFromList(dirPrefId.get());
+ if (server)
+ {
+ DIR_SetServerFileName(server);
+ // Now ensure the prefs are saved
+ DIR_SavePrefsForOneServer(server);
+ }
+ }
+
+ rv = mDirectory->SetReplicationFileName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDirectory->GetLDAPURL(getter_AddRefs(mURL));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDirectory->GetAuthDn(mLogin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mConnection = do_CreateInstance(NS_LDAPCONNECTION_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv);
+
+ return rv;
+}
+
+nsresult nsAbLDAPReplicationQuery::ConnectToLDAPServer()
+{
+ if (!mInitialized || !mURL)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ nsCOMPtr<nsILDAPMessageListener> mDp = do_QueryInterface(mDataProcessor,
+ &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNEXPECTED;
+
+ // this could be a rebind call
+ int32_t replicationState = nsIAbLDAPProcessReplicationData::kIdle;
+ rv = mDataProcessor->GetReplicationState(&replicationState);
+ if (NS_FAILED(rv) ||
+ replicationState != nsIAbLDAPProcessReplicationData::kIdle)
+ return rv;
+
+ uint32_t protocolVersion;
+ rv = mDirectory->GetProtocolVersion(&protocolVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // initialize the LDAP connection
+ return mConnection->Init(mURL, mLogin, mDp, nullptr, protocolVersion);
+}
+
+NS_IMETHODIMP nsAbLDAPReplicationQuery::Init(nsIAbLDAPDirectory *aDirectory,
+ nsIWebProgressListener *aProgressListener)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ mDirectory = aDirectory;
+
+ nsresult rv = InitLDAPData();
+ if (NS_FAILED(rv))
+ return rv;
+
+ mDataProcessor =
+ do_CreateInstance(NS_ABLDAP_PROCESSREPLICATIONDATA_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // 'this' initialized
+ mInitialized = true;
+
+ return mDataProcessor->Init(mDirectory, mConnection, mURL, this,
+ aProgressListener);
+}
+
+NS_IMETHODIMP nsAbLDAPReplicationQuery::DoReplicationQuery()
+{
+ return ConnectToLDAPServer();
+}
+
+NS_IMETHODIMP nsAbLDAPReplicationQuery::CancelQuery()
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mDataProcessor->Abort();
+}
+
+NS_IMETHODIMP nsAbLDAPReplicationQuery::Done(bool aSuccess)
+{
+ if (!mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIAbLDAPReplicationService> replicationService =
+ do_GetService(NS_ABLDAP_REPLICATIONSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ replicationService->Done(aSuccess);
+
+ return rv;
+}
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationQuery.h b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.h
new file mode 100644
index 000000000..ebae95cbd
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsAbLDAPReplicationQuery_h__
+#define nsAbLDAPReplicationQuery_h__
+
+#include "nsIAbLDAPReplicationQuery.h"
+#include "nsIAbLDAPReplicationData.h"
+#include "nsIAbLDAPDirectory.h"
+#include "nsILDAPConnection.h"
+#include "nsILDAPOperation.h"
+#include "nsILDAPURL.h"
+#include "nsDirPrefs.h"
+#include "nsStringGlue.h"
+
+class nsAbLDAPReplicationQuery final : public nsIAbLDAPReplicationQuery
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABLDAPREPLICATIONQUERY
+
+ nsAbLDAPReplicationQuery();
+
+ nsresult InitLDAPData();
+ nsresult ConnectToLDAPServer();
+
+protected :
+ ~nsAbLDAPReplicationQuery() {}
+ // pointer to interfaces used by this object
+ nsCOMPtr<nsILDAPConnection> mConnection;
+ nsCOMPtr<nsILDAPOperation> mOperation;
+ nsCOMPtr<nsILDAPURL> mURL;
+ nsCOMPtr<nsIAbLDAPDirectory> mDirectory;
+
+ nsCOMPtr<nsIAbLDAPProcessReplicationData> mDataProcessor;
+
+ bool mInitialized;
+ nsCString mLogin;
+};
+
+#endif // nsAbLDAPReplicationQuery_h__
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationService.cpp b/mailnews/addrbook/src/nsAbLDAPReplicationService.cpp
new file mode 100644
index 000000000..0ee53d719
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationService.cpp
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsCOMPtr.h"
+#include "nsAbLDAPReplicationService.h"
+#include "nsAbLDAPReplicationQuery.h"
+#include "nsAbBaseCID.h"
+#include "nsIWebProgressListener.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+// XXX Change log replication doesn't work. Bug 311632 should fix it.
+//#include "nsAbLDAPChangeLogQuery.h"
+#include "nsIAbLDAPReplicationData.h"
+
+/*** implementation of the service ******/
+
+NS_IMPL_ISUPPORTS(nsAbLDAPReplicationService, nsIAbLDAPReplicationService)
+
+nsAbLDAPReplicationService::nsAbLDAPReplicationService()
+ : mReplicating(false)
+{
+}
+
+nsAbLDAPReplicationService::~nsAbLDAPReplicationService()
+{
+}
+
+/* void startReplication(in string aURI, in nsIWebProgressListener progressListener); */
+NS_IMETHODIMP nsAbLDAPReplicationService::StartReplication(nsIAbLDAPDirectory *aDirectory,
+ nsIWebProgressListener *progressListener)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+#ifdef DEBUG_rdayal
+ printf("Start Replication called");
+#endif
+
+ // Makes sure to allow only one replication at a time.
+ if(mReplicating)
+ return NS_ERROR_FAILURE;
+
+ mDirectory = aDirectory;
+
+ nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
+
+ switch (DecideProtocol())
+ {
+ case nsIAbLDAPProcessReplicationData::kDefaultDownloadAll:
+ mQuery = do_CreateInstance(NS_ABLDAP_REPLICATIONQUERY_CONTRACTID, &rv);
+ break;
+// XXX Change log replication doesn't work. Bug 311632 should fix it.
+//case nsIAbLDAPProcessReplicationData::kChangeLogProtocol:
+// mQuery = do_CreateInstance (NS_ABLDAP_CHANGELOGQUERY_CONTRACTID, &rv);
+// break;
+ default:
+ break;
+ }
+
+ if (NS_SUCCEEDED(rv) && mQuery)
+ {
+ rv = mQuery->Init(mDirectory, progressListener);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = mQuery->DoReplicationQuery();
+ if (NS_SUCCEEDED(rv))
+ {
+ mReplicating = true;
+ return rv;
+ }
+ }
+ }
+
+ if (progressListener && NS_FAILED(rv))
+ progressListener->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP,
+ NS_OK);
+
+ if (NS_FAILED(rv))
+ {
+ mDirectory = nullptr;
+ mQuery = nullptr;
+ }
+
+ return rv;
+}
+
+/* void cancelReplication(in string aURI); */
+NS_IMETHODIMP nsAbLDAPReplicationService::CancelReplication(nsIAbLDAPDirectory *aDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (aDirectory == mDirectory)
+ {
+ if (mQuery && mReplicating)
+ rv = mQuery->CancelQuery();
+ }
+
+ // If query has been cancelled successfully
+ if (NS_SUCCEEDED(rv))
+ Done(false);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbLDAPReplicationService::Done(bool aSuccess)
+{
+ mReplicating = false;
+ if (mQuery)
+ {
+ mQuery = nullptr; // Release query obj
+ mDirectory = nullptr; // Release directory
+ }
+
+ return NS_OK;
+}
+
+
+// XXX: This method should query the RootDSE for the changeLog attribute,
+// if it exists ChangeLog protocol is supported.
+int32_t nsAbLDAPReplicationService::DecideProtocol()
+{
+ // Do the changeLog, it will decide if there is a need to replicate all
+ // entries or only update existing DB and will do the appropriate thing.
+ //
+ // XXX: Bug 231965 changed this from kChangeLogProtocol to
+ // kDefaultDownloadAll because of a problem with ldap replication not
+ // working correctly. We need to change this back at some stage (bug 311632).
+ return nsIAbLDAPProcessReplicationData::kDefaultDownloadAll;
+}
+
+
diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationService.h b/mailnews/addrbook/src/nsAbLDAPReplicationService.h
new file mode 100644
index 000000000..07fb768f7
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPReplicationService.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+
+#ifndef nsAbLDAPReplicationService_h___
+#define nsAbLDAPReplicationService_h___
+
+#include "nsIAbLDAPReplicationService.h"
+#include "nsIAbLDAPReplicationQuery.h"
+#include "nsStringGlue.h"
+
+class nsAbLDAPReplicationService : public nsIAbLDAPReplicationService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABLDAPREPLICATIONSERVICE
+
+ nsAbLDAPReplicationService();
+
+ int32_t DecideProtocol();
+
+protected:
+ virtual ~nsAbLDAPReplicationService();
+ nsCOMPtr<nsIAbLDAPReplicationQuery> mQuery;
+ bool mReplicating;
+ nsCOMPtr<nsIAbLDAPDirectory> mDirectory;
+
+};
+
+
+#endif /* nsAbLDAPReplicationService_h___ */
diff --git a/mailnews/addrbook/src/nsAbLDIFService.cpp b/mailnews/addrbook/src/nsAbLDIFService.cpp
new file mode 100644
index 000000000..95eb4b96a
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDIFService.cpp
@@ -0,0 +1,868 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsIAddrDatabase.h"
+#include "nsStringGlue.h"
+#include "nsAbLDIFService.h"
+#include "nsIFile.h"
+#include "nsILineInputStream.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsISeekableStream.h"
+#include "mdb.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "prprf.h"
+#include "nsCRTGlue.h"
+#include "nsTArray.h"
+
+#include <ctype.h>
+
+NS_IMPL_ISUPPORTS(nsAbLDIFService, nsIAbLDIFService)
+
+// If we get a line longer than 32K it's just toooooo bad!
+#define kTextAddressBufferSz (64 * 1024)
+
+nsAbLDIFService::nsAbLDIFService()
+{
+ mStoreLocAsHome = false;
+ mLFCount = 0;
+ mCRCount = 0;
+}
+
+nsAbLDIFService::~nsAbLDIFService()
+{
+}
+
+#define RIGHT2 0x03
+#define RIGHT4 0x0f
+#define CONTINUED_LINE_MARKER '\001'
+
+// XXX TODO fix me
+// use the NSPR base64 library. see plbase64.h
+// see bug #145367
+static unsigned char b642nib[0x80] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+ 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+NS_IMETHODIMP nsAbLDIFService::ImportLDIFFile(nsIAddrDatabase *aDb, nsIFile *aSrc, bool aStoreLocAsHome, uint32_t *aProgress)
+{
+ NS_ENSURE_ARG_POINTER(aSrc);
+ NS_ENSURE_ARG_POINTER(aDb);
+
+ mStoreLocAsHome = aStoreLocAsHome;
+
+ char buf[1024];
+ char* pBuf = &buf[0];
+ int32_t startPos = 0;
+ uint32_t len = 0;
+ nsTArray<int32_t> listPosArray; // where each list/group starts in ldif file
+ nsTArray<int32_t> listSizeArray; // size of the list/group info
+ int32_t savedStartPos = 0;
+ int32_t filePos = 0;
+ uint64_t bytesLeft = 0;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initialize the parser for a run...
+ mLdifLine.Truncate();
+
+ while (NS_SUCCEEDED(inputStream->Available(&bytesLeft)) && bytesLeft > 0)
+ {
+ if (NS_SUCCEEDED(inputStream->Read(pBuf, sizeof(buf), &len)) && len > 0)
+ {
+ startPos = 0;
+
+ while (NS_SUCCEEDED(GetLdifStringRecord(buf, len, startPos)))
+ {
+ if (mLdifLine.Find("groupOfNames") == -1)
+ AddLdifRowToDatabase(aDb, false);
+ else
+ {
+ //keep file position for mailing list
+ listPosArray.AppendElement(savedStartPos);
+ listSizeArray.AppendElement(filePos + startPos-savedStartPos);
+ ClearLdifRecordBuffer();
+ }
+ savedStartPos = filePos + startPos;
+ }
+ filePos += len;
+ if (aProgress)
+ *aProgress = (uint32_t)filePos;
+ }
+ }
+ //last row
+ if (!mLdifLine.IsEmpty() && mLdifLine.Find("groupOfNames") == -1)
+ AddLdifRowToDatabase(aDb, false);
+
+ // mail Lists
+ int32_t i, pos;
+ uint32_t size;
+ int32_t listTotal = listPosArray.Length();
+ char *listBuf;
+ ClearLdifRecordBuffer(); // make sure the buffer is clean
+
+ nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(inputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (i = 0; i < listTotal; i++)
+ {
+ pos = listPosArray[i];
+ size = listSizeArray[i];
+ if (NS_SUCCEEDED(seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, pos)))
+ {
+ // Allocate enough space for the lists/groups as the size varies.
+ listBuf = (char *) PR_Malloc(size);
+ if (!listBuf)
+ continue;
+ if (NS_SUCCEEDED(inputStream->Read(listBuf, size, &len)) && len > 0)
+ {
+ startPos = 0;
+
+ while (NS_SUCCEEDED(GetLdifStringRecord(listBuf, len, startPos)))
+ {
+ if (mLdifLine.Find("groupOfNames") != -1)
+ {
+ AddLdifRowToDatabase(aDb, true);
+ if (NS_SUCCEEDED(seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, 0)))
+ break;
+ }
+ }
+ }
+ PR_FREEIF(listBuf);
+ }
+ }
+
+ rv = inputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally commit everything to the database and return.
+ return aDb->Commit(nsAddrDBCommitType::kLargeCommit);
+}
+
+/*
+ * str_parse_line - takes a line of the form "type:[:] value" and splits it
+ * into components "type" and "value". if a double colon separates type from
+ * value, then value is encoded in base 64, and parse_line un-decodes it
+ * (in place) before returning.
+ * in LDIF, non-ASCII data is treated as base64 encoded UTF-8
+ */
+
+nsresult nsAbLDIFService::str_parse_line(char *line, char **type, char **value, int *vlen) const
+{
+ char *p, *s, *d, *byte, *stop;
+ char nib;
+ int i, b64;
+
+ /* skip any leading space */
+ while ( isspace( *line ) ) {
+ line++;
+ }
+ *type = line;
+
+ for ( s = line; *s && *s != ':'; s++ )
+ ; /* NULL */
+ if ( *s == '\0' ) {
+ return NS_ERROR_FAILURE;
+ }
+
+ /* trim any space between type and : */
+ for ( p = s - 1; p > line && isspace( *p ); p-- ) {
+ *p = '\0';
+ }
+ *s++ = '\0';
+
+ /* check for double : - indicates base 64 encoded value */
+ if ( *s == ':' ) {
+ s++;
+ b64 = 1;
+ /* single : - normally encoded value */
+ } else {
+ b64 = 0;
+ }
+
+ /* skip space between : and value */
+ while ( isspace( *s ) ) {
+ s++;
+ }
+
+ /* if no value is present, error out */
+ if ( *s == '\0' ) {
+ return NS_ERROR_FAILURE;
+ }
+
+ /* check for continued line markers that should be deleted */
+ for ( p = s, d = s; *p; p++ ) {
+ if ( *p != CONTINUED_LINE_MARKER )
+ *d++ = *p;
+ }
+ *d = '\0';
+
+ *value = s;
+ if ( b64 ) {
+ stop = PL_strchr( s, '\0' );
+ byte = s;
+ for ( p = s, *vlen = 0; p < stop; p += 4, *vlen += 3 ) {
+ for ( i = 0; i < 3; i++ ) {
+ if ( p[i] != '=' && (p[i] & 0x80 ||
+ b642nib[ p[i] & 0x7f ] > 0x3f) ) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ /* first digit */
+ nib = b642nib[ p[0] & 0x7f ];
+ byte[0] = nib << 2;
+ /* second digit */
+ nib = b642nib[ p[1] & 0x7f ];
+ byte[0] |= nib >> 4;
+ byte[1] = (nib & RIGHT4) << 4;
+ /* third digit */
+ if ( p[2] == '=' ) {
+ *vlen += 1;
+ break;
+ }
+ nib = b642nib[ p[2] & 0x7f ];
+ byte[1] |= nib >> 2;
+ byte[2] = (nib & RIGHT2) << 6;
+ /* fourth digit */
+ if ( p[3] == '=' ) {
+ *vlen += 2;
+ break;
+ }
+ nib = b642nib[ p[3] & 0x7f ];
+ byte[2] |= nib;
+
+ byte += 3;
+ }
+ s[ *vlen ] = '\0';
+ } else {
+ *vlen = (int) (d - s);
+ }
+ return NS_OK;
+}
+
+/*
+ * str_getline - return the next "line" (minus newline) of input from a
+ * string buffer of lines separated by newlines, terminated by \n\n
+ * or \0. this routine handles continued lines, bundling them into
+ * a single big line before returning. if a line begins with a white
+ * space character, it is a continuation of the previous line. the white
+ * space character (nb: only one char), and preceeding newline are changed
+ * into CONTINUED_LINE_MARKER chars, to be deleted later by the
+ * str_parse_line() routine above.
+ *
+ * it takes a pointer to a pointer to the buffer on the first call,
+ * which it updates and must be supplied on subsequent calls.
+ */
+
+char* nsAbLDIFService::str_getline(char **next) const
+{
+ char *lineStr;
+ char c;
+
+ if ( *next == nullptr || **next == '\n' || **next == '\0' ) {
+ return( nullptr);
+ }
+
+ lineStr = *next;
+ while ( (*next = PL_strchr( *next, '\n' )) != NULL ) {
+ c = *(*next + 1);
+ if ( isspace( c ) && c != '\n' ) {
+ **next = CONTINUED_LINE_MARKER;
+ *(*next+1) = CONTINUED_LINE_MARKER;
+ } else {
+ *(*next)++ = '\0';
+ break;
+ }
+ }
+
+ return( lineStr );
+}
+
+nsresult nsAbLDIFService::GetLdifStringRecord(char* buf, int32_t len, int32_t& stopPos)
+{
+ for (; stopPos < len; stopPos++)
+ {
+ char c = buf[stopPos];
+
+ if (c == 0xA)
+ {
+ mLFCount++;
+ }
+ else if (c == 0xD)
+ {
+ mCRCount++;
+ }
+ else
+ {
+ if (mLFCount == 0 && mCRCount == 0)
+ mLdifLine.Append(c);
+ else if (( mLFCount > 1) || ( mCRCount > 2 && mLFCount ) ||
+ ( !mLFCount && mCRCount > 1 ))
+ {
+ return NS_OK;
+ }
+ else if ((mLFCount == 1 || mCRCount == 1))
+ {
+ mLdifLine.Append('\n');
+ mLdifLine.Append(c);
+ mLFCount = 0;
+ mCRCount = 0;
+ }
+ }
+ }
+
+ if (((stopPos == len) && (mLFCount > 1)) || (mCRCount > 2 && mLFCount) ||
+ (!mLFCount && mCRCount > 1))
+ return NS_OK;
+
+ return NS_ERROR_FAILURE;
+}
+
+void nsAbLDIFService::AddLdifRowToDatabase(nsIAddrDatabase *aDatabase,
+ bool bIsList)
+{
+ // If no data to process then reset CR/LF counters and return.
+ if (mLdifLine.IsEmpty())
+ {
+ mLFCount = 0;
+ mCRCount = 0;
+ return;
+ }
+
+ nsCOMPtr <nsIMdbRow> newRow;
+ if (aDatabase)
+ {
+ if (bIsList)
+ aDatabase->GetNewListRow(getter_AddRefs(newRow));
+ else
+ aDatabase->GetNewRow(getter_AddRefs(newRow));
+
+ if (!newRow)
+ return;
+ }
+ else
+ return;
+
+ char* cursor = ToNewCString(mLdifLine);
+ char* saveCursor = cursor; /* keep for deleting */
+ char* line = 0;
+ char* typeSlot = 0;
+ char* valueSlot = 0;
+ int length = 0; // the length of an ldif attribute
+ while ( (line = str_getline(&cursor)) != nullptr)
+ {
+ if (NS_SUCCEEDED(str_parse_line(line, &typeSlot, &valueSlot, &length))) {
+ AddLdifColToDatabase(aDatabase, newRow, typeSlot, valueSlot, bIsList);
+ }
+ else
+ continue; // parse error: continue with next loop iteration
+ }
+ free(saveCursor);
+ aDatabase->AddCardRowToDB(newRow);
+
+ if (bIsList)
+ aDatabase->AddListDirNode(newRow);
+
+ // Clear buffer for next record
+ ClearLdifRecordBuffer();
+}
+
+void nsAbLDIFService::AddLdifColToDatabase(nsIAddrDatabase *aDatabase,
+ nsIMdbRow* newRow, char* typeSlot,
+ char* valueSlot, bool bIsList)
+{
+ nsAutoCString colType(typeSlot);
+ nsAutoCString column(valueSlot);
+
+ // 4.x exports attributes like "givenname",
+ // mozilla does "givenName" to be compliant with RFC 2798
+ ToLowerCase(colType);
+
+ mdb_u1 firstByte = (mdb_u1)(colType.get())[0];
+ switch ( firstByte )
+ {
+ case 'b':
+ if (colType.EqualsLiteral("birthyear"))
+ aDatabase->AddBirthYear(newRow, column.get());
+ else if (colType.EqualsLiteral("birthmonth"))
+ aDatabase->AddBirthMonth(newRow, column.get());
+ else if (colType.EqualsLiteral("birthday"))
+ aDatabase->AddBirthDay(newRow, column.get());
+ break; // 'b'
+
+ case 'c':
+ if (colType.EqualsLiteral("cn") || colType.EqualsLiteral("commonname"))
+ {
+ if (bIsList)
+ aDatabase->AddListName(newRow, column.get());
+ else
+ aDatabase->AddDisplayName(newRow, column.get());
+ }
+ else if (colType.EqualsLiteral("c") || colType.EqualsLiteral("countryname"))
+ {
+ if (mStoreLocAsHome )
+ aDatabase->AddHomeCountry(newRow, column.get());
+ else
+ aDatabase->AddWorkCountry(newRow, column.get());
+ }
+
+ else if (colType.EqualsLiteral("cellphone") )
+ aDatabase->AddCellularNumber(newRow, column.get());
+
+ else if (colType.EqualsLiteral("carphone"))
+ aDatabase->AddCellularNumber(newRow, column.get());
+
+ else if (colType.EqualsLiteral("custom1"))
+ aDatabase->AddCustom1(newRow, column.get());
+
+ else if (colType.EqualsLiteral("custom2"))
+ aDatabase->AddCustom2(newRow, column.get());
+
+ else if (colType.EqualsLiteral("custom3"))
+ aDatabase->AddCustom3(newRow, column.get());
+
+ else if (colType.EqualsLiteral("custom4"))
+ aDatabase->AddCustom4(newRow, column.get());
+
+ else if (colType.EqualsLiteral("company"))
+ aDatabase->AddCompany(newRow, column.get());
+ break; // 'c'
+
+ case 'd':
+ if (colType.EqualsLiteral("description"))
+ {
+ if (bIsList)
+ aDatabase->AddListDescription(newRow, column.get());
+ else
+ aDatabase->AddNotes(newRow, column.get());
+ }
+
+ else if (colType.EqualsLiteral("department"))
+ aDatabase->AddDepartment(newRow, column.get());
+
+ else if (colType.EqualsLiteral("displayname"))
+ {
+ if (bIsList)
+ aDatabase->AddListName(newRow, column.get());
+ else
+ aDatabase->AddDisplayName(newRow, column.get());
+ }
+ break; // 'd'
+
+ case 'f':
+
+ if (colType.EqualsLiteral("fax") ||
+ colType.EqualsLiteral("facsimiletelephonenumber"))
+ aDatabase->AddFaxNumber(newRow, column.get());
+ break; // 'f'
+
+ case 'g':
+ if (colType.EqualsLiteral("givenname"))
+ aDatabase->AddFirstName(newRow, column.get());
+
+ break; // 'g'
+
+ case 'h':
+ if (colType.EqualsLiteral("homephone"))
+ aDatabase->AddHomePhone(newRow, column.get());
+
+ else if (colType.EqualsLiteral("homestreet"))
+ aDatabase->AddHomeAddress(newRow, column.get());
+
+ else if (colType.EqualsLiteral("homeurl"))
+ aDatabase->AddWebPage2(newRow, column.get());
+ break; // 'h'
+
+ case 'l':
+ if (colType.EqualsLiteral("l") || colType.EqualsLiteral("locality"))
+ {
+ if (mStoreLocAsHome)
+ aDatabase->AddHomeCity(newRow, column.get());
+ else
+ aDatabase->AddWorkCity(newRow, column.get());
+ }
+ // labeledURI contains a URI and, optionally, a label
+ // This will remove the label and place the URI as the work URL
+ else if (colType.EqualsLiteral("labeleduri"))
+ {
+ int32_t index = column.FindChar(' ');
+ if (index != -1)
+ column.SetLength(index);
+
+ aDatabase->AddWebPage1(newRow, column.get());
+ }
+
+ break; // 'l'
+
+ case 'm':
+ if (colType.EqualsLiteral("mail"))
+ aDatabase->AddPrimaryEmail(newRow, column.get());
+
+ else if (colType.EqualsLiteral("member") && bIsList)
+ aDatabase->AddLdifListMember(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mobile"))
+ aDatabase->AddCellularNumber(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozilla_aimscreenname"))
+ aDatabase->AddAimScreenName(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillacustom1"))
+ aDatabase->AddCustom1(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillacustom2"))
+ aDatabase->AddCustom2(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillacustom3"))
+ aDatabase->AddCustom3(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillacustom4"))
+ aDatabase->AddCustom4(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomecountryname"))
+ aDatabase->AddHomeCountry(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomelocalityname"))
+ aDatabase->AddHomeCity(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomestate"))
+ aDatabase->AddHomeState(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomestreet"))
+ aDatabase->AddHomeAddress(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomestreet2"))
+ aDatabase->AddHomeAddress2(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomepostalcode"))
+ aDatabase->AddHomeZipCode(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillahomeurl"))
+ aDatabase->AddWebPage2(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillanickname"))
+ {
+ if (bIsList)
+ aDatabase->AddListNickName(newRow, column.get());
+ else
+ aDatabase->AddNickName(newRow, column.get());
+ }
+
+ else if (colType.EqualsLiteral("mozillasecondemail"))
+ aDatabase->Add2ndEmail(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillausehtmlmail"))
+ {
+ ToLowerCase(column);
+ if (-1 != column.Find("true"))
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::html);
+ else if (-1 != column.Find("false"))
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::plaintext);
+ else
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::unknown);
+ }
+
+ else if (colType.EqualsLiteral("mozillaworkstreet2"))
+ aDatabase->AddWorkAddress2(newRow, column.get());
+
+ else if (colType.EqualsLiteral("mozillaworkurl"))
+ aDatabase->AddWebPage1(newRow, column.get());
+
+ break; // 'm'
+
+ case 'n':
+ if (colType.EqualsLiteral("notes"))
+ aDatabase->AddNotes(newRow, column.get());
+
+ else if (colType.EqualsLiteral("nscpaimscreenname") ||
+ colType.EqualsLiteral("nsaimid"))
+ aDatabase->AddAimScreenName(newRow, column.get());
+
+ break; // 'n'
+
+ case 'o':
+ if (colType.EqualsLiteral("objectclass"))
+ break;
+
+ else if (colType.EqualsLiteral("ou") || colType.EqualsLiteral("orgunit"))
+ aDatabase->AddDepartment(newRow, column.get());
+
+ else if (colType.EqualsLiteral("o")) // organization
+ aDatabase->AddCompany(newRow, column.get());
+
+ break; // 'o'
+
+ case 'p':
+ if (colType.EqualsLiteral("postalcode"))
+ {
+ if (mStoreLocAsHome)
+ aDatabase->AddHomeZipCode(newRow, column.get());
+ else
+ aDatabase->AddWorkZipCode(newRow, column.get());
+ }
+
+ else if (colType.EqualsLiteral("postofficebox"))
+ {
+ nsAutoCString workAddr1, workAddr2;
+ SplitCRLFAddressField(column, workAddr1, workAddr2);
+ aDatabase->AddWorkAddress(newRow, workAddr1.get());
+ aDatabase->AddWorkAddress2(newRow, workAddr2.get());
+ }
+ else if (colType.EqualsLiteral("pager") || colType.EqualsLiteral("pagerphone"))
+ aDatabase->AddPagerNumber(newRow, column.get());
+
+ break; // 'p'
+
+ case 'r':
+ if (colType.EqualsLiteral("region"))
+ {
+ aDatabase->AddWorkState(newRow, column.get());
+ }
+
+ break; // 'r'
+
+ case 's':
+ if (colType.EqualsLiteral("sn") || colType.EqualsLiteral("surname"))
+ aDatabase->AddLastName(newRow, column.get());
+
+ else if (colType.EqualsLiteral("street"))
+ aDatabase->AddWorkAddress(newRow, column.get());
+
+ else if (colType.EqualsLiteral("streetaddress"))
+ {
+ nsAutoCString addr1, addr2;
+ SplitCRLFAddressField(column, addr1, addr2);
+ if (mStoreLocAsHome)
+ {
+ aDatabase->AddHomeAddress(newRow, addr1.get());
+ aDatabase->AddHomeAddress2(newRow, addr2.get());
+ }
+ else
+ {
+ aDatabase->AddWorkAddress(newRow, addr1.get());
+ aDatabase->AddWorkAddress2(newRow, addr2.get());
+ }
+ }
+ else if (colType.EqualsLiteral("st"))
+ {
+ if (mStoreLocAsHome)
+ aDatabase->AddHomeState(newRow, column.get());
+ else
+ aDatabase->AddWorkState(newRow, column.get());
+ }
+
+ break; // 's'
+
+ case 't':
+ if (colType.EqualsLiteral("title"))
+ aDatabase->AddJobTitle(newRow, column.get());
+
+ else if (colType.EqualsLiteral("telephonenumber") )
+ {
+ aDatabase->AddWorkPhone(newRow, column.get());
+ }
+
+ break; // 't'
+
+ case 'u':
+
+ if (colType.EqualsLiteral("uniquemember") && bIsList)
+ aDatabase->AddLdifListMember(newRow, column.get());
+
+ break; // 'u'
+
+ case 'w':
+ if (colType.EqualsLiteral("workurl"))
+ aDatabase->AddWebPage1(newRow, column.get());
+
+ break; // 'w'
+
+ case 'x':
+ if (colType.EqualsLiteral("xmozillanickname"))
+ {
+ if (bIsList)
+ aDatabase->AddListNickName(newRow, column.get());
+ else
+ aDatabase->AddNickName(newRow, column.get());
+ }
+
+ else if (colType.EqualsLiteral("xmozillausehtmlmail"))
+ {
+ ToLowerCase(column);
+ if (-1 != column.Find("true"))
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::html);
+ else if (-1 != column.Find("false"))
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::plaintext);
+ else
+ aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::unknown);
+ }
+
+ break; // 'x'
+
+ case 'z':
+ if (colType.EqualsLiteral("zip")) // alias for postalcode
+ {
+ if (mStoreLocAsHome)
+ aDatabase->AddHomeZipCode(newRow, column.get());
+ else
+ aDatabase->AddWorkZipCode(newRow, column.get());
+ }
+
+ break; // 'z'
+
+ default:
+ break; // default
+ }
+}
+
+void nsAbLDIFService::ClearLdifRecordBuffer()
+{
+ if (!mLdifLine.IsEmpty())
+ {
+ mLdifLine.Truncate();
+ mLFCount = 0;
+ mCRCount = 0;
+ }
+}
+
+// Some common ldif fields, it an ldif file has NONE of these entries
+// then it is most likely NOT an ldif file!
+static const char *const sLDIFFields[] = {
+ "objectclass",
+ "sn",
+ "dn",
+ "cn",
+ "givenName",
+ "mail",
+ nullptr
+};
+#define kMaxLDIFLen 14
+
+// Count total number of legal ldif fields and records in the first 100 lines of the
+// file and if the average legal ldif field is 3 or higher than it's a valid ldif file.
+NS_IMETHODIMP nsAbLDIFService::IsLDIFFile(nsIFile *pSrc, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(pSrc);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = false;
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIInputStream> fileStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), pSrc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t lineLen = 0;
+ int32_t lineCount = 0;
+ int32_t ldifFields = 0; // total number of legal ldif fields.
+ char field[kMaxLDIFLen];
+ int32_t fLen = 0;
+ const char *pChar;
+ int32_t recCount = 0; // total number of records.
+ int32_t i;
+ bool gotLDIF = false;
+ bool more = true;
+ nsCString line;
+
+ while (more && NS_SUCCEEDED(rv) && (lineCount < 100))
+ {
+ rv = lineInputStream->ReadLine(line, &more);
+
+ if (NS_SUCCEEDED(rv) && more)
+ {
+ pChar = line.get();
+ lineLen = line.Length();
+ if (!lineLen && gotLDIF)
+ {
+ recCount++;
+ gotLDIF = false;
+ }
+
+ if (lineLen && (*pChar != ' ') && (*pChar != '\t'))
+ {
+ fLen = 0;
+
+ while (lineLen && (fLen < (kMaxLDIFLen - 1)) && (*pChar != ':'))
+ {
+ field[fLen] = *pChar;
+ pChar++;
+ fLen++;
+ lineLen--;
+ }
+
+ field[fLen] = 0;
+
+ if (lineLen && (*pChar == ':') && (fLen < (kMaxLDIFLen - 1)))
+ {
+ // see if this is an ldif field (case insensitive)?
+ i = 0;
+ while (sLDIFFields[i])
+ {
+ if (!PL_strcasecmp( sLDIFFields[i], field))
+ {
+ ldifFields++;
+ gotLDIF = true;
+ break;
+ }
+ i++;
+ }
+ }
+ }
+ }
+ lineCount++;
+ }
+
+ // If we just saw ldif address, increment recCount.
+ if (gotLDIF)
+ recCount++;
+
+ rv = fileStream->Close();
+
+ if (recCount > 1)
+ ldifFields /= recCount;
+
+ // If the average field number >= 3 then it's a good ldif file.
+ if (ldifFields >= 3)
+ {
+ *_retval = true;
+ }
+
+ return rv;
+}
+
+void nsAbLDIFService::SplitCRLFAddressField(nsCString &inputAddress, nsCString &outputLine1, nsCString &outputLine2) const
+{
+ int32_t crlfPos = inputAddress.Find("\r\n");
+ if (crlfPos != -1)
+ {
+ outputLine1 = Substring(inputAddress, 0, crlfPos);
+ outputLine2 = Substring(inputAddress, crlfPos + 2);
+ }
+ else
+ outputLine1.Assign(inputAddress);
+}
+
diff --git a/mailnews/addrbook/src/nsAbLDIFService.h b/mailnews/addrbook/src/nsAbLDIFService.h
new file mode 100644
index 000000000..8f50559c9
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDIFService.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef __nsAbLDIFService_h
+#define __nsAbLDIFService_h
+
+#include "nsIAbLDIFService.h"
+#include "nsCOMPtr.h"
+
+class nsIMdbRow;
+
+class nsAbLDIFService : public nsIAbLDIFService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABLDIFSERVICE
+
+ nsAbLDIFService();
+private:
+ virtual ~nsAbLDIFService();
+ nsresult str_parse_line(char *line, char **type, char **value, int *vlen) const;
+ char * str_getline(char **next) const;
+ nsresult GetLdifStringRecord(char* buf, int32_t len, int32_t& stopPos);
+ void AddLdifRowToDatabase(nsIAddrDatabase *aDatabase, bool aIsList);
+ void AddLdifColToDatabase(nsIAddrDatabase *aDatabase, nsIMdbRow* newRow,
+ char* typeSlot, char* valueSlot, bool bIsList);
+ void ClearLdifRecordBuffer();
+ void SplitCRLFAddressField(nsCString &inputAddress, nsCString &outputLine1, nsCString &outputLine2) const;
+
+ bool mStoreLocAsHome;
+ nsCString mLdifLine;
+ int32_t mLFCount;
+ int32_t mCRCount;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbMDBCard.cpp b/mailnews/addrbook/src/nsAbMDBCard.cpp
new file mode 100644
index 000000000..14af6a875
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBCard.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbMDBCard.h"
+
+nsAbMDBCard::nsAbMDBCard(void)
+{
+}
+
+nsAbMDBCard::~nsAbMDBCard(void)
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsAbMDBCard, nsAbCardProperty)
+
+NS_IMETHODIMP nsAbMDBCard::Equals(nsIAbCard *card, bool *result)
+{
+ NS_ENSURE_ARG_POINTER(card);
+ NS_ENSURE_ARG_POINTER(result);
+
+ if (this == card) {
+ *result = true;
+ return NS_OK;
+ }
+
+ // If we have the same directory, we will equal the other card merely given
+ // the row IDs. If not, we are never equal. But we are dumb in that we don't
+ // know who our directory is, which may change in the future. For now,
+ // however, the only known users of this method are for locating us in a list
+ // of cards, most commonly mailing lists; a warning on the IDL has also
+ // notified consumers that this method is not generally safe to use. In this
+ // respect, it is safe to assume that the directory portion is satisfied when
+ // making this call.
+ // However, if we make the wrong assumption, one of two things will happen.
+ // If the other directory is a local address book, we could return a spurious
+ // true result. If not, then DbRowID should be unset and we can definitively
+ // return false.
+
+ uint32_t row;
+ nsresult rv = card->GetPropertyAsUint32("DbRowID", &row);
+ if (NS_FAILED(rv))
+ {
+ *result = false;
+ return NS_OK;
+ }
+
+ uint32_t ourRow;
+ rv = GetPropertyAsUint32("DbRowID", &ourRow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *result = (row == ourRow);
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbMDBCard.h b/mailnews/addrbook/src/nsAbMDBCard.h
new file mode 100644
index 000000000..0d9123bc4
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBCard.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbMDBCard_h__
+#define nsAbMDBCard_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbCardProperty.h"
+#include "nsCOMPtr.h"
+
+class nsAbMDBCard: public nsAbCardProperty
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsAbMDBCard(void);
+
+ NS_IMETHOD Equals(nsIAbCard *card, bool *result) override;
+
+private:
+ virtual ~nsAbMDBCard();
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbMDBDirFactory.cpp b/mailnews/addrbook/src/nsAbMDBDirFactory.cpp
new file mode 100644
index 000000000..546a80d17
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirFactory.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbMDBDirFactory.h"
+#include "nsAbUtils.h"
+#include "nsStringGlue.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+#include "nsIAbManager.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsAbMDBDirFactory.h"
+#include "nsIAddrDatabase.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsAbBaseCID.h"
+
+NS_IMPL_ISUPPORTS(nsAbMDBDirFactory, nsIAbDirFactory)
+
+nsAbMDBDirFactory::nsAbMDBDirFactory()
+{
+}
+
+nsAbMDBDirFactory::~nsAbMDBDirFactory()
+{
+}
+
+NS_IMETHODIMP nsAbMDBDirFactory::GetDirectories(const nsAString &aDirName,
+ const nsACString &aURI,
+ const nsACString &aPrefName,
+ nsISimpleEnumerator **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(aURI, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->SetDirPrefId(aPrefName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dbPath;
+ rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath));
+
+ nsCOMPtr<nsIAddrDatabase> listDatabase;
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString fileName;
+
+ if (StringBeginsWith(aURI, NS_LITERAL_CSTRING(kMDBDirectoryRoot)))
+ fileName = Substring(aURI, kMDBDirectoryRootLen, aURI.Length() - kMDBDirectoryRootLen);
+
+ rv = dbPath->AppendNative(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory = do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = addrDBFactory->Open(dbPath, true, true, getter_AddRefs(listDatabase));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = listDatabase->GetMailingListsFromDB(directory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewSingletonEnumerator(_retval, directory);
+}
+
+/* void deleteDirectory (in nsIAbDirectory directory); */
+NS_IMETHODIMP nsAbMDBDirFactory::DeleteDirectory(nsIAbDirectory *directory)
+{
+ if (!directory)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMutableArray> pAddressLists;
+ rv = directory->GetAddressLists(getter_AddRefs(pAddressLists));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t total;
+ rv = pAddressLists->GetLength(&total);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < total; i++)
+ {
+ nsCOMPtr<nsIAbDirectory> listDir(do_QueryElementAt(pAddressLists, i, &rv));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIAbMDBDirectory> dblistDir(do_QueryInterface(listDir, &rv));
+ if (NS_FAILED(rv))
+ break;
+
+ rv = directory->DeleteDirectory(listDir);
+ if (NS_FAILED(rv))
+ break;
+
+ rv = dblistDir->RemoveElementsFromAddressList();
+ if (NS_FAILED(rv))
+ break;
+ }
+ pAddressLists->Clear();
+
+ nsCOMPtr<nsIAbMDBDirectory> dbdirectory(do_QueryInterface(directory, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return dbdirectory->ClearDatabase();
+}
+
diff --git a/mailnews/addrbook/src/nsAbMDBDirFactory.h b/mailnews/addrbook/src/nsAbMDBDirFactory.h
new file mode 100644
index 000000000..200175f62
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirFactory.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbMDBDirFactory_h__
+#define nsAbMDBDirFactory_h__
+
+#include "nsIAbDirFactory.h"
+
+class nsAbMDBDirFactory : public nsIAbDirFactory
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRFACTORY
+
+ nsAbMDBDirFactory();
+
+private:
+ virtual ~nsAbMDBDirFactory();
+};
+
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbMDBDirProperty.cpp b/mailnews/addrbook/src/nsAbMDBDirProperty.cpp
new file mode 100644
index 000000000..1a7ae59d7
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirProperty.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbMDBDirProperty.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+#include "nsAbBaseCID.h"
+#include "nsAddrDatabase.h"
+#include "nsIAbCard.h"
+#include "nsArrayUtils.h"
+#include "mdb.h"
+#include "nsComponentManagerUtils.h"
+
+nsAbMDBDirProperty::nsAbMDBDirProperty(void)
+ : nsAbDirProperty()
+{
+ m_dbRowID = 0;
+}
+
+nsAbMDBDirProperty::~nsAbMDBDirProperty(void)
+{
+}
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbMDBDirProperty, nsAbDirProperty,
+ nsIAbDirectory,
+ nsISupportsWeakReference, nsIAbMDBDirectory)
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+
+// nsIAbMDBDirectory attributes
+
+NS_IMETHODIMP nsAbMDBDirProperty::GetDbRowID(uint32_t *aDbRowID)
+{
+ *aDbRowID = m_dbRowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirProperty::SetDbRowID(uint32_t aDbRowID)
+{
+ m_dbRowID = aDbRowID;
+ return NS_OK;
+}
+
+
+// nsIAbMDBDirectory methods
+
+/* add mailing list to the parent directory */
+NS_IMETHODIMP nsAbMDBDirProperty::AddMailListToDirectory(nsIAbDirectory *mailList)
+{
+ if (!m_AddressList)
+ {
+ nsresult rv;
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint32_t position;
+ if (NS_FAILED(m_AddressList->IndexOf(0, mailList, &position)))
+ m_AddressList->AppendElement(mailList, false);
+
+ return NS_OK;
+}
+
+/* add addresses to the mailing list */
+NS_IMETHODIMP nsAbMDBDirProperty::AddAddressToList(nsIAbCard *card)
+{
+ if (!m_AddressList)
+ {
+ nsresult rv;
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint32_t position;
+ if (NS_FAILED(m_AddressList->IndexOf(0, card, &position)))
+ m_AddressList->AppendElement(card, false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirProperty::CopyDBMailList(nsIAbMDBDirectory* srcListDB)
+{
+ nsresult err = NS_OK;
+ nsCOMPtr<nsIAbDirectory> srcList(do_QueryInterface(srcListDB));
+ if (NS_FAILED(err))
+ return NS_ERROR_NULL_POINTER;
+
+ CopyMailList (srcList);
+
+ uint32_t rowID;
+ srcListDB->GetDbRowID(&rowID);
+ SetDbRowID(rowID);
+
+ return NS_OK;
+}
+
+
+// nsIAbMDBDirectory NOT IMPLEMENTED methods
+
+/* nsIAbDirectory addDirectory (in string uriName); */
+NS_IMETHODIMP nsAbMDBDirProperty::AddDirectory(const char *uriName, nsIAbDirectory **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void removeElementsFromAddressList (); */
+NS_IMETHODIMP nsAbMDBDirProperty::RemoveElementsFromAddressList()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void removeEmailAddressAt (in unsigned long aIndex); */
+NS_IMETHODIMP nsAbMDBDirProperty::RemoveEmailAddressAt(uint32_t aIndex)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void notifyDirItemAdded (in nsISupports item); */
+NS_IMETHODIMP nsAbMDBDirProperty::NotifyDirItemAdded(nsISupports *item)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void clearDatabase (); */
+NS_IMETHODIMP nsAbMDBDirProperty::ClearDatabase()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbMDBDirProperty::GetDatabaseFile(nsIFile **aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbMDBDirProperty::GetDatabase(nsIAddrDatabase **aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/mailnews/addrbook/src/nsAbMDBDirProperty.h b/mailnews/addrbook/src/nsAbMDBDirProperty.h
new file mode 100644
index 000000000..fce4db297
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirProperty.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/********************************************************************************************************
+
+ Interface for representing Address Book Directory
+
+*********************************************************************************************************/
+
+#ifndef nsAbMDBDirProperty_h__
+#define nsAbMDBDirProperty_h__
+
+#include "nsIAbMDBDirectory.h"
+#include "nsAbDirProperty.h"
+#include "nsIAbCard.h"
+#include "nsCOMPtr.h"
+#include "nsDirPrefs.h"
+#include "nsIAddrDatabase.h"
+
+ /*
+ * Address Book Directory
+ */
+
+class nsAbMDBDirProperty: public nsIAbMDBDirectory, public nsAbDirProperty
+{
+public:
+ nsAbMDBDirProperty(void);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIABMDBDIRECTORY
+
+protected:
+ virtual ~nsAbMDBDirProperty();
+
+ uint32_t m_dbRowID;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbMDBDirectory.cpp b/mailnews/addrbook/src/nsAbMDBDirectory.cpp
new file mode 100644
index 000000000..cb212158e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirectory.cpp
@@ -0,0 +1,1123 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbMDBDirectory.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+#include "nsAbBaseCID.h"
+#include "nsAddrDatabase.h"
+#include "nsIAbManager.h"
+#include "nsNetCID.h"
+#include "nsAbDirectoryQuery.h"
+#include "nsIAbDirectoryQueryProxy.h"
+#include "nsAbQueryStringToExpression.h"
+#include "nsIMutableArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsEnumeratorUtils.h"
+#include "mdb.h"
+#include "prprf.h"
+#include "nsIPrefService.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "nsArrayUtils.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/DebugOnly.h"
+
+nsAbMDBDirectory::nsAbMDBDirectory(void):
+ nsAbMDBDirProperty(),
+ mPerformingQuery(false)
+{
+}
+
+nsAbMDBDirectory::~nsAbMDBDirectory(void)
+{
+ if (mDatabase) {
+ mDatabase->RemoveListener(this);
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbMDBDirectory, nsAbMDBDirProperty,
+ nsIAbDirSearchListener,
+ nsIAbDirectorySearch,
+ nsIAddrDBListener)
+
+NS_IMETHODIMP nsAbMDBDirectory::Init(const char *aUri)
+{
+ // We need to ensure that the m_DirPrefId is initialized properly
+ nsDependentCString uri(aUri);
+
+ // Find the first ? (of the search params) if there is one.
+ // We know we can start at the end of the moz-abmdbdirectory:// because
+ // that's the URI we should have been passed.
+ int32_t searchCharLocation = uri.FindChar('?', kMDBDirectoryRootLen);
+ nsAutoCString URINoQuery;
+ if (searchCharLocation != kNotFound)
+ {
+ URINoQuery = Substring(uri, 0, searchCharLocation);
+ } else {
+ URINoQuery.Assign(uri);
+ }
+
+ // In the non-query part of the URI, check if we are a mailinglist
+ if (URINoQuery.Find("MailList") != kNotFound)
+ m_IsMailList = true;
+
+ // Mailing lists don't have their own prefs.
+ if (m_DirPrefId.IsEmpty() && !m_IsMailList)
+ {
+ nsAutoCString filename;
+
+ // Extract the filename from the uri.
+ filename = Substring(URINoQuery, kMDBDirectoryRootLen);
+
+ // Get the pref servers and the address book directory branch
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefService->GetBranch(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME ".").get(),
+ getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char** childArray;
+ uint32_t childCount, i;
+ int32_t dotOffset;
+ nsCString childValue;
+ nsDependentCString child;
+
+ rv = prefBranch->GetChildList("", &childCount, &childArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (i = 0; i < childCount; ++i)
+ {
+ child.Assign(childArray[i]);
+
+ if (StringEndsWith(child, NS_LITERAL_CSTRING(".filename")))
+ {
+ if (NS_SUCCEEDED(prefBranch->GetCharPref(child.get(),
+ getter_Copies(childValue))))
+ {
+ if (childValue == filename)
+ {
+ dotOffset = child.RFindChar('.');
+ if (dotOffset != -1)
+ {
+ nsAutoCString prefName(StringHead(child, dotOffset));
+ m_DirPrefId.AssignLiteral(PREF_LDAP_SERVER_TREE_NAME ".");
+ m_DirPrefId.Append(prefName);
+ }
+ }
+ }
+ }
+ }
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(childCount, childArray);
+
+ NS_ASSERTION(!m_DirPrefId.IsEmpty(),
+ "Error, Could not set m_DirPrefId in nsAbMDBDirectory::Init");
+ }
+
+ return nsAbDirProperty::Init(aUri);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsAbMDBDirectory::RemoveCardFromAddressList(nsIAbCard* card)
+{
+ nsresult rv = NS_OK;
+ uint32_t listTotal;
+ int32_t i, j;
+
+ // These checks ensure we don't run into null pointers
+ // as we did when we caused bug 280463.
+ if (!mDatabase)
+ {
+ rv = GetAbDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!m_AddressList)
+ {
+ rv = mDatabase->GetMailingListsFromDB(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the previous call didn't gives us an m_AddressList (and succeeded)
+ // then we haven't got any mailing lists to try and remove the card from.
+ // So just return without doing anything
+ if (!m_AddressList)
+ return NS_OK;
+ }
+
+ rv = m_AddressList->GetLength(&listTotal);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ for (i = listTotal - 1; i >= 0; i--)
+ {
+ nsCOMPtr<nsIAbDirectory> listDir(do_QueryElementAt(m_AddressList, i, &rv));
+ if (listDir)
+ {
+ // First remove the instance in the database
+ mDatabase->DeleteCardFromMailList(listDir, card, false);
+
+ // Now remove the instance in any lists we hold.
+ nsCOMPtr<nsIMutableArray> pAddressLists;
+ listDir->GetAddressLists(getter_AddRefs(pAddressLists));
+ if (pAddressLists)
+ {
+ uint32_t total;
+ rv = pAddressLists->GetLength(&total);
+ for (j = total - 1; j >= 0; j--)
+ {
+ nsCOMPtr<nsIAbCard> cardInList(do_QueryElementAt(pAddressLists, j, &rv));
+ bool equals;
+ rv = cardInList->Equals(card, &equals); // should we checking email?
+ if (NS_SUCCEEDED(rv) && equals)
+ pAddressLists->RemoveElementAt(j);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::DeleteDirectory(nsIAbDirectory *directory)
+{
+ NS_ENSURE_ARG_POINTER(directory);
+
+ nsCOMPtr<nsIAddrDatabase> database;
+ nsresult rv = GetDatabase(getter_AddRefs(database));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = database->DeleteMailList(directory, this);
+
+ if (NS_SUCCEEDED(rv))
+ database->Commit(nsAddrDBCommitType::kLargeCommit);
+
+ uint32_t dirIndex;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, directory, &dirIndex)))
+ m_AddressList->RemoveElementAt(dirIndex);
+ // XXX Cast from bool to nsresult
+ rv = static_cast<nsresult>(mSubDirectories.RemoveObject(directory));
+
+ NotifyItemDeleted(directory);
+ return rv;
+}
+
+nsresult nsAbMDBDirectory::NotifyItemChanged(nsISupports *item)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = abManager->NotifyItemPropertyChanged(item, nullptr, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsAbMDBDirectory::NotifyPropertyChanged(nsIAbDirectory *list, const char *property, const char16_t* oldValue, const char16_t* newValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> supports = do_QueryInterface(list, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = abManager->NotifyItemPropertyChanged(supports, property, oldValue, newValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsAbMDBDirectory::NotifyItemAdded(nsISupports *item)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if(NS_SUCCEEDED(rv))
+ abManager->NotifyDirectoryItemAdded(this, item);
+ return NS_OK;
+}
+
+nsresult nsAbMDBDirectory::NotifyItemDeleted(nsISupports *item)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if(NS_SUCCEEDED(rv))
+ abManager->NotifyDirectoryItemDeleted(this, item);
+
+ return NS_OK;
+}
+
+// nsIAbMDBDirectory methods
+
+NS_IMETHODIMP nsAbMDBDirectory::ClearDatabase()
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (mDatabase)
+ {
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::RemoveElementsFromAddressList()
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (m_AddressList)
+ {
+ uint32_t count;
+ mozilla::DebugOnly<nsresult> rv = m_AddressList->GetLength(&count);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Count failed");
+ int32_t i;
+ for (i = count - 1; i >= 0; i--)
+ m_AddressList->RemoveElementAt(i);
+ }
+ m_AddressList = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::RemoveEmailAddressAt(uint32_t aIndex)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (m_AddressList)
+ {
+ return m_AddressList->RemoveElementAt(aIndex);
+ }
+ else
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::AddDirectory(const char *uriName, nsIAbDirectory **childDir)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (!childDir || !uriName)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(nsDependentCString(uriName), getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mSubDirectories.IndexOf(directory) == -1)
+ mSubDirectories.AppendObject(directory);
+ NS_IF_ADDREF(*childDir = directory);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetDatabaseFile(nsIFile **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCString fileName;
+ nsresult rv = GetStringValue("filename", EmptyCString(), fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fileName.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIFile> dbFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dbFile->AppendNative(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = dbFile);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetDatabase(nsIAddrDatabase **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> databaseFile;
+ rv = GetDatabaseFile(getter_AddRefs(databaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory =
+ do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return addrDBFactory->Open(databaseFile, false /* no create */, true,
+ aResult);
+}
+
+// nsIAbDirectory methods
+
+NS_IMETHODIMP nsAbMDBDirectory::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetChildNodes(nsISimpleEnumerator* *aResult)
+{
+ if (mIsQueryURI)
+ return NS_NewEmptyEnumerator(aResult);
+
+ return NS_NewArrayEnumerator(aResult, mSubDirectories);
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetChildCards(nsISimpleEnumerator* *result)
+{
+ nsresult rv;
+
+ if (mIsQueryURI)
+ {
+ rv = StartSearch();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // TODO
+ // Search is synchronous so need to return
+ // results after search is complete
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ for (auto iter = mSearchCache.Iter(); !iter.Done(); iter.Next()) {
+ array->AppendElement(iter.Data(), false);
+ }
+ return NS_NewArrayEnumerator(result, array);
+ }
+
+ rv = GetAbDatabase();
+
+ if (NS_FAILED(rv) || !mDatabase)
+ return rv;
+
+ return m_IsMailList ? mDatabase->EnumerateListAddresses(this, result) :
+ mDatabase->EnumerateCards(this, result);
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetIsQuery(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mIsQueryURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::DeleteCards(nsIArray *aCards)
+{
+ NS_ENSURE_ARG_POINTER(aCards);
+ nsresult rv = NS_OK;
+
+ if (mIsQueryURI) {
+ // if this is a query, delete the cards from the directory (without the query)
+ // before we do the delete, make this directory (which represents the search)
+ // a listener on the database, so that it will get notified when the cards are deleted
+ // after delete, remove this query as a listener.
+ nsCOMPtr<nsIAddrDatabase> database;
+ rv = GetDatabase(getter_AddRefs(database));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = database->AddListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(mURINoQuery, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->DeleteCards(aCards);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = database->RemoveListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+ }
+
+ if (!mDatabase)
+ rv = GetAbDatabase();
+
+ if (NS_SUCCEEDED(rv) && mDatabase)
+ {
+ uint32_t cardCount;
+ uint32_t i;
+ rv = aCards->GetLength(&cardCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (i = 0; i < cardCount; i++)
+ {
+ nsCOMPtr<nsIAbCard> card(do_QueryElementAt(aCards, i, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (card)
+ {
+ uint32_t rowID;
+ rv = card->GetPropertyAsUint32("DbRowID", &rowID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_IsMailList)
+ {
+ mDatabase->DeleteCardFromMailList(this, card, true);
+
+ uint32_t cardTotal = 0;
+ int32_t i;
+ if (m_AddressList)
+ rv = m_AddressList->GetLength(&cardTotal);
+ for (i = cardTotal - 1; i >= 0; i--)
+ {
+ nsCOMPtr<nsIAbCard> arrayCard(do_QueryElementAt(m_AddressList, i, &rv));
+ if (arrayCard)
+ {
+ // No card can have a row ID of 0
+ uint32_t arrayRowID = 0;
+ arrayCard->GetPropertyAsUint32("DbRowID", &arrayRowID);
+ if (rowID == arrayRowID)
+ m_AddressList->RemoveElementAt(i);
+ }
+ }
+ }
+ else
+ {
+ mDatabase->DeleteCard(card, true, this);
+ bool bIsMailList = false;
+ card->GetIsMailList(&bIsMailList);
+ if (bIsMailList)
+ {
+ //to do, get mailing list dir side uri and notify nsIAbManager to remove it
+ nsAutoCString listUri(mURI);
+ listUri.AppendLiteral("/MailList");
+ listUri.AppendInt(rowID);
+ if (!listUri.IsEmpty())
+ {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> listDir;
+ rv = abManager->GetDirectory(listUri, getter_AddRefs(listDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t dirIndex;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, listDir, &dirIndex)))
+ m_AddressList->RemoveElementAt(dirIndex);
+
+ mSubDirectories.RemoveObject(listDir);
+
+ if (listDir)
+ NotifyItemDeleted(listDir);
+ }
+ }
+ else
+ {
+ rv = RemoveCardFromAddressList(card);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ }
+ }
+ mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::HasCard(nsIAbCard *cards, bool *hasCard)
+{
+ if(!hasCard)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mIsQueryURI)
+ {
+ *hasCard = mSearchCache.Get(cards, nullptr);
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ rv = GetAbDatabase();
+
+ if(NS_SUCCEEDED(rv) && mDatabase)
+ {
+ if(NS_SUCCEEDED(rv))
+ rv = mDatabase->ContainsCard(cards, hasCard);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::HasDirectory(nsIAbDirectory *dir, bool *hasDir)
+{
+ if (!hasDir)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbdir(do_QueryInterface(dir, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool bIsMailingList = false;
+ dir->GetIsMailList(&bIsMailingList);
+ if (bIsMailingList)
+ {
+ nsCOMPtr<nsIAddrDatabase> database;
+ rv = GetDatabase(getter_AddRefs(database));
+
+ if (NS_SUCCEEDED(rv))
+ rv = database->ContainsMailList(dir, hasDir);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::HasMailListWithName(const char16_t *aName, bool *aHasList)
+{
+ NS_ENSURE_ARG_POINTER(aHasList);
+
+ nsCOMPtr<nsIAddrDatabase> database;
+ nsresult rv = GetDatabase(getter_AddRefs(database));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = database->FindMailListbyUnicodeName(aName, aHasList);
+ if (NS_SUCCEEDED(rv) && *aHasList)
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::AddMailList(nsIAbDirectory *list, nsIAbDirectory **addedList)
+{
+ NS_ENSURE_ARG_POINTER(addedList);
+
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ rv = GetAbDatabase();
+
+ if (NS_FAILED(rv) || !mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAbMDBDirectory> dblist(do_QueryInterface(list, &rv));
+ if (NS_FAILED(rv))
+ {
+ nsCOMPtr<nsIAbDirectory> newlist(new nsAbMDBDirProperty);
+ if (!newlist)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = newlist->CopyMailList(list);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dblist = do_QueryInterface(newlist, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDatabase->CreateMailListAndAddToDB(newlist, true, this);
+ }
+ else
+ mDatabase->CreateMailListAndAddToDB(list, true, this);
+
+ mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+
+ uint32_t dbRowID;
+ dblist->GetDbRowID(&dbRowID);
+
+ nsAutoCString listUri(mURI);
+ listUri.AppendLiteral("/MailList");
+ listUri.AppendInt(dbRowID);
+
+ nsCOMPtr<nsIAbDirectory> newList;
+ rv = AddDirectory(listUri.get(), getter_AddRefs(newList));
+ if (NS_SUCCEEDED(rv) && newList)
+ {
+ nsCOMPtr<nsIAbMDBDirectory> dbnewList(do_QueryInterface(newList, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dbnewList->CopyDBMailList(dblist);
+ AddMailListToDirectory(newList);
+ NotifyItemAdded(newList);
+ }
+
+ NS_IF_ADDREF(*addedList = newList);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::AddCard(nsIAbCard* card, nsIAbCard **addedCard)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ rv = GetAbDatabase();
+
+ if (NS_FAILED(rv) || !mDatabase)
+ return NS_ERROR_FAILURE;
+
+ if (m_IsMailList)
+ rv = mDatabase->CreateNewListCardAndAddToDB(this, m_dbRowID, card, true /* notify */);
+ else
+ rv = mDatabase->CreateNewCardAndAddToDB(card, true, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+
+ NS_IF_ADDREF(*addedCard = card);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::ModifyCard(nsIAbCard *aModifiedCard)
+{
+ NS_ENSURE_ARG_POINTER(aModifiedCard);
+
+ nsresult rv;
+ if (!mDatabase)
+ {
+ rv = GetAbDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDatabase->EditCard(aModifiedCard, true, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::DropCard(nsIAbCard* aCard, bool needToCopyCard)
+{
+ NS_ENSURE_ARG_POINTER(aCard);
+
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsresult rv = NS_OK;
+
+ if (!mDatabase)
+ rv = GetAbDatabase();
+
+ if (NS_FAILED(rv) || !mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAbCard> newCard;
+
+ if (needToCopyCard) {
+ newCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = newCard->Copy(aCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ newCard = aCard;
+ }
+
+ if (m_IsMailList) {
+ if (needToCopyCard) {
+ nsCOMPtr <nsIMdbRow> cardRow;
+ // if card doesn't exist in db, add the card to the directory that
+ // contains the mailing list.
+ mDatabase->FindRowByCard(newCard, getter_AddRefs(cardRow));
+ if (!cardRow)
+ mDatabase->CreateNewCardAndAddToDB(newCard, true /* notify */, this);
+ else
+ mDatabase->InitCardFromRow(newCard, cardRow);
+ }
+ // since we didn't copy the card, we don't have to notify that it was inserted
+ mDatabase->CreateNewListCardAndAddToDB(this, m_dbRowID, newCard, false /* notify */);
+ }
+ else {
+ mDatabase->CreateNewCardAndAddToDB(newCard, true /* notify */, this);
+ }
+ mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::EditMailListToDatabase(nsIAbCard *listCard)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (!m_IsMailList)
+ return NS_ERROR_UNEXPECTED;
+
+ nsresult rv = GetAbDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDatabase->EditMailList(this, listCard, true);
+ mDatabase->Commit(nsAddrDBCommitType::kLargeCommit);
+
+ return NS_OK;
+}
+
+static bool ContainsDirectory(nsIAbDirectory *parent, nsIAbDirectory *directory)
+{
+ // If parent is a maillist, 'addressLists' contains AbCards.
+ bool bIsMailList = false;
+ nsresult rv = parent->GetIsMailList(&bIsMailList);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (bIsMailList)
+ return false;
+
+ nsCOMPtr<nsIMutableArray> pAddressLists;
+ parent->GetAddressLists(getter_AddRefs(pAddressLists));
+ if (pAddressLists)
+ {
+ uint32_t total;
+ rv = pAddressLists->GetLength(&total);
+ for (uint32_t i = 0; i < total; ++i)
+ {
+ nsCOMPtr<nsIAbDirectory> pList(do_QueryElementAt(pAddressLists, i, &rv));
+
+ if (directory == pList)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// nsIAddrDBListener methods
+
+NS_IMETHODIMP nsAbMDBDirectory::OnCardAttribChange(uint32_t abCode)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::OnCardEntryChange
+(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent)
+{
+ // Don't notify AbManager unless we have the parent
+ if (!aParent)
+ return NS_OK;
+
+ NS_ENSURE_ARG_POINTER(aCard);
+ nsCOMPtr<nsISupports> cardSupports(do_QueryInterface(aCard));
+ nsresult rv;
+
+ // Notify when
+ // - any operation is done to a card belonging to this
+ // => if <this> is <aParent>, or
+ // - a card belonging to a directory which is parent of this is deleted
+ // => if aAbCode is AB_NotifyDeleted && <this> is child of <aParent>, or
+ // - a card belonging to a directory which is child of this is added/modified
+ // => if aAbCode is !AB_NotifyDeleted && <this> is parent of <aParent>
+
+ if (aParent != this)
+ {
+ bool isChild = false;
+ if (aAbCode != AB_NotifyDeleted)
+ isChild = ContainsDirectory(this, aParent);
+ else
+ isChild = ContainsDirectory(aParent, this);
+
+ if (!isChild)
+ return NS_OK;
+ }
+
+ switch (aAbCode) {
+ case AB_NotifyInserted:
+ rv = NotifyItemAdded(cardSupports);
+ break;
+ case AB_NotifyDeleted:
+ rv = NotifyItemDeleted(cardSupports);
+ break;
+ case AB_NotifyPropertyChanged:
+ rv = NotifyItemChanged(cardSupports);
+ break;
+ default:
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::OnListEntryChange
+(uint32_t abCode, nsIAbDirectory *list)
+{
+ nsresult rv = NS_OK;
+
+ if (abCode == AB_NotifyPropertyChanged && list)
+ {
+ bool bIsMailList = false;
+ rv = list->GetIsMailList(&bIsMailList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIAbMDBDirectory> dblist(do_QueryInterface(list, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (bIsMailList) {
+ nsString listName;
+ rv = list->GetDirName(listName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = NotifyPropertyChanged(list, "DirName", nullptr, listName.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::OnAnnouncerGoingAway()
+{
+ if (mDatabase)
+ mDatabase->RemoveListener(this);
+ return NS_OK;
+}
+
+// nsIAbDirectorySearch methods
+
+NS_IMETHODIMP nsAbMDBDirectory::StartSearch()
+{
+ if (!mIsQueryURI)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+
+ mPerformingQuery = true;
+ mSearchCache.Clear();
+
+ nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> expression;
+ rv = nsAbQueryStringToExpression::Convert(mQueryString,
+ getter_AddRefs(expression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetExpression(expression);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // don't search the subdirectories
+ // if the current directory is a mailing list, it won't have any subdirectories
+ // if the current directory is a addressbook, searching both it
+ // and the subdirectories (the mailing lists), will yield duplicate results
+ // because every entry in a mailing list will be an entry in the parent addressbook
+ rv = arguments->SetQuerySubDirectories(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the directory without the query
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(mURINoQuery, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bug 280232 - something was causing continuous loops in searching. Add a
+ // check here for the directory to search not being a query uri as well in
+ // the hopes that will at least break us out of the continuous loop even if
+ // we don't know how we got into it.
+ bool isQuery;
+ rv = directory->GetIsQuery(&isQuery);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isQuery)
+ {
+ NS_ERROR("Attempting to search a directory within a search");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Initiate the proxy query with the no query directory
+ nsCOMPtr<nsIAbDirectoryQueryProxy> queryProxy =
+ do_CreateInstance(NS_ABDIRECTORYQUERYPROXY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = queryProxy->Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = queryProxy->DoQuery(directory, arguments, this, -1, 0, &mContext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::StopSearch()
+{
+ if (!mIsQueryURI)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+
+// nsAbDirSearchListenerContext methods
+
+NS_IMETHODIMP nsAbMDBDirectory::OnSearchFinished(int32_t aResult,
+ const nsAString &aErrorMsg)
+{
+ mPerformingQuery = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::OnSearchFoundCard(nsIAbCard* card)
+{
+ mSearchCache.Put(card, card);
+
+ // TODO
+ // Search is synchronous so asserting on the
+ // datasource will not work since the getChildCards
+ // method will not have returned with results.
+ // NotifyItemAdded (card);
+ return NS_OK;
+}
+
+nsresult nsAbMDBDirectory::GetAbDatabase()
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (mDatabase)
+ return NS_OK;
+
+ nsresult rv;
+
+ if (m_IsMailList)
+ {
+ // Get the database of the parent directory.
+ nsAutoCString parentURI(mURINoQuery);
+
+ int32_t pos = parentURI.RFindChar('/');
+
+ // If we didn't find a / something really bad has happened
+ if (pos == -1)
+ return NS_ERROR_FAILURE;
+
+ parentURI = StringHead(parentURI, pos);
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(parentURI, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbMDBDirectory> mdbDir(do_QueryInterface(directory, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mdbDir->GetDatabase(getter_AddRefs(mDatabase));
+ }
+ else
+ rv = GetDatabase(getter_AddRefs(mDatabase));
+
+ if (NS_SUCCEEDED(rv))
+ rv = mDatabase->AddListener(this);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::CardForEmailAddress(const nsACString &aEmailAddress, nsIAbCard ** aAbCard)
+{
+ NS_ENSURE_ARG_POINTER(aAbCard);
+
+ *aAbCard = nullptr;
+
+ // Ensure that if we've not been given an email address we never match
+ // so that we don't fail out unnecessarily and we don't match a blank email
+ // address against random cards that the user hasn't supplied an email for.
+ if (aEmailAddress.IsEmpty())
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ rv = GetAbDatabase();
+ if (rv == NS_ERROR_FILE_NOT_FOUND)
+ {
+ // If file wasn't found, the card cannot exist.
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Convert Email to lower case in UTF-16 format. This correctly lower-cases
+ // it and doing this change means that we can use a hash lookup in the
+ // database rather than searching and comparing each line individually.
+ NS_ConvertUTF8toUTF16 lowerEmail(aEmailAddress);
+ ToLowerCase(lowerEmail);
+
+ // If lower email is empty, something went wrong somewhere, e.g. the conversion.
+ // Hence, don't go looking for a card with no email address. Something is wrong.
+ if (lowerEmail.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ mDatabase->GetCardFromAttribute(this, kLowerPriEmailColumn,
+ NS_ConvertUTF16toUTF8(lowerEmail),
+ false, aAbCard);
+ if (!*aAbCard)
+ {
+ mDatabase->GetCardFromAttribute(this, kLower2ndEmailColumn,
+ NS_ConvertUTF16toUTF8(lowerEmail),
+ false, aAbCard);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetCardFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsIAbCard **result)
+{
+ NS_ENSURE_ARG(aProperty);
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = nullptr;
+
+ // If the value is empty, don't match.
+ if (aValue.IsEmpty())
+ return NS_OK;
+
+ nsresult rv;
+ if (!mDatabase)
+ {
+ rv = GetAbDatabase();
+ // We can't find the database file, so we can't find the card at all.
+ if (rv == NS_ERROR_FILE_NOT_FOUND)
+ return NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // nsIAddrDatabase has aCaseInsensitive as its parameter
+ return mDatabase->GetCardFromAttribute(this, aProperty, aValue,
+ !caseSensitive, result);
+}
+
+NS_IMETHODIMP nsAbMDBDirectory::GetCardsFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsISimpleEnumerator **result)
+{
+ NS_ENSURE_ARG(aProperty);
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = nullptr;
+
+ if (aValue.IsEmpty())
+ return NS_OK;
+
+ if (!mDatabase)
+ {
+ nsresult rv = GetAbDatabase();
+ if (rv == NS_ERROR_FILE_NOT_FOUND)
+ return NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return mDatabase->GetCardsFromAttribute(this, aProperty, aValue,
+ !caseSensitive, result);
+}
diff --git a/mailnews/addrbook/src/nsAbMDBDirectory.h b/mailnews/addrbook/src/nsAbMDBDirectory.h
new file mode 100644
index 000000000..fb25c5708
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbMDBDirectory.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/********************************************************************************************************
+
+ Interface for representing Address Book Directory
+
+*********************************************************************************************************/
+
+#ifndef nsAbMDBDirectory_h__
+#define nsAbMDBDirectory_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAbMDBDirProperty.h"
+#include "nsIAbCard.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsDirPrefs.h"
+#include "nsIAbDirectorySearch.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIAddrDBListener.h"
+
+/*
+ * Address Book Directory
+ */
+
+class nsAbMDBDirectory:
+ public nsAbMDBDirProperty, // nsIAbDirectory, nsIAbMDBDirectory
+ public nsIAbDirSearchListener,
+ public nsIAddrDBListener,
+ public nsIAbDirectorySearch
+{
+public:
+ nsAbMDBDirectory(void);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIADDRDBLISTENER
+
+ // Override nsAbMDBDirProperty::Init
+ NS_IMETHOD Init(const char *aUri) override;
+
+ // nsIAbMDBDirectory methods
+ NS_IMETHOD GetURI(nsACString &aURI) override;
+ NS_IMETHOD ClearDatabase() override;
+ NS_IMETHOD NotifyDirItemAdded(nsISupports *item) override { return NotifyItemAdded(item);}
+ NS_IMETHOD RemoveElementsFromAddressList() override;
+ NS_IMETHOD RemoveEmailAddressAt(uint32_t aIndex) override;
+ NS_IMETHOD AddDirectory(const char *uriName, nsIAbDirectory **childDir) override;
+ NS_IMETHOD GetDatabaseFile(nsIFile **aResult) override;
+ NS_IMETHOD GetDatabase(nsIAddrDatabase **aResult) override;
+
+ // nsIAbDirectory methods:
+ NS_IMETHOD GetChildNodes(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD GetChildCards(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD GetIsQuery(bool *aResult) override;
+ NS_IMETHOD DeleteDirectory(nsIAbDirectory *directory) override;
+ NS_IMETHOD DeleteCards(nsIArray *cards) override;
+ NS_IMETHOD HasCard(nsIAbCard *cards, bool *hasCard) override;
+ NS_IMETHOD HasDirectory(nsIAbDirectory *dir, bool *hasDir) override;
+ NS_IMETHOD HasMailListWithName(const char16_t *aName, bool *aHasList) override;
+ NS_IMETHOD AddMailList(nsIAbDirectory *list, nsIAbDirectory **addedList) override;
+ NS_IMETHOD AddCard(nsIAbCard *card, nsIAbCard **addedCard) override;
+ NS_IMETHOD ModifyCard(nsIAbCard *aModifiedCard) override;
+ NS_IMETHOD DropCard(nsIAbCard *card, bool needToCopyCard) override;
+ NS_IMETHOD EditMailListToDatabase(nsIAbCard *listCard) override;
+ NS_IMETHOD CardForEmailAddress(const nsACString &aEmailAddress,
+ nsIAbCard ** aAbCard) override;
+ NS_IMETHOD GetCardFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive, nsIAbCard **result) override;
+ NS_IMETHOD GetCardsFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsISimpleEnumerator **result) override;
+
+ // nsIAbDirectorySearch methods
+ NS_DECL_NSIABDIRECTORYSEARCH
+
+ // nsIAbDirSearchListener methods
+ NS_DECL_NSIABDIRSEARCHLISTENER
+
+protected:
+ virtual ~nsAbMDBDirectory();
+ nsresult NotifyPropertyChanged(nsIAbDirectory *list, const char *property, const char16_t* oldValue, const char16_t* newValue);
+ nsresult NotifyItemAdded(nsISupports *item);
+ nsresult NotifyItemDeleted(nsISupports *item);
+ nsresult NotifyItemChanged(nsISupports *item);
+ nsresult RemoveCardFromAddressList(nsIAbCard* card);
+
+ nsresult GetAbDatabase();
+ nsCOMPtr<nsIAddrDatabase> mDatabase;
+
+ nsCOMArray<nsIAbDirectory> mSubDirectories;
+
+ int32_t mContext;
+ bool mPerformingQuery;
+
+ nsInterfaceHashtable<nsISupportsHashKey, nsIAbCard> mSearchCache;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbManager.cpp b/mailnews/addrbook/src/nsAbManager.cpp
new file mode 100644
index 000000000..ca640248e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbManager.cpp
@@ -0,0 +1,1418 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbManager.h"
+#include "nsAbBaseCID.h"
+#include "nsAddrDatabase.h"
+#include "nsIOutputStream.h"
+#include "nsNetUtil.h"
+#include "nsMsgI18N.h"
+#include "nsIStringBundle.h"
+#include "nsMsgUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "mozIDOMWindow.h"
+#include "nsIFilePicker.h"
+#include "plbase64.h"
+#include "nsIWindowWatcher.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsVCard.h"
+#include "nsVCardObj.h"
+#include "nsIAbLDAPAttributeMap.h"
+#include "nsICommandLine.h"
+#include "nsIFile.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIObserverService.h"
+#include "nsDirPrefs.h"
+#include "nsThreadUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsAbQueryStringToExpression.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+using namespace mozilla;
+
+struct ExportAttributesTableStruct
+{
+ const char* abPropertyName;
+ uint32_t plainTextStringID;
+};
+
+// our schema is not fixed yet, but we still want some sort of objectclass
+// for now, use obsolete in the class name, hinting that this will change
+// see bugs bug #116692 and #118454
+#define MOZ_AB_OBJECTCLASS "mozillaAbPersonAlpha"
+
+// for now, the oder of the attributes with true for includeForPlainText
+// should be in the same order as they are in the import code
+// see importMsgProperties and nsImportStringBundle.
+//
+// XXX todo, merge with what's in nsAbLDAPProperties.cpp, so we can
+// use this for LDAP and LDIF export
+//
+// here's how we're coming up with the ldapPropertyName values
+// if they are specified in RFC 2798, use them
+// else use the 4.x LDIF attribute names (for example, "xmozillanickname"
+// as we want to allow export from mozilla back to 4.x, and other apps
+// are probably out there that can handle 4.x LDIF)
+// else use the MOZ_AB_LDIF_PREFIX prefix, see nsIAddrDatabase.idl
+
+const ExportAttributesTableStruct EXPORT_ATTRIBUTES_TABLE[] = {
+ {kFirstNameProperty, 2100},
+ {kLastNameProperty, 2101},
+ {kDisplayNameProperty, 2102},
+ {kNicknameProperty, 2103},
+ {kPriEmailProperty, 2104},
+ {k2ndEmailProperty, 2105},
+ {kScreenNameProperty, 2136},
+ {kPreferMailFormatProperty, 0},
+ {kLastModifiedDateProperty, 0},
+ {kWorkPhoneProperty, 2106},
+ {kWorkPhoneTypeProperty, 0},
+ {kHomePhoneProperty, 2107},
+ {kHomePhoneTypeProperty, 0},
+ {kFaxProperty, 2108},
+ {kFaxTypeProperty, 0},
+ {kPagerProperty, 2109},
+ {kPagerTypeProperty, 0},
+ {kCellularProperty, 2110},
+ {kCellularTypeProperty, 0},
+ {kHomeAddressProperty, 2111},
+ {kHomeAddress2Property, 2112},
+ {kHomeCityProperty, 2113},
+ {kHomeStateProperty, 2114},
+ {kHomeZipCodeProperty, 2115},
+ {kHomeCountryProperty, 2116},
+ {kWorkAddressProperty, 2117},
+ {kWorkAddress2Property, 2118},
+ {kWorkCityProperty, 2119},
+ {kWorkStateProperty, 2120},
+ {kWorkZipCodeProperty, 2121},
+ {kWorkCountryProperty, 2122},
+ {kJobTitleProperty, 2123},
+ {kDepartmentProperty, 2124},
+ {kCompanyProperty, 2125},
+ {kWorkWebPageProperty, 2126},
+ {kHomeWebPageProperty, 2127},
+ {kBirthYearProperty, 2128}, // unused for now
+ {kBirthMonthProperty, 2129}, // unused for now
+ {kBirthDayProperty, 2130}, // unused for now
+ {kCustom1Property, 2131},
+ {kCustom2Property, 2132},
+ {kCustom3Property, 2133},
+ {kCustom4Property, 2134},
+ {kNotesProperty, 2135},
+ {kAnniversaryYearProperty, 0},
+ {kAnniversaryMonthProperty, 0},
+ {kAnniversaryDayProperty, 0},
+ {kSpouseNameProperty, 0},
+ {kFamilyNameProperty, 0},
+};
+
+//
+// nsAbManager
+//
+nsAbManager::nsAbManager()
+{
+}
+
+nsAbManager::~nsAbManager()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsAbManager, nsIAbManager, nsICommandLineHandler,
+ nsIObserver)
+
+nsresult nsAbManager::Init()
+{
+ NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = observerService->AddObserver(this, "profile-do-change", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *someData)
+{
+ // The nsDirPrefs code caches all the directories that it got
+ // from the first profiles prefs.js.
+ // When we profile switch, we need to force it to shut down.
+ // We'll re-load all the directories from the second profiles prefs.js
+ // that happens in nsAbBSDirectory::GetChildNodes()
+ // when we call DIR_GetDirectories().
+ if (!strcmp(aTopic, "profile-do-change"))
+ {
+ DIR_ShutDown();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID))
+ {
+ DIR_ShutDown();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = observerService->RemoveObserver(this, "profile-do-change");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+//
+// nsIAbManager
+//
+
+NS_IMETHODIMP nsAbManager::GetDirectories(nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // We cache the top level AB to ensure that nsIAbDirectory items are not
+ // created and dumped every time GetDirectories is called. This was causing
+ // performance problems, especially with the content policy on messages
+ // with lots of urls.
+ nsresult rv;
+ nsCOMPtr<nsIAbDirectory> rootAddressBook;
+ rv = GetRootDirectory(getter_AddRefs(rootAddressBook));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rootAddressBook->GetChildNodes(aResult);
+}
+
+nsresult
+nsAbManager::GetRootDirectory(nsIAbDirectory **aResult)
+{
+ // We cache the top level AB to ensure that nsIAbDirectory items are not
+ // created and dumped every time GetDirectories is called. This was causing
+ // performance problems, especially with the content policy on messages
+ // with lots of urls.
+ nsresult rv;
+
+ if (!mCacheTopLevelAb)
+ {
+ nsCOMPtr<nsIAbDirectory> rootAddressBook(do_GetService(NS_ABDIRECTORY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCacheTopLevelAb = rootAddressBook;
+ }
+
+ NS_IF_ADDREF(*aResult = mCacheTopLevelAb);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::GetDirectoryFromId(const nsACString &aDirPrefId,
+ nsIAbDirectory **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = GetDirectories(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISupports> support;
+ nsCOMPtr<nsIAbDirectory> directory;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
+ rv = enumerator->GetNext(getter_AddRefs(support));
+ NS_ENSURE_SUCCESS(rv, rv);
+ directory = do_QueryInterface(support, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unable to select Address book nsAbManager::GetDirectoryFromId()");
+ continue;
+ }
+
+ nsCString dirPrefId;
+ directory->GetDirPrefId(dirPrefId);
+ if (dirPrefId.Equals(aDirPrefId)) {
+ directory.forget(aResult);
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::GetDirectory(const nsACString &aURI,
+ nsIAbDirectory **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ nsCOMPtr<nsIAbDirectory> directory;
+
+ // Was the directory root requested?
+ if (aURI.EqualsLiteral(kAllDirectoryRoot))
+ {
+ rv = GetRootDirectory(getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_IF_ADDREF(*aResult = directory);
+ return NS_OK;
+ }
+
+ // Do we have a copy of this directory already within our look-up table?
+ if (!mAbStore.Get(aURI, getter_AddRefs(directory)))
+ {
+ // The directory wasn't in our look-up table, so we need to instantiate
+ // it. First, extract the scheme from the URI...
+
+ nsAutoCString scheme;
+
+ int32_t colon = aURI.FindChar(':');
+ if (colon <= 0)
+ return NS_ERROR_MALFORMED_URI;
+ scheme = Substring(aURI, 0, colon);
+
+ // Construct the appropriate nsIAbDirectory...
+ nsAutoCString contractID;
+ contractID.AssignLiteral(NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX);
+ contractID.Append(scheme);
+ directory = do_CreateInstance(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Init it with the URI
+ rv = directory->Init(PromiseFlatCString(aURI).get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if this directory was initiated with a search query. If so,
+ // we don't cache it.
+ bool isQuery = false;
+ rv = directory->GetIsQuery(&isQuery);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isQuery)
+ mAbStore.Put(aURI, directory);
+ }
+ NS_IF_ADDREF(*aResult = directory);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::NewAddressBook(const nsAString &aDirName,
+ const nsACString &aURI,
+ const uint32_t aType,
+ const nsACString &aPrefName,
+ nsACString &aResult)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIAbDirectory> parentDir;
+ rv = GetRootDirectory(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return parentDir->CreateNewDirectory(aDirName, aURI, aType, aPrefName, aResult);
+}
+
+NS_IMETHODIMP nsAbManager::DeleteAddressBook(const nsACString &aURI)
+{
+ // Find the address book
+ nsresult rv;
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = GetDirectory(aURI, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> rootDirectory;
+ rv = GetRootDirectory(getter_AddRefs(rootDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Go through each of the children of the address book
+ // (so, the mailing lists) and remove their entries from
+ // the look up table.
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = directory->GetChildNodes(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> item;
+ nsCOMPtr<nsIAbDirectory> childDirectory;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ childDirectory = do_QueryInterface(item, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString childURI;
+ rv = childDirectory->GetURI(childURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mAbStore.Remove(childURI);
+ }
+ }
+
+ mAbStore.Remove(aURI);
+
+ bool isMailList;
+ rv = directory->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isMailList)
+ // If we're not a mailing list, then our parent
+ // must be the root address book directory.
+ return rootDirectory->DeleteDirectory(directory);
+
+ nsCString parentUri;
+ parentUri.Append(aURI);
+ int32_t pos = parentUri.RFindChar('/');
+
+ // If we didn't find a /, we're in trouble.
+ if (pos == -1)
+ return NS_ERROR_FAILURE;
+
+ parentUri = StringHead(parentUri, pos);
+ nsCOMPtr<nsIAbDirectory> parentDirectory;
+ rv = GetDirectory(parentUri, getter_AddRefs(parentDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return parentDirectory->DeleteDirectory(directory);
+}
+
+NS_IMETHODIMP nsAbManager::AddAddressBookListener(nsIAbListener *aListener,
+ abListenerNotifyFlagValue aNotifyFlags)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ abListener newListener(aListener, aNotifyFlags);
+ mListeners.AppendElementUnlessExists(newListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::RemoveAddressBookListener(nsIAbListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+#define NOTIFY_AB_LISTENERS(propertyflag_, propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<abListener>::ForwardIterator iter(mListeners); \
+ while (iter.HasMore()) { \
+ const abListener &abL = iter.GetNext(); \
+ if (abL.mNotifyFlags & nsIAbListener::propertyflag_) \
+ abL.mListener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+NS_IMETHODIMP nsAbManager::NotifyItemPropertyChanged(nsISupports *aItem,
+ const char *aProperty,
+ const char16_t* aOldValue,
+ const char16_t* aNewValue)
+{
+ NOTIFY_AB_LISTENERS(itemChanged, OnItemPropertyChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::NotifyDirectoryItemAdded(nsIAbDirectory *aParentDirectory,
+ nsISupports *aItem)
+{
+ NOTIFY_AB_LISTENERS(itemAdded, OnItemAdded, (aParentDirectory, aItem));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::NotifyDirectoryItemDeleted(nsIAbDirectory *aParentDirectory,
+ nsISupports *aItem)
+{
+ NOTIFY_AB_LISTENERS(directoryItemRemoved, OnItemRemoved,
+ (aParentDirectory, aItem));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::NotifyDirectoryDeleted(nsIAbDirectory *aParentDirectory,
+ nsISupports *aDirectory)
+{
+ NOTIFY_AB_LISTENERS(directoryRemoved, OnItemRemoved,
+ (aParentDirectory, aDirectory));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::GetUserProfileDirectory(nsIFile **userDir)
+{
+ NS_ENSURE_ARG_POINTER(userDir);
+ *userDir = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> profileDir;
+ nsAutoCString pathBuf;
+
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ profileDir.forget(userDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbManager::MailListNameExists(const char16_t *aName, bool *aExists)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aExists);
+
+ *aExists = false;
+
+ // now get the top-level book
+ nsCOMPtr<nsIAbDirectory> topDirectory;
+ rv = GetRootDirectory(getter_AddRefs(topDirectory));
+
+ // now go through the address books
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = topDirectory->GetChildNodes(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory = do_QueryInterface(item, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ rv = directory->HasMailListWithName(aName, aExists);
+ if (NS_SUCCEEDED(rv) && *aExists)
+ return NS_OK;
+ }
+
+ *aExists = false;
+ return NS_OK;
+}
+
+#define CSV_DELIM ","
+#define CSV_DELIM_LEN 1
+#define TAB_DELIM "\t"
+#define TAB_DELIM_LEN 1
+
+#define CSV_FILE_EXTENSION ".csv"
+#define TAB_FILE_EXTENSION ".tab"
+#define TXT_FILE_EXTENSION ".txt"
+#define VCF_FILE_EXTENSION ".vcf"
+#define LDIF_FILE_EXTENSION ".ldi"
+#define LDIF_FILE_EXTENSION2 ".ldif"
+
+enum ADDRESSBOOK_EXPORT_FILE_TYPE
+{
+ CSV_EXPORT_TYPE = 0,
+ CSV_EXPORT_TYPE_UTF8 = 1,
+ TAB_EXPORT_TYPE = 2,
+ TAB_EXPORT_TYPE_UTF8 = 3,
+ VCF_EXPORT_TYPE = 4,
+ LDIF_EXPORT_TYPE = 5,
+};
+
+NS_IMETHODIMP nsAbManager::ExportAddressBook(mozIDOMWindowProxy *aParentWin, nsIAbDirectory *aDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aParentWin);
+
+ nsresult rv;
+ nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString dirName;
+ aDirectory->GetDirName(dirName);
+ const char16_t *formatStrings[] = { dirName.get() };
+
+ nsString title;
+ rv = bundle->FormatStringFromName(u"ExportAddressBookNameTitle", formatStrings,
+ ArrayLength(formatStrings), getter_Copies(title));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filePicker->Init(aParentWin, title, nsIFilePicker::modeSave);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ filePicker->SetDefaultString(dirName);
+
+ nsString filterString;
+
+ // CSV: System charset and UTF-8.
+ rv = bundle->GetStringFromName(u"CSVFilesSysCharset", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.csv"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->GetStringFromName(u"CSVFilesUTF8", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.csv"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Tab separated: System charset and UTF-8.
+ rv = bundle->GetStringFromName(u"TABFilesSysCharset", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.tab; *.txt"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->GetStringFromName(u"TABFilesUTF8", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.tab; *.txt"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = bundle->GetStringFromName(u"VCFFiles", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.vcf"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = bundle->GetStringFromName(u"LDIFFiles", getter_Copies(filterString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.ldi; *.ldif"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int16_t dialogResult;
+ filePicker->Show(&dialogResult);
+
+ if (dialogResult == nsIFilePicker::returnCancel)
+ return rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = filePicker->GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dialogResult == nsIFilePicker::returnReplace) {
+ // be extra safe and only delete when the file is really a file
+ bool isFile;
+ rv = localFile->IsFile(&isFile);
+ if (NS_SUCCEEDED(rv) && isFile) {
+ rv = localFile->Remove(false /* recursive delete */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // The type of export is determined by the drop-down in
+ // the file picker dialog.
+ int32_t exportType;
+ rv = filePicker->GetFilterIndex(&exportType);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoString fileName;
+ rv = localFile->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch ( exportType )
+ {
+ default:
+ case LDIF_EXPORT_TYPE: // ldif
+ // If filename does not have the correct ext, add one.
+ if ((MsgFind(fileName, LDIF_FILE_EXTENSION, true, fileName.Length() - strlen(LDIF_FILE_EXTENSION)) == -1) &&
+ (MsgFind(fileName, LDIF_FILE_EXTENSION2, true, fileName.Length() - strlen(LDIF_FILE_EXTENSION2)) == -1)) {
+
+ // Add the extension and build a new localFile.
+ fileName.AppendLiteral(LDIF_FILE_EXTENSION2);
+ localFile->SetLeafName(fileName);
+ }
+ rv = ExportDirectoryToLDIF(aDirectory, localFile);
+ break;
+
+ case CSV_EXPORT_TYPE: // csv
+ case CSV_EXPORT_TYPE_UTF8:
+ // If filename does not have the correct ext, add one.
+ if (MsgFind(fileName, CSV_FILE_EXTENSION, true, fileName.Length() - strlen(CSV_FILE_EXTENSION)) == -1) {
+
+ // Add the extension and build a new localFile.
+ fileName.AppendLiteral(CSV_FILE_EXTENSION);
+ localFile->SetLeafName(fileName);
+ }
+ rv = ExportDirectoryToDelimitedText(aDirectory, CSV_DELIM, CSV_DELIM_LEN, localFile,
+ exportType==CSV_EXPORT_TYPE_UTF8);
+ break;
+
+ case TAB_EXPORT_TYPE: // tab & text
+ case TAB_EXPORT_TYPE_UTF8:
+ // If filename does not have the correct ext, add one.
+ if ((MsgFind(fileName, TXT_FILE_EXTENSION, true, fileName.Length() - strlen(TXT_FILE_EXTENSION)) == -1) &&
+ (MsgFind(fileName, TAB_FILE_EXTENSION, true, fileName.Length() - strlen(TAB_FILE_EXTENSION)) == -1)) {
+
+ // Add the extension and build a new localFile.
+ fileName.AppendLiteral(TXT_FILE_EXTENSION);
+ localFile->SetLeafName(fileName);
+ }
+ rv = ExportDirectoryToDelimitedText(aDirectory, TAB_DELIM, TAB_DELIM_LEN, localFile,
+ exportType==TAB_EXPORT_TYPE_UTF8);
+ break;
+
+ case VCF_EXPORT_TYPE: // vCard
+ // If filename does not have the correct ext, add one.
+ if (MsgFind(fileName, VCF_FILE_EXTENSION, true, fileName.Length() - strlen(VCF_FILE_EXTENSION)) == -1) {
+
+ // Add the extension and build a new localFile.
+ fileName.AppendLiteral(VCF_FILE_EXTENSION);
+ localFile->SetLeafName(fileName);
+ }
+ rv = ExportDirectoryToVCard(aDirectory, localFile);
+ break;
+ };
+
+ return rv;
+}
+
+nsresult
+nsAbManager::ExportDirectoryToDelimitedText(nsIAbDirectory *aDirectory,
+ const char *aDelim,
+ uint32_t aDelimLen,
+ nsIFile *aLocalFile,
+ bool useUTF8)
+{
+ nsCOMPtr <nsISimpleEnumerator> cardsEnumerator;
+ nsCOMPtr <nsIAbCard> card;
+
+ nsresult rv;
+
+ nsCOMPtr <nsIOutputStream> outputStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream),
+ aLocalFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+
+ // the desired file may be read only
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t i;
+ uint32_t writeCount;
+ uint32_t length;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/importMsgs.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString revisedName;
+ nsString columnName;
+
+ for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) {
+ if (EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID != 0) {
+
+ // We don't need to truncate the string here as getter_Copies will
+ // do that for us.
+ if (NS_FAILED(bundle->GetStringFromID(EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID, getter_Copies(columnName))))
+ columnName.AppendInt(EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID);
+
+ rv = nsMsgI18NConvertFromUnicode(useUTF8 ? "UTF-8" : nsMsgI18NFileSystemCharset(),
+ columnName, revisedName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = outputStream->Write(revisedName.get(),
+ revisedName.Length(),
+ &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (revisedName.Length() != writeCount)
+ return NS_ERROR_FAILURE;
+
+ if (i < ArrayLength(EXPORT_ATTRIBUTES_TABLE) - 1) {
+ rv = outputStream->Write(aDelim, aDelimLen, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aDelimLen != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (MSG_LINEBREAK_LEN != writeCount)
+ return NS_ERROR_FAILURE;
+
+ rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator));
+ if (NS_SUCCEEDED(rv) && cardsEnumerator) {
+ nsCOMPtr<nsISupports> item;
+ bool more;
+ while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) {
+ rv = cardsEnumerator->GetNext(getter_AddRefs(item));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isMailList;
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+
+ if (isMailList) {
+ // .tab, .txt and .csv aren't able to export mailing lists
+ // use LDIF for that.
+ }
+ else {
+ nsString value;
+ nsCString valueCStr;
+
+ for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) {
+ if (EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID != 0) {
+ rv = card->GetPropertyAsAString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, value);
+ if (NS_FAILED(rv))
+ value.Truncate();
+
+ // If a string contains at least one comma, tab or double quote then
+ // we need to quote the entire string. Also if double quote is part
+ // of the string we need to quote the double quote(s) as well.
+ nsAutoString newValue(value);
+ bool needsQuotes = false;
+ if(newValue.FindChar('"') != -1)
+ {
+ needsQuotes = true;
+
+ int32_t match = 0;
+ uint32_t offset = 0;
+ nsString oldSubstr = NS_LITERAL_STRING("\"");
+ nsString newSubstr = NS_LITERAL_STRING("\"\"");
+ while (offset < newValue.Length()) {
+ match = newValue.Find(oldSubstr, offset);
+ if (match == -1)
+ break;
+
+ newValue.Replace(offset + match, oldSubstr.Length(), newSubstr);
+ offset += (match + newSubstr.Length());
+ }
+ }
+ if (!needsQuotes && (newValue.FindChar(',') != -1 || newValue.FindChar('\x09') != -1))
+ needsQuotes = true;
+
+ // Make sure we quote if containing CR/LF.
+ if (newValue.FindChar('\r') != -1 ||
+ newValue.FindChar('\n') != -1)
+ needsQuotes = true;
+
+ if (needsQuotes)
+ {
+ newValue.Insert(NS_LITERAL_STRING("\""), 0);
+ newValue.AppendLiteral("\"");
+ }
+
+ rv = nsMsgI18NConvertFromUnicode(useUTF8 ? "UTF-8" : nsMsgI18NFileSystemCharset(),
+ newValue, valueCStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (NS_FAILED(rv)) {
+ NS_ERROR("failed to convert string to system charset. use LDIF");
+ valueCStr = "?";
+ }
+
+ length = valueCStr.Length();
+ if (length) {
+ rv = outputStream->Write(valueCStr.get(), length, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (length != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ valueCStr = "";
+ }
+ else {
+ // something we don't support for the current export
+ // for example, .tab doesn't export preferred html format
+ continue; // go to next field
+ }
+
+ if (i < ArrayLength(EXPORT_ATTRIBUTES_TABLE) - 1) {
+ rv = outputStream->Write(aDelim, aDelimLen, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (aDelimLen != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // write out the linebreak that separates the cards
+ rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (MSG_LINEBREAK_LEN != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+
+ rv = outputStream->Flush();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = outputStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+nsresult
+nsAbManager::ExportDirectoryToVCard(nsIAbDirectory *aDirectory, nsIFile *aLocalFile)
+{
+ nsCOMPtr <nsISimpleEnumerator> cardsEnumerator;
+ nsCOMPtr <nsIAbCard> card;
+
+ nsresult rv;
+
+ nsCOMPtr <nsIOutputStream> outputStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream),
+ aLocalFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+
+ // the desired file may be read only
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t writeCount;
+ uint32_t length;
+
+ rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator));
+ if (NS_SUCCEEDED(rv) && cardsEnumerator) {
+ nsCOMPtr<nsISupports> item;
+ bool more;
+ while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) {
+ rv = cardsEnumerator->GetNext(getter_AddRefs(item));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isMailList;
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (isMailList) {
+ // we don't know how to export mailing lists to vcf
+ // use LDIF for that.
+ }
+ else {
+ nsCString escapedValue;
+ rv = card->TranslateTo(NS_LITERAL_CSTRING("vcard"), escapedValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString valueCStr;
+ MsgUnescapeString(escapedValue, 0, valueCStr);
+
+ length = valueCStr.Length();
+ rv = outputStream->Write(valueCStr.get(), length, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (length != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+
+ rv = outputStream->Flush();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = outputStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+
+nsresult
+nsAbManager::ExportDirectoryToLDIF(nsIAbDirectory *aDirectory, nsIFile *aLocalFile)
+{
+ nsCOMPtr <nsISimpleEnumerator> cardsEnumerator;
+ nsCOMPtr <nsIAbCard> card;
+
+ nsresult rv;
+
+ nsCOMPtr <nsIOutputStream> outputStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream),
+ aLocalFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+
+ // the desired file may be read only
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Get the default attribute map for ldap. We use the default attribute
+ // map rather than one for a specific server because if people want an
+ // ldif export using a servers specific schema, then they can use ldapsearch
+ nsCOMPtr<nsIAbLDAPAttributeMapService> mapSrv =
+ do_GetService("@mozilla.org/addressbook/ldap-attribute-map-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
+ rv = mapSrv->GetMapForPrefBranch(NS_LITERAL_CSTRING("ldap_2.servers.default.attrmap"),
+ getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i;
+ uint32_t writeCount;
+ uint32_t length;
+
+ rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator));
+ if (NS_SUCCEEDED(rv) && cardsEnumerator) {
+ nsCOMPtr<nsISupports> item;
+ bool more;
+ while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) {
+ rv = cardsEnumerator->GetNext(getter_AddRefs(item));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isMailList;
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (isMailList) {
+ nsCString mailListCStr;
+
+ rv = AppendLDIFForMailList(card, attrMap, mailListCStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ length = mailListCStr.Length();
+ rv = outputStream->Write(mailListCStr.get(), length, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (length != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ else {
+ nsString value;
+ nsCString valueCStr;
+
+ rv = AppendBasicLDIFForCard(card, attrMap, valueCStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ length = valueCStr.Length();
+ rv = outputStream->Write(valueCStr.get(), length, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (length != writeCount)
+ return NS_ERROR_FAILURE;
+
+ valueCStr.Truncate();
+
+ nsAutoCString ldapAttribute;
+
+ for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) {
+ if (NS_SUCCEEDED(attrMap->GetFirstAttribute(nsDependentCString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName),
+ ldapAttribute)) &&
+ !ldapAttribute.IsEmpty()) {
+
+ rv = card->GetPropertyAsAString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, value);
+ if (NS_FAILED(rv))
+ value.Truncate();
+
+ if (!PL_strcmp(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, kPreferMailFormatProperty)) {
+ if (value.EqualsLiteral("html"))
+ value.AssignLiteral("true");
+ else if (value.EqualsLiteral("plaintext"))
+ value.AssignLiteral("false");
+ else
+ value.Truncate(); // unknown.
+ }
+
+ if (!value.IsEmpty()) {
+ rv = AppendProperty(ldapAttribute.get(), value.get(), valueCStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ valueCStr += MSG_LINEBREAK;
+ }
+ else
+ valueCStr.Truncate();
+
+ length = valueCStr.Length();
+ if (length) {
+ rv = outputStream->Write(valueCStr.get(), length, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (length != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ valueCStr.Truncate();
+ }
+ else {
+ // something we don't support yet
+ // ldif doesn't export multiple addresses
+ }
+ }
+
+ // write out the linebreak that separates the cards
+ rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (MSG_LINEBREAK_LEN != writeCount)
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+
+ rv = outputStream->Flush();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = outputStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+nsresult nsAbManager::AppendLDIFForMailList(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult)
+{
+ nsresult rv;
+ nsString attrValue;
+
+ rv = AppendDNForCard("dn", aCard, aAttrMap, aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ aResult += MSG_LINEBREAK \
+ "objectclass: top" MSG_LINEBREAK \
+ "objectclass: groupOfNames" MSG_LINEBREAK;
+
+ rv = aCard->GetDisplayName(attrValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString ldapAttributeName;
+
+ rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kDisplayNameProperty),
+ ldapAttributeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+ aResult += MSG_LINEBREAK;
+
+ rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kNicknameProperty),
+ ldapAttributeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aCard->GetPropertyAsAString(kNicknameProperty, attrValue);
+ if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty()) {
+ rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+ aResult += MSG_LINEBREAK;
+ }
+
+ rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kNotesProperty),
+ ldapAttributeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aCard->GetPropertyAsAString(kNotesProperty, attrValue);
+ if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty()) {
+ rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+ aResult += MSG_LINEBREAK;
+ }
+
+ nsCString mailListURI;
+ rv = aCard->GetMailListURI(getter_Copies(mailListURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIAbDirectory> mailList;
+ rv = GetDirectory(mailListURI, getter_AddRefs(mailList));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMutableArray> addresses;
+ rv = mailList->GetAddressLists(getter_AddRefs(addresses));
+ if (addresses) {
+ uint32_t total = 0;
+ addresses->GetLength(&total);
+ if (total) {
+ uint32_t i;
+ for (i = 0; i < total; i++) {
+ nsCOMPtr <nsIAbCard> listCard = do_QueryElementAt(addresses, i, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AppendDNForCard("member", listCard, aAttrMap, aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ aResult += MSG_LINEBREAK;
+ }
+ }
+ }
+
+ aResult += MSG_LINEBREAK;
+ return NS_OK;
+}
+
+nsresult nsAbManager::AppendDNForCard(const char *aProperty, nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult)
+{
+ nsString email;
+ nsString displayName;
+ nsAutoCString ldapAttributeName;
+
+ nsresult rv = aCard->GetPrimaryEmail(email);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aCard->GetDisplayName(displayName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString cnStr;
+
+ rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kDisplayNameProperty),
+ ldapAttributeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!displayName.IsEmpty()) {
+ cnStr += NS_ConvertUTF8toUTF16(ldapAttributeName).get();
+ cnStr.AppendLiteral("=");
+ cnStr.Append(displayName);
+ if (!email.IsEmpty()) {
+ cnStr.AppendLiteral(",");
+ }
+ }
+
+ rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kPriEmailProperty),
+ ldapAttributeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!email.IsEmpty()) {
+ cnStr += NS_ConvertUTF8toUTF16(ldapAttributeName).get();
+ cnStr.AppendLiteral("=");
+ cnStr.Append(email);
+ }
+
+ rv = AppendProperty(aProperty, cnStr.get(), aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsAbManager::AppendBasicLDIFForCard(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult)
+{
+ nsresult rv = AppendDNForCard("dn", aCard, aAttrMap, aResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+ aResult += MSG_LINEBREAK \
+ "objectclass: top" MSG_LINEBREAK \
+ "objectclass: person" MSG_LINEBREAK \
+ "objectclass: organizationalPerson" MSG_LINEBREAK \
+ "objectclass: inetOrgPerson" MSG_LINEBREAK \
+ "objectclass: " MOZ_AB_OBJECTCLASS MSG_LINEBREAK;
+ return rv;
+}
+
+bool nsAbManager::IsSafeLDIFString(const char16_t *aStr)
+{
+ // follow RFC 2849 to determine if something is safe "as is" for LDIF
+ if (aStr[0] == char16_t(' ') ||
+ aStr[0] == char16_t(':') ||
+ aStr[0] == char16_t('<'))
+ return false;
+
+ uint32_t i;
+ uint32_t len = NS_strlen(aStr);
+ for (i=0; i<len; i++) {
+ // If string contains CR or LF, it is not safe for LDIF
+ // and MUST be base64 encoded
+ if ((aStr[i] == char16_t('\n')) ||
+ (aStr[i] == char16_t('\r')) ||
+ (!NS_IsAscii(aStr[i])))
+ return false;
+ }
+ return true;
+}
+
+nsresult nsAbManager::AppendProperty(const char *aProperty, const char16_t *aValue, nsACString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+
+ aResult += aProperty;
+
+ // if the string is not safe "as is", base64 encode it
+ if (IsSafeLDIFString(aValue)) {
+ aResult.AppendLiteral(": ");
+ aResult.Append(NS_LossyConvertUTF16toASCII(aValue));
+ }
+ else {
+ char *base64Str = PL_Base64Encode(NS_ConvertUTF16toUTF8(aValue).get(), 0, nullptr);
+ if (!base64Str)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ aResult.AppendLiteral(":: ");
+ aResult.Append(nsDependentCString(base64Str));
+ PR_Free(base64Str);
+ }
+
+ return NS_OK;
+}
+
+char *getCString(VObject *vObj)
+{
+ if (VALUE_TYPE(vObj) == VCVT_USTRINGZ)
+ return fakeCString(vObjectUStringZValue(vObj));
+ if (VALUE_TYPE(vObj) == VCVT_STRINGZ)
+ return PL_strdup(vObjectStringZValue(vObj));
+ return NULL;
+}
+
+static void convertNameValue(VObject *vObj, nsIAbCard *aCard)
+{
+ const char *cardPropName = NULL;
+
+ // if the vCard property is not a root property then we need to determine its exact property.
+ // a good example of this is VCTelephoneProp, this prop has four objects underneath it:
+ // fax, work and home and cellular.
+ if (PL_strcasecmp(VCCityProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkCityProperty;
+ else if (PL_strcasecmp(VCTelephoneProp, vObjectName(vObj)) == 0)
+ {
+ if (isAPropertyOf(vObj, VCFaxProp))
+ cardPropName = kFaxProperty;
+ else if (isAPropertyOf(vObj, VCWorkProp))
+ cardPropName = kWorkPhoneProperty;
+ else if (isAPropertyOf(vObj, VCHomeProp))
+ cardPropName = kHomePhoneProperty;
+ else if (isAPropertyOf(vObj, VCCellularProp))
+ cardPropName = kCellularProperty;
+ else if (isAPropertyOf(vObj, VCPagerProp))
+ cardPropName = kPagerProperty;
+ else
+ return;
+ }
+ else if (PL_strcasecmp(VCEmailAddressProp, vObjectName(vObj)) == 0)
+ cardPropName = kPriEmailProperty;
+ else if (PL_strcasecmp(VCFamilyNameProp, vObjectName(vObj)) == 0)
+ cardPropName = kLastNameProperty;
+ else if (PL_strcasecmp(VCFullNameProp, vObjectName(vObj)) == 0)
+ cardPropName = kDisplayNameProperty;
+ else if (PL_strcasecmp(VCGivenNameProp, vObjectName(vObj)) == 0)
+ cardPropName = kFirstNameProperty;
+ else if (PL_strcasecmp(VCOrgNameProp, vObjectName(vObj)) == 0)
+ cardPropName = kCompanyProperty;
+ else if (PL_strcasecmp(VCOrgUnitProp, vObjectName(vObj)) == 0)
+ cardPropName = kDepartmentProperty;
+ else if (PL_strcasecmp(VCPostalCodeProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkZipCodeProperty;
+ else if (PL_strcasecmp(VCRegionProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkStateProperty;
+ else if (PL_strcasecmp(VCStreetAddressProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkAddressProperty;
+ else if (PL_strcasecmp(VCPostalBoxProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkAddress2Property;
+ else if (PL_strcasecmp(VCCountryNameProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkCountryProperty;
+ else if (PL_strcasecmp(VCTitleProp, vObjectName(vObj)) == 0)
+ cardPropName = kJobTitleProperty;
+ else if (PL_strcasecmp(VCUseHTML, vObjectName(vObj)) == 0)
+ cardPropName = kPreferMailFormatProperty;
+ else if (PL_strcasecmp(VCNoteProp, vObjectName(vObj)) == 0)
+ cardPropName = kNotesProperty;
+ else if (PL_strcasecmp(VCURLProp, vObjectName(vObj)) == 0)
+ cardPropName = kWorkWebPageProperty;
+ else
+ return;
+
+ if (!VALUE_TYPE(vObj))
+ return;
+
+ char *cardPropValue = getCString(vObj);
+ if (PL_strcmp(cardPropName, kPreferMailFormatProperty)) {
+ aCard->SetPropertyAsAUTF8String(cardPropName, nsDependentCString(cardPropValue));
+ } else {
+ if (!PL_strcmp(cardPropValue, "TRUE"))
+ aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::html);
+ else if (!PL_strcmp(cardPropValue, "FALSE"))
+ aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::plaintext);
+ else
+ aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::unknown);
+ }
+ PR_FREEIF(cardPropValue);
+ return;
+}
+
+static void convertFromVObject(VObject *vObj, nsIAbCard *aCard)
+{
+ if (vObj)
+ {
+ VObjectIterator t;
+
+ convertNameValue(vObj, aCard);
+
+ initPropIterator(&t, vObj);
+ while (moreIteration(&t))
+ {
+ VObject * nextObject = nextVObject(&t);
+ convertFromVObject(nextObject, aCard);
+ }
+ }
+ return;
+}
+
+NS_IMETHODIMP nsAbManager::EscapedVCardToAbCard(const char *aEscapedVCardStr, nsIAbCard **aCard)
+{
+ NS_ENSURE_ARG_POINTER(aEscapedVCardStr);
+ NS_ENSURE_ARG_POINTER(aCard);
+
+ nsCOMPtr <nsIAbCard> cardFromVCard = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID);
+ if (!cardFromVCard)
+ return NS_ERROR_FAILURE;
+
+ // aEscapedVCardStr will be "" the first time, before you have a vCard
+ if (*aEscapedVCardStr != '\0') {
+ nsCString unescapedData;
+ MsgUnescapeString(nsDependentCString(aEscapedVCardStr), 0, unescapedData);
+
+ VObject *vObj = parse_MIME(unescapedData.get(), unescapedData.Length());
+ if (vObj)
+ {
+ convertFromVObject(vObj, cardFromVCard);
+
+ cleanVObject(vObj);
+ }
+ else
+ NS_WARNING("Parse of vCard failed");
+ }
+
+ NS_IF_ADDREF(*aCard = cardFromVCard);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbManager::Handle(nsICommandLine* aCmdLine)
+{
+ nsresult rv;
+ bool found;
+
+ rv = aCmdLine->HandleFlag(NS_LITERAL_STRING("addressbook"), false, &found);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!found)
+ return NS_OK;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch (do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE);
+
+ nsCOMPtr<mozIDOMWindowProxy> opened;
+ wwatch->OpenWindow(nullptr,
+ "chrome://messenger/content/addressbook/addressbook.xul",
+ "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar",
+ nullptr, getter_AddRefs(opened));
+ aCmdLine->SetPreventDefault(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbManager::GetHelpInfo(nsACString& aResult)
+{
+ aResult.Assign(NS_LITERAL_CSTRING(" -addressbook Open the address book at startup.\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbManager::GenerateUUID(const nsACString &aDirectoryId,
+ const nsACString &aLocalId, nsACString &uuid)
+{
+ uuid.Assign(aDirectoryId);
+ uuid.Append('#');
+ uuid.Append(aLocalId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbManager::ConvertQueryStringToExpression(const nsACString &aQueryString,
+ nsIAbBooleanExpression **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ return nsAbQueryStringToExpression::Convert(aQueryString,
+ _retval);
+}
diff --git a/mailnews/addrbook/src/nsAbManager.h b/mailnews/addrbook/src/nsAbManager.h
new file mode 100644
index 000000000..0922db38d
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbManager.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsAbManager_h
+#define __nsAbManager_h
+
+#include "nsIAbManager.h"
+#include "nsTObserverArray.h"
+#include "nsCOMPtr.h"
+#include "nsICommandLineHandler.h"
+#include "nsIObserver.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIAbDirectory.h"
+
+class nsIAbLDAPAttributeMap;
+
+class nsAbManager : public nsIAbManager,
+ public nsICommandLineHandler,
+ public nsIObserver
+{
+
+public:
+ nsAbManager();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABMANAGER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSICOMMANDLINEHANDLER
+
+ nsresult Init();
+
+private:
+ virtual ~nsAbManager();
+ nsresult GetRootDirectory(nsIAbDirectory **aResult);
+ nsresult ExportDirectoryToDelimitedText(nsIAbDirectory *aDirectory, const char *aDelim,
+ uint32_t aDelimLen, nsIFile *aLocalFile, bool useUTF8);
+ nsresult ExportDirectoryToVCard(nsIAbDirectory *aDirectory, nsIFile *aLocalFile);
+ nsresult ExportDirectoryToLDIF(nsIAbDirectory *aDirectory, nsIFile *aLocalFile);
+ nsresult AppendLDIFForMailList(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult);
+ nsresult AppendDNForCard(const char *aProperty, nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult);
+ nsresult AppendBasicLDIFForCard(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult);
+ nsresult AppendProperty(const char *aProperty, const char16_t *aValue, nsACString &aResult);
+ bool IsSafeLDIFString(const char16_t *aStr);
+
+ struct abListener {
+ nsCOMPtr<nsIAbListener> mListener;
+ uint32_t mNotifyFlags;
+
+ abListener(nsIAbListener *aListener, uint32_t aNotifyFlags)
+ : mListener(aListener), mNotifyFlags(aNotifyFlags) {}
+ abListener(const abListener &aListener)
+ : mListener(aListener.mListener), mNotifyFlags(aListener.mNotifyFlags) {}
+ ~abListener() {}
+
+ int operator==(nsIAbListener* aListener) const {
+ return mListener == aListener;
+ }
+ int operator==(const abListener &aListener) const {
+ return mListener == aListener.mListener;
+ }
+ };
+
+ nsTObserverArray<abListener> mListeners;
+ nsCOMPtr<nsIAbDirectory> mCacheTopLevelAb;
+ nsInterfaceHashtable<nsCStringHashKey, nsIAbDirectory> mAbStore;
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbOSXCard.h b/mailnews/addrbook/src/nsAbOSXCard.h
new file mode 100644
index 000000000..51f41c1d7
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXCard.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbOSXCard_h___
+#define nsAbOSXCard_h___
+
+#include "mozilla/Attributes.h"
+#include "nsAbCardProperty.h"
+
+#define NS_ABOSXCARD_URI_PREFIX NS_ABOSXCARD_PREFIX "://"
+
+#define NS_IABOSXCARD_IID \
+ { 0xa7e5b697, 0x772d, 0x4fb5, \
+ { 0x81, 0x16, 0x23, 0xb7, 0x5a, 0xac, 0x94, 0x56 } }
+
+class nsIAbOSXCard : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IABOSXCARD_IID)
+
+ virtual nsresult Init(const char *aUri) = 0;
+ virtual nsresult Update(bool aNotify) = 0;
+ virtual nsresult GetURI(nsACString &aURI) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIAbOSXCard, NS_IABOSXCARD_IID)
+
+class nsAbOSXCard : public nsAbCardProperty,
+ public nsIAbOSXCard
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsresult Update(bool aNotify) override;
+ nsresult GetURI(nsACString &aURI) override;
+ nsresult Init(const char *aUri) override;
+ // this is needed so nsAbOSXUtils.mm can get at nsAbCardProperty
+ friend class nsAbOSXUtils;
+private:
+ nsCString mURI;
+
+ virtual ~nsAbOSXCard() {}
+};
+
+#endif // nsAbOSXCard_h___
diff --git a/mailnews/addrbook/src/nsAbOSXCard.mm b/mailnews/addrbook/src/nsAbOSXCard.mm
new file mode 100644
index 000000000..28f978c24
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXCard.mm
@@ -0,0 +1,401 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbOSXCard.h"
+#include "nsAbOSXDirectory.h"
+#include "nsAbOSXUtils.h"
+#include "nsAutoPtr.h"
+#include "nsIAbManager.h"
+#include "nsObjCExceptions.h"
+#include "nsServiceManagerUtils.h"
+
+#include <AddressBook/AddressBook.h>
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbOSXCard,
+ nsAbCardProperty,
+ nsIAbOSXCard)
+
+#ifdef DEBUG
+static ABPropertyType
+GetPropertType(ABRecord *aCard, NSString *aProperty)
+{
+ ABPropertyType propertyType = kABErrorInProperty;
+ if ([aCard isKindOfClass:[ABPerson class]])
+ propertyType = [ABPerson typeOfProperty:aProperty];
+ else if ([aCard isKindOfClass:[ABGroup class]])
+ propertyType = [ABGroup typeOfProperty:aProperty];
+ return propertyType;
+}
+#endif
+
+static void
+SetStringProperty(nsAbOSXCard *aCard, const nsString &aValue,
+ const char *aMemberName, bool aNotify,
+ nsIAbManager *aAbManager)
+{
+ nsString oldValue;
+ nsresult rv = aCard->GetPropertyAsAString(aMemberName, oldValue);
+ if (NS_FAILED(rv))
+ oldValue.Truncate();
+
+ if (!aNotify) {
+ aCard->SetPropertyAsAString(aMemberName, aValue);
+ }
+ else if (!oldValue.Equals(aValue)) {
+ aCard->SetPropertyAsAString(aMemberName, aValue);
+
+ nsISupports *supports = NS_ISUPPORTS_CAST(nsAbCardProperty*, aCard);
+
+ aAbManager->NotifyItemPropertyChanged(supports, aMemberName,
+ oldValue.get(), aValue.get());
+ }
+}
+
+static void
+SetStringProperty(nsAbOSXCard *aCard, NSString *aValue, const char *aMemberName,
+ bool aNotify, nsIAbManager *aAbManager)
+{
+ nsAutoString value;
+ if (aValue)
+ AppendToString(aValue, value);
+
+ SetStringProperty(aCard, value, aMemberName, aNotify, aAbManager);
+}
+
+static void
+MapStringProperty(nsAbOSXCard *aCard, ABRecord *aOSXCard, NSString *aProperty,
+ const char *aMemberName, bool aNotify,
+ nsIAbManager *aAbManager)
+{
+ NS_ASSERTION(aProperty, "This is bad! You asked for an unresolved symbol.");
+ NS_ASSERTION(GetPropertType(aOSXCard, aProperty) == kABStringProperty,
+ "Wrong type!");
+
+ SetStringProperty(aCard, [aOSXCard valueForProperty:aProperty], aMemberName,
+ aNotify, aAbManager);
+}
+
+static ABMutableMultiValue*
+GetMultiValue(ABRecord *aCard, NSString *aProperty)
+{
+ NS_ASSERTION(aProperty, "This is bad! You asked for an unresolved symbol.");
+ NS_ASSERTION(GetPropertType(aCard, aProperty) & kABMultiValueMask,
+ "Wrong type!");
+
+ return [aCard valueForProperty:aProperty];
+}
+
+static void
+MapDate(nsAbOSXCard *aCard, NSDate *aDate, const char *aYearPropName,
+ const char *aMonthPropName, const char *aDayPropName, bool aNotify,
+ nsIAbManager *aAbManager)
+{
+ // XXX Should we pass a format and timezone?
+ NSCalendarDate *date = [aDate dateWithCalendarFormat:nil timeZone:nil];
+
+ nsAutoString value;
+ value.AppendInt(static_cast<int32_t>([date yearOfCommonEra]));
+ SetStringProperty(aCard, value, aYearPropName, aNotify, aAbManager);
+ value.Truncate();
+ value.AppendInt(static_cast<int32_t>([date monthOfYear]));
+ SetStringProperty(aCard, value, aMonthPropName, aNotify, aAbManager);
+ value.Truncate();
+ value.AppendInt(static_cast<int32_t>([date dayOfMonth]));
+ SetStringProperty(aCard, value, aDayPropName, aNotify, aAbManager);
+}
+
+static bool
+MapMultiValue(nsAbOSXCard *aCard, ABRecord *aOSXCard,
+ const nsAbOSXPropertyMap &aMap, bool aNotify,
+ nsIAbManager *aAbManager)
+{
+ ABMultiValue *value = GetMultiValue(aOSXCard, aMap.mOSXProperty);
+ if (value) {
+ unsigned int j;
+ unsigned int count = [value count];
+ for (j = 0; j < count; ++j) {
+ if ([[value labelAtIndex:j] isEqualToString:aMap.mOSXLabel]) {
+ NSString *stringValue = (aMap.mOSXKey)
+ ? [[value valueAtIndex:j] objectForKey:aMap.mOSXKey]
+ : [value valueAtIndex:j];
+
+ SetStringProperty(aCard, stringValue, aMap.mPropertyName, aNotify,
+ aAbManager);
+
+ return true;
+ }
+ }
+ }
+ // String wasn't found, set value of card to empty if it was set previously
+ SetStringProperty(aCard, EmptyString(), aMap.mPropertyName, aNotify,
+ aAbManager);
+
+ return false;
+}
+
+// Maps Address Book's instant messenger name to the corresponding nsIAbCard field name.
+static const char*
+InstantMessengerFieldName(NSString* aInstantMessengerName)
+{
+ if ([aInstantMessengerName isEqualToString:@"AIMInstant"]) {
+ return "_AimScreenName";
+ }
+ if ([aInstantMessengerName isEqualToString:@"GoogleTalkInstant"]) {
+ return "_GoogleTalk";
+ }
+ if ([aInstantMessengerName isEqualToString:@"ICQInstant"]) {
+ return "_ICQ";
+ }
+ if ([aInstantMessengerName isEqualToString:@"JabberInstant"]) {
+ return "_JabberId";
+ }
+ if ([aInstantMessengerName isEqualToString:@"MSNInstant"]) {
+ return "_MSN";
+ }
+ if ([aInstantMessengerName isEqualToString:@"QQInstant"]) {
+ return "_QQ";
+ }
+ if ([aInstantMessengerName isEqualToString:@"SkypeInstant"]) {
+ return "_Skype";
+ }
+ if ([aInstantMessengerName isEqualToString:@"YahooInstant"]) {
+ return "_Yahoo";
+ }
+
+ // Fall back to AIM for everything else.
+ // We don't have nsIAbCard fields for FacebookInstant and GaduGaduInstant.
+ return "_AimScreenName";
+}
+
+nsresult
+nsAbOSXCard::Init(const char *aUri)
+{
+ if (strncmp(aUri, NS_ABOSXCARD_URI_PREFIX,
+ sizeof(NS_ABOSXCARD_URI_PREFIX) - 1) != 0)
+ return NS_ERROR_FAILURE;
+
+ mURI = aUri;
+
+ SetLocalId(nsDependentCString(aUri));
+
+ return Update(false);
+}
+
+nsresult
+nsAbOSXCard::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+nsresult
+nsAbOSXCard::Update(bool aNotify)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
+
+ const char *uid = &((mURI.get())[16]);
+ ABRecord *card = [addressBook recordForUniqueId:[NSString stringWithUTF8String:uid]];
+ NS_ENSURE_TRUE(card, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIAbManager> abManager;
+ nsresult rv;
+ if (aNotify) {
+ abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if ([card isKindOfClass:[ABGroup class]]) {
+ m_IsMailList = true;
+ m_MailListURI.AssignLiteral(NS_ABOSXDIRECTORY_URI_PREFIX);
+ m_MailListURI.Append(uid);
+ MapStringProperty(this, card, kABGroupNameProperty, "DisplayName", aNotify,
+ abManager);
+ MapStringProperty(this, card, kABGroupNameProperty, "LastName", aNotify,
+ abManager);
+
+ return NS_OK;
+ }
+
+ bool foundHome = false, foundWork = false;
+
+ uint32_t i;
+ for (i = 0; i < nsAbOSXUtils::kPropertyMapSize; ++i) {
+ const nsAbOSXPropertyMap &propertyMap = nsAbOSXUtils::kPropertyMap[i];
+ if (!propertyMap.mOSXProperty)
+ continue;
+
+ if (propertyMap.mOSXLabel) {
+ if (MapMultiValue(this, card, propertyMap, aNotify,
+ abManager) && propertyMap.mOSXProperty == kABAddressProperty) {
+ if (propertyMap.mOSXLabel == kABAddressHomeLabel)
+ foundHome = true;
+ else
+ foundWork = true;
+ }
+ }
+ else {
+ MapStringProperty(this, card, propertyMap.mOSXProperty,
+ propertyMap.mPropertyName, aNotify, abManager);
+ }
+ }
+
+ int flags = 0;
+ if (kABPersonFlags)
+ flags = [[card valueForProperty:kABPersonFlags] intValue];
+
+#define SET_STRING(_value, _name, _notify, _session) \
+ SetStringProperty(this, _value, #_name, _notify, _session)
+
+ // If kABShowAsCompany is set we use the company name as display name.
+ if (kABPersonFlags && (flags & kABShowAsCompany)) {
+ nsString company;
+ nsresult rv = GetPropertyAsAString(kCompanyProperty, company);
+ if (NS_FAILED(rv))
+ company.Truncate();
+ SET_STRING(company, DisplayName, aNotify, abManager);
+ }
+ else {
+ // Use the order used in the OS X address book to set DisplayName.
+ int order = kABPersonFlags && (flags & kABNameOrderingMask);
+ if (kABPersonFlags && (order == kABDefaultNameOrdering)) {
+ order = [addressBook defaultNameOrdering];
+ }
+
+ nsAutoString displayName, tempName;
+ if (kABPersonFlags && (order == kABFirstNameFirst)) {
+ GetFirstName(tempName);
+ displayName.Append(tempName);
+
+ GetLastName(tempName);
+
+ // Only append a space if the last name and the first name are not empty
+ if (!tempName.IsEmpty() && !displayName.IsEmpty())
+ displayName.Append(' ');
+
+ displayName.Append(tempName);
+ }
+ else {
+ GetLastName(tempName);
+ displayName.Append(tempName);
+
+ GetFirstName(tempName);
+
+ // Only append a space if the last name and the first name are not empty
+ if (!tempName.IsEmpty() && !displayName.IsEmpty())
+ displayName.Append(' ');
+
+ displayName.Append(tempName);
+ }
+ SET_STRING(displayName, DisplayName, aNotify, abManager);
+ }
+
+ ABMultiValue *value = GetMultiValue(card, kABEmailProperty);
+ if (value) {
+ unsigned int count = [value count];
+ if (count > 0) {
+ unsigned int j = [value indexForIdentifier:[value primaryIdentifier]];
+
+ if (j < count)
+ SET_STRING([value valueAtIndex:j], PrimaryEmail, aNotify,
+ abManager);
+
+ // If j is 0 (first in the list) we want the second in the list
+ // (index 1), if j is anything else we want the first in the list
+ // (index 0).
+ j = (j == 0);
+ if (j < count)
+ SET_STRING([value valueAtIndex:j], SecondEmail, aNotify,
+ abManager);
+ }
+ }
+
+ // We map the first home address we can find and the first work address
+ // we can find. If we find none, we map the primary address to the home
+ // address.
+ if (!foundHome && !foundWork) {
+ value = GetMultiValue(card, kABAddressProperty);
+ if (value) {
+ unsigned int count = [value count];
+ unsigned int j = [value indexForIdentifier:[value primaryIdentifier]];
+
+ if (j < count) {
+ NSDictionary *address = [value valueAtIndex:j];
+ if (address) {
+ SET_STRING([address objectForKey:kABAddressStreetKey],
+ HomeAddress, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressCityKey],
+ HomeCity, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressStateKey],
+ HomeState, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressZIPKey],
+ HomeZipCode, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressCountryKey],
+ HomeCountry, aNotify, abManager);
+ }
+ }
+ }
+ }
+ // This was kABAIMInstantProperty previously, but it was deprecated in OS X 10.7.
+ value = GetMultiValue(card, kABInstantMessageProperty);
+ if (value) {
+ unsigned int count = [value count];
+ for (size_t i = 0; i < count; i++) {
+ id imValue = [value valueAtIndex:i];
+ // Depending on the macOS version, imValue can be an NSString or an NSDictionary.
+ if ([imValue isKindOfClass:[NSString class]]) {
+ if (i == [value indexForIdentifier:[value primaryIdentifier]]) {
+ SET_STRING(imValue, _AimScreenName, aNotify, abManager);
+ }
+ } else if ([imValue isKindOfClass:[NSDictionary class]]) {
+ NSString* instantMessageService = [imValue objectForKey:@"InstantMessageService"];
+ const char* fieldName = InstantMessengerFieldName(instantMessageService);
+ NSString* userName = [imValue objectForKey:@"InstantMessageUsername"];
+ SetStringProperty(this, userName, fieldName, aNotify, abManager);
+ }
+ }
+ }
+
+#define MAP_DATE(_date, _name, _notify, _session) \
+ MapDate(this, _date, #_name"Year", #_name"Month", #_name"Day", _notify, \
+ _session)
+
+ NSDate *date = [card valueForProperty:kABBirthdayProperty];
+ if (date)
+ MAP_DATE(date, Birth, aNotify, abManager);
+
+ if (kABOtherDatesProperty) {
+ value = GetMultiValue(card, kABOtherDatesProperty);
+ if (value) {
+ unsigned int j, count = [value count];
+ for (j = 0; j < count; ++j) {
+ if ([[value labelAtIndex:j] isEqualToString:kABAnniversaryLabel]) {
+ date = [value valueAtIndex:j];
+ if (date) {
+ MAP_DATE(date, Anniversary, aNotify, abManager);
+
+ break;
+ }
+ }
+ }
+ }
+ }
+#undef MAP_DATE
+#undef SET_STRING
+
+ date = [card valueForProperty:kABModificationDateProperty];
+ if (date)
+ SetPropertyAsUint32("LastModifiedDate",
+ uint32_t([date timeIntervalSince1970]));
+ // XXX No way to notify about this?
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/mailnews/addrbook/src/nsAbOSXDirFactory.cpp b/mailnews/addrbook/src/nsAbOSXDirFactory.cpp
new file mode 100644
index 000000000..e9f2d7d3b
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXDirFactory.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbOSXDirFactory.h"
+#include "nsAbBaseCID.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbManager.h"
+#include "nsStringGlue.h"
+#include "nsServiceManagerUtils.h"
+#include "nsAbOSXDirectory.h"
+
+NS_IMPL_ISUPPORTS(nsAbOSXDirFactory, nsIAbDirFactory)
+
+NS_IMETHODIMP
+nsAbOSXDirFactory::GetDirectories(const nsAString &aDirName,
+ const nsACString &aURI,
+ const nsACString &aPrefName,
+ nsISimpleEnumerator **aDirectories)
+{
+ NS_ENSURE_ARG_POINTER(aDirectories);
+
+ *aDirectories = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX "/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory(do_QueryInterface(directory, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = osxDirectory->AssertChildNodes();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewSingletonEnumerator(aDirectories, osxDirectory);
+}
+
+// No actual deletion, since you cannot create the address books from Mozilla.
+NS_IMETHODIMP
+nsAbOSXDirFactory::DeleteDirectory(nsIAbDirectory *aDirectory)
+{
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbOSXDirFactory.h b/mailnews/addrbook/src/nsAbOSXDirFactory.h
new file mode 100644
index 000000000..4c86211d3
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXDirFactory.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbOSXDirFactory_h___
+#define nsAbOSXDirFactory_h___
+
+#include "nsIAbDirFactory.h"
+
+class nsAbOSXDirFactory final : public nsIAbDirFactory
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRFACTORY
+
+private:
+ ~nsAbOSXDirFactory() {}
+};
+
+#endif // nsAbOSXDirFactory_h___
diff --git a/mailnews/addrbook/src/nsAbOSXDirectory.h b/mailnews/addrbook/src/nsAbOSXDirectory.h
new file mode 100644
index 000000000..1be054480
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXDirectory.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbOSXDirectory_h___
+#define nsAbOSXDirectory_h___
+
+#include "mozilla/Attributes.h"
+#include "nsISupports.h"
+#include "nsAbBaseCID.h"
+#include "nsAbDirProperty.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsIMutableArray.h"
+#include "nsInterfaceHashtable.h"
+#include "nsAbOSXCard.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+class nsIAbManager;
+class nsIAbBooleanExpression;
+
+#define NS_ABOSXDIRECTORY_URI_PREFIX NS_ABOSXDIRECTORY_PREFIX "://"
+
+#define NS_IABOSXDIRECTORY_IID \
+{ 0x87ee4bd9, 0x8552, 0x498f, \
+ { 0x80, 0x85, 0x34, 0xf0, 0x2a, 0xbb, 0x56, 0x16 } }
+
+class nsIAbOSXDirectory : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IABOSXDIRECTORY_IID)
+
+ virtual nsresult AssertChildNodes() = 0;
+ virtual nsresult Update() = 0;
+ virtual nsresult AssertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory) = 0;
+ virtual nsresult AssertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard) = 0;
+ virtual nsresult UnassertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard,
+ nsIMutableArray *aCardList) = 0;
+ virtual nsresult UnassertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory) = 0;
+ virtual nsresult DeleteUid(const nsACString &aUid) = 0;
+ virtual nsresult GetURI(nsACString &aURI) = 0;
+ virtual nsresult Init(const char *aUri) = 0;
+ virtual nsresult GetCardByUri(const nsACString &aUri, nsIAbOSXCard **aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIAbOSXDirectory, NS_IABOSXDIRECTORY_IID)
+
+class nsAbOSXDirectory final : public nsAbDirProperty,
+public nsIAbDirSearchListener,
+public nsIAbOSXDirectory
+{
+public:
+ nsAbOSXDirectory();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIABDIRSEARCHLISTENER
+
+ // nsIAbOSXDirectory method
+ NS_IMETHOD Init(const char *aUri) override;
+
+ // nsAbDirProperty methods
+ NS_IMETHOD GetReadOnly(bool *aReadOnly) override;
+ NS_IMETHOD GetChildCards(nsISimpleEnumerator **aCards) override;
+ NS_IMETHOD GetChildNodes(nsISimpleEnumerator **aNodes) override;
+ NS_IMETHOD GetIsQuery(bool *aResult) override;
+ NS_IMETHOD HasCard(nsIAbCard *aCard, bool *aHasCard) override;
+ NS_IMETHOD HasDirectory(nsIAbDirectory *aDirectory, bool *aHasDirectory) override;
+ NS_IMETHOD GetURI(nsACString &aURI) override;
+ NS_IMETHOD GetCardFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool caseSensitive,
+ nsIAbCard **aResult) override;
+ NS_IMETHOD GetCardsFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool aCaseSensitive,
+ nsISimpleEnumerator **aResult) override;
+ NS_IMETHOD CardForEmailAddress(const nsACString &aEmailAddress,
+ nsIAbCard **aResult) override;
+
+ // nsIAbOSXDirectory
+ nsresult AssertChildNodes() override;
+ nsresult AssertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory) override;
+ nsresult AssertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard) override;
+ nsresult UnassertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard,
+ nsIMutableArray *aCardList) override;
+ nsresult UnassertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory) override;
+
+ nsresult Update() override;
+
+ nsresult DeleteUid(const nsACString &aUid) override;
+
+ nsresult GetCardByUri(const nsACString &aUri, nsIAbOSXCard **aResult) override;
+
+ nsresult GetRootOSXDirectory(nsIAbOSXDirectory **aResult);
+
+private:
+ ~nsAbOSXDirectory();
+ nsresult FallbackSearch(nsIAbBooleanExpression *aExpression,
+ nsISimpleEnumerator **aCards);
+
+ // This is a list of nsIAbCards, kept separate from m_AddressList because:
+ // - nsIAbDirectory items that are mailing lists, must keep a list of
+ // nsIAbCards in m_AddressList, however
+ // - nsIAbDirectory items that are address books, must keep a list of
+ // nsIAbDirectory (i.e. mailing lists) in m_AddressList, AND no nsIAbCards.
+ //
+ // This wasn't too bad for mork, as that just gets a list from its database,
+ // but because we store our own copy of the list, we must store a separate
+ // list of nsIAbCards here. nsIMutableArray is used, because then it is
+ // interchangeable with m_AddressList.
+ nsCOMPtr<nsIMutableArray> mCardList;
+ nsInterfaceHashtable<nsCStringHashKey, nsIAbOSXCard> mCardStore;
+ nsCOMPtr<nsIAbOSXDirectory> mCacheTopLevelOSXAb;
+};
+
+#endif // nsAbOSXDirectory_h___
diff --git a/mailnews/addrbook/src/nsAbOSXDirectory.mm b/mailnews/addrbook/src/nsAbOSXDirectory.mm
new file mode 100644
index 000000000..e195fe3a7
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXDirectory.mm
@@ -0,0 +1,1374 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbOSXDirectory.h"
+#include "nsAbOSXCard.h"
+#include "nsAbOSXUtils.h"
+#include "nsAbQueryStringToExpression.h"
+#include "nsArrayEnumerator.h"
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIAbDirectoryQueryProxy.h"
+#include "nsIAbManager.h"
+#include "nsObjCExceptions.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsIAbBooleanExpression.h"
+#include "nsComponentManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+
+#include <AddressBook/AddressBook.h>
+
+#define kABDeletedRecords (kABDeletedRecords? kABDeletedRecords : @"ABDeletedRecords")
+#define kABUpdatedRecords (kABUpdatedRecords ? kABUpdatedRecords : @"ABUpdatedRecords")
+#define kABInsertedRecords (kABInsertedRecords ? kABInsertedRecords : @"ABInsertedRecords")
+
+static nsresult
+GetOrCreateGroup(NSString *aUid, nsIAbDirectory **aResult)
+{
+ NS_ASSERTION(aUid, "No UID for group!.");
+
+ nsAutoCString uri(NS_ABOSXDIRECTORY_URI_PREFIX);
+ AppendToCString(aUid, uri);
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(uri, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = directory);
+ return NS_OK;
+}
+
+static nsresult
+GetCard(ABRecord *aRecord, nsIAbCard **aResult, nsIAbOSXDirectory *osxDirectory)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString *uid = [aRecord uniqueId];
+ NS_ASSERTION(uid, "No UID for card!.");
+ if (!uid)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString uri(NS_ABOSXCARD_URI_PREFIX);
+ AppendToCString(uid, uri);
+ nsCOMPtr<nsIAbOSXCard> osxCard;
+ nsresult rv = osxDirectory->GetCardByUri(uri, getter_AddRefs(osxCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card = do_QueryInterface(osxCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = card);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+static nsresult
+CreateCard(ABRecord *aRecord, nsIAbCard **aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString *uid = [aRecord uniqueId];
+ NS_ASSERTION(uid, "No UID for card!.");
+ if (!uid)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_CreateInstance(NS_ABOSXCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri(NS_ABOSXCARD_URI_PREFIX);
+ AppendToCString(uid, uri);
+
+ rv = osxCard->Init(uri.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card = do_QueryInterface(osxCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = card);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+static nsresult
+Sync(NSString *aUid)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
+ ABRecord *card = [addressBook recordForUniqueId:aUid];
+ if ([card isKindOfClass:[ABGroup class]])
+ {
+ nsCOMPtr<nsIAbDirectory> directory;
+ GetOrCreateGroup(aUid, getter_AddRefs(directory));
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory =
+ do_QueryInterface(directory);
+
+ if (osxDirectory) {
+ osxDirectory->Update();
+ }
+ }
+ else {
+ nsCOMPtr<nsIAbCard> abCard;
+ nsresult rv;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory =
+ do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetCard(card, getter_AddRefs(abCard), osxDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(abCard);
+ osxCard->Update(true);
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+@interface ABChangedMonitor : NSObject
+-(void)ABChanged:(NSNotification *)aNotification;
+@end
+
+@implementation ABChangedMonitor
+-(void)ABChanged:(NSNotification *)aNotification
+{
+ NSDictionary *changes = [aNotification userInfo];
+
+ nsresult rv;
+ NSArray *inserted = [changes objectForKey:kABInsertedRecords];
+
+ if (inserted) {
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory =
+ do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ unsigned int i, count = [inserted count];
+ for (i = 0; i < count; ++i) {
+ ABAddressBook *addressBook =
+ [ABAddressBook sharedAddressBook];
+ ABRecord *card =
+ [addressBook recordForUniqueId:[inserted objectAtIndex:i]];
+ if ([card isKindOfClass:[ABGroup class]]) {
+ nsCOMPtr<nsIAbDirectory> directory;
+ GetOrCreateGroup([inserted objectAtIndex:i],
+ getter_AddRefs(directory));
+
+ rv = osxDirectory->AssertDirectory(abManager, directory);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ else {
+ nsCOMPtr<nsIAbCard> abCard;
+ // Construct a card
+ nsresult rv = CreateCard(card, getter_AddRefs(abCard));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = osxDirectory->AssertCard(abManager, abCard);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+ }
+
+ NSArray *updated = [changes objectForKey:kABUpdatedRecords];
+ if (updated) {
+ unsigned int i, count = [updated count];
+ for (i = 0; i < count; ++i) {
+ NSString *uid = [updated objectAtIndex:i];
+ Sync(uid);
+ }
+ }
+
+ NSArray *deleted = [changes objectForKey:kABDeletedRecords];
+ if (deleted) {
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory =
+ do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ unsigned int i, count = [deleted count];
+ for (i = 0; i < count; ++i) {
+ NSString *deletedUid = [deleted objectAtIndex:i];
+
+ nsAutoCString uid;
+ AppendToCString(deletedUid, uid);
+
+ rv = osxDirectory->DeleteUid(uid);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+
+ if (!inserted && !updated && !deleted) {
+ // XXX This is supposed to mean "everything was updated", but we get
+ // this whenever something has changed, so not sure what to do.
+ }
+}
+@end
+
+static nsresult
+MapConditionString(nsIAbBooleanConditionString *aCondition, bool aNegate,
+ bool &aCanHandle, ABSearchElement **aResult)
+{
+ aCanHandle = false;
+
+ nsAbBooleanConditionType conditionType = 0;
+ nsresult rv = aCondition->GetCondition(&conditionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ABSearchComparison comparison;
+ switch (conditionType) {
+ case nsIAbBooleanConditionTypes::Contains:
+ {
+ if (!aNegate) {
+ comparison = kABContainsSubString;
+ aCanHandle = true;
+ }
+ break;
+ }
+ case nsIAbBooleanConditionTypes::DoesNotContain:
+ {
+ if (aNegate) {
+ comparison = kABContainsSubString;
+ aCanHandle = true;
+ }
+ break;
+ }
+ case nsIAbBooleanConditionTypes::Is:
+ {
+ comparison = aNegate ? kABNotEqual : kABEqual;
+ aCanHandle = true;
+ break;
+ }
+ case nsIAbBooleanConditionTypes::IsNot:
+ {
+ comparison = aNegate ? kABEqual : kABNotEqual;
+ aCanHandle = true;
+ break;
+ }
+ case nsIAbBooleanConditionTypes::BeginsWith:
+ {
+ if (!aNegate) {
+ comparison = kABPrefixMatch;
+ aCanHandle = true;
+ }
+ break;
+ }
+ case nsIAbBooleanConditionTypes::EndsWith:
+ {
+ //comparison = kABSuffixMatch;
+ break;
+ }
+ case nsIAbBooleanConditionTypes::LessThan:
+ {
+ comparison = aNegate ? kABGreaterThanOrEqual : kABLessThan;
+ aCanHandle = true;
+ break;
+ }
+ case nsIAbBooleanConditionTypes::GreaterThan:
+ {
+ comparison = aNegate ? kABLessThanOrEqual : kABGreaterThan;
+ aCanHandle = true;
+ break;
+ }
+ }
+
+ if (!aCanHandle)
+ return NS_OK;
+
+ nsCString name;
+ rv = aCondition->GetName(getter_Copies(name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString value;
+ rv = aCondition->GetValue(getter_Copies(value));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t length = value.Length();
+
+ uint32_t i;
+ for (i = 0; i < nsAbOSXUtils::kPropertyMapSize; ++i) {
+ if (name.Equals(nsAbOSXUtils::kPropertyMap[i].mPropertyName)) {
+ *aResult =
+ [ABPerson searchElementForProperty:nsAbOSXUtils::kPropertyMap[i].mOSXProperty
+ label:nsAbOSXUtils::kPropertyMap[i].mOSXLabel
+ key:nsAbOSXUtils::kPropertyMap[i].mOSXKey
+ value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length]
+ comparison:comparison];
+
+ return NS_OK;
+ }
+ }
+
+ if (name.EqualsLiteral("DisplayName") && comparison == kABContainsSubString) {
+ ABSearchElement *first =
+ [ABPerson searchElementForProperty:kABFirstNameProperty
+ label:nil
+ key:nil
+ value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length]
+ comparison:comparison];
+ ABSearchElement *second =
+ [ABPerson searchElementForProperty:kABLastNameProperty
+ label:nil
+ key:nil
+ value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length]
+ comparison:comparison];
+ ABSearchElement *third =
+ [ABGroup searchElementForProperty:kABGroupNameProperty
+ label:nil
+ key:nil
+ value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length]
+ comparison:comparison];
+
+ *aResult = [ABSearchElement searchElementForConjunction:kABSearchOr children:[NSArray arrayWithObjects:first, second, third, nil]];
+
+ return NS_OK;
+ }
+
+ aCanHandle = false;
+
+ return NS_OK;
+}
+
+static nsresult
+BuildSearchElements(nsIAbBooleanExpression *aExpression,
+ bool &aCanHandle,
+ ABSearchElement **aResult)
+{
+ aCanHandle = true;
+
+ nsCOMPtr<nsIArray> expressions;
+ nsresult rv = aExpression->GetExpressions(getter_AddRefs(expressions));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAbBooleanOperationType operation;
+ rv = aExpression->GetOperation(&operation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ rv = expressions->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(count > 1 && operation != nsIAbBooleanOperationTypes::NOT,
+ "This doesn't make sense!");
+
+ NSMutableArray *array = nullptr;
+ if (count > 1)
+ array = [[NSMutableArray alloc] init];
+
+ uint32_t i;
+ nsCOMPtr<nsIAbBooleanConditionString> condition;
+ nsCOMPtr<nsIAbBooleanExpression> subExpression;
+ for (i = 0; i < count; ++i) {
+ ABSearchElement *element = nullptr;
+
+ condition = do_QueryElementAt(expressions, i);
+ if (condition) {
+ rv = MapConditionString(condition, operation == nsIAbBooleanOperationTypes::NOT, aCanHandle, &element);
+ if (NS_FAILED(rv))
+ break;
+ }
+ else {
+ subExpression = do_QueryElementAt(expressions, i);
+ if (subExpression) {
+ rv = BuildSearchElements(subExpression, aCanHandle, &element);
+ if (NS_FAILED(rv))
+ break;
+ }
+ }
+
+ if (!aCanHandle) {
+ // remember to free the array when returning early
+ [array release];
+ return NS_OK;
+ }
+
+ if (element) {
+ if (array)
+ [array addObject:element];
+ else
+ *aResult = element;
+ }
+ }
+
+ if (array) {
+ if (NS_SUCCEEDED(rv)) {
+ ABSearchConjunction conjunction = operation == nsIAbBooleanOperationTypes::AND ? kABSearchAnd : kABSearchOr;
+ *aResult = [ABSearchElement searchElementForConjunction:conjunction children:array];
+ }
+ [array release];
+ }
+
+ return rv;
+}
+
+static bool
+Search(nsIAbBooleanExpression *aExpression, NSArray **aResult)
+{
+ bool canHandle = false;
+ ABSearchElement *searchElement;
+ nsresult rv = BuildSearchElements(aExpression, canHandle, &searchElement);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (canHandle)
+ *aResult = [[ABAddressBook sharedAddressBook] recordsMatchingSearchElement:searchElement];
+
+ return canHandle;
+}
+
+static uint32_t sObserverCount = 0;
+static ABChangedMonitor *sObserver = nullptr;
+
+nsAbOSXDirectory::nsAbOSXDirectory()
+{
+}
+
+nsAbOSXDirectory::~nsAbOSXDirectory()
+{
+ if (--sObserverCount == 0) {
+ [[NSNotificationCenter defaultCenter] removeObserver:sObserver];
+ [sObserver release];
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbOSXDirectory,
+ nsAbDirProperty,
+ nsIAbOSXDirectory,
+ nsIAbDirSearchListener)
+
+NS_IMETHODIMP
+nsAbOSXDirectory::Init(const char *aUri)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv;
+ rv = nsAbDirProperty::Init(aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
+ if (sObserverCount == 0) {
+ sObserver = [[ABChangedMonitor alloc] init];
+ [[NSNotificationCenter defaultCenter] addObserver:(ABChangedMonitor*)sObserver
+ selector:@selector(ABChanged:)
+ name:kABDatabaseChangedExternallyNotification
+ object:nil];
+ }
+ ++sObserverCount;
+
+ NSArray *cards;
+ nsCOMPtr<nsIMutableArray> cardList;
+ bool isRootOSXDirectory = false;
+
+ if (!mIsQueryURI && mURINoQuery.Length() <= sizeof(NS_ABOSXDIRECTORY_URI_PREFIX))
+ isRootOSXDirectory = true;
+
+ if (mIsQueryURI || isRootOSXDirectory)
+ {
+ m_DirPrefId.AssignLiteral("ldap_2.servers.osx");
+
+ cards = [[addressBook people] arrayByAddingObjectsFromArray:[addressBook groups]];
+ if (!mCardList)
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ else
+ rv = mCardList->Clear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ cardList = mCardList;
+ }
+ else
+ {
+ nsAutoCString uid(Substring(mURINoQuery, sizeof(NS_ABOSXDIRECTORY_URI_PREFIX) - 1));
+ ABRecord *card = [addressBook recordForUniqueId:[NSString stringWithUTF8String:uid.get()]];
+ NS_ASSERTION([card isKindOfClass:[ABGroup class]], "Huh.");
+
+ m_IsMailList = true;
+ AppendToString([card valueForProperty:kABGroupNameProperty], m_ListDirName);
+
+ ABGroup *group = (ABGroup*)[addressBook recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURINoQuery, 21)).get()]];
+ cards = [[group members] arrayByAddingObjectsFromArray:[group subgroups]];
+
+ if (!m_AddressList)
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ else
+ rv = m_AddressList->Clear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ cardList = m_AddressList;
+ }
+
+
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+
+ unsigned int nbCards = [cards count];
+ nsCOMPtr<nsIAbCard> card;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory;
+ if (!isRootOSXDirectory)
+ {
+ rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ for (unsigned int i = 0; i < nbCards; ++i)
+ {
+ // If we're a Group, it's likely that the cards we're going
+ // to create were already created in the root nsAbOSXDirectory,
+ if (!isRootOSXDirectory)
+ rv = GetCard([cards objectAtIndex:i], getter_AddRefs(card),
+ rootOSXDirectory);
+ else
+ {
+ // If we're not a Group, that means we're the root nsAbOSXDirectory,
+ // which means we have to create the cards from scratch.
+ rv = CreateCard([cards objectAtIndex:i], getter_AddRefs(card));
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we're not a query directory, we're going to want to
+ // tell the AB Manager that we've added some cards so that they
+ // show up in the address book views.
+ if (!mIsQueryURI)
+ AssertCard(abManager, card);
+
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetReadOnly(bool *aReadOnly)
+{
+ NS_ENSURE_ARG_POINTER(aReadOnly);
+
+ *aReadOnly = true;
+ return NS_OK;
+}
+
+static bool
+CheckRedundantCards(nsIAbManager *aManager, nsIAbDirectory *aDirectory,
+ nsIAbCard *aCard, NSMutableArray *aCardList)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(aCard, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString uri;
+ rv = osxCard->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, false);
+ NSString *uid = [NSString stringWithUTF8String:(uri.get() + 21)];
+
+ unsigned int i, count = [aCardList count];
+ for (i = 0; i < count; ++i) {
+ if ([[[aCardList objectAtIndex:i] uniqueId] isEqualToString:uid]) {
+ [aCardList removeObjectAtIndex:i];
+ break;
+ }
+ }
+
+ if (i == count) {
+ aManager->NotifyDirectoryItemDeleted(aDirectory, aCard);
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsAbOSXDirectory::GetRootOSXDirectory(nsIAbOSXDirectory **aResult)
+{
+ if (!mCacheTopLevelOSXAb)
+ {
+ // Attempt to get card from the toplevel directories
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory =
+ do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCacheTopLevelOSXAb = osxDirectory;
+ }
+
+ NS_IF_ADDREF(*aResult = mCacheTopLevelOSXAb);
+ return NS_OK;
+}
+
+nsresult
+nsAbOSXDirectory::Update()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mIsQueryURI) {
+ return NS_OK;
+ }
+
+ ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
+ // Due to the horrible way the address book code works wrt mailing lists
+ // we have to use a different list depending on what we are. This pointer
+ // holds a reference to that list.
+ nsIMutableArray* cardList;
+ NSArray *groups, *cards;
+ if (m_IsMailList) {
+ ABGroup *group = (ABGroup*)[addressBook recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURINoQuery, 21)).get()]];
+ groups = nil;
+ cards = [[group members] arrayByAddingObjectsFromArray:[group subgroups]];
+
+ if (!m_AddressList)
+ {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // For mailing lists, store the cards in m_AddressList
+ cardList = m_AddressList;
+ }
+ else {
+ groups = [addressBook groups];
+ cards = [[addressBook people] arrayByAddingObjectsFromArray:groups];
+
+ if (!mCardList)
+ {
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // For directories, store the cards in mCardList
+ cardList = mCardList;
+ }
+
+ NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:cards];
+ uint32_t addressCount;
+ rv = cardList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (addressCount--)
+ {
+ nsCOMPtr<nsIAbCard> card(do_QueryElementAt(cardList, addressCount, &rv));
+ if (NS_FAILED(rv))
+ break;
+
+ if (CheckRedundantCards(abManager, this, card, mutableArray))
+ cardList->RemoveElementAt(addressCount);
+ }
+
+ NSEnumerator *enumerator = [mutableArray objectEnumerator];
+ ABRecord *card;
+ nsCOMPtr<nsIAbCard> abCard;
+ nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory;
+ rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while ((card = [enumerator nextObject]))
+ {
+ rv = GetCard(card, getter_AddRefs(abCard), rootOSXDirectory);
+ if (NS_FAILED(rv))
+ rv = CreateCard(card, getter_AddRefs(abCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ AssertCard(abManager, abCard);
+ }
+
+ card = (ABRecord*)[addressBook recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURINoQuery, 21)).get()]];
+ NSString * stringValue = [card valueForProperty:kABGroupNameProperty];
+ if (![stringValue isEqualToString:WrapString(m_ListDirName)])
+ {
+ nsAutoString oldValue(m_ListDirName);
+ AssignToString(stringValue, m_ListDirName);
+ nsCOMPtr<nsISupports> supports = do_QueryInterface(static_cast<nsIAbDirectory *>(this), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ abManager->NotifyItemPropertyChanged(supports, "DirName",
+ oldValue.get(), m_ListDirName.get());
+ }
+
+ if (groups)
+ {
+ mutableArray = [NSMutableArray arrayWithArray:groups];
+ nsCOMPtr<nsIAbDirectory> directory;
+ // It is ok to use m_AddressList here as only top-level directories have
+ // groups, and they will be in m_AddressList
+ if (m_AddressList)
+ {
+ rv = m_AddressList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (addressCount--)
+ {
+ directory = do_QueryElementAt(m_AddressList, addressCount, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsAutoCString uri;
+ directory->GetURI(uri);
+ uri.Cut(0, 21);
+ NSString *uid = [NSString stringWithUTF8String:uri.get()];
+
+ unsigned int j, arrayCount = [mutableArray count];
+ for (j = 0; j < arrayCount; ++j) {
+ if ([[[mutableArray objectAtIndex:j] uniqueId] isEqualToString:uid]) {
+ [mutableArray removeObjectAtIndex:j];
+ break;
+ }
+ }
+
+ if (j == arrayCount) {
+ UnassertDirectory(abManager, directory);
+ }
+ }
+ }
+
+ enumerator = [mutableArray objectEnumerator];
+ while ((card = [enumerator nextObject])) {
+ rv = GetOrCreateGroup([card uniqueId], getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AssertDirectory(abManager, directory);
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsAbOSXDirectory::AssertChildNodes()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Queries and mailing lists can't have childnodes.
+ if (mIsQueryURI || m_IsMailList) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NSArray *groups = [[ABAddressBook sharedAddressBook] groups];
+
+ unsigned int i, count = [groups count];
+
+ if (count > 0 && !m_AddressList) {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ for (i = 0; i < count; ++i) {
+ rv = GetOrCreateGroup([[groups objectAtIndex:i] uniqueId],
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AssertDirectory(abManager, directory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsAbOSXDirectory::AssertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory)
+{
+ uint32_t pos;
+ if (m_AddressList &&
+ NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ // We already have this directory, so no point in adding it again.
+ return NS_OK;
+
+ nsresult rv;
+ if (!m_AddressList) {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = m_AddressList->AppendElement(aDirectory, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aManager->NotifyDirectoryItemAdded(this, aDirectory);
+}
+
+nsresult
+nsAbOSXDirectory::AssertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard)
+{
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+ aCard->SetDirectoryId(ourUuid);
+
+ nsresult rv = m_IsMailList ? m_AddressList->AppendElement(aCard, false) :
+ mCardList->AppendElement(aCard, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the card's URI and add it to our card store
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(aCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString uri;
+ rv = osxCard->GetURI(uri);
+
+ nsCOMPtr<nsIAbOSXCard> retrievedCard;
+ if (!mCardStore.Get(uri, getter_AddRefs(retrievedCard)))
+ mCardStore.Put(uri, osxCard);
+
+ return aManager->NotifyDirectoryItemAdded(this, aCard);
+}
+
+nsresult
+nsAbOSXDirectory::UnassertCard(nsIAbManager *aManager,
+ nsIAbCard *aCard,
+ nsIMutableArray *aCardList)
+{
+ nsresult rv;
+ uint32_t pos;
+
+ if (NS_SUCCEEDED(aCardList->IndexOf(0, aCard, &pos)))
+ rv = aCardList->RemoveElementAt(pos);
+
+ return aManager->NotifyDirectoryItemDeleted(this, aCard);
+}
+
+nsresult
+nsAbOSXDirectory::UnassertDirectory(nsIAbManager *aManager,
+ nsIAbDirectory *aDirectory)
+{
+ NS_ENSURE_TRUE(m_AddressList, NS_ERROR_NULL_POINTER);
+
+ uint32_t pos;
+ if (NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ {
+ nsresult rv = m_AddressList->RemoveElementAt(pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return aManager->NotifyDirectoryItemDeleted(this, aDirectory);
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetChildNodes(nsISimpleEnumerator **aNodes)
+{
+ NS_ENSURE_ARG_POINTER(aNodes);
+
+ // Queries don't have childnodes.
+ if (mIsQueryURI || m_IsMailList || !m_AddressList)
+ return NS_NewEmptyEnumerator(aNodes);
+
+ return NS_NewArrayEnumerator(aNodes, m_AddressList);
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetChildCards(nsISimpleEnumerator **aCards)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_ARG_POINTER(aCards);
+
+ nsresult rv;
+ NSArray *cards;
+ if (mIsQueryURI)
+ {
+ nsCOMPtr<nsIAbBooleanExpression> expression;
+ rv = nsAbQueryStringToExpression::Convert(mQueryString,
+ getter_AddRefs(expression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool canHandle = !m_IsMailList && Search(expression, &cards);
+ if (!canHandle)
+ return FallbackSearch(expression, aCards);
+
+ if (!mCardList)
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ else
+ mCardList->Clear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The uuid for initializing cards
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+
+ // Fill the results array and update the card list
+ unsigned int nbCards = [cards count];
+
+ unsigned int i;
+ nsCOMPtr<nsIAbCard> card;
+ nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory;
+ rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (i = 0; i < nbCards; ++i)
+ {
+ rv = GetCard([cards objectAtIndex:i], getter_AddRefs(card),
+ rootOSXDirectory);
+
+ if (NS_FAILED(rv))
+ rv = CreateCard([cards objectAtIndex:i],
+ getter_AddRefs(card));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ card->SetDirectoryId(ourUuid);
+
+ mCardList->AppendElement(card, false);
+ }
+
+ return NS_NewArrayEnumerator(aCards, mCardList);
+ }
+
+ // Not a search, so just return the appropriate list of items.
+ return m_IsMailList ? NS_NewArrayEnumerator(aCards, m_AddressList) :
+ NS_NewArrayEnumerator(aCards, mCardList);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetIsQuery(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mIsQueryURI;
+ return NS_OK;
+}
+
+/* Recursive method that searches for a child card by URI. If it cannot find
+ * it within this directory, it checks all subfolders.
+ */
+NS_IMETHODIMP
+nsAbOSXDirectory::GetCardByUri(const nsACString &aUri, nsIAbOSXCard **aResult)
+{
+ nsCOMPtr<nsIAbOSXCard> osxCard;
+
+ // Base Case
+ if (mCardStore.Get(aUri, getter_AddRefs(osxCard)))
+ {
+ NS_IF_ADDREF(*aResult = osxCard);
+ return NS_OK;
+ }
+ // Search children
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = this->GetChildNodes(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> item;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> childDirectory;
+ childDirectory = do_QueryInterface(item, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = childDirectory->GetCardByUri(aUri, getter_AddRefs(osxCard));
+ if (NS_SUCCEEDED(rv))
+ {
+ NS_IF_ADDREF(*aResult = osxCard);
+ return NS_OK;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetCardFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool aCaseSensitive,
+ nsIAbCard **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (aValue.IsEmpty())
+ return NS_OK;
+
+ nsIMutableArray *list = m_IsMailList ? m_AddressList : mCardList;
+
+ if (!list)
+ return NS_OK;
+
+ uint32_t length;
+ nsresult rv = list->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card;
+ nsAutoCString cardValue;
+
+ for (uint32_t i = 0; i < length && !*aResult; ++i)
+ {
+ card = do_QueryElementAt(list, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = card->GetPropertyAsAUTF8String(aProperty, cardValue);
+ if (NS_SUCCEEDED(rv))
+ {
+#ifdef MOZILLA_INTERNAL_API
+ bool equal = aCaseSensitive ? cardValue.Equals(aValue) :
+ cardValue.Equals(aValue, nsCaseInsensitiveCStringComparator());
+#else
+ bool equal = aCaseSensitive ? cardValue.Equals(aValue) :
+ cardValue.Equals(aValue, CaseInsensitiveCompare);
+#endif
+ if (equal)
+ NS_IF_ADDREF(*aResult = card);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetCardsFromProperty(const char *aProperty,
+ const nsACString &aValue,
+ bool aCaseSensitive,
+ nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (aValue.IsEmpty())
+ return NS_NewEmptyEnumerator(aResult);
+
+ nsIMutableArray *list = m_IsMailList ? m_AddressList : mCardList;
+
+ if (!list)
+ return NS_NewEmptyEnumerator(aResult);
+
+ uint32_t length;
+ nsresult rv = list->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMArray<nsIAbCard> resultArray;
+ nsCOMPtr<nsIAbCard> card;
+ nsAutoCString cardValue;
+
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ card = do_QueryElementAt(list, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = card->GetPropertyAsAUTF8String(aProperty, cardValue);
+ if (NS_SUCCEEDED(rv))
+ {
+#ifdef MOZILLA_INTERNAL_API
+ bool equal = aCaseSensitive ? cardValue.Equals(aValue) :
+ cardValue.Equals(aValue, nsCaseInsensitiveCStringComparator());
+#else
+ bool equal = aCaseSensitive ? cardValue.Equals(aValue) :
+ cardValue.Equals(aValue, CaseInsensitiveCompare);
+#endif
+ if (equal)
+ resultArray.AppendObject(card);
+ }
+ }
+ }
+
+ return NS_NewArrayEnumerator(aResult, resultArray);
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::CardForEmailAddress(const nsACString &aEmailAddress,
+ nsIAbCard **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (aEmailAddress.IsEmpty())
+ return NS_OK;
+
+ nsIMutableArray *list = m_IsMailList ? m_AddressList : mCardList;
+
+ if (!list)
+ return NS_OK;
+
+ uint32_t length;
+ nsresult rv = list->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card;
+
+ for (uint32_t i = 0; i < length && !*aResult; ++i)
+ {
+ card = do_QueryElementAt(list, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ bool hasEmailAddress = false;
+
+ rv = card->HasEmailAddress(aEmailAddress, &hasEmailAddress);
+ if (NS_SUCCEEDED(rv) && hasEmailAddress)
+ NS_IF_ADDREF(*aResult = card);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::HasCard(nsIAbCard *aCard, bool *aHasCard)
+{
+ NS_ENSURE_ARG_POINTER(aCard);
+ NS_ENSURE_ARG_POINTER(aHasCard);
+
+ nsresult rv = NS_OK;
+ uint32_t index;
+ if (m_IsMailList)
+ {
+ if (m_AddressList)
+ rv = m_AddressList->IndexOf(0, aCard, &index);
+ }
+ else if (mCardList)
+ rv = mCardList->IndexOf(0, aCard, &index);
+
+ *aHasCard = NS_SUCCEEDED(rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::HasDirectory(nsIAbDirectory *aDirectory,
+ bool *aHasDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+ NS_ENSURE_ARG_POINTER(aHasDirectory);
+
+ *aHasDirectory = false;
+
+ uint32_t pos;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ *aHasDirectory = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::OnSearchFinished(int32_t aResult, const nsAString &aErrorMsg)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::OnSearchFoundCard(nsIAbCard *aCard)
+{
+ nsresult rv;
+ if (!m_AddressList) {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!mCardList) {
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = m_AddressList->AppendElement(aCard, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mCardList->AppendElement(aCard, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+ aCard->SetDirectoryId(ourUuid);
+
+ return NS_OK;
+}
+
+nsresult
+nsAbOSXDirectory::FallbackSearch(nsIAbBooleanExpression *aExpression,
+ nsISimpleEnumerator **aCards)
+{
+ nsresult rv;
+
+ if (mCardList)
+ rv = mCardList->Clear();
+ else
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_AddressList) {
+ m_AddressList->Clear();
+ }
+ else {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIAbDirectoryQueryArguments> arguments =
+ do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetExpression(aExpression);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't search the subdirectories. If the current directory is a mailing
+ // list, it won't have any subdirectories. If the current directory is an
+ // addressbook, searching both it and the subdirectories (the mailing
+ // lists), will yield duplicate results because every entry in a mailing
+ // list will be an entry in the parent addressbook.
+ rv = arguments->SetQuerySubDirectories(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the directory without the query
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(mURINoQuery, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initiate the proxy query with the no query directory
+ nsCOMPtr<nsIAbDirectoryQueryProxy> queryProxy =
+ do_CreateInstance(NS_ABDIRECTORYQUERYPROXY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = queryProxy->Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t context = 0;
+ rv = queryProxy->DoQuery(directory, arguments, this, -1, 0, &context);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewArrayEnumerator(aCards, m_AddressList);
+}
+
+nsresult nsAbOSXDirectory::DeleteUid(const nsACString &aUid)
+{
+ if (!m_AddressList)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // At this stage we don't know if aUid represents a card or group. The OS X
+ // interfaces don't give us chance to find out, so we have to go through
+ // our lists to find it.
+
+ // First, we'll see if its in the group list as it is likely to be shorter.
+
+ // See if this item is in our address list
+ uint32_t addressCount;
+ rv = m_AddressList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri(NS_ABOSXDIRECTORY_URI_PREFIX);
+ uri.Append(aUid);
+
+ // Iterate backwards in case we remove something
+ while (addressCount--)
+ {
+ nsCOMPtr<nsIAbItem> abItem(do_QueryElementAt(m_AddressList,
+ addressCount, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIAbDirectory> directory(do_QueryInterface(abItem, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString dirUri;
+ directory->GetURI(dirUri);
+ if (uri.Equals(dirUri))
+ return UnassertDirectory(abManager, directory);
+ } else {
+ nsCOMPtr<nsIAbOSXCard> osxCard(do_QueryInterface(abItem, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString cardUri;
+ osxCard->GetURI(cardUri);
+ if (uri.Equals(cardUri))
+ {
+ nsCOMPtr<nsIAbCard> card(do_QueryInterface(osxCard, &rv));
+ if (NS_SUCCEEDED(rv))
+ return UnassertCard(abManager, card, m_AddressList);
+ }
+ }
+ }
+ }
+
+ // Second, see if it is one of the cards.
+ if (!mCardList)
+ return NS_ERROR_FAILURE;
+
+ uri = NS_ABOSXCARD_URI_PREFIX;
+ uri.Append(aUid);
+
+ rv = mCardList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (addressCount--)
+ {
+ nsCOMPtr<nsIAbOSXCard> osxCard(do_QueryElementAt(mCardList, addressCount, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsAutoCString cardUri;
+ osxCard->GetURI(cardUri);
+
+ if (uri.Equals(cardUri)) {
+ nsCOMPtr<nsIAbCard> card(do_QueryInterface(osxCard, &rv));
+ if (NS_SUCCEEDED(rv))
+ return UnassertCard(abManager, card, mCardList);
+ }
+ }
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbOSXUtils.h b/mailnews/addrbook/src/nsAbOSXUtils.h
new file mode 100644
index 000000000..fc2f2a546
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXUtils.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbOSXUtils_h___
+#define nsAbOSXUtils_h___
+
+#include <Foundation/NSString.h>
+#include "nsStringGlue.h"
+
+class nsString;
+class nsCString;
+class nsAbCardProperty;
+
+NSString *WrapString(const nsString &aString);
+void AppendToString(const NSString *aString, nsString &aResult);
+void AssignToString(const NSString *aString, nsString &aResult);
+void AppendToCString(const NSString *aString, nsCString &aResult);
+
+struct nsAbOSXPropertyMap
+{
+ NSString * const mOSXProperty;
+ NSString * const mOSXLabel;
+ NSString * const mOSXKey;
+ const char *mPropertyName;
+};
+
+class nsAbOSXUtils
+{
+public:
+ static const nsAbOSXPropertyMap kPropertyMap[];
+ static const uint32_t kPropertyMapSize;
+};
+
+#endif // nsAbOSXUtils_h___
diff --git a/mailnews/addrbook/src/nsAbOSXUtils.mm b/mailnews/addrbook/src/nsAbOSXUtils.mm
new file mode 100644
index 000000000..60802d772
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOSXUtils.mm
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbOSXUtils.h"
+#include "nsStringGlue.h"
+#include "nsAbOSXCard.h"
+#include "nsMemory.h"
+#include "mozilla/ArrayUtils.h"
+using namespace mozilla;
+
+#include <AddressBook/AddressBook.h>
+#define kABDepartmentProperty (kABDepartmentProperty ? kABDepartmentProperty : @"ABDepartment")
+
+NSString*
+WrapString(const nsString &aString)
+{
+ unichar* chars = reinterpret_cast<unichar*>(const_cast<char16_t*>(aString.get()));
+
+ return [NSString stringWithCharacters:chars
+ length:aString.Length()];
+}
+
+void
+AppendToString(const NSString *aString, nsString &aResult)
+{
+ if (aString) {
+ const char *chars = [aString UTF8String];
+ if (chars) {
+ aResult.Append(NS_ConvertUTF8toUTF16(chars));
+ }
+ }
+}
+
+void
+AssignToString(const NSString *aString, nsString &aResult)
+{
+ if (aString) {
+ const char *chars = [aString UTF8String];
+ if (chars)
+ CopyUTF8toUTF16(nsDependentCString(chars), aResult);
+ }
+}
+
+void
+AppendToCString(const NSString *aString, nsCString &aResult)
+{
+ if (aString) {
+ const char *chars = [aString UTF8String];
+ if (chars) {
+ aResult.Append(chars);
+ }
+ }
+}
+
+// Some properties can't be easily mapped back and forth.
+#define DONT_MAP(moz_name, osx_property, osx_label, osx_key)
+
+#define DEFINE_PROPERTY(moz_name, osx_property, osx_label, osx_key) \
+ { osx_property, osx_label, osx_key, #moz_name },
+
+const nsAbOSXPropertyMap nsAbOSXUtils::kPropertyMap[] = {
+ DEFINE_PROPERTY(FirstName, kABFirstNameProperty, nil, nil)
+ DEFINE_PROPERTY(LastName, kABLastNameProperty, nil, nil)
+ DONT_MAP("DisplayName", nil, nil, nil)
+ DEFINE_PROPERTY(PhoneticFirstName, kABFirstNamePhoneticProperty, nil, nil)
+ DEFINE_PROPERTY(PhoneticLastName, kABLastNamePhoneticProperty, nil, nil)
+ DEFINE_PROPERTY(NickName, kABNicknameProperty, nil, nil)
+ DONT_MAP(PrimaryEmail, kABEmailProperty, nil, nil)
+ DONT_MAP(SecondEmail, kABEmailProperty, nil, nil)
+ DEFINE_PROPERTY(WorkPhone, kABPhoneProperty, kABPhoneWorkLabel, nil)
+ DEFINE_PROPERTY(HomePhone, kABPhoneProperty, kABPhoneHomeLabel, nil)
+ DEFINE_PROPERTY(FaxNumber, kABPhoneProperty, kABPhoneWorkFAXLabel, nil)
+ DEFINE_PROPERTY(PagerNumber, kABPhoneProperty, kABPhonePagerLabel, nil)
+ DEFINE_PROPERTY(CellularNumber, kABPhoneProperty, kABPhoneMobileLabel, nil)
+ DEFINE_PROPERTY(HomeAddress, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressStreetKey)
+ DEFINE_PROPERTY(HomeCity, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressCityKey)
+ DEFINE_PROPERTY(HomeState, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressStateKey)
+ DEFINE_PROPERTY(HomeZipCode, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressZIPKey)
+ DEFINE_PROPERTY(HomeCountry, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressCountryKey)
+ DEFINE_PROPERTY(WorkAddress, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressStreetKey)
+ DEFINE_PROPERTY(WorkCity, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressCityKey)
+ DEFINE_PROPERTY(WorkState, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressStateKey)
+ DEFINE_PROPERTY(WorkZipCode, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressZIPKey)
+ DEFINE_PROPERTY(WorkCountry, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressCountryKey)
+ DEFINE_PROPERTY(JobTitle, kABJobTitleProperty, nil, nil)
+ DEFINE_PROPERTY(Department, kABDepartmentProperty, nil, nil)
+ DEFINE_PROPERTY(Company, kABOrganizationProperty, nil, nil)
+ // This was kABAIMInstantProperty previously, but it was deprecated in OS X 10.7.
+ DONT_MAP(_AimScreenName, kABInstantMessageProperty, nil, nil)
+ DEFINE_PROPERTY(WebPage1, kABHomePageProperty, nil, nil)
+ DONT_MAP(WebPage2, kABHomePageProperty, nil, nil)
+ DONT_MAP(BirthYear, "birthyear", nil, nil)
+ DONT_MAP(BirthMonth, "birthmonth", nil, nil)
+ DONT_MAP(BirthDay, "birthday", nil, nil)
+ DONT_MAP(Custom1, "custom1", nil, nil)
+ DONT_MAP(Custom2, "custom2", nil, nil)
+ DONT_MAP(Custom3, "custom3", nil, nil)
+ DONT_MAP(Custom4, "custom4", nil, nil)
+ DEFINE_PROPERTY(Note, kABNoteProperty, nil, nil)
+ DONT_MAP("PreferMailFormat", nil, nil, nil)
+ DONT_MAP("LastModifiedDate", modifytimestamp, nil, nil)
+};
+
+const uint32_t nsAbOSXUtils::kPropertyMapSize =
+ ArrayLength(nsAbOSXUtils::kPropertyMap);
diff --git a/mailnews/addrbook/src/nsAbOutlookDirFactory.cpp b/mailnews/addrbook/src/nsAbOutlookDirFactory.cpp
new file mode 100644
index 000000000..164e5a475
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOutlookDirFactory.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsAbOutlookDirFactory.h"
+#include "nsAbWinHelper.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbManager.h"
+#include "nsEnumeratorUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsAbBaseCID.h"
+#include "mozilla/Logging.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gAbOutlookDirFactoryLog
+ = PR_NewLogModule("nsAbOutlookDirFactoryLog");
+#endif
+
+#define PRINTF(args) MOZ_LOG(nsAbOutlookDirFactoryLog, mozilla::LogLevel::Debug, args)
+
+
+NS_IMPL_ISUPPORTS(nsAbOutlookDirFactory, nsIAbDirFactory)
+
+nsAbOutlookDirFactory::nsAbOutlookDirFactory(void)
+{
+}
+
+nsAbOutlookDirFactory::~nsAbOutlookDirFactory(void)
+{
+}
+
+extern const char *kOutlookDirectoryScheme;
+
+NS_IMETHODIMP
+nsAbOutlookDirFactory::GetDirectories(const nsAString &aDirName,
+ const nsACString &aURI,
+ const nsACString &aPrefName,
+ nsISimpleEnumerator **aDirectories)
+{
+ NS_ENSURE_ARG_POINTER(aDirectories);
+
+ *aDirectories = nullptr;
+ nsresult rv = NS_OK;
+ nsCString stub;
+ nsCString entry;
+ nsAbWinType abType = getAbWinType(kOutlookDirectoryScheme,
+ nsCString(aURI).get(), stub, entry);
+
+ if (abType == nsAbWinType_Unknown) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAbWinHelperGuard mapiAddBook(abType);
+ nsMapiEntryArray folders;
+ ULONG nbFolders = 0;
+ nsCOMPtr<nsIMutableArray> directories(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mapiAddBook->IsOK() || !mapiAddBook->GetFolders(folders)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString entryId;
+ nsAutoCString uri;
+
+ for (ULONG i = 0; i < folders.mNbEntries; ++i) {
+ folders.mEntries[i].ToString(entryId);
+ buildAbWinUri(kOutlookDirectoryScheme, abType, uri);
+ uri.Append(entryId);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(uri, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ directories->AppendElement(directory, false);
+ }
+ return NS_NewArrayEnumerator(aDirectories, directories);
+}
+
+// No actual deletion, since you cannot create the address books from Mozilla.
+NS_IMETHODIMP nsAbOutlookDirFactory::DeleteDirectory(nsIAbDirectory *aDirectory)
+{
+ return NS_OK;
+}
+
diff --git a/mailnews/addrbook/src/nsAbOutlookDirFactory.h b/mailnews/addrbook/src/nsAbOutlookDirFactory.h
new file mode 100644
index 000000000..c66a9c904
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOutlookDirFactory.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsAbOutlookDirFactory_h___
+#define nsAbOutlookDirFactory_h___
+
+#include "nsIAbDirFactory.h"
+
+class nsAbOutlookDirFactory : public nsIAbDirFactory
+{
+public:
+ nsAbOutlookDirFactory(void);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRFACTORY
+
+private:
+ virtual ~nsAbOutlookDirFactory(void);
+};
+
+#endif // nsAbOutlookDirFactory_h___
diff --git a/mailnews/addrbook/src/nsAbOutlookDirectory.cpp b/mailnews/addrbook/src/nsAbOutlookDirectory.cpp
new file mode 100644
index 000000000..0d39d1d17
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOutlookDirectory.cpp
@@ -0,0 +1,1539 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsAbOutlookDirectory.h"
+#include "nsAbWinHelper.h"
+
+#include "nsAbBaseCID.h"
+#include "nsIAbCard.h"
+#include "nsStringGlue.h"
+#include "nsAbDirectoryQuery.h"
+#include "nsIAbBooleanExpression.h"
+#include "nsIAbManager.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsAbQueryStringToExpression.h"
+#include "nsAbUtils.h"
+#include "nsEnumeratorUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Logging.h"
+#include "prthread.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsCRTGlue.h"
+#include "nsArrayUtils.h"
+#include "nsArrayEnumerator.h"
+#include "nsMsgUtils.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gAbOutlookDirectoryLog
+ = PR_NewLogModule("nsAbOutlookDirectoryLog");
+#endif
+
+#define PRINTF(args) MOZ_LOG(gAbOutlookDirectoryLog, mozilla::LogLevel::Debug, args)
+
+nsAbOutlookDirectory::nsAbOutlookDirectory(void)
+ : nsAbDirProperty(),
+ mCurrentQueryId(0), mSearchContext(-1),
+ mAbWinType(nsAbWinType_Unknown), mMapiData(nullptr)
+{
+ mMapiData = new nsMapiEntry ;
+ mProtector = PR_NewLock() ;
+}
+
+nsAbOutlookDirectory::~nsAbOutlookDirectory(void)
+{
+ if (mMapiData) { delete mMapiData ; }
+ if (mProtector) { PR_DestroyLock(mProtector) ; }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbOutlookDirectory, nsAbDirProperty,
+ nsIAbDirectoryQuery, nsIAbDirectorySearch,
+ nsIAbDirSearchListener)
+
+NS_IMETHODIMP nsAbOutlookDirectory::Init(const char *aUri)
+{
+ nsresult rv = nsAbDirProperty::Init(aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString entry;
+ nsAutoCString stub;
+
+ mAbWinType = getAbWinType(kOutlookDirectoryScheme, mURINoQuery.get(), stub, entry);
+ if (mAbWinType == nsAbWinType_Unknown) {
+ PRINTF(("Huge problem URI=%s.\n", mURINoQuery));
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsAbWinHelperGuard mapiAddBook (mAbWinType);
+ nsString prefix;
+ nsAutoString unichars;
+ ULONG objectType = 0;
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ mMapiData->Assign(entry);
+ if (!mapiAddBook->GetPropertyLong(*mMapiData, PR_OBJECT_TYPE, objectType)) {
+ PRINTF(("Cannot get type.\n"));
+ return NS_ERROR_FAILURE;
+ }
+ if (!mapiAddBook->GetPropertyUString(*mMapiData, PR_DISPLAY_NAME_W, unichars)) {
+ PRINTF(("Cannot get name.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mAbWinType == nsAbWinType_Outlook)
+ prefix.AssignLiteral("OP ");
+ else
+ prefix.AssignLiteral("OE ");
+ prefix.Append(unichars);
+
+ if (objectType == MAPI_DISTLIST) {
+ m_IsMailList = true;
+ SetDirName(unichars);
+ }
+ else {
+ m_IsMailList = false;
+ SetDirName(prefix);
+ }
+
+ return UpdateAddressList();
+}
+
+// nsIAbDirectory methods
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetDirType(int32_t *aDirType)
+{
+ NS_ENSURE_ARG_POINTER(aDirType);
+ *aDirType = MAPIDirectory;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetURI(nsACString &aURI)
+{
+ if (mURI.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetChildNodes(nsISimpleEnumerator **aNodes)
+{
+ NS_ENSURE_ARG_POINTER(aNodes);
+
+ *aNodes = nullptr;
+
+ if (mIsQueryURI) {
+ return NS_NewEmptyEnumerator(aNodes);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> nodeList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetChildNodes(nodeList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewArrayEnumerator(aNodes, nodeList);
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetChildCards(nsISimpleEnumerator **aCards)
+{
+ NS_ENSURE_ARG_POINTER(aCards);
+ *aCards = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> cardList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCardList.Clear();
+
+ rv = mIsQueryURI ? StartSearch() : GetChildCards(cardList, nullptr);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!m_AddressList)
+ {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Fill the results array and update the card list
+ // Also update the address list and notify any changes.
+ uint32_t nbCards = 0;
+
+ NS_NewArrayEnumerator(aCards, cardList);
+ cardList->GetLength(&nbCards);
+
+ nsCOMPtr<nsIAbCard> card;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+
+ for (uint32_t i = 0; i < nbCards; ++i)
+ {
+ card = do_QueryElementAt(cardList, i, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ if (!mCardList.Get(card, nullptr))
+ {
+ // We are dealing with a new element (probably directly
+ // added from Outlook), we may need to sync m_AddressList
+ mCardList.Put(card, card);
+
+ bool isMailList = false;
+
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isMailList)
+ {
+ // We can have mailing lists only in folder,
+ // we must add the directory to m_AddressList
+ nsCString mailListUri;
+ rv = card->GetMailListURI(getter_Copies(mailListUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIAbDirectory> mailList;
+ rv = abManager->GetDirectory(mailListUri, getter_AddRefs(mailList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_AddressList->AppendElement(mailList, false);
+ NotifyItemAddition(mailList);
+ }
+ else if (m_IsMailList)
+ {
+ m_AddressList->AppendElement(card, false);
+ NotifyItemAddition(card);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetIsQuery(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mIsQueryURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::HasCard(nsIAbCard *aCard, bool *aHasCard)
+{
+ if (!aCard || !aHasCard)
+ return NS_ERROR_NULL_POINTER;
+
+ *aHasCard = mCardList.Get(aCard, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::HasDirectory(nsIAbDirectory *aDirectory, bool *aHasDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+ NS_ENSURE_ARG_POINTER(aHasDirectory);
+
+ *aHasDirectory = false;
+
+ uint32_t pos;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ *aHasDirectory = true;
+
+ return NS_OK;
+}
+
+
+static nsresult ExtractCardEntry(nsIAbCard *aCard, nsCString& aEntry)
+{
+ aEntry.Truncate();
+
+ nsCString uri;
+ aCard->GetPropertyAsAUTF8String("OutlookEntryURI", uri);
+
+ // If we don't have a URI, uri will be empty. getAbWinType doesn't set
+ // aEntry to anything if uri is empty, so it will be truncated, allowing us
+ // to accept cards not initialized by us.
+ nsAutoCString stub;
+ getAbWinType(kOutlookCardScheme, uri.get(), stub, aEntry);
+ return NS_OK;
+}
+
+static nsresult ExtractDirectoryEntry(nsIAbDirectory *aDirectory, nsCString& aEntry)
+{
+ aEntry.Truncate();
+ nsCString uri;
+ nsresult rv = aDirectory->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString stub;
+ nsAbWinType objType = getAbWinType(kOutlookDirectoryScheme, uri.get(), stub, aEntry);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DeleteCards(nsIArray *aCardList)
+{
+ if (mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; }
+ if (!aCardList) { return NS_ERROR_NULL_POINTER ; }
+ uint32_t nbCards = 0 ;
+ nsresult retCode = NS_OK ;
+ nsAbWinHelperGuard mapiAddBook (mAbWinType) ;
+
+ if (!mapiAddBook->IsOK()) { return NS_ERROR_FAILURE ; }
+
+ retCode = aCardList->GetLength(&nbCards);
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ uint32_t i = 0 ;
+ nsAutoCString entryString ;
+ nsMapiEntry cardEntry ;
+
+ for (i = 0 ; i < nbCards ; ++ i) {
+ nsCOMPtr<nsIAbCard> card(do_QueryElementAt(aCardList, i, &retCode));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ retCode = ExtractCardEntry(card, entryString) ;
+ if (NS_SUCCEEDED(retCode) && !entryString.IsEmpty()) {
+ card->SetDirectoryId(EmptyCString());
+
+ cardEntry.Assign(entryString) ;
+ if (!mapiAddBook->DeleteEntry(*mMapiData, cardEntry)) {
+ PRINTF(("Cannot delete card %s.\n", entryString.get())) ;
+ }
+ else {
+ mCardList.Remove(card);
+ if (m_IsMailList && m_AddressList)
+ {
+ uint32_t pos;
+ if (NS_SUCCEEDED(m_AddressList->IndexOf(0, card, &pos)))
+ m_AddressList->RemoveElementAt(pos);
+ }
+ retCode = NotifyItemDeletion(card);
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ }
+ }
+ else {
+ PRINTF(("Card doesn't belong in this directory.\n")) ;
+ }
+ }
+ return NS_OK ;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DeleteDirectory(nsIAbDirectory *aDirectory)
+{
+ if (mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; }
+ if (!aDirectory) { return NS_ERROR_NULL_POINTER ; }
+ nsresult retCode = NS_OK ;
+ nsAbWinHelperGuard mapiAddBook (mAbWinType) ;
+ nsAutoCString entryString ;
+
+ if (!mapiAddBook->IsOK()) { return NS_ERROR_FAILURE ; }
+ retCode = ExtractDirectoryEntry(aDirectory, entryString) ;
+ if (NS_SUCCEEDED(retCode) && !entryString.IsEmpty()) {
+ nsMapiEntry directoryEntry ;
+
+ directoryEntry.Assign(entryString) ;
+ if (!mapiAddBook->DeleteEntry(*mMapiData, directoryEntry)) {
+ PRINTF(("Cannot delete directory %s.\n", entryString.get())) ;
+ }
+ else {
+ uint32_t pos;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ m_AddressList->RemoveElementAt(pos);
+
+ retCode = NotifyItemDeletion(aDirectory);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ }
+ }
+ else {
+ PRINTF(("Directory doesn't belong to this folder.\n")) ;
+ }
+ return retCode ;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::AddCard(nsIAbCard *aData, nsIAbCard **addedCard)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ NS_ENSURE_ARG_POINTER(aData);
+
+ nsresult retCode = NS_OK ;
+ bool hasCard = false ;
+
+ retCode = HasCard(aData, &hasCard) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ if (hasCard) {
+ PRINTF(("Has card.\n")) ;
+ NS_IF_ADDREF(*addedCard = aData);
+ return NS_OK ;
+ }
+ retCode = CreateCard(aData, addedCard) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+
+ mCardList.Put(*addedCard, *addedCard);
+
+ if (!m_AddressList)
+ {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &retCode);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ }
+
+ if (m_IsMailList)
+ m_AddressList->AppendElement(*addedCard, false);
+ NotifyItemAddition(*addedCard) ;
+ return retCode ;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DropCard(nsIAbCard *aData, bool needToCopyCard)
+{
+ nsCOMPtr <nsIAbCard> addedCard;
+ return AddCard(aData, getter_AddRefs(addedCard));
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::AddMailList(nsIAbDirectory *aMailList, nsIAbDirectory **addedList)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+ NS_ENSURE_ARG_POINTER(aMailList);
+ NS_ENSURE_ARG_POINTER(addedList);
+ if (m_IsMailList)
+ return NS_OK;
+ nsAbWinHelperGuard mapiAddBook (mAbWinType);
+ nsAutoCString entryString;
+ nsMapiEntry newEntry;
+ bool didCopy = false;
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+ nsresult rv = ExtractDirectoryEntry(aMailList, entryString);
+ if (NS_SUCCEEDED(rv) && !entryString.IsEmpty())
+ {
+ nsMapiEntry sourceEntry;
+
+ sourceEntry.Assign(entryString);
+ mapiAddBook->CopyEntry(*mMapiData, sourceEntry, newEntry);
+ }
+ if (newEntry.mByteCount == 0)
+ {
+ if (!mapiAddBook->CreateDistList(*mMapiData, newEntry))
+ return NS_ERROR_FAILURE;
+ }
+ else {
+ didCopy = true;
+ }
+ newEntry.ToString(entryString);
+ nsAutoCString uri;
+
+ buildAbWinUri(kOutlookDirectoryScheme, mAbWinType, uri);
+ uri.Append(entryString);
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> newList;
+ rv = abManager->GetDirectory(uri, getter_AddRefs(newList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!didCopy)
+ {
+ rv = newList->CopyMailList(aMailList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = newList->EditMailListToDatabase(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!m_AddressList)
+ {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ m_AddressList->AppendElement(newList, false);
+ NotifyItemAddition(newList);
+ NS_IF_ADDREF(*addedList = newList);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::EditMailListToDatabase(nsIAbCard *listCard)
+{
+ if (mIsQueryURI)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsresult rv;
+ nsString name;
+ nsAbWinHelperGuard mapiAddBook(mAbWinType);
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ rv = GetDirName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mapiAddBook->SetPropertyUString(*mMapiData, PR_DISPLAY_NAME_W,
+ name.get()))
+ return NS_ERROR_FAILURE;
+
+ return CommitAddressList();
+}
+
+struct OutlookTableAttr
+{
+ const char *mOuterName ;
+ ULONG mMapiProp ;
+} ;
+
+// Here, we are forced to use the Ascii versions of the properties
+// instead of the widechar ones, because the content restriction
+// operators do not work on unicode strings in mapi.
+static const OutlookTableAttr OutlookTableStringToProp [] =
+{
+ {kFirstNameProperty, PR_GIVEN_NAME_A},
+ {kLastNameProperty, PR_SURNAME_A},
+ {kDisplayNameProperty, PR_DISPLAY_NAME_A},
+ {kNicknameProperty, PR_NICKNAME_A},
+ {kPriEmailProperty, PR_EMAIL_ADDRESS_A},
+ {kWorkPhoneProperty, PR_BUSINESS_TELEPHONE_NUMBER_A},
+ {kHomePhoneProperty, PR_HOME_TELEPHONE_NUMBER_A},
+ {kFaxProperty, PR_BUSINESS_FAX_NUMBER_A},
+ {kPagerProperty, PR_PAGER_TELEPHONE_NUMBER_A},
+ {kCellularProperty, PR_MOBILE_TELEPHONE_NUMBER_A},
+ {kHomeAddressProperty, PR_HOME_ADDRESS_STREET_A},
+ {kHomeCityProperty, PR_HOME_ADDRESS_CITY_A},
+ {kHomeStateProperty, PR_HOME_ADDRESS_STATE_OR_PROVINCE_A},
+ {kHomeZipCodeProperty, PR_HOME_ADDRESS_POSTAL_CODE_A},
+ {kHomeCountryProperty, PR_HOME_ADDRESS_COUNTRY_A},
+ {kWorkAddressProperty, PR_BUSINESS_ADDRESS_STREET_A},
+ {kWorkCityProperty, PR_BUSINESS_ADDRESS_CITY_A},
+ {kWorkStateProperty, PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_A},
+ {kWorkZipCodeProperty, PR_BUSINESS_ADDRESS_POSTAL_CODE_A},
+ {kWorkCountryProperty, PR_BUSINESS_ADDRESS_COUNTRY_A},
+ {kJobTitleProperty, PR_TITLE_A},
+ {kDepartmentProperty, PR_DEPARTMENT_NAME_A},
+ {kCompanyProperty, PR_COMPANY_NAME_A},
+ {kWorkWebPageProperty, PR_BUSINESS_HOME_PAGE_A},
+ {kHomeWebPageProperty, PR_PERSONAL_HOME_PAGE_A},
+ // For the moment, we don't support querying on the birthday
+ // sub-elements.
+#if 0
+ {kBirthYearProperty, PR_BIRTHDAY},
+ {kBirthMonthProperty, PR_BIRTHDAY},
+ {kBirthDayProperty, PR_BIRTHDAY},
+#endif // 0
+ {kNotesProperty, PR_COMMENT_A}
+} ;
+
+static const uint32_t OutlookTableNbProps = sizeof(OutlookTableStringToProp) /
+ sizeof(OutlookTableStringToProp [0]) ;
+
+static ULONG findPropertyTag(const char *aName) {
+ uint32_t i = 0 ;
+
+ for (i = 0 ; i < OutlookTableNbProps ; ++ i) {
+ if (strcmp(aName, OutlookTableStringToProp [i].mOuterName) == 0) {
+ return OutlookTableStringToProp [i].mMapiProp ;
+ }
+ }
+ return 0 ;
+}
+
+static nsresult BuildRestriction(nsIAbBooleanConditionString *aCondition,
+ SRestriction& aRestriction,
+ bool& aSkipItem)
+{
+ if (!aCondition) { return NS_ERROR_NULL_POINTER ; }
+ aSkipItem = false ;
+ nsAbBooleanConditionType conditionType = 0 ;
+ nsresult retCode = NS_OK ;
+ nsCString name;
+ nsString value;
+ ULONG propertyTag = 0 ;
+ nsAutoCString valueAscii ;
+
+ retCode = aCondition->GetCondition(&conditionType) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ retCode = aCondition->GetName(getter_Copies(name)) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ retCode = aCondition->GetValue(getter_Copies(value)) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ LossyCopyUTF16toASCII(value, valueAscii);
+ propertyTag = findPropertyTag(name.get()) ;
+ if (propertyTag == 0) {
+ aSkipItem = true ;
+ return retCode ;
+ }
+ switch (conditionType) {
+ case nsIAbBooleanConditionTypes::Exists :
+ aRestriction.rt = RES_EXIST ;
+ aRestriction.res.resExist.ulPropTag = propertyTag ;
+ break ;
+ case nsIAbBooleanConditionTypes::DoesNotExist :
+ aRestriction.rt = RES_NOT ;
+ aRestriction.res.resNot.lpRes = new SRestriction ;
+ aRestriction.res.resNot.lpRes->rt = RES_EXIST ;
+ aRestriction.res.resNot.lpRes->res.resExist.ulPropTag = propertyTag ;
+ break ;
+ case nsIAbBooleanConditionTypes::Contains :
+ aRestriction.rt = RES_CONTENT ;
+ aRestriction.res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_LOOSE ;
+ aRestriction.res.resContent.ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp = new SPropValue ;
+ aRestriction.res.resContent.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::DoesNotContain :
+ aRestriction.rt = RES_NOT ;
+ aRestriction.res.resNot.lpRes = new SRestriction ;
+ aRestriction.res.resNot.lpRes->rt = RES_CONTENT ;
+ aRestriction.res.resNot.lpRes->res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_LOOSE ;
+ aRestriction.res.resNot.lpRes->res.resContent.ulPropTag = propertyTag ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp = new SPropValue ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::Is :
+ aRestriction.rt = RES_CONTENT ;
+ aRestriction.res.resContent.ulFuzzyLevel = FL_FULLSTRING | FL_LOOSE ;
+ aRestriction.res.resContent.ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp = new SPropValue ;
+ aRestriction.res.resContent.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::IsNot :
+ aRestriction.rt = RES_NOT ;
+ aRestriction.res.resNot.lpRes = new SRestriction ;
+ aRestriction.res.resNot.lpRes->rt = RES_CONTENT ;
+ aRestriction.res.resNot.lpRes->res.resContent.ulFuzzyLevel = FL_FULLSTRING | FL_LOOSE ;
+ aRestriction.res.resNot.lpRes->res.resContent.ulPropTag = propertyTag ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp = new SPropValue ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resNot.lpRes->res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::BeginsWith :
+ aRestriction.rt = RES_CONTENT ;
+ aRestriction.res.resContent.ulFuzzyLevel = FL_PREFIX | FL_LOOSE ;
+ aRestriction.res.resContent.ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp = new SPropValue ;
+ aRestriction.res.resContent.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::EndsWith :
+ // This condition should be implemented through regular expressions,
+ // but MAPI doesn't match them correctly.
+#if 0
+ aRestriction.rt = RES_PROPERTY ;
+ aRestriction.res.resProperty.relop = RELOP_RE ;
+ aRestriction.res.resProperty.ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp = new SPropValue ;
+ aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+#else
+ aSkipItem = true ;
+#endif // 0
+ break ;
+ case nsIAbBooleanConditionTypes::SoundsLike :
+ // This condition cannot be implemented in MAPI.
+ aSkipItem = true ;
+ break ;
+ case nsIAbBooleanConditionTypes::RegExp :
+ // This condition should be implemented this way, but the following
+ // code will never match (through MAPI's fault).
+#if 0
+ aRestriction.rt = RES_PROPERTY ;
+ aRestriction.res.resProperty.relop = RELOP_RE ;
+ aRestriction.res.resProperty.ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp = new SPropValue ;
+ aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+#else
+ aSkipItem = true ;
+#endif // 0
+ break ;
+ case nsIAbBooleanConditionTypes::LessThan :
+ aRestriction.rt = RES_PROPERTY ;
+ aRestriction.res.resProperty.relop = RELOP_LT ;
+ aRestriction.res.resProperty.ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp = new SPropValue ;
+ aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ case nsIAbBooleanConditionTypes::GreaterThan :
+ aRestriction.rt = RES_PROPERTY ;
+ aRestriction.res.resProperty.relop = RELOP_GT ;
+ aRestriction.res.resProperty.ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp = new SPropValue ;
+ aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ;
+ aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ;
+ break ;
+ default :
+ aSkipItem = true ;
+ break ;
+ }
+ return retCode ;
+}
+
+static nsresult BuildRestriction(nsIAbBooleanExpression *aLevel,
+ SRestriction& aRestriction)
+{
+ if (!aLevel) { return NS_ERROR_NULL_POINTER ; }
+ aRestriction.rt = RES_COMMENT ;
+ nsresult retCode = NS_OK ;
+ nsAbBooleanOperationType operationType = 0 ;
+ uint32_t nbExpressions = 0 ;
+ nsCOMPtr<nsIArray> expressions;
+
+ retCode = aLevel->GetOperation(&operationType);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ retCode = aLevel->GetExpressions(getter_AddRefs(expressions));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ retCode = expressions->GetLength(&nbExpressions);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ if (nbExpressions == 0) {
+ PRINTF(("Error, no expressions.\n")) ;
+ return NS_OK ;
+ }
+ if (operationType == nsIAbBooleanOperationTypes::NOT && nbExpressions != 1) {
+ PRINTF(("Error, unary operation NOT with multiple operands.\n")) ;
+ return NS_OK ;
+ }
+ LPSRestriction restrictionArray = new SRestriction [nbExpressions] ;
+ uint32_t realNbExpressions = 0 ;
+ bool skipItem = false ;
+ uint32_t i = 0 ;
+
+ nsCOMPtr<nsIAbBooleanConditionString> condition;
+ nsCOMPtr<nsIAbBooleanExpression> subExpression;
+
+ for (i = 0; i < nbExpressions; ++i) {
+ condition = do_QueryElementAt(expressions, i, &retCode);
+
+ if (NS_SUCCEEDED(retCode)) {
+ retCode = BuildRestriction(condition, *restrictionArray, skipItem);
+ if (NS_SUCCEEDED(retCode)) {
+ if (!skipItem) {
+ ++restrictionArray;
+ ++realNbExpressions;
+ }
+ }
+ else
+ PRINTF(("Cannot build restriction for item %d %08x.\n", i, retCode));
+ }
+ else {
+ subExpression = do_QueryElementAt(expressions, i, &retCode);
+
+ if (NS_SUCCEEDED(retCode)) {
+ retCode = BuildRestriction(subExpression, *restrictionArray);
+ if (NS_SUCCEEDED(retCode)) {
+ if (restrictionArray->rt != RES_COMMENT) {
+ ++restrictionArray;
+ ++realNbExpressions;
+ }
+ }
+ }
+ else
+ PRINTF(("Cannot get interface for item %d %08x.\n", i, retCode));
+ }
+ }
+
+ restrictionArray -= realNbExpressions ;
+ if (realNbExpressions > 1) {
+ if (operationType == nsIAbBooleanOperationTypes::OR) {
+ aRestriction.rt = RES_OR ;
+ aRestriction.res.resOr.lpRes = restrictionArray ;
+ aRestriction.res.resOr.cRes = realNbExpressions ;
+ }
+ else if (operationType == nsIAbBooleanOperationTypes::AND) {
+ aRestriction.rt = RES_AND ;
+ aRestriction.res.resAnd.lpRes = restrictionArray ;
+ aRestriction.res.resAnd.cRes = realNbExpressions ;
+ }
+ else {
+ PRINTF(("Unsupported operation %d.\n", operationType)) ;
+ }
+ }
+ else if (realNbExpressions == 1) {
+ if (operationType == nsIAbBooleanOperationTypes::NOT) {
+ aRestriction.rt = RES_NOT ;
+ // This copy is to ensure that every NOT restriction is being
+ // allocated by new and not new[] (see destruction of restriction)
+ aRestriction.res.resNot.lpRes = new SRestriction ;
+ memcpy(aRestriction.res.resNot.lpRes, restrictionArray, sizeof(SRestriction)) ;
+ }
+ else {
+ // Case where the restriction array is redundant,
+ // we need to fill the restriction directly.
+ memcpy(&aRestriction, restrictionArray, sizeof(SRestriction)) ;
+ }
+ delete [] restrictionArray ;
+ }
+ if (aRestriction.rt == RES_COMMENT) {
+ // This means we haven't really built any useful expression
+ delete [] restrictionArray ;
+ }
+ return NS_OK ;
+}
+
+static nsresult BuildRestriction(nsIAbDirectoryQueryArguments *aArguments,
+ SRestriction& aRestriction)
+{
+ if (!aArguments) { return NS_ERROR_NULL_POINTER ; }
+ nsresult retCode = NS_OK ;
+
+ nsCOMPtr<nsISupports> supports ;
+ retCode = aArguments->GetExpression(getter_AddRefs(supports)) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ nsCOMPtr<nsIAbBooleanExpression> booleanQuery =
+ do_QueryInterface(supports, &retCode) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ retCode = BuildRestriction(booleanQuery, aRestriction) ;
+ return retCode ;
+}
+
+static void DestroyRestriction(SRestriction& aRestriction)
+{
+ switch(aRestriction.rt) {
+ case RES_AND :
+ case RES_OR :
+ {
+ for (ULONG i = 0 ; i < aRestriction.res.resAnd.cRes ; ++ i) {
+ DestroyRestriction(aRestriction.res.resAnd.lpRes [i]) ;
+ }
+ delete [] aRestriction.res.resAnd.lpRes ;
+ }
+ break ;
+ case RES_COMMENT :
+ break ;
+ case RES_CONTENT :
+ if (PROP_TYPE(aRestriction.res.resContent.ulPropTag) == PT_UNICODE) {
+ NS_Free(aRestriction.res.resContent.lpProp->Value.lpszW) ;
+ }
+ else if (PROP_TYPE(aRestriction.res.resContent.ulPropTag) == PT_STRING8) {
+ NS_Free(aRestriction.res.resContent.lpProp->Value.lpszA) ;
+ }
+ delete aRestriction.res.resContent.lpProp ;
+ break ;
+ case RES_EXIST :
+ break ;
+ case RES_NOT :
+ DestroyRestriction(*aRestriction.res.resNot.lpRes) ;
+ delete aRestriction.res.resNot.lpRes ;
+ break ;
+ case RES_BITMASK :
+ case RES_COMPAREPROPS :
+ break ;
+ case RES_PROPERTY :
+ if (PROP_TYPE(aRestriction.res.resProperty.ulPropTag) == PT_UNICODE) {
+ NS_Free(aRestriction.res.resProperty.lpProp->Value.lpszW) ;
+ }
+ else if (PROP_TYPE(aRestriction.res.resProperty.ulPropTag) == PT_STRING8) {
+ NS_Free(aRestriction.res.resProperty.lpProp->Value.lpszA) ;
+ }
+ delete aRestriction.res.resProperty.lpProp ;
+ case RES_SIZE :
+ case RES_SUBRESTRICTION :
+ break ;
+ }
+}
+
+struct QueryThreadArgs
+{
+ nsAbOutlookDirectory *mThis ;
+ SRestriction mRestriction ;
+ nsCOMPtr<nsIAbDirSearchListener> mListener ;
+ int32_t mResultLimit ;
+ int32_t mTimeout ;
+ int32_t mThreadId ;
+} ;
+
+static void QueryThreadFunc(void *aArguments)
+{
+ QueryThreadArgs *arguments = reinterpret_cast<QueryThreadArgs *>(aArguments) ;
+
+ if (!aArguments) { return ; }
+ arguments->mThis->ExecuteQuery(arguments->mRestriction, arguments->mListener,
+ arguments->mResultLimit, arguments->mTimeout,
+ arguments->mThreadId) ;
+ DestroyRestriction(arguments->mRestriction) ;
+ delete arguments ;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DoQuery(nsIAbDirectory *aDirectory,
+ nsIAbDirectoryQueryArguments *aArguments,
+ nsIAbDirSearchListener *aListener,
+ int32_t aResultLimit, int32_t aTimeout,
+ int32_t *aReturnValue)
+{
+ if (!aArguments || !aListener || !aReturnValue) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aReturnValue = -1;
+
+ QueryThreadArgs *threadArgs = new QueryThreadArgs;
+ PRThread *newThread = nullptr;
+
+ if (!threadArgs)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = BuildRestriction(aArguments, threadArgs->mRestriction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ threadArgs->mThis = this;
+ threadArgs->mListener = aListener;
+ threadArgs->mResultLimit = aResultLimit;
+ threadArgs->mTimeout = aTimeout;
+
+ PR_Lock(mProtector);
+ *aReturnValue = ++mCurrentQueryId;
+ PR_Unlock(mProtector);
+
+ threadArgs->mThreadId = *aReturnValue;
+ newThread = PR_CreateThread(PR_USER_THREAD,
+ QueryThreadFunc,
+ threadArgs,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD,
+ 0);
+
+ if (!newThread ) {
+ DestroyRestriction(threadArgs->mRestriction);
+ delete threadArgs;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mQueryThreads.Put(*aReturnValue, newThread);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::StopQuery(int32_t aContext)
+{
+ PRThread *queryThread;
+ if (mQueryThreads.Get(aContext, &queryThread)) {
+ PR_Interrupt(queryThread);
+ mQueryThreads.Remove(aContext);
+ }
+ return NS_OK;
+}
+
+// nsIAbDirectorySearch methods
+NS_IMETHODIMP nsAbOutlookDirectory::StartSearch(void)
+{
+ if (!mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; }
+ nsresult retCode = NS_OK ;
+
+ retCode = StopSearch() ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ mCardList.Clear();
+
+ nsCOMPtr<nsIAbBooleanExpression> expression ;
+
+ nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID,&retCode);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ retCode = nsAbQueryStringToExpression::Convert(mQueryString, getter_AddRefs(expression)) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ retCode = arguments->SetExpression(expression) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+
+ retCode = arguments->SetQuerySubDirectories(true) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+
+ return DoQuery(this, arguments, this, -1, 0, &mSearchContext);
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::StopSearch(void)
+{
+ if (!mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; }
+ return StopQuery(mSearchContext) ;
+}
+
+// nsIAbDirSearchListener
+NS_IMETHODIMP nsAbOutlookDirectory::OnSearchFinished(int32_t aResult,
+ const nsAString &aErrorMsg)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::OnSearchFoundCard(nsIAbCard *aCard)
+{
+ mCardList.Put(aCard, aCard);
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ rv = abManager->NotifyDirectoryItemAdded(this, aCard);
+
+ return rv;
+}
+
+nsresult nsAbOutlookDirectory::ExecuteQuery(SRestriction &aRestriction,
+ nsIAbDirSearchListener *aListener,
+ int32_t aResultLimit, int32_t aTimeout,
+ int32_t aThreadId)
+
+{
+ if (!aListener)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult retCode = NS_OK;
+
+ nsCOMPtr<nsIMutableArray> resultsArray(do_CreateInstance(NS_ARRAY_CONTRACTID,
+ &retCode));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ retCode = GetChildCards(resultsArray,
+ aRestriction.rt == RES_COMMENT ? nullptr : &aRestriction);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ uint32_t nbResults = 0;
+ retCode = resultsArray->GetLength(&nbResults);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ if (aResultLimit > 0 && nbResults > static_cast<uint32_t>(aResultLimit)) {
+ nbResults = static_cast<uint32_t>(aResultLimit) ;
+ }
+
+ uint32_t i = 0;
+ nsCOMPtr<nsIAbCard> card;
+
+ for (i = 0 ; i < nbResults ; ++ i) {
+ card = do_QueryElementAt(resultsArray, i, &retCode);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ aListener->OnSearchFoundCard(card);
+ }
+
+ mQueryThreads.Remove(aThreadId);
+
+ aListener->OnSearchFinished(nsIAbDirectoryQueryResultListener::queryResultComplete,
+ EmptyString());
+ return retCode;
+}
+
+// This function expects the aCards array to already be created.
+nsresult nsAbOutlookDirectory::GetChildCards(nsIMutableArray *aCards,
+ void *aRestriction)
+{
+ nsAbWinHelperGuard mapiAddBook(mAbWinType);
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ nsMapiEntryArray cardEntries;
+ LPSRestriction restriction = (LPSRestriction) aRestriction;
+
+ if (!mapiAddBook->GetCards(*mMapiData, restriction, cardEntries)) {
+ PRINTF(("Cannot get cards.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+
+ nsAutoCString entryId;
+ nsAutoCString uriName;
+ nsCOMPtr<nsIAbCard> childCard;
+ nsresult rv;
+
+ for (ULONG card = 0; card < cardEntries.mNbEntries; ++card) {
+ cardEntries.mEntries[card].ToString(entryId);
+ buildAbWinUri(kOutlookCardScheme, mAbWinType, uriName);
+ uriName.Append(entryId);
+
+ rv = OutlookCardForURI(uriName, getter_AddRefs(childCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ childCard->SetDirectoryId(ourUuid);
+
+ aCards->AppendElement(childCard, false);
+ }
+ return rv;
+}
+
+nsresult nsAbOutlookDirectory::GetChildNodes(nsIMutableArray* aNodes)
+{
+ NS_ENSURE_ARG_POINTER(aNodes);
+
+ aNodes->Clear();
+
+ nsAbWinHelperGuard mapiAddBook(mAbWinType);
+ nsMapiEntryArray nodeEntries;
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ if (!mapiAddBook->GetNodes(*mMapiData, nodeEntries))
+ {
+ PRINTF(("Cannot get nodes.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString entryId;
+ nsAutoCString uriName;
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (ULONG node = 0; node < nodeEntries.mNbEntries; ++node)
+ {
+ nodeEntries.mEntries[node].ToString(entryId);
+ buildAbWinUri(kOutlookDirectoryScheme, mAbWinType, uriName);
+ uriName.Append(entryId);
+
+ nsCOMPtr <nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(uriName, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aNodes->AppendElement(directory, false);
+ }
+ return rv;
+}
+
+nsresult nsAbOutlookDirectory::NotifyItemDeletion(nsISupports *aItem)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv))
+ rv = abManager->NotifyDirectoryItemDeleted(this, aItem);
+
+ return rv;
+}
+
+nsresult nsAbOutlookDirectory::NotifyItemAddition(nsISupports *aItem)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = abManager->NotifyDirectoryItemAdded(this, aItem);
+
+ return rv;
+}
+
+// This is called from EditMailListToDatabase.
+// We got m_AddressList containing the list of cards the mailing
+// list is supposed to contain at the end.
+nsresult nsAbOutlookDirectory::CommitAddressList(void)
+{
+ if (!m_IsMailList) {
+ PRINTF(("We are not in a mailing list, no commit can be done.\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ uint32_t i = 0;
+ nsCOMPtr<nsIMutableArray> oldList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetChildCards(oldList, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_AddressList)
+ return NS_ERROR_NULL_POINTER;
+
+ uint32_t nbCards = 0;
+ rv = m_AddressList->GetLength(&nbCards);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> element;
+ nsCOMPtr<nsIAbCard> newCard;
+ uint32_t pos;
+
+ for (i = 0; i < nbCards; ++i) {
+ element = do_QueryElementAt(m_AddressList, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_SUCCEEDED(oldList->IndexOf(0, element, &pos))) {
+ rv = oldList->RemoveElementAt(pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The entry was not already there
+ nsCOMPtr<nsIAbCard> card(do_QueryInterface(element, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CreateCard(card, getter_AddRefs(newCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_AddressList->ReplaceElementAt(newCard, i, false);
+ }
+ }
+ return DeleteCards(oldList);
+}
+
+nsresult nsAbOutlookDirectory::UpdateAddressList(void)
+{
+ if (!m_AddressList)
+ {
+ nsresult rv;
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return m_IsMailList ? GetChildCards(m_AddressList, nullptr) :
+ GetChildNodes(m_AddressList);
+}
+
+nsresult nsAbOutlookDirectory::CreateCard(nsIAbCard *aData, nsIAbCard **aNewCard)
+{
+ if (!aData || !aNewCard) { return NS_ERROR_NULL_POINTER ; }
+ *aNewCard = nullptr ;
+ nsresult retCode = NS_OK ;
+ nsAbWinHelperGuard mapiAddBook (mAbWinType) ;
+ nsMapiEntry newEntry ;
+ nsAutoCString entryString ;
+ bool didCopy = false ;
+
+ if (!mapiAddBook->IsOK()) { return NS_ERROR_FAILURE ; }
+ // If we get an nsIAbCard that maps onto an Outlook card uri
+ // we simply copy the contents of the Outlook card.
+ retCode = ExtractCardEntry(aData, entryString) ;
+ if (NS_SUCCEEDED(retCode) && !entryString.IsEmpty()) {
+ nsMapiEntry sourceEntry ;
+
+
+ sourceEntry.Assign(entryString) ;
+ if (m_IsMailList) {
+ // In the case of a mailing list, we can use the address
+ // as a direct template to build the new one (which is done
+ // by CopyEntry).
+ mapiAddBook->CopyEntry(*mMapiData, sourceEntry, newEntry) ;
+ didCopy = true ;
+ }
+ else {
+ // Else, we have to create a temporary address and copy the
+ // source into it. Yes it's silly.
+ mapiAddBook->CreateEntry(*mMapiData, newEntry) ;
+ }
+ }
+ // If this approach doesn't work, well we're back to creating and copying.
+ if (newEntry.mByteCount == 0) {
+ // In the case of a mailing list, we cannot directly create a new card,
+ // we have to create a temporary one in a real folder (to be able to use
+ // templates) and then copy it to the mailing list.
+ if (m_IsMailList) {
+ nsMapiEntry parentEntry ;
+ nsMapiEntry temporaryEntry ;
+
+ if (!mapiAddBook->GetDefaultContainer(parentEntry)) {
+ return NS_ERROR_FAILURE ;
+ }
+ if (!mapiAddBook->CreateEntry(parentEntry, temporaryEntry)) {
+ return NS_ERROR_FAILURE ;
+ }
+ if (!mapiAddBook->CopyEntry(*mMapiData, temporaryEntry, newEntry)) {
+ return NS_ERROR_FAILURE ;
+ }
+ if (!mapiAddBook->DeleteEntry(parentEntry, temporaryEntry)) {
+ return NS_ERROR_FAILURE ;
+ }
+ }
+ // If we're on a real address book folder, we can directly create an
+ // empty card.
+ else if (!mapiAddBook->CreateEntry(*mMapiData, newEntry)) {
+ return NS_ERROR_FAILURE ;
+ }
+ }
+ newEntry.ToString(entryString) ;
+ nsAutoCString uri ;
+
+ buildAbWinUri(kOutlookCardScheme, mAbWinType, uri) ;
+ uri.Append(entryString) ;
+
+ nsCOMPtr<nsIAbCard> newCard;
+ retCode = OutlookCardForURI(uri, getter_AddRefs(newCard));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ nsAutoCString ourUuid;
+ GetUuid(ourUuid);
+ newCard->SetDirectoryId(ourUuid);
+
+ if (!didCopy) {
+ retCode = newCard->Copy(aData) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ retCode = ModifyCard(newCard) ;
+ NS_ENSURE_SUCCESS(retCode, retCode) ;
+ }
+ *aNewCard = newCard ;
+ NS_ADDREF(*aNewCard) ;
+ return retCode ;
+}
+
+static void UnicodeToWord(const char16_t *aUnicode, WORD& aWord)
+{
+ aWord = 0 ;
+ if (aUnicode == nullptr || *aUnicode == 0) { return ; }
+ nsresult errorCode = NS_OK;
+ nsAutoString unichar (aUnicode) ;
+
+ aWord = static_cast<WORD>(unichar.ToInteger(&errorCode));
+ if (NS_FAILED(errorCode)) {
+ PRINTF(("Error conversion string %S: %08x.\n", unichar.get(), errorCode)) ;
+ }
+}
+
+#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst"
+
+
+NS_IMETHODIMP nsAbOutlookDirectory::ModifyCard(nsIAbCard *aModifiedCard)
+{
+ NS_ENSURE_ARG_POINTER(aModifiedCard);
+
+ nsString *properties = nullptr;
+ nsAutoString utility;
+ nsAbWinHelperGuard mapiAddBook(mAbWinType);
+
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ nsCString entry;
+ nsresult retCode = ExtractCardEntry(aModifiedCard, entry);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ // If we don't have the card entry, we can't work.
+ if (entry.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ nsMapiEntry mapiData;
+ mapiData.Assign(entry);
+
+ // First, all the standard properties in one go
+ properties = new nsString[index_LastProp];
+ if (!properties) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aModifiedCard->GetFirstName(properties[index_FirstName]);
+ aModifiedCard->GetLastName(properties[index_LastName]);
+ // This triple search for something to put in the name
+ // is because in the case of a mailing list edition in
+ // Mozilla, the display name will not be provided, and
+ // MAPI doesn't allow that, so we fall back on an optional
+ // name, and when all fails, on the email address.
+ aModifiedCard->GetDisplayName(properties[index_DisplayName]);
+ if (properties[index_DisplayName].IsEmpty()) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t format;
+ rv = prefBranch->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &format);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aModifiedCard->GenerateName(format, nullptr,
+ properties[index_DisplayName]);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (properties[index_DisplayName].IsEmpty()) {
+ aModifiedCard->GetPrimaryEmail(properties[index_DisplayName]);
+ }
+ }
+ aModifiedCard->SetDisplayName(properties[index_DisplayName]);
+ aModifiedCard->GetPrimaryEmail(properties[index_EmailAddress]);
+ aModifiedCard->GetPropertyAsAString(kNicknameProperty, properties[index_NickName]);
+ aModifiedCard->GetPropertyAsAString(kWorkPhoneProperty, properties[index_WorkPhoneNumber]);
+ aModifiedCard->GetPropertyAsAString(kHomePhoneProperty, properties[index_HomePhoneNumber]);
+ aModifiedCard->GetPropertyAsAString(kFaxProperty, properties[index_WorkFaxNumber]);
+ aModifiedCard->GetPropertyAsAString(kPagerProperty, properties[index_PagerNumber]);
+ aModifiedCard->GetPropertyAsAString(kCellularProperty, properties[index_MobileNumber]);
+ aModifiedCard->GetPropertyAsAString(kHomeCityProperty, properties[index_HomeCity]);
+ aModifiedCard->GetPropertyAsAString(kHomeStateProperty, properties[index_HomeState]);
+ aModifiedCard->GetPropertyAsAString(kHomeZipCodeProperty, properties[index_HomeZip]);
+ aModifiedCard->GetPropertyAsAString(kHomeCountryProperty, properties[index_HomeCountry]);
+ aModifiedCard->GetPropertyAsAString(kWorkCityProperty, properties[index_WorkCity]);
+ aModifiedCard->GetPropertyAsAString(kWorkStateProperty, properties[index_WorkState]);
+ aModifiedCard->GetPropertyAsAString(kWorkZipCodeProperty, properties[index_WorkZip]);
+ aModifiedCard->GetPropertyAsAString(kWorkCountryProperty, properties[index_WorkCountry]);
+ aModifiedCard->GetPropertyAsAString(kJobTitleProperty, properties[index_JobTitle]);
+ aModifiedCard->GetPropertyAsAString(kDepartmentProperty, properties[index_Department]);
+ aModifiedCard->GetPropertyAsAString(kCompanyProperty, properties[index_Company]);
+ aModifiedCard->GetPropertyAsAString(kWorkWebPageProperty, properties[index_WorkWebPage]);
+ aModifiedCard->GetPropertyAsAString(kHomeWebPageProperty, properties[index_HomeWebPage]);
+ aModifiedCard->GetPropertyAsAString(kNotesProperty, properties[index_Comments]);
+ if (!mapiAddBook->SetPropertiesUString(mapiData, OutlookCardMAPIProps,
+ index_LastProp, properties)) {
+ PRINTF(("Cannot set general properties.\n")) ;
+ }
+
+ delete [] properties;
+ nsString unichar;
+ nsString unichar2;
+ WORD year = 0;
+ WORD month = 0;
+ WORD day = 0;
+
+ aModifiedCard->GetPropertyAsAString(kHomeAddressProperty, unichar);
+ aModifiedCard->GetPropertyAsAString(kHomeAddress2Property, unichar2);
+
+ utility.Assign(unichar.get());
+ if (!utility.IsEmpty())
+ utility.AppendLiteral("\r\n");
+
+ utility.Append(unichar2.get());
+ if (!mapiAddBook->SetPropertyUString(mapiData, PR_HOME_ADDRESS_STREET_W, utility.get())) {
+ PRINTF(("Cannot set home address.\n")) ;
+ }
+
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kWorkAddressProperty, unichar);
+ unichar2.Truncate();
+ aModifiedCard->GetPropertyAsAString(kWorkAddress2Property, unichar2);
+
+ utility.Assign(unichar.get());
+ if (!utility.IsEmpty())
+ utility.AppendLiteral("\r\n");
+
+ utility.Append(unichar2.get());
+ if (!mapiAddBook->SetPropertyUString(mapiData, PR_BUSINESS_ADDRESS_STREET_W, utility.get())) {
+ PRINTF(("Cannot set work address.\n")) ;
+ }
+
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kBirthYearProperty, unichar);
+ UnicodeToWord(unichar.get(), year);
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kBirthMonthProperty, unichar);
+ UnicodeToWord(unichar.get(), month);
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kBirthDayProperty, unichar);
+ UnicodeToWord(unichar.get(), day);
+ if (!mapiAddBook->SetPropertyDate(mapiData, PR_BIRTHDAY, year, month, day)) {
+ PRINTF(("Cannot set date.\n")) ;
+ }
+
+ return retCode;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::OnQueryFoundCard(nsIAbCard *aCard)
+{
+ return OnSearchFoundCard(aCard);
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::OnQueryResult(int32_t aResult,
+ int32_t aErrorCode)
+{
+ return OnSearchFinished(aResult, EmptyString());
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ return NS_OK;
+}
+
+static void splitString(nsString& aSource, nsString& aTarget)
+{
+ aTarget.Truncate();
+ int32_t offset = aSource.FindChar('\n');
+
+ if (offset >= 0)
+ {
+ const char16_t *source = aSource.get() + offset + 1;
+ while (*source)
+ {
+ if (*source == '\n' || *source == '\r')
+ aTarget.Append(char16_t(' '));
+ else
+ aTarget.Append(*source);
+ ++source;
+ }
+ aSource.SetLength(offset);
+ }
+}
+
+nsresult OutlookCardForURI(const nsACString &aUri, nsIAbCard **newCard)
+{
+ NS_ENSURE_ARG_POINTER(newCard);
+
+ nsAutoCString entry;
+ nsAutoCString stub;
+ uint32_t abWinType = getAbWinType(kOutlookCardScheme,
+ PromiseFlatCString(aUri).get(), stub, entry);
+ if (abWinType == nsAbWinType_Unknown)
+ {
+ PRINTF(("Huge problem URI=%s.\n", PromiseFlatCString(aUri).get()));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAbWinHelperGuard mapiAddBook(abWinType);
+ if (!mapiAddBook->IsOK())
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbCard> card = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ card->SetPropertyAsAUTF8String("OutlookEntryURI", aUri);
+ card->SetLocalId(aUri);
+
+ nsMapiEntry mapiData;
+ mapiData.Assign(entry);
+
+ nsString unichars[index_LastProp];
+
+ if (mapiAddBook->GetPropertiesUString(mapiData, OutlookCardMAPIProps,
+ index_LastProp, unichars))
+ {
+ card->SetFirstName(unichars[index_FirstName]);
+ card->SetLastName(unichars[index_LastName]);
+ card->SetDisplayName(unichars[index_DisplayName]);
+ card->SetPrimaryEmail(unichars[index_EmailAddress]);
+ card->SetPropertyAsAString(kNicknameProperty, unichars[index_NickName]);
+ card->SetPropertyAsAString(kWorkPhoneProperty, unichars[index_WorkPhoneNumber]);
+ card->SetPropertyAsAString(kHomePhoneProperty, unichars[index_HomePhoneNumber]);
+ card->SetPropertyAsAString(kFaxProperty, unichars[index_WorkFaxNumber]);
+ card->SetPropertyAsAString(kPagerProperty, unichars[index_PagerNumber]);
+ card->SetPropertyAsAString(kCellularProperty, unichars[index_MobileNumber]);
+ card->SetPropertyAsAString(kHomeCityProperty, unichars[index_HomeCity]);
+ card->SetPropertyAsAString(kHomeStateProperty, unichars[index_HomeState]);
+ card->SetPropertyAsAString(kHomeZipCodeProperty, unichars[index_HomeZip]);
+ card->SetPropertyAsAString(kHomeCountryProperty, unichars[index_HomeCountry]);
+ card->SetPropertyAsAString(kWorkCityProperty, unichars[index_WorkCity]);
+ card->SetPropertyAsAString(kWorkStateProperty, unichars[index_WorkState]);
+ card->SetPropertyAsAString(kWorkZipCodeProperty, unichars[index_WorkZip]);
+ card->SetPropertyAsAString(kWorkCountryProperty, unichars[index_WorkCountry]);
+ card->SetPropertyAsAString(kJobTitleProperty, unichars[index_JobTitle]);
+ card->SetPropertyAsAString(kDepartmentProperty, unichars[index_Department]);
+ card->SetPropertyAsAString(kCompanyProperty, unichars[index_Company]);
+ card->SetPropertyAsAString(kWorkWebPageProperty, unichars[index_WorkWebPage]);
+ card->SetPropertyAsAString(kHomeWebPageProperty, unichars[index_HomeWebPage]);
+ card->SetPropertyAsAString(kNotesProperty, unichars[index_Comments]);
+ }
+
+ ULONG cardType = 0;
+ if (mapiAddBook->GetPropertyLong(mapiData, PR_OBJECT_TYPE, cardType))
+ {
+ card->SetIsMailList(cardType == MAPI_DISTLIST);
+ if (cardType == MAPI_DISTLIST)
+ {
+ nsAutoCString normalChars;
+ buildAbWinUri(kOutlookDirectoryScheme, abWinType, normalChars);
+ normalChars.Append(entry);
+ card->SetMailListURI(normalChars.get());
+ }
+ }
+
+ nsAutoString unichar;
+ nsAutoString unicharBis;
+ if (mapiAddBook->GetPropertyUString(mapiData, PR_HOME_ADDRESS_STREET_W, unichar))
+ {
+ splitString(unichar, unicharBis);
+ card->SetPropertyAsAString(kHomeAddressProperty, unichar);
+ card->SetPropertyAsAString(kHomeAddress2Property, unicharBis);
+ }
+ if (mapiAddBook->GetPropertyUString(mapiData, PR_BUSINESS_ADDRESS_STREET_W,
+ unichar))
+ {
+ splitString(unichar, unicharBis);
+ card->SetPropertyAsAString(kWorkAddressProperty, unichar);
+ card->SetPropertyAsAString(kWorkAddress2Property, unicharBis);
+ }
+
+ WORD year = 0, month = 0, day = 0;
+ if (mapiAddBook->GetPropertyDate(mapiData, PR_BIRTHDAY, year, month, day))
+ {
+ card->SetPropertyAsUint32(kBirthYearProperty, year);
+ card->SetPropertyAsUint32(kBirthMonthProperty, month);
+ card->SetPropertyAsUint32(kBirthDayProperty, day);
+ }
+
+ card.swap(*newCard);
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbOutlookDirectory.h b/mailnews/addrbook/src/nsAbOutlookDirectory.h
new file mode 100644
index 000000000..116966ce3
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbOutlookDirectory.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsAbOutlookDirectory_h___
+#define nsAbOutlookDirectory_h___
+
+#include "mozilla/Attributes.h"
+#include "nsAbDirProperty.h"
+#include "nsIAbDirectoryQuery.h"
+#include "nsIAbDirectorySearch.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsDataHashtable.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIMutableArray.h"
+#include "nsAbWinHelper.h"
+
+struct nsMapiEntry ;
+
+class nsAbOutlookDirectory : public nsAbDirProperty, // nsIAbDirectory
+ public nsIAbDirectoryQuery,
+ public nsIAbDirectorySearch,
+ public nsIAbDirSearchListener,
+ public nsIAbDirectoryQueryResultListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIABDIRSEARCHLISTENER
+ NS_DECL_NSIABDIRECTORYQUERYRESULTLISTENER
+
+ nsAbOutlookDirectory(void);
+
+ // nsAbDirProperty methods
+ NS_IMETHOD GetDirType(int32_t *aDirType) override;
+ NS_IMETHOD GetURI(nsACString &aURI) override;
+ NS_IMETHOD GetChildCards(nsISimpleEnumerator **aCards) override;
+ NS_IMETHOD GetChildNodes(nsISimpleEnumerator **aNodes) override;
+ NS_IMETHOD GetIsQuery(bool *aResult) override;
+ NS_IMETHOD HasCard(nsIAbCard *aCard, bool *aHasCard) override;
+ NS_IMETHOD HasDirectory(nsIAbDirectory *aDirectory, bool *aHasDirectory) override;
+ NS_IMETHOD DeleteCards(nsIArray *aCardList) override;
+ NS_IMETHOD DeleteDirectory(nsIAbDirectory *aDirectory) override;
+ NS_IMETHOD UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult) override;
+ NS_IMETHOD AddCard(nsIAbCard *aData, nsIAbCard **addedCard) override;
+ NS_IMETHOD ModifyCard(nsIAbCard *aModifiedCard) override;
+ NS_IMETHOD DropCard(nsIAbCard *aData, bool needToCopyCard) override;
+ NS_IMETHOD AddMailList(nsIAbDirectory *aMailList, nsIAbDirectory **addedList) override;
+ NS_IMETHOD EditMailListToDatabase(nsIAbCard *listCard) override;
+
+ // nsAbDirProperty method
+ NS_IMETHOD Init(const char *aUri) override;
+ // nsIAbDirectoryQuery methods
+ NS_DECL_NSIABDIRECTORYQUERY
+ // nsIAbDirectorySearch methods
+ NS_DECL_NSIABDIRECTORYSEARCH
+ // Perform a MAPI query (function executed in a separate thread)
+ nsresult ExecuteQuery(SRestriction &aRestriction,
+ nsIAbDirSearchListener *aListener,
+ int32_t aResultLimit, int32_t aTimeout,
+ int32_t aThreadId);
+
+protected:
+ // Retrieve hierarchy as cards, with an optional restriction
+ nsresult GetChildCards(nsIMutableArray *aCards, void *aRestriction);
+ // Retrieve hierarchy as directories
+ nsresult GetChildNodes(nsIMutableArray *aNodes);
+ // Create a new card
+ nsresult CreateCard(nsIAbCard *aData, nsIAbCard **aNewCard);
+ // Notification for the UI
+ nsresult NotifyItemDeletion(nsISupports *aItem);
+ nsresult NotifyItemAddition(nsISupports *aItem);
+ // Force update of MAPI repository for mailing list
+ nsresult CommitAddressList(void);
+ // Read MAPI repository
+ nsresult UpdateAddressList(void);
+
+ nsMapiEntry *mMapiData;
+ // Container for the query threads
+ nsDataHashtable<nsUint32HashKey, PRThread*> mQueryThreads;
+ int32_t mCurrentQueryId;
+ PRLock *mProtector;
+ // Data for the search interfaces
+ nsInterfaceHashtable<nsISupportsHashKey, nsIAbCard> mCardList;
+ int32_t mSearchContext;
+ // Windows AB type
+ uint32_t mAbWinType;
+
+private:
+ virtual ~nsAbOutlookDirectory(void);
+
+};
+
+enum
+{
+ index_DisplayName = 0,
+ index_EmailAddress,
+ index_FirstName,
+ index_LastName,
+ index_NickName,
+ index_WorkPhoneNumber,
+ index_HomePhoneNumber,
+ index_WorkFaxNumber,
+ index_PagerNumber,
+ index_MobileNumber,
+ index_HomeCity,
+ index_HomeState,
+ index_HomeZip,
+ index_HomeCountry,
+ index_WorkCity,
+ index_WorkState,
+ index_WorkZip,
+ index_WorkCountry,
+ index_JobTitle,
+ index_Department,
+ index_Company,
+ index_WorkWebPage,
+ index_HomeWebPage,
+ index_Comments,
+ index_LastProp
+};
+
+static const ULONG OutlookCardMAPIProps[] =
+{
+ PR_DISPLAY_NAME_W,
+ PR_EMAIL_ADDRESS_W,
+ PR_GIVEN_NAME_W,
+ PR_SURNAME_W,
+ PR_NICKNAME_W,
+ PR_BUSINESS_TELEPHONE_NUMBER_W,
+ PR_HOME_TELEPHONE_NUMBER_W,
+ PR_BUSINESS_FAX_NUMBER_W,
+ PR_PAGER_TELEPHONE_NUMBER_W,
+ PR_MOBILE_TELEPHONE_NUMBER_W,
+ PR_HOME_ADDRESS_CITY_W,
+ PR_HOME_ADDRESS_STATE_OR_PROVINCE_W,
+ PR_HOME_ADDRESS_POSTAL_CODE_W,
+ PR_HOME_ADDRESS_COUNTRY_W,
+ PR_BUSINESS_ADDRESS_CITY_W,
+ PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W,
+ PR_BUSINESS_ADDRESS_POSTAL_CODE_W,
+ PR_BUSINESS_ADDRESS_COUNTRY_W,
+ PR_TITLE_W,
+ PR_DEPARTMENT_NAME_W,
+ PR_COMPANY_NAME_W,
+ PR_BUSINESS_HOME_PAGE_W,
+ PR_PERSONAL_HOME_PAGE_W,
+ PR_COMMENT_W
+};
+
+nsresult OutlookCardForURI(const nsACString &aUri, nsIAbCard **card);
+
+#endif // nsAbOutlookDirectory_h___
diff --git a/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp b/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp
new file mode 100644
index 000000000..fe1f22e00
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp
@@ -0,0 +1,337 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbQueryStringToExpression.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsITextToSubURI.h"
+#include "nsAbBooleanExpression.h"
+#include "nsAbBaseCID.h"
+#include "plstr.h"
+#include "nsIMutableArray.h"
+
+/**
+ * This code parses the query expression passed in as an addressbook URI.
+ * The expression takes the form:
+ * (BOOL1(FIELD1,OP1,VALUE1)..(FIELDn,OPn,VALUEn)(BOOL2(FIELD1,OP1,VALUE1)...)...)
+ *
+ * BOOLn A boolean operator joining subsequent terms delimited by ().
+ * For possible values see CreateBooleanExpression().
+ * FIELDn An addressbook card data field.
+ * OPn An operator for the search term.
+ * For possible values see CreateBooleanConditionString().
+ * VALUEn The value to be matched in the FIELDn via the OPn operator.
+ * The value must be URL encoded by the caller, if it contains any special
+ * characters including '(' and ')'.
+ */
+nsresult nsAbQueryStringToExpression::Convert (
+ const nsACString &aQueryString,
+ nsIAbBooleanExpression** expression)
+{
+ nsresult rv;
+
+ nsAutoCString q(aQueryString);
+ q.StripWhitespace();
+ const char *queryChars = q.get();
+
+ nsCOMPtr<nsISupports> s;
+ rv = ParseExpression(&queryChars, getter_AddRefs(s));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Case: Not end of string
+ if (*queryChars != 0)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAbBooleanExpression> e(do_QueryInterface(s, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*expression = e);
+ return rv;
+}
+
+nsresult nsAbQueryStringToExpression::ParseExpression (
+ const char** index,
+ nsISupports** expression)
+{
+ nsresult rv;
+
+ if (**index != '(')
+ return NS_ERROR_FAILURE;
+
+ const char* indexBracket = *index + 1;
+ while (*indexBracket &&
+ *indexBracket != '(' && *indexBracket != ')')
+ indexBracket++;
+
+ // Case: End of string
+ if (*indexBracket == 0)
+ return NS_ERROR_FAILURE;
+
+ // Case: "((" or "()"
+ if (indexBracket == *index + 1)
+ {
+ return NS_ERROR_FAILURE;
+ }
+ // Case: "(*("
+ else if (*indexBracket == '(')
+ {
+ // printf ("Case: (*(: %s\n", *index);
+
+ nsCString operation;
+ rv = ParseOperationEntry (
+ *index, indexBracket,
+ getter_Copies (operation));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> e;
+ rv = CreateBooleanExpression(operation.get(),
+ getter_AddRefs(e));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Case: "(*)(*)....(*))"
+ *index = indexBracket;
+ rv = ParseExpressions (index, e);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*expression = e);
+ }
+ // Case" "(*)"
+ else if (*indexBracket == ')')
+ {
+ // printf ("Case: (*): %s\n", *index);
+
+ nsCOMPtr<nsIAbBooleanConditionString> conditionString;
+ rv = ParseCondition (index, indexBracket,
+ getter_AddRefs(conditionString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*expression = conditionString);
+ }
+
+ if (**index != ')')
+ return NS_ERROR_FAILURE;
+
+ (*index)++;
+
+ return NS_OK;
+}
+
+
+nsresult nsAbQueryStringToExpression::ParseExpressions (
+ const char** index,
+ nsIAbBooleanExpression* expression)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> expressions(do_CreateInstance(NS_ARRAY_CONTRACTID,
+ &rv));
+ if (NS_FAILED(rv))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Case: ")(*)(*)....(*))"
+ // printf ("Case: )(*)(*)....(*)): %s\n", *index);
+ while (**index == '(')
+ {
+ nsCOMPtr<nsISupports> childExpression;
+ rv = ParseExpression(index, getter_AddRefs (childExpression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ expressions->AppendElement(childExpression, false);
+ }
+
+ if (**index == 0)
+ return NS_ERROR_FAILURE;
+
+ // Case: "))"
+ // printf ("Case: )): %s\n", *index);
+
+ if (**index != ')')
+ return NS_ERROR_FAILURE;
+
+ expression->SetExpressions (expressions);
+
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::ParseCondition (
+ const char** index,
+ const char* indexBracketClose,
+ nsIAbBooleanConditionString** conditionString)
+{
+ nsresult rv;
+
+ (*index)++;
+
+ nsCString entries[3];
+ for (int i = 0; i < 3; i++)
+ {
+ rv = ParseConditionEntry (index, indexBracketClose,
+ getter_Copies (entries[i]));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*index == indexBracketClose)
+ break;
+ }
+
+ if (*index != indexBracketClose)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAbBooleanConditionString> c;
+ rv = CreateBooleanConditionString (
+ entries[0].get(),
+ entries[1].get(),
+ entries[2].get(),
+ getter_AddRefs (c));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*conditionString = c);
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::ParseConditionEntry (
+ const char** index,
+ const char* indexBracketClose,
+ char** entry)
+{
+ const char* indexDeliminator = *index;
+ while (indexDeliminator != indexBracketClose &&
+ *indexDeliminator != ',')
+ indexDeliminator++;
+
+ int entryLength = indexDeliminator - *index;
+ if (entryLength)
+ *entry = PL_strndup (*index, entryLength);
+ else
+ *entry = 0;
+
+ if (indexDeliminator != indexBracketClose)
+ *index = indexDeliminator + 1;
+ else
+ *index = indexDeliminator;
+
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::ParseOperationEntry (
+ const char* indexBracketOpen1,
+ const char* indexBracketOpen2,
+ char** operation)
+{
+ int operationLength = indexBracketOpen2 - indexBracketOpen1 - 1;
+ if (operationLength)
+ *operation = PL_strndup (indexBracketOpen1 + 1,
+ operationLength);
+ else
+ *operation = 0;
+
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::CreateBooleanExpression(
+ const char* operation,
+ nsIAbBooleanExpression** expression)
+{
+ nsAbBooleanOperationType op;
+ if (PL_strcasecmp (operation, "and") == 0)
+ op = nsIAbBooleanOperationTypes::AND;
+ else if (PL_strcasecmp (operation, "or") == 0)
+ op = nsIAbBooleanOperationTypes::OR;
+ else if (PL_strcasecmp (operation, "not") == 0)
+ op = nsIAbBooleanOperationTypes::NOT;
+ else
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+
+ nsCOMPtr <nsIAbBooleanExpression> expr = do_CreateInstance(NS_BOOLEANEXPRESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*expression = expr);
+
+ rv = expr->SetOperation (op);
+ return rv;
+}
+
+nsresult nsAbQueryStringToExpression::CreateBooleanConditionString (
+ const char* attribute,
+ const char* condition,
+ const char* value,
+ nsIAbBooleanConditionString** conditionString)
+{
+ if (attribute == 0 || condition == 0 || value == 0)
+ return NS_ERROR_FAILURE;
+
+ nsAbBooleanConditionType c;
+
+ if (PL_strcasecmp (condition, "=") == 0)
+ c = nsIAbBooleanConditionTypes::Is;
+ else if (PL_strcasecmp (condition, "!=") == 0)
+ c = nsIAbBooleanConditionTypes::IsNot;
+ else if (PL_strcasecmp (condition, "lt") == 0)
+ c = nsIAbBooleanConditionTypes::LessThan;
+ else if (PL_strcasecmp (condition, "gt") == 0)
+ c = nsIAbBooleanConditionTypes::GreaterThan;
+ else if (PL_strcasecmp (condition, "bw") == 0)
+ c = nsIAbBooleanConditionTypes::BeginsWith;
+ else if (PL_strcasecmp (condition, "ew") == 0)
+ c = nsIAbBooleanConditionTypes::EndsWith;
+ else if (PL_strcasecmp (condition, "c")== 0)
+ c = nsIAbBooleanConditionTypes::Contains;
+ else if (PL_strcasecmp (condition, "!c") == 0)
+ c = nsIAbBooleanConditionTypes::DoesNotContain;
+ else if (PL_strcasecmp (condition, "~=") == 0)
+ c = nsIAbBooleanConditionTypes::SoundsLike;
+ else if (PL_strcasecmp (condition, "regex") == 0)
+ c = nsIAbBooleanConditionTypes::RegExp;
+ else
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbBooleanConditionString> cs = do_CreateInstance(NS_BOOLEANCONDITIONSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cs->SetCondition (c);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID,&rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString attributeUCS2;
+ nsString valueUCS2;
+
+ rv = textToSubURI->UnEscapeAndConvert("UTF-8",
+ attribute, getter_Copies(attributeUCS2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = textToSubURI->UnEscapeAndConvert("UTF-8",
+ value, getter_Copies(valueUCS2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF16toUTF8 attributeUTF8(attributeUCS2);
+
+ rv = cs->SetName (attributeUTF8.get ());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = cs->SetValue(valueUCS2.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ NS_ConvertUTF8toUTF16 valueUCS2(value);
+
+ rv = cs->SetName (attribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = cs->SetValue (valueUCS2.get ());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+
+ NS_IF_ADDREF(*conditionString = cs);
+ return NS_OK;
+}
+
+
diff --git a/mailnews/addrbook/src/nsAbQueryStringToExpression.h b/mailnews/addrbook/src/nsAbQueryStringToExpression.h
new file mode 100644
index 000000000..dfd8da0c5
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbQueryStringToExpression.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbQueryStringToExpression_h__
+#define nsAbQueryStringToExpression_h__
+
+#include "nsIAbBooleanExpression.h"
+
+class nsAbQueryStringToExpression
+{
+public:
+ static nsresult Convert (
+ const nsACString &aQueryString,
+ nsIAbBooleanExpression** expression);
+
+protected:
+ static nsresult ParseExpression (
+ const char** index,
+ nsISupports** expression);
+ static nsresult ParseExpressions (
+ const char** index,
+ nsIAbBooleanExpression* expression);
+ static nsresult ParseCondition (
+ const char** index,
+ const char* indexBracketClose,
+ nsIAbBooleanConditionString** conditionString);
+
+ static nsresult ParseConditionEntry (
+ const char** index,
+ const char* indexBracketClose,
+ char** entry);
+ static nsresult ParseOperationEntry (
+ const char* indexBracketOpen1,
+ const char* indexBracketOpen2,
+ char** operation);
+
+ static nsresult CreateBooleanExpression(
+ const char* operation,
+ nsIAbBooleanExpression** expression);
+ static nsresult CreateBooleanConditionString (
+ const char* attribute,
+ const char* condition,
+ const char* value,
+ nsIAbBooleanConditionString** conditionString);
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAbUtils.h b/mailnews/addrbook/src/nsAbUtils.h
new file mode 100644
index 000000000..c50ba4d0d
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbUtils.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAbUtils_h__
+#define nsAbUtils_h__
+
+#include "nsMemory.h"
+
+/*
+ * Wrapper class to automatically free an array of
+ * char* when class goes out of scope
+ */
+class CharPtrArrayGuard
+{
+public:
+ explicit CharPtrArrayGuard (bool freeElements = true) :
+ mFreeElements (freeElements),
+ mArray (0),
+ mSize (0)
+ {
+ }
+
+ ~CharPtrArrayGuard ()
+ {
+ Free ();
+ }
+
+ char* operator[](int i)
+ {
+ return mArray[i];
+ }
+
+ uint32_t* GetSizeAddr(void)
+ {
+ return &mSize;
+ }
+
+ uint32_t GetSize(void)
+ {
+ return mSize;
+ }
+
+ char*** GetArrayAddr(void)
+ {
+ return &mArray;
+ }
+
+ const char** GetArray(void)
+ {
+ return (const char** ) mArray;
+ }
+
+public:
+
+private:
+ bool mFreeElements;
+ char **mArray;
+ uint32_t mSize;
+
+ void Free ()
+ {
+ if (!mArray)
+ return;
+
+ if (mFreeElements)
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mSize, mArray);
+ else
+ {
+ free(mArray);
+ }
+ }
+};
+
+/*
+ * Wrapper class to automatically free an array of
+ * char16_t* when class goes out of scope
+ */
+class PRUnicharPtrArrayGuard
+{
+public:
+ explicit PRUnicharPtrArrayGuard (bool freeElements = true) :
+ mFreeElements (freeElements),
+ mArray (0),
+ mSize (0)
+ {
+ }
+
+ ~PRUnicharPtrArrayGuard ()
+ {
+ Free ();
+ }
+
+ char16_t* operator[](int i)
+ {
+ return mArray[i];
+ }
+
+ uint32_t* GetSizeAddr(void)
+ {
+ return &mSize;
+ }
+
+ uint32_t GetSize(void)
+ {
+ return mSize;
+ }
+
+ char16_t*** GetArrayAddr(void)
+ {
+ return &mArray;
+ }
+
+ const char16_t** GetArray(void)
+ {
+ return (const char16_t** ) mArray;
+ }
+
+public:
+
+private:
+ bool mFreeElements;
+ char16_t **mArray;
+ uint32_t mSize;
+ void Free ()
+ {
+ if (!mArray)
+ return;
+
+ if (mFreeElements)
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mSize, mArray);
+ else
+ {
+ free(mArray);
+ }
+ }
+};
+
+#endif /* nsAbUtils_h__ */
diff --git a/mailnews/addrbook/src/nsAbView.cpp b/mailnews/addrbook/src/nsAbView.cpp
new file mode 100644
index 000000000..0cd52396d
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbView.cpp
@@ -0,0 +1,1453 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsAbView.h"
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "nsIAbCard.h"
+#include "nsILocale.h"
+#include "nsILocaleService.h"
+#include "prmem.h"
+#include "nsCollationCID.h"
+#include "nsIAbManager.h"
+#include "nsAbBaseCID.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITreeColumns.h"
+#include "nsCRTGlue.h"
+#include "nsIMutableArray.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIStringBundle.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsArrayUtils.h"
+#include "nsIAddrDatabase.h" // for kPhoneticNameColumn
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+#define CARD_NOT_FOUND -1
+#define ALL_ROWS -1
+
+#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst"
+#define PREF_MAIL_ADDR_BOOK_DISPLAYNAME_AUTOGENERATION "mail.addr_book.displayName.autoGeneration"
+#define PREF_MAIL_ADDR_BOOK_DISPLAYNAME_LASTNAMEFIRST "mail.addr_book.displayName.lastnamefirst"
+
+// Also, our default primary sort
+#define GENERATED_NAME_COLUMN_ID "GeneratedName"
+
+NS_IMPL_ISUPPORTS(nsAbView, nsIAbView, nsITreeView, nsIAbListener, nsIObserver)
+
+nsAbView::nsAbView() : mInitialized(false),
+ mIsAllDirectoryRootView(false),
+ mSuppressSelectionChange(false),
+ mSuppressCountChange(false),
+ mGeneratedNameFormat(0)
+{
+}
+
+nsAbView::~nsAbView()
+{
+ if (mInitialized) {
+ NS_ASSERTION(NS_SUCCEEDED(ClearView()), "failed to close view");
+ }
+}
+
+NS_IMETHODIMP nsAbView::ClearView()
+{
+ mDirectory = nullptr;
+ mAbViewListener = nullptr;
+ if (mTree)
+ mTree->SetView(nullptr);
+ mTree = nullptr;
+ mTreeSelection = nullptr;
+
+ if (mInitialized)
+ {
+ nsresult rv;
+ mInitialized = false;
+ nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = pbi->RemoveObserver(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = abManager->RemoveAddressBookListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ int32_t i = mCards.Length();
+ while(i-- > 0)
+ NS_ASSERTION(NS_SUCCEEDED(RemoveCardAt(i)), "remove card failed\n");
+
+ return NS_OK;
+}
+
+nsresult nsAbView::RemoveCardAt(int32_t row)
+{
+ nsresult rv;
+
+ AbCard *abcard = mCards.ElementAt(row);
+ NS_IF_RELEASE(abcard->card);
+ mCards.RemoveElementAt(row);
+ PR_FREEIF(abcard->primaryCollationKey);
+ PR_FREEIF(abcard->secondaryCollationKey);
+ PR_FREEIF(abcard);
+
+
+ // This needs to happen after we remove the card, as RowCountChanged() will call GetRowCount()
+ if (mTree) {
+ rv = mTree->RowCountChanged(row, -1);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (mAbViewListener && !mSuppressCountChange) {
+ rv = mAbViewListener->OnCountChanged(mCards.Length());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ return NS_OK;
+}
+
+nsresult nsAbView::SetGeneratedNameFormatFromPrefs()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return prefBranchInt->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &mGeneratedNameFormat);
+}
+
+nsresult nsAbView::Initialize()
+{
+ if (mInitialized)
+ return NS_OK;
+
+ mInitialized = true;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = abManager->AddAddressBookListener(this, nsIAbListener::all);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pbi->AddObserver(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, this, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mABBundle)
+ {
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED);
+
+ rv = stringBundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(mABBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return SetGeneratedNameFormatFromPrefs();
+}
+
+NS_IMETHODIMP nsAbView::SetView(nsIAbDirectory *aAddressBook,
+ nsIAbViewListener *aAbViewListener,
+ const nsAString &aSortColumn,
+ const nsAString &aSortDirection,
+ nsAString &aResult)
+{
+ // Ensure we are initialized
+ nsresult rv = Initialize();
+
+ mAbViewListener = nullptr;
+ if (mTree)
+ {
+ // Try and speed deletion of old cards by disconnecting the tree from us.
+ mTreeSelection->ClearSelection();
+ mTree->SetView(nullptr);
+ }
+
+ // Clear out old cards
+ int32_t i = mCards.Length();
+ while(i-- > 0)
+ {
+ rv = RemoveCardAt(i);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "remove card failed\n");
+ }
+
+ // We replace all cards so any sorting is no longer valid.
+ mSortColumn.AssignLiteral("");
+ mSortDirection.AssignLiteral("");
+
+ nsCString uri;
+ aAddressBook->GetURI(uri);
+ int32_t searchBegin = uri.FindChar('?');
+ nsCString searchQuery(Substring(uri, searchBegin));
+ // This is a special case, a workaround basically, to just have all ABs.
+ if (searchQuery.EqualsLiteral("?")) {
+ searchQuery.AssignLiteral("");
+ }
+
+ if (Substring(uri, 0, searchBegin).EqualsLiteral(kAllDirectoryRoot)) {
+ mIsAllDirectoryRootView = true;
+ // We have special request case to search all addressbooks, so we need
+ // to iterate over all addressbooks.
+ // Since the request is for all addressbooks, the URI must have been
+ // passed with an extra '?'. We still check it for sanity and trim it here.
+ if (searchQuery.Find("??") == 0)
+ searchQuery = Substring(searchQuery, 1);
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = abManager->GetDirectories(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ nsCOMPtr<nsISupports> support;
+ nsCOMPtr<nsIAbDirectory> directory;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
+ rv = enumerator->GetNext(getter_AddRefs(support));
+ NS_ENSURE_SUCCESS(rv, rv);
+ directory = do_QueryInterface(support, &rv);
+
+ // If, for some reason, we are unable to get a directory, we continue.
+ if (NS_FAILED(rv))
+ continue;
+
+ // Get appropriate directory with search query.
+ nsCString uri;
+ directory->GetURI(uri);
+ rv = abManager->GetDirectory(uri + searchQuery, getter_AddRefs(directory));
+ mDirectory = directory;
+ rv = EnumerateCards();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ mIsAllDirectoryRootView = false;
+ mDirectory = aAddressBook;
+ rv = EnumerateCards();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_NAMED_LITERAL_STRING(generatedNameColumnId, GENERATED_NAME_COLUMN_ID);
+
+ // See if the persisted sortColumn is valid.
+ // It may not be, if you migrated from older versions, or switched between
+ // a mozilla build and a commercial build, which have different columns.
+ nsAutoString actualSortColumn;
+ if (!generatedNameColumnId.Equals(aSortColumn) && mCards.Length()) {
+ nsIAbCard *card = mCards.ElementAt(0)->card;
+ nsString value;
+ // XXX todo
+ // Need to check if _Generic is valid. GetCardValue() will always return NS_OK for _Generic
+ // We're going to have to ask mDirectory if it is.
+ // It might not be. example: _ScreenName is valid in Netscape, but not Mozilla.
+ rv = GetCardValue(card, PromiseFlatString(aSortColumn).get(), value);
+ if (NS_FAILED(rv))
+ actualSortColumn = generatedNameColumnId;
+ else
+ actualSortColumn = aSortColumn;
+ }
+ else
+ actualSortColumn = aSortColumn;
+
+ rv = SortBy(actualSortColumn.get(), PromiseFlatString(aSortDirection).get(), false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mAbViewListener = aAbViewListener;
+ if (mAbViewListener && !mSuppressCountChange) {
+ rv = mAbViewListener->OnCountChanged(mCards.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aResult = actualSortColumn;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetDirectory(nsIAbDirectory **aDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+ NS_IF_ADDREF(*aDirectory = mDirectory);
+ return NS_OK;
+}
+
+nsresult nsAbView::EnumerateCards()
+{
+ nsresult rv;
+ nsCOMPtr<nsISimpleEnumerator> cardsEnumerator;
+ nsCOMPtr<nsIAbCard> card;
+
+ if (!mDirectory)
+ return NS_ERROR_UNEXPECTED;
+
+ rv = mDirectory->GetChildCards(getter_AddRefs(cardsEnumerator));
+ if (NS_SUCCEEDED(rv) && cardsEnumerator)
+ {
+ nsCOMPtr<nsISupports> item;
+ bool more;
+ while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more)
+ {
+ rv = cardsEnumerator->GetNext(getter_AddRefs(item));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item);
+ // Malloc these from an arena
+ AbCard *abcard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard));
+ if (!abcard)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ abcard->card = card;
+ NS_IF_ADDREF(abcard->card);
+
+ // XXX todo
+ // Would it be better to do an insertion sort, than append and sort?
+ // XXX todo
+ // If we knew how many cards there was going to be
+ // we could allocate an array of the size,
+ // instead of growing and copying as we append.
+ DebugOnly<bool> didAppend = mCards.AppendElement(abcard);
+ NS_ASSERTION(didAppend, "failed to append card");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetRowCount(int32_t *aRowCount)
+{
+ *aRowCount = mCards.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetSelection(nsITreeSelection * *aSelection)
+{
+ NS_IF_ADDREF(*aSelection = mTreeSelection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::SetSelection(nsITreeSelection * aSelection)
+{
+ mTreeSelection = aSelection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetRowProperties(int32_t index, nsAString& properties)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetCellProperties(int32_t row, nsITreeColumn* col, nsAString& properties)
+{
+ NS_ENSURE_TRUE(row >= 0, NS_ERROR_UNEXPECTED);
+
+ if (mCards.Length() <= (size_t)row)
+ return NS_OK;
+
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+ // "G" == "GeneratedName"
+ if (colID[0] != char16_t('G'))
+ return NS_OK;
+
+ nsIAbCard *card = mCards.ElementAt(row)->card;
+
+ bool isMailList;
+ nsresult rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (isMailList)
+ properties.AssignLiteral("MailList");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetColumnProperties(nsITreeColumn* col, nsAString& properties)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::IsContainer(int32_t index, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::IsContainerOpen(int32_t index, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::IsContainerEmpty(int32_t index, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::IsSeparator(int32_t index, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::IsSorted(bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::CanDrop(int32_t index,
+ int32_t orientation,
+ nsIDOMDataTransfer *dataTransfer,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::Drop(int32_t row,
+ int32_t orientation,
+ nsIDOMDataTransfer *dataTransfer)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::GetParentIndex(int32_t rowIndex, int32_t *_retval)
+{
+ *_retval = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::GetLevel(int32_t index, int32_t *_retval)
+{
+ *_retval = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ return NS_OK;
+}
+
+nsresult nsAbView::GetCardValue(nsIAbCard *card, const char16_t *colID,
+ nsAString &_retval)
+{
+ if (nsString(colID).EqualsLiteral("addrbook")) {
+ nsCString dirID;
+ nsresult rv = card->GetDirectoryId(dirID);
+ if (NS_SUCCEEDED(rv))
+ CopyUTF8toUTF16(Substring(dirID, dirID.FindChar('&') + 1), _retval);
+
+ return rv;
+ }
+
+ // "G" == "GeneratedName", "_P" == "_PhoneticName"
+ // else, standard column (like PrimaryEmail and _AimScreenName)
+ if (colID[0] == char16_t('G'))
+ return card->GenerateName(mGeneratedNameFormat, mABBundle, _retval);
+
+ if (colID[0] == char16_t('_') && colID[1] == char16_t('P'))
+ // Use LN/FN order for the phonetic name
+ return card->GeneratePhoneticName(true, _retval);
+
+ if (!NS_strcmp(colID, u"ChatName"))
+ return card->GenerateChatName(_retval);
+
+ nsresult rv = card->GetPropertyAsAString(NS_ConvertUTF16toUTF8(colID).get(), _retval);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ rv = NS_OK;
+ _retval.Truncate();
+ }
+ return rv;
+}
+
+nsresult nsAbView::RefreshTree()
+{
+ nsresult rv;
+
+ // The PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST pref affects how the GeneratedName column looks.
+ // so if the GeneratedName is our primary or secondary sort,
+ // we need to resort.
+ // the same applies for kPhoneticNameColumn
+ //
+ // XXX optimize me
+ // PrimaryEmail is always the secondary sort, unless it is currently the
+ // primary sort. So, if PrimaryEmail is the primary sort,
+ // GeneratedName might be the secondary sort.
+ //
+ // One day, we can get fancy and remember what the secondary sort is.
+ // We do that, we can fix this code. At best, it will turn a sort into a invalidate.
+ //
+ // If neither the primary nor the secondary sorts are GeneratedName (or kPhoneticNameColumn),
+ // all we have to do is invalidate (to show the new GeneratedNames),
+ // but the sort will not change.
+ if (mSortColumn.EqualsLiteral(GENERATED_NAME_COLUMN_ID) ||
+ mSortColumn.EqualsLiteral(kPriEmailProperty) ||
+ mSortColumn.EqualsLiteral(kPhoneticNameColumn)) {
+ rv = SortBy(mSortColumn.get(), mSortDirection.get(), true);
+ }
+ else {
+ rv = InvalidateTree(ALL_ROWS);
+
+ // Although the selection hasn't changed, the card that is selected may need
+ // to be displayed differently, therefore pretend that the selection has
+ // changed to force that update.
+ SelectionChanged();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbView::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ NS_ENSURE_TRUE(row >= 0 && (size_t)row < mCards.Length(), NS_ERROR_UNEXPECTED);
+
+ nsIAbCard *card = mCards.ElementAt(row)->card;
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+ return GetCardValue(card, colID, _retval);
+}
+
+NS_IMETHODIMP nsAbView::SetTree(nsITreeBoxObject *tree)
+{
+ mTree = tree;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::ToggleOpenState(int32_t index)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::CycleHeader(nsITreeColumn* col)
+{
+ return NS_OK;
+}
+
+nsresult nsAbView::InvalidateTree(int32_t row)
+{
+ if (!mTree)
+ return NS_OK;
+
+ if (row == ALL_ROWS)
+ return mTree->Invalidate();
+ else
+ return mTree->InvalidateRow(row);
+}
+
+NS_IMETHODIMP nsAbView::SelectionChanged()
+{
+ if (mAbViewListener && !mSuppressSelectionChange) {
+ nsresult rv = mAbViewListener->OnSelectionChanged();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::CycleCell(int32_t row, nsITreeColumn* col)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::IsEditable(int32_t row, nsITreeColumn* col, bool* _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::IsSelectable(int32_t row, nsITreeColumn* col, bool* _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::PerformAction(const char16_t *action)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::PerformActionOnRow(const char16_t *action, int32_t row)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::PerformActionOnCell(const char16_t *action, int32_t row, nsITreeColumn* col)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbView::GetCardFromRow(int32_t row, nsIAbCard **aCard)
+{
+ *aCard = nullptr;
+ NS_ENSURE_TRUE(row >= 0, NS_ERROR_UNEXPECTED);
+ if (mCards.Length() <= (size_t)row) {
+ return NS_OK;
+ }
+
+ AbCard *a = mCards.ElementAt(row);
+ if (!a)
+ return NS_OK;
+
+ NS_IF_ADDREF(*aCard = a->card);
+ return NS_OK;
+}
+
+#define DESCENDING_SORT_FACTOR -1
+#define ASCENDING_SORT_FACTOR 1
+
+typedef struct SortClosure
+{
+ const char16_t *colID;
+ int32_t factor;
+ nsAbView *abView;
+} SortClosure;
+
+static int
+inplaceSortCallback(const AbCard *card1, const AbCard *card2, SortClosure *closure)
+{
+ int32_t sortValue;
+
+ // If we are sorting the "PrimaryEmail", swap the collation keys, as the secondary is always the
+ // PrimaryEmail. Use the last primary key as the secondary key.
+ //
+ // "Pr" to distinguish "PrimaryEmail" from "PagerNumber"
+ if (closure->colID[0] == char16_t('P') && closure->colID[1] == char16_t('r')) {
+ sortValue = closure->abView->CompareCollationKeys(card1->secondaryCollationKey,card1->secondaryCollationKeyLen,card2->secondaryCollationKey,card2->secondaryCollationKeyLen);
+ if (sortValue)
+ return sortValue * closure->factor;
+ else
+ return closure->abView->CompareCollationKeys(card1->primaryCollationKey,card1->primaryCollationKeyLen,card2->primaryCollationKey,card2->primaryCollationKeyLen) * (closure->factor);
+ }
+ else {
+ sortValue = closure->abView->CompareCollationKeys(card1->primaryCollationKey,card1->primaryCollationKeyLen,card2->primaryCollationKey,card2->primaryCollationKeyLen);
+ if (sortValue)
+ return sortValue * (closure->factor);
+ else
+ return closure->abView->CompareCollationKeys(card1->secondaryCollationKey,card1->secondaryCollationKeyLen,card2->secondaryCollationKey,card2->secondaryCollationKeyLen) * (closure->factor);
+ }
+}
+
+static void SetSortClosure(const char16_t *sortColumn, const char16_t *sortDirection, nsAbView *abView, SortClosure *closure)
+{
+ closure->colID = sortColumn;
+
+ if (sortDirection && !NS_strcmp(sortDirection, u"descending"))
+ closure->factor = DESCENDING_SORT_FACTOR;
+ else
+ closure->factor = ASCENDING_SORT_FACTOR;
+
+ closure->abView = abView;
+ return;
+}
+
+class CardComparator
+{
+public:
+ void SetClosure(SortClosure *closure) { m_closure = closure; };
+
+ bool Equals(const AbCard *a, const AbCard *b) const {
+ return inplaceSortCallback(a, b, m_closure) == 0;
+ }
+ bool LessThan(const AbCard *a, const AbCard *b) const{
+ return inplaceSortCallback(a, b, m_closure) < 0;
+ }
+
+private:
+ SortClosure *m_closure;
+};
+
+NS_IMETHODIMP nsAbView::SortBy(const char16_t *colID, const char16_t *sortDir, bool aResort = false)
+{
+ nsresult rv;
+
+ int32_t count = mCards.Length();
+
+ nsAutoString sortColumn;
+ if (!colID)
+ sortColumn = NS_LITERAL_STRING(GENERATED_NAME_COLUMN_ID); // default sort column
+ else
+ sortColumn = colID;
+
+ nsAutoString sortDirection;
+ if (!sortDir)
+ sortDirection = NS_LITERAL_STRING("ascending"); // default direction
+ else
+ sortDirection = sortDir;
+
+ if (mSortColumn.Equals(sortColumn) && !aResort) {
+ if (mSortDirection.Equals(sortDir)) {
+ // If sortColumn and sortDirection are identical since the last call, do nothing.
+ return NS_OK;
+ } else {
+ // If we are sorting by how we are already sorted,
+ // and just the sort direction changes, just reverse.
+ int32_t halfPoint = count / 2;
+ for (int32_t i = 0; i < halfPoint; i++) {
+ // Swap the elements.
+ AbCard *ptr1 = mCards.ElementAt(i);
+ AbCard *ptr2 = mCards.ElementAt(count - i - 1);
+ mCards.ReplaceElementAt(i, ptr2);
+ mCards.ReplaceElementAt(count - i - 1, ptr1);
+ }
+ mSortDirection = sortDir;
+ }
+ }
+ else {
+ // Generate collation keys
+ for (int32_t i = 0; i < count; i++) {
+ AbCard *abcard = mCards.ElementAt(i);
+
+ rv = GenerateCollationKeysForCard(sortColumn.get(), abcard);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // We need to do full sort.
+ SortClosure closure;
+ SetSortClosure(sortColumn.get(), sortDirection.get(), this, &closure);
+
+ nsCOMPtr<nsIMutableArray> selectedCards = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetSelectedCards(selectedCards);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> indexCard;
+
+ if (mTreeSelection) {
+ int32_t currentIndex = -1;
+
+ rv = mTreeSelection->GetCurrentIndex(&currentIndex);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (currentIndex != -1) {
+ rv = GetCardFromRow(currentIndex, getter_AddRefs(indexCard));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+
+ CardComparator cardComparator;
+ cardComparator.SetClosure(&closure);
+ mCards.Sort(cardComparator);
+
+ rv = ReselectCards(selectedCards, indexCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSortColumn = sortColumn;
+ mSortDirection = sortDirection;
+ }
+
+ rv = InvalidateTree(ALL_ROWS);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+int32_t nsAbView::CompareCollationKeys(uint8_t *key1, uint32_t len1, uint8_t *key2, uint32_t len2)
+{
+ NS_ASSERTION(mCollationKeyGenerator, "no key generator");
+ if (!mCollationKeyGenerator)
+ return 0;
+
+ int32_t result;
+
+ nsresult rv = mCollationKeyGenerator->CompareRawSortKey(key1,len1,key2,len2,&result);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "key compare failed");
+ if (NS_FAILED(rv))
+ result = 0;
+ return result;
+}
+
+nsresult nsAbView::GenerateCollationKeysForCard(const char16_t *colID, AbCard *abcard)
+{
+ nsresult rv;
+ nsString value;
+
+ if (!mCollationKeyGenerator)
+ {
+ nsCOMPtr<nsILocaleService> localeSvc = do_GetService(NS_LOCALESERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILocale> locale;
+ rv = localeSvc->GetApplicationLocale(getter_AddRefs(locale));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsICollationFactory> factory = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = factory->CreateCollation(locale, getter_AddRefs(mCollationKeyGenerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = GetCardValue(abcard->card, colID, value);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ PR_FREEIF(abcard->primaryCollationKey);
+ rv = mCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive,
+ value, &(abcard->primaryCollationKey), &(abcard->primaryCollationKeyLen));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Hardcode email to be our secondary key. As we are doing this, just call
+ // the card's GetCardValue direct, rather than our own function which will
+ // end up doing the same as then we can save a bit of time.
+ rv = abcard->card->GetPrimaryEmail(value);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ PR_FREEIF(abcard->secondaryCollationKey);
+ rv = mCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive,
+ value, &(abcard->secondaryCollationKey), &(abcard->secondaryCollationKeyLen));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+// A helper method currently returns true if the directory is an LDAP.
+// We can tweak this to return true for all Remote Address Books where the
+// search is asynchronous.
+bool isDirectoryRemote(nsCOMPtr<nsIAbDirectory> aDir)
+{
+ nsCString uri;
+ aDir->GetURI(uri);
+ return (uri.Find("moz-abldapdirectory") != kNotFound);
+}
+
+// A helper method to get the query string for nsIAbDirectory.
+nsCString getQuery(nsCOMPtr<nsIAbDirectory> aDir)
+{
+ nsCString uri;
+ aDir->GetURI(uri);
+ int32_t searchBegin = uri.FindChar('?');
+ if (searchBegin == kNotFound)
+ return EmptyCString();
+
+ return nsCString(Substring(uri, searchBegin));
+}
+
+NS_IMETHODIMP nsAbView::OnItemAdded(nsISupports *parentDir, nsISupports *item)
+{
+ if (!mDirectory) // No address book selected.
+ return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr <nsIAbDirectory> directory = do_QueryInterface(parentDir, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isRemote = isDirectoryRemote(directory);
+ // If the search is performed on All Address books, its possible that the LDAP
+ // results start coming when mDirectory has changed (LDAP search works in an
+ // asynchronous manner).
+ // Since the listeners are being added to all nsAbView instances, we need to
+ // make sure that all the views aren't updated by the listeners.
+ bool isDirectoryQuery = false;
+ bool isMDirectoryQuery = false;
+ // See if current parent directory to which the item is added is a query
+ // directory.
+ directory->GetIsQuery(&isDirectoryQuery);
+ // Get the query string for the directory in Advanced AB Search window.
+ nsCString directoryQuery(getQuery(directory));
+ // See if the selected directory in Address book main window is a query
+ // directory.
+ mDirectory->GetIsQuery(&isMDirectoryQuery);
+ // Get the query string for the selected directory in the main AB window.
+ nsCString mDirectoryQuery(getQuery(mDirectory));
+ if ((mIsAllDirectoryRootView && isRemote &&
+ isDirectoryQuery && isMDirectoryQuery &&
+ directoryQuery.Equals(mDirectoryQuery)) ||
+ directory.get() == mDirectory.get()) {
+ nsCOMPtr <nsIAbCard> addedCard = do_QueryInterface(item);
+ if (addedCard) {
+ // Malloc these from an arena
+ AbCard *abcard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard));
+ if (!abcard)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ abcard->card = addedCard;
+ NS_IF_ADDREF(abcard->card);
+
+ rv = GenerateCollationKeysForCard(mSortColumn.get(), abcard);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t index;
+ rv = AddCard(abcard, false /* select card */, &index);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbView::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ if (nsDependentString(someData).EqualsLiteral(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST)) {
+ nsresult rv = SetGeneratedNameFormatFromPrefs();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = RefreshTree();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsAbView::AddCard(AbCard *abcard, bool selectCardAfterAdding, int32_t *index)
+{
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG_POINTER(abcard);
+
+ *index = FindIndexForInsert(abcard);
+ mCards.InsertElementAt(*index, abcard);
+
+ // This needs to happen after we insert the card, as RowCountChanged() will call GetRowCount()
+ if (mTree)
+ rv = mTree->RowCountChanged(*index, 1);
+
+ // Checking for mTree here works around core bug 399227
+ if (selectCardAfterAdding && mTreeSelection && mTree) {
+ mTreeSelection->SetCurrentIndex(*index);
+ mTreeSelection->RangedSelect(*index, *index, false /* augment */);
+ }
+
+ if (mAbViewListener && !mSuppressCountChange) {
+ rv = mAbViewListener->OnCountChanged(mCards.Length());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ return rv;
+}
+
+int32_t nsAbView::FindIndexForInsert(AbCard *abcard)
+{
+ int32_t count = mCards.Length();
+ int32_t i;
+
+ SortClosure closure;
+ SetSortClosure(mSortColumn.get(), mSortDirection.get(), this, &closure);
+
+ // XXX todo
+ // Make this a binary search
+ for (i=0; i < count; i++) {
+ int32_t value = inplaceSortCallback(abcard, mCards.ElementAt(i), &closure);
+ // XXX Fix me, this is not right for both ascending and descending
+ if (value <= 0)
+ break;
+ }
+ return i;
+}
+
+NS_IMETHODIMP nsAbView::OnItemRemoved(nsISupports *parentDir, nsISupports *item)
+{
+ nsresult rv;
+
+ nsCOMPtr <nsIAbDirectory> directory = do_QueryInterface(parentDir,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (directory.get() == mDirectory.get())
+ return RemoveCardAndSelectNextCard(item);
+
+ // The pointers aren't the same, are the URI strings similar? This is most
+ // likely the case if the current directory is a search on a directory.
+ nsCString currentURI;
+ rv = mDirectory->GetURI(currentURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If it is a search, it will have something like ?(or(PrimaryEmail...
+ // on the end of the string, so remove that before comparing
+ int32_t pos = currentURI.FindChar('?');
+ if (pos != -1)
+ currentURI.SetLength(pos);
+
+ nsCString notifiedURI;
+ rv = directory->GetURI(notifiedURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (currentURI.Equals(notifiedURI))
+ return RemoveCardAndSelectNextCard(item);
+
+ return NS_OK;
+}
+
+nsresult nsAbView::RemoveCardAndSelectNextCard(nsISupports *item)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item);
+ if (card) {
+ int32_t index = FindIndexForCard(card);
+ if (index != CARD_NOT_FOUND) {
+ bool selectNextCard = false;
+ if (mTreeSelection) {
+ int32_t selectedIndex;
+ // XXX todo
+ // Make sure it works if nothing selected
+ mTreeSelection->GetCurrentIndex(&selectedIndex);
+ if (index == selectedIndex)
+ selectNextCard = true;
+ }
+
+ rv = RemoveCardAt(index);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (selectNextCard) {
+ int32_t count = mCards.Length();
+ if (count && mTreeSelection) {
+ // If we deleted the last card, adjust so we select the new "last" card
+ if (index >= (count - 1)) {
+ index = count -1;
+ }
+ mTreeSelection->SetCurrentIndex(index);
+ mTreeSelection->RangedSelect(index, index, false /* augment */);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+int32_t nsAbView::FindIndexForCard(nsIAbCard *card)
+{
+ int32_t count = mCards.Length();
+ int32_t i;
+
+ // You can't implement the binary search here, as all you have is the nsIAbCard
+ // you might be here because one of the card properties has changed, and that property
+ // could be the collation key.
+ for (i=0; i < count; i++) {
+ AbCard *abcard = mCards.ElementAt(i);
+ bool equals;
+ nsresult rv = card->Equals(abcard->card, &equals);
+ if (NS_SUCCEEDED(rv) && equals) {
+ return i;
+ }
+ }
+ return CARD_NOT_FOUND;
+}
+
+NS_IMETHODIMP nsAbView::OnItemPropertyChanged(nsISupports *item, const char *property, const char16_t *oldValue, const char16_t *newValue)
+{
+ nsresult rv;
+
+ nsCOMPtr <nsIAbCard> card = do_QueryInterface(item);
+ if (!card)
+ return NS_OK;
+
+ int32_t index = FindIndexForCard(card);
+ if (index == -1)
+ return NS_OK;
+
+ AbCard *oldCard = mCards.ElementAt(index);
+
+ // Malloc these from an arena
+ AbCard *newCard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard));
+ if (!newCard)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ newCard->card = card;
+ NS_IF_ADDREF(newCard->card);
+
+ rv = GenerateCollationKeysForCard(mSortColumn.get(), newCard);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool cardWasSelected = false;
+
+ if (mTreeSelection) {
+ rv = mTreeSelection->IsSelected(index, &cardWasSelected);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (!CompareCollationKeys(newCard->primaryCollationKey,newCard->primaryCollationKeyLen,oldCard->primaryCollationKey,oldCard->primaryCollationKeyLen)
+ && CompareCollationKeys(newCard->secondaryCollationKey,newCard->secondaryCollationKeyLen,oldCard->secondaryCollationKey,oldCard->secondaryCollationKeyLen)) {
+ // No need to remove and add, since the collation keys haven't changed.
+ // Since they haven't changed, the card will sort to the same place.
+ // We just need to clean up what we allocated.
+ NS_IF_RELEASE(newCard->card);
+ if (newCard->primaryCollationKey)
+ free(newCard->primaryCollationKey);
+ if (newCard->secondaryCollationKey)
+ free(newCard->secondaryCollationKey);
+ PR_FREEIF(newCard);
+
+ // Still need to invalidate, as the other columns may have changed.
+ rv = InvalidateTree(index);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else {
+ mSuppressSelectionChange = true;
+ mSuppressCountChange = true;
+
+ // Remove the old card.
+ rv = RemoveCardAt(index);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "remove card failed\n");
+
+ // Add the card we created, and select it (to restore selection) if it was selected.
+ rv = AddCard(newCard, cardWasSelected /* select card */, &index);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "add card failed\n");
+
+ mSuppressSelectionChange = false;
+ mSuppressCountChange = false;
+
+ // Ensure restored selection is visible
+ if (cardWasSelected && mTree)
+ mTree->EnsureRowIsVisible(index);
+ }
+
+ // Although the selection hasn't changed, the card that is selected may need
+ // to be displayed differently, therefore pretend that the selection has
+ // changed to force that update.
+ if (cardWasSelected)
+ SelectionChanged();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::SelectAll()
+{
+ if (mTreeSelection && mTree) {
+ mTreeSelection->SelectAll();
+ mTree->Invalidate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetSortDirection(nsAString & aDirection)
+{
+ aDirection = mSortDirection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::GetSortColumn(nsAString & aColumn)
+{
+ aColumn = mSortColumn;
+ return NS_OK;
+}
+
+nsresult nsAbView::ReselectCards(nsIArray *aCards, nsIAbCard *aIndexCard)
+{
+ uint32_t count;
+ uint32_t i;
+
+ if (!mTreeSelection || !aCards)
+ return NS_OK;
+
+ nsresult rv = mTreeSelection->ClearSelection();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aCards->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we don't have any cards selected, nothing else to do.
+ if (!count)
+ return NS_OK;
+
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbCard> card = do_QueryElementAt(aCards, i);
+ if (card) {
+ int32_t index = FindIndexForCard(card);
+ if (index != CARD_NOT_FOUND) {
+ mTreeSelection->RangedSelect(index, index, true /* augment */);
+ }
+ }
+ }
+
+ // Reset the index card, and ensure it is visible.
+ if (aIndexCard) {
+ int32_t currentIndex = FindIndexForCard(aIndexCard);
+ rv = mTreeSelection->SetCurrentIndex(currentIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mTree) {
+ rv = mTree->EnsureRowIsVisible(currentIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::DeleteSelectedCards()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> cardsToDelete = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetSelectedCards(cardsToDelete);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // mDirectory should not be null
+ // Bullet proof (and assert) to help figure out bug #127748
+ NS_ENSURE_TRUE(mDirectory, NS_ERROR_UNEXPECTED);
+
+ rv = mDirectory->DeleteCards(cardsToDelete);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+nsresult nsAbView::GetSelectedCards(nsCOMPtr<nsIMutableArray> &aSelectedCards)
+{
+ if (!mTreeSelection)
+ return NS_OK;
+
+ int32_t selectionCount;
+ nsresult rv = mTreeSelection->GetRangeCount(&selectionCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!selectionCount)
+ return NS_OK;
+
+ for (int32_t i = 0; i < selectionCount; i++)
+ {
+ int32_t startRange;
+ int32_t endRange;
+ rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ int32_t totalCards = mCards.Length();
+ if (startRange >= 0 && startRange < totalCards)
+ {
+ for (int32_t rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < totalCards; rangeIndex++) {
+ nsCOMPtr<nsIAbCard> abCard;
+ rv = GetCardFromRow(rangeIndex, getter_AddRefs(abCard));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aSelectedCards->AppendElement(abCard, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbView::SwapFirstNameLastName()
+{
+ if (!mTreeSelection)
+ return NS_OK;
+
+ int32_t selectionCount;
+ nsresult rv = mTreeSelection->GetRangeCount(&selectionCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!selectionCount)
+ return NS_OK;
+
+ // Prepare for displayname generation
+ // No cache for pref and bundle since the swap operation is not executed frequently
+ bool displayNameAutoGeneration;
+ bool displayNameLastnamefirst = false;
+
+ nsCOMPtr<nsIPrefBranch> pPrefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pPrefBranchInt->GetBoolPref(PREF_MAIL_ADDR_BOOK_DISPLAYNAME_AUTOGENERATION, &displayNameAutoGeneration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (displayNameAutoGeneration)
+ {
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ rv = pPrefBranchInt->GetComplexValue(PREF_MAIL_ADDR_BOOK_DISPLAYNAME_LASTNAMEFIRST,
+ NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString str;
+ pls->ToString(getter_Copies(str));
+ displayNameLastnamefirst = str.EqualsLiteral("true");
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ for (int32_t i = 0; i < selectionCount; i++)
+ {
+ int32_t startRange;
+ int32_t endRange;
+ rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ int32_t totalCards = mCards.Length();
+ if (startRange >= 0 && startRange < totalCards)
+ {
+ for (int32_t rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < totalCards; rangeIndex++) {
+ nsCOMPtr<nsIAbCard> abCard;
+ rv = GetCardFromRow(rangeIndex, getter_AddRefs(abCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Swap FN/LN
+ nsAutoString fn, ln;
+ abCard->GetFirstName(fn);
+ abCard->GetLastName(ln);
+ if (!fn.IsEmpty() || !ln.IsEmpty())
+ {
+ abCard->SetFirstName(ln);
+ abCard->SetLastName(fn);
+
+ // Generate display name using the new order
+ if (displayNameAutoGeneration &&
+ !fn.IsEmpty() && !ln.IsEmpty())
+ {
+ nsString dnLnFn;
+ nsString dnFnLn;
+ const char16_t *nameString[2];
+ const char16_t *formatString;
+
+ // The format should stays the same before/after we swap the names
+ formatString = displayNameLastnamefirst ?
+ u"lastFirstFormat" :
+ u"firstLastFormat";
+
+ // Generate both ln/fn and fn/ln combination since we need both later
+ // to check to see if the current display name was edited
+ // note that fn/ln still hold the values before the swap
+ nameString[0] = ln.get();
+ nameString[1] = fn.get();
+ rv = bundle->FormatStringFromName(formatString,
+ nameString, 2, getter_Copies(dnLnFn));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nameString[0] = fn.get();
+ nameString[1] = ln.get();
+ rv = bundle->FormatStringFromName(formatString,
+ nameString, 2, getter_Copies(dnFnLn));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current display name
+ nsAutoString dn;
+ rv = abCard->GetDisplayName(dn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Swap the display name if not edited
+ if (displayNameLastnamefirst)
+ {
+ if (dn.Equals(dnLnFn))
+ abCard->SetDisplayName(dnFnLn);
+ }
+ else
+ {
+ if (dn.Equals(dnFnLn))
+ abCard->SetDisplayName(dnLnFn);
+ }
+ }
+
+ // Swap phonetic names
+ rv = abCard->GetPropertyAsAString(kPhoneticFirstNameProperty, fn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = abCard->GetPropertyAsAString(kPhoneticLastNameProperty, ln);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!fn.IsEmpty() || !ln.IsEmpty())
+ {
+ abCard->SetPropertyAsAString(kPhoneticFirstNameProperty, ln);
+ abCard->SetPropertyAsAString(kPhoneticLastNameProperty, fn);
+ }
+ }
+ }
+ }
+ }
+ // Update the tree
+ // Re-sort if either generated or phonetic name is primary or secondary sort,
+ // otherwise invalidate to reflect the change
+ rv = RefreshTree();
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbView::GetSelectedAddresses(nsIArray **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> selectedCards = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetSelectedCards(selectedCards);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> addresses = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t count;
+ selectedCards->GetLength(&count);
+
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbCard> card(do_QueryElementAt(selectedCards, i, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isMailList;
+ card->GetIsMailList(&isMailList);
+ nsAutoString primaryEmail;
+ if (isMailList) {
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString mailListURI;
+ rv = card->GetMailListURI(getter_Copies(mailListURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> mailList;
+ rv = abManager->GetDirectory(mailListURI, getter_AddRefs(mailList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> mailListAddresses;
+ rv = mailList->GetAddressLists(getter_AddRefs(mailListAddresses));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t mailListCount = 0;
+ mailListAddresses->GetLength(&mailListCount);
+
+ for (uint32_t j = 0; j < mailListCount; j++) {
+ nsCOMPtr<nsIAbCard> mailListCard = do_QueryElementAt(mailListAddresses, j, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mailListCard->GetPrimaryEmail(primaryEmail);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!primaryEmail.IsEmpty()) {
+ nsCOMPtr<nsISupportsString> supportsEmail(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ supportsEmail->SetData(primaryEmail);
+ addresses->AppendElement(supportsEmail, false);
+ }
+ }
+ }
+ else {
+ rv = card->GetPrimaryEmail(primaryEmail);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!primaryEmail.IsEmpty()) {
+ nsCOMPtr<nsISupportsString> supportsEmail(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ supportsEmail->SetData(primaryEmail);
+ addresses->AppendElement(supportsEmail, false);
+ }
+ }
+ }
+
+ NS_IF_ADDREF(*_retval = addresses);
+
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAbView.h b/mailnews/addrbook/src/nsAbView.h
new file mode 100644
index 000000000..f0a913082
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbView.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsAbView_H_
+#define _nsAbView_H_
+
+#include "nsISupports.h"
+#include "nsStringGlue.h"
+#include "nsIAbView.h"
+#include "nsITreeView.h"
+#include "nsITreeBoxObject.h"
+#include "nsITreeSelection.h"
+#include "nsTArray.h"
+#include "nsIAbDirectory.h"
+#include "nsIAtom.h"
+#include "nsICollation.h"
+#include "nsIAbListener.h"
+#include "nsIObserver.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "nsIStringBundle.h"
+
+typedef struct AbCard
+{
+ nsIAbCard *card;
+ uint32_t primaryCollationKeyLen;
+ uint32_t secondaryCollationKeyLen;
+ uint8_t *primaryCollationKey;
+ uint8_t *secondaryCollationKey;
+} AbCard;
+
+
+class nsAbView : public nsIAbView, public nsITreeView, public nsIAbListener, public nsIObserver
+{
+public:
+ nsAbView();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABVIEW
+ NS_DECL_NSITREEVIEW
+ NS_DECL_NSIABLISTENER
+ NS_DECL_NSIOBSERVER
+
+ int32_t CompareCollationKeys(uint8_t *key1, uint32_t len1, uint8_t *key2, uint32_t len2);
+
+private:
+ virtual ~nsAbView();
+ nsresult Initialize();
+ int32_t FindIndexForInsert(AbCard *abcard);
+ int32_t FindIndexForCard(nsIAbCard *card);
+ nsresult GenerateCollationKeysForCard(const char16_t *colID, AbCard *abcard);
+ nsresult InvalidateTree(int32_t row);
+ nsresult RemoveCardAt(int32_t row);
+ nsresult AddCard(AbCard *abcard, bool selectCardAfterAdding, int32_t *index);
+ nsresult RemoveCardAndSelectNextCard(nsISupports *item);
+ nsresult EnumerateCards();
+ nsresult SetGeneratedNameFormatFromPrefs();
+ nsresult GetSelectedCards(nsCOMPtr<nsIMutableArray> &aSelectedCards);
+ nsresult ReselectCards(nsIArray *aCards, nsIAbCard *aIndexCard);
+ nsresult GetCardValue(nsIAbCard *card, const char16_t *colID, nsAString &_retval);
+ nsresult RefreshTree();
+
+ nsCOMPtr<nsITreeBoxObject> mTree;
+ nsCOMPtr<nsITreeSelection> mTreeSelection;
+ nsCOMPtr <nsIAbDirectory> mDirectory;
+ nsTArray<AbCard*> mCards;
+ nsString mSortColumn;
+ nsString mSortDirection;
+ nsCOMPtr<nsICollation> mCollationKeyGenerator;
+ nsCOMPtr<nsIAbViewListener> mAbViewListener;
+ nsCOMPtr<nsIStringBundle> mABBundle;
+
+ bool mInitialized;
+ bool mIsAllDirectoryRootView;
+ bool mSuppressSelectionChange;
+ bool mSuppressCountChange;
+ int32_t mGeneratedNameFormat;
+};
+
+#endif /* _nsAbView_H_ */
diff --git a/mailnews/addrbook/src/nsAbWinHelper.cpp b/mailnews/addrbook/src/nsAbWinHelper.cpp
new file mode 100644
index 000000000..c5b9d131f
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbWinHelper.cpp
@@ -0,0 +1,1003 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#define INITGUID
+#define USES_IID_IMAPIProp
+#define USES_IID_IMAPIContainer
+#define USES_IID_IABContainer
+#define USES_IID_IMAPITable
+#define USES_IID_IDistList
+
+#include "nsAbWinHelper.h"
+#include "nsMapiAddressBook.h"
+#include "nsWabAddressBook.h"
+
+#include <mapiguid.h>
+
+#include "mozilla/Logging.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gAbWinHelperLog
+ = PR_NewLogModule("nsAbWinHelperLog");
+#endif
+
+#define PRINTF(args) MOZ_LOG(gAbWinHelperLog, mozilla::LogLevel::Debug, args)
+
+// Small utility to ensure release of all MAPI interfaces
+template <class tInterface> struct nsMapiInterfaceWrapper
+{
+ tInterface mInterface ;
+
+ nsMapiInterfaceWrapper(void) : mInterface(NULL) {}
+ ~nsMapiInterfaceWrapper(void) {
+ if (mInterface != NULL) { mInterface->Release() ; }
+ }
+ operator LPUNKNOWN *(void) { return reinterpret_cast<LPUNKNOWN *>(&mInterface) ; }
+ tInterface operator -> (void) const { return mInterface ; }
+ operator tInterface *(void) { return &mInterface ; }
+} ;
+
+static void assignEntryID(LPENTRYID& aTarget, LPENTRYID aSource, ULONG aByteCount)
+{
+ if (aTarget != NULL) {
+ delete [] (reinterpret_cast<LPBYTE>(aTarget)) ;
+ aTarget = NULL ;
+ }
+ if (aSource != NULL) {
+ aTarget = reinterpret_cast<LPENTRYID>(new BYTE [aByteCount]) ;
+ memcpy(aTarget, aSource, aByteCount) ;
+ }
+}
+
+nsMapiEntry::nsMapiEntry(void)
+: mByteCount(0), mEntryId(NULL)
+{
+ MOZ_COUNT_CTOR(nsMapiEntry) ;
+}
+
+nsMapiEntry::nsMapiEntry(ULONG aByteCount, LPENTRYID aEntryId)
+: mByteCount(0), mEntryId(NULL)
+{
+ Assign(aByteCount, aEntryId) ;
+ MOZ_COUNT_CTOR(nsMapiEntry) ;
+}
+
+nsMapiEntry::~nsMapiEntry(void)
+{
+ Assign(0, NULL) ;
+ MOZ_COUNT_DTOR(nsMapiEntry) ;
+}
+
+void nsMapiEntry::Assign(ULONG aByteCount, LPENTRYID aEntryId)
+{
+ assignEntryID(mEntryId, aEntryId, aByteCount) ;
+ mByteCount = aByteCount ;
+}
+
+static char UnsignedToChar(unsigned char aUnsigned)
+{
+ if (aUnsigned < 0xA) { return '0' + aUnsigned ; }
+ return 'A' + aUnsigned - 0xA ;
+}
+
+static char kBase64Encoding [] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-." ;
+static const int kARank = 0 ;
+static const int kaRank = 26 ;
+static const int k0Rank = 52 ;
+static const unsigned char kMinusRank = 62 ;
+static const unsigned char kDotRank = 63 ;
+
+static void UnsignedToBase64(unsigned char *& aUnsigned,
+ ULONG aNbUnsigned, nsCString& aString)
+{
+ if (aNbUnsigned > 0) {
+ unsigned char remain0 = (*aUnsigned & 0x03) << 4 ;
+
+ aString.Append(kBase64Encoding [(*aUnsigned >> 2) & 0x3F]) ;
+ ++ aUnsigned ;
+ if (aNbUnsigned > 1) {
+ unsigned char remain1 = (*aUnsigned & 0x0F) << 2 ;
+
+ aString.Append(kBase64Encoding [remain0 | ((*aUnsigned >> 4) & 0x0F)]) ;
+ ++ aUnsigned ;
+ if (aNbUnsigned > 2) {
+ aString.Append(kBase64Encoding [remain1 | ((*aUnsigned >> 6) & 0x03)]) ;
+ aString.Append(kBase64Encoding [*aUnsigned & 0x3F]) ;
+ ++ aUnsigned ;
+ }
+ else {
+ aString.Append(kBase64Encoding [remain1]) ;
+ }
+ }
+ else {
+ aString.Append(kBase64Encoding [remain0]) ;
+ }
+ }
+}
+
+static unsigned char CharToUnsigned(char aChar)
+{
+ if (aChar >= '0' && aChar <= '9') { return static_cast<unsigned char>(aChar) - '0' ; }
+ return static_cast<unsigned char>(aChar) - 'A' + 0xA ;
+}
+
+// This function must return the rank in kBase64Encoding of the
+// character provided.
+static unsigned char Base64To6Bits(char aBase64)
+{
+ if (aBase64 >= 'A' && aBase64 <= 'Z') {
+ return static_cast<unsigned char>(aBase64 - 'A' + kARank) ;
+ }
+ if (aBase64 >= 'a' && aBase64 <= 'z') {
+ return static_cast<unsigned char>(aBase64 - 'a' + kaRank) ;
+ }
+ if (aBase64 >= '0' && aBase64 <= '9') {
+ return static_cast<unsigned char>(aBase64 - '0' + k0Rank) ;
+ }
+ if (aBase64 == '-') { return kMinusRank ; }
+ if (aBase64 == '.') { return kDotRank ; }
+ return 0 ;
+}
+
+static void Base64ToUnsigned(const char *& aBase64, uint32_t aNbBase64,
+ unsigned char *&aUnsigned)
+{
+ // By design of the encoding, we must have at least two characters to use
+ if (aNbBase64 > 1) {
+ unsigned char first6Bits = Base64To6Bits(*aBase64 ++) ;
+ unsigned char second6Bits = Base64To6Bits(*aBase64 ++) ;
+
+ *aUnsigned = first6Bits << 2 ;
+ *aUnsigned |= second6Bits >> 4 ;
+ ++ aUnsigned ;
+ if (aNbBase64 > 2) {
+ unsigned char third6Bits = Base64To6Bits(*aBase64 ++) ;
+
+ *aUnsigned = second6Bits << 4 ;
+ *aUnsigned |= third6Bits >> 2 ;
+ ++ aUnsigned ;
+ if (aNbBase64 > 3) {
+ unsigned char fourth6Bits = Base64To6Bits(*aBase64 ++) ;
+
+ *aUnsigned = third6Bits << 6 ;
+ *aUnsigned |= fourth6Bits ;
+ ++ aUnsigned ;
+ }
+ }
+ }
+}
+
+void nsMapiEntry::Assign(const nsCString& aString)
+{
+ Assign(0, NULL) ;
+ ULONG byteCount = aString.Length() / 4 * 3 ;
+
+ if ((aString.Length() & 0x03) != 0) {
+ byteCount += (aString.Length() & 0x03) - 1 ;
+ }
+ const char *currentSource = aString.get() ;
+ unsigned char *currentTarget = new unsigned char [byteCount] ;
+ uint32_t i = 0 ;
+
+ mByteCount = byteCount ;
+ mEntryId = reinterpret_cast<LPENTRYID>(currentTarget) ;
+ for (i = aString.Length() ; i >= 4 ; i -= 4) {
+ Base64ToUnsigned(currentSource, 4, currentTarget) ;
+ }
+ Base64ToUnsigned(currentSource, i, currentTarget) ;
+}
+
+void nsMapiEntry::ToString(nsCString& aString) const
+{
+ aString.Truncate() ;
+ ULONG i = 0 ;
+ unsigned char *currentSource = reinterpret_cast<unsigned char *>(mEntryId) ;
+
+ for (i = mByteCount ; i >= 3 ; i -= 3) {
+ UnsignedToBase64(currentSource, 3, aString) ;
+ }
+ UnsignedToBase64(currentSource, i, aString) ;
+}
+
+void nsMapiEntry::Dump(void) const
+{
+ PRINTF(("%d\n", mByteCount)) ;
+ for (ULONG i = 0 ; i < mByteCount ; ++ i) {
+ PRINTF(("%02X", (reinterpret_cast<unsigned char *>(mEntryId)) [i])) ;
+ }
+ PRINTF(("\n")) ;
+}
+
+nsMapiEntryArray::nsMapiEntryArray(void)
+: mEntries(NULL), mNbEntries(0)
+{
+ MOZ_COUNT_CTOR(nsMapiEntryArray) ;
+}
+
+nsMapiEntryArray::~nsMapiEntryArray(void)
+{
+ if (mEntries) { delete [] mEntries ; }
+ MOZ_COUNT_DTOR(nsMapiEntryArray) ;
+}
+
+void nsMapiEntryArray::CleanUp(void)
+{
+ if (mEntries != NULL) {
+ delete [] mEntries ;
+ mEntries = NULL ;
+ mNbEntries = 0 ;
+ }
+}
+
+using namespace mozilla;
+
+uint32_t nsAbWinHelper::mEntryCounter = 0;
+nsAutoPtr<mozilla::Mutex> nsAbWinHelper::mMutex;
+uint32_t nsAbWinHelper::mUseCount = 0;
+// There seems to be a deadlock/auto-destruction issue
+// in MAPI when multiple threads perform init/release
+// operations at the same time. So I've put a mutex
+// around both the initialize process and the destruction
+// one. I just hope the rest of the calls don't need the
+// same protection (MAPI is supposed to be thread-safe).
+
+nsAbWinHelper::nsAbWinHelper(void)
+: mAddressBook(NULL), mLastError(S_OK)
+{
+ if (!mUseCount++)
+ mMutex = new mozilla::Mutex("nsAbWinHelper.mMutex");
+
+ MOZ_COUNT_CTOR(nsAbWinHelper);
+}
+
+nsAbWinHelper::~nsAbWinHelper(void)
+{
+ if (!--mUseCount)
+ mMutex = nullptr;
+ MOZ_COUNT_DTOR(nsAbWinHelper) ;
+}
+
+BOOL nsAbWinHelper::GetFolders(nsMapiEntryArray& aFolders)
+{
+ aFolders.CleanUp() ;
+ nsMapiInterfaceWrapper<LPABCONT> rootFolder ;
+ nsMapiInterfaceWrapper<LPMAPITABLE> folders ;
+ ULONG objType = 0 ;
+ ULONG rowCount = 0 ;
+ SRestriction restriction ;
+ SPropTagArray folderColumns ;
+
+ mLastError = mAddressBook->OpenEntry(0, NULL, NULL, 0, &objType,
+ rootFolder) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open root %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = rootFolder->GetHierarchyTable(0, folders) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get hierarchy %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ // We only take into account modifiable containers,
+ // otherwise, we end up with all the directory services...
+ restriction.rt = RES_BITMASK ;
+ restriction.res.resBitMask.ulPropTag = PR_CONTAINER_FLAGS ;
+ restriction.res.resBitMask.relBMR = BMR_NEZ ;
+ restriction.res.resBitMask.ulMask = AB_MODIFIABLE ;
+ mLastError = folders->Restrict(&restriction, 0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot restrict table %08x.\n", mLastError)) ;
+ }
+ folderColumns.cValues = 1 ;
+ folderColumns.aulPropTag [0] = PR_ENTRYID ;
+ mLastError = folders->SetColumns(&folderColumns, 0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set columns %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = folders->GetRowCount(0, &rowCount) ;
+ if (HR_SUCCEEDED(mLastError)) {
+ aFolders.mEntries = new nsMapiEntry [rowCount] ;
+ aFolders.mNbEntries = 0 ;
+ do {
+ LPSRowSet rowSet = NULL ;
+
+ rowCount = 0 ;
+ mLastError = folders->QueryRows(1, 0, &rowSet) ;
+ if (HR_SUCCEEDED(mLastError)) {
+ rowCount = rowSet->cRows ;
+ if (rowCount > 0) {
+ nsMapiEntry& current = aFolders.mEntries [aFolders.mNbEntries ++] ;
+ SPropValue& currentValue = rowSet->aRow->lpProps [0] ;
+
+ current.Assign(currentValue.Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb)) ;
+ }
+ MyFreeProws(rowSet) ;
+ }
+ else {
+ PRINTF(("Cannot query rows %08x.\n", mLastError)) ;
+ }
+ } while (rowCount > 0) ;
+ }
+ return HR_SUCCEEDED(mLastError) ;
+}
+
+BOOL nsAbWinHelper::GetCards(const nsMapiEntry& aParent, LPSRestriction aRestriction,
+ nsMapiEntryArray& aCards)
+{
+ aCards.CleanUp() ;
+ return GetContents(aParent, aRestriction, &aCards.mEntries, aCards.mNbEntries, 0) ;
+}
+
+BOOL nsAbWinHelper::GetNodes(const nsMapiEntry& aParent, nsMapiEntryArray& aNodes)
+{
+ aNodes.CleanUp() ;
+ return GetContents(aParent, NULL, &aNodes.mEntries, aNodes.mNbEntries, MAPI_DISTLIST) ;
+}
+
+BOOL nsAbWinHelper::GetCardsCount(const nsMapiEntry& aParent, ULONG& aNbCards)
+{
+ aNbCards = 0 ;
+ return GetContents(aParent, NULL, NULL, aNbCards, 0) ;
+}
+
+BOOL nsAbWinHelper::GetPropertyString(const nsMapiEntry& aObject,
+ ULONG aPropertyTag,
+ nsCString& aName)
+{
+ aName.Truncate() ;
+ LPSPropValue values = NULL ;
+ ULONG valueCount = 0 ;
+
+ if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; }
+ if (valueCount == 1 && values != NULL) {
+ if (PROP_TYPE(values->ulPropTag) == PT_STRING8)
+ aName = values->Value.lpszA ;
+ else if (PROP_TYPE(values->ulPropTag) == PT_UNICODE)
+ aName = NS_LossyConvertUTF16toASCII(values->Value.lpszW);
+ }
+ FreeBuffer(values) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ nsString& aName)
+{
+ aName.Truncate() ;
+ LPSPropValue values = NULL ;
+ ULONG valueCount = 0 ;
+
+ if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; }
+ if (valueCount == 1 && values != NULL) {
+ if (PROP_TYPE(values->ulPropTag) == PT_UNICODE)
+ aName = values->Value.lpszW ;
+ else if (PROP_TYPE(values->ulPropTag) == PT_STRING8)
+ aName.AssignASCII(values->Value.lpszA);
+ }
+ FreeBuffer(values) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertyTags,
+ ULONG aNbProperties, nsString *aNames)
+{
+ LPSPropValue values = NULL;
+ ULONG valueCount = 0;
+
+ if (!GetMAPIProperties(aObject, aPropertyTags, aNbProperties, values,
+ valueCount))
+ return FALSE;
+
+ if (valueCount == aNbProperties && values != NULL)
+ {
+ for (ULONG i = 0 ; i < valueCount ; ++ i)
+ {
+ if (PROP_ID(values[i].ulPropTag) == PROP_ID(aPropertyTags[i]))
+ {
+ if (PROP_TYPE(values[i].ulPropTag) == PT_STRING8)
+ aNames[i].AssignASCII(values[i].Value.lpszA);
+ else if (PROP_TYPE(values[i].ulPropTag) == PT_UNICODE)
+ aNames[i] = values[i].Value.lpszW;
+ }
+ }
+ FreeBuffer(values);
+ }
+ return TRUE;
+}
+
+BOOL nsAbWinHelper::GetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ WORD& aYear, WORD& aMonth, WORD& aDay)
+{
+ aYear = 0 ;
+ aMonth = 0 ;
+ aDay = 0 ;
+ LPSPropValue values = NULL ;
+ ULONG valueCount = 0 ;
+
+ if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; }
+ if (valueCount == 1 && values != NULL && PROP_TYPE(values->ulPropTag) == PT_SYSTIME) {
+ SYSTEMTIME readableTime ;
+
+ if (FileTimeToSystemTime(&values->Value.ft, &readableTime)) {
+ aYear = readableTime.wYear ;
+ aMonth = readableTime.wMonth ;
+ aDay = readableTime.wDay ;
+ }
+ }
+ FreeBuffer(values) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetPropertyLong(const nsMapiEntry& aObject,
+ ULONG aPropertyTag,
+ ULONG& aValue)
+{
+ aValue = 0 ;
+ LPSPropValue values = NULL ;
+ ULONG valueCount = 0 ;
+
+ if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; }
+ if (valueCount == 1 && values != NULL && PROP_TYPE(values->ulPropTag) == PT_LONG) {
+ aValue = values->Value.ul ;
+ }
+ FreeBuffer(values) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetPropertyBin(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ nsMapiEntry& aValue)
+{
+ aValue.Assign(0, NULL) ;
+ LPSPropValue values = NULL ;
+ ULONG valueCount = 0 ;
+
+ if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; }
+ if (valueCount == 1 && values != NULL && PROP_TYPE(values->ulPropTag) == PT_BINARY) {
+ aValue.Assign(values->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(values->Value.bin.lpb)) ;
+ }
+ FreeBuffer(values) ;
+ return TRUE ;
+}
+
+// This function, supposedly indicating whether a particular entry was
+// in a particular container, doesn't seem to work very well (has
+// a tendency to return TRUE even if we're talking to different containers...).
+BOOL nsAbWinHelper::TestOpenEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry)
+{
+ nsMapiInterfaceWrapper<LPMAPICONTAINER> container ;
+ nsMapiInterfaceWrapper<LPMAPIPROP> subObject ;
+ ULONG objType = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId,
+ &IID_IMAPIContainer, 0, &objType,
+ container) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = container->OpenEntry(aEntry.mByteCount, aEntry.mEntryId,
+ NULL, 0, &objType, subObject) ;
+ return HR_SUCCEEDED(mLastError) ;
+}
+
+BOOL nsAbWinHelper::DeleteEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry)
+{
+ nsMapiInterfaceWrapper<LPABCONT> container ;
+ ULONG objType = 0 ;
+ SBinary entry ;
+ SBinaryArray entryArray ;
+
+ mLastError = mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId,
+ &IID_IABContainer, MAPI_MODIFY, &objType,
+ container) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ entry.cb = aEntry.mByteCount ;
+ entry.lpb = reinterpret_cast<LPBYTE>(aEntry.mEntryId) ;
+ entryArray.cValues = 1 ;
+ entryArray.lpbin = &entry ;
+ mLastError = container->DeleteEntries(&entryArray, 0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot delete entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::SetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ const char16_t *aValue)
+{
+ SPropValue value ;
+ nsAutoCString alternativeValue ;
+
+ value.ulPropTag = aPropertyTag ;
+ if (PROP_TYPE(aPropertyTag) == PT_UNICODE) {
+ value.Value.lpszW = wwc(const_cast<char16_t *>(aValue)) ;
+ }
+ else if (PROP_TYPE(aPropertyTag) == PT_STRING8) {
+ alternativeValue = NS_LossyConvertUTF16toASCII(aValue);
+ value.Value.lpszA = const_cast<char *>(alternativeValue.get()) ;
+ }
+ else {
+ PRINTF(("Property %08x is not a string.\n", aPropertyTag)) ;
+ return TRUE ;
+ }
+ return SetMAPIProperties(aObject, 1, &value) ;
+}
+
+BOOL nsAbWinHelper::SetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertiesTag,
+ ULONG aNbProperties, nsString *aValues)
+{
+ LPSPropValue values = new SPropValue [aNbProperties] ;
+ if (!values)
+ return FALSE ;
+
+ ULONG i = 0 ;
+ ULONG currentValue = 0 ;
+ nsAutoCString alternativeValue ;
+ BOOL retCode = TRUE ;
+
+ for (i = 0 ; i < aNbProperties ; ++ i) {
+ values [currentValue].ulPropTag = aPropertiesTag [i] ;
+ if (PROP_TYPE(aPropertiesTag [i]) == PT_UNICODE) {
+ const wchar_t *value = aValues [i].get() ;
+ values [currentValue ++].Value.lpszW = const_cast<wchar_t *>(value) ;
+ }
+ else if (PROP_TYPE(aPropertiesTag [i]) == PT_STRING8) {
+ LossyCopyUTF16toASCII(aValues [i], alternativeValue);
+ char *av = strdup(alternativeValue.get()) ;
+ if (!av) {
+ retCode = FALSE ;
+ break ;
+ }
+ values [currentValue ++].Value.lpszA = av ;
+ }
+ }
+ if (retCode)
+ retCode = SetMAPIProperties(aObject, currentValue, values) ;
+ for (i = 0 ; i < currentValue ; ++ i) {
+ if (PROP_TYPE(aPropertiesTag [i]) == PT_STRING8) {
+ NS_Free(values [i].Value.lpszA) ;
+ }
+ }
+ delete [] values ;
+ return retCode ;
+}
+
+BOOL nsAbWinHelper::SetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ WORD aYear, WORD aMonth, WORD aDay)
+{
+ SPropValue value ;
+
+ value.ulPropTag = aPropertyTag ;
+ if (PROP_TYPE(aPropertyTag) == PT_SYSTIME) {
+ SYSTEMTIME readableTime ;
+
+ readableTime.wYear = aYear ;
+ readableTime.wMonth = aMonth ;
+ readableTime.wDay = aDay ;
+ readableTime.wDayOfWeek = 0 ;
+ readableTime.wHour = 0 ;
+ readableTime.wMinute = 0 ;
+ readableTime.wSecond = 0 ;
+ readableTime.wMilliseconds = 0 ;
+ if (SystemTimeToFileTime(&readableTime, &value.Value.ft)) {
+ return SetMAPIProperties(aObject, 1, &value) ;
+ }
+ return TRUE ;
+ }
+ return FALSE ;
+}
+
+BOOL nsAbWinHelper::CreateEntry(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry)
+{
+ nsMapiInterfaceWrapper<LPABCONT> container ;
+ ULONG objType = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId,
+ &IID_IABContainer, MAPI_MODIFY, &objType,
+ container) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ SPropTagArray property ;
+ LPSPropValue value = NULL ;
+ ULONG valueCount = 0 ;
+
+ property.cValues = 1 ;
+ property.aulPropTag [0] = PR_DEF_CREATE_MAILUSER ;
+ mLastError = container->GetProps(&property, 0, &valueCount, &value) ;
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot obtain template %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ nsMapiInterfaceWrapper<LPMAPIPROP> newEntry ;
+
+ mLastError = container->CreateEntry(value->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(value->Value.bin.lpb),
+ CREATE_CHECK_DUP_LOOSE,
+ newEntry) ;
+ FreeBuffer(value) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot create new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ SPropValue displayName ;
+ LPSPropProblemArray problems = NULL ;
+ nsAutoString tempName ;
+
+ displayName.ulPropTag = PR_DISPLAY_NAME_W ;
+ tempName.AssignLiteral("__MailUser__") ;
+ tempName.AppendInt(mEntryCounter ++) ;
+ const wchar_t *tempNameValue = tempName.get();
+ displayName.Value.lpszW = const_cast<wchar_t *>(tempNameValue) ;
+ mLastError = newEntry->SetProps(1, &displayName, &problems) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set temporary name %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ property.aulPropTag [0] = PR_ENTRYID ;
+ mLastError = newEntry->GetProps(&property, 0, &valueCount, &value) ;
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot get entry id %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ aNewEntry.Assign(value->Value.bin.cb, reinterpret_cast<LPENTRYID>(value->Value.bin.lpb)) ;
+ FreeBuffer(value) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::CreateDistList(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry)
+{
+ nsMapiInterfaceWrapper<LPABCONT> container ;
+ ULONG objType = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId,
+ &IID_IABContainer, MAPI_MODIFY, &objType,
+ container) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ SPropTagArray property ;
+ LPSPropValue value = NULL ;
+ ULONG valueCount = 0 ;
+
+ property.cValues = 1 ;
+ property.aulPropTag [0] = PR_DEF_CREATE_DL ;
+ mLastError = container->GetProps(&property, 0, &valueCount, &value) ;
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot obtain template %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ nsMapiInterfaceWrapper<LPMAPIPROP> newEntry ;
+
+ mLastError = container->CreateEntry(value->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(value->Value.bin.lpb),
+ CREATE_CHECK_DUP_LOOSE,
+ newEntry) ;
+ FreeBuffer(value) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot create new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ SPropValue displayName ;
+ LPSPropProblemArray problems = NULL ;
+ nsAutoString tempName ;
+
+ displayName.ulPropTag = PR_DISPLAY_NAME_W ;
+ tempName.AssignLiteral("__MailList__") ;
+ tempName.AppendInt(mEntryCounter ++) ;
+ const wchar_t *tempNameValue = tempName.get() ;
+ displayName.Value.lpszW = const_cast<wchar_t *>(tempNameValue) ;
+ mLastError = newEntry->SetProps(1, &displayName, &problems) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set temporary name %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ property.aulPropTag [0] = PR_ENTRYID ;
+ mLastError = newEntry->GetProps(&property, 0, &valueCount, &value) ;
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot get entry id %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ aNewEntry.Assign(value->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(value->Value.bin.lpb)) ;
+ FreeBuffer(value) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::CopyEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aSource,
+ nsMapiEntry& aTarget)
+{
+ nsMapiInterfaceWrapper<LPABCONT> container ;
+ ULONG objType = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId,
+ &IID_IABContainer, MAPI_MODIFY, &objType,
+ container) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ nsMapiInterfaceWrapper<LPMAPIPROP> newEntry ;
+
+ mLastError = container->CreateEntry(aSource.mByteCount, aSource.mEntryId,
+ CREATE_CHECK_DUP_LOOSE, newEntry) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot create new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit new entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ SPropTagArray property ;
+ LPSPropValue value = NULL ;
+ ULONG valueCount = 0 ;
+
+ property.cValues = 1 ;
+ property.aulPropTag [0] = PR_ENTRYID ;
+ mLastError = newEntry->GetProps(&property, 0, &valueCount, &value) ;
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot get entry id %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ aTarget.Assign(value->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(value->Value.bin.lpb)) ;
+ FreeBuffer(value) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetDefaultContainer(nsMapiEntry& aContainer)
+{
+ LPENTRYID entryId = NULL ;
+ ULONG byteCount = 0 ;
+
+ mLastError = mAddressBook->GetPAB(&byteCount, &entryId) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get PAB %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ aContainer.Assign(byteCount, entryId) ;
+ FreeBuffer(entryId) ;
+ return TRUE ;
+}
+
+enum
+{
+ ContentsColumnEntryId = 0,
+ ContentsColumnObjectType,
+ ContentsColumnsSize
+} ;
+
+static const SizedSPropTagArray(ContentsColumnsSize, ContentsColumns) =
+{
+ ContentsColumnsSize,
+ {
+ PR_ENTRYID,
+ PR_OBJECT_TYPE
+ }
+} ;
+
+BOOL nsAbWinHelper::GetContents(const nsMapiEntry& aParent, LPSRestriction aRestriction,
+ nsMapiEntry **aList, ULONG& aNbElements, ULONG aMapiType)
+{
+ if (aList != NULL) { *aList = NULL ; }
+ aNbElements = 0 ;
+ nsMapiInterfaceWrapper<LPMAPICONTAINER> parent ;
+ nsMapiInterfaceWrapper<LPMAPITABLE> contents ;
+ ULONG objType = 0 ;
+ ULONG rowCount = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId,
+ &IID_IMAPIContainer, 0, &objType,
+ parent) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open parent %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ // Here, flags for WAB and MAPI could be different, so this works
+ // only as long as we don't want to use any flag in GetContentsTable
+ mLastError = parent->GetContentsTable(0, contents) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get contents %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ if (aRestriction != NULL) {
+ mLastError = contents->Restrict(aRestriction, 0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set restriction %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ }
+ mLastError = contents->SetColumns((LPSPropTagArray) &ContentsColumns, 0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set columns %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = contents->GetRowCount(0, &rowCount) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get result count %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ if (aList != NULL) { *aList = new nsMapiEntry [rowCount] ; }
+ aNbElements = 0 ;
+ do {
+ LPSRowSet rowSet = NULL ;
+
+ rowCount = 0 ;
+ mLastError = contents->QueryRows(1, 0, &rowSet) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot query rows %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ rowCount = rowSet->cRows ;
+ if (rowCount > 0 &&
+ (aMapiType == 0 ||
+ rowSet->aRow->lpProps[ContentsColumnObjectType].Value.ul == aMapiType)) {
+ if (aList != NULL) {
+ nsMapiEntry& current = (*aList) [aNbElements] ;
+ SPropValue& currentValue = rowSet->aRow->lpProps[ContentsColumnEntryId] ;
+
+ current.Assign(currentValue.Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb)) ;
+ }
+ ++ aNbElements ;
+ }
+ MyFreeProws(rowSet) ;
+ } while (rowCount > 0) ;
+ return TRUE ;
+}
+
+BOOL nsAbWinHelper::GetMAPIProperties(const nsMapiEntry& aObject, const ULONG *aPropertyTags,
+ ULONG aNbProperties, LPSPropValue& aValue,
+ ULONG& aValueCount)
+{
+ nsMapiInterfaceWrapper<LPMAPIPROP> object ;
+ ULONG objType = 0 ;
+ LPSPropTagArray properties = NULL ;
+ ULONG i = 0 ;
+
+ mLastError = mAddressBook->OpenEntry(aObject.mByteCount, aObject.mEntryId,
+ &IID_IMAPIProp, 0, &objType,
+ object) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ AllocateBuffer(CbNewSPropTagArray(aNbProperties),
+ reinterpret_cast<void **>(&properties)) ;
+ properties->cValues = aNbProperties ;
+ for (i = 0 ; i < aNbProperties ; ++ i) {
+ properties->aulPropTag [i] = aPropertyTags [i] ;
+ }
+ mLastError = object->GetProps(properties, 0, &aValueCount, &aValue) ;
+ FreeBuffer(properties) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get props %08x.\n", mLastError)) ;
+ }
+ return HR_SUCCEEDED(mLastError) ;
+}
+
+BOOL nsAbWinHelper::SetMAPIProperties(const nsMapiEntry& aObject, ULONG aNbProperties,
+ const LPSPropValue& aValues)
+{
+ nsMapiInterfaceWrapper<LPMAPIPROP> object ;
+ ULONG objType = 0 ;
+ LPSPropProblemArray problems = NULL ;
+
+ mLastError = mAddressBook->OpenEntry(aObject.mByteCount, aObject.mEntryId,
+ &IID_IMAPIProp, MAPI_MODIFY, &objType,
+ object) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open entry %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ mLastError = object->SetProps(aNbProperties, aValues, &problems) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot update the object %08x.\n", mLastError)) ;
+ return FALSE ;
+ }
+ if (problems != NULL) {
+ for (ULONG i = 0 ; i < problems->cProblem ; ++ i) {
+ PRINTF(("Problem %d: index %d code %08x.\n", i,
+ problems->aProblem [i].ulIndex,
+ problems->aProblem [i].scode)) ;
+ }
+ }
+ mLastError = object->SaveChanges(0) ;
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit changes %08x.\n", mLastError)) ;
+ }
+ return HR_SUCCEEDED(mLastError) ;
+}
+
+void nsAbWinHelper::MyFreeProws(LPSRowSet aRowset)
+{
+ if (aRowset == NULL) { return ; }
+ ULONG i = 0 ;
+
+ for (i = 0 ; i < aRowset->cRows ; ++ i) {
+ FreeBuffer(aRowset->aRow [i].lpProps) ;
+ }
+ FreeBuffer(aRowset) ;
+}
+
+nsAbWinHelperGuard::nsAbWinHelperGuard(uint32_t aType)
+: mHelper(NULL)
+{
+ switch(aType) {
+ case nsAbWinType_Outlook: mHelper = new nsMapiAddressBook ; break ;
+ case nsAbWinType_OutlookExp: mHelper = new nsWabAddressBook ; break ;
+ default: break ;
+ }
+}
+
+nsAbWinHelperGuard::~nsAbWinHelperGuard(void)
+{
+ delete mHelper ;
+}
+
+const char *kOutlookDirectoryScheme = "moz-aboutlookdirectory://" ;
+const int kOutlookDirSchemeLength = 21 ;
+const char *kOutlookStub = "op/" ;
+const int kOutlookStubLength = 3 ;
+const char *kOutlookExpStub = "oe/" ;
+const int kOutlookExpStubLength = 3 ;
+const char *kOutlookCardScheme = "moz-aboutlookcard://" ;
+const int kOutlookCardSchemeLength = 16 ;
+
+nsAbWinType getAbWinType(const char *aScheme, const char *aUri, nsCString& aStub, nsCString& aEntry)
+{
+ aStub.Truncate() ;
+ aEntry.Truncate() ;
+ uint32_t schemeLength = strlen(aScheme) ;
+
+ if (strncmp(aUri, aScheme, schemeLength) == 0) {
+ if (strncmp(aUri + schemeLength, kOutlookStub, kOutlookStubLength) == 0) {
+ aEntry = aUri + schemeLength + kOutlookStubLength ;
+ aStub = kOutlookStub ;
+ return nsAbWinType_Outlook ;
+ }
+ if (strncmp(aUri + schemeLength, kOutlookExpStub, kOutlookExpStubLength) == 0) {
+ aEntry = aUri + schemeLength + kOutlookExpStubLength ;
+ aStub = kOutlookExpStub ;
+ return nsAbWinType_OutlookExp ;
+ }
+ }
+ return nsAbWinType_Unknown ;
+}
+
+void buildAbWinUri(const char *aScheme, uint32_t aType, nsCString& aUri)
+{
+ aUri.Assign(aScheme) ;
+ switch(aType) {
+ case nsAbWinType_Outlook: aUri.Append(kOutlookStub) ; break ;
+ case nsAbWinType_OutlookExp: aUri.Append(kOutlookExpStub) ; break ;
+ default: aUri.Assign("") ;
+ }
+}
+
+
+
+
+
+
diff --git a/mailnews/addrbook/src/nsAbWinHelper.h b/mailnews/addrbook/src/nsAbWinHelper.h
new file mode 100644
index 000000000..fb0ad7e44
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbWinHelper.h
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsAbWinHelper_h___
+#define nsAbWinHelper_h___
+
+#include <windows.h>
+#include <mapix.h>
+
+#include "nsStringGlue.h"
+#include "mozilla/Mutex.h"
+#include "nsAutoPtr.h"
+
+struct nsMapiEntry
+{
+ ULONG mByteCount ;
+ LPENTRYID mEntryId ;
+
+ nsMapiEntry(void) ;
+ ~nsMapiEntry(void) ;
+ nsMapiEntry(ULONG aByteCount, LPENTRYID aEntryId) ;
+
+ void Assign(ULONG aByteCount, LPENTRYID aEntryId) ;
+ void Assign(const nsCString& aString) ;
+ void ToString(nsCString& aString) const ;
+ void Dump(void) const ;
+} ;
+
+struct nsMapiEntryArray
+{
+ nsMapiEntry *mEntries ;
+ ULONG mNbEntries ;
+
+ nsMapiEntryArray(void) ;
+ ~nsMapiEntryArray(void) ;
+
+ const nsMapiEntry& operator [] (int aIndex) const { return mEntries [aIndex] ; }
+ void CleanUp(void) ;
+} ;
+
+class nsAbWinHelper
+{
+public:
+ nsAbWinHelper(void) ;
+ virtual ~nsAbWinHelper(void) ;
+
+ // Get the top address books
+ BOOL GetFolders(nsMapiEntryArray& aFolders) ;
+ // Get a list of entries for cards/mailing lists in a folder/mailing list
+ BOOL GetCards(const nsMapiEntry& aParent, LPSRestriction aRestriction,
+ nsMapiEntryArray& aCards) ;
+ // Get a list of mailing lists in a folder
+ BOOL GetNodes(const nsMapiEntry& aParent, nsMapiEntryArray& aNodes) ;
+ // Get the number of cards/mailing lists in a folder/mailing list
+ BOOL GetCardsCount(const nsMapiEntry& aParent, ULONG& aNbCards) ;
+ // Access last MAPI error
+ HRESULT LastError(void) const { return mLastError ; }
+ // Get the value of a MAPI property of type string
+ BOOL GetPropertyString(const nsMapiEntry& aObject, ULONG aPropertyTag, nsCString& aValue) ;
+ // Same as previous, but string is returned as unicode
+ BOOL GetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag, nsString& aValue) ;
+ // Get multiple string MAPI properties in one call.
+ BOOL GetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertiesTag,
+ ULONG aNbProperties, nsString *aValues);
+ // Get the value of a MAPI property of type SYSTIME
+ BOOL GetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ WORD& aYear, WORD& aMonth, WORD& aDay) ;
+ // Get the value of a MAPI property of type LONG
+ BOOL GetPropertyLong(const nsMapiEntry& aObject, ULONG aPropertyTag, ULONG& aValue) ;
+ // Get the value of a MAPI property of type BIN
+ BOOL GetPropertyBin(const nsMapiEntry& aObject, ULONG aPropertyTag, nsMapiEntry& aValue) ;
+ // Tests if a container contains an entry
+ BOOL TestOpenEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry) ;
+ // Delete an entry in the address book
+ BOOL DeleteEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry) ;
+ // Set the value of a MAPI property of type string in unicode
+ BOOL SetPropertyUString (const nsMapiEntry& aObject, ULONG aPropertyTag,
+ const char16_t *aValue) ;
+ // Same as previous, but with a bunch of properties in one call
+ BOOL SetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertiesTag,
+ ULONG aNbProperties, nsString *aValues) ;
+ // Set the value of a MAPI property of type SYSTIME
+ BOOL SetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ WORD aYear, WORD aMonth, WORD aDay) ;
+ // Create entry in the address book
+ BOOL CreateEntry(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry) ;
+ // Create a distribution list in the address book
+ BOOL CreateDistList(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry) ;
+ // Copy an existing entry in the address book
+ BOOL CopyEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aSource, nsMapiEntry& aTarget) ;
+ // Get a default address book container
+ BOOL GetDefaultContainer(nsMapiEntry& aContainer) ;
+ // Is the helper correctly initialised?
+ BOOL IsOK(void) const { return mAddressBook != NULL ; }
+
+protected:
+ HRESULT mLastError ;
+ LPADRBOOK mAddressBook ;
+ static uint32_t mEntryCounter ;
+ static uint32_t mUseCount ;
+ static nsAutoPtr<mozilla::Mutex> mMutex ;
+
+ // Retrieve the contents of a container, with an optional restriction
+ BOOL GetContents(const nsMapiEntry& aParent, LPSRestriction aRestriction,
+ nsMapiEntry **aList, ULONG &aNbElements, ULONG aMapiType) ;
+ // Retrieve the values of a set of properties on a MAPI object
+ BOOL GetMAPIProperties(const nsMapiEntry& aObject, const ULONG *aPropertyTags,
+ ULONG aNbProperties,
+ LPSPropValue& aValues, ULONG& aValueCount) ;
+ // Set the values of a set of properties on a MAPI object
+ BOOL SetMAPIProperties(const nsMapiEntry& aObject, ULONG aNbProperties,
+ const LPSPropValue& aValues) ;
+ // Clean-up a rowset returned by QueryRows
+ void MyFreeProws(LPSRowSet aSet) ;
+ // Allocation of a buffer for transmission to interfaces
+ virtual void AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) = 0 ;
+ // Destruction of a buffer provided by the interfaces
+ virtual void FreeBuffer(LPVOID aBuffer) = 0 ;
+
+private:
+} ;
+
+enum nsAbWinType
+{
+ nsAbWinType_Unknown,
+ nsAbWinType_Outlook,
+ nsAbWinType_OutlookExp
+} ;
+
+class nsAbWinHelperGuard
+{
+public :
+ nsAbWinHelperGuard(uint32_t aType) ;
+ ~nsAbWinHelperGuard(void) ;
+
+ nsAbWinHelper *operator ->(void) { return mHelper ; }
+
+private:
+ nsAbWinHelper *mHelper ;
+} ;
+
+extern const char *kOutlookDirectoryScheme ;
+extern const int kOutlookDirSchemeLength ;
+extern const char *kOutlookStub ;
+extern const char *kOutlookExpStub ;
+extern const char *kOutlookCardScheme ;
+
+nsAbWinType getAbWinType(const char *aScheme, const char *aUri,
+ nsCString& aStub, nsCString& aEntry) ;
+void buildAbWinUri(const char *aScheme, uint32_t aType, nsCString& aUri) ;
+
+#endif // nsAbWinHelper_h___
+
+
+
diff --git a/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp b/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp
new file mode 100644
index 000000000..8681ae4a6
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp
@@ -0,0 +1,327 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "msgCore.h" // precompiled header...
+#include "nsStringGlue.h"
+
+#include "nsAddbookProtocolHandler.h"
+
+#include "nsAddbookUrl.h"
+#include "nsAddbookProtocolHandler.h"
+#include "nsCOMPtr.h"
+#include "nsAbBaseCID.h"
+#include "nsNetUtil.h"
+#include "nsStringStream.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbManager.h"
+#include "prmem.h"
+#include "nsIAbView.h"
+#include "nsITreeView.h"
+#include "nsIStringBundle.h"
+#include "mozilla/Services.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIPipe.h"
+#include "nsIPrincipal.h"
+
+nsAddbookProtocolHandler::nsAddbookProtocolHandler()
+{
+ mAddbookOperation = nsIAddbookUrlOperation::InvalidUrl;
+}
+
+nsAddbookProtocolHandler::~nsAddbookProtocolHandler()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsAddbookProtocolHandler, nsIProtocolHandler)
+
+NS_IMETHODIMP nsAddbookProtocolHandler::GetScheme(nsACString &aScheme)
+{
+ aScheme = "addbook";
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddbookProtocolHandler::GetDefaultPort(int32_t *aDefaultPort)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddbookProtocolHandler::GetProtocolFlags(uint32_t *aUritype)
+{
+ *aUritype = URI_STD | URI_LOADABLE_BY_ANYONE | URI_FORBIDS_COOKIE_ACCESS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddbookProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aOriginCharset, // ignored
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ nsresult rv;
+ nsCOMPtr <nsIAddbookUrl> addbookUrl = do_CreateInstance(NS_ADDBOOKURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = addbookUrl->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIURI> uri = do_QueryInterface(addbookUrl, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ADDREF(*_retval = uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAddbookProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+nsresult
+nsAddbookProtocolHandler::GenerateXMLOutputChannel( nsString &aOutput,
+ nsIAddbookUrl *addbookUrl,
+ nsIURI *aURI,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringInputStream> inStr(do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF16toUTF8 utf8String(aOutput.get());
+
+ rv = inStr->SetData(utf8String.get(), utf8String.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLoadInfo) {
+ return NS_NewInputStreamChannelInternal(_retval,
+ aURI,
+ inStr,
+ NS_LITERAL_CSTRING("text/xml"),
+ EmptyCString(),
+ aLoadInfo);
+ }
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipalfailed.");
+ if (NS_FAILED(rv))
+ return rv;
+
+ return NS_NewInputStreamChannel(_retval,
+ aURI,
+ inStr,
+ nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ NS_LITERAL_CSTRING("text/xml"));
+}
+
+NS_IMETHODIMP
+nsAddbookProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+NS_IMETHODIMP
+nsAddbookProtocolHandler::NewChannel2(nsIURI *aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel **_retval)
+{
+ nsresult rv;
+ nsCOMPtr <nsIAddbookUrl> addbookUrl = do_QueryInterface(aURI, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = addbookUrl->GetAddbookOperation(&mAddbookOperation);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (mAddbookOperation == nsIAddbookUrlOperation::InvalidUrl) {
+ nsAutoString errorString;
+ errorString.AssignLiteral("Unsupported format/operation requested for ");
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ errorString.Append(NS_ConvertUTF8toUTF16(spec));
+ rv = GenerateXMLOutputChannel(errorString, addbookUrl, aURI, aLoadInfo, _retval);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+ }
+
+ if (mAddbookOperation == nsIAddbookUrlOperation::AddVCard) {
+ // create an empty pipe for use with the input stream channel.
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+
+ rv = pipe->Init(false, false, 0, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // These always succeed because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(pipeIn)));
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(pipeOut)));
+
+ pipeOut->Close();
+ if (aLoadInfo) {
+ return NS_NewInputStreamChannelInternal(_retval,
+ aURI,
+ pipeIn,
+ NS_LITERAL_CSTRING("application/x-addvcard"),
+ EmptyCString(),
+ aLoadInfo);
+ }
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed.");
+ if (NS_FAILED(rv))
+ return rv;
+
+ return NS_NewInputStreamChannel(_retval,
+ aURI,
+ pipeIn,
+ nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ NS_LITERAL_CSTRING("application/x-addvcard"));
+ }
+
+ nsString output;
+ rv = GeneratePrintOutput(addbookUrl, output);
+ if (NS_FAILED(rv)) {
+ output.AssignLiteral("failed to print. url=");
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+ output.Append(NS_ConvertUTF8toUTF16(spec));
+ }
+
+ rv = GenerateXMLOutputChannel(output, addbookUrl, aURI, aLoadInfo, _retval);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+nsresult
+nsAddbookProtocolHandler::GeneratePrintOutput(nsIAddbookUrl *addbookUrl,
+ nsString &aOutput)
+{
+ NS_ENSURE_ARG_POINTER(addbookUrl);
+
+ nsAutoCString uri;
+ nsresult rv = addbookUrl->GetPath(uri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ /* turn
+ "//moz-abmdbdirectory/abook.mab?action=print"
+ into "moz-abmdbdirectory://abook.mab"
+ */
+
+ /* step 1:
+ turn "//moz-abmdbdirectory/abook.mab?action=print"
+ into "moz-abmdbdirectory/abook.mab?action=print"
+ */
+ if (uri[0] != '/' && uri[1] != '/')
+ return NS_ERROR_UNEXPECTED;
+
+ uri.Cut(0,2);
+
+ /* step 2:
+ turn "moz-abmdbdirectory/abook.mab?action=print"
+ into "moz-abmdbdirectory/abook.mab"
+ */
+ int32_t pos = uri.Find("?action=print");
+ if (pos == -1)
+ return NS_ERROR_UNEXPECTED;
+
+ uri.SetLength(pos);
+
+ /* step 2:
+ turn "moz-abmdbdirectory/abook.mab"
+ into "moz-abmdbdirectory://abook.mab"
+ */
+ pos = uri.FindChar('/');
+ if (pos == -1)
+ return NS_ERROR_UNEXPECTED;
+
+ uri.Insert('/', pos);
+ uri.Insert(':', pos);
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(uri, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = BuildDirectoryXML(directory, aOutput);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsAddbookProtocolHandler::BuildDirectoryXML(nsIAbDirectory *aDirectory,
+ nsString &aOutput)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ nsresult rv;
+ nsCOMPtr<nsISimpleEnumerator> cardsEnumerator;
+ nsCOMPtr<nsIAbCard> card;
+
+ aOutput.AppendLiteral("<?xml version=\"1.0\"?>\n"
+ "<?xml-stylesheet type=\"text/css\" href=\"chrome://messagebody/content/addressbook/print.css\"?>\n"
+ "<directory>\n");
+
+ // Get Address Book string and set it as title of XML document
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ if (stringBundleService) {
+ rv = stringBundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsString addrBook;
+ rv = bundle->GetStringFromName(u"addressBook", getter_Copies(addrBook));
+ if (NS_SUCCEEDED(rv)) {
+ aOutput.AppendLiteral("<title xmlns=\"http://www.w3.org/1999/xhtml\">");
+ aOutput.Append(addrBook);
+ aOutput.AppendLiteral("</title>\n");
+ }
+ }
+ }
+
+ // create a view and init it with the generated name sort order. Then, iterate
+ // over the view, getting the card for each row, and printing them.
+ nsString sortColumn;
+ nsCOMPtr <nsIAbView> view = do_CreateInstance("@mozilla.org/addressbook/abview;1", &rv);
+
+ view->SetView(aDirectory, nullptr, NS_LITERAL_STRING("GeneratedName"),
+ NS_LITERAL_STRING("ascending"), sortColumn);
+
+ int32_t numRows;
+ nsCOMPtr <nsITreeView> treeView = do_QueryInterface(view, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ treeView->GetRowCount(&numRows);
+
+ for (int32_t row = 0; row < numRows; row++)
+ {
+
+ nsCOMPtr <nsIAbCard> card;
+ view->GetCardFromRow(row, getter_AddRefs(card));
+ nsCString xmlSubstr;
+
+ rv = card->TranslateTo(NS_LITERAL_CSTRING("xml"), xmlSubstr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ aOutput.AppendLiteral("<separator/>");
+ aOutput.Append(NS_ConvertUTF8toUTF16(xmlSubstr));
+ aOutput.AppendLiteral("<separator/>");
+ }
+
+ aOutput.AppendLiteral("</directory>\n");
+
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAddbookProtocolHandler.h b/mailnews/addrbook/src/nsAddbookProtocolHandler.h
new file mode 100644
index 000000000..f14d54c4e
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddbookProtocolHandler.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAddbookProtocolHandler_h___
+#define nsAddbookProtocolHandler_h___
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsAddbookProtocolHandler.h"
+#include "nsIProtocolHandler.h"
+#include "nsIAddbookUrl.h"
+#include "nsIAbDirectory.h"
+
+class nsAddbookProtocolHandler : public nsIProtocolHandler
+{
+public:
+ nsAddbookProtocolHandler();
+
+ NS_DECL_ISUPPORTS
+
+ //////////////////////////////////////////////////////////////////////////
+ // We support the nsIProtocolHandler interface.
+ //////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIPROTOCOLHANDLER
+
+private:
+ virtual ~nsAddbookProtocolHandler();
+ nsresult GenerateXMLOutputChannel(nsString &aOutput,
+ nsIAddbookUrl *addbookUrl,
+ nsIURI *aURI,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel **_retval);
+
+ nsresult GeneratePrintOutput(nsIAddbookUrl *addbookUrl,
+ nsString &aOutput);
+
+ nsresult BuildDirectoryXML(nsIAbDirectory *aDirectory,
+ nsString &aOutput);
+
+ int32_t mAddbookOperation;
+};
+
+#endif /* nsAddbookProtocolHandler_h___ */
diff --git a/mailnews/addrbook/src/nsAddbookUrl.cpp b/mailnews/addrbook/src/nsAddbookUrl.cpp
new file mode 100644
index 000000000..5d8d1c266
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddbookUrl.cpp
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsAddbookUrl.h"
+#include "nsStringGlue.h"
+#include "nsAbBaseCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsAutoPtr.h"
+
+/////////////////////////////////////////////////////////////////////////////////////
+// addbook url definition
+/////////////////////////////////////////////////////////////////////////////////////
+nsAddbookUrl::nsAddbookUrl()
+{
+ m_baseURL = do_CreateInstance(NS_SIMPLEURI_CONTRACTID);
+
+ mOperationType = nsIAddbookUrlOperation::InvalidUrl;
+}
+
+nsAddbookUrl::~nsAddbookUrl()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsAddbookUrl, nsIAddbookUrl, nsIURI)
+
+NS_IMETHODIMP
+nsAddbookUrl::SetSpec(const nsACString &aSpec)
+{
+ nsresult rv = m_baseURL->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+nsresult nsAddbookUrl::ParseUrl()
+{
+ nsAutoCString pathStr;
+
+ nsresult rv = m_baseURL->GetPath(pathStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (strstr(pathStr.get(), "?action=print"))
+ mOperationType = nsIAddbookUrlOperation::PrintAddressBook;
+ else if (strstr(pathStr.get(), "?action=add"))
+ mOperationType = nsIAddbookUrlOperation::AddVCard;
+ else
+ mOperationType = nsIAddbookUrlOperation::InvalidUrl;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIURI support
+////////////////////////////////////////////////////////////////////////////////////
+
+
+NS_IMETHODIMP nsAddbookUrl::GetSpec(nsACString &aSpec)
+{
+ return m_baseURL->GetSpec(aSpec);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetPrePath(nsACString &aPrePath)
+{
+ return m_baseURL->GetPrePath(aPrePath);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetScheme(nsACString &aScheme)
+{
+ return m_baseURL->GetScheme(aScheme);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetScheme(const nsACString &aScheme)
+{
+ return m_baseURL->SetScheme(aScheme);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetUserPass(nsACString &aUserPass)
+{
+ return m_baseURL->GetUserPass(aUserPass);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetUserPass(const nsACString &aUserPass)
+{
+ return m_baseURL->SetUserPass(aUserPass);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetUsername(nsACString &aUsername)
+{
+ return m_baseURL->GetUsername(aUsername);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetUsername(const nsACString &aUsername)
+{
+ return m_baseURL->SetUsername(aUsername);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetPassword(nsACString &aPassword)
+{
+ return m_baseURL->GetPassword(aPassword);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetPassword(const nsACString &aPassword)
+{
+ return m_baseURL->SetPassword(aPassword);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetHostPort(nsACString &aHostPort)
+{
+ return m_baseURL->GetHostPort(aHostPort);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetHostPort(const nsACString &aHostPort)
+{
+ return m_baseURL->SetHostPort(aHostPort);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetHostAndPort(const nsACString &aHostPort)
+{
+ return m_baseURL->SetHostAndPort(aHostPort);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetHost(nsACString &aHost)
+{
+ return m_baseURL->GetHost(aHost);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetHost(const nsACString &aHost)
+{
+ return m_baseURL->SetHost(aHost);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetPort(int32_t *aPort)
+{
+ return m_baseURL->GetPort(aPort);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetPort(int32_t aPort)
+{
+ return m_baseURL->SetPort(aPort);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetPath(nsACString &aPath)
+{
+ return m_baseURL->GetPath(aPath);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SetPath(const nsACString &aPath)
+{
+ m_baseURL->SetPath(aPath);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetAsciiHost(nsACString &aHostA)
+{
+ return m_baseURL->GetAsciiHost(aHostA);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetAsciiHostPort(nsACString &aHostPortA)
+{
+ return m_baseURL->GetAsciiHostPort(aHostPortA);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetAsciiSpec(nsACString &aSpecA)
+{
+ return m_baseURL->GetAsciiSpec(aSpecA);
+}
+
+NS_IMETHODIMP nsAddbookUrl::GetOriginCharset(nsACString &aOriginCharset)
+{
+ return m_baseURL->GetOriginCharset(aOriginCharset);
+}
+
+NS_IMETHODIMP nsAddbookUrl::SchemeIs(const char *aScheme, bool *_retval)
+{
+ return m_baseURL->SchemeIs(aScheme, _retval);
+}
+
+NS_IMETHODIMP nsAddbookUrl::Equals(nsIURI *other, bool *_retval)
+{
+ // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its
+ // Equals method. The other nsMailtoUrl will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other)
+ return other->Equals(m_baseURL, _retval);
+
+ return m_baseURL->Equals(other, _retval);
+}
+
+nsresult
+nsAddbookUrl::CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef, nsIURI** _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ RefPtr<nsAddbookUrl> clone = new nsAddbookUrl();
+
+ if (!clone)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+ if (aRefHandlingMode == eHonorRef) {
+ rv = m_baseURL->Clone(getter_AddRefs(clone->m_baseURL));
+ } else if (aRefHandlingMode == eReplaceRef) {
+ rv = m_baseURL->CloneWithNewRef(newRef, getter_AddRefs(clone->m_baseURL));
+ } else {
+ rv = m_baseURL->CloneIgnoringRef(getter_AddRefs(clone->m_baseURL));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ clone->ParseUrl();
+ clone.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddbookUrl::Clone(nsIURI **_retval)
+{
+ return CloneInternal(eHonorRef, EmptyCString(), _retval);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::CloneIgnoringRef(nsIURI** _retval)
+{
+ return CloneInternal(eIgnoreRef, EmptyCString(), _retval);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::CloneWithNewRef(const nsACString& newRef, nsIURI** _retval)
+{
+ return CloneInternal(eReplaceRef, newRef, _retval);
+}
+
+NS_IMETHODIMP nsAddbookUrl::Resolve(const nsACString &relativePath, nsACString &result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::GetRef(nsACString &result)
+{
+ return m_baseURL->GetRef(result);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::SetRef(const nsACString &aRef)
+{
+ m_baseURL->SetRef(aRef);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::GetFilePath(nsACString &aFilePath)
+{
+ return m_baseURL->GetFilePath(aFilePath);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::SetFilePath(const nsACString &aFilePath)
+{
+ return m_baseURL->SetFilePath(aFilePath);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::GetQuery(nsACString &aQuery)
+{
+ return m_baseURL->GetQuery(aQuery);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::SetQuery(const nsACString &aQuery)
+{
+ return m_baseURL->SetQuery(aQuery);
+}
+
+NS_IMETHODIMP nsAddbookUrl::EqualsExceptRef(nsIURI *other, bool *_retval)
+{
+ // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its
+ // Equals method. The other nsMailtoUrl will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other)
+ return other->EqualsExceptRef(m_baseURL, _retval);
+
+ return m_baseURL->EqualsExceptRef(other, _retval);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::GetSpecIgnoringRef(nsACString &result)
+{
+ return m_baseURL->GetSpecIgnoringRef(result);
+}
+
+NS_IMETHODIMP
+nsAddbookUrl::GetHasRef(bool *result)
+{
+ return m_baseURL->GetHasRef(result);
+}
+
+//
+// Specific nsAddbookUrl operations
+//
+NS_IMETHODIMP
+nsAddbookUrl::GetAddbookOperation(int32_t *_retval)
+{
+ *_retval = mOperationType;
+ return NS_OK;
+}
diff --git a/mailnews/addrbook/src/nsAddbookUrl.h b/mailnews/addrbook/src/nsAddbookUrl.h
new file mode 100644
index 000000000..71a34abe8
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddbookUrl.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAddbookUrl_h__
+#define nsAddbookUrl_h__
+
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsIAddbookUrl.h"
+
+class nsAddbookUrl : public nsIAddbookUrl
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIADDBOOKURL
+
+ nsAddbookUrl();
+
+protected:
+ enum RefHandlingEnum {
+ eIgnoreRef,
+ eHonorRef,
+ eReplaceRef
+ };
+ virtual ~nsAddbookUrl();
+ nsresult
+ CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef, nsIURI** _retval);
+
+ nsresult ParseUrl();
+ int32_t mOperationType; // the internal ID for the operation
+
+ nsCOMPtr<nsIURI> m_baseURL; // the base URL for the object
+};
+
+#endif // nsAddbookUrl_h__
diff --git a/mailnews/addrbook/src/nsAddrDatabase.cpp b/mailnews/addrbook/src/nsAddrDatabase.cpp
new file mode 100644
index 000000000..463437958
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddrDatabase.cpp
@@ -0,0 +1,3334 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// this file implements the nsAddrDatabase interface using the MDB Interface.
+
+#include "nsAddrDatabase.h"
+#include "nsStringGlue.h"
+#include "nsAutoPtr.h"
+#include "nsUnicharUtils.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "nsMorkCID.h"
+#include "nsIMdbFactoryFactory.h"
+#include "prprf.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsIPromptService.h"
+#include "nsIStringBundle.h"
+#include "nsIFile.h"
+#include "nsEmbedCID.h"
+#include "nsIProperty.h"
+#include "nsIVariant.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIAbManager.h"
+#include "mozilla/Services.h"
+
+#define ID_PAB_TABLE 1
+#define ID_DELETEDCARDS_TABLE 2
+
+// There's two books by default, although Mac may have one more, so set this
+// to three. Its not going to affect much, but will save us a few reallocations
+// when the cache is allocated.
+const uint32_t kInitialAddrDBCacheSize = 3;
+
+static const char kPabTableKind[] = "ns:addrbk:db:table:kind:pab";
+static const char kDeletedCardsTableKind[] = "ns:addrbk:db:table:kind:deleted"; // this table is used to keep the deleted cards
+
+static const char kCardRowScope[] = "ns:addrbk:db:row:scope:card:all";
+static const char kListRowScope[] = "ns:addrbk:db:row:scope:list:all";
+static const char kDataRowScope[] = "ns:addrbk:db:row:scope:data:all";
+
+#define DATAROW_ROWID 1
+
+#define COLUMN_STR_MAX 16
+
+#define PURGE_CUTOFF_COUNT 50
+
+static const char kRecordKeyColumn[] = "RecordKey";
+static const char kLastRecordKeyColumn[] = "LastRecordKey";
+static const char kRowIDProperty[] = "DbRowID";
+
+static const char kLowerListNameColumn[] = "LowercaseListName";
+
+struct mdbOid gAddressBookTableOID;
+
+static const char kMailListAddressFormat[] = "Address%d";
+
+nsAddrDatabase::nsAddrDatabase()
+ : m_mdbEnv(nullptr), m_mdbStore(nullptr),
+ m_mdbPabTable(nullptr),
+ m_mdbDeletedCardsTable(nullptr),
+ m_mdbTokensInitialized(false),
+ m_PabTableKind(0),
+ m_MailListTableKind(0),
+ m_DeletedCardsTableKind(0),
+ m_CardRowScopeToken(0),
+ m_FirstNameColumnToken(0),
+ m_LastNameColumnToken(0),
+ m_PhoneticFirstNameColumnToken(0),
+ m_PhoneticLastNameColumnToken(0),
+ m_DisplayNameColumnToken(0),
+ m_NickNameColumnToken(0),
+ m_PriEmailColumnToken(0),
+ m_2ndEmailColumnToken(0),
+ m_WorkPhoneColumnToken(0),
+ m_HomePhoneColumnToken(0),
+ m_FaxColumnToken(0),
+ m_PagerColumnToken(0),
+ m_CellularColumnToken(0),
+ m_WorkPhoneTypeColumnToken(0),
+ m_HomePhoneTypeColumnToken(0),
+ m_FaxTypeColumnToken(0),
+ m_PagerTypeColumnToken(0),
+ m_CellularTypeColumnToken(0),
+ m_HomeAddressColumnToken(0),
+ m_HomeAddress2ColumnToken(0),
+ m_HomeCityColumnToken(0),
+ m_HomeStateColumnToken(0),
+ m_HomeZipCodeColumnToken(0),
+ m_HomeCountryColumnToken(0),
+ m_WorkAddressColumnToken(0),
+ m_WorkAddress2ColumnToken(0),
+ m_WorkCityColumnToken(0),
+ m_WorkStateColumnToken(0),
+ m_WorkZipCodeColumnToken(0),
+ m_WorkCountryColumnToken(0),
+ m_CompanyColumnToken(0),
+ m_AimScreenNameColumnToken(0),
+ m_AnniversaryYearColumnToken(0),
+ m_AnniversaryMonthColumnToken(0),
+ m_AnniversaryDayColumnToken(0),
+ m_SpouseNameColumnToken(0),
+ m_FamilyNameColumnToken(0),
+ m_DefaultAddressColumnToken(0),
+ m_CategoryColumnToken(0),
+ m_WebPage1ColumnToken(0),
+ m_WebPage2ColumnToken(0),
+ m_BirthYearColumnToken(0),
+ m_BirthMonthColumnToken(0),
+ m_BirthDayColumnToken(0),
+ m_Custom1ColumnToken(0),
+ m_Custom2ColumnToken(0),
+ m_Custom3ColumnToken(0),
+ m_Custom4ColumnToken(0),
+ m_NotesColumnToken(0),
+ m_LastModDateColumnToken(0),
+ m_MailFormatColumnToken(0),
+ m_PopularityIndexColumnToken(0),
+ m_AddressCharSetColumnToken(0),
+ m_LastRecordKey(0),
+ m_dbDirectory(nullptr)
+{
+}
+
+nsAddrDatabase::~nsAddrDatabase()
+{
+ Close(false); // better have already been closed.
+
+ // better not be any listeners, because we're going away.
+ NS_ASSERTION(m_ChangeListeners.Length() == 0, "shouldn't have any listeners");
+
+ RemoveFromCache(this);
+ // clean up after ourself!
+ if (m_mdbPabTable)
+ m_mdbPabTable->Release();
+ if (m_mdbDeletedCardsTable)
+ m_mdbDeletedCardsTable->Release();
+ NS_IF_RELEASE(m_mdbStore);
+ NS_IF_RELEASE(m_mdbEnv);
+}
+
+NS_IMPL_ISUPPORTS(nsAddrDatabase, nsIAddrDatabase, nsIAddrDBAnnouncer)
+
+NS_IMETHODIMP nsAddrDatabase::AddListener(nsIAddrDBListener *listener)
+{
+ NS_ENSURE_ARG_POINTER(listener);
+ return m_ChangeListeners.AppendElement(listener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAddrDatabase::RemoveListener(nsIAddrDBListener *listener)
+{
+ NS_ENSURE_ARG_POINTER(listener);
+ return m_ChangeListeners.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAddrDatabase::NotifyCardAttribChange(uint32_t abCode)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
+ OnCardAttribChange, (abCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::NotifyCardEntryChange(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent)
+{
+ int32_t currentDisplayNameVersion = 0;
+
+ //Update "mail.displayname.version" prefernce
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ prefs->GetIntPref("mail.displayname.version",&currentDisplayNameVersion);
+
+ prefs->SetIntPref("mail.displayname.version",++currentDisplayNameVersion);
+
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
+ OnCardEntryChange, (aAbCode, aCard, aParent));
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::NotifyListEntryChange(uint32_t abCode, nsIAbDirectory *dir)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
+ OnListEntryChange, (abCode, dir));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsAddrDatabase::NotifyAnnouncerGoingAway(void)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
+ OnAnnouncerGoingAway, ());
+ return NS_OK;
+}
+
+
+// Apparently its not good for nsTArray to be allocated as static. Don't know
+// why it isn't but its not, so don't think about making it a static variable.
+// Maybe bz knows.
+nsTArray<nsAddrDatabase*>* nsAddrDatabase::m_dbCache = nullptr;
+
+nsTArray<nsAddrDatabase*>*
+nsAddrDatabase::GetDBCache()
+{
+ if (!m_dbCache)
+ m_dbCache = new AutoTArray<nsAddrDatabase*, kInitialAddrDBCacheSize>;
+
+ return m_dbCache;
+}
+
+void
+nsAddrDatabase::CleanupCache()
+{
+ if (m_dbCache)
+ {
+ for (int32_t i = m_dbCache->Length() - 1; i >= 0; --i)
+ {
+ nsAddrDatabase* pAddrDB = m_dbCache->ElementAt(i);
+ if (pAddrDB)
+ pAddrDB->ForceClosed();
+ }
+ // NS_ASSERTION(m_dbCache.Length() == 0, "some msg dbs left open"); // better not be any open db's.
+ delete m_dbCache;
+ m_dbCache = nullptr;
+ }
+}
+
+//----------------------------------------------------------------------
+// FindInCache - this addrefs the db it finds.
+//----------------------------------------------------------------------
+nsAddrDatabase* nsAddrDatabase::FindInCache(nsIFile *dbName)
+{
+ nsTArray<nsAddrDatabase*>* dbCache = GetDBCache();
+ uint32_t length = dbCache->Length();
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ nsAddrDatabase* pAddrDB = dbCache->ElementAt(i);
+ if (pAddrDB->MatchDbName(dbName))
+ {
+ NS_ADDREF(pAddrDB);
+ return pAddrDB;
+ }
+ }
+ return nullptr;
+}
+
+bool nsAddrDatabase::MatchDbName(nsIFile* dbName) // returns true if they match
+{
+ bool dbMatches = false;
+
+ nsresult rv = m_dbName->Equals(dbName, &dbMatches);
+ if (NS_FAILED(rv))
+ return false;
+
+ return dbMatches;
+}
+
+//----------------------------------------------------------------------
+// RemoveFromCache
+//----------------------------------------------------------------------
+void nsAddrDatabase::RemoveFromCache(nsAddrDatabase* pAddrDB)
+{
+ if (m_dbCache)
+ m_dbCache->RemoveElement(pAddrDB);
+}
+
+void nsAddrDatabase::GetMDBFactory(nsIMdbFactory ** aMdbFactory)
+{
+ if (!mMdbFactory)
+ {
+ nsresult rv;
+ nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && mdbFactoryService)
+ rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory));
+ }
+ NS_IF_ADDREF(*aMdbFactory = mMdbFactory);
+}
+
+/* caller need to delete *aDbPath */
+NS_IMETHODIMP nsAddrDatabase::GetDbPath(nsIFile* *aDbPath)
+{
+ if (!aDbPath)
+ return NS_ERROR_NULL_POINTER;
+
+ return m_dbName->Clone(aDbPath);
+}
+
+NS_IMETHODIMP nsAddrDatabase::SetDbPath(nsIFile* aDbPath)
+{
+ return aDbPath->Clone(getter_AddRefs(m_dbName));
+}
+
+NS_IMETHODIMP nsAddrDatabase::Open
+(nsIFile *aMabFile, bool aCreate, bool upgrading /* unused */, nsIAddrDatabase** pAddrDB)
+{
+ *pAddrDB = nullptr;
+
+ nsAddrDatabase *pAddressBookDB = FindInCache(aMabFile);
+
+ if (pAddressBookDB) {
+ *pAddrDB = pAddressBookDB;
+ return NS_OK;
+ }
+
+ nsresult rv = OpenInternal(aMabFile, aCreate, pAddrDB);
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+
+ if (rv == NS_ERROR_FILE_ACCESS_DENIED)
+ {
+ static bool gAlreadyAlerted;
+ // only do this once per session to avoid annoying the user
+ if (!gAlreadyAlerted)
+ {
+ gAlreadyAlerted = true;
+ nsAutoString mabFileName;
+ rv = aMabFile->GetLeafName(mabFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AlertAboutLockedMabFile(mabFileName.get());
+
+ // We just overwrote rv, so return the proper value here.
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ }
+ // try one more time
+ // but first rename corrupt mab file
+ // and prompt the user
+ else if (aCreate)
+ {
+ nsCOMPtr<nsIFile> dummyBackupMabFile;
+ nsCOMPtr<nsIFile> actualBackupMabFile;
+
+ // First create a clone of the corrupt mab file that we'll
+ // use to generate the name for the backup file that we are
+ // going to move it to.
+ rv = aMabFile->Clone(getter_AddRefs(dummyBackupMabFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now create a second clone that we'll use to do the move
+ // (this allows us to leave the original name intact)
+ rv = aMabFile->Clone(getter_AddRefs(actualBackupMabFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now we try and generate a new name for the corrupt mab
+ // file using the dummy backup mab file
+
+ // First append .bak - we have to do this the long way as
+ // AppendNative is to the path, not the LeafName.
+ nsAutoCString dummyBackupMabFileName;
+ rv = dummyBackupMabFile->GetNativeLeafName(dummyBackupMabFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dummyBackupMabFileName.Append(NS_LITERAL_CSTRING(".bak"));
+
+ rv = dummyBackupMabFile->SetNativeLeafName(dummyBackupMabFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now see if we can create it unique
+ rv = dummyBackupMabFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now get the new name
+ nsAutoCString backupMabFileName;
+ rv = dummyBackupMabFile->GetNativeLeafName(backupMabFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // And the parent directory
+ nsCOMPtr<nsIFile> parentDir;
+ rv = dummyBackupMabFile->GetParent(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now move the corrupt file to its backup location
+ rv = actualBackupMabFile->MoveToNative(parentDir, backupMabFileName);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to rename corrupt mab file");
+
+ if (NS_SUCCEEDED(rv)) {
+ // now we can try to recreate the original mab file
+ rv = OpenInternal(aMabFile, aCreate, pAddrDB);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create .mab file, after rename");
+
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString originalMabFileName;
+ rv = aMabFile->GetLeafName(originalMabFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if this fails, we don't care
+ (void)AlertAboutCorruptMabFile(originalMabFileName.get(),
+ NS_ConvertASCIItoUTF16(backupMabFileName).get());
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsAddrDatabase::DisplayAlert(const char16_t *titleName, const char16_t *alertStringName, const char16_t **formatStrings, int32_t numFormatStrings)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString alertMessage;
+ rv = bundle->FormatStringFromName(alertStringName, formatStrings, numFormatStrings,
+ getter_Copies(alertMessage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString alertTitle;
+ rv = bundle->GetStringFromName(titleName, getter_Copies(alertTitle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPromptService> prompter =
+ do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return prompter->Alert(nullptr /* we don't know the parent window */, alertTitle.get(), alertMessage.get());
+}
+
+nsresult nsAddrDatabase::AlertAboutCorruptMabFile(const char16_t *aOldFileName, const char16_t *aNewFileName)
+{
+ const char16_t *formatStrings[] = { aOldFileName, aOldFileName, aNewFileName };
+ return DisplayAlert(u"corruptMabFileTitle",
+ u"corruptMabFileAlert", formatStrings, 3);
+}
+
+nsresult nsAddrDatabase::AlertAboutLockedMabFile(const char16_t *aFileName)
+{
+ const char16_t *formatStrings[] = { aFileName };
+ return DisplayAlert(u"lockedMabFileTitle",
+ u"lockedMabFileAlert", formatStrings, 1);
+}
+
+nsresult
+nsAddrDatabase::OpenInternal(nsIFile *aMabFile, bool aCreate, nsIAddrDatabase** pAddrDB)
+{
+ nsAddrDatabase *pAddressBookDB = new nsAddrDatabase();
+ if (!pAddressBookDB) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_ADDREF(pAddressBookDB);
+
+ nsresult rv = pAddressBookDB->OpenMDB(aMabFile, aCreate);
+ if (NS_SUCCEEDED(rv))
+ {
+ pAddressBookDB->SetDbPath(aMabFile);
+ GetDBCache()->AppendElement(pAddressBookDB);
+ *pAddrDB = pAddressBookDB;
+ }
+ else
+ {
+ *pAddrDB = nullptr;
+ pAddressBookDB->ForceClosed();
+ NS_IF_RELEASE(pAddressBookDB);
+ pAddressBookDB = nullptr;
+ }
+ return rv;
+}
+
+// Open the MDB database synchronously. If successful, this routine
+// will set up the m_mdbStore and m_mdbEnv of the database object
+// so other database calls can work.
+NS_IMETHODIMP nsAddrDatabase::OpenMDB(nsIFile *dbName, bool create)
+{
+ nsresult ret;
+ nsCOMPtr<nsIMdbFactory> mdbFactory;
+ GetMDBFactory(getter_AddRefs(mdbFactory));
+ NS_ENSURE_TRUE(mdbFactory, NS_ERROR_FAILURE);
+
+ ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv);
+ if (NS_SUCCEEDED(ret))
+ {
+ nsIMdbThumb *thumb = nullptr;
+ nsAutoCString filePath;
+
+ ret = dbName->GetNativePath(filePath);
+ NS_ENSURE_SUCCESS(ret, ret);
+
+ nsIMdbHeap* dbHeap = nullptr;
+
+ if (m_mdbEnv)
+ m_mdbEnv->SetAutoClear(true);
+
+ bool dbNameExists = false;
+ ret = dbName->Exists(&dbNameExists);
+ NS_ENSURE_SUCCESS(ret, ret);
+
+ if (!dbNameExists)
+ ret = NS_ERROR_FILE_NOT_FOUND;
+ else
+ {
+ mdbOpenPolicy inOpenPolicy;
+ mdb_bool canOpen;
+ mdbYarn outFormatVersion;
+ nsIMdbFile* oldFile = nullptr;
+ int64_t fileSize;
+ ret = dbName->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(ret, ret);
+
+ ret = mdbFactory->OpenOldFile(m_mdbEnv, dbHeap, filePath.get(),
+ mdbBool_kFalse, // not readonly, we want modifiable
+ &oldFile);
+ if ( oldFile )
+ {
+ if (NS_SUCCEEDED(ret))
+ {
+ ret = mdbFactory->CanOpenFilePort(m_mdbEnv, oldFile, // the file to investigate
+ &canOpen, &outFormatVersion);
+ if (NS_SUCCEEDED(ret) && canOpen)
+ {
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->OpenFileStore(m_mdbEnv, dbHeap,
+ oldFile, &inOpenPolicy, &thumb);
+ }
+ else if (fileSize != 0)
+ ret = NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ NS_RELEASE(oldFile); // always release our file ref, store has own
+ }
+ if (NS_FAILED(ret))
+ ret = NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
+ if (NS_SUCCEEDED(ret) && thumb)
+ {
+ mdb_count outTotal; // total somethings to do in operation
+ mdb_count outCurrent; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken; // is operation irreparably dead and broken?
+ do
+ {
+ ret = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken);
+ if (NS_FAILED(ret))
+ {
+ outDone = true;
+ break;
+ }
+ }
+ while (NS_SUCCEEDED(ret) && !outBroken && !outDone);
+ if (NS_SUCCEEDED(ret) && outDone)
+ {
+ ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore);
+ if (NS_SUCCEEDED(ret) && m_mdbStore)
+ {
+ ret = InitExistingDB();
+ create = false;
+ }
+ }
+ }
+ else if (create && ret != NS_ERROR_FILE_ACCESS_DENIED)
+ {
+ nsIMdbFile* newFile = 0;
+ ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, filePath.get(), &newFile);
+ if ( newFile )
+ {
+ if (NS_SUCCEEDED(ret))
+ {
+ mdbOpenPolicy inOpenPolicy;
+
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->CreateNewFileStore(m_mdbEnv, dbHeap,
+ newFile, &inOpenPolicy,
+ &m_mdbStore);
+ if (NS_SUCCEEDED(ret))
+ ret = InitNewDB();
+ }
+ NS_RELEASE(newFile); // always release our file ref, store has own
+ }
+ }
+ NS_IF_RELEASE(thumb);
+ }
+ return ret;
+}
+
+NS_IMETHODIMP nsAddrDatabase::CloseMDB(bool commit)
+{
+ if (commit)
+ Commit(nsAddrDBCommitType::kSessionCommit);
+//??? RemoveFromCache(this); // if we've closed it, better not leave it in the cache.
+ return NS_OK;
+}
+
+// force the database to close - this'll flush out anybody holding onto
+// a database without having a listener!
+// This is evil in the com world, but there are times we need to delete the file.
+NS_IMETHODIMP nsAddrDatabase::ForceClosed()
+{
+ nsresult err = NS_OK;
+ nsCOMPtr<nsIAddrDatabase> aDb(do_QueryInterface(this, &err));
+
+ // make sure someone has a reference so object won't get deleted out from under us.
+ AddRef();
+ NotifyAnnouncerGoingAway();
+ // OK, remove from cache first and close the store.
+ RemoveFromCache(this);
+
+ err = CloseMDB(false); // since we're about to delete it, no need to commit.
+ NS_IF_RELEASE(m_mdbStore);
+ Release();
+ return err;
+}
+
+NS_IMETHODIMP nsAddrDatabase::Commit(uint32_t commitType)
+{
+ nsresult err = NS_OK;
+ nsIMdbThumb *commitThumb = nullptr;
+
+ if (commitType == nsAddrDBCommitType::kLargeCommit ||
+ commitType == nsAddrDBCommitType::kSessionCommit)
+ {
+ mdb_percent outActualWaste = 0;
+ mdb_bool outShould;
+ if (m_mdbStore && m_mdbEnv)
+ {
+ // check how much space would be saved by doing a compress commit.
+ // If it's more than 30%, go for it.
+ // N.B. - I'm not sure this calls works in Mork for all cases.
+ err = m_mdbStore->ShouldCompress(m_mdbEnv, 30, &outActualWaste, &outShould);
+ if (NS_SUCCEEDED(err) && outShould)
+ {
+ commitType = nsAddrDBCommitType::kCompressCommit;
+ }
+ }
+ }
+
+ if (m_mdbStore && m_mdbEnv)
+ {
+ switch (commitType)
+ {
+ case nsAddrDBCommitType::kLargeCommit:
+ err = m_mdbStore->LargeCommit(m_mdbEnv, &commitThumb);
+ break;
+ case nsAddrDBCommitType::kSessionCommit:
+ // comment out until persistence works.
+ err = m_mdbStore->SessionCommit(m_mdbEnv, &commitThumb);
+ break;
+ case nsAddrDBCommitType::kCompressCommit:
+ err = m_mdbStore->CompressCommit(m_mdbEnv, &commitThumb);
+ break;
+ }
+ }
+ if (commitThumb && m_mdbEnv)
+ {
+ mdb_count outTotal = 0; // total somethings to do in operation
+ mdb_count outCurrent = 0; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken = false; // is operation irreparably dead and broken?
+ while (!outDone && !outBroken && NS_SUCCEEDED(err))
+ {
+ err = commitThumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken);
+ }
+ NS_RELEASE(commitThumb);
+ }
+ // ### do something with error, but clear it now because mork errors out on commits.
+ if (m_mdbEnv)
+ m_mdbEnv->ClearErrors();
+ return err;
+}
+
+NS_IMETHODIMP nsAddrDatabase::Close(bool forceCommit /* = TRUE */)
+{
+ return CloseMDB(forceCommit);
+}
+
+// set up empty tablesetc.
+nsresult nsAddrDatabase::InitNewDB()
+{
+ nsresult err = InitMDBInfo();
+ if (NS_SUCCEEDED(err))
+ {
+ err = InitPabTable();
+ err = InitLastRecorKey();
+ Commit(nsAddrDBCommitType::kLargeCommit);
+ }
+ return err;
+}
+
+nsresult nsAddrDatabase::AddRowToDeletedCardsTable(nsIAbCard *card, nsIMdbRow **pCardRow)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+ if (!m_mdbDeletedCardsTable)
+ rv = InitDeletedCardsTable(true);
+
+ if (NS_SUCCEEDED(rv)) {
+ // lets first purge old records if there are more than PURGE_CUTOFF_COUNT records
+ PurgeDeletedCardTable();
+ nsCOMPtr<nsIMdbRow> cardRow;
+ rv = GetNewRow(getter_AddRefs(cardRow));
+ if (NS_SUCCEEDED(rv) && cardRow) {
+ nsresult merror = m_mdbDeletedCardsTable->AddRow(m_mdbEnv, cardRow);
+ NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE);
+ nsString unicodeStr;
+ card->GetFirstName(unicodeStr);
+ AddFirstName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get());
+
+ card->GetLastName(unicodeStr);
+ AddLastName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get());
+
+ card->GetDisplayName(unicodeStr);
+ AddDisplayName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get());
+
+ card->GetPrimaryEmail(unicodeStr);
+ if (!unicodeStr.IsEmpty())
+ AddUnicodeToColumn(cardRow, m_PriEmailColumnToken, m_LowerPriEmailColumnToken, unicodeStr.get());
+
+ card->GetPropertyAsAString(k2ndEmailProperty, unicodeStr);
+ if (!unicodeStr.IsEmpty())
+ AddUnicodeToColumn(cardRow, m_2ndEmailColumnToken, m_Lower2ndEmailColumnToken, unicodeStr.get());
+
+ uint32_t nowInSeconds;
+ PRTime now = PR_Now();
+ PRTime2Seconds(now, &nowInSeconds);
+ AddIntColumn(cardRow, m_LastModDateColumnToken, nowInSeconds);
+
+ nsString value;
+ GetCardValue(card, CARD_ATTRIB_PALMID, getter_Copies(value));
+ if (!value.IsEmpty())
+ {
+ nsCOMPtr<nsIAbCard> addedCard;
+ rv = CreateCardFromDeletedCardsTable(cardRow, 0, getter_AddRefs(addedCard));
+ if (NS_SUCCEEDED(rv))
+ SetCardValue(addedCard, CARD_ATTRIB_PALMID, value.get(), false);
+ }
+ NS_IF_ADDREF(*pCardRow = cardRow);
+ }
+ Commit(nsAddrDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+nsresult nsAddrDatabase::DeleteRowFromDeletedCardsTable(nsIMdbRow *pCardRow)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult merror = NS_OK;
+ if (m_mdbDeletedCardsTable) {
+ pCardRow->CutAllColumns(m_mdbEnv);
+ merror = m_mdbDeletedCardsTable->CutRow(m_mdbEnv, pCardRow);
+ }
+ return merror;
+}
+
+
+nsresult nsAddrDatabase::InitDeletedCardsTable(bool aCreate)
+{
+ nsresult mdberr = NS_OK;
+ if (!m_mdbDeletedCardsTable)
+ {
+ struct mdbOid deletedCardsTableOID;
+ deletedCardsTableOID.mOid_Scope = m_CardRowScopeToken;
+ deletedCardsTableOID.mOid_Id = ID_DELETEDCARDS_TABLE;
+ if (m_mdbStore && m_mdbEnv)
+ {
+ m_mdbStore->GetTable(m_mdbEnv, &deletedCardsTableOID, &m_mdbDeletedCardsTable);
+ // if deletedCardsTable does not exist and bCreate is set, create a new one
+ if (!m_mdbDeletedCardsTable && aCreate)
+ {
+ mdberr = (nsresult) m_mdbStore->NewTableWithOid(m_mdbEnv, &deletedCardsTableOID,
+ m_DeletedCardsTableKind,
+ true, (const mdbOid*)nullptr,
+ &m_mdbDeletedCardsTable);
+ }
+ }
+ }
+ return mdberr;
+}
+
+nsresult nsAddrDatabase::InitPabTable()
+{
+ return m_mdbStore && m_mdbEnv ? m_mdbStore->NewTableWithOid(m_mdbEnv,
+ &gAddressBookTableOID,
+ m_PabTableKind,
+ false,
+ (const mdbOid*)nullptr,
+ &m_mdbPabTable)
+ : NS_ERROR_NULL_POINTER;
+}
+
+//save the last record number, store in m_DataRowScopeToken, row 1
+nsresult nsAddrDatabase::InitLastRecorKey()
+{
+ if (!m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsIMdbRow *pDataRow = nullptr;
+ mdbOid dataRowOid;
+ dataRowOid.mOid_Scope = m_DataRowScopeToken;
+ dataRowOid.mOid_Id = DATAROW_ROWID;
+ nsresult err = m_mdbStore->NewRowWithOid(m_mdbEnv, &dataRowOid, &pDataRow);
+
+ if (NS_SUCCEEDED(err) && pDataRow)
+ {
+ m_LastRecordKey = 0;
+ err = AddIntColumn(pDataRow, m_LastRecordKeyColumnToken, 0);
+ err = m_mdbPabTable->AddRow(m_mdbEnv, pDataRow);
+ NS_RELEASE(pDataRow);
+ }
+ return err;
+}
+
+nsresult nsAddrDatabase::GetDataRow(nsIMdbRow **pDataRow)
+{
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsIMdbRow *pRow = nullptr;
+ mdbOid dataRowOid;
+ dataRowOid.mOid_Scope = m_DataRowScopeToken;
+ dataRowOid.mOid_Id = DATAROW_ROWID;
+ m_mdbStore->GetRow(m_mdbEnv, &dataRowOid, &pRow);
+ *pDataRow = pRow;
+
+ return pRow ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::GetLastRecordKey()
+{
+ if (!m_mdbPabTable)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr <nsIMdbRow> pDataRow;
+ nsresult err = GetDataRow(getter_AddRefs(pDataRow));
+
+ if (NS_SUCCEEDED(err) && pDataRow)
+ {
+ m_LastRecordKey = 0;
+ err = GetIntColumn(pDataRow, m_LastRecordKeyColumnToken, &m_LastRecordKey, 0);
+ if (NS_FAILED(err))
+ err = NS_ERROR_NOT_AVAILABLE;
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsAddrDatabase::UpdateLastRecordKey()
+{
+ if (!m_mdbPabTable || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr <nsIMdbRow> pDataRow;
+ nsresult err = GetDataRow(getter_AddRefs(pDataRow));
+
+ if (NS_SUCCEEDED(err) && pDataRow)
+ {
+ err = AddIntColumn(pDataRow, m_LastRecordKeyColumnToken, m_LastRecordKey);
+ err = m_mdbPabTable->AddRow(m_mdbEnv, pDataRow);
+ return NS_OK;
+ }
+ else if (!pDataRow)
+ err = InitLastRecorKey();
+ else
+ return NS_ERROR_NOT_AVAILABLE;
+ return err;
+}
+
+nsresult nsAddrDatabase::InitExistingDB()
+{
+ nsresult err = InitMDBInfo();
+ if (NS_SUCCEEDED(err))
+ {
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->GetTable(m_mdbEnv, &gAddressBookTableOID, &m_mdbPabTable);
+ if (NS_SUCCEEDED(err) && m_mdbPabTable)
+ {
+ err = GetLastRecordKey();
+ if (err == NS_ERROR_NOT_AVAILABLE)
+ CheckAndUpdateRecordKey();
+ UpdateLowercaseEmailListName();
+ }
+ }
+ return err;
+}
+
+nsresult nsAddrDatabase::CheckAndUpdateRecordKey()
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ nsIMdbTableRowCursor* rowCursor = nullptr;
+ nsIMdbRow* findRow = nullptr;
+ mdb_pos rowPos = 0;
+
+ nsresult merror = m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor);
+
+ NS_ENSURE_TRUE(NS_SUCCEEDED(merror) && rowCursor, NS_ERROR_FAILURE);
+
+ nsCOMPtr <nsIMdbRow> pDataRow;
+ err = GetDataRow(getter_AddRefs(pDataRow));
+ if (NS_FAILED(err))
+ InitLastRecorKey();
+
+ do
+ { //add key to each card and mailing list row
+ merror = rowCursor->NextRow(m_mdbEnv, &findRow, &rowPos);
+ if (NS_SUCCEEDED(merror) && findRow)
+ {
+ mdbOid rowOid;
+
+ if (NS_SUCCEEDED(findRow->GetOid(m_mdbEnv, &rowOid)))
+ {
+ if (!IsDataRowScopeToken(rowOid.mOid_Scope))
+ {
+ m_LastRecordKey++;
+ err = AddIntColumn(findRow, m_RecordKeyColumnToken, m_LastRecordKey);
+ }
+ }
+ }
+ } while (findRow);
+
+ UpdateLastRecordKey();
+ Commit(nsAddrDBCommitType::kLargeCommit);
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::UpdateLowercaseEmailListName()
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ nsIMdbTableRowCursor* rowCursor = nullptr;
+ nsIMdbRow* findRow = nullptr;
+ mdb_pos rowPos = 0;
+ bool commitRequired = false;
+
+ nsresult merror = m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor);
+
+ NS_ENSURE_TRUE(NS_SUCCEEDED(merror) && rowCursor, NS_ERROR_FAILURE);
+
+ do
+ { // Add lowercase primary+secondary email to each card and mailing list row.
+ merror = rowCursor->NextRow(m_mdbEnv, &findRow, &rowPos);
+ if (NS_SUCCEEDED(merror) && findRow)
+ {
+ mdbOid rowOid;
+
+ if (NS_SUCCEEDED(findRow->GetOid(m_mdbEnv, &rowOid)))
+ {
+ nsAutoString tempString;
+ if (IsCardRowScopeToken(rowOid.mOid_Scope))
+ {
+ err = GetStringColumn(findRow, m_LowerPriEmailColumnToken, tempString);
+ if (NS_FAILED(err)) // not set yet
+ {
+ err = ConvertAndAddLowercaseColumn(findRow, m_PriEmailColumnToken,
+ m_LowerPriEmailColumnToken);
+ commitRequired = commitRequired || NS_SUCCEEDED(err);
+ }
+
+ err = GetStringColumn(findRow, m_Lower2ndEmailColumnToken, tempString);
+ if (NS_FAILED(err)) // not set yet
+ {
+ err = ConvertAndAddLowercaseColumn(findRow, m_2ndEmailColumnToken,
+ m_Lower2ndEmailColumnToken);
+ commitRequired = commitRequired || NS_SUCCEEDED(err);
+ }
+ }
+ else if (IsListRowScopeToken(rowOid.mOid_Scope))
+ {
+ err = GetStringColumn(findRow, m_LowerListNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err)) // already set up
+ continue;
+
+ err = ConvertAndAddLowercaseColumn(findRow, m_ListNameColumnToken,
+ m_LowerListNameColumnToken);
+ commitRequired = commitRequired || NS_SUCCEEDED(err);
+ }
+ }
+ findRow->Release();
+ }
+ } while (findRow);
+
+ if (findRow)
+ findRow->Release();
+ rowCursor->Release();
+ if (commitRequired)
+ Commit(nsAddrDBCommitType::kLargeCommit);
+ return NS_OK;
+}
+
+/*
+We store UTF8 strings in the database. We need to convert the UTF8
+string into unicode string, then convert to lower case. Before storing
+back into the database, we need to convert the lowercase unicode string
+into UTF8 string.
+*/
+nsresult nsAddrDatabase::ConvertAndAddLowercaseColumn
+(nsIMdbRow * row, mdb_token fromCol, mdb_token toCol)
+{
+ nsAutoString colString;
+
+ nsresult rv = GetStringColumn(row, fromCol, colString);
+ if (!colString.IsEmpty())
+ {
+ rv = AddLowercaseColumn(row, toCol, NS_ConvertUTF16toUTF8(colString).get());
+ }
+ return rv;
+}
+
+// Change the unicode string to lowercase, then convert to UTF8 string to store in db
+nsresult nsAddrDatabase::AddUnicodeToColumn(nsIMdbRow * row, mdb_token aColToken, mdb_token aLowerCaseColToken, const char16_t* aUnicodeStr)
+{
+ nsresult rv = AddCharStringColumn(row, aColToken, NS_ConvertUTF16toUTF8(aUnicodeStr).get());
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AddLowercaseColumn(row, aLowerCaseColToken, NS_ConvertUTF16toUTF8(aUnicodeStr).get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+// initialize the various tokens and tables in our db's env
+nsresult nsAddrDatabase::InitMDBInfo()
+{
+ nsresult err = NS_OK;
+
+ if (!m_mdbTokensInitialized && m_mdbStore && m_mdbEnv)
+ {
+ m_mdbTokensInitialized = true;
+ err = m_mdbStore->StringToToken(m_mdbEnv, kCardRowScope, &m_CardRowScopeToken);
+ err = m_mdbStore->StringToToken(m_mdbEnv, kListRowScope, &m_ListRowScopeToken);
+ err = m_mdbStore->StringToToken(m_mdbEnv, kDataRowScope, &m_DataRowScopeToken);
+ gAddressBookTableOID.mOid_Scope = m_CardRowScopeToken;
+ gAddressBookTableOID.mOid_Id = ID_PAB_TABLE;
+ if (NS_SUCCEEDED(err))
+ {
+ m_mdbStore->StringToToken(m_mdbEnv, kFirstNameProperty, &m_FirstNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLastNameProperty, &m_LastNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPhoneticFirstNameProperty, &m_PhoneticFirstNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPhoneticLastNameProperty, &m_PhoneticLastNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kDisplayNameProperty, &m_DisplayNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kNicknameProperty, &m_NickNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPriEmailProperty, &m_PriEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLowerPriEmailColumn, &m_LowerPriEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, k2ndEmailProperty, &m_2ndEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLower2ndEmailColumn, &m_Lower2ndEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPreferMailFormatProperty, &m_MailFormatColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPopularityIndexProperty, &m_PopularityIndexColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkPhoneProperty, &m_WorkPhoneColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomePhoneProperty, &m_HomePhoneColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kFaxProperty, &m_FaxColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPagerProperty, &m_PagerColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCellularProperty, &m_CellularColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkPhoneTypeProperty, &m_WorkPhoneTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomePhoneTypeProperty, &m_HomePhoneTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kFaxTypeProperty, &m_FaxTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPagerTypeProperty, &m_PagerTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCellularTypeProperty, &m_CellularTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeAddressProperty, &m_HomeAddressColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeAddress2Property, &m_HomeAddress2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeCityProperty, &m_HomeCityColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeStateProperty, &m_HomeStateColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeZipCodeProperty, &m_HomeZipCodeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeCountryProperty, &m_HomeCountryColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkAddressProperty, &m_WorkAddressColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkAddress2Property, &m_WorkAddress2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkCityProperty, &m_WorkCityColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkStateProperty, &m_WorkStateColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkZipCodeProperty, &m_WorkZipCodeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkCountryProperty, &m_WorkCountryColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kJobTitleProperty, &m_JobTitleColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kDepartmentProperty, &m_DepartmentColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCompanyProperty, &m_CompanyColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kScreenNameProperty, &m_AimScreenNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryYearProperty, &m_AnniversaryYearColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryMonthProperty, &m_AnniversaryMonthColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryDayProperty, &m_AnniversaryDayColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kSpouseNameProperty, &m_SpouseNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kFamilyNameProperty, &m_FamilyNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkWebPageProperty, &m_WebPage1ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeWebPageProperty, &m_WebPage2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kBirthYearProperty, &m_BirthYearColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kBirthMonthProperty, &m_BirthMonthColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kBirthDayProperty, &m_BirthDayColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom1Property, &m_Custom1ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom2Property, &m_Custom2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom3Property, &m_Custom3ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom4Property, &m_Custom4ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kNotesProperty, &m_NotesColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLastModifiedDateProperty, &m_LastModDateColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kRecordKeyColumn, &m_RecordKeyColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAddressCharSetColumn, &m_AddressCharSetColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLastRecordKeyColumn, &m_LastRecordKeyColumnToken);
+
+ err = m_mdbStore->StringToToken(m_mdbEnv, kPabTableKind, &m_PabTableKind);
+
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListName, &m_ListNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListNickName, &m_ListNickNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListDescription, &m_ListDescriptionColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListTotalAddresses, &m_ListTotalColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLowerListNameColumn, &m_LowerListNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kDeletedCardsTableKind, &m_DeletedCardsTableKind);
+ }
+ }
+ return err;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsAddrDatabase::AddRecordKeyColumnToRow(nsIMdbRow *pRow)
+{
+ if (pRow && m_mdbEnv)
+ {
+ m_LastRecordKey++;
+ nsresult err = AddIntColumn(pRow, m_RecordKeyColumnToken, m_LastRecordKey);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = m_mdbPabTable->AddRow(m_mdbEnv, pRow);
+ UpdateLastRecordKey();
+ return err;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+nsresult nsAddrDatabase::AddAttributeColumnsToRow(nsIAbCard *card, nsIMdbRow *cardRow)
+{
+ nsresult rv = NS_OK;
+
+ if ((!card && !cardRow) || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdbOid rowOid;
+ cardRow->GetOid(m_mdbEnv, &rowOid);
+
+ card->SetPropertyAsUint32(kRowIDProperty, rowOid.mOid_Id);
+
+ // add the row to the singleton table.
+ if (card && cardRow)
+ {
+ nsCOMPtr<nsISimpleEnumerator> properties;
+ rv = card->GetProperties(getter_AddRefs(properties));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(properties->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> next;
+ rv = properties->GetNext(getter_AddRefs(next));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIProperty> prop = do_QueryInterface(next);
+ nsAutoString name;
+ prop->GetName(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ prop->GetValue(getter_AddRefs(variant));
+
+ // We can't get as a char * because that messes up UTF8 stuff
+ nsAutoCString value;
+ variant->GetAsAUTF8String(value);
+
+ mdb_token token;
+ rv = m_mdbStore->StringToToken(m_mdbEnv, NS_ConvertUTF16toUTF8(name).get(), &token);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddCharStringColumn(cardRow, token, value.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Primary email is special: it is stored lowercase as well as in its
+ // original format.
+ nsAutoString primaryEmail;
+ card->GetPrimaryEmail(primaryEmail);
+ AddPrimaryEmail(cardRow, NS_ConvertUTF16toUTF8(primaryEmail).get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::CreateNewCardAndAddToDB(nsIAbCard *aNewCard, bool aNotify /* = FALSE */, nsIAbDirectory *aParent)
+{
+ nsCOMPtr <nsIMdbRow> cardRow;
+
+ if (!aNewCard || !m_mdbPabTable || !m_mdbEnv || !m_mdbStore)
+ return NS_ERROR_NULL_POINTER;
+
+ // Per the UUID requirements, we want to try to reuse the local id if at all
+ // possible. nsACString::ToInteger probably won't fail if the local id looks
+ // like "23bozo" (returning 23 instead), but it's okay since we aren't going
+ // to overwrite anything with 23 if it already exists and the id for the row
+ // doesn't matter otherwise.
+ nsresult rv;
+
+ nsAutoCString id;
+ aNewCard->GetLocalId(id);
+
+ mdbOid rowId;
+ rowId.mOid_Scope = m_CardRowScopeToken;
+ rowId.mOid_Id = id.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ // Mork is being very naughty here. If the table does not have the oid, we
+ // should be able to reuse it. To be on the safe side, however, we're going
+ // to reference the store's reference count.
+ mdb_count rowCount = 1;
+ m_mdbStore->GetRowRefCount(m_mdbEnv, &rowId, &rowCount);
+ if (rowCount == 0)
+ {
+ // So apparently, the row can have a count of 0 yet still exist (probably
+ // meaning we haven't flushed it out of memory). In this case, we need to
+ // get the row and cut its cells.
+ rv = m_mdbStore->GetRow(m_mdbEnv, &rowId, getter_AddRefs(cardRow));
+ if (NS_SUCCEEDED(rv) && cardRow)
+ cardRow->CutAllColumns(m_mdbEnv);
+ else
+ rv = m_mdbStore->NewRowWithOid(m_mdbEnv, &rowId, getter_AddRefs(cardRow));
+ }
+ }
+
+ // If we don't have a cardRow yet, just get one with any ol' id.
+ if (!cardRow)
+ rv = GetNewRow(getter_AddRefs(cardRow));
+
+ if (NS_SUCCEEDED(rv) && cardRow)
+ {
+ AddAttributeColumnsToRow(aNewCard, cardRow);
+ AddRecordKeyColumnToRow(cardRow);
+
+ // we need to do this for dnd
+ uint32_t key = 0;
+ rv = GetIntColumn(cardRow, m_RecordKeyColumnToken, &key, 0);
+ if (NS_SUCCEEDED(rv))
+ aNewCard->SetPropertyAsUint32(kRecordKeyColumn, key);
+
+ aNewCard->GetPropertyAsAUTF8String(kRowIDProperty, id);
+ aNewCard->SetLocalId(id);
+
+ nsCOMPtr<nsIAbDirectory> abDir = do_QueryReferent(m_dbDirectory);
+ if (abDir)
+ abDir->GetUuid(id);
+
+ aNewCard->SetDirectoryId(id);
+
+ nsresult merror = m_mdbPabTable->AddRow(m_mdbEnv, cardRow);
+ NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE);
+ }
+ else
+ return rv;
+
+ // do notification
+ if (aNotify)
+ {
+ NotifyCardEntryChange(AB_NotifyInserted, aNewCard, aParent);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAddrDatabase::CreateNewListCardAndAddToDB(nsIAbDirectory *aList, uint32_t listRowID, nsIAbCard *newCard, bool notify /* = FALSE */)
+{
+ if (!newCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsIMdbRow* pListRow = nullptr;
+ mdbOid listRowOid;
+ listRowOid.mOid_Scope = m_ListRowScopeToken;
+ listRowOid.mOid_Id = listRowID;
+ nsresult rv = m_mdbStore->GetRow(m_mdbEnv, &listRowOid, &pListRow);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!pListRow)
+ return NS_OK;
+
+ nsCOMPtr<nsIMutableArray> addressList;
+ rv = aList->GetAddressLists(getter_AddRefs(addressList));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t count;
+ addressList->GetLength(&count);
+
+ nsAutoString newEmail;
+ rv = newCard->GetPrimaryEmail(newEmail);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t i;
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbCard> currentCard = do_QueryElementAt(addressList, i, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool equals;
+ rv = newCard->Equals(currentCard, &equals);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (equals) {
+ // card is already in list, bail out.
+ // this can happen when dropping a card on a mailing list from the directory that contains the mailing list
+ return NS_OK;
+ }
+
+ nsAutoString currentEmail;
+ rv = currentCard->GetPrimaryEmail(currentEmail);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (newEmail.Equals(currentEmail)) {
+ // card is already in list, bail out
+ // this can happen when dropping a card on a mailing list from another directory (not the one that contains the mailing list
+ // or if you have multiple cards on a directory, with the same primary email address.
+ return NS_OK;
+ }
+ }
+
+ // start from 1
+ uint32_t totalAddress = GetListAddressTotal(pListRow) + 1;
+ SetListAddressTotal(pListRow, totalAddress);
+ nsCOMPtr<nsIAbCard> pNewCard;
+ rv = AddListCardColumnsToRow(newCard, pListRow, totalAddress, getter_AddRefs(pNewCard), true /* aInMailingList */, aList, nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ addressList->AppendElement(newCard, false);
+
+ if (notify)
+ NotifyCardEntryChange(AB_NotifyInserted, newCard, aList);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAddrDatabase::AddListCardColumnsToRow
+(nsIAbCard *aPCard, nsIMdbRow *aPListRow, uint32_t aPos, nsIAbCard** aPNewCard, bool aInMailingList, nsIAbDirectory *aParent, nsIAbDirectory *aRoot)
+{
+ if (!aPCard || !aPListRow || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ nsString email;
+ aPCard->GetPrimaryEmail(email);
+ if (!email.IsEmpty())
+ {
+ nsIMdbRow *pCardRow = nullptr;
+ // Please DO NOT change the 3rd param of GetRowFromAttribute() call to
+ // true (ie, case insensitive) without reading bugs #128535 and #121478.
+ err = GetRowFromAttribute(kPriEmailProperty, NS_ConvertUTF16toUTF8(email),
+ false /* retain case */, &pCardRow, nullptr);
+ bool cardWasAdded = false;
+ if (NS_FAILED(err) || !pCardRow)
+ {
+ //New Email, then add a new row with this email
+ err = GetNewRow(&pCardRow);
+
+ if (NS_SUCCEEDED(err) && pCardRow)
+ {
+ AddPrimaryEmail(pCardRow, NS_ConvertUTF16toUTF8(email).get());
+ err = m_mdbPabTable->AddRow(m_mdbEnv, pCardRow);
+ // Create a key for this row as well.
+ if (NS_SUCCEEDED(err))
+ AddRecordKeyColumnToRow(pCardRow);
+ }
+
+ cardWasAdded = true;
+ }
+
+ NS_ENSURE_TRUE(pCardRow, NS_ERROR_NULL_POINTER);
+
+ nsString name;
+ aPCard->GetDisplayName(name);
+ if (!name.IsEmpty()) {
+ AddDisplayName(pCardRow, NS_ConvertUTF16toUTF8(name).get());
+ err = m_mdbPabTable->AddRow(m_mdbEnv, pCardRow);
+ }
+
+ nsCOMPtr<nsIAbCard> newCard;
+ CreateABCard(pCardRow, 0, getter_AddRefs(newCard));
+ NS_IF_ADDREF(*aPNewCard = newCard);
+
+ if (cardWasAdded) {
+ NotifyCardEntryChange(AB_NotifyInserted, newCard, aParent);
+ if (aRoot)
+ NotifyCardEntryChange(AB_NotifyInserted, newCard, aRoot);
+ }
+ else if (!aInMailingList) {
+ nsresult rv;
+ nsCOMPtr<nsIAddrDBListener> parentListener(do_QueryInterface(aParent, &rv));
+
+ // Ensure the parent is in the listener list (and hence wants to be notified)
+ if (NS_SUCCEEDED(rv) && m_ChangeListeners.Contains(parentListener))
+ parentListener->OnCardEntryChange(AB_NotifyInserted, aPCard, aParent);
+ }
+ else {
+ NotifyCardEntryChange(AB_NotifyPropertyChanged, aPCard, aParent);
+ }
+
+ //add a column with address row id to the list row
+ mdb_token listAddressColumnToken;
+
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, aPos);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ mdbOid outOid;
+
+ if (NS_SUCCEEDED(pCardRow->GetOid(m_mdbEnv, &outOid)))
+ {
+ //save address row ID to the list row
+ err = AddIntColumn(aPListRow, listAddressColumnToken, outOid.mOid_Id);
+ }
+ NS_RELEASE(pCardRow);
+
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::AddListAttributeColumnsToRow(nsIAbDirectory *list, nsIMdbRow *listRow, nsIAbDirectory *aParent)
+{
+ nsresult err = NS_OK;
+
+ if ((!list && !listRow) || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdbOid rowOid, tableOid;
+ m_mdbPabTable->GetOid(m_mdbEnv, &tableOid);
+ listRow->GetOid(m_mdbEnv, &rowOid);
+
+ nsCOMPtr<nsIAbMDBDirectory> dblist(do_QueryInterface(list,&err));
+ if (NS_SUCCEEDED(err))
+ dblist->SetDbRowID(rowOid.mOid_Id);
+
+ // add the row to the singleton table.
+ if (NS_SUCCEEDED(err) && listRow)
+ {
+ nsString unicodeStr;
+
+ list->GetDirName(unicodeStr);
+ if (!unicodeStr.IsEmpty())
+ AddUnicodeToColumn(listRow, m_ListNameColumnToken, m_LowerListNameColumnToken, unicodeStr.get());
+
+ list->GetListNickName(unicodeStr);
+ AddListNickName(listRow, NS_ConvertUTF16toUTF8(unicodeStr).get());
+
+ list->GetDescription(unicodeStr);
+ AddListDescription(listRow, NS_ConvertUTF16toUTF8(unicodeStr).get());
+
+ // XXX todo, this code has problems if you manually enter duplicate emails.
+ nsCOMPtr<nsIMutableArray> pAddressLists;
+ list->GetAddressLists(getter_AddRefs(pAddressLists));
+
+ uint32_t count;
+ pAddressLists->GetLength(&count);
+
+ nsAutoString email;
+ uint32_t i, total;
+ total = 0;
+ for (i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIAbCard> pCard(do_QueryElementAt(pAddressLists, i, &err));
+
+ if (NS_FAILED(err))
+ continue;
+
+ pCard->GetPrimaryEmail(email);
+ if (!email.IsEmpty())
+ total++;
+ }
+ SetListAddressTotal(listRow, total);
+
+ uint32_t pos;
+ for (i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIAbCard> pCard(do_QueryElementAt(pAddressLists, i, &err));
+
+ if (NS_FAILED(err))
+ continue;
+
+ bool listHasCard = false;
+ err = list->HasCard(pCard, &listHasCard);
+
+ // start from 1
+ pos = i + 1;
+ pCard->GetPrimaryEmail(email);
+ if (!email.IsEmpty())
+ {
+ nsCOMPtr<nsIAbCard> pNewCard;
+ err = AddListCardColumnsToRow(pCard, listRow, pos, getter_AddRefs(pNewCard), listHasCard, list, aParent);
+ if (pNewCard)
+ pAddressLists->ReplaceElementAt(pNewCard, i, false);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+uint32_t nsAddrDatabase::GetListAddressTotal(nsIMdbRow* listRow)
+{
+ uint32_t count = 0;
+ GetIntColumn(listRow, m_ListTotalColumnToken, &count, 0);
+ return count;
+}
+
+NS_IMETHODIMP nsAddrDatabase::SetListAddressTotal(nsIMdbRow* aListRow, uint32_t aTotal)
+{
+ return AddIntColumn(aListRow, m_ListTotalColumnToken, aTotal);
+}
+
+NS_IMETHODIMP nsAddrDatabase::FindRowByCard(nsIAbCard * aCard,nsIMdbRow **aRow)
+{
+ nsString primaryEmail;
+ aCard->GetPrimaryEmail(primaryEmail);
+ return GetRowForCharColumn(primaryEmail.get(), m_PriEmailColumnToken,
+ true, true, aRow, nullptr);
+}
+
+nsresult nsAddrDatabase::GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos, nsIMdbRow** cardRow)
+{
+ if (!m_mdbStore || !listRow || !cardRow || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdb_token listAddressColumnToken;
+
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ nsAutoString tempString;
+ mdb_id rowID;
+ nsresult err = GetIntColumn(listRow, listAddressColumnToken, (uint32_t*)&rowID, 0);
+ NS_ENSURE_SUCCESS(err, err);
+
+ return GetCardRowByRowID(rowID, cardRow);
+}
+
+NS_IMETHODIMP nsAddrDatabase::CreateMailListAndAddToDB(nsIAbDirectory *aNewList, bool aNotify /* = FALSE */, nsIAbDirectory *aParent)
+{
+ if (!aNewList || !m_mdbPabTable || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsIMdbRow *listRow;
+ nsresult err = GetNewListRow(&listRow);
+
+ if (NS_SUCCEEDED(err) && listRow)
+ {
+ AddListAttributeColumnsToRow(aNewList, listRow, aParent);
+ AddRecordKeyColumnToRow(listRow);
+ nsresult merror = m_mdbPabTable->AddRow(m_mdbEnv, listRow);
+ NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIAbCard> listCard;
+ CreateABListCard(listRow, getter_AddRefs(listCard));
+ NotifyCardEntryChange(AB_NotifyInserted, listCard, aParent);
+
+ NS_RELEASE(listRow);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+void nsAddrDatabase::DeleteCardFromAllMailLists(mdb_id cardRowID)
+{
+ if (!m_mdbEnv)
+ return;
+
+ nsCOMPtr <nsIMdbTableRowCursor> rowCursor;
+ m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor));
+
+ if (rowCursor)
+ {
+ nsCOMPtr <nsIMdbRow> pListRow;
+ mdb_pos rowPos;
+ do
+ {
+ nsresult err = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(pListRow), &rowPos);
+
+ if (NS_SUCCEEDED(err) && pListRow)
+ {
+ mdbOid rowOid;
+
+ if (NS_SUCCEEDED(pListRow->GetOid(m_mdbEnv, &rowOid)))
+ {
+ if (IsListRowScopeToken(rowOid.mOid_Scope))
+ DeleteCardFromListRow(pListRow, cardRowID);
+ }
+ }
+ } while (pListRow);
+ }
+}
+
+NS_IMETHODIMP nsAddrDatabase::DeleteCard(nsIAbCard *aCard, bool aNotify, nsIAbDirectory *aParent)
+{
+ if (!aCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ bool bIsMailList = false;
+ aCard->GetIsMailList(&bIsMailList);
+
+ // get the right row
+ nsIMdbRow* pCardRow = nullptr;
+ mdbOid rowOid;
+
+ rowOid.mOid_Scope = bIsMailList ? m_ListRowScopeToken : m_CardRowScopeToken;
+
+ err = aCard->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, &pCardRow);
+ NS_ENSURE_SUCCESS(err,err);
+ if (!pCardRow)
+ return NS_OK;
+
+ // Reset the directory id
+ aCard->SetDirectoryId(EmptyCString());
+
+ // Add the deleted card to the deletedcards table
+ nsCOMPtr <nsIMdbRow> cardRow;
+ AddRowToDeletedCardsTable(aCard, getter_AddRefs(cardRow));
+ err = DeleteRow(m_mdbPabTable, pCardRow);
+
+ //delete the person card from all mailing list
+ if (!bIsMailList)
+ DeleteCardFromAllMailLists(rowOid.mOid_Id);
+
+ if (NS_SUCCEEDED(err)) {
+ if (aNotify)
+ NotifyCardEntryChange(AB_NotifyDeleted, aCard, aParent);
+ }
+ else
+ DeleteRowFromDeletedCardsTable(cardRow);
+
+ NS_RELEASE(pCardRow);
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::DeleteCardFromListRow(nsIMdbRow* pListRow, mdb_id cardRowID)
+{
+ NS_ENSURE_ARG_POINTER(pListRow);
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ uint32_t totalAddress = GetListAddressTotal(pListRow);
+
+ uint32_t pos;
+ for (pos = 1; pos <= totalAddress; pos++)
+ {
+ mdb_token listAddressColumnToken;
+ mdb_id rowID;
+
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ err = GetIntColumn(pListRow, listAddressColumnToken, (uint32_t*)&rowID, 0);
+
+ if (cardRowID == rowID)
+ {
+ if (pos == totalAddress)
+ err = pListRow->CutColumn(m_mdbEnv, listAddressColumnToken);
+ else
+ {
+ //replace the deleted one with the last one and delete the last one
+ mdb_id lastRowID;
+ mdb_token lastAddressColumnToken;
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, totalAddress);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &lastAddressColumnToken);
+
+ err = GetIntColumn(pListRow, lastAddressColumnToken, (uint32_t*)&lastRowID, 0);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = AddIntColumn(pListRow, listAddressColumnToken, lastRowID);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = pListRow->CutColumn(m_mdbEnv, lastAddressColumnToken);
+ NS_ENSURE_SUCCESS(err, err);
+ }
+
+ // Reset total count after the card has been deleted.
+ SetListAddressTotal(pListRow, totalAddress-1);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::DeleteCardFromMailList(nsIAbDirectory *mailList, nsIAbCard *card, bool aNotify)
+{
+ if (!card || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ // get the right row
+ nsIMdbRow* pListRow = nullptr;
+ mdbOid listRowOid;
+ listRowOid.mOid_Scope = m_ListRowScopeToken;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList,&err));
+ NS_ENSURE_SUCCESS(err, err);
+
+ dbmailList->GetDbRowID((uint32_t*)&listRowOid.mOid_Id);
+
+ err = m_mdbStore->GetRow(m_mdbEnv, &listRowOid, &pListRow);
+ NS_ENSURE_SUCCESS(err,err);
+ if (!pListRow)
+ return NS_OK;
+
+ uint32_t cardRowID;
+
+ err = card->GetPropertyAsUint32(kRowIDProperty, &cardRowID);
+ if (NS_FAILED(err))
+ return NS_ERROR_NULL_POINTER;
+
+ err = DeleteCardFromListRow(pListRow, cardRowID);
+ if (NS_SUCCEEDED(err) && aNotify) {
+ NotifyCardEntryChange(AB_NotifyDeleted, card, mailList);
+ }
+ NS_RELEASE(pListRow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::SetCardValue(nsIAbCard *card, const char *name, const char16_t *value, bool notify)
+{
+ NS_ENSURE_ARG_POINTER(card);
+ NS_ENSURE_ARG_POINTER(name);
+ NS_ENSURE_ARG_POINTER(value);
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsIMdbRow> cardRow;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+
+ // it might be that the caller always has a nsAbMDBCard
+ rv = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!cardRow)
+ return NS_OK;
+
+ mdb_token token;
+ rv = m_mdbStore->StringToToken(m_mdbEnv, name, &token);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return AddCharStringColumn(cardRow, token, NS_ConvertUTF16toUTF8(value).get());
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetCardValue(nsIAbCard *card, const char *name, char16_t **value)
+{
+ if (!m_mdbStore || !card || !name || !value || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsIMdbRow> cardRow;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+
+ // it might be that the caller always has a nsAbMDBCard
+ rv = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!cardRow) {
+ *value = nullptr;
+ // this can happen when adding cards when editing a mailing list
+ return NS_OK;
+ }
+
+ mdb_token token;
+ m_mdbStore->StringToToken(m_mdbEnv, name, &token);
+
+ // XXX fix me
+ // avoid extra copying and allocations (did dmb already do this on the trunk?)
+ nsAutoString tempString;
+ rv = GetStringColumn(cardRow, token, tempString);
+ if (NS_FAILED(rv)) {
+ // not all cards are going this column
+ *value = nullptr;
+ return NS_OK;
+ }
+
+ *value = NS_strdup(tempString.get());
+ if (!*value)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetDeletedCardList(nsIArray **aResult)
+{
+ if (!m_mdbEnv || !aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ *aResult = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> resultCardArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make sure the member is set properly
+ InitDeletedCardsTable(false);
+ if (m_mdbDeletedCardsTable)
+ {
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+ mdb_pos rowPos;
+ bool done = false;
+ nsCOMPtr<nsIMdbRow> currentRow;
+
+ m_mdbDeletedCardsTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor));
+ if (!rowCursor)
+ return NS_ERROR_FAILURE;
+ while (!done)
+ {
+ rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
+ if (currentRow && NS_SUCCEEDED(rv))
+ {
+ mdbOid rowOid;
+ if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid)))
+ {
+ nsCOMPtr<nsIAbCard> card;
+ rv = CreateCardFromDeletedCardsTable(currentRow, 0, getter_AddRefs(card));
+ if (NS_SUCCEEDED(rv)) {
+ resultCardArray->AppendElement(card, false);
+ }
+ }
+ }
+ else
+ done = true;
+ }
+ }
+
+ NS_IF_ADDREF(*aResult = resultCardArray);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetDeletedCardCount(uint32_t *aCount)
+{
+ // initialize count first
+ *aCount = 0;
+ InitDeletedCardsTable(false);
+ if (m_mdbDeletedCardsTable)
+ return m_mdbDeletedCardsTable->GetCount(m_mdbEnv, aCount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::PurgeDeletedCardTable()
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ if (m_mdbDeletedCardsTable) {
+ mdb_count cardCount=0;
+ // if not too many cards let it be
+ m_mdbDeletedCardsTable->GetCount(m_mdbEnv, &cardCount);
+ if(cardCount < PURGE_CUTOFF_COUNT)
+ return NS_OK;
+ uint32_t purgeTimeInSec;
+ PRTime2Seconds(PR_Now(), &purgeTimeInSec);
+ purgeTimeInSec -= (182*24*60*60); // six months in seconds
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+ nsresult rv = m_mdbDeletedCardsTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor));
+ while(NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMdbRow> currentRow;
+ mdb_pos rowPos;
+ rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
+ if(currentRow) {
+ uint32_t deletedTimeStamp = 0;
+ GetIntColumn(currentRow, m_LastModDateColumnToken, &deletedTimeStamp, 0);
+ // if record was deleted more than six months earlier, purge it
+ if(deletedTimeStamp && (deletedTimeStamp < purgeTimeInSec)) {
+ if(NS_SUCCEEDED(currentRow->CutAllColumns(m_mdbEnv)))
+ m_mdbDeletedCardsTable->CutRow(m_mdbEnv, currentRow);
+ }
+ else
+ // since the ordering in Mork is maintained and thus
+ // the cards added later appear on the top when retrieved
+ break;
+ }
+ else
+ break; // no more row
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::EditCard(nsIAbCard *aCard, bool aNotify, nsIAbDirectory *aParent)
+{
+ // XXX make sure this isn't getting called when we're just editing one or two well known fields
+ if (!aCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ nsCOMPtr <nsIMdbRow> cardRow;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+
+ uint32_t nowInSeconds;
+ PRTime now = PR_Now();
+ PRTime2Seconds(now, &nowInSeconds);
+ aCard->SetPropertyAsUint32(kLastModifiedDateProperty, nowInSeconds);
+ err = aCard->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow));
+ NS_ENSURE_SUCCESS(err, err);
+
+ if (!cardRow)
+ return NS_OK;
+
+ err = AddAttributeColumnsToRow(aCard, cardRow);
+ NS_ENSURE_SUCCESS(err, err);
+
+ if (aNotify)
+ NotifyCardEntryChange(AB_NotifyPropertyChanged, aCard, aParent);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::ContainsCard(nsIAbCard *card, bool *hasCard)
+{
+ if (!card || !m_mdbPabTable || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ mdb_bool hasOid;
+ mdbOid rowOid;
+ bool bIsMailList;
+
+ card->GetIsMailList(&bIsMailList);
+
+ if (bIsMailList)
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+ else
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+
+ err = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = m_mdbPabTable->HasOid(m_mdbEnv, &rowOid, &hasOid);
+ if (NS_SUCCEEDED(err))
+ {
+ *hasCard = hasOid;
+ }
+
+ return err;
+}
+
+NS_IMETHODIMP nsAddrDatabase::DeleteMailList(nsIAbDirectory *aMailList,
+ nsIAbDirectory *aParent)
+{
+ if (!aMailList || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ // get the row
+ nsCOMPtr<nsIMdbRow> pListRow;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(aMailList, &err));
+ NS_ENSURE_SUCCESS(err, err);
+ dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id);
+
+ err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(pListRow));
+ NS_ENSURE_SUCCESS(err,err);
+
+ if (!pListRow)
+ return NS_OK;
+
+ nsCOMPtr<nsIAbCard> card;
+ err = CreateABListCard(pListRow, getter_AddRefs(card));
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = DeleteRow(m_mdbPabTable, pListRow);
+
+ if (NS_SUCCEEDED(err) && aParent)
+ NotifyCardEntryChange(AB_NotifyDeleted, card, aParent);
+
+ return err;
+}
+
+NS_IMETHODIMP nsAddrDatabase::EditMailList(nsIAbDirectory *mailList, nsIAbCard *listCard, bool notify)
+{
+ if (!mailList || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ nsIMdbRow* pListRow = nullptr;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList, &err));
+ NS_ENSURE_SUCCESS(err, err);
+ dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id);
+
+ err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, &pListRow);
+ NS_ENSURE_SUCCESS(err, err);
+
+ if (!pListRow)
+ return NS_OK;
+
+ err = AddListAttributeColumnsToRow(mailList, pListRow, mailList);
+ NS_ENSURE_SUCCESS(err, err);
+
+ if (notify)
+ {
+ NotifyListEntryChange(AB_NotifyPropertyChanged, mailList);
+
+ if (listCard)
+ {
+ NotifyCardEntryChange(AB_NotifyPropertyChanged, listCard, mailList);
+ }
+ }
+
+ NS_RELEASE(pListRow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::ContainsMailList(nsIAbDirectory *mailList, bool *hasList)
+{
+ if (!mailList || !m_mdbPabTable || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+ mdb_bool hasOid;
+ mdbOid rowOid;
+
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList,&err));
+ NS_ENSURE_SUCCESS(err, err);
+ dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id);
+
+ err = m_mdbPabTable->HasOid(m_mdbEnv, &rowOid, &hasOid);
+ if (NS_SUCCEEDED(err))
+ *hasList = hasOid;
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetNewRow(nsIMdbRow * *newRow)
+{
+ if (!m_mdbStore || !newRow || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ return m_mdbStore->NewRow(m_mdbEnv, m_CardRowScopeToken, newRow);
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetNewListRow(nsIMdbRow * *newRow)
+{
+ if (!m_mdbStore || !newRow || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ return m_mdbStore->NewRow(m_mdbEnv, m_ListRowScopeToken, newRow);
+}
+
+NS_IMETHODIMP nsAddrDatabase::AddCardRowToDB(nsIMdbRow *newRow)
+{
+ if (m_mdbPabTable && m_mdbEnv)
+ {
+ if (NS_SUCCEEDED(m_mdbPabTable->AddRow(m_mdbEnv, newRow)))
+ {
+ AddRecordKeyColumnToRow(newRow);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAddrDatabase::AddLdifListMember(nsIMdbRow* listRow, const char* value)
+{
+ if (!m_mdbStore || !listRow || !value || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ uint32_t total = GetListAddressTotal(listRow);
+ //add member
+ nsAutoCString valueString(value);
+ nsAutoCString email;
+ int32_t emailPos = valueString.Find("mail=");
+ emailPos += strlen("mail=");
+ email = Substring(valueString, emailPos);
+ nsCOMPtr <nsIMdbRow> cardRow;
+ // Please DO NOT change the 3rd param of GetRowFromAttribute() call to
+ // true (ie, case insensitive) without reading bugs #128535 and #121478.
+ nsresult rv = GetRowFromAttribute(kPriEmailProperty, email, false /* retain case */,
+ getter_AddRefs(cardRow), nullptr);
+ if (NS_SUCCEEDED(rv) && cardRow)
+ {
+ mdbOid outOid;
+ mdb_id rowID = 0;
+ if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid)))
+ rowID = outOid.mOid_Id;
+
+ // start from 1
+ total += 1;
+ mdb_token listAddressColumnToken;
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, total);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ rv = AddIntColumn(listRow, listAddressColumnToken, rowID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetListAddressTotal(listRow, total);
+ }
+ return NS_OK;
+}
+
+
+void nsAddrDatabase::GetCharStringYarn(char* str, struct mdbYarn* strYarn)
+{
+ strYarn->mYarn_Grow = nullptr;
+ strYarn->mYarn_Buf = str;
+ strYarn->mYarn_Size = PL_strlen((const char *) strYarn->mYarn_Buf) + 1;
+ strYarn->mYarn_Fill = strYarn->mYarn_Size - 1;
+ strYarn->mYarn_Form = 0;
+}
+
+void nsAddrDatabase::GetStringYarn(const nsAString & aStr, struct mdbYarn* strYarn)
+{
+ strYarn->mYarn_Buf = ToNewUTF8String(aStr);
+ strYarn->mYarn_Size = PL_strlen((const char *) strYarn->mYarn_Buf) + 1;
+ strYarn->mYarn_Fill = strYarn->mYarn_Size - 1;
+ strYarn->mYarn_Form = 0;
+}
+
+void nsAddrDatabase::GetIntYarn(uint32_t nValue, struct mdbYarn* intYarn)
+{
+ intYarn->mYarn_Fill = intYarn->mYarn_Size;
+ intYarn->mYarn_Form = 0;
+ intYarn->mYarn_Grow = nullptr;
+
+ PR_snprintf((char*)intYarn->mYarn_Buf, intYarn->mYarn_Size, "%lx", nValue);
+ intYarn->mYarn_Fill = PL_strlen((const char *) intYarn->mYarn_Buf);
+}
+
+nsresult nsAddrDatabase::AddCharStringColumn(nsIMdbRow* cardRow, mdb_column inColumn, const char* str)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ struct mdbYarn yarn;
+
+ GetCharStringYarn((char *) str, &yarn);
+ nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn);
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::AddStringColumn(nsIMdbRow* aCardRow, mdb_column aInColumn, const nsAString & aStr)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ struct mdbYarn yarn;
+
+ GetStringYarn(aStr, &yarn);
+ nsresult err = aCardRow->AddColumn(m_mdbEnv, aInColumn, &yarn);
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::AddIntColumn(nsIMdbRow* cardRow, mdb_column inColumn, uint32_t nValue)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ struct mdbYarn yarn;
+ char yarnBuf[100];
+
+ yarn.mYarn_Buf = (void *) yarnBuf;
+ yarn.mYarn_Size = sizeof(yarnBuf);
+ GetIntYarn(nValue, &yarn);
+ nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn);
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::AddBoolColumn(nsIMdbRow* cardRow, mdb_column inColumn, bool bValue)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ struct mdbYarn yarn;
+ char yarnBuf[100];
+
+ yarn.mYarn_Buf = (void *) yarnBuf;
+ yarn.mYarn_Size = sizeof(yarnBuf);
+
+ GetIntYarn(bValue ? 1 : 0, &yarn);
+
+ nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn);
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::GetStringColumn(nsIMdbRow *cardRow, mdb_token outToken, nsString& str)
+{
+ nsresult err = NS_ERROR_NULL_POINTER;
+ nsIMdbCell *cardCell;
+
+ if (cardRow && m_mdbEnv)
+ {
+ err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
+ if (NS_SUCCEEDED(err) && cardCell)
+ {
+ struct mdbYarn yarn;
+ cardCell->AliasYarn(m_mdbEnv, &yarn);
+ NS_ConvertUTF8toUTF16 uniStr((const char*) yarn.mYarn_Buf, yarn.mYarn_Fill);
+ if (!uniStr.IsEmpty())
+ str.Assign(uniStr);
+ else
+ err = NS_ERROR_FAILURE;
+ cardCell->Release(); // always release ref
+ }
+ else
+ err = NS_ERROR_FAILURE;
+ }
+ return err;
+}
+
+void nsAddrDatabase::YarnToUInt32(struct mdbYarn *yarn, uint32_t *pResult)
+{
+ uint8_t numChars = std::min<mdb_fill>(8, yarn->mYarn_Fill);
+ *pResult = MsgUnhex((char *) yarn->mYarn_Buf, numChars);
+}
+
+nsresult nsAddrDatabase::GetIntColumn
+(nsIMdbRow *cardRow, mdb_token outToken, uint32_t* pValue, uint32_t defaultValue)
+{
+ nsresult err = NS_ERROR_NULL_POINTER;
+ nsIMdbCell *cardCell;
+
+ if (pValue)
+ *pValue = defaultValue;
+ if (cardRow && m_mdbEnv)
+ {
+ err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
+ if (NS_SUCCEEDED(err) && cardCell)
+ {
+ struct mdbYarn yarn;
+ cardCell->AliasYarn(m_mdbEnv, &yarn);
+ YarnToUInt32(&yarn, pValue);
+ cardCell->Release();
+ }
+ else
+ err = NS_ERROR_FAILURE;
+ }
+ return err;
+}
+
+nsresult nsAddrDatabase::GetBoolColumn(nsIMdbRow *cardRow, mdb_token outToken, bool* pValue)
+{
+ NS_ENSURE_ARG_POINTER(pValue);
+
+ nsresult err = NS_ERROR_NULL_POINTER;
+ nsIMdbCell *cardCell;
+ uint32_t nValue = 0;
+
+ if (cardRow && m_mdbEnv)
+ {
+ err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
+ if (NS_SUCCEEDED(err) && cardCell)
+ {
+ struct mdbYarn yarn;
+ cardCell->AliasYarn(m_mdbEnv, &yarn);
+ YarnToUInt32(&yarn, &nValue);
+ cardCell->Release();
+ }
+ else
+ err = NS_ERROR_FAILURE;
+ }
+
+ *pValue = nValue ? true : false;
+ return err;
+}
+
+/* value is UTF8 string */
+NS_IMETHODIMP nsAddrDatabase::AddPrimaryEmail(nsIMdbRow *aRow, const char *aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+
+ nsresult rv = AddCharStringColumn(aRow, m_PriEmailColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AddLowercaseColumn(aRow, m_LowerPriEmailColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+/* value is UTF8 string */
+NS_IMETHODIMP nsAddrDatabase::Add2ndEmail(nsIMdbRow *aRow, const char *aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+
+ nsresult rv = AddCharStringColumn(aRow, m_2ndEmailColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AddLowercaseColumn(aRow, m_Lower2ndEmailColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+/* value is UTF8 string */
+NS_IMETHODIMP nsAddrDatabase::AddListName(nsIMdbRow *aRow, const char *aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+
+ nsresult rv = AddCharStringColumn(aRow, m_ListNameColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AddLowercaseColumn(aRow, m_LowerListNameColumnToken, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+/*
+columnValue is UTF8 string, need to convert back to lowercase unicode then
+back to UTF8 string
+*/
+nsresult nsAddrDatabase::AddLowercaseColumn
+(nsIMdbRow * row, mdb_token columnToken, const char* columnValue)
+{
+ nsresult rv = NS_OK;
+ if (columnValue)
+ {
+ NS_ConvertUTF8toUTF16 newUnicodeString(columnValue);
+ ToLowerCase(newUnicodeString);
+ rv = AddCharStringColumn(row, columnToken, NS_ConvertUTF16toUTF8(newUnicodeString).get());
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAddrDatabase::InitCardFromRow(nsIAbCard *newCard, nsIMdbRow* cardRow)
+{
+ nsresult rv = NS_OK;
+ if (!newCard || !cardRow || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsIMdbRowCellCursor> cursor;
+ nsCOMPtr<nsIMdbCell> cell;
+
+ rv = cardRow->GetRowCellCursor(m_mdbEnv, -1, getter_AddRefs(cursor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mdb_column columnNumber;
+ char columnName[100];
+ struct mdbYarn colYarn = {columnName, 0, sizeof(columnName), 0, 0, nullptr};
+ struct mdbYarn cellYarn;
+
+ do
+ {
+ rv = cursor->NextCell(m_mdbEnv, getter_AddRefs(cell), &columnNumber, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!cell)
+ break;
+
+ // Get the value of the cell
+ cell->AliasYarn(m_mdbEnv, &cellYarn);
+ NS_ConvertUTF8toUTF16 value(static_cast<const char*>(cellYarn.mYarn_Buf),
+ cellYarn.mYarn_Fill);
+
+ if (!value.IsEmpty())
+ {
+ // Get the column of the cell
+ // Mork makes this so hard...
+ rv = m_mdbStore->TokenToString(m_mdbEnv, columnNumber, &colYarn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char *name = PL_strndup(static_cast<char *>(colYarn.mYarn_Buf),
+ colYarn.mYarn_Fill);
+ newCard->SetPropertyAsAString(name, value);
+ PL_strfree(name);
+ }
+ } while (true);
+
+ uint32_t key = 0;
+ rv = GetIntColumn(cardRow, m_RecordKeyColumnToken, &key, 0);
+ if (NS_SUCCEEDED(rv))
+ newCard->SetPropertyAsUint32(kRecordKeyColumn, key);
+
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::GetListCardFromDB(nsIAbCard *listCard, nsIMdbRow* listRow)
+{
+ nsresult err = NS_OK;
+ if (!listCard || !listRow)
+ return NS_ERROR_NULL_POINTER;
+
+ nsAutoString tempString;
+
+ err = GetStringColumn(listRow, m_ListNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ listCard->SetDisplayName(tempString);
+ listCard->SetLastName(tempString);
+ }
+ err = GetStringColumn(listRow, m_ListNickNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ listCard->SetPropertyAsAString(kNicknameProperty, tempString);
+ }
+ err = GetStringColumn(listRow, m_ListDescriptionColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ listCard->SetPropertyAsAString(kNotesProperty, tempString);
+ }
+ uint32_t key = 0;
+ err = GetIntColumn(listRow, m_RecordKeyColumnToken, &key, 0);
+ if (NS_SUCCEEDED(err))
+ listCard->SetPropertyAsUint32(kRecordKeyColumn, key);
+ return err;
+}
+
+nsresult nsAddrDatabase::GetListFromDB(nsIAbDirectory *newList, nsIMdbRow* listRow)
+{
+ nsresult err = NS_OK;
+ if (!newList || !listRow || !m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsAutoString tempString;
+
+ err = GetStringColumn(listRow, m_ListNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ newList->SetDirName(tempString);
+ }
+ err = GetStringColumn(listRow, m_ListNickNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ newList->SetListNickName(tempString);
+ }
+ err = GetStringColumn(listRow, m_ListDescriptionColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
+ {
+ newList->SetDescription(tempString);
+ }
+
+ nsCOMPtr<nsIAbMDBDirectory> dbnewList(do_QueryInterface(newList, &err));
+ NS_ENSURE_SUCCESS(err, err);
+
+ uint32_t totalAddress = GetListAddressTotal(listRow);
+ uint32_t pos;
+ for (pos = 1; pos <= totalAddress; ++pos)
+ {
+ mdb_token listAddressColumnToken;
+ mdb_id rowID;
+
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ nsCOMPtr <nsIMdbRow> cardRow;
+ err = GetIntColumn(listRow, listAddressColumnToken, (uint32_t*)&rowID, 0);
+ NS_ENSURE_SUCCESS(err, err);
+ err = GetCardRowByRowID(rowID, getter_AddRefs(cardRow));
+ NS_ENSURE_SUCCESS(err, err);
+
+ if (cardRow)
+ {
+ nsCOMPtr<nsIAbCard> card;
+ err = CreateABCard(cardRow, 0, getter_AddRefs(card));
+
+ if(NS_SUCCEEDED(err))
+ dbnewList->AddAddressToList(card);
+ }
+// NS_IF_ADDREF(card);
+ }
+
+ return err;
+}
+
+class nsAddrDBEnumerator : public nsISimpleEnumerator, public nsIAddrDBListener
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+ NS_DECL_NSIADDRDBLISTENER
+ // nsAddrDBEnumerator methods:
+
+ explicit nsAddrDBEnumerator(nsAddrDatabase* aDb);
+ void Clear();
+protected:
+ virtual ~nsAddrDBEnumerator();
+ RefPtr<nsAddrDatabase> mDb;
+ nsIMdbTable *mDbTable;
+ nsCOMPtr<nsIMdbTableRowCursor> mRowCursor;
+ nsCOMPtr<nsIMdbRow> mCurrentRow;
+ mdb_pos mRowPos;
+};
+
+nsAddrDBEnumerator::nsAddrDBEnumerator(nsAddrDatabase* aDb)
+ : mDb(aDb),
+ mDbTable(aDb->GetPabTable()),
+ mRowPos(-1)
+{
+ if (aDb)
+ aDb->AddListener(this);
+}
+
+nsAddrDBEnumerator::~nsAddrDBEnumerator()
+{
+ Clear();
+}
+
+void nsAddrDBEnumerator::Clear()
+{
+ mRowCursor = nullptr;
+ mCurrentRow = nullptr;
+ mDbTable = nullptr;
+ if (mDb)
+ mDb->RemoveListener(this);
+}
+
+NS_IMPL_ISUPPORTS(nsAddrDBEnumerator, nsISimpleEnumerator, nsIAddrDBListener)
+
+NS_IMETHODIMP
+nsAddrDBEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ if (!mDbTable || !mDb->GetEnv())
+ {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+ mDbTable->GetTableRowCursor(mDb->GetEnv(), mRowPos,
+ getter_AddRefs(rowCursor));
+ NS_ENSURE_TRUE(rowCursor, NS_ERROR_FAILURE);
+
+ mdbOid rowOid;
+ rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr);
+ while (rowOid.mOid_Id != (mdb_id)-1)
+ {
+ if (mDb->IsListRowScopeToken(rowOid.mOid_Scope) ||
+ mDb->IsCardRowScopeToken(rowOid.mOid_Scope))
+ {
+ *aResult = true;
+
+ return NS_OK;
+ }
+
+ if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope))
+ {
+ return NS_ERROR_FAILURE;
+ }
+
+ rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAddrDBEnumerator::GetNext(nsISupports **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (!mDbTable || !mDb->GetEnv())
+ {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!mRowCursor)
+ {
+ mDbTable->GetTableRowCursor(mDb->GetEnv(), -1,
+ getter_AddRefs(mRowCursor));
+ NS_ENSURE_TRUE(mRowCursor, NS_ERROR_FAILURE);
+ }
+
+ nsCOMPtr<nsIAbCard> resultCard;
+ mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow), &mRowPos);
+ while (mCurrentRow)
+ {
+ mdbOid rowOid;
+ if (NS_SUCCEEDED(mCurrentRow->GetOid(mDb->GetEnv(), &rowOid)))
+ {
+ nsresult rv;
+ if (mDb->IsListRowScopeToken(rowOid.mOid_Scope))
+ {
+ rv = mDb->CreateABListCard(mCurrentRow,
+ getter_AddRefs(resultCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (mDb->IsCardRowScopeToken(rowOid.mOid_Scope))
+ {
+ rv = mDb->CreateABCard(mCurrentRow, 0,
+ getter_AddRefs(resultCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope))
+ {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (resultCard)
+ {
+ return CallQueryInterface(resultCard, aResult);
+ }
+ }
+
+ mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow),
+ &mRowPos);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAddrDBEnumerator::OnCardAttribChange(uint32_t abCode)
+{
+ return NS_OK;
+}
+
+/* void onCardEntryChange (in unsigned long aAbCode, in nsIAbCard aCard, in nsIAbDirectory aParent); */
+NS_IMETHODIMP nsAddrDBEnumerator::OnCardEntryChange(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent)
+{
+ return NS_OK;
+}
+
+/* void onListEntryChange (in unsigned long abCode, in nsIAbDirectory list); */
+NS_IMETHODIMP nsAddrDBEnumerator::OnListEntryChange(uint32_t abCode, nsIAbDirectory *list)
+{
+ return NS_OK;
+}
+
+/* void onAnnouncerGoingAway (); */
+NS_IMETHODIMP nsAddrDBEnumerator::OnAnnouncerGoingAway()
+{
+ Clear();
+ return NS_OK;
+}
+
+class nsListAddressEnumerator final : public nsISimpleEnumerator
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsListAddressEnumerator methods:
+
+ nsListAddressEnumerator(nsAddrDatabase* aDb, mdb_id aRowID);
+
+protected:
+ ~nsListAddressEnumerator() {}
+ RefPtr<nsAddrDatabase> mDb;
+ nsIMdbTable *mDbTable;
+ nsCOMPtr<nsIMdbRow> mListRow;
+ mdb_id mListRowID;
+ uint32_t mAddressTotal;
+ uint16_t mAddressPos;
+};
+
+nsListAddressEnumerator::nsListAddressEnumerator(nsAddrDatabase* aDb,
+ mdb_id aRowID)
+ : mDb(aDb),
+ mDbTable(aDb->GetPabTable()),
+ mListRowID(aRowID),
+ mAddressPos(0)
+{
+ mDb->GetListRowByRowID(mListRowID, getter_AddRefs(mListRow));
+ mAddressTotal = aDb->GetListAddressTotal(mListRow);
+}
+
+NS_IMPL_ISUPPORTS(nsListAddressEnumerator, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+nsListAddressEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = false;
+
+ if (!mDbTable || !mDb->GetEnv())
+ {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // In some cases it is possible that GetAddressRowByPos returns success,
+ // but currentRow is null. This is typically due to the fact that a card
+ // has been deleted from the parent and not the list. Whilst we have fixed
+ // that there are still a few dbs around there that we need to support
+ // correctly. Therefore, whilst processing lists ensure that we don't return
+ // false if the only thing stopping us is a blank row, just skip it and try
+ // the next one.
+ while (mAddressPos < mAddressTotal)
+ {
+ nsCOMPtr<nsIMdbRow> currentRow;
+ nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos + 1,
+ getter_AddRefs(currentRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (currentRow)
+ {
+ *aResult = true;
+ break;
+ }
+
+ ++mAddressPos;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsListAddressEnumerator::GetNext(nsISupports **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (!mDbTable || !mDb->GetEnv())
+ {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (++mAddressPos <= mAddressTotal)
+ {
+ nsCOMPtr<nsIMdbRow> currentRow;
+ nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos,
+ getter_AddRefs(currentRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> resultCard;
+ rv = mDb->CreateABCard(currentRow, mListRowID,
+ getter_AddRefs(resultCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(resultCard, aResult);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsAddrDatabase::EnumerateCards(nsIAbDirectory *directory, nsISimpleEnumerator **result)
+{
+ nsAddrDBEnumerator* e = new nsAddrDBEnumerator(this);
+ m_dbDirectory = do_GetWeakReference(directory);
+ if (!e)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(e);
+ *result = e;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetMailingListsFromDB(nsIAbDirectory *parentDir)
+{
+ nsCOMPtr<nsIAbDirectory> resultList;
+ nsIMdbTableRowCursor* rowCursor = nullptr;
+ nsCOMPtr<nsIMdbRow> currentRow;
+ mdb_pos rowPos;
+ bool done = false;
+
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ m_dbDirectory = do_GetWeakReference(parentDir);
+
+ nsIMdbTable* dbTable = GetPabTable();
+
+ if (!dbTable)
+ return NS_ERROR_FAILURE;
+
+ dbTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor);
+ if (!rowCursor)
+ return NS_ERROR_FAILURE;
+
+ while (!done)
+ {
+ nsresult rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
+ if (currentRow && NS_SUCCEEDED(rv))
+ {
+ mdbOid rowOid;
+
+ if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid)))
+ {
+ if (IsListRowScopeToken(rowOid.mOid_Scope))
+ rv = CreateABList(currentRow, getter_AddRefs(resultList));
+ }
+ }
+ else
+ done = true;
+ }
+ NS_IF_RELEASE(rowCursor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::EnumerateListAddresses(nsIAbDirectory *directory, nsISimpleEnumerator **result)
+{
+ nsresult rv = NS_OK;
+ mdb_id rowID;
+
+ nsCOMPtr<nsIAbMDBDirectory> dbdirectory(do_QueryInterface(directory,&rv));
+
+ if(NS_SUCCEEDED(rv))
+ {
+ dbdirectory->GetDbRowID((uint32_t*)&rowID);
+
+ nsListAddressEnumerator* e = new nsListAddressEnumerator(this, rowID);
+ m_dbDirectory = do_GetWeakReference(directory);
+ if (!e)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(e);
+ *result = e;
+ }
+ return rv;
+}
+
+nsresult nsAddrDatabase::CreateCardFromDeletedCardsTable(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result)
+{
+ if (!cardRow || !m_mdbEnv || !result)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ mdbOid outOid;
+ mdb_id rowID = 0;
+
+ if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid)))
+ rowID = outOid.mOid_Id;
+
+ if(NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIAbCard> personCard;
+ personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ InitCardFromRow(personCard, cardRow);
+ personCard->SetPropertyAsUint32(kRowIDProperty, rowID);
+
+ NS_IF_ADDREF(*result = personCard);
+ }
+
+ return rv;
+}
+
+nsresult nsAddrDatabase::CreateCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result)
+{
+ if (!cardRow || !m_mdbEnv || !result)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ mdbOid outOid;
+ mdb_id rowID = 0;
+
+ if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid)))
+ rowID = outOid.mOid_Id;
+
+ if(NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIAbCard> personCard;
+ personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ InitCardFromRow(personCard, cardRow);
+ personCard->SetPropertyAsUint32(kRowIDProperty, rowID);
+
+ nsAutoCString id;
+ id.AppendInt(rowID);
+ personCard->SetLocalId(id);
+
+ nsCOMPtr<nsIAbDirectory> abDir(do_QueryReferent(m_dbDirectory));
+ if (abDir)
+ abDir->GetUuid(id);
+
+ personCard->SetDirectoryId(id);
+
+ NS_IF_ADDREF(*result = personCard);
+ }
+
+ return rv;
+}
+
+nsresult nsAddrDatabase::CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result)
+{
+ return CreateCard(cardRow, listRowID, result);
+}
+
+/* create a card for mailing list in the address book */
+nsresult nsAddrDatabase::CreateABListCard(nsIMdbRow* listRow, nsIAbCard **result)
+{
+ if (!listRow || !m_mdbEnv || !result)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ mdbOid outOid;
+ mdb_id rowID = 0;
+
+ if (NS_SUCCEEDED(listRow->GetOid(m_mdbEnv, &outOid)))
+ rowID = outOid.mOid_Id;
+
+ char* listURI = nullptr;
+
+ nsAutoString fileName;
+ rv = m_dbName->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ listURI = PR_smprintf("%s%s/MailList%ld", kMDBDirectoryRoot, NS_ConvertUTF16toUTF8(fileName).get(), rowID);
+
+ nsCOMPtr<nsIAbCard> personCard;
+ nsCOMPtr<nsIAbMDBDirectory> dbm_dbDirectory(do_QueryReferent(m_dbDirectory,
+ &rv));
+ if (NS_SUCCEEDED(rv) && dbm_dbDirectory)
+ {
+ personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (personCard)
+ {
+ GetListCardFromDB(personCard, listRow);
+
+ personCard->SetPropertyAsUint32(kRowIDProperty, rowID);
+ personCard->SetIsMailList(true);
+ personCard->SetMailListURI(listURI);
+
+ nsAutoCString id;
+ id.AppendInt(rowID);
+ personCard->SetLocalId(id);
+
+ nsCOMPtr<nsIAbDirectory> abDir(do_QueryReferent(m_dbDirectory));
+ if (abDir)
+ abDir->GetUuid(id);
+ personCard->SetDirectoryId(id);
+ }
+
+ NS_IF_ADDREF(*result = personCard);
+ }
+ if (listURI)
+ PR_smprintf_free(listURI);
+
+ return rv;
+}
+
+/* create a sub directory for mailing list in the address book left pane */
+nsresult nsAddrDatabase::CreateABList(nsIMdbRow* listRow, nsIAbDirectory **result)
+{
+ nsresult rv = NS_OK;
+
+ if (!listRow || !m_mdbEnv || !result)
+ return NS_ERROR_NULL_POINTER;
+
+ mdbOid outOid;
+ mdb_id rowID = 0;
+
+ if (NS_SUCCEEDED(listRow->GetOid(m_mdbEnv, &outOid)))
+ rowID = outOid.mOid_Id;
+
+ char* listURI = nullptr;
+
+ nsAutoString fileName;
+ m_dbName->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ listURI = PR_smprintf("%s%s/MailList%ld", kMDBDirectoryRoot, NS_ConvertUTF16toUTF8(fileName).get(), rowID);
+
+ nsCOMPtr<nsIAbDirectory> mailList;
+ nsCOMPtr<nsIAbMDBDirectory> dbm_dbDirectory(do_QueryReferent(m_dbDirectory,
+ &rv));
+ if (NS_SUCCEEDED(rv) && dbm_dbDirectory)
+ {
+ rv = dbm_dbDirectory->AddDirectory(listURI, getter_AddRefs(mailList));
+
+ nsCOMPtr<nsIAbMDBDirectory> dbmailList (do_QueryInterface(mailList, &rv));
+
+ if (mailList)
+ {
+ // if we are using turbo, and we "exit" and restart with the same profile
+ // the current mailing list will still be in memory, so when we do
+ // GetResource() and QI, we'll get it again.
+ // in that scenario, the mailList that we pass in will already be
+ // be a mailing list, with a valid row and all the entries
+ // in that scenario, we can skip GetListFromDB(), which would have
+ // have added all the cards to the list again.
+ // see bug #134743
+ mdb_id existingID;
+ dbmailList->GetDbRowID(&existingID);
+ if (existingID != rowID) {
+ // Ensure IsMailList is set up first.
+ mailList->SetIsMailList(true);
+ GetListFromDB(mailList, listRow);
+ dbmailList->SetDbRowID(rowID);
+ }
+
+ dbm_dbDirectory->AddMailListToDirectory(mailList);
+ NS_IF_ADDREF(*result = mailList);
+ }
+ }
+
+ if (listURI)
+ PR_smprintf_free(listURI);
+
+ return rv;
+}
+
+nsresult nsAddrDatabase::GetCardRowByRowID(mdb_id rowID, nsIMdbRow **dbRow)
+{
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+ rowOid.mOid_Id = rowID;
+
+ return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow);
+}
+
+nsresult nsAddrDatabase::GetListRowByRowID(mdb_id rowID, nsIMdbRow **dbRow)
+{
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+ rowOid.mOid_Id = rowID;
+
+ return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow);
+}
+
+nsresult nsAddrDatabase::GetRowFromAttribute(const char *aName,
+ const nsACString &aUTF8Value,
+ bool aCaseInsensitive,
+ nsIMdbRow **aCardRow,
+ mdb_pos *aRowPos)
+{
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ENSURE_ARG_POINTER(aCardRow);
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdb_token token;
+ m_mdbStore->StringToToken(m_mdbEnv, aName, &token);
+ NS_ConvertUTF8toUTF16 newUnicodeString(aUTF8Value);
+
+ return GetRowForCharColumn(newUnicodeString.get(), token, true,
+ aCaseInsensitive, aCardRow, aRowPos);
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetCardFromAttribute(nsIAbDirectory *aDirectory,
+ const char *aName,
+ const nsACString &aUTF8Value,
+ bool aCaseInsensitive,
+ nsIAbCard **aCardResult)
+{
+ NS_ENSURE_ARG_POINTER(aCardResult);
+
+ m_dbDirectory = do_GetWeakReference(aDirectory);
+ nsCOMPtr<nsIMdbRow> cardRow;
+ if (NS_SUCCEEDED(GetRowFromAttribute(aName, aUTF8Value, aCaseInsensitive,
+ getter_AddRefs(cardRow), nullptr)) && cardRow)
+ return CreateABCard(cardRow, 0, aCardResult);
+
+ *aCardResult = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetCardsFromAttribute(nsIAbDirectory *aDirectory,
+ const char *aName,
+ const nsACString & aUTF8Value,
+ bool aCaseInsensitive,
+ nsISimpleEnumerator **cards)
+{
+ NS_ENSURE_ARG_POINTER(cards);
+
+ m_dbDirectory = do_GetWeakReference(aDirectory);
+ nsCOMPtr<nsIMdbRow> row;
+ bool done = false;
+ nsCOMArray<nsIAbCard> list;
+ nsCOMPtr<nsIAbCard> card;
+ mdb_pos rowPos = -1;
+
+ do
+ {
+ if (NS_SUCCEEDED(GetRowFromAttribute(aName, aUTF8Value, aCaseInsensitive,
+ getter_AddRefs(row), &rowPos)) && row)
+ {
+ if (NS_FAILED(CreateABCard(row, 0, getter_AddRefs(card))))
+ continue;
+ list.AppendObject(card);
+ }
+ else
+ done = true;
+ } while (!done);
+
+ return NS_NewArrayEnumerator(cards, list);
+}
+
+NS_IMETHODIMP nsAddrDatabase::AddListDirNode(nsIMdbRow * listRow)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoString parentURI;
+ rv = m_dbName->GetLeafName(parentURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ parentURI.Replace(0, 0, NS_LITERAL_STRING(kMDBDirectoryRoot));
+
+ nsCOMPtr<nsIAbDirectory> parentDir;
+ rv = abManager->GetDirectory(NS_ConvertUTF16toUTF8(parentURI),
+ getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (parentDir)
+ {
+ m_dbDirectory = do_GetWeakReference(parentDir);
+ nsCOMPtr<nsIAbDirectory> mailList;
+ rv = CreateABList(listRow, getter_AddRefs(mailList));
+ if (mailList)
+ {
+ nsCOMPtr<nsIAbMDBDirectory> dbparentDir(do_QueryInterface(parentDir, &rv));
+ if(NS_SUCCEEDED(rv))
+ dbparentDir->NotifyDirItemAdded(mailList);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAddrDatabase::FindMailListbyUnicodeName(const char16_t *listName, bool *exist)
+{
+ nsAutoString unicodeString(listName);
+ ToLowerCase(unicodeString);
+
+ nsCOMPtr <nsIMdbRow> listRow;
+ nsresult rv = GetRowForCharColumn(unicodeString.get(),
+ m_LowerListNameColumnToken, false,
+ false, getter_AddRefs(listRow), nullptr);
+ *exist = (NS_SUCCEEDED(rv) && listRow);
+ return rv;
+}
+
+NS_IMETHODIMP nsAddrDatabase::GetCardCount(uint32_t *count)
+{
+ nsresult rv;
+ mdb_count c;
+ rv = m_mdbPabTable->GetCount(m_mdbEnv, &c);
+ if (NS_SUCCEEDED(rv))
+ *count = c - 1; // Don't count LastRecordKey
+
+ return rv;
+}
+
+bool
+nsAddrDatabase::HasRowButDeletedForCharColumn(const char16_t *unicodeStr, mdb_column findColumn, bool aIsCard, nsIMdbRow **aFindRow)
+{
+ if (!m_mdbStore || !aFindRow || !m_mdbEnv)
+ return false;
+
+ mdbYarn sourceYarn;
+
+ NS_ConvertUTF16toUTF8 UTF8String(unicodeStr);
+ sourceYarn.mYarn_Buf = (void *) UTF8String.get();
+ sourceYarn.mYarn_Fill = UTF8String.Length();
+ sourceYarn.mYarn_Form = 0;
+ sourceYarn.mYarn_Size = sourceYarn.mYarn_Fill;
+
+ mdbOid outRowId;
+ nsresult rv;
+
+ if (aIsCard)
+ {
+ rv = m_mdbStore->FindRow(m_mdbEnv, m_CardRowScopeToken,
+ findColumn, &sourceYarn, &outRowId, aFindRow);
+
+ // no such card, so bail out early
+ if (NS_FAILED(rv) || !*aFindRow)
+ return false;
+
+ // we might not have loaded the "delete cards" table yet
+ // so do that (but don't create it, if we don't have one),
+ // so we can see if the row is really a delete card.
+ if (!m_mdbDeletedCardsTable)
+ rv = InitDeletedCardsTable(false);
+
+ // if still no deleted cards table, there are no deleted cards
+ if (!m_mdbDeletedCardsTable)
+ return false;
+
+ mdb_bool hasRow = false;
+ rv = m_mdbDeletedCardsTable->HasRow(m_mdbEnv, *aFindRow, &hasRow);
+ return (NS_SUCCEEDED(rv) && hasRow);
+ }
+
+ rv = m_mdbStore->FindRow(m_mdbEnv, m_ListRowScopeToken,
+ findColumn, &sourceYarn, &outRowId, aFindRow);
+ return (NS_SUCCEEDED(rv) && *aFindRow);
+}
+
+/* @param aRowPos Contains the row position for multiple calls. Should be
+ * instantiated to -1 on the first call. Or can be null
+ * if you are not making multiple calls.
+ */
+nsresult
+nsAddrDatabase::GetRowForCharColumn(const char16_t *unicodeStr,
+ mdb_column findColumn, bool aIsCard,
+ bool aCaseInsensitive,
+ nsIMdbRow **aFindRow,
+ mdb_pos *aRowPos)
+{
+ NS_ENSURE_ARG_POINTER(unicodeStr);
+ NS_ENSURE_ARG_POINTER(aFindRow);
+ NS_ENSURE_TRUE(m_mdbEnv && m_mdbPabTable, NS_ERROR_NULL_POINTER);
+
+ *aFindRow = nullptr;
+
+ // see bug #198303
+ // the addition of the m_mdbDeletedCardsTable table has complicated life in the addressbook
+ // (it was added for palm sync). until we fix the underlying problem, we have to jump through hoops
+ // in order to know if we have a row (think card) for a given column value (think email=foo@bar.com)
+ // there are 4 scenarios:
+ // 1) no cards with a match
+ // 2) at least one deleted card with a match, but no non-deleted cards
+ // 3) at least one non-deleted card with a match, but no deleted cards
+ // 4) at least one deleted card, and one non-deleted card with a match.
+ //
+ // if we have no cards that match (FindRow() returns nothing), we can bail early
+ // but if FindRow() returns something, we have to check if it is in the deleted table
+ // if not in the deleted table we can return the row (we found a non-deleted card)
+ // but if so, we have to search through the table of non-deleted cards
+ // for a match. If we find one, we return it. but if not, we report that there are no
+ // non-deleted cards. This is the expensive part. The worse case scenario is to have
+ // deleted lots of cards, and then have a lot of non-deleted cards.
+ // we'd have to call FindRow(), HasRow(), and then search the list of non-deleted cards
+ // each time we call GetRowForCharColumn().
+ if (!aRowPos && !HasRowButDeletedForCharColumn(unicodeStr, findColumn, aIsCard, aFindRow))
+ {
+ // If we have a row, it's the row for the non-delete card, so return NS_OK.
+ // If we don't have a row, there are two possible conditions: either the
+ // card does not exist, or we are doing case-insensitive searching and the
+ // value isn't lowercase.
+
+ // Valid result, return.
+ if (*aFindRow)
+ return NS_OK;
+
+ // We definitely don't have anything at this point if case-sensitive.
+ if (!aCaseInsensitive)
+ return NS_ERROR_FAILURE;
+ }
+
+ // check if there is a non-deleted card
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+ mdb_pos rowPos = -1;
+ bool done = false;
+ nsCOMPtr<nsIMdbRow> currentRow;
+ nsAutoString columnValue;
+
+ if (aRowPos)
+ rowPos = *aRowPos;
+
+ mdb_scope targetScope = aIsCard ? m_CardRowScopeToken : m_ListRowScopeToken;
+
+ m_mdbPabTable->GetTableRowCursor(m_mdbEnv, rowPos, getter_AddRefs(rowCursor));
+ if (!rowCursor)
+ return NS_ERROR_FAILURE;
+
+ while (!done)
+ {
+ nsresult rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
+ if (currentRow && NS_SUCCEEDED(rv))
+ {
+ mdbOid rowOid;
+ if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid)) && (rowOid.mOid_Scope == targetScope))
+ {
+ rv = GetStringColumn(currentRow, findColumn, columnValue);
+
+ bool equals = aCaseInsensitive ?
+ columnValue.Equals(unicodeStr, nsCaseInsensitiveStringComparator()) :
+ columnValue.Equals(unicodeStr);
+
+ if (NS_SUCCEEDED(rv) && equals)
+ {
+ NS_IF_ADDREF(*aFindRow = currentRow);
+ if (aRowPos)
+ *aRowPos = rowPos;
+ return NS_OK;
+ }
+ }
+ }
+ else
+ done = true;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsAddrDatabase::DeleteRow(nsIMdbTable* dbTable, nsIMdbRow* dbRow)
+{
+ if (!m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = dbRow->CutAllColumns(m_mdbEnv);
+ err = dbTable->CutRow(m_mdbEnv, dbRow);
+
+ return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+}
diff --git a/mailnews/addrbook/src/nsAddrDatabase.h b/mailnews/addrbook/src/nsAddrDatabase.h
new file mode 100644
index 000000000..6a12f1744
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddrDatabase.h
@@ -0,0 +1,438 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsAddrDatabase_H_
+#define _nsAddrDatabase_H_
+
+#include "mozilla/Attributes.h"
+#include "nsIAddrDatabase.h"
+#include "mdb.h"
+#include "nsStringGlue.h"
+#include "nsIAddrDBListener.h"
+#include "nsCOMPtr.h"
+#include "nsTObserverArray.h"
+#include "nsWeakPtr.h"
+
+typedef enum
+{
+ AB_NotifyInserted,
+ AB_NotifyDeleted,
+ AB_NotifyPropertyChanged
+} AB_NOTIFY_CODE;
+
+class nsAddrDatabase : public nsIAddrDatabase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIADDRDBANNOUNCER
+ //////////////////////////////////////////////////////////////////////////////
+ // nsIAddrDatabase methods:
+
+ NS_IMETHOD GetDbPath(nsIFile * *aDbPath) override;
+ NS_IMETHOD SetDbPath(nsIFile * aDbPath) override;
+ NS_IMETHOD Open(nsIFile *aMabFile, bool aCreate, bool upgrading, nsIAddrDatabase **pCardDB) override;
+ NS_IMETHOD Close(bool forceCommit) override;
+ NS_IMETHOD OpenMDB(nsIFile *dbName, bool create) override;
+ NS_IMETHOD CloseMDB(bool commit) override;
+ NS_IMETHOD Commit(uint32_t commitType) override;
+ NS_IMETHOD ForceClosed() override;
+
+ NS_IMETHOD CreateNewCardAndAddToDB(nsIAbCard *newCard, bool notify, nsIAbDirectory *parent) override;
+ NS_IMETHOD CreateNewListCardAndAddToDB(nsIAbDirectory *list, uint32_t listRowID, nsIAbCard *newCard, bool notify) override;
+ NS_IMETHOD CreateMailListAndAddToDB(nsIAbDirectory *newList, bool notify, nsIAbDirectory *parent) override;
+ NS_IMETHOD EnumerateCards(nsIAbDirectory *directory, nsISimpleEnumerator **result) override;
+ NS_IMETHOD GetMailingListsFromDB(nsIAbDirectory *parentDir) override;
+ NS_IMETHOD EnumerateListAddresses(nsIAbDirectory *directory, nsISimpleEnumerator **result) override;
+ NS_IMETHOD DeleteCard(nsIAbCard *newCard, bool notify, nsIAbDirectory *parent) override;
+ NS_IMETHOD EditCard(nsIAbCard *card, bool notify, nsIAbDirectory *parent) override;
+ NS_IMETHOD ContainsCard(nsIAbCard *card, bool *hasCard) override;
+ NS_IMETHOD DeleteMailList(nsIAbDirectory *aMailList, nsIAbDirectory *aParent) override;
+ NS_IMETHOD EditMailList(nsIAbDirectory *mailList, nsIAbCard *listCard, bool notify) override;
+ NS_IMETHOD ContainsMailList(nsIAbDirectory *mailList, bool *hasCard) override;
+ NS_IMETHOD DeleteCardFromMailList(nsIAbDirectory *mailList, nsIAbCard *card, bool aNotify) override;
+ NS_IMETHOD GetCardFromAttribute(nsIAbDirectory *aDirectory, const char *aName,
+ const nsACString &aValue,
+ bool aCaseInsensitive, nsIAbCard **card) override;
+ NS_IMETHOD GetCardsFromAttribute(nsIAbDirectory *aDirectory,
+ const char *aName,
+ const nsACString & uUTF8Value,
+ bool aCaseInsensitive,
+ nsISimpleEnumerator **cards) override;
+ NS_IMETHOD GetNewRow(nsIMdbRow * *newRow) override;
+ NS_IMETHOD GetNewListRow(nsIMdbRow * *newRow) override;
+ NS_IMETHOD AddCardRowToDB(nsIMdbRow *newRow) override;
+ NS_IMETHOD AddLdifListMember(nsIMdbRow* row, const char * value) override;
+
+ NS_IMETHOD GetDeletedCardList(nsIArray **aResult) override;
+ NS_IMETHOD GetDeletedCardCount(uint32_t *aCount) override;
+ NS_IMETHOD PurgeDeletedCardTable();
+
+ NS_IMETHOD AddFirstName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_FirstNameColumnToken, value); }
+
+ NS_IMETHOD AddLastName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_LastNameColumnToken, value); }
+
+ NS_IMETHOD AddPhoneticFirstName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_PhoneticFirstNameColumnToken, value); }
+
+ NS_IMETHOD AddPhoneticLastName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_PhoneticLastNameColumnToken, value); }
+
+ NS_IMETHOD AddDisplayName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_DisplayNameColumnToken, value); }
+
+ NS_IMETHOD AddNickName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_NickNameColumnToken, value); }
+
+ NS_IMETHOD AddPrimaryEmail(nsIMdbRow * row, const char * value) override;
+
+ NS_IMETHOD Add2ndEmail(nsIMdbRow * row, const char * value) override;
+
+ NS_IMETHOD AddPreferMailFormat(nsIMdbRow * row, uint32_t value) override
+ { return AddIntColumn(row, m_MailFormatColumnToken, value); }
+
+ NS_IMETHOD AddPopularityIndex(nsIMdbRow * row, uint32_t value) override
+ { return AddIntColumn(row, m_PopularityIndexColumnToken, value); }
+
+ NS_IMETHOD AddWorkPhone(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkPhoneColumnToken, value); }
+
+ NS_IMETHOD AddHomePhone(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomePhoneColumnToken, value); }
+
+ NS_IMETHOD AddFaxNumber(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_FaxColumnToken, value); }
+
+ NS_IMETHOD AddPagerNumber(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_PagerColumnToken, value); }
+
+ NS_IMETHOD AddCellularNumber(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_CellularColumnToken, value); }
+
+ NS_IMETHOD AddWorkPhoneType(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkPhoneTypeColumnToken, value); }
+
+ NS_IMETHOD AddHomePhoneType(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomePhoneTypeColumnToken, value); }
+
+ NS_IMETHOD AddFaxNumberType(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_FaxTypeColumnToken, value); }
+
+ NS_IMETHOD AddPagerNumberType(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_PagerTypeColumnToken, value); }
+
+ NS_IMETHOD AddCellularNumberType(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_CellularTypeColumnToken, value); }
+
+ NS_IMETHOD AddHomeAddress(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeAddressColumnToken, value); }
+
+ NS_IMETHOD AddHomeAddress2(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeAddress2ColumnToken, value); }
+
+ NS_IMETHOD AddHomeCity(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeCityColumnToken, value); }
+
+ NS_IMETHOD AddHomeState(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeStateColumnToken, value); }
+
+ NS_IMETHOD AddHomeZipCode(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeZipCodeColumnToken, value); }
+
+ NS_IMETHOD AddHomeCountry(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_HomeCountryColumnToken, value); }
+
+ NS_IMETHOD AddWorkAddress(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkAddressColumnToken, value); }
+
+ NS_IMETHOD AddWorkAddress2(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkAddress2ColumnToken, value); }
+
+ NS_IMETHOD AddWorkCity(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkCityColumnToken, value); }
+
+ NS_IMETHOD AddWorkState(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkStateColumnToken, value); }
+
+ NS_IMETHOD AddWorkZipCode(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkZipCodeColumnToken, value); }
+
+ NS_IMETHOD AddWorkCountry(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WorkCountryColumnToken, value); }
+
+ NS_IMETHOD AddJobTitle(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_JobTitleColumnToken, value); }
+
+ NS_IMETHOD AddDepartment(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_DepartmentColumnToken, value); }
+
+ NS_IMETHOD AddCompany(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_CompanyColumnToken, value); }
+
+ NS_IMETHOD AddAimScreenName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_AimScreenNameColumnToken, value); }
+
+ NS_IMETHOD AddAnniversaryYear(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_AnniversaryYearColumnToken, value); }
+
+ NS_IMETHOD AddAnniversaryMonth(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_AnniversaryMonthColumnToken, value); }
+
+ NS_IMETHOD AddAnniversaryDay(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_AnniversaryDayColumnToken, value); }
+
+ NS_IMETHOD AddSpouseName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_SpouseNameColumnToken, value); }
+
+ NS_IMETHOD AddFamilyName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_FamilyNameColumnToken, value); }
+
+ NS_IMETHOD AddDefaultAddress(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_DefaultAddressColumnToken, value); }
+
+ NS_IMETHOD AddCategory(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_CategoryColumnToken, value); }
+
+ NS_IMETHOD AddWebPage1(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WebPage1ColumnToken, value); }
+
+ NS_IMETHOD AddWebPage2(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_WebPage2ColumnToken, value); }
+
+ NS_IMETHOD AddBirthYear(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_BirthYearColumnToken, value); }
+
+ NS_IMETHOD AddBirthMonth(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_BirthMonthColumnToken, value); }
+
+ NS_IMETHOD AddBirthDay(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_BirthDayColumnToken, value); }
+
+ NS_IMETHOD AddCustom1(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_Custom1ColumnToken, value); }
+
+ NS_IMETHOD AddCustom2(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_Custom2ColumnToken, value); }
+
+ NS_IMETHOD AddCustom3(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_Custom3ColumnToken, value); }
+
+ NS_IMETHOD AddCustom4(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_Custom4ColumnToken, value); }
+
+ NS_IMETHOD AddNotes(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_NotesColumnToken, value); }
+
+ NS_IMETHOD AddListName(nsIMdbRow * row, const char * value) override;
+
+ NS_IMETHOD AddListNickName(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_ListNickNameColumnToken, value); }
+
+ NS_IMETHOD AddListDescription(nsIMdbRow * row, const char * value) override
+ { return AddCharStringColumn(row, m_ListDescriptionColumnToken, value); }
+
+
+ NS_IMETHOD AddListDirNode(nsIMdbRow * listRow) override;
+
+ NS_IMETHOD FindMailListbyUnicodeName(const char16_t *listName, bool *exist) override;
+
+ NS_IMETHOD GetCardCount(uint32_t *count) override;
+
+ NS_IMETHOD SetCardValue(nsIAbCard *card, const char *name, const char16_t *value, bool notify) override;
+ NS_IMETHOD GetCardValue(nsIAbCard *card, const char *name, char16_t **value) override;
+ // nsAddrDatabase methods:
+
+ nsAddrDatabase();
+
+ void GetMDBFactory(nsIMdbFactory ** aMdbFactory);
+ nsIMdbEnv *GetEnv() {return m_mdbEnv;}
+ uint32_t GetCurVersion();
+ nsIMdbTableRowCursor *GetTableRowCursor();
+ nsIMdbTable *GetPabTable() {return m_mdbPabTable;}
+
+ static nsAddrDatabase* FindInCache(nsIFile *dbName);
+
+ static void CleanupCache();
+
+ nsresult CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result);
+ nsresult CreateABListCard(nsIMdbRow* listRow, nsIAbCard **result);
+ nsresult CreateABList(nsIMdbRow* listRow, nsIAbDirectory **result);
+
+ bool IsListRowScopeToken(mdb_scope scope) { return (scope == m_ListRowScopeToken) ? true: false; }
+ bool IsCardRowScopeToken(mdb_scope scope) { return (scope == m_CardRowScopeToken) ? true: false; }
+ bool IsDataRowScopeToken(mdb_scope scope) { return (scope == m_DataRowScopeToken) ? true: false; }
+ nsresult GetCardRowByRowID(mdb_id rowID, nsIMdbRow **dbRow);
+ nsresult GetListRowByRowID(mdb_id rowID, nsIMdbRow **dbRow);
+
+ uint32_t GetListAddressTotal(nsIMdbRow* listRow);
+ nsresult GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos, nsIMdbRow** cardRow);
+
+ NS_IMETHOD AddListCardColumnsToRow(nsIAbCard *aPCard, nsIMdbRow *aPListRow, uint32_t aPos, nsIAbCard** aPNewCard, bool aInMailingList, nsIAbDirectory *aParent, nsIAbDirectory *aRoot) override;
+ NS_IMETHOD InitCardFromRow(nsIAbCard *aNewCard, nsIMdbRow* aCardRow) override;
+ NS_IMETHOD SetListAddressTotal(nsIMdbRow* aListRow, uint32_t aTotal) override;
+ NS_IMETHOD FindRowByCard(nsIAbCard * card,nsIMdbRow **aRow) override;
+
+protected:
+ virtual ~nsAddrDatabase();
+
+ static void RemoveFromCache(nsAddrDatabase* pAddrDB);
+ bool MatchDbName(nsIFile *dbName); // returns TRUE if they match
+
+ void YarnToUInt32(struct mdbYarn *yarn, uint32_t *pResult);
+ void GetCharStringYarn(char* str, struct mdbYarn* strYarn);
+ void GetStringYarn(const nsAString & aStr, struct mdbYarn* strYarn);
+ void GetIntYarn(uint32_t nValue, struct mdbYarn* intYarn);
+ nsresult AddCharStringColumn(nsIMdbRow* cardRow, mdb_column inColumn, const char* str);
+ nsresult AddStringColumn(nsIMdbRow* aCardRow, mdb_column aInColumn, const nsAString & aStr);
+ nsresult AddIntColumn(nsIMdbRow* cardRow, mdb_column inColumn, uint32_t nValue);
+ nsresult AddBoolColumn(nsIMdbRow* cardRow, mdb_column inColumn, bool bValue);
+ nsresult GetStringColumn(nsIMdbRow *cardRow, mdb_token outToken, nsString& str);
+ nsresult GetIntColumn(nsIMdbRow *cardRow, mdb_token outToken,
+ uint32_t* pValue, uint32_t defaultValue);
+ nsresult GetBoolColumn(nsIMdbRow *cardRow, mdb_token outToken, bool* pValue);
+ nsresult GetListCardFromDB(nsIAbCard *listCard, nsIMdbRow* listRow);
+ nsresult GetListFromDB(nsIAbDirectory *newCard, nsIMdbRow* listRow);
+ nsresult AddRecordKeyColumnToRow(nsIMdbRow *pRow);
+ nsresult AddAttributeColumnsToRow(nsIAbCard *card, nsIMdbRow *cardRow);
+ nsresult AddListAttributeColumnsToRow(nsIAbDirectory *list, nsIMdbRow *listRow, nsIAbDirectory *parent);
+ nsresult CreateCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result);
+ nsresult CreateCardFromDeletedCardsTable(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result);
+ nsresult DeleteCardFromListRow(nsIMdbRow* pListRow, mdb_id cardRowID);
+ void DeleteCardFromAllMailLists(mdb_id cardRowID);
+ nsresult NotifyListEntryChange(uint32_t abCode, nsIAbDirectory *dir);
+
+ nsresult AddLowercaseColumn(nsIMdbRow * row, mdb_token columnToken, const char* utf8String);
+ nsresult GetRowFromAttribute(const char *aName, const nsACString &aUTF8Value,
+ bool aCaseInsensitive, nsIMdbRow **aCardRow,
+ mdb_pos *aRowPos);
+
+ static nsTArray<nsAddrDatabase*>* m_dbCache;
+ static nsTArray<nsAddrDatabase*>* GetDBCache();
+
+ // mdb bookkeeping stuff
+ nsresult InitExistingDB();
+ nsresult InitNewDB();
+ nsresult InitMDBInfo();
+ nsresult InitPabTable();
+ nsresult InitDeletedCardsTable(bool aCreate);
+ nsresult AddRowToDeletedCardsTable(nsIAbCard *card, nsIMdbRow **pCardRow);
+ nsresult DeleteRowFromDeletedCardsTable(nsIMdbRow *pCardRow);
+
+ nsresult InitLastRecorKey();
+ nsresult GetDataRow(nsIMdbRow **pDataRow);
+ nsresult GetLastRecordKey();
+ nsresult UpdateLastRecordKey();
+ nsresult CheckAndUpdateRecordKey();
+ nsresult UpdateLowercaseEmailListName();
+ nsresult ConvertAndAddLowercaseColumn(nsIMdbRow * row, mdb_token fromCol, mdb_token toCol);
+ nsresult AddUnicodeToColumn(nsIMdbRow * row, mdb_token colToken, mdb_token lowerCaseColToken, const char16_t* pUnicodeStr);
+
+ nsresult DeleteRow(nsIMdbTable* dbTable, nsIMdbRow* dbRow);
+
+ nsIMdbEnv *m_mdbEnv; // to be used in all the db calls.
+ nsIMdbStore *m_mdbStore;
+ nsIMdbTable *m_mdbPabTable;
+ nsIMdbTable *m_mdbDeletedCardsTable;
+ nsCOMPtr<nsIFile> m_dbName;
+ bool m_mdbTokensInitialized;
+ nsTObserverArray<nsIAddrDBListener*> m_ChangeListeners;
+
+ mdb_kind m_PabTableKind;
+ mdb_kind m_MailListTableKind;
+ mdb_kind m_DeletedCardsTableKind;
+
+ mdb_scope m_CardRowScopeToken;
+ mdb_scope m_ListRowScopeToken;
+ mdb_scope m_DataRowScopeToken;
+
+ mdb_token m_FirstNameColumnToken;
+ mdb_token m_LastNameColumnToken;
+ mdb_token m_PhoneticFirstNameColumnToken;
+ mdb_token m_PhoneticLastNameColumnToken;
+ mdb_token m_DisplayNameColumnToken;
+ mdb_token m_NickNameColumnToken;
+ mdb_token m_PriEmailColumnToken;
+ mdb_token m_2ndEmailColumnToken;
+ mdb_token m_DefaultEmailColumnToken;
+ mdb_token m_CardTypeColumnToken;
+ mdb_token m_WorkPhoneColumnToken;
+ mdb_token m_HomePhoneColumnToken;
+ mdb_token m_FaxColumnToken;
+ mdb_token m_PagerColumnToken;
+ mdb_token m_CellularColumnToken;
+ mdb_token m_WorkPhoneTypeColumnToken;
+ mdb_token m_HomePhoneTypeColumnToken;
+ mdb_token m_FaxTypeColumnToken;
+ mdb_token m_PagerTypeColumnToken;
+ mdb_token m_CellularTypeColumnToken;
+ mdb_token m_HomeAddressColumnToken;
+ mdb_token m_HomeAddress2ColumnToken;
+ mdb_token m_HomeCityColumnToken;
+ mdb_token m_HomeStateColumnToken;
+ mdb_token m_HomeZipCodeColumnToken;
+ mdb_token m_HomeCountryColumnToken;
+ mdb_token m_WorkAddressColumnToken;
+ mdb_token m_WorkAddress2ColumnToken;
+ mdb_token m_WorkCityColumnToken;
+ mdb_token m_WorkStateColumnToken;
+ mdb_token m_WorkZipCodeColumnToken;
+ mdb_token m_WorkCountryColumnToken;
+ mdb_token m_JobTitleColumnToken;
+ mdb_token m_DepartmentColumnToken;
+ mdb_token m_CompanyColumnToken;
+ mdb_token m_AimScreenNameColumnToken;
+ mdb_token m_AnniversaryYearColumnToken;
+ mdb_token m_AnniversaryMonthColumnToken;
+ mdb_token m_AnniversaryDayColumnToken;
+ mdb_token m_SpouseNameColumnToken;
+ mdb_token m_FamilyNameColumnToken;
+ mdb_token m_DefaultAddressColumnToken;
+ mdb_token m_CategoryColumnToken;
+ mdb_token m_WebPage1ColumnToken;
+ mdb_token m_WebPage2ColumnToken;
+ mdb_token m_BirthYearColumnToken;
+ mdb_token m_BirthMonthColumnToken;
+ mdb_token m_BirthDayColumnToken;
+ mdb_token m_Custom1ColumnToken;
+ mdb_token m_Custom2ColumnToken;
+ mdb_token m_Custom3ColumnToken;
+ mdb_token m_Custom4ColumnToken;
+ mdb_token m_NotesColumnToken;
+ mdb_token m_LastModDateColumnToken;
+ mdb_token m_RecordKeyColumnToken;
+ mdb_token m_LowerPriEmailColumnToken;
+ mdb_token m_Lower2ndEmailColumnToken;
+
+ mdb_token m_MailFormatColumnToken;
+ mdb_token m_PopularityIndexColumnToken;
+
+ mdb_token m_AddressCharSetColumnToken;
+ mdb_token m_LastRecordKeyColumnToken;
+
+ mdb_token m_ListNameColumnToken;
+ mdb_token m_ListNickNameColumnToken;
+ mdb_token m_ListDescriptionColumnToken;
+ mdb_token m_ListTotalColumnToken;
+ mdb_token m_LowerListNameColumnToken;
+
+ uint32_t m_LastRecordKey;
+ nsWeakPtr m_dbDirectory;
+ nsCOMPtr<nsIMdbFactory> mMdbFactory;
+
+private:
+ nsresult GetRowForCharColumn(const char16_t *unicodeStr,
+ mdb_column findColumn, bool bIsCard,
+ bool aCaseInsensitive, nsIMdbRow **findRow,
+ mdb_pos *aRowPos);
+ bool HasRowButDeletedForCharColumn(const char16_t *unicodeStr, mdb_column findColumn, bool aIsCard, nsIMdbRow **aFindRow);
+ nsresult OpenInternal(nsIFile *aMabFile, bool aCreate, nsIAddrDatabase **pCardDB);
+ nsresult AlertAboutCorruptMabFile(const char16_t *aOldFileName, const char16_t *aNewFileName);
+ nsresult AlertAboutLockedMabFile(const char16_t *aFileName);
+ nsresult DisplayAlert(const char16_t *titleName, const char16_t *alertStringName,
+ const char16_t **formatStrings, int32_t numFormatStrings);
+};
+
+#endif
diff --git a/mailnews/addrbook/src/nsAddrbook.manifest b/mailnews/addrbook/src/nsAddrbook.manifest
new file mode 100644
index 000000000..56efc3bda
--- /dev/null
+++ b/mailnews/addrbook/src/nsAddrbook.manifest
@@ -0,0 +1,12 @@
+component {5b259db2-e451-4de9-8a6f-cfba91402973} nsAbAutoCompleteMyDomain.js
+contract @mozilla.org/autocomplete/search;1?name=mydomain {5b259db2-e451-4de9-8a6f-cfba91402973}
+component {2f946df9-114c-41fe-8899-81f10daf4f0c} nsAbAutoCompleteSearch.js
+contract @mozilla.org/autocomplete/search;1?name=addrbook {2f946df9-114c-41fe-8899-81f10daf4f0c}
+component {127b341a-bdda-4270-85e1-edff569a9b85} nsAbLDAPAttributeMap.js
+contract @mozilla.org/addressbook/ldap-attribute-map;1 {127b341a-bdda-4270-85e1-edff569a9b85}
+component {4ed7d5e1-8800-40da-9e78-c4f509d7ac5e} nsAbLDAPAttributeMap.js
+contract @mozilla.org/addressbook/ldap-attribute-map-service;1 {4ed7d5e1-8800-40da-9e78-c4f509d7ac5e}
+#ifdef MOZ_LDAP_XPCOM
+component {227e6482-fe9f-441f-9b7d-7b60375e7449} nsAbLDAPAutoCompleteSearch.js
+contract @mozilla.org/autocomplete/search;1?name=ldap {227e6482-fe9f-441f-9b7d-7b60375e7449}
+#endif \ No newline at end of file
diff --git a/mailnews/addrbook/src/nsDirPrefs.cpp b/mailnews/addrbook/src/nsDirPrefs.cpp
new file mode 100644
index 000000000..5e4c046b2
--- /dev/null
+++ b/mailnews/addrbook/src/nsDirPrefs.cpp
@@ -0,0 +1,1452 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* directory server preferences (used to be dirprefs.c in 4.x) */
+
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsDirPrefs.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsIAddrDatabase.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbManager.h"
+#include "nsIFile.h"
+#include "nsWeakReference.h"
+#include "nsIAbMDBDirectory.h"
+#if defined(MOZ_LDAP_XPCOM)
+#include "nsIAbLDAPDirectory.h"
+#endif
+#include "prmem.h"
+#include "prprf.h"
+#include "plstr.h"
+#include "nsQuickSort.h"
+#include "nsComponentManagerUtils.h"
+#include "msgCore.h"
+#include "nsStringGlue.h"
+
+#include <ctype.h>
+
+/*****************************************************************************
+ * Private definitions
+ */
+
+/* Default settings for site-configurable prefs */
+#define kDefaultPosition 1
+static bool dir_IsServerDeleted(DIR_Server * server);
+
+static char *DIR_GetStringPref(const char *prefRoot, const char *prefLeaf, const char *defaultValue);
+static int32_t DIR_GetIntPref(const char *prefRoot, const char *prefLeaf, int32_t defaultValue);
+static char *DIR_GetLocalizedStringPref(const char *prefRoot, const char *prefLeaf);
+
+static char * dir_ConvertDescriptionToPrefName(DIR_Server * server);
+
+void DIR_SetFileName(char** filename, const char* leafName);
+static void DIR_SetIntPref(const char *prefRoot, const char *prefLeaf, int32_t value, int32_t defaultValue);
+static DIR_Server *dir_MatchServerPrefToServer(nsTArray<DIR_Server*> *wholeList, const char *pref);
+static bool dir_ValidateAndAddNewServer(nsTArray<DIR_Server*> *wholeList, const char *fullprefname);
+static void DIR_DeleteServerList(nsTArray<DIR_Server*> *wholeList);
+
+static char *dir_CreateServerPrefName(DIR_Server *server);
+static void DIR_GetPrefsForOneServer(DIR_Server *server);
+
+static void DIR_InitServer(DIR_Server *server, DirectoryType dirType = (DirectoryType)0);
+static DIR_PrefId DIR_AtomizePrefName(const char *prefname);
+
+const int32_t DIR_POS_APPEND = -1;
+const int32_t DIR_POS_DELETE = -2;
+static bool DIR_SetServerPosition(nsTArray<DIR_Server*> *wholeList, DIR_Server *server, int32_t position);
+
+/* These two routines should be called to initialize and save
+ * directory preferences from the XP Java Script preferences
+ */
+static nsresult DIR_GetServerPreferences(nsTArray<DIR_Server*>** list);
+static void DIR_SaveServerPreferences(nsTArray<DIR_Server*> *wholeList);
+
+static int32_t dir_UserId = 0;
+nsTArray<DIR_Server*> *dir_ServerList = nullptr;
+
+/*****************************************************************************
+ * Functions for creating the new back end managed DIR_Server list.
+ */
+class DirPrefObserver final : public nsSupportsWeakReference,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+private:
+ ~DirPrefObserver() {}
+};
+
+NS_IMPL_ISUPPORTS(DirPrefObserver, nsISupportsWeakReference, nsIObserver)
+
+NS_IMETHODIMP DirPrefObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_QueryInterface(aSubject));
+ nsCString strPrefName;
+ strPrefName.Assign(NS_ConvertUTF16toUTF8(aData));
+ const char *prefname = strPrefName.get();
+
+ DIR_PrefId id = DIR_AtomizePrefName(prefname);
+
+ // Just get out if we get nothing here - we don't need to do anything
+ if (id == idNone)
+ return NS_OK;
+
+ /* Check to see if the server is in the unified server list.
+ */
+ DIR_Server *server = dir_MatchServerPrefToServer(dir_ServerList, prefname);
+ if (server)
+ {
+ /* If the server is in the process of being saved, just ignore this
+ * change. The DIR_Server structure is not really changing.
+ */
+ if (server->savingServer)
+ return NS_OK;
+
+ /* If the pref that changed is the position, read it in. If the new
+ * position is zero, remove the server from the list.
+ */
+ if (id == idPosition)
+ {
+ int32_t position;
+
+ /* We must not do anything if the new position is the same as the
+ * position in the DIR_Server. This avoids recursion in cases
+ * where we are deleting the server.
+ */
+ prefBranch->GetIntPref(prefname, &position);
+ if (position != server->position)
+ {
+ server->position = position;
+ if (dir_IsServerDeleted(server))
+ DIR_SetServerPosition(dir_ServerList, server, DIR_POS_DELETE);
+ }
+ }
+
+ if (id == idDescription) {
+ // Ensure the local copy of the description is kept up to date.
+ PR_FREEIF(server->description);
+ server->description = DIR_GetLocalizedStringPref(prefname, nullptr);
+ }
+ }
+ /* If the server is not in the unified list, we may need to add it. Servers
+ * are only added when the position, serverName and description are valid.
+ */
+ else if (id == idPosition || id == idType || id == idDescription)
+ {
+ dir_ValidateAndAddNewServer(dir_ServerList, prefname);
+ }
+
+ return NS_OK;
+}
+
+// A pointer to the pref observer
+static DirPrefObserver *prefObserver = nullptr;
+
+static nsresult DIR_GetDirServers()
+{
+ nsresult rv = NS_OK;
+
+ if (!dir_ServerList)
+ {
+ /* we need to build the DIR_Server list */
+ rv = DIR_GetServerPreferences(&dir_ServerList);
+
+ /* Register the preference call back if necessary. */
+ if (NS_SUCCEEDED(rv) && !prefObserver)
+ {
+ nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ prefObserver = new DirPrefObserver();
+
+ if (!prefObserver)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(prefObserver);
+
+ pbi->AddObserver(PREF_LDAP_SERVER_TREE_NAME, prefObserver, true);
+ }
+ }
+ return rv;
+}
+
+nsTArray<DIR_Server*>* DIR_GetDirectories()
+{
+ if (!dir_ServerList)
+ DIR_GetDirServers();
+ return dir_ServerList;
+}
+
+DIR_Server* DIR_GetServerFromList(const char* prefName)
+{
+ DIR_Server* result = nullptr;
+
+ if (!dir_ServerList)
+ DIR_GetDirServers();
+
+ if (dir_ServerList)
+ {
+ int32_t count = dir_ServerList->Length();
+ int32_t i;
+ for (i = 0; i < count; ++i)
+ {
+ DIR_Server *server = dir_ServerList->ElementAt(i);
+
+ if (server && strcmp(server->prefName, prefName) == 0)
+ {
+ result = server;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+static nsresult SavePrefsFile()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ return pPref->SavePrefFile(nullptr);
+}
+
+nsresult DIR_ShutDown() /* FEs should call this when the app is shutting down. It frees all DIR_Servers regardless of ref count values! */
+{
+ nsresult rv = SavePrefsFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DIR_DeleteServerList(dir_ServerList);
+ dir_ServerList = nullptr;
+
+ /* unregister the preference call back, if necessary.
+ * we need to do this as DIR_Shutdown() is called when switching profiles
+ * when using turbo. (see nsAbDirectoryDataSource::Observe())
+ * When switching profiles, prefs get unloaded and then re-loaded
+ * we don't want our callback to get called for all that.
+ * We'll reset our callback the first time DIR_GetDirServers() is called
+ * after we've switched profiles.
+ */
+ NS_IF_RELEASE(prefObserver);
+
+ return NS_OK;
+}
+
+nsresult DIR_ContainsServer(DIR_Server* pServer, bool *hasDir)
+{
+ if (dir_ServerList)
+ {
+ int32_t count = dir_ServerList->Length();
+ int32_t i;
+ for (i = 0; i < count; i++)
+ {
+ DIR_Server* server = dir_ServerList->ElementAt(i);
+ if (server == pServer)
+ {
+ *hasDir = true;
+ return NS_OK;
+ }
+ }
+ }
+ *hasDir = false;
+ return NS_OK;
+}
+
+nsresult DIR_AddNewAddressBook(const nsAString &dirName,
+ const nsACString &fileName,
+ const nsACString &uri,
+ DirectoryType dirType,
+ const nsACString &prefName,
+ DIR_Server** pServer)
+{
+ DIR_Server * server = (DIR_Server *) PR_Malloc(sizeof(DIR_Server));
+ if (!server)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ DIR_InitServer(server, dirType);
+ if (!dir_ServerList)
+ DIR_GetDirServers();
+ if (dir_ServerList)
+ {
+ server->description = ToNewCString(NS_ConvertUTF16toUTF8(dirName));
+ server->position = kDefaultPosition; // don't set position so alphabetic sort will happen.
+
+ if (!fileName.IsEmpty())
+ server->fileName = ToNewCString(fileName);
+ else if (dirType == PABDirectory)
+ DIR_SetFileName(&server->fileName, kPersonalAddressbook);
+ else if (dirType == LDAPDirectory)
+ DIR_SetFileName(&server->fileName, kMainLdapAddressBook);
+
+ if (dirType != PABDirectory) {
+ if (!uri.IsEmpty())
+ server->uri = ToNewCString(uri);
+ }
+
+ if (!prefName.IsEmpty())
+ server->prefName = ToNewCString(prefName);
+
+ dir_ServerList->AppendElement(server);
+
+ DIR_SavePrefsForOneServer(server);
+
+ *pServer = server;
+
+ // save new address book into pref file
+ return SavePrefsFile();
+ }
+ return NS_ERROR_FAILURE;
+}
+
+/*****************************************************************************
+ * Functions for creating DIR_Servers
+ */
+static void DIR_InitServer(DIR_Server *server, DirectoryType dirType)
+{
+ if (!server) {
+ NS_WARNING("DIR_InitServer: server parameter not initialized");
+ return;
+ }
+
+ memset(server, 0, sizeof(DIR_Server));
+ server->position = kDefaultPosition;
+ server->uri = nullptr;
+ server->savingServer = false;
+ server->dirType = dirType;
+}
+
+/* Function for setting the position of a server. Can be used to append,
+ * delete, or move a server in a server list.
+ *
+ * The third parameter specifies the new position the server is to occupy.
+ * The resulting position may differ depending on the lock state of the
+ * given server and other servers in the list. The following special values
+ * are supported:
+ * DIR_POS_APPEND - Appends the server to the end of the list. If the server
+ * is already in the list, does nothing.
+ * DIR_POS_DELETE - Deletes the given server from the list. Note that this
+ * does not cause the server structure to be freed.
+ *
+ * Returns true if the server list was re-sorted.
+ */
+static bool DIR_SetServerPosition(nsTArray<DIR_Server*> *wholeList, DIR_Server *server, int32_t position)
+ {
+ NS_ENSURE_TRUE(wholeList, false);
+
+ int32_t i, count, num;
+ bool resort = false;
+ DIR_Server *s=nullptr;
+
+ switch (position) {
+ case DIR_POS_APPEND:
+ /* Do nothing if the request is to append a server that is already
+ * in the list.
+ */
+ count = wholeList->Length();
+ for (i= 0; i < count; i++)
+ {
+ if ((s = wholeList->ElementAt(i)) != nullptr)
+ if (s == server)
+ return false;
+ }
+ /* In general, if there are any servers already in the list, set the
+ * position to the position of the last server plus one. If there
+ * are none, set it to position 1.
+ */
+ if (count > 0)
+ {
+ s = wholeList->ElementAt(count - 1);
+ server->position = s->position + 1;
+ }
+ else
+ server->position = 1;
+
+ wholeList->AppendElement(server);
+ break;
+
+ case DIR_POS_DELETE:
+ /* Remove the prefs corresponding to the given server. If the prefName
+ * value is nullptr, the server has never been saved and there are no
+ * prefs to remove.
+ */
+ if (server->prefName)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return false;
+
+ pPref->DeleteBranch(server->prefName);
+
+ // mark the server as deleted by setting its position to 0
+ DIR_SetIntPref(server->prefName, "position", 0, -1);
+ }
+
+ /* If the server is in the server list, remove it.
+ */
+ num = wholeList->IndexOf(server);
+ if (num >= 0)
+ {
+ /* The list does not need to be re-sorted if the server is the
+ * last one in the list.
+ */
+ count = wholeList->Length();
+ if (num == count - 1)
+ {
+ wholeList->RemoveElementAt(num);
+ }
+ else
+ {
+ resort = true;
+ wholeList->RemoveElement(server);
+ }
+ }
+ break;
+
+ default:
+ /* See if the server is already in the list.
+ */
+ count = wholeList->Length();
+ for (i= 0; i < count; i++)
+ {
+ if ((s = wholeList->ElementAt(i)) != nullptr)
+ if (s == server)
+ break;
+ }
+
+ /* If the server is not in the list, add it to the beginning and re-sort.
+ */
+ if (s == nullptr)
+ {
+ server->position = position;
+ wholeList->AppendElement(server);
+ resort = true;
+ }
+
+ /* Don't re-sort if the server is already in the requested position.
+ */
+ else if (server->position != position)
+ {
+ server->position = position;
+ wholeList->RemoveElement(server);
+ wholeList->AppendElement(server);
+ resort = true;
+ }
+ break;
+ }
+
+ /* Make sure our position changes get saved back to prefs
+ */
+ DIR_SaveServerPreferences(wholeList);
+
+ return resort;
+}
+
+/*****************************************************************************
+ * DIR_Server Callback Notification Functions
+ */
+
+/* dir_matchServerPrefToServer
+ *
+ * This function finds the DIR_Server in the unified DIR_Server list to which
+ * the given preference string belongs.
+ */
+static DIR_Server *dir_MatchServerPrefToServer(nsTArray<DIR_Server*> *wholeList, const char *pref)
+{
+ DIR_Server *server;
+
+ int32_t count = wholeList->Length();
+ int32_t i;
+ for (i = 0; i < count; i++)
+ {
+ if ((server = wholeList->ElementAt(i)) != nullptr)
+ {
+ if (server->prefName && PL_strstr(pref, server->prefName) == pref)
+ {
+ char c = pref[PL_strlen(server->prefName)];
+ if (c == 0 || c == '.')
+ return server;
+ }
+ }
+ }
+ return nullptr;
+}
+
+/* dir_ValidateAndAddNewServer
+ *
+ * This function verifies that the position, serverName and description values
+ * are set for the given prefName. If they are then it adds the server to the
+ * unified server list.
+ */
+static bool dir_ValidateAndAddNewServer(nsTArray<DIR_Server*> *wholeList, const char *fullprefname)
+{
+ bool rc = false;
+
+ const char *endname = PL_strchr(&fullprefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.');
+ if (endname)
+ {
+ char *prefname = (char *)PR_Malloc(endname - fullprefname + 1);
+ if (prefname)
+ {
+ int32_t dirType;
+ char *t1 = nullptr, *t2 = nullptr;
+
+ PL_strncpyz(prefname, fullprefname, endname - fullprefname + 1);
+
+ dirType = DIR_GetIntPref(prefname, "dirType", -1);
+ if (dirType != -1 &&
+ DIR_GetIntPref(prefname, "position", 0) != 0 &&
+ (t1 = DIR_GetLocalizedStringPref(prefname, "description")) != nullptr)
+ {
+ if (dirType == PABDirectory ||
+ (t2 = DIR_GetStringPref(prefname, "serverName", nullptr)) != nullptr)
+ {
+ DIR_Server *server = (DIR_Server *)PR_Malloc(sizeof(DIR_Server));
+ if (server)
+ {
+ DIR_InitServer(server, (DirectoryType)dirType);
+ server->prefName = prefname;
+ DIR_GetPrefsForOneServer(server);
+ DIR_SetServerPosition(wholeList, server, server->position);
+ rc = true;
+ }
+ PR_FREEIF(t2);
+ }
+ PR_Free(t1);
+ }
+ else
+ PR_Free(prefname);
+ }
+ }
+
+ return rc;
+}
+
+static DIR_PrefId DIR_AtomizePrefName(const char *prefname)
+{
+ if (!prefname)
+ return idNone;
+
+ DIR_PrefId rc = idNone;
+
+ /* Skip the "ldap_2.servers.<server-name>." portion of the string.
+ */
+ if (PL_strstr(prefname, PREF_LDAP_SERVER_TREE_NAME) == prefname)
+ {
+ prefname = PL_strchr(&prefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.');
+ if (!prefname)
+ return idNone;
+ else
+ prefname = prefname + 1;
+ }
+
+ switch (prefname[0]) {
+ case 'd':
+ switch (prefname[1]) {
+ case 'e': /* description */
+ rc = idDescription;
+ break;
+ case 'i': /* dirType */
+ rc = idType;
+ break;
+ }
+ break;
+
+ case 'f':
+ rc = idFileName;
+ break;
+
+ case 'p':
+ switch (prefname[1]) {
+ case 'o':
+ switch (prefname[2]) {
+ case 's': /* position */
+ rc = idPosition;
+ break;
+ }
+ break;
+ }
+ break;
+
+ case 'u': /* uri */
+ rc = idUri;
+ break;
+ }
+
+ return rc;
+}
+
+/*****************************************************************************
+ * Functions for destroying DIR_Servers
+ */
+
+/* this function determines if the passed in server is no longer part of the of
+ the global server list. */
+static bool dir_IsServerDeleted(DIR_Server * server)
+{
+ return (server && server->position == 0);
+}
+
+/* when the back end manages the server list, deleting a server just decrements its ref count,
+ in the old world, we actually delete the server */
+static void DIR_DeleteServer(DIR_Server *server)
+{
+ if (server)
+ {
+ /* when destroying the server check its clear flag to see if things need cleared */
+#ifdef XP_FileRemove
+ if (DIR_TestFlag(server, DIR_CLEAR_SERVER))
+ {
+ if (server->fileName)
+ XP_FileRemove (server->fileName, xpAddrBookNew);
+ }
+#endif /* XP_FileRemove */
+ PR_Free(server->prefName);
+ PR_Free(server->description);
+ PR_Free(server->fileName);
+ PR_Free(server->uri);
+ PR_Free(server);
+ }
+}
+
+nsresult DIR_DeleteServerFromList(DIR_Server *server)
+{
+ if (!server)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> dbPath;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ // close the database, as long as it isn't the special ones
+ // (personal addressbook and collected addressbook)
+ // which can never be deleted. There was a bug where we would slap in
+ // "abook.mab" as the file name for LDAP directories, which would cause a crash
+ // on delete of LDAP directories. this is just extra protection.
+ if (server->fileName &&
+ strcmp(server->fileName, kPersonalAddressbook) &&
+ strcmp(server->fileName, kCollectedAddressbook))
+ {
+ nsCOMPtr<nsIAddrDatabase> database;
+
+ rv = dbPath->AppendNative(nsDependentCString(server->fileName));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // close file before delete it
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory =
+ do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv) && addrDBFactory)
+ rv = addrDBFactory->Open(dbPath, false, true, getter_AddRefs(database));
+ if (database) /* database exists */
+ {
+ database->ForceClosed();
+ rv = dbPath->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsTArray<DIR_Server*> *dirList = DIR_GetDirectories();
+ DIR_SetServerPosition(dirList, server, DIR_POS_DELETE);
+ DIR_DeleteServer(server);
+
+ return SavePrefsFile();
+ }
+
+ return NS_ERROR_NULL_POINTER;
+}
+
+static void DIR_DeleteServerList(nsTArray<DIR_Server*> *wholeList)
+{
+ if (wholeList)
+ {
+ DIR_Server *server = nullptr;
+
+ /* TBD: Send notifications? */
+ int32_t count = wholeList->Length();
+ int32_t i;
+ for (i = count - 1; i >=0; i--)
+ {
+ server = wholeList->ElementAt(i);
+ if (server != nullptr)
+ DIR_DeleteServer(server);
+ }
+ delete wholeList;
+ }
+}
+
+/*****************************************************************************
+ * Functions for managing JavaScript prefs for the DIR_Servers
+ */
+
+static int
+comparePrefArrayMembers(const void* aElement1, const void* aElement2, void* aData)
+{
+ const char* element1 = *static_cast<const char* const *>(aElement1);
+ const char* element2 = *static_cast<const char* const *>(aElement2);
+ const uint32_t offset = *((const uint32_t*)aData);
+
+ // begin the comparison at |offset| chars into the string -
+ // this avoids comparing the "ldap_2.servers." portion of every element,
+ // which will always remain the same.
+ return strcmp(element1 + offset, element2 + offset);
+}
+
+static nsresult dir_GetChildList(const nsCString &aBranch,
+ uint32_t *aCount, char ***aChildList)
+{
+ uint32_t branchLen = aBranch.Length();
+
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefBranch) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = prefBranch->GetChildList(aBranch.get(), aCount, aChildList);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // traverse the list, and truncate all the descendant strings to just
+ // one branch level below the root branch.
+ for (uint32_t i = *aCount; i--; ) {
+ // The prefname we passed to GetChildList was of the form
+ // "ldap_2.servers." and we are returned the descendants
+ // in the form of "ldap_2.servers.servername.foo"
+ // But we want the prefbranch of the servername, so
+ // write a NUL character in to terminate the string early.
+ char *endToken = strchr((*aChildList)[i] + branchLen, '.');
+ if (endToken)
+ *endToken = '\0';
+ }
+
+ if (*aCount > 1) {
+ // sort the list, in preparation for duplicate entry removal
+ NS_QuickSort(*aChildList, *aCount, sizeof(char*), comparePrefArrayMembers, &branchLen);
+
+ // traverse the list and remove duplicate entries.
+ // we use two positions in the list; the current entry and the next
+ // entry; and perform a bunch of in-place ptr moves. so |cur| points
+ // to the last unique entry, and |next| points to some (possibly much
+ // later) entry to test, at any given point. we know we have >= 2
+ // elements in the list here, so we just init the two counters sensibly
+ // to begin with.
+ uint32_t cur = 0;
+ for (uint32_t next = 1; next < *aCount; ++next) {
+ // check if the elements are equal or unique
+ if (!comparePrefArrayMembers(&((*aChildList)[cur]), &((*aChildList)[next]), &branchLen)) {
+ // equal - just free & increment the next element ptr
+
+ free((*aChildList)[next]);
+ } else {
+ // cur & next are unique, so we need to shift the element.
+ // ++cur will point to the next free location in the
+ // reduced array (it's okay if that's == next)
+ (*aChildList)[++cur] = (*aChildList)[next];
+ }
+ }
+
+ // update the unique element count
+ *aCount = cur + 1;
+ }
+
+ return NS_OK;
+}
+
+static char *DIR_GetStringPref(const char *prefRoot, const char *prefLeaf, const char *defaultValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ nsCString value;
+ nsAutoCString prefLocation(prefRoot);
+
+ prefLocation.Append('.');
+ prefLocation.Append(prefLeaf);
+
+ if (NS_SUCCEEDED(pPref->GetCharPref(prefLocation.get(), getter_Copies(value))))
+ {
+ /* unfortunately, there may be some prefs out there which look like this */
+ if (value.EqualsLiteral("(null)"))
+ {
+ if (defaultValue)
+ value = defaultValue;
+ else
+ value.Truncate();
+ }
+
+ if (value.IsEmpty())
+ {
+ rv = pPref->GetCharPref(prefLocation.get(), getter_Copies(value));
+ }
+ }
+ else
+ value = defaultValue;
+
+ return ToNewCString(value);
+}
+
+/**
+ * Get localized unicode string pref from properties file, convert into an UTF8 string
+ * since address book prefs store as UTF8 strings. So far there are 2 default
+ * prefs stored in addressbook.properties.
+ * "ldap_2.servers.pab.description"
+ * "ldap_2.servers.history.description"
+ */
+static char *DIR_GetLocalizedStringPref(const char *prefRoot, const char *prefLeaf)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ nsAutoCString prefLocation(prefRoot);
+ if (prefLeaf) {
+ prefLocation.Append('.');
+ prefLocation.Append(prefLeaf);
+ }
+
+ nsString wvalue;
+ nsCOMPtr<nsIPrefLocalizedString> locStr;
+
+ rv = pPref->GetComplexValue(prefLocation.get(), NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(locStr));
+ if (NS_SUCCEEDED(rv))
+ rv = locStr->ToString(getter_Copies(wvalue));
+
+ char *value = nullptr;
+ if (!wvalue.IsEmpty())
+ {
+ value = ToNewCString(NS_ConvertUTF16toUTF8(wvalue));
+ }
+ else
+ {
+ // In TB 2 only some prefs had chrome:// URIs. We had code in place that would
+ // only get the localized string pref for the particular address books that
+ // were built-in.
+ // Additionally, nsIPrefBranch::getComplexValue will only get a non-user-set,
+ // non-locked pref value if it is a chrome:// URI and will get the string
+ // value at that chrome URI. This breaks extensions/autoconfig that want to
+ // set default pref values and allow users to change directory names.
+ //
+ // Now we have to support this, and so if for whatever reason we fail to get
+ // the localized version, then we try and get the non-localized version
+ // instead. If the string value is empty, then we'll just get the empty value
+ // back here.
+ rv = pPref->GetCharPref(prefLocation.get(), &value);
+ if (NS_FAILED(rv))
+ value = nullptr;
+ }
+
+ return value;
+}
+
+static int32_t DIR_GetIntPref(const char *prefRoot, const char *prefLeaf, int32_t defaultValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+
+ if (NS_FAILED(rv))
+ return defaultValue;
+
+ int32_t value;
+ nsAutoCString prefLocation(prefRoot);
+
+ prefLocation.Append('.');
+ prefLocation.Append(prefLeaf);
+
+ if (NS_FAILED(pPref->GetIntPref(prefLocation.get(), &value)))
+ value = defaultValue;
+
+ return value;
+}
+
+/* This will convert from the old preference that was a path and filename */
+/* to a just a filename */
+static void DIR_ConvertServerFileName(DIR_Server* pServer)
+{
+ char* leafName = pServer->fileName;
+ char* newLeafName = nullptr;
+#if defined(XP_WIN)
+ /* jefft -- bug 73349 This is to allow users share same address book.
+ * It only works if the user specify a full path filename.
+ */
+#ifdef XP_FileIsFullPath
+ if (! XP_FileIsFullPath(leafName))
+ newLeafName = XP_STRRCHR (leafName, '\\');
+#endif /* XP_FileIsFullPath */
+#else
+ newLeafName = strrchr(leafName, '/');
+#endif
+ pServer->fileName = newLeafName ? strdup(newLeafName + 1) : strdup(leafName);
+ if (leafName) PR_Free(leafName);
+}
+
+/* This will generate a correct filename and then remove the path.
+ * Note: we are assuming that the default name is in the native
+ * filesystem charset. The filename will be returned as a UTF8
+ * string.
+ */
+void DIR_SetFileName(char** fileName, const char* defaultName)
+{
+ if (!fileName)
+ return;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> dbPath;
+
+ *fileName = nullptr;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = dbPath->AppendNative(nsDependentCString(defaultName));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0664);
+
+ nsAutoString realFileName;
+ rv = dbPath->GetLeafName(realFileName);
+
+ if (NS_SUCCEEDED(rv))
+ *fileName = ToNewUTF8String(realFileName);
+ }
+ }
+}
+
+/****************************************************************
+Helper function used to generate a file name from the description
+of a directory. Caller must free returned string.
+An extension is not applied
+*****************************************************************/
+
+static char * dir_ConvertDescriptionToPrefName(DIR_Server * server)
+{
+#define MAX_PREF_NAME_SIZE 25
+ char * fileName = nullptr;
+ char fileNameBuf[MAX_PREF_NAME_SIZE];
+ int32_t srcIndex = 0;
+ int32_t destIndex = 0;
+ int32_t numSrcBytes = 0;
+ const char * descr = nullptr;
+ if (server && server->description)
+ {
+ descr = server->description;
+ numSrcBytes = PL_strlen(descr);
+ while (srcIndex < numSrcBytes && destIndex < MAX_PREF_NAME_SIZE-1)
+ {
+ if (IS_DIGIT(descr[srcIndex]) || IS_ALPHA(descr[srcIndex]))
+ {
+ fileNameBuf[destIndex] = descr[srcIndex];
+ destIndex++;
+ }
+
+ srcIndex++;
+ }
+
+ fileNameBuf[destIndex] = '\0'; /* zero out the last character */
+ }
+
+ if (destIndex) /* have at least one character in the file name? */
+ fileName = strdup(fileNameBuf);
+
+ return fileName;
+}
+
+
+void DIR_SetServerFileName(DIR_Server *server)
+{
+ char * tempName = nullptr;
+ const char * prefName = nullptr;
+ uint32_t numHeaderBytes = 0;
+
+ if (server && (!server->fileName || !(*server->fileName)) )
+ {
+ PR_FREEIF(server->fileName); // might be one byte empty string.
+ /* make sure we have a pref name...*/
+ if (!server->prefName || !*server->prefName)
+ server->prefName = dir_CreateServerPrefName(server);
+
+ /* set default personal address book file name*/
+ if ((server->position == 1) && (server->dirType == PABDirectory))
+ server->fileName = strdup(kPersonalAddressbook);
+ else
+ {
+ /* now use the pref name as the file name since we know the pref name
+ will be unique */
+ prefName = server->prefName;
+ if (prefName && *prefName)
+ {
+ /* extract just the pref name part and not the ldap tree name portion from the string */
+ numHeaderBytes = PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1; /* + 1 for the '.' b4 the name */
+ if (PL_strlen(prefName) > numHeaderBytes)
+ tempName = strdup(prefName + numHeaderBytes);
+
+ if (tempName)
+ {
+ server->fileName = PR_smprintf("%s%s", tempName, kABFileName_CurrentSuffix);
+ PR_Free(tempName);
+ }
+ }
+ }
+
+ if (!server->fileName || !*server->fileName) /* when all else has failed, generate a default name */
+ {
+ if (server->dirType == LDAPDirectory)
+ DIR_SetFileName(&(server->fileName), kMainLdapAddressBook); /* generates file name with an ldap prefix */
+ else
+ DIR_SetFileName(&(server->fileName), kPersonalAddressbook);
+ }
+ }
+}
+
+static char *dir_CreateServerPrefName (DIR_Server *server)
+{
+ /* we are going to try to be smart in how we generate our server
+ pref name. We'll try to convert the description into a pref name
+ and then verify that it is unique. If it is unique then use it... */
+ char * leafName = dir_ConvertDescriptionToPrefName(server);
+ char * prefName = nullptr;
+ bool isUnique = false;
+
+ if (!leafName || !*leafName)
+ {
+ // we need to handle this in case the description has no alphanumeric chars
+ // it's very common for cjk users
+ leafName = strdup("_nonascii");
+ }
+
+ if (leafName)
+ {
+ int32_t uniqueIDCnt = 0;
+ char **children = nullptr;
+ /* we need to verify that this pref string name is unique */
+ prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".%s", leafName);
+ isUnique = false;
+ uint32_t prefCount;
+ nsresult rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."),
+ &prefCount, &children);
+ if (NS_SUCCEEDED(rv))
+ {
+ while (!isUnique && prefName)
+ {
+ isUnique = true; /* now flip the logic and assume we are unique until we find a match */
+ for (uint32_t i = 0; i < prefCount && isUnique; ++i)
+ {
+ if (!PL_strcasecmp(children[i], prefName)) /* are they the same branch? */
+ isUnique = false;
+ }
+ if (!isUnique) /* then try generating a new pref name and try again */
+ {
+ PR_smprintf_free(prefName);
+ prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".%s_%d", leafName, ++uniqueIDCnt);
+ }
+ } /* if we have a list of pref Names */
+
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, children);
+ } /* while we don't have a unique name */
+
+ // fallback to "user_directory_N" form if we failed to verify
+ if (!isUnique && prefName)
+ {
+ PR_smprintf_free(prefName);
+ prefName = nullptr;
+ }
+
+ PR_Free(leafName);
+
+ } /* if leafName */
+
+ if (!prefName) /* last resort if we still don't have a pref name is to use user_directory string */
+ return PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".user_directory_%d", ++dir_UserId);
+ else
+ return prefName;
+}
+
+static void DIR_GetPrefsForOneServer(DIR_Server *server)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ char *prefstring = server->prefName;
+
+ // this call fills in tempstring with the position pref, and
+ // we then check to see if it's locked.
+ server->position = DIR_GetIntPref (prefstring, "position", kDefaultPosition);
+
+ // For default address books, this will get the name from the chrome
+ // file referenced, for other address books it'll just retrieve it from prefs
+ // as normal.
+ server->description = DIR_GetLocalizedStringPref(prefstring, "description");
+
+ server->dirType = (DirectoryType)DIR_GetIntPref (prefstring, "dirType", LDAPDirectory);
+
+ server->fileName = DIR_GetStringPref (prefstring, "filename", "");
+ // if we don't have a file name try and get one
+ if (!server->fileName || !*(server->fileName))
+ DIR_SetServerFileName (server);
+ if (server->fileName && *server->fileName)
+ DIR_ConvertServerFileName(server);
+
+ // the string "s" is the default uri ( <scheme> + "://" + <filename> )
+ nsCString s((server->dirType == PABDirectory || server->dirType == MAPIDirectory) ?
+#if defined(MOZ_LDAP_XPCOM)
+ kMDBDirectoryRoot : kLDAPDirectoryRoot);
+#else
+ // Fallback to the all directory root in the non-ldap enabled case.
+ kMDBDirectoryRoot : kAllDirectoryRoot);
+#endif
+ s.Append (server->fileName);
+ server->uri = DIR_GetStringPref (prefstring, "uri", s.get ());
+}
+
+static nsresult dir_GetPrefs(nsTArray<DIR_Server*> **list)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ (*list) = new nsTArray<DIR_Server*>();
+ if (!(*list))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ char **children;
+ uint32_t prefCount;
+
+ rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."),
+ &prefCount, &children);
+ if (NS_FAILED(rv))
+ return rv;
+
+ /* TBD: Temporary code to read broken "ldap" preferences tree.
+ * Remove line with if statement after M10.
+ */
+ if (dir_UserId == 0)
+ pPref->GetIntPref(PREF_LDAP_GLOBAL_TREE_NAME".user_id", &dir_UserId);
+
+ for (uint32_t i = 0; i < prefCount; ++i)
+ {
+ DIR_Server *server;
+
+ server = (DIR_Server *)PR_Calloc(1, sizeof(DIR_Server));
+ if (server)
+ {
+ DIR_InitServer(server);
+ server->prefName = strdup(children[i]);
+ DIR_GetPrefsForOneServer(server);
+ if (server->description && server->description[0] &&
+ ((server->dirType == PABDirectory ||
+ server->dirType == MAPIDirectory ||
+ server->dirType == FixedQueryLDAPDirectory || // this one might go away
+ server->dirType == LDAPDirectory)))
+ {
+ if (!dir_IsServerDeleted(server))
+ {
+ (*list)->AppendElement(server);
+ }
+ else
+ DIR_DeleteServer(server);
+ }
+ else
+ {
+ DIR_DeleteServer(server);
+ }
+ }
+ }
+
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, children);
+
+ return NS_OK;
+}
+
+// I don't think we care about locked positions, etc.
+void DIR_SortServersByPosition(nsTArray<DIR_Server*> *serverList)
+{
+ int i, j;
+ DIR_Server *server;
+
+ int count = serverList->Length();
+ for (i = 0; i < count - 1; i++)
+ {
+ for (j = i + 1; j < count; j++)
+ {
+ if (serverList->ElementAt(j)->position < serverList->ElementAt(i)->position)
+ {
+ server = serverList->ElementAt(i);
+ serverList->ReplaceElementAt(i, serverList->ElementAt(j));
+ serverList->ReplaceElementAt(j, server);
+ }
+ }
+ }
+}
+
+static nsresult DIR_GetServerPreferences(nsTArray<DIR_Server*>** list)
+{
+ nsresult err;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &err));
+ if (NS_FAILED(err))
+ return err;
+
+ int32_t version = -1;
+ nsTArray<DIR_Server*> *newList = nullptr;
+
+ /* Update the ldap list version and see if there are old prefs to migrate. */
+ err = pPref->GetIntPref(PREF_LDAP_VERSION_NAME, &version);
+ NS_ENSURE_SUCCESS(err, err);
+
+ /* Find the new-style "ldap_2.servers" tree in prefs */
+ err = dir_GetPrefs(&newList);
+
+ if (version < kCurrentListVersion)
+ {
+ pPref->SetIntPref(PREF_LDAP_VERSION_NAME, kCurrentListVersion);
+ }
+
+ DIR_SortServersByPosition(newList);
+
+ *list = newList;
+
+ return err;
+}
+
+static void DIR_SetStringPref(const char *prefRoot, const char *prefLeaf, const char *value, const char *defaultValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCString defaultPref;
+ nsAutoCString prefLocation(prefRoot);
+
+ prefLocation.Append('.');
+ prefLocation.Append(prefLeaf);
+
+ if (NS_SUCCEEDED(pPref->GetCharPref(prefLocation.get(), getter_Copies(defaultPref))))
+ {
+ /* If there's a default pref, just set ours in and let libpref worry
+ * about potential defaults in all.js
+ */
+ if (value) /* added this check to make sure we have a value before we try to set it..*/
+ rv = pPref->SetCharPref (prefLocation.get(), value);
+ else
+ rv = pPref->ClearUserPref(prefLocation.get());
+ }
+ else
+ {
+ /* If there's no default pref, look for a user pref, and only set our value in
+ * if the user pref is different than one of them.
+ */
+ nsCString userPref;
+ if (NS_SUCCEEDED(pPref->GetCharPref (prefLocation.get(), getter_Copies(userPref))))
+ {
+ if (value && (defaultValue ? PL_strcasecmp(value, defaultValue) : value != defaultValue))
+ rv = pPref->SetCharPref (prefLocation.get(), value);
+ else
+ rv = pPref->ClearUserPref(prefLocation.get());
+ }
+ else
+ {
+ if (value && (defaultValue ? PL_strcasecmp(value, defaultValue) : value != defaultValue))
+ rv = pPref->SetCharPref (prefLocation.get(), value);
+ }
+ }
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetStringPref");
+}
+
+static void DIR_SetLocalizedStringPref
+(const char *prefRoot, const char *prefLeaf, const char *value)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+
+ if (NS_FAILED(rv))
+ return;
+
+ nsAutoCString prefLocation(prefRoot);
+ prefLocation.Append('.');
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefSvc->GetBranch(prefLocation.get(), getter_AddRefs(prefBranch));
+ if (NS_FAILED(rv))
+ return;
+
+ nsString wvalue;
+ nsCOMPtr<nsIPrefLocalizedString> newStr(
+ do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ {
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Could not createInstance in DIR_SetLocalizedStringPref");
+ return;
+ }
+
+ NS_ConvertUTF8toUTF16 newValue(value);
+
+ rv = newStr->SetData(newValue.get());
+ if (NS_FAILED(rv))
+ {
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref data in DIR_SetLocalizedStringPref");
+ return;
+ }
+ nsCOMPtr<nsIPrefLocalizedString> locStr;
+ if (NS_SUCCEEDED(prefBranch->GetComplexValue(prefLeaf,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(locStr))))
+ {
+ nsString data;
+ locStr->GetData(getter_Copies(data));
+
+ // Only set the pref if the data values aren't the same (i.e. don't change
+ // unnecessarily, but also, don't change in the case that its a chrome
+ // string pointing to the value we want to set the pref to).
+ if (newValue != data)
+ rv = prefBranch->SetComplexValue(prefLeaf,
+ NS_GET_IID(nsIPrefLocalizedString),
+ newStr);
+ }
+ else {
+ // No value set, but check the default pref branch (i.e. user may have
+ // cleared the pref)
+ nsCOMPtr<nsIPrefBranch> dPB;
+ rv = prefSvc->GetDefaultBranch(prefLocation.get(),
+ getter_AddRefs(dPB));
+
+ if (NS_SUCCEEDED(dPB->GetComplexValue(prefLeaf,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(locStr))))
+ {
+ // Default branch has a value
+ nsString data;
+ locStr->GetData(getter_Copies(data));
+
+ if (newValue != data)
+ // If the vales aren't the same, set the data on the main pref branch
+ rv = prefBranch->SetComplexValue(prefLeaf,
+ NS_GET_IID(nsIPrefLocalizedString),
+ newStr);
+ else
+ // Else if they are, kill the user pref
+ rv = prefBranch->ClearUserPref(prefLeaf);
+ }
+ else
+ // No values set anywhere, so just set the pref
+ rv = prefBranch->SetComplexValue(prefLeaf,
+ NS_GET_IID(nsIPrefLocalizedString),
+ newStr);
+ }
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetLocalizedStringPref");
+}
+
+
+static void DIR_SetIntPref(const char *prefRoot, const char *prefLeaf, int32_t value, int32_t defaultValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ int32_t defaultPref;
+ nsAutoCString prefLocation(prefRoot);
+
+ prefLocation.Append('.');
+ prefLocation.Append(prefLeaf);
+
+ if (NS_SUCCEEDED(pPref->GetIntPref(prefLocation.get(), &defaultPref)))
+ {
+ /* solve the problem where reordering user prefs must override default prefs */
+ rv = pPref->SetIntPref(prefLocation.get(), value);
+ }
+ else
+ {
+ int32_t userPref;
+ if (NS_SUCCEEDED(pPref->GetIntPref(prefLocation.get(), &userPref)))
+ {
+ if (value != defaultValue)
+ rv = pPref->SetIntPref(prefLocation.get(), value);
+ else
+ rv = pPref->ClearUserPref(prefLocation.get());
+ }
+ else
+ {
+ if (value != defaultValue)
+ rv = pPref->SetIntPref(prefLocation.get(), value);
+ }
+ }
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetIntPref");
+}
+
+void DIR_SavePrefsForOneServer(DIR_Server *server)
+{
+ if (!server)
+ return;
+
+ char *prefstring;
+
+ if (server->prefName == nullptr)
+ server->prefName = dir_CreateServerPrefName(server);
+ prefstring = server->prefName;
+
+ server->savingServer = true;
+
+ DIR_SetIntPref (prefstring, "position", server->position, kDefaultPosition);
+
+ // Only save the non-default address book name
+ DIR_SetLocalizedStringPref(prefstring, "description", server->description);
+
+ DIR_SetStringPref(prefstring, "filename", server->fileName, "");
+ DIR_SetIntPref(prefstring, "dirType", server->dirType, LDAPDirectory);
+
+ if (server->dirType != PABDirectory)
+ DIR_SetStringPref(prefstring, "uri", server->uri, "");
+
+ server->savingServer = false;
+}
+
+static void DIR_SaveServerPreferences(nsTArray<DIR_Server*> *wholeList)
+{
+ if (wholeList)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("DIR_SaveServerPreferences: Failed to get the pref service\n");
+ return;
+ }
+
+ int32_t i;
+ int32_t count = wholeList->Length();
+ DIR_Server *server;
+
+ for (i = 0; i < count; i++)
+ {
+ server = wholeList->ElementAt(i);
+ if (server)
+ DIR_SavePrefsForOneServer(server);
+ }
+ pPref->SetIntPref(PREF_LDAP_GLOBAL_TREE_NAME".user_id", dir_UserId);
+ }
+}
diff --git a/mailnews/addrbook/src/nsDirPrefs.h b/mailnews/addrbook/src/nsDirPrefs.h
new file mode 100644
index 000000000..d27a4fcb5
--- /dev/null
+++ b/mailnews/addrbook/src/nsDirPrefs.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _NSDIRPREFS_H_
+#define _NSDIRPREFS_H_
+
+#include "nsTArray.h"
+
+//
+// XXX nsDirPrefs is being greatly reduced if not removed altogether. Directory
+// Prefs etc. should be handled via their appropriate nsAb*Directory classes.
+//
+
+#define kPreviousListVersion 2
+#define kCurrentListVersion 3
+#define PREF_LDAP_GLOBAL_TREE_NAME "ldap_2"
+#define PREF_LDAP_VERSION_NAME "ldap_2.version"
+#define PREF_LDAP_SERVER_TREE_NAME "ldap_2.servers"
+
+#define kMainLdapAddressBook "ldap.mab" /* v3 main ldap address book file */
+
+/* DIR_Server.dirType */
+typedef enum
+{
+ LDAPDirectory,
+ HTMLDirectory,
+ PABDirectory,
+ MAPIDirectory,
+ FixedQueryLDAPDirectory = 777
+} DirectoryType;
+
+typedef enum
+{
+ idNone = 0, /* Special value */
+ idPrefName,
+ idPosition,
+ idDescription,
+ idFileName,
+ idUri,
+ idType
+} DIR_PrefId;
+
+#define DIR_Server_typedef 1 /* this quiets a redeclare warning in libaddr */
+
+typedef struct DIR_Server
+{
+ /* Housekeeping fields */
+ char *prefName; /* preference name, this server's subtree */
+ int32_t position; /* relative position in server list */
+
+ /* General purpose fields */
+ char *description; /* human readable name */
+ char *fileName; /* XP path name of local DB */
+ DirectoryType dirType;
+ char *uri; // URI of the address book
+
+ // Set whilst saving the server to avoid updating it again
+ bool savingServer;
+} DIR_Server;
+
+/* We are developing a new model for managing DIR_Servers. In the 4.0x world, the FEs managed each list.
+ Calls to FE_GetDirServer caused the FEs to manage and return the DIR_Server list. In our new view of the
+ world, the back end does most of the list management so we are going to have the back end create and
+ manage the list. Replace calls to FE_GetDirServers() with DIR_GetDirServers(). */
+
+nsTArray<DIR_Server*>* DIR_GetDirectories();
+DIR_Server* DIR_GetServerFromList(const char* prefName);
+nsresult DIR_ShutDown(void); /* FEs should call this when the app is shutting down. It frees all DIR_Servers regardless of ref count values! */
+
+nsresult DIR_AddNewAddressBook(const nsAString &dirName,
+ const nsACString &fileName,
+ const nsACString &uri,
+ DirectoryType dirType,
+ const nsACString &prefName,
+ DIR_Server** pServer);
+nsresult DIR_ContainsServer(DIR_Server* pServer, bool *hasDir);
+
+nsresult DIR_DeleteServerFromList (DIR_Server *);
+
+void DIR_SavePrefsForOneServer(DIR_Server *server);
+
+void DIR_SetServerFileName(DIR_Server* pServer);
+
+#endif /* dirprefs.h */
diff --git a/mailnews/addrbook/src/nsMapiAddressBook.cpp b/mailnews/addrbook/src/nsMapiAddressBook.cpp
new file mode 100644
index 000000000..80d737fb2
--- /dev/null
+++ b/mailnews/addrbook/src/nsMapiAddressBook.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsMapiAddressBook.h"
+
+#include "mozilla/Logging.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gMapiAddressBookLog
+ = PR_NewLogModule("nsMapiAddressBookLog");
+#endif
+
+#define PRINTF(args) MOZ_LOG(gMapiAddressBookLog, mozilla::LogLevel::Debug, args)
+
+using namespace mozilla;
+
+HMODULE nsMapiAddressBook::mLibrary = NULL ;
+int32_t nsMapiAddressBook::mLibUsage = 0 ;
+LPMAPIINITIALIZE nsMapiAddressBook::mMAPIInitialize = NULL ;
+LPMAPIUNINITIALIZE nsMapiAddressBook::mMAPIUninitialize = NULL ;
+LPMAPIALLOCATEBUFFER nsMapiAddressBook::mMAPIAllocateBuffer = NULL ;
+LPMAPIFREEBUFFER nsMapiAddressBook::mMAPIFreeBuffer = NULL ;
+LPMAPILOGONEX nsMapiAddressBook::mMAPILogonEx = NULL ;
+
+BOOL nsMapiAddressBook::mInitialized = FALSE ;
+BOOL nsMapiAddressBook::mLogonDone = FALSE ;
+LPMAPISESSION nsMapiAddressBook::mRootSession = NULL ;
+LPADRBOOK nsMapiAddressBook::mRootBook = NULL ;
+
+BOOL nsMapiAddressBook::LoadMapiLibrary(void)
+{
+ if (mLibrary) { ++ mLibUsage ; return TRUE ; }
+ HMODULE libraryHandle = LoadLibrary("MAPI32.DLL") ;
+
+ if (!libraryHandle) { return FALSE ; }
+ FARPROC entryPoint = GetProcAddress(libraryHandle, "MAPIGetNetscapeVersion") ;
+
+ if (entryPoint) {
+ FreeLibrary(libraryHandle) ;
+ libraryHandle = LoadLibrary("MAPI32BAK.DLL") ;
+ if (!libraryHandle) { return FALSE ; }
+ }
+ mLibrary = libraryHandle ;
+ ++ mLibUsage ;
+ mMAPIInitialize = reinterpret_cast<LPMAPIINITIALIZE>(GetProcAddress(mLibrary, "MAPIInitialize")) ;
+ if (!mMAPIInitialize) { return FALSE ; }
+ mMAPIUninitialize = reinterpret_cast<LPMAPIUNINITIALIZE>(GetProcAddress(mLibrary, "MAPIUninitialize")) ;
+ if (!mMAPIUninitialize) { return FALSE ; }
+ mMAPIAllocateBuffer = reinterpret_cast<LPMAPIALLOCATEBUFFER>(GetProcAddress(mLibrary, "MAPIAllocateBuffer")) ;
+ if (!mMAPIAllocateBuffer) { return FALSE ; }
+ mMAPIFreeBuffer = reinterpret_cast<LPMAPIFREEBUFFER>(GetProcAddress(mLibrary, "MAPIFreeBuffer")) ;
+ if (!mMAPIFreeBuffer) { return FALSE ; }
+ mMAPILogonEx = reinterpret_cast<LPMAPILOGONEX>(GetProcAddress(mLibrary, "MAPILogonEx")) ;
+ if (!mMAPILogonEx) { return FALSE ; }
+ MAPIINIT_0 mapiInit = { MAPI_INIT_VERSION, MAPI_MULTITHREAD_NOTIFICATIONS } ;
+ HRESULT retCode = mMAPIInitialize(&mapiInit) ;
+
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot initialize MAPI %08x.\n", retCode)) ; return FALSE ;
+ }
+ mInitialized = TRUE ;
+ retCode = mMAPILogonEx(0, NULL, NULL,
+ MAPI_NO_MAIL |
+ MAPI_USE_DEFAULT |
+ MAPI_EXTENDED |
+ MAPI_NEW_SESSION,
+ &mRootSession) ;
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot logon to MAPI %08x.\n", retCode)) ; return FALSE ;
+ }
+ mLogonDone = TRUE ;
+ retCode = mRootSession->OpenAddressBook(0, NULL, 0, &mRootBook) ;
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot open MAPI address book %08x.\n", retCode)) ;
+ }
+ return HR_SUCCEEDED(retCode) ;
+}
+
+void nsMapiAddressBook::FreeMapiLibrary(void)
+{
+ if (mLibrary) {
+ if (-- mLibUsage == 0) {
+ {
+ if (mRootBook) { mRootBook->Release() ; }
+ if (mRootSession) {
+ if (mLogonDone) {
+ mRootSession->Logoff(NULL, 0, 0) ;
+ mLogonDone = FALSE ;
+ }
+ mRootSession->Release() ;
+ }
+ if (mInitialized) {
+ mMAPIUninitialize() ;
+ mInitialized = FALSE ;
+ }
+ }
+ FreeLibrary(mLibrary) ;
+ mLibrary = NULL ;
+ }
+ }
+}
+
+nsMapiAddressBook::nsMapiAddressBook(void)
+: nsAbWinHelper()
+{
+ BOOL result = Initialize() ;
+
+ NS_ASSERTION(result == TRUE, "Couldn't initialize Mapi Helper") ;
+ MOZ_COUNT_CTOR(nsMapiAddressBook) ;
+}
+
+nsMapiAddressBook::~nsMapiAddressBook(void)
+{
+ MutexAutoLock guard(*mMutex) ;
+
+ FreeMapiLibrary() ;
+ MOZ_COUNT_DTOR(nsMapiAddressBook) ;
+}
+
+BOOL nsMapiAddressBook::Initialize(void)
+{
+ if (mAddressBook) { return TRUE ; }
+ MutexAutoLock guard(*mMutex) ;
+
+ if (!LoadMapiLibrary()) {
+ PRINTF(("Cannot load library.\n")) ;
+ return FALSE ;
+ }
+ mAddressBook = mRootBook ;
+ return TRUE ;
+}
+
+void nsMapiAddressBook::AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer)
+{
+ mMAPIAllocateBuffer(aByteCount, aBuffer) ;
+}
+
+void nsMapiAddressBook::FreeBuffer(LPVOID aBuffer)
+{
+ mMAPIFreeBuffer(aBuffer) ;
+}
+
+
+
+
+
diff --git a/mailnews/addrbook/src/nsMapiAddressBook.h b/mailnews/addrbook/src/nsMapiAddressBook.h
new file mode 100644
index 000000000..ed19b01be
--- /dev/null
+++ b/mailnews/addrbook/src/nsMapiAddressBook.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsMapiAddressBook_h___
+#define nsMapiAddressBook_h___
+
+#include "mozilla/Attributes.h"
+#include "nsAbWinHelper.h"
+
+class nsMapiAddressBook : public nsAbWinHelper
+{
+public :
+ nsMapiAddressBook(void) ;
+ virtual ~nsMapiAddressBook(void) ;
+
+protected :
+ // Class members to handle the library/entry points
+ static HMODULE mLibrary ;
+ static int32_t mLibUsage ;
+ static LPMAPIINITIALIZE mMAPIInitialize ;
+ static LPMAPIUNINITIALIZE mMAPIUninitialize ;
+ static LPMAPIALLOCATEBUFFER mMAPIAllocateBuffer ;
+ static LPMAPIFREEBUFFER mMAPIFreeBuffer ;
+ static LPMAPILOGONEX mMAPILogonEx ;
+ // Shared session and address book used by all instances.
+ // For reasons best left unknown, MAPI doesn't seem to like
+ // having different threads playing with supposedly different
+ // sessions and address books. They ll end up fighting over
+ // the same resources, with hangups and GPF resulting. Not nice.
+ // So it seems that if everybody (as long as some client is
+ // still alive) is using the same sessions and address books,
+ // MAPI feels better. And who are we to get in the way of MAPI
+ // happiness? Thus the following class members:
+ static BOOL mInitialized ;
+ static BOOL mLogonDone ;
+ static LPMAPISESSION mRootSession ;
+ static LPADRBOOK mRootBook ;
+
+ // Load the MAPI environment
+ BOOL Initialize(void) ;
+ // Allocation of a buffer for transmission to interfaces
+ virtual void AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) override;
+ // Destruction of a buffer provided by the interfaces
+ virtual void FreeBuffer(LPVOID aBuffer) override;
+ // Library management
+ static BOOL LoadMapiLibrary(void) ;
+ static void FreeMapiLibrary(void) ;
+
+private :
+} ;
+
+#endif // nsMapiAddressBook_h___
+
diff --git a/mailnews/addrbook/src/nsMsgVCardService.cpp b/mailnews/addrbook/src/nsMsgVCardService.cpp
new file mode 100644
index 000000000..91f6f522e
--- /dev/null
+++ b/mailnews/addrbook/src/nsMsgVCardService.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgVCardService.h"
+#include "nsVCard.h"
+#include "prmem.h"
+#include "plstr.h"
+
+NS_IMPL_ISUPPORTS(nsMsgVCardService, nsIMsgVCardService)
+
+nsMsgVCardService::nsMsgVCardService()
+{
+}
+
+nsMsgVCardService::~nsMsgVCardService()
+{
+}
+
+NS_IMETHODIMP_(void) nsMsgVCardService::CleanVObject(VObject * o)
+{
+ cleanVObject(o);
+}
+
+NS_IMETHODIMP_(VObject *) nsMsgVCardService::NextVObjectInList(VObject * o)
+{
+ return nextVObjectInList(o);
+}
+
+NS_IMETHODIMP_(VObject *) nsMsgVCardService::Parse_MIME(const char *input, uint32_t len)
+{
+ return parse_MIME(input, (unsigned long)len);
+}
+
+NS_IMETHODIMP_(char *) nsMsgVCardService::FakeCString(VObject * o)
+{
+ return fakeCString(vObjectUStringZValue(o));
+}
+
+NS_IMETHODIMP_(VObject *) nsMsgVCardService::IsAPropertyOf(VObject * o, const char *id)
+{
+ return isAPropertyOf(o,id);
+}
+
+NS_IMETHODIMP_(char *) nsMsgVCardService::WriteMemoryVObjects(const char *s, int32_t *len, VObject * list, bool expandSpaces)
+{
+ return writeMemoryVObjects((char *)s, len, list, expandSpaces);
+}
+
+NS_IMETHODIMP_(VObject *) nsMsgVCardService::NextVObject(VObjectIterator * i)
+{
+ return nextVObject(i);
+}
+
+NS_IMETHODIMP_(void) nsMsgVCardService::InitPropIterator(VObjectIterator * i, VObject * o)
+{
+ initPropIterator(i,o);
+}
+
+NS_IMETHODIMP_(int32_t) nsMsgVCardService::MoreIteration(VObjectIterator * i)
+{
+ return ((int32_t)moreIteration(i));
+}
+
+NS_IMETHODIMP_(const char *) nsMsgVCardService::VObjectName(VObject * o)
+{
+ return vObjectName(o);
+}
+
+NS_IMETHODIMP_(char *) nsMsgVCardService::VObjectAnyValue(VObject * o)
+{
+ char *retval = (char *)PR_MALLOC(strlen((char *)vObjectAnyValue(o)) + 1);
+ if (retval)
+ PL_strcpy(retval, (char *) vObjectAnyValue(o));
+ return retval;
+}
diff --git a/mailnews/addrbook/src/nsMsgVCardService.h b/mailnews/addrbook/src/nsMsgVCardService.h
new file mode 100644
index 000000000..7e7da8766
--- /dev/null
+++ b/mailnews/addrbook/src/nsMsgVCardService.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgVCardService_h___
+#define nsMsgVCardService_h___
+
+#include "nsIMsgVCardService.h"
+#include "nsISupports.h"
+
+class nsMsgVCardService : public nsIMsgVCardService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGVCARDSERVICE
+
+ nsMsgVCardService();
+
+private:
+ virtual ~nsMsgVCardService();
+};
+
+#endif /* nsMsgVCardService_h___ */
diff --git a/mailnews/addrbook/src/nsVCard.cpp b/mailnews/addrbook/src/nsVCard.cpp
new file mode 100644
index 000000000..b8c2455b2
--- /dev/null
+++ b/mailnews/addrbook/src/nsVCard.cpp
@@ -0,0 +1,1571 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/***************************************************************************
+(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+
+For purposes of this license notice, the term Licensors shall mean,
+collectively, Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+The term Licensor shall mean any of the Licensors.
+
+Subject to acceptance of the following conditions, permission is hereby
+granted by Licensors without the need for written agreement and without
+license or royalty fees, to use, copy, modify and distribute this
+software for any purpose.
+
+The above copyright notice and the following four paragraphs must be
+reproduced in all copies of this software and any software including
+this software.
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE
+ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR
+MODIFICATIONS.
+
+IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT,
+INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT
+OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.
+
+The software is provided with RESTRICTED RIGHTS. Use, duplication, or
+disclosure by the government are subject to restrictions set forth in
+DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable.
+
+***************************************************************************/
+
+/*
+ * src: vcc.c
+ * doc: Parser for vCard and vCalendar. Note that this code is
+ * generated by a yacc parser generator. Generally it should not
+ * be edited by hand. The real source is vcc.y. The #line directives
+ * can be commented out here to make it easier to trace through
+ * in a debugger. However, if a bug is found it should
+ *
+ * the vcc.y that _this_ vcc.c comes from is lost.
+ * I couldn't find it in the 4.x tree
+ * I bet we took it from IMC's original SDK, but the SDK has been taken down.
+ * see http://www.imc.org/imc-vcard/mail-archive/msg00460.html
+ *
+ * for what it's worth, see
+ * http://softwarestudio.org/libical/
+ * http://lxr.mozilla.org/mozilla/source/other-licenses/libical/src/libicalvcal/vcc.y
+ * http://lxr.mozilla.org/mozilla/source/other-licenses/libical/src/libicalvcal/vcc.c
+ */
+#include "nsVCard.h"
+#include "nsVCardObj.h"
+#include "prprf.h"
+#include "nscore.h"
+#include <string.h>
+#include <ctype.h>
+
+#ifndef lint
+char yysccsid[] = "@(#)yaccpar 1.4 (Berkeley) 02/25/90";
+#endif
+/*#line 2 "vcc.y" */
+
+/* debugging utilities */
+#define DBG_(x)
+
+#ifndef _NO_LINE_FOLDING
+#define _SUPPORT_LINE_FOLDING
+#endif
+
+/**** External Functions ****/
+
+/* assign local name to parser variables and functions so that
+ we can use more than one yacc based parser.
+*/
+
+#define yyparse mime_parse
+#define yylex mime_lex
+#define yyerror mime_error
+#define yychar mime_char
+/* #define p_yyval p_mime_val */
+#undef yyval
+#define yyval mime_yyval
+/* #define p_yylval p_mime_lval */
+#undef yylval
+#define yylval mime_yylval
+#define yydebug mime_debug
+#define yynerrs mime_nerrs
+#define yyerrflag mime_errflag
+#define yyss mime_ss
+#define yyssp mime_ssp
+#define yyvs mime_vs
+#define yyvsp mime_vsp
+#define yylhs mime_lhs
+#define yylen mime_len
+#define yydefred mime_defred
+#define yydgoto mime_dgoto
+#define yysindex mime_sindex
+#define yyrindex mime_rindex
+#define yygindex mime_gindex
+#define yytable mime_table
+#define yycheck mime_check
+#define yyname mime_name
+#define yyrule mime_rule
+#define YYPREFIX "mime_"
+
+#include "prmem.h"
+#include "plstr.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+/**** Types, Constants ****/
+
+#define YYDEBUG 0 /* 1 to compile in some debugging code */
+#define PR_MAXTOKEN 256 /* maximum token (line) length */
+#define YYSTACKSIZE 50 /* ~unref ?*/
+#define PR_MAXLEVEL 10 /* max # of nested objects parseable */
+ /* (includes outermost) */
+
+
+/**** Global Variables ****/
+int mime_lineNum, mime_numErrors; /* yyerror() can use these */
+static VObject* vObjList;
+static VObject *curProp;
+static VObject *curObj;
+static VObject* ObjStack[PR_MAXLEVEL];
+static int ObjStackTop;
+
+
+/* A helpful utility for the rest of the app. */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ extern void yyerror(const char *s);
+ extern char** fieldedProp;
+
+#ifdef __cplusplus
+ }
+#endif
+
+int yyparse();
+
+enum LexMode {
+ L_NORMAL,
+ L_VCARD,
+ L_VCAL,
+ L_VEVENT,
+ L_VTODO,
+ L_VALUES,
+ L_BASE64,
+ L_QUOTED_PRINTABLE
+ };
+
+/**** Private Forward Declarations ****/
+static int pushVObject(const char *prop);
+static VObject* popVObject();
+static int lexGeta();
+static int lexGetc_();
+static int lexGetc();
+static void lexSkipLookahead();
+static int lexLookahead();
+static void lexSkipWhite();
+static void lexClearToken();
+static char * lexStr();
+static char * lexGetDataFromBase64();
+static char * lexGetQuotedPrintable();
+static char * lexGet1Value();
+static char * lexGetWord();
+static void finiLex();
+
+static VObject* parse_MIMEHelper();
+
+/*static char* lexDataFromBase64();*/
+static void lexPopMode(int top);
+static int lexWithinMode(enum LexMode mode);
+static void lexPushMode(enum LexMode mode);
+static void enterProps(const char *s);
+static void enterAttr(const char *s1, const char *s2);
+static void enterValues(const char *value);
+
+/*#line 250 "vcc.y" */
+typedef union {
+ char *str;
+ VObject *vobj;
+ } YYSTYPE;
+/*#line 253 "y_tab.c"*/
+#define EQ 257
+#define COLON 258
+#define DOT 259
+#define SEMICOLON 260
+#define SPACE 261
+#define HTAB 262
+#define LINESEP 263
+#define NEWLINE 264
+#define BEGIN_VCARD 265
+#define END_VCARD 266
+#define BEGIN_VCAL 267
+#define END_VCAL 268
+#define BEGIN_VEVENT 269
+#define END_VEVENT 270
+#define BEGIN_VTODO 271
+#define END_VTODO 272
+#define ID 273
+#define STRING 274
+#define YYERRCODE 256
+short yylhs[] = { -1,
+ 0, 7, 6, 6, 5, 5, 9, 3, 10, 3,
+ 8, 8, 14, 11, 11, 16, 12, 12, 15, 15,
+ 17, 18, 18, 1, 19, 13, 13, 2, 2, 21,
+ 4, 22, 4, 20, 20, 23, 23, 23, 26, 24,
+ 27, 24, 28, 25, 29, 25,
+};
+short yylen[] = { 2,
+ 1, 0, 3, 1, 1, 1, 0, 4, 0, 3,
+ 2, 1, 0, 5, 1, 0, 3, 1, 2, 1,
+ 2, 1, 3, 1, 0, 4, 1, 1, 0, 0,
+ 4, 0, 3, 2, 1, 1, 1, 1, 0, 4,
+ 0, 3, 0, 4, 0, 3,
+};
+short yydefred[] = { 0,
+ 0, 0, 0, 5, 6, 0, 1, 0, 0, 0,
+ 0, 0, 15, 24, 0, 0, 0, 0, 10, 0,
+ 0, 38, 0, 0, 36, 37, 33, 3, 0, 8,
+ 11, 13, 0, 0, 0, 0, 31, 34, 0, 17,
+ 0, 0, 0, 42, 0, 46, 0, 21, 19, 28,
+ 0, 0, 40, 44, 0, 25, 14, 23, 0, 26,
+};
+short yydgoto[] = { 3,
+ 15, 51, 4, 5, 6, 7, 12, 22, 8, 9,
+ 17, 18, 52, 42, 40, 29, 41, 48, 59, 23,
+ 10, 11, 24, 25, 26, 33, 34, 35, 36,
+};
+short yysindex[] = { -227,
+ 0, 0, 0, 0, 0, 0, 0, -249, -262, -253,
+ -258, -227, 0, 0, 0, -234, -249, -215, 0, 0,
+ 0, 0, -223, -253, 0, 0, 0, 0, -247, 0,
+ 0, 0, -249, -222, -249, -225, 0, 0, -224, 0,
+ -247, -221, -220, 0, -218, 0, -206, 0, 0, 0,
+ -208, -207, 0, 0, -224, 0, 0, 0, -221, 0,
+};
+short yyrindex[] = { 0,
+ -245, -254, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, -219, 0, -235, 0, 0, -244,
+ -250, 0, 0, -213, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ -201, -255, 0, 0, 0, 0, -216, 0, 0, 0,
+ -205, 0, 0, 0, 0, 0, 0, 0, -255, 0,
+};
+short yygindex[] = { 0,
+ -9, 0, 0, 0, 0, 47, 0, -8, 0, 0,
+ 0, 0, 2, 0, 19, 0, 0, 0, 0, 38,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+#define YYTABLESIZE 268
+short yytable[] = { 16,
+ 4, 30, 13, 19, 29, 43, 13, 29, 31, 27,
+ 7, 39, 39, 32, 30, 20, 30, 21, 30, 14,
+ 9, 45, 43, 14, 43, 41, 45, 7, 39, 47,
+ 12, 30, 12, 12, 12, 12, 12, 1, 18, 2,
+ 16, 22, 32, 22, 37, 58, 46, 44, 14, 53,
+ 55, 56, 50, 54, 35, 57, 20, 27, 28, 49,
+ 60, 38, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 2,
+};
+short yycheck[] = { 8,
+ 0, 256, 256, 266, 260, 256, 256, 263, 17, 268,
+ 256, 256, 260, 268, 269, 269, 271, 271, 273, 273,
+ 266, 272, 273, 273, 33, 270, 35, 273, 273, 39,
+ 266, 266, 268, 269, 270, 271, 272, 265, 258, 267,
+ 260, 258, 258, 260, 268, 55, 272, 270, 273, 270,
+ 257, 260, 274, 272, 268, 263, 258, 263, 12, 41,
+ 59, 24, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 265, -1, 267,
+};
+#define YYFINAL 3
+#ifndef YYDEBUG
+#define YYDEBUG 0
+#endif
+#define YYPR_MAXTOKEN 274
+#if YYDEBUG
+char *yyname[] = {
+"end-of-file",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"EQ","COLON","DOT","SEMICOLON",
+"SPACE","HTAB","LINESEP","NEWLINE","BEGIN_VCARD","END_VCARD","BEGIN_VCAL",
+"END_VCAL","BEGIN_VEVENT","END_VEVENT","BEGIN_VTODO","END_VTODO","ID","STRING",
+};
+char *yyrule[] = {
+"$accept : mime",
+"mime : vobjects",
+"$$1 :",
+"vobjects : vobject $$1 vobjects",
+"vobjects : vobject",
+"vobject : vcard",
+"vobject : vcal",
+"$$2 :",
+"vcard : BEGIN_VCARD $$2 items END_VCARD",
+"$$3 :",
+"vcard : BEGIN_VCARD $$3 END_VCARD",
+"items : item items",
+"items : item",
+"$$4 :",
+"item : prop COLON $$4 values LINESEP",
+"item : error",
+"$$5 :",
+"prop : name $$5 attr_params",
+"prop : name",
+"attr_params : attr_param attr_params",
+"attr_params : attr_param",
+"attr_param : SEMICOLON attr",
+"attr : name",
+"attr : name EQ name",
+"name : ID",
+"$$6 :",
+"values : value SEMICOLON $$6 values",
+"values : value",
+"value : STRING",
+"value :",
+"$$7 :",
+"vcal : BEGIN_VCAL $$7 calitems END_VCAL",
+"$$8 :",
+"vcal : BEGIN_VCAL $$8 END_VCAL",
+"calitems : calitem calitems",
+"calitems : calitem",
+"calitem : eventitem",
+"calitem : todoitem",
+"calitem : items",
+"$$9 :",
+"eventitem : BEGIN_VEVENT $$9 items END_VEVENT",
+"$$10 :",
+"eventitem : BEGIN_VEVENT $$10 END_VEVENT",
+"$$11 :",
+"todoitem : BEGIN_VTODO $$11 items END_VTODO",
+"$$12 :",
+"todoitem : BEGIN_VTODO $$12 END_VTODO",
+};
+#endif
+#define yyclearin (yychar=(-1))
+#define yyerrok (yyerrflag=0)
+#ifndef YYSTACKSIZE
+#ifdef YYPR_MAXDEPTH
+#define YYSTACKSIZE YYPR_MAXDEPTH
+#else
+#define YYSTACKSIZE 300
+#endif
+#endif
+int yydebug;
+int yynerrs;
+int yyerrflag;
+int yychar;
+short *yyssp;
+YYSTYPE *yyvsp;
+YYSTYPE yyval;
+YYSTYPE yylval;
+#define yystacksize YYSTACKSIZE
+short yyss[YYSTACKSIZE];
+YYSTYPE yyvs[YYSTACKSIZE];
+/*#line 444 "vcc.y"*/
+/******************************************************************************/
+static int pushVObject(const char *prop)
+ {
+ VObject *newObj;
+ if (ObjStackTop == PR_MAXLEVEL)
+ return FALSE;
+
+ ObjStack[++ObjStackTop] = curObj;
+
+ if (curObj) {
+ newObj = addProp(curObj,prop);
+ curObj = newObj;
+ }
+ else
+ curObj = newVObject(prop);
+
+ return TRUE;
+ }
+
+
+/******************************************************************************/
+/* This pops the recently built vCard off the stack and returns it. */
+static VObject* popVObject()
+ {
+ VObject *oldObj;
+ if (ObjStackTop < 0) {
+ yyerror("pop on empty Object Stack\n");
+ return 0;
+ }
+ oldObj = curObj;
+ curObj = ObjStack[ObjStackTop--];
+
+ return oldObj;
+ }
+
+extern "C" void deleteString(char *p);
+
+static void enterValues(const char *value)
+ {
+ if (fieldedProp && *fieldedProp) {
+ if (value) {
+ addPropValue(curProp,*fieldedProp,value);
+ }
+ /* else this field is empty, advance to next field */
+ fieldedProp++;
+ }
+ else {
+ if (value) {
+ setVObjectUStringZValue_(curProp,fakeUnicode(value,0));
+ }
+ }
+ deleteString((char *)value);
+ }
+
+static void enterProps(const char *s)
+ {
+ curProp = addGroup(curObj,s);
+ deleteString((char *)s);
+ }
+
+static void enterAttr(const char *s1, const char *s2)
+{
+ const char *p1, *p2 = nullptr;
+ p1 = lookupProp_(s1);
+ if (s2) {
+ VObject *a;
+ p2 = lookupProp_(s2);
+ a = addProp(curProp,p1);
+ setVObjectStringZValue(a,p2);
+ }
+ else
+ addProp(curProp,p1);
+ if (PL_strcasecmp(p1,VCBase64Prop) == 0 || (s2 && PL_strcasecmp(p2,VCBase64Prop)==0))
+ lexPushMode(L_BASE64);
+ else if (PL_strcasecmp(p1,VCQuotedPrintableProp) == 0
+ || (s2 && PL_strcasecmp(p2,VCQuotedPrintableProp)==0))
+ lexPushMode(L_QUOTED_PRINTABLE);
+ deleteString((char *)s1); deleteString((char *)s2);
+}
+
+
+#define PR_MAX_LEX_LOOKAHEAD_0 32
+#define PR_MAX_LEX_LOOKAHEAD 64
+#define PR_MAX_LEX_MODE_STACK_SIZE 10
+#define LEXMODE() (lexBuf.lexModeStack[lexBuf.lexModeStackTop])
+
+struct LexBuf {
+ /* input */
+ char *inputString;
+ unsigned long curPos;
+ unsigned long inputLen;
+ /* lookahead buffer */
+ /* -- lookahead buffer is short instead of char so that EOF
+ / can be represented correctly.
+ */
+ unsigned long len;
+ short buf[PR_MAX_LEX_LOOKAHEAD];
+ unsigned long getPtr;
+ /* context stack */
+ unsigned long lexModeStackTop;
+ enum LexMode lexModeStack[PR_MAX_LEX_MODE_STACK_SIZE];
+ /* token buffer */
+ unsigned long maxToken;
+ char *strs;
+ unsigned long strsLen;
+ } lexBuf;
+
+static void lexPushMode(enum LexMode mode)
+ {
+ if (lexBuf.lexModeStackTop == (PR_MAX_LEX_MODE_STACK_SIZE-1))
+ yyerror("lexical context stack overflow");
+ else {
+ lexBuf.lexModeStack[++lexBuf.lexModeStackTop] = mode;
+ }
+ }
+
+static void lexPopMode(int top)
+ {
+ /* special case of pop for ease of error recovery -- this
+ version will never underflow */
+ if (top)
+ lexBuf.lexModeStackTop = 0;
+ else
+ if (lexBuf.lexModeStackTop > 0) lexBuf.lexModeStackTop--;
+ }
+
+static int lexWithinMode(enum LexMode mode) {
+ unsigned long i;
+ for (i=0;i<lexBuf.lexModeStackTop;i++)
+ if (mode == lexBuf.lexModeStack[i]) return 1;
+ return 0;
+ }
+
+static int lexGetc_()
+{
+ /* get next char from input, no buffering. */
+ if (lexBuf.curPos == lexBuf.inputLen)
+ return EOF;
+ else if (lexBuf.inputString)
+ return *(lexBuf.inputString + lexBuf.curPos++);
+
+ return -1;
+}
+
+static int lexGeta()
+ {
+ ++lexBuf.len;
+ return (lexBuf.buf[lexBuf.getPtr] = lexGetc_());
+ }
+
+static int lexGeta_(int i)
+ {
+ ++lexBuf.len;
+ return (lexBuf.buf[(lexBuf.getPtr+i)%PR_MAX_LEX_LOOKAHEAD] = lexGetc_());
+ }
+
+static void lexSkipLookahead() {
+ if (lexBuf.len > 0 && lexBuf.buf[lexBuf.getPtr]!=EOF) {
+ /* don't skip EOF. */
+ lexBuf.getPtr = (lexBuf.getPtr + 1) % PR_MAX_LEX_LOOKAHEAD;
+ lexBuf.len--;
+ }
+ }
+
+static int lexLookahead() {
+ int c = (lexBuf.len)?
+ lexBuf.buf[lexBuf.getPtr]:
+ lexGeta();
+ /* do the \r\n -> \n or \r -> \n translation here */
+ if (c == '\r') {
+ int a = (lexBuf.len>1)?
+ lexBuf.buf[(lexBuf.getPtr+1)%PR_MAX_LEX_LOOKAHEAD]:
+ lexGeta_(1);
+ if (a == '\n') {
+ lexSkipLookahead();
+ }
+ lexBuf.buf[lexBuf.getPtr] = c = '\n';
+ }
+ else if (c == '\n') {
+ int a = (lexBuf.len>1)?
+ lexBuf.buf[lexBuf.getPtr+1]:
+ lexGeta_(1);
+ if (a == '\r') {
+ lexSkipLookahead();
+ }
+ lexBuf.buf[lexBuf.getPtr] = '\n';
+ }
+ return c;
+ }
+
+static int lexGetc() {
+ int c = lexLookahead();
+ if (lexBuf.len > 0 && lexBuf.buf[lexBuf.getPtr]!=EOF) {
+ /* EOF will remain in lookahead buffer */
+ lexBuf.getPtr = (lexBuf.getPtr + 1) % PR_MAX_LEX_LOOKAHEAD;
+ lexBuf.len--;
+ }
+ return c;
+ }
+
+static void lexSkipLookaheadWord() {
+ if (lexBuf.strsLen <= lexBuf.len) {
+ lexBuf.len -= lexBuf.strsLen;
+ lexBuf.getPtr = (lexBuf.getPtr + lexBuf.strsLen) % PR_MAX_LEX_LOOKAHEAD;
+ }
+ }
+
+static void lexClearToken()
+ {
+ lexBuf.strsLen = 0;
+ }
+
+static void lexAppendc(int c)
+ {
+ lexBuf.strs[lexBuf.strsLen] = c;
+ /* append up to zero termination */
+ if (c == 0) return;
+ lexBuf.strsLen++;
+ if (lexBuf.strsLen >= lexBuf.maxToken) {
+ /* double the token string size */
+ lexBuf.maxToken <<= 1;
+ lexBuf.strs = (char*) PR_Realloc(lexBuf.strs,lexBuf.maxToken);
+ }
+ }
+
+static char* lexStr() {
+ return dupStr(lexBuf.strs,lexBuf.strsLen+1);
+ }
+
+static void lexSkipWhite() {
+ int c = lexLookahead();
+ while (c == ' ' || c == '\t') {
+ lexSkipLookahead();
+ c = lexLookahead();
+ }
+ }
+
+static char* lexGetWord() {
+ int c;
+ lexSkipWhite();
+ lexClearToken();
+ c = lexLookahead();
+ while (c != EOF && !PL_strchr("\t\n ;:=",(char)c)) {
+ lexAppendc(c);
+ lexSkipLookahead();
+ c = lexLookahead();
+ }
+ lexAppendc(0);
+ return lexStr();
+ }
+
+#if 0
+static void lexPushLookahead(char *s, int len) {
+ int putptr;
+ if (len == 0) len = PL_strlen(s);
+ putptr = lexBuf.getPtr - len;
+ /* this function assumes that length of word to push back
+ / is not greater than PR_MAX_LEX_LOOKAHEAD.
+ */
+ if (putptr < 0) putptr += PR_MAX_LEX_LOOKAHEAD;
+ lexBuf.getPtr = putptr;
+ while (*s) {
+ lexBuf.buf[putptr] = *s++;
+ putptr = (putptr + 1) % PR_MAX_LEX_LOOKAHEAD;
+ }
+ lexBuf.len += len;
+ }
+#endif
+
+static void lexPushLookaheadc(int c) {
+ int putptr;
+ /* can't putback EOF, because it never leaves lookahead buffer */
+ if (c == EOF) return;
+ putptr = (int) lexBuf.getPtr - 1;
+ if (putptr < 0) putptr += PR_MAX_LEX_LOOKAHEAD;
+ lexBuf.getPtr = putptr;
+ lexBuf.buf[putptr] = c;
+ lexBuf.len += 1;
+ }
+
+static char* lexLookaheadWord() {
+ /* this function can lookahead word with max size of PR_MAX_LEX_LOOKAHEAD_0
+ / and thing bigger than that will stop the lookahead and return 0;
+ / leading white spaces are not recoverable.
+ */
+ int c;
+ int len = 0;
+ int curgetptr = 0;
+ lexSkipWhite();
+ lexClearToken();
+ curgetptr = (int) lexBuf.getPtr; /* remember! */
+ while (len < (PR_MAX_LEX_LOOKAHEAD_0)) {
+ c = lexGetc();
+ len++;
+ if (c == EOF || PL_strchr("\t\n ;:=", (char)c)) {
+ lexAppendc(0);
+ /* restore lookahead buf. */
+ lexBuf.len += len;
+ lexBuf.getPtr = curgetptr;
+ return lexStr();
+ }
+ else
+ lexAppendc(c);
+ }
+ lexBuf.len += len; /* char that has been moved to lookahead buffer */
+ lexBuf.getPtr = curgetptr;
+ return 0;
+ }
+
+#ifdef _SUPPORT_LINE_FOLDING
+static void handleMoreRFC822LineBreak(int c) {
+ /* support RFC 822 line break in cases like
+ * ADR: foo;
+ * morefoo;
+ * more foo;
+ */
+ if (c == ';') {
+ int a;
+ lexSkipLookahead();
+ /* skip white spaces */
+ a = lexLookahead();
+ while (a == ' ' || a == '\t') {
+ lexSkipLookahead();
+ a = lexLookahead();
+ }
+ if (a == '\n') {
+ lexSkipLookahead();
+ a = lexLookahead();
+ if (a == ' ' || a == '\t') {
+ /* continuation, throw away all the \n and spaces read so
+ * far
+ */
+ lexSkipWhite();
+ lexPushLookaheadc(';');
+ }
+ else {
+ lexPushLookaheadc('\n');
+ lexPushLookaheadc(';');
+ }
+ }
+ else {
+ lexPushLookaheadc(';');
+ }
+ }
+ }
+
+static char* lexGet1Value() {
+/* int size = 0; */
+ int c;
+ lexSkipWhite();
+ c = lexLookahead();
+ lexClearToken();
+ while (c != EOF && c != ';') {
+ if (c == '\n') {
+ int a;
+ lexSkipLookahead();
+ a = lexLookahead();
+ if (a == ' ' || a == '\t') {
+ lexAppendc(' ');
+ lexSkipLookahead();
+ }
+ else {
+ lexPushLookaheadc('\n');
+ break;
+ }
+ }
+ else if (c == '\\') {
+ int a;
+ lexSkipLookahead();
+ a = lexLookahead();
+ if (a == '\\' || a == ',' || a == ';' || a == ':') {
+ lexAppendc(a);
+ }
+ else if (a == 'n' || a == 'N') {
+ lexAppendc('\n');
+ }
+ else {
+ lexAppendc(c);
+ lexAppendc(a);
+ }
+ lexSkipLookahead();
+ }
+ else {
+ lexAppendc(c);
+ lexSkipLookahead();
+ }
+ c = lexLookahead();
+ }
+ lexAppendc(0);
+ handleMoreRFC822LineBreak(c);
+ return c==EOF?0:lexStr();
+ }
+#endif
+
+
+#ifndef _SUPPORT_LINE_FOLDING
+static char* lexGetStrUntil(char *termset) {
+ int c = lexLookahead();
+ lexClearToken();
+ while (c != EOF && !PL_strchr(termset,c)) {
+ lexAppendc(c);
+ lexSkipLookahead();
+ c = lexLookahead();
+ }
+ lexAppendc(0);
+ return c==EOF?0:lexStr();
+ }
+#endif /* ! _SUPPORT_LINE_FOLDING */
+
+static int match_begin_name(int end) {
+ char *n = lexLookaheadWord();
+ int token = ID;
+ if (n) {
+ if (!PL_strcasecmp(n,"vcard")) token = end?END_VCARD:BEGIN_VCARD;
+ else if (!PL_strcasecmp(n,"vcalendar")) token = end?END_VCAL:BEGIN_VCAL;
+ else if (!PL_strcasecmp(n,"vevent")) token = end?END_VEVENT:BEGIN_VEVENT;
+ else if (!PL_strcasecmp(n,"vtodo")) token = end?END_VTODO:BEGIN_VTODO;
+ deleteString(n);
+ return token;
+ }
+ return 0;
+ }
+
+void initLex(const char *inputstring, unsigned long inputlen)
+ {
+ /* initialize lex mode stack */
+ lexBuf.lexModeStack[lexBuf.lexModeStackTop=0] = L_NORMAL;
+
+ /* iniatialize lex buffer. */
+ lexBuf.inputString = (char*) inputstring;
+ lexBuf.inputLen = inputlen;
+ lexBuf.curPos = 0;
+
+ lexBuf.len = 0;
+ lexBuf.getPtr = 0;
+
+ lexBuf.maxToken = PR_MAXTOKEN;
+ lexBuf.strs = (char*)PR_CALLOC(PR_MAXTOKEN);
+ lexBuf.strsLen = 0;
+
+ }
+
+static void finiLex() {
+ PR_FREEIF(lexBuf.strs);
+ }
+
+
+/******************************************************************************/
+/* This parses and converts the base64 format for binary encoding into
+ * a decoded buffer (allocated with new). See RFC 1521.
+ */
+static char * lexGetDataFromBase64()
+ {
+ unsigned long bytesLen = 0, bytesMax = 0;
+ int quadIx = 0, pad = 0;
+ unsigned long trip = 0;
+ unsigned char b;
+ int c;
+ unsigned char *bytes = nullptr;
+ unsigned char *oldBytes = nullptr;
+
+ DBG_(("db: lexGetDataFromBase64\n"));
+ while (1) {
+ c = lexGetc();
+ if (c == '\n') {
+ ++mime_lineNum;
+ if (lexLookahead() == '\n') {
+ /* a '\n' character by itself means end of data */
+ break;
+ }
+ else continue; /* ignore '\n' */
+ }
+ else {
+ if ((c >= 'A') && (c <= 'Z'))
+ b = (unsigned char)(c - 'A');
+ else if ((c >= 'a') && (c <= 'z'))
+ b = (unsigned char)(c - 'a') + 26;
+ else if ((c >= '0') && (c <= '9'))
+ b = (unsigned char)(c - '0') + 52;
+ else if (c == '+')
+ b = 62;
+ else if (c == '/')
+ b = 63;
+ else if (c == '=' && (quadIx == 2 || quadIx == 3)) {
+ b = 0;
+ pad++;
+ } else if ((c == ' ') || (c == '\t')) {
+ continue;
+ } else { /* error condition */
+ if (bytes)
+ PR_Free (bytes);
+ else if (oldBytes)
+ PR_Free (oldBytes);
+ /* error recovery: skip until 2 adjacent newlines. */
+ DBG_(("db: invalid character 0x%x '%c'\n", c,c));
+ if (c != EOF) {
+ c = lexGetc();
+ while (c != EOF) {
+ if (c == '\n' && lexLookahead() == '\n') {
+ ++mime_lineNum;
+ break;
+ }
+ c = lexGetc();
+ }
+ }
+ return NULL;
+ }
+ trip = (trip << 6) | b;
+ if (++quadIx == 4) {
+ unsigned char outBytes[3];
+ int numOut;
+ int i;
+ for (i = 0; i < 3; i++) {
+ outBytes[2-i] = (unsigned char)(trip & 0xFF);
+ trip >>= 8;
+ }
+ numOut = 3 - pad;
+ if (bytesLen + numOut > bytesMax) {
+ if (!bytes) {
+ bytesMax = 1024;
+ } else {
+ bytesMax <<= 2;
+ oldBytes = bytes;
+ }
+ bytes = (unsigned char*) PR_Realloc(oldBytes, bytesMax);
+ if (!bytes) {
+ mime_error("out of memory while processing BASE64 data\n");
+ break;
+ }
+ }
+ if (bytes) {
+ memcpy(bytes + bytesLen, outBytes, numOut);
+ bytesLen += numOut;
+ }
+ trip = 0;
+ quadIx = 0;
+ pad = 0;
+ }
+ }
+ } /* while */
+ DBG_(("db: bytesLen = %d\n", bytesLen));
+ /* kludge: all this won't be necessary if we have tree form
+ representation */
+ if (bytes) {
+ setValueWithSize(curProp,bytes,(unsigned int)bytesLen);
+ PR_FREEIF(bytes);
+ }
+ else if (oldBytes) {
+ setValueWithSize(curProp,oldBytes,(unsigned int)bytesLen);
+ PR_FREEIF(oldBytes);
+ }
+ return 0;
+ }
+
+static int match_begin_end_name(int end) {
+ int token;
+ lexSkipWhite();
+ if (lexLookahead() != ':') return ID;
+ lexSkipLookahead();
+ lexSkipWhite();
+ token = match_begin_name(end);
+ if (token == ID) {
+ lexPushLookaheadc(':');
+ DBG_(("db: ID '%s'\n", yylval.str));
+ return ID;
+ }
+ else if (token != 0) {
+ lexSkipLookaheadWord();
+ deleteString(yylval.str);
+ DBG_(("db: begin/end %d\n", token));
+ return token;
+ }
+ return 0;
+ }
+
+static char* lexGetQuotedPrintable()
+ {
+ char cur;
+/* unsigned long len = 0; */
+
+ lexClearToken();
+ do {
+ cur = lexGetc();
+ switch (cur) {
+ case '=': {
+ int c = 0;
+ int next[2];
+ int tab [1];
+ int i;
+ for (i = 0; i < 2; i++) {
+ next[i] = lexGetc();
+ if (next[i] >= '0' && next[i] <= '9')
+ c = c * 16 + next[i] - '0';
+ else if (next[i] >= 'A' && next[i] <= 'F')
+ c = c * 16 + next[i] - 'A' + 10;
+ else
+ break;
+ }
+ if (i == 0) {
+ /* single '=' follow by LINESEP is continuation sign? */
+ if (next[0] == '\n') {
+ tab[0] = lexGetc();
+ if (tab[0] == '\t')
+ lexSkipWhite();
+ ++mime_lineNum;
+ }
+ else {
+ lexAppendc(cur);
+ /* lexPushLookaheadc('=');
+ goto EndString; */
+ }
+ }
+ else if (i == 1) {
+ lexPushLookaheadc(next[1]);
+ lexPushLookaheadc(next[0]);
+ lexAppendc('=');
+ } else {
+ lexAppendc(c);
+ }
+ break;
+ } /* '=' */
+ case '\n': {
+ lexPushLookaheadc('\n');
+ goto EndString;
+ }
+ case ';': {
+ lexPushLookaheadc(';');
+ goto EndString;
+ }
+ case (char)EOF:
+ break;
+ default:
+ lexAppendc(cur);
+ break;
+ } /* switch */
+ } while (cur != (char)EOF);
+
+EndString:
+ lexAppendc(0);
+ return lexStr();
+ } /* LexQuotedPrintable */
+
+static int yylex() {
+/* int token = 0; */
+
+ int lexmode = LEXMODE();
+ if (lexmode == L_VALUES) {
+ int c = lexGetc();
+ if (c == ';') {
+ DBG_(("db: SEMICOLON\n"));
+#ifdef _SUPPORT_LINE_FOLDING
+ lexPushLookaheadc(c);
+ handleMoreRFC822LineBreak(c);
+ lexSkipLookahead();
+#endif
+ return SEMICOLON;
+ }
+ else if (PL_strchr("\n",(char)c)) {
+ ++mime_lineNum;
+ /* consume all line separator(s) adjacent to each other */
+ c = lexLookahead();
+ while (PL_strchr("\n",(char)c)) {
+ lexSkipLookahead();
+ c = lexLookahead();
+ ++mime_lineNum;
+ }
+ DBG_(("db: LINESEP\n"));
+ return LINESEP;
+ }
+ else {
+ char *p = 0;
+ lexPushLookaheadc(c);
+ if (lexWithinMode(L_BASE64)) {
+ /* get each char and convert to bin on the fly... */
+ p = lexGetDataFromBase64();
+ yylval.str = p;
+ return !p && lexLookahead() == EOF ? 0 : STRING;
+ }
+ else if (lexWithinMode(L_QUOTED_PRINTABLE)) {
+ p = lexGetQuotedPrintable();
+ }
+ else {
+#ifdef _SUPPORT_LINE_FOLDING
+ p = lexGet1Value();
+#else
+ p = lexGetStrUntil(";\n");
+#endif
+ }
+ if (p && (*p || lexLookahead() != EOF)) {
+ DBG_(("db: STRING: '%s'\n", p));
+ yylval.str = p;
+ return STRING;
+ }
+ else return 0;
+ }
+ }
+ else {
+ /* normal mode */
+ while (1) {
+ int c = lexGetc();
+ switch(c) {
+ case ':': {
+ /* consume all line separator(s) adjacent to each other */
+ /* ignoring linesep immediately after colon. */
+ c = lexLookahead();
+ while (PL_strchr("\n",(char)c)) {
+ lexSkipLookahead();
+ c = lexLookahead();
+ ++mime_lineNum;
+ }
+ DBG_(("db: COLON\n"));
+ return COLON;
+ }
+ case ';':
+ DBG_(("db: SEMICOLON\n"));
+ return SEMICOLON;
+ case '=':
+ DBG_(("db: EQ\n"));
+ return EQ;
+ /* ignore whitespace in this mode */
+ case '\t':
+ case ' ': continue;
+ case '\n': {
+ ++mime_lineNum;
+ continue;
+ }
+ case EOF: return 0;
+ break;
+ default: {
+ lexPushLookaheadc(c);
+ if (isalpha(c)) {
+ char *t = lexGetWord();
+ yylval.str = t;
+ if (!PL_strcasecmp(t, "BEGIN")) {
+ return match_begin_end_name(0);
+ }
+ else if (!PL_strcasecmp(t,"END")) {
+ return match_begin_end_name(1);
+ }
+ else {
+ DBG_(("db: ID '%s'\n", t));
+ return ID;
+ }
+ }
+ else {
+ /* unknown token */
+ return 0;
+ }
+ break;
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+
+/***************************************************************************/
+/*** Public Functions ****/
+/***************************************************************************/
+
+static VObject* parse_MIMEHelper()
+ {
+ ObjStackTop = -1;
+ mime_numErrors = 0;
+ mime_lineNum = 1;
+ vObjList = 0;
+ curObj = 0;
+
+ if (yyparse() != 0)
+ return 0;
+
+ finiLex();
+ return vObjList;
+ }
+
+/******************************************************************************/
+VObject* parse_MIME(const char *input, unsigned long len)
+ {
+ initLex(input, len);
+ return parse_MIMEHelper();
+ }
+
+static MimeErrorHandler mimeErrorHandler;
+
+void registerMimeErrorHandler(MimeErrorHandler me)
+ {
+ mimeErrorHandler = me;
+ }
+
+void mime_error(const char *s)
+{
+ char msg[256];
+ if (mimeErrorHandler) {
+ PR_snprintf(msg, sizeof(msg), "%s at line %d", s, mime_lineNum);
+ mimeErrorHandler(msg);
+ }
+}
+
+/*#line 1221 "y_tab.c"*/
+#define YYABORT goto yyabort
+#define YYACCEPT goto yyaccept
+#define YYERROR goto yyerrlab
+int
+yyparse()
+{
+ int yym, yyn, yystate;
+#if YYDEBUG
+ char *yys;
+ extern char *getenv();
+
+ if (yys = getenv("YYDEBUG"))
+ {
+ yyn = *yys;
+ if (yyn >= '0' && yyn <= '9')
+ yydebug = yyn - '0';
+ }
+#endif
+
+ yynerrs = 0;
+ yyerrflag = 0;
+ yychar = (-1);
+
+ yyssp = yyss;
+ yyvsp = yyvs;
+ *yyssp = yystate = 0;
+
+yyloop:
+ if ((yyn = yydefred[yystate])) goto yyreduce;
+ if (yychar < 0)
+ {
+ if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYPR_MAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("yydebug: state %d, reading %d (%s)\n", yystate,
+ yychar, yys);
+ }
+#endif
+ }
+ if ((yyn = yysindex[yystate]) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: state %d, shifting to state %d\n",
+ yystate, yytable[yyn]);
+#endif
+ if (yyssp >= yyss + yystacksize - 1)
+ {
+ goto yyoverflow;
+ }
+ *++yyssp = yystate = yytable[yyn];
+ *++yyvsp = yylval;
+ yychar = (-1);
+ if (yyerrflag > 0) --yyerrflag;
+ goto yyloop;
+ }
+ if ((yyn = yyrindex[yystate]) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+ {
+ yyn = yytable[yyn];
+ goto yyreduce;
+ }
+ if (yyerrflag) goto yyinrecovery;
+#ifdef lint
+ goto yynewerror;
+#endif
+/*yynewerror: */
+ yyerror("syntax error");
+#ifdef lint
+ goto yyerrlab;
+#endif
+yyerrlab:
+ ++yynerrs;
+yyinrecovery:
+ if (yyerrflag < 3)
+ {
+ yyerrflag = 3;
+ for (;;)
+ {
+ if ((yyn = yysindex[*yyssp]) && (yyn += YYERRCODE) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == YYERRCODE)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: state %d, error recovery shifting\
+ to state %d\n", *yyssp, yytable[yyn]);
+#endif
+ if (yyssp >= yyss + yystacksize - 1)
+ {
+ goto yyoverflow;
+ }
+ *++yyssp = yystate = yytable[yyn];
+ *++yyvsp = yylval;
+ goto yyloop;
+ }
+ else
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: error recovery discarding state %d\n",
+ *yyssp);
+#endif
+ if (yyssp <= yyss) goto yyabort;
+ --yyssp;
+ --yyvsp;
+ }
+ }
+ }
+ else
+ {
+ if (yychar == 0) goto yyabort;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYPR_MAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("yydebug: state %d, error recovery discards token %d (%s)\n",
+ yystate, yychar, yys);
+ }
+#endif
+ yychar = (-1);
+ goto yyloop;
+ }
+yyreduce:
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: state %d, reducing by rule %d (%s)\n",
+ yystate, yyn, yyrule[yyn]);
+#endif
+ yym = yylen[yyn];
+ yyval = yyvsp[1-yym];
+ switch (yyn)
+ {
+case 2:
+/*#line 282 "vcc.y"*/
+{ addList(&vObjList, yyvsp[0].vobj ); curObj = 0; }
+break;
+case 4:
+/*#line 285 "vcc.y"*/
+{ addList(&vObjList, yyvsp[0].vobj ); curObj = 0; }
+break;
+case 7:
+/*#line 294 "vcc.y"*/
+{
+ lexPushMode(L_VCARD);
+ if (!pushVObject(VCCardProp)) YYERROR;
+ }
+break;
+case 8:
+/*#line 299 "vcc.y"*/
+{
+ lexPopMode(0);
+ yyval.vobj = popVObject();
+ }
+break;
+case 9:
+/*#line 304 "vcc.y"*/
+{
+ lexPushMode(L_VCARD);
+ if (!pushVObject(VCCardProp)) YYERROR;
+ }
+break;
+case 10:
+/*#line 309 "vcc.y"*/
+{
+ lexPopMode(0);
+ yyval.vobj = popVObject();
+ }
+break;
+case 13:
+/*#line 320 "vcc.y"*/
+{
+ lexPushMode(L_VALUES);
+ }
+break;
+case 14:
+/*#line 324 "vcc.y"*/
+{
+ if (lexWithinMode(L_BASE64) || lexWithinMode(L_QUOTED_PRINTABLE))
+ lexPopMode(0);
+ lexPopMode(0);
+ }
+break;
+case 16:
+/*#line 332 "vcc.y"*/
+{
+ enterProps(yyvsp[0].str );
+ }
+break;
+case 18:
+/*#line 337 "vcc.y"*/
+{
+ enterProps(yyvsp[0].str );
+ }
+break;
+case 22:
+/*#line 350 "vcc.y"*/
+{
+ enterAttr(yyvsp[0].str ,0);
+ }
+break;
+case 23:
+/*#line 354 "vcc.y"*/
+{
+ enterAttr(yyvsp[-2].str ,yyvsp[0].str );
+
+ }
+break;
+case 25:
+/*#line 363 "vcc.y"*/
+{ enterValues(yyvsp[-1].str ); }
+break;
+case 27:
+/*#line 365 "vcc.y"*/
+{ enterValues(yyvsp[0].str ); }
+break;
+case 29:
+/*#line 370 "vcc.y"*/
+{ yyval.str = 0; }
+break;
+case 30:
+/*#line 375 "vcc.y"*/
+{ if (!pushVObject(VCCalProp)) YYERROR; }
+break;
+case 31:
+/*#line 378 "vcc.y"*/
+{ yyval.vobj = popVObject(); }
+break;
+case 32:
+/*#line 380 "vcc.y"*/
+{ if (!pushVObject(VCCalProp)) YYERROR; }
+break;
+case 33:
+/*#line 382 "vcc.y"*/
+{ yyval.vobj = popVObject(); }
+break;
+case 39:
+/*#line 397 "vcc.y"*/
+{
+ lexPushMode(L_VEVENT);
+ if (!pushVObject(VCEventProp)) YYERROR;
+ }
+break;
+case 40:
+/*#line 403 "vcc.y"*/
+{
+ lexPopMode(0);
+ popVObject();
+ }
+break;
+case 41:
+/*#line 408 "vcc.y"*/
+{
+ lexPushMode(L_VEVENT);
+ if (!pushVObject(VCEventProp)) YYERROR;
+ }
+break;
+case 42:
+/*#line 413 "vcc.y"*/
+{
+ lexPopMode(0);
+ popVObject();
+ }
+break;
+case 43:
+/*#line 421 "vcc.y"*/
+{
+ lexPushMode(L_VTODO);
+ if (!pushVObject(VCTodoProp)) YYERROR;
+ }
+break;
+case 44:
+/*#line 427 "vcc.y"*/
+{
+ lexPopMode(0);
+ popVObject();
+ }
+break;
+case 45:
+/*#line 432 "vcc.y"*/
+{
+ lexPushMode(L_VTODO);
+ if (!pushVObject(VCTodoProp)) YYERROR;
+ }
+break;
+case 46:
+/*#line 437 "vcc.y"*/
+{
+ lexPopMode(0);
+ popVObject();
+ }
+break;
+/*#line 1520 "y_tab.c"*/
+ }
+ yyssp -= yym;
+ yystate = *yyssp;
+ yyvsp -= yym;
+ yym = yylhs[yyn];
+ if (yystate == 0 && yym == 0)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: after reduction, shifting from state 0 to\
+ state %d\n", YYFINAL);
+#endif
+ yystate = YYFINAL;
+ *++yyssp = YYFINAL;
+ *++yyvsp = yyval;
+ if (yychar < 0)
+ {
+ if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYPR_MAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("yydebug: state %d, reading %d (%s)\n",
+ YYFINAL, yychar, yys);
+ }
+#endif
+ }
+ if (yychar == 0) goto yyaccept;
+ goto yyloop;
+ }
+ if ((yyn = yygindex[yym]) && (yyn += yystate) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yystate)
+ yystate = yytable[yyn];
+ else
+ yystate = yydgoto[yym];
+#if YYDEBUG
+ if (yydebug)
+ printf("yydebug: after reduction, shifting from state %d \
+to state %d\n", *yyssp, yystate);
+#endif
+ if (yyssp >= yyss + yystacksize - 1)
+ {
+ goto yyoverflow;
+ }
+ *++yyssp = yystate;
+ *++yyvsp = yyval;
+ goto yyloop;
+yyoverflow:
+ yyerror("yacc stack overflow");
+yyabort:
+ return (1);
+yyaccept:
+ return (0);
+}
diff --git a/mailnews/addrbook/src/nsVCard.h b/mailnews/addrbook/src/nsVCard.h
new file mode 100644
index 000000000..041cf0042
--- /dev/null
+++ b/mailnews/addrbook/src/nsVCard.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/***************************************************************************
+(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+
+For purposes of this license notice, the term Licensors shall mean,
+collectively, Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+The term Licensor shall mean any of the Licensors.
+
+Subject to acceptance of the following conditions, permission is hereby
+granted by Licensors without the need for written agreement and without
+license or royalty fees, to use, copy, modify and distribute this
+software for any purpose.
+
+The above copyright notice and the following four paragraphs must be
+reproduced in all copies of this software and any software including
+this software.
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE
+ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR
+MODIFICATIONS.
+
+IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT,
+INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT
+OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.
+
+The software is provided with RESTRICTED RIGHTS. Use, duplication, or
+disclosure by the government are subject to restrictions set forth in
+DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable.
+
+***************************************************************************/
+
+#ifndef __VCC_H__
+#define __VCC_H__ 1
+
+#include "nsVCardObj.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VObject* parse_MIME(const char *input, unsigned long len);
+
+typedef void (*MimeErrorHandler)(char *);
+
+void registerMimeErrorHandler(MimeErrorHandler);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __VCC_H__ */
diff --git a/mailnews/addrbook/src/nsVCardObj.cpp b/mailnews/addrbook/src/nsVCardObj.cpp
new file mode 100644
index 000000000..bf8ceb2fb
--- /dev/null
+++ b/mailnews/addrbook/src/nsVCardObj.cpp
@@ -0,0 +1,1330 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/***************************************************************************
+(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+
+For purposes of this license notice, the term Licensors shall mean,
+collectively, Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+The term Licensor shall mean any of the Licensors.
+
+Subject to acceptance of the following conditions, permission is hereby
+granted by Licensors without the need for written agreement and without
+license or royalty fees, to use, copy, modify and distribute this
+software for any purpose.
+
+The above copyright notice and the following four paragraphs must be
+reproduced in all copies of this software and any software including
+this software.
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE
+ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR
+MODIFICATIONS.
+
+IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT,
+INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT
+OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.
+
+The software is provided with RESTRICTED RIGHTS. Use, duplication, or
+disclosure by the government are subject to restrictions set forth in
+DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable.
+
+***************************************************************************/
+
+/*
+ * doc: vobject and APIs to construct vobject, APIs pretty print
+ * vobject, and convert a vobject into its textual representation.
+ */
+
+#include "prlog.h"
+#include "nsVCard.h"
+#include "nsVCardObj.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+#include "nsStringGlue.h"
+
+/* debugging utilities */
+#define DBG_(x)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ char **fieldedProp;
+
+#ifdef __cplusplus
+ }
+#endif
+
+
+
+static VObject* newVObject_(const char *id);
+#if 0
+static int vObjectValueType(VObject *o);
+static void initVObjectIterator(VObjectIterator *i, VObject *o);
+#endif
+
+/*----------------------------------------------------------------------
+ The following functions involve with memory allocation:
+ newVObject
+ deleteVObject
+ dupStr
+ deleteString
+ newStrItem
+ deleteStrItem
+ ----------------------------------------------------------------------*/
+
+static bool needsQuotedPrintable (const char *s)
+{
+ const unsigned char *p = (const unsigned char *)s;
+
+ while (*p) {
+ if (*p & 0x80 || *p == '\015' || *p == '\012')
+ return true;
+ p++;
+ }
+
+ return false;
+}
+
+VObject* newVObject_(const char *id)
+{
+ VObject *p = (VObject*) new(VObject);
+ p->next = 0;
+ p->id = id;
+ p->prop = 0;
+ VALUE_TYPE(p) = 0;
+ ANY_VALUE_OF(p) = 0;
+ return p;
+}
+
+VObject* newVObject(const char *id)
+{
+ return newVObject_(lookupStr(id));
+}
+
+void deleteVObject(VObject *p)
+{
+ unUseStr(p->id);
+ delete (p);
+}
+
+char* dupStr(const char *s, unsigned int size)
+{
+ char *t;
+ if (size == 0) {
+ size = PL_strlen(s);
+ }
+ t = (char*)PR_CALLOC(size+1);
+ if (t) {
+ memcpy(t,s,size);
+ t[size] = 0;
+ return t;
+ }
+ else {
+ return (char*)0;
+ }
+}
+
+static StrItem* newStrItem(const char *s, StrItem *next)
+{
+ StrItem *p = (StrItem*)PR_CALLOC(sizeof(StrItem));
+ p->next = next;
+ p->s = s;
+ p->refCnt = 1;
+ return p;
+}
+
+extern "C"
+void deleteString(char *p)
+{
+ if (p)
+ PR_Free ((void*)p);
+}
+
+extern "C"
+void deleteStrItem(StrItem *p)
+{
+ if (p)
+ PR_FREEIF (p);
+}
+
+
+
+/*----------------------------------------------------------------------
+ The following function provide accesses to VObject's value.
+ ----------------------------------------------------------------------*/
+
+const char* vObjectName(VObject *o)
+{
+ return NAME_OF(o);
+}
+
+void setVObjectName(VObject *o, const char* id)
+{
+ NAME_OF(o) = id;
+}
+
+const char* vObjectStringZValue(VObject *o)
+{
+ return STRINGZ_VALUE_OF(o);
+}
+
+void setVObjectStringZValue(VObject *o, const char *s)
+{
+ STRINGZ_VALUE_OF(o) = dupStr(s,0);
+ VALUE_TYPE(o) = VCVT_STRINGZ;
+}
+
+void setVObjectStringZValue_(VObject *o, const char *s)
+{
+ STRINGZ_VALUE_OF(o) = s;
+ VALUE_TYPE(o) = VCVT_STRINGZ;
+}
+
+const vwchar_t* vObjectUStringZValue(VObject *o)
+{
+ return USTRINGZ_VALUE_OF(o);
+}
+
+void setVObjectUStringZValue(VObject *o, const vwchar_t *s)
+{
+ USTRINGZ_VALUE_OF(o) = (vwchar_t*) dupStr((char*)s,(uStrLen(s)+1)*2);
+ VALUE_TYPE(o) = VCVT_USTRINGZ;
+}
+
+void setVObjectUStringZValue_(VObject *o, const vwchar_t *s)
+{
+ USTRINGZ_VALUE_OF(o) = s;
+ VALUE_TYPE(o) = VCVT_USTRINGZ;
+}
+
+unsigned int vObjectIntegerValue(VObject *o)
+{
+ return INTEGER_VALUE_OF(o);
+}
+
+void setVObjectIntegerValue(VObject *o, unsigned int i)
+{
+ INTEGER_VALUE_OF(o) = i;
+ VALUE_TYPE(o) = VCVT_UINT;
+}
+
+unsigned long vObjectLongValue(VObject *o)
+{
+ return LONG_VALUE_OF(o);
+}
+
+void setVObjectLongValue(VObject *o, unsigned long l)
+{
+ LONG_VALUE_OF(o) = l;
+ VALUE_TYPE(o) = VCVT_ULONG;
+}
+
+void* vObjectAnyValue(VObject *o)
+{
+ return ANY_VALUE_OF(o);
+}
+
+void setVObjectAnyValue(VObject *o, void *t)
+{
+ ANY_VALUE_OF(o) = t;
+ VALUE_TYPE(o) = VCVT_RAW;
+}
+
+VObject* vObjectVObjectValue(VObject *o)
+{
+ return VOBJECT_VALUE_OF(o);
+}
+
+void setVObjectVObjectValue(VObject *o, VObject *p)
+{
+ VOBJECT_VALUE_OF(o) = p;
+ VALUE_TYPE(o) = VCVT_VOBJECT;
+}
+
+#if 0
+int vObjectValueType(VObject *o)
+{
+ return VALUE_TYPE(o);
+}
+#endif
+
+
+/*----------------------------------------------------------------------
+ The following functions can be used to build VObject.
+ ----------------------------------------------------------------------*/
+
+VObject* addVObjectProp(VObject *o, VObject *p)
+{
+ /* circular link list pointed to tail */
+ /*
+ o {next,id,prop,val}
+ V
+ pn {next,id,prop,val}
+ V
+ ...
+ p1 {next,id,prop,val}
+ V
+ pn
+ -->
+ o {next,id,prop,val}
+ V
+ pn {next,id,prop,val}
+ V
+ p {next,id,prop,val}
+ ...
+ p1 {next,id,prop,val}
+ V
+ pn
+ */
+
+ VObject *tail = o->prop;
+ if (tail) {
+ p->next = tail->next;
+ o->prop = tail->next = p;
+ }
+ else {
+ o->prop = p->next = p;
+ }
+ return p;
+}
+
+VObject* addProp(VObject *o, const char *id)
+{
+ return addVObjectProp(o,newVObject(id));
+}
+
+VObject* addProp_(VObject *o, const char *id)
+{
+ return addVObjectProp(o,newVObject_(id));
+}
+
+void addList(VObject **o, VObject *p)
+{
+ p->next = 0;
+ if (*o == 0) {
+ *o = p;
+ }
+ else {
+ VObject *t = *o;
+ while (t->next) {
+ t = t->next;
+ }
+ t->next = p;
+ }
+}
+
+VObject* nextVObjectInList(VObject *o)
+{
+ return o->next;
+}
+
+VObject* setValueWithSize_(VObject *prop, void *val, unsigned int size)
+{
+ VObject *sizeProp;
+ setVObjectAnyValue(prop, val);
+ sizeProp = addProp(prop,VCDataSizeProp);
+ setVObjectLongValue(sizeProp, size);
+ return prop;
+}
+
+VObject* setValueWithSize(VObject *prop, void *val, unsigned int size)
+{
+ void *p = dupStr((const char *)val,size);
+ return setValueWithSize_(prop,p,p?size:0);
+}
+
+void initPropIterator(VObjectIterator *i, VObject *o)
+{
+ i->start = o->prop;
+ i->next = 0;
+}
+
+#if 0
+void initVObjectIterator(VObjectIterator *i, VObject *o)
+{
+ i->start = o->next;
+ i->next = 0;
+}
+#endif
+
+int moreIteration(VObjectIterator *i)
+{
+ return (i->start && (i->next==0 || i->next!=i->start));
+}
+
+VObject* nextVObject(VObjectIterator *i)
+{
+ if (i->start && i->next != i->start) {
+ if (i->next == 0) {
+ i->next = i->start->next;
+ return i->next;
+ }
+ else {
+ i->next = i->next->next;
+ return i->next;
+ }
+ }
+ else return (VObject*)0;
+}
+
+VObject* isAPropertyOf(VObject *o, const char *id)
+{
+ VObjectIterator i;
+ initPropIterator(&i,o);
+ while (moreIteration(&i)) {
+ VObject *each = nextVObject(&i);
+ if (!PL_strcasecmp(id,each->id))
+ return each;
+ }
+ return (VObject*)0;
+}
+
+VObject* addGroup(VObject *o, const char *g)
+{
+ /*
+ a.b.c
+ -->
+ prop(c)
+ prop(VCGrouping=b)
+ prop(VCGrouping=a)
+ */
+ char *dot = PL_strrchr(g,'.');
+ if (dot) {
+ VObject *p, *t;
+ char *gs, *n = dot+1;
+ gs = dupStr(g,0); /* so we can write to it. */
+ t = p = addProp_(o,lookupProp(n));
+ dot = PL_strrchr(gs,'.');
+ *dot = 0;
+ do {
+ dot = PL_strrchr(gs,'.');
+ if (dot) {
+ n = dot+1;
+ *dot=0;
+ }
+ else
+ n = gs;
+ /* property(VCGroupingProp=n);
+ * and the value may have VCGrouping property
+ */
+ t = addProp(t,VCGroupingProp);
+ setVObjectStringZValue(t,lookupProp_(n));
+ } while (n != gs);
+ deleteString(gs);
+ return p;
+ }
+ else
+ return addProp_(o,lookupProp(g));
+}
+
+VObject* addPropValue(VObject *o, const char *p, const char *v)
+{
+ VObject *prop;
+ prop = addProp(o,p);
+ if (v) {
+ setVObjectUStringZValue_(prop, fakeUnicode(v,0));
+ if (needsQuotedPrintable (v)) {
+ if (PL_strcasecmp (VCCardProp, vObjectName(o)) == 0)
+ addProp (prop, VCQuotedPrintableProp);
+ else
+ addProp (o, VCQuotedPrintableProp);
+ }
+ }
+ else
+ setVObjectUStringZValue_(prop, fakeUnicode("",0));
+
+ return prop;
+}
+
+VObject* addPropSizedValue_(VObject *o, const char *p, const char *v,
+ unsigned int size)
+{
+ VObject *prop;
+ prop = addProp(o,p);
+ setValueWithSize_(prop, (void*)v, size);
+ return prop;
+}
+
+VObject* addPropSizedValue(VObject *o, const char *p, const char *v,
+ unsigned int size)
+{
+ return addPropSizedValue_(o,p,dupStr(v,size),size);
+}
+
+void cleanVObject(VObject *o)
+{
+ if (o == 0) return;
+ if (o->prop) {
+ /* destroy time: cannot use the iterator here.
+ Have to break the cycle in the circular link
+ list and turns it into regular NULL-terminated
+ list -- since at some point of destruction,
+ the reference entry for the iterator to work
+ will not longer be valid.
+ */
+ VObject *p;
+ p = o->prop->next;
+ o->prop->next = 0;
+ do {
+ VObject *t = p->next;
+ cleanVObject(p);
+ p = t;
+ } while (p);
+ }
+ switch (VALUE_TYPE(o)) {
+ case VCVT_USTRINGZ:
+ case VCVT_STRINGZ:
+ case VCVT_RAW:
+ /* assume they are all allocated by malloc. */
+ if ((char*) STRINGZ_VALUE_OF(o))
+ PR_Free ((char*)STRINGZ_VALUE_OF(o));
+ break;
+ case VCVT_VOBJECT:
+ cleanVObject(VOBJECT_VALUE_OF(o));
+ break;
+ }
+ deleteVObject(o);
+}
+
+void cleanVObjects(VObject *list)
+{
+ while (list) {
+ VObject *t = list;
+ list = nextVObjectInList(list);
+ cleanVObject(t);
+ }
+}
+
+/*----------------------------------------------------------------------
+ The following is a String Table Facilities.
+ ----------------------------------------------------------------------*/
+
+#define STRTBLSIZE 255
+
+static StrItem *strTbl[STRTBLSIZE];
+
+static unsigned int hashStr(const char *s)
+{
+ unsigned int h = 0;
+ int i;
+ for (i=0;s[i];i++) {
+ h += s[i]*i;
+ }
+ return h % STRTBLSIZE;
+}
+
+void unUseStr(const char *s)
+{
+ StrItem *t, *p;
+ unsigned int h = hashStr(s);
+ if ((t = strTbl[h]) != 0) {
+ p = t;
+ do {
+ if (PL_strcasecmp(t->s,s) == 0) {
+ t->refCnt--;
+ if (t->refCnt == 0) {
+ if (t == strTbl[h]) {
+ strTbl[h] = t->next;
+ }
+ else {
+ p->next = t->next;
+ }
+ deleteString((char *)t->s);
+ deleteStrItem(t);
+ return;
+ }
+ }
+ p = t;
+ t = t->next;
+ } while (t);
+ }
+}
+
+struct PreDefProp {
+ const char *name;
+ const char *alias;
+ const char** fields;
+ unsigned int flags;
+ };
+
+/* flags in PreDefProp */
+#define PD_BEGIN 0x1
+#define PD_INTERNAL 0x2
+
+static const char *adrFields[] = {
+ VCPostalBoxProp,
+ VCExtAddressProp,
+ VCStreetAddressProp,
+ VCCityProp,
+ VCRegionProp,
+ VCPostalCodeProp,
+ VCCountryNameProp,
+ 0
+};
+
+static const char *nameFields[] = {
+ VCFamilyNameProp,
+ VCGivenNameProp,
+ VCAdditionalNamesProp,
+ VCNamePrefixesProp,
+ VCNameSuffixesProp,
+ NULL
+ };
+
+static const char *orgFields[] = {
+ VCOrgNameProp,
+ VCOrgUnitProp,
+ VCOrgUnit2Prop,
+ VCOrgUnit3Prop,
+ VCOrgUnit4Prop,
+ NULL
+ };
+
+static const char *AAlarmFields[] = {
+ VCRunTimeProp,
+ VCSnoozeTimeProp,
+ VCRepeatCountProp,
+ VCAudioContentProp,
+ 0
+ };
+
+static const char *coolTalkFields[] = {
+ VCCooltalkAddress,
+ VCUseServer,
+ 0
+ };
+
+/* ExDate -- has unamed fields */
+/* RDate -- has unamed fields */
+
+static const char *DAlarmFields[] = {
+ VCRunTimeProp,
+ VCSnoozeTimeProp,
+ VCRepeatCountProp,
+ VCDisplayStringProp,
+ 0
+ };
+
+static const char *MAlarmFields[] = {
+ VCRunTimeProp,
+ VCSnoozeTimeProp,
+ VCRepeatCountProp,
+ VCEmailAddressProp,
+ VCNoteProp,
+ 0
+ };
+
+static const char *PAlarmFields[] = {
+ VCRunTimeProp,
+ VCSnoozeTimeProp,
+ VCRepeatCountProp,
+ VCProcedureNameProp,
+ 0
+ };
+
+static struct PreDefProp propNames[] = {
+ { VC7bitProp, 0, 0, 0 },
+ { VC8bitProp, 0, 0, 0 },
+ { VCAAlarmProp, 0, AAlarmFields, 0 },
+ { VCAdditionalNamesProp, 0, 0, 0 },
+ { VCAdrProp, 0, adrFields, 0 },
+ { VCAgentProp, 0, 0, 0 },
+ { VCAIFFProp, 0, 0, 0 },
+ { VCAOLProp, 0, 0, 0 },
+ { VCAppleLinkProp, 0, 0, 0 },
+ { VCAttachProp, 0, 0, 0 },
+ { VCAttendeeProp, 0, 0, 0 },
+ { VCATTMailProp, 0, 0, 0 },
+ { VCAudioContentProp, 0, 0, 0 },
+ { VCAVIProp, 0, 0, 0 },
+ { VCBase64Prop, 0, 0, 0 },
+ { VCBBSProp, 0, 0, 0 },
+ { VCBirthDateProp, 0, 0, 0 },
+ { VCBMPProp, 0, 0, 0 },
+ { VCBodyProp, 0, 0, 0 },
+ { VCBusinessRoleProp, 0, 0, 0 },
+ { VCCalProp, 0, 0, PD_BEGIN },
+ { VCCaptionProp, 0, 0, 0 },
+ { VCCardProp, 0, 0, PD_BEGIN },
+ { VCCarProp, 0, 0, 0 },
+ { VCCategoriesProp, 0, 0, 0 },
+ { VCCellularProp, 0, 0, 0 },
+ { VCCGMProp, 0, 0, 0 },
+ { VCCharSetProp, 0, 0, 0 },
+ { VCCIDProp, VCContentIDProp, 0, 0 },
+ { VCCISProp, 0, 0, 0 },
+ { VCCityProp, 0, 0, 0 },
+ { VCClassProp, 0, 0, 0 },
+ { VCCommentProp, 0, 0, 0 },
+ { VCCompletedProp, 0, 0, 0 },
+ { VCContentIDProp, 0, 0, 0 },
+ { VCCountryNameProp, 0, 0, 0 },
+ { VCDAlarmProp, 0, DAlarmFields, 0 },
+ { VCDataSizeProp, 0, 0, PD_INTERNAL },
+ { VCDayLightProp, 0, 0 ,0 },
+ { VCDCreatedProp, 0, 0, 0 },
+ { VCDeliveryLabelProp, 0, 0, 0 },
+ { VCDescriptionProp, 0, 0, 0 },
+ { VCDIBProp, 0, 0, 0 },
+ { VCDisplayStringProp, 0, 0, 0 },
+ { VCDomesticProp, 0, 0, 0 },
+ { VCDTendProp, 0, 0, 0 },
+ { VCDTstartProp, 0, 0, 0 },
+ { VCDueProp, 0, 0, 0 },
+ { VCEmailAddressProp, 0, 0, 0 },
+ { VCEncodingProp, 0, 0, 0 },
+ { VCEndProp, 0, 0, 0 },
+ { VCEventProp, 0, 0, PD_BEGIN },
+ { VCEWorldProp, 0, 0, 0 },
+ { VCExNumProp, 0, 0, 0 },
+ { VCExpDateProp, 0, 0, 0 },
+ { VCExpectProp, 0, 0, 0 },
+ { VCExtAddressProp, 0, 0, 0 },
+ { VCFamilyNameProp, 0, 0, 0 },
+ { VCFaxProp, 0, 0, 0 },
+ { VCFullNameProp, 0, 0, 0 },
+ { VCGeoLocationProp, 0, 0, 0 },
+ { VCGeoProp, 0, 0, 0 },
+ { VCGIFProp, 0, 0, 0 },
+ { VCGivenNameProp, 0, 0, 0 },
+ { VCGroupingProp, 0, 0, 0 },
+ { VCHomeProp, 0, 0, 0 },
+ { VCIBMMailProp, 0, 0, 0 },
+ { VCInlineProp, 0, 0, 0 },
+ { VCInternationalProp, 0, 0, 0 },
+ { VCInternetProp, 0, 0, 0 },
+ { VCISDNProp, 0, 0, 0 },
+ { VCJPEGProp, 0, 0, 0 },
+ { VCLanguageProp, 0, 0, 0 },
+ { VCLastModifiedProp, 0, 0, 0 },
+ { VCLastRevisedProp, 0, 0, 0 },
+ { VCLocationProp, 0, 0, 0 },
+ { VCLogoProp, 0, 0, 0 },
+ { VCMailerProp, 0, 0, 0 },
+ { VCMAlarmProp, 0, MAlarmFields, 0 },
+ { VCMCIMailProp, 0, 0, 0 },
+ { VCMessageProp, 0, 0, 0 },
+ { VCMETProp, 0, 0, 0 },
+ { VCModemProp, 0, 0, 0 },
+ { VCMPEG2Prop, 0, 0, 0 },
+ { VCMPEGProp, 0, 0, 0 },
+ { VCMSNProp, 0, 0, 0 },
+ { VCNamePrefixesProp, 0, 0, 0 },
+ { VCNameProp, 0, nameFields, 0 },
+ { VCNameSuffixesProp, 0, 0, 0 },
+ { VCNoteProp, 0, 0, 0 },
+ { VCOrgNameProp, 0, 0, 0 },
+ { VCOrgProp, 0, orgFields, 0 },
+ { VCOrgUnit2Prop, 0, 0, 0 },
+ { VCOrgUnit3Prop, 0, 0, 0 },
+ { VCOrgUnit4Prop, 0, 0, 0 },
+ { VCOrgUnitProp, 0, 0, 0 },
+ { VCPagerProp, 0, 0, 0 },
+ { VCPAlarmProp, 0, PAlarmFields, 0 },
+ { VCParcelProp, 0, 0, 0 },
+ { VCPartProp, 0, 0, 0 },
+ { VCPCMProp, 0, 0, 0 },
+ { VCPDFProp, 0, 0, 0 },
+ { VCPGPProp, 0, 0, 0 },
+ { VCPhotoProp, 0, 0, 0 },
+ { VCPICTProp, 0, 0, 0 },
+ { VCPMBProp, 0, 0, 0 },
+ { VCPostalBoxProp, 0, 0, 0 },
+ { VCPostalCodeProp, 0, 0, 0 },
+ { VCPostalProp, 0, 0, 0 },
+ { VCPowerShareProp, 0, 0, 0 },
+ { VCPreferredProp, 0, 0, 0 },
+ { VCPriorityProp, 0, 0, 0 },
+ { VCProcedureNameProp, 0, 0, 0 },
+ { VCProdIdProp, 0, 0, 0 },
+ { VCProdigyProp, 0, 0, 0 },
+ { VCPronunciationProp, 0, 0, 0 },
+ { VCPSProp, 0, 0, 0 },
+ { VCPublicKeyProp, 0, 0, 0 },
+ { VCQPProp, VCQuotedPrintableProp, 0, 0 },
+ { VCQuickTimeProp, 0, 0, 0 },
+ { VCQuotedPrintableProp, 0, 0, 0 },
+ { VCRDateProp, 0, 0, 0 },
+ { VCRegionProp, 0, 0, 0 },
+ { VCRelatedToProp, 0, 0, 0 },
+ { VCRepeatCountProp, 0, 0, 0 },
+ { VCResourcesProp, 0, 0, 0 },
+ { VCRNumProp, 0, 0, 0 },
+ { VCRoleProp, 0, 0, 0 },
+ { VCRRuleProp, 0, 0, 0 },
+ { VCRSVPProp, 0, 0, 0 },
+ { VCRunTimeProp, 0, 0, 0 },
+ { VCSequenceProp, 0, 0, 0 },
+ { VCSnoozeTimeProp, 0, 0, 0 },
+ { VCStartProp, 0, 0, 0 },
+ { VCStatusProp, 0, 0, 0 },
+ { VCStreetAddressProp, 0, 0, 0 },
+ { VCSubTypeProp, 0, 0, 0 },
+ { VCSummaryProp, 0, 0, 0 },
+ { VCTelephoneProp, 0, 0, 0 },
+ { VCTIFFProp, 0, 0, 0 },
+ { VCTimeZoneProp, 0, 0, 0 },
+ { VCTitleProp, 0, 0, 0 },
+ { VCTLXProp, 0, 0, 0 },
+ { VCTodoProp, 0, 0, PD_BEGIN },
+ { VCTranspProp, 0, 0, 0 },
+ { VCUniqueStringProp, 0, 0, 0 },
+ { VCURLProp, 0, 0, 0 },
+ { VCURLValueProp, 0, 0, 0 },
+ { VCValueProp, 0, 0, 0 },
+ { VCVersionProp, 0, 0, 0 },
+ { VCVideoProp, 0, 0, 0 },
+ { VCVoiceProp, 0, 0, 0 },
+ { VCWAVEProp, 0, 0, 0 },
+ { VCWMFProp, 0, 0, 0 },
+ { VCWorkProp, 0, 0, 0 },
+ { VCX400Prop, 0, 0, 0 },
+ { VCX509Prop, 0, 0, 0 },
+ { VCXRuleProp, 0, 0, 0 },
+ { VCCooltalk, 0, coolTalkFields, 0 },
+ { VCCooltalkAddress, 0, 0, 0 },
+ { VCUseServer, 0, 0, 0 },
+ { VCUseHTML, 0, 0, 0 },
+ { 0,0,0,0 }
+ };
+
+
+static struct PreDefProp* lookupPropInfo(const char* str)
+{
+ /* brute force for now, could use a hash table here. */
+ int i;
+
+ for (i = 0; propNames[i].name; i++)
+ if (PL_strcasecmp(str, propNames[i].name) == 0) {
+ return &propNames[i];
+ }
+
+ return 0;
+}
+
+
+const char* lookupProp_(const char* str)
+{
+ int i;
+
+ for (i = 0; propNames[i].name; i++)
+ if (PL_strcasecmp(str, propNames[i].name) == 0) {
+ const char* s;
+ s = propNames[i].alias?propNames[i].alias:propNames[i].name;
+ return lookupStr(s);
+ }
+ return lookupStr(str);
+}
+
+
+const char* lookupProp(const char* str)
+{
+ int i;
+
+ for (i = 0; propNames[i].name; i++)
+ if (PL_strcasecmp(str, propNames[i].name) == 0) {
+ const char *s;
+ fieldedProp = (char **)propNames[i].fields;
+ s = propNames[i].alias?propNames[i].alias:propNames[i].name;
+ return lookupStr(s);
+ }
+ fieldedProp = 0;
+ return lookupStr(str);
+}
+
+
+/*----------------------------------------------------------------------
+ APIs to Output text form.
+ ----------------------------------------------------------------------*/
+#define OFILE_REALLOC_SIZE 256
+
+static void appendcOFile_(OFile *fp, char c)
+{
+ if (fp->fail)
+ return;
+stuff:
+ if (fp->len+1 < fp->limit) {
+ fp->s[fp->len] = c;
+ fp->len++;
+ return;
+ }
+ else if (fp->alloc) {
+ fp->limit = fp->limit + OFILE_REALLOC_SIZE;
+ char* newBuf = (char *) PR_Realloc(fp->s, fp->limit);
+ if (newBuf) {
+ fp->s = newBuf;
+ goto stuff;
+ }
+ }
+ if (fp->alloc)
+ PR_FREEIF(fp->s);
+ fp->s = 0;
+ fp->fail = 1;
+}
+
+static void appendcOFile(OFile *fp, char c)
+{
+/* int i = 0; */
+ if (c == '\n') {
+ /* write out as <CR><LF> */
+ /* for (i = 0; i < LINEBREAK_LEN; i++)
+ appendcOFile_(fp,LINEBREAK [ i ]); */
+ appendcOFile_(fp,0xd);
+ appendcOFile_(fp,0xa);
+ }
+ else
+ appendcOFile_(fp,c);
+}
+
+static void appendsOFile(OFile *fp, const char *s)
+{
+ int i, slen;
+ slen = PL_strlen (s);
+ for (i=0; i<slen; i++) {
+ appendcOFile(fp,s[i]);
+ }
+}
+
+static void initMemOFile(OFile *fp, char *s, int len)
+{
+ fp->s = s;
+ fp->len = 0;
+ fp->limit = s?len:0;
+ fp->alloc = s?0:1;
+ fp->fail = 0;
+}
+
+
+static int writeBase64(OFile *fp, unsigned char *s, long len)
+{
+ long cur = 0;
+ int i, numQuads = 0;
+ unsigned long trip;
+ unsigned char b;
+ char quad[5];
+#define PR_MAXQUADS 16
+
+ quad[4] = 0;
+
+ while (cur < len) {
+ /* collect the triplet of bytes into 'trip' */
+ trip = 0;
+ for (i = 0; i < 3; i++) {
+ b = (cur < len) ? *(s + cur) : 0;
+ cur++;
+ trip = trip << 8 | b;
+ }
+ /* fill in 'quad' with the appropriate four characters */
+ for (i = 3; i >= 0; i--) {
+ b = (unsigned char)(trip & 0x3F);
+ trip = trip >> 6;
+ if ((3 - i) < (cur - len))
+ quad[i] = '='; /* pad char */
+ else if (b < 26) quad[i] = (char)b + 'A';
+ else if (b < 52) quad[i] = (char)(b - 26) + 'a';
+ else if (b < 62) quad[i] = (char)(b - 52) + '0';
+ else if (b == 62) quad[i] = '+';
+ else quad[i] = '/';
+ }
+ /* now output 'quad' with appropriate whitespace and line ending */
+ appendsOFile(fp, (numQuads == 0 ? " " : ""));
+ appendsOFile(fp, quad);
+ appendsOFile(fp, ((cur >= len)?"\n" :(numQuads==PR_MAXQUADS-1?"\n" : "")));
+ numQuads = (numQuads + 1) % PR_MAXQUADS;
+ }
+ appendcOFile(fp,'\n');
+
+ return 1;
+}
+
+static void writeQPString(OFile *fp, const char *s)
+{
+ const unsigned char *p = (const unsigned char *)s;
+ int current_column = 0;
+ static const char hexdigits[] = "0123456789ABCDEF";
+ bool white = false;
+ bool contWhite = false;
+ bool mb_p = false;
+
+ if (needsQuotedPrintable (s))
+ {
+ while (*p) {
+ if (*p == '\r' || *p == '\n')
+ {
+ /* Whitespace cannot be allowed to occur at the end of the line.
+ So we encode " \n" as " =\n\n", that is, the whitespace, a
+ soft line break, and then a hard line break.
+ */
+
+ if (white)
+ {
+ appendcOFile(fp,'=');
+ appendcOFile(fp,'\n');
+ appendcOFile(fp,'\t');
+ appendsOFile(fp,"=0D");
+ appendsOFile(fp,"=0A");
+ appendcOFile(fp,'=');
+ appendcOFile(fp,'\n');
+ appendcOFile(fp,'\t');
+ }
+ else
+ {
+ appendsOFile(fp,"=0D");
+ appendsOFile(fp,"=0A");
+ appendcOFile(fp,'=');
+ appendcOFile(fp,'\n');
+ appendcOFile(fp,'\t');
+ contWhite = false;
+ }
+
+ /* If its CRLF, swallow two chars instead of one. */
+ if (*p == '\r' && *(p+1) == '\n')
+ p++;
+ white = false;
+ current_column = 0;
+ }
+ else
+ {
+ if ((*p >= 33 && *p <= 60) || /* safe printing chars */
+ (*p >= 62 && *p <= 126) ||
+ (mb_p && (*p == 61 || *p == 127 || *p == 0x1B)))
+ {
+ appendcOFile(fp,*p);
+ current_column++;
+ white = false;
+ contWhite = false;
+ }
+ else if (*p == ' ' || *p == '\t') /* whitespace */
+ {
+ if (contWhite)
+ {
+ appendcOFile(fp,'=');
+ appendcOFile(fp,hexdigits[*p >> 4]);
+ appendcOFile(fp,hexdigits[*p & 0xF]);
+ current_column += 3;
+ contWhite = false;
+ }
+ else
+ {
+ appendcOFile(fp,*p);
+ current_column++;
+ }
+ white = true;
+ }
+ else /* print as =FF */
+ {
+ appendcOFile(fp,'=');
+ appendcOFile(fp,hexdigits[*p >> 4]);
+ appendcOFile(fp,hexdigits[*p & 0xF]);
+ current_column += 3;
+ white = false;
+ contWhite = false;
+ }
+
+ NS_ASSERTION(current_column <= 76, "1.10 <rhp@netscape.com> 06 Jan 2000 08:01"); /* Hard limit required by spec */
+
+ if (current_column >= 73 || ((*(p+1) == ' ') && (current_column + 3 >= 73))) /* soft line break: "=\r\n" */
+ {
+ appendcOFile(fp,'=');
+ appendcOFile(fp,'\n');
+ appendcOFile(fp,'\t');
+ current_column = 0;
+ if (white)
+ contWhite = true;
+ else
+ contWhite = false;
+ white = false;
+ }
+ }
+ p++;
+ } /* while */
+ } /* if */
+ else
+ {
+ while (*p) {
+ appendcOFile(fp,*p);
+ p++;
+ }
+ }
+}
+
+
+static void writeValue(OFile *fp, VObject *o, unsigned long size)
+{
+ if (o == 0) return;
+ switch (VALUE_TYPE(o)) {
+ case VCVT_USTRINGZ: {
+ char *s = fakeCString(USTRINGZ_VALUE_OF(o));
+ writeQPString(fp, s);
+ deleteString(s);
+ break;
+ }
+ case VCVT_STRINGZ: {
+ writeQPString(fp, STRINGZ_VALUE_OF(o));
+ break;
+ }
+ case VCVT_UINT: {
+ char buf[11];
+ sprintf(buf,"%u", INTEGER_VALUE_OF(o));
+ appendsOFile(fp, buf);
+ break;
+ }
+ case VCVT_ULONG: {
+ char buf[21];
+ sprintf(buf,"%lu", LONG_VALUE_OF(o));
+ appendsOFile(fp, buf);
+ break;
+ }
+ case VCVT_RAW: {
+ appendcOFile(fp,'\n');
+ writeBase64(fp,(unsigned char*)(ANY_VALUE_OF(o)),size);
+ break;
+ }
+ case VCVT_VOBJECT:
+ appendcOFile(fp,'\n');
+ writeVObject_(fp,VOBJECT_VALUE_OF(o));
+ break;
+ }
+}
+
+static void writeAttrValue(OFile *fp, VObject *o, int* length)
+{
+ int ilen = 0;
+ if (NAME_OF(o)) {
+ struct PreDefProp *pi;
+ pi = lookupPropInfo(NAME_OF(o));
+ if (pi && ((pi->flags & PD_INTERNAL) != 0)) return;
+ appendcOFile(fp,';');
+ if (*length != -1)
+ (*length)++;
+ appendsOFile(fp,NAME_OF(o));
+ if (*length != -1)
+ (*length) += PL_strlen (NAME_OF(o));
+ }
+ else {
+ appendcOFile(fp,';');
+ (*length)++;
+ }
+ if (VALUE_TYPE(o)) {
+ appendcOFile(fp,'=');
+ if (*length != -1) {
+ (*length)++;
+ for (ilen = 0; ilen < MAXMOZPROPNAMESIZE - (*length); ilen++)
+ appendcOFile(fp,' ');
+ }
+ writeValue(fp,o,0);
+ }
+}
+
+static void writeGroup(OFile *fp, VObject *o)
+{
+ nsAutoCString buf(NAME_OF(o));
+
+ while ((o=isAPropertyOf(o,VCGroupingProp)) != 0) {
+ buf.Insert(NS_LITERAL_CSTRING("."), 0);
+ buf.Insert(STRINGZ_VALUE_OF(o), 0);
+ }
+ appendsOFile(fp, buf.get());
+}
+
+static int inList(const char **list, const char *s)
+{
+ if (list == 0) return 0;
+ while (*list) {
+ if (PL_strcasecmp(*list,s) == 0) return 1;
+ list++;
+ }
+ return 0;
+}
+
+static void writeProp(OFile *fp, VObject *o)
+{
+ int length = -1;
+ //int ilen = 0;
+
+ if (NAME_OF(o)) {
+ struct PreDefProp *pi;
+ VObjectIterator t;
+ const char **fields_ = 0;
+ pi = lookupPropInfo(NAME_OF(o));
+ if (pi && ((pi->flags & PD_BEGIN) != 0)) {
+ writeVObject_(fp,o);
+ return;
+ }
+ if (isAPropertyOf(o,VCGroupingProp))
+ writeGroup(fp,o);
+ else
+ appendsOFile(fp,NAME_OF(o));
+ if (pi) fields_ = pi->fields;
+ initPropIterator(&t,o);
+ while (moreIteration(&t)) {
+ const char *s;
+ VObject *eachProp = nextVObject(&t);
+ s = NAME_OF(eachProp);
+ if (PL_strcasecmp(VCGroupingProp,s) && !inList(fields_,s))
+ writeAttrValue(fp,eachProp, &length);
+ }
+ if (fields_) {
+ int i = 0, n = 0;
+ const char** fields = fields_;
+ /* output prop as fields */
+ appendcOFile(fp,':');
+ while (*fields) {
+ VObject *tt = isAPropertyOf(o,*fields);
+ i++;
+ if (tt) n = i;
+ fields++;
+ }
+ fields = fields_;
+ for (i=0;i<n;i++) {
+ writeValue(fp,isAPropertyOf(o,*fields),0);
+ fields++;
+ if (i<(n-1)) appendcOFile(fp,';');
+ }
+ }
+ }
+
+ if (VALUE_TYPE(o)) {
+ unsigned long size = 0;
+ VObject *p = isAPropertyOf(o,VCDataSizeProp);
+ if (p) size = LONG_VALUE_OF(p);
+ appendcOFile(fp,':');
+ writeValue(fp,o,size);
+ }
+ appendcOFile(fp,'\n');
+}
+
+void writeVObject_(OFile *fp, VObject *o)
+{
+ //int ilen = 0;
+ if (NAME_OF(o)) {
+ struct PreDefProp *pi;
+ pi = lookupPropInfo(NAME_OF(o));
+
+ if (pi && ((pi->flags & PD_BEGIN) != 0)) {
+ VObjectIterator t;
+ const char *begin = NAME_OF(o);
+ appendsOFile(fp,"begin:");
+ appendsOFile(fp,begin);
+ appendcOFile(fp,'\n');
+ initPropIterator(&t,o);
+ while (moreIteration(&t)) {
+ VObject *eachProp = nextVObject(&t);
+ writeProp(fp, eachProp);
+ }
+ appendsOFile(fp,"end:");
+ appendsOFile(fp,begin);
+ appendsOFile(fp,"\n\n");
+ }
+ }
+}
+
+char* writeMemVObject(char *s, int *len, VObject *o)
+{
+ OFile ofp;
+ initMemOFile(&ofp,s,len?*len:0);
+ writeVObject_(&ofp,o);
+ if (len) *len = ofp.len;
+ appendcOFile(&ofp,0);
+ return ofp.s;
+}
+
+extern "C"
+char * writeMemoryVObjects(char *s, int *len, VObject *list, bool expandSpaces)
+{
+ OFile ofp;
+ initMemOFile(&ofp,s,len?*len:0);
+ while (list) {
+ writeVObject_(&ofp,list);
+ list = nextVObjectInList(list);
+ }
+ if (len) *len = ofp.len;
+ appendcOFile(&ofp,0);
+ return ofp.s;
+}
+
+/*----------------------------------------------------------------------
+ APIs to do fake Unicode stuff.
+ ----------------------------------------------------------------------*/
+vwchar_t* fakeUnicode(const char *ps, int *bytes)
+{
+ vwchar_t *r, *pw;
+ int len = strlen(ps)+1;
+
+ pw = r = (vwchar_t*)PR_CALLOC(sizeof(vwchar_t)*len);
+ if (bytes)
+ *bytes = len * sizeof(vwchar_t);
+
+ while (*ps) {
+ if (*ps == '\n')
+ *pw = (vwchar_t)0x2028;
+ else if (*ps == '\r')
+ *pw = (vwchar_t)0x2029;
+ else
+ *pw = (vwchar_t)(unsigned char)*ps;
+ ps++; pw++;
+ }
+ *pw = (vwchar_t)0;
+
+ return r;
+}
+
+int uStrLen(const vwchar_t *u)
+{
+ if (!u)
+ return 0;
+ int i = 0;
+ while (*u != (vwchar_t)0) { u++; i++; }
+ return i;
+}
+
+char* fakeCString(const vwchar_t *u)
+{
+ char *s, *t;
+ int len = uStrLen(u) + 1;
+ t = s = (char*)PR_CALLOC(len);
+ if (u) {
+ while (*u) {
+ if (*u == (vwchar_t)0x2028)
+ *t = '\n';
+ else if (*u == (vwchar_t)0x2029)
+ *t = '\r';
+ else
+ *t = (char)*u;
+ u++; t++;
+ }
+ }
+ *t = 0;
+ return s;
+}
+
+const char* lookupStr(const char *s)
+{
+ StrItem *t;
+ unsigned int h = hashStr(s);
+ if ((t = strTbl[h]) != 0) {
+ do {
+ if (PL_strcasecmp(t->s,s) == 0) {
+ t->refCnt++;
+ return t->s;
+ }
+ t = t->next;
+ } while (t);
+ }
+ s = dupStr(s,0);
+ strTbl[h] = newStrItem(s,strTbl[h]);
+ return s;
+}
diff --git a/mailnews/addrbook/src/nsVCardObj.h b/mailnews/addrbook/src/nsVCardObj.h
new file mode 100644
index 000000000..b7ca36997
--- /dev/null
+++ b/mailnews/addrbook/src/nsVCardObj.h
@@ -0,0 +1,396 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/***************************************************************************
+(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+
+For purposes of this license notice, the term Licensors shall mean,
+collectively, Apple Computer, Inc., AT&T Corp., International
+Business Machines Corporation and Siemens Rolm Communications Inc.
+The term Licensor shall mean any of the Licensors.
+
+Subject to acceptance of the following conditions, permission is hereby
+granted by Licensors without the need for written agreement and without
+license or royalty fees, to use, copy, modify and distribute this
+software for any purpose.
+
+The above copyright notice and the following four paragraphs must be
+reproduced in all copies of this software and any software including
+this software.
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE
+ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR
+MODIFICATIONS.
+
+IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT,
+INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT
+OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.
+
+The software is provided with RESTRICTED RIGHTS. Use, duplication, or
+disclosure by the government are subject to restrictions set forth in
+DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable.
+
+***************************************************************************/
+
+/*
+
+The vCard/vCalendar C interface is implemented in the set
+of files as follows:
+
+vcc.y, yacc source, and vcc.c, the yacc output you will use
+implements the core parser
+
+vobject.c implements an API that insulates the caller from
+the parser and changes in the vCard/vCalendar BNF
+
+port.h defines compilation environment dependent stuff
+
+vcc.h and vobject.h are header files for their .c counterparts
+
+vcaltmp.h and vcaltmp.c implement vCalendar "macro" functions
+which you may find useful.
+
+test.c is a standalone test driver that exercises some of
+the features of the APIs provided. Invoke test.exe on a
+VCARD/VCALENDAR input text file and you will see the pretty
+print output of the internal representation (this pretty print
+output should give you a good idea of how the internal
+representation looks like -- there is one such output in the
+following too). Also, a file with the .out suffix is generated
+to show that the internal representation can be written back
+in the original text format.
+
+For more information on this API see the readme.txt file
+which accompanied this distribution.
+
+ Also visit:
+
+ http://www.versit.com
+ http://www.ralden.com
+
+*/
+
+
+#ifndef __VOBJECT_H__
+#define __VOBJECT_H__ 1
+
+/*
+Unfortunately, on the Mac (and possibly other platforms) with our current, out-dated
+libraries (Plauger), |wchar_t| is defined incorrectly, which breaks vcards.
+
+We can't fix Plauger because it doesn't come with source. Later, when we
+upgrade to MSL, we can make this evil hack go away. In the mean time,
+vcards are not allowed to use the (incorrectly defined) |wchar_t| type. Instead,
+they will use an appropriately defined local type |vwchar_t|.
+*/
+
+typedef wchar_t vwchar_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define VC7bitProp "7bit"
+#define VC8bitProp "8bit"
+#define VCAAlarmProp "aalarm"
+#define VCAdditionalNamesProp "addn"
+#define VCAdrProp "adr"
+#define VCAgentProp "agent"
+#define VCAIFFProp "aiff"
+#define VCAOLProp "aol"
+#define VCAppleLinkProp "applelink"
+#define VCAttachProp "attach"
+#define VCAttendeeProp "attendee"
+#define VCATTMailProp "attmail"
+#define VCAudioContentProp "audiocontent"
+#define VCAVIProp "avi"
+#define VCBase64Prop "base64"
+#define VCBBSProp "bbs"
+#define VCBirthDateProp "bday"
+#define VCBMPProp "bmp"
+#define VCBodyProp "body"
+#define VCBusinessRoleProp "role"
+#define VCCalProp "vcalendar"
+#define VCCaptionProp "cap"
+#define VCCardProp "vcard"
+#define VCCarProp "car"
+#define VCCategoriesProp "categories"
+#define VCCellularProp "cell"
+#define VCCGMProp "cgm"
+#define VCCharSetProp "cs"
+#define VCCIDProp "cid"
+#define VCCISProp "cis"
+#define VCCityProp "l"
+#define VCClassProp "class"
+#define VCCommentProp "note"
+#define VCCompletedProp "completed"
+#define VCContentIDProp "content-id"
+#define VCCountryNameProp "c"
+#define VCDAlarmProp "dalarm"
+#define VCDataSizeProp "datasize"
+#define VCDayLightProp "daylight"
+#define VCDCreatedProp "dcreated"
+#define VCDeliveryLabelProp "label"
+#define VCDescriptionProp "description"
+#define VCDIBProp "dib"
+#define VCDisplayStringProp "displaystring"
+#define VCDomesticProp "dom"
+#define VCDTendProp "dtend"
+#define VCDTstartProp "dtstart"
+#define VCDueProp "due"
+#define VCEmailAddressProp "email"
+#define VCEncodingProp "encoding"
+#define VCEndProp "end"
+#define VCEventProp "vevent"
+#define VCEWorldProp "eworld"
+#define VCExNumProp "exnum"
+#define VCExpDateProp "exdate"
+#define VCExpectProp "expect"
+#define VCExtAddressProp "ext add"
+#define VCFamilyNameProp "f"
+#define VCFaxProp "fax"
+#define VCFullNameProp "fn"
+#define VCGeoProp "geo"
+#define VCGeoLocationProp "geo"
+#define VCGIFProp "gif"
+#define VCGivenNameProp "g"
+#define VCGroupingProp "grouping"
+#define VCHomeProp "home"
+#define VCIBMMailProp "ibmmail"
+#define VCInlineProp "inline"
+#define VCInternationalProp "intl"
+#define VCInternetProp "internet"
+#define VCISDNProp "isdn"
+#define VCJPEGProp "jpeg"
+#define VCLanguageProp "lang"
+#define VCLastModifiedProp "last-modified"
+#define VCLastRevisedProp "rev"
+#define VCLocationProp "location"
+#define VCLogoProp "logo"
+#define VCMailerProp "mailer"
+#define VCMAlarmProp "malarm"
+#define VCMCIMailProp "mcimail"
+#define VCMessageProp "msg"
+#define VCMETProp "met"
+#define VCModemProp "modem"
+#define VCMPEG2Prop "mpeg2"
+#define VCMPEGProp "mpeg"
+#define VCMSNProp "msn"
+#define VCNamePrefixesProp "npre"
+#define VCNameProp "n"
+#define VCNameSuffixesProp "nsuf"
+#define VCNoteProp "note"
+#define VCOrgNameProp "orgname"
+#define VCOrgProp "org"
+#define VCOrgUnit2Prop "oun2"
+#define VCOrgUnit3Prop "oun3"
+#define VCOrgUnit4Prop "oun4"
+#define VCOrgUnitProp "oun"
+#define VCPagerProp "pager"
+#define VCPAlarmProp "palarm"
+#define VCParcelProp "parcel"
+#define VCPartProp "part"
+#define VCPCMProp "pcm"
+#define VCPDFProp "pdf"
+#define VCPGPProp "pgp"
+#define VCPhotoProp "photo"
+#define VCPICTProp "pict"
+#define VCPMBProp "pmb"
+#define VCPostalBoxProp "box"
+#define VCPostalCodeProp "pc"
+#define VCPostalProp "postal"
+#define VCPowerShareProp "powershare"
+#define VCPreferredProp "pref"
+#define VCPriorityProp "priority"
+#define VCProcedureNameProp "procedurename"
+#define VCProdIdProp "prodid"
+#define VCProdigyProp "prodigy"
+#define VCPronunciationProp "sound"
+#define VCPSProp "ps"
+#define VCPublicKeyProp "key"
+#define VCQPProp "qp"
+#define VCQuickTimeProp "qtime"
+#define VCQuotedPrintableProp "quoted-printable"
+#define VCRDateProp "rdate"
+#define VCRegionProp "r"
+#define VCRelatedToProp "related-to"
+#define VCRepeatCountProp "repeatcount"
+#define VCResourcesProp "resources"
+#define VCRNumProp "rnum"
+#define VCRoleProp "role"
+#define VCRRuleProp "rrule"
+#define VCRSVPProp "rsvp"
+#define VCRunTimeProp "runtime"
+#define VCSequenceProp "sequence"
+#define VCSnoozeTimeProp "snoozetime"
+#define VCStartProp "start"
+#define VCStatusProp "status"
+#define VCStreetAddressProp "street"
+#define VCSubTypeProp "subtype"
+#define VCSummaryProp "summary"
+#define VCTelephoneProp "tel"
+#define VCTIFFProp "tiff"
+#define VCTimeZoneProp "tz"
+#define VCTitleProp "title"
+#define VCTLXProp "tlx"
+#define VCTodoProp "vtodo"
+#define VCTranspProp "transp"
+#define VCUniqueStringProp "uid"
+#define VCURLProp "url"
+#define VCURLValueProp "urlval"
+#define VCValueProp "value"
+#define VCVersionProp "version"
+#define VCVideoProp "video"
+#define VCVoiceProp "voice"
+#define VCWAVEProp "wave"
+#define VCWMFProp "wmf"
+#define VCWorkProp "work"
+#define VCX400Prop "x400"
+#define VCX509Prop "x509"
+#define VCXRuleProp "xrule"
+#define VCCooltalk "x-mozilla-cpt"
+#define VCCooltalkAddress "x-moxilla-cpadr"
+#define VCUseServer "x-mozilla-cpsrv"
+#define VCUseHTML "x-mozilla-html"
+
+/* return type of vObjectValueType: */
+#define VCVT_NOVALUE 0
+ /* if the VObject has no value associated with it. */
+#define VCVT_STRINGZ 1
+ /* if the VObject has value set by setVObjectStringZValue. */
+#define VCVT_USTRINGZ 2
+ /* if the VObject has value set by setVObjectUStringZValue. */
+#define VCVT_UINT 3
+ /* if the VObject has value set by setVObjectIntegerValue. */
+#define VCVT_ULONG 4
+ /* if the VObject has value set by setVObjectLongValue. */
+#define VCVT_RAW 5
+ /* if the VObject has value set by setVObjectAnyValue. */
+#define VCVT_VOBJECT 6
+ /* if the VObject has value set by setVObjectVObjectValue. */
+
+#define NAME_OF(o) o->id
+#define VALUE_TYPE(o) o->valType
+#define STRINGZ_VALUE_OF(o) o->val.strs
+#define USTRINGZ_VALUE_OF(o) o->val.ustrs
+#define INTEGER_VALUE_OF(o) o->val.i
+#define LONG_VALUE_OF(o) o->val.l
+#define ANY_VALUE_OF(o) o->val.any
+#define VOBJECT_VALUE_OF(o) o->val.vobj
+
+typedef struct VObject VObject;
+
+typedef union ValueItem {
+ const char *strs;
+ const vwchar_t *ustrs;
+ unsigned int i;
+ unsigned long l;
+ void *any;
+ VObject *vobj;
+ } ValueItem;
+
+struct VObject {
+ VObject *next;
+ const char *id;
+ VObject *prop;
+ unsigned short valType;
+ ValueItem val;
+ };
+
+typedef struct StrItem StrItem;
+
+struct StrItem {
+ StrItem *next;
+ const char *s;
+ unsigned int refCnt;
+ };
+
+typedef struct OFile {
+ char *s;
+ int len;
+ int limit;
+ int alloc:1;
+ int fail:1;
+ } OFile;
+
+typedef struct VObjectIterator {
+ VObject* start;
+ VObject* next;
+ } VObjectIterator;
+
+VObject* newVObject(const char *id);
+void deleteVObject(VObject *p);
+char* dupStr(const char *s, unsigned int size);
+extern "C" void deleteString(char *p);
+void unUseStr(const char *s);
+
+void setVObjectName(VObject *o, const char* id);
+void setVObjectStringZValue(VObject *o, const char *s);
+void setVObjectStringZValue_(VObject *o, const char *s);
+void setVObjectUStringZValue(VObject *o, const vwchar_t *s);
+void setVObjectUStringZValue_(VObject *o, const vwchar_t *s);
+void setVObjectIntegerValue(VObject *o, unsigned int i);
+void setVObjectLongValue(VObject *o, unsigned long l);
+void setVObjectAnyValue(VObject *o, void *t);
+VObject* setValueWithSize(VObject *prop, void *val, unsigned int size);
+VObject* setValueWithSize_(VObject *prop, void *val, unsigned int size);
+
+const char* vObjectName(VObject *o);
+const char* vObjectStringZValue(VObject *o);
+const vwchar_t* vObjectUStringZValue(VObject *o);
+unsigned int vObjectIntegerValue(VObject *o);
+unsigned long vObjectLongValue(VObject *o);
+void* vObjectAnyValue(VObject *o);
+VObject* vObjectVObjectValue(VObject *o);
+void setVObjectVObjectValue(VObject *o, VObject *p);
+
+VObject* addVObjectProp(VObject *o, VObject *p);
+VObject* addProp(VObject *o, const char *id);
+VObject* addProp_(VObject *o, const char *id);
+VObject* addPropValue(VObject *o, const char *p, const char *v);
+VObject* addPropSizedValue_(VObject *o, const char *p, const char *v, unsigned int size);
+VObject* addPropSizedValue(VObject *o, const char *p, const char *v, unsigned int size);
+VObject* addGroup(VObject *o, const char *g);
+void addList(VObject **o, VObject *p);
+
+VObject* isAPropertyOf(VObject *o, const char *id);
+
+VObject* nextVObjectInList(VObject *o);
+void initPropIterator(VObjectIterator *i, VObject *o);
+int moreIteration(VObjectIterator *i);
+VObject* nextVObject(VObjectIterator *i);
+
+void writeVObject_(OFile *fp, VObject *o);
+char* writeMemVObject(char *s, int *len, VObject *o);
+extern "C" char* writeMemoryVObjects(char *s, int *len, VObject *list, bool expandSpaces);
+
+const char* lookupStr(const char *s);
+
+void cleanVObject(VObject *o);
+void cleanVObjects(VObject *list);
+
+const char* lookupProp(const char* str);
+const char* lookupProp_(const char* str);
+
+vwchar_t* fakeUnicode(const char *ps, int *bytes);
+int uStrLen(const vwchar_t *u);
+char* fakeCString(const vwchar_t *u);
+
+#define MAXPROPNAMESIZE 256
+#define MAXMOZPROPNAMESIZE 16
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __VOBJECT_H__ */
+
+
diff --git a/mailnews/addrbook/src/nsWabAddressBook.cpp b/mailnews/addrbook/src/nsWabAddressBook.cpp
new file mode 100644
index 000000000..44ccdea2c
--- /dev/null
+++ b/mailnews/addrbook/src/nsWabAddressBook.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <tchar.h>
+#include "nsWabAddressBook.h"
+#include "mozilla/Logging.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gWabAddressBookLog
+ = PR_NewLogModule("nsWabAddressBookLog");
+#endif
+
+#define PRINTF(args) MOZ_LOG(gWabAddressBookLog, mozilla::LogLevel::Debug, args)
+
+using namespace mozilla;
+
+HMODULE nsWabAddressBook::mLibrary = NULL ;
+int32_t nsWabAddressBook::mLibUsage = 0 ;
+LPWABOPEN nsWabAddressBook::mWABOpen = NULL ;
+LPWABOBJECT nsWabAddressBook::mRootSession = NULL ;
+LPADRBOOK nsWabAddressBook::mRootBook = NULL ;
+
+BOOL nsWabAddressBook::LoadWabLibrary(void)
+{
+ if (mLibrary) { ++ mLibUsage ; return TRUE ; }
+ // We try to fetch the location of the WAB DLL from the registry
+ TCHAR wabDLLPath [MAX_PATH] ;
+ DWORD keyType = 0 ;
+ ULONG byteCount = sizeof(wabDLLPath) ;
+ HKEY keyHandle = NULL ;
+ wabDLLPath [MAX_PATH - 1] = 0 ;
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, WAB_DLL_PATH_KEY, 0, KEY_READ, &keyHandle) == ERROR_SUCCESS) {
+ RegQueryValueEx(keyHandle, "", NULL, &keyType, (LPBYTE) wabDLLPath, &byteCount) ;
+ if (keyType == REG_EXPAND_SZ) {
+ // Expand the environment variables
+ DWORD bufferSize = ExpandEnvironmentStrings(wabDLLPath, NULL, 0);
+ if (bufferSize && bufferSize < MAX_PATH) {
+ TCHAR tmp[MAX_PATH];
+ ExpandEnvironmentStrings(wabDLLPath, tmp, bufferSize);
+ _tcscpy(wabDLLPath, tmp);
+ }
+ else {
+ return FALSE;
+ }
+ }
+ }
+ else {
+ if (GetSystemDirectory(wabDLLPath, MAX_PATH)) {
+ _tcsncat(wabDLLPath, WAB_DLL_NAME,
+ std::min(_tcslen(WAB_DLL_NAME), MAX_PATH - _tcslen(wabDLLPath) - 1));
+ }
+ else {
+ return FALSE;
+ }
+ }
+ if (keyHandle) { RegCloseKey(keyHandle) ; }
+ mLibrary = LoadLibrary( (lstrlen(wabDLLPath)) ? wabDLLPath : WAB_DLL_NAME );
+ if (!mLibrary) { return FALSE ; }
+ ++ mLibUsage ;
+ mWABOpen = reinterpret_cast<LPWABOPEN>(GetProcAddress(mLibrary, "WABOpen")) ;
+ if (!mWABOpen) { return FALSE ; }
+ HRESULT retCode = mWABOpen(&mRootBook, &mRootSession, NULL, 0) ;
+
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot initialize WAB %08x.\n", retCode)) ; return FALSE ;
+ }
+ return TRUE ;
+}
+
+void nsWabAddressBook::FreeWabLibrary(void)
+{
+ if (mLibrary) {
+ if (-- mLibUsage == 0) {
+ if (mRootBook) { mRootBook->Release() ; }
+ if (mRootSession) { mRootSession->Release() ; }
+ FreeLibrary(mLibrary) ;
+ mLibrary = NULL ;
+ }
+ }
+}
+
+nsWabAddressBook::nsWabAddressBook(void)
+: nsAbWinHelper()
+{
+ BOOL result = Initialize() ;
+
+ NS_ASSERTION(result == TRUE, "Couldn't initialize Wab Helper") ;
+ MOZ_COUNT_CTOR(nsWabAddressBook) ;
+}
+
+nsWabAddressBook::~nsWabAddressBook(void)
+{
+ MutexAutoLock guard(*mMutex) ;
+ FreeWabLibrary() ;
+ MOZ_COUNT_DTOR(nsWabAddressBook) ;
+}
+
+BOOL nsWabAddressBook::Initialize(void)
+{
+ if (mAddressBook) { return TRUE ; }
+ MutexAutoLock guard(*mMutex) ;
+
+ if (!LoadWabLibrary()) {
+ PRINTF(("Cannot load library.\n")) ;
+ return FALSE ;
+ }
+ mAddressBook = mRootBook ;
+ return TRUE ;
+}
+
+void nsWabAddressBook::AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer)
+{
+ mRootSession->AllocateBuffer(aByteCount, aBuffer) ;
+}
+
+void nsWabAddressBook::FreeBuffer(LPVOID aBuffer)
+{
+ mRootSession->FreeBuffer(aBuffer) ;
+}
+
+
+
+
+
+
diff --git a/mailnews/addrbook/src/nsWabAddressBook.h b/mailnews/addrbook/src/nsWabAddressBook.h
new file mode 100644
index 000000000..35b76b213
--- /dev/null
+++ b/mailnews/addrbook/src/nsWabAddressBook.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsWabAddressBook_h___
+#define nsWabAddressBook_h___
+
+#include "mozilla/Attributes.h"
+#include "nsAbWinHelper.h"
+#include <wab.h>
+
+class nsWabAddressBook : public nsAbWinHelper
+{
+public :
+ nsWabAddressBook(void) ;
+ virtual ~nsWabAddressBook(void) ;
+
+protected :
+ // Session and address book that will be shared by all instances
+ // (see nsMapiAddressBook.h for details)
+ static LPWABOBJECT mRootSession ;
+ static LPADRBOOK mRootBook ;
+ // Class members to handle library loading/entry points
+ static int32_t mLibUsage ;
+ static HMODULE mLibrary ;
+ static LPWABOPEN mWABOpen ;
+
+ // Load the WAB environment
+ BOOL Initialize(void) ;
+ // Allocation of a buffer for transmission to interfaces
+ virtual void AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) override;
+ // Destruction of a buffer provided by the interfaces
+ virtual void FreeBuffer(LPVOID aBuffer) override;
+ // Manage the library
+ static BOOL LoadWabLibrary(void) ;
+ static void FreeWabLibrary(void) ;
+
+private :
+} ;
+
+// Additional definitions for WAB stuff. These properties are
+// only defined with regards to the default character sizes,
+// and not in two _A and _W versions...
+#define PR_BUSINESS_ADDRESS_CITY_A PR_LOCALITY_A
+#define PR_BUSINESS_ADDRESS_COUNTRY_A PR_COUNTRY_A
+#define PR_BUSINESS_ADDRESS_POSTAL_CODE_A PR_POSTAL_CODE_A
+#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_A PR_STATE_OR_PROVINCE_A
+#define PR_BUSINESS_ADDRESS_STREET_A PR_STREET_ADDRESS_A
+
+#define PR_BUSINESS_ADDRESS_CITY_W PR_LOCALITY_W
+#define PR_BUSINESS_ADDRESS_COUNTRY_W PR_COUNTRY_W
+#define PR_BUSINESS_ADDRESS_POSTAL_CODE_W PR_POSTAL_CODE_W
+#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W PR_STATE_OR_PROVINCE_W
+#define PR_BUSINESS_ADDRESS_STREET_W PR_STREET_ADDRESS_W
+
+#endif // nsWABAddressBook_h___
+
diff --git a/mailnews/base/content/charsetList.css b/mailnews/base/content/charsetList.css
new file mode 100644
index 000000000..cfc9af542
--- /dev/null
+++ b/mailnews/base/content/charsetList.css
@@ -0,0 +1,5 @@
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+menulist[type="charset"] {
+ -moz-binding: url("chrome://messenger/content/charsetList.xml#charsetpicker");
+}
diff --git a/mailnews/base/content/charsetList.xml b/mailnews/base/content/charsetList.xml
new file mode 100644
index 000000000..bff43059e
--- /dev/null
+++ b/mailnews/base/content/charsetList.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-->
+
+<bindings id="charsetListBinding"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="charsetpicker"
+ extends="chrome://global/content/bindings/menulist.xml#menulist">
+ <implementation>
+ <constructor><![CDATA[
+ let charsetValues = "";
+
+ if (this.getAttribute("subset") == "sending")
+ charsetValues = ["UTF-8", "EUC-KR", "gbk", "gb18030", "ISO-2022-JP",
+ "ISO-8859-1", "ISO-8859-7", "windows-1252"];
+
+ else if (!this.getAttribute("subset")
+ || this.getAttribute("subset") == "viewing")
+ charsetValues = ["UTF-8", "Big5", "EUC-KR", "gbk", "ISO-2022-JP",
+ "ISO-8859-1", "ISO-8859-2", "ISO-8859-7",
+ "windows-874", "windows-1250", "windows-1251",
+ "windows-1252", "windows-1255", "windows-1256",
+ "windows-1257", "windows-1258"];
+
+ Components.utils.import("resource://gre/modules/Services.jsm");
+
+ let charsetBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/charsetTitles.properties");
+ let aMenuList = this;
+ let menuLabels = [];
+
+ charsetValues.forEach(function(item) {
+ let strCharset = charsetBundle.GetStringFromName(
+ item.toLowerCase() + ".title");
+
+ menuLabels.push({label: strCharset, value: item});
+ });
+
+ menuLabels.sort(function(a, b) {
+ if (a.value == "UTF-8" || a.label < b.label)
+ return -1;
+ else if (b.value == "UTF-8" || a.label > b.label)
+ return 1;
+ return 0;
+ });
+
+ menuLabels.forEach(function(item) {
+ aMenuList.appendItem(item.label, item.value);
+ });
+
+ // Selecting appropiate menu item corresponding to preference stored
+ // value.
+ if (this.hasAttribute("preference")) {
+ const Ci = Components.interfaces;
+
+ let preference = Services.prefs.getComplexValue(
+ this.getAttribute("preference"), Ci.nsIPrefLocalizedString);
+ this.value = preference.data;
+ }
+ ]]></constructor>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/mailnews/base/content/dateFormat.js b/mailnews/base/content/dateFormat.js
new file mode 100644
index 000000000..71c628697
--- /dev/null
+++ b/mailnews/base/content/dateFormat.js
@@ -0,0 +1,225 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gSearchDateFormat = 0;
+var gSearchDateSeparator;
+var gSearchDateLeadingZeros;
+
+/**
+ * Get the short date format option of the current locale.
+ * This supports the common case which the date separator is
+ * either '/', '-', '.' and using Christian year.
+ */
+function initLocaleShortDateFormat()
+{
+ // default to mm/dd/yyyy
+ gSearchDateFormat = 3;
+ gSearchDateSeparator = "/";
+ gSearchDateLeadingZeros = true;
+
+ try {
+ var dateFormatService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Components.interfaces.nsIScriptableDateFormat);
+ var dateString = dateFormatService.FormatDate("",
+ dateFormatService.dateFormatShort,
+ 1999,
+ 12,
+ 1);
+
+ // find out the separator
+ var possibleSeparators = "/-.";
+ var arrayOfStrings;
+ for ( var i = 0; i < possibleSeparators.length; ++i )
+ {
+ arrayOfStrings = dateString.split( possibleSeparators[i] );
+ if ( arrayOfStrings.length == 3 )
+ {
+ gSearchDateSeparator = possibleSeparators[i];
+ break;
+ }
+ }
+
+ // check the format option
+ if ( arrayOfStrings.length != 3 ) // no successfull split
+ {
+ dump("getLocaleShortDateFormat: could not analyze the date format, defaulting to mm/dd/yyyy\n");
+ }
+ else
+ {
+ // the date will contain a zero if that system settings include leading zeros
+ gSearchDateLeadingZeros = dateString.includes("0");
+
+ // match 1 as number, since that will match both "1" and "01"
+ if ( arrayOfStrings[0] == 1 )
+ {
+ // 01.12.1999 or 01.1999.12
+ gSearchDateFormat = arrayOfStrings[1] == "12" ? 5 : 6;
+ }
+ else if ( arrayOfStrings[1] == 1 )
+ {
+ // 12.01.1999 or 1999.01.12
+ gSearchDateFormat = arrayOfStrings[0] == "12" ? 3 : 2;
+ }
+ else // implies arrayOfStrings[2] == 1
+ {
+ // 12.1999.01 or 1999.12.01
+ gSearchDateFormat = arrayOfStrings[0] == "12" ? 4 : 1;
+ }
+ }
+ }
+ catch (e)
+ {
+ dump("getLocaleShortDateFormat: caught an exception!\n");
+ }
+}
+
+function initializeSearchDateFormat()
+{
+ if (gSearchDateFormat)
+ return;
+
+ // get a search date format option and a seprator
+ try {
+ gSearchDateFormat =
+ Services.prefs.getComplexValue("mailnews.search_date_format",
+ Components.interfaces.nsIPrefLocalizedString);
+ gSearchDateFormat = parseInt(gSearchDateFormat);
+
+ // if the option is 0 then try to use the format of the current locale
+ if (gSearchDateFormat == 0)
+ initLocaleShortDateFormat();
+ else
+ {
+ // initialize the search date format based on preferences
+ if ( gSearchDateFormat < 1 || gSearchDateFormat > 6 )
+ gSearchDateFormat = 3;
+
+ gSearchDateSeparator =
+ Services.prefs.getComplexValue("mailnews.search_date_separator",
+ Components.interfaces.nsIPrefLocalizedString);
+
+ gSearchDateLeadingZeros =
+ (Services.prefs.getComplexValue(
+ "mailnews.search_date_leading_zeros",
+ Components.interfaces.nsIPrefLocalizedString).data == "true");
+ }
+ }
+ catch (e)
+ {
+ Components.utils.reportError("initializeSearchDateFormat: caught an exception: " + e);
+ // set to mm/dd/yyyy in case of error
+ gSearchDateFormat = 3;
+ gSearchDateSeparator = "/";
+ gSearchDateLeadingZeros = true;
+ }
+}
+
+function convertPRTimeToString(tm)
+{
+ var time = new Date();
+ // PRTime is in microseconds, JavaScript time is in milliseconds
+ // so divide by 1000 when converting
+ time.setTime(tm / 1000);
+
+ return convertDateToString(time);
+}
+
+function convertDateToString(time)
+{
+ initializeSearchDateFormat();
+
+ var year = time.getFullYear();
+ var month = time.getMonth() + 1; // since js month is 0-11
+ if ( gSearchDateLeadingZeros && month < 10 )
+ month = "0" + month;
+ var date = time.getDate();
+ if ( gSearchDateLeadingZeros && date < 10 )
+ date = "0" + date;
+
+ var dateStr;
+ var sep = gSearchDateSeparator;
+
+ switch (gSearchDateFormat)
+ {
+ case 1:
+ dateStr = year + sep + month + sep + date;
+ break;
+ case 2:
+ dateStr = year + sep + date + sep + month;
+ break;
+ case 3:
+ dateStr = month + sep + date + sep + year;
+ break;
+ case 4:
+ dateStr = month + sep + year + sep + date;
+ break;
+ case 5:
+ dateStr = date + sep + month + sep + year;
+ break;
+ case 6:
+ dateStr = date + sep + year + sep + month;
+ break;
+ default:
+ dump("valid search date format option is 1-6\n");
+ }
+
+ return dateStr;
+}
+
+function convertStringToPRTime(str)
+{
+ initializeSearchDateFormat();
+
+ var arrayOfStrings = str.split(gSearchDateSeparator);
+ var year, month, date;
+
+ // set year, month, date based on the format option
+ switch (gSearchDateFormat)
+ {
+ case 1:
+ year = arrayOfStrings[0];
+ month = arrayOfStrings[1];
+ date = arrayOfStrings[2];
+ break;
+ case 2:
+ year = arrayOfStrings[0];
+ month = arrayOfStrings[2];
+ date = arrayOfStrings[1];
+ break;
+ case 3:
+ year = arrayOfStrings[2];
+ month = arrayOfStrings[0];
+ date = arrayOfStrings[1];
+ break;
+ case 4:
+ year = arrayOfStrings[1];
+ month = arrayOfStrings[0];
+ date = arrayOfStrings[2];
+ break;
+ case 5:
+ year = arrayOfStrings[2];
+ month = arrayOfStrings[1];
+ date = arrayOfStrings[0];
+ break;
+ case 6:
+ year = arrayOfStrings[1];
+ month = arrayOfStrings[2];
+ date = arrayOfStrings[0];
+ break;
+ default:
+ dump("valid search date format option is 1-6\n");
+ }
+
+ month -= 1; // since js month is 0-11
+
+ var time = new Date(year, month, date);
+
+ // JavaScript time is in milliseconds, PRTime is in microseconds
+ // so multiply by 1000 when converting
+ return (time.getTime() * 1000);
+}
+
diff --git a/mailnews/base/content/folderProps.js b/mailnews/base/content/folderProps.js
new file mode 100644
index 000000000..98e1312e8
--- /dev/null
+++ b/mailnews/base/content/folderProps.js
@@ -0,0 +1,387 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/gloda/gloda.js");
+
+var gMsgFolder;
+var gLockedPref = null;
+
+// The folderPropsSink is the class that gets notified of an imap folder's properties
+
+var gFolderPropsSink = {
+ setFolderType: function(folderTypeString)
+ {
+ var typeLabel = document.getElementById("folderType.text");
+ if (typeLabel)
+ {
+ typeLabel.setAttribute("value",folderTypeString);
+ }
+ // get the element for the folder type label and set value on it.
+ },
+
+ setFolderTypeDescription: function(folderDescription)
+ {
+ var folderTypeLabel = document.getElementById("folderDescription.text");
+ if (folderTypeLabel)
+ folderTypeLabel.setAttribute("value", folderDescription);
+ },
+
+ setFolderPermissions: function(folderPermissions)
+ {
+ var permissionsLabel = document.getElementById("folderPermissions.text");
+ var descTextNode = document.createTextNode(folderPermissions);
+ permissionsLabel.appendChild(descTextNode);
+ },
+
+ serverDoesntSupportACL : function()
+ {
+ var typeLabel = document.getElementById("folderTypeLabel");
+ if (typeLabel)
+ typeLabel.setAttribute("hidden", "true");
+ var permissionsLabel = document.getElementById("permissionsDescLabel");
+ if (permissionsLabel)
+ permissionsLabel.setAttribute("hidden", "true");
+
+ },
+
+ setQuotaStatus : function(folderQuotaStatus)
+ {
+ var quotaStatusLabel = document.getElementById("folderQuotaStatus");
+ if(quotaStatusLabel)
+ quotaStatusLabel.setAttribute("value", folderQuotaStatus);
+ },
+
+ showQuotaData : function(showData)
+ {
+ var quotaStatusLabel = document.getElementById("folderQuotaStatus");
+ var folderQuotaData = document.getElementById("folderQuotaData");
+
+ if(quotaStatusLabel && folderQuotaData)
+ {
+ quotaStatusLabel.hidden = showData;
+ folderQuotaData.hidden = ! showData;
+ }
+ },
+
+ setQuotaData : function(root, usedKB, maxKB)
+ {
+ var quotaRoot = document.getElementById("quotaRoot");
+ if (quotaRoot)
+ quotaRoot.setAttribute("value", '"' + root + '"');
+
+ var percentage = (maxKB != 0) ? Math.round(usedKB / maxKB * 100) : 0;
+
+ var quotaPercentageBar = document.getElementById("quotaPercentageBar");
+ if (quotaPercentageBar)
+ quotaPercentageBar.setAttribute("value", percentage);
+
+ var bundle = document.getElementById("bundle_messenger");
+ if(bundle)
+ {
+ var usedFreeCaption = bundle.getFormattedString("quotaUsedFree", [usedKB, maxKB], 2);
+ var quotaCaption = document.getElementById("quotaUsedFree");
+ if(quotaCaption)
+ quotaCaption.setAttribute("value", usedFreeCaption);
+
+ var percentUsedCaption = bundle.getFormattedString("quotaPercentUsed", [percentage], 1);
+ var percentUsed = document.getElementById("quotaPercentUsed");
+ if(percentUsed)
+ percentUsed.setAttribute("value", percentUsedCaption);
+ }
+ }
+
+};
+
+function doEnabling()
+{
+ var nameTextbox = document.getElementById("name");
+ document.documentElement.getButton("accept").disabled = !nameTextbox.value;
+}
+
+function folderPropsOKButton()
+{
+ if (gMsgFolder)
+ {
+ const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
+ // set charset attributes
+ var folderCharsetList = document.getElementById("folderCharsetList");
+
+ // Log to the Error Console the charset value for the folder
+ // if it is unknown to us. Value will be preserved by the menu-item.
+ if (folderCharsetList.selectedIndex == -1)
+ {
+ Components.utils.reportError("Unknown folder encoding; folder=" +
+ gMsgFolder.name + ", charset=" + gMsgFolder.charset);
+ }
+
+ gMsgFolder.charset = folderCharsetList.getAttribute("value");
+ gMsgFolder.charsetOverride = document.getElementById("folderCharsetOverride")
+ .checked;
+
+ if(document.getElementById("offline.selectForOfflineFolder").checked ||
+ document.getElementById("offline.selectForOfflineNewsgroup").checked)
+ gMsgFolder.setFlag(nsMsgFolderFlags.Offline);
+ else
+ gMsgFolder.clearFlag(nsMsgFolderFlags.Offline);
+
+ if(document.getElementById("folderCheckForNewMessages").checked)
+ gMsgFolder.setFlag(nsMsgFolderFlags.CheckNew);
+ else
+ gMsgFolder.clearFlag(nsMsgFolderFlags.CheckNew);
+
+ let glodaCheckbox = document.getElementById("folderIncludeInGlobalSearch");
+ if (!glodaCheckbox.hidden) {
+ if(glodaCheckbox.checked) {
+ // We pass true here so that folders such as trash and junk can still
+ // have a priority set.
+ Gloda.resetFolderIndexingPriority(gMsgFolder, true);
+ } else {
+ Gloda.setFolderIndexingPriority(gMsgFolder,
+ Gloda.getFolderForFolder(gMsgFolder).kIndexingNeverPriority);
+ }
+ }
+
+ var retentionSettings = saveCommonRetentionSettings(gMsgFolder.retentionSettings);
+ retentionSettings.useServerDefaults = document.getElementById("retention.useDefault").checked;
+ gMsgFolder.retentionSettings = retentionSettings;
+
+ }
+
+ try
+ {
+ // This throws an exception when an illegal folder name was entered.
+ okCallback(document.getElementById("name").value, window.arguments[0].name,
+ gMsgFolder.URI);
+
+ return true;
+ }
+ catch (e)
+ {
+ return false;
+ }
+}
+
+function folderPropsOnLoad()
+{
+ // look in arguments[0] for parameters
+ if (window.arguments && window.arguments[0]) {
+ if ( window.arguments[0].title ) {
+ document.title = window.arguments[0].title;
+ }
+ if ( window.arguments[0].okCallback ) {
+ top.okCallback = window.arguments[0].okCallback;
+ }
+ }
+
+ // fill in folder name, based on what they selected in the folder pane
+ if (window.arguments[0].folder) {
+ gMsgFolder = window.arguments[0].folder;
+ } else {
+ dump("passed null for folder, do nothing\n");
+ }
+
+ if(window.arguments[0].name)
+ {
+ // Initialize name textbox with the given name and remember this
+ // value so we can tell whether the folder needs to be renamed
+ // when the dialog is accepted.
+ var nameTextbox = document.getElementById("name");
+ nameTextbox.value = window.arguments[0].name;
+
+// name.setSelectionRange(0,-1);
+// name.focusTextField();
+ }
+
+ const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
+ const serverType = window.arguments[0].serverType;
+
+ // Do this first, because of gloda we may want to override some of the hidden
+ // statuses.
+ hideShowControls(serverType);
+
+ if (gMsgFolder) {
+ // We really need a functioning database, so we'll detect problems
+ // and create one if we have to.
+ try {
+ var db = gMsgFolder.getDatabase(null);
+ }
+ catch (e) {
+ gMsgFolder.updateFolder(window.arguments[0].msgWindow);
+ }
+
+ var locationTextbox = document.getElementById("location");
+
+ // Decode the displayed mailbox:// URL as it's useful primarily for debugging,
+ // whereas imap and news urls are sent around.
+ locationTextbox.value = (serverType == "imap" || serverType == "nntp") ?
+ gMsgFolder.folderURL : decodeURI(gMsgFolder.folderURL);
+
+ if (gMsgFolder.canRename)
+ document.getElementById("name").removeAttribute("readonly");
+
+ if (gMsgFolder.flags & nsMsgFolderFlags.Offline) {
+
+ if(serverType == "imap" || serverType == "pop3")
+ document.getElementById("offline.selectForOfflineFolder").checked = true;
+
+ if(serverType == "nntp")
+ document.getElementById("offline.selectForOfflineNewsgroup").checked = true;
+ }
+ else {
+ if(serverType == "imap" || serverType == "pop3")
+ document.getElementById("offline.selectForOfflineFolder").checked = false;
+
+ if(serverType == "nntp")
+ document.getElementById("offline.selectForOfflineNewsgroup").checked = false;
+ }
+
+ // select the menu item
+ var folderCharsetList = document.getElementById("folderCharsetList");
+ folderCharsetList.value = gMsgFolder.charset;
+
+ // set override checkbox
+ document.getElementById("folderCharsetOverride").checked = gMsgFolder.charsetOverride;
+
+ // set check for new mail checkbox
+ document.getElementById("folderCheckForNewMessages").checked = gMsgFolder.flags & nsMsgFolderFlags.CheckNew;
+
+ // if gloda indexing is off, hide the related checkbox
+ var glodaCheckbox = document.getElementById("folderIncludeInGlobalSearch");
+ var glodaEnabled = Services.prefs
+ .getBoolPref("mailnews.database.global.indexer.enabled");
+ if (!glodaEnabled || (gMsgFolder.flags & (nsMsgFolderFlags.Queue |
+ nsMsgFolderFlags.Newsgroup))) {
+ glodaCheckbox.hidden = true;
+ } else {
+ // otherwise, the user can choose whether this file gets indexed
+ let glodaFolder = Gloda.getFolderForFolder(gMsgFolder);
+ glodaCheckbox.checked =
+ glodaFolder.indexingPriority != glodaFolder.kIndexingNeverPriority;
+ }
+ }
+
+ if (serverType == "imap")
+ {
+ var imapFolder = gMsgFolder.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
+ if (imapFolder)
+ imapFolder.fillInFolderProps(gFolderPropsSink);
+ }
+
+ var retentionSettings = gMsgFolder.retentionSettings;
+ initCommonRetentionSettings(retentionSettings);
+ document.getElementById("retention.useDefault").checked = retentionSettings.useServerDefaults;
+
+ // set folder sizes
+ let numberOfMsgs = gMsgFolder.getTotalMessages(false);
+ if (numberOfMsgs >= 0)
+ document.getElementById("numberOfMessages").value = numberOfMsgs;
+
+ try {
+ let sizeOnDisk = Components.classes["@mozilla.org/messenger;1"]
+ .createInstance(Components.interfaces.nsIMessenger)
+ .formatFileSize(gMsgFolder.sizeOnDisk, true);
+ document.getElementById("sizeOnDisk").value = sizeOnDisk;
+ } catch (e) { }
+
+ // select the initial tab
+ if (window.arguments[0].tabID) {
+ try {
+ document.getElementById("folderPropTabBox").selectedTab =
+ document.getElementById(window.arguments[0].tabID);
+ }
+ catch (ex) {}
+ }
+ onCheckKeepMsg();
+ onUseDefaultRetentionSettings();
+}
+
+function hideShowControls(serverType)
+{
+ let controls = document.querySelectorAll("[hidefor]");
+ var len = controls.length;
+ for (var i=0; i<len; i++) {
+ var control = controls[i];
+ var hideFor = control.getAttribute("hidefor");
+ if (!hideFor)
+ throw "hidefor empty";
+
+ // hide unsupported server type
+ // adding support for hiding multiple server types using hideFor="server1,server2"
+ var hideForBool = false;
+ var hideForTokens = hideFor.split(",");
+ for (var j = 0; j < hideForTokens.length; j++) {
+ if (hideForTokens[j] == serverType) {
+ hideForBool = true;
+ break;
+ }
+ }
+ control.hidden = hideForBool;
+ }
+
+ // hide the priviliges button if the imap folder doesn't have an admin url
+ // mabye should leave this hidden by default and only show it in this case instead
+ try {
+ var imapFolder = gMsgFolder.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
+ if (imapFolder)
+ {
+ var privilegesButton = document.getElementById("imap.FolderPrivileges");
+ if (privilegesButton)
+ {
+ if (!imapFolder.hasAdminUrl)
+ privilegesButton.setAttribute("hidden", "true");
+ }
+ }
+ }
+ catch (ex) {}
+
+ if (gMsgFolder)
+ {
+ const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
+ // Hide "check for new mail" checkbox if this is an Inbox.
+ if (gMsgFolder.flags & nsMsgFolderFlags.Inbox)
+ document.getElementById("folderCheckForNewMessages").hidden = true;
+ // Retention policy doesn't apply to Drafts/Templates/Outbox.
+ if (gMsgFolder.isSpecialFolder(nsMsgFolderFlags.Drafts |
+ nsMsgFolderFlags.Templates |
+ nsMsgFolderFlags.Queue, true))
+ document.getElementById("Retention").hidden = true;
+ }
+}
+
+function onOfflineFolderDownload()
+{
+ // we need to create a progress window and pass that in as the second parameter here.
+ gMsgFolder.downloadAllForOffline(null, window.arguments[0].msgWindow);
+}
+
+function onFolderPrivileges()
+{
+ var imapFolder = gMsgFolder.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
+ if (imapFolder)
+ imapFolder.folderPrivileges(window.arguments[0].msgWindow);
+ // let's try closing the modal dialog to see if it fixes the various problems running this url
+ window.close();
+}
+
+
+function onUseDefaultRetentionSettings()
+{
+ var useDefault = document.getElementById("retention.useDefault").checked;
+ document.getElementById('retention.keepMsg').disabled = useDefault;
+ document.getElementById('retention.keepNewMsgMinLabel').disabled = useDefault;
+ document.getElementById('retention.keepOldMsgMinLabel').disabled = useDefault;
+
+ var keepMsg = document.getElementById("retention.keepMsg").value;
+ const nsIMsgRetentionSettings = Components.interfaces.nsIMsgRetentionSettings;
+ document.getElementById('retention.keepOldMsgMin').disabled =
+ useDefault || (keepMsg != nsIMsgRetentionSettings.nsMsgRetainByAge);
+ document.getElementById('retention.keepNewMsgMin').disabled =
+ useDefault || (keepMsg != nsIMsgRetentionSettings.nsMsgRetainByNumHeaders);
+}
+
+function RebuildSummaryInformation()
+{
+ window.arguments[0].rebuildSummaryCallback(gMsgFolder);
+}
diff --git a/mailnews/base/content/folderProps.xul b/mailnews/base/content/folderProps.xul
new file mode 100644
index 000000000..23b9c3d9b
--- /dev/null
+++ b/mailnews/base/content/folderProps.xul
@@ -0,0 +1,216 @@
+<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
+<!--
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/preferences/preferences.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/content/charsetList.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/folderProps.dtd">
+
+<dialog
+ id="folderPropertiesDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&folderProps.windowtitle.label;"
+ buttons="accept,cancel"
+ onload="folderPropsOnLoad();" style="width: 56ch;"
+ ondialogaccept="return folderPropsOKButton();">
+
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/retention.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/folderProps.js"/>
+ <script type="application/javascript">
+ <![CDATA[
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Services.obs.notifyObservers(null, "charsetmenu-selected", "other");
+ ]]>
+ </script>
+
+<tabbox id="folderPropTabBox">
+ <tabs id="folderPropTabs">
+ <tab id="GeneralTab" label="&generalInfo.label;"/>
+ <tab id="Retention" label="&retention.label;"/>
+ <tab id="SynchronizationTab" hidefor="movemail,pop3,rss,none" label="&folderSynchronizationTab.label;"/>
+ <tab id="SharingTab" hidefor="movemail,pop3,rss,none,nntp" label="&folderSharingTab.label;"/>
+ <tab id="QuotaTab" hidefor="movemail,pop3,rss,none,nntp" label="&folderQuotaTab.label;"/>
+ </tabs>
+ <tabpanels id="folderPropTabPanels">
+
+ <vbox id="GeneralPanel">
+ <hbox id="nameBox" align="center">
+ <label value="&folderProps.name.label;" control="name"
+ accesskey="&folderProps.name.accesskey;"/>
+ <textbox id="name" readonly="true" oninput="doEnabling();" flex="1"/>
+ </hbox>
+ <hbox align="center">
+ <label value="&folderProps.location.label;" control="location"
+ accesskey="&folderProps.location.accesskey;"/>
+ <textbox id="location" readonly="true" flex="1" class="uri-element"/>
+ </hbox>
+ <vbox>
+ <spacer height="2"/>
+ <hbox align="center">
+ <label value="&numberOfMessages.label;"/>
+ <label id="numberOfMessages" value="&numberUnknown.label;"/>
+ <spacer flex="1"/>
+ <label value="&sizeOnDisk.label;"/>
+ <label id="sizeOnDisk" value="&sizeUnknown.label;"/>
+ </hbox>
+ <spacer height="2"/>
+ </vbox>
+ <checkbox id="folderIncludeInGlobalSearch" hidefor="nntp"
+ label="&folderIncludeInGlobalSearch.label;"
+ accesskey="&folderIncludeInGlobalSearch.accesskey;"/>
+ <checkbox hidefor="movemail,pop3,none,nntp"
+ id="folderCheckForNewMessages"
+ label="&folderCheckForNewMessages2.label;"
+ accesskey="&folderCheckForNewMessages2.accesskey;"/>
+ <vbox>
+ <hbox align="center" valign="middle">
+ <label value="&folderCharsetFallback2.label;"
+ accesskey="&folderCharsetFallback2.accesskey;"
+ control="folderCharsetList"/>
+ <menulist id="folderCharsetList" type="charset"/>
+ </hbox>
+ <checkbox class="indent" id="folderCharsetOverride"
+ label="&folderCharsetEnforce2.label;"
+ accesskey="&folderCharsetEnforce2.accesskey;"/>
+ </vbox>
+ <groupbox id="folderRebuildSummaryGroupBox" align="baseline">
+ <grid>
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row>
+ <description id="folderRebuildSummaryExplanation">&folderRebuildSummaryFile.explanation;</description>
+ <vbox>
+ <button id="folderRebuildSummaryButton"
+ label="&folderRebuildSummaryFile2.label;"
+ oncommand="RebuildSummaryInformation();"
+ accesskey="&folderRebuildSummaryFile2.accesskey;"
+ tooltiptext="&folderRebuildSummaryFileTip2.label;"
+ align="center"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </vbox>
+
+ <vbox id="RetentionPanel" align="start">
+ <description hidefor="imap,pop3" class="desc">&retentionCleanup.label;</description>
+ <description hidefor="movemail,pop3,rss,none,nntp" class="desc">&retentionCleanupImap.label;</description>
+ <description hidefor="movemail,imap,rss,none,nntp" class="desc">&retentionCleanupPop.label;</description>
+
+ <hbox align="center" class="indent">
+ <checkbox wsm_persist="true" id="retention.useDefault" accesskey="&retentionUseAccount.accesskey;"
+ label="&retentionUseAccount.label;" checked="true" oncommand="onUseDefaultRetentionSettings()"/>
+ </hbox>
+ <vbox class="indent">
+ <hbox class="indent">
+ <radiogroup wsm_persist="true" id="retention.keepMsg" aria-labelledby="retention.useDefault">
+ <radio wsm_persist="true" value="1" accesskey="&retentionKeepAll.accesskey;"
+ label="&retentionKeepAll.label;" oncommand="onCheckKeepMsg();"/>
+ <hbox flex="1" align="center">
+ <radio wsm_persist="true" id="keepNewMsg" accesskey="&retentionKeepRecent.accesskey;"
+ value="3" label="&retentionKeepRecent.label;" oncommand="onCheckKeepMsg();"/>
+ <textbox wsm_persist="true" id="retention.keepNewMsgMin"
+ type="number" min="1" increment="10" size="4" value="2000"
+ aria-labelledby="keepNewMsg retention.keepNewMsgMin retention.keepNewMsgMinLabel"/>
+ <label value="&message.label;" control="retention.keepNewMsgMin" id="retention.keepNewMsgMinLabel"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio wsm_persist="true" id="keepMsg" accesskey="&retentionDeleteMsg.accesskey;"
+ value="2" label="&retentionDeleteMsg.label;" oncommand="onCheckKeepMsg();"/>
+ <textbox wsm_persist="true" id="retention.keepOldMsgMin"
+ type="number" min="1" size="2" value="30"
+ aria-labelledby="keepMsg retention.keepOldMsgMin retention.keepOldMsgMinLabel"/>
+ <label value="&daysOld.label;" control="retention.keepOldMsgMin" id="retention.keepOldMsgMinLabel"/>
+ </hbox>
+ </radiogroup>
+ </hbox>
+ <hbox class="indent">
+ <checkbox id="retention.applyToFlagged" wsm_persist="true"
+ label="&retentionApplyToFlagged.label;"
+ accesskey="&retentionApplyToFlagged.accesskey;"
+ observes="retention.keepMsg" checked="true"/>
+ </hbox>
+ </vbox>
+ </vbox>
+
+ <vbox id="SyncPanel" valign="top" align="start">
+ <vbox>
+ <checkbox hidefor="nntp"
+ wsm_persist="true" id="offline.selectForOfflineFolder"
+ label="&offlineFolder.check.label;"
+ accesskey="&offlineFolder.check.accesskey;"/>
+ <checkbox hidefor="imap"
+ wsm_persist="true" id="offline.selectForOfflineNewsgroup"
+ label="&selectofflineNewsgroup.check.label;"
+ accesskey="&selectofflineNewsgroup.check.accesskey;"/>
+ </vbox>
+ <button hidefor="nntp" label="&offlineFolder.button.label;"
+ oncommand="onOfflineFolderDownload();" accesskey="&offlineFolder.button.accesskey;"
+ id="offline.offlineFolderDownloadButton" orient="right"/>
+ <button hidefor="imap" label="&offlineNewsgroup.button.label;"
+ oncommand="onOfflineFolderDownload();" accesskey="&offlineNewsgroup.button.accesskey;"
+ id="offline.offlineNewsgroupDownloadButton" orient="right"/>
+ </vbox>
+
+ <vbox id="SharingPanel" valign="top">
+ <hbox align="start">
+ <label value="&folderType.label;" id="folderTypeLabel"/>
+ <label value="" id="folderType.text"/>
+ </hbox>
+ <vbox align="start">
+ <label value="" id="folderDescription.text"/>
+ <label value=" "/>
+ <label value="&permissionsDesc.label;" id="permissionsDescLabel"/>
+
+ <description id="folderPermissions.text"></description>
+ </vbox>
+ <spacer flex="100%"/>
+ <vbox align="start">
+ <button hidefor="movemail,pop3,none,rss,nntp" label="&privileges.button.label;"
+ oncommand="onFolderPrivileges();" accesskey="&privileges.button.accesskey;"
+ id="imap.FolderPrivileges" orient="right"/>
+ </vbox>
+ </vbox>
+
+ <vbox id="QuotaPanel" valign="top">
+ <label id="folderQuotaStatus" flex="1"/>
+
+ <grid id="folderQuotaData" hidden="true" flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&folderQuotaRoot.label;" control="quotaRoot"/>
+ <textbox id="quotaRoot" readonly="true"/>
+ </row>
+ <row>
+ <label value="&folderQuotaUsage.label;"/>
+ <description id="quotaUsedFree"/>
+ </row>
+ <row align="center">
+ <label value="&folderQuotaStatus.label;"/>
+ <hbox align="center">
+ <progressmeter id="quotaPercentageBar" mode="determined" value="0%"/>
+ <label id="quotaPercentUsed"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ </tabpanels>
+</tabbox>
+
+</dialog>
diff --git a/mailnews/base/content/folderWidgets.xml b/mailnews/base/content/folderWidgets.xml
new file mode 100644
index 000000000..605f4230c
--- /dev/null
+++ b/mailnews/base/content/folderWidgets.xml
@@ -0,0 +1,829 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-->
+
+<bindings id="mailFolderBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="folder-menupopup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation>
+ <constructor><![CDATA[
+ Components.utils.import("resource:///modules/FeedUtils.jsm", this);
+ Components.utils.import("resource:///modules/folderUtils.jsm", this);
+ Components.utils.import("resource:///modules/iteratorUtils.jsm", this);
+ Components.utils.import("resource:///modules/mailServices.js", this);
+ Components.utils.import("resource:///modules/MailUtils.js", this);
+ Components.utils.import("resource:///modules/StringBundle.js", this);
+ this._stringBundle = new this
+ .StringBundle("chrome://messenger/locale/folderWidgets.properties");
+
+ // Get the displayformat if set.
+ if (this.parentNode && this.parentNode.localName == "menulist")
+ this._displayformat = this.parentNode.getAttribute("displayformat");
+
+ // Find out if we are in a wrapper (customize toolbars mode is active).
+ let inWrapper = false;
+ let node = this;
+ while (node instanceof XULElement) {
+ if (node.id.startsWith("wrapper-")) {
+ inWrapper = true;
+ break;
+ }
+ node = node.parentNode;
+ }
+
+ if (!inWrapper) {
+ if (this.hasAttribute("original-width")) {
+ // If we were in a wrapper before and have a width stored, restore it now.
+ if (this.getAttribute("original-width") == "none")
+ this.removeAttribute("width");
+ else
+ this.setAttribute("width", this.getAttribute("original-width"));
+
+ this.removeAttribute("original-width");
+ }
+
+ // If we are a child of a menulist, and we aren't in a wrapper, we
+ // need to build our content right away, otherwise the menulist
+ // won't have proper sizing.
+ if (this.parentNode && this.parentNode.localName == "menulist")
+ this._ensureInitialized();
+ } else {
+ // But if we're in a wrapper, remove our children, because we're
+ // getting re-created when the toolbar customization closes.
+ this._teardown();
+
+ // Store our current width and set a safe small width when we show
+ // in a wrapper.
+ if (!this.hasAttribute("original-width")) {
+ this.setAttribute("original-width", this.hasAttribute("width") ?
+ this.getAttribute("width") : "none");
+ this.setAttribute("width", "100");
+ }
+ }
+ ]]></constructor>
+ <destructor><![CDATA[
+ // Clean up when being destroyed.
+ this._removeListener();
+ ]]></destructor>
+ <!--
+ - Make sure we remove our listener when the window is being destroyed
+ - or the widget teared down.
+ -->
+ <method name="_removeListener">
+ <body><![CDATA[
+ if (!this._initialized)
+ return;
+
+ this.MailServices.mailSession.RemoveFolderListener(this._listener);
+ ]]></body>
+ </method>
+
+ <field name="_stringBundle">null</field>
+
+ <!--
+ - If non-null, the subFolders of this nsIMsgFolder will be used to
+ - populate this menu. If this is null, the menu will be populated
+ - using the root-folders for all accounts
+ -->
+ <field name="_parentFolder">null</field>
+ <property name="parentFolder"
+ onget="return this._parentFolder;"
+ onset="return this._parentFolder = val;"/>
+
+ <!--
+ - Various filtering modes can be used with this menu-binding. To use
+ - one of them, append the mode="foo" attribute to the element. When
+ - building the menu, we will then use this._filters[mode] as a filter
+ - function to eliminate folders that should not be shown.
+ -
+ - Note that extensions should feel free to plug in here!
+ -->
+ <field name="_filters"><![CDATA[({
+ // Returns true if messages can be filed in the folder
+ filing: function filter_filing(aFolder) {
+ if (!aFolder.server.canFileMessagesOnServer)
+ return false;
+
+ return (aFolder.canFileMessages || aFolder.hasSubFolders);
+ },
+
+ // Returns true if we can get mail for this folder. (usually this just
+ // means the "root" fake folder)
+ getMail: function filter_getMail(aFolder) {
+ if (aFolder.isServer && aFolder.server.type != "none")
+ return true;
+ if (aFolder.server.type == "nntp" || aFolder.server.type == "rss")
+ return true;
+ return false;
+ },
+
+ // Returns true if we can add filters to this folder/account
+ filters: function filter_filter(aFolder) {
+ // We can always filter news
+ if (aFolder.server.type == "nntp")
+ return true;
+
+ return aFolder.server.canHaveFilters;
+ },
+
+ subscribe: function filter_subscribe(aFolder) {
+ return aFolder.canSubscribe;
+ },
+
+ newFolder: function filter_newFolder(aFolder) {
+ return aFolder.canCreateSubfolders &&
+ aFolder.server.canCreateFoldersOnServer;
+ },
+
+ deferred: function filter_defered(aFolder) {
+ return aFolder.server.canCreateFoldersOnServer &&
+ !aFolder.supportsOffline;
+ },
+
+ // Folders that are not in a deferred account
+ notDeferred: function(aFolder) {
+ let server = aFolder.server;
+ return !(server instanceof Components.interfaces.nsIPop3IncomingServer &&
+ server.deferredToAccount);
+ },
+
+ // Folders that can be searched.
+ search: function filter_search(aFolder) {
+ if (!aFolder.server.canSearchMessages ||
+ aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Virtual))
+ return false;
+ return true;
+ },
+
+ // Folders that can subscribe feeds.
+ feeds: function filter_feeds(aFolder) {
+ if (aFolder.server.type != "rss" ||
+ aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Trash) ||
+ aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Virtual))
+ return false;
+ return true;
+ },
+
+ junk: function filter_junk(aFolder) {
+ // Don't show servers (nntp & any others) which do not allow search or filing
+ // I don't really understand why canSearchMessages is needed, but it was included in
+ // earlier code, so I include it as well.
+ if (!aFolder.server.canFileMessagesOnServer || !aFolder.server.canSearchMessages)
+ return false;
+ // show parents that might have usable subfolders, or usable folders
+ return aFolder.hasSubFolders || aFolder.canFileMessages;
+ }
+ })]]></field>
+
+ <!--
+ - The maximum number of entries in the "Recent" menu
+ -->
+ <field name="_MAXRECENT">15</field>
+
+ <!--
+ - Is this list containing only servers (accounts) and no real folders?
+ -->
+ <field name="_serversOnly">true</field>
+
+ <!--
+ - Our listener to let us know when folders change/appear/disappear so
+ - we can know to rebuild ourselves.
+ -->
+ <field name="_listener">
+ <![CDATA[({
+ _menu: this,
+ _clearMenu: function(aMenu) {
+ if (aMenu._teardown)
+ aMenu._teardown();
+ },
+ OnItemAdded: function act_add(aRDFParentItem, aItem) {
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+ return;
+ if (this._filterFunction && !this._filterFunction(aItem)) {
+ return;
+ }
+ //xxx we can optimize this later
+ //xxx I'm not quite sure why this isn't always a function
+ if (this._menu._teardown)
+ this._menu._teardown();
+ },
+
+ OnItemRemoved: function act_remove(aRDFParentItem, aItem) {
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+ return;
+ if (this._filterFunction && !this._filterFunction(aItem)) {
+ return;
+ }
+ //xxx we can optimize this later
+ if (this._menu._teardown)
+ this._menu._teardown();
+ },
+
+ //xxx I stole this listener list from nsMsgFolderDatasource.cpp, but
+ // someone should really document what events are fired when, so that
+ // we make sure we're updating at the right times.
+ OnItemPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+ OnItemIntPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ var child = this._getChildForItem(aItem);
+ if (child)
+ this._menu._setCssSelectors(child._folder, child);
+ },
+ OnItemBoolPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ var child = this._getChildForItem(aItem);
+ if (child)
+ this._menu._setCssSelectors(child._folder, child);
+ },
+ OnItemUnicharPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ var child = this._getChildForItem(aItem);
+ if (child)
+ this._menu._setCssSelectors(child._folder, child);
+ },
+ OnItemPropertyFlagChanged: function(aItem, aProperty, aOld, aNew) {},
+ OnItemEvent: function(aFolder, aEvent) {
+ if (aEvent.toString() == "MRMTimeChanged") {
+ if (this._menu.getAttribute("showRecent") != "true" ||
+ !this._menu.firstChild || !this._menu.firstChild.firstChild)
+ return;
+ // if this folder is already in the recent menu, return.
+ if (this._getChildForItem(aFolder,
+ this._menu.firstChild.firstChild))
+ return;
+ }
+ // Special casing folder renames here, since they require more work
+ // since sort-order may have changed.
+ else if (aEvent.toString() == "RenameCompleted") {
+ if (!this._getChildForItem(aFolder))
+ return;
+ }
+ else
+ return;
+ // folder renamed, or new recent folder, so rebuild.
+ setTimeout(this._clearMenu, 0, this._menu);
+ },
+
+ /**
+ * Helper function to check and see whether we have a menuitem for this
+ * particular nsIMsgFolder
+ *
+ * @param aItem the nsIMsgFolder to check
+ * @param aMenu (optional) menu to look in, defaults to this._menu.
+ * @returns null if no child for that folder exists, otherwise the
+ * menuitem for that child
+ */
+ _getChildForItem: function act__itemIsChild(aItem, aMenu) {
+ aMenu = aMenu || this._menu;
+ if (!aMenu || !aMenu.childNodes)
+ return null;
+
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+ return null;
+ for (let i = 0; i < aMenu.childNodes.length; i++) {
+ let folder = aMenu.childNodes[i]._folder;
+ if (folder && folder.URI == aItem.URI)
+ return aMenu.childNodes[i];
+ }
+ return null;
+ }
+ })]]></field>
+
+ <!--
+ - True if we have already built our menu-items and are now just
+ - listening for changes
+ -->
+ <field name="_initialized">false</field>
+
+ <!--
+ - Call this if you are unsure whether the menu-items have been built,
+ - but know that they need to be built now if they haven't.
+ -->
+ <method name="_ensureInitialized">
+ <body><![CDATA[
+ if (this._initialized)
+ return;
+
+ // I really wish they'd just make this global...
+ const Ci = Components.interfaces;
+
+ let folders;
+
+ // Figure out which folders to build. If we don't have a parent, then
+ // we assume we should build the top-level accounts. (Actually we
+ // build the fake root folders for those accounts.)
+ if (!this._parentFolder) {
+ let accounts = this.allAccountsSorted(true);
+
+ // Now generate our folder-list. Note that we'll special case this
+ // situation below, to avoid destroying the sort order we just made
+ folders = accounts.map(acct => acct.incomingServer.rootFolder);
+ } else {
+ // If we do have a parent folder, then we just build based on those
+ // subFolders for that parent.
+ folders = this.toArray(this.fixIterator(this._parentFolder.subFolders,
+ Ci.nsIMsgFolder));
+ }
+
+ this._build(folders);
+
+ // Lastly, we add a listener to get notified of changes in the folder
+ // structure.
+ this.MailServices.mailSession.AddFolderListener(this._listener,
+ Ci.nsIFolderListener.all);
+
+ this._initialized = true;
+ ]]></body>
+ </method>
+
+ <!--
+ - Actually constructs the menu-items based on the folders given.
+ -
+ - @param aFolders An array of nsIMsgFolders to use for building.
+ -->
+ <method name="_build">
+ <parameter name="aFolders"/>
+ <body><![CDATA[
+ let folders;
+ let excludeServers = [];
+ let disableServers = [];
+
+ // excludeServers attribute is a comma separated list of server keys.
+ if (this.hasAttribute("excludeServers"))
+ excludeServers = this.getAttribute("excludeServers").split(",");
+
+ // disableServers attribute is a comma separated list of server keys.
+ if (this.hasAttribute("disableServers"))
+ disableServers = this.getAttribute("disableServers").split(",");
+
+ // Extensions and other consumers can add to these modes too, see the
+ // above note on the _filters field.
+ var mode = this.getAttribute("mode");
+ if (mode && mode != "") {
+ var filterFunction = this._filters[mode];
+ folders = aFolders.filter(filterFunction);
+ this._listener._filterFunction = filterFunction;
+ } else {
+ folders = aFolders;
+ }
+
+ if (excludeServers.length > 0) {
+ folders = folders.filter(function(aFolder) {
+ return !(excludeServers.indexOf(aFolder.server.key) != -1); });
+ }
+
+ /* This code block will do the following: Add a menu item that refers
+ back to the parent folder when there is a showFileHereLabel
+ attribute or no mode attribute. However the code won't add such a
+ menu item if one of the following conditions is met:
+ (*) There is no parent folder
+ (*) Folder is server and showAccountsFileHere is explicitly false
+ (*) Current folder has a mode, the parent folder can be selected,
+ no messages can be filed into the parent folder (e.g. when the
+ parent folder is a news group or news server) and the folder
+ mode is not equal to newFolder
+
+ The menu item will have the value of the fileHereLabel attribute as
+ label or if the attribute does not exist the name of the parent
+ folder instead.
+ */
+ let parent = this._parentFolder;
+ if (parent && (this.getAttribute("showFileHereLabel") == "true" || !mode)) {
+ let showAccountsFileHere = this.getAttribute("showAccountsFileHere");
+ if ((!parent.isServer || showAccountsFileHere != "false") &&
+ (!mode || mode == "newFolder" || parent.noSelect ||
+ parent.canFileMessages || showAccountsFileHere == "true")) {
+ var menuitem = document.createElement("menuitem");
+ menuitem._folder = this._parentFolder;
+ menuitem.setAttribute("generated", "true");
+ if (this.hasAttribute("fileHereLabel")) {
+ menuitem.setAttribute("label", this.getAttribute("fileHereLabel"));
+ menuitem.setAttribute("accesskey", this.getAttribute("fileHereAccessKey"));
+ } else {
+ menuitem.setAttribute("label", this._parentFolder.prettyName);
+ menuitem.setAttribute("class", "folderMenuItem menuitem-iconic");
+ this._setCssSelectors(this._parentFolder, menuitem);
+ }
+ // Eww. have to support some legacy code here...
+ menuitem.setAttribute("id", this._parentFolder.URI);
+ this.appendChild(menuitem);
+
+ if (this._parentFolder.noSelect)
+ menuitem.setAttribute("disabled", "true");
+
+ var sep= document.createElement("menuseparator");
+ sep.setAttribute("generated", "true");
+ this.appendChild(sep);
+ }
+ }
+
+ let globalInboxFolder = null;
+ // See if this is the toplevel menu (usually with accounts).
+ if (!this._parentFolder) {
+ // Some menus want a "Recent" option, but that should only be on our
+ // top-level menu
+ if (this.getAttribute("showRecent") == "true")
+ this._buildRecentMenu();
+ // If we are showing the accounts for deferring, move Local Folders to the top.
+ if (mode == "deferred") {
+ globalInboxFolder = this.MailServices.accounts.localFoldersServer
+ .rootFolder;
+ let localFoldersIndex = folders.indexOf(globalInboxFolder);
+ if (localFoldersIndex != -1) {
+ folders.splice(localFoldersIndex, 1);
+ folders.unshift(globalInboxFolder);
+ }
+ }
+ // If we're the root of the folder hierarchy, then we actually don't
+ // want to sort the folders, but rather the accounts to which the
+ // folders belong. Since that sorting was already done, we don't need
+ // to do anything for that case here.
+ } else {
+ // Sorts the list of folders. We give first priority to the sortKey
+ // property if it is available, otherwise a case-insensitive
+ // comparison of names.
+ folders = folders.sort(function nameCompare(a, b) {
+ return a.compareSortKeys(b);
+ });
+ }
+
+ /* In some cases, the user wants to have a list of subfolders for only
+ * some account types (or maybe all of them). So we use this to
+ * determine what the user wanted.
+ */
+ var shouldExpand;
+ var labels = null;
+ if (this.getAttribute("expandFolders") == "true" ||
+ !this.hasAttribute("expandFolders")) {
+ shouldExpand = function (e) { return true; };
+ } else if (this.getAttribute("expandFolders") == "false") {
+ shouldExpand = function (e) { return false; };
+ } else {
+ /* We want a subfolder list for only some servers. We also may need
+ * to create headers to select the servers. If so, then headlabels
+ * is a comma-delimited list of labels corresponding to the server
+ * types specified in expandFolders.
+ */
+ var types = this.getAttribute("expandFolders").split(/ *, */);
+ // Set the labels. labels[type] = label
+ if (this.hasAttribute("headlabels")) {
+ var labelNames = this.getAttribute("headlabels").split(/ *, */);
+ labels = {};
+ // If the length isn't equal, don't give them any of the labels,
+ // since any combination will probably be wrong.
+ if (labelNames.length == types.length) {
+ for (var index in types)
+ labels[types[index]] = labelNames[index];
+ }
+ }
+ shouldExpand = function (e) { return types.indexOf(e) != -1; };
+ }
+
+ // We need to call this, or hasSubFolders will always return false.
+ // Remove this workaround when Bug 502900 is fixed.
+ this.MailUtils.discoverFolders();
+ this._serversOnly = true;
+
+ for (let folder of folders) {
+ let node;
+ if (!folder.isServer)
+ this._serversOnly = false;
+
+ // If we're going to add subFolders, we need to make menus, not
+ // menuitems.
+ if (!folder.hasSubFolders || !shouldExpand(folder.server.type)) {
+ node = document.createElement("menuitem");
+ // Grumble, grumble, legacy code support
+ node.setAttribute("id", folder.URI);
+ node.setAttribute("class", "folderMenuItem menuitem-iconic");
+ node.setAttribute("generated", "true");
+ this.appendChild(node);
+ } else {
+ this._serversOnly = false;
+ //xxx this is slightly problematic in that we haven't confirmed
+ // whether any of the subfolders will pass the filter
+ node = document.createElement("menu");
+ node.setAttribute("class", "folderMenuItem menu-iconic");
+ node.setAttribute("generated", "true");
+ this.appendChild(node);
+
+ // Create the submenu
+ // (We must use cloneNode here because on OS X the native menu
+ // functionality and very sad limitations of XBL1 cause the bindings
+ // to never get created for popup if we create a new element. We
+ // perform a shallow clone to avoid picking up any of our children.)
+ var popup = this.cloneNode(false);
+ popup._parentFolder = folder;
+ popup.setAttribute("class", this.getAttribute("class"));
+ popup.setAttribute("type", this.getAttribute("type"));
+ if (this.hasAttribute("fileHereLabel"))
+ popup.setAttribute("fileHereLabel",
+ this.getAttribute("fileHereLabel"));
+ popup.setAttribute("showFileHereLabel",
+ this.getAttribute("showFileHereLabel"));
+ popup.setAttribute("oncommand",
+ this.getAttribute("oncommand"));
+ popup.setAttribute("mode",
+ this.getAttribute("mode"));
+ if (this.hasAttribute("disableServers"))
+ popup.setAttribute("disableServers",
+ this.getAttribute("disableServers"));
+ if (this.hasAttribute("position"))
+ popup.setAttribute("position",
+ this.getAttribute("position"));
+
+ // If there are labels, add the labels now
+ if (labels) {
+ var serverNode = document.createElement("menuitem");
+ serverNode.setAttribute("label", labels[folder.server.type]);
+ serverNode._folder = folder;
+ serverNode.setAttribute("generated", "true");
+ popup.appendChild(serverNode);
+ var sep = document.createElement("menuseparator");
+ sep.setAttribute("generated", "true");
+ popup.appendChild(sep);
+ }
+
+ popup.setAttribute("generated", "true");
+ node.appendChild(popup);
+ }
+
+ if (disableServers.indexOf(folder.server.key) != -1)
+ node.setAttribute("disabled", "true");
+
+ node._folder = folder;
+ let label = "";
+ if (mode == "deferred" && folder.isServer &&
+ folder.server.rootFolder == globalInboxFolder) {
+ label = this._stringBundle.get("globalInbox", [folder.prettyName]);
+ } else {
+ label = folder.prettyName;
+ }
+ node.setAttribute("label", label);
+ this._setCssSelectors(folder, node);
+ }
+ ]]></body>
+ </method>
+
+ <!--
+ - Builds a submenu with all of the recently used folders in it, to
+ - allow for easy access.
+ -->
+ <method name="_buildRecentMenu">
+ <body><![CDATA[
+ const Ci = Components.interfaces;
+
+ // Iterate through all folders in all accounts, and find 15 (_MAXRECENT)
+ // of most recently modified ones.
+ let allFolders = this.toArray(
+ this.fixIterator(this.MailServices.accounts.allFolders, Ci.nsIMsgFolder));
+
+ allFolders = allFolders.filter(f => f.canFileMessages);
+
+ let recentFolders = this.getMostRecentFolders(allFolders,
+ this._MAXRECENT,
+ "MRMTime");
+
+ // Cache the pretty names so that they do not need to be fetched
+ // _MAXRECENT^2 times later.
+ recentFolders = recentFolders.map(
+ function (f) { return { folder: f, name: f.prettyName } });
+
+ // Because we're scanning across multiple accounts, we can end up with
+ // several folders with the same name. Find those dupes.
+ let dupeNames = new Set();
+ for (let i = 0; i < recentFolders.length; i++) {
+ for (let j = i + 1; j < recentFolders.length; j++) {
+ if (recentFolders[i].name == recentFolders[j].name)
+ dupeNames.add(recentFolders[i].name);
+ }
+ }
+
+ for (let folderItem of recentFolders) {
+ // If this folder name appears multiple times in the recent list,
+ // append the server name to disambiguate.
+ // TODO:
+ // - maybe this could use verboseFolderFormat from messenger.properties
+ // instead of hardcoded " - ".
+ // - disambiguate folders with same name in same account
+ // (in different subtrees).
+ let label = folderItem.name;
+ if (dupeNames.has(label))
+ label += " - " + folderItem.folder.server.prettyName;
+
+ folderItem.label = label;
+ }
+
+ // Make sure the entries are sorted alphabetically.
+ recentFolders.sort((a, b) => this.folderNameCompare(a.label, b.label));
+
+ // Now create the Recent folder and its children
+ var menu = document.createElement("menu");
+ menu.setAttribute("label", this.getAttribute("recentLabel"));
+ menu.setAttribute("accesskey", this.getAttribute("recentAccessKey"));
+ var popup = document.createElement("menupopup");
+ popup.setAttribute("class", this.getAttribute("class"));
+ popup.setAttribute("generated", "true");
+ menu.appendChild(popup);
+
+ // Create entries for each of the recent folders.
+ for (let folderItem of recentFolders) {
+ let node = document.createElement("menuitem");
+
+ node.setAttribute("label", folderItem.label);
+ node._folder = folderItem.folder;
+
+ node.setAttribute("class", "folderMenuItem menuitem-iconic");
+ this._setCssSelectors(folderItem.folder, node);
+ node.setAttribute("generated", "true");
+ popup.appendChild(node);
+ }
+ menu.setAttribute("generated", "true");
+ this.appendChild(menu);
+ if (!recentFolders.length)
+ menu.setAttribute("disabled", "true");
+
+ var sep = document.createElement("menuseparator");
+ sep.setAttribute("generated", "true");
+ this.appendChild(sep);
+ ]]></body>
+ </method>
+
+ <!--
+ - This function adds attributes on menu/menuitems to make it easier for
+ - css to style them.
+ -
+ - @param aFolder the folder that corresponds to the menu/menuitem
+ - @param aMenuNode the actual DOM node to set attributes on
+ -->
+ <method name="_setCssSelectors">
+ <parameter name="aFolder"/>
+ <parameter name="aMenuNode"/>
+ <body><![CDATA[
+
+ // First set the SpecialFolder attribute
+ aMenuNode.setAttribute("SpecialFolder", this.getSpecialFolderString(aFolder));
+
+ // Now set the biffState
+ let biffStates = ["NewMail", "NoMail", "UnknownMail"];
+ for (let state of biffStates) {
+ if (aFolder.biffState ==
+ Components.interfaces.nsIMsgFolder["nsMsgBiffState_" + state]) {
+ aMenuNode.setAttribute("BiffState", state);
+ break;
+ }
+ }
+
+ aMenuNode.setAttribute("IsServer", aFolder.isServer);
+ aMenuNode.setAttribute("IsSecure", aFolder.server.isSecure);
+ aMenuNode.setAttribute("ServerType", aFolder.server.type);
+ aMenuNode.setAttribute("IsFeedFolder",
+ (this.FeedUtils.getFeedUrlsInFolder(aFolder) ? true : false));
+ ]]></body>
+ </method>
+
+ <!--
+ - This function returns a formatted display name for a menulist
+ - selected folder. The desired format is set as the 'displayformat'
+ - attribute of the folderpicker's <menulist>, one of:
+ - 'name' (default) - Folder
+ - 'verbose' - Folder on Account
+ - 'path' - Account/Folder/Subfolder
+ -
+ - @param aFolder the folder that corresponds to the menu/menuitem
+ - @return string display name
+ -->
+ <field name="_displayformat">null</field>
+ <method name="getDisplayName">
+ <parameter name="aFolder"/>
+ <body><![CDATA[
+ if (aFolder.isServer)
+ return aFolder.prettyName;
+
+ if (this._displayformat == "verbose")
+ return this._stringBundle.getFormattedString("verboseFolderFormat",
+ [aFolder.prettyName, aFolder.server.prettyName]);
+
+ if (this._displayformat == "path")
+ return this.FeedUtils.getFolderPrettyPath(aFolder) || aFolder.name;
+
+ return aFolder.name;
+ ]]></body>
+ </method>
+
+ <!--
+ - Makes a given folder selected.
+ -
+ - @param aFolder the folder to select (if none, then Choose Folder)
+ - @note If aFolder is not in this popup, but is instead a descendant of
+ - a member of the popup, that ancestor will be selected.
+ - @return true if any usable folder was found, otherwise false.
+ -->
+ <method name="selectFolder">
+ <parameter name="aFolder"/>
+ <body><![CDATA[
+ // Set the label of the menulist element as if aFolder had been selected.
+ function setupParent(aFolder, aMenulist, aNoFolders) {
+ let menupopup = aMenulist.menupopup;
+ if (aFolder) {
+ aMenulist.setAttribute("label", menupopup.getDisplayName(aFolder));
+ } else {
+ aMenulist.setAttribute("label", menupopup._stringBundle.getString(
+ aNoFolders ? "noFolders" :
+ (menupopup._serversOnly ? "chooseAccount" : "chooseFolder")));
+ }
+ aMenulist.setAttribute("value",
+ aFolder ? aFolder.URI : "");
+ aMenulist.setAttribute("IsServer",
+ aFolder ? aFolder.isServer : false);
+ aMenulist.setAttribute("IsSecure",
+ aFolder ? aFolder.server.isSecure : false);
+ aMenulist.setAttribute("ServerType",
+ aFolder ? aFolder.server.type : "none");
+ aMenulist.setAttribute("SpecialFolder",
+ aFolder ? menupopup.getSpecialFolderString(aFolder) : "none");
+ aMenulist.setAttribute("IsFeedFolder", Boolean(
+ aFolder && menupopup.FeedUtils.getFeedUrlsInFolder(aFolder)));
+ }
+
+ let folder;
+ if (aFolder) {
+ for (let child of this.childNodes) {
+ if (child && child._folder && !child.disabled &&
+ (child._folder.URI == aFolder.URI ||
+ (child.tagName == "menu" &&
+ child._folder.isAncestorOf(aFolder)))) {
+ if (child._folder.URI == aFolder.URI)
+ this.parentNode.selectedItem = child;
+ folder = aFolder;
+ break;
+ }
+ }
+ }
+
+ // If the caller specified a folder to select and it was not
+ // found, or if the caller didn't pass a folder (meaning a logical
+ // and valid folder wasn't determined), don't blow up but reset
+ // attributes and set a nice Choose Folder label so the user may
+ // select a valid folder per the filter for this picker. If there are
+ // no children, then no folder passed the filter; disable the menulist
+ // as there's nothing to choose from.
+ let noFolders;
+ if (!this.childElementCount)
+ {
+ this.parentNode.setAttribute("disabled", true);
+ noFolders = true;
+ }
+ else
+ {
+ this.parentNode.removeAttribute("disabled");
+ noFolders = false;
+ }
+
+ setupParent(folder, this.parentNode, noFolders);
+ return folder ? true : false;
+ ]]></body>
+ </method>
+
+ <!--
+ - Removes all menu-items for this popup, resets all fields, and
+ - removes the listener. This function is invoked when a change
+ - that affects this menu is detected by our listener.
+ -->
+ <method name="_teardown">
+ <body><![CDATA[
+ for (let i = this.childNodes.length - 1; i >= 0; i--) {
+ let child = this.childNodes[i];
+ if (child.getAttribute("generated") != "true")
+ continue;
+ if ("_teardown" in child)
+ child._teardown();
+ child.remove();
+ }
+
+ this._removeListener();
+
+ this._initialized = false;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <!--
+ - In order to improve performance, we're not going to build any of the
+ - menu until we're shown (unless we're the child of a menulist, see
+ - note in the constructor).
+ -
+ - @note _ensureInitialized can be called repeatedly without issue, so
+ - don't worry about it here.
+ -->
+ <handler event="popupshowing" phase="capturing">
+ this._ensureInitialized();
+ </handler>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/mailnews/base/content/jsTreeView.js b/mailnews/base/content/jsTreeView.js
new file mode 100644
index 000000000..abec7df4a
--- /dev/null
+++ b/mailnews/base/content/jsTreeView.js
@@ -0,0 +1,235 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains a prototype object designed to make the implementation of
+ * nsITreeViews in javascript simpler. This object requires that consumers
+ * override the _rebuild function. This function must set the _rowMap object to
+ * an array of objects fitting the following interface:
+ *
+ * readonly attribute string id - a unique identifier for the row/object
+ * readonly attribute integer level - the hierarchy level of the row
+ * attribute boolean open - whether or not this item's children are exposed
+ * string getText(aColName) - return the text to display for this row in the
+ * specified column
+ * string getProperties() - return the css-selectors
+ * attribute array children - return an array of child-objects also meeting this
+ * interface
+ */
+
+function PROTO_TREE_VIEW() {
+ this._tree = null;
+ this._rowMap = [];
+ this._persistOpenMap = [];
+}
+
+PROTO_TREE_VIEW.prototype = {
+ get rowCount() {
+ return this._rowMap.length;
+ },
+
+ /**
+ * CSS files will cue off of these. Note that we reach into the rowMap's
+ * items so that custom data-displays can define their own properties
+ */
+ getCellProperties: function jstv_getCellProperties(aRow, aCol) {
+ return this._rowMap[aRow].getProperties(aCol);
+ },
+
+ /**
+ * The actual text to display in the tree
+ */
+ getCellText: function jstv_getCellText(aRow, aCol) {
+ return this._rowMap[aRow].getText(aCol.id);
+ },
+
+ /**
+ * The jstv items take care of assigning this when building children lists
+ */
+ getLevel: function jstv_getLevel(aIndex) {
+ return this._rowMap[aIndex].level;
+ },
+
+ /**
+ * This is easy since the jstv items assigned the _parent property when making
+ * the child lists
+ */
+ getParentIndex: function jstv_getParentIndex(aIndex) {
+ return this._rowMap.indexOf(this._rowMap[aIndex]._parent);
+ },
+
+ /**
+ * This is duplicative for our normal jstv views, but custom data-displays may
+ * want to do something special here
+ */
+ getRowProperties: function jstv_getRowProperties(aRow) {
+ return this._rowMap[aRow].getProperties();
+ },
+
+ /**
+ * If an item in our list has the same level and parent as us, it's a sibling
+ */
+ hasNextSibling: function jstv_hasNextSibling(aIndex, aNextIndex) {
+ let targetLevel = this._rowMap[aIndex].level;
+ for (let i = aNextIndex + 1; i < this._rowMap.length; i++) {
+ if (this._rowMap[i].level == targetLevel)
+ return true;
+ if (this._rowMap[i].level < targetLevel)
+ return false;
+ }
+ return false;
+ },
+
+ /**
+ * If we have a child-list with at least one element, we are a container.
+ */
+ isContainer: function jstv_isContainer(aIndex) {
+ return this._rowMap[aIndex].children.length > 0;
+ },
+
+ isContainerEmpty: function jstv_isContainerEmpty(aIndex) {
+ // If the container has no children, the container is empty.
+ return !this._rowMap[aIndex].children.length;
+ },
+
+ /**
+ * Just look at the jstv item here
+ */
+ isContainerOpen: function jstv_isContainerOpen(aIndex) {
+ return this._rowMap[aIndex].open;
+ },
+
+ isEditable: function jstv_isEditable(aRow, aCol) {
+ // We don't support editing rows in the tree yet.
+ return false;
+ },
+
+ isSeparator: function jstv_isSeparator(aIndex) {
+ // There are no separators in our trees
+ return false;
+ },
+
+ isSorted: function jstv_isSorted() {
+ // We do our own customized sorting
+ return false;
+ },
+
+ setTree: function jstv_setTree(aTree) {
+ this._tree = aTree;
+ },
+
+ recursivelyAddToMap: function jstv_recursivelyAddToMap(aChild, aNewIndex) {
+ // When we add sub-children, we're going to need to increase our index
+ // for the next add item at our own level.
+ let currentCount = this._rowMap.length;
+ if (aChild.children.length && aChild.open) {
+ for (let [i, child] in Iterator(this._rowMap[aNewIndex].children)) {
+ let index = aNewIndex + i + 1;
+ this._rowMap.splice(index, 0, child);
+ aNewIndex += this.recursivelyAddToMap(child, index);
+ }
+ }
+ return this._rowMap.length - currentCount;
+ },
+
+ /**
+ * Opens or closes a container with children. The logic here is a bit hairy, so
+ * be very careful about changing anything.
+ */
+ toggleOpenState: function jstv_toggleOpenState(aIndex) {
+
+ // Ok, this is a bit tricky.
+ this._rowMap[aIndex]._open = !this._rowMap[aIndex].open;
+
+ if (!this._rowMap[aIndex].open) {
+ // We're closing the current container. Remove the children
+
+ // Note that we can't simply splice out children.length, because some of
+ // them might have children too. Find out how many items we're actually
+ // going to splice
+ let level = this._rowMap[aIndex].level;
+ let row = aIndex + 1;
+ while (row < this._rowMap.length && this._rowMap[row].level > level) {
+ row++;
+ }
+ let count = row - aIndex - 1;
+ this._rowMap.splice(aIndex + 1, count);
+
+ // Remove us from the persist map
+ let index = this._persistOpenMap.indexOf(this._rowMap[aIndex].id);
+ if (index != -1)
+ this._persistOpenMap.splice(index, 1);
+
+ // Notify the tree of changes
+ if (this._tree) {
+ this._tree.rowCountChanged(aIndex + 1, -count);
+ }
+ } else {
+ // We're opening the container. Add the children to our map
+
+ // Note that these children may have been open when we were last closed,
+ // and if they are, we also have to add those grandchildren to the map
+ let oldCount = this._rowMap.length;
+ this.recursivelyAddToMap(this._rowMap[aIndex], aIndex);
+
+ // Add this container to the persist map
+ let id = this._rowMap[aIndex].id;
+ if (this._persistOpenMap.indexOf(id) == -1)
+ this._persistOpenMap.push(id);
+
+ // Notify the tree of changes
+ if (this._tree)
+ this._tree.rowCountChanged(aIndex + 1, this._rowMap.length - oldCount);
+ }
+
+ // Invalidate the toggled row, so that the open/closed marker changes
+ if (this._tree)
+ this._tree.invalidateRow(aIndex);
+ },
+
+ // We don't implement any of these at the moment
+ canDrop: function jstv_canDrop(aIndex, aOrientation) {},
+ drop: function jstv_drop(aRow, aOrientation) {},
+ performAction: function jstv_performAction(aAction) {},
+ performActionOnCell: function jstv_performActionOnCell(aAction, aRow, aCol) {},
+ performActionOnRow: function jstv_performActionOnRow(aAction, aRow) {},
+ selectionChanged: function jstv_selectionChanged() {},
+ setCellText: function jstv_setCellText(aRow, aCol, aValue) {},
+ setCellValue: function jstv_setCellValue(aRow, aCol, aValue) {},
+ getCellValue: function jstv_getCellValue(aRow, aCol) {},
+ getColumnProperties: function jstv_getColumnProperties(aCol) { return ""; },
+ getImageSrc: function jstv_getImageSrc(aRow, aCol) {},
+ getProgressMode: function jstv_getProgressMode(aRow, aCol) {},
+ cycleCell: function jstv_cycleCell(aRow, aCol) {},
+ cycleHeader: function jstv_cycleHeader(aCol) {},
+
+ _tree: null,
+
+ /**
+ * An array of jstv items, where each item corresponds to a row in the tree
+ */
+ _rowMap: null,
+
+ /**
+ * This is a javascript map of which containers we had open, so that we can
+ * persist their state over-time. It is designed to be used as a JSON object.
+ */
+ _persistOpenMap: null,
+
+ _restoreOpenStates: function jstv__restoreOpenStates() {
+ // Note that as we iterate through here, .length may grow
+ for (let i = 0; i < this._rowMap.length; i++) {
+ if (this._persistOpenMap.indexOf(this._rowMap[i].id) != -1)
+ this.toggleOpenState(i);
+ }
+ },
+
+ QueryInterface: function QueryInterface(aIID) {
+ if (aIID.equals(Components.interfaces.nsITreeView) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+};
diff --git a/mailnews/base/content/junkCommands.js b/mailnews/base/content/junkCommands.js
new file mode 100644
index 000000000..6332d193f
--- /dev/null
+++ b/mailnews/base/content/junkCommands.js
@@ -0,0 +1,456 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ /* functions use for junk processing commands
+ *
+ * TODO: These functions make the false assumption that a view only contains
+ * a single folder. This is not true for XF saved searches.
+ *
+ * globals prerequisites used:
+ *
+ * window.MsgStatusFeedback
+ *
+ * One of:
+ * GetSelectedIndices(view) (in suite)
+ * gFolderDisplay (in mail)
+ *
+ * messenger
+ * gMessengerBundle
+ * gDBView
+ * either gMsgFolderSelected or gFolderDisplay
+ * MsgJunkMailInfo(aCheckFirstUse)
+ * SetNextMessageAfterDelete()
+ * pref
+ * msgWindow
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/MailUtils.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/*
+ * determineActionsForJunkMsgs
+ *
+ * Determines the actions that should be carried out on the messages
+ * that are being marked as junk
+ *
+ * @param aFolder
+ * the folder with messages being marked as junk
+ *
+ * @return an object with two properties: 'markRead' (boolean) indicating
+ * whether the messages should be marked as read, and 'junkTargetFolder'
+ * (nsIMsgFolder) specifying where the messages should be moved, or
+ * null if they should not be moved.
+ */
+function determineActionsForJunkMsgs(aFolder)
+{
+ var actions = { markRead: false, junkTargetFolder: null };
+ var spamSettings = aFolder.server.spamSettings;
+
+ // note we will do moves/marking as read even if the spam
+ // feature is disabled, since the user has asked to use it
+ // despite the disabling
+
+ actions.markRead = spamSettings.markAsReadOnSpam;
+ actions.junkTargetFolder = null;
+
+ // move only when the corresponding setting is activated
+ // and the currently viewed folder is not the junk folder.
+ if (spamSettings.moveOnSpam &&
+ !(aFolder.flags & Components.interfaces.nsMsgFolderFlags.Junk))
+ {
+ var spamFolderURI = spamSettings.spamFolderURI;
+ if (!spamFolderURI)
+ {
+ // XXX TODO
+ // we should use nsIPromptService to inform the user of the problem,
+ // e.g. when the junk folder was accidentally deleted.
+ dump('determineActionsForJunkMsgs: no spam folder found, not moving.');
+ }
+ else
+ actions.junkTargetFolder = MailUtils.getFolderForURI(spamFolderURI);
+ }
+
+ return actions;
+}
+
+/**
+ * performActionsOnJunkMsgs
+ *
+ * Performs required operations on a list of newly-classified junk messages
+ *
+ * @param aFolder
+ * the folder with messages being marked as junk
+ *
+ * @param aJunkMsgHdrs
+ * nsIArray containing headers (nsIMsgDBHdr) of new junk messages
+ *
+ * @param aGoodMsgHdrs
+ * nsIArray containing headers (nsIMsgDBHdr) of new good messages
+ */
+ function performActionsOnJunkMsgs(aFolder, aJunkMsgHdrs, aGoodMsgHdrs)
+{
+ if (aFolder instanceof Components.interfaces.nsIMsgImapMailFolder) // need to update IMAP custom flags
+ {
+ if (aJunkMsgHdrs.length)
+ {
+ var junkMsgKeys = new Array();
+ for (var i = 0; i < aJunkMsgHdrs.length; i++)
+ junkMsgKeys[i] = aJunkMsgHdrs.queryElementAt(i, Components.interfaces.nsIMsgDBHdr).messageKey;
+ aFolder.storeCustomKeywords(null, "Junk", "NonJunk", junkMsgKeys, junkMsgKeys.length);
+ }
+
+ if (aGoodMsgHdrs.length)
+ {
+ var goodMsgKeys = new Array();
+ for (var i = 0; i < aGoodMsgHdrs.length; i++)
+ goodMsgKeys[i] = aGoodMsgHdrs.queryElementAt(i, Components.interfaces.nsIMsgDBHdr).messageKey;
+ aFolder.storeCustomKeywords(null, "NonJunk", "Junk", goodMsgKeys, goodMsgKeys.length);
+ }
+ }
+
+ if (aJunkMsgHdrs.length)
+ {
+ var actionParams = determineActionsForJunkMsgs(aFolder);
+ if (actionParams.markRead)
+ aFolder.markMessagesRead(aJunkMsgHdrs, true);
+
+ if (actionParams.junkTargetFolder)
+ MailServices.copy
+ .CopyMessages(aFolder, aJunkMsgHdrs, actionParams.junkTargetFolder,
+ true /* isMove */, null, msgWindow, true /* allow undo */);
+ }
+}
+
+/**
+ * MessageClassifier
+ *
+ * Helper object storing the list of pending messages to process,
+ * and implementing junk processing callback
+ *
+ * @param aFolder
+ * the folder with messages to be analyzed for junk
+ * @param aTotalMessages
+ * Number of messages to process, used for progress report only
+ */
+
+function MessageClassifier(aFolder, aTotalMessages)
+{
+ this.mFolder = aFolder;
+ this.mJunkMsgHdrs = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ this.mGoodMsgHdrs = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ this.mMessages = new Object();
+ this.mMessageQueue = new Array();
+ this.mTotalMessages = aTotalMessages;
+ this.mProcessedMessages = 0;
+ this.firstMessage = true;
+ this.lastStatusTime = Date.now();
+}
+
+MessageClassifier.prototype =
+{
+ /**
+ * analyzeMessage
+ *
+ * Starts the message classification process for a message. If the message
+ * sender's address is whitelisted, the message is skipped.
+ *
+ * @param aMsgHdr
+ * The header (nsIMsgDBHdr) of the message to classify.
+ * @param aSpamSettings
+ * nsISpamSettings object with information about whitelists
+ */
+ analyzeMessage: function(aMsgHdr, aSpamSettings)
+ {
+ var junkscoreorigin = aMsgHdr.getStringProperty("junkscoreorigin");
+ if (junkscoreorigin == "user") // don't override user-set junk status
+ return;
+
+ // check whitelisting
+ if (aSpamSettings.checkWhiteList(aMsgHdr))
+ {
+ // message is ham from whitelist
+ var db = aMsgHdr.folder.msgDatabase;
+ db.setStringProperty(aMsgHdr.messageKey, "junkscore",
+ Components.interfaces.nsIJunkMailPlugin.IS_HAM_SCORE);
+ db.setStringProperty(aMsgHdr.messageKey, "junkscoreorigin", "whitelist");
+ this.mGoodMsgHdrs.appendElement(aMsgHdr, false);
+ return;
+ }
+
+ var messageURI = aMsgHdr.folder.generateMessageURI(aMsgHdr.messageKey) + "?fetchCompleteMessage=true";
+ this.mMessages[messageURI] = aMsgHdr;
+ if (this.firstMessage)
+ {
+ this.firstMessage = false;
+ MailServices.junk.classifyMessage(messageURI, msgWindow, this);
+ }
+ else
+ this.mMessageQueue.push(messageURI);
+ },
+
+ /*
+ * nsIJunkMailClassificationListener implementation
+ * onMessageClassified
+ *
+ * Callback function from nsIJunkMailPlugin with classification results
+ *
+ * @param aClassifiedMsgURI
+ * URI of classified message
+ * @param aClassification
+ * Junk classification (0: UNCLASSIFIED, 1: GOOD, 2: JUNK)
+ * @param aJunkPercent
+ * 0 - 100 indicator of junk likelihood, with 100 meaning probably junk
+ */
+ onMessageClassified: function(aClassifiedMsgURI, aClassification, aJunkPercent)
+ {
+ if (!aClassifiedMsgURI)
+ return; // ignore end of batch
+ var nsIJunkMailPlugin = Components.interfaces.nsIJunkMailPlugin;
+ var score = (aClassification == nsIJunkMailPlugin.JUNK) ?
+ nsIJunkMailPlugin.IS_SPAM_SCORE : nsIJunkMailPlugin.IS_HAM_SCORE;
+ const statusDisplayInterval = 1000; // milliseconds between status updates
+
+ // set these props via the db (instead of the message header
+ // directly) so that the nsMsgDBView knows to update the UI
+ //
+ var msgHdr = this.mMessages[aClassifiedMsgURI];
+ var db = msgHdr.folder.msgDatabase;
+ db.setStringProperty(msgHdr.messageKey, "junkscore", score);
+ db.setStringProperty(msgHdr.messageKey, "junkscoreorigin", "plugin");
+ db.setStringProperty(msgHdr.messageKey, "junkpercent", aJunkPercent);
+
+ if (aClassification == nsIJunkMailPlugin.JUNK)
+ this.mJunkMsgHdrs.appendElement(msgHdr, false);
+ else if (aClassification == nsIJunkMailPlugin.GOOD)
+ this.mGoodMsgHdrs.appendElement(msgHdr, false);
+
+ var nextMsgURI = this.mMessageQueue.shift();
+ if (nextMsgURI)
+ {
+ ++this.mProcessedMessages;
+ if (Date.now() > this.lastStatusTime + statusDisplayInterval)
+ {
+ this.lastStatusTime = Date.now();
+ var percentDone = 0;
+ if (this.mTotalMessages)
+ percentDone = Math.round(this.mProcessedMessages * 100 / this.mTotalMessages);
+ var percentStr = percentDone + "%";
+ window.MsgStatusFeedback.showStatusString(
+ document.getElementById("bundle_messenger")
+ .getFormattedString("junkAnalysisPercentComplete",
+ [percentStr]));
+ }
+
+ MailServices.junk.classifyMessage(nextMsgURI, msgWindow, this);
+ }
+ else
+ {
+ window.MsgStatusFeedback.showStatusString(
+ document.getElementById("bundle_messenger")
+ .getString("processingJunkMessages"));
+ performActionsOnJunkMsgs(this.mFolder, this.mJunkMsgHdrs, this.mGoodMsgHdrs);
+ window.MsgStatusFeedback.showStatusString("");
+ }
+ }
+}
+
+/*
+ * filterFolderForJunk
+ *
+ * Filter all messages in the current folder for junk
+ */
+function filterFolderForJunk() { processFolderForJunk(true); }
+
+/*
+ * analyzeMessagesForJunk
+ *
+ * Filter selected messages in the current folder for junk
+ */
+function analyzeMessagesForJunk() { processFolderForJunk(false); }
+
+/*
+ * processFolderForJunk
+ *
+ * Filter messages in the current folder for junk
+ *
+ * @param aAll: true to filter all messages, else filter selection
+ */
+function processFolderForJunk(aAll)
+{
+ MsgJunkMailInfo(true);
+
+ if (aAll)
+ {
+ // need to expand all threads, so we analyze everything
+ gDBView.doCommand(nsMsgViewCommandType.expandAll);
+ var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
+ var count = treeView.rowCount;
+ if (!count)
+ return;
+ }
+ else
+ {
+ // suite uses GetSelectedIndices, mail uses gFolderDisplay.selectedMessages
+ var indices = typeof GetSelectedIndices != "undefined" ?
+ GetSelectedIndices(gDBView) :
+ gFolderDisplay.selectedIndices;
+ if (!indices || !indices.length)
+ return;
+ }
+ var totalMessages = aAll ? count : indices.length;
+
+ // retrieve server and its spam settings via the header of an arbitrary message
+ for (var i = 0; i < totalMessages; i++)
+ {
+ var index = aAll ? i : indices[i];
+ try
+ {
+ var tmpMsgURI = gDBView.getURIForViewIndex(index);
+ break;
+ }
+ catch (e)
+ {
+ // dummy headers will fail, so look for another
+ continue;
+ }
+ }
+ if (!tmpMsgURI)
+ return;
+
+ var tmpMsgHdr = messenger.messageServiceFromURI(tmpMsgURI).messageURIToMsgHdr(tmpMsgURI);
+ var spamSettings = tmpMsgHdr.folder.server.spamSettings;
+
+ // create a classifier instance to classify messages in the folder.
+ var msgClassifier = new MessageClassifier(tmpMsgHdr.folder, totalMessages);
+
+ for ( i = 0; i < totalMessages; i++)
+ {
+ var index = aAll ? i : indices[i];
+ try
+ {
+ var msgURI = gDBView.getURIForViewIndex(index);
+ var msgHdr = messenger.messageServiceFromURI(msgURI).messageURIToMsgHdr(msgURI);
+ msgClassifier.analyzeMessage(msgHdr, spamSettings);
+ }
+ catch (ex)
+ {
+ // blow off errors here - dummy headers will fail
+ var msgURI = null;
+ }
+ }
+ if (msgClassifier.firstMessage) // the async plugin was not used, maybe all whitelisted?
+ performActionsOnJunkMsgs(msgClassifier.mFolder,
+ msgClassifier.mJunkMsgHdrs,
+ msgClassifier.mGoodMsgHdrs);
+}
+
+function JunkSelectedMessages(setAsJunk)
+{
+ MsgJunkMailInfo(true);
+
+ // When the user explicitly marks a message as junk, we can mark it as read,
+ // too. This is independent of the "markAsReadOnSpam" pref, which applies
+ // only to automatically-classified messages.
+ // Note that this behaviour should match the one in the back end for marking
+ // as junk via clicking the 'junk' column.
+
+ if (setAsJunk && Services.prefs.getBoolPref("mailnews.ui.junk.manualMarkAsJunkMarksRead"))
+ MarkSelectedMessagesRead(true);
+
+ gDBView.doCommand(setAsJunk ? nsMsgViewCommandType.junk
+ : nsMsgViewCommandType.unjunk);
+}
+
+/**
+ * Delete junk messages in the current folder. This provides the guarantee that
+ * the method will be synchronous if no messages are deleted.
+ *
+ * @returns The number of messages deleted.
+ */
+function deleteJunkInFolder()
+{
+ MsgJunkMailInfo(true);
+
+ // use direct folder commands if possible so we don't mess with the selection
+ let selectedFolder = gFolderDisplay.displayedFolder;
+ if ( !(selectedFolder.flags & Components.interfaces.nsMsgFolderFlags.Virtual) )
+ {
+ var junkMsgHdrs = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ var enumerator = gDBView.msgFolder.messages;
+ while (enumerator.hasMoreElements())
+ {
+ var msgHdr = enumerator.getNext().QueryInterface(Components.interfaces.nsIMsgDBHdr);
+ var junkScore = msgHdr.getStringProperty("junkscore");
+ if (junkScore == Components.interfaces.nsIJunkMailPlugin.IS_SPAM_SCORE)
+ junkMsgHdrs.appendElement(msgHdr, false);
+ }
+
+ if (junkMsgHdrs.length)
+ gDBView.msgFolder.deleteMessages(junkMsgHdrs, msgWindow, false, false, null, true);
+ return junkMsgHdrs.length;
+ }
+
+ // Folder is virtual, let the view do the work (but we lose selection)
+
+ // need to expand all threads, so we find everything
+ gDBView.doCommand(nsMsgViewCommandType.expandAll);
+
+ var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
+ var count = treeView.rowCount;
+ if (!count)
+ return 0;
+
+ var treeSelection = treeView.selection;
+
+ var clearedSelection = false;
+
+ // select the junk messages
+ var messageUri;
+ let numMessagesDeleted = 0;
+ for (var i = 0; i < count; ++i)
+ {
+ try {
+ messageUri = gDBView.getURIForViewIndex(i);
+ }
+ catch (ex) {continue;} // blow off errors for dummy rows
+ var msgHdr = messenger.messageServiceFromURI(messageUri).messageURIToMsgHdr(messageUri);
+ var junkScore = msgHdr.getStringProperty("junkscore");
+ var isJunk = (junkScore == Components.interfaces.nsIJunkMailPlugin.IS_SPAM_SCORE);
+ // if the message is junk, select it.
+ if (isJunk)
+ {
+ // only do this once
+ if (!clearedSelection)
+ {
+ // clear the current selection
+ // since we will be deleting all selected messages
+ treeSelection.clearSelection();
+ clearedSelection = true;
+ treeSelection.selectEventsSuppressed = true;
+ }
+ treeSelection.rangedSelect(i, i, true /* augment */);
+ numMessagesDeleted++;
+ }
+ }
+
+ // if we didn't clear the selection
+ // there was no junk, so bail.
+ if (!clearedSelection)
+ return 0;
+
+ treeSelection.selectEventsSuppressed = false;
+ // delete the selected messages
+ //
+ // We'll leave no selection after the delete
+ gNextMessageViewIndexAfterDelete = nsMsgViewIndex_None;
+ gDBView.doCommand(nsMsgViewCommandType.deleteMsg);
+ treeSelection.clearSelection();
+ ClearMessagePane();
+ return numMessagesDeleted;
+}
+
diff --git a/mailnews/base/content/junkLog.js b/mailnews/base/content/junkLog.js
new file mode 100644
index 000000000..148f0e507
--- /dev/null
+++ b/mailnews/base/content/junkLog.js
@@ -0,0 +1,33 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gLogView;
+var gLogFile;
+
+function onLoad()
+{
+ gLogView = document.getElementById("logView");
+ gLogView.docShell.allowJavascript = false; // for security, disable JS
+
+ gLogFile = Services.dirsvc.get("ProfD", Components.interfaces.nsIFile);
+ gLogFile.append("junklog.html");
+
+ if (gLogFile.exists())
+ {
+ // convert the file to a URL so we can load it.
+ gLogView.setAttribute("src", Services.io.newFileURI(gLogFile).spec);
+ }
+}
+
+function clearLog()
+{
+ if (gLogFile.exists())
+ {
+ gLogFile.remove(false);
+ gLogView.setAttribute("src", "about:blank"); // we don't have a log file to show
+ }
+}
diff --git a/mailnews/base/content/junkLog.xul b/mailnews/base/content/junkLog.xul
new file mode 100644
index 000000000..49e8796c3
--- /dev/null
+++ b/mailnews/base/content/junkLog.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+
+<!-- -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/junkLog.dtd">
+
+<dialog id="viewLogWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ title="&adaptiveJunkLog.title;"
+ windowtype="mailnews:junklog"
+ buttons="accept"
+ buttonlabelaccept="&closeLog.label;"
+ buttonaccesskeyaccept="&closeLog.accesskey;"
+ ondialogaccept="window.close();"
+ persist="screenX screenY width height"
+ style="width: 40em; height: 25em;">
+
+ <script type="application/javascript" src="chrome://messenger/content/junkLog.js"/>
+
+ <vbox flex="1">
+ <hbox>
+ <label value="&adaptiveJunkLogInfo.label;"/>
+ <spacer flex="1"/>
+ <button label="&clearLog.label;"
+ accesskey="&clearLog.accesskey;"
+ oncommand="clearLog();"/>
+ </hbox>
+ <separator class="thin"/>
+ <hbox flex="1">
+ <browser id="logView"
+ class="inset"
+ type="content"
+ disablehistory="true"
+ disablesecurity="true"
+ src="about:blank"
+ autofind="false"
+ flex="1"/>
+ </hbox>
+ </vbox>
+</dialog>
diff --git a/mailnews/base/content/junkMailInfo.xul b/mailnews/base/content/junkMailInfo.xul
new file mode 100644
index 000000000..f4658f1c2
--- /dev/null
+++ b/mailnews/base/content/junkMailInfo.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % junkMailInfoDTD SYSTEM "chrome://messenger/locale/junkMailInfo.dtd" >
+%junkMailInfoDTD;
+]>
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/primaryToolbar.css" type="text/css"?>
+
+<dialog id="junkMailInfo"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&window.title;"
+ windowtype="mailnews:junkmailinfo"
+ buttons="accept">
+
+ <vbox flex="1" width="&window.width;">
+ <spacer/>
+ <description>&info1a.label;<image align="center" id="junkIcon"/>&info1b.label;</description>
+ <spacer/>
+ <description>&info2.label;</description>
+ <spacer/>
+ <description>&info3.label;</description>
+#ifndef MOZ_THUNDERBIRD
+ <spacer/>
+ <description>&info4.label;</description>
+#endif
+ </vbox>
+</dialog>
diff --git a/mailnews/base/content/markByDate.js b/mailnews/base/content/markByDate.js
new file mode 100644
index 000000000..9a35f64a5
--- /dev/null
+++ b/mailnews/base/content/markByDate.js
@@ -0,0 +1,133 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var MILLISECONDS_PER_HOUR = 60 * 60 * 1000;
+var MICROSECONDS_PER_DAY = 1000 * MILLISECONDS_PER_HOUR * 24;
+
+function onLoad()
+{
+ var upperDateBox = document.getElementById("upperDate");
+ // focus the upper bound control - this is where we expect most users to enter
+ // a date
+ upperDateBox.focus();
+
+ // and give it an initial date - "yesterday"
+ var initialDate = new Date();
+ initialDate.setHours( 0 );
+ initialDate.setTime( initialDate.getTime() - MILLISECONDS_PER_HOUR );
+ // note that this is sufficient - though it is at the end of the previous day,
+ // we convert it to a date string, and then the time part is truncated
+ upperDateBox.value = convertDateToString( initialDate );
+ upperDateBox.select(); // allows to start overwriting immediately
+}
+
+function onAccept()
+{
+ // get the times as entered by the user
+ var lowerDateString = document.getElementById( "lowerDate" ).value;
+ // the fallback for the lower bound, if not entered, is the "beginning of
+ // time" (1970-01-01), which actually is simply 0 :)
+ var prLower = lowerDateString ? convertStringToPRTime( lowerDateString ) : 0;
+
+ var upperDateString = document.getElementById( "upperDate" ).value;
+ var prUpper;
+ if ( upperDateString == "" )
+ {
+ // for the upper bound, the fallback is "today".
+ var dateThisMorning = new Date();
+ dateThisMorning.setMilliseconds( 0 );
+ dateThisMorning.setSeconds( 0 );
+ dateThisMorning.setMinutes( 0 );
+ dateThisMorning.setHours( 0 );
+ // Javascript time is in milliseconds, PRTime is in microseconds
+ prUpper = dateThisMorning.getTime() * 1000;
+ }
+ else
+ prUpper = convertStringToPRTime( upperDateString );
+
+ // for the upper date, we have to do a correction:
+ // if the user enters a date, then she means (hopefully) that all messages sent
+ // at this day should be marked, too, but the PRTime calculated from this would
+ // point to the beginning of the day. So we need to increment it by
+ // [number of micro seconds per day]. This will denote the first microsecond of
+ // the next day then, which is later used as exclusive boundary
+ prUpper += MICROSECONDS_PER_DAY;
+
+ markInDatabase( prLower, prUpper );
+
+ return true; // allow closing
+}
+
+/** marks all headers in the database, whose time is between the two
+ given times, as read.
+ @param lower
+ PRTime for the lower bound - this boundary is inclusive
+ @param upper
+ PRTime for the upper bound - this boundary is exclusive
+*/
+function markInDatabase( lower, upper )
+{
+ var messageFolder;
+ var messageDatabase;
+ // extract the database
+ if ( window.arguments && window.arguments[0] )
+ {
+ messageFolder = window.arguments[0];
+ messageDatabase = messageFolder.msgDatabase;
+ }
+
+ if ( !messageDatabase )
+ {
+ dump( "markByDate::markInDatabase: there /is/ no database to operate on!\n" );
+ return;
+ }
+
+ // the headers which are going to be marked
+ var headers = Components.classes["@mozilla.org/array;1"].createInstance( Components.interfaces.nsIMutableArray );
+ var searchSession = Components.classes["@mozilla.org/messenger/searchSession;1"].createInstance( Components.interfaces.nsIMsgSearchSession );
+ var searchTerms = Components.classes["@mozilla.org/array;1"].createInstance( Components.interfaces.nsIMutableArray );
+ searchSession.addScopeTerm( Components.interfaces.nsMsgSearchScope.offlineMail, messageFolder );
+
+ const nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
+ const nsMsgSearchOp = Components.interfaces.nsMsgSearchOp;
+
+ var searchTerm = searchSession.createTerm();
+ searchTerm.attrib = nsMsgSearchAttrib.Date;
+ searchTerm.op = nsMsgSearchOp.IsBefore;
+ var value = searchTerm.value;
+ value.attrib = nsMsgSearchAttrib.Date;
+ value.date = upper;
+ searchTerm.value = value;
+ searchTerms.appendElement( searchTerm, false );
+
+ if ( lower )
+ {
+ searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = true;
+ searchTerm.attrib = nsMsgSearchAttrib.Date;
+ searchTerm.op = nsMsgSearchOp.IsAfter;
+ value = searchTerm.value;
+ value.attrib = nsMsgSearchAttrib.Date;
+ value.date = lower;
+ searchTerm.value = value;
+ searchTerms.appendElement( searchTerm, false );
+ }
+
+ var filterEnumerator = messageDatabase.getFilterEnumerator( searchTerms );
+
+ if ( filterEnumerator )
+ {
+ var keepGoing;
+ var numMatches = {};
+ do
+ {
+ keepGoing = messageDatabase.nextMatchingHdrs(filterEnumerator, 0, 0, headers, numMatches);
+ }
+ while ( keepGoing );
+ }
+
+ if ( headers.length )
+ messageFolder.markMessagesRead( headers, true );
+}
diff --git a/mailnews/base/content/markByDate.xul b/mailnews/base/content/markByDate.xul
new file mode 100644
index 000000000..cf472fa33
--- /dev/null
+++ b/mailnews/base/content/markByDate.xul
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/markByDate.dtd">
+
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&messageMarkByDate.label;"
+ buttons="accept,cancel"
+ onload="onLoad();"
+ ondialogaccept="return onAccept();">
+
+ <script type="application/javascript" src="chrome://messenger/content/markByDate.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/dateFormat.js"/>
+
+ <hbox align="center" pack="end">
+ <label control="lowerDate"
+ value="&markByDateLower.label;"
+ accesskey="&markByDateLower.accesskey;"/>
+ <textbox size="11" id="lowerDate"/>
+ </hbox>
+ <hbox align="center" pack="end">
+ <label control="upperDate"
+ value="&markByDateUpper.label;"
+ accesskey="&markByDateUpper.accesskey;"/>
+ <textbox size="11" id="upperDate"/>
+ </hbox>
+
+</dialog>
diff --git a/mailnews/base/content/msgAccountCentral.js b/mailnews/base/content/msgAccountCentral.js
new file mode 100644
index 000000000..de5543dff
--- /dev/null
+++ b/mailnews/base/content/msgAccountCentral.js
@@ -0,0 +1,341 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var selectedServer = null;
+
+function OnInit()
+{
+ // Set the header for the page.
+ // Title containts the brand name of the application and the account
+ // type (mail/news) and the name of the account
+ try {
+ let msgFolder = null;
+ let title;
+
+ // Get the brand name
+ let brandName = document.getElementById("bundle_brand")
+ .getString("brandShortName");
+ let messengerBundle = document.getElementById("bundle_messenger");
+
+ selectedServer = GetSelectedServer();
+ if (selectedServer) {
+ // Get the account type
+ let serverType = selectedServer.type;
+ let acctType;
+ if (serverType == "nntp")
+ acctType = messengerBundle.getString("newsAcctType");
+ else if (serverType == "rss")
+ acctType = messengerBundle.getString("feedsAcctType");
+ else
+ acctType = messengerBundle.getString("mailAcctType");
+
+ // Get the account name
+ msgFolder = GetSelectedMsgFolder();
+ ArrangeAccountCentralItems(selectedServer, msgFolder);
+
+ let acctName = msgFolder.prettyName;
+ // Display and collapse items presented to the user based on account type
+ title = messengerBundle.getFormattedString("acctCentralTitleFormat",
+ [brandName, acctType, acctName]);
+ } else {
+ // If there is no selectedServer, we are in a brand new profile with
+ // no accounts - show the create account rows.
+ title = brandName;
+ SetItemDisplay("AccountsHeader", true);
+ SetItemDisplay("CreateAccount", true);
+ SetItemDisplay("CreateAccounts", true);
+ }
+
+ // Set the title for the document
+ document.getElementById("AccountCentralTitle").setAttribute("value", title);
+ }
+ catch(ex) {
+ Components.utils.reportError("Error getting selected account: " + ex + "\n");
+ }
+}
+
+// Show items in the AccountCentral page depending on the capabilities
+// of the given account
+function ArrangeAccountCentralItems(server, msgFolder)
+{
+ let exceptions = [];
+ let protocolInfo = null;
+ try {
+ protocolInfo = server.protocolInfo;
+ } catch (e) {
+ exceptions.push(e);
+ }
+
+ // Is this a RSS account?
+ let displayRssHeader = server && server.type == 'rss';
+
+ /***** Email header and items : Begin *****/
+
+ // Read Messages
+ let canGetMessages = false;
+ try {
+ canGetMessages = protocolInfo && protocolInfo.canGetMessages;
+ SetItemDisplay("ReadMessages", canGetMessages && !displayRssHeader);
+ } catch (e) { exceptions.push(e); }
+
+ // Compose Messages link
+ let showComposeMsgLink = false;
+ try {
+ showComposeMsgLink = protocolInfo && protocolInfo.showComposeMsgLink;
+ SetItemDisplay("ComposeMessage", showComposeMsgLink);
+ } catch (e) { exceptions.push(e); }
+
+ // Junk mail settings (false, until ready for prime time)
+ let canControlJunkEmail = false
+ try {
+ canControlJunkEmail = false && protocolInfo &&
+ protocolInfo.canGetIncomingMessages &&
+ protocolInfo.canGetMessages;
+ SetItemDisplay("JunkSettingsMail", canControlJunkEmail);
+ } catch (e) { exceptions.push(e); }
+
+ // Display Email header, only if any of the items are displayed
+ let displayEmailHeader = !displayRssHeader &&
+ (canGetMessages || showComposeMsgLink ||
+ canControlJunkEmail);
+ SetItemDisplay("EmailHeader", displayEmailHeader);
+
+ /***** Email header and items : End *****/
+
+ /***** News header and items : Begin *****/
+
+ // Subscribe to Newsgroups
+ let canSubscribe = false;
+ try {
+ canSubscribe = msgFolder && msgFolder.canSubscribe &&
+ protocolInfo && !protocolInfo.canGetMessages;
+ SetItemDisplay("SubscribeNewsgroups", canSubscribe);
+ } catch (e) { exceptions.push(e); }
+
+ // Junk news settings (false, until ready for prime time)
+ let canControlJunkNews = false;
+ try {
+ canControlJunkNews = false && protocolInfo &&
+ protocolInfo.canGetIncomingMessages &&
+ !protocolInfo.canGetMessages;
+ SetItemDisplay("JunkSettingsNews", canControlJunkNews);
+ } catch (e) { exceptions.push(e); }
+
+ // Display News header, only if any of the items are displayed
+ let displayNewsHeader = canSubscribe || canControlJunkNews;
+ SetItemDisplay("NewsHeader", displayNewsHeader);
+
+ /***** News header and items : End *****/
+
+ /***** RSS header and items : Begin *****/
+
+ // Display RSS header, only if this is RSS account
+ SetItemDisplay("rssHeader", displayRssHeader);
+
+ // Subscribe to RSS Feeds
+ SetItemDisplay("SubscribeRSS", displayRssHeader);
+
+ /***** RSS header and items : End *****/
+
+ // If either of above sections exists, show section separators
+ SetItemDisplay("MessagesSection",
+ displayNewsHeader || displayEmailHeader || displayRssHeader);
+
+ /***** Accounts : Begin *****/
+
+ // Account Settings if a server is found
+ let canShowAccountSettings = server != null;
+ SetItemDisplay("AccountSettings", canShowAccountSettings);
+
+ // Show New Mail Account Wizard if not prohibited by pref
+ let canShowCreateAccount = false;
+ try {
+ canShowCreateAccount = !Services.prefs
+ .prefIsLocked("mail.disable_new_account_addition");
+ SetItemDisplay("CreateAccount", canShowCreateAccount);
+ SetItemDisplay("CreateAccounts", canShowCreateAccount);
+ } catch (e) { exceptions.push(e); }
+
+ // Display Accounts header, only if any of the items are displayed
+ let displayAccountsHeader = canShowAccountSettings || canShowCreateAccount;
+ SetItemDisplay("AccountsHeader", canShowCreateAccount);
+
+ /***** Accounts : End *****/
+
+ /***** Advanced Features header and items : Begin *****/
+
+ // Search Messages
+ let canSearchMessages = false;
+ try {
+ canSearchMessages = server && server.canSearchMessages;
+ SetItemDisplay("SearchMessages", canSearchMessages);
+ } catch (e) { exceptions.push(e); }
+
+ // Create Filters
+ let canHaveFilters = false;
+ try {
+ canHaveFilters = server && server.canHaveFilters;
+ SetItemDisplay("CreateFilters", canHaveFilters);
+ } catch (e) { exceptions.push(e); }
+
+ // Subscribe to IMAP Folders
+ let canSubscribeImapFolders = false;
+ try {
+ canSubscribeImapFolders = msgFolder && msgFolder.canSubscribe &&
+ protocolInfo && protocolInfo.canGetMessages;
+ SetItemDisplay("SubscribeImapFolders", canSubscribeImapFolders);
+ } catch (e) { exceptions.push(e); }
+
+ // Offline Settings
+ let supportsOffline = false;
+ try {
+ supportsOffline = server && server.offlineSupportLevel != 0;
+ SetItemDisplay("OfflineSettings", supportsOffline);
+ } catch (e) { exceptions.push(e); }
+
+ // Display Adv Features header, only if any of the items are displayed
+ let displayAdvFeatures = canSearchMessages || canHaveFilters ||
+ canSubscribeImapFolders|| supportsOffline;
+ SetItemDisplay("AdvancedFeaturesHeader", displayAdvFeatures);
+
+ /***** Advanced Featuers header and items : End *****/
+
+ // If either of above features exist, show section separators
+ SetItemDisplay("AccountsSection", displayAdvFeatures);
+
+ while (exceptions.length) {
+ Components.utils.reportError("Error in setting AccountCentral Items: "
+ + exceptions.pop() + "\n");
+ }
+}
+
+// Show the item if the item feature is supported
+function SetItemDisplay(elemId, displayThisItem)
+{
+ if (displayThisItem) {
+ let elem = document.getElementById(elemId);
+ if (elem)
+ elem.setAttribute("collapsed", false);
+
+ let elemSpacer = document.getElementById(elemId + ".spacer");
+ if (elemSpacer)
+ elemSpacer.setAttribute("collapsed", false);
+ }
+}
+
+// From the current folder tree, return the selected server or null
+function GetSelectedServer()
+{
+ let currentFolder = GetSelectedMsgFolder();
+ return currentFolder ? currentFolder.server : null;
+}
+
+// From the current folder tree, return the selected folder,
+// the root folder of default account or null
+function GetSelectedMsgFolder()
+{
+ return window.parent.GetSelectedMsgFolders()[0] ||
+ window.parent.GetDefaultAccountRootFolder();
+}
+
+/**
+ * Open Inbox for selected server.
+ * If needed, open the twisty and select Inbox.
+ */
+function ReadMessages()
+{
+ if (!selectedServer)
+ return;
+ try {
+ window.parent.OpenInboxForServer(selectedServer);
+ }
+ catch(ex) {
+ Components.utils
+ .reportError("Error opening Inbox for server: " + ex + "\n");
+ }
+}
+
+// Trigger composer for a new message
+function ComposeAMessage(event)
+{
+ // Pass event to allow holding Shift key for toggling HTML vs. plaintext format
+ window.parent.MsgNewMessage(event);
+}
+
+/**
+ * Open AccountManager to view settings for a given account
+ * @param selectPage the xul file name for the viewing page,
+ * null for the account main page, other pages are
+ * 'am-server.xul', 'am-copies.xul', 'am-offline.xul',
+ * 'am-addressing.xul', 'am-smtp.xul'
+ */
+function ViewSettings(selectPage)
+{
+ window.parent.MsgAccountManager(selectPage);
+}
+
+// Open AccountWizard to create an account
+function CreateNewAccount()
+{
+ window.parent.msgOpenAccountWizard();
+}
+
+function CreateNewAccountTB(type)
+{
+ if (type == "mail") {
+ if (Services.prefs.getBoolPref("mail.provider.enabled"))
+ NewMailAccountProvisioner(null);
+ else
+ AddMailAccount();
+ return;
+ }
+
+ if (type == "feeds") {
+ AddFeedAccount();
+ return;
+ }
+
+ window.parent.msgOpenAccountWizard(
+ function(state) {
+ let win = getMostRecentMailWindow();
+ if (state && win && win.gFolderTreeView && this.gCurrentAccount) {
+ win.gFolderTreeView.selectFolder(
+ this.gCurrentAccount.incomingServer.rootMsgFolder);
+ }
+ },
+ type);
+}
+
+// Bring up search interface for selected account
+function SearchMessages()
+{
+ window.parent.MsgSearchMessages();
+}
+
+// Open filters window
+function CreateMsgFilters()
+{
+ window.parent.MsgFilters(null, null);
+}
+
+// Open Subscribe dialog
+function Subscribe()
+{
+ if (!selectedServer)
+ return;
+ if (selectedServer.type == 'rss')
+ window.parent.openSubscriptionsDialog(selectedServer.rootFolder);
+ else
+ window.parent.MsgSubscribe();
+}
+
+// Open junk mail settings dialog
+function JunkSettings()
+{
+ // TODO: function does not exist yet, will throw an exception if exposed
+ window.parent.MsgJunkMail();
+}
diff --git a/mailnews/base/content/msgAccountCentral.xul b/mailnews/base/content/msgAccountCentral.xul
new file mode 100644
index 000000000..c1a43b035
--- /dev/null
+++ b/mailnews/base/content/msgAccountCentral.xul
@@ -0,0 +1,246 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountCentral.css" type="text/css"?>
+
+<!DOCTYPE page [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % acctCentralDTD SYSTEM "chrome://messenger/locale/msgAccountCentral.dtd">
+ %acctCentralDTD;
+]>
+
+<page
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="OnInit();">
+
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/commandglue.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/mailWindowOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/mailWindow.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/msgMail3PaneWindow.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/msgAccountCentral.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/mailCore.js"/>
+
+ <grid id="acctCentralGrid" flex="1" style="overflow: auto;">
+ <columns id="acctCentralColumns">
+ <column flex="40" id="acctCentralActionsColumn"/>
+ <column flex="60" id="acctCentralHelpDataColumn"/>
+ </columns>
+
+ <rows id="acctCentralRows">
+ <row id="acctCentralHeaderRow">
+ <label id="AccountCentralTitle"/>
+ </row>
+ <spacer id="acctCentralHeader.spacer" flex="2"/>
+
+ <row id="EmailHeader" class="acctCentralTitleRow" collapsed="true">
+ <hbox class="acctCentralRowTitleBox">
+ <description>&emailSectionHdr.label;</description>
+ </hbox>
+ </row>
+
+ <spacer id="ReadMessages.spacer" flex="1" collapsed="true"/>
+ <row id="ReadMessages" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&readMsgsLink.label;"
+ chromedir="&locale.dir;"
+ onclick="ReadMessages();"/>
+ </hbox>
+ </row>
+
+ <spacer id="ComposeMessage.spacer" flex="1" collapsed="true"/>
+ <row id="ComposeMessage" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&composeMsgLink.label;"
+ chromedir="&locale.dir;"
+ onclick="ComposeAMessage(event);"/>
+ </hbox>
+ </row>
+
+ <spacer id="JunkSettingsMail.spacer" flex="1" collapsed="true"/>
+ <row id="JunkSettingsMail" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&junkSettings.label;"
+ onclick="JunkSettings();"/>
+ </hbox>
+ </row>
+
+ <spacer id="NewsHeader.spacer" flex="1" collapsed="true"/>
+ <row id="NewsHeader" class="acctCentralTitleRow" collapsed="true">
+ <hbox class="acctCentralRowTitleBox">
+ <description>&newsSectionHdr.label;</description>
+ </hbox>
+ </row>
+
+ <spacer id="SubscribeNewsgroups.spacer" flex="1" collapsed="true"/>
+ <row id="SubscribeNewsgroups" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&subscribeNewsLink.label;"
+ chromedir="&locale.dir;"
+ onclick="Subscribe();"/>
+ </hbox>
+ </row>
+
+ <spacer id="JunkSettingsNews.spacer" flex="1" collapsed="true"/>
+ <row id="JunkSettingsNews" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&junkSettings.label;"
+ onclick="JunkSettings();"/>
+ </hbox>
+ </row>
+
+ <spacer id="rssHeader.spacer" flex="1" collapsed="true"/>
+ <row id="rssHeader" class="acctCentralTitleRow" collapsed="true">
+ <hbox class="acctCentralRowTitleBox">
+ <description>&feedsSectionHdr.label;</description>
+ </hbox>
+ </row>
+
+ <spacer id="SubscribeRSS.spacer" flex="1" collapsed="true"/>
+ <row id="SubscribeRSS" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&subscribeFeeds.label;"
+ chromedir="&locale.dir;"
+ onclick="Subscribe();"/>
+ </hbox>
+ </row>
+
+ <spacer id="MessagesSection.spacer" class="big" flex="5" collapsed="true"/>
+
+ <row id="AccountsHeader" class="acctCentralTitleRow" collapsed="true">
+ <hbox class="acctCentralRowTitleBox">
+ <description>&accountsSectionHdr.label;</description>
+ </hbox>
+ </row>
+
+ <spacer id="AccountSettings.spacer" flex="1" collapsed="true"/>
+ <row id="AccountSettings" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&settingsLink.label;"
+ chromedir="&locale.dir;"
+ onclick="ViewSettings(null);"/>
+ </hbox>
+ </row>
+
+ <spacer id="CreateAccount.spacer" flex="1" collapsed="true"/>
+ <row id="CreateAccount" class="acctCentralRow" collapsed="true">
+ <hbox>
+#if defined(MOZ_THUNDERBIRD) && defined(HYPE_ICEDOVE)
+ <label class="acctCentralText"
+ value="&newAcct.label;"
+ chromedir="&locale.dir;"/>
+#else
+ <label class="acctCentralText acctCentralLinkText"
+ value="&newAcctLink.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateNewAccount();"/>
+#endif
+ </hbox>
+ </row>
+#if defined(MOZ_THUNDERBIRD) && defined(HYPE_ICEDOVE)
+ <row id="CreateAccounts" class="acctCentralRow" collapsed="true">
+ <vbox id="CreateAccountsList">
+ <label id="CreateAccountMail"
+ class="acctCentralNewAcctText acctCentralLinkText"
+ value="&emailSectionHdr.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateNewAccountTB('mail');"/>
+ <label id="CreateAccountNewsgroups"
+ class="acctCentralNewAcctText acctCentralLinkText"
+ value="&newsSectionHdr.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateNewAccountTB('newsgroups');"/>
+ <label id="CreateAccountRSS"
+ class="acctCentralNewAcctText acctCentralLinkText"
+ value="&feedsSectionHdr.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateNewAccountTB('feeds');"/>
+#ifdef HAVE_MOVEMAIL
+ <label id="CreateAccountMovemail"
+ class="acctCentralNewAcctText acctCentralLinkText"
+ value="&movemail.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateNewAccountTB('movemail');"/>
+#endif
+ </vbox>
+ </row>
+#endif
+
+ <spacer id="AccountsSection.spacer" class="big" flex="5" collapsed="true"/>
+
+ <row id="AdvancedFeaturesHeader" class="acctCentralTitleRow" collapsed="true">
+ <hbox class="acctCentralRowTitleBox">
+ <description>&advFeaturesSectionHdr.label;</description>
+ </hbox>
+ </row>
+
+ <spacer id="SearchMessages.spacer" flex="1" collapsed="true"/>
+ <row id="SearchMessages" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&searchMsgsLink.label;"
+ chromedir="&locale.dir;"
+ onclick="SearchMessages();"/>
+ </hbox>
+ </row>
+
+ <spacer id="CreateFilters.spacer" flex="1" collapsed="true"/>
+ <row id="CreateFilters" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&filtersLink.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateMsgFilters();"/>
+ </hbox>
+ </row>
+
+ <spacer id="SubscribeImapFolders.spacer" flex="1" collapsed="true"/>
+ <row id="SubscribeImapFolders" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&subscribeImapFolders.label;"
+ chromedir="&locale.dir;"
+ onclick="Subscribe();"/>
+ </hbox>
+ </row>
+
+ <spacer id="OfflineSettings.spacer" flex="1" collapsed="true"/>
+ <row id="OfflineSettings" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&offlineLink.label;"
+ chromedir="&locale.dir;"
+ onclick="ViewSettings('am-offline.xul');"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+</page>
diff --git a/mailnews/base/content/msgFolderPickerOverlay.js b/mailnews/base/content/msgFolderPickerOverlay.js
new file mode 100644
index 000000000..35b1e86cf
--- /dev/null
+++ b/mailnews/base/content/msgFolderPickerOverlay.js
@@ -0,0 +1,99 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/MailUtils.js");
+
+var gMessengerBundle;
+
+// call this from dialog onload() to set the menu item to the correct value
+function MsgFolderPickerOnLoad(pickerID)
+{
+ var uri = null;
+ try {
+ uri = window.arguments[0].preselectedURI;
+ }
+ catch (ex) {
+ uri = null;
+ }
+
+ if (uri) {
+ //dump("on loading, set titled button to " + uri + "\n");
+
+ // verify that the value we are attempting to
+ // pre-flight the menu with is valid for this
+ // picker type
+ var msgfolder = MailUtils.getFolderForURI(uri, true);
+ if (!msgfolder) return;
+
+ var verifyFunction = null;
+
+ switch (pickerID) {
+ case "msgNewFolderPicker":
+ verifyFunction = msgfolder.canCreateSubfolders;
+ break;
+ case "msgRenameFolderPicker":
+ verifyFunction = msgfolder.canRename;
+ break;
+ default:
+ verifyFunction = msgfolder.canFileMessages;
+ break;
+ }
+
+ if (verifyFunction) {
+ SetFolderPicker(uri,pickerID);
+ }
+ }
+}
+
+function PickedMsgFolder(selection,pickerID)
+{
+ var selectedUri = selection.getAttribute('id');
+ SetFolderPicker(selectedUri,pickerID);
+}
+
+function SetFolderPickerElement(uri, picker)
+{
+ var msgfolder = MailUtils.getFolderForURI(uri, true);
+
+ if (!msgfolder)
+ return;
+
+ var selectedValue = null;
+ var serverName;
+
+ if (msgfolder.isServer)
+ selectedValue = msgfolder.name;
+ else {
+ if (msgfolder.server)
+ serverName = msgfolder.server.prettyName;
+ else {
+ dump("Can't find server for " + uri + "\n");
+ serverName = "???";
+ }
+
+ switch (picker.id) {
+ case "runFiltersFolder":
+ selectedValue = msgfolder.name;
+ break;
+ case "msgTrashFolderPicker":
+ selectedValue = msgfolder.name;
+ break;
+ default:
+ if (!gMessengerBundle)
+ gMessengerBundle = document.getElementById("bundle_messenger");
+ selectedValue = gMessengerBundle.getFormattedString("verboseFolderFormat",
+ [msgfolder.name, serverName]);
+ break;
+ }
+ }
+
+ picker.setAttribute("label",selectedValue);
+ picker.setAttribute("uri",uri);
+}
+
+function SetFolderPicker(uri,pickerID)
+{
+ SetFolderPickerElement(uri, document.getElementById(pickerID));
+}
diff --git a/mailnews/base/content/msgPrintEngine.js b/mailnews/base/content/msgPrintEngine.js
new file mode 100644
index 000000000..4dc01ca6d
--- /dev/null
+++ b/mailnews/base/content/msgPrintEngine.js
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This is where functions related to the print engine are kept */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/* globals for a particular window */
+var printEngineContractID = "@mozilla.org/messenger/msgPrintEngine;1";
+var printEngineWindow;
+var printEngine;
+var printSettings = null;
+var printOpener = null;
+
+var kMsgBundle = "chrome://messenger/locale/messenger.properties";
+
+/* Functions related to startup */
+function OnLoadPrintEngine()
+{
+ PrintEngineCreateGlobals();
+ InitPrintEngineWindow();
+ printEngine.startPrintOperation(printSettings);
+}
+
+function PrintEngineCreateGlobals()
+{
+ /* get the print engine instance */
+ printEngine = Components.classes[printEngineContractID].createInstance();
+ printEngine = printEngine.QueryInterface(Components.interfaces.nsIMsgPrintEngine);
+ printSettings = PrintUtils.getPrintSettings();
+ if (printSettings) {
+ printSettings.isCancelled = false;
+ }
+}
+
+var PrintPreviewListener = {
+ getPrintPreviewBrowser: function () {
+ var browser = document.getElementById("ppBrowser");
+ if (!browser) {
+ browser = document.createElement("browser");
+ browser.setAttribute("id", "ppBrowser");
+ browser.setAttribute("flex", "1");
+ browser.setAttribute("disablehistory", "true");
+ browser.setAttribute("disablesecurity", "true");
+ browser.setAttribute("type", "content");
+ document.documentElement.appendChild(browser);
+ }
+ return browser;
+ },
+ getSourceBrowser: function () {
+ return document.getElementById("content");
+ },
+ getNavToolbox: function () {
+ return document.getElementById("content");
+ },
+ onEnter: function () {
+ setPPTitle(document.getElementById("content").contentDocument.title);
+ document.getElementById("content").collapsed = true;
+ printEngine.showWindow(true);
+ },
+ onExit: function () {
+ window.close();
+ }
+};
+
+function getBundle(aURI)
+{
+ if (!aURI)
+ return null;
+
+ var bundle = null;
+ try
+ {
+ bundle = Services.strings.createBundle(aURI);
+ }
+ catch (ex)
+ {
+ bundle = null;
+ debug("Exception getting bundle " + aURI + ": " + ex);
+ }
+
+ return bundle;
+}
+
+function setPPTitle(aTitle)
+{
+ var title = aTitle;
+ try {
+ var gBrandBundle = document.getElementById("bundle_brand");
+ if (gBrandBundle) {
+ var msgBundle = this.getBundle(kMsgBundle);
+ if (msgBundle) {
+ var brandStr = gBrandBundle.getString("brandShortName")
+ var array = [title, brandStr];
+ title = msgBundle.formatStringFromName("PreviewTitle", array, array.length);
+ }
+ }
+ } catch (e) {}
+ document.title = title;
+}
+
+// Pref listener constants
+var gStartupPPObserver =
+{
+ observe: function(subject, topic, prefName)
+ {
+ PrintUtils.printPreview(PrintPreviewListener);
+ }
+};
+
+function ReplaceWithSelection()
+{
+ if (!printOpener.content)
+ return;
+
+ var selection = printOpener.content.getSelection();
+
+ if ( selection != "" ) {
+ var range = selection.getRangeAt( 0 );
+ var contents = range.cloneContents();
+
+ var aBody = window.content.document.querySelector( "body" );
+
+ /* Replace the content of <body> with the users' selection. */
+ if ( aBody ) {
+ aBody.innerHTML = "";
+ aBody.appendChild( contents );
+ }
+ }
+}
+
+function InitPrintEngineWindow()
+{
+ /* Store the current opener for later access in ReplaceWithSelection() */
+ printOpener = opener;
+
+ /* Register the event listener to be able to replace the document
+ * content with the user selection when loading is finished.
+ */
+ document.getElementById("content").addEventListener("load", ReplaceWithSelection, true);
+
+ /* Tell the nsIPrintEngine object what window is rendering the email */
+ printEngine.setWindow(window);
+
+ /* hide the printEngine window. see bug #73995 */
+
+ /* See if we got arguments.
+ * Window was opened via window.openDialog. Copy argument
+ * and perform compose initialization
+ */
+ if ( window.arguments && window.arguments[0] != null ) {
+ var numSelected = window.arguments[0];
+ var uriArray = window.arguments[1];
+ var statusFeedback = window.arguments[2];
+
+ if (window.arguments[3]) {
+ printEngine.doPrintPreview = window.arguments[3];
+ } else {
+ printEngine.doPrintPreview = false;
+ }
+ printEngine.showWindow(false);
+
+ if (window.arguments.length > 4) {
+ printEngine.setMsgType(window.arguments[4]);
+ } else {
+ printEngine.setMsgType(Components.interfaces.nsIMsgPrintEngine.MNAB_START);
+ }
+
+ if (window.arguments.length > 5) {
+ printEngine.setParentWindow(window.arguments[5]);
+ } else {
+ printEngine.setParentWindow(null);
+ }
+
+ printEngine.setStatusFeedback(statusFeedback);
+ printEngine.setStartupPPObserver(gStartupPPObserver);
+
+ if (numSelected > 0) {
+ printEngine.setPrintURICount(numSelected);
+ for (var i = 0; i < numSelected; i++) {
+ printEngine.addPrintURI(uriArray[i]);
+ //dump(uriArray[i] + "\n");
+ }
+ }
+ }
+}
+
+function ClearPrintEnginePane()
+{
+ if (window.frames["content"].location.href != "about:blank")
+ window.frames["content"].location.href = "about:blank";
+}
+
+function StopUrls()
+{
+ printEngine.stopUrls();
+}
+
+function PrintEnginePrint()
+{
+ printEngineWindow = window.openDialog("chrome://messenger/content/msgPrintEngine.xul", "", "chrome,dialog=no,all,centerscreen", false);
+}
+
+function PrintEnginePrintPreview()
+{
+ printEngineWindow = window.openDialog("chrome://messenger/content/msgPrintEngine.xul", "", "chrome,dialog=no,all,centerscreen", true);
+}
diff --git a/mailnews/base/content/msgSynchronize.js b/mailnews/base/content/msgSynchronize.js
new file mode 100644
index 000000000..250c8a26c
--- /dev/null
+++ b/mailnews/base/content/msgSynchronize.js
@@ -0,0 +1,199 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gSynchronizeTree = null;
+var gParentMsgWindow;
+var gMsgWindow;
+
+var gInitialFolderStates = {};
+
+function OnLoad()
+{
+ if (window.arguments && window.arguments[0]) {
+ if (window.arguments[0].msgWindow) {
+ gParentMsgWindow = window.arguments[0].msgWindow;
+ }
+ }
+
+ document.getElementById("syncMail").checked =
+ Services.prefs.getBoolPref("mailnews.offline_sync_mail");
+ document.getElementById("syncNews").checked =
+ Services.prefs.getBoolPref("mailnews.offline_sync_news");
+ document.getElementById("sendMessage").checked =
+ Services.prefs.getBoolPref("mailnews.offline_sync_send_unsent");
+ document.getElementById("workOffline").checked =
+ Services.prefs.getBoolPref("mailnews.offline_sync_work_offline");
+
+ return true;
+}
+
+function syncOkButton()
+{
+
+ var syncMail = document.getElementById("syncMail").checked;
+ var syncNews = document.getElementById("syncNews").checked;
+ var sendMessage = document.getElementById("sendMessage").checked;
+ var workOffline = document.getElementById("workOffline").checked;
+
+ Services.prefs.setBoolPref("mailnews.offline_sync_mail", syncMail);
+ Services.prefs.setBoolPref("mailnews.offline_sync_news", syncNews);
+ Services.prefs.setBoolPref("mailnews.offline_sync_send_unsent", sendMessage);
+ Services.prefs.setBoolPref("mailnews.offline_sync_work_offline", workOffline);
+
+ if (syncMail || syncNews || sendMessage || workOffline) {
+ var offlineManager = Components.classes["@mozilla.org/messenger/offline-manager;1"]
+ .getService(Components.interfaces.nsIMsgOfflineManager);
+ if(offlineManager)
+ offlineManager.synchronizeForOffline(syncNews, syncMail, sendMessage, workOffline, gParentMsgWindow)
+ }
+
+ return true;
+}
+
+function OnSelect()
+{
+ top.window.openDialog("chrome://messenger/content/msgSelectOffline.xul", "",
+ "centerscreen,chrome,modal,titlebar,resizable=yes");
+ return true;
+}
+
+function selectOkButton()
+{
+ return true;
+}
+
+function selectCancelButton()
+{
+ var RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
+ .getService(Components.interfaces.nsIRDFService);
+ for (var resourceValue in gInitialFolderStates) {
+ var resource = RDF.GetResource(resourceValue);
+ var folder = resource.QueryInterface(Components.interfaces.nsIMsgFolder);
+ if (gInitialFolderStates[resourceValue])
+ folder.setFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ else
+ folder.clearFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ }
+ return true;
+}
+
+function selectOnLoad()
+{
+ gMsgWindow = Components.classes["@mozilla.org/messenger/msgwindow;1"]
+ .createInstance(Components.interfaces.nsIMsgWindow);
+ gMsgWindow.domWindow = window;
+ gMsgWindow.rootDocShell.appType = Components.interfaces.nsIDocShell.APP_TYPE_MAIL;
+
+ gSynchronizeTree = document.getElementById('synchronizeTree');
+
+ SortSynchronizePane('folderNameCol', '?folderTreeNameSort');
+}
+
+function SortSynchronizePane(column, sortKey)
+{
+ var node = FindInWindow(window, column);
+ if(!node) {
+ dump('Couldnt find sort column\n');
+ return;
+ }
+
+ node.setAttribute("sort", sortKey);
+ node.setAttribute("sortDirection", "natural");
+ var col = gSynchronizeTree.columns[column];
+ gSynchronizeTree.view.cycleHeader(col);
+}
+
+function FindInWindow(currentWindow, id)
+{
+ var item = currentWindow.document.getElementById(id);
+ if(item)
+ return item;
+
+ for(var i = 0; i < currentWindow.frames.length; i++) {
+ var frameItem = FindInWindow(currentWindow.frames[i], id);
+ if(frameItem)
+ return frameItem;
+ }
+
+ return null;
+}
+
+
+function onSynchronizeClick(event)
+{
+ // we only care about button 0 (left click) events
+ if (event.button != 0)
+ return;
+
+ var row = {}
+ var col = {}
+ var elt = {}
+
+ gSynchronizeTree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, elt);
+ if (row.value == -1)
+ return;
+
+ if (elt.value == "twisty") {
+ var folderResource = GetFolderResource(gSynchronizeTree, row.value);
+ var folder = folderResource.QueryInterface(Components.interfaces.nsIMsgFolder);
+
+ if (!(gSynchronizeTree.treeBoxObject.view.isContainerOpen(row.value))) {
+ var serverType = folder.server.type;
+ // imap is the only server type that does folder discovery
+ if (serverType != "imap") return;
+
+ if (folder.isServer) {
+ var server = folder.server;
+ server.performExpand(gMsgWindow);
+ }
+ else {
+ var imapFolder = folderResource.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
+ if (imapFolder) {
+ imapFolder.performExpand(gMsgWindow);
+ }
+ }
+ }
+ }
+ else {
+ if (col.value.id == "syncCol") {
+ UpdateNode(GetFolderResource(gSynchronizeTree, row.value), row.value);
+ }
+ }
+}
+
+function onSynchronizeTreeKeyPress(event)
+{
+ // for now, only do something on space key
+ if (event.charCode != KeyEvent.DOM_VK_SPACE)
+ return;
+
+ var treeSelection = gSynchronizeTree.view.selection;
+ for (var i=0;i<treeSelection.getRangeCount();i++) {
+ var start = {}, end = {};
+ treeSelection.getRangeAt(i,start,end);
+ for (var k=start.value;k<=end.value;k++)
+ UpdateNode(GetFolderResource(gSynchronizeTree, k), k);
+ }
+}
+
+function UpdateNode(resource, row)
+{
+ var folder = resource.QueryInterface(Components.interfaces.nsIMsgFolder);
+
+ if (folder.isServer)
+ return;
+
+ if (!(resource.Value in gInitialFolderStates)) {
+ gInitialFolderStates[resource.Value] = folder.getFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ }
+
+ folder.toggleFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+}
+
+function GetFolderResource(aTree, aIndex) {
+ return aTree.builderView.getResourceAtIndex(aIndex);
+}
diff --git a/mailnews/base/content/msgSynchronize.xul b/mailnews/base/content/msgSynchronize.xul
new file mode 100644
index 000000000..e3cf90c2c
--- /dev/null
+++ b/mailnews/base/content/msgSynchronize.xul
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<!--
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/msgSynchronize.dtd" >
+<dialog xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="msg-synchronizer"
+ title="&MsgSynchronize.label;"
+ onload="OnLoad();"
+ style="width: 35em;"
+ ondialogaccept="return syncOkButton();">
+
+<script type="application/javascript" src="chrome://messenger/content/msgSynchronize.js"/>
+
+ <keyset id="keyset"/>
+ <label hidden="true" wsm_persist="true" id="server.type"/>
+
+ <description class="desc">&MsgSyncDesc.label;</description>
+ <separator class="thin"/>
+ <label value="&MsgSyncDirections.label;"/>
+
+ <vbox class="indent" align="start">
+ <checkbox id="syncMail" hidable="true" hidefor="movemail,pop3" label="&syncTypeMail.label;"
+ accesskey="&syncTypeMail.accesskey;"/>
+ <checkbox id="syncNews" label="&syncTypeNews.label;" accesskey="&syncTypeNews.accesskey;"/>
+ </vbox>
+ <vbox align="start">
+ <checkbox id="sendMessage" label="&sendMessage.label;" accesskey="&sendMessage.accesskey;"/>
+ <checkbox id="workOffline" label="&workOffline.label;" accesskey="&workOffline.accesskey;"/>
+ </vbox>
+ <separator class="thin"/>
+ <hbox align="right">
+ <button id="select" label="&selectButton.label;" accesskey="&selectButton.accesskey;"
+ oncommand="OnSelect();"/>
+ </hbox>
+</dialog>
diff --git a/mailnews/base/content/newFolderDialog.js b/mailnews/base/content/newFolderDialog.js
new file mode 100644
index 000000000..31a24c286
--- /dev/null
+++ b/mailnews/base/content/newFolderDialog.js
@@ -0,0 +1,85 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var FOLDERS = 1;
+var MESSAGES = 2;
+var dialog;
+
+function onLoad()
+{
+ var windowArgs = window.arguments[0];
+
+ dialog = {};
+
+ dialog.nameField = document.getElementById("name");
+ dialog.nameField.focus();
+
+ // call this when OK is pressed
+ dialog.okCallback = windowArgs.okCallback;
+
+ // pre select the folderPicker, based on what they selected in the folder pane
+ dialog.folder = windowArgs.folder;
+ try {
+ document.getElementById("MsgNewFolderPopup").selectFolder(windowArgs.folder);
+ } catch(ex) {
+ // selected a child folder
+ document.getElementById("msgNewFolderPicker")
+ .setAttribute("label", windowArgs.folder.prettyName);
+ }
+
+ // can folders contain both folders and messages?
+ if (windowArgs.dualUseFolders) {
+ dialog.folderType = FOLDERS | MESSAGES;
+
+ // hide the section when folder contain both folders and messages.
+ var newFolderTypeBox = document.getElementById("newFolderTypeBox");
+ newFolderTypeBox.setAttribute("hidden", "true");
+ } else {
+ // set our folder type by calling the default selected type's oncommand
+ var selectedFolderType = document.getElementById("folderGroup").selectedItem;
+ eval(selectedFolderType.getAttribute("oncommand"));
+ }
+
+ doEnabling();
+}
+
+function onFolderSelect(event) {
+ dialog.folder = event.target._folder;
+ document.getElementById("msgNewFolderPicker")
+ .setAttribute("label", dialog.folder.prettyName);
+}
+
+function onOK()
+{
+ var name = dialog.nameField.value;
+ var uri = dialog.folder;
+
+ // do name validity check?
+
+ // make sure name ends in "/" if folder to create can only contain folders
+ if ((dialog.folderType == FOLDERS) && !name.endsWith("/"))
+ dialog.okCallback(name + "/", dialog.folder);
+ else
+ dialog.okCallback(name, dialog.folder);
+
+ return true;
+}
+
+function onFoldersOnly()
+{
+ dialog.folderType = FOLDERS;
+}
+
+function onMessagesOnly()
+{
+ dialog.folderType = MESSAGES;
+}
+
+function doEnabling()
+{
+ document.documentElement.getButton("accept").disabled = !dialog.nameField.value;
+}
+
diff --git a/mailnews/base/content/newFolderDialog.xul b/mailnews/base/content/newFolderDialog.xul
new file mode 100644
index 000000000..1587bfdae
--- /dev/null
+++ b/mailnews/base/content/newFolderDialog.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % newFolderDTD SYSTEM "chrome://messenger/locale/newFolderDialog.dtd">%newFolderDTD;
+]>
+
+<dialog xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&newFolderDialog.title;"
+ onload="onLoad();"
+ buttonlabelaccept="&accept.label;"
+ buttonaccesskeyaccept="&accept.accesskey;"
+ ondialogaccept="return onOK();">
+
+ <stringbundleset id="stringbundleset"/>
+ <script type="application/javascript" src="chrome://messenger/content/newFolderDialog.js"/>
+
+ <label value="&name.label;" accesskey="&name.accesskey;" control="name"/>
+ <textbox id="name" oninput="doEnabling();"/>
+
+ <separator/>
+
+ <label value="&description.label;" accesskey="&description.accesskey;" control="msgNewFolderPicker"/>
+
+ <menulist id="msgNewFolderPicker" class="folderMenuItem"
+ displayformat="verbose">
+ <menupopup id="MsgNewFolderPopup" type="folder" showFileHereLabel="true"
+ class="menulist-menupopup"
+ mode="newFolder"
+ oncommand="onFolderSelect(event)"/>
+ </menulist>
+
+ <vbox id="newFolderTypeBox">
+
+ <separator class="thin"/>
+
+ <label value="&folderRestriction1.label;"/>
+ <label value="&folderRestriction2.label;"/>
+
+ <separator class="thin"/>
+
+ <radiogroup id="folderGroup" orient="horizontal" class="indent">
+ <radio oncommand="onFoldersOnly();" label="&foldersOnly.label;"/>
+ <radio oncommand="onMessagesOnly();" label="&messagesOnly.label;" selected="true"/>
+ </radiogroup>
+
+ </vbox>
+
+</dialog>
diff --git a/mailnews/base/content/newmailalert.css b/mailnews/base/content/newmailalert.css
new file mode 100644
index 000000000..52746fae0
--- /dev/null
+++ b/mailnews/base/content/newmailalert.css
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#alertContainer {
+ opacity: 0;
+}
+
+#alertContainer[noanimation] {
+ opacity: 1;
+}
+
+#alertContainer[fade-in] {
+ animation-timing-function: ease-out;
+ animation-duration: 2s;
+ animation-fill-mode: both;
+ animation-name: fade-in;
+}
+
+@keyframes fade-in {
+ from {opacity: 0;}
+ to {opacity: 1;}
+}
+
+#alertContainer[fade-out] {
+ animation-timing-function: ease-in;
+ animation-duration: 2s;
+ animation-fill-mode: both;
+ animation-name: fade-out;
+}
+
+@keyframes fade-out {
+ from {opacity: 1;}
+ to {opacity: 0;}
+}
diff --git a/mailnews/base/content/newmailalert.js b/mailnews/base/content/newmailalert.js
new file mode 100644
index 000000000..f54d165b1
--- /dev/null
+++ b/mailnews/base/content/newmailalert.js
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+
+// Copied from nsILookAndFeel.h, see comments on eMetric_AlertNotificationOrigin
+var NS_ALERT_HORIZONTAL = 1;
+var NS_ALERT_LEFT = 2;
+var NS_ALERT_TOP = 4;
+
+var gNumNewMsgsToShowInAlert = 4; // the more messages we show in the alert, the larger it will be
+var gOpenTime = 4000; // total time the alert should stay up once we are done animating.
+
+var gAlertListener = null;
+var gPendingPreviewFetchRequests = 0;
+var gUserInitiated = false;
+var gOrigin = 0; // Default value: alert from bottom right.
+
+function prefillAlertInfo()
+{
+ const Ci = Components.interfaces;
+ // unwrap all the args....
+ // arguments[0] --> nsIArray of folders with new mail
+ // arguments[1] --> the observer to call back with notifications about the alert
+ // arguments[2] --> user initiated boolean. true if the user initiated opening the alert
+ // (which means skip the fade effect and don't auto close the alert)
+ // arguments[3] --> the alert origin returned by the look and feel
+ var foldersWithNewMail = window.arguments[0];
+ gAlertListener = window.arguments[1];
+ gUserInitiated = window.arguments[2];
+ gOrigin = window.arguments[3];
+
+ // For now just grab the first folder which should be a root folder
+ // for the account that has new mail. If we can't find a folder, just
+ // return to avoid the exception and empty dialog in upper left-hand corner.
+ if (!foldersWithNewMail || foldersWithNewMail.length < 1)
+ return;
+ let rootFolder = foldersWithNewMail.queryElementAt(0, Ci.nsIWeakReference)
+ .QueryReferent(Ci.nsIMsgFolder);
+
+ // Generate an account label string based on the root folder.
+ var label = document.getElementById('alertTitle');
+ var totalNumNewMessages = rootFolder.getNumNewMessages(true);
+ var message = totalNumNewMessages == 1 ? "newMailNotification_message"
+ : "newMailNotification_messages";
+ label.value = document.getElementById('bundle_messenger')
+ .getFormattedString(message,
+ [rootFolder.prettiestName,
+ totalNumNewMessages]);
+
+ // This is really the root folder and we have to walk through the list to
+ // find the real folder that has new mail in it...:(
+ let allFolders = rootFolder.descendants;
+ var folderSummaryInfoEl = document.getElementById('folderSummaryInfo');
+ folderSummaryInfoEl.mMaxMsgHdrsInPopup = gNumNewMsgsToShowInAlert;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder))
+ {
+ if (folder.hasNewMessages) {
+ let notify =
+ // Any folder which is an inbox or ...
+ folder.getFlag(Ci.nsMsgFolderFlags.Inbox) ||
+ // any non-special or non-virtual folder. In other words, we don't
+ // notify for Drafts|Trash|SentMail|Templates|Junk|Archive|Queue or virtual.
+ !(folder.flags & (Ci.nsMsgFolderFlags.SpecialUse | Ci.nsMsgFolderFlags.Virtual));
+
+ if (notify) {
+ var asyncFetch = {};
+ folderSummaryInfoEl.parseFolder(folder, new urlListener(folder), asyncFetch);
+ if (asyncFetch.value)
+ gPendingPreviewFetchRequests++;
+ }
+ }
+ }
+}
+
+function urlListener(aFolder)
+{
+ this.mFolder = aFolder;
+}
+
+urlListener.prototype =
+{
+ OnStartRunningUrl: function(aUrl)
+ {
+ },
+
+ OnStopRunningUrl: function(aUrl, aExitCode)
+ {
+ var folderSummaryInfoEl = document.getElementById('folderSummaryInfo');
+ folderSummaryInfoEl.parseFolder(this.mFolder, null, {});
+ gPendingPreviewFetchRequests--;
+
+ // when we are done running all of our urls for fetching the preview text,
+ // start the alert.
+ if (!gPendingPreviewFetchRequests)
+ showAlert();
+ }
+}
+
+function onAlertLoad()
+{
+ prefillAlertInfo();
+ // read out our initial settings from prefs.
+ try
+ {
+ gOpenTime = Services.prefs.getIntPref("alerts.totalOpenTime");
+ } catch (ex) {}
+
+ // bogus call to make sure the window is moved offscreen until we are ready for it.
+ resizeAlert(true);
+
+ // if we aren't waiting to fetch preview text, then go ahead and
+ // start showing the alert.
+ if (!gPendingPreviewFetchRequests)
+ setTimeout(showAlert, 0); // let the JS thread unwind, to give layout
+ // a chance to recompute the styles and widths for our alert text.
+}
+
+// If the user initiated the alert, show it right away, otherwise start opening the alert with
+// the fade effect.
+function showAlert()
+{
+ if (!document.getElementById("folderSummaryInfo").hasMessages) {
+ closeAlert(); // no mail, so don't bother showing the alert...
+ return;
+ }
+
+ // resize the alert based on our current content
+ resizeAlert(false);
+
+ var alertContainer = document.getElementById("alertContainer");
+ // Don't fade in if the user opened the alert or the pref is true.
+ if (gUserInitiated ||
+ Services.prefs.getBoolPref("alerts.disableSlidingEffect")) {
+ alertContainer.setAttribute("noanimation", true);
+ setTimeout(closeAlert, gOpenTime);
+ return;
+ }
+
+ alertContainer.addEventListener("animationend", function hideAlert(event) {
+ if (event.animationName == "fade-in") {
+ alertContainer.removeEventListener("animationend", hideAlert, false);
+ let remaining = Math.max(Math.round(gOpenTime - event.elapsedTime * 1000), 0);
+ setTimeout(fadeOutAlert, remaining);
+ }
+ }, false);
+ alertContainer.setAttribute("fade-in", true);
+}
+
+function resizeAlert(aMoveOffScreen)
+{
+ var alertTextBox = document.getElementById("alertTextBox");
+ var alertImageBox = document.getElementById("alertImageBox");
+ alertImageBox.style.minHeight = alertTextBox.scrollHeight + "px";
+
+ sizeToContent();
+
+ // leftover hack to get the window properly hidden when we first open it
+ if (aMoveOffScreen)
+ window.outerHeight = 1;
+
+ // Determine position
+ var x = gOrigin & NS_ALERT_LEFT ? screen.availLeft :
+ screen.availLeft + screen.availWidth - window.outerWidth;
+ var y = gOrigin & NS_ALERT_TOP ? screen.availTop :
+ screen.availTop + screen.availHeight - window.outerHeight;
+
+ // Offset the alert by 10 pixels from the edge of the screen
+ y += gOrigin & NS_ALERT_TOP ? 10 : -10;
+ x += gOrigin & NS_ALERT_LEFT ? 10 : -10;
+
+ window.moveTo(x, y);
+}
+
+function fadeOutAlert()
+{
+ var alertContainer = document.getElementById("alertContainer");
+ alertContainer.addEventListener("animationend", function fadeOut(event) {
+ if (event.animationName == "fade-out") {
+ alertContainer.removeEventListener("animationend", fadeOut, false);
+ closeAlert();
+ }
+ }, false);
+ alertContainer.setAttribute("fade-out", true);
+}
+
+function closeAlert()
+{
+ if (gAlertListener)
+ gAlertListener.observe(null, "alertfinished", "");
+ window.close();
+}
diff --git a/mailnews/base/content/newmailalert.xul b/mailnews/base/content/newmailalert.xul
new file mode 100644
index 000000000..75e839f54
--- /dev/null
+++ b/mailnews/base/content/newmailalert.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/content/newmailalert.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/newmailalert.css" type="text/css"?>
+
+<window id="newMailAlertNotification"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="alert:alert"
+ role="alert"
+ align="start"
+ onload="onAlertLoad()">
+
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <script type="application/javascript" src="chrome://messenger/content/newmailalert.js"/>
+
+ <stack id="alertContainer" mousethrough="always">
+ <hbox id="alertBox">
+ <hbox id ="alertImageBox" align="center" pack="center">
+ <image id="alertImage"/>
+ </hbox>
+
+ <vbox id="alertTextBox">
+ <label id="alertTitle"/>
+ <separator id="alertGroove" class="groove"/>
+ <folderSummary id="folderSummaryInfo" mousethrough="never"/>
+ </vbox>
+ </hbox>
+
+ <toolbarbutton id="closeButton" class="close-icon" top="0" right="0" onclick="closeAlert();"/>
+ </stack>
+</window>
diff --git a/mailnews/base/content/renameFolderDialog.js b/mailnews/base/content/renameFolderDialog.js
new file mode 100644
index 000000000..74a176249
--- /dev/null
+++ b/mailnews/base/content/renameFolderDialog.js
@@ -0,0 +1,47 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var dialog;
+
+function onLoad()
+{
+ var windowArgs = window.arguments[0];
+
+ dialog = {};
+
+ dialog.OKButton = document.documentElement.getButton("accept");
+
+ dialog.nameField = document.getElementById("name");
+ dialog.nameField.value = windowArgs.name;
+ dialog.nameField.select();
+ dialog.nameField.focus();
+
+ // call this when OK is pressed
+ dialog.okCallback = windowArgs.okCallback;
+
+ // pre select the folderPicker, based on what they selected in the folder pane
+ dialog.preselectedFolderURI = windowArgs.preselectedURI;
+
+ doEnabling();
+}
+
+function onOK()
+{
+ dialog.okCallback(dialog.nameField.value, dialog.preselectedFolderURI);
+
+ return true;
+}
+
+function doEnabling()
+{
+ if (dialog.nameField.value) {
+ if (dialog.OKButton.disabled)
+ dialog.OKButton.disabled = false;
+ } else {
+ if (!dialog.OKButton.disabled)
+ dialog.OKButton.disabled = true;
+ }
+}
diff --git a/mailnews/base/content/renameFolderDialog.xul b/mailnews/base/content/renameFolderDialog.xul
new file mode 100644
index 000000000..d94d62204
--- /dev/null
+++ b/mailnews/base/content/renameFolderDialog.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?> <!-- -*- Mode: xml; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- -->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/renameFolderDialog.dtd">
+
+<dialog xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&renameFolderDialog.title;"
+ buttonlabelaccept="&accept.label;"
+ buttonaccesskeyaccept="&accept.accesskey;"
+ ondialogaccept="return onOK();"
+ onload="onLoad();">
+
+ <stringbundleset id="stringbundleset"/>
+ <script type="application/javascript" src="chrome://messenger/content/renameFolderDialog.js"/>
+
+ <label value="&rename.label;" accesskey="&rename.accesskey;" control="name"/>
+ <textbox id="name" oninput="doEnabling();"/>
+</dialog>
diff --git a/mailnews/base/content/retention.js b/mailnews/base/content/retention.js
new file mode 100644
index 000000000..1db195b81
--- /dev/null
+++ b/mailnews/base/content/retention.js
@@ -0,0 +1,45 @@
+/*
+ * -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+function initCommonRetentionSettings(retentionSettings)
+{
+ document.getElementById("retention.keepMsg").value = retentionSettings.retainByPreference;
+ document.getElementById("retention.keepOldMsgMin").value =
+ (retentionSettings.daysToKeepHdrs > 0) ? retentionSettings.daysToKeepHdrs : 30;
+ document.getElementById("retention.keepNewMsgMin").value =
+ (retentionSettings.numHeadersToKeep > 0) ? retentionSettings.numHeadersToKeep : 2000;
+
+ document.getElementById("retention.applyToFlagged").checked =
+ !retentionSettings.applyToFlaggedMessages;
+}
+
+function saveCommonRetentionSettings(aRetentionSettings)
+{
+ aRetentionSettings.retainByPreference = document.getElementById("retention.keepMsg").value;
+
+ aRetentionSettings.daysToKeepHdrs = document.getElementById("retention.keepOldMsgMin").value;
+ aRetentionSettings.numHeadersToKeep = document.getElementById("retention.keepNewMsgMin").value;
+
+ aRetentionSettings.applyToFlaggedMessages =
+ !document.getElementById("retention.applyToFlagged").checked;
+
+ return aRetentionSettings;
+}
+
+function onCheckKeepMsg()
+{
+ if (gLockedPref && gLockedPref["retention.keepMsg"]) {
+ // if the pref associated with the radiobutton is locked, as indicated
+ // by the gLockedPref, skip this function. All elements in this
+ // radiogroup have been locked by the function onLockPreference.
+ return;
+ }
+
+ var keepMsg = document.getElementById("retention.keepMsg").value;
+ document.getElementById("retention.keepOldMsgMin").disabled = keepMsg != 2;
+ document.getElementById("retention.keepNewMsgMin").disabled = keepMsg != 3;
+}
diff --git a/mailnews/base/content/shareglue.js b/mailnews/base/content/shareglue.js
new file mode 100644
index 000000000..29026033c
--- /dev/null
+++ b/mailnews/base/content/shareglue.js
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * code in here is generic, shared utility code across all messenger
+ * components. There should be no command or widget specific code here
+ */
+
+function MessengerSetForcedCharacterSet(aCharset)
+{
+ messenger.setDocumentCharset(aCharset);
+ msgWindow.mailCharacterSet = aCharset;
+ msgWindow.charsetOverride = true;
+
+ // DO NOT try to reload the message here. we do this automatically now in
+ // messenger.SetDocumentCharset. You'll just break things and reak havoc
+ // if you call ReloadMessage() here...
+}
diff --git a/mailnews/base/content/shutdownWindow.js b/mailnews/base/content/shutdownWindow.js
new file mode 100644
index 000000000..e7b853eec
--- /dev/null
+++ b/mailnews/base/content/shutdownWindow.js
@@ -0,0 +1,99 @@
+/*
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+var curTaskIndex = 0;
+var numTasks = 0;
+var stringBundle;
+
+var msgShutdownService = Components.classes["@mozilla.org/messenger/msgshutdownservice;1"]
+ .getService(Components.interfaces.nsIMsgShutdownService);
+
+function onLoad()
+{
+ numTasks = msgShutdownService.getNumTasks();
+
+ stringBundle = document.getElementById("bundle_shutdown");
+ document.title = stringBundle.getString("shutdownDialogTitle");
+
+ updateTaskProgressLabel(1);
+ updateProgressMeter(0);
+
+ msgShutdownService.startShutdownTasks();
+}
+
+function updateProgressLabel(inTaskName)
+{
+ var curTaskLabel = document.getElementById("shutdownStatus_label");
+ curTaskLabel.value = inTaskName;
+}
+
+function updateTaskProgressLabel(inCurTaskNum)
+{
+ var taskProgressLabel = document.getElementById("shutdownTask_label");
+ taskProgressLabel.value = stringBundle.getFormattedString("taskProgress", [inCurTaskNum, numTasks]);
+}
+
+function updateProgressMeter(inPercent)
+{
+ var taskProgressmeter = document.getElementById('shutdown_progressmeter');
+ taskProgressmeter.value = inPercent;
+}
+
+function onCancel()
+{
+ msgShutdownService.cancelShutdownTasks();
+}
+
+function nsMsgShutdownTaskListener()
+{
+ msgShutdownService.setShutdownListener(this);
+}
+
+nsMsgShutdownTaskListener.prototype =
+{
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Components.interfaces.nsIWebProgressListener) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
+ {
+ window.close();
+ }
+ },
+
+ onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
+ {
+ updateProgressMeter(((aCurTotalProgress / aMaxTotalProgress) * 100));
+ updateTaskProgressLabel(aCurTotalProgress + 1);
+ },
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ // we can ignore this notification
+ },
+
+ onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ if (aMessage)
+ updateProgressLabel(aMessage);
+ },
+
+ onSecurityChange: function(aWebProgress, aRequest, state)
+ {
+ // we can ignore this notification
+ }
+}
+
+var MsgShutdownTaskListener = new nsMsgShutdownTaskListener();
+
diff --git a/mailnews/base/content/shutdownWindow.xul b/mailnews/base/content/shutdownWindow.xul
new file mode 100644
index 000000000..3158ccc6d
--- /dev/null
+++ b/mailnews/base/content/shutdownWindow.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!--
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="padding: 20px; width: 350px"
+ onload="onLoad()"
+ buttons="cancel"
+ ondialogcancel="return onCancel();">
+
+ <script type="application/javascript" src="chrome://messenger/content/shutdownWindow.js"/>
+ <stringbundle id="bundle_shutdown" src="chrome://messenger/locale/shutdownWindow.properties"/>
+
+ <vbox align="center">
+ <label id="shutdownStatus_label" value="" />
+ <separator class="thin" />
+ </vbox>
+
+ <progressmeter id="shutdown_progressmeter" mode="determined" />
+
+ <vbox align="center">
+ <label id="shutdownTask_label" value="" />
+ <separator class="thick" />
+ </vbox>
+
+</dialog>
diff --git a/mailnews/base/content/virtualFolderListDialog.js b/mailnews/base/content/virtualFolderListDialog.js
new file mode 100644
index 000000000..bfe3c0b69
--- /dev/null
+++ b/mailnews/base/content/virtualFolderListDialog.js
@@ -0,0 +1,136 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/MailUtils.js");
+
+var gFolderPickerTree = null;
+
+function onLoad()
+{
+ gFolderPickerTree = document.getElementById("folderPickerTree");
+
+ if (window.arguments[0].searchFolderURIs)
+ {
+ // for each folder uri,
+ var srchFolderUriArray = window.arguments[0].searchFolderURIs.split('|');
+ // get the folder for each search URI and set the searchThisFolder flag on it
+ for (var i in srchFolderUriArray)
+ {
+ var realFolder = MailUtils.getFolderForURI(srchFolderUriArray[i]);
+ if (realFolder)
+ realFolder.setInVFEditSearchScope(true, false);
+ }
+ }
+}
+
+function onUnLoad()
+{
+ resetFolderToSearchAttribute();
+}
+
+function onOK()
+{
+ if ( window.arguments[0].okCallback )
+ window.arguments[0].okCallback(generateFoldersToSearchList());
+}
+
+function onCancel()
+{
+ // onunload will clear out the folder attributes we changed
+}
+
+function addFolderToSearchListString(aFolder, aCurrentSearchURIString)
+{
+ if (aCurrentSearchURIString)
+ aCurrentSearchURIString += '|';
+ aCurrentSearchURIString += aFolder.URI;
+
+ return aCurrentSearchURIString;
+}
+
+function processSearchSettingForFolder(aFolder, aCurrentSearchURIString)
+{
+ if (aFolder.inVFEditSearchScope)
+ aCurrentSearchURIString = addFolderToSearchListString(aFolder, aCurrentSearchURIString);
+
+ aFolder.setInVFEditSearchScope(false, false);
+ return aCurrentSearchURIString;
+}
+
+// warning: this routine also clears out the search property list from all of the msg folders
+function generateFoldersToSearchList()
+{
+ let uriSearchString = "";
+ let allFolders = MailServices.accounts.allFolders;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder))
+ uriSearchString = processSearchSettingForFolder(folder, uriSearchString);
+
+ return uriSearchString;
+}
+
+function resetFolderToSearchAttribute()
+{
+ // iterates over all accounts and all folders, clearing out the inVFEditScope property in case
+ // we set it.
+ let allFolders = MailServices.accounts.allFolders;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder))
+ folder.setInVFEditSearchScope(false, false);
+}
+
+function ReverseStateFromNode(row)
+{
+ var folder = GetFolderResource(row).QueryInterface(Components.interfaces.nsIMsgFolder);
+ var currentState = folder.inVFEditSearchScope;
+
+ folder.setInVFEditSearchScope(!currentState, false);
+}
+
+function GetFolderResource(rowIndex)
+{
+ return gFolderPickerTree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder).getResourceAtIndex(rowIndex);
+}
+
+function selectFolderTreeOnClick(event)
+{
+ // we only care about button 0 (left click) events
+ if (event.button != 0 || event.originalTarget.localName != "treechildren")
+ return;
+
+ var row = {}, col = {}, obj = {};
+ gFolderPickerTree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
+ if (row.value == -1 || row.value > (gFolderPickerTree.view.rowCount - 1))
+ return;
+
+ if (event.detail == 2) {
+ // only toggle the search folder state when double clicking something
+ // that isn't a container
+ if (!gFolderPickerTree.view.isContainer(row.value)) {
+ ReverseStateFromNode(row.value);
+ return;
+ }
+ }
+ else if (event.detail == 1)
+ {
+ if (obj.value != "twisty" && col.value.id == "selectedColumn")
+ ReverseStateFromNode(row.value)
+ }
+}
+
+function onSelectFolderTreeKeyPress(event)
+{
+ // for now, only do something on space key
+ if (event.charCode != KeyEvent.DOM_VK_SPACE)
+ return;
+
+ var treeSelection = gFolderPickerTree.view.selection;
+ for (var i=0;i<treeSelection.getRangeCount();i++) {
+ var start = {}, end = {};
+ treeSelection.getRangeAt(i,start,end);
+ for (var k=start.value;k<=end.value;k++)
+ ReverseStateFromNode(k);
+ }
+}
diff --git a/mailnews/base/content/virtualFolderListDialog.xul b/mailnews/base/content/virtualFolderListDialog.xul
new file mode 100644
index 000000000..7dcc5e8fe
--- /dev/null
+++ b/mailnews/base/content/virtualFolderListDialog.xul
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ -->
+
+<?xml-stylesheet href="chrome://messenger/skin/virtualFolderListDialog.css" type="text/css"?>
+
+<!DOCTYPE window SYSTEM "chrome://messenger/locale/virtualFolderListDialog.dtd">
+
+<dialog id="searchFolderWindow"
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&virtualFolderListTitle.title;"
+ style="width: 27em; height: 25em;"
+ persist="width height screenX screenY"
+ onload="onLoad();"
+ onunload="onUnLoad();"
+ ondialogaccept="return onOK();"
+ ondialogcancel="return onCancel();">
+
+<script type="application/javascript" src="chrome://messenger/content/virtualFolderListDialog.js"/>
+
+ <label control="folderPickerTree">&virtualFolderDesc.label;</label>
+
+ <tree id="folderPickerTree"
+ treelines="true"
+ flex="1"
+ hidecolumnpicker="true"
+ seltype="multiple"
+ disableKeyNavigation="true"
+ datasources="rdf:msgaccountmanager rdf:mailnewsfolders"
+ ref="msgaccounts:/"
+ flags="dont-build-content"
+ onkeypress="onSelectFolderTreeKeyPress(event);"
+ onclick="selectFolderTreeOnClick(event);">
+ <template>
+ <rule>
+ <conditions>
+ <content uri="?container"/>
+ <member container="?container" child="?member"/>
+ <triple subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#Virtual"
+ object="false"/>
+ <triple subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#IsDeferred"
+ object="false"/>
+ </conditions>
+
+ <bindings>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#FolderTreeName"
+ object="?folderTreeName"/>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#FolderTreeName?sort=true"
+ object="?folderTreeNameSort"/>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#ServerType"
+ object="?serverType"/>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#SpecialFolder"
+ object="?specialFolder"/>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#HasUnreadMessages"
+ object="?hasUnreadMessages"/>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#InVFEditSearchScope"
+ object="?inVFEditSearchScope"/>
+ </bindings>
+
+ <action>
+ <treechildren>
+ <treeitem uri="?member">
+ <treerow>
+ <treecell label="?folderTreeName" properties="ServerType-?ServerType specialFolder-?specialFolder hasUnreadMessages-?hasUnreadMessages"/>
+ <treecell properties="inVFEditSearchScope-?inVFEditSearchScope"/>/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </action>
+ </rule>
+ </template>
+
+ <treecols>
+ <treecol id="folderNameCol" sort="?folderTreeNameSort" sortActive="true" sortDirection="ascending"
+ flex="10" primary="true" hideheader="true" crop="center"/>
+ <treecol id="selectedColumn" hideheader="true" flex="1"/>
+ </treecols>
+ </tree>
+</dialog>
diff --git a/mailnews/base/content/virtualFolderProperties.js b/mailnews/base/content/virtualFolderProperties.js
new file mode 100644
index 000000000..b9a984bd1
--- /dev/null
+++ b/mailnews/base/content/virtualFolderProperties.js
@@ -0,0 +1,266 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gPickedFolder;
+var gMailView = null;
+var msgWindow; // important, don't change the name of this variable. it's really a global used by commandglue.js
+var gSearchTermSession; // really an in memory temporary filter we use to read in and write out the search terms
+var gSearchFolderURIs = "";
+var gMessengerBundle = null;
+
+var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/virtualFolderWrapper.js");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/MailUtils.js");
+
+function onLoad()
+{
+ var windowArgs = window.arguments[0];
+ var acceptButton = document.documentElement.getButton("accept");
+
+ gMessengerBundle = document.getElementById("bundle_messenger");
+
+ // call this when OK is pressed
+ msgWindow = windowArgs.msgWindow;
+
+ initializeSearchWidgets();
+
+ setSearchScope(nsMsgSearchScope.offlineMail);
+ if (windowArgs.editExistingFolder)
+ {
+ acceptButton.label =
+ document.documentElement.getAttribute("editFolderAcceptButtonLabel");
+ acceptButton.accesskey =
+ document.documentElement.getAttribute("editFolderAcceptButtonAccessKey");
+ InitDialogWithVirtualFolder(windowArgs.folder);
+ }
+ else // we are creating a new virtual folder
+ {
+ acceptButton.label =
+ document.documentElement.getAttribute("newFolderAcceptButtonLabel");
+ acceptButton.accesskey =
+ document.documentElement.getAttribute("newFolderAcceptButtonAccessKey");
+ // it is possible that we were given arguments to pre-fill the dialog with...
+ gSearchTermSession = Components.classes["@mozilla.org/messenger/searchSession;1"]
+ .createInstance(Components.interfaces.nsIMsgSearchSession);
+
+ if (windowArgs.searchTerms) // then add them to our search session
+ {
+ for (let searchTerm in fixIterator(windowArgs.searchTerms,
+ Components.interfaces.nsIMsgSearchTerm))
+ gSearchTermSession.appendTerm(searchTerm);
+ }
+ if (windowArgs.folder)
+ {
+ // pre select the folderPicker, based on what they selected in the folder pane
+ gPickedFolder = windowArgs.folder;
+ try {
+ document.getElementById("msgNewFolderPopup").selectFolder(windowArgs.folder);
+ } catch(ex) {
+ document.getElementById("msgNewFolderPicker")
+ .setAttribute("label", windowArgs.folder.prettyName);
+ }
+
+ // if the passed in URI is not a server then pre-select it as the folder to search
+ if (!windowArgs.folder.isServer)
+ gSearchFolderURIs = windowArgs.folder.URI;
+ }
+
+ let folderNameField = document.getElementById("name");
+ folderNameField.hidden = false;
+ folderNameField.focus();
+ if (windowArgs.newFolderName)
+ folderNameField.value = windowArgs.newFolderName;
+ if (windowArgs.searchFolderURIs)
+ gSearchFolderURIs = windowArgs.searchFolderURIs;
+
+ setupSearchRows(gSearchTermSession.searchTerms);
+ doEnabling(); // we only need to disable/enable the OK button for new virtual folders
+ }
+
+ if (typeof windowArgs.searchOnline != "undefined")
+ document.getElementById('searchOnline').checked = windowArgs.searchOnline;
+ updateOnlineSearchState();
+ updateFoldersCount();
+}
+
+function setupSearchRows(aSearchTerms)
+{
+ if (aSearchTerms && aSearchTerms.Count() > 0)
+ initializeSearchRows(nsMsgSearchScope.offlineMail, aSearchTerms); // load the search terms for the folder
+ else
+ onMore(null);
+}
+
+function updateOnlineSearchState()
+{
+ var enableCheckbox = false;
+ var checkbox = document.getElementById('searchOnline');
+ // only enable the checkbox for selection, for online servers
+ var srchFolderUriArray = gSearchFolderURIs.split('|');
+ if (srchFolderUriArray[0])
+ {
+ var realFolder = MailUtils.getFolderForURI(srchFolderUriArray[0]);
+ enableCheckbox = realFolder.server.offlineSupportLevel; // anything greater than 0 is an online server like IMAP or news
+ }
+
+ if (enableCheckbox)
+ checkbox.removeAttribute('disabled');
+ else
+ {
+ checkbox.setAttribute('disabled', true);
+ checkbox.checked = false;
+ }
+}
+
+function InitDialogWithVirtualFolder(aVirtualFolder)
+{
+ let virtualFolderWrapper =
+ VirtualFolderHelper.wrapVirtualFolder(window.arguments[0].folder);
+
+ // when editing an existing folder, hide the folder picker that stores the parent location of the folder
+ document.getElementById("chooseFolderLocationRow").collapsed = true;
+ let folderNameField = document.getElementById("existingName");
+ folderNameField.hidden = false;
+
+ gSearchFolderURIs = virtualFolderWrapper.searchFolderURIs;
+ updateFoldersCount();
+ document.getElementById('searchOnline').checked = virtualFolderWrapper.onlineSearch;
+ gSearchTermSession = virtualFolderWrapper.searchTermsSession;
+
+ setupSearchRows(gSearchTermSession.searchTerms);
+
+ // set the name of the folder
+ let folderBundle = document.getElementById("bundle_folder");
+ let name = folderBundle.getFormattedString("verboseFolderFormat",
+ [aVirtualFolder.prettyName, aVirtualFolder.server.prettyName]);
+ folderNameField.setAttribute("label", name);
+ // update the window title based on the name of the saved search
+ document.title = gMessengerBundle.getFormattedString("editVirtualFolderPropertiesTitle",
+ [aVirtualFolder.prettyName]);
+}
+
+function onFolderPick(aEvent) {
+ gPickedFolder = aEvent.target._folder;
+ document.getElementById("msgNewFolderPopup")
+ .selectFolder(gPickedFolder);
+}
+
+function onOK()
+{
+ var name = document.getElementById("name").value;
+ var searchOnline = document.getElementById('searchOnline').checked;
+
+ if (!gSearchFolderURIs)
+ {
+ Services.prompt.alert(window, null,
+ gMessengerBundle.getString('alertNoSearchFoldersSelected'));
+ return false;
+ }
+
+ if (window.arguments[0].editExistingFolder)
+ {
+ // update the search terms
+ saveSearchTerms(gSearchTermSession.searchTerms, gSearchTermSession);
+ // save the settings
+ let virtualFolderWrapper =
+ VirtualFolderHelper.wrapVirtualFolder(window.arguments[0].folder);
+ virtualFolderWrapper.searchTerms = gSearchTermSession.searchTerms;
+ virtualFolderWrapper.searchFolders = gSearchFolderURIs;
+ virtualFolderWrapper.onlineSearch = searchOnline;
+ virtualFolderWrapper.cleanUpMessageDatabase();
+
+ MailServices.accounts.saveVirtualFolders();
+
+ if (window.arguments[0].onOKCallback)
+ window.arguments[0].onOKCallback(virtualFolderWrapper.virtualFolder.URI);
+ return true;
+ }
+ var uri = gPickedFolder.URI;
+ if (name && uri) // create a new virtual folder
+ {
+ // check to see if we already have a folder with the same name and alert the user if so...
+ var parentFolder = MailUtils.getFolderForURI(uri);
+
+ // sanity check the name based on the logic used by nsMsgBaseUtils.cpp. It can't start with a '.', it can't end with a '.', '~' or ' '.
+ // it can't contain a ';' or '#'.
+ if (/^\.|[\.\~ ]$|[\;\#]/.test(name))
+ {
+ Services.prompt.alert(window, null,
+ gMessengerBundle.getString("folderCreationFailed"));
+ return false;
+ }
+ else if (parentFolder.containsChildNamed(name))
+ {
+ Services.prompt.alert(window, null,
+ gMessengerBundle.getString("folderExists"));
+ return false;
+ }
+
+ saveSearchTerms(gSearchTermSession.searchTerms, gSearchTermSession);
+ VirtualFolderHelper.createNewVirtualFolder(name, parentFolder, gSearchFolderURIs,
+ gSearchTermSession.searchTerms,
+ searchOnline);
+ }
+
+ return true;
+}
+
+function doEnabling()
+{
+ var acceptButton = document.documentElement.getButton("accept");
+ acceptButton.disabled = !document.getElementById("name").value;
+}
+
+function chooseFoldersToSearch()
+{
+ // if we have some search folders already, then root the folder picker dialog off the account
+ // for those folders. Otherwise fall back to the preselectedfolderURI which is the parent folder
+ // for this new virtual folder.
+ var dialog = window.openDialog("chrome://messenger/content/virtualFolderListDialog.xul", "",
+ "chrome,titlebar,modal,centerscreen,resizable",
+ {searchFolderURIs:gSearchFolderURIs,
+ okCallback:onFolderListDialogCallback});
+}
+
+// callback routine from chooseFoldersToSearch
+function onFolderListDialogCallback(searchFolderURIs)
+{
+ gSearchFolderURIs = searchFolderURIs;
+ updateFoldersCount();
+ updateOnlineSearchState(); // we may have changed the server type we are searching...
+}
+
+function updateFoldersCount()
+{
+ let srchFolderUriArray = gSearchFolderURIs.split('|');
+ let folderCount = gSearchFolderURIs ? srchFolderUriArray.length : 0;
+ let foldersList = document.getElementById("chosenFoldersCount");
+ foldersList.textContent =
+ PluralForm.get(folderCount, gMessengerBundle.getString("virtualFolderSourcesChosen"))
+ .replace("#1", folderCount);
+ if (folderCount > 0) {
+ let folderNames = [];
+ for (let folderURI of srchFolderUriArray) {
+ let folder = MailUtils.getFolderForURI(folderURI);
+ let name = this.gMessengerBundle.getFormattedString("verboseFolderFormat",
+ [folder.prettyName, folder.server.prettyName]);
+ folderNames.push(name);
+ }
+ foldersList.setAttribute("tooltiptext", folderNames.join("\n"));
+ } else {
+ foldersList.removeAttribute("tooltiptext");
+ }
+}
+
+function onEnterInSearchTerm()
+{
+ // stub function called by the core search widget code...
+ // nothing for us to do here
+}
diff --git a/mailnews/base/content/virtualFolderProperties.xul b/mailnews/base/content/virtualFolderProperties.xul
new file mode 100644
index 000000000..99c140e82
--- /dev/null
+++ b/mailnews/base/content/virtualFolderProperties.xul
@@ -0,0 +1,103 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/searchDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/searchTermOverlay.xul"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % folderDTD SYSTEM "chrome://messenger/locale/virtualFolderProperties.dtd">
+ %folderDTD;
+]>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="virtualFolderPropertiesDialog"
+ title="&virtualFolderProperties.title;"
+ onload="onLoad();"
+ buttons="accept,cancel"
+ newFolderAcceptButtonLabel="&newFolderButton.label;"
+ newFolderAcceptButtonAccessKey="&newFolderButton.accesskey;"
+ editFolderAcceptButtonLabel="&editFolderButton.label;"
+ editFolderAcceptButtonAccessKey="&editFolderButton.accesskey;"
+ style="width: 50em; height: 28em;"
+ windowtype="mailnews:virtualFolderProperties"
+ ondialogaccept="return onOK();">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_folder" src="chrome://messenger/locale/folderWidgets.properties"/>
+ </stringbundleset>
+
+ <script type="application/javascript" src="chrome://messenger/content/mailCommands.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/commandglue.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/virtualFolderProperties.js"/>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ <column flex="2"/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <label value="&name.label;" accesskey="&name.accesskey;" control="name"/>
+ <textbox id="name" hidden="true"
+ oninput="doEnabling();"/>
+ <filefield id="existingName" class="folderMenuItem"
+ hidden="true" tabindex="0"
+ SpecialFolder="Virtual"/>
+ <spring/>
+ </row>
+
+ <row align="center" id="chooseFolderLocationRow">
+ <label value="&description.label;" accesskey="&description.accesskey;"
+ control="msgNewFolderPicker"/>
+ <menulist id="msgNewFolderPicker" class="folderMenuItem" flex="1"
+ displayformat="verbose">
+ <menupopup id="msgNewFolderPopup"
+ class="menulist-menupopup"
+ type="folder"
+ mode="newFolder"
+ showFileHereLabel="true"
+ oncommand="onFolderPick(event);"/>
+ </menulist>
+ <spring/>
+ </row>
+
+ <row align="center">
+ <label value="&folderSelectionCaption.label;"/>
+ <hbox align="center">
+ <label id="chosenFoldersCount"/>
+ <spacer flex="1"/>
+ <button label="&chooseFoldersButton.label;"
+ accesskey="&chooseFoldersButton.accesskey;"
+ oncommand="chooseFoldersToSearch();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+
+ <hbox flex="1">
+ <checkbox id="searchOnline" label="&searchOnline.label;"
+ accesskey="&searchOnline.accesskey;"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <vbox flex="2">
+ <label value="&searchTermCaption.label;"/>
+ <hbox flex="1">
+ <vbox id="searchTermListBox" flex="2"/>
+ </hbox>
+ </vbox>
+
+</dialog>
+
diff --git a/mailnews/base/ispdata/README b/mailnews/base/ispdata/README
new file mode 100644
index 000000000..a4848db9f
--- /dev/null
+++ b/mailnews/base/ispdata/README
@@ -0,0 +1,3 @@
+gmail.rdf is up to date and is a good example to use for POP3
+movemail.rdf is new, from pkw@us.ibm.com
+aol.rdf is a bad example, it was used by Netscape 7.x
diff --git a/mailnews/base/ispdata/aol.rdf b/mailnews/base/ispdata/aol.rdf
new file mode 100644
index 000000000..901b234b8
--- /dev/null
+++ b/mailnews/base/ispdata/aol.rdf
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!DOCTYPE RDF
+ [
+ <!ENTITY aol.wizardLongName "AOL account (For example, jsmith@aol.com)">
+ <!ENTITY aol.emailIDFieldTitle "Screen Name:">
+ <!ENTITY aol.emailIDDescription "screen name">
+ <!ENTITY aol.prettyName "AOL Mail">
+ ]
+>
+
+<RDF:RDF
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+
+ <RDF:Description about="NC:ispinfo">
+ <NC:providers>
+ <NC:nsIMsgAccount about="domain:aol.com">
+
+ <!-- server info -->
+ <NC:incomingServer>
+ <NC:nsIMsgIncomingServer>
+ <NC:prettyName>&aol.prettyName;</NC:prettyName>
+ <NC:hostName>imap.mail.aol.com</NC:hostName>
+ <NC:type>imap
+ <NC:ServerType-imap>
+ <NC:nsIImapIncomingServer>
+ <NC:deleteModel>2</NC:deleteModel>
+ </NC:nsIImapIncomingServer>
+ </NC:ServerType-imap>
+ </NC:type>
+ <NC:offlineSupportLevel>0</NC:offlineSupportLevel>
+ <NC:defaultCopiesAndFoldersPrefsToServer>false</NC:defaultCopiesAndFoldersPrefsToServer>
+ <NC:canCreateFoldersOnServer>false</NC:canCreateFoldersOnServer>
+ <NC:canCompactFoldersOnServer>false</NC:canCompactFoldersOnServer>
+ <NC:canUndoDeleteOnServer>false</NC:canUndoDeleteOnServer>
+ <NC:canSearchMessages>false</NC:canSearchMessages>
+ <NC:canFileMessagesOnServer>false</NC:canFileMessagesOnServer>
+ <NC:canEmptyTrashOnExit>false</NC:canEmptyTrashOnExit>
+ <NC:isSecureServer>false</NC:isSecureServer>
+ <NC:canSearchMessages>false</NC:canSearchMessages>
+ </NC:nsIMsgIncomingServer>
+ </NC:incomingServer>
+
+ <!-- identity defaults -->
+ <NC:identity>
+ <NC:nsIMsgIdentity>
+ <NC:composeHtml>true</NC:composeHtml>
+ <NC:doFcc>false</NC:doFcc>
+ </NC:nsIMsgIdentity>
+ </NC:identity>
+
+ <NC:smtp>
+ <NC:nsISmtpServer>
+ <NC:hostname>smtp.aol.com</NC:hostname>
+ </NC:nsISmtpServer>
+ </NC:smtp>
+
+ <!-- other options... see http://www.mozilla.org/mailnews/?? -->
+
+ <NC:smtpRequiresUsername>true</NC:smtpRequiresUsername>
+ <NC:smtpCreateNewServer>true</NC:smtpCreateNewServer>
+ <NC:smtpUsePreferredServer>true</NC:smtpUsePreferredServer>
+
+ <NC:wizardSkipPanels>true</NC:wizardSkipPanels>
+ <NC:wizardShortName>AOL</NC:wizardShortName>
+ <NC:wizardLongName>&aol.wizardLongName;</NC:wizardLongName>
+ <NC:wizardShow>true</NC:wizardShow>
+ <NC:wizardPromote>true</NC:wizardPromote>
+ <NC:emailProviderName>AOL</NC:emailProviderName>
+ <NC:sampleEmail>screenname@aol.com</NC:sampleEmail>
+ <NC:sampleUserName>screenname</NC:sampleUserName>
+ <NC:emailIDDescription>&aol.emailIDDescription;</NC:emailIDDescription>
+ <NC:emailIDFieldTitle>&aol.emailIDFieldTitle;</NC:emailIDFieldTitle>
+ <NC:showServerDetailsOnWizardSummary>false</NC:showServerDetailsOnWizardSummary>
+ </NC:nsIMsgAccount>
+ </NC:providers>
+ </RDF:Description>
+
+</RDF:RDF>
diff --git a/mailnews/base/ispdata/gmail.rdf b/mailnews/base/ispdata/gmail.rdf
new file mode 100644
index 000000000..97a5d244d
--- /dev/null
+++ b/mailnews/base/ispdata/gmail.rdf
@@ -0,0 +1,127 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE RDF>
+<RDF:RDF
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+
+ <RDF:Description about="NC:ispinfo">
+ <NC:providers>
+ <!-- Gmail IMAP access -->
+ <NC:nsIMsgAccount about="domain:gmail.com?type=imap">
+ <!-- IMAP server info -->
+ <NC:incomingServer>
+ <NC:nsIMsgIncomingServer>
+ <NC:prettyName>Gmail</NC:prettyName>
+ <NC:hostName>imap.gmail.com</NC:hostName>
+ <NC:type>imap</NC:type>
+ <NC:ServerType-imap>
+ <NC:nsIImapIncomingServer>
+ <NC:usingSubscription>false</NC:usingSubscription>
+ <!-- IMAP delete model: "remove it immediately". -->
+ <NC:deleteModel>2</NC:deleteModel>
+ <NC:trashFolderName>[Gmail]/Trash</NC:trashFolderName>
+ </NC:nsIImapIncomingServer>
+ </NC:ServerType-imap>
+ <NC:loginAtStartUp>true</NC:loginAtStartUp>
+ <NC:port>993</NC:port>
+ <NC:socketType>3</NC:socketType>
+ </NC:nsIMsgIncomingServer>
+ </NC:incomingServer>
+
+ <!-- SMTP server info -->
+ <NC:smtp>
+ <NC:nsISmtpServer>
+ <NC:hostname>smtp.gmail.com</NC:hostname>
+ <NC:port>587</NC:port>
+ <NC:trySSL>2</NC:trySSL>
+ <NC:description>Gmail SMTP</NC:description>
+ </NC:nsISmtpServer>
+ </NC:smtp>
+ <NC:smtpRequiresUsername>true</NC:smtpRequiresUsername>
+ <NC:smtpCreateNewServer>true</NC:smtpCreateNewServer>
+ <NC:smtpUsePreferredServer>true</NC:smtpUsePreferredServer>
+
+ <!-- identity defaults -->
+ <NC:identity>
+ <NC:nsIMsgIdentity>
+ <!-- Don't store sent mail in a Sent folder, as Gmail will show it
+ in [Gmail]/Sent Mail anyway. -->
+ <NC:doFcc>false</NC:doFcc>
+ <NC:FccFolder>[Gmail]/Sent Mail</NC:FccFolder>
+ <NC:DraftFolder>[Gmail]/Drafts</NC:DraftFolder>
+ </NC:nsIMsgIdentity>
+ </NC:identity>
+
+ <!-- other options -->
+ <NC:wizardSkipPanels>true</NC:wizardSkipPanels>
+ <NC:wizardShortName>Gmail</NC:wizardShortName>
+ <NC:wizardLongName>Gmail IMAP</NC:wizardLongName>
+ <NC:wizardLongNameAccesskey>M</NC:wizardLongNameAccesskey>
+ <NC:wizardShow>true</NC:wizardShow>
+ <NC:wizardPromote>true</NC:wizardPromote>
+ <NC:emailProviderName>Gmail</NC:emailProviderName>
+ <NC:sampleEmail>example@gmail.com</NC:sampleEmail>
+ <NC:sampleUserName>example</NC:sampleUserName>
+ <NC:emailIDDescription>Gmail Username:</NC:emailIDDescription>
+ <NC:showServerDetailsOnWizardSummary>true</NC:showServerDetailsOnWizardSummary>
+ </NC:nsIMsgAccount>
+
+ <NC:nsIMsgAccount about="domain:gmail.com?type=pop3">
+ <!-- pop3 server info -->
+ <NC:incomingServer>
+ <NC:nsIMsgIncomingServer>
+ <NC:prettyName>Gmail</NC:prettyName>
+ <NC:hostName>pop.gmail.com</NC:hostName>
+ <NC:type>pop3</NC:type>
+ <NC:ServerType-pop3>
+ <NC:nsIPopIncomingServer>
+ <NC:leaveMessagesOnServer>true</NC:leaveMessagesOnServer>
+ <NC:deleteMailLeftOnServer>false</NC:deleteMailLeftOnServer>
+ </NC:nsIPopIncomingServer>
+ </NC:ServerType-pop3>
+ <NC:loginAtStartUp>true</NC:loginAtStartUp>
+ <NC:downloadOnBiff>true</NC:downloadOnBiff>
+ <NC:port>995</NC:port>
+ <NC:socketType>3</NC:socketType>
+ </NC:nsIMsgIncomingServer>
+ </NC:incomingServer>
+
+ <!-- smtp server info -->
+ <NC:smtp>
+ <NC:nsISmtpServer>
+ <NC:hostname>smtp.gmail.com</NC:hostname>
+ <NC:port>587</NC:port>
+ <NC:trySSL>2</NC:trySSL>
+ <NC:description>Gmail</NC:description>
+ </NC:nsISmtpServer>
+ </NC:smtp>
+ <NC:smtpRequiresUsername>true</NC:smtpRequiresUsername>
+ <NC:smtpCreateNewServer>true</NC:smtpCreateNewServer>
+ <NC:smtpUsePreferredServer>true</NC:smtpUsePreferredServer>
+
+ <!-- identity defaults -->
+ <NC:identity>
+ <NC:nsIMsgIdentity>
+ </NC:nsIMsgIdentity>
+ </NC:identity>
+
+ <!-- other options -->
+ <NC:wizardSkipPanels>true</NC:wizardSkipPanels>
+ <NC:wizardShortName>Gmail</NC:wizardShortName>
+ <NC:wizardLongName>Gmail POP</NC:wizardLongName>
+ <NC:wizardLongNameAccesskey>G</NC:wizardLongNameAccesskey>
+ <NC:wizardShow>true</NC:wizardShow>
+ <NC:wizardPromote>true</NC:wizardPromote>
+ <NC:emailProviderName>Gmail</NC:emailProviderName>
+ <NC:sampleEmail>example@gmail.com</NC:sampleEmail>
+ <NC:sampleUserName>example</NC:sampleUserName>
+ <NC:emailIDDescription>Gmail Username:</NC:emailIDDescription>
+ <NC:showServerDetailsOnWizardSummary>true</NC:showServerDetailsOnWizardSummary>
+ </NC:nsIMsgAccount>
+ </NC:providers>
+ </RDF:Description>
+</RDF:RDF>
diff --git a/mailnews/base/ispdata/movemail.rdf b/mailnews/base/ispdata/movemail.rdf
new file mode 100644
index 000000000..554bc6ccb
--- /dev/null
+++ b/mailnews/base/ispdata/movemail.rdf
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<RDF:RDF
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+
+ <RDF:Description about="NC:ispinfo">
+ <NC:providers>
+ <NC:nsIMsgAccount about="domain:">
+
+ <!-- server info -->
+ <NC:incomingServer>
+ <NC:nsIMsgIncomingServer>
+ <NC:hostName>localhost</NC:hostName>
+ <NC:type>movemail</NC:type>
+ </NC:nsIMsgIncomingServer>
+ </NC:incomingServer>
+
+ <!-- identity defaults -->
+ <NC:identity>
+ <NC:nsIMsgIdentity>
+ <NC:composeHtml>false</NC:composeHtml>
+ </NC:nsIMsgIdentity>
+ </NC:identity>
+
+ <NC:wizardHideIncoming>true</NC:wizardHideIncoming>
+ <NC:wizardSkipPanels>incomingpage</NC:wizardSkipPanels>
+ <NC:wizardShortName>Movemail</NC:wizardShortName>
+ <NC:wizardLongName>Unix Mailspool (Movemail)</NC:wizardLongName>
+ <NC:wizardLongNameAccesskey>U</NC:wizardLongNameAccesskey>
+ <NC:wizardShow>true</NC:wizardShow>
+ <NC:emailProviderName>Movemail</NC:emailProviderName>
+
+ <NC:showServerDetailsOnWizardSummary>true</NC:showServerDetailsOnWizardSummary>
+ </NC:nsIMsgAccount>
+ </NC:providers>
+ </RDF:Description>
+
+</RDF:RDF>
diff --git a/mailnews/base/ispdata/moz.build b/mailnews/base/ispdata/moz.build
new file mode 100644
index 000000000..7d3f82e91
--- /dev/null
+++ b/mailnews/base/ispdata/moz.build
@@ -0,0 +1,8 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Disable movemail for Thunderbird on OSX
+if CONFIG['MOZ_MOVEMAIL'] and not (CONFIG['MOZ_THUNDERBIRD'] and CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa'):
+ FINAL_TARGET_FILES.isp += ['movemail.rdf']
diff --git a/mailnews/base/moz.build b/mailnews/base/moz.build
new file mode 100644
index 000000000..17a9c0791
--- /dev/null
+++ b/mailnews/base/moz.build
@@ -0,0 +1,13 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'public',
+ 'src',
+ 'util',
+ 'ispdata',
+ 'search/public',
+ 'search/src',
+]
diff --git a/mailnews/base/prefs/content/AccountManager.js b/mailnews/base/prefs/content/AccountManager.js
new file mode 100644
index 000000000..5de98ecd0
--- /dev/null
+++ b/mailnews/base/prefs/content/AccountManager.js
@@ -0,0 +1,1604 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * here's how this dialog works:
+ * The main dialog contains a tree on the left (accounttree) and a
+ * deck on the right. Each card in the deck on the right contains an
+ * IFRAME which loads a particular preference document (such as am-main.xul)
+ *
+ * when the user clicks on items in the tree on the right, two things have
+ * to be determined before the UI can be updated:
+ * - the relevant account
+ * - the relevant page
+ *
+ * when both of these are known, this is what happens:
+ * - every form element of the previous page is saved in the account value
+ * hashtable for the previous account
+ * - the card containing the relevant page is brought to the front
+ * - each form element in the page is filled in with an appropriate value
+ * from the current account's hashtable
+ * - in the IFRAME inside the page, if there is an onInit() method,
+ * it is called. The onInit method can further update this page based
+ * on values set in the previous step.
+ */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/folderUtils.jsm");
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+
+// If Local directory has changed the app needs to restart. Once this is set
+// a restart will be attempted at each attempt to close the Account manager with OK.
+var gRestartNeeded = false;
+
+// This is a hash-map for every account we've touched in the pane. Each entry
+// has additional maps of attribute-value pairs that we're going to want to save
+// when the user hits OK.
+var accountArray;
+var gGenericAttributeTypes;
+
+var currentAccount;
+var currentPageId;
+
+var pendingAccount;
+var pendingPageId;
+
+/**
+ * This array contains filesystem folders that are deemed inappropriate
+ * for use as the local directory pref for message storage.
+ * It is global to allow extensions to add to/remove from it if needed.
+ * Extentions adding new server types should first consider setting
+ * nsIMsgProtocolInfo(of the server type).defaultLocalPath properly
+ * so that the test will allow that directory automatically.
+ * See the checkLocalDirectoryIsSafe function for description of the members.
+ */
+var gDangerousLocalStorageDirs = [
+ // profile folder
+ { dirsvc: "ProfD", OS: null },
+ // GRE install folder
+ { dirsvc: "GreD", OS: null },
+ // Application install folder
+ { dirsvc: "CurProcD", OS: null },
+ // system temporary folder
+ { dirsvc: "TmpD", OS: null },
+ // Windows system folder
+ { dirsvc: "SysD", OS: "WINNT" },
+ // Windows folder
+ { dirsvc: "WinD", OS: "WINNT" },
+ // Program Files folder
+ { dirsvc: "ProgF", OS: "WINNT" },
+ // trash folder
+ { dirsvc: "Trsh", OS: "Darwin" },
+ // Mac OS system folder
+ { dir: "/System", OS: "Darwin" },
+ // devices folder
+ { dir: "/dev", OS: "Darwin,Linux" },
+ // process info folder
+ { dir: "/proc", OS: "Linux" },
+ // system state folder
+ { dir: "/sys", OS: "Linux" }
+];
+
+// This sets an attribute in a xul element so that we can later
+// know what value to substitute in a prefstring. Different
+// preference types set different attributes. We get the value
+// in the same way as the function getAccountValue() determines it.
+function updateElementWithKeys(account, element, type) {
+ switch (type)
+ {
+ case "identity":
+ element["identitykey"] = account.defaultIdentity.key;
+ break;
+ case "pop3":
+ case "imap":
+ case "nntp":
+ case "server":
+ element["serverkey"] = account.incomingServer.key;
+ break;
+ case "smtp":
+ if (MailServices.smtp.defaultServer)
+ element["serverkey"] = MailServices.smtp.defaultServer.key;
+ break;
+ default:
+// dump("unknown element type! "+type+"\n");
+ }
+}
+
+function hideShowControls(serverType) {
+ let controls = document.querySelectorAll("[hidefor]");
+ for (let controlNo = 0; controlNo < controls.length; controlNo++) {
+ let control = controls[controlNo];
+ let hideFor = control.getAttribute("hidefor");
+
+ // Hide unsupported server types using hideFor="servertype1,servertype2".
+ let hide = false;
+ let hideForTokens = hideFor.split(",");
+ for (let tokenNo = 0; tokenNo < hideForTokens.length; tokenNo++) {
+ if (hideForTokens[tokenNo] == serverType) {
+ hide = true;
+ break;
+ }
+ }
+
+ if (hide)
+ control.setAttribute("hidden", "true");
+ else
+ control.removeAttribute("hidden");
+ }
+}
+
+// called when the whole document loads
+// perform initialization here
+function onLoad() {
+ var selectedServer;
+ var selectPage = null;
+
+ // Arguments can have two properties: (1) "server," the nsIMsgIncomingServer
+ // to select initially and (2) "selectPage," the page for that server to that
+ // should be selected.
+ if ("arguments" in window && window.arguments[0]) {
+ selectedServer = window.arguments[0].server;
+ selectPage = window.arguments[0].selectPage;
+ }
+
+ accountArray = new Object();
+ gGenericAttributeTypes = new Object();
+
+ gAccountTree.load();
+
+ setTimeout(selectServer, 0, selectedServer, selectPage);
+
+ // Make sure the account manager window fits the screen.
+ document.getElementById("accountManager").style.maxHeight =
+ (window.screen.availHeight - 30) + "px";
+}
+
+function onUnload() {
+ gAccountTree.unload();
+}
+
+function selectServer(server, selectPageId)
+{
+ let childrenNode = document.getElementById("account-tree-children");
+
+ // Default to showing the first account.
+ let accountNode = childrenNode.firstChild;
+
+ // Find the tree-node for the account we want to select
+ if (server) {
+ for (let i = 0; i < childrenNode.childNodes.length; i++) {
+ let account = childrenNode.childNodes[i]._account;
+ if (account && server == account.incomingServer) {
+ accountNode = childrenNode.childNodes[i];
+ // Make sure all the panes of the account to be selected are shown.
+ accountNode.setAttribute("open", "true");
+ break;
+ }
+ }
+ }
+
+ let pageToSelect = accountNode;
+
+ if (selectPageId) {
+ // Find the page that also corresponds to this server.
+ // It either is the accountNode itself...
+ let pageId = accountNode.getAttribute("PageTag");
+ if (pageId != selectPageId) {
+ // ... or one of its children.
+ pageToSelect = accountNode.querySelector('[PageTag="' + selectPageId + '"]');
+ }
+ }
+
+ let accountTree = document.getElementById("accounttree");
+ let index = accountTree.contentView.getIndexOfItem(pageToSelect);
+ accountTree.view.selection.select(index);
+ accountTree.treeBoxObject.ensureRowIsVisible(index);
+
+ let lastItem = accountNode.lastChild.lastChild;
+ if (lastItem.localName == "treeitem")
+ index = accountTree.contentView.getIndexOfItem(lastItem);
+
+ accountTree.treeBoxObject.ensureRowIsVisible(index);
+}
+
+function replaceWithDefaultSmtpServer(deletedSmtpServerKey)
+{
+ // First we replace the smtpserverkey in every identity.
+ let am = MailServices.accounts;
+ for (let identity in fixIterator(am.allIdentities,
+ Components.interfaces.nsIMsgIdentity)) {
+ if (identity.smtpServerKey == deletedSmtpServerKey)
+ identity.smtpServerKey = "";
+ }
+
+ // When accounts have already been loaded in the panel then the first
+ // replacement will be overwritten when the accountvalues are written out
+ // from the pagedata. We get the loaded accounts and check to make sure
+ // that the account exists for the accountid and that it has a default
+ // identity associated with it (to exclude smtpservers and local folders)
+ // Then we check only for the identity[type] and smtpServerKey[slot] and
+ // replace that with the default smtpserverkey if necessary.
+
+ for (var accountid in accountArray) {
+ var account = accountArray[accountid]._account;
+ if (account && account.defaultIdentity) {
+ var accountValues = accountArray[accountid];
+ var smtpServerKey = getAccountValue(account, accountValues, "identity",
+ "smtpServerKey", null, false);
+ if (smtpServerKey == deletedSmtpServerKey)
+ setAccountValue(accountValues, "identity", "smtpServerKey", "");
+ }
+ }
+}
+
+/**
+ * Called when OK is clicked on the dialog.
+ *
+ * @param aDoChecks If true, execute checks on data, otherwise hope they
+ * were already done elsewhere and proceed directly to saving
+ * the data.
+ */
+function onAccept(aDoChecks) {
+ if (aDoChecks) {
+ // Check if user/host have been modified correctly.
+ if (!checkUserServerChanges(true))
+ return false;
+
+ if (!checkAccountNameIsValid())
+ return false;
+ }
+
+ if (!onSave())
+ return false;
+
+ // hack hack - save the prefs file NOW in case we crash
+ Services.prefs.savePrefFile(null);
+
+ if (gRestartNeeded) {
+ gRestartNeeded = !BrowserUtils.restartApplication();
+ // returns false so that Account manager is not exited when restart failed
+ return !gRestartNeeded;
+ }
+
+ return true;
+}
+
+/**
+ * See if the given path to a directory is usable on the current OS.
+ *
+ * aLocalPath the nsIFile of a directory to check.
+ */
+function checkDirectoryIsValid(aLocalPath) {
+ // Any directory selected in the file picker already exists.
+ // Any directory specified in prefs.js will be created at start if it does
+ // not exist yet.
+ // If at the time of entering Account Manager the directory does not exist,
+ // it must be invalid in the current OS or not creatable due to permissions.
+ // Even then, the backend sometimes tries to create a new one
+ // under the current profile.
+ if (!aLocalPath.exists() || !aLocalPath.isDirectory())
+ return false;
+
+ if (Services.appinfo.OS == "WINNT") {
+ // Do not allow some special filenames on Windows.
+ // Taken from mozilla/widget/windows/nsDataObj.cpp::MangleTextToValidFilename()
+ let dirLeafName = aLocalPath.leafName;
+ const kForbiddenNames = [
+ "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
+ "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
+ "CON", "PRN", "AUX", "NUL", "CLOCK$" ];
+ if (kForbiddenNames.indexOf(dirLeafName) != -1)
+ return false;
+ }
+
+ // The directory must be readable and writable to work as a mail store.
+ if (!(aLocalPath.isReadable() && aLocalPath.isWritable()))
+ return false;
+
+ return true;
+}
+
+/**
+ * Even if the local path is usable, there are some special folders we do not
+ * want to allow for message storage as they cause problems (see e.g. bug 750781).
+ *
+ * aLocalPath The nsIFile of a directory to check.
+ */
+function checkDirectoryIsAllowed(aLocalPath) {
+ /**
+ * Check if the local path (aLocalPath) is 'safe' i.e. NOT a parent
+ * or subdirectory of the given special system/app directory (aDirToCheck).
+ *
+ * @param aDirToCheck An object describing the special directory.
+ * The object has the following members:
+ * dirsvc : A path keyword to retrieve from the Directory service.
+ * dir : An absolute filesystem path.
+ * Only one of 'dirsvc' or 'dir' can be specified.
+ * OS : A string of comma separated values defining on which
+ * Operating systems the folder is unusable:
+ * null = all
+ * WINNT = Windows
+ * Darwin = OS X
+ * Linux = Linux
+ * safeSubdirs : An array of directory names that are allowed to be used
+ * under the tested directory.
+ * @param aLocalPath An nsIFile of the directory to check, intended for message storage.
+ */
+ function checkLocalDirectoryIsSafe(aDirToCheck, aLocalPath) {
+ if (aDirToCheck.OS) {
+ if (aDirToCheck.OS.split(",").indexOf(Services.appinfo.OS) == -1)
+ return true;
+ }
+
+ let testDir = null;
+ if ("dirsvc" in aDirToCheck) {
+ try {
+ testDir = Services.dirsvc.get(aDirToCheck.dirsvc, Components.interfaces.nsIFile);
+ } catch (e) {
+ Components.utils.reportError("The special folder " + aDirToCheck.dirsvc +
+ " cannot be retrieved on this platform: " + e);
+ }
+
+ if (!testDir)
+ return true;
+ }
+ else if ("dir" in aDirToCheck) {
+ testDir = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsIFile);
+ testDir.initWithPath(aDirToCheck.dir);
+ if (!testDir.exists())
+ return true;
+ } else {
+ Components.utils.reportError("No directory to check?");
+ return true;
+ }
+
+ testDir.normalize();
+
+ if (testDir.equals(aLocalPath) || aLocalPath.contains(testDir))
+ return false;
+
+ if (testDir.contains(aLocalPath)) {
+ if (!("safeSubdirs" in aDirToCheck))
+ return false;
+
+ // While the tested directory may not be safe,
+ // a subdirectory of some safe subdirectories may be fine.
+ let isInSubdir = false;
+ for (let subDir of aDirToCheck.safeSubdirs) {
+ let checkDir = testDir.clone();
+ checkDir.append(subDir);
+ if (checkDir.contains(aLocalPath)) {
+ isInSubdir = true;
+ break;
+ }
+ }
+ return isInSubdir;
+ }
+
+ return true;
+ } // end of checkDirectoryIsNotSpecial
+
+ // If the server type has a nsIMsgProtocolInfo.defaultLocalPath set,
+ // allow that directory.
+ if (currentAccount.incomingServer) {
+ try {
+ let defaultPath = currentAccount.incomingServer.protocolInfo.defaultLocalPath;
+ if (defaultPath) {
+ defaultPath.normalize();
+ if (defaultPath.contains(aLocalPath))
+ return true;
+ }
+ } catch (e) { /* No problem if this fails. */ }
+ }
+
+ for (let tryDir of gDangerousLocalStorageDirs) {
+ if (!checkLocalDirectoryIsSafe(tryDir, aLocalPath))
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Check if the specified directory does meet all the requirements
+ * for safe mail storage.
+ *
+ * aLocalPath the nsIFile of a directory to check.
+ */
+function checkDirectoryIsUsable(aLocalPath) {
+ const kAlertTitle = document.getElementById("bundle_prefs")
+ .getString("prefPanel-server");
+ const originalPath = aLocalPath;
+
+ let invalidPath = false;
+ try{
+ aLocalPath.normalize();
+ } catch (e) { invalidPath = true; }
+
+ if (invalidPath || !checkDirectoryIsValid(aLocalPath)) {
+ let alertString = document.getElementById("bundle_prefs")
+ .getFormattedString("localDirectoryInvalid",
+ [originalPath.path]);
+ Services.prompt.alert(window, kAlertTitle, alertString);
+ return false;
+ }
+
+ if (!checkDirectoryIsAllowed(aLocalPath)) {
+ let alertNotAllowed = document.getElementById("bundle_prefs")
+ .getFormattedString("localDirectoryNotAllowed",
+ [originalPath.path]);
+ Services.prompt.alert(window, kAlertTitle, alertNotAllowed);
+ return false;
+ }
+
+ // Check that no other account has this same or dependent local directory.
+ let allServers = MailServices.accounts.allServers;
+
+ for (let server in fixIterator(allServers,
+ Components.interfaces.nsIMsgIncomingServer))
+ {
+ if (server.key == currentAccount.incomingServer.key)
+ continue;
+
+ let serverPath = server.localPath;
+ try {
+ serverPath.normalize();
+ let alertStringID = null;
+ if (serverPath.equals(aLocalPath))
+ alertStringID = "directoryAlreadyUsedByOtherAccount";
+ else if (serverPath.contains(aLocalPath))
+ alertStringID = "directoryParentUsedByOtherAccount";
+ else if (aLocalPath.contains(serverPath))
+ alertStringID = "directoryChildUsedByOtherAccount";
+
+ if (alertStringID) {
+ let alertString = document.getElementById("bundle_prefs")
+ .getFormattedString(alertStringID,
+ [server.prettyName]);
+
+ Services.prompt.alert(window, kAlertTitle, alertString);
+ return false;
+ }
+ } catch (e) {
+ // The other account's path is seriously broken, so we can't compare it.
+ Components.utils.reportError("The Local Directory path of the account " +
+ server.prettyName + " seems invalid.");
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Check if the user and/or host names have been changed and if so check
+ * if the new names already exists for an account or are empty.
+ * Also check if the Local Directory path was changed.
+ *
+ * @param showAlert show and alert if a problem with the host / user name is found
+ */
+function checkUserServerChanges(showAlert) {
+ const prefBundle = document.getElementById("bundle_prefs");
+ const alertTitle = prefBundle.getString("prefPanel-server");
+ var alertText = null;
+
+ var accountValues = getValueArrayFor(currentAccount);
+ if (!accountValues)
+ return true;
+
+ let currentServer = currentAccount ? currentAccount.incomingServer : null;
+
+ // If this type doesn't exist (just removed) then return.
+ if (!("server" in accountValues) || !accountValues["server"])
+ return true;
+
+ // Get the new username, hostname and type from the page.
+ var typeElem = getPageFormElement("server.type");
+ var hostElem = getPageFormElement("server.realHostName");
+ var userElem = getPageFormElement("server.realUsername");
+ if (typeElem && userElem && hostElem) {
+ var newType = getFormElementValue(typeElem);
+ var oldHost = getAccountValue(currentAccount, accountValues, "server", "realHostName",
+ null, false);
+ var newHost = getFormElementValue(hostElem);
+ var oldUser = getAccountValue(currentAccount, accountValues, "server", "realUsername",
+ null, false);
+
+ var newUser = getFormElementValue(userElem);
+ var checkUser = true;
+ // There is no username needed for e.g. news so reset it.
+ if (currentServer && !currentServer.protocolInfo.requiresUsername) {
+ oldUser = newUser = "";
+ checkUser = false;
+ }
+ alertText = null;
+ // If something is changed then check if the new user/host already exists.
+ if ((oldUser != newUser) || (oldHost != newHost)) {
+ newUser = newUser.trim();
+ newHost = cleanUpHostName(newHost);
+ if (checkUser && (newUser == "")) {
+ alertText = prefBundle.getString("userNameEmpty");
+ }
+ else if (!isLegalHostNameOrIP(newHost)) {
+ alertText = prefBundle.getString("enterValidServerName");
+ }
+ else {
+ let sameServer = MailServices.accounts
+ .findRealServer(newUser, newHost, newType, 0);
+ if (sameServer && (sameServer != currentServer)) {
+ alertText = prefBundle.getString("modifiedAccountExists");
+ } else {
+ // New hostname passed all checks. We may have cleaned it up so set
+ // the new value back into the input element.
+ setFormElementValue(hostElem, newHost);
+ }
+ }
+
+ if (alertText) {
+ if (showAlert)
+ Services.prompt.alert(window, alertTitle, alertText);
+ // Restore the old values before return
+ if (checkUser)
+ setFormElementValue(userElem, oldUser);
+ setFormElementValue(hostElem, oldHost);
+ // If no message is shown to the user, silently revert the values
+ // and consider the check a success.
+ return !showAlert;
+ }
+
+ // If username is changed remind users to change Your Name and Email Address.
+ // If server name is changed and has defined filters then remind users
+ // to edit rules.
+ if (showAlert) {
+ let filterList;
+ if (currentServer && checkUser) {
+ filterList = currentServer.getEditableFilterList(null);
+ }
+ let changeText = "";
+ if ((oldHost != newHost) &&
+ (filterList != undefined) && filterList.filterCount)
+ changeText = prefBundle.getString("serverNameChanged");
+ // In the event that oldHost == newHost or oldUser == newUser,
+ // the \n\n will be trimmed off before the message is shown.
+ if (oldUser != newUser)
+ changeText = changeText + "\n\n" + prefBundle.getString("userNameChanged");
+
+ if (changeText != "")
+ Services.prompt.alert(window, alertTitle, changeText.trim());
+ }
+ }
+ }
+
+ // Check the new value of the server.localPath field for validity.
+ var pathElem = getPageFormElement("server.localPath");
+ if (!pathElem)
+ return true;
+
+ if (!checkDirectoryIsUsable(getFormElementValue(pathElem))) {
+// return false; // Temporarily disable this. Just show warning but do not block. See bug 921371.
+ Components.utils.reportError("Local directory '" +
+ getFormElementValue(pathElem).path + "' of account " +
+ currentAccount.key + " is not safe to use. Consider changing it.");
+ }
+
+ // Warn if the Local directory path was changed.
+ // This can be removed once bug 2654 is fixed.
+ let oldLocalDir = getAccountValue(currentAccount, accountValues, "server", "localPath",
+ null, false); // both return nsIFile
+ let newLocalDir = getFormElementValue(pathElem);
+ if (oldLocalDir && newLocalDir && (oldLocalDir.path != newLocalDir.path)) {
+ let brandName = document.getElementById("bundle_brand").getString("brandShortName");
+ alertText = prefBundle.getFormattedString("localDirectoryChanged", [brandName]);
+
+ let cancel = Services.prompt.confirmEx(window, alertTitle, alertText,
+ (Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING) +
+ (Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL),
+ prefBundle.getString("localDirectoryRestart"), null, null, null, {});
+ if (cancel) {
+ setFormElementValue(pathElem, oldLocalDir);
+ return false;
+ }
+ gRestartNeeded = true;
+ }
+
+ return true;
+}
+
+/**
+ * If account name is not valid, alert the user.
+ */
+function checkAccountNameIsValid() {
+ if (!currentAccount)
+ return true;
+
+ const prefBundle = document.getElementById("bundle_prefs");
+ let alertText = null;
+
+ let serverNameElem = getPageFormElement("server.prettyName");
+ if (serverNameElem) {
+ let accountName = getFormElementValue(serverNameElem);
+
+ if (!accountName)
+ alertText = prefBundle.getString("accountNameEmpty");
+ else if (accountNameExists(accountName, currentAccount.key))
+ alertText = prefBundle.getString("accountNameExists");
+
+ if (alertText) {
+ const alertTitle = prefBundle.getString("accountWizard");
+ Services.prompt.alert(window, alertTitle, alertText);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function onSave() {
+ if (pendingPageId) {
+ dump("ERROR: " + pendingPageId + " hasn't loaded yet! Not saving.\n");
+ return false;
+ }
+
+ // make sure the current visible page is saved
+ savePage(currentAccount);
+
+ for (var accountid in accountArray) {
+ var accountValues = accountArray[accountid];
+ var account = accountArray[accountid]._account;
+ if (!saveAccount(accountValues, account))
+ return false;
+ }
+
+ return true;
+}
+
+function onAddAccount() {
+ MsgAccountWizard();
+}
+
+function AddMailAccount()
+{
+ NewMailAccount(MailServices.mailSession.topmostMsgWindow);
+}
+
+/**
+ * Highlight the default account row in the account tree,
+ * optionally un-highlight the previous one.
+ *
+ * @param newDefault The account that has become the new default.
+ * Can be given as null if there is none.
+ * @param oldDefault The account that has stopped being the default.
+ * Can be given as null if there was none.
+ */
+function markDefaultServer(newDefault, oldDefault) {
+ if (oldDefault == newDefault)
+ return;
+
+ let accountTreeNodes = document.getElementById("account-tree-children")
+ .childNodes;
+ for (let i = 0; i < accountTreeNodes.length; i++) {
+ let accountNode = accountTreeNodes[i];
+ if (newDefault && newDefault == accountNode._account) {
+ let props = accountNode.firstChild.firstChild.getAttribute("properties");
+ accountNode.firstChild.firstChild
+ .setAttribute("properties", props + " isDefaultServer-true");
+ }
+ if (oldDefault && oldDefault == accountNode._account) {
+ let props = accountNode.firstChild.firstChild.getAttribute("properties");
+ props = props.replace(/isDefaultServer-true/, "");
+ accountNode.firstChild.firstChild.setAttribute("properties", props);
+ }
+ }
+}
+
+/**
+ * Make currentAccount (currently selected in the account tree) the default one.
+ */
+function onSetDefault(event) {
+ // Make sure this function was not called while the control item is disabled
+ if (event.target.getAttribute("disabled") == "true")
+ return;
+
+ let previousDefault = MailServices.accounts.defaultAccount;
+ MailServices.accounts.defaultAccount = currentAccount;
+ markDefaultServer(currentAccount, previousDefault);
+
+ // This is only needed on Seamonkey which has this button.
+ setEnabled(document.getElementById("setDefaultButton"), false);
+}
+
+function onRemoveAccount(event) {
+ if (event.target.getAttribute("disabled") == "true" || !currentAccount)
+ return;
+
+ let server = currentAccount.incomingServer;
+
+ let canDelete = server.protocolInfo.canDelete || server.canDelete;
+ if (!canDelete)
+ return;
+
+ let serverList = [];
+ let accountTreeNode = document.getElementById("account-tree-children");
+ // build the list of servers in the account tree (order is important)
+ for (let i = 0; i < accountTreeNode.childNodes.length; i++) {
+ if ("_account" in accountTreeNode.childNodes[i]) {
+ let curServer = accountTreeNode.childNodes[i]._account.incomingServer;
+ if (serverList.indexOf(curServer) == -1)
+ serverList.push(curServer);
+ }
+ }
+
+ // get position of the current server in the server list
+ let serverIndex = serverList.indexOf(server);
+
+ // After the current server is deleted, choose the next server/account,
+ // or the previous one if the last one was deleted.
+ if (serverIndex == serverList.length - 1)
+ serverIndex--;
+ else
+ serverIndex++;
+
+ // Need to save these before the account and its server is removed.
+ let serverId = server.serverURI;
+
+ // Confirm account deletion.
+ let removeArgs = { server: server, account: currentAccount,
+ result: false };
+
+ window.openDialog("chrome://messenger/content/removeAccount.xul",
+ "removeAccount",
+ "chrome,titlebar,modal,centerscreen,resizable=no",
+ removeArgs);
+
+ // If result is true, the account was removed.
+ if (!removeArgs.result)
+ return;
+
+ // clear cached data out of the account array
+ currentAccount = currentPageId = null;
+ if (serverId in accountArray) {
+ delete accountArray[serverId];
+ }
+
+ if ((serverIndex >= 0) && (serverIndex < serverList.length))
+ selectServer(serverList[serverIndex], null);
+
+ // Either the default account was deleted so there is a new one
+ // or the default account was not changed. Either way, there is
+ // no need to unmark the old one.
+ markDefaultServer(MailServices.accounts.defaultAccount, null);
+}
+
+function saveAccount(accountValues, account)
+{
+ var identity = null;
+ var server = null;
+
+ if (account) {
+ identity = account.defaultIdentity;
+ server = account.incomingServer;
+ }
+
+ for (var type in accountValues) {
+ var typeArray = accountValues[type];
+
+ for (var slot in typeArray) {
+ var dest;
+ try {
+ if (type == "identity")
+ dest = identity;
+ else if (type == "server")
+ dest = server;
+ else if (type == "pop3")
+ dest = server.QueryInterface(Components.interfaces.nsIPop3IncomingServer);
+ else if (type == "imap")
+ dest = server.QueryInterface(Components.interfaces.nsIImapIncomingServer);
+ else if (type == "none")
+ dest = server.QueryInterface(Components.interfaces.nsINoIncomingServer);
+ else if (type == "nntp")
+ dest = server.QueryInterface(Components.interfaces.nsINntpIncomingServer);
+ else if (type == "smtp")
+ dest = MailServices.smtp.defaultServer;
+
+ } catch (ex) {
+ // don't do anything, just means we don't support that
+ }
+ if (dest == undefined) continue;
+
+ if ((type in gGenericAttributeTypes) && (slot in gGenericAttributeTypes[type])) {
+ var methodName = "get";
+ switch (gGenericAttributeTypes[type][slot]) {
+ case "int":
+ methodName += "Int";
+ break;
+ case "wstring":
+ methodName += "Unichar";
+ break;
+ case "string":
+ methodName += "Char";
+ break;
+ case "bool":
+ // in some cases
+ // like for radiogroups of type boolean
+ // the value will be "false" instead of false
+ // we need to convert it.
+ if (typeArray[slot] == "false")
+ typeArray[slot] = false;
+ else if (typeArray[slot] == "true")
+ typeArray[slot] = true;
+
+ methodName += "Bool";
+ break;
+ default:
+ dump("unexpected preftype: " + preftype + "\n");
+ break;
+ }
+ methodName += ((methodName + "Value") in dest ? "Value" : "Attribute");
+ if (dest[methodName](slot) != typeArray[slot]) {
+ methodName = methodName.replace("get", "set");
+ dest[methodName](slot, typeArray[slot]);
+ }
+ }
+ else if (slot in dest && typeArray[slot] != undefined && dest[slot] != typeArray[slot]) {
+ try {
+ dest[slot] = typeArray[slot];
+ } catch (ex) {
+ // hrm... need to handle special types here
+ }
+ }
+ }
+ }
+
+ // if we made account changes to the spam settings, we'll need to re-initialize
+ // our settings object
+ if (server && server.spamSettings) {
+ try {
+ server.spamSettings.initialize(server);
+ } catch(e) {
+ let accountName = getAccountValue(account, getValueArrayFor(account), "server",
+ "prettyName", null, false);
+ let alertText = document.getElementById("bundle_prefs")
+ .getFormattedString("junkSettingsBroken", [accountName]);
+ let review = Services.prompt.confirmEx(window, null, alertText,
+ (Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES) +
+ (Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO),
+ null, null, null, null, {});
+ if (!review) {
+ onAccountTreeSelect("am-junk.xul", account);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Set enabled/disabled state for account actions buttons.
+ * Called by all apps, but if the buttons do not exist, exits early.
+ */
+function updateButtons(tree, account) {
+ let addAccountButton = document.getElementById("addAccountButton");
+ let removeButton = document.getElementById("removeButton");
+ let setDefaultButton = document.getElementById("setDefaultButton");
+
+ if (!addAccountButton && !removeButton && !setDefaultButton)
+ return; // Thunderbird isn't using these.
+
+ updateItems(tree, account, addAccountButton, setDefaultButton, removeButton);
+ updateBlockedItems([addAccountButton, setDefaultButton, removeButton], false);
+}
+
+/**
+ * Set enabled/disabled state for the actions in the Account Actions menu.
+ * Called only by Thunderbird.
+ */
+function initAccountActionsButtons(menupopup) {
+ updateItems(
+ document.getElementById("accounttree"),
+ getCurrentAccount(),
+ document.getElementById("accountActionsAddMailAccount"),
+ document.getElementById("accountActionsDropdownSetDefault"),
+ document.getElementById("accountActionsDropdownRemove"));
+
+ updateBlockedItems(menupopup.childNodes, true);
+}
+
+/**
+ * Determine enabled/disabled state for the passed in elements
+ * representing account actions.
+ */
+function updateItems(tree, account, addAccountItem, setDefaultItem, removeItem) {
+ // Start with items disabled and then find out what can be enabled.
+ let canSetDefault = false;
+ let canDelete = false;
+
+ if (account && (tree.view.selection.count >= 1)) {
+ // Only try to check properties if there was anything selected in the tree
+ // and it belongs to an account.
+ // Otherwise we have either selected a SMTP server, or there is some
+ // problem. Either way, we don't want the user to act on it.
+ let server = account.incomingServer;
+
+ if (account != MailServices.accounts.defaultAccount &&
+ server.canBeDefaultServer && account.identities.length > 0)
+ canSetDefault = true;
+
+ canDelete = server.protocolInfo.canDelete || server.canDelete;
+ }
+
+ setEnabled(addAccountItem, true);
+ setEnabled(setDefaultItem, canSetDefault);
+ setEnabled(removeItem, canDelete);
+}
+
+/**
+ * Disable buttons/menu items if their control preference is locked.
+ * SeaMonkey: Not currently handled by WSM or the main loop yet
+ * since these buttons aren't under the IFRAME.
+ *
+ * @param aItems an array or NodeList of elements to be checked
+ * @param aMustBeTrue if true then the pref must be boolean and set to true
+ * to trigger the disabling (TB requires this, SM not)
+ */
+function updateBlockedItems(aItems, aMustBeTrue) {
+ for (let item of aItems) {
+ let prefstring = item.getAttribute("prefstring");
+ if (!prefstring)
+ continue;
+
+ if (Services.prefs.prefIsLocked(prefstring) &&
+ (!aMustBeTrue || Services.prefs.getBoolPref(prefstring)))
+ item.setAttribute("disabled", true);
+ }
+}
+
+/**
+ * Set enabled/disabled state for the control.
+ */
+function setEnabled(control, enabled)
+{
+ if (!control)
+ return;
+
+ if (enabled)
+ control.removeAttribute("disabled");
+ else
+ control.setAttribute("disabled", true);
+}
+
+// Called when someone clicks on an account. Figure out context by what they
+// clicked on. This is also called when an account is removed. In this case,
+// nothing is selected.
+function onAccountTreeSelect(pageId, account)
+{
+ let tree = document.getElementById("accounttree");
+
+ let changeView = pageId && account;
+ if (!changeView) {
+ if (tree.view.selection.count < 1)
+ return false;
+
+ let node = tree.contentView.getItemAtIndex(tree.currentIndex);
+ account = ("_account" in node) ? node._account : null;
+
+ pageId = node.getAttribute("PageTag")
+ }
+
+ if (pageId == currentPageId && account == currentAccount)
+ return true;
+
+ if (document.getElementById("contentFrame").contentDocument.getElementById("server.localPath")) {
+ // Check if user/host names have been changed or the Local Directory is invalid.
+ if (!checkUserServerChanges(false)) {
+ changeView = true;
+ account = currentAccount;
+ pageId = currentPageId;
+ }
+
+ if (gRestartNeeded)
+ onAccept(false);
+ }
+
+ if (document.getElementById("contentFrame").contentDocument.getElementById("server.prettyName")) {
+ // Check if account name is valid.
+ if (!checkAccountNameIsValid()) {
+ changeView = true;
+ account = currentAccount;
+ pageId = currentPageId;
+ }
+ }
+
+ if (currentPageId) {
+ // Change focus to the account tree first so that any 'onchange' handlers
+ // on elements in the current page have a chance to run before the page
+ // is saved and replaced by the new one.
+ tree.focus();
+ }
+
+ // save the previous page
+ savePage(currentAccount);
+
+ let changeAccount = (account != currentAccount);
+
+ if (changeView)
+ selectServer(account.incomingServer, pageId);
+
+ if (pageId != currentPageId) {
+ // loading a complete different page
+
+ // prevent overwriting with bad stuff
+ currentAccount = currentPageId = null;
+
+ pendingAccount = account;
+ pendingPageId = pageId;
+ loadPage(pageId);
+ } else if (changeAccount) {
+ // same page, different server
+ restorePage(pageId, account);
+ }
+
+ if (changeAccount)
+ updateButtons(tree, account);
+
+ return true;
+}
+
+// page has loaded
+function onPanelLoaded(pageId) {
+ if (pageId != pendingPageId) {
+ // if we're reloading the current page, we'll assume the
+ // page has asked itself to be completely reloaded from
+ // the prefs. to do this, clear out the the old entry in
+ // the account data, and then restore theh page
+ if (pageId == currentPageId) {
+ var serverId = currentAccount ?
+ currentAccount.incomingServer.serverURI : "global"
+ delete accountArray[serverId];
+ restorePage(currentPageId, currentAccount);
+ }
+ } else {
+ restorePage(pendingPageId, pendingAccount);
+ }
+
+ // probably unnecessary, but useful for debugging
+ pendingAccount = null;
+ pendingPageId = null;
+}
+
+function pageURL(pageId)
+{
+ // If we have a special non account manager pane (e.g. about:blank),
+ // do not translate it into ChromePackageName URL.
+ if (!pageId.startsWith("am-"))
+ return pageId;
+
+ let chromePackageName;
+ try {
+ // we could compare against "main","server","copies","offline","addressing",
+ // "smtp" and "advanced" first to save the work, but don't,
+ // as some of these might be turned into extensions (for thunderbird)
+ let packageName = pageId.split("am-")[1].split(".xul")[0];
+ chromePackageName = MailServices.accounts.getChromePackageName(packageName);
+ }
+ catch (ex) {
+ chromePackageName = "messenger";
+ }
+ return "chrome://" + chromePackageName + "/content/" + pageId;
+}
+
+function loadPage(pageId)
+{
+ const LOAD_FLAGS_NONE = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
+ document.getElementById("contentFrame").webNavigation.loadURI(pageURL(pageId),
+ LOAD_FLAGS_NONE, null, null, null);
+}
+
+// save the values of the widgets to the given server
+function savePage(account)
+{
+ if (!account)
+ return;
+
+ // tell the page that it's about to save
+ if ("onSave" in top.frames["contentFrame"])
+ top.frames["contentFrame"].onSave();
+
+ var accountValues = getValueArrayFor(account);
+ if (!accountValues)
+ return;
+
+ var pageElements = getPageFormElements();
+ if (!pageElements)
+ return;
+
+ // store the value in the account
+ for (let i = 0; i < pageElements.length; i++) {
+ if (pageElements[i].id) {
+ let vals = pageElements[i].id.split(".");
+ if (vals.length >= 2) {
+ let type = vals[0];
+ let slot = pageElements[i].id.slice(type.length + 1);
+
+ setAccountValue(accountValues,
+ type, slot,
+ getFormElementValue(pageElements[i]));
+ }
+ }
+ }
+}
+
+function setAccountValue(accountValues, type, slot, value) {
+ if (!(type in accountValues))
+ accountValues[type] = new Object();
+
+ accountValues[type][slot] = value;
+}
+
+function getAccountValue(account, accountValues, type, slot, preftype, isGeneric) {
+ if (!(type in accountValues))
+ accountValues[type] = new Object();
+
+ // fill in the slot from the account if necessary
+ if (!(slot in accountValues[type]) || accountValues[type][slot] == undefined) {
+ var server;
+ if (account)
+ server= account.incomingServer;
+ var source = null;
+ try {
+ if (type == "identity")
+ source = account.defaultIdentity;
+ else if (type == "server")
+ source = account.incomingServer;
+ else if (type == "pop3")
+ source = server.QueryInterface(Components.interfaces.nsIPop3IncomingServer);
+ else if (type == "imap")
+ source = server.QueryInterface(Components.interfaces.nsIImapIncomingServer);
+ else if (type == "none")
+ source = server.QueryInterface(Components.interfaces.nsINoIncomingServer);
+ else if (type == "nntp")
+ source = server.QueryInterface(Components.interfaces.nsINntpIncomingServer);
+ else if (type == "smtp")
+ source = MailServices.smtp.defaultServer;
+ } catch (ex) {
+ }
+
+ if (source) {
+ if (isGeneric) {
+ if (!(type in gGenericAttributeTypes))
+ gGenericAttributeTypes[type] = new Object();
+
+ // we need the preftype later, for setting when we save.
+ gGenericAttributeTypes[type][slot] = preftype;
+ var methodName = "get";
+ switch (preftype) {
+ case "int":
+ methodName += "Int";
+ break;
+ case "wstring":
+ methodName += "Unichar";
+ break;
+ case "string":
+ methodName += "Char";
+ break;
+ case "bool":
+ methodName += "Bool";
+ break;
+ default:
+ dump("unexpected preftype: " + preftype + "\n");
+ break;
+ }
+ methodName += ((methodName + "Value") in source ? "Value" : "Attribute");
+ accountValues[type][slot] = source[methodName](slot);
+ }
+ else if (slot in source) {
+ accountValues[type][slot] = source[slot];
+ } else {
+ accountValues[type][slot] = null;
+ }
+ }
+ else {
+ accountValues[type][slot] = null;
+ }
+ }
+ return accountValues[type][slot];
+}
+
+// restore the values of the widgets from the given server
+function restorePage(pageId, account)
+{
+ if (!account)
+ return;
+
+ var accountValues = getValueArrayFor(account);
+ if (!accountValues)
+ return;
+
+ if ("onPreInit" in top.frames["contentFrame"])
+ top.frames["contentFrame"].onPreInit(account, accountValues);
+
+ var pageElements = getPageFormElements();
+ if (!pageElements)
+ return;
+
+ // restore the value from the account
+ for (let i = 0; i < pageElements.length; i++) {
+ if (pageElements[i].id) {
+ let vals = pageElements[i].id.split(".");
+ if (vals.length >= 2) {
+ let type = vals[0];
+ let slot = pageElements[i].id.slice(type.length + 1);
+
+ // buttons are lockable, but don't have any data so we skip that part.
+ // elements that do have data, we get the values at poke them in.
+ if (pageElements[i].localName != "button") {
+ var value = getAccountValue(account, accountValues, type, slot, pageElements[i].getAttribute("preftype"), (pageElements[i].getAttribute("genericattr") == "true"));
+ setFormElementValue(pageElements[i], value);
+ }
+ var element = pageElements[i];
+ switch (type) {
+ case "identity":
+ element["identitykey"] = account.defaultIdentity.key;
+ break;
+ case "pop3":
+ case "imap":
+ case "nntp":
+ case "server":
+ element["serverkey"] = account.incomingServer.key;
+ break;
+ case "smtp":
+ if (MailServices.smtp.defaultServer)
+ element["serverkey"] = MailServices.smtp.defaultServer.key;
+ break;
+ }
+ var isLocked = getAccountValueIsLocked(pageElements[i]);
+ setEnabled(pageElements[i], !isLocked);
+ }
+ }
+ }
+
+ // tell the page that new values have been loaded
+ if ("onInit" in top.frames["contentFrame"])
+ top.frames["contentFrame"].onInit(pageId, account.incomingServer.serverURI);
+
+ // everything has succeeded, vervied by setting currentPageId
+ currentPageId = pageId;
+ currentAccount = account;
+}
+
+/**
+ * Gets the value of a widget in current the account settings page,
+ * automatically setting the right property of it depending on element type.
+ *
+ * @param formElement A XUL input element.
+ */
+function getFormElementValue(formElement) {
+ try {
+ var type = formElement.localName;
+ if (type == "checkbox") {
+ if (formElement.getAttribute("reversed"))
+ return !formElement.checked;
+ return formElement.checked;
+ }
+ if (type == "textbox" &&
+ formElement.getAttribute("datatype") == "nsIFile") {
+ if (formElement.value) {
+ let localfile = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsIFile);
+
+ localfile.initWithPath(formElement.value);
+ return localfile;
+ }
+ return null;
+ }
+ if ((type == "textbox") || ("value" in formElement)) {
+ return formElement.value;
+ }
+ return null;
+ }
+ catch (ex) {
+ Components.utils.reportError("getFormElementValue failed, ex=" + ex + "\n");
+ }
+ return null;
+}
+
+/**
+ * Sets the value of a widget in current the account settings page,
+ * automatically setting the right property of it depending on element type.
+ *
+ * @param formElement A XUL input element.
+ * @param value The value to store in the element.
+ */
+function setFormElementValue(formElement, value) {
+ var type = formElement.localName;
+ if (type == "checkbox") {
+ if (value == null) {
+ formElement.checked = false;
+ } else {
+ if (formElement.getAttribute("reversed"))
+ formElement.checked = !value;
+ else
+ formElement.checked = value;
+ }
+ }
+ else if (type == "radiogroup" || type == "menulist") {
+ if (value == null)
+ formElement.selectedIndex = 0;
+ else
+ formElement.value = value;
+ }
+ // handle nsIFile
+ else if (type == "textbox" &&
+ formElement.getAttribute("datatype") == "nsIFile") {
+ if (value) {
+ let localfile = value.QueryInterface(Components.interfaces.nsIFile);
+ try {
+ formElement.value = localfile.path;
+ } catch (ex) {
+ dump("Still need to fix uninitialized nsIFile problem!\n");
+ }
+ } else {
+ formElement.value = "";
+ }
+ }
+ else if (type == "textbox") {
+ if (value == null)
+ formElement.value = null;
+ else
+ formElement.value = value;
+ }
+ else if (type == "label") {
+ formElement.value = value || "";
+ }
+ // let the form figure out what to do with it
+ else {
+ if (value == null)
+ formElement.value = null;
+ else
+ formElement.value = value;
+ }
+}
+
+//
+// conversion routines - get data associated
+// with a given pageId, serverId, etc
+//
+
+// helper routine for account manager panels to get the current account for the selected server
+function getCurrentAccount()
+{
+ return currentAccount;
+}
+
+/**
+ * Get the array of persisted form elements for the given page.
+ */
+function getPageFormElements() {
+ // Uses getElementsByAttribute() which returns a live NodeList which is usually
+ // faster than e.g. querySelector().
+ if ("getElementsByAttribute" in top.frames["contentFrame"].document)
+ return top.frames["contentFrame"].document
+ .getElementsByAttribute("wsm_persist", "true");
+
+ return null;
+}
+
+/**
+ * Get a single persisted form element in the current page.
+ *
+ * @param aId ID of the element requested.
+ */
+function getPageFormElement(aId) {
+ let elem = top.frames["contentFrame"].document.getElementById(aId);
+ if (elem && (elem.getAttribute("wsm_persist") == "true"))
+ return elem;
+
+ return null;
+}
+
+// get the value array for the given account
+function getValueArrayFor(account) {
+ var serverId = account ? account.incomingServer.serverURI : "global";
+
+ if (!(serverId in accountArray)) {
+ accountArray[serverId] = new Object();
+ accountArray[serverId]._account = account;
+ }
+
+ return accountArray[serverId];
+}
+
+var gAccountTree = {
+ load: function at_load() {
+ this._build();
+
+ MailServices.accounts.addIncomingServerListener(this);
+ },
+ unload: function at_unload() {
+ MailServices.accounts.removeIncomingServerListener(this);
+ },
+ onServerLoaded: function at_onServerLoaded(aServer) {
+ this._build();
+ },
+ onServerUnloaded: function at_onServerUnloaded(aServer) {
+ this._build();
+ },
+ onServerChanged: function at_onServerChanged(aServer) {},
+
+ _dataStore: Components.classes["@mozilla.org/xul/xulstore;1"]
+ .getService(Components.interfaces.nsIXULStore),
+
+ /**
+ * Retrieve from XULStore.json whether the account should be expanded (open)
+ * in the account tree.
+ *
+ * @param aAccountKey key of the account to check
+ */
+ _getAccountOpenState: function at_getAccountOpenState(aAccountKey) {
+ if (!this._dataStore.hasValue(document.documentURI, aAccountKey, "open")) {
+ // If there was no value stored, use opened state.
+ return "true";
+ } else {
+ // Retrieve the persisted value from XULStore.json.
+ // It is stored under the URI of the current document and ID of the XUL element.
+ return this._dataStore
+ .getValue(document.documentURI, aAccountKey, "open");
+ }
+ },
+
+ _build: function at_build() {
+ const Ci = Components.interfaces;
+ var bundle = document.getElementById("bundle_prefs");
+ function getString(aString) { return bundle.getString(aString); }
+ var panels = [{string: getString("prefPanel-server"), src: "am-server.xul"},
+ {string: getString("prefPanel-copies"), src: "am-copies.xul"},
+ {string: getString("prefPanel-synchronization"), src: "am-offline.xul"},
+ {string: getString("prefPanel-diskspace"), src: "am-offline.xul"},
+ {string: getString("prefPanel-addressing"), src: "am-addressing.xul"},
+ {string: getString("prefPanel-junk"), src: "am-junk.xul"}];
+
+ let accounts = allAccountsSorted(false);
+
+ let mainTree = document.getElementById("account-tree-children");
+ // Clear off all children...
+ while (mainTree.hasChildNodes())
+ mainTree.lastChild.remove();
+
+ for (let account of accounts) {
+ let accountName = null;
+ let accountKey = account.key;
+ let amChrome = "about:blank";
+ let panelsToKeep = [];
+ let server = null;
+
+ // This "try {} catch {}" block is intentionally very long to catch
+ // unknown exceptions and confine them to this single account.
+ // This may happen from broken accounts. See e.g. bug 813929.
+ // Other accounts can still be shown properly if they are valid.
+ try {
+ server = account.incomingServer;
+
+ accountName = server.prettyName;
+
+ // Now add our panels.
+ let idents = MailServices.accounts.getIdentitiesForServer(server);
+ if (idents.length) {
+ panelsToKeep.push(panels[0]); // The server panel is valid
+ panelsToKeep.push(panels[1]); // also the copies panel
+ panelsToKeep.push(panels[4]); // and addresssing
+ }
+
+ // Everyone except News, RSS and IM has a junk panel
+ // XXX: unextensible!
+ // The existence of server.spamSettings can't currently be used for this.
+ if (server.type != "nntp" && server.type != "rss" && server.type != "im")
+ panelsToKeep.push(panels[5]);
+
+ // Check offline/diskspace support level.
+ let diskspace = server.supportsDiskSpace;
+ if (server.offlineSupportLevel >= 10 && diskspace)
+ panelsToKeep.push(panels[2]);
+ else if (diskspace)
+ panelsToKeep.push(panels[3]);
+
+ // extensions
+ let catMan = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Ci.nsICategoryManager);
+ const CATEGORY = "mailnews-accountmanager-extensions";
+ let catEnum = catMan.enumerateCategory(CATEGORY);
+ while (catEnum.hasMoreElements()) {
+ let entryName = null;
+ try {
+ entryName = catEnum.getNext().QueryInterface(Ci.nsISupportsCString).data;
+ let svc = Components.classes[catMan.getCategoryEntry(CATEGORY, entryName)]
+ .getService(Ci.nsIMsgAccountManagerExtension);
+ if (svc.showPanel(server)) {
+ let bundleName = "chrome://" + svc.chromePackageName +
+ "/locale/am-" + svc.name + ".properties";
+ let bundle = Services.strings.createBundle(bundleName);
+ let title = bundle.GetStringFromName("prefPanel-" + svc.name);
+ panelsToKeep.push({string: title, src: "am-" + svc.name + ".xul"});
+ }
+ } catch(e) {
+ // Fetching of this extension panel failed so do not show it,
+ // just log error.
+ let extName = entryName || "(unknown)";
+ Components.utils.reportError("Error accessing panel from extension '" +
+ extName + "': " + e);
+ }
+ }
+ amChrome = server.accountManagerChrome;
+ } catch(e) {
+ // Show only a placeholder in the account list saying this account
+ // is broken, with no child panels.
+ let accountID = (accountName || accountKey);
+ Components.utils.reportError("Error accessing account " + accountID + ": " + e);
+ accountName = "Invalid account " + accountID;
+ panelsToKeep.length = 0;
+ }
+
+ // Create the top level tree-item.
+ var treeitem = document.createElement("treeitem");
+ mainTree.appendChild(treeitem);
+ var treerow = document.createElement("treerow");
+ treeitem.appendChild(treerow);
+ var treecell = document.createElement("treecell");
+ treerow.appendChild(treecell);
+ treecell.setAttribute("label", accountName);
+ treeitem.setAttribute("PageTag", amChrome);
+ // Add icons based on account type.
+ if (server) {
+ treecell.setAttribute("properties", "folderNameCol isServer-true" +
+ " serverType-" + server.type);
+ // For IM accounts, we can try to fetch a protocol specific icon.
+ if (server.type == "im") {
+ treecell.setAttribute("src", server.wrappedJSObject.imAccount
+ .protocol.iconBaseURI + "icon.png");
+ }
+ }
+
+ if (panelsToKeep.length > 0) {
+ var treekids = document.createElement("treechildren");
+ treeitem.appendChild(treekids);
+ for (let panel of panelsToKeep) {
+ var kidtreeitem = document.createElement("treeitem");
+ treekids.appendChild(kidtreeitem);
+ var kidtreerow = document.createElement("treerow");
+ kidtreeitem.appendChild(kidtreerow);
+ var kidtreecell = document.createElement("treecell");
+ kidtreerow.appendChild(kidtreecell);
+ kidtreecell.setAttribute("label", panel.string);
+ kidtreeitem.setAttribute("PageTag", panel.src);
+ kidtreeitem._account = account;
+ }
+ treeitem.setAttribute("container", "true");
+ treeitem.id = accountKey;
+ // Load the 'open' state of the account from XULStore.json.
+ treeitem.setAttribute("open", this._getAccountOpenState(accountKey));
+ // Let the XULStore.json automatically save the 'open' state of the
+ // account when it is changed.
+ treeitem.setAttribute("persist", "open");
+ }
+ treeitem._account = account;
+ }
+
+ markDefaultServer(MailServices.accounts.defaultAccount, null);
+
+ // Now add the outgoing server node.
+ var treeitem = document.createElement("treeitem");
+ mainTree.appendChild(treeitem);
+ var treerow = document.createElement("treerow");
+ treeitem.appendChild(treerow);
+ var treecell = document.createElement("treecell");
+ treerow.appendChild(treecell);
+ treecell.setAttribute("label", getString("prefPanel-smtp"));
+ treeitem.setAttribute("PageTag", "am-smtp.xul");
+ treecell.setAttribute("properties",
+ "folderNameCol isServer-true serverType-smtp");
+ }
+};
diff --git a/mailnews/base/prefs/content/AccountManager.xul b/mailnews/base/prefs/content/AccountManager.xul
new file mode 100644
index 000000000..a14156b32
--- /dev/null
+++ b/mailnews/base/prefs/content/AccountManager.xul
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/AccountManager.dtd">
+<dialog id="accountManager"
+ windowtype="mailnews:accountmanager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&accountManagerTitle.label;"
+ style="&accountManager.size;"
+ persist="width height screenX screenY"
+ buttons="accept,cancel"
+ onload="onLoad(event);"
+ onunload="onUnload();"
+ ondialogaccept="return onAccept(true);">
+<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+<stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+<script type="application/javascript" src="chrome://messenger/content/accountUtils.js"/>
+<script type="application/javascript" src="chrome://messenger/content/am-prefs.js"/>
+<script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+<script type="application/javascript" src="chrome://messenger/content/am-help.js"/>
+<script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+
+ <hbox flex="1">
+ <vbox style="&accountTree.width;">
+ <tree flex="1" onselect="onAccountTreeSelect(null, null);" id="accounttree"
+ seltype="single" hidecolumnpicker="true">
+ <treecols>
+ <treecol id="AccountCol" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren id="account-tree-children"/>
+ </tree>
+
+#if defined(MOZ_THUNDERBIRD) && defined(HYPE_ICEDOVE)
+ <button id="accountActionsButton" type="menu"
+ label="&accountActionsButton.label;"
+ accesskey="&accountActionsButton.accesskey;">
+ <menupopup id="accountActionsDropdown"
+ onpopupshowing="initAccountActionsButtons(this);">
+ <menuitem id="accountActionsAddMailAccount"
+ label="&addMailAccountButton.label;"
+ accesskey="&addMailAccountButton.accesskey;"
+ prefstring="mail.disable_new_account_addition"
+ oncommand="AddMailAccount(event); event.stopPropagation();"/>
+ <menuitem id="accountActionsAddFeedAccount"
+ label="&addFeedAccountButton.label;"
+ accesskey="&addFeedAccountButton.accesskey;"
+ prefstring="mail.disable_new_account_addition"
+ oncommand="AddFeedAccount(event); event.stopPropagation();"/>
+ <menuitem id="accountActionsAddOtherAccount"
+ label="&addOtherAccountButton.label;"
+ accesskey="&addOtherAccountButton.accesskey;"
+ prefstring="mail.disable_new_account_addition"
+ oncommand="onAddAccount(event); event.stopPropagation();"/>
+ <menuseparator id="accountActionsDropdownSep1"/>
+ <menuitem id="accountActionsDropdownSetDefault"
+ label="&setDefaultButton.label;"
+ accesskey="&setDefaultButton.accesskey;"
+ prefstring="mail.disable_button.set_default_account"
+ oncommand="onSetDefault(event); event.stopPropagation();"/>
+ <menuitem id="accountActionsDropdownRemove"
+ label="&removeButton.label;"
+ accesskey="&removeButton.accesskey;"
+ prefstring="mail.disable_button.delete_account"
+ oncommand="onRemoveAccount(event); event.stopPropagation();"/>
+ </menupopup>
+ </button>
+#else
+ <button label="&addAccountButton.label;" oncommand="onAddAccount(event);" id="addAccountButton"
+ prefstring="mail.disable_new_account_addition"
+ accesskey="&addAccountButton.accesskey;"/>
+ <button label="&setDefaultButton.label;" oncommand="onSetDefault(event);" disabled="true" id="setDefaultButton"
+ prefstring="mail.disable_button.set_default_account"
+ accesskey="&setDefaultButton.accesskey;"/>
+ <button disabled="true" label="&removeButton.label;" oncommand="onRemoveAccount(event);" id="removeButton"
+ prefstring="mail.disable_button.delete_account"
+ accesskey="&removeButton.accesskey;"/>
+#endif
+ </vbox>
+
+ <iframe id="contentFrame" name="contentFrame" flex="1"/>
+ </hbox>
+</dialog>
diff --git a/mailnews/base/prefs/content/AccountWizard.js b/mailnews/base/prefs/content/AccountWizard.js
new file mode 100644
index 000000000..90d729418
--- /dev/null
+++ b/mailnews/base/prefs/content/AccountWizard.js
@@ -0,0 +1,988 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* the okCallback is used for sending a callback for the parent window */
+var okCallback = null;
+/* The account wizard creates new accounts */
+
+/*
+ data flow into the account wizard like this:
+
+ For new accounts:
+ * pageData -> Array -> createAccount -> finishAccount
+
+ For accounts coming from the ISP setup:
+ * RDF -> Array -> pageData -> Array -> createAccount -> finishAccount
+
+ for "unfinished accounts"
+ * account -> Array -> pageData -> Array -> finishAccount
+
+ Where:
+ pageData - the actual pages coming out of the Widget State Manager
+ RDF - the ISP datasource
+ Array - associative array of attributes, that very closely
+ resembles the nsIMsgAccount/nsIMsgIncomingServer/nsIMsgIdentity
+ structure
+ createAccount() - creates an account from the above Array
+ finishAccount() - fills an existing account with data from the above Array
+
+*/
+
+/*
+ the account wizard path is something like:
+
+ accounttype -> identity -> server -> login -> accname -> done
+ \-> newsserver ----/
+
+ where the accounttype determines which path to take
+ (server vs. newsserver)
+*/
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+var contentWindow;
+
+var gPageData;
+
+var nsIMsgIdentity = Components.interfaces.nsIMsgIdentity;
+var nsIMsgIncomingServer = Components.interfaces.nsIMsgIncomingServer;
+var gPrefsBundle, gMessengerBundle;
+
+// the current nsIMsgAccount
+var gCurrentAccount;
+
+// The default account before we create a new account.
+// We need to store this as just asking for the default account may switch
+// it to the newly created one if there was none before.
+var gDefaultAccount;
+
+// the current associative array that
+// will eventually be dumped into the account
+var gCurrentAccountData;
+
+// default picker mode for copies and folders
+var gDefaultSpecialFolderPickerMode = "0";
+
+// event handlers
+function onAccountWizardLoad() {
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ gMessengerBundle = document.getElementById("bundle_messenger");
+
+ if ("testingIspServices" in this) {
+ if ("SetCustomizedWizardDimensions" in this && testingIspServices()) {
+ SetCustomizedWizardDimensions();
+ }
+ }
+
+ /* We are checking here for the callback argument */
+ if (window.arguments && window.arguments[0]) {
+ if(window.arguments[0].okCallback )
+ {
+ //dump("There is okCallback");
+ top.okCallback = window.arguments[0].okCallback;
+ }
+ }
+
+ checkForInvalidAccounts();
+
+ // It is fine if there is no default account, this is expected the first
+ // time you launch mail on a new profile.
+ gDefaultAccount = MailServices.accounts.defaultAccount;
+
+ // Set default value for global inbox checkbox
+ var checkGlobalInbox = document.getElementById("deferStorage");
+ try {
+ checkGlobalInbox.checked = Services.prefs.getBoolPref("mail.accountwizard.deferstorage");
+ } catch(e) {}
+}
+
+function onCancel()
+{
+ if ("ActivationOnCancel" in this && ActivationOnCancel())
+ return false;
+ var firstInvalidAccount = getFirstInvalidAccount();
+ var closeWizard = true;
+
+ // if the user cancels the the wizard when it pops up because of
+ // an invalid account (example, a webmail account that activation started)
+ // we just force create it by setting some values and calling the FinishAccount()
+ // see bug #47521 for the full discussion
+ if (firstInvalidAccount) {
+ var pageData = GetPageData();
+ // set the fullName if it doesn't exist
+ if (!pageData.identity.fullName || !pageData.identity.fullName.value) {
+ setPageData(pageData, "identity", "fullName", "");
+ }
+
+ // set the email if it doesn't exist
+ if (!pageData.identity.email || !pageData.identity.email.value) {
+ setPageData(pageData, "identity", "email", "user@domain.invalid");
+ }
+
+ // call FinishAccount() and not onFinish(), since the "finish"
+ // button may be disabled
+ FinishAccount();
+ }
+ else {
+ // since this is not an invalid account
+ // really cancel if the user hits the "cancel" button
+ // if the length of the account list is less than 1, there are no accounts
+ if (MailServices.accounts.accounts.length < 1) {
+ let confirmMsg = gPrefsBundle.getString("cancelWizard");
+ let confirmTitle = gPrefsBundle.getString("accountWizard");
+ let result = Services.prompt.confirmEx(window, confirmTitle, confirmMsg,
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1),
+ gPrefsBundle.getString('WizardExit'),
+ gPrefsBundle.getString('WizardContinue'),
+ null, null, {value:0});
+
+ if (result == 1)
+ closeWizard = false;
+ }
+
+ if(top.okCallback && closeWizard) {
+ var state = false;
+ top.okCallback(state);
+ }
+ }
+ return closeWizard;
+}
+
+function FinishAccount()
+{
+ try {
+ var pageData = GetPageData();
+
+ var accountData= gCurrentAccountData;
+
+ if (!accountData)
+ {
+ accountData = new Object;
+ // Time to set the smtpRequiresUsername attribute
+ if (!serverIsNntp(pageData))
+ accountData.smtpRequiresUsername = true;
+ }
+
+ // we may need local folders before account is "Finished"
+ // if it's a pop3 account which defers to Local Folders.
+ verifyLocalFoldersAccount();
+
+ PageDataToAccountData(pageData, accountData);
+
+ FixupAccountDataForIsp(accountData);
+
+ // we might be simply finishing another account
+ if (!gCurrentAccount)
+ gCurrentAccount = createAccount(accountData);
+
+ // transfer all attributes from the accountdata
+ finishAccount(gCurrentAccount, accountData);
+
+ setupCopiesAndFoldersServer(gCurrentAccount, getCurrentServerIsDeferred(pageData), accountData);
+
+ if (gCurrentAccount.incomingServer.canBeDefaultServer)
+ EnableCheckMailAtStartUpIfNeeded(gCurrentAccount);
+
+ if (!document.getElementById("downloadMsgs").hidden) {
+ // skip the default biff, we will load messages manually if needed
+ window.opener.gLoadStartFolder = false;
+ if (document.getElementById("downloadMsgs").checked) {
+ window.opener.gNewAccountToLoad = gCurrentAccount; // load messages for new POP account
+ }
+ }
+
+ // in case we crash, force us a save of the prefs file NOW
+ try {
+ MailServices.accounts.saveAccountInfo();
+ }
+ catch (ex) {
+ dump("Error saving account info: " + ex + "\n");
+ }
+ window.close();
+ if(top.okCallback)
+ {
+ var state = true;
+ //dump("finish callback");
+ top.okCallback(state);
+ }
+ }
+ catch(ex) {
+ dump("FinishAccount failed, " + ex +"\n");
+ }
+}
+
+// prepopulate pageData with stuff from accountData
+// use: to prepopulate the wizard with account information
+function AccountDataToPageData(accountData, pageData)
+{
+ if (!accountData) {
+ dump("null account data! clearing..\n");
+ // handle null accountData as if it were an empty object
+ // so that we clear-out any old pagedata from a
+ // previous accountdata. The trick is that
+ // with an empty object, accountData.identity.slot is undefined,
+ // so this will clear out the prefill data in setPageData
+
+ accountData = new Object;
+ accountData.incomingServer = new Object;
+ accountData.identity = new Object;
+ accountData.smtp = new Object;
+ }
+
+ var server = accountData.incomingServer;
+
+ if (server.type == undefined) {
+ // clear out the old server data
+ //setPageData(pageData, "accounttype", "mailaccount", undefined);
+ // setPageData(pageData, "accounttype", "newsaccount", undefined);
+ setPageData(pageData, "server", "servertype", undefined);
+ setPageData(pageData, "server", "hostname", undefined);
+
+ }
+ else {
+ if (server.type == "nntp") {
+ setPageData(pageData, "accounttype", "newsaccount", true);
+ setPageData(pageData, "accounttype", "mailaccount", false);
+ setPageData(pageData, "newsserver", "hostname", server.hostName);
+ }
+ else {
+ setPageData(pageData, "accounttype", "mailaccount", true);
+ setPageData(pageData, "accounttype", "newsaccount", false);
+ setPageData(pageData, "server", "servertype", server.type);
+ setPageData(pageData, "server", "hostname", server.hostName);
+ }
+ setPageData(pageData, "accounttype", "otheraccount", false);
+ }
+
+ setPageData(pageData, "login", "username", server.username || "");
+ setPageData(pageData, "login", "password", server.password || "");
+ setPageData(pageData, "accname", "prettyName", server.prettyName || "");
+ setPageData(pageData, "accname", "userset", false);
+ setPageData(pageData, "ispdata", "supplied", false);
+
+ var identity;
+
+ if (accountData.identity) {
+ dump("This is an accountdata\n");
+ identity = accountData.identity;
+ }
+ else if (accountData.identities) {
+ identity = accountData.identities.queryElementAt(0, Components.interfaces.nsIMsgIdentity);
+ dump("this is an account, id= " + identity + "\n");
+ }
+
+ setPageData(pageData, "identity", "email", identity.email || "");
+ setPageData(pageData, "identity", "fullName", identity.fullName || "");
+
+ var smtp;
+
+ if (accountData.smtp) {
+ smtp = accountData.smtp;
+ setPageData(pageData, "server", "smtphostname", smtp.hostname);
+ setPageData(pageData, "login", "smtpusername", smtp.username);
+ }
+}
+
+// take data from each page of pageData and dump it into accountData
+// use: to put results of wizard into a account-oriented object
+function PageDataToAccountData(pageData, accountData)
+{
+ if (!accountData.identity)
+ accountData.identity = new Object;
+ if (!accountData.incomingServer)
+ accountData.incomingServer = new Object;
+ if (!accountData.smtp)
+ accountData.smtp = new Object;
+ if (!accountData.pop3)
+ accountData.pop3 = new Object;
+ if (!accountData.imap)
+ accountData.imap = new Object;
+
+ var identity = accountData.identity;
+ var server = accountData.incomingServer;
+ var smtp = accountData.smtp;
+ var pop3 = accountData.pop3;
+ var imap = accountData.imap;
+
+ if (pageData.identity.email)
+ identity.email = pageData.identity.email.value;
+ if (pageData.identity.fullName)
+ identity.fullName = pageData.identity.fullName.value;
+
+ server.type = getCurrentServerType(pageData);
+ server.hostName = getCurrentHostname(pageData);
+ if (getCurrentServerIsDeferred(pageData))
+ {
+ try
+ {
+ let localFoldersServer = MailServices.accounts.localFoldersServer;
+ let localFoldersAccount = MailServices.accounts.FindAccountForServer(localFoldersServer);
+ pop3.deferredToAccount = localFoldersAccount.key;
+ pop3.deferGetNewMail = true;
+ server["ServerType-pop3"] = pop3;
+ }
+ catch (ex) {dump ("exception setting up deferred account" + ex);}
+ }
+ if (serverIsNntp(pageData)) {
+ // this stuff probably not relevant
+ dump("not setting username/password/etc\n");
+ }
+ else {
+ if (pageData.login) {
+ if (pageData.login.username)
+ server.username = pageData.login.username.value;
+ if (pageData.login.password)
+ server.password = pageData.login.password.value;
+ if (pageData.login.smtpusername)
+ smtp.username = pageData.login.smtpusername.value;
+ }
+
+ dump("pageData.server = " + pageData.server + "\n");
+ if (pageData.server) {
+ dump("pageData.server.smtphostname.value = " + pageData.server.smtphostname + "\n");
+ if (pageData.server.smtphostname &&
+ pageData.server.smtphostname.value)
+ smtp.hostname = pageData.server.smtphostname.value;
+ }
+ if (pageData.identity && pageData.identity.smtpServerKey)
+ identity.smtpServerKey = pageData.identity.smtpServerKey.value;
+
+ if (pageData.server.port &&
+ pageData.server.port.value)
+ {
+ if (server.type == 'imap')
+ {
+ imap.port = pageData.server.port.value;
+ server["ServerType-imap"] = imap;
+ }
+ else if (server.type == 'pop3')
+ {
+ pop3.port = pageData.server.port.value;
+ server["ServerType-pop3"] = pop3;
+ }
+ }
+
+ if (pageData.server.leaveMessagesOnServer &&
+ pageData.server.leaveMessagesOnServer.value)
+ {
+ pop3.leaveMessagesOnServer = pageData.server.leaveMessagesOnServer.value;
+ server["ServerType-pop3"] = pop3;
+ }
+ }
+
+ if (pageData.accname) {
+ if (pageData.accname.prettyName)
+ server.prettyName = pageData.accname.prettyName.value;
+ }
+
+}
+
+// given an accountData structure, create an account
+// (but don't fill in any fields, that's for finishAccount()
+function createAccount(accountData)
+{
+ // Retrieve the server (data) from the account data.
+ var server = accountData.incomingServer;
+
+ // for news, username is always null
+ var username = (server.type == "nntp") ? null : server.username;
+ dump("MailServices.accounts.createIncomingServer(" +
+ username + ", " + server.hostName + ", " + server.type + ")\n");
+ // Create a (actual) server.
+ server = MailServices.accounts.createIncomingServer(username, server.hostName, server.type);
+
+ dump("MailServices.accounts.createAccount()\n");
+ // Create an account.
+ let account = MailServices.accounts.createAccount();
+
+ // only create an identity for this account if we really have one
+ // (use the email address as a check)
+ if (accountData.identity && accountData.identity.email)
+ {
+ dump("MailServices.accounts.createIdentity()\n");
+ // Create an identity.
+ let identity = MailServices.accounts.createIdentity();
+
+ // New nntp identities should use plain text by default;
+ // we want that GNKSA (The Good Net-Keeping Seal of Approval).
+ if (server.type == "nntp")
+ identity.composeHtml = false;
+
+ account.addIdentity(identity);
+ }
+
+ // we mark the server as invalid so that the account manager won't
+ // tell RDF about the new server - it's not quite finished getting
+ // set up yet, in particular, the deferred storage pref hasn't been set.
+ server.valid = false;
+ // Set the new account to use the new server.
+ account.incomingServer = server;
+ server.valid = true;
+ return account;
+}
+
+// given an accountData structure, copy the data into the
+// given account, incoming server, and so forth
+function finishAccount(account, accountData)
+{
+ if (accountData.incomingServer) {
+
+ var destServer = account.incomingServer;
+ var srcServer = accountData.incomingServer;
+ copyObjectToInterface(destServer, srcServer, true);
+
+ // see if there are any protocol-specific attributes
+ // if so, we use the type to get the IID, QueryInterface
+ // as appropriate, then copy the data over
+ dump("srcServer.ServerType-" + srcServer.type + " = " +
+ srcServer["ServerType-" + srcServer.type] + "\n");
+ if (srcServer["ServerType-" + srcServer.type]) {
+ // handle server-specific stuff
+ var IID;
+ try {
+ IID = destServer.protocolInfo.serverIID;
+ } catch (ex) {
+ Components.utils.reportError("Could not get IID for " + srcServer.type + ": " + ex);
+ }
+
+ if (IID) {
+ destProtocolServer = destServer.QueryInterface(IID);
+ srcProtocolServer = srcServer["ServerType-" + srcServer.type];
+
+ dump("Copying over " + srcServer.type + "-specific data\n");
+ copyObjectToInterface(destProtocolServer, srcProtocolServer, false);
+ }
+ }
+
+ account.incomingServer.valid=true;
+ // hack to cause an account loaded notification now the server is valid
+ account.incomingServer = account.incomingServer;
+ }
+
+ // copy identity info
+ var destIdentity = account.identities.length ?
+ account.identities.queryElementAt(0, nsIMsgIdentity) :
+ null;
+
+ if (destIdentity) // does this account have an identity?
+ {
+ if (accountData.identity && accountData.identity.email) {
+ // fixup the email address if we have a default domain
+ var emailArray = accountData.identity.email.split('@');
+ if (emailArray.length < 2 && accountData.domain) {
+ accountData.identity.email += '@' + accountData.domain;
+ }
+
+ copyObjectToInterface(destIdentity, accountData.identity, true);
+ destIdentity.valid=true;
+ }
+
+ /**
+ * If signature file need to be set, get the path to the signature file.
+ * Signature files, if exist, are placed under default location. Get
+ * default files location for messenger using directory service. Signature
+ * file name should be extracted from the account data to build the complete
+ * path for signature file. Once the path is built, set the identity's signature pref.
+ */
+ if (destIdentity.attachSignature)
+ {
+ var sigFileName = accountData.signatureFileName;
+ let sigFile = MailServices.mailSession.getDataFilesDir("messenger");
+ sigFile.append(sigFileName);
+ destIdentity.signature = sigFile;
+ }
+
+ if (accountData.smtp.hostname && !destIdentity.smtpServerKey)
+ {
+ // hostname + no key => create a new SMTP server.
+
+ let smtpServer = MailServices.smtp.createServer();
+ var isDefaultSmtpServer;
+ if (!MailServices.smtp.defaultServer.hostname) {
+ MailServices.smtp.defaultServer = smtpServer;
+ isDefaultSmtpServer = true;
+ }
+
+ copyObjectToInterface(smtpServer, accountData.smtp, false);
+
+ // If it's the default server we created, make the identity use
+ // "Use Default" by default.
+ destIdentity.smtpServerKey =
+ (isDefaultSmtpServer) ? "" : smtpServer.key;
+ }
+ } // if the account has an identity...
+
+ if (this.FinishAccountHook != undefined) {
+ FinishAccountHook(accountData.domain);
+ }
+}
+
+// Helper method used by copyObjectToInterface which attempts to set dest[attribute] as a generic
+// attribute on the xpconnect object, src.
+// This routine skips any attribute that begins with ServerType-
+function setGenericAttribute(dest, src, attribute)
+{
+ if (!(attribute.toLowerCase().startsWith("servertype-")) && src[attribute])
+ {
+ switch (typeof src[attribute])
+ {
+ case "string":
+ dest.setUnicharAttribute(attribute, src[attribute]);
+ break;
+ case "boolean":
+ dest.setBoolAttribute(attribute, src[attribute]);
+ break;
+ case "number":
+ dest.setIntAttribute(attribute, src[attribute]);
+ break;
+ default:
+ dump("Error: No Generic attribute " + attribute + " found for: " + dest + "\n");
+ break;
+ }
+ }
+}
+
+// copy over all attributes from dest into src that already exist in src
+// the assumption is that src is an XPConnect interface full of attributes
+// @param useGenericFallback if we can't set an attribute directly on src, then fall back
+// and try setting it generically. This assumes that src supports setIntAttribute, setUnicharAttribute
+// and setBoolAttribute.
+function copyObjectToInterface(dest, src, useGenericFallback)
+{
+ if (!dest) return;
+ if (!src) return;
+
+ var attribute;
+ for (attribute in src)
+ {
+ if (dest.__lookupSetter__(attribute))
+ {
+ if (dest[attribute] != src[attribute])
+ dest[attribute] = src[attribute];
+ }
+ else if (useGenericFallback) // fall back to setting the attribute generically
+ setGenericAttribute(dest, src, attribute);
+ } // for each attribute in src we want to copy
+}
+
+// check if there already is a "Local Folders"
+// if not, create it.
+function verifyLocalFoldersAccount()
+{
+ var localMailServer = null;
+ try {
+ localMailServer = MailServices.accounts.localFoldersServer;
+ }
+ catch (ex) {
+ // dump("exception in findserver: " + ex + "\n");
+ localMailServer = null;
+ }
+
+ try {
+ if (!localMailServer)
+ {
+ // dump("Creating local mail account\n");
+ // creates a copy of the identity you pass in
+ MailServices.accounts.createLocalMailAccount();
+ try {
+ localMailServer = MailServices.accounts.localFoldersServer;
+ }
+ catch (ex) {
+ dump("error! we should have found the local mail server after we created it.\n");
+ localMailServer = null;
+ }
+ }
+ }
+ catch (ex) {dump("Error in verifyLocalFoldersAccount" + ex + "\n"); }
+
+}
+
+function setupCopiesAndFoldersServer(account, accountIsDeferred, accountData)
+{
+ try {
+ var server = account.incomingServer;
+
+ // This function sets up the default send preferences. The send preferences
+ // go on identities, so there is no need to continue without any identities.
+ if (server.type == "rss" || account.identities.length == 0)
+ return false;
+ let identity = account.identities.queryElementAt(0, Components.interfaces.nsIMsgIdentity);
+ // For this server, do we default the folder prefs to this server, or to the "Local Folders" server
+ // If it's deferred, we use the local folders account.
+ var defaultCopiesAndFoldersPrefsToServer = !accountIsDeferred && server.defaultCopiesAndFoldersPrefsToServer;
+
+ var copiesAndFoldersServer = null;
+ if (defaultCopiesAndFoldersPrefsToServer)
+ {
+ copiesAndFoldersServer = server;
+ }
+ else
+ {
+ if (!MailServices.accounts.localFoldersServer)
+ {
+ dump("error! we should have a local mail server at this point\n");
+ return false;
+ }
+ copiesAndFoldersServer = MailServices.accounts.localFoldersServer;
+ }
+
+ setDefaultCopiesAndFoldersPrefs(identity, copiesAndFoldersServer, accountData);
+
+ } catch (ex) {
+ // return false (meaning we did not setupCopiesAndFoldersServer)
+ // on any error
+ dump("Error in setupCopiesAndFoldersServer: " + ex + "\n");
+ return false;
+ }
+ return true;
+}
+
+function setDefaultCopiesAndFoldersPrefs(identity, server, accountData)
+{
+ var rootFolder = server.rootFolder;
+
+ // we need to do this or it is possible that the server's draft,
+ // stationery fcc folder will not be in rdf
+ //
+ // this can happen in a couple cases
+ // 1) the first account we create, creates the local mail. since
+ // local mail was just created, it obviously hasn't been opened,
+ // or in rdf..
+ // 2) the account we created is of a type where
+ // defaultCopiesAndFoldersPrefsToServer is true
+ // this since we are creating the server, it obviously hasn't been
+ // opened, or in rdf.
+ //
+ // this makes the assumption that the server's draft, stationery fcc folder
+ // are at the top level (ie subfolders of the root folder.) this works
+ // because we happen to be doing things that way, and if the user changes
+ // that, it will work because to change the folder, it must be in rdf,
+ // coming from the folder cache, in the worst case.
+ var msgFolder = rootFolder.QueryInterface(Components.interfaces.nsIMsgFolder);
+
+ /**
+ * When a new account is created, folders 'Sent', 'Drafts'
+ * and 'Templates' are not created then, but created on demand at runtime.
+ * But we do need to present them as possible choices in the Copies and Folders
+ * UI. To do that, folder URIs have to be created and stored in the prefs file.
+ * So, if there is a need to build special folders, append the special folder
+ * names and create right URIs.
+ */
+ var folderDelim = "/";
+
+ /* we use internal names known to everyone like Sent, Templates and Drafts */
+ /* if folder names were already given in isp rdf, we use them,
+ otherwise we use internal names known to everyone like Sent, Templates and Drafts */
+
+ // Note the capital F, D and S!
+ var draftFolder = (accountData.identity && accountData.identity.DraftFolder ?
+ accountData.identity.DraftFolder : "Drafts");
+ var stationeryFolder = (accountData.identity && accountData.identity.StationeryFolder ?
+ accountData.identity.StationeryFolder : "Templates");
+ var fccFolder = (accountData.identity && accountData.identity.FccFolder ?
+ accountData.identity.FccFolder : "Sent");
+
+ identity.draftFolder = msgFolder.server.serverURI+ folderDelim + draftFolder;
+ identity.stationeryFolder = msgFolder.server.serverURI+ folderDelim + stationeryFolder;
+ identity.fccFolder = msgFolder.server.serverURI+ folderDelim + fccFolder;
+
+ // Note the capital F, D and S!
+ identity.fccFolderPickerMode = (accountData.identity &&
+ accountData.identity.FccFolder ? 1 : gDefaultSpecialFolderPickerMode);
+ identity.draftsFolderPickerMode = (accountData.identity &&
+ accountData.identity.DraftFolder ? 1 : gDefaultSpecialFolderPickerMode);
+ identity.tmplFolderPickerMode = (accountData.identity &&
+ accountData.identity.StationeryFolder ? 1 : gDefaultSpecialFolderPickerMode);
+}
+
+function AccountExists(userName, hostName, serverType)
+{
+ return MailServices.accounts.findRealServer(userName, hostName, serverType, 0);
+}
+
+function getFirstInvalidAccount()
+{
+ let invalidAccounts = getInvalidAccounts(MailServices.accounts.accounts);
+
+ if (invalidAccounts.length > 0)
+ return invalidAccounts[0];
+ else
+ return null;
+}
+
+function checkForInvalidAccounts()
+{
+ var firstInvalidAccount = getFirstInvalidAccount();
+
+ if (firstInvalidAccount) {
+ var pageData = GetPageData();
+ dump("We have an invalid account, " + firstInvalidAccount + ", let's use that!\n");
+ gCurrentAccount = firstInvalidAccount;
+
+ // there's a possibility that the invalid account has ISP defaults
+ // as well.. so first pre-fill accountData with ISP info, then
+ // overwrite it with the account data
+
+
+ var identity =
+ firstInvalidAccount.identities.queryElementAt(0, nsIMsgIdentity);
+
+ var accountData = null;
+ // If there is a email address already provided, try to get to other ISP defaults.
+ // If not, get pre-configured data, if any.
+ if (identity.email) {
+ dump("Invalid account: trying to get ISP data for " + identity.email + "\n");
+ accountData = getIspDefaultsForEmail(identity.email);
+ dump("Invalid account: Got " + accountData + "\n");
+
+ // account -> accountData -> pageData
+ accountData = AccountToAccountData(firstInvalidAccount, accountData);
+ }
+ else {
+ accountData = getPreConfigDataForAccount(firstInvalidAccount);
+ }
+
+ AccountDataToPageData(accountData, pageData);
+
+ gCurrentAccountData = accountData;
+
+ setupWizardPanels();
+ // Set the page index to identity page.
+ document.documentElement.pageIndex = 1;
+ }
+}
+
+// Transfer all invalid account information to AccountData. Also, get those special
+// preferences (not associated with any interfaces but preconfigurable via prefs or rdf files)
+// like whether not the smtp server associated with this account requires
+// a user name (mail.identity.<id_key>.smtpRequiresUsername) and the choice of skipping
+// panels (mail.identity.<id_key>.wizardSkipPanels).
+function getPreConfigDataForAccount(account)
+{
+ var accountData = new Object;
+ accountData = new Object;
+ accountData.incomingServer = new Object;
+ accountData.identity = new Object;
+ accountData.smtp = new Object;
+
+ accountData = AccountToAccountData(account, null);
+
+ let identity = account.identities.queryElementAt(0, nsIMsgIdentity);
+
+ try {
+ var skipPanelsPrefStr = "mail.identity." + identity.key + ".wizardSkipPanels";
+ accountData.wizardSkipPanels = Services.prefs.getCharPref(skipPanelsPrefStr);
+
+ if (identity.smtpServerKey) {
+ let smtpServer = MailServices.smtp.getServerByKey(identity.smtpServerKey);
+ accountData.smtp = smtpServer;
+
+ var smtpRequiresUsername = false;
+ var smtpRequiresPrefStr = "mail.identity." + identity.key + ".smtpRequiresUsername";
+ smtpRequiresUsername = Services.prefs.getBoolPref(smtpRequiresPrefStr);
+ accountData.smtpRequiresUsername = smtpRequiresUsername;
+ }
+ }
+ catch(ex) {
+ // reached here as special identity pre-configuration prefs
+ // (wizardSkipPanels, smtpRequiresUsername) are not defined.
+ }
+
+ return accountData;
+}
+
+function AccountToAccountData(account, defaultAccountData)
+{
+ dump("AccountToAccountData(" + account + ", " + defaultAccountData + ")\n");
+ var accountData = defaultAccountData;
+ if (!accountData)
+ accountData = new Object;
+
+ accountData.incomingServer = account.incomingServer;
+ accountData.identity = account.identities.queryElementAt(0, nsIMsgIdentity);
+ accountData.smtp = MailServices.smtp.defaultServer;
+
+ return accountData;
+}
+
+// sets the page data, automatically creating the arrays as necessary
+function setPageData(pageData, tag, slot, value) {
+ if (!pageData[tag]) pageData[tag] = [];
+
+ if (value == undefined) {
+ // clear out this slot
+ if (pageData[tag][slot]) delete pageData[tag][slot];
+ }
+ else {
+ // pre-fill this slot
+ if (!pageData[tag][slot]) pageData[tag][slot] = [];
+ pageData[tag][slot].id = slot;
+ pageData[tag][slot].value = value;
+ }
+}
+
+// value of checkbox on the first page
+function serverIsNntp(pageData) {
+ if (pageData.accounttype.newsaccount)
+ return pageData.accounttype.newsaccount.value;
+ return false;
+}
+
+function getUsernameFromEmail(aEmail, aEnsureDomain)
+{
+ var username = aEmail.substr(0, aEmail.indexOf("@"));
+ if (aEnsureDomain && gCurrentAccountData && gCurrentAccountData.domain)
+ username += '@' + gCurrentAccountData.domain;
+ return username;
+}
+
+function getCurrentUserName(pageData)
+{
+ var userName = "";
+
+ if (pageData.login) {
+ if (pageData.login.username) {
+ userName = pageData.login.username.value;
+ }
+ }
+ if (userName == "") {
+ var email = pageData.identity.email.value;
+ userName = getUsernameFromEmail(email, false);
+ }
+ return userName;
+}
+
+function getCurrentServerType(pageData) {
+ var servertype = "pop3"; // hopefully don't resort to default!
+ if (serverIsNntp(pageData))
+ servertype = "nntp";
+ else if (pageData.server && pageData.server.servertype)
+ servertype = pageData.server.servertype.value;
+ return servertype;
+}
+
+function getCurrentServerIsDeferred(pageData) {
+ var serverDeferred = false;
+ if (getCurrentServerType(pageData) == "pop3" && pageData.server && pageData.server.deferStorage)
+ serverDeferred = pageData.server.deferStorage.value;
+
+ return serverDeferred;
+}
+
+function getCurrentHostname(pageData) {
+ if (serverIsNntp(pageData))
+ return pageData.newsserver.hostname.value;
+ else
+ return pageData.server.hostname.value;
+}
+
+function GetPageData()
+{
+ if (!gPageData)
+ gPageData = new Object;
+
+ return gPageData;
+}
+
+function PrefillAccountForIsp(ispName)
+{
+ dump("AccountWizard.prefillAccountForIsp(" + ispName + ")\n");
+
+ var ispData = getIspDefaultsForUri(ispName);
+
+ var pageData = GetPageData();
+
+ if (!ispData) {
+ SetCurrentAccountData(null);
+ return;
+ }
+
+ // prefill the rest of the wizard
+ dump("PrefillAccountForISP: filling with " + ispData + "\n");
+ SetCurrentAccountData(ispData);
+ AccountDataToPageData(ispData, pageData);
+
+ setPageData(pageData, "ispdata", "supplied", true);
+}
+
+// does any cleanup work for the the account data
+// - sets the username from the email address if it's not already set
+// - anything else?
+function FixupAccountDataForIsp(accountData)
+{
+ // no fixup for news
+ // setting the username does bad things
+ // see bugs #42105 and #154213
+ if (accountData.incomingServer.type == "nntp")
+ return;
+
+ var email = accountData.identity.email;
+
+ // The identity might not have an email address, which is what the rest of
+ // this function is looking for.
+ if (!email)
+ return;
+
+ // fix up the username
+ if (!accountData.incomingServer.username)
+ accountData.incomingServer.username =
+ getUsernameFromEmail(email, accountData.incomingServerUserNameRequiresDomain);
+
+ if (!accountData.smtp.username &&
+ accountData.smtpRequiresUsername) {
+ // fix for bug #107953
+ // if incoming hostname is same as smtp hostname
+ // use the server username (instead of the email username)
+ if (accountData.smtp.hostname == accountData.incomingServer.hostName &&
+ accountData.smtpUserNameRequiresDomain == accountData.incomingServerUserNameRequiresDomain)
+ accountData.smtp.username = accountData.incomingServer.username;
+ else
+ accountData.smtp.username = getUsernameFromEmail(email, accountData.smtpUserNameRequiresDomain);
+ }
+}
+
+function SetCurrentAccountData(accountData)
+{
+ // dump("Setting current account data (" + gCurrentAccountData + ") to " + accountData + "\n");
+ gCurrentAccountData = accountData;
+}
+
+// flush the XUL cache - just for debugging purposes - not called
+function onFlush() {
+ Services.prefs.setBoolPref("nglayout.debug.disable_xul_cache", true);
+ Services.prefs.setBoolPref("nglayout.debug.disable_xul_cache", false);
+}
+
+/** If there are no default accounts..
+ * this is will be the new default, so enable
+ * check for mail at startup
+ */
+function EnableCheckMailAtStartUpIfNeeded(newAccount)
+{
+ // Check if default account existed.
+ // If no such account, make this one the default account
+ // and turn on the new mail check at startup for the current account
+ if (!gDefaultAccount) {
+ MailServices.accounts.defaultAccount = newAccount;
+ newAccount.incomingServer.loginAtStartUp = true;
+ newAccount.incomingServer.downloadOnBiff = true;
+ }
+}
+
+function SetSmtpRequiresUsernameAttribute(accountData)
+{
+ // If this is the default server, time to set the smtp user name
+ // Set the generic attribute for requiring user name for smtp to true.
+ // ISPs can override the pref via rdf files.
+ if (!gDefaultAccount) {
+ accountData.smtpRequiresUsername = true;
+ }
+}
+
+function setNextPage(currentPageId, nextPageId) {
+ var currentPage = document.getElementById(currentPageId);
+ currentPage.next = nextPageId;
+}
diff --git a/mailnews/base/prefs/content/AccountWizard.xul b/mailnews/base/prefs/content/AccountWizard.xul
new file mode 100644
index 000000000..2aee2e3a5
--- /dev/null
+++ b/mailnews/base/prefs/content/AccountWizard.xul
@@ -0,0 +1,371 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountWizard.css" type="text/css"?>
+
+<!DOCTYPE wizard SYSTEM "chrome://messenger/locale/AccountWizard.dtd">
+
+<wizard id="AccountWizard" title="&windowTitle.label;"
+ onwizardcancel="return onCancel();"
+ onwizardfinish="return FinishAccount();"
+#if defined(MOZ_THUNDERBIRD) && defined(HYPE_ICEDOVE)
+ onload="onAccountWizardLoad(); initAccountWizardTB(window.arguments);"
+#else
+ onload="onAccountWizardLoad();"
+#endif
+ style="&accountWizard.size;"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <script type="application/javascript" src="chrome://messenger/content/accountUtils.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/AccountWizard.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/ispUtils.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-accounttype.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-identity.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-incoming.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-outgoing.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-accname.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-done.js"/>
+
+ <!-- Account Type page : Displays choices of mail and news accounts that user can create -->
+ <wizardpage id="accounttype" pageid="accounttype"
+ label="&accountTypeTitle.label;"
+ onpageshow="document.documentElement.canAdvance = true;"
+ onpageadvanced="return acctTypePageUnload();">
+ <vbox flex="1">
+ <description>&accountSetupInfo2.label;</description>
+ <description>&accountTypeDesc2.label;</description>
+ <label control="acctyperadio">&accountTypeDirections.label;</label>
+ <separator/>
+ <radiogroup id="acctyperadio" >
+#if !defined(MOZ_THUNDERBIRD) || !defined(HYPE_ICEDOVE)
+ <radio id="mailaccount" value="mailaccount"
+ label="&accountTypeMail.label;" accesskey="&accountTypeMail.accesskey;"
+ selected="true"/>
+#endif
+ <vbox datasources="rdf:ispdefaults"
+ containment="http://home.netscape.com/NC-rdf#providers"
+ id="ispBox"
+ ref="NC:ispinfo">
+ <template>
+ <rule nc:wizardShow="true">
+ <radio uri="..."
+ value="rdf:http://home.netscape.com/NC-rdf#wizardShortName"
+ label="rdf:http://home.netscape.com/NC-rdf#wizardLongName"
+ accesskey="rdf:http://home.netscape.com/NC-rdf#wizardLongNameAccesskey"/>
+ </rule>
+ </template>
+ </vbox>
+ <radio id="newsaccount" value="newsaccount"
+ label="&accountTypeNews.label;" accesskey="&accountTypeNews.accesskey;"/>
+ </radiogroup>
+ </vbox>
+ </wizardpage>
+
+ <!-- Identity page : Collects user's full name and email address -->
+ <wizardpage id="identitypage" pageid="identitypage"
+ label="&identityTitle.label;"
+ onpageshow="return identityPageInit();"
+ onpageadvanced="return identityPageUnload();">
+ <vbox flex="1">
+ <description>&identityDesc.label;</description>
+ <separator/>
+ <description>&fullnameDesc.label; &fullnameExample.label;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label class="awIdentityLabel" value="&fullnameLabel.label;"
+ accesskey="&fullnameLabel.accesskey;" control="fullName"/>
+ <textbox mailtype="identity" wsm_persist="true" name="fullName" id="fullName" flex="1" oninput="identityPageValidate();"/>
+ </hbox>
+ <separator/>
+ <grid>
+ <columns>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <description id="emailDescText"/>
+ </row>
+ <separator class="thin"/>
+ <row>
+ <hbox align="center">
+ <label class="awIdentityLabel" id="emailFieldLabel" value="&emailLabel.label;"
+ accesskey="&emailLabel.accesskey;" control="email"/>
+ <hbox class="uri-element" align="center" flex="1">
+ <textbox wsm_persist="true" mailtype="identity" name="email"
+ oninput="identityPageValidate();"
+ id="email" flex="6" class="uri-element"/>
+ <label id="postEmailText"/>
+ </hbox>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ </wizardpage>
+
+ <!-- Incoming page : User can choose to create mail account of his choice, POP3 or IMAP -->
+ <!-- Collects incoming server name and login name. -->
+ <!-- Login name is prefilled with user id from the email address provided in identity page -->
+ <!-- User can enter a login name here if it is different from the user id of his/her email address -->
+ <wizardpage id="incomingpage" pageid="incomingpage"
+ label="&incomingTitle.label;"
+ onpageshow="return incomingPageInit();"
+ onpageadvanced="return incomingPageUnload();">
+ <vbox flex="1">
+ <vbox id="serverTypeBox">
+ <label control="servertype">&incomingServerTypeDesc.label;</label>
+ <separator class="thin"/>
+ <hbox align="center" class="serverDataBox">
+ <!-- The initial value for the servertype radiogroup is set in onInit() -->
+ <radiogroup id="servertype" wsm_persist="true" orient="horizontal">
+ <radio group="servertype" value="pop3" id="pop3" label="&popType.label;"
+ wsm_persist="true" oncommand="setServerType();" accesskey="&popType.accesskey;"/>
+ <radio group="servertype" value="imap" id="imap" label="&imapType.label;"
+ wsm_persist="true" oncommand="setServerType();" accesskey="&imapType.accesskey;"/>
+ </radiogroup>
+ <label id="serverPortLabel" control="serverPort"
+ accesskey="&portNum.accesskey;"
+ value="&portNum.label;"/>
+ <textbox id="serverPort" type="number" size="3" max="65535"/>
+ <label id="defaultPortLabel" value="&defaultPortLabel.label;"/>
+ <label id="defaultPortValue" value="&defaultPortValue.label;"/>
+ </hbox>
+ <separator/>
+ </vbox>
+
+ <vbox id="incomingServerbox">
+ <description>&incomingServer.description;</description>
+ <hbox align="center" class="serverDataBox">
+ <label class="label, serverLabel"
+ value="&incomingServer.label;"
+ accesskey="&incomingServer.accesskey;"
+ control="incomingServer"/>
+ <textbox wsm_persist="true"
+ id="incomingServer"
+ flex="1"
+ class="uri-element"
+ oninput="incomingPageValidate();"/>
+ </hbox>
+ <hbox id="leaveMsgsOnSrvrBox" class="indent">
+ <checkbox id="leaveMessagesOnServer"
+ label="&leaveMsgsOnSrvr.label;"
+ accesskey="&leaveMsgsOnSrvr.accesskey;"
+ wsm_persist="true"
+ oncommand="setServerPrefs(this);"
+ checked="true"/>
+ </hbox>
+ <separator/>
+ </vbox>
+ <description>&incomingUsername.description;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label class="label"
+ value="&incomingUsername.label;"
+ style="width: 8em;"
+ accesskey="&incomingUsername.accesskey;"
+ control="username"/>
+ <textbox id="username"
+ wsm_persist="true"
+ flex="1"
+ oninput="incomingPageValidate();"/>
+ </hbox>
+ <vbox id="deferStorageBox">
+ <separator class="groove"/>
+ <description> &deferStorageDesc.label;</description>
+ <hbox>
+ <checkbox id="deferStorage"
+ label="&deferStorage.label;"
+ accesskey="&deferStorage.accesskey;"
+ checked="true"
+ wsm_persist="true"
+ oncommand="setServerPrefs(this);"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ </wizardpage>
+
+ <!-- Outgoing page : Collects outgoing server name and login name. -->
+ <!-- Outgoing server name is collected if there isn't one already -->
+ <!-- Login name is prefilled with user id from the email address provided in identity page -->
+ <!-- User can enter a login name here if it is different from the user id of his/her email address -->
+ <wizardpage id="outgoingpage" pageid="outgoingpage"
+ label="&outgoingTitle.label;"
+ onpageshow="return outgoingPageInit();"
+ onpageadvanced="return outgoingPageUnload();">
+ <vbox flex="1">
+ <vbox id="noSmtp">
+ <description>&outgoingServer.description;</description>
+ <hbox align="center" class="serverDataBox">
+ <label class="label, serverLabel"
+ value="&outgoingServer.label;"
+ accesskey="&outgoingServer.accesskey;"
+ control="smtphostname"/>
+ <textbox id="smtphostname"
+ wsm_persist="true"
+ flex="1"
+ class="uri-element"
+ oninput="outgoingPageValidate();"/>
+ </hbox>
+ </vbox>
+
+ <vbox id="haveSmtp">
+ <description id="smtpStaticText1"
+ style="width: 200px;"
+ prefix="&haveSmtp1.prefix;"
+ suffix="&haveSmtp1.suffix3; &modifyOutgoing.suffix;">*</description>
+ </vbox>
+
+ <vbox id="loginSet1">
+ <description>&outgoingUsername.description;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label class="label"
+ value="&outgoingUsername.label;"
+ style="width: 8em;"
+ accesskey="&outgoingUsername.accesskey;"
+ control="smtpusername"/>
+ <textbox id="smtpusername" wsm_persist="true" flex="1"/>
+ </hbox>
+ </vbox>
+
+ <vbox id="loginSet2" hidden="true">
+ <description id="smtpStaticText2" style="width: 200px;" prefix="&haveSmtp2.prefix;"
+ suffix="&haveSmtp2.suffix3; &modifyOutgoing.suffix;">*</description>
+ </vbox>
+
+ <vbox id="loginSet3" hidden="true">
+ <description id="smtpStaticText3" style="width: 200px;" prefix="&haveSmtp3.prefix;"
+ suffix="&haveSmtp3.suffix3; &modifyOutgoing.suffix;">*</description>
+ </vbox>
+
+ </vbox>
+ </wizardpage>
+
+ <!-- News Server page : Collects the News groups server name -->
+ <wizardpage id="newsserver" pageid="newsserver"
+ label="&incomingTitle.label;"
+ onpageshow="return incomingPageInit();"
+ onpageadvanced="return incomingPageUnload();">
+ <vbox flex="1">
+ <description>&newsServerNameDesc.label;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label control="newsServer" value="&newsServerLabel.label;" accesskey="&newsServerLabel.accesskey;" style="width: 8em;"/>
+ <textbox id="newsServer"
+ wsm_persist="true"
+ flex="1"
+ class="uri-element"
+ oninput="incomingPageValidate();"/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <!-- Account name page : User gets a choice to enter a pretty name for the account -->
+ <!-- Defaults : Mail account -> Email address, Newsgroup account -> Newsgroup server name -->
+ <wizardpage id="accnamepage" pageid="accnamepage"
+ label="&accnameTitle.label;"
+ onpageshow="return acctNamePageInit();"
+ onpageadvanced="return acctNamePageUnload();">
+ <vbox flex="1">
+ <description>&accnameDesc.label;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label class="label" value="&accnameLabel.label;" style="width: 8em;"
+ accesskey="&accnameLabel.accesskey;" control="prettyName"/>
+ <textbox id="prettyName" size="40" wsm_persist="true" flex="1" oninput="acctNamePageValidate();"/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <!-- Done page : this page summarizes information collected to create a mail/news account -->
+ <wizardpage id="done" pageid="done"
+ label="&completionTitle.label;"
+ onpageshow="return donePageInit();">
+ <vbox flex="1">
+ <description>&completionText.label;</description>
+ <separator class="thin"/>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center" id="account.name">
+ <label class="label" flex="1" id="account.name.label" value="&accnameLabel.label;"/>
+ <label class="label" id="account.name.text"/>
+ </row>
+ <row align="center" id="identity.email">
+ <label class="label" flex="1" id="identity.email.label" value="&emailLabel.label;"/>
+ <label class="label" id="identity.email.text"/>
+ </row>
+ <row align="center" id="server.username">
+ <label class="label" flex="1" id="server.username.label" value="&incomingUsername.label;"/>
+ <label class="label" id="server.username.text"/>
+ </row>
+ <row align="center" id="server.name">
+ <label class="label" flex="1" id="server.name.label" value="&serverNamePrefix.label;"/>
+ <label class="label" id="server.name.text"/>
+ </row>
+ <row align="center" id="server.type">
+ <label class="label" flex="1" id="server.type.label" value="&serverTypePrefix.label;"/>
+ <label class="label" id="server.type.text"/>
+ </row>
+ <row align="center" id="server.port">
+ <label class="label" id="server.port.label" flex="1" value="&portNum.label;"/>
+ <label class="label" id="server.port.text"/>
+ </row>
+ <row align="center" id="newsServer.name">
+ <label class="label" flex="1" id="newsServer.name.label" value="&newsServerNamePrefix.label;"/>
+ <label class="label" id="newsServer.name.text"/>
+ </row>
+ <row align="center" id="smtpServer.username">
+ <label class="label" flex="1" id="smtpServer.username.label" value="&outgoingUsername.label;"/>
+ <label class="label" id="smtpServer.username.text"/>
+ </row>
+ <row align="center" id="smtpServer.name">
+ <label class="label" flex="1" id="smtpServer.name.label" value="&smtpServerNamePrefix.label;"/>
+ <label class="label" id="smtpServer.name.text"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ <hbox id="downloadMsgsBox">
+ <checkbox id="downloadMsgs"
+ label="&downloadOnLogin.label;"
+ accesskey="&downloadOnLogin.accesskey;"
+ hidden="true"
+ checked="true"/>
+ </hbox>
+ <spacer flex="1"/>
+#ifndef XP_MACOSX
+ <description>&clickFinish.label;</description>
+#else
+ <description>&clickFinish.labelMac;</description>
+#endif
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="ispPage1"/>
+ <wizardpage id="ispPage2"/>
+ <wizardpage id="ispPage3"/>
+ <wizardpage id="ispPage4"/>
+ <wizardpage id="ispPage5"/>
+ <wizardpage id="ispPage6"/>
+ <wizardpage id="ispPage7"/>
+ <wizardpage id="ispPage8"/>
+ <wizardpage id="ispPage9"/>
+ <wizardpage id="ispPage10"/>
+ <wizardpage id="ispPage11"/>
+ <wizardpage id="ispPage12"/>
+ <wizardpage id="ispPage13"/>
+ <wizardpage id="ispPage14"/>
+ <wizardpage id="ispPage15"/>
+ <wizardpage id="ispPage16"/>
+
+</wizard>
diff --git a/mailnews/base/prefs/content/SmtpServerEdit.js b/mailnews/base/prefs/content/SmtpServerEdit.js
new file mode 100644
index 000000000..f84bbd00b
--- /dev/null
+++ b/mailnews/base/prefs/content/SmtpServerEdit.js
@@ -0,0 +1,46 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gSmtpServer;
+
+function onLoad(event)
+{
+ gSmtpServer = window.arguments[0].server;
+ initSmtpSettings(gSmtpServer);
+}
+
+function onAccept()
+{
+ if (!isLegalHostNameOrIP(cleanUpHostName(gSmtpHostname.value))) {
+ let prefsBundle = document.getElementById("bundle_prefs");
+ let brandBundle = document.getElementById("bundle_brand");
+ let alertTitle = brandBundle.getString("brandShortName");
+ let alertMsg = prefsBundle.getString("enterValidServerName");
+ Services.prompt.alert(window, alertTitle, alertMsg);
+
+ window.arguments[0].result = false;
+ return false;
+ }
+
+ // If we didn't have an SMTP server to initialize with,
+ // we must be creating one.
+ try {
+ if (!gSmtpServer) {
+ gSmtpServer = MailServices.smtp.createServer();
+ window.arguments[0].addSmtpServer = gSmtpServer.key;
+ }
+
+ saveSmtpSettings(gSmtpServer);
+ } catch (ex) {
+ Components.utils.reportError("Error saving smtp server: " + ex);
+ }
+
+ window.arguments[0].result = true;
+ return true;
+}
diff --git a/mailnews/base/prefs/content/SmtpServerEdit.xul b/mailnews/base/prefs/content/SmtpServerEdit.xul
new file mode 100644
index 000000000..767020d4d
--- /dev/null
+++ b/mailnews/base/prefs/content/SmtpServerEdit.xul
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xul-overlay href="chrome://messenger/content/smtpEditOverlay.xul"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/smtpEditOverlay.dtd">
+
+<dialog title="&smtpEditTitle.label;"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ ondialogaccept="return onAccept();">
+ <stringbundle id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"/>
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/SmtpServerEdit.js"/>
+ <vbox id="smtpServerEditor"/>
+</dialog>
diff --git a/mailnews/base/prefs/content/accountUtils.js b/mailnews/base/prefs/content/accountUtils.js
new file mode 100644
index 000000000..8b5020915
--- /dev/null
+++ b/mailnews/base/prefs/content/accountUtils.js
@@ -0,0 +1,462 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gAnyValidIdentity = false; //If there are no valid identities for any account
+// returns the first account with an invalid server or identity
+
+var gNewAccountToLoad = null; // used to load new messages if we come from the mail3pane
+
+function getInvalidAccounts(accounts)
+{
+ let numAccounts = accounts.length;
+ let invalidAccounts = new Array;
+ let numIdentities = 0;
+ for (let i = 0; i < numAccounts; i++) {
+ let account = accounts.queryElementAt(i, Components.interfaces.nsIMsgAccount);
+ try {
+ if (!account.incomingServer.valid) {
+ invalidAccounts[invalidAccounts.length] = account;
+ // skip to the next account
+ continue;
+ }
+ } catch (ex) {
+ // this account is busted, just keep going
+ continue;
+ }
+
+ var identities = account.identities;
+ numIdentities = identities.length;
+
+ for (var j = 0; j < numIdentities; j++) {
+ let identity = identities.queryElementAt(j, Components.interfaces.nsIMsgIdentity);
+ if (identity.valid) {
+ gAnyValidIdentity = true;
+ }
+ else {
+ invalidAccounts[invalidAccounts.length] = account;
+ }
+ }
+ }
+ return invalidAccounts;
+}
+
+function showMailIntegrationDialog() {
+ const nsIShellService = Components.interfaces.nsIShellService;
+
+ try {
+ var shellService = Components.classes["@mozilla.org/suite/shell-service;1"]
+ .getService(nsIShellService);
+ var appTypesCheck = shellService.shouldBeDefaultClientFor &
+ (nsIShellService.MAIL | nsIShellService.NEWS);
+
+ // show the default client dialog only if we have at least one account,
+ // if we should check for the default client, and we want to check if we are
+ // the default for mail/news and are not the default client for mail/news
+ if (appTypesCheck && shellService.shouldCheckDefaultClient &&
+ !shellService.isDefaultClient(true, appTypesCheck))
+ window.openDialog("chrome://communicator/content/defaultClientDialog.xul",
+ "DefaultClient", "modal,centerscreen,chrome,resizable=no");
+ } catch (ex) {}
+}
+
+/**
+ * Verify that there is at least one account. If not, open a new account wizard.
+ *
+ * @param wizardCallback if the wizard is run, callback when it is done.
+ * @param needsIdentity True only when verifyAccounts is called from the
+ * compose window. This last condition is so that we open
+ * the account wizard if the user does not have any
+ * identities defined and tries to compose mail.
+ * @param wizardOpen optional param that allows the caller to specify a
+ * different method to open a wizard. The wizardOpen method
+ * takes wizardCallback as an argument. The wizardCallback
+ * doesn't take any arguments.
+ */
+function verifyAccounts(wizardCallback, needsIdentity, wizardOpen)
+{
+ var openWizard = false;
+ var prefillAccount;
+ var state=true;
+ var ret = true;
+
+ try {
+ // migrate quoting preferences from global to per account. This function returns
+ // true if it had to migrate, which we will use to mean this is a just migrated
+ // or new profile
+ var newProfile = migrateGlobalQuotingPrefs(MailServices.accounts.allIdentities);
+
+ var accounts = MailServices.accounts.accounts;
+
+ // as long as we have some accounts, we're fine.
+ var accountCount = accounts.length;
+ var invalidAccounts = getInvalidAccounts(accounts);
+ if (invalidAccounts.length > 0 && invalidAccounts.length == accountCount) {
+ prefillAccount = invalidAccounts[0];
+ }
+
+ // if there are no accounts, or all accounts are "invalid"
+ // then kick off the account migration. Or if this is a new (to Mozilla) profile.
+ // MCD can set up accounts without the profile being used yet
+ if (newProfile) {
+ // check if MCD is configured. If not, say this is not a new profile
+ // so that we don't accidentally remigrate non MCD profiles.
+ var adminUrl;
+ try {
+ adminUrl = Services.prefs.getCharPref("autoadmin.global_config_url");
+ }
+ catch (ex) {}
+ if (!adminUrl)
+ newProfile = false;
+ }
+ if ((newProfile && !accountCount) || accountCount == invalidAccounts.length)
+ openWizard = true;
+
+ // openWizard is true if messenger migration returns some kind of
+ // error (including those cases where there is nothing to migrate).
+ // prefillAccount is non-null if there is at least one invalid account.
+ // gAnyValidIdentity is true when you've got at least one *valid*
+ // identity. Since local and RSS folders are identity-less accounts, if you
+ // only have one of those, it will be false.
+ // needsIdentity is true only when verifyAccounts is called from the
+ // compose window. This last condition is so that we open the account
+ // wizard if the user does not have any identities defined and tries to
+ // compose mail.
+
+ if (openWizard || prefillAccount || ((!gAnyValidIdentity) && needsIdentity))
+ {
+ if (wizardOpen != undefined)
+ wizardOpen(wizardCallback)
+ else
+ MsgAccountWizard(wizardCallback);
+ ret = false;
+ }
+ else
+ {
+ var localFoldersExists;
+ try
+ {
+ localFoldersExists = MailServices.accounts.localFoldersServer;
+ }
+ catch (ex)
+ {
+ localFoldersExists = false;
+ }
+
+ // we didn't create the MsgAccountWizard - we need to verify that local folders exists.
+ if (!localFoldersExists)
+ MailServices.accounts.createLocalMailAccount();
+ }
+
+ // This will do nothing on platforms without a shell service
+ const NS_SHELLSERVICE_CID = "@mozilla.org/suite/shell-service;1"
+ if (NS_SHELLSERVICE_CID in Components.classes)
+ {
+ // hack, set a time out to do this, so that the window can load first
+ setTimeout(showMailIntegrationDialog, 0);
+ }
+ return ret;
+ }
+ catch (ex) {
+ dump("error verifying accounts " + ex + "\n");
+ return false;
+ }
+}
+
+// we do this from a timer because if this is called from the onload=
+// handler, then the parent window doesn't appear until after the wizard
+// has closed, and this is confusing to the user
+function MsgAccountWizard(wizardCallback)
+{
+ setTimeout(function() { msgOpenAccountWizard(wizardCallback); }, 0);
+}
+
+/**
+ * Open the Old Mail Account Wizard, or focus it if it's already open.
+ *
+ * @param wizardCallback if the wizard is run, callback when it is done.
+ * @param type - optional account type token, for Tb.
+ * @see msgNewMailAccount below for the new implementation.
+ */
+function msgOpenAccountWizard(wizardCallback, type)
+{
+ gNewAccountToLoad = null;
+
+ window.openDialog("chrome://messenger/content/AccountWizard.xul", "AccountWizard",
+ "chrome,modal,titlebar,centerscreen",
+ {okCallback: wizardCallback, acctType: type});
+
+ loadInboxForNewAccount();
+
+ // If we started with no servers at all and "smtp servers" list selected,
+ // refresh display somehow. Bug 58506.
+ // TODO Better fix: select newly created account (in all cases)
+ if (typeof(getCurrentAccount) == "function" && // in AccountManager, not menu
+ !getCurrentAccount())
+ selectServer(null, null);
+}
+
+function initAccountWizardTB(args) {
+ let type = args[0] && args[0].acctType;
+ let selType = type == "newsgroups" ? "newsaccount" :
+ type == "movemail" ? "Movemail" : null;
+ let accountwizard = document.getElementById("AccountWizard");
+ let acctyperadio = document.getElementById("acctyperadio");
+ let feedRadio = acctyperadio.querySelector("radio[value='Feeds']");
+ if (feedRadio)
+ feedRadio.remove();
+ if (selType) {
+ acctyperadio.selectedItem = acctyperadio.querySelector("radio[value='"+selType+"']");
+ accountwizard.advance("identitypage");
+ }
+ else
+ acctyperadio.selectedItem = acctyperadio.getItemAtIndex(0);
+}
+
+function AddFeedAccount() {
+ window.openDialog("chrome://messenger-newsblog/content/feedAccountWizard.xul",
+ "", "chrome,modal,titlebar,centerscreen");
+}
+
+/**
+ * Opens the account settings window on the specified account
+ * and page of settings. If the window is already open it is only focused.
+ *
+ * @param selectPage The xul file name for the viewing page or
+ * null for the account main page. Other pages are
+ * 'am-server.xul', 'am-copies.xul', 'am-offline.xul',
+ * 'am-addressing.xul', 'am-smtp.xul'
+ * @param aServer The server of the account to select. Optional.
+ */
+function MsgAccountManager(selectPage, aServer)
+{
+ var existingAccountManager = Services.wm.getMostRecentWindow("mailnews:accountmanager");
+
+ if (existingAccountManager)
+ existingAccountManager.focus();
+ else {
+ if (!aServer) {
+ if (typeof GetSelectedMsgFolders === "function") {
+ let folders = GetSelectedMsgFolders();
+ if (folders.length > 0)
+ aServer = folders[0].server;
+ }
+ if (!aServer && (typeof GetDefaultAccountRootFolder === "function")) {
+ let folder = GetDefaultAccountRootFolder();
+ if (folder instanceof Components.interfaces.nsIMsgFolder)
+ aServer = folder.server;
+ }
+ }
+
+ window.openDialog("chrome://messenger/content/AccountManager.xul",
+ "AccountManager",
+ "chrome,centerscreen,modal,titlebar,resizable",
+ { server: aServer, selectPage: selectPage });
+ }
+}
+
+function loadInboxForNewAccount()
+{
+ // gNewAccountToLoad is set in the final screen of the Account Wizard if a POP account
+ // was created, the download messages box is checked, and the wizard was opened from the 3pane
+ if (gNewAccountToLoad) {
+ var rootMsgFolder = gNewAccountToLoad.incomingServer.rootMsgFolder;
+ const kInboxFlag = Components.interfaces.nsMsgFolderFlags.Inbox;
+ var inboxFolder = rootMsgFolder.getFolderWithFlags(kInboxFlag);
+ SelectFolder(inboxFolder.URI);
+ window.focus();
+ setTimeout(MsgGetMessage, 0);
+ gNewAccountToLoad = null;
+ }
+}
+
+// returns true if we migrated - it knows this because 4.x did not have the
+// pref mailnews.quotingPrefs.version, so if it's not set, we're either
+// migrating from 4.x, or a much older version of Mozilla.
+function migrateGlobalQuotingPrefs(allIdentities)
+{
+ // if reply_on_top and auto_quote exist then, if non-default
+ // migrate and delete, if default just delete.
+ var reply_on_top = 0;
+ var auto_quote = true;
+ var quotingPrefs = 0;
+ var migrated = false;
+ try {
+ quotingPrefs = Services.prefs.getIntPref("mailnews.quotingPrefs.version");
+ } catch (ex) {}
+
+ // If the quotingPrefs version is 0 then we need to migrate our preferences
+ if (quotingPrefs == 0) {
+ migrated = true;
+ try {
+ reply_on_top = Services.prefs.getIntPref("mailnews.reply_on_top");
+ auto_quote = Services.prefs.getBoolPref("mail.auto_quote");
+ } catch (ex) {}
+
+ if (!auto_quote || reply_on_top) {
+ let numIdentities = allIdentities.length;
+ var identity = null;
+ for (var j = 0; j < numIdentities; j++) {
+ identity = allIdentities.queryElementAt(j, Components.interfaces.nsIMsgIdentity);
+ if (identity.valid) {
+ identity.autoQuote = auto_quote;
+ identity.replyOnTop = reply_on_top;
+ }
+ }
+ }
+ Services.prefs.setIntPref("mailnews.quotingPrefs.version", 1);
+ }
+ return migrated;
+}
+
+// we do this from a timer because if this is called from the onload=
+// handler, then the parent window doesn't appear until after the wizard
+// has closed, and this is confusing to the user
+function NewMailAccount(msgWindow, okCallback, extraData)
+{
+ if (!msgWindow)
+ throw new Error("NewMailAccount must be given a msgWindow.");
+
+ // Populate the extra data.
+ if (!extraData)
+ extraData = {};
+ extraData.msgWindow = msgWindow;
+
+ let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane");
+
+ if (!extraData.NewMailAccount)
+ extraData.NewMailAccount = NewMailAccount;
+
+ if (!extraData.msgNewMailAccount)
+ extraData.msgNewMailAccount = msgNewMailAccount;
+
+ if (!extraData.NewComposeMessage)
+ extraData.NewComposeMessage = mail3Pane.ComposeMessage;
+
+ if (!extraData.openAddonsMgr)
+ extraData.openAddonsMgr = mail3Pane.openAddonsMgr;
+
+ if (!extraData.okCallback)
+ extraData.okCallback = null;
+
+ if (!extraData.success)
+ extraData.success = false;
+
+ setTimeout(extraData.msgNewMailAccount, 0, msgWindow, okCallback, extraData);
+}
+
+function NewMailAccountProvisioner(aMsgWindow, args) {
+ if (!args)
+ args = {};
+ if (!aMsgWindow)
+ aMsgWindow = MailServices.mailSession.topmostMsgWindow;
+
+ args.msgWindow = aMsgWindow;
+
+ let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane");
+
+ // If we couldn't find a 3pane, bail out.
+ if (!mail3Pane) {
+ Components.utils.reportError("Could not find a 3pane to connect to.");
+ return;
+ }
+
+ let tabmail = mail3Pane.document.getElementById("tabmail");
+
+ if (!tabmail) {
+ Components.utils.reportError("Could not find a tabmail in the 3pane!");
+ return;
+ }
+
+ // If there's already an accountProvisionerTab open, just focus it instead
+ // of opening a new dialog.
+ let apTab = tabmail.getTabInfoForCurrentOrFirstModeInstance(
+ tabmail.tabModes["accountProvisionerTab"]);
+
+ if (apTab) {
+ tabmail.switchToTab(apTab);
+ return;
+ }
+
+ // XXX make sure these are all defined in all contexts... to be on the safe
+ // side, just get a mail:3pane and borrow the functions from it?
+ if (!args.NewMailAccount)
+ args.NewMailAccount = NewMailAccount;
+
+ if (!args.msgNewMailAccount)
+ args.msgNewMailAccount = msgNewMailAccount;
+
+ if (!args.NewComposeMessage)
+ args.NewComposeMessage = mail3Pane.ComposeMessage;
+
+ if (!args.openAddonsMgr)
+ args.openAddonsMgr = mail3Pane.openAddonsMgr;
+
+ if (!args.okCallback)
+ args.okCallback = null;
+
+ let windowParams = "chrome,titlebar,centerscreen,width=640,height=480";
+
+ if (!args.success) {
+ args.success = false;
+ // If we're not opening up the success dialog, then our window should be
+ // modal.
+ windowParams = "modal," + windowParams;
+ }
+
+ // NOTE: If you're a developer, and you notice that the jQuery code in
+ // accountProvisioner.xhtml isn't throwing errors or warnings, that's due
+ // to bug 688273. Just make the window non-modal to get those errors and
+ // warnings back, and then clear this comment when bug 688273 is closed.
+ window.openDialog(
+ "chrome://messenger/content/newmailaccount/accountProvisioner.xhtml",
+ "AccountCreation",
+ windowParams,
+ args);
+}
+
+/**
+ * Open the New Mail Account Wizard, or focus it if it's already open.
+ *
+ * @param msgWindow a msgWindow for us to use to verify the accounts.
+ * @param okCallback an optional callback for us to call back to if
+ * everything's okay.
+ * @param extraData an optional param that allows us to pass data in and
+ * out. Used in the upcoming AccountProvisioner add-on.
+ * @see msgOpenAccountWizard above for the previous implementation.
+ */
+function msgNewMailAccount(msgWindow, okCallback, extraData)
+{
+ if (!msgWindow)
+ throw new Error("msgNewMailAccount must be given a msgWindow.");
+
+ let existingWindow = Services.wm.getMostRecentWindow("mail:autoconfig");
+ if (existingWindow) {
+ existingWindow.focus();
+ } else {
+ // disabling modal for the time being, see 688273 REMOVEME
+ window.openDialog("chrome://messenger/content/accountcreation/emailWizard.xul",
+ "AccountSetup", "chrome,titlebar,centerscreen",
+ {msgWindow:msgWindow,
+ okCallback:okCallback,
+ extraData:extraData});
+ }
+
+ /*
+ // TODO: Enable this block of code once the dialog above is made modal.
+ // If we started with no servers at all and "smtp servers" list selected,
+ // refresh display somehow. Bug 58506.
+ // TODO Better fix: select newly created account (in all cases)
+ let existingAccountManager =
+ Services.wm.getMostRecentWindow("mailnews:accountmanager");
+ // in AccountManager, not menu
+ if (existingAccountManager && typeof(getCurrentAccount) == "function" &&
+ !getCurrentAccount()) {
+ selectServer(null, null);
+ }
+ */
+}
diff --git a/mailnews/base/prefs/content/accountcreation/MyBadCertHandler.js b/mailnews/base/prefs/content/accountcreation/MyBadCertHandler.js
new file mode 100644
index 000000000..9f4c5034e
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/MyBadCertHandler.js
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This class implements nsIBadCertListener. It's job is to prevent "bad cert"
+ * security dialogs from being shown to the user. We call back to the
+ * 'callback' object's method "processCertError" so that it can deal with it as
+ * needed (in the case of autoconfig, setting up temporary overrides).
+ */
+function BadCertHandler(callback)
+{
+ this._init(callback);
+}
+
+BadCertHandler.prototype =
+{
+ _init: function(callback) {
+ this._callback = callback;
+ },
+
+ // Suppress any certificate errors
+ notifyCertProblem: function(socketInfo, status, targetSite) {
+ return this._callback.processCertError(socketInfo, status, targetSite);
+ },
+
+ // nsIInterfaceRequestor
+ getInterface: function(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsISupports
+ QueryInterface: function(iid) {
+ if (!iid.equals(Components.interfaces.nsIBadCertListener2) &&
+ !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
+ !iid.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
diff --git a/mailnews/base/prefs/content/accountcreation/accountConfig.js b/mailnews/base/prefs/content/accountcreation/accountConfig.js
new file mode 100644
index 000000000..752c72624
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/accountConfig.js
@@ -0,0 +1,261 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file creates the class AccountConfig, which is a JS object that holds
+ * a configuration for a certain account. It is *not* created in the backend
+ * yet (use aw-createAccount.js for that), and it may be incomplete.
+ *
+ * Several AccountConfig objects may co-exist, e.g. for autoconfig.
+ * One AccountConfig object is used to prefill and read the widgets
+ * in the Wizard UI.
+ * When we autoconfigure, we autoconfig writes the values into a
+ * new object and returns that, and the caller can copy these
+ * values into the object used by the UI.
+ *
+ * See also
+ * <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>
+ * for values stored.
+ */
+
+function AccountConfig()
+{
+ this.incoming = this.createNewIncoming();
+ this.incomingAlternatives = [];
+ this.outgoing = this.createNewOutgoing();
+ this.outgoingAlternatives = [];
+ this.identity =
+ {
+ // displayed real name of user
+ realname : "%REALNAME%",
+ // email address of user, as shown in From of outgoing mails
+ emailAddress : "%EMAILADDRESS%",
+ };
+ this.inputFields = [];
+ this.domains = [];
+};
+AccountConfig.prototype =
+{
+ // @see createNewIncoming()
+ incoming : null,
+ // @see createNewOutgoing()
+ outgoing : null,
+ /**
+ * Other servers which can be used instead of |incoming|,
+ * in order of decreasing preference.
+ * (|incoming| itself should not be included here.)
+ * { Array of incoming/createNewIncoming() }
+ */
+ incomingAlternatives : null,
+ outgoingAlternatives : null,
+#ifdef MOZ_MAILNEWS_OAUTH2
+ // OAuth2 configuration, if needed.
+ oauthSettings : null,
+#endif
+ // just an internal string to refer to this. Do not show to user.
+ id : null,
+ // who created the config.
+ // { one of kSource* }
+ source : 0,
+ displayName : null,
+ // { Array of { varname (value without %), displayName, exampleValue } }
+ inputFields : null,
+ // email address domains for which this config is applicable
+ // { Array of Strings }
+ domains : null,
+
+ /**
+ * Factory function for incoming and incomingAlternatives
+ */
+ createNewIncoming : function()
+ {
+ return {
+ // { String-enum: "pop3", "imap", "nntp" }
+ type : null,
+ hostname : null,
+ // { Integer }
+ port : null,
+ // May be a placeholder (starts and ends with %). { String }
+ username : null,
+ password : null,
+ // { enum: 1 = plain, 2 = SSL/TLS, 3 = STARTTLS always, 0 = not inited }
+ // ('TLS when available' is insecure and not supported here)
+ socketType : 0,
+ /**
+ * true when the cert is invalid (and thus SSL useless), because it's
+ * 1) not from an accepted CA (including self-signed certs)
+ * 2) for a different hostname or
+ * 3) expired.
+ * May go back to false when user explicitly accepted the cert.
+ */
+ badCert : false,
+ /**
+ * How to log in to the server: plaintext or encrypted pw, GSSAPI etc.
+ * Defined by Ci.nsMsgAuthMethod
+ * Same as server pref "authMethod".
+ */
+ auth : 0,
+ /**
+ * Other auth methods that we think the server supports.
+ * They are ordered by descreasing preference.
+ * (|auth| itself is not included in |authAlternatives|)
+ * {Array of Ci.nsMsgAuthMethod} (same as .auth)
+ */
+ authAlternatives : null,
+ // in minutes { Integer }
+ checkInterval : 10,
+ loginAtStartup : true,
+ // POP3 only:
+ // Not yet implemented. { Boolean }
+ useGlobalInbox : false,
+ leaveMessagesOnServer : true,
+ daysToLeaveMessagesOnServer : 14,
+ deleteByAgeFromServer : true,
+ // When user hits delete, delete from local store and from server
+ deleteOnServerWhenLocalDelete : true,
+ downloadOnBiff : true,
+ };
+ },
+ /**
+ * Factory function for outgoing and outgoingAlternatives
+ */
+ createNewOutgoing : function()
+ {
+ return {
+ type : "smtp",
+ hostname : null,
+ port : null, // see incoming
+ username : null, // see incoming. may be null, if auth is 0.
+ password : null, // see incoming. may be null, if auth is 0.
+ socketType : 0, // see incoming
+ badCert : false, // see incoming
+ auth : 0, // see incoming
+ authAlternatives : null, // see incoming
+ addThisServer : true, // if we already have an SMTP server, add this
+ // if we already have an SMTP server, use it.
+ useGlobalPreferredServer : false,
+ // we should reuse an already configured SMTP server.
+ // nsISmtpServer.key
+ existingServerKey : null,
+ // user display value for existingServerKey
+ existingServerLabel : null,
+ };
+ },
+
+ /**
+ * Returns a deep copy of this object,
+ * i.e. modifying the copy will not affect the original object.
+ */
+ copy : function()
+ {
+ // Workaround: deepCopy() fails to preserve base obj (instanceof)
+ var result = new AccountConfig();
+ for (var prop in this)
+ result[prop] = deepCopy(this[prop]);
+
+ return result;
+ },
+ isComplete : function()
+ {
+ return (!!this.incoming.hostname && !!this.incoming.port &&
+ !!this.incoming.socketType && !!this.incoming.auth &&
+ !!this.incoming.username &&
+ (!!this.outgoing.existingServerKey ||
+ (!!this.outgoing.hostname && !!this.outgoing.port &&
+ !!this.outgoing.socketType && !!this.outgoing.auth &&
+ !!this.outgoing.username)));
+ },
+};
+
+
+// enum consts
+
+// .source
+AccountConfig.kSourceUser = 1; // user manually entered the config
+AccountConfig.kSourceXML = 2; // config from XML from ISP or Mozilla DB
+AccountConfig.kSourceGuess = 3; // guessConfig()
+
+
+/**
+ * Some fields on the account config accept placeholders (when coming from XML).
+ *
+ * These are the predefined ones
+ * * %EMAILADDRESS% (full email address of the user, usually entered by user)
+ * * %EMAILLOCALPART% (email address, part before @)
+ * * %EMAILDOMAIN% (email address, part after @)
+ * * %REALNAME%
+ * as well as those defined in account.inputFields.*.varname, with % added
+ * before and after.
+ *
+ * These must replaced with real values, supplied by the user or app,
+ * before the account is created. This is done here. You call this function once
+ * you have all the data - gathered the standard vars mentioned above as well as
+ * all listed in account.inputFields, and pass them in here. This function will
+ * insert them in the fields, returning a fully filled-out account ready to be
+ * created.
+ *
+ * @param account {AccountConfig}
+ * The account data to be modified. It may or may not contain placeholders.
+ * After this function, it should not contain placeholders anymore.
+ * This object will be modified in-place.
+ *
+ * @param emailfull {String}
+ * Full email address of this account, e.g. "joe@example.com".
+ * Empty of incomplete email addresses will/may be rejected.
+ *
+ * @param realname {String}
+ * Real name of user, as will appear in From of outgoing messages
+ *
+ * @param password {String}
+ * The password for the incoming server and (if necessary) the outgoing server
+ */
+function replaceVariables(account, realname, emailfull, password)
+{
+ sanitize.nonemptystring(emailfull);
+ let emailsplit = emailfull.split("@");
+ assert(emailsplit.length == 2,
+ "email address not in expected format: must contain exactly one @");
+ let emaillocal = sanitize.nonemptystring(emailsplit[0]);
+ let emaildomain = sanitize.hostname(emailsplit[1]);
+ sanitize.label(realname);
+ sanitize.nonemptystring(realname);
+
+ let otherVariables = {};
+ otherVariables.EMAILADDRESS = emailfull;
+ otherVariables.EMAILLOCALPART = emaillocal;
+ otherVariables.EMAILDOMAIN = emaildomain;
+ otherVariables.REALNAME = realname;
+
+ if (password) {
+ account.incoming.password = password;
+ account.outgoing.password = password; // set member only if auth required?
+ }
+ account.incoming.username = _replaceVariable(account.incoming.username,
+ otherVariables);
+ account.outgoing.username = _replaceVariable(account.outgoing.username,
+ otherVariables);
+ account.incoming.hostname =
+ _replaceVariable(account.incoming.hostname, otherVariables);
+ if (account.outgoing.hostname) // will be null if user picked existing server.
+ account.outgoing.hostname =
+ _replaceVariable(account.outgoing.hostname, otherVariables);
+ account.identity.realname =
+ _replaceVariable(account.identity.realname, otherVariables);
+ account.identity.emailAddress =
+ _replaceVariable(account.identity.emailAddress, otherVariables);
+ account.displayName = _replaceVariable(account.displayName, otherVariables);
+}
+
+function _replaceVariable(variable, values)
+{
+ let str = variable;
+ if (typeof(str) != "string")
+ return str;
+
+ for (let varname in values)
+ str = str.replace("%" + varname + "%", values[varname]);
+
+ return str;
+}
diff --git a/mailnews/base/prefs/content/accountcreation/createInBackend.js b/mailnews/base/prefs/content/accountcreation/createInBackend.js
new file mode 100644
index 000000000..cd984fd3c
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/createInBackend.js
@@ -0,0 +1,337 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Takes an |AccountConfig| JS object and creates that account in the
+ * Thunderbird backend (which also writes it to prefs).
+ *
+ * @param config {AccountConfig} The account to create
+ *
+ * @return - the account created.
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function createAccountInBackend(config)
+{
+ // incoming server
+ let inServer = MailServices.accounts.createIncomingServer(
+ config.incoming.username,
+ config.incoming.hostname,
+ sanitize.enum(config.incoming.type, ["pop3", "imap", "nntp"]));
+ inServer.port = config.incoming.port;
+ inServer.authMethod = config.incoming.auth;
+ inServer.password = config.incoming.password;
+ if (config.rememberPassword && config.incoming.password.length)
+ rememberPassword(inServer, config.incoming.password);
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ if (inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) {
+ inServer.setCharValue("oauth2.scope", config.oauthSettings.scope);
+ inServer.setCharValue("oauth2.issuer", config.oauthSettings.issuer);
+ }
+#endif
+
+ // SSL
+ if (config.incoming.socketType == 1) // plain
+ inServer.socketType = Ci.nsMsgSocketType.plain;
+ else if (config.incoming.socketType == 2) // SSL / TLS
+ inServer.socketType = Ci.nsMsgSocketType.SSL;
+ else if (config.incoming.socketType == 3) // STARTTLS
+ inServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+ //inServer.prettyName = config.displayName;
+ inServer.prettyName = config.identity.emailAddress;
+
+ inServer.doBiff = true;
+ inServer.biffMinutes = config.incoming.checkInterval;
+ const loginAtStartupPrefTemplate =
+ "mail.server.%serverkey%.login_at_startup";
+ var loginAtStartupPref =
+ loginAtStartupPrefTemplate.replace("%serverkey%", inServer.key);
+ Services.prefs.setBoolPref(loginAtStartupPref,
+ config.incoming.loginAtStartup);
+ if (config.incoming.type == "pop3")
+ {
+ const leaveOnServerPrefTemplate =
+ "mail.server.%serverkey%.leave_on_server";
+ const daysToLeaveOnServerPrefTemplate =
+ "mail.server.%serverkey%.num_days_to_leave_on_server";
+ const deleteFromServerPrefTemplate =
+ "mail.server.%serverkey%.delete_mail_left_on_server";
+ const deleteByAgeFromServerPrefTemplate =
+ "mail.server.%serverkey%.delete_by_age_from_server";
+ const downloadOnBiffPrefTemplate =
+ "mail.server.%serverkey%.download_on_biff";
+ var leaveOnServerPref =
+ leaveOnServerPrefTemplate.replace("%serverkey%", inServer.key);
+ var ageFromServerPref =
+ deleteByAgeFromServerPrefTemplate.replace("%serverkey%", inServer.key);
+ var daysToLeaveOnServerPref =
+ daysToLeaveOnServerPrefTemplate.replace("%serverkey%", inServer.key);
+ var deleteFromServerPref =
+ deleteFromServerPrefTemplate.replace("%serverkey%", inServer.key);
+ let downloadOnBiffPref =
+ downloadOnBiffPrefTemplate.replace("%serverkey%", inServer.key);
+ Services.prefs.setBoolPref(leaveOnServerPref,
+ config.incoming.leaveMessagesOnServer);
+ Services.prefs.setIntPref(daysToLeaveOnServerPref,
+ config.incoming.daysToLeaveMessagesOnServer);
+ Services.prefs.setBoolPref(deleteFromServerPref,
+ config.incoming.deleteOnServerWhenLocalDelete);
+ Services.prefs.setBoolPref(ageFromServerPref,
+ config.incoming.deleteByAgeFromServer);
+ Services.prefs.setBoolPref(downloadOnBiffPref,
+ config.incoming.downloadOnBiff);
+ }
+ inServer.valid = true;
+
+ let username = config.outgoing.auth > 1 ? config.outgoing.username : null;
+ let outServer = MailServices.smtp.findServer(username, config.outgoing.hostname);
+ assert(config.outgoing.addThisServer ||
+ config.outgoing.useGlobalPreferredServer ||
+ config.outgoing.existingServerKey,
+ "No SMTP server: inconsistent flags");
+
+ if (config.outgoing.addThisServer && !outServer)
+ {
+ outServer = MailServices.smtp.createServer();
+ outServer.hostname = config.outgoing.hostname;
+ outServer.port = config.outgoing.port;
+ outServer.authMethod = config.outgoing.auth;
+ if (config.outgoing.auth > 1)
+ {
+ outServer.username = username;
+ outServer.password = config.incoming.password;
+ if (config.rememberPassword && config.incoming.password.length)
+ rememberPassword(outServer, config.incoming.password);
+ }
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ if (outServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) {
+ let pref = "mail.smtpserver." + outServer.key + ".";
+ Services.prefs.setCharPref(pref + "oauth2.scope",
+ config.oauthSettings.scope);
+ Services.prefs.setCharPref(pref + "oauth2.issuer",
+ config.oauthSettings.issuer);
+ }
+#endif
+
+ if (config.outgoing.socketType == 1) // no SSL
+ outServer.socketType = Ci.nsMsgSocketType.plain;
+ else if (config.outgoing.socketType == 2) // SSL / TLS
+ outServer.socketType = Ci.nsMsgSocketType.SSL;
+ else if (config.outgoing.socketType == 3) // STARTTLS
+ outServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+
+ // API problem: <http://mxr.mozilla.org/seamonkey/source/mailnews/compose/public/nsISmtpServer.idl#93>
+ outServer.description = config.displayName;
+ if (config.password)
+ outServer.password = config.outgoing.password;
+
+ // If this is the first SMTP server, set it as default
+ if (!MailServices.smtp.defaultServer ||
+ !MailServices.smtp.defaultServer.hostname)
+ MailServices.smtp.defaultServer = outServer;
+ }
+
+ // identity
+ // TODO accounts without identity?
+ let identity = MailServices.accounts.createIdentity();
+ identity.fullName = config.identity.realname;
+ identity.email = config.identity.emailAddress;
+
+ // for new accounts, default to replies being positioned above the quote
+ // if a default account is defined already, take its settings instead
+ if (config.incoming.type == "imap" || config.incoming.type == "pop3")
+ {
+ identity.replyOnTop = 1;
+ // identity.sigBottom = false; // don't set this until Bug 218346 is fixed
+
+ if (MailServices.accounts.accounts.length &&
+ MailServices.accounts.defaultAccount)
+ {
+ let defAccount = MailServices.accounts.defaultAccount;
+ let defIdentity = defAccount.defaultIdentity;
+ if (defAccount.incomingServer.canBeDefaultServer &&
+ defIdentity && defIdentity.valid)
+ {
+ identity.replyOnTop = defIdentity.replyOnTop;
+ identity.sigBottom = defIdentity.sigBottom;
+ }
+ }
+ }
+
+ // due to accepted conventions, news accounts should default to plain text
+ if (config.incoming.type == "nntp")
+ identity.composeHtml = false;
+
+ identity.valid = true;
+
+ if (config.outgoing.existingServerKey)
+ identity.smtpServerKey = config.outgoing.existingServerKey;
+ else if (!config.outgoing.useGlobalPreferredServer)
+ identity.smtpServerKey = outServer.key;
+
+ // account and hook up
+ // Note: Setting incomingServer will cause the AccountManager to refresh
+ // itself, which could be a problem if we came from it and we haven't set
+ // the identity (see bug 521955), so make sure everything else on the
+ // account is set up before you set the incomingServer.
+ let account = MailServices.accounts.createAccount();
+ account.addIdentity(identity);
+ account.incomingServer = inServer;
+ if (inServer.canBeDefaultServer && (!MailServices.accounts.defaultAccount ||
+ !MailServices.accounts.defaultAccount
+ .incomingServer.canBeDefaultServer))
+ MailServices.accounts.defaultAccount = account;
+
+ verifyLocalFoldersAccount(MailServices.accounts);
+ setFolders(identity, inServer);
+
+ // save
+ MailServices.accounts.saveAccountInfo();
+ try {
+ Services.prefs.savePrefFile(null);
+ } catch (ex) {
+ ddump("Could not write out prefs: " + ex);
+ }
+ return account;
+}
+
+function setFolders(identity, server)
+{
+ // TODO: support for local folders for global inbox (or use smart search
+ // folder instead)
+
+ var baseURI = server.serverURI + "/";
+
+ // Names will be localized in UI, not in folder names on server/disk
+ // TODO allow to override these names in the XML config file,
+ // in case e.g. Google or AOL use different names?
+ // Workaround: Let user fix it :)
+ var fccName = "Sent";
+ var draftName = "Drafts";
+ var templatesName = "Templates";
+
+ identity.draftFolder = baseURI + draftName;
+ identity.stationeryFolder = baseURI + templatesName;
+ identity.fccFolder = baseURI + fccName;
+
+ identity.fccFolderPickerMode = 0;
+ identity.draftsFolderPickerMode = 0;
+ identity.tmplFolderPickerMode = 0;
+}
+
+function rememberPassword(server, password)
+{
+ if (server instanceof Components.interfaces.nsIMsgIncomingServer)
+ var passwordURI = server.localStoreType + "://" + server.hostName;
+ else if (server instanceof Components.interfaces.nsISmtpServer)
+ var passwordURI = "smtp://" + server.hostname;
+ else
+ throw new NotReached("Server type not supported");
+
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
+ .createInstance(Ci.nsILoginInfo);
+ login.init(passwordURI, null, passwordURI, server.username, password, "", "");
+ try {
+ Services.logins.addLogin(login);
+ } catch (e) {
+ if (e.message.includes("This login already exists")) {
+ // TODO modify
+ } else {
+ throw e;
+ }
+ }
+}
+
+/**
+ * Check whether the user's setup already has an incoming server
+ * which matches (hostname, port, username) the primary one
+ * in the config.
+ * (We also check the email address as username.)
+ *
+ * @param config {AccountConfig} filled in (no placeholders)
+ * @return {nsIMsgIncomingServer} If it already exists, the server
+ * object is returned.
+ * If it's a new server, |null| is returned.
+ */
+function checkIncomingServerAlreadyExists(config)
+{
+ assert(config instanceof AccountConfig);
+ let incoming = config.incoming;
+ let existing = MailServices.accounts.findRealServer(incoming.username,
+ incoming.hostname,
+ sanitize.enum(incoming.type, ["pop3", "imap", "nntp"]),
+ incoming.port);
+
+ // if username does not have an '@', also check the e-mail
+ // address form of the name.
+ if (!existing && !incoming.username.includes("@"))
+ existing = MailServices.accounts.findRealServer(config.identity.emailAddress,
+ incoming.hostname,
+ sanitize.enum(incoming.type, ["pop3", "imap", "nntp"]),
+ incoming.port);
+ return existing;
+};
+
+/**
+ * Check whether the user's setup already has an outgoing server
+ * which matches (hostname, port, username) the primary one
+ * in the config.
+ *
+ * @param config {AccountConfig} filled in (no placeholders)
+ * @return {nsISmtpServer} If it already exists, the server
+ * object is returned.
+ * If it's a new server, |null| is returned.
+ */
+function checkOutgoingServerAlreadyExists(config)
+{
+ assert(config instanceof AccountConfig);
+ let smtpServers = MailServices.smtp.servers;
+ while (smtpServers.hasMoreElements())
+ {
+ let existingServer = smtpServers.getNext()
+ .QueryInterface(Ci.nsISmtpServer);
+ // TODO check username with full email address, too, like for incoming
+ if (existingServer.hostname == config.outgoing.hostname &&
+ existingServer.port == config.outgoing.port &&
+ existingServer.username == config.outgoing.username)
+ return existingServer;
+ }
+ return null;
+};
+
+/**
+ * Check if there already is a "Local Folders". If not, create it.
+ * Copied from AccountWizard.js with minor updates.
+ */
+function verifyLocalFoldersAccount(am)
+{
+ let localMailServer;
+ try {
+ localMailServer = am.localFoldersServer;
+ }
+ catch (ex) {
+ localMailServer = null;
+ }
+
+ try {
+ if (!localMailServer)
+ {
+ // creates a copy of the identity you pass in
+ am.createLocalMailAccount();
+ try {
+ localMailServer = am.localFoldersServer;
+ }
+ catch (ex) {
+ ddump("Error! we should have found the local mail server " +
+ "after we created it.");
+ }
+ }
+ }
+ catch (ex) { ddump("Error in verifyLocalFoldersAccount " + ex); }
+}
diff --git a/mailnews/base/prefs/content/accountcreation/emailWizard.js b/mailnews/base/prefs/content/accountcreation/emailWizard.js
new file mode 100644
index 000000000..2e4a44e2a
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/emailWizard.js
@@ -0,0 +1,1976 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+#ifdef MOZ_MAILNEWS_OAUTH2
+Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
+#endif
+
+/**
+ * This is the dialog opened by menu File | New account | Mail... .
+ *
+ * It gets the user's realname, email address and password,
+ * and tries to automatically configure the account from that,
+ * using various mechanisms. If all fails, the user can enter/edit
+ * the config, then we create the account.
+ *
+ * Steps:
+ * - User enters realname, email address and password
+ * - check for config files on disk
+ * (shipping with Thunderbird, for enterprise deployments)
+ * - (if fails) try to get the config file from the ISP via a
+ * fixed URL on the domain of the email address
+ * - (if fails) try to get the config file from our own database
+ * at MoMo servers, maintained by the community
+ * - (if fails) try to guess the config, by guessing hostnames,
+ * probing ports, checking config via server's CAPS line etc..
+ * - verify the setup, by trying to login to the configured servers
+ * - let user verify and maybe edit the server names and ports
+ * - If user clicks OK, create the account
+ */
+
+
+// from http://xyfer.blogspot.com/2005/01/javascript-regexp-email-validator.html
+var emailRE = /^[-_a-z0-9\'+*$^&%=~!?{}]+(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*@(?:[-a-z0-9.]+\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d+)?$/i;
+
+if (typeof gEmailWizardLogger == "undefined") {
+ Cu.import("resource:///modules/gloda/log4moz.js");
+ var gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
+}
+
+var gStringsBundle;
+var gMessengerBundle;
+var gBrandShortName;
+
+/*********************
+TODO for bug 549045
+- autodetect protocol
+Polish
+- reformat code style to match
+<https://developer.mozilla.org/En/Mozilla_Coding_Style_Guide#Control_Structures>
+- bold status
+- remove status when user edited in manual edit
+- add and adapt test from bug 534588
+Bugs
+- SSL cert errors
+ - invalid cert (hostname mismatch) doesn't trigger warning dialog as it should
+ - accept self-signed cert (e.g. imap.mail.ru) doesn't work
+ (works without my patch),
+ verifyConfig.js line 124 has no inServer, for whatever reason,
+ although I didn't change verifyConfig.js at all
+ (the change you see in that file is irrelevant: that was an attempt to fix
+ the bug and clean up the code).
+- Set radio IMAP vs. POP3, see TODO in code
+Things to test (works for me):
+- state transitions, buttons enable, status msgs
+ - stop button
+ - showes up again after stopping detection and restarting it
+ - when stopping [retest]: buttons proper?
+ - enter nonsense domain. guess fails, (so automatically) manual,
+ change domain to real one (not in DB), guess succeeds.
+ former bug: goes to manual first shortly, then to result
+**********************/
+
+// To debug, set mail.wizard.logging.dump (or .console)="All"
+
+function e(elementID)
+{
+ return document.getElementById(elementID);
+};
+
+function _hide(id)
+{
+ e(id).hidden = true;
+}
+
+function _show(id)
+{
+ e(id).hidden = false;
+}
+
+function _enable(id)
+{
+ e(id).disabled = false;
+}
+
+function _disable(id)
+{
+ e(id).disabled = true;
+}
+
+function setText(id, value)
+{
+ var element = e(id);
+ assert(element, "setText() on non-existant element ID");
+
+ if (element.localName == "textbox" || element.localName == "label") {
+ element.value = value;
+ } else if (element.localName == "description") {
+ element.textContent = value;
+ } else {
+ throw new NotReached("XUL element type not supported");
+ }
+}
+
+function setLabelFromStringBundle(elementID, stringName)
+{
+ e(elementID).label = gMessengerBundle.getString(stringName);
+};
+
+function EmailConfigWizard()
+{
+ this._init();
+}
+EmailConfigWizard.prototype =
+{
+ _init : function EmailConfigWizard__init()
+ {
+ gEmailWizardLogger.info("Initializing setup wizard");
+ this._abortable = null;
+ },
+
+ onLoad : function()
+ {
+ /**
+ * this._currentConfig is the config we got either from the XML file or
+ * from guessing or from the user. Unless it's from the user, it contains
+ * placeholders like %EMAILLOCALPART% in username and other fields.
+ *
+ * The config here must retain these placeholders, to be able to
+ * adapt when the user enters a different realname, or password or
+ * email local part. (A change of the domain name will trigger a new
+ * detection anyways.)
+ * That means, before you actually use the config (e.g. to create an
+ * account or to show it to the user), you need to run replaceVariables().
+ */
+ this._currentConfig = null;
+
+ let userFullname;
+ try {
+ let userInfo = Cc["@mozilla.org/userinfo;1"].getService(Ci.nsIUserInfo);
+ userFullname = userInfo.fullname;
+ } catch(e) {
+ // nsIUserInfo may not be implemented on all platforms, and name might
+ // not be avaialble even if it is.
+ }
+
+ this._domain = "";
+ this._email = "";
+ this._realname = (userFullname) ? userFullname : "";
+ e("realname").value = this._realname;
+ this._password = "";
+ this._okCallback = null;
+
+ if (window.arguments && window.arguments[0]) {
+ if (window.arguments[0].msgWindow) {
+ this._parentMsgWindow = window.arguments[0].msgWindow;
+ }
+ if (window.arguments[0].okCallback) {
+ this._okCallback = window.arguments[0].okCallback;
+ }
+ }
+
+ gEmailWizardLogger.info("Email account setup dialog loaded.");
+
+ gStringsBundle = e("strings");
+ gMessengerBundle = e("bundle_messenger");
+ gBrandShortName = e("bundle_brand").getString("brandShortName");
+
+ setLabelFromStringBundle("in-authMethod-password-cleartext",
+ "authPasswordCleartextViaSSL"); // will warn about insecure later
+ setLabelFromStringBundle("in-authMethod-password-encrypted",
+ "authPasswordEncrypted");
+ setLabelFromStringBundle("in-authMethod-kerberos", "authKerberos");
+ setLabelFromStringBundle("in-authMethod-ntlm", "authNTLM");
+#ifdef MOZ_MAILNEWS_OAUTH2
+ setLabelFromStringBundle("in-authMethod-oauth2", "authOAuth2");
+#endif
+ setLabelFromStringBundle("out-authMethod-no", "authNo");
+ setLabelFromStringBundle("out-authMethod-password-cleartext",
+ "authPasswordCleartextViaSSL"); // will warn about insecure later
+ setLabelFromStringBundle("out-authMethod-password-encrypted",
+ "authPasswordEncrypted");
+ setLabelFromStringBundle("out-authMethod-kerberos", "authKerberos");
+ setLabelFromStringBundle("out-authMethod-ntlm", "authNTLM");
+#ifdef MOZ_MAILNEWS_OAUTH2
+ setLabelFromStringBundle("out-authMethod-oauth2", "authOAuth2");
+#endif
+
+ e("incoming_port").value = gStringsBundle.getString("port_auto");
+ this.fillPortDropdown("smtp");
+
+ // If the account provisioner is preffed off, don't display
+ // the account provisioner button.
+ if (!Services.prefs.getBoolPref("mail.provider.enabled"))
+ _hide("provisioner_button");
+
+ // Populate SMTP server dropdown with already configured SMTP servers from
+ // other accounts.
+ var menulist = e("outgoing_hostname");
+ let smtpServers = MailServices.smtp.servers;
+ while (smtpServers.hasMoreElements()) {
+ let server = smtpServers.getNext().QueryInterface(Ci.nsISmtpServer);
+ let label = server.displayname;
+ let key = server.key;
+ if (MailServices.smtp.defaultServer &&
+ MailServices.smtp.defaultServer.key == key) {
+ label += " " + gStringsBundle.getString("default_server_tag");
+ }
+ let menuitem = menulist.appendItem(label, key, ""); // label,value,descr
+ menuitem.serverKey = key;
+ }
+ // Add the entry for the new host to the menulist
+ let menuitem = menulist.insertItemAt(0, "", "-new-"); // pos,label,value
+ menuitem.serverKey = null;
+
+ // admin-locked prefs hurray
+ if (!Services.prefs.getBoolPref("signon.rememberSignons")) {
+ let rememberPasswordE = e("remember_password");
+ rememberPasswordE.checked = false;
+ rememberPasswordE.disabled = true;
+ }
+
+ // First, unhide the main window areas, and store the width,
+ // so that we don't resize wildly when we unhide areas.
+ // switchToMode() will then hide the unneeded parts again.
+ // We will add some leeway of 10px, in case some of the <description>s wrap,
+ // e.g. outgoing username != incoming username.
+ _show("status_area");
+ _show("result_area");
+ _hide("manual-edit_area");
+ window.sizeToContent();
+ e("mastervbox").setAttribute("style",
+ "min-width: " + document.width + "px; " +
+ "min-height: " + (document.height + 10) + "px;");
+
+ this.switchToMode("start");
+ e("realname").select();
+ },
+
+ /**
+ * Changes the window configuration to the different modes we have.
+ * Shows/hides various window parts and buttons.
+ * @param modename {String-enum}
+ * "start" : Just the realname, email address, password fields
+ * "find-config" : detection step, adds the progress message/spinner
+ * "result" : We found a config and display it to the user.
+ * The user may create the account.
+ * "manual-edit" : The user wants (or needs) to manually enter their
+ * the server hostname and other settings. We'll use them as provided.
+ * Additionally, there are the following sub-modes which can be entered after
+ * you entered the main mode:
+ * "manual-edit-have-hostname" : user entered a hostname for both servers
+ * that we can use
+ * "manual-edit-testing" : User pressed the [Re-test] button and
+ * we're currently detecting the "Auto" values
+ * "manual-edit-complete" : user entered (or we tested) all necessary
+ * values, and we're ready to create to account
+ * Currently, this doesn't cover the warning dialogs etc.. It may later.
+ */
+ switchToMode : function(modename)
+ {
+ if (modename == this._currentModename) {
+ return;
+ }
+ this._currentModename = modename;
+ gEmailWizardLogger.info("switching to UI mode " + modename)
+
+ //_show("initialSettings"); always visible
+ //_show("cancel_button"); always visible
+ if (modename == "start") {
+ _hide("status_area");
+ _hide("result_area");
+ _hide("manual-edit_area");
+
+ _show("next_button");
+ _disable("next_button"); // will be enabled by code
+ _hide("half-manual-test_button");
+ _hide("create_button");
+ _hide("stop_button");
+ _hide("manual-edit_button");
+ _hide("advanced-setup_button");
+ } else if (modename == "find-config") {
+ _show("status_area");
+ _hide("result_area");
+ _hide("manual-edit_area");
+
+ _show("next_button");
+ _disable("next_button");
+ _hide("half-manual-test_button");
+ _hide("create_button");
+ _show("stop_button");
+ this.onStop = this.onStopFindConfig;
+ _show("manual-edit_button");
+ _hide("advanced-setup_button");
+ } else if (modename == "result") {
+ _show("status_area");
+ _show("result_area");
+ _hide("manual-edit_area");
+
+ _hide("next_button");
+ _hide("half-manual-test_button");
+ _show("create_button");
+ _enable("create_button");
+ _hide("stop_button");
+ _show("manual-edit_button");
+ _hide("advanced-setup_button");
+ } else if (modename == "manual-edit") {
+ _show("status_area");
+ _hide("result_area");
+ _show("manual-edit_area");
+
+ _hide("next_button");
+ _show("half-manual-test_button");
+ _disable("half-manual-test_button");
+ _show("create_button");
+ _disable("create_button");
+ _hide("stop_button");
+ _hide("manual-edit_button");
+ _show("advanced-setup_button");
+ _disable("advanced-setup_button");
+ } else if (modename == "manual-edit-have-hostname") {
+ _show("status_area");
+ _hide("result_area");
+ _show("manual-edit_area");
+ _hide("manual-edit_button");
+ _hide("next_button");
+ _show("create_button");
+
+ _show("half-manual-test_button");
+ _enable("half-manual-test_button");
+ _disable("create_button");
+ _hide("stop_button");
+ _show("advanced-setup_button");
+ _disable("advanced-setup_button");
+ } else if (modename == "manual-edit-testing") {
+ _show("status_area");
+ _hide("result_area");
+ _show("manual-edit_area");
+ _hide("manual-edit_button");
+ _hide("next_button");
+ _show("create_button");
+
+ _show("half-manual-test_button");
+ _disable("half-manual-test_button");
+ _disable("create_button");
+ _show("stop_button");
+ this.onStop = this.onStopHalfManualTesting;
+ _show("advanced-setup_button");
+ _disable("advanced-setup_button");
+ } else if (modename == "manual-edit-complete") {
+ _show("status_area");
+ _hide("result_area");
+ _show("manual-edit_area");
+ _hide("manual-edit_button");
+ _hide("next_button");
+ _show("create_button");
+
+ _show("half-manual-test_button");
+ _enable("half-manual-test_button");
+ _enable("create_button");
+ _hide("stop_button");
+ _show("advanced-setup_button");
+ _enable("advanced-setup_button");
+ } else {
+ throw new NotReached("unknown mode");
+ }
+ // If we're offline, we're going to disable the create button, but enable
+ // the advanced config button if we have a current config.
+ if (Services.io.offline) {
+ if (this._currentConfig != null) {
+ _show("advanced-setup_button");
+ _enable("advanced-setup_button");
+ _hide("half-manual-test_button");
+ _hide("create_button");
+ _hide("manual-edit_button");
+ }
+ }
+ window.sizeToContent();
+ },
+
+ /**
+ * Start from beginning with possibly new email address.
+ */
+ onStartOver : function() {
+ this._currentConfig = null;
+ if (this._abortable) {
+ this.onStop();
+ }
+ this.switchToMode("start");
+ },
+
+ getConcreteConfig : function()
+ {
+ var result = this._currentConfig.copy();
+ replaceVariables(result, this._realname, this._email, this._password);
+ result.rememberPassword = e("remember_password").checked &&
+ !!this._password;
+ return result;
+ },
+
+ /*
+ * This checks if the email address is at least possibly valid, meaning it
+ * has an '@' before the last char.
+ */
+ validateEmailMinimally : function(emailAddr)
+ {
+ let atPos = emailAddr.lastIndexOf("@");
+ return atPos > 0 && atPos + 1 < emailAddr.length;
+ },
+
+ /*
+ * This checks if the email address is syntactically valid,
+ * as far as we can determine. We try hard to make full checks.
+ *
+ * OTOH, we have a very small chance of false negatives,
+ * because the RFC822 address spec is insanely complicated,
+ * but rarely needed, so when this here fails, we show an error message,
+ * but don't stop the user from continuing.
+ * In contrast, if validateEmailMinimally() fails, we stop the user.
+ */
+ validateEmail : function(emailAddr)
+ {
+ return emailRE.test(emailAddr);
+ },
+
+ /**
+ * onInputEmail and onInputRealname are called on input = keypresses, and
+ * enable/disable the next button based on whether there's a semi-proper
+ * e-mail address and non-blank realname to start with.
+ *
+ * A change to the email address also automatically restarts the
+ * whole process.
+ */
+ onInputEmail : function()
+ {
+ this._email = e("email").value;
+ this.onStartOver();
+ this.checkStartDone();
+ },
+ onInputRealname : function()
+ {
+ this._realname = e("realname").value;
+ this.checkStartDone();
+ },
+
+ onInputPassword : function()
+ {
+ this._password = e("password").value;
+ },
+
+ /**
+ * This does very little other than to check that a name was entered at all
+ * Since this is such an insignificant test we should be using a very light
+ * or even jovial warning.
+ */
+ onBlurRealname : function()
+ {
+ let realnameEl = e("realname");
+ if (this._realname) {
+ this.clearError("nameerror");
+ _show("nametext");
+ realnameEl.removeAttribute("error");
+ // bug 638790: don't show realname error until user enter an email address
+ } else if (this.validateEmailMinimally(this._email)) {
+ _hide("nametext");
+ this.setError("nameerror", "please_enter_name");
+ realnameEl.setAttribute("error", "true");
+ }
+ },
+
+ /**
+ * This check is only done as an informative warning.
+ * We don't want to block the person, if they've entered an email address
+ * that doesn't conform to our regex.
+ */
+ onBlurEmail : function()
+ {
+ if (!this._email) {
+ return;
+ }
+ var emailEl = e("email");
+ if (this.validateEmail(this._email)) {
+ this.clearError("emailerror");
+ emailEl.removeAttribute("error");
+ this.onBlurRealname();
+ } else {
+ this.setError("emailerror", "double_check_email");
+ emailEl.setAttribute("error", "true");
+ }
+ },
+
+ /**
+ * If the user just tabbed through the password input without entering
+ * anything, set the type back to text so we don't wind up showing the
+ * emptytext as bullet characters.
+ */
+ onBlurPassword : function()
+ {
+ if (!this._password) {
+ e("password").type = "text";
+ }
+ },
+
+ /**
+ * @see onBlurPassword()
+ */
+ onFocusPassword : function()
+ {
+ e("password").type = "password";
+ },
+
+ /**
+ * Check whether the user entered the minimum of information
+ * needed to leave the "start" mode (entering of name, email, pw)
+ * and is allowed to proceed to detection step.
+ */
+ checkStartDone : function()
+ {
+ if (this.validateEmailMinimally(this._email) &&
+ this._realname) {
+ this._domain = this._email.split("@")[1].toLowerCase();
+ _enable("next_button");
+ } else {
+ _disable("next_button");
+ }
+ },
+
+ /**
+ * When the [Continue] button is clicked, we move from the initial account
+ * information stage to using that information to configure account details.
+ */
+ onNext : function()
+ {
+ this.findConfig(this._domain, this._email);
+ },
+
+
+ /////////////////////////////////////////////////////////////////
+ // Detection step
+
+ /**
+ * Try to find an account configuration for this email address.
+ * This is the function which runs the autoconfig.
+ */
+ findConfig : function(domain, email)
+ {
+ gEmailWizardLogger.info("findConfig()");
+ if (this._abortable) {
+ this.onStop();
+ }
+ this.switchToMode("find-config");
+ this.startSpinner("looking_up_settings_disk");
+ var self = this;
+ this._abortable = fetchConfigFromDisk(domain,
+ function(config) // success
+ {
+ self._abortable = null;
+ self.foundConfig(config);
+ self.stopSpinner("found_settings_disk");
+ },
+ function(e) // fetchConfigFromDisk failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ gEmailWizardLogger.info("fetchConfigFromDisk failed: " + e);
+ self.startSpinner("looking_up_settings_isp");
+ self._abortable = fetchConfigFromISP(domain, email,
+ function(config) // success
+ {
+ self._abortable = null;
+ self.foundConfig(config);
+ self.stopSpinner("found_settings_isp");
+ },
+ function(e) // fetchConfigFromISP failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ gEmailWizardLogger.info("fetchConfigFromISP failed: " + e);
+ logException(e);
+ self.startSpinner("looking_up_settings_db");
+ self._abortable = fetchConfigFromDB(domain,
+ function(config) // success
+ {
+ self._abortable = null;
+ self.foundConfig(config);
+ self.stopSpinner("found_settings_db");
+ },
+ function(e) // fetchConfigFromDB failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ logException(e);
+ gEmailWizardLogger.info("fetchConfigFromDB failed: " + e);
+ self.startSpinner("looking_up_settings_db");
+ self._abortable = fetchConfigForMX(domain,
+ function(config) // success
+ {
+ self._abortable = null;
+ self.foundConfig(config);
+ self.stopSpinner("found_settings_db");
+ },
+ function(e) // fetchConfigForMX failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ logException(e);
+ gEmailWizardLogger.info("fetchConfigForMX failed: " + e);
+ var initialConfig = new AccountConfig();
+ self._prefillConfig(initialConfig);
+ self._guessConfig(domain, initialConfig);
+ });
+ });
+ });
+ });
+ },
+
+ /**
+ * Just a continuation of findConfig()
+ */
+ _guessConfig : function(domain, initialConfig)
+ {
+ this.startSpinner("looking_up_settings_guess")
+ var self = this;
+ self._abortable = guessConfig(domain,
+ function(type, hostname, port, ssl, done, config) // progress
+ {
+ var msg = hostname + ":" + port + " ssl=" + ssl + " " +
+ type + ": progress callback";
+ gEmailWizardLogger.info(msg);
+ },
+ function(config) // success
+ {
+ self._abortable = null;
+ self.foundConfig(config);
+ self.stopSpinner(Services.io.offline ?
+ "guessed_settings_offline" : "found_settings_guess");
+ window.sizeToContent();
+ },
+ function(e, config) // guessconfig failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ self._abortable = null;
+ gEmailWizardLogger.info("guessConfig failed: " + e);
+ self.showErrorStatus("failed_to_find_settings");
+ self.editConfigDetails();
+ },
+ initialConfig, "both");
+ },
+
+ /**
+ * When findConfig() was successful, it calls this.
+ * This displays the config to the user.
+ */
+ foundConfig : function(config)
+ {
+ gEmailWizardLogger.info("foundConfig()");
+ assert(config instanceof AccountConfig,
+ "BUG: Arg 'config' needs to be an AccountConfig object");
+
+ this._haveValidConfigForDomain = this._email.split("@")[1];
+
+ if (!this._realname || !this._email) {
+ return;
+ }
+ this._foundConfig2(config);
+ },
+
+ // Continuation of foundConfig2() after custom fields.
+ _foundConfig2 : function(config)
+ {
+ this.displayConfigResult(config);
+ },
+
+ /**
+ * [Stop] button click handler.
+ * This allows the user to abort any longer operation, esp. network activity.
+ * We currently have 3 such cases here:
+ * 1. findConfig(), i.e. fetch config from DB, guessConfig etc.
+ * 2. onHalfManualTest(), i.e. the [Retest] button in manual config.
+ * 3. verifyConfig() - We can't stop this yet, so irrelevant here currently.
+ * Given that these need slightly different actions, this function will be set
+ * to a function (i.e. overwritten) by whoever enables the stop button.
+ *
+ * We also call this from the code when the user started a different action
+ * without explicitly clicking [Stop] for the old one first.
+ */
+ onStop : function()
+ {
+ throw new NotReached("onStop should be overridden by now");
+ },
+ _onStopCommon : function()
+ {
+ if (!this._abortable) {
+ throw new NotReached("onStop called although there's nothing to stop");
+ }
+ gEmailWizardLogger.info("onStop cancelled _abortable");
+ this._abortable.cancel(new UserCancelledException());
+ this._abortable = null;
+ this.stopSpinner();
+ },
+ onStopFindConfig : function()
+ {
+ this._onStopCommon();
+ this.switchToMode("start");
+ this.checkStartDone();
+ },
+ onStopHalfManualTesting : function()
+ {
+ this._onStopCommon();
+ this.validateManualEditComplete();
+ },
+
+
+
+ ///////////////////////////////////////////////////////////////////
+ // status area
+
+ startSpinner : function(actionStrName)
+ {
+ e("status_area").setAttribute("status", "loading");
+ gEmailWizardLogger.warn("spinner start " + actionStrName);
+ this._showStatusTitle(actionStrName);
+ },
+
+ stopSpinner : function(actionStrName)
+ {
+ e("status_area").setAttribute("status", "result");
+ _hide("stop_button");
+ this._showStatusTitle(actionStrName);
+ gEmailWizardLogger.warn("all spinner stop " + actionStrName);
+ },
+
+ showErrorStatus : function(actionStrName)
+ {
+ e("status_area").setAttribute("status", "error");
+ gEmailWizardLogger.warn("status error " + actionStrName);
+ this._showStatusTitle(actionStrName);
+ },
+
+ _showStatusTitle : function(msgName)
+ {
+ let msg = " "; // assure height. Do via min-height in CSS, for 2 lines?
+ try {
+ if (msgName) {
+ msg = gStringsBundle.getFormattedString(msgName, [gBrandShortName]);
+ }
+ } catch(ex) {
+ gEmailWizardLogger.error("missing string for " + msgName);
+ msg = msgName + " (missing string in translation!)";
+ }
+
+ e("status_msg").textContent = msg;
+ gEmailWizardLogger.info("status msg: " + msg);
+ },
+
+
+
+ /////////////////////////////////////////////////////////////////
+ // Result area
+
+ /**
+ * Displays a (probed) config to the user,
+ * in the result config details area.
+ *
+ * @param config {AccountConfig} The config to present to user
+ */
+ displayConfigResult : function(config)
+ {
+ assert(config instanceof AccountConfig);
+ this._currentConfig = config;
+ var configFilledIn = this.getConcreteConfig();
+
+ var unknownString = gStringsBundle.getString("resultUnknown");
+
+ function _makeHostDisplayString(server, stringName)
+ {
+ let type = gStringsBundle.getString(sanitize.translate(server.type,
+ { imap : "resultIMAP", pop3 : "resultPOP3", smtp : "resultSMTP" }),
+ unknownString);
+ let host = server.hostname +
+ (isStandardPort(server.port) ? "" : ":" + server.port);
+ let ssl = gStringsBundle.getString(sanitize.translate(server.socketType,
+ { 1 : "resultNoEncryption", 2 : "resultSSL", 3 : "resultSTARTTLS" }),
+ unknownString);
+ let certStatus = gStringsBundle.getString(server.badCert ?
+ "resultSSLCertWeak" : "resultSSLCertOK");
+ // TODO: we should really also display authentication method here.
+ return gStringsBundle.getFormattedString(stringName,
+ [ type, host, ssl, certStatus ]);
+ };
+
+ var incomingResult = unknownString;
+ if (configFilledIn.incoming.hostname) {
+ incomingResult = _makeHostDisplayString(configFilledIn.incoming,
+ "resultIncoming");
+ }
+
+ var outgoingResult = unknownString;
+ if (!config.outgoing.existingServerKey) {
+ if (configFilledIn.outgoing.hostname) {
+ outgoingResult = _makeHostDisplayString(configFilledIn.outgoing,
+ "resultOutgoing");
+ }
+ } else {
+ outgoingResult = gStringsBundle.getString("resultOutgoingExisting");
+ }
+
+ var usernameResult;
+ if (configFilledIn.incoming.username == configFilledIn.outgoing.username) {
+ usernameResult = gStringsBundle.getFormattedString("resultUsernameBoth",
+ [ configFilledIn.incoming.username || unknownString ]);
+ } else {
+ usernameResult = gStringsBundle.getFormattedString(
+ "resultUsernameDifferent",
+ [ configFilledIn.incoming.username || unknownString,
+ configFilledIn.outgoing.username || unknownString ]);
+ }
+
+ setText("result-incoming", incomingResult);
+ setText("result-outgoing", outgoingResult);
+ setText("result-username", usernameResult);
+
+ gEmailWizardLogger.info(debugObject(config, "config"));
+ // IMAP / POP dropdown
+ var lookForAltType =
+ config.incoming.type == "imap" ? "pop3" : "imap";
+ var alternative = null;
+ for (let i = 0; i < config.incomingAlternatives.length; i++) {
+ let alt = config.incomingAlternatives[i];
+ if (alt.type == lookForAltType) {
+ alternative = alt;
+ break;
+ }
+ }
+ if (alternative) {
+ _show("result_imappop");
+ e("result_select_" + alternative.type).configIncoming = alternative;
+ e("result_select_" + config.incoming.type).configIncoming =
+ config.incoming;
+ e("result_imappop").value =
+ config.incoming.type == "imap" ? 1 : 2;
+ } else {
+ _hide("result_imappop");
+ }
+
+ this.switchToMode("result");
+ },
+
+ /**
+ * Handle the user switching between IMAP and POP3 settings using the
+ * radio buttons.
+ *
+ * Note: This function must only be called by user action, not by setting
+ * the value or selectedItem or selectedIndex of the radiogroup!
+ * This is why we use the oncommand attribute of the radio elements
+ * instead of the onselect attribute of the radiogroup.
+ */
+ onResultIMAPOrPOP3 : function()
+ {
+ var config = this._currentConfig;
+ var radiogroup = e("result_imappop");
+ // add current server as best alternative to start of array
+ config.incomingAlternatives.unshift(config.incoming);
+ // use selected server (stored as special property on the <radio> node)
+ config.incoming = radiogroup.selectedItem.configIncoming;
+ // remove newly selected server from list of alternatives
+ config.incomingAlternatives = config.incomingAlternatives.filter(
+ function(e) { return e != config.incoming; });
+ this.displayConfigResult(config);
+ },
+
+
+
+ /////////////////////////////////////////////////////////////////
+ // Manual Edit area
+
+ /**
+ * Gets the values from the user in the manual edit area.
+ *
+ * Realname and password are not part of that area and still
+ * placeholders, but hostname and username are concrete and
+ * no placeholders anymore.
+ */
+ getUserConfig : function()
+ {
+ var config = this.getConcreteConfig();
+ if (!config) {
+ config = new AccountConfig();
+ }
+ config.source = AccountConfig.kSourceUser;
+
+ // Incoming server
+ try {
+ var inHostnameField = e("incoming_hostname");
+ config.incoming.hostname = sanitize.hostname(inHostnameField.value);
+ inHostnameField.value = config.incoming.hostname;
+ } catch (e) { gEmailWizardLogger.warn(e); }
+ try {
+ config.incoming.port = sanitize.integerRange(e("incoming_port").value,
+ kMinPort, kMaxPort);
+ } catch (e) {
+ config.incoming.port = undefined; // incl. default "Auto"
+ }
+ config.incoming.type = sanitize.translate(e("incoming_protocol").value,
+ { 1: "imap", 2 : "pop3", 0 : null });
+ config.incoming.socketType = sanitize.integer(e("incoming_ssl").value);
+ config.incoming.auth = sanitize.integer(e("incoming_authMethod").value);
+ config.incoming.username = e("incoming_username").value;
+
+ // Outgoing server
+
+ // Did the user select one of the already configured SMTP servers from the
+ // drop-down list? If so, use it.
+ var outHostnameCombo = e("outgoing_hostname");
+ var outMenuitem = outHostnameCombo.selectedItem;
+ if (outMenuitem && outMenuitem.serverKey) {
+ config.outgoing.existingServerKey = outMenuitem.serverKey;
+ config.outgoing.existingServerLabel = outMenuitem.label;
+ config.outgoing.addThisServer = false;
+ config.outgoing.useGlobalPreferredServer = false;
+ } else {
+ config.outgoing.existingServerKey = null;
+ config.outgoing.addThisServer = true;
+ config.outgoing.useGlobalPreferredServer = false;
+
+ try {
+ config.outgoing.hostname = sanitize.hostname(
+ outHostnameCombo.inputField.value);
+ outHostnameCombo.inputField.value = config.outgoing.hostname;
+ } catch (e) { gEmailWizardLogger.warn(e); }
+ try {
+ config.outgoing.port = sanitize.integerRange(e("outgoing_port").value,
+ kMinPort, kMaxPort);
+ } catch (e) {
+ config.outgoing.port = undefined; // incl. default "Auto"
+ }
+ config.outgoing.socketType = sanitize.integer(e("outgoing_ssl").value);
+ config.outgoing.auth = sanitize.integer(e("outgoing_authMethod").value);
+ }
+ config.outgoing.username = e("outgoing_username").value;
+
+ return config;
+ },
+
+ /**
+ * [Manual Config] button click handler. This turns the config details area
+ * into an editable form and makes the (Go) button appear. The edit button
+ * should only be available after the config probing is completely finished,
+ * replacing what was the (Stop) button.
+ */
+ onManualEdit : function()
+ {
+ if (this._abortable) {
+ this.onStop();
+ }
+ this.editConfigDetails();
+ },
+
+ /**
+ * Setting the config details form so it can be edited. We also disable
+ * (and hide) the create button during this time because we don't know what
+ * might have changed. The function called from the button that restarts
+ * the config check should be enabling the config button as needed.
+ */
+ editConfigDetails : function()
+ {
+ gEmailWizardLogger.info("manual edit");
+
+ if (!this._currentConfig) {
+ this._currentConfig = new AccountConfig();
+ this._currentConfig.incoming.type = "imap";
+ this._currentConfig.incoming.username = "%EMAILLOCALPART%";
+ this._currentConfig.outgoing.username = "%EMAILLOCALPART%";
+ this._currentConfig.incoming.hostname = ".%EMAILDOMAIN%";
+ this._currentConfig.outgoing.hostname = ".%EMAILDOMAIN%";
+ }
+ // Although we go manual, and we need to display the concrete username,
+ // however the realname and password is not part of manual config and
+ // must stay a placeholder in _currentConfig. @see getUserConfig()
+
+ this._fillManualEditFields(this.getConcreteConfig());
+
+ // _fillManualEditFields() indirectly calls validateManualEditComplete(),
+ // but it's important to not forget it in case the code is rewritten,
+ // so calling it explicitly again. Doesn't do harm, speed is irrelevant.
+ this.validateManualEditComplete();
+ },
+
+ /**
+ * Fills the manual edit textfields with the provided config.
+ * @param config {AccountConfig} The config to present to user
+ */
+ _fillManualEditFields : function(config)
+ {
+ assert(config instanceof AccountConfig);
+
+ // incoming server
+ e("incoming_protocol").value = sanitize.translate(config.incoming.type,
+ { "imap" : 1, "pop3" : 2 }, 1);
+ e("incoming_hostname").value = config.incoming.hostname;
+ e("incoming_ssl").value = sanitize.enum(config.incoming.socketType,
+ [ 0, 1, 2, 3 ], 0);
+ e("incoming_authMethod").value = sanitize.enum(config.incoming.auth,
+#ifdef MOZ_MAILNEWS_OAUTH2
+ [ 0, 3, 4, 5, 6, 10 ], 0);
+#else
+ [ 0, 3, 4, 5, 6 ], 0);
+#endif
+ e("incoming_username").value = config.incoming.username;
+ if (config.incoming.port) {
+ e("incoming_port").value = config.incoming.port;
+ } else {
+ this.adjustIncomingPortToSSLAndProtocol(config);
+ }
+ this.fillPortDropdown(config.incoming.type);
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ // If the hostname supports OAuth2 and imap is enabled, enable OAuth2.
+ let iDetails = OAuth2Providers.getHostnameDetails(config.incoming.hostname);
+ gEmailWizardLogger.info("OAuth2 details for incoming hostname " +
+ config.incoming.hostname + " is " + iDetails);
+ e("in-authMethod-oauth2").hidden = !(iDetails && e("incoming_protocol").value == 1);
+ if (!e("in-authMethod-oauth2").hidden) {
+ config.oauthSettings = {};
+ [config.oauthSettings.issuer, config.oauthSettings.scope] = iDetails;
+ // oauthsettings are not stored nor changable in the user interface, so just
+ // store them in the base configuration.
+ this._currentConfig.oauthSettings = config.oauthSettings;
+ }
+#endif
+
+ // outgoing server
+ e("outgoing_hostname").value = config.outgoing.hostname;
+ e("outgoing_username").value = config.outgoing.username;
+ // While sameInOutUsernames is true we synchronize values of incoming
+ // and outgoing username.
+ this.sameInOutUsernames = true;
+ e("outgoing_ssl").value = sanitize.enum(config.outgoing.socketType,
+ [ 0, 1, 2, 3 ], 0);
+ e("outgoing_authMethod").value = sanitize.enum(config.outgoing.auth,
+ [ 0, 1, 3, 4, 5, 6, 10 ], 0);
+ if (config.outgoing.port) {
+ e("outgoing_port").value = config.outgoing.port;
+ } else {
+ this.adjustOutgoingPortToSSLAndProtocol(config);
+ }
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ // If the hostname supports OAuth2 and imap is enabled, enable OAuth2.
+ let oDetails = OAuth2Providers.getHostnameDetails(config.outgoing.hostname);
+ gEmailWizardLogger.info("OAuth2 details for outgoing hostname " +
+ config.outgoing.hostname + " is " + oDetails);
+ e("out-authMethod-oauth2").hidden = !oDetails;
+ if (!e("out-authMethod-oauth2").hidden) {
+ config.oauthSettings = {};
+ [config.oauthSettings.issuer, config.oauthSettings.scope] = oDetails;
+ // oauthsettings are not stored nor changable in the user interface, so just
+ // store them in the base configuration.
+ this._currentConfig.oauthSettings = config.oauthSettings;
+ }
+#endif
+
+ // populate fields even if existingServerKey, in case user changes back
+ if (config.outgoing.existingServerKey) {
+ let menulist = e("outgoing_hostname");
+ // We can't use menulist.value = config.outgoing.existingServerKey
+ // because would overwrite the text field, so have to do it manually:
+ let menuitems = menulist.menupopup.childNodes;
+ for (let menuitem of menuitems) {
+ if (menuitem.serverKey == config.outgoing.existingServerKey) {
+ menulist.selectedItem = menuitem;
+ break;
+ }
+ }
+ }
+ this.onChangedOutgoingDropdown(); // show/hide outgoing port, SSL, ...
+ },
+
+ /**
+ * Automatically fill port field in manual edit,
+ * unless user entered a non-standard port.
+ * @param config {AccountConfig}
+ */
+ adjustIncomingPortToSSLAndProtocol : function(config)
+ {
+ var autoPort = gStringsBundle.getString("port_auto");
+ var incoming = config.incoming;
+ // we could use getHostEntry() here, but that API is bad, so don't bother
+ var newInPort = undefined;
+ if (!incoming.port || isStandardPort(incoming.port)) {
+ if (incoming.type == "imap") {
+ if (incoming.socketType == 1 || incoming.socketType == 3) {
+ newInPort = 143;
+ } else if (incoming.socketType == 2) { // Normal SSL
+ newInPort = 993;
+ } else { // auto
+ newInPort = autoPort;
+ }
+ } else if (incoming.type == "pop3") {
+ if (incoming.socketType == 1 || incoming.socketType == 3) {
+ newInPort = 110;
+ } else if (incoming.socketType == 2) { // Normal SSLs
+ newInPort = 995;
+ } else { // auto
+ newInPort = autoPort;
+ }
+ }
+ }
+ if (newInPort != undefined) {
+ e("incoming_port").value = newInPort;
+ e("incoming_authMethod").value = 0; // auto
+ }
+ },
+
+ /**
+ * @see adjustIncomingPortToSSLAndProtocol()
+ */
+ adjustOutgoingPortToSSLAndProtocol : function(config)
+ {
+ var autoPort = gStringsBundle.getString("port_auto");
+ var outgoing = config.outgoing;
+ var newOutPort = undefined;
+ if (!outgoing.port || isStandardPort(outgoing.port)) {
+ if (outgoing.socketType == 1 || outgoing.socketType == 3) {
+ // standard port is 587 *or* 25, so set to auto
+ // unless user or config already entered one of these two ports.
+ if (outgoing.port != 25 && outgoing.port != 587) {
+ newOutPort = autoPort;
+ }
+ } else if (outgoing.socketType == 2) { // Normal SSL
+ newOutPort = 465;
+ } else { // auto
+ newOutPort = autoPort;
+ }
+ }
+ if (newOutPort != undefined) {
+ e("outgoing_port").value = newOutPort;
+ e("outgoing_authMethod").value = 0; // auto
+ }
+ },
+
+ /**
+ * If the user changed the port manually, adjust the SSL value,
+ * (only) if the new port is impossible with the old SSL value.
+ * @param config {AccountConfig}
+ */
+ adjustIncomingSSLToPort : function(config)
+ {
+ var incoming = config.incoming;
+ var newInSocketType = undefined;
+ if (!incoming.port || // auto
+ !isStandardPort(incoming.port)) {
+ return;
+ }
+ if (incoming.type == "imap") {
+ // normal SSL impossible
+ if (incoming.port == 143 && incoming.socketType == 2) {
+ newInSocketType = 0; // auto
+ // must be normal SSL
+ } else if (incoming.port == 993 && incoming.socketType != 2) {
+ newInSocketType = 2;
+ }
+ } else if (incoming.type == "pop3") {
+ // normal SSL impossible
+ if (incoming.port == 110 && incoming.socketType == 2) {
+ newInSocketType = 0; // auto
+ // must be normal SSL
+ } else if (incoming.port == 995 && incoming.socketType != 2) {
+ newInSocketType = 2;
+ }
+ }
+ if (newInSocketType != undefined) {
+ e("incoming_ssl").value = newInSocketType;
+ e("incoming_authMethod").value = 0; // auto
+ }
+ },
+
+ /**
+ * @see adjustIncomingSSLToPort()
+ */
+ adjustOutgoingSSLToPort : function(config)
+ {
+ var outgoing = config.outgoing;
+ var newOutSocketType = undefined;
+ if (!outgoing.port || // auto
+ !isStandardPort(outgoing.port)) {
+ return;
+ }
+ // normal SSL impossible
+ if ((outgoing.port == 587 || outgoing.port == 25) &&
+ outgoing.socketType == 2) {
+ newOutSocketType = 0; // auto
+ // must be normal SSL
+ } else if (outgoing.port == 465 && outgoing.socketType != 2) {
+ newOutSocketType = 2;
+ }
+ if (newOutSocketType != undefined) {
+ e("outgoing_ssl").value = newOutSocketType;
+ e("outgoing_authMethod").value = 0; // auto
+ }
+ },
+
+ /**
+ * Sets the prefilled values of the port fields.
+ * Filled statically with the standard ports for the given protocol,
+ * plus "Auto".
+ */
+ fillPortDropdown : function(protocolType)
+ {
+ var menu = e(protocolType == "smtp" ? "outgoing_port" : "incoming_port");
+
+ // menulist.removeAllItems() is nice, but nicely clears the user value, too
+ var popup = menu.menupopup;
+ while (popup.hasChildNodes())
+ popup.lastChild.remove();
+
+ // add standard ports
+ var autoPort = gStringsBundle.getString("port_auto");
+ menu.appendItem(autoPort, autoPort, ""); // label,value,descr
+ for (let port of getStandardPorts(protocolType)) {
+ menu.appendItem(port, port, ""); // label,value,descr
+ }
+ },
+
+ onChangedProtocolIncoming : function()
+ {
+ var config = this.getUserConfig();
+ this.adjustIncomingPortToSSLAndProtocol(config);
+ this.fillPortDropdown(config.incoming.type);
+ this.onChangedManualEdit();
+ },
+ onChangedPortIncoming : function()
+ {
+ gEmailWizardLogger.info("incoming port changed");
+ this.adjustIncomingSSLToPort(this.getUserConfig());
+ this.onChangedManualEdit();
+ },
+ onChangedPortOutgoing : function()
+ {
+ gEmailWizardLogger.info("outgoing port changed");
+ this.adjustOutgoingSSLToPort(this.getUserConfig());
+ this.onChangedManualEdit();
+ },
+ onChangedSSLIncoming : function()
+ {
+ this.adjustIncomingPortToSSLAndProtocol(this.getUserConfig());
+ this.onChangedManualEdit();
+ },
+ onChangedSSLOutgoing : function()
+ {
+ this.adjustOutgoingPortToSSLAndProtocol(this.getUserConfig());
+ this.onChangedManualEdit();
+ },
+ onChangedInAuth : function()
+ {
+ this.onChangedManualEdit();
+ },
+ onChangedOutAuth : function(aSelectedAuth)
+ {
+ if (aSelectedAuth) {
+ e("outgoing_label").hidden = e("outgoing_username").hidden =
+ (aSelectedAuth.id == "out-authMethod-no");
+ }
+ this.onChangedManualEdit();
+ },
+ onInputInUsername : function()
+ {
+ if (this.sameInOutUsernames)
+ e("outgoing_username").value = e("incoming_username").value;
+ this.onChangedManualEdit();
+ },
+ onInputOutUsername : function()
+ {
+ this.sameInOutUsernames = false;
+ this.onChangedManualEdit();
+ },
+ onInputHostname : function()
+ {
+ this.onChangedManualEdit();
+ },
+
+ /**
+ * Sets the label of the first entry of the dropdown which represents
+ * the new outgoing server.
+ */
+ onOpenOutgoingDropdown : function()
+ {
+ var menulist = e("outgoing_hostname");
+ // If the menulist is not editable, there is nothing to update
+ // and menulist.inputField does not even exist.
+ if (!menulist.editable)
+ return;
+
+ var menuitem = menulist.getItemAtIndex(0);
+ assert(!menuitem.serverKey, "I wanted the special item for the new host");
+ menuitem.label = menulist.inputField.value;
+ },
+
+ /**
+ * User selected an existing SMTP server (or deselected it).
+ * This changes only the UI. The values are read in getUserConfig().
+ */
+ onChangedOutgoingDropdown : function()
+ {
+ var menulist = e("outgoing_hostname");
+ var menuitem = menulist.selectedItem;
+ if (menuitem && menuitem.serverKey) {
+ // an existing server has been selected from the dropdown
+ menulist.editable = false;
+ _hide("outgoing_port");
+ _hide("outgoing_ssl");
+ _hide("outgoing_authMethod");
+ } else {
+ // new server, with hostname, port etc.
+ menulist.editable = true;
+ _show("outgoing_port");
+ _show("outgoing_ssl");
+ _show("outgoing_authMethod");
+ }
+
+ this.onChangedManualEdit();
+ },
+
+ onChangedManualEdit : function()
+ {
+ if (this._abortable) {
+ this.onStop();
+ }
+ this.validateManualEditComplete();
+ },
+
+ /**
+ * This enables the buttons which allow the user to proceed
+ * once he has entered enough information.
+ *
+ * We can easily and fairly surely autodetect everything apart from the
+ * hostname (and username). So, once the user has entered
+ * proper hostnames, change to "manual-edit-have-hostname" mode
+ * which allows to press [Re-test], which starts the detection
+ * of the other values.
+ * Once the user has entered (or we detected) all values, he may
+ * do [Create Account] (tests login and if successful creates the account)
+ * or [Advanced Setup] (goes to Account Manager). Esp. in the latter case,
+ * we will not second-guess his setup and just to as told, so here we make
+ * sure that he at least entered all values.
+ */
+ validateManualEditComplete : function()
+ {
+ // getUserConfig() is expensive, but still OK, not a problem
+ var manualConfig = this.getUserConfig();
+ this._currentConfig = manualConfig;
+ if (manualConfig.isComplete()) {
+ this.switchToMode("manual-edit-complete");
+ } else if (!!manualConfig.incoming.hostname &&
+ !!manualConfig.outgoing.hostname) {
+ this.switchToMode("manual-edit-have-hostname");
+ } else {
+ this.switchToMode("manual-edit");
+ }
+ },
+
+ /**
+ * [Switch to provisioner] button click handler. Always active, allows
+ * one to switch to the account provisioner screen.
+ */
+ onSwitchToProvisioner : function ()
+ {
+ // We have to close this window first, otherwise msgNewMailAccount
+ // in accountUtils.js will think that this window still
+ // exists when it's called from the account provisioner window.
+ // This is because the account provisioner window is modal,
+ // and therefore blocks. Therefore, we override the _okCallback
+ // with a function that spawns the account provisioner, and then
+ // close the window.
+ this._okCallback = function() {
+ NewMailAccountProvisioner(window.arguments[0].msgWindow, window.arguments[0].extraData);
+ }
+ window.close();
+ },
+
+ /**
+ * [Advanced Setup...] button click handler
+ * Only active in manual edit mode, and goes straight into
+ * Account Settings (pref UI) dialog. Requires a backend account,
+ * which requires proper hostname, port and protocol.
+ */
+ onAdvancedSetup : function()
+ {
+ assert(this._currentConfig instanceof AccountConfig);
+ let configFilledIn = this.getConcreteConfig();
+
+ if (checkIncomingServerAlreadyExists(configFilledIn)) {
+ alertPrompt(gStringsBundle.getString("error_creating_account"),
+ gStringsBundle.getString("incoming_server_exists"));
+ return;
+ }
+
+ gEmailWizardLogger.info("creating account in backend");
+ let newAccount = createAccountInBackend(configFilledIn);
+
+ let existingAccountManager = Services.wm
+ .getMostRecentWindow("mailnews:accountmanager");
+ if (existingAccountManager) {
+ existingAccountManager.focus();
+ } else {
+ window.openDialog("chrome://messenger/content/AccountManager.xul",
+ "AccountManager", "chrome,centerscreen,modal,titlebar",
+ { server: newAccount.incomingServer,
+ selectPage: "am-server.xul" });
+ }
+ window.close();
+ },
+
+ /**
+ * [Re-test] button click handler.
+ * Restarts the config guessing process after a person editing the server
+ * fields.
+ * It's called "half-manual", because we take the user-entered values
+ * as given and will not second-guess them, to respect the user wishes.
+ * (Yes, Sir! Will do as told!)
+ * The values that the user left empty or on "Auto" will be guessed/probed
+ * here. We will also check that the user-provided values work.
+ */
+ onHalfManualTest : function()
+ {
+ var newConfig = this.getUserConfig();
+ gEmailWizardLogger.info(debugObject(newConfig, "manualConfigToTest"));
+ this.startSpinner("looking_up_settings_halfmanual");
+ this.switchToMode("manual-edit-testing");
+ // if (this._userPickedOutgoingServer) TODO
+ var self = this;
+ this._abortable = guessConfig(this._domain,
+ function(type, hostname, port, ssl, done, config) // progress
+ {
+ gEmailWizardLogger.info("progress callback host " + hostname +
+ " port " + port + " type " + type);
+ },
+ function(config) // success
+ {
+ self._abortable = null;
+ self._fillManualEditFields(config);
+ self.switchToMode("manual-edit-complete");
+ self.stopSpinner("found_settings_halfmanual");
+ },
+ function(e, config) // guessconfig failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ self._abortable = null;
+ gEmailWizardLogger.info("guessConfig failed: " + e);
+ self.showErrorStatus("failed_to_find_settings");
+ self.switchToMode("manual-edit-have-hostname");
+ },
+ newConfig,
+ newConfig.outgoing.existingServerKey ? "incoming" : "both");
+ },
+
+
+
+ /////////////////////////////////////////////////////////////////
+ // UI helper functions
+
+ _prefillConfig : function(initialConfig)
+ {
+ var emailsplit = this._email.split("@");
+ assert(emailsplit.length > 1);
+ var emaillocal = sanitize.nonemptystring(emailsplit[0]);
+ initialConfig.incoming.username = emaillocal;
+ initialConfig.outgoing.username = emaillocal;
+ return initialConfig;
+ },
+
+ clearError : function(which)
+ {
+ _hide(which);
+ _hide(which + "icon");
+ e(which).textContent = "";
+ },
+
+ setError : function(which, msg_name)
+ {
+ try {
+ _show(which);
+ _show(which + "icon");
+ e(which).textContent = gStringsBundle.getString(msg_name);
+ window.sizeToContent();
+ } catch (ex) { alertPrompt("missing error string", msg_name); }
+ },
+
+
+
+ /////////////////////////////////////////////////////////////////
+ // Finish & dialog close functions
+
+ onKeyDown : function(event)
+ {
+ let key = event.keyCode;
+ if (key == 27) { // Escape key
+ this.onCancel();
+ return true;
+ }
+ if (key == 13) { // OK key
+ let buttons = [
+ { id: "next_button", action: makeCallback(this, this.onNext) },
+ { id: "create_button", action: makeCallback(this, this.onCreate) },
+ { id: "half-manual-test_button",
+ action: makeCallback(this, this.onHalfManualTest) },
+ ];
+ for (let button of buttons) {
+ button.e = e(button.id);
+ if (button.e.hidden || button.e.disabled) {
+ continue;
+ }
+ button.action();
+ return true;
+ }
+ }
+ return false;
+ },
+
+ onCancel : function()
+ {
+ window.close();
+ // The window onclose handler will call onWizardShutdown for us.
+ },
+
+ onWizardShutdown : function()
+ {
+ if (this._abortable) {
+ this._abortable.cancel(new UserCancelledException());
+ }
+
+ if (this._okCallback) {
+ this._okCallback();
+ }
+ gEmailWizardLogger.info("Shutting down email config dialog");
+ },
+
+
+ onCreate : function()
+ {
+ try {
+ gEmailWizardLogger.info("Create button clicked");
+
+ var configFilledIn = this.getConcreteConfig();
+ var self = this;
+ // If the dialog is not needed, it will go straight to OK callback
+ gSecurityWarningDialog.open(this._currentConfig, configFilledIn, true,
+ function() // on OK
+ {
+ self.validateAndFinish(configFilledIn);
+ },
+ function() {}); // on cancel, do nothing
+ } catch (ex) {
+ gEmailWizardLogger.error("Error creating account. ex=" + ex +
+ ", stack=" + ex.stack);
+ alertPrompt(gStringsBundle.getString("error_creating_account"), ex);
+ }
+ },
+
+ // called by onCreate()
+ validateAndFinish : function()
+ {
+ var configFilledIn = this.getConcreteConfig();
+
+ if (checkIncomingServerAlreadyExists(configFilledIn)) {
+ alertPrompt(gStringsBundle.getString("error_creating_account"),
+ gStringsBundle.getString("incoming_server_exists"));
+ return;
+ }
+
+ if (configFilledIn.outgoing.addThisServer) {
+ let existingServer = checkOutgoingServerAlreadyExists(configFilledIn);
+ if (existingServer) {
+ configFilledIn.outgoing.addThisServer = false;
+ configFilledIn.outgoing.existingServerKey = existingServer.key;
+ }
+ }
+
+ // TODO use a UI mode (switchToMode()) for verfication, too.
+ // But we need to go back to the previous mode, because we might be in
+ // "result" or "manual-edit-complete" mode.
+ _disable("create_button");
+ _disable("half-manual-test_button");
+ _disable("advanced-setup_button");
+ // no stop button: backend has no ability to stop :-(
+ var self = this;
+ this.startSpinner("checking_password");
+ // logic function defined in verifyConfig.js
+ verifyConfig(
+ configFilledIn,
+ // guess login config?
+ configFilledIn.source != AccountConfig.kSourceXML,
+ // TODO Instead, the following line would be correct, but I cannot use it,
+ // because some other code doesn't adhere to the expectations/specs.
+ // Find out what it was and fix it.
+ //concreteConfig.source == AccountConfig.kSourceGuess,
+ this._parentMsgWindow,
+ function(successfulConfig) // success
+ {
+ self.stopSpinner(successfulConfig.incoming.password ?
+ "password_ok" : null);
+
+ // the auth might have changed, so we
+ // should back-port it to the current config.
+ self._currentConfig.incoming.auth = successfulConfig.incoming.auth;
+ self._currentConfig.outgoing.auth = successfulConfig.outgoing.auth;
+ self._currentConfig.incoming.username = successfulConfig.incoming.username;
+ self._currentConfig.outgoing.username = successfulConfig.outgoing.username;
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ // We loaded dynamic client registration, fill this data back in to the
+ // config set.
+ if (successfulConfig.oauthSettings)
+ self._currentConfig.oauthSettings = successfulConfig.oauthSettings;
+#endif
+
+ self.finish();
+ },
+ function(e) // failed
+ {
+ self.showErrorStatus("config_unverifiable");
+ // TODO bug 555448: wrong error msg, there may be a 1000 other
+ // reasons why this failed, and this is misleading users.
+ self.setError("passworderror", "user_pass_invalid");
+ // TODO use switchToMode(), see above
+ // give user something to proceed after fixing
+ _enable("create_button");
+ // hidden in non-manual mode, so it's fine to enable
+ _enable("half-manual-test_button");
+ _enable("advanced-setup_button");
+ });
+ },
+
+ finish : function()
+ {
+ gEmailWizardLogger.info("creating account in backend");
+ createAccountInBackend(this.getConcreteConfig());
+ window.close();
+ },
+};
+
+var gEmailConfigWizard = new EmailConfigWizard();
+
+function serverMatches(a, b)
+{
+ return a.type == b.type &&
+ a.hostname == b.hostname &&
+ a.port == b.port &&
+ a.socketType == b.socketType &&
+ a.auth == b.auth;
+}
+
+var _gStandardPorts = {};
+_gStandardPorts["imap"] = [ 143, 993 ];
+_gStandardPorts["pop3"] = [ 110, 995 ];
+_gStandardPorts["smtp"] = [ 587, 25, 465 ]; // order matters
+var _gAllStandardPorts = _gStandardPorts["smtp"]
+ .concat(_gStandardPorts["imap"]).concat(_gStandardPorts["pop3"]);
+
+function isStandardPort(port)
+{
+ return _gAllStandardPorts.indexOf(port) != -1;
+}
+
+function getStandardPorts(protocolType)
+{
+ return _gStandardPorts[protocolType];
+}
+
+
+/**
+ * Warning dialog, warning user about lack of, or inappropriate, encryption.
+ *
+ * This is effectively a separate dialog, but implemented as part of
+ * this dialog. It works by hiding the main dialog part and unhiding
+ * the this part, and vice versa, and resizing the dialog.
+ */
+function SecurityWarningDialog()
+{
+ this._acknowledged = new Array();
+}
+SecurityWarningDialog.prototype =
+{
+ /**
+ * {Array of {(incoming or outgoing) server part of {AccountConfig}}
+ * A list of the servers for which we already showed this dialog and the
+ * user approved the configs. For those, we won't show the warning again.
+ * (Make sure to store a copy in case the underlying object is changed.)
+ */
+ _acknowledged : null,
+
+ _inSecurityBad: 0x0001,
+ _inCertBad: 0x0010,
+ _outSecurityBad: 0x0100,
+ _outCertBad: 0x1000,
+
+ /**
+ * Checks whether we need to warn about this config.
+ *
+ * We (currently) warn if
+ * - the mail travels unsecured (no SSL/STARTTLS)
+ * - the SSL certificate is not proper
+ * - (We don't warn about unencrypted passwords specifically,
+ * because they'd be encrypted with SSL and without SSL, we'd
+ * warn anyways.)
+ *
+ * We may not warn despite these conditions if we had shown the
+ * warning for that server before and the user acknowledged it.
+ * (Given that this dialog object is static/global and persistent,
+ * we can store that approval state here in this object.)
+ *
+ * @param configSchema @see open()
+ * @param configFilledIn @see open()
+ * @returns {Boolean} true when the dialog should be shown
+ * (call open()). if false, the dialog can and should be skipped.
+ */
+ needed : function(configSchema, configFilledIn)
+ {
+ assert(configSchema instanceof AccountConfig);
+ assert(configFilledIn instanceof AccountConfig);
+ assert(configSchema.isComplete());
+ assert(configFilledIn.isComplete());
+
+ var incomingBad = ((configFilledIn.incoming.socketType > 1) ? 0 : this._inSecurityBad) |
+ ((configFilledIn.incoming.badCert) ? this._inCertBad : 0);
+ var outgoingBad = 0;
+ if (!configFilledIn.outgoing.existingServerKey) {
+ outgoingBad = ((configFilledIn.outgoing.socketType > 1) ? 0 : this._outSecurityBad) |
+ ((configFilledIn.outgoing.badCert) ? this._outCertBad : 0);
+ }
+
+ if (incomingBad > 0) {
+ if (this._acknowledged.some(
+ function(ackServer) {
+ return serverMatches(ackServer, configFilledIn.incoming);
+ }))
+ incomingBad = 0;
+ }
+ if (outgoingBad > 0) {
+ if (this._acknowledged.some(
+ function(ackServer) {
+ return serverMatches(ackServer, configFilledIn.outgoing);
+ }))
+ outgoingBad = 0;
+ }
+
+ return incomingBad | outgoingBad;
+ },
+
+ /**
+ * Opens the dialog, fills it with values, and shows it to the user.
+ *
+ * The function is async: it returns immediately, and when the user clicks
+ * OK or Cancel, the callbacks are called. There the callers proceed as
+ * appropriate.
+ *
+ * @param configSchema The config, with placeholders not replaced yet.
+ * This object may be modified to store the user's confirmations, but
+ * currently that's not the case.
+ * @param configFilledIn The concrete config with placeholders replaced.
+ * @param onlyIfNeeded {Boolean} If there is nothing to warn about,
+ * call okCallback() immediately (and sync).
+ * @param okCallback {function(config {AccountConfig})}
+ * Called when the user clicked OK and approved the config including
+ * the warnings. |config| is without placeholders replaced.
+ * @param cancalCallback {function()}
+ * Called when the user decided to heed the warnings and not approve.
+ */
+ open : function(configSchema, configFilledIn, onlyIfNeeded,
+ okCallback, cancelCallback)
+ {
+ assert(typeof(okCallback) == "function");
+ assert(typeof(cancelCallback) == "function");
+ // needed() also checks the parameters
+ var needed = this.needed(configSchema, configFilledIn);
+ if ((needed == 0) && onlyIfNeeded) {
+ okCallback();
+ return;
+ }
+
+ assert(needed > 0 , "security dialog opened needlessly");
+ this._currentConfigFilledIn = configFilledIn;
+ this._okCallback = okCallback;
+ this._cancelCallback = cancelCallback;
+ var incoming = configFilledIn.incoming;
+ var outgoing = configFilledIn.outgoing;
+
+ _hide("mastervbox");
+ _show("warningbox");
+ // reset dialog, in case we've shown it before
+ e("acknowledge_warning").checked = false;
+ _disable("iknow");
+ e("incoming_technical").removeAttribute("expanded");
+ e("incoming_details").setAttribute("collapsed", true);
+ e("outgoing_technical").removeAttribute("expanded");
+ e("outgoing_details").setAttribute("collapsed", true);
+
+ if (needed & this._inSecurityBad) {
+ setText("warning_incoming", gStringsBundle.getFormattedString(
+ "cleartext_warning", [incoming.hostname]));
+ setText("incoming_details", gStringsBundle.getString(
+ "cleartext_details"));
+ _show("incoming_box");
+ } else if (needed & this._inCertBad) {
+ setText("warning_incoming", gStringsBundle.getFormattedString(
+ "selfsigned_warning", [incoming.hostname]));
+ setText("incoming_details", gStringsBundle.getString(
+ "selfsigned_details"));
+ _show("incoming_box");
+ } else {
+ _hide("incoming_box");
+ }
+
+ if (needed & this._outSecurityBad) {
+ setText("warning_outgoing", gStringsBundle.getFormattedString(
+ "cleartext_warning", [outgoing.hostname]));
+ setText("outgoing_details", gStringsBundle.getString(
+ "cleartext_details"));
+ _show("outgoing_box");
+ } else if (needed & this._outCertBad) {
+ setText("warning_outgoing", gStringsBundle.getFormattedString(
+ "selfsigned_warning", [outgoing.hostname]));
+ setText("outgoing_details", gStringsBundle.getString(
+ "selfsigned_details"));
+ _show("outgoing_box");
+ } else {
+ _hide("outgoing_box");
+ }
+ _show("acknowledge_warning");
+ assert(!e("incoming_box").hidden || !e("outgoing_box").hidden,
+ "warning dialog shown for unknown reason");
+
+ window.sizeToContent();
+ },
+
+ toggleDetails : function (id)
+ {
+ let details = e(id + "_details");
+ let tech = e(id + "_technical");
+ if (details.getAttribute("collapsed")) {
+ details.removeAttribute("collapsed");
+ tech.setAttribute("expanded", true);
+ } else {
+ details.setAttribute("collapsed", true);
+ tech.removeAttribute("expanded");
+ }
+ },
+
+ /**
+ * user checked checkbox that he understood it and wishes
+ * to ignore the warning.
+ */
+ toggleAcknowledge : function()
+ {
+ if (e("acknowledge_warning").checked) {
+ _enable("iknow");
+ } else {
+ _disable("iknow");
+ }
+ },
+
+ /**
+ * [Cancel] button pressed. Get me out of here!
+ */
+ onCancel : function()
+ {
+ _hide("warningbox");
+ _show("mastervbox");
+ window.sizeToContent();
+
+ this._cancelCallback();
+ },
+
+ /**
+ * [OK] button pressed.
+ * Implies that the user toggled the acknowledge checkbox,
+ * i.e. approved the config and ignored the warnings,
+ * otherwise the button would have been disabled.
+ */
+ onOK : function()
+ {
+ assert(e("acknowledge_warning").checked);
+
+ var overrideOK = this.showCertOverrideDialog(this._currentConfigFilledIn);
+ if (!overrideOK) {
+ this.onCancel();
+ return;
+ }
+
+ // need filled in, in case hostname is placeholder
+ var storeConfig = this._currentConfigFilledIn.copy();
+ this._acknowledged.push(storeConfig.incoming);
+ this._acknowledged.push(storeConfig.outgoing);
+
+ _show("mastervbox");
+ _hide("warningbox");
+ window.sizeToContent();
+
+ this._okCallback();
+ },
+
+ /**
+ * Shows a(nother) dialog which allows the user to see and override
+ * (manually accept) a bad certificate. It also optionally adds it
+ * permanently to the "good certs" store of NSS in the profile.
+ * Only shows the dialog, if there are bad certs. Otherwise, it's a no-op.
+ *
+ * The dialog is the standard PSM cert override dialog.
+ *
+ * @param config {AccountConfig} concrete
+ * @returns true, if all certs are fine or the user accepted them.
+ * false, if the user cancelled.
+ *
+ * static function
+ * sync function: blocks until the dialog is closed.
+ */
+ showCertOverrideDialog : function(config)
+ {
+ if (config.incoming.socketType > 1 && // SSL or STARTTLS
+ config.incoming.badCert) {
+ var params = {
+ exceptionAdded : false,
+ prefetchCert : true,
+ location : config.incoming.targetSite,
+ };
+ window.openDialog("chrome://pippki/content/exceptionDialog.xul",
+ "","chrome,centerscreen,modal", params);
+ if (params.exceptionAdded) { // set by dialog
+ config.incoming.badCert = false;
+ } else {
+ return false;
+ }
+ }
+ if (!config.outgoing.existingServerKey) {
+ if (config.outgoing.socketType > 1 && // SSL or STARTTLS
+ config.outgoing.badCert) {
+ var params = {
+ exceptionAdded : false,
+ prefetchCert : true,
+ location : config.outgoing.targetSite,
+ };
+ window.openDialog("chrome://pippki/content/exceptionDialog.xul",
+ "","chrome,centerscreen,modal", params);
+ if (params.exceptionAdded) { // set by dialog
+ config.outgoing.badCert = false;
+ } else {
+ return false;
+ }
+ }
+ }
+ return true;
+ },
+}
+var gSecurityWarningDialog = new SecurityWarningDialog();
diff --git a/mailnews/base/prefs/content/accountcreation/emailWizard.xul b/mailnews/base/prefs/content/accountcreation/emailWizard.xul
new file mode 100644
index 000000000..261545671
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/emailWizard.xul
@@ -0,0 +1,497 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountCreation.css"
+ type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % acDTD SYSTEM "chrome://messenger/locale/accountCreation.dtd">
+ %acDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="autoconfigWizard"
+ windowtype="mail:autoconfig"
+ title="&autoconfigWizard.title;"
+ onload="gEmailConfigWizard.onLoad();"
+ onkeypress="gEmailConfigWizard.onKeyDown(event);"
+ onclose="gEmailConfigWizard.onWizardShutdown();"
+ onunload="gEmailConfigWizard.onWizardShutdown();"
+ >
+
+ <stringbundleset>
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="strings"
+ src="chrome://messenger/locale/accountCreation.properties"/>
+ <stringbundle id="utilstrings"
+ src="chrome://messenger/locale/accountCreationUtil.properties"/>
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+ </stringbundleset>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/util.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/accountConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/emailWizard.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/sanitizeDatatypes.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/fetchhttp.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/readFromXML.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/guessConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/verifyConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/fetchConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/createInBackend.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/MyBadCertHandler.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountUtils.js" />
+
+ <keyset id="mailKeys">
+ <key keycode="VK_ESCAPE" oncommand="window.close();"/>
+ </keyset>
+
+ <panel id="insecureserver-cleartext-panel" class="popup-panel">
+ <hbox>
+ <image class="insecureLarry"/>
+ <vbox flex="1">
+ <description class="title">&insecureServer.tooltip.title;</description>
+ <description class="details">
+ &insecureUnencrypted.description;</description>
+ </vbox>
+ </hbox>
+ </panel>
+ <panel id="insecureserver-selfsigned-panel" class="popup-panel">
+ <hbox>
+ <image class="insecureLarry"/>
+ <vbox flex="1">
+ <description class="title">&insecureServer.tooltip.title;</description>
+ <description class="details">
+ &insecureSelfSigned.description;</description>
+ </vbox>
+ </hbox>
+ </panel>
+ <panel id="secureserver-panel" class="popup-panel">
+ <hbox>
+ <image class="secureLarry"/>
+ <vbox flex="1">
+ <description class="title">&secureServer.description;</description>
+ </vbox>
+ </hbox>
+ </panel>
+
+ <tooltip id="insecureserver-cleartext">
+ <hbox>
+ <image class="insecureLarry"/>
+ <vbox>
+ <description class="title">&insecureServer.tooltip.title;</description>
+ <description class="details">
+ &insecureServer.tooltip.details;</description>
+ </vbox>
+ </hbox>
+ </tooltip>
+ <tooltip id="insecureserver-selfsigned">
+ <hbox>
+ <image class="insecureLarry"/>
+ <vbox>
+ <description class="title">&insecureServer.tooltip.title;</description>
+ <description class="details">
+ &insecureServer.tooltip.details;</description>
+ </vbox>
+ </hbox>
+ </tooltip>
+ <tooltip id="secureservertooltip">
+ <hbox>
+ <image class="secureLarry"/>
+ <description class="title">&secureServer.description;</description>
+ </hbox>
+ </tooltip>
+ <tooltip id="optional-password">
+ <description>&password.text;</description>
+ </tooltip>
+
+ <spacer id="fullwidth"/>
+
+ <vbox id="mastervbox" class="mastervbox" flex="1">
+ <grid id="initialSettings">
+ <columns>
+ <column/>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label accesskey="&name.accesskey;"
+ class="autoconfigLabel"
+ value="&name.label;"
+ control="realname"/>
+ <textbox id="realname"
+ class="padded"
+ placeholder="&name.placeholder;"
+ oninput="gEmailConfigWizard.onInputRealname();"
+ onblur="gEmailConfigWizard.onBlurRealname();"/>
+ <hbox>
+ <description id="nametext" class="initialDesc">&name.text;</description>
+ <image id="nameerroricon"
+ hidden="true"
+ class="warningicon"/>
+ <description id="nameerror" class="errordescription" hidden="true"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label accesskey="&email.accesskey;"
+ class="autoconfigLabel"
+ value="&email.label;"
+ control="email"/>
+ <textbox id="email"
+ class="padded uri-element"
+ placeholder="&email.placeholder;"
+ oninput="gEmailConfigWizard.onInputEmail();"
+ onblur="gEmailConfigWizard.onBlurEmail();"/>
+ <hbox>
+ <image id="emailerroricon"
+ hidden="true"
+ class="warningicon"/>
+ <description id="emailerror" class="errordescription" hidden="true"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <!-- this starts out as text so the emptytext shows, but then
+ changes to type=password once it's not empty -->
+ <label accesskey="&password.accesskey;"
+ class="autoconfigLabel"
+ value="&password.label;"
+ control="password"
+ tooltip="optional-password"/>
+ <textbox id="password"
+ class="padded"
+ placeholder="&password.placeholder;"
+ type="text"
+ oninput="gEmailConfigWizard.onInputPassword();"
+ onfocus="gEmailConfigWizard.onFocusPassword();"
+ onblur="gEmailConfigWizard.onBlurPassword();"/>
+ <hbox>
+ <image id="passworderroricon"
+ hidden="true"
+ class="warningicon"/>
+ <description id="passworderror" class="errordescription" hidden="true"/>
+ </hbox>
+ </row>
+ <row align="center" pack="start">
+ <label class="autoconfigLabel"/>
+ <checkbox id="remember_password"
+ label="&rememberPassword.label;"
+ accesskey="&rememberPassword.accesskey;"
+ checked="true"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1" />
+
+ <hbox id="status_area" flex="1">
+ <vbox id="status_img_before" pack="start"/>
+ <description id="status_msg">&#160;</description>
+ <!-- Include 160 = nbsp, to make the element occupy the
+ full height, for at least one line. With a normal space,
+ it does not have sufficient height. -->
+ <vbox id="status_img_after" pack="start"/>
+ </hbox>
+
+ <groupbox id="result_area" hidden="true">
+ <radiogroup id="result_imappop" orient="horizontal">
+ <radio id="result_select_imap" label="&imapLong.label;" value="1"
+ oncommand="gEmailConfigWizard.onResultIMAPOrPOP3();"/>
+ <radio id="result_select_pop3" label="&pop3Long.label;" value="2"
+ oncommand="gEmailConfigWizard.onResultIMAPOrPOP3();"/>
+ </radiogroup>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label class="textbox-label" value="&incoming.label;"
+ control="result-incoming"/>
+ <textbox id="result-incoming" disabled="true" flex="1"/>
+ </row>
+ <row align="center">
+ <label class="textbox-label" value="&outgoing.label;"
+ control="result-outgoing"/>
+ <textbox id="result-outgoing" disabled="true" flex="1"/>
+ </row>
+ <row align="center">
+ <label class="textbox-label" value="&username.label;"
+ control="result-username"/>
+ <textbox id="result-username" disabled="true" flex="1"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox id="manual-edit_area" hidden="true">
+ <grid>
+ <columns>
+ <column/><!-- row label, e.g. "incoming" -->
+ <column/><!-- protocol, e.g. "IMAP" -->
+ <column flex="1"/><!-- hostname / username -->
+ <column/><!-- port -->
+ <column/><!-- SSL -->
+ <column/><!-- auth method -->
+ </columns>
+ <rows>
+ <row id="labels_row" align="center">
+ <spacer/>
+ <spacer/>
+ <label value="&hostname.label;" class="columnHeader"/>
+ <label value="&port.label;" class="columnHeader"/>
+ <label value="&ssl.label;" class="columnHeader"/>
+ <label value="&auth.label;" class="columnHeader"/>
+ </row>
+ <row id="incoming_server_area">
+ <hbox align="center" pack="end">
+ <label class="textbox-label"
+ value="&incoming.label;"
+ control="incoming_hostname"/>
+ </hbox>
+ <menulist id="incoming_protocol"
+ oncommand="gEmailConfigWizard.onChangedProtocolIncoming();"
+ sizetopopup="always">
+ <menupopup>
+ <menuitem label="&imap.label;" value="1"/>
+ <menuitem label="&pop3.label;" value="2"/>
+ </menupopup>
+ </menulist>
+ <textbox id="incoming_hostname"
+ oninput="gEmailConfigWizard.onInputHostname();"
+ class="host uri-element"/>
+ <menulist id="incoming_port"
+ editable="true"
+ oninput="gEmailConfigWizard.onChangedPortIncoming();"
+ oncommand="gEmailConfigWizard.onChangedPortIncoming();"
+ class="port">
+ <menupopup/>
+ </menulist>
+ <menulist id="incoming_ssl"
+ class="security"
+ oncommand="gEmailConfigWizard.onChangedSSLIncoming();"
+ sizetopopup="always">
+ <menupopup>
+ <!-- values defined in nsMsgSocketType -->
+ <menuitem label="&autodetect.label;" value="0"/>
+ <menuitem label="&noEncryption.label;" value="1"/>
+ <menuitem label="&starttls.label;" value="3"/>
+ <menuitem label="&sslTls.label;" value="2"/>
+ </menupopup>
+ </menulist>
+ <menulist id="incoming_authMethod"
+ class="auth"
+ oncommand="gEmailConfigWizard.onChangedInAuth();"
+ sizetopopup="always">
+ <menupopup>
+ <menuitem label="&autodetect.label;" value="0"/>
+ <!-- values defined in nsMsgAuthMethod -->
+ <!-- labels set from messenger.properties
+ to avoid duplication -->
+ <menuitem id="in-authMethod-password-cleartext" value="3"/>
+ <menuitem id="in-authMethod-password-encrypted" value="4"/>
+ <menuitem id="in-authMethod-kerberos" value="5"/>
+ <menuitem id="in-authMethod-ntlm" value="6"/>
+#ifdef MOZ_MAILNEWS_OAUTH2
+ <menuitem id="in-authMethod-oauth2" value="10" hidden="true"/>
+#endif
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="outgoing_server_area" align="center">
+ <label class="textbox-label"
+ value="&outgoing.label;"
+ control="outgoing_hostname"/>
+ <label id="outgoing_protocol"
+ value="&smtp.label;"/>
+ <menulist id="outgoing_hostname"
+ editable="true"
+ sizetopopup="none"
+ oninput="gEmailConfigWizard.onInputHostname();"
+ oncommand="gEmailConfigWizard.onChangedOutgoingDropdown();"
+ onpopupshowing="gEmailConfigWizard.onOpenOutgoingDropdown();"
+ class="host uri-element">
+ <menupopup id="outgoing_hostname_popup"/>
+ </menulist>
+ <menulist id="outgoing_port"
+ editable="true"
+ oninput="gEmailConfigWizard.onChangedPortOutgoing();"
+ oncommand="gEmailConfigWizard.onChangedPortOutgoing();"
+ class="port">
+ <menupopup/>
+ </menulist>
+ <menulist id="outgoing_ssl"
+ class="security"
+ oncommand="gEmailConfigWizard.onChangedSSLOutgoing();"
+ sizetopopup="always">
+ <menupopup>
+ <!-- @see incoming -->
+ <menuitem label="&autodetect.label;" value="0"/>
+ <menuitem label="&noEncryption.label;" value="1"/>
+ <menuitem label="&starttls.label;" value="3"/>
+ <menuitem label="&sslTls.label;" value="2"/>
+ </menupopup>
+ </menulist>
+ <menulist id="outgoing_authMethod"
+ class="auth"
+ oncommand="gEmailConfigWizard.onChangedOutAuth(this.selectedItem);"
+ sizetopopup="always">
+ <menupopup>
+ <menuitem label="&autodetect.label;" value="0"/>
+ <!-- @see incoming -->
+ <menuitem id="out-authMethod-no" value="1"/>
+ <menuitem id="out-authMethod-password-cleartext" value="3"/>
+ <menuitem id="out-authMethod-password-encrypted" value="4"/>
+ <menuitem id="out-authMethod-kerberos" value="5"/>
+ <menuitem id="out-authMethod-ntlm" value="6"/>
+#ifdef MOZ_MAILNEWS_OAUTH2
+ <menuitem id="out-authMethod-oauth2" value="10" hidden="true"/>
+#endif
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="username_area" align="center">
+ <label class="textbox-label"
+ value="&username.label;"/>
+ <label class="columnHeader"
+ value="&incoming.label;"
+ control="incoming_username"/>
+ <textbox id="incoming_username"
+ oninput="gEmailConfigWizard.onInputInUsername();"
+ class="username"/>
+ <spacer/>
+ <label class="columnHeader"
+ id="outgoing_label"
+ value="&outgoing.label;"
+ control="outgoing_username"/>
+ <textbox id="outgoing_username"
+ oninput="gEmailConfigWizard.onInputOutUsername();"
+ class="username"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <spacer flex="1" />
+ <hbox id="buttons_area">
+ <hbox id="left_buttons_area" align="center" pack="start">
+ <button id="provisioner_button"
+ label="&switch-to-provisioner.label;"
+ accesskey="&switch-to-provisioner.accesskey;"
+ class="larger-button"
+ oncommand="gEmailConfigWizard.onSwitchToProvisioner();"/>
+ <button id="manual-edit_button"
+ label="&manual-edit.label;"
+ accesskey="&manual-edit.accesskey;"
+ hidden="true"
+ oncommand="gEmailConfigWizard.onManualEdit();"/>
+ <button id="advanced-setup_button"
+ label="&advancedSetup.label;"
+ accesskey="&advancedSetup.accesskey;"
+ disabled="true"
+ hidden="true"
+ oncommand="gEmailConfigWizard.onAdvancedSetup();"/>
+ </hbox>
+ <spacer flex="1"/>
+ <hbox id="right_buttons_area" align="center" pack="end">
+ <button id="stop_button"
+ label="&stop.label;"
+ accesskey="&stop.accesskey;"
+ hidden="true"
+ oncommand="gEmailConfigWizard.onStop();"/>
+ <button id="cancel_button"
+ label="&cancel.label;"
+ accesskey="&cancel.accesskey;"
+ oncommand="gEmailConfigWizard.onCancel();"/>
+ <button id="half-manual-test_button"
+ label="&half-manual-test.label;"
+ accesskey="&half-manual-test.accesskey;"
+ hidden="true"
+ oncommand="gEmailConfigWizard.onHalfManualTest();"/>
+ <button id="next_button"
+ label="&continue.label;"
+ accesskey="&continue.accesskey;"
+ hidden="false"
+ oncommand="gEmailConfigWizard.onNext();"/>
+ <button id="create_button"
+ label="&doneAccount.label;"
+ accesskey="&doneAccount.accesskey;"
+ class="important-button"
+ hidden="true"
+ oncommand="gEmailConfigWizard.onCreate();"/>
+ </hbox>
+ </hbox>
+ </vbox>
+
+
+ <vbox id="warningbox" hidden="true" flex="1">
+ <hbox class="warning" flex="1">
+ <vbox class="larrybox">
+ <image id="insecure_larry" class="insecureLarry"/>
+ </vbox>
+ <vbox flex="1" class="warning_text">
+ <label class="warning-heading">&warning.label;</label>
+ <vbox id="incoming_box">
+ <hbox>
+ <label class="warning_settings" value="&incomingSettings.label;"/>
+ <description id="warning_incoming"/>
+ </hbox>
+ <label id="incoming_technical"
+ class="technical_details"
+ value="&technicaldetails.label;"
+ onclick="gSecurityWarningDialog.toggleDetails('incoming');"/>
+ <description id="incoming_details" collapsed="true"/>
+ </vbox>
+ <vbox id="outgoing_box">
+ <hbox>
+ <label class="warning_settings" value="&outgoingSettings.label;"/>
+ <description id="warning_outgoing"/>
+ </hbox>
+ <label id="outgoing_technical"
+ class="technical_details"
+ value="&technicaldetails.label;"
+ onclick="gSecurityWarningDialog.toggleDetails('outgoing');"/>
+ <description id="outgoing_details" collapsed="true"/>
+ </vbox>
+ <spacer flex="10"/>
+ <description id="findoutmore">
+ &contactYourProvider.description;</description>
+ <spacer flex="100"/>
+ <checkbox id="acknowledge_warning"
+ label="&confirmWarning.label;"
+ accesskey="&confirmWarning.accesskey;"
+ class="acknowledge_checkbox"
+ oncommand="gSecurityWarningDialog.toggleAcknowledge()"/>
+ <hbox>
+ <button id="getmeoutofhere"
+ label="&changeSettings.label;"
+ accesskey="&changeSettings.accesskey;"
+ oncommand="gSecurityWarningDialog.onCancel()"/>
+ <spacer flex="1"/>
+ <button id="iknow"
+ label="&doneAccount.label;"
+ accesskey="&doneAccount.accesskey;"
+ disabled="true"
+ oncommand="gSecurityWarningDialog.onOK()"/>
+ </hbox>
+ </vbox>
+ </hbox>
+ </vbox>
+</window>
diff --git a/mailnews/base/prefs/content/accountcreation/fetchConfig.js b/mailnews/base/prefs/content/accountcreation/fetchConfig.js
new file mode 100644
index 000000000..2fe604e69
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/fetchConfig.js
@@ -0,0 +1,237 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tries to find a configuration for this ISP on the local harddisk, in the
+ * application install directory's "isp" subdirectory.
+ * Params @see fetchConfigFromISP()
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/JXON.js");
+
+
+function fetchConfigFromDisk(domain, successCallback, errorCallback)
+{
+ return new TimeoutAbortable(runAsync(function()
+ {
+ try {
+ // <TB installdir>/isp/example.com.xml
+ var configLocation = Services.dirsvc.get("CurProcD", Ci.nsIFile);
+ configLocation.append("isp");
+ configLocation.append(sanitize.hostname(domain) + ".xml");
+
+ var contents =
+ readURLasUTF8(Services.io.newFileURI(configLocation));
+ let domParser = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+ successCallback(readFromXML(JXON.build(
+ domParser.parseFromString(contents, "text/xml"))));
+ } catch (e) { errorCallback(e); }
+ }));
+}
+
+/**
+ * Tries to get a configuration from the ISP / mail provider directly.
+ *
+ * Disclaimers:
+ * - To support domain hosters, we cannot use SSL. That means we
+ * rely on insecure DNS and http, which means the results may be
+ * forged when under attack. The same is true for guessConfig(), though.
+ *
+ * @param domain {String} The domain part of the user's email address
+ * @param emailAddress {String} The user's email address
+ * @param successCallback {Function(config {AccountConfig}})} A callback that
+ * will be called when we could retrieve a configuration.
+ * The AccountConfig object will be passed in as first parameter.
+ * @param errorCallback {Function(ex)} A callback that
+ * will be called when we could not retrieve a configuration,
+ * for whatever reason. This is expected (e.g. when there's no config
+ * for this domain at this location),
+ * so do not unconditionally show this to the user.
+ * The first paramter will be an exception object or error string.
+ */
+function fetchConfigFromISP(domain, emailAddress, successCallback,
+ errorCallback)
+{
+ if (!Services.prefs.getBoolPref(
+ "mailnews.auto_config.fetchFromISP.enabled")) {
+ errorCallback("ISP fetch disabled per user preference");
+ return;
+ }
+
+ let conf1 = "autoconfig." + sanitize.hostname(domain) +
+ "/mail/config-v1.1.xml";
+ // .well-known/ <http://tools.ietf.org/html/draft-nottingham-site-meta-04>
+ let conf2 = sanitize.hostname(domain) +
+ "/.well-known/autoconfig/mail/config-v1.1.xml";
+ // This list is sorted by decreasing priority
+ var urls = ["https://" + conf1, "https://" + conf2];
+ if (!Services.prefs.getBoolPref("mailnews.auto_config.fetchFromISP.sslOnly")) {
+ urls.push("http://" + conf1, "http://" + conf2);
+ }
+ let sucAbortable = new SuccessiveAbortable();
+ var urlArgs = { emailaddress: emailAddress };
+ if (!Services.prefs.getBoolPref(
+ "mailnews.auto_config.fetchFromISP.sendEmailAddress")) {
+ delete urlArgs.emailaddress;
+ }
+ var time;
+
+ let success = function(result) {
+ successCallback(readFromXML(result));
+ };
+
+ let error = function(i, e) {
+ ddump("fetchisp " + i + " <" + urls[i] + "> took " +
+ (Date.now() - time) + "ms and failed with " + e);
+
+ if (i == urls.length - 1 || // implies all fetches failed
+ e instanceof CancelledException) {
+ errorCallback(e);
+ return;
+ }
+ let fetch = new FetchHTTP(urls[i + 1], urlArgs, false, success,
+ function(e) { error(i + 1, e) });
+ sucAbortable.current = fetch;
+ time = Date.now();
+ fetch.start();
+ };
+
+ let fetch = new FetchHTTP(urls[0], urlArgs, false, success,
+ function(e) { error(0, e) });
+ sucAbortable.current = fetch;
+ time = Date.now();
+ fetch.start();
+ return sucAbortable;
+}
+
+/**
+ * Tries to get a configuration for this ISP from a central database at
+ * Mozilla servers.
+ * Params @see fetchConfigFromISP()
+ */
+
+function fetchConfigFromDB(domain, successCallback, errorCallback)
+{
+ let url = Services.prefs.getCharPref("mailnews.auto_config_url");
+ domain = sanitize.hostname(domain);
+
+ // If we don't specify a place to put the domain, put it at the end.
+ if (!url.includes("{{domain}}"))
+ url = url + domain;
+ else
+ url = url.replace("{{domain}}", domain);
+ url = url.replace("{{accounts}}", MailServices.accounts.accounts.length);
+
+ if (!url.length)
+ return errorCallback("no fetch url set");
+ let fetch = new FetchHTTP(url, null, false,
+ function(result)
+ {
+ successCallback(readFromXML(result));
+ },
+ errorCallback);
+ fetch.start();
+ return fetch;
+}
+
+/**
+ * Does a lookup of DNS MX, to get the server who is responsible for
+ * recieving mail for this domain. Then it takes the domain of that
+ * server, and does another lookup (in ISPDB and possible at ISP autoconfig
+ * server) and if such a config is found, returns that.
+ *
+ * Disclaimers:
+ * - DNS is unprotected, meaning the results could be forged.
+ * The same is true for fetchConfigFromISP() and guessConfig(), though.
+ * - DNS MX tells us the incoming server, not the mailbox (IMAP) server.
+ * They are different. This mechnism is only an approximation
+ * for hosted domains (yourname.com is served by mx.hoster.com and
+ * therefore imap.hoster.com - that "therefore" is exactly the
+ * conclusional jump we make here.) and alternative domains
+ * (e.g. yahoo.de -> yahoo.com).
+ * - We make a look up for the base domain. E.g. if MX is
+ * mx1.incoming.servers.hoster.com, we look up hoster.com.
+ * Thanks to Services.eTLD, we also get bbc.co.uk right.
+ *
+ * Params @see fetchConfigFromISP()
+ */
+function fetchConfigForMX(domain, successCallback, errorCallback)
+{
+ domain = sanitize.hostname(domain);
+
+ var sucAbortable = new SuccessiveAbortable();
+ var time = Date.now();
+ sucAbortable.current = getMX(domain,
+ function(mxHostname) // success
+ {
+ ddump("getmx took " + (Date.now() - time) + "ms");
+ let sld = Services.eTLD.getBaseDomainFromHost(mxHostname);
+ ddump("base domain " + sld + " for " + mxHostname);
+ if (sld == domain)
+ {
+ errorCallback("MX lookup would be no different from domain");
+ return;
+ }
+ sucAbortable.current = fetchConfigFromDB(sld, successCallback,
+ errorCallback);
+ },
+ errorCallback);
+ return sucAbortable;
+}
+
+/**
+ * Queries the DNS MX for the domain
+ *
+ * The current implementation goes to a web service to do the
+ * DNS resolve for us, because Mozilla unfortunately has no implementation
+ * to do it. That's just a workaround. Once bug 545866 is fixed, we make
+ * the DNS query directly on the client. The API of this function should not
+ * change then.
+ *
+ * Returns (in successCallback) the hostname of the MX server.
+ * If there are several entires with different preference values,
+ * only the most preferred (i.e. those with the lowest value)
+ * is returned. If there are several most preferred servers (i.e.
+ * round robin), only one of them is returned.
+ *
+ * @param domain @see fetchConfigFromISP()
+ * @param successCallback {function(hostname {String})
+ * Called when we found an MX for the domain.
+ * For |hostname|, see description above.
+ * @param errorCallback @see fetchConfigFromISP()
+ * @returns @see fetchConfigFromISP()
+ */
+function getMX(domain, successCallback, errorCallback)
+{
+ domain = sanitize.hostname(domain);
+
+ let url = Services.prefs.getCharPref("mailnews.mx_service_url");
+ if (!url)
+ errorCallback("no URL for MX service configured");
+ url += domain;
+
+ let fetch = new FetchHTTP(url, null, false,
+ function(result)
+ {
+ // result is plain text, with one line per server.
+ // So just take the first line
+ ddump("MX query result: \n" + result + "(end)");
+ assert(typeof(result) == "string");
+ let first = result.split("\n")[0];
+ first.toLowerCase().replace(/[^a-z0-9\-_\.]*/g, "");
+ if (first.length == 0)
+ {
+ errorCallback("no MX found");
+ return;
+ }
+ successCallback(first);
+ },
+ errorCallback);
+ fetch.start();
+ return fetch;
+}
diff --git a/mailnews/base/prefs/content/accountcreation/fetchhttp.js b/mailnews/base/prefs/content/accountcreation/fetchhttp.js
new file mode 100644
index 000000000..d2323f7b6
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/fetchhttp.js
@@ -0,0 +1,264 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is a small wrapper around XMLHttpRequest, which solves various
+ * inadequacies of the API, e.g. error handling. It is entirely generic and
+ * can be used for purposes outside of even mail.
+ *
+ * It does not provide download progress, but assumes that the
+ * fetched resource is so small (<1 10 KB) that the roundtrip and
+ * response generation is far more significant than the
+ * download time of the response. In other words, it's fine for RPC,
+ * but not for bigger file downloads.
+ */
+
+Components.utils.import("resource://gre/modules/JXON.js");
+
+/**
+ * Set up a fetch.
+ *
+ * @param url {String} URL of the server function.
+ * ATTENTION: The caller needs to make sure that the URL is secure to call.
+ * @param urlArgs {Object, associative array} Parameters to add
+ * to the end of the URL as query string. E.g.
+ * { foo: "bla", bar: "blub blub" } will add "?foo=bla&bar=blub%20blub"
+ * to the URL
+ * (unless the URL already has a "?", then it adds "&foo...").
+ * The values will be urlComponentEncoded, so pass them unencoded.
+ * @param post {Boolean} HTTP GET or POST
+ * Only influences the HTTP request method,
+ * i.e. first line of the HTTP request, not the body or parameters.
+ * Use POST when you modify server state,
+ * GET when you only request information.
+ *
+ * @param successCallback {Function(result {String})}
+ * Called when the server call worked (no errors).
+ * |result| will contain the body of the HTTP reponse, as string.
+ * @param errorCallback {Function(ex)}
+ * Called in case of error. ex contains the error
+ * with a user-displayable but not localized |.message| and maybe a
+ * |.code|, which can be either
+ * - an nsresult error code,
+ * - an HTTP result error code (0...1000) or
+ * - negative: 0...-100 :
+ * -2 = can't resolve server in DNS etc.
+ * -4 = response body (e.g. XML) malformed
+ */
+/* not yet supported:
+ * @param headers {Object, associative array} Like urlArgs,
+ * just that the params will be added as HTTP headers.
+ * { foo: "blub blub" } will add "Foo: Blub blub"
+ * The values will be urlComponentEncoded, apart from space,
+ * so pass them unencoded.
+ * @param headerArgs {Object, associative array} Like urlArgs,
+ * just that the params will be added as HTTP headers.
+ * { foo: "blub blub" } will add "X-Moz-Arg-Foo: Blub blub"
+ * The values will be urlComponentEncoded, apart from space,
+ * so pass them unencoded.
+ * @param bodyArgs {Object, associative array} Like urlArgs,
+ * just that the params will be sent x-url-encoded in the body,
+ * like a HTML form post.
+ * The values will be urlComponentEncoded, so pass them unencoded.
+ * This cannot be used together with |uploadBody|.
+ * @param uploadbody {Object} Arbitrary object, which to use as
+ * body of the HTTP request. Will also set the mimetype accordingly.
+ * Only supported object types, currently only E4X is supported
+ * (sending XML).
+ * Usually, you have nothing to upload, so just pass |null|.
+ */
+function FetchHTTP(url, urlArgs, post, successCallback, errorCallback)
+{
+ assert(typeof(successCallback) == "function", "BUG: successCallback");
+ assert(typeof(errorCallback) == "function", "BUG: errorCallback");
+ this._url = sanitize.string(url);
+ if (!urlArgs)
+ urlArgs = {};
+
+ this._urlArgs = urlArgs;
+ this._post = sanitize.boolean(post);
+ this._successCallback = successCallback;
+ this._errorCallback = errorCallback;
+}
+FetchHTTP.prototype =
+{
+ __proto__: Abortable.prototype,
+ _url : null, // URL as passed to ctor, without arguments
+ _urlArgs : null,
+ _post : null,
+ _successCallback : null,
+ _errorCallback : null,
+ _request : null, // the XMLHttpRequest object
+ result : null,
+
+ start : function()
+ {
+ var url = this._url;
+ for (var name in this._urlArgs)
+ {
+ url += (!url.includes("?") ? "?" : "&") +
+ name + "=" + encodeURIComponent(this._urlArgs[name]);
+ }
+ this._request = new XMLHttpRequest();
+ let request = this._request;
+ request.open(this._post ? "POST" : "GET", url);
+ request.channel.loadGroup = null;
+ // needs bug 407190 patch v4 (or higher) - uncomment if that lands.
+ // try {
+ // var channel = request.channel.QueryInterface(Ci.nsIHttpChannel2);
+ // channel.connectTimeout = 5;
+ // channel.requestTimeout = 5;
+ // } catch (e) { dump(e + "\n"); }
+
+ var me = this;
+ request.onload = function() { me._response(true); }
+ request.onerror = function() { me._response(false); }
+ request.send(null);
+ },
+ _response : function(success, exStored)
+ {
+ try
+ {
+ var errorCode = null;
+ var errorStr = null;
+
+ if (success && this._request.status >= 200 &&
+ this._request.status < 300) // HTTP level success
+ {
+ try
+ {
+ // response
+ var mimetype = this._request.getResponseHeader("Content-Type");
+ if (!mimetype)
+ mimetype = "";
+ mimetype = mimetype.split(";")[0];
+ if (mimetype == "text/xml" ||
+ mimetype == "application/xml" ||
+ mimetype == "text/rdf")
+ {
+ this.result = JXON.build(this._request.responseXML);
+ }
+ else
+ {
+ //ddump("mimetype: " + mimetype + " only supported as text");
+ this.result = this._request.responseText;
+ }
+ //ddump("result:\n" + this.result);
+ }
+ catch (e)
+ {
+ success = false;
+ errorStr = getStringBundle(
+ "chrome://messenger/locale/accountCreationUtil.properties")
+ .GetStringFromName("bad_response_content.error");
+ errorCode = -4;
+ }
+ }
+ else
+ {
+ success = false;
+ errorCode = this._request.status;
+ if (errorCode == 0) {
+ errorStr = getStringBundle(
+ "chrome://messenger/locale/accountCreationUtil.properties")
+ .GetStringFromName("cannot_contact_server.error");
+ ddump(errorStr);
+ } else {
+ errorStr = this._request.statusText;
+ }
+ }
+
+ // Callbacks
+ if (success)
+ {
+ try {
+ this._successCallback(this.result);
+ } catch (e) {
+ logException(e);
+ this._error(e);
+ }
+ }
+ else if (exStored)
+ this._error(exStored);
+ else
+ this._error(new ServerException(errorStr, errorCode, this._url));
+
+ if (this._finishedCallback)
+ {
+ try {
+ this._finishedCallback(this);
+ } catch (e) {
+ logException(e);
+ this._error(e);
+ }
+ }
+
+ } catch (e) {
+ // error in our fetchhttp._response() code
+ logException(e);
+ this._error(e);
+ }
+ },
+ _error : function(e)
+ {
+ try {
+ this._errorCallback(e);
+ } catch (e) {
+ // error in errorCallback, too!
+ logException(e);
+ alertPrompt("Error in errorCallback for fetchhttp", e);
+ }
+ },
+ /**
+ * Call this between start() and finishedCallback fired.
+ */
+ cancel : function(ex)
+ {
+ assert(!this.result, "Call already returned");
+
+ this._request.abort();
+
+ // Need to manually call error handler
+ // <https://bugzilla.mozilla.org/show_bug.cgi?id=218236#c11>
+ this._response(false, ex ? ex : new UserCancelledException());
+ },
+ /**
+ * Allows caller or lib to be notified when the call is done.
+ * This is useful to enable and disable a Cancel button in the UI,
+ * which allows to cancel the network request.
+ */
+ setFinishedCallback : function(finishedCallback)
+ {
+ this._finishedCallback = finishedCallback;
+ }
+}
+
+function CancelledException(msg)
+{
+ Exception.call(this, msg);
+}
+CancelledException.prototype = Object.create(Exception.prototype);
+CancelledException.prototype.constructor = CancelledException;
+
+function UserCancelledException(msg)
+{
+ // The user knows they cancelled so I don't see a need
+ // for a message to that effect.
+ if (!msg)
+ msg = "User cancelled";
+ CancelledException.call(this, msg);
+}
+UserCancelledException.prototype = Object.create(CancelledException.prototype);
+UserCancelledException.prototype.constructor = UserCancelledException;
+
+function ServerException(msg, code, uri)
+{
+ Exception.call(this, msg);
+ this.code = code;
+ this.uri = uri;
+}
+ServerException.prototype = Object.create(Exception.prototype);
+ServerException.prototype.constructor = ServerException;
+
diff --git a/mailnews/base/prefs/content/accountcreation/guessConfig.js b/mailnews/base/prefs/content/accountcreation/guessConfig.js
new file mode 100644
index 000000000..6ba9f1dfb
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/guessConfig.js
@@ -0,0 +1,1192 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// This is a bit ugly - we set outgoingDone to false
+// when emailWizard.js cancels the outgoing probe because the user picked
+// an outoing server. It does this by poking the probeAbortable object,
+// so we need outgoingDone to have global scope.
+var outgoingDone = false;
+
+/**
+ * Try to guess the config, by:
+ * - guessing hostnames (pop3.<domain>, pop.<domain>, imap.<domain>,
+ * mail.<domain> etc.)
+ * - probing known ports (for IMAP, POP3 etc., with SSL, STARTTLS etc.)
+ * - opening a connection via the right protocol and checking the
+ * protocol-specific CAPABILITIES like that the server returns.
+ *
+ * Final verification is not done here, but in verifyConfig().
+ *
+ * This function is async.
+ * @param domain {String} the domain part of the email address
+ * @param progressCallback {function(type, hostname, port, ssl, done)}
+ * Called when we try a new hostname/port.
+ * type {String-enum} @see AccountConfig type - "imap", "pop3", "smtp"
+ * hostname {String}
+ * port {Integer}
+ * socketType {Integer-enum} @see AccountConfig.incoming.socketType
+ * 1 = plain, 2 = SSL, 3 = STARTTLS
+ * done {Boolean} false, if we start probing this host/port, true if we're
+ * done and the host is good. (there is no notification when a host is
+ * bad, we'll just tell about the next host tried)
+ * @param successCallback {function(config {AccountConfig})}
+ * Called when we could guess the config.
+ * param accountConfig {AccountConfig} The guessed account config.
+ * username, password, realname, emailaddress etc. are not filled out,
+ * but placeholders to be filled out via replaceVariables().
+ * @param errorCallback function(ex)
+ * Called when we could guess not the config, either
+ * because we have not found anything or
+ * because there was an error (e.g. no network connection).
+ * The ex.message will contain a user-presentable message.
+ * @param resultConfig {AccountConfig} (optional)
+ * A config which may be partially filled in. If so, it will be used as base
+ * for the guess.
+ * @param which {String-enum} (optional) "incoming", "outgoing", or "both".
+ * Default "both". Whether to guess only the incoming or outgoing server.
+ * @result {Abortable} Allows you to cancel the guess
+ */
+function guessConfig(domain, progressCallback, successCallback, errorCallback,
+ resultConfig, which)
+{
+ assert(typeof(progressCallback) == "function", "need progressCallback");
+ assert(typeof(successCallback) == "function", "need successCallback");
+ assert(typeof(errorCallback) == "function", "need errorCallback");
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ // Servers that we know enough that they support OAuth2 do not need guessing.
+ if (resultConfig.incoming.auth == Ci.nsMsgAuthMethod.OAuth2) {
+ successCallback(resultConfig);
+ return null;
+ }
+#endif
+
+ if (!resultConfig)
+ resultConfig = new AccountConfig();
+ resultConfig.source = AccountConfig.kSourceGuess;
+
+ if (!Services.prefs.getBoolPref(
+ "mailnews.auto_config.guess.enabled")) {
+ errorCallback("Guessing config disabled per user preference");
+ return;
+ }
+
+ var incomingHostDetector = null;
+ var outgoingHostDetector = null;
+ var incomingEx = null; // if incoming had error, store ex here
+ var outgoingEx = null; // if incoming had error, store ex here
+ var incomingDone = (which == "outgoing");
+ var outgoingDone = (which == "incoming");
+ // If we're offline, we're going to pick the most common settings.
+ // (Not the "best" settings, but common).
+ if (Services.io.offline)
+ {
+ resultConfig.source = AccountConfig.kSourceUser;
+ resultConfig.incoming.hostname = "mail." + domain;
+ resultConfig.incoming.username = resultConfig.identity.emailAddress;
+ resultConfig.outgoing.username = resultConfig.identity.emailAddress;
+ resultConfig.incoming.type = "imap";
+ resultConfig.incoming.port = 143;
+ resultConfig.incoming.socketType = 3; // starttls
+ resultConfig.incoming.auth = Ci.nsMsgAuthMethod.passwordCleartext;
+ resultConfig.outgoing.hostname = "smtp." + domain;
+ resultConfig.outgoing.socketType = 1;
+ resultConfig.outgoing.port = 587;
+ resultConfig.outgoing.auth = Ci.nsMsgAuthMethod.passwordCleartext;
+ resultConfig.incomingAlternatives.push({
+ hostname: "mail." + domain,
+ username: resultConfig.identity.emailAddress,
+ type: "pop3",
+ port: 110,
+ socketType: 3,
+ auth: Ci.nsMsgAuthMethod.passwordCleartext
+ });
+ successCallback(resultConfig);
+ return null;
+ }
+ var progress = function(thisTry)
+ {
+ progressCallback(protocolToString(thisTry.protocol), thisTry.hostname,
+ thisTry.port, sslConvertToSocketType(thisTry.ssl), false,
+ resultConfig);
+ };
+
+ var updateConfig = function(config)
+ {
+ resultConfig = config;
+ };
+
+ var errorInCallback = function(e)
+ {
+ // The caller's errorCallback threw.
+ // hopefully shouldn't happen for users.
+ alertPrompt("Error in errorCallback for guessConfig()", e);
+ };
+
+ var checkDone = function()
+ {
+ if (incomingEx)
+ {
+ try {
+ errorCallback(incomingEx, resultConfig);
+ } catch (e) { errorInCallback(e); }
+ return;
+ }
+ if (outgoingEx)
+ {
+ try {
+ errorCallback(outgoingEx, resultConfig);
+ } catch (e) { errorInCallback(e); }
+ return;
+ }
+ if (incomingDone && outgoingDone)
+ {
+ try {
+ successCallback(resultConfig);
+ } catch (e) {
+ try {
+ errorCallback(e);
+ } catch (e) { errorInCallback(e); }
+ }
+ return;
+ }
+ };
+
+ var logger = Log4Moz.getConfiguredLogger("mail.wizard");
+ var HostTryToAccountServer = function(thisTry, server)
+ {
+ server.type = protocolToString(thisTry.protocol);
+ server.hostname = thisTry.hostname;
+ server.port = thisTry.port;
+ server.socketType = sslConvertToSocketType(thisTry.ssl);
+ server.auth = chooseBestAuthMethod(thisTry.authMethods);
+ server.authAlternatives = thisTry.authMethods;
+ // TODO
+ // cert is also bad when targetSite is set. (Same below for incoming.)
+ // Fix SSLErrorHandler and security warning dialog in emailWizard.js.
+ server.badCert = thisTry.selfSignedCert;
+ server.targetSite = thisTry.targetSite;
+ logger.info("CHOOSING " + server.type + " "+ server.hostname + ":" +
+ server.port + ", auth method " + server.auth + " " +
+ server.authAlternatives.join(",") + ", SSL " + server.socketType +
+ (server.badCert ? " (bad cert!)" : ""));
+ };
+
+ var outgoingSuccess = function(thisTry, alternativeTries)
+ {
+ assert(thisTry.protocol == SMTP, "I only know SMTP for outgoing");
+ // Ensure there are no previously saved outgoing errors, if we've got
+ // success here.
+ outgoingEx = null;
+ HostTryToAccountServer(thisTry, resultConfig.outgoing);
+
+ for (let alternativeTry of alternativeTries)
+ {
+ // resultConfig.createNewOutgoing(); misses username etc., so copy
+ let altServer = deepCopy(resultConfig.outgoing);
+ HostTryToAccountServer(alternativeTry, altServer);
+ assert(resultConfig.outgoingAlternatives);
+ resultConfig.outgoingAlternatives.push(altServer);
+ }
+
+ progressCallback(resultConfig.outgoing.type,
+ resultConfig.outgoing.hostname, resultConfig.outgoing.port,
+ resultConfig.outgoing.socketType, true, resultConfig);
+ outgoingDone = true;
+ checkDone();
+ };
+
+ var incomingSuccess = function(thisTry, alternativeTries)
+ {
+ // Ensure there are no previously saved incoming errors, if we've got
+ // success here.
+ incomingEx = null;
+ HostTryToAccountServer(thisTry, resultConfig.incoming);
+
+ for (let alternativeTry of alternativeTries)
+ {
+ // resultConfig.createNewIncoming(); misses username etc., so copy
+ let altServer = deepCopy(resultConfig.incoming);
+ HostTryToAccountServer(alternativeTry, altServer);
+ assert(resultConfig.incomingAlternatives);
+ resultConfig.incomingAlternatives.push(altServer);
+ }
+
+ progressCallback(resultConfig.incoming.type,
+ resultConfig.incoming.hostname, resultConfig.incoming.port,
+ resultConfig.incoming.socketType, true, resultConfig);
+ incomingDone = true;
+ checkDone();
+ };
+
+ var incomingError = function(ex)
+ {
+ incomingEx = ex;
+ checkDone();
+ incomingHostDetector.cancel(new CancelOthersException());
+ outgoingHostDetector.cancel(new CancelOthersException());
+ };
+
+ var outgoingError = function(ex)
+ {
+ outgoingEx = ex;
+ checkDone();
+ incomingHostDetector.cancel(new CancelOthersException());
+ outgoingHostDetector.cancel(new CancelOthersException());
+ };
+
+ incomingHostDetector = new IncomingHostDetector(progress, incomingSuccess,
+ incomingError);
+ outgoingHostDetector = new OutgoingHostDetector(progress, outgoingSuccess,
+ outgoingError);
+ if (which == "incoming" || which == "both")
+ {
+ incomingHostDetector.start(resultConfig.incoming.hostname ?
+ resultConfig.incoming.hostname : domain,
+ !!resultConfig.incoming.hostname, resultConfig.incoming.type,
+ resultConfig.incoming.port, resultConfig.incoming.socketType);
+ }
+ if (which == "outgoing" || which == "both")
+ {
+ outgoingHostDetector.start(resultConfig.outgoing.hostname ?
+ resultConfig.outgoing.hostname : domain,
+ !!resultConfig.outgoing.hostname, "smtp",
+ resultConfig.outgoing.port, resultConfig.outgoing.socketType);
+ }
+
+ return new GuessAbortable(incomingHostDetector, outgoingHostDetector,
+ updateConfig);
+}
+
+function GuessAbortable(incomingHostDetector, outgoingHostDetector,
+ updateConfig)
+{
+ Abortable.call(this);
+ this._incomingHostDetector = incomingHostDetector;
+ this._outgoingHostDetector = outgoingHostDetector;
+ this._updateConfig = updateConfig;
+}
+GuessAbortable.prototype = Object.create(Abortable.prototype);
+GuessAbortable.prototype.constructor = GuessAbortable;
+GuessAbortable.prototype.cancel = function(ex)
+{
+ this._incomingHostDetector.cancel(ex);
+ this._outgoingHostDetector.cancel(ex);
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// Implementation
+//
+// Objects, functions and constants that follow are not to be used outside
+// this file.
+
+var kNotTried = 0;
+var kOngoing = 1;
+var kFailed = 2;
+var kSuccess = 3;
+
+/**
+ * Internal object holding one server that we should try or did try.
+ * Used as |thisTry|.
+ *
+ * Note: The consts it uses for protocol and ssl are defined towards the end
+ * of this file and not the same as those used in AccountConfig (type,
+ * socketType). (fix this)
+ */
+function HostTry()
+{
+}
+HostTry.prototype =
+{
+ // IMAP, POP or SMTP
+ protocol : UNKNOWN,
+ // {String}
+ hostname : undefined,
+ // {Integer}
+ port : undefined,
+ // NONE, SSL or TLS
+ ssl : UNKNOWN,
+ // {String} what to send to server
+ commands : null,
+ // {Integer-enum} kNotTried, kOngoing, kFailed or kSuccess
+ status : kNotTried,
+ // {Abortable} allows to cancel the socket comm
+ abortable : null,
+
+ // {Array of {Integer-enum}} @see _advertisesAuthMethods() result
+ // Info about the server, from the protocol and SSL chat
+ authMethods : null,
+ // {String} Whether the SSL cert is not from a proper CA
+ selfSignedCert : false,
+ // {String} Which host the SSL cert is made for, if not hostname.
+ // If set, this is an SSL error.
+ targetSite : null,
+};
+
+/**
+ * When the success or errorCallbacks are called to abort the other requests
+ * which happened in parallel, this ex is used as param for cancel(), so that
+ * the cancel doesn't trigger another callback.
+ */
+function CancelOthersException()
+{
+ CancelledException.call(this, "we're done, cancelling the other probes");
+}
+CancelOthersException.prototype = Object.create(CancelledException.prototype);
+CancelOthersException.prototype.constructor = CancelOthersException;
+
+/**
+ * @param successCallback {function(result {HostTry}, alts {Array of HostTry})}
+ * Called when the config is OK
+ * |result| is the most preferred server.
+ * |alts| currently exists only for |IncomingHostDetector| and contains
+ * some servers of the other type (POP3 instead of IMAP), if available.
+ * @param errorCallback {function(ex)} Called when we could not find a config
+ * @param progressCallback { function(server {HostTry}) } Called when we tried
+ * (will try?) a new hostname and port
+ */
+function HostDetector(progressCallback, successCallback, errorCallback)
+{
+ this.mSuccessCallback = successCallback;
+ this.mProgressCallback = progressCallback;
+ this.mErrorCallback = errorCallback;
+ this._cancel = false;
+ // {Array of {HostTry}}, ordered by decreasing preference
+ this._hostsToTry = new Array();
+
+ // init logging
+ this._log = Log4Moz.getConfiguredLogger("mail.wizard");
+ this._log.info("created host detector");
+}
+
+HostDetector.prototype =
+{
+ cancel : function(ex)
+ {
+ this._cancel = true;
+ // We have to actively stop the network calls, as they may result in
+ // callbacks e.g. to the cert handler. If the dialog is gone by the time
+ // this happens, the javascript stack is horked.
+ for (let i = 0; i < this._hostsToTry.length; i++)
+ {
+ let thisTry = this._hostsToTry[i]; // {HostTry}
+ if (thisTry.abortable)
+ thisTry.abortable.cancel(ex);
+ thisTry.status = kFailed; // or don't set? Maybe we want to continue.
+ }
+ if (ex instanceof CancelOthersException)
+ return;
+ if (!ex)
+ ex = new CancelledException();
+ this.mErrorCallback(ex);
+ },
+
+ /**
+ * Start the detection
+ *
+ * @param domain {String} to be used as base for guessing.
+ * Should be a domain (e.g. yahoo.co.uk).
+ * If hostIsPrecise == true, it should be a full hostname
+ * @param hostIsPrecise {Boolean} (default false) use only this hostname,
+ * do not guess hostnames.
+ * @param type {String-enum}@see AccountConfig type
+ * (Optional. default, 0, undefined, null = guess it)
+ * @param port {Integer} (Optional. default, 0, undefined, null = guess it)
+ * @param socketType {Integer-enum}@see AccountConfig socketType
+ * (Optional. default, 0, undefined, null = guess it)
+ */
+ start : function(domain, hostIsPrecise, type, port, socketType)
+ {
+ domain = domain.replace(/\s*/g, ""); // Remove whitespace
+ if (!hostIsPrecise)
+ hostIsPrecise = false;
+ var protocol = sanitize.translate(type,
+ { "imap" : IMAP, "pop3" : POP, "smtp" : SMTP }, UNKNOWN);
+ if (!port)
+ port = UNKNOWN;
+ var ssl_only = Services.prefs.getBoolPref("mailnews.auto_config.guess.sslOnly");
+ var ssl = ConvertSocketTypeToSSL(socketType);
+ this._cancel = false;
+ this._log.info("doing auto detect for protocol " + protocol +
+ ", domain " + domain + ", (exactly: " + hostIsPrecise +
+ "), port " + port + ", ssl " + ssl);
+
+ // fill this._hostsToTry
+ this._hostsToTry = [];
+ var hostnamesToTry = [];
+ // if hostIsPrecise is true, it's because that's what the user input
+ // explicitly, and we'll just try it, nothing else.
+ if (hostIsPrecise)
+ hostnamesToTry.push(domain);
+ else
+ hostnamesToTry = this._hostnamesToTry(protocol, domain);
+
+ for (let i = 0; i < hostnamesToTry.length; i++)
+ {
+ let hostname = hostnamesToTry[i];
+ let hostEntries = this._portsToTry(hostname, protocol, ssl, port);
+ for (let j = 0; j < hostEntries.length; j++)
+ {
+ let hostTry = hostEntries[j]; // from getHostEntry()
+ if (ssl_only && hostTry.ssl == NONE)
+ continue;
+ hostTry.hostname = hostname;
+ hostTry.status = kNotTried;
+ hostTry.desc = hostTry.hostname + ":" + hostTry.port +
+ " ssl=" + hostTry.ssl + " " +
+ protocolToString(hostTry.protocol);
+ this._hostsToTry.push(hostTry);
+ }
+ }
+
+ this._hostsToTry = sortTriesByPreference(this._hostsToTry);
+ this._tryAll();
+ },
+
+ // We make all host/port combinations run in parallel, store their
+ // results in an array, and as soon as one finishes successfully and all
+ // higher-priority ones have failed, we abort all lower-priority ones.
+
+ _tryAll : function()
+ {
+ if (this._cancel)
+ return;
+ var me = this;
+ var timeout = Services.prefs.getIntPref("mailnews.auto_config.guess.timeout");
+ // We assume we'll resolve the same proxy for all tries, and
+ // proceed to use the first resolved proxy for all tries. This
+ // assumption is generally sound, but not always: mechanisms like
+ // the pref network.proxy.no_proxies_on can make imap.domain and
+ // pop.domain resolve differently.
+ doProxy(this._hostsToTry[0].hostname, function(proxy) {
+ for (let i = 0; i < me._hostsToTry.length; i++) {
+ let thisTry = me._hostsToTry[i]; // {HostTry}
+ if (thisTry.status != kNotTried)
+ continue;
+ me._log.info(thisTry.desc + ": initializing probe...");
+ if (i == 0) // showing 50 servers at once is pointless
+ me.mProgressCallback(thisTry);
+
+ thisTry.abortable = SocketUtil(
+ thisTry.hostname, thisTry.port, thisTry.ssl,
+ thisTry.commands, timeout, proxy,
+ new SSLErrorHandler(thisTry, me._log),
+ function(wiredata) // result callback
+ {
+ if (me._cancel)
+ return; // don't use response anymore
+ me.mProgressCallback(thisTry);
+ me._processResult(thisTry, wiredata);
+ me._checkFinished();
+ },
+ function(e) // error callback
+ {
+ if (me._cancel)
+ return; // who set cancel to true already called mErrorCallback()
+ me._log.warn(thisTry.desc + ": " + e);
+ thisTry.status = kFailed;
+ me._checkFinished();
+ }
+ );
+ thisTry.status = kOngoing;
+ }
+ });
+ },
+
+ /**
+ * @param thisTry {HostTry}
+ * @param wiredata {Array of {String}} what the server returned
+ * in response to our protocol chat
+ */
+ _processResult : function(thisTry, wiredata)
+ {
+ if (thisTry._gotCertError)
+ {
+ if (thisTry._gotCertError == Ci.nsICertOverrideService.ERROR_MISMATCH)
+ {
+ thisTry._gotCertError = 0;
+ thisTry.status = kFailed;
+ return;
+ }
+
+ if (thisTry._gotCertError == Ci.nsICertOverrideService.ERROR_UNTRUSTED ||
+ thisTry._gotCertError == Ci.nsICertOverrideService.ERROR_TIME)
+ {
+ this._log.info(thisTry.desc + ": TRYING AGAIN, hopefully with exception recorded");
+ thisTry._gotCertError = 0;
+ thisTry.selfSignedCert = true; // _next_ run gets this exception
+ thisTry.status = kNotTried; // try again (with exception)
+ this._tryAll();
+ return;
+ }
+ }
+
+ if (wiredata == null || wiredata === undefined)
+ {
+ this._log.info(thisTry.desc + ": no data");
+ thisTry.status = kFailed;
+ return;
+ }
+ this._log.info(thisTry.desc + ": wiredata: " + wiredata.join(""));
+ thisTry.authMethods =
+ this._advertisesAuthMethods(thisTry.protocol, wiredata);
+ if (thisTry.ssl == TLS && !this._hasTLS(thisTry, wiredata))
+ {
+ this._log.info(thisTry.desc + ": STARTTLS wanted, but not offered");
+ thisTry.status = kFailed;
+ return;
+ }
+ this._log.info(thisTry.desc + ": success" +
+ (thisTry.selfSignedCert ? " (selfSignedCert)" : ""));
+ thisTry.status = kSuccess;
+
+ if (thisTry.selfSignedCert) { // eh, ERROR_UNTRUSTED or ERROR_TIME
+ // We clear the temporary override now after success. If we clear it
+ // earlier we get into an infinite loop, probably because the cert
+ // remembering is temporary and the next try gets a new connection which
+ // isn't covered by that temporariness.
+ this._log.info(thisTry.desc + ": clearing validity override for " +
+ thisTry.hostname);
+ Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService)
+ .clearValidityOverride(thisTry.hostname, thisTry.port);
+ }
+ },
+
+ _checkFinished : function()
+ {
+ var successfulTry = null;
+ var successfulTryAlternative = null; // POP3
+ var unfinishedBusiness = false;
+ // this._hostsToTry is ordered by decreasing preference
+ for (let i = 0; i < this._hostsToTry.length; i++)
+ {
+ let thisTry = this._hostsToTry[i];
+ if (thisTry.status == kNotTried || thisTry.status == kOngoing)
+ unfinishedBusiness = true;
+ // thisTry is good, and all higher preference tries failed, so use this
+ else if (thisTry.status == kSuccess && !unfinishedBusiness)
+ {
+ if (!successfulTry)
+ {
+ successfulTry = thisTry;
+ if (successfulTry.protocol == SMTP)
+ break;
+ }
+ else if (successfulTry.protocol != thisTry.protocol)
+ {
+ successfulTryAlternative = thisTry;
+ break;
+ }
+ }
+ }
+ if (successfulTry && (successfulTryAlternative || !unfinishedBusiness))
+ {
+ this.mSuccessCallback(successfulTry,
+ successfulTryAlternative ? [ successfulTryAlternative ] : []);
+ this.cancel(new CancelOthersException());
+ }
+ else if (!unfinishedBusiness) // all failed
+ {
+ this._log.info("ran out of options");
+ var errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_find_server.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ // no need to cancel, all failed
+ }
+ // else let ongoing calls continue
+ },
+
+
+ /**
+ * Which auth mechanism the server claims to support.
+ * (That doesn't necessarily reflect reality, it is more an upper bound.)
+ *
+ * @param protocol {Integer-enum} IMAP, POP or SMTP
+ * @param capaResponse {Array of {String}} on the wire data
+ * that the server returned. May be the full exchange or just capa.
+ * @returns {Array of {Integer-enum} values for AccountConfig.incoming.auth
+ * (or outgoing), in decreasing order of preference.
+ * E.g. [ 5, 4 ] for a server that supports only Kerberos and
+ * encrypted passwords.
+ */
+ _advertisesAuthMethods : function(protocol, capaResponse)
+ {
+ // for imap, capabilities include e.g.:
+ // "AUTH=CRAM-MD5", "AUTH=NTLM", "AUTH=GSSAPI", "AUTH=MSN"
+ // for pop3, the auth mechanisms are returned in capa as the following:
+ // "CRAM-MD5", "NTLM", "MSN", "GSSAPI"
+ // For smtp, EHLO will return AUTH and then a list of the
+ // mechanism(s) supported, e.g.,
+ // AUTH LOGIN NTLM MSN CRAM-MD5 GSSAPI
+ var result = new Array();
+ var line = capaResponse.join("\n").toUpperCase();
+ var prefix = "";
+ if (protocol == POP)
+ prefix = "";
+ else if (protocol == IMAP)
+ prefix = "AUTH=";
+ else if (protocol == SMTP)
+ prefix = "AUTH.*";
+ else
+ throw NotReached("must pass protocol");
+ // add in decreasing order of preference
+ if (new RegExp(prefix + "GSSAPI").test(line))
+ result.push(Ci.nsMsgAuthMethod.GSSAPI);
+ if (new RegExp(prefix + "CRAM-MD5").test(line))
+ result.push(Ci.nsMsgAuthMethod.passwordEncrypted);
+ if (new RegExp(prefix + "(NTLM|MSN)").test(line))
+ result.push(Ci.nsMsgAuthMethod.NTLM);
+ if (protocol != IMAP || !line.includes("LOGINDISABLED"))
+ result.push(Ci.nsMsgAuthMethod.passwordCleartext);
+ return result;
+ },
+
+ _hasTLS : function(thisTry, wiredata)
+ {
+ var capa = thisTry.protocol == POP ? "STLS" : "STARTTLS";
+ return thisTry.ssl == TLS &&
+ wiredata.join("").toUpperCase().includes(capa);
+ },
+}
+
+/**
+ * @param authMethods @see return value of _advertisesAuthMethods()
+ * Note: the returned auth method will be removed from the array.
+ * @return one of them, the preferred one
+ * Note: this might be Kerberos, which might not actually work,
+ * so you might need to try the others, too.
+ */
+function chooseBestAuthMethod(authMethods)
+{
+ if (!authMethods || !authMethods.length)
+ return Ci.nsMsgAuthMethod.passwordCleartext;
+ return authMethods.shift(); // take first (= most preferred)
+}
+
+
+function IncomingHostDetector(
+ progressCallback, successCallback, errorCallback)
+{
+ HostDetector.call(this, progressCallback, successCallback, errorCallback);
+}
+IncomingHostDetector.prototype =
+{
+ __proto__: HostDetector.prototype,
+ _hostnamesToTry : function(protocol, domain)
+ {
+ var hostnamesToTry = [];
+ if (protocol != POP)
+ hostnamesToTry.push("imap." + domain);
+ if (protocol != IMAP)
+ {
+ hostnamesToTry.push("pop3." + domain);
+ hostnamesToTry.push("pop." + domain);
+ }
+ hostnamesToTry.push("mail." + domain);
+ hostnamesToTry.push(domain);
+ return hostnamesToTry;
+ },
+ _portsToTry : getIncomingTryOrder,
+}
+
+function OutgoingHostDetector(
+ progressCallback, successCallback, errorCallback)
+{
+ HostDetector.call(this, progressCallback, successCallback, errorCallback);
+}
+OutgoingHostDetector.prototype =
+{
+ __proto__: HostDetector.prototype,
+ _hostnamesToTry : function(protocol, domain)
+ {
+ var hostnamesToTry = [];
+ hostnamesToTry.push("smtp." + domain);
+ hostnamesToTry.push("mail." + domain);
+ hostnamesToTry.push(domain);
+ return hostnamesToTry;
+ },
+ _portsToTry : getOutgoingTryOrder,
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Encode protocol ports and order of preference
+
+// Protocol Types
+var UNKNOWN = -1;
+var IMAP = 0;
+var POP = 1;
+var SMTP = 2;
+// Security Types
+var NONE = 0; // no encryption
+//1 would be "TLS if available"
+var TLS = 2; // STARTTLS
+var SSL = 3; // SSL / TLS
+
+var IMAP_PORTS = {}
+IMAP_PORTS[NONE] = 143;
+IMAP_PORTS[TLS] = 143;
+IMAP_PORTS[SSL] = 993;
+
+var POP_PORTS = {}
+POP_PORTS[NONE] = 110;
+POP_PORTS[TLS] = 110;
+POP_PORTS[SSL] = 995;
+
+var SMTP_PORTS = {}
+SMTP_PORTS[NONE] = 587;
+SMTP_PORTS[TLS] = 587;
+SMTP_PORTS[SSL] = 465;
+
+var CMDS = {}
+CMDS[IMAP] = ["1 CAPABILITY\r\n", "2 LOGOUT\r\n"];
+CMDS[POP] = ["CAPA\r\n", "QUIT\r\n"];
+CMDS[SMTP] = ["EHLO we-guess.mozilla.org\r\n", "QUIT\r\n"];
+
+/**
+ * Sort by preference of SSL, IMAP etc.
+ * @param tries {Array of {HostTry}}
+ * @returns {Array of {HostTry}}
+ */
+function sortTriesByPreference(tries)
+{
+ return tries.sort(function __sortByPreference(a, b)
+ {
+ // -1 = a is better; 1 = b is better; 0 = equal
+ // Prefer SSL/TLS above all else
+ if (a.ssl != NONE && b.ssl == NONE)
+ return -1;
+ if (b.ssl != NONE && a.ssl == NONE)
+ return 1;
+ // Prefer IMAP over POP
+ if (a.protocol == IMAP && b.protocol == POP)
+ return -1;
+ if (b.protocol == IMAP && a.protocol == POP)
+ return 1;
+ // For hostnames, leave existing sorting, as in _hostnamesToTry()
+ // For ports, leave existing sorting, as in getOutgoingTryOrder()
+ return 0;
+ });
+};
+
+// TODO prefer SSL over STARTTLS,
+// either in sortTriesByPreference or in getIncomingTryOrder() (and outgoing)
+
+/**
+ * @returns {Array of {HostTry}}
+ */
+function getIncomingTryOrder(host, protocol, ssl, port)
+{
+ var lowerCaseHost = host.toLowerCase();
+
+ if (protocol == UNKNOWN &&
+ (lowerCaseHost.startsWith("pop.") || lowerCaseHost.startsWith("pop3.")))
+ protocol = POP;
+ else if (protocol == UNKNOWN && lowerCaseHost.startsWith("imap."))
+ protocol = IMAP;
+
+ if (protocol != UNKNOWN) {
+ if (ssl == UNKNOWN)
+ return [getHostEntry(protocol, TLS, port),
+ //getHostEntry(protocol, SSL, port),
+ getHostEntry(protocol, NONE, port)];
+ return [getHostEntry(protocol, ssl, port)];
+ }
+ if (ssl == UNKNOWN)
+ return [getHostEntry(IMAP, TLS, port),
+ //getHostEntry(IMAP, SSL, port),
+ getHostEntry(POP, TLS, port),
+ //getHostEntry(POP, SSL, port),
+ getHostEntry(IMAP, NONE, port),
+ getHostEntry(POP, NONE, port)];
+ return [getHostEntry(IMAP, ssl, port),
+ getHostEntry(POP, ssl, port)];
+};
+
+/**
+ * @returns {Array of {HostTry}}
+ */
+function getOutgoingTryOrder(host, protocol, ssl, port)
+{
+ assert(protocol == SMTP, "need SMTP as protocol for outgoing");
+ if (ssl == UNKNOWN)
+ {
+ if (port == UNKNOWN)
+ // neither SSL nor port known
+ return [getHostEntry(SMTP, TLS, UNKNOWN),
+ getHostEntry(SMTP, TLS, 25),
+ //getHostEntry(SMTP, SSL, UNKNOWN),
+ getHostEntry(SMTP, NONE, UNKNOWN),
+ getHostEntry(SMTP, NONE, 25)];
+ // port known, SSL not
+ return [getHostEntry(SMTP, TLS, port),
+ //getHostEntry(SMTP, SSL, port),
+ getHostEntry(SMTP, NONE, port)];
+ }
+ // SSL known, port not
+ if (port == UNKNOWN)
+ {
+ if (ssl == SSL)
+ return [getHostEntry(SMTP, SSL, UNKNOWN)];
+ else // TLS or NONE
+ return [getHostEntry(SMTP, ssl, UNKNOWN),
+ getHostEntry(SMTP, ssl, 25)];
+ }
+ // SSL and port known
+ return [getHostEntry(SMTP, ssl, port)];
+};
+
+/**
+ * @returns {HostTry} with proper default port and commands,
+ * but without hostname.
+ */
+function getHostEntry(protocol, ssl, port)
+{
+ if (!port || port == UNKNOWN) {
+ switch (protocol) {
+ case POP:
+ port = POP_PORTS[ssl];
+ break;
+ case IMAP:
+ port = IMAP_PORTS[ssl];
+ break;
+ case SMTP:
+ port = SMTP_PORTS[ssl];
+ break;
+ default:
+ throw new NotReached("unsupported protocol " + protocol);
+ }
+ }
+
+ var r = new HostTry();
+ r.protocol = protocol;
+ r.ssl = ssl;
+ r.port = port;
+ r.commands = CMDS[protocol];
+ return r;
+};
+
+
+// Convert consts from those used here to those from AccountConfig
+// TODO adjust consts to match AccountConfig
+
+// here -> AccountConfig
+function sslConvertToSocketType(ssl)
+{
+ if (ssl == NONE)
+ return 1;
+ if (ssl == SSL)
+ return 2;
+ if (ssl == TLS)
+ return 3;
+ throw new NotReached("unexpected SSL type");
+}
+
+// AccountConfig -> here
+function ConvertSocketTypeToSSL(socketType)
+{
+ if (socketType == 1)
+ return NONE;
+ if (socketType == 2)
+ return SSL;
+ if (socketType == 3)
+ return TLS;
+ return UNKNOWN;
+}
+
+// here -> AccountConfig
+function protocolToString(type)
+{
+ if (type == IMAP)
+ return "imap";
+ if (type == POP)
+ return "pop3";
+ if (type == SMTP)
+ return "smtp";
+ throw new NotReached("unexpected protocol");
+}
+
+
+
+/////////////////////////////////////////////////////////
+// SSL cert error handler
+
+/**
+ * Called by MyBadCertHandler.js, which called by PSM
+ * to tell us about SSL certificate errors.
+ * @param thisTry {HostTry}
+ * @param logger {Log4Moz logger}
+ */
+function SSLErrorHandler(thisTry, logger)
+{
+ this._try = thisTry;
+ this._log = logger;
+ // _ gotCertError will be set to an error code (one of those defined in
+ // nsICertOverrideService)
+ this._gotCertError = 0;
+}
+SSLErrorHandler.prototype =
+{
+ processCertError : function(socketInfo, status, targetSite)
+ {
+ this._log.error("Got Cert error for "+ targetSite);
+
+ if (!status)
+ return true;
+
+ let cert = status.QueryInterface(Ci.nsISSLStatus).serverCert;
+ let flags = 0;
+
+ let parts = targetSite.split(":");
+ let host = parts[0];
+ let port = parts[1];
+
+ /* The following 2 cert problems are unfortunately common:
+ * 1) hostname mismatch:
+ * user is custeromer at a domain hoster, he owns yourname.org,
+ * and the IMAP server is imap.hoster.com (but also reachable as
+ * imap.yourname.org), and has a cert for imap.hoster.com.
+ * 2) self-signed:
+ * a company has an internal IMAP server, and it's only for
+ * 30 employees, and they didn't want to buy a cert, so
+ * they use a self-signed cert.
+ *
+ * We would like the above to pass, somehow, with user confirmation.
+ * The following case should *not* pass:
+ *
+ * 1) MITM
+ * User has @gmail.com, and an attacker is between the user and
+ * the Internet and runs a man-in-the-middle (MITM) attack.
+ * Attacker controls DNS and sends imap.gmail.com to his own
+ * imap.attacker.com. He has either a valid, CA-issued
+ * cert for imap.attacker.com, or a self-signed cert.
+ * Of course, attacker.com could also be legit-sounding gmailservers.com.
+ *
+ * What makes it dangerous is that we (!) propose the server to the user,
+ * and he cannot judge whether imap.gmailservers.com is correct or not,
+ * and he will likely approve it.
+ */
+
+ if (status.isDomainMismatch) {
+ this._try._gotCertError = Ci.nsICertOverrideService.ERROR_MISMATCH;
+ flags |= Ci.nsICertOverrideService.ERROR_MISMATCH;
+ }
+ else if (status.isUntrusted) { // e.g. self-signed
+ this._try._gotCertError = Ci.nsICertOverrideService.ERROR_UNTRUSTED;
+ flags |= Ci.nsICertOverrideService.ERROR_UNTRUSTED;
+ }
+ else if (status.isNotValidAtThisTime) {
+ this._try._gotCertError = Ci.nsICertOverrideService.ERROR_TIME;
+ flags |= Ci.nsICertOverrideService.ERROR_TIME;
+ }
+ else {
+ this._try._gotCertError = -1; // other
+ }
+
+ /* We will add a temporary cert exception here, so that
+ * we can continue and connect and try.
+ * But we will remove it again as soon as we close the
+ * connection, in _processResult().
+ * _gotCertError will serve as the marker that we
+ * have to clear the override later.
+ *
+ * In verifyConfig(), before we send the password, we *must*
+ * get another cert exception, this time with dialog to the user
+ * so that he gets informed about this and can make a choice.
+ */
+
+ this._try.targetSite = targetSite;
+ Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService)
+ .rememberValidityOverride(host, port, cert, flags,
+ true); // temporary override
+ this._log.warn("!! Overrode bad cert temporarily " + host + " " + port +
+ " flags=" + flags + "\n");
+ return true;
+ },
+}
+
+
+
+//////////////////////////////////////////////////////////////////
+// Socket Util
+
+
+/**
+ * @param hostname {String} The DNS hostname to connect to.
+ * @param port {Integer} The numberic port to connect to on the host.
+ * @param ssl {Integer} SSL, TLS or NONE
+ * @param commands {Array of String}: protocol commands
+ * to send to the server.
+ * @param timeout {Integer} seconds to wait for a server response, then cancel.
+ * @param proxy {nsIProxyInfo} The proxy to use (or null to not use any).
+ * @param sslErrorHandler {SSLErrorHandler}
+ * @param resultCallback {function(wiredata)} This function will
+ * be called with the result string array from the server
+ * or null if no communication occurred.
+ * @param errorCallback {function(e)}
+ */
+function SocketUtil(hostname, port, ssl, commands, timeout, proxy,
+ sslErrorHandler, resultCallback, errorCallback)
+{
+ assert(commands && commands.length, "need commands");
+
+ var index = 0; // commands[index] is next to send to server
+ var initialized = false;
+ var aborted = false;
+
+ function _error(e)
+ {
+ if (aborted)
+ return;
+ aborted = true;
+ errorCallback(e);
+ }
+
+ function timeoutFunc()
+ {
+ if (!initialized)
+ _error("timeout");
+ }
+
+ // In case DNS takes too long or does not resolve or another blocking
+ // issue occurs before the timeout can be set on the socket, this
+ // ensures that the listener callback will be fired in a timely manner.
+ // XXX There might to be some clean up needed after the timeout is fired
+ // for socket and io resources.
+
+ // The timeout value plus 2 seconds
+ setTimeout(timeoutFunc, (timeout * 1000) + 2000);
+
+ var transportService = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+
+ // @see NS_NETWORK_SOCKET_CONTRACTID_PREFIX
+ var socketTypeName = ssl == SSL ? "ssl" : (ssl == TLS ? "starttls" : null);
+ var transport = transportService.createTransport([socketTypeName],
+ ssl == NONE ? 0 : 1,
+ hostname, port, proxy);
+
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, timeout);
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, timeout);
+ try {
+ transport.securityCallbacks = new BadCertHandler(sslErrorHandler);
+ } catch (e) {
+ _error(e);
+ }
+ var outstream = transport.openOutputStream(0, 0, 0);
+ var stream = transport.openInputStream(0, 0, 0);
+ var instream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ instream.init(stream);
+
+ var dataListener =
+ {
+ data : new Array(),
+ onStartRequest: function(request, context)
+ {
+ try {
+ initialized = true;
+ if (!aborted)
+ {
+ // Send the first request
+ let outputData = commands[index++];
+ outstream.write(outputData, outputData.length);
+ }
+ } catch (e) { _error(e); }
+ },
+ onStopRequest: function(request, context, status)
+ {
+ try {
+ instream.close();
+ outstream.close();
+ resultCallback(this.data.length ? this.data : null);
+ } catch (e) { _error(e); }
+ },
+ onDataAvailable: function(request, context, inputStream, offset, count)
+ {
+ try {
+ if (!aborted)
+ {
+ let inputData = instream.read(count);
+ this.data.push(inputData);
+ if (index < commands.length)
+ {
+ // Send the next request to the server.
+ let outputData = commands[index++];
+ outstream.write(outputData, outputData.length);
+ }
+ }
+ } catch (e) { _error(e); }
+ }
+ };
+
+ try {
+ var pump = Cc["@mozilla.org/network/input-stream-pump;1"]
+ .createInstance(Ci.nsIInputStreamPump);
+
+ pump.init(stream, -1, -1, 0, 0, false);
+ pump.asyncRead(dataListener, null);
+ return new SocketAbortable(transport);
+ } catch (e) { _error(e); }
+ return null;
+}
+
+function SocketAbortable(transport)
+{
+ Abortable.call(this);
+ assert(transport instanceof Ci.nsITransport, "need transport");
+ this._transport = transport;
+}
+SocketAbortable.prototype = Object.create(Abortable.prototype);
+SocketAbortable.prototype.constructor = UserCancelledException;
+SocketAbortable.prototype.cancel = function(ex)
+{
+ try {
+ this._transport.close(Components.results.NS_ERROR_ABORT);
+ } catch (e) {
+ ddump("canceling socket failed: " + e);
+ }
+}
+
+/**
+ * Resolve a proxy for some domain and expose it via a callback.
+ *
+ * @param hostname {String} The hostname which a proxy will be resolved for
+ * @param resultCallback {function(proxyInfo)}
+ * Called after the proxy has been resolved for hostname.
+ * proxy {nsIProxyInfo} The resolved proxy, or null if none were found
+ * for hostname
+ */
+function doProxy(hostname, resultCallback) {
+ // This implements the nsIProtocolProxyCallback interface:
+ function ProxyResolveCallback() { }
+ ProxyResolveCallback.prototype = {
+ onProxyAvailable(req, uri, proxy, status) {
+ // Anything but a SOCKS proxy will be unusable for email.
+ if (proxy != null && proxy.type != "socks" &&
+ proxy.type != "socks4") {
+ proxy = null;
+ }
+ resultCallback(proxy);
+ },
+ };
+ var proxyService = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService(Ci.nsIProtocolProxyService);
+ // Use some arbitrary scheme just because it is required...
+ var uri = Services.io.newURI("http://" + hostname);
+ // ... we'll ignore it any way. We prefer SOCKS since that's the
+ // only thing we can use for email protocols.
+ var proxyFlags = Ci.nsIProtocolProxyService.RESOLVE_IGNORE_URI_SCHEME |
+ Ci.nsIProtocolProxyService.RESOLVE_PREFER_SOCKS_PROXY;
+ if (Services.prefs.getBoolPref("network.proxy.socks_remote_dns")) {
+ proxyFlags |= Ci.nsIProtocolProxyService.RESOLVE_ALWAYS_TUNNEL;
+ }
+ proxyService.asyncResolve(uri, proxyFlags, new ProxyResolveCallback());
+}
diff --git a/mailnews/base/prefs/content/accountcreation/readFromXML.js b/mailnews/base/prefs/content/accountcreation/readFromXML.js
new file mode 100644
index 000000000..4ef13ec1d
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/readFromXML.js
@@ -0,0 +1,243 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Takes an XML snipplet (as JXON) and reads the values into
+ * a new AccountConfig object.
+ * It does so securely (or tries to), by trying to avoid remote execution
+ * and similar holes which can appear when reading too naively.
+ * Of course it cannot tell whether the actual values are correct,
+ * e.g. it can't tell whether the host name is a good server.
+ *
+ * The XML format is documented at
+ * <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>
+ *
+ * @param clientConfigXML {JXON} The <clientConfig> node.
+ * @return AccountConfig object filled with the data from XML
+ */
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+
+function readFromXML(clientConfigXML)
+{
+ function array_or_undef(value) {
+ return value === undefined ? [] : value;
+ }
+ var exception;
+ if (typeof(clientConfigXML) != "object" ||
+ !("clientConfig" in clientConfigXML) ||
+ !("emailProvider" in clientConfigXML.clientConfig))
+ {
+ dump("client config xml = " + JSON.stringify(clientConfigXML) + "\n");
+ var stringBundle = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties");
+ throw stringBundle.GetStringFromName("no_emailProvider.error");
+ }
+ var xml = clientConfigXML.clientConfig.emailProvider;
+
+ var d = new AccountConfig();
+ d.source = AccountConfig.kSourceXML;
+
+ d.id = sanitize.hostname(xml["@id"]);
+ d.displayName = d.id;
+ try {
+ d.displayName = sanitize.label(xml.displayName);
+ } catch (e) { logException(e); }
+ for (var domain of xml.$domain)
+ {
+ try {
+ d.domains.push(sanitize.hostname(domain));
+ } catch (e) { logException(e); exception = e; }
+ }
+ if (d.domains.length == 0)
+ throw exception ? exception : "need proper <domain> in XML";
+ exception = null;
+
+ // incoming server
+ for (let iX of array_or_undef(xml.$incomingServer)) // input (XML)
+ {
+ let iO = d.createNewIncoming(); // output (object)
+ try {
+ // throws if not supported
+ iO.type = sanitize.enum(iX["@type"], ["pop3", "imap", "nntp"]);
+ iO.hostname = sanitize.hostname(iX.hostname);
+ iO.port = sanitize.integerRange(iX.port, kMinPort, kMaxPort);
+ // We need a username even for Kerberos, need it even internally.
+ iO.username = sanitize.string(iX.username); // may be a %VARIABLE%
+
+ if ("password" in iX) {
+ d.rememberPassword = true;
+ iO.password = sanitize.string(iX.password);
+ }
+
+ for (let iXsocketType of array_or_undef(iX.$socketType))
+ {
+ try {
+ iO.socketType = sanitize.translate(iXsocketType,
+ { plain : 1, SSL: 2, STARTTLS: 3 });
+ break; // take first that we support
+ } catch (e) { exception = e; }
+ }
+ if (!iO.socketType)
+ throw exception ? exception : "need proper <socketType> in XML";
+ exception = null;
+
+ for (let iXauth of array_or_undef(iX.$authentication))
+ {
+ try {
+ iO.auth = sanitize.translate(iXauth,
+ { "password-cleartext" : Ci.nsMsgAuthMethod.passwordCleartext,
+ // @deprecated TODO remove
+ "plain" : Ci.nsMsgAuthMethod.passwordCleartext,
+ "password-encrypted" : Ci.nsMsgAuthMethod.passwordEncrypted,
+ // @deprecated TODO remove
+ "secure" : Ci.nsMsgAuthMethod.passwordEncrypted,
+ "GSSAPI" : Ci.nsMsgAuthMethod.GSSAPI,
+ "NTLM" : Ci.nsMsgAuthMethod.NTLM,
+#ifdef MOZ_MAILNEWS_OAUTH2
+ "OAuth2" : Ci.nsMsgAuthMethod.OAuth2
+#endif
+ });
+ break; // take first that we support
+ } catch (e) { exception = e; }
+ }
+ if (!iO.auth)
+ throw exception ? exception : "need proper <authentication> in XML";
+ exception = null;
+
+ // defaults are in accountConfig.js
+ if (iO.type == "pop3" && "pop3" in iX)
+ {
+ try {
+ if ("leaveMessagesOnServer" in iX.pop3)
+ iO.leaveMessagesOnServer =
+ sanitize.boolean(iX.pop3.leaveMessagesOnServer);
+ if ("daysToLeaveMessagesOnServer" in iX.pop3)
+ iO.daysToLeaveMessagesOnServer =
+ sanitize.integer(iX.pop3.daysToLeaveMessagesOnServer);
+ } catch (e) { logException(e); }
+ try {
+ if ("downloadOnBiff" in iX.pop3)
+ iO.downloadOnBiff = sanitize.boolean(iX.pop3.downloadOnBiff);
+ } catch (e) { logException(e); }
+ }
+
+ // processed successfully, now add to result object
+ if (!d.incoming.hostname) // first valid
+ d.incoming = iO;
+ else
+ d.incomingAlternatives.push(iO);
+ } catch (e) { exception = e; }
+ }
+ if (!d.incoming.hostname)
+ // throw exception for last server
+ throw exception ? exception : "Need proper <incomingServer> in XML file";
+ exception = null;
+
+ // outgoing server
+ for (let oX of array_or_undef(xml.$outgoingServer)) // input (XML)
+ {
+ let oO = d.createNewOutgoing(); // output (object)
+ try {
+ if (oX["@type"] != "smtp")
+ {
+ var stringBundle = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties");
+ throw stringBundle.GetStringFromName("outgoing_not_smtp.error");
+ }
+ oO.hostname = sanitize.hostname(oX.hostname);
+ oO.port = sanitize.integerRange(oX.port, kMinPort, kMaxPort);
+
+ for (let oXsocketType of array_or_undef(oX.$socketType))
+ {
+ try {
+ oO.socketType = sanitize.translate(oXsocketType,
+ { plain : 1, SSL: 2, STARTTLS: 3 });
+ break; // take first that we support
+ } catch (e) { exception = e; }
+ }
+ if (!oO.socketType)
+ throw exception ? exception : "need proper <socketType> in XML";
+ exception = null;
+
+ for (let oXauth of array_or_undef(oX.$authentication))
+ {
+ try {
+ oO.auth = sanitize.translate(oXauth,
+ { // open relay
+ "none" : Ci.nsMsgAuthMethod.none,
+ // inside ISP or corp network
+ "client-IP-address" : Ci.nsMsgAuthMethod.none,
+ // hope for the best
+ "smtp-after-pop" : Ci.nsMsgAuthMethod.none,
+ "password-cleartext" : Ci.nsMsgAuthMethod.passwordCleartext,
+ // @deprecated TODO remove
+ "plain" : Ci.nsMsgAuthMethod.passwordCleartext,
+ "password-encrypted" : Ci.nsMsgAuthMethod.passwordEncrypted,
+ // @deprecated TODO remove
+ "secure" : Ci.nsMsgAuthMethod.passwordEncrypted,
+ "GSSAPI" : Ci.nsMsgAuthMethod.GSSAPI,
+ "NTLM" : Ci.nsMsgAuthMethod.NTLM,
+#ifdef MOZ_MAILNEWS_OAUTH2
+ "OAuth2" : Ci.nsMsgAuthMethod.OAuth2,
+#endif
+ });
+
+ break; // take first that we support
+ } catch (e) { exception = e; }
+ }
+ if (!oO.auth)
+ throw exception ? exception : "need proper <authentication> in XML";
+ exception = null;
+
+ if ("username" in oX ||
+ // if password-based auth, we need a username,
+ // so go there anyways and throw.
+ oO.auth == Ci.nsMsgAuthMethod.passwordCleartext ||
+ oO.auth == Ci.nsMsgAuthMethod.passwordEncrypted)
+ oO.username = sanitize.string(oX.username);
+
+ if ("password" in oX) {
+ d.rememberPassword = true;
+ oO.password = sanitize.string(oX.password);
+ }
+
+ try {
+ // defaults are in accountConfig.js
+ if ("addThisServer" in oX)
+ oO.addThisServer = sanitize.boolean(oX.addThisServer);
+ if ("useGlobalPreferredServer" in oX)
+ oO.useGlobalPreferredServer =
+ sanitize.boolean(oX.useGlobalPreferredServer);
+ } catch (e) { logException(e); }
+
+ // processed successfully, now add to result object
+ if (!d.outgoing.hostname) // first valid
+ d.outgoing = oO;
+ else
+ d.outgoingAlternatives.push(oO);
+ } catch (e) { logException(e); exception = e; }
+ }
+ if (!d.outgoing.hostname)
+ // throw exception for last server
+ throw exception ? exception : "Need proper <outgoingServer> in XML file";
+ exception = null;
+
+ d.inputFields = new Array();
+ for (let inputField of array_or_undef(xml.$inputField))
+ {
+ try {
+ var fieldset =
+ {
+ varname : sanitize.alphanumdash(inputField["@key"]).toUpperCase(),
+ displayName : sanitize.label(inputField["@label"]),
+ exampleValue : sanitize.label(inputField.value)
+ };
+ d.inputFields.push(fieldset);
+ } catch (e) { logException(e); } // for now, don't throw,
+ // because we don't support custom fields yet anyways.
+ }
+
+ return d;
+}
diff --git a/mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js b/mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js
new file mode 100644
index 000000000..3c927a1af
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js
@@ -0,0 +1,205 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is a generic input validation lib. Use it when you process
+ * data from the network.
+ *
+ * Just a few functions which verify, for security purposes, that the
+ * input variables (strings, if nothing else is noted) are of the expected
+ * type and syntax.
+ *
+ * The functions take a string (unless noted otherwise) and return
+ * the expected datatype in JS types. If the value is not as expected,
+ * they throw exceptions.
+ */
+
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+
+var sanitize =
+{
+ integer : function(unchecked)
+ {
+ if (typeof(unchecked) == "number" && !isNaN(unchecked))
+ return unchecked;
+
+ var r = parseInt(unchecked);
+ if (isNaN(r))
+ throw new MalformedException("no_number.error", unchecked);
+
+ return r;
+ },
+
+ integerRange : function(unchecked, min, max)
+ {
+ var int = this.integer(unchecked);
+ if (int < min)
+ throw new MalformedException("number_too_small.error", unchecked);
+
+ if (int > max)
+ throw new MalformedException("number_too_large.error", unchecked);
+
+ return int;
+ },
+
+ boolean : function(unchecked)
+ {
+ if (typeof(unchecked) == "boolean")
+ return unchecked;
+
+ if (unchecked == "true")
+ return true;
+
+ if (unchecked == "false")
+ return false;
+
+ throw new MalformedException("boolean.error", unchecked);
+ },
+
+ string : function(unchecked)
+ {
+ return String(unchecked);
+ },
+
+ nonemptystring : function(unchecked)
+ {
+ if (!unchecked)
+ throw new MalformedException("string_empty.error", unchecked);
+
+ return this.string(unchecked);
+ },
+
+ /**
+ * Allow only letters, numbers, "-" and "_".
+ *
+ * Empty strings not allowed (good idea?).
+ */
+ alphanumdash : function(unchecked)
+ {
+ var str = this.nonemptystring(unchecked);
+ if (!/^[a-zA-Z0-9\-\_]*$/.test(str))
+ throw new MalformedException("alphanumdash.error", unchecked);
+
+ return str;
+ },
+
+ /**
+ * DNS hostnames like foo.bar.example.com
+ * Allow only letters, numbers, "-" and "."
+ * Empty strings not allowed.
+ * Currently does not support IDN (international domain names).
+ */
+ hostname : function(unchecked)
+ {
+ let str = cleanUpHostName(this.nonemptystring(unchecked));
+
+ // Allow placeholders. TODO move to a new hostnameOrPlaceholder()
+ // The regex is "anything, followed by one or more (placeholders than
+ // anything)". This doesn't catch the non-placeholder case, but that's
+ // handled down below.
+ if (/^[a-zA-Z0-9\-\.]*(%[A-Z0-9]+%[a-zA-Z0-9\-\.]*)+$/.test(str))
+ return str;
+
+ if (!isLegalHostNameOrIP(str))
+ throw new MalformedException("hostname_syntax.error", unchecked);
+
+ return str.toLowerCase();
+ },
+ /**
+ * A non-chrome URL that's safe to request.
+ */
+ url : function (unchecked)
+ {
+ var str = this.string(unchecked);
+ if (!str.startsWith("http") && !str.startsWith("https"))
+ throw new MalformedException("url_scheme.error", unchecked);
+
+ var uri;
+ try {
+ uri = Services.io.newURI(str, null, null);
+ uri = uri.QueryInterface(Ci.nsIURL);
+ } catch (e) {
+ throw new MalformedException("url_parsing.error", unchecked);
+ }
+
+ if (uri.scheme != "http" && uri.scheme != "https")
+ throw new MalformedException("url_scheme.error", unchecked);
+
+ return uri.spec;
+ },
+
+ /**
+ * A value which should be shown to the user in the UI as label
+ */
+ label : function(unchecked)
+ {
+ return this.string(unchecked);
+ },
+
+ /**
+ * Allows only certain values as input, otherwise throw.
+ *
+ * @param unchecked {Any} The value to check
+ * @param allowedValues {Array} List of values that |unchecked| may have.
+ * @param defaultValue {Any} (Optional) If |unchecked| does not match
+ * anything in |mapping|, a |defaultValue| can be returned instead of
+ * throwing an exception. The latter is the default and happens when
+ * no |defaultValue| is passed.
+ * @throws MalformedException
+ */
+ enum : function(unchecked, allowedValues, defaultValue)
+ {
+ for (let allowedValue of allowedValues)
+ {
+ if (allowedValue == unchecked)
+ return allowedValue;
+ }
+ // value is bad
+ if (typeof(defaultValue) == "undefined")
+ throw new MalformedException("allowed_value.error", unchecked);
+ return defaultValue;
+ },
+
+ /**
+ * Like enum, allows only certain (string) values as input, but allows the
+ * caller to specify another value to return instead of the input value. E.g.,
+ * if unchecked == "foo", return 1, if unchecked == "bar", return 2,
+ * otherwise throw. This allows to translate string enums into integer enums.
+ *
+ * @param unchecked {Any} The value to check
+ * @param mapping {Object} Associative array. property name is the input
+ * value, property value is the output value. E.g. the example above
+ * would be: { foo: 1, bar : 2 }.
+ * Use quotes when you need freaky characters: "baz-" : 3.
+ * @param defaultValue {Any} (Optional) If |unchecked| does not match
+ * anything in |mapping|, a |defaultValue| can be returned instead of
+ * throwing an exception. The latter is the default and happens when
+ * no |defaultValue| is passed.
+ * @throws MalformedException
+ */
+ translate : function(unchecked, mapping, defaultValue)
+ {
+ for (var inputValue in mapping)
+ {
+ if (inputValue == unchecked)
+ return mapping[inputValue];
+ }
+ // value is bad
+ if (typeof(defaultValue) == "undefined")
+ throw new MalformedException("allowed_value.error", unchecked);
+ return defaultValue;
+ }
+};
+
+function MalformedException(msgID, uncheckedBadValue)
+{
+ var stringBundle = getStringBundle(
+ "chrome://messenger/locale/accountCreationUtil.properties");
+ var msg = stringBundle.GetStringFromName(msgID);
+ Exception.call(this, msg);
+}
+MalformedException.prototype = Object.create(Exception.prototype);
+MalformedException.prototype.constructor = MalformedException;
+
diff --git a/mailnews/base/prefs/content/accountcreation/util.js b/mailnews/base/prefs/content/accountcreation/util.js
new file mode 100644
index 000000000..44c22ac45
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/util.js
@@ -0,0 +1,304 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/**
+ * Some common, generic functions
+ */
+
+try {
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+} catch (e) { ddump(e); } // if already declared, as in xpcshell-tests
+try {
+ var Cu = Components.utils;
+} catch (e) { ddump(e); }
+
+Cu.import("resource:///modules/errUtils.js");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function assert(test, errorMsg)
+{
+ if (!test)
+ throw new NotReached(errorMsg ? errorMsg :
+ "Programming bug. Assertion failed, see log.");
+}
+
+function makeCallback(obj, func)
+{
+ return function()
+ {
+ return func.apply(obj, arguments);
+ }
+}
+
+
+/**
+ * Runs the given function sometime later
+ *
+ * Currently implemented using setTimeout(), but
+ * can later be replaced with an nsITimer impl,
+ * when code wants to use it in a module.
+ */
+function runAsync(func)
+{
+ setTimeout(func, 0);
+}
+
+
+/**
+ * @param uriStr {String}
+ * @result {nsIURI}
+ */
+function makeNSIURI(uriStr)
+{
+ return Services.io.newURI(uriStr, null, null);
+}
+
+
+/**
+ * Reads UTF8 data from a URL.
+ *
+ * @param uri {nsIURI} what you want to read
+ * @return {Array of String} the contents of the file, one string per line
+ */
+function readURLasUTF8(uri)
+{
+ assert(uri instanceof Ci.nsIURI, "uri must be an nsIURI");
+ try {
+ let chan = Services.io.newChannelFromURI2(uri,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ let is = Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Ci.nsIConverterInputStream);
+ is.init(chan.open(), "UTF-8", 1024,
+ Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+
+ let content = "";
+ let strOut = new Object();
+ try {
+ while (is.readString(1024, strOut) != 0)
+ content += strOut.value;
+ // catch in outer try/catch
+ } finally {
+ is.close();
+ }
+
+ return content;
+ } catch (e) {
+ // TODO this has a numeric error message. We need to ship translations
+ // into human language.
+ throw e;
+ }
+}
+
+/**
+ * Takes a string (which is typically the content of a file,
+ * e.g. the result returned from readURLUTF8() ), and splits
+ * it into lines, and returns an array with one string per line
+ *
+ * Linebreaks are not contained in the result,,
+ * and all of \r\n, (Windows) \r (Mac) and \n (Unix) counts as linebreak.
+ *
+ * @param content {String} one long string with the whole file
+ * @return {Array of String} one string per line (no linebreaks)
+ */
+function splitLines(content)
+{
+ content = content.replace("\r\n", "\n");
+ content = content.replace("\r", "\n");
+ return content.split("\n");
+}
+
+/**
+ * @param bundleURI {String} chrome URL to properties file
+ * @return nsIStringBundle
+ */
+function getStringBundle(bundleURI)
+{
+ try {
+ return Services.strings.createBundle(bundleURI);
+ } catch (e) {
+ throw new Exception("Failed to get stringbundle URI <" + bundleURI +
+ ">. Error: " + e);
+ }
+}
+
+
+function Exception(msg)
+{
+ this._message = msg;
+
+ // get stack
+ try {
+ not.found.here += 1; // force a native exception ...
+ } catch (e) {
+ this.stack = e.stack; // ... to get the current stack
+ }
+}
+Exception.prototype =
+{
+ get message()
+ {
+ return this._message;
+ },
+ toString : function()
+ {
+ return this._message;
+ }
+}
+
+function NotReached(msg)
+{
+ Exception.call(this, msg); // call super constructor
+ logException(this);
+}
+// Make NotReached extend Exception.
+NotReached.prototype = Object.create(Exception.prototype);
+NotReached.prototype.constructor = NotReached;
+
+/**
+ * A handle for an async function which you can cancel.
+ * The async function will return an object of this type (a subtype)
+ * and you can call cancel() when you feel like killing the function.
+ */
+function Abortable()
+{
+}
+Abortable.prototype =
+{
+ cancel : function()
+ {
+ }
+}
+
+/**
+ * Utility implementation, for allowing to abort a setTimeout.
+ * Use like: return new TimeoutAbortable(setTimeout(function(){ ... }, 0));
+ * @param setTimeoutID {Integer} Return value of setTimeout()
+ */
+function TimeoutAbortable(setTimeoutID)
+{
+ Abortable.call(this, setTimeoutID); // call super constructor
+ this._id = setTimeoutID;
+}
+TimeoutAbortable.prototype = Object.create(Abortable.prototype);
+TimeoutAbortable.prototype.constructor = TimeoutAbortable;
+TimeoutAbortable.prototype.cancel = function() { clearTimeout(this._id); }
+
+/**
+ * Utility implementation, for allowing to abort a setTimeout.
+ * Use like: return new TimeoutAbortable(setTimeout(function(){ ... }, 0));
+ * @param setIntervalID {Integer} Return value of setInterval()
+ */
+function IntervalAbortable(setIntervalID)
+{
+ Abortable.call(this, setIntervalID); // call super constructor
+ this._id = setIntervalID;
+}
+IntervalAbortable.prototype = Object.create(Abortable.prototype);
+IntervalAbortable.prototype.constructor = IntervalAbortable;
+IntervalAbortable.prototype.cancel = function() { clearInterval(this._id); }
+
+// Allows you to make several network calls, but return
+// only one Abortable object.
+function SuccessiveAbortable()
+{
+ Abortable.call(this); // call super constructor
+ this._current = null;
+}
+SuccessiveAbortable.prototype = {
+ __proto__: Abortable.prototype,
+ get current() { return this._current; },
+ set current(abortable)
+ {
+ assert(abortable instanceof Abortable || abortable == null,
+ "need an Abortable object (or null)");
+ this._current = abortable;
+ },
+ cancel: function()
+ {
+ if (this._current)
+ this._current.cancel();
+ }
+}
+
+function deepCopy(org)
+{
+ if (typeof(org) == "undefined")
+ return undefined;
+ if (org == null)
+ return null;
+ if (typeof(org) == "string")
+ return org;
+ if (typeof(org) == "number")
+ return org;
+ if (typeof(org) == "boolean")
+ return org == true;
+ if (typeof(org) == "function")
+ return org;
+ if (typeof(org) != "object")
+ throw "can't copy objects of type " + typeof(org) + " yet";
+
+ //TODO still instanceof org != instanceof copy
+ //var result = new org.constructor();
+ var result = new Object();
+ if (typeof(org.length) != "undefined")
+ var result = new Array();
+ for (var prop in org)
+ result[prop] = deepCopy(org[prop]);
+ return result;
+}
+
+if (typeof gEmailWizardLogger == "undefined") {
+ Cu.import("resource:///modules/gloda/log4moz.js");
+ var gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
+}
+function ddump(text)
+{
+ gEmailWizardLogger.info(text);
+}
+
+function debugObject(obj, name, maxDepth, curDepth)
+{
+ if (curDepth == undefined)
+ curDepth = 0;
+ if (maxDepth != undefined && curDepth > maxDepth)
+ return "";
+
+ var result = "";
+ var i = 0;
+ for (let prop in obj)
+ {
+ i++;
+ try {
+ if (typeof(obj[prop]) == "object")
+ {
+ if (obj[prop] && obj[prop].length != undefined)
+ result += name + "." + prop + "=[probably array, length " +
+ obj[prop].length + "]\n";
+ else
+ result += name + "." + prop + "=[" + typeof(obj[prop]) + "]\n";
+ result += debugObject(obj[prop], name + "." + prop,
+ maxDepth, curDepth + 1);
+ }
+ else if (typeof(obj[prop]) == "function")
+ result += name + "." + prop + "=[function]\n";
+ else
+ result += name + "." + prop + "=" + obj[prop] + "\n";
+ } catch (e) {
+ result += name + "." + prop + "-> Exception(" + e + ")\n";
+ }
+ }
+ if (!i)
+ result += name + " is empty\n";
+ return result;
+}
+
+function alertPrompt(alertTitle, alertMsg)
+{
+ Services.prompt.alert(window, alertTitle, alertMsg);
+}
diff --git a/mailnews/base/prefs/content/accountcreation/verifyConfig.js b/mailnews/base/prefs/content/accountcreation/verifyConfig.js
new file mode 100644
index 000000000..073487ba9
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/verifyConfig.js
@@ -0,0 +1,353 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This checks a given config, by trying a real connection and login,
+ * with username and password.
+ *
+ * TODO
+ * - give specific errors, bug 555448
+ * - return a working |Abortable| to allow cancel
+ *
+ * @param accountConfig {AccountConfig} The guessed account config.
+ * username, password, realname, emailaddress etc. are not filled out,
+ * but placeholders to be filled out via replaceVariables().
+ * @param alter {boolean}
+ * Try other usernames and login schemes, until login works.
+ * Warning: Modifies |accountConfig|.
+ *
+ * This function is async.
+ * @param successCallback function(accountConfig)
+ * Called when we could guess the config.
+ * For accountConfig, see below.
+ * @param errorCallback function(ex)
+ * Called when we could guess not the config, either
+ * because we have not found anything or
+ * because there was an error (e.g. no network connection).
+ * The ex.message will contain a user-presentable message.
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+#ifdef MOZ_MAILNEWS_OAUTH2
+Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
+#endif
+
+if (typeof gEmailWizardLogger == "undefined") {
+ Cu.import("resource:///modules/gloda/log4moz.js");
+ var gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
+}
+
+function verifyConfig(config, alter, msgWindow, successCallback, errorCallback)
+{
+ ddump(debugObject(config, "config", 3));
+ assert(config instanceof AccountConfig,
+ "BUG: Arg 'config' needs to be an AccountConfig object");
+ assert(typeof(alter) == "boolean");
+ assert(typeof(successCallback) == "function");
+ assert(typeof(errorCallback) == "function");
+
+ if (MailServices.accounts.findRealServer(config.incoming.username,
+ config.incoming.hostname,
+ sanitize.enum(config.incoming.type,
+ ["pop3", "imap", "nntp"]),
+ config.incoming.port)) {
+ errorCallback("Incoming server exists");
+ return;
+ }
+
+ // incoming server
+ let inServer =
+ MailServices.accounts.createIncomingServer(config.incoming.username,
+ config.incoming.hostname,
+ sanitize.enum(config.incoming.type,
+ ["pop3", "imap", "nntp"]));
+ inServer.port = config.incoming.port;
+ inServer.password = config.incoming.password;
+ if (config.incoming.socketType == 1) // plain
+ inServer.socketType = Ci.nsMsgSocketType.plain;
+ else if (config.incoming.socketType == 2) // SSL
+ inServer.socketType = Ci.nsMsgSocketType.SSL;
+ else if (config.incoming.socketType == 3) // STARTTLS
+ inServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+
+ gEmailWizardLogger.info("Setting incoming server authMethod to " +
+ config.incoming.auth);
+ inServer.authMethod = config.incoming.auth;
+
+ try {
+#ifdef MOZ_MAILNEWS_OAUTH2
+ // Lookup issuer if needed.
+ if (config.incoming.auth == Ci.nsMsgAuthMethod.OAuth2 ||
+ config.outgoing.auth == Ci.nsMsgAuthMethod.OAuth2) {
+ if (!config.oauthSettings)
+ config.oauthSettings = {};
+ if (!config.oauthSettings.issuer || !config.oauthSettings.scope) {
+ // lookup issuer or scope from hostname
+ let hostname = (config.incoming.auth == Ci.nsMsgAuthMethod.OAuth2) ?
+ config.incoming.hostname : config.outgoing.hostname;
+ let hostDetails = OAuth2Providers.getHostnameDetails(hostname);
+ if (hostDetails)
+ [config.oauthSettings.issuer, config.oauthSettings.scope] = hostDetails;
+ if (!config.oauthSettings.issuer || !config.oauthSettings.scope)
+ throw "Could not get issuer for oauth2 authentication";
+ }
+ gEmailWizardLogger.info("Saving oauth parameters for issuer " +
+ config.oauthSettings.issuer);
+ inServer.setCharValue("oauth2.scope", config.oauthSettings.scope);
+ inServer.setCharValue("oauth2.issuer", config.oauthSettings.issuer);
+ gEmailWizardLogger.info("OAuth2 issuer, scope is " +
+ config.oauthSettings.issuer + ", " + config.oauthSettings.scope);
+ }
+
+ if (inServer.password ||
+ inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2)
+#else
+ if (inServer.password)
+#endif
+ verifyLogon(config, inServer, alter, msgWindow,
+ successCallback, errorCallback);
+ else {
+ // Avoid pref pollution, clear out server prefs.
+ MailServices.accounts.removeIncomingServer(inServer, true);
+ successCallback(config);
+ }
+ return;
+ }
+ catch (e) {
+ gEmailWizardLogger.error("ERROR: verify logon shouldn't have failed");
+ }
+ // Avoid pref pollution, clear out server prefs.
+ MailServices.accounts.removeIncomingServer(inServer, true);
+ errorCallback(e);
+}
+
+function verifyLogon(config, inServer, alter, msgWindow, successCallback,
+ errorCallback)
+{
+ gEmailWizardLogger.info("verifyLogon for server at " + inServer.hostName);
+ // hack - save away the old callbacks.
+ let saveCallbacks = msgWindow.notificationCallbacks;
+ // set our own callbacks - this works because verifyLogon will
+ // synchronously create the transport and use the notification callbacks.
+ let listener = new urlListener(config, inServer, alter, msgWindow,
+ successCallback, errorCallback);
+ // our listener listens both for the url and cert errors.
+ msgWindow.notificationCallbacks = listener;
+ // try to work around bug where backend is clearing password.
+ try {
+ inServer.password = config.incoming.password;
+ let uri = inServer.verifyLogon(listener, msgWindow);
+ // clear msgWindow so url won't prompt for passwords.
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl).msgWindow = null;
+ }
+ catch (e) { gEmailWizardLogger.error("verifyLogon failed: " + e); throw e;}
+ finally {
+ // restore them
+ msgWindow.notificationCallbacks = saveCallbacks;
+ }
+}
+
+/**
+ * The url listener also implements nsIBadCertListener2. Its job is to prevent
+ * "bad cert" security dialogs from being shown to the user. Currently it puts
+ * up the cert override dialog, though we'd like to give the user more detailed
+ * information in the future.
+ */
+
+function urlListener(config, server, alter, msgWindow, successCallback,
+ errorCallback)
+{
+ this.mConfig = config;
+ this.mServer = server;
+ this.mAlter = alter;
+ this.mSuccessCallback = successCallback;
+ this.mErrorCallback = errorCallback;
+ this.mMsgWindow = msgWindow;
+ this.mCertError = false;
+ this._log = Log4Moz.getConfiguredLogger("mail.wizard");
+}
+urlListener.prototype =
+{
+ OnStartRunningUrl: function(aUrl)
+ {
+ this._log.info("Starting to test username");
+ this._log.info(" username=" + (this.mConfig.incoming.username !=
+ this.mConfig.identity.emailAddress) +
+ ", have savedUsername=" +
+ (this.mConfig.usernameSaved ? "true" : "false"));
+ this._log.info(" authMethod=" + this.mServer.authMethod);
+ },
+
+ OnStopRunningUrl: function(aUrl, aExitCode)
+ {
+ this._log.info("Finished verifyConfig resulted in " + aExitCode);
+ if (Components.isSuccessCode(aExitCode))
+ {
+ this._cleanup();
+ this.mSuccessCallback(this.mConfig);
+ }
+ // Logon failed, and we aren't supposed to try other variations.
+ else if (!this.mAlter)
+ {
+ this._cleanup();
+ var errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_login.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ }
+ // Try other variations, unless there's a cert error, in which
+ // case we'll see what the user chooses.
+ else if (!this.mCertError)
+ {
+ this.tryNextLogon()
+ }
+ },
+
+ tryNextLogon: function()
+ {
+ this._log.info("tryNextLogon()");
+ this._log.info(" username=" + (this.mConfig.incoming.username !=
+ this.mConfig.identity.emailAddress) +
+ ", have savedUsername=" +
+ (this.mConfig.usernameSaved ? "true" : "false"));
+ this._log.info(" authMethod=" + this.mServer.authMethod);
+ // check if we tried full email address as username
+ if (this.mConfig.incoming.username != this.mConfig.identity.emailAddress)
+ {
+ this._log.info(" Changing username to email address.");
+ this.mConfig.usernameSaved = this.mConfig.incoming.username;
+ this.mConfig.incoming.username = this.mConfig.identity.emailAddress;
+ this.mConfig.outgoing.username = this.mConfig.identity.emailAddress;
+ this.mServer.username = this.mConfig.incoming.username;
+ this.mServer.password = this.mConfig.incoming.password;
+ verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
+ this.mSuccessCallback, this.mErrorCallback);
+ return;
+ }
+
+ if (this.mConfig.usernameSaved)
+ {
+ this._log.info(" Re-setting username.");
+ // If we tried the full email address as the username, then let's go
+ // back to trying just the username before trying the other cases.
+ this.mConfig.incoming.username = this.mConfig.usernameSaved;
+ this.mConfig.outgoing.username = this.mConfig.usernameSaved;
+ this.mConfig.usernameSaved = null;
+ this.mServer.username = this.mConfig.incoming.username;
+ this.mServer.password = this.mConfig.incoming.password;
+ }
+
+ // sec auth seems to have failed, and we've tried both
+ // varieties of user name, sadly.
+ // So fall back to non-secure auth, and
+ // again try the user name and email address as username
+ assert(this.mConfig.incoming.auth == this.mServer.authMethod);
+ this._log.info(" Using SSL: " +
+ (this.mServer.socketType == Ci.nsMsgSocketType.SSL ||
+ this.mServer.socketType == Ci.nsMsgSocketType.alwaysSTARTTLS));
+ if (this.mConfig.incoming.authAlternatives &&
+ this.mConfig.incoming.authAlternatives.length)
+ // We may be dropping back to insecure auth methods here,
+ // which is not good. But then again, we already warned the user,
+ // if it is a config without SSL.
+ {
+ this._log.info(" auth alternatives = " +
+ this.mConfig.incoming.authAlternatives.join(","));
+ this._log.info(" Decreasing auth.");
+ this._log.info(" Have password: " +
+ (this.mServer.password ? "true" : "false"));
+ let brokenAuth = this.mConfig.incoming.auth;
+ // take the next best method (compare chooseBestAuthMethod() in guess)
+ this.mConfig.incoming.auth =
+ this.mConfig.incoming.authAlternatives.shift();
+ this.mServer.authMethod = this.mConfig.incoming.auth;
+ // Assume that SMTP server has same methods working as incoming.
+ // Broken assumption, but we currently have no SMTP verification.
+ // TODO implement real SMTP verification
+ if (this.mConfig.outgoing.auth == brokenAuth &&
+ this.mConfig.outgoing.authAlternatives.indexOf(
+ this.mConfig.incoming.auth) != -1)
+ this.mConfig.outgoing.auth = this.mConfig.incoming.auth;
+ this._log.info(" outgoing auth: " + this.mConfig.outgoing.auth);
+ verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
+ this.mSuccessCallback, this.mErrorCallback);
+ return;
+ }
+
+ // Tried all variations we can. Give up.
+ this._log.info("Giving up.");
+ this._cleanup();
+ let errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_login.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ return;
+ },
+
+ _cleanup : function()
+ {
+ try {
+ // Avoid pref pollution, clear out server prefs.
+ if (this.mServer) {
+ MailServices.accounts.removeIncomingServer(this.mServer, true);
+ this.mServer = null;
+ }
+ } catch (e) { this._log.error(e); }
+ },
+
+ // Suppress any certificate errors
+ notifyCertProblem: function(socketInfo, status, targetSite) {
+ this.mCertError = true;
+ this._log.error("cert error");
+ let self = this;
+ setTimeout(function () {
+ try {
+ self.informUserOfCertError(socketInfo, status, targetSite);
+ } catch (e) { logException(e); }
+ }, 0);
+ return true;
+ },
+
+ informUserOfCertError : function(socketInfo, status, targetSite) {
+ var params = {
+ exceptionAdded : false,
+ sslStatus : status,
+ prefetchCert : true,
+ location : targetSite,
+ };
+ window.openDialog("chrome://pippki/content/exceptionDialog.xul",
+ "","chrome,centerscreen,modal", params);
+ this._log.info("cert exception dialog closed");
+ this._log.info("cert exceptionAdded = " + params.exceptionAdded);
+ if (!params.exceptionAdded) {
+ this._cleanup();
+ let errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_login.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ }
+ else {
+ // Retry the logon now that we've added the cert exception.
+ verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
+ this.mSuccessCallback, this.mErrorCallback);
+ }
+ },
+
+ // nsIInterfaceRequestor
+ getInterface: function(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsISupports
+ QueryInterface: function(iid) {
+ if (!iid.equals(Components.interfaces.nsIBadCertListener2) &&
+ !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
+ !iid.equals(Components.interfaces.nsIUrlListener) &&
+ !iid.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ }
+}
diff --git a/mailnews/base/prefs/content/am-addressing.js b/mailnews/base/prefs/content/am-addressing.js
new file mode 100644
index 000000000..9f2584767
--- /dev/null
+++ b/mailnews/base/prefs/content/am-addressing.js
@@ -0,0 +1,79 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function onLoad()
+{
+ parent.onPanelLoaded('am-addressing.xul');
+}
+
+function onInit(aPageId, aServerId)
+{
+ onInitCompositionAndAddressing();
+}
+
+function onInitCompositionAndAddressing()
+{
+ LDAPenabling();
+ quoteEnabling();
+}
+
+function onEditDirectories()
+{
+ window.openDialog("chrome://messenger/content/addressbook/pref-editdirectories.xul",
+ "editDirectories", "chrome,modal=yes,resizable=no", null);
+}
+
+function onPreInit(account, accountValues)
+{
+}
+
+function LDAPenabling()
+{
+ onCheckItem("identity.directoryServer", ["directories"]);
+ onCheckItem("editButton", ["directories"]);
+}
+
+function quoteEnabling()
+{
+ var quotebox = document.getElementById("thenBox");
+ var placebox = document.getElementById("placeBox");
+ var quotecheck = document.getElementById("identity.autoQuote");
+
+ if (quotecheck.checked && !quotecheck.disabled &&
+ (document.getElementById("identity.replyOnTop").value == 1)) {
+ placebox.firstChild.removeAttribute("disabled");
+ placebox.lastChild.removeAttribute("disabled");
+ }
+ else {
+ placebox.firstChild.setAttribute("disabled", "true");
+ placebox.lastChild.setAttribute("disabled", "true");
+ }
+ if (quotecheck.checked && !quotecheck.disabled) {
+ quotebox.firstChild.removeAttribute("disabled");
+ quotebox.lastChild.removeAttribute("disabled");
+ }
+ else {
+ quotebox.firstChild.setAttribute("disabled", "true");
+ quotebox.lastChild.setAttribute("disabled", "true");
+ }
+}
+
+/**
+ * Open the Preferences dialog on the tab with Addressing options.
+ */
+function showGlobalAddressingPrefs()
+{
+ openPrefsFromAccountManager("paneCompose", "addressingTab", null, "addressing_pane");
+}
+
+/**
+ * Open the Preferences dialog on the tab with Composing options.
+ */
+function showGlobalComposingPrefs()
+{
+ openPrefsFromAccountManager("paneCompose", "generalTab", null, "composing_messages_pane");
+}
diff --git a/mailnews/base/prefs/content/am-addressing.xul b/mailnews/base/prefs/content/am-addressing.xul
new file mode 100644
index 000000000..91eae4aac
--- /dev/null
+++ b/mailnews/base/prefs/content/am-addressing.xul
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xul-overlay href="chrome://messenger/content/am-addressingOverlay.xul"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-addressing.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&addressing.label;"
+ onload="onLoad();">
+ <vbox flex="1" style="overflow: auto;">
+ <dialogheader title="&addressing.label;"/>
+ <vbox id="compositionAndAddressing"/>
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-addressingOverlay.xul b/mailnews/base/prefs/content/am-addressingOverlay.xul
new file mode 100644
index 000000000..a5f3c1582
--- /dev/null
+++ b/mailnews/base/prefs/content/am-addressingOverlay.xul
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/content/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/am-addressing.dtd">
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://messenger/content/am-addressing.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-prefs.js"/>
+
+ <vbox flex="1" id="compositionAndAddressing">
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+
+ <groupbox>
+ <caption label="&compositionGroupTitle.label;"/>
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.composeHtml" label="&useHtml.label;"
+ accesskey="&useHtml.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.compose_html"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.autoQuote" label="&autoQuote.label;"
+ oncommand="quoteEnabling();" accesskey="&autoQuote.accesskey;"
+ pref="true" preftype="bool" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.auto_quote"/>
+ </hbox>
+ <hbox class="indent" align="center" id="thenBox">
+ <label value="&then.label;" accesskey="&then.accesskey;" control="identity.replyOnTop"/>
+ <menulist wsm_persist="true" id="identity.replyOnTop" oncommand="quoteEnabling();"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.reply_on_top">
+ <menupopup>
+ <menuitem value="1" label="&aboveQuote.label;"/>
+ <menuitem value="0" label="&belowQuote.label;"/>
+ <menuitem value="2" label="&selectAndQuote.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <hbox class="indent" align="center" id="placeBox">
+ <label value="&place.label;" accesskey="&place.accesskey;" control="identity.sig_bottom"/>
+ <menulist wsm_persist="true" id="identity.sig_bottom" genericattr="true"
+ pref="true" preftype="bool" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.sig_bottom">
+ <menupopup>
+ <menuitem value="true" label="&belowText.label;"/>
+ <menuitem value="false" label="&aboveText.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <checkbox id="identity.sig_on_reply" wsm_persist="true"
+ label="&includeSigOnReply.label;"
+ accesskey="&includeSigOnReply.accesskey;"
+ preftype="bool" genericattr="true"
+ prefstring="mail.identity.%identitykey%.sig_on_reply"/>
+
+ <checkbox id="identity.sig_on_fwd" wsm_persist="true"
+ label="&includeSigOnForward.label;"
+ accesskey="&includeSigOnForward.accesskey;"
+ preftype="bool" genericattr="true"
+ prefstring="mail.identity.%identitykey%.sig_on_fwd"/>
+
+ <separator class="thin"/>
+
+ <hbox pack="start">
+ <button id="globalComposingPrefsLink"
+ label="&globalComposingPrefs.label;"
+ accesskey="&globalComposingPrefs.accesskey;"
+ oncommand="showGlobalComposingPrefs();"/>
+ </hbox>
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <caption label="&addressingGroupTitle.label;"/>
+#ifndef MOZ_THUNDERBIRD
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.autocompleteToMyDomain"
+ label="&autocompleteToMyDomain.label;"
+ accesskey="&autocompleteToMyDomain.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.autocompleteToMyDomain"/>
+ </hbox>
+
+ <separator class="thin"/>
+#endif
+
+ <label control="identity.overrideGlobal_Pref">&addressingText.label;</label>
+ <radiogroup id="identity.overrideGlobal_Pref" class="indent"
+ oncommand="LDAPenabling();" wsm_persist="true"
+ genericattr="true" preftype="bool"
+ prefstring="mail.identity.%identitykey%.overrideGlobal_Pref">
+ <radio value="false" label="&useGlobal.label;"
+ accesskey="&useGlobal.accesskey;"/>
+ <radio value="true" id="directories" label="&directories.label;"
+ accesskey="&directories.accesskey;"/>
+ <hbox class="indent">
+ <menulist id="identity.directoryServer"
+ wsm_persist="true" preftype="string"
+ prefstring="mail.identity.%identitykey%.directoryServer"
+ style="min-width: 16em;" aria-labelledby="directories">
+ <menupopup class="addrbooksPopup"
+ none="&directoriesNone.label;"
+ remoteonly="true" value="dirPrefId"/>
+ </menulist>
+ <button id="editButton" label="&editDirectories.label;"
+ accesskey="&editDirectories.accesskey;"
+ oncommand="onEditDirectories();"/>
+ </hbox>
+ </radiogroup>
+
+ <separator class="thin"/>
+
+ <hbox pack="start">
+ <button id="globalAddressingPrefsLink"
+ label="&globalAddressingPrefs.label;"
+ accesskey="&globalAddressingPrefs.accesskey;"
+ oncommand="showGlobalAddressingPrefs();"/>
+ </hbox>
+ </groupbox>
+ </vbox>
+</overlay>
diff --git a/mailnews/base/prefs/content/am-archiveoptions.js b/mailnews/base/prefs/content/am-archiveoptions.js
new file mode 100644
index 000000000..ae09ae156
--- /dev/null
+++ b/mailnews/base/prefs/content/am-archiveoptions.js
@@ -0,0 +1,69 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gIdentity = null;
+
+/**
+ * Load the archive options dialog, set the radio/checkbox items to the
+ * appropriate values, and update the archive hierarchy example.
+ */
+function onLoadArchiveOptions() {
+ // extract the account
+ gIdentity = window.arguments[0];
+
+ let granularity = document.getElementById("archiveGranularity");
+ granularity.selectedIndex = gIdentity.archiveGranularity;
+ granularity.addEventListener("command", updateArchiveExample, false);
+
+ let kfs = document.getElementById("archiveKeepFolderStructure");
+ kfs.checked = gIdentity.archiveKeepFolderStructure;
+ kfs.addEventListener("command", updateArchiveExample, false);
+
+ updateArchiveExample();
+}
+
+/**
+ * Save the archive settings to the current identity.
+ */
+function onAcceptArchiveOptions() {
+ gIdentity.archiveGranularity =
+ document.getElementById("archiveGranularity").selectedIndex;
+ gIdentity.archiveKeepFolderStructure =
+ document.getElementById("archiveKeepFolderStructure").checked;
+}
+
+/**
+ * Update the example tree to show what the current options would look like.
+ */
+function updateArchiveExample() {
+ let granularity = document.getElementById("archiveGranularity").selectedIndex;
+ let kfs = document.getElementById("archiveKeepFolderStructure").checked;
+ let hierarchy = [ document.getElementsByClassName("root"),
+ document.getElementsByClassName("year"),
+ document.getElementsByClassName("month") ];
+
+ // First, show/hide the appropriate levels in the hierarchy and turn the
+ // necessary items into containers.
+ for (let i = 0; i < hierarchy.length; i++) {
+ for (let j = 0; j < hierarchy[i].length; j++) {
+ hierarchy[i][j].setAttribute("container", granularity > i);
+ hierarchy[i][j].setAttribute("open", granularity > i);
+ hierarchy[i][j].hidden = granularity < i;
+ }
+ }
+
+ // Next, handle the "keep folder structures" case by moving a tree item around
+ // and making sure its parent is a container.
+ let folders = document.getElementById("folders");
+ folders.hidden = !kfs;
+ if (kfs) {
+ let parent = hierarchy[granularity][0];
+ parent.setAttribute("container", true);
+ parent.setAttribute("open", true);
+
+ let treechildren = parent.children[1];
+ treechildren.appendChild(folders);
+ }
+}
diff --git a/mailnews/base/prefs/content/am-archiveoptions.xul b/mailnews/base/prefs/content/am-archiveoptions.xul
new file mode 100644
index 000000000..9d7ecb57c
--- /dev/null
+++ b/mailnews/base/prefs/content/am-archiveoptions.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/am-archiveoptions.dtd" >
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="archive-options"
+ title="&dialogTitle.label;"
+ width="350" height="350"
+ persist="width height"
+ onload="onLoadArchiveOptions();"
+ ondialogaccept="onAcceptArchiveOptions();">
+
+ <script type="application/javascript" src="chrome://messenger/content/am-archiveoptions.js"/>
+
+ <vbox flex="1">
+ <label>&archiveGranularityPrefix.label;</label>
+ <radiogroup id="archiveGranularity">
+ <radio label="&archiveFlat.label;" accesskey="&archiveFlat.accesskey;"
+ class="indent"/>
+ <radio label="&archiveYearly.label;"
+ accesskey="&archiveYearly.accesskey;" class="indent"/>
+ <radio label="&archiveMonthly.label;"
+ accesskey="&archiveMonthly.accesskey;" class="indent"/>
+ </radiogroup>
+ <checkbox id="archiveKeepFolderStructure"
+ label="&keepFolderStructure.label;"
+ accesskey="&keepFolderStructure.accesskey;"/>
+
+ <groupbox flex="1">
+ <caption label="&archiveExample.label;"/>
+ <tree id="archiveTree" hidecolumnpicker="true" disabled="true" flex="1">
+ <treecols>
+ <treecol primary="true" hideheader="true" flex="1"
+ id="folderNameCol"/>
+ </treecols>
+ <treechildren>
+ <treeitem class="root">
+ <treerow>
+ <treecell properties="specialFolder-Archive"
+ label="&archiveFolderName.label;"/>
+ </treerow>
+ <treechildren>
+ <treeitem id="folders">
+ <treerow>
+ <treecell label="&inboxFolderName.label;"/>
+ </treerow>
+ </treeitem>
+ <treeitem class="year">
+ <treerow>
+ <treecell label="2010"/>
+ </treerow>
+ <treechildren>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2010-11"/>
+ </treerow>
+ <treechildren/>
+ </treeitem>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2010-12"/>
+ </treerow>
+ <treechildren/>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem class="year">
+ <treerow>
+ <treecell label="2011"/>
+ </treerow>
+ <treechildren>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2011-01"/>
+ </treerow>
+ <treechildren/>
+ </treeitem>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2011-02"/>
+ </treerow>
+ <treechildren/>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ </treechildren>
+ </tree>
+ </groupbox>
+ </vbox>
+</dialog>
diff --git a/mailnews/base/prefs/content/am-copies.js b/mailnews/base/prefs/content/am-copies.js
new file mode 100644
index 000000000..5fc3836a6
--- /dev/null
+++ b/mailnews/base/prefs/content/am-copies.js
@@ -0,0 +1,471 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/MailUtils.js");
+
+var gFccRadioElemChoice, gDraftsRadioElemChoice, gArchivesRadioElemChoice, gTmplRadioElemChoice;
+var gFccRadioElemChoiceLocked, gDraftsRadioElemChoiceLocked, gArchivesRadioElemChoiceLocked, gTmplRadioElemChoiceLocked;
+var gDefaultPickerMode = "1";
+
+var gFccFolderWithDelim, gDraftsFolderWithDelim, gArchivesFolderWithDelim, gTemplatesFolderWithDelim;
+var gAccount;
+var gCurrentServerId;
+
+function onPreInit(account, accountValues)
+{
+ gAccount = account;
+ var type = parent.getAccountValue(account, accountValues, "server", "type", null, false);
+ hideShowControls(type);
+}
+
+/*
+ * Set the global radio element choices and initialize folder/account pickers.
+ * Also, initialize other UI elements (cc, bcc, fcc picker controller checkboxes).
+ */
+function onInit(aPageId, aServerId)
+{
+ gCurrentServerId = aServerId;
+ onInitCopiesAndFolders();
+}
+
+function onInitCopiesAndFolders()
+{
+ SetGlobalRadioElemChoices();
+
+ SetFolderDisplay(gFccRadioElemChoice, gFccRadioElemChoiceLocked,
+ "fcc",
+ "msgFccAccountPicker",
+ "identity.fccFolder",
+ "msgFccFolderPicker");
+
+ SetFolderDisplay(gArchivesRadioElemChoice, gArchivesRadioElemChoiceLocked,
+ "archive",
+ "msgArchivesAccountPicker",
+ "identity.archiveFolder",
+ "msgArchivesFolderPicker");
+
+ SetFolderDisplay(gDraftsRadioElemChoice, gDraftsRadioElemChoiceLocked,
+ "draft",
+ "msgDraftsAccountPicker",
+ "identity.draftFolder",
+ "msgDraftsFolderPicker");
+
+ SetFolderDisplay(gTmplRadioElemChoice, gTmplRadioElemChoiceLocked,
+ "tmpl",
+ "msgStationeryAccountPicker",
+ "identity.stationeryFolder",
+ "msgStationeryFolderPicker");
+
+ setupCcTextbox(true);
+ setupBccTextbox(true);
+ setupFccItems();
+ setupArchiveItems();
+
+ SetSpecialFolderNamesWithDelims();
+}
+
+// Initialize the picker mode choices (account/folder picker) into global vars
+function SetGlobalRadioElemChoices()
+{
+ var pickerModeElement = document.getElementById("identity.fccFolderPickerMode");
+ gFccRadioElemChoice = pickerModeElement.getAttribute("value");
+ gFccRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled");
+ if (!gFccRadioElemChoice) gFccRadioElemChoice = gDefaultPickerMode;
+
+ pickerModeElement = document.getElementById("identity.archivesFolderPickerMode");
+ gArchivesRadioElemChoice = pickerModeElement.getAttribute("value");
+ gArchivesRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled");
+ if (!gArchivesRadioElemChoice) gArchivesRadioElemChoice = gDefaultPickerMode;
+
+ pickerModeElement = document.getElementById("identity.draftsFolderPickerMode");
+ gDraftsRadioElemChoice = pickerModeElement.getAttribute("value");
+ gDraftsRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled");
+ if (!gDraftsRadioElemChoice) gDraftsRadioElemChoice = gDefaultPickerMode;
+
+ pickerModeElement = document.getElementById("identity.tmplFolderPickerMode");
+ gTmplRadioElemChoice = pickerModeElement.getAttribute("value");
+ gTmplRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled");
+ if (!gTmplRadioElemChoice) gTmplRadioElemChoice = gDefaultPickerMode;
+}
+
+/*
+ * Set Account and Folder elements based on the values read from
+ * preferences file. Default picker mode, if none specified at this stage, is
+ * set to 1 i.e., Other picker displaying the folder value read from the
+ * preferences file.
+ */
+function SetFolderDisplay(pickerMode, disableMode,
+ radioElemPrefix,
+ accountPickerId,
+ folderPickedField,
+ folderPickerId)
+{
+ if (!pickerMode)
+ pickerMode = gDefaultPickerMode;
+
+ var selectAccountRadioId = radioElemPrefix + "_selectAccount";
+ var selectAccountRadioElem = document.getElementById(selectAccountRadioId);
+ var selectFolderRadioId = radioElemPrefix + "_selectFolder";
+ var selectFolderRadioElem = document.getElementById(selectFolderRadioId);
+ var accountPicker = document.getElementById(accountPickerId);
+ var folderPicker = document.getElementById(folderPickerId);
+ var rg = selectAccountRadioElem.radioGroup;
+ var folderPickedElement = document.getElementById(folderPickedField);
+ var uri = folderPickedElement.getAttribute("value");
+ // Get message folder from the given uri. Second argument (false) signifies
+ // that there is no need to check for the existence of special folders as
+ // these folders are created on demand at runtime in case of imap accounts.
+ // For POP3 accounts, special folders are created at the account creation time.
+ var msgFolder = MailUtils.getFolderForURI(uri, false);
+ InitFolderDisplay(msgFolder.server.rootFolder, accountPicker);
+ InitFolderDisplay(msgFolder, folderPicker);
+
+ switch (pickerMode)
+ {
+ case "0" :
+ rg.selectedItem = selectAccountRadioElem;
+ SetPickerEnabling(accountPickerId, folderPickerId);
+ break;
+
+ case "1" :
+ rg.selectedItem = selectFolderRadioElem;
+ SetPickerEnabling(folderPickerId, accountPickerId);
+ break;
+
+ default :
+ dump("Error in setting initial folder display on pickers\n");
+ break;
+ }
+
+ // Check to see if we need to lock page elements. Disable radio buttons
+ // and account/folder pickers when locked.
+ if (disableMode) {
+ selectAccountRadioElem.setAttribute("disabled","true");
+ selectFolderRadioElem.setAttribute("disabled","true");
+ accountPicker.setAttribute("disabled","true");
+ folderPicker.setAttribute("disabled","true");
+ }
+}
+
+// Initialize the folder display based on prefs values
+function InitFolderDisplay(folder, folderPicker) {
+ folderPicker.menupopup.selectFolder(folder);
+ folderPicker.folder = folder;
+}
+
+/**
+ * Capture any menulist changes and update the folder property.
+ *
+ * @param aGroup the prefix for the menulist we're handling (e.g. "drafts")
+ * @param aType "Account" for the account picker or "Folder" for the folder
+ * picker
+ * @param aEvent the event that we're responding to
+ */
+function noteSelectionChange(aGroup, aType, aEvent)
+{
+ var checkedElem = document.getElementById(aGroup+"_select"+aType);
+ var folder = aEvent.target._folder;
+ var modeValue = checkedElem.value;
+ var radioGroup = checkedElem.radioGroup.getAttribute("id");
+ var picker;
+
+ switch (radioGroup) {
+ case "doFcc" :
+ gFccRadioElemChoice = modeValue;
+ picker = document.getElementById("msgFcc"+aType+"Picker");
+ break;
+
+ case "messageArchives" :
+ gArchivesRadioElemChoice = modeValue;
+ picker = document.getElementById("msgArchives"+aType+"Picker");
+ updateArchiveHierarchyButton(folder);
+ break;
+
+ case "messageDrafts" :
+ gDraftsRadioElemChoice = modeValue;
+ picker = document.getElementById("msgDrafts"+aType+"Picker");
+ break;
+
+ case "messageTemplates" :
+ gTmplRadioElemChoice = modeValue;
+ picker = document.getElementById("msgStationery"+aType+"Picker");
+ break;
+ }
+
+ picker.folder = folder;
+ picker.menupopup.selectFolder(folder);
+}
+
+// Need to append special folders when account picker is selected.
+// Create a set of global special folder vars to be suffixed to the
+// server URI of the selected account.
+function SetSpecialFolderNamesWithDelims()
+{
+ var folderDelim = "/";
+ /* we use internal names known to everyone like "Sent", "Templates" and "Drafts" */
+
+ gFccFolderWithDelim = folderDelim + "Sent";
+ gArchivesFolderWithDelim = folderDelim + "Archives";
+ gDraftsFolderWithDelim = folderDelim + "Drafts";
+ gTemplatesFolderWithDelim = folderDelim + "Templates";
+}
+
+// Save all changes on this page
+function onSave()
+{
+ onSaveCopiesAndFolders();
+}
+
+function onSaveCopiesAndFolders()
+{
+ SaveFolderSettings( gFccRadioElemChoice,
+ "doFcc",
+ gFccFolderWithDelim,
+ "msgFccAccountPicker",
+ "msgFccFolderPicker",
+ "identity.fccFolder",
+ "identity.fccFolderPickerMode" );
+
+ SaveFolderSettings( gArchivesRadioElemChoice,
+ "messageArchives",
+ gArchivesFolderWithDelim,
+ "msgArchivesAccountPicker",
+ "msgArchivesFolderPicker",
+ "identity.archiveFolder",
+ "identity.archivesFolderPickerMode" );
+
+ SaveFolderSettings( gDraftsRadioElemChoice,
+ "messageDrafts",
+ gDraftsFolderWithDelim,
+ "msgDraftsAccountPicker",
+ "msgDraftsFolderPicker",
+ "identity.draftFolder",
+ "identity.draftsFolderPickerMode" );
+
+ SaveFolderSettings( gTmplRadioElemChoice,
+ "messageTemplates",
+ gTemplatesFolderWithDelim,
+ "msgStationeryAccountPicker",
+ "msgStationeryFolderPicker",
+ "identity.stationeryFolder",
+ "identity.tmplFolderPickerMode" );
+}
+
+// Save folder settings and radio element choices
+function SaveFolderSettings(radioElemChoice,
+ radioGroupId,
+ folderSuffix,
+ accountPickerId,
+ folderPickerId,
+ folderElementId,
+ folderPickerModeId)
+{
+ var formElement = document.getElementById(folderElementId);
+ var uri;
+
+ if (radioElemChoice == "0" ||
+ !document.getElementById(folderPickerId).value) {
+ // Default or revert to default if no folder chosen.
+ radioElemChoice = "0";
+ uri = document.getElementById(accountPickerId).folder.URI;
+ if (uri) {
+ // Create Folder URI.
+ uri = uri + folderSuffix;
+ }
+ }
+ else if (radioElemChoice == "1") {
+ uri = document.getElementById(folderPickerId).folder.URI;
+ }
+ else {
+ dump ("Error saving folder preferences.\n");
+ return;
+ }
+
+ formElement.setAttribute("value", uri);
+
+ formElement = document.getElementById(folderPickerModeId);
+ formElement.setAttribute("value", radioElemChoice);
+}
+
+// Check the Fcc Self item and setup associated picker state
+function setupFccItems()
+{
+ var broadcaster = document.getElementById("broadcaster_doFcc");
+
+ var checked = document.getElementById("identity.doFcc").checked;
+ if (checked) {
+ broadcaster.removeAttribute("disabled");
+ switch (gFccRadioElemChoice) {
+ case "0" :
+ if (!gFccRadioElemChoiceLocked)
+ SetPickerEnabling("msgFccAccountPicker", "msgFccFolderPicker");
+ SetRadioButtons("fcc_selectAccount", "fcc_selectFolder");
+ break;
+
+ case "1" :
+ if (!gFccRadioElemChoiceLocked)
+ SetPickerEnabling("msgFccFolderPicker", "msgFccAccountPicker");
+ SetRadioButtons("fcc_selectFolder", "fcc_selectAccount");
+ break;
+
+ default :
+ dump("Error in setting Fcc elements.\n");
+ break;
+ }
+ }
+ else
+ broadcaster.setAttribute("disabled", "true");
+}
+
+// Disable CC textbox if CC checkbox is not checked
+function setupCcTextbox(init)
+{
+ var ccChecked = document.getElementById("identity.doCc").checked;
+ var ccTextbox = document.getElementById("identity.doCcList");
+
+ ccTextbox.disabled = !ccChecked;
+
+ if (ccChecked) {
+ if (ccTextbox.value == "") {
+ ccTextbox.value = document.getElementById("identity.email").value;
+ if (!init)
+ ccTextbox.select();
+ }
+ } else if ((ccTextbox.value == document.getElementById("identity.email").value) ||
+ (init && ccTextbox.getAttribute("value") == ""))
+ ccTextbox.value = "";
+}
+
+// Disable BCC textbox if BCC checkbox is not checked
+function setupBccTextbox(init)
+{
+ var bccChecked = document.getElementById("identity.doBcc").checked;
+ var bccTextbox = document.getElementById("identity.doBccList");
+
+ bccTextbox.disabled = !bccChecked;
+
+ if (bccChecked) {
+ if (bccTextbox.value == "") {
+ bccTextbox.value = document.getElementById("identity.email").value;
+ if (!init)
+ bccTextbox.select();
+ }
+ } else if ((bccTextbox.value == document.getElementById("identity.email").value) ||
+ (init && bccTextbox.getAttribute("value") == ""))
+ bccTextbox.value = "";
+}
+
+// Enable and disable pickers based on the radio element clicked
+function SetPickerEnabling(enablePickerId, disablePickerId)
+{
+ var activePicker = document.getElementById(enablePickerId);
+ activePicker.removeAttribute("disabled");
+
+ var inactivePicker = document.getElementById(disablePickerId);
+ inactivePicker.setAttribute("disabled", "true");
+}
+
+// Set radio element choices and picker states
+function setPickersState(enablePickerId, disablePickerId, event)
+{
+ SetPickerEnabling(enablePickerId, disablePickerId);
+
+ var radioElemValue = event.target.value;
+
+ switch (event.target.id) {
+ case "fcc_selectAccount":
+ case "fcc_selectFolder":
+ gFccRadioElemChoice = radioElemValue;
+ break;
+ case "archive_selectAccount":
+ case "archive_selectFolder":
+ gArchivesRadioElemChoice = radioElemValue;
+ updateArchiveHierarchyButton(document.getElementById(enablePickerId)
+ .folder);
+ break;
+ case "draft_selectAccount":
+ case "draft_selectFolder":
+ gDraftsRadioElemChoice = radioElemValue;
+ break;
+ case "tmpl_selectAccount":
+ case "tmpl_selectFolder":
+ gTmplRadioElemChoice = radioElemValue;
+ break;
+ default:
+ dump("Error in setting picker state.\n");
+ return;
+ }
+}
+
+// This routine is to restore the correct radio element
+// state when the fcc self checkbox broadcasts the change
+function SetRadioButtons(selectPickerId, unselectPickerId)
+{
+ var activeRadioElem = document.getElementById(selectPickerId);
+ activeRadioElem.radioGroup.selectedItem = activeRadioElem;
+}
+
+/**
+ * Enable/disable the archive hierarchy button depending on what folder is
+ * currently selected (Gmail IMAP folders should have the button disabled, since
+ * changing the archive hierarchy does nothing there.
+ *
+ * @param archiveFolder the currently-selected folder to store archives in
+ */
+function updateArchiveHierarchyButton(archiveFolder) {
+ let isGmailImap = (archiveFolder.server.type == "imap" &&
+ archiveFolder.server.QueryInterface(
+ Components.interfaces.nsIImapIncomingServer)
+ .isGMailServer);
+ document.getElementById("archiveHierarchyButton").disabled = isGmailImap;
+}
+
+/**
+ * Enable or disable (as appropriate) the controls for setting archive options
+ */
+function setupArchiveItems() {
+ let broadcaster = document.getElementById("broadcaster_archiveEnabled");
+ let checked = document.getElementById("identity.archiveEnabled").checked;
+ let archiveFolder;
+
+ if (checked) {
+ broadcaster.removeAttribute("disabled");
+ switch (gArchivesRadioElemChoice) {
+ case "0":
+ if (!gArchivesRadioElemChoiceLocked)
+ SetPickerEnabling("msgArchivesAccountPicker", "msgArchivesFolderPicker");
+ SetRadioButtons("archive_selectAccount", "archive_selectFolder");
+ updateArchiveHierarchyButton(document.getElementById(
+ "msgArchivesAccountPicker").folder);
+ break;
+
+ case "1":
+ if (!gArchivesRadioElemChoiceLocked)
+ SetPickerEnabling("msgArchivesFolderPicker", "msgArchivesAccountPicker");
+ SetRadioButtons("archive_selectFolder", "archive_selectAccount");
+ updateArchiveHierarchyButton(document.getElementById(
+ "msgArchivesFolderPicker").folder);
+ break;
+
+ default:
+ dump("Error in setting Archive elements.\n");
+ return;
+ }
+ }
+ else
+ broadcaster.setAttribute("disabled", "true");
+}
+
+/**
+ * Open a dialog to edit the folder hierarchy used when archiving messages.
+ */
+function ChangeArchiveHierarchy() {
+ let identity = parent.gIdentity || parent.getCurrentAccount().defaultIdentity;
+
+ top.window.openDialog("chrome://messenger/content/am-archiveoptions.xul",
+ "", "centerscreen,chrome,modal,titlebar,resizable=yes",
+ identity);
+ return true;
+}
diff --git a/mailnews/base/prefs/content/am-copies.xul b/mailnews/base/prefs/content/am-copies.xul
new file mode 100644
index 000000000..dce174a22
--- /dev/null
+++ b/mailnews/base/prefs/content/am-copies.xul
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xul-overlay href="chrome://messenger/content/am-copiesOverlay.xul"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-copies.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&copyAndFolderTitle.label;"
+ onload="parent.onPanelLoaded('am-copies.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+
+ <dialogheader title="&copyAndFolderTitle.label;"/>
+ <vbox id="copiesAndFolders"/>
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-copiesOverlay.xul b/mailnews/base/prefs/content/am-copiesOverlay.xul
new file mode 100644
index 000000000..4b4cdbcfb
--- /dev/null
+++ b/mailnews/base/prefs/content/am-copiesOverlay.xul
@@ -0,0 +1,311 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % copiesDTD SYSTEM "chrome://messenger/locale/am-copies.dtd">
+%copiesDTD;
+]>
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://messenger/content/am-copies.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+
+ <vbox flex="1" id="copiesAndFolders">
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ </stringbundleset>
+
+ <broadcaster id="broadcaster_doFcc"/>
+ <broadcaster id="broadcaster_archiveEnabled"/>
+
+ <label hidden="true" wsm_persist="true" id="identity.fccFolder"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc_folder"/>
+ <label hidden="true" wsm_persist="true" id="identity.draftFolder"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.draft_folder"/>
+ <label hidden="true" wsm_persist="true" id="identity.archiveFolder"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.archive_folder"/>
+ <label hidden="true" wsm_persist="true" id="identity.stationeryFolder"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.stationary_folder"/>
+ <label hidden="true" wsm_persist="true" id="identity.email"/>
+ <label hidden="true" wsm_persist="true" id="identity.fccFolderPickerMode"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc_folder_picker_mode"/>
+ <label hidden="true" wsm_persist="true" id="identity.draftsFolderPickerMode"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.drafts_folder_picker_mode"/>
+ <label hidden="true" wsm_persist="true" id="identity.archivesFolderPickerMode"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.archives_folder_picker_mode"/>
+ <label hidden="true" wsm_persist="true" id="identity.tmplFolderPickerMode"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.tmpl_folder_picker_mode"/>
+ <groupbox id="copiesGroup">
+ <caption label="&sendingPrefix.label;"/>
+
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.doFcc" label="&fccMailFolder.label;"
+ accesskey="&fccMailFolder.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc"
+ oncommand="setupFccItems();"/>
+ </hbox>
+ <radiogroup id="doFcc" aria-labelledby="identity.doFcc">
+ <grid class="specialFolderPickerGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <radio id="fcc_selectAccount"
+ value="0" label="&sentFolderOn.label;"
+ accesskey="&sentFolderOn.accesskey;"
+ oncommand="setPickersState('msgFccAccountPicker', 'msgFccFolderPicker', event)"
+ observes="broadcaster_doFcc"/>
+ <menulist id="msgFccAccountPicker"
+ class="folderMenuItem"
+ aria-labelledby="fcc_selectAccount"
+ observes="broadcaster_doFcc">
+ <menupopup id="msgFccAccountPopup" type="folder" mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('fcc', 'Account', event)"/>
+ </menulist>
+ </row>
+ <row align="center">
+ <radio id="fcc_selectFolder"
+ value="1" label="&sentInOtherFolder.label;"
+ accesskey="&sentInOtherFolder.accesskey;"
+ oncommand="setPickersState('msgFccFolderPicker', 'msgFccAccountPicker', event)"
+ observes="broadcaster_doFcc"/>
+ <menulist id="msgFccFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="fcc_selectFolder"
+ displayformat="verbose"
+ observes="broadcaster_doFcc">
+ <menupopup id="msgFccFolderPopup" type="folder" mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('fcc', 'Folder', event)"/>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+
+ <hbox align="center" class="fccReplyFollowsParent" hidable="true" hidefor="nntp,rss">
+ <checkbox wsm_persist="true" id="identity.fccReplyFollowsParent"
+ label="&fccReplyFollowsParent.label;"
+ accesskey="&fccReplyFollowsParent.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc_reply_follows_parent"
+ observes="broadcaster_doFcc"/>
+ </hbox>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <checkbox wsm_persist="true" id="identity.doCc" label="&ccAddress.label;"
+ accesskey="&ccAddress.accesskey;"
+ control="identity.doCcList"
+ oncommand="setupCcTextbox();"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.doCc"/>
+ <textbox wsm_persist="true" id="identity.doCcList" flex="1"
+ aria-labelledby="identity.doCc"
+ prefstring="mail.identity.%identitykey%.doCcList" class="uri-element"
+ placeholder="&ccAddressList.placeholder;"/>
+ </row>
+ <row align="center">
+ <checkbox wsm_persist="true" id="identity.doBcc" label="&bccAddress.label;"
+ accesskey="&bccAddress.accesskey;"
+ control="identity.doBccList"
+ oncommand="setupBccTextbox();"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.doBcc"/>
+ <textbox wsm_persist="true" id="identity.doBccList" flex="1"
+ aria-labelledby="identity.doBcc"
+ prefstring="mail.identity.%identitykey%.doBccList" class="uri-element"
+ placeholder="&bccAddressList.placeholder;"/>
+ </row>
+ </rows>
+ </grid>
+
+ </groupbox>
+
+ <groupbox id="archivesGroup">
+ <caption label="&archivesTitle.label;"/>
+
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.archiveEnabled"
+ label="&keepArchives.label;"
+ accesskey="&keepArchives.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.archive_enabled"
+ oncommand="setupArchiveItems();"/>
+ <spacer flex="1"/>
+ <button id="archiveHierarchyButton"
+ label="&archiveHierarchyButton.label;"
+ accesskey="&archiveHierarchyButton.accesskey;"
+ oncommand="ChangeArchiveHierarchy();"
+ observes="broadcaster_archiveEnabled"/>
+ </hbox>
+
+ <radiogroup id="messageArchives">
+ <grid class="specialFolderPickerGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <radio id="archive_selectAccount"
+ value="0" label="&archivesFolderOn.label;"
+ accesskey="&archivesFolderOn.accesskey;"
+ oncommand="setPickersState('msgArchivesAccountPicker', 'msgArchivesFolderPicker', event)"
+ observes="broadcaster_archiveEnabled"/>
+ <menulist id="msgArchivesAccountPicker"
+ class="folderMenuItem"
+ aria-labelledby="archive_selectAccount"
+ observes="broadcaster_archiveEnabled">
+ <menupopup id="msgArchivesAccountPopup" type="folder" mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('archive', 'Account', event)"/>
+ </menulist>
+ </row>
+ <row align="center">
+ <radio id="archive_selectFolder"
+ value="1" label="&archiveInOtherFolder.label;"
+ accesskey="&archiveInOtherFolder.accesskey;"
+ oncommand="setPickersState('msgArchivesFolderPicker', 'msgArchivesAccountPicker', event)"
+ observes="broadcaster_archiveEnabled"/>
+ <menulist id="msgArchivesFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="archive_selectFolder"
+ displayformat="verbose"
+ observes="broadcaster_archiveEnabled">
+ <menupopup id="msgArchivesFolderPopup" type="folder" mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('archive', 'Folder', event)"/>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+ </groupbox>
+
+ <groupbox id="foldersGroup">
+ <caption label="&specialFolders.label;"/>
+
+ <hbox align="center">
+ <label value="&keepDrafts2.label;" control="messageDrafts"/>
+ </hbox>
+
+ <radiogroup id="messageDrafts">
+ <grid class="specialFolderPickerGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <radio id="draft_selectAccount"
+ oncommand="setPickersState('msgDraftsAccountPicker', 'msgDraftsFolderPicker', event)"
+ value="0" label="&draftsFolderOn.label;"
+ accesskey="&draftsFolderOn.accesskey;"/>
+ <menulist id="msgDraftsAccountPicker"
+ class="folderMenuItem"
+ aria-labelledby="draft_selectAccount">
+ <menupopup id="msgDraftAccountPopup" type="folder" mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('draft', 'Account', event)"/>
+ </menulist>
+ </row>
+ <row align="center">
+ <radio id="draft_selectFolder"
+ oncommand="setPickersState('msgDraftsFolderPicker', 'msgDraftsAccountPicker', event)"
+ value="1" label="&draftInOtherFolder.label;"
+ accesskey="&draftInOtherFolder.accesskey;"/>
+ <menulist id="msgDraftsFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="draft_selectFolder"
+ displayformat="verbose">
+ <menupopup id="msgDraftFolderPopup" type="folder" mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('draft', 'Folder', event)"/>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+
+ <hbox align="center">
+ <label value="&keepTemplates.label;" control="messageTemplates"/>
+ </hbox>
+
+ <radiogroup id="messageTemplates">
+ <grid class="specialFolderPickerGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <radio id="tmpl_selectAccount"
+ oncommand="setPickersState('msgStationeryAccountPicker', 'msgStationeryFolderPicker', event)"
+ value="0" label="&templatesFolderOn.label;"
+ accesskey="&templatesFolderOn.accesskey;"/>
+ <menulist id="msgStationeryAccountPicker"
+ class="folderMenuItem"
+ aria-labelledby="tmpl_selectAccount">
+ <menupopup id="msgFccAccountPopup" type="folder" mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('tmpl', 'Account', event)"/>
+ </menulist>
+ </row>
+ <row align="center">
+ <radio id="tmpl_selectFolder"
+ oncommand="setPickersState('msgStationeryFolderPicker', 'msgStationeryAccountPicker', event)"
+ value="1" label="&templateInOtherFolder.label;"
+ accesskey="&templateInOtherFolder.accesskey;"/>
+ <menulist id="msgStationeryFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="tmpl_selectFolder"
+ displayformat="verbose">
+ <menupopup id="msgTemplFolderPopup" type="folder" mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('tmpl', 'Folder', event)"/>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.showSaveMsgDlg" label="&saveMessageDlg.label;"
+ accesskey="&saveMessageDlg.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.showSaveMsgDlg"/>
+ </hbox>
+ </groupbox>
+ </vbox>
+</overlay>
diff --git a/mailnews/base/prefs/content/am-help.js b/mailnews/base/prefs/content/am-help.js
new file mode 100644
index 000000000..da9d0b076
--- /dev/null
+++ b/mailnews/base/prefs/content/am-help.js
@@ -0,0 +1,76 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Key value pairs to derive the tag based on the page loaded.
+ * Each key is the page loaded when user clicks on one of the items on
+ * the accounttree of the AccountManager window.
+ * Value is a tag that is preset which will be used to display
+ * context sensitive help.
+ */
+var pageTagPairs = {
+ "chrome://messenger/content/am-main.xul": "mail_account_identity",
+ "chrome://messenger/content/am-server.xul": "mail",
+ "chrome://messenger/content/am-copies.xul": "mail_copies",
+ "chrome://messenger/content/am-addressing.xul": "mail_addressing_settings",
+ "chrome://messenger/content/am-junk.xul": "mail-account-junk",
+ "chrome://messenger/content/am-offline.xul": "mail-offline-accounts",
+ "chrome://messenger/content/am-smtp.xul": "mail_smtp",
+ "chrome://messenger/content/am-smime.xul": "mail_security_settings",
+ "chrome://messenger/content/am-serverwithnoidentities.xul": "mail_local_folders_settings",
+ "chrome://messenger/content/am-mdn.xul": "mail-account-receipts",
+}
+
+function doHelpButton()
+{
+ // Get the URI of the page loaded in the AccountManager's content frame.
+ var pageSourceURI = contentFrame.location.href;
+ // Get the help tag corresponding to the page loaded.
+ var helpTag = pageTagPairs[pageSourceURI];
+
+ // If the help tag is generic or offline, check if there is a need to set tags per server type
+ if ((helpTag == "mail") || (helpTag == "mail-offline-accounts")) {
+ // Get server type, as we may need to set help tags per server type for some pages
+ var serverType = GetServerType();
+
+ /**
+ * Check the page to be loaded. Following pages needed to be presented with the
+ * help content that is based on server type. For any pages with such requirement
+ * do add comments here about the page and a new case statement for pageSourceURI
+ * switch.
+ * - server settings ("chrome://messenger/content/am-server.xul")
+ * - offline/diskspace settings ("chrome://messenger/content/am-offline.xul")
+ */
+ switch (pageSourceURI) {
+ case "chrome://messenger/content/am-server.xul":
+ helpTag = "mail_server_" + serverType;
+ break;
+
+ case "chrome://messenger/content/am-offline.xul":
+ helpTag = "mail_offline_" + serverType;
+ break;
+
+ default :
+ break;
+ }
+ }
+
+ if ( helpTag )
+ openHelp(helpTag);
+ else
+ openHelp('mail');
+}
+
+/**
+ * Get server type of the seleted item
+ */
+function GetServerType()
+{
+ var serverType = null;
+ var currentAccount = parent.getCurrentAccount();
+ if (currentAccount)
+ serverType = currentAccount.incomingServer.type;
+ return serverType;
+}
diff --git a/mailnews/base/prefs/content/am-identities-list.js b/mailnews/base/prefs/content/am-identities-list.js
new file mode 100644
index 000000000..2cabb400d
--- /dev/null
+++ b/mailnews/base/prefs/content/am-identities-list.js
@@ -0,0 +1,180 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gIdentityListBox; // the root <listbox> node
+var gAddButton;
+var gEditButton;
+var gSetDefaultButton;
+var gDeleteButton;
+
+var gAccount = null; // the account we are showing the identities for
+
+function onLoad()
+{
+ gIdentityListBox = document.getElementById("identitiesList");
+ gAddButton = document.getElementById("cmd_add");
+ gEditButton = document.getElementById("cmd_edit");
+ gSetDefaultButton = document.getElementById("cmd_default");
+ gDeleteButton = document.getElementById("cmd_delete");
+
+ // extract the account
+ gAccount = window.arguments[0].account;
+
+ var accountName = window.arguments[0].accountName;
+ document.title = document.getElementById("bundle_prefs")
+ .getFormattedString("identity-list-title", [accountName]);
+
+ refreshIdentityList(0);
+}
+
+/**
+ * Rebuilds the listbox holding the list of identities.
+ *
+ * @param aSelectIndex Attempt to select the identity with this index.
+ */
+function refreshIdentityList(aSelectIndex)
+{
+ // Remove all children.
+ while (gIdentityListBox.hasChildNodes())
+ gIdentityListBox.lastChild.remove();
+
+ // Build the list from the identities array.
+ let identities = gAccount.identities;
+ for (let identity in fixIterator(identities,
+ Components.interfaces.nsIMsgIdentity))
+ {
+ if (identity.valid)
+ {
+ let listitem = document.createElement("listitem");
+ listitem.setAttribute("label", identity.identityName);
+ listitem.setAttribute("key", identity.key);
+ gIdentityListBox.appendChild(listitem);
+ }
+ }
+
+ // Ensure one identity is always selected.
+ if (!aSelectIndex || aSelectIndex < 0)
+ aSelectIndex = 0;
+ else if (aSelectIndex >= gIdentityListBox.itemCount)
+ aSelectIndex = gIdentityListBox.itemCount - 1;
+
+ // This also fires the onselect event, which in turn calls updateButtons().
+ gIdentityListBox.selectedIndex = aSelectIndex;
+}
+
+/**
+ * Opens the identity editor dialog.
+ *
+ * @param identity the identity (if any) to load in the dialog
+ */
+function openIdentityEditor(identity)
+{
+ let args = { identity: identity, account: gAccount, result: false };
+
+ let indexToSelect = identity ? gIdentityListBox.selectedIndex :
+ gIdentityListBox.itemCount;
+
+ window.openDialog("am-identity-edit.xul", "",
+ "chrome,modal,resizable,centerscreen", args);
+
+ if (args.result)
+ refreshIdentityList(indexToSelect);
+}
+
+function getSelectedIdentity()
+{
+ if (gIdentityListBox.selectedItems.length != 1)
+ return null;
+
+ var identityKey = gIdentityListBox.selectedItems[0].getAttribute("key");
+ let identities = gAccount.identities;
+ for (let identity in fixIterator(identities,
+ Components.interfaces.nsIMsgIdentity))
+ {
+ if (identity.valid && identity.key == identityKey)
+ return identity;
+ }
+
+ return null; // no identity found
+}
+
+function onEdit(event)
+{
+ var id = (event.target.localName == 'listbox') ? null : getSelectedIdentity();
+ openIdentityEditor(id);
+}
+
+/**
+ * Enable/disable buttons depending on number of identities and current selection.
+ */
+function updateButtons()
+{
+ // In this listbox there should always be one item selected.
+ if (gIdentityListBox.selectedItems.length != 1 || gIdentityListBox.itemCount == 0) {
+ // But in case this is not met (e.g. there is no identity for some reason,
+ // or the list is being rebuilt), disable all buttons.
+ gEditButton.setAttribute("disabled", "true");
+ gDeleteButton.setAttribute("disabled", "true");
+ gSetDefaultButton.setAttribute("disabled", "true");
+ return;
+ }
+
+ gEditButton.setAttribute("disabled", "false");
+ gDeleteButton.setAttribute("disabled", (gIdentityListBox.itemCount <= 1) ? "true" : "false");
+ gSetDefaultButton.setAttribute("disabled", (gIdentityListBox.selectedIndex == 0) ? "true" : "false");
+ // The Add command is always enabled.
+}
+
+function onSetDefault(event)
+{
+ let identity = getSelectedIdentity();
+ if (!identity)
+ return;
+
+ // If the first identity is selected, there is nothing to do.
+ if (gIdentityListBox.selectedIndex == 0)
+ return;
+
+ gAccount.defaultIdentity = identity;
+ // Rebuilt the identity list and select the moved identity again.
+ refreshIdentityList(0);
+}
+
+function onDelete(event)
+{
+ if (gIdentityListBox.itemCount <= 1) // don't support deleting the last identity
+ return;
+
+ // get delete confirmation
+ let selectedIdentity = getSelectedIdentity();
+
+ let prefsBundle = document.getElementById("bundle_prefs");
+ let confirmTitle = prefsBundle.getFormattedString("identity-delete-confirm-title",
+ [window.arguments[0].accountName]);
+ let confirmText = prefsBundle.getFormattedString("identity-delete-confirm",
+ [selectedIdentity.identityName]);
+ let confirmButton = prefsBundle.getString("identity-delete-confirm-button");
+
+ if (Services.prompt.confirmEx(window, confirmTitle, confirmText,
+ (Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING) +
+ (Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL),
+ confirmButton, null, null, null, {}))
+ return;
+
+ let selectedItemIndex = gIdentityListBox.selectedIndex;
+
+ gAccount.removeIdentity(selectedIdentity);
+
+ refreshIdentityList(selectedItemIndex);
+}
+
+function onOk()
+{
+ window.arguments[0].result = true;
+ return true;
+}
diff --git a/mailnews/base/prefs/content/am-identities-list.xul b/mailnews/base/prefs/content/am-identities-list.xul
new file mode 100644
index 000000000..2acbc45d4
--- /dev/null
+++ b/mailnews/base/prefs/content/am-identities-list.xul
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/am-identities-list.dtd">
+
+<dialog
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ buttons="accept"
+ buttonlabelaccept="&identitiesListClose.label;"
+ buttonaccesskeyaccept="&identitiesListClose.accesskey;"
+ ondialogaccept="return onOk();"
+ ondialogcancel="return onOk();"
+ style="width: 40em;">
+
+<stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+<script type="application/javascript"
+ src="chrome://messenger/content/am-identities-list.js"/>
+
+<commandset>
+ <command id="cmd_add" oncommand="openIdentityEditor(null);"/>
+ <command id="cmd_edit" oncommand="onEdit(event);" disabled="true"/>
+ <command id="cmd_default" oncommand="onSetDefault(event);" disabled="true"/>
+ <command id="cmd_delete" oncommand="onDelete(event);" disabled="true"/>
+</commandset>
+
+<keyset>
+ <key keycode="VK_INSERT" command="cmd_add"/>
+ <key keycode="VK_DELETE" command="cmd_delete"/>
+</keyset>
+
+<label control="identitiesList">&identitiesListManageDesc.label;</label>
+
+<separator class="thin"/>
+
+<hbox flex="1">
+ <listbox id="identitiesList"
+ ondblclick="onEdit(event);"
+ onselect="updateButtons();"
+ seltype="single"
+ flex="1"
+ style="height: 0px;"/>
+
+ <vbox>
+ <button id="addButton"
+ command="cmd_add" label="&identitiesListAdd.label;"
+ accesskey="&identitiesListAdd.accesskey;"/>
+ <button id="editButton" disabled="true"
+ command="cmd_edit" label="&identitiesListEdit.label;"
+ accesskey="&identitiesListEdit.accesskey;"/>
+ <button id="setDefaultButton" disabled="true"
+ command="cmd_default" label="&identitiesListDefault.label;"
+ accesskey="&identitiesListDefault.accesskey;"/>
+ <button id="deleteButton" disabled="true"
+ command="cmd_delete" label="&identitiesListDelete.label;"
+ accesskey="&identitiesListDelete.accesskey;"/>
+ </vbox>
+</hbox>
+
+</dialog>
diff --git a/mailnews/base/prefs/content/am-identity-edit.js b/mailnews/base/prefs/content/am-identity-edit.js
new file mode 100644
index 000000000..cd6750779
--- /dev/null
+++ b/mailnews/base/prefs/content/am-identity-edit.js
@@ -0,0 +1,405 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gIdentity = null; // the identity we are editing (may be null for a new identity)
+var gAccount = null; // the account the identity is (or will be) associated with
+
+function onLoadIdentityProperties()
+{
+ // extract the account
+ gIdentity = window.arguments[0].identity;
+ gAccount = window.arguments[0].account;
+ let prefBundle = document.getElementById("bundle_prefs");
+
+ // Make the dialog the same height and 90% of the width of the main Account
+ // manager page when the Account manager is not maximized.
+ let accountDialog = Services.wm.getMostRecentWindow("mailnews:accountmanager")
+ .document;
+ if (accountDialog.documentElement.getAttribute("sizemode") != "maximized") {
+ document.getElementById("identityDialog").style.width =
+ accountDialog.getElementById("accountManager").clientWidth * 0.9 + "px";
+ document.getElementById("identityDialog").style.height =
+ accountDialog.getElementById("accountManager").clientHeight + "px";
+ }
+
+ if (gIdentity) {
+ let listName = gIdentity.identityName;
+ document.title = prefBundle
+ .getFormattedString("identityDialogTitleEdit", [listName]);
+ } else {
+ document.title = prefBundle.getString("identityDialogTitleAdd");
+ }
+
+ loadSMTPServerList();
+
+ initIdentityValues(gIdentity);
+ initCopiesAndFolder(gIdentity);
+ initCompositionAndAddressing(gIdentity);
+}
+
+// based on the values of gIdentity, initialize the identity fields we expose to the user
+function initIdentityValues(identity)
+{
+ function initSmtpServer(aServerKey) {
+ // Select a server in the SMTP server menulist by its key.
+ // The value of the identity.smtpServerKey is null when the
+ // "use default server" option is used so, if we get that passed in, select
+ // the useDefaultItem representing this option by using the value of "".
+ document.getElementById("identity.smtpServerKey").value = aServerKey || "";
+ }
+
+ if (identity)
+ {
+ document.getElementById('identity.fullName').value = identity.fullName;
+ document.getElementById('identity.email').value = identity.email;
+ document.getElementById('identity.replyTo').value = identity.replyTo;
+ document.getElementById('identity.organization').value = identity.organization;
+ document.getElementById('identity.attachSignature').checked = identity.attachSignature;
+ document.getElementById('identity.htmlSigText').value = identity.htmlSigText;
+ document.getElementById('identity.htmlSigFormat').checked = identity.htmlSigFormat;
+
+ if (identity.signature)
+ document.getElementById('identity.signature').value = identity.signature.path;
+
+ document.getElementById('identity.attachVCard').checked = identity.attachVCard;
+ document.getElementById('identity.escapedVCard').value = identity.escapedVCard;
+ initSmtpServer(identity.smtpServerKey);
+
+ // This field does not exist for the default identity shown in the am-main.xul pane.
+ let idLabel = document.getElementById("identity.label");
+ if (idLabel)
+ idLabel.value = identity.label;
+ }
+ else
+ {
+ // We're adding an identity, use the best default we have.
+ initSmtpServer(gAccount.defaultIdentity.smtpServerKey);
+ }
+
+ setupSignatureItems();
+}
+
+function initCopiesAndFolder(identity)
+{
+ // if we are editing an existing identity, use it...otherwise copy our values from the default identity
+ var copiesAndFoldersIdentity = identity ? identity : gAccount.defaultIdentity;
+
+ document.getElementById('identity.fccFolder').value = copiesAndFoldersIdentity.fccFolder;
+ document.getElementById('identity.draftFolder').value = copiesAndFoldersIdentity.draftFolder;
+ document.getElementById('identity.archiveFolder').value = copiesAndFoldersIdentity.archiveFolder;
+ document.getElementById('identity.stationeryFolder').value = copiesAndFoldersIdentity.stationeryFolder;
+
+ document.getElementById('identity.fccFolderPickerMode').value = copiesAndFoldersIdentity.fccFolderPickerMode ? copiesAndFoldersIdentity.fccFolderPickerMode : 0;
+ document.getElementById('identity.draftsFolderPickerMode').value = copiesAndFoldersIdentity.draftsFolderPickerMode ? copiesAndFoldersIdentity.draftsFolderPickerMode : 0;
+ document.getElementById('identity.archivesFolderPickerMode').value = copiesAndFoldersIdentity.archivesFolderPickerMode ? copiesAndFoldersIdentity.archivesFolderPickerMode : 0;
+ document.getElementById('identity.tmplFolderPickerMode').value = copiesAndFoldersIdentity.tmplFolderPickerMode ? copiesAndFoldersIdentity.tmplFolderPickerMode : 0;
+
+ document.getElementById('identity.doCc').checked = copiesAndFoldersIdentity.doCc;
+ document.getElementById('identity.doCcList').value = copiesAndFoldersIdentity.doCcList;
+ document.getElementById('identity.doBcc').checked = copiesAndFoldersIdentity.doBcc;
+ document.getElementById('identity.doBccList').value = copiesAndFoldersIdentity.doBccList;
+ document.getElementById('identity.doFcc').checked = copiesAndFoldersIdentity.doFcc;
+ document.getElementById('identity.fccReplyFollowsParent').checked = copiesAndFoldersIdentity.fccReplyFollowsParent;
+ document.getElementById('identity.showSaveMsgDlg').checked = copiesAndFoldersIdentity.showSaveMsgDlg;
+ document.getElementById('identity.archiveEnabled').checked = copiesAndFoldersIdentity.archiveEnabled;
+
+ onInitCopiesAndFolders(); // am-copies.js method
+}
+
+function initCompositionAndAddressing(identity)
+{
+ // if we are editing an existing identity, use it...otherwise copy our values from the default identity
+ var addressingIdentity = identity ? identity : gAccount.defaultIdentity;
+
+ document.getElementById('identity.directoryServer').value = addressingIdentity.directoryServer;
+ document.getElementById('identity.overrideGlobal_Pref').value = addressingIdentity.overrideGlobalPref;
+ let autoCompleteElement = document.getElementById('identity.autocompleteToMyDomain');
+ if (autoCompleteElement) // Thunderbird does not have this element.
+ autoCompleteElement.checked = addressingIdentity.autocompleteToMyDomain;
+
+ document.getElementById('identity.composeHtml').checked = addressingIdentity.composeHtml;
+ document.getElementById('identity.autoQuote').checked = addressingIdentity.autoQuote;
+ document.getElementById('identity.replyOnTop').value = addressingIdentity.replyOnTop;
+ document.getElementById('identity.sig_bottom').value = addressingIdentity.sigBottom;
+ document.getElementById('identity.sig_on_reply').checked = addressingIdentity.sigOnReply;
+ document.getElementById('identity.sig_on_fwd').checked = addressingIdentity.sigOnForward;
+
+ onInitCompositionAndAddressing(); // am-addressing.js method
+}
+
+function onOk()
+{
+ if (!validEmailAddress())
+ return false;
+
+ // if we are adding a new identity, create an identity, set the fields and add it to the
+ // account.
+ if (!gIdentity)
+ {
+ // ask the account manager to create a new identity for us
+ gIdentity = MailServices.accounts.createIdentity();
+
+ // copy in the default identity settings so we inherit lots of stuff like the defaul drafts folder, etc.
+ gIdentity.copy(gAccount.defaultIdentity);
+
+ // assume the identity is valid by default?
+ gIdentity.valid = true;
+
+ // add the identity to the account
+ gAccount.addIdentity(gIdentity);
+
+ // now fall through to saveFields which will save our new values
+ }
+
+ // if we are modifying an existing identity, save the fields
+ saveIdentitySettings(gIdentity);
+ saveCopiesAndFolderSettings(gIdentity);
+ saveAddressingAndCompositionSettings(gIdentity);
+
+ window.arguments[0].result = true;
+
+ return true;
+}
+
+// returns false and prompts the user if
+// the identity does not have an email address
+function validEmailAddress()
+{
+ var emailAddress = document.getElementById('identity.email').value;
+
+ // quickly test for an @ sign to test for an email address. We don't have
+ // to be anymore precise than that.
+ if (!emailAddress.includes("@"))
+ {
+ // alert user about an invalid email address
+
+ var prefBundle = document.getElementById("bundle_prefs");
+
+ Services.prompt.alert(window, prefBundle.getString("identity-edit-req-title"),
+ prefBundle.getString("identity-edit-req"));
+ return false;
+ }
+
+ return true;
+}
+
+function saveIdentitySettings(identity)
+{
+ if (identity)
+ {
+ let idLabel = document.getElementById('identity.label');
+ if (idLabel)
+ identity.label = idLabel.value;
+ identity.fullName = document.getElementById('identity.fullName').value;
+ identity.email = document.getElementById('identity.email').value;
+ identity.replyTo = document.getElementById('identity.replyTo').value;
+ identity.organization = document.getElementById('identity.organization').value;
+ identity.attachSignature = document.getElementById('identity.attachSignature').checked;
+ identity.htmlSigText = document.getElementById('identity.htmlSigText').value;
+ identity.htmlSigFormat = document.getElementById('identity.htmlSigFormat').checked;
+
+ identity.attachVCard = document.getElementById('identity.attachVCard').checked;
+ identity.escapedVCard = document.getElementById('identity.escapedVCard').value;
+ identity.smtpServerKey = document.getElementById('identity.smtpServerKey').value;
+
+ var attachSignaturePath = document.getElementById('identity.signature').value;
+ identity.signature = null; // this is important so we don't accidentally inherit the default
+
+ if (attachSignaturePath)
+ {
+ // convert signature path back into a nsIFile
+ var sfile = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsIFile);
+ sfile.initWithPath(attachSignaturePath);
+ if (sfile.exists())
+ identity.signature = sfile;
+ }
+ }
+}
+
+function saveCopiesAndFolderSettings(identity)
+{
+ onSaveCopiesAndFolders(); // am-copies.js routine
+
+ identity.fccFolder = document.getElementById('identity.fccFolder').value;
+ identity.draftFolder = document.getElementById('identity.draftFolder').value;
+ identity.archiveFolder = document.getElementById('identity.archiveFolder').value;
+ identity.stationeryFolder = document.getElementById('identity.stationeryFolder').value;
+ identity.fccFolderPickerMode = document.getElementById('identity.fccFolderPickerMode').value;
+ identity.draftsFolderPickerMode = document.getElementById('identity.draftsFolderPickerMode').value;
+ identity.archivesFolderPickerMode = document.getElementById('identity.archivesFolderPickerMode').value;
+ identity.tmplFolderPickerMode = document.getElementById('identity.tmplFolderPickerMode').value;
+ identity.doCc = document.getElementById('identity.doCc').checked;
+ identity.doCcList = document.getElementById('identity.doCcList').value;
+ identity.doBcc = document.getElementById('identity.doBcc').checked;
+ identity.doBccList = document.getElementById('identity.doBccList').value;
+ identity.doFcc = document.getElementById('identity.doFcc').checked;
+ identity.fccReplyFollowsParent = document.getElementById('identity.fccReplyFollowsParent').checked;
+ identity.showSaveMsgDlg = document.getElementById('identity.showSaveMsgDlg').checked;
+ identity.archiveEnabled = document.getElementById('identity.archiveEnabled').checked;
+}
+
+function saveAddressingAndCompositionSettings(identity)
+{
+ identity.directoryServer = document.getElementById('identity.directoryServer').value;
+ identity.overrideGlobalPref = document.getElementById('identity.overrideGlobal_Pref').value == "true";
+ let autoCompleteElement = document.getElementById('identity.autocompleteToMyDomain');
+ if (autoCompleteElement) // Thunderbird does not have this element.
+ identity.autocompleteToMyDomain = autoCompleteElement.checked;
+ identity.composeHtml = document.getElementById('identity.composeHtml').checked;
+ identity.autoQuote = document.getElementById('identity.autoQuote').checked;
+ identity.replyOnTop = document.getElementById('identity.replyOnTop').value;
+ identity.sigBottom = document.getElementById('identity.sig_bottom').value == 'true';
+ identity.sigOnReply = document.getElementById('identity.sig_on_reply').checked;
+ identity.sigOnForward = document.getElementById('identity.sig_on_fwd').checked;
+}
+
+function selectFile()
+{
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+
+ var fp = Components.classes["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+
+ var prefBundle = document.getElementById("bundle_prefs");
+ var title = prefBundle.getString("choosefile");
+ fp.init(window, title, nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ // Get current signature folder, if there is one.
+ // We can set that to be the initial folder so that users
+ // can maintain their signatures better.
+ var sigFolder = GetSigFolder();
+ if (sigFolder)
+ fp.displayDirectory = sigFolder;
+
+ var ret = fp.show();
+ if (ret == nsIFilePicker.returnOK) {
+ var folderField = document.getElementById("identity.signature");
+ folderField.value = fp.file.path;
+ }
+}
+
+function GetSigFolder()
+{
+ var sigFolder = null;
+ try
+ {
+ var account = parent.getCurrentAccount();
+ var identity = account.defaultIdentity;
+ var signatureFile = identity.signature;
+
+ if (signatureFile)
+ {
+ signatureFile = signatureFile.QueryInterface(Components.interfaces.nsIFile);
+ sigFolder = signatureFile.parent;
+
+ if (!sigFolder.exists())
+ sigFolder = null;
+ }
+ }
+ catch (ex) {
+ dump("failed to get signature folder..\n");
+ }
+ return sigFolder;
+}
+
+// Signature textbox is active unless option to select from file is checked.
+// If a signature is need to be attached, the associated items which
+// displays the absolute path to the signature (in a textbox) and the way
+// to select a new signature file (a button) are enabled. Otherwise, they
+// are disabled. Check to see if the attachSignature is locked to block
+// broadcasting events.
+function setupSignatureItems()
+{
+ var signature = document.getElementById("identity.signature");
+ var browse = document.getElementById("identity.sigbrowsebutton");
+ var htmlSigText = document.getElementById("identity.htmlSigText");
+ var htmlSigFormat = document.getElementById("identity.htmlSigFormat");
+ var attachSignature = document.getElementById("identity.attachSignature");
+ var checked = attachSignature.checked;
+
+ if (checked)
+ {
+ htmlSigText.setAttribute("disabled", "true");
+ htmlSigFormat.setAttribute("disabled", "true");
+ }
+ else
+ {
+ htmlSigText.removeAttribute("disabled");
+ htmlSigFormat.removeAttribute("disabled");
+ }
+
+ if (checked && !getAccountValueIsLocked(signature))
+ signature.removeAttribute("disabled");
+ else
+ signature.setAttribute("disabled", "true");
+
+ if (checked && !getAccountValueIsLocked(browse))
+ browse.removeAttribute("disabled");
+ else
+ browse.setAttribute("disabled", "true");
+}
+
+function editVCardCallback(escapedVCardStr)
+{
+ var escapedVCard = document.getElementById("identity.escapedVCard");
+ escapedVCard.value = escapedVCardStr;
+}
+
+function editVCard()
+{
+ var escapedVCard = document.getElementById("identity.escapedVCard");
+
+ // read vCard hidden value from UI
+ window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul",
+ "",
+ "chrome,modal,resizable=no,centerscreen",
+ {escapedVCardStr:escapedVCard.value, okCallback:editVCardCallback,
+ titleProperty:"editVCardTitle", hideABPicker:true});
+}
+
+function getAccountForFolderPickerState()
+{
+ return gAccount;
+}
+
+/**
+ * Build the SMTP server list for display.
+ */
+function loadSMTPServerList()
+{
+ var smtpServerList = document.getElementById("identity.smtpServerKey");
+ let servers = MailServices.smtp.servers;
+ let defaultServer = MailServices.smtp.defaultServer;
+
+ var smtpPopup = smtpServerList.menupopup;
+ while (smtpPopup.lastChild.nodeName != "menuseparator")
+ smtpPopup.lastChild.remove();
+
+ while (servers.hasMoreElements())
+ {
+ var server = servers.getNext();
+
+ if (server instanceof Components.interfaces.nsISmtpServer)
+ {
+ var serverName = "";
+ if (server.description)
+ serverName = server.description + ' - ';
+ else if (server.username)
+ serverName = server.username + ' - ';
+ serverName += server.hostname;
+
+ if (defaultServer.key == server.key)
+ serverName += " " + document.getElementById("bundle_messenger")
+ .getString("defaultServerTag");
+
+ smtpServerList.appendItem(serverName, server.key);
+ }
+ }
+}
diff --git a/mailnews/base/prefs/content/am-identity-edit.xul b/mailnews/base/prefs/content/am-identity-edit.xul
new file mode 100644
index 000000000..448892382
--- /dev/null
+++ b/mailnews/base/prefs/content/am-identity-edit.xul
@@ -0,0 +1,154 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/am-copiesOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/am-addressingOverlay.xul"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % identityEditDTD SYSTEM "chrome://messenger/locale/am-identity-edit.dtd" >
+%identityEditDTD;
+<!ENTITY % identityDTD SYSTEM "chrome://messenger/locale/am-main.dtd" >
+%identityDTD;
+]>
+
+<dialog id="identityDialog"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoadIdentityProperties();"
+ ondialogaccept="return onOk();"
+ style="&identityDialog.style;">
+
+ <stringbundle id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"/>
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/am-prefs.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/am-identity-edit.js"/>
+
+ <broadcaster id="broadcaster_attachSignature"/>
+
+ <description flex="1">&identityListDesc.label;</description>
+ <separator class="thin"/>
+
+ <tabbox flex="1" style="overflow: auto;">
+ <tabs id="identitySettings">
+ <tab label="&settingsTab.label;"/>
+ <tab label="&copiesFoldersTab.label;"/>
+ <tab label="&addressingTab.label;"/>
+ </tabs>
+
+ <tabpanels id="identityTabsPanels" flex="1">
+ <!-- Identity Settings Tab -->
+ <vbox flex="1" name="settings">
+ <groupbox>
+ <caption label="&publicData.label;"/>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&name.label;" control="identity.fullName" accesskey="&name.accesskey;"/>
+ <textbox id="identity.fullName" size="30"/>
+ </row>
+ <row align="center">
+ <label value="&email.label;" control="identity.email" accesskey="&email.accesskey;"/>
+ <textbox id="identity.email" class="uri-element"/>
+ </row>
+ <row align="center">
+ <label value="&replyTo.label;" control="identity.replyTo" accesskey="&replyTo.accesskey;"/>
+ <textbox id="identity.replyTo" class="uri-element" placeholder="&replyTo.placeholder;"/>
+ </row>
+ <row align="center">
+ <label value="&organization.label;" control="identity.organization" accesskey="&organization.accesskey;"/>
+ <textbox id="identity.organization"/>
+ </row>
+ <separator class="thin"/>
+ <row align="center">
+ <label value="&signatureText.label;" control="identity.htmlSigText" accesskey="&signatureText.accesskey;"/>
+ <checkbox id="identity.htmlSigFormat" label="&signatureHtml.label;" accesskey="&signatureHtml.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+
+ <hbox class="indent" flex="1">
+ <textbox id="identity.htmlSigText" flex="1" multiline="true" wrap="off" rows="4" class="signatureBox"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="identity.attachSignature" label="&signatureFile.label;" flex="1"
+ accesskey="&signatureFile.accesskey;"
+ oncommand="setupSignatureItems();"/>
+ </hbox>
+
+ <hbox align="center" class="indent">
+ <textbox id="identity.signature" datatype="nsIFile" flex="1" name="identity.signature"
+ aria-labelledby="identity.attachSignature"
+ observes="broadcaster_attachSignature" class="uri-element"/>
+ <button class="push" name="browse" label="&choose.label;"
+ accesskey="&choose.accesskey;"
+ oncommand="selectFile()"
+ observes="broadcaster_attachSignature"
+ id="identity.sigbrowsebutton"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="identity.attachVCard" label="&attachVCard.label;" flex="1"
+ accesskey="&attachVCard.accesskey;"/>
+ <button class="push" name="editVCard" label="&editVCard.label;"
+ accesskey="&editVCard.accesskey;"
+ oncommand="editVCard()"/>
+ <label hidden="true" id="identity.escapedVCard"/>
+ </hbox>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&privateData.label;"/>
+
+ <hbox align="center">
+ <label value="&smtpName.label;"
+ control="identity.smtpServerKey"
+ accesskey="&smtpName.accesskey;"/>
+ <menulist id="identity.smtpServerKey" flex="1">
+ <menupopup id="smtpPopup">
+ <menuitem value=""
+ label="&smtpDefaultServer.label;"
+ id="useDefaultItem"/>
+ <menuseparator/>
+ <!-- list will be inserted here -->
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <hbox align="center">
+ <label value="&identityAlias.label;"
+ accesskey="&identityAlias.accesskey;"
+ control="identity.label"/>
+ <textbox id="identity.label" flex="1"/>
+ </hbox>
+ </groupbox>
+
+ <spacer flex="1"/>
+ </vbox>
+
+ <!-- Copies & Folders Tab -->
+ <vbox flex="1" name="copiesAndFolders" id="copiesAndFolders"/>
+
+ <!-- Composition & Addressing Tab -->
+ <vbox flex="1" name="composeAddressing" id="compositionAndAddressing"/>
+
+ </tabpanels>
+ </tabbox>
+</dialog>
diff --git a/mailnews/base/prefs/content/am-junk.js b/mailnews/base/prefs/content/am-junk.js
new file mode 100644
index 000000000..77daac2ca
--- /dev/null
+++ b/mailnews/base/prefs/content/am-junk.js
@@ -0,0 +1,296 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/MailUtils.js");
+
+var gDeferredToAccount = "";
+
+function onInit(aPageId, aServerId)
+{
+ // manually adjust several pref UI elements
+ document.getElementById('server.spamLevel.visible').setAttribute("checked",
+ document.getElementById('server.spamLevel').value > 0);
+
+ let deferredToURI = null;
+ if (gDeferredToAccount)
+ deferredToURI = MailServices.accounts
+ .getAccount(gDeferredToAccount)
+ .incomingServer.serverURI;
+
+ let spamActionTargetAccountElement =
+ document.getElementById("server.spamActionTargetAccount");
+ let spamActionTargetFolderElement =
+ document.getElementById("server.spamActionTargetFolder");
+
+ let spamActionTargetAccount = spamActionTargetAccountElement.value;
+ let spamActionTargetFolder = spamActionTargetFolderElement.value;
+
+ let moveOnSpamCheckbox = document.getElementById("server.moveOnSpam");
+ let moveOnSpamValue = moveOnSpamCheckbox.checked;
+
+ // Check if there are any invalid junk targets and fix them.
+ [ spamActionTargetAccount, spamActionTargetFolder, moveOnSpamValue ] =
+ sanitizeJunkTargets(spamActionTargetAccount,
+ spamActionTargetFolder,
+ deferredToURI || aServerId,
+ document.getElementById("server.moveTargetMode").value,
+ MailUtils.getFolderForURI(aServerId, false).server.spamSettings,
+ moveOnSpamValue);
+
+ spamActionTargetAccountElement.value = spamActionTargetAccount;
+ spamActionTargetFolderElement.value = spamActionTargetFolder;
+ moveOnSpamCheckbox.checked = moveOnSpamValue;
+
+ let server = MailUtils.getFolderForURI(spamActionTargetAccount, false);
+ document.getElementById("actionAccountPopup").selectFolder(server);
+
+ let folder = MailUtils.getFolderForURI(spamActionTargetFolder, true);
+ document.getElementById("actionFolderPopup").selectFolder(folder);
+
+ var currentArray = [];
+ if (document.getElementById("server.useWhiteList").checked)
+ currentArray = document.getElementById("server.whiteListAbURI").value.split(" ");
+
+ // set up the whitelist UI
+ var wList = document.getElementById("whiteListAbURI");
+ // Ensure the whitelist is empty
+ for (let i = wList.itemCount - 1; i >= 0; i--) {
+ wList.removeItemAt(i);
+ }
+
+ // Populate the listbox with address books
+ let abItems = [];
+ for (let ab in fixIterator(MailServices.ab.directories,
+ Components.interfaces.nsIAbDirectory)) {
+ // We skip mailing lists and remote address books.
+ if (ab.isMailList || ab.isRemote)
+ continue;
+
+ abItems.push({ label: ab.dirName, URI: ab.URI });
+ }
+
+ // Sort the list
+ function sortFunc(a, b) {
+ return a.label.localeCompare(b.label);
+ }
+ abItems.sort(sortFunc);
+
+ // And then append each item to the listbox
+ for (let abItem of abItems) {
+ let item = wList.appendItem(abItem.label, abItem.URI);
+ item.setAttribute("type", "checkbox");
+ item.setAttribute("class", "listitem-iconic");
+
+ // Due to bug 448582, we have to use setAttribute to set the
+ // checked value of the listitem.
+ item.setAttribute("checked", currentArray.includes(abItem.URI));
+ }
+
+ // enable or disable the whitelist
+ onAdaptiveJunkToggle();
+
+ // set up trusted IP headers
+ var serverFilterList = document.getElementById("useServerFilterList");
+ serverFilterList.value =
+ document.getElementById("server.serverFilterName").value;
+ if (!serverFilterList.selectedItem)
+ serverFilterList.selectedIndex = 0;
+
+ // enable or disable the useServerFilter checkbox
+ onCheckItem("useServerFilterList", ["server.useServerFilter"]);
+
+ updateJunkTargetsAndRetention();
+}
+
+function onPreInit(account, accountValues)
+{
+ if (getAccountValue(account, accountValues, "server", "type", null, false) == "pop3")
+ gDeferredToAccount = getAccountValue(account, accountValues,
+ "pop3", "deferredToAccount",
+ null, false);
+
+ buildServerFilterMenuList();
+}
+
+/**
+ * Called when someone checks or unchecks the adaptive junk mail checkbox.
+ * set the value of the hidden element accordingly
+ *
+ * @param aValue the boolean value of the checkbox
+ */
+function updateSpamLevel(aValue)
+{
+ document.getElementById('server.spamLevel').value = aValue ? 100 : 0;
+ onAdaptiveJunkToggle();
+}
+
+/**
+ * Propagate changes to the server filter menu list back to
+ * our hidden wsm element.
+ */
+function onServerFilterListChange()
+{
+ document.getElementById('server.serverFilterName').value =
+ document.getElementById("useServerFilterList").value;
+}
+
+/**
+ * Called when someone checks or unchecks the adaptive junk mail checkbox.
+ * We need to enable or disable the whitelist accordingly.
+ */
+function onAdaptiveJunkToggle()
+{
+ onCheckItem("whiteListAbURI", ["server.spamLevel.visible"]);
+ onCheckItem("whiteListLabel", ["server.spamLevel.visible"]);
+
+ // Enable/disable individual listbox rows.
+ // Setting enable/disable on the parent listbox does not seem to work.
+ let wList = document.getElementById("whiteListAbURI");
+ let wListDisabled = wList.disabled;
+
+ for (let i = 0; i < wList.getRowCount(); i++) {
+ wList.getItemAtIndex(i).setAttribute("disabled", wListDisabled);
+ }
+}
+
+/**
+ * Called when someone checks or unchecks the "move new junk messages to"
+ * Enable/disable the radio group accordingly.
+ */
+function updateJunkTargetsAndRetention() {
+ onCheckItem("server.moveTargetMode", ["server.moveOnSpam"]);
+ updateJunkTargets();
+ onCheckItem("server.purgeSpam", ["server.moveOnSpam"]);
+ document.getElementById("purgeLabel").disabled =
+ document.getElementById("server.purgeSpam").disabled;
+ updateJunkRetention();
+}
+
+/**
+ * Enable/disable the folder pickers depending on which radio item is selected.
+ */
+function updateJunkTargets() {
+ onCheckItem("actionTargetAccount", ["server.moveOnSpam", "moveTargetMode0"]);
+ onCheckItem("actionTargetFolder", ["server.moveOnSpam", "moveTargetMode1"]);
+}
+
+/**
+ * Enable/disable the junk deletion interval depending on the state
+ * of the controlling checkbox.
+ */
+function updateJunkRetention() {
+ onCheckItem("server.purgeSpamInterval", ["server.purgeSpam", "server.moveOnSpam"]);
+}
+
+function onSave()
+{
+ onSaveWhiteList();
+}
+
+/**
+ * Propagate changes to the whitelist menu list back to
+ * our hidden wsm element.
+ */
+function onSaveWhiteList()
+{
+ var wList = document.getElementById("whiteListAbURI");
+ var wlArray = [];
+
+ for (var i = 0; i < wList.getRowCount(); i++)
+ {
+ // Due to bug 448582, do not trust any properties of the listitems
+ // as they may not return the right value or may even not exist.
+ // Always get the attributes only.
+ var wlNode = wList.getItemAtIndex(i);
+ if (wlNode.getAttribute("checked") == "true") {
+ let abURI = wlNode.getAttribute("value");
+ wlArray.push(abURI);
+ }
+ }
+ var wlValue = wlArray.join(" ");
+ document.getElementById("server.whiteListAbURI").setAttribute("value", wlValue);
+ document.getElementById("server.useWhiteList").checked = (wlValue != "");
+}
+
+/**
+ * Called when a new value is chosen in one of the junk target folder pickers.
+ * Sets the menu label according to the folder name.
+ */
+function onActionTargetChange(aEvent, aWSMElementId)
+{
+ let folder = aEvent.target._folder;
+ document.getElementById(aWSMElementId).value = folder.URI;
+ document.getElementById("actionFolderPopup").selectFolder(folder);
+}
+
+/**
+ * Enumerates over the "ISPDL" directories, calling buildServerFilterListFromDir
+ * for each one.
+ */
+function buildServerFilterMenuList()
+{
+ const KEY_ISP_DIRECTORY_LIST = "ISPDL";
+ let ispHeaderList = document.getElementById("useServerFilterList");
+
+ // Ensure the menulist is empty.
+ ispHeaderList.removeAllItems();
+
+ // Now walk through the isp directories looking for sfd files.
+ let ispDirectories = Services.dirsvc.get(KEY_ISP_DIRECTORY_LIST,
+ Components.interfaces.nsISimpleEnumerator);
+
+ let menuEntries = [];
+ while (ispDirectories.hasMoreElements())
+ {
+ let ispDirectory = ispDirectories.getNext()
+ .QueryInterface(Components.interfaces.nsIFile);
+ if (ispDirectory)
+ menuEntries.push.apply(menuEntries, buildServerFilterListFromDir(ispDirectory, menuEntries));
+ }
+
+ menuEntries.sort((a, b) => a.localeCompare(b));
+ for (let entry of menuEntries) {
+ ispHeaderList.appendItem(entry, entry);
+ }
+}
+
+/**
+ * Helper function called by buildServerFilterMenuList. Enumerates over the
+ * passed in directory looking for .sfd files. For each entry found, it gets
+ * appended to the menu list.
+ *
+ * @param aDir directory to look for .sfd files
+ * @param aExistingEntries Filter names already found.
+ */
+function buildServerFilterListFromDir(aDir, aExistingEntries)
+{
+ let newEntries = [];
+ // Now iterate over each file in the directory looking for .sfd files.
+ const kSuffix = ".sfd";
+ let entries = aDir.directoryEntries
+ .QueryInterface(Components.interfaces.nsIDirectoryEnumerator);
+
+ while (entries.hasMoreElements()) {
+ let entry = entries.nextFile;
+ // we only care about files that end in .sfd
+ if (entry.isFile() && entry.leafName.endsWith(kSuffix)) {
+ let fileName = entry.leafName.slice(0, -kSuffix.length);
+ // If we've already added an item with this name, then don't add it again.
+ if (aExistingEntries.indexOf(fileName) == -1)
+ newEntries.push(fileName);
+ }
+ }
+ return newEntries;
+}
+
+/**
+ * Open the Preferences dialog on the Junk settings tab.
+ */
+function showGlobalJunkPrefs()
+{
+ openPrefsFromAccountManager("paneSecurity", "junkTab", null, "junk_pane");
+}
diff --git a/mailnews/base/prefs/content/am-junk.xul b/mailnews/base/prefs/content/am-junk.xul
new file mode 100644
index 000000000..96ee9eaf6
--- /dev/null
+++ b/mailnews/base/prefs/content/am-junk.xul
@@ -0,0 +1,232 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % junkMailDTD SYSTEM "chrome://messenger/locale/am-junk.dtd">
+%junkMailDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ title="&junkSettings.label;"
+ onload="parent.onPanelLoaded('am-junk.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-junk.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-prefs.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+
+ <dialogheader title="&junkSettings.label;"/>
+
+ <groupbox>
+ <caption label="&junkClassification.label;"/>
+
+ <label hidden="true"
+ id="server.spamLevel"
+ wsm_persist="true"
+ pref="true"
+ preftype="int"
+ prefattribute="value"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.spamLevel"/>
+ <label hidden="true"
+ id="server.spamActionTargetAccount"
+ wsm_persist="true"
+ pref="true"
+ preftype="string"
+ prefattribute="value"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.spamActionTargetAccount"/>
+ <label hidden="true"
+ id="server.spamActionTargetFolder"
+ wsm_persist="true"
+ pref="true"
+ preftype="string"
+ prefattribute="value"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.spamActionTargetFolder"/>
+ <label hidden="true"
+ id="server.whiteListAbURI"
+ wsm_persist="true"
+ pref="true"
+ preftype="string"
+ prefattribute="value"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.whiteListAbURI"/>
+ <label hidden="true"
+ id="server.serverFilterName"
+ wsm_persist="true"
+ pref="true"
+ preftype="string"
+ prefattribute="value"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.serverFilterName"/>
+
+ <checkbox id="server.spamLevel.visible"
+ oncommand="updateSpamLevel(this.checked);"
+ accesskey="&level.accesskey;"
+ prefstring="mail.server.%serverkey%.spamLevel"
+ label="&level.label;"/>
+
+ <separator class="thin"/>
+
+ <description width="1">&trainingDescription.label;</description>
+
+ <separator class="thin"/>
+ <spacer height="3"/>
+
+ <vbox class="indent">
+ <checkbox hidden="true"
+ id="server.useWhiteList"
+ genericattr="true"
+ pref="true"
+ wsm_persist="true"
+ preftype="bool"
+ prefstring="mail.server.%serverkey%.useWhiteList"/>
+ <label id="whiteListLabel"
+ accesskey="&whitelistHeader.accesskey;"
+ control="whiteListAbURI">&whitelistHeader.label;</label>
+ <listbox id="whiteListAbURI" rows="5"/>
+ </vbox>
+
+ <separator/>
+
+ <vbox>
+ <hbox>
+ <checkbox id="server.useServerFilter"
+ label="&ispHeaders.label;"
+ accesskey="&ispHeaders.accesskey;"
+ genericattr="true"
+ pref="true"
+ wsm_persist="true"
+ preftype="bool"
+ oncommand="onCheckItem('useServerFilterList', [this.id]);"
+ prefstring="mail.server.%serverkey%.useServerFilter"/>
+ <menulist id="useServerFilterList"
+ oncommand="onServerFilterListChange();"
+ aria-labelledby="server.useServerFilter"/>
+ </hbox>
+ </vbox>
+
+ <separator class="thin"/>
+
+ <description width="1">&ispHeadersWarning.label;</description>
+
+ </groupbox>
+
+ <groupbox>
+ <caption label="&junkActions.label;"/>
+
+ <checkbox id="server.moveOnSpam"
+ label="&move.label;"
+ accesskey="&move.accesskey;"
+ oncommand="updateJunkTargetsAndRetention();"
+ wsm_persist="true"
+ pref="true"
+ preftype="bool"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.moveOnSpam"/>
+
+ <radiogroup id="server.moveTargetMode"
+ aria-labelledby="server.moveOnSpam"
+ prefstring="mail.server.%serverkey%.moveTargetMode"
+ wsm_persist="true"
+ pref="true"
+ preftype="int"
+ genericattr="true"
+ oncommand="updateJunkTargets();"
+ prefvalue="value">
+
+ <grid class="specialFolderPickerGrid indent">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <radio id="moveTargetMode0"
+ value="0"
+ label="&junkFolderOn.label;"
+ accesskey="&junkFolderOn.accesskey;"/>
+ <menulist id="actionTargetAccount"
+ class="folderMenuItem"
+ aria-labelledby="moveTargetMode0">
+ <menupopup id="actionAccountPopup"
+ type="folder"
+ class="menulist-menupopup"
+ expandFolders="false"
+ mode="filing"
+ oncommand="onActionTargetChange(event, 'server.spamActionTargetAccount');"/>
+ </menulist>
+ </row>
+ <row>
+ <radio id="moveTargetMode1"
+ value="1"
+ label="&otherFolder.label;"
+ accesskey="&otherFolder.accesskey;"/>
+ <menulist id="actionTargetFolder"
+ class="folderMenuItem"
+ aria-labelledby="moveTargetMode1"
+ displayformat="verbose">
+ <menupopup id="actionFolderPopup"
+ type="folder"
+ mode="junk"
+ showFileHereLabel="true"
+ oncommand="onActionTargetChange(event, 'server.spamActionTargetFolder');"/>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+
+ <hbox align="center" class="indent">
+ <checkbox id="server.purgeSpam"
+ genericattr="true"
+ pref="true"
+ wsm_persist="true"
+ preftype="bool"
+ prefstring="mail.server.%serverkey%.purgeSpam"
+ accesskey="&purge1.accesskey;"
+ oncommand="updateJunkRetention();"
+ label="&purge1.label;"/>
+ <textbox size="3"
+ type="number"
+ min="1"
+ id="server.purgeSpamInterval"
+ genericattr="true"
+ pref="true"
+ wsm_persist="true"
+ preftype="int"
+ aria-labelledby="server.purgeSpam server.purgeSpamInterval purgeLabel"
+ prefstring="mail.server.%serverkey%.purgeSpamInterval"/>
+ <label id="purgeLabel"
+ value="&purge2.label;"
+ control="server.purgeSpamInterval"/>
+ </hbox>
+
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <hbox pack="start">
+ <button id="globalJunkPrefsLink"
+ label="&globalJunkPrefs.label;"
+ accesskey="&globalJunkPrefs.accesskey;"
+ oncommand="showGlobalJunkPrefs();"/>
+ </hbox>
+
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-main.js b/mailnews/base/prefs/content/am-main.js
new file mode 100644
index 000000000..6725239e7
--- /dev/null
+++ b/mailnews/base/prefs/content/am-main.js
@@ -0,0 +1,55 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function onInit(aPageId, aServerId)
+{
+ var accountName = document.getElementById("server.prettyName");
+ var title = document.getElementById("am-main-title");
+ var defaultTitle = title.getAttribute("defaultTitle");
+ var titleValue;
+
+ if(accountName.value)
+ titleValue = defaultTitle+" - <"+accountName.value+">";
+ else
+ titleValue = defaultTitle;
+
+ title.setAttribute("title",titleValue);
+ document.title = titleValue;
+
+ setupSignatureItems();
+}
+
+function onPreInit(account, accountValues)
+{
+ loadSMTPServerList();
+}
+
+function manageIdentities()
+{
+ // We want to save the current identity information before bringing up the multiple identities
+ // UI. This ensures that the changes are reflected in the identity list dialog
+ // onSave();
+
+ var account = parent.getCurrentAccount();
+ if (!account)
+ return;
+
+ var accountName = document.getElementById("server.prettyName").value;
+
+ var args = { account: account, accountName: accountName, result: false };
+
+ // save the current identity settings so they show up correctly
+ // if the user just changed them in the manage identities dialog
+ var identity = account.defaultIdentity;
+ saveIdentitySettings(identity);
+
+ window.openDialog("am-identities-list.xul", "", "chrome,modal,resizable=no,centerscreen", args);
+
+ if (args.result) {
+ // now re-initialize the default identity settings in case they changed
+ identity = account.defaultIdentity; // refetch the default identity in case it changed
+ initIdentityValues(identity);
+ }
+}
diff --git a/mailnews/base/prefs/content/am-main.xul b/mailnews/base/prefs/content/am-main.xul
new file mode 100644
index 000000000..53e87bec1
--- /dev/null
+++ b/mailnews/base/prefs/content/am-main.xul
@@ -0,0 +1,147 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-main.dtd" >
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="parent.onPanelLoaded('am-main.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+
+ <script type="application/javascript" src="chrome://messenger/content/am-identity-edit.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-main.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-prefs.js"/>
+
+ <broadcaster id="broadcaster_attachSignature"/>
+
+ <dialogheader id="am-main-title" defaultTitle="&accountTitle.label;"/>
+
+ <hbox align="center">
+ <label value="&accountName.label;" control="server.prettyName"
+ accesskey="&accountName.accesskey;"/>
+ <textbox wsm_persist="true" size="30" id="server.prettyName"
+ prefstring="mail.server.%serverkey%.name"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <groupbox style="width: 20em !important;" flex="1">
+ <caption label="&identityTitle.label;"/>
+ <description>&identityDesc.label;</description>
+ <separator class="thin"/>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&name.label;" control="identity.fullName"
+ accesskey="&name.accesskey;"/>
+ <textbox wsm_persist="true" id="identity.fullName" size="30"
+ prefstring="mail.identity.%identitykey%.fullName"/>
+ </row>
+ <row align="center">
+ <label value="&email.label;" control="identity.email"
+ accesskey="&email.accesskey;"/>
+ <textbox wsm_persist="true" id="identity.email"
+ prefstring="mail.identity.%identitykey%.useremail" class="uri-element"/>
+ </row>
+ <row align="center">
+ <label value="&replyTo.label;" control="identity.replyTo"
+ accesskey="&replyTo.accesskey;"/>
+ <textbox wsm_persist="true" id="identity.replyTo"
+ prefstring="mail.identity.%identitykey%.reply_to" class="uri-element"
+ placeholder="&replyTo.placeholder;"/>
+ </row>
+ <row align="center">
+ <label value="&organization.label;" control="identity.organization"
+ accesskey="&organization.accesskey;"/>
+ <textbox wsm_persist="true" id="identity.organization"
+ prefstring="mail.identity.%identitykey%.organization"/>
+ </row>
+ <separator class="thin"/>
+ <row align="center">
+ <label value="&signatureText.label;" control="identity.htmlSigText" accesskey="&signatureText.accesskey;"/>
+ <checkbox wsm_persist="true" id="identity.htmlSigFormat" label="&signatureHtml.label;"
+ prefattribute="value" accesskey="&signatureHtml.accesskey;"
+ prefstring="mail.identity.%identitykey%.htmlSigFormat"/>
+ </row>
+ </rows>
+ </grid>
+
+ <hbox class="indent" flex="1" style="min-height: 50px;">
+ <textbox wsm_persist="true" id="identity.htmlSigText" multiline="true" wrap="off" rows="4" flex="1"
+ prefstring="mail.identity.%identitykey%.htmlSigText" class="signatureBox"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.attachSignature" label="&signatureFile.label;" flex="1"
+ accesskey="&signatureFile.accesskey;"
+ oncommand="setupSignatureItems();"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.attach_signature"/>
+ </hbox>
+
+ <hbox align="center" class="indent">
+ <textbox wsm_persist="true" id="identity.signature" datatype="nsIFile" flex="1" name="identity.signature"
+ aria-labelledby="identity.attachSignature"
+ observes="broadcaster_attachSignature"
+ prefstring="mail.identity.%identitykey%.sig_file" class="uri-element"/>
+ <button class="push" name="browse" label="&choose.label;"
+ accesskey="&choose.accesskey;"
+ oncommand="selectFile()"
+ observes="broadcaster_attachSignature"
+ wsm_persist="true" id="identity.sigbrowsebutton"
+ prefstring="mail.identity.%identitykey%.sigbrowse.disable"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.attachVCard" label="&attachVCard.label;" flex="1"
+ accesskey="&attachVCard.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.attach_vcard"/>
+ <button class="push" name="editVCard" label="&editVCard.label;"
+ accesskey="&editVCard.accesskey;"
+ oncommand="editVCard()"/>
+ <label hidden="true" wsm_persist="true" id="identity.escapedVCard"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.escapedVCard"/>
+ </hbox>
+
+ <separator class="thin"/>
+ <hbox align="center">
+ <label value="&smtpName.label;" control="identity.smtpServerKey"
+ accesskey="&smtpName.accesskey;"/>
+ <menulist wsm_persist="true" id="identity.smtpServerKey" flex="1"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.smtpServer">
+ <menupopup id="smtpPopup">
+ <menuitem value="" label="&smtpDefaultServer.label;" id="useDefaultItem"/>
+ <menuseparator/>
+ <!-- list will be inserted here -->
+ </menupopup>
+ </menulist>
+ </hbox>
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <hbox align="center">
+ <spacer flex="1"/>
+ <button label="&manageIdentities.label;" oncommand="manageIdentities(event);"
+ accesskey="&manageIdentities.accesskey;"
+ wsm_persist="true" id="identity.manageIdentitiesbutton"/>
+ </hbox>
+
+ <spacer flex="1"/>
+ </vbox>
+
+</page>
diff --git a/mailnews/base/prefs/content/am-offline.js b/mailnews/base/prefs/content/am-offline.js
new file mode 100644
index 000000000..3f45db967
--- /dev/null
+++ b/mailnews/base/prefs/content/am-offline.js
@@ -0,0 +1,351 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+
+var gIncomingServer;
+var gServerType;
+var gImapIncomingServer;
+var gPref = null;
+var gLockedPref = null;
+var gOfflineMap = null; // map of folder URLs to offline flags
+
+function onInit(aPageId, aServerId)
+{
+ onLockPreference();
+
+ // init values here
+ initServerSettings();
+ initRetentionSettings();
+ initDownloadSettings();
+ initOfflineSettings();
+
+ onCheckItem("offline.notDownloadMin", "offline.notDownload");
+ onCheckItem("nntp.downloadMsgMin", "nntp.downloadMsg");
+ onCheckItem("nntp.removeBodyMin", "nntp.removeBody");
+ onCheckKeepMsg();
+}
+
+function initOfflineSettings()
+{
+ gOfflineMap = collectOfflineFolders();
+}
+
+function initServerSettings()
+{
+ document.getElementById("offline.notDownload").checked = gIncomingServer.limitOfflineMessageSize;
+ document.getElementById("autosync.notDownload").checked = gIncomingServer.limitOfflineMessageSize;
+ if(gIncomingServer.maxMessageSize > 0)
+ document.getElementById("offline.notDownloadMin").value = gIncomingServer.maxMessageSize;
+ else
+ document.getElementById("offline.notDownloadMin").value = "50";
+
+ if(gServerType == "imap") {
+ gImapIncomingServer = gIncomingServer.QueryInterface(Components.interfaces.nsIImapIncomingServer);
+ document.getElementById("offline.folders").checked = gImapIncomingServer.offlineDownload;
+ }
+}
+
+function initRetentionSettings()
+{
+ let retentionSettings = gIncomingServer.retentionSettings;
+ initCommonRetentionSettings(retentionSettings);
+
+ document.getElementById("nntp.removeBody").checked = retentionSettings.cleanupBodiesByDays;
+ document.getElementById("nntp.removeBodyMin").value =
+ (retentionSettings.daysToKeepBodies > 0) ? retentionSettings.daysToKeepBodies : 30;
+}
+
+function initDownloadSettings()
+{
+ let downloadSettings = gIncomingServer.downloadSettings;
+ document.getElementById("nntp.downloadMsg").checked = downloadSettings.downloadByDate;
+ document.getElementById("nntp.notDownloadRead").checked = downloadSettings.downloadUnreadOnly;
+ document.getElementById("nntp.downloadMsgMin").value =
+ (downloadSettings.ageLimitOfMsgsToDownload > 0) ?
+ downloadSettings.ageLimitOfMsgsToDownload : 30;
+
+ // Figure out what the most natural division of the autosync pref into
+ // a value and an interval is.
+ let autosyncSelect = document.getElementById("autosyncSelect");
+ let autosyncInterval = document.getElementById("autosyncInterval");
+ let autosyncValue = document.getElementById("autosyncValue");
+ let autosyncPref = document.getElementById("imap.autoSyncMaxAgeDays");
+ let autosyncPrefValue = (autosyncPref.value == "") ? -1 :
+ parseInt(autosyncPref.value, 10);
+
+ // Clear the preference until we're done initializing.
+ autosyncPref.value = "";
+
+ if (autosyncPrefValue <= 0) {
+ // Special-case values <= 0 to have an interval of "All" and disabled
+ // controls for value and interval.
+ autosyncSelect.value = 0;
+ autosyncInterval.value = 1;
+ autosyncInterval.disabled = true;
+ autosyncValue.value = 30;
+ autosyncValue.disabled = true;
+ }
+ else {
+ // Otherwise, get the list of possible intervals, in order from
+ // largest to smallest.
+ let valuesToTest = [];
+ for (let i = autosyncInterval.itemCount - 1; i >= 0; i--)
+ valuesToTest.push(autosyncInterval.getItemAtIndex(i).value);
+
+ // and find the first one that divides the preference evenly.
+ for (let i in valuesToTest) {
+ if (!(autosyncPrefValue % valuesToTest[i])) {
+ autosyncSelect.value = 1;
+ autosyncInterval.value = valuesToTest[i];
+ autosyncValue.value = autosyncPrefValue / autosyncInterval.value;
+ break;
+ }
+ }
+ autosyncInterval.disabled = false;
+ autosyncValue.disabled = false;
+ }
+ autosyncPref.value = autosyncPrefValue;
+}
+
+
+function onPreInit(account, accountValues)
+{
+ gServerType = getAccountValue(account, accountValues, "server", "type", null, false);
+ hideShowControls(gServerType);
+ gIncomingServer = account.incomingServer;
+ gIncomingServer.type = gServerType;
+
+ // 10 is OFFLINE_SUPPORT_LEVEL_REGULAR, see nsIMsgIncomingServer.idl
+ // currently, there is no offline without diskspace
+ var titleStringID = (gIncomingServer.offlineSupportLevel >= 10) ?
+ "prefPanel-synchronization" : "prefPanel-diskspace";
+
+ var prefBundle = document.getElementById("bundle_prefs");
+ var headertitle = document.getElementById("headertitle");
+ headertitle.setAttribute('title',prefBundle.getString(titleStringID));
+ document.title = prefBundle.getString(titleStringID);
+
+ if (gServerType == "pop3") {
+ var pop3Server = gIncomingServer.QueryInterface(Components.interfaces.nsIPop3IncomingServer);
+ // hide retention settings for deferred accounts
+ if (pop3Server.deferredToAccount.length) {
+ var retentionRadio = document.getElementById("retention.keepMsg");
+ retentionRadio.setAttribute("hidden", "true");
+ var retentionLabel = document.getElementById("retentionDescriptionPop");
+ retentionLabel.setAttribute("hidden", "true");
+ var applyToFlaggedCheckbox = document.getElementById("retention.applyToFlagged");
+ applyToFlaggedCheckbox.setAttribute("hidden", "true");
+ }
+ }
+}
+
+function onClickSelect()
+{
+
+ top.window.openDialog("chrome://messenger/content/msgSelectOffline.xul", "", "centerscreen,chrome,modal,titlebar,resizable=yes");
+ return true;
+
+}
+
+/**
+ * Handle updates to the Autosync
+ */
+function onAutosyncChange()
+{
+ let autosyncSelect = document.getElementById("autosyncSelect");
+ let autosyncInterval = document.getElementById("autosyncInterval");
+ let autosyncValue = document.getElementById("autosyncValue");
+ let autosyncPref = document.getElementById("imap.autoSyncMaxAgeDays");
+
+ // If we're not done initializing, don't do anything.
+ // (See initDownloadSettings() for more details.)
+ if (autosyncPref.value == "")
+ return;
+
+ // If the user selected the All option, disable the autosync and the
+ // textbox.
+ if (autosyncSelect.value == 0) {
+ autosyncPref.value = -1;
+ autosyncInterval.disabled = true;
+ autosyncValue.disabled = true;
+ return;
+ }
+
+ let max = 0x7FFFFFFF / (60 * 60 * 24 * autosyncInterval.value);
+ autosyncValue.setAttribute("max", max);
+ if (autosyncValue.value > max)
+ autosyncValue.value = Math.floor(max);
+
+ autosyncInterval.disabled = false;
+ autosyncValue.disabled = false;
+ autosyncPref.value = autosyncValue.value * autosyncInterval.value;
+}
+
+function onAutosyncNotDownload()
+{
+ // This function is called when the autosync version of offline.notDownload
+ // is changed it simply copies the new checkbox value over to the element
+ // driving the preference.
+ document.getElementById("offline.notDownload").checked =
+ document.getElementById("autosync.notDownload").checked;
+ onCheckItem("offline.notDownloadMin", "offline.notDownload");
+}
+
+function onCancel()
+{
+ // restore the offline flags for all folders
+ restoreOfflineFolders(gOfflineMap);
+ return true;
+}
+
+function onSave()
+{
+ var downloadSettings =
+ Components.classes["@mozilla.org/msgDatabase/downloadSettings;1"]
+ .createInstance(Components.interfaces.nsIMsgDownloadSettings);
+
+ gIncomingServer.limitOfflineMessageSize = document.getElementById("offline.notDownload").checked;
+ gIncomingServer.maxMessageSize = document.getElementById("offline.notDownloadMin").value;
+
+ var retentionSettings = saveCommonRetentionSettings(gIncomingServer.retentionSettings);
+
+ retentionSettings.daysToKeepBodies = document.getElementById("nntp.removeBodyMin").value;
+ retentionSettings.cleanupBodiesByDays = document.getElementById("nntp.removeBody").checked;
+
+ downloadSettings.downloadByDate = document.getElementById("nntp.downloadMsg").checked;
+ downloadSettings.downloadUnreadOnly = document.getElementById("nntp.notDownloadRead").checked;
+ downloadSettings.ageLimitOfMsgsToDownload = document.getElementById("nntp.downloadMsgMin").value;
+
+ gIncomingServer.retentionSettings = retentionSettings;
+ gIncomingServer.downloadSettings = downloadSettings;
+
+ if (gImapIncomingServer) {
+ // Set the pref on the incomingserver, and set the flag on all folders.
+ gImapIncomingServer.offlineDownload = document.getElementById("offline.folders").checked;
+ }
+}
+
+// Does the work of disabling an element given the array which contains xul id/prefstring pairs.
+// Also saves the id/locked state in an array so that other areas of the code can avoid
+// stomping on the disabled state indiscriminately.
+function disableIfLocked( prefstrArray )
+{
+ if (!gLockedPref)
+ gLockedPref = new Array;
+
+ for (var i=0; i<prefstrArray.length; i++) {
+ var id = prefstrArray[i].id;
+ var element = document.getElementById(id);
+ if (gPref.prefIsLocked(prefstrArray[i].prefstring)) {
+ element.disabled = true;
+ gLockedPref[id] = true;
+ } else {
+ element.removeAttribute("disabled");
+ gLockedPref[id] = false;
+ }
+ }
+}
+
+// Disables xul elements that have associated preferences locked.
+function onLockPreference()
+{
+ var isDownloadLocked = false;
+ var isGetNewLocked = false;
+ var initPrefString = "mail.server";
+ var finalPrefString;
+
+ // This panel does not use the code in AccountManager.js to handle
+ // the load/unload/disable. keep in mind new prefstrings and changes
+ // to code in AccountManager, and update these as well.
+ var allPrefElements = [
+ { prefstring:"limit_offline_message_size", id:"offline.notDownload"},
+ { prefstring:"limit_offline_message_size", id:"autosync.notDownload"},
+ { prefstring:"max_size", id:"offline.notDownloadMin"},
+ { prefstring:"downloadUnreadOnly", id:"nntp.notDownloadRead"},
+ { prefstring:"downloadByDate", id:"nntp.downloadMsg"},
+ { prefstring:"ageLimit", id:"nntp.downloadMsgMin"},
+ { prefstring:"retainBy", id:"retention.keepMsg"},
+ { prefstring:"daysToKeepHdrs", id:"retention.keepOldMsgMin"},
+ { prefstring:"numHdrsToKeep", id:"retention.keepNewMsgMin"},
+ { prefstring:"daysToKeepBodies", id:"nntp.removeBodyMin"},
+ { prefstring:"cleanupBodies", id:"nntp.removeBody" },
+ { prefstring:"applyToFlagged", id:"retention.applyToFlagged"},
+ { prefstring:"disable_button.selectFolder", id:"selectNewsgroupsButton"},
+ { prefstring:"disable_button.selectFolder", id:"selectImapFoldersButton"}
+ ];
+
+ finalPrefString = initPrefString + "." + gIncomingServer.key + ".";
+ gPref = Services.prefs.getBranch(finalPrefString);
+
+ disableIfLocked( allPrefElements );
+}
+
+function onCheckItem(changeElementId, checkElementId)
+{
+ var element = document.getElementById(changeElementId);
+ var checked = document.getElementById(checkElementId).checked;
+ if(checked && !gLockedPref[checkElementId] ) {
+ element.removeAttribute("disabled");
+ }
+ else {
+ element.setAttribute("disabled", "true");
+ }
+}
+
+function toggleOffline()
+{
+ let offline = document.getElementById("offline.folders").checked;
+ let allFolders = gIncomingServer.rootFolder.descendants;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder)) {
+ if (offline)
+ folder.setFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ else
+ folder.clearFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ }
+}
+
+function collectOfflineFolders()
+{
+ let offlineFolderMap = {};
+ let allFolders = gIncomingServer.rootFolder.descendants;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder))
+ offlineFolderMap[folder.folderURL] = folder.getFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+
+ return offlineFolderMap;
+}
+
+function restoreOfflineFolders(offlineFolderMap)
+{
+ let allFolders = gIncomingServer.rootFolder.descendants;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder)) {
+ if (offlineFolderMap[folder.folderURL])
+ folder.setFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ else
+ folder.clearFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ }
+}
+
+/**
+ * Checks if the user selected a permanent removal of messages from a server
+ * listed in the confirmfor attribute and warns about it.
+ *
+ * @param aRadio The radiogroup element containing the retention options.
+ */
+function warnServerRemove(aRadio)
+{
+ let confirmFor = aRadio.getAttribute("confirmfor");
+
+ if (confirmFor && confirmFor.split(',').includes(gServerType) && aRadio.value != 1) {
+ let prefBundle = document.getElementById("bundle_prefs");
+ let title = prefBundle.getString("removeFromServerTitle");
+ let question = prefBundle.getString("removeFromServer");
+ if (!Services.prompt.confirm(window, title, question)) {
+ // If the user doesn't agree, fall back to not deleting anything.
+ aRadio.value = 1;
+ onCheckKeepMsg();
+ }
+ }
+}
diff --git a/mailnews/base/prefs/content/am-offline.xul b/mailnews/base/prefs/content/am-offline.xul
new file mode 100644
index 000000000..0c312dd74
--- /dev/null
+++ b/mailnews/base/prefs/content/am-offline.xul
@@ -0,0 +1,158 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-offline.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="parent.onPanelLoaded('am-offline.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/retention.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-offline.js"/>
+
+ <label hidden="true" wsm_persist="true" id="server.type"/>
+ <label id="imap.autoSyncMaxAgeDays" hidden="true"
+ wsm_persist="true" preftype="int"
+ prefstring="mail.server.%serverkey%.autosync_max_age_days"/>
+
+ <dialogheader id="headertitle"/>
+
+ <groupbox id="offline.titlebox" hidefor="movemail,pop3,none,rss">
+ <caption label="&syncGroupTitle.label;"/>
+
+ <checkbox hidefor="movemail,pop3,nntp,none"
+ id="offline.folders" label="&allFoldersOffline.label;"
+ oncommand="toggleOffline()"
+ accesskey="&allFoldersOffline.accesskey;"/>
+
+ <separator class="thin" hidefor="movemail,pop3,nntp,none"/>
+
+ <hbox hidefor="movemail,pop3,nntp,none" align="right">
+ <button label="&offlineImapAdvancedOffline.label;" accesskey="&offlineImapAdvancedOffline.accesskey;"
+ oncommand="onClickSelect()" id="selectImapFoldersButton" class="selectForOfflineUseButton"/>
+ </hbox>
+
+ <hbox hidefor="movemail,pop3,imap,none" align="right">
+ <button label="&offlineSelectNntp.label;" accesskey="&offlineSelectNntp.accesskey;"
+ oncommand="onClickSelect()" id="selectNewsgroupsButton" class="selectForOfflineUseButton"/>
+ </hbox>
+
+ </groupbox>
+
+ <groupbox id="diskspace.titlebox">
+ <caption label="&diskspaceGroupTitle.label;" hidefor="movemail,pop3,none,rss"/>
+
+ <description hidefor="pop3,nntp,movemail,none,rss">&doNotDownloadImap.label;</description>
+ <description hidefor="pop3,imap,movemail,none,rss">&doNotDownloadNntp.label;</description>
+ <description hidefor="imap,nntp,none,rss">&doNotDownloadPop3Movemail.label;</description>
+
+ <!-- IMAP Autosync Preference -->
+ <radiogroup hidefor="pop3,nntp,movemail,none,rss" id="autosyncSelect" class="indent">
+ <radio id="useAutosync.AllMsg" value="0" accesskey="&allAutosync.accesskey;"
+ label="&allAutosync.label;" oncommand="onAutosyncChange();"/>
+ <hbox flex="1" align="center">
+ <radio id="useAutosync.ByAge" accesskey="&ageAutosync.accesskey;"
+ value="1" label="&ageAutosyncBefore.label;" oncommand="onAutosyncChange();"/>
+ <textbox id="autosyncValue" type="number" size="4" min="1"
+ class="autosync" onchange="onAutosyncChange();"
+ aria-labelledby="ageAutosyncBefore autosyncValue ageAutosyncMiddle autosyncInterval ageAutosyncAfter"/>
+ <label id="ageAutosyncMiddle" control="autosyncValue" value="&ageAutosyncMiddle.label;"/>
+ <menulist id="autosyncInterval" onselect="onAutosyncChange();">
+ <menupopup>
+ <menuitem label="&dayAgeInterval.label;" value="1"/>
+ <menuitem label="&weekAgeInterval.label;" value="7"/>
+ <menuitem label="&monthAgeInterval.label;" value="31"/>
+ <menuitem label="&yearAgeInterval.label;" value="365"/>
+ </menupopup>
+ </menulist>
+ <label id="ageAutosyncAfter" control="autosyncInterval" value="&ageAutosyncAfter.label;"/>
+ </hbox>
+ </radiogroup>
+
+ <hbox align="center" class="indent" hidefor="rss">
+ <checkbox hidefor="movemail,pop3,imap,none"
+ id="nntp.notDownloadRead" wsm_persist="true"
+ label="&nntpNotDownloadRead.label;"
+ accesskey="&nntpNotDownloadRead.accesskey;"/>
+ </hbox>
+
+ <hbox align="center" class="indent" hidefor="none,rss">
+ <checkbox wsm_persist="true" id="offline.notDownload" hidefor="imap"
+ label="&offlineNotDownload.label;"
+ accesskey="&offlineNotDownload.accesskey;"
+ oncommand="onCheckItem('offline.notDownloadMin', 'offline.notDownload');"/>
+ <checkbox wsm_persist="true" id="autosync.notDownload" hidefor="pop3,nntp,movemail"
+ label="&autosyncNotDownload.label;"
+ accesskey="&autosyncNotDownload.accesskey;"
+ oncommand="onAutosyncNotDownload();"/>
+ <textbox wsm_persist="true" id="offline.notDownloadMin"
+ type="number" min="1" increment="10" size="4" value="50"
+ aria-labelledby="offline.notDownload offline.notDownloadMin kbLabel"/>
+ <label value="&kb.label;" control="offline.notDownloadMin" id="kbLabel"/>
+ </hbox>
+
+ <hbox align="center" class="indent" hidefor="movemail,pop3,imap,none,rss">
+ <checkbox wsm_persist="true" id="nntp.downloadMsg"
+ label="&nntpDownloadMsg.label;"
+ accesskey="&nntpDownloadMsg.accesskey;"
+ oncommand="onCheckItem('nntp.downloadMsgMin', 'nntp.downloadMsg');"/>
+ <textbox wsm_persist="true" id="nntp.downloadMsgMin"
+ type="number" min="1" size="2" value="30"
+ aria-labelledby="nntp.downloadMsg nntp.downloadMsgMin daysOldLabel"/>
+ <label value="&daysOld.label;" control="nntp.downloadMsgMin"
+ id="daysOldLabel"/>
+ </hbox>
+
+ <vbox align="start">
+ <separator hidefor="none,rss"/>
+ <label id="retentionDescription" hidefor="imap,pop3" class="desc" control="retention.keepMsg">&retentionCleanup.label;</label>
+ <label id="retentionDescriptionImap" hidefor="movemail,pop3,nntp,none,rss" class="desc" control="retention.keepMsg">&retentionCleanupImap.label;</label>
+ <label id="retentionDescriptionPop" hidefor="movemail,imap,nntp,none,rss" class="desc" control="retention.keepMsg">&retentionCleanupPop.label;</label>
+
+ <radiogroup hidefor="" confirmfor="imap,pop3" id="retention.keepMsg" class="indent"
+ oncommand="warnServerRemove(this);">
+ <radio id="retention.keepAllMsg" value="1" accesskey="&retentionKeepAll.accesskey;"
+ label="&retentionKeepAll.label;" oncommand="onCheckKeepMsg();"/>
+ <hbox flex="1" align="center">
+ <radio id="retention.keepNewMsg" accesskey="&retentionKeepRecent.accesskey;"
+ value="3" label="&retentionKeepRecent.label;" oncommand="onCheckKeepMsg();"/>
+ <textbox id="retention.keepNewMsgMin"
+ type="number" min="1" increment="10" size="4" value="2000"
+ aria-labelledby="retention.keepNewMsg retention.keepNewMsgMin newMsgLabel"/>
+ <label value="&message.label;" control="retention.keepNewMsgMin" id="newMsgLabel"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio id="retention.keepOldMsg" accesskey="&retentionKeepMsg.accesskey;"
+ value="2" label="&retentionKeepMsg.label;" oncommand="onCheckKeepMsg();"/>
+ <textbox id="retention.keepOldMsgMin"
+ type="number" min="1" size="4" value="30"
+ aria-labelledby="retention.keepOldMsg retention.keepOldMsgMin oldMsgLabel"/>
+ <label value="&daysOld.label;" control="retention.keepOldMsgMin" id="oldMsgLabel"/>
+ </hbox>
+ </radiogroup>
+
+ <hbox align="center" class="indent">
+ <checkbox id="retention.applyToFlagged"
+ label="&retentionApplyToFlagged.label;" hidefor=""
+ accesskey="&retentionApplyToFlagged.accesskey;"
+ checked="true"/>
+ </hbox>
+ <hbox align="center" class="indent" hidefor="movemail,pop3,imap,none,rss">
+ <checkbox id="nntp.removeBody" accesskey="&nntpRemoveMsgBody.accesskey;"
+ label="&nntpRemoveMsgBody.label;" oncommand="onCheckItem('nntp.removeBodyMin','nntp.removeBody');"/>
+ <textbox id="nntp.removeBodyMin" size="2" value="30"
+ type="number" min="1"
+ aria-labelledby="nntp.removeBody nntp.removeBodyMin daysOldMsg"/>
+ <label value="&daysOld.label;" control="nntp.removeBodyMin" id="daysOldMsg"/>
+ </hbox>
+ </vbox>
+ </groupbox>
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-prefs.js b/mailnews/base/prefs/content/am-prefs.js
new file mode 100644
index 000000000..6f88af016
--- /dev/null
+++ b/mailnews/base/prefs/content/am-prefs.js
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* functions for disabling front end elements when the appropriate
+ back-end preference is locked. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Prefs in MailNews require dynamic portions to indicate
+ * which of multiple servers or identities. This function
+ * takes a string and a xul element.
+ *
+ * @param aStr The string is a prefstring with a token %tokenname%.
+ * @param aElement The xul element has an attribute of name |tokenname|
+ * whose value is substituted into the string and returned
+ * by the function.
+ *
+ * Any tokens which do not have associated attribute value
+ * are not substituted, and left in the string as-is.
+ */
+function substPrefTokens(aStr, aElement)
+{
+ let tokenpat = /%(\w+)%/;
+ let token;
+ let newprefstr = "";
+
+ let prefPartsArray = aStr.split(".");
+ /* here's a little loop that goes through
+ each part of the string separated by a dot, and
+ if any parts are of the form %string%, it will replace
+ them with the value of the attribute of that name from
+ the xul object */
+ for (let i = 0; i < prefPartsArray.length; i++) {
+ token = prefPartsArray[i].match(tokenpat);
+ if (token) { /* we've got a %% match */
+ if (token[1]) {
+ if (aElement[token[1]]) {
+ newprefstr += aElement[token[1]] + "."; // here's where we get the info
+ } else { /* all we got was this stinkin % */
+ newprefstr += prefPartsArray[i] + ".";
+ }
+ }
+ } else /* if (token) */ {
+ newprefstr += prefPartsArray[i] + ".";
+ }
+ }
+ newprefstr = newprefstr.slice(0, -1); // remove the last char, a dot
+ if (newprefstr.length <= 0)
+ newprefstr = null;
+
+ return newprefstr;
+}
+
+/**
+ * A simple function to check if a pref in an element is locked.
+ *
+ * @param aElement a xul element with the pref related attributes
+ * (pref, preftype, prefstring)
+ * @return whether the prefstring specified in that element is
+ * locked (true/false).
+ * If it does not have a valid prefstring, a false is returned.
+ */
+function getAccountValueIsLocked(aElement)
+{
+ let prefstring = aElement.getAttribute("prefstring");
+ if (prefstring) {
+ let prefstr = substPrefTokens(prefstring, aElement);
+ // see if the prefstring is locked
+ if (prefstr)
+ return Services.prefs.prefIsLocked(prefstr);
+ }
+ return false;
+}
+
+/**
+ * Enables/disables element (slave) according to the checked state
+ * of another elements (masters).
+ *
+ * @param aChangeElementId Slave element which should be enabled
+ * if all the checkElementIDs are checked.
+ * Otherwise it gets disabled.
+ * @param aCheckElementIds An array of IDs of the master elements.
+ *
+ * See bug 728681 for the pattern on how this is used.
+ */
+function onCheckItem(aChangeElementId, aCheckElementIds)
+{
+ let elementToControl = document.getElementById(aChangeElementId);
+ let disabled = false;
+
+ for (let notifyId of aCheckElementIds) {
+ let notifyElement = document.getElementById(notifyId);
+ let notifyElementState = null;
+ if ("checked" in notifyElement)
+ notifyElementState = notifyElement.checked;
+ else if ("selected" in notifyElement)
+ notifyElementState = notifyElement.selected;
+ else
+ Components.utils.reportError("Unknown type of control element: " + notifyElement.id);
+
+ if (!notifyElementState) {
+ disabled = true;
+ break;
+ }
+ }
+
+ if (!disabled && getAccountValueIsLocked(elementToControl))
+ disabled = true;
+
+ elementToControl.disabled = disabled;
+}
diff --git a/mailnews/base/prefs/content/am-server-advanced.js b/mailnews/base/prefs/content/am-server-advanced.js
new file mode 100644
index 000000000..9232f525a
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server-advanced.js
@@ -0,0 +1,157 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// pull stuff out of window.arguments
+var gServerSettings = window.arguments[0];
+
+var gFirstDeferredAccount;
+// initialize the controls with the "gServerSettings" argument
+
+var gControls;
+function getControls()
+{
+ if (!gControls)
+ gControls = document.getElementsByAttribute("amsa_persist", "true");
+ return gControls;
+}
+
+function getLocalFoldersAccount()
+{
+ return MailServices.accounts
+ .FindAccountForServer(MailServices.accounts.localFoldersServer);
+}
+
+function onLoad()
+{
+ var prettyName = gServerSettings.serverPrettyName;
+
+ if (prettyName)
+ document.getElementById("serverPrettyName").value =
+ document.getElementById("bundle_prefs")
+ .getFormattedString("forAccount", [prettyName]);
+
+ if (gServerSettings.serverType == "imap")
+ {
+ document.getElementById("pop3Panel").hidden = true;
+ }
+ else if (gServerSettings.serverType == "pop3")
+ {
+ document.getElementById("imapPanel").hidden = true;
+ let radioGroup = document.getElementById("folderStorage");
+
+ gFirstDeferredAccount = gServerSettings.deferredToAccount;
+ let folderPopup = document.getElementById("deferredServerPopup");
+
+ // The current account should not be shown in the folder picker
+ // of the "other account" option.
+ folderPopup._teardown();
+ folderPopup.setAttribute("excludeServers",
+ gServerSettings.account.incomingServer.key);
+ folderPopup._ensureInitialized();
+
+ if (gFirstDeferredAccount.length)
+ {
+ // The current account is deferred.
+ let account = MailServices.accounts.getAccount(gFirstDeferredAccount);
+ radioGroup.value = "otherAccount";
+ folderPopup.selectFolder(account.incomingServer.rootFolder);
+ }
+ else
+ {
+ // Current account is not deferred.
+ radioGroup.value = "currentAccount";
+ // If there are no suitable accounts to defer to, then the menulist is
+ // disabled by the picker with an appropriate message.
+ folderPopup.selectFolder();
+ if (gServerSettings.account.incomingServer.isDeferredTo) {
+ // Some other account already defers to this account
+ // therefore this one can't be deferred further.
+ radioGroup.disabled = true;
+ }
+ }
+
+ let picker = document.getElementById("deferredServerFolderPicker");
+ picker.disabled = radioGroup.selectedIndex != 1;
+ }
+
+ var controls = getControls();
+
+ for (var i = 0; i < controls.length; i++)
+ {
+ var slot = controls[i].id;
+ if (slot in gServerSettings)
+ {
+ if (controls[i].localName == "checkbox")
+ controls[i].checked = gServerSettings[slot];
+ else
+ controls[i].value = gServerSettings[slot];
+ }
+ }
+}
+
+function onOk()
+{
+ // Handle account deferral settings for POP3 accounts.
+ if (gServerSettings.serverType == "pop3")
+ {
+ var radioGroup = document.getElementById("folderStorage");
+ var gPrefsBundle = document.getElementById("bundle_prefs");
+ let picker = document.getElementById("deferredServerFolderPicker");
+
+ // This account wasn't previously deferred, but is now deferred.
+ if (radioGroup.value != "currentAccount" && !gFirstDeferredAccount.length)
+ {
+ // If the user hasn't selected a folder, keep the default.
+ if (!picker.selectedItem)
+ return true;
+
+ var confirmDeferAccount =
+ gPrefsBundle.getString("confirmDeferAccountWarning");
+
+ var confirmTitle = gPrefsBundle.getString("confirmDeferAccountTitle");
+
+ if (!Services.prompt.confirm(window, confirmTitle, confirmDeferAccount))
+ return false;
+ }
+ switch (radioGroup.value)
+ {
+ case "currentAccount":
+ gServerSettings['deferredToAccount'] = "";
+ break;
+ case "otherAccount":
+ let server = picker.selectedItem._folder.server;
+ let account = MailServices.accounts.FindAccountForServer(server);
+ gServerSettings['deferredToAccount'] = account.key;
+ break;
+ }
+ }
+
+ // Save the controls back to the "gServerSettings" array.
+ var controls = getControls();
+ for (var i = 0; i < controls.length; i++)
+ {
+ var slot = controls[i].id;
+ if (slot in gServerSettings)
+ {
+ if (controls[i].localName == "checkbox")
+ gServerSettings[slot] = controls[i].checked;
+ else
+ gServerSettings[slot] = controls[i].value;
+ }
+ }
+
+ return true;
+}
+
+
+// Set radio element choices and picker states
+function updateInboxAccount(enablePicker)
+{
+ document.getElementById("deferredServerFolderPicker").disabled = !enablePicker;
+ document.getElementById("deferGetNewMail").disabled = !enablePicker;
+}
diff --git a/mailnews/base/prefs/content/am-server-advanced.xul b/mailnews/base/prefs/content/am-server-advanced.xul
new file mode 100644
index 000000000..82b85b975
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server-advanced.xul
@@ -0,0 +1,146 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/am-server-advanced.dtd">
+
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&serverAdvanced.label;"
+ buttons="accept,cancel"
+ onload="onLoad();"
+ ondialogaccept="return onOk();">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/am-server-advanced.js"/>
+ <stringbundle id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"/>
+
+ <label id="serverPrettyName"/>
+
+ <separator class="thin"/>
+
+ <!-- IMAP Panel -->
+ <vbox id="imapPanel">
+ <hbox align="center">
+ <label value="&serverDirectory.label;"
+ accesskey="&serverDirectory.accesskey;"
+ control="serverDirectory"/>
+ <textbox amsa_persist="true" id="serverDirectory"/>
+ </hbox>
+
+ <checkbox amsa_persist="true" id="usingSubscription"
+ label="&usingSubscription.label;"
+ accesskey="&usingSubscription.accesskey;"/>
+
+ <checkbox amsa_persist="true" id="dualUseFolders"
+ label="&dualUseFolders.label;"
+ accesskey="&dualUseFolders.accesskey;"/>
+
+ <separator class="groove"/>
+ <row>
+ <hbox align="center">
+ <label control="maximumConnectionsNumber"
+ value="&maximumConnectionsNumber.label;"
+ accesskey="&maximumConnectionsNumber.accesskey;"/>
+ <textbox amsa_persist="true" size="3" type="number"
+ min="1" max="1000" id="maximumConnectionsNumber"/>
+ </hbox>
+ </row>
+
+ <separator class="groove"/>
+ <description>&namespaceDesc.label;</description>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <separator class="indent"/>
+ <label control="personalNamespace" value="&personalNamespace.label;"
+ accesskey="&personalNamespace.accesskey;"/>
+ <textbox amsa_persist="true" id="personalNamespace" />
+ </row>
+ <row align="center">
+ <separator class="indent"/>
+ <label control="publicNamespace" value="&publicNamespace.label;"
+ accesskey="&publicNamespace.accesskey;"/>
+ <textbox amsa_persist="true" id="publicNamespace"/>
+ </row>
+ <row align="center">
+ <separator class="indent"/>
+ <label control="otherUsersNamespace" value="&otherUsersNamespace.label;"
+ accesskey="&otherUsersNamespace.accesskey;"/>
+ <textbox amsa_persist="true" id="otherUsersNamespace"/>
+ </row>
+ </rows>
+ </grid>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <separator class="indent"/>
+ <checkbox amsa_persist="true" id="overrideNamespaces"
+ label="&overrideNamespaces.label;"
+ accesskey="&overrideNamespaces.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+
+
+ <!-- POP3 Panel -->
+ <vbox id="pop3Panel">
+ <label flex="1" control="folderStorage">&pop3DeferringDesc.label;</label>
+ <hbox align="center">
+ <radiogroup id="folderStorage"
+ orient="horizontal"
+ amsa_persist="true"
+ onselect="updateInboxAccount(this.selectedIndex == 1);">
+ <rows>
+ <row>
+ <radio id="deferToCurrentAccount"
+ value="currentAccount"
+ label="&accountInbox.label;"
+ accesskey="&accountInbox.accesskey;"/>
+ </row>
+ <row>
+ <vbox>
+ <hbox>
+ <radio id="deferToOtherAccount"
+ value="otherAccount"
+ label="&deferToServer.label;"
+ accesskey="&deferToServer.accesskey;">
+ </radio>
+ <menulist id="deferredServerFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="deferToServer">
+ <menupopup id="deferredServerPopup"
+ type="folder"
+ expandFolders="false"
+ mode="deferred"
+ oncommand="this.selectFolder(event.target._folder);"/>
+ </menulist>
+ </hbox>
+ <checkbox amsa_persist="true" id="deferGetNewMail"
+ label="&deferGetNewMail.label;" class="indent"
+ accesskey="&deferGetNewMail.accesskey;"/>
+ </vbox>
+ </row>
+ </rows>
+ </radiogroup>
+ </hbox>
+
+ </vbox>
+</dialog>
diff --git a/mailnews/base/prefs/content/am-server-top.xul b/mailnews/base/prefs/content/am-server-top.xul
new file mode 100644
index 000000000..890fa7a79
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server-top.xul
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-server-top.dtd" >
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+</page>
diff --git a/mailnews/base/prefs/content/am-server.js b/mailnews/base/prefs/content/am-server.js
new file mode 100644
index 000000000..01b22e4ae
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server.js
@@ -0,0 +1,404 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/MailUtils.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gServer;
+
+function onSave()
+{
+ let storeContractID = document.getElementById("server.storeTypeMenulist")
+ .selectedItem
+ .value;
+ document.getElementById("server.storeContractID")
+ .setAttribute("value", storeContractID);
+}
+
+function onInit(aPageId, aServerId)
+{
+ initServerType();
+
+ onCheckItem("server.biffMinutes", ["server.doBiff"]);
+ onCheckItem("nntp.maxArticles", ["nntp.notifyOn"]);
+ setupMailOnServerUI();
+ setupFixedUI();
+ let serverType = document.getElementById("server.type").getAttribute("value");
+ if (serverType == "imap")
+ setupImapDeleteUI(aServerId);
+
+ // TLS Cert (External) and OAuth2 are only supported on IMAP.
+#ifdef MOZ_MAILNEWS_OAUTH2
+ document.getElementById("authMethod-oauth2").hidden = (serverType != "imap");
+#endif
+ document.getElementById("authMethod-external").hidden = (serverType != "imap");
+
+ // "STARTTLS, if available" is vulnerable to MITM attacks so we shouldn't
+ // allow users to choose it anymore. Hide the option unless the user already
+ // has it set.
+ hideUnlessSelected(document.getElementById("connectionSecurityType-1"));
+
+ // UI for account store type.
+ let storeTypeElement = document.getElementById("server.storeTypeMenulist");
+ // set the menuitem to match the account
+ let currentStoreID = document.getElementById("server.storeContractID")
+ .getAttribute("value");
+ let targetItem = storeTypeElement.getElementsByAttribute("value", currentStoreID);
+ storeTypeElement.selectedItem = targetItem[0];
+ // disable store type change if store has already been used
+ storeTypeElement.setAttribute("disabled",
+ gServer.getBoolValue("canChangeStoreType") ? "false" : "true");
+}
+
+function onPreInit(account, accountValues)
+{
+ var type = parent.getAccountValue(account, accountValues, "server", "type", null, false);
+ hideShowControls(type);
+
+ Services.obs.notifyObservers(null, "charsetmenu-selected", "other");
+
+ gServer = account.incomingServer;
+
+ if(!account.incomingServer.canEmptyTrashOnExit)
+ {
+ document.getElementById("server.emptyTrashOnExit").setAttribute("hidden", "true");
+ document.getElementById("imap.deleteModel.box").setAttribute("hidden", "true");
+ }
+}
+
+function initServerType()
+{
+ var serverType = document.getElementById("server.type").getAttribute("value");
+ var propertyName = "serverType-" + serverType;
+
+ var messengerBundle = document.getElementById("bundle_messenger");
+ var verboseName = messengerBundle.getString(propertyName);
+ setDivText("servertype.verbose", verboseName);
+
+ secureSelect(true);
+
+ setLabelFromStringBundle("authMethod-no", "authNo");
+ setLabelFromStringBundle("authMethod-old", "authOld");
+ setLabelFromStringBundle("authMethod-kerberos", "authKerberos");
+ setLabelFromStringBundle("authMethod-external", "authExternal");
+ setLabelFromStringBundle("authMethod-ntlm", "authNTLM");
+#ifdef MOZ_MAILNEWS_OAUTH2
+ setLabelFromStringBundle("authMethod-oauth2", "authOAuth2");
+#endif
+ setLabelFromStringBundle("authMethod-anysecure", "authAnySecure");
+ setLabelFromStringBundle("authMethod-any", "authAny");
+ setLabelFromStringBundle("authMethod-password-encrypted",
+ "authPasswordEncrypted");
+ //authMethod-password-cleartext already set in selectSelect()
+
+ // Hide deprecated/hidden auth options, unless selected
+ hideUnlessSelected(document.getElementById("authMethod-no"));
+ hideUnlessSelected(document.getElementById("authMethod-old"));
+ hideUnlessSelected(document.getElementById("authMethod-anysecure"));
+ hideUnlessSelected(document.getElementById("authMethod-any"));
+}
+
+function hideUnlessSelected(element)
+{
+ element.hidden = !element.selected;
+}
+
+function setLabelFromStringBundle(elementID, stringName)
+{
+ document.getElementById(elementID).label =
+ document.getElementById("bundle_messenger").getString(stringName);
+}
+
+function setDivText(divname, value)
+{
+ var div = document.getElementById(divname);
+ if (!div)
+ return;
+ div.setAttribute("value", value);
+}
+
+
+function onAdvanced()
+{
+ // Store the server type and, if an IMAP or POP3 server,
+ // the settings needed for the IMAP/POP3 tab into the array
+ var serverSettings = {};
+ var serverType = document.getElementById("server.type").getAttribute("value");
+ serverSettings.serverType = serverType;
+
+ serverSettings.serverPrettyName = gServer.prettyName;
+ serverSettings.account = top.getCurrentAccount();
+
+ if (serverType == "imap")
+ {
+ serverSettings.dualUseFolders = document.getElementById("imap.dualUseFolders").checked;
+ serverSettings.usingSubscription = document.getElementById("imap.usingSubscription").checked;
+ serverSettings.maximumConnectionsNumber = document.getElementById("imap.maximumConnectionsNumber").getAttribute("value");
+ // string prefs
+ serverSettings.personalNamespace = document.getElementById("imap.personalNamespace").getAttribute("value");
+ serverSettings.publicNamespace = document.getElementById("imap.publicNamespace").getAttribute("value");
+ serverSettings.serverDirectory = document.getElementById("imap.serverDirectory").getAttribute("value");
+ serverSettings.otherUsersNamespace = document.getElementById("imap.otherUsersNamespace").getAttribute("value");
+ serverSettings.overrideNamespaces = document.getElementById("imap.overrideNamespaces").checked;
+ }
+ else if (serverType == "pop3")
+ {
+ serverSettings.deferGetNewMail = document.getElementById("pop3.deferGetNewMail").checked;
+ serverSettings.deferredToAccount = document.getElementById("pop3.deferredToAccount").getAttribute("value");
+ }
+
+ window.openDialog("chrome://messenger/content/am-server-advanced.xul",
+ "_blank", "chrome,modal,titlebar", serverSettings);
+
+ if (serverType == "imap")
+ {
+ document.getElementById("imap.dualUseFolders").checked = serverSettings.dualUseFolders;
+ document.getElementById("imap.usingSubscription").checked = serverSettings.usingSubscription;
+ document.getElementById("imap.maximumConnectionsNumber").setAttribute("value", serverSettings.maximumConnectionsNumber);
+ // string prefs
+ document.getElementById("imap.personalNamespace").setAttribute("value", serverSettings.personalNamespace);
+ document.getElementById("imap.publicNamespace").setAttribute("value", serverSettings.publicNamespace);
+ document.getElementById("imap.serverDirectory").setAttribute("value", serverSettings.serverDirectory);
+ document.getElementById("imap.otherUsersNamespace").setAttribute("value", serverSettings.otherUsersNamespace);
+ document.getElementById("imap.overrideNamespaces").checked = serverSettings.overrideNamespaces;
+ }
+ else if (serverType == "pop3")
+ {
+ document.getElementById("pop3.deferGetNewMail").checked = serverSettings.deferGetNewMail;
+ document.getElementById("pop3.deferredToAccount").setAttribute("value", serverSettings.deferredToAccount);
+ let pop3Server = gServer.QueryInterface(Components.interfaces.nsIPop3IncomingServer);
+ // we're explicitly setting this so we'll go through the SetDeferredToAccount method
+ pop3Server.deferredToAccount = serverSettings.deferredToAccount;
+ // Setting the server to be deferred causes a rebuild of the account tree,
+ // losing the current selection. Reselect the current server again as it
+ // didn't really disappear.
+ parent.selectServer(parent.getCurrentAccount().incomingServer, parent.currentPageId);
+
+ // Iterate over all accounts to see if any of their junk targets are now
+ // invalid (pointed to the account that is now deferred).
+ // If any such target is found it is reset to a new safe folder
+ // (the deferred to account or Local Folders). If junk was really moved
+ // to that folder (moveOnSpam = true) then moving junk is disabled
+ // (so that the user notices it and checks the settings).
+ // This is the same sanitization as in am-junk.js, just applied to all POP accounts.
+ let deferredURI = serverSettings.deferredToAccount &&
+ MailServices.accounts.getAccount(serverSettings.deferredToAccount)
+ .incomingServer.serverURI;
+
+ for (let account in fixIterator(MailServices.accounts.accounts,
+ Components.interfaces.nsIMsgAccount)) {
+ let accountValues = parent.getValueArrayFor(account);
+ let type = parent.getAccountValue(account, accountValues, "server", "type",
+ null, false);
+ // Try to keep this list of account types not having Junk settings
+ // synchronized with the list in AccountManager.js.
+ if (type != "nntp" && type != "rss" && type != "im") {
+ let spamActionTargetAccount = parent.getAccountValue(account, accountValues,
+ "server", "spamActionTargetAccount", "string", true);
+ let spamActionTargetFolder = parent.getAccountValue(account, accountValues,
+ "server", "spamActionTargetFolder", "string", true);
+ let moveOnSpam = parent.getAccountValue(account, accountValues,
+ "server", "moveOnSpam", "bool", true);
+
+ // Check if there are any invalid junk targets and fix them.
+ [ spamActionTargetAccount, spamActionTargetFolder, moveOnSpam ] =
+ sanitizeJunkTargets(spamActionTargetAccount,
+ spamActionTargetFolder,
+ deferredURI || account.incomingServer.serverURI,
+ parent.getAccountValue(account, accountValues, "server", "moveTargetMode", "int", true),
+ account.incomingServer.spamSettings,
+ moveOnSpam);
+
+ parent.setAccountValue(accountValues, "server", "moveOnSpam", moveOnSpam);
+ parent.setAccountValue(accountValues, "server", "spamActionTargetAccount",
+ spamActionTargetAccount);
+ parent.setAccountValue(accountValues, "server", "spamActionTargetFolder",
+ spamActionTargetFolder);
+ }
+ }
+ }
+}
+
+function secureSelect(aLoading)
+{
+ var socketType = document.getElementById("server.socketType").value;
+ var serverType = document.getElementById("server.type").value;
+ var defaultPort = gServer.protocolInfo.getDefaultServerPort(false);
+ var defaultPortSecure = gServer.protocolInfo.getDefaultServerPort(true);
+ var port = document.getElementById("server.port");
+ var portDefault = document.getElementById("defaultPort");
+ var prevDefaultPort = portDefault.value;
+
+ const Ci = Components.interfaces;
+ if (socketType == Ci.nsMsgSocketType.SSL) {
+ portDefault.value = defaultPortSecure;
+ if (port.value == "" || (!aLoading && port.value == defaultPort && prevDefaultPort != portDefault.value))
+ port.value = defaultPortSecure;
+ } else {
+ portDefault.value = defaultPort;
+ if (port.value == "" || (!aLoading && port.value == defaultPortSecure && prevDefaultPort != portDefault.value))
+ port.value = defaultPort;
+ }
+
+ // switch "insecure password" label
+ setLabelFromStringBundle("authMethod-password-cleartext",
+ socketType == Ci.nsMsgSocketType.SSL ||
+ socketType == Ci.nsMsgSocketType.alwaysSTARTTLS ?
+ "authPasswordCleartextViaSSL" : "authPasswordCleartextInsecurely");
+}
+
+function setupMailOnServerUI()
+{
+ onCheckItem("pop3.deleteMailLeftOnServer", ["pop3.leaveMessagesOnServer"]);
+ setupAgeMsgOnServerUI();
+}
+
+function setupAgeMsgOnServerUI()
+{
+ const kLeaveMsgsId = "pop3.leaveMessagesOnServer";
+ const kDeleteByAgeId = "pop3.deleteByAgeFromServer";
+ onCheckItem(kDeleteByAgeId, [kLeaveMsgsId]);
+ onCheckItem("daysEnd", [kLeaveMsgsId]);
+ onCheckItem("pop3.numDaysToLeaveOnServer", [kLeaveMsgsId, kDeleteByAgeId]);
+}
+
+function setupFixedUI()
+{
+ var controls = [document.getElementById("fixedServerName"),
+ document.getElementById("fixedUserName"),
+ document.getElementById("fixedServerPort")];
+
+ var len = controls.length;
+ for (var i=0; i<len; i++) {
+ var fixedElement = controls[i];
+ var otherElement = document.getElementById(fixedElement.getAttribute("use"));
+
+ fixedElement.setAttribute("collapsed", "true");
+ otherElement.removeAttribute("collapsed");
+ }
+}
+
+function BrowseForNewsrc()
+{
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+ const nsIFile = Components.interfaces.nsIFile;
+
+ var newsrcTextBox = document.getElementById("nntp.newsrcFilePath");
+ var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ fp.init(window,
+ document.getElementById("browseForNewsrc").getAttribute("filepickertitle"),
+ nsIFilePicker.modeSave);
+
+ var currentNewsrcFile;
+ try {
+ currentNewsrcFile = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(nsIFile);
+ currentNewsrcFile.initWithPath(newsrcTextBox.value);
+ } catch (e) {
+ dump("Failed to create nsIFile instance for the current newsrc file.\n");
+ }
+
+ if (currentNewsrcFile) {
+ fp.displayDirectory = currentNewsrcFile.parent;
+ fp.defaultString = currentNewsrcFile.leafName;
+ }
+
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ if (fp.show() != nsIFilePicker.returnCancel)
+ newsrcTextBox.value = fp.file.path;
+}
+
+function setupImapDeleteUI(aServerId)
+{
+ // read delete_model preference
+ var deleteModel = document.getElementById("imap.deleteModel").getAttribute("value");
+ selectImapDeleteModel(deleteModel);
+
+ // read trash_folder_name preference
+ var trashFolderName = getTrashFolderName();
+
+ // set folderPicker menulist
+ var trashPopup = document.getElementById("msgTrashFolderPopup");
+ trashPopup._teardown();
+ trashPopup._parentFolder = MailUtils.getFolderForURI(aServerId);
+ trashPopup._ensureInitialized();
+
+ // TODO: There is something wrong here, selectFolder() fails even if the
+ // folder does exist. Try to fix in bug 802609.
+ let trashFolder = MailUtils.getFolderForURI(aServerId + "/" + trashFolderName, false);
+ try {
+ trashPopup.selectFolder(trashFolder);
+ } catch(ex) {
+ trashPopup.parentNode.setAttribute("label", trashFolder.prettyName);
+ }
+ trashPopup.parentNode.folder = trashFolder;
+}
+
+function selectImapDeleteModel(choice)
+{
+ // set deleteModel to selected mode
+ document.getElementById("imap.deleteModel").setAttribute("value", choice);
+
+ switch (choice)
+ {
+ case "0" : // markDeleted
+ // disable folderPicker
+ document.getElementById("msgTrashFolderPicker").setAttribute("disabled", "true");
+ break;
+ case "1" : // moveToTrashFolder
+ // enable folderPicker
+ document.getElementById("msgTrashFolderPicker").removeAttribute("disabled");
+ break;
+ case "2" : // deleteImmediately
+ // disable folderPicker
+ document.getElementById("msgTrashFolderPicker").setAttribute("disabled", "true");
+ break;
+ default :
+ dump("Error in enabling/disabling server.TrashFolderPicker\n");
+ break;
+ }
+}
+
+// Capture any menulist changes from folderPicker
+function folderPickerChange(aEvent)
+{
+ var folder = aEvent.target._folder;
+ var folderPath = getFolderPathFromRoot(folder);
+
+ // Set the value to be persisted.
+ document.getElementById("imap.trashFolderName")
+ .setAttribute("value", folderPath);
+
+ // Update the widget to show/do correct things even for subfolders.
+ var trashFolderPicker = document.getElementById("msgTrashFolderPicker");
+ trashFolderPicker.menupopup.selectFolder(folder);
+}
+
+/** Generate the relative folder path from the root. */
+function getFolderPathFromRoot(folder)
+{
+ var path = folder.name;
+ var parentFolder = folder.parent;
+ while (parentFolder && parentFolder != folder.rootFolder) {
+ path = parentFolder.name + "/" + path;
+ parentFolder = parentFolder.parent;
+ }
+ // IMAP Inbox URI's start with INBOX, not Inbox.
+ return path.replace(/^Inbox/, "INBOX");
+}
+
+// Get trash_folder_name from prefs
+function getTrashFolderName()
+{
+ var trashFolderName = document.getElementById("imap.trashFolderName").getAttribute("value");
+ // if the preference hasn't been set, set it to a sane default
+ if (!trashFolderName) {
+ trashFolderName = "Trash";
+ document.getElementById("imap.trashFolderName").setAttribute("value",trashFolderName);
+ }
+ return trashFolderName;
+}
diff --git a/mailnews/base/prefs/content/am-server.xul b/mailnews/base/prefs/content/am-server.xul
new file mode 100644
index 000000000..7828e4cf4
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server.xul
@@ -0,0 +1,470 @@
+<?xml version="1.0"?>
+
+<!--
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/content/charsetList.css" type="text/css"?>
+
+<!DOCTYPE page [
+<!ENTITY % trashDTD SYSTEM "chrome://messenger/locale/am-server-top.dtd">
+%trashDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&serverSettings.label;"
+ onload="parent.onPanelLoaded('am-server.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-server.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-prefs.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+
+ <label hidden="true" wsm_persist="true" id="server.type"/>
+ <label hidden="true"
+ wsm_persist="true"
+ preftype="string"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.storeContractID"
+ genericattr="true"
+ id="server.storeContractID"/>
+
+ <dialogheader title="&serverSettings.label;"/>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&serverType.label;"/>
+ <label id="servertype.verbose"/>
+ </row>
+ <row align="center">
+ <hbox>
+ <label value="&serverName.label;" accesskey="&serverName.accesskey;"
+ control="server.realHostName"/>
+ </hbox>
+ <hbox align="center">
+ <label id="fixedServerName" collapsed="true" use="server.realHostName"/>
+ <textbox wsm_persist="true"
+ size="20"
+ flex="1"
+ id="server.realHostName"
+ prefstring="mail.server.%serverkey%.realhostname"
+ class="uri-element"/>
+ </hbox>
+ <hbox align="center">
+ <label hidefor="movemail" value="&port.label;"
+ accesskey="&port.accesskey;" control="server.port"/>
+ <label id="fixedServerPort" hidefor="movemail"
+ collapsed="true" use="server.port"/>
+ <textbox wsm_persist="true" size="3" id="server.port"
+ preftype="int" hidefor="movemail"
+ prefstring="mail.server.%serverkey%.port"
+ type="number" min="1" max="65535" increment="1"/>
+ <label value="&serverPortDefault.label;" hidefor="movemail"/>
+ <label id="defaultPort" hidefor="movemail"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <hbox align="center" hidefor="nntp">
+ <label value="&userName.label;"
+ accesskey="&userName.accesskey;"
+ control="server.realUsername"/>
+ </hbox>
+ <hbox align="center" hidefor="nntp">
+ <label id="fixedUserName" collapsed="true" use="server.realUsername"/>
+ <textbox wsm_persist="true"
+ size="20"
+ flex="1"
+ id="server.realUsername"
+ prefstring="mail.server.%serverkey%.realusername"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+
+ <separator class="thin"/>
+
+ <groupbox hidefor="movemail">
+ <caption label="&securitySettings.label;"/>
+
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&connectionSecurity.label;"
+ accesskey="&connectionSecurity.accesskey;"
+ control="server.socketType"/>
+ <menulist wsm_persist="true" id="server.socketType"
+ oncommand="secureSelect();">
+ <menupopup id="server.socketTypePopup">
+ <menuitem value="0" label="&connectionSecurityType-0.label;"/>
+ <menuitem id="connectionSecurityType-1"
+ value="1" label="&connectionSecurityType-1.label;"
+ disabled="true"/>
+ <menuitem value="2" label="&connectionSecurityType-2.label;"
+ hidefor="nntp"/>
+ <menuitem value="3" label="&connectionSecurityType-3.label;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center" hidefor="nntp,movemail">
+ <label value="&authMethod.label;"
+ accesskey="&authMethod.accesskey;"
+ control="server.authMethod"/>
+ <!-- same in smtpEditOverlay.xul/js -->
+ <menulist id="server.authMethod"
+ wsm_persist="true"
+ preftype="int" type="number"
+ prefstring="mail.server.%serverkey%.authMethod">
+ <menupopup id="server.authMethodPopup">
+ <menuitem id="authMethod-no" value="1"/>
+ <menuitem id="authMethod-old" value="2"/>
+ <menuitem id="authMethod-password-cleartext" value="3"/>
+ <menuitem id="authMethod-password-encrypted" value="4"/>
+ <menuitem id="authMethod-kerberos" value="5"/>
+ <menuitem id="authMethod-ntlm" value="6"/>
+ <menuitem id="authMethod-external" value="7"/>
+#ifdef MOZ_MAILNEWS_OAUTH2
+ <menuitem id="authMethod-oauth2" value="10"/>
+#endif
+ <menuitem id="authMethod-anysecure" value="8"/>
+ <menuitem id="authMethod-any" value="9"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&serverSettings.label;"/>
+ <vbox align="start">
+ <checkbox wsm_persist="true"
+ id="server.loginAtStartUp"
+ label="&loginAtStartup.label;"
+ accesskey="&loginAtStartup.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.login_at_startup"/>
+ </vbox>
+ <hbox align="center">
+ <checkbox wsm_persist="true"
+ id="server.doBiff"
+ label="&biffStart.label;"
+ accesskey="&biffStart.accesskey;"
+ oncommand="onCheckItem('server.biffMinutes', [this.id]);"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.check_new_mail"/>
+ <textbox wsm_persist="true"
+ id="server.biffMinutes"
+ type="number"
+ size="3"
+ min="1"
+ increment="1"
+ aria-labelledby="server.doBiff server.biffMinutes biffEnd"
+ preftype="int"
+ prefstring="mail.server.%serverkey%.check_time"/>
+ <label id="biffEnd"
+ control="server.biffMinutes"
+ value="&biffEnd.label;"/>
+ </hbox>
+ <vbox align="start"
+ hidefor="pop3,nntp,movemail">
+ <checkbox wsm_persist="true"
+ id="imap.useIdle"
+ label="&useIdleNotifications.label;"
+ accesskey="&useIdleNotifications.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.use_idle"/>
+ </vbox>
+
+ <!-- Necessary for POP3 and Movemail (Bug 480945) -->
+ <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=480945 -->
+ <vbox align="start" hidefor="imap,nntp">
+ <checkbox wsm_persist="true" id="server.downloadOnBiff"
+ label="&downloadOnBiff.label;" prefattribute="value"
+ accesskey="&downloadOnBiff.accesskey;"
+ prefstring="mail.server.%serverkey%.download_on_biff"/>
+ </vbox>
+ <!-- POP3 -->
+ <vbox align="start" hidefor="imap,nntp,movemail">
+ <checkbox wsm_persist="true" id="pop3.headersOnly"
+ label="&headersOnly.label;"
+ accesskey="&headersOnly.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.headers_only"/>
+
+ <checkbox wsm_persist="true" id="pop3.leaveMessagesOnServer"
+ label="&leaveOnServer.label;" oncommand="setupMailOnServerUI();"
+ accesskey="&leaveOnServer.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.leave_on_server"/>
+
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="pop3.deleteByAgeFromServer" class="indent"
+ label="&deleteByAgeFromServer.label;" oncommand="setupAgeMsgOnServerUI();"
+ accesskey="&deleteByAgeFromServer.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.delete_by_age_from_server"/>
+ <textbox wsm_persist="true" id="pop3.numDaysToLeaveOnServer" size="3"
+ aria-labelledby="pop3.deleteByAgeFromServer pop3.numDaysToLeaveOnServer daysEnd"
+ preftype="int" type="number" min="1" increment="1"
+ prefstring="mail.server.%serverkey%.num_days_to_leave_on_server"/>
+ <label id="daysEnd" control="pop3.numDaysToLeaveOnServer" value="&daysEnd.label;"/>
+ </hbox>
+
+ <checkbox wsm_persist="true" id="pop3.deleteMailLeftOnServer" class="indent"
+ label="&deleteOnServer2.label;"
+ accesskey="&deleteOnServer2.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.delete_mail_left_on_server"/>
+
+ <!-- hidden elements for data transfer to and from advanced... dialog -->
+ <hbox flex="1" hidefor="imap,nntp,movemail" hidden="true">
+ <checkbox hidden="true" wsm_persist="true" id="pop3.deferGetNewMail"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.deferGetNewMail"/>
+ <label hidden="true" wsm_persist="true" id="pop3.deferredToAccount"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.deferredToAccount"/>
+
+ </hbox>
+ </vbox>
+ <!-- IMAP -->
+ <label hidden="true" wsm_persist="true" id="imap.trashFolderName"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.trash_folder_name"/>
+
+ <hbox align="center" hidefor="pop3,nntp,movemail">
+ <label value="&deleteMessagePrefix.label;" align="left"
+ control="imap.deleteModel"/>
+ </hbox>
+ <vbox>
+ <hbox align="center"
+ id="imap.deleteModel.box"
+ hidefor="pop3,nntp,movemail"
+ flex="1">
+ <radiogroup id="imap.deleteModel"
+ wsm_persist="true"
+ oncommand="selectImapDeleteModel(this.value);"
+ prefstring="mail.server.%serverkey%.delete_model">
+ <grid class="specialFolderPickerGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <radio id="modelMoveToTrash"
+ value="1"
+ label="&modelMoveToTrash.label;"
+ accesskey="&modelMoveToTrash.accesskey;"/>
+ <menulist id="msgTrashFolderPicker" flex="1"
+ class="folderMenuItem"
+ maxwidth="300" crop="center"
+ aria-labelledby="modelMoveToTrash"
+ displayformat="verbose">
+ <menupopup id="msgTrashFolderPopup"
+ type="folder"
+ mode="filing"
+ showFileHereLabel="true"
+ oncommand="folderPickerChange(event);"/>
+ </menulist>
+ </row>
+ <row align="center">
+ <radio id="modelMarkDeleted"
+ value="0"
+ label="&modelMarkDeleted.label;"
+ accesskey="&modelMarkDeleted.accesskey;"/>
+ </row>
+ <row align="center">
+ <radio id="modelDeleteImmediately"
+ value="2"
+ label="&modelDeleteImmediately.label;"
+ accesskey="&modelDeleteImmediately.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+ </hbox>
+ <hbox pack="end">
+ <!-- This button should have identical attributes to the
+ server.popAdvancedButton except the hidefor attribute. -->
+ <button label="&advancedButton.label;"
+ accesskey="&advancedButton.accesskey;"
+ oncommand="onAdvanced();"
+ wsm_persist="true"
+ id="server.imapAdvancedButton"
+ prefstring="mail.server.%serverkey%.advanced.disable"
+ hidefor="pop3,nntp,movemail"/>
+ </hbox>
+ </vbox>
+
+ <!-- Advanced IMAP settings -->
+ <hbox flex="1" hidefor="pop3,nntp,movemail" hidden="true">
+ <checkbox hidden="true" wsm_persist="true" id="imap.dualUseFolders"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.dual_use_folders"/>
+ <checkbox hidden="true" wsm_persist="true" id="imap.usingSubscription"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.using_subscription"/>
+ <label hidden="true" wsm_persist="true" id="imap.maximumConnectionsNumber"/>
+ <label hidden="true" wsm_persist="true" id="imap.personalNamespace"/>
+ <label hidden="true" wsm_persist="true" id="imap.publicNamespace"/>
+ <label hidden="true" wsm_persist="true" id="imap.otherUsersNamespace"/>
+ <label hidden="true" wsm_persist="true" id="imap.serverDirectory"/>
+ <checkbox hidden="true" wsm_persist="true" id="imap.overrideNamespaces"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.override_namespaces"/>
+ </hbox>
+
+ <!-- NNTP -->
+ <hbox hidefor="pop3,imap,movemail" align="center">
+ <checkbox wsm_persist="true" id="nntp.notifyOn"
+ label="&maxMessagesStart.label;"
+ accesskey="&maxMessagesStart.accesskey;"
+ oncommand="onCheckItem('nntp.maxArticles', [this.id]);"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.notify.on"/>
+ <textbox wsm_persist="true" id="nntp.maxArticles"
+ type="number" size="4" min="1" increment="10"
+ aria-labelledby="nntp.notifyOn nntp.maxArticles maxMessagesEnd"
+ preftype="int"
+ prefstring="mail.server.%serverkey%.max_articles"/>
+ <label control="nntp.maxArticles" value="&maxMessagesEnd.label;" id="maxMessagesEnd"/>
+ </hbox>
+ <checkbox hidefor="pop3,imap,movemail" wsm_persist="true" id="nntp.pushAuth"
+ label="&alwaysAuthenticate.label;"
+ accesskey="&alwaysAuthenticate.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.always_authenticate"/>
+
+ <!-- take out for now - bug 45079
+ <hbox flex="1" hidefor="pop3,imap,movemail">
+ <groupbox>
+ <caption class="header" label="&abbreviate.label;"/>
+
+ <radiogroup wsm_persist="true" id="nntp.abbreviate">
+ <radio value="true"
+ label="&abbreviateOn.label;"/>
+ <radio value="false"
+ label="&abbreviateOff.label;"/>
+ </radiogroup>
+ </groupbox>
+ </hbox>
+ -->
+
+ </groupbox>
+
+ <groupbox>
+ <caption label="&messageStorage.label;"/>
+
+ <hbox align="end">
+ <vbox align="start" flex="1" id="exitHandlingBox=">
+ <checkbox hidefor="pop3,nntp,movemail"
+ wsm_persist="true"
+ id="imap.cleanupInboxOnExit"
+ label="&expungeOnExit.label;"
+ accesskey="&expungeOnExit.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.cleanup_inbox_on_exit"/>
+ <checkbox hidefor="nntp"
+ wsm_persist="true"
+ id="server.emptyTrashOnExit"
+ label="&emptyTrashOnExit.label;"
+ accesskey="&emptyTrashOnExit.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.empty_trash_on_exit"/>
+ </vbox>
+ <button label="&advancedButton.label;"
+ accesskey="&advancedButton.accesskey;"
+ oncommand="onAdvanced();"
+ wsm_persist="true"
+ id="server.popAdvancedButton"
+ prefstring="mail.server.%serverkey%.advanced.disable"
+ hidefor="imap,nntp,movemail"/>
+ </hbox>
+ <hbox align="center">
+ <label value="&storeType.label;"
+ accesskey="&storeType.accesskey;"
+ control="server.storeTypeMenulist"/>
+ <menulist id="server.storeTypeMenulist">
+ <menupopup id="server.storeTypeMenupopup">
+ <menuitem id="server.mboxStore"
+ value="@mozilla.org/msgstore/berkeleystore;1"
+ label="&mboxStore2.label;"/>
+ <menuitem id="server.maildirStore"
+ value="@mozilla.org/msgstore/maildirstore;1"
+ label="&maildirStore.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <vbox hidefor="imap,pop3,movemail">
+ <label value="&newsrcFilePath.label;" control="nntp.newsrcFilePath"/>
+ <hbox align="center">
+ <textbox readonly="true"
+ wsm_persist="true"
+ flex="1"
+ id="nntp.newsrcFilePath"
+ datatype="nsIFile"
+ prefstring="mail.server.%serverkey%.newsrc.file"
+ class="uri-element"/>
+ <button id="browseForNewsrc"
+ label="&browseNewsrc.label;"
+ filepickertitle="&newsrcPicker.label;"
+ accesskey="&browseNewsrc.accesskey;"
+ oncommand="BrowseForNewsrc();"/>
+ </hbox>
+ </vbox>
+
+ <separator class="thin"/>
+
+ <vbox>
+ <label value="&localPath.label;" control="server.localPath"/>
+ <hbox align="center">
+ <textbox readonly="true"
+ wsm_persist="true"
+ flex="1"
+ id="server.localPath"
+ datatype="nsIFile"
+ prefstring="mail.server.%serverkey%.directory"
+ class="uri-element"/>
+ <button id="browseForLocalFolder"
+ label="&browseFolder.label;"
+ filepickertitle="&localFolderPicker.label;"
+ accesskey="&browseFolder.accesskey;"
+ oncommand="BrowseForLocalFolders();"/>
+ </hbox>
+ </vbox>
+
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <hbox hidefor="imap,pop3,movemail" align="center" valign="middle" iscontrolcontainer="true">
+ <label value="&serverDefaultCharset2.label;" control="nntp.charset"/>
+ <menulist hidable="true"
+ type="charset"
+ hidefor="imap,pop3,movemail"
+ wsm_persist="true"
+ id="nntp.charset"
+ preftype="string"
+ prefstring="mail.server.%serverkey%.charset"/>
+ </hbox>
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-serverwithnoidentities.js b/mailnews/base/prefs/content/am-serverwithnoidentities.js
new file mode 100644
index 000000000..696dc71b0
--- /dev/null
+++ b/mailnews/base/prefs/content/am-serverwithnoidentities.js
@@ -0,0 +1,34 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gServer;
+
+function onInit(aPageId, aServerId) {
+
+ // UI for account store type
+ let storeTypeElement = document.getElementById("server.storeTypeMenulist");
+ // set the menuitem to match the account
+ let currentStoreID = document.getElementById("server.storeContractID")
+ .getAttribute("value");
+ let targetItem = storeTypeElement.getElementsByAttribute("value", currentStoreID);
+ storeTypeElement.selectedItem = targetItem[0];
+ // disable store type change if store has already been used
+ storeTypeElement.setAttribute("disabled",
+ gServer.getBoolValue("canChangeStoreType") ? "false" : "true");
+}
+
+function onPreInit(account, accountValues) {
+ gServer = account.incomingServer;
+}
+
+function onSave()
+{
+ let storeContractID = document.getElementById("server.storeTypeMenulist")
+ .selectedItem
+ .value;
+ document.getElementById("server.storeContractID")
+ .setAttribute("value", storeContractID);
+}
+
diff --git a/mailnews/base/prefs/content/am-serverwithnoidentities.xul b/mailnews/base/prefs/content/am-serverwithnoidentities.xul
new file mode 100644
index 000000000..e33216234
--- /dev/null
+++ b/mailnews/base/prefs/content/am-serverwithnoidentities.xul
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE page [
+<!ENTITY % accountNoIdentDTD SYSTEM "chrome://messenger/locale/am-serverwithnoidentities.dtd" >%accountNoIdentDTD;
+<!ENTITY % accountServerTopDTD SYSTEM "chrome://messenger/locale/am-server-top.dtd">%accountServerTopDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&accountTitle.label;"
+ onload="parent.onPanelLoaded('am-serverwithnoidentities.xul');">
+
+ <script type="application/javascript" src="chrome://messenger/content/am-serverwithnoidentities.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+
+ <dialogheader title="&accountTitle.label;"/>
+
+ <label hidden="true"
+ wsm_persist="true"
+ preftype="string"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.storeContractID"
+ genericattr="true"
+ id="server.storeContractID"/>
+
+ <description class="secDesc">&accountSettingsDesc.label;</description>
+ <hbox align="center">
+ <label value="&accountName.label;" control="server.prettyName"
+ accesskey="&accountName.accesskey;"/>
+ <textbox wsm_persist="true" size="30" id="server.prettyName"
+ prefstring="mail.server.%serverkey%.name"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <caption label="&messageStorage.label;"/>
+
+ <vbox align="start">
+ <checkbox wsm_persist="true" id="server.emptyTrashOnExit"
+ label="&emptyTrashOnExit.label;"
+ accesskey="&emptyTrashOnExit.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.empty_trash_on_exit"/>
+ <hbox align="center">
+ <label value="&storeType.label;"
+ accesskey="&storeType.accesskey;"
+ control="server.storeTypeMenulist"/>
+ <menulist id="server.storeTypeMenulist">
+ <menupopup id="server.storeTypeMenupopup">
+ <menuitem id="server.mboxStore"
+ value="@mozilla.org/msgstore/berkeleystore;1"
+ label="&mboxStore2.label;"/>
+ <menuitem id="server.maildirStore"
+ value="@mozilla.org/msgstore/maildirstore;1"
+ label="&maildirStore.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </vbox>
+
+ <separator class="thin"/>
+
+ <label value="&localPath.label;" control="server.localPath"/>
+ <hbox align="center">
+ <textbox readonly="true" wsm_persist="true" flex="1" id="server.localPath" datatype="nsIFile"
+ prefstring="mail.server.%serverkey%.directory" class="uri-element"/>
+ <button id="browseForLocalFolder" label="&browseFolder.label;" filepickertitle="&localFolderPicker.label;"
+ accesskey="&browseFolder.accesskey;" oncommand="BrowseForLocalFolders()"/>
+ </hbox>
+ </groupbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-smtp.js b/mailnews/base/prefs/content/am-smtp.js
new file mode 100644
index 000000000..44580bc20
--- /dev/null
+++ b/mailnews/base/prefs/content/am-smtp.js
@@ -0,0 +1,263 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gSmtpServerListWindow =
+{
+ mBundle: null,
+ mServerList: null,
+ mAddButton: null,
+ mEditButton: null,
+ mDeleteButton: null,
+ mSetDefaultServerButton: null,
+
+ onLoad: function()
+ {
+ parent.onPanelLoaded('am-smtp.xul');
+
+ this.mBundle = document.getElementById("bundle_messenger");
+ this.mServerList = document.getElementById("smtpList");
+ this.mAddButton = document.getElementById("addButton");
+ this.mEditButton = document.getElementById("editButton");
+ this.mDeleteButton = document.getElementById("deleteButton");
+ this.mSetDefaultServerButton = document.getElementById("setDefaultButton");
+
+ this.refreshServerList("", false);
+
+ this.updateButtons(this.getSelectedServer());
+ },
+
+ onSelectionChanged: function(aEvent)
+ {
+ if (this.mServerList.selectedItems.length <= 0)
+ return;
+
+ var server = this.getSelectedServer();
+ this.updateButtons(server);
+ this.updateServerInfoBox(server);
+ },
+
+ onDeleteServer: function (aEvent)
+ {
+ var server = this.getSelectedServer();
+ if (server)
+ {
+ // confirm deletion
+ let cancel = Services.prompt.confirmEx(window,
+ this.mBundle.getString('smtpServers-confirmServerDeletionTitle'),
+ this.mBundle.getFormattedString('smtpServers-confirmServerDeletion', [server.hostname], 1),
+ Services.prompt.STD_YES_NO_BUTTONS, null, null, null, null, { });
+
+ if (!cancel)
+ {
+ // Remove password information first.
+ try {
+ server.forgetPassword();
+ } catch (e) { /* It is OK if this fails. */ }
+ // Remove the server.
+ MailServices.smtp.deleteServer(server);
+ parent.replaceWithDefaultSmtpServer(server.key);
+ this.refreshServerList("", true);
+ }
+ }
+ },
+
+ onAddServer: function (aEvent)
+ {
+ this.openServerEditor(null);
+ },
+
+ onEditServer: function (aEvent)
+ {
+ if (this.mServerList.selectedItems.length <= 0)
+ return;
+ this.openServerEditor(this.getSelectedServer());
+ },
+
+ onSetDefaultServer: function(aEvent)
+ {
+ if (this.mServerList.selectedItems.length <= 0)
+ return;
+
+ MailServices.smtp.defaultServer = this.getSelectedServer();
+ this.refreshServerList(MailServices.smtp.defaultServer.key, true);
+ },
+
+ updateButtons: function(aServer)
+ {
+ // can't delete default server
+ if (MailServices.smtp.defaultServer == aServer)
+ {
+ this.mSetDefaultServerButton.setAttribute("disabled", "true");
+ this.mDeleteButton.setAttribute("disabled", "true");
+ }
+ else
+ {
+ this.mSetDefaultServerButton.removeAttribute("disabled");
+ this.mDeleteButton.removeAttribute("disabled");
+ }
+
+ if (this.mServerList.selectedItems.length == 0)
+ this.mEditButton.setAttribute("disabled", "true");
+ else
+ this.mEditButton.removeAttribute("disabled");
+ },
+
+ updateServerInfoBox: function(aServer)
+ {
+ var noneSelected = this.mBundle.getString("smtpServerList-NotSpecified");
+
+ document.getElementById('nameValue').value = aServer.hostname;
+ document.getElementById('descriptionValue').value = aServer.description || noneSelected;
+ document.getElementById('portValue').value = aServer.port;
+ document.getElementById('userNameValue').value = aServer.username || noneSelected;
+ document.getElementById('useSecureConnectionValue').value =
+ this.mBundle.getString("smtpServer-ConnectionSecurityType-" +
+ aServer.socketType);
+
+ const AuthMethod = Components.interfaces.nsMsgAuthMethod;
+ const SocketType = Components.interfaces.nsMsgSocketType;
+ var authStr = "";
+ switch (aServer.authMethod)
+ {
+ case AuthMethod.none:
+ authStr = "authNo";
+ break;
+ case AuthMethod.passwordEncrypted:
+ authStr = "authPasswordEncrypted";
+ break;
+ case AuthMethod.GSSAPI:
+ authStr = "authKerberos";
+ break;
+ case AuthMethod.NTLM:
+ authStr = "authNTLM";
+ break;
+ case AuthMethod.secure:
+ authStr = "authAnySecure";
+ break;
+ case AuthMethod.passwordCleartext:
+ authStr = (aServer.socketType == SocketType.SSL ||
+ aServer.socketType == SocketType.alwaysSTARTTLS)
+ ? "authPasswordCleartextViaSSL"
+ : "authPasswordCleartextInsecurely";
+ break;
+#ifdef MOZ_MAILNEWS_OAUTH2
+ case AuthMethod.OAuth2:
+ authStr = "authOAuth2";
+ break;
+#endif
+ default:
+ // leave empty
+ Components.utils.reportError("Warning: unknown value for smtpserver... authMethod: " +
+ aServer.authMethod);
+ }
+ if (authStr)
+ document.getElementById("authMethodValue").value =
+ this.mBundle.getString(authStr);
+ },
+
+ refreshServerList: function(aServerKeyToSelect, aFocusList)
+ {
+ // remove all children
+ while (this.mServerList.hasChildNodes())
+ this.mServerList.lastChild.remove();
+
+ this.fillSmtpServers(this.mServerList,
+ MailServices.smtp.servers,
+ MailServices.smtp.defaultServer);
+
+ if (aServerKeyToSelect)
+ this.setSelectedServer(this.mServerList.querySelector('[key="' + aServerKeyToSelect + '"]'));
+ else // select the default server
+ this.setSelectedServer(this.mServerList.querySelector('[default="true"]'));
+
+ if (aFocusList)
+ this.mServerList.focus();
+ },
+
+ fillSmtpServers: function(aListBox, aServers, aDefaultServer)
+ {
+ if (!aListBox || !aServers)
+ return;
+
+ while (aServers.hasMoreElements())
+ {
+ var server = aServers.getNext();
+
+ if (server instanceof Components.interfaces.nsISmtpServer)
+ {
+ var isDefault = (aDefaultServer.key == server.key);
+
+ var listitem = this.createSmtpListItem(server, isDefault);
+ aListBox.appendChild(listitem);
+ }
+ }
+ },
+
+ createSmtpListItem: function(aServer, aIsDefault)
+ {
+ var listitem = document.createElement("listitem");
+ var serverName = "";
+
+ if (aServer.description)
+ serverName = aServer.description + ' - ';
+ else if (aServer.username)
+ serverName = aServer.username + ' - ';
+
+ serverName += aServer.hostname;
+
+ if (aIsDefault)
+ {
+ serverName += " " + this.mBundle.getString("defaultServerTag");
+ listitem.setAttribute("default", "true");
+ }
+
+ listitem.setAttribute("label", serverName);
+ listitem.setAttribute("key", aServer.key);
+ listitem.setAttribute("class", "smtpServerListItem");
+
+ // give it some unique id
+ listitem.id = "smtpServer." + aServer.key;
+ return listitem;
+ },
+
+ openServerEditor: function(aServer)
+ {
+ var args = {server: aServer,
+ result: false,
+ addSmtpServer: ""};
+
+ window.openDialog("chrome://messenger/content/SmtpServerEdit.xul",
+ "smtpEdit", "chrome,titlebar,modal,centerscreen", args);
+
+ // now re-select the server which was just added
+ if (args.result)
+ this.refreshServerList(aServer ? aServer.key : args.addSmtpServer, true);
+
+ return args.result;
+ },
+
+ setSelectedServer: function(aServer)
+ {
+ if (!aServer)
+ return;
+
+ setTimeout(function(aServerList) {
+ aServerList.ensureElementIsVisible(aServer);
+ aServerList.selectItem(aServer);
+ }, 0, this.mServerList);
+ },
+
+ getSelectedServer: function()
+ {
+ if (this.mServerList.selectedItems.length == 0)
+ return null;
+
+ let serverKey = this.mServerList.selectedItems[0].getAttribute("key");
+ return MailServices.smtp.getServerByKey(serverKey);
+ }
+};
diff --git a/mailnews/base/prefs/content/am-smtp.xul b/mailnews/base/prefs/content/am-smtp.xul
new file mode 100644
index 000000000..3c18bfe11
--- /dev/null
+++ b/mailnews/base/prefs/content/am-smtp.xul
@@ -0,0 +1,112 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-advanced.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&smtpServer.label;"
+ onload="gSmtpServerListWindow.onLoad();">
+ <script type="application/javascript"
+ src="chrome://messenger/content/amUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/am-smtp.js"/>
+
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+
+ <vbox flex="1" style="overflow: auto;">
+ <dialogheader title="&smtpServer.label;"/>
+
+ <label control="smtpList">&smtpDescription.label;</label>
+ <separator class="thin"/>
+
+ <hbox flex="1">
+ <listbox id="smtpList"
+ onselect="gSmtpServerListWindow.onSelectionChanged(event);"
+ ondblclick="gSmtpServerListWindow.onEditServer(event);"
+ flex="1"/>
+
+ <vbox>
+ <button id="addButton"
+ oncommand="gSmtpServerListWindow.onAddServer(event);"
+ label="&smtpListAdd.label;"
+ accesskey="&smtpListAdd.accesskey;"/>
+ <button id="editButton"
+ oncommand="gSmtpServerListWindow.onEditServer(event);"
+ label="&smtpListEdit.label;"
+ accesskey="&smtpListEdit.accesskey;"/>
+ <separator/>
+ <button id="deleteButton" disabled="true"
+ oncommand="gSmtpServerListWindow.onDeleteServer(event);"
+ label="&smtpListDelete.label;"
+ accesskey="&smtpListDelete.accesskey;"/>
+ <button id="setDefaultButton" disabled="true"
+ oncommand="gSmtpServerListWindow.onSetDefaultServer(event);"
+ label="&smtpListSetDefault.label;"
+ accesskey="&smtpListSetDefault.accesskey;"/>
+ </vbox>
+ </hbox>
+
+ <separator/>
+
+ <label class="header">&serverDetails.label;</label>
+ <hbox id="smtpServerInfoBox">
+ <stack flex="1" class="inset">
+ <spacer id="backgroundBox"/>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <hbox pack="end">
+ <label id="descriptionLabel"
+ value="&serverDescription.label;"
+ control="descriptionValue"/>
+ </hbox>
+ <textbox id="descriptionValue" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label id="nameLabel" value="&serverName.label;" control="nameValue"/>
+ </hbox>
+ <textbox id="nameValue" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label id="portLabel" value="&serverPort.label;" control="portValue"/>
+ </hbox>
+ <textbox id="portValue" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label id="userNameLabel" value="&userName.label;" control="userNameValue"/>
+ </hbox>
+ <textbox id="userNameValue" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label id="authMethodLabel" value="&authMethod.label;" control="authMethodValue"/>
+ </hbox>
+ <textbox id="authMethodValue" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label id="connectionSecurityLabel" value="&connectionSecurity.label;"
+ control="useSecureConnectionValue"/>
+ </hbox>
+ <textbox id="useSecureConnectionValue" readonly="true" class="plain"/>
+ </row>
+ </rows>
+ </grid>
+ </stack>
+ </hbox>
+ <separator flex="1"/>
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/amUtils.js b/mailnews/base/prefs/content/amUtils.js
new file mode 100644
index 000000000..2b101f9f1
--- /dev/null
+++ b/mailnews/base/prefs/content/amUtils.js
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/MailUtils.js");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+
+function BrowseForLocalFolders()
+{
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+ const nsIFile = Components.interfaces.nsIFile;
+
+ var currentFolderTextBox = document.getElementById("server.localPath");
+ var fp = Components.classes["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+
+ fp.init(window,
+ document.getElementById("browseForLocalFolder")
+ .getAttribute("filepickertitle"),
+ nsIFilePicker.modeGetFolder);
+
+ var currentFolder = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(nsIFile);
+ currentFolder.initWithPath(currentFolderTextBox.value);
+ fp.displayDirectory = currentFolder;
+
+ if (fp.show() != nsIFilePicker.returnOK)
+ return;
+
+ // Retrieve the selected folder.
+ let selectedFolder = fp.file;
+
+ // Check if the folder can be used for mail storage.
+ if (!top.checkDirectoryIsUsable(selectedFolder))
+ return;
+
+ currentFolderTextBox.value = selectedFolder.path;
+}
+
+/**
+ * Return server/folder name formatted with server name if needed.
+ *
+ * @param aTargetFolder nsIMsgFolder to format name for
+ * If target.isServer then only its name is returned.
+ * Otherwise return the name as "<foldername> on <servername>".
+ */
+function prettyFolderName(aTargetFolder)
+{
+ if (aTargetFolder.isServer)
+ return aTargetFolder.prettyName;
+
+ return document.getElementById("bundle_messenger")
+ .getFormattedString("verboseFolderFormat",
+ [aTargetFolder.prettyName,
+ aTargetFolder.server.prettyName]);
+}
+
+/**
+ * Checks validity of junk target server name and folder.
+ *
+ * @param aTargetURI the URI specification to check
+ * @param aIsServer true if the URI specifies only a server (without folder)
+ *
+ * @return the value of aTargetURI if it is valid (usable), otherwise null
+ */
+function checkJunkTargetFolder(aTargetURI, aIsServer)
+{
+ try {
+ // Does the target account exist?
+ let targetServer = MailUtils.getFolderForURI(aTargetURI + (aIsServer ? "/Junk" : ""),
+ !aIsServer).server;
+
+ // If the target server has deferred storage, Junk can't be stored into it.
+ if (targetServer.rootFolder != targetServer.rootMsgFolder)
+ return null;
+ } catch (e) {
+ return null;
+ }
+
+ return aTargetURI;
+}
+
+/**
+ * Finds a usable target for storing Junk mail.
+ * If the passed in server URI is not usable, choose Local Folders.
+ *
+ * @param aTargetURI the URI of a server or folder to try first
+ * @param aIsServer true if the URI specifies only a server (without folder)
+ *
+ * @return the server/folder URI of a usable target for storing Junk
+ */
+function chooseJunkTargetFolder(aTargetURI, aIsServer)
+{
+ let server = null;
+
+ if (aTargetURI) {
+ server = MailUtils.getFolderForURI(aTargetURI, false).server;
+ if (!server.canCreateFoldersOnServer || !server.canSearchMessages ||
+ (server.rootFolder != server.rootMsgFolder))
+ server = null;
+ }
+ if (!server)
+ server = MailServices.accounts.localFoldersServer;
+
+ return server.serverURI + (!aIsServer ? "/Junk" : "");
+}
+
+/**
+ * Fixes junk target folders if they point to an invalid/unusable (e.g. deferred)
+ * folder/account. Only returns the new safe values. It is up to the caller
+ * to push them to the proper elements/prefs.
+ *
+ * @param aSpamActionTargetAccount The value of the server.*.spamActionTargetAccount pref value (URI).
+ * @param aSpamActionTargetFolder The value of the server.*.spamActionTargetFolder pref value (URI).
+ * @param aProposedTarget The URI of a new target to try.
+ * @param aMoveTargetModeValue The value of the server.*.moveTargetMode pref value (0/1).
+ * @param aServerSpamSettings The nsISpamSettings object of any server
+ * (used just for the MOVE_TARGET_MODE_* constants).
+ * @param aMoveOnSpam The server.*.moveOnSpam pref value (bool).
+ *
+ * @return an array containing:
+ * newTargetAccount new safe junk target account
+ * newTargetAccount new safe junk target folder
+ * newMoveOnSpam new moveOnSpam value
+ */
+function sanitizeJunkTargets(aSpamActionTargetAccount,
+ aSpamActionTargetFolder,
+ aProposedTarget,
+ aMoveTargetModeValue,
+ aServerSpamSettings,
+ aMoveOnSpam)
+{
+ // Check if folder targets are valid.
+ aSpamActionTargetAccount = checkJunkTargetFolder(aSpamActionTargetAccount, true);
+ if (!aSpamActionTargetAccount) {
+ // If aSpamActionTargetAccount is not valid,
+ // reset to default behavior to NOT move junk messages...
+ if (aMoveTargetModeValue == aServerSpamSettings.MOVE_TARGET_MODE_ACCOUNT)
+ aMoveOnSpam = false;
+
+ // ... and find a good default target.
+ aSpamActionTargetAccount = chooseJunkTargetFolder(aProposedTarget, true);
+ }
+
+ aSpamActionTargetFolder = checkJunkTargetFolder(aSpamActionTargetFolder, false);
+ if (!aSpamActionTargetFolder) {
+ // If aSpamActionTargetFolder is not valid,
+ // reset to default behavior to NOT move junk messages...
+ if (aMoveTargetModeValue == aServerSpamSettings.MOVE_TARGET_MODE_FOLDER)
+ aMoveOnSpam = false;
+
+ // ... and find a good default target.
+ aSpamActionTargetFolder = chooseJunkTargetFolder(aProposedTarget, false);
+ }
+
+ return [ aSpamActionTargetAccount, aSpamActionTargetFolder, aMoveOnSpam ];
+}
+
+/**
+ * Opens Preferences (Options) dialog on the Advanced pane, General tab
+ * so that the user sees where the global receipts settings can be found.
+ *
+ * @param aTBPaneId Thunderbird pref paneID to open.
+ * @param aTBTabId Thunderbird tabID to open.
+ * @param aTBOtherArgs Other arguments to send to the pref tab.
+ * @param aSMPaneId Seamonkey pref pane to open.
+ */
+function openPrefsFromAccountManager(aTBPaneId, aTBTabId, aTBOtherArgs, aSMPaneId) {
+ let win = Services.wm.getMostRecentWindow("mail:3pane") ||
+ Services.wm.getMostRecentWindow("mail:messageWindow") ||
+ Services.wm.getMostRecentWindow("msgcompose");
+ if (!win)
+ return;
+
+ // If openOptionsDialog() exists, we are in Thunderbird.
+ if (typeof win.openOptionsDialog == "function")
+ win.openOptionsDialog(aTBPaneId, aTBTabId, aTBOtherArgs);
+ // If goPreferences() exists, we are in Seamonkey.
+ if (typeof win.goPreferences == "function")
+ win.goPreferences(aSMPaneId);
+}
+
+/**
+ * Check if the given account name already exists in any account.
+ *
+ * @param aAccountName The account name string to look for.
+ * @param aAccountKey Optional. The key of an account that is skipped when
+ * searching the name. If unset, do not skip any account.
+ */
+function accountNameExists(aAccountName, aAccountKey)
+{
+ for (let account in fixIterator(MailServices.accounts.accounts,
+ Components.interfaces.nsIMsgAccount))
+ {
+ if (account.key != aAccountKey && account.incomingServer &&
+ aAccountName == account.incomingServer.prettyName) {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/mailnews/base/prefs/content/aw-accname.js b/mailnews/base/prefs/content/aw-accname.js
new file mode 100644
index 000000000..6fce2e878
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-accname.js
@@ -0,0 +1,73 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gPrefsBundle;
+
+function acctNamePageValidate()
+{
+ var accountname = document.getElementById("prettyName").value;
+ var canAdvance = accountname ? true : false;
+
+ // Check if this accountname already exists. If so, return false so that
+ // user can enter a different unique account name.
+ if (canAdvance && accountNameExists(accountname))
+ canAdvance = false;
+
+ document.documentElement.canAdvance = canAdvance;
+}
+
+function acctNamePageUnload() {
+ var pageData = parent.GetPageData();
+
+ // fix for bug #255473
+ // allow for multiple RSS accounts.
+ // if our isp.rdf file defines "wizardAutoGenerateUniqueHostname"
+ // we generate a unique hostname until we have one that doesn't exist
+ // for RSS accounts, in rss.rdf, userName, hostName and serverType
+ // default to the same thing, so we need to do this to allow for
+ // multiple RSS accounts. Note, they can all have the same pretty name.
+ if (gCurrentAccountData &&
+ gCurrentAccountData.wizardAutoGenerateUniqueHostname)
+ {
+ var serverType = parent.getCurrentServerType(pageData);
+ var userName = parent.getCurrentUserName(pageData);
+ var hostName = parent.getCurrentHostname(pageData);
+ var hostNamePref = hostName;
+ var i = 2;
+ while (parent.AccountExists(userName, hostName, serverType))
+ {
+ // If "Feeds" exists, try "Feeds-2", then "Feeds-3", etc.
+ hostName = hostNamePref + "-" + i;
+ i++;
+ }
+ setPageData(pageData, "server", "hostname", hostName);
+ }
+
+ var accountname = document.getElementById("prettyName").value;
+ setPageData(pageData, "accname", "prettyName", accountname);
+ // Set this to true so we know the user has set the name.
+ setPageData(pageData, "accname", "userset", true);
+ return true;
+}
+
+function acctNamePageInit()
+{
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ var accountNameInput = document.getElementById("prettyName");
+ if (accountNameInput.value=="") {
+ var pageData = parent.GetPageData();
+ var type = parent.getCurrentServerType(pageData);
+ var accountName;
+
+ if (gCurrentAccountData && gCurrentAccountData.wizardAccountName)
+ accountName = gCurrentAccountData.wizardAccountName;
+ else if (type == "nntp")
+ accountName = pageData.newsserver.hostname.value;
+ else
+ accountName = pageData.identity.email.value;
+ accountNameInput.value = accountName;
+ }
+ acctNamePageValidate();
+}
diff --git a/mailnews/base/prefs/content/aw-accounttype.js b/mailnews/base/prefs/content/aw-accounttype.js
new file mode 100644
index 000000000..64b828594
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-accounttype.js
@@ -0,0 +1,114 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function setAccountTypeData()
+{
+ var rg = document.getElementById("acctyperadio");
+ var selectedItemId = rg.selectedItem.id;
+ var mail = selectedItemId == "mailaccount";
+ var news = selectedItemId == "newsaccount";
+
+ var pageData = parent.GetPageData();
+ setPageData(pageData, "accounttype", "mailaccount", mail);
+ setPageData(pageData, "accounttype", "newsaccount", news);
+
+ // Other account type, e.g. Movemail
+ setPageData(pageData, "accounttype", "otheraccount", !(news || mail));
+}
+
+function acctTypePageUnload() {
+ gCurrentAccountData = null;
+ setAccountTypeData();
+ initializeIspData();
+ setupWizardPanels();
+ return true;
+}
+
+function setupWizardPanels() {
+ if (gCurrentAccountData && gCurrentAccountData.useOverlayPanels) {
+ if ("testingIspServices" in this) {
+ if ("SetPageMappings" in this && testingIspServices()) {
+ SetPageMappings(document.documentElement.currentPage.id, "done");
+ }
+ }
+ }
+
+ var pageData = parent.GetPageData();
+
+ // We default this to false, even though we could set it to true if we
+ // are going to display the page. However as the accname page will set
+ // it to true for us, we'll just default it to false and not work it out
+ // twice.
+ setPageData(pageData, "accname", "userset", false);
+
+ // If we need to skip wizardpanels, set the wizard to jump to the
+ // summary page i.e., last page. Otherwise, set the flow based
+ // on type of account (mail or news) user is creating.
+ var skipPanels = "";
+ try {
+ if (gCurrentAccountData.wizardSkipPanels)
+ skipPanels = gCurrentAccountData.wizardSkipPanels.toString().toLowerCase();
+ } catch(ex) {}
+
+ // "done" is the only required panel for all accounts. We used to require an identity panel but not anymore.
+ // initialize wizardPanels with the optional mail/news panels
+ var wizardPanels, i;
+ var isMailAccount = pageData.accounttype.mailaccount;
+ var isNewsAccount = pageData.accounttype.newsaccount;
+ if (skipPanels == "true") // Support old syntax of true/false for wizardSkipPanels
+ wizardPanels = new Array("identitypage");
+ else if (isMailAccount && isMailAccount.value)
+ wizardPanels = new Array("identitypage", "incomingpage", "outgoingpage", "accnamepage");
+ else if (isNewsAccount && isNewsAccount.value)
+ wizardPanels = new Array("identitypage", "newsserver", "accnamepage");
+ else { // An account created by an extension and XUL overlays
+ var button = document.getElementById("acctyperadio").selectedItem;
+ wizardPanels = button.value.split(/ *, */);
+ }
+
+ // Create a hash table of the panels to skip
+ var skipArray = skipPanels.split(",");
+ var skipHash = new Array();
+ for (i = 0; i < skipArray.length; i++)
+ skipHash[skipArray[i]] = skipArray[i];
+
+ // Remove skipped panels
+ i = 0;
+ while (i < wizardPanels.length) {
+ if (wizardPanels[i] in skipHash)
+ wizardPanels.splice(i, 1);
+ else
+ i++;
+ }
+
+ wizardPanels.push("done");
+
+ // Set up order of panels
+ for (i = 0; i < (wizardPanels.length-1); i++)
+ setNextPage(wizardPanels[i], wizardPanels[i+1]);
+
+ // make the account type page go to the very first of our approved wizard panels...this is usually going to
+ // be accounttype --> identitypage unless we were configured to skip the identity page
+ setNextPage("accounttype",wizardPanels[0]);
+}
+
+function initializeIspData()
+{
+ let mailAccount = document.getElementById("mailaccount");
+ if (!mailAccount || !mailAccount.selected) {
+ parent.SetCurrentAccountData(null);
+ }
+
+ // now reflect the datasource up into the parent
+ var accountSelection = document.getElementById("acctyperadio");
+
+ var ispName = accountSelection.selectedItem.id;
+
+ dump("initializing ISP data for " + ispName + "\n");
+
+ if (!ispName || ispName == "") return;
+
+ parent.PrefillAccountForIsp(ispName);
+}
diff --git a/mailnews/base/prefs/content/aw-done.js b/mailnews/base/prefs/content/aw-done.js
new file mode 100644
index 000000000..79fb7d000
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-done.js
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gPrefsBundle;
+
+function donePageInit() {
+ var pageData = parent.GetPageData();
+ var currentAccountData = gCurrentAccountData;
+
+ if ("testingIspServices" in this) {
+ if (testingIspServices()) {
+ if ("setOtherISPServices" in this) {
+ setOtherISPServices();
+ }
+
+ if (currentAccountData && currentAccountData.useOverlayPanels && currentAccountData.createNewAccount) {
+ var backButton = document.documentElement.getButton("back");
+ backButton.setAttribute("disabled", true);
+
+ var cancelButton = document.documentElement.getButton("cancel");
+ cancelButton.setAttribute("disabled", true);
+
+ setPageData(pageData, "identity", "email", gEmailAddress);
+ setPageData(pageData, "identity", "fullName", gUserFullName);
+ setPageData(pageData, "login", "username", gScreenName);
+ }
+ }
+ }
+
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ var showMailServerDetails = true;
+
+ if (currentAccountData) {
+ // find out if we need to hide server details
+ showMailServerDetails = currentAccountData.showServerDetailsOnWizardSummary;
+ // Change the username field description to email field label in aw-identity
+ if (currentAccountData.emailIDFieldTitle)
+ setUserNameDescField(currentAccountData.emailIDFieldTitle);
+ }
+
+ // Find out if we need to hide details for incoming servers
+ var hideIncoming = (gCurrentAccountData && gCurrentAccountData.wizardHideIncoming);
+
+ var email = "";
+ if (pageData.identity && pageData.identity.email) {
+ // fixup the email
+ email = pageData.identity.email.value;
+ if (email.split('@').length < 2 &&
+ currentAccountData &&
+ currentAccountData.domain)
+ email += "@" + currentAccountData.domain;
+ }
+ setDivTextFromForm("identity.email", email);
+
+ var userName="";
+ if (pageData.login && pageData.login.username)
+ userName = pageData.login.username.value;
+ if (!userName && email) {
+ if (currentAccountData && currentAccountData.incomingServerUserNameRequiresDomain == undefined)
+ currentAccountData.incomingServerUserNameRequiresDomain = false;
+ userName = getUsernameFromEmail(email, currentAccountData &&
+ currentAccountData.incomingServerUserNameRequiresDomain);
+ }
+ // Hide the "username" field if we don't want to show information
+ // on the incoming server.
+ setDivTextFromForm("server.username", hideIncoming ? null : userName);
+
+ var smtpUserName="";
+ if (pageData.login && pageData.login.smtpusername)
+ smtpUserName = pageData.login.smtpusername.value;
+ if (!smtpUserName && email)
+ smtpUserName = getUsernameFromEmail(email, currentAccountData &&
+ currentAccountData.smtpUserNameRequiresDomain);
+ setDivTextFromForm("smtpServer.username", smtpUserName);
+
+ if (pageData.accname && pageData.accname.prettyName) {
+ // If currentAccountData has a pretty name (e.g. news link or from
+ // isp data) only set the user name with the pretty name if the
+ // pretty name originally came from ispdata
+ if ((currentAccountData &&
+ currentAccountData.incomingServer.prettyName) &&
+ (pageData.ispdata &&
+ pageData.ispdata.supplied &&
+ pageData.ispdata.supplied.value))
+ {
+ // Get the polished account name - if the user has modified the
+ // account name then use that, otherwise use the userName.
+ pageData.accname.prettyName.value =
+ gPrefsBundle.getFormattedString("accountName",
+ [currentAccountData.incomingServer.prettyName,
+ (pageData.accname.userset && pageData.accname.userset.value) ? pageData.accname.prettyName.value : userName]);
+ }
+ // else just use the default supplied name
+
+ setDivTextFromForm("account.name", pageData.accname.prettyName.value);
+ } else {
+ setDivTextFromForm("account.name", "");
+ }
+
+ // Show mail servers (incoming&outgoing) details
+ // based on current account data. ISP can set
+ // rdf value of literal showServerDetailsOnWizardSummary
+ // to false to hide server details
+ if (showMailServerDetails && !serverIsNntp(pageData)) {
+ var incomingServerName="";
+ if (pageData.server && pageData.server.hostname) {
+ incomingServerName = pageData.server.hostname.value;
+ if (!incomingServerName &&
+ currentAccountData &&
+ currentAccountData.incomingServer.hostname)
+ incomingServerName = currentAccountData.incomingServer.hostName;
+ }
+ // Hide the incoming server name field if the user specified
+ // wizardHideIncoming in the ISP defaults file
+ setDivTextFromForm("server.name", hideIncoming ? null : incomingServerName);
+ setDivTextFromForm("server.port", pageData.server.port ? pageData.server.port.value : null);
+ var incomingServerType="";
+ if (pageData.server && pageData.server.servertype) {
+ incomingServerType = pageData.server.servertype.value;
+ if (!incomingServerType &&
+ currentAccountData &&
+ currentAccountData.incomingServer.type)
+ incomingServerType = currentAccountData.incomingServer.type;
+ }
+ setDivTextFromForm("server.type", incomingServerType.toUpperCase());
+
+ var smtpServerName="";
+ if (pageData.server && pageData.server.smtphostname) {
+ let smtpServer = MailServices.smtp.defaultServer;
+ smtpServerName = pageData.server.smtphostname.value;
+ if (!smtpServerName && smtpServer && smtpServer.hostname)
+ smtpServerName = smtpServer.hostname;
+ }
+ setDivTextFromForm("smtpServer.name", smtpServerName);
+ }
+ else {
+ setDivTextFromForm("server.name", null);
+ setDivTextFromForm("server.type", null);
+ setDivTextFromForm("server.port", null);
+ setDivTextFromForm("smtpServer.name", null);
+ }
+
+ if (serverIsNntp(pageData)) {
+ var newsServerName="";
+ if (pageData.newsserver && pageData.newsserver.hostname)
+ newsServerName = pageData.newsserver.hostname.value;
+ if (newsServerName) {
+ // No need to show username for news account
+ setDivTextFromForm("server.username", null);
+ }
+ setDivTextFromForm("newsServer.name", newsServerName);
+ setDivTextFromForm("server.port", null);
+ }
+ else {
+ setDivTextFromForm("newsServer.name", null);
+ }
+
+ var isPop = false;
+ if (pageData.server && pageData.server.servertype) {
+ isPop = (pageData.server.servertype.value == "pop3");
+ }
+
+ hideShowDownloadMsgsUI(isPop);
+}
+
+function hideShowDownloadMsgsUI(isPop)
+{
+ // only show the "download messages now" UI
+ // if this is a pop account, we are online, and this was opened
+ // from the 3 pane
+ var downloadMsgs = document.getElementById("downloadMsgs");
+ if (isPop) {
+ if (!Services.io.offline) {
+ if (window.opener.location.href == "chrome://messenger/content/messenger.xul") {
+ downloadMsgs.hidden = false;
+ return;
+ }
+ }
+ }
+
+ // else hide it
+ downloadMsgs.hidden = true;
+}
+
+function setDivTextFromForm(divid, value) {
+
+ // collapse the row if the div has no value
+ var div = document.getElementById(divid);
+ if (!value) {
+ div.setAttribute("collapsed","true");
+ return;
+ }
+ else {
+ div.removeAttribute("collapsed");
+ }
+
+ // otherwise fill in the .text element
+ div = document.getElementById(divid+".text");
+ if (!div) return;
+
+ // set the value
+ div.setAttribute("value", value);
+}
+
+function setUserNameDescField(name)
+{
+ if (name) {
+ var userNameField = document.getElementById("server.username.label");
+ userNameField.setAttribute("value", name);
+ }
+}
diff --git a/mailnews/base/prefs/content/aw-identity.js b/mailnews/base/prefs/content/aw-identity.js
new file mode 100644
index 000000000..ed99c4400
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-identity.js
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gCurrentDomain;
+var gPrefsBundle;
+
+function identityPageValidate()
+{
+ var canAdvance = false;
+ var name = document.getElementById("fullName").value;
+ let email = document.getElementById("email").value.trim();
+
+ if (name && email) {
+ canAdvance = gCurrentDomain ? emailNameIsLegal(email) : emailNameAndDomainAreLegal(email);
+
+ if (gCurrentDomain && canAdvance) {
+ // For prefilled ISP data we must check if the account already exists as
+ // there is no second chance. The email is the username since the user
+ // fills in [______]@example.com.
+ var pageData = parent.GetPageData();
+ var serverType = parent.getCurrentServerType(pageData);
+ var hostName = parent.getCurrentHostname(pageData);
+ var usernameWithDomain = email + "@" + gCurrentDomain;
+ if (parent.AccountExists(email, hostName, serverType) ||
+ parent.AccountExists(usernameWithDomain, hostName, serverType))
+ canAdvance = false;
+ }
+ }
+
+ document.documentElement.canAdvance = canAdvance;
+}
+
+function identityPageUnload()
+{
+ var pageData = parent.GetPageData();
+ var name = document.getElementById("fullName").value;
+ let email = document.getElementById("email").value.trim();
+ setPageData(pageData, "identity", "fullName", name);
+ setPageData(pageData, "identity", "email", email);
+
+ return true;
+}
+
+// This is for the case when the code appends the domain
+// unnecessarily.
+// This simply gets rid of "@domain" from "foo@domain"
+
+function fixPreFilledEmail()
+{
+ var emailElement = document.getElementById("email");
+ var email = emailElement.value;
+ var emailArray = email.split('@');
+
+ if (gCurrentDomain) {
+ // check if user entered an @ sign even though we have a domain
+ if (emailArray.length >= 2) {
+ email = emailArray[0];
+ emailElement.value = email;
+ }
+ }
+}
+
+/**
+ * This function checks for common illegal characters.
+ * It shouldn't be too strict, since we do more extensive tests later.
+ */
+function emailNameIsLegal(aString)
+{
+ return aString && !/[^!-?A-~]/.test(aString);
+}
+
+function emailNameAndDomainAreLegal(aString)
+{
+ return /^[!-?A-~]+\@[A-Za-z0-9.-]+$/.test(aString);
+}
+
+function identityPageInit()
+{
+ gCurrentDomain = null;
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ clearEmailTextItems();
+ setEmailDescriptionText();
+ checkForDomain();
+ checkForFullName();
+ checkForEmail();
+ fixPreFilledEmail();
+ identityPageValidate();
+}
+
+function clearEmailTextItems()
+{
+ var emailDescText = document.getElementById("emailDescText");
+
+ if (emailDescText.hasChildNodes())
+ emailDescText.firstChild.remove();
+
+ var postEmailText = document.getElementById("postEmailText");
+ postEmailText.setAttribute("value", "");
+ postEmailText.hidden = true;
+}
+
+// Use email example data that ISP has provided. ISP data, if avaialble
+// for the choice user has made, will be read into CurrentAccountData.
+// Default example data from properties will be used when the info is missing.
+function setEmailDescriptionText()
+{
+ var emailDescText = document.getElementById("emailDescText");
+ var emailFieldLabel = document.getElementById("emailFieldLabel");
+ var currentAccountData = parent.gCurrentAccountData;
+
+ var displayText = null;
+ var emailFieldLabelData = null;
+ var setDefaultEmailDescStrings = true;
+
+ // Set the default field label
+ emailFieldLabel.setAttribute("value", gPrefsBundle.getString("emailFieldText"));
+
+ // Get values for customized data from current account
+ if (currentAccountData)
+ {
+ var emailProvider = currentAccountData.emailProviderName;
+ var sampleEmail = currentAccountData.sampleEmail;
+ var sampleUserName = currentAccountData.sampleUserName;
+ var emailIDDesc = currentAccountData.emailIDDescription;
+ var emailIDTitle = currentAccountData.emailIDFieldTitle;
+
+ if (emailProvider &&
+ sampleEmail &&
+ sampleUserName &&
+ emailIDDesc &&
+ emailIDTitle)
+ {
+ // Get email description data
+ displayText = gPrefsBundle.getFormattedString("customizedEmailText",
+ [emailProvider,
+ emailIDDesc,
+ sampleEmail,
+ sampleUserName]);
+
+ // Set emailfield label
+ emailFieldLabelData = emailIDTitle;
+ emailFieldLabel.setAttribute("value", emailFieldLabelData);
+
+ // Need to display customized data. Turn off default settings.
+ setDefaultEmailDescStrings = false;
+ }
+ }
+
+ if (setDefaultEmailDescStrings)
+ {
+ // Check for obtained values and set with default values if needed
+ var username = gPrefsBundle.getString("exampleEmailUserName");
+ var domain = gPrefsBundle.getString("exampleEmailDomain");
+
+ displayText = gPrefsBundle.getFormattedString("defaultEmailText",
+ [username, domain]);
+ }
+
+ // Create a text nodes with text to be displayed
+ var emailDescTextNode = document.createTextNode(displayText);
+
+ // Display the dynamically generated text for email description
+ emailDescText.appendChild(emailDescTextNode);
+}
+
+// retrieve the current domain from the parent wizard window,
+// and update the UI to add the @domain static text
+function checkForDomain()
+{
+ var accountData = parent.gCurrentAccountData;
+ if (!accountData || !accountData.domain)
+ return;
+
+ // save in global variable
+ gCurrentDomain = accountData.domain;
+ var postEmailText = document.getElementById("postEmailText");
+ postEmailText.setAttribute("value", "@" + gCurrentDomain);
+ postEmailText.hidden = false;
+}
+
+function checkForFullName() {
+ var name = document.getElementById("fullName");
+ if (name.value=="") {
+ try {
+ var userInfo = Components.classes["@mozilla.org/userinfo;1"].getService(Components.interfaces.nsIUserInfo);
+ name.value = userInfo.fullname;
+ }
+ catch (ex) {
+ // dump ("checkForFullName failed: " + ex + "\n");
+ }
+ }
+}
+
+function checkForEmail()
+{
+ var email = document.getElementById("email");
+ var pageData = parent.GetPageData();
+ if (pageData && pageData.identity && pageData.identity.email) {
+ email.value = pageData.identity.email.value;
+ }
+ if (email.value=="") {
+ try {
+ var userInfo = Components.classes["@mozilla.org/userinfo;1"].getService(Components.interfaces.nsIUserInfo);
+ email.value = userInfo.emailAddress;
+ }
+ catch (ex) {
+ // dump ("checkForEmail failed: " + ex + "\n");
+ }
+ }
+}
diff --git a/mailnews/base/prefs/content/aw-incoming.js b/mailnews/base/prefs/content/aw-incoming.js
new file mode 100644
index 000000000..fcc063fed
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-incoming.js
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+
+var gOnMailServersPage;
+var gOnNewsServerPage;
+var gHideIncoming;
+var gProtocolInfo = null;
+
+function incomingPageValidate()
+{
+ var canAdvance = true;
+ var hostName;
+
+ if (gOnMailServersPage) {
+ hostName = document.getElementById("incomingServer").value;
+ if (!gHideIncoming && !isLegalHostNameOrIP(cleanUpHostName(hostName)))
+ canAdvance = false;
+ }
+ if (gOnNewsServerPage) {
+ hostName = document.getElementById("newsServer").value;
+ if (!isLegalHostNameOrIP(cleanUpHostName(hostName)))
+ canAdvance = false;
+ }
+
+ if (canAdvance) {
+ var pageData = parent.GetPageData();
+ var serverType = parent.getCurrentServerType(pageData);
+ var username = document.getElementById("username").value;
+ if (gProtocolInfo && gProtocolInfo.requiresUsername && !username ||
+ parent.AccountExists(username, hostName, serverType))
+ canAdvance = false;
+ }
+
+ document.documentElement.canAdvance = canAdvance;
+}
+
+function incomingPageUnload()
+{
+ var pageData = parent.GetPageData();
+
+ if (gOnMailServersPage) {
+ // If we have hidden the incoming server dialogs, we don't want
+ // to set the server to an empty value here
+ if (!gHideIncoming) {
+ var incomingServerName = document.getElementById("incomingServer");
+ setPageData(pageData, "server", "hostname", cleanUpHostName(incomingServerName.value));
+ }
+ var serverport = document.getElementById("serverPort").value;
+ setPageData(pageData, "server", "port", serverport);
+ var username = document.getElementById("username").value;
+ setPageData(pageData, "login", "username", username);
+ }
+ else if (gOnNewsServerPage) {
+ var newsServerName = document.getElementById("newsServer");
+ setPageData(pageData, "newsserver", "hostname", cleanUpHostName(newsServerName.value));
+ }
+
+ return true;
+}
+
+function incomingPageInit() {
+ gOnMailServersPage = (document.documentElement.currentPage.id == "incomingpage");
+ gOnNewsServerPage = (document.documentElement.currentPage.id == "newsserver");
+ if (gOnNewsServerPage)
+ {
+ var newsServer = document.getElementById("newsServer");
+ var pageData = parent.GetPageData();
+ try
+ {
+ newsServer.value = pageData.newsserver.hostname.value;
+ }
+ catch (ex){}
+ }
+
+ gHideIncoming = false;
+ if (gCurrentAccountData && gCurrentAccountData.wizardHideIncoming)
+ gHideIncoming = true;
+
+ var incomingServerbox = document.getElementById("incomingServerbox");
+ var serverTypeBox = document.getElementById("serverTypeBox");
+ if (incomingServerbox && serverTypeBox) {
+ if (gHideIncoming) {
+ incomingServerbox.setAttribute("hidden", "true");
+ serverTypeBox.setAttribute("hidden", "true");
+ }
+ else {
+ incomingServerbox.removeAttribute("hidden");
+ serverTypeBox.removeAttribute("hidden");
+ }
+ }
+
+ // Server type selection (pop3 or imap) is for mail accounts only
+ var pageData = parent.GetPageData();
+ var isMailAccount = pageData.accounttype.mailaccount.value;
+ var isOtherAccount = pageData.accounttype.otheraccount.value;
+ if (isMailAccount && !gHideIncoming) {
+ var serverTypeRadioGroup = document.getElementById("servertype");
+ /*
+ * Check to see if the radiogroup has any value. If there is no
+ * value, this must be the first time user visting this page in the
+ * account setup process. So, the default is set to pop3. If there
+ * is a value (it's used automatically), user has already visited
+ * page and server type selection is done. Once user visits the page,
+ * the server type value from then on will persist (whether the selection
+ * came from the default or the user action).
+ */
+ if (!serverTypeRadioGroup.value) {
+ /*
+ * if server type was set to imap in isp data, then
+ * we preset the server type radio group accordingly,
+ * otherwise, use pop3 as the default.
+ */
+ var serverTypeRadioItem = document.getElementById(pageData.server &&
+ pageData.server.servertype && pageData.server.servertype.value == "imap" ?
+ "imap" : "pop3");
+ serverTypeRadioGroup.selectedItem = serverTypeRadioItem; // Set pop3 server type as default selection
+ }
+ var leaveMessages = document.getElementById("leaveMessagesOnServer");
+ var deferStorage = document.getElementById("deferStorage");
+ setServerType();
+ setServerPrefs(leaveMessages);
+ setServerPrefs(deferStorage);
+ }
+ else if (isOtherAccount) {
+ document.getElementById("deferStorageBox").hidden = true;
+ }
+
+ if (pageData.server && pageData.server.hostname) {
+ var incomingServerTextBox = document.getElementById("incomingServer");
+ if (incomingServerTextBox && incomingServerTextBox.value == "")
+ incomingServerTextBox.value = pageData.server.hostname.value;
+ }
+
+ // pageData.server is not a real nsMsgIncomingServer so it does not have
+ // protocolInfo property implemented.
+ let type = parent.getCurrentServerType(pageData);
+ gProtocolInfo = Components.classes["@mozilla.org/messenger/protocol/info;1?type=" + type]
+ .getService(Components.interfaces.nsIMsgProtocolInfo);
+ var loginNameInput = document.getElementById("username");
+
+ if (loginNameInput.value == "") {
+ if (gProtocolInfo.requiresUsername) {
+ // since we require a username, use the uid from the email address
+ loginNameInput.value = parent.getUsernameFromEmail(pageData.identity.email.value, gCurrentAccountData &&
+ gCurrentAccountData.incomingServerUserNameRequiresDomain);
+ }
+ }
+ incomingPageValidate();
+}
+
+function setServerType()
+{
+ var pageData = parent.GetPageData();
+ var serverType = document.getElementById("servertype").value;
+ var deferStorageBox = document.getElementById("deferStorageBox");
+ var leaveMessages = document.getElementById("leaveMsgsOnSrvrBox");
+ var port = serverType == "pop3" ? 110 : 143;
+
+ document.getElementById("serverPort").value = port;
+ document.getElementById("defaultPortValue").value = port;
+
+ deferStorageBox.hidden = serverType == "imap";
+ leaveMessages.hidden = serverType == "imap";
+ setPageData(pageData, "server", "servertype", serverType);
+ setPageData(pageData, "server", "port", port);
+ incomingPageValidate();
+}
+
+function setServerPrefs(aThis)
+{
+ setPageData(parent.GetPageData(), "server", aThis.id, aThis.checked);
+}
diff --git a/mailnews/base/prefs/content/aw-outgoing.js b/mailnews/base/prefs/content/aw-outgoing.js
new file mode 100644
index 000000000..8caffc274
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-outgoing.js
@@ -0,0 +1,151 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gProtocolInfo = null;
+var gPrefsBundle;
+
+function outgoingPageValidate() {
+ let canAdvance = true;
+
+ let smtpServer = document.getElementById("smtphostname").value;
+ let usingDefaultSMTP = document.getElementById("noSmtp").hidden;
+ if (!usingDefaultSMTP && !isLegalHostNameOrIP(cleanUpHostName(smtpServer)))
+ canAdvance = false;
+
+ document.documentElement.canAdvance = canAdvance;
+}
+
+function outgoingPageUnload() {
+ var pageData = parent.GetPageData();
+ var username = document.getElementById("username").value;
+ let smtpserver = document.getElementById("smtphostname").value;
+ setPageData(pageData, "server", "smtphostname", cleanUpHostName(smtpserver));
+
+ // If SMTP username box is blank it is because the
+ // incoming and outgoing server names were the same,
+ // so set to be same as incoming username
+ var smtpusername = document.getElementById("smtpusername").value || username;
+
+ setPageData(pageData, "login", "smtpusername", smtpusername);
+
+ return true;
+}
+
+function outgoingPageInit() {
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ var pageData = parent.GetPageData();
+
+ var smtpServer = null;
+ var smtpCreateNewServer = gCurrentAccountData && gCurrentAccountData.smtpCreateNewServer;
+
+ // Don't use the default smtp server if smtp server creation was explicitly
+ // requested in isp rdf.
+ // If we're reusing the default smtp we should not set the smtp hostname.
+ if (MailServices.smtp.defaultServer && !smtpCreateNewServer) {
+ smtpServer = MailServices.smtp.defaultServer;
+ setPageData(pageData, "identity", "smtpServerKey", "");
+ }
+
+ var noSmtpBox = document.getElementById("noSmtp");
+ var haveSmtpBox = document.getElementById("haveSmtp");
+
+ var boxToHide;
+ var boxToShow;
+
+ if (pageData.server && pageData.server.smtphostname && smtpCreateNewServer) {
+ var smtpTextBox = document.getElementById("smtphostname");
+ if (smtpTextBox && smtpTextBox.value == "")
+ smtpTextBox.value = pageData.server.smtphostname.value;
+ }
+
+ if (smtpServer && smtpServer.hostname) {
+ // we have a hostname, so modify and show the static text and
+ // store the value of the default smtp server in the textbox.
+ modifyStaticText(smtpServer.hostname, "1")
+ boxToShow = haveSmtpBox;
+ boxToHide = noSmtpBox;
+ }
+ else {
+ // no default hostname yet
+ boxToShow = noSmtpBox;
+ boxToHide = haveSmtpBox;
+ }
+
+ if (boxToHide)
+ boxToHide.setAttribute("hidden", "true");
+
+ if (boxToShow)
+ boxToShow.removeAttribute("hidden");
+
+ var smtpNameInput = document.getElementById("smtpusername");
+ smtpServer = MailServices.smtp.defaultServer;
+ if (smtpServer && smtpServer.hostname && smtpServer.username) {
+ // we have a default SMTP server, so modify and show the static text
+ // and store the username for the default server in the textbox.
+ modifyStaticText(smtpServer.username, "2")
+ hideShowLoginSettings(2, 1, 3);
+ smtpNameInput.value = smtpServer.username;
+ }
+ else {
+ // no default SMTP server yet, so need to compare
+ // incoming and outgoing server names
+ var smtpServerName = pageData.server.smtphostname.value;
+ var incomingServerName = pageData.server.hostname.value;
+ if (smtpServerName == incomingServerName) {
+ // incoming and outgoing server names are the same, so show
+ // the static text and make sure textbox blank for later tests.
+ modifyStaticText(smtpServerName, "3")
+ hideShowLoginSettings(3, 1, 2);
+ smtpNameInput.value = "";
+ }
+ else {
+ // incoming and outgoing server names are different, so set smtp
+ // username's textbox to be the same as incoming's one, unless already set.
+ hideShowLoginSettings(1, 2, 3);
+ smtpNameInput.value = smtpNameInput.value || loginNameInput.value;
+ }
+ }
+ outgoingPageValidate();
+}
+
+function modifyStaticText(smtpMod, smtpBox)
+{
+ // modify the value in the smtp display if we already have a
+ // smtp server so that the single string displays the hostname
+ // or username for the smtp server.
+ var smtpStatic = document.getElementById("smtpStaticText"+smtpBox);
+ if (smtpStatic && smtpStatic.hasChildNodes())
+ smtpStatic.childNodes[0].nodeValue = smtpStatic.getAttribute("prefix") +
+ smtpMod + smtpStatic.getAttribute("suffix");
+}
+
+function hideShowLoginSettings(aEle, bEle, cEle)
+{
+ document.getElementById("loginSet" + aEle).hidden = false;
+ document.getElementById("loginSet" + bEle).hidden = true;
+ document.getElementById("loginSet" + cEle).hidden = true;
+}
+
+var savedPassword="";
+
+function onSavePassword(target) {
+ dump("savePassword changed! (" + target.checked + ")\n");
+ var passwordField = document.getElementById("server.password");
+ if (!passwordField) return;
+
+ if (target.checked) {
+ passwordField.removeAttribute("disabled");
+ passwordField.value = savedPassword;
+ }
+ else {
+ passwordField.setAttribute("disabled", "true");
+ savedPassword = passwordField.value;
+ passwordField.value = "";
+ }
+
+}
diff --git a/mailnews/base/prefs/content/ispUtils.js b/mailnews/base/prefs/content/ispUtils.js
new file mode 100644
index 000000000..7735d8a47
--- /dev/null
+++ b/mailnews/base/prefs/content/ispUtils.js
@@ -0,0 +1,166 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// using the rdf service extensively here
+var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
+
+// all the RDF resources we'll be retrieving
+var NC = "http://home.netscape.com/NC-rdf#";
+var Server = rdf.GetResource(NC + "Server");
+var SmtpServer = rdf.GetResource(NC + "SmtpServer");
+var ServerHost = rdf.GetResource(NC + "ServerHost");
+var ServerType = rdf.GetResource(NC + "ServerType");
+var PrefixIsUsername = rdf.GetResource(NC + "PrefixIsUsername");
+var UseAuthenticatedSmtp= rdf.GetResource(NC + "UseAuthenticatedSmtp");
+
+// this is possibly expensive, not sure what to do here
+var ispDefaults;
+
+var nsIRDFResource = Components.interfaces.nsIRDFResource;
+var nsIRDFLiteral = Components.interfaces.nsIRDFLiteral;
+
+var ispRoot = rdf.GetResource("NC:ispinfo");
+
+// given an ISP's domain URI, look up all relevant information about it
+function getIspDefaultsForUri(domainURI)
+{
+ if (!ispDefaults)
+ ispDefaults = rdf.GetDataSource("rdf:ispdefaults");
+
+ var domainRes = rdf.GetResource(domainURI);
+
+ var result = dataSourceToObject(ispDefaults, domainRes);
+
+ if (!result) return null;
+
+ // The domainURI should be in the format domain:example.com. (Where
+ // example.com is the domain name to use for all email addresses). If
+ // it does not match this pattern, then it is possible no domain
+ // has been specified, so we should leave it uninitialized.
+ if (domainURI.startsWith("domain:")) {
+ // add this extra attribute which is the domain itself
+ var domainData = domainURI.split(':');
+ if (domainData.length > 1) {
+ // To faciltate distributing two different account types for one ISP,
+ // it's possible to add parameters to the domain URI
+ // - e.g. domain:example.com?type=imap.
+ // This is necessary so RDF doesn't think they're the same.
+
+ // Save the domain, but only the part up to the (possible) question mark.
+ result.domain = domainData[1].replace(/\?.*/, "");
+ }
+ }
+ return result;
+}
+
+// construct an ISP's domain URI based on it's domain
+// (i.e. turns example.com -> domain:example.com)
+function getIspDefaultsForDomain(domain) {
+ let domainURI = "domain:" + domain;
+ return getIspDefaultsForUri(domainURI);
+}
+
+// Given an email address (like "joe@example.com") look up
+function getIspDefaultsForEmail(email) {
+
+ var emailData = getEmailInfo(email);
+
+ var ispData = null;
+ if (emailData)
+ ispData = getIspDefaultsForDomain(emailData.domain);
+
+ prefillIspData(ispData, email);
+
+ return ispData;
+}
+
+// given an email address, split it into username and domain
+// return in an associative array
+function getEmailInfo(email) {
+ if (!email) return null;
+
+ var result = new Object;
+
+ var emailData = email.split('@');
+
+ if (emailData.length != 2) {
+ dump("bad e-mail address!\n");
+ return null;
+ }
+
+ // all the variables we'll be returning
+ result.username = emailData[0];
+ result.domain = emailData[1];
+
+ return result;
+}
+
+function prefillIspData(ispData, email, fullName) {
+ if (!ispData) return;
+
+ // make sure these objects exist
+ if (!ispData.identity) ispData.identity = new Object;
+ if (!ispData.incomingServer) ispData.incomingServer = new Object;
+
+ // fill in e-mail if it's not already there
+ if (email && !ispData.identity.email)
+ ispData.identity.email = email;
+
+ var emailData = getEmailInfo(email);
+ if (emailData) {
+
+ // fill in the username (assuming the ISP doesn't prevent it)
+ if (!ispData.incomingServer.userName &&
+ !ispData.incomingServer.noDefaultUsername)
+ ispData.incomingServer.username = emailData.username;
+ }
+}
+
+// this function will extract an entire datasource into a giant
+// associative array for easy retrieval from JS
+var NClength = NC.length;
+function dataSourceToObject(datasource, root)
+{
+ var result = null;
+ var arcs = datasource.ArcLabelsOut(root);
+
+ while (arcs.hasMoreElements()) {
+ var arc = arcs.getNext().QueryInterface(nsIRDFResource);
+
+ var arcName = arc.Value;
+ arcName = arcName.substring(NClength, arcName.length);
+
+ if (!result) result = new Object;
+
+ var target = datasource.GetTarget(root, arc, true);
+
+ var value;
+ var targetHasChildren = false;
+ try {
+ target = target.QueryInterface(nsIRDFResource);
+ targetHasChildren = true;
+ } catch (ex) {
+ target = target.QueryInterface(nsIRDFLiteral);
+ }
+
+ if (targetHasChildren)
+ value = dataSourceToObject(datasource, target);
+ else {
+ value = target.Value;
+
+ // fixup booleans/numbers/etc
+ if (value == "true") value = true;
+ else if (value == "false") value = false;
+ else {
+ var num = Number(value);
+ if (!isNaN(num)) value = num;
+ }
+ }
+
+ // add this value
+ result[arcName] = value;
+ }
+ return result;
+}
diff --git a/mailnews/base/prefs/content/removeAccount.js b/mailnews/base/prefs/content/removeAccount.js
new file mode 100644
index 000000000..77bbe58d5
--- /dev/null
+++ b/mailnews/base/prefs/content/removeAccount.js
@@ -0,0 +1,148 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gServer;
+var gDialog;
+
+function onLoad(event) {
+ gServer = window.arguments[0].account.incomingServer;
+ gDialog = document.documentElement;
+
+ let bundle = document.getElementById("bundle_removeAccount");
+ let removeQuestion = bundle.getFormattedString("removeQuestion",
+ [gServer.prettyName]);
+ document.getElementById("accountName").textContent = removeQuestion;
+
+ // Allow to remove account data if it has a local storage.
+ let localDirectory = gServer.localPath;
+ if (localDirectory && localDirectory.exists()) {
+ localDirectory.normalize();
+
+ // Do not allow removal if localPath is outside of profile folder.
+ let profilePath = Services.dirsvc.get("ProfD", Components.interfaces.nsIFile);
+ profilePath.normalize();
+
+ // TODO: bug 77652, decide what to do for deferred accounts.
+ // And inform the user if the account localPath is outside the profile.
+ if ((gServer.isDeferredTo ||
+ (gServer instanceof Components.interfaces.nsIPop3IncomingServer &&
+ gServer.deferredToAccount)) ||
+ !profilePath.contains(localDirectory)) {
+ document.getElementById("removeData").disabled = true;
+ }
+ } else {
+ document.getElementById("removeDataPossibility").collapsed = true;
+ }
+
+ enableRemove();
+ window.sizeToContent();
+}
+
+function enableRemove() {
+ gDialog.getButton("accept").disabled =
+ (!document.getElementById("removeAccount").checked &&
+ !document.getElementById("removeData").checked);
+}
+
+/**
+ * Show the local directory.
+ */
+function openLocalDirectory() {
+ let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
+ "nsILocalFile", "initWithPath");
+ let localDir = gServer.localPath.path;
+ try {
+ new nsLocalFile(localDir).reveal();
+ } catch(e) {
+ // Reveal may fail e.g. on Linux, then just show the path as a string.
+ document.getElementById("localDirectory").value = localDir;
+ document.getElementById("localDirectory").collapsed = false;
+ }
+}
+
+function showInfo() {
+ let descs = document.querySelectorAll("vbox.indent");
+ for (let desc of descs) {
+ desc.collapsed = false;
+ }
+
+ // TODO: bug 1238271, this should use showFor attributes if possible.
+ if (gServer.type == "imap" || gServer.type == "nntp") {
+ document.getElementById("serverAccount").collapsed = false;
+ } else {
+ document.getElementById("localAccount").collapsed = false;
+ }
+
+ window.sizeToContent();
+ gDialog.getButton("disclosure").disabled = true;
+ gDialog.getButton("disclosure").blur();
+}
+
+function removeAccount() {
+ let removeAccount = document.getElementById("removeAccount").checked;
+ let removeData = document.getElementById("removeData").checked;
+ try {
+ // Remove the requested account data.
+ if (removeAccount) {
+ // Remove account
+ // Need to save these before the account and its server is removed.
+ let serverUri = gServer.type + "://" + gServer.hostName;
+ let serverUsername = gServer.username;
+
+ MailServices.accounts.removeAccount(window.arguments[0].account, removeData);
+ window.arguments[0].result = true;
+
+ // Remove password information.
+ let logins = Services.logins.findLogins({}, serverUri, null, serverUri);
+ for (let i = 0; i < logins.length; i++) {
+ if (logins[i].username == serverUsername) {
+ Services.logins.removeLogin(logins[i]);
+ break;
+ }
+ }
+ } else if (removeData) {
+ // Remove files only.
+ // TODO: bug 1302193
+ window.arguments[0].result = false;
+ }
+
+ document.getElementById("status").selectedPanel =
+ document.getElementById("success");
+ } catch (ex) {
+ document.getElementById("status").selectedPanel =
+ document.getElementById("failure");
+ Components.utils.reportError("Failure to remove account: " + ex);
+ window.arguments[0].result = false;
+ }
+}
+
+function onAccept() {
+ // If Cancel is disabled, we already tried to remove the account
+ // and can only close the dialog.
+ if (gDialog.getButton("cancel").disabled)
+ return true;
+
+ gDialog.getButton("accept").disabled = true;
+ gDialog.getButton("cancel").disabled = true;
+ gDialog.getButton("disclosure").disabled = true;
+
+ // Change the "Remove" to an "OK" button by clearing the custom label.
+ gDialog.removeAttribute("buttonlabelaccept");
+ gDialog.removeAttribute("buttonaccesskeyaccept");
+ gDialog.getButton("accept").removeAttribute("label");
+ gDialog.getButton("accept").removeAttribute("accesskey");
+ gDialog.buttons = "accept";
+
+ document.getElementById("infoPane").selectedIndex = 1;
+ window.sizeToContent();
+
+ removeAccount();
+
+ gDialog.getButton("accept").disabled = false;
+ return false;
+}
diff --git a/mailnews/base/prefs/content/removeAccount.xul b/mailnews/base/prefs/content/removeAccount.xul
new file mode 100644
index 000000000..c1d565973
--- /dev/null
+++ b/mailnews/base/prefs/content/removeAccount.xul
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % removalDTD SYSTEM "chrome://messenger/locale/removeAccount.dtd">
+%removalDTD;
+]>
+
+<dialog id="removeAccountDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&dialogTitle;"
+ width="600"
+ buttons="accept,disclosure,cancel"
+ buttonlabelaccept="&removeButton.label;"
+ buttonaccesskeyaccept="&removeButton.accesskey;"
+ defaultButton="cancel"
+ onload="onLoad();"
+ ondialogdisclosure="showInfo();"
+ ondialogaccept="return onAccept();">
+ <stringbundle id="bundle_removeAccount"
+ src="chrome://messenger/locale/removeAccount.properties"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/removeAccount.js"/>
+ <deck id="infoPane">
+ <vbox flex="1">
+ <label id="accountName"></label>
+ <separator class="thin"/>
+ <checkbox id="removeAccount"
+ label="&removeAccount.label;"
+ checked="true"
+ disabled="true"
+ accesskey="&removeAccount.accesskey;"
+ oncommand="enableRemove();"/>
+ <vbox class="indent" collapsed="true">
+ <description>
+ &removeAccount.desc;
+ </description>
+ </vbox>
+ <vbox id="removeDataPossibility" collapsed="false">
+ <checkbox id="removeData"
+ label="&removeData.label;"
+ checked="false"
+ accesskey="&removeData.accesskey;"
+ oncommand="enableRemove();"/>
+ <vbox class="indent" collapsed="true">
+ <description id="localAccount" collapsed="true">
+ &removeDataLocalAccount.desc;
+ </description>
+ <description id="serverAccount" collapsed="true">
+ &removeDataServerAccount.desc;
+ </description>
+ <hbox align="center">
+ <button id="showLocalDirectory"
+ label="&showData.label;"
+ accesskey="&showData.accesskey;"
+ oncommand="openLocalDirectory();"/>
+ <label id="localDirectory" collapsed="true"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ </vbox>
+ <vbox align="center">
+ <spacer flex="1"/>
+ <deck id="status">
+ <vbox align="center">
+ <label>&progressPending;</label>
+ <progressmeter mode="undetermined"/>
+ </vbox>
+ <label id="success">&progressSuccess;</label>
+ <label id="failure">&progressFailure;</label>
+ </deck>
+ <spacer flex="1"/>
+ </vbox>
+ </deck>
+</dialog>
diff --git a/mailnews/base/prefs/content/smtpEditOverlay.js b/mailnews/base/prefs/content/smtpEditOverlay.js
new file mode 100644
index 000000000..d8d216065
--- /dev/null
+++ b/mailnews/base/prefs/content/smtpEditOverlay.js
@@ -0,0 +1,184 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+// be real hacky with document.getElementById until document.controls works
+// with the new XUL widgets
+
+var gSmtpUsername;
+var gSmtpDescription;
+var gSmtpUsernameLabel;
+var gSmtpHostname;
+var gSmtpPort;
+var gSmtpAuthMethod;
+var gSmtpSocketType;
+var gPort;
+var gDefaultPort;
+var Ci = Components.interfaces;
+
+function initSmtpSettings(server) {
+ gSmtpUsername = document.getElementById("smtp.username");
+ gSmtpDescription = document.getElementById("smtp.description");
+ gSmtpUsernameLabel = document.getElementById("smtp.username.label");
+ gSmtpHostname = document.getElementById("smtp.hostname");
+ gSmtpPort = document.getElementById("smtp.port");
+ gSmtpAuthMethod = document.getElementById("smtp.authMethod");
+ gSmtpSocketType = document.getElementById("smtp.socketType");
+ gDefaultPort = document.getElementById("smtp.defaultPort");
+ gPort = document.getElementById("smtp.port");
+
+ if (server) {
+ gSmtpHostname.value = server.hostname;
+ gSmtpDescription.value = server.description;
+ gSmtpPort.value = server.port;
+ gSmtpUsername.value = server.username;
+ gSmtpAuthMethod.value = server.authMethod;
+ gSmtpSocketType.value = (server.socketType < 4) ? server.socketType : 1;
+ } else {
+ // New server, load default values.
+ gSmtpAuthMethod.value = Services.prefs.getIntPref("mail.smtpserver.default.authMethod");
+ gSmtpSocketType.value = Services.prefs.getIntPref("mail.smtpserver.default.try_ssl");
+ }
+
+ // Although sslChanged will set a label for cleartext password,
+ // we need to use the long label so that we can size the dialog.
+ setLabelFromStringBundle("authMethod-no", "authNo");
+ setLabelFromStringBundle("authMethod-password-encrypted",
+ "authPasswordEncrypted");
+ setLabelFromStringBundle("authMethod-password-cleartext",
+ "authPasswordCleartextInsecurely");
+ setLabelFromStringBundle("authMethod-kerberos", "authKerberos");
+ setLabelFromStringBundle("authMethod-ntlm", "authNTLM");
+#ifdef MOZ_MAILNEWS_OAUTH2
+ setLabelFromStringBundle("authMethod-oauth2", "authOAuth2");
+#endif
+ setLabelFromStringBundle("authMethod-anysecure", "authAnySecure");
+ setLabelFromStringBundle("authMethod-any", "authAny");
+
+ sizeToContent();
+
+ sslChanged(false);
+ authMethodChanged(false);
+
+ if (MailServices.smtp.defaultServer)
+ onLockPreference();
+
+ // Hide deprecated/hidden auth options, unless selected
+ hideUnlessSelected(document.getElementById("authMethod-anysecure"));
+ hideUnlessSelected(document.getElementById("authMethod-any"));
+
+ // "STARTTLS, if available" is vulnerable to MITM attacks so we shouldn't
+ // allow users to choose it anymore. Hide the option unless the user already
+ // has it set.
+ hideUnlessSelected(document.getElementById("connectionSecurityType-1"));
+}
+
+function hideUnlessSelected(element)
+{
+ element.hidden = !element.selected;
+}
+
+function setLabelFromStringBundle(elementID, stringName)
+{
+ document.getElementById(elementID).label =
+ document.getElementById("bundle_messenger").getString(stringName);
+}
+
+// Disables xul elements that have associated preferences locked.
+function onLockPreference()
+{
+ try {
+ let allPrefElements = {
+ hostname: gSmtpHostname,
+ description: gSmtpDescription,
+ port: gSmtpPort,
+ authMethod: gSmtpAuthMethod,
+ try_ssl: gSmtpSocketType
+ };
+ disableIfLocked(allPrefElements);
+ } catch (e) { // non-fatal
+ Components.utils.reportError("Error while getting locked prefs: " + e);
+ }
+}
+
+/**
+ * Does the work of disabling an element given the array which contains xul id/prefstring pairs.
+ *
+ * @param prefstrArray array of XUL elements to check
+ *
+ * TODO: try to merge this with disableIfLocked function in am-offline.js (bug 755885)
+ */
+function disableIfLocked(prefstrArray)
+{
+ let finalPrefString = "mail.smtpserver." +
+ MailServices.smtp.defaultServer.key + ".";
+ let smtpPrefBranch = Services.prefs.getBranch(finalPrefString);
+
+ for (let prefstring in prefstrArray)
+ if (smtpPrefBranch.prefIsLocked(prefstring))
+ prefstrArray[prefstring].disabled = true;
+}
+
+function saveSmtpSettings(server)
+{
+ //dump("Saving to " + server + "\n");
+ if (server) {
+ server.hostname = cleanUpHostName(gSmtpHostname.value);
+ server.description = gSmtpDescription.value;
+ server.port = gSmtpPort.value;
+ server.authMethod = gSmtpAuthMethod.value;
+ server.username = gSmtpUsername.value;
+ server.socketType = gSmtpSocketType.value;
+ }
+}
+
+function authMethodChanged(userAction)
+{
+ var noUsername = gSmtpAuthMethod.value == Ci.nsMsgAuthMethod.none;
+ gSmtpUsername.disabled = noUsername;
+ gSmtpUsernameLabel.disabled = noUsername;
+}
+
+/**
+ * Resets the default port to SMTP or SMTPS, dependending on
+ * the |gSmtpSocketType| value, and sets the port to use to this default,
+ * if that's appropriate.
+ *
+ * @param userAction false for dialog initialization,
+ * true for user action.
+ */
+function sslChanged(userAction)
+{
+ const DEFAULT_SMTP_PORT = "587";
+ const DEFAULT_SMTPS_PORT = "465";
+ var socketType = gSmtpSocketType.value;
+ var otherDefaultPort;
+ var prevDefaultPort = gDefaultPort.value;
+
+ if (socketType == Ci.nsMsgSocketType.SSL) {
+ gDefaultPort.value = DEFAULT_SMTPS_PORT;
+ otherDefaultPort = DEFAULT_SMTP_PORT;
+ } else {
+ gDefaultPort.value = DEFAULT_SMTP_PORT;
+ otherDefaultPort = DEFAULT_SMTPS_PORT;
+ }
+
+ // If the port is not set,
+ // or the user is causing the default port to change,
+ // and the port is set to the default for the other protocol,
+ // then set the port to the default for the new protocol.
+ if ((gPort.value == 0) ||
+ (userAction && (gDefaultPort.value != prevDefaultPort) &&
+ (gPort.value == otherDefaultPort)))
+ gPort.value = gDefaultPort.value;
+
+ // switch "insecure password" label
+ setLabelFromStringBundle("authMethod-password-cleartext",
+ socketType == Ci.nsMsgSocketType.SSL ||
+ socketType == Ci.nsMsgSocketType.alwaysSTARTTLS ?
+ "authPasswordCleartextViaSSL" : "authPasswordCleartextInsecurely");
+}
diff --git a/mailnews/base/prefs/content/smtpEditOverlay.xul b/mailnews/base/prefs/content/smtpEditOverlay.xul
new file mode 100644
index 000000000..0bc900a3d
--- /dev/null
+++ b/mailnews/base/prefs/content/smtpEditOverlay.xul
@@ -0,0 +1,126 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/smtpEditOverlay.dtd">
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/smtpEditOverlay.js"/>
+
+ <vbox id="smtpServerEditor">
+ <groupbox>
+ <caption label="&settings.caption;"/>
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&serverDescription.label;"
+ accesskey="&serverDescription.accesskey;"
+ control="smtp.description"/>
+ <textbox id="smtp.description"
+ flex="1"
+ preftype="string"
+ prefstring="mail.smtpserver.%serverkey%.description"/>
+ </row>
+ <row align="center">
+ <label value="&serverName.label;"
+ accesskey="&serverName.accesskey;"
+ control="smtp.hostname"/>
+ <textbox id="smtp.hostname"
+ flex="1"
+ preftype="string"
+ class="uri-element"
+ prefstring="mail.smtpserver.%serverkey%.hostname"/>
+ </row>
+ <row align="center">
+ <label value="&serverPort.label;"
+ accesskey="&serverPort.accesskey;"
+ control="smtp.port"/>
+ <hbox align="center">
+ <textbox id="smtp.port"
+ type="number"
+ min="0"
+ max="65535"
+ size="5"
+ preftype="int"
+ prefstring="mail.smtpserver.%serverkey%.port"/>
+ <label value="&serverPortDefault.label;"/>
+ <label id="smtp.defaultPort"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <caption label="&security.caption;"/>
+
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&connectionSecurity.label;"
+ accesskey="&connectionSecurity.accesskey;"
+ control="smtp.socketType"/>
+ <menulist id="smtp.socketType" oncommand="sslChanged(true);"
+ prefstring="mail.smtpserver.%serverkey%.try_ssl">
+ <menupopup id="smtp.socketTypePopup">
+ <menuitem value="0" label="&connectionSecurityType-0.label;"/>
+ <menuitem id="connectionSecurityType-1"
+ value="1" label="&connectionSecurityType-1.label;"
+ disabled="true" hidden="true"/>
+ <menuitem value="2" label="&connectionSecurityType-2.label;"/>
+ <menuitem value="3" label="&connectionSecurityType-3.label;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label value="&authMethod.label;"
+ accesskey="&authMethod.accesskey;"
+ control="server.authMethod"/>
+ <!-- same in am-server.xul/js -->
+ <menulist id="smtp.authMethod"
+ oncommand="authMethodChanged(true);"
+ wsm_persist="true"
+ preftype="int" type="number"
+ prefstring="mail.smtpserver.%serverkey%.authMethod">
+ <menupopup id="smtp.authMethodPopup">
+ <menuitem id="authMethod-no" value="1"/>
+ <menuitem id="authMethod-password-cleartext" value="3"/>
+ <menuitem id="authMethod-password-encrypted" value="4"/>
+ <menuitem id="authMethod-kerberos" value="5"/>
+ <menuitem id="authMethod-ntlm" value="6"/>
+#ifdef MOZ_MAILNEWS_OAUTH2
+ <menuitem id="authMethod-oauth2" value="10"/>
+#endif
+ <menuitem id="authMethod-anysecure" value="8"/>
+ <menuitem id="authMethod-any" value="9"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="smtp.username.box" align="center">
+ <label id="smtp.username.label" value="&userName.label;"
+ accesskey="&userName.accesskey;"
+ control="smtp.username"/>
+ <textbox id="smtp.username" flex="1"
+ preftype="string"
+ prefstring="mail.smtpserver.%serverkey%.username"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </vbox>
+</overlay>
diff --git a/mailnews/base/public/MailNewsTypes.h b/mailnews/base/public/MailNewsTypes.h
new file mode 100644
index 000000000..d16e3f3af
--- /dev/null
+++ b/mailnews/base/public/MailNewsTypes.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MailNewsTypes_h__
+#define MailNewsTypes_h__
+
+#include "msgCore.h"
+#include "MailNewsTypes2.h"
+
+/* nsMsgKey is a unique ID for a particular message in a folder. If you want
+ a handle to a message that will remain valid even after resorting the folder
+ or otherwise changing their indices, you want one of these rather than a
+ nsMsgViewIndex. nsMsgKeys don't survive local mail folder compression, however.
+ */
+const nsMsgKey nsMsgKey_None = 0xffffffff;
+
+/* nsMsgViewIndex
+ *
+ * A generic index type from which other index types are derived. All nsMsgViewIndex
+ * derived types are zero based.
+ *
+ * The following index types are currently supported:
+ * - nsMsgViewIndex - an index into the list of messages or folders or groups,
+ * where zero is the first one to show, one is the second, etc...
+ * - AB_SelectionIndex
+ * - AB_NameCompletionIndex
+ */
+
+const nsMsgViewIndex nsMsgViewIndex_None = 0xFFFFFFFF;
+
+/* kSizeUnknown is a special value of folder size that indicates the size
+ * is unknown yet. Usually this causes the folder to determine the real size
+ * immediately as it is queried by a consumer.
+ */
+const int64_t kSizeUnknown = -1;
+
+#endif
diff --git a/mailnews/base/public/MailNewsTypes2.idl b/mailnews/base/public/MailNewsTypes2.idl
new file mode 100644
index 000000000..5c6d922cc
--- /dev/null
+++ b/mailnews/base/public/MailNewsTypes2.idl
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+typedef unsigned long nsMsgKey;
+typedef unsigned long nsMsgViewIndex;
+
+typedef long nsMsgSearchScopeValue;
+
+typedef long nsMsgPriorityValue;
+typedef long nsMsgSocketTypeValue;
+typedef long nsMsgAuthMethodValue;
+
+typedef unsigned long nsMsgJunkStatus;
+
+typedef unsigned long nsMsgJunkScore;
+
+[scriptable, uuid(94C0D8D8-2045-11d3-8A8F-0060B0FC04D2)]
+interface nsMsgPriority {
+ const nsMsgPriorityValue notSet = 0;
+ const nsMsgPriorityValue none = 1;
+ const nsMsgPriorityValue lowest = 2;
+ const nsMsgPriorityValue low = 3;
+ const nsMsgPriorityValue normal = 4;
+ const nsMsgPriorityValue high = 5;
+ const nsMsgPriorityValue highest = 6;
+ //the default priority (if none) is set in the message
+ const nsMsgPriorityValue Default = 4;
+};
+
+/**
+ * Defines whether to use SSL or STARTTLS or not.
+ * Used by @see nsIMsgIncomingServer.socketType
+ * and @see nsISmtpServer.socketType
+ */
+[scriptable, uuid(bc78bc74-1b34-48e8-ac2b-968e8dff1aeb)]
+interface nsMsgSocketType {
+ /// No SSL or STARTTLS
+ const nsMsgSocketTypeValue plain = 0;
+ /// Use TLS via STARTTLS, but only if server offers it.
+ /// @deprecated This is vulnerable to MITM attacks
+ const nsMsgSocketTypeValue trySTARTTLS = 1;
+ /// Insist on TLS via STARTTLS.
+ /// Uses normal port.
+ const nsMsgSocketTypeValue alwaysSTARTTLS = 2;
+ /// Connect via SSL.
+ /// Needs special SSL port.
+ const nsMsgSocketTypeValue SSL = 3;
+};
+
+/**
+ * Defines which authentication schemes we should try.
+ * Used by @see nsIMsgIncomingServer.authMethod
+ * and @see nsISmtpServer.authMethod
+ */
+[scriptable, uuid(4a10e647-d179-4a53-b7ef-df575ff5f405)]
+interface nsMsgAuthMethod {
+ // 0 is intentionally undefined and invalid
+ /// No login needed. E.g. IP-address-based.
+ const nsMsgAuthMethodValue none = 1;
+ /// Do not use AUTH commands (e.g. AUTH=PLAIN),
+ /// but the original login commands that the protocol specified
+ /// (POP: "USER"/"PASS", IMAP: "login", not valid for SMTP)
+ const nsMsgAuthMethodValue old = 2;
+ /// password in the clear. AUTH=PLAIN/LOGIN or old-style login.
+ const nsMsgAuthMethodValue passwordCleartext = 3;
+ /// hashed password. CRAM-MD5, DIGEST-MD5
+ const nsMsgAuthMethodValue passwordEncrypted = 4;
+ /// Kerberos / GSSAPI (Unix single-signon)
+ const nsMsgAuthMethodValue GSSAPI = 5;
+ /// NTLM is a Windows single-singon scheme.
+ /// Includes MSN / Passport.net, which is the same with a different name.
+ const nsMsgAuthMethodValue NTLM = 6;
+ /// Auth External is cert-based authentication
+ const nsMsgAuthMethodValue External = 7;
+ /// Encrypted password or Kerberos / GSSAPI or NTLM.
+ /// @deprecated - for migration only.
+ const nsMsgAuthMethodValue secure = 8;
+ /// Let us pick any of the auth types supported by the server.
+ /// Discouraged, because vulnerable to MITM attacks, even if server offers secure auth.
+ const nsMsgAuthMethodValue anything = 9;
+
+ /// Use OAuth2 to authenticate.
+ const nsMsgAuthMethodValue OAuth2 = 10;
+};
+
+typedef unsigned long nsMsgLabelValue;
+
+typedef long nsMsgViewSortOrderValue;
+typedef long nsMsgViewSortTypeValue;
+typedef long nsMsgViewTypeValue;
+typedef long nsMsgViewFlagsTypeValue;
diff --git a/mailnews/base/public/moz.build b/mailnews/base/public/moz.build
new file mode 100644
index 000000000..e38c223e4
--- /dev/null
+++ b/mailnews/base/public/moz.build
@@ -0,0 +1,77 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['MOZ_MAILNEWS_OAUTH2']:
+ XPIDL_SOURCES += ['msgIOAuth2Module.idl']
+
+XPIDL_SOURCES += [
+ 'MailNewsTypes2.idl',
+ 'mozINewMailListener.idl',
+ 'mozINewMailNotificationService.idl',
+ 'nsICopyMessageListener.idl',
+ 'nsICopyMsgStreamListener.idl',
+ 'nsIFolderListener.idl',
+ 'nsIFolderLookupService.idl',
+ 'nsIIncomingServerListener.idl',
+ 'nsIMapiRegistry.idl',
+ 'nsIMessenger.idl',
+ 'nsIMessengerMigrator.idl',
+ 'nsIMessengerOSIntegration.idl',
+ 'nsIMessengerWindowService.idl',
+ 'nsIMsgAccount.idl',
+ 'nsIMsgAccountManager.idl',
+ 'nsIMsgAsyncPrompter.idl',
+ 'nsIMsgBiffManager.idl',
+ 'nsIMsgContentPolicy.idl',
+ 'nsIMsgCopyService.idl',
+ 'nsIMsgCopyServiceListener.idl',
+ 'nsIMsgCustomColumnHandler.idl',
+ 'nsIMsgDBView.idl',
+ 'nsIMsgFolder.idl',
+ 'nsIMsgFolderCache.idl',
+ 'nsIMsgFolderCacheElement.idl',
+ 'nsIMsgFolderCompactor.idl',
+ 'nsIMsgFolderListener.idl',
+ 'nsIMsgFolderNotificationService.idl',
+ 'nsIMsgHdr.idl',
+ 'nsIMsgIdentity.idl',
+ 'nsIMsgIncomingServer.idl',
+ 'nsIMsgKeyArray.idl',
+ 'nsIMsgMailNewsUrl.idl',
+ 'nsIMsgMailSession.idl',
+ 'nsIMsgMdnGenerator.idl',
+ 'nsIMsgMessageService.idl',
+ 'nsIMsgOfflineManager.idl',
+ 'nsIMsgPluggableStore.idl',
+ 'nsIMsgPrintEngine.idl',
+ 'nsIMsgProgress.idl',
+ 'nsIMsgProtocolInfo.idl',
+ 'nsIMsgPurgeService.idl',
+ 'nsIMsgRDFDataSource.idl',
+ 'nsIMsgShutdown.idl',
+ 'nsIMsgStatusFeedback.idl',
+ 'nsIMsgTagService.idl',
+ 'nsIMsgThread.idl',
+ 'nsIMsgUserFeedbackListener.idl',
+ 'nsIMsgWindow.idl',
+ 'nsISpamSettings.idl',
+ 'nsIStatusBarBiffManager.idl',
+ 'nsIStopwatch.idl',
+ 'nsISubscribableServer.idl',
+ 'nsIUrlListener.idl',
+ 'nsMsgFolderFlags.idl',
+ 'nsMsgMessageFlags.idl',
+]
+
+XPIDL_MODULE = 'msgbase'
+
+EXPORTS += [
+ 'MailNewsTypes.h',
+ 'msgCore.h',
+ 'nsMsgBaseCID.h',
+ 'nsMsgHeaderMasks.h',
+ 'nsMsgLocalFolderHdrs.h',
+]
+
diff --git a/mailnews/base/public/mozINewMailListener.idl b/mailnews/base/public/mozINewMailListener.idl
new file mode 100644
index 000000000..467acb671
--- /dev/null
+++ b/mailnews/base/public/mozINewMailListener.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(e15f104f-a16d-4e51-a362-4b4c5efe05b9)]
+/**
+ * Callback interface for objects interested in receiving new mail notifications
+ * from mozINewMailNotificationService
+ * NOTE: THIS INTERFACE IS UNDER ACTIVE DEVELOPMENT AND SUBJECT TO CHANGE,
+ * see https://bugzilla.mozilla.org/show_bug.cgi?id=715799
+ */
+interface mozINewMailListener : nsISupports {
+ /** The new mail notification service will call this when the number of interesting
+ * messages has changed
+ *
+ * @param unreadCount The number of unread messages the user cares to be notified about
+ */
+ void onCountChanged(in unsigned long count);
+};
diff --git a/mailnews/base/public/mozINewMailNotificationService.idl b/mailnews/base/public/mozINewMailNotificationService.idl
new file mode 100644
index 000000000..cdd049bcc
--- /dev/null
+++ b/mailnews/base/public/mozINewMailNotificationService.idl
@@ -0,0 +1,58 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozINewMailListener;
+
+typedef long newMailListenerFlag;
+
+[scriptable, uuid(7fef9018-c9f1-4cbd-b57c-d6555cf3a668)]
+/** New mail notification service. This service watches all the relevant
+ * folder and message change events, preferences etc. and keeps track of
+ * the specific messages the user wants notifications for.
+ * NOTE: THIS INTERFACE IS UNDER ACTIVE DEVELOPMENT AND SUBJECT TO CHANGE,
+ * see https://bugzilla.mozilla.org/show_bug.cgi?id=715799
+ * Registered mozINewMailListeners are called when the message count or
+ * specific list of notified messages changes.
+ * ** Should also document the observer service callback that allows
+ * plugins to override notifications by folder
+ */
+interface mozINewMailNotificationService : nsISupports {
+ /**
+ * @name Notification flags
+ * These flags determine which notifications will be sent.
+ * @{
+ */
+ /// mozINewMailListener::count notification
+ const newMailListenerFlag count = 0x1;
+
+ /// mozINewMailListener::messages notification
+ const newMailListenerFlag messages = 0x2;
+
+ /** @} */
+
+ /**
+ * addListener - Register a mozINewMailListener to receive callbacks
+ * when the count or list of notification-worthy messages
+ * changes.
+ * @param aListener mozINewMailListener to call back
+ * @param flags Bitmask of newMailListenerFlag values indicating
+ * the particular callbacks this listener wants.
+ * If the listener is already registered with the
+ * notification service, the existing set of flags is
+ * replaced by the values passed in this parameter.
+ */
+ void addListener(in mozINewMailListener aListener,
+ in newMailListenerFlag flags);
+ /**
+ * removeListener - remove a listener from the service
+ * @param aListener The listener to remove
+ */
+ void removeListener(in mozINewMailListener aListener);
+
+ /// The current count of notification-worth unread messages
+ readonly attribute long messageCount;
+};
diff --git a/mailnews/base/public/msgCore.h b/mailnews/base/public/msgCore.h
new file mode 100644
index 000000000..d14ff6140
--- /dev/null
+++ b/mailnews/base/public/msgCore.h
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Include files we are going to want available to all files....these files include
+ NSPR, memory, and string header files among others */
+
+#ifndef msgCore_h__
+#define msgCore_h__
+
+#include "nscore.h"
+#include "nspr.h"
+#include "plstr.h"
+#include "nsCRTGlue.h"
+
+class nsIMsgDBHdr;
+class nsIMsgFolder;
+
+// include common interfaces such as the service manager and the repository....
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+
+/*
+ * The suffix we use for the mail summary file.
+ */
+#define SUMMARY_SUFFIX ".msf"
+
+/*
+ * The suffix we use for folder subdirectories.
+ */
+#define FOLDER_SUFFIX ".sbd"
+
+/*
+ * These are folder property strings, which are used in several places.
+
+ */
+// Most recently used (opened, moved to, got new messages)
+#define MRU_TIME_PROPERTY "MRUTime"
+// Most recently moved to, for recent folders list in move menu
+#define MRM_TIME_PROPERTY "MRMTime"
+
+/* NS_ERROR_MODULE_MAILNEWS is defined in mozilla/xpcom/public/nsError.h */
+
+/*
+ * NS_ERROR macros - use these macros to generate error constants
+ * to be used by XPCOM interfaces and possibly other useful things
+ * do not use these macros in your code - declare error macros for
+ * each specific error you need.
+ *
+ * for example:
+ * #define NS_MSG_ERROR_NO_SUCH_FOLDER NS_MSG_GENERATE_FAILURE(4)
+ *
+ */
+
+/* use these routines to generate error values */
+#define NS_MSG_GENERATE_RESULT(severity, value) \
+NS_ERROR_GENERATE(severity, NS_ERROR_MODULE_MAILNEWS, value)
+
+#define NS_MSG_GENERATE_SUCCESS(value) \
+NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_MAILNEWS, value)
+
+#define NS_MSG_GENERATE_FAILURE(value) \
+NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_MAILNEWS, value)
+
+/* these are shortcuts to generate simple errors with a zero value */
+#define NS_MSG_SUCCESS NS_MSG_GENERATE_SUCCESS(0)
+#define NS_MSG_FAILURE NS_MSG_GENERATE_FAILURE(0)
+
+#define IS_SPACE(VAL) \
+ (((((PRIntn)(VAL)) & 0x7f) == ((PRIntn)(VAL))) && isspace((PRIntn)(VAL)))
+
+#define IS_DIGIT(i) ((((unsigned int) (i)) > 0x7f) ? (int) 0 : isdigit(i))
+#if defined(XP_WIN)
+#define IS_ALPHA(VAL) (isascii((int)(VAL)) && isalpha((int)(VAL)))
+#else
+#define IS_ALPHA(VAL) ((((unsigned int) (VAL)) > 0x7f) ? (int) 0 : isalpha((int)(VAL)))
+#endif
+
+/* for retrieving information out of messenger nsresults */
+
+#define NS_IS_MSG_ERROR(err) \
+ (NS_ERROR_GET_MODULE(err) == NS_ERROR_MODULE_MAILNEWS)
+
+#define NS_MSG_SUCCEEDED(err) \
+ (NS_IS_MSG_ERROR(err) && NS_SUCCEEDED(err))
+
+#define NS_MSG_FAILED(err) \
+ (NS_IS_MSG_ERROR(err) && NS_FAILED(err))
+
+#define NS_MSG_PASSWORD_PROMPT_CANCELLED NS_MSG_GENERATE_SUCCESS(1)
+
+/**
+ * Indicates that a search is done/terminated because it was interrupted.
+ * Interrupting a search originally notified listeners with
+ * OnSearchDone(NS_OK), so we define a success value to continue doing this,
+ * and because the search was fine except for an explicit call to interrupt it.
+ */
+#define NS_MSG_SEARCH_INTERRUPTED NS_MSG_GENERATE_SUCCESS(2)
+
+/* This is where we define our errors. There has to be a central
+ place so we don't use the same error codes for different errors.
+*/
+#define NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE NS_MSG_GENERATE_FAILURE(5)
+#define NS_MSG_ERROR_FOLDER_SUMMARY_MISSING NS_MSG_GENERATE_FAILURE(6)
+#define NS_MSG_ERROR_FOLDER_MISSING NS_MSG_GENERATE_FAILURE(7)
+
+#define NS_MSG_MESSAGE_NOT_FOUND NS_MSG_GENERATE_FAILURE(8)
+#define NS_MSG_NOT_A_MAIL_FOLDER NS_MSG_GENERATE_FAILURE(9)
+
+#define NS_MSG_FOLDER_BUSY NS_MSG_GENERATE_FAILURE(10)
+
+#define NS_MSG_COULD_NOT_CREATE_DIRECTORY NS_MSG_GENERATE_FAILURE(11)
+#define NS_MSG_CANT_CREATE_FOLDER NS_MSG_GENERATE_FAILURE(12)
+
+#define NS_MSG_FILTER_PARSE_ERROR NS_MSG_GENERATE_FAILURE(13)
+
+#define NS_MSG_FOLDER_UNREADABLE NS_MSG_GENERATE_FAILURE(14)
+
+#define NS_MSG_ERROR_WRITING_MAIL_FOLDER NS_MSG_GENERATE_FAILURE(15)
+
+#define NS_MSG_ERROR_NO_SEARCH_VALUES NS_MSG_GENERATE_FAILURE(16)
+
+#define NS_MSG_ERROR_INVALID_SEARCH_SCOPE NS_MSG_GENERATE_FAILURE(17)
+
+#define NS_MSG_ERROR_INVALID_SEARCH_TERM NS_MSG_GENERATE_FAILURE(18)
+
+#define NS_MSG_FOLDER_EXISTS NS_MSG_GENERATE_FAILURE(19)
+
+#define NS_MSG_ERROR_OFFLINE NS_MSG_GENERATE_FAILURE(20)
+
+#define NS_MSG_POP_FILTER_TARGET_ERROR NS_MSG_GENERATE_FAILURE(21)
+
+#define NS_MSG_INVALID_OR_MISSING_SERVER NS_MSG_GENERATE_FAILURE(22)
+
+#define NS_MSG_SERVER_USERNAME_MISSING NS_MSG_GENERATE_FAILURE(23)
+
+#define NS_MSG_INVALID_DBVIEW_INDEX NS_MSG_GENERATE_FAILURE(24)
+
+#define NS_MSG_NEWS_ARTICLE_NOT_FOUND NS_MSG_GENERATE_FAILURE(25)
+
+#define NS_MSG_ERROR_COPY_FOLDER_ABORTED NS_MSG_GENERATE_FAILURE(26)
+// this error means a url was queued but never run because one of the urls
+// it was queued after failed. We send an OnStopRunningUrl with this error code
+// so the listeners can know that we didn't run the url.
+#define NS_MSG_ERROR_URL_ABORTED NS_MSG_GENERATE_FAILURE(27)
+#define NS_MSG_CUSTOM_HEADERS_OVERFLOW NS_MSG_GENERATE_FAILURE(28) //when num of custom headers exceeds 50
+#define NS_MSG_INVALID_CUSTOM_HEADER NS_MSG_GENERATE_FAILURE(29) //when custom header has invalid characters (as per rfc 2822)
+
+#define NS_MSG_USER_NOT_AUTHENTICATED NS_MSG_GENERATE_FAILURE(30) // when local caches are password protect and user isn't auth
+
+#define NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD NS_MSG_GENERATE_FAILURE(31) // pop3 downloaded to tmp file, and failed.
+
+// The code tried to stream a message using the aLocalOnly argument, but
+// the message was not cached locally.
+#define NS_MSG_ERROR_MSG_NOT_OFFLINE NS_MSG_GENERATE_FAILURE(32)
+
+// The imap server returned NO or BAD for an IMAP command
+#define NS_MSG_ERROR_IMAP_COMMAND_FAILED NS_MSG_GENERATE_FAILURE(33)
+
+#define NS_MSG_ERROR_INVALID_FOLDER_NAME NS_MSG_GENERATE_FAILURE(34)
+
+/* Error codes for message compose are defined in
+ compose\src\nsMsgComposeStringBundle.h. Message compose use the same error
+ code space as other mailnews modules. To avoid any conflict, values between
+ 12500 and 12999 are reserved.
+*/
+#define NS_MSGCOMP_ERROR_BEGIN 12500
+/* NS_ERROR_NNTP_NO_CROSS_POSTING lives here, and not in nsMsgComposeStringBundle.h, because it is used in news and compose. */
+#define NS_ERROR_NNTP_NO_CROSS_POSTING NS_MSG_GENERATE_FAILURE(12554)
+#define NS_MSGCOMP_ERROR_END 12999
+
+#if defined(XP_WIN)
+#define MSG_LINEBREAK "\015\012"
+#define MSG_LINEBREAK_LEN 2
+#else
+#define MSG_LINEBREAK "\012"
+#define MSG_LINEBREAK_LEN 1
+#endif
+
+/*
+ * On Windows, we use \r\n as the line terminator in mbox files. On
+ * other platforms, we use \n. However, we need to be able to
+ * recognize line terminators produced on any platform, because we
+ * allow profiles (including the mbox files they contain) to be shared
+ * between platforms.
+ *
+ * Returns 0 (i.e., false) if the line is not blank, or otherwise the
+ * length of the line terminator, i.e., 1 for \n or 2 for \r\n.
+ */
+#define IS_MSG_LINEBREAK(line) \
+ (line[0] == '\012' ? 1 : ((line[0] == '\015' && line[1] == '\012') ? 2 : 0))
+
+#define NS_MSG_BASE
+#define NS_MSG_BASE_STATIC_MEMBER_(type) type
+
+/// The number of microseconds in a day. This comes up a lot.
+#define PR_USEC_PER_DAY (PRTime(PR_USEC_PER_SEC) * 60 * 60 * 24)
+
+#endif // msgCore_h__
+
diff --git a/mailnews/base/public/msgIOAuth2Module.idl b/mailnews/base/public/msgIOAuth2Module.idl
new file mode 100644
index 000000000..a24630e35
--- /dev/null
+++ b/mailnews/base/public/msgIOAuth2Module.idl
@@ -0,0 +1,59 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgIncomingServer;
+interface nsISmtpServer;
+
+/**
+ * A listener callback for OAuth2 SASL authentication. This would be represented
+ * as a promise, but this needs to be consumed by C++ code.
+ */
+[scriptable, uuid(9a088b49-bc13-4f99-9478-053a6a43e370)]
+interface msgIOAuth2ModuleListener : nsISupports {
+ /// Called on successful OAuth2 authentication with the bearer token to use.
+ void onSuccess(in ACString aBearerToken);
+
+ /// Called on failed OAuth2 authentication.
+ void onFailure(in nsresult aError);
+};
+
+/**
+ * An interface for managing the responsibilities of using OAuth2 to produce a
+ * bearer token, for use in SASL steps.
+ */
+[scriptable, uuid(68c275f8-cfa7-4622-b279-af290616cae6)]
+interface msgIOAuth2Module : nsISupports {
+ /**
+ * Initialize the OAuth2 parameters from an SMTP server, and return whether or
+ * not we can authenticate with OAuth2.
+ */
+ bool initFromSmtp(in nsISmtpServer aSmtpServer);
+
+ /**
+ * Initialize the OAuth2 parameters from an incoming server, and return
+ * whether or not we can authenticate with OAuth2.
+ */
+ bool initFromMail(in nsIMsgIncomingServer aServer);
+
+ /**
+ * Connect to the OAuth2 server to get an access token.
+ * @param aWithUI If false, do not allow a dialog to be popped up to query
+ * for a password.
+ * @param aCallback Listener that handles the async response.
+ */
+ void connect(in boolean aWithUI, in msgIOAuth2ModuleListener aCallback);
+
+ /**
+ * Return the base64-encoded string to send as the client initial response for
+ * SASL XOAUTH2.
+ */
+ ACString buildXOAuth2String();
+};
+
+%{C++
+#define MSGIOAUTH2MODULE_CONTRACTID "@mozilla.org/mail/oauth2-module;1"
+%}
diff --git a/mailnews/base/public/nsICopyMessageListener.idl b/mailnews/base/public/nsICopyMessageListener.idl
new file mode 100644
index 000000000..f55c135bc
--- /dev/null
+++ b/mailnews/base/public/nsICopyMessageListener.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgDBHdr;
+interface nsIInputStream;
+
+[scriptable, uuid(53CA78FE-E231-11d2-8A4D-0060B0FC04D2)]
+
+/* Use this for any object that wants to handle copying/moving messages to it */
+
+interface nsICopyMessageListener : nsISupports
+{
+ void beginCopy(in nsIMsgDBHdr message);
+ void startMessage();
+ void copyData(in nsIInputStream aIStream, in long aLength);
+ void endMessage(in nsMsgKey key);
+ void endCopy(in boolean copySucceeded);
+ void endMove(in boolean moveSucceeded);
+};
diff --git a/mailnews/base/public/nsICopyMsgStreamListener.idl b/mailnews/base/public/nsICopyMsgStreamListener.idl
new file mode 100644
index 000000000..8c3c4e080
--- /dev/null
+++ b/mailnews/base/public/nsICopyMsgStreamListener.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgFolder.idl"
+#include "nsICopyMessageListener.idl"
+
+[scriptable, uuid(7741DAEC-2125-11d3-8A90-0060B0FC04D2)]
+
+interface nsICopyMessageStreamListener: nsISupports
+{
+ void Init(in nsIMsgFolder srcFolder, in nsICopyMessageListener destination, in nsISupports listenerData);
+ void StartMessage();
+ void EndMessage(in nsMsgKey key);
+ void EndCopy(in nsISupports url, in nsresult aStatus);
+};
diff --git a/mailnews/base/public/nsIFolderListener.idl b/mailnews/base/public/nsIFolderListener.idl
new file mode 100644
index 000000000..7448099eb
--- /dev/null
+++ b/mailnews/base/public/nsIFolderListener.idl
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIAtom.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgDBHdr;
+
+typedef unsigned long folderListenerNotifyFlagValue;
+
+[scriptable, uuid(f60ee1a2-6d81-422c-958f-d408b1b2daa7)]
+interface nsIFolderListener : nsISupports {
+ const folderListenerNotifyFlagValue added = 0x1;
+ void OnItemAdded(in nsIMsgFolder aParentItem,
+ in nsISupports aItem);
+
+ const folderListenerNotifyFlagValue removed = 0x2;
+ void OnItemRemoved(in nsIMsgFolder aParentItem,
+ in nsISupports aItem);
+
+ const folderListenerNotifyFlagValue propertyChanged = 0x4;
+ void OnItemPropertyChanged(in nsIMsgFolder aItem,
+ in nsIAtom aProperty,
+ in string aOldValue,
+ in string aNewValue);
+
+ const folderListenerNotifyFlagValue intPropertyChanged = 0x8;
+ // While this property handles long long (64bit wide) values,
+ // the Javascript engine will only pass values up to 2^53 to the consumers.
+ void OnItemIntPropertyChanged(in nsIMsgFolder aItem,
+ in nsIAtom aProperty,
+ in long long aOldValue,
+ in long long aNewValue);
+
+ const folderListenerNotifyFlagValue boolPropertyChanged = 0x10;
+ void OnItemBoolPropertyChanged(in nsIMsgFolder aItem,
+ in nsIAtom aProperty,
+ in boolean aOldValue,
+ in boolean aNewValue);
+
+ const folderListenerNotifyFlagValue unicharPropertyChanged = 0x20;
+ void OnItemUnicharPropertyChanged(in nsIMsgFolder aItem,
+ in nsIAtom aProperty,
+ in wstring aOldValue,
+ in wstring aNewValue);
+
+ const folderListenerNotifyFlagValue propertyFlagChanged = 0x40;
+ void OnItemPropertyFlagChanged(in nsIMsgDBHdr aItem,
+ in nsIAtom aProperty,
+ in unsigned long aOldFlag,
+ in unsigned long aNewFlag);
+
+ const folderListenerNotifyFlagValue event = 0x80;
+ void OnItemEvent(in nsIMsgFolder aItem, in nsIAtom aEvent);
+
+ const folderListenerNotifyFlagValue all = 0xFFFFFFFF;
+
+ // void OnFolderLoaded(in nsIMsgFolder aFolder);
+ // void OnDeleteOrMoveMessagesCompleted(in nsIMsgFolder aFolder);
+};
diff --git a/mailnews/base/public/nsIFolderLookupService.idl b/mailnews/base/public/nsIFolderLookupService.idl
new file mode 100644
index 000000000..50894da91
--- /dev/null
+++ b/mailnews/base/public/nsIFolderLookupService.idl
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgFolder;
+
+/**
+ * This service provides a way to lookup any nsIMsgFolder.
+ *
+ * When looking up folders by URL, note that the URL must be encoded to be a
+ * valid folder URL. Of particular note are the following requirements:
+ * - invalid characters in paths must be percent-encoded
+ * - the URL MUST NOT have a trailing slash (excepting root folders)
+ * - the case must match the expected value exactly
+ * An example of a valid URL is thus:
+ * imap://someuser%40google.com@imap.google.com/INBOX
+ *
+ * The contractid for this service is "@mozilla.org/mail/folder-lookup;1".
+ */
+[scriptable,uuid(f5ed5997-3945-48fc-a59d-d2191a94bb60)]
+interface nsIFolderLookupService : nsISupports
+{
+ /**
+ * Returns a folder with the given URL or null if no such folder exists.
+ *
+ * @param aUrl The folder URL
+ */
+ nsIMsgFolder getFolderForURL(in ACString aUrl);
+};
+
+%{C++
+#define NSIFLS_CONTRACTID "@mozilla.org/mail/folder-lookup;1"
+%}
diff --git a/mailnews/base/public/nsIIncomingServerListener.idl b/mailnews/base/public/nsIIncomingServerListener.idl
new file mode 100644
index 000000000..807c903bd
--- /dev/null
+++ b/mailnews/base/public/nsIIncomingServerListener.idl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgIncomingServer.idl"
+
+[scriptable, uuid(E6B64B86-90CB-11d3-8B02-0060B0FC04D2)]
+interface nsIIncomingServerListener : nsISupports {
+ /**
+ * Notification sent when a server is first loaded into the account manager.
+ *
+ * @param server Loaded server.
+ */
+ void onServerLoaded(in nsIMsgIncomingServer server);
+
+ /**
+ * Notification sent when a server is unloaded from the account manager.
+ *
+ * @param server Unloaded server.
+ */
+ void onServerUnloaded(in nsIMsgIncomingServer server);
+
+ /**
+ * Notification sent when a server hostname or username changes.
+ *
+ * @param server Server that was changed.
+ */
+ void onServerChanged(in nsIMsgIncomingServer server);
+};
+
diff --git a/mailnews/base/public/nsIMapiRegistry.idl b/mailnews/base/public/nsIMapiRegistry.idl
new file mode 100644
index 000000000..a14f62422
--- /dev/null
+++ b/mailnews/base/public/nsIMapiRegistry.idl
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+/**
+ * This interface provides support for registering Mozilla as the default
+ * Mail Client. This interface can also be used to get/set the user preference
+ * for the default Mail Client.
+ *
+ */
+
+[scriptable, uuid(47D707C3-4369-46A6-A053-5118E12579D6)]
+interface nsIMapiRegistry: nsISupports {
+
+ /** This is set to TRUE if Mozilla is the default mail application
+ */
+ attribute boolean isDefaultMailClient;
+
+ /* Set to TRUE if Mozilla is the default news application */
+ attribute boolean isDefaultNewsClient;
+
+ /* Set to TRUE if we are the default feed/rss application */
+ attribute boolean isDefaultFeedClient;
+
+ /** This is set TRUE only once per session.
+ */
+ readonly attribute boolean showDialog;
+
+ /** This will bring the dialog asking the user if he/she wants to set
+ * Mozilla as default Mail Client.
+ * Call this only if Mozilla is not the default Mail client
+ */
+ void showMailIntegrationDialog(in mozIDOMWindowProxy parentWindow);
+
+ /* After being installed, when we first launch, make sure we add the correct
+ OS registry entries to make us show up as regsitered mail and news client
+ in the OS
+ */
+
+ void registerMailAndNewsClient();
+};
+
+%{C++
+#define NS_IMAPIREGISTRY_CONTRACTID "@mozilla.org/mapiregistry;1"
+#define NS_IMAPIREGISTRY_CLASSNAME "Mozilla MAPI Registry"
+%}
diff --git a/mailnews/base/public/nsIMessenger.idl b/mailnews/base/public/nsIMessenger.idl
new file mode 100644
index 000000000..fd6ecfc22
--- /dev/null
+++ b/mailnews/base/public/nsIMessenger.idl
@@ -0,0 +1,141 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsrootidl.idl"
+#include "nsIMsgWindow.idl"
+#include "nsIMsgIdentity.idl"
+
+interface nsIMsgDBHdr;
+interface nsIDOMWindow;
+interface mozIDOMWindowProxy;
+interface nsITransactionManager;
+interface nsIMsgMessageService;
+interface nsIFile;
+interface nsIUrlListener;
+
+[scriptable, uuid(01b967c8-b289-4e32-ad46-6eb7c89d4106)]
+interface nsIMessenger : nsISupports {
+
+ const long eUnknown = 0;
+ const long eDeleteMsg = 1;
+ const long eMoveMsg = 2;
+ const long eCopyMsg = 3;
+ const long eMarkAllMsg = 4;
+
+ void setDisplayCharset(in ACString aCharset);
+
+ readonly attribute nsITransactionManager transactionManager;
+
+ void setWindow(in mozIDOMWindowProxy ptr, in nsIMsgWindow msgWindow);
+
+ void addMsgUrlToNavigateHistory(in ACString aURL);
+ void openURL(in ACString aURL);
+
+ /** load a custom message by url, e.g load a attachment as a email
+ */
+ void loadURL(in mozIDOMWindowProxy ptr, in ACString aURL);
+
+ void launchExternalURL(in ACString aURL);
+
+ boolean canUndo();
+ boolean canRedo();
+ unsigned long getUndoTransactionType();
+ unsigned long getRedoTransactionType();
+ void undo(in nsIMsgWindow msgWindow);
+ void redo(in nsIMsgWindow msgWindow);
+ void setDocumentCharset(in ACString characterSet);
+ /**
+ * Saves a given message to a file or template.
+ *
+ * @param aURI The URI of the message to save
+ * @param aAsFile If true, save as file, otherwise save as a template
+ * @param aIdentity When saving as a template, this is used to determine
+ * the location to save the template to.
+ * @param aMsgFilename When saving as a file, the filename to save the
+ * message as, or the default filename for the file
+ * picker.
+ * @param aBypassFilePicker
+ * If not specified or false, this function will show
+ * a file picker when saving as a file. If true, no
+ * file picker will be shown.
+ */
+ void saveAs(in ACString aURI, in boolean aAsFile,
+ in nsIMsgIdentity aIdentity, in AString aMsgFilename,
+ [optional] in boolean aBypassFilePicker);
+
+ /**
+ * Save the given messages as files in a folder - the user will be prompted
+ * for which folder to use.
+ * @param count message count
+ * @param filenameArray the filenames to use
+ * @param messageUriArray uris of the messages to save
+ */
+ void saveMessages(in unsigned long count,
+ [array, size_is(count)] in wstring filenameArray,
+ [array, size_is(count)] in string messageUriArray);
+
+ void openAttachment(in ACString contentTpe, in ACString url, in ACString displayName, in ACString messageUri, in boolean isExternalAttachment);
+ void saveAttachment(in ACString contentTpe, in ACString url, in ACString displayName, in ACString messageUri, in boolean isExternalAttachment);
+ void saveAllAttachments(in unsigned long count, [array, size_is(count)] in string contentTypeArray,
+ [array, size_is(count)] in string urlArray, [array, size_is(count)] in string displayNameArray,
+ [array, size_is(count)] in string messageUriArray);
+
+ void saveAttachmentToFile(in nsIFile aFile, in ACString aUrl, in ACString aMessageUri,
+ in ACString aContentType, in nsIUrlListener aListener);
+
+ /**
+ * For a single message and attachments, save these attachments to a file, and
+ * remove from the message. No warning windows will appear, so this is
+ * suitable for use in test and filtering.
+ *
+ * @param aDestFolder Folder to save files in
+ * @param aCount Number of attachments to save
+ * @param aContentTypeArray Content types of the attachments
+ * @param aUrlArray Urls for the attachments
+ * @param aDisplayNameArray Files names to save attachments to. Unique
+ * names will be created if needed.
+ * @param aMessageUriArray Uri for the source message
+ * @param aListener Listener to inform of start and stop of detach
+ */
+ void detachAttachmentsWOPrompts(in nsIFile aDestFolder,
+ in unsigned long aCount,
+ [array, size_is(aCount)] in string aContentTypeArray,
+ [array, size_is(aCount)] in string aUrlArray,
+ [array, size_is(aCount)] in string aDisplayNameArray,
+ [array, size_is(aCount)] in string aMessageUriArray,
+ in nsIUrlListener aListener);
+
+ void detachAttachment(in string contentTpe, in string url, in string displayName, in string messageUri, in boolean saveFirst, [optional] in boolean withoutWarning);
+ void detachAllAttachments(in unsigned long count, [array, size_is(count)] in string contentTypeArray,
+ [array, size_is(count)] in string urlArray, [array, size_is(count)] in string displayNameArray,
+ [array, size_is(count)] in string messageUriArray, in boolean saveFirst, [optional] in boolean withoutWarning);
+ // saveAttachmentToFolder is used by the drag and drop code to drop an attachment to a destination folder
+ // We need to return the actual file path (including the filename).
+ nsIFile saveAttachmentToFolder(in ACString contentType, in ACString url, in ACString displayName, in ACString messageUri, in nsIFile aDestFolder);
+
+ readonly attribute ACString lastDisplayedMessageUri;
+
+ nsIMsgMessageService messageServiceFromURI(in ACString aUri);
+ nsIMsgDBHdr msgHdrFromURI(in ACString aUri);
+ // For back forward history, we need a list of visited messages,
+ // and where we are in the list.
+
+ // aPos is relative to the current history cursor - 1 is forward, -1 is back.
+ // Unfortunately, you must call this before navigating to this position,
+ // because calling this has the side effect of making us adjust our current
+ // history pos, and *not* adding the loaded message to the history queue.
+ ACString getMsgUriAtNavigatePos(in long aPos);
+ ACString getFolderUriAtNavigatePos(in long aPos);
+ attribute long navigatePos;
+ // If caller just wants the count and cur pos, they can pass in a null history pointer, which will be more efficient
+ // if they want a list suitable for display in a back/forward menu drop down, they should pass in a aHistory pointer,
+ // and they'll get returned an array with strings containing something like subject and sender of the message -
+ // other possible info is the folder containing the message, and the preview text, if available.
+ void getNavigateHistory(out unsigned long aCurPos, out unsigned long aCount, [array, size_is(aCount)] out string aHistory);
+
+ AString formatFileSize(in unsigned long long aPos, [optional] in boolean aUseKB);
+};
+
diff --git a/mailnews/base/public/nsIMessengerMigrator.idl b/mailnews/base/public/nsIMessengerMigrator.idl
new file mode 100644
index 000000000..2ae6b5372
--- /dev/null
+++ b/mailnews/base/public/nsIMessengerMigrator.idl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(54818d98-1dd2-11b2-82aa-a9197f997503)]
+interface nsIMessengerMigrator: nsISupports {
+ /* migrate old mailnews prefs to the 5.x world */
+ void UpgradePrefs();
+
+ void createLocalMailAccount(in boolean migrating);
+};
+
diff --git a/mailnews/base/public/nsIMessengerOSIntegration.idl b/mailnews/base/public/nsIMessengerOSIntegration.idl
new file mode 100644
index 000000000..06e694b98
--- /dev/null
+++ b/mailnews/base/public/nsIMessengerOSIntegration.idl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(d9e45fee-1dd1-11b2-938c-9147855ed837)]
+interface nsIMessengerOSIntegration : nsISupports {
+ // for now, nothing. it's up to the implementation to
+ // do all the work of registering itself as listeners
+ // all we guarantee is your service will be created
+ // when accounts are loaded by the account manager
+};
diff --git a/mailnews/base/public/nsIMessengerWindowService.idl b/mailnews/base/public/nsIMessengerWindowService.idl
new file mode 100644
index 000000000..78d1e3a44
--- /dev/null
+++ b/mailnews/base/public/nsIMessengerWindowService.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+[scriptable, uuid(113a1a5a-1dd2-11b2-b1b7-a85ccc06c8ce)]
+interface nsIMessengerWindowService : nsISupports {
+ /* aWindowType --> the type of window you want to create. i.e. "mail:3pane"
+ aFolderURI --> the folder resource you want pre-selected (if any)
+ aMsgKey --> a particular message you may want selected in that folder (if any)
+ */
+ void openMessengerWindowWithUri(in string aWindowType, in string aFolderURI, in nsMsgKey aMsgKey);
+};
+
diff --git a/mailnews/base/public/nsIMsgAccount.idl b/mailnews/base/public/nsIMsgAccount.idl
new file mode 100644
index 000000000..b4965087e
--- /dev/null
+++ b/mailnews/base/public/nsIMsgAccount.idl
@@ -0,0 +1,88 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgIncomingServer.idl"
+#include "nsIMsgIdentity.idl"
+
+interface nsIArray;
+
+/**
+ * An account consists of an incoming server and one or more
+ * outgoing identities. An account is identified by a key,
+ * which is the <account> string in the account preferences,
+ * such as in mail.account.<account>.identities.
+ */
+
+[scriptable, uuid(84181351-4ec8-4ca8-8439-5c68cc591177)]
+interface nsIMsgAccount : nsISupports {
+
+ /// Internal key identifying itself
+ attribute ACString key;
+
+ /// Incoming server stuff
+ attribute nsIMsgIncomingServer incomingServer;
+
+ /// Outgoing identity list (array of nsIMsgIdentity's)
+ readonly attribute nsIArray identities;
+
+ /// The default identity for this account.
+ attribute nsIMsgIdentity defaultIdentity;
+
+ /// Add a new identity to this account
+ void addIdentity(in nsIMsgIdentity identity);
+
+ /// Remove an identity from this account
+ void removeIdentity(in nsIMsgIdentity identity);
+
+ /// Clear all user preferences associated with an account.
+ void clearAllValues();
+
+ /// Name in javascript
+ AString toString();
+
+ /**
+ * Create the server, with error returns.
+ *
+ * Normally each valid account also has a valid server.
+ *
+ * If an extension that creates a server type failed to load, then we
+ * may have an existing account without a valid server. We don't want
+ * to simply delete that account, as that would make the user re-enter
+ * all of the account information after what may be a temporary
+ * update glitch. But we also don't want to leave junk lying around
+ * forever. So what we do is note the time when we first noticed
+ * that the server was unavailable. After a period of time set
+ * in a preference, if the server is still unavailable then delete
+ * the associated account.
+ *
+ * Accounts with invalid server are not shown in any way by the account
+ * manager. But if the server becomes available (for example if an extension
+ * is loaded), then the account will reappear when accounts are loaded.
+ *
+ * Preference definitions:
+ *
+ * mail.server.serverN.secondsToLeaveUnavailable
+ * mail.server.serverN.timeFoundUnavailable
+ *
+ * secondsToLeaveUnavailable: is set by the extension to indicate the
+ * delay, in seconds, between first detection that a server is
+ * unavailable, and the time it can be deleted. This should be set
+ * by the extension when the server is created. If missing, treat as 0.
+ * A typical value would be 2592000 (30 days)(24 hr/day)(3600 seconds/hr)
+ *
+ * timeFoundUnavailable: is set by core code the first time that a
+ * server is detected as unavailable, using now() converted to seconds.
+ * If that time + secondsToLeaveUnavailable is exceeded, core code may
+ * delete the server and its associated account (though for now we default
+ * to the previous behavior, which is to just delete the account).
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the server component type could not
+ * be created
+ * NS_ERROR_ALREADY_INITIALIZED if the server is already created
+ * (Other errors may also be possible)
+ */
+ void createServer();
+};
diff --git a/mailnews/base/public/nsIMsgAccountManager.idl b/mailnews/base/public/nsIMsgAccountManager.idl
new file mode 100644
index 000000000..53ea2351e
--- /dev/null
+++ b/mailnews/base/public/nsIMsgAccountManager.idl
@@ -0,0 +1,240 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgAccount.idl"
+#include "nsIMsgIdentity.idl"
+#include "nsIMsgIncomingServer.idl"
+#include "nsIIncomingServerListener.idl"
+#include "nsIMsgFolder.idl"
+
+interface nsIMsgFolderCache;
+interface nsIFolderListener;
+
+[scriptable, uuid(d5ab0eea-49c5-42f2-b2e6-8ad306606d8b)]
+interface nsIMsgAccountManager : nsISupports {
+
+ nsIMsgAccount createAccount();
+ /*
+ * Return the account with the provided key, or null if none found.
+ */
+ nsIMsgAccount getAccount(in ACString key);
+
+ /**
+ * Removes the account from the list of accounts.
+ *
+ * @param aAccount the account to remove
+ * @param aRemoveFiles remove data directory (local directory) of this account
+ */
+ void removeAccount(in nsIMsgAccount aAccount, [optional] in boolean aRemoveFiles);
+
+ /*
+ * creates a new identity and assigns it a new, unique "key"
+ */
+ nsIMsgIdentity createIdentity();
+
+ /*
+ * creates a new server and assigns it a new, unique "key"
+ * the given type will be used to construct a ContractID
+ *
+ * @param type "imap", "pop3", "nntp", "movemail", "none", "rss", "generic"
+ * (suffix of contract ID @mozilla.org/messenger/server;1?type= )
+ */
+ nsIMsgIncomingServer createIncomingServer(in ACString username,
+ in ACString hostname,
+ in ACString type);
+
+ /**
+ * Removes the server from the list of servers
+ *
+ * @param aServer server to remove
+ * @param aRemoveFiles remove directory from profile
+ *
+ * @throws NS_ERROR_FAILURE if server not found
+ */
+ void removeIncomingServer(in nsIMsgIncomingServer aServer,
+ in boolean aRemoveFiles);
+ /*
+ * get the identity with the given key
+ * if the identity does not exist, it will be created
+ */
+ nsIMsgIdentity getIdentity(in ACString key);
+
+ /*
+ * Gets the existing incoming server with the given key
+ * if the server's type does not exist in the preference,
+ * an error is returned/thrown
+ */
+ nsIMsgIncomingServer getIncomingServer(in ACString key);
+
+ /* account list stuff */
+
+ /**
+ * Returns the account that was marked as the default one.
+ * Only some server types can serve as default account.
+ * If there is no such account, null is returned.
+ * You can only set the defaultAccount to an
+ * account already in the account manager.
+ */
+ attribute nsIMsgAccount defaultAccount;
+
+ /**
+ * Ordered list of all accounts, by the order they are in the prefs.
+ * Accounts with hidden servers are not returned.
+ * array of nsIMsgAccount
+ */
+ readonly attribute nsIArray accounts;
+
+ /* list of all identities in all accounts
+ * array of nsIMsgIdentity
+ */
+ readonly attribute nsIArray allIdentities;
+
+ /* list of all servers in all accounts, except for hidden and IM servers
+ * array of nsIMsgIncomingServer
+ */
+ readonly attribute nsIArray allServers;
+
+ /* summary of summary files folder cache */
+ readonly attribute nsIMsgFolderCache folderCache;
+
+ /* are we shutting down */
+ readonly attribute boolean shutdownInProgress;
+
+ /**
+ * for preventing unauthenticated users from seeing header information
+ */
+ attribute boolean userNeedsToAuthenticate;
+ /*
+ * search for the server with the given username, hostname, and type
+ * the type is the same as is specified in the preferences,
+ * i.e. "imap", "pop3", "none", or "nntp"
+ */
+ nsIMsgIncomingServer
+ FindServer(in ACString userName, in ACString hostname, in ACString type);
+
+ /*
+ * search for the server with the given uri
+ * an analog to FindServer()
+ * The boolean flag selects whether we compare input against the
+ * 'realhostname' and 'realuserName' pref settings.
+ */
+ nsIMsgIncomingServer
+ findServerByURI(in nsIURI aURI, in boolean aRealFlag);
+
+ /*
+ * Same as FindServer() except it compares the input values against
+ * 'realhostname' and 'realuserName' pref settings.
+ */
+ nsIMsgIncomingServer
+ findRealServer(in ACString userName, in ACString hostname, in ACString type, in long port );
+
+ /**
+ * find the index of this server in the (ordered) list of accounts
+ */
+ long FindServerIndex(in nsIMsgIncomingServer server);
+
+ /**
+ * Finds an account for the given incoming server.
+ *
+ * @param server An incoming server to find the account for.
+ * @return If found, the nsIMsgAccount representing the account found.
+ * Otherwise returns null.
+ */
+ nsIMsgAccount FindAccountForServer(in nsIMsgIncomingServer server);
+
+ /* given a server, return all identities in accounts that have this server
+ * returns an array of nsIMsgIdentity
+ */
+ nsIArray getIdentitiesForServer(in nsIMsgIncomingServer server);
+
+ /**
+ * given a server, return the first identity in accounts that have this server
+ */
+ nsIMsgIdentity getFirstIdentityForServer(in nsIMsgIncomingServer server);
+
+ /* given an identity, return all servers in accounts that have
+ * this identity
+ * returns an array of nsIMsgIncomingServer
+ */
+ nsIArray getServersForIdentity(in nsIMsgIdentity identity);
+
+ /* there is a special server "Local Folders" that is guaranteed to exist.
+ * this will allow you to get */
+ attribute nsIMsgIncomingServer localFoldersServer;
+
+ // Create the account for that special server.
+ void createLocalMailAccount();
+
+ /* load accounts kicks off the creation of all accounts. You do not need
+ * to call this and all accounts should be loaded lazily if you use any
+ * of the above.
+ */
+ void LoadAccounts();
+
+ void setSpecialFolders();
+
+ void loadVirtualFolders();
+
+ /* unload accounts frees all the account manager data structures */
+ void UnloadAccounts();
+
+ void WriteToFolderCache(in nsIMsgFolderCache folderCache);
+ void saveVirtualFolders();
+ void closeCachedConnections();
+ void shutdownServers();
+
+ void CleanupOnExit();
+ void SetFolderDoingEmptyTrash(in nsIMsgFolder folder);
+ boolean GetEmptyTrashInProgress();
+
+ void SetFolderDoingCleanupInbox(in nsIMsgFolder folder);
+ boolean GetCleanupInboxInProgress();
+
+ void addRootFolderListener(in nsIFolderListener listener);
+ void removeRootFolderListener(in nsIFolderListener listener);
+
+ // these are going away in favor of add/removeRootFolderListener
+ void addIncomingServerListener(in nsIIncomingServerListener serverListener);
+ void removeIncomingServerListener(in nsIIncomingServerListener serverListener);
+
+ // these are going away in favor of nsIMsgFolder::NotifyEvent(in nsIAtom event);
+ void notifyServerLoaded(in nsIMsgIncomingServer server);
+ void notifyServerUnloaded(in nsIMsgIncomingServer server);
+ void notifyServerChanged(in nsIMsgIncomingServer server);
+
+ // force account info out to prefs file
+ void saveAccountInfo();
+
+ ACString getChromePackageName(in ACString aExtensionName);
+
+ /// Enumerate all incoming servers and their folders and return in an array.
+ readonly attribute nsIArray allFolders;
+
+ /**
+ * Iterates over all folders looking for one with the passed in path,
+ * and returns the uri for the matching folder. In the future,
+ * the folder lookup service will provide this functionality.
+ *
+ * @param aLocalPath path of the folder whose uri we want.
+ * @return the URI of the folder that corresponds to aLocalPath
+ */
+ ACString folderUriForPath(in nsIFile aLocalPath);
+
+ // Used to sort servers (accounts) for e.g. the folder pane
+ long getSortOrder(in nsIMsgIncomingServer server);
+};
+
+%{C++
+#define MAILNEWS_ACCOUNTMANAGER_EXTENSIONS "mailnews-accountmanager-extensions"
+%}
+
+[scriptable, uuid(70032DE0-CD59-41ba-839D-FC1B65367EE7)]
+interface nsIMsgAccountManagerExtension : nsISupports
+{
+ readonly attribute ACString name; // examples: mdn
+ boolean showPanel(in nsIMsgIncomingServer server);
+ readonly attribute ACString chromePackageName; // example: messenger, chrome://messenger/content/am-mdn.xul and chrome://messenger/locale/am-mdn.properties
+};
diff --git a/mailnews/base/public/nsIMsgAsyncPrompter.idl b/mailnews/base/public/nsIMsgAsyncPrompter.idl
new file mode 100644
index 000000000..5a59c4f39
--- /dev/null
+++ b/mailnews/base/public/nsIMsgAsyncPrompter.idl
@@ -0,0 +1,63 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgAsyncPromptListener;
+
+/**
+ * The nsIMsgAsyncPrompter is intended to provide a way to make asynchoronous
+ * message prompts into synchronous ones - so that the user is only prompted
+ * with one at a time.
+ */
+[scriptable, uuid(15f67d0f-947a-4a1e-8f72-6ab7162b4b9c)]
+interface nsIMsgAsyncPrompter : nsISupports {
+ /**
+ * Queues an async prompt request. If there are none queued then this will be
+ * actioned straight away, otherwise the prompt will be queued for action
+ * once previous prompt(s) have been cleared.
+ *
+ * Queued prompts using the same aKey may be amalgamated into one prompt to
+ * save repeated prompts to the user.
+ *
+ * @param aKey A key to determine whether or not the queued prompts can
+ * be combined.
+ * @param aPromptImmediately If the user is retrying a failed password, we
+ * need to prompt right away, even if there is a
+ * prompt up, or prompts queued up. Note that
+ * immediately may not be synchronously, on OS/X.
+ * @param aCaller An nsIMsgAsyncPromptListener to call back to when the prompt
+ * is ready to be made.
+ */
+ void queueAsyncAuthPrompt(in ACString aKey, in boolean aPromptImmediately,
+ in nsIMsgAsyncPromptListener aCaller);
+};
+
+/**
+ * This is used in combination with nsIMsgAsyncPrompter.
+ */
+[scriptable, uuid(fb5307a3-39d0-462e-92c8-c5c288a2612f)]
+interface nsIMsgAsyncPromptListener : nsISupports {
+ /**
+ * Called when the listener should do its prompt. The listener
+ * should not return until the prompt is complete.
+ *
+ * @return True if there is auth information available following the prompt,
+ * false otherwise.
+ */
+ boolean onPromptStart();
+
+ /**
+ * Called in the case that the queued prompt was combined with another and
+ * there is now authentication information available.
+ */
+ void onPromptAuthAvailable();
+
+ /**
+ * Called in the case that the queued prompt was combined with another but
+ * the prompt was canceled.
+ */
+ void onPromptCanceled();
+};
diff --git a/mailnews/base/public/nsIMsgBiffManager.idl b/mailnews/base/public/nsIMsgBiffManager.idl
new file mode 100644
index 000000000..52c53d6fa
--- /dev/null
+++ b/mailnews/base/public/nsIMsgBiffManager.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgIncomingServer.idl"
+
+[scriptable, uuid(17275D52-1622-11d3-8A84-0060B0FC04D2)]
+interface nsIMsgBiffManager : nsISupports {
+
+ void init();
+ void addServerBiff(in nsIMsgIncomingServer server);
+ void removeServerBiff(in nsIMsgIncomingServer server);
+ void forceBiff(in nsIMsgIncomingServer server);
+ void forceBiffAll();
+ void shutdown();
+};
+
diff --git a/mailnews/base/public/nsIMsgContentPolicy.idl b/mailnews/base/public/nsIMsgContentPolicy.idl
new file mode 100644
index 000000000..f3c793854
--- /dev/null
+++ b/mailnews/base/public/nsIMsgContentPolicy.idl
@@ -0,0 +1,36 @@
+/* -*- mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(c29b2fd3-64d0-4083-a096-c20a9b847a99)]
+
+/**
+ * This interface provide functions which help extension developers
+ * add their customized schema to the exposed protocls of nsMsgContentPolicy.
+ * By default, a list of existing protocols (such as imap and nntp)
+ * are allowed to process urls locally, while non-matching urls are required
+ * to be processed as external.
+ * This interface allows additional protocols to be added to
+ * the list of protocols that are processed locally.
+ * Typically this would be used in cases where a new messaging protocol
+ * is being added by an extension.
+ */
+interface nsIMsgContentPolicy : nsISupports {
+ /**
+ * Add the specific aScheme to nsMsgContentPolicy's exposed protocols.
+ *
+ * @param aScheme scheme who will be added to nsMsgContentPolicy's exposed protocols
+ */
+ void addExposedProtocol(in ACString aScheme);
+
+ /**
+ * Remove the specific aScheme from nsMsgContentPolicy's exposed protocols.
+ *
+ * @param aScheme scheme who will be removed from nsMsgContentPolicy's exposed protocols
+ */
+ void removeExposedProtocol(in ACString aScheme);
+};
+
diff --git a/mailnews/base/public/nsIMsgCopyService.idl b/mailnews/base/public/nsIMsgCopyService.idl
new file mode 100644
index 000000000..bbb935d16
--- /dev/null
+++ b/mailnews/base/public/nsIMsgCopyService.idl
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsrootidl.idl"
+#include "nsISupports.idl"
+#include "nsIMsgFolder.idl"
+#include "nsIMsgCopyServiceListener.idl"
+
+interface nsIMsgDBHdr;
+interface nsIMsgWindow;
+interface nsIFile;
+interface nsIArray;
+
+[scriptable, uuid(f21e428b-73c5-4607-993b-d37325b33722)]
+interface nsIMsgCopyService : nsISupports {
+
+ /**
+ * Copies or moves existing messages from source folder to destination folder.
+ *
+ * @param srcFolder Source folder of an operation.
+ * @param messages The array of nsIMsgHdrs in source folder which will be moved/copied.
+ * @param dstFolder Destination folder of operation.
+ * @param isMove false for copy operation, true for move operation.
+ * @param listener Listener which receive operation notifications
+ * @param msgWindow Window for notification callbacks, can be null.
+ * @param allowUndo Specifies if this operation will be done as an transaction
+ * that can be undone.
+ */
+ void CopyMessages(in nsIMsgFolder srcFolder,
+ in nsIArray messages,
+ in nsIMsgFolder dstFolder,
+ in boolean isMove,
+ in nsIMsgCopyServiceListener listener,
+ in nsIMsgWindow msgWindow,
+ in boolean allowUndo);
+
+ /**
+ * Copies or moves existing folders do destination folder.
+ *
+ * @param folders The array of nsIMsgFolders which will be moved/copied.
+ * @param dstFolder The destination folder of operation.
+ * @param isMove false for copy operation, true for move operation.
+ * @param listener Listener which receive operation notifications.
+ * @param msgWindow Window for notification callbacks, can be null.
+ */
+ void CopyFolders(in nsIArray folders,
+ in nsIMsgFolder dstFolder,
+ in boolean isMove,
+ in nsIMsgCopyServiceListener listener,
+ in nsIMsgWindow msgWindow);
+
+ /**
+ * Copies message in rfc format from file to folder.
+ *
+ * @param aFile A file which contains message in rfc format which
+ * will copied to destFolder.
+ * @param dstFolder Destination folder where a message will be copied.
+ * @param msgToReplace Header which identifies a message to use as a source
+ * of message properties, or null. For example, when
+ * deleting an attachment, the processed message is
+ * stored in a file, but the metadata should be copied
+ * from the original message. This method will NOT delete
+ * the original message.
+ * @param isDraftOrTemplate Specifies whether a message is a stored in draft
+ * folder or not. If is true listener should
+ * implement GetMessageId and return unique id for
+ * message in destination folder. This is important
+ * for IMAP servers which doesn't support uidplus.
+ * If destination folder contains message with the
+ * same message-id then it is possible that listener
+ * get wrong message key in callback
+ * nsIMsgCopyServiceListener::SetMessageKey.
+ * @param aMsgFlags Message flags which will be set after message is
+ * copied
+ * @param aMsgKeywords Keywords which will be set for newly copied
+ * message.
+ * @param listener Listener which receive copy notifications.
+ * @param msgWindow Window for notification callbacks, can be null.
+ */
+ void CopyFileMessage(in nsIFile aFile,
+ in nsIMsgFolder dstFolder,
+ in nsIMsgDBHdr msgToReplace,
+ in boolean isDraftOrTemplate,
+ in unsigned long aMsgFlags,
+ in ACString aMsgKeywords,
+ in nsIMsgCopyServiceListener listener,
+ in nsIMsgWindow msgWindow);
+
+ /**
+ * Notify the message copy service that the destination folder has finished
+ * it's messages copying operation so that the copy service can continue
+ * copying the rest of the messages if there are more to copy with.
+ * aSupport and dstFolder uniquely identify a copy service request.
+ *
+ * @param aSupport The originator of CopyMessages or CopyFileMessage; it can
+ * be either a nsIMsgFolder or a nsIFile
+ * @param dstFolder The destination folder which performs the copy operation
+ * @param result The result of the copy operation
+ */
+ void NotifyCompletion(in nsISupports aSupport,
+ in nsIMsgFolder dstFolder,
+ in nsresult result);
+};
diff --git a/mailnews/base/public/nsIMsgCopyServiceListener.idl b/mailnews/base/public/nsIMsgCopyServiceListener.idl
new file mode 100644
index 000000000..2d775e280
--- /dev/null
+++ b/mailnews/base/public/nsIMsgCopyServiceListener.idl
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsrootidl.idl"
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+[scriptable, uuid(da6b9843-5464-4630-b121-c5970aa3d6ed)]
+interface nsIMsgCopyServiceListener : nsISupports {
+
+ /**
+ * Notify the observer that the message has started to be copied. This
+ * method is called only once, at the beginning of a message
+ * copyoperation.
+ */
+ void OnStartCopy();
+
+ /**
+ * Notify the observer that progress as occurred for the message copy
+ * aProgress -
+ * aProgressMax -
+ */
+ void OnProgress(in uint32_t aProgress,
+ in uint32_t aProgressMax);
+
+ /**
+ * Setting newly created message key. This method is taylored specifically
+ * for nsIMsgCopyService::CopyFileMessage() when saveing Drafts/Templates.
+ * We need to have a way to inform the client what's the key of the newly
+ * created message.
+ * aKey -
+ */
+ void SetMessageKey(in nsMsgKey aKey);
+
+ /**
+ * Getting the file message message ID. This method is taylored
+ * specifically for nsIMsgCopyService::CopyFileMessage() when saving
+ * Drafts/Templates. In order to work with imap server which doesn't
+ * support uidplus we have to use search comman to retrieve the key of
+ * newly created message. Message ID generated by the compose gurantee its
+ * uniqueness.
+ * aMessageId -
+ */
+ void GetMessageId(out ACString aMessageId);
+
+ /**
+ * Notify the observer that the message copied operation has completed.
+ * This method is called regardless of whether the the operation was
+ * successful.
+ * aStatus - indicate whether the operation was succeeded
+ */
+ void OnStopCopy(in nsresult aStatus);
+};
+
diff --git a/mailnews/base/public/nsIMsgCustomColumnHandler.idl b/mailnews/base/public/nsIMsgCustomColumnHandler.idl
new file mode 100644
index 000000000..87c67f869
--- /dev/null
+++ b/mailnews/base/public/nsIMsgCustomColumnHandler.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsITreeView.idl"
+
+interface nsIMsgDBHdr;
+
+ /* //TODO JavaDoc
+ When implementing a js custom column handler (of type nsITreeView) you must implement the following
+ functions:
+ 1. isEditable
+ 2. GetCellProperties
+ 3. GetImageSrc
+ 4. GetCellText
+ 5. CycleCell
+ 6. GetSortStringForRow
+ 7. GetSortLongForRow
+ 8. isString
+
+ You can, at your option, implement
+ 9. GetRowProperties.
+
+ With Bug 1192696, Grouped By Sort was implemented for custom columns.
+ Implementers should consider that the value returned by GetSortStringForRow
+ will be displayed in the grouped header row, as well as be used as the
+ sort string.
+
+ If implementing a c++ custom column handler, you must define all
+ nsITreeView and nsIMsgCustomColumnHandler methods.
+ */
+
+
+[scriptable, uuid(00f75b13-3ac4-4a17-a8b9-c6e4dd1b3f32)]
+interface nsIMsgCustomColumnHandler : nsITreeView
+{
+ AString getSortStringForRow(in nsIMsgDBHdr aHdr);
+ unsigned long getSortLongForRow(in nsIMsgDBHdr aHdr);
+ boolean isString();
+};
+
diff --git a/mailnews/base/public/nsIMsgDBView.idl b/mailnews/base/public/nsIMsgDBView.idl
new file mode 100644
index 000000000..2d64c547f
--- /dev/null
+++ b/mailnews/base/public/nsIMsgDBView.idl
@@ -0,0 +1,527 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+interface nsIMessenger;
+interface nsIMsgDBHdr;
+interface nsIMsgThread;
+interface nsIMsgDBViewCommandUpdater;
+interface nsIMsgDatabase;
+interface nsIMsgSearchSession;
+interface nsIMutableArray;
+interface nsISimpleEnumerator;
+interface nsIMsgCustomColumnHandler;
+
+typedef long nsMsgViewNotificationCodeValue;
+typedef long nsMsgViewCommandCheckStateValue;
+typedef long nsMsgViewCommandTypeValue;
+typedef long nsMsgNavigationTypeValue;
+
+[scriptable, uuid(682a18be-fd18-11d4-a5be-0060b0fc04b7)]
+interface nsMsgViewSortOrder
+{
+ const nsMsgViewSortOrderValue none = 0;
+ const nsMsgViewSortOrderValue ascending = 1;
+ const nsMsgViewSortOrderValue descending = 2;
+};
+
+[scriptable, uuid(f28a1cdf-06c3-4e98-8f66-f49991670071)]
+interface nsMsgViewType {
+ const nsMsgViewTypeValue eShowAllThreads = 0;
+ const nsMsgViewTypeValue eShowThreadsWithUnread = 2;
+ const nsMsgViewTypeValue eShowWatchedThreadsWithUnread = 3;
+ const nsMsgViewTypeValue eShowQuickSearchResults = 4;
+ const nsMsgViewTypeValue eShowVirtualFolderResults = 5;
+ const nsMsgViewTypeValue eShowSearch = 6;
+};
+
+[scriptable, uuid(64852276-1dd2-11b2-8103-afe12002c053)]
+interface nsMsgViewFlagsType
+{
+ /**
+ * flags for GetViewFlags
+ */
+ const nsMsgViewFlagsTypeValue kNone = 0x0;
+ const nsMsgViewFlagsTypeValue kThreadedDisplay = 0x1;
+ const nsMsgViewFlagsTypeValue kShowIgnored = 0x8;
+ const nsMsgViewFlagsTypeValue kUnreadOnly = 0x10;
+ const nsMsgViewFlagsTypeValue kExpandAll = 0x20;
+ const nsMsgViewFlagsTypeValue kGroupBySort = 0x40;
+};
+
+[scriptable, uuid(b94fc200-3008-420a-85c7-67842f133ef8)]
+interface nsMsgViewSortType
+{
+ const nsMsgViewSortTypeValue byNone = 0x11; /* not sorted */
+ const nsMsgViewSortTypeValue byDate = 0x12;
+ const nsMsgViewSortTypeValue bySubject = 0x13;
+ const nsMsgViewSortTypeValue byAuthor = 0x14;
+ const nsMsgViewSortTypeValue byId = 0x15;
+ const nsMsgViewSortTypeValue byThread = 0x16;
+ const nsMsgViewSortTypeValue byPriority = 0x17;
+ const nsMsgViewSortTypeValue byStatus = 0x18;
+ const nsMsgViewSortTypeValue bySize = 0x19;
+ const nsMsgViewSortTypeValue byFlagged = 0x1a;
+ const nsMsgViewSortTypeValue byUnread = 0x1b;
+ const nsMsgViewSortTypeValue byRecipient = 0x1c;
+ const nsMsgViewSortTypeValue byLocation = 0x1d;
+ const nsMsgViewSortTypeValue byTags = 0x1e;
+ const nsMsgViewSortTypeValue byJunkStatus = 0x1f;
+ const nsMsgViewSortTypeValue byAttachments = 0x20;
+ const nsMsgViewSortTypeValue byAccount = 0x21;
+ const nsMsgViewSortTypeValue byCustom = 0x22;
+ const nsMsgViewSortTypeValue byReceived = 0x23;
+ const nsMsgViewSortTypeValue byCorrespondent = 0x24;
+};
+
+[scriptable, uuid(255d1c1e-fde7-11d4-a5be-0060b0fc04b7)]
+interface nsMsgViewNotificationCode
+{
+ const nsMsgViewNotificationCodeValue none = 0;
+ /* No change; this call is just being used to potentially nest other sets of calls
+ inside it. The "where" and "num" parameters are unused.
+ */
+ const nsMsgViewNotificationCodeValue insertOrDelete = 1;
+ /* Some lines have been inserted or deleted.
+ The "where" parameter will indicate the first line that has been added or
+ removed; the "num" parameter will indicate how many lines, and will be positive on
+ an insertion and negative on a deletion.
+ */
+ const nsMsgViewNotificationCodeValue changed = 2;
+ /* Some lines have had their contents changed (e.g., messages have been marked read
+ or something.) "where" indicates the first line with a change; "num" indicates
+ how many chaged.
+ */
+ const nsMsgViewNotificationCodeValue scramble = 3;
+ /* Everything changed. Probably means we resorted the folder. We are still working
+ with the same set of items, or at least have some overlap, but all the indices are
+ invalid. The "where" and "num" parameters are unused.
+ */
+ const nsMsgViewNotificationCodeValue all = 4;
+ /* Everything changed. We're now not displaying anything like what we were; we
+ probably opened a new folder or something. The FE needs to forget anything it ever knew
+ about what was being displayed, and start over. The "where" and "num" parameters are
+ unused.
+ */
+ const nsMsgViewNotificationCodeValue totalContentChanged = 5;
+ /* Introduced for the address book to support virtual list views. The total number of
+ entries on the LDAP directory has changed and the FE must update its scrollbar. The
+ "num" parameter contains the total number of entries on the LDAP server.
+ */
+ const nsMsgViewNotificationCodeValue newTopIndex = 6;
+ /* Introduced for the address book to support virtual list views. The virtual list view
+ cache data has changed and the FE view may be out of date. The view should be updated
+ so that the first/top index in the view is the index in the "where" parameter. The
+ scrollbar should be updated to match the new position.
+ */
+
+};
+
+[scriptable, uuid(4ec9248e-0108-11d5-a5be-0060b0fc04b7)]
+interface nsMsgViewCommandCheckState
+{
+ const nsMsgViewCommandCheckStateValue notUsed = 0;
+ const nsMsgViewCommandCheckStateValue checked = 1;
+ const nsMsgViewCommandCheckStateValue unchecked = 2;
+};
+
+[scriptable, uuid(ad36e6cc-0109-11d5-a5be-0060b0fc04b7)]
+interface nsMsgViewCommandType
+{
+ const nsMsgViewCommandTypeValue markMessagesRead = 0;
+ const nsMsgViewCommandTypeValue markMessagesUnread = 1;
+ const nsMsgViewCommandTypeValue toggleMessageRead = 2;
+
+ const nsMsgViewCommandTypeValue flagMessages = 3;
+ const nsMsgViewCommandTypeValue unflagMessages = 4;
+
+ const nsMsgViewCommandTypeValue toggleThreadWatched = 6;
+
+ const nsMsgViewCommandTypeValue deleteMsg = 7;
+ const nsMsgViewCommandTypeValue deleteNoTrash = 8;
+ const nsMsgViewCommandTypeValue markThreadRead = 9;
+ const nsMsgViewCommandTypeValue markAllRead = 10;
+ const nsMsgViewCommandTypeValue expandAll = 11;
+ const nsMsgViewCommandTypeValue collapseAll = 12;
+
+ const nsMsgViewCommandTypeValue copyMessages = 13;
+ const nsMsgViewCommandTypeValue moveMessages = 14;
+
+ const nsMsgViewCommandTypeValue selectAll = 15;
+ const nsMsgViewCommandTypeValue downloadSelectedForOffline = 16;
+ const nsMsgViewCommandTypeValue downloadFlaggedForOffline = 17;
+
+ const nsMsgViewCommandTypeValue selectThread = 18;
+ const nsMsgViewCommandTypeValue selectFlagged = 19;
+ const nsMsgViewCommandTypeValue cmdRequiringMsgBody = 20;
+ const nsMsgViewCommandTypeValue label0 = 21;
+ const nsMsgViewCommandTypeValue label1 = 22;
+ const nsMsgViewCommandTypeValue label2 = 23;
+ const nsMsgViewCommandTypeValue label3 = 24;
+ const nsMsgViewCommandTypeValue label4 = 25;
+ const nsMsgViewCommandTypeValue label5 = 26;
+ const nsMsgViewCommandTypeValue lastLabel = 26;
+
+ const nsMsgViewCommandTypeValue junk = 27;
+ const nsMsgViewCommandTypeValue unjunk = 28;
+ const nsMsgViewCommandTypeValue undeleteMsg = 29;
+
+ const nsMsgViewCommandTypeValue applyFilters = 30;
+ const nsMsgViewCommandTypeValue runJunkControls = 31;
+ const nsMsgViewCommandTypeValue deleteJunk = 32;
+};
+
+[scriptable, uuid(65903eb2-1dd2-11b2-ac45-c5b69c1618d7)]
+interface nsMsgNavigationType
+{
+ const nsMsgNavigationTypeValue firstMessage = 1;
+ const nsMsgNavigationTypeValue nextMessage = 2;
+ const nsMsgNavigationTypeValue previousMessage = 3;
+ const nsMsgNavigationTypeValue lastMessage = 4;
+ /**
+ * must match nsMsgViewCommandTypeValue toggleThreadKilled
+ */
+ const nsMsgNavigationTypeValue toggleThreadKilled = 5;
+ const nsMsgNavigationTypeValue firstUnreadMessage = 6;
+ const nsMsgNavigationTypeValue nextUnreadMessage = 7;
+ const nsMsgNavigationTypeValue previousUnreadMessage = 8;
+ const nsMsgNavigationTypeValue lastUnreadMessage = 9;
+ const nsMsgNavigationTypeValue nextUnreadThread = 10;
+ const nsMsgNavigationTypeValue nextUnreadFolder = 11;
+ const nsMsgNavigationTypeValue nextFolder = 12;
+ const nsMsgNavigationTypeValue readMore = 13;
+ /**
+ * Go back to the previous visited message
+ */
+ const nsMsgNavigationTypeValue back = 15;
+ /**
+ * Go forward to the previous visited message
+ */
+ const nsMsgNavigationTypeValue forward = 16;
+ const nsMsgNavigationTypeValue firstFlagged = 17;
+ const nsMsgNavigationTypeValue nextFlagged = 18;
+ const nsMsgNavigationTypeValue previousFlagged = 19;
+ const nsMsgNavigationTypeValue firstNew = 20;
+ const nsMsgNavigationTypeValue editUndo = 21;
+ const nsMsgNavigationTypeValue editRedo = 22;
+ const nsMsgNavigationTypeValue toggleSubthreadKilled = 23;
+};
+
+
+[scriptable, uuid(fe8a2326-4dd0-11e5-8b8a-206a8aa7a25c)]
+interface nsIMsgDBView : nsISupports
+{
+ void open(in nsIMsgFolder folder, in nsMsgViewSortTypeValue sortType, in nsMsgViewSortOrderValue sortOrder, in nsMsgViewFlagsTypeValue viewFlags, out long count);
+ void openWithHdrs(in nsISimpleEnumerator aHeaders, in nsMsgViewSortTypeValue aSortType,
+ in nsMsgViewSortOrderValue aSortOrder,
+ in nsMsgViewFlagsTypeValue aViewFlags, out long aCount);
+ void close();
+
+ void init(in nsIMessenger aMessengerInstance, in nsIMsgWindow aMsgWindow, in nsIMsgDBViewCommandUpdater aCommandUpdater);
+
+ void sort(in nsMsgViewSortTypeValue sortType, in nsMsgViewSortOrderValue sortOrder);
+
+ void doCommand(in nsMsgViewCommandTypeValue command);
+ void doCommandWithFolder(in nsMsgViewCommandTypeValue command, in nsIMsgFolder destFolder);
+ void getCommandStatus(in nsMsgViewCommandTypeValue command, out boolean selectable_p,
+ out nsMsgViewCommandCheckStateValue selected_p);
+
+ readonly attribute nsMsgViewTypeValue viewType;
+ attribute nsMsgViewFlagsTypeValue viewFlags;
+ /** Assigning to this value does not induce a sort; use the sort() method! */
+ attribute nsMsgViewSortTypeValue sortType;
+ readonly attribute nsMsgViewSortOrderValue sortOrder;
+ /**
+ * Reflects the current secondary sort when a secondary sort is in effect.
+ * If the primary sort is by date or id, the value of this attribute is moot.
+ * Assigning to this value does not induce a sort; use the sort() method once
+ * to set your secondary sort, then use it again to set your primary sort.
+ * The only conceivable reason to write to this value is if you have a
+ * grouped view where you want to affect the sort order of the (secondary)
+ * date sort. (Secondary sort is always by date for grouped views.)
+ */
+ attribute nsMsgViewSortTypeValue secondarySortType;
+ /**
+ * Reflects the current secondary sort order.
+ * Assigning to this value does not induce a sort; use the sort() method for
+ * all primary and secondary sort needs. The only reason to assign to this
+ * value is to affect the secondary sort of a grouped view.
+ */
+ attribute nsMsgViewSortOrderValue secondarySortOrder;
+ readonly attribute nsMsgKey keyForFirstSelectedMessage;
+ readonly attribute nsMsgViewIndex viewIndexForFirstSelectedMsg;
+ /**
+ * this method will automatically expand the destination thread,
+ * if needs be.
+ */
+ void viewNavigate(in nsMsgNavigationTypeValue motion, out nsMsgKey resultId, out nsMsgViewIndex resultIndex, out nsMsgViewIndex threadIndex, in boolean wrap);
+
+ /**
+ * Indicates if navigation of the passed motion type is valid.
+ */
+ boolean navigateStatus(in nsMsgNavigationTypeValue motion);
+
+ readonly attribute nsIMsgFolder msgFolder;
+ attribute nsIMsgFolder viewFolder; // in the case of virtual folders, the VF db.
+
+ nsMsgKey getKeyAt(in nsMsgViewIndex index);
+
+ /**
+ * Get the view flags at the passed in index.
+ *
+ * @param aIndex - index to get the view flags for
+ *
+ * @ return - 32 bit view flags (e.g., elided)
+ */
+ unsigned long getFlagsAt(in nsMsgViewIndex aIndex);
+
+ /**
+ * Get the msg hdr at the passed in index
+ *
+ * @param aIndex - index to get the msg hdr at.
+ *
+ * @return - msg hdr at the passed in index
+ * @exception - NS_MSG_INVALID_DBVIEW_INDEX
+ */
+ nsIMsgDBHdr getMsgHdrAt(in nsMsgViewIndex aIndex);
+
+ nsIMsgFolder getFolderForViewIndex(in nsMsgViewIndex index); // mainly for search
+ ACString getURIForViewIndex(in nsMsgViewIndex index);
+ nsIMsgDBView cloneDBView(in nsIMessenger aMessengerInstance, in nsIMsgWindow aMsgWindow, in nsIMsgDBViewCommandUpdater aCommandUpdater);
+
+ /**
+ * Provides a list of the message headers for the currently selected messages.
+ * If the "mail.operate_on_msgs_in_collapsed_threads" preference is enabled,
+ * then any collapsed thread roots that are selected will also (conceptually)
+ * have all of the messages in that thread selected and they will be included
+ * in the returned list. The one exception to this is if the front end fails
+ * to summarize the selection, and we fall back to just displaying a single
+ * message. In that case, we won't include the children of the collapsed
+ * thread. However, the numSelected attribute will count those children,
+ * because the summarizeSelection code uses that to know that it should
+ * try to summarize the selection.
+ *
+ * If the user has right-clicked on a message, this will return that message
+ * (and any collapsed children if so enabled) and not the selection prior to
+ * the right-click.
+ *
+ * @return an array containing the selected message headers. You are free to
+ * mutate the array; it will not affect the underlying selection.
+ */
+ void getSelectedMsgHdrs([optional] out unsigned long count,
+ [retval, array, size_is(count)]
+ out nsIMsgDBHdr headers);
+
+ [deprecated] nsIMutableArray getMsgHdrsForSelection();
+
+ void getURIsForSelection(out unsigned long count, [retval, array, size_is(count)] out string uris);
+ void getIndicesForSelection(out unsigned long count, [retval, array, size_is(count)] out nsMsgViewIndex indices);
+
+ readonly attribute ACString URIForFirstSelectedMessage;
+ readonly attribute nsIMsgDBHdr hdrForFirstSelectedMessage;
+ void loadMessageByMsgKey(in nsMsgKey aMsgKey);
+ void loadMessageByViewIndex(in nsMsgViewIndex aIndex);
+ void loadMessageByUrl(in string aUrl);
+ void reloadMessage();
+ void reloadMessageWithAllParts();
+
+ /**
+ * The number of selected messages. If the
+ * "mail.operate_on_msgs_in_collapsed_threads" preference is enabled, then
+ * any collapsed thread roots that are selected will also conceptually have
+ * all of the messages in that thread selected.
+ */
+ readonly attribute unsigned long numSelected;
+ readonly attribute nsMsgViewIndex msgToSelectAfterDelete;
+ readonly attribute nsMsgViewIndex currentlyDisplayedMessage;
+
+ /**
+ * Number of messages in view, including messages in collapsed threads.
+ * Not currently implemented for threads with unread or watched threads
+ * with unread.
+ */
+ readonly attribute long numMsgsInView;
+ // used by "go to folder" feature
+ // and "remember last selected message" feature
+ // if key is not found, we don't select.
+ void selectMsgByKey(in nsMsgKey key);
+
+ void selectFolderMsgByKey(in nsIMsgFolder aFolder, in nsMsgKey aKey);
+ // we'll suppress displaying messages if the message pane is collapsed
+ attribute boolean suppressMsgDisplay;
+
+ // we'll suppress command updating during folder loading
+ attribute boolean suppressCommandUpdating;
+
+ /**
+ * Suppress change notifications. This is faster than Begin/EndUpdateBatch
+ * on the tree, but less safe in that you're responsible for row invalidation
+ * and row count changes.
+ */
+ attribute boolean suppressChangeNotifications;
+
+ //to notify tree that rows are going away
+ void onDeleteCompleted(in boolean succeeded);
+
+ readonly attribute nsIMsgDatabase db;
+
+ readonly attribute boolean supportsThreading;
+
+ attribute nsIMsgSearchSession searchSession;
+ readonly attribute boolean removeRowOnMoveOrDelete;
+
+ /**
+ * Finds the view index of the passed in msgKey. Note this should not
+ * be called on cross-folder views since the msgKey may not be unique.
+ *
+ * @param aMsgKey - key to find.
+ * @param aExpand - whether to expand a collapsed thread to find the key.
+ *
+ * @return - view index of msg hdr, -1 if hdr not found.
+ */
+ nsMsgViewIndex findIndexFromKey(in nsMsgKey aMsgKey, in boolean aExpand);
+ /**
+ * Finds the view index of the passed in msgHdr.
+ *
+ * @param aMsgHdr - hdr to find.
+ * @param aExpand - whether to expand a collapsed thread to find the hdr.
+ *
+ * @return - view index of msg hdr, -1 if hdr not found.
+ */
+ nsMsgViewIndex findIndexOfMsgHdr(in nsIMsgDBHdr aMsgHdr, in boolean aExpand);
+ void ExpandAndSelectThreadByIndex(in nsMsgViewIndex aIndex, in boolean aAugment);
+
+ /**
+ * This method returns the nsIMsgThread object containing the header displayed
+ * at the desired row. For grouped views and cross folder saved searches,
+ * this will be the view thread, not the db thread.
+ *
+ * @param aIndex view index we want corresponding thread object of.
+ *
+ * @return the thread object at the requested view index
+ */
+ nsIMsgThread getThreadContainingIndex(in nsMsgViewIndex aIndex);
+
+ /**
+ * Insert rows into the view. The caller should use NoteChange() below to
+ * update the view.
+ *
+ * @param aIndex view index for insertion start.
+ * @param aNumRows number of rows to insert.
+ * @param aKey msgKey.
+ * @param aFlags msgFlags.
+ * @param aLevel treeview indent level.
+ * @param aFolder nsIMsgFolder, required for search/xfvf views.
+ */
+ void insertTreeRows(in nsMsgViewIndex aIndex, in unsigned long aNumRows,
+ in nsMsgKey aKey, in nsMsgViewFlagsTypeValue aFlags,
+ in unsigned long aLevel, in nsIMsgFolder aFolder);
+
+ /**
+ * Remove rows from the view. The caller should use NoteChange() below to
+ * update the view.
+ *
+ * @param aIndex view index for removal start.
+ * @param aNumRows number of rows to remove.
+ */
+ void removeTreeRows(in nsMsgViewIndex aIndex, in unsigned long aNumRows);
+
+ /**
+ * Notify tree that rows have changed.
+ *
+ * @param aFirstLineChanged first view index for changed rows.
+ * @param aNumRows number of rows changed; < 0 means removed.
+ * @param aChangeType changeType.
+ */
+ void NoteChange(in nsMsgViewIndex aFirstLineChanged, in long aNumRows,
+ in nsMsgViewNotificationCodeValue aChangeType);
+
+ /**
+ * Return the view thread corresponding to aMsgHdr. If we're a cross-folder
+ * view, then it would be the cross folder view thread, otherwise, the
+ * db thread object.
+ *
+ * @param aMsgHdr message header we want the view thread object of.
+ *
+ * @return view thread object for msg hdr.
+ */
+ nsIMsgThread getThreadContainingMsgHdr(in nsIMsgDBHdr aMsgHdr);
+
+ // use lines or kB for size?
+ readonly attribute boolean usingLines;
+
+ // Custom Column Implementation note: see nsIMsgCustomColumnHandler
+
+ // attaches a custom column handler to a specific column (can be a new column or a built in)
+ void addColumnHandler(in AString aColumn, in nsIMsgCustomColumnHandler aHandler);
+
+ // removes a custom column handler leaving the column to be handled by the system
+ void removeColumnHandler(in AString aColumn);
+
+ // returns the custom column handler attached to a specific column - if any
+ nsIMsgCustomColumnHandler getColumnHandler(in AString aColumn);
+
+ /**
+ * The custom column to use for sorting purposes (when sort type is
+ * nsMsgViewSortType.byCustom.)
+ */
+ attribute AString curCustomColumn;
+
+ /**
+ * The custom column used for a secondary sort, blank if secondarySort is
+ * not byCustom. The secondary sort design is such that the desired secondary
+ * is sorted first, followed by sort by desired primary. The secondary is
+ * read only, as it is set internally according to this design.
+ */
+ readonly attribute AString secondaryCustomColumn;
+ /**
+ * Scriptable accessor for the cell text for a column
+ *
+ * @param aRow - row we want cell text for
+ * @param aColumnName - name of column we want cell text for
+ *
+ * @returns The cell text for the given row and column, if any.
+ * @notes This does not work for custom columns yet.
+ */
+ AString cellTextForColumn(in long aRow, in wstring aColumnName);
+};
+
+/* this interface is rapidly morphing from a command updater interface into a more generic
+ FE updater interface to handle changes in the view
+*/
+
+[scriptable, uuid(ce8f52ee-e742-4b31-8bdd-2b3a8168a117)]
+interface nsIMsgDBViewCommandUpdater : nsISupports
+{
+ /* Eventually we'll flush this out into some kind of rich interface
+ which may take specifc selection changed type notifications like
+ no selections, single selection, multi-selection, etc. For starters,
+ we are going to keep it generic. The back end will only push an update
+ command status when the # of selected items changes.
+ */
+
+ void updateCommandStatus();
+
+ /* displayed message has changed */
+ void displayMessageChanged(in nsIMsgFolder aFolder, in AString aSubject, in ACString aKeywords);
+
+ /**
+ * allows the backend to tell the front end to re-determine
+ * which message we should selet after a delete or move
+ */
+ void updateNextMessageAfterDelete();
+
+ /**
+ * tell the front end that the selection has changed, and may need to be
+ * resummarized.
+ *
+ * @return true if we did summarize, false otherwise.
+ */
+ boolean summarizeSelection();
+};
diff --git a/mailnews/base/public/nsIMsgFolder.idl b/mailnews/base/public/nsIMsgFolder.idl
new file mode 100644
index 000000000..72beeb154
--- /dev/null
+++ b/mailnews/base/public/nsIMsgFolder.idl
@@ -0,0 +1,853 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIFolderListener.idl"
+#include "nsIMsgIncomingServer.idl"
+#include "nsIMsgCopyServiceListener.idl"
+#include "nsIUrlListener.idl"
+#include "nsISimpleEnumerator.idl"
+
+interface nsIMsgDBHdr;
+interface nsIMsgWindow;
+interface nsIMsgDatabase;
+interface nsIDBFolderInfo;
+interface nsIMsgFilterList;
+interface nsIFile;
+interface nsIOutputStream;
+interface nsIInputStream;
+interface nsIFile;
+interface nsIMsgIdentity;
+interface nsIMsgThread;
+interface nsIArray;
+interface nsIMutableArray;
+interface nsIMsgPluggableStore;
+
+typedef long nsMsgBiffState;
+
+// enumerated type for determining if a message has been replied to, forwarded, etc.
+typedef long nsMsgDispositionState;
+
+[scriptable, uuid(5d253ba2-42aa-43a7-b584-0059855ababf)]
+interface nsIMsgFolder : nsISupports {
+
+ const nsMsgBiffState nsMsgBiffState_NewMail = 0; // User has new mail waiting.
+ const nsMsgBiffState nsMsgBiffState_NoMail = 1; // No new mail is waiting.
+ const nsMsgBiffState nsMsgBiffState_Unknown = 2; // We dunno whether there is new mail.
+
+ /// Returns an enumerator containing the messages within the current database.
+ readonly attribute nsISimpleEnumerator messages;
+
+ void startFolderLoading();
+ void endFolderLoading();
+
+ /* get new headers for db */
+ void updateFolder(in nsIMsgWindow aWindow);
+
+ readonly attribute AString prettiestName;
+
+ /**
+ * URL for this folder
+ */
+ readonly attribute ACString folderURL;
+
+ /**
+ * should probably move to the server
+ */
+ readonly attribute boolean showDeletedMessages;
+
+ /**
+ * this folder's parent server
+ */
+ readonly attribute nsIMsgIncomingServer server;
+
+ /**
+ * is this folder the "phantom" server folder?
+ */
+ readonly attribute boolean isServer;
+ readonly attribute boolean canSubscribe;
+ readonly attribute boolean canFileMessages;
+ readonly attribute boolean noSelect; // this is an imap no select folder
+ readonly attribute boolean imapShared; // this is an imap shared folder
+ readonly attribute boolean canDeleteMessages; // can't delete from imap read-only
+
+ /**
+ * does this folder allow subfolders?
+ * for example, newsgroups cannot have subfolders, and the INBOX
+ * on some IMAP servers cannot have subfolders
+ */
+ readonly attribute boolean canCreateSubfolders;
+
+ /**
+ * can you change the name of this folder?
+ * for example, newsgroups
+ * and some special folders can't be renamed
+ */
+ readonly attribute boolean canRename;
+
+ readonly attribute boolean canCompact;
+
+ /**
+ * the phantom server folder
+ */
+ readonly attribute nsIMsgFolder rootFolder;
+
+ /**
+ * Get the server's list of filters. (Or in the case of news, the
+ * filter list for this newsgroup)
+ * This list SHOULD be used for all incoming messages.
+ *
+ * Since the returned nsIMsgFilterList is mutable, it is not necessary to call
+ * setFilterList after the filters have been changed.
+ *
+ * @param aMsgWindow @ref msgwindow "The standard message window"
+ * @return The list of filters
+ */
+ nsIMsgFilterList getFilterList(in nsIMsgWindow msgWindow);
+
+ /**
+ * Set the server's list of filters.
+ *
+ * Note that this does not persist the filter list. To change the contents
+ * of the existing filters, use getFilterList and mutate the values as
+ * appopriate.
+ *
+ * @param aFilterList The new list of filters.
+ */
+ void setFilterList(in nsIMsgFilterList filterList);
+
+ /**
+ * Get user editable filter list. This does not have to be the same as
+ * the filterlist above, typically depending on the users preferences.
+ * The filters in this list are not processed, but only to be edited by
+ * the user.
+ * @see getFilterList
+ *
+ * @param aMsgWindow @ref msgwindow "The standard message window"
+ * @return The list of filters
+ */
+ nsIMsgFilterList getEditableFilterList(in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Set user editable filter list.
+ * This does not persist the filterlist, @see setFilterList
+ * @see getEditableFilterList
+ * @see setFilterList
+ *
+ * @param aFilterList The new list of filters.
+ */
+ void setEditableFilterList(in nsIMsgFilterList aFilterList);
+
+ /**
+ * Force close the mail database associated with this folder.
+ */
+ void ForceDBClosed();
+ /**
+ * Close and backup a folder database prior to reparsing
+ *
+ * @param newName New name of the corresponding message folder.
+ * Used in rename to set the file name to match the renamed
+ * folder. Set to empty to use the existing folder name.
+ */
+ void closeAndBackupFolderDB(in ACString newName);
+
+ void Delete ();
+
+ void deleteSubFolders(in nsIArray folders, in nsIMsgWindow msgWindow);
+ void propagateDelete(in nsIMsgFolder folder, in boolean deleteStorage,
+ in nsIMsgWindow msgWindow);
+ void recursiveDelete(in boolean deleteStorage, in nsIMsgWindow msgWindow);
+
+ /**
+ * Create a subfolder of the current folder with the passed in name.
+ * For IMAP, this will be an async operation and the folder won't exist
+ * until it is created on the server.
+ *
+ * @param folderName name of the folder to create.
+ * @param msgWindow msgWindow to display status feedback in.
+ *
+ * @exception NS_MSG_FOLDER_EXISTS
+ */
+ void createSubfolder(in AString folderName, in nsIMsgWindow msgWindow);
+
+ /**
+ * Adds the subfolder with the passed name to the folder hierarchy.
+ * This is used internally during folder discovery; It shouldn't be
+ * used to create folders since it won't create storage for the folder,
+ * especially for imap. Unless you know exactly what you're doing, you
+ * should be using createSubfolder + getChildNamed or createLocalSubfolder.
+ *
+ * @param aFolderName Name of the folder to add.
+ * @returns The folder added.
+ */
+ nsIMsgFolder addSubfolder(in AString aFolderName);
+
+ /* this method ensures the storage for the folder exists.
+ For local folders, it creates the berkeley mailbox if missing.
+ For imap folders, it subscribes to the folder if it exists,
+ or creates it if it doesn't exist
+ */
+ void createStorageIfMissing(in nsIUrlListener urlListener);
+
+ /**
+ * Compact this folder. For IMAP folders configured for offline use,
+ * it will also compact the offline store, and the completed notification
+ * will occur when the Expunge is finished, not the offline store compaction.
+ *
+ * @param aListener Notified of completion, can be null.
+ * @param aMsgWindow For progress/status, can be null.
+ */
+ void compact(in nsIUrlListener aListener, in nsIMsgWindow aMsgWindow);
+ /**
+ * Compact all folders in the account corresponding to this folder/
+ * Optionally compact their offline stores as well (imap/news)
+ *
+ * @param aListener Notified of completion, can be null.
+ * @param aMsgWindow For progress/status, can be null.
+ * @param aCompactOfflineAlso This controls whether we compact all
+ * offline stores as well.
+ */
+ void compactAll(in nsIUrlListener aListener, in nsIMsgWindow aMsgWindow,
+ in boolean aCompactOfflineAlso);
+
+ void compactAllOfflineStores(in nsIUrlListener aListener,
+ in nsIMsgWindow aMsgWindow,
+ in nsIArray aOfflineFolderArray);
+
+ void emptyTrash(in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
+
+ /**
+ * change the name of the folder
+ *
+ * @param name the new name of the folder
+ */
+ void rename(in AString name, in nsIMsgWindow msgWindow);
+ void renameSubFolders( in nsIMsgWindow msgWindow, in nsIMsgFolder oldFolder);
+
+ AString generateUniqueSubfolderName(in AString prefix,
+ in nsIMsgFolder otherFolder);
+
+ void updateSummaryTotals(in boolean force);
+ void summaryChanged();
+ /**
+ * get the total number of unread messages in this folder,
+ * or in all subfolders
+ *
+ * @param deep if true, descends into all subfolders and gets a grand total
+ */
+ long getNumUnread(in boolean deep);
+
+ /**
+ * get the total number of messages in this folder,
+ * or in all subfolders
+ *
+ * @param deep if true, descends into all subfolders and gets a grand total
+ */
+ long getTotalMessages(in boolean deep);
+
+ /**
+ * These functions are used for tricking the front end into thinking that we
+ * have more messages than are really in the DB. This is usually after an
+ * IMAP message copy where we don't want to do an expensive select until the
+ * user actually opens that folder. These functions are called when
+ * MSG_Master::GetFolderLineById is populating a MSG_FolderLine struct used
+ * by the FE.
+ */
+ readonly attribute long numPendingUnread;
+ readonly attribute long numPendingTotalMessages;
+ void changeNumPendingUnread(in long delta);
+ void changeNumPendingTotalMessages(in long delta);
+
+ /**
+ * does this folder have new messages
+ *
+ */
+ attribute boolean hasNewMessages;
+
+ /**
+ * Indicates whether this folder or any of its subfolders have new messages.
+ */
+ readonly attribute boolean hasFolderOrSubfolderNewMessages;
+
+ /**
+ * return the first new message in the folder
+ *
+ */
+ readonly attribute nsIMsgDBHdr firstNewMessage;
+
+ /**
+ * clear new status flag of all of the new messages
+ */
+ void clearNewMessages();
+
+ readonly attribute long long expungedBytes;
+
+ /**
+ * Can this folder be deleted?
+ * For example, special folders and isServer folders cannot be deleted.
+ */
+ readonly attribute boolean deletable;
+
+ /**
+ * should we be displaying recipients instead of the sender?
+ * for example, in the Sent folder, recipients are more relevant
+ * than the sender
+ */
+ readonly attribute boolean displayRecipients;
+
+ /**
+ * used to determine if it will take a long time to download all
+ * the headers in this folder - so that we can do folder notifications
+ * synchronously instead of asynchronously
+ */
+ readonly attribute boolean manyHeadersToDownload;
+
+ readonly attribute ACString relativePathName;
+
+ /**
+ * size of this folder on disk (not including .msf file)
+ * for imap, it's the sum of the size of the messages
+ */
+ attribute long long sizeOnDisk;
+
+ readonly attribute ACString username;
+ readonly attribute ACString hostname;
+
+ /**
+ * Sets a flag on the folder. The known flags are defined in
+ * nsMsgFolderFlags.h.
+ *
+ * @param flag The flag to set on the folder.
+ */
+ void setFlag(in unsigned long flag);
+
+ /**
+ * Clears a flag on the folder. The known flags are defined in
+ * nsMsgFolderFlags.h.
+ *
+ * @param flag The flag to clear on the folder.
+ */
+ void clearFlag(in unsigned long flag);
+
+ /**
+ * Determines if a flag is set on the folder or not. The known flags are
+ * defined in nsMsgFolderFlags.h.
+ *
+ * @param flag The flag to check on the folder.
+ * @return True if the flag exists.
+ */
+ boolean getFlag(in unsigned long flag);
+
+ /**
+ * Toggles a flag on the folder. The known flags are defined in
+ * nsMsgFolderFlags.h.
+ *
+ * @param flag The flag to toggle
+ */
+ void toggleFlag(in unsigned long flag);
+
+ /**
+ * Called to notify the database and/or listeners of a change of flag. The
+ * known flags are defined in nsMsgFolderFlags.h
+ *
+ * @note This doesn't need to be called for normal flag changes via
+ * the *Flag functions on this interface.
+ *
+ * @param flag The flag that was changed.
+ */
+ void onFlagChange(in unsigned long flag);
+
+ /**
+ * Direct access to the set/get all the flags at once.
+ */
+ attribute unsigned long flags;
+
+ /**
+ * Gets the first folder that has the specified flags set.
+ *
+ * @param flags The flag(s) to check for.
+ * @return The folder or the first available child folder that has
+ * the specified flags set, or null if there are none.
+ */
+ nsIMsgFolder getFolderWithFlags(in unsigned long flags);
+
+ /**
+ * Gets the folders that have the specified flag set.
+ *
+ * @param flags The flag(s) to check for.
+ * @return An array of folders that have the specified flags set.
+ * The array may have zero elements.
+ */
+ nsIArray getFoldersWithFlags(in unsigned long flags);
+
+ /**
+ * Lists the folders that have the specified flag set.
+ *
+ * @param flags The flag(s) to check for.
+ * @param folders The array in which to append the found folder(s).
+ */
+ void listFoldersWithFlags(in unsigned long flags,
+ in nsIMutableArray folders);
+
+ /**
+ * Check if this folder (or one of its ancestors) is special.
+ *
+ * @param flags The "special" flags to check.
+ * @param checkAncestors Should ancestors be checked too.
+ */
+ boolean isSpecialFolder(in unsigned long flags,
+ [optional] in boolean checkAncestors);
+
+ ACString getUriForMsg(in nsIMsgDBHdr msgHdr);
+
+ /**
+ * Deletes the messages from the folder.
+ *
+ * @param messages The array of nsIMsgDBHdr objects to be deleted.
+ * @param msgWindow The standard message window object, for alerts et al.
+ * @param deleteStorage Whether or not the message should be truly deleted, as
+ opposed to moving to trash.
+ * @param isMove Whether or not this is a deletion for moving messages.
+ * @param allowUndo Whether this action should be undoable.
+ */
+ void deleteMessages(in nsIArray messages,
+ in nsIMsgWindow msgWindow,
+ in boolean deleteStorage, in boolean isMove,
+ in nsIMsgCopyServiceListener listener, in boolean allowUndo);
+
+ void copyMessages(in nsIMsgFolder srcFolder, in nsIArray messages,
+ in boolean isMove, in nsIMsgWindow msgWindow,
+ in nsIMsgCopyServiceListener listener, in boolean isFolder,
+ in boolean allowUndo);
+
+ void copyFolder(in nsIMsgFolder srcFolder, in boolean isMoveFolder,
+ in nsIMsgWindow msgWindow, in nsIMsgCopyServiceListener listener );
+
+ void copyFileMessage(in nsIFile file, in nsIMsgDBHdr msgToReplace,
+ in boolean isDraft, in unsigned long newMsgFlags,
+ in ACString aKeywords,
+ in nsIMsgWindow msgWindow,
+ in nsIMsgCopyServiceListener listener);
+
+ void acquireSemaphore (in nsISupports semHolder);
+ void releaseSemaphore (in nsISupports semHolder);
+ boolean testSemaphore (in nsISupports semHolder);
+ readonly attribute boolean locked;
+
+ void getNewMessages(in nsIMsgWindow aWindow, in nsIUrlListener aListener);
+
+ /**
+ * write out summary data for this folder
+ * to the given folder cache (i.e. panacea.dat)
+ */
+ void writeToFolderCache(in nsIMsgFolderCache folderCache, in boolean deep);
+
+ /**
+ * the charset of this folder
+ */
+ attribute ACString charset;
+ attribute boolean charsetOverride;
+ attribute unsigned long biffState;
+
+ /**
+ * The number of new messages since this folder's last biff.
+ *
+ * @param deep if true, descends into all subfolders and gets a grand total
+ */
+
+ long getNumNewMessages (in boolean deep);
+
+ void setNumNewMessages(in long numNewMessages);
+
+ /**
+ * are we running a url as a result of the user clicking get msg?
+ */
+ attribute boolean gettingNewMessages;
+
+ /**
+ * local path of this folder
+ */
+ attribute nsIFile filePath;
+
+ /// an nsIFile corresponding to the .msf file.
+ readonly attribute nsIFile summaryFile;
+
+ readonly attribute ACString baseMessageURI;
+ ACString generateMessageURI(in nsMsgKey msgKey);
+
+ const nsMsgDispositionState nsMsgDispositionState_None = -1;
+ const nsMsgDispositionState nsMsgDispositionState_Replied = 0;
+ const nsMsgDispositionState nsMsgDispositionState_Forwarded = 1;
+ void addMessageDispositionState(in nsIMsgDBHdr aMessage,
+ in nsMsgDispositionState aDispositionFlag);
+
+ void markMessagesRead(in nsIArray messages, in boolean markRead);
+ void markAllMessagesRead(in nsIMsgWindow aMsgWindow);
+ void markMessagesFlagged(in nsIArray messages, in boolean markFlagged);
+ void markThreadRead(in nsIMsgThread thread);
+ void setLabelForMessages(in nsIArray messages, in nsMsgLabelValue label);
+ /**
+ * Gets the message database for the folder.
+ *
+ * Note that if the database is out of date, the implementation MAY choose to
+ * throw an error. For a handle to the database which MAY NOT throw an error,
+ * one can use getDBFolderInfoAndDB.
+ *
+ * The attribute can also be set to another database or to null to force the
+ * folder to reopen the same database when it is needed again.
+ *
+ * @exception NS_MSG_ERROR_FOLDER_SUMMARY_MISSING If the database does not
+ * exist.
+ * @exception NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE If the database contains
+ * out of date information.
+ * @see nsIMsgFolder::getDBFolderInfoAndDB.
+ */
+ attribute nsIMsgDatabase msgDatabase;
+
+ /// Does the folder have a local reference to the msgDatabase?
+ readonly attribute boolean databaseOpen;
+
+ /**
+ * Get the backup message database, used in reparsing. This database must
+ * be created first using closeAndBackupFolderDB()
+ *
+ * @return backup message database
+ */
+ nsIMsgDatabase getBackupMsgDatabase();
+ /**
+ * Remove the backup message database file
+ */
+ void removeBackupMsgDatabase();
+ /**
+ * Open the backup message database file
+ */
+ void openBackupMsgDatabase();
+ nsIMsgDatabase getDBFolderInfoAndDB(out nsIDBFolderInfo folderInfo);
+ nsIMsgDBHdr GetMessageHeader(in nsMsgKey msgKey);
+
+ readonly attribute boolean supportsOffline;
+ boolean shouldStoreMsgOffline(in nsMsgKey msgKey);
+ boolean hasMsgOffline(in nsMsgKey msgKey);
+
+ /**
+ * Get an input stream to read the offline contents of an imap or news
+ * message.
+ *
+ * @param aMsgKey key of message to get input stream for.
+ * @param[out] aOffset filled in with the offset into the stream of the message.
+ * @param[out] aSize filled in with the size of the message in the offline store.
+ *
+ * @returns input stream to read the message from.
+ */
+ nsIInputStream getOfflineFileStream(in nsMsgKey aMsgKey,
+ out long long aOffset,
+ out unsigned long aSize);
+
+ /**
+ * Get the folder where the msg could be present.
+ * @param msgKey key of the msg for which we are trying to get the folder;
+ * @returns aMsgFolder required folder;
+ *
+ */
+ nsIMsgFolder GetOfflineMsgFolder(in nsMsgKey msgKey);
+
+ /**
+ * Get an offline store output stream for the passed message header.
+ *
+ * @param aHdr hdr of message to get outputstream for
+ * @returns An output stream to write to.
+ */
+ nsIOutputStream getOfflineStoreOutputStream(in nsIMsgDBHdr aHdr);
+
+ /**
+ * Get an input stream for the passed message header. The stream will
+ * be positioned at the start of the message.
+ *
+ * @param aHdr hdr of message to get the input stream for.
+ * @param[out] aReusable set to true if the stream can be re-used, in which
+ case the caller might not want to close it.
+ * @returns an input stream to read the message from
+ */
+ nsIInputStream getMsgInputStream(in nsIMsgDBHdr aHdr, out boolean aReusable);
+
+ void DownloadMessagesForOffline(in nsIArray messages,
+ in nsIMsgWindow window);
+ nsIMsgFolder getChildWithURI(in ACString uri, in boolean deep,
+ in boolean caseInsensitive);
+ void downloadAllForOffline(in nsIUrlListener listener, in nsIMsgWindow window);
+ /**
+ * Turn notifications on/off for various notification types. Currently only
+ * supporting allMessageCountNotifications which refers to both total and
+ * unread message counts.
+ */
+ const unsigned long allMessageCountNotifications = 0;
+ void enableNotifications(in long notificationType, in boolean enable,
+ in boolean dbBatching);
+ boolean isCommandEnabled(in ACString command);
+ boolean matchOrChangeFilterDestination(in nsIMsgFolder folder,
+ in boolean caseInsensitive);
+ boolean confirmFolderDeletionForFilter(in nsIMsgWindow msgWindow);
+ void alertFilterChanged(in nsIMsgWindow msgWindow);
+ void throwAlertMsg(in string msgName, in nsIMsgWindow msgWindow);
+ AString getStringWithFolderNameFromBundle(in string msgName);
+ void notifyCompactCompleted();
+ long compareSortKeys(in nsIMsgFolder msgFolder);
+ /**
+ * Returns a sort key that can be used to sort a list of folders.
+ *
+ * Prefer nsIMsgFolder::compareSortKeys over this function.
+ */
+ void getSortKey(out unsigned long length, [array, size_is(length), retval] out octet key);
+
+ attribute nsIMsgRetentionSettings retentionSettings;
+ attribute nsIMsgDownloadSettings downloadSettings;
+ boolean callFilterPlugins(in nsIMsgWindow aMsgWindow);
+ /**
+ * used for order in the folder pane, folder pickers, etc.
+ */
+ attribute long sortOrder;
+
+ attribute nsIDBFolderInfo dBTransferInfo;
+
+ ACString getStringProperty(in string propertyName);
+ void setStringProperty(in string propertyName, in ACString propertyValue);
+
+ /* does not persist across sessions */
+ attribute nsMsgKey lastMessageLoaded;
+
+ /* old nsIFolder properties and methods */
+ readonly attribute ACString URI;
+ attribute AString name;
+ attribute AString prettyName;
+ readonly attribute AString abbreviatedName;
+
+ attribute nsIMsgFolder parent;
+
+ /**
+ * Returns an enumerator containing a list of nsIMsgFolder items that are
+ * subfolders of the instance this is called on.
+ */
+ readonly attribute nsISimpleEnumerator subFolders;
+
+ /**
+ * Returns true if this folder has sub folders.
+ */
+ readonly attribute boolean hasSubFolders;
+
+ /**
+ * Returns the number of sub folders that this folder has.
+ */
+ readonly attribute unsigned long numSubFolders;
+
+ /**
+ * Determines if this folder is an ancestor of the supplied folder.
+ *
+ * @param folder The folder that may or may not be a descendent of this
+ * folder.
+ */
+ boolean isAncestorOf(in nsIMsgFolder folder);
+
+ /**
+ * Looks in immediate children of this folder for the given name.
+ *
+ * @param name the name of the target subfolder
+ */
+ boolean containsChildNamed(in AString name);
+
+ /**
+ * Return the child folder which the specified name.
+ *
+ * @param aName The name of the child folder to find
+ * @return The child folder
+ * @exception NS_ERROR_FAILURE Thrown if the folder with aName does not exist
+ */
+ nsIMsgFolder getChildNamed(in AString aName);
+
+ /**
+ * Finds the sub folder with the specified name.
+ *
+ * @param escapedSubFolderName The name of the sub folder to find.
+ * @note Even if the folder doesn't currently exist,
+ * a nsIMsgFolder may be returned.
+ */
+ nsIMsgFolder findSubFolder(in ACString escapedSubFolderName);
+
+ void AddFolderListener(in nsIFolderListener listener);
+ void RemoveFolderListener(in nsIFolderListener listener);
+ void NotifyPropertyChanged(in nsIAtom property,
+ in ACString oldValue,
+ in ACString newValue);
+ void NotifyIntPropertyChanged(in nsIAtom property,
+ in long long oldValue,
+ in long long newValue);
+ void NotifyBoolPropertyChanged(in nsIAtom property,
+ in boolean oldValue,
+ in boolean newValue);
+ void NotifyPropertyFlagChanged(in nsIMsgDBHdr item,
+ in nsIAtom property,
+ in unsigned long oldValue,
+ in unsigned long newValue);
+ void NotifyUnicharPropertyChanged(in nsIAtom property,
+ in AString oldValue,
+ in AString newValue);
+
+ void NotifyItemAdded(in nsISupports item);
+ void NotifyItemRemoved(in nsISupports item);
+
+ void NotifyFolderEvent(in nsIAtom event);
+
+ // void NotifyFolderLoaded();
+ // void NotifyDeleteOrMoveMessagesCompleted(in nsIMsgFolder folder);
+
+ // Gets all descendants, not just first level children.
+ readonly attribute nsIArray descendants;
+ /*
+ * Lists all the folders that are subfolders of the current folder,
+ * all levels of children.
+ *
+ * @param aDescendants The array in which to append the found folder(s).
+ */
+ void ListDescendants(in nsIMutableArray aDescendants);
+ void Shutdown(in boolean shutdownChildren);
+
+ readonly attribute boolean inVFEditSearchScope;
+ void setInVFEditSearchScope(in boolean aSearchThisFolder, in boolean aSetOnSubFolders);
+ void copyDataToOutputStreamForAppend(in nsIInputStream aIStream,
+ in long aLength, in nsIOutputStream outputStream);
+ void copyDataDone();
+ void setJunkScoreForMessages(in nsIArray aMessages, in ACString aJunkScore);
+ void applyRetentionSettings();
+
+ /**
+ * Get the beginning of the message bodies for the passed in keys and store
+ * them in the msg hdr property "preview". This is intended for
+ * new mail alerts, title tips on folders with new messages, and perhaps
+ * titletips/message preview in the thread pane.
+ *
+ * @param aKeysToFetch keys of msgs to fetch
+ * @param aNumKeys number of keys to fetch
+ * @param aLocalOnly whether to fetch msgs from server (imap msgs might
+ * be in memory cache from junk filter)
+ * @param aUrlListener url listener to notify if we run url to fetch msgs
+ *
+ * @result aAsyncResults if true, we ran a url to fetch one or more of msg bodies
+ *
+ */
+ boolean fetchMsgPreviewText([array, size_is (aNumKeys)] in nsMsgKey aKeysToFetch,
+ in unsigned long aNumKeys, in boolean aLocalOnly,
+ in nsIUrlListener aUrlListener);
+
+ // used to set/clear tags - we could have a single method to setKeywords which
+ // would figure out the diffs, but these methods might be more convenient.
+ // keywords are space delimited, in the case of multiple keywords
+ void addKeywordsToMessages(in nsIArray aMessages, in ACString aKeywords);
+ void removeKeywordsFromMessages(in nsIArray aMessages, in ACString aKeywords);
+ /**
+ * Extract the message text from aStream.
+ *
+ * @param aStream stream to read from
+ * @param aCharset character set to use to interpret the body. If an empty string, then the
+ * charset is retrieved from the headers. msgHdr.charset is recommended in case you have it.
+ * @param aBytesToRead number of bytes to read from the stream. The function will read till the end
+ * of the line, and there will also be some read ahead due to NS_ReadLine
+ * @param aMaxOutputLen desired length of the converted message text. Used to control how many characters
+ * of msg text we want to store.
+ * @param aCompressQuotes Replace quotes and citations with " ... " in the preview text
+ * @param aStripHTMLTags strip HTML tags from the output, if present
+ * @param[out] aContentType the content type of the MIME part that was used to generate the text --
+ * for an HTML part, this will be "text/html" even though aStripHTMLTags might be true
+ */
+ AUTF8String getMsgTextFromStream(in nsIInputStream aStream, in ACString aCharset,
+ in unsigned long aBytesToRead, in unsigned long aMaxOutputLen,
+ in boolean aCompressQuotes, in boolean aStripHTMLTags,
+ out ACString aContentType);
+
+ AString convertMsgSnippetToPlainText(in AString aMessageText);
+
+ // this allows a folder to have a special identity. E.g., you might want to
+ // associate an identity with a particular newsgroup, or for IMAP shared folders in
+ // the other users namespace, you might want to create a delegated identity
+ readonly attribute nsIMsgIdentity customIdentity;
+
+ /**
+ * @{
+ * Processing flags, used to manage message processing.
+ *
+ * @param msgKey message key
+ * @return processing flags
+ */
+ unsigned long getProcessingFlags(in nsMsgKey msgKey);
+
+ /**
+ * @param msgKey message key
+ * @param mask mask to OR into the flags
+ */
+ void orProcessingFlags(in nsMsgKey msgKey, in unsigned long mask);
+
+ /**
+ * @param msgKey message key
+ * @param mask mask to AND into the flags
+ */
+ void andProcessingFlags(in nsMsgKey msgKey, in unsigned long mask);
+ /** @} */
+
+ /**
+ * Gets an inherited string property from the folder.
+ *
+ * If the forcePropertyEmpty boolean is set (see below), return an
+ * empty string.
+ *
+ * If the specified folder has a non-empty value for the property,
+ * return that value. Otherwise, return getInheritedStringProperty
+ * for the folder's parent.
+ *
+ * If a folder is the root folder for a server, then instead of
+ * checking the folder property, check the property of the same name
+ * for the server using nsIMsgIncomingServer.getCharValue(...)
+ *
+ * Note nsIMsgIncomingServer.getCharValue for a server inherits from
+ * the preference mail.server.default.(propertyName) as a global value
+ *
+ * (ex: if propertyName = "IAmAGlobal" and no folder nor server properties
+ * are set, then the inherited property will return the preference value
+ * mail.server.default.IAmAGlobal)
+ *
+ * If the propertyName is undefined, returns an empty, void string.
+ *
+ * @param propertyName The name of the property for the value to retrieve.
+ */
+ ACString getInheritedStringProperty(in string propertyName);
+
+ /**
+ * Set a boolean to force an inherited propertyName to return empty instead
+ * of inheriting from a parent folder, server, or the global
+ *
+ * @param propertyName The name of the property
+ * @param aForcePropertyEmpty true if an empty inherited property should be returned
+ */
+ void setForcePropertyEmpty(in string propertyName, in boolean aForcePropertyEmpty);
+
+ /**
+ * Get a boolean to force an inherited propertyName to return empty instead
+ * of inheriting from a parent folder, server, or the global
+ *
+ * @param propertyName The name of the property
+ *
+ * @return true if an empty inherited property should be returned
+ */
+ boolean getForcePropertyEmpty(in string propertyName);
+
+ /**
+ * Pluggable store for this folder. Currently, this will always be the same
+ * as the pluggable store for the server.
+ */
+ readonly attribute nsIMsgPluggableStore msgStore;
+
+ /**
+ * Protocol type, i.e. "pop3", "imap", "nntp", "none", etc
+ * used to construct URLs for this account type.
+ */
+ readonly attribute ACString incomingServerType;
+};
diff --git a/mailnews/base/public/nsIMsgFolderCache.idl b/mailnews/base/public/nsIMsgFolderCache.idl
new file mode 100644
index 000000000..8b3497085
--- /dev/null
+++ b/mailnews/base/public/nsIMsgFolderCache.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIMsgFolderCacheElement;
+
+[scriptable, uuid(78C2B6A2-E29F-44de-9543-10DBB51E245C)]
+interface nsIMsgFolderCache : nsISupports
+{
+ void Init(in nsIFile aFile);
+ nsIMsgFolderCacheElement GetCacheElement(in ACString key, in boolean createIfMissing);
+ void clear();
+ void close();
+ void commit(in boolean compress);
+ void removeElement(in ACString key);
+};
diff --git a/mailnews/base/public/nsIMsgFolderCacheElement.idl b/mailnews/base/public/nsIMsgFolderCacheElement.idl
new file mode 100644
index 000000000..fa158befa
--- /dev/null
+++ b/mailnews/base/public/nsIMsgFolderCacheElement.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(c7392b12-f68a-46b2-af5e-d47350bb17c3)]
+interface nsIMsgFolderCacheElement : nsISupports
+{
+ attribute ACString key;
+ ACString getStringProperty(in string propertyName);
+ long getInt32Property(in string propertyName);
+ long long getInt64Property(in string propertyName);
+ void setStringProperty(in string propertyName, in ACString propertyValue);
+ void setInt32Property(in string propertyName, in long propertyValue);
+ void setInt64Property(in string propertyName, in long long propertyValue);
+};
diff --git a/mailnews/base/public/nsIMsgFolderCompactor.idl b/mailnews/base/public/nsIMsgFolderCompactor.idl
new file mode 100644
index 000000000..66fc27854
--- /dev/null
+++ b/mailnews/base/public/nsIMsgFolderCompactor.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+interface nsIUrlListener;
+interface nsIArray;
+
+[scriptable, uuid(38c7e876-3083-4aea-8dcd-0ea0ec1753a3)]
+
+/**
+ * Use this for any object that wants to handle compacting folders.
+ * Currently, the folders themselves create this object.
+ */
+
+interface nsIMsgFolderCompactor : nsISupports
+{
+ /**
+ * Compact the given folder, or its offline store (imap/news only)
+ *
+ * @param aFolder The folder to compact
+ * @param aOfflineStore Just compact the offline store?
+ * @param aListener Notified of completion, can be null.
+ * @param aMsgWindow Used for progress/status, can be null
+ */
+ void compact(in nsIMsgFolder aFolder, in boolean aOfflineStore,
+ in nsIUrlListener aListener, in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Compact the passed in array of folders, and the passed in offline stores.
+ *
+ * @param aArrayOfFoldersToCompact The folders to compact.
+ * @param aOfflineFolderArray Folders whose offline stores we
+ * should compact, can be null.
+ * @param aListener Notified of completion, can be null.
+ * @param aMsgWindow Used for progress/status, can be null
+ */
+ void compactFolders(in nsIArray aArrayOfFoldersToCompact,
+ in nsIArray aOfflineFolderArray,
+ in nsIUrlListener aListener,
+ in nsIMsgWindow aMsgWindow);
+};
diff --git a/mailnews/base/public/nsIMsgFolderListener.idl b/mailnews/base/public/nsIMsgFolderListener.idl
new file mode 100644
index 000000000..de557b846
--- /dev/null
+++ b/mailnews/base/public/nsIMsgFolderListener.idl
@@ -0,0 +1,212 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgDBHdr;
+interface nsIMsgFolder;
+interface nsIArray;
+
+/**
+ * This is similar to nsIFolderListener, but with slightly different semantics,
+ * especially w.r.t. moving messages and folders. Some listeners want to know
+ * about moves, instead of getting an itemAdded and itemRemoved notification.
+ * Folder listeners also only tend to get called if a view is open on the folder,
+ * which is not always the case. I don't want to change nsIFolderListener at this
+ * point since there are lots of extensions that rely on it. Eventually,
+ * these two interfaces should be combined somehow.
+ */
+
+[scriptable, uuid(2f87be72-0565-4e64-a824-0eb9c258f884)]
+interface nsIMsgFolderListener : nsISupports {
+ /**
+ * Notified immediately after a message is added to a folder. This could be a
+ * new incoming message to a local folder, or a new message in an IMAP folder
+ * when it is opened.
+ *
+ * You may want to consider using the msgsClassified notification instead of
+ * this notification if any of the following are true:
+ *
+ * - You only want to be notified about messages after junk classification
+ * has occurred (if it is going to occur for a message). This also goes for
+ * trait classification which is a generic use of the bayesian engine at
+ * the heart of the spam logic.
+ *
+ * - You only want to be notified about messages after all filters have been
+ * run. Although some filters may be run before the msgAdded notification
+ * is generated, filters dependent on junk/trait classification wait until
+ * classification completes.
+ *
+ * @param aMsg The message header that was just added
+ */
+ void msgAdded(in nsIMsgDBHdr aMsg);
+
+ /**
+ * Notification that (new to the client) messages have been through junk and
+ * trait classification. This event will occur for all messages at some point
+ * after their existence is revealed by msgAdded.
+ *
+ * Because junk classification does not run if no messages have ever been
+ * marked as junk by the user, it is possible to receive this message without
+ * any classification having actually been performed. We still generate the
+ * notification in this case so that code is reliably notified about the
+ * existence of the new message headers.
+ *
+ * @param aMsgs The message headers that have been classified or were
+ * intentionally not classified.
+ * @param aJunkProcessed Were the messages processed for junk classification?
+ * @param aTraitProcessed Were the messages processed for trait
+ * classification?
+ */
+ void msgsClassified(in nsIArray aMsgs, in boolean aJunkProcessed,
+ in boolean aTraitProcessed);
+
+ /**
+ * Notified after a command to delete a group of messages has been given, but before the
+ * messages have actually been deleted.
+ *
+ * @param aMsgs An array of the message headers about to be deleted
+ *
+ * @note
+ * This notification will not take place if the messages are being deleted from the folder
+ * as the result of a move to another folder. Instead, the msgsMoveCopyCompleted() notification
+ * takes place.
+ *
+ * @note
+ * "Deleting" to a trash folder is actually a move, and is covered by msgsMoveCopyCompleted()
+ *
+ * @note
+ * If the user has selected the IMAP delete model (marking messages as deleted, then purging them
+ * later) for an IMAP account, this notification will not take place on the delete. This will only
+ * take place on the purge.
+ */
+ void msgsDeleted(in nsIArray aMsgs);
+
+ /**
+ * Notified after a command to move or copy a group of messages completes. In
+ * case of a move, this is before the messages have been deleted from the
+ * source folder.
+ *
+ * @param aMove true if a move, false if a copy
+ * @param aSrcMsgs An array of the message headers in the source folder
+ * @param aDestFolder The folder these messages were moved to.
+ * @param aDestMsgs This provides the list of target message headers.
+ For imap messages, these will be "pseudo" headers, with
+ a made up UID. When we download the "real" header, we
+ will send a msgKeyChanged notification. Currently, if
+ the imap move/copy happens strictly online (essentially,
+ not user-initiated), then aDestMsgs will be null.
+ *
+ * @note
+ * If messages are moved from a server which uses the IMAP delete model,
+ * you'll get aMove = false. That's because the messages are not deleted from
+ * the source database, but instead simply marked deleted.
+ */
+ void msgsMoveCopyCompleted(in boolean aMove,
+ in nsIArray aSrcMsgs,
+ in nsIMsgFolder aDestFolder,
+ in nsIArray aDestMsgs);
+
+ /**
+ * Notification sent when the msg key for a header may have changed.
+ * This is used when we create a header for an offline imap move result,
+ * without knowing what the ultimate UID will be. When we download the
+ * headers for the new message, we replace the old "pseudo" header with
+ * a new header that has the correct UID/message key. The uid of the new hdr
+ * may turn out to be the same as aOldKey if we've guessed correctly but
+ * the listener can use this notification to know that it can ignore the
+ * msgAdded notification that's coming for aNewHdr. We do NOT send a
+ * msgsDeleted notification for the pseudo header.
+ *
+ * @param aOldKey The fake UID. The header with this key has been removed
+ * by the time this is called.
+ * @param aNewHdr The header that replaces the header with aOldKey.
+ */
+ void msgKeyChanged(in nsMsgKey aOldKey, in nsIMsgDBHdr aNewHdr);
+
+ /**
+ * Notified after a folder has been added.
+ *
+ * @param aFolder The folder that has just been added
+ */
+ void folderAdded(in nsIMsgFolder aFolder);
+
+ /**
+ * Notified after a folder has been deleted and its corresponding file(s) deleted from disk.
+ *
+ * @param aFolder The folder that has just been deleted
+ *
+ * @note
+ * "Deleting" to a trash folder is actually a move, and is covered by folderMoveCopyCompleted()
+ */
+ void folderDeleted(in nsIMsgFolder aFolder);
+
+ /**
+ * Notified after a command to move or copy a folder completes. In case of a move, at this point,
+ * the original folder and its files have already been moved to the new location.
+ *
+ * @param aMove true if a move, false if a copy
+ * @param aSrcFolder The original folder that was moved
+ * @param aDestFolder The parent folder this folder was moved to
+ */
+ void folderMoveCopyCompleted(in boolean aMove,
+ in nsIMsgFolder aSrcFolder,
+ in nsIMsgFolder aDestFolder);
+
+ /**
+ * Notified after a folder is renamed.
+ *
+ * @param aOrigFolder The folder with the old name
+ * @param aNewFolder The folder with the new name
+ */
+ void folderRenamed(in nsIMsgFolder aOrigFolder, in nsIMsgFolder aNewFolder);
+
+ /**
+ * Notified when a particular event takes place for an item.
+ *
+ * Current uses by event string:
+ *
+ * - FolderCompactStart: nsIMsgFolderCompactor is beginning compaction of the
+ * folder. If the summary file was missing or out-of-date and a parse
+ * is required, this notification will come after the completion of the
+ * parse. The compactor will be holding the folder's semaphore when
+ * this notification is generated. This only happens for local folders
+ * currently. aItem is the folder.
+ * - FolderCompactFinish: nsIMsgFolderCompactor has completed compaction of
+ * the folder. This notification will be generated immediately prior to
+ * the nsIFolderListener::itemEvent() notification with a
+ * "CompactCompleted" atom. At this point, the folder semaphore has been
+ * released and the database has been committed. aItem is the folder.
+ *
+ * - FolderReindexTriggered: The user has opted to rebuild the mork msf index
+ * for a folder. Following this notification, the database will be
+ * closed, backed up (so that header properties can be propagated), and
+ * then rebuilt from the source. The rebuild is triggered by a call to
+ * updateFolder, so an nsIFolderListener OnItemEvent(folder,
+ * FolderLoaded atom) notification will be received if you want to know
+ * when this is all completed. Note: this event is only generated for
+ * Thunderbird because the event currently comes from Thunderbird-specific
+ * code.
+ *
+ * - JunkStatusChanged: Indicates that some messages that had already been
+ * reported by msgsClassified have had their junk status changed. This
+ * event will not fire for the initial automatic classification of
+ * messages; msgsClassified will tell you about those messages. This
+ * notification may be promoted to an explicit callback function at some
+ * point. This is not guaranteed to be a comprehensive source of junk
+ * notification events; right now any time an nsMsgDBView marks things as
+ * junk/non-junk a notification is produced. aItem is an nsIArray of
+ * messages, aData is either a "junk" or "notjunk" atom if all of the
+ * messages have the same state.
+ *
+ * - UnincorporatedMessageMoved: A messages received via POP was moved
+ * by a "before junk" rule.
+ *
+ * @param aItem The item the event takes place on
+ * @param aEvent String describing the event
+ * @param aData Data relevant to the event
+ */
+ void itemEvent(in nsISupports aItem, in ACString aEvent, in nsISupports aData);
+};
diff --git a/mailnews/base/public/nsIMsgFolderNotificationService.idl b/mailnews/base/public/nsIMsgFolderNotificationService.idl
new file mode 100644
index 000000000..0318a1107
--- /dev/null
+++ b/mailnews/base/public/nsIMsgFolderNotificationService.idl
@@ -0,0 +1,98 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgDBHdr;
+interface nsIMsgFolder;
+interface nsIMsgFolderListener;
+interface nsIArray;
+
+typedef unsigned long msgFolderListenerFlag;
+
+[scriptable, uuid(e54a592c-2f23-4771-9670-bdb9d4f5dbbd)]
+interface nsIMsgFolderNotificationService : nsISupports {
+ /**
+ * @name Notification flags
+ * These flags determine which notifications will be sent.
+ * @{
+ */
+ /// nsIMsgFolderListener::msgAdded notification
+ const msgFolderListenerFlag msgAdded = 0x1;
+
+ /// nsIMsgFolderListener::msgsDeleted notification
+ const msgFolderListenerFlag msgsDeleted = 0x2;
+
+ /// nsIMsgFolderListener::msgsMoveCopyCompleted notification
+ const msgFolderListenerFlag msgsMoveCopyCompleted = 0x4;
+
+ /// nsIMsgFolderListener::msgsClassified notification
+ const msgFolderListenerFlag msgsClassified = 0x8;
+
+ /// nsIMsgFolderListener::folderAdded notification
+ const msgFolderListenerFlag folderAdded = 0x8000;
+
+ /// nsIMsgFolderListener::folderDeleted notification
+ const msgFolderListenerFlag folderDeleted = 0x1000;
+
+ /// nsIMsgFolderListener::folderMoveCopyCompleted notification
+ const msgFolderListenerFlag folderMoveCopyCompleted = 0x2000;
+
+ /// nsIMsgFolderListener::folderRenamed notification
+ const msgFolderListenerFlag folderRenamed = 0x4000;
+
+ /// nsIMsgFolderListener::itemEvent notification
+ const msgFolderListenerFlag itemEvent = 0x1000000;
+
+ /// nsIMsgFolderListener::msgKeyChanged notification
+ const msgFolderListenerFlag msgKeyChanged = 0x2000000;
+
+ /** @} */
+
+ readonly attribute boolean hasListeners;
+ void addListener(in nsIMsgFolderListener aListener,
+ in msgFolderListenerFlag flags);
+ void removeListener(in nsIMsgFolderListener aListener);
+
+ // message-specific functions
+ // single message for added, array for delete/move/copy
+ void notifyMsgAdded(in nsIMsgDBHdr aMsg);
+ void notifyMsgsClassified(in nsIArray aMsgs,
+ in boolean aJunkProcessed,
+ in boolean aTraitProcessed);
+ void notifyMsgsDeleted(in nsIArray aMsgs);
+ void notifyMsgsMoveCopyCompleted(in boolean aMove,
+ in nsIArray aSrcMsgs,
+ in nsIMsgFolder aDestFolder,
+ in nsIArray aDestMsgs);
+
+ /**
+ * Notify listeners that the msg key for a header has changed. Currently,
+ * this is used when we create a header for an offline imap move result,
+ * without knowing what the ultimate UID will be. When we download the
+ * headers for the new message, we replace the old "pseudo" header with
+ * a new header that has the correct UID/message key, by cloning the pseudo
+ * header, which maintains all the existing header attributes.
+ *
+ * @param aOldKey The fake UID. The header with this key has been removed
+ * by the time this is called.
+ * @param aNewHdr The header that replaces the header with aOldKey.
+ */
+ void notifyMsgKeyChanged(in nsMsgKey aOldKey, in nsIMsgDBHdr aNewHdr);
+ // folder specific functions
+ // single folders, all the time
+ void notifyFolderAdded(in nsIMsgFolder aFolder);
+ void notifyFolderDeleted(in nsIMsgFolder aFolder);
+ void notifyFolderMoveCopyCompleted(in boolean aMove,
+ in nsIMsgFolder aSrcFolder,
+ in nsIMsgFolder aDestFolder);
+ void notifyFolderRenamed(in nsIMsgFolder aOrigFolder,
+ in nsIMsgFolder aNewFolder);
+
+ // extensibility hook
+ void notifyItemEvent(in nsISupports aItem,
+ in ACString aEvent,
+ in nsISupports aData);
+};
diff --git a/mailnews/base/public/nsIMsgHdr.idl b/mailnews/base/public/nsIMsgHdr.idl
new file mode 100644
index 000000000..977695c10
--- /dev/null
+++ b/mailnews/base/public/nsIMsgHdr.idl
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgFolder;
+interface nsIUTF8StringEnumerator;
+
+[scriptable, uuid(3c11ddbe-c805-40c5-b9c9-d065fad5d0be)]
+interface nsIMsgDBHdr : nsISupports
+{
+ /* general property routines - I think this can retrieve any
+ header in the db */
+ AString getProperty(in string propertyName);
+ void setProperty(in string propertyName, in AString propertyStr);
+ void setStringProperty(in string propertyName, in string propertyValue);
+ string getStringProperty(in string propertyName);
+ unsigned long getUint32Property(in string propertyName);
+ void setUint32Property(in string propertyName,
+ in unsigned long propertyVal);
+
+ // accessors, to make our JS cleaner
+ readonly attribute boolean isRead;
+ readonly attribute boolean isFlagged;
+
+ // Special accessor that checks if a message is part of an ignored subthread
+ readonly attribute boolean isKilled;
+
+ // Mark message routines
+ void markRead(in boolean read);
+ void markFlagged(in boolean flagged);
+ void markHasAttachments(in boolean hasAttachments);
+
+ attribute nsMsgPriorityValue priority;
+ void setPriorityString(in string priority);
+
+ /* flag handling routines */
+ attribute unsigned long flags;
+ unsigned long OrFlags(in unsigned long flags);
+ unsigned long AndFlags(in unsigned long flags);
+
+ /* various threading stuff */
+ attribute nsMsgKey threadId;
+ attribute nsMsgKey messageKey;
+ attribute nsMsgKey threadParent;
+
+ /* meta information about the message, learned from reading the message */
+ attribute unsigned long messageSize;
+ attribute unsigned long lineCount;
+ attribute unsigned long statusOffset;
+ /**
+ * The offset into the local folder/offline store of the message. This
+ * will be pluggable store-dependent, e.g., for mail dir it should
+ * always be 0.
+ */
+ attribute unsigned long long messageOffset;
+ attribute unsigned long offlineMessageSize;
+ /* common headers */
+ attribute PRTime date;
+ readonly attribute unsigned long dateInSeconds;
+ attribute string messageId;
+ attribute string ccList;
+ attribute string bccList;
+ attribute string author;
+ attribute string subject;
+ attribute string recipients;
+
+ /* anything below here still has to be fixed */
+ void setReferences(in string references);
+ readonly attribute unsigned short numReferences;
+ ACString getStringReference(in long refNum);
+
+ readonly attribute AString mime2DecodedAuthor;
+ readonly attribute AString mime2DecodedSubject;
+ readonly attribute AString mime2DecodedRecipients;
+
+ void getAuthorCollationKey(out unsigned long aCount,
+ [array, size_is(aCount)] out octet aKey);
+ void getSubjectCollationKey(out unsigned long aCount,
+ [array, size_is(aCount)] out octet aKey);
+ void getRecipientsCollationKey(out unsigned long aCount,
+ [array, size_is(aCount)] out octet aKey);
+
+ attribute string Charset;
+ /**
+ * Returns the effective character set for the message (@ref Charset).
+ * If there is no specific set defined for the message or the
+ * characterSetOverride option is turned on for the folder it will return
+ * the effective character set of the folder instead.
+ */
+ readonly attribute ACString effectiveCharset;
+ attribute nsMsgLabelValue label;
+ attribute string accountKey;
+ readonly attribute nsIMsgFolder folder;
+
+ /// Enumerator for names of all database properties in the header.
+ readonly attribute nsIUTF8StringEnumerator propertyEnumerator;
+};
+/* *******************************************************************************/
+
diff --git a/mailnews/base/public/nsIMsgIdentity.idl b/mailnews/base/public/nsIMsgIdentity.idl
new file mode 100644
index 000000000..88298bd04
--- /dev/null
+++ b/mailnews/base/public/nsIMsgIdentity.idl
@@ -0,0 +1,250 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIFile.idl"
+
+/**
+ * This interface contains all the personal outgoing mail information
+ * for a given person.
+ * Each identity is identified by a key, which is the <id> string in
+ * the identity preferences, such as in mail.identity.<id>.replyTo.
+ */
+[scriptable, uuid(9dede9a0-f6fc-4afc-8fc9-a6af52414b3d)]
+interface nsIMsgIdentity : nsISupports {
+ /**
+ * Internal preferences ID.
+ */
+ attribute ACString key;
+
+ /**
+ * Label describing this identity. May be empty.
+ */
+ attribute AString label;
+
+ /**
+ * Pretty display name to identify this specific identity. Will return a
+ * composed string like "fullname <email> (label)".
+ */
+ readonly attribute AString identityName;
+
+ /**
+ * User's full name, i.e. John Doe.
+ */
+ attribute AString fullName;
+
+ /**
+ * User's e-mail address, i.e. john@doe.com.
+ */
+ attribute ACString email;
+
+ /**
+ * Formats fullName and email into the proper string to use as sender:
+ * name <email>
+ */
+ readonly attribute AString fullAddress;
+
+ /**
+ * Optional replyTo address, i.e. johnNOSPAM@doe.com.
+ */
+ attribute AUTF8String replyTo;
+
+ /**
+ * Optional organization.
+ */
+ attribute AString organization;
+
+ /**
+ * Should we compose with HTML by default?
+ */
+ attribute boolean composeHtml;
+
+ /**
+ * Should we attach a signature from file?
+ */
+ attribute boolean attachSignature;
+
+ /**
+ * Should we attach a vcard by default?
+ */
+ attribute boolean attachVCard;
+
+ /**
+ * Should we automatically quote the original message?
+ */
+ attribute boolean autoQuote;
+
+ /**
+ * What should our quoting preference be?
+ */
+ attribute long replyOnTop;
+
+ /**
+ * Should our signature be at the end of the quoted text when replying
+ * above it?
+ */
+ attribute boolean sigBottom;
+
+ /**
+ * Include a signature when forwarding a message?
+ */
+ attribute boolean sigOnForward;
+
+ /**
+ * Include a signature when replying to a message?
+ */
+ attribute boolean sigOnReply;
+
+ /**
+ * The current signature file.
+ */
+ attribute nsIFile signature;
+
+ /**
+ * Modification time of the signature file.
+ */
+ attribute long signatureDate;
+
+ /**
+ * Signature text if not read from file; format depends on htmlSigFormat.
+ */
+ attribute AString htmlSigText;
+
+ /**
+ * Does htmlSigText contain HTML? Use plain text if false.
+ */
+ attribute boolean htmlSigFormat;
+
+ /**
+ * Suppress the double-dash signature separator
+ */
+ attribute boolean suppressSigSep;
+
+ /**
+ * The encoded string representing the vcard.
+ */
+ attribute ACString escapedVCard;
+
+ attribute boolean doFcc;
+ /// URI for the fcc (Sent) folder
+ attribute ACString fccFolder;
+ attribute boolean fccReplyFollowsParent;
+
+ /**
+ * @{
+ * these attributes control whether the special folder pickers for
+ * fcc, drafts,archives, and templates are set to pick between servers
+ * (e.g., Sent on accountName) or to pick any folder on any account.
+ * "0" means choose between servers; "1" means use the full folder picker.
+ */
+ attribute ACString fccFolderPickerMode;
+ attribute ACString draftsFolderPickerMode;
+ attribute ACString archivesFolderPickerMode;
+ attribute ACString tmplFolderPickerMode;
+ /** @} */
+
+ // Don't call bccSelf, bccOthers, and bccList directly, they are
+ // only used for migration and backward compatability. Use doBcc
+ // and doBccList instead.
+ attribute boolean bccSelf;
+ attribute boolean bccOthers;
+ attribute ACString bccList;
+
+ attribute boolean doCc;
+ attribute AUTF8String doCcList;
+
+ attribute boolean doBcc;
+ attribute AUTF8String doBccList;
+ /**
+ * @{
+ * URIs for the special folders (drafts, templates, archive)
+ */
+ attribute ACString draftFolder;
+ attribute ACString archiveFolder;
+ attribute ACString stationeryFolder;
+ /** @} */
+
+ attribute boolean archiveEnabled;
+ /**
+ * @{
+ * This attribute and constants control the granularity of sub-folders of the
+ * Archives folder - either messages go in the single archive folder, or a
+ * yearly archive folder, or in a monthly archive folder with a yearly
+ * parent folder. If the server doesn't support folders that both contain
+ * messages and have sub-folders, we will ignore this setting.
+ */
+ attribute long archiveGranularity;
+ const long singleArchiveFolder = 0;
+ const long perYearArchiveFolders = 1;
+ const long perMonthArchiveFolders = 2;
+ /// Maintain the source folder name when creating Archive subfolders
+ attribute boolean archiveKeepFolderStructure;
+ /** @} */
+
+ attribute boolean showSaveMsgDlg;
+ attribute ACString directoryServer;
+ attribute boolean overrideGlobalPref;
+ /**
+ * If this is false, don't append the user's domain
+ * to an autocomplete address with no matches
+ */
+ attribute boolean autocompleteToMyDomain;
+ /**
+ * valid determines if the UI should use this identity
+ * and the wizard uses this to determine whether or not
+ * to ask the user to complete all the fields
+ */
+ attribute boolean valid;
+
+ /**
+ * this is really dangerous. this destroys all pref values
+ * do not call this unless you know what you're doing!
+ */
+ void clearAllValues();
+
+ /**
+ * the preferred smtp server for this identity.
+ * if this is set, this the smtp server that should be used
+ * for the message send
+ */
+ attribute ACString smtpServerKey;
+
+ /**
+ * default request for return receipt option for this identity
+ * if this is set, the Return Receipt menu item on the compose
+ * window will be checked
+ */
+ readonly attribute boolean requestReturnReceipt;
+ readonly attribute long receiptHeaderType;
+
+ /**
+ * default request for DSN option for this identity
+ * if this is set, the DSN menu item on the compose
+ * window will be checked
+ */
+ readonly attribute boolean requestDSN;
+
+ /* copy the attributes of the identity we pass in */
+ void copy(in nsIMsgIdentity identity);
+
+ /**
+ * these generic getter / setters, useful for extending mailnews
+ * note, these attributes persist across sessions
+ */
+ AString getUnicharAttribute(in string name);
+ void setUnicharAttribute(in string name, in AString value);
+
+ ACString getCharAttribute(in string name);
+ void setCharAttribute(in string name, in ACString value);
+
+ boolean getBoolAttribute(in string name);
+ void setBoolAttribute(in string name, in boolean value);
+
+ long getIntAttribute(in string name);
+ void setIntAttribute(in string name, in long value);
+
+ /* useful for debugging */
+ AString toString();
+};
diff --git a/mailnews/base/public/nsIMsgIncomingServer.idl b/mailnews/base/public/nsIMsgIncomingServer.idl
new file mode 100644
index 000000000..a4349257d
--- /dev/null
+++ b/mailnews/base/public/nsIMsgIncomingServer.idl
@@ -0,0 +1,590 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgFolderCache;
+interface nsIMsgWindow;
+interface nsIMsgProtocolInfo;
+interface nsIMsgFilterList;
+interface nsIMsgRetentionSettings;
+interface nsIMsgDownloadSettings;
+interface nsISpamSettings;
+interface nsIMsgFilterPlugin;
+interface nsIUrlListener;
+interface nsIMsgDBHdr;
+interface nsIFile;
+interface nsIURI;
+interface nsIMsgPluggableStore;
+
+/*
+ * Interface for incoming mail/news host
+ * this is the base interface for all mail server types (imap, pop, nntp, etc)
+ * often you will want to add extra interfaces that give you server-specific
+ * attributes and methods.
+ */
+[scriptable, uuid(aa9a3389-9dac-41f1-9ec5-18287cfaa47c)]
+interface nsIMsgIncomingServer : nsISupports {
+
+ /**
+ * internal pref key - guaranteed to be unique across all servers
+ */
+ attribute ACString key;
+
+ /**
+ * pretty name - should be "userid on hostname"
+ * if the pref is not set
+ */
+ attribute AString prettyName;
+
+ /**
+ * helper function to construct the pretty name in a server type
+ * specific way - e.g., mail for foo@test.com, news on news.mozilla.org
+ */
+ readonly attribute AString constructedPrettyName;
+
+ /**
+ * hostname of the server
+ */
+ attribute ACString hostName;
+
+ /**
+ * real hostname of the server (if server name is changed it's stored here)
+ */
+ attribute ACString realHostName;
+
+ /* port of the server */
+ attribute long port;
+
+ /**
+ * userid to log into the server
+ */
+ attribute ACString username;
+
+ /**
+ * real username of the server (if username is changed it's stored here)
+ */
+ attribute ACString realUsername;
+
+ /**
+ * protocol type, i.e. "pop3", "imap", "nntp", "none", etc
+ * used to construct URLs
+ */
+ attribute ACString type;
+
+ /**
+ * The proper instance of nsIMsgProtocolInfo corresponding to this server type.
+ */
+ readonly attribute nsIMsgProtocolInfo protocolInfo;
+
+ readonly attribute AString accountManagerChrome;
+
+ /**
+ * The schema for the local mail store, such as "mailbox", "imap", or "news"
+ * used to construct URIs. The contractID for the nsIMsgMessageService
+ * implementation that will manage access to messages associated with this
+ * server is constructed using this type.
+ */
+ readonly attribute ACString localStoreType;
+
+ /**
+ * The schema for the nsIMsgDatabase implementation, such as "mailbox" or
+ * "imap", that will be used to construct the database instance used by
+ * message folders associated with this server.
+ */
+ readonly attribute ACString localDatabaseType;
+
+ // Perform specific tasks (reset flags, remove files, etc) for account user/server name changes.
+ void onUserOrHostNameChanged(in ACString oldName, in ACString newName,
+ in bool hostnameChanged);
+
+ /* cleartext version of the password */
+ attribute ACString password;
+
+ /**
+ * Attempts to get the password first from the password manager, if that
+ * fails it will attempt to get it from the user if aMsgWindow is supplied.
+ *
+ * @param aPromptString The text of the prompt if the user is prompted for
+ * password.
+ * @param aPromptTitle The title of the prompt if the user is prompted.
+ * @param aMsgWindow A message window to associate the prompt with.
+ * @return The obtained password. Could be an empty password.
+ *
+ * @exception NS_ERROR_FAILURE The password could not be obtained.
+ *
+ * @note NS_MSG_PASSWORD_PROMPT_CANCELLED is a success code that is returned
+ * if the prompt was presented to the user but the user cancelled the
+ * prompt.
+ */
+ ACString getPasswordWithUI(in AString aPromptString, in AString aPromptTitle,
+ in nsIMsgWindow aMsgWindow);
+
+ /* forget the password in memory and in single signon database */
+ void forgetPassword();
+
+ /* forget the password in memory which is cached for the session */
+ void forgetSessionPassword();
+
+ /* should we download whole messages when biff goes off? */
+ attribute boolean downloadOnBiff;
+
+ /* should we biff the server? */
+ attribute boolean doBiff;
+
+ /* how often to biff */
+ attribute long biffMinutes;
+
+ /* current biff state */
+ attribute unsigned long biffState;
+
+ /* are we running a url as a result of biff going off? (different from user clicking get msg) */
+ attribute boolean performingBiff;
+
+ /* the on-disk path to message storage for this server */
+ attribute nsIFile localPath;
+
+ /// message store to use for the folders under this server.
+ readonly attribute nsIMsgPluggableStore msgStore;
+
+ /* the RDF URI for the root mail folder */
+ readonly attribute ACString serverURI;
+
+ /* the root folder for this server, even if server is deferred */
+ attribute nsIMsgFolder rootFolder;
+
+ /* root folder for this account
+ - if account is deferred, root folder of deferred-to account */
+ readonly attribute nsIMsgFolder rootMsgFolder;
+
+ /* are we already getting new Messages on the current server..
+ This is used to help us prevent multiple get new msg commands from
+ going off at the same time. */
+ attribute boolean serverBusy;
+
+ /**
+ * Is the server using a secure channel (SSL or STARTTLS).
+ */
+ readonly attribute boolean isSecure;
+
+ /**
+ * Authentication mechanism.
+ *
+ * @see nsMsgAuthMethod (in MailNewsTypes2.idl)
+ * Same as "mail.server...authMethod" pref
+ */
+ attribute nsMsgAuthMethodValue authMethod;
+
+ /**
+ * Whether to SSL or STARTTLS or not
+ *
+ * @see nsMsgSocketType (in MailNewsTypes2.idl)
+ * Same as "mail.server...socketType" pref
+ */
+ attribute nsMsgSocketTypeValue socketType;
+
+ /* empty trash on exit */
+ attribute boolean emptyTrashOnExit;
+
+ /**
+ * Get the server's list of filters.
+ *
+ * This SHOULD be the same filter list as the root folder's, if the server
+ * supports per-folder filters. Furthermore, this list SHOULD be used for all
+ * incoming messages.
+ *
+ * Since the returned nsIMsgFilterList is mutable, it is not necessary to call
+ * setFilterList after the filters have been changed.
+ *
+ * @param aMsgWindow @ref msgwindow "The standard message window"
+ * @return The list of filters.
+ */
+ nsIMsgFilterList getFilterList(in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Set the server's list of filters.
+ *
+ * Note that this does not persist the filter list. To change the contents
+ * of the existing filters, use getFilterList and mutate the values as
+ * appopriate.
+ *
+ * @param aFilterList The new list of filters.
+ */
+ void setFilterList(in nsIMsgFilterList aFilterList);
+
+ /**
+ * Get user editable filter list. This does not have to be the same as
+ * the filterlist above, typically depending on the users preferences.
+ * The filters in this list are not processed, but only to be edited by
+ * the user.
+ * @see getFilterList
+ *
+ * @param aMsgWindow @ref msgwindow "The standard message window"
+ * @return The list of filters.
+ */
+ nsIMsgFilterList getEditableFilterList(in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Set user editable filter list.
+ * This does not persist the filterlist, @see setFilterList
+ * @see getEditableFilterList
+ * @see setFilterList
+ *
+ * @param aFilterList The new list of filters.
+ */
+ void setEditableFilterList(in nsIMsgFilterList aFilterList);
+
+ /* we use this to set the default local path. we use this when migrating prefs */
+ void setDefaultLocalPath(in nsIFile aDefaultLocalPath);
+
+ /**
+ * Verify that we can logon
+ *
+ * @param aUrlListener - gets called back with success or failure.
+ * @param aMsgWindow nsIMsgWindow to use for notification callbacks.
+ * @return - the url that we run.
+ */
+ nsIURI verifyLogon(in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow);
+
+ /* do a biff */
+ void performBiff(in nsIMsgWindow aMsgWindow);
+
+ /* get new messages */
+ void getNewMessages(in nsIMsgFolder aFolder, in nsIMsgWindow aMsgWindow,
+ in nsIUrlListener aUrlListener);
+ /* this checks if a server needs a password to do biff */
+ readonly attribute boolean serverRequiresPasswordForBiff;
+
+ /* this gets called when the server is expanded in the folder pane */
+ void performExpand(in nsIMsgWindow aMsgWindow);
+
+ /* Write out all known folder data to panacea.dat */
+ void writeToFolderCache(in nsIMsgFolderCache folderCache);
+
+ /* close any server connections */
+ void closeCachedConnections();
+
+ /* ... */
+ void shutdown();
+
+ /**
+ * Get or set the value as determined by the preference tree.
+ *
+ * These methods MUST NOT fail if the preference is not set, and therefore
+ * they MUST have a default value. This default value is provided in practice
+ * by use of a default preference tree. The standard format for the pref
+ * branches are <tt>mail.server.<i>key</i>.</tt> for per-server preferences,
+ * such that the preference is <tt>mail.server.<i>key</i>.<i>attr</i></tt>.
+ *
+ * The attributes are passed in as strings for ease of access by the C++
+ * consumers of this method.
+ *
+ * @param attr The value for which the preference should be accessed.
+ * @param value The value of the preference to set.
+ * @return The value of the preference.
+ * @{
+ */
+ boolean getBoolValue(in string attr);
+ void setBoolValue(in string attr, in boolean value);
+
+ ACString getCharValue(in string attr);
+ void setCharValue(in string attr, in ACString value);
+
+ AString getUnicharValue(in string attr);
+ void setUnicharValue(in string attr, in AString value);
+
+ long getIntValue(in string attr);
+ void setIntValue(in string attr, in long value);
+ /** @} */
+
+ /**
+ * Get or set the value as determined by the preference tree.
+ *
+ * These methods MUST NOT fail if the preference is not set, and therefore
+ * they MUST have a default value. This default value is provided in practice
+ * by use of a default preference tree. The standard format for the pref
+ * branches are <tt>mail.server.<i>key</i>.</tt> for per-server preferences,
+ * such that the preference is <tt>mail.server.<i>key</i>.<i>attr</i></tt>.
+ *
+ * The attributes are passed in as strings for ease of access by the C++
+ * consumers of this method.
+ *
+ * There are two preference names on here for legacy reasons, where the first
+ * is the name which will be using a (preferred) relative preference and the
+ * second a deprecated absolute preference. Implementations that do not have
+ * to worry about supporting legacy preferences can safely ignore this second
+ * parameter. Callers must still provide a valid value, though.
+ *
+ * @param relpref The name of the relative file preference.
+ * @param absref The name of the absolute file preference.
+ * @param aValue The value of the preference to set.
+ * @return The value of the preference.
+ * @{
+ */
+ nsIFile getFileValue(in string relpref, in string abspref);
+ void setFileValue(in string relpref, in string abspref, in nsIFile aValue);
+ /** @} */
+
+ /**
+ * this is really dangerous. this destroys all pref values
+ * do not call this unless you know what you're doing!
+ */
+ void clearAllValues();
+
+ /**
+ * This is also very dangerous. This will low-level remove the files
+ * associated with this server on disk. It does not notify any listeners.
+ */
+ void removeFiles();
+
+ attribute boolean valid;
+
+ AString toString();
+
+ void displayOfflineMsg(in nsIMsgWindow aWindow);
+
+ /* used for comparing nsIMsgIncomingServers */
+ boolean equals(in nsIMsgIncomingServer server);
+
+ /* Get Messages at startup */
+ readonly attribute boolean downloadMessagesAtStartup;
+
+ /* check to this if the server supports filters */
+ attribute boolean canHaveFilters;
+
+ /**
+ * can this server be removed from the account manager? for
+ * instance, local mail is not removable, but an imported folder is
+ */
+ attribute boolean canDelete;
+
+ attribute boolean loginAtStartUp;
+
+ attribute boolean limitOfflineMessageSize;
+ attribute long maxMessageSize;
+
+ attribute nsIMsgRetentionSettings retentionSettings;
+
+ /* check if this server can be a default server */
+ readonly attribute boolean canBeDefaultServer;
+
+ /* check if this server allows search operations */
+ readonly attribute boolean canSearchMessages;
+
+ /* check if this server allows canEmptyTrashOnExit operations */
+ readonly attribute boolean canEmptyTrashOnExit;
+
+ /* display startup page once per account per session */
+ attribute boolean displayStartupPage;
+ attribute nsIMsgDownloadSettings downloadSettings;
+
+ /*
+ * Offline support level. Support level can vary based on abilities
+ * and features each server can offer wrt to offline service.
+ * Here is the legend to determine the each support level details
+ *
+ * supportLevel == 0 --> no offline support (default)
+ * supportLevel == 10 --> regular offline feature support
+ * supportLevel == 20 --> extended offline feature support
+ *
+ * Each server can initialize itself to the support level if needed
+ * to override the default choice i.e., no offline support.
+ *
+ * POP3, None and Movemail will default to 0.
+ * IMAP level 10 and NEWS with level 20.
+ *
+ */
+ attribute long offlineSupportLevel;
+
+ /* create pretty name for migrated accounts */
+ AString generatePrettyNameForMigration();
+
+ /* does this server have disk space settings? */
+ readonly attribute boolean supportsDiskSpace;
+
+ /**
+ * Hide this server/account from the UI - used for smart mailboxes.
+ * The server can be retrieved from the account manager by name using the
+ * various Find methods, but nsIMsgAccountManager's GetAccounts and
+ * GetAllServers methods won't return the server/account.
+ */
+ attribute boolean hidden;
+
+ /**
+ * If the server supports Fcc/Sent/etc, default prefs can point to
+ * the server. Otherwise, copies and folders prefs should point to
+ * Local Folders.
+ *
+ * By default this value is set to true via global pref 'allows_specialfolders_usage'
+ * (mailnews.js). For Nntp, the value is overridden to be false.
+ * If ISPs want to modify this value, they should do that in their rdf file
+ * by using this attribute. Please look at mozilla/mailnews/base/ispdata/aol.rdf for
+ * usage example.
+ */
+ attribute boolean defaultCopiesAndFoldersPrefsToServer;
+
+ /* can this server allows sub folder creation */
+ attribute boolean canCreateFoldersOnServer;
+
+ /* can this server allows message filing ? */
+ attribute boolean canFileMessagesOnServer;
+
+ /* can this server allow compacting folders ? */
+ readonly attribute boolean canCompactFoldersOnServer;
+
+ /* can this server allow undo delete ? */
+ readonly attribute boolean canUndoDeleteOnServer;
+
+ /* used for setting up the filter UI */
+ readonly attribute nsMsgSearchScopeValue filterScope;
+
+ /* used for setting up the search UI */
+ readonly attribute nsMsgSearchScopeValue searchScope;
+
+ /**
+ * If the password for the server is available either via authentication
+ * in the current session or from password manager stored entries, return
+ * false. Otherwise, return true. If password is obtained from password
+ * manager, set the password member variable.
+ */
+ readonly attribute boolean passwordPromptRequired;
+
+ /**
+ * for mail, this configures both the MDN filter, and the server-side
+ * spam filter filters, if needed.
+ *
+ * If we have set up to filter return receipts into
+ * our Sent folder, this utility method creates
+ * a filter to do that, and adds it to our filterList
+ * if it doesn't exist. If it does, it will enable it.
+ *
+ * this is not used by news filters (yet).
+ */
+ void configureTemporaryFilters(in nsIMsgFilterList filterList);
+
+ /**
+ * If Sent folder pref is changed we need to clear the temporary
+ * return receipt filter so that the new return receipt filter can
+ * be recreated (by ConfigureTemporaryReturnReceiptsFilter()).
+ */
+ void clearTemporaryReturnReceiptsFilter();
+
+ /**
+ * spam settings
+ */
+ readonly attribute nsISpamSettings spamSettings;
+ readonly attribute nsIMsgFilterPlugin spamFilterPlugin;
+
+ nsIMsgFolder getMsgFolderFromURI(in nsIMsgFolder aFolderResource, in ACString aURI);
+
+ /// Indicates if any other server has deferred storage to this account.
+ readonly attribute boolean isDeferredTo;
+
+ const long keepDups = 0;
+ const long deleteDups = 1;
+ const long moveDupsToTrash = 2;
+ const long markDupsRead = 3;
+
+ attribute long incomingDuplicateAction;
+
+ // check if new hdr is a duplicate of a recently arrived header
+ boolean isNewHdrDuplicate(in nsIMsgDBHdr aNewHdr);
+
+ /**
+ * Set a boolean to force an inherited propertyName to return empty instead
+ * of inheriting from a parent folder, server, or the global
+ *
+ * @param propertyName The name of the property
+ * @param aForcePropertyEmpty true if an empty inherited property should be returned
+ */
+ void setForcePropertyEmpty(in string propertyName, in boolean aForcePropertyEmpty);
+
+ /**
+ * Get a boolean to force an inherited propertyName to return empty instead
+ * of inheriting from a parent folder, server, or the global
+ *
+ * @param propertyName The name of the property
+ *
+ * @return true if an empty inherited property should be returned
+ */
+ boolean getForcePropertyEmpty(in string propertyName);
+
+ /**
+ * Return the order in which this server type should appear in the folder pane.
+ * This sort order is a number between 100000000 and 900000000 so that RDF can
+ * use it as a string.
+ * The current return values are these:
+ * 0 = default account, 100000000 = mail accounts (POP3/IMAP4),
+ * 200000000 = Local Folders, 300000000 = IM accounts,
+ * 400000000 = RSS, 500000000 = News
+ * If a new server type is created a TB UI reviewer must decide its sort order.
+ */
+ readonly attribute long sortOrder;
+};
+
+%{C++
+/*
+ * Following values for offline support have been used by
+ * various files. If you are modifying any of the values
+ * below, please do take care of the following files.
+ * - mozilla/mailnews/base/src/nsMsgAccountManagerDS.cpp
+ * - mozilla/mailnews/base/util/nsMsgIncomingServer.cpp
+ * - mozilla/mailnews/imap/src/nsImapIncomingServer.cpp
+ * - mozilla/mailnews/local/src/nsPop3IncomingServer.cpp
+ * - mozilla/mailnews/news/src/nsNntpIncomingServer.cpp
+ * - mozilla/mailnews/base/content/msgAccountCentral.js
+ * - mozilla/modules/libpref/src/init/mailnews.js
+ * - ns/modules/libpref/src/init/mailnews-ns.js
+ * - ns/mailnews/base/ispdata/aol.rdf
+ * - ns/mailnews/base/ispdata/nswebmail.rdf
+ */
+#define OFFLINE_SUPPORT_LEVEL_NONE 0
+#define OFFLINE_SUPPORT_LEVEL_REGULAR 10
+#define OFFLINE_SUPPORT_LEVEL_EXTENDED 20
+#define OFFLINE_SUPPORT_LEVEL_UNDEFINED -1
+
+// Value when no port setting is found
+#define PORT_NOT_SET -1
+
+/* some useful macros to implement nsIMsgIncomingServer accessors */
+#define NS_IMPL_SERVERPREF_STR(_class, _postfix, _prefname) \
+NS_IMETHODIMP \
+_class::Get##_postfix(nsACString& retval) \
+{ \
+ return GetCharValue(_prefname, retval); \
+} \
+NS_IMETHODIMP \
+_class::Set##_postfix(const nsACString& chvalue) \
+{ \
+ return SetCharValue(_prefname, chvalue); \
+}
+
+#define NS_IMPL_SERVERPREF_BOOL(_class, _postfix, _prefname)\
+NS_IMETHODIMP \
+_class::Get##_postfix(bool *retval) \
+{ \
+ return GetBoolValue(_prefname, retval); \
+} \
+NS_IMETHODIMP \
+_class::Set##_postfix(bool bvalue) \
+{ \
+ return SetBoolValue(_prefname, bvalue); \
+}
+
+#define NS_IMPL_SERVERPREF_INT(_class, _postfix, _prefname)\
+NS_IMETHODIMP \
+_class::Get##_postfix(int32_t *retval) \
+{ \
+ return GetIntValue(_prefname, retval); \
+} \
+NS_IMETHODIMP \
+_class::Set##_postfix(int32_t ivalue) \
+{ \
+ return SetIntValue(_prefname, ivalue); \
+}
+
+%}
diff --git a/mailnews/base/public/nsIMsgKeyArray.idl b/mailnews/base/public/nsIMsgKeyArray.idl
new file mode 100644
index 000000000..183195440
--- /dev/null
+++ b/mailnews/base/public/nsIMsgKeyArray.idl
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/**
+ * This interface wraps an nsTArray<nsMsgKey> so that we can pass arrays
+ * back and forth between c++ and js (or via xpconnect generally).
+ */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+[scriptable, uuid(4aa3ed2e-5bb1-40b3-ad03-2f1cfef386c4)]
+interface nsIMsgKeyArray : nsISupports {
+ /**
+ * Get the key at the specified 0-based array index.
+ *
+ * @param aIndex 0-based index.
+ * @returns key at the specified index.
+ */
+ nsMsgKey getKeyAt(in long aIndex);
+
+ readonly attribute unsigned long length;
+
+ void setCapacity(in unsigned long aCapacity);
+ /**
+ * Adds a key to the end of the array
+ * @param key to append to the array.
+ */
+ void appendElement(in nsMsgKey aMsgKey);
+
+ /**
+ * Inserts a key into a previously sorted array
+ * @param key to insert into the array.
+ */
+ void insertElementSorted(in nsMsgKey aMsgKey);
+
+ /**
+ * Sort the array by key.
+ */
+ void sort();
+
+ /**
+ * Retrieves the entire array in such a way that xpconnect can easily
+ * create a js array of the keys.
+ *
+ * @returns array of the keys
+ */
+ void getArray(out unsigned long aCount,
+ [array, size_is(aCount)] out nsMsgKey aKeys);
+};
+
diff --git a/mailnews/base/public/nsIMsgMailNewsUrl.idl b/mailnews/base/public/nsIMsgMailNewsUrl.idl
new file mode 100644
index 000000000..e0ce7f07d
--- /dev/null
+++ b/mailnews/base/public/nsIMsgMailNewsUrl.idl
@@ -0,0 +1,201 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIURL.idl"
+
+interface nsIFile;
+interface nsIUrlListener;
+interface nsIMsgStatusFeedback;
+interface nsIMsgIncomingServer;
+interface nsIMsgWindow;
+interface nsILoadGroup;
+interface nsIMsgSearchSession;
+interface nsICacheEntry;
+interface nsIMimeHeaders;
+interface nsIStreamListener;
+interface nsIMsgFolder;
+interface nsIMsgHeaderSink;
+interface nsIMsgDBHdr;
+interface nsIDocShellLoadInfo;
+interface nsIDocShell;
+
+[scriptable, uuid(995455ba-5bb4-4643-8d70-2b877a2e1320)]
+interface nsIMsgMailNewsUrl : nsIURL {
+ ///////////////////////////////////////////////////////////////////////////////
+ // Eventually we'd like to push this type of functionality up into nsIURI.
+ // The idea is to allow the "application" (the part of the code which wants to
+ // run a url in order to perform some action) to register itself as a listener
+ // on url. As a url listener, the app will be informed when the url begins to run
+ // and when the url is finished.
+ ////////////////////////////////////////////////////////////////////////////////
+ void RegisterListener (in nsIUrlListener aUrlListener);
+ void UnRegisterListener (in nsIUrlListener aUrlListener);
+
+ readonly attribute nsIURI baseURI;
+
+ // if you really want to know what the current state of the url is (running or not
+ // running) you should look into becoming a urlListener...
+ void SetUrlState(in boolean runningUrl, in nsresult aStatusCode);
+ void GetUrlState(out boolean runningUrl);
+
+ // These are used by cloneInternal to determine the disposition of the uri
+ // ref when cloning. They should match the RefHandlingEnum enums defined,
+ // for example, in nsStandardURI.h but are not strictly required to do so.
+
+ /// Ignore the ref field, replacing with blank.
+ const unsigned long IGNORE_REF = 0;
+
+ /// Keep the existing ref field.
+ const unsigned long HONOR_REF = 1;
+
+ /// Replace the ref field with a new value.
+ const unsigned long REPLACE_REF = 2;
+
+ /**
+ * The general clone method that other clone methods use.
+ *
+ * This class should NOT be called except from classes that override
+ * a base class.
+ *
+ * @param aRefHandlingMode one of the Ref constants defined above.
+ * @param aNewRef the new reference, if needed.
+ * @return a cloned version of the current object.
+ */
+ nsIURI cloneInternal(in unsigned long aRefHandlingMode, in AUTF8String aNewRef);
+
+ readonly attribute nsIMsgIncomingServer server;
+
+ /**
+ * The folder associated with this url.
+ *
+ * @exception NS_ERROR_FAILURE May be thrown if the url does not
+ * relate to a folder, e.g. standalone
+ * .eml messages.
+ */
+ attribute nsIMsgFolder folder;
+
+ attribute nsIMsgStatusFeedback statusFeedback;
+
+ /**
+ * The maximum progress for this URL. This might be a count, or it might
+ * be a number of bytes. A value of -1 indicates that this is unknown.
+ */
+ attribute long long maxProgress;
+
+ attribute nsIMsgWindow msgWindow;
+
+ // current mime headers if reading message
+ attribute nsIMimeHeaders mimeHeaders;
+
+ // the load group is computed from the msgWindow
+ readonly attribute nsILoadGroup loadGroup;
+
+ // search session, if we're running a search.
+ attribute nsIMsgSearchSession searchSession;
+ attribute boolean updatingFolder;
+ attribute boolean msgIsInLocalCache;
+ attribute boolean suppressErrorMsgs; // used to avoid displaying biff error messages
+
+ attribute nsICacheEntry memCacheEntry;
+
+ const unsigned long eCopy = 0;
+ const unsigned long eMove = 1;
+ const unsigned long eDisplay = 2;
+ boolean IsUrlType(in unsigned long type);
+ nsIStreamListener getSaveAsListener(in boolean addDummyEnvelope, in nsIFile aFile);
+
+ // typically the header sink is tied to the nsIMsgWindow, but in certain circumstances, a consumer
+ // may chose to provide its own header sink for this url
+ attribute nsIMsgHeaderSink msgHeaderSink;
+
+ /// Returns true if the URI is for a message (e.g., imap-message://)
+ readonly attribute boolean isMessageUri;
+
+ /**
+ * Loads the URI in a docshell. This will give priority to loading the
+ * URI in the passed-in docshell. If it can't be loaded there
+ * however, the URL dispatcher will go through its normal process of content
+ * loading.
+ *
+ * @param docshell The docshell that will consume the load.
+ *
+ * @param loadInfo This is the extended load info for this load. This
+ * most often will be null, but if you need to do
+ * additional setup for this load you can get a loadInfo
+ * object by calling createLoadInfo. Once you have this
+ * object you can set the needed properties on it and
+ * then pass it to loadURI.
+ *
+ * @param aLoadFlags Flags to modify load behaviour. Flags are defined in
+ * nsIWebNavigation. Note that using flags outside
+ * LOAD_FLAGS_MASK is only allowed if passing in a
+ * non-null loadInfo. And even some of those might not
+ * be allowed. Use at your own risk. See nsIDocShell.idl
+ */
+ void loadURI(in nsIDocShell docshell,
+ in nsIDocShellLoadInfo loadInfo,
+ in unsigned long aLoadFlags);
+
+};
+
+//////////////////////////////////////////////////////////////////////////////////
+// This is a very small interface which I'm grouping with the mailnewsUrl interface.
+// Several url types (mailbox, imap, nntp) have similar properties because they can
+// represent mail messages. For instance, these urls can be have URI
+// equivalents which represent a message.
+// We want to provide the app the ability to get the URI for the
+// url. This URI to URL mapping doesn't exist for all mailnews urls...hence I'm
+// grouping it into a separate interface...
+//////////////////////////////////////////////////////////////////////////////////
+
+[scriptable, uuid(388a37ec-2e1a-4a4f-9d8b-189bedf1bda2)]
+interface nsIMsgMessageUrl : nsISupports {
+ // get and set the RDF URI associated with the url. Note, not all urls have
+ // had uri's set on them so be prepared to handle cases where this string is empty.
+ attribute string uri;
+ // used by imap, pop and nntp in order to implement save message to disk
+ attribute nsIFile messageFile;
+ attribute boolean AddDummyEnvelope;
+ attribute boolean canonicalLineEnding;
+ attribute string originalSpec;
+
+ // This is used when creating a principal for the URL.
+ readonly attribute AUTF8String principalSpec;
+
+ /**
+ * A message db header for that message.
+ *
+ * @note This attribute is not guaranteed to be set, so callers that
+ * actually require an nsIMsgDBHdr will need to use the uri attribute
+ * on this interface to get the appropriate nsIMsgMessageService and
+ * then get the header from there.
+ */
+ attribute nsIMsgDBHdr messageHeader;
+};
+
+//////////////////////////////////////////////////////////////////////////////////
+// This is a very small interface which I'm grouping with the mailnewsUrl interface.
+// I want to isolate out all the I18N specific information that may be associated with
+// any given mailnews url. This gives I18N their own "sandbox" of routines they can add
+// and tweak as they see fit. For now it contains mostly charset information.
+//////////////////////////////////////////////////////////////////////////////////
+
+[scriptable, uuid(D71E0785-2862-11d4-98C1-001083010E9B)]
+interface nsIMsgI18NUrl : nsISupports {
+
+ /**
+ * the charset associated with a folder for this url.
+ *
+ * @exception NS_ERROR_FAILURE May be thrown if the url does not
+ * relate to a folder, e.g. standalone
+ * .eml messages.
+ */
+ readonly attribute string folderCharset;
+ readonly attribute boolean folderCharsetOverride;
+ // the charsetOverRide is a charset the user may have specified via the menu for
+ // a particular message
+ attribute string charsetOverRide;
+};
diff --git a/mailnews/base/public/nsIMsgMailSession.idl b/mailnews/base/public/nsIMsgMailSession.idl
new file mode 100644
index 000000000..ca3fe8a6e
--- /dev/null
+++ b/mailnews/base/public/nsIMsgMailSession.idl
@@ -0,0 +1,79 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/*
+ * The mail session is a replacement for the old 4.x MSG_Master object. It
+ * contains mail session generic information such as the account manager, etc
+ * I'm starting this off as an empty interface and as people feel they need to
+ * add more information to it, they can. I think this is a better approach
+ * than trying to port over the old MSG_Master in its entirety as that had a
+ * lot of cruft in it....
+ */
+
+#include "nsIFolderListener.idl"
+
+interface nsIFile;
+interface nsIMsgWindow;
+interface nsIMsgUserFeedbackListener;
+interface nsIMsgMailNewsUrl;
+
+[scriptable, uuid(577ead34-553e-4cd6-b484-76ff6662082d)]
+interface nsIMsgMailSession : nsISupports {
+ void Shutdown();
+
+ /**
+ * Adds a listener to be notified when folders update.
+ *
+ * @param aListener The listener to add.
+ * @param aNotifyFlags A combination of flags detailing on which operations
+ * to notify the listener. See nsIFolderListener.idl for
+ * details.
+ */
+ void AddFolderListener(in nsIFolderListener aListener,
+ in folderListenerNotifyFlagValue aNotifyFlags);
+ /**
+ * Removes a listener from the folder notification list.
+ *
+ * @param aListener The listener to remove.
+ */
+ void RemoveFolderListener(in nsIFolderListener aListener);
+
+ /**
+ * Adds a listener to be notified of alert or prompt style feedback that
+ * should go to the user.
+ *
+ * @param aListener The listener to add.
+ */
+ void addUserFeedbackListener(in nsIMsgUserFeedbackListener aListener);
+
+ /**
+ * Removes a user feedback listener.
+ *
+ * @param aListener The listener to remove.
+ */
+ void removeUserFeedbackListener(in nsIMsgUserFeedbackListener aListener);
+
+ /**
+ * Call to alert the listeners of the message. If there are no listeners,
+ * or the listeners do not handle the alert, then this function will present
+ * the user with a modal dialog if aMsgWindow isn't null.
+ *
+ * @param aMessage The localized message string to alert.
+ * @param aUrl Optional mailnews url which is relevant to the operation
+ * which caused the alert to be generated.
+ */
+ void alertUser(in AString aMessage, [optional] in nsIMsgMailNewsUrl aUrl);
+
+ readonly attribute nsIMsgWindow topmostMsgWindow;
+ void AddMsgWindow(in nsIMsgWindow msgWindow);
+ void RemoveMsgWindow(in nsIMsgWindow msgWindow);
+ boolean IsFolderOpenInWindow(in nsIMsgFolder folder);
+
+ string ConvertMsgURIToMsgURL(in string aURI, in nsIMsgWindow aMsgWindow);
+ nsIFile getDataFilesDir(in string dirName);
+};
+
diff --git a/mailnews/base/public/nsIMsgMdnGenerator.idl b/mailnews/base/public/nsIMsgMdnGenerator.idl
new file mode 100644
index 000000000..277975e71
--- /dev/null
+++ b/mailnews/base/public/nsIMsgMdnGenerator.idl
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgWindow;
+interface nsIMsgFolder;
+interface nsIMimeHeaders;
+
+typedef long EDisposeType;
+typedef long ReceiptHdrType;
+typedef long MDNIncorporateType;
+
+%{C++
+#define NS_MSGMDNGENERATOR_CONTRACTID \
+ "@mozilla.org/messenger-mdn/generator;1"
+
+#define NS_MSGMDNGENERATOR_CID \
+{ /* ec917b13-8f73-4d4d-9146-d7f7aafe9076 */ \
+ 0xec917b13, 0x8f73, 0x4d4d, \
+ { 0x91, 0x46, 0xd7, 0xf7, 0xaa, 0xfe, 0x90, 0x76 }}
+%}
+
+[scriptable, uuid(440EA3DE-DACA-4886-9875-84E6CD7D7927)]
+interface nsIMsgMdnGenerator : nsISupports
+{
+ const EDisposeType eDisplayed = 0;
+ const EDisposeType eDispatched = 1;
+ const EDisposeType eProcessed = 2;
+ const EDisposeType eDeleted = 3;
+ const EDisposeType eDenied = 4;
+ const EDisposeType eFailed = 5;
+
+ const ReceiptHdrType eDntType = 0;
+ const ReceiptHdrType eRrtType = 1;
+ const ReceiptHdrType eDntRrtType = 2;
+
+ const MDNIncorporateType eIncorporateInbox = 0;
+ const MDNIncorporateType eIncorporateSent = 1;
+
+
+ /**
+ * Prepare the sending of a mdn reply, and checks the prefs whether a
+ * reply should be send. Might send the message automatically if the
+ * prefs say it should.
+ * @param eType One of EDisposeType above, indicating the action that led
+ * to sending the mdn reply
+ * @param aWindow The window the message was displayed in, acting as parent
+ * for any (error) dialogs
+ * @param folder The folder the message is in
+ * @param key the message key
+ * @param headers the message headers
+ * @param autoAction true if the request action led to sending the mdn
+ * reply was an automatic action, false if it was user initiated
+ * @returns true if the user needs to be asked for permission
+ * false in other cases (whether the message was sent or denied)
+ */
+ boolean process(in EDisposeType eType, in nsIMsgWindow aWindow,
+ in nsIMsgFolder folder, in nsMsgKey key,
+ in nsIMimeHeaders headers, in boolean autoAction);
+
+ /**
+ * Must be called when the user was asked for permission and agreed to
+ * sending the mdn reply.
+ * May only be called when |process| returned |true|. Behaviour is
+ * unspecified in other cases
+ */
+ void userAgreed();
+
+ /**
+ * Must be called when the user was asked for permission and declined to
+ * send the mdn reply.
+ * Will mark the message so that the user won't be asked next time.
+ * May only be called when |process| returned |true|. Behaviour is
+ * unspecified in other cases.
+ */
+ void userDeclined();
+};
diff --git a/mailnews/base/public/nsIMsgMessageService.idl b/mailnews/base/public/nsIMsgMessageService.idl
new file mode 100644
index 000000000..38a2e2821
--- /dev/null
+++ b/mailnews/base/public/nsIMsgMessageService.idl
@@ -0,0 +1,259 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+interface nsIURI;
+interface nsIUrlListener;
+interface nsIStreamListener;
+interface nsIMsgWindow;
+interface nsIFile;
+interface nsIMsgFolder;
+interface nsIMsgSearchSession;
+interface nsIMsgDBHdr;
+interface nsIStreamConverter;
+interface nsICacheEntry;
+
+%{C++
+#include "MailNewsTypes.h"
+%}
+
+[scriptable, uuid(3aa7080a-73ac-4394-9636-fc00e182319b)]
+interface nsIMsgMessageService : nsISupports {
+
+ /**
+ * If you want a handle on the running task, pass in a valid nsIURI
+ * ptr. You can later interrupt this action by asking the netlib
+ * service manager to interrupt the url you are given back.
+ * Remember to release aURL when you are done with it. Pass nullptr
+ * in for aURL if you don't care about the returned URL.
+ */
+
+
+ /**
+ * Pass in the URI for the message you want to have copied.
+ *
+ * @param aSrcURI
+ * @param aCopyListener already knows about the destination folder.
+ * @param aMoveMessage TRUE if you want the message to be moved.
+ * FALSE leaves it as just a copy.
+ * @param aUrlListener
+ * @param aMsgWindow
+ * @param aURL
+ */
+ void CopyMessage(in string aSrcURI, in nsIStreamListener aCopyListener, in boolean aMoveMessage,
+ in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow, out nsIURI aURL);
+
+ /**
+ * Copy multiple messages at a time
+ *
+ * @param keys
+ * @param srcFolder
+ * @param aCopyListener
+ * @param aMoveMessage
+ * @param aUrlListener
+ * @param aMsgWindow
+ * @returns URI that's run to perform the copy
+ */
+ nsIURI CopyMessages(in unsigned long aNumKeys,
+ [array, size_is (aNumKeys)] in nsMsgKey aKeys,
+ in nsIMsgFolder srcFolder,
+ in nsIStreamListener aCopyListener,
+ in boolean aMoveMessage,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+
+ /**
+ * When you want a message displayed....
+ *
+ * @param aMessageURI Is a uri representing the message to display.
+ * @param aDisplayConsumer Is (for now) an nsIDocShell which we'll use to load
+ * the message into.
+ * XXXbz Should it be an nsIWebNavigation or something?
+ * @param aMsgWindow
+ * @param aUrlListener
+ * @param aCharsetOverride (optional) character set over ride to force the message to use.
+ * @param aURL
+ */
+ void DisplayMessage(in string aMessageURI,
+ in nsISupports aDisplayConsumer,
+ in nsIMsgWindow aMsgWindow,
+ in nsIUrlListener aUrlListener,
+ in string aCharsetOverride,
+ out nsIURI aURL);
+
+ /**
+ * When you want an attachment downloaded
+ *
+ * @param aContentType We need to know the content type of the attachment
+ * @param aFileName The name of the attachment.
+ * @param aUrl String representation of the original url associated with the msg.
+ * @param aMessageUri RDF resource that describes the message
+ * @param aDisplayConsumer
+ * @param aMsgWindow Message window
+ * @param aUrlListener
+ */
+ void openAttachment(in string aContentType,
+ in string aFileName,
+ in string aUrl,
+ in string aMessageUri,
+ in nsISupports aDisplayConsumer,
+ in nsIMsgWindow aMsgWindow,
+ in nsIUrlListener aUrlListener);
+
+ /**
+ * When you want to spool a message out to a file on disk.
+ * This is an asynch operation of course. You must pass in a
+ * url listener in order to figure out when the operation is done.
+ *
+ * @param aMessageURI The uri representing the message to spool out to disk.
+ * @param aFile The file you want the message saved to
+ * @param aGenerateDummyEnvelope Usually FALSE. Set to TRUE if you want the msg
+ * appended at the end of the file.
+ * @param aUrlListener
+ * @param aURL
+ * @param canonicalLineEnding
+ * @param aMsgWindow
+ */
+ void SaveMessageToDisk(in string aMessageURI, in nsIFile aFile,
+ in boolean aGenerateDummyEnvelope,
+ in nsIUrlListener aUrlListener, out nsIURI aURL,
+ in boolean canonicalLineEnding, in nsIMsgWindow aMsgWindow);
+
+ /**
+ * When you have a uri and you would like to convert that
+ * to a url which can be run through necko, you can use this method.
+ * the Uri MUST refer to a message and not a folder!
+ *
+ * @param aMessageURI A message uri to convert.
+ * @param aURL URL which can be run through necko.
+ * @param aMsgWindow
+ */
+ void GetUrlForUri(in string aMessageURI, out nsIURI aURL, in nsIMsgWindow aMsgWindow);
+
+ /**
+ * When you want a message displayed in a format that is suitable for printing....
+ *
+ * @param aMessageURI A uri representing the message to display.
+ * @param aDisplayConsumer is (for now) a nsIDocShell which we'll use to load
+ * the message into.
+ * XXXbz should it be an nsIWebNavigation?
+ * @param aMsgWindow
+ * @param aUrlListener
+ * @param aURL
+ */
+ void DisplayMessageForPrinting(in string aMessageURI, in nsISupports aDisplayConsumer,
+ in nsIMsgWindow aMsgWindow,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL);
+
+ /**
+ *
+ *
+ * @param aSearchSession
+ * @param aMsgWindow
+ * @param aMsgFolder
+ * @param aSearchUri
+ */
+ void Search(in nsIMsgSearchSession aSearchSession, in nsIMsgWindow aMsgWindow, in nsIMsgFolder aMsgFolder, in string aSearchUri);
+
+ /**
+ * This method streams a message to the passed in consumer. If aConvertData is true, it
+ * will create a stream converter from message rfc822 to star/star. It will also tack
+ * aAdditionalHeader onto the url (e.g., "header=filter").
+ *
+ * @param aMessageURI uri of message to stream
+ * @param aConsumer generally, a stream listener listening to the message
+ * @param aMsgWindow msgWindow for give progress and status feedback
+ * @param aUrlListener gets notified when url starts and stops
+ * @param aConvertData should we create a stream converter?
+ * @param aAdditionalHeader added to URI, e.g., "header=filter"
+ * @param aLocalOnly whether data should be retrieved only from local caches
+ * If streaming over the network is required and this is true, then
+ * an exception is thrown. This defaults to false.
+ *
+ * @note If we're offline, then even if aLocalOnly is false, we won't stream over the
+ * network
+ *
+ * @return the URL that gets run
+ */
+ nsIURI streamMessage(in string aMessageURI, in nsISupports aConsumer,
+ in nsIMsgWindow aMsgWindow,
+ in nsIUrlListener aUrlListener,
+ in boolean aConvertData,
+ in ACString aAdditionalHeader,
+ [optional] in boolean aLocalOnly);
+
+ /**
+ * This method streams a message's headers to the passed in consumer.
+ * This is for consumers who want a particular header but don't
+ * want to stream the whole message.
+ *
+ * @param aMessageURI uri of message whose headers we are to stream
+ * @param aConsumer a stream listener listening to the message
+ headers.
+ * @param aUrlListener gets notified when url starts and stops, if we run a url.
+ * @param aLocalOnly whether data should be retrieved only from local caches
+ * If streaming over the network is required and this is true, then
+ * an exception is thrown. This defaults to false.
+ *
+ * @note If we're offline, then even if aLocalOnly is false, we won't stream over the
+ * network
+ *
+ * @return the URL that gets run, if any.
+ */
+ nsIURI streamHeaders(in string aMessageURI, in nsIStreamListener aConsumer,
+ in nsIUrlListener aUrlListener,
+ [optional] in boolean aLocalOnly);
+
+ /**
+ * Determines whether a message is in the memory cache. Local folders
+ * don't implement this.
+ * The URL needs to address a message, not a message part, all query
+ * qualifiers will be stripped before looking up the entry in the cache.
+ *
+ * @param aUrl The URL of the message, possibly with an appropriate command in it
+ * @param aFolder The folder this message is in
+ *
+ * @return TRUE if the message is in mem cache; FALSE if it is not.
+ */
+ boolean isMsgInMemCache(in nsIURI aUrl,
+ in nsIMsgFolder aFolder);
+
+ /**
+ * now the the message datasource is going away
+ * we need away to go from message uri to go nsIMsgDBHdr
+ *
+ * @param uri A message uri to get nsIMsgDBHdr for.
+ *
+ * @return nsIMsgDBHdr for specified uri or null if failed.
+ */
+ nsIMsgDBHdr messageURIToMsgHdr(in string uri);
+};
+
+/**
+ * Some mail protocols (like imap) allow you to fetch individual mime parts. We use this interface
+ * to represent message services whose protocols support this. To use this interface, you should get
+ * the message service then QI for this interface. If it's present, then can fetch a mime part.
+ */
+[scriptable, uuid(3728C255-480C-11d4-98D0-001083010E9B)]
+interface nsIMsgMessageFetchPartService : nsISupports
+{
+ /**
+ * Used to fetch an individual mime part
+ *
+ * @param aURI url representing the message
+ * @param aMessageURI RDF URI including the part to fetch
+ * @param aDisplayConsumer
+ * @param aMsgWindow
+ * @param aUrlListener
+ *
+ * @return
+ */
+ nsIURI fetchMimePart(in nsIURI aURI, in string aMessageUri, in nsISupports aDisplayConsumer,
+ in nsIMsgWindow aMsgWindow,
+ in nsIUrlListener aUrlListener);
+};
diff --git a/mailnews/base/public/nsIMsgOfflineManager.idl b/mailnews/base/public/nsIMsgOfflineManager.idl
new file mode 100644
index 000000000..983075f2c
--- /dev/null
+++ b/mailnews/base/public/nsIMsgOfflineManager.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+// this is a service -there's only one Offline Manager, because you can only do one operation at a time
+// (go online or offline).
+
+interface nsIMsgWindow;
+
+[scriptable, uuid(5e885fec-09b0-11d5-a5bf-0060b0fc04b7)]
+interface nsIMsgOfflineManager : nsISupports
+{
+ attribute nsIMsgWindow window; // should be a progress window.
+ attribute boolean inProgress; // an online->offine or online->offline operation in progress.
+ // the offline menu should be disabled.
+ void goOnline(in boolean sendUnsentMessages, in boolean playbackOfflineImapOperations, in nsIMsgWindow aMsgWindow);
+ void synchronizeForOffline(in boolean downloadNews, in boolean downloadMail, in boolean sendUnsentMessages,
+ in boolean goOfflineWhenDone, in nsIMsgWindow aMsgWindow);
+};
+
diff --git a/mailnews/base/public/nsIMsgPluggableStore.idl b/mailnews/base/public/nsIMsgPluggableStore.idl
new file mode 100644
index 000000000..12713b236
--- /dev/null
+++ b/mailnews/base/public/nsIMsgPluggableStore.idl
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIMsgFolder;
+interface nsIMsgCopyServiceListener;
+interface nsIMsgDBHdr;
+interface nsIMsgWindow;
+interface nsIOutputStream;
+interface nsIInputStream;
+interface nsIArray;
+interface nsIUrlListener;
+interface nsIMsgDatabase;
+interface nsITransaction;
+
+[scriptable, uuid(F732CE58-E540-4dc4-B803-9456056EBEFC)]
+
+/**
+ * Pluggable message store interface. Each incoming server can have a different
+ * message store.
+ * All methods are synchronous unless otherwise specified.
+ */
+interface nsIMsgPluggableStore : nsISupports {
+ /**
+ * Examines the store and adds subfolders for the existing folders in the
+ * profile directory. aParentFolder->AddSubfolder is the normal way
+ * to register the subfolders. This method is expected to be synchronous.
+ * This shouldn't be confused with server folder discovery, which is allowed
+ * to be asynchronous.
+ *
+ * @param aParentFolder folder whose existing children we want to discover.
+ * This will be the root folder for the server object.
+ * @param aDeep true if we should discover all descendents. Would we ever
+ * not want to do this?
+ */
+
+ void discoverSubFolders(in nsIMsgFolder aParentFolder, in boolean aDeep);
+ /**
+ * Creates storage for a new, empty folder.
+ *
+ * @param aParent parent folder
+ * @param aFolderName leaf name of folder.
+ * @return newly created folder.
+ * @exception NS_MSG_FOLDER_EXISTS If the child exists.
+ * @exception NS_MSG_CANT_CREATE_FOLDER for other errors.
+ */
+ nsIMsgFolder createFolder(in nsIMsgFolder aParent, in AString aFolderName);
+
+ /**
+ * Delete storage for a folder and its subfolders, if any.
+ * This is a real delete, not a move to the trash folder.
+ *
+ * @param aFolder folder to delete
+ */
+ void deleteFolder(in nsIMsgFolder aFolder);
+
+ /**
+ * Rename storage for an existing folder.
+ *
+ * @param aFolder folder to rename
+ * @param aNewName name to give new folder
+ * @return the renamed folder object
+ */
+ nsIMsgFolder renameFolder(in nsIMsgFolder aFolder, in AString aNewName);
+
+ /**
+ * Tells if the store has the requested amount of space available in the
+ * specified folder.
+ *
+ * @param aFolder folder we want to add messages to.
+ * @param aSpaceRequested How many bytes we're trying to add to the store.
+ *
+ * The function returns an exception if there is not enough space to
+ * indicate the reason of the shortage:
+ * NS_ERROR_FILE_TOO_BIG = the store cannot grow further due to internal limits
+ * NS_ERROR_FILE_DISK_FULL = there is not enough space on the disk
+ */
+ boolean hasSpaceAvailable(in nsIMsgFolder aFolder,
+ in long long aSpaceRequested);
+
+ /**
+ * Move/Copy a folder to a new parent folder. This method is asynchronous.
+ * The store needs to use the aListener to notify the core code of the
+ * completion of the operation. And it must send the appropriate
+ * nsIMsgFolderNotificationService notifications.
+ *
+ * @param aSrcFolder folder to move/copy
+ * @param aDstFolder parent dest folder
+ * @param aIsMoveFolder true if move, false if copy. If move, source folder
+ * is deleted when copy completes.
+ * @param aMsgWindow used to display progress, may be null
+ * @param aListener - used to get notification when copy is done.
+ * @param aNewName Optional new name for the target folder.
+ * If rename is not needed, set this to empty string.
+ */
+ void copyFolder(in nsIMsgFolder aSrcFolder, in nsIMsgFolder aDstFolder,
+ in boolean aIsMoveFolder, in nsIMsgWindow aMsgWindow,
+ in nsIMsgCopyServiceListener aListener,
+ in AString aNewName);
+
+ /**
+ * Get an output stream for a message in a folder.
+ *
+ * @param aFolder folder to create a message output stream for.
+ * @param aNewHdr If aNewHdr is set on input, then this is probably for
+ * offline storage of an existing message. If null, the
+ * this is a newly downloaded message and the store needs
+ * to create a new header for the new message. If the db
+ * is invalid, this can be null. But if the db is valid,
+ * the store should create a message header with the right
+ * message key, or whatever other property it needs to set to
+ * be able to retrieve the message contents later. If the store
+ * needs to base any of this on the contents of the message,
+ * it will need remember the message header and hook into
+ * the output stream somehow to alter the message header.
+ * @param aReusable set to true on output if the caller can reuse the
+ * stream for multiple messages, e.g., mbox format.
+ * This means the caller will likely get the same stream
+ * back on multiple calls to this method, and shouldn't
+ * close the stream in between calls if they want reuse.
+ *
+ * @return The output stream to write to. The output stream will be positioned
+ * for writing (e.g., for berkeley mailbox, it will be at the end).
+ */
+ nsIOutputStream getNewMsgOutputStream(in nsIMsgFolder aFolder,
+ inout nsIMsgDBHdr aNewHdr,
+ out boolean aReusable);
+
+ /**
+ * Called when the current message is discarded, e.g., it is moved
+ * to an other folder as a filter action, or is deleted because it's
+ * a duplicate. This gives the berkeley mailbox store a chance to simply
+ * truncate the Inbox w/o leaving a deleted message in the store.
+ *
+ * @param aOutputStream stream we were writing the message to be discarded to
+ * @param aNewHdr header of message to discard
+ */
+ void discardNewMessage(in nsIOutputStream aOutputStream,
+ in nsIMsgDBHdr aNewHdr);
+
+ /**
+ * Must be called by code that calls getNewMsgOutputStream to finish
+ * the process of storing a new message, if the new msg has not been
+ * discarded. Could/should this be combined with discardNewMessage?
+ *
+ * @param aOutputStream stream we were writing the message to.
+ * @param aNewHdr header of message finished.
+ */
+ void finishNewMessage(in nsIOutputStream aOutputStream,
+ in nsIMsgDBHdr aNewHdr);
+
+ /**
+ * Called by pop3 message filters when a newly downloaded message is being
+ * moved by an incoming filter. This is called before finishNewMessage, and
+ * it allows the store to optimize that case.
+ *
+ * @param aNewHdr msg hdr of message being moved.
+ * @param aDestFolder folder to move message to, in the same store.
+ *
+ * @return true if successful, false if the store doesn't want to optimize
+ * this.
+ * @exception If the moved failed. values TBD
+ */
+ boolean moveNewlyDownloadedMessage(in nsIMsgDBHdr aNewHdr,
+ in nsIMsgFolder aDestFolder);
+
+ /**
+ * Get an input stream that we can read the contents of a message from.
+ * If the input stream is reusable, and the caller is going to ask
+ * for input streams for other messages in the folder, then the caller
+ * should not close the stream until it is done with its messages.
+ *
+ * @param aMsgFolder Folder containing the message
+ * @param aMsgToken token that identifies message. This is store-dependent,
+ * and must be set as a string property "storeToken" on the
+ * message hdr by the store when the message is added
+ * to the store.
+ * @param aOffset offset in the returned stream of the message.
+ * @param[optional] aHdr msgHdr to use in case storeToken is not set. This is
+ * for upgrade from existing profiles.
+ * @param[optional] aReusable Is the returned stream re-usable for other
+ * messages' input streams?
+ */
+ nsIInputStream getMsgInputStream(in nsIMsgFolder aFolder,
+ in ACString aMsgToken,
+ out long long aOffset,
+ [optional] in nsIMsgDBHdr aHdr,
+ [optional] out boolean aReusable);
+
+ /**
+ * Delete the passed in messages. These message should all be in the
+ * same folder.
+ * @param aHdrArray array of nsIMsgDBHdr's.
+ */
+ void deleteMessages(in nsIArray aHdrArray);
+
+ /**
+ * This allows the store to handle a msg move/copy if it wants. This lets
+ * it optimize move/copies within the same store. E.g., for maildir, a
+ * msg move mostly entails moving the file containing the message, and
+ * updating the db. If the store does not want to implement this, the core
+ * code will use getMsgInputStream on the source message,
+ * getNewMsgOutputStream for the dest message, and stream the input to
+ * the output. This operation can be asynchronous.
+ * If the store does the copy, it must return the appropriate undo action,
+ * which can be store dependent. And it must send the appropriate
+ * nsIMsgFolderNotificationService notifications.
+ *
+ * @param isMove true if this is a move, false if it is a copy.
+ * @param aHdrArray array of nsIMsgDBHdr's, all in the same folder
+ * @param aDstFolder folder to move/copy the messages to.
+ * @param aListener listener to notify of copy status.
+ * @param aDstHdrs array of nsIMsgDBHdr's in the destination folder.
+ * @param[out,optional] aUndoAction transaction to provide undo, if
+ * the store does the copy itself.
+ * @return true if messages were copied, false if the core code should
+ * do the copy.
+ */
+ boolean copyMessages(in boolean isMove,
+ in nsIArray aHdrArray,
+ in nsIMsgFolder aDstFolder,
+ in nsIMsgCopyServiceListener aListener,
+ out nsIArray aDstHdrs,
+ out nsITransaction aUndoAction);
+
+ /**
+ * Does this store require compaction? For example, maildir doesn't require
+ * compaction at all. Berkeley mailbox does. A sqlite store probably doesn't.
+ * This is a static property of the store. It doesn't mean that any particular
+ * folder has space that can be reclaimed via compaction. Right now, the core
+ * code keeps track of the size of messages deleted, which it can use in
+ * conjunction with this store attribute.
+ */
+ readonly attribute boolean supportsCompaction;
+
+ /**
+ * Remove deleted messages from the store, reclaiming space. Some stores
+ * won't need to do anything here (e.g., maildir), and those stores
+ * should return false for needsCompaction. This operation is asynchronous,
+ * and the passed url listener should be called when the operation is done.
+ *
+ * @param aFolder folder whose storage is to be compacted
+ * @param aListener listener notified when compaction is done.
+ * @param aMsgWindow window to display progress/status in.
+ */
+ void compactFolder(in nsIMsgFolder aFolder, in nsIUrlListener aListener,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Is the summary file for the passed folder valid? For Berkeley Mailboxes,
+ * for local mail folders, this checks the timestamp and size of the local
+ * mail folder against values stored in the db. For other stores, this may
+ * be a noop, though other stores could certainly become invalid. For
+ * Berkeley Mailboxes, this is to deal with the case of other apps altering
+ * mailboxes from outside mailnews code, and this is certainly possible
+ * with other stores.
+ *
+ * @param aFolder Folder to check if summary is valid for.
+ * @param aDB DB to check validity of.
+ *
+ * @return return true if the summary file is valid, false otherwise.
+ */
+ boolean isSummaryFileValid(in nsIMsgFolder aFolder, in nsIMsgDatabase aDB);
+
+ /**
+ * Marks the summary file for aFolder as valid or invalid. This method
+ * may not be required, since it's really used by Berkeley Mailbox code
+ * to fix the timestamp and size for a folder.
+ *
+ * @param aFolder folder whose summary file should be marked (in)valid.
+ * @param aDB db to mark valid (may not be the folder's db in odd cases
+ * like folder compaction.
+ * @param aValid whether to mark it valid or invalid.
+ */
+ void setSummaryFileValid(in nsIMsgFolder aFolder, in nsIMsgDatabase aDB,
+ in boolean aValid);
+
+ /**
+ * Rebuild the index from information in the store. This involves creating
+ * a new nsIMsgDatabase for the folder, adding the information for all the
+ * messages in the store, and then copying the new msg database over the
+ * existing database. For Berkeley mailbox, we try to maintain meta data
+ * stored in the existing database when possible, and other stores should do
+ * the same. Ideally, I would figure out a way of making that easy. That
+ * might entail reworking the rebuild index process into one where the store
+ * would iterate over the messages, and stream each message through the
+ * message parser, and the common code would handle maintaining the
+ * meta data. But the berkeley mailbox code needs to do some parsing because
+ * it doesn't know how big the message is (i.e., the stream can't simply be
+ * a file stream).
+ * This operation is asynchronous,
+ * and the passed url listener should be called when the operation is done.
+ *
+ * @param aFolder folder whose storage is to be compacted
+ * @param aMsgDB db to put parsed headers in.
+ * @param aMsgWindow msgWindow to use for progress updates.
+ * @param aListener listener notified when the index is rebuilt.
+ */
+ void rebuildIndex(in nsIMsgFolder aFolder, in nsIMsgDatabase aMsgDB,
+ in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
+
+ /**
+ * Sets/Clears the passed flags on the passed messages.
+ * @param aHdrArray array of nsIMsgDBHdr's
+ * @param aFlags flags to set/clear
+ * @param aSet true to set the flag(s), false to clear.
+ */
+ void changeFlags(in nsIArray aHdrArray, in unsigned long aFlags,
+ in boolean aSet);
+ /**
+ *Sets/Clears the passed keywords on the passed messages.
+ * @param aHdrArray array of nsIMsgDBHdr's
+ * @param aKeywords keywords to set/clear
+ * @param aAdd true to add the keyword(s), false to remove.
+ */
+ void changeKeywords(in nsIArray aHdrArray, in ACString aKeywords,
+ in boolean aAdd);
+
+ /**
+ * Identifies a specific type of store. Please use this only for legacy
+ * bug fixes, and not as a method to change behavior!
+ *
+ * Typical values: "mbox", "maildir"
+ */
+ readonly attribute ACString storeType;
+};
diff --git a/mailnews/base/public/nsIMsgPrintEngine.idl b/mailnews/base/public/nsIMsgPrintEngine.idl
new file mode 100644
index 000000000..725471eed
--- /dev/null
+++ b/mailnews/base/public/nsIMsgPrintEngine.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsrootidl.idl"
+#include "nsIMsgStatusFeedback.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIPrintSettings;
+interface nsIObserver;
+
+[scriptable, uuid(9bdd0812-dd48-4f04-87a9-74dcfc3abadc)]
+interface nsIMsgPrintEngine : nsISupports {
+ /**
+ * Print/PrintPreview Msg Type
+ */
+ const short MNAB_START = 0;
+ const short MNAB_PRINT_MSG = 0;
+ const short MNAB_PRINTPREVIEW_MSG = 1;
+ const short MNAB_PRINT_AB_CARD = 2;
+ const short MNAB_PRINTPREVIEW_AB_CARD = 3;
+ const short MNAB_PRINT_ADDRBOOK = 4;
+ const short MNAB_PRINTPREVIEW_ADDRBOOK = 5;
+ const short MNAB_END = 6;
+
+ void setWindow(in mozIDOMWindowProxy ptr);
+ void setParentWindow(in mozIDOMWindowProxy ptr);
+ void showWindow(in boolean aShow);
+ void setStatusFeedback(in nsIMsgStatusFeedback feedback);
+ void setPrintURICount(in int32_t aCount);
+ void addPrintURI(in wstring aURI);
+ void startPrintOperation(in nsIPrintSettings aPS);
+ void setStartupPPObserver(in nsIObserver startupPPObs);
+ void setMsgType(in long aMsgType);
+
+ attribute boolean doPrintPreview;
+
+};
+
diff --git a/mailnews/base/public/nsIMsgProgress.idl b/mailnews/base/public/nsIMsgProgress.idl
new file mode 100644
index 000000000..79994c093
--- /dev/null
+++ b/mailnews/base/public/nsIMsgProgress.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+#include "domstubs.idl"
+#include "nsIPrompt.idl"
+#include "nsIWebProgressListener.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIMsgWindow;
+
+[scriptable, uuid(6d6fe91d-7f9a-4552-9737-9f74b0e75538)]
+interface nsIMsgProgress: nsIWebProgressListener {
+
+ /**
+ * Open the progress dialog, you can specify parameters through an xpcom object
+ */
+ void openProgressDialog(in mozIDOMWindowProxy parent,
+ in nsIMsgWindow aMsgWindow,
+ in string dialogURL,
+ in boolean inDisplayModal,
+ in nsISupports parameters);
+
+ /* Close the progress dialog */
+ void closeProgressDialog(in boolean forceClose);
+
+ /* Register a Web Progress Listener */
+ void registerListener(in nsIWebProgressListener listener);
+
+ /* Unregister a Web Progress Listener */
+ void unregisterListener(in nsIWebProgressListener listener);
+
+ /* Indicated if the user asked to cancel the current process */
+ attribute boolean processCanceledByUser;
+
+ attribute nsIMsgWindow msgWindow;
+};
+
+
diff --git a/mailnews/base/public/nsIMsgProtocolInfo.idl b/mailnews/base/public/nsIMsgProtocolInfo.idl
new file mode 100644
index 000000000..0913d93bd
--- /dev/null
+++ b/mailnews/base/public/nsIMsgProtocolInfo.idl
@@ -0,0 +1,99 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+%{C++
+#define NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX \
+ "@mozilla.org/messenger/protocol/info;1?type="
+%}
+
+
+[scriptable, uuid(9428b5f5-8b12-493c-aae2-18296c2877b1)]
+interface nsIMsgProtocolInfo : nsISupports
+{
+ /**
+ * the default path to store local data for this type of
+ * server. Each server is usually in a subdirectory below this
+ */
+ attribute nsIFile defaultLocalPath;
+
+ /**
+ * the IID of the protocol-specific interface for this server
+ * usually used from JS to dynamically get server-specific attributes
+ */
+ readonly attribute nsIIDPtr serverIID;
+
+ /**
+ * does this server type require a username?
+ * for instance, news does not but IMAP/POP do
+ */
+ readonly attribute boolean requiresUsername;
+
+ /**
+ * if the pretty name of the server should
+ * just be the e-mail address. Otherwise it usually
+ * ends up being something like "news on hostname"
+ */
+ readonly attribute boolean preflightPrettyNameWithEmailAddress;
+
+ /**
+ * can this type of server be removed from the account manager?
+ * for instance, local mail is not removable
+ */
+ readonly attribute boolean canDelete;
+
+ /**
+ * can this type of server log in at startup?
+ */
+ readonly attribute boolean canLoginAtStartUp;
+
+ /**
+ * can you duplicate this server?
+ * for instance, local mail is unique and should not be duplicated.
+ */
+ readonly attribute boolean canDuplicate;
+
+ /* the default port
+ This is similar to nsIProtocolHanderl.defaultPort,
+ but for architectural reasons, there is a mail-specific interface to this.
+ When the input param isSecure is set to true, for all supported protocols,
+ the secure port value is returned. If isSecure is set to false the default
+ port value is returned */
+ long getDefaultServerPort(in boolean isSecure);
+
+ /**
+ * An attribute that tell us whether on not we can
+ * get messages for the given server type
+ * this is poorly named right now.
+ * it's really is there an inbox for this type?
+ * XXX todo, rename this.
+ */
+ readonly attribute boolean canGetMessages;
+
+ /**
+ * do messages arrive for this server
+ * if they do, we can use our junk controls on it.
+ */
+ readonly attribute boolean canGetIncomingMessages;
+
+ /**
+ * do biff by default?
+ */
+ readonly attribute boolean defaultDoBiff;
+
+ /**
+ * do we need to show compose message link in the AccountCentral page ?
+ */
+ readonly attribute boolean showComposeMsgLink;
+
+ /**
+ * Will new folders be created asynchronously?
+ */
+ readonly attribute boolean foldersCreatedAsync;
+};
+
diff --git a/mailnews/base/public/nsIMsgPurgeService.idl b/mailnews/base/public/nsIMsgPurgeService.idl
new file mode 100644
index 000000000..e418444de
--- /dev/null
+++ b/mailnews/base/public/nsIMsgPurgeService.idl
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(c73294b2-b619-4915-b0e8-314d4215e08d)]
+interface nsIMsgPurgeService : nsISupports {
+
+ void init();
+ void shutdown();
+};
+
diff --git a/mailnews/base/public/nsIMsgRDFDataSource.idl b/mailnews/base/public/nsIMsgRDFDataSource.idl
new file mode 100644
index 000000000..3b2a185cf
--- /dev/null
+++ b/mailnews/base/public/nsIMsgRDFDataSource.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgWindow.idl"
+
+[scriptable, uuid(BB460DFE-8BF0-11d3-8AFE-0060B0FC04D2)]
+
+/* Interface used to access functions particular to mailnews datasources */
+
+interface nsIMsgRDFDataSource : nsISupports
+{
+ attribute nsIMsgWindow window;
+};
diff --git a/mailnews/base/public/nsIMsgShutdown.idl b/mailnews/base/public/nsIMsgShutdown.idl
new file mode 100644
index 000000000..1750f8ba8
--- /dev/null
+++ b/mailnews/base/public/nsIMsgShutdown.idl
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIUrlListener;
+interface nsIMsgWindow;
+interface nsIWebProgressListener;
+
+[scriptable, uuid(D1B43428-B631-4629-B691-AB0E01A2DB4B)]
+interface nsIMsgShutdownTask : nsISupports
+{
+ /**
+ * Inform the caller wheter or not the task needs to be run. This method
+ * gives the task the flexibility to cancel running a task on shutdown
+ * if nothing needs to be run.
+ */
+ readonly attribute boolean needsToRunTask;
+
+ /**
+ * At shutdown-time, this function will be called to all registered implementors.
+ * Shutdown will be temporarily postponed until |OnStopRequest()| has been called
+ * on the passed in url-listener.
+ * @param inUrlListener The URL listener to report events to.
+ * @param inMsgWindow The current message window to allow for posing dialogs.
+ * @return If the shutdown URL was run or not. If the URL is running, the task
+ * will be responsible for notifying |inUrlListener| when the task is completed.
+ */
+ boolean doShutdownTask(in nsIUrlListener inUrlListener, in nsIMsgWindow inMsgWindow);
+
+ /**
+ * Get the displayable name of the current task. This textual information will be
+ * shown to the user so they know what shutdown task is being performed.
+ * @return The name of the current task being performed.
+ */
+ AString getCurrentTaskName();
+};
+
+[scriptable, uuid(483C8ABB-ECF9-48A3-A394-2C604B603BD5)]
+interface nsIMsgShutdownService : nsISupports
+{
+ /**
+ * Get the number of tasks that will need to be processed at shutdown time.
+ * @return The number of shutdown tasks to do.
+ */
+ long getNumTasks();
+
+ /**
+ * Start the shutdown tasks.
+ */
+ void startShutdownTasks();
+
+ /**
+ * Tell the service to stop running tasks and go ahead and shutdown the application.
+ */
+ void cancelShutdownTasks();
+
+ /**
+ * Set the shutdown listener.
+ */
+ void setShutdownListener(in nsIWebProgressListener inListener);
+
+ /**
+ * Set the status text of the shutdown progress dialog.
+ */
+ void setStatusText(in AString inStatusString);
+};
+
+
diff --git a/mailnews/base/public/nsIMsgStatusFeedback.idl b/mailnews/base/public/nsIMsgStatusFeedback.idl
new file mode 100644
index 000000000..2f637e619
--- /dev/null
+++ b/mailnews/base/public/nsIMsgStatusFeedback.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(AACBFA34-8D29-4A08-9283-A8E5B3AB067F)]
+interface nsIMsgStatusFeedback : nsISupports {
+ void showStatusString(in AString aStatus);
+ void startMeteors();
+ void stopMeteors();
+ void showProgress(in long aPercent);
+ void setStatusString(in AString aStatus); // will be displayed until next user action
+
+ /* aStatusFeedback: a wrapped JS status feedback object */
+ void setWrappedStatusFeedback(in nsIMsgStatusFeedback aStatusFeedback);
+};
+
diff --git a/mailnews/base/public/nsIMsgTagService.idl b/mailnews/base/public/nsIMsgTagService.idl
new file mode 100644
index 000000000..b666c3b62
--- /dev/null
+++ b/mailnews/base/public/nsIMsgTagService.idl
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/*
+ * Keys are the internal representation of tags, and use a limited range of
+ * characters, basically the characters allowed in imap keywords, which are
+ * alphanumeric characters, but don't include spaces. Keys are stored on
+ * the imap server, in local mail messages, and in summary files.
+ *
+ * Tags are the user visible representation of keys, and are full unicode
+ * strings. Tags should allow any unicode character.
+ *
+ * This service will do the mapping between keys and tags. When a tag
+ * is added, we'll need to "compute" the corresponding key to use. This
+ * will probably entail replacing illegal ascii characters (' ', '/', etc)
+ * with '_' and then converting to imap mod utf7. We'll then need to make
+ * sure that no other keyword has the same value since that algorithm
+ * doesn't guarantee a unique mapping.
+ *
+ * Tags are sorted internally by 'importance' by their ordinal strings (which by
+ * default are equal to a tag's key and thus only stored if different).
+ * The alphanumerically 'smallest' string is called the 'most important' one and
+ * comes first in any sorted array. The remainder follows in ascending order.
+ */
+
+[scriptable, uuid(84d593a3-5d8a-45e6-96e2-9189acd422e1)]
+interface nsIMsgTag : nsISupports {
+ readonly attribute ACString key; // distinct tag identifier
+ readonly attribute AString tag; // human readable tag name
+ readonly attribute ACString color; // tag color
+ readonly attribute ACString ordinal; // custom sort string (usually empty)
+};
+
+[scriptable, uuid(97360ce3-0fba-4f1c-8214-af7bdc6f8587)]
+interface nsIMsgTagService : nsISupports {
+ // create new tag by deriving the key from the tag
+ void addTag(in AString tag, in ACString color, in ACString ordinal);
+ // create/update tag with known key
+ void addTagForKey(in ACString key, in AString tag, in ACString color, in ACString ordinal);
+ // get the key representation of a given tag
+ ACString getKeyForTag(in AString tag);
+ // get the first key by ordinal order
+ ACString getTopKey(in ACString keyList);
+ // support functions for single tag aspects
+ AString getTagForKey(in ACString key); // look up the tag for a key.
+ void setTagForKey(in ACString key, in AString tag); // this can be used to "rename" a tag
+ ACString getColorForKey(in ACString key);
+ void setColorForKey(in ACString key, in ACString color);
+ ACString getOrdinalForKey(in ACString key);
+ void setOrdinalForKey(in ACString key, in ACString ordinal);
+ // delete a tag from the list of known tags (but not from any messages)
+ void deleteKey(in ACString key);
+ // get all known tags
+ void getAllTags(out unsigned long count,
+ [retval, array, size_is(count)] out nsIMsgTag tagArray);
+ /*
+ * Determines if the token in aKey corresponds to a current valid tag
+ *
+ * @param aKey The string to test
+ * @return True if aKey is a current token
+ */
+ boolean isValidKey(in ACString aKey);
+};
diff --git a/mailnews/base/public/nsIMsgThread.idl b/mailnews/base/public/nsIMsgThread.idl
new file mode 100644
index 000000000..9af6b2dc9
--- /dev/null
+++ b/mailnews/base/public/nsIMsgThread.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgDBHdr;
+
+interface nsIDBChangeAnnouncer;
+
+[scriptable, uuid(84052876-90e9-4e21-ad38-13e2bb751d8f)]
+interface nsIMsgThread : nsISupports {
+ attribute nsMsgKey threadKey;
+ attribute unsigned long flags;
+ attribute ACString subject;
+ attribute unsigned long newestMsgDate;
+ readonly attribute unsigned long numChildren;
+ readonly attribute unsigned long numUnreadChildren;
+
+ void addChild(in nsIMsgDBHdr child, in nsIMsgDBHdr inReplyTo, in boolean threadInThread, in nsIDBChangeAnnouncer announcer);
+ nsMsgKey getChildKeyAt(in unsigned long index);
+ nsIMsgDBHdr getChild(in nsMsgKey msgKey);
+ nsIMsgDBHdr getChildHdrAt(in unsigned long index);
+ nsIMsgDBHdr getRootHdr(out long index);
+ void removeChildAt(in unsigned long index);
+ void removeChildHdr(in nsIMsgDBHdr child, in nsIDBChangeAnnouncer announcer);
+
+ void markChildRead(in boolean bRead);
+
+ nsIMsgDBHdr getFirstUnreadChild();
+
+ nsISimpleEnumerator enumerateMessages(in nsMsgKey parent);
+};
diff --git a/mailnews/base/public/nsIMsgUserFeedbackListener.idl b/mailnews/base/public/nsIMsgUserFeedbackListener.idl
new file mode 100644
index 000000000..f798c4b86
--- /dev/null
+++ b/mailnews/base/public/nsIMsgUserFeedbackListener.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgMailNewsUrl;
+
+/**
+ * Implement this interface to subscribe to errors and warnings passed out via
+ * nsIMsgMailSession.
+ */
+[scriptable, uuid(5e909ffa-77fe-4ce3-bf3c-06c54596d03d)]
+interface nsIMsgUserFeedbackListener : nsISupports {
+ /**
+ * Called when an alert from a protocol level implementation is generated.
+ *
+ * @param aMessage The localized message string to alert.
+ * @param aUrl Optional mailnews url which is relevant to the operation
+ * which caused the alert to be generated.
+ * @return True if you serviced the alert and it does not need
+ * to be prompted to the user separately.
+ * Note: The caller won't prompt if msgWindow in aUrl is
+ * null, regardless of the value returned.
+ */
+ boolean onAlert(in AString aMessage, [optional] in nsIMsgMailNewsUrl aUrl);
+};
diff --git a/mailnews/base/public/nsIMsgWindow.idl b/mailnews/base/public/nsIMsgWindow.idl
new file mode 100644
index 000000000..00673732b
--- /dev/null
+++ b/mailnews/base/public/nsIMsgWindow.idl
@@ -0,0 +1,98 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgStatusFeedback;
+interface nsIMsgFolder;
+interface nsITransactionManager;
+interface nsIDocShell;
+interface mozIDOMWindowProxy;
+interface nsIMsgHeaderSink;
+interface nsIPrompt;
+interface nsIInterfaceRequestor;
+interface nsIAuthPrompt;
+
+[scriptable, uuid(7B8F4A65-CFC4-4b3f-BF5C-152AA8D5CD10)]
+interface nsIMsgWindowCommands : nsISupports {
+ void selectFolder(in ACString folderUri);
+ void selectMessage(in ACString messageUri);
+ void clearMsgPane();
+};
+
+[scriptable, uuid(a846fe48-4022-4296-a1c4-1dcd7eaecfe5)]
+interface nsIMsgWindow : nsISupports {
+ attribute nsIMsgStatusFeedback statusFeedback;
+ attribute nsIMsgWindowCommands windowCommands;
+ attribute nsIMsgHeaderSink msgHeaderSink;
+ attribute nsITransactionManager transactionManager;
+ attribute nsIMsgFolder openFolder;
+
+ /**
+ * @note Setting this attribute has various side effects, including
+ * wiring up this object as the parent nsIURIContentListener for the
+ * passed-in docshell as well as setting the message content policy service
+ * to listen for OnLocationChange notifications.
+ */
+ attribute nsIDocShell rootDocShell;
+
+ /**
+ * @note Small helper function used to optimize our use of a weak reference
+ * on the message window docshell. Under no circumstances should you be
+ * holding on to the docshell returned here outside the scope of your routine.
+ */
+ readonly attribute nsIDocShell messageWindowDocShell;
+
+ /**
+ * Returns the auth prompt associated with the window. This will only return
+ * a value if the rootDocShell has been set.
+ */
+ readonly attribute nsIAuthPrompt authPrompt;
+
+ /**
+ * These are currently used to set notification callbacks on
+ * protocol channels to handle things like bad cert exceptions.
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ void displayHTMLInMessagePane(in AString title, in AString body, in boolean clearMsgHdr);
+
+ readonly attribute nsIPrompt promptDialog;
+ attribute ACString mailCharacterSet;
+
+ /**
+ Remember the message's charaset was overridden, so it can be inherited (e.g for quoting).
+ */
+ attribute boolean charsetOverride;
+
+ /**
+ Has a running url been stopped? If you care about checking
+ this flag, you need to clear it before you start your operation since
+ there's no convenient place to clear it.
+ */
+ attribute boolean stopped;
+
+ attribute mozIDOMWindowProxy domWindow;
+
+ void StopUrls();
+
+ /**
+ when the msg window is being unloaded from the content window,
+ we can use this notification to force a flush on anything the
+ msg window hangs on too. For some reason xpconnect is still hanging
+ onto the msg window even though all of our objects have let go of it
+ this forces a release...
+ */
+ void closeWindow();
+};
+
+[scriptable, uuid(FFBC8B13-243F-4cd9-92D0-01636CDA425E)]
+interface nsIMsgWindowTest : nsISupports {
+ /**
+ * For testing only, allow setting a few read-only attributes
+ */
+ void setPromptDialog(in nsIPrompt promptDialog);
+ void setAuthPrompt(in nsIAuthPrompt authPrompt);
+};
diff --git a/mailnews/base/public/nsIMsgWindowData.idl b/mailnews/base/public/nsIMsgWindowData.idl
new file mode 100644
index 000000000..78b965fd9
--- /dev/null
+++ b/mailnews/base/public/nsIMsgWindowData.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgStatusFeedback.idl"
+#include "nsIMessageView.idl"
+
+interface nsITransactionManager;
+
+/*
+ * This interface can be used to set data specific to a window.
+ */
+
+
+[scriptable, uuid(BD85A416-5433-11d3-8AC5-0060B0FC04D2)]
+interface nsIMsgWindowData : nsISupports {
+
+ attribute nsIMsgStatusFeedback statusFeedback;
+ attribute nsITransactionManager transactionManager;
+ attribute nsIMessageView messageView;
+}; \ No newline at end of file
diff --git a/mailnews/base/public/nsISpamSettings.idl b/mailnews/base/public/nsISpamSettings.idl
new file mode 100644
index 000000000..2c6e2a092
--- /dev/null
+++ b/mailnews/base/public/nsISpamSettings.idl
@@ -0,0 +1,97 @@
+/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+
+interface nsIOutputStream;
+interface nsIMsgIncomingServer;
+interface nsIMsgDBHdr;
+interface nsIFile;
+
+[scriptable, uuid(1772BE95-FDA9-4dfd-A663-8AF92C1E3024)]
+interface nsISpamSettings: nsISupports {
+ /**
+ * 0 for nothing, 100 for highest
+ */
+ attribute long level;
+
+ attribute boolean moveOnSpam;
+ readonly attribute boolean markAsReadOnSpam;
+
+ /**
+ * Most consumers will just use spamFolderURI rather than accessing any of
+ * target attributes directly.
+ */
+ attribute long moveTargetMode;
+ const long MOVE_TARGET_MODE_ACCOUNT = 0;
+ const long MOVE_TARGET_MODE_FOLDER = 1;
+ attribute string actionTargetAccount;
+ attribute string actionTargetFolder;
+
+ /**
+ * built from moveTargetMode, actionTargetAccount, actionTargetFolder
+ */
+ readonly attribute string spamFolderURI;
+
+ attribute boolean purge;
+ /**
+ * interval, in days
+ */
+ attribute long purgeInterval;
+
+ attribute boolean useWhiteList;
+ attribute string whiteListAbURI;
+
+ /**
+ * Should we do something when the user manually marks a message as junk?
+ */
+ readonly attribute boolean manualMark;
+
+ /**
+ * With manualMark true, which action (move to the Junk folder, or delete)
+ * should we take when the user marks a message as junk.
+ */
+ readonly attribute long manualMarkMode;
+ const long MANUAL_MARK_MODE_MOVE = 0;
+ const long MANUAL_MARK_MODE_DELETE = 1;
+
+ /**
+ * integrate with server-side spam detection programs
+ */
+ attribute boolean useServerFilter;
+ attribute ACString serverFilterName;
+ readonly attribute nsIFile serverFilterFile;
+ const long TRUST_POSITIVES = 1;
+ const long TRUST_NEGATIVES = 2;
+ attribute long serverFilterTrustFlags;
+
+ // for logging
+ readonly attribute boolean loggingEnabled;
+ attribute nsIOutputStream logStream;
+ void logJunkHit(in nsIMsgDBHdr aMsgHdr, in boolean aMoveMessage);
+ void logJunkString(in string aLogText);
+ void clone(in nsISpamSettings aSpamSettings);
+
+ // aServer -> spam settings are associated with a particular server
+ void initialize(in nsIMsgIncomingServer aServer);
+
+ /**
+ * check if junk processing for a message should be bypassed
+ *
+ * Typically this is determined by comparing message to: address
+ * to a whitelist of known good addresses or domains.
+ *
+ * @param aMsgHdr database header representing the message.
+ *
+ * @return true if this message is whitelisted, and junk
+ * processing should be bypassed
+ *
+ * false otherwise (including in case of error)
+ */
+ boolean checkWhiteList(in nsIMsgDBHdr aMsgHdr);
+
+};
diff --git a/mailnews/base/public/nsIStatusBarBiffManager.idl b/mailnews/base/public/nsIStatusBarBiffManager.idl
new file mode 100644
index 000000000..88dc4d7b4
--- /dev/null
+++ b/mailnews/base/public/nsIStatusBarBiffManager.idl
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMsgFolder.idl"
+#include "nsIFolderListener.idl"
+
+[scriptable, uuid(20b81f2b-ea81-4baa-b378-c5e6d3dc94e5)]
+interface nsIStatusBarBiffManager : nsIFolderListener {
+ // see nsIMsgFolder for definition and constants
+ readonly attribute nsMsgBiffState biffState;
+};
diff --git a/mailnews/base/public/nsIStopwatch.idl b/mailnews/base/public/nsIStopwatch.idl
new file mode 100644
index 000000000..6e40ace07
--- /dev/null
+++ b/mailnews/base/public/nsIStopwatch.idl
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+
+/**
+ * Simple stopwatch mechanism for determining the amount of wall-clock time and
+ * CPU time (user + system) that has elapsed. It is not fancy. It is either
+ * running or it is not. If you want coherent cpu and real time values, then
+ * you had better stop it first. It does not keep counting when stopped,
+ * although one could add a resumeRetroactive or something to accomplish that.
+ */
+[scriptable, uuid(7a671d6e-d48f-4a4f-b87e-644815a5e381)]
+interface nsIStopwatch : nsISupports {
+ /**
+ * Start the stopwatch; all counters are reset to zero. If you want to
+ * keep the already accumulated values, use resume instead.
+ */
+ void start();
+
+ /**
+ * Stop the stopwatch.
+ */
+ void stop();
+
+ /**
+ * Resume the stopwatch without clearing the existing counters. Any time
+ * already accumulated on cpuTime/realTime will be kept.
+ */
+ void resume();
+
+ /**
+ * The total CPU time (user + system) in seconds accumulated between calls to
+ * start/resume and stop. You have to stop the stopwatch to cause this value
+ * to update.
+ */
+ readonly attribute double cpuTimeSeconds;
+ /**
+ * The total wall clock time in seconds accumulated between calls to
+ * start/resume and stop. You have to stop the stopwatch to cause this value
+ * to update.
+ */
+ readonly attribute double realTimeSeconds;
+};
diff --git a/mailnews/base/public/nsISubscribableServer.idl b/mailnews/base/public/nsISubscribableServer.idl
new file mode 100644
index 000000000..5d1f71b03
--- /dev/null
+++ b/mailnews/base/public/nsISubscribableServer.idl
@@ -0,0 +1,76 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgWindow;
+interface nsIMsgIncomingServer;
+interface nsIRDFResource;
+interface nsIRDFNode;
+interface nsISimpleEnumerator;
+
+[scriptable, uuid(61a08c3a-1dd2-11b2-b64f-c4b2de1cf129)]
+interface nsISubscribeDataSource : nsISupports {
+ readonly attribute boolean hasObservers;
+ void NotifyObservers(in nsIRDFResource subject, in nsIRDFResource property, in nsIRDFNode object, in boolean isAssert, in boolean isChange);
+};
+
+[scriptable, uuid(f337b84a-1dd1-11b2-97c7-fb8b2e3f2280)]
+interface nsISubscribeListener : nsISupports {
+ void OnDonePopulating();
+};
+
+[scriptable, uuid(14b8597a-755b-4e93-b364-e0903801e6ea)]
+interface nsISubscribableServer : nsISupports {
+ attribute nsISubscribeListener subscribeListener;
+ attribute char delimiter;
+
+ void startPopulating(in nsIMsgWindow aMsgWindow, in boolean forceToServer, in boolean getOnlyNew);
+ void startPopulatingWithUri(in nsIMsgWindow aMsgWindow, in boolean forceToServer, in string uri);
+ void stopPopulating(in nsIMsgWindow aMsgWindow);
+
+ // return true if state changed, false otherwise
+ boolean setState(in AUTF8String path, in boolean state);
+
+ void subscribeCleanup();
+
+ void subscribe(in wstring name);
+ void unsubscribe(in wstring name);
+
+ void commitSubscribeChanges();
+
+ // other stuff
+ void setIncomingServer(in nsIMsgIncomingServer server);
+ void addTo(in AUTF8String aName, in boolean addAsSubscribed,
+ in boolean aSubscribable, in boolean aChangeIfExists);
+ void setAsSubscribed(in AUTF8String path);
+ void updateSubscribed();
+ void setShowFullName(in boolean showFullName);
+
+ // if path is null, use the root
+ boolean hasChildren(in AUTF8String path);
+ // if path is null, use the root
+ boolean isSubscribed(in AUTF8String path);
+ // if path is null, use the root
+ boolean isSubscribable(in AUTF8String path);
+ // if path is null, use the root
+ AString getLeafName(in AUTF8String path);
+
+ /**
+ * Returns the children underneath the specified uri (path).
+ *
+ * @param aPath The server's uri; If this is null or empty, then the
+ * root server uri will be used.
+ * @return Enumerator containing the children.
+ */
+ nsISimpleEnumerator getChildren(in AUTF8String aPath);
+ // if path is null, use the root
+ AUTF8String getFirstChildURI(in AUTF8String path);
+
+ // for searching
+ void setSearchValue(in AString searchValue);
+ readonly attribute boolean supportsSubscribeSearch;
+};
+
diff --git a/mailnews/base/public/nsIUrlListener.idl b/mailnews/base/public/nsIUrlListener.idl
new file mode 100644
index 000000000..93df30b6c
--- /dev/null
+++ b/mailnews/base/public/nsIUrlListener.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+%{C++
+#include "nsIURL.h"
+%}
+
+/// General interface that signify URL processing.
+[scriptable, uuid(47618220-D008-11d2-8069-006008128C4E)]
+interface nsIUrlListener : nsISupports {
+ /**
+ * Called to signify the beginning of an URL processing.
+ *
+ * @param url URL being processed.
+ */
+ void OnStartRunningUrl(in nsIURI url);
+
+ /**
+ * Called to signify the end of an URL processing.
+ * This call is always preceded by a call to OnStartRunningUrl.
+ *
+ * @param url URL being processed.
+ * @param aExitCode A result code of URL processing.
+ */
+ void OnStopRunningUrl(in nsIURI url, in nsresult aExitCode);
+};
diff --git a/mailnews/base/public/nsMsgBaseCID.h b/mailnews/base/public/nsMsgBaseCID.h
new file mode 100644
index 000000000..efdfcbb71
--- /dev/null
+++ b/mailnews/base/public/nsMsgBaseCID.h
@@ -0,0 +1,536 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMessageBaseCID_h__
+#define nsMessageBaseCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+//
+// nsMsgFolderDataSource
+//
+#define NS_MAILNEWSFOLDERDATASOURCE_CONTRACTID \
+ NS_RDF_DATASOURCE_CONTRACTID_PREFIX "mailnewsfolders"
+
+#define NS_MAILNEWSFOLDERDATASOURCE_CID \
+{ /* 2B8ED4A4-F684-11d2-8A5D-0060B0FC04D2 */ \
+ 0x2b8ed4a4, \
+ 0xf684, \
+ 0x11d2, \
+ {0x8a, 0x5d, 0x0, 0x60, 0xb0, 0xfc, 0x4, 0xd2} \
+}
+
+#define NS_MAILNEWSUNREADFOLDERDATASOURCE_CONTRACTID \
+ NS_RDF_DATASOURCE_CONTRACTID_PREFIX "mailnewsunreadfolders"
+
+#define NS_MAILNEWSUNREADFOLDERDATASOURCE_CID \
+{ /* 39b6b638-5b9d-45aa-b645-77fe56bbddb7 */ \
+ 0x39b6b638, \
+ 0x5b9d, \
+ 0x45aa, \
+ {0xb6, 0x45, 0x77, 0xfe, 0x56, 0xbb, 0xdd, 0xb7} \
+}
+
+#define NS_MAILNEWSFAVORITEFOLDERDATASOURCE_CONTRACTID \
+ NS_RDF_DATASOURCE_CONTRACTID_PREFIX "mailnewsfavefolders"
+
+#define NS_MAILNEWSFAVORITEFOLDERDATASOURCE_CID \
+{ /* dfdedc28-1b0c-4b7a-bbff-c98808034242 */ \
+ 0xdfdedc28, \
+ 0x1b0c, \
+ 0x4b7a, \
+ {0xbb, 0xff, 0xc9, 0x88, 0x08, 0x03, 0x42, 0x42} \
+}
+
+#define NS_MAILNEWSRECENTFOLDERDATASOURCE_CONTRACTID \
+ NS_RDF_DATASOURCE_CONTRACTID_PREFIX "mailnewsrecentfolders"
+
+#define NS_MAILNEWSRECENTFOLDERDATASOURCE_CID \
+{ /* 64921b82-24bb-4473-ada9-dc89426129a6 */ \
+ 0x64921b82, \
+ 0x24bb, \
+ 0x4473, \
+ {0xad, 0xa9, 0xdc, 0x89, 0x42, 0x61, 0x29, 0xa6} \
+}
+
+//
+// nsMsgAccountManager
+//
+#define NS_MSGACCOUNTMANAGER_CONTRACTID \
+ "@mozilla.org/messenger/account-manager;1"
+
+#define NS_MSGACCOUNTMANAGER_CID \
+{ /* D2876E50-E62C-11d2-B7FC-00805F05FFA5 */ \
+ 0xd2876e50, 0xe62c, 0x11d2, \
+ {0xb7, 0xfc, 0x0, 0x80, 0x5f, 0x5, 0xff, 0xa5 }}
+
+
+//
+// nsMsgIdentity
+//
+#define NS_MSGIDENTITY_CONTRACTID \
+ "@mozilla.org/messenger/identity;1"
+
+#define NS_MSGIDENTITY_CID \
+{ /* 8fbf6ac0-ebcc-11d2-b7fc-00805f05ffa5 */ \
+ 0x8fbf6ac0, 0xebcc, 0x11d2, \
+ {0xb7, 0xfc, 0x0, 0x80, 0x5f, 0x5, 0xff, 0xa5 }}
+
+//
+// nsMsgIncomingServer
+#define NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX \
+ "@mozilla.org/messenger/server;1?type="
+
+#define NS_MSGINCOMINGSERVER_CONTRACTID \
+ NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX "generic"
+
+/* {66e5ff08-5126-11d3-9711-006008948010} */
+#define NS_MSGINCOMINGSERVER_CID \
+ {0x66e5ff08, 0x5126, 0x11d3, \
+ {0x97, 0x11, 0x00, 0x60, 0x08, 0x94, 0x80, 0x10}}
+
+
+//
+// nsMsgAccount
+//
+#define NS_MSGACCOUNT_CONTRACTID \
+ "@mozilla.org/messenger/account;1"
+
+#define NS_MSGACCOUNT_CID \
+{ /* 68b25510-e641-11d2-b7fc-00805f05ffa5 */ \
+ 0x68b25510, 0xe641, 0x11d2, \
+ {0xb7, 0xfc, 0x0, 0x80, 0x5f, 0x5, 0xff, 0xa5 }}
+
+//
+// nsMsgFilterService
+//
+#define NS_MSGFILTERSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/services/filters;1"
+
+#define NS_MSGFILTERSERVICE_CID \
+{ 0x5cbb0700, 0x04bc, 0x11d3, \
+ { 0xa5, 0x0a, 0x0, 0x60, 0xb0, 0xfc, 0x04, 0xb7 } }
+
+
+//
+// nsMsgSearchSession
+//
+/* e9a7cd70-0303-11d3-a50a-0060b0fc04b7 */
+#define NS_MSGSEARCHSESSION_CID \
+{ 0xe9a7cd70, 0x0303, 0x11d3, \
+ { 0xa5, 0x0a, 0x0, 0x60, 0xb0, 0xfc, 0x04, 0xb7 } }
+
+#define NS_MSGSEARCHSESSION_CONTRACTID \
+ "@mozilla.org/messenger/searchSession;1"
+
+/* E1DA397D-FDC5-4b23-A6FE-D46A034D80B3 */
+#define NS_MSGSEARCHTERM_CID \
+{ 0xe1da397d, 0xfdc5, 0x4b23, \
+ { 0xa6, 0xfe, 0xd4, 0x6a, 0x3, 0x4d, 0x80, 0xb3 } }
+
+#define NS_MSGSEARCHTERM_CONTRACTID \
+ "@mozilla.org/messenger/searchTerm;1"
+
+//
+// nsMsgSearchValidityManager
+//
+/* 1510faee-ad1a-4194-8039-33de32d5a882 */
+#define NS_MSGSEARCHVALIDITYMANAGER_CID \
+ {0x1510faee, 0xad1a, 0x4194, \
+ { 0x80, 0x39, 0x33, 0xde, 0x32, 0xd5, 0xa8, 0x82 }}
+
+#define NS_MSGSEARCHVALIDITYMANAGER_CONTRACTID \
+ "@mozilla.org/mail/search/validityManager;1"
+
+//
+// nsMsgMailSession
+//
+#define NS_MSGMAILSESSION_CONTRACTID \
+ "@mozilla.org/messenger/services/session;1"
+
+/* D5124441-D59E-11d2-806A-006008128C4E */
+#define NS_MSGMAILSESSION_CID \
+{ 0xd5124441, 0xd59e, 0x11d2, \
+ { 0x80, 0x6a, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e } }
+
+//
+// nsMsgBiffManager
+//
+#define NS_MSGBIFFMANAGER_CONTRACTID \
+ "@mozilla.org/messenger/biffManager;1"
+
+/* 4A374E7E-190F-11d3-8A88-0060B0FC04D2 */
+#define NS_MSGBIFFMANAGER_CID \
+{ 0x4a374e7e, 0x190f, 0x11d3, \
+ { 0x8a, 0x88, 0x0, 0x60, 0xb0, 0xfc, 0x4, 0xd2 } }
+
+
+//
+// nsMsgPurgeService
+//
+#define NS_MSGPURGESERVICE_CONTRACTID \
+ "@mozilla.org/messenger/purgeService;1"
+
+/* a687b474-afd8-418f-8ad9-f362202ae9a9 */
+#define NS_MSGPURGESERVICE_CID \
+{ 0xa687b474, 0xafd8, 0x418f, \
+ { 0x8a, 0xd9, 0xf3, 0x62, 0x20, 0x2a, 0xe9, 0xa9 } }
+
+//
+// nsStatusBarBiffManager
+//
+#define NS_STATUSBARBIFFMANAGER_CONTRACTID \
+ "@mozilla.org/messenger/statusBarBiffManager;1"
+
+/* 7f9a9fb0-4161-11d4-9876-00c04fa0d2a6 */
+#define NS_STATUSBARBIFFMANAGER_CID \
+{ 0x7f9a9fb0, 0x4161, 0x11d4, \
+ {0x98, 0x76, 0x00, 0xc0, 0x4f, 0xa0, 0xd2, 0xa6} }
+
+//
+// nsCopyMessageStreamListener
+//
+#define NS_COPYMESSAGESTREAMLISTENER_CONTRACTID \
+ "@mozilla.org/messenger/copymessagestreamlistener;1"
+
+#define NS_COPYMESSAGESTREAMLISTENER_CID \
+{ 0x7741daed, 0x2125, 0x11d3, \
+ { 0x8a, 0x90, 0x0, 0x60, 0xb0, 0xfc, 0x4, 0xd2 } }
+
+//
+// nsMsgCopyService
+//
+#define NS_MSGCOPYSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/messagecopyservice;1"
+
+/* c766e666-29bd-11d3-afb3-001083002da8 */
+#define NS_MSGCOPYSERVICE_CID \
+{ 0xc766e666, 0x29bd, 0x11d3, \
+ { 0xaf, 0xb3, 0x00, 0x10, 0x83, 0x00, 0x2d, 0xa8 } }
+
+#define NS_MSGFOLDERCACHE_CONTRACTID \
+ "@mozilla.org/messenger/msgFolderCache;1"
+
+/* bcdca970-3b22-11d3-8d76-00805f8a6617 */
+#define NS_MSGFOLDERCACHE_CID \
+{ 0xbcdca970, 0x3b22, 0x11d3, \
+ { 0x8d, 0x76, 0x00, 0x80, 0xf5, 0x8a, 0x66, 0x17 } }
+
+//
+// nsMessengerBootstrap
+//
+#define NS_MESSENGERBOOTSTRAP_CONTRACTID \
+ "@mozilla.org/appshell/component/messenger;1"
+#define NS_MAILOPTIONSTARTUPHANDLER_CONTRACTID \
+ "@mozilla.org/commandlinehandler/general-startup;1?type=options"
+#define NS_MESSENGERWINDOWSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/windowservice;1"
+#define NS_MESSENGERWINDOWSERVICE_CID \
+{ 0xa01b6724, 0x1dd1, 0x11b2, \
+ {0xaa, 0xb9, 0x82,0xf2, 0x4c,0x59, 0x5f, 0x41} }
+
+//
+// nsMessenger
+//
+#define NS_MESSENGER_CONTRACTID \
+ "@mozilla.org/messenger;1"
+
+//
+// nsMsgStatusFeedback
+//
+#define NS_MSGSTATUSFEEDBACK_CONTRACTID \
+ "@mozilla.org/messenger/statusfeedback;1"
+
+/* B1AA0820-D04B-11d2-8069-006008128C4E */
+#define NS_MSGSTATUSFEEDBACK_CID \
+{ 0xbd85a417, 0x5433, 0x11d3, \
+ {0x8a, 0xc5, 0x0, 0x60, 0xb0, 0xfc, 0x4, 0xd2} }
+
+//
+// nsMsgKeyArray
+//
+#define NS_MSGKEYARRAY_CONTRACTID \
+ "@mozilla.org/messenger/msgkeyarray;1"
+
+/* 86989d1d-c8a1-4e8e-aae6-d0dabcacd8c2 */
+#define NS_MSGKEYARRAY_CID \
+{ 0x86989d1d, 0xc8a1, 0x4e8e, \
+ {0xaa, 0xe6, 0xd0, 0xda, 0xbc, 0xac, 0xd8, 0xc2 }}
+
+//
+//nsMsgWindow
+//
+#define NS_MSGWINDOW_CONTRACTID \
+ "@mozilla.org/messenger/msgwindow;1"
+
+/* BB460DFF-8BF0-11d3-8AFE-0060B0FC04D2*/
+#define NS_MSGWINDOW_CID \
+{ 0xbb460dff, 0x8bf0, 0x11d3, \
+ { 0x8a, 0xfe, 0x0, 0x60, 0xb0, 0xfc, 0x4, 0xd2}}
+
+//
+// Print Engine...
+//
+#define NS_MSGPRINTENGINE_CONTRACTID \
+ "@mozilla.org/messenger/msgPrintEngine;1"
+
+#define NS_MSG_PRINTENGINE_CID \
+ { /* 91FD6B19-E0BC-11d3-8F97-000064657374 */ \
+ 0x91fd6b19, 0xe0bc, 0x11d3, \
+ { 0x8f, 0x97, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } }
+
+//
+// nsMsgServiceProviderService
+//
+#define NS_MSGSERVICEPROVIDERSERVICE_CONTRACTID \
+ NS_RDF_DATASOURCE_CONTRACTID_PREFIX "ispdefaults"
+
+/* 10998cef-d7f2-4772-b7db-bd097454984c */
+#define NS_MSGSERVICEPROVIDERSERVICE_CID \
+{ 0x10998cef, 0xd7f2, 0x4772, \
+ { 0xb7, 0xdb, 0xbd, 0x09, 0x74, 0x54, 0x98, 0x4c}}
+
+#define NS_MSGLOGONREDIRECTORSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/msglogonredirector;1"
+
+#define NS_MSGLOGONREDIRECTORSERVICE_CID \
+{0x0d7456ae, 0xe28a, 0x11d3, \
+ {0xa5, 0x60, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7}}
+
+//
+// nsSubscribableServer
+//
+#define NS_SUBSCRIBABLESERVER_CONTRACTID \
+ "@mozilla.org/messenger/subscribableserver;1"
+
+#define NS_SUBSCRIBABLESERVER_CID \
+{0x8510876a, 0x1dd2, 0x11b2, \
+ {0x82, 0x53, 0x91, 0xf7, 0x1b, 0x34, 0x8a, 0x25}}
+
+//
+// nsSubscribeDataSource
+//
+#define NS_SUBSCRIBEDATASOURCE_CONTRACTID \
+ NS_RDF_DATASOURCE_CONTRACTID_PREFIX "subscribe"
+
+/* 00e89c82-1dd2-11b2-9a1c-e75995d7d595 */
+#define NS_SUBSCRIBEDATASOURCE_CID \
+{ 0x00e89c82, 0x1dd2, 0x11b2, \
+ { 0x9a, 0x1c, 0xe7, 0x59, 0x95, 0xd7, 0xd5, 0x95}}
+
+#define NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID \
+ "@mozilla.org/messenger/localfoldercompactor;1"
+
+/* 7d1d315c-e5c6-11d4-a5b7-0060b0fc04b7 */
+#define NS_MSGLOCALFOLDERCOMPACTOR_CID \
+ {0x7d1d315c, 0xe5c6, 0x11d4, \
+ {0xa5, 0xb7, 0x00,0x60, 0xb0, 0xfc, 0x04, 0xb7 }}
+
+#define NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID \
+ "@mozilla.org/messenger/offlinestorecompactor;1"
+
+/* 2db43d16-e5c8-11d4-a5b7-0060b0fc04b7 */
+#define NS_MSG_OFFLINESTORECOMPACTOR_CID \
+ {0x2db43d16, 0xe5c8, 0x11d4, \
+ {0xa5, 0xb7, 0x00,0x60, 0xb0, 0xfc, 0x04, 0xb7 }}
+
+//
+// nsMsgDBView
+//
+#define NS_MSGDBVIEW_CONTRACTID_PREFIX \
+ "@mozilla.org/messenger/msgdbview;1?type="
+
+#define NS_MSGTHREADEDDBVIEW_CONTRACTID \
+ NS_MSGDBVIEW_CONTRACTID_PREFIX "threaded"
+
+#define NS_MSGTHREADSWITHUNREADDBVIEW_CONTRACTID \
+ NS_MSGDBVIEW_CONTRACTID_PREFIX "threadswithunread"
+
+#define NS_MSGWATCHEDTHREADSWITHUNREADDBVIEW_CONTRACTID \
+ NS_MSGDBVIEW_CONTRACTID_PREFIX "watchedthreadswithunread"
+
+#define NS_MSGSEARCHDBVIEW_CONTRACTID \
+ NS_MSGDBVIEW_CONTRACTID_PREFIX "search"
+
+#define NS_MSGQUICKSEARCHDBVIEW_CONTRACTID \
+ NS_MSGDBVIEW_CONTRACTID_PREFIX "quicksearch"
+
+#define NS_MSGXFVFDBVIEW_CONTRACTID \
+ NS_MSGDBVIEW_CONTRACTID_PREFIX "xfvf"
+
+#define NS_MSGGROUPDBVIEW_CONTRACTID \
+ NS_MSGDBVIEW_CONTRACTID_PREFIX "group"
+
+/* 52f860e0-1dd2-11b2-aa72-bb751981bd00 */
+#define NS_MSGTHREADEDDBVIEW_CID \
+ {0x52f860e0, 0x1dd2, 0x11b2, \
+ {0xaa, 0x72, 0xbb, 0x75, 0x19, 0x81, 0xbd, 0x00 }}
+
+/* ca79a00e-010d-11d5-a5be-0060b0fc04b7 */
+#define NS_MSGTHREADSWITHUNREADDBVIEW_CID \
+ {0xca79a00e, 0x010d, 0x11d5, \
+ {0xa5, 0xbe, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 }}
+
+/* 597e1ffe-0123-11d5-a5be-0060b0fc04b7 */
+#define NS_MSGWATCHEDTHREADSWITHUNREADDBVIEW_CID \
+ {0x597e1ffe, 0x0123, 0x11d5, \
+ {0xa5, 0xbe, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 }}
+
+/* aeac118c-0823-11d5-a5bf-0060b0fc04b7 */
+#define NS_MSGSEARCHDBVIEW_CID \
+ {0xaeac118c, 0x0823, 0x11d5, \
+ {0xa5, 0xbf, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7}}
+
+/* 2dd9d0fe-b609-11d6-bacc-00108335748d */
+#define NS_MSGQUICKSEARCHDBVIEW_CID \
+ {0x2dd9d0fe, 0xb609, 0x11d6, \
+ {0xba, 0xcc, 0x00, 0x10, 0x83, 0x35, 0x74, 0x8d}}
+
+/* 2af6e050-04f6-495a-8387-86b0aeb1863c */
+#define NS_MSG_XFVFDBVIEW_CID \
+ {0x2af6e050, 0x04f6, 0x495a, \
+ {0x83, 0x87, 0x86, 0xb0, 0xae, 0xb1, 0x86, 0x3c}}
+
+/* e4603d6c-0a74-47c5-b69e-2f8876990304 */
+#define NS_MSG_GROUPDBVIEW_CID \
+ {0xe4603d6c, 0x0a74, 0x47c5, \
+ {0xb6, 0x9e, 0x2f, 0x88, 0x76, 0x99, 0x03, 0x04}}
+//
+// nsMsgAccountManager
+//
+#define NS_MSGOFFLINEMANAGER_CONTRACTID \
+ "@mozilla.org/messenger/offline-manager;1"
+
+#define NS_MSGOFFLINEMANAGER_CID \
+{ /* ac6c518a-09b2-11d5-a5bf-0060b0fc04b7 */ \
+ 0xac6c518a, 0x09b2, 0x11d5, \
+ {0xa5, 0xbf, 0x0, 0x60, 0xb0, 0xfc, 0x04, 0xb7 }}
+
+
+//
+// nsMsgProgress
+//
+#define NS_MSGPROGRESS_CONTRACTID \
+ "@mozilla.org/messenger/progress;1"
+
+#define NS_MSGPROGRESS_CID \
+{ /* 9f4dd201-3b1f-11d5-9daa-c345c9453d3c */ \
+ 0x9f4dd201, 0x3b1f, 0x11d5, \
+ {0x9d, 0xaa, 0xc3, 0x45, 0xc9, 0x45, 0x3d, 0x3c }}
+
+//
+// nsSpamSettings
+//
+#define NS_SPAMSETTINGS_CONTRACTID \
+ "@mozilla.org/messenger/spamsettings;1"
+
+#define NS_SPAMSETTINGS_CID \
+{ /* ce6038ae-e5e0-4372-9cff-2a6633333b2b */ \
+ 0xce6038ae, 0xe5e0, 0x4372, \
+ {0x9c, 0xff, 0x2a, 0x66, 0x33, 0x33, 0x3b, 0x2b }}
+
+//
+// nsMsgTagService
+//
+#define NS_MSGTAGSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/tagservice;1"
+
+#define NS_MSGTAGSERVICE_CID \
+{ /* b897da55-8256-4cf5-892b-32e77bc7c50b */ \
+ 0xb897da55, 0x8256, 0x4cf5, \
+ { 0x89, 0x2b, 0x32, 0xe7, 0x7b, 0xc7, 0xc5, 0x0b}}
+
+//
+// nsMsgNotificationService
+//
+#define NS_MSGNOTIFICATIONSERVICE_CONTRACTID \
+"@mozilla.org/messenger/msgnotificationservice;1"
+
+#define NS_MSGNOTIFICATIONSERVICE_CID \
+{ /* F1F7CBCD-D5E3-45A0-AA2D-CECF1A95AB03 */ \
+ 0xf1f7cbcd, 0xd5e3, 0x45a0, \
+ {0xaa, 0x2d, 0xce, 0xcf, 0x1a, 0x95, 0xab, 0x03}}
+
+//
+// nsMessengerOSIntegration
+//
+#define NS_MESSENGEROSINTEGRATION_CONTRACTID \
+ "@mozilla.org/messenger/osintegration;1"
+
+//
+// cid protocol handler
+//
+#define NS_CIDPROTOCOLHANDLER_CONTRACTID \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "cid"
+
+#define NS_CIDPROTOCOL_CID \
+{ /* b3db9392-1b15-48ba-a136-0cc3db13d87b */ \
+ 0xb3db9392, 0x1b15, 0x48ba, \
+ {0xa1, 0x36, 0x0c, 0xc3, 0xdb, 0x13, 0xd8, 0x7b }}
+
+//
+// Mail Directory Provider
+//
+#define NS_MAILDIRPROVIDER_CONTRACTID \
+ "@mozilla.org/mail/dir-provider;1"
+
+#define MAILDIRPROVIDER_CID \
+{ 0x3f9bb53, 0xa680, 0x4349, \
+ { 0x8d, 0xe9, 0xd2, 0x68, 0x64, 0xd9, 0xff, 0xd9 } }
+
+//
+// nsMessengerContentHandler
+//
+#define NS_MESSENGERCONTENTHANDLER_CID \
+{ /* 57E1BCBB-1FBA-47e7-B96B-F59E392473B0 */ \
+ 0x57e1bcbb, 0x1fba, 0x47e7, \
+ {0xb9, 0x6b, 0xf5, 0x9e, 0x39, 0x24, 0x73, 0xb0}}
+
+#define NS_MESSENGERCONTENTHANDLER_CONTRACTID \
+ NS_CONTENT_HANDLER_CONTRACTID_PREFIX "application/x-message-display"
+
+//
+// nsMsgShutdownService
+//
+#define NS_MSGSHUTDOWNSERVICE_CID \
+{ /* 483c8abb-ecf9-48a3-a394-2c604b603bd5 */ \
+ 0x483c8abb, 0xecf9, 0x48a3, \
+ { 0xa3, 0x94, 0x2c, 0x60, 0x4b, 0x60, 0x3b, 0xd5 }}
+
+#define NS_MSGSHUTDOWNSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/msgshutdownservice;1"
+
+//
+// msgAsyncPrompter (only contract id for utility purposes as the CID is defined
+// in js).
+//
+#define NS_MSGASYNCPROMPTER_CONTRACTID \
+ "@mozilla.org/messenger/msgAsyncPrompter;1"
+
+//
+// MailNewsDLF
+//
+#define NS_MAILNEWSDLF_CID \
+{ /* DE0F34A9-A87F-4F4C-B978-6187DB187B90 */ \
+ 0xde0f34a9, 0xa87f, 0x4f4c, \
+ { 0xb9, 0x78, 0x61, 0x87, 0xdb, 0x18, 0x7b, 0x90 }}
+
+#define NS_MAILNEWSDLF_CONTRACTID \
+ "@mozilla.org/mailnews/document-loader-factory;1"
+
+//
+// NewMailNotificationService
+//
+#define MOZ_NEWMAILNOTIFICATIONSERVICE_CID \
+{ /* 740880E6-E299-4165-B82F-DF1DCAB3AE22 */ \
+ 0x740880E6, 0xE299, 0x4165, \
+ { 0xB8, 0x2F, 0xDF, 0x1D, 0xCA, 0xB3, 0xAE, 0x22 }}
+
+#define MOZ_NEWMAILNOTIFICATIONSERVICE_CONTRACTID \
+ "@mozilla.org/newMailNotificationService;1"
+
+#endif // nsMessageBaseCID_h__
diff --git a/mailnews/base/public/nsMsgFolderFlags.idl b/mailnews/base/public/nsMsgFolderFlags.idl
new file mode 100644
index 000000000..ba7e0a776
--- /dev/null
+++ b/mailnews/base/public/nsMsgFolderFlags.idl
@@ -0,0 +1,115 @@
+/*-*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This must be limited to unsigned long (uint32_t, no uint64_t)
+// as long as nsIMsgFolder exposes the 'flags' property which contains
+// all the flags values. The callers are used to do
+// (folder.flags & nsMsgFolderFlags.<flagname>) in Javascript
+// which cuts the value to 32bit only. See bug 813459.
+typedef unsigned long nsMsgFolderFlagType;
+
+/// Flags about a folder or a newsgroup.
+[scriptable,uuid(440cd0fc-b4b3-4a0f-a492-92fbe7920588)]
+interface nsMsgFolderFlags {
+ /**
+ * @name Folder Type Flags
+ * These flags define the type of folder. Exactly one will be set.
+ * @{
+ */
+ /// This folder is a newsgroup folder.
+ const nsMsgFolderFlagType Newsgroup = 0x00000001;
+ /// Used to be for a folder that is a news server (NewsHost).
+ const nsMsgFolderFlagType Unused3 = 0x00000002;
+ /// This folder is a mail folder.
+ const nsMsgFolderFlagType Mail = 0x00000004;
+ /** @} */
+
+ /** Whether this is a directory: NewsHosts are always directories;
+ * NewsGroups can be directories if we are in ``show all groups'' mode;
+ * Mail folders will have this bit if they are really directories, not files.
+ * (Note that directories may have zero children.)
+ */
+ const nsMsgFolderFlagType Directory = 0x00000008;
+ /** Whether the children of this folder are currently hidden in the listing.
+ * This will only be present if the nsMsgFolderFlags::Directory bit is on.
+ */
+ const nsMsgFolderFlagType Elided = 0x00000010;
+ /// Whether this is a virtual search folder
+ const nsMsgFolderFlagType Virtual = 0x00000020;
+
+ /** @name News Folder Flags
+ * These flags only occur in folders which have
+ * the nsMsgFolderFlags::Newsgroup bit set, and do
+ * not have the nsMsgFolderFlags::Directory or
+ * nsMsgFolderFlags::Elided bits set.
+ * @{
+ */
+ /// Used to be for folders representing a subscribed newsgroup (Subscribed).
+ const nsMsgFolderFlagType Unused5 = 0x00000040;
+ /// Used to be for new newsgroups added by the `Check New Groups' command.
+ const nsMsgFolderFlagType Unused2 = 0x00000080;
+ /** @} */
+
+ /** @name Mail Folder Flags
+ * These flags only occur in folders which have
+ * the nsMsgFolderFlags::Mail bit set, and do
+ * not have the nsMsgFolderFlags::Directory or
+ * nsMsgFolderFlags::Elided bits set.
+ * @{
+ */
+ /// Whether this is the trash folder.
+ const nsMsgFolderFlagType Trash = 0x00000100;
+ /// Whether this is a folder that sent mail gets delivered to.
+ const nsMsgFolderFlagType SentMail = 0x00000200;
+ /// Whether this is the folder in which unfinished, unsent messages are saved for later editing.
+ const nsMsgFolderFlagType Drafts = 0x00000400;
+ /// Whether this is the folder in which messages are queued for later delivery.
+ const nsMsgFolderFlagType Queue = 0x00000800;
+ /// Whether this is the primary inbox folder.
+ const nsMsgFolderFlagType Inbox = 0x00001000;
+ /// Whether this folder on online IMAP
+ const nsMsgFolderFlagType ImapBox = 0x00002000;
+ /// Whether this is an archive folder
+ const nsMsgFolderFlagType Archive = 0x00004000;
+ /// This used to be used for virtual newsgroups
+ const nsMsgFolderFlagType Unused1 = 0x00008000;
+ /// Used to be for categories
+ const nsMsgFolderFlagType Unused4 = 0x00010000;
+ /// Used to be for new msgs in a folder
+ const nsMsgFolderFlagType Unused7 = 0x00020000;
+ /// Used to be for a folder that is an IMAP server (ImapServer)
+ const nsMsgFolderFlagType Unused6 = 0x00040000;
+ /// This folder is an IMAP personal folder
+ const nsMsgFolderFlagType ImapPersonal = 0x00080000;
+ /// This folder is an IMAP public folder
+ const nsMsgFolderFlagType ImapPublic = 0x00100000;
+ /// This folder is another user's IMAP folder. Think of it like a folder that someone would share.
+ const nsMsgFolderFlagType ImapOtherUser = 0x00200000;
+ /// Whether this is the template folder
+ const nsMsgFolderFlagType Templates = 0x00400000;
+ /// This folder is one of your personal folders that is shared with other users
+ const nsMsgFolderFlagType PersonalShared = 0x00800000;
+ /// This folder is an IMAP \\Noselect folder
+ const nsMsgFolderFlagType ImapNoselect = 0x01000000;
+ /// This folder created offline (this is never set in current code,
+ /// but it is still checked for and obeyed if found on a folder.
+ const nsMsgFolderFlagType CreatedOffline = 0x02000000;
+ /// This imap folder cannot have children :-(
+ const nsMsgFolderFlagType ImapNoinferiors = 0x04000000;
+ /// This folder configured for offline use
+ const nsMsgFolderFlagType Offline = 0x08000000;
+ /// This folder has offline events to play back
+ const nsMsgFolderFlagType OfflineEvents = 0x10000000;
+ /// This folder is checked for new messages
+ const nsMsgFolderFlagType CheckNew = 0x20000000;
+ /// This folder is for spam messages
+ const nsMsgFolderFlagType Junk = 0x40000000;
+ /// This folder is in favorites view
+ const nsMsgFolderFlagType Favorite = 0x80000000;
+ /// Special-use folders
+ const nsMsgFolderFlagType SpecialUse = Inbox|Drafts|Trash|SentMail|
+ Templates|Junk|Archive|Queue;
+ /** @} */
+};
diff --git a/mailnews/base/public/nsMsgGroupnameFlags.h b/mailnews/base/public/nsMsgGroupnameFlags.h
new file mode 100644
index 000000000..59061e2ca
--- /dev/null
+++ b/mailnews/base/public/nsMsgGroupnameFlags.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _msgGroupnameFlags_h_
+#define _msgGroupnameFlags_h_
+
+
+/* Flags in the subscribe pane (used inside of MSG_GroupNameLine). Where
+ the flags overlap with the nsMsgFolderFlags flags, it has the same value,
+ to reduce the chance of someone using the wrong constant. */
+
+#define MSG_GROUPNAME_FLAG_ELIDED 0x0010 /* Whether the children of this
+ group are currently hidden
+ in the listing. This will
+ only be present if it has
+ any children. */
+
+#define MSG_GROUPNAME_FLAG_MODERATED 0x0020 /* Whether this folder
+ represents a moderated
+ newsgroup. */
+#define MSG_GROUPNAME_FLAG_SUBSCRIBED 0x0040 /* Whether this folder
+ represents a subscribed
+ newsgroup. */
+#define MSG_GROUPNAME_FLAG_NEW_GROUP 0x0080 /* A newsgroup which has just
+ been added by the `Check
+ New Groups' command. */
+#define MSG_GROUPNAME_FLAG_HASCHILDREN 0x40000 /* Whether there are children
+ of this group. Whether those
+ chilren are visible in this
+ list is determined by the
+ above "ELIDED" flag.
+ Setting this to the same value
+ as an nsMsgFolderFlags IMAP server,
+ since an IMAP _server_ will never
+ appear in the subscribe pane. */
+#define MSG_GROUPNAME_FLAG_IMAP_PERSONAL 0x80000 /* folder is an IMAP personal folder */
+
+#define MSG_GROUPNAME_FLAG_IMAP_PUBLIC 0x100000 /* folder is an IMAP public folder */
+
+#define MSG_GROUPNAME_FLAG_IMAP_OTHER_USER 0x200000 /* folder is another user's IMAP folder */
+
+#define MSG_GROUPNAME_FLAG_IMAP_NOSELECT 0x400000 /* A \NoSelect IMAP folder */
+
+#define MSG_GROUPNAME_FLAG_PERSONAL_SHARED 0x800000 /* whether or not this folder is one of your personal folders that
+ ` is shared with other users */
+
+#endif
diff --git a/mailnews/base/public/nsMsgHeaderMasks.h b/mailnews/base/public/nsMsgHeaderMasks.h
new file mode 100644
index 000000000..5ffeb3a53
--- /dev/null
+++ b/mailnews/base/public/nsMsgHeaderMasks.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _msgHeaderMasks_h_
+#define _msgHeaderMasks_h_
+
+DO NOT USE ANYMORE!!!
+/* This set enumerates the header fields which may be displayed in the
+ message composition window.
+ */
+#define MSG_FROM_HEADER_MASK 0x00000001
+#define MSG_REPLY_TO_HEADER_MASK 0x00000002
+#define MSG_TO_HEADER_MASK 0x00000004
+#define MSG_CC_HEADER_MASK 0x00000008
+#define MSG_BCC_HEADER_MASK 0x00000010
+#define MSG_FCC_HEADER_MASK 0x00000020
+#define MSG_NEWSGROUPS_HEADER_MASK 0x00000040
+#define MSG_FOLLOWUP_TO_HEADER_MASK 0x00000080
+#define MSG_SUBJECT_HEADER_MASK 0x00000100
+#define MSG_ATTACHMENTS_HEADER_MASK 0x00000200
+
+/* These next four are typically not ever displayed in the UI, but are still
+ stored and used internally. */
+#define MSG_ORGANIZATION_HEADER_MASK 0x00000400
+#define MSG_REFERENCES_HEADER_MASK 0x00000800
+#define MSG_OTHERRANDOMHEADERS_HEADER_MASK 0x00001000
+#define MSG_NEWSPOSTURL_HEADER_MASK 0x00002000
+
+#define MSG_PRIORITY_HEADER_MASK 0x00004000
+//#define MSG_NEWS_FCC_HEADER_MASK 0x00008000
+//#define MSG_MESSAGE_ENCODING_HEADER_MASK 0x00010000
+#define MSG_CHARACTER_SET_HEADER_MASK 0x00008000
+#define MSG_MESSAGE_ID_HEADER_MASK 0x00010000
+//#define MSG_NEWS_BCC_HEADER_MASK 0x00080000
+
+/* This is also not exposed to the UI; it's used internally to help remember
+ whether the original message had an HTML portion that we can quote. */
+//#define MSG_HTML_PART_HEADER_MASK 0x00100000
+
+/* The "body=" pseudo-header (as in "mailto:me?body=hi+there") */
+//#define MSG_DEFAULTBODY_HEADER_MASK 0x00200000
+
+#define MSG_X_TEMPLATE_HEADER_MASK 0x00020000
+
+#define MSG_FCC2_HEADER_MASK 0x00400000
+
+/* IMAP folders for posting */
+//#define MSG_IMAP_FOLDER_HEADER_MASK 0x02000000
+
+
+#endif
diff --git a/mailnews/base/public/nsMsgLocalFolderHdrs.h b/mailnews/base/public/nsMsgLocalFolderHdrs.h
new file mode 100644
index 000000000..e34c7fc70
--- /dev/null
+++ b/mailnews/base/public/nsMsgLocalFolderHdrs.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgLocalFolderHdrs_H
+#define _nsMsgLocalFolderHdrs_H
+
+/* The Netscape-specific header fields that we use for storing our
+ various bits of state in mail folders.
+ */
+#define X_MOZILLA_STATUS "X-Mozilla-Status"
+#define X_MOZILLA_STATUS_FORMAT X_MOZILLA_STATUS ": %04.4x"
+#define X_MOZILLA_STATUS_LEN /*1234567890123456*/ 16
+
+#define X_MOZILLA_STATUS2 "X-Mozilla-Status2"
+#define X_MOZILLA_STATUS2_FORMAT X_MOZILLA_STATUS2 ": %08.8x"
+#define X_MOZILLA_STATUS2_LEN /*12345678901234567*/ 17
+
+#define X_MOZILLA_DRAFT_INFO "X-Mozilla-Draft-Info"
+#define X_MOZILLA_DRAFT_INFO_LEN /*12345678901234567890*/ 20
+
+#define X_MOZILLA_NEWSHOST "X-Mozilla-News-Host"
+#define X_MOZILLA_NEWSHOST_LEN /*1234567890123456789*/ 19
+
+#define X_UIDL "X-UIDL"
+#define X_UIDL_LEN /*123456*/ 6
+
+#define CONTENT_LENGTH "Content-Length"
+#define CONTENT_LENGTH_LEN /*12345678901234*/ 14
+
+/* Provide a common means of detecting empty lines in a message. i.e. to detect the end of headers among other things...*/
+#define EMPTY_MESSAGE_LINE(buf) (buf[0] == '\r' || buf[0] == '\n' || buf[0] == '\0')
+
+/* blank filled header to store keyword/tags in the mailbox */
+#define X_MOZILLA_KEYWORDS "X-Mozilla-Keys" ": " MSG_LINEBREAK
+#define X_MOZILLA_KEYWORDS_LEN (sizeof(X_MOZILLA_KEYWORDS) - 1)
+
+#endif
diff --git a/mailnews/base/public/nsMsgMessageFlags.idl b/mailnews/base/public/nsMsgMessageFlags.idl
new file mode 100644
index 000000000..4139e47f9
--- /dev/null
+++ b/mailnews/base/public/nsMsgMessageFlags.idl
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+typedef unsigned long nsMsgMessageFlagType;
+
+/// Flags about a single message.
+[scriptable,uuid(1ea3acdb-7b9f-4e35-9513-76e0a0cc6baa)]
+interface nsMsgMessageFlags
+{
+ /// This message has been read
+ const nsMsgMessageFlagType Read = 0x00000001;
+
+ /// A reply to this message has been successfully sent
+ const nsMsgMessageFlagType Replied = 0x00000002;
+
+ /// This message has been flagged
+ const nsMsgMessageFlagType Marked = 0x00000004;
+
+ /**
+ * This message has already gone, but the folder hasn't been compacted yet.
+ * Since actually removing a message from a folder is a semi-expensive
+ * operation, we tend to delay it; messages with this bit set will be removed
+ * the next time folder compaction is done. Once this bit is set, it never
+ * gets un-set.
+ */
+ const nsMsgMessageFlagType Expunged = 0x00000008;
+
+ /**
+ * The subject of this message has "Re:" on the front. The folder summary
+ * uniquifies all of the strings in it, and to help this, any string which
+ * begins with "Re:" has that stripped first. This bit is then set, so that
+ * when presenting the message, we know to put it back (since the "Re:" is
+ * not itself stored in the file.)
+ */
+ const nsMsgMessageFlagType HasRe = 0x00000010;
+
+ /// The children of this sub-thread are folded in the display
+ const nsMsgMessageFlagType Elided = 0x00000020;
+
+ /// The message is a feed, originally downloaded in a server.type=rss account
+ const nsMsgMessageFlagType FeedMsg = 0x00000040;
+
+ /// This news article or IMAP message is present in the disk cache
+ const nsMsgMessageFlagType Offline = 0x00000080;
+
+ /// This thread is being watched
+ const nsMsgMessageFlagType Watched = 0x00000100;
+
+ /// This message's sender has been authenticated when sending this message
+ const nsMsgMessageFlagType SenderAuthed = 0x00000200;
+
+ /**
+ * This message's body is only the first ten or so of the message, and we
+ * need to add a link to let the user download the rest of it from the POP
+ * server.
+ */
+ const nsMsgMessageFlagType Partial = 0x00000400;
+
+ /**
+ * This message is queued for delivery. This only ever gets set on messages
+ * in the queue folder, but is used to protect against the case of othe
+ * messages having made their way in there somehow -- if some other program
+ * put a message in the queue, we don't want to later deliver it!
+ */
+ const nsMsgMessageFlagType Queued = 0x00000800;
+
+ /// This message has been forwarded
+ const nsMsgMessageFlagType Forwarded = 0x00001000;
+
+ /**
+ * These are used to remember the message priority in the mozilla status
+ * flags, so we can regenerate a priority after a rule (or user) has changed
+ * it. They are not returned in MSG_MessageLine.flags, just in mozilla-status,
+ * so if you need more non-persistent flags, you could share these bits. But
+ * it would be wrong.
+ */
+ const nsMsgMessageFlagType Priorities = 0x0000E000;
+
+ /// This message is new since the last time the folder was closed
+ const nsMsgMessageFlagType New = 0x00010000;
+
+ /// This thread has been ignored
+ const nsMsgMessageFlagType Ignored = 0x00040000;
+
+ /// This IMAP message has been marked deleted on the server
+ const nsMsgMessageFlagType IMAPDeleted = 0x00200000;
+
+ /**
+ * This message has requested to send a message delivery notification to its
+ * sender
+ */
+ const nsMsgMessageFlagType MDNReportNeeded = 0x00400000;
+
+ /**
+ * A message delivery notification has been sent for this message. No more
+ * reports should be sent.
+ */
+ const nsMsgMessageFlagType MDNReportSent = 0x00800000;
+
+ /// This message is a template
+ const nsMsgMessageFlagType Template = 0x01000000;
+
+ /// This message has files attached to it
+ const nsMsgMessageFlagType Attachment = 0x10000000;
+
+ /**
+ * These are used to remember the message labels in the mozilla status2
+ * flags. so we can regenerate a priority after a rule (or user) has changed
+ * it. They are not returned in nsMsgHdr.flags, just in mozilla-status2, so
+ * if you need more non-persistent flags, you could share these bits. But it
+ * would be wrong.
+ */
+ const nsMsgMessageFlagType Labels = 0x0E000000;
+
+ // We're trying to reserve the high byte of the flags for view flags, so,
+ // don't add flags to the high byte if possible.
+
+ /// The list of all message flags to not write to disk
+ const nsMsgMessageFlagType RuntimeOnly = Elided;
+};
+
+typedef unsigned long nsMsgProcessingFlagType;
+
+/**
+ * Definitions of processing flags. These flags are not saved to the database.
+ * They are used to define states for message processing. Any changes
+ * to these flags need to be supported in the key sets in nsMsgDBFolder
+ */
+[scriptable,uuid(1f7d642b-de2a-45f0-a27f-9c9ce0b741d8)]
+interface nsMsgProcessingFlags
+{
+ /// This message needs junk classification
+ const nsMsgProcessingFlagType ClassifyJunk = 0x00000001;
+
+ /// This message needs traits classification
+ const nsMsgProcessingFlagType ClassifyTraits = 0x00000002;
+
+ /// This message has completed any needed traits classification
+ const nsMsgProcessingFlagType TraitsDone = 0x00000004;
+
+ /// This message has completed any needed postPlugin filtering
+ const nsMsgProcessingFlagType FiltersDone = 0x00000008;
+
+ /// This message has a move scheduled by filters
+ const nsMsgProcessingFlagType FilterToMove = 0x00000010;
+
+ /**
+ * This message is new to the folder and has yet to be reported via the
+ * msgsClassified notification. This flag is required because the previously
+ * used mechanism relied on the database's list of new messages and its
+ * concept of 'new' is overloaded and has user-visible ramifications. This
+ * led to messages potentially being considered multiple times.
+ *
+ * Unfortunately none of the Done processing flags above are suitable for our
+ * needs because they are not consistently applied and basically constitute
+ * memory leaks (which makes the not consistently applied thing a good
+ * thing.)
+ *
+ * I suspect we cannot reliably convert the Done flags above to our use case
+ * either because of the situation where the user quits the program after the
+ * messages are added but before the messages are processed. Since the
+ * processing flags are suppression flags, assuming the 'new' status is
+ * persisted to the next time we are run, then this would represent a
+ * change in behaviour. I would need to exactly understand the new semantics
+ * to know for sure though.
+ */
+ const nsMsgProcessingFlagType NotReportedClassified = 0x00000020;
+
+ /// Number of processing flags
+ const nsMsgProcessingFlagType NumberOfFlags = 6;
+};
diff --git a/mailnews/base/search/content/CustomHeaders.js b/mailnews/base/search/content/CustomHeaders.js
new file mode 100644
index 000000000..174ba100e
--- /dev/null
+++ b/mailnews/base/search/content/CustomHeaders.js
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gAddButton;
+var gRemoveButton;
+var gHeaderInputElement;
+var gArrayHdrs;
+var gHdrsList;
+var gContainer;
+var gFilterBundle=null;
+var gCustomBundle=null;
+
+function onLoad()
+{
+ let hdrs = Services.prefs.getCharPref("mailnews.customHeaders");
+ gHeaderInputElement = document.getElementById("headerInput");
+ gHeaderInputElement.focus();
+
+ gHdrsList = document.getElementById("headerList");
+ gArrayHdrs = new Array();
+ gAddButton = document.getElementById("addButton");
+ gRemoveButton = document.getElementById("removeButton");
+
+ initializeDialog(hdrs);
+ updateAddButton(true);
+ updateRemoveButton();
+}
+
+function initializeDialog(hdrs)
+{
+ if (hdrs)
+ {
+ hdrs = hdrs.replace(/\s+/g,''); //remove white spaces before splitting
+ gArrayHdrs = hdrs.split(":");
+ for (var i = 0; i < gArrayHdrs.length; i++)
+ if (!gArrayHdrs[i])
+ gArrayHdrs.splice(i,1); //remove any null elements
+ initializeRows();
+ }
+}
+
+function initializeRows()
+{
+ for (var i = 0; i < gArrayHdrs.length; i++)
+ addRow(TrimString(gArrayHdrs[i]));
+}
+
+function onTextInput()
+{
+ // enable the add button if the user has started to type text
+ updateAddButton( (gHeaderInputElement.value == "") );
+}
+
+function onOk()
+{
+ if (gArrayHdrs.length)
+ {
+ var hdrs;
+ if (gArrayHdrs.length == 1)
+ hdrs = gArrayHdrs;
+ else
+ hdrs = gArrayHdrs.join(": ");
+ Services.prefs.setCharPref("mailnews.customHeaders", hdrs);
+ // flush prefs to disk, in case we crash, to avoid dataloss and problems with filters that use the custom headers
+ Services.prefs.savePrefFile(null);
+ }
+ else
+ {
+ Services.prefs.clearUserPref("mailnews.customHeaders"); //clear the pref, no custom headers
+ }
+
+ window.arguments[0].selectedVal = gHdrsList.selectedItem ? gHdrsList.selectedItem.label : null;
+ return true;
+}
+
+function customHeaderOverflow()
+{
+ var nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
+ if (gArrayHdrs.length >= (nsMsgSearchAttrib.kNumMsgSearchAttributes - nsMsgSearchAttrib.OtherHeader - 1))
+ {
+ if (!gFilterBundle)
+ gFilterBundle = document.getElementById("bundle_filter");
+
+ var alertText = gFilterBundle.getString("customHeaderOverflow");
+ Services.prompt.alert(window, null, alertText);
+ return true;
+ }
+ return false;
+}
+
+function onAddHeader()
+{
+ var newHdr = TrimString(gHeaderInputElement.value);
+
+ if (!isRFC2822Header(newHdr)) // if user entered an invalid rfc822 header field name, bail out.
+ {
+ if (!gCustomBundle)
+ gCustomBundle = document.getElementById("bundle_custom");
+
+ var alertText = gCustomBundle.getString("colonInHeaderName");
+ Services.prompt.alert(window, null, alertText);
+ return;
+ }
+
+ gHeaderInputElement.value = "";
+ if (!newHdr || customHeaderOverflow())
+ return;
+ if (!duplicateHdrExists(newHdr))
+ {
+ gArrayHdrs[gArrayHdrs.length] = newHdr;
+ var newItem = addRow(newHdr);
+ gHdrsList.selectItem (newItem); // make sure the new entry is selected in the tree
+ // now disable the add button
+ updateAddButton(true);
+ gHeaderInputElement.focus(); // refocus the input field for the next custom header
+ }
+}
+
+function isRFC2822Header(hdr)
+{
+ var charCode;
+ for (var i = 0; i < hdr.length; i++)
+ {
+ charCode = hdr.charCodeAt(i);
+ //58 is for colon and 33 and 126 are us-ascii bounds that should be used for header field name, as per rfc2822
+
+ if (charCode < 33 || charCode == 58 || charCode > 126)
+ return false;
+ }
+ return true;
+}
+
+function duplicateHdrExists(hdr)
+{
+ for (var i = 0;i < gArrayHdrs.length; i++)
+ {
+ if (gArrayHdrs[i] == hdr)
+ return true;
+ }
+ return false;
+}
+
+function onRemoveHeader()
+{
+ var listitem = gHdrsList.selectedItems[0]
+ if (!listitem) return;
+ listitem.remove();
+ var selectedHdr = GetListItemAttributeStr(listitem);
+ var j=0;
+ for (var i = 0; i < gArrayHdrs.length; i++)
+ {
+ if (gArrayHdrs[i] == selectedHdr)
+ {
+ gArrayHdrs.splice(i,1);
+ break;
+ }
+ }
+}
+
+function GetListItemAttributeStr(listitem)
+{
+ if (listitem)
+ return TrimString(listitem.getAttribute("label"));
+
+ return "";
+}
+
+function addRow(newHdr)
+{
+ var listitem = document.createElement("listitem");
+ listitem.setAttribute("label", newHdr);
+ gHdrsList.appendChild(listitem);
+ return listitem;
+}
+
+function updateAddButton(aDisable)
+{
+ // only update the button if the disabled state changed
+ if (aDisable == gAddButton.disabled)
+ return;
+
+ gAddButton.disabled = aDisable;
+ document.documentElement.defaultButton = aDisable ? "accept" : "extra1";
+}
+
+function updateRemoveButton()
+{
+ var headerSelected = (gHdrsList.selectedItems.length > 0);
+ gRemoveButton.disabled = !headerSelected;
+ if (gRemoveButton.disabled)
+ gHeaderInputElement.focus();
+}
+
+//Remove whitespace from both ends of a string
+function TrimString(string)
+{
+ if (!string) return "";
+ return string.trim();
+}
diff --git a/mailnews/base/search/content/CustomHeaders.xul b/mailnews/base/search/content/CustomHeaders.xul
new file mode 100644
index 000000000..c13a8dba8
--- /dev/null
+++ b/mailnews/base/search/content/CustomHeaders.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/CustomHeaders.dtd">
+<dialog id="customHeadersDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ ondialogaccept="return onOk();"
+ ondialogextra1="onAddHeader();"
+ ondialogextra2="onRemoveHeader();"
+ style="width: 30em; height: 25em;"
+ persist="width height screenX screenY"
+ title="&window.title;"
+ buttons="accept,cancel,extra1,extra2">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_filter" src="chrome://messenger/locale/filter.properties"/>
+ <stringbundle id="bundle_custom" src="chrome://messenger/locale/custom.properties"/>
+ </stringbundleset>
+
+ <script type="application/javascript" src="chrome://messenger/content/CustomHeaders.js"/>
+
+ <grid flex="1">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row>
+ <label accesskey="&newMsgHeader.accesskey;" control="headerInput" value="&newMsgHeader.label;"/>
+ </row>
+ <row>
+ <textbox id="headerInput" onfocus="this.select();" oninput="onTextInput();"/>
+ </row>
+
+ <row flex="1">
+ <vbox>
+ <listbox id="headerList" flex="1" onselect="updateRemoveButton();" />
+ </vbox>
+
+ <vbox>
+ <button id="addButton"
+ label="&addButton.label;"
+ accesskey="&addButton.accesskey;"
+ dlgtype="extra1"/>
+ <button id="removeButton"
+ label="&removeButton.label;"
+ accesskey="&removeButton.accesskey;"
+ dlgtype="extra2"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+</dialog>
diff --git a/mailnews/base/search/content/FilterEditor.js b/mailnews/base/search/content/FilterEditor.js
new file mode 100644
index 000000000..ecfe9febc
--- /dev/null
+++ b/mailnews/base/search/content/FilterEditor.js
@@ -0,0 +1,856 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/MailUtils.js");
+
+// The actual filter that we're editing if it is a _saved_ filter or prefill;
+// void otherwise.
+var gFilter;
+// cache the key elements we need
+var gFilterList;
+// The filter name as it appears in the "Filter Name" field of dialog.
+var gFilterNameElement;
+var gFilterTypeSelector;
+var gFilterBundle;
+var gPreFillName;
+var gSessionFolderListenerAdded = false;
+var gFilterActionList;
+var gCustomActions = null;
+var gFilterType;
+var gFilterPosition = 0;
+
+var gFilterActionStrings = ["none", "movemessage", "setpriorityto", "deletemessage",
+ "markasread", "ignorethread", "watchthread", "markasflagged",
+ "label", "replytomessage", "forwardmessage", "stopexecution",
+ "deletefrompopserver", "leaveonpopserver", "setjunkscore",
+ "fetchfrompopserver", "copymessage", "addtagtomessage",
+ "ignoresubthread", "markasunread"];
+
+// A temporary filter with the current state of actions in the UI.
+var gTempFilter = null;
+// A nsIArray of the currently defined actions in the order they will be run.
+var gActionListOrdered = null;
+
+var gFilterEditorMsgWindow = null;
+
+var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
+var nsMsgFilterType = Components.interfaces.nsMsgFilterType;
+var nsIMsgRuleAction = Components.interfaces.nsIMsgRuleAction;
+var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
+
+function filterEditorOnLoad()
+{
+ getCustomActions();
+ initializeSearchWidgets();
+ initializeFilterWidgets();
+
+ gFilterBundle = document.getElementById("bundle_filter");
+
+ if ("arguments" in window && window.arguments[0])
+ {
+ var args = window.arguments[0];
+
+ if ("filterList" in args)
+ {
+ gFilterList = args.filterList;
+ // the postPlugin filters cannot be applied to servers that are
+ // deferred, (you must define them on the deferredTo server instead).
+ let server = gFilterList.folder.server;
+ if (server.rootFolder != server.rootMsgFolder)
+ gFilterTypeSelector.disableDeferredAccount();
+ }
+
+ if ("filterPosition" in args)
+ {
+ gFilterPosition = args.filterPosition;
+ }
+
+ if ("filter" in args)
+ {
+ // editing a filter
+ gFilter = window.arguments[0].filter;
+ initializeDialog(gFilter);
+ }
+ else
+ {
+ if (gFilterList)
+ setSearchScope(getScopeFromFilterList(gFilterList));
+ // if doing prefill filter create a new filter and populate it.
+ if ("filterName" in args)
+ {
+ gPreFillName = args.filterName;
+
+ // Passing null as the parameter to createFilter to keep the name empty
+ // until later where we assign the name.
+ gFilter = gFilterList.createFilter(null);
+
+ var term = gFilter.createTerm();
+
+ term.attrib = Components.interfaces.nsMsgSearchAttrib.Default;
+ if (("fieldName" in args) && args.fieldName) {
+ // fieldName should contain the name of the field in which to search,
+ // from nsMsgSearchTerm.cpp::SearchAttribEntryTable, e.g. "to" or "cc"
+ try {
+ term.attrib = term.getAttributeFromString(args.fieldName);
+ } catch (e) { /* Invalid string is fine, just ignore it. */ }
+ }
+ if (term.attrib == Components.interfaces.nsMsgSearchAttrib.Default)
+ term.attrib = Components.interfaces.nsMsgSearchAttrib.Sender;
+
+ term.op = Components.interfaces.nsMsgSearchOp.Is;
+ term.booleanAnd = gSearchBooleanRadiogroup.value == "and";
+
+ var termValue = term.value;
+ termValue.attrib = term.attrib;
+ termValue.str = gPreFillName;
+
+ term.value = termValue;
+
+ gFilter.appendTerm(term);
+
+ // the default action for news filters is Delete
+ // for everything else, it's MoveToFolder
+ var filterAction = gFilter.createAction();
+ filterAction.type = (getScopeFromFilterList(gFilterList) ==
+ nsMsgSearchScope.newsFilter) ?
+ nsMsgFilterAction.Delete : nsMsgFilterAction.MoveToFolder;
+ gFilter.appendAction(filterAction);
+ initializeDialog(gFilter);
+ }
+ else if ("copiedFilter" in args)
+ {
+ // we are copying a filter
+ let copiedFilter = args.copiedFilter;
+ let copiedName = gFilterBundle.getFormattedString("copyToNewFilterName",
+ [copiedFilter.filterName]);
+ let newFilter = gFilterList.createFilter(copiedName);
+
+ // copy the actions
+ for (let i = 0; i < copiedFilter.actionCount; i++)
+ {
+ let filterAction = copiedFilter.getActionAt(i);
+ newFilter.appendAction(filterAction);
+ }
+
+ // copy the search terms
+ for (let i = 0; i < copiedFilter.searchTerms.Count(); i++)
+ {
+ var searchTerm = copiedFilter.searchTerms.QueryElementAt(i,
+ Components.interfaces.nsIMsgSearchTerm);
+
+ let newTerm = newFilter.createTerm();
+ newTerm.attrib = searchTerm.attrib;
+ newTerm.op = searchTerm.op;
+ newTerm.booleanAnd = searchTerm.booleanAnd;
+ newTerm.value = searchTerm.value;
+ newFilter.appendTerm(newTerm);
+ }
+
+ newFilter.filterType = copiedFilter.filterType;
+
+ gPreFillName = copiedName;
+ gFilter = newFilter;
+
+ initializeDialog(gFilter);
+
+ // We reset the filter name, because otherwise the saveFilter()
+ // function thinks we are editing a filter, and will thus skip the name
+ // uniqueness check.
+ gFilter.filterName = "";
+ }
+ else
+ {
+ // fake the first more button press
+ onMore(null);
+ }
+ }
+ }
+
+ if (!gFilter)
+ {
+ // This is a new filter. Set to both Incoming and Manual contexts.
+ gFilterTypeSelector.setType(nsMsgFilterType.Incoming | nsMsgFilterType.Manual);
+ }
+
+ // in the case of a new filter, we may not have an action row yet.
+ ensureActionRow();
+ gFilterType = gFilterTypeSelector.getType();
+
+ gFilterNameElement.select();
+ // This call is required on mac and linux. It has no effect under win32. See bug 94800.
+ gFilterNameElement.focus();
+}
+
+function filterEditorOnUnload()
+{
+ if (gSessionFolderListenerAdded)
+ MailServices.mailSession.RemoveFolderListener(gFolderListener);
+}
+
+function onEnterInSearchTerm(event)
+{
+ if (event.ctrlKey || (Services.appinfo.OS == "Darwin" && event.metaKey)) {
+ // If accel key (Ctrl on Win/Linux, Cmd on Mac) was held too, accept the dialog.
+ document.getElementById("FilterEditor").acceptDialog();
+ } else {
+ // If only plain Enter was pressed, add a new rule line.
+ onMore(event);
+ }
+}
+
+function onAccept()
+{
+ try {
+ if (!saveFilter())
+ return false;
+ } catch(e) {Components.utils.reportError(e); return false;}
+
+ // parent should refresh filter list..
+ // this should REALLY only happen when some criteria changes that
+ // are displayed in the filter dialog, like the filter name
+ window.arguments[0].refresh = true;
+ window.arguments[0].newFilter = gFilter;
+ return true;
+}
+
+// the folderListener object
+var gFolderListener = {
+ OnItemAdded: function(parentItem, item) {},
+
+ OnItemRemoved: function(parentItem, item){},
+
+ OnItemPropertyChanged: function(item, property, oldValue, newValue) {},
+
+ OnItemIntPropertyChanged: function(item, property, oldValue, newValue) {},
+
+ OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) {},
+
+ OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue){},
+ OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
+
+ OnItemEvent: function(folder, event)
+ {
+ var eventType = event.toString();
+
+ if (eventType == "FolderCreateCompleted")
+ {
+ gActionTargetElement.selectFolder(folder);
+ SetBusyCursor(window, false);
+ }
+ else if (eventType == "FolderCreateFailed")
+ SetBusyCursor(window, false);
+ }
+}
+
+function duplicateFilterNameExists(filterName)
+{
+ if (gFilterList)
+ for (var i = 0; i < gFilterList.filterCount; i++)
+ if (filterName == gFilterList.getFilterAt(i).filterName)
+ return true;
+ return false;
+}
+
+function getScopeFromFilterList(filterList)
+{
+ if (!filterList)
+ {
+ dump("yikes, null filterList\n");
+ return nsMsgSearchScope.offlineMail;
+ }
+ return filterList.folder.server.filterScope;
+}
+
+function getScope(filter)
+{
+ return getScopeFromFilterList(filter.filterList);
+}
+
+function initializeFilterWidgets()
+{
+ gFilterNameElement = document.getElementById("filterName");
+ gFilterActionList = document.getElementById("filterActionList");
+ initializeFilterTypeSelector();
+}
+
+function initializeFilterTypeSelector()
+{
+ /**
+ * This object controls code interaction with the widget allowing specifying
+ * the filter type (event when the filter is run).
+ */
+ gFilterTypeSelector = {
+ checkBoxManual: document.getElementById("runManual"),
+ checkBoxIncoming : document.getElementById("runIncoming"),
+
+ menulistIncoming: document.getElementById("pluginsRunOrder"),
+
+ menuitemBeforePlugins: document.getElementById("runBeforePlugins"),
+ menuitemAfterPlugins: document.getElementById("runAfterPlugins"),
+
+ checkBoxArchive: document.getElementById("runArchive"),
+ checkBoxOutgoing: document.getElementById("runOutgoing"),
+
+ /**
+ * Returns the currently set filter type (checkboxes) in terms
+ * of a Components.interfaces.nsMsgFilterType value.
+ */
+ getType: function()
+ {
+ let type = nsMsgFilterType.None;
+
+ if (this.checkBoxManual.checked)
+ type |= nsMsgFilterType.Manual;
+
+ if (this.checkBoxIncoming.checked) {
+ if (this.menulistIncoming.selectedItem == this.menuitemAfterPlugins) {
+ type |= nsMsgFilterType.PostPlugin;
+ } else {
+ // this.menuitemBeforePlugins selected
+ if (getScopeFromFilterList(gFilterList) ==
+ nsMsgSearchScope.newsFilter)
+ type |= nsMsgFilterType.NewsRule;
+ else
+ type |= nsMsgFilterType.InboxRule;
+ }
+ }
+
+ if (this.checkBoxArchive.checked)
+ type |= nsMsgFilterType.Archive;
+
+ if (this.checkBoxOutgoing.checked)
+ type |= nsMsgFilterType.PostOutgoing;
+
+ return type;
+ },
+
+ /**
+ * Sets the checkboxes to represent the filter type passed in.
+ *
+ * @param aType the filter type to set in terms
+ * of Components.interfaces.nsMsgFilterType values.
+ */
+ setType: function(aType)
+ {
+ // If there is no type (event) requested, force "when manually run"
+ if (aType == nsMsgFilterType.None)
+ aType = nsMsgFilterType.Manual;
+
+ this.checkBoxManual.checked = aType & nsMsgFilterType.Manual;
+
+ this.checkBoxIncoming.checked = aType & (nsMsgFilterType.PostPlugin |
+ nsMsgFilterType.Incoming);
+
+ this.menulistIncoming.selectedItem = aType & nsMsgFilterType.PostPlugin ?
+ this.menuitemAfterPlugins : this.menuitemBeforePlugins;
+
+ this.checkBoxArchive.checked = aType & nsMsgFilterType.Archive;
+
+ this.checkBoxOutgoing.checked = aType & nsMsgFilterType.PostOutgoing;
+
+ this.updateClassificationMenu();
+ },
+
+ /**
+ * Enable the "before/after classification" menulist depending on
+ * whether "run when incoming mail" is selected.
+ */
+ updateClassificationMenu: function()
+ {
+ this.menulistIncoming.disabled = !this.checkBoxIncoming.checked;
+ updateFilterType();
+ },
+
+ /**
+ * Disable the options unsuitable for deferred accounts.
+ */
+ disableDeferredAccount: function()
+ {
+ this.menuitemAfterPlugins.disabled = true;
+ this.checkBoxOutgoing.disabled = true;
+ }
+ };
+}
+
+function initializeDialog(filter)
+{
+ gFilterNameElement.value = filter.filterName;
+ let filterType = filter.filterType;
+ gFilterTypeSelector.setType(filter.filterType);
+
+ let numActions = filter.actionCount;
+ for (let actionIndex = 0; actionIndex < numActions; actionIndex++)
+ {
+ let filterAction = filter.getActionAt(actionIndex);
+
+ var newActionRow = document.createElement('listitem');
+ newActionRow.setAttribute('initialActionIndex', actionIndex);
+ newActionRow.className = 'ruleaction';
+ gFilterActionList.appendChild(newActionRow);
+ newActionRow.setAttribute('value',
+ filterAction.type == nsMsgFilterAction.Custom ?
+ filterAction.customId : gFilterActionStrings[filterAction.type]);
+ newActionRow.setAttribute('onfocus', 'this.storeFocus();');
+ }
+
+ var gSearchScope = getFilterScope(getScope(filter), filter.filterType, filter.filterList);
+ initializeSearchRows(gSearchScope, filter.searchTerms);
+ setFilterScope(filter.filterType, filter.filterList);
+}
+
+function ensureActionRow()
+{
+ // make sure we have at least one action row visible to the user
+ if (!gFilterActionList.getRowCount())
+ {
+ var newActionRow = document.createElement('listitem');
+ newActionRow.className = 'ruleaction';
+ gFilterActionList.appendChild(newActionRow);
+ newActionRow.mRemoveButton.disabled = true;
+ }
+}
+
+// move to overlay
+function saveFilter()
+{
+ // See if at least one filter type (activation event) is selected.
+ if (gFilterType == nsMsgFilterType.None) {
+ Services.prompt.alert(window,
+ gFilterBundle.getString("mustHaveFilterTypeTitle"),
+ gFilterBundle.getString("mustHaveFilterTypeMessage"));
+ return false;
+ }
+
+ let filterName = gFilterNameElement.value;
+ // If we think have a duplicate, then we need to check that if we
+ // have an original filter name (i.e. we are editing a filter), then
+ // we must check that the original is not the current as that is what
+ // the duplicateFilterNameExists function will have picked up.
+ if ((!gFilter || gFilter.filterName != filterName) && duplicateFilterNameExists(filterName))
+ {
+ Services.prompt.alert(window,
+ gFilterBundle.getString("cannotHaveDuplicateFilterTitle"),
+ gFilterBundle.getString("cannotHaveDuplicateFilterMessage"));
+ return false;
+ }
+
+ // Check that all of the search attributes and operators are valid.
+ function rule_desc(index, obj) {
+ return (index + 1) + " (" + obj.searchattribute.label + ", " + obj.searchoperator.label + ")";
+ }
+
+ let invalidRule = false;
+ for (let index = 0; index < gSearchTerms.length; index++)
+ {
+ let obj = gSearchTerms[index].obj;
+ // We don't need to check validity of matchAll terms
+ if (obj.matchAll)
+ continue;
+
+ // the term might be an offscreen one that we haven't initialized yet
+ let searchTerm = obj.searchTerm;
+ if (!searchTerm && !gSearchTerms[index].initialized)
+ continue;
+
+ if (isNaN(obj.searchattribute.value)) // is this a custom term?
+ {
+ let customTerm = MailServices.filters.getCustomTerm(obj.searchattribute.value);
+ if (!customTerm)
+ {
+ invalidRule = true;
+ Components.utils.reportError("Filter not saved because custom search term '" +
+ obj.searchattribute.value + "' in rule " + rule_desc(index, obj) + " not found");
+ }
+ else
+ {
+ if (!customTerm.getAvailable(obj.searchScope, obj.searchattribute.value))
+ {
+ invalidRule = true;
+ Components.utils.reportError("Filter not saved because custom search term '" +
+ customTerm.name + "' in rule " + rule_desc(index, obj) + " not available");
+ }
+ }
+ }
+ else
+ {
+ let otherHeader = Components.interfaces.nsMsgSearchAttrib.OtherHeader;
+ let attribValue = (obj.searchattribute.value > otherHeader) ?
+ otherHeader : obj.searchattribute.value;
+ if (!obj.searchattribute
+ .validityTable
+ .getAvailable(attribValue, obj.searchoperator.value))
+ {
+ invalidRule = true;
+ Components.utils.reportError("Filter not saved because standard search term '" +
+ attribValue + "' in rule " + rule_desc(index, obj) + " not available in this context");
+ }
+ }
+
+ if (invalidRule) {
+ Services.prompt.alert(window,
+ gFilterBundle.getString("searchTermsInvalidTitle"),
+ gFilterBundle.getFormattedString("searchTermsInvalidRule",
+ [obj.searchattribute.label,
+ obj.searchoperator.label]));
+ return false;
+ }
+
+ }
+
+ // before we go any further, validate each specified filter action, abort the save
+ // if any of the actions is invalid...
+ for (let index = 0; index < gFilterActionList.itemCount; index++)
+ {
+ var listItem = gFilterActionList.getItemAtIndex(index);
+ if (!listItem.validateAction())
+ return false;
+ }
+
+ // if we made it here, all of the actions are valid, so go ahead and save the filter
+ let isNewFilter;
+ if (!gFilter)
+ {
+ // This is a new filter
+ gFilter = gFilterList.createFilter(filterName);
+ isNewFilter = true;
+ gFilter.enabled = true;
+ }
+ else
+ {
+ // We are working with an existing filter object,
+ // either editing or using prefill
+ gFilter.filterName = filterName;
+ //Prefilter is treated as a new filter.
+ if (gPreFillName)
+ {
+ isNewFilter = true;
+ gFilter.enabled = true;
+ }
+ else
+ isNewFilter = false;
+
+ gFilter.clearActionList();
+ }
+
+ // add each filteraction to the filter
+ for (let index = 0; index < gFilterActionList.itemCount; index++)
+ gFilterActionList.getItemAtIndex(index).saveToFilter(gFilter);
+
+ // If we do not have a filter name at this point, generate one.
+ if (!gFilter.filterName)
+ AssignMeaningfulName();
+
+ gFilter.filterType = gFilterType;
+ saveSearchTerms(gFilter.searchTerms, gFilter);
+
+ if (isNewFilter)
+ {
+ // new filter - insert into gFilterList
+ gFilterList.insertFilterAt(gFilterPosition, gFilter);
+ }
+
+ // success!
+ return true;
+}
+
+/**
+ * Check if the list of actions the user created will be executed in a different order.
+ * Exposes a note to the user if that is the case.
+ */
+function checkActionsReorder()
+{
+ setTimeout(_checkActionsReorder, 0);
+}
+
+/**
+ * This should be called from setTimeout otherwise some of the elements calling
+ * may not be fully initialized yet (e.g. we get ".saveToFilter is not a function").
+ * It is OK to schedule multiple timeouts with this function.
+ */
+function _checkActionsReorder() {
+ // Create a temporary disposable filter and add current actions to it.
+ if (!gTempFilter)
+ gTempFilter = gFilterList.createFilter("");
+ else
+ gTempFilter.clearActionList();
+
+ for (let index = 0; index < gFilterActionList.itemCount; index++)
+ gFilterActionList.getItemAtIndex(index).saveToFilter(gTempFilter);
+
+ // Now get the actions out of the filter in the order they will be executed in.
+ gActionListOrdered = gTempFilter.sortedActionList;
+
+ // Compare the two lists.
+ let statusBar = document.getElementById("statusbar");
+ for (let index = 0; index < gActionListOrdered.length; index++) {
+ if (index != gTempFilter.getActionIndex(
+ gActionListOrdered.queryElementAt(index, nsIMsgRuleAction)))
+ {
+ // If the lists are not the same unhide the status bar and show warning.
+ statusBar.style.visibility = "visible";
+ return;
+ }
+ }
+
+ statusBar.style.visibility = "hidden";
+}
+
+/**
+ * Show a dialog with the ordered list of actions.
+ * The fetching of action label and argument is separated from checkActionsReorder
+ * function to make that one more lightweight. The list is built only upon
+ * user request.
+ */
+function showActionsOrder()
+{
+ // Fetch the actions and arguments as a string.
+ let actionStrings = [];
+ for (let index = 0; index < gFilterActionList.itemCount; index++)
+ gFilterActionList.getItemAtIndex(index).getActionStrings(actionStrings);
+
+ // Present a nicely formatted list of action names and arguments.
+ let actionList = gFilterBundle.getString("filterActionOrderExplanation");
+ for (let i = 0; i < gActionListOrdered.length; i++) {
+ let actionIndex = gTempFilter.getActionIndex(
+ gActionListOrdered.queryElementAt(i, nsIMsgRuleAction));
+ let action = actionStrings[actionIndex];
+ actionList += gFilterBundle.getFormattedString("filterActionItem",
+ [(i + 1), action.label, action.argument]);
+ }
+
+ Services.prompt.confirmEx(window,
+ gFilterBundle.getString("filterActionOrderTitle"),
+ actionList, Services.prompt.BUTTON_TITLE_OK,
+ null, null, null, null, {value:false});
+}
+
+function AssignMeaningfulName()
+{
+ // termRoot points to the first search object, which is the one we care about.
+ let termRoot = gSearchTerms[0].obj;
+ // stub is used as the base name for a filter.
+ let stub;
+
+ // If this is a Match All Messages Filter, we already know the name to assign.
+ if (termRoot.matchAll)
+ stub = gFilterBundle.getString( "matchAllFilterName" );
+ else
+ {
+ // Assign a name based on the first search term.
+ let searchValue = termRoot.searchvalue;
+ let selIndex = searchValue.getAttribute( "selectedIndex" );
+ let children = document.getAnonymousNodes(searchValue);
+ let activeItem = children[selIndex];
+ let attribs = Components.interfaces.nsMsgSearchAttrib;
+
+ // Term, Operator and Value are the three parts of a filter match
+ // Term and Operator are easy to retrieve
+ let term = termRoot.searchattribute.label;
+ let operator = termRoot.searchoperator.label;
+
+ // Values are either popup menu items or edit fields.
+ // For popup menus use activeItem.label; for
+ // edit fields, activeItem.value
+ let value;
+ switch (Number(termRoot.searchattribute.value))
+ {
+ case attribs.Priority:
+ case attribs.MsgStatus:
+ case attribs.Keywords:
+ case attribs.HasAttachmentStatus:
+ case attribs.JunkStatus:
+ case attribs.JunkScoreOrigin:
+ if (activeItem)
+ value = activeItem.label;
+ else
+ value = "";
+ break;
+
+ default:
+ try
+ {
+ value = activeItem.value;
+ }
+ catch (ex)
+ {
+ // We should never get here, but for safety's sake,
+ // let's name the filter "Untitled Filter".
+ stub = gFilterBundle.getString( "untitledFilterName" );
+ // Do not 'Return'. Instead fall through and deal with the untitled filter below.
+ }
+ break;
+ }
+ // We are now ready to name the filter.
+ // If at this point stub is empty, we know that this is not a Match All Filter
+ // and is not an "untitledFilterName" Filter, so assign it a name using
+ // a string format from the Filter Bundle.
+ if (!stub)
+ stub = gFilterBundle.getFormattedString("filterAutoNameStr", [term, operator, value]);
+ }
+
+ // Whatever name we have used, 'uniquify' it.
+ let tempName = stub;
+ let count = 1;
+ while (duplicateFilterNameExists(tempName))
+ {
+ count++;
+ tempName = stub + " " + count;
+ }
+ gFilter.filterName = tempName;
+}
+
+
+function GetFirstSelectedMsgFolder()
+{
+ var selectedFolder = gActionTargetElement.getAttribute("uri");
+ if (!selectedFolder)
+ return null;
+
+ var msgFolder = MailUtils.getFolderForURI(selectedFolder, true);
+ return msgFolder;
+}
+
+function SearchNewFolderOkCallback(name, uri)
+{
+ var msgFolder = MailUtils.getFolderForURI(uri, true);
+ var imapFolder = null;
+ try
+ {
+ imapFolder = msgFolder.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
+ }
+ catch(ex) {}
+ if (imapFolder) //imapFolder creation is asynchronous.
+ {
+ if (!gSessionFolderListenerAdded) {
+ try
+ {
+ let notifyFlags = Components.interfaces.nsIFolderListener.event;
+ MailServices.mailSession.AddFolderListener(gFolderListener, notifyFlags);
+ gSessionFolderListenerAdded = true;
+ }
+ catch (ex)
+ {
+ Components.utils.reportError("Error adding to session: " + ex + "\n");
+ }
+ }
+ }
+
+ var msgWindow = GetFilterEditorMsgWindow();
+
+ if (imapFolder)
+ SetBusyCursor(window, true);
+
+ msgFolder.createSubfolder(name, msgWindow);
+
+ if (!imapFolder)
+ {
+ var curFolder = uri+"/"+encodeURIComponent(name);
+ let folder = MailUtils.getFolderForURI(curFolder);
+ gActionTargetElement.selectFolder(folder);
+ }
+}
+
+function UpdateAfterCustomHeaderChange()
+{
+ updateSearchAttributes();
+}
+
+//if you use msgWindow, please make sure that destructor gets called when you close the "window"
+function GetFilterEditorMsgWindow()
+{
+ if (!gFilterEditorMsgWindow)
+ {
+ var msgWindowContractID = "@mozilla.org/messenger/msgwindow;1";
+ var nsIMsgWindow = Components.interfaces.nsIMsgWindow;
+ gFilterEditorMsgWindow = Components.classes[msgWindowContractID].createInstance(nsIMsgWindow);
+ gFilterEditorMsgWindow.domWindow = window;
+ gFilterEditorMsgWindow.rootDocShell.appType = Components.interfaces.nsIDocShell.APP_TYPE_MAIL;
+ }
+ return gFilterEditorMsgWindow;
+}
+
+function SetBusyCursor(window, enable)
+{
+ // setCursor() is only available for chrome windows.
+ // However one of our frames is the start page which
+ // is a non-chrome window, so check if this window has a
+ // setCursor method
+ if ("setCursor" in window)
+ {
+ if (enable)
+ window.setCursor("wait");
+ else
+ window.setCursor("auto");
+ }
+}
+
+function doHelpButton()
+{
+ openHelp("mail-filters");
+}
+
+function getCustomActions()
+{
+ if (!gCustomActions)
+ {
+ gCustomActions = [];
+ let customActionsEnum = MailServices.filters.getCustomActions();
+ while (customActionsEnum.hasMoreElements())
+ gCustomActions.push(customActionsEnum.getNext().QueryInterface(
+ Components.interfaces.nsIMsgFilterCustomAction));
+ }
+}
+
+function updateFilterType()
+{
+ gFilterType = gFilterTypeSelector.getType();
+ setFilterScope(gFilterType, gFilterList);
+
+ // set valid actions
+ var ruleActions = gFilterActionList.getElementsByAttribute('class', 'ruleaction');
+ for (var i = 0; i < ruleActions.length; i++)
+ ruleActions[i].mRuleActionType.hideInvalidActions();
+}
+
+// Given a filter type, set the global search scope to the filter scope
+function setFilterScope(aFilterType, aFilterList)
+{
+ let filterScope = getFilterScope(getScopeFromFilterList(aFilterList),
+ aFilterType, aFilterList);
+ setSearchScope(filterScope);
+}
+
+//
+// Given the base filter scope for a server, and the filter
+// type, return the scope used for filter. This assumes a
+// hierarchy of contexts, with incoming the most restrictive,
+// followed by manual and post-plugin.
+function getFilterScope(aServerFilterScope, aFilterType, aFilterList)
+{
+ if (aFilterType & nsMsgFilterType.Incoming)
+ return aServerFilterScope;
+
+ // Manual or PostPlugin
+ // local mail allows body and junk types
+ if (aServerFilterScope == nsMsgSearchScope.offlineMailFilter)
+ return nsMsgSearchScope.offlineMail;
+ // IMAP and NEWS online don't allow body
+ return nsMsgSearchScope.onlineManual;
+}
+
+/**
+ * Re-focus the action that was focused before focus was lost.
+ */
+function setLastActionFocus() {
+ let lastAction = gFilterActionList.getAttribute("focusedAction");
+ if (!lastAction || lastAction < 0)
+ lastAction = 0;
+ if (lastAction >= gFilterActionList.itemCount)
+ lastAction = gFilterActionList.itemCount - 1;
+
+ gFilterActionList.getItemAtIndex(lastAction).mRuleActionType.menulist.focus();
+}
diff --git a/mailnews/base/search/content/FilterEditor.xul b/mailnews/base/search/content/FilterEditor.xul
new file mode 100644
index 000000000..0e322264d
--- /dev/null
+++ b/mailnews/base/search/content/FilterEditor.xul
@@ -0,0 +1,125 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/filterDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/searchTermOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/FilterEditor.dtd">
+
+<dialog id="FilterEditor"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&window.title;"
+ style="&filterEditorDialog.dimensions;"
+ windowtype="mailnews:filtereditor"
+ persist="width height screenX screenY"
+ buttons="accept,cancel"
+ onload="filterEditorOnLoad();"
+ onunload="filterEditorOnUnload();"
+ ondialogaccept="return onAccept();">
+
+ <dummy class="usesMailWidgets"/>
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_filter" src="chrome://messenger/locale/filter.properties"/>
+ <stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
+ </stringbundleset>
+
+ <script type="application/javascript" src="chrome://messenger/content/mailWindowOverlay.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/mailCommands.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/FilterEditor.js"/>
+
+ <commandset>
+ <command id="cmd_updateFilterType" oncommand="updateFilterType();"/>
+ <command id="cmd_updateClassificationMenu" oncommand="gFilterTypeSelector.updateClassificationMenu();"/>
+ </commandset>
+
+ <vbox>
+ <hbox align="center">
+ <label value="&filterName.label;" accesskey="&filterName.accesskey;" control="filterName"/>
+ <textbox flex="1" id="filterName"/>
+ <spacer flex="1"/>
+ </hbox>
+ </vbox>
+
+ <separator class="thin"/>
+
+ <vbox flex="1">
+ <groupbox>
+ <caption label="&contextDesc.label;"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row>
+ <checkbox id="runManual"
+ label="&contextManual.label;"
+ accesskey="&contextManual.accesskey;"
+ command="cmd_updateFilterType"/>
+ </row>
+ <row>
+ <checkbox id="runIncoming"
+ label="&contextIncomingMail.label;"
+ accesskey="&contextIncomingMail.accesskey;"
+ command="cmd_updateClassificationMenu"/>
+ <menulist id="pluginsRunOrder"
+ command="cmd_updateFilterType">
+ <menupopup>
+ <menuitem id="runBeforePlugins"
+ label="&contextBeforeCls.label;"/>
+ <menuitem id="runAfterPlugins"
+ label="&contextAfterCls.label;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row>
+ <checkbox id="runArchive"
+ label="&contextArchive.label;"
+ accesskey="&contextArchive.accesskey;"
+ command="cmd_updateFilterType"/>
+ </row>
+ <row>
+ <checkbox id="runOutgoing"
+ label="&contextOutgoing.label;"
+ accesskey="&contextOutgoing.accesskey;"
+ command="cmd_updateFilterType"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <vbox id="searchTermListBox" flex="1"/>
+ </vbox>
+
+ <splitter id="gray_horizontal_splitter" persist="state"/>
+
+ <vbox flex="1">
+ <label value="&filterActionDesc.label;"
+ accesskey="&filterActionDesc.accesskey;"
+ control="filterActionList"/>
+ <listbox id="filterActionList" flex="1" rows="4" minheight="35%"
+ onfocus="setLastActionFocus();" focusedAction="0">
+ <listcols>
+ <listcol flex="&filterActionTypeFlexValue;"/>
+ <listcol flex="&filterActionTargetFlexValue;"/>
+ <listcol class="filler"/>
+ </listcols>
+ </listbox>
+ </vbox>
+
+ <vbox id="statusbar" style="visibility: hidden;">
+ <hbox align="center">
+ <label>
+ &filterActionOrderWarning.label;
+ </label>
+ <label class="text-link" onclick="showActionsOrder();">&filterActionOrder.label;</label>
+ </hbox>
+ </vbox>
+</dialog>
diff --git a/mailnews/base/search/content/searchTermOverlay.js b/mailnews/base/search/content/searchTermOverlay.js
new file mode 100644
index 000000000..faf4ba91c
--- /dev/null
+++ b/mailnews/base/search/content/searchTermOverlay.js
@@ -0,0 +1,536 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gTotalSearchTerms=0;
+var gSearchTermList;
+var gSearchTerms = new Array;
+var gSearchRemovedTerms = new Array;
+var gSearchScope;
+var gSearchBooleanRadiogroup;
+
+var gUniqueSearchTermCounter = 0; // gets bumped every time we add a search term so we can always
+ // dynamically generate unique IDs for the terms.
+
+// cache these so we don't have to hit the string bundle for them
+var gMoreButtonTooltipText;
+var gLessButtonTooltipText;
+var gLoading = true;
+
+
+function searchTermContainer() {}
+
+searchTermContainer.prototype = {
+ internalSearchTerm : '',
+ internalBooleanAnd : '',
+
+ // this.searchTerm: the actual nsIMsgSearchTerm object
+ get searchTerm() { return this.internalSearchTerm; },
+ set searchTerm(val) {
+ this.internalSearchTerm = val;
+
+ var term = val;
+ // val is a nsIMsgSearchTerm
+ var searchAttribute=this.searchattribute;
+ var searchOperator=this.searchoperator;
+ var searchValue=this.searchvalue;
+
+ // now reflect all attributes of the searchterm into the widgets
+ if (searchAttribute)
+ {
+ // for custom, the value is the custom id, not the integer attribute
+ if (term.attrib == Components.interfaces.nsMsgSearchAttrib.Custom)
+ searchAttribute.value = term.customId;
+ else
+ searchAttribute.value = term.attrib;
+ }
+ if (searchOperator) searchOperator.value = val.op;
+ if (searchValue) searchValue.value = term.value;
+
+ this.booleanAnd = val.booleanAnd;
+ this.matchAll = val.matchAll;
+ return val;
+ },
+
+ // searchscope - just forward to the searchattribute
+ get searchScope() {
+ if (this.searchattribute)
+ return this.searchattribute.searchScope;
+ return undefined;
+ },
+ set searchScope(val) {
+ var searchAttribute = this.searchattribute;
+ if (searchAttribute) searchAttribute.searchScope=val;
+ return val;
+ },
+
+ saveId: function (element, slot) {
+ this[slot] = element.id;
+ },
+
+ getElement: function (slot) {
+ return document.getElementById(this[slot]);
+ },
+
+ // three well-defined properties:
+ // searchattribute, searchoperator, searchvalue
+ // the trick going on here is that we're storing the Element's Id,
+ // not the element itself, because the XBL object may change out
+ // from underneath us
+ get searchattribute() { return this.getElement("internalSearchAttributeId"); },
+ set searchattribute(val) {
+ this.saveId(val, "internalSearchAttributeId");
+ return val;
+ },
+ get searchoperator() { return this.getElement("internalSearchOperatorId"); },
+ set searchoperator(val) {
+ this.saveId(val, "internalSearchOperatorId");
+ return val;
+ },
+ get searchvalue() { return this.getElement("internalSearchValueId"); },
+ set searchvalue(val) {
+ this.saveId(val, "internalSearchValueId");
+ return val;
+ },
+
+ booleanNodes: null,
+ get booleanAnd() { return this.internalBooleanAnd; },
+ set booleanAnd(val) {
+ this.internalBooleanAnd = val;
+ return val;
+ },
+
+ save: function () {
+ var searchTerm = this.searchTerm;
+ var nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
+
+ if (isNaN(this.searchattribute.value)) // is this a custom term?
+ {
+ searchTerm.attrib = nsMsgSearchAttrib.Custom;
+ searchTerm.customId = this.searchattribute.value;
+ }
+ else
+ {
+ searchTerm.attrib = this.searchattribute.value;
+ }
+
+ if (this.searchattribute.value > nsMsgSearchAttrib.OtherHeader && this.searchattribute.value < nsMsgSearchAttrib.kNumMsgSearchAttributes)
+ searchTerm.arbitraryHeader = this.searchattribute.label;
+ searchTerm.op = this.searchoperator.value;
+ if (this.searchvalue.value)
+ this.searchvalue.save();
+ else
+ this.searchvalue.saveTo(searchTerm.value);
+ searchTerm.value = this.searchvalue.value;
+ searchTerm.booleanAnd = this.booleanAnd;
+ searchTerm.matchAll = this.matchAll;
+ },
+ // if you have a search term element with no search term
+ saveTo: function(searchTerm) {
+ this.internalSearchTerm = searchTerm;
+ this.save();
+ }
+}
+
+var nsIMsgSearchTerm = Components.interfaces.nsIMsgSearchTerm;
+
+function initializeSearchWidgets()
+{
+ gSearchBooleanRadiogroup = document.getElementById("booleanAndGroup");
+ gSearchTermList = document.getElementById("searchTermList");
+
+ // initialize some strings
+ var bundle = document.getElementById('bundle_search');
+ gMoreButtonTooltipText = bundle.getString('moreButtonTooltipText');
+ gLessButtonTooltipText = bundle.getString('lessButtonTooltipText');
+}
+
+function initializeBooleanWidgets()
+{
+ var booleanAnd = true;
+ var matchAll = false;
+ // get the boolean value from the first term
+ var firstTerm = gSearchTerms[0].searchTerm;
+ if (firstTerm)
+ {
+ // If there is a second term, it should actually define whether we're
+ // using 'and' or not. Note that our UI is not as rich as the
+ // underlying search model, so there's the potential to lose here when
+ // grouping is involved.
+ booleanAnd = (gSearchTerms.length > 1) ?
+ gSearchTerms[1].searchTerm.booleanAnd : firstTerm.booleanAnd;
+ matchAll = firstTerm.matchAll;
+ }
+ // target radio items have value="and" or value="or" or "all"
+ gSearchBooleanRadiogroup.value = matchAll
+ ? "matchAll"
+ : (booleanAnd ? "and" : "or")
+ var searchTerms = document.getElementById("searchTermList");
+ if (searchTerms)
+ updateSearchTermsListbox(matchAll);
+}
+
+function initializeSearchRows(scope, searchTerms)
+{
+ for (var i = 0; i < searchTerms.Count(); i++) {
+ var searchTerm = searchTerms.QueryElementAt(i, nsIMsgSearchTerm);
+ createSearchRow(i, scope, searchTerm, false);
+ gTotalSearchTerms++;
+ }
+ initializeBooleanWidgets();
+ updateRemoveRowButton();
+}
+
+/**
+ * Enables/disables all the visible elements inside the search terms listbox.
+ *
+ * @param matchAllValue boolean value from the first search term
+ */
+function updateSearchTermsListbox(matchAllValue)
+{
+ var searchTerms = document.getElementById("searchTermList");
+ searchTerms.setAttribute("disabled", matchAllValue);
+ var searchAttributeList = searchTerms.getElementsByTagName("searchattribute");
+ var searchOperatorList = searchTerms.getElementsByTagName("searchoperator");
+ var searchValueList = searchTerms.getElementsByTagName("searchvalue");
+ for (var i = 0; i < searchAttributeList.length; i++) {
+ searchAttributeList[i].setAttribute("disabled", matchAllValue);
+ searchOperatorList[i].setAttribute("disabled", matchAllValue);
+ searchValueList[i].setAttribute("disabled", matchAllValue);
+ if (!matchAllValue)
+ searchValueList[i].removeAttribute("disabled");
+ }
+ var moreOrLessButtonsList = searchTerms.getElementsByTagName("button");
+ for (var i = 0; i < moreOrLessButtonsList.length; i++) {
+ moreOrLessButtonsList[i].setAttribute("disabled", matchAllValue);
+ }
+ if (!matchAllValue)
+ updateRemoveRowButton();
+}
+
+// enables/disables the less button for the first row of search terms.
+function updateRemoveRowButton()
+{
+ var firstListItem = gSearchTermList.getItemAtIndex(0);
+ if (firstListItem)
+ firstListItem.lastChild.lastChild.setAttribute("disabled", gTotalSearchTerms == 1);
+}
+
+// Returns the actual list item row index in the list of search rows
+// that contains the passed in element id.
+function getSearchRowIndexForElement(aElement)
+{
+ var listItem = aElement;
+
+ while (listItem && listItem.localName != "listitem")
+ listItem = listItem.parentNode;
+
+ return gSearchTermList.getIndexOfItem(listItem);
+}
+
+function onMore(event)
+{
+ // if we have an event, extract the list row index and use that as the row number
+ // for our insertion point. If there is no event, append to the end....
+ var rowIndex;
+
+ if (event)
+ rowIndex = getSearchRowIndexForElement(event.target) + 1;
+ else
+ rowIndex = gSearchTermList.getRowCount();
+
+ createSearchRow(rowIndex, gSearchScope, null, event != null);
+ gTotalSearchTerms++;
+ updateRemoveRowButton();
+
+ // the user just added a term, so scroll to it
+ gSearchTermList.ensureIndexIsVisible(rowIndex);
+}
+
+function onLess(event)
+{
+ if (event && gTotalSearchTerms > 1)
+ {
+ removeSearchRow(getSearchRowIndexForElement(event.target));
+ --gTotalSearchTerms;
+ }
+
+ updateRemoveRowButton();
+}
+
+// set scope on all visible searchattribute tags
+function setSearchScope(scope)
+{
+ gSearchScope = scope;
+ for (var i = 0; i < gSearchTerms.length; i++)
+ {
+ // don't set element attributes if XBL hasn't loaded
+ if (!(gSearchTerms[i].obj.searchattribute.searchScope === undefined))
+ {
+ gSearchTerms[i].obj.searchattribute.searchScope = scope;
+ // act like the user "selected" this, see bug #202848
+ gSearchTerms[i].obj.searchattribute.onSelect(null /* no event */);
+ }
+ gSearchTerms[i].scope = scope;
+ }
+}
+
+function updateSearchAttributes()
+{
+ for (var i=0; i<gSearchTerms.length; i++)
+ gSearchTerms[i].obj.searchattribute.refreshList();
+ }
+
+function booleanChanged(event) {
+ // when boolean changes, we have to update all the attributes on the search terms
+ var newBoolValue = (event.target.getAttribute("value") == "and");
+ var matchAllValue = (event.target.getAttribute("value") == "matchAll");
+ if (document.getElementById("abPopup")) {
+ var selectedAB = document.getElementById("abPopup").selectedItem.value;
+ setSearchScope(GetScopeForDirectoryURI(selectedAB));
+ }
+ for (var i=0; i<gSearchTerms.length; i++) {
+ let searchTerm = gSearchTerms[i].obj;
+ // If term is not yet initialized in the UI, change the original object.
+ if (!searchTerm || !gSearchTerms[i].initialized)
+ searchTerm = gSearchTerms[i].searchTerm;
+
+ searchTerm.booleanAnd = newBoolValue;
+ searchTerm.matchAll = matchAllValue;
+ }
+ var searchTerms = document.getElementById("searchTermList");
+ if (searchTerms)
+ {
+ if (!matchAllValue && searchTerms.hidden && !gTotalSearchTerms)
+ onMore(null); // fake to get empty row.
+ updateSearchTermsListbox(matchAllValue);
+ }
+}
+
+/**
+ * Create a new search row with all the needed elements.
+ *
+ * @param index index of the position in the menulist where to add the row
+ * @param scope a nsMsgSearchScope constant indicating scope of this search rule
+ * @param searchTerm nsIMsgSearchTerm object to hold the search term
+ * @param aUserAdded boolean indicating if the row addition was initiated by the user
+ * (e.g. via the '+' button)
+ */
+function createSearchRow(index, scope, searchTerm, aUserAdded)
+{
+ var searchAttr = document.createElement("searchattribute");
+ var searchOp = document.createElement("searchoperator");
+ var searchVal = document.createElement("searchvalue");
+
+ var moreButton = document.createElement("button");
+ var lessButton = document.createElement("button");
+ moreButton.setAttribute("class", "small-button");
+ moreButton.setAttribute("oncommand", "onMore(event);");
+ moreButton.setAttribute('label', '+');
+ moreButton.setAttribute('tooltiptext', gMoreButtonTooltipText);
+ lessButton.setAttribute("class", "small-button");
+ lessButton.setAttribute("oncommand", "onLess(event);");
+ lessButton.setAttribute('label', '\u2212');
+ lessButton.setAttribute('tooltiptext', gLessButtonTooltipText);
+
+ // now set up ids:
+ searchAttr.id = "searchAttr" + gUniqueSearchTermCounter;
+ searchOp.id = "searchOp" + gUniqueSearchTermCounter;
+ searchVal.id = "searchVal" + gUniqueSearchTermCounter;
+
+ searchAttr.setAttribute("for", searchOp.id + "," + searchVal.id);
+ searchOp.setAttribute("opfor", searchVal.id);
+
+ var rowdata = [searchAttr, searchOp, searchVal,
+ [moreButton, lessButton] ];
+ var searchrow = constructRow(rowdata);
+ searchrow.id = "searchRow" + gUniqueSearchTermCounter;
+
+ var searchTermObj = new searchTermContainer;
+ searchTermObj.searchattribute = searchAttr;
+ searchTermObj.searchoperator = searchOp;
+ searchTermObj.searchvalue = searchVal;
+
+ // now insert the new search term into our list of terms
+ gSearchTerms.splice(index, 0, {obj:searchTermObj, scope:scope, searchTerm:searchTerm, initialized:false});
+
+ var editFilter = null;
+ try { editFilter = gFilter; } catch(e) { }
+
+ var editMailView = null;
+ try { editMailView = gMailView; } catch(e) { }
+
+ if ((!editFilter && !editMailView) ||
+ (editFilter && index == gTotalSearchTerms) ||
+ (editMailView && index == gTotalSearchTerms))
+ gLoading = false;
+
+ // index is index of new row
+ // gTotalSearchTerms has not been updated yet
+ if (gLoading || index == gTotalSearchTerms) {
+ gSearchTermList.appendChild(searchrow);
+ }
+ else {
+ var currentItem = gSearchTermList.getItemAtIndex(index);
+ gSearchTermList.insertBefore(searchrow, currentItem);
+ }
+
+ // If this row was added by user action, focus the value field.
+ if (aUserAdded) {
+ document.commandDispatcher.advanceFocusIntoSubtree(searchVal);
+ searchrow.setAttribute("highlight", "true");
+ }
+
+ // bump our unique search term counter
+ gUniqueSearchTermCounter++;
+}
+
+function initializeTermFromId(id)
+{
+ initializeTermFromIndex(getSearchRowIndexForElement(document.getElementById(id)));
+}
+
+function initializeTermFromIndex(index)
+{
+ var searchTermObj = gSearchTerms[index].obj;
+
+ searchTermObj.searchScope = gSearchTerms[index].scope;
+ // the search term will initialize the searchTerm element, including
+ // .booleanAnd
+ if (gSearchTerms[index].searchTerm)
+ searchTermObj.searchTerm = gSearchTerms[index].searchTerm;
+ // here, we don't have a searchTerm, so it's probably a new element -
+ // we'll initialize the .booleanAnd from the existing setting in
+ // the UI
+ else
+ {
+ searchTermObj.booleanAnd = (gSearchBooleanRadiogroup.value == "and");
+ if (index)
+ {
+ // If we weren't pre-initialized with a searchTerm then steal the
+ // search attribute and operator from the previous row.
+ searchTermObj.searchattribute.value = gSearchTerms[index - 1].obj.searchattribute.value;
+ searchTermObj.searchoperator.value = gSearchTerms[index - 1].obj.searchoperator.value;
+ }
+ }
+
+ gSearchTerms[index].initialized = true;
+}
+
+/**
+ * Creates a <listitem> using the array children as the children
+ * of each listcell.
+ * @param aChildren An array of XUL elements to put into the listitem.
+ * Each array member is put into a separate listcell.
+ * If the member itself is an array of elements,
+ * all of them are put into the same listcell.
+ */
+function constructRow(aChildren)
+{
+ let listitem = document.createElement("listitem");
+ listitem.setAttribute("allowevents", "true");
+ for (let i = 0; i < aChildren.length; i++) {
+ let listcell = document.createElement("listcell");
+ let child = aChildren[i];
+
+ if (child instanceof Array) {
+ for (let j = 0; j < child.length; j++)
+ listcell.appendChild(child[j]);
+ } else {
+ child.setAttribute("flex", "1");
+ listcell.appendChild(child);
+ }
+ listitem.appendChild(listcell);
+ }
+ return listitem;
+}
+
+function removeSearchRow(index)
+{
+ var searchTermObj = gSearchTerms[index].obj;
+ if (!searchTermObj) {
+ return;
+ }
+
+ // if it is an existing (but offscreen) term,
+ // make sure it is initialized before we remove it.
+ if (!gSearchTerms[index].searchTerm && !gSearchTerms[index].initialized)
+ initializeTermFromIndex(index);
+
+ // need to remove row from list, so walk upwards from the
+ // searchattribute to find the first <listitem>
+ var listitem = searchTermObj.searchattribute;
+
+ while (listitem) {
+ if (listitem.localName == "listitem") break;
+ listitem = listitem.parentNode;
+ }
+
+ if (!listitem) {
+ dump("Error: couldn't find parent listitem!\n");
+ return;
+ }
+
+
+ if (searchTermObj.searchTerm) {
+ gSearchRemovedTerms[gSearchRemovedTerms.length] = searchTermObj.searchTerm;
+ } else {
+ //dump("That wasn't real. ignoring \n");
+ }
+
+ listitem.remove();
+
+ // now remove the item from our list of terms
+ gSearchTerms.splice(index, 1);
+}
+
+// save the search terms from the UI back to the actual search terms
+// searchTerms: nsISupportsArray of terms
+// termOwner: object which can contain and create the terms
+// (will be unnecessary if we just make terms creatable
+// via XPCOM)
+function saveSearchTerms(searchTerms, termOwner)
+{
+ var matchAll = gSearchBooleanRadiogroup.value == 'matchAll';
+ var i;
+ for (i = 0; i < gSearchRemovedTerms.length; i++)
+ searchTerms.RemoveElement(gSearchRemovedTerms[i]);
+
+ for (i = 0; i < gSearchTerms.length; i++) {
+ try {
+ gSearchTerms[i].obj.matchAll = matchAll;
+ var searchTerm = gSearchTerms[i].obj.searchTerm;
+ if (searchTerm) {
+ gSearchTerms[i].obj.save();
+ } else if (!gSearchTerms[i].initialized) {
+ // the term might be an offscreen one we haven't initialized yet
+ searchTerm = gSearchTerms[i].searchTerm;
+ } else {
+ // need to create a new searchTerm, and somehow save it to that
+ searchTerm = termOwner.createTerm();
+ gSearchTerms[i].obj.saveTo(searchTerm);
+ // this might not be the right place for the term,
+ // but we need to make the array longer anyway
+ termOwner.appendTerm(searchTerm);
+ }
+ searchTerms.SetElementAt(i, searchTerm);
+ } catch (ex) {
+ dump("** Error saving element " + i + ": " + ex + "\n");
+ }
+ }
+}
+
+function onReset(event)
+{
+ while (gTotalSearchTerms>0)
+ removeSearchRow(--gTotalSearchTerms);
+ onMore(null);
+}
+
+function hideMatchAllItem()
+{
+ var allItems = document.getElementById('matchAllItem');
+ if (allItems)
+ allItems.hidden = true;
+}
diff --git a/mailnews/base/search/content/searchTermOverlay.xul b/mailnews/base/search/content/searchTermOverlay.xul
new file mode 100644
index 000000000..c14e05cb0
--- /dev/null
+++ b/mailnews/base/search/content/searchTermOverlay.xul
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/searchTermOverlay.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/searchTermOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/dateFormat.js"/>
+
+ <vbox id="searchTermListBox">
+
+ <radiogroup id="booleanAndGroup" orient="horizontal" value="and"
+ oncommand="booleanChanged(event);">
+ <radio value="and" label="&matchAll.label;"
+ accesskey="&matchAll.accesskey;"/>
+ <radio value="or" label="&matchAny.label;"
+ accesskey="&matchAny.accesskey;"/>
+ <radio value="matchAll" id="matchAllItem" label="&matchAllMsgs.label;"
+ accesskey="&matchAllMsgs.accesskey;"/>
+ </radiogroup>
+
+ <hbox flex="1">
+ <hbox id="searchterms"/>
+ <listbox flex="1" id="searchTermList" rows="4" minheight="35%">
+ <listcols>
+ <listcol flex="&searchTermListAttributesFlexValue;"/>
+ <listcol flex="&searchTermListOperatorsFlexValue;"/>
+ <listcol flex="&searchTermListValueFlexValue;"/>
+ <listcol class="filler"/>
+ </listcols>
+
+ <!-- this is what the listitems will look like:
+ <listitem id="searchListItem">
+ <listcell allowevents="true">
+ <searchattribute id="searchAttr1" for="searchOp1,searchValue1" flex="1"/>
+ </listcell>
+ <listcell allowevents="true">
+ <searchoperator id="searchOp1" opfor="searchValue1" flex="1"/>
+ </listcell>
+ <listcell allowevents="true" >
+ <searchvalue id="searchValue1" flex="1"/>
+ </listcell>
+ <listcell>
+ <button label="add"/>
+ <button label="remove"/>
+ </listcell>
+ </listitem>
+ <listitem>
+ <listcell label="the.."/>
+ <listcell label="contains.."/>
+ <listcell label="text here"/>
+ <listcell label="+/-"/>
+ </listitem>
+ -->
+ </listbox>
+
+ </hbox>
+ </vbox>
+
+</overlay>
diff --git a/mailnews/base/search/content/searchWidgets.xml b/mailnews/base/search/content/searchWidgets.xml
new file mode 100644
index 000000000..69f3b2ae2
--- /dev/null
+++ b/mailnews/base/search/content/searchWidgets.xml
@@ -0,0 +1,740 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!--
+ This file has the following external dependencies:
+ -gFilterActionStrings from FilterEditor.js
+ -gFilterList from FilterEditor.js
+ -gFilter from FilterEditor.js
+ -gCustomActions from FilterEditor.js
+ -gFilterType from FilterEditor.js
+ -checkActionsReorder from FilterEditor.js
+-->
+
+<!DOCTYPE dialog [
+ <!ENTITY % filterEditorDTD SYSTEM "chrome://messenger/locale/FilterEditor.dtd" >
+%filterEditorDTD;
+ <!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
+%messengerDTD;
+]>
+
+<bindings id="filterBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="ruleactiontype-menulist">
+ <content>
+ <xul:menulist class="ruleaction-type">
+ <xul:menupopup>
+ <xul:menuitem label="&moveMessage.label;" value="movemessage" enablefornews="false"/>
+ <xul:menuitem label="&copyMessage.label;" value="copymessage"/>
+ <xul:menuseparator enablefornews="false"/>
+ <xul:menuitem label="&forwardTo.label;" value="forwardmessage" enablefornews="false"/>
+ <xul:menuitem label="&replyWithTemplate.label;" value="replytomessage" enablefornews="false"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&markMessageRead.label;" value="markasread"/>
+ <xul:menuitem label="&markMessageUnread.label;" value="markasunread"/>
+ <xul:menuitem label="&markMessageStarred.label;" value="markasflagged"/>
+ <xul:menuitem label="&setPriority.label;" value="setpriorityto"/>
+ <xul:menuitem label="&addTag.label;" value="addtagtomessage"/>
+ <xul:menuitem label="&setJunkScore.label;" value="setjunkscore" enablefornews="false"/>
+ <xul:menuseparator enableforpop3="true"/>
+ <xul:menuitem label="&deleteMessage.label;" value="deletemessage"/>
+ <xul:menuitem label="&deleteFromPOP.label;" value="deletefrompopserver" enableforpop3="true"/>
+ <xul:menuitem label="&fetchFromPOP.label;" value="fetchfrompopserver" enableforpop3="true"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&ignoreThread.label;" value="ignorethread"/>
+ <xul:menuitem label="&ignoreSubthread.label;" value="ignoresubthread"/>
+ <xul:menuitem label="&watchThread.label;" value="watchthread"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&stopExecution.label;" value="stopexecution"/>
+ </xul:menupopup>
+ </xul:menulist>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ this.addCustomActions();
+ this.hideInvalidActions();
+ // differentiate between creating a new, next available action,
+ // and creating a row which will be initialized with an action
+ if (!this.parentNode.hasAttribute('initialActionIndex'))
+ {
+ var unavailableActions = this.usedActionsList();
+ // select the first one that's not in the list
+ for (var index = 0; index < this.menuitems.length; index++)
+ {
+ var menu = this.menuitems[index];
+ if (!(menu.value in unavailableActions) && !menu.hidden)
+ {
+ this.menulist.value = menu.value;
+ this.parentNode.setAttribute('value', menu.value);
+ break;
+ }
+ }
+ }
+ else
+ {
+ this.parentNode.mActionTypeInitialized = true;
+ this.parentNode.clearInitialActionIndex();
+ }
+ ]]>
+ </constructor>
+
+ <field name="menulist">document.getAnonymousNodes(this)[0]</field>
+ <field name="menuitems">this.menulist.getElementsByTagNameNS(this.menulist.namespaceURI, 'menuitem')</field>
+
+ <method name="hideInvalidActions">
+ <body>
+ <![CDATA[
+ let menupopup = this.menulist.menupopup;
+ let scope = getScopeFromFilterList(gFilterList);
+
+ // walk through the list of filter actions and hide any actions which aren't valid
+ // for our given scope (news, imap, pop, etc) and context
+ let elements, i;
+
+ // disable / enable all elements in the "filteractionlist"
+ // based on the scope and the "enablefornews" attribute
+ elements = menupopup.getElementsByAttribute("enablefornews", "true");
+ for (i = 0; i < elements.length; i++)
+ elements[i].hidden = scope != Components.interfaces.nsMsgSearchScope.newsFilter;
+
+ elements = menupopup.getElementsByAttribute("enablefornews", "false");
+ for (i = 0; i < elements.length; i++)
+ elements[i].hidden = scope == Components.interfaces.nsMsgSearchScope.newsFilter;
+
+ elements = menupopup.getElementsByAttribute("enableforpop3", "true");
+ for (i = 0; i < elements.length; i++)
+ elements[i].hidden = !((gFilterList.folder.server.type == "pop3") ||
+ (gFilterList.folder.server.type == "none"));
+
+ elements = menupopup.getElementsByAttribute("isCustom", "true");
+ // Note there might be an additional element here as a placeholder
+ // for a missing action, so we iterate over the known actions
+ // instead of the elements.
+ for (i = 0; i < gCustomActions.length; i++)
+ elements[i].hidden = !gCustomActions[i]
+ .isValidForType(gFilterType, scope);
+
+ // Disable "Reply with Template" if there are no templates.
+ if (!this.getTemplates(false)) {
+ elements = menupopup.getElementsByAttribute("value", "replytomessage");
+ if (elements.length == 1)
+ elements[0].hidden = true;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="addCustomActions">
+ <body>
+ <![CDATA[
+ var menupopup = this.menulist.menupopup;
+ for (var i = 0; i < gCustomActions.length; i++)
+ {
+ var customAction = gCustomActions[i];
+ var menuitem = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "xul:menuitem");
+ menuitem.setAttribute("label", customAction.name);
+ menuitem.setAttribute("value", customAction.id);
+ menuitem.setAttribute("isCustom", "true");
+ menupopup.appendChild(menuitem);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- returns a hash containing all of the filter actions which are currently being used by other filteractionrows -->
+ <method name="usedActionsList">
+ <body>
+ <![CDATA[
+ var usedActions = {};
+ var currentFilterActionRow = this.parentNode;
+ var listBox = currentFilterActionRow.mListBox; // need to account for the list item
+ // now iterate over each list item in the list box
+ for (var index = 0; index < listBox.getRowCount(); index++)
+ {
+ var filterActionRow = listBox.getItemAtIndex(index);
+ if (filterActionRow != currentFilterActionRow)
+ {
+ var actionValue = filterActionRow.getAttribute('value');
+
+ // let custom actions decide if dups are allowed
+ var isCustom = false;
+ for (var i = 0; i < gCustomActions.length; i++)
+ {
+ if (gCustomActions[i].id == actionValue)
+ {
+ isCustom = true;
+ if (!gCustomActions[i].allowDuplicates)
+ usedActions[actionValue] = true;
+ break;
+ }
+ }
+
+ if (!isCustom) {
+ // The following actions can appear more than once in a single filter
+ // so do not set them as already used.
+ if (actionValue != 'addtagtomessage' &&
+ actionValue != 'forwardmessage' &&
+ actionValue != 'copymessage')
+ usedActions[actionValue] = true;
+ // If either Delete message or Move message exists, disable the other one.
+ // It does not make sense to apply both to the same message.
+ if (actionValue == 'deletemessage')
+ usedActions['movemessage'] = true;
+ else if (actionValue == 'movemessage')
+ usedActions['deletemessage'] = true;
+ // The same with Mark as read/Mark as Unread.
+ else if (actionValue == 'markasread')
+ usedActions['markasunread'] = true;
+ else if (actionValue == 'markasunread')
+ usedActions['markasread'] = true;
+ }
+ }
+ }
+ return usedActions;
+ ]]>
+ </body>
+ </method>
+
+ <!--
+ - Check if there exist any templates in this account.
+ -
+ - @param populateTemplateList If true, create menuitems representing
+ - the found templates.
+ - @param templateMenuList The menulist element to create items in.
+ -
+ - @return True if at least one template was found, otherwise false.
+ -->
+ <method name="getTemplates">
+ <parameter name="populateTemplateList"/>
+ <parameter name="templateMenuList"/>
+ <body>
+ <![CDATA[
+ Components.utils.import("resource:///modules/iteratorUtils.jsm", this);
+ let identitiesRaw = MailServices.accounts
+ .getIdentitiesForServer(gFilterList.folder.server);
+ let identities = Array.from(this.fixIterator(identitiesRaw,
+ Ci.nsIMsgIdentity));
+
+ if (identities.length == 0) { // typically if this is Local Folders
+ if (MailServices.accounts.defaultAccount)
+ identities.push(MailServices.accounts.defaultAccount.defaultIdentity);
+ }
+
+ let templateFound = false;
+ let foldersScanned = [];
+
+ for (let identity of identities) {
+ let enumerator = null;
+ let msgFolder;
+ try {
+ msgFolder = Components.classes["@mozilla.org/rdf/rdf-service;1"]
+ .getService(Components.interfaces.nsIRDFService)
+ .GetResource(identity.stationeryFolder)
+ .QueryInterface(Components.interfaces.nsIMsgFolder);
+ // If we already processed this folder, do not set enumerator
+ // so that we skip this identity.
+ if (foldersScanned.indexOf(msgFolder) == -1) {
+ foldersScanned.push(msgFolder);
+ enumerator = msgFolder.msgDatabase.EnumerateMessages();
+ }
+ } catch (e) {
+ // The Templates folder may not exist, that is OK.
+ }
+
+ if (!enumerator)
+ continue;
+
+ while (enumerator.hasMoreElements()) {
+ let header = enumerator.getNext();
+ if (header instanceof Components.interfaces.nsIMsgDBHdr) {
+ templateFound = true;
+ if (!populateTemplateList)
+ return true;
+ let msgTemplateUri = msgFolder.URI + "?messageId=" +
+ header.messageId + '&subject=' + header.mime2DecodedSubject;
+ let newItem = templateMenuList.appendItem(header.mime2DecodedSubject,
+ msgTemplateUri);
+ }
+ }
+ }
+
+ return templateFound;
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ this.parentNode.setAttribute('value', this.menulist.value);
+ checkActionsReorder();
+ ]]>
+ </handler>
+
+ <handler event="popupshowing">
+ <![CDATA[
+ var unavailableActions = this.usedActionsList();
+ for (var index = 0; index < this.menuitems.length; index++)
+ {
+ var menu = this.menuitems[index];
+ menu.setAttribute('disabled', menu.value in unavailableActions);
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <!-- This binding exists to disable the default binding of a listitem
+ in the search terms. -->
+ <binding id="listitem">
+ <implementation>
+ <method name="_fireEvent">
+ <parameter name="aName"/>
+ <body>
+ <![CDATA[
+ /* This provides a dummy _fireEvent function that
+ the listbox expects to be able to call.
+ See bug 202036. */
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="ruleaction" extends="#listitem">
+ <content allowevents="true">
+ <xul:listcell class="ruleactiontype"
+ orient="vertical" align="stretch" pack="center"/>
+ <xul:listcell class="ruleactiontarget" xbl:inherits="type=value"
+ orient="vertical" align="stretch" pack="center"/>
+ <xul:listcell>
+ <xul:button class="small-button"
+ label="+"
+ tooltiptext="&addAction.tooltip;"
+ oncommand="this.parentNode.parentNode.addRow();"/>
+ <xul:button class="small-button"
+ label="&#x2212;"
+ tooltiptext="&removeAction.tooltip;"
+ oncommand="this.parentNode.parentNode.removeRow();"
+ anonid="removeButton"/>
+ </xul:listcell>
+ </content>
+
+ <implementation>
+ <field name="mListBox">this.parentNode</field>
+ <field name="mRemoveButton">document.getAnonymousElementByAttribute(this, "anonid", "removeButton")</field>
+ <field name="mActionTypeInitialized">false</field>
+ <field name="mRuleActionTargetInitialized">false</field>
+ <field name="mRuleActionType">document.getAnonymousNodes(this)[0]</field>
+
+ <method name="clearInitialActionIndex">
+ <body>
+ <![CDATA[
+ // we should only remove the initialActionIndex after we have been told that
+ // both the rule action type and the rule action target have both been built since they both need
+ // this piece of information. This complication arises because both of these child elements are getting
+ // bound asynchronously after the search row has been constructed
+
+ if (this.mActionTypeInitialized && this.mRuleActionTargetInitialized)
+ this.removeAttribute('initialActionIndex');
+ ]]>
+ </body>
+ </method>
+
+ <method name="initWithAction">
+ <parameter name="aFilterAction"/>
+ <body>
+ <![CDATA[
+ var filterActionStr;
+ var actionTarget = document.getAnonymousNodes(this)[1];
+ var actionItem = document.getAnonymousNodes(actionTarget);
+ var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
+ switch (aFilterAction.type)
+ {
+ case nsMsgFilterAction.Custom:
+ filterActionStr = aFilterAction.customId;
+ if (actionItem)
+ actionItem[0].value = aFilterAction.strValue;
+
+ // Make sure the custom action has been added. If not, it
+ // probably was from an extension that has been removed. We'll
+ // show a dummy menuitem to warn the user.
+ var needCustomLabel = true;
+ for (var i = 0; i < gCustomActions.length; i++)
+ {
+ if (gCustomActions[i].id == filterActionStr)
+ {
+ needCustomLabel = false;
+ break;
+ }
+ }
+ if (needCustomLabel)
+ {
+ var menuitem = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "xul:menuitem");
+ menuitem.setAttribute("label",
+ gFilterBundle.getString("filterMissingCustomAction"));
+ menuitem.setAttribute("value", filterActionStr);
+ menuitem.disabled = true;
+ this.mRuleActionType.menulist.menupopup.appendChild(menuitem);
+ var scriptError = Components.classes["@mozilla.org/scripterror;1"]
+ .createInstance(Components.interfaces.nsIScriptError);
+ scriptError.init("Missing custom action " + filterActionStr,
+ null, null, 0, 0,
+ Components.interfaces.nsIScriptError.errorFlag,
+ "component javascript");
+ Services.console.logMessage(scriptError);
+ }
+ break;
+ case nsMsgFilterAction.MoveToFolder:
+ case nsMsgFilterAction.CopyToFolder:
+ actionItem[0].value = aFilterAction.targetFolderUri;
+ break;
+ case nsMsgFilterAction.Reply:
+ case nsMsgFilterAction.Forward:
+ actionItem[0].value = aFilterAction.strValue;
+ break;
+ case nsMsgFilterAction.Label:
+ actionItem[0].value = aFilterAction.label;
+ break;
+ case nsMsgFilterAction.ChangePriority:
+ actionItem[0].value = aFilterAction.priority;
+ break;
+ case nsMsgFilterAction.JunkScore:
+ actionItem[0].value = aFilterAction.junkScore;
+ break;
+ case nsMsgFilterAction.AddTag:
+ actionItem[0].value = aFilterAction.strValue;
+ break;
+ default:
+ break;
+ }
+ if (aFilterAction.type != nsMsgFilterAction.Custom)
+ filterActionStr = gFilterActionStrings[aFilterAction.type];
+ document.getAnonymousNodes(this.mRuleActionType)[0]
+ .value = filterActionStr;
+ this.mRuleActionTargetInitialized = true;
+ this.clearInitialActionIndex();
+ checkActionsReorder();
+ ]]>
+ </body>
+ </method>
+
+ <method name="validateAction">
+ <body>
+ <![CDATA[
+ // returns true if this row represents a valid filter action and false otherwise.
+ // This routine also prompts the user.
+ Components.utils.import("resource:///modules/MailUtils.js", this);
+ var filterActionString = this.getAttribute('value');
+ var actionTarget = document.getAnonymousNodes(this)[1];
+ var errorString, customError;
+
+ switch (filterActionString)
+ {
+ case "movemessage":
+ case "copymessage":
+ let msgFolder = document.getAnonymousNodes(actionTarget)[0].value ?
+ this.MailUtils.getFolderForURI(document.getAnonymousNodes(actionTarget)[0].value) : null;
+ if (!msgFolder || !msgFolder.canFileMessages)
+ errorString = "mustSelectFolder";
+ break;
+ case "forwardmessage":
+ if (document.getAnonymousNodes(actionTarget)[0].value.length < 3 ||
+ document.getAnonymousNodes(actionTarget)[0].value.indexOf('@') < 1)
+ errorString = "enterValidEmailAddress";
+ break;
+ case "replytomessage":
+ if (!document.getAnonymousNodes(actionTarget)[0].selectedItem)
+ errorString = "pickTemplateToReplyWith";
+ break;
+ default:
+ // some custom actions have no action value node
+ if (!document.getAnonymousNodes(actionTarget))
+ return true;
+ // locate the correct custom action, and check validity
+ for (var i = 0; i < gCustomActions.length; i++)
+ if (gCustomActions[i].id == filterActionString)
+ {
+ customError =
+ gCustomActions[i].validateActionValue(
+ document.getAnonymousNodes(actionTarget)[0].value,
+ gFilterList.folder, gFilterType);
+ break;
+ }
+ break;
+ }
+
+ errorString = errorString ?
+ gFilterBundle.getString(errorString) :
+ customError;
+ if (errorString)
+ Services.prompt.alert(window, null, errorString);
+
+ return !errorString;
+ ]]>
+ </body>
+ </method>
+
+ <method name="saveToFilter">
+ <parameter name="aFilter"/>
+ <body>
+ <![CDATA[
+ // create a new filter action, fill it in, and then append it to the filter
+ var filterAction = aFilter.createAction();
+ var filterActionString = this.getAttribute('value');
+ filterAction.type = gFilterActionStrings.indexOf(filterActionString);
+ var actionTarget = document.getAnonymousNodes(this)[1];
+ var actionItem = document.getAnonymousNodes(actionTarget);
+ var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
+ switch (filterAction.type)
+ {
+ case nsMsgFilterAction.Label:
+ filterAction.label = actionItem[0].getAttribute("value");
+ break;
+ case nsMsgFilterAction.ChangePriority:
+ filterAction.priority = actionItem[0].getAttribute("value");
+ break;
+ case nsMsgFilterAction.MoveToFolder:
+ case nsMsgFilterAction.CopyToFolder:
+ filterAction.targetFolderUri = actionItem[0].value;
+ break;
+ case nsMsgFilterAction.JunkScore:
+ filterAction.junkScore = actionItem[0].value;
+ break;
+ case nsMsgFilterAction.Custom:
+ filterAction.customId = filterActionString;
+ // fall through to set the value
+ default:
+ if (actionItem)
+ filterAction.strValue = actionItem[0].value;
+ break;
+ }
+ aFilter.appendAction(filterAction);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getActionStrings">
+ <parameter name="aActionStrings"/>
+ <body>
+ <![CDATA[
+ // Collect the action names and arguments in a plain string form.
+ let actionTarget = document.getAnonymousNodes(this)[1];
+ let actionItem = document.getAnonymousNodes(actionTarget);
+
+ aActionStrings.push({
+ label: document.getAnonymousNodes(this.mRuleActionType)[0].label,
+ argument: actionItem ?
+ (actionItem[0].label ?
+ actionItem[0].label : actionItem[0].value) : ""
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateRemoveButton">
+ <body>
+ <![CDATA[
+ // if we only have one row of actions, then disable the remove button for that row
+ this.mListBox.getItemAtIndex(0).mRemoveButton.disabled = this.mListBox.getRowCount() == 1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="addRow">
+ <body>
+ <![CDATA[
+ let listItem = document.createElement('listitem');
+ listItem.className = 'ruleaction';
+ listItem.setAttribute('onfocus','this.storeFocus();');
+ this.mListBox.insertBefore(listItem, this.nextSibling);
+ this.mListBox.ensureElementIsVisible(listItem);
+
+ // make sure the first remove button is enabled
+ this.updateRemoveButton();
+ checkActionsReorder();
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeRow">
+ <body>
+ <![CDATA[
+ // this.mListBox will fail after the row is removed, so save it
+ let listBox = this.mListBox;
+ if (listBox.getRowCount() > 1)
+ this.remove();
+ // can't use 'this' as it is destroyed now
+ listBox.getItemAtIndex(0).updateRemoveButton();
+ checkActionsReorder();
+ ]]>
+ </body>
+ </method>
+
+ <method name="storeFocus">
+ <body>
+ <![CDATA[
+ // When this action row is focused, store its index in the parent listbox.
+ this.mListBox.setAttribute("focusedAction", this.mListBox.getIndexOfItem(this));
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="ruleactiontarget-base">
+ <implementation>
+ <constructor>
+ <![CDATA[
+ if (this.parentNode.hasAttribute('initialActionIndex'))
+ {
+ let actionIndex = this.parentNode.getAttribute('initialActionIndex');
+ let filterAction = gFilter.getActionAt(actionIndex);
+ this.parentNode.initWithAction(filterAction);
+ }
+ this.parentNode.updateRemoveButton();
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="ruleactiontarget-tag" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:menulist class="ruleactionitem">
+ <xul:menupopup>
+ </xul:menupopup>
+ </xul:menulist>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ let menuPopup = document.getAnonymousNodes(this)[0].menupopup;
+ let tagArray = MailServices.tags.getAllTags({});
+ for (let i = 0; i < tagArray.length; ++i)
+ {
+ var taginfo = tagArray[i];
+ var newMenuItem = document.createElement('menuitem');
+ newMenuItem.setAttribute('label', taginfo.tag);
+ newMenuItem.setAttribute('value', taginfo.key);
+ menuPopup.appendChild(newMenuItem);
+ }
+ // propagating a pre-existing hack to make the tag get displayed correctly in the menulist
+ // now that we've changed the tags for each menu list. We need to use the current selectedIndex
+ // (if its defined) to handle the case where we were initialized with a filter action already.
+ var currentItem = document.getAnonymousNodes(this)[0].selectedItem;
+ document.getAnonymousNodes(this)[0].selectedItem = null;
+ document.getAnonymousNodes(this)[0].selectedItem = currentItem;
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="ruleactiontarget-priority" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:menulist class="ruleactionitem">
+ <xul:menupopup>
+ <xul:menuitem value="6" label="&highestPriorityCmd.label;"/>
+ <xul:menuitem value="5" label="&highPriorityCmd.label;"/>
+ <xul:menuitem value="4" label="&normalPriorityCmd.label;"/>
+ <xul:menuitem value="3" label="&lowPriorityCmd.label;"/>
+ <xul:menuitem value="2" label="&lowestPriorityCmd.label;"/>
+ </xul:menupopup>
+ </xul:menulist>
+ </content>
+ </binding>
+
+ <binding id="ruleactiontarget-junkscore" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:menulist class="ruleactionitem">
+ <xul:menupopup>
+ <xul:menuitem value="100" label="&junk.label;"/>
+ <xul:menuitem value="0" label="&notJunk.label;"/>
+ </xul:menupopup>
+ </xul:menulist>
+ </content>
+ </binding>
+
+ <binding id="ruleactiontarget-replyto" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:menulist class="ruleactionitem">
+ <xul:menupopup>
+ </xul:menupopup>
+ </xul:menulist>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ document.getAnonymousElementByAttribute(
+ this.parentNode, "class", "ruleactiontype")
+ .getTemplates(true, document.getAnonymousNodes(this)[0]);
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="ruleactiontarget-forwardto" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:textbox class="ruleactionitem" flex="1"/>
+ </content>
+ </binding>
+
+ <binding id="ruleactiontarget-folder" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:menulist class="ruleactionitem folderMenuItem"
+ displayformat="verbose"
+ oncommand="this.parentNode.setPicker(event);">
+ <xul:menupopup type="folder"
+ mode="filing"
+ class="menulist-menupopup"
+ showRecent="true"
+ recentLabel="&recentFolders.label;"
+ showFileHereLabel="true"/>
+ </xul:menulist>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ Components.utils.import("resource:///modules/MailUtils.js", this);
+ let folder = this.menulist.value ?
+ this.MailUtils.getFolderForURI(this.menulist.value) :
+ gFilterList.folder;
+ // An account folder is not a move/copy target; show "Choose Folder".
+ folder = folder.isServer ? null : folder;
+ let menupopup = this.menulist.menupopup;
+ // The menupopup constructor needs to finish first.
+ setTimeout(function() { menupopup.selectFolder(folder); }, 0);
+ ]]>
+ </constructor>
+
+ <field name="menulist">document.getAnonymousNodes(this)[0]</field>
+ <method name="setPicker">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.menulist.menupopup.selectFolder(aEvent.target._folder);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/mailnews/base/search/content/viewLog.js b/mailnews/base/search/content/viewLog.js
new file mode 100644
index 000000000..61ae44df7
--- /dev/null
+++ b/mailnews/base/search/content/viewLog.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gFilterList;
+var gLogFilters;
+var gLogView;
+
+function onLoad()
+{
+ gFilterList = window.arguments[0].filterList;
+
+ gLogFilters = document.getElementById("logFilters");
+ gLogFilters.checked = gFilterList.loggingEnabled;
+
+ gLogView = document.getElementById("logView");
+
+ // for security, disable JS
+ gLogView.docShell.allowJavascript = false;
+
+ gLogView.setAttribute("src", gFilterList.logURL);
+}
+
+function toggleLogFilters()
+{
+ gFilterList.loggingEnabled = gLogFilters.checked;
+}
+
+function clearLog()
+{
+ gFilterList.clearLog();
+
+ // reload the newly truncated file
+ gLogView.reload();
+}
+
diff --git a/mailnews/base/search/content/viewLog.xul b/mailnews/base/search/content/viewLog.xul
new file mode 100644
index 000000000..4ee766395
--- /dev/null
+++ b/mailnews/base/search/content/viewLog.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/viewLog.dtd">
+
+<dialog id="viewLogWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ title="&viewLog.title;"
+ windowtype="mailnews:filterlog"
+ buttons="accept"
+ buttonlabelaccept="&closeLog.label;"
+ buttonaccesskeyaccept="&closeLog.accesskey;"
+ ondialogaccept="window.close();"
+ persist="screenX screenY width height"
+ style="width: 40em; height: 25em;">
+
+ <script type="application/javascript" src="chrome://messenger/content/viewLog.js"/>
+
+ <vbox flex="1">
+ <description>&viewLogInfo.text;</description>
+ <hbox>
+ <checkbox id="logFilters"
+ label="&enableLog.label;"
+ accesskey="&enableLog.accesskey;"
+ oncommand="toggleLogFilters();"/>
+ <spacer flex="1"/>
+ <button label="&clearLog.label;"
+ accesskey="&clearLog.accesskey;"
+ oncommand="clearLog();"/>
+ </hbox>
+ <separator class="thin"/>
+ <hbox flex="1">
+ <browser id="logView"
+ class="inset"
+ type="content"
+ disablehistory="true"
+ disablesecurity="true"
+ src="about:blank"
+ autofind="false"
+ flex="1"/>
+ </hbox>
+ </vbox>
+</dialog>
diff --git a/mailnews/base/search/public/moz.build b/mailnews/base/search/public/moz.build
new file mode 100644
index 000000000..60eb4a18a
--- /dev/null
+++ b/mailnews/base/search/public/moz.build
@@ -0,0 +1,38 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIMsgFilter.idl',
+ 'nsIMsgFilterCustomAction.idl',
+ 'nsIMsgFilterHitNotify.idl',
+ 'nsIMsgFilterList.idl',
+ 'nsIMsgFilterPlugin.idl',
+ 'nsIMsgFilterService.idl',
+ 'nsIMsgOperationListener.idl',
+ 'nsIMsgSearchAdapter.idl',
+ 'nsIMsgSearchCustomTerm.idl',
+ 'nsIMsgSearchNotify.idl',
+ 'nsIMsgSearchScopeTerm.idl',
+ 'nsIMsgSearchSession.idl',
+ 'nsIMsgSearchTerm.idl',
+ 'nsIMsgSearchValidityManager.idl',
+ 'nsIMsgSearchValidityTable.idl',
+ 'nsIMsgSearchValue.idl',
+ 'nsIMsgTraitService.idl',
+ 'nsMsgFilterCore.idl',
+ 'nsMsgSearchCore.idl',
+]
+
+XPIDL_MODULE = 'msgsearch'
+
+EXPORTS += [
+ 'nsMsgBodyHandler.h',
+ 'nsMsgResultElement.h',
+ 'nsMsgSearchAdapter.h',
+ 'nsMsgSearchBoolExpression.h',
+ 'nsMsgSearchScopeTerm.h',
+ 'nsMsgSearchTerm.h',
+]
+
diff --git a/mailnews/base/search/public/nsIMsgFilter.idl b/mailnews/base/search/public/nsIMsgFilter.idl
new file mode 100644
index 000000000..5571b2f9a
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilter.idl
@@ -0,0 +1,142 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+#include "nsMsgFilterCore.idl"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+%{C++
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+%}
+interface nsISupportsArray;
+
+interface nsIArray;
+interface nsIOutputStream;
+interface nsIMsgFilterCustomAction;
+interface nsIMsgFilterList;
+interface nsIMsgSearchScopeTerm;
+interface nsIMsgSearchValue;
+interface nsIMsgSearchTerm;
+
+[scriptable, uuid(36d2748e-9246-44f3-bb74-46cbb0b8c23a)]
+interface nsIMsgRuleAction : nsISupports {
+
+ attribute nsMsgRuleActionType type;
+
+ // target priority.. throws an exception if the action is not priority
+ attribute nsMsgPriorityValue priority;
+
+ // target folder.. throws an exception if the action is not move to folder
+ attribute ACString targetFolderUri;
+
+ // target label. throws an exception if the action is not label
+ attribute nsMsgLabelValue label;
+
+ attribute long junkScore;
+
+ attribute AUTF8String strValue;
+
+ // action id if type is Custom
+ attribute ACString customId;
+
+ // custom action associated with customId
+ // (which must be set prior to reading this attribute)
+ readonly attribute nsIMsgFilterCustomAction customAction;
+
+};
+
+[scriptable, uuid(d304fcfc-b588-11e4-981c-770e1e5d46b0)]
+interface nsIMsgFilter : nsISupports {
+ attribute nsMsgFilterTypeType filterType;
+ /**
+ * some filters are "temporary". For example, the filters we create when the user
+ * filters return receipts to the Sent folder.
+ * we don't show temporary filters in the UI
+ * and we don't write them to disk.
+ */
+ attribute boolean temporary;
+ attribute boolean enabled;
+ attribute AString filterName;
+ attribute ACString filterDesc;
+ attribute ACString unparsedBuffer; //holds the entire filter if we don't know how to handle it
+ attribute boolean unparseable; //whether we could parse the filter or not
+
+ attribute nsIMsgFilterList filterList; // owning filter list
+
+ void AddTerm(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op,
+ in nsIMsgSearchValue value,
+ in boolean BooleanAND,
+ in ACString arbitraryHeader);
+
+ void GetTerm(in long termIndex,
+ out nsMsgSearchAttribValue attrib,
+ out nsMsgSearchOpValue op,
+ out nsIMsgSearchValue value, // bad! using shared structure
+ out boolean BooleanAND,
+ out ACString arbitraryHeader);
+
+ void appendTerm(in nsIMsgSearchTerm term);
+
+ nsIMsgSearchTerm createTerm();
+
+ attribute nsISupportsArray searchTerms;
+
+ attribute nsIMsgSearchScopeTerm scope;
+
+ // Marking noscript because "headers" is actually a null-separated
+ // list of headers, which is not scriptable.
+ [noscript] void MatchHdr(in nsIMsgDBHdr msgHdr, in nsIMsgFolder folder,
+ in nsIMsgDatabase db,
+ in string headers,
+ // [array, size_is(headerSize)] in string headers,
+ in unsigned long headerSize, out boolean result);
+
+
+ /*
+ * Report that Rule was matched and executed when filter logging is enabled.
+ *
+ * @param aFilterAction The filter rule that was invoked.
+ * @param aHeader The header information of the message acted on by
+ * the filter.
+ */
+ void logRuleHit(in nsIMsgRuleAction aFilterAction,
+ in nsIMsgDBHdr aHeader);
+
+ /* Report that filtering failed for some reason when filter logging is enabled.
+ *
+ * @param aFilterAction Filter rule that was invoked.
+ * @param aHeader Header of the message acted on by the filter.
+ * @param aRcode Error code returned by low-level routine that
+ * led to the filter failure.
+ * @param aErrmsg Error message
+ */
+ void logRuleHitFail(in nsIMsgRuleAction aFilterAction,
+ in nsIMsgDBHdr aHeader,
+ in nsresult aRcode,
+ in string aErrmsg );
+
+ nsIMsgRuleAction createAction();
+
+ nsIMsgRuleAction getActionAt(in unsigned long aIndex);
+
+ long getActionIndex(in nsIMsgRuleAction aAction);
+
+ void appendAction(in nsIMsgRuleAction action);
+
+ readonly attribute unsigned long actionCount;
+
+ void clearActionList();
+
+ // Returns the action list in the order it will be really executed in.
+ readonly attribute nsIArray sortedActionList;
+
+ void SaveToTextFile(in nsIOutputStream aStream);
+};
diff --git a/mailnews/base/search/public/nsIMsgFilterCustomAction.idl b/mailnews/base/search/public/nsIMsgFilterCustomAction.idl
new file mode 100644
index 000000000..d3a1c5584
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilterCustomAction.idl
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgFilterCore.idl"
+
+interface nsIArray;
+interface nsIMsgCopyServiceListener;
+interface nsIMsgWindow;
+
+/**
+ * describes a custom action added to a message filter
+ */
+[scriptable,uuid(4699C41E-3671-436e-B6AE-4FD8106747E4)]
+interface nsIMsgFilterCustomAction : nsISupports
+{
+ /* globally unique string to identify this filter action.
+ * recommended form: ExtensionName@example.com#ActionName
+ */
+ readonly attribute ACString id;
+
+ /* action name to display in action list. This should be localized. */
+ readonly attribute AString name;
+
+ /**
+ * Is this custom action valid for a particular filter type?
+ *
+ * @param type the filter type
+ * @param scope the search scope
+ *
+ * @return true if valid
+ */
+ boolean isValidForType(in nsMsgFilterTypeType type, in nsMsgSearchScopeValue scope);
+
+ /**
+ * After the user inputs a particular action value for the action, determine
+ * if that value is valid.
+ *
+ * @param actionValue The value entered.
+ * @param actionFolder Folder in the filter list
+ * @param filterType Filter Type (Manual, OfflineMail, etc.)
+ *
+ * @return errorMessage A localized message to display if invalid
+ * Set to null if the actionValue is valid
+ */
+ AUTF8String validateActionValue(in AUTF8String actionValue,
+ in nsIMsgFolder actionFolder,
+ in nsMsgFilterTypeType filterType);
+
+ /* allow duplicate actions in the same filter list? Default No. */
+ attribute boolean allowDuplicates;
+
+ /*
+ * The custom action itself
+ *
+ * Generally for the apply method, folder-based methods give correct
+ * results and are preferred if available. Otherwise, be careful
+ * that the action does correct notifications to maintain counts, and correct
+ * manipulations of both IMAP and local non-database storage of message
+ * metadata.
+ */
+
+ /**
+ * Apply the custom action to an array of messages
+ *
+ * @param msgHdrs array of nsIMsgDBHdr objects of messages
+ * @param actionValue user-set value to use in the action
+ * @param copyListener calling method (filterType Manual only)
+ * @param filterType type of filter being applied
+ * @param msgWindow message window
+ */
+
+ void apply(in nsIArray msgHdrs /* nsIMsgDBHdr array */,
+ in AUTF8String actionValue,
+ in nsIMsgCopyServiceListener copyListener,
+ in nsMsgFilterTypeType filterType,
+ in nsIMsgWindow msgWindow);
+
+ /* does this action start an async action? If so, a copy listener must
+ * be used to continue filter processing after the action. This only
+ * applies to after-the-fact (manual) filters. Call OnStopCopy when done
+ * using the copyListener to continue.
+ */
+ readonly attribute boolean isAsync;
+
+ /// Does this action need the message body?
+ readonly attribute boolean needsBody;
+};
+
+
diff --git a/mailnews/base/search/public/nsIMsgFilterHitNotify.idl b/mailnews/base/search/public/nsIMsgFilterHitNotify.idl
new file mode 100644
index 000000000..d85226e60
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilterHitNotify.idl
@@ -0,0 +1,27 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgFilter;
+interface nsIMsgWindow;
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIMsgFilterHitNotify is an interface designed to make evaluating filters
+// easier. Clients typically open a filter list and ask the filter list to
+// evaluate the filters for a particular message, and pass in an
+// interface pointer to be notified of hits. The filter list will call the
+// ApplyFilterHit method on the interface pointer in case of hits, along with
+// the desired action and value.
+// return value is used to indicate whether the
+// filter list should continue trying to apply filters or not.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+[scriptable, uuid(c9f15174-1f3f-11d3-a51b-0060b0fc04b7)]
+interface nsIMsgFilterHitNotify : nsISupports {
+ boolean applyFilterHit(in nsIMsgFilter filter, in nsIMsgWindow msgWindow);
+};
+
diff --git a/mailnews/base/search/public/nsIMsgFilterList.idl b/mailnews/base/search/public/nsIMsgFilterList.idl
new file mode 100644
index 000000000..b9c24b526
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilterList.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgFilterHitNotify.idl"
+#include "nsMsgFilterCore.idl"
+
+interface nsIFile;
+interface nsIOutputStream;
+interface nsIMsgFilter;
+interface nsIMsgFolder;
+
+///////////////////////////////////////////////////////////////////////////////
+// The Msg Filter List is an interface designed to make accessing filter lists
+// easier. Clients typically open a filter list and either enumerate the filters,
+// or add new filters, or change the order around...
+//
+///////////////////////////////////////////////////////////////////////////////
+
+typedef long nsMsgFilterFileAttribValue;
+
+[scriptable, uuid(5d0ec03e-7e2f-49e9-b58a-b274c85f279e)]
+interface nsIMsgFilterList : nsISupports {
+
+ const nsMsgFilterFileAttribValue attribNone = 0;
+ const nsMsgFilterFileAttribValue attribVersion = 1;
+ const nsMsgFilterFileAttribValue attribLogging = 2;
+ const nsMsgFilterFileAttribValue attribName = 3;
+ const nsMsgFilterFileAttribValue attribEnabled = 4;
+ const nsMsgFilterFileAttribValue attribDescription = 5;
+ const nsMsgFilterFileAttribValue attribType = 6;
+ const nsMsgFilterFileAttribValue attribScriptFile = 7;
+ const nsMsgFilterFileAttribValue attribAction = 8;
+ const nsMsgFilterFileAttribValue attribActionValue = 9;
+ const nsMsgFilterFileAttribValue attribCondition = 10;
+ const nsMsgFilterFileAttribValue attribCustomId = 11;
+
+ attribute nsIMsgFolder folder;
+ readonly attribute short version;
+ readonly attribute ACString arbitraryHeaders;
+ readonly attribute boolean shouldDownloadAllHeaders;
+ readonly attribute unsigned long filterCount;
+ nsIMsgFilter getFilterAt(in unsigned long filterIndex);
+ nsIMsgFilter getFilterNamed(in AString filterName);
+
+ void setFilterAt(in unsigned long filterIndex, in nsIMsgFilter filter);
+ void removeFilter(in nsIMsgFilter filter);
+ void removeFilterAt(in unsigned long filterIndex);
+
+ void moveFilterAt(in unsigned long filterIndex,
+ in nsMsgFilterMotionValue motion);
+ void moveFilter(in nsIMsgFilter filter,
+ in nsMsgFilterMotionValue motion);
+
+ void insertFilterAt(in unsigned long filterIndex, in nsIMsgFilter filter);
+
+ attribute boolean loggingEnabled;
+
+ nsIMsgFilter createFilter(in AString name);
+
+ void saveToFile(in nsIOutputStream stream);
+
+ void parseCondition(in nsIMsgFilter aFilter, in string condition);
+ // this is temporary so that we can save the filterlist to disk
+ // without knowing where the filters were read from intially
+ // (such as the filter list dialog)
+ attribute nsIFile defaultFile;
+ void saveToDefaultFile();
+
+
+ // marking noscript because headers is a null-separated list
+ // of strings, which is not scriptable
+ [noscript]
+ void applyFiltersToHdr(in nsMsgFilterTypeType filterType,
+ in nsIMsgDBHdr msgHdr,
+ in nsIMsgFolder folder,
+ in nsIMsgDatabase db,
+ in string headers,
+ //[array, size_is(headerSize)] in string headers,
+ in unsigned long headerSize,
+ in nsIMsgFilterHitNotify listener,
+ in nsIMsgWindow msgWindow);
+
+ // IO routines, used by filter object filing code.
+ void writeIntAttr(in nsMsgFilterFileAttribValue attrib, in long value, in nsIOutputStream stream);
+ void writeStrAttr(in nsMsgFilterFileAttribValue attrib, in string value, in nsIOutputStream stream);
+ void writeWstrAttr(in nsMsgFilterFileAttribValue attrib, in wstring value, in nsIOutputStream stream);
+ void writeBoolAttr(in nsMsgFilterFileAttribValue attrib, in boolean value, in nsIOutputStream stream);
+ boolean matchOrChangeFilterTarget(in ACString oldUri, in ACString newUri, in boolean caseInsensitive);
+
+ // for filter logging
+ // If both attributes are fetched successfully, they guarantee
+ // the log file exists and is set up with a header.
+ attribute nsIOutputStream logStream;
+ readonly attribute ACString logURL;
+ void clearLog();
+ void flushLogIfNecessary();
+};
+
+
+/* these longs are all actually of type nsMsgFilterMotionValue */
+[scriptable, uuid(d067b528-304e-11d3-a0e1-00a0c900d445)]
+interface nsMsgFilterMotion {
+ const long up = 0;
+ const long down = 1;
+};
diff --git a/mailnews/base/search/public/nsIMsgFilterPlugin.idl b/mailnews/base/search/public/nsIMsgFilterPlugin.idl
new file mode 100644
index 000000000..9632471c2
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilterPlugin.idl
@@ -0,0 +1,350 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgWindow;
+interface nsIFile;
+
+/**
+ * This interface is still very much under development, and is not yet stable.
+ */
+
+[scriptable, uuid(e2e56690-a676-11d6-80c9-00008646b737)]
+interface nsIMsgFilterPlugin : nsISupports
+{
+ /**
+ * Do any necessary cleanup: flush and close any open files, etc.
+ */
+ void shutdown();
+
+ /**
+ * Some protocols (ie IMAP) can, as an optimization, avoid
+ * downloading all message header lines. If your plugin doesn't need
+ * any more than the minimal set, it can return false for this attribute.
+ */
+ readonly attribute boolean shouldDownloadAllHeaders;
+
+};
+
+/*
+ * These interfaces typically implement a Bayesian classifier of messages.
+ *
+ * Two sets of interfaces may be used: the older junk-only interfaces, and
+ * the newer trait-oriented interfaces that treat junk classification as
+ * one of a set of classifications to accomplish.
+ */
+
+[scriptable, uuid(b15a0f9c-df07-4af0-9ba8-80dca68ac35d)]
+interface nsIJunkMailClassificationListener : nsISupports
+{
+ /**
+ * Inform a listener of a message's classification as junk. At the end
+ * of a batch of classifications, signify end of batch by calling with
+ * null aMsgURI (other parameters are don't care)
+ *
+ * @param aMsgURI URI of the message that was classified.
+ * @param aClassification classification of message as UNCLASSIFIED, GOOD,
+ * or JUNK.
+ * @param aJunkPercent indicator of degree of uncertainty, with 100 being
+ * probably junk, and 0 probably good
+ */
+ void onMessageClassified(in string aMsgURI,
+ in nsMsgJunkStatus aClassification,
+ in uint32_t aJunkPercent);
+};
+
+[scriptable, uuid(AF247D07-72F0-482d-9EAB-5A786407AA4C)]
+interface nsIMsgTraitClassificationListener : nsISupports
+{
+ /**
+ * Inform a listener of a message's match to traits. The list
+ * of traits being matched is in aTraits. Corresponding
+ * indicator of match (percent) is in aPercents. At the end
+ * of a batch of classifications, signify end of batch by calling with
+ * null aMsgURI (other parameters are don't care)
+ *
+ * @param aMsgURI URI of the message that was classified
+ * @param aTraitCount length of aTraits and aPercents arrays
+ * @param aTraits array of matched trait ids
+ * @param aPercents array of percent match (0 is unmatched, 100 is fully
+ * matched) of the trait with the corresponding array
+ * index in aTraits
+ */
+ void onMessageTraitsClassified(in string aMsgURI,
+ in unsigned long aTraitCount,
+ [array, size_is(aTraitCount)] in unsigned long aTraits,
+ [array, size_is(aTraitCount)] in unsigned long aPercents);
+};
+
+[scriptable, uuid(12667532-88D1-44a7-AD48-F73719BE5C92)]
+interface nsIMsgTraitDetailListener : nsISupports
+{
+ /**
+ * Inform a listener of details of a message's match to traits.
+ * This returns the tokens that were used in the calculation,
+ * the calculated percent probability that each token matches the trait,
+ * and a running estimate (starting with the strongest tokens) of the
+ * combined total probability that a message matches the trait, when
+ * only tokens stronger than the current token are used.
+ *
+ * @param aMsgURI URI of the message that was classified
+ * @param aProTrait trait id of pro trait for the calculation
+ * @param tokenCount length of arrays that follow
+ * @param tokenStrings the string for a particular token
+ * @param tokenPercents calculated probability that a message with that token
+ * matches the trait
+ * @param runningPercents calculated probability that the message matches the
+ * trait, accounting for this token and all stronger tokens.
+ */
+ void onMessageTraitDetails(in string aMsgUri,
+ in unsigned long aProTrait,
+ in unsigned long tokenCount,
+ [array, size_is(tokenCount)] in wstring tokenStrings,
+ [array, size_is(tokenCount)] in unsigned long tokenPercents,
+ [array, size_is(tokenCount)] in unsigned long runningPercents);
+};
+
+[scriptable, uuid(8EA5BBCA-F735-4d43-8541-D203D8E2FF2F)]
+interface nsIJunkMailPlugin : nsIMsgFilterPlugin
+{
+ /**
+ * Message classifications.
+ */
+ const nsMsgJunkStatus UNCLASSIFIED = 0;
+ const nsMsgJunkStatus GOOD = 1;
+ const nsMsgJunkStatus JUNK = 2;
+
+ /**
+ * Message junk score constants. Junkscore can only be one of these two
+ * values (or not set).
+ */
+ const nsMsgJunkScore IS_SPAM_SCORE = 100; // junk
+ const nsMsgJunkScore IS_HAM_SCORE = 0; // not junk
+
+ /**
+ * Trait ids for junk analysis. These values are fixed to ensure
+ * backwards compatibility with existing junk-oriented classification
+ * code.
+ */
+
+ const unsigned long GOOD_TRAIT = 1; // good
+ const unsigned long JUNK_TRAIT = 2; // junk
+
+ /**
+ * Given a message URI, determine what its current classification is
+ * according to the current training set.
+ */
+ void classifyMessage(in string aMsgURI, in nsIMsgWindow aMsgWindow,
+ in nsIJunkMailClassificationListener aListener);
+
+ void classifyMessages(in unsigned long aCount,
+ [array, size_is(aCount)] in string aMsgURIs,
+ in nsIMsgWindow aMsgWindow,
+ in nsIJunkMailClassificationListener aListener);
+
+ /**
+ * Given a message URI, evaluate its relative match to a list of
+ * traits according to the current training set.
+ *
+ * @param aMsgURI URI of the message to be evaluated
+ * @param aTraitCount length of aProTraits, aAntiTraits arrays
+ * @param aProTraits array of trait ids for trained messages that
+ * match the tested trait (for example,
+ * JUNK_TRAIT if testing for junk)
+ * @param aAntiTraits array of trait ids for trained messages that
+ * do not match the tested trait (for example,
+ * GOOD_TRAIT if testing for junk)
+ * @param aTraitListener trait-oriented callback listener (may be null)
+ * @param aMsgWindow current message window (may be null)
+ * @param aJunkListener junk-oriented callback listener (may be null)
+ */
+
+ void classifyTraitsInMessage(
+ in string aMsgURI,
+ in unsigned long aTraitCount,
+ [array, size_is(aTraitCount)] in unsigned long aProTraits,
+ [array, size_is(aTraitCount)] in unsigned long aAntiTraits,
+ in nsIMsgTraitClassificationListener aTraitListener,
+ [optional] in nsIMsgWindow aMsgWindow,
+ [optional] in nsIJunkMailClassificationListener aJunkListener);
+
+ /**
+ * Given an array of message URIs, evaluate their relative match to a
+ * list of traits according to the current training set.
+ *
+ * @param aCount Number of messages to evaluate
+ * @param aMsgURIs array of URIs of the messages to be evaluated
+ * @param aTraitCount length of aProTraits, aAntiTraits arrays
+ * @param aProTraits array of trait ids for trained messages that
+ * match the tested trait (for example,
+ * JUNK_TRAIT if testing for junk)
+ * @param aAntiTraits array of trait ids for trained messages that
+ * do not match the tested trait (for example,
+ * GOOD_TRAIT if testing for junk)
+ * @param aTraitListener trait-oriented callback listener (may be null)
+ * @param aMsgWindow current message window (may be null)
+ * @param aJunkListener junk-oriented callback listener (may be null)
+ */
+
+ void classifyTraitsInMessages(
+ in unsigned long aCount,
+ [array, size_is(aCount)] in string aMsgURIs,
+ in unsigned long aTraitCount,
+ [array, size_is(aTraitCount)] in unsigned long aProTraits,
+ [array, size_is(aTraitCount)] in unsigned long aAntiTraits,
+ in nsIMsgTraitClassificationListener aTraitListener,
+ [optional] in nsIMsgWindow aMsgWindow,
+ [optional] in nsIJunkMailClassificationListener aJunkListener);
+
+ /**
+ * Called when a user forces the classification of a message. Should
+ * cause the training set to be updated appropriately.
+ *
+ * @arg aMsgURI URI of the message to be classified
+ * @arg aOldUserClassification Was it previous manually classified
+ * by the user? If so, how?
+ * @arg aNewClassification New manual classification.
+ * @arg aListener Callback (may be null)
+ */
+ void setMessageClassification(
+ in string aMsgURI, in nsMsgJunkStatus aOldUserClassification,
+ in nsMsgJunkStatus aNewClassification,
+ in nsIMsgWindow aMsgWindow,
+ in nsIJunkMailClassificationListener aListener);
+
+ /**
+ * Called when a user forces a change in the classification of a message.
+ * Should cause the training set to be updated appropriately.
+ *
+ * @param aMsgURI URI of the message to be classified
+ * @param aOldCount length of aOldTraits array
+ * @param aOldTraits array of trait IDs of the old
+ * message classification(s), if any
+ * @param aNewCount length of aNewTraits array
+ * @param aNewTraits array of trait IDs of the new
+ * message classification(s), if any
+ * @param aTraitListener trait-oriented listener (may be null)
+ * @param aMsgWindow current message window (may be null)
+ * @param aJunkListener junk-oriented listener (may be null)
+ */
+ void setMsgTraitClassification(
+ in string aMsgURI,
+ in unsigned long aOldCount,
+ [array, size_is(aOldCount)] in unsigned long aOldTraits,
+ in unsigned long aNewCount,
+ [array, size_is(aNewCount)] in unsigned long aNewTraits,
+ [optional] in nsIMsgTraitClassificationListener aTraitListener,
+ [optional] in nsIMsgWindow aMsgWindow,
+ [optional] in nsIJunkMailClassificationListener aJunkListener);
+
+ readonly attribute boolean userHasClassified;
+
+ /** Removes the training file and clears out any in memory training tokens.
+ User must retrain after doing this.
+ **/
+ void resetTrainingData();
+
+ /**
+ * Given a message URI, return a list of tokens and their contribution to
+ * the analysis of a message's match to a trait according to the
+ * current training set.
+ *
+ * @param aMsgURI URI of the message to be evaluated
+ * @param aProTrait trait id for trained messages that match the
+ * tested trait (for example, JUNK_TRAIT if testing
+ * for junk)
+ * @param aAntiTrait trait id for trained messages that do not match
+ * the tested trait (for example, GOOD_TRAIT
+ * if testing for junk)
+ * @param aListener callback listener for results
+ * @param aMsgWindow current message window (may be null)
+ */
+ void detailMessage(
+ in string aMsgURI,
+ in unsigned long aProTrait,
+ in unsigned long aAntiTrait,
+ in nsIMsgTraitDetailListener aListener,
+ [optional] in nsIMsgWindow aMsgWindow);
+
+};
+
+/**
+ * The nsIMsgCorpus interface manages a corpus of mail data used for
+ * statistical analysis of messages.
+ */
+[scriptable, uuid(70BAD26F-DFD4-41bd-8FAB-4C09B9C1E845)]
+interface nsIMsgCorpus : nsISupports
+{
+ /**
+ * Clear the corpus data for a trait id.
+ *
+ * @param aTrait trait id
+ */
+ void clearTrait(in unsigned long aTrait);
+
+ /**
+ * Update corpus data from a file.
+ *
+ * @param aFile the file with the data, in the format:
+ *
+ * Format of the trait file for version 1:
+ * [0xFCA93601] (the 01 is the version)
+ * for each trait to write:
+ * [id of trait to write] (0 means end of list)
+ * [number of messages per trait]
+ * for each token with non-zero count
+ * [count]
+ * [length of word]word
+ *
+ * @param aIsAdd should the data be added, or removed? True if
+ * adding, false if removing.
+ *
+ * @param aRemapCount number of items in the parallel arrays aFromTraits,
+ * aToTraits. These arrays allow conversion of the
+ * trait id stored in the file (which may be originated
+ * externally) to the trait id used in the local corpus
+ * (which is defined locally using nsIMsgTraitService, and
+ * mapped by that interface to a globally unique trait
+ * id string).
+ *
+ * @param aFromTraits array of trait ids used in aFile. If aFile contains
+ * trait ids that are not in this array, they are not
+ * remapped, but assummed to be local trait ids.
+ *
+ * @param aToTraits array of trait ids, corresponding to elements of
+ * aFromTraits, that represent the local trait ids to
+ * be used in storing data from aFile into the local corpus.
+ */
+ void updateData(in nsIFile aFile, in boolean aIsAdd,
+ [optional] in unsigned long aRemapCount,
+ [optional, array, size_is(aRemapCount)] in unsigned long aFromTraits,
+ [optional, array, size_is(aRemapCount)] in unsigned long aToTraits);
+
+ /**
+ * Get the corpus count for a token as a string.
+ *
+ * @param aWord string of characters representing the token
+ * @param aTrait trait id
+ *
+ * @return count of that token in the corpus
+ *
+ */
+ unsigned long getTokenCount(in AUTF8String aWord, in unsigned long aTrait);
+
+ /**
+ * Gives information on token and message count information in the
+ * training data corpus.
+ *
+ * @param aTrait trait id (may be null)
+ * @param aMessageCount count of messages that have been trained with aTrait
+ *
+ * @return token count for all traits
+ */
+
+ unsigned long corpusCounts(in unsigned long aTrait, out unsigned long aMessageCount);
+};
diff --git a/mailnews/base/search/public/nsIMsgFilterService.idl b/mailnews/base/search/public/nsIMsgFilterService.idl
new file mode 100644
index 000000000..8cf69aeed
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilterService.idl
@@ -0,0 +1,95 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsMsgFilterCore.idl"
+
+interface nsIMsgFilterList;
+interface nsIMsgWindow;
+interface nsIMsgFilterCustomAction;
+interface nsISimpleEnumerator;
+interface nsIFile;
+interface nsIMsgFolder;
+interface nsIMsgSearchCustomTerm;
+interface nsIArray;
+interface nsIMsgOperationListener;
+
+[scriptable, uuid(78a74023-1692-4567-8d72-9ca58fbbd427)]
+interface nsIMsgFilterService : nsISupports {
+
+ nsIMsgFilterList OpenFilterList(in nsIFile filterFile, in nsIMsgFolder rootFolder, in nsIMsgWindow msgWindow);
+ void CloseFilterList(in nsIMsgFilterList filterList);
+
+ void SaveFilterList(in nsIMsgFilterList filterList,
+ in nsIFile filterFile);
+
+ void CancelFilterList(in nsIMsgFilterList filterList);
+ nsIMsgFilterList getTempFilterList(in nsIMsgFolder aFolder);
+ void applyFiltersToFolders(in nsIMsgFilterList aFilterList,
+ in nsIArray aFolders,
+ in nsIMsgWindow aMsgWindow,
+ [optional] in nsIMsgOperationListener aCallback);
+
+ /*
+ * Apply filters to a specific list of messages in a folder.
+ * @param aFilterType The type of filter to match against
+ * @param aMsgHdrList The list of message headers (nsIMsgDBHdr objects)
+ * @param aFolder The folder the messages belong to
+ * @param aMsgWindow A UI window for attaching progress/dialogs
+ * @param aCallback A listener that gets notified of any filtering error
+ */
+ void applyFilters(in nsMsgFilterTypeType aFilterType,
+ in nsIArray aMsgHdrList,
+ in nsIMsgFolder aFolder,
+ in nsIMsgWindow aMsgWindow,
+ [optional] in nsIMsgOperationListener aCallback);
+
+ /**
+ * add a custom filter action
+ *
+ * @param aAction the custom action to add
+ */
+ void addCustomAction(in nsIMsgFilterCustomAction aAction);
+
+ /**
+ * get the list of custom actions
+ *
+ * @return enumerator of nsIMsgFilterCustomAction objects
+ */
+ nsISimpleEnumerator getCustomActions();
+
+ /**
+ * lookup a custom action given its id
+ *
+ * @param id unique identifier for a particular custom action
+ *
+ * @return the custom action, or null if not found
+ */
+ nsIMsgFilterCustomAction getCustomAction(in ACString id);
+
+ /**
+ * add a custom search term
+ *
+ * @param aTerm the custom term to add
+ */
+ void addCustomTerm(in nsIMsgSearchCustomTerm aTerm);
+
+ /**
+ * get the list of custom search terms
+ *
+ * @return enumerator of nsIMsgSearchCustomTerm objects
+ */
+ nsISimpleEnumerator getCustomTerms();
+
+ /**
+ * lookup a custom search term given its id
+ *
+ * @param id unique identifier for a particular custom search term
+ *
+ * @return the custom search term, or null if not found
+ */
+ nsIMsgSearchCustomTerm getCustomTerm(in ACString id);
+
+};
diff --git a/mailnews/base/search/public/nsIMsgOperationListener.idl b/mailnews/base/search/public/nsIMsgOperationListener.idl
new file mode 100644
index 000000000..30751cd5c
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgOperationListener.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+// Listener used to notify when an operation has completed.
+[scriptable, uuid(bdaef6ff-0909-435b-8fcd-76525dd2364c)]
+interface nsIMsgOperationListener : nsISupports {
+ /**
+ * Called when the operation stops (possibly with errors)
+ *
+ * @param aStatus Success or failure of the operation
+ */
+ void onStopOperation(in nsresult aStatus);
+};
diff --git a/mailnews/base/search/public/nsIMsgSearchAdapter.idl b/mailnews/base/search/public/nsIMsgSearchAdapter.idl
new file mode 100644
index 000000000..b818939a1
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchAdapter.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+#include "nsIMsgSearchScopeTerm.idl"
+
+[ptr] native nsMsgResultElement(nsMsgResultElement);
+
+%{C++
+class nsMsgResultElement;
+%}
+
+[scriptable, uuid(0b09078b-e0cd-440a-afee-01f45808ee74)]
+interface nsIMsgSearchAdapter : nsISupports {
+ void ValidateTerms();
+ void Search(out boolean done);
+ void SendUrl();
+ void CurrentUrlDone(in nsresult exitCode);
+
+ void AddHit(in nsMsgKey key);
+ void AddResultElement(in nsIMsgDBHdr aHdr);
+
+ [noscript] void OpenResultElement(in nsMsgResultElement element);
+ [noscript] void ModifyResultElement(in nsMsgResultElement element,
+ in nsMsgSearchValue value);
+
+ readonly attribute string encoding;
+
+ [noscript] nsIMsgFolder FindTargetFolder([const] in nsMsgResultElement
+ element);
+ void Abort();
+ void getSearchCharsets(out AString srcCharset, out AString destCharset);
+ /*
+ * Clear the saved scope reference. This is used when deleting scope, which is not
+ * reference counted in nsMsgSearchSession
+ */
+ void clearScope();
+};
+
diff --git a/mailnews/base/search/public/nsIMsgSearchCustomTerm.idl b/mailnews/base/search/public/nsIMsgSearchCustomTerm.idl
new file mode 100644
index 000000000..3983eb1a3
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchCustomTerm.idl
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgSearchCore.idl"
+
+/**
+ * describes a custom term added to a message search or filter
+ */
+[scriptable,uuid(925DB5AA-21AF-494c-8652-984BC7BAD13A)]
+interface nsIMsgSearchCustomTerm : nsISupports
+{
+ /**
+ * globally unique string to identify this search term.
+ * recommended form: ExtensionName@example.com#TermName
+ * Commas and quotes are not allowed, the id must not
+ * parse to an integer, and names of standard search
+ * attributes in SearchAttribEntryTable in nsMsgSearchTerm.cpp
+ * are not allowed.
+ */
+ readonly attribute ACString id;
+
+ /// name to display in term list. This should be localized. */
+ readonly attribute AString name;
+
+ /// Does this term need the message body?
+ readonly attribute boolean needsBody;
+
+ /**
+ * Is this custom term enabled?
+ *
+ * @param scope search scope (nsMsgSearchScope)
+ * @param op search operator (nsMsgSearchOp). If null, determine
+ * if term is available for any operator.
+ *
+ * @return true if enabled
+ */
+ boolean getEnabled(in nsMsgSearchScopeValue scope,
+ in nsMsgSearchOpValue op);
+
+ /**
+ * Is this custom term available?
+ *
+ * @param scope search scope (nsMsgSearchScope)
+ * @param op search operator (nsMsgSearchOp). If null, determine
+ * if term is available for any operator.
+ *
+ * @return true if available
+ */
+ boolean getAvailable(in nsMsgSearchScopeValue scope,
+ in nsMsgSearchOpValue op);
+
+ /**
+ * List the valid operators for this term.
+ *
+ * @param scope search scope (nsMsgSearchScope)
+ * @param length object to hold array length
+ *
+ * @return array of operators
+ */
+ void getAvailableOperators(in nsMsgSearchScopeValue scope,
+ out unsigned long length,
+ [retval, array, size_is(length)]
+ out nsMsgSearchOpValue operators);
+
+ /**
+ * Apply the custom search term to a message
+ *
+ * @param msgHdr header database reference representing the message
+ * @param searchValue user-set value to use in the search
+ * @param searchOp search operator (Contains, IsHigherThan, etc.)
+ *
+ * @return true if the term matches the message, else false
+ */
+
+ boolean match(in nsIMsgDBHdr msgHdr,
+ in AUTF8String searchValue,
+ in nsMsgSearchOpValue searchOp);
+};
diff --git a/mailnews/base/search/public/nsIMsgSearchNotify.idl b/mailnews/base/search/public/nsIMsgSearchNotify.idl
new file mode 100644
index 000000000..c602f708c
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchNotify.idl
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgDBHdr;
+interface nsIMsgSearchSession;
+interface nsIMsgFolder;
+
+// when a search is run, this interface is passed in as a listener
+// on the search.
+[scriptable, uuid(ca37784d-352b-4c39-8ccb-0abc1a93f681)]
+interface nsIMsgSearchNotify : nsISupports
+{
+ void onSearchHit(in nsIMsgDBHdr header, in nsIMsgFolder folder);
+
+ // notification that a search has finished.
+ void onSearchDone(in nsresult status);
+ /*
+ * until we can encode searches with a URI, this will be an
+ * out-of-bound way to connect a set of search terms to a datasource
+ */
+
+ /*
+ * called when a new search begins
+ */
+ void onNewSearch();
+};
+
diff --git a/mailnews/base/search/public/nsIMsgSearchScopeTerm.idl b/mailnews/base/search/public/nsIMsgSearchScopeTerm.idl
new file mode 100644
index 000000000..32c590979
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchScopeTerm.idl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMsgSearchSession.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgDBHdr;
+interface nsILineInputStream;
+interface nsIInputStream;
+
+[scriptable, uuid(934672c3-9b8f-488a-935d-87b4023fa0be)]
+interface nsIMsgSearchScopeTerm : nsISupports {
+ nsIInputStream getInputStream(in nsIMsgDBHdr aHdr);
+ void closeInputStream();
+ readonly attribute nsIMsgFolder folder;
+ readonly attribute nsIMsgSearchSession searchSession;
+};
+
diff --git a/mailnews/base/search/public/nsIMsgSearchSession.idl b/mailnews/base/search/public/nsIMsgSearchSession.idl
new file mode 100644
index 000000000..36ed7409e
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchSession.idl
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgSearchValue.idl"
+
+interface nsIMsgSearchAdapter;
+interface nsIMsgSearchTerm;
+interface nsIMsgSearchNotify;
+interface nsIMsgHdr;
+interface nsIMsgDatabase;
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+%{C++
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+%}
+interface nsISupportsArray;
+interface nsIMsgWindow;
+
+//////////////////////////////////////////////////////////////////////////////
+// The Msg Search Session is an interface designed to make constructing
+// searches easier. Clients typically build up search terms, and then run
+// the search
+//////////////////////////////////////////////////////////////////////////////
+
+[scriptable, uuid(1ed69bbf-7983-4602-9a9b-2f2263a78878)]
+interface nsIMsgSearchSession : nsISupports {
+
+/**
+ * add a search term to the search session
+ *
+ * @param attrib search attribute (e.g. nsMsgSearchAttrib::Subject)
+ * @param op search operator (e.g. nsMsgSearchOp::Contains)
+ * @param value search value (e.g. "Dogbert", see nsIMsgSearchValue)
+ * @param BooleanAND set to true if associated boolean operator is AND
+ * @param customString if attrib > nsMsgSearchAttrib::OtherHeader,
+ * a user defined arbitrary header
+ * if attrib == nsMsgSearchAttrib::Custom, the custom id
+ * otherwise ignored
+ */
+ void addSearchTerm(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op,
+ in nsIMsgSearchValue value,
+ in boolean BooleanAND,
+ in string customString);
+
+ readonly attribute nsISupportsArray searchTerms;
+
+ nsIMsgSearchTerm createTerm ();
+ void appendTerm(in nsIMsgSearchTerm term);
+
+ /**
+ * @name Search notification flags
+ * These flags determine which notifications will be sent.
+ * @{
+ */
+ /// search started notification
+ const long onNewSearch = 0x1;
+
+ /// search finished notification
+ const long onSearchDone = 0x2;
+
+ /// search hit notification
+ const long onSearchHit = 0x4;
+
+ const long allNotifications = 0x7;
+ /** @} */
+
+ /**
+ * Add a listener to get notified of search starts, stops, and hits.
+ *
+ * @param aListener listener
+ * @param aNotifyFlags which notifications to send. Defaults to all
+ */
+ void registerListener (in nsIMsgSearchNotify aListener,
+ [optional] in long aNotifyFlags);
+ void unregisterListener (in nsIMsgSearchNotify listener);
+
+ readonly attribute unsigned long numSearchTerms;
+
+ readonly attribute nsIMsgSearchAdapter runningAdapter;
+
+ void getNthSearchTerm(in long whichTerm,
+ in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op,
+ in nsIMsgSearchValue value); // wrong, should be out
+
+ long countSearchScopes();
+
+ void getNthSearchScope(in long which,out nsMsgSearchScopeValue scopeId, out nsIMsgFolder folder);
+
+ /* add a scope (e.g. a mail folder) to the search */
+ void addScopeTerm(in nsMsgSearchScopeValue scope,
+ in nsIMsgFolder folder);
+
+ void addDirectoryScopeTerm(in nsMsgSearchScopeValue scope);
+
+ void clearScopes();
+
+ /* Call this function everytime the scope changes! It informs the FE if
+ the current scope support custom header use. FEs should not display the
+ custom header dialog if custom headers are not supported */
+ [noscript] boolean ScopeUsesCustomHeaders(in nsMsgSearchScopeValue scope,
+ /* could be a folder or server based on scope */
+ in voidPtr selection,
+ in boolean forFilters);
+
+ /* use this to determine if your attribute is a string attrib */
+ boolean IsStringAttribute(in nsMsgSearchAttribValue attrib);
+
+ /* add all scopes of a given type to the search */
+ void AddAllScopes(in nsMsgSearchScopeValue attrib);
+
+ void search(in nsIMsgWindow aWindow);
+ void interruptSearch();
+
+ // these two methods are used when the search session is using
+ // a timer to do local search, and the search adapter needs
+ // to run a url (e.g., to reparse a local folder) and wants to
+ // pause the timer while running the url. This will fail if the
+ // current adapter is not using a timer.
+ void pauseSearch();
+ void resumeSearch();
+
+ [noscript] readonly attribute voidPtr searchParam;
+ readonly attribute nsMsgSearchType searchType;
+
+ [noscript] nsMsgSearchType SetSearchParam(in nsMsgSearchType type,
+ in voidPtr param);
+
+ boolean MatchHdr(in nsIMsgDBHdr aMsgHdr, in nsIMsgDatabase aDatabase);
+
+ void addSearchHit(in nsIMsgDBHdr header, in nsIMsgFolder folder);
+
+ readonly attribute long numResults;
+ attribute nsIMsgWindow window;
+
+ /* these longs are all actually of type nsMsgSearchBooleanOp */
+ const long BooleanOR=0;
+ const long BooleanAND=1;
+};
diff --git a/mailnews/base/search/public/nsIMsgSearchTerm.idl b/mailnews/base/search/public/nsIMsgSearchTerm.idl
new file mode 100644
index 000000000..e464ecd70
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchTerm.idl
@@ -0,0 +1,156 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsMsgSearchCore.idl"
+#include "nsIMsgSearchValue.idl"
+
+interface nsIMsgDBHdr;
+interface nsIMsgDatabase;
+interface nsIMsgSearchScopeTerm;
+
+[scriptable, uuid(705a2b5a-5efc-495c-897a-bef1161cd3c0)]
+interface nsIMsgSearchTerm : nsISupports {
+ attribute nsMsgSearchAttribValue attrib;
+ attribute nsMsgSearchOpValue op;
+ attribute nsIMsgSearchValue value;
+
+ attribute boolean booleanAnd;
+ attribute ACString arbitraryHeader;
+ /**
+ * Not to be confused with arbitraryHeader, which is a header in the
+ * rfc822 message. This is a property of the nsIMsgDBHdr, and may have
+ * nothing to do the message headers, e.g., gloda-id.
+ * value.str will be compared with nsIMsgHdr::GetProperty(hdrProperty).
+ */
+ attribute ACString hdrProperty;
+
+ /// identifier for a custom id used for this term, if any.
+ attribute ACString customId;
+
+ attribute boolean beginsGrouping;
+ attribute boolean endsGrouping;
+
+ /**
+ * Match the value against one of the emails found in the incoming
+ * 2047-encoded string.
+ */
+ boolean matchRfc822String(in ACString aString, in string charset);
+ /**
+ * Match the current header value against the incoming 2047-encoded string.
+ *
+ * This method will first apply the nsIMimeConverter decoding to the string
+ * (using the supplied parameters) and will then match the value against the
+ * decoded result.
+ */
+ boolean matchRfc2047String(in ACString aString, in string charset, in boolean charsetOverride);
+ boolean matchDate(in PRTime aTime);
+ boolean matchStatus(in unsigned long aStatus);
+ boolean matchPriority(in nsMsgPriorityValue priority);
+ boolean matchAge(in PRTime days);
+ boolean matchSize(in unsigned long size);
+ boolean matchLabel(in nsMsgLabelValue aLabelValue);
+ boolean matchJunkStatus(in string aJunkScore);
+ /*
+ * Test search term match for junkpercent
+ *
+ * @param aJunkPercent junkpercent for message (0-100, 100 is junk)
+ * @return true if matches
+ */
+ boolean matchJunkPercent(in unsigned long aJunkPercent);
+ /*
+ * Test search term match for junkscoreorigin
+ * @param aJunkScoreOrigin Who set junk score? Possible values:
+ * plugin filter imapflag user whitelist
+ * @return true if matches
+ */
+ boolean matchJunkScoreOrigin(in string aJunkScoreOrigin);
+
+ /**
+ * Test if the body of the passed in message matches "this" search term.
+ * @param aScopeTerm scope of search
+ * @param aOffset offset of message in message store.
+ * @param aLength length of message.
+ * @param aCharset folder charset.
+ * @param aMsg db msg hdr of message to match.
+ * @param aDB db containing msg header.
+ */
+ boolean matchBody(in nsIMsgSearchScopeTerm aScopeTerm,
+ in unsigned long long aOffset,
+ in unsigned long aLength,
+ in string aCharset,
+ in nsIMsgDBHdr aMsg,
+ in nsIMsgDatabase aDb);
+
+ /**
+ * Test if the arbitrary header specified by this search term
+ * matches the corresponding header in the passed in message.
+ *
+ * @param aScopeTerm scope of search
+ * @param aLength length of message
+ * @param aCharset The charset to apply to un-labeled non-UTF-8 data.
+ * @param aCharsetOverride If true, aCharset is used instead of any
+ * charset labeling other than UTF-8.
+ *
+ * N.B. This is noscript because headers is a null-separated list of
+ * strings, which is not scriptable.
+ */
+ [noscript]
+ boolean matchArbitraryHeader(in nsIMsgSearchScopeTerm aScopeTerm,
+ in unsigned long aLength,
+ in string aCharset,
+ in boolean aCharsetOverride,
+ in nsIMsgDBHdr aMsg,
+ in nsIMsgDatabase aDb,
+ //[array, size_is(headerLength)] in string headers,
+ in string aHeaders,
+ in unsigned long aHeaderLength,
+ in boolean aForFilters);
+
+ /**
+ * Compares value.str with nsIMsgHdr::GetProperty(hdrProperty).
+ * @param msg msg to match db hdr property of.
+ *
+ * @returns true if msg matches property, false otherwise.
+ */
+ boolean matchHdrProperty(in nsIMsgDBHdr msg);
+
+ /**
+ * Compares value.status with nsIMsgHdr::GetUint32Property(hdrProperty).
+ * @param msg msg to match db hdr property of.
+ *
+ * @returns true if msg matches property, false otherwise.
+ */
+ boolean matchUint32HdrProperty(in nsIMsgDBHdr msg);
+
+ /**
+ * Compares value.status with the folder flags of the msg's folder.
+ * @param msg msgHdr whose folder's flag we want to compare.
+ *
+ * @returns true if folder's flags match value.status, false otherwise.
+ */
+ boolean matchFolderFlag(in nsIMsgDBHdr msg);
+
+ readonly attribute boolean matchAllBeforeDeciding;
+
+ readonly attribute ACString termAsString;
+ boolean matchKeyword(in ACString keyword); // used for tag searches
+ attribute boolean matchAll;
+ /**
+ * Does the message match the custom search term?
+ *
+ * @param msg message database object representing the message
+ *
+ * @return true if message matches
+ */
+ boolean matchCustom(in nsIMsgDBHdr msg);
+
+ /**
+ * Returns a nsMsgSearchAttribValue value corresponding to a field string from
+ * the nsMsgSearchTerm.cpp::SearchAttribEntryTable table.
+ * Does not handle custom attributes yet.
+ */
+ nsMsgSearchAttribValue getAttributeFromString(in string aAttribName);
+};
diff --git a/mailnews/base/search/public/nsIMsgSearchValidityManager.idl b/mailnews/base/search/public/nsIMsgSearchValidityManager.idl
new file mode 100644
index 000000000..dbc7958bd
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchValidityManager.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgSearchValidityTable.idl"
+
+typedef long nsMsgSearchValidityScope;
+
+[scriptable, uuid(6A352055-DE6E-49d2-A256-89E0B9EC405E)]
+interface nsIMsgSearchValidityManager : nsISupports {
+ nsIMsgSearchValidityTable getTable(in nsMsgSearchValidityScope scope);
+
+ /**
+ * Given a search attribute (which is an internal numerical id), return
+ * the string name that you can use as a key to look up the localized
+ * string in the search-attributes.properties file.
+ *
+ * @param aSearchAttribute attribute type from interface nsMsgSearchAttrib
+ *
+ * @return localization-friendly string representation
+ * of the attribute
+ */
+ AString getAttributeProperty(in nsMsgSearchAttribValue aSearchAttribute);
+};
diff --git a/mailnews/base/search/public/nsIMsgSearchValidityTable.idl b/mailnews/base/search/public/nsIMsgSearchValidityTable.idl
new file mode 100644
index 000000000..43856c88b
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchValidityTable.idl
@@ -0,0 +1,50 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsMsgSearchCore.idl"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+%{C++
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+%}
+interface nsISupportsArray;
+
+[scriptable, uuid(b07f1cb6-fae9-4d92-9edb-03f9ad249c66)]
+interface nsIMsgSearchValidityTable : nsISupports {
+
+ void setAvailable(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op, in boolean active);
+ void setEnabled(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op, in boolean enabled);
+ void setValidButNotShown(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op, in boolean valid);
+
+ boolean getAvailable(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op);
+ boolean getEnabled(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op);
+ boolean getValidButNotShown(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op);
+
+ [noscript] void validateTerms(in nsISupportsArray terms);
+
+ readonly attribute long numAvailAttribs;
+
+ void getAvailableAttributes(out unsigned long length,
+ [retval, array, size_is(length)]
+ out nsMsgSearchAttribValue attrs);
+
+ void getAvailableOperators(in nsMsgSearchAttribValue attrib,
+ out unsigned long length,
+ [retval, array, size_is(length)]
+ out nsMsgSearchOpValue operators);
+
+ void setDefaultAttrib(in nsMsgSearchAttribValue defaultAttrib);
+};
diff --git a/mailnews/base/search/public/nsIMsgSearchValue.idl b/mailnews/base/search/public/nsIMsgSearchValue.idl
new file mode 100644
index 000000000..f6d4845bd
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchValue.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgSearchCore.idl"
+
+interface nsIMsgFolder;
+
+[scriptable, uuid(783758a0-cdb5-11dc-95ff-0800200c9a66)]
+interface nsIMsgSearchValue : nsISupports {
+ // type of object
+ attribute nsMsgSearchAttribValue attrib;
+
+ // accessing these will throw an exception if the above
+ // attribute does not match the type!
+ attribute AString str;
+ attribute nsMsgPriorityValue priority;
+ attribute PRTime date;
+ // see nsMsgMessageFlags.idl and nsMsgFolderFlags.idl
+ attribute unsigned long status;
+ attribute unsigned long size;
+ attribute nsMsgKey msgKey;
+ attribute long age; // in days
+ attribute nsIMsgFolder folder;
+ attribute nsMsgLabelValue label;
+ attribute nsMsgJunkStatus junkStatus;
+ /*
+ * junkPercent is set by the message filter plugin, and is approximately
+ * proportional to the probability that a message is junk.
+ * (range 0-100, 100 is junk)
+ */
+ attribute unsigned long junkPercent;
+
+ AString toString();
+};
diff --git a/mailnews/base/search/public/nsIMsgTraitService.idl b/mailnews/base/search/public/nsIMsgTraitService.idl
new file mode 100644
index 000000000..78d9704e3
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgTraitService.idl
@@ -0,0 +1,174 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ /**
+ * This interface provides management of traits that are used to categorize
+ * messages. A trait is some characteristic of a message, such as being "junk"
+ * or "personal", that may be discoverable by analysis of the message.
+ *
+ * Traits are described by a universal identifier "id" as a string, as well
+ * as a local integer identifer "index". One purpose of this service is to
+ * provide the mapping between those forms.
+ *
+ * Recommended (but not required) format for id:
+ * "extensionName@example.org#traitName"
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(2CB15FB0-A912-40d3-8882-F2765C75655F)]
+interface nsIMsgTraitService : nsISupports
+{
+ /**
+ * the highest ever index for a registered trait. The first trait is 1,
+ * == 0 means no traits are defined
+ */
+ readonly attribute long lastIndex;
+
+ /**
+ * Register a trait. May be called multiple times, but subsequent
+ * calls do not register the trait
+ *
+ * @param id the trait universal identifier
+ *
+ * @return the internal index for the registered trait if newly
+ * registered, else 0
+ */
+ unsigned long registerTrait(in ACString id);
+
+ /**
+ * Unregister a trait.
+ *
+ * @param id the trait universal identifier
+ */
+ void unRegisterTrait(in ACString id);
+
+ /**
+ * is a trait registered?
+ *
+ * @param id the trait universal identifier
+ *
+ * @return true if registered
+ */
+ boolean isRegistered(in ACString id);
+
+ /**
+ * set the trait name, which is an optional short description of the trait
+ *
+ * @param id the trait universal identifier
+ * @param name description of the trait.
+ */
+ void setName(in ACString id, in ACString name);
+
+ /**
+ * get the trait name, which is an optional short description of the trait
+ *
+ * @param id the trait universal identifier
+ *
+ * @return description of the trait
+ */
+ ACString getName(in ACString id);
+
+ /**
+ * get the internal index number for the trait.
+ *
+ * @param id the trait universal identifier
+ *
+ * @return internal index number for the trait
+ */
+ unsigned long getIndex(in ACString id);
+
+ /**
+ * get the trait universal identifier for an internal trait index
+ *
+ * @param index the internal identifier for the trait
+ *
+ * @return trait universal identifier
+ */
+ ACString getId(in unsigned long index);
+
+ /**
+ * enable the trait for analysis. Each enabled trait will be analyzed by
+ * the bayesian code. The enabled trait is the "pro" trait that represents
+ * messages matching the trait. Each enabled trait also needs a corresponding
+ * anti trait defined, which represents messages that do not match the trait.
+ * The anti trait does not need to be enabled
+ *
+ * @param id the trait universal identifier
+ * @param enabled should this trait be processed by the bayesian analyzer?
+ */
+ void setEnabled(in ACString id, in boolean enabled);
+
+ /**
+ * Should this trait be processed by the bayes analyzer?
+ *
+ * @param id the trait universal identifier
+ *
+ * @return true if this is a "pro" trait to process
+ */
+ boolean getEnabled(in ACString id);
+
+ /**
+ * set the anti trait, which indicates messages that have been marked as
+ * NOT matching a particular trait.
+ *
+ * @param id the trait universal identifier
+ * @param antiId trait id for messages marked as not matching the trait
+ */
+ void setAntiId(in ACString id, in ACString antiId);
+
+ /**
+ * get the id of traits that do not match a particular trait
+ *
+ * @param id the trait universal identifier for a "pro" trait
+ *
+ * @return universal trait identifier for an "anti" trait that does not
+ * match the "pro" trait messages
+ */
+ ACString getAntiId(in ACString id);
+
+ /**
+ * get an array of traits to be analyzed by the bayesian code. This is
+ * a pair of traits: a "pro" trait of messages that match the trait (and is
+ * set enabled) and an "anti" trait of messages that do not match the trait.
+ *
+ * @param count length of proIndices and antiIndices arrays
+ * @param proIndices trait internal index for "pro" trait to analyze
+ * @param antiIndices trait internal index for corresponding "anti" traits
+ */
+ void getEnabledIndices(out unsigned long count,
+ [array, size_is(count)] out unsigned long proIndices,
+ [array, size_is(count)] out unsigned long antiIndices);
+
+ /**
+ * Add a trait as an alias of another trait. An alias is a trait whose
+ * counts will be combined with the aliased trait. This allows multiple sets
+ * of corpus data to be used to provide information on a single message
+ * characteristic, while allowing each individual set of corpus data to
+ * retain its own identity.
+ *
+ * @param aTraitIndex the internal identifier for the aliased trait
+ * @param aTraitAlias the internal identifier for the alias to add
+ */
+ void addAlias(in unsigned long aTraitIndex, in unsigned long aTraitAlias);
+
+ /**
+ * Removes a trait as an alias of another trait.
+ *
+ * @param aTraitIndex the internal identifier for the aliased trait
+ * @param aTraitAlias the internal identifier for the alias to remove
+ */
+ void removeAlias(in unsigned long aTraitIndex, in unsigned long aTraitAlias);
+
+ /**
+ * Get an array of trait aliases for a trait index, if any
+ *
+ * @param aTraitIndex the internal identifier for the aliased trait
+ * @param aLength length of array of aliases
+ * @param aAliases array of internal identifiers for aliases
+ */
+ void getAliases(in unsigned long aTraitIndex, out unsigned long aLength,
+ [retval, array, size_is(aLength)] out unsigned long aAliases);
+
+};
diff --git a/mailnews/base/search/public/nsMsgBodyHandler.h b/mailnews/base/search/public/nsMsgBodyHandler.h
new file mode 100644
index 000000000..417f166d6
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgBodyHandler.h
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef __nsMsgBodyHandler_h
+#define __nsMsgBodyHandler_h
+
+#include "nsIMsgSearchScopeTerm.h"
+#include "nsILineInputStream.h"
+#include "nsIMsgDatabase.h"
+
+//---------------------------------------------------------------------------
+// nsMsgBodyHandler: used to retrieve lines from POP and IMAP offline messages.
+// This is a helper class used by nsMsgSearchTerm::MatchBody
+//---------------------------------------------------------------------------
+class nsMsgBodyHandler
+{
+public:
+ nsMsgBodyHandler (nsIMsgSearchScopeTerm *,
+ uint32_t length,
+ nsIMsgDBHdr * msg,
+ nsIMsgDatabase * db);
+
+ // we can also create a body handler when doing arbitrary header
+ // filtering...we need the list of headers and the header size as well
+ // if we are doing filtering...if ForFilters is false, headers and
+ // headersSize is ignored!!!
+ nsMsgBodyHandler (nsIMsgSearchScopeTerm *,
+ uint32_t length, nsIMsgDBHdr * msg, nsIMsgDatabase * db,
+ const char * headers /* NULL terminated list of headers */,
+ uint32_t headersSize, bool ForFilters);
+
+ virtual ~nsMsgBodyHandler();
+
+ // Returns next message line in buf and the applicable charset, if found.
+ // The return value is the length of 'buf' or -1 for EOF.
+ int32_t GetNextLine(nsCString &buf, nsCString &charset);
+
+ // Transformations
+ void SetStripHtml (bool strip) { m_stripHtml = strip; }
+ void SetStripHeaders (bool strip) { m_stripHeaders = strip; }
+
+protected:
+ void Initialize(); // common initialization code
+
+ // filter related methods. For filtering we always use the headers
+ // list instead of the database...
+ bool m_Filtering;
+ int32_t GetNextFilterLine(nsCString &buf);
+ // pointer into the headers list in the original message hdr db...
+ const char * m_headers;
+ uint32_t m_headersSize;
+ uint32_t m_headerBytesRead;
+
+ // local / POP related methods
+ void OpenLocalFolder();
+
+ // goes through the mail folder
+ int32_t GetNextLocalLine(nsCString &buf);
+
+ nsIMsgSearchScopeTerm *m_scope;
+ nsCOMPtr <nsILineInputStream> m_fileLineStream;
+ nsCOMPtr <nsIFile> m_localFile;
+
+ /**
+ * The number of lines in the message. If |m_lineCountInBodyLines| then this
+ * is the number of body lines, otherwise this is the entire number of lines
+ * in the message. This is important so we know when to stop reading the file
+ * without accidentally reading part of the next message.
+ */
+ uint32_t m_numLocalLines;
+ /**
+ * When true, |m_numLocalLines| is the number of body lines in the message,
+ * when false it is the entire number of lines in the message.
+ *
+ * When a message is an offline IMAP or news message, then the number of lines
+ * will be the entire number of lines, so this should be false. When the
+ * message is a local message, the number of lines will be the number of body
+ * lines.
+ */
+ bool m_lineCountInBodyLines;
+
+ // Offline IMAP related methods & state
+
+
+ nsCOMPtr<nsIMsgDBHdr> m_msgHdr;
+ nsCOMPtr<nsIMsgDatabase> m_db;
+
+ // Transformations
+ // With the exception of m_isMultipart, these all apply to the various parts
+ bool m_stripHeaders; // true if we're supposed to strip of message headers
+ bool m_stripHtml; // true if we're supposed to strip off HTML tags
+ bool m_pastMsgHeaders; // true if we've already skipped over the message headers
+ bool m_pastPartHeaders; // true if we've already skipped over the part headers
+ bool m_partIsHtml; // true if the Content-type header claims text/html
+ bool m_base64part; // true if the current part is in base64
+ bool m_isMultipart; // true if the message is a multipart/* message
+ bool m_partIsText; // true if the current part is text/*
+ bool m_inMessageAttachment; // true if current part is message/*
+
+ nsTArray<nsCString> m_boundaries; // The boundary strings to look for
+ nsCString m_partCharset; // The charset found in the part
+
+ // See implementation for comments
+ int32_t ApplyTransformations (const nsCString &line, int32_t length,
+ bool &returnThisLine, nsCString &buf);
+ void SniffPossibleMIMEHeader (const nsCString &line);
+ static void StripHtml (nsCString &buf);
+ static void Base64Decode (nsCString &buf);
+};
+#endif
diff --git a/mailnews/base/search/public/nsMsgFilterCore.idl b/mailnews/base/search/public/nsMsgFilterCore.idl
new file mode 100644
index 000000000..66ecb076a
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgFilterCore.idl
@@ -0,0 +1,63 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgSearchCore.idl"
+
+typedef long nsMsgFilterTypeType;
+
+[scriptable,uuid(b963a9c6-3a75-4d91-9f79-7186418d4d2d)]
+interface nsMsgFilterType {
+ /* these longs are all actually of type nsMsgFilterTypeType */
+ const long None = 0x00;
+ const long InboxRule = 0x01;
+ const long InboxJavaScript = 0x02;
+ const long Inbox = InboxRule | InboxJavaScript;
+ const long NewsRule = 0x04;
+ const long NewsJavaScript = 0x08;
+ const long News = NewsRule | NewsJavaScript;
+ const long Incoming = Inbox | News;
+ const long Manual = 0x10;
+ const long PostPlugin = 0x20; // After bayes filtering
+ const long PostOutgoing = 0x40; // After sending
+ const long Archive = 0x80; // Before archiving
+ const long All = Incoming | Manual;
+};
+
+typedef long nsMsgFilterMotionValue;
+
+typedef long nsMsgFilterIndex;
+
+typedef long nsMsgRuleActionType;
+
+[scriptable, uuid(7726FE79-AFA3-4a39-8292-733AEE288737)]
+interface nsMsgFilterAction {
+
+ // Custom Action.
+ const long Custom=-1;
+ /* if you change these, you need to update filter.properties,
+ look for filterActionX */
+ /* these longs are all actually of type nsMsgFilterActionType */
+ const long None=0; /* uninitialized state */
+ const long MoveToFolder=1;
+ const long ChangePriority=2;
+ const long Delete=3;
+ const long MarkRead=4;
+ const long KillThread=5;
+ const long WatchThread=6;
+ const long MarkFlagged=7;
+ const long Label=8;
+ const long Reply=9;
+ const long Forward=10;
+ const long StopExecution=11;
+ const long DeleteFromPop3Server=12;
+ const long LeaveOnPop3Server=13;
+ const long JunkScore=14;
+ const long FetchBodyFromPop3Server=15;
+ const long CopyToFolder=16;
+ const long AddTag=17;
+ const long KillSubthread=18;
+ const long MarkUnread=19;
+};
+
diff --git a/mailnews/base/search/public/nsMsgResultElement.h b/mailnews/base/search/public/nsMsgResultElement.h
new file mode 100644
index 000000000..f8cdf4404
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgResultElement.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMsgResultElement_h
+#define __nsMsgResultElement_h
+
+#include "nsMsgSearchCore.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsTArray.h"
+
+// nsMsgResultElement specifies a single search hit.
+
+//---------------------------------------------------------------------------
+// nsMsgResultElement is a list of attribute/value pairs which are used to
+// represent a search hit without requiring a message header or server connection
+//---------------------------------------------------------------------------
+
+class nsMsgResultElement
+{
+public:
+ explicit nsMsgResultElement (nsIMsgSearchAdapter *);
+ virtual ~nsMsgResultElement ();
+
+ static nsresult AssignValues (nsIMsgSearchValue *src, nsMsgSearchValue *dst);
+ nsresult GetValue (nsMsgSearchAttribValue, nsMsgSearchValue **) const;
+ nsresult AddValue (nsIMsgSearchValue*);
+ nsresult AddValue (nsMsgSearchValue*);
+
+ nsresult GetPrettyName (nsMsgSearchValue**);
+ nsresult Open (void *window);
+
+ nsTArray<nsCOMPtr<nsIMsgSearchValue> > m_valueList;
+ nsIMsgSearchAdapter *m_adapter;
+
+protected:
+};
+
+#endif
diff --git a/mailnews/base/search/public/nsMsgSearchAdapter.h b/mailnews/base/search/public/nsMsgSearchAdapter.h
new file mode 100644
index 000000000..0b1cd71da
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgSearchAdapter.h
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgSearchAdapter_H_
+#define _nsMsgSearchAdapter_H_
+
+#include "nsMsgSearchCore.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIMsgSearchValidityTable.h"
+#include "nsIMsgSearchValidityManager.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsINntpIncomingServer.h"
+
+class nsIMsgSearchScopeTerm;
+
+//-----------------------------------------------------------------------------
+// These Adapter classes contain the smarts to convert search criteria from
+// the canonical structures in msg_srch.h into whatever format is required
+// by their protocol.
+//
+// There is a separate Adapter class for area (pop, imap, nntp, ldap) to contain
+// the special smarts for that protocol.
+//-----------------------------------------------------------------------------
+
+class nsMsgSearchAdapter : public nsIMsgSearchAdapter
+{
+public:
+ nsMsgSearchAdapter (nsIMsgSearchScopeTerm*, nsISupportsArray *);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSEARCHADAPTER
+
+ nsIMsgSearchScopeTerm *m_scope;
+ nsCOMPtr<nsISupportsArray> m_searchTerms; /* linked list of criteria terms */
+
+ bool m_abortCalled;
+ nsString m_defaultCharset;
+ bool m_forceAsciiSearch;
+
+ static nsresult EncodeImap (char **ppEncoding,
+ nsISupportsArray *searchTerms,
+ const char16_t *srcCharset,
+ const char16_t *destCharset,
+ bool reallyDredd = false);
+
+ static nsresult EncodeImapValue(char *encoding, const char *value, bool useQuotes, bool reallyDredd);
+
+ static char *GetImapCharsetParam(const char16_t *destCharset);
+ static char16_t *EscapeSearchUrl (const char16_t *nntpCommand);
+ static char16_t *EscapeImapSearchProtocol(const char16_t *imapCommand);
+ static char16_t *EscapeQuoteImapSearchProtocol(const char16_t *imapCommand);
+ static char *UnEscapeSearchUrl (const char *commandSpecificData);
+ // This stuff lives in the base class because the IMAP search syntax
+ // is used by the Dredd SEARCH command as well as IMAP itself
+ static const char *m_kImapBefore;
+ static const char *m_kImapBody;
+ static const char *m_kImapCC;
+ static const char *m_kImapFrom;
+ static const char *m_kImapNot;
+ static const char *m_kImapOr;
+ static const char *m_kImapSince;
+ static const char *m_kImapSubject;
+ static const char *m_kImapTo;
+ static const char *m_kImapHeader;
+ static const char *m_kImapAnyText;
+ static const char *m_kImapKeyword;
+ static const char *m_kNntpKeywords;
+ static const char *m_kImapSentOn;
+ static const char *m_kImapSeen;
+ static const char *m_kImapAnswered;
+ static const char *m_kImapNotSeen;
+ static const char *m_kImapNotAnswered;
+ static const char *m_kImapCharset;
+ static const char *m_kImapUnDeleted;
+ static const char *m_kImapSizeSmaller;
+ static const char *m_kImapSizeLarger;
+ static const char *m_kImapNew;
+ static const char *m_kImapNotNew;
+ static const char *m_kImapFlagged;
+ static const char *m_kImapNotFlagged;
+protected:
+ virtual ~nsMsgSearchAdapter();
+ typedef enum _msg_TransformType
+ {
+ kOverwrite, /* "John Doe" -> "John*Doe", simple contains */
+ kInsert, /* "John Doe" -> "John* Doe", name completion */
+ kSurround /* "John Doe" -> "John* *Doe", advanced contains */
+ } msg_TransformType;
+
+ char *TransformSpacesToStars (const char *, msg_TransformType transformType);
+ nsresult OpenNewsResultInUnknownGroup (nsMsgResultElement*);
+
+ static nsresult EncodeImapTerm (nsIMsgSearchTerm *, bool reallyDredd, const char16_t *srcCharset, const char16_t *destCharset, char **ppOutTerm);
+};
+
+//-----------------------------------------------------------------------------
+// Validity checking for attrib/op pairs. We need to know what operations are
+// legal in three places:
+// 1. when the FE brings up the dialog box and needs to know how to build
+// the menus and enable their items
+// 2. when the FE fires off a search, we need to check their lists for
+// correctness
+// 3. for on-the-fly capability negotion e.g. with XSEARCH-capable news
+// servers
+//-----------------------------------------------------------------------------
+
+class nsMsgSearchValidityTable final : public nsIMsgSearchValidityTable
+{
+public:
+ nsMsgSearchValidityTable ();
+ NS_DECL_NSIMSGSEARCHVALIDITYTABLE
+ NS_DECL_ISUPPORTS
+
+protected:
+ int m_numAvailAttribs; // number of rows with at least one available operator
+ typedef struct vtBits
+ {
+ uint16_t bitEnabled : 1;
+ uint16_t bitAvailable : 1;
+ uint16_t bitValidButNotShown : 1;
+ } vtBits;
+ vtBits m_table [nsMsgSearchAttrib::kNumMsgSearchAttributes][nsMsgSearchOp::kNumMsgSearchOperators];
+private:
+ ~nsMsgSearchValidityTable() {}
+ nsMsgSearchAttribValue m_defaultAttrib;
+};
+
+// Using getters and setters seems a little nicer then dumping the 2-D array
+// syntax all over the code
+#define CHECK_AO if (a < 0 || \
+ a >= nsMsgSearchAttrib::kNumMsgSearchAttributes || \
+ o < 0 || \
+ o >= nsMsgSearchOp::kNumMsgSearchOperators) \
+ return NS_ERROR_ILLEGAL_VALUE;
+inline nsresult nsMsgSearchValidityTable::SetAvailable (int a, int o, bool b)
+{ CHECK_AO; m_table [a][o].bitAvailable = b; return NS_OK;}
+inline nsresult nsMsgSearchValidityTable::SetEnabled (int a, int o, bool b)
+{ CHECK_AO; m_table [a][o].bitEnabled = b; return NS_OK; }
+inline nsresult nsMsgSearchValidityTable::SetValidButNotShown (int a, int o, bool b)
+{ CHECK_AO; m_table [a][o].bitValidButNotShown = b; return NS_OK;}
+
+inline nsresult nsMsgSearchValidityTable::GetAvailable (int a, int o, bool *aResult)
+{ CHECK_AO; *aResult = m_table [a][o].bitAvailable; return NS_OK;}
+inline nsresult nsMsgSearchValidityTable::GetEnabled (int a, int o, bool *aResult)
+{ CHECK_AO; *aResult = m_table [a][o].bitEnabled; return NS_OK;}
+inline nsresult nsMsgSearchValidityTable::GetValidButNotShown (int a, int o, bool *aResult)
+{ CHECK_AO; *aResult = m_table [a][o].bitValidButNotShown; return NS_OK;}
+#undef CHECK_AO
+
+class nsMsgSearchValidityManager : public nsIMsgSearchValidityManager
+{
+public:
+ nsMsgSearchValidityManager ();
+
+protected:
+ virtual ~nsMsgSearchValidityManager ();
+
+public:
+ NS_DECL_NSIMSGSEARCHVALIDITYMANAGER
+ NS_DECL_ISUPPORTS
+
+ nsresult GetTable (int, nsMsgSearchValidityTable**);
+
+protected:
+
+ // There's one global validity manager that everyone uses. You *could* do
+ // this with static members of the adapter classes, but having a dedicated
+ // object makes cleanup of these tables (at shutdown-time) automagic.
+
+ nsCOMPtr<nsIMsgSearchValidityTable> m_offlineMailTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_offlineMailFilterTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_onlineMailTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_onlineMailFilterTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_onlineManualFilterTable;
+
+ nsCOMPtr<nsIMsgSearchValidityTable> m_newsTable; // online news
+
+ // Local news tables, used for local news searching or offline.
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localNewsTable; // base table
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localNewsJunkTable; // base + junk
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localNewsBodyTable; // base + body
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localNewsJunkBodyTable; // base + junk + body
+ nsCOMPtr<nsIMsgSearchValidityTable> m_ldapTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_ldapAndTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localABTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localABAndTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_newsFilterTable;
+
+ nsresult NewTable (nsIMsgSearchValidityTable **);
+
+ nsresult InitOfflineMailTable();
+ nsresult InitOfflineMailFilterTable();
+ nsresult InitOnlineMailTable();
+ nsresult InitOnlineMailFilterTable();
+ nsresult InitOnlineManualFilterTable();
+ nsresult InitNewsTable();
+ nsresult InitLocalNewsTable();
+ nsresult InitLocalNewsJunkTable();
+ nsresult InitLocalNewsBodyTable();
+ nsresult InitLocalNewsJunkBodyTable();
+ nsresult InitNewsFilterTable();
+
+ //set the custom headers in the table, changes whenever "mailnews.customHeaders" pref changes.
+ nsresult SetOtherHeadersInTable(nsIMsgSearchValidityTable *table, const char *customHeaders);
+
+ nsresult InitLdapTable();
+ nsresult InitLdapAndTable();
+ nsresult InitLocalABTable();
+ nsresult InitLocalABAndTable();
+ nsresult SetUpABTable(nsIMsgSearchValidityTable *aTable, bool isOrTable);
+ nsresult EnableDirectoryAttribute(nsIMsgSearchValidityTable *table, nsMsgSearchAttribValue aSearchAttrib);
+};
+
+#endif
diff --git a/mailnews/base/search/public/nsMsgSearchBoolExpression.h b/mailnews/base/search/public/nsMsgSearchBoolExpression.h
new file mode 100644
index 000000000..9c6a9c8d8
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgSearchBoolExpression.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgSearchCore.h"
+
+#ifndef __nsMsgSearchBoolExpression_h
+#define __nsMsgSearchBoolExpression_h
+
+//-----------------------------------------------------------------------------
+// nsMsgSearchBoolExpression is a class added to provide AND/OR terms in search queries.
+// A nsMsgSearchBoolExpression contains either a search term or two nsMsgSearchBoolExpressions and
+// a boolean operator.
+// I (mscott) am placing it here for now....
+//-----------------------------------------------------------------------------
+
+/* CBoolExpression --> encapsulates one or more search terms by internally
+ representing the search terms and their boolean operators as a binary
+ expression tree. Each node in the tree consists of either
+ (1) a boolean operator and two nsMsgSearchBoolExpressions or
+ (2) if the node is a leaf node then it contains a search term.
+ With each search term that is part of the expression we may also keep
+ a character string. The character
+ string is used to store the IMAP/NNTP encoding of the search term. This
+ makes generating a search encoding (for online) easier.
+
+ For IMAP/NNTP: nsMsgSearchBoolExpression has/assumes knowledge about how
+ AND and OR search terms are combined according to IMAP4 and NNTP protocol.
+ That is the only piece of IMAP/NNTP knowledge it is aware of.
+
+ Order of Evaluation: Okay, the way in which the boolean expression tree
+ is put together directly effects the order of evaluation. We currently
+ support left to right evaluation.
+ Supporting other order of evaluations involves adding new internal add
+ term methods.
+ */
+
+class nsMsgSearchBoolExpression
+{
+public:
+
+ // create a leaf node expression
+ explicit nsMsgSearchBoolExpression(nsIMsgSearchTerm * aNewTerm,
+ char * aEncodingString = NULL);
+
+ // create a non-leaf node expression containing 2 expressions
+ // and a boolean operator
+ nsMsgSearchBoolExpression(nsMsgSearchBoolExpression *,
+ nsMsgSearchBoolExpression *,
+ nsMsgSearchBooleanOperator boolOp);
+
+ nsMsgSearchBoolExpression();
+ ~nsMsgSearchBoolExpression(); // recursively destroys all sub
+ // expressions as well
+
+ // accessors
+
+ // Offline
+ static nsMsgSearchBoolExpression * AddSearchTerm (nsMsgSearchBoolExpression * aOrigExpr, nsIMsgSearchTerm * aNewTerm, char * aEncodingStr); // IMAP/NNTP
+ static nsMsgSearchBoolExpression * AddExpressionTree(nsMsgSearchBoolExpression * aOrigExpr, nsMsgSearchBoolExpression * aExpression, bool aBoolOp);
+
+ // parses the expression tree and all
+ // expressions underneath this node to
+ // determine if the end result is true or false.
+ bool OfflineEvaluate(nsIMsgDBHdr *msgToMatch,
+ const char *defaultCharset, nsIMsgSearchScopeTerm *scope,
+ nsIMsgDatabase *db, const char *headers, uint32_t headerSize,
+ bool Filtering);
+
+ // assuming the expression is for online
+ // searches, determine the length of the
+ // resulting IMAP/NNTP encoding string
+ int32_t CalcEncodeStrSize();
+
+ // fills pre-allocated
+ // memory in buffer with
+ // the IMAP/NNTP encoding for the expression
+ void GenerateEncodeStr(nsCString * buffer);
+
+ // if we are not a leaf node, then we have two other expressions
+ // and a boolean operator
+ nsMsgSearchBoolExpression * m_leftChild;
+ nsMsgSearchBoolExpression * m_rightChild;
+ nsMsgSearchBooleanOperator m_boolOp;
+
+protected:
+ // if we are a leaf node, all we have is a search term
+
+ nsIMsgSearchTerm * m_term;
+
+ // store IMAP/NNTP encoding for the search term if applicable
+ nsCString m_encodingStr;
+
+ // internal methods
+
+ // the idea is to separate the public interface for adding terms to
+ // the expression tree from the order of evaluation which influences
+ // how we internally construct the tree. Right now, we are supporting
+ // left to right evaluation so the tree is constructed to represent
+ // that by calling leftToRightAddTerm. If future forms of evaluation
+ // need to be supported, add new methods here for proper tree construction.
+ nsMsgSearchBoolExpression * leftToRightAddTerm(nsIMsgSearchTerm * newTerm,
+ char * encodingStr);
+};
+
+#endif
diff --git a/mailnews/base/search/public/nsMsgSearchCore.idl b/mailnews/base/search/public/nsMsgSearchCore.idl
new file mode 100644
index 000000000..6431aa8c3
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgSearchCore.idl
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgFolder;
+
+interface nsIMsgDatabase;
+interface nsIMsgDBHdr;
+
+[scriptable, uuid(6e893e59-af98-4f62-a326-0f00f32147cd)]
+
+interface nsMsgSearchScope {
+ const nsMsgSearchScopeValue offlineMail = 0;
+ const nsMsgSearchScopeValue offlineMailFilter = 1;
+ const nsMsgSearchScopeValue onlineMail = 2;
+ const nsMsgSearchScopeValue onlineMailFilter = 3;
+ /// offline news, base table, no body or junk
+ const nsMsgSearchScopeValue localNews = 4;
+ const nsMsgSearchScopeValue news = 5;
+ const nsMsgSearchScopeValue newsEx = 6;
+ const nsMsgSearchScopeValue LDAP = 7;
+ const nsMsgSearchScopeValue LocalAB = 8;
+ const nsMsgSearchScopeValue allSearchableGroups = 9;
+ const nsMsgSearchScopeValue newsFilter = 10;
+ const nsMsgSearchScopeValue LocalABAnd = 11;
+ const nsMsgSearchScopeValue LDAPAnd = 12;
+ // IMAP and NEWS, searched using local headers
+ const nsMsgSearchScopeValue onlineManual = 13;
+ /// local news + junk
+ const nsMsgSearchScopeValue localNewsJunk = 14;
+ /// local news + body
+ const nsMsgSearchScopeValue localNewsBody = 15;
+ /// local news + junk + body
+ const nsMsgSearchScopeValue localNewsJunkBody = 16;
+};
+
+typedef long nsMsgSearchAttribValue;
+
+/**
+ * Definitions of search attribute types. The numerical order
+ * from here will also be used to determine the order that the
+ * attributes display in the filter editor.
+ */
+[scriptable, uuid(a83ca7e8-4591-4111-8fb8-fd76ac73c866)]
+interface nsMsgSearchAttrib {
+ const nsMsgSearchAttribValue Custom = -2; /* a custom term, see nsIMsgSearchCustomTerm */
+ const nsMsgSearchAttribValue Default = -1;
+ const nsMsgSearchAttribValue Subject = 0; /* mail and news */
+ const nsMsgSearchAttribValue Sender = 1;
+ const nsMsgSearchAttribValue Body = 2;
+ const nsMsgSearchAttribValue Date = 3;
+
+ const nsMsgSearchAttribValue Priority = 4; /* mail only */
+ const nsMsgSearchAttribValue MsgStatus = 5;
+ const nsMsgSearchAttribValue To = 6;
+ const nsMsgSearchAttribValue CC = 7;
+ const nsMsgSearchAttribValue ToOrCC = 8;
+ const nsMsgSearchAttribValue AllAddresses = 9;
+
+ const nsMsgSearchAttribValue Location = 10; /* result list only */
+ const nsMsgSearchAttribValue MessageKey = 11; /* message result elems */
+ const nsMsgSearchAttribValue AgeInDays = 12;
+ const nsMsgSearchAttribValue FolderInfo = 13; /* for "view thread context" from result */
+ const nsMsgSearchAttribValue Size = 14;
+ const nsMsgSearchAttribValue AnyText = 15;
+ const nsMsgSearchAttribValue Keywords = 16; // keywords are the internal representation of tags.
+
+ const nsMsgSearchAttribValue Name = 17;
+ const nsMsgSearchAttribValue DisplayName = 18;
+ const nsMsgSearchAttribValue Nickname = 19;
+ const nsMsgSearchAttribValue ScreenName = 20;
+ const nsMsgSearchAttribValue Email = 21;
+ const nsMsgSearchAttribValue AdditionalEmail = 22;
+ const nsMsgSearchAttribValue PhoneNumber = 23;
+ const nsMsgSearchAttribValue WorkPhone = 24;
+ const nsMsgSearchAttribValue HomePhone = 25;
+ const nsMsgSearchAttribValue Fax = 26;
+ const nsMsgSearchAttribValue Pager = 27;
+ const nsMsgSearchAttribValue Mobile = 28;
+ const nsMsgSearchAttribValue City = 29;
+ const nsMsgSearchAttribValue Street = 30;
+ const nsMsgSearchAttribValue Title = 31;
+ const nsMsgSearchAttribValue Organization = 32;
+ const nsMsgSearchAttribValue Department = 33;
+
+ // 34 - 43, reserved for ab / LDAP;
+ const nsMsgSearchAttribValue HasAttachmentStatus = 44;
+ const nsMsgSearchAttribValue JunkStatus = 45;
+ const nsMsgSearchAttribValue JunkPercent = 46;
+ const nsMsgSearchAttribValue JunkScoreOrigin = 47;
+ const nsMsgSearchAttribValue Label = 48; /* mail only...can search by label */
+ const nsMsgSearchAttribValue HdrProperty = 49; // uses nsIMsgSearchTerm::hdrProperty
+ const nsMsgSearchAttribValue FolderFlag = 50; // uses nsIMsgSearchTerm::status
+ const nsMsgSearchAttribValue Uint32HdrProperty = 51; // uses nsIMsgSearchTerm::hdrProperty
+
+ // 52 is for showing customize - in ui headers start from 53 onwards up until 99.
+
+ /** OtherHeader MUST ALWAYS BE LAST attribute since
+ * we can have an arbitrary # of these. The number can be changed,
+ * however, because we never persist AttribValues as integers.
+ */
+ const nsMsgSearchAttribValue OtherHeader = 52;
+ // must be last attribute
+ const nsMsgSearchAttribValue kNumMsgSearchAttributes = 100;
+};
+
+typedef long nsMsgSearchOpValue;
+
+[scriptable, uuid(9160b196-6fcb-4eba-aaaf-6c806c4ee420)]
+interface nsMsgSearchOp {
+ const nsMsgSearchOpValue Contains = 0; /* for text attributes */
+ const nsMsgSearchOpValue DoesntContain = 1;
+ const nsMsgSearchOpValue Is = 2; /* is and isn't also apply to some non-text attrs */
+ const nsMsgSearchOpValue Isnt = 3;
+ const nsMsgSearchOpValue IsEmpty = 4;
+
+ const nsMsgSearchOpValue IsBefore = 5; /* for date attributes */
+ const nsMsgSearchOpValue IsAfter = 6;
+
+ const nsMsgSearchOpValue IsHigherThan = 7; /* for priority. Is also applies */
+ const nsMsgSearchOpValue IsLowerThan = 8;
+
+ const nsMsgSearchOpValue BeginsWith = 9;
+ const nsMsgSearchOpValue EndsWith = 10;
+
+ const nsMsgSearchOpValue SoundsLike = 11; /* for LDAP phoenetic matching */
+ const nsMsgSearchOpValue LdapDwim = 12; /* Do What I Mean for simple search */
+
+ const nsMsgSearchOpValue IsGreaterThan = 13;
+ const nsMsgSearchOpValue IsLessThan = 14;
+
+ const nsMsgSearchOpValue NameCompletion = 15; /* Name Completion operator...as the name implies =) */
+ const nsMsgSearchOpValue IsInAB = 16;
+ const nsMsgSearchOpValue IsntInAB = 17;
+ const nsMsgSearchOpValue IsntEmpty = 18; /* primarily for tags */
+ const nsMsgSearchOpValue Matches = 19; /* generic term for use by custom terms */
+ const nsMsgSearchOpValue DoesntMatch = 20; /* generic term for use by custom terms */
+ const nsMsgSearchOpValue kNumMsgSearchOperators = 21; /* must be last operator */
+};
+
+typedef long nsMsgSearchWidgetValue;
+
+/* FEs use this to help build the search dialog box */
+[scriptable,uuid(903dd2e8-304e-11d3-92e6-00a0c900d445)]
+interface nsMsgSearchWidget {
+ const nsMsgSearchWidgetValue Text = 0;
+ const nsMsgSearchWidgetValue Date = 1;
+ const nsMsgSearchWidgetValue Menu = 2;
+ const nsMsgSearchWidgetValue Int = 3; /* added to account for age in days which requires an integer field */
+ const nsMsgSearchWidgetValue None = 4;
+};
+
+typedef long nsMsgSearchTypeValue;
+
+
+/* Used to specify type of search to be performed */
+[scriptable,uuid(964b7f32-304e-11d3-ae13-00a0c900d445)]
+interface nsMsgSearchType {
+ const nsMsgSearchTypeValue None = 0;
+ const nsMsgSearchTypeValue RootDSE = 1;
+ const nsMsgSearchTypeValue Normal = 2;
+ const nsMsgSearchTypeValue LdapVLV = 3;
+ const nsMsgSearchTypeValue NameCompletion = 4;
+};
+
+typedef long nsMsgSearchBooleanOperator;
+
+[scriptable, uuid(a37f3f4a-304e-11d3-8f94-00a0c900d445)]
+interface nsMsgSearchBooleanOp {
+ const nsMsgSearchBooleanOperator BooleanOR = 0;
+ const nsMsgSearchBooleanOperator BooleanAND = 1;
+};
+
+/* Use this to specify the value of a search term */
+
+[ptr] native nsMsgSearchValue(nsMsgSearchValue);
+
+%{C++
+#include "nsStringGlue.h"
+
+typedef struct nsMsgSearchValue
+{
+ nsMsgSearchAttribValue attribute;
+ union
+ {
+ nsMsgPriorityValue priority;
+ PRTime date;
+ uint32_t msgStatus; /* see MSG_FLAG in msgcom.h */
+ uint32_t size;
+ nsMsgKey key;
+ int32_t age; /* in days */
+ nsIMsgFolder *folder;
+ nsMsgLabelValue label;
+ uint32_t junkStatus;
+ uint32_t junkPercent;
+ } u;
+ char *string;
+ nsString utf16String;
+} nsMsgSearchValue;
+%}
+
+[ptr] native nsMsgSearchTerm(nsMsgSearchTerm);
+
+// Please note the ! at the start of this macro, which means the macro
+// needs to enumerate the non-string attributes.
+%{C++
+#define IS_STRING_ATTRIBUTE(_a) \
+(!(_a == nsMsgSearchAttrib::Priority || _a == nsMsgSearchAttrib::Date || \
+ _a == nsMsgSearchAttrib::MsgStatus || _a == nsMsgSearchAttrib::MessageKey || \
+ _a == nsMsgSearchAttrib::Size || _a == nsMsgSearchAttrib::AgeInDays || \
+ _a == nsMsgSearchAttrib::FolderInfo || _a == nsMsgSearchAttrib::Location || \
+ _a == nsMsgSearchAttrib::Label || _a == nsMsgSearchAttrib::JunkStatus || \
+ _a == nsMsgSearchAttrib::FolderFlag || _a == nsMsgSearchAttrib::Uint32HdrProperty || \
+ _a == nsMsgSearchAttrib::JunkPercent || _a == nsMsgSearchAttrib::HasAttachmentStatus))
+%}
+
+[ptr] native nsSearchMenuItem(nsSearchMenuItem);
+
diff --git a/mailnews/base/search/public/nsMsgSearchScopeTerm.h b/mailnews/base/search/public/nsMsgSearchScopeTerm.h
new file mode 100644
index 000000000..74afd8d19
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgSearchScopeTerm.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMsgSearchScopeTerm_h
+#define __nsMsgSearchScopeTerm_h
+
+#include "nsMsgSearchCore.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIMsgSearchSession.h"
+#include "nsCOMPtr.h"
+#include "nsIWeakReference.h"
+#include "nsIWeakReferenceUtils.h"
+
+class nsMsgSearchScopeTerm : public nsIMsgSearchScopeTerm
+{
+public:
+ nsMsgSearchScopeTerm (nsIMsgSearchSession *, nsMsgSearchScopeValue, nsIMsgFolder *);
+ nsMsgSearchScopeTerm ();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSEARCHSCOPETERM
+
+ nsresult TimeSlice (bool *aDone);
+ nsresult InitializeAdapter (nsISupportsArray *termList);
+
+ char *GetStatusBarName ();
+
+ nsMsgSearchScopeValue m_attribute;
+ char *m_name;
+ nsCOMPtr <nsIMsgFolder> m_folder;
+ nsCOMPtr <nsIMsgSearchAdapter> m_adapter;
+ nsCOMPtr <nsIInputStream> m_inputStream; // for message bodies
+ nsWeakPtr m_searchSession;
+ bool m_searchServer;
+
+private:
+ virtual ~nsMsgSearchScopeTerm();
+};
+
+#endif
diff --git a/mailnews/base/search/public/nsMsgSearchTerm.h b/mailnews/base/search/public/nsMsgSearchTerm.h
new file mode 100644
index 000000000..ffcdc9539
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgSearchTerm.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef __nsMsgSearchTerm_h
+#define __nsMsgSearchTerm_h
+//---------------------------------------------------------------------------
+// nsMsgSearchTerm specifies one criterion, e.g. name contains phil
+//---------------------------------------------------------------------------
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgSearchScopeTerm.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgSearchCustomTerm.h"
+
+// needed to search for addresses in address books
+#include "nsIAbDirectory.h"
+
+#define EMPTY_MESSAGE_LINE(buf) (buf[0] == '\r' || buf[0] == '\n' || buf[0] == '\0')
+
+class nsMsgSearchTerm : public nsIMsgSearchTerm
+{
+public:
+ nsMsgSearchTerm();
+ nsMsgSearchTerm (nsMsgSearchAttribValue, nsMsgSearchOpValue, nsIMsgSearchValue *, nsMsgSearchBooleanOperator, const char * arbitraryHeader);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSEARCHTERM
+
+ nsresult DeStream (char *, int16_t length);
+ nsresult DeStreamNew (char *, int16_t length);
+
+ nsresult GetLocalTimes (PRTime, PRTime, PRExplodedTime &, PRExplodedTime &);
+
+ bool IsBooleanOpAND() { return m_booleanOp == nsMsgSearchBooleanOp::BooleanAND ? true : false;}
+ nsMsgSearchBooleanOperator GetBooleanOp() {return m_booleanOp;}
+ // maybe should return nsString & ??
+ const char * GetArbitraryHeader() {return m_arbitraryHeader.get();}
+
+ static char * EscapeQuotesInStr(const char *str);
+
+ nsMsgSearchAttribValue m_attribute;
+ nsMsgSearchOpValue m_operator;
+ nsMsgSearchValue m_value;
+
+ // boolean operator to be applied to this search term and the search term which precedes it.
+ nsMsgSearchBooleanOperator m_booleanOp;
+
+ // user specified string for the name of the arbitrary header to be used in the search
+ // only has a value when m_attribute = OtherHeader!!!!
+ nsCString m_arbitraryHeader;
+
+ // db hdr property name to use - used when m_attribute = HdrProperty.
+ nsCString m_hdrProperty;
+ bool m_matchAll; // does this term match all headers?
+ nsCString m_customId; // id of custom search term
+
+protected:
+ virtual ~nsMsgSearchTerm();
+
+ nsresult MatchString(const nsACString &stringToMatch, const char *charset,
+ bool *pResult);
+ nsresult MatchString(const nsAString &stringToMatch, bool *pResult);
+ nsresult OutputValue(nsCString &outputStr);
+ nsresult ParseAttribute(char *inStream, nsMsgSearchAttribValue *attrib);
+ nsresult ParseOperator(char *inStream, nsMsgSearchOpValue *value);
+ nsresult ParseValue(char *inStream);
+ /**
+ * Switch a string to lower case, except for special database rows
+ * that are not headers, but could be headers
+ *
+ * @param aValue the string to switch
+ */
+ void ToLowerCaseExceptSpecials(nsACString &aValue);
+ nsresult InitializeAddressBook();
+ nsresult MatchInAddressBook(const nsAString &aAddress, bool *pResult);
+ // fields used by search in address book
+ nsCOMPtr <nsIAbDirectory> mDirectory;
+
+ bool mBeginsGrouping;
+ bool mEndsGrouping;
+};
+
+#endif
diff --git a/mailnews/base/search/src/Bogofilter.sfd b/mailnews/base/search/src/Bogofilter.sfd
new file mode 100644
index 000000000..a29a818e6
--- /dev/null
+++ b/mailnews/base/search/src/Bogofilter.sfd
@@ -0,0 +1,14 @@
+version="9"
+logging="yes"
+name="BogofilterYes"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="100"
+condition="OR (\"X-Bogosity\",begins with,Spam) OR (\"X-Bogosity\",begins with,Y)"
+name="BogofilterNo"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="0"
+condition="OR (\"X-Bogosity\",begins with,Ham) OR (\"X-Bogosity\",begins with,N)"
diff --git a/mailnews/base/search/src/DSPAM.sfd b/mailnews/base/search/src/DSPAM.sfd
new file mode 100644
index 000000000..40bc00df7
--- /dev/null
+++ b/mailnews/base/search/src/DSPAM.sfd
@@ -0,0 +1,14 @@
+version="9"
+logging="yes"
+name="DSPAMYes"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="100"
+condition="OR (\"X-DSPAM-Result\",begins with,Spam)"
+name="DSPAMNo"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="0"
+condition="OR (\"X-DSPAM-Result\",begins with,Innocent)"
diff --git a/mailnews/base/search/src/Habeas.sfd b/mailnews/base/search/src/Habeas.sfd
new file mode 100644
index 000000000..ceffff16e
--- /dev/null
+++ b/mailnews/base/search/src/Habeas.sfd
@@ -0,0 +1,8 @@
+version="8"
+logging="yes"
+name="HabeasNo"
+enabled="yes"
+type="1"
+action="JunkScore"
+actionValue="0"
+condition="OR (\"X-Habeas-SWE-3\",is,\"like Habeas SWE (tm)\")"
diff --git a/mailnews/base/search/src/POPFile.sfd b/mailnews/base/search/src/POPFile.sfd
new file mode 100644
index 000000000..a791705aa
--- /dev/null
+++ b/mailnews/base/search/src/POPFile.sfd
@@ -0,0 +1,14 @@
+version="9"
+logging="yes"
+name="POPFileYes"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="100"
+condition="OR (\"X-Text-Classification\",begins with,spam)"
+name="POPFileNo"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="0"
+condition="OR (\"X-Text-Classification\",begins with,inbox) OR (\"X-Text-Classification\",begins with,allowed)"
diff --git a/mailnews/base/search/src/SpamAssassin.sfd b/mailnews/base/search/src/SpamAssassin.sfd
new file mode 100644
index 000000000..d8d0ecdb1
--- /dev/null
+++ b/mailnews/base/search/src/SpamAssassin.sfd
@@ -0,0 +1,14 @@
+version="9"
+logging="yes"
+name="SpamAssassinYes"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="100"
+condition="OR (\"X-Spam-Status\",begins with,Yes) OR (\"X-Spam-Flag\",begins with,YES) OR (subject,begins with,***SPAM***)"
+name="SpamAssassinNo"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="0"
+condition="OR (\"X-Spam-Status\",begins with,No)"
diff --git a/mailnews/base/search/src/SpamCatcher.sfd b/mailnews/base/search/src/SpamCatcher.sfd
new file mode 100644
index 000000000..d16cd80a2
--- /dev/null
+++ b/mailnews/base/search/src/SpamCatcher.sfd
@@ -0,0 +1,14 @@
+version="9"
+logging="yes"
+name="SpamCatcherNo"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="0"
+condition="OR (\"x-SpamCatcher\",begins with,No)"
+name="SpamCatcherYes"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="100"
+condition="OR (\"x-SpamCatcher\",begins with,Yes)"
diff --git a/mailnews/base/search/src/SpamPal.sfd b/mailnews/base/search/src/SpamPal.sfd
new file mode 100644
index 000000000..830b1937b
--- /dev/null
+++ b/mailnews/base/search/src/SpamPal.sfd
@@ -0,0 +1,14 @@
+version="9"
+logging="yes"
+name="SpamPalNo"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="0"
+condition="OR (\"X-SpamPal\",begins with,PASS)"
+name="SpamPalYes"
+enabled="yes"
+type="17"
+action="JunkScore"
+actionValue="100"
+condition="OR (\"X-SpamPal\",begins with,SPAM)"
diff --git a/mailnews/base/search/src/moz.build b/mailnews/base/search/src/moz.build
new file mode 100644
index 000000000..ae3a57fa5
--- /dev/null
+++ b/mailnews/base/search/src/moz.build
@@ -0,0 +1,33 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsMsgBodyHandler.cpp',
+ 'nsMsgFilter.cpp',
+ 'nsMsgFilterList.cpp',
+ 'nsMsgFilterService.cpp',
+ 'nsMsgImapSearch.cpp',
+ 'nsMsgLocalSearch.cpp',
+ 'nsMsgSearchAdapter.cpp',
+ 'nsMsgSearchNews.cpp',
+ 'nsMsgSearchSession.cpp',
+ 'nsMsgSearchTerm.cpp',
+ 'nsMsgSearchValue.cpp',
+]
+
+EXTRA_COMPONENTS += [
+ 'nsMsgTraitService.js',
+ 'nsMsgTraitService.manifest',
+]
+
+FINAL_LIBRARY = 'mail'
+
+FINAL_TARGET_FILES.isp += [
+ 'Bogofilter.sfd',
+ 'DSPAM.sfd',
+ 'POPFile.sfd',
+ 'SpamAssassin.sfd',
+ 'SpamPal.sfd',
+]
diff --git a/mailnews/base/search/src/nsMsgBodyHandler.cpp b/mailnews/base/search/src/nsMsgBodyHandler.cpp
new file mode 100644
index 000000000..873713bbb
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgBodyHandler.cpp
@@ -0,0 +1,487 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgSearchCore.h"
+#include "nsMsgUtils.h"
+#include "nsMsgBodyHandler.h"
+#include "nsMsgSearchTerm.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgMessageFlags.h"
+#include "nsISeekableStream.h"
+#include "nsIInputStream.h"
+#include "nsIFile.h"
+#include "plbase64.h"
+#include "prmem.h"
+#include "nsMimeTypes.h"
+
+nsMsgBodyHandler::nsMsgBodyHandler (nsIMsgSearchScopeTerm * scope,
+ uint32_t numLines,
+ nsIMsgDBHdr* msg, nsIMsgDatabase * db)
+{
+ m_scope = scope;
+ m_numLocalLines = numLines;
+ uint32_t flags;
+ m_lineCountInBodyLines = NS_SUCCEEDED(msg->GetFlags(&flags)) ?
+ !(flags & nsMsgMessageFlags::Offline) : true;
+ // account for added x-mozilla-status lines, and envelope line.
+ if (!m_lineCountInBodyLines)
+ m_numLocalLines += 3;
+ m_msgHdr = msg;
+ m_db = db;
+
+ // the following are variables used when the body handler is handling stuff from filters....through this constructor, that is not the
+ // case so we set them to NULL.
+ m_headers = NULL;
+ m_headersSize = 0;
+ m_Filtering = false; // make sure we set this before we call initialize...
+
+ Initialize(); // common initialization stuff
+ OpenLocalFolder();
+}
+
+nsMsgBodyHandler::nsMsgBodyHandler(nsIMsgSearchScopeTerm * scope,
+ uint32_t numLines,
+ nsIMsgDBHdr* msg, nsIMsgDatabase* db,
+ const char * headers, uint32_t headersSize,
+ bool Filtering)
+{
+ m_scope = scope;
+ m_numLocalLines = numLines;
+ uint32_t flags;
+ m_lineCountInBodyLines = NS_SUCCEEDED(msg->GetFlags(&flags)) ?
+ !(flags & nsMsgMessageFlags::Offline) : true;
+ // account for added x-mozilla-status lines, and envelope line.
+ if (!m_lineCountInBodyLines)
+ m_numLocalLines += 3;
+ m_msgHdr = msg;
+ m_db = db;
+ m_headersSize = headersSize;
+ m_Filtering = Filtering;
+
+ Initialize();
+
+ if (m_Filtering)
+ m_headers = headers;
+ else
+ OpenLocalFolder(); // if nothing else applies, then we must be a POP folder file
+}
+
+void nsMsgBodyHandler::Initialize()
+// common initialization code regardless of what body type we are handling...
+{
+ // Default transformations for local message search and MAPI access
+ m_stripHeaders = true;
+ m_stripHtml = true;
+ m_partIsHtml = false;
+ m_base64part = false;
+ m_isMultipart = false;
+ m_partIsText = true; // Default is text/plain, maybe proven otherwise later.
+ m_pastMsgHeaders = false;
+ m_pastPartHeaders = false;
+ m_inMessageAttachment = false;
+ m_headerBytesRead = 0;
+}
+
+nsMsgBodyHandler::~nsMsgBodyHandler()
+{
+}
+
+int32_t nsMsgBodyHandler::GetNextLine (nsCString &buf, nsCString &charset)
+{
+ int32_t length = -1; // length of incoming line or -1 eof
+ int32_t outLength = -1; // length of outgoing line or -1 eof
+ bool eatThisLine = true;
+ nsAutoCString nextLine;
+
+ while (eatThisLine) {
+ // first, handle the filtering case...this is easy....
+ if (m_Filtering)
+ length = GetNextFilterLine(nextLine);
+ else
+ {
+ // 3 cases: Offline IMAP, POP, or we are dealing with a news message....
+ // Offline cases should be same as local mail cases, since we're going
+ // to store offline messages in berkeley format folders.
+ if (m_db)
+ {
+ length = GetNextLocalLine (nextLine); // (2) POP
+ }
+ }
+
+ if (length < 0)
+ break; // eof in
+
+ outLength = ApplyTransformations(nextLine, length, eatThisLine, buf);
+ }
+
+ if (outLength < 0)
+ return -1; // eof out
+
+ // For non-multipart messages, the entire message minus headers is encoded
+ // ApplyTransformations can only decode a part
+ if (!m_isMultipart && m_base64part)
+ {
+ Base64Decode(buf);
+ m_base64part = false;
+ // And reapply our transformations...
+ outLength = ApplyTransformations(buf, buf.Length(), eatThisLine, buf);
+ }
+
+ charset = m_partCharset;
+ return outLength;
+}
+
+void nsMsgBodyHandler::OpenLocalFolder()
+{
+ nsCOMPtr <nsIInputStream> inputStream;
+ nsresult rv = m_scope->GetInputStream(m_msgHdr, getter_AddRefs(inputStream));
+ // Warn and return if GetInputStream fails
+ NS_ENSURE_SUCCESS_VOID(rv);
+ m_fileLineStream = do_QueryInterface(inputStream);
+}
+
+int32_t nsMsgBodyHandler::GetNextFilterLine(nsCString &buf)
+{
+ // m_nextHdr always points to the next header in the list....the list is NULL terminated...
+ uint32_t numBytesCopied = 0;
+ if (m_headersSize > 0)
+ {
+ // #mscott. Ugly hack! filter headers list have CRs & LFs inside the NULL delimited list of header
+ // strings. It is possible to have: To NULL CR LF From. We want to skip over these CR/LFs if they start
+ // at the beginning of what we think is another header.
+
+ while (m_headersSize > 0 && (m_headers[0] == '\r' || m_headers[0] == '\n' || m_headers[0] == ' ' || m_headers[0] == '\0'))
+ {
+ m_headers++; // skip over these chars...
+ m_headersSize--;
+ }
+
+ if (m_headersSize > 0)
+ {
+ numBytesCopied = strlen(m_headers) + 1 ;
+ buf.Assign(m_headers);
+ m_headers += numBytesCopied;
+ // be careful...m_headersSize is unsigned. Don't let it go negative or we overflow to 2^32....*yikes*
+ if (m_headersSize < numBytesCopied)
+ m_headersSize = 0;
+ else
+ m_headersSize -= numBytesCopied; // update # bytes we have read from the headers list
+
+ return (int32_t) numBytesCopied;
+ }
+ }
+ else if (m_headersSize == 0) {
+ buf.Truncate();
+ }
+ return -1;
+}
+
+// return -1 if no more local lines, length of next line otherwise.
+
+int32_t nsMsgBodyHandler::GetNextLocalLine(nsCString &buf)
+// returns number of bytes copied
+{
+ if (m_numLocalLines)
+ {
+ // I the line count is in body lines, only decrement once we have
+ // processed all the headers. Otherwise the line is not in body
+ // lines and we want to decrement for every line.
+ if (m_pastMsgHeaders || !m_lineCountInBodyLines)
+ m_numLocalLines--;
+ // do we need to check the return value here?
+ if (m_fileLineStream)
+ {
+ bool more = false;
+ nsresult rv = m_fileLineStream->ReadLine(buf, &more);
+ if (NS_SUCCEEDED(rv))
+ return buf.Length();
+ }
+ }
+
+ return -1;
+}
+
+/**
+ * This method applies a sequence of transformations to the line.
+ *
+ * It applies the following sequences in order
+ * * Removes headers if the searcher doesn't want them
+ * (sets m_past*Headers)
+ * * Determines the current MIME type.
+ * (via SniffPossibleMIMEHeader)
+ * * Strips any HTML if the searcher doesn't want it
+ * * Strips non-text parts
+ * * Decodes any base64 part
+ * (resetting part variables: m_base64part, m_pastPartHeaders, m_partIsHtml,
+ * m_partIsText)
+ *
+ * @param line (in) the current line
+ * @param length (in) the length of said line
+ * @param eatThisLine (out) whether or not to ignore this line
+ * @param buf (inout) if m_base64part, the current part as needed for
+ * decoding; else, it is treated as an out param (a
+ * redundant version of line).
+ * @return the length of the line after applying transformations
+ */
+int32_t nsMsgBodyHandler::ApplyTransformations (const nsCString &line, int32_t length,
+ bool &eatThisLine, nsCString &buf)
+{
+ eatThisLine = false;
+
+ if (!m_pastPartHeaders) // line is a line from the part headers
+ {
+ if (m_stripHeaders)
+ eatThisLine = true;
+
+ // We have already grabbed all worthwhile information from the headers,
+ // so there is no need to keep track of the current lines
+ buf.Assign(line);
+
+ SniffPossibleMIMEHeader(buf);
+
+ if (buf.IsEmpty() || buf.First() == '\r' || buf.First() == '\n') {
+ if (!m_inMessageAttachment) {
+ m_pastPartHeaders = true;
+ } else {
+ // We're in a message attachment and have just read past the
+ // part header for the attached message. We now need to read
+ // the message headers and any part headers.
+ // We can now forget about the special handling of attached messages.
+ m_inMessageAttachment = false;
+ }
+ }
+
+ // We set m_pastMsgHeaders to 'true' only once.
+ if (m_pastPartHeaders)
+ m_pastMsgHeaders = true;
+
+ return length;
+ }
+
+ // Check to see if this is one of our boundary strings.
+ bool matchedBoundary = false;
+ if (m_isMultipart && m_boundaries.Length() > 0) {
+ for (int32_t i = (int32_t)m_boundaries.Length() - 1; i >= 0; i--) {
+ if (StringBeginsWith(line, m_boundaries[i])) {
+ matchedBoundary = true;
+ // If we matched a boundary, we won't need the nested/later ones any more.
+ m_boundaries.SetLength(i+1);
+ break;
+ }
+ }
+ }
+ if (matchedBoundary)
+ {
+ if (m_base64part && m_partIsText)
+ {
+ Base64Decode(buf);
+ // Work on the parsed string
+ if (!buf.Length())
+ {
+ NS_WARNING("Trying to transform an empty buffer");
+ eatThisLine = true;
+ }
+ else
+ {
+ // It is wrong to call ApplyTransformations() here since this will
+ // lead to the buffer being doubled-up at |buf.Append(line.get());| below.
+ // ApplyTransformations(buf, buf.Length(), eatThisLine, buf);
+ // Avoid spurious failures
+ eatThisLine = false;
+ }
+ }
+ else
+ {
+ buf.Truncate();
+ eatThisLine = true; // We have no content...
+ }
+
+ // Reset all assumed headers
+ m_base64part = false;
+ // Get ready to sniff new part headers, but do not reset m_pastMsgHeaders
+ // since it will screw the body line count.
+ m_pastPartHeaders = false;
+ m_partIsHtml = false;
+ // If we ever see a multipart message, each part needs to set 'm_partIsText',
+ // so no more defaulting to 'true' when the part is done.
+ m_partIsText = false;
+
+ return buf.Length();
+ }
+
+ if (!m_partIsText)
+ {
+ // Ignore non-text parts
+ buf.Truncate();
+ eatThisLine = true;
+ return 0;
+ }
+
+ if (m_base64part)
+ {
+ // We need to keep track of all lines to parse base64encoded...
+ buf.Append(line.get());
+ eatThisLine = true;
+ return buf.Length();
+ }
+
+ // ... but there's no point if we're not parsing base64.
+ buf.Assign(line);
+ if (m_stripHtml && m_partIsHtml)
+ {
+ StripHtml (buf);
+ }
+
+ return buf.Length();
+}
+
+void nsMsgBodyHandler::StripHtml (nsCString &pBufInOut)
+{
+ char *pBuf = (char*) PR_Malloc (pBufInOut.Length() + 1);
+ if (pBuf)
+ {
+ char *pWalk = pBuf;
+
+ char *pWalkInOut = (char *) pBufInOut.get();
+ bool inTag = false;
+ while (*pWalkInOut) // throw away everything inside < >
+ {
+ if (!inTag)
+ if (*pWalkInOut == '<')
+ inTag = true;
+ else
+ *pWalk++ = *pWalkInOut;
+ else
+ if (*pWalkInOut == '>')
+ inTag = false;
+ pWalkInOut++;
+ }
+ *pWalk = 0; // null terminator
+
+ pBufInOut.Adopt(pBuf);
+ }
+}
+
+/**
+ * Determines the MIME type, if present, from the current line.
+ *
+ * m_partIsHtml, m_isMultipart, m_partIsText, m_base64part, and boundary are
+ * all set by this method at various points in time.
+ *
+ * @param line (in) a header line that may contain a MIME header
+ */
+void nsMsgBodyHandler::SniffPossibleMIMEHeader(const nsCString &line)
+{
+ // Some parts of MIME are case-sensitive and other parts are case-insensitive;
+ // specifically, the headers are all case-insensitive and the values we care
+ // about are also case-insensitive, with the sole exception of the boundary
+ // string, so we can't just take the input line and make it lower case.
+ nsCString lowerCaseLine(line);
+ ToLowerCase(lowerCaseLine);
+
+ if (StringBeginsWith(lowerCaseLine, NS_LITERAL_CSTRING("content-type:")))
+ {
+ if (lowerCaseLine.Find("text/html", CaseInsensitiveCompare) != -1)
+ {
+ m_partIsText = true;
+ m_partIsHtml = true;
+ }
+ else if (lowerCaseLine.Find("multipart/", CaseInsensitiveCompare) != -1)
+ {
+ if (m_isMultipart)
+ {
+ // Nested multipart, get ready for new headers.
+ m_base64part = false;
+ m_pastPartHeaders = false;
+ m_partIsHtml = false;
+ m_partIsText = false;
+ }
+ m_isMultipart = true;
+ m_partCharset.Truncate();
+ }
+ else if (lowerCaseLine.Find("message/", CaseInsensitiveCompare) != -1)
+ {
+ // Initialise again.
+ m_base64part = false;
+ m_pastPartHeaders = false;
+ m_partIsHtml = false;
+ m_partIsText = true; // Default is text/plain, maybe proven otherwise later.
+ m_inMessageAttachment = true;
+ }
+ else if (lowerCaseLine.Find("text/", CaseInsensitiveCompare) != -1)
+ m_partIsText = true;
+ else if (lowerCaseLine.Find("text/", CaseInsensitiveCompare) == -1)
+ m_partIsText = false; // We have disproven our assumption.
+ }
+
+ int32_t start;
+ if (m_isMultipart &&
+ (start = lowerCaseLine.Find("boundary=", CaseInsensitiveCompare)) != -1)
+ {
+ start += 9; // strlen("boundary=")
+ if (line[start] == '\"')
+ start++;
+ int32_t end = line.RFindChar('\"');
+ if (end == -1)
+ end = line.Length();
+
+ // Collect all boundaries. Since we only react to crossing a boundary,
+ // we can simply collect the boundaries instead of forming a tree
+ // structure from the message. Keep it simple ;-)
+ nsCString boundary;
+ boundary.Assign("--");
+ boundary.Append(Substring(line, start, end-start));
+ if (!m_boundaries.Contains(boundary))
+ m_boundaries.AppendElement(boundary);
+ }
+
+ if (m_isMultipart &&
+ (start = lowerCaseLine.Find("charset=", CaseInsensitiveCompare)) != -1)
+ {
+ start += 8; // strlen("charset=")
+ bool foundQuote = false;
+ if (line[start] == '\"') {
+ start++;
+ foundQuote = true;
+ }
+ int32_t end = line.FindChar(foundQuote ? '\"' : ';', start);
+ if (end == -1)
+ end = line.Length();
+
+ m_partCharset.Assign(Substring(line, start, end-start));
+ }
+
+ if (StringBeginsWith(lowerCaseLine,
+ NS_LITERAL_CSTRING("content-transfer-encoding:")) &&
+ lowerCaseLine.Find(ENCODING_BASE64, CaseInsensitiveCompare) != kNotFound)
+ m_base64part = true;
+}
+
+/**
+ * Decodes the given base64 string.
+ *
+ * It returns its decoded string in its input.
+ *
+ * @param pBufInOut (inout) a buffer of the string
+ */
+void nsMsgBodyHandler::Base64Decode (nsCString &pBufInOut)
+{
+ char *decodedBody = PL_Base64Decode(pBufInOut.get(), pBufInOut.Length(), nullptr);
+ if (decodedBody)
+ pBufInOut.Adopt(decodedBody);
+
+ int32_t offset = pBufInOut.FindChar('\n');
+ while (offset != -1) {
+ pBufInOut.Replace(offset, 1, ' ');
+ offset = pBufInOut.FindChar('\n', offset);
+ }
+ offset = pBufInOut.FindChar('\r');
+ while (offset != -1) {
+ pBufInOut.Replace(offset, 1, ' ');
+ offset = pBufInOut.FindChar('\r', offset);
+ }
+}
+
diff --git a/mailnews/base/search/src/nsMsgFilter.cpp b/mailnews/base/search/src/nsMsgFilter.cpp
new file mode 100644
index 000000000..e94240f29
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilter.cpp
@@ -0,0 +1,1057 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// this file implements the nsMsgFilter interface
+
+#include "msgCore.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgFilterList.h" // for kFileVersion
+#include "nsMsgFilter.h"
+#include "nsMsgUtils.h"
+#include "nsMsgLocalSearch.h"
+#include "nsMsgSearchTerm.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsMsgSearchValue.h"
+#include "nsMsgI18N.h"
+#include "nsIOutputStream.h"
+#include "nsIStringBundle.h"
+#include "nsDateTimeFormatCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMutableArray.h"
+#include "prmem.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+
+static const char *kImapPrefix = "//imap:";
+static const char *kWhitespace = "\b\t\r\n ";
+
+nsMsgRuleAction::nsMsgRuleAction()
+{
+}
+
+nsMsgRuleAction::~nsMsgRuleAction()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsMsgRuleAction, nsIMsgRuleAction)
+
+NS_IMPL_GETSET(nsMsgRuleAction, Type, nsMsgRuleActionType, m_type)
+
+NS_IMETHODIMP nsMsgRuleAction::SetPriority(nsMsgPriorityValue aPriority)
+{
+ NS_ENSURE_TRUE(m_type == nsMsgFilterAction::ChangePriority,
+ NS_ERROR_ILLEGAL_VALUE);
+ m_priority = aPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRuleAction::GetPriority(nsMsgPriorityValue *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_TRUE(m_type == nsMsgFilterAction::ChangePriority,
+ NS_ERROR_ILLEGAL_VALUE);
+ *aResult = m_priority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRuleAction::SetLabel(nsMsgLabelValue aLabel)
+{
+ NS_ENSURE_TRUE(m_type == nsMsgFilterAction::Label,
+ NS_ERROR_ILLEGAL_VALUE);
+ m_label = aLabel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRuleAction::GetLabel(nsMsgLabelValue *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_TRUE(m_type == nsMsgFilterAction::Label, NS_ERROR_ILLEGAL_VALUE);
+ *aResult = m_label;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRuleAction::SetTargetFolderUri(const nsACString &aUri)
+{
+ NS_ENSURE_TRUE(m_type == nsMsgFilterAction::MoveToFolder ||
+ m_type == nsMsgFilterAction::CopyToFolder,
+ NS_ERROR_ILLEGAL_VALUE);
+ m_folderUri = aUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRuleAction::GetTargetFolderUri(nsACString &aResult)
+{
+ NS_ENSURE_TRUE(m_type == nsMsgFilterAction::MoveToFolder ||
+ m_type == nsMsgFilterAction::CopyToFolder,
+ NS_ERROR_ILLEGAL_VALUE);
+ aResult = m_folderUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRuleAction::SetJunkScore(int32_t aJunkScore)
+{
+ NS_ENSURE_TRUE(m_type == nsMsgFilterAction::JunkScore && aJunkScore >= 0 && aJunkScore <= 100,
+ NS_ERROR_ILLEGAL_VALUE);
+ m_junkScore = aJunkScore;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRuleAction::GetJunkScore(int32_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_TRUE(m_type == nsMsgFilterAction::JunkScore, NS_ERROR_ILLEGAL_VALUE);
+ *aResult = m_junkScore;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRuleAction::SetStrValue(const nsACString &aStrValue)
+{
+ m_strValue = aStrValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRuleAction::GetStrValue(nsACString &aStrValue)
+{
+ aStrValue = m_strValue;
+ return NS_OK;
+}
+
+/* attribute ACString customId; */
+NS_IMETHODIMP nsMsgRuleAction::GetCustomId(nsACString & aCustomId)
+{
+ aCustomId = m_customId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgRuleAction::SetCustomId(const nsACString & aCustomId)
+{
+ m_customId = aCustomId;
+ return NS_OK;
+}
+
+// this can only be called after the customId is set
+NS_IMETHODIMP nsMsgRuleAction::GetCustomAction(nsIMsgFilterCustomAction **aCustomAction)
+{
+ NS_ENSURE_ARG_POINTER(aCustomAction);
+ if (!m_customAction)
+ {
+ if (m_customId.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+ nsresult rv;
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filterService->GetCustomAction(m_customId, getter_AddRefs(m_customAction));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // found the correct custom action
+ NS_ADDREF(*aCustomAction = m_customAction);
+ return NS_OK;
+}
+
+nsMsgFilter::nsMsgFilter():
+ m_temporary(false),
+ m_unparseable(false),
+ m_filterList(nullptr),
+ m_expressionTree(nullptr)
+{
+ nsresult rv = NS_NewISupportsArray(getter_AddRefs(m_termList));
+ if (NS_FAILED(rv))
+ NS_ASSERTION(false, "Failed to allocate a nsISupportsArray for nsMsgFilter");
+
+ m_type = nsMsgFilterType::InboxRule | nsMsgFilterType::Manual;
+}
+
+nsMsgFilter::~nsMsgFilter()
+{
+ delete m_expressionTree;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgFilter, nsIMsgFilter)
+
+NS_IMPL_GETSET(nsMsgFilter, FilterType, nsMsgFilterTypeType, m_type)
+NS_IMPL_GETSET(nsMsgFilter, Enabled, bool, m_enabled)
+NS_IMPL_GETSET(nsMsgFilter, Temporary, bool, m_temporary)
+NS_IMPL_GETSET(nsMsgFilter, Unparseable, bool, m_unparseable)
+
+NS_IMETHODIMP nsMsgFilter::GetFilterName(nsAString &name)
+{
+ name = m_filterName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::SetFilterName(const nsAString &name)
+{
+ m_filterName.Assign(name);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::GetFilterDesc(nsACString &description)
+{
+ description = m_description;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::SetFilterDesc(const nsACString &description)
+{
+ m_description.Assign(description);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::GetUnparsedBuffer(nsACString &unparsedBuffer)
+{
+ unparsedBuffer = m_unparsedBuffer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::SetUnparsedBuffer(const nsACString &unparsedBuffer)
+{
+ m_unparsedBuffer.Assign(unparsedBuffer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::AddTerm(
+ nsMsgSearchAttribValue attrib, /* attribute for this term */
+ nsMsgSearchOpValue op, /* operator e.g. opContains */
+ nsIMsgSearchValue *value, /* value e.g. "Dogbert" */
+ bool BooleanAND, /* true if AND is the boolean operator.
+ false if OR is the boolean operators */
+ const nsACString & arbitraryHeader) /* arbitrary header specified by user.
+ ignored unless attrib = attribOtherHeader */
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::AppendTerm(nsIMsgSearchTerm * aTerm)
+{
+ NS_ENSURE_TRUE(aTerm, NS_ERROR_NULL_POINTER);
+ // invalidate expression tree if we're changing the terms
+ delete m_expressionTree;
+ m_expressionTree = nullptr;
+ return m_termList->AppendElement(static_cast<nsISupports*>(aTerm));
+}
+
+NS_IMETHODIMP
+nsMsgFilter::CreateTerm(nsIMsgSearchTerm **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsMsgSearchTerm *term = new nsMsgSearchTerm;
+ NS_ENSURE_TRUE(term, NS_ERROR_OUT_OF_MEMORY);
+
+ *aResult = static_cast<nsIMsgSearchTerm*>(term);
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilter::CreateAction(nsIMsgRuleAction **aAction)
+{
+ NS_ENSURE_ARG_POINTER(aAction);
+ nsMsgRuleAction *action = new nsMsgRuleAction;
+ NS_ENSURE_TRUE(action, NS_ERROR_OUT_OF_MEMORY);
+
+ *aAction = static_cast<nsIMsgRuleAction*>(action);
+ NS_ADDREF(*aAction);
+ return NS_OK;
+}
+
+// All the rules' actions form a unit, with no real order imposed.
+// But certain actions like MoveToFolder or StopExecution would make us drop
+// consecutive actions, while actions like AddTag implicitly care about the
+// order of invocation. Hence we do as little reordering as possible, keeping
+// the user-defined order as much as possible.
+// We explicitly don't allow for filters which do "tag message as Important,
+// copy it to another folder, tag it as To Do also, copy this different state
+// elsewhere" in one go. You need to define separate filters for that.
+//
+// The order of actions returned by this method:
+// index action(s)
+// ------- ---------
+// 0 FetchBodyFromPop3Server
+// 1..n all other 'normal' actions, in their original order
+// n+1..m CopyToFolder
+// m+1 MoveToFolder or Delete
+// m+2 StopExecution
+NS_IMETHODIMP
+nsMsgFilter::GetSortedActionList(nsIArray **aActionList)
+{
+ NS_ENSURE_ARG_POINTER(aActionList);
+
+ uint32_t numActions;
+ nsresult rv = GetActionCount(&numActions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> orderedActions(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // hold separate pointers into the action list
+ uint32_t nextIndexForNormal = 0, nextIndexForCopy = 0, nextIndexForMove = 0;
+ for (uint32_t index = 0; index < numActions; ++index)
+ {
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = GetActionAt(index, getter_AddRefs(action));
+ if (NS_FAILED(rv) || !action)
+ continue;
+
+ nsMsgRuleActionType actionType;
+ action->GetType(&actionType);
+ switch (actionType)
+ {
+ case nsMsgFilterAction::FetchBodyFromPop3Server:
+ {
+ // always insert in front
+ rv = orderedActions->InsertElementAt(action, 0, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ++nextIndexForNormal;
+ ++nextIndexForCopy;
+ ++nextIndexForMove;
+ break;
+ }
+
+ case nsMsgFilterAction::CopyToFolder:
+ {
+ // insert into copy actions block, in order of appearance
+ rv = orderedActions->InsertElementAt(action, nextIndexForCopy, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ++nextIndexForCopy;
+ ++nextIndexForMove;
+ break;
+ }
+
+ case nsMsgFilterAction::MoveToFolder:
+ case nsMsgFilterAction::Delete:
+ {
+ // insert into move/delete action block
+ rv = orderedActions->InsertElementAt(action, nextIndexForMove, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ++nextIndexForMove;
+ break;
+ }
+
+ case nsMsgFilterAction::StopExecution:
+ {
+ // insert into stop action block
+ rv = orderedActions->AppendElement(action, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+
+ default:
+ {
+ // insert into normal action block, in order of appearance
+ rv = orderedActions->InsertElementAt(action, nextIndexForNormal, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ++nextIndexForNormal;
+ ++nextIndexForCopy;
+ ++nextIndexForMove;
+ break;
+ }
+ }
+ }
+
+ orderedActions.forget(aActionList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilter::AppendAction(nsIMsgRuleAction *aAction)
+{
+ NS_ENSURE_ARG_POINTER(aAction);
+
+ m_actionList.AppendElement(aAction);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilter::GetActionAt(uint32_t aIndex, nsIMsgRuleAction **aAction)
+{
+ NS_ENSURE_ARG_POINTER(aAction);
+ NS_ENSURE_ARG(aIndex < m_actionList.Length());
+
+ NS_ENSURE_TRUE(*aAction = m_actionList[aIndex], NS_ERROR_ILLEGAL_VALUE);
+ NS_IF_ADDREF(*aAction);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilter::GetActionIndex(nsIMsgRuleAction *aAction, int32_t *aIndex)
+{
+ NS_ENSURE_ARG_POINTER(aIndex);
+
+ *aIndex = m_actionList.IndexOf(aAction);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilter::GetActionCount(uint32_t *aCount)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+
+ *aCount = m_actionList.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP //for editing a filter
+nsMsgFilter::ClearActionList()
+{
+ m_actionList.Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::GetTerm(int32_t termIndex,
+ nsMsgSearchAttribValue *attrib, /* attribute for this term */
+ nsMsgSearchOpValue *op, /* operator e.g. opContains */
+ nsIMsgSearchValue **value, /* value e.g. "Dogbert" */
+ bool *booleanAnd, /* true if AND is the boolean operator. false if OR is the boolean operator */
+ nsACString &arbitraryHeader) /* arbitrary header specified by user.ignore unless attrib = attribOtherHeader */
+{
+ nsCOMPtr<nsIMsgSearchTerm> term;
+ nsresult rv = m_termList->QueryElementAt(termIndex, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(term));
+ if (NS_SUCCEEDED(rv) && term)
+ {
+ if (attrib)
+ term->GetAttrib(attrib);
+ if (op)
+ term->GetOp(op);
+ if (value)
+ term->GetValue(value);
+ if (booleanAnd)
+ term->GetBooleanAnd(booleanAnd);
+ if (attrib && *attrib > nsMsgSearchAttrib::OtherHeader
+ && *attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes)
+ term->GetArbitraryHeader(arbitraryHeader);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::GetSearchTerms(nsISupportsArray **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // caller can change m_termList, which can invalidate m_expressionTree.
+ delete m_expressionTree;
+ m_expressionTree = nullptr;
+ NS_IF_ADDREF(*aResult = m_termList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::SetSearchTerms(nsISupportsArray *aSearchList)
+{
+ delete m_expressionTree;
+ m_expressionTree = nullptr;
+ m_termList = aSearchList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::SetScope(nsIMsgSearchScopeTerm *aResult)
+{
+ m_scope = aResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::GetScope(nsIMsgSearchScopeTerm **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_IF_ADDREF(*aResult = m_scope);
+ return NS_OK;
+}
+
+#define LOG_ENTRY_START_TAG "<p>\n"
+#define LOG_ENTRY_START_TAG_LEN (strlen(LOG_ENTRY_START_TAG))
+#define LOG_ENTRY_END_TAG "</p>\n"
+#define LOG_ENTRY_END_TAG_LEN (strlen(LOG_ENTRY_END_TAG))
+// Does this need to be localizable?
+#define LOG_ENTRY_TIMESTAMP "[$S] "
+
+// This function handles the logging both for success of filtering
+// (NS_SUCCEEDED(aRcode)), and for error reporting (NS_FAILED(aRcode)
+// when the filter action (such as file move/copy) failed.
+//
+// @param aRcode NS_OK for successful filtering
+// operation, otherwise, an error code for filtering failure.
+// @param aErrmsg Not used for success case (ignored), and a non-null
+// error message for failure case.
+//
+// CAUTION: Unless logging is enabled, no error/warning is shown.
+// So enable logging if you would like to see the error/warning.
+//
+// XXX The current code in this file does not report errors of minor
+// operations such as adding labels and so forth which may fail when
+// underlying file system for the message store experiences
+// failure. For now, most visible major errors such as message
+// move/copy failures are taken care of.
+//
+// XXX Possible Improvement: For error case reporting, someone might
+// want to implement a transient message that appears and stick until
+// the user clears in the message status bar, etc. For now, we log an
+// error in a similar form as a conventional successful filter event
+// with additional error information at the beginning.
+//
+nsresult
+nsMsgFilter::LogRuleHitGeneric(nsIMsgRuleAction *aFilterAction,
+ nsIMsgDBHdr *aMsgHdr,
+ nsresult aRcode,
+ const char *aErrmsg)
+{
+ NS_ENSURE_ARG_POINTER(aFilterAction);
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+
+ NS_ENSURE_TRUE(m_filterList, NS_OK);
+ nsCOMPtr <nsIOutputStream> logStream;
+ nsresult rv = m_filterList->GetLogStream(getter_AddRefs(logStream));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ PRTime date;
+ nsMsgRuleActionType actionType;
+
+ nsString authorValue;
+ nsString subjectValue;
+ nsString filterName;
+ nsString dateValue;
+
+ GetFilterName(filterName);
+ aFilterAction->GetType(&actionType);
+ (void)aMsgHdr->GetDate(&date);
+ PRExplodedTime exploded;
+ PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded);
+
+ if (!mDateFormatter)
+ {
+ mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mDateFormatter)
+ {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort,
+ kTimeFormatSeconds, &exploded,
+ dateValue);
+
+ (void)aMsgHdr->GetMime2DecodedAuthor(authorValue);
+ (void)aMsgHdr->GetMime2DecodedSubject(subjectValue);
+
+ nsCString buffer;
+#ifdef MOZILLA_INTERNAL_API
+ // this is big enough to hold a log entry.
+ // do this so we avoid growing and copying as we append to the log.
+ buffer.SetCapacity(512);
+#endif
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/filter.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If error, prefix with the error code and error message.
+ // A desired wording (without NEWLINEs):
+ // Filter Action Failed "Move failed" with error code=0x80004005
+ // while attempting: Applied filter "test" to message from
+ // Some Test <test@example.com> - send test 3 at 2/13/2015 11:32:53 AM
+ // moved message id = 54DE5165.7000907@example.com to
+ // mailbox://nobody@Local%20Folders/test
+
+ if (NS_FAILED(aRcode))
+ {
+
+ // Let us put "Filter Action Failed: "%s" with error code=%s while attempting: " inside bundle.
+ // Convert aErrmsg to UTF16 string, and
+ // convert aRcode to UTF16 string in advance.
+
+ char tcode[20];
+ PR_snprintf(tcode, sizeof(tcode), "0x%08x", aRcode);
+
+ NS_ConvertASCIItoUTF16 tcode16(tcode) ;
+ NS_ConvertASCIItoUTF16 tErrmsg16(aErrmsg) ;
+
+ const char16_t *logErrorFormatStrings[2] = { tErrmsg16.get(), tcode16.get()};
+ nsString filterFailureWarningPrefix;
+ rv = bundle->FormatStringFromName(
+ u"filterFailureWarningPrefix",
+ logErrorFormatStrings, 2,
+ getter_Copies(filterFailureWarningPrefix));
+ NS_ENSURE_SUCCESS(rv, rv);
+ buffer += NS_ConvertUTF16toUTF8(filterFailureWarningPrefix);
+ buffer += "\n";
+ }
+
+ const char16_t *filterLogDetectFormatStrings[4] = { filterName.get(), authorValue.get(), subjectValue.get(), dateValue.get() };
+ nsString filterLogDetectStr;
+ rv = bundle->FormatStringFromName(
+ u"filterLogDetectStr",
+ filterLogDetectFormatStrings, 4,
+ getter_Copies(filterLogDetectStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ buffer += NS_ConvertUTF16toUTF8(filterLogDetectStr);
+ buffer += "\n";
+
+ if (actionType == nsMsgFilterAction::MoveToFolder ||
+ actionType == nsMsgFilterAction::CopyToFolder)
+ {
+ nsCString actionFolderUri;
+ aFilterAction->GetTargetFolderUri(actionFolderUri);
+ NS_ConvertASCIItoUTF16 actionFolderUriValue(actionFolderUri);
+
+ nsCString msgId;
+ aMsgHdr->GetMessageId(getter_Copies(msgId));
+ NS_ConvertASCIItoUTF16 msgIdValue(msgId);
+
+ const char16_t *logMoveFormatStrings[2] = { msgIdValue.get(), actionFolderUriValue.get() };
+ nsString logMoveStr;
+ rv = bundle->FormatStringFromName(
+ (actionType == nsMsgFilterAction::MoveToFolder) ?
+ u"logMoveStr" : u"logCopyStr",
+ logMoveFormatStrings, 2,
+ getter_Copies(logMoveStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ buffer += NS_ConvertUTF16toUTF8(logMoveStr);
+ }
+ else if (actionType == nsMsgFilterAction::Custom)
+ {
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ nsAutoString filterActionName;
+ rv = aFilterAction->GetCustomAction(getter_AddRefs(customAction));
+ if (NS_SUCCEEDED(rv) && customAction)
+ customAction->GetName(filterActionName);
+ if (filterActionName.IsEmpty())
+ bundle->GetStringFromName(
+ u"filterMissingCustomAction",
+ getter_Copies(filterActionName));
+ buffer += NS_ConvertUTF16toUTF8(filterActionName);
+ }
+ else
+ {
+ nsString actionValue;
+ nsAutoString filterActionID;
+ filterActionID = NS_LITERAL_STRING("filterAction");
+ filterActionID.AppendInt(actionType);
+ rv = bundle->GetStringFromName(filterActionID.get(), getter_Copies(actionValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ buffer += NS_ConvertUTF16toUTF8(actionValue);
+ }
+ buffer += "\n";
+
+ // Prepare timestamp
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort,
+ kTimeFormatSeconds, &exploded,
+ dateValue);
+
+ nsCString timestampString(LOG_ENTRY_TIMESTAMP);
+ MsgReplaceSubstring(timestampString, "$S", NS_ConvertUTF16toUTF8(dateValue).get());
+
+ // XXX: Finally, here we have enough context and buffer
+ // (string) to display the filtering error if we want: for
+ // example, a sticky error message in status bar, etc.
+
+ uint32_t writeCount;
+
+ rv = logStream->Write(LOG_ENTRY_START_TAG, LOG_ENTRY_START_TAG_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ASSERTION(writeCount == LOG_ENTRY_START_TAG_LEN, "failed to write out start log tag");
+
+ rv = logStream->Write(timestampString.get(), timestampString.Length(), &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ASSERTION(writeCount == timestampString.Length(), "failed to write out timestamp");
+
+ // HTML-escape the log for security reasons.
+ // We don't want someone to send us a message with a subject with
+ // HTML tags, especially <script>.
+ char *escapedBuffer = MsgEscapeHTML(buffer.get());
+ if (!escapedBuffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ uint32_t escapedBufferLen = strlen(escapedBuffer);
+ rv = logStream->Write(escapedBuffer, escapedBufferLen, &writeCount);
+ PR_Free(escapedBuffer);
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ASSERTION(writeCount == escapedBufferLen, "failed to write out log hit");
+
+ rv = logStream->Write(LOG_ENTRY_END_TAG, LOG_ENTRY_END_TAG_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ASSERTION(writeCount == LOG_ENTRY_END_TAG_LEN, "failed to write out end log tag");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::LogRuleHit(nsIMsgRuleAction *aFilterAction,
+ nsIMsgDBHdr *aMsgHdr)
+{
+ return nsMsgFilter::LogRuleHitGeneric(aFilterAction, aMsgHdr, NS_OK, nullptr);
+}
+
+NS_IMETHODIMP nsMsgFilter::LogRuleHitFail(nsIMsgRuleAction *aFilterAction,
+ nsIMsgDBHdr *aMsgHdr,
+ nsresult aRcode,
+ const char *aErrMsg)
+{
+ return nsMsgFilter::LogRuleHitGeneric(aFilterAction, aMsgHdr, aRcode, aErrMsg);
+}
+
+NS_IMETHODIMP
+nsMsgFilter::MatchHdr(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder,
+ nsIMsgDatabase *db, const char *headers,
+ uint32_t headersSize, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(folder);
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ // use offlineMail because
+ nsCString folderCharset;
+ folder->GetCharset(folderCharset);
+ nsresult rv = nsMsgSearchOfflineMail::MatchTermsForFilter(msgHdr, m_termList,
+ folderCharset.get(), m_scope, db, headers, headersSize, &m_expressionTree, pResult);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgFilter::SetFilterList(nsIMsgFilterList *filterList)
+{
+ // doesn't hold a ref.
+ m_filterList = filterList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilter::GetFilterList(nsIMsgFilterList **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_IF_ADDREF(*aResult = m_filterList);
+ return NS_OK;
+}
+
+void nsMsgFilter::SetFilterScript(nsCString *fileName)
+{
+ m_scriptFileName = *fileName;
+}
+
+nsresult nsMsgFilter::ConvertMoveOrCopyToFolderValue(nsIMsgRuleAction *filterAction, nsCString &moveValue)
+{
+ NS_ENSURE_ARG_POINTER(filterAction);
+ int16_t filterVersion = kFileVersion;
+ if (m_filterList)
+ m_filterList->GetVersion(&filterVersion);
+ if (filterVersion <= k60Beta1Version)
+ {
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ nsCString folderUri;
+
+ m_filterList->GetFolder(getter_AddRefs(rootFolder));
+ // if relative path starts with kImap, this is a move to folder on the same server
+ if (moveValue.Find(kImapPrefix) == 0)
+ {
+ int32_t prefixLen = PL_strlen(kImapPrefix);
+ nsAutoCString originalServerPath(Substring(moveValue, prefixLen));
+ if (filterVersion == k45Version)
+ {
+ nsAutoString unicodeStr;
+ nsresult rv = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(),
+ originalServerPath,
+ unicodeStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CopyUTF16toMUTF7(unicodeStr, originalServerPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr <nsIMsgFolder> destIFolder;
+ if (rootFolder)
+ {
+ rootFolder->FindSubFolder(originalServerPath, getter_AddRefs(destIFolder));
+ if (destIFolder)
+ {
+ destIFolder->GetURI(folderUri);
+ filterAction->SetTargetFolderUri(folderUri);
+ moveValue.Assign(folderUri);
+ }
+ }
+ }
+ else
+ {
+ // start off leaving the value the same.
+ filterAction->SetTargetFolderUri(moveValue);
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgFolder> localMailRoot;
+ rootFolder->GetURI(folderUri);
+ // if the root folder is not imap, than the local mail root is the server root.
+ // otherwise, it's the migrated local folders.
+ if (!StringBeginsWith(folderUri, NS_LITERAL_CSTRING("imap:")))
+ localMailRoot = rootFolder;
+ else
+ {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ rv = server->GetRootFolder(getter_AddRefs(localMailRoot));
+ }
+ if (NS_SUCCEEDED(rv) && localMailRoot)
+ {
+ nsCString localRootURI;
+ nsCOMPtr <nsIMsgFolder> destIMsgFolder;
+ nsCOMPtr <nsIMsgFolder> localMailRootMsgFolder = do_QueryInterface(localMailRoot);
+ localMailRoot->GetURI(localRootURI);
+ nsCString destFolderUri;
+ destFolderUri.Assign( localRootURI);
+ // need to remove ".sbd" from moveValue, and perhaps escape it.
+ int32_t offset = moveValue.Find(".sbd/");
+ if (offset != -1)
+ moveValue.Cut(offset, 4);
+
+#ifdef XP_MACOSX
+ nsCString unescapedMoveValue;
+ MsgUnescapeString(moveValue, 0, unescapedMoveValue);
+ moveValue = unescapedMoveValue;
+#endif
+ destFolderUri.Append('/');
+ if (filterVersion == k45Version)
+ {
+ nsAutoString unicodeStr;
+ rv = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(),
+ moveValue, unicodeStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_MsgEscapeEncodeURLPath(unicodeStr, moveValue);
+ }
+ destFolderUri.Append(moveValue);
+ localMailRootMsgFolder->GetChildWithURI (destFolderUri, true, false /*caseInsensitive*/, getter_AddRefs(destIMsgFolder));
+
+ if (destIMsgFolder)
+ {
+ destIMsgFolder->GetURI(folderUri);
+ filterAction->SetTargetFolderUri(folderUri);
+ moveValue.Assign(folderUri);
+ }
+ }
+ }
+ }
+ else
+ filterAction->SetTargetFolderUri(moveValue);
+
+ return NS_OK;
+ // set m_action.m_value.m_folderUri
+}
+
+NS_IMETHODIMP
+nsMsgFilter::SaveToTextFile(nsIOutputStream *aStream)
+{
+ NS_ENSURE_ARG_POINTER(aStream);
+ if (m_unparseable)
+ {
+ uint32_t bytesWritten;
+ //we need to trim leading whitespaces before filing out
+ m_unparsedBuffer.Trim(kWhitespace, true /*leadingCharacters*/, false /*trailingCharacters*/);
+ return aStream->Write(m_unparsedBuffer.get(), m_unparsedBuffer.Length(), &bytesWritten);
+ }
+ nsresult err = m_filterList->WriteWstrAttr(nsIMsgFilterList::attribName, m_filterName.get(), aStream);
+ err = m_filterList->WriteBoolAttr(nsIMsgFilterList::attribEnabled, m_enabled, aStream);
+ err = m_filterList->WriteStrAttr(nsIMsgFilterList::attribDescription, m_description.get(), aStream);
+ err = m_filterList->WriteIntAttr(nsIMsgFilterList::attribType, m_type, aStream);
+ if (IsScript())
+ err = m_filterList->WriteStrAttr(nsIMsgFilterList::attribScriptFile, m_scriptFileName.get(), aStream);
+ else
+ err = SaveRule(aStream);
+ return err;
+}
+
+nsresult nsMsgFilter::SaveRule(nsIOutputStream *aStream)
+{
+ nsresult err = NS_OK;
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ GetFilterList(getter_AddRefs(filterList));
+ nsAutoCString actionFilingStr;
+
+ uint32_t numActions;
+ err = GetActionCount(&numActions);
+ NS_ENSURE_SUCCESS(err, err);
+
+ for (uint32_t index = 0; index < numActions; index++)
+ {
+ nsCOMPtr<nsIMsgRuleAction> action;
+ err = GetActionAt(index, getter_AddRefs(action));
+ if (NS_FAILED(err) || !action)
+ continue;
+
+ nsMsgRuleActionType actionType;
+ action->GetType(&actionType);
+ GetActionFilingStr(actionType, actionFilingStr);
+
+ err = filterList->WriteStrAttr(nsIMsgFilterList::attribAction, actionFilingStr.get(), aStream);
+ NS_ENSURE_SUCCESS(err, err);
+
+ switch(actionType)
+ {
+ case nsMsgFilterAction::MoveToFolder:
+ case nsMsgFilterAction::CopyToFolder:
+ {
+ nsCString imapTargetString;
+ action->GetTargetFolderUri(imapTargetString);
+ err = filterList->WriteStrAttr(nsIMsgFilterList::attribActionValue, imapTargetString.get(), aStream);
+ }
+ break;
+ case nsMsgFilterAction::ChangePriority:
+ {
+ nsMsgPriorityValue priorityValue;
+ action->GetPriority(&priorityValue);
+ nsAutoCString priority;
+ NS_MsgGetUntranslatedPriorityName(priorityValue, priority);
+ err = filterList->WriteStrAttr(
+ nsIMsgFilterList::attribActionValue, priority.get(), aStream);
+ }
+ break;
+ case nsMsgFilterAction::Label:
+ {
+ nsMsgLabelValue label;
+ action->GetLabel(&label);
+ err = filterList->WriteIntAttr(nsIMsgFilterList::attribActionValue, label, aStream);
+ }
+ break;
+ case nsMsgFilterAction::JunkScore:
+ {
+ int32_t junkScore;
+ action->GetJunkScore(&junkScore);
+ err = filterList->WriteIntAttr(nsIMsgFilterList::attribActionValue, junkScore, aStream);
+ }
+ break;
+ case nsMsgFilterAction::AddTag:
+ case nsMsgFilterAction::Reply:
+ case nsMsgFilterAction::Forward:
+ {
+ nsCString strValue;
+ action->GetStrValue(strValue);
+ // strValue is e-mail address
+ err = filterList->WriteStrAttr(nsIMsgFilterList::attribActionValue, strValue.get(), aStream);
+ }
+ break;
+ case nsMsgFilterAction::Custom:
+ {
+ nsAutoCString id;
+ action->GetCustomId(id);
+ err = filterList->WriteStrAttr(nsIMsgFilterList::attribCustomId, id.get(), aStream);
+ nsAutoCString strValue;
+ action->GetStrValue(strValue);
+ if (strValue.Length())
+ err = filterList->WriteWstrAttr(nsIMsgFilterList::attribActionValue,
+ NS_ConvertUTF8toUTF16(strValue).get(),
+ aStream);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ // and here the fun begins - file out term list...
+ nsAutoCString condition;
+ err = MsgTermListToString(m_termList, condition);
+ if (NS_SUCCEEDED(err))
+ err = filterList->WriteStrAttr(nsIMsgFilterList::attribCondition, condition.get(), aStream);
+ return err;
+}
+
+// for each action, this table encodes the filterTypes that support the action.
+struct RuleActionsTableEntry
+{
+ nsMsgRuleActionType action;
+ const char* actionFilingStr; /* used for filing out filters, don't translate! */
+};
+
+static struct RuleActionsTableEntry ruleActionsTable[] =
+{
+ { nsMsgFilterAction::MoveToFolder, "Move to folder"},
+ { nsMsgFilterAction::CopyToFolder, "Copy to folder"},
+ { nsMsgFilterAction::ChangePriority, "Change priority"},
+ { nsMsgFilterAction::Delete, "Delete"},
+ { nsMsgFilterAction::MarkRead, "Mark read"},
+ { nsMsgFilterAction::KillThread, "Ignore thread"},
+ { nsMsgFilterAction::KillSubthread, "Ignore subthread"},
+ { nsMsgFilterAction::WatchThread, "Watch thread"},
+ { nsMsgFilterAction::MarkFlagged, "Mark flagged"},
+ { nsMsgFilterAction::Label, "Label"},
+ { nsMsgFilterAction::Reply, "Reply"},
+ { nsMsgFilterAction::Forward, "Forward"},
+ { nsMsgFilterAction::StopExecution, "Stop execution"},
+ { nsMsgFilterAction::DeleteFromPop3Server, "Delete from Pop3 server"},
+ { nsMsgFilterAction::LeaveOnPop3Server, "Leave on Pop3 server"},
+ { nsMsgFilterAction::JunkScore, "JunkScore"},
+ { nsMsgFilterAction::FetchBodyFromPop3Server, "Fetch body from Pop3Server"},
+ { nsMsgFilterAction::AddTag, "AddTag"},
+ { nsMsgFilterAction::MarkUnread, "Mark unread"},
+ { nsMsgFilterAction::Custom, "Custom"},
+};
+
+static const unsigned int sNumActions = MOZ_ARRAY_LENGTH(ruleActionsTable);
+
+const char *nsMsgFilter::GetActionStr(nsMsgRuleActionType action)
+{
+ for (unsigned int i = 0; i < sNumActions; i++)
+ {
+ if (action == ruleActionsTable[i].action)
+ return ruleActionsTable[i].actionFilingStr;
+ }
+ return "";
+}
+/*static */nsresult nsMsgFilter::GetActionFilingStr(nsMsgRuleActionType action, nsCString &actionStr)
+{
+ for (unsigned int i = 0; i < sNumActions; i++)
+ {
+ if (action == ruleActionsTable[i].action)
+ {
+ actionStr = ruleActionsTable[i].actionFilingStr;
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_INVALID_ARG;
+}
+
+
+nsMsgRuleActionType nsMsgFilter::GetActionForFilingStr(nsCString &actionStr)
+{
+ for (unsigned int i = 0; i < sNumActions; i++)
+ {
+ if (actionStr.Equals(ruleActionsTable[i].actionFilingStr))
+ return ruleActionsTable[i].action;
+ }
+ return nsMsgFilterAction::None;
+}
+
+int16_t
+nsMsgFilter::GetVersion()
+{
+ if (!m_filterList) return 0;
+ int16_t version;
+ m_filterList->GetVersion(&version);
+ return version;
+}
+
+#ifdef DEBUG
+void nsMsgFilter::Dump()
+{
+ nsAutoCString s;
+ LossyCopyUTF16toASCII(m_filterName, s);
+ printf("filter %s type = %c desc = %s\n", s.get(), m_type + '0', m_description.get());
+}
+#endif
diff --git a/mailnews/base/search/src/nsMsgFilter.h b/mailnews/base/search/src/nsMsgFilter.h
new file mode 100644
index 000000000..077baa2ff
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilter.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgFilter_H_
+#define _nsMsgFilter_H_
+
+#include "nscore.h"
+#include "nsISupports.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgSearchScopeTerm.h"
+#include "nsMsgSearchBoolExpression.h"
+#include "nsIDateTimeFormat.h"
+#include "nsIMsgFilterCustomAction.h"
+
+class nsMsgRuleAction : public nsIMsgRuleAction
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsMsgRuleAction();
+
+ NS_DECL_NSIMSGRULEACTION
+
+private:
+ virtual ~nsMsgRuleAction();
+
+ nsMsgRuleActionType m_type;
+ // this used to be a union - why bother?
+ nsMsgPriorityValue m_priority; /* priority to set rule to */
+ nsMsgLabelValue m_label; /* label to set rule to */
+ nsCString m_folderUri;
+ int32_t m_junkScore; /* junk score (or arbitrary int value?) */
+ // arbitrary string value. Currently, email address to forward to
+ nsCString m_strValue;
+ nsCString m_customId;
+ nsCOMPtr<nsIMsgFilterCustomAction> m_customAction;
+} ;
+
+
+class nsMsgFilter : public nsIMsgFilter
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsMsgFilter();
+
+ NS_DECL_NSIMSGFILTER
+
+ nsMsgFilterTypeType GetType() {return m_type;}
+ void SetType(nsMsgFilterTypeType type) {m_type = type;}
+ bool GetEnabled() {return m_enabled;}
+ void SetFilterScript(nsCString *filterName);
+
+ bool IsScript() {return (m_type &
+ (nsMsgFilterType::InboxJavaScript |
+ nsMsgFilterType::NewsJavaScript)) != 0;}
+
+ // filing routines.
+ nsresult SaveRule(nsIOutputStream *aStream);
+
+ int16_t GetVersion();
+#ifdef DEBUG
+ void Dump();
+#endif
+
+ nsresult ConvertMoveOrCopyToFolderValue(nsIMsgRuleAction *filterAction, nsCString &relativePath);
+ static const char *GetActionStr(nsMsgRuleActionType action);
+ static nsresult GetActionFilingStr(nsMsgRuleActionType action, nsCString &actionStr);
+ static nsMsgRuleActionType GetActionForFilingStr(nsCString &actionStr);
+protected:
+
+ /*
+ * Reporting function for filtering success/failure.
+ * Logging has to be enabled for the message to appear.
+ */
+ nsresult LogRuleHitGeneric(nsIMsgRuleAction *aFilterAction,
+ nsIMsgDBHdr *aMsgHdr,
+ nsresult aRcode,
+ const char *aErrmsg);
+
+ virtual ~nsMsgFilter();
+
+ nsMsgFilterTypeType m_type;
+ nsString m_filterName;
+ nsCString m_scriptFileName; // iff this filter is a script.
+ nsCString m_description;
+ nsCString m_unparsedBuffer;
+
+ bool m_enabled;
+ bool m_temporary;
+ bool m_unparseable;
+ nsIMsgFilterList *m_filterList; /* owning filter list */
+ nsCOMPtr<nsISupportsArray> m_termList; /* linked list of criteria terms */
+ nsCOMPtr<nsIMsgSearchScopeTerm> m_scope; /* default for mail rules is inbox, but news rules could
+ have a newsgroup - LDAP would be invalid */
+ nsTArray<nsCOMPtr<nsIMsgRuleAction> > m_actionList;
+ nsMsgSearchBoolExpression *m_expressionTree;
+ nsCOMPtr<nsIDateTimeFormat> mDateFormatter;
+};
+
+#endif
diff --git a/mailnews/base/search/src/nsMsgFilterList.cpp b/mailnews/base/search/src/nsMsgFilterList.cpp
new file mode 100644
index 000000000..d5b93fdc2
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilterList.cpp
@@ -0,0 +1,1198 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// this file implements the nsMsgFilterList interface
+
+#include "nsTextFormatter.h"
+
+#include "msgCore.h"
+#include "nsMsgFilterList.h"
+#include "nsMsgFilter.h"
+#include "nsIMsgFilterHitNotify.h"
+#include "nsMsgUtils.h"
+#include "nsMsgSearchTerm.h"
+#include "nsStringGlue.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgFilterService.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsMsgI18N.h"
+#include "nsMemory.h"
+#include "prmem.h"
+#include "mozilla/ArrayUtils.h"
+#include <ctype.h>
+
+// unicode "%s" format string
+static const char16_t unicodeFormatter[] = {
+ (char16_t)'%',
+ (char16_t)'s',
+ (char16_t)0,
+};
+
+// Marker for EOF or failure during read
+#define EOF_CHAR -1
+
+nsMsgFilterList::nsMsgFilterList() :
+ m_fileVersion(0)
+{
+ m_loggingEnabled = false;
+ m_startWritingToBuffer = false;
+ m_temporaryList = false;
+ m_curFilter = nullptr;
+}
+
+NS_IMPL_ADDREF(nsMsgFilterList)
+NS_IMPL_RELEASE(nsMsgFilterList)
+NS_IMPL_QUERY_INTERFACE(nsMsgFilterList, nsIMsgFilterList)
+
+NS_IMETHODIMP nsMsgFilterList::CreateFilter(const nsAString &name,class nsIMsgFilter **aFilter)
+{
+ NS_ENSURE_ARG_POINTER(aFilter);
+
+ nsMsgFilter *filter = new nsMsgFilter;
+ NS_ENSURE_TRUE(filter, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ADDREF(*aFilter = filter);
+
+ filter->SetFilterName(name);
+ filter->SetFilterList(this);
+
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsMsgFilterList, LoggingEnabled, bool, m_loggingEnabled)
+
+NS_IMETHODIMP nsMsgFilterList::GetFolder(nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ *aFolder = m_folder;
+ NS_IF_ADDREF(*aFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::SetFolder(nsIMsgFolder *aFolder)
+{
+ m_folder = aFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::SaveToFile(nsIOutputStream *stream)
+{
+ if (!stream)
+ return NS_ERROR_NULL_POINTER;
+ return SaveTextFilters(stream);
+}
+
+#define LOG_HEADER "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style type=\"text/css\">body{font-family:Consolas,\"Lucida Console\",Monaco,\"Courier New\",Courier,monospace;font-size:small}</style>\n</head>\n<body>\n"
+#define LOG_HEADER_LEN (strlen(LOG_HEADER))
+
+nsresult nsMsgFilterList::EnsureLogFile(nsIFile *file)
+{
+ bool exists;
+ nsresult rv = file->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists) {
+ rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ int64_t fileSize;
+ rv = file->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // write the header at the start
+ if (fileSize == 0)
+ {
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = MsgGetFileStream(file, getter_AddRefs(outputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t writeCount;
+ rv = outputStream->Write(LOG_HEADER, LOG_HEADER_LEN, &writeCount);
+ NS_ASSERTION(writeCount == LOG_HEADER_LEN, "failed to write out log header");
+ NS_ENSURE_SUCCESS(rv, rv);
+ outputStream->Close();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::TruncateLog()
+{
+ // This will flush and close the stream.
+ nsresult rv = SetLogStream(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIFile> file;
+ rv = GetLogFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ file->Remove(false);
+
+ return EnsureLogFile(file);
+}
+
+NS_IMETHODIMP nsMsgFilterList::ClearLog()
+{
+ bool loggingEnabled = m_loggingEnabled;
+
+ // disable logging while clearing
+ m_loggingEnabled = false;
+
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ TruncateLog();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to truncate filter log");
+
+ m_loggingEnabled = loggingEnabled;
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgFilterList::GetLogFile(nsIFile **aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ // XXX todo
+ // the path to the log file won't change
+ // should we cache it?
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsresult rv = GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString type;
+ rv = server->GetType(type);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isServer = false;
+ rv = folder->GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // for news folders (not servers), the filter file is
+ // mcom.test.dat
+ // where the summary file is
+ // mcom.test.msf
+ // since the log is an html file we make it
+ // mcom.test.htm
+ if (type.Equals("nntp") && !isServer)
+ {
+ nsCOMPtr<nsIFile> thisFolder;
+ rv = m_folder->GetFilePath(getter_AddRefs(thisFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> filterLogFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = filterLogFile->InitWithFile(thisFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // NOTE:
+ // we don't we need to call NS_MsgHashIfNecessary()
+ // it's already been hashed, if necessary
+ nsAutoString filterLogName;
+ rv = filterLogFile->GetLeafName(filterLogName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ filterLogName.Append(NS_LITERAL_STRING(".htm"));
+
+ rv = filterLogFile->SetLeafName(filterLogName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*aFile = filterLogFile);
+ }
+ else {
+ rv = server->GetLocalPath(aFile);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = (*aFile)->AppendNative(NS_LITERAL_CSTRING("filterlog.html"));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ return EnsureLogFile(*aFile);
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::GetLogURL(nsACString &aLogURL)
+{
+ nsCOMPtr <nsIFile> file;
+ nsresult rv = GetLogFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = NS_GetURLSpecFromFile(file, aLogURL);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return !aLogURL.IsEmpty() ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::SetLogStream(nsIOutputStream *aLogStream)
+{
+ // if there is a log stream already, close it
+ if (m_logStream) {
+ // will flush
+ nsresult rv = m_logStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ m_logStream = aLogStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::GetLogStream(nsIOutputStream **aLogStream)
+{
+ NS_ENSURE_ARG_POINTER(aLogStream);
+
+ nsresult rv;
+
+ if (!m_logStream) {
+ nsCOMPtr <nsIFile> logFile;
+ rv = GetLogFile(getter_AddRefs(logFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // append to the end of the log file
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_logStream),
+ logFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_APPEND,
+ 0666);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!m_logStream)
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ADDREF(*aLogStream = m_logStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::ApplyFiltersToHdr(nsMsgFilterTypeType filterType,
+ nsIMsgDBHdr *msgHdr,
+ nsIMsgFolder *folder,
+ nsIMsgDatabase *db,
+ const char*headers,
+ uint32_t headersSize,
+ nsIMsgFilterHitNotify *listener,
+ nsIMsgWindow *msgWindow)
+{
+ nsCOMPtr<nsIMsgFilter> filter;
+ uint32_t filterCount = 0;
+ nsresult rv = GetFilterCount(&filterCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgSearchScopeTerm* scope = new nsMsgSearchScopeTerm(nullptr, nsMsgSearchScope::offlineMail, folder);
+ scope->AddRef();
+ if (!scope) return NS_ERROR_OUT_OF_MEMORY;
+
+ for (uint32_t filterIndex = 0; filterIndex < filterCount; filterIndex++)
+ {
+ if (NS_SUCCEEDED(GetFilterAt(filterIndex, getter_AddRefs(filter))))
+ {
+ bool isEnabled;
+ nsMsgFilterTypeType curFilterType;
+
+ filter->GetEnabled(&isEnabled);
+ if (!isEnabled)
+ continue;
+
+ filter->GetFilterType(&curFilterType);
+ if (curFilterType & filterType)
+ {
+ nsresult matchTermStatus = NS_OK;
+ bool result;
+
+ filter->SetScope(scope);
+ matchTermStatus = filter->MatchHdr(msgHdr, folder, db, headers, headersSize, &result);
+ filter->SetScope(nullptr);
+ if (NS_SUCCEEDED(matchTermStatus) && result && listener)
+ {
+ bool applyMore = true;
+
+ rv = listener->ApplyFilterHit(filter, msgWindow, &applyMore);
+ if (NS_FAILED(rv) || !applyMore)
+ break;
+ }
+ }
+ }
+ }
+ scope->Release();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::SetDefaultFile(nsIFile *aFile)
+{
+ m_defaultFile = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::GetDefaultFile(nsIFile **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ NS_IF_ADDREF(*aResult = m_defaultFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::SaveToDefaultFile()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return filterService->SaveFilterList(this, m_defaultFile);
+}
+
+typedef struct
+{
+ nsMsgFilterFileAttribValue attrib;
+ const char *attribName;
+} FilterFileAttribEntry;
+
+static FilterFileAttribEntry FilterFileAttribTable[] =
+{
+ {nsIMsgFilterList::attribNone, ""},
+ {nsIMsgFilterList::attribVersion, "version"},
+ {nsIMsgFilterList::attribLogging, "logging"},
+ {nsIMsgFilterList::attribName, "name"},
+ {nsIMsgFilterList::attribEnabled, "enabled"},
+ {nsIMsgFilterList::attribDescription, "description"},
+ {nsIMsgFilterList::attribType, "type"},
+ {nsIMsgFilterList::attribScriptFile, "scriptName"},
+ {nsIMsgFilterList::attribAction, "action"},
+ {nsIMsgFilterList::attribActionValue, "actionValue"},
+ {nsIMsgFilterList::attribCondition, "condition"},
+ {nsIMsgFilterList::attribCustomId, "customId"},
+};
+
+static const unsigned int sNumFilterFileAttribTable =
+ MOZ_ARRAY_LENGTH(FilterFileAttribTable);
+
+// If we want to buffer file IO, wrap it in here.
+int nsMsgFilterList::ReadChar(nsIInputStream *aStream)
+{
+ char newChar;
+ uint32_t bytesRead;
+ nsresult rv = aStream->Read(&newChar, 1, &bytesRead);
+ if (NS_FAILED(rv) || !bytesRead)
+ return EOF_CHAR;
+ uint64_t bytesAvailable;
+ rv = aStream->Available(&bytesAvailable);
+ if (NS_FAILED(rv))
+ return EOF_CHAR;
+ else
+ {
+ if (m_startWritingToBuffer)
+ m_unparsedFilterBuffer.Append(newChar);
+ return (unsigned char)newChar; // Make sure the char is unsigned.
+ }
+}
+
+int nsMsgFilterList::SkipWhitespace(nsIInputStream *aStream)
+{
+ int ch;
+ do
+ {
+ ch = ReadChar(aStream);
+ } while (!(ch & 0x80) && isspace(ch)); // isspace can crash with non-ascii input
+
+ return ch;
+}
+
+bool nsMsgFilterList::StrToBool(nsCString &str)
+{
+ return str.Equals("yes") ;
+}
+
+int nsMsgFilterList::LoadAttrib(nsMsgFilterFileAttribValue &attrib, nsIInputStream *aStream)
+{
+ char attribStr[100];
+ int curChar;
+ attrib = nsIMsgFilterList::attribNone;
+
+ curChar = SkipWhitespace(aStream);
+ int i;
+ for (i = 0; i + 1 < (int)(sizeof(attribStr)); )
+ {
+ if (curChar == EOF_CHAR || (!(curChar & 0x80) && isspace(curChar)) || curChar == '=')
+ break;
+ attribStr[i++] = curChar;
+ curChar = ReadChar(aStream);
+ }
+ attribStr[i] = '\0';
+ for (unsigned int tableIndex = 0; tableIndex < sNumFilterFileAttribTable; tableIndex++)
+ {
+ if (!PL_strcasecmp(attribStr, FilterFileAttribTable[tableIndex].attribName))
+ {
+ attrib = FilterFileAttribTable[tableIndex].attrib;
+ break;
+ }
+ }
+ return curChar;
+}
+
+const char *nsMsgFilterList::GetStringForAttrib(nsMsgFilterFileAttribValue attrib)
+{
+ for (unsigned int tableIndex = 0; tableIndex < sNumFilterFileAttribTable; tableIndex++)
+ {
+ if (attrib == FilterFileAttribTable[tableIndex].attrib)
+ return FilterFileAttribTable[tableIndex].attribName;
+ }
+ return nullptr;
+}
+
+nsresult nsMsgFilterList::LoadValue(nsCString &value, nsIInputStream *aStream)
+{
+ nsAutoCString valueStr;
+ int curChar;
+ value = "";
+ curChar = SkipWhitespace(aStream);
+ if (curChar != '"')
+ {
+ NS_ASSERTION(false, "expecting quote as start of value");
+ return NS_MSG_FILTER_PARSE_ERROR;
+ }
+ curChar = ReadChar(aStream);
+ do
+ {
+ if (curChar == '\\')
+ {
+ int nextChar = ReadChar(aStream);
+ if (nextChar == '"')
+ curChar = '"';
+ else if (nextChar == '\\') // replace "\\" with "\"
+ {
+ valueStr += curChar;
+ curChar = ReadChar(aStream);
+ }
+ else
+ {
+ valueStr += curChar;
+ curChar = nextChar;
+ }
+ }
+ else
+ {
+ if (curChar == EOF_CHAR || curChar == '"' || curChar == '\n' || curChar == '\r')
+ {
+ value += valueStr;
+ break;
+ }
+ }
+ valueStr += curChar;
+ curChar = ReadChar(aStream);
+ }
+ while (curChar != EOF_CHAR);
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::LoadTextFilters(nsIInputStream *aStream)
+{
+ nsresult err = NS_OK;
+ uint64_t bytesAvailable;
+
+ nsCOMPtr<nsIInputStream> bufStream;
+ err = NS_NewBufferedInputStream(getter_AddRefs(bufStream), aStream, FILE_IO_BUFFER_SIZE);
+ NS_ENSURE_SUCCESS(err, err);
+
+ nsMsgFilterFileAttribValue attrib;
+ nsCOMPtr<nsIMsgRuleAction> currentFilterAction;
+ // We'd really like to move lot's of these into the objects that they refer to.
+ do
+ {
+ nsAutoCString value;
+ nsresult intToStringResult;
+
+ int curChar;
+ curChar = LoadAttrib(attrib, bufStream);
+ if (curChar == EOF_CHAR) //reached eof
+ break;
+ err = LoadValue(value, bufStream);
+ if (NS_FAILED(err))
+ break;
+
+ switch(attrib)
+ {
+ case nsIMsgFilterList::attribNone:
+ if (m_curFilter)
+ m_curFilter->SetUnparseable(true);
+ break;
+ case nsIMsgFilterList::attribVersion:
+ m_fileVersion = value.ToInteger(&intToStringResult);
+ if (NS_FAILED(intToStringResult))
+ {
+ attrib = nsIMsgFilterList::attribNone;
+ NS_ASSERTION(false, "error parsing filter file version");
+ }
+ break;
+ case nsIMsgFilterList::attribLogging:
+ m_loggingEnabled = StrToBool(value);
+ m_unparsedFilterBuffer.Truncate(); //we are going to buffer each filter as we read them, make sure no garbage is there
+ m_startWritingToBuffer = true; //filters begin now
+ break;
+ case nsIMsgFilterList::attribName: //every filter starts w/ a name
+ {
+ if (m_curFilter)
+ {
+ int32_t nextFilterStartPos = m_unparsedFilterBuffer.RFind("name");
+
+ nsAutoCString nextFilterPart;
+ nextFilterPart = Substring(m_unparsedFilterBuffer, nextFilterStartPos, m_unparsedFilterBuffer.Length());
+ m_unparsedFilterBuffer.SetLength(nextFilterStartPos);
+
+ bool unparseableFilter;
+ m_curFilter->GetUnparseable(&unparseableFilter);
+ if (unparseableFilter)
+ {
+ m_curFilter->SetUnparsedBuffer(m_unparsedFilterBuffer);
+ m_curFilter->SetEnabled(false); //disable the filter because we don't know how to apply it
+ }
+ m_unparsedFilterBuffer = nextFilterPart;
+ }
+ nsMsgFilter *filter = new nsMsgFilter;
+ if (filter == nullptr)
+ {
+ err = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+ filter->SetFilterList(static_cast<nsIMsgFilterList*>(this));
+ if (m_fileVersion == k45Version)
+ {
+ nsAutoString unicodeStr;
+ err = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(),
+ value, unicodeStr);
+ if (NS_FAILED(err))
+ break;
+
+ filter->SetFilterName(unicodeStr);
+ }
+ else
+ {
+ // ### fix me - this is silly.
+ char16_t *unicodeString =
+ nsTextFormatter::smprintf(unicodeFormatter, value.get());
+ filter->SetFilterName(nsDependentString(unicodeString));
+ nsTextFormatter::smprintf_free(unicodeString);
+ }
+ m_curFilter = filter;
+ m_filters.AppendElement(filter);
+ }
+ break;
+ case nsIMsgFilterList::attribEnabled:
+ if (m_curFilter)
+ m_curFilter->SetEnabled(StrToBool(value));
+ break;
+ case nsIMsgFilterList::attribDescription:
+ if (m_curFilter)
+ m_curFilter->SetFilterDesc(value);
+ break;
+ case nsIMsgFilterList::attribType:
+ if (m_curFilter)
+ {
+ // Older versions of filters didn't have the ability to turn on/off the
+ // manual filter context, so default manual to be on in that case
+ int32_t filterType = value.ToInteger(&intToStringResult);
+ if (m_fileVersion < kManualContextVersion)
+ filterType |= nsMsgFilterType::Manual;
+ m_curFilter->SetType((nsMsgFilterTypeType) filterType);
+ }
+ break;
+ case nsIMsgFilterList::attribScriptFile:
+ if (m_curFilter)
+ m_curFilter->SetFilterScript(&value);
+ break;
+ case nsIMsgFilterList::attribAction:
+ if (m_curFilter)
+ {
+ nsMsgRuleActionType actionType = nsMsgFilter::GetActionForFilingStr(value);
+ if (actionType == nsMsgFilterAction::None)
+ m_curFilter->SetUnparseable(true);
+ else
+ {
+ err = m_curFilter->CreateAction(getter_AddRefs(currentFilterAction));
+ NS_ENSURE_SUCCESS(err, err);
+ currentFilterAction->SetType(actionType);
+ m_curFilter->AppendAction(currentFilterAction);
+ }
+ }
+ break;
+ case nsIMsgFilterList::attribActionValue:
+ if (m_curFilter && currentFilterAction)
+ {
+ nsMsgRuleActionType type;
+ currentFilterAction->GetType(&type);
+ if (type == nsMsgFilterAction::MoveToFolder ||
+ type == nsMsgFilterAction::CopyToFolder)
+ err = m_curFilter->ConvertMoveOrCopyToFolderValue(currentFilterAction, value);
+ else if (type == nsMsgFilterAction::ChangePriority)
+ {
+ nsMsgPriorityValue outPriority;
+ nsresult res = NS_MsgGetPriorityFromString(value.get(), outPriority);
+ if (NS_SUCCEEDED(res))
+ currentFilterAction->SetPriority(outPriority);
+ else
+ NS_ASSERTION(false, "invalid priority in filter file");
+ }
+ else if (type == nsMsgFilterAction::Label)
+ {
+ // upgrade label to corresponding tag/keyword
+ nsresult res;
+ int32_t labelInt = value.ToInteger(&res);
+ if (NS_SUCCEEDED(res))
+ {
+ nsAutoCString keyword("$label");
+ keyword.Append('0' + labelInt);
+ currentFilterAction->SetType(nsMsgFilterAction::AddTag);
+ currentFilterAction->SetStrValue(keyword);
+ }
+ }
+ else if (type == nsMsgFilterAction::JunkScore)
+ {
+ nsresult res;
+ int32_t junkScore = value.ToInteger(&res);
+ if (NS_SUCCEEDED(res))
+ currentFilterAction->SetJunkScore(junkScore);
+ }
+ else if (type == nsMsgFilterAction::Forward ||
+ type == nsMsgFilterAction::Reply ||
+ type == nsMsgFilterAction::AddTag ||
+ type == nsMsgFilterAction::Custom)
+ {
+ currentFilterAction->SetStrValue(value);
+ }
+ }
+ break;
+ case nsIMsgFilterList::attribCondition:
+ if (m_curFilter)
+ {
+ if (m_fileVersion == k45Version)
+ {
+ nsAutoString unicodeStr;
+ err = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(),
+ value, unicodeStr);
+ if (NS_FAILED(err))
+ break;
+
+ char *utf8 = ToNewUTF8String(unicodeStr);
+ value.Assign(utf8);
+ free(utf8);
+ }
+ err = ParseCondition(m_curFilter, value.get());
+ if (err == NS_ERROR_INVALID_ARG)
+ err = m_curFilter->SetUnparseable(true);
+ NS_ENSURE_SUCCESS(err, err);
+ }
+ break;
+ case nsIMsgFilterList::attribCustomId:
+ if (m_curFilter && currentFilterAction)
+ {
+ err = currentFilterAction->SetCustomId(value);
+ NS_ENSURE_SUCCESS(err, err);
+ }
+ break;
+
+ }
+ } while (NS_SUCCEEDED(bufStream->Available(&bytesAvailable)));
+
+ if (m_curFilter)
+ {
+ bool unparseableFilter;
+ m_curFilter->GetUnparseable(&unparseableFilter);
+ if (unparseableFilter)
+ {
+ m_curFilter->SetUnparsedBuffer(m_unparsedFilterBuffer);
+ m_curFilter->SetEnabled(false); //disable the filter because we don't know how to apply it
+ }
+ }
+
+ return err;
+}
+
+// parse condition like "(subject, contains, fred) AND (body, isn't, "foo)")"
+// values with close parens will be quoted.
+// what about values with close parens and quotes? e.g., (body, isn't, "foo")")
+// I guess interior quotes will need to be escaped - ("foo\")")
+// which will get written out as (\"foo\\")\") and read in as ("foo\")"
+// ALL means match all messages.
+NS_IMETHODIMP nsMsgFilterList::ParseCondition(nsIMsgFilter *aFilter, const char *aCondition)
+{
+ NS_ENSURE_ARG_POINTER(aFilter);
+
+ bool done = false;
+ nsresult err = NS_OK;
+ const char *curPtr = aCondition;
+ if (!strcmp(aCondition, "ALL"))
+ {
+ nsMsgSearchTerm *newTerm = new nsMsgSearchTerm;
+
+ if (newTerm)
+ {
+ newTerm->m_matchAll = true;
+ aFilter->AppendTerm(newTerm);
+ }
+ return (newTerm) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ while (!done)
+ {
+ // insert code to save the boolean operator if there is one for this search term....
+ const char *openParen = PL_strchr(curPtr, '(');
+ const char *orTermPos = PL_strchr(curPtr, 'O'); // determine if an "OR" appears b4 the openParen...
+ bool ANDTerm = true;
+ if (orTermPos && orTermPos < openParen) // make sure OR term falls before the '('
+ ANDTerm = false;
+
+ char *termDup = nullptr;
+ if (openParen)
+ {
+ bool foundEndTerm = false;
+ bool inQuote = false;
+ for (curPtr = openParen +1; *curPtr; curPtr++)
+ {
+ if (*curPtr == '\\' && *(curPtr + 1) == '"')
+ curPtr++;
+ else if (*curPtr == ')' && !inQuote)
+ {
+ foundEndTerm = true;
+ break;
+ }
+ else if (*curPtr == '"')
+ inQuote = !inQuote;
+ }
+ if (foundEndTerm)
+ {
+ int termLen = curPtr - openParen - 1;
+ termDup = (char *) PR_Malloc(termLen + 1);
+ if (termDup)
+ {
+ PL_strncpy(termDup, openParen + 1, termLen + 1);
+ termDup[termLen] = '\0';
+ }
+ else
+ {
+ err = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+ }
+ }
+ else
+ break;
+ if (termDup)
+ {
+ nsMsgSearchTerm *newTerm = new nsMsgSearchTerm;
+
+ if (newTerm)
+ {
+ /* Invert nsMsgSearchTerm::EscapeQuotesInStr() */
+ for (char *to = termDup, *from = termDup;;)
+ {
+ if (*from == '\\' && from[1] == '"') from++;
+ if (!(*to++ = *from++)) break;
+ }
+ newTerm->m_booleanOp = (ANDTerm) ? nsMsgSearchBooleanOp::BooleanAND
+ : nsMsgSearchBooleanOp::BooleanOR;
+
+ err = newTerm->DeStreamNew(termDup, PL_strlen(termDup));
+ NS_ENSURE_SUCCESS(err, err);
+ aFilter->AppendTerm(newTerm);
+ }
+ PR_FREEIF(termDup);
+ }
+ else
+ break;
+ }
+ return err;
+}
+
+nsresult nsMsgFilterList::WriteIntAttr(nsMsgFilterFileAttribValue attrib, int value, nsIOutputStream *aStream)
+{
+ nsresult rv = NS_OK;
+ const char *attribStr = GetStringForAttrib(attrib);
+ if (attribStr)
+ {
+ uint32_t bytesWritten;
+ nsAutoCString writeStr(attribStr);
+ writeStr.AppendLiteral("=\"");
+ writeStr.AppendInt(value);
+ writeStr.AppendLiteral("\"" MSG_LINEBREAK);
+ rv = aStream->Write(writeStr.get(), writeStr.Length(), &bytesWritten);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::WriteStrAttr(nsMsgFilterFileAttribValue attrib,
+ const char *aStr, nsIOutputStream *aStream)
+{
+ nsresult rv = NS_OK;
+ if (aStr && *aStr && aStream) // only proceed if we actually have a string to write out.
+ {
+ char *escapedStr = nullptr;
+ if (PL_strchr(aStr, '"'))
+ escapedStr = nsMsgSearchTerm::EscapeQuotesInStr(aStr);
+
+ const char *attribStr = GetStringForAttrib(attrib);
+ if (attribStr)
+ {
+ uint32_t bytesWritten;
+ nsAutoCString writeStr(attribStr);
+ writeStr.AppendLiteral("=\"");
+ writeStr.Append((escapedStr) ? escapedStr : aStr);
+ writeStr.AppendLiteral("\"" MSG_LINEBREAK);
+ rv = aStream->Write(writeStr.get(), writeStr.Length(), &bytesWritten);
+ }
+ PR_Free(escapedStr);
+ }
+ return rv;
+}
+
+nsresult nsMsgFilterList::WriteBoolAttr(nsMsgFilterFileAttribValue attrib, bool boolVal, nsIOutputStream *aStream)
+{
+ return WriteStrAttr(attrib, (boolVal) ? "yes" : "no", aStream);
+}
+
+nsresult
+nsMsgFilterList::WriteWstrAttr(nsMsgFilterFileAttribValue attrib,
+ const char16_t *aFilterName, nsIOutputStream *aStream)
+{
+ WriteStrAttr(attrib, NS_ConvertUTF16toUTF8(aFilterName).get(), aStream);
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::SaveTextFilters(nsIOutputStream *aStream)
+{
+ uint32_t filterCount = 0;
+ nsresult err = GetFilterCount(&filterCount);
+ NS_ENSURE_SUCCESS(err, err);
+
+ err = WriteIntAttr(nsIMsgFilterList::attribVersion, kFileVersion, aStream);
+ NS_ENSURE_SUCCESS(err, err);
+ err = WriteBoolAttr(nsIMsgFilterList::attribLogging, m_loggingEnabled, aStream);
+ NS_ENSURE_SUCCESS(err, err);
+ for (uint32_t i = 0; i < filterCount; i ++)
+ {
+ nsCOMPtr<nsIMsgFilter> filter;
+ if (NS_SUCCEEDED(GetFilterAt(i, getter_AddRefs(filter))) && filter)
+ {
+ filter->SetFilterList(this);
+
+ // if the filter is temporary, don't write it to disk
+ bool isTemporary;
+ err = filter->GetTemporary(&isTemporary);
+ if (NS_SUCCEEDED(err) && !isTemporary) {
+ err = filter->SaveToTextFile(aStream);
+ if (NS_FAILED(err))
+ break;
+ }
+ }
+ else
+ break;
+ }
+ if (NS_SUCCEEDED(err))
+ m_arbitraryHeaders.Truncate();
+ return err;
+}
+
+nsMsgFilterList::~nsMsgFilterList()
+{
+}
+
+nsresult nsMsgFilterList::Close()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsMsgFilterList::GetFilterCount(uint32_t *pCount)
+{
+ NS_ENSURE_ARG_POINTER(pCount);
+
+ *pCount = m_filters.Length();
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::GetFilterAt(uint32_t filterIndex, nsIMsgFilter **filter)
+{
+ NS_ENSURE_ARG_POINTER(filter);
+
+ uint32_t filterCount = 0;
+ GetFilterCount(&filterCount);
+ NS_ENSURE_ARG(filterIndex < filterCount);
+
+ NS_IF_ADDREF(*filter = m_filters[filterIndex]);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFilterList::GetFilterNamed(const nsAString &aName, nsIMsgFilter **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ uint32_t count = 0;
+ nsresult rv = GetFilterCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = nullptr;
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIMsgFilter> filter;
+ rv = GetFilterAt(i, getter_AddRefs(filter));
+ if (NS_FAILED(rv)) continue;
+
+ nsString filterName;
+ filter->GetFilterName(filterName);
+ if (filterName.Equals(aName))
+ {
+ *aResult = filter;
+ break;
+ }
+ }
+
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::SetFilterAt(uint32_t filterIndex, nsIMsgFilter *filter)
+{
+ m_filters[filterIndex] = filter;
+ return NS_OK;
+}
+
+
+nsresult nsMsgFilterList::RemoveFilterAt(uint32_t filterIndex)
+{
+ m_filters.RemoveElementAt(filterIndex);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFilterList::RemoveFilter(nsIMsgFilter *aFilter)
+{
+ m_filters.RemoveElement(aFilter);
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::InsertFilterAt(uint32_t filterIndex, nsIMsgFilter *aFilter)
+{
+ if (!m_temporaryList)
+ aFilter->SetFilterList(this);
+ m_filters.InsertElementAt(filterIndex, aFilter);
+
+ return NS_OK;
+}
+
+// Attempt to move the filter at index filterIndex in the specified direction.
+// If motion not possible in that direction, we still return success.
+// We could return an error if the FE's want to beep or something.
+nsresult nsMsgFilterList::MoveFilterAt(uint32_t filterIndex,
+ nsMsgFilterMotionValue motion)
+{
+ NS_ENSURE_ARG((motion == nsMsgFilterMotion::up) ||
+ (motion == nsMsgFilterMotion::down));
+
+ uint32_t filterCount = 0;
+ nsresult rv = GetFilterCount(&filterCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_ARG(filterIndex < filterCount);
+
+ uint32_t newIndex = filterIndex;
+
+ if (motion == nsMsgFilterMotion::up)
+ {
+ // are we already at the top?
+ if (filterIndex == 0)
+ return NS_OK;
+
+ newIndex = filterIndex - 1;
+ }
+ else if (motion == nsMsgFilterMotion::down)
+ {
+ // are we already at the bottom?
+ if (filterIndex == filterCount - 1)
+ return NS_OK;
+
+ newIndex = filterIndex + 1;
+ }
+
+ nsCOMPtr<nsIMsgFilter> tempFilter1;
+ rv = GetFilterAt(newIndex, getter_AddRefs(tempFilter1));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFilter> tempFilter2;
+ rv = GetFilterAt(filterIndex, getter_AddRefs(tempFilter2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetFilterAt(newIndex, tempFilter2);
+ SetFilterAt(filterIndex, tempFilter1);
+
+ return NS_OK;
+}
+
+nsresult nsMsgFilterList::MoveFilter(nsIMsgFilter *aFilter,
+ nsMsgFilterMotionValue motion)
+{
+ size_t filterIndex = m_filters.IndexOf(aFilter, 0);
+ NS_ENSURE_ARG(filterIndex != m_filters.NoIndex);
+
+ return MoveFilterAt(filterIndex, motion);
+}
+
+nsresult
+nsMsgFilterList::GetVersion(int16_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_fileVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::MatchOrChangeFilterTarget(const nsACString &oldFolderUri, const nsACString &newFolderUri, bool caseInsensitive, bool *found)
+{
+ NS_ENSURE_ARG_POINTER(found);
+
+ uint32_t numFilters = 0;
+ nsresult rv = GetFilterCount(&numFilters);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFilter> filter;
+ nsCString folderUri;
+ *found = false;
+ for (uint32_t index = 0; index < numFilters; index++)
+ {
+ rv = GetFilterAt(index, getter_AddRefs(filter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numActions;
+ rv = filter->GetActionCount(&numActions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++)
+ {
+ nsCOMPtr<nsIMsgRuleAction> filterAction;
+ rv = filter->GetActionAt(actionIndex, getter_AddRefs(filterAction));
+ if (NS_FAILED(rv) || !filterAction)
+ continue;
+
+ nsMsgRuleActionType actionType;
+ if (NS_FAILED(filterAction->GetType(&actionType)))
+ continue;
+
+ if (actionType == nsMsgFilterAction::MoveToFolder ||
+ actionType == nsMsgFilterAction::CopyToFolder)
+ {
+ rv = filterAction->GetTargetFolderUri(folderUri);
+ if (NS_SUCCEEDED(rv) && !folderUri.IsEmpty())
+ {
+ bool matchFound = false;
+ if (caseInsensitive)
+ {
+ if (folderUri.Equals(oldFolderUri, nsCaseInsensitiveCStringComparator())) //local
+ matchFound = true;
+ }
+ else
+ {
+ if (folderUri.Equals(oldFolderUri)) //imap
+ matchFound = true;
+ }
+ if (matchFound)
+ {
+ *found = true;
+ //if we just want to match the uri's, newFolderUri will be null
+ if (!newFolderUri.IsEmpty())
+ {
+ rv = filterAction->SetTargetFolderUri(newFolderUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+// this would only return true if any filter was on "any header", which we
+// don't support in 6.x
+NS_IMETHODIMP nsMsgFilterList::GetShouldDownloadAllHeaders(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = false;
+ return NS_OK;
+}
+
+// leaves m_arbitraryHeaders filed in with the arbitrary headers.
+nsresult nsMsgFilterList::ComputeArbitraryHeaders()
+{
+ NS_ENSURE_TRUE (m_arbitraryHeaders.IsEmpty(), NS_OK);
+
+ uint32_t numFilters = 0;
+ nsresult rv = GetFilterCount(&numFilters);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFilter> filter;
+ nsMsgSearchAttribValue attrib;
+ nsCString arbitraryHeader;
+ for (uint32_t index = 0; index < numFilters; index++)
+ {
+ rv = GetFilterAt(index, getter_AddRefs(filter));
+ if (!(NS_SUCCEEDED(rv) && filter)) continue;
+
+ nsCOMPtr <nsISupportsArray> searchTerms;
+ uint32_t numSearchTerms=0;
+ filter->GetSearchTerms(getter_AddRefs(searchTerms));
+ if (searchTerms)
+ searchTerms->Count(&numSearchTerms);
+ for (uint32_t i = 0; i < numSearchTerms; i++)
+ {
+ filter->GetTerm(i, &attrib, nullptr, nullptr, nullptr, arbitraryHeader);
+ if (!arbitraryHeader.IsEmpty())
+ {
+ if (m_arbitraryHeaders.IsEmpty())
+ m_arbitraryHeaders.Assign(arbitraryHeader);
+ else if (m_arbitraryHeaders.Find(arbitraryHeader, CaseInsensitiveCompare) == -1)
+ {
+ m_arbitraryHeaders.Append(" ");
+ m_arbitraryHeaders.Append(arbitraryHeader);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::GetArbitraryHeaders(nsACString &aResult)
+{
+ ComputeArbitraryHeaders();
+ aResult = m_arbitraryHeaders;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterList::FlushLogIfNecessary()
+{
+ // only flush the log if we are logging
+ bool loggingEnabled = false;
+ nsresult rv = GetLoggingEnabled(&loggingEnabled);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (loggingEnabled)
+ {
+ nsCOMPtr <nsIOutputStream> logStream;
+ rv = GetLogStream(getter_AddRefs(logStream));
+ if (NS_SUCCEEDED(rv) && logStream) {
+ rv = logStream->Flush();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return rv;
+}
+// ------------ End FilterList methods ------------------
diff --git a/mailnews/base/search/src/nsMsgFilterList.h b/mailnews/base/search/src/nsMsgFilterList.h
new file mode 100644
index 000000000..2bb441d38
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilterList.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgFilterList_H_
+#define _nsMsgFilterList_H_
+
+#include "nscore.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgFilterList.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIFile.h"
+#include "nsIOutputStream.h"
+
+const int16_t kFileVersion = 9;
+const int16_t kManualContextVersion = 9;
+const int16_t k60Beta1Version = 7;
+const int16_t k45Version = 6;
+
+////////////////////////////////////////////////////////////////////////////////////////
+// The Msg Filter List is an interface designed to make accessing filter lists
+// easier. Clients typically open a filter list and either enumerate the filters,
+// or add new filters, or change the order around...
+//
+////////////////////////////////////////////////////////////////////////////////////////
+
+class nsIMsgFilter;
+class nsMsgFilter;
+
+class nsMsgFilterList : public nsIMsgFilterList
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGFILTERLIST
+
+ nsMsgFilterList();
+
+ nsresult Close();
+ nsresult LoadTextFilters(nsIInputStream *aStream);
+
+ bool m_temporaryList;
+
+protected:
+ virtual ~nsMsgFilterList();
+
+ nsresult ComputeArbitraryHeaders();
+ nsresult SaveTextFilters(nsIOutputStream *aStream);
+ // file streaming methods
+ int ReadChar(nsIInputStream *aStream);
+ int SkipWhitespace(nsIInputStream *aStream);
+ bool StrToBool(nsCString &str);
+ int LoadAttrib(nsMsgFilterFileAttribValue &attrib, nsIInputStream *aStream);
+ const char *GetStringForAttrib(nsMsgFilterFileAttribValue attrib);
+ nsresult LoadValue(nsCString &value, nsIInputStream *aStream);
+ int16_t m_fileVersion;
+ bool m_loggingEnabled;
+ bool m_startWritingToBuffer; //tells us when to start writing one whole filter to m_unparsedBuffer
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsMsgFilter *m_curFilter; // filter we're filing in or out(?)
+ nsCString m_filterFileName;
+ nsTArray<nsCOMPtr<nsIMsgFilter> > m_filters;
+ nsCString m_arbitraryHeaders;
+ nsCOMPtr<nsIFile> m_defaultFile;
+ nsCString m_unparsedFilterBuffer; //holds one entire filter unparsed
+
+private:
+ nsresult TruncateLog();
+ nsresult GetLogFile(nsIFile **aFile);
+ nsresult EnsureLogFile(nsIFile *file);
+ nsCOMPtr<nsIOutputStream> m_logStream;
+};
+
+#endif
diff --git a/mailnews/base/search/src/nsMsgFilterService.cpp b/mailnews/base/search/src/nsMsgFilterService.cpp
new file mode 100644
index 000000000..c8f52de5a
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilterService.cpp
@@ -0,0 +1,1216 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// this file implements the nsMsgFilterService interface
+
+#include "msgCore.h"
+#include "nsMsgFilterService.h"
+#include "nsMsgFilterList.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIPrompt.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIRDFService.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgCopyService.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsIMsgComposeService.h"
+#include "nsMsgCompCID.h"
+#include "nsNetUtil.h"
+#include "nsMsgUtils.h"
+#include "nsIMutableArray.h"
+#include "nsIMsgMailSession.h"
+#include "nsArrayUtils.h"
+#include "nsCOMArray.h"
+#include "nsIMsgFilterCustomAction.h"
+#include "nsArrayEnumerator.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgSearchCustomTerm.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgThread.h"
+#include "nsAutoPtr.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgOperationListener.h"
+#include "mozilla/Attributes.h"
+
+#define BREAK_IF_FAILURE(_rv, _text) if (NS_FAILED(_rv)) { \
+ NS_WARNING(_text); \
+ mFinalResult = _rv; \
+ break; \
+}
+
+#define CONTINUE_IF_FAILURE(_rv, _text) if (NS_FAILED(_rv)) { \
+ NS_WARNING(_text); \
+ mFinalResult = _rv; \
+ if (m_msgWindow && !ContinueExecutionPrompt()) \
+ return OnEndExecution(); \
+ continue; \
+}
+
+#define BREAK_IF_FALSE(_assertTrue, _text) if (!(_assertTrue)) { \
+ NS_WARNING(_text); \
+ mFinalResult = NS_ERROR_FAILURE; \
+ break; \
+}
+
+#define CONTINUE_IF_FALSE(_assertTrue, _text) if (!(_assertTrue)) { \
+ NS_WARNING(_text); \
+ mFinalResult = NS_ERROR_FAILURE; \
+ if (m_msgWindow && !ContinueExecutionPrompt()) \
+ return OnEndExecution(); \
+ continue; \
+}
+
+NS_IMPL_ISUPPORTS(nsMsgFilterService, nsIMsgFilterService)
+
+nsMsgFilterService::nsMsgFilterService()
+{
+}
+
+nsMsgFilterService::~nsMsgFilterService()
+{
+}
+
+NS_IMETHODIMP nsMsgFilterService::OpenFilterList(nsIFile *aFilterFile,
+ nsIMsgFolder *rootFolder,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList **resultFilterList)
+{
+ NS_ENSURE_ARG_POINTER(aFilterFile);
+ NS_ENSURE_ARG_POINTER(resultFilterList);
+
+ bool exists = false;
+ nsresult rv = aFilterFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ {
+ rv = aFilterFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIInputStream> fileStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), aFilterFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(fileStream, NS_ERROR_OUT_OF_MEMORY);
+
+ RefPtr<nsMsgFilterList> filterList = new nsMsgFilterList();
+ NS_ENSURE_TRUE(filterList, NS_ERROR_OUT_OF_MEMORY);
+ filterList->SetFolder(rootFolder);
+
+ // temporarily tell the filter where its file path is
+ filterList->SetDefaultFile(aFilterFile);
+
+ int64_t size = 0;
+ rv = aFilterFile->GetFileSize(&size);
+ if (NS_SUCCEEDED(rv) && size > 0)
+ rv = filterList->LoadTextFilters(fileStream);
+ fileStream->Close();
+ fileStream = nullptr;
+ if (NS_SUCCEEDED(rv))
+ {
+ int16_t version;
+ filterList->GetVersion(&version);
+ if (version != kFileVersion)
+ SaveFilterList(filterList, aFilterFile);
+ }
+ else
+ {
+ if (rv == NS_MSG_FILTER_PARSE_ERROR && aMsgWindow)
+ {
+ rv = BackUpFilterFile(aFilterFile, aMsgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aFilterFile->SetFileSize(0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return OpenFilterList(aFilterFile, rootFolder, aMsgWindow, resultFilterList);
+ }
+ else if (rv == NS_MSG_CUSTOM_HEADERS_OVERFLOW && aMsgWindow)
+ ThrowAlertMsg("filterCustomHeaderOverflow", aMsgWindow);
+ else if (rv == NS_MSG_INVALID_CUSTOM_HEADER && aMsgWindow)
+ ThrowAlertMsg("invalidCustomHeader", aMsgWindow);
+ }
+
+ NS_ADDREF(*resultFilterList = filterList);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgFilterService::CloseFilterList(nsIMsgFilterList *filterList)
+{
+ //NS_ASSERTION(false,"CloseFilterList doesn't do anything yet");
+ return NS_OK;
+}
+
+/* save without deleting */
+NS_IMETHODIMP nsMsgFilterService::SaveFilterList(nsIMsgFilterList *filterList, nsIFile *filterFile)
+{
+ NS_ENSURE_ARG_POINTER(filterFile);
+ NS_ENSURE_ARG_POINTER(filterList);
+
+ nsCOMPtr<nsIOutputStream> strm;
+ nsresult rv = MsgNewSafeBufferedFileOutputStream(getter_AddRefs(strm),
+ filterFile, -1, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filterList->SaveToFile(strm);
+
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(strm);
+ NS_ASSERTION(safeStream, "expected a safe output stream!");
+ if (safeStream) {
+ rv = safeStream->Finish();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to save filter file! possible data loss");
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgFilterService::CancelFilterList(nsIMsgFilterList *filterList)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsMsgFilterService::BackUpFilterFile(nsIFile *aFilterFile, nsIMsgWindow *aMsgWindow)
+{
+ AlertBackingUpFilterFile(aMsgWindow);
+
+ nsCOMPtr<nsIFile> localParentDir;
+ nsresult rv = aFilterFile->GetParent(getter_AddRefs(localParentDir));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ //if back-up file exists delete the back up file otherwise copy fails.
+ nsCOMPtr <nsIFile> backupFile;
+ rv = localParentDir->Clone(getter_AddRefs(backupFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+ backupFile->AppendNative(NS_LITERAL_CSTRING("rulesbackup.dat"));
+ bool exists;
+ backupFile->Exists(&exists);
+ if (exists)
+ backupFile->Remove(false);
+
+ return aFilterFile->CopyToNative(localParentDir, NS_LITERAL_CSTRING("rulesbackup.dat"));
+}
+
+nsresult nsMsgFilterService::AlertBackingUpFilterFile(nsIMsgWindow *aMsgWindow)
+{
+ return ThrowAlertMsg("filterListBackUpMsg", aMsgWindow);
+}
+
+nsresult //Do not use this routine if you have to call it very often because it creates a new bundle each time
+nsMsgFilterService::GetStringFromBundle(const char *aMsgName, char16_t **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr <nsIStringBundle> bundle;
+ nsresult rv = GetFilterStringBundle(getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle)
+ rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMsgName).get(), aResult);
+ return rv;
+
+}
+
+nsresult
+nsMsgFilterService::GetFilterStringBundle(nsIStringBundle **aBundle)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (bundleService)
+ bundleService->CreateBundle("chrome://messenger/locale/filter.properties",
+ getter_AddRefs(bundle));
+ NS_IF_ADDREF(*aBundle = bundle);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFilterService::ThrowAlertMsg(const char*aMsgName, nsIMsgWindow *aMsgWindow)
+{
+ nsString alertString;
+ nsresult rv = GetStringFromBundle(aMsgName, getter_Copies(alertString));
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryInterface(aMsgWindow));
+ if (!msgWindow) {
+ nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ }
+
+ if (NS_SUCCEEDED(rv) && !alertString.IsEmpty() && msgWindow)
+ {
+ nsCOMPtr <nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (docShell)
+ {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ if (dialog && !alertString.IsEmpty())
+ dialog->Alert(nullptr, alertString.get());
+ }
+ }
+ return rv;
+}
+
+// this class is used to run filters after the fact, i.e., after new mail has been downloaded from the server.
+// It can do the following:
+// 1. Apply a single imap or pop3 filter on a single folder.
+// 2. Apply multiple filters on a single imap or pop3 folder.
+// 3. Apply a single filter on multiple imap or pop3 folders in the same account.
+// 4. Apply multiple filters on multiple imap or pop3 folders in the same account.
+// This will be called from the front end js code in the case of the apply filters to folder menu code,
+// and from the filter dialog js code with the run filter now command.
+
+
+// this class holds the list of filters and folders, and applies them in turn, first iterating
+// over all the filters on one folder, and then advancing to the next folder and repeating.
+// For each filter,we take the filter criteria and create a search term list. Then, we execute the search.
+// We are a search listener so that we can build up the list of search hits.
+// Then, when the search is done, we will apply the filter action(s) en-masse, so, for example, if the action is a move,
+// we calls one method to move all the messages to the destination folder. Or, mark all the messages read.
+// In the case of imap operations, or imap/local moves, the action will be asynchronous, so we'll need to be a url listener
+// as well, and kick off the next filter when the action completes.
+class nsMsgFilterAfterTheFact : public nsIUrlListener, public nsIMsgSearchNotify, public nsIMsgCopyServiceListener
+{
+public:
+ nsMsgFilterAfterTheFact(nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList *aFilterList, nsIArray *aFolderList,
+ nsIMsgOperationListener *aCallback);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGSEARCHNOTIFY
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ nsresult AdvanceToNextFolder(); // kicks off the process
+protected:
+ virtual ~nsMsgFilterAfterTheFact();
+ virtual nsresult RunNextFilter();
+ /**
+ * apply filter actions to current search hits
+ */
+ nsresult ApplyFilter();
+ nsresult OnEndExecution(); // do what we have to do to cleanup.
+ bool ContinueExecutionPrompt();
+ nsresult DisplayConfirmationPrompt(nsIMsgWindow *msgWindow, const char16_t *confirmString, bool *confirmed);
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ nsCOMPtr<nsIMsgFilterList> m_filters;
+ nsCOMPtr<nsIArray> m_folders;
+ nsCOMPtr<nsIMsgFolder> m_curFolder;
+ nsCOMPtr<nsIMsgDatabase> m_curFolderDB;
+ nsCOMPtr<nsIMsgFilter> m_curFilter;
+ uint32_t m_curFilterIndex;
+ uint32_t m_curFolderIndex;
+ uint32_t m_numFilters;
+ uint32_t m_numFolders;
+ nsTArray<nsMsgKey> m_searchHits;
+ nsCOMPtr<nsIMutableArray> m_searchHitHdrs;
+ nsTArray<nsMsgKey> m_stopFiltering;
+ nsCOMPtr<nsIMsgSearchSession> m_searchSession;
+ nsCOMPtr<nsIMsgOperationListener> m_callback;
+ uint32_t m_nextAction; // next filter action to perform
+ nsresult mFinalResult; // report of overall success or failure
+ bool mNeedsRelease; // Did we need to release ourself?
+};
+
+NS_IMPL_ISUPPORTS(nsMsgFilterAfterTheFact, nsIUrlListener, nsIMsgSearchNotify, nsIMsgCopyServiceListener)
+
+nsMsgFilterAfterTheFact::nsMsgFilterAfterTheFact(nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList *aFilterList,
+ nsIArray *aFolderList,
+ nsIMsgOperationListener *aCallback)
+{
+ m_curFilterIndex = m_curFolderIndex = m_nextAction = 0;
+ m_msgWindow = aMsgWindow;
+ m_filters = aFilterList;
+ m_folders = aFolderList;
+ m_filters->GetFilterCount(&m_numFilters);
+ m_folders->GetLength(&m_numFolders);
+
+ NS_ADDREF(this); // we own ourselves, and will release ourselves when execution is done.
+ mNeedsRelease = true;
+
+ m_searchHitHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ m_callback = aCallback;
+ mFinalResult = NS_OK;
+}
+
+nsMsgFilterAfterTheFact::~nsMsgFilterAfterTheFact()
+{
+}
+
+// do what we have to do to cleanup.
+nsresult nsMsgFilterAfterTheFact::OnEndExecution()
+{
+ if (m_searchSession)
+ m_searchSession->UnregisterListener(this);
+
+ if (m_filters)
+ (void)m_filters->FlushLogIfNecessary();
+
+ if (m_callback)
+ (void)m_callback->OnStopOperation(mFinalResult);
+
+ nsresult rv = mFinalResult;
+ // OnEndExecution() can be called a second time when a rule execution fails
+ // and the user is prompted whether he wants to continue.
+ if (mNeedsRelease)
+ {
+ Release(); // release ourselves.
+ mNeedsRelease = false;
+ }
+ return rv;
+}
+
+nsresult nsMsgFilterAfterTheFact::RunNextFilter()
+{
+ nsresult rv = NS_OK;
+ while (true)
+ {
+ m_curFilter = nullptr;
+ if (m_curFilterIndex >= m_numFilters)
+ break;
+ BREAK_IF_FALSE(m_filters, "Missing filters");
+ rv = m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter));
+ CONTINUE_IF_FAILURE(rv, "Could not get filter at index");
+
+ nsCOMPtr <nsISupportsArray> searchTerms;
+ rv = m_curFilter->GetSearchTerms(getter_AddRefs(searchTerms));
+ CONTINUE_IF_FAILURE(rv, "Could not get searchTerms");
+
+ if (m_searchSession)
+ m_searchSession->UnregisterListener(this);
+ m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
+ BREAK_IF_FAILURE(rv, "Failed to get search session");
+
+ nsMsgSearchScopeValue searchScope = nsMsgSearchScope::offlineMail;
+ uint32_t termCount;
+ searchTerms->Count(&termCount);
+ for (uint32_t termIndex = 0; termIndex < termCount; termIndex++)
+ {
+ nsCOMPtr <nsIMsgSearchTerm> term;
+ nsresult rv = searchTerms->QueryElementAt(termIndex, NS_GET_IID(nsIMsgSearchTerm), getter_AddRefs(term));
+ BREAK_IF_FAILURE(rv, "Could not get search term");
+ rv = m_searchSession->AppendTerm(term);
+ BREAK_IF_FAILURE(rv, "Could not append search term");
+ }
+ CONTINUE_IF_FAILURE(rv, "Failed to setup search terms");
+ m_searchSession->RegisterListener(this,
+ nsIMsgSearchSession::allNotifications);
+
+ rv = m_searchSession->AddScopeTerm(searchScope, m_curFolder);
+ CONTINUE_IF_FAILURE(rv, "Failed to add scope term");
+ m_nextAction = 0;
+ rv = m_searchSession->Search(m_msgWindow);
+ CONTINUE_IF_FAILURE(rv, "Search failed");
+ return NS_OK; // OnSearchDone will continue
+ }
+ m_curFilter = nullptr;
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Search failed");
+ return AdvanceToNextFolder();
+}
+
+nsresult nsMsgFilterAfterTheFact::AdvanceToNextFolder()
+{
+ nsresult rv = NS_OK;
+ // Advance through folders, making sure m_curFolder is null on errors
+ while (true)
+ {
+ m_stopFiltering.Clear();
+ m_curFolder = nullptr;
+ if (m_curFolderIndex >= m_numFolders)
+ // final end of nsMsgFilterAfterTheFact object
+ return OnEndExecution();
+
+ // reset the filter index to apply all filters to this new folder
+ m_curFilterIndex = 0;
+ m_nextAction = 0;
+ rv = m_folders->QueryElementAt(m_curFolderIndex++, NS_GET_IID(nsIMsgFolder), getter_AddRefs(m_curFolder));
+ CONTINUE_IF_FAILURE(rv, "Could not get next folder");
+
+ // Note: I got rv = NS_OK but null m_curFolder after deleting a folder
+ // outside of TB, when I select a single message and "run filter on message"
+ // and the filter is to move the message to the deleted folder.
+
+ // m_curFolder may be null when the folder is deleted externally.
+ CONTINUE_IF_FALSE(m_curFolder, "Next folder returned null");
+
+ rv = m_curFolder->GetMsgDatabase(getter_AddRefs(m_curFolderDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder, &rv);
+ if (NS_SUCCEEDED(rv) && localFolder)
+ // will continue with OnStopRunningUrl
+ return localFolder->ParseFolder(m_msgWindow, this);
+ }
+ CONTINUE_IF_FAILURE(rv, "Could not get folder db");
+
+ rv = RunNextFilter();
+ // RunNextFilter returns success when either filters are done, or an async process has started.
+ // It will call AdvanceToNextFolder itself if possible, so no need to call here.
+ BREAK_IF_FAILURE(rv, "Failed to run next filter");
+ break;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStartRunningUrl(nsIURI *aUrl)
+{
+ return NS_OK;
+}
+
+// This is the return from a folder parse
+NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ if (NS_SUCCEEDED(aExitCode))
+ return RunNextFilter();
+
+ mFinalResult = aExitCode;
+ // If m_msgWindow then we are in a context where the user can deal with
+ // errors. Put up a prompt, and exit if user wants.
+ if (m_msgWindow && !ContinueExecutionPrompt())
+ return OnEndExecution();
+
+ // folder parse failed, so stop processing this folder.
+ return AdvanceToNextFolder();
+}
+
+NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchHit(nsIMsgDBHdr *header, nsIMsgFolder *folder)
+{
+ NS_ENSURE_ARG_POINTER(header);
+ NS_ENSURE_TRUE(m_searchHitHdrs, NS_ERROR_NOT_INITIALIZED);
+
+ nsMsgKey msgKey;
+ header->GetMessageKey(&msgKey);
+
+ // Under various previous actions (a move, delete, or stopExecution)
+ // we do not want to process filters on a per-message basis.
+ if (m_stopFiltering.Contains(msgKey))
+ return NS_OK;
+
+ m_searchHits.AppendElement(msgKey);
+ m_searchHitHdrs->AppendElement(header, false);
+ return NS_OK;
+}
+
+// Continue after an async operation.
+NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchDone(nsresult status)
+{
+ if (NS_SUCCEEDED(status))
+ return m_searchHits.IsEmpty() ? RunNextFilter() : ApplyFilter();
+
+ mFinalResult = status;
+ if (m_msgWindow && !ContinueExecutionPrompt())
+ return OnEndExecution();
+
+ // The search failed, so move on to the next filter.
+ return RunNextFilter();
+}
+
+NS_IMETHODIMP nsMsgFilterAfterTheFact::OnNewSearch()
+{
+ m_searchHits.Clear();
+ m_searchHitHdrs->Clear();
+ return NS_OK;
+}
+
+// This method will apply filters. It will continue to advance though headers,
+// filters, and folders until done, unless it starts an async operation with
+// a callback. The callback should call ApplyFilter again. It only returns
+// an error if it is impossible to continue after attempting to continue the
+// next filter action, filter, or folder.
+nsresult nsMsgFilterAfterTheFact::ApplyFilter()
+{
+ nsresult rv;
+ do { // error management block, break if unable to continue with filter.
+ if (!m_curFilter)
+ break; // Maybe not an error, we just need to call RunNextFilter();
+ if (!m_curFolder)
+ break; // Maybe not an error, we just need to call AdvanceToNextFolder();
+ BREAK_IF_FALSE(m_searchHitHdrs, "No search headers object");
+ // we're going to log the filter actions before firing them because some actions are async
+ bool loggingEnabled = false;
+ if (m_filters)
+ (void)m_filters->GetLoggingEnabled(&loggingEnabled);
+
+ nsCOMPtr<nsIArray> actionList;
+ rv = m_curFilter->GetSortedActionList(getter_AddRefs(actionList));
+ BREAK_IF_FAILURE(rv, "Could not get action list for filter");
+
+ uint32_t numActions;
+ actionList->GetLength(&numActions);
+
+ // We start from m_nextAction to allow us to continue applying actions
+ // after the return from an async copy.
+ while (m_nextAction < numActions)
+ {
+ nsCOMPtr<nsIMsgRuleAction>filterAction(do_QueryElementAt(actionList, m_nextAction++, &rv));
+ CONTINUE_IF_FAILURE(rv, "actionList cannot QI element");
+
+ nsMsgRuleActionType actionType;
+ rv = filterAction->GetType(&actionType);
+ CONTINUE_IF_FAILURE(rv, "Could not get type for filter action");
+
+ nsCString actionTargetFolderUri;
+ if (actionType == nsMsgFilterAction::MoveToFolder ||
+ actionType == nsMsgFilterAction::CopyToFolder)
+ {
+ rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
+ CONTINUE_IF_FALSE(NS_SUCCEEDED(rv) && !actionTargetFolderUri.IsEmpty(),
+ "actionTargetFolderUri is empty");
+ }
+
+ if (loggingEnabled)
+ {
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ if (msgHdr)
+ (void)m_curFilter->LogRuleHit(filterAction, msgHdr);
+ else
+ NS_WARNING("could not QI element to nsIMsgDBHdr");
+ }
+ }
+
+ // all actions that pass "this" as a listener in order to chain filter execution
+ // when the action is finished need to return before reaching the bottom of this
+ // routine, because we run the next filter at the end of this routine.
+ switch (actionType)
+ {
+ case nsMsgFilterAction::Delete:
+ // we can't pass ourselves in as a copy service listener because the copy service
+ // listener won't get called in several situations (e.g., the delete model is imap delete)
+ // and we rely on the listener getting called to continue the filter application.
+ // This means we're going to end up firing off the delete, and then subsequently
+ // issuing a search for the next filter, which will block until the delete finishes.
+ m_curFolder->DeleteMessages(m_searchHitHdrs, m_msgWindow, false, false, nullptr, false /*allow Undo*/ );
+
+ // don't allow any more filters on this message
+ m_stopFiltering.AppendElements(m_searchHits);
+ for (uint32_t i = 0; i < m_searchHits.Length(); i++)
+ m_curFolder->OrProcessingFlags(m_searchHits[i], nsMsgProcessingFlags::FilterToMove);
+ //if we are deleting then we couldn't care less about applying remaining filter actions
+ m_nextAction = numActions;
+ break;
+
+ case nsMsgFilterAction::MoveToFolder:
+ // Even if move fails we will not run additional actions, as they
+ // would not have run if move succeeded.
+ m_nextAction = numActions;
+ // Fall through to the copy case.
+ MOZ_FALLTHROUGH;
+ case nsMsgFilterAction::CopyToFolder:
+ {
+ nsCString uri;
+ m_curFolder->GetURI(uri);
+ if (!actionTargetFolderUri.IsEmpty() &&
+ !uri.Equals(actionTargetFolderUri))
+ {
+ nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv);
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(actionTargetFolderUri, getter_AddRefs(res));
+ CONTINUE_IF_FAILURE(rv, "Could not get resource for action folder");
+
+ nsCOMPtr<nsIMsgFolder> destIFolder(do_QueryInterface(res, &rv));
+ CONTINUE_IF_FAILURE(rv, "Could not QI resource to folder");
+
+ bool canFileMessages = true;
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ destIFolder->GetParent(getter_AddRefs(parentFolder));
+ if (parentFolder)
+ destIFolder->GetCanFileMessages(&canFileMessages);
+ if (!parentFolder || !canFileMessages)
+ {
+ m_curFilter->SetEnabled(false);
+ destIFolder->ThrowAlertMsg("filterDisabled",m_msgWindow);
+ // we need to explicitly save the filter file.
+ m_filters->SaveToDefaultFile();
+ // In the case of applying multiple filters
+ // we might want to remove the filter from the list, but
+ // that's a bit evil since we really don't know that we own
+ // the list. Disabling it doesn't do a lot of good since
+ // we still apply disabled filters. Currently, we don't
+ // have any clients that apply filters to multiple folders,
+ // so this might be the edge case of an edge case.
+ m_nextAction = numActions;
+ mFinalResult = NS_ERROR_FAILURE;
+ break;
+ }
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ CONTINUE_IF_FAILURE(rv, "Could not get copy service")
+
+ if (actionType == nsMsgFilterAction::MoveToFolder)
+ {
+ m_stopFiltering.AppendElements(m_searchHits);
+ for (uint32_t i = 0; i < m_searchHits.Length(); i++)
+ m_curFolder->OrProcessingFlags(m_searchHits[i],
+ nsMsgProcessingFlags::FilterToMove);
+ }
+
+ rv = copyService->CopyMessages(m_curFolder, m_searchHitHdrs,
+ destIFolder, actionType == nsMsgFilterAction::MoveToFolder,
+ this, m_msgWindow, false);
+ CONTINUE_IF_FAILURE(rv, "CopyMessages failed");
+ return NS_OK; // OnStopCopy callback to continue;
+ }
+ else
+ NS_WARNING("Move or copy failed, empty or unchanged destination");
+ }
+ break;
+ case nsMsgFilterAction::MarkRead:
+ // crud, no listener support here - we'll probably just need to go on and apply
+ // the next filter, and, in the imap case, rely on multiple connection and url
+ // queueing to stay out of trouble
+ m_curFolder->MarkMessagesRead(m_searchHitHdrs, true);
+ break;
+ case nsMsgFilterAction::MarkUnread:
+ m_curFolder->MarkMessagesRead(m_searchHitHdrs, false);
+ break;
+ case nsMsgFilterAction::MarkFlagged:
+ m_curFolder->MarkMessagesFlagged(m_searchHitHdrs, true);
+ break;
+ case nsMsgFilterAction::KillThread:
+ case nsMsgFilterAction::WatchThread:
+ {
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msg header");
+
+ nsCOMPtr<nsIMsgThread> msgThread;
+ nsMsgKey threadKey;
+ m_curFolderDB->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(msgThread));
+ CONTINUE_IF_FALSE(msgThread, "Could not find msg thread");
+ msgThread->GetThreadKey(&threadKey);
+ if (actionType == nsMsgFilterAction::KillThread)
+ m_curFolderDB->MarkThreadIgnored(msgThread, threadKey, true, nullptr);
+ else
+ m_curFolderDB->MarkThreadWatched(msgThread, threadKey, true, nullptr);
+ }
+ }
+ break;
+ case nsMsgFilterAction::KillSubthread:
+ {
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msg header");
+ m_curFolderDB->MarkHeaderKilled(msgHdr, true, nullptr);
+ }
+ }
+ break;
+ case nsMsgFilterAction::ChangePriority:
+ {
+ nsMsgPriorityValue filterPriority;
+ filterAction->GetPriority(&filterPriority);
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msg header");
+ msgHdr->SetPriority(filterPriority);
+ }
+ }
+ break;
+ case nsMsgFilterAction::Label:
+ {
+ nsMsgLabelValue filterLabel;
+ filterAction->GetLabel(&filterLabel);
+ m_curFolder->SetLabelForMessages(m_searchHitHdrs, filterLabel);
+ }
+ break;
+ case nsMsgFilterAction::AddTag:
+ {
+ nsCString keyword;
+ filterAction->GetStrValue(keyword);
+ m_curFolder->AddKeywordsToMessages(m_searchHitHdrs, keyword);
+ }
+ break;
+ case nsMsgFilterAction::JunkScore:
+ {
+ nsAutoCString junkScoreStr;
+ int32_t junkScore;
+ filterAction->GetJunkScore(&junkScore);
+ junkScoreStr.AppendInt(junkScore);
+ m_curFolder->SetJunkScoreForMessages(m_searchHitHdrs, junkScoreStr);
+ }
+ break;
+ case nsMsgFilterAction::Forward:
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = m_curFolder->GetServer(getter_AddRefs(server));
+ CONTINUE_IF_FAILURE(rv, "Could not get server");
+ nsCString forwardTo;
+ filterAction->GetStrValue(forwardTo);
+ CONTINUE_IF_FALSE(!forwardTo.IsEmpty(), "blank forwardTo URI");
+ nsCOMPtr<nsIMsgComposeService> compService =
+ do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
+ CONTINUE_IF_FAILURE(rv, "Could not get compose service");
+
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryElementAt(m_searchHitHdrs,
+ msgIndex));
+ if (msgHdr)
+ rv = compService->ForwardMessage(NS_ConvertASCIItoUTF16(forwardTo),
+ msgHdr, m_msgWindow, server,
+ nsIMsgComposeService::kForwardAsDefault);
+ CONTINUE_IF_FALSE(msgHdr && NS_SUCCEEDED(rv), "Forward action failed");
+ }
+ }
+ break;
+ case nsMsgFilterAction::Reply:
+ {
+ nsCString replyTemplateUri;
+ filterAction->GetStrValue(replyTemplateUri);
+ CONTINUE_IF_FALSE(!replyTemplateUri.IsEmpty(), "Empty reply template URI");
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = m_curFolder->GetServer(getter_AddRefs(server));
+ CONTINUE_IF_FAILURE(rv, "Could not get server");
+
+ nsCOMPtr<nsIMsgComposeService> compService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
+ CONTINUE_IF_FAILURE(rv, "Could not get compose service");
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr");
+ rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri.get(), m_msgWindow, server);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_ABORT) {
+ m_curFilter->LogRuleHitFail(filterAction, msgHdr, rv, "Sending reply aborted");
+ } else {
+ m_curFilter->LogRuleHitFail(filterAction, msgHdr, rv, "Error sending reply");
+ }
+ }
+ CONTINUE_IF_FAILURE(rv, "ReplyWithTemplate failed");
+ }
+ }
+ break;
+ case nsMsgFilterAction::DeleteFromPop3Server:
+ {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder);
+ CONTINUE_IF_FALSE(localFolder, "Current folder not a local folder");
+ // This action ignores the deleteMailLeftOnServer preference
+ rv = localFolder->MarkMsgsOnPop3Server(m_searchHitHdrs, POP3_FORCE_DEL);
+ CONTINUE_IF_FAILURE(rv, "MarkMsgsOnPop3Server failed");
+
+ nsCOMPtr<nsIMutableArray> partialMsgs;
+ // Delete the partial headers. They're useless now
+ // that the server copy is being deleted.
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr");
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial)
+ {
+ if (!partialMsgs)
+ partialMsgs = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ CONTINUE_IF_FALSE(partialMsgs, "Could not create partialMsgs array");
+ partialMsgs->AppendElement(msgHdr, false);
+ m_stopFiltering.AppendElement(m_searchHits[msgIndex]);
+ m_curFolder->OrProcessingFlags(m_searchHits[msgIndex],
+ nsMsgProcessingFlags::FilterToMove);
+ }
+ }
+ if (partialMsgs)
+ {
+ m_curFolder->DeleteMessages(partialMsgs, m_msgWindow, true, false, nullptr, false);
+ CONTINUE_IF_FAILURE(rv, "Delete messages failed");
+ }
+ }
+ break;
+ case nsMsgFilterAction::FetchBodyFromPop3Server:
+ {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder);
+ CONTINUE_IF_FALSE(localFolder, "current folder not local");
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ CONTINUE_IF_FAILURE(rv, "Could not create messages array");
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr");
+ uint32_t flags = 0;
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial)
+ messages->AppendElement(msgHdr, false);
+ }
+ uint32_t msgsToFetch;
+ messages->GetLength(&msgsToFetch);
+ if (msgsToFetch > 0)
+ {
+ rv = m_curFolder->DownloadMessagesForOffline(messages, m_msgWindow);
+ CONTINUE_IF_FAILURE(rv, "DownloadMessagesForOffline failed");
+ }
+ }
+ break;
+
+ case nsMsgFilterAction::StopExecution:
+ {
+ // don't apply any more filters
+ m_stopFiltering.AppendElements(m_searchHits);
+ m_nextAction = numActions;
+ }
+ break;
+
+ case nsMsgFilterAction::Custom:
+ {
+ nsMsgFilterTypeType filterType;
+ m_curFilter->GetFilterType(&filterType);
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
+ CONTINUE_IF_FAILURE(rv, "Could not get custom action");
+
+ nsAutoCString value;
+ filterAction->GetStrValue(value);
+ bool isAsync = false;
+ customAction->GetIsAsync(&isAsync);
+ rv = customAction->Apply(m_searchHitHdrs, value, this,
+ filterType, m_msgWindow);
+ CONTINUE_IF_FAILURE(rv, "custom action failed to apply");
+ if (isAsync)
+ return NS_OK; // custom action should call ApplyFilter on callback
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ } while (false); // end error management block
+ return RunNextFilter();
+}
+
+NS_IMETHODIMP nsMsgFilterService::GetTempFilterList(nsIMsgFolder *aFolder, nsIMsgFilterList **aFilterList)
+{
+ NS_ENSURE_ARG_POINTER(aFilterList);
+
+ nsMsgFilterList *filterList = new nsMsgFilterList;
+ NS_ENSURE_TRUE(filterList, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*aFilterList = filterList);
+ (*aFilterList)->SetFolder(aFolder);
+ filterList->m_temporaryList = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterService::ApplyFiltersToFolders(nsIMsgFilterList *aFilterList,
+ nsIArray *aFolders,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgOperationListener *aCallback)
+{
+ NS_ENSURE_ARG_POINTER(aFilterList);
+ NS_ENSURE_ARG_POINTER(aFolders);
+
+ RefPtr<nsMsgFilterAfterTheFact> filterExecutor =
+ new nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolders, aCallback);
+ if (filterExecutor)
+ return filterExecutor->AdvanceToNextFolder();
+ else
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgFilterService::AddCustomAction(nsIMsgFilterCustomAction *aAction)
+{
+ mCustomActions.AppendObject(aAction);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterService::GetCustomActions(nsISimpleEnumerator** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ return NS_NewArrayEnumerator(aResult, mCustomActions);
+}
+
+NS_IMETHODIMP
+nsMsgFilterService::GetCustomAction(const nsACString & aId,
+ nsIMsgFilterCustomAction** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ for (int32_t i = 0; i < mCustomActions.Count(); i++)
+ {
+ nsAutoCString id;
+ nsresult rv = mCustomActions[i]->GetId(id);
+ if (NS_SUCCEEDED(rv) && aId.Equals(id))
+ {
+ NS_ADDREF(*aResult = mCustomActions[i]);
+ return NS_OK;
+ }
+ }
+ aResult = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgFilterService::AddCustomTerm(nsIMsgSearchCustomTerm *aTerm)
+{
+ mCustomTerms.AppendObject(aTerm);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterService::GetCustomTerms(nsISimpleEnumerator** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ return NS_NewArrayEnumerator(aResult, mCustomTerms);
+}
+
+NS_IMETHODIMP
+nsMsgFilterService::GetCustomTerm(const nsACString& aId,
+ nsIMsgSearchCustomTerm** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ for (int32_t i = 0; i < mCustomTerms.Count(); i++)
+ {
+ nsAutoCString id;
+ nsresult rv = mCustomTerms[i]->GetId(id);
+ if (NS_SUCCEEDED(rv) && aId.Equals(id))
+ {
+ NS_ADDREF(*aResult = mCustomTerms[i]);
+ return NS_OK;
+ }
+ }
+ aResult = nullptr;
+ // we use a null result to indicate failure to find a term
+ return NS_OK;
+}
+
+// nsMsgApplyFiltersToMessages overrides nsMsgFilterAfterTheFact in order to
+// apply filters to a list of messages, rather than an entire folder
+class nsMsgApplyFiltersToMessages : public nsMsgFilterAfterTheFact
+{
+public:
+ nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList *aFilterList,
+ nsIArray *aFolderList, nsIArray *aMsgHdrList,
+ nsMsgFilterTypeType aFilterType,
+ nsIMsgOperationListener *aCallback);
+
+protected:
+ virtual nsresult RunNextFilter();
+
+ nsCOMArray<nsIMsgDBHdr> m_msgHdrList;
+ nsMsgFilterTypeType m_filterType;
+};
+
+nsMsgApplyFiltersToMessages::nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList *aFilterList,
+ nsIArray *aFolderList,
+ nsIArray *aMsgHdrList,
+ nsMsgFilterTypeType aFilterType,
+ nsIMsgOperationListener *aCallback)
+: nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolderList, aCallback),
+ m_filterType(aFilterType)
+{
+ nsCOMPtr<nsISimpleEnumerator> msgEnumerator;
+ if (NS_SUCCEEDED(aMsgHdrList->Enumerate(getter_AddRefs(msgEnumerator))))
+ {
+ uint32_t length;
+ if (NS_SUCCEEDED(aMsgHdrList->GetLength(&length)))
+ m_msgHdrList.SetCapacity(length);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(msgEnumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ if (NS_SUCCEEDED(msgEnumerator->GetNext(getter_AddRefs(supports))) &&
+ (msgHdr = do_QueryInterface(supports)))
+ m_msgHdrList.AppendObject(msgHdr);
+ }
+ }
+}
+
+nsresult nsMsgApplyFiltersToMessages::RunNextFilter()
+{
+ nsresult rv = NS_OK;
+ while (true)
+ {
+ m_curFilter = nullptr; // we are done with the current filter
+ if (!m_curFolder || // Not an error, we just need to run AdvanceToNextFolder()
+ m_curFilterIndex >= m_numFilters)
+ break;
+ BREAK_IF_FALSE(m_filters, "No filters");
+ nsMsgFilterTypeType filterType;
+ bool isEnabled;
+ rv = m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter));
+ CONTINUE_IF_FAILURE(rv, "Could not get filter");
+ rv = m_curFilter->GetFilterType(&filterType);
+ CONTINUE_IF_FAILURE(rv, "Could not get filter type");
+ if (!(filterType & m_filterType))
+ continue;
+ rv = m_curFilter->GetEnabled(&isEnabled);
+ CONTINUE_IF_FAILURE(rv, "Could not get isEnabled");
+ if (!isEnabled)
+ continue;
+
+ nsCOMPtr<nsIMsgSearchScopeTerm> scope(new nsMsgSearchScopeTerm(nullptr, nsMsgSearchScope::offlineMail, m_curFolder));
+ BREAK_IF_FALSE(scope, "Could not create scope, OOM?");
+ m_curFilter->SetScope(scope);
+ OnNewSearch();
+
+ for (int32_t i = 0; i < m_msgHdrList.Count(); i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = m_msgHdrList[i];
+ CONTINUE_IF_FALSE(msgHdr, "null msgHdr");
+
+ bool matched;
+ rv = m_curFilter->MatchHdr(msgHdr, m_curFolder, m_curFolderDB, nullptr, 0, &matched);
+ if (NS_SUCCEEDED(rv) && matched)
+ {
+ // In order to work with nsMsgFilterAfterTheFact::ApplyFilter we initialize
+ // nsMsgFilterAfterTheFact's information with a search hit now for the message
+ // that we're filtering.
+ OnSearchHit(msgHdr, m_curFolder);
+ }
+ }
+ m_curFilter->SetScope(nullptr);
+
+ if (m_searchHits.Length() > 0)
+ {
+ m_nextAction = 0;
+ rv = ApplyFilter();
+ if (NS_SUCCEEDED(rv))
+ return NS_OK; // async callback will continue, or we are done.
+ }
+ }
+ m_curFilter = nullptr;
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to run filters");
+ // We expect the failure is already recorded through one of the macro
+ // expressions, that will have console logging added to them.
+ // So an additional console warning is not needed here.
+ return AdvanceToNextFolder();
+}
+
+NS_IMETHODIMP nsMsgFilterService::ApplyFilters(nsMsgFilterTypeType aFilterType,
+ nsIArray *aMsgHdrList,
+ nsIMsgFolder *aFolder,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgOperationListener *aCallback)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ nsresult rv = aFolder->GetFilterList(aMsgWindow, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> folderList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ folderList->AppendElement(aFolder, false);
+
+ // Create our nsMsgApplyFiltersToMessages object which will be called when ApplyFiltersToHdr
+ // finds one or more filters that hit.
+ RefPtr<nsMsgApplyFiltersToMessages> filterExecutor =
+ new nsMsgApplyFiltersToMessages(aMsgWindow, filterList, folderList,
+ aMsgHdrList, aFilterType, aCallback);
+
+ if (filterExecutor)
+ return filterExecutor->AdvanceToNextFolder();
+
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+/* void OnStartCopy (); */
+NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStartCopy()
+{
+ return NS_OK;
+}
+
+/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
+NS_IMETHODIMP nsMsgFilterAfterTheFact::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
+{
+ return NS_OK;
+}
+
+/* void SetMessageKey (in uint32_t aKey); */
+NS_IMETHODIMP nsMsgFilterAfterTheFact::SetMessageKey(nsMsgKey /* aKey */)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterAfterTheFact::GetMessageId(nsACString& messageId)
+{
+ return NS_OK;
+}
+
+/* void OnStopCopy (in nsresult aStatus); */
+NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStopCopy(nsresult aStatus)
+{
+ if (NS_SUCCEEDED(aStatus))
+ return ApplyFilter();
+
+ mFinalResult = aStatus;
+ if (m_msgWindow && !ContinueExecutionPrompt())
+ return OnEndExecution();
+
+ // Copy failed, so run the next filter
+ return RunNextFilter();
+}
+
+bool nsMsgFilterAfterTheFact::ContinueExecutionPrompt()
+{
+ if (!m_curFilter)
+ return false;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService)
+ return false;
+ bundleService->CreateBundle("chrome://messenger/locale/filter.properties",
+ getter_AddRefs(bundle));
+ if (!bundle)
+ return false;
+ nsString filterName;
+ m_curFilter->GetFilterName(filterName);
+ nsString formatString;
+ nsString confirmText;
+ const char16_t *formatStrings[] =
+ {
+ filterName.get()
+ };
+ nsresult rv = bundle->FormatStringFromName(u"continueFilterExecution",
+ formatStrings, 1, getter_Copies(confirmText));
+ if (NS_FAILED(rv))
+ return false;
+ bool returnVal = false;
+ (void) DisplayConfirmationPrompt(m_msgWindow, confirmText.get(), &returnVal);
+ return returnVal;
+}
+
+nsresult
+nsMsgFilterAfterTheFact::DisplayConfirmationPrompt(nsIMsgWindow *msgWindow, const char16_t *confirmString, bool *confirmed)
+{
+ if (msgWindow)
+ {
+ nsCOMPtr <nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (docShell)
+ {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ if (dialog && confirmString)
+ dialog->Confirm(nullptr, confirmString, confirmed);
+ }
+ }
+ return NS_OK;
+}
diff --git a/mailnews/base/search/src/nsMsgFilterService.h b/mailnews/base/search/src/nsMsgFilterService.h
new file mode 100644
index 000000000..8fdc6a5cb
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilterService.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgFilterService_H_
+#define _nsMsgFilterService_H_
+
+#include "nsIMsgFilterService.h"
+#include "nsCOMArray.h"
+
+class nsIMsgWindow;
+class nsIStringBundle;
+
+
+
+// The filter service is used to acquire and manipulate filter lists.
+
+class nsMsgFilterService : public nsIMsgFilterService
+{
+
+public:
+ nsMsgFilterService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGFILTERSERVICE
+/* clients call OpenFilterList to get a handle to a FilterList, of existing nsMsgFilter *.
+ These are manipulated by the front end as a result of user interaction
+ with dialog boxes. To apply the new list call MSG_CloseFilterList.
+*/
+ nsresult BackUpFilterFile(nsIFile *aFilterFile, nsIMsgWindow *aMsgWindow);
+ nsresult AlertBackingUpFilterFile(nsIMsgWindow *aMsgWindow);
+ nsresult ThrowAlertMsg(const char*aMsgName, nsIMsgWindow *aMsgWindow);
+ nsresult GetStringFromBundle(const char *aMsgName, char16_t **aResult);
+ nsresult GetFilterStringBundle(nsIStringBundle **aBundle);
+
+protected:
+ virtual ~nsMsgFilterService();
+
+ nsCOMArray<nsIMsgFilterCustomAction> mCustomActions; // defined custom action list
+ nsCOMArray<nsIMsgSearchCustomTerm> mCustomTerms; // defined custom term list
+
+};
+
+#endif // _nsMsgFilterService_H_
+
diff --git a/mailnews/base/search/src/nsMsgImapSearch.cpp b/mailnews/base/search/src/nsMsgImapSearch.cpp
new file mode 100644
index 000000000..09442aeea
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgImapSearch.cpp
@@ -0,0 +1,1004 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "msgCore.h"
+#include "nsMsgSearchAdapter.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsMsgResultElement.h"
+#include "nsMsgSearchTerm.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgSearchImap.h"
+#include "prmem.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+// Implementation of search for IMAP mail folders
+
+
+nsMsgSearchOnlineMail::nsMsgSearchOnlineMail (nsMsgSearchScopeTerm *scope, nsISupportsArray *termList) : nsMsgSearchAdapter (scope, termList)
+{
+}
+
+
+nsMsgSearchOnlineMail::~nsMsgSearchOnlineMail ()
+{
+}
+
+
+nsresult nsMsgSearchOnlineMail::ValidateTerms ()
+{
+ nsresult err = nsMsgSearchAdapter::ValidateTerms ();
+
+ if (NS_SUCCEEDED(err))
+ {
+ // ### mwelch Figure out the charsets to use
+ // for the search terms and targets.
+ nsAutoString srcCharset, dstCharset;
+ GetSearchCharsets(srcCharset, dstCharset);
+
+ // do IMAP specific validation
+ err = Encode (m_encoding, m_searchTerms, dstCharset.get());
+ NS_ASSERTION(NS_SUCCEEDED(err), "failed to encode imap search");
+ }
+
+ return err;
+}
+
+
+NS_IMETHODIMP nsMsgSearchOnlineMail::GetEncoding (char **result)
+{
+ *result = ToNewCString(m_encoding);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchOnlineMail::AddResultElement (nsIMsgDBHdr *pHeaders)
+{
+ nsresult err = NS_OK;
+
+ nsCOMPtr<nsIMsgSearchSession> searchSession;
+ m_scope->GetSearchSession(getter_AddRefs(searchSession));
+ if (searchSession)
+ {
+ nsCOMPtr <nsIMsgFolder> scopeFolder;
+ err = m_scope->GetFolder(getter_AddRefs(scopeFolder));
+ searchSession->AddSearchHit(pHeaders, scopeFolder);
+ }
+ //XXXX alecf do not checkin without fixing! m_scope->m_searchSession->AddResultElement (newResult);
+ return err;
+}
+
+nsresult nsMsgSearchOnlineMail::Search (bool *aDone)
+{
+ // we should never end up here for a purely online
+ // folder. We might for an offline IMAP folder.
+ nsresult err = NS_ERROR_NOT_IMPLEMENTED;
+
+ return err;
+}
+
+nsresult nsMsgSearchOnlineMail::Encode (nsCString& pEncoding,
+ nsISupportsArray *searchTerms,
+ const char16_t *destCharset)
+{
+ nsCString imapTerms;
+
+ //check if searchTerms are ascii only
+ bool asciiOnly = true;
+ // ### what's this mean in the NWO?????
+
+ if (true) // !(srcCharset & CODESET_MASK == STATEFUL || srcCharset & CODESET_MASK == WIDECHAR) ) //assume all single/multiple bytes charset has ascii as subset
+ {
+ uint32_t termCount;
+ searchTerms->Count(&termCount);
+ uint32_t i = 0;
+
+ for (i = 0; i < termCount && asciiOnly; i++)
+ {
+ nsCOMPtr<nsIMsgSearchTerm> pTerm;
+ searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(pTerm));
+
+ nsMsgSearchAttribValue attribute;
+ pTerm->GetAttrib(&attribute);
+ if (IS_STRING_ATTRIBUTE(attribute))
+ {
+ nsString pchar;
+ nsCOMPtr <nsIMsgSearchValue> searchValue;
+
+ nsresult rv = pTerm->GetValue(getter_AddRefs(searchValue));
+ if (NS_FAILED(rv) || !searchValue)
+ continue;
+
+
+ rv = searchValue->GetStr(pchar);
+ if (NS_FAILED(rv) || pchar.IsEmpty())
+ continue;
+ asciiOnly = NS_IsAscii(static_cast<const char16_t*>(pchar.get()));
+ }
+ }
+ }
+// else
+// asciiOnly = false; // TODO: enable this line when the condition is not a plain "true" in the if().
+
+ const char16_t* usAsciiCharSet = u"us-ascii";
+ // Get the optional CHARSET parameter, in case we need it.
+ char *csname = GetImapCharsetParam(asciiOnly ? usAsciiCharSet : destCharset);
+
+ // We do not need "srcCharset" since the search term in always unicode.
+ // I just pass destCharset for both src and dest charset instead of removing srcCharst from the arguemnt.
+ nsresult err = nsMsgSearchAdapter::EncodeImap (getter_Copies(imapTerms), searchTerms,
+ asciiOnly ? usAsciiCharSet : destCharset,
+ asciiOnly ? usAsciiCharSet : destCharset, false);
+ if (NS_SUCCEEDED(err))
+ {
+ pEncoding.Append("SEARCH");
+ if (csname)
+ pEncoding.Append(csname);
+ pEncoding.Append(imapTerms);
+ }
+ PR_FREEIF(csname);
+ return err;
+}
+
+
+nsresult
+nsMsgSearchValidityManager::InitOfflineMailTable()
+{
+ NS_ASSERTION(!m_offlineMailTable, "offline mail table already initted");
+ nsresult rv = NewTable (getter_AddRefs(m_offlineMailTable));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+
+ // m_offlineMailTable->SetValidButNotShown (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsHigherThan, 1);
+ // m_offlineMailTable->SetValidButNotShown (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLowerThan, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+ m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+ m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+ return rv;
+}
+
+
+nsresult
+nsMsgSearchValidityManager::InitOnlineMailTable()
+{
+ NS_ASSERTION(!m_onlineMailTable, "Online mail table already initted!");
+ nsresult rv = NewTable (getter_AddRefs(m_onlineMailTable));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+
+ return rv;
+}
+
+nsresult
+nsMsgSearchValidityManager::InitOnlineMailFilterTable()
+{
+ // Oh what a tangled web...
+ //
+ // IMAP filtering happens on the client, fundamentally using the same
+ // capabilities as POP filtering. However, since we don't yet have the
+ // IMAP message body, we can't filter on body attributes. So this table
+ // is supposed to be the same as offline mail, except that the body
+ // attribute is omitted
+ NS_ASSERTION(!m_onlineMailFilterTable, "online filter table already initted");
+ nsresult rv = NewTable (getter_AddRefs(m_onlineMailFilterTable));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+
+ return rv;
+}
+
+nsresult
+nsMsgSearchValidityManager::InitOfflineMailFilterTable()
+{
+ NS_ASSERTION(!m_offlineMailFilterTable, "offline mail filter table already initted");
+ nsresult rv = NewTable (getter_AddRefs(m_offlineMailFilterTable));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+
+ // junk status and attachment status not available for offline mail (POP) filters
+ // because we won't know those until after the message has been analyzed.
+ // see bug #185937
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+
+ return rv;
+}
+
+// Online Manual is used for IMAP and NEWS, where at manual
+// filtering we have junk info, but cannot assure that the
+// body is available.
+nsresult
+nsMsgSearchValidityManager::InitOnlineManualFilterTable()
+{
+ NS_ASSERTION(!m_onlineManualFilterTable, "online manual filter table already initted");
+ nsresult rv = NewTable(getter_AddRefs(m_onlineManualFilterTable));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1);
+
+ // HasAttachmentStatus does not work reliably until the user has opened a
+ // message to force it through MIME. We need a solution for this (bug 105169)
+ // but in the meantime, I'm doing the same thing here that we do in the
+ // offline mail table, as this does not really depend at the moment on
+ // whether we have downloaded the body for offline use.
+ m_onlineManualFilterTable->SetAvailable (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ m_onlineManualFilterTable->SetEnabled(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+
+ return rv;
+}
diff --git a/mailnews/base/search/src/nsMsgLocalSearch.cpp b/mailnews/base/search/src/nsMsgLocalSearch.cpp
new file mode 100644
index 000000000..3ce510b6d
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgLocalSearch.cpp
@@ -0,0 +1,1022 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Implementation of db search for POP and offline IMAP mail folders
+
+#include "msgCore.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgSearchCore.h"
+#include "nsMsgLocalSearch.h"
+#include "nsIStreamListener.h"
+#include "nsMsgSearchBoolExpression.h"
+#include "nsMsgSearchTerm.h"
+#include "nsMsgResultElement.h"
+#include "nsIDBFolderInfo.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgSearchValue.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgFolder.h"
+
+extern "C"
+{
+ extern int MK_MSG_SEARCH_STATUS;
+ extern int MK_MSG_CANT_SEARCH_IF_NO_SUMMARY;
+ extern int MK_MSG_SEARCH_HITS_NOT_IN_DB;
+}
+
+
+//----------------------------------------------------------------------------
+// Class definitions for the boolean expression structure....
+//----------------------------------------------------------------------------
+
+
+nsMsgSearchBoolExpression * nsMsgSearchBoolExpression::AddSearchTerm(nsMsgSearchBoolExpression * aOrigExpr, nsIMsgSearchTerm * aNewTerm, char * aEncodingStr)
+// appropriately add the search term to the current expression and return a pointer to the
+// new expression. The encodingStr is the IMAP/NNTP encoding string for newTerm.
+{
+ return aOrigExpr->leftToRightAddTerm(aNewTerm, aEncodingStr);
+}
+
+nsMsgSearchBoolExpression * nsMsgSearchBoolExpression::AddExpressionTree(nsMsgSearchBoolExpression * aOrigExpr, nsMsgSearchBoolExpression * aExpression, bool aBoolOp)
+{
+ if (!aOrigExpr->m_term && !aOrigExpr->m_leftChild && !aOrigExpr->m_rightChild)
+ {
+ // just use the original expression tree...
+ // delete the original since we have a new original to use
+ delete aOrigExpr;
+ return aExpression;
+ }
+
+ nsMsgSearchBoolExpression * newExpr = new nsMsgSearchBoolExpression (aOrigExpr, aExpression, aBoolOp);
+ return (newExpr) ? newExpr : aOrigExpr;
+}
+
+nsMsgSearchBoolExpression::nsMsgSearchBoolExpression()
+{
+ m_term = nullptr;
+ m_boolOp = nsMsgSearchBooleanOp::BooleanAND;
+ m_leftChild = nullptr;
+ m_rightChild = nullptr;
+}
+
+nsMsgSearchBoolExpression::nsMsgSearchBoolExpression (nsIMsgSearchTerm * newTerm, char * encodingStr)
+// we are creating an expression which contains a single search term (newTerm)
+// and the search term's IMAP or NNTP encoding value for online search expressions AND
+// a boolean evaluation value which is used for offline search expressions.
+{
+ m_term = newTerm;
+ m_encodingStr = encodingStr;
+ m_boolOp = nsMsgSearchBooleanOp::BooleanAND;
+
+ // this expression does not contain sub expressions
+ m_leftChild = nullptr;
+ m_rightChild = nullptr;
+}
+
+
+nsMsgSearchBoolExpression::nsMsgSearchBoolExpression (nsMsgSearchBoolExpression * expr1, nsMsgSearchBoolExpression * expr2, nsMsgSearchBooleanOperator boolOp)
+// we are creating an expression which contains two sub expressions and a boolean operator used to combine
+// them.
+{
+ m_leftChild = expr1;
+ m_rightChild = expr2;
+ m_boolOp = boolOp;
+
+ m_term = nullptr;
+}
+
+nsMsgSearchBoolExpression::~nsMsgSearchBoolExpression()
+{
+ // we must recursively destroy all sub expressions before we destroy ourself.....We leave search terms alone!
+ delete m_leftChild;
+ delete m_rightChild;
+}
+
+nsMsgSearchBoolExpression *
+nsMsgSearchBoolExpression::leftToRightAddTerm(nsIMsgSearchTerm * newTerm, char * encodingStr)
+{
+ // we have a base case where this is the first term being added to the expression:
+ if (!m_term && !m_leftChild && !m_rightChild)
+ {
+ m_term = newTerm;
+ m_encodingStr = encodingStr;
+ return this;
+ }
+
+ nsMsgSearchBoolExpression * tempExpr = new nsMsgSearchBoolExpression (newTerm,encodingStr);
+ if (tempExpr) // make sure creation succeeded
+ {
+ bool booleanAnd;
+ newTerm->GetBooleanAnd(&booleanAnd);
+ nsMsgSearchBoolExpression * newExpr = new nsMsgSearchBoolExpression (this, tempExpr, booleanAnd);
+ if (newExpr)
+ return newExpr;
+ else
+ delete tempExpr; // clean up memory allocation in case of failure
+ }
+ return this; // in case we failed to create a new expression, return self
+}
+
+
+// returns true or false depending on what the current expression evaluates to.
+bool nsMsgSearchBoolExpression::OfflineEvaluate(nsIMsgDBHdr *msgToMatch, const char *defaultCharset,
+ nsIMsgSearchScopeTerm *scope, nsIMsgDatabase *db, const char *headers,
+ uint32_t headerSize, bool Filtering)
+{
+ bool result = true; // always default to false positives
+ bool isAnd;
+
+ if (m_term) // do we contain just a search term?
+ {
+ nsMsgSearchOfflineMail::ProcessSearchTerm(msgToMatch, m_term,
+ defaultCharset, scope, db, headers, headerSize, Filtering, &result);
+ return result;
+ }
+
+ // otherwise we must recursively determine the value of our sub expressions
+
+ isAnd = (m_boolOp == nsMsgSearchBooleanOp::BooleanAND);
+
+ if (m_leftChild)
+ {
+ result = m_leftChild->OfflineEvaluate(msgToMatch, defaultCharset,
+ scope, db, headers, headerSize, Filtering);
+ if ( (result && !isAnd) || (!result && isAnd))
+ return result;
+ }
+
+ // If we got this far, either there was no leftChild (which is impossible)
+ // or we got (FALSE and OR) or (TRUE and AND) from the first result. That
+ // means the outcome depends entirely on the rightChild.
+ if (m_rightChild)
+ result = m_rightChild->OfflineEvaluate(msgToMatch, defaultCharset,
+ scope, db, headers, headerSize, Filtering);
+
+ return result;
+}
+
+// ### Maybe we can get rid of these because of our use of nsString???
+// constants used for online searching with IMAP/NNTP encoded search terms.
+// the + 1 is to account for null terminators we add at each stage of assembling the expression...
+const int sizeOfORTerm = 6+1; // 6 bytes if we are combining two sub expressions with an OR term
+const int sizeOfANDTerm = 1+1; // 1 byte if we are combining two sub expressions with an AND term
+
+int32_t nsMsgSearchBoolExpression::CalcEncodeStrSize()
+// recursively examine each sub expression and calculate a final size for the entire IMAP/NNTP encoding
+{
+ if (!m_term && (!m_leftChild || !m_rightChild)) // is the expression empty?
+ return 0;
+ if (m_term) // are we a leaf node?
+ return m_encodingStr.Length();
+ if (m_boolOp == nsMsgSearchBooleanOp::BooleanOR)
+ return sizeOfORTerm + m_leftChild->CalcEncodeStrSize() + m_rightChild->CalcEncodeStrSize();
+ if (m_boolOp == nsMsgSearchBooleanOp::BooleanAND)
+ return sizeOfANDTerm + m_leftChild->CalcEncodeStrSize() + m_rightChild->CalcEncodeStrSize();
+ return 0;
+}
+
+
+void nsMsgSearchBoolExpression::GenerateEncodeStr(nsCString * buffer)
+// recurively combine sub expressions to form a single IMAP/NNTP encoded string
+{
+ if ((!m_term && (!m_leftChild || !m_rightChild))) // is expression empty?
+ return;
+
+ if (m_term) // are we a leaf expression?
+ {
+ *buffer += m_encodingStr;
+ return;
+ }
+
+ // add encode strings of each sub expression
+ if (m_boolOp == nsMsgSearchBooleanOp::BooleanOR)
+ {
+ *buffer += " (OR";
+
+ m_leftChild->GenerateEncodeStr(buffer); // insert left expression into the buffer
+ m_rightChild->GenerateEncodeStr(buffer); // insert right expression into the buffer
+
+ // HACK ALERT!!! if last returned character in the buffer is now a ' ' then we need to remove it because we don't want
+ // a ' ' to preceded the closing paren in the OR encoding.
+ uint32_t lastCharPos = buffer->Length() - 1;
+ if (buffer->CharAt(lastCharPos) == ' ')
+ {
+ buffer->SetLength(lastCharPos);
+ }
+
+ *buffer += ')';
+ }
+ else if (m_boolOp == nsMsgSearchBooleanOp::BooleanAND)
+ {
+ m_leftChild->GenerateEncodeStr(buffer); // insert left expression
+ m_rightChild->GenerateEncodeStr(buffer);
+ }
+ return;
+}
+
+
+//-----------------------------------------------------------------------------
+//---------------- Adapter class for searching offline folders ----------------
+//-----------------------------------------------------------------------------
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgSearchOfflineMail, nsMsgSearchAdapter, nsIUrlListener)
+
+nsMsgSearchOfflineMail::nsMsgSearchOfflineMail (nsIMsgSearchScopeTerm *scope, nsISupportsArray *termList) : nsMsgSearchAdapter (scope, termList)
+{
+}
+
+nsMsgSearchOfflineMail::~nsMsgSearchOfflineMail ()
+{
+ // Database should have been closed when the scope term finished.
+ CleanUpScope();
+ NS_ASSERTION(!m_db, "db not closed");
+}
+
+
+nsresult nsMsgSearchOfflineMail::ValidateTerms ()
+{
+ return nsMsgSearchAdapter::ValidateTerms ();
+}
+
+
+nsresult nsMsgSearchOfflineMail::OpenSummaryFile ()
+{
+ nsCOMPtr <nsIMsgDatabase> mailDB ;
+
+ nsresult err = NS_OK;
+ // do password protection of local cache thing.
+#ifdef DOING_FOLDER_CACHE_PASSWORDS
+ if (m_scope->m_folder && m_scope->m_folder->UserNeedsToAuthenticateForFolder(false) && m_scope->m_folder->GetMaster()->PromptForHostPassword(m_scope->m_frame->GetContext(), m_scope->m_folder) != 0)
+ {
+ m_scope->m_frame->StopRunning();
+ return SearchError_ScopeDone;
+ }
+#endif
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ nsCOMPtr <nsIMsgFolder> scopeFolder;
+ err = m_scope->GetFolder(getter_AddRefs(scopeFolder));
+ if (NS_SUCCEEDED(err) && scopeFolder)
+ {
+ err = scopeFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(m_db));
+ }
+ else
+ return err; // not sure why m_folder wouldn't be set.
+
+ if (NS_SUCCEEDED(err))
+ return NS_OK;
+
+ if ((err == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) ||
+ (err == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE))
+ {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(scopeFolder, &err);
+ if (NS_SUCCEEDED(err) && localFolder)
+ {
+ nsCOMPtr<nsIMsgSearchSession> searchSession;
+ m_scope->GetSearchSession(getter_AddRefs(searchSession));
+ if (searchSession)
+ {
+ nsCOMPtr <nsIMsgWindow> searchWindow;
+
+ searchSession->GetWindow(getter_AddRefs(searchWindow));
+ searchSession->PauseSearch();
+ localFolder->ParseFolder(searchWindow, this);
+ }
+ }
+ }
+ else
+ {
+ NS_ASSERTION(false, "unexpected error opening db");
+ }
+
+ return err;
+}
+
+
+nsresult
+nsMsgSearchOfflineMail::MatchTermsForFilter(nsIMsgDBHdr *msgToMatch,
+ nsISupportsArray *termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm * scope,
+ nsIMsgDatabase * db,
+ const char * headers,
+ uint32_t headerSize,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult)
+{
+ return MatchTerms(msgToMatch, termList, defaultCharset, scope, db, headers, headerSize, true, aExpressionTree, pResult);
+}
+
+// static method which matches a header against a list of search terms.
+nsresult
+nsMsgSearchOfflineMail::MatchTermsForSearch(nsIMsgDBHdr *msgToMatch,
+ nsISupportsArray* termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm *scope,
+ nsIMsgDatabase *db,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult)
+{
+
+ return MatchTerms(msgToMatch, termList, defaultCharset, scope, db, nullptr, 0, false, aExpressionTree, pResult);
+}
+
+nsresult nsMsgSearchOfflineMail::ConstructExpressionTree(nsISupportsArray * termList,
+ uint32_t termCount,
+ uint32_t &aStartPosInList,
+ nsMsgSearchBoolExpression ** aExpressionTree)
+{
+ nsMsgSearchBoolExpression * finalExpression = *aExpressionTree;
+
+ if (!finalExpression)
+ finalExpression = new nsMsgSearchBoolExpression();
+
+ while (aStartPosInList < termCount)
+ {
+ nsCOMPtr<nsIMsgSearchTerm> pTerm;
+ termList->QueryElementAt(aStartPosInList, NS_GET_IID(nsIMsgSearchTerm), (void **)getter_AddRefs(pTerm));
+ NS_ASSERTION (pTerm, "couldn't get term to match");
+
+ bool beginsGrouping;
+ bool endsGrouping;
+ pTerm->GetBeginsGrouping(&beginsGrouping);
+ pTerm->GetEndsGrouping(&endsGrouping);
+
+ if (beginsGrouping)
+ {
+ //temporarily turn off the grouping for our recursive call
+ pTerm->SetBeginsGrouping(false);
+ nsMsgSearchBoolExpression * innerExpression = new nsMsgSearchBoolExpression();
+
+ // the first search term in the grouping is the one that holds the operator for how this search term
+ // should be joined with the expressions to it's left.
+ bool booleanAnd;
+ pTerm->GetBooleanAnd(&booleanAnd);
+
+ // now add this expression tree to our overall expression tree...
+ finalExpression = nsMsgSearchBoolExpression::AddExpressionTree(finalExpression, innerExpression, booleanAnd);
+
+ // recursively process this inner expression
+ ConstructExpressionTree(termList, termCount, aStartPosInList,
+ &finalExpression->m_rightChild);
+
+ // undo our damage
+ pTerm->SetBeginsGrouping(true);
+
+ }
+ else
+ {
+ finalExpression = nsMsgSearchBoolExpression::AddSearchTerm(finalExpression, pTerm, nullptr); // add the term to the expression tree
+
+ if (endsGrouping)
+ break;
+ }
+
+ aStartPosInList++;
+ } // while we still have terms to process in this group
+
+ *aExpressionTree = finalExpression;
+
+ return NS_OK;
+}
+
+nsresult nsMsgSearchOfflineMail::ProcessSearchTerm(nsIMsgDBHdr *msgToMatch,
+ nsIMsgSearchTerm * aTerm,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm * scope,
+ nsIMsgDatabase * db,
+ const char * headers,
+ uint32_t headerSize,
+ bool Filtering,
+ bool *pResult)
+{
+ nsresult err = NS_OK;
+ nsCString recipients;
+ nsCString ccList;
+ nsCString matchString;
+ nsCString msgCharset;
+ const char *charset;
+ bool charsetOverride = false; /* XXX BUG 68706 */
+ uint32_t msgFlags;
+ bool result;
+ bool matchAll;
+
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ aTerm->GetMatchAll(&matchAll);
+ if (matchAll)
+ {
+ *pResult = true;
+ return NS_OK;
+ }
+ *pResult = false;
+
+ nsMsgSearchAttribValue attrib;
+ aTerm->GetAttrib(&attrib);
+ msgToMatch->GetCharset(getter_Copies(msgCharset));
+ charset = msgCharset.get();
+ if (!charset || !*charset)
+ charset = (const char*)defaultCharset;
+ msgToMatch->GetFlags(&msgFlags);
+
+ switch (attrib)
+ {
+ case nsMsgSearchAttrib::Sender:
+ msgToMatch->GetAuthor(getter_Copies(matchString));
+ err = aTerm->MatchRfc822String(matchString, charset, &result);
+ break;
+ case nsMsgSearchAttrib::Subject:
+ {
+ msgToMatch->GetSubject(getter_Copies(matchString) /* , true */);
+ if (msgFlags & nsMsgMessageFlags::HasRe)
+ {
+ // Make sure we pass along the "Re: " part of the subject if this is a reply.
+ nsCString reString;
+ reString.Assign("Re: ");
+ reString.Append(matchString);
+ err = aTerm->MatchRfc2047String(reString, charset, charsetOverride, &result);
+ }
+ else
+ err = aTerm->MatchRfc2047String(matchString, charset, charsetOverride, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::ToOrCC:
+ {
+ bool boolKeepGoing;
+ aTerm->GetMatchAllBeforeDeciding(&boolKeepGoing);
+ msgToMatch->GetRecipients(getter_Copies(recipients));
+ err = aTerm->MatchRfc822String(recipients, charset, &result);
+ if (boolKeepGoing == result)
+ {
+ msgToMatch->GetCcList(getter_Copies(ccList));
+ err = aTerm->MatchRfc822String(ccList, charset, &result);
+ }
+ break;
+ }
+ case nsMsgSearchAttrib::AllAddresses:
+ {
+ bool boolKeepGoing;
+ aTerm->GetMatchAllBeforeDeciding(&boolKeepGoing);
+ msgToMatch->GetRecipients(getter_Copies(recipients));
+ err = aTerm->MatchRfc822String(recipients, charset, &result);
+ if (boolKeepGoing == result)
+ {
+ msgToMatch->GetCcList(getter_Copies(ccList));
+ err = aTerm->MatchRfc822String(ccList, charset, &result);
+ }
+ if (boolKeepGoing == result)
+ {
+ msgToMatch->GetAuthor(getter_Copies(matchString));
+ err = aTerm->MatchRfc822String(matchString, charset, &result);
+ }
+ if (boolKeepGoing == result)
+ {
+ nsCString bccList;
+ msgToMatch->GetBccList(getter_Copies(bccList));
+ err = aTerm->MatchRfc822String(bccList, charset, &result);
+ }
+ break;
+ }
+ case nsMsgSearchAttrib::Body:
+ {
+ uint64_t messageOffset;
+ uint32_t lineCount;
+ msgToMatch->GetMessageOffset(&messageOffset);
+ msgToMatch->GetLineCount(&lineCount);
+ err = aTerm->MatchBody (scope, messageOffset, lineCount, charset, msgToMatch, db, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::Date:
+ {
+ PRTime date;
+ msgToMatch->GetDate(&date);
+ err = aTerm->MatchDate (date, &result);
+
+ break;
+ }
+ case nsMsgSearchAttrib::HasAttachmentStatus:
+ case nsMsgSearchAttrib::MsgStatus:
+ err = aTerm->MatchStatus (msgFlags, &result);
+ break;
+ case nsMsgSearchAttrib::Priority:
+ {
+ nsMsgPriorityValue msgPriority;
+ msgToMatch->GetPriority(&msgPriority);
+ err = aTerm->MatchPriority (msgPriority, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::Size:
+ {
+ uint32_t messageSize;
+ msgToMatch->GetMessageSize(&messageSize);
+ err = aTerm->MatchSize (messageSize, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::To:
+ msgToMatch->GetRecipients(getter_Copies(recipients));
+ err = aTerm->MatchRfc822String(recipients, charset, &result);
+ break;
+ case nsMsgSearchAttrib::CC:
+ msgToMatch->GetCcList(getter_Copies(ccList));
+ err = aTerm->MatchRfc822String(ccList, charset, &result);
+ break;
+ case nsMsgSearchAttrib::AgeInDays:
+ {
+ PRTime date;
+ msgToMatch->GetDate(&date);
+ err = aTerm->MatchAge (date, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::Label:
+ {
+ nsMsgLabelValue label;
+ msgToMatch->GetLabel(&label);
+ err = aTerm->MatchLabel(label, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::Keywords:
+ {
+ nsCString keywords;
+ nsMsgLabelValue label;
+ msgToMatch->GetStringProperty("keywords", getter_Copies(keywords));
+ msgToMatch->GetLabel(&label);
+ if (label >= 1)
+ {
+ if (!keywords.IsEmpty())
+ keywords.Append(' ');
+ keywords.Append("$label");
+ keywords.Append(label + '0');
+ }
+ err = aTerm->MatchKeyword(keywords, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::JunkStatus:
+ {
+ nsCString junkScoreStr;
+ msgToMatch->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ err = aTerm->MatchJunkStatus(junkScoreStr.get(), &result);
+ break;
+ }
+ case nsMsgSearchAttrib::JunkPercent:
+ {
+ // When the junk status is set by the plugin, use junkpercent (if available)
+ // Otherwise, use the limits (0 or 100) depending on the junkscore.
+ uint32_t junkPercent;
+ nsresult rv;
+ nsCString junkScoreOriginStr;
+ nsCString junkPercentStr;
+ msgToMatch->GetStringProperty("junkscoreorigin", getter_Copies(junkScoreOriginStr));
+ msgToMatch->GetStringProperty("junkpercent", getter_Copies(junkPercentStr));
+ if ( junkScoreOriginStr.EqualsLiteral("plugin") &&
+ !junkPercentStr.IsEmpty())
+ {
+ junkPercent = junkPercentStr.ToInteger(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ nsCString junkScoreStr;
+ msgToMatch->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ // When junk status is not set (uncertain) we'll set the value to ham.
+ if (junkScoreStr.IsEmpty())
+ junkPercent = nsIJunkMailPlugin::IS_HAM_SCORE;
+ else
+ {
+ junkPercent = junkScoreStr.ToInteger(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ err = aTerm->MatchJunkPercent(junkPercent, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::JunkScoreOrigin:
+ {
+ nsCString junkScoreOriginStr;
+ msgToMatch->GetStringProperty("junkscoreorigin", getter_Copies(junkScoreOriginStr));
+ err = aTerm->MatchJunkScoreOrigin(junkScoreOriginStr.get(), &result);
+ break;
+ }
+ case nsMsgSearchAttrib::HdrProperty:
+ {
+ err = aTerm->MatchHdrProperty(msgToMatch, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::Uint32HdrProperty:
+ {
+ err = aTerm->MatchUint32HdrProperty(msgToMatch, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::Custom:
+ {
+ err = aTerm->MatchCustom(msgToMatch, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::FolderFlag:
+ {
+ err = aTerm->MatchFolderFlag(msgToMatch, &result);
+ break;
+ }
+ default:
+ // XXX todo
+ // for the temporary return receipts filters, we use a custom header for Content-Type
+ // but unlike the other custom headers, this one doesn't show up in the search / filter
+ // UI. we set the attrib to be nsMsgSearchAttrib::OtherHeader, where as for user
+ // defined custom headers start at nsMsgSearchAttrib::OtherHeader + 1
+ // Not sure if there is a better way to do this yet. Maybe reserve the last
+ // custom header for ::Content-Type? But if we do, make sure that change
+ // doesn't cause nsMsgFilter::GetTerm() to change, and start making us
+ // ask IMAP servers for the Content-Type header on all messages.
+ if (attrib >= nsMsgSearchAttrib::OtherHeader &&
+ attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes)
+ {
+ uint32_t lineCount;
+ msgToMatch->GetLineCount(&lineCount);
+ uint64_t messageOffset;
+ msgToMatch->GetMessageOffset(&messageOffset);
+ err = aTerm->MatchArbitraryHeader(scope, lineCount,charset,
+ charsetOverride, msgToMatch, db,
+ headers, headerSize, Filtering,
+ &result);
+ }
+ else {
+ err = NS_ERROR_INVALID_ARG; // ### was SearchError_InvalidAttribute
+ result = false;
+ }
+ }
+
+ *pResult = result;
+ return err;
+}
+
+nsresult nsMsgSearchOfflineMail::MatchTerms(nsIMsgDBHdr *msgToMatch,
+ nsISupportsArray * termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm * scope,
+ nsIMsgDatabase * db,
+ const char * headers,
+ uint32_t headerSize,
+ bool Filtering,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult)
+{
+ NS_ENSURE_ARG(aExpressionTree);
+ nsresult err;
+
+ if (!*aExpressionTree)
+ {
+ uint32_t initialPos = 0;
+ uint32_t count;
+ termList->Count(&count);
+ err = ConstructExpressionTree(termList, count, initialPos, aExpressionTree);
+ if (NS_FAILED(err))
+ return err;
+ }
+
+ // evaluate the expression tree and return the result
+ *pResult = (*aExpressionTree)
+ ? (*aExpressionTree)->OfflineEvaluate(msgToMatch,
+ defaultCharset, scope, db, headers, headerSize, Filtering)
+ :true; // vacuously true...
+
+ return NS_OK;
+}
+
+nsresult nsMsgSearchOfflineMail::Search(bool *aDone)
+{
+ nsresult err = NS_OK;
+
+ NS_ENSURE_ARG(aDone);
+ nsresult dbErr = NS_OK;
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
+ nsMsgSearchBoolExpression *expressionTree = nullptr;
+
+ const uint32_t kTimeSliceInMS = 200;
+
+ *aDone = false;
+ // Try to open the DB lazily. This will set up a parser if one is required
+ if (!m_db)
+ err = OpenSummaryFile ();
+ if (!m_db) // must be reparsing.
+ return err;
+
+ // Reparsing is unnecessary or completed
+ if (NS_SUCCEEDED(err))
+ {
+ if (!m_listContext)
+ dbErr = m_db->ReverseEnumerateMessages(getter_AddRefs(m_listContext));
+ if (NS_SUCCEEDED(dbErr) && m_listContext)
+ {
+ PRIntervalTime startTime = PR_IntervalNow();
+ while (!*aDone) // we'll break out of the loop after kTimeSliceInMS milliseconds
+ {
+ nsCOMPtr<nsISupports> currentItem;
+
+ dbErr = m_listContext->GetNext(getter_AddRefs(currentItem));
+ if(NS_SUCCEEDED(dbErr))
+ {
+ msgDBHdr = do_QueryInterface(currentItem, &dbErr);
+ }
+ if (NS_FAILED(dbErr))
+ *aDone = true; //###phil dbErr is dropped on the floor. just note that we did have an error so we'll clean up later
+ else
+ {
+ bool match = false;
+ nsAutoString nullCharset, folderCharset;
+ GetSearchCharsets(nullCharset, folderCharset);
+ NS_ConvertUTF16toUTF8 charset(folderCharset);
+ // Is this message a hit?
+ err = MatchTermsForSearch (msgDBHdr, m_searchTerms, charset.get(), m_scope, m_db, &expressionTree, &match);
+ // Add search hits to the results list
+ if (NS_SUCCEEDED(err) && match)
+ {
+ AddResultElement (msgDBHdr);
+ }
+ PRIntervalTime elapsedTime = PR_IntervalNow() - startTime;
+ // check if more than kTimeSliceInMS milliseconds have elapsed in this time slice started
+ if (PR_IntervalToMilliseconds(elapsedTime) > kTimeSliceInMS)
+ break;
+ }
+ }
+ }
+ }
+ else
+ *aDone = true; // we couldn't open up the DB. This is an unrecoverable error so mark the scope as done.
+
+ delete expressionTree;
+
+ // in the past an error here would cause an "infinite" search because the url would continue to run...
+ // i.e. if we couldn't open the database, it returns an error code but the caller of this function says, oh,
+ // we did not finish so continue...what we really want is to treat this current scope as done
+ if (*aDone)
+ CleanUpScope(); // Do clean up for end-of-scope processing
+ return err;
+}
+
+void nsMsgSearchOfflineMail::CleanUpScope()
+{
+ // Let go of the DB when we're done with it so we don't kill the db cache
+ if (m_db)
+ {
+ m_listContext = nullptr;
+ m_db->Close(false);
+ }
+ m_db = nullptr;
+
+ if (m_scope)
+ m_scope->CloseInputStream();
+}
+
+NS_IMETHODIMP nsMsgSearchOfflineMail::AddResultElement (nsIMsgDBHdr *pHeaders)
+{
+ nsresult err = NS_OK;
+
+ nsCOMPtr<nsIMsgSearchSession> searchSession;
+ m_scope->GetSearchSession(getter_AddRefs(searchSession));
+ if (searchSession)
+ {
+ nsCOMPtr <nsIMsgFolder> scopeFolder;
+ err = m_scope->GetFolder(getter_AddRefs(scopeFolder));
+ searchSession->AddSearchHit(pHeaders, scopeFolder);
+ }
+ return err;
+}
+
+NS_IMETHODIMP
+nsMsgSearchOfflineMail::Abort ()
+{
+ // Let go of the DB when we're done with it so we don't kill the db cache
+ if (m_db)
+ m_db->Close(true /* commit in case we downloaded new headers */);
+ m_db = nullptr;
+ return nsMsgSearchAdapter::Abort ();
+}
+
+/* void OnStartRunningUrl (in nsIURI url); */
+NS_IMETHODIMP nsMsgSearchOfflineMail::OnStartRunningUrl(nsIURI *url)
+{
+ return NS_OK;
+}
+
+/* void OnStopRunningUrl (in nsIURI url, in nsresult aExitCode); */
+NS_IMETHODIMP nsMsgSearchOfflineMail::OnStopRunningUrl(nsIURI *url, nsresult aExitCode)
+{
+ nsCOMPtr<nsIMsgSearchSession> searchSession;
+ if (m_scope)
+ m_scope->GetSearchSession(getter_AddRefs(searchSession));
+ if (searchSession)
+ searchSession->ResumeSearch();
+
+ return NS_OK;
+}
+
+nsMsgSearchOfflineNews::nsMsgSearchOfflineNews (nsIMsgSearchScopeTerm *scope, nsISupportsArray *termList) : nsMsgSearchOfflineMail (scope, termList)
+{
+}
+
+
+nsMsgSearchOfflineNews::~nsMsgSearchOfflineNews ()
+{
+}
+
+
+nsresult nsMsgSearchOfflineNews::OpenSummaryFile ()
+{
+ nsresult err = NS_OK;
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ nsCOMPtr <nsIMsgFolder> scopeFolder;
+ err = m_scope->GetFolder(getter_AddRefs(scopeFolder));
+ // code here used to check if offline store existed, which breaks offline news.
+ if (NS_SUCCEEDED(err) && scopeFolder)
+ err = scopeFolder->GetMsgDatabase(getter_AddRefs(m_db));
+ return err;
+}
+
+nsresult nsMsgSearchOfflineNews::ValidateTerms ()
+{
+ return nsMsgSearchOfflineMail::ValidateTerms ();
+}
+
+// local helper functions to set subsets of the validity table
+
+nsresult SetJunk(nsIMsgSearchValidityTable* aTable)
+{
+ NS_ENSURE_ARG_POINTER(aTable);
+
+ aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1);
+ aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1);
+ aTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1);
+ aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1);
+ aTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1);
+ aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1);
+ aTable->SetEnabled(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1);
+
+ aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1);
+ aTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1);
+ aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1);
+ aTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1);
+ aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1);
+
+ aTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1);
+ aTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1);
+ aTable->SetEnabled(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1);
+
+ return NS_OK;
+}
+
+nsresult SetBody(nsIMsgSearchValidityTable* aTable)
+{
+ NS_ENSURE_ARG_POINTER(aTable);
+ aTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1);
+
+ return NS_OK;
+}
+
+// set the base validity table values for local news
+nsresult SetLocalNews(nsIMsgSearchValidityTable* aTable)
+{
+ NS_ENSURE_ARG_POINTER(aTable);
+
+ aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+
+ aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+
+ aTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+
+ aTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1);
+
+ aTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1);
+
+ aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+
+ aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ aTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ return NS_OK;
+
+}
+
+nsresult nsMsgSearchValidityManager::InitLocalNewsTable()
+{
+ NS_ASSERTION (nullptr == m_localNewsTable, "already have local news validity table");
+ nsresult rv = NewTable(getter_AddRefs(m_localNewsTable));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetLocalNews(m_localNewsTable);
+}
+
+nsresult nsMsgSearchValidityManager::InitLocalNewsBodyTable()
+{
+ NS_ASSERTION (nullptr == m_localNewsBodyTable, "already have local news+body validity table");
+ nsresult rv = NewTable(getter_AddRefs(m_localNewsBodyTable));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetLocalNews(m_localNewsBodyTable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetBody(m_localNewsBodyTable);
+}
+
+nsresult nsMsgSearchValidityManager::InitLocalNewsJunkTable()
+{
+ NS_ASSERTION (nullptr == m_localNewsJunkTable, "already have local news+junk validity table");
+ nsresult rv = NewTable(getter_AddRefs(m_localNewsJunkTable));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetLocalNews(m_localNewsJunkTable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetJunk(m_localNewsJunkTable);
+}
+
+nsresult nsMsgSearchValidityManager::InitLocalNewsJunkBodyTable()
+{
+ NS_ASSERTION (nullptr == m_localNewsJunkBodyTable, "already have local news+junk+body validity table");
+ nsresult rv = NewTable(getter_AddRefs(m_localNewsJunkBodyTable));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetLocalNews(m_localNewsJunkBodyTable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetJunk(m_localNewsJunkBodyTable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetBody(m_localNewsJunkBodyTable);
+}
diff --git a/mailnews/base/search/src/nsMsgLocalSearch.h b/mailnews/base/search/src/nsMsgLocalSearch.h
new file mode 100644
index 000000000..020d7b579
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgLocalSearch.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgLocalSearch_H
+#define _nsMsgLocalSearch_H
+
+// inherit interface here
+#include "mozilla/Attributes.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIUrlListener.h"
+
+// inherit base implementation
+#include "nsMsgSearchAdapter.h"
+#include "nsISimpleEnumerator.h"
+
+
+class nsIMsgDBHdr;
+class nsIMsgSearchScopeTerm;
+class nsIMsgFolder;
+class nsMsgSearchBoolExpression;
+
+class nsMsgSearchOfflineMail : public nsMsgSearchAdapter, public nsIUrlListener
+{
+public:
+ nsMsgSearchOfflineMail (nsIMsgSearchScopeTerm*, nsISupportsArray *);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIURLLISTENER
+
+ NS_IMETHOD ValidateTerms () override;
+ NS_IMETHOD Search (bool *aDone) override;
+ NS_IMETHOD Abort () override;
+ NS_IMETHOD AddResultElement (nsIMsgDBHdr *) override;
+
+ static nsresult MatchTermsForFilter(nsIMsgDBHdr * msgToMatch,
+ nsISupportsArray *termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm *scope,
+ nsIMsgDatabase * db,
+ const char * headers,
+ uint32_t headerSize,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult);
+
+ static nsresult MatchTermsForSearch(nsIMsgDBHdr * msgTomatch,
+ nsISupportsArray * termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm *scope,
+ nsIMsgDatabase *db,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult);
+
+ virtual nsresult OpenSummaryFile ();
+
+ static nsresult ProcessSearchTerm(nsIMsgDBHdr *msgToMatch,
+ nsIMsgSearchTerm * aTerm,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm * scope,
+ nsIMsgDatabase * db,
+ const char * headers,
+ uint32_t headerSize,
+ bool Filtering,
+ bool *pResult);
+protected:
+ virtual ~nsMsgSearchOfflineMail();
+ static nsresult MatchTerms(nsIMsgDBHdr *msgToMatch,
+ nsISupportsArray *termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm *scope,
+ nsIMsgDatabase * db,
+ const char * headers,
+ uint32_t headerSize,
+ bool ForFilters,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult);
+
+ static nsresult ConstructExpressionTree(nsISupportsArray * termList,
+ uint32_t termCount,
+ uint32_t &aStartPosInList,
+ nsMsgSearchBoolExpression ** aExpressionTree);
+
+ nsCOMPtr <nsIMsgDatabase> m_db;
+ nsCOMPtr<nsISimpleEnumerator> m_listContext;
+ void CleanUpScope();
+};
+
+
+class nsMsgSearchOfflineNews : public nsMsgSearchOfflineMail
+{
+public:
+ nsMsgSearchOfflineNews (nsIMsgSearchScopeTerm*, nsISupportsArray *);
+ virtual ~nsMsgSearchOfflineNews ();
+ NS_IMETHOD ValidateTerms () override;
+
+ virtual nsresult OpenSummaryFile () override;
+};
+
+
+
+#endif
+
diff --git a/mailnews/base/search/src/nsMsgSearchAdapter.cpp b/mailnews/base/search/src/nsMsgSearchAdapter.cpp
new file mode 100644
index 000000000..a6f877830
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchAdapter.cpp
@@ -0,0 +1,1332 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsTextFormatter.h"
+#include "nsMsgSearchCore.h"
+#include "nsMsgSearchAdapter.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsMsgI18N.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsMsgSearchTerm.h"
+#include "nsMsgSearchBoolExpression.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "prprf.h"
+#include "mozilla/UniquePtr.h"
+#include "prmem.h"
+#include "MailNewsTypes.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsMsgMessageFlags.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsAlgorithm.h"
+#include <algorithm>
+#include "mozilla/Attributes.h"
+
+// This stuff lives in the base class because the IMAP search syntax
+// is used by the Dredd SEARCH command as well as IMAP itself
+
+// km - the NOT and HEADER strings are not encoded with a trailing
+// <space> because they always precede a mnemonic that has a
+// preceding <space> and double <space> characters cause some
+// imap servers to return an error.
+const char *nsMsgSearchAdapter::m_kImapBefore = " SENTBEFORE ";
+const char *nsMsgSearchAdapter::m_kImapBody = " BODY ";
+const char *nsMsgSearchAdapter::m_kImapCC = " CC ";
+const char *nsMsgSearchAdapter::m_kImapFrom = " FROM ";
+const char *nsMsgSearchAdapter::m_kImapNot = " NOT";
+const char *nsMsgSearchAdapter::m_kImapUnDeleted= " UNDELETED";
+const char *nsMsgSearchAdapter::m_kImapOr = " OR";
+const char *nsMsgSearchAdapter::m_kImapSince = " SENTSINCE ";
+const char *nsMsgSearchAdapter::m_kImapSubject = " SUBJECT ";
+const char *nsMsgSearchAdapter::m_kImapTo = " TO ";
+const char *nsMsgSearchAdapter::m_kImapHeader = " HEADER";
+const char *nsMsgSearchAdapter::m_kImapAnyText = " TEXT ";
+const char *nsMsgSearchAdapter::m_kImapKeyword = " KEYWORD ";
+const char *nsMsgSearchAdapter::m_kNntpKeywords = " KEYWORDS "; //ggrrrr...
+const char *nsMsgSearchAdapter::m_kImapSentOn = " SENTON ";
+const char *nsMsgSearchAdapter::m_kImapSeen = " SEEN ";
+const char *nsMsgSearchAdapter::m_kImapAnswered = " ANSWERED ";
+const char *nsMsgSearchAdapter::m_kImapNotSeen = " UNSEEN ";
+const char *nsMsgSearchAdapter::m_kImapNotAnswered = " UNANSWERED ";
+const char *nsMsgSearchAdapter::m_kImapCharset = " CHARSET ";
+const char *nsMsgSearchAdapter::m_kImapSizeSmaller = " SMALLER ";
+const char *nsMsgSearchAdapter::m_kImapSizeLarger = " LARGER ";
+const char *nsMsgSearchAdapter::m_kImapNew = " NEW ";
+const char *nsMsgSearchAdapter::m_kImapNotNew = " OLD SEEN ";
+const char *nsMsgSearchAdapter::m_kImapFlagged = " FLAGGED ";
+const char *nsMsgSearchAdapter::m_kImapNotFlagged = " UNFLAGGED ";
+
+#define PREF_CUSTOM_HEADERS "mailnews.customHeaders"
+
+NS_IMETHODIMP nsMsgSearchAdapter::FindTargetFolder(const nsMsgResultElement *,nsIMsgFolder * *)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgSearchAdapter::ModifyResultElement(nsMsgResultElement *, nsMsgSearchValue *)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgSearchAdapter::OpenResultElement(nsMsgResultElement *)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgSearchAdapter, nsIMsgSearchAdapter)
+
+nsMsgSearchAdapter::nsMsgSearchAdapter(nsIMsgSearchScopeTerm *scope, nsISupportsArray *searchTerms)
+ : m_searchTerms(searchTerms)
+{
+ m_scope = scope;
+}
+
+nsMsgSearchAdapter::~nsMsgSearchAdapter()
+{
+}
+
+NS_IMETHODIMP nsMsgSearchAdapter::ClearScope()
+{
+ if (m_scope)
+ {
+ m_scope->CloseInputStream();
+ m_scope = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchAdapter::ValidateTerms ()
+{
+ // all this used to do is check if the object had been deleted - we can skip that.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchAdapter::Abort ()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+}
+NS_IMETHODIMP nsMsgSearchAdapter::Search (bool *aDone)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchAdapter::SendUrl ()
+{
+ return NS_OK;
+}
+
+/* void CurrentUrlDone (in nsresult exitCode); */
+NS_IMETHODIMP nsMsgSearchAdapter::CurrentUrlDone(nsresult exitCode)
+{
+ // base implementation doesn't need to do anything.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchAdapter::GetEncoding (char **encoding)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchAdapter::AddResultElement (nsIMsgDBHdr *pHeaders)
+{
+ NS_ASSERTION(false, "shouldn't call this base class impl");
+ return NS_ERROR_FAILURE;
+}
+
+
+NS_IMETHODIMP nsMsgSearchAdapter::AddHit(nsMsgKey key)
+{
+ NS_ASSERTION(false, "shouldn't call this base class impl");
+ return NS_ERROR_FAILURE;
+}
+
+
+char *
+nsMsgSearchAdapter::GetImapCharsetParam(const char16_t *destCharset)
+{
+ char *result = nullptr;
+
+ // Specify a character set unless we happen to be US-ASCII.
+ if (NS_strcmp(destCharset, u"us-ascii"))
+ result = PR_smprintf("%s%s", nsMsgSearchAdapter::m_kImapCharset, NS_ConvertUTF16toUTF8(destCharset).get());
+
+ return result;
+}
+
+/*
+ 09/21/2000 - taka@netscape.com
+ This method is bogus. Escape must be done against char * not char16_t *
+ should be rewritten later.
+ for now, just duplicate the string.
+*/
+char16_t *nsMsgSearchAdapter::EscapeSearchUrl (const char16_t *nntpCommand)
+{
+ return nntpCommand ? NS_strdup(nntpCommand) : nullptr;
+}
+
+/*
+ 09/21/2000 - taka@netscape.com
+ This method is bogus. Escape must be done against char * not char16_t *
+ should be rewritten later.
+ for now, just duplicate the string.
+*/
+char16_t *
+nsMsgSearchAdapter::EscapeImapSearchProtocol(const char16_t *imapCommand)
+{
+ return imapCommand ? NS_strdup(imapCommand) : nullptr;
+}
+
+/*
+ 09/21/2000 - taka@netscape.com
+ This method is bogus. Escape must be done against char * not char16_t *
+ should be rewritten later.
+ for now, just duplicate the string.
+*/
+char16_t *
+nsMsgSearchAdapter::EscapeQuoteImapSearchProtocol(const char16_t *imapCommand)
+{
+ return imapCommand ? NS_strdup(imapCommand) : nullptr;
+}
+
+char *nsMsgSearchAdapter::UnEscapeSearchUrl (const char *commandSpecificData)
+{
+ char *result = (char*) PR_Malloc (strlen(commandSpecificData) + 1);
+ if (result)
+ {
+ char *resultPtr = result;
+ while (1)
+ {
+ char ch = *commandSpecificData++;
+ if (!ch)
+ break;
+ if (ch == '\\')
+ {
+ char scratchBuf[3];
+ scratchBuf[0] = (char) *commandSpecificData++;
+ scratchBuf[1] = (char) *commandSpecificData++;
+ scratchBuf[2] = '\0';
+ unsigned int accum = 0;
+ sscanf (scratchBuf, "%X", &accum);
+ *resultPtr++ = (char) accum;
+ }
+ else
+ *resultPtr++ = ch;
+ }
+ *resultPtr = '\0';
+ }
+ return result;
+}
+
+
+nsresult
+nsMsgSearchAdapter::GetSearchCharsets(nsAString &srcCharset, nsAString &dstCharset)
+{
+ nsresult rv;
+
+ if (m_defaultCharset.IsEmpty())
+ {
+ m_forceAsciiSearch = false; // set the default value in case of error
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIPrefLocalizedString> localizedstr;
+ rv = prefs->GetComplexValue("mailnews.view_default_charset", NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(localizedstr));
+ if (NS_SUCCEEDED(rv))
+ localizedstr->GetData(getter_Copies(m_defaultCharset));
+
+ prefs->GetBoolPref("mailnews.force_ascii_search", &m_forceAsciiSearch);
+ }
+ }
+ srcCharset = m_defaultCharset.IsEmpty() ?
+ static_cast<const nsAString&>(NS_LITERAL_STRING("ISO-8859-1")) :
+ m_defaultCharset;
+
+ if (m_scope)
+ {
+ // ### DMB is there a way to get the charset for the "window"?
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = m_scope->GetFolder(getter_AddRefs(folder));
+
+ // Ask the newsgroup/folder for its csid.
+ if (NS_SUCCEEDED(rv) && folder)
+ {
+ nsCString folderCharset;
+ folder->GetCharset(folderCharset);
+ dstCharset.Append(NS_ConvertASCIItoUTF16(folderCharset));
+ }
+ }
+ else
+ dstCharset.Assign(srcCharset);
+
+ // If
+ // the destination is still CS_DEFAULT, make the destination match
+ // the source. (CS_DEFAULT is an indication that the charset
+ // was undefined or unavailable.)
+ // ### well, it's not really anymore. Is there an equivalent?
+ if (dstCharset.Equals(m_defaultCharset))
+ {
+ dstCharset.Assign(srcCharset);
+ }
+
+ if (m_forceAsciiSearch)
+ {
+ // Special cases to use in order to force US-ASCII searching with Latin1
+ // or MacRoman text. Eurgh. This only has to happen because IMAP
+ // and Dredd servers currently (4/23/97) only support US-ASCII.
+ //
+ // If the dest csid is ISO Latin 1 or MacRoman, attempt to convert the
+ // source text to US-ASCII. (Not for now.)
+ // if ((dst_csid == CS_LATIN1) || (dst_csid == CS_MAC_ROMAN))
+ dstCharset.AssignLiteral("us-ascii");
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgSearchAdapter::EncodeImapTerm (nsIMsgSearchTerm *term, bool reallyDredd, const char16_t *srcCharset, const char16_t *destCharset, char **ppOutTerm)
+{
+ NS_ENSURE_ARG_POINTER(term);
+ NS_ENSURE_ARG_POINTER(ppOutTerm);
+
+ nsresult err = NS_OK;
+ bool useNot = false;
+ bool useQuotes = false;
+ bool ignoreValue = false;
+ nsAutoCString arbitraryHeader;
+ const char *whichMnemonic = nullptr;
+ const char *orHeaderMnemonic = nullptr;
+
+ *ppOutTerm = nullptr;
+
+ nsCOMPtr <nsIMsgSearchValue> searchValue;
+ nsresult rv = term->GetValue(getter_AddRefs(searchValue));
+
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsMsgSearchOpValue op;
+ term->GetOp(&op);
+
+ if (op == nsMsgSearchOp::DoesntContain || op == nsMsgSearchOp::Isnt)
+ useNot = true;
+
+ nsMsgSearchAttribValue attrib;
+ term->GetAttrib(&attrib);
+
+ switch (attrib)
+ {
+ case nsMsgSearchAttrib::ToOrCC:
+ orHeaderMnemonic = m_kImapCC;
+ // fall through to case nsMsgSearchAttrib::To:
+ MOZ_FALLTHROUGH;
+ case nsMsgSearchAttrib::To:
+ whichMnemonic = m_kImapTo;
+ break;
+ case nsMsgSearchAttrib::CC:
+ whichMnemonic = m_kImapCC;
+ break;
+ case nsMsgSearchAttrib::Sender:
+ whichMnemonic = m_kImapFrom;
+ break;
+ case nsMsgSearchAttrib::Subject:
+ whichMnemonic = m_kImapSubject;
+ break;
+ case nsMsgSearchAttrib::Body:
+ whichMnemonic = m_kImapBody;
+ break;
+ case nsMsgSearchAttrib::AgeInDays: // added for searching online for age in days...
+ // for AgeInDays, we are actually going to perform a search by date, so convert the operations for age
+ // to the IMAP mnemonics that we would use for date!
+ {
+ // If we have a future date, the > and < are reversed.
+ // e.g. ageInDays > 2 means more than 2 days old ("date before X") whereas
+ // ageInDays > -2 should be more than 2 days in the future ("date after X")
+ int32_t ageInDays;
+ searchValue->GetAge(&ageInDays);
+ bool dateInFuture = (ageInDays < 0);
+ switch (op)
+ {
+ case nsMsgSearchOp::IsGreaterThan:
+ whichMnemonic = (!dateInFuture) ? m_kImapBefore : m_kImapSince;
+ break;
+ case nsMsgSearchOp::IsLessThan:
+ whichMnemonic = (!dateInFuture) ? m_kImapSince : m_kImapBefore;
+ break;
+ case nsMsgSearchOp::Is:
+ whichMnemonic = m_kImapSentOn;
+ break;
+ default:
+ NS_ASSERTION(false, "invalid search operator");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ break;
+ case nsMsgSearchAttrib::Size:
+ switch (op)
+ {
+ case nsMsgSearchOp::IsGreaterThan:
+ whichMnemonic = m_kImapSizeLarger;
+ break;
+ case nsMsgSearchOp::IsLessThan:
+ whichMnemonic = m_kImapSizeSmaller;
+ break;
+ default:
+ NS_ASSERTION(false, "invalid search operator");
+ return NS_ERROR_INVALID_ARG;
+ }
+ break;
+ case nsMsgSearchAttrib::Date:
+ switch (op)
+ {
+ case nsMsgSearchOp::IsBefore:
+ whichMnemonic = m_kImapBefore;
+ break;
+ case nsMsgSearchOp::IsAfter:
+ whichMnemonic = m_kImapSince;
+ break;
+ case nsMsgSearchOp::Isnt: /* we've already added the "Not" so just process it like it was a date is search */
+ case nsMsgSearchOp::Is:
+ whichMnemonic = m_kImapSentOn;
+ break;
+ default:
+ NS_ASSERTION(false, "invalid search operator");
+ return NS_ERROR_INVALID_ARG;
+ }
+ break;
+ case nsMsgSearchAttrib::AnyText:
+ whichMnemonic = m_kImapAnyText;
+ break;
+ case nsMsgSearchAttrib::Keywords:
+ whichMnemonic = m_kImapKeyword;
+ break;
+ case nsMsgSearchAttrib::MsgStatus:
+ useNot = false; // bizarrely, NOT SEEN is wrong, but UNSEEN is right.
+ ignoreValue = true; // the mnemonic is all we need
+ uint32_t status;
+ searchValue->GetStatus(&status);
+
+ switch (status)
+ {
+ case nsMsgMessageFlags::Read:
+ whichMnemonic = op == nsMsgSearchOp::Is ? m_kImapSeen : m_kImapNotSeen;
+ break;
+ case nsMsgMessageFlags::Replied:
+ whichMnemonic = op == nsMsgSearchOp::Is ? m_kImapAnswered : m_kImapNotAnswered;
+ break;
+ case nsMsgMessageFlags::New:
+ whichMnemonic = op == nsMsgSearchOp::Is ? m_kImapNew : m_kImapNotNew;
+ break;
+ case nsMsgMessageFlags::Marked:
+ whichMnemonic = op == nsMsgSearchOp::Is ? m_kImapFlagged : m_kImapNotFlagged;
+ break;
+ default:
+ NS_ASSERTION(false, "invalid search operator");
+ return NS_ERROR_INVALID_ARG;
+ }
+ break;
+ default:
+ if ( attrib > nsMsgSearchAttrib::OtherHeader && attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes)
+ {
+ nsCString arbitraryHeaderTerm;
+ term->GetArbitraryHeader(arbitraryHeaderTerm);
+ if (!arbitraryHeaderTerm.IsEmpty())
+ {
+ arbitraryHeader.AssignLiteral(" \"");
+ arbitraryHeader.Append(arbitraryHeaderTerm);
+ arbitraryHeader.AppendLiteral("\" ");
+ whichMnemonic = arbitraryHeader.get();
+ }
+ else
+ return NS_ERROR_FAILURE;
+ }
+ else
+ {
+ NS_ASSERTION(false, "invalid search operator");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ char *value = nullptr;
+ char dateBuf[100];
+ dateBuf[0] = '\0';
+
+ bool valueWasAllocated = false;
+ if (attrib == nsMsgSearchAttrib::Date)
+ {
+ // note that there used to be code here that encoded an RFC822 date for imap searches.
+ // The IMAP RFC 2060 is misleading to the point that it looks like it requires an RFC822
+ // date but really it expects dd-mmm-yyyy, like dredd, and refers to the RFC822 date only in that the
+ // dd-mmm-yyyy date will match the RFC822 date within the message.
+
+ PRTime adjustedDate;
+ searchValue->GetDate(&adjustedDate);
+ if (whichMnemonic == m_kImapSince)
+ {
+ // it looks like the IMAP server searches on Since includes the date in question...
+ // our UI presents Is, IsGreater and IsLessThan. For the IsGreater case (m_kImapSince)
+ // we need to adjust the date so we get greater than and not greater than or equal to which
+ // is what the IMAP server wants to search on
+ // won't work on Mac.
+ adjustedDate += PR_USEC_PER_DAY;
+ }
+
+ PRExplodedTime exploded;
+ PR_ExplodeTime(adjustedDate, PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded);
+ // strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", localtime (/* &term->m_value.u.date */ &adjustedDate));
+ value = dateBuf;
+ }
+ else
+ {
+ if (attrib == nsMsgSearchAttrib::AgeInDays)
+ {
+ // okay, take the current date, subtract off the age in days, then do an appropriate Date search on
+ // the resulting day.
+ int32_t ageInDays;
+
+ searchValue->GetAge(&ageInDays);
+
+ PRTime now = PR_Now();
+ PRTime matchDay = now - ageInDays * PR_USEC_PER_DAY;
+
+ PRExplodedTime exploded;
+ PR_ExplodeTime(matchDay, PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded);
+ // strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", localtime (&matchDay));
+ value = dateBuf;
+ }
+ else if (attrib == nsMsgSearchAttrib::Size)
+ {
+ uint32_t sizeValue;
+ nsAutoCString searchTermValue;
+ searchValue->GetSize(&sizeValue);
+
+ // Multiply by 1024 to get into kb resolution
+ sizeValue *= 1024;
+
+ // Ensure that greater than is really greater than
+ // in kb resolution.
+ if (op == nsMsgSearchOp::IsGreaterThan)
+ sizeValue += 1024;
+
+ searchTermValue.AppendInt(sizeValue);
+
+ value = ToNewCString(searchTermValue);
+ valueWasAllocated = true;
+ }
+ else
+
+ if (IS_STRING_ATTRIBUTE(attrib))
+ {
+ char16_t *convertedValue; // = reallyDredd ? MSG_EscapeSearchUrl (term->m_value.u.string) : msg_EscapeImapSearchProtocol(term->m_value.u.string);
+ nsString searchTermValue;
+ searchValue->GetStr(searchTermValue);
+ // Ugly switch for Korean mail/news charsets.
+ // We want to do this here because here is where
+ // we know what charset we want to use.
+#ifdef DOING_CHARSET
+ if (reallyDredd)
+ dest_csid = INTL_DefaultNewsCharSetID(dest_csid);
+ else
+ dest_csid = INTL_DefaultMailCharSetID(dest_csid);
+#endif
+
+ // do all sorts of crazy escaping
+ convertedValue = reallyDredd ? EscapeSearchUrl (searchTermValue.get()) :
+ EscapeImapSearchProtocol(searchTermValue.get());
+ useQuotes = ((!reallyDredd ||
+ (nsDependentString(convertedValue).FindChar(char16_t(' ')) != -1)) &&
+ (attrib != nsMsgSearchAttrib::Keywords));
+ // now convert to char* and escape quoted_specials
+ nsAutoCString valueStr;
+ nsresult rv = ConvertFromUnicode(NS_LossyConvertUTF16toASCII(destCharset).get(),
+ nsDependentString(convertedValue), valueStr);
+ if (NS_SUCCEEDED(rv))
+ {
+ const char *vptr = valueStr.get();
+ // max escaped length is one extra character for every character in the cmd.
+ mozilla::UniquePtr<char[]> newValue = mozilla::MakeUnique<char[]>(2*strlen(vptr) + 1);
+ if (newValue)
+ {
+ char *p = newValue.get();
+ while (1)
+ {
+ char ch = *vptr++;
+ if (!ch)
+ break;
+ if ((useQuotes ? ch == '"' : 0) || ch == '\\')
+ *p++ = '\\';
+ *p++ = ch;
+ }
+ *p = '\0';
+ value = strdup(newValue.get()); // realloc down to smaller size
+ }
+ }
+ else
+ value = strdup("");
+ NS_Free(convertedValue);
+ valueWasAllocated = true;
+
+ }
+ }
+
+ // this should be rewritten to use nsCString
+ int subLen =
+ (value ? strlen(value) : 0) +
+ (useNot ? strlen(m_kImapNot) : 0) +
+ strlen(m_kImapHeader);
+ int len = strlen(whichMnemonic) + subLen + (useQuotes ? 2 : 0) +
+ (orHeaderMnemonic
+ ? (subLen + strlen(m_kImapOr) + strlen(orHeaderMnemonic) + 2 /*""*/)
+ : 0) +
+ 10; // add slough for imap string literals
+ char *encoding = new char[len];
+ if (encoding)
+ {
+ encoding[0] = '\0';
+ // Remember: if ToOrCC and useNot then the expression becomes NOT To AND Not CC as opposed to (NOT TO) || (NOT CC)
+ if (orHeaderMnemonic && !useNot)
+ PL_strcat(encoding, m_kImapOr);
+ if (useNot)
+ PL_strcat (encoding, m_kImapNot);
+ if (!arbitraryHeader.IsEmpty())
+ PL_strcat (encoding, m_kImapHeader);
+ PL_strcat (encoding, whichMnemonic);
+ if (!ignoreValue)
+ err = EncodeImapValue(encoding, value, useQuotes, reallyDredd);
+
+ if (orHeaderMnemonic)
+ {
+ if (useNot)
+ PL_strcat(encoding, m_kImapNot);
+
+ PL_strcat (encoding, m_kImapHeader);
+
+ PL_strcat (encoding, orHeaderMnemonic);
+ if (!ignoreValue)
+ err = EncodeImapValue(encoding, value, useQuotes, reallyDredd);
+ }
+
+ // kmcentee, don't let the encoding end with whitespace,
+ // this throws off later url STRCMP
+ if (*encoding && *(encoding + strlen(encoding) - 1) == ' ')
+ *(encoding + strlen(encoding) - 1) = '\0';
+ }
+
+ if (value && valueWasAllocated)
+ NS_Free (value);
+
+ *ppOutTerm = encoding;
+
+ return err;
+}
+
+nsresult nsMsgSearchAdapter::EncodeImapValue(char *encoding, const char *value, bool useQuotes, bool reallyDredd)
+{
+ // By NNTP RFC, SEARCH HEADER SUBJECT "" is legal and means 'find messages without a subject header'
+ if (!reallyDredd)
+ {
+ // By IMAP RFC, SEARCH HEADER SUBJECT "" is illegal and will generate an error from the server
+ if (!value || !value[0])
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!NS_IsAscii(value))
+ {
+ nsAutoCString lengthStr;
+ PL_strcat(encoding, "{");
+ lengthStr.AppendInt((int32_t) strlen(value));
+ PL_strcat(encoding, lengthStr.get());
+ PL_strcat(encoding, "}" CRLF);
+ PL_strcat(encoding, value);
+ return NS_OK;
+ }
+ if (useQuotes)
+ PL_strcat(encoding, "\"");
+ PL_strcat (encoding, value);
+ if (useQuotes)
+ PL_strcat(encoding, "\"");
+
+ return NS_OK;
+}
+
+
+nsresult nsMsgSearchAdapter::EncodeImap (char **ppOutEncoding, nsISupportsArray *searchTerms, const char16_t *srcCharset, const char16_t *destCharset, bool reallyDredd)
+{
+ // i've left the old code (before using CBoolExpression for debugging purposes to make sure that
+ // the new code generates the same encoding string as the old code.....
+
+ nsresult err = NS_OK;
+ *ppOutEncoding = nullptr;
+
+ uint32_t termCount;
+ searchTerms->Count(&termCount);
+ uint32_t i = 0;
+
+ // create our expression
+ nsMsgSearchBoolExpression * expression = new nsMsgSearchBoolExpression();
+ if (!expression)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ for (i = 0; i < termCount && NS_SUCCEEDED(err); i++)
+ {
+ char *termEncoding;
+ bool matchAll;
+ nsCOMPtr<nsIMsgSearchTerm> pTerm;
+ searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(pTerm));
+ pTerm->GetMatchAll(&matchAll);
+ if (matchAll)
+ continue;
+ err = EncodeImapTerm (pTerm, reallyDredd, srcCharset, destCharset, &termEncoding);
+ if (NS_SUCCEEDED(err) && nullptr != termEncoding)
+ {
+ expression = nsMsgSearchBoolExpression::AddSearchTerm(expression, pTerm, termEncoding);
+ delete [] termEncoding;
+ }
+ }
+
+ if (NS_SUCCEEDED(err))
+ {
+ // Catenate the intermediate encodings together into a big string
+ nsAutoCString encodingBuff;
+
+ if (!reallyDredd)
+ encodingBuff.Append(m_kImapUnDeleted);
+
+ expression->GenerateEncodeStr(&encodingBuff);
+ *ppOutEncoding = ToNewCString(encodingBuff);
+ }
+
+ delete expression;
+
+ return err;
+}
+
+
+char *nsMsgSearchAdapter::TransformSpacesToStars (const char *spaceString, msg_TransformType transformType)
+{
+ char *starString;
+
+ if (transformType == kOverwrite)
+ {
+ if ((starString = strdup(spaceString)) != nullptr)
+ {
+ char *star = starString;
+ while ((star = PL_strchr(star, ' ')) != nullptr)
+ *star = '*';
+ }
+ }
+ else
+ {
+ int i, count;
+
+ for (i = 0, count = 0; spaceString[i]; )
+ {
+ if (spaceString[i++] == ' ')
+ {
+ count++;
+ while (spaceString[i] && spaceString[i] == ' ') i++;
+ }
+ }
+
+ if (transformType == kSurround)
+ count *= 2;
+
+ if (count > 0)
+ {
+ if ((starString = (char *)PR_Malloc(i + count + 1)) != nullptr)
+ {
+ int j;
+
+ for (i = 0, j = 0; spaceString[i]; )
+ {
+ if (spaceString[i] == ' ')
+ {
+ starString[j++] = '*';
+ starString[j++] = ' ';
+ if (transformType == kSurround)
+ starString[j++] = '*';
+
+ i++;
+ while (spaceString[i] && spaceString[i] == ' ')
+ i++;
+ }
+ else
+ starString[j++] = spaceString[i++];
+ }
+ starString[j] = 0;
+ }
+ }
+ else
+ starString = strdup(spaceString);
+ }
+
+ return starString;
+}
+
+//-----------------------------------------------------------------------------
+//------------------- Validity checking for menu items etc. -------------------
+//-----------------------------------------------------------------------------
+
+nsMsgSearchValidityTable::nsMsgSearchValidityTable ()
+{
+ // Set everything to be unavailable and disabled
+ for (int i = 0; i < nsMsgSearchAttrib::kNumMsgSearchAttributes; i++)
+ for (int j = 0; j < nsMsgSearchOp::kNumMsgSearchOperators; j++)
+ {
+ SetAvailable (i, j, false);
+ SetEnabled (i, j, false);
+ SetValidButNotShown (i,j, false);
+ }
+ m_numAvailAttribs = 0; // # of attributes marked with at least one available operator
+ // assume default is Subject, which it is for mail and news search
+ // it's not for LDAP, so we'll call SetDefaultAttrib()
+ m_defaultAttrib = nsMsgSearchAttrib::Subject;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgSearchValidityTable, nsIMsgSearchValidityTable)
+
+
+nsresult
+nsMsgSearchValidityTable::GetNumAvailAttribs(int32_t *aResult)
+{
+ m_numAvailAttribs = 0;
+ for (int i = 0; i < nsMsgSearchAttrib::kNumMsgSearchAttributes; i++)
+ for (int j = 0; j < nsMsgSearchOp::kNumMsgSearchOperators; j++) {
+ bool available;
+ GetAvailable(i, j, &available);
+ if (available)
+ {
+ m_numAvailAttribs++;
+ break;
+ }
+ }
+ *aResult = m_numAvailAttribs;
+ return NS_OK;
+}
+
+nsresult
+nsMsgSearchValidityTable::ValidateTerms (nsISupportsArray *searchTerms)
+{
+ nsresult err = NS_OK;
+ uint32_t count;
+
+ NS_ENSURE_ARG(searchTerms);
+
+ searchTerms->Count(&count);
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgSearchTerm> pTerm;
+ searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(pTerm));
+
+ nsIMsgSearchTerm *iTerm = pTerm;
+ nsMsgSearchTerm *term = static_cast<nsMsgSearchTerm *>(iTerm);
+// XP_ASSERT(term->IsValid());
+ bool enabled;
+ bool available;
+ GetEnabled(term->m_attribute, term->m_operator, &enabled);
+ GetAvailable(term->m_attribute, term->m_operator, &available);
+ if (!enabled || !available)
+ {
+ bool validNotShown;
+ GetValidButNotShown(term->m_attribute, term->m_operator,
+ &validNotShown);
+ if (!validNotShown)
+ err = NS_MSG_ERROR_INVALID_SEARCH_SCOPE;
+ }
+ }
+
+ return err;
+}
+
+nsresult
+nsMsgSearchValidityTable::GetAvailableAttributes(uint32_t *length,
+ nsMsgSearchAttribValue **aResult)
+{
+ NS_ENSURE_ARG_POINTER(length);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // count first
+ uint32_t totalAttributes=0;
+ int32_t i, j;
+ for (i = 0; i< nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) {
+ for (j=0; j< nsMsgSearchOp::kNumMsgSearchOperators; j++) {
+ if (m_table[i][j].bitAvailable) {
+ totalAttributes++;
+ break;
+ }
+ }
+ }
+
+ nsMsgSearchAttribValue *array = (nsMsgSearchAttribValue*)
+ moz_xmalloc(sizeof(nsMsgSearchAttribValue) * totalAttributes);
+ NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t numStored=0;
+ for (i = 0; i< nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) {
+ for (j=0; j< nsMsgSearchOp::kNumMsgSearchOperators; j++) {
+ if (m_table[i][j].bitAvailable) {
+ array[numStored++] = i;
+ break;
+ }
+ }
+ }
+
+ NS_ASSERTION(totalAttributes == numStored, "Search Attributes not lining up");
+ *length = totalAttributes;
+ *aResult = array;
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgSearchValidityTable::GetAvailableOperators(nsMsgSearchAttribValue aAttribute,
+ uint32_t *aLength,
+ nsMsgSearchOpValue **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aLength);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsMsgSearchAttribValue attr;
+ if (aAttribute == nsMsgSearchAttrib::Default)
+ attr = m_defaultAttrib;
+ else
+ attr = std::min(aAttribute,
+ (nsMsgSearchAttribValue)nsMsgSearchAttrib::OtherHeader);
+
+ uint32_t totalOperators=0;
+ int32_t i;
+ for (i=0; i<nsMsgSearchOp::kNumMsgSearchOperators; i++) {
+ if (m_table[attr][i].bitAvailable)
+ totalOperators++;
+ }
+
+ nsMsgSearchOpValue *array = (nsMsgSearchOpValue*)
+ moz_xmalloc(sizeof(nsMsgSearchOpValue) * totalOperators);
+ NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t numStored = 0;
+ for (i=0; i<nsMsgSearchOp::kNumMsgSearchOperators;i++) {
+ if (m_table[attr][i].bitAvailable)
+ array[numStored++] = i;
+ }
+
+ NS_ASSERTION(totalOperators == numStored, "Search Operators not lining up");
+ *aLength = totalOperators;
+ *aResult = array;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchValidityTable::SetDefaultAttrib(nsMsgSearchAttribValue aAttribute)
+{
+ m_defaultAttrib = aAttribute;
+ return NS_OK;
+}
+
+
+nsMsgSearchValidityManager::nsMsgSearchValidityManager ()
+{
+}
+
+
+nsMsgSearchValidityManager::~nsMsgSearchValidityManager ()
+{
+ // tables released by nsCOMPtr
+}
+
+NS_IMPL_ISUPPORTS(nsMsgSearchValidityManager, nsIMsgSearchValidityManager)
+
+//-----------------------------------------------------------------------------
+// Bottleneck accesses to the objects so we can allocate and initialize them
+// lazily. This way, there's no heap overhead for the validity tables until the
+// user actually searches that scope.
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP nsMsgSearchValidityManager::GetTable (int whichTable, nsIMsgSearchValidityTable **ppOutTable)
+{
+ NS_ENSURE_ARG_POINTER(ppOutTable);
+
+ nsresult rv;
+ *ppOutTable = nullptr;
+
+ nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ nsCString customHeaders;
+ if (NS_SUCCEEDED(rv))
+ pref->GetCharPref(PREF_CUSTOM_HEADERS, getter_Copies(customHeaders));
+
+ switch (whichTable)
+ {
+ case nsMsgSearchScope::offlineMail:
+ if (!m_offlineMailTable)
+ rv = InitOfflineMailTable ();
+ if (m_offlineMailTable)
+ rv = SetOtherHeadersInTable(m_offlineMailTable, customHeaders.get());
+ *ppOutTable = m_offlineMailTable;
+ break;
+ case nsMsgSearchScope::offlineMailFilter:
+ if (!m_offlineMailFilterTable)
+ rv = InitOfflineMailFilterTable ();
+ if (m_offlineMailFilterTable)
+ rv = SetOtherHeadersInTable(m_offlineMailFilterTable, customHeaders.get());
+ *ppOutTable = m_offlineMailFilterTable;
+ break;
+ case nsMsgSearchScope::onlineMail:
+ if (!m_onlineMailTable)
+ rv = InitOnlineMailTable ();
+ if (m_onlineMailTable)
+ rv = SetOtherHeadersInTable(m_onlineMailTable, customHeaders.get());
+ *ppOutTable = m_onlineMailTable;
+ break;
+ case nsMsgSearchScope::onlineMailFilter:
+ if (!m_onlineMailFilterTable)
+ rv = InitOnlineMailFilterTable ();
+ if (m_onlineMailFilterTable)
+ rv = SetOtherHeadersInTable(m_onlineMailFilterTable, customHeaders.get());
+ *ppOutTable = m_onlineMailFilterTable;
+ break;
+ case nsMsgSearchScope::news:
+ if (!m_newsTable)
+ rv = InitNewsTable();
+ if (m_newsTable)
+ rv = SetOtherHeadersInTable(m_newsTable, customHeaders.get());
+ *ppOutTable = m_newsTable;
+ break;
+ case nsMsgSearchScope::newsFilter:
+ if (!m_newsFilterTable)
+ rv = InitNewsFilterTable();
+ if (m_newsFilterTable)
+ rv = SetOtherHeadersInTable(m_newsFilterTable, customHeaders.get());
+ *ppOutTable = m_newsFilterTable;
+ break;
+ case nsMsgSearchScope::localNews:
+ if (!m_localNewsTable)
+ rv = InitLocalNewsTable();
+ if (m_localNewsTable)
+ rv = SetOtherHeadersInTable(m_localNewsTable, customHeaders.get());
+ *ppOutTable = m_localNewsTable;
+ break;
+ case nsMsgSearchScope::localNewsJunk:
+ if (!m_localNewsJunkTable)
+ rv = InitLocalNewsJunkTable();
+ if (m_localNewsJunkTable)
+ rv = SetOtherHeadersInTable(m_localNewsJunkTable, customHeaders.get());
+ *ppOutTable = m_localNewsJunkTable;
+ break;
+ case nsMsgSearchScope::localNewsBody:
+ if (!m_localNewsBodyTable)
+ rv = InitLocalNewsBodyTable();
+ if (m_localNewsBodyTable)
+ rv = SetOtherHeadersInTable(m_localNewsBodyTable, customHeaders.get());
+ *ppOutTable = m_localNewsBodyTable;
+ break;
+ case nsMsgSearchScope::localNewsJunkBody:
+ if (!m_localNewsJunkBodyTable)
+ rv = InitLocalNewsJunkBodyTable();
+ if (m_localNewsJunkBodyTable)
+ rv = SetOtherHeadersInTable(m_localNewsJunkBodyTable, customHeaders.get());
+ *ppOutTable = m_localNewsJunkBodyTable;
+ break;
+
+ case nsMsgSearchScope::onlineManual:
+ if (!m_onlineManualFilterTable)
+ rv = InitOnlineManualFilterTable();
+ if (m_onlineManualFilterTable)
+ rv = SetOtherHeadersInTable(m_onlineManualFilterTable, customHeaders.get());
+ *ppOutTable = m_onlineManualFilterTable;
+ break;
+ case nsMsgSearchScope::LDAP:
+ if (!m_ldapTable)
+ rv = InitLdapTable ();
+ *ppOutTable = m_ldapTable;
+ break;
+ case nsMsgSearchScope::LDAPAnd:
+ if (!m_ldapAndTable)
+ rv = InitLdapAndTable ();
+ *ppOutTable = m_ldapAndTable;
+ break;
+ case nsMsgSearchScope::LocalAB:
+ if (!m_localABTable)
+ rv = InitLocalABTable ();
+ *ppOutTable = m_localABTable;
+ break;
+ case nsMsgSearchScope::LocalABAnd:
+ if (!m_localABAndTable)
+ rv = InitLocalABAndTable ();
+ *ppOutTable = m_localABAndTable;
+ break;
+ default:
+ NS_ASSERTION(false, "invalid table type");
+ rv = NS_MSG_ERROR_INVALID_SEARCH_TERM;
+ }
+
+ NS_IF_ADDREF(*ppOutTable);
+ return rv;
+}
+
+// mapping between ordered attribute values, and property strings
+// see search-attributes.properties
+static struct
+{
+ nsMsgSearchAttribValue id;
+ const char* property;
+}
+nsMsgSearchAttribMap[] =
+{
+ {nsMsgSearchAttrib::Subject, "Subject"},
+ {nsMsgSearchAttrib::Sender, "From"},
+ {nsMsgSearchAttrib::Body, "Body"},
+ {nsMsgSearchAttrib::Date, "Date"},
+ {nsMsgSearchAttrib::Priority, "Priority"},
+ {nsMsgSearchAttrib::MsgStatus, "Status"},
+ {nsMsgSearchAttrib::To, "To"},
+ {nsMsgSearchAttrib::CC, "Cc"},
+ {nsMsgSearchAttrib::ToOrCC, "ToOrCc"},
+ {nsMsgSearchAttrib::AgeInDays, "AgeInDays"},
+ {nsMsgSearchAttrib::Size, "SizeKB"},
+ {nsMsgSearchAttrib::Keywords, "Tags"},
+ {nsMsgSearchAttrib::Name, "AnyName"},
+ {nsMsgSearchAttrib::DisplayName, "DisplayName"},
+ {nsMsgSearchAttrib::Nickname, "Nickname"},
+ {nsMsgSearchAttrib::ScreenName, "ScreenName"},
+ {nsMsgSearchAttrib::Email, "Email"},
+ {nsMsgSearchAttrib::AdditionalEmail, "AdditionalEmail"},
+ {nsMsgSearchAttrib::PhoneNumber, "AnyNumber"},
+ {nsMsgSearchAttrib::WorkPhone, "WorkPhone"},
+ {nsMsgSearchAttrib::HomePhone, "HomePhone"},
+ {nsMsgSearchAttrib::Fax, "Fax"},
+ {nsMsgSearchAttrib::Pager, "Pager"},
+ {nsMsgSearchAttrib::Mobile, "Mobile"},
+ {nsMsgSearchAttrib::City, "City"},
+ {nsMsgSearchAttrib::Street, "Street"},
+ {nsMsgSearchAttrib::Title, "Title"},
+ {nsMsgSearchAttrib::Organization, "Organization"},
+ {nsMsgSearchAttrib::Department, "Department"},
+ {nsMsgSearchAttrib::AllAddresses, "FromToCcOrBcc"},
+ {nsMsgSearchAttrib::JunkScoreOrigin, "JunkScoreOrigin"},
+ {nsMsgSearchAttrib::JunkPercent, "JunkPercent"},
+ {nsMsgSearchAttrib::HasAttachmentStatus, "AttachmentStatus"},
+ {nsMsgSearchAttrib::JunkStatus, "JunkStatus"},
+ {nsMsgSearchAttrib::Label, "Label"},
+ {nsMsgSearchAttrib::OtherHeader, "Customize"},
+ // the last id is -1 to denote end of table
+ {-1, ""}
+};
+
+NS_IMETHODIMP
+nsMsgSearchValidityManager::GetAttributeProperty(nsMsgSearchAttribValue aSearchAttribute,
+ nsAString& aProperty)
+{
+ for (int32_t i = 0; nsMsgSearchAttribMap[i].id >= 0; ++i)
+ {
+ if (nsMsgSearchAttribMap[i].id == aSearchAttribute)
+ {
+ aProperty.Assign(NS_ConvertUTF8toUTF16(nsMsgSearchAttribMap[i].property));
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+nsMsgSearchValidityManager::NewTable(nsIMsgSearchValidityTable **aTable)
+{
+ NS_ENSURE_ARG_POINTER(aTable);
+ *aTable = new nsMsgSearchValidityTable;
+ if (!*aTable)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aTable);
+ return NS_OK;
+}
+
+nsresult
+nsMsgSearchValidityManager::SetOtherHeadersInTable (nsIMsgSearchValidityTable *aTable, const char *customHeaders)
+{
+ uint32_t customHeadersLength = strlen(customHeaders);
+ uint32_t numHeaders=0;
+ if (customHeadersLength)
+ {
+ nsAutoCString hdrStr(customHeaders);
+ hdrStr.StripWhitespace(); //remove whitespace before parsing
+ char *newStr = hdrStr.BeginWriting();
+ char *token = NS_strtok(":", &newStr);
+ while(token)
+ {
+ numHeaders++;
+ token = NS_strtok(":", &newStr);
+ }
+ }
+
+ NS_ASSERTION(nsMsgSearchAttrib::OtherHeader + numHeaders < nsMsgSearchAttrib::kNumMsgSearchAttributes, "more headers than the table can hold");
+
+ uint32_t maxHdrs = std::min(nsMsgSearchAttrib::OtherHeader + numHeaders + 1,
+ (uint32_t)nsMsgSearchAttrib::kNumMsgSearchAttributes);
+ for (uint32_t i=nsMsgSearchAttrib::OtherHeader+1;i< maxHdrs;i++)
+ {
+ aTable->SetAvailable (i, nsMsgSearchOp::Contains, 1); // added for arbitrary headers
+ aTable->SetEnabled (i, nsMsgSearchOp::Contains, 1);
+ aTable->SetAvailable (i, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetEnabled (i, nsMsgSearchOp::DoesntContain, 1);
+ aTable->SetAvailable (i, nsMsgSearchOp::Is, 1);
+ aTable->SetEnabled (i, nsMsgSearchOp::Is, 1);
+ aTable->SetAvailable (i, nsMsgSearchOp::Isnt, 1);
+ aTable->SetEnabled (i, nsMsgSearchOp::Isnt, 1);
+ }
+ //because custom headers can change; so reset the table for those which are no longer used.
+ for (uint32_t j=maxHdrs; j < nsMsgSearchAttrib::kNumMsgSearchAttributes; j++)
+ {
+ for (uint32_t k=0; k < nsMsgSearchOp::kNumMsgSearchOperators; k++)
+ {
+ aTable->SetAvailable(j,k,0);
+ aTable->SetEnabled(j,k,0);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgSearchValidityManager::EnableDirectoryAttribute(nsIMsgSearchValidityTable *table, nsMsgSearchAttribValue aSearchAttrib)
+{
+ table->SetAvailable (aSearchAttrib, nsMsgSearchOp::Contains, 1);
+ table->SetEnabled (aSearchAttrib, nsMsgSearchOp::Contains, 1);
+ table->SetAvailable (aSearchAttrib, nsMsgSearchOp::DoesntContain, 1);
+ table->SetEnabled (aSearchAttrib, nsMsgSearchOp::DoesntContain, 1);
+ table->SetAvailable (aSearchAttrib, nsMsgSearchOp::Is, 1);
+ table->SetEnabled (aSearchAttrib, nsMsgSearchOp::Is, 1);
+ table->SetAvailable (aSearchAttrib, nsMsgSearchOp::Isnt, 1);
+ table->SetEnabled (aSearchAttrib, nsMsgSearchOp::Isnt, 1);
+ table->SetAvailable (aSearchAttrib, nsMsgSearchOp::BeginsWith, 1);
+ table->SetEnabled (aSearchAttrib, nsMsgSearchOp::BeginsWith, 1);
+ table->SetAvailable (aSearchAttrib, nsMsgSearchOp::EndsWith, 1);
+ table->SetEnabled (aSearchAttrib, nsMsgSearchOp::EndsWith, 1);
+ table->SetAvailable (aSearchAttrib, nsMsgSearchOp::SoundsLike, 1);
+ table->SetEnabled (aSearchAttrib, nsMsgSearchOp::SoundsLike, 1);
+ return NS_OK;
+}
+
+nsresult nsMsgSearchValidityManager::InitLdapTable()
+{
+ NS_ASSERTION(!m_ldapTable,"don't call this twice!");
+
+ nsresult rv = NewTable(getter_AddRefs(m_ldapTable));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = SetUpABTable(m_ldapTable, true);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsMsgSearchValidityManager::InitLdapAndTable()
+{
+ NS_ASSERTION(!m_ldapAndTable,"don't call this twice!");
+
+ nsresult rv = NewTable(getter_AddRefs(m_ldapAndTable));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = SetUpABTable(m_ldapAndTable, false);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsMsgSearchValidityManager::InitLocalABTable()
+{
+ NS_ASSERTION(!m_localABTable,"don't call this twice!");
+
+ nsresult rv = NewTable(getter_AddRefs(m_localABTable));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = SetUpABTable(m_localABTable, true);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsMsgSearchValidityManager::InitLocalABAndTable()
+{
+ NS_ASSERTION(!m_localABAndTable,"don't call this twice!");
+
+ nsresult rv = NewTable(getter_AddRefs(m_localABAndTable));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = SetUpABTable(m_localABAndTable, false);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult
+nsMsgSearchValidityManager::SetUpABTable(nsIMsgSearchValidityTable *aTable, bool isOrTable)
+{
+ nsresult rv = aTable->SetDefaultAttrib(isOrTable ? nsMsgSearchAttrib::Name : nsMsgSearchAttrib::DisplayName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (isOrTable) {
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Name);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::PhoneNumber);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::DisplayName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Email);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::AdditionalEmail);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::ScreenName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Street);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::City);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Title);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Organization);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Department);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Nickname);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::WorkPhone);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::HomePhone);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Fax);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Pager);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Mobile);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return rv;
+}
diff --git a/mailnews/base/search/src/nsMsgSearchImap.h b/mailnews/base/search/src/nsMsgSearchImap.h
new file mode 100644
index 000000000..8f2849da6
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchImap.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgSearchImap_h__
+#include "mozilla/Attributes.h"
+#include "nsMsgSearchAdapter.h"
+
+//-----------------------------------------------------------------------------
+//---------- Adapter class for searching online (IMAP) folders ----------------
+//-----------------------------------------------------------------------------
+
+class nsMsgSearchOnlineMail : public nsMsgSearchAdapter
+{
+public:
+ nsMsgSearchOnlineMail (nsMsgSearchScopeTerm *scope, nsISupportsArray *termList);
+ virtual ~nsMsgSearchOnlineMail ();
+
+ NS_IMETHOD ValidateTerms () override;
+ NS_IMETHOD Search (bool *aDone) override;
+ NS_IMETHOD GetEncoding (char **result) override;
+ NS_IMETHOD AddResultElement (nsIMsgDBHdr *) override;
+
+ static nsresult Encode (nsCString& ppEncoding,
+ nsISupportsArray *searchTerms,
+ const char16_t *destCharset);
+
+
+protected:
+ nsCString m_encoding;
+};
+
+
+
+#endif
+
diff --git a/mailnews/base/search/src/nsMsgSearchNews.cpp b/mailnews/base/search/src/nsMsgSearchNews.cpp
new file mode 100644
index 000000000..72d2bd648
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchNews.cpp
@@ -0,0 +1,511 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "msgCore.h"
+#include "nsMsgSearchAdapter.h"
+#include "nsUnicharUtils.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsMsgResultElement.h"
+#include "nsMsgSearchTerm.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgSearchNews.h"
+#include "nsIDBFolderInfo.h"
+#include "prprf.h"
+#include "nsIMsgDatabase.h"
+#include "nsMemory.h"
+#include <ctype.h>
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+
+// Implementation of search for IMAP mail folders
+
+
+// Implementation of search for newsgroups
+
+
+//-----------------------------------------------------------------------------
+//----------- Adapter class for searching XPAT-capable news servers -----------
+//-----------------------------------------------------------------------------
+
+
+const char *nsMsgSearchNews::m_kNntpFrom = "FROM ";
+const char *nsMsgSearchNews::m_kNntpSubject = "SUBJECT ";
+const char *nsMsgSearchNews::m_kTermSeparator = "/";
+
+
+nsMsgSearchNews::nsMsgSearchNews (nsMsgSearchScopeTerm *scope, nsISupportsArray *termList) : nsMsgSearchAdapter (scope, termList)
+{
+}
+
+
+nsMsgSearchNews::~nsMsgSearchNews ()
+{
+}
+
+
+nsresult nsMsgSearchNews::ValidateTerms ()
+{
+ nsresult err = nsMsgSearchAdapter::ValidateTerms ();
+ if (NS_OK == err)
+ {
+ err = Encode (&m_encoding);
+ }
+
+ return err;
+}
+
+
+nsresult nsMsgSearchNews::Search (bool *aDone)
+{
+ // the state machine runs in the news: handler
+ nsresult err = NS_ERROR_NOT_IMPLEMENTED;
+ return err;
+}
+
+char16_t *nsMsgSearchNews::EncodeToWildmat (const char16_t *value)
+{
+ // Here we take advantage of XPAT's use of the wildmat format, which allows
+ // a case-insensitive match by specifying each case possibility for each character
+ // So, "FooBar" is encoded as "[Ff][Oo][Bb][Aa][Rr]"
+
+ char16_t *caseInsensitiveValue = (char16_t*) moz_xmalloc(sizeof(char16_t) * ((4 * NS_strlen(value)) + 1));
+ if (caseInsensitiveValue)
+ {
+ char16_t *walkValue = caseInsensitiveValue;
+ while (*value)
+ {
+ if (isalpha(*value))
+ {
+ *walkValue++ = (char16_t)'[';
+ *walkValue++ = ToUpperCase((char16_t)*value);
+ *walkValue++ = ToLowerCase((char16_t)*value);
+ *walkValue++ = (char16_t)']';
+ }
+ else
+ *walkValue++ = *value;
+ value++;
+ }
+ *walkValue = 0;
+ }
+ return caseInsensitiveValue;
+}
+
+
+char *nsMsgSearchNews::EncodeTerm (nsIMsgSearchTerm *term)
+{
+ // Develop an XPAT-style encoding for the search term
+
+ NS_ASSERTION(term, "null term");
+ if (!term)
+ return nullptr;
+
+ // Find a string to represent the attribute
+ const char *attribEncoding = nullptr;
+ nsMsgSearchAttribValue attrib;
+
+ term->GetAttrib(&attrib);
+
+ switch (attrib)
+ {
+ case nsMsgSearchAttrib::Sender:
+ attribEncoding = m_kNntpFrom;
+ break;
+ case nsMsgSearchAttrib::Subject:
+ attribEncoding = m_kNntpSubject;
+ break;
+ default:
+ nsCString header;
+ term->GetArbitraryHeader(header);
+ if (header.IsEmpty())
+ {
+ NS_ASSERTION(false,"malformed search"); // malformed search term?
+ return nullptr;
+ }
+ attribEncoding = header.get();
+ }
+
+ // Build a string to represent the string pattern
+ bool leadingStar = false;
+ bool trailingStar = false;
+ nsMsgSearchOpValue op;
+ term->GetOp(&op);
+
+ switch (op)
+ {
+ case nsMsgSearchOp::Contains:
+ leadingStar = true;
+ trailingStar = true;
+ break;
+ case nsMsgSearchOp::Is:
+ break;
+ case nsMsgSearchOp::BeginsWith:
+ trailingStar = true;
+ break;
+ case nsMsgSearchOp::EndsWith:
+ leadingStar = true;
+ break;
+ default:
+ NS_ASSERTION(false,"malformed search"); // malformed search term?
+ return nullptr;
+ }
+
+ // ### i18N problem Get the csid from FE, which is the correct csid for term
+// int16 wincsid = INTL_GetCharSetID(INTL_DefaultTextWidgetCsidSel);
+
+ // Do INTL_FormatNNTPXPATInRFC1522Format trick for non-ASCII string
+// unsigned char *intlNonRFC1522Value = INTL_FormatNNTPXPATInNonRFC1522Format (wincsid, (unsigned char*)term->m_value.u.string);
+ nsCOMPtr <nsIMsgSearchValue> searchValue;
+
+ nsresult rv = term->GetValue(getter_AddRefs(searchValue));
+ if (NS_FAILED(rv) || !searchValue)
+ return nullptr;
+
+
+ nsString intlNonRFC1522Value;
+ rv = searchValue->GetStr(intlNonRFC1522Value);
+ if (NS_FAILED(rv) || intlNonRFC1522Value.IsEmpty())
+ return nullptr;
+
+ char16_t *caseInsensitiveValue = EncodeToWildmat (intlNonRFC1522Value.get());
+ if (!caseInsensitiveValue)
+ return nullptr;
+
+ // TO DO: Do INTL_FormatNNTPXPATInRFC1522Format trick for non-ASCII string
+ // Unfortunately, we currently do not handle xxx or xxx search in XPAT
+ // Need to add the INTL_FormatNNTPXPATInRFC1522Format call after we can do that
+ // so we should search a string in either RFC1522 format and non-RFC1522 format
+
+ char16_t *escapedValue = EscapeSearchUrl (caseInsensitiveValue);
+ free(caseInsensitiveValue);
+ if (!escapedValue)
+ return nullptr;
+
+ nsAutoCString pattern;
+
+ if (leadingStar)
+ pattern.Append('*');
+ pattern.Append(NS_ConvertUTF16toUTF8(escapedValue));
+ if (trailingStar)
+ pattern.Append('*');
+
+ // Combine the XPAT command syntax with the attribute and the pattern to
+ // form the term encoding
+ const char xpatTemplate[] = "XPAT %s 1- %s";
+ int termLength = (sizeof(xpatTemplate) - 1) + strlen(attribEncoding) + pattern.Length() + 1;
+ char *termEncoding = new char [termLength];
+ if (termEncoding)
+ PR_snprintf (termEncoding, termLength, xpatTemplate, attribEncoding, pattern.get());
+
+ return termEncoding;
+}
+
+nsresult nsMsgSearchNews::GetEncoding(char **result)
+{
+ NS_ENSURE_ARG(result);
+ *result = ToNewCString(m_encoding);
+ return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult nsMsgSearchNews::Encode (nsCString *outEncoding)
+{
+ NS_ASSERTION(outEncoding, "no out encoding");
+ if (!outEncoding)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult err = NS_OK;
+
+ uint32_t numTerms;
+
+ m_searchTerms->Count(&numTerms);
+ char **intermediateEncodings = new char * [numTerms];
+ if (intermediateEncodings)
+ {
+ // Build an XPAT command for each term
+ int encodingLength = 0;
+ uint32_t i;
+ for (i = 0; i < numTerms; i++)
+ {
+ nsCOMPtr<nsIMsgSearchTerm> pTerm;
+ m_searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(pTerm));
+ // set boolean OR term if any of the search terms are an OR...this only works if we are using
+ // homogeneous boolean operators.
+ bool isBooleanOpAnd;
+ pTerm->GetBooleanAnd(&isBooleanOpAnd);
+ m_ORSearch = !isBooleanOpAnd;
+
+ intermediateEncodings[i] = EncodeTerm (pTerm);
+ if (intermediateEncodings[i])
+ encodingLength += strlen(intermediateEncodings[i]) + strlen(m_kTermSeparator);
+ }
+ encodingLength += strlen("?search");
+ // Combine all the term encodings into one big encoding
+ char *encoding = new char [encodingLength + 1];
+ if (encoding)
+ {
+ PL_strcpy (encoding, "?search");
+
+ m_searchTerms->Count(&numTerms);
+
+ for (i = 0; i < numTerms; i++)
+ {
+ if (intermediateEncodings[i])
+ {
+ PL_strcat (encoding, m_kTermSeparator);
+ PL_strcat (encoding, intermediateEncodings[i]);
+ delete [] intermediateEncodings[i];
+ }
+ }
+ *outEncoding = encoding;
+ }
+ else
+ err = NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ err = NS_ERROR_OUT_OF_MEMORY;
+ delete [] intermediateEncodings;
+
+ return err;
+}
+
+NS_IMETHODIMP nsMsgSearchNews::AddHit(nsMsgKey key)
+{
+ m_candidateHits.AppendElement(key);
+ return NS_OK;
+}
+
+/* void CurrentUrlDone (in nsresult exitCode); */
+NS_IMETHODIMP nsMsgSearchNews::CurrentUrlDone(nsresult exitCode)
+{
+ CollateHits();
+ ReportHits();
+ return NS_OK;
+}
+
+
+#if 0 // need to switch this to a notify stop loading handler, I think.
+void nsMsgSearchNews::PreExitFunction (URL_Struct * /*url*/, int status, MWContext *context)
+{
+ MSG_SearchFrame *frame = MSG_SearchFrame::FromContext (context);
+ nsMsgSearchNews *adapter = (nsMsgSearchNews*) frame->GetRunningAdapter();
+ adapter->CollateHits();
+ adapter->ReportHits();
+
+ if (status == MK_INTERRUPTED)
+ {
+ adapter->Abort();
+ frame->EndCylonMode();
+ }
+ else
+ {
+ frame->m_idxRunningScope++;
+ if (frame->m_idxRunningScope >= frame->m_scopeList.Count())
+ frame->EndCylonMode();
+ }
+}
+#endif // 0
+
+void nsMsgSearchNews::CollateHits()
+{
+ // Since the XPAT commands are processed one at a time, the result set for the
+ // entire query is the intersection of results for each XPAT command if an AND search,
+ // otherwise we want the union of all the search hits (minus the duplicates of course).
+
+ uint32_t size = m_candidateHits.Length();
+ if (!size)
+ return;
+
+ // Sort the article numbers first, so it's easy to tell how many hits
+ // on a given article we got
+ m_candidateHits.Sort();
+
+ // For an OR search we only need to count the first occurrence of a candidate.
+ uint32_t termCount = 1;
+ if (!m_ORSearch)
+ {
+ // We have a traditional AND search which must be collated. In order to
+ // get promoted into the hits list, a candidate article number must appear
+ // in the results of each XPAT command. So if we fire 3 XPAT commands (one
+ // per search term), the article number must appear 3 times. If it appears
+ // fewer than 3 times, it matched some search terms, but not all.
+ m_searchTerms->Count(&termCount);
+ }
+ uint32_t candidateCount = 0;
+ uint32_t candidate = m_candidateHits[0];
+ for (uint32_t index = 0; index < size; ++index)
+ {
+ uint32_t possibleCandidate = m_candidateHits[index];
+ if (candidate == possibleCandidate)
+ {
+ ++candidateCount;
+ }
+ else
+ {
+ candidateCount = 1;
+ candidate = possibleCandidate;
+ }
+ if (candidateCount == termCount)
+ m_hits.AppendElement(candidate);
+ }
+}
+
+void nsMsgSearchNews::ReportHits ()
+{
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ nsCOMPtr <nsIMsgFolder> scopeFolder;
+
+ nsresult err = m_scope->GetFolder(getter_AddRefs(scopeFolder));
+ if (NS_SUCCEEDED(err) && scopeFolder)
+ {
+ err = scopeFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ }
+
+ if (db)
+ {
+ uint32_t size = m_hits.Length();
+ for (uint32_t i = 0; i < size; ++i)
+ {
+ nsCOMPtr <nsIMsgDBHdr> header;
+
+ db->GetMsgHdrForKey(m_hits.ElementAt(i), getter_AddRefs(header));
+ if (header)
+ ReportHit(header, scopeFolder);
+ }
+ }
+}
+
+// ### this should take an nsIMsgFolder instead of a string location.
+void nsMsgSearchNews::ReportHit (nsIMsgDBHdr *pHeaders, nsIMsgFolder *folder)
+{
+ // this is totally filched from msg_SearchOfflineMail until I decide whether the
+ // right thing is to get them from the db or from NNTP
+ nsCOMPtr<nsIMsgSearchSession> session;
+ nsCOMPtr<nsIMsgFolder> scopeFolder;
+ m_scope->GetFolder(getter_AddRefs(scopeFolder));
+ m_scope->GetSearchSession(getter_AddRefs(session));
+ if (session)
+ session->AddSearchHit (pHeaders, scopeFolder);
+}
+
+nsresult nsMsgSearchValidityManager::InitNewsTable()
+{
+ NS_ASSERTION (nullptr == m_newsTable,"don't call this twice!");
+ nsresult rv = NewTable (getter_AddRefs(m_newsTable));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+
+#if 0
+ // Size should be handled after the fact...
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+#endif
+
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ }
+
+ return rv;
+}
+
+nsresult nsMsgSearchValidityManager::InitNewsFilterTable()
+{
+ NS_ASSERTION (nullptr == m_newsFilterTable, "news filter table already initted");
+ nsresult rv = NewTable (getter_AddRefs(m_newsFilterTable));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1);
+
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
+
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1);
+
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1);
+
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
+
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
+ m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
+ }
+
+ return rv;
+}
diff --git a/mailnews/base/search/src/nsMsgSearchNews.h b/mailnews/base/search/src/nsMsgSearchNews.h
new file mode 100644
index 000000000..5c5c1ec31
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchNews.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgSearchNews_h__
+#include "nsMsgSearchAdapter.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+
+//-----------------------------------------------------------------------------
+//---------- Adapter class for searching online (news) folders ----------------
+//-----------------------------------------------------------------------------
+
+class nsMsgSearchNews : public nsMsgSearchAdapter
+{
+public:
+ nsMsgSearchNews (nsMsgSearchScopeTerm *scope, nsISupportsArray *termList);
+ virtual ~nsMsgSearchNews ();
+
+ NS_IMETHOD ValidateTerms () override;
+ NS_IMETHOD Search (bool *aDone) override;
+ NS_IMETHOD GetEncoding (char **result) override;
+ NS_IMETHOD AddHit(nsMsgKey key) override;
+ NS_IMETHOD CurrentUrlDone(nsresult exitCode) override;
+
+ virtual nsresult Encode (nsCString *outEncoding);
+ virtual char *EncodeTerm (nsIMsgSearchTerm *);
+ char16_t *EncodeToWildmat (const char16_t *);
+
+ void ReportHits ();
+ void CollateHits ();
+ void ReportHit (nsIMsgDBHdr *pHeaders, nsIMsgFolder *folder);
+
+protected:
+ nsCString m_encoding;
+ bool m_ORSearch; // set to true if any of the search terms contains an OR for a boolean operator.
+
+ nsTArray<nsMsgKey> m_candidateHits;
+ nsTArray<nsMsgKey> m_hits;
+
+ static const char *m_kNntpFrom;
+ static const char *m_kNntpSubject;
+ static const char *m_kTermSeparator;
+ static const char *m_kUrlPrefix;
+};
+
+#endif
+
diff --git a/mailnews/base/search/src/nsMsgSearchSession.cpp b/mailnews/base/search/src/nsMsgSearchSession.cpp
new file mode 100644
index 000000000..422e17c40
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchSession.cpp
@@ -0,0 +1,675 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgSearchCore.h"
+#include "nsMsgSearchAdapter.h"
+#include "nsMsgSearchBoolExpression.h"
+#include "nsMsgSearchSession.h"
+#include "nsMsgResultElement.h"
+#include "nsMsgSearchTerm.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgLocalSearch.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsAutoPtr.h"
+
+NS_IMPL_ISUPPORTS(nsMsgSearchSession, nsIMsgSearchSession, nsIUrlListener,
+ nsISupportsWeakReference)
+
+nsMsgSearchSession::nsMsgSearchSession()
+{
+ m_sortAttribute = nsMsgSearchAttrib::Sender;
+ m_idxRunningScope = 0;
+ m_handlingError = false;
+ m_expressionTree = nullptr;
+ m_searchPaused = false;
+ nsresult rv = NS_NewISupportsArray(getter_AddRefs(m_termList));
+ if (NS_FAILED(rv))
+ NS_ASSERTION(false, "Failed to allocate a nsISupportsArray for nsMsgFilter");
+}
+
+nsMsgSearchSession::~nsMsgSearchSession()
+{
+ InterruptSearch();
+ delete m_expressionTree;
+ DestroyScopeList();
+ DestroyTermList();
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::AddSearchTerm(nsMsgSearchAttribValue attrib,
+ nsMsgSearchOpValue op,
+ nsIMsgSearchValue *value,
+ bool BooleanANDp,
+ const char *customString)
+{
+ // stupid gcc
+ nsMsgSearchBooleanOperator boolOp;
+ if (BooleanANDp)
+ boolOp = (nsMsgSearchBooleanOperator)nsMsgSearchBooleanOp::BooleanAND;
+ else
+ boolOp = (nsMsgSearchBooleanOperator)nsMsgSearchBooleanOp::BooleanOR;
+ nsMsgSearchTerm *pTerm = new nsMsgSearchTerm(attrib, op, value,
+ boolOp, customString);
+ NS_ENSURE_TRUE(pTerm, NS_ERROR_OUT_OF_MEMORY);
+
+ m_termList->AppendElement(pTerm);
+ // force the expression tree to rebuild whenever we change the terms
+ delete m_expressionTree;
+ m_expressionTree = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::AppendTerm(nsIMsgSearchTerm *aTerm)
+{
+ NS_ENSURE_ARG_POINTER(aTerm);
+ NS_ENSURE_TRUE(m_termList, NS_ERROR_NOT_INITIALIZED);
+ delete m_expressionTree;
+ m_expressionTree = nullptr;
+ return m_termList->AppendElement(aTerm);
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::GetSearchTerms(nsISupportsArray **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_termList;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::CreateTerm(nsIMsgSearchTerm **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsMsgSearchTerm *term = new nsMsgSearchTerm;
+ NS_ENSURE_TRUE(term, NS_ERROR_OUT_OF_MEMORY);
+
+ *aResult = static_cast<nsIMsgSearchTerm*>(term);
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::RegisterListener(nsIMsgSearchNotify *aListener,
+ int32_t aNotifyFlags)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ m_listenerList.AppendElement(aListener);
+ m_listenerFlagList.AppendElement(aNotifyFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::UnregisterListener(nsIMsgSearchNotify *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ size_t listenerIndex = m_listenerList.IndexOf(aListener);
+ if (listenerIndex != m_listenerList.NoIndex)
+ {
+ m_listenerList.RemoveElementAt(listenerIndex);
+ m_listenerFlagList.RemoveElementAt(listenerIndex);
+
+ // Adjust our iterator if it is active.
+ // Removal of something at a higher index than the iterator does not affect
+ // it; we only care if the the index we were pointing at gets shifted down,
+ // in which case we also want to shift down.
+ if (m_iListener != -1 && (signed)listenerIndex <= m_iListener)
+ m_iListener--;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::GetNumSearchTerms(uint32_t *aNumSearchTerms)
+{
+ NS_ENSURE_ARG(aNumSearchTerms);
+ return m_termList->Count(aNumSearchTerms);
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::GetNthSearchTerm(int32_t whichTerm,
+ nsMsgSearchAttribValue attrib,
+ nsMsgSearchOpValue op,
+ nsIMsgSearchValue *value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::CountSearchScopes(int32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_scopeList.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::GetNthSearchScope(int32_t which,
+ nsMsgSearchScopeValue *scopeId,
+ nsIMsgFolder **folder)
+{
+ NS_ENSURE_ARG_POINTER(scopeId);
+ NS_ENSURE_ARG_POINTER(folder);
+
+ nsMsgSearchScopeTerm *scopeTerm = m_scopeList.SafeElementAt(which, nullptr);
+ NS_ENSURE_ARG(scopeTerm);
+
+ *scopeId = scopeTerm->m_attribute;
+ *folder = scopeTerm->m_folder;
+ NS_IF_ADDREF(*folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::AddScopeTerm(nsMsgSearchScopeValue scope,
+ nsIMsgFolder *folder)
+{
+ if (scope != nsMsgSearchScope::allSearchableGroups)
+ {
+ NS_ASSERTION(folder, "need folder if not searching all groups");
+ NS_ENSURE_TRUE(folder, NS_ERROR_NULL_POINTER);
+ }
+
+ nsMsgSearchScopeTerm *pScopeTerm = new nsMsgSearchScopeTerm(this, scope, folder);
+ NS_ENSURE_TRUE(pScopeTerm, NS_ERROR_OUT_OF_MEMORY);
+
+ m_scopeList.AppendElement(pScopeTerm);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::AddDirectoryScopeTerm(nsMsgSearchScopeValue scope)
+{
+ nsMsgSearchScopeTerm *pScopeTerm = new nsMsgSearchScopeTerm(this, scope, nullptr);
+ NS_ENSURE_TRUE(pScopeTerm, NS_ERROR_OUT_OF_MEMORY);
+
+ m_scopeList.AppendElement(pScopeTerm);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::ClearScopes()
+{
+ DestroyScopeList();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::ScopeUsesCustomHeaders(nsMsgSearchScopeValue scope,
+ void *selection,
+ bool forFilters,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::IsStringAttribute(nsMsgSearchAttribValue attrib,
+ bool *_retval)
+{
+ // Is this check needed?
+ NS_ENSURE_ARG(_retval);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::AddAllScopes(nsMsgSearchScopeValue attrib)
+{
+ // don't think this is needed.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::Search(nsIMsgWindow *aWindow)
+{
+ nsresult rv = Initialize();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgSearchNotify> listener;
+ m_iListener = 0;
+ while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length())
+ {
+ listener = m_listenerList[m_iListener];
+ int32_t listenerFlags = m_listenerFlagList[m_iListener++];
+ if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onNewSearch))
+ listener->OnNewSearch();
+ }
+ m_iListener = -1;
+
+ m_msgWindowWeak = do_GetWeakReference(aWindow);
+
+ return BeginSearching();
+}
+
+NS_IMETHODIMP nsMsgSearchSession::InterruptSearch()
+{
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ if (msgWindow)
+ {
+ EnableFolderNotifications(true);
+ if (m_idxRunningScope < m_scopeList.Length())
+ msgWindow->StopUrls();
+
+ while (m_idxRunningScope < m_scopeList.Length())
+ {
+ ReleaseFolderDBRef();
+ m_idxRunningScope++;
+ }
+ //m_idxRunningScope = m_scopeList.Length() so it will make us not run another url
+ }
+ if (m_backgroundTimer)
+ {
+ m_backgroundTimer->Cancel();
+ NotifyListenersDone(NS_MSG_SEARCH_INTERRUPTED);
+
+ m_backgroundTimer = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::PauseSearch()
+{
+ if (m_backgroundTimer)
+ {
+ m_backgroundTimer->Cancel();
+ m_searchPaused = true;
+ return NS_OK;
+ }
+ else
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::ResumeSearch()
+{
+ if (m_searchPaused)
+ {
+ m_searchPaused = false;
+ return StartTimer();
+ }
+ else
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::GetSearchParam(void **aSearchParam)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::GetSearchType(nsMsgSearchType **aSearchType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::SetSearchParam(nsMsgSearchType *type,
+ void *param,
+ nsMsgSearchType **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::GetNumResults(int32_t *aNumResults)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::SetWindow(nsIMsgWindow *aWindow)
+{
+ m_msgWindowWeak = do_GetWeakReference(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::GetWindow(nsIMsgWindow **aWindow)
+{
+ NS_ENSURE_ARG_POINTER(aWindow);
+ *aWindow = nullptr;
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ msgWindow.swap(*aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::OnStartRunningUrl(nsIURI *url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::OnStopRunningUrl(nsIURI *url, nsresult aExitCode)
+{
+ nsCOMPtr<nsIMsgSearchAdapter> runningAdapter;
+
+ nsresult rv = GetRunningAdapter(getter_AddRefs(runningAdapter));
+ // tell the current adapter that the current url has run.
+ if (NS_SUCCEEDED(rv) && runningAdapter)
+ {
+ runningAdapter->CurrentUrlDone(aExitCode);
+ EnableFolderNotifications(true);
+ ReleaseFolderDBRef();
+ }
+ if (++m_idxRunningScope < m_scopeList.Length())
+ DoNextSearch();
+ else
+ NotifyListenersDone(aExitCode);
+ return NS_OK;
+}
+
+
+nsresult nsMsgSearchSession::Initialize()
+{
+ // Loop over scope terms, initializing an adapter per term. This
+ // architecture is necessitated by two things:
+ // 1. There might be more than one kind of adapter per if online
+ // *and* offline mail mail folders are selected, or if newsgroups
+ // belonging to Dredd *and* INN are selected
+ // 2. Most of the protocols are only capable of searching one scope at a
+ // time, so we'll do each scope in a separate adapter on the client
+
+ nsMsgSearchScopeTerm *scopeTerm = nullptr;
+ nsresult rv = NS_OK;
+
+ uint32_t numTerms;
+ m_termList->Count(&numTerms);
+ // Ensure that the FE has added scopes and terms to this search
+ NS_ASSERTION(numTerms > 0, "no terms to search!");
+ if (numTerms == 0)
+ return NS_MSG_ERROR_NO_SEARCH_VALUES;
+
+ // if we don't have any search scopes to search, return that code.
+ if (m_scopeList.Length() == 0)
+ return NS_MSG_ERROR_INVALID_SEARCH_SCOPE;
+
+ m_runningUrl.Truncate(); // clear out old url, if any.
+ m_idxRunningScope = 0;
+
+ // If this term list (loosely specified here by the first term) should be
+ // scheduled in parallel, build up a list of scopes to do the round-robin scheduling
+ for (uint32_t i = 0; i < m_scopeList.Length() && NS_SUCCEEDED(rv); i++)
+ {
+ scopeTerm = m_scopeList.ElementAt(i);
+ // NS_ASSERTION(scopeTerm->IsValid());
+
+ rv = scopeTerm->InitializeAdapter(m_termList);
+ }
+
+ return rv;
+}
+
+nsresult nsMsgSearchSession::BeginSearching()
+{
+ // Here's a sloppy way to start the URL, but I don't really have time to
+ // unify the scheduling mechanisms. If the first scope is a newsgroup, and
+ // it's not Dredd-capable, we build the URL queue. All other searches can be
+ // done with one URL
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ if (msgWindow)
+ msgWindow->SetStopped(false);
+ return DoNextSearch();
+}
+
+nsresult nsMsgSearchSession::DoNextSearch()
+{
+ nsMsgSearchScopeTerm *scope = m_scopeList.ElementAt(m_idxRunningScope);
+ if (scope->m_attribute == nsMsgSearchScope::onlineMail ||
+ (scope->m_attribute == nsMsgSearchScope::news && scope->m_searchServer))
+ {
+ nsCOMPtr<nsIMsgSearchAdapter> adapter = do_QueryInterface(scope->m_adapter);
+ if (adapter)
+ {
+ m_runningUrl.Truncate();
+ adapter->GetEncoding(getter_Copies(m_runningUrl));
+ }
+ NS_ENSURE_STATE(!m_runningUrl.IsEmpty());
+ return GetNextUrl();
+ }
+ else
+ {
+ return SearchWOUrls();
+ }
+}
+
+nsresult nsMsgSearchSession::GetNextUrl()
+{
+ nsCOMPtr<nsIMsgMessageService> msgService;
+
+ bool stopped = false;
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ if (msgWindow)
+ msgWindow->GetStopped(&stopped);
+ if (stopped)
+ return NS_OK;
+
+ nsMsgSearchScopeTerm *currentTerm = GetRunningScope();
+ NS_ENSURE_TRUE(currentTerm, NS_ERROR_NULL_POINTER);
+ EnableFolderNotifications(false);
+ nsCOMPtr<nsIMsgFolder> folder = currentTerm->m_folder;
+ if (folder)
+ {
+ nsCString folderUri;
+ folder->GetURI(folderUri);
+ nsresult rv = GetMessageServiceFromURI(folderUri, getter_AddRefs(msgService));
+
+ if (NS_SUCCEEDED(rv) && msgService && currentTerm)
+ msgService->Search(this, msgWindow, currentTerm->m_folder, m_runningUrl.get());
+ return rv;
+ }
+ return NS_OK;
+}
+
+/* static */
+void nsMsgSearchSession::TimerCallback(nsITimer *aTimer, void *aClosure)
+{
+ NS_ENSURE_TRUE_VOID(aClosure);
+ nsMsgSearchSession *searchSession = (nsMsgSearchSession *) aClosure;
+ bool done;
+ bool stopped = false;
+
+ searchSession->TimeSlice(&done);
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(searchSession->m_msgWindowWeak));
+ if (msgWindow)
+ msgWindow->GetStopped(&stopped);
+
+ if (done || stopped)
+ {
+ if (aTimer)
+ aTimer->Cancel();
+ searchSession->m_backgroundTimer = nullptr;
+ if (searchSession->m_idxRunningScope < searchSession->m_scopeList.Length())
+ searchSession->DoNextSearch();
+ else
+ searchSession->NotifyListenersDone(NS_OK);
+ }
+}
+
+nsresult nsMsgSearchSession::StartTimer()
+{
+ nsresult rv;
+
+ m_backgroundTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_backgroundTimer->InitWithFuncCallback(TimerCallback, (void *) this, 0,
+ nsITimer::TYPE_REPEATING_SLACK);
+ TimerCallback(m_backgroundTimer, this);
+ return NS_OK;
+}
+
+nsresult nsMsgSearchSession::SearchWOUrls()
+{
+ EnableFolderNotifications(false);
+ return StartTimer();
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::GetRunningAdapter(nsIMsgSearchAdapter **aSearchAdapter)
+{
+ NS_ENSURE_ARG_POINTER(aSearchAdapter);
+ *aSearchAdapter = nullptr;
+ nsMsgSearchScopeTerm *scope = GetRunningScope();
+ if (scope)
+ {
+ NS_IF_ADDREF(*aSearchAdapter = scope->m_adapter);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::AddSearchHit(nsIMsgDBHdr *aHeader,
+ nsIMsgFolder *aFolder)
+{
+ nsCOMPtr<nsIMsgSearchNotify> listener;
+ m_iListener = 0;
+ while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length())
+ {
+ listener = m_listenerList[m_iListener];
+ int32_t listenerFlags = m_listenerFlagList[m_iListener++];
+ if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onSearchHit))
+ listener->OnSearchHit(aHeader, aFolder);
+ }
+ m_iListener = -1;
+ return NS_OK;
+}
+
+nsresult nsMsgSearchSession::NotifyListenersDone(nsresult aStatus)
+{
+ // need to stabilize "this" in case one of the listeners releases the last
+ // reference to us.
+ RefPtr<nsIMsgSearchSession> kungFuDeathGrip(this);
+
+ nsCOMPtr<nsIMsgSearchNotify> listener;
+ m_iListener = 0;
+ while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length())
+ {
+ listener = m_listenerList[m_iListener];
+ int32_t listenerFlags = m_listenerFlagList[m_iListener++];
+ if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onSearchDone))
+ listener->OnSearchDone(aStatus);
+ }
+ m_iListener = -1;
+ return NS_OK;
+}
+
+void nsMsgSearchSession::DestroyScopeList()
+{
+ nsMsgSearchScopeTerm *scope = nullptr;
+
+ for (int32_t i = m_scopeList.Length() - 1; i >= 0; i--)
+ {
+ scope = m_scopeList.ElementAt(i);
+ // NS_ASSERTION (scope->IsValid(), "invalid search scope");
+ if (scope->m_adapter)
+ scope->m_adapter->ClearScope();
+ }
+ m_scopeList.Clear();
+}
+
+
+void nsMsgSearchSession::DestroyTermList()
+{
+ m_termList->Clear();
+}
+
+nsMsgSearchScopeTerm *nsMsgSearchSession::GetRunningScope()
+{
+ return m_scopeList.SafeElementAt(m_idxRunningScope, nullptr);
+}
+
+nsresult nsMsgSearchSession::TimeSlice(bool *aDone)
+{
+ // we only do serial for now.
+ return TimeSliceSerial(aDone);
+}
+
+void nsMsgSearchSession::ReleaseFolderDBRef()
+{
+ nsMsgSearchScopeTerm *scope = GetRunningScope();
+ if (!scope)
+ return;
+
+ bool isOpen = false;
+ uint32_t flags;
+ nsCOMPtr<nsIMsgFolder> folder;
+ scope->GetFolder(getter_AddRefs(folder));
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID);
+ if (!mailSession || !folder)
+ return;
+
+ mailSession->IsFolderOpenInWindow(folder, &isOpen);
+ folder->GetFlags(&flags);
+
+ /*we don't null out the db reference for inbox because inbox is like the "main" folder
+ and performance outweighs footprint */
+ if (!isOpen && !(nsMsgFolderFlags::Inbox & flags))
+ folder->SetMsgDatabase(nullptr);
+}
+nsresult nsMsgSearchSession::TimeSliceSerial(bool *aDone)
+{
+ // This version of TimeSlice runs each scope term one at a time, and waits until one
+ // scope term is finished before starting another one. When we're searching the local
+ // disk, this is the fastest way to do it.
+
+ NS_ENSURE_ARG_POINTER(aDone);
+
+ nsMsgSearchScopeTerm *scope = GetRunningScope();
+ if (!scope)
+ {
+ *aDone = true;
+ return NS_OK;
+ }
+
+ nsresult rv = scope->TimeSlice(aDone);
+ if (*aDone || NS_FAILED(rv))
+ {
+ EnableFolderNotifications(true);
+ ReleaseFolderDBRef();
+ m_idxRunningScope++;
+ EnableFolderNotifications(false);
+ // check if the next scope is an online search; if so,
+ // set *aDone to true so that we'll try to run the next
+ // search in TimerCallback.
+ scope = GetRunningScope();
+ if (scope && (scope->m_attribute == nsMsgSearchScope::onlineMail ||
+ (scope->m_attribute == nsMsgSearchScope::news && scope->m_searchServer)))
+ {
+ *aDone = true;
+ return rv;
+ }
+ }
+ *aDone = false;
+ return rv;
+}
+
+void
+nsMsgSearchSession::EnableFolderNotifications(bool aEnable)
+{
+ nsMsgSearchScopeTerm *scope = GetRunningScope();
+ if (scope)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ scope->GetFolder(getter_AddRefs(folder));
+ if (folder) //enable msg count notifications
+ folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, aEnable, false);
+ }
+}
+
+//this method is used for adding new hdrs to quick search view
+NS_IMETHODIMP
+nsMsgSearchSession::MatchHdr(nsIMsgDBHdr *aMsgHdr, nsIMsgDatabase *aDatabase, bool *aResult)
+{
+ nsMsgSearchScopeTerm *scope = m_scopeList.SafeElementAt(0, nullptr);
+ if (scope)
+ {
+ if (!scope->m_adapter)
+ scope->InitializeAdapter(m_termList);
+ if (scope->m_adapter)
+ {
+ nsAutoString nullCharset, folderCharset;
+ scope->m_adapter->GetSearchCharsets(nullCharset, folderCharset);
+ NS_ConvertUTF16toUTF8 charset(folderCharset.get());
+ nsMsgSearchOfflineMail::MatchTermsForSearch(aMsgHdr, m_termList,
+ charset.get(), scope, aDatabase, &m_expressionTree, aResult);
+ }
+ }
+ return NS_OK;
+}
diff --git a/mailnews/base/search/src/nsMsgSearchSession.h b/mailnews/base/search/src/nsMsgSearchSession.h
new file mode 100644
index 000000000..d5d62654f
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchSession.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgSearchSession_h___
+#define nsMsgSearchSession_h___
+
+#include "nscore.h"
+#include "nsMsgSearchCore.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgWindow.h"
+#include "nsITimer.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsCOMArray.h"
+#include "nsWeakReference.h"
+#include "nsTObserverArray.h"
+
+class nsMsgSearchAdapter;
+class nsMsgSearchBoolExpression;
+class nsMsgSearchScopeTerm;
+
+class nsMsgSearchSession : public nsIMsgSearchSession, public nsIUrlListener, public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSEARCHSESSION
+ NS_DECL_NSIURLLISTENER
+
+ nsMsgSearchSession();
+
+protected:
+ virtual ~nsMsgSearchSession();
+
+ nsWeakPtr m_msgWindowWeak;
+ nsresult Initialize();
+ nsresult StartTimer();
+ nsresult TimeSlice (bool *aDone);
+ nsMsgSearchScopeTerm *GetRunningScope();
+ void StopRunning();
+ nsresult BeginSearching();
+ nsresult DoNextSearch();
+ nsresult SearchWOUrls ();
+ nsresult GetNextUrl();
+ nsresult NotifyListenersDone(nsresult status);
+ void EnableFolderNotifications(bool aEnable);
+ void ReleaseFolderDBRef();
+
+ nsTArray<RefPtr<nsMsgSearchScopeTerm>> m_scopeList;
+ nsCOMPtr <nsISupportsArray> m_termList;
+
+ nsTArray<nsCOMPtr<nsIMsgSearchNotify> > m_listenerList;
+ nsTArray<int32_t> m_listenerFlagList;
+ /**
+ * Iterator index for m_listenerList/m_listenerFlagList. We used to use an
+ * nsTObserverArray for m_listenerList but its auto-adjusting iterator was
+ * not helping us keep our m_listenerFlagList iterator correct.
+ *
+ * We are making the simplifying assumption that our notifications are
+ * non-reentrant. In the exceptional case that it turns out they are
+ * reentrant, we assume that this is the result of canceling a search while
+ * the session is active and initiating a new one. In that case, we assume
+ * the outer iteration can safely be abandoned.
+ *
+ * This value is defined to be the index of the next listener we will process.
+ * This allows us to use the sentinel value of -1 to convey that no iteration
+ * is in progress (and the iteration process to abort if the value transitions
+ * to -1, which we always set on conclusion of our loop).
+ */
+ int32_t m_iListener;
+
+ void DestroyTermList ();
+ void DestroyScopeList ();
+
+ static void TimerCallback(nsITimer *aTimer, void *aClosure);
+ // support for searching multiple scopes in serial
+ nsresult TimeSliceSerial (bool *aDone);
+ nsresult TimeSliceParallel ();
+
+ nsMsgSearchAttribValue m_sortAttribute;
+ uint32_t m_idxRunningScope;
+ nsMsgSearchType m_searchType;
+ bool m_handlingError;
+ nsCString m_runningUrl; // The url for the current search
+ nsCOMPtr <nsITimer> m_backgroundTimer;
+ bool m_searchPaused;
+ nsMsgSearchBoolExpression *m_expressionTree;
+};
+
+#endif
diff --git a/mailnews/base/search/src/nsMsgSearchTerm.cpp b/mailnews/base/search/src/nsMsgSearchTerm.cpp
new file mode 100644
index 000000000..139ab7b20
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchTerm.cpp
@@ -0,0 +1,2088 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "prmem.h"
+#include "nsMsgSearchCore.h"
+#include "nsIMsgSearchSession.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgSearchTerm.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsMsgBodyHandler.h"
+#include "nsMsgResultElement.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsMsgSearchImap.h"
+#include "nsMsgLocalSearch.h"
+#include "nsMsgSearchNews.h"
+#include "nsMsgSearchValue.h"
+#include "nsMsgI18N.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgMimeCID.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIFile.h"
+#include "nsISeekableStream.h"
+#include "nsNetCID.h"
+#include "nsIFileStreams.h"
+#include "nsUnicharUtils.h"
+#include "nsIAbCard.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include <ctype.h>
+#include "nsMsgBaseCID.h"
+#include "nsIMsgTagService.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbManager.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+using namespace mozilla::mailnews;
+
+//---------------------------------------------------------------------------
+// nsMsgSearchTerm specifies one criterion, e.g. name contains phil
+//---------------------------------------------------------------------------
+
+
+//-----------------------------------------------------------------------------
+//-------------------- Implementation of nsMsgSearchTerm -----------------------
+//-----------------------------------------------------------------------------
+#define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders"
+
+typedef struct
+{
+ nsMsgSearchAttribValue attrib;
+ const char *attribName;
+} nsMsgSearchAttribEntry;
+
+nsMsgSearchAttribEntry SearchAttribEntryTable[] =
+{
+ {nsMsgSearchAttrib::Subject, "subject"},
+ {nsMsgSearchAttrib::Sender, "from"},
+ {nsMsgSearchAttrib::Body, "body"},
+ {nsMsgSearchAttrib::Date, "date"},
+ {nsMsgSearchAttrib::Priority, "priority"},
+ {nsMsgSearchAttrib::MsgStatus, "status"},
+ {nsMsgSearchAttrib::To, "to"},
+ {nsMsgSearchAttrib::CC, "cc"},
+ {nsMsgSearchAttrib::ToOrCC, "to or cc"},
+ {nsMsgSearchAttrib::AllAddresses, "all addresses"},
+ {nsMsgSearchAttrib::AgeInDays, "age in days"},
+ {nsMsgSearchAttrib::Label, "label"},
+ {nsMsgSearchAttrib::Keywords, "tag"},
+ {nsMsgSearchAttrib::Size, "size"},
+ // this used to be nsMsgSearchAttrib::SenderInAddressBook
+ // we used to have two Sender menuitems
+ // for backward compatability, we can still parse
+ // the old style. see bug #179803
+ {nsMsgSearchAttrib::Sender, "from in ab"},
+ {nsMsgSearchAttrib::JunkStatus, "junk status"},
+ {nsMsgSearchAttrib::JunkPercent, "junk percent"},
+ {nsMsgSearchAttrib::JunkScoreOrigin, "junk score origin"},
+ {nsMsgSearchAttrib::HasAttachmentStatus, "has attachment status"},
+};
+
+static const unsigned int sNumSearchAttribEntryTable =
+ MOZ_ARRAY_LENGTH(SearchAttribEntryTable);
+
+// Take a string which starts off with an attribute
+// and return the matching attribute. If the string is not in the table, and it
+// begins with a quote, then we can conclude that it is an arbitrary header.
+// Otherwise if not in the table, it is the id for a custom search term.
+nsresult NS_MsgGetAttributeFromString(const char *string, nsMsgSearchAttribValue *attrib,
+ nsACString &aCustomId)
+{
+ NS_ENSURE_ARG_POINTER(string);
+ NS_ENSURE_ARG_POINTER(attrib);
+
+ bool found = false;
+ bool isArbitraryHeader = false;
+ // arbitrary headers have a leading quote
+ if (*string != '"')
+ {
+ for (unsigned int idxAttrib = 0; idxAttrib < sNumSearchAttribEntryTable; idxAttrib++)
+ {
+ if (!PL_strcasecmp(string, SearchAttribEntryTable[idxAttrib].attribName))
+ {
+ found = true;
+ *attrib = SearchAttribEntryTable[idxAttrib].attrib;
+ break;
+ }
+ }
+ }
+ else // remove the leading quote
+ {
+ string++;
+ isArbitraryHeader = true;
+ }
+
+ if (!found && !isArbitraryHeader)
+ {
+ // must be a custom attribute
+ *attrib = nsMsgSearchAttrib::Custom;
+ aCustomId.Assign(string);
+ return NS_OK;
+ }
+
+ if (!found)
+ {
+ nsresult rv;
+ bool goodHdr;
+ IsRFC822HeaderFieldName(string, &goodHdr);
+ if (!goodHdr)
+ return NS_MSG_INVALID_CUSTOM_HEADER;
+ //49 is for showing customize... in ui, headers start from 50 onwards up until 99.
+ *attrib = nsMsgSearchAttrib::OtherHeader+1;
+
+ nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefService->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString headers;
+ prefBranch->GetCharPref(MAILNEWS_CUSTOM_HEADERS, getter_Copies(headers));
+
+ if (!headers.IsEmpty())
+ {
+ nsAutoCString hdrStr(headers);
+ hdrStr.StripWhitespace(); //remove whitespace before parsing
+
+ char *newStr= hdrStr.BeginWriting();
+ char *token = NS_strtok(":", &newStr);
+ uint32_t i=0;
+ while (token)
+ {
+ if (PL_strcasecmp(token, string) == 0)
+ {
+ *attrib += i; //we found custom header in the pref
+ found = true;
+ break;
+ }
+ token = NS_strtok(":", &newStr);
+ i++;
+ }
+ }
+ }
+ // If we didn't find the header in MAILNEWS_CUSTOM_HEADERS, we're
+ // going to return NS_OK and an attrib of nsMsgSearchAttrib::OtherHeader+1.
+ // in case it's a client side spam filter description filter,
+ // which doesn't add its headers to mailnews.customMailHeaders.
+ // We've already checked that it's a valid header and returned
+ // an error if so.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchTerm::GetAttributeFromString(const char *aString,
+ nsMsgSearchAttribValue *aAttrib)
+{
+ nsAutoCString customId;
+ return NS_MsgGetAttributeFromString(aString, aAttrib, customId);
+}
+
+nsresult NS_MsgGetStringForAttribute(int16_t attrib, const char **string)
+{
+ NS_ENSURE_ARG_POINTER(string);
+
+ bool found = false;
+ for (unsigned int idxAttrib = 0; idxAttrib < sNumSearchAttribEntryTable; idxAttrib++)
+ {
+ // I'm using the idx's as aliases into MSG_SearchAttribute and
+ // MSG_SearchOperator enums which is legal because of the way the
+ // enums are defined (starts at 0, numItems at end)
+ if (attrib == SearchAttribEntryTable[idxAttrib].attrib)
+ {
+ found = true;
+ *string = SearchAttribEntryTable[idxAttrib].attribName;
+ break;
+ }
+ }
+ if (!found)
+ *string = ""; // don't leave the string uninitialized
+
+ // we no longer return invalid attribute. If we cannot find the string in the table,
+ // then it is an arbitrary header. Return success regardless if found or not
+ return NS_OK;
+}
+
+typedef struct
+{
+ nsMsgSearchOpValue op;
+ const char *opName;
+} nsMsgSearchOperatorEntry;
+
+nsMsgSearchOperatorEntry SearchOperatorEntryTable[] =
+{
+ {nsMsgSearchOp::Contains, "contains"},
+ {nsMsgSearchOp::DoesntContain,"doesn't contain"},
+ {nsMsgSearchOp::Is,"is"},
+ {nsMsgSearchOp::Isnt, "isn't"},
+ {nsMsgSearchOp::IsEmpty, "is empty"},
+ {nsMsgSearchOp::IsntEmpty, "isn't empty"},
+ {nsMsgSearchOp::IsBefore, "is before"},
+ {nsMsgSearchOp::IsAfter, "is after"},
+ {nsMsgSearchOp::IsHigherThan, "is higher than"},
+ {nsMsgSearchOp::IsLowerThan, "is lower than"},
+ {nsMsgSearchOp::BeginsWith, "begins with"},
+ {nsMsgSearchOp::EndsWith, "ends with"},
+ {nsMsgSearchOp::IsInAB, "is in ab"},
+ {nsMsgSearchOp::IsntInAB, "isn't in ab"},
+ {nsMsgSearchOp::IsGreaterThan, "is greater than"},
+ {nsMsgSearchOp::IsLessThan, "is less than"},
+ {nsMsgSearchOp::Matches, "matches"},
+ {nsMsgSearchOp::DoesntMatch, "doesn't match"}
+};
+
+static const unsigned int sNumSearchOperatorEntryTable =
+ MOZ_ARRAY_LENGTH(SearchOperatorEntryTable);
+
+nsresult NS_MsgGetOperatorFromString(const char *string, int16_t *op)
+{
+ NS_ENSURE_ARG_POINTER(string);
+ NS_ENSURE_ARG_POINTER(op);
+
+ bool found = false;
+ for (unsigned int idxOp = 0; idxOp < sNumSearchOperatorEntryTable; idxOp++)
+ {
+ // I'm using the idx's as aliases into MSG_SearchAttribute and
+ // MSG_SearchOperator enums which is legal because of the way the
+ // enums are defined (starts at 0, numItems at end)
+ if (!PL_strcasecmp(string, SearchOperatorEntryTable[idxOp].opName))
+ {
+ found = true;
+ *op = SearchOperatorEntryTable[idxOp].op;
+ break;
+ }
+ }
+ return (found) ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
+nsresult NS_MsgGetStringForOperator(int16_t op, const char **string)
+{
+ NS_ENSURE_ARG_POINTER(string);
+
+ bool found = false;
+ for (unsigned int idxOp = 0; idxOp < sNumSearchOperatorEntryTable; idxOp++)
+ {
+ // I'm using the idx's as aliases into MSG_SearchAttribute and
+ // MSG_SearchOperator enums which is legal because of the way the
+ // enums are defined (starts at 0, numItems at end)
+ if (op == SearchOperatorEntryTable[idxOp].op)
+ {
+ found = true;
+ *string = SearchOperatorEntryTable[idxOp].opName;
+ break;
+ }
+ }
+
+ return (found) ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
+void NS_MsgGetUntranslatedStatusName (uint32_t s, nsCString *outName)
+{
+ const char *tmpOutName = NULL;
+#define MSG_STATUS_MASK (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |\
+ nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::New | nsMsgMessageFlags::Marked)
+ uint32_t maskOut = (s & MSG_STATUS_MASK);
+
+ // diddle the flags to pay attention to the most important ones first, if multiple
+ // flags are set. Should remove this code from the winfe.
+ if (maskOut & nsMsgMessageFlags::New)
+ maskOut = nsMsgMessageFlags::New;
+ if (maskOut & nsMsgMessageFlags::Replied &&
+ maskOut & nsMsgMessageFlags::Forwarded)
+ maskOut = nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded;
+ else if (maskOut & nsMsgMessageFlags::Forwarded)
+ maskOut = nsMsgMessageFlags::Forwarded;
+ else if (maskOut & nsMsgMessageFlags::Replied)
+ maskOut = nsMsgMessageFlags::Replied;
+
+ switch (maskOut)
+ {
+ case nsMsgMessageFlags::Read:
+ tmpOutName = "read";
+ break;
+ case nsMsgMessageFlags::Replied:
+ tmpOutName = "replied";
+ break;
+ case nsMsgMessageFlags::Forwarded:
+ tmpOutName = "forwarded";
+ break;
+ case nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied:
+ tmpOutName = "replied and forwarded";
+ break;
+ case nsMsgMessageFlags::New:
+ tmpOutName = "new";
+ break;
+ case nsMsgMessageFlags::Marked:
+ tmpOutName = "flagged";
+ break;
+ default:
+ // This is fine, status may be "unread" for example
+ break;
+ }
+
+ if (tmpOutName)
+ *outName = tmpOutName;
+}
+
+
+int32_t NS_MsgGetStatusValueFromName(char *name)
+{
+ if (!strcmp("read", name))
+ return nsMsgMessageFlags::Read;
+ if (!strcmp("replied", name))
+ return nsMsgMessageFlags::Replied;
+ if (!strcmp("forwarded", name))
+ return nsMsgMessageFlags::Forwarded;
+ if (!strcmp("replied and forwarded", name))
+ return nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied;
+ if (!strcmp("new", name))
+ return nsMsgMessageFlags::New;
+ if (!strcmp("flagged", name))
+ return nsMsgMessageFlags::Marked;
+ return 0;
+}
+
+
+// Needed for DeStream method.
+nsMsgSearchTerm::nsMsgSearchTerm()
+{
+ // initialize this to zero
+ m_value.string=nullptr;
+ m_value.attribute=0;
+ m_value.u.priority=0;
+ m_attribute = nsMsgSearchAttrib::Default;
+ m_operator = nsMsgSearchOp::Contains;
+ mBeginsGrouping = false;
+ mEndsGrouping = false;
+ m_matchAll = false;
+
+ // valgrind warning during GC/java data check suggests
+ // m_booleanp needs to be initialized too.
+ m_booleanOp = nsMsgSearchBooleanOp::BooleanAND;
+}
+
+nsMsgSearchTerm::nsMsgSearchTerm (
+ nsMsgSearchAttribValue attrib,
+ nsMsgSearchOpValue op,
+ nsIMsgSearchValue *val,
+ nsMsgSearchBooleanOperator boolOp,
+ const char * aCustomString)
+{
+ m_operator = op;
+ m_attribute = attrib;
+ m_booleanOp = boolOp;
+ if (attrib > nsMsgSearchAttrib::OtherHeader && attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes && aCustomString)
+ {
+ m_arbitraryHeader = aCustomString;
+ ToLowerCaseExceptSpecials(m_arbitraryHeader);
+ }
+ else if (attrib == nsMsgSearchAttrib::Custom)
+ {
+ m_customId = aCustomString;
+ }
+
+ nsMsgResultElement::AssignValues (val, &m_value);
+ m_matchAll = false;
+}
+
+
+
+nsMsgSearchTerm::~nsMsgSearchTerm ()
+{
+ if (IS_STRING_ATTRIBUTE (m_attribute) && m_value.string)
+ NS_Free(m_value.string);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgSearchTerm, nsIMsgSearchTerm)
+
+
+// Perhaps we could find a better place for this?
+// Caller needs to free.
+/* static */char *nsMsgSearchTerm::EscapeQuotesInStr(const char *str)
+{
+ int numQuotes = 0;
+ for (const char *strPtr = str; *strPtr; strPtr++)
+ if (*strPtr == '"')
+ numQuotes++;
+ int escapedStrLen = PL_strlen(str) + numQuotes;
+ char *escapedStr = (char *) PR_Malloc(escapedStrLen + 1);
+ if (escapedStr)
+ {
+ char *destPtr;
+ for (destPtr = escapedStr; *str; str++)
+ {
+ if (*str == '"')
+ *destPtr++ = '\\';
+ *destPtr++ = *str;
+ }
+ *destPtr = '\0';
+ }
+ return escapedStr;
+}
+
+
+nsresult nsMsgSearchTerm::OutputValue(nsCString &outputStr)
+{
+ if (IS_STRING_ATTRIBUTE(m_attribute) && m_value.string)
+ {
+ bool quoteVal = false;
+ // need to quote strings with ')' and strings starting with '"' or ' '
+ // filter code will escape quotes
+ if (PL_strchr(m_value.string, ')') ||
+ (m_value.string[0] == ' ') ||
+ (m_value.string[0] == '"'))
+ {
+ quoteVal = true;
+ outputStr += "\"";
+ }
+ if (PL_strchr(m_value.string, '"'))
+ {
+ char *escapedString = nsMsgSearchTerm::EscapeQuotesInStr(m_value.string);
+ if (escapedString)
+ {
+ outputStr += escapedString;
+ PR_Free(escapedString);
+ }
+
+ }
+ else
+ {
+ outputStr += m_value.string;
+ }
+ if (quoteVal)
+ outputStr += "\"";
+ }
+ else
+ {
+ switch (m_attribute)
+ {
+ case nsMsgSearchAttrib::Date:
+ {
+ PRExplodedTime exploded;
+ PR_ExplodeTime(m_value.u.date, PR_LocalTimeParameters, &exploded);
+
+ // wow, so tm_mon is 0 based, tm_mday is 1 based.
+ char dateBuf[100];
+ PR_FormatTimeUSEnglish (dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded);
+ outputStr += dateBuf;
+ break;
+ }
+ case nsMsgSearchAttrib::AgeInDays:
+ {
+ outputStr.AppendInt(m_value.u.age);
+ break;
+ }
+ case nsMsgSearchAttrib::Label:
+ {
+ outputStr.AppendInt(m_value.u.label);
+ break;
+ }
+ case nsMsgSearchAttrib::JunkStatus:
+ {
+ outputStr.AppendInt(m_value.u.junkStatus); // only if we write to disk, right?
+ break;
+ }
+ case nsMsgSearchAttrib::JunkPercent:
+ {
+ outputStr.AppendInt(m_value.u.junkPercent);
+ break;
+ }
+ case nsMsgSearchAttrib::MsgStatus:
+ {
+ nsAutoCString status;
+ NS_MsgGetUntranslatedStatusName (m_value.u.msgStatus, &status);
+ outputStr += status;
+ break;
+ }
+ case nsMsgSearchAttrib::Priority:
+ {
+ nsAutoCString priority;
+ NS_MsgGetUntranslatedPriorityName(m_value.u.priority, priority);
+ outputStr += priority;
+ break;
+ }
+ case nsMsgSearchAttrib::HasAttachmentStatus:
+ {
+ outputStr.Append("true"); // don't need anything here, really
+ break;
+ }
+ case nsMsgSearchAttrib::Size:
+ {
+ outputStr.AppendInt(m_value.u.size);
+ break;
+ }
+ case nsMsgSearchAttrib::Uint32HdrProperty:
+ {
+ outputStr.AppendInt(m_value.u.msgStatus);
+ break;
+ }
+ default:
+ NS_ASSERTION(false, "trying to output invalid attribute");
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchTerm::GetTermAsString (nsACString &outStream)
+{
+ const char *operatorStr;
+ nsAutoCString outputStr;
+ nsresult rv;
+
+ if (m_matchAll)
+ {
+ outStream = "ALL";
+ return NS_OK;
+ }
+
+ // if arbitrary header, use it instead!
+ if (m_attribute > nsMsgSearchAttrib::OtherHeader &&
+ m_attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes)
+ {
+ outputStr = "\"";
+ outputStr += m_arbitraryHeader;
+ outputStr += "\"";
+ }
+ else if (m_attribute == nsMsgSearchAttrib::Custom)
+ {
+ // use the custom id as the string
+ outputStr = m_customId;
+ }
+
+ else if (m_attribute == nsMsgSearchAttrib::Uint32HdrProperty)
+ {
+ outputStr = m_hdrProperty;
+ }
+ else {
+ const char *attrib;
+ rv = NS_MsgGetStringForAttribute(m_attribute, &attrib);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outputStr = attrib;
+ }
+
+ outputStr += ',';
+
+ rv = NS_MsgGetStringForOperator(m_operator, &operatorStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outputStr += operatorStr;
+ outputStr += ',';
+
+ OutputValue(outputStr);
+ outStream = outputStr;
+ return NS_OK;
+}
+
+// fill in m_value from the input stream.
+nsresult nsMsgSearchTerm::ParseValue(char *inStream)
+{
+ if (IS_STRING_ATTRIBUTE(m_attribute))
+ {
+ bool quoteVal = false;
+ while (isspace(*inStream))
+ inStream++;
+ // need to remove pair of '"', if present
+ if (*inStream == '"')
+ {
+ quoteVal = true;
+ inStream++;
+ }
+ int valueLen = PL_strlen(inStream);
+ if (quoteVal && inStream[valueLen - 1] == '"')
+ valueLen--;
+
+ m_value.string = (char *) PR_Malloc(valueLen + 1);
+ PL_strncpy(m_value.string, inStream, valueLen + 1);
+ m_value.string[valueLen] = '\0';
+ CopyUTF8toUTF16(m_value.string, m_value.utf16String);
+ }
+ else
+ {
+ switch (m_attribute)
+ {
+ case nsMsgSearchAttrib::Date:
+ PR_ParseTimeString (inStream, false, &m_value.u.date);
+ break;
+ case nsMsgSearchAttrib::MsgStatus:
+ m_value.u.msgStatus = NS_MsgGetStatusValueFromName(inStream);
+ break;
+ case nsMsgSearchAttrib::Priority:
+ NS_MsgGetPriorityFromString(inStream, m_value.u.priority);
+ break;
+ case nsMsgSearchAttrib::AgeInDays:
+ m_value.u.age = atoi(inStream);
+ break;
+ case nsMsgSearchAttrib::Label:
+ m_value.u.label = atoi(inStream);
+ break;
+ case nsMsgSearchAttrib::JunkStatus:
+ m_value.u.junkStatus = atoi(inStream); // only if we read from disk, right?
+ break;
+ case nsMsgSearchAttrib::JunkPercent:
+ m_value.u.junkPercent = atoi(inStream);
+ break;
+ case nsMsgSearchAttrib::HasAttachmentStatus:
+ m_value.u.msgStatus = nsMsgMessageFlags::Attachment;
+ break; // this should always be true.
+ case nsMsgSearchAttrib::Size:
+ m_value.u.size = atoi(inStream);
+ break;
+ default:
+ NS_ASSERTION(false, "invalid attribute parsing search term value");
+ break;
+ }
+ }
+ m_value.attribute = m_attribute;
+ return NS_OK;
+}
+
+// find the operator code for this operator string.
+nsresult
+nsMsgSearchTerm::ParseOperator(char *inStream, nsMsgSearchOpValue *value)
+{
+ NS_ENSURE_ARG_POINTER(value);
+ int16_t operatorVal;
+ while (isspace(*inStream))
+ inStream++;
+
+ char *commaSep = PL_strchr(inStream, ',');
+
+ if (commaSep)
+ *commaSep = '\0';
+
+ nsresult err = NS_MsgGetOperatorFromString(inStream, &operatorVal);
+ *value = (nsMsgSearchOpValue) operatorVal;
+ return err;
+}
+
+// find the attribute code for this comma-delimited attribute.
+nsresult
+nsMsgSearchTerm::ParseAttribute(char *inStream, nsMsgSearchAttribValue *attrib)
+{
+ while (isspace(*inStream))
+ inStream++;
+
+ // if we are dealing with an arbitrary header, it will be quoted....
+ // it seems like a kludge, but to distinguish arbitrary headers from
+ // standard headers with the same name, like "Date", we'll use the
+ // presence of the quote to recognize arbitrary headers. We leave the
+ // leading quote as a flag, but remove the trailing quote.
+ bool quoteVal = false;
+ if (*inStream == '"')
+ quoteVal = true;
+
+ // arbitrary headers are quoted. Skip first character, which will be the
+ // first quote for arbitrary headers
+ char *separator = strchr(inStream + 1, quoteVal ? '"' : ',');
+
+ if (separator)
+ *separator = '\0';
+
+ nsAutoCString customId;
+ nsresult rv = NS_MsgGetAttributeFromString(inStream, attrib, m_customId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*attrib > nsMsgSearchAttrib::OtherHeader && *attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) // if we are dealing with an arbitrary header....
+ {
+ m_arbitraryHeader = inStream + 1; // remove the leading quote
+ ToLowerCaseExceptSpecials(m_arbitraryHeader);
+ }
+ return rv;
+}
+
+// De stream one search term. If the condition looks like
+// condition = "(to or cc, contains, r-thompson) AND (body, doesn't contain, fred)"
+// This routine should get called twice, the first time
+// with "to or cc, contains, r-thompson", the second time with
+// "body, doesn't contain, fred"
+
+nsresult nsMsgSearchTerm::DeStreamNew (char *inStream, int16_t /*length*/)
+{
+ if (!strcmp(inStream, "ALL"))
+ {
+ m_matchAll = true;
+ return NS_OK;
+ }
+ char *commaSep = PL_strchr(inStream, ',');
+ nsresult rv = ParseAttribute(inStream, &m_attribute); // will allocate space for arbitrary header if necessary
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!commaSep)
+ return NS_ERROR_INVALID_ARG;
+ char *secondCommaSep = PL_strchr(commaSep + 1, ',');
+ if (commaSep)
+ rv = ParseOperator(commaSep + 1, &m_operator);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // convert label filters and saved searches to keyword equivalents
+ if (secondCommaSep)
+ ParseValue(secondCommaSep + 1);
+ if (m_attribute == nsMsgSearchAttrib::Label)
+ {
+ nsAutoCString keyword("$label");
+ m_value.attribute = m_attribute = nsMsgSearchAttrib::Keywords;
+ keyword.Append('0' + m_value.u.label);
+ m_value.string = PL_strdup(keyword.get());
+ CopyUTF8toUTF16(m_value.string, m_value.utf16String);
+ }
+ return NS_OK;
+}
+
+
+// Looks in the MessageDB for the user specified arbitrary header, if it finds the header, it then looks for a match against
+// the value for the header.
+nsresult nsMsgSearchTerm::MatchArbitraryHeader (nsIMsgSearchScopeTerm *scope,
+ uint32_t length /* in lines*/,
+ const char *charset,
+ bool charsetOverride,
+ nsIMsgDBHdr *msg,
+ nsIMsgDatabase* db,
+ const char * headers,
+ uint32_t headersSize,
+ bool ForFiltering,
+ bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ *pResult = false;
+ nsresult rv = NS_OK;
+ bool matchExpected = m_operator == nsMsgSearchOp::Contains ||
+ m_operator == nsMsgSearchOp::Is ||
+ m_operator == nsMsgSearchOp::BeginsWith ||
+ m_operator == nsMsgSearchOp::EndsWith;
+ // init result to what we want if we don't find the header at all
+ bool result = !matchExpected;
+
+ nsCString dbHdrValue;
+ msg->GetStringProperty(m_arbitraryHeader.get(), getter_Copies(dbHdrValue));
+ if (!dbHdrValue.IsEmpty())
+ // match value with the other info.
+ return MatchRfc2047String(dbHdrValue, charset, charsetOverride, pResult);
+
+ nsMsgBodyHandler * bodyHandler =
+ new nsMsgBodyHandler (scope, length, msg, db, headers, headersSize,
+ ForFiltering);
+ NS_ENSURE_TRUE(bodyHandler, NS_ERROR_OUT_OF_MEMORY);
+
+ bodyHandler->SetStripHeaders (false);
+
+ nsCString headerFullValue; // contains matched header value accumulated over multiple lines.
+ nsAutoCString buf;
+ nsAutoCString curMsgHeader;
+ bool searchingHeaders = true;
+
+ // We will allow accumulation of received headers;
+ bool isReceivedHeader = m_arbitraryHeader.EqualsLiteral("received");
+
+ while (searchingHeaders)
+ {
+ nsCString charsetIgnored;
+ if (bodyHandler->GetNextLine(buf, charsetIgnored) < 0 || EMPTY_MESSAGE_LINE(buf))
+ searchingHeaders = false;
+ bool isContinuationHeader = searchingHeaders ? NS_IsAsciiWhitespace(buf.CharAt(0))
+ : false;
+
+ // We try to match the header from the last time through the loop, which should now
+ // have accumulated over possible multiple lines. For all headers except received,
+ // we process a single accumulation, but process accumulated received at the end.
+ if (!searchingHeaders || (!isContinuationHeader &&
+ (!headerFullValue.IsEmpty() && !isReceivedHeader)))
+ {
+ // Make sure buf has info besides just the header.
+ // Otherwise, it's either an empty header, or header not found.
+ if (!headerFullValue.IsEmpty())
+ {
+ bool stringMatches;
+ // match value with the other info.
+ rv = MatchRfc2047String(headerFullValue, charset, charsetOverride, &stringMatches);
+ if (matchExpected == stringMatches) // if we found a match
+ {
+ searchingHeaders = false; // then stop examining the headers
+ result = stringMatches;
+ }
+ }
+ break;
+ }
+
+ char * buf_end = (char *) (buf.get() + buf.Length());
+ int headerLength = m_arbitraryHeader.Length();
+
+ // If the line starts with whitespace, then we use the current header.
+ if (!isContinuationHeader)
+ {
+ // here we start a new header
+ uint32_t colonPos = buf.FindChar(':');
+ curMsgHeader = StringHead(buf, colonPos);
+ }
+
+ if (curMsgHeader.Equals(m_arbitraryHeader, nsCaseInsensitiveCStringComparator()))
+ {
+ // process the value
+ // value occurs after the header name or whitespace continuation char.
+ const char * headerValue = buf.get() + (isContinuationHeader ? 1 : headerLength);
+ if (headerValue < buf_end && headerValue[0] == ':') // + 1 to account for the colon which is MANDATORY
+ headerValue++;
+
+ // strip leading white space
+ while (headerValue < buf_end && isspace(*headerValue))
+ headerValue++; // advance to next character
+
+ // strip trailing white space
+ char * end = buf_end - 1;
+ while (end > headerValue && isspace(*end)) // while we haven't gone back past the start and we are white space....
+ {
+ *end = '\0'; // eat up the white space
+ end--; // move back and examine the previous character....
+ }
+
+ // any continuation whitespace is converted to a single space. This includes both a continuation line, or a
+ // second value of the same header (eg the received header)
+ if (!headerFullValue.IsEmpty())
+ headerFullValue.AppendLiteral(" ");
+ headerFullValue.Append(nsDependentCString(headerValue));
+ }
+ }
+ delete bodyHandler;
+ *pResult = result;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgSearchTerm::MatchHdrProperty(nsIMsgDBHdr *aHdr, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aHdr);
+
+ nsCString dbHdrValue;
+ aHdr->GetStringProperty(m_hdrProperty.get(), getter_Copies(dbHdrValue));
+ return MatchString(dbHdrValue, nullptr, aResult);
+}
+
+NS_IMETHODIMP nsMsgSearchTerm::MatchFolderFlag(nsIMsgDBHdr *aMsgToMatch, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aMsgToMatch);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nsresult rv = aMsgToMatch->GetFolder(getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t folderFlags;
+ msgFolder->GetFlags(&folderFlags);
+ return MatchStatus(folderFlags, aResult);
+}
+
+NS_IMETHODIMP nsMsgSearchTerm::MatchUint32HdrProperty(nsIMsgDBHdr *aHdr, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aHdr);
+
+ nsresult rv = NS_OK;
+ uint32_t dbHdrValue;
+ aHdr->GetUint32Property(m_hdrProperty.get(), &dbHdrValue);
+
+ bool result = false;
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::IsGreaterThan:
+ if (dbHdrValue > m_value.u.msgStatus)
+ result = true;
+ break;
+ case nsMsgSearchOp::IsLessThan:
+ if (dbHdrValue < m_value.u.msgStatus)
+ result = true;
+ break;
+ case nsMsgSearchOp::Is:
+ if (dbHdrValue == m_value.u.msgStatus)
+ result = true;
+ break;
+ case nsMsgSearchOp::Isnt:
+ if (dbHdrValue != m_value.u.msgStatus)
+ result = true;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for uint");
+ }
+ *aResult = result;
+ return rv;
+}
+
+nsresult nsMsgSearchTerm::MatchBody (nsIMsgSearchScopeTerm *scope, uint64_t offset, uint32_t length /*in lines*/, const char *folderCharset,
+ nsIMsgDBHdr *msg, nsIMsgDatabase* db, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ nsresult rv = NS_OK;
+
+ bool result = false;
+ *pResult = false;
+
+ // Small hack so we don't look all through a message when someone has
+ // specified "BODY IS foo". ### Since length is in lines, this is not quite right.
+ if ((length > 0) && (m_operator == nsMsgSearchOp::Is || m_operator == nsMsgSearchOp::Isnt))
+ length = PL_strlen (m_value.string);
+
+ nsMsgBodyHandler * bodyHan = new nsMsgBodyHandler (scope, length, msg, db);
+ if (!bodyHan)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsAutoCString buf;
+ bool endOfFile = false; // if retValue == 0, we've hit the end of the file
+ uint32_t lines = 0;
+
+ // Change the sense of the loop so we don't bail out prematurely
+ // on negative terms. i.e. opDoesntContain must look at all lines
+ bool boolContinueLoop;
+ GetMatchAllBeforeDeciding(&boolContinueLoop);
+ result = boolContinueLoop;
+
+ // If there's a '=' in the search term, then we're not going to do
+ // quoted printable decoding. Otherwise we assume everything is
+ // quoted printable. Obviously everything isn't quoted printable, but
+ // since we don't have a MIME parser handy, and we want to err on the
+ // side of too many hits rather than not enough, we'll assume in that
+ // general direction. Blech. ### FIX ME
+ // bug fix #314637: for stateful charsets like ISO-2022-JP, we don't
+ // want to decode quoted printable since it contains '='.
+ bool isQuotedPrintable = !nsMsgI18Nstateful_charset(folderCharset) &&
+ (PL_strchr (m_value.string, '=') == nullptr);
+
+ nsCString compare;
+ nsCString charset;
+ while (!endOfFile && result == boolContinueLoop)
+ {
+ if (bodyHan->GetNextLine(buf, charset) >= 0)
+ {
+ bool softLineBreak = false;
+ // Do in-place decoding of quoted printable
+ if (isQuotedPrintable)
+ {
+ softLineBreak = StringEndsWith(buf, NS_LITERAL_CSTRING("="));
+ MsgStripQuotedPrintable ((unsigned char*)buf.get());
+ // in case the string shrunk, reset the length. If soft line break,
+ // chop off the last char as well.
+ size_t bufLength = strlen(buf.get());
+ if ((bufLength > 0) && softLineBreak)
+ --bufLength;
+ buf.SetLength(bufLength);
+ }
+ compare.Append(buf);
+ // If this line ends with a soft line break, loop around
+ // and get the next line before looking for the search string.
+ // This assumes the message can't end on a QP soft-line break.
+ // That seems like a pretty safe assumption.
+ if (softLineBreak)
+ continue;
+ if (!compare.IsEmpty())
+ {
+ char startChar = (char) compare.CharAt(0);
+ if (startChar != '\r' && startChar != '\n')
+ {
+ rv = MatchString(compare,
+ charset.IsEmpty() ? folderCharset : charset.get(),
+ &result);
+ lines++;
+ }
+ compare.Truncate();
+ }
+ }
+ else
+ endOfFile = true;
+ }
+#ifdef DO_I18N
+ if(conv)
+ INTL_DestroyCharCodeConverter(conv);
+#endif
+ delete bodyHan;
+ *pResult = result;
+ return rv;
+}
+
+nsresult nsMsgSearchTerm::InitializeAddressBook()
+{
+ // the search attribute value has the URI for the address book we need to load.
+ // we need both the database and the directory.
+ nsresult rv = NS_OK;
+
+ if (mDirectory)
+ {
+ nsCString uri;
+ rv = mDirectory->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!uri.Equals(m_value.string))
+ // clear out the directory....we are no longer pointing to the right one
+ mDirectory = nullptr;
+ }
+ if (!mDirectory)
+ {
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = abManager->GetDirectory(nsDependentCString(m_value.string), getter_AddRefs(mDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgSearchTerm::MatchInAddressBook(const nsAString &aAddress,
+ bool *pResult)
+{
+ nsresult rv = InitializeAddressBook();
+ *pResult = false;
+
+ // Some junkmails have empty From: fields.
+ if (aAddress.IsEmpty())
+ return rv;
+
+ if (mDirectory)
+ {
+ nsCOMPtr<nsIAbCard> cardForAddress = nullptr;
+ rv = mDirectory->CardForEmailAddress(NS_ConvertUTF16toUTF8(aAddress),
+ getter_AddRefs(cardForAddress));
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED)
+ return rv;
+ switch(m_operator)
+ {
+ case nsMsgSearchOp::IsInAB:
+ if (cardForAddress)
+ *pResult = true;
+ break;
+ case nsMsgSearchOp::IsntInAB:
+ if (!cardForAddress)
+ *pResult = true;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for address book");
+ }
+ }
+
+ return rv;
+}
+
+// *pResult is false when strings don't match, true if they do.
+nsresult nsMsgSearchTerm::MatchRfc2047String(const nsACString &rfc2047string,
+ const char *charset,
+ bool charsetOverride,
+ bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ nsCOMPtr<nsIMimeConverter> mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID);
+ nsAutoString stringToMatch;
+ nsresult rv = mimeConverter->DecodeMimeHeader(
+ PromiseFlatCString(rfc2047string).get(), charset, charsetOverride, false,
+ stringToMatch);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (m_operator == nsMsgSearchOp::IsInAB ||
+ m_operator == nsMsgSearchOp::IsntInAB)
+ return MatchInAddressBook(stringToMatch, pResult);
+
+ return MatchString(stringToMatch, pResult);
+}
+
+// *pResult is false when strings don't match, true if they do.
+nsresult nsMsgSearchTerm::MatchString(const nsACString &stringToMatch,
+ const char *charset,
+ bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ bool result = false;
+
+ nsresult rv = NS_OK;
+
+ // Save some performance for opIsEmpty / opIsntEmpty
+ if (nsMsgSearchOp::IsEmpty == m_operator)
+ {
+ if (stringToMatch.IsEmpty())
+ result = true;
+ }
+ else if (nsMsgSearchOp::IsntEmpty == m_operator)
+ {
+ if (!stringToMatch.IsEmpty())
+ result = true;
+ }
+ else
+ {
+ nsAutoString utf16StrToMatch;
+ if (charset != nullptr)
+ {
+ ConvertToUnicode(charset, nsCString(stringToMatch), utf16StrToMatch);
+ }
+ else {
+ NS_ASSERTION(MsgIsUTF8(stringToMatch), "stringToMatch is not UTF-8");
+ CopyUTF8toUTF16(stringToMatch, utf16StrToMatch);
+ }
+ rv = MatchString(utf16StrToMatch, &result);
+ }
+
+ *pResult = result;
+ return rv;
+}
+
+// *pResult is false when strings don't match, true if they do.
+nsresult nsMsgSearchTerm::MatchString(const nsAString &utf16StrToMatch,
+ bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ bool result = false;
+
+ nsresult rv = NS_OK;
+ auto needle = m_value.utf16String;
+
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::Contains:
+ if (CaseInsensitiveFindInReadable(needle, utf16StrToMatch))
+ result = true;
+ break;
+ case nsMsgSearchOp::DoesntContain:
+ if (!CaseInsensitiveFindInReadable(needle, utf16StrToMatch))
+ result = true;
+ break;
+ case nsMsgSearchOp::Is:
+ if(needle.Equals(utf16StrToMatch, nsCaseInsensitiveStringComparator()))
+ result = true;
+ break;
+ case nsMsgSearchOp::Isnt:
+ if(!needle.Equals(utf16StrToMatch, nsCaseInsensitiveStringComparator()))
+ result = true;
+ break;
+ case nsMsgSearchOp::IsEmpty:
+ if (utf16StrToMatch.IsEmpty())
+ result = true;
+ break;
+ case nsMsgSearchOp::IsntEmpty:
+ if (!utf16StrToMatch.IsEmpty())
+ result = true;
+ break;
+ case nsMsgSearchOp::BeginsWith:
+ if (StringBeginsWith(utf16StrToMatch, needle,
+ nsCaseInsensitiveStringComparator()))
+ result = true;
+ break;
+ case nsMsgSearchOp::EndsWith:
+ if (StringEndsWith(utf16StrToMatch, needle,
+ nsCaseInsensitiveStringComparator()))
+ result = true;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for matching search results");
+ }
+
+ *pResult = result;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgSearchTerm::GetMatchAllBeforeDeciding (bool *aResult)
+{
+ *aResult = (m_operator == nsMsgSearchOp::DoesntContain || m_operator == nsMsgSearchOp::Isnt);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchTerm::MatchRfc822String(const nsACString &string,
+ const char *charset,
+ bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ *pResult = false;
+ bool result;
+
+ // Change the sense of the loop so we don't bail out prematurely
+ // on negative terms. i.e. opDoesntContain must look at all recipients
+ bool boolContinueLoop;
+ GetMatchAllBeforeDeciding(&boolContinueLoop);
+ result = boolContinueLoop;
+
+ // If the operator is Contains, then we can cheat and avoid having to parse
+ // addresses. This does open up potential spurious matches for punctuation
+ // (e.g., ; or <), but the likelihood of users intending to search for these
+ // and also being able to match them is rather low. This optimization is not
+ // applicable to any other search type.
+ if (m_operator == nsMsgSearchOp::Contains)
+ return MatchRfc2047String(string, charset, false, pResult);
+
+ nsTArray<nsString> names, addresses;
+ ExtractAllAddresses(EncodedHeader(string, charset), names, addresses);
+ uint32_t count = names.Length();
+
+ nsresult rv = NS_OK;
+ for (uint32_t i = 0; i < count && result == boolContinueLoop; i++)
+ {
+ if ( m_operator == nsMsgSearchOp::IsInAB ||
+ m_operator == nsMsgSearchOp::IsntInAB)
+ {
+ rv = MatchInAddressBook(addresses[i], &result);
+ }
+ else
+ {
+ rv = MatchString(names[i], &result);
+ if (boolContinueLoop == result)
+ rv = MatchString(addresses[i], &result);
+ }
+ }
+ *pResult = result;
+ return rv;
+}
+
+
+nsresult nsMsgSearchTerm::GetLocalTimes (PRTime a, PRTime b, PRExplodedTime &aExploded, PRExplodedTime &bExploded)
+{
+ PR_ExplodeTime(a, PR_LocalTimeParameters, &aExploded);
+ PR_ExplodeTime(b, PR_LocalTimeParameters, &bExploded);
+ return NS_OK;
+}
+
+
+nsresult nsMsgSearchTerm::MatchDate (PRTime dateToMatch, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ nsresult rv = NS_OK;
+ bool result = false;
+
+ PRExplodedTime tmToMatch, tmThis;
+ if (NS_SUCCEEDED(GetLocalTimes(dateToMatch, m_value.u.date, tmToMatch, tmThis)))
+ {
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::IsBefore:
+ if (tmToMatch.tm_year < tmThis.tm_year ||
+ (tmToMatch.tm_year == tmThis.tm_year &&
+ tmToMatch.tm_yday < tmThis.tm_yday))
+ result = true;
+ break;
+ case nsMsgSearchOp::IsAfter:
+ if (tmToMatch.tm_year > tmThis.tm_year ||
+ (tmToMatch.tm_year == tmThis.tm_year &&
+ tmToMatch.tm_yday > tmThis.tm_yday))
+ result = true;
+ break;
+ case nsMsgSearchOp::Is:
+ if (tmThis.tm_year == tmToMatch.tm_year &&
+ tmThis.tm_month == tmToMatch.tm_month &&
+ tmThis.tm_mday == tmToMatch.tm_mday)
+ result = true;
+ break;
+ case nsMsgSearchOp::Isnt:
+ if (tmThis.tm_year != tmToMatch.tm_year ||
+ tmThis.tm_month != tmToMatch.tm_month ||
+ tmThis.tm_mday != tmToMatch.tm_mday)
+ result = true;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for dates");
+ }
+ }
+ *pResult = result;
+ return rv;
+}
+
+
+nsresult nsMsgSearchTerm::MatchAge (PRTime msgDate, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ bool result = false;
+ nsresult rv = NS_OK;
+
+ PRTime now = PR_Now();
+ PRTime cutOffDay = now - m_value.u.age * PR_USEC_PER_DAY;
+
+ bool cutOffDayInTheFuture = m_value.u.age < 0;
+
+ // So now cutOffDay is the PRTime cut-off point.
+ // Any msg with a time less than that will be past the age.
+
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::IsGreaterThan: // is older than, or more in the future
+ if ((!cutOffDayInTheFuture && msgDate < cutOffDay) ||
+ (cutOffDayInTheFuture && msgDate > cutOffDay))
+ result = true;
+ break;
+ case nsMsgSearchOp::IsLessThan: // is younger than, or less in the future
+ if ((!cutOffDayInTheFuture && msgDate > cutOffDay) ||
+ (cutOffDayInTheFuture && msgDate < cutOffDay))
+ result = true;
+ break;
+ case nsMsgSearchOp::Is:
+ PRExplodedTime msgDateExploded;
+ PRExplodedTime cutOffDayExploded;
+ if (NS_SUCCEEDED(GetLocalTimes(msgDate, cutOffDay, msgDateExploded, cutOffDayExploded)))
+ {
+ if ((msgDateExploded.tm_mday == cutOffDayExploded.tm_mday) &&
+ (msgDateExploded.tm_month == cutOffDayExploded.tm_month) &&
+ (msgDateExploded.tm_year == cutOffDayExploded.tm_year))
+ result = true;
+ }
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for msg age");
+ }
+ *pResult = result;
+ return rv;
+}
+
+
+nsresult nsMsgSearchTerm::MatchSize (uint32_t sizeToMatch, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ nsresult rv = NS_OK;
+ bool result = false;
+ // We reduce the sizeToMatch rather than supplied size
+ // as then we can do an exact match on the displayed value
+ // which will be less confusing to the user.
+ uint32_t sizeToMatchKB = sizeToMatch;
+
+ if (sizeToMatchKB < 1024)
+ sizeToMatchKB = 1024;
+
+ sizeToMatchKB /= 1024;
+
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::IsGreaterThan:
+ if (sizeToMatchKB > m_value.u.size)
+ result = true;
+ break;
+ case nsMsgSearchOp::IsLessThan:
+ if (sizeToMatchKB < m_value.u.size)
+ result = true;
+ break;
+ case nsMsgSearchOp::Is:
+ if (sizeToMatchKB == m_value.u.size)
+ result = true;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for size to match");
+ }
+ *pResult = result;
+ return rv;
+}
+
+nsresult nsMsgSearchTerm::MatchJunkStatus(const char *aJunkScore, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ if (m_operator == nsMsgSearchOp::IsEmpty)
+ {
+ *pResult = !(aJunkScore && *aJunkScore);
+ return NS_OK;
+ }
+ if (m_operator == nsMsgSearchOp::IsntEmpty)
+ {
+ *pResult = (aJunkScore && *aJunkScore);
+ return NS_OK;
+ }
+
+ nsMsgJunkStatus junkStatus;
+ if (aJunkScore && *aJunkScore) {
+ junkStatus = (atoi(aJunkScore) == nsIJunkMailPlugin::IS_SPAM_SCORE) ?
+ nsIJunkMailPlugin::JUNK :
+ nsIJunkMailPlugin::GOOD;
+
+ }
+ else {
+ // the in UI, we only show "junk" or "not junk"
+ // unknown, or nsIJunkMailPlugin::UNCLASSIFIED is shown as not junk
+ // so for the search to work as expected, treat unknown as not junk
+ junkStatus = nsIJunkMailPlugin::GOOD;
+ }
+
+ nsresult rv = NS_OK;
+ bool matches = (junkStatus == m_value.u.junkStatus);
+
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::Is:
+ break;
+ case nsMsgSearchOp::Isnt:
+ matches = !matches;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ matches = false;
+ NS_ERROR("invalid compare op for junk status");
+ }
+
+ *pResult = matches;
+ return rv;
+}
+
+nsresult nsMsgSearchTerm::MatchJunkScoreOrigin(const char *aJunkScoreOrigin, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ bool matches = false;
+ nsresult rv = NS_OK;
+
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::Is:
+ matches = aJunkScoreOrigin && !strcmp(aJunkScoreOrigin, m_value.string);
+ break;
+ case nsMsgSearchOp::Isnt:
+ matches = !aJunkScoreOrigin || strcmp(aJunkScoreOrigin, m_value.string);
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for junk score origin");
+ }
+
+ *pResult = matches;
+ return rv;
+}
+
+nsresult nsMsgSearchTerm::MatchJunkPercent(uint32_t aJunkPercent, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ nsresult rv = NS_OK;
+ bool result = false;
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::IsGreaterThan:
+ if (aJunkPercent > m_value.u.junkPercent)
+ result = true;
+ break;
+ case nsMsgSearchOp::IsLessThan:
+ if (aJunkPercent < m_value.u.junkPercent)
+ result = true;
+ break;
+ case nsMsgSearchOp::Is:
+ if (aJunkPercent == m_value.u.junkPercent)
+ result = true;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for junk percent");
+ }
+ *pResult = result;
+ return rv;
+}
+
+
+nsresult nsMsgSearchTerm::MatchLabel(nsMsgLabelValue aLabelValue, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ nsresult rv = NS_OK;
+ bool result = false;
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::Is:
+ if (m_value.u.label == aLabelValue)
+ result = true;
+ break;
+ case nsMsgSearchOp::Isnt:
+ if (m_value.u.label != aLabelValue)
+ result = true;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for label value");
+ }
+
+ *pResult = result;
+ return rv;
+}
+
+// MatchStatus () is not only used for nsMsgMessageFlags but also for
+// nsMsgFolderFlags (both being 'unsigned long')
+nsresult nsMsgSearchTerm::MatchStatus(uint32_t statusToMatch, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ nsresult rv = NS_OK;
+ bool matches = (statusToMatch & m_value.u.msgStatus);
+
+// nsMsgSearchOp::Is and nsMsgSearchOp::Isnt are intentionally used as
+// Contains and DoesntContain respectively, for legacy reasons.
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::Is:
+ break;
+ case nsMsgSearchOp::Isnt:
+ matches = !matches;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ matches = false;
+ NS_ERROR("invalid compare op for msg status");
+ }
+
+ *pResult = matches;
+ return rv;
+}
+
+/*
+ * MatchKeyword Logic table (*pResult: + is true, - is false)
+ *
+ * # Valid Tokens IsEmpty IsntEmpty Contains DoesntContain Is Isnt
+ * 0 + - - + - +
+ * Term found? N Y N Y N Y N Y
+ * 1 - + - + + - - + + -
+ * >1 - + - + + - - - + +
+ */
+// look up nsMsgSearchTerm::m_value in space-delimited keywordList
+nsresult nsMsgSearchTerm::MatchKeyword(const nsACString& keywordList, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ bool matches = false;
+
+ // special-case empty for performance reasons
+ if (keywordList.IsEmpty())
+ {
+ *pResult = m_operator != nsMsgSearchOp::Contains &&
+ m_operator != nsMsgSearchOp::Is &&
+ m_operator != nsMsgSearchOp::IsntEmpty;
+ return NS_OK;
+ }
+
+ // check if we can skip expensive valid keywordList test
+ if (m_operator == nsMsgSearchOp::DoesntContain ||
+ m_operator == nsMsgSearchOp::Contains)
+ {
+ nsCString keywordString(keywordList);
+ const uint32_t kKeywordLen = PL_strlen(m_value.string);
+ const char* matchStart = PL_strstr(keywordString.get(), m_value.string);
+ while (matchStart)
+ {
+ // For a real match, matchStart must be the start of the keywordList or
+ // preceded by a space and matchEnd must point to a \0 or space.
+ const char* matchEnd = matchStart + kKeywordLen;
+ if ((matchStart == keywordString.get() || matchStart[-1] == ' ') &&
+ (!*matchEnd || *matchEnd == ' '))
+ {
+ // found the keyword
+ *pResult = m_operator == nsMsgSearchOp::Contains;
+ return NS_OK;
+ }
+ // no match yet, so search on
+ matchStart = PL_strstr(matchEnd, m_value.string);
+ }
+ // keyword not found
+ *pResult = m_operator == nsMsgSearchOp::DoesntContain;
+ return NS_OK;
+ }
+
+ // Only accept valid keys in tokens.
+ nsresult rv = NS_OK;
+ nsTArray<nsCString> keywordArray;
+ ParseString(keywordList, ' ', keywordArray);
+ nsCOMPtr<nsIMsgTagService> tagService(do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through tokens in keywords
+ uint32_t count = keywordArray.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ // is this token a valid tag? Otherwise ignore it
+ bool isValid;
+ rv = tagService->IsValidKey(keywordArray[i], &isValid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isValid)
+ {
+ // IsEmpty fails on any valid token
+ if (m_operator == nsMsgSearchOp::IsEmpty)
+ {
+ *pResult = false;
+ return rv;
+ }
+
+ // IsntEmpty succeeds on any valid token
+ if (m_operator == nsMsgSearchOp::IsntEmpty)
+ {
+ *pResult = true;
+ return rv;
+ }
+
+ // Does this valid tag key match our search term?
+ matches = keywordArray[i].Equals(m_value.string);
+
+ // Is or Isn't partly determined on a single unmatched token
+ if (!matches)
+ {
+ if (m_operator == nsMsgSearchOp::Is)
+ {
+ *pResult = false;
+ return rv;
+ }
+ if (m_operator == nsMsgSearchOp::Isnt)
+ {
+ *pResult = true;
+ return rv;
+ }
+ }
+ }
+ }
+
+ if (m_operator == nsMsgSearchOp::Is)
+ {
+ *pResult = matches;
+ return NS_OK;
+ }
+
+ if (m_operator == nsMsgSearchOp::Isnt)
+ {
+ *pResult = !matches;
+ return NS_OK;
+ }
+
+ if (m_operator == nsMsgSearchOp::IsEmpty)
+ {
+ *pResult = true;
+ return NS_OK;
+ }
+
+ if (m_operator == nsMsgSearchOp::IsntEmpty)
+ {
+ *pResult = false;
+ return NS_OK;
+ }
+
+ // no valid match operator found
+ *pResult = false;
+ NS_ERROR("invalid compare op for msg status");
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+nsMsgSearchTerm::MatchPriority (nsMsgPriorityValue priorityToMatch,
+ bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ nsresult rv = NS_OK;
+ bool result = false;
+
+ // Use this ugly little hack to get around the fact that enums don't have
+ // integer compare operators
+ int p1 = (priorityToMatch == nsMsgPriority::none) ? (int) nsMsgPriority::normal : (int) priorityToMatch;
+ int p2 = (int) m_value.u.priority;
+
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::IsHigherThan:
+ if (p1 > p2)
+ result = true;
+ break;
+ case nsMsgSearchOp::IsLowerThan:
+ if (p1 < p2)
+ result = true;
+ break;
+ case nsMsgSearchOp::Is:
+ if (p1 == p2)
+ result = true;
+ break;
+ case nsMsgSearchOp::Isnt:
+ if (p1 != p2)
+ result = true;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for priority");
+ }
+ *pResult = result;
+ return rv;
+}
+
+// match a custom search term
+NS_IMETHODIMP nsMsgSearchTerm::MatchCustom(nsIMsgDBHdr* aHdr, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgSearchCustomTerm> customTerm;
+ rv = filterService->GetCustomTerm(m_customId, getter_AddRefs(customTerm));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (customTerm)
+ return customTerm->Match(aHdr, nsDependentCString(m_value.string),
+ m_operator, pResult);
+ *pResult = false; // default to no match if term is missing
+ return NS_ERROR_FAILURE; // missing custom term
+}
+
+// set the id of a custom search term
+NS_IMETHODIMP nsMsgSearchTerm::SetCustomId(const nsACString &aId)
+{
+ m_customId = aId;
+ return NS_OK;
+}
+
+// get the id of a custom search term
+NS_IMETHODIMP nsMsgSearchTerm::GetCustomId(nsACString &aResult)
+{
+ aResult = m_customId;
+ return NS_OK;
+}
+
+
+NS_IMPL_GETSET(nsMsgSearchTerm, Attrib, nsMsgSearchAttribValue, m_attribute)
+NS_IMPL_GETSET(nsMsgSearchTerm, Op, nsMsgSearchOpValue, m_operator)
+NS_IMPL_GETSET(nsMsgSearchTerm, MatchAll, bool, m_matchAll)
+
+NS_IMETHODIMP
+nsMsgSearchTerm::GetValue(nsIMsgSearchValue **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = new nsMsgSearchValueImpl(&m_value);
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchTerm::SetValue(nsIMsgSearchValue* aValue)
+{
+ nsMsgResultElement::AssignValues (aValue, &m_value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchTerm::GetBooleanAnd(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = (m_booleanOp == nsMsgSearchBooleanOp::BooleanAND);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchTerm::SetBooleanAnd(bool aValue)
+{
+ if (aValue)
+ m_booleanOp = nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanAND);
+ else
+ m_booleanOp = nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanOR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchTerm::GetArbitraryHeader(nsACString &aResult)
+{
+ aResult = m_arbitraryHeader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchTerm::SetArbitraryHeader(const nsACString &aValue)
+{
+ m_arbitraryHeader = aValue;
+ ToLowerCaseExceptSpecials(m_arbitraryHeader);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchTerm::GetHdrProperty(nsACString &aResult)
+{
+ aResult = m_hdrProperty;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchTerm::SetHdrProperty(const nsACString &aValue)
+{
+ m_hdrProperty = aValue;
+ ToLowerCaseExceptSpecials(m_hdrProperty);
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsMsgSearchTerm, BeginsGrouping, bool, mBeginsGrouping)
+NS_IMPL_GETSET(nsMsgSearchTerm, EndsGrouping, bool, mEndsGrouping)
+
+//
+// Certain possible standard values of a message database row also sometimes
+// appear as header values. To prevent a naming collision, we use all
+// lower case for the standard headers, and first capital when those
+// same strings are requested as arbitrary headers. This routine is used
+// when setting arbitrary headers.
+//
+void nsMsgSearchTerm::ToLowerCaseExceptSpecials(nsACString &aValue)
+{
+ if (aValue.LowerCaseEqualsLiteral("sender"))
+ aValue.Assign(NS_LITERAL_CSTRING("Sender"));
+ else if (aValue.LowerCaseEqualsLiteral("date"))
+ aValue.Assign(NS_LITERAL_CSTRING("Date"));
+ else if (aValue.LowerCaseEqualsLiteral("status"))
+ aValue.Assign(NS_LITERAL_CSTRING("Status"));
+ else
+ ToLowerCase(aValue);
+}
+
+
+//-----------------------------------------------------------------------------
+// nsMsgSearchScopeTerm implementation
+//-----------------------------------------------------------------------------
+nsMsgSearchScopeTerm::nsMsgSearchScopeTerm (nsIMsgSearchSession *session,
+ nsMsgSearchScopeValue attribute,
+ nsIMsgFolder *folder)
+{
+ m_attribute = attribute;
+ m_folder = folder;
+ m_searchServer = true;
+ m_searchSession = do_GetWeakReference(session);
+}
+
+nsMsgSearchScopeTerm::nsMsgSearchScopeTerm ()
+{
+ m_searchServer = true;
+}
+
+nsMsgSearchScopeTerm::~nsMsgSearchScopeTerm ()
+{
+ if (m_inputStream)
+ m_inputStream->Close();
+ m_inputStream = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgSearchScopeTerm, nsIMsgSearchScopeTerm)
+
+NS_IMETHODIMP
+nsMsgSearchScopeTerm::GetFolder(nsIMsgFolder **aResult)
+{
+ NS_IF_ADDREF(*aResult = m_folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchScopeTerm::GetSearchSession(nsIMsgSearchSession** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsCOMPtr<nsIMsgSearchSession> searchSession = do_QueryReferent (m_searchSession);
+ NS_IF_ADDREF(*aResult = searchSession);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchScopeTerm::GetInputStream(nsIMsgDBHdr *aMsgHdr,
+ nsIInputStream **aInputStream)
+{
+ NS_ENSURE_ARG_POINTER(aInputStream);
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_TRUE(m_folder, NS_ERROR_NULL_POINTER);
+ bool reusable;
+ nsresult rv = m_folder->GetMsgInputStream(aMsgHdr, &reusable,
+ getter_AddRefs(m_inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_IF_ADDREF(*aInputStream = m_inputStream);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgSearchScopeTerm::CloseInputStream()
+{
+ if (m_inputStream)
+{
+ m_inputStream->Close();
+ m_inputStream = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgSearchScopeTerm::TimeSlice (bool *aDone)
+{
+ return m_adapter->Search(aDone);
+}
+
+nsresult nsMsgSearchScopeTerm::InitializeAdapter (nsISupportsArray *termList)
+{
+ if (m_adapter)
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+
+ switch (m_attribute)
+ {
+ case nsMsgSearchScope::onlineMail:
+ m_adapter = new nsMsgSearchOnlineMail (this, termList);
+ break;
+ case nsMsgSearchScope::offlineMail:
+ case nsMsgSearchScope::onlineManual:
+ m_adapter = new nsMsgSearchOfflineMail (this, termList);
+ break;
+ case nsMsgSearchScope::newsEx:
+ NS_ASSERTION(false, "not supporting newsEx yet");
+ break;
+ case nsMsgSearchScope::news:
+ m_adapter = new nsMsgSearchNews (this, termList);
+ break;
+ case nsMsgSearchScope::allSearchableGroups:
+ NS_ASSERTION(false, "not supporting allSearchableGroups yet");
+ break;
+ case nsMsgSearchScope::LDAP:
+ NS_ASSERTION(false, "not supporting LDAP yet");
+ break;
+ case nsMsgSearchScope::localNews:
+ case nsMsgSearchScope::localNewsJunk:
+ case nsMsgSearchScope::localNewsBody:
+ case nsMsgSearchScope::localNewsJunkBody:
+ m_adapter = new nsMsgSearchOfflineNews (this, termList);
+ break;
+ default:
+ NS_ASSERTION(false, "invalid scope");
+ rv = NS_ERROR_FAILURE;
+ }
+
+ if (m_adapter)
+ rv = m_adapter->ValidateTerms ();
+
+ return rv;
+}
+
+
+char *nsMsgSearchScopeTerm::GetStatusBarName ()
+{
+ return nullptr;
+}
+
+
+//-----------------------------------------------------------------------------
+// nsMsgResultElement implementation
+//-----------------------------------------------------------------------------
+
+
+nsMsgResultElement::nsMsgResultElement(nsIMsgSearchAdapter *adapter)
+{
+ m_adapter = adapter;
+}
+
+
+nsMsgResultElement::~nsMsgResultElement ()
+{
+}
+
+
+nsresult nsMsgResultElement::AddValue (nsIMsgSearchValue *value)
+{
+ m_valueList.AppendElement(value);
+ return NS_OK;
+}
+
+nsresult nsMsgResultElement::AddValue (nsMsgSearchValue *value)
+{
+ nsMsgSearchValueImpl* valueImpl = new nsMsgSearchValueImpl(value);
+ delete value; // we keep the nsIMsgSearchValue, not
+ // the nsMsgSearchValue
+ return AddValue(valueImpl);
+}
+
+
+nsresult nsMsgResultElement::AssignValues (nsIMsgSearchValue *src, nsMsgSearchValue *dst)
+{
+ NS_ENSURE_ARG_POINTER(src);
+ NS_ENSURE_ARG_POINTER(dst);
+ // Yes, this could be an operator overload, but nsMsgSearchValue is totally public, so I'd
+ // have to define a derived class with nothing by operator=, and that seems like a bit much
+ nsresult rv = NS_OK;
+ src->GetAttrib(&dst->attribute);
+ switch (dst->attribute)
+ {
+ case nsMsgSearchAttrib::Priority:
+ rv = src->GetPriority(&dst->u.priority);
+ break;
+ case nsMsgSearchAttrib::Date:
+ rv = src->GetDate(&dst->u.date);
+ break;
+ case nsMsgSearchAttrib::HasAttachmentStatus:
+ case nsMsgSearchAttrib::MsgStatus:
+ case nsMsgSearchAttrib::FolderFlag:
+ case nsMsgSearchAttrib::Uint32HdrProperty:
+ rv = src->GetStatus(&dst->u.msgStatus);
+ break;
+ case nsMsgSearchAttrib::MessageKey:
+ rv = src->GetMsgKey(&dst->u.key);
+ break;
+ case nsMsgSearchAttrib::AgeInDays:
+ rv = src->GetAge(&dst->u.age);
+ break;
+ case nsMsgSearchAttrib::Label:
+ rv = src->GetLabel(&dst->u.label);
+ break;
+ case nsMsgSearchAttrib::JunkStatus:
+ rv = src->GetJunkStatus(&dst->u.junkStatus);
+ break;
+ case nsMsgSearchAttrib::JunkPercent:
+ rv = src->GetJunkPercent(&dst->u.junkPercent);
+ break;
+ case nsMsgSearchAttrib::Size:
+ rv = src->GetSize(&dst->u.size);
+ break;
+ default:
+ if (dst->attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes)
+ {
+ NS_ASSERTION(IS_STRING_ATTRIBUTE(dst->attribute), "assigning non-string result");
+ nsString unicodeString;
+ rv = src->GetStr(unicodeString);
+ dst->string = ToNewUTF8String(unicodeString);
+ dst->utf16String = unicodeString;
+ }
+ else
+ rv = NS_ERROR_INVALID_ARG;
+ }
+ return rv;
+}
+
+
+nsresult nsMsgResultElement::GetValue (nsMsgSearchAttribValue attrib,
+ nsMsgSearchValue **outValue) const
+{
+ nsresult rv = NS_OK;
+ *outValue = NULL;
+
+ for (uint32_t i = 0; i < m_valueList.Length() && NS_FAILED(rv); i++)
+ {
+ nsMsgSearchAttribValue valueAttribute;
+ m_valueList[i]->GetAttrib(&valueAttribute);
+ if (attrib == valueAttribute)
+ {
+ *outValue = new nsMsgSearchValue;
+ if (*outValue)
+ {
+ rv = AssignValues(m_valueList[i], *outValue);
+ // Now this is really strange! What is this assignment doing here?
+ rv = NS_OK;
+ }
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgResultElement::GetPrettyName (nsMsgSearchValue **value)
+{
+ return GetValue (nsMsgSearchAttrib::Location, value);
+}
+
+nsresult nsMsgResultElement::Open (void *window)
+{
+ return NS_ERROR_NULL_POINTER;
+}
+
+
diff --git a/mailnews/base/search/src/nsMsgSearchValue.cpp b/mailnews/base/search/src/nsMsgSearchValue.cpp
new file mode 100644
index 000000000..7685c4a73
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchValue.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MailNewsTypes.h"
+#include "nsMsgSearchValue.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgUtils.h"
+#include "nsStringGlue.h"
+
+nsMsgSearchValueImpl::nsMsgSearchValueImpl(nsMsgSearchValue *aInitialValue)
+{
+ mValue = *aInitialValue;
+ if (IS_STRING_ATTRIBUTE(aInitialValue->attribute) && aInitialValue->string)
+ {
+ mValue.string = NS_strdup(aInitialValue->string);
+ CopyUTF8toUTF16(mValue.string, mValue.utf16String);
+ }
+ else
+ mValue.string = 0;
+}
+
+nsMsgSearchValueImpl::~nsMsgSearchValueImpl()
+{
+ if (IS_STRING_ATTRIBUTE(mValue.attribute))
+ NS_Free(mValue.string);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgSearchValueImpl, nsIMsgSearchValue)
+
+NS_IMPL_GETSET(nsMsgSearchValueImpl, Priority, nsMsgPriorityValue, mValue.u.priority)
+NS_IMPL_GETSET(nsMsgSearchValueImpl, Status, uint32_t, mValue.u.msgStatus)
+NS_IMPL_GETSET(nsMsgSearchValueImpl, Size, uint32_t, mValue.u.size)
+NS_IMPL_GETSET(nsMsgSearchValueImpl, MsgKey, nsMsgKey, mValue.u.key)
+NS_IMPL_GETSET(nsMsgSearchValueImpl, Age, int32_t, mValue.u.age)
+NS_IMPL_GETSET(nsMsgSearchValueImpl, Date, PRTime, mValue.u.date)
+NS_IMPL_GETSET(nsMsgSearchValueImpl, Attrib, nsMsgSearchAttribValue, mValue.attribute)
+NS_IMPL_GETSET(nsMsgSearchValueImpl, Label, nsMsgLabelValue, mValue.u.label)
+NS_IMPL_GETSET(nsMsgSearchValueImpl, JunkStatus, uint32_t, mValue.u.junkStatus)
+NS_IMPL_GETSET(nsMsgSearchValueImpl, JunkPercent, uint32_t, mValue.u.junkPercent)
+
+NS_IMETHODIMP
+nsMsgSearchValueImpl::GetFolder(nsIMsgFolder* *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_TRUE(mValue.attribute == nsMsgSearchAttrib::FolderInfo, NS_ERROR_ILLEGAL_VALUE);
+ *aResult = mValue.u.folder;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchValueImpl::SetFolder(nsIMsgFolder* aValue)
+{
+ NS_ENSURE_TRUE(mValue.attribute == nsMsgSearchAttrib::FolderInfo, NS_ERROR_ILLEGAL_VALUE);
+ mValue.u.folder = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchValueImpl::GetStr(nsAString &aResult)
+{
+ NS_ENSURE_TRUE(IS_STRING_ATTRIBUTE(mValue.attribute), NS_ERROR_ILLEGAL_VALUE);
+ aResult = mValue.utf16String;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchValueImpl::SetStr(const nsAString &aValue)
+{
+ NS_ENSURE_TRUE(IS_STRING_ATTRIBUTE(mValue.attribute), NS_ERROR_ILLEGAL_VALUE);
+ if (mValue.string)
+ NS_Free(mValue.string);
+ mValue.string = ToNewUTF8String(aValue);
+ mValue.utf16String = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchValueImpl::ToString(nsAString &aResult)
+{
+ aResult.AssignLiteral("[nsIMsgSearchValue: ");
+ if (IS_STRING_ATTRIBUTE(mValue.attribute)) {
+ aResult.Append(mValue.utf16String);
+ return NS_OK;
+ }
+
+
+ switch (mValue.attribute) {
+
+ case nsMsgSearchAttrib::Priority:
+ case nsMsgSearchAttrib::Date:
+ case nsMsgSearchAttrib::MsgStatus:
+ case nsMsgSearchAttrib::MessageKey:
+ case nsMsgSearchAttrib::Size:
+ case nsMsgSearchAttrib::AgeInDays:
+ case nsMsgSearchAttrib::FolderInfo:
+ case nsMsgSearchAttrib::Label:
+ case nsMsgSearchAttrib::JunkStatus:
+ case nsMsgSearchAttrib::JunkPercent:
+ {
+ nsAutoString tempInt;
+ tempInt.AppendInt(mValue.attribute);
+
+ aResult.AppendLiteral("type=");
+ aResult.Append(tempInt);
+ }
+ break;
+ default:
+ NS_ERROR("Unknown search value type");
+ }
+
+ aResult.AppendLiteral("]");
+
+ return NS_OK;
+}
diff --git a/mailnews/base/search/src/nsMsgSearchValue.h b/mailnews/base/search/src/nsMsgSearchValue.h
new file mode 100644
index 000000000..3d08d27fa
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchValue.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMsgSearchValue_h
+#define __nsMsgSearchValue_h
+
+#include "nsIMsgSearchValue.h"
+#include "nsMsgSearchCore.h"
+
+class nsMsgSearchValueImpl : public nsIMsgSearchValue {
+ public:
+ explicit nsMsgSearchValueImpl(nsMsgSearchValue *aInitialValue);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSEARCHVALUE
+
+ private:
+ virtual ~nsMsgSearchValueImpl();
+
+ nsMsgSearchValue mValue;
+
+};
+
+#endif
diff --git a/mailnews/base/search/src/nsMsgTraitService.js b/mailnews/base/search/src/nsMsgTraitService.js
new file mode 100644
index 000000000..eda20bbf8
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgTraitService.js
@@ -0,0 +1,239 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// local static variables
+
+var _lastIndex = 0; // the first index will be one
+var _traits = {};
+
+var traitsBranch = Services.prefs.getBranch("mailnews.traits.");
+
+function _registerTrait(aId, aIndex)
+{
+ var trait = {};
+ trait.enabled = false;
+ trait.name = "";
+ trait.antiId = "";
+ trait.index = aIndex;
+ _traits[aId] = trait;
+ return;
+}
+
+function nsMsgTraitService() {}
+
+nsMsgTraitService.prototype =
+{
+ // Component setup
+ classID: Components.ID("{A2E95F4F-DA72-4a41-9493-661AD353C00A}"),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Components.interfaces.nsIMsgTraitService]),
+
+ // nsIMsgTraitService implementation
+
+ get lastIndex()
+ {
+ return _lastIndex;
+ },
+
+ registerTrait: function(aId)
+ {
+ if (_traits[aId])
+ return 0; // meaning already registered
+ _registerTrait(aId, ++_lastIndex);
+ traitsBranch.setBoolPref("enabled." + _lastIndex, false);
+ traitsBranch.setCharPref("id." + _lastIndex, aId);
+ return _lastIndex;
+ },
+
+ unRegisterTrait: function(aId)
+ {
+ if (_traits[aId])
+ {
+ var index = _traits[aId].index;
+ _traits[aId] = null;
+ traitsBranch.clearUserPref("id." + index);
+ traitsBranch.clearUserPref("enabled." + index);
+ traitsBranch.clearUserPref("antiId." + index);
+ traitsBranch.clearUserPref("name." + index);
+ }
+ return;
+ },
+
+ isRegistered: function(aId)
+ {
+ return _traits[aId] ? true : false;
+ },
+
+ setName: function(aId, aName)
+ {
+ traitsBranch.setCharPref("name." + _traits[aId].index, aName);
+ _traits[aId].name = aName;
+ },
+
+ getName: function(aId)
+ {
+ return _traits[aId].name;
+ },
+
+ getIndex: function(aId)
+ {
+ return _traits[aId].index;
+ },
+
+ getId: function(aIndex)
+ {
+ for (let id in _traits)
+ if (_traits[id].index == aIndex)
+ return id;
+ return null;
+ },
+
+ setEnabled: function(aId, aEnabled)
+ {
+ traitsBranch.setBoolPref("enabled." + _traits[aId].index, aEnabled);
+ _traits[aId].enabled = aEnabled;
+ },
+
+ getEnabled: function(aId)
+ {
+ return _traits[aId].enabled;
+ },
+
+ setAntiId: function(aId, aAntiId)
+ {
+ traitsBranch.setCharPref("antiId." + _traits[aId].index, aAntiId);
+ _traits[aId].antiId = aAntiId;
+ },
+
+ getAntiId: function(aId)
+ {
+ return _traits[aId].antiId;
+ },
+
+ getEnabledIndices: function(aCount, aProIndices, aAntiIndices)
+ {
+ let proIndices = [];
+ let antiIndices = [];
+ for (let id in _traits)
+ if (_traits[id].enabled)
+ {
+ proIndices.push(_traits[id].index);
+ antiIndices.push(_traits[_traits[id].antiId].index);
+ }
+ aCount.value = proIndices.length;
+ aProIndices.value = proIndices;
+ aAntiIndices.value = antiIndices;
+ return;
+ },
+
+ addAlias: function addAlias(aTraitIndex, aTraitAliasIndex)
+ {
+ let aliasesString = "";
+ try {
+ aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex);
+ }
+ catch (e) {}
+ let aliases;
+ if (aliasesString.length)
+ aliases = aliasesString.split(",");
+ else
+ aliases = [];
+ if (aliases.indexOf(aTraitAliasIndex.toString()) == -1)
+ {
+ aliases.push(aTraitAliasIndex);
+ traitsBranch.setCharPref("aliases." + aTraitIndex, aliases.join());
+ }
+ },
+
+ removeAlias: function removeAlias(aTraitIndex, aTraitAliasIndex)
+ {
+ let aliasesString = "";
+ try {
+ aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex);
+ }
+ catch (e) {
+ return;
+ }
+ let aliases;
+ if (aliasesString.length)
+ aliases = aliasesString.split(",");
+ else
+ aliases = [];
+ let location;
+ if ((location = aliases.indexOf(aTraitAliasIndex.toString())) != -1)
+ {
+ aliases.splice(location, 1);
+ traitsBranch.setCharPref("aliases." + aTraitIndex, aliases.join());
+ }
+ },
+
+ getAliases: function getAliases(aTraitIndex, aLength)
+ {
+ let aliasesString = "";
+ try {
+ aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex);
+ }
+ catch (e) {}
+
+ let aliases;
+ if (aliasesString.length)
+ aliases = aliasesString.split(",");
+ else
+ aliases = [];
+ aLength.value = aliases.length;
+ return aliases;
+ },
+};
+
+// initialization
+
+_init();
+
+function _init()
+{
+ // get existing traits
+ var idBranch = Services.prefs.getBranch("mailnews.traits.id.");
+ var nameBranch = Services.prefs.getBranch("mailnews.traits.name.");
+ var enabledBranch = Services.prefs.getBranch("mailnews.traits.enabled.");
+ var antiIdBranch = Services.prefs.getBranch("mailnews.traits.antiId.");
+ _lastIndex = Services.prefs.getBranch("mailnews.traits.").getIntPref("lastIndex");
+ var ids = idBranch.getChildList("");
+ for (var i = 0; i < ids.length; i++)
+ {
+ var id = idBranch.getCharPref(ids[i]);
+ var index = parseInt(ids[i]);
+ _registerTrait(id, index, false);
+
+ // Read in values, ignore errors since that usually means the
+ // value does not exist
+ try {
+ _traits[id].name = nameBranch.getCharPref(ids[i]);
+ }
+ catch (e) {}
+
+ try {
+ _traits[id].enabled = enabledBranch.getBoolPref(ids[i]);
+ }
+ catch (e) {}
+
+ try {
+ _traits[id].antiId = antiIdBranch.getCharPref(ids[i]);
+ }
+ catch (e) {}
+
+ if (_lastIndex < index)
+ _lastIndex = index;
+ }
+
+ //for (traitId in _traits)
+ // dump("\nindex of " + traitId + " is " + _traits[traitId].index);
+ //dump("\n");
+}
+
+var components = [nsMsgTraitService];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/base/search/src/nsMsgTraitService.manifest b/mailnews/base/search/src/nsMsgTraitService.manifest
new file mode 100644
index 000000000..0a13ee781
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgTraitService.manifest
@@ -0,0 +1,2 @@
+component {A2E95F4F-DA72-4a41-9493-661AD353C00A} nsMsgTraitService.js
+contract @mozilla.org/msg-trait-service;1 {A2E95F4F-DA72-4a41-9493-661AD353C00A}
diff --git a/mailnews/base/src/MailNewsDLF.cpp b/mailnews/base/src/MailNewsDLF.cpp
new file mode 100644
index 000000000..839d86006
--- /dev/null
+++ b/mailnews/base/src/MailNewsDLF.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "MailNewsDLF.h"
+#include "nsIChannel.h"
+#include "plstr.h"
+#include "nsStringGlue.h"
+#include "nsICategoryManager.h"
+#include "nsIServiceManager.h"
+#include "nsIStreamConverterService.h"
+#include "nsIStreamListener.h"
+#include "nsNetCID.h"
+#include "nsMsgUtils.h"
+
+namespace mozilla {
+namespace mailnews {
+NS_IMPL_ISUPPORTS(MailNewsDLF, nsIDocumentLoaderFactory)
+
+MailNewsDLF::MailNewsDLF()
+{
+}
+
+MailNewsDLF::~MailNewsDLF()
+{
+}
+
+NS_IMETHODIMP
+MailNewsDLF::CreateInstance(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ const nsACString& aContentType,
+ nsIDocShell* aContainer,
+ nsISupports* aExtraInfo,
+ nsIStreamListener** aDocListener,
+ nsIContentViewer** aDocViewer)
+{
+ nsresult rv;
+
+ bool viewSource = (PL_strstr(PromiseFlatCString(aContentType).get(),
+ "view-source") != 0);
+
+ aChannel->SetContentType(NS_LITERAL_CSTRING(TEXT_HTML));
+
+ // Get the HTML category
+ nsCOMPtr<nsICategoryManager> catMan(
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contractID;
+ rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", TEXT_HTML,
+ getter_Copies(contractID));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocumentLoaderFactory> factory(do_GetService(contractID.get(),
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamListener> listener;
+
+ if (viewSource) {
+ rv = factory->CreateInstance("view-source", aChannel, aLoadGroup,
+ NS_LITERAL_CSTRING(TEXT_HTML "; x-view-type=view-source"),
+ aContainer, aExtraInfo, getter_AddRefs(listener),
+ aDocViewer);
+ } else {
+ rv = factory->CreateInstance("view", aChannel, aLoadGroup, NS_LITERAL_CSTRING(TEXT_HTML),
+ aContainer, aExtraInfo, getter_AddRefs(listener),
+ aDocViewer);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamConverterService> scs(
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return scs->AsyncConvertData(MESSAGE_RFC822, TEXT_HTML, listener, aChannel,
+ aDocListener);
+}
+
+NS_IMETHODIMP
+MailNewsDLF::CreateInstanceForDocument(nsISupports* aContainer,
+ nsIDocument* aDocument,
+ const char* aCommand,
+ nsIContentViewer** aDocViewer)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MailNewsDLF::CreateBlankDocument(nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal,
+ nsIDocument** aDocument)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+}
+}
diff --git a/mailnews/base/src/MailNewsDLF.h b/mailnews/base/src/MailNewsDLF.h
new file mode 100644
index 000000000..b0bd77b30
--- /dev/null
+++ b/mailnews/base/src/MailNewsDLF.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MailNewsDLF_h__
+#define MailNewsDLF_h__
+
+#include "nsIDocumentLoaderFactory.h"
+#include "nsMimeTypes.h"
+#include "nsMsgBaseCID.h"
+
+namespace mozilla {
+namespace mailnews {
+
+/*
+ * This factory is a thin wrapper around the text/html loader factory. All it
+ * does is convert message/rfc822 to text/html and delegate the rest of the
+ * work to the text/html factory.
+ */
+class MailNewsDLF : public nsIDocumentLoaderFactory
+{
+public:
+ MailNewsDLF();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOCUMENTLOADERFACTORY
+
+private:
+ virtual ~MailNewsDLF();
+};
+}
+}
+
+#define MAILNEWSDLF_CATEGORIES \
+ { "Gecko-Content-Viewers", MESSAGE_RFC822, NS_MAILNEWSDLF_CONTRACTID }, \
+
+#endif
diff --git a/mailnews/base/src/MailnewsLoadContextInfo.cpp b/mailnews/base/src/MailnewsLoadContextInfo.cpp
new file mode 100644
index 000000000..89aa56672
--- /dev/null
+++ b/mailnews/base/src/MailnewsLoadContextInfo.cpp
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This was copied from netwerk/base/LoadContextInfo.cpp
+
+#include "MailnewsLoadContextInfo.h"
+
+#include "mozilla/dom/ToJSValue.h"
+#include "nsIChannel.h"
+#include "nsILoadContext.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+
+// MailnewsLoadContextInfo
+
+NS_IMPL_ISUPPORTS(MailnewsLoadContextInfo, nsILoadContextInfo)
+
+MailnewsLoadContextInfo::MailnewsLoadContextInfo(bool aIsPrivate, bool aIsAnonymous, mozilla::NeckoOriginAttributes aOriginAttributes)
+ : mIsPrivate(aIsPrivate)
+ , mIsAnonymous(aIsAnonymous)
+ , mOriginAttributes(aOriginAttributes)
+{
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(mIsPrivate);
+}
+
+MailnewsLoadContextInfo::~MailnewsLoadContextInfo()
+{
+}
+
+NS_IMETHODIMP MailnewsLoadContextInfo::GetIsPrivate(bool *aIsPrivate)
+{
+ *aIsPrivate = mIsPrivate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP MailnewsLoadContextInfo::GetIsAnonymous(bool *aIsAnonymous)
+{
+ *aIsAnonymous = mIsAnonymous;
+ return NS_OK;
+}
+
+mozilla::NeckoOriginAttributes const* MailnewsLoadContextInfo::OriginAttributesPtr()
+{
+ return &mOriginAttributes;
+}
+
+NS_IMETHODIMP MailnewsLoadContextInfo::GetOriginAttributes(JSContext *aCx,
+ JS::MutableHandle<JS::Value> aVal)
+{
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
diff --git a/mailnews/base/src/MailnewsLoadContextInfo.h b/mailnews/base/src/MailnewsLoadContextInfo.h
new file mode 100644
index 000000000..6a127bf4a
--- /dev/null
+++ b/mailnews/base/src/MailnewsLoadContextInfo.h
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This was copied from netwerk/base/LoadContextInfo.h
+
+#ifndef MailnewsLoadContextInfo_h__
+#define MailnewsLoadContextInfo_h__
+
+#include "nsILoadContextInfo.h"
+
+class nsIChannel;
+class nsILoadContext;
+
+class MailnewsLoadContextInfo : public nsILoadContextInfo
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSILOADCONTEXTINFO
+
+ MailnewsLoadContextInfo(bool aIsPrivate, bool aIsAnonymous, mozilla::NeckoOriginAttributes aOriginAttributes);
+
+private:
+ virtual ~MailnewsLoadContextInfo();
+
+protected:
+ bool mIsPrivate : 1;
+ bool mIsAnonymous : 1;
+ mozilla::NeckoOriginAttributes mOriginAttributes;
+};
+
+#endif
diff --git a/mailnews/base/src/folderLookupService.js b/mailnews/base/src/folderLookupService.js
new file mode 100644
index 000000000..9c64b5ef0
--- /dev/null
+++ b/mailnews/base/src/folderLookupService.js
@@ -0,0 +1,99 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This module implements the folder lookup service. Presently, this uses RDF as
+ * the backing store, but the intent is that this will eventually become the
+ * authoritative map.
+ */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function isValidFolder(folder) {
+ // RDF is liable to return folders that don't exist, and we may be working
+ // with a deleted folder but we're still holding on to the reference. For
+ // valid folders, one of two scenarios is true: either the folder has a parent
+ // (the deletion code clears the parent to indicate its nonvalidity), or the
+ // folder is a root folder of some server. Getting the root folder may throw
+ // an exception if we attempted to create a server that doesn't exist, so we
+ // need to guard for that error.
+ try {
+ return folder.parent != null || folder.rootFolder == folder;
+ } catch (e) {
+ return false;
+ }
+}
+
+// This insures that the service is only created once
+var gCreated = false;
+
+function folderLookupService() {
+ if (gCreated)
+ throw Cr.NS_ERROR_ALREADY_INITIALIZED;
+ this._map = new Map();
+ gCreated = true;
+}
+folderLookupService.prototype = {
+ // XPCOM registration stuff
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFolderLookupService]),
+ classID: Components.ID("{a30be08c-afc8-4fed-9af7-79778a23db23}"),
+
+ // nsIFolderLookupService impl
+ getFolderForURL: function (aUrl) {
+ let folder = null;
+ // First, see if the folder is in our cache.
+ if (this._map.has(aUrl)) {
+ let valid = false;
+ try {
+ folder = this._map.get(aUrl).QueryReferent(Ci.nsIMsgFolder);
+ valid = isValidFolder(folder);
+ } catch (e) {
+ // The object was deleted, so it's not valid
+ }
+
+ if (valid)
+ return folder;
+
+ // Don't keep around invalid folders.
+ this._map.delete(aUrl);
+ folder = null;
+ }
+
+ // If we get here, then the folder was not in our map. It could be that the
+ // folder was created by somebody else, so try to find that folder.
+ // For now, we use the RDF service, since it results in minimal changes. But
+ // RDF has a tendency to create objects without checking to see if they
+ // really exist---use the parent property to see if the folder is a real
+ // folder.
+ if (folder == null) {
+ let rdf = Cc["@mozilla.org/rdf/rdf-service;1"]
+ .getService(Ci.nsIRDFService);
+ try {
+ folder = rdf.GetResource(aUrl)
+ .QueryInterface(Ci.nsIMsgFolder);
+ } catch (e) {
+ // If the QI fails, then we somehow picked up an RDF resource that isn't
+ // a folder. Return null in this case.
+ return null;
+ }
+ }
+ if (!isValidFolder(folder))
+ return null;
+
+ // Add the new folder to our map. Store a weak reference instead, so that
+ // the folder can be closed when necessary.
+ let weakRef = folder.QueryInterface(Ci.nsISupportsWeakReference)
+ .GetWeakReference();
+ this._map.set(aUrl, weakRef);
+ return folder;
+ },
+};
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([folderLookupService]);
diff --git a/mailnews/base/src/moz.build b/mailnews/base/src/moz.build
new file mode 100644
index 000000000..55dbab154
--- /dev/null
+++ b/mailnews/base/src/moz.build
@@ -0,0 +1,85 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'nsMailDirServiceDefs.h',
+ 'nsMsgRDFDataSource.h',
+ 'nsMsgRDFUtils.h',
+]
+
+SOURCES += [
+ 'MailNewsDLF.cpp',
+ 'MailnewsLoadContextInfo.cpp',
+ 'nsCidProtocolHandler.cpp',
+ 'nsCopyMessageStreamListener.cpp',
+ 'nsMailDirProvider.cpp',
+ 'nsMessenger.cpp',
+ 'nsMessengerBootstrap.cpp',
+ 'nsMessengerContentHandler.cpp',
+ 'nsMsgAccount.cpp',
+ 'nsMsgAccountManager.cpp',
+ 'nsMsgAccountManagerDS.cpp',
+ 'nsMsgBiffManager.cpp',
+ 'nsMsgContentPolicy.cpp',
+ 'nsMsgCopyService.cpp',
+ 'nsMsgDBView.cpp',
+ 'nsMsgFolderCache.cpp',
+ 'nsMsgFolderCacheElement.cpp',
+ 'nsMsgFolderCompactor.cpp',
+ 'nsMsgFolderDataSource.cpp',
+ 'nsMsgFolderNotificationService.cpp',
+ 'nsMsgGroupThread.cpp',
+ 'nsMsgGroupView.cpp',
+ 'nsMsgMailSession.cpp',
+ 'nsMsgOfflineManager.cpp',
+ 'nsMsgProgress.cpp',
+ 'nsMsgPurgeService.cpp',
+ 'nsMsgQuickSearchDBView.cpp',
+ 'nsMsgRDFDataSource.cpp',
+ 'nsMsgRDFUtils.cpp',
+ 'nsMsgSearchDBView.cpp',
+ 'nsMsgServiceProvider.cpp',
+ 'nsMsgSpecialViews.cpp',
+ 'nsMsgStatusFeedback.cpp',
+ 'nsMsgTagService.cpp',
+ 'nsMsgThreadedDBView.cpp',
+ 'nsMsgWindow.cpp',
+ 'nsMsgXFViewThread.cpp',
+ 'nsMsgXFVirtualFolderDBView.cpp',
+ 'nsSpamSettings.cpp',
+ 'nsStatusBarBiffManager.cpp',
+ 'nsSubscribableServer.cpp',
+ 'nsSubscribeDataSource.cpp',
+]
+
+if CONFIG['NS_PRINTING']:
+ SOURCES += ['nsMsgPrintEngine.cpp']
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += ['nsMessengerWinIntegration.cpp']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'qt'):
+ SOURCES += ['nsMessengerUnixIntegration.cpp']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += ['nsMessengerOSXIntegration.mm']
+
+EXTRA_COMPONENTS += [
+ 'folderLookupService.js',
+ 'msgAsyncPrompter.js',
+ 'newMailNotificationService.js',
+ 'nsMailNewsCommandLineHandler.js',
+]
+
+if CONFIG['MOZ_MAILNEWS_OAUTH2']:
+ EXTRA_COMPONENTS += ['msgOAuth2Module.js']
+
+EXTRA_PP_COMPONENTS += ['msgBase.manifest']
+
+EXTRA_JS_MODULES += [
+ 'virtualFolderWrapper.js',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/base/src/msgAsyncPrompter.js b/mailnews/base/src/msgAsyncPrompter.js
new file mode 100644
index 000000000..58b5288e9
--- /dev/null
+++ b/mailnews/base/src/msgAsyncPrompter.js
@@ -0,0 +1,126 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource:///modules/gloda/log4moz.js");
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+function runnablePrompter(asyncPrompter, hashKey) {
+ this._asyncPrompter = asyncPrompter;
+ this._hashKey = hashKey;
+}
+
+runnablePrompter.prototype = {
+ _asyncPrompter: null,
+ _hashKey: null,
+
+ run: Task.async(function *() {
+ yield Services.logins.initializationPromise;
+ this._asyncPrompter._log.debug("Running prompt for " + this._hashKey);
+ let prompter = this._asyncPrompter._pendingPrompts[this._hashKey];
+ let ok = false;
+ try {
+ ok = prompter.first.onPromptStart();
+ }
+ catch (ex) {
+ Components.utils.reportError("runnablePrompter:run: " + ex + "\n");
+ }
+
+ delete this._asyncPrompter._pendingPrompts[this._hashKey];
+
+ for (var consumer of prompter.consumers) {
+ try {
+ if (ok)
+ consumer.onPromptAuthAvailable();
+ else
+ consumer.onPromptCanceled();
+ }
+ catch (ex) {
+ // Log the error for extension devs and others to pick up.
+ Components.utils.reportError("runnablePrompter:run: consumer.onPrompt* reported an exception: " + ex + "\n");
+ }
+ }
+ this._asyncPrompter._asyncPromptInProgress--;
+
+ this._asyncPrompter._log.debug("Finished running prompter for " + this._hashKey);
+ this._asyncPrompter._doAsyncAuthPrompt();
+ })
+};
+
+function msgAsyncPrompter() {
+ this._pendingPrompts = {};
+ // By default, only log warnings to the error console and errors to dump().
+ // You can use the preferences:
+ // msgAsyncPrompter.logging.console
+ // msgAsyncPrompter.logging.dump
+ // To change this up. Values should be one of:
+ // Fatal/Error/Warn/Info/Config/Debug/Trace/All
+ this._log = Log4Moz.getConfiguredLogger("msgAsyncPrompter",
+ Log4Moz.Level.Warn,
+ Log4Moz.Level.Warn);
+}
+
+msgAsyncPrompter.prototype = {
+ classID: Components.ID("{49b04761-23dd-45d7-903d-619418a4d319}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgAsyncPrompter]),
+
+ _pendingPrompts: null,
+ _asyncPromptInProgress: 0,
+ _log: null,
+
+ queueAsyncAuthPrompt: function(aKey, aJumpQueue, aCaller) {
+ if (aKey in this._pendingPrompts) {
+ this._log.debug("Prompt bound to an existing one in the queue, key: " + aKey);
+ this._pendingPrompts[aKey].consumers.push(aCaller);
+ return;
+ }
+
+ this._log.debug("Adding new prompt to the queue, key: " + aKey);
+ let asyncPrompt = {
+ first: aCaller,
+ consumers: []
+ };
+
+ this._pendingPrompts[aKey] = asyncPrompt;
+ if (aJumpQueue) {
+ this._asyncPromptInProgress++;
+
+ this._log.debug("Forcing runnablePrompter for " + aKey);
+
+ let runnable = new runnablePrompter(this, aKey);
+ Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ else
+ this._doAsyncAuthPrompt();
+ },
+
+ _doAsyncAuthPrompt: function() {
+ if (this._asyncPromptInProgress > 0) {
+ this._log.debug("_doAsyncAuthPrompt bypassed - prompt already in progress");
+ return;
+ }
+
+ // Find the first prompt key we have in the queue.
+ let hashKey = null;
+ for (hashKey in this._pendingPrompts)
+ break;
+
+ if (!hashKey)
+ return;
+
+ this._asyncPromptInProgress++;
+
+ this._log.debug("Dispatching runnablePrompter for " + hashKey);
+
+ let runnable = new runnablePrompter(this, hashKey);
+ Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+};
+
+var components = [msgAsyncPrompter];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/base/src/msgBase.manifest b/mailnews/base/src/msgBase.manifest
new file mode 100644
index 000000000..1753939b8
--- /dev/null
+++ b/mailnews/base/src/msgBase.manifest
@@ -0,0 +1,14 @@
+component {49b04761-23dd-45d7-903d-619418a4d319} msgAsyncPrompter.js
+contract @mozilla.org/messenger/msgAsyncPrompter;1 {49b04761-23dd-45d7-903d-619418a4d319}
+component {2f86d554-f9d9-4e76-8eb7-243f047333ee} nsMailNewsCommandLineHandler.js
+contract @mozilla.org/commandlinehandler/general-startup;1?type=mail {2f86d554-f9d9-4e76-8eb7-243f047333ee}
+category command-line-handler m-mail @mozilla.org/commandlinehandler/general-startup;1?type=mail
+component {740880E6-E299-4165-B82F-DF1DCAB3AE22} newMailNotificationService.js
+contract @mozilla.org/newMailNotificationService;1 {740880E6-E299-4165-B82F-DF1DCAB3AE22}
+category profile-after-change NewMailNotificationService @mozilla.org/newMailNotificationService;1
+component {a30be08c-afc8-4fed-9af7-79778a23db23} folderLookupService.js
+contract @mozilla.org/mail/folder-lookup;1 {a30be08c-afc8-4fed-9af7-79778a23db23}
+#ifdef MOZ_MAILNEWS_OAUTH2
+component {b63d8e4c-bf60-439b-be0e-7c9f67291042} msgOAuth2Module.js
+contract @mozilla.org/mail/oauth2-module;1 {b63d8e4c-bf60-439b-be0e-7c9f67291042}
+#endif \ No newline at end of file
diff --git a/mailnews/base/src/msgOAuth2Module.js b/mailnews/base/src/msgOAuth2Module.js
new file mode 100644
index 000000000..407ab0519
--- /dev/null
+++ b/mailnews/base/src/msgOAuth2Module.js
@@ -0,0 +1,150 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/OAuth2.jsm");
+Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
+Components.utils.import("resource://gre/modules/Preferences.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function OAuth2Module() {
+ this._refreshToken = '';
+}
+OAuth2Module.prototype = {
+ // XPCOM registration stuff
+ QueryInterface: XPCOMUtils.generateQI([Ci.msgIOAuth2Module]),
+ classID: Components.ID("{b63d8e4c-bf60-439b-be0e-7c9f67291042}"),
+
+ _loadOAuthClientDetails(aIssuer) {
+ let details = OAuth2Providers.getIssuerDetails(aIssuer);
+ if (details)
+ [this._appKey, this._appSecret, this._authURI, this._tokenURI] = details;
+ else
+ throw Cr.NS_ERROR_INVALID_ARGUMENT;
+ },
+ initFromSmtp(aServer) {
+ return this._initPrefs("mail.smtpserver." + aServer.key + ".",
+ aServer.username, aServer.hostname);
+ },
+ initFromMail(aServer) {
+ return this._initPrefs("mail.server." + aServer.key + ".",
+ aServer.username, aServer.realHostName);
+ },
+ _initPrefs(root, aUsername, aHostname) {
+ // Load all of the parameters from preferences.
+ let issuer = Preferences.get(root + "oauth2.issuer", "");
+ let scope = Preferences.get(root + "oauth2.scope", "");
+
+ // These properties are absolutely essential to OAuth2 support. If we don't
+ // have them, we don't support OAuth2.
+ if (!issuer || !scope) {
+ // Since we currently only support gmail, init values if server matches.
+ let details = OAuth2Providers.getHostnameDetails(aHostname);
+ if (details)
+ {
+ [issuer, scope] = details;
+ Preferences.set(root + "oauth2.issuer", issuer);
+ Preferences.set(root + "oauth2.scope", scope);
+ }
+ else
+ return false;
+ }
+
+ // Find the app key we need for the OAuth2 string. Eventually, this should
+ // be using dynamic client registration, but there are no current
+ // implementations that we can test this with.
+ this._loadOAuthClientDetails(issuer);
+
+ // Username is needed to generate the XOAUTH2 string.
+ this._username = aUsername;
+ // LoginURL is needed to save the refresh token in the password manager.
+ this._loginUrl = "oauth://" + issuer;
+ // We use the scope to indicate the realm.
+ this._scope = scope;
+
+ // Define the OAuth property and store it.
+ this._oauth = new OAuth2(this._authURI, scope, this._appKey,
+ this._appSecret);
+ this._oauth.authURI = this._authURI;
+ this._oauth.tokenURI = this._tokenURI;
+
+ // Try hinting the username...
+ this._oauth.extraAuthParams = [
+ ["login_hint", aUsername]
+ ];
+
+ // Set the window title to something more useful than "Unnamed"
+ this._oauth.requestWindowTitle =
+ Services.strings.createBundle("chrome://messenger/locale/messenger.properties")
+ .formatStringFromName("oauth2WindowTitle",
+ [aUsername, aHostname], 2);
+
+ // This stores the refresh token in the login manager.
+ Object.defineProperty(this._oauth, "refreshToken", {
+ get: () => this.refreshToken,
+ set: (token) => this.refreshToken = token
+ });
+
+ return true;
+ },
+
+ get refreshToken() {
+ let loginMgr = Cc["@mozilla.org/login-manager;1"]
+ .getService(Ci.nsILoginManager);
+ let logins = loginMgr.findLogins({}, this._loginUrl, null, this._scope);
+ for (let login of logins) {
+ if (login.username == this._username)
+ return login.password;
+ }
+ return '';
+ },
+ set refreshToken(token) {
+ let loginMgr = Cc["@mozilla.org/login-manager;1"]
+ .getService(Ci.nsILoginManager);
+
+ // Check if we already have a login with this username, and modify the
+ // password on that, if we do.
+ let logins = loginMgr.findLogins({}, this._loginUrl, null, this._scope);
+ for (let login of logins) {
+ if (login.username == this._username) {
+ if (token) {
+ let propBag = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ propBag.setProperty("password", token);
+ loginMgr.modifyLogin(login, propBag);
+ }
+ else
+ loginMgr.removeLogin(login);
+ return token;
+ }
+ }
+
+ // Otherwise, we need a new login, so create one and fill it in.
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
+ .createInstance(Ci.nsILoginInfo);
+ login.init(this._loginUrl, null, this._scope, this._username, token,
+ '', '');
+ loginMgr.addLogin(login);
+ return token;
+ },
+
+ connect(aWithUI, aListener) {
+ this._oauth.connect(() => aListener.onSuccess(this._oauth.accessToken),
+ x => aListener.onFailure(x),
+ aWithUI, false);
+ },
+
+ buildXOAuth2String() {
+ return btoa("user=" + this._username + "\x01auth=Bearer " +
+ this._oauth.accessToken + "\x01\x01");
+ },
+};
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([OAuth2Module]);
diff --git a/mailnews/base/src/newMailNotificationService.js b/mailnews/base/src/newMailNotificationService.js
new file mode 100644
index 000000000..4fe72f554
--- /dev/null
+++ b/mailnews/base/src/newMailNotificationService.js
@@ -0,0 +1,377 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* platform-independent code to count new and unread messages and pass the information to
+ * platform-specific notification modules
+ *
+ * Logging for this module uses the TB version of log4moz. Default logging is at the Warn
+ * level. Other possibly interesting messages are at Error, Info and Debug. To configure, set the
+ * preferences "mail.notification.logging.console" (for the error console) or
+ * "mail.notification.logging.dump" (for stderr) to the string indicating the level you want.
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+Cu.import("resource:///modules/iteratorUtils.jsm");
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var NMNS = Ci.mozINewMailNotificationService;
+
+var countInboxesPref = "mail.notification.count.inbox_only";
+// Old name for pref
+var countNewMessagesPref = "mail.biff.use_new_count_in_mac_dock";
+// When we go cross-platform we should migrate to
+// const countNewMessagesPref = "mail.notification.count.new";
+
+// Helper function to retrieve a boolean preference with a default
+function getBoolPref(pref, defaultValue) {
+ try {
+ return Services.prefs.getBoolPref(pref);
+ }
+ catch(e) {
+ return defaultValue;
+ }
+}
+
+
+// constructor
+function NewMailNotificationService() {
+ this._mUnreadCount = 0;
+ this._mNewCount = 0;
+ this._listeners = [];
+ this.wrappedJSObject = this;
+
+ this._log = Log4Moz.getConfiguredLogger("mail.notification",
+ Log4Moz.Level.Warn,
+ Log4Moz.Level.Warn,
+ Log4Moz.Level.Warn);
+
+ // Listen for mail-startup-done to do the rest of our setup after folders are initialized
+ Services.obs.addObserver(this, "mail-startup-done", false);
+}
+
+NewMailNotificationService.prototype = {
+ classDescription: "Maintain counts of new and unread messages",
+ classID: Components.ID("{740880E6-E299-4165-B82F-DF1DCAB3AE22}"),
+ contractID: "@mozilla.org/newMailNotificationService;1",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIFolderListener, Ci.mozINewMailNotificationService]),
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(NewMailNotificationService),
+
+ _mUnreadCount: 0,
+ _mNewCount: 0,
+ _listeners: null,
+ _log: null,
+
+ get countNew() {
+ return getBoolPref(countNewMessagesPref, false);
+ },
+
+ observe: function NMNS_Observe(aSubject, aTopic, aData) {
+ // Set up to catch updates to unread count
+ this._log.info("NMNS_Observe: " + aTopic);
+
+ try {
+ if (aTopic == "mail-startup-done") {
+ try {
+ Services.obs.removeObserver(this, "mail-startup-done");
+ }
+ catch (e) {
+ this._log.error("NMNS_Observe: unable to deregister mail-startup-done listener: " + e);
+ }
+ Services.obs.addObserver(this, "profile-before-change", false);
+ MailServices.mailSession.AddFolderListener(this, Ci.nsIFolderListener.intPropertyChanged |
+ Ci.nsIFolderListener.added |
+ Ci.nsIFolderListener.removed |
+ Ci.nsIFolderListener.propertyFlagChanged);
+ this._initUnreadCount();
+ }
+ else if (aTopic == "profile-before-change") {
+ try {
+ MailServices.mailSession.RemoveFolderListener(this);
+ Services.obs.removeObserver(this, "profile-before-change");
+ }
+ catch (e) {
+ this._log.error("NMNS_Observe: unable to deregister listeners at shutdown: " + e);
+ }
+ }
+ } catch (error) {
+ this._log.error("NMNS_Observe failed: " + error);
+ }
+ },
+
+ _initUnreadCount: function NMNS_initUnreadCount() {
+ let total = 0;
+ let allServers = MailServices.accounts.allServers;
+ for (let i = 0; i < allServers.length; i++) {
+ let currentServer = allServers.queryElementAt(i, Ci.nsIMsgIncomingServer);
+ this._log.debug("NMNS_initUnread: server " + currentServer.prettyName + " type " + currentServer.type);
+ // Don't bother counting RSS or NNTP servers
+ let type = currentServer.type;
+ if (type == "rss" || type == "nntp")
+ continue;
+
+ let rootFolder = currentServer.rootFolder;
+ if (rootFolder) {
+ total += this._countUnread(rootFolder);
+ }
+ }
+ this._mUnreadCount = total;
+ if (!this.countNew) {
+ this._log.info("NMNS_initUnread notifying listeners: " + total + " total unread messages");
+ this._notifyListeners(NMNS.count, "onCountChanged", total);
+ }
+ },
+
+ // Count all the unread messages below the given folder
+ _countUnread: function NMNS_countUnread(folder) {
+ this._log.trace("NMNS_countUnread: parent folder " + folder.URI);
+ let unreadCount = 0;
+
+ if (this.confirmShouldCount(folder)) {
+ let count = folder.getNumUnread(false);
+ this._log.debug("NMNS_countUnread: folder " + folder.URI + ", " + count + " unread");
+ if (count > 0)
+ unreadCount += count;
+ }
+
+ let allFolders = folder.descendants;
+ for (let folder in fixIterator(allFolders, Ci.nsIMsgFolder)) {
+ if (this.confirmShouldCount(folder)) {
+ let count = folder.getNumUnread(false);
+ this._log.debug("NMNS_countUnread: folder " + folder.URI + ", " + count + " unread");
+ if (count > 0)
+ unreadCount += count;
+ }
+ }
+ return unreadCount;
+ },
+
+ // Filter out special folders and then ask for observers to see if
+ // we should monitor unread messages in this folder
+ confirmShouldCount: function NMNS_confirmShouldCount(aFolder) {
+ let shouldCount = Cc['@mozilla.org/supports-PRBool;1'].createInstance(Ci.nsISupportsPRBool);
+ shouldCount.data = true;
+ this._log.trace("NMNS_confirmShouldCount: folder " + aFolder.URI + " flags " + aFolder.flags);
+ let srv = null;
+
+ // If it's not a mail folder we don't count it by default
+ if (!(aFolder.flags & Ci.nsMsgFolderFlags.Mail))
+ shouldCount.data = false;
+
+ // For whatever reason, RSS folders have the 'Mail' flag
+ else if ((srv = aFolder.server) && (srv.type == "rss"))
+ shouldCount.data = false;
+
+ // If it's a special folder *other than the inbox* we don't count it by default
+ else if ((aFolder.flags & Ci.nsMsgFolderFlags.SpecialUse)
+ && !(aFolder.flags & Ci.nsMsgFolderFlags.Inbox))
+ shouldCount.data = false;
+
+ else if (aFolder.flags & Ci.nsMsgFolderFlags.Virtual)
+ shouldCount.data = false;
+
+ // if we're only counting inboxes and it's not an inbox...
+ else
+ try {
+ // If we can't get this pref, just leave it as the default
+ let onlyCountInboxes = Services.prefs.getBoolPref(countInboxesPref);
+ if (onlyCountInboxes && !(aFolder.flags & Ci.nsMsgFolderFlags.Inbox))
+ shouldCount.data = false;
+ } catch (error) {}
+
+ this._log.trace("NMNS_confirmShouldCount: before observers " + shouldCount.data);
+ Services.obs.notifyObservers(shouldCount, "before-count-unread-for-folder", aFolder.URI);
+ this._log.trace("NMNS_confirmShouldCount: after observers " + shouldCount.data);
+
+ return shouldCount.data;
+ },
+
+ OnItemIntPropertyChanged: function NMNS_OnItemIntPropertyChanged(folder, property, oldValue, newValue) {
+ try {
+ if (property == "FolderSize")
+ return;
+ this._log.trace("NMNS_OnItemIntPropertyChanged: folder " + folder.URI + " " + property + " " + oldValue + " " + newValue);
+ if (property == "BiffState") {
+ this._biffStateChanged(folder, oldValue, newValue);
+ }
+ else if (property == "TotalUnreadMessages") {
+ this._updateUnreadCount(folder, oldValue, newValue);
+ }
+ else if (property == "NewMailReceived") {
+ this._newMailReceived(folder, oldValue, newValue);
+ }
+ } catch (error) {
+ this._log.error("NMNS_OnItemIntPropertyChanged: exception " + error);
+ }
+ },
+
+ _biffStateChanged: function NMNS_biffStateChanged(folder, oldValue, newValue) {
+ if (newValue == Ci.nsIMsgFolder.nsMsgBiffState_NewMail) {
+ if (folder.server && !folder.server.performingBiff) {
+ this._log.debug("NMNS_biffStateChanged: folder " + folder.URI + " notified, but server not performing biff");
+ return;
+ }
+
+ // Biff notifications come in for the top level of the server, we need to look for
+ // the folder that actually contains the new mail
+
+ let allFolders = folder.descendants;
+ let numFolders = allFolders.length;
+
+ this._log.trace("NMNS_biffStateChanged: folder " + folder.URI + " New mail, " + numFolders + " subfolders");
+ let newCount = 0;
+
+ if (this.confirmShouldCount(folder)) {
+ let folderNew = folder.getNumNewMessages(false);
+ this._log.debug("NMNS_biffStateChanged: folder " + folder.URI + " new messages: " + folderNew);
+ if (folderNew > 0)
+ newCount += folderNew;
+ }
+
+ for (let folder in fixIterator(allFolders, Ci.nsIMsgFolder)) {
+ if (this.confirmShouldCount(folder)) {
+ let folderNew = folder.getNumNewMessages(false);
+ this._log.debug("NMNS_biffStateChanged: folder " + folder.URI + " new messages: " + folderNew);
+ if (folderNew > 0)
+ newCount += folderNew;
+ }
+ }
+ if (newCount > 0) {
+ this._mNewCount += newCount;
+ this._log.debug("NMNS_biffStateChanged: " + folder.URI + " New mail count " + this._mNewCount);
+ if (this.countNew)
+ this._notifyListeners(NMNS.count, "onCountChanged", this._mNewCount);
+ }
+ }
+ else if (newValue == Ci.nsIMsgFolder.nsMsgBiffState_NoMail) {
+ // Dodgy - when any folder tells us it has no mail, clear all unread mail
+ this._mNewCount = 0;
+ this._log.debug("NMNS_biffStateChanged: " + folder.URI + " New mail count 0");
+ if (this.countNew)
+ this._notifyListeners(NMNS.count, "onCountChanged", this._mNewCount);
+ }
+ },
+
+ _newMailReceived: function NMNS_newMailReceived(folder, oldValue, newValue) {
+ if (!this.confirmShouldCount(folder))
+ return;
+
+ if (!oldValue || (oldValue < 0))
+ oldValue = 0;
+ let oldTotal = this._mNewCount;
+ this._mNewCount += (newValue - oldValue);
+ this._log.debug("NMNS_newMailReceived: " + folder.URI +
+ " Old folder " + oldValue + " New folder " + newValue +
+ " Old total " + oldTotal + " New total " + this._mNewCount);
+ if (this.countNew)
+ this._notifyListeners(NMNS.count, "onCountChanged", this._mNewCount);
+ },
+
+ _updateUnreadCount: function NMNS_updateUnreadCount(folder, oldValue, newValue) {
+ if (!this.confirmShouldCount(folder))
+ return;
+
+ // treat "count unknown" as zero
+ if (oldValue < 0)
+ oldValue = 0;
+ if (newValue < 0)
+ newValue = 0;
+
+ this._mUnreadCount += (newValue - oldValue);
+ if (!this.countNew) {
+ this._log.info("NMNS_updateUnreadCount notifying listeners: unread count " + this._mUnreadCount);
+ this._notifyListeners(NMNS.count, "onCountChanged", this._mUnreadCount);
+ }
+ },
+
+ OnItemAdded: function NMNS_OnItemAdded(parentItem, item) {
+ if (item instanceof Ci.nsIMsgDBHdr) {
+ if (this.confirmShouldCount(item.folder)) {
+ this._log.trace("NMNS_OnItemAdded: item " + item.folder.getUriForMsg(item) + " added to " + item.folder.folderURL);
+ }
+ }
+ },
+
+ OnItemPropertyFlagChanged: function NMNS_OnItemPropertyFlagChanged(item,
+ property,
+ oldFlag,
+ newFlag) {
+ if (item instanceof Ci.nsIMsgDBHdr) {
+ if ((oldFlag & Ci.nsMsgMessageFlags.New)
+ && !(newFlag & Ci.nsMsgMessageFlags.New)) {
+ this._log.trace("NMNS_OnItemPropertyFlagChanged: item " + item.folder.getUriForMsg(item) + " marked read");
+ }
+ else if (newFlag & Ci.nsMsgMessageFlags.New) {
+ this._log.trace("NMNS_OnItemPropertyFlagChanged: item " + item.folder.getUriForMsg(item) + " marked unread");
+ }
+ }
+ },
+
+ OnItemRemoved: function NMNS_OnItemRemoved(parentItem, item) {
+ if (item instanceof Ci.nsIMsgDBHdr && !item.isRead) {
+ this._log.trace("NMNS_OnItemRemoved: unread item " + item.folder.getUriForMsg(item) + " removed from " + item.folder.folderURL);
+ }
+ },
+
+
+ // Implement mozINewMailNotificationService
+
+ get messageCount() {
+ if (this.countNew)
+ return this._mNewCount;
+ return this._mUnreadCount;
+ },
+
+ addListener: function NMNS_addListener(aListener, flags) {
+ this._log.trace("NMNS_addListener: listener " + aListener.toSource + " flags " + flags);
+ for (let i = 0; i < this._listeners.length; i++) {
+ let l = this._listeners[i];
+ if (l.obj === aListener) {
+ l.flags = flags;
+ return;
+ }
+ }
+ // If we get here, the listener wasn't already in the list
+ this._listeners.push({obj: aListener, flags: flags});
+ },
+
+ removeListener: function NMNS_removeListener(aListener) {
+ this._log.trace("NMNS_removeListener: listener " + aListener.toSource);
+ for (let i = 0; i < this._listeners.length; i++) {
+ let l = this._listeners[i];
+ if (l.obj === aListener) {
+ this._listeners.splice(i, 1);
+ return;
+ }
+ }
+ },
+
+ _listenersForFlag: function NMNS_listenersForFlag(flag) {
+ this._log.trace("NMNS_listenersForFlag " + flag + " length " + this._listeners.length + " " + this._listeners.toSource());
+ let list = [];
+ for (let i = 0; i < this._listeners.length; i++) {
+ let l = this._listeners[i];
+ if (l.flags & flag) {
+ list.push(l.obj);
+ }
+ }
+ return list;
+ },
+
+ _notifyListeners: function NMNS_notifyListeners(flag, func, value) {
+ let list = this._listenersForFlag(flag);
+ for (let i = 0; i < list.length; i++) {
+ this._log.debug("NMNS_notifyListeners " + flag + " " + func + " " + value);
+ list[i][func].call(list[i], value);
+ }
+ }
+};
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([NewMailNotificationService]);
diff --git a/mailnews/base/src/nsCidProtocolHandler.cpp b/mailnews/base/src/nsCidProtocolHandler.cpp
new file mode 100644
index 000000000..8f3f520be
--- /dev/null
+++ b/mailnews/base/src/nsCidProtocolHandler.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCidProtocolHandler.h"
+#include "nsStringGlue.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsComponentManagerUtils.h"
+
+nsCidProtocolHandler::nsCidProtocolHandler()
+{
+}
+
+nsCidProtocolHandler::~nsCidProtocolHandler()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsCidProtocolHandler, nsIProtocolHandler)
+
+NS_IMETHODIMP nsCidProtocolHandler::GetScheme(nsACString & aScheme)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCidProtocolHandler::GetDefaultPort(int32_t *aDefaultPort)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCidProtocolHandler::GetProtocolFlags(uint32_t *aProtocolFlags)
+{
+ // XXXbz so why does this protocol handler exist, exactly?
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCidProtocolHandler::NewURI(const nsACString & aSpec, const char *aOriginCharset, nsIURI *aBaseURI, nsIURI **_retval)
+{
+ nsresult rv;
+ nsCOMPtr <nsIURI> url = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // the right fix is to use the baseSpec (or aBaseUri)
+ // and specify the cid, and then fix mime
+ // to handle that, like it does with "...&part=1.2"
+ // for now, do about blank to prevent spam
+ // from popping up annoying alerts about not implementing the cid
+ // protocol
+ rv = url->SetSpec(nsDependentCString("about:blank"));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*_retval = url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCidProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCidProtocolHandler::NewChannel2(nsIURI *aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCidProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
diff --git a/mailnews/base/src/nsCidProtocolHandler.h b/mailnews/base/src/nsCidProtocolHandler.h
new file mode 100644
index 000000000..035763d48
--- /dev/null
+++ b/mailnews/base/src/nsCidProtocolHandler.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCidProtocolHandler_h__
+#define nsCidProtocolHandler_h__
+
+#include "nsCOMPtr.h"
+#include "nsIProtocolHandler.h"
+
+class nsCidProtocolHandler : public nsIProtocolHandler
+{
+public:
+ nsCidProtocolHandler();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+private:
+ virtual ~nsCidProtocolHandler();
+};
+
+#endif /* nsCidProtocolHandler_h__ */
diff --git a/mailnews/base/src/nsCopyMessageStreamListener.cpp b/mailnews/base/src/nsCopyMessageStreamListener.cpp
new file mode 100644
index 000000000..ed41aa51b
--- /dev/null
+++ b/mailnews/base/src/nsCopyMessageStreamListener.cpp
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCopyMessageStreamListener.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMailboxUrl.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#include "netCore.h"
+
+NS_IMPL_ISUPPORTS(nsCopyMessageStreamListener, nsIStreamListener,
+ nsIRequestObserver, nsICopyMessageStreamListener)
+
+static nsresult GetMessage(nsIURI *aURL, nsIMsgDBHdr **message)
+{
+ NS_ENSURE_ARG_POINTER(message);
+
+ nsCOMPtr<nsIMsgMessageUrl> uriURL;
+ nsresult rv;
+
+ //Need to get message we are about to copy
+ uriURL = do_QueryInterface(aURL, &rv);
+ if(NS_FAILED(rv))
+ return rv;
+
+ // get the uri. first try and use the original message spec
+ // if that fails, use the spec of nsIURI that we're called with
+ nsCString uri;
+ rv = uriURL->GetOriginalSpec(getter_Copies(uri));
+ if (NS_FAILED(rv) || uri.IsEmpty()) {
+ rv = uriURL->GetUri(getter_Copies(uri));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ nsCOMPtr <nsIMsgMessageService> msgMessageService;
+ rv = GetMessageServiceFromURI(uri, getter_AddRefs(msgMessageService));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!msgMessageService)
+ return NS_ERROR_FAILURE;
+
+ rv = msgMessageService->MessageURIToMsgHdr(uri.get(), message);
+ return rv;
+}
+
+nsCopyMessageStreamListener::nsCopyMessageStreamListener()
+{
+}
+
+nsCopyMessageStreamListener::~nsCopyMessageStreamListener()
+{
+ //All member variables are nsCOMPtr's.
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::Init(nsIMsgFolder *srcFolder, nsICopyMessageListener *destination, nsISupports *listenerData)
+{
+ mSrcFolder = srcFolder;
+ mDestination = destination;
+ mListenerData = listenerData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::StartMessage()
+{
+ if (mDestination)
+ mDestination->StartMessage();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::EndMessage(nsMsgKey key)
+{
+ if (mDestination)
+ mDestination->EndMessage(key);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsCopyMessageStreamListener::OnDataAvailable(nsIRequest * /* request */, nsISupports *ctxt, nsIInputStream *aIStream, uint64_t sourceOffset, uint32_t aLength)
+{
+ nsresult rv;
+ rv = mDestination->CopyData(aIStream, aLength);
+ return rv;
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::OnStartRequest(nsIRequest * request, nsISupports *ctxt)
+{
+ nsCOMPtr<nsIMsgDBHdr> message;
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(ctxt, &rv);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "ahah...someone didn't pass in the expected context!!!");
+
+ if (NS_SUCCEEDED(rv))
+ rv = GetMessage(uri, getter_AddRefs(message));
+ if(NS_SUCCEEDED(rv))
+ rv = mDestination->BeginCopy(message);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::EndCopy(nsISupports *url, nsresult aStatus)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(url, &rv);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool copySucceeded = (aStatus == NS_BINDING_SUCCEEDED);
+ rv = mDestination->EndCopy(copySucceeded);
+ //If this is a move and we finished the copy, delete the old message.
+ bool moveMessage = false;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailURL(do_QueryInterface(uri));
+ if (mailURL)
+ rv = mailURL->IsUrlType(nsIMsgMailNewsUrl::eMove, &moveMessage);
+
+ if (NS_FAILED(rv))
+ moveMessage = false;
+
+ // OK, this is wrong if we're moving to an imap folder, for example. This really says that
+ // we were able to pull the message from the source, NOT that we were able to
+ // put it in the destination!
+ if (moveMessage)
+ {
+ // don't do this if we're moving to an imap folder - that's handled elsewhere.
+ nsCOMPtr<nsIMsgImapMailFolder> destImap = do_QueryInterface(mDestination);
+ // if the destination is a local folder, it will handle the delete from the source in EndMove
+ if (!destImap)
+ rv = mDestination->EndMove(copySucceeded);
+ }
+ // Even if the above actions failed we probably still want to return NS_OK.
+ // There should probably be some error dialog if either the copy or delete failed.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::OnStopRequest(nsIRequest* request, nsISupports *ctxt, nsresult aStatus)
+{
+ return EndCopy(ctxt, aStatus);
+}
+
diff --git a/mailnews/base/src/nsCopyMessageStreamListener.h b/mailnews/base/src/nsCopyMessageStreamListener.h
new file mode 100644
index 000000000..b857401ed
--- /dev/null
+++ b/mailnews/base/src/nsCopyMessageStreamListener.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSCOPYMESSAGESTREAMLISTENER_H
+#define NSCOPYMESSAGESTREAMLISTENER_H
+
+#include "nsICopyMsgStreamListener.h"
+#include "nsIStreamListener.h"
+#include "nsIMsgFolder.h"
+#include "nsICopyMessageListener.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+
+class nsCopyMessageStreamListener : public nsIStreamListener, public nsICopyMessageStreamListener {
+
+public:
+ nsCopyMessageStreamListener();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICOPYMESSAGESTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+protected:
+ virtual ~nsCopyMessageStreamListener();
+
+ nsCOMPtr<nsICopyMessageListener> mDestination;
+ nsCOMPtr<nsISupports> mListenerData;
+ nsCOMPtr<nsIMsgFolder> mSrcFolder;
+
+};
+
+
+
+#endif
diff --git a/mailnews/base/src/nsMailDirProvider.cpp b/mailnews/base/src/nsMailDirProvider.cpp
new file mode 100644
index 000000000..6df8d89e1
--- /dev/null
+++ b/mailnews/base/src/nsMailDirProvider.cpp
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMailDirProvider.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsXULAppAPI.h"
+#include "nsMsgBaseCID.h"
+#include "nsArrayEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsEnumeratorUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIChromeRegistry.h"
+#include "nsICategoryManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/Services.h"
+
+#define MAIL_DIR_50_NAME "Mail"
+#define IMAP_MAIL_DIR_50_NAME "ImapMail"
+#define NEWS_DIR_50_NAME "News"
+#define MSG_FOLDER_CACHE_DIR_50_NAME "panacea.dat"
+
+nsresult
+nsMailDirProvider::EnsureDirectory(nsIFile *aDirectory)
+{
+ bool exists;
+ nsresult rv = aDirectory->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists)
+ rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0700);
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsMailDirProvider,
+ nsIDirectoryServiceProvider,
+ nsIDirectoryServiceProvider2)
+
+NS_IMETHODIMP
+nsMailDirProvider::GetFile(const char *aKey, bool *aPersist,
+ nsIFile **aResult)
+{
+ // NOTE: This function can be reentrant through the NS_GetSpecialDirectory
+ // call, so be careful not to cause infinite recursion.
+ // i.e. the check for supported files must come first.
+ const char* leafName = nullptr;
+ bool isDirectory = true;
+
+ if (!strcmp(aKey, NS_APP_MAIL_50_DIR))
+ leafName = MAIL_DIR_50_NAME;
+ else if (!strcmp(aKey, NS_APP_IMAP_MAIL_50_DIR))
+ leafName = IMAP_MAIL_DIR_50_NAME;
+ else if (!strcmp(aKey, NS_APP_NEWS_50_DIR))
+ leafName = NEWS_DIR_50_NAME;
+ else if (!strcmp(aKey, NS_APP_MESSENGER_FOLDER_CACHE_50_FILE)) {
+ isDirectory = false;
+ leafName = MSG_FOLDER_CACHE_DIR_50_NAME;
+ }
+ else
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> parentDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(parentDir));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIFile> file;
+ rv = parentDir->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsDependentCString leafStr(leafName);
+ rv = file->AppendNative(leafStr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool exists;
+ if (isDirectory && NS_SUCCEEDED(file->Exists(&exists)) && !exists)
+ rv = EnsureDirectory(file);
+
+ *aPersist = true;
+ file.swap(*aResult);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMailDirProvider::GetFiles(const char *aKey,
+ nsISimpleEnumerator **aResult)
+{
+ if (strcmp(aKey, ISP_DIRECTORY_LIST) != 0)
+ return NS_ERROR_FAILURE;
+
+ // The list of isp directories includes the isp directory
+ // in the current process dir (i.e. <path to thunderbird.exe>\isp and
+ // <path to thunderbird.exe>\isp\locale
+ // and isp and isp\locale for each active extension
+
+ nsCOMPtr<nsIProperties> dirSvc =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
+ if (!dirSvc)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> currentProcessDir;
+ nsresult rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR,
+ NS_GET_IID(nsIFile), getter_AddRefs(currentProcessDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ rv = NS_NewSingletonEnumerator(getter_AddRefs(directoryEnumerator), currentProcessDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> combinedEnumerator;
+ nsCOMPtr<nsISimpleEnumerator> extensionsEnum;
+
+ // xpcshell-tests don't have XRE_EXTENSIONS_DIR_LIST, so accept a null return here.
+ dirSvc->Get(XRE_EXTENSIONS_DIR_LIST,
+ NS_GET_IID(nsISimpleEnumerator),
+ getter_AddRefs(extensionsEnum));
+
+ rv = NS_NewUnionEnumerator(getter_AddRefs(combinedEnumerator), directoryEnumerator, extensionsEnum);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = new AppendingEnumerator(combinedEnumerator));
+ return NS_SUCCESS_AGGREGATE_RESULT;
+}
+
+NS_IMPL_ISUPPORTS(nsMailDirProvider::AppendingEnumerator,
+ nsISimpleEnumerator)
+
+NS_IMETHODIMP
+nsMailDirProvider::AppendingEnumerator::HasMoreElements(bool *aResult)
+{
+ *aResult = mNext || mNextWithLocale ? true : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailDirProvider::AppendingEnumerator::GetNext(nsISupports* *aResult)
+{
+ // Set the return value to the next directory we want to enumerate over
+ if (aResult)
+ NS_ADDREF(*aResult = mNext);
+
+ if (mNextWithLocale)
+ {
+ mNext = mNextWithLocale;
+ mNextWithLocale = nullptr;
+ return NS_OK;
+ }
+
+ mNext = nullptr;
+
+ // Ignore all errors
+
+ bool more;
+ while (NS_SUCCEEDED(mBase->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> nextbasesupp;
+ mBase->GetNext(getter_AddRefs(nextbasesupp));
+
+ nsCOMPtr<nsIFile> nextbase(do_QueryInterface(nextbasesupp));
+ if (!nextbase)
+ continue;
+
+ nextbase->Clone(getter_AddRefs(mNext));
+ if (!mNext)
+ continue;
+
+ mNext->AppendNative(NS_LITERAL_CSTRING("isp"));
+ bool exists;
+ nsresult rv = mNext->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists)
+ {
+ if (!mLocale.IsEmpty())
+ {
+ mNext->Clone(getter_AddRefs(mNextWithLocale));
+ mNextWithLocale->AppendNative(mLocale);
+ rv = mNextWithLocale->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ mNextWithLocale = nullptr; // clear out mNextWithLocale, so we don't try to iterate over it
+ }
+ break;
+ }
+
+ mNext = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsMailDirProvider::AppendingEnumerator::AppendingEnumerator
+ (nsISimpleEnumerator* aBase) :
+ mBase(aBase)
+{
+ nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
+ mozilla::services::GetXULChromeRegistryService();
+ if (packageRegistry)
+ packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"), false, mLocale);
+ // Initialize mNext to begin
+ GetNext(nullptr);
+}
diff --git a/mailnews/base/src/nsMailDirProvider.h b/mailnews/base/src/nsMailDirProvider.h
new file mode 100644
index 000000000..613551a79
--- /dev/null
+++ b/mailnews/base/src/nsMailDirProvider.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMailDirProvider_h__
+#define nsMailDirProvider_h__
+
+#include "nsIDirectoryService.h"
+#include "nsISimpleEnumerator.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+
+class nsMailDirProvider final : public nsIDirectoryServiceProvider2
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
+
+private:
+ ~nsMailDirProvider() {}
+
+ nsresult EnsureDirectory(nsIFile *aDirectory);
+
+ class AppendingEnumerator final : public nsISimpleEnumerator
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ explicit AppendingEnumerator(nsISimpleEnumerator* aBase);
+
+ private:
+ ~AppendingEnumerator() {}
+ nsCOMPtr<nsISimpleEnumerator> mBase;
+ nsCOMPtr<nsIFile> mNext;
+ nsCOMPtr<nsIFile> mNextWithLocale;
+ nsCString mLocale;
+ };
+};
+
+#endif // nsMailDirProvider_h__
diff --git a/mailnews/base/src/nsMailDirServiceDefs.h b/mailnews/base/src/nsMailDirServiceDefs.h
new file mode 100644
index 000000000..fe12ed0d6
--- /dev/null
+++ b/mailnews/base/src/nsMailDirServiceDefs.h
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsMailDirectoryServiceDefs_h___
+#define nsMailDirectoryServiceDefs_h___
+
+//=============================================================================
+//
+// Defines property names for directories available from the mail-specific
+// nsMailDirProvider.
+//
+// System and XPCOM properties are defined in nsDirectoryServiceDefs.h.
+// General application properties are defined in nsAppDirectoryServiceDefs.h.
+//
+//=============================================================================
+
+// ----------------------------------------------------------------------------
+// Files and directories that exist on a per-profile basis.
+// ----------------------------------------------------------------------------
+
+#define NS_APP_MAIL_50_DIR "MailD"
+#define NS_APP_IMAP_MAIL_50_DIR "IMapMD"
+#define NS_APP_NEWS_50_DIR "NewsD"
+
+#define NS_APP_MESSENGER_FOLDER_CACHE_50_FILE "MFCaF"
+
+#define ISP_DIRECTORY_LIST "ISPDL"
+
+#endif
diff --git a/mailnews/base/src/nsMailNewsCommandLineHandler.js b/mailnews/base/src/nsMailNewsCommandLineHandler.js
new file mode 100644
index 000000000..3a85ec336
--- /dev/null
+++ b/mailnews/base/src/nsMailNewsCommandLineHandler.js
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+var MAPI_STARTUP_ARG = "MapiStartup";
+var MESSAGE_ID_PARAM = "?messageid=";
+
+var CMDLINEHANDLER_CID = Components.ID("{2f86d554-f9d9-4e76-8eb7-243f047333ee}");
+var CMDLINEHANDLER_CONTRACTID = "@mozilla.org/commandlinehandler/general-startup;1?type=mail";
+
+var nsMailNewsCommandLineHandler =
+{
+ get _messenger() {
+ delete this._messenger;
+ return this._messenger = Cc["@mozilla.org/messenger;1"]
+ .createInstance(Ci.nsIMessenger);
+ },
+
+ /* nsICommandLineHandler */
+
+ /**
+ * Handles the following command line arguments:
+ * - -mail: opens the mail folder view
+ * - -MapiStartup: indicates that this startup is due to MAPI.
+ * Don't do anything for now.
+ */
+ handle: function nsMailNewsCommandLineHandler_handle(aCommandLine) {
+ // Do this here because xpcshell isn't too happy with this at startup
+ Components.utils.import("resource:///modules/MailUtils.js");
+ // -mail <URL>
+ let mailURL = null;
+ try {
+ mailURL = aCommandLine.handleFlagWithParam("mail", false);
+ }
+ catch (e) {
+ // We're going to cover -mail without a parameter later
+ }
+
+ if (mailURL && mailURL.length > 0) {
+ let msgHdr = null;
+ if (/^(mailbox|imap|news)-message:\/\//.test(mailURL)) {
+ // This might be a standard message URI, or one with a messageID
+ // parameter. Handle both cases.
+ let messageIDIndex = mailURL.toLowerCase().indexOf(MESSAGE_ID_PARAM);
+ if (messageIDIndex != -1) {
+ // messageID parameter
+ // Convert the message URI into a folder URI
+ let folderURI = mailURL.slice(0, messageIDIndex)
+ .replace("-message", "");
+ // Get the message ID
+ let messageID = mailURL.slice(messageIDIndex + MESSAGE_ID_PARAM.length);
+ // Make sure the folder tree is initialized
+ MailUtils.discoverFolders();
+
+ let folder = MailUtils.getFolderForURI(folderURI, true);
+ // The folder might not exist, so guard against that
+ if (folder && messageID.length > 0)
+ msgHdr = folder.msgDatabase.getMsgHdrForMessageID(messageID);
+ }
+ else {
+ // message URI
+ msgHdr = this._messenger.msgHdrFromURI(mailURL);
+ }
+ }
+ else {
+ // Necko URL, so convert it into a message header
+ let neckoURL = null;
+ try {
+ neckoURL = Services.io.newURI(mailURL, null, null);
+ }
+ catch (e) {
+ // We failed to convert the URI. Oh well.
+ }
+
+ if (neckoURL instanceof Ci.nsIMsgMessageUrl)
+ msgHdr = neckoURL.messageHeader;
+ }
+
+ if (msgHdr) {
+ aCommandLine.preventDefault = true;
+ MailUtils.displayMessage(msgHdr);
+ }
+ else if (AppConstants.MOZ_APP_NAME == "seamonkey" &&
+ /\.(eml|msg)$/i.test(mailURL)) {
+ try {
+ let file = aCommandLine.resolveFile(mailURL);
+ // No point in trying to open a file if it doesn't exist or is empty
+ if (file.exists() && file.fileSize > 0) {
+ // Get the URL for this file
+ let fileURL = Services.io.newFileURI(file)
+ .QueryInterface(Ci.nsIFileURL);
+ fileURL.query = "?type=application/x-message-display";
+ // Open this file in a new message window.
+ Services.ww.openWindow(null,
+ "chrome://messenger/content/messageWindow.xul",
+ "_blank", "all,chrome,dialog=no,status,toolbar",
+ fileURL);
+ aCommandLine.preventDefault = true;
+ }
+ }
+ catch (e) {
+ }
+ }
+ else {
+ dump("Unrecognized URL: " + mailURL + "\n");
+ Services.console.logStringMessage("Unrecognized URL: " + mailURL);
+ }
+ }
+
+ // -mail (no parameter)
+ let mailFlag = aCommandLine.handleFlag("mail", false);
+ if (mailFlag) {
+ // Focus the 3pane window if one is present, else open one
+ let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane");
+ if (mail3PaneWindow) {
+ mail3PaneWindow.focus();
+ }
+ else {
+ Services.ww.openWindow(null, "chrome://messenger/content/", "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar,dialog=no",
+ null);
+ }
+ aCommandLine.preventDefault = true;
+ }
+
+ // -MapiStartup
+ aCommandLine.handleFlag(MAPI_STARTUP_ARG, false);
+ },
+
+ helpInfo: " -mail Open the mail folder view.\n" +
+ " -mail <URL> Open the message specified by this URL.\n",
+
+ classInfo: XPCOMUtils.generateCI({classID: CMDLINEHANDLER_CID,
+ contractID: CMDLINEHANDLER_CONTRACTID,
+ interfaces: [Ci.nsICommandLineHandler],
+ flags: Ci.nsIClassInfo.SINGLETON}),
+
+ /* nsIFactory */
+ createInstance: function(outer, iid) {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+
+ return this.QueryInterface(iid);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler,
+ Ci.nsIFactory])
+};
+
+function mailNewsCommandLineHandlerModule() {}
+mailNewsCommandLineHandlerModule.prototype =
+{
+ // XPCOM registration
+ classID: CMDLINEHANDLER_CID,
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIModule]),
+
+ _xpcom_factory: nsMailNewsCommandLineHandler
+};
+
+var components = [mailNewsCommandLineHandlerModule];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/base/src/nsMessenger.cpp b/mailnews/base/src/nsMessenger.cpp
new file mode 100644
index 000000000..a13c4adbc
--- /dev/null
+++ b/mailnews/base/src/nsMessenger.cpp
@@ -0,0 +1,3072 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prsystem.h"
+
+#include "nsMessenger.h"
+
+// xpcom
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsIStringStream.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsQuickSort.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIMutableArray.h"
+#include "mozilla/Services.h"
+
+// necko
+#include "nsMimeTypes.h"
+#include "nsIURL.h"
+#include "nsIPrompt.h"
+#include "nsIStreamListener.h"
+#include "nsIStreamConverterService.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsIMIMEInfo.h"
+
+// rdf
+#include "nsIRDFResource.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+
+// gecko
+#include "nsLayoutCID.h"
+#include "nsIContentViewer.h"
+
+// embedding
+#ifdef NS_PRINTING
+#include "nsIWebBrowserPrint.h"
+#include "nsMsgPrintEngine.h"
+#endif
+
+/* for access to docshell */
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIWebNavigation.h"
+
+// mail
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMailboxUrl.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgIncomingServer.h"
+
+#include "nsIMsgMessageService.h"
+#include "nsMsgRDFUtils.h"
+
+#include "nsIMsgHdr.h"
+#include "nsIMimeMiscStatus.h"
+// compose
+#include "nsMsgCompCID.h"
+#include "nsMsgI18N.h"
+#include "nsNativeCharsetUtils.h"
+
+// draft/folders/sendlater/etc
+#include "nsIMsgCopyService.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIUrlListener.h"
+
+// undo
+#include "nsITransaction.h"
+#include "nsMsgTxn.h"
+
+// charset conversions
+#include "nsMsgMimeCID.h"
+#include "nsIMimeConverter.h"
+
+// Save As
+#include "nsIFilePicker.h"
+#include "nsIStringBundle.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIExternalProtocolService.h"
+#include "nsIMIMEService.h"
+#include "nsITransfer.h"
+
+#include "nsILinkHandler.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+#define MESSENGER_SAVE_DIR_PREF_NAME "messenger.save.dir"
+#define MIMETYPE_DELETED "text/x-moz-deleted"
+#define ATTACHMENT_PERMISSION 00664
+
+//
+// Convert an nsString buffer to plain text...
+//
+#include "nsMsgUtils.h"
+#include "nsCharsetSource.h"
+#include "nsIChannel.h"
+#include "nsIOutputStream.h"
+#include "nsIPrincipal.h"
+
+static void ConvertAndSanitizeFileName(const char * displayName, nsString& aResult)
+{
+ nsCString unescapedName;
+
+ /* we need to convert the UTF-8 fileName to platform specific character set.
+ The display name is in UTF-8 because it has been escaped from JS
+ */
+ MsgUnescapeString(nsDependentCString(displayName), 0, unescapedName);
+ CopyUTF8toUTF16(unescapedName, aResult);
+
+ // replace platform specific path separator and illegale characters to avoid any confusion
+ MsgReplaceChar(aResult, FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
+}
+
+// ***************************************************
+// jefft - this is a rather obscured class serves for Save Message As File,
+// Save Message As Template, and Save Attachment to a file
+//
+class nsSaveAllAttachmentsState;
+
+class nsSaveMsgListener : public nsIUrlListener,
+ public nsIMsgCopyServiceListener,
+ public nsIStreamListener,
+ public nsICancelable
+{
+public:
+ nsSaveMsgListener(nsIFile *file, nsMessenger *aMessenger, nsIUrlListener *aListener);
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSICANCELABLE
+
+ nsCOMPtr<nsIFile> m_file;
+ nsCOMPtr<nsIOutputStream> m_outputStream;
+ char m_dataBuffer[FILE_IO_BUFFER_SIZE];
+ nsCOMPtr<nsIChannel> m_channel;
+ nsCString m_templateUri;
+ nsMessenger *m_messenger; // not ref counted
+ nsSaveAllAttachmentsState *m_saveAllAttachmentsState;
+
+ // rhp: For character set handling
+ bool m_doCharsetConversion;
+ nsString m_charset;
+ enum {
+ eUnknown,
+ ePlainText,
+ eHTML
+ } m_outputFormat;
+ nsCString m_msgBuffer;
+
+ nsCString m_contentType; // used only when saving attachment
+
+ nsCOMPtr<nsITransfer> mTransfer;
+ nsCOMPtr<nsIUrlListener> mListener;
+ nsCOMPtr<nsIURI> mListenerUri;
+ int64_t mProgress;
+ int64_t mMaxProgress;
+ bool mCanceled;
+ bool mInitialized;
+ bool mUrlHasStopped;
+ bool mRequestHasStopped;
+ nsresult InitializeDownload(nsIRequest * aRequest);
+
+private:
+ virtual ~nsSaveMsgListener();
+};
+
+class nsSaveAllAttachmentsState
+{
+public:
+ nsSaveAllAttachmentsState(uint32_t count,
+ const char **contentTypeArray,
+ const char **urlArray,
+ const char **displayNameArray,
+ const char **messageUriArray,
+ const char *directoryName,
+ bool detachingAttachments);
+ virtual ~nsSaveAllAttachmentsState();
+
+ uint32_t m_count;
+ uint32_t m_curIndex;
+ char* m_directoryName;
+ char** m_contentTypeArray;
+ char** m_urlArray;
+ char** m_displayNameArray;
+ char** m_messageUriArray;
+ bool m_detachingAttachments;
+
+ // if detaching, do without warning? Will create unique files instead of
+ // prompting if duplicate files exist.
+ bool m_withoutWarning;
+ nsTArray<nsCString> m_savedFiles; // if detaching first, remember where we saved to.
+};
+
+//
+// nsMessenger
+//
+nsMessenger::nsMessenger()
+{
+ mCurHistoryPos = -2; // first message selected goes at position 0.
+ // InitializeFolderRoot();
+}
+
+nsMessenger::~nsMessenger()
+{
+}
+
+
+NS_IMPL_ISUPPORTS(nsMessenger, nsIMessenger, nsISupportsWeakReference, nsIFolderListener)
+
+NS_IMETHODIMP nsMessenger::SetWindow(mozIDOMWindowProxy *aWin, nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aWin)
+ {
+ mMsgWindow = aMsgWindow;
+ mWindow = aWin;
+
+ rv = mailSession->AddFolderListener(this, nsIFolderListener::removed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(aWin, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(aWin);
+
+ nsIDocShell *docShell = win->GetDocShell();
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(docShell));
+ NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocShellTreeItem> rootDocShellAsItem;
+ docShellAsItem->GetSameTypeRootTreeItem(getter_AddRefs(rootDocShellAsItem));
+
+ nsCOMPtr<nsIDocShellTreeItem> childAsItem;
+ rv = rootDocShellAsItem->FindChildWithName(NS_LITERAL_STRING("messagepane"), true, false,
+ nullptr, nullptr, getter_AddRefs(childAsItem));
+
+ mDocShell = do_QueryInterface(childAsItem);
+ if (NS_SUCCEEDED(rv) && mDocShell) {
+ mCurrentDisplayCharset = ""; // Important! Clear out mCurrentDisplayCharset so we reset a default charset on mDocshell the next time we try to load something into it.
+
+ if (aMsgWindow)
+ aMsgWindow->GetTransactionManager(getter_AddRefs(mTxnMgr));
+ }
+
+ // we don't always have a message pane, like in the addressbook
+ // so if we don't have a docshell, use the one for the xul window.
+ // we do this so OpenURL() will work.
+ if (!mDocShell)
+ mDocShell = docShell;
+ } // if aWin
+ else
+ {
+ // Remove the folder listener if we added it, i.e. if mWindow is non-null
+ if (mWindow)
+ {
+ rv = mailSession->RemoveFolderListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mWindow = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMessenger::SetDisplayCharset(const nsACString& aCharset)
+{
+ // libmime always converts to UTF-8 (both HTML and XML)
+ if (mDocShell)
+ {
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv)
+ {
+ cv->SetHintCharacterSet(aCharset);
+ cv->SetHintCharacterSetSource(kCharsetFromChannel);
+
+ mCurrentDisplayCharset = aCharset;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMessenger::PromptIfFileExists(nsIFile *file)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ bool exists;
+ file->Exists(&exists);
+ if (exists)
+ {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
+ if (!dialog) return rv;
+ nsAutoString path;
+ bool dialogResult = false;
+ nsString errorMessage;
+
+ file->GetPath(path);
+ const char16_t *pathFormatStrings[] = { path.get() };
+
+ if (!mStringBundle)
+ {
+ rv = InitStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = mStringBundle->FormatStringFromName(u"fileExists",
+ pathFormatStrings, 1,
+ getter_Copies(errorMessage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dialog->Confirm(nullptr, errorMessage.get(), &dialogResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dialogResult)
+ {
+ return NS_OK; // user says okay to replace
+ }
+ else
+ {
+ // if we don't re-init the path for redisplay the picker will
+ // show the full path, not just the file name
+ nsCOMPtr<nsIFile> currentFile = do_CreateInstance("@mozilla.org/file/local;1");
+ if (!currentFile) return NS_ERROR_FAILURE;
+
+ rv = currentFile->InitWithPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString leafName;
+ currentFile->GetLeafName(leafName);
+ if (!leafName.IsEmpty())
+ path.Assign(leafName); // path should be a copy of leafName
+
+ nsCOMPtr<nsIFilePicker> filePicker =
+ do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString saveAttachmentStr;
+ GetString(NS_LITERAL_STRING("SaveAttachment"), saveAttachmentStr);
+ filePicker->Init(mWindow,
+ saveAttachmentStr,
+ nsIFilePicker::modeSave);
+ filePicker->SetDefaultString(path);
+ filePicker->AppendFilters(nsIFilePicker::filterAll);
+
+ nsCOMPtr <nsIFile> lastSaveDir;
+ rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
+ if (NS_SUCCEEDED(rv) && lastSaveDir) {
+ filePicker->SetDisplayDirectory(lastSaveDir);
+ }
+
+ int16_t dialogReturn;
+ rv = filePicker->Show(&dialogReturn);
+ if (NS_FAILED(rv) || dialogReturn == nsIFilePicker::returnCancel) {
+ // XXX todo
+ // don't overload the return value like this
+ // change this function to have an out boolean
+ // that we check to see if the user cancelled
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+
+ rv = filePicker->GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetLastSaveDirectory(localFile);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // reset the file to point to the new path
+ return file->InitWithFile(localFile);
+ }
+ }
+ else
+ {
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::AddMsgUrlToNavigateHistory(const nsACString& aURL)
+{
+ // mNavigatingToUri is set to a url if we're already doing a back/forward,
+ // in which case we don't want to add the url to the history list.
+ // Or if the entry at the cur history pos is the same as what we're loading, don't
+ // add it to the list.
+ if (!mNavigatingToUri.Equals(aURL) && (mCurHistoryPos < 0 || !mLoadedMsgHistory[mCurHistoryPos].Equals(aURL)))
+ {
+ mNavigatingToUri = aURL;
+ nsCString curLoadedFolderUri;
+ nsCOMPtr <nsIMsgFolder> curLoadedFolder;
+
+ mMsgWindow->GetOpenFolder(getter_AddRefs(curLoadedFolder));
+ // for virtual folders, we want to select the right folder,
+ // which isn't the same as the folder specified in the msg uri.
+ // So add the uri for the currently loaded folder to the history list.
+ if (curLoadedFolder)
+ curLoadedFolder->GetURI(curLoadedFolderUri);
+
+ mLoadedMsgHistory.InsertElementAt(mCurHistoryPos++ + 2, mNavigatingToUri);
+ mLoadedMsgHistory.InsertElementAt(mCurHistoryPos++ + 2, curLoadedFolderUri);
+ // we may want to prune this history if it gets large, but I think it's
+ // more interesting to prune the back and forward menu.
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessenger::OpenURL(const nsACString& aURL)
+{
+ // This is to setup the display DocShell as UTF-8 capable...
+ SetDisplayCharset(NS_LITERAL_CSTRING("UTF-8"));
+
+ nsCOMPtr <nsIMsgMessageService> messageService;
+ nsresult rv = GetMessageServiceFromURI(aURL, getter_AddRefs(messageService));
+
+ if (NS_SUCCEEDED(rv) && messageService)
+ {
+ nsCOMPtr<nsIURI> dummyNull;
+ messageService->DisplayMessage(PromiseFlatCString(aURL).get(), mDocShell,
+ mMsgWindow, nullptr, nullptr, getter_AddRefs(dummyNull));
+ AddMsgUrlToNavigateHistory(aURL);
+ mLastDisplayURI = aURL; // remember the last uri we displayed....
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ if(!webNav)
+ return NS_ERROR_FAILURE;
+ rv = webNav->LoadURI(NS_ConvertASCIItoUTF16(aURL).get(), // URI string
+ nsIWebNavigation::LOAD_FLAGS_IS_LINK, // Load flags
+ nullptr, // Referring URI
+ nullptr, // Post stream
+ nullptr); // Extra headers
+ return rv;
+}
+
+NS_IMETHODIMP nsMessenger::LaunchExternalURL(const nsACString& aURL)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), PromiseFlatCString(aURL).get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIExternalProtocolService> extProtService = do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return extProtService->LoadUrl(uri);
+}
+
+NS_IMETHODIMP
+nsMessenger::LoadURL(mozIDOMWindowProxy *aWin, const nsACString& aURL)
+{
+ nsresult rv;
+
+ SetDisplayCharset(NS_LITERAL_CSTRING("UTF-8"));
+
+ NS_ConvertASCIItoUTF16 uriString(aURL);
+ // Cleanup the empty spaces that might be on each end.
+ uriString.Trim(" ");
+ // Eliminate embedded newlines, which single-line text fields now allow:
+ uriString.StripChars("\r\n");
+ NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE);
+
+ bool loadingFromFile = false;
+ bool getDummyMsgHdr = false;
+ int64_t fileSize;
+
+ if (StringBeginsWith(uriString, NS_LITERAL_STRING("file:")))
+ {
+ nsCOMPtr<nsIURI> fileUri;
+ rv = NS_NewURI(getter_AddRefs(fileUri), uriString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFileURL> fileUrl = do_QueryInterface(fileUri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ file->GetFileSize(&fileSize);
+ uriString.Replace(0, 5, NS_LITERAL_STRING("mailbox:"));
+ uriString.Append(NS_LITERAL_STRING("&number=0"));
+ loadingFromFile = true;
+ getDummyMsgHdr = true;
+ }
+ else if (StringBeginsWith(uriString, NS_LITERAL_STRING("mailbox:")) &&
+ (CaseInsensitiveFindInReadable(NS_LITERAL_STRING(".eml?"), uriString)))
+ {
+ // if we have a mailbox:// url that points to an .eml file, we have to read
+ // the file size as well
+ uriString.Replace(0, 8, NS_LITERAL_STRING("file:"));
+ nsCOMPtr<nsIURI> fileUri;
+ rv = NS_NewURI(getter_AddRefs(fileUri), uriString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFileURL> fileUrl = do_QueryInterface(fileUri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ file->GetFileSize(&fileSize);
+ uriString.Replace(0, 5, NS_LITERAL_STRING("mailbox:"));
+ loadingFromFile = true;
+ getDummyMsgHdr = true;
+ }
+ else if (uriString.Find("type=application/x-message-display") >= 0)
+ getDummyMsgHdr = true;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl = do_QueryInterface(uri);
+ if (msgurl)
+ {
+ msgurl->SetMsgWindow(mMsgWindow);
+ if (loadingFromFile || getDummyMsgHdr)
+ {
+ if (loadingFromFile)
+ {
+ nsCOMPtr <nsIMailboxUrl> mailboxUrl = do_QueryInterface(msgurl, &rv);
+ mailboxUrl->SetMessageSize((uint32_t) fileSize);
+ }
+ if (getDummyMsgHdr)
+ {
+ nsCOMPtr <nsIMsgHeaderSink> headerSink;
+ // need to tell the header sink to capture some headers to create a fake db header
+ // so we can do reply to a .eml file or a rfc822 msg attachment.
+ mMsgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink));
+ if (headerSink)
+ {
+ nsCOMPtr <nsIMsgDBHdr> dummyHeader;
+ headerSink->GetDummyMsgHeader(getter_AddRefs(dummyHeader));
+ if (dummyHeader && loadingFromFile)
+ dummyHeader->SetMessageSize((uint32_t) fileSize);
+ }
+ }
+ }
+ }
+
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ rv = mDocShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadNormal);
+ AddMsgUrlToNavigateHistory(aURL);
+ mNavigatingToUri.Truncate();
+ mLastDisplayURI = aURL; // Remember the last uri we displayed.
+ return mDocShell->LoadURI(uri, loadInfo, 0, true);
+}
+
+NS_IMETHODIMP nsMessenger::SaveAttachmentToFile(nsIFile *aFile,
+ const nsACString &aURL,
+ const nsACString &aMessageUri,
+ const nsACString &aContentType,
+ nsIUrlListener *aListener)
+{
+ return SaveAttachment(aFile, aURL, aMessageUri, aContentType, nullptr, aListener);
+}
+
+NS_IMETHODIMP
+nsMessenger::DetachAttachmentsWOPrompts(nsIFile* aDestFolder,
+ uint32_t aCount,
+ const char **aContentTypeArray,
+ const char **aUrlArray,
+ const char **aDisplayNameArray,
+ const char **aMessageUriArray,
+ nsIUrlListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aDestFolder);
+ NS_ENSURE_ARG_POINTER(aContentTypeArray);
+ NS_ENSURE_ARG_POINTER(aUrlArray);
+ NS_ENSURE_ARG_POINTER(aMessageUriArray);
+ NS_ENSURE_ARG_POINTER(aDisplayNameArray);
+ if (!aCount)
+ return NS_OK;
+ nsSaveAllAttachmentsState *saveState;
+ nsCOMPtr<nsIFile> attachmentDestination;
+ nsresult rv = aDestFolder->Clone(getter_AddRefs(attachmentDestination));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString path;
+ rv = attachmentDestination->GetNativePath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString unescapedFileName;
+ ConvertAndSanitizeFileName(aDisplayNameArray[0], unescapedFileName);
+ rv = attachmentDestination->Append(unescapedFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = attachmentDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE, ATTACHMENT_PERMISSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ saveState = new nsSaveAllAttachmentsState(aCount,
+ aContentTypeArray,
+ aUrlArray,
+ aDisplayNameArray,
+ aMessageUriArray,
+ path.get(),
+ true);
+
+ // This method is used in filters, where we don't want to warn
+ saveState->m_withoutWarning = true;
+ rv = SaveAttachment(attachmentDestination,
+ nsDependentCString(aUrlArray[0]),
+ nsDependentCString(aMessageUriArray[0]),
+ nsDependentCString(aContentTypeArray[0]),
+ (void *)saveState,
+ aListener);
+ return rv;
+}
+
+nsresult nsMessenger::SaveAttachment(nsIFile *aFile,
+ const nsACString &aURL,
+ const nsACString &aMessageUri,
+ const nsACString &aContentType,
+ void *closure,
+ nsIUrlListener *aListener)
+{
+ nsCOMPtr<nsIMsgMessageService> messageService;
+ nsSaveAllAttachmentsState *saveState= (nsSaveAllAttachmentsState*) closure;
+ nsCOMPtr<nsIMsgMessageFetchPartService> fetchService;
+ nsAutoCString urlString;
+ nsCOMPtr<nsIURI> URL;
+ nsAutoCString fullMessageUri(aMessageUri);
+
+ // This instance will be held onto by the listeners, and will be released once
+ // the transfer has been completed.
+ RefPtr<nsSaveMsgListener> saveListener(new nsSaveMsgListener(aFile, this, aListener));
+ if (!saveListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ saveListener->m_contentType = aContentType;
+ if (saveState)
+ {
+ saveListener->m_saveAllAttachmentsState = saveState;
+ if (saveState->m_detachingAttachments)
+ {
+ nsCOMPtr<nsIURI> outputURI;
+ nsresult rv = NS_NewFileURI(getter_AddRefs(outputURI), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString fileUriSpec;
+ rv = outputURI->GetSpec(fileUriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ saveState->m_savedFiles.AppendElement(fileUriSpec);
+ }
+ }
+
+ urlString = aURL;
+ // strip out ?type=application/x-message-display because it confuses libmime
+
+ int32_t typeIndex = urlString.Find("?type=application/x-message-display");
+ if (typeIndex != kNotFound)
+ {
+ urlString.Cut(typeIndex, sizeof("?type=application/x-message-display") - 1);
+ // we also need to replace the next '&' with '?'
+ int32_t firstPartIndex = urlString.FindChar('&');
+ if (firstPartIndex != kNotFound)
+ urlString.SetCharAt('?', firstPartIndex);
+ }
+
+ MsgReplaceSubstring(urlString, "/;section", "?section");
+ nsresult rv = CreateStartupUrl(urlString.get(), getter_AddRefs(URL));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = GetMessageServiceFromURI(aMessageUri, getter_AddRefs(messageService));
+ if (NS_SUCCEEDED(rv))
+ {
+ fetchService = do_QueryInterface(messageService);
+ // if the message service has a fetch part service then we know we can fetch mime parts...
+ if (fetchService)
+ {
+ int32_t partPos = urlString.FindChar('?');
+ if (partPos == kNotFound)
+ return NS_ERROR_FAILURE;
+ fullMessageUri.Append(Substring(urlString, partPos));
+ }
+
+ nsCOMPtr<nsIStreamListener> convertedListener;
+ saveListener->QueryInterface(NS_GET_IID(nsIStreamListener),
+ getter_AddRefs(convertedListener));
+
+#ifndef XP_MACOSX
+ // if the content type is bin hex we are going to do a hokey hack and make sure we decode the bin hex
+ // when saving an attachment to disk..
+ if (MsgLowerCaseEqualsLiteral(aContentType, APPLICATION_BINHEX))
+ {
+ nsCOMPtr<nsIStreamListener> listener (do_QueryInterface(convertedListener));
+ nsCOMPtr<nsIStreamConverterService> streamConverterService = do_GetService("@mozilla.org/streamConverters;1", &rv);
+ nsCOMPtr<nsISupports> channelSupport = do_QueryInterface(saveListener->m_channel);
+
+ rv = streamConverterService->AsyncConvertData(APPLICATION_BINHEX,
+ "*/*",
+ listener,
+ channelSupport,
+ getter_AddRefs(convertedListener));
+ }
+#endif
+ nsCOMPtr<nsIURI> dummyNull;
+ if (fetchService)
+ rv = fetchService->FetchMimePart(URL, fullMessageUri.get(),
+ convertedListener, mMsgWindow,
+ saveListener, getter_AddRefs(dummyNull));
+ else
+ rv = messageService->DisplayMessage(fullMessageUri.get(),
+ convertedListener, mMsgWindow,
+ nullptr, nullptr,
+ getter_AddRefs(dummyNull));
+ } // if we got a message service
+ } // if we created a url
+
+ if (NS_FAILED(rv))
+ Alert("saveAttachmentFailed");
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::OpenAttachment(const nsACString& aContentType, const nsACString& aURL,
+ const nsACString& aDisplayName, const nsACString& aMessageUri, bool aIsExternalAttachment)
+{
+ nsresult rv = NS_OK;
+
+ // open external attachments inside our message pane which in turn should trigger the
+ // helper app dialog...
+ if (aIsExternalAttachment)
+ rv = OpenURL(aURL);
+ else
+ {
+ nsCOMPtr <nsIMsgMessageService> messageService;
+ rv = GetMessageServiceFromURI(aMessageUri, getter_AddRefs(messageService));
+ if (messageService)
+ rv = messageService->OpenAttachment(PromiseFlatCString(aContentType).get(), PromiseFlatCString(aDisplayName).get(),
+ PromiseFlatCString(aURL).get(), PromiseFlatCString(aMessageUri).get(),
+ mDocShell, mMsgWindow, nullptr);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::SaveAttachmentToFolder(const nsACString& contentType, const nsACString& url, const nsACString& displayName,
+ const nsACString& messageUri, nsIFile * aDestFolder, nsIFile ** aOutFile)
+{
+ NS_ENSURE_ARG_POINTER(aDestFolder);
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> attachmentDestination;
+ rv = aDestFolder->Clone(getter_AddRefs(attachmentDestination));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString unescapedFileName;
+ ConvertAndSanitizeFileName(PromiseFlatCString(displayName).get(), unescapedFileName);
+ rv = attachmentDestination->Append(unescapedFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+#ifdef XP_MACOSX
+ rv = attachmentDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE, ATTACHMENT_PERMISSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+
+ rv = SaveAttachment(attachmentDestination, url, messageUri, contentType, nullptr, nullptr);
+ attachmentDestination.swap(*aOutFile);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::SaveAttachment(const nsACString& aContentType, const nsACString& aURL,
+ const nsACString& aDisplayName, const nsACString& aMessageUri, bool aIsExternalAttachment)
+{
+ // open external attachments inside our message pane which in turn should trigger the
+ // helper app dialog...
+ if (aIsExternalAttachment)
+ return OpenURL(aURL);
+ return SaveOneAttachment(PromiseFlatCString(aContentType).get(),
+ PromiseFlatCString(aURL).get(),
+ PromiseFlatCString(aDisplayName).get(),
+ PromiseFlatCString(aMessageUri).get(),
+ false);
+}
+
+nsresult
+nsMessenger::SaveOneAttachment(const char * aContentType, const char * aURL,
+ const char * aDisplayName, const char * aMessageUri,
+ bool detaching)
+{
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ nsCOMPtr<nsIFilePicker> filePicker =
+ do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int16_t dialogResult;
+ nsCOMPtr<nsIFile> localFile;
+ nsCOMPtr<nsIFile> lastSaveDir;
+ nsCString filePath;
+ nsString saveAttachmentStr;
+ nsString defaultDisplayString;
+ ConvertAndSanitizeFileName(aDisplayName, defaultDisplayString);
+
+ GetString(NS_LITERAL_STRING("SaveAttachment"), saveAttachmentStr);
+ filePicker->Init(mWindow, saveAttachmentStr,
+ nsIFilePicker::modeSave);
+ filePicker->SetDefaultString(defaultDisplayString);
+
+ // Check if the attachment file name has an extension (which must not
+ // contain spaces) and set it as the default extension for the attachment.
+ int32_t extensionIndex = defaultDisplayString.RFindChar('.');
+ if (extensionIndex > 0 &&
+ defaultDisplayString.FindChar(' ', extensionIndex) == kNotFound)
+ {
+ nsString extension;
+ extension = Substring(defaultDisplayString, extensionIndex + 1);
+ filePicker->SetDefaultExtension(extension);
+ if (!mStringBundle)
+ {
+ rv = InitStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsString filterName;
+ const char16_t *extensionParam[] = { extension.get() };
+ rv = mStringBundle->FormatStringFromName(
+ u"saveAsType", extensionParam, 1, getter_Copies(filterName));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ extension.Insert(NS_LITERAL_STRING("*."), 0);
+ filePicker->AppendFilter(filterName, extension);
+ }
+
+ filePicker->AppendFilters(nsIFilePicker::filterAll);
+
+ rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
+ if (NS_SUCCEEDED(rv) && lastSaveDir)
+ filePicker->SetDisplayDirectory(lastSaveDir);
+
+ rv = filePicker->Show(&dialogResult);
+ if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel)
+ return rv;
+
+ rv = filePicker->GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetLastSaveDirectory(localFile);
+
+ nsCString dirName;
+ rv = localFile->GetNativePath(dirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsSaveAllAttachmentsState *saveState =
+ new nsSaveAllAttachmentsState(1,
+ &aContentType,
+ &aURL,
+ &aDisplayName,
+ &aMessageUri,
+ dirName.get(),
+ detaching);
+
+ return SaveAttachment(localFile, nsDependentCString(aURL), nsDependentCString(aMessageUri),
+ nsDependentCString(aContentType), (void *)saveState, nullptr);
+}
+
+
+NS_IMETHODIMP
+nsMessenger::SaveAllAttachments(uint32_t count,
+ const char **contentTypeArray,
+ const char **urlArray,
+ const char **displayNameArray,
+ const char **messageUriArray)
+{
+ if (!count)
+ return NS_ERROR_INVALID_ARG;
+ return SaveAllAttachments(count, contentTypeArray, urlArray, displayNameArray, messageUriArray, false);
+}
+
+nsresult
+nsMessenger::SaveAllAttachments(uint32_t count,
+ const char **contentTypeArray,
+ const char **urlArray,
+ const char **displayNameArray,
+ const char **messageUriArray,
+ bool detaching)
+{
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ nsCOMPtr<nsIFilePicker> filePicker =
+ do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ nsCOMPtr<nsIFile> localFile;
+ nsCOMPtr<nsIFile> lastSaveDir;
+ int16_t dialogResult;
+ nsString saveAttachmentStr;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ GetString(NS_LITERAL_STRING("SaveAllAttachments"), saveAttachmentStr);
+ filePicker->Init(mWindow,
+ saveAttachmentStr,
+ nsIFilePicker::modeGetFolder);
+
+ rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
+ if (NS_SUCCEEDED(rv) && lastSaveDir)
+ filePicker->SetDisplayDirectory(lastSaveDir);
+
+ rv = filePicker->Show(&dialogResult);
+ if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel)
+ return rv;
+
+ rv = filePicker->GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetLastSaveDirectory(localFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString dirName;
+ nsSaveAllAttachmentsState *saveState = nullptr;
+ rv = localFile->GetNativePath(dirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ saveState = new nsSaveAllAttachmentsState(count,
+ contentTypeArray,
+ urlArray,
+ displayNameArray,
+ messageUriArray,
+ dirName.get(),
+ detaching);
+ nsString unescapedName;
+ ConvertAndSanitizeFileName(displayNameArray[0], unescapedName);
+ rv = localFile->Append(unescapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = PromptIfFileExists(localFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SaveAttachment(localFile, nsDependentCString(urlArray[0]), nsDependentCString(messageUriArray[0]),
+ nsDependentCString(contentTypeArray[0]), (void *)saveState, nullptr);
+ return rv;
+}
+
+enum MESSENGER_SAVEAS_FILE_TYPE
+{
+ EML_FILE_TYPE = 0,
+ HTML_FILE_TYPE = 1,
+ TEXT_FILE_TYPE = 2,
+ ANY_FILE_TYPE = 3
+};
+#define HTML_FILE_EXTENSION ".htm"
+#define HTML_FILE_EXTENSION2 ".html"
+#define TEXT_FILE_EXTENSION ".txt"
+
+/**
+ * Adjust the file name, removing characters from the middle of the name if
+ * the name would otherwise be too long - too long for what file systems
+ * usually support.
+ */
+nsresult nsMessenger::AdjustFileIfNameTooLong(nsIFile* aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsAutoString path;
+ nsresult rv = aFile->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Most common file systems have a max filename length of 255. On windows, the
+ // total path length is (at least for all practical purposees) limited to 255.
+ // Let's just don't allow paths longer than that elsewhere either for
+ // simplicity.
+ uint32_t MAX = 255;
+ if (path.Length() > MAX) {
+ nsAutoString leafName;
+ rv = aFile->GetLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t pathLengthUpToLeaf = path.Length() - leafName.Length();
+ if (pathLengthUpToLeaf >= MAX - 8) { // want at least 8 chars for name
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ }
+ uint32_t x = MAX - pathLengthUpToLeaf; // x = max leaf size
+ nsAutoString truncatedLeaf;
+ truncatedLeaf.Append(Substring(leafName, 0, x/2));
+ truncatedLeaf.AppendLiteral("...");
+ truncatedLeaf.Append(Substring(leafName, leafName.Length() - x/2 + 3,
+ leafName.Length()));
+ rv = aFile->SetLeafName(truncatedLeaf);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::SaveAs(const nsACString& aURI, bool aAsFile,
+ nsIMsgIdentity *aIdentity, const nsAString& aMsgFilename,
+ bool aBypassFilePicker)
+{
+ nsCOMPtr<nsIMsgMessageService> messageService;
+ nsCOMPtr<nsIUrlListener> urlListener;
+ nsSaveMsgListener *saveListener = nullptr;
+ nsCOMPtr<nsIURI> url;
+ nsCOMPtr<nsIStreamListener> convertedListener;
+ int32_t saveAsFileType = EML_FILE_TYPE;
+
+ nsresult rv = GetMessageServiceFromURI(aURI, getter_AddRefs(messageService));
+ if (NS_FAILED(rv))
+ goto done;
+
+ if (aAsFile)
+ {
+ nsCOMPtr<nsIFile> saveAsFile;
+ // show the file picker if BypassFilePicker is not specified (null) or false
+ if (!aBypassFilePicker) {
+ rv = GetSaveAsFile(aMsgFilename, &saveAsFileType, getter_AddRefs(saveAsFile));
+ // A null saveAsFile means that the user canceled the save as
+ if (NS_FAILED(rv) || !saveAsFile)
+ goto done;
+ }
+ else {
+ saveAsFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ rv = saveAsFile->InitWithPath(aMsgFilename);
+ if (NS_FAILED(rv))
+ goto done;
+ if (StringEndsWith(aMsgFilename, NS_LITERAL_STRING(TEXT_FILE_EXTENSION),
+ nsCaseInsensitiveStringComparator()))
+ saveAsFileType = TEXT_FILE_TYPE;
+ else if ((StringEndsWith(aMsgFilename,
+ NS_LITERAL_STRING(HTML_FILE_EXTENSION),
+ nsCaseInsensitiveStringComparator())) ||
+ (StringEndsWith(aMsgFilename,
+ NS_LITERAL_STRING(HTML_FILE_EXTENSION2),
+ nsCaseInsensitiveStringComparator())))
+ saveAsFileType = HTML_FILE_TYPE;
+ else
+ saveAsFileType = EML_FILE_TYPE;
+ }
+
+ rv = AdjustFileIfNameTooLong(saveAsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = PromptIfFileExists(saveAsFile);
+ if (NS_FAILED(rv)) {
+ goto done;
+ }
+
+ // After saveListener goes out of scope, the listener will be owned by
+ // whoever the listener is registered with, usually a URL.
+ RefPtr<nsSaveMsgListener> saveListener = new nsSaveMsgListener(saveAsFile, this, nullptr);
+ if (!saveListener) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
+ if (NS_FAILED(rv))
+ goto done;
+
+ if (saveAsFileType == EML_FILE_TYPE)
+ {
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = messageService->SaveMessageToDisk(PromiseFlatCString(aURI).get(), saveAsFile, false,
+ urlListener, getter_AddRefs(dummyNull),
+ true, mMsgWindow);
+ }
+ else
+ {
+ nsAutoCString urlString(aURI);
+
+ // we can't go RFC822 to TXT until bug #1775 is fixed
+ // so until then, do the HTML to TXT conversion in
+ // nsSaveMsgListener::OnStopRequest(), see ConvertBufToPlainText()
+ //
+ // Setup the URL for a "Save As..." Operation...
+ // For now, if this is a save as TEXT operation, then do
+ // a "printing" operation
+ if (saveAsFileType == TEXT_FILE_TYPE)
+ {
+ saveListener->m_outputFormat = nsSaveMsgListener::ePlainText;
+ saveListener->m_doCharsetConversion = true;
+ urlString.AppendLiteral("?header=print");
+ }
+ else
+ {
+ saveListener->m_outputFormat = nsSaveMsgListener::eHTML;
+ saveListener->m_doCharsetConversion = false;
+ urlString.AppendLiteral("?header=saveas");
+ }
+
+ rv = CreateStartupUrl(urlString.get(), getter_AddRefs(url));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CreateStartupUrl failed");
+ if (NS_FAILED(rv))
+ goto done;
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed");
+ if (NS_FAILED(rv))
+ goto done;
+
+ saveListener->m_channel = nullptr;
+ rv = NS_NewInputStreamChannel(getter_AddRefs(saveListener->m_channel),
+ url,
+ nullptr,
+ nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewChannel failed");
+ if (NS_FAILED(rv))
+ goto done;
+
+ nsCOMPtr<nsIStreamConverterService> streamConverterService = do_GetService("@mozilla.org/streamConverters;1");
+ nsCOMPtr<nsISupports> channelSupport = do_QueryInterface(saveListener->m_channel);
+
+ // we can't go RFC822 to TXT until bug #1775 is fixed
+ // so until then, do the HTML to TXT conversion in
+ // nsSaveMsgListener::OnStopRequest(), see ConvertBufToPlainText()
+ rv = streamConverterService->AsyncConvertData(MESSAGE_RFC822,
+ TEXT_HTML,
+ saveListener,
+ channelSupport,
+ getter_AddRefs(convertedListener));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncConvertData failed");
+ if (NS_FAILED(rv))
+ goto done;
+
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = messageService->DisplayMessage(urlString.get(), convertedListener, mMsgWindow,
+ nullptr, nullptr, getter_AddRefs(dummyNull));
+ }
+ }
+ else
+ {
+ // ** save as Template
+ nsCOMPtr <nsIFile> tmpFile;
+ nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "nsmail.tmp",
+ getter_AddRefs(tmpFile));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // For temp file, we should use restrictive 00600 instead of ATTACHMENT_PERMISSION
+ rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv)) goto done;
+
+ // The saveListener is owned by whoever we ultimately register the
+ // listener with, generally a URL.
+ saveListener = new nsSaveMsgListener(tmpFile, this, nullptr);
+ if (!saveListener) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ if (aIdentity)
+ rv = aIdentity->GetStationeryFolder(saveListener->m_templateUri);
+ if (NS_FAILED(rv))
+ goto done;
+
+ bool needDummyHeader = StringBeginsWith(saveListener->m_templateUri, NS_LITERAL_CSTRING("mailbox://"));
+ bool canonicalLineEnding = StringBeginsWith(saveListener->m_templateUri, NS_LITERAL_CSTRING("imap://"));
+
+ rv = saveListener->QueryInterface(
+ NS_GET_IID(nsIUrlListener),
+ getter_AddRefs(urlListener));
+ if (NS_FAILED(rv))
+ goto done;
+
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = messageService->SaveMessageToDisk(PromiseFlatCString(aURI).get(), tmpFile,
+ needDummyHeader,
+ urlListener, getter_AddRefs(dummyNull),
+ canonicalLineEnding, mMsgWindow);
+ }
+
+done:
+ if (NS_FAILED(rv))
+ {
+ NS_IF_RELEASE(saveListener);
+ Alert("saveMessageFailed");
+ }
+ return rv;
+}
+
+nsresult
+nsMessenger::GetSaveAsFile(const nsAString& aMsgFilename, int32_t *aSaveAsFileType,
+ nsIFile **aSaveAsFile)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString saveMailAsStr;
+ GetString(NS_LITERAL_STRING("SaveMailAs"), saveMailAsStr);
+ filePicker->Init(mWindow, saveMailAsStr, nsIFilePicker::modeSave);
+
+ // if we have a non-null filename use it, otherwise use default save message one
+ if (aMsgFilename.IsEmpty())
+ {
+ nsString saveMsgStr;
+ GetString(NS_LITERAL_STRING("defaultSaveMessageAsFileName"), saveMsgStr);
+ filePicker->SetDefaultString(saveMsgStr);
+ }
+ else
+ {
+ filePicker->SetDefaultString(aMsgFilename);
+ }
+
+ // because we will be using GetFilterIndex()
+ // we must call AppendFilters() one at a time,
+ // in MESSENGER_SAVEAS_FILE_TYPE order
+ nsString emlFilesStr;
+ GetString(NS_LITERAL_STRING("EMLFiles"), emlFilesStr);
+ filePicker->AppendFilter(emlFilesStr,
+ NS_LITERAL_STRING("*.eml"));
+ filePicker->AppendFilters(nsIFilePicker::filterHTML);
+ filePicker->AppendFilters(nsIFilePicker::filterText);
+ filePicker->AppendFilters(nsIFilePicker::filterAll);
+
+ // Save as the "All Files" file type by default. We want to save as .eml by
+ // default, but the filepickers on some platforms don't switch extensions
+ // based on the file type selected (bug 508597).
+ filePicker->SetFilterIndex(ANY_FILE_TYPE);
+ // Yes, this is fine even if we ultimately save as HTML or text. On Windows,
+ // this actually is a boolean telling the file picker to automatically add
+ // the correct extension depending on the filter. On Mac or Linux this is a
+ // no-op.
+ filePicker->SetDefaultExtension(NS_LITERAL_STRING("eml"));
+
+ int16_t dialogResult;
+
+ nsCOMPtr <nsIFile> lastSaveDir;
+ rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
+ if (NS_SUCCEEDED(rv) && lastSaveDir)
+ filePicker->SetDisplayDirectory(lastSaveDir);
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = filePicker->Show(&dialogResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (dialogResult == nsIFilePicker::returnCancel)
+ {
+ // We'll indicate this by setting the outparam to null.
+ *aSaveAsFile = nullptr;
+ return NS_OK;
+ }
+
+ rv = filePicker->GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetLastSaveDirectory(localFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t selectedSaveAsFileType;
+ rv = filePicker->GetFilterIndex(&selectedSaveAsFileType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If All Files was selected, look at the extension
+ if (selectedSaveAsFileType == ANY_FILE_TYPE)
+ {
+ nsAutoString fileName;
+ rv = localFile->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (StringEndsWith(fileName, NS_LITERAL_STRING(HTML_FILE_EXTENSION),
+ nsCaseInsensitiveStringComparator()) ||
+ StringEndsWith(fileName, NS_LITERAL_STRING(HTML_FILE_EXTENSION2),
+ nsCaseInsensitiveStringComparator()))
+ *aSaveAsFileType = HTML_FILE_TYPE;
+ else if (StringEndsWith(fileName, NS_LITERAL_STRING(TEXT_FILE_EXTENSION),
+ nsCaseInsensitiveStringComparator()))
+ *aSaveAsFileType = TEXT_FILE_TYPE;
+ else
+ // The default is .eml
+ *aSaveAsFileType = EML_FILE_TYPE;
+ }
+ else
+ {
+ *aSaveAsFileType = selectedSaveAsFileType;
+ }
+
+ if (dialogResult == nsIFilePicker::returnReplace)
+ {
+ // be extra safe and only delete when the file is really a file
+ bool isFile;
+ rv = localFile->IsFile(&isFile);
+ if (NS_SUCCEEDED(rv) && isFile)
+ {
+ rv = localFile->Remove(false /* recursive delete */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ // We failed, or this isn't a file. We can't do anything about it.
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ *aSaveAsFile = nullptr;
+ localFile.swap(*aSaveAsFile);
+ return NS_OK;
+}
+
+/**
+ * Show a Save All dialog allowing the user to pick which folder to save
+ * messages to.
+ * @param [out] aSaveDir directory to save to. Will be null on cancel.
+ */
+nsresult
+nsMessenger::GetSaveToDir(nsIFile **aSaveDir)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFilePicker> filePicker =
+ do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString chooseFolderStr;
+ GetString(NS_LITERAL_STRING("ChooseFolder"), chooseFolderStr);
+ filePicker->Init(mWindow, chooseFolderStr, nsIFilePicker::modeGetFolder);
+
+ nsCOMPtr<nsIFile> lastSaveDir;
+ rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
+ if (NS_SUCCEEDED(rv) && lastSaveDir)
+ filePicker->SetDisplayDirectory(lastSaveDir);
+
+ int16_t dialogResult;
+ rv = filePicker->Show(&dialogResult);
+ if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel)
+ {
+ // We'll indicate this by setting the outparam to null.
+ *aSaveDir = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> dir;
+ rv = filePicker->GetFile(getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetLastSaveDirectory(dir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aSaveDir = nullptr;
+ dir.swap(*aSaveDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessenger::SaveMessages(uint32_t aCount,
+ const char16_t **aFilenameArray,
+ const char **aMessageUriArray)
+{
+ NS_ENSURE_ARG_MIN(aCount, 1);
+ NS_ENSURE_ARG_POINTER(aFilenameArray);
+ NS_ENSURE_ARG_POINTER(aMessageUriArray);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> saveDir;
+ rv = GetSaveToDir(getter_AddRefs(saveDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!saveDir) // A null saveDir means that the user canceled the save.
+ return NS_OK;
+
+ for (uint32_t i = 0; i < aCount; i++) {
+ if (!aFilenameArray[i]) // just to be sure
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> saveToFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = saveToFile->InitWithFile(saveDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = saveToFile->Append(nsDependentString(aFilenameArray[i]));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AdjustFileIfNameTooLong(saveToFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = PromptIfFileExists(saveToFile);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIMsgMessageService> messageService;
+ nsCOMPtr<nsIUrlListener> urlListener;
+
+ rv = GetMessageServiceFromURI(nsDependentCString(aMessageUriArray[i]),
+ getter_AddRefs(messageService));
+ if (NS_FAILED(rv)) {
+ Alert("saveMessageFailed");
+ return rv;
+ }
+
+ nsSaveMsgListener *saveListener = new nsSaveMsgListener(saveToFile, this, nullptr);
+ if (!saveListener) {
+ NS_IF_RELEASE(saveListener);
+ Alert("saveMessageFailed");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_ADDREF(saveListener);
+
+ rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener),
+ getter_AddRefs(urlListener));
+ if (NS_FAILED(rv)) {
+ NS_IF_RELEASE(saveListener);
+ Alert("saveMessageFailed");
+ return rv;
+ }
+
+ // Ok, now save the message.
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = messageService->SaveMessageToDisk(aMessageUriArray[i],
+ saveToFile, false,
+ urlListener, getter_AddRefs(dummyNull),
+ true, mMsgWindow);
+ if (NS_FAILED(rv)) {
+ NS_IF_RELEASE(saveListener);
+ Alert("saveMessageFailed");
+ return rv;
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsMessenger::Alert(const char *stringName)
+{
+ nsresult rv = NS_OK;
+
+ if (mDocShell)
+ {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
+
+ if (dialog)
+ {
+ nsString alertStr;
+ GetString(NS_ConvertASCIItoUTF16(stringName), alertStr);
+ rv = dialog->Alert(nullptr, alertStr.get());
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::MessageServiceFromURI(const nsACString& aUri, nsIMsgMessageService **aMsgService)
+{
+ NS_ENSURE_ARG_POINTER(aMsgService);
+ return GetMessageServiceFromURI(aUri, aMsgService);
+}
+
+NS_IMETHODIMP
+nsMessenger::MsgHdrFromURI(const nsACString& aUri, nsIMsgDBHdr **aMsgHdr)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ nsCOMPtr <nsIMsgMessageService> msgService;
+ nsresult rv;
+
+
+ if (mMsgWindow &&
+ (StringBeginsWith(aUri, NS_LITERAL_CSTRING("file:")) ||
+ PromiseFlatCString(aUri).Find("type=application/x-message-display") >= 0))
+ {
+ nsCOMPtr <nsIMsgHeaderSink> headerSink;
+ mMsgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink));
+ if (headerSink)
+ {
+ rv = headerSink->GetDummyMsgHeader(aMsgHdr);
+ // Is there a way to check if they're asking for the hdr currently
+ // displayed in a stand-alone msg window from a .eml file?
+ // (pretty likely if this is a file: uri)
+ return rv;
+ }
+ }
+
+ rv = GetMessageServiceFromURI(aUri, getter_AddRefs(msgService));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgService->MessageURIToMsgHdr(PromiseFlatCString(aUri).get(), aMsgHdr);
+}
+
+NS_IMETHODIMP nsMessenger::GetUndoTransactionType(uint32_t *txnType)
+{
+ NS_ENSURE_TRUE(txnType && mTxnMgr, NS_ERROR_NULL_POINTER);
+
+ nsresult rv;
+ *txnType = nsMessenger::eUnknown;
+ nsCOMPtr<nsITransaction> txn;
+ rv = mTxnMgr->PeekUndoStack(getter_AddRefs(txn));
+ if (NS_SUCCEEDED(rv) && txn)
+ {
+ nsCOMPtr <nsIPropertyBag2> propertyBag = do_QueryInterface(txn, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return propertyBag->GetPropertyAsUint32(NS_LITERAL_STRING("type"), txnType);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMessenger::CanUndo(bool *bValue)
+{
+ NS_ENSURE_TRUE(bValue && mTxnMgr, NS_ERROR_NULL_POINTER);
+
+ nsresult rv;
+ *bValue = false;
+ int32_t count = 0;
+ rv = mTxnMgr->GetNumberOfUndoItems(&count);
+ if (NS_SUCCEEDED(rv) && count > 0)
+ *bValue = true;
+ return rv;
+}
+
+NS_IMETHODIMP nsMessenger::GetRedoTransactionType(uint32_t *txnType)
+{
+ NS_ENSURE_TRUE(txnType && mTxnMgr, NS_ERROR_NULL_POINTER);
+
+ nsresult rv;
+ *txnType = nsMessenger::eUnknown;
+ nsCOMPtr<nsITransaction> txn;
+ rv = mTxnMgr->PeekRedoStack(getter_AddRefs(txn));
+ if (NS_SUCCEEDED(rv) && txn)
+ {
+ nsCOMPtr <nsIPropertyBag2> propertyBag = do_QueryInterface(txn, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return propertyBag->GetPropertyAsUint32(NS_LITERAL_STRING("type"), txnType);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMessenger::CanRedo(bool *bValue)
+{
+ NS_ENSURE_TRUE(bValue && mTxnMgr, NS_ERROR_NULL_POINTER);
+
+ nsresult rv;
+ *bValue = false;
+ int32_t count = 0;
+ rv = mTxnMgr->GetNumberOfRedoItems(&count);
+ if (NS_SUCCEEDED(rv) && count > 0)
+ *bValue = true;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::Undo(nsIMsgWindow *msgWindow)
+{
+ nsresult rv = NS_OK;
+ if (mTxnMgr)
+ {
+ int32_t numTxn = 0;
+ rv = mTxnMgr->GetNumberOfUndoItems(&numTxn);
+ if (NS_SUCCEEDED(rv) && numTxn > 0)
+ {
+ nsCOMPtr<nsITransaction> txn;
+ rv = mTxnMgr->PeekUndoStack(getter_AddRefs(txn));
+ if (NS_SUCCEEDED(rv) && txn)
+ {
+ static_cast<nsMsgTxn*>(static_cast<nsITransaction*>(txn.get()))->SetMsgWindow(msgWindow);
+ }
+ mTxnMgr->UndoTransaction();
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::Redo(nsIMsgWindow *msgWindow)
+{
+ nsresult rv = NS_OK;
+ if (mTxnMgr)
+ {
+ int32_t numTxn = 0;
+ rv = mTxnMgr->GetNumberOfRedoItems(&numTxn);
+ if (NS_SUCCEEDED(rv) && numTxn > 0)
+ {
+ nsCOMPtr<nsITransaction> txn;
+ rv = mTxnMgr->PeekRedoStack(getter_AddRefs(txn));
+ if (NS_SUCCEEDED(rv) && txn)
+ {
+ static_cast<nsMsgTxn*>(static_cast<nsITransaction*>(txn.get()))->SetMsgWindow(msgWindow);
+ }
+ mTxnMgr->RedoTransaction();
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::GetTransactionManager(nsITransactionManager* *aTxnMgr)
+{
+ NS_ENSURE_TRUE(mTxnMgr && aTxnMgr, NS_ERROR_NULL_POINTER);
+
+ *aTxnMgr = mTxnMgr;
+ NS_ADDREF(*aTxnMgr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMessenger::SetDocumentCharset(const nsACString& aCharacterSet)
+{
+ // We want to redisplay the currently selected message (if any) but forcing the
+ // redisplay to use characterSet
+ if (!mLastDisplayURI.IsEmpty())
+ {
+ SetDisplayCharset(NS_LITERAL_CSTRING("UTF-8"));
+
+ nsCOMPtr <nsIMsgMessageService> messageService;
+ nsresult rv = GetMessageServiceFromURI(mLastDisplayURI, getter_AddRefs(messageService));
+
+ if (NS_SUCCEEDED(rv) && messageService)
+ {
+ nsCOMPtr<nsIURI> dummyNull;
+ messageService->DisplayMessage(mLastDisplayURI.get(), mDocShell, mMsgWindow, nullptr,
+ PromiseFlatCString(aCharacterSet).get(), getter_AddRefs(dummyNull));
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessenger::GetLastDisplayedMessageUri(nsACString& aLastDisplayedMessageUri)
+{
+ aLastDisplayedMessageUri = mLastDisplayURI;
+ return NS_OK;
+}
+
+nsSaveMsgListener::nsSaveMsgListener(nsIFile* aFile, nsMessenger *aMessenger, nsIUrlListener *aListener)
+{
+ m_file = do_QueryInterface(aFile);
+ m_messenger = aMessenger;
+ mListener = aListener;
+ mUrlHasStopped = false;
+ mRequestHasStopped = false;
+
+ // rhp: for charset handling
+ m_doCharsetConversion = false;
+ m_saveAllAttachmentsState = nullptr;
+ mProgress = 0;
+ mMaxProgress = -1;
+ mCanceled = false;
+ m_outputFormat = eUnknown;
+ mInitialized = false;
+}
+
+nsSaveMsgListener::~nsSaveMsgListener()
+{
+}
+
+//
+// nsISupports
+//
+NS_IMPL_ISUPPORTS(nsSaveMsgListener,
+ nsIUrlListener,
+ nsIMsgCopyServiceListener,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsICancelable)
+
+NS_IMETHODIMP
+nsSaveMsgListener::Cancel(nsresult status)
+{
+ mCanceled = true;
+ return NS_OK;
+}
+
+//
+// nsIUrlListener
+//
+NS_IMETHODIMP
+nsSaveMsgListener::OnStartRunningUrl(nsIURI* url)
+{
+ if (mListener)
+ mListener->OnStartRunningUrl(url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnStopRunningUrl(nsIURI *url, nsresult exitCode)
+{
+ nsresult rv = exitCode;
+ mUrlHasStopped = true;
+
+ // ** save as template goes here
+ if (!m_templateUri.IsEmpty())
+ {
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ if (NS_FAILED(rv)) goto done;
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(m_templateUri, getter_AddRefs(res));
+ if (NS_FAILED(rv)) goto done;
+ nsCOMPtr<nsIMsgFolder> templateFolder;
+ templateFolder = do_QueryInterface(res, &rv);
+ if (NS_FAILED(rv)) goto done;
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID);
+ if (copyService)
+ {
+ nsCOMPtr<nsIFile> clone;
+ m_file->Clone(getter_AddRefs(clone));
+ rv = copyService->CopyFileMessage(clone, templateFolder, nullptr,
+ true, nsMsgMessageFlags::Read,
+ EmptyCString(), this, nullptr);
+ // Clear this so we don't end up in a loop if OnStopRunningUrl gets
+ // called again.
+ m_templateUri.Truncate();
+ }
+ }
+ else if (m_outputStream && mRequestHasStopped)
+ {
+ m_outputStream->Close();
+ m_outputStream = nullptr;
+ }
+
+done:
+ if (NS_FAILED(rv))
+ {
+ if (m_file)
+ m_file->Remove(false);
+ if (m_messenger)
+ m_messenger->Alert("saveMessageFailed");
+ }
+
+ if (mRequestHasStopped && mListener)
+ mListener->OnStopRunningUrl(url, exitCode);
+ else
+ mListenerUri = url;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnStartCopy(void)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::SetMessageKey(nsMsgKey aKey)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::GetMessageId(nsACString& aMessageId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnStopCopy(nsresult aStatus)
+{
+ if (m_file)
+ m_file->Remove(false);
+ return aStatus;
+}
+
+// initializes the progress window if we are going to show one
+// and for OSX, sets creator flags on the output file
+nsresult nsSaveMsgListener::InitializeDownload(nsIRequest * aRequest)
+{
+ nsresult rv = NS_OK;
+
+ mInitialized = true;
+ nsCOMPtr<nsIChannel> channel (do_QueryInterface(aRequest));
+
+ if (!channel)
+ return rv;
+
+ // Get the max progress from the URL if we haven't already got it.
+ if (mMaxProgress == -1)
+ {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(uri));
+ if (mailnewsUrl)
+ mailnewsUrl->GetMaxProgress(&mMaxProgress);
+ }
+
+ if (!m_contentType.IsEmpty())
+ {
+ nsCOMPtr<nsIMIMEService> mimeService (do_GetService(NS_MIMESERVICE_CONTRACTID));
+ nsCOMPtr<nsIMIMEInfo> mimeinfo;
+
+ mimeService->GetFromTypeAndExtension(m_contentType, EmptyCString(), getter_AddRefs(mimeinfo));
+
+ // create a download progress window
+
+ // Set saveToDisk explicitly to avoid launching the saved file.
+ // See http://hg.mozilla.org/mozilla-central/file/814a6f071472/toolkit/components/jsdownloads/src/DownloadLegacy.js#l164
+ mimeinfo->SetPreferredAction(nsIHandlerInfo::saveToDisk);
+
+ // When we don't allow warnings, also don't show progress, as this
+ // is an environment (typically filters) where we don't want
+ // interruption.
+ bool allowProgress = true;
+ if (m_saveAllAttachmentsState)
+ allowProgress = !m_saveAllAttachmentsState->m_withoutWarning;
+ if (allowProgress)
+ {
+ nsCOMPtr<nsITransfer> tr = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
+ if (tr && m_file)
+ {
+ PRTime timeDownloadStarted = PR_Now();
+
+ nsCOMPtr<nsIURI> outputURI;
+ NS_NewFileURI(getter_AddRefs(outputURI), m_file);
+
+ nsCOMPtr<nsIURI> url;
+ channel->GetURI(getter_AddRefs(url));
+ rv = tr->Init(url, outputURI, EmptyString(), mimeinfo,
+ timeDownloadStarted, nullptr, this, false);
+
+ // now store the web progresslistener
+ mTransfer = tr;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnStartRequest(nsIRequest* request, nsISupports* aSupport)
+{
+ if (m_file)
+ MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream), m_file, -1, ATTACHMENT_PERMISSION);
+ if (!m_outputStream)
+ {
+ mCanceled = true;
+ if (m_messenger)
+ m_messenger->Alert("saveAttachmentFailed");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnStopRequest(nsIRequest* request, nsISupports* aSupport,
+ nsresult status)
+{
+ nsresult rv = NS_OK;
+ mRequestHasStopped = true;
+
+ // rhp: If we are doing the charset conversion magic, this is different
+ // processing, otherwise, its just business as usual.
+ // If we need text/plain, then we need to convert the HTML and then convert
+ // to the systems charset.
+ if (m_doCharsetConversion && m_outputStream)
+ {
+ // For HTML, code is emitted immediately in OnDataAvailable.
+ MOZ_ASSERT(m_outputFormat == ePlainText,
+ "For HTML, m_doCharsetConversion shouldn't be set");
+ NS_ConvertUTF8toUTF16 utf16Buffer(m_msgBuffer);
+ ConvertBufToPlainText(utf16Buffer, false, false, false, false);
+
+ nsCString outCString;
+ rv = nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(),
+ utf16Buffer, outCString, false, true);
+ if (rv == NS_ERROR_UENC_NOMAPPING) {
+ // If we can't encode with the preferred charset, use UTF-8.
+ CopyUTF16toUTF8(utf16Buffer, outCString);
+ rv = NS_OK;
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t writeCount;
+ rv = m_outputStream->Write(outCString.get(), outCString.Length(), &writeCount);
+ if (outCString.Length() != writeCount)
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+
+ if (m_outputStream)
+ {
+ m_outputStream->Close();
+ m_outputStream = nullptr;
+ }
+
+ if (m_saveAllAttachmentsState)
+ {
+ m_saveAllAttachmentsState->m_curIndex++;
+ if (!mCanceled && m_saveAllAttachmentsState->m_curIndex < m_saveAllAttachmentsState->m_count)
+ {
+ nsSaveAllAttachmentsState *state = m_saveAllAttachmentsState;
+ uint32_t i = state->m_curIndex;
+ nsString unescapedName;
+ nsCOMPtr<nsIFile> localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) goto done;
+ rv = localFile->InitWithNativePath(nsDependentCString(state->m_directoryName));
+
+ if (NS_FAILED(rv)) goto done;
+
+ ConvertAndSanitizeFileName(state->m_displayNameArray[i], unescapedName);
+ rv = localFile->Append(unescapedName);
+ if (NS_FAILED(rv))
+ goto done;
+
+ // When we are running with no warnings (typically filters and other automatic
+ // uses), then don't prompt for duplicates, but create a unique file
+ // instead.
+ if (!m_saveAllAttachmentsState->m_withoutWarning)
+ {
+ rv = m_messenger->PromptIfFileExists(localFile);
+ if (NS_FAILED(rv)) goto done;
+ }
+ else
+ {
+ rv = localFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, ATTACHMENT_PERMISSION);
+ if (NS_FAILED(rv)) goto done;
+ }
+ rv = m_messenger->SaveAttachment(localFile,
+ nsDependentCString(state->m_urlArray[i]),
+ nsDependentCString(state->m_messageUriArray[i]),
+ nsDependentCString(state->m_contentTypeArray[i]),
+ (void *)state, nullptr);
+ done:
+ if (NS_FAILED(rv))
+ {
+ delete state;
+ m_saveAllAttachmentsState = nullptr;
+ }
+ }
+ else
+ {
+ // check if we're saving attachments prior to detaching them.
+ if (m_saveAllAttachmentsState->m_detachingAttachments && !mCanceled)
+ {
+ nsSaveAllAttachmentsState *state = m_saveAllAttachmentsState;
+ m_messenger->DetachAttachments(state->m_count,
+ (const char **) state->m_contentTypeArray,
+ (const char **) state->m_urlArray,
+ (const char **) state->m_displayNameArray,
+ (const char **) state->m_messageUriArray,
+ &state->m_savedFiles,
+ state->m_withoutWarning);
+ }
+
+ delete m_saveAllAttachmentsState;
+ m_saveAllAttachmentsState = nullptr;
+ }
+ }
+
+ if(mTransfer)
+ {
+ mTransfer->OnProgressChange64(nullptr, nullptr, mMaxProgress, mMaxProgress, mMaxProgress, mMaxProgress);
+ mTransfer->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
+ mTransfer = nullptr; // break any circular dependencies between the progress dialog and use
+ }
+
+ if (mUrlHasStopped && mListener)
+ mListener->OnStopRunningUrl(mListenerUri, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnDataAvailable(nsIRequest* request,
+ nsISupports* aSupport,
+ nsIInputStream* inStream,
+ uint64_t srcOffset,
+ uint32_t count)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ // first, check to see if we've been canceled....
+ if (mCanceled) // then go cancel our underlying channel too
+ return request->Cancel(NS_BINDING_ABORTED);
+
+ if (!mInitialized)
+ InitializeDownload(request);
+
+ if (m_outputStream)
+ {
+ mProgress += count;
+ uint64_t available;
+ uint32_t readCount, maxReadCount = sizeof(m_dataBuffer);
+ uint32_t writeCount;
+ rv = inStream->Available(&available);
+ while (NS_SUCCEEDED(rv) && available)
+ {
+ if (maxReadCount > available)
+ maxReadCount = (uint32_t)available;
+ rv = inStream->Read(m_dataBuffer, maxReadCount, &readCount);
+
+ // rhp:
+ // Ok, now we do one of two things. If we are sending out HTML, then
+ // just write it to the HTML stream as it comes along...but if this is
+ // a save as TEXT operation, we need to buffer this up for conversion
+ // when we are done. When the stream converter for HTML-TEXT gets in place,
+ // this magic can go away.
+ //
+ if (NS_SUCCEEDED(rv))
+ {
+ if ( (m_doCharsetConversion) && (m_outputFormat == ePlainText) )
+ m_msgBuffer.Append(Substring(m_dataBuffer, m_dataBuffer + readCount));
+ else
+ rv = m_outputStream->Write(m_dataBuffer, readCount, &writeCount);
+
+ available -= readCount;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && mTransfer) // Send progress notification.
+ mTransfer->OnProgressChange64(nullptr, request, mProgress, mMaxProgress, mProgress, mMaxProgress);
+ }
+ return rv;
+}
+
+#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
+
+nsresult
+nsMessenger::InitStringBundle()
+{
+ if (mStringBundle)
+ return NS_OK;
+
+ const char propertyURL[] = MESSENGER_STRING_URL;
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+ return sBundleService->CreateBundle(propertyURL,
+ getter_AddRefs(mStringBundle));
+}
+
+void
+nsMessenger::GetString(const nsString& aStringName, nsString& aValue)
+{
+ nsresult rv;
+ aValue.Truncate();
+
+ if (!mStringBundle)
+ rv = InitStringBundle();
+
+ if (mStringBundle)
+ rv = mStringBundle->GetStringFromName(aStringName.get(), getter_Copies(aValue));
+ else
+ rv = NS_ERROR_FAILURE;
+
+ if (NS_FAILED(rv) || aValue.IsEmpty())
+ aValue = aStringName;
+ return;
+}
+
+nsSaveAllAttachmentsState::nsSaveAllAttachmentsState(uint32_t count,
+ const char **contentTypeArray,
+ const char **urlArray,
+ const char **nameArray,
+ const char **uriArray,
+ const char *dirName,
+ bool detachingAttachments)
+ : m_withoutWarning(false)
+{
+ uint32_t i;
+ NS_ASSERTION(count && urlArray && nameArray && uriArray && dirName,
+ "fatal - invalid parameters\n");
+
+ m_count = count;
+ m_curIndex = 0;
+ m_contentTypeArray = new char*[count];
+ m_urlArray = new char*[count];
+ m_displayNameArray = new char*[count];
+ m_messageUriArray = new char*[count];
+ for (i = 0; i < count; i++)
+ {
+ m_contentTypeArray[i] = strdup(contentTypeArray[i]);
+ m_urlArray[i] = strdup(urlArray[i]);
+ m_displayNameArray[i] = strdup(nameArray[i]);
+ m_messageUriArray[i] = strdup(uriArray[i]);
+ }
+ m_directoryName = strdup(dirName);
+ m_detachingAttachments = detachingAttachments;
+}
+
+nsSaveAllAttachmentsState::~nsSaveAllAttachmentsState()
+{
+ uint32_t i;
+ for (i = 0; i < m_count; i++)
+ {
+ NS_Free(m_contentTypeArray[i]);
+ NS_Free(m_urlArray[i]);
+ NS_Free(m_displayNameArray[i]);
+ NS_Free(m_messageUriArray[i]);
+ }
+ delete[] m_contentTypeArray;
+ delete[] m_urlArray;
+ delete[] m_displayNameArray;
+ delete[] m_messageUriArray;
+ NS_Free(m_directoryName);
+}
+
+nsresult
+nsMessenger::GetLastSaveDirectory(nsIFile **aLastSaveDir)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // this can fail, and it will, on the first time we call it, as there is no default for this pref.
+ nsCOMPtr <nsIFile> localFile;
+ rv = prefBranch->GetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME, NS_GET_IID(nsIFile), getter_AddRefs(localFile));
+ if (NS_SUCCEEDED(rv)) {
+ NS_IF_ADDREF(*aLastSaveDir = localFile);
+ }
+ return rv;
+}
+
+nsresult
+nsMessenger::SetLastSaveDirectory(nsIFile *aLocalFile)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIFile> file = do_QueryInterface(aLocalFile, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if the file is a directory, just use it for the last dir chosen
+ // otherwise, use the parent of the file as the last dir chosen.
+ // IsDirectory() will return error on saving a file, as the
+ // file doesn't exist yet.
+ bool isDirectory;
+ rv = file->IsDirectory(&isDirectory);
+ if (NS_SUCCEEDED(rv) && isDirectory) {
+ rv = prefBranch->SetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME, NS_GET_IID(nsIFile), aLocalFile);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else {
+ nsCOMPtr <nsIFile> parent;
+ rv = file->GetParent(getter_AddRefs(parent));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = prefBranch->SetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME, NS_GET_IID(nsIFile), parent);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ return NS_OK;
+}
+
+/* void getUrisAtNavigatePos (in long aPos, out ACString aFolderUri, out ACString aMsgUri); */
+// aPos is relative to the current history cursor - 1 is forward, -1 is back.
+NS_IMETHODIMP nsMessenger::GetMsgUriAtNavigatePos(int32_t aPos, nsACString& aMsgUri)
+{
+ int32_t desiredArrayIndex = (mCurHistoryPos + (aPos << 1));
+ if (desiredArrayIndex >= 0 && desiredArrayIndex < (int32_t)mLoadedMsgHistory.Length())
+ {
+ mNavigatingToUri = mLoadedMsgHistory[desiredArrayIndex];
+ aMsgUri = mNavigatingToUri;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMessenger::SetNavigatePos(int32_t aPos)
+{
+ if ((aPos << 1) < (int32_t)mLoadedMsgHistory.Length())
+ {
+ mCurHistoryPos = aPos << 1;
+ return NS_OK;
+ }
+ else
+ return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP nsMessenger::GetNavigatePos(int32_t *aPos)
+{
+ NS_ENSURE_ARG_POINTER(aPos);
+ *aPos = mCurHistoryPos >> 1;
+ return NS_OK;
+}
+
+// aPos is relative to the current history cursor - 1 is forward, -1 is back.
+NS_IMETHODIMP nsMessenger::GetFolderUriAtNavigatePos(int32_t aPos, nsACString& aFolderUri)
+{
+ int32_t desiredArrayIndex = (mCurHistoryPos + (aPos << 1));
+ if (desiredArrayIndex >= 0 && desiredArrayIndex < (int32_t)mLoadedMsgHistory.Length())
+ {
+ mNavigatingToUri = mLoadedMsgHistory[desiredArrayIndex + 1];
+ aFolderUri = mNavigatingToUri;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMessenger::GetNavigateHistory(uint32_t *aCurPos, uint32_t *aCount, char *** aHistoryUris)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+ NS_ENSURE_ARG_POINTER(aCurPos);
+
+ *aCurPos = mCurHistoryPos >> 1;
+ *aCount = mLoadedMsgHistory.Length();
+ // for just enabling commands, we don't need the history uris.
+ if (!aHistoryUris)
+ return NS_OK;
+
+ char **outArray, **next;
+ next = outArray = (char **)moz_xmalloc(*aCount * sizeof(char *));
+ if (!outArray) return NS_ERROR_OUT_OF_MEMORY;
+ for (uint32_t i = 0; i < *aCount; i++)
+ {
+ *next = ToNewCString(mLoadedMsgHistory[i]);
+ if (!*next)
+ return NS_ERROR_OUT_OF_MEMORY;
+ next++;
+ }
+ *aHistoryUris = outArray;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessenger::FormatFileSize(uint64_t aSize, bool aUseKB, nsAString& aFormattedSize)
+{
+ return ::FormatFileSize(aSize, aUseKB, aFormattedSize);
+}
+
+
+/* void OnItemAdded (in nsIMsgFolder parentItem, in nsISupports item); */
+NS_IMETHODIMP nsMessenger::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnItemRemoved (in nsIMsgFolder parentItem, in nsISupports item); */
+NS_IMETHODIMP nsMessenger::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ // check if this item is a message header that's in our history list. If so,
+ // remove it from the history list.
+ nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryInterface(item);
+ if (msgHdr)
+ {
+ nsCOMPtr <nsIMsgFolder> folder;
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ if (folder)
+ {
+ nsCString msgUri;
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ folder->GenerateMessageURI(msgKey, msgUri);
+ // need to remove the correspnding folder entry, and
+ // adjust the current history pos.
+ size_t uriPos = mLoadedMsgHistory.IndexOf(msgUri);
+ if (uriPos != mLoadedMsgHistory.NoIndex)
+ {
+ mLoadedMsgHistory.RemoveElementAt(uriPos);
+ mLoadedMsgHistory.RemoveElementAt(uriPos); // and the folder uri entry
+ if (mCurHistoryPos >= (int32_t)uriPos)
+ mCurHistoryPos -= 2;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+/* void OnItemPropertyChanged (in nsIMsgFolder item, in nsIAtom property, in string oldValue, in string newValue); */
+NS_IMETHODIMP nsMessenger::OnItemPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char *oldValue, const char *newValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnItemIntPropertyChanged (in nsIMsgFolder item, in nsIAtom property, in long long oldValue, in long long newValue); */
+NS_IMETHODIMP nsMessenger::OnItemIntPropertyChanged(nsIMsgFolder *item, nsIAtom *property, int64_t oldValue, int64_t newValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnItemBoolPropertyChanged (in nsIMsgFolder item, in nsIAtom property, in boolean oldValue, in boolean newValue); */
+NS_IMETHODIMP nsMessenger::OnItemBoolPropertyChanged(nsIMsgFolder *item, nsIAtom *property, bool oldValue, bool newValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnItemUnicharPropertyChanged (in nsIMsgFolder item, in nsIAtom property, in wstring oldValue, in wstring newValue); */
+NS_IMETHODIMP nsMessenger::OnItemUnicharPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char16_t *oldValue, const char16_t *newValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnItemPropertyFlagChanged (in nsIMsgDBHdr item, in nsIAtom property, in unsigned long oldFlag, in unsigned long newFlag); */
+NS_IMETHODIMP nsMessenger::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnItemEvent (in nsIMsgFolder item, in nsIAtom event); */
+NS_IMETHODIMP nsMessenger::OnItemEvent(nsIMsgFolder *item, nsIAtom *event)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Detach/Delete Attachments
+///////////////////////////////////////////////////////////////////////////////
+
+static const char * GetAttachmentPartId(const char * aAttachmentUrl)
+{
+ static const char partIdPrefix[] = "part=";
+ const char * partId = PL_strstr(aAttachmentUrl, partIdPrefix);
+ return partId ? (partId + sizeof(partIdPrefix) - 1) : nullptr;
+}
+
+static int CompareAttachmentPartId(const char * aAttachUrlLeft, const char * aAttachUrlRight)
+{
+ // part ids are numbers separated by periods, like "1.2.3.4".
+ // we sort by doing a numerical comparison on each item in turn. e.g. "1.4" < "1.25"
+ // shorter entries come before longer entries. e.g. "1.4" < "1.4.1.2"
+ // return values:
+ // -2 left is a parent of right
+ // -1 left is less than right
+ // 0 left == right
+ // 1 right is greater than left
+ // 2 right is a parent of left
+
+ const char * partIdLeft = GetAttachmentPartId(aAttachUrlLeft);
+ const char * partIdRight = GetAttachmentPartId(aAttachUrlRight);
+
+ // for detached attachments the URL does not contain any "part=xx"
+ if(!partIdLeft)
+ partIdLeft = "0";
+
+ if(!partIdRight)
+ partIdRight = "0";
+
+ long idLeft, idRight;
+ do
+ {
+ MOZ_ASSERT(partIdLeft && IS_DIGIT(*partIdLeft), "Invalid character in part id string");
+ MOZ_ASSERT(partIdRight && IS_DIGIT(*partIdRight), "Invalid character in part id string");
+
+ // if the part numbers are different then the numerically smaller one is first
+ char *fixConstLoss;
+ idLeft = strtol(partIdLeft, &fixConstLoss, 10);
+ partIdLeft = fixConstLoss;
+ idRight = strtol(partIdRight, &fixConstLoss, 10);
+ partIdRight = fixConstLoss;
+ if (idLeft != idRight)
+ return idLeft < idRight ? -1 : 1;
+
+ // if one part id is complete but the other isn't, then the shortest one
+ // is first (parents before children)
+ if (*partIdLeft != *partIdRight)
+ return *partIdRight ? -2 : 2;
+
+ // if both part ids are complete (*partIdLeft == *partIdRight now) then
+ // they are equal
+ if (!*partIdLeft)
+ return 0;
+
+ MOZ_ASSERT(*partIdLeft == '.', "Invalid character in part id string");
+ MOZ_ASSERT(*partIdRight == '.', "Invalid character in part id string");
+
+ ++partIdLeft;
+ ++partIdRight;
+ }
+ while (true);
+ return 0;
+}
+
+// ------------------------------------
+
+// struct on purpose -> show that we don't ever want a vtable
+struct msgAttachment
+{
+ msgAttachment()
+ : mContentType(nullptr),
+ mUrl(nullptr),
+ mDisplayName(nullptr),
+ mMessageUri(nullptr)
+ {
+ }
+
+ ~msgAttachment()
+ {
+ Clear();
+ }
+
+ void Clear()
+ {
+ NS_Free(mContentType);
+ NS_Free(mUrl);
+ NS_Free(mDisplayName);
+ NS_Free(mMessageUri);
+ }
+
+ bool Init(const char * aContentType, const char * aUrl,
+ const char * aDisplayName, const char * aMessageUri)
+ {
+ Clear();
+ mContentType = strdup(aContentType);
+ mUrl = strdup(aUrl);
+ mDisplayName = strdup(aDisplayName);
+ mMessageUri = strdup(aMessageUri);
+ return (mContentType && mUrl && mDisplayName && mMessageUri);
+ }
+
+ // take the pointers from aSource
+ void Adopt(msgAttachment & aSource)
+ {
+ Clear();
+
+ mContentType = aSource.mContentType;
+ mUrl = aSource.mUrl;
+ mDisplayName = aSource.mDisplayName;
+ mMessageUri = aSource.mMessageUri;
+
+ aSource.mContentType = nullptr;
+ aSource.mUrl = nullptr;
+ aSource.mDisplayName = nullptr;
+ aSource.mMessageUri = nullptr;
+ }
+
+ char* mContentType;
+ char* mUrl;
+ char* mDisplayName;
+ char* mMessageUri;
+
+private:
+ // disable by not implementing
+ msgAttachment(const msgAttachment & rhs);
+ msgAttachment & operator=(const msgAttachment & rhs);
+};
+
+// ------------------------------------
+
+class nsAttachmentState
+{
+public:
+ nsAttachmentState();
+ ~nsAttachmentState();
+ nsresult Init(uint32_t aCount,
+ const char **aContentTypeArray,
+ const char **aUrlArray,
+ const char **aDisplayNameArray,
+ const char **aMessageUriArray);
+ nsresult PrepareForAttachmentDelete();
+
+private:
+ static int SortAttachmentsByPartId(const void * aLeft, const void * aRight, void *);
+
+public:
+ uint32_t mCount;
+ uint32_t mCurIndex;
+ msgAttachment* mAttachmentArray;
+};
+
+nsAttachmentState::nsAttachmentState()
+ : mCount(0),
+ mCurIndex(0),
+ mAttachmentArray(nullptr)
+{
+}
+
+nsAttachmentState::~nsAttachmentState()
+{
+ delete[] mAttachmentArray;
+}
+
+nsresult
+nsAttachmentState::Init(uint32_t aCount, const char ** aContentTypeArray,
+ const char ** aUrlArray, const char ** aDisplayNameArray,
+ const char ** aMessageUriArray)
+{
+ MOZ_ASSERT(aCount > 0, "count is invalid");
+
+ mCount = aCount;
+ mCurIndex = 0;
+ delete[] mAttachmentArray;
+
+ mAttachmentArray = new msgAttachment[aCount];
+ if (!mAttachmentArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ for(uint32_t u = 0; u < aCount; ++u)
+ {
+ if (!mAttachmentArray[u].Init(aContentTypeArray[u], aUrlArray[u],
+ aDisplayNameArray[u], aMessageUriArray[u]))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAttachmentState::PrepareForAttachmentDelete()
+{
+ // this must be called before any processing
+ if (mCurIndex != 0)
+ return NS_ERROR_FAILURE;
+
+ // this prepares the attachment list for use in deletion. In order to prepare, we
+ // sort the attachments in numerical ascending order on their part id, remove all
+ // duplicates and remove any subparts which will be removed automatically by the
+ // removal of the parent.
+ //
+ // e.g. the attachment list processing (showing only part ids)
+ // before: 1.11, 1.3, 1.2, 1.2.1.3, 1.4.1.2
+ // sorted: 1.2, 1.2.1.3, 1.3, 1.4.1.2, 1.11
+ // after: 1.2, 1.3, 1.4.1.2, 1.11
+
+ // sort
+ NS_QuickSort(mAttachmentArray, mCount, sizeof(msgAttachment), SortAttachmentsByPartId, nullptr);
+
+ // remove duplicates and sub-items
+ int nCompare;
+ for(uint32_t u = 1; u < mCount;)
+ {
+ nCompare = ::CompareAttachmentPartId(mAttachmentArray[u-1].mUrl, mAttachmentArray[u].mUrl);
+ if (nCompare == 0 || nCompare == -2) // [u-1] is the same as or a parent of [u]
+ {
+ // shuffle the array down (and thus keeping the sorted order)
+ // this will get rid of the current unnecessary element
+ for (uint32_t i = u + 1; i < mCount; ++i)
+ {
+ mAttachmentArray[i-1].Adopt(mAttachmentArray[i]);
+ }
+ --mCount;
+ }
+ else
+ {
+ ++u;
+ }
+ }
+
+ return NS_OK;
+}
+
+int
+nsAttachmentState::SortAttachmentsByPartId(const void * aLeft, const void * aRight, void *)
+{
+ msgAttachment & attachLeft = *((msgAttachment*) aLeft);
+ msgAttachment & attachRight = *((msgAttachment*) aRight);
+ return ::CompareAttachmentPartId(attachLeft.mUrl, attachRight.mUrl);
+}
+
+// ------------------------------------
+
+class nsDelAttachListener : public nsIStreamListener,
+ public nsIUrlListener,
+ public nsIMsgCopyServiceListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+public:
+ nsDelAttachListener();
+ nsresult StartProcessing(nsMessenger * aMessenger, nsIMsgWindow * aMsgWindow,
+ nsAttachmentState * aAttach, bool aSaveFirst);
+ nsresult DeleteOriginalMessage();
+ void SelectNewMessage();
+
+public:
+ nsAttachmentState * mAttach; // list of attachments to process
+ bool mSaveFirst; // detach (true) or delete (false)
+ nsCOMPtr<nsIFile> mMsgFile; // temporary file (processed mail)
+ nsCOMPtr<nsIOutputStream> mMsgFileStream; // temporary file (processed mail)
+ nsCOMPtr<nsIMsgMessageService> mMessageService; // original message service
+ nsCOMPtr<nsIMsgDBHdr> mOriginalMessage; // original message header
+ nsCOMPtr<nsIMsgFolder> mMessageFolder; // original message folder
+ nsCOMPtr<nsIMessenger> mMessenger; // our messenger instance
+ nsCOMPtr<nsIMsgWindow> mMsgWindow; // our UI window
+ nsMsgKey mNewMessageKey; // new message key
+ uint32_t mOrigMsgFlags;
+
+
+ enum {
+ eStarting,
+ eCopyingNewMsg,
+ eUpdatingFolder, // for IMAP
+ eDeletingOldMessage,
+ eSelectingNewMessage
+ } m_state;
+ // temp
+ bool mWrittenExtra;
+ bool mDetaching;
+ nsTArray<nsCString> mDetachedFileUris;
+
+private:
+ virtual ~nsDelAttachListener();
+};
+
+//
+// nsISupports
+//
+NS_IMPL_ISUPPORTS(nsDelAttachListener,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIUrlListener,
+ nsIMsgCopyServiceListener)
+
+//
+// nsIRequestObserver
+//
+NS_IMETHODIMP
+nsDelAttachListener::OnStartRequest(nsIRequest * aRequest, nsISupports * aContext)
+{
+ // called when we start processing the StreamMessage request.
+ // This is called after OnStartRunningUrl().
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDelAttachListener::OnStopRequest(nsIRequest * aRequest, nsISupports * aContext, nsresult aStatusCode)
+{
+ // called when we have completed processing the StreamMessage request.
+ // This is called after OnStopRequest(). This means that we have now
+ // received all data of the message and we have completed processing.
+ // We now start to copy the processed message from the temporary file
+ // back into the message store, replacing the original message.
+
+ mMessageFolder->CopyDataDone();
+ if (NS_FAILED(aStatusCode))
+ return aStatusCode;
+
+ // called when we complete processing of the StreamMessage request.
+ // This is called before OnStopRunningUrl().
+ nsresult rv;
+
+ // copy the file back into the folder. Note: setting msgToReplace only copies
+ // metadata, so we do the delete ourselves
+ nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService;
+ rv = this->QueryInterface( NS_GET_IID(nsIMsgCopyServiceListener), getter_AddRefs(listenerCopyService) );
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ mMsgFileStream->Close();
+ mMsgFileStream = nullptr;
+ mNewMessageKey = nsMsgKey_None;
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID);
+ m_state = eCopyingNewMsg;
+ // clone file because nsIFile on Windows caches the wrong file size.
+ nsCOMPtr <nsIFile> clone;
+ mMsgFile->Clone(getter_AddRefs(clone));
+ if (copyService)
+ {
+ nsCString originalKeys;
+ mOriginalMessage->GetStringProperty("keywords", getter_Copies(originalKeys));
+ rv = copyService->CopyFileMessage(clone, mMessageFolder, mOriginalMessage, false,
+ mOrigMsgFlags, originalKeys, listenerCopyService, mMsgWindow);
+ }
+ return rv;
+}
+
+//
+// nsIStreamListener
+//
+
+NS_IMETHODIMP
+nsDelAttachListener::OnDataAvailable(nsIRequest * aRequest, nsISupports * aSupport,
+ nsIInputStream * aInStream, uint64_t aSrcOffset,
+ uint32_t aCount)
+{
+ if (!mMsgFileStream)
+ return NS_ERROR_NULL_POINTER;
+ return mMessageFolder->CopyDataToOutputStreamForAppend(aInStream, aCount, mMsgFileStream);
+}
+
+//
+// nsIUrlListener
+//
+
+NS_IMETHODIMP
+nsDelAttachListener::OnStartRunningUrl(nsIURI * aUrl)
+{
+ // called when we start processing the StreamMessage request. This is
+ // called before OnStartRequest().
+ return NS_OK;
+}
+
+nsresult nsDelAttachListener::DeleteOriginalMessage()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = messageArray->AppendElement(mOriginalMessage, false);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService;
+
+ QueryInterface( NS_GET_IID(nsIMsgCopyServiceListener), getter_AddRefs(listenerCopyService) );
+
+ mOriginalMessage = nullptr;
+ m_state = eDeletingOldMessage;
+ return mMessageFolder->DeleteMessages(
+ messageArray, // messages
+ mMsgWindow, // msgWindow
+ true, // deleteStorage
+ false, // isMove
+ listenerCopyService, // listener
+ false); // allowUndo
+}
+
+void nsDelAttachListener::SelectNewMessage()
+{
+ nsCString displayUri;
+ // all attachments refer to the same message
+ const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri;
+ mMessenger->GetLastDisplayedMessageUri(displayUri);
+ if (displayUri.Equals(messageUri))
+ {
+ mMessageFolder->GenerateMessageURI(mNewMessageKey, displayUri);
+ if (!displayUri.IsEmpty() && mMsgWindow)
+ {
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ mMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectMessage(displayUri);
+ }
+ }
+ mNewMessageKey = nsMsgKey_None;
+}
+
+NS_IMETHODIMP
+nsDelAttachListener::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode)
+{
+ nsresult rv = NS_OK;
+ const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri;
+ if (mOriginalMessage && !strncmp(messageUri, "imap-message:", 13))
+ {
+ if (m_state == eUpdatingFolder)
+ rv = DeleteOriginalMessage();
+ }
+ // check if we've deleted the original message, and we know the new msg id.
+ else if (m_state == eDeletingOldMessage && mMsgWindow)
+ SelectNewMessage();
+
+ return rv;
+}
+
+//
+// nsIMsgCopyServiceListener
+//
+
+NS_IMETHODIMP
+nsDelAttachListener::OnStartCopy(void)
+{
+ // never called?
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDelAttachListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
+{
+ // never called?
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDelAttachListener::SetMessageKey(nsMsgKey aKey)
+{
+ // called during the copy of the modified message back into the message
+ // store to notify us of the message key of the newly created message.
+ mNewMessageKey = aKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDelAttachListener::GetMessageId(nsACString& aMessageId)
+{
+ // never called?
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDelAttachListener::OnStopCopy(nsresult aStatus)
+{
+ // only if the currently selected message is the one that we are about to delete then we
+ // change the selection to the new message that we just added. Failures in this code are not fatal.
+ // Note that can only do this if we have the new message key, which we don't always get from IMAP.
+ // delete the original message
+ if (NS_FAILED(aStatus))
+ return aStatus;
+
+ // check if we've deleted the original message, and we know the new msg id.
+ if (m_state == eDeletingOldMessage && mMsgWindow)
+ SelectNewMessage();
+ // do this for non-imap messages - for imap, we'll do the delete in
+ // OnStopRunningUrl. For local messages, we won't get an OnStopRunningUrl
+ // notification. And for imap, it's too late to delete the message here,
+ // because we'll be updating the folder naturally as a result of
+ // running an append url. If we delete the header here, that folder
+ // update will think we need to download the header...If we do it
+ // in OnStopRunningUrl, we'll issue the delete before we do the
+ // update....all nasty stuff.
+ const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri;
+ if (mOriginalMessage && strncmp(messageUri, "imap-message:", 13))
+ return DeleteOriginalMessage();
+ else
+ m_state = eUpdatingFolder;
+ return NS_OK;
+}
+
+//
+// local methods
+//
+
+nsDelAttachListener::nsDelAttachListener()
+{
+ mAttach = nullptr;
+ mSaveFirst = false;
+ mWrittenExtra = false;
+ mNewMessageKey = nsMsgKey_None;
+ m_state = eStarting;
+}
+
+nsDelAttachListener::~nsDelAttachListener()
+{
+ if (mAttach)
+ {
+ delete mAttach;
+ }
+ if (mMsgFileStream)
+ {
+ mMsgFileStream->Close();
+ mMsgFileStream = nullptr;
+ }
+ if (mMsgFile)
+ {
+ mMsgFile->Remove(false);
+ }
+}
+
+nsresult
+nsDelAttachListener::StartProcessing(nsMessenger * aMessenger, nsIMsgWindow * aMsgWindow,
+ nsAttachmentState * aAttach, bool detaching)
+{
+ aMessenger->QueryInterface(NS_GET_IID(nsIMessenger), getter_AddRefs(mMessenger));
+ mMsgWindow = aMsgWindow;
+ mAttach = aAttach;
+ mDetaching = detaching;
+
+ nsresult rv;
+
+ // all attachments refer to the same message
+ const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri;
+
+ // get the message service, original message and folder for this message
+ rv = GetMessageServiceFromURI(nsDependentCString(messageUri), getter_AddRefs(mMessageService));
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mMessageService->MessageURIToMsgHdr(messageUri, getter_AddRefs(mOriginalMessage));
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mOriginalMessage->GetFolder(getter_AddRefs(mMessageFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ mOriginalMessage->GetFlags(&mOrigMsgFlags);
+
+ // ensure that we can store and delete messages in this folder, if we
+ // can't then we can't do attachment deleting
+ bool canDelete = false;
+ mMessageFolder->GetCanDeleteMessages(&canDelete);
+ bool canFile = false;
+ mMessageFolder->GetCanFileMessages(&canFile);
+ if (!canDelete || !canFile)
+ return NS_ERROR_FAILURE;
+
+ // create an output stream on a temporary file. This stream will save the modified
+ // message data to a file which we will later use to replace the existing message.
+ // The file is removed in the destructor.
+ rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "nsmail.tmp",
+ getter_AddRefs(mMsgFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // For temp file, we should use restrictive 00600 instead of ATTACHMENT_PERMISSION
+ rv = mMsgFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mMsgFileStream), mMsgFile, -1, ATTACHMENT_PERMISSION);
+
+ // create the additional header for data conversion. This will tell the stream converter
+ // which MIME emitter we want to use, and it will tell the MIME emitter which attachments
+ // should be deleted.
+ const char * partId;
+ const char * nextField;
+ nsAutoCString sHeader("attach&del=");
+ nsAutoCString detachToHeader("&detachTo=");
+ for (uint32_t u = 0; u < mAttach->mCount; ++u)
+ {
+ if (u > 0)
+ {
+ sHeader.Append(",");
+ if (detaching)
+ detachToHeader.Append(",");
+ }
+ partId = GetAttachmentPartId(mAttach->mAttachmentArray[u].mUrl);
+ nextField = PL_strchr(partId, '&');
+ sHeader.Append(partId, nextField ? nextField - partId : -1);
+ if (detaching)
+ detachToHeader.Append(mDetachedFileUris[u]);
+ }
+
+ if (detaching)
+ sHeader.Append(detachToHeader);
+ // stream this message to our listener converting it via the attachment mime
+ // converter. The listener will just write the converted message straight to disk.
+ nsCOMPtr<nsISupports> listenerSupports;
+ rv = this->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(listenerSupports));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIUrlListener> listenerUrlListener = do_QueryInterface(listenerSupports, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = mMessageService->StreamMessage(messageUri, listenerSupports, mMsgWindow,
+ listenerUrlListener, true, sHeader,
+ false, getter_AddRefs(dummyNull));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_OK;
+}
+
+// ------------------------------------
+
+NS_IMETHODIMP
+nsMessenger::DetachAttachment(const char * aContentType, const char * aUrl,
+ const char * aDisplayName, const char * aMessageUri,
+ bool aSaveFirst, bool withoutWarning = false)
+{
+ NS_ENSURE_ARG_POINTER(aContentType);
+ NS_ENSURE_ARG_POINTER(aUrl);
+ NS_ENSURE_ARG_POINTER(aDisplayName);
+ NS_ENSURE_ARG_POINTER(aMessageUri);
+
+ if (aSaveFirst)
+ return SaveOneAttachment(aContentType, aUrl, aDisplayName, aMessageUri, true);
+ return DetachAttachments(1, &aContentType, &aUrl, &aDisplayName, &aMessageUri, nullptr, withoutWarning);
+}
+
+NS_IMETHODIMP
+nsMessenger::DetachAllAttachments(uint32_t aCount,
+ const char ** aContentTypeArray,
+ const char ** aUrlArray,
+ const char ** aDisplayNameArray,
+ const char ** aMessageUriArray,
+ bool aSaveFirst,
+ bool withoutWarning = false)
+{
+ NS_ENSURE_ARG_MIN(aCount, 1);
+ NS_ENSURE_ARG_POINTER(aContentTypeArray);
+ NS_ENSURE_ARG_POINTER(aUrlArray);
+ NS_ENSURE_ARG_POINTER(aDisplayNameArray);
+ NS_ENSURE_ARG_POINTER(aMessageUriArray);
+
+ if (aSaveFirst)
+ return SaveAllAttachments(aCount, aContentTypeArray, aUrlArray, aDisplayNameArray, aMessageUriArray, true);
+ else
+ return DetachAttachments(aCount, aContentTypeArray, aUrlArray, aDisplayNameArray, aMessageUriArray, nullptr, withoutWarning);
+}
+
+nsresult
+nsMessenger::DetachAttachments(uint32_t aCount,
+ const char ** aContentTypeArray,
+ const char ** aUrlArray,
+ const char ** aDisplayNameArray,
+ const char ** aMessageUriArray,
+ nsTArray<nsCString> *saveFileUris,
+ bool withoutWarning)
+{
+ // if withoutWarning no dialog for user
+ if (!withoutWarning && NS_FAILED(PromptIfDeleteAttachments(saveFileUris != nullptr, aCount, aDisplayNameArray)))
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+
+ // ensure that our arguments are valid
+// char * partId;
+ for (uint32_t u = 0; u < aCount; ++u)
+ {
+ // ensure all of the message URI are the same, we cannot process
+ // attachments from different messages
+ if (u > 0 && 0 != strcmp(aMessageUriArray[0], aMessageUriArray[u]))
+ {
+ rv = NS_ERROR_INVALID_ARG;
+ break;
+ }
+
+ // ensure that we don't have deleted messages in this list
+ if (0 == strcmp(aContentTypeArray[u], MIMETYPE_DELETED))
+ {
+ rv = NS_ERROR_INVALID_ARG;
+ break;
+ }
+
+ // for the moment we prevent any attachments other than root level
+ // attachments being deleted (i.e. you can't delete attachments from a
+ // email forwarded as an attachment). We do this by ensuring that the
+ // part id only has a single period in it (e.g. "1.2").
+ //TODO: support non-root level attachment delete
+// partId = ::GetAttachmentPartId(aUrlArray[u]);
+// if (!partId || PL_strchr(partId, '.') != PL_strrchr(partId, '.'))
+// {
+// rv = NS_ERROR_INVALID_ARG;
+// break;
+// }
+ }
+ if (NS_FAILED(rv))
+ {
+ Alert("deleteAttachmentFailure");
+ return rv;
+ }
+
+ //TODO: ensure that nothing else is processing this message uri at the same time
+
+ //TODO: if any of the selected attachments are messages that contain other
+ // attachments we need to warn the user that all sub-attachments of those
+ // messages will also be deleted. Best to display a list of them.
+
+ // get the listener for running the url
+ nsDelAttachListener * listener = new nsDelAttachListener;
+ if (!listener)
+ return NS_ERROR_OUT_OF_MEMORY;
+ nsCOMPtr<nsISupports> listenerSupports; // auto-delete of the listener with error
+ listener->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(listenerSupports));
+
+ if (saveFileUris)
+ listener->mDetachedFileUris = *saveFileUris;
+ // create the attachments for use by the listener
+ nsAttachmentState * attach = new nsAttachmentState;
+ if (!attach)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = attach->Init(aCount, aContentTypeArray, aUrlArray, aDisplayNameArray, aMessageUriArray);
+ if (NS_SUCCEEDED(rv))
+ rv = attach->PrepareForAttachmentDelete();
+ if (NS_FAILED(rv))
+ {
+ delete attach;
+ return rv;
+ }
+
+ // initialize our listener with the attachments and details. The listener takes ownership
+ // of 'attach' immediately irrespective of the return value (error or not).
+ return listener->StartProcessing(this, mMsgWindow, attach, saveFileUris != nullptr);
+}
+
+nsresult
+nsMessenger::PromptIfDeleteAttachments(bool aSaveFirst,
+ uint32_t aCount,
+ const char ** aDisplayNameArray)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
+ if (!dialog) return rv;
+
+ if (!mStringBundle)
+ {
+ rv = InitStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // create the list of attachments we are removing
+ nsString displayString;
+ nsString attachmentList;
+ for (uint32_t u = 0; u < aCount; ++u)
+ {
+ ConvertAndSanitizeFileName(aDisplayNameArray[u], displayString);
+ attachmentList.Append(displayString);
+ attachmentList.Append(char16_t('\n'));
+ }
+ const char16_t *formatStrings[] = { attachmentList.get() };
+
+ // format the message and display
+ nsString promptMessage;
+ const char16_t * propertyName = aSaveFirst ?
+ u"detachAttachments" : u"deleteAttachments";
+ rv = mStringBundle->FormatStringFromName(propertyName, formatStrings, 1,getter_Copies(promptMessage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool dialogResult = false;
+ rv = dialog->Confirm(nullptr, promptMessage.get(), &dialogResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return dialogResult ? NS_OK : NS_ERROR_FAILURE;
+}
+
diff --git a/mailnews/base/src/nsMessenger.h b/mailnews/base/src/nsMessenger.h
new file mode 100644
index 000000000..9ba22e26e
--- /dev/null
+++ b/mailnews/base/src/nsMessenger.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMsgAppCore_h
+#define __nsMsgAppCore_h
+
+#include "nscore.h"
+#include "nsIMessenger.h"
+#include "nsCOMPtr.h"
+#include "nsITransactionManager.h"
+#include "nsIFile.h"
+#include "nsIDocShell.h"
+#include "nsIStringBundle.h"
+#include "nsIFile.h"
+#include "nsWeakReference.h"
+#include "mozIDOMWindow.h"
+#include "nsTArray.h"
+#include "nsIFolderListener.h"
+
+class nsMessenger : public nsIMessenger, public nsSupportsWeakReference, public nsIFolderListener
+{
+
+public:
+ nsMessenger();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMESSENGER
+ NS_DECL_NSIFOLDERLISTENER
+
+ nsresult Alert(const char * stringName);
+
+ nsresult SaveAttachment(nsIFile *file, const nsACString& unescapedUrl,
+ const nsACString& messageUri, const nsACString& contentType,
+ void *closure, nsIUrlListener *aListener);
+ nsresult PromptIfFileExists(nsIFile *file);
+ nsresult DetachAttachments(uint32_t aCount,
+ const char ** aContentTypeArray,
+ const char ** aUrlArray,
+ const char ** aDisplayNameArray,
+ const char ** aMessageUriArray,
+ nsTArray<nsCString> *saveFileUris,
+ bool withoutWarning = false);
+ nsresult SaveAllAttachments(uint32_t count,
+ const char **contentTypeArray,
+ const char **urlArray,
+ const char **displayNameArray,
+ const char **messageUriArray,
+ bool detaching);
+ nsresult SaveOneAttachment(const char* aContentType,
+ const char* aURL,
+ const char* aDisplayName,
+ const char* aMessageUri,
+ bool detaching);
+
+protected:
+ virtual ~nsMessenger();
+
+ void GetString(const nsString& aStringName, nsString& stringValue);
+ nsresult InitStringBundle();
+ nsresult PromptIfDeleteAttachments(bool saveFirst, uint32_t count, const char **displayNameArray);
+
+private:
+ nsresult GetLastSaveDirectory(nsIFile **aLastSaveAsDir);
+ // if aLocalFile is a dir, we use it. otherwise, we use the parent of aLocalFile.
+ nsresult SetLastSaveDirectory(nsIFile *aLocalFile);
+
+ nsresult AdjustFileIfNameTooLong(nsIFile* aFile);
+
+ nsresult GetSaveAsFile(const nsAString& aMsgFilename, int32_t *aSaveAsFileType,
+ nsIFile **aSaveAsFile);
+
+ nsresult GetSaveToDir(nsIFile **aSaveToDir);
+
+ nsString mId;
+ nsCOMPtr<nsITransactionManager> mTxnMgr;
+
+ /* rhp - need this to drive message display */
+ nsCOMPtr<mozIDOMWindowProxy> mWindow;
+ nsCOMPtr<nsIMsgWindow> mMsgWindow;
+ nsCOMPtr<nsIDocShell> mDocShell;
+
+ // String bundles...
+ nsCOMPtr<nsIStringBundle> mStringBundle;
+
+ nsCString mCurrentDisplayCharset;
+
+ nsCOMPtr<nsISupports> mSearchContext;
+ nsCString mLastDisplayURI; // this used when the user attempts to force a charset reload of a message...we need to get the last displayed
+ // uri so we can re-display it..
+ nsCString mNavigatingToUri;
+ nsTArray<nsCString> mLoadedMsgHistory;
+ int32_t mCurHistoryPos;
+};
+
+#define NS_MESSENGER_CID \
+{ /* f436a174-e2c0-4955-9afe-e3feb68aee56 */ \
+ 0xf436a174, 0xe2c0, 0x4955, \
+ {0x9a, 0xfe, 0xe3, 0xfe, 0xb6, 0x8a, 0xee, 0x56}}
+
+#endif
diff --git a/mailnews/base/src/nsMessengerBootstrap.cpp b/mailnews/base/src/nsMessengerBootstrap.cpp
new file mode 100644
index 000000000..a78ba3b7c
--- /dev/null
+++ b/mailnews/base/src/nsMessengerBootstrap.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMessengerBootstrap.h"
+#include "nsCOMPtr.h"
+
+#include "nsIMutableArray.h"
+#include "nsIMsgFolder.h"
+#include "nsIWindowWatcher.h"
+#include "nsMsgUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "mozIDOMWindow.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+NS_IMPL_ISUPPORTS(nsMessengerBootstrap, nsIMessengerWindowService)
+
+nsMessengerBootstrap::nsMessengerBootstrap()
+{
+}
+
+nsMessengerBootstrap::~nsMessengerBootstrap()
+{
+}
+
+NS_IMETHODIMP nsMessengerBootstrap::OpenMessengerWindowWithUri(const char *windowType, const char * aFolderURI, nsMsgKey aMessageKey)
+{
+ bool standAloneMsgWindow = false;
+ nsAutoCString chromeUrl("chrome://messenger/content/");
+ if (windowType && !strcmp(windowType, "mail:messageWindow"))
+ {
+ chromeUrl.Append("messageWindow.xul");
+ standAloneMsgWindow = true;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> argsArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create scriptable versions of our strings that we can store in our nsIMutableArray....
+ if (aFolderURI)
+ {
+ if (standAloneMsgWindow)
+ {
+ nsCOMPtr <nsIMsgFolder> folder;
+ rv = GetExistingFolder(nsDependentCString(aFolderURI), getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString msgUri;
+ folder->GetBaseMessageURI(msgUri);
+
+ nsCOMPtr<nsISupportsCString> scriptableMsgURI (do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableMsgURI, NS_ERROR_FAILURE);
+ msgUri.Append('#');
+ msgUri.AppendInt(aMessageKey, 10);
+ scriptableMsgURI->SetData(msgUri);
+ argsArray->AppendElement(scriptableMsgURI, false);
+ }
+ nsCOMPtr<nsISupportsCString> scriptableFolderURI (do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableFolderURI, NS_ERROR_FAILURE);
+
+ scriptableFolderURI->SetData(nsDependentCString(aFolderURI));
+ argsArray->AppendElement(scriptableFolderURI, false);
+
+ if (!standAloneMsgWindow)
+ {
+ nsCOMPtr<nsISupportsPRUint32> scriptableMessageKey (do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableMessageKey, NS_ERROR_FAILURE);
+ scriptableMessageKey->SetData(aMessageKey);
+ argsArray->AppendElement(scriptableMessageKey, false);
+ }
+ }
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we need to use the "mailnews.reuse_thread_window2" pref
+ // to determine if we should open a new window, or use an existing one.
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ return wwatch->OpenWindow(0, chromeUrl.get(), "_blank",
+ "chrome,all,dialog=no", argsArray,
+ getter_AddRefs(newWindow));
+}
diff --git a/mailnews/base/src/nsMessengerBootstrap.h b/mailnews/base/src/nsMessengerBootstrap.h
new file mode 100644
index 000000000..94016a0ea
--- /dev/null
+++ b/mailnews/base/src/nsMessengerBootstrap.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef __nsMessenger_h
+#define __nsMessenger_h
+
+#include "nscore.h"
+#include "nsIMessengerWindowService.h"
+
+#define NS_MESSENGERBOOTSTRAP_CID \
+{ /* 4a85a5d0-cddd-11d2-b7f6-00805f05ffa5 */ \
+ 0x4a85a5d0, 0xcddd, 0x11d2, \
+ {0xb7, 0xf6, 0x00, 0x80, 0x5f, 0x05, 0xff, 0xa5}}
+
+class nsMessengerBootstrap :
+ public nsIMessengerWindowService
+{
+public:
+ nsMessengerBootstrap();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMESSENGERWINDOWSERVICE
+
+private:
+ virtual ~nsMessengerBootstrap();
+};
+
+#endif
diff --git a/mailnews/base/src/nsMessengerContentHandler.cpp b/mailnews/base/src/nsMessengerContentHandler.cpp
new file mode 100644
index 000000000..5c6460965
--- /dev/null
+++ b/mailnews/base/src/nsMessengerContentHandler.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMessengerContentHandler.h"
+#include "nsIChannel.h"
+#include "nsPIDOMWindow.h"
+#include "nsIServiceManager.h"
+#include "nsIWindowWatcher.h"
+#include "nsIDocShell.h"
+#include "nsIWebNavigation.h"
+#include "nsIURL.h"
+#include "nsStringGlue.h"
+#include "nsMsgBaseCID.h"
+#include "plstr.h"
+#include "nsIURL.h"
+#include "nsServiceManagerUtils.h"
+
+nsMessengerContentHandler::nsMessengerContentHandler()
+{
+}
+
+/* the following macro actually implement addref, release and query interface for our component. */
+NS_IMPL_ISUPPORTS(nsMessengerContentHandler, nsIContentHandler)
+
+nsMessengerContentHandler::~nsMessengerContentHandler()
+{
+}
+
+NS_IMETHODIMP nsMessengerContentHandler::HandleContent(const char * aContentType,
+ nsIInterfaceRequestor* aWindowContext, nsIRequest *request)
+{
+ nsresult rv = NS_OK;
+ if (!request)
+ return NS_ERROR_NULL_POINTER;
+
+ // First of all, get the content type and make sure it is a content type we know how to handle!
+ if (PL_strcasecmp(aContentType, "application/x-message-display") == 0) {
+ nsCOMPtr<nsIURI> aUri;
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (!aChannel) return NS_ERROR_FAILURE;
+
+ rv = aChannel->GetURI(getter_AddRefs(aUri));
+ if (aUri)
+ {
+ rv = request->Cancel(NS_ERROR_ABORT);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURL> aUrl = do_QueryInterface(aUri);
+ if (aUrl)
+ {
+ nsAutoCString queryPart;
+ aUrl->GetQuery(queryPart);
+ queryPart.Replace(queryPart.Find("type=message/rfc822"),
+ sizeof("type=message/rfc822") - 1,
+ "type=application/x-message-display");
+ aUrl->SetQuery(queryPart);
+ rv = OpenWindow(aUri);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+// Utility function to open a message display window and and load the message in it.
+nsresult nsMessengerContentHandler::OpenWindow(nsIURI* aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIWindowWatcher> wwatch = do_GetService("@mozilla.org/embedcomp/window-watcher;1");
+ if (!wwatch)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ return wwatch->OpenWindow(0, "chrome://messenger/content/messageWindow.xul",
+ "_blank", "all,chrome,dialog=no,status,toolbar", aURI,
+ getter_AddRefs(newWindow));
+}
diff --git a/mailnews/base/src/nsMessengerContentHandler.h b/mailnews/base/src/nsMessengerContentHandler.h
new file mode 100644
index 000000000..dad69493b
--- /dev/null
+++ b/mailnews/base/src/nsMessengerContentHandler.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIContentHandler.h"
+#include "nsIURI.h"
+
+class nsMessengerContentHandler : public nsIContentHandler
+{
+public:
+ nsMessengerContentHandler();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTHANDLER
+
+private:
+ virtual ~nsMessengerContentHandler();
+ nsresult OpenWindow(nsIURI* aURI);
+};
diff --git a/mailnews/base/src/nsMessengerOSXIntegration.h b/mailnews/base/src/nsMessengerOSXIntegration.h
new file mode 100644
index 000000000..91f42f2a2
--- /dev/null
+++ b/mailnews/base/src/nsMessengerOSXIntegration.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMessengerOSXIntegration_h
+#define __nsMessengerOSXIntegration_h
+
+#include "nsIMessengerOSIntegration.h"
+#include "nsIFolderListener.h"
+#include "nsIAtom.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIObserver.h"
+#include "nsIAlertsService.h"
+#include "mozINewMailListener.h"
+
+#define NS_MESSENGEROSXINTEGRATION_CID \
+ {0xaa83266, 0x4225, 0x4c4b, \
+ {0x93, 0xf8, 0x94, 0xb1, 0x82, 0x58, 0x6f, 0x93}}
+
+class nsIStringBundle;
+
+class nsMessengerOSXIntegration : public nsIMessengerOSIntegration,
+ public nsIFolderListener,
+ public nsIObserver,
+ public mozINewMailListener
+{
+public:
+ nsMessengerOSXIntegration();
+ virtual nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMESSENGEROSINTEGRATION
+ NS_DECL_NSIFOLDERLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_MOZINEWMAILLISTENER
+
+private:
+ virtual ~nsMessengerOSXIntegration();
+
+ nsCOMPtr<nsIAtom> mBiffStateAtom;
+ nsCOMPtr<nsIAtom> mNewMailReceivedAtom;
+ nsresult ShowAlertMessage(const nsAString& aAlertTitle, const nsAString& aAlertText, const nsACString& aFolderURI);
+ nsresult OnAlertFinished();
+ nsresult OnAlertClicked(const char16_t * aAlertCookie);
+#ifdef MOZ_SUITE
+ nsresult OnAlertClickedSimple();
+#endif
+ nsresult GetStringBundle(nsIStringBundle **aBundle);
+ void FillToolTipInfo(nsIMsgFolder *aFolder, int32_t aNewCount);
+ nsresult GetFirstFolderWithNewMail(nsIMsgFolder* aFolder, nsCString& aFolderURI);
+ nsresult BadgeDockIcon();
+ nsresult RestoreDockIcon();
+ nsresult BounceDockIcon();
+ nsresult GetNewMailAuthors(nsIMsgFolder* aFolder, nsString& aAuthors, int32_t aNewCount, int32_t* aNotDisplayed);
+
+ int32_t mUnreadTotal;
+ int32_t mUnreadChat;
+};
+
+#endif // __nsMessengerOSXIntegration_h
diff --git a/mailnews/base/src/nsMessengerOSXIntegration.mm b/mailnews/base/src/nsMessengerOSXIntegration.mm
new file mode 100644
index 000000000..280bdf433
--- /dev/null
+++ b/mailnews/base/src/nsMessengerOSXIntegration.mm
@@ -0,0 +1,708 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "nsMsgUtils.h"
+#include "nsArrayUtils.h"
+#include "nsMessengerOSXIntegration.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgIdentity.h"
+#include "nsIMsgAccount.h"
+#include "nsIMsgFolder.h"
+#include "nsCOMPtr.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIDirectoryService.h"
+#include "MailNewsTypes.h"
+#include "nsIWindowMediator.h"
+#include "nsIDOMChromeWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIMessengerWindowService.h"
+#include "prprf.h"
+#include "nsIAlertsService.h"
+#include "nsIStringBundle.h"
+#include "nsToolkitCompsCID.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIWindowWatcher.h"
+#include "nsMsgLocalCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMessenger.h"
+#include "nsObjCExceptions.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozINewMailNotificationService.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+#include <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+
+#define kBiffAnimateDockIconPref "mail.biff.animate_dock_icon"
+#define kMaxDisplayCount 10
+
+using namespace mozilla::mailnews;
+
+// HACK: Limitations in Focus/SetFocus on Mac (see bug 465446)
+nsresult FocusAppNative()
+{
+ ProcessSerialNumber psn;
+
+ if (::GetCurrentProcess(&psn) != 0)
+ return NS_ERROR_FAILURE;
+
+ if (::SetFrontProcess(&psn) != 0)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+static void openMailWindow(const nsCString& aUri)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIMsgWindow> topMostMsgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow));
+ if (topMostMsgWindow)
+ {
+ if (!aUri.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUri(do_CreateInstance(NS_MAILBOXURL_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ rv = msgUri->SetSpec(aUri);
+ if (NS_FAILED(rv))
+ return;
+
+ bool isMessageUri = false;
+ msgUri->GetIsMessageUri(&isMessageUri);
+ if (isMessageUri)
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ // SeaMonkey only supports message uris, whereas Thunderbird only
+ // supports message headers. This should be simplified/removed when
+ // bug 507593 is implemented.
+#ifdef MOZ_SUITE
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ wwatch->OpenWindow(0, "chrome://messenger/content/messageWindow.xul",
+ "_blank", "all,chrome,dialog=no,status,toolbar", msgUri,
+ getter_AddRefs(newWindow));
+#else
+ nsCOMPtr<nsIMessenger> messenger(do_CreateInstance(NS_MESSENGER_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ messenger->MsgHdrFromURI(aUri, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ wwatch->OpenWindow(0, "chrome://messenger/content/messageWindow.xul",
+ "_blank", "all,chrome,dialog=no,status,toolbar", msgHdr,
+ getter_AddRefs(newWindow));
+ }
+#endif
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ topMostMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectFolder(aUri);
+ }
+ }
+
+ FocusAppNative();
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(domWindow);
+ privateWindow->Focus();
+ }
+ }
+ else
+ {
+ // the user doesn't have a mail window open already so open one for them...
+ nsCOMPtr<nsIMessengerWindowService> messengerWindowService =
+ do_GetService(NS_MESSENGERWINDOWSERVICE_CONTRACTID);
+ // if we want to preselect the first account with new mail,
+ // here is where we would try to generate a uri to pass in
+ // (and add code to the messenger window service to make that work)
+ if (messengerWindowService)
+ messengerWindowService->OpenMessengerWindowWithUri(
+ "mail:3pane", aUri.get(), nsMsgKey_None);
+ }
+}
+
+nsMessengerOSXIntegration::nsMessengerOSXIntegration()
+{
+ mBiffStateAtom = MsgGetAtom("BiffState");
+ mNewMailReceivedAtom = MsgGetAtom("NewMailReceived");
+ mUnreadTotal = 0;
+}
+
+nsMessengerOSXIntegration::~nsMessengerOSXIntegration()
+{
+ RestoreDockIcon();
+}
+
+NS_IMPL_ADDREF(nsMessengerOSXIntegration)
+NS_IMPL_RELEASE(nsMessengerOSXIntegration)
+
+NS_INTERFACE_MAP_BEGIN(nsMessengerOSXIntegration)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessengerOSIntegration)
+ NS_INTERFACE_MAP_ENTRY(nsIMessengerOSIntegration)
+ NS_INTERFACE_MAP_ENTRY(nsIFolderListener)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(mozINewMailListener)
+NS_INTERFACE_MAP_END
+
+
+nsresult
+nsMessengerOSXIntegration::Init()
+{
+ nsresult rv;
+ nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return observerService->AddObserver(this, "mail-startup-done", false);
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemUnicharPropertyChanged(nsIMsgFolder *, nsIAtom *, const char16_t *, const char16_t *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemRemoved(nsIMsgFolder *, nsISupports *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+ if (!strcmp(aTopic, "alertfinished"))
+ return OnAlertFinished();
+
+ if (!strcmp(aTopic, "alertclickcallback"))
+ return OnAlertClicked(aData);
+
+#ifdef MOZ_SUITE
+ // SeaMonkey does most of the GUI work in JS code when clicking on a mail
+ // notification, so it needs an extra function here
+ if (!strcmp(aTopic, "alertclicksimplecallback"))
+ return OnAlertClickedSimple();
+#endif
+
+ if (!strcmp(aTopic, "mail-startup-done")) {
+ nsresult rv;
+ nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ observerService->RemoveObserver(this, "mail-startup-done");
+
+ nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ }
+
+ // Register with the new mail service for changes to the unread message count
+ nsCOMPtr<mozINewMailNotificationService> newmail
+ = do_GetService(MOZ_NEWMAILNOTIFICATIONSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv); // This should really be an assert with a helpful message
+ rv = newmail->AddListener(this, mozINewMailNotificationService::count);
+ NS_ENSURE_SUCCESS(rv, rv); // This should really be an assert with a helpful message
+
+ // Get the initial unread count. Ignore return value; if code above didn't fail, this won't
+ rv = newmail->GetMessageCount(&mUnreadTotal);
+ BadgeDockIcon();
+
+ // register with the mail sesson for folder events
+ // we care about new count, biff status
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mailSession->AddFolderListener(this, nsIFolderListener::boolPropertyChanged | nsIFolderListener::intPropertyChanged);
+ }
+
+ if (!strcmp(aTopic, kUnreadImCountChangedTopic)) {
+ nsresult rv;
+ nsCOMPtr<nsISupportsPRInt32> unreadCount = do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return BadgeDockIcon();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::GetStringBundle(nsIStringBundle **aBundle)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (bundleService && NS_SUCCEEDED(rv))
+ bundleService->CreateBundle("chrome://messenger/locale/messenger.properties", getter_AddRefs(bundle));
+ bundle.swap(*aBundle);
+ return rv;
+}
+
+void
+nsMessengerOSXIntegration::FillToolTipInfo(nsIMsgFolder *aFolder, int32_t aNewCount)
+{
+ if (aFolder)
+ {
+ nsString authors;
+ int32_t numNotDisplayed;
+ nsresult rv = GetNewMailAuthors(aFolder, authors, aNewCount, &numNotDisplayed);
+
+ // If all senders are vetoed, the authors string will be empty.
+ if (NS_FAILED(rv) || authors.IsEmpty())
+ return;
+
+ // If this isn't the root folder, get it so we can report for it.
+ // GetRootFolder always returns the server's root, so calling on the root itself is fine.
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ aFolder->GetRootFolder(getter_AddRefs(rootFolder));
+ if (!rootFolder)
+ return;
+
+ nsString accountName;
+ rootFolder->GetPrettiestName(accountName);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ GetStringBundle(getter_AddRefs(bundle));
+ if (bundle)
+ {
+ nsAutoString numNewMsgsText;
+ numNewMsgsText.AppendInt(aNewCount);
+ nsString finalText;
+ nsCString uri;
+ aFolder->GetURI(uri);
+
+ if (numNotDisplayed > 0)
+ {
+ nsAutoString numNotDisplayedText;
+ numNotDisplayedText.AppendInt(numNotDisplayed);
+ const char16_t *formatStrings[3] = { numNewMsgsText.get(), authors.get(), numNotDisplayedText.get() };
+ bundle->FormatStringFromName(u"macBiffNotification_messages_extra",
+ formatStrings,
+ 3,
+ getter_Copies(finalText));
+ }
+ else
+ {
+ const char16_t *formatStrings[2] = { numNewMsgsText.get(), authors.get() };
+
+ if (aNewCount == 1)
+ {
+ bundle->FormatStringFromName(u"macBiffNotification_message",
+ formatStrings,
+ 2,
+ getter_Copies(finalText));
+ // Since there is only 1 message, use the most recent mail's URI instead of the folder's
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = aFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db)
+ {
+ uint32_t numNewKeys;
+ uint32_t *newMessageKeys;
+ rv = db->GetNewList(&numNewKeys, &newMessageKeys);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = db->GetMsgHdrForKey(newMessageKeys[numNewKeys - 1],
+ getter_AddRefs(hdr));
+ if (NS_SUCCEEDED(rv) && hdr)
+ aFolder->GetUriForMsg(hdr, uri);
+ }
+ NS_Free(newMessageKeys);
+ }
+ }
+ else
+ bundle->FormatStringFromName(u"macBiffNotification_messages",
+ formatStrings,
+ 2,
+ getter_Copies(finalText));
+ }
+ ShowAlertMessage(accountName, finalText, uri);
+ } // if we got a bundle
+ } // if we got a folder
+}
+
+nsresult
+nsMessengerOSXIntegration::ShowAlertMessage(const nsAString& aAlertTitle,
+ const nsAString& aAlertText,
+ const nsACString& aFolderURI)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIAlertsService> alertsService (do_GetService(NS_ALERTSERVICE_CONTRACTID, &rv));
+ // If we have an nsIAlertsService implementation, use it:
+ if (NS_SUCCEEDED(rv))
+ {
+ alertsService->ShowAlertNotification(EmptyString(),
+ aAlertTitle, aAlertText, true,
+ NS_ConvertASCIItoUTF16(aFolderURI),
+ this, EmptyString(),
+ NS_LITERAL_STRING("auto"),
+ EmptyString(), EmptyString(),
+ nullptr,
+ false,
+ false);
+ }
+
+ BounceDockIcon();
+
+ if (NS_FAILED(rv))
+ OnAlertFinished();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aFolder,
+ nsIAtom *aProperty,
+ int64_t aOldValue,
+ int64_t aNewValue)
+{
+ // if we got new mail show an alert
+ if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail)
+ {
+ bool performingBiff = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ aFolder->GetServer(getter_AddRefs(server));
+ if (server)
+ server->GetPerformingBiff(&performingBiff);
+ if (!performingBiff)
+ return NS_OK; // kick out right now...
+
+ // Biff happens for the root folder, but we want info for the child with new mail
+ nsCString folderUri;
+ GetFirstFolderWithNewMail(aFolder, folderUri);
+ nsCOMPtr<nsIMsgFolder> childFolder;
+ nsresult rv = aFolder->GetChildWithURI(folderUri, true, true,
+ getter_AddRefs(childFolder));
+ if (NS_FAILED(rv) || !childFolder)
+ return NS_ERROR_FAILURE;
+
+ int32_t numNewMessages = 0;
+ childFolder->GetNumNewMessages(true, &numNewMessages);
+ FillToolTipInfo(childFolder, numNewMessages);
+ }
+ else if (mNewMailReceivedAtom == aProperty)
+ {
+ FillToolTipInfo(aFolder, aNewValue);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::OnAlertClicked(const char16_t* aAlertCookie)
+{
+ openMailWindow(NS_ConvertUTF16toUTF8(aAlertCookie));
+ return NS_OK;
+}
+
+#ifdef MOZ_SUITE
+nsresult
+nsMessengerOSXIntegration::OnAlertClickedSimple()
+{
+ // SeaMonkey only function; only focus the app here, rest of the work will
+ // be done in suite/mailnews/mailWidgets.xml
+ FocusAppNative();
+ return NS_OK;
+}
+#endif
+
+nsresult
+nsMessengerOSXIntegration::OnAlertFinished()
+{
+ return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::BounceDockIcon()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool bounceDockIcon = false;
+ rv = prefBranch->GetBoolPref(kBiffAnimateDockIconPref, &bounceDockIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!bounceDockIcon)
+ return NS_OK;
+
+ nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (mediator)
+ {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mediator->GetMostRecentWindow(u"mail:3pane", getter_AddRefs(domWindow));
+ if (domWindow)
+ {
+ nsCOMPtr<nsIDOMChromeWindow> chromeWindow(do_QueryInterface(domWindow));
+ chromeWindow->GetAttention();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::RestoreDockIcon()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ id tile = [[NSApplication sharedApplication] dockTile];
+ [tile setBadgeLabel: nil];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsMessengerOSXIntegration::BadgeDockIcon()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ int32_t unreadCount = mUnreadTotal;
+ // If count is less than one, we should restore the original dock icon.
+ if (unreadCount < 1)
+ {
+ RestoreDockIcon();
+ return NS_OK;
+ }
+
+ // Draw the number, first giving extensions a chance to modify.
+ // Extensions might wish to transform "1000" into "100+" or some
+ // other short string. Getting back the empty string will cause
+ // nothing to be drawn and us to return early.
+ nsresult rv;
+ nsCOMPtr<nsIObserverService> os
+ (do_GetService("@mozilla.org/observer-service;1", &rv));
+ if (NS_FAILED(rv))
+ {
+ RestoreDockIcon();
+ return rv;
+ }
+
+ nsCOMPtr<nsISupportsString> str
+ (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ {
+ RestoreDockIcon();
+ return rv;
+ }
+
+ nsAutoString total;
+ total.AppendInt(unreadCount);
+ str->SetData(total);
+ os->NotifyObservers(str, "before-unread-count-display",
+ total.get());
+ nsAutoString badgeString;
+ str->GetData(badgeString);
+ if (badgeString.IsEmpty())
+ {
+ RestoreDockIcon();
+ return NS_OK;
+ }
+
+ id tile = [[NSApplication sharedApplication] dockTile];
+ [tile setBadgeLabel:[NSString stringWithFormat:@"%S", (const unichar*)badgeString.get()]];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemAdded(nsIMsgFolder *, nsISupports *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemBoolPropertyChanged(nsIMsgFolder *aItem,
+ nsIAtom *aProperty,
+ bool aOldValue,
+ bool aNewValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemEvent(nsIMsgFolder *, nsIAtom *)
+{
+ return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::GetNewMailAuthors(nsIMsgFolder* aFolder,
+ nsString& aAuthors,
+ int32_t aNewCount,
+ int32_t* aNotDisplayed)
+{
+ // Get a list of names or email addresses for the folder's authors
+ // with new mail. Note that we only process the most recent "new"
+ // mail (aNewCount), working from most recently added. Duplicates
+ // are removed, and names are displayed to a set limit
+ // (kMaxDisplayCount) with the remaining count being returned in
+ // aNotDisplayed. Extension developers can listen for
+ // "newmail-notification-requested" and then make a decision about
+ // including a given author or not. As a result, it is possible that
+ // the resulting length of aAuthors will be 0.
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = aFolder->GetMsgDatabase(getter_AddRefs(db));
+ uint32_t numNewKeys = 0;
+ if (NS_SUCCEEDED(rv) && db)
+ {
+ nsCOMPtr<nsIObserverService> os =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get proper l10n list separator -- ", " in English
+ nsCOMPtr<nsIStringBundle> bundle;
+ GetStringBundle(getter_AddRefs(bundle));
+ if (!bundle)
+ return NS_ERROR_FAILURE;
+
+ uint32_t *newMessageKeys;
+ rv = db->GetNewList(&numNewKeys, &newMessageKeys);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString listSeparator;
+ bundle->GetStringFromName(u"macBiffNotification_separator", getter_Copies(listSeparator));
+
+ int32_t displayed = 0;
+ for (int32_t i = numNewKeys - 1; i >= 0; i--, aNewCount--)
+ {
+ if (0 == aNewCount || displayed == kMaxDisplayCount)
+ break;
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = db->GetMsgHdrForKey(newMessageKeys[i],
+ getter_AddRefs(hdr));
+ if (NS_SUCCEEDED(rv) && hdr)
+ {
+ nsString author;
+ rv = hdr->GetMime2DecodedAuthor(author);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsString name;
+ ExtractName(DecodedHeader(author), name);
+
+ // Give extensions a chance to suppress notifications for this author
+ nsCOMPtr<nsISupportsPRBool> notify =
+ do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
+
+ notify->SetData(true);
+ os->NotifyObservers(notify, "newmail-notification-requested",
+ author.get());
+
+ bool includeSender;
+ notify->GetData(&includeSender);
+
+ // Don't add unwanted or duplicate names
+ if (includeSender && aAuthors.Find(name, true) == -1)
+ {
+ if (displayed > 0)
+ aAuthors.Append(listSeparator);
+ aAuthors.Append(name);
+ displayed++;
+ }
+ }
+ }
+ }
+ NS_Free(newMessageKeys);
+ }
+ *aNotDisplayed = aNewCount;
+ return rv;
+}
+
+nsresult
+nsMessengerOSXIntegration::GetFirstFolderWithNewMail(nsIMsgFolder* aFolder, nsCString& aFolderURI)
+{
+ // Find the subfolder in aFolder with new mail and return the folderURI
+ if (aFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ // enumerate over the folders under this root folder till we find one with new mail....
+ nsCOMPtr<nsIArray> allFolders;
+ nsresult rv = aFolder->GetDescendants(getter_AddRefs(allFolders));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = allFolders->Enumerate(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ nsCOMPtr<nsISupports> supports;
+ int32_t numNewMessages = 0;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv) && supports)
+ {
+ msgFolder = do_QueryInterface(supports, &rv);
+ if (msgFolder)
+ {
+ numNewMessages = 0;
+ msgFolder->GetNumNewMessages(false, &numNewMessages);
+ if (numNewMessages)
+ break; // kick out of the while loop
+ }
+ } // if we have a folder
+ } // if we have more potential folders to enumerate
+ } // if enumerator
+
+ if (msgFolder)
+ msgFolder->GetURI(aFolderURI);
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Method implementations for mozINewMailListener
+ */
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnCountChanged(uint32_t count)
+{
+ mUnreadTotal = count;
+ BadgeDockIcon();
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMessengerUnixIntegration.cpp b/mailnews/base/src/nsMessengerUnixIntegration.cpp
new file mode 100644
index 000000000..cb76a5fba
--- /dev/null
+++ b/mailnews/base/src/nsMessengerUnixIntegration.cpp
@@ -0,0 +1,766 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMessengerUnixIntegration.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgIdentity.h"
+#include "nsIMsgAccount.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgWindow.h"
+#include "nsCOMPtr.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIDirectoryService.h"
+#include "nsIWindowWatcher.h"
+#include "nsIWindowMediator.h"
+#include "mozIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIBaseWindow.h"
+#include "MailNewsTypes.h"
+#include "nsIMessengerWindowService.h"
+#include "prprf.h"
+#include "nsIWeakReference.h"
+#include "nsIStringBundle.h"
+#include "nsIAlertsService.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsAutoPtr.h"
+#include "prmem.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIWeakReferenceUtils.h"
+
+#include "nsNativeCharsetUtils.h"
+#include "nsToolkitCompsCID.h"
+#include "nsMsgUtils.h"
+#include "msgCore.h"
+#include "nsCOMArray.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsMemory.h"
+#include "mozilla/Services.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+#define ALERT_CHROME_URL "chrome://messenger/content/newmailalert.xul"
+#define NEW_MAIL_ALERT_ICON "chrome://messenger/skin/icons/new-mail-alert.png"
+#define SHOW_ALERT_PREF "mail.biff.show_alert"
+#define SHOW_ALERT_PREVIEW_LENGTH "mail.biff.alert.preview_length"
+#define SHOW_ALERT_PREVIEW_LENGTH_DEFAULT 40
+#define SHOW_ALERT_PREVIEW "mail.biff.alert.show_preview"
+#define SHOW_ALERT_SENDER "mail.biff.alert.show_sender"
+#define SHOW_ALERT_SUBJECT "mail.biff.alert.show_subject"
+#define SHOW_ALERT_SYSTEM "mail.biff.use_system_alert"
+
+using namespace mozilla::mailnews;
+
+static void openMailWindow(const nsACString& aFolderUri)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIMsgWindow> topMostMsgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow));
+ if (topMostMsgWindow)
+ {
+ if (!aFolderUri.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ topMostMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectFolder(aFolderUri);
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(domWindow);
+ privateWindow->Focus();
+ }
+ }
+ else
+ {
+ // the user doesn't have a mail window open already so open one for them...
+ nsCOMPtr<nsIMessengerWindowService> messengerWindowService =
+ do_GetService(NS_MESSENGERWINDOWSERVICE_CONTRACTID);
+ // if we want to preselect the first account with new mail,
+ // here is where we would try to generate a uri to pass in
+ // (and add code to the messenger window service to make that work)
+ if (messengerWindowService)
+ messengerWindowService->OpenMessengerWindowWithUri(
+ "mail:3pane", nsCString(aFolderUri).get(), nsMsgKey_None);
+ }
+}
+
+nsMessengerUnixIntegration::nsMessengerUnixIntegration()
+{
+ mBiffStateAtom = MsgGetAtom("BiffState");
+ mNewMailReceivedAtom = MsgGetAtom("NewMailReceived");
+ mAlertInProgress = false;
+ mFoldersWithNewMail = do_CreateInstance(NS_ARRAY_CONTRACTID);
+}
+
+NS_IMPL_ISUPPORTS(nsMessengerUnixIntegration, nsIFolderListener, nsIObserver,
+ nsIMessengerOSIntegration, nsIUrlListener)
+
+nsresult
+nsMessengerUnixIntegration::Init()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mailSession->AddFolderListener(this, nsIFolderListener::intPropertyChanged);
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnItemUnicharPropertyChanged(nsIMsgFolder *, nsIAtom *, const char16_t *, const char16_t *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnItemRemoved(nsIMsgFolder *, nsISupports *)
+{
+ return NS_OK;
+}
+
+nsresult nsMessengerUnixIntegration::GetStringBundle(nsIStringBundle **aBundle)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ bundle.swap(*aBundle);
+ return NS_OK;
+}
+
+bool
+nsMessengerUnixIntegration::BuildNotificationTitle(nsIMsgFolder *aFolder, nsIStringBundle *aBundle, nsString &aTitle)
+{
+ nsString accountName;
+ aFolder->GetPrettiestName(accountName);
+
+ int32_t numNewMessages = 0;
+ aFolder->GetNumNewMessages(true, &numNewMessages);
+
+ if (!numNewMessages)
+ return false;
+
+ nsAutoString numNewMsgsText;
+ numNewMsgsText.AppendInt(numNewMessages);
+
+ const char16_t *formatStrings[] =
+ {
+ accountName.get(), numNewMsgsText.get()
+ };
+
+ aBundle->FormatStringFromName(numNewMessages == 1 ?
+ u"newMailNotification_message" :
+ u"newMailNotification_messages",
+ formatStrings, 2, getter_Copies(aTitle));
+ return true;
+}
+
+/* This comparator lets us sort an nsCOMArray of nsIMsgDBHdr's by
+ * their dateInSeconds attributes in ascending order.
+ */
+static int
+nsMsgDbHdrTimestampComparator(nsIMsgDBHdr *aElement1,
+ nsIMsgDBHdr *aElement2,
+ void *aData)
+{
+ uint32_t aElement1Timestamp;
+ nsresult rv = aElement1->GetDateInSeconds(&aElement1Timestamp);
+ if (NS_FAILED(rv))
+ return 0;
+
+ uint32_t aElement2Timestamp;
+ rv = aElement2->GetDateInSeconds(&aElement2Timestamp);
+ if (NS_FAILED(rv))
+ return 0;
+
+ return aElement1Timestamp - aElement2Timestamp;
+}
+
+
+bool
+nsMessengerUnixIntegration::BuildNotificationBody(nsIMsgDBHdr *aHdr,
+ nsIStringBundle *aBundle,
+ nsString &aBody)
+{
+ nsAutoString alertBody;
+
+ bool showPreview = true;
+ bool showSubject = true;
+ bool showSender = true;
+ int32_t previewLength = SHOW_ALERT_PREVIEW_LENGTH_DEFAULT;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (!prefBranch)
+ return false;
+
+ prefBranch->GetBoolPref(SHOW_ALERT_PREVIEW, &showPreview);
+ prefBranch->GetBoolPref(SHOW_ALERT_SENDER, &showSender);
+ prefBranch->GetBoolPref(SHOW_ALERT_SUBJECT, &showSubject);
+ prefBranch->GetIntPref(SHOW_ALERT_PREVIEW_LENGTH, &previewLength);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aHdr->GetFolder(getter_AddRefs(folder));
+
+ if (!folder)
+ return false;
+
+ nsCString msgURI;
+ folder->GetUriForMsg(aHdr, msgURI);
+
+ bool localOnly;
+
+ size_t msgURIIndex = mFetchingURIs.IndexOf(msgURI);
+ if (msgURIIndex == mFetchingURIs.NoIndex)
+ {
+ localOnly = false;
+ mFetchingURIs.AppendElement(msgURI);
+ }
+ else
+ localOnly = true;
+
+ nsMsgKey messageKey;
+ if (NS_FAILED(aHdr->GetMessageKey(&messageKey)))
+ return false;
+
+ bool asyncResult = false;
+ nsresult rv = folder->FetchMsgPreviewText(&messageKey, 1,
+ localOnly, this,
+ &asyncResult);
+ // If we're still waiting on getting the message previews,
+ // bail early. We'll come back later when the async operation
+ // finishes.
+ if (NS_FAILED(rv) || asyncResult)
+ return false;
+
+ // If we got here, that means that we've retrieved the message preview,
+ // so we can stop tracking it with our mFetchingURIs array.
+ if (msgURIIndex != mFetchingURIs.NoIndex)
+ mFetchingURIs.RemoveElementAt(msgURIIndex);
+
+ nsCString utf8previewString;
+ if (showPreview &&
+ NS_FAILED(aHdr->GetStringProperty("preview", getter_Copies(utf8previewString))))
+ return false;
+
+ // need listener that mailbox is remote such as IMAP
+ // to generate preview message
+ nsString previewString;
+ CopyUTF8toUTF16(utf8previewString, previewString);
+
+ nsString subject;
+ if (showSubject && NS_FAILED(aHdr->GetMime2DecodedSubject(subject)))
+ return false;
+
+ nsString author;
+ if (showSender)
+ {
+ nsString fullHeader;
+ if (NS_FAILED(aHdr->GetMime2DecodedAuthor(fullHeader)))
+ return false;
+
+ ExtractName(DecodedHeader(fullHeader), author);
+ }
+
+ if (showSubject && showSender)
+ {
+ nsString msgTitle;
+ const char16_t *formatStrings[] =
+ {
+ subject.get(), author.get()
+ };
+ aBundle->FormatStringFromName(u"newMailNotification_messagetitle",
+ formatStrings, 2, getter_Copies(msgTitle));
+ alertBody.Append(msgTitle);
+ }
+ else if (showSubject)
+ alertBody.Append(subject);
+ else if (showSender)
+ alertBody.Append(author);
+
+ if (showPreview && (showSubject || showSender))
+ {
+ alertBody.AppendLiteral("\n");
+ }
+
+ if (showPreview)
+ alertBody.Append(StringHead(previewString, previewLength));
+
+ if (alertBody.IsEmpty())
+ return false;
+
+ aBody.Assign(alertBody);
+ return true;
+}
+
+nsresult nsMessengerUnixIntegration::ShowAlertMessage(const nsAString& aAlertTitle, const nsAString& aAlertText, const nsACString& aFolderURI)
+{
+ nsresult rv;
+ // if we are already in the process of showing an alert, don't try to show another....
+ if (mAlertInProgress)
+ return NS_OK;
+
+ mAlertInProgress = true;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // determine if we should use libnotify or the built-in alert system
+ bool useSystemAlert = true;
+ prefBranch->GetBoolPref(SHOW_ALERT_SYSTEM, &useSystemAlert);
+
+ if (useSystemAlert) {
+ nsCOMPtr<nsIAlertsService> alertsService(do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = alertsService->ShowAlertNotification(NS_LITERAL_STRING(NEW_MAIL_ALERT_ICON),
+ aAlertTitle,
+ aAlertText,
+ false,
+ NS_ConvertASCIItoUTF16(aFolderURI),
+ this,
+ EmptyString(),
+ NS_LITERAL_STRING("auto"),
+ EmptyString(),
+ EmptyString(),
+ nullptr,
+ false,
+ false);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+ }
+ AlertFinished();
+ rv = ShowNewAlertNotification(false);
+
+ if (NS_FAILED(rv)) // go straight to showing the system tray icon.
+ AlertFinished();
+
+ return rv;
+}
+
+// Opening Thunderbird's new mail alert notification window for not supporting libnotify
+// aUserInitiated --> true if we are opening the alert notification in response to a user action
+// like clicking on the biff icon
+nsresult nsMessengerUnixIntegration::ShowNewAlertNotification(bool aUserInitiated)
+{
+
+ nsresult rv;
+
+ // if we are already in the process of showing an alert, don't try to show another....
+ if (mAlertInProgress)
+ return NS_OK;
+
+ nsCOMPtr<nsIMutableArray> argsArray = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ if (!argsArray)
+ return NS_ERROR_FAILURE;
+
+ // pass in the array of folders with unread messages
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ifptr->SetData(mFoldersWithNewMail);
+ ifptr->SetDataIID(&NS_GET_IID(nsIArray));
+ argsArray->AppendElement(ifptr, false);
+
+ // pass in the observer
+ ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMessengerOSIntegration*>(this));
+ ifptr->SetData(supports);
+ ifptr->SetDataIID(&NS_GET_IID(nsIObserver));
+ argsArray->AppendElement(ifptr, false);
+
+ // pass in the animation flag
+ nsCOMPtr<nsISupportsPRBool> scriptableUserInitiated (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ scriptableUserInitiated->SetData(aUserInitiated);
+ argsArray->AppendElement(scriptableUserInitiated, false);
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+
+ mAlertInProgress = true;
+ rv = wwatch->OpenWindow(0, ALERT_CHROME_URL, "_blank",
+ "chrome,dialog=yes,titlebar=no,popup=yes", argsArray,
+ getter_AddRefs(newWindow));
+
+ if (NS_FAILED(rv))
+ AlertFinished();
+
+ return rv;
+}
+
+nsresult nsMessengerUnixIntegration::AlertFinished()
+{
+ mAlertInProgress = false;
+ return NS_OK;
+}
+
+nsresult nsMessengerUnixIntegration::AlertClicked()
+{
+ nsCString folderURI;
+ GetFirstFolderWithNewMail(folderURI);
+ openMailWindow(folderURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+ if (strcmp(aTopic, "alertfinished") == 0)
+ return AlertFinished();
+ if (strcmp(aTopic, "alertclickcallback") == 0)
+ return AlertClicked();
+
+ return NS_OK;
+}
+
+void nsMessengerUnixIntegration::FillToolTipInfo()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,);
+
+ bool showAlert = true;
+ prefBranch->GetBoolPref(SHOW_ALERT_PREF, &showAlert);
+ if (!showAlert)
+ return;
+
+ nsCString folderUri;
+ GetFirstFolderWithNewMail(folderUri);
+
+ uint32_t count = 0;
+ NS_ENSURE_SUCCESS_VOID(mFoldersWithNewMail->GetLength(&count));
+
+ nsCOMPtr<nsIWeakReference> weakReference;
+ nsCOMPtr<nsIMsgFolder> folder = nullptr;
+ nsCOMPtr<nsIMsgFolder> folderWithNewMail = nullptr;
+
+ uint32_t i;
+ for (i = 0; i < count && !folderWithNewMail; i++)
+ {
+ weakReference = do_QueryElementAt(mFoldersWithNewMail, i);
+ folder = do_QueryReferent(weakReference);
+ folder->GetChildWithURI(folderUri, true, true,
+ getter_AddRefs(folderWithNewMail));
+ }
+
+ if (folder && folderWithNewMail)
+ {
+ nsCOMPtr<nsIStringBundle> bundle;
+ GetStringBundle(getter_AddRefs(bundle));
+
+ if (!bundle)
+ return;
+
+ // Create the notification title
+ nsString alertTitle;
+ if (!BuildNotificationTitle(folder, bundle, alertTitle))
+ return;
+
+ // Let's get the new mail for this folder
+ nsCOMPtr<nsIMsgDatabase> db;
+ if (NS_FAILED(folderWithNewMail->GetMsgDatabase(getter_AddRefs(db))))
+ return;
+
+ uint32_t numNewKeys = 0;
+ uint32_t *newMessageKeys;
+ db->GetNewList(&numNewKeys, &newMessageKeys);
+
+ // If we had new messages, we *should* have new keys, but we'll
+ // check just in case.
+ if (numNewKeys <= 0) {
+ NS_Free(newMessageKeys);
+ return;
+ }
+
+ // Find the rootFolder that folder belongs to, and find out
+ // what MRUTime it maps to. Assign this to lastMRUTime.
+ uint32_t lastMRUTime = 0;
+ if (NS_FAILED(GetMRUTimestampForFolder(folder, &lastMRUTime)))
+ lastMRUTime = 0;
+
+ // Next, add the new message headers to an nsCOMArray. We
+ // only add message headers that are newer than lastMRUTime.
+ nsCOMArray<nsIMsgDBHdr> newMsgHdrs;
+ for (unsigned int i = 0; i < numNewKeys; ++i) {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ if (NS_FAILED(db->GetMsgHdrForKey(newMessageKeys[i], getter_AddRefs(hdr))))
+ continue;
+
+ uint32_t dateInSeconds = 0;
+ hdr->GetDateInSeconds(&dateInSeconds);
+
+ if (dateInSeconds > lastMRUTime)
+ newMsgHdrs.AppendObject(hdr);
+
+ }
+
+ // At this point, we don't need newMessageKeys any more,
+ // so let's free it.
+ NS_Free(newMessageKeys);
+
+ // If we didn't happen to add any message headers, bail out
+ if (!newMsgHdrs.Count())
+ return;
+
+ // Sort the message headers by dateInSeconds, in ascending
+ // order
+ newMsgHdrs.Sort(nsMsgDbHdrTimestampComparator, nullptr);
+
+ nsString alertBody;
+
+ // Build the body text of the notification.
+ if (!BuildNotificationBody(newMsgHdrs[0], bundle, alertBody))
+ return;
+
+ // Show the notification
+ ShowAlertMessage(alertTitle, alertBody, EmptyCString());
+
+ // Find the last, and therefore newest message header
+ // in our nsCOMArray
+ nsCOMPtr<nsIMsgDBHdr> lastMsgHdr = newMsgHdrs[newMsgHdrs.Count() - 1];
+
+ uint32_t dateInSeconds = 0;
+ if (NS_FAILED(lastMsgHdr->GetDateInSeconds(&dateInSeconds)))
+ return;
+
+ // Write the newest message timestamp to the appropriate
+ // mapping in our hashtable of MRUTime's.
+ PutMRUTimestampForFolder(folder, dateInSeconds);
+ } // if we got a folder
+}
+
+// Get the first top level folder which we know has new mail, then enumerate over
+// all the subfolders looking for the first real folder with new mail.
+// Return the folderURI for that folder.
+nsresult nsMessengerUnixIntegration::GetFirstFolderWithNewMail(nsACString& aFolderURI)
+{
+ NS_ENSURE_TRUE(mFoldersWithNewMail, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIWeakReference> weakReference;
+
+ uint32_t count = 0;
+ nsresult rv = mFoldersWithNewMail->GetLength(&count);
+ if (NS_FAILED(rv) || !count) // kick out if we don't have any folders with new mail
+ return NS_OK;
+
+ uint32_t i;
+ for(i = 0; i < count; i++)
+ {
+ weakReference = do_QueryElementAt(mFoldersWithNewMail, i);
+ folder = do_QueryReferent(weakReference);
+
+ // We only want to find folders which haven't been notified
+ // yet. This is specific to Thunderbird. In Seamonkey, we
+ // just return 0, and we don't care about timestamps anymore.
+ uint32_t lastMRUTime = 0;
+ rv = GetMRUTimestampForFolder(folder, &lastMRUTime);
+ if (NS_FAILED(rv))
+ lastMRUTime = 0;
+
+ if (!folder)
+ continue;
+ // enumerate over the folders under this root folder till we find one with new mail....
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nsCOMPtr<nsIArray> allFolders;
+ rv = folder->GetDescendants(getter_AddRefs(allFolders));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t subfolderCount = 0;
+ allFolders->GetLength(&subfolderCount);
+ uint32_t j;
+ for (j = 0; j < subfolderCount; j++)
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder = do_QueryElementAt(allFolders, j);
+
+ if (!msgFolder)
+ continue;
+
+ uint32_t flags;
+ rv = msgFolder->GetFlags(&flags);
+
+ if (NS_FAILED(rv))
+ continue;
+
+ bool notify =
+ // Any folder which is an inbox or ...
+ flags & nsMsgFolderFlags::Inbox ||
+ // any non-special or non-virtual folder. In other words, we don't
+ // notify for Drafts|Trash|SentMail|Templates|Junk|Archive|Queue or virtual.
+ !(flags & (nsMsgFolderFlags::SpecialUse | nsMsgFolderFlags::Virtual));
+
+ if (!notify)
+ continue;
+
+ nsCString folderURI;
+ msgFolder->GetURI(folderURI);
+ bool hasNew = false;
+ rv = msgFolder->GetHasNewMessages(&hasNew);
+
+ if (NS_FAILED(rv))
+ continue;
+
+ // Grab the MRUTime property from the folder
+ nsCString dateStr;
+ msgFolder->GetStringProperty(MRU_TIME_PROPERTY, dateStr);
+ uint32_t MRUTime = (uint32_t) dateStr.ToInteger(&rv, 10);
+ if (NS_FAILED(rv))
+ MRUTime = 0;
+
+ if (hasNew && MRUTime > lastMRUTime)
+ {
+ rv = msgFolder->GetURI(aFolderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ } // if we have more potential folders to enumerate
+ }
+
+ // If we got here, then something when pretty wrong.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnItemAdded(nsIMsgFolder *, nsISupports *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnItemBoolPropertyChanged(nsIMsgFolder *aItem,
+ nsIAtom *aProperty,
+ bool aOldValue,
+ bool aNewValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnItemEvent(nsIMsgFolder *, nsIAtom *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty, int64_t aOldValue, int64_t aNewValue)
+{
+ nsCString atomName;
+ // if we got new mail show an icon in the system tray
+ if (mBiffStateAtom == aProperty && mFoldersWithNewMail)
+ {
+ nsCOMPtr<nsIWeakReference> weakFolder = do_GetWeakReference(aItem);
+ uint32_t indexInNewArray;
+ nsresult rv = mFoldersWithNewMail->IndexOf(0, weakFolder, &indexInNewArray);
+ bool folderFound = NS_SUCCEEDED(rv);
+
+ if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail)
+ {
+ // only show a system tray icon iff we are performing biff
+ // (as opposed to the user getting new mail)
+ bool performingBiff = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ aItem->GetServer(getter_AddRefs(server));
+ if (server)
+ server->GetPerformingBiff(&performingBiff);
+ if (!performingBiff)
+ return NS_OK; // kick out right now...
+
+ if (!folderFound)
+ mFoldersWithNewMail->AppendElement(weakFolder, false);
+ // now regenerate the tooltip
+ FillToolTipInfo();
+ }
+ else if (aNewValue == nsIMsgFolder::nsMsgBiffState_NoMail)
+ {
+ if (folderFound) {
+ mFoldersWithNewMail->RemoveElementAt(indexInNewArray);
+ }
+ }
+ } // if the biff property changed
+ else if (mNewMailReceivedAtom == aProperty)
+ {
+ FillToolTipInfo();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnStartRunningUrl(nsIURI *aUrl)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ if (NS_SUCCEEDED(aExitCode))
+ // preview fetch is done.
+ FillToolTipInfo();
+ return NS_OK;
+}
+
+nsresult
+nsMessengerUnixIntegration::GetMRUTimestampForFolder(nsIMsgFolder *aFolder,
+ uint32_t *aLastMRUTime)
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder = nullptr;
+ nsresult rv = aFolder->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString rootFolderURI;
+ rootFolder->GetURI(rootFolderURI);
+ if (!mLastMRUTimes.Get(rootFolderURI, aLastMRUTime))
+ aLastMRUTime = 0;
+
+ return NS_OK;
+}
+
+nsresult
+nsMessengerUnixIntegration::PutMRUTimestampForFolder(nsIMsgFolder *aFolder,
+ uint32_t aLastMRUTime)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> rootFolder = nullptr;
+ rv = aFolder->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString rootFolderURI;
+ rootFolder->GetURI(rootFolderURI);
+ mLastMRUTimes.Put(rootFolderURI, aLastMRUTime);
+
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMessengerUnixIntegration.h b/mailnews/base/src/nsMessengerUnixIntegration.h
new file mode 100644
index 000000000..ba9f0f3f5
--- /dev/null
+++ b/mailnews/base/src/nsMessengerUnixIntegration.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMessengerUnixIntegration_h
+#define __nsMessengerUnixIntegration_h
+
+#include "nsIMessengerOSIntegration.h"
+#include "nsIFolderListener.h"
+#include "nsIUrlListener.h"
+#include "nsIMutableArray.h"
+#include "nsIStringBundle.h"
+#include "nsIObserver.h"
+#include "nsIAtom.h"
+#include "nsDataHashtable.h"
+#include "nsTArray.h"
+
+#define NS_MESSENGERUNIXINTEGRATION_CID \
+ {0xf62f3d3a, 0x1dd1, 0x11b2, \
+ {0xa5, 0x16, 0xef, 0xad, 0xb1, 0x31, 0x61, 0x5c}}
+
+class nsIStringBundle;
+
+class nsMessengerUnixIntegration : public nsIFolderListener,
+ public nsIObserver,
+ public nsIUrlListener,
+ public nsIMessengerOSIntegration
+{
+public:
+ nsMessengerUnixIntegration();
+ virtual nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMESSENGEROSINTEGRATION
+ NS_DECL_NSIFOLDERLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIURLLISTENER
+
+private:
+ virtual ~nsMessengerUnixIntegration() {}
+ nsresult ShowAlertMessage(const nsAString& aAlertTitle, const nsAString& aAlertText, const nsACString& aFolderURI);
+ nsresult GetFirstFolderWithNewMail(nsACString& aFolderURI);
+ nsresult GetStringBundle(nsIStringBundle **aBundle);
+ nsresult AlertFinished();
+ nsresult AlertClicked();
+ void FillToolTipInfo();
+ nsresult GetMRUTimestampForFolder(nsIMsgFolder *aFolder, uint32_t *aLastMRUTime);
+
+ bool BuildNotificationBody(nsIMsgDBHdr *aHdr, nsIStringBundle *Bundle, nsString &aBody);
+ bool BuildNotificationTitle(nsIMsgFolder *aFolder, nsIStringBundle *aBundle, nsString &aTitle);
+ nsresult ShowNewAlertNotification(bool aUserInitiated);
+ nsresult PutMRUTimestampForFolder(nsIMsgFolder *aFolder, uint32_t aLastMRUTime);
+
+ nsCOMPtr<nsIMutableArray> mFoldersWithNewMail; // keep track of all the root folders with pending new mail
+ nsCOMPtr<nsIAtom> mBiffStateAtom;
+ nsCOMPtr<nsIAtom> mNewMailReceivedAtom;
+ bool mAlertInProgress;
+ nsDataHashtable<nsCStringHashKey, uint32_t> mLastMRUTimes; // We keep track of the last time we did a new mail notification for each account
+ nsTArray<nsCString> mFetchingURIs;
+};
+
+#endif // __nsMessengerUnixIntegration_h
diff --git a/mailnews/base/src/nsMessengerWinIntegration.cpp b/mailnews/base/src/nsMessengerWinIntegration.cpp
new file mode 100644
index 000000000..ff0c08041
--- /dev/null
+++ b/mailnews/base/src/nsMessengerWinIntegration.cpp
@@ -0,0 +1,1184 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include <shellapi.h>
+
+#include "nsMessengerWinIntegration.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgIdentity.h"
+#include "nsIMsgAccount.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgWindow.h"
+#include "nsCOMPtr.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIDirectoryService.h"
+#include "nsIWindowWatcher.h"
+#include "nsIWindowMediator.h"
+#include "mozIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+#include "nsWidgetsCID.h"
+#include "MailNewsTypes.h"
+#include "nsIMessengerWindowService.h"
+#include "prprf.h"
+#include "nsIWeakReference.h"
+#include "nsIStringBundle.h"
+#include "nsIAlertsService.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIProperties.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsMsgUtils.h"
+#ifdef MOZILLA_INTERNAL_API
+#include "mozilla/LookAndFeel.h"
+#endif
+#include "mozilla/Services.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+
+#include "nsToolkitCompsCID.h"
+#include <stdlib.h>
+#define PROFILE_COMMANDLINE_ARG " -profile "
+
+#define NOTIFICATIONCLASSNAME "MailBiffNotificationMessageWindow"
+#define UNREADMAILNODEKEY "Software\\Microsoft\\Windows\\CurrentVersion\\UnreadMail\\"
+#define DOUBLE_QUOTE "\""
+#define MAIL_COMMANDLINE_ARG " -mail"
+#define IDI_MAILBIFF 32576
+#define UNREAD_UPDATE_INTERVAL (20 * 1000) // 20 seconds
+#define ALERT_CHROME_URL "chrome://messenger/content/newmailalert.xul"
+#define NEW_MAIL_ALERT_ICON "chrome://messenger/skin/icons/new-mail-alert.png"
+#define SHOW_ALERT_PREF "mail.biff.show_alert"
+#define SHOW_TRAY_ICON_PREF "mail.biff.show_tray_icon"
+#define SHOW_BALLOON_PREF "mail.biff.show_balloon"
+#define SHOW_NEW_ALERT_PREF "mail.biff.show_new_alert"
+#define ALERT_ORIGIN_PREF "ui.alertNotificationOrigin"
+
+// since we are including windows.h in this file, undefine get user name....
+#ifdef GetUserName
+#undef GetUserName
+#endif
+
+#ifndef NIIF_USER
+#define NIIF_USER 0x00000004
+#endif
+
+#ifndef NIIF_NOSOUND
+#define NIIF_NOSOUND 0x00000010
+#endif
+
+#ifndef NIN_BALOONUSERCLICK
+#define NIN_BALLOONUSERCLICK (WM_USER + 5)
+#endif
+
+#ifndef MOZILLA_INTERNAL_API
+// from LookAndFeel.h
+#define NS_ALERT_HORIZONTAL 1
+#define NS_ALERT_LEFT 2
+#define NS_ALERT_TOP 4
+#endif
+
+using namespace mozilla;
+
+// begin shameless copying from nsNativeAppSupportWin
+HWND hwndForDOMWindow( mozIDOMWindowProxy *window )
+{
+ if ( !window ) {
+ return 0;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> pidomwindow = nsPIDOMWindowOuter::From(window);
+
+ nsCOMPtr<nsIBaseWindow> ppBaseWindow =
+ do_QueryInterface( pidomwindow->GetDocShell() );
+ if (!ppBaseWindow)
+ return 0;
+
+ nsCOMPtr<nsIWidget> ppWidget;
+ ppBaseWindow->GetMainWidget( getter_AddRefs( ppWidget ) );
+
+ return (HWND)( ppWidget->GetNativeData( NS_NATIVE_WIDGET ) );
+}
+
+static void activateWindow( mozIDOMWindowProxy *win )
+{
+ // Try to get native window handle.
+ HWND hwnd = hwndForDOMWindow( win );
+ if ( hwnd )
+ {
+ // Restore the window if it is minimized.
+ if ( ::IsIconic( hwnd ) )
+ ::ShowWindow( hwnd, SW_RESTORE );
+ // Use the OS call, if possible.
+ ::SetForegroundWindow( hwnd );
+ } else {
+ // Use internal method.
+ nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(win);
+ privateWindow->Focus();
+ }
+}
+// end shameless copying from nsNativeAppWinSupport.cpp
+
+static void openMailWindow(const nsACString& aFolderUri)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIMsgWindow> topMostMsgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow));
+ if (topMostMsgWindow)
+ {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow));
+ if (domWindow)
+ {
+ if (!aFolderUri.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ topMostMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectFolder(aFolderUri);
+ }
+ activateWindow(domWindow);
+ return;
+ }
+ }
+
+ {
+ // the user doesn't have a mail window open already so open one for them...
+ nsCOMPtr<nsIMessengerWindowService> messengerWindowService =
+ do_GetService(NS_MESSENGERWINDOWSERVICE_CONTRACTID);
+ // if we want to preselect the first account with new mail,
+ // here is where we would try to generate a uri to pass in
+ // (and add code to the messenger window service to make that work)
+ if (messengerWindowService)
+ messengerWindowService->OpenMessengerWindowWithUri(
+ "mail:3pane", nsCString(aFolderUri).get(), nsMsgKey_None);
+ }
+}
+
+static void CALLBACK delayedSingleClick(HWND msgWindow, UINT msg, INT_PTR idEvent, DWORD dwTime)
+{
+ ::KillTimer(msgWindow, idEvent);
+
+ // single clicks on the biff icon should re-open the alert notification
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMessengerOSIntegration> integrationService =
+ do_GetService(NS_MESSENGEROSINTEGRATION_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ // we know we are dealing with the windows integration object
+ nsMessengerWinIntegration * winIntegrationService = static_cast<nsMessengerWinIntegration*>
+ (static_cast<nsIMessengerOSIntegration*>(integrationService.get()));
+ winIntegrationService->ShowNewAlertNotification(true, EmptyString(), EmptyString());
+ }
+}
+
+// Window proc.
+static LRESULT CALLBACK MessageWindowProc( HWND msgWindow, UINT msg, WPARAM wp, LPARAM lp )
+{
+ if (msg == WM_USER)
+ {
+ if (lp == WM_LBUTTONDOWN)
+ {
+ // the only way to tell a single left click
+ // from a double left click is to fire a timer which gets cleared if we end up getting
+ // a WM_LBUTTONDBLK event.
+ ::SetTimer(msgWindow, 1, GetDoubleClickTime(), (TIMERPROC) delayedSingleClick);
+ }
+ else if (lp == WM_LBUTTONDBLCLK || lp == NIN_BALLOONUSERCLICK)
+ {
+ ::KillTimer(msgWindow, 1);
+ openMailWindow(EmptyCString());
+ }
+ }
+
+ return TRUE;
+}
+
+static HWND msgWindow;
+
+// Create: Register class and create window.
+static nsresult Create()
+{
+ if (msgWindow)
+ return NS_OK;
+
+ WNDCLASS classStruct = { 0, // style
+ &MessageWindowProc, // lpfnWndProc
+ 0, // cbClsExtra
+ 0, // cbWndExtra
+ 0, // hInstance
+ 0, // hIcon
+ 0, // hCursor
+ 0, // hbrBackground
+ 0, // lpszMenuName
+ NOTIFICATIONCLASSNAME }; // lpszClassName
+
+ // Register the window class.
+ NS_ENSURE_TRUE( ::RegisterClass( &classStruct ), NS_ERROR_FAILURE );
+ // Create the window.
+ NS_ENSURE_TRUE( msgWindow = ::CreateWindow( NOTIFICATIONCLASSNAME,
+ 0, // title
+ WS_CAPTION, // style
+ 0,0,0,0, // x, y, cx, cy
+ 0, // parent
+ 0, // menu
+ 0, // instance
+ 0 ), // create struct
+ NS_ERROR_FAILURE );
+ return NS_OK;
+}
+
+
+nsMessengerWinIntegration::nsMessengerWinIntegration()
+{
+ mDefaultServerAtom = MsgGetAtom("DefaultServer");
+ mTotalUnreadMessagesAtom = MsgGetAtom("TotalUnreadMessages");
+
+ mUnreadTimerActive = false;
+
+ mBiffStateAtom = MsgGetAtom("BiffState");
+ mBiffIconVisible = false;
+ mSuppressBiffIcon = false;
+ mAlertInProgress = false;
+ mBiffIconInitialized = false;
+ mFoldersWithNewMail = do_CreateInstance(NS_ARRAY_CONTRACTID);
+}
+
+nsMessengerWinIntegration::~nsMessengerWinIntegration()
+{
+ if (mUnreadCountUpdateTimer) {
+ mUnreadCountUpdateTimer->Cancel();
+ mUnreadCountUpdateTimer = nullptr;
+ }
+
+ // one last attempt, update the registry
+ nsresult rv = UpdateRegistryWithCurrent();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to update registry on shutdown");
+ DestroyBiffIcon();
+}
+
+NS_IMPL_ADDREF(nsMessengerWinIntegration)
+NS_IMPL_RELEASE(nsMessengerWinIntegration)
+
+NS_INTERFACE_MAP_BEGIN(nsMessengerWinIntegration)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessengerOSIntegration)
+ NS_INTERFACE_MAP_ENTRY(nsIMessengerOSIntegration)
+ NS_INTERFACE_MAP_ENTRY(nsIFolderListener)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+
+nsresult
+nsMessengerWinIntegration::ResetCurrent()
+{
+ mInboxURI.Truncate();
+ mEmail.Truncate();
+
+ mCurrentUnreadCount = -1;
+ mLastUnreadCountWrittenToRegistry = -1;
+
+ mDefaultAccountMightHaveAnInbox = true;
+ return NS_OK;
+}
+
+NOTIFYICONDATAW sBiffIconData = { NOTIFYICONDATAW_V2_SIZE,
+ 0,
+ 2,
+ NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO,
+ WM_USER,
+ 0,
+ L"",
+ 0,
+ 0,
+ L"",
+ 30000,
+ L"",
+ NIIF_USER | NIIF_NOSOUND };
+// allow for the null terminator
+static const uint32_t kMaxTooltipSize = sizeof(sBiffIconData.szTip) /
+ sizeof(sBiffIconData.szTip[0]) - 1;
+static const uint32_t kMaxBalloonSize = sizeof(sBiffIconData.szInfo) /
+ sizeof(sBiffIconData.szInfo[0]) - 1;
+static const uint32_t kMaxBalloonTitle = sizeof(sBiffIconData.szInfoTitle) /
+ sizeof(sBiffIconData.szInfoTitle[0]) - 1;
+
+void nsMessengerWinIntegration::InitializeBiffStatusIcon()
+{
+ // initialize our biff status bar icon
+ Create();
+
+ sBiffIconData.hWnd = (HWND) msgWindow;
+ sBiffIconData.hIcon = ::LoadIcon( ::GetModuleHandle( NULL ), MAKEINTRESOURCE(IDI_MAILBIFF) );
+
+ mBiffIconInitialized = true;
+}
+
+nsresult
+nsMessengerWinIntegration::Init()
+{
+ nsresult rv;
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // because we care if the default server changes
+ rv = accountManager->AddRootFolderListener(this);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // because we care if the unread total count changes
+ rv = mailSession->AddFolderListener(this, nsIFolderListener::boolPropertyChanged | nsIFolderListener::intPropertyChanged);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // get current profile path for the commandliner
+ nsCOMPtr<nsIProperties> directoryService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIFile> profilePath;
+ rv = directoryService->Get(NS_APP_USER_PROFILE_50_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(profilePath));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = profilePath->GetPath(mProfilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get application path
+ WCHAR appPath[_MAX_PATH] = {0};
+ ::GetModuleFileNameW(nullptr, appPath, sizeof(appPath));
+ mAppName.Assign((char16_t *)appPath);
+
+ rv = ResetCurrent();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::OnItemUnicharPropertyChanged(nsIMsgFolder *, nsIAtom *, const char16_t *, const char16_t *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::OnItemRemoved(nsIMsgFolder *, nsISupports *)
+{
+ return NS_OK;
+}
+
+nsresult nsMessengerWinIntegration::GetStringBundle(nsIStringBundle **aBundle)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ NS_IF_ADDREF(*aBundle = bundle);
+ return NS_OK;
+}
+
+#ifndef MOZ_THUNDERBIRD
+nsresult nsMessengerWinIntegration::ShowAlertMessage(const nsString& aAlertTitle,
+ const nsString& aAlertText,
+ const nsACString& aFolderURI)
+{
+ nsresult rv;
+
+ // if we are already in the process of showing an alert, don't try to show another....
+ if (mAlertInProgress)
+ return NS_OK;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool showBalloon = false;
+ prefBranch->GetBoolPref(SHOW_BALLOON_PREF, &showBalloon);
+ sBiffIconData.szInfo[0] = '\0';
+ if (showBalloon) {
+ ::wcsncpy( sBiffIconData.szInfoTitle, aAlertTitle.get(), kMaxBalloonTitle);
+ ::wcsncpy( sBiffIconData.szInfo, aAlertText.get(), kMaxBalloonSize);
+ }
+
+ bool showAlert = true;
+ prefBranch->GetBoolPref(SHOW_ALERT_PREF, &showAlert);
+
+ if (showAlert)
+ {
+ nsCOMPtr<nsIAlertsService> alertsService (do_GetService(NS_ALERTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = alertsService->ShowAlertNotification(NS_LITERAL_STRING(NEW_MAIL_ALERT_ICON), aAlertTitle,
+ aAlertText, true,
+ NS_ConvertASCIItoUTF16(aFolderURI), this,
+ EmptyString(),
+ NS_LITERAL_STRING("auto"),
+ EmptyString(), EmptyString(),
+ nullptr,
+ false,
+ false);
+ mAlertInProgress = true;
+ }
+ }
+
+ if (!showAlert || NS_FAILED(rv)) // go straight to showing the system tray icon.
+ AlertFinished();
+
+ return rv;
+}
+#endif
+// Opening Thunderbird's new mail alert notification window
+// aUserInitiated --> true if we are opening the alert notification in response to a user action
+// like clicking on the biff icon
+nsresult nsMessengerWinIntegration::ShowNewAlertNotification(bool aUserInitiated, const nsString& aAlertTitle, const nsString& aAlertText)
+{
+ nsresult rv;
+
+ // if we are already in the process of showing an alert, don't try to show another....
+ if (mAlertInProgress)
+ return NS_OK;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool showBalloon = false;
+ prefBranch->GetBoolPref(SHOW_BALLOON_PREF, &showBalloon);
+ sBiffIconData.szInfo[0] = '\0';
+ if (showBalloon) {
+ ::wcsncpy( sBiffIconData.szInfoTitle, aAlertTitle.get(), kMaxBalloonTitle);
+ ::wcsncpy( sBiffIconData.szInfo, aAlertText.get(), kMaxBalloonSize);
+ }
+
+ bool showAlert = true;
+
+ if (prefBranch)
+ prefBranch->GetBoolPref(SHOW_ALERT_PREF, &showAlert);
+
+ // check if we are allowed to show a notification
+ if (showAlert) {
+ QUERY_USER_NOTIFICATION_STATE qstate;
+
+ if (SUCCEEDED(SHQueryUserNotificationState(&qstate))) {
+ if (qstate != QUNS_ACCEPTS_NOTIFICATIONS) {
+ showAlert = false;
+ }
+ }
+ }
+
+ if (showAlert)
+ {
+ nsCOMPtr<nsIMutableArray> argsArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pass in the array of folders with unread messages
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ifptr->SetData(mFoldersWithNewMail);
+ ifptr->SetDataIID(&NS_GET_IID(nsIArray));
+ rv = argsArray->AppendElement(ifptr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pass in the observer
+ ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMessengerOSIntegration*>(this));
+ ifptr->SetData(supports);
+ ifptr->SetDataIID(&NS_GET_IID(nsIObserver));
+ rv = argsArray->AppendElement(ifptr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pass in the animation flag
+ nsCOMPtr<nsISupportsPRBool> scriptableUserInitiated (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ scriptableUserInitiated->SetData(aUserInitiated);
+ rv = argsArray->AppendElement(scriptableUserInitiated, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pass in the alert origin
+ nsCOMPtr<nsISupportsPRUint8> scriptableOrigin (do_CreateInstance(NS_SUPPORTS_PRUINT8_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableOrigin, NS_ERROR_FAILURE);
+ scriptableOrigin->SetData(0);
+ int32_t origin = 0;
+#ifdef MOZILLA_INTERNAL_API
+ origin = LookAndFeel::GetInt(LookAndFeel::eIntID_AlertNotificationOrigin);
+#else
+ // Get task bar window handle
+ HWND shellWindow = FindWindowW(L"Shell_TrayWnd", NULL);
+
+ rv = prefBranch->GetIntPref(ALERT_ORIGIN_PREF, &origin);
+ if (NS_FAILED(rv) && (shellWindow != NULL))
+ {
+ // Determine position
+ APPBARDATA appBarData;
+ appBarData.hWnd = shellWindow;
+ appBarData.cbSize = sizeof(appBarData);
+ if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData))
+ {
+ // Set alert origin as a bit field - see LookAndFeel.h
+ // 0 represents bottom right, sliding vertically.
+ switch(appBarData.uEdge)
+ {
+ case ABE_LEFT:
+ origin = NS_ALERT_HORIZONTAL | NS_ALERT_LEFT;
+ break;
+ case ABE_RIGHT:
+ origin = NS_ALERT_HORIZONTAL;
+ break;
+ case ABE_TOP:
+ origin = NS_ALERT_TOP;
+ // fall through for the right-to-left handling.
+ case ABE_BOTTOM:
+ // If the task bar is right-to-left,
+ // move the origin to the left
+ if (::GetWindowLong(shellWindow, GWL_EXSTYLE) &
+ WS_EX_LAYOUTRTL)
+ origin |= NS_ALERT_LEFT;
+ break;
+ }
+ }
+ }
+#endif
+ scriptableOrigin->SetData(origin);
+
+ rv = argsArray->AppendElement(scriptableOrigin, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ rv = wwatch->OpenWindow(0, ALERT_CHROME_URL, "_blank",
+ "chrome,dialog=yes,titlebar=no,popup=yes", argsArray,
+ getter_AddRefs(newWindow));
+
+ mAlertInProgress = true;
+ }
+
+ // if the user has turned off the mail alert, or openWindow generated an error,
+ // then go straight to the system tray.
+ if (!showAlert || NS_FAILED(rv))
+ AlertFinished();
+
+ return rv;
+}
+
+nsresult nsMessengerWinIntegration::AlertFinished()
+{
+ // okay, we are done showing the alert
+ // now put an icon in the system tray, if allowed
+ bool showTrayIcon = !mSuppressBiffIcon || sBiffIconData.szInfo[0];
+ if (showTrayIcon)
+ {
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ prefBranch->GetBoolPref(SHOW_TRAY_ICON_PREF, &showTrayIcon);
+ }
+ if (showTrayIcon)
+ {
+ GenericShellNotify(NIM_ADD);
+ mBiffIconVisible = true;
+ }
+ mSuppressBiffIcon = false;
+ mAlertInProgress = false;
+ return NS_OK;
+}
+
+nsresult nsMessengerWinIntegration::AlertClicked()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIMsgWindow> topMostMsgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow));
+ if (topMostMsgWindow)
+ {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow));
+ if (domWindow)
+ {
+ activateWindow(domWindow);
+ return NS_OK;
+ }
+ }
+ // make sure we don't insert the icon in the system tray since the user clicked on the alert.
+ mSuppressBiffIcon = true;
+ nsCString folderURI;
+ GetFirstFolderWithNewMail(folderURI);
+ openMailWindow(folderURI);
+ return NS_OK;
+}
+
+#ifdef MOZ_SUITE
+nsresult nsMessengerWinIntegration::AlertClickedSimple()
+{
+ mSuppressBiffIcon = true;
+ return NS_OK;
+}
+#endif MOZ_SUITE
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+ if (strcmp(aTopic, "alertfinished") == 0)
+ return AlertFinished();
+
+ if (strcmp(aTopic, "alertclickcallback") == 0)
+ return AlertClicked();
+
+#ifdef MOZ_SUITE
+ // SeaMonkey does most of the GUI work in JS code when clicking on a mail
+ // notification, so it needs an extra function here
+ if (strcmp(aTopic, "alertclicksimplecallback") == 0)
+ return AlertClickedSimple();
+#endif
+
+ return NS_OK;
+}
+
+static void EscapeAmpersands(nsString& aToolTip)
+{
+ // First, check to see whether we have any ampersands.
+ int32_t pos = aToolTip.FindChar('&');
+ if (pos == kNotFound)
+ return;
+
+ // Next, see if we only have bare ampersands.
+ pos = MsgFind(aToolTip, "&&", false, pos);
+
+ // Windows tooltip code removes one ampersand from each run,
+ // then collapses pairs of amperands. This means that in the easy case,
+ // we need to replace each ampersand with three.
+ MsgReplaceSubstring(aToolTip, NS_LITERAL_STRING("&"), NS_LITERAL_STRING("&&&"));
+ if (pos == kNotFound)
+ return;
+
+ // We inserted too many ampersands. Remove some.
+ for (;;) {
+ pos = MsgFind(aToolTip, "&&&&&&", false, pos);
+ if (pos == kNotFound)
+ return;
+
+ aToolTip.Cut(pos, 1);
+ pos += 2;
+ }
+}
+
+void nsMessengerWinIntegration::FillToolTipInfo()
+{
+ // iterate over all the folders in mFoldersWithNewMail
+ nsString accountName;
+ nsCString hostName;
+ nsString toolTipLine;
+ nsAutoString toolTipText;
+ nsAutoString animatedAlertText;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIWeakReference> weakReference;
+ int32_t numNewMessages = 0;
+
+ uint32_t count = 0;
+ NS_ENSURE_SUCCESS_VOID(mFoldersWithNewMail->GetLength(&count));
+
+ for (uint32_t index = 0; index < count; index++)
+ {
+ weakReference = do_QueryElementAt(mFoldersWithNewMail, index);
+ folder = do_QueryReferent(weakReference);
+ if (folder)
+ {
+ folder->GetPrettiestName(accountName);
+
+ numNewMessages = 0;
+ folder->GetNumNewMessages(true, &numNewMessages);
+ nsCOMPtr<nsIStringBundle> bundle;
+ GetStringBundle(getter_AddRefs(bundle));
+ if (bundle)
+ {
+ nsAutoString numNewMsgsText;
+ numNewMsgsText.AppendInt(numNewMessages);
+
+ const char16_t *formatStrings[] =
+ {
+ numNewMsgsText.get(),
+ };
+
+ nsString finalText;
+ if (numNewMessages == 1)
+ bundle->FormatStringFromName(u"biffNotification_message", formatStrings, 1, getter_Copies(finalText));
+ else
+ bundle->FormatStringFromName(u"biffNotification_messages", formatStrings, 1, getter_Copies(finalText));
+
+ // the alert message is special...we actually only want to show the first account with
+ // new mail in the alert.
+ if (animatedAlertText.IsEmpty()) // if we haven't filled in the animated alert text yet
+ animatedAlertText = finalText;
+
+ toolTipLine.Append(accountName);
+ toolTipLine.Append(' ');
+ toolTipLine.Append(finalText);
+ EscapeAmpersands(toolTipLine);
+
+ // only add this new string if it will fit without truncation....
+ if (toolTipLine.Length() + toolTipText.Length() <= kMaxTooltipSize)
+ toolTipText.Append(toolTipLine);
+
+ // clear out the tooltip line for the next folder
+ toolTipLine.Assign('\n');
+ } // if we got a bundle
+ } // if we got a folder
+ } // for each folder
+
+ ::wcsncpy( sBiffIconData.szTip, toolTipText.get(), kMaxTooltipSize);
+
+ if (!mBiffIconVisible)
+ {
+#ifndef MOZ_THUNDERBIRD
+ nsresult rv;
+ bool showNewAlert = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ prefBranch->GetBoolPref(SHOW_NEW_ALERT_PREF, &showNewAlert);
+ if (!showNewAlert)
+ ShowAlertMessage(accountName, animatedAlertText, EmptyCString());
+ else
+#endif
+ ShowNewAlertNotification(false, accountName, animatedAlertText);
+ }
+ else
+ GenericShellNotify( NIM_MODIFY);
+}
+
+// Get the first top level folder which we know has new mail, then enumerate over
+// all the subfolders looking for the first real folder with new mail.
+// Return the folderURI for that folder.
+nsresult nsMessengerWinIntegration::GetFirstFolderWithNewMail(nsACString& aFolderURI)
+{
+ NS_ENSURE_TRUE(mFoldersWithNewMail, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIWeakReference> weakReference;
+ int32_t numNewMessages = 0;
+
+ uint32_t count = 0;
+ nsresult rv = mFoldersWithNewMail->GetLength(&count);
+ if (NS_FAILED(rv) || !count) // kick out if we don't have any folders with new mail
+ return NS_OK;
+
+ weakReference = do_QueryElementAt(mFoldersWithNewMail, 0);
+ folder = do_QueryReferent(weakReference);
+
+ if (folder)
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ // enumerate over the folders under this root folder till we find one with new mail....
+ nsCOMPtr<nsIArray> allFolders;
+ rv = folder->GetDescendants(getter_AddRefs(allFolders));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = allFolders->Enumerate(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ nsCOMPtr<nsISupports> supports;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv) && supports)
+ {
+ msgFolder = do_QueryInterface(supports, &rv);
+ if (msgFolder)
+ {
+ numNewMessages = 0;
+ msgFolder->GetNumNewMessages(false, &numNewMessages);
+ if (numNewMessages)
+ break; // kick out of the while loop
+ }
+ } // if we have a folder
+ } // if we have more potential folders to enumerate
+ } // if enumerator
+
+ if (msgFolder)
+ msgFolder->GetURI(aFolderURI);
+ }
+
+ return NS_OK;
+}
+
+void nsMessengerWinIntegration::DestroyBiffIcon()
+{
+ GenericShellNotify(NIM_DELETE);
+ // Don't call DestroyIcon(). see http://bugzilla.mozilla.org/show_bug.cgi?id=134745
+}
+
+void nsMessengerWinIntegration::GenericShellNotify(DWORD aMessage)
+{
+ ::Shell_NotifyIconW( aMessage, &sBiffIconData );
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::OnItemAdded(nsIMsgFolder *, nsISupports *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::OnItemBoolPropertyChanged(nsIMsgFolder *aItem,
+ nsIAtom *aProperty,
+ bool aOldValue,
+ bool aNewValue)
+{
+ if (aProperty == mDefaultServerAtom) {
+ nsresult rv;
+
+ // this property changes multiple times
+ // on account deletion or when the user changes their
+ // default account. ResetCurrent() will set
+ // mInboxURI to null, so we use that
+ // to prevent us from attempting to remove
+ // something from the registry that has already been removed
+ if (!mInboxURI.IsEmpty() && !mEmail.IsEmpty()) {
+ rv = RemoveCurrentFromRegistry();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // reset so we'll go get the new default server next time
+ rv = ResetCurrent();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = UpdateUnreadCount();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::OnItemEvent(nsIMsgFolder *, nsIAtom *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty, int64_t aOldValue, int64_t aNewValue)
+{
+ // if we got new mail show a icon in the system tray
+ if (mBiffStateAtom == aProperty && mFoldersWithNewMail)
+ {
+ nsCOMPtr<nsIWeakReference> weakFolder = do_GetWeakReference(aItem);
+ uint32_t indexInNewArray;
+ nsresult rv = mFoldersWithNewMail->IndexOf(0, weakFolder, &indexInNewArray);
+ bool folderFound = NS_SUCCEEDED(rv);
+
+ if (!mBiffIconInitialized)
+ InitializeBiffStatusIcon();
+
+ if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail)
+ {
+ // if the icon is not already visible, only show a system tray icon iff
+ // we are performing biff (as opposed to the user getting new mail)
+ if (!mBiffIconVisible)
+ {
+ bool performingBiff = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ aItem->GetServer(getter_AddRefs(server));
+ if (server)
+ server->GetPerformingBiff(&performingBiff);
+ if (!performingBiff)
+ return NS_OK; // kick out right now...
+ }
+ if (!folderFound)
+ mFoldersWithNewMail->InsertElementAt(weakFolder, 0, false);
+ // now regenerate the tooltip
+ FillToolTipInfo();
+ }
+ else if (aNewValue == nsIMsgFolder::nsMsgBiffState_NoMail)
+ {
+ // we are always going to remove the icon whenever we get our first no mail
+ // notification.
+
+ // avoid a race condition where we are told to remove the icon before we've actually
+ // added it to the system tray. This happens when the user reads a new message before
+ // the animated alert has gone away.
+ if (mAlertInProgress)
+ mSuppressBiffIcon = true;
+
+ if (folderFound)
+ mFoldersWithNewMail->RemoveElementAt(indexInNewArray);
+ if (mBiffIconVisible)
+ {
+ mBiffIconVisible = false;
+ GenericShellNotify(NIM_DELETE);
+ }
+ }
+ } // if the biff property changed
+
+ if (aProperty == mTotalUnreadMessagesAtom) {
+ nsCString itemURI;
+ nsresult rv;
+ rv = aItem->GetURI(itemURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (mInboxURI.Equals(itemURI))
+ mCurrentUnreadCount = aNewValue;
+
+ // If the timer isn't running yet, then we immediately update the
+ // registry and then start a one-shot timer. If the Unread counter
+ // has toggled zero / nonzero, we also update immediately.
+ // Otherwise, if the timer is running, defer the update. This means
+ // that all counter updates that occur within the timer interval are
+ // batched into a single registry update, to avoid hitting the
+ // registry too frequently. We also do a final update on shutdown,
+ // regardless of the timer.
+ if (!mUnreadTimerActive ||
+ (!mCurrentUnreadCount && mLastUnreadCountWrittenToRegistry) ||
+ (mCurrentUnreadCount && mLastUnreadCountWrittenToRegistry < 1)) {
+ rv = UpdateUnreadCount();
+ NS_ENSURE_SUCCESS(rv,rv);
+ // If timer wasn't running, start it.
+ if (!mUnreadTimerActive)
+ rv = SetupUnreadCountUpdateTimer();
+ }
+ }
+ return NS_OK;
+}
+
+void
+nsMessengerWinIntegration::OnUnreadCountUpdateTimer(nsITimer *timer, void *osIntegration)
+{
+ nsMessengerWinIntegration *winIntegration = (nsMessengerWinIntegration*)osIntegration;
+
+ winIntegration->mUnreadTimerActive = false;
+ nsresult rv = winIntegration->UpdateUnreadCount();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "updating unread count failed");
+}
+
+nsresult
+nsMessengerWinIntegration::RemoveCurrentFromRegistry()
+{
+ // If Windows XP, open the registry and get rid of old account registry entries
+ // If there is a email prefix, get it and use it to build the registry key.
+ // Otherwise, just the email address will be the registry key.
+ nsAutoString currentUnreadMailCountKey;
+ if (!mEmailPrefix.IsEmpty()) {
+ currentUnreadMailCountKey.Assign(mEmailPrefix);
+ currentUnreadMailCountKey.Append(NS_ConvertASCIItoUTF16(mEmail));
+ }
+ else
+ CopyASCIItoUTF16(mEmail, currentUnreadMailCountKey);
+
+ WCHAR registryUnreadMailCountKey[_MAX_PATH] = {0};
+ // Enumerate through registry entries to delete the key matching
+ // currentUnreadMailCountKey
+ int index = 0;
+ while (SUCCEEDED(SHEnumerateUnreadMailAccountsW(HKEY_CURRENT_USER,
+ index,
+ registryUnreadMailCountKey,
+ sizeof(registryUnreadMailCountKey))))
+ {
+ if (wcscmp(registryUnreadMailCountKey, currentUnreadMailCountKey.get())==0) {
+ nsAutoString deleteKey;
+ deleteKey.Assign(NS_LITERAL_STRING(UNREADMAILNODEKEY).get());
+ deleteKey.Append(currentUnreadMailCountKey.get());
+
+ if (!deleteKey.IsEmpty()) {
+ // delete this key and berak out of the loop
+ RegDeleteKey(HKEY_CURRENT_USER,
+ NS_ConvertUTF16toUTF8(deleteKey).get());
+ break;
+ }
+ else {
+ index++;
+ }
+ }
+ else {
+ index++;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMessengerWinIntegration::UpdateRegistryWithCurrent()
+{
+ if (mInboxURI.IsEmpty() || mEmail.IsEmpty())
+ return NS_OK;
+
+ // only update the registry if the count has changed
+ // and if the unread count is valid
+ if ((mCurrentUnreadCount < 0) || (mCurrentUnreadCount == mLastUnreadCountWrittenToRegistry))
+ return NS_OK;
+
+ // commandliner has to be built in the form of statement
+ // which can be open the mailer app to the default user account
+ // For given profile 'foo', commandliner will be built as
+ // ""<absolute path to application>" -p foo -mail" where absolute
+ // path to application is extracted from mAppName
+ nsAutoString commandLinerForAppLaunch;
+ commandLinerForAppLaunch.Assign(NS_LITERAL_STRING(DOUBLE_QUOTE));
+ commandLinerForAppLaunch.Append(mAppName);
+ commandLinerForAppLaunch.Append(NS_LITERAL_STRING(DOUBLE_QUOTE));
+ commandLinerForAppLaunch.Append(NS_LITERAL_STRING(PROFILE_COMMANDLINE_ARG));
+ commandLinerForAppLaunch.Append(NS_LITERAL_STRING(DOUBLE_QUOTE));
+ commandLinerForAppLaunch.Append(mProfilePath);
+ commandLinerForAppLaunch.Append(NS_LITERAL_STRING(DOUBLE_QUOTE));
+ commandLinerForAppLaunch.Append(NS_LITERAL_STRING(MAIL_COMMANDLINE_ARG));
+
+ if (!commandLinerForAppLaunch.IsEmpty())
+ {
+ nsAutoString pBuffer;
+
+ if (!mEmailPrefix.IsEmpty()) {
+ pBuffer.Assign(mEmailPrefix);
+ pBuffer.Append(NS_ConvertASCIItoUTF16(mEmail));
+ }
+ else
+ CopyASCIItoUTF16(mEmail, pBuffer);
+
+ // Write the info into the registry
+ HRESULT hr = SHSetUnreadMailCountW(pBuffer.get(),
+ mCurrentUnreadCount,
+ commandLinerForAppLaunch.get());
+ }
+
+ // do this last
+ mLastUnreadCountWrittenToRegistry = mCurrentUnreadCount;
+
+ return NS_OK;
+}
+
+nsresult
+nsMessengerWinIntegration::SetupInbox()
+{
+ nsresult rv;
+
+ // get default account
+ nsCOMPtr <nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgAccount> account;
+ rv = accountManager->GetDefaultAccount(getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!account) {
+ // this can happen if we launch mail on a new profile
+ // we don't have a default account yet
+ mDefaultAccountMightHaveAnInbox = false;
+ return NS_OK;
+ }
+
+ // get incoming server
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = account->GetIncomingServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!server)
+ return NS_ERROR_FAILURE;
+
+ nsCString type;
+ rv = server->GetType(type);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // we only care about imap and pop3
+ if (type.EqualsLiteral("imap") || type.EqualsLiteral("pop3")) {
+ // imap and pop3 account should have an Inbox
+ mDefaultAccountMightHaveAnInbox = true;
+
+ mEmailPrefix.Truncate();
+
+ // Get user's email address
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = account->GetDefaultIdentity(getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!identity)
+ return NS_ERROR_FAILURE;
+
+ rv = identity->GetEmail(mEmail);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!rootMsgFolder)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr <nsIMsgFolder> inboxFolder;
+ rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(inboxFolder));
+ NS_ENSURE_TRUE(inboxFolder, NS_ERROR_FAILURE);
+
+ rv = inboxFolder->GetURI(mInboxURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = inboxFolder->GetNumUnread(false, &mCurrentUnreadCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else {
+ // the default account is valid, but it's not something
+ // that we expect to have an inbox. (local folders, news accounts)
+ // set this flag to avoid calling SetupInbox() every time
+ // the timer goes off.
+ mDefaultAccountMightHaveAnInbox = false;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMessengerWinIntegration::UpdateUnreadCount()
+{
+ nsresult rv;
+
+ if (mDefaultAccountMightHaveAnInbox && mInboxURI.IsEmpty()) {
+ rv = SetupInbox();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ return UpdateRegistryWithCurrent();
+}
+
+nsresult
+nsMessengerWinIntegration::SetupUnreadCountUpdateTimer()
+{
+ mUnreadTimerActive = true;
+ if (mUnreadCountUpdateTimer)
+ mUnreadCountUpdateTimer->Cancel();
+ else
+ mUnreadCountUpdateTimer = do_CreateInstance("@mozilla.org/timer;1");
+
+ mUnreadCountUpdateTimer->InitWithFuncCallback(OnUnreadCountUpdateTimer,
+ (void *)this, UNREAD_UPDATE_INTERVAL, nsITimer::TYPE_ONE_SHOT);
+
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMessengerWinIntegration.h b/mailnews/base/src/nsMessengerWinIntegration.h
new file mode 100644
index 000000000..344e05e76
--- /dev/null
+++ b/mailnews/base/src/nsMessengerWinIntegration.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMessengerWinIntegration_h
+#define __nsMessengerWinIntegration_h
+
+#include <windows.h>
+
+// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <shellapi.h>
+
+#include "nsIMessengerOSIntegration.h"
+#include "nsIFolderListener.h"
+#include "nsIAtom.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIMutableArray.h"
+#include "nsIObserver.h"
+
+#define NS_MESSENGERWININTEGRATION_CID \
+ {0xf62f3d3a, 0x1dd1, 0x11b2, \
+ {0xa5, 0x16, 0xef, 0xad, 0xb1, 0x31, 0x61, 0x5c}}
+
+class nsIStringBundle;
+
+class nsMessengerWinIntegration : public nsIMessengerOSIntegration,
+ public nsIFolderListener,
+ public nsIObserver
+{
+public:
+ nsMessengerWinIntegration();
+ virtual nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMESSENGEROSINTEGRATION
+ NS_DECL_NSIFOLDERLISTENER
+ NS_DECL_NSIOBSERVER
+
+ nsresult ShowNewAlertNotification(bool aUserInitiated, const nsString& aAlertTitle, const nsString& aAlertText);
+#ifndef MOZ_THUNDERBIRD
+ nsresult ShowAlertMessage(const nsString& aAlertTitle, const nsString& aAlertText, const nsACString& aFolderURI);
+#endif
+
+private:
+ virtual ~nsMessengerWinIntegration();
+ nsresult AlertFinished();
+ nsresult AlertClicked();
+#ifdef MOZ_SUITE
+ nsresult AlertClickedSimple();
+#endif
+
+ void InitializeBiffStatusIcon();
+ void FillToolTipInfo();
+ void GenericShellNotify(DWORD aMessage);
+ void DestroyBiffIcon();
+
+ nsresult GetFirstFolderWithNewMail(nsACString& aFolderURI);
+
+ nsresult GetStringBundle(nsIStringBundle **aBundle);
+ nsCOMPtr<nsIMutableArray> mFoldersWithNewMail; // keep track of all the root folders with pending new mail
+ nsCOMPtr<nsIAtom> mBiffStateAtom;
+ uint32_t mCurrentBiffState;
+
+ bool mBiffIconVisible;
+ bool mBiffIconInitialized;
+ bool mSuppressBiffIcon;
+ bool mAlertInProgress;
+
+ // "might" because we don't know until we check
+ // what type of server is associated with the default account
+ bool mDefaultAccountMightHaveAnInbox;
+
+ // True if the timer is running
+ bool mUnreadTimerActive;
+
+ nsresult ResetCurrent();
+ nsresult RemoveCurrentFromRegistry();
+ nsresult UpdateRegistryWithCurrent();
+ nsresult SetupInbox();
+
+ nsresult SetupUnreadCountUpdateTimer();
+ static void OnUnreadCountUpdateTimer(nsITimer *timer, void *osIntegration);
+ nsresult UpdateUnreadCount();
+
+ nsCOMPtr <nsIAtom> mDefaultServerAtom;
+ nsCOMPtr <nsIAtom> mTotalUnreadMessagesAtom;
+ nsCOMPtr <nsITimer> mUnreadCountUpdateTimer;
+
+ nsCString mInboxURI;
+ nsCString mEmail;
+
+ nsString mAppName;
+ nsString mEmailPrefix;
+
+ nsString mProfilePath;
+
+ int32_t mCurrentUnreadCount;
+ int32_t mLastUnreadCountWrittenToRegistry;
+};
+
+#endif // __nsMessengerWinIntegration_h
diff --git a/mailnews/base/src/nsMsgAccount.cpp b/mailnews/base/src/nsMsgAccount.cpp
new file mode 100644
index 000000000..9022176bf
--- /dev/null
+++ b/mailnews/base/src/nsMsgAccount.cpp
@@ -0,0 +1,435 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "prprf.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsCRTGlue.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFolderNotificationService.h"
+
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgAccount.h"
+#include "nsIMsgAccount.h"
+#include "nsIMsgAccountManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "nsArrayUtils.h"
+
+NS_IMPL_ISUPPORTS(nsMsgAccount, nsIMsgAccount)
+
+nsMsgAccount::nsMsgAccount()
+ : mTriedToGetServer(false)
+{
+}
+
+nsMsgAccount::~nsMsgAccount()
+{
+}
+
+nsresult
+nsMsgAccount::getPrefService()
+{
+ if (m_prefs)
+ return NS_OK;
+
+ nsresult rv;
+ NS_ENSURE_FALSE(m_accountKey.IsEmpty(), NS_ERROR_NOT_INITIALIZED);
+ nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString accountRoot("mail.account.");
+ accountRoot.Append(m_accountKey);
+ accountRoot.Append('.');
+ return prefs->GetBranch(accountRoot.get(), getter_AddRefs(m_prefs));
+}
+
+NS_IMETHODIMP
+nsMsgAccount::GetIncomingServer(nsIMsgIncomingServer **aIncomingServer)
+{
+ NS_ENSURE_ARG_POINTER(aIncomingServer);
+
+ // create the incoming server lazily
+ if (!mTriedToGetServer && !m_incomingServer) {
+ mTriedToGetServer = true;
+ // ignore the error (and return null), but it's still bad so warn
+ mozilla::DebugOnly<nsresult> rv = createIncomingServer();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "couldn't lazily create the server\n");
+ }
+
+ NS_IF_ADDREF(*aIncomingServer = m_incomingServer);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::CreateServer()
+{
+ if (m_incomingServer)
+ return NS_ERROR_ALREADY_INITIALIZED;
+ return createIncomingServer();
+}
+
+nsresult
+nsMsgAccount::createIncomingServer()
+{
+ // from here, load mail.account.myaccount.server
+ // Load the incoming server
+ //
+ // ex) mail.account.myaccount.server = "myserver"
+
+ nsresult rv = getPrefService();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the "server" pref
+ nsCString serverKey;
+ rv = m_prefs->GetCharPref("server", getter_Copies(serverKey));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the server from the account manager
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = accountManager->GetIncomingServer(serverKey, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // store the server in this structure
+ m_incomingServer = server;
+ accountManager->NotifyServerLoaded(server);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMsgAccount::SetIncomingServer(nsIMsgIncomingServer *aIncomingServer)
+{
+ NS_ENSURE_ARG_POINTER(aIncomingServer);
+
+ nsCString key;
+ nsresult rv = aIncomingServer->GetKey(key);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = getPrefService();
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_prefs->SetCharPref("server", key.get());
+ }
+
+ m_incomingServer = aIncomingServer;
+
+ bool serverValid;
+ (void) aIncomingServer->GetValid(&serverValid);
+ // only notify server loaded if server is valid so
+ // account manager only gets told about finished accounts.
+ if (serverValid)
+ {
+ // this is the point at which we can notify listeners about the
+ // creation of the root folder, which implies creation of the new server.
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = aIncomingServer->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFolderListener> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mailSession->OnItemAdded(nullptr, rootFolder);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ notifier->NotifyFolderAdded(rootFolder);
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ accountManager->NotifyServerLoaded(aIncomingServer);
+
+ // Force built-in folders to be created and discovered. Then, notify listeners
+ // about them.
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = rootFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item));
+ if (!msgFolder)
+ continue;
+ mailSession->OnItemAdded(rootFolder, msgFolder);
+ notifier->NotifyFolderAdded(msgFolder);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::GetIdentities(nsIArray **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_ENSURE_TRUE(m_identities, NS_ERROR_FAILURE);
+
+ NS_IF_ADDREF(*_retval = m_identities);
+ return NS_OK;
+}
+
+/*
+ * set up the m_identities array
+ * do not call this more than once or we'll leak.
+ */
+nsresult
+nsMsgAccount::createIdentities()
+{
+ NS_ENSURE_FALSE(m_identities, NS_ERROR_FAILURE);
+
+ nsresult rv;
+ m_identities = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString identityKey;
+ rv = getPrefService();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_prefs->GetCharPref("identities", getter_Copies(identityKey));
+ if (identityKey.IsEmpty()) // not an error if no identities, but
+ return NS_OK; // strtok will be unhappy
+ // get the server from the account manager
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char* newStr = identityKey.BeginWriting();
+ char* token = NS_strtok(",", &newStr);
+
+ // temporaries used inside the loop
+ nsCOMPtr<nsIMsgIdentity> identity;
+ nsAutoCString key;
+
+ // iterate through id1,id2, etc
+ while (token) {
+ key = token;
+ key.StripWhitespace();
+
+ // create the account
+ rv = accountManager->GetIdentity(key, getter_AddRefs(identity));
+ if (NS_SUCCEEDED(rv)) {
+ // ignore error from addIdentityInternal() - if it fails, it fails.
+ rv = addIdentityInternal(identity);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Couldn't create identity");
+ }
+
+ // advance to next key, if any
+ token = NS_strtok(",", &newStr);
+ }
+
+ return rv;
+}
+
+
+/* attribute nsIMsgIdentity defaultIdentity; */
+NS_IMETHODIMP
+nsMsgAccount::GetDefaultIdentity(nsIMsgIdentity **aDefaultIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultIdentity);
+ NS_ENSURE_TRUE(m_identities, NS_ERROR_NOT_INITIALIZED);
+
+ *aDefaultIdentity = nullptr;
+ uint32_t count;
+ nsresult rv = m_identities->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (count == 0)
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgIdentity> identity = do_QueryElementAt(m_identities, 0, &rv);
+ identity.swap(*aDefaultIdentity);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::SetDefaultIdentity(nsIMsgIdentity *aDefaultIdentity)
+{
+ NS_ENSURE_TRUE(m_identities, NS_ERROR_FAILURE);
+
+ uint32_t position = 0;
+ nsresult rv = m_identities->IndexOf(0, aDefaultIdentity, &position);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_identities->RemoveElementAt(position);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The passed in identity is in the list, so we have at least one element.
+ rv = m_identities->InsertElementAt(aDefaultIdentity, 0, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return saveIdentitiesPref();
+}
+
+// add the identity to m_identities, but don't fiddle with the
+// prefs. The assumption here is that the pref for this identity is
+// already set.
+nsresult
+nsMsgAccount::addIdentityInternal(nsIMsgIdentity *identity)
+{
+ NS_ENSURE_TRUE(m_identities, NS_ERROR_FAILURE);
+
+ return m_identities->AppendElement(identity, false);
+}
+
+/* void addIdentity (in nsIMsgIdentity identity); */
+NS_IMETHODIMP
+nsMsgAccount::AddIdentity(nsIMsgIdentity *identity)
+{
+ NS_ENSURE_ARG_POINTER(identity);
+
+ // hack hack - need to add this to the list of identities.
+ // for now just treat this as a Setxxx accessor
+ // when this is actually implemented, don't refcount the default identity
+ nsCString key;
+ nsresult rv = identity->GetKey(key);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCString identityList;
+ m_prefs->GetCharPref("identities", getter_Copies(identityList));
+
+ nsAutoCString newIdentityList(identityList);
+
+ nsAutoCString testKey; // temporary to strip whitespace
+ bool foundIdentity = false; // if the input identity is found
+
+ if (!identityList.IsEmpty()) {
+ char *newStr = identityList.BeginWriting();
+ char *token = NS_strtok(",", &newStr);
+
+ // look for the identity key that we're adding
+ while (token) {
+ testKey = token;
+ testKey.StripWhitespace();
+
+ if (testKey.Equals(key))
+ foundIdentity = true;
+
+ token = NS_strtok(",", &newStr);
+ }
+ }
+
+ // if it didn't already exist, append it
+ if (!foundIdentity) {
+ if (newIdentityList.IsEmpty())
+ newIdentityList = key;
+ else {
+ newIdentityList.Append(',');
+ newIdentityList.Append(key);
+ }
+ }
+
+ m_prefs->SetCharPref("identities", newIdentityList.get());
+ }
+
+ // now add it to the in-memory list
+ return addIdentityInternal(identity);
+}
+
+/* void removeIdentity (in nsIMsgIdentity identity); */
+NS_IMETHODIMP
+nsMsgAccount::RemoveIdentity(nsIMsgIdentity *aIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ NS_ENSURE_TRUE(m_identities, NS_ERROR_FAILURE);
+
+ uint32_t count = 0;
+ m_identities->GetLength(&count);
+ // At least one identity must stay after the delete.
+ NS_ENSURE_TRUE(count > 1, NS_ERROR_FAILURE);
+
+ uint32_t pos = 0;
+ nsresult rv = m_identities->IndexOf(0, aIdentity, &pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // remove our identity
+ m_identities->RemoveElementAt(pos);
+
+ // clear out the actual pref values associated with the identity
+ aIdentity->ClearAllValues();
+
+ return saveIdentitiesPref();
+}
+
+nsresult
+nsMsgAccount::saveIdentitiesPref()
+{
+ nsAutoCString newIdentityList;
+
+ // Iterate over the existing identities and build the pref value,
+ // a string of identity keys: id1, id2, idX...
+ uint32_t count;
+ nsresult rv = m_identities->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString key;
+ for (uint32_t index = 0; index < count; index++)
+ {
+ nsCOMPtr<nsIMsgIdentity> identity = do_QueryElementAt(m_identities, index, &rv);
+ if (identity)
+ {
+ identity->GetKey(key);
+
+ if (!index) {
+ newIdentityList = key;
+ }
+ else
+ {
+ newIdentityList.Append(',');
+ newIdentityList.Append(key);
+ }
+ }
+ }
+
+ // Save the pref.
+ m_prefs->SetCharPref("identities", newIdentityList.get());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccount::GetKey(nsACString& accountKey)
+{
+ accountKey = m_accountKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::SetKey(const nsACString& accountKey)
+{
+ m_accountKey = accountKey;
+ m_prefs = nullptr;
+ m_identities = nullptr;
+ return createIdentities();
+}
+
+NS_IMETHODIMP
+nsMsgAccount::ToString(nsAString& aResult)
+{
+ nsAutoString val;
+ aResult.AssignLiteral("[nsIMsgAccount: ");
+ aResult.Append(NS_ConvertASCIItoUTF16(m_accountKey));
+ aResult.AppendLiteral("]");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::ClearAllValues()
+{
+ nsresult rv = getPrefService();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return m_prefs->DeleteBranch("");
+}
diff --git a/mailnews/base/src/nsMsgAccount.h b/mailnews/base/src/nsMsgAccount.h
new file mode 100644
index 000000000..5a12dab1a
--- /dev/null
+++ b/mailnews/base/src/nsMsgAccount.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "nsIMsgAccount.h"
+#include "nsIPrefBranch.h"
+#include "nsStringGlue.h"
+#include "nsIMutableArray.h"
+
+class nsMsgAccount : public nsIMsgAccount
+{
+
+public:
+ nsMsgAccount();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGACCOUNT
+
+private:
+ virtual ~nsMsgAccount();
+ nsCString m_accountKey;
+ nsCOMPtr<nsIPrefBranch> m_prefs;
+ nsCOMPtr<nsIMsgIncomingServer> m_incomingServer;
+
+ nsCOMPtr<nsIMutableArray> m_identities;
+
+ nsresult getPrefService();
+ nsresult createIncomingServer();
+ nsresult createIdentities();
+ nsresult saveIdentitiesPref();
+ nsresult addIdentityInternal(nsIMsgIdentity* identity);
+
+ // Have we tried to get the server yet?
+ bool mTriedToGetServer;
+};
+
diff --git a/mailnews/base/src/nsMsgAccountManager.cpp b/mailnews/base/src/nsMsgAccountManager.cpp
new file mode 100644
index 000000000..4ebac58a5
--- /dev/null
+++ b/mailnews/base/src/nsMsgAccountManager.cpp
@@ -0,0 +1,3707 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * The account manager service - manages all accounts, servers, and identities
+ */
+
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+#include "nsMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgCompCID.h"
+#include "nsMsgDBCID.h"
+#include "prmem.h"
+#include "prcmon.h"
+#include "prthread.h"
+#include "plstr.h"
+#include "nsStringGlue.h"
+#include "nsUnicharUtils.h"
+#include "nscore.h"
+#include "prprf.h"
+#include "nsIMsgFolderCache.h"
+#include "nsMsgUtils.h"
+#include "nsIFile.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsISmtpService.h"
+#include "nsIMsgBiffManager.h"
+#include "nsIMsgPurgeService.h"
+#include "nsIObserverService.h"
+#include "nsINoIncomingServer.h"
+#include "nsIMsgMailSession.h"
+#include "nsIDirectoryService.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIImapUrl.h"
+#include "nsIMessengerOSIntegration.h"
+#include "nsICategoryManager.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMutableArray.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgHdr.h"
+#include "nsILineInputStream.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsIStringBundle.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFilterList.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/Services.h"
+#include "nsIFileStreams.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+
+#define PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS "mail.accountmanager.accounts"
+#define PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT "mail.accountmanager.defaultaccount"
+#define PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER "mail.accountmanager.localfoldersserver"
+#define PREF_MAIL_SERVER_PREFIX "mail.server."
+#define ACCOUNT_PREFIX "account"
+#define SERVER_PREFIX "server"
+#define ID_PREFIX "id"
+#define ABOUT_TO_GO_OFFLINE_TOPIC "network:offline-about-to-go-offline"
+#define ACCOUNT_DELIMITER ','
+#define APPEND_ACCOUNTS_VERSION_PREF_NAME "append_preconfig_accounts.version"
+#define MAILNEWS_ROOT_PREF "mailnews."
+#define PREF_MAIL_ACCOUNTMANAGER_APPEND_ACCOUNTS "mail.accountmanager.appendaccounts"
+
+static NS_DEFINE_CID(kMsgAccountCID, NS_MSGACCOUNT_CID);
+static NS_DEFINE_CID(kMsgFolderCacheCID, NS_MSGFOLDERCACHE_CID);
+
+#define SEARCH_FOLDER_FLAG "searchFolderFlag"
+#define SEARCH_FOLDER_FLAG_LEN (sizeof(SEARCH_FOLDER_FLAG) - 1)
+
+const char *kSearchFolderUriProp = "searchFolderUri";
+
+bool nsMsgAccountManager::m_haveShutdown = false;
+bool nsMsgAccountManager::m_shutdownInProgress = false;
+
+NS_IMPL_ISUPPORTS(nsMsgAccountManager,
+ nsIMsgAccountManager,
+ nsIObserver,
+ nsISupportsWeakReference,
+ nsIUrlListener,
+ nsIFolderListener)
+
+nsMsgAccountManager::nsMsgAccountManager() :
+ m_accountsLoaded(false),
+ m_emptyTrashInProgress(false),
+ m_cleanupInboxInProgress(false),
+ m_userAuthenticated(false),
+ m_loadingVirtualFolders(false),
+ m_virtualFoldersLoaded(false)
+{
+}
+
+nsMsgAccountManager::~nsMsgAccountManager()
+{
+ if(!m_haveShutdown)
+ {
+ Shutdown();
+ //Don't remove from Observer service in Shutdown because Shutdown also gets called
+ //from xpcom shutdown observer. And we don't want to remove from the service in that case.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ observerService->RemoveObserver(this, "quit-application-granted");
+ observerService->RemoveObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC);
+ observerService->RemoveObserver(this, "sleep_notification");
+ }
+ }
+}
+
+nsresult nsMsgAccountManager::Init()
+{
+ nsresult rv;
+
+ m_prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ {
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ observerService->AddObserver(this, "quit-application-granted" , true);
+ observerService->AddObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC, true);
+ observerService->AddObserver(this, "profile-before-change", true);
+ observerService->AddObserver(this, "sleep_notification", true);
+ }
+
+ // Make sure PSM gets initialized before any accounts use certificates.
+ net_EnsurePSMInit();
+
+ return NS_OK;
+}
+
+nsresult nsMsgAccountManager::Shutdown()
+{
+ if (m_haveShutdown) // do not shutdown twice
+ return NS_OK;
+
+ nsresult rv;
+
+ SaveVirtualFolders();
+
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ if (msgDBService)
+ {
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener> >::ForwardIterator iter(m_virtualFolderListeners);
+ RefPtr<VirtualFolderChangeListener> listener;
+
+ while (iter.HasMore())
+ {
+ listener = iter.GetNext();
+ msgDBService->UnregisterPendingListener(listener);
+ }
+ }
+ if(m_msgFolderCache)
+ WriteToFolderCache(m_msgFolderCache);
+ (void)ShutdownServers();
+ (void)UnloadAccounts();
+
+ //shutdown removes nsIIncomingServer listener from biff manager, so do it after accounts have been unloaded
+ nsCOMPtr<nsIMsgBiffManager> biffService = do_GetService(NS_MSGBIFFMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && biffService)
+ biffService->Shutdown();
+
+ //shutdown removes nsIIncomingServer listener from purge service, so do it after accounts have been unloaded
+ nsCOMPtr<nsIMsgPurgeService> purgeService = do_GetService(NS_MSGPURGESERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && purgeService)
+ purgeService->Shutdown();
+
+ m_msgFolderCache = nullptr;
+ m_haveShutdown = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetShutdownInProgress(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_shutdownInProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetUserNeedsToAuthenticate(bool *aRetval)
+{
+ NS_ENSURE_ARG_POINTER(aRetval);
+ if (!m_userAuthenticated)
+ return m_prefs->GetBoolPref("mail.password_protect_local_cache", aRetval);
+ *aRetval = !m_userAuthenticated;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SetUserNeedsToAuthenticate(bool aUserNeedsToAuthenticate)
+{
+ m_userAuthenticated = !aUserNeedsToAuthenticate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ if(!strcmp(aTopic,NS_XPCOM_SHUTDOWN_OBSERVER_ID))
+ {
+ Shutdown();
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, "quit-application-granted"))
+ {
+ // CleanupOnExit will set m_shutdownInProgress to true.
+ CleanupOnExit();
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, ABOUT_TO_GO_OFFLINE_TOPIC))
+ {
+ nsAutoString dataString(NS_LITERAL_STRING("offline"));
+ if (someData)
+ {
+ nsAutoString someDataString(someData);
+ if (dataString.Equals(someDataString))
+ CloseCachedConnections();
+ }
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, "sleep_notification"))
+ return CloseCachedConnections();
+
+ if (!strcmp(aTopic, "profile-before-change"))
+ {
+ Shutdown();
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+void
+nsMsgAccountManager::getUniqueAccountKey(nsCString& aResult)
+{
+ int32_t lastKey = 0;
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefservice(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ prefservice->GetBranch("", getter_AddRefs(prefBranch));
+
+ rv = prefBranch->GetIntPref("mail.account.lastKey", &lastKey);
+ if (NS_FAILED(rv) || lastKey == 0) {
+ // If lastKey pref does not contain a valid value, loop over existing
+ // pref names mail.account.* .
+ nsCOMPtr<nsIPrefBranch> prefBranchAccount;
+ rv = prefservice->GetBranch("mail.account.", getter_AddRefs(prefBranchAccount));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t prefCount;
+ char **prefList;
+ rv = prefBranchAccount->GetChildList("", &prefCount, &prefList);
+ if (NS_SUCCEEDED(rv)) {
+ // Pref names are of the format accountX.
+ // Find the maximum value of 'X' used so far.
+ for (uint32_t i = 0; i < prefCount; i++) {
+ nsCString prefName;
+ prefName.Assign(prefList[i]);
+ if (StringBeginsWith(prefName, NS_LITERAL_CSTRING(ACCOUNT_PREFIX))) {
+ int32_t dotPos = prefName.FindChar('.');
+ if (dotPos != kNotFound) {
+ nsCString keyString(Substring(prefName, strlen(ACCOUNT_PREFIX),
+ dotPos - strlen(ACCOUNT_PREFIX)));
+ int32_t thisKey = keyString.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv))
+ lastKey = std::max(lastKey, thisKey);
+ }
+ }
+ }
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList);
+ }
+ }
+ }
+
+ // Use next available key and store the value in the pref.
+ aResult.Assign(ACCOUNT_PREFIX);
+ aResult.AppendInt(++lastKey);
+ rv = prefBranch->SetIntPref("mail.account.lastKey", lastKey);
+ } else {
+ // If pref service is not working, try to find a free accountX key
+ // by checking which keys exist.
+ int32_t i = 1;
+ nsCOMPtr<nsIMsgAccount> account;
+
+ do {
+ aResult = ACCOUNT_PREFIX;
+ aResult.AppendInt(i++);
+ GetAccount(aResult, getter_AddRefs(account));
+ } while (account);
+ }
+}
+
+void
+nsMsgAccountManager::GetUniqueServerKey(nsACString& aResult)
+{
+ nsAutoCString prefResult;
+ bool usePrefsScan = true;
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ if (NS_FAILED(rv))
+ usePrefsScan = false;
+
+ // Loop over existing pref names mail.server.server(lastKey).type
+ nsCOMPtr<nsIPrefBranch> prefBranchServer;
+ if (prefService)
+ {
+ rv = prefService->GetBranch(PREF_MAIL_SERVER_PREFIX, getter_AddRefs(prefBranchServer));
+ if (NS_FAILED(rv))
+ usePrefsScan = false;
+ }
+
+ if (usePrefsScan)
+ {
+ nsAutoCString type;
+ nsAutoCString typeKey;
+ for (uint32_t lastKey = 1; ; lastKey++)
+ {
+ aResult.AssignLiteral(SERVER_PREFIX);
+ aResult.AppendInt(lastKey);
+ typeKey.Assign(aResult);
+ typeKey.AppendLiteral(".type");
+ prefBranchServer->GetCharPref(typeKey.get(), getter_Copies(type));
+ if (type.IsEmpty()) // a server slot with no type is considered empty
+ return;
+ }
+ }
+ else
+ {
+ // If pref service fails, try to find a free serverX key
+ // by checking which keys exist.
+ nsAutoCString internalResult;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ uint32_t i = 1;
+ do {
+ aResult.AssignLiteral(SERVER_PREFIX);
+ aResult.AppendInt(i++);
+ m_incomingServers.Get(aResult, getter_AddRefs(server));
+ } while (server);
+ return;
+ }
+}
+
+nsresult
+nsMsgAccountManager::CreateIdentity(nsIMsgIdentity **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv;
+ nsAutoCString key;
+ nsCOMPtr<nsIMsgIdentity> identity;
+ int32_t i = 1;
+ do {
+ key.AssignLiteral(ID_PREFIX);
+ key.AppendInt(i++);
+ m_identities.Get(key, getter_AddRefs(identity));
+ } while (identity);
+
+ rv = createKeyedIdentity(key, _retval);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetIdentity(const nsACString& key, nsIMsgIdentity **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv = NS_OK;
+ *_retval = nullptr;
+
+ if (!key.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgIdentity> identity;
+ m_identities.Get(key, getter_AddRefs(identity));
+ if (identity)
+ identity.swap(*_retval);
+ else // identity doesn't exist. create it.
+ rv = createKeyedIdentity(key, _retval);
+ }
+
+ return rv;
+}
+
+/*
+ * the shared identity-creation code
+ * create an identity and add it to the accountmanager's list.
+ */
+nsresult
+nsMsgAccountManager::createKeyedIdentity(const nsACString& key,
+ nsIMsgIdentity ** aIdentity)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgIdentity> identity =
+ do_CreateInstance(NS_MSGIDENTITY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ identity->SetKey(key);
+ m_identities.Put(key, identity);
+ identity.swap(*aIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::CreateIncomingServer(const nsACString& username,
+ const nsACString& hostname,
+ const nsACString& type,
+ nsIMsgIncomingServer **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString key;
+ GetUniqueServerKey(key);
+ rv = createKeyedServer(key, username, hostname, type, _retval);
+ if (*_retval)
+ {
+ nsCString defaultStore;
+ m_prefs->GetCharPref("mail.serverDefaultStoreContractID", getter_Copies(defaultStore));
+ (*_retval)->SetCharValue("storeContractID", defaultStore);
+
+ // From when we first create the account until we have created some folders,
+ // we can change the store type.
+ (*_retval)->SetBoolValue("canChangeStoreType", true);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetIncomingServer(const nsACString& key,
+ nsIMsgIncomingServer **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv;
+
+ if (m_incomingServers.Get(key, _retval))
+ return NS_OK;
+
+ // server doesn't exist, so create it
+ // this is really horrible because we are doing our own prefname munging
+ // instead of leaving it up to the incoming server.
+ // this should be fixed somehow so that we can create the incoming server
+ // and then read from the incoming server's attributes
+
+ // in order to create the right kind of server, we have to look
+ // at the pref for this server to get the username, hostname, and type
+ nsAutoCString serverPrefPrefix(PREF_MAIL_SERVER_PREFIX);
+ serverPrefPrefix.Append(key);
+
+ nsCString serverType;
+ nsAutoCString serverPref (serverPrefPrefix);
+ serverPref.AppendLiteral(".type");
+ rv = m_prefs->GetCharPref(serverPref.get(), getter_Copies(serverType));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED);
+
+ //
+ // .userName
+ serverPref = serverPrefPrefix;
+ serverPref.AppendLiteral(".userName");
+ nsCString username;
+ rv = m_prefs->GetCharPref(serverPref.get(), getter_Copies(username));
+
+ // .hostname
+ serverPref = serverPrefPrefix;
+ serverPref.AppendLiteral(".hostname");
+ nsCString hostname;
+ rv = m_prefs->GetCharPref(serverPref.get(), getter_Copies(hostname));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED);
+
+ return createKeyedServer(key, username, hostname, serverType, _retval);
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::RemoveIncomingServer(nsIMsgIncomingServer *aServer,
+ bool aRemoveFiles)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ nsCString serverKey;
+ nsresult rv = aServer->GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LogoutOfServer(aServer); // close cached connections and forget session password
+
+ // invalidate the FindServer() cache if we are removing the cached server
+ if (m_lastFindServerResult == aServer)
+ SetLastServerFound(nullptr, EmptyCString(), EmptyCString(), 0, EmptyCString());
+
+ m_incomingServers.Remove(serverKey);
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIArray> allDescendants;
+
+ rv = aServer->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = rootFolder->GetDescendants(getter_AddRefs(allDescendants));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cnt = 0;
+ rv = allDescendants->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier =
+ do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID);
+ nsCOMPtr<nsIFolderListener> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID);
+
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendants, i);
+ if (folder)
+ {
+ folder->ForceDBClosed();
+ if (notifier)
+ notifier->NotifyFolderDeleted(folder);
+ if (mailSession)
+ {
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ folder->GetParent(getter_AddRefs(parentFolder));
+ mailSession->OnItemRemoved(parentFolder, folder);
+ }
+ }
+ }
+ if (notifier)
+ notifier->NotifyFolderDeleted(rootFolder);
+ if (mailSession)
+ mailSession->OnItemRemoved(nullptr, rootFolder);
+
+ removeListenersFromFolder(rootFolder);
+ NotifyServerUnloaded(aServer);
+ if (aRemoveFiles)
+ {
+ rv = aServer->RemoveFiles();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // now clear out the server once and for all.
+ // watch out! could be scary
+ aServer->ClearAllValues();
+ rootFolder->Shutdown(true);
+ return rv;
+}
+
+/*
+ * create a server when you know the key and the type
+ */
+nsresult
+nsMsgAccountManager::createKeyedServer(const nsACString& key,
+ const nsACString& username,
+ const nsACString& hostname,
+ const nsACString& type,
+ nsIMsgIncomingServer ** aServer)
+{
+ nsresult rv;
+ *aServer = nullptr;
+
+ //construct the contractid
+ nsAutoCString serverContractID(NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX);
+ serverContractID += type;
+
+ // finally, create the server
+ // (This will fail if type is from an extension that has been removed)
+ nsCOMPtr<nsIMsgIncomingServer> server =
+ do_CreateInstance(serverContractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_AVAILABLE);
+
+ int32_t port;
+ nsCOMPtr <nsIMsgIncomingServer> existingServer;
+ server->SetKey(key);
+ server->SetType(type);
+ server->SetUsername(username);
+ server->SetHostName(hostname);
+ server->GetPort(&port);
+ FindRealServer(username, hostname, type, port, getter_AddRefs(existingServer));
+ // don't allow duplicate servers.
+ if (existingServer)
+ return NS_ERROR_FAILURE;
+
+ m_incomingServers.Put(key, server);
+
+ // now add all listeners that are supposed to be
+ // waiting on root folders
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTObserverArray<nsCOMPtr<nsIFolderListener> >::ForwardIterator iter(mFolderListeners);
+ while (iter.HasMore())
+ {
+ rootFolder->AddFolderListener(iter.GetNext());
+ }
+
+ server.swap(*aServer);
+ return NS_OK;
+}
+
+void
+nsMsgAccountManager::removeListenersFromFolder(nsIMsgFolder *aFolder)
+{
+ nsTObserverArray<nsCOMPtr<nsIFolderListener> >::ForwardIterator iter(mFolderListeners);
+ while (iter.HasMore())
+ {
+ aFolder->RemoveFolderListener(iter.GetNext());
+ }
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::RemoveAccount(nsIMsgAccount *aAccount,
+ bool aRemoveFiles = false)
+{
+ NS_ENSURE_ARG_POINTER(aAccount);
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool accountRemoved = m_accounts.RemoveElement(aAccount);
+
+ rv = OutputAccountsPref();
+ // If we couldn't write out the pref, restore the account.
+ if (NS_FAILED(rv) && accountRemoved)
+ {
+ m_accounts.AppendElement(aAccount);
+ return rv;
+ }
+
+ // If it's the default, choose a new default account.
+ if (m_defaultAccount.get() == aAccount)
+ AutosetDefaultAccount();
+
+ // XXX - need to figure out if this is the last time this server is
+ // being used, and only send notification then.
+ // (and only remove from hashtable then too!)
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = aAccount->GetIncomingServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ RemoveIncomingServer(server, aRemoveFiles);
+
+ nsCOMPtr<nsIArray> identityArray;
+ rv = aAccount->GetIdentities(getter_AddRefs(identityArray));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ identityArray->GetLength(&count);
+ uint32_t i;
+ for (i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgIdentity> identity( do_QueryElementAt(identityArray, i, &rv));
+ bool identityStillUsed = false;
+ // for each identity, see if any existing account still uses it,
+ // and if not, clear it.
+ // Note that we are also searching here accounts with missing servers from
+ // unloaded extension types.
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t index;
+ for (index = 0; index < m_accounts.Length() && !identityStillUsed; index++)
+ {
+ nsCOMPtr<nsIArray> existingIdentitiesArray;
+
+ rv = m_accounts[index]->GetIdentities(getter_AddRefs(existingIdentitiesArray));
+ uint32_t pos;
+ if (NS_SUCCEEDED(existingIdentitiesArray->IndexOf(0, identity, &pos)))
+ {
+ identityStillUsed = true;
+ break;
+ }
+ }
+ }
+ // clear out all identity information if no other account uses it.
+ if (!identityStillUsed)
+ identity->ClearAllValues();
+ }
+ }
+
+ // It is not a critical problem if this fails as the account was already
+ // removed from the list of accounts so should not ever be referenced.
+ // Just print it out for debugging.
+ rv = aAccount->ClearAllValues();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "removing of account prefs failed");
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManager::OutputAccountsPref()
+{
+ nsCString accountKey;
+ mAccountKeyList.Truncate();
+
+ for (uint32_t index = 0; index < m_accounts.Length(); index++)
+ {
+ m_accounts[index]->GetKey(accountKey);
+ if (index)
+ mAccountKeyList.Append(ACCOUNT_DELIMITER);
+ mAccountKeyList.Append(accountKey);
+ }
+ return m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS,
+ mAccountKeyList.get());
+}
+
+/**
+ * Get the default account. If no default account, return null.
+ */
+NS_IMETHODIMP
+nsMsgAccountManager::GetDefaultAccount(nsIMsgAccount **aDefaultAccount)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultAccount);
+
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_defaultAccount) {
+ nsCString defaultKey;
+ rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT, getter_Copies(defaultKey));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = GetAccount(defaultKey, getter_AddRefs(m_defaultAccount));
+ if (NS_SUCCEEDED(rv) && m_defaultAccount) {
+ bool canBeDefault = false;
+ rv = CheckDefaultAccount(m_defaultAccount, canBeDefault);
+ if (NS_FAILED(rv) || !canBeDefault)
+ m_defaultAccount = nullptr;
+ }
+ }
+ }
+
+ NS_IF_ADDREF(*aDefaultAccount = m_defaultAccount);
+ return NS_OK;
+}
+
+/**
+ * Check if the given account can be default.
+ */
+nsresult
+nsMsgAccountManager::CheckDefaultAccount(nsIMsgAccount *aAccount, bool &aCanBeDefault)
+{
+ aCanBeDefault = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // Server could be null if created by an unloaded extension.
+ nsresult rv = aAccount->GetIncomingServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (server) {
+ // Check if server can be default.
+ rv = server->GetCanBeDefaultServer(&aCanBeDefault);
+ }
+ return rv;
+}
+
+/**
+ * Pick the first account that can be default and make it the default.
+ */
+nsresult
+nsMsgAccountManager::AutosetDefaultAccount()
+{
+ for (nsIMsgAccount* account : m_accounts) {
+ bool canBeDefault = false;
+ nsresult rv = CheckDefaultAccount(account, canBeDefault);
+ if (NS_SUCCEEDED(rv) && canBeDefault) {
+ return SetDefaultAccount(account);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SetDefaultAccount(nsIMsgAccount *aDefaultAccount)
+{
+ if (!aDefaultAccount)
+ return NS_ERROR_INVALID_ARG;
+
+ if (m_defaultAccount != aDefaultAccount)
+ {
+ bool canBeDefault = false;
+ nsresult rv = CheckDefaultAccount(aDefaultAccount, canBeDefault);
+ if (NS_FAILED(rv) || !canBeDefault) {
+ // Report failure if we were explicitly asked to use an unusable server.
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIMsgAccount> oldAccount = m_defaultAccount;
+ m_defaultAccount = aDefaultAccount;
+ (void) setDefaultAccountPref(aDefaultAccount);
+ (void) notifyDefaultServerChange(oldAccount, aDefaultAccount);
+ }
+ return NS_OK;
+}
+
+// fire notifications
+nsresult
+nsMsgAccountManager::notifyDefaultServerChange(nsIMsgAccount *aOldAccount,
+ nsIMsgAccount *aNewAccount)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+
+ // first tell old server it's no longer the default
+ if (aOldAccount) {
+ rv = aOldAccount->GetIncomingServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) {
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ rootFolder->NotifyBoolPropertyChanged(kDefaultServerAtom,
+ true, false);
+ }
+ }
+
+ // now tell new server it is.
+ if (aNewAccount) {
+ rv = aNewAccount->GetIncomingServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) {
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ rootFolder->NotifyBoolPropertyChanged(kDefaultServerAtom,
+ false, true);
+ }
+ }
+
+ if (aOldAccount && aNewAccount) //only notify if the user goes and changes default account
+ {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService)
+ observerService->NotifyObservers(nullptr,"mailDefaultAccountChanged",nullptr);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManager::setDefaultAccountPref(nsIMsgAccount* aDefaultAccount)
+{
+ nsresult rv;
+
+ if (aDefaultAccount) {
+ nsCString key;
+ rv = aDefaultAccount->GetKey(key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT, key.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else
+ m_prefs->ClearUserPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT);
+
+ return NS_OK;
+}
+
+void nsMsgAccountManager::LogoutOfServer(nsIMsgIncomingServer *aServer)
+{
+ if (!aServer)
+ return;
+ mozilla::DebugOnly<nsresult> rv = aServer->Shutdown();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Shutdown of server failed");
+ rv = aServer->ForgetSessionPassword();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove the password associated with server");
+}
+
+NS_IMETHODIMP nsMsgAccountManager::GetFolderCache(nsIMsgFolderCache* *aFolderCache)
+{
+ NS_ENSURE_ARG_POINTER(aFolderCache);
+ nsresult rv = NS_OK;
+
+ if (!m_msgFolderCache)
+ {
+ m_msgFolderCache = do_CreateInstance(kMsgFolderCacheCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> cacheFile;
+ rv = NS_GetSpecialDirectory(NS_APP_MESSENGER_FOLDER_CACHE_50_FILE,
+ getter_AddRefs(cacheFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_msgFolderCache->Init(cacheFile);
+ }
+
+ NS_IF_ADDREF(*aFolderCache = m_msgFolderCache);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetAccounts(nsIArray **_retval)
+{
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> accounts(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t index = 0; index < m_accounts.Length(); index++)
+ {
+ nsCOMPtr<nsIMsgAccount> existingAccount(m_accounts[index]);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ existingAccount->GetIncomingServer(getter_AddRefs(server));
+ if (!server)
+ continue;
+ if (server)
+ {
+ bool hidden = false;
+ server->GetHidden(&hidden);
+ if (hidden)
+ continue;
+ }
+ accounts->AppendElement(existingAccount, false);
+ }
+ NS_IF_ADDREF(*_retval = accounts);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetAllIdentities(nsIArray **_retval)
+{
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> result(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIArray> identities;
+
+ for (uint32_t i = 0; i < m_accounts.Length(); ++i) {
+ rv = m_accounts[i]->GetIdentities(getter_AddRefs(identities));
+ if (NS_FAILED(rv))
+ continue;
+
+ uint32_t idCount;
+ rv = identities->GetLength(&idCount);
+ if (NS_FAILED(rv))
+ continue;
+
+ for (uint32_t j = 0; j < idCount; ++j) {
+ nsCOMPtr<nsIMsgIdentity> identity(do_QueryElementAt(identities, j, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsAutoCString key;
+ rv = identity->GetKey(key);
+ if (NS_FAILED(rv))
+ continue;
+
+ uint32_t resultCount;
+ rv = result->GetLength(&resultCount);
+ if (NS_FAILED(rv))
+ continue;
+
+ bool found = false;
+ for (uint32_t k = 0; k < resultCount && !found; ++k) {
+ nsCOMPtr<nsIMsgIdentity> thisIdentity(do_QueryElementAt(result, k, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsAutoCString thisKey;
+ rv = thisIdentity->GetKey(thisKey);
+ if (NS_FAILED(rv))
+ continue;
+
+ if (key == thisKey)
+ found = true;
+ }
+
+ if (!found)
+ result->AppendElement(identity, false);
+ }
+ }
+ result.forget(_retval);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetAllServers(nsIArray **_retval)
+{
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> servers(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ if (!server)
+ continue;
+
+ bool hidden = false;
+ server->GetHidden(&hidden);
+ if (hidden)
+ continue;
+
+ nsCString type;
+ if (NS_FAILED(server->GetType(type))) {
+ NS_WARNING("server->GetType() failed");
+ continue;
+ }
+
+ if (!type.EqualsLiteral("im")) {
+ servers->AppendElement(server, false);
+ }
+ }
+
+ servers.forget(_retval);
+ return rv;
+}
+
+nsresult
+nsMsgAccountManager::LoadAccounts()
+{
+ nsresult rv;
+
+ // for now safeguard multiple calls to this function
+ if (m_accountsLoaded)
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv))
+ mailSession->AddFolderListener(this, nsIFolderListener::added |
+ nsIFolderListener::removed |
+ nsIFolderListener::intPropertyChanged);
+ // If we have code trying to do things after we've unloaded accounts,
+ // ignore it.
+ if (m_shutdownInProgress || m_haveShutdown)
+ return NS_ERROR_FAILURE;
+
+ kDefaultServerAtom = MsgGetAtom("DefaultServer");
+ mFolderFlagAtom = MsgGetAtom("FolderFlag");
+
+ //Ensure biff service has started
+ nsCOMPtr<nsIMsgBiffManager> biffService =
+ do_GetService(NS_MSGBIFFMANAGER_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv))
+ biffService->Init();
+
+ //Ensure purge service has started
+ nsCOMPtr<nsIMsgPurgeService> purgeService =
+ do_GetService(NS_MSGPURGESERVICE_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv))
+ purgeService->Init();
+
+ nsCOMPtr<nsIPrefService> prefservice(do_GetService(NS_PREFSERVICE_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure messenger OS integration service has started
+ // note, you can't expect the integrationService to be there
+ // we don't have OS integration on all platforms.
+ nsCOMPtr<nsIMessengerOSIntegration> integrationService =
+ do_GetService(NS_MESSENGEROSINTEGRATION_CONTRACTID, &rv);
+
+ // mail.accountmanager.accounts is the main entry point for all accounts
+ nsCString accountList;
+ rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, getter_Copies(accountList));
+
+ /**
+ * Check to see if we need to add pre-configured accounts.
+ * Following prefs are important to note in understanding the procedure here.
+ *
+ * 1. pref("mailnews.append_preconfig_accounts.version", version number);
+ * This pref registers the current version in the user prefs file. A default value
+ * is stored in mailnews.js file. If a given vendor needs to add more preconfigured
+ * accounts, the default version number can be increased. Comparing version
+ * number from user's prefs file and the default one from mailnews.js, we
+ * can add new accounts and any other version level changes that need to be done.
+ *
+ * 2. pref("mail.accountmanager.appendaccounts", <comma separated account list>);
+ * This pref contains the list of pre-configured accounts that ISP/Vendor wants to
+ * to add to the existing accounts list.
+ */
+ nsCOMPtr<nsIPrefBranch> defaultsPrefBranch;
+ rv = prefservice->GetDefaultBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(defaultsPrefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefservice->GetBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t appendAccountsCurrentVersion=0;
+ int32_t appendAccountsDefaultVersion=0;
+ rv = prefBranch->GetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME, &appendAccountsCurrentVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = defaultsPrefBranch->GetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME, &appendAccountsDefaultVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Update the account list if needed
+ if ((appendAccountsCurrentVersion <= appendAccountsDefaultVersion)) {
+
+ // Get a list of pre-configured accounts
+ nsCString appendAccountList;
+ rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_APPEND_ACCOUNTS,
+ getter_Copies(appendAccountList));
+ appendAccountList.StripWhitespace();
+
+ // If there are pre-configured accounts, we need to add them to the
+ // existing list.
+ if (!appendAccountList.IsEmpty())
+ {
+ if (!accountList.IsEmpty())
+ {
+ // Tokenize the data and add each account
+ // in the user's current mailnews account list
+ nsTArray<nsCString> accountsArray;
+ ParseString(accountList, ACCOUNT_DELIMITER, accountsArray);
+ uint32_t i = accountsArray.Length();
+
+ // Append each account in the pre-configured account list
+ ParseString(appendAccountList, ACCOUNT_DELIMITER, accountsArray);
+
+ // Now add each account that does not already appear in the list
+ for (; i < accountsArray.Length(); i++)
+ {
+ if (accountsArray.IndexOf(accountsArray[i]) == i)
+ {
+ accountList.Append(ACCOUNT_DELIMITER);
+ accountList.Append(accountsArray[i]);
+ }
+ }
+ }
+ else
+ {
+ accountList = appendAccountList;
+ }
+ // Increase the version number so that updates will happen as and when needed
+ rv = prefBranch->SetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME, appendAccountsCurrentVersion + 1);
+ }
+ }
+
+ // It is ok to return null accounts like when we create new profile.
+ m_accountsLoaded = true;
+ m_haveShutdown = false;
+
+ if (accountList.IsEmpty())
+ return NS_OK;
+
+ /* parse accountList and run loadAccount on each string, comma-separated */
+ nsCOMPtr<nsIMsgAccount> account;
+ // Tokenize the data and add each account
+ // in the user's current mailnews account list
+ nsTArray<nsCString> accountsArray;
+ ParseString(accountList, ACCOUNT_DELIMITER, accountsArray);
+
+ // These are the duplicate accounts we found. We keep track of these
+ // because if any other server defers to one of these accounts, we need
+ // to defer to the correct account.
+ nsCOMArray<nsIMsgAccount> dupAccounts;
+
+ // Now add each account that does not already appear in the list
+ for (uint32_t i = 0; i < accountsArray.Length(); i++)
+ {
+ // if we've already seen this exact account, advance to the next account.
+ // After the loop, we'll notice that we don't have as many actual accounts
+ // as there were accounts in the pref, and rewrite the pref.
+ if (accountsArray.IndexOf(accountsArray[i]) != i)
+ continue;
+
+ // get the "server" pref to see if we already have an account with this
+ // server. If we do, we ignore this account.
+ nsAutoCString serverKeyPref("mail.account.");
+ serverKeyPref += accountsArray[i];
+
+ nsCOMPtr<nsIPrefBranch> accountPrefBranch;
+ rv = prefservice->GetBranch(serverKeyPref.get(),
+ getter_AddRefs(accountPrefBranch));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ serverKeyPref += ".server";
+ nsCString serverKey;
+ rv = m_prefs->GetCharPref(serverKeyPref.get(), getter_Copies(serverKey));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIMsgAccount> serverAccount;
+ findAccountByServerKey(serverKey, getter_AddRefs(serverAccount));
+ // If we have an existing account with the same server, ignore this account
+ if (serverAccount)
+ continue;
+
+ if (NS_FAILED(createKeyedAccount(accountsArray[i],
+ getter_AddRefs(account))) || !account)
+ {
+ NS_WARNING("unexpected entry in account list; prefs corrupt?");
+ continue;
+ }
+
+ // See nsIMsgAccount.idl for a description of the secondsToLeaveUnavailable
+ // and timeFoundUnavailable preferences
+ nsAutoCString toLeavePref(PREF_MAIL_SERVER_PREFIX);
+ toLeavePref.Append(serverKey);
+ nsAutoCString unavailablePref(toLeavePref); // this is the server-specific prefix
+ unavailablePref.AppendLiteral(".timeFoundUnavailable");
+ toLeavePref.AppendLiteral(".secondsToLeaveUnavailable");
+ int32_t secondsToLeave = 0;
+ int32_t timeUnavailable = 0;
+
+ m_prefs->GetIntPref(toLeavePref.get(), &secondsToLeave);
+
+ // force load of accounts (need to find a better way to do this)
+ nsCOMPtr<nsIArray> identities;
+ account->GetIdentities(getter_AddRefs(identities));
+
+ rv = account->CreateServer();
+ bool deleteAccount = NS_FAILED(rv);
+
+ if (secondsToLeave)
+ { // we need to process timeUnavailable
+ if (NS_SUCCEEDED(rv)) // clear the time if server is available
+ {
+ m_prefs->ClearUserPref(unavailablePref.get());
+ }
+ // NS_ERROR_NOT_AVAILABLE signifies a server that could not be
+ // instantiated, presumably because of an invalid type.
+ else if (rv == NS_ERROR_NOT_AVAILABLE)
+ {
+ m_prefs->GetIntPref(unavailablePref.get(), &timeUnavailable);
+ if (!timeUnavailable)
+ { // we need to set it, this must be the first time unavailable
+ uint32_t nowSeconds;
+ PRTime2Seconds(PR_Now(), &nowSeconds);
+ m_prefs->SetIntPref(unavailablePref.get(), nowSeconds);
+ deleteAccount = false;
+ }
+ }
+ }
+
+ if (rv == NS_ERROR_NOT_AVAILABLE && timeUnavailable != 0)
+ { // Our server is still unavailable. Have we timed out yet?
+ uint32_t nowSeconds;
+ PRTime2Seconds(PR_Now(), &nowSeconds);
+ if ((int32_t)nowSeconds < timeUnavailable + secondsToLeave)
+ deleteAccount = false;
+ }
+
+ if (deleteAccount)
+ {
+ dupAccounts.AppendObject(account);
+ m_accounts.RemoveElement(account);
+ }
+ }
+
+ // Check if we removed one or more of the accounts in the pref string.
+ // If so, rewrite the pref string.
+ if (accountsArray.Length() != m_accounts.Length())
+ OutputAccountsPref();
+
+ int32_t cnt = dupAccounts.Count();
+ nsCOMPtr<nsIMsgAccount> dupAccount;
+
+ // Go through the accounts seeing if any existing server is deferred to
+ // an account we removed. If so, fix the deferral. Then clean up the prefs
+ // for the removed account.
+ for (int32_t i = 0; i < cnt; i++)
+ {
+ dupAccount = dupAccounts[i];
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ /*
+ * This loop gets run for every incoming server, and is passed a
+ * duplicate account. It checks that the server is not deferred to the
+ * duplicate account. If it is, then it looks up the information for the
+ * duplicate account's server (username, hostName, type), and finds an
+ * account with a server with the same username, hostname, and type, and
+ * if it finds one, defers to that account instead. Generally, this will
+ * be a Local Folders account, since 2.0 has a bug where duplicate Local
+ * Folders accounts are created.
+ */
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); // njn: rename
+ nsCString type;
+ server->GetType(type);
+ if (type.EqualsLiteral("pop3"))
+ {
+ nsCString deferredToAccount;
+ // Get the pref directly, because the GetDeferredToAccount accessor
+ // attempts to fix broken deferrals, but we know more about what the
+ // deferred to account was.
+ server->GetCharValue("deferred_to_account", deferredToAccount);
+ if (!deferredToAccount.IsEmpty())
+ {
+ nsCString dupAccountKey;
+ dupAccount->GetKey(dupAccountKey);
+ if (deferredToAccount.Equals(dupAccountKey))
+ {
+ nsresult rv;
+ nsCString accountPref("mail.account.");
+ nsCString dupAccountServerKey;
+ accountPref.Append(dupAccountKey);
+ accountPref.Append(".server");
+ nsCOMPtr<nsIPrefService> prefservice(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ rv = prefBranch->GetCharPref(accountPref.get(),
+ getter_Copies(dupAccountServerKey));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ nsCOMPtr<nsIPrefBranch> serverPrefBranch;
+ nsCString serverKeyPref(PREF_MAIL_SERVER_PREFIX);
+ serverKeyPref.Append(dupAccountServerKey);
+ serverKeyPref.Append(".");
+ rv = prefservice->GetBranch(serverKeyPref.get(),
+ getter_AddRefs(serverPrefBranch));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ nsCString userName;
+ nsCString hostName;
+ nsCString type;
+ serverPrefBranch->GetCharPref("userName", getter_Copies(userName));
+ serverPrefBranch->GetCharPref("hostname", getter_Copies(hostName));
+ serverPrefBranch->GetCharPref("type", getter_Copies(type));
+ // Find a server with the same info.
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ accountManager->FindServer(userName, hostName, type,
+ getter_AddRefs(server));
+ if (server)
+ {
+ nsCOMPtr<nsIMsgAccount> replacement;
+ accountManager->FindAccountForServer(server,
+ getter_AddRefs(replacement));
+ if (replacement)
+ {
+ nsCString accountKey;
+ replacement->GetKey(accountKey);
+ if (!accountKey.IsEmpty())
+ server->SetCharValue("deferred_to_account", accountKey);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ nsAutoCString accountKeyPref("mail.account.");
+ nsCString dupAccountKey;
+ dupAccount->GetKey(dupAccountKey);
+ if (dupAccountKey.IsEmpty())
+ continue;
+ accountKeyPref += dupAccountKey;
+
+ nsCOMPtr<nsIPrefBranch> accountPrefBranch;
+ rv = prefservice->GetBranch(accountKeyPref.get(),
+ getter_AddRefs(accountPrefBranch));
+ if (accountPrefBranch)
+ accountPrefBranch->DeleteBranch("");
+ }
+
+ // Make sure we have an account that points at the local folders server
+ nsCString localFoldersServerKey;
+ rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER,
+ getter_Copies(localFoldersServerKey));
+
+ if (!localFoldersServerKey.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetIncomingServer(localFoldersServerKey, getter_AddRefs(server));
+ if (server)
+ {
+ nsCOMPtr<nsIMsgAccount> localFoldersAccount;
+ findAccountByServerKey(localFoldersServerKey, getter_AddRefs(localFoldersAccount));
+ // If we don't have an existing account pointing at the local folders
+ // server, we're going to add one.
+ if (!localFoldersAccount)
+ {
+ nsCOMPtr<nsIMsgAccount> account;
+ (void) CreateAccount(getter_AddRefs(account));
+ if (account)
+ account->SetIncomingServer(server);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+// this routine goes through all the identities and makes sure
+// that the special folders for each identity have the
+// correct special folder flags set, e.g, the Sent folder has
+// the sent flag set.
+//
+// it also goes through all the spam settings for each account
+// and makes sure the folder flags are set there, too
+NS_IMETHODIMP
+nsMsgAccountManager::SetSpecialFolders()
+{
+ nsresult rv;
+ nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIArray> identities;
+ GetAllIdentities(getter_AddRefs(identities));
+
+ uint32_t idCount = 0;
+ identities->GetLength(&idCount);
+
+ uint32_t id;
+ nsCString identityKey;
+
+ for (id = 0; id < idCount; id++)
+ {
+ nsCOMPtr<nsIMsgIdentity> thisIdentity(do_QueryElementAt(identities, id, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ if (NS_SUCCEEDED(rv) && thisIdentity)
+ {
+ nsCString folderUri;
+ nsCOMPtr<nsIRDFResource> res;
+ nsCOMPtr<nsIMsgFolder> folder;
+ thisIdentity->GetFccFolder(folderUri);
+ if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res))))
+ {
+ folder = do_QueryInterface(res, &rv);
+ nsCOMPtr <nsIMsgFolder> parent;
+ if (folder && NS_SUCCEEDED(rv))
+ {
+ rv = folder->GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent)
+ rv = folder->SetFlag(nsMsgFolderFlags::SentMail);
+ }
+ }
+ thisIdentity->GetDraftFolder(folderUri);
+ if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res))))
+ {
+ folder = do_QueryInterface(res, &rv);
+ nsCOMPtr <nsIMsgFolder> parent;
+ if (folder && NS_SUCCEEDED(rv))
+ {
+ rv = folder->GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent)
+ rv = folder->SetFlag(nsMsgFolderFlags::Drafts);
+ }
+ }
+ thisIdentity->GetArchiveFolder(folderUri);
+ if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res))))
+ {
+ folder = do_QueryInterface(res, &rv);
+ nsCOMPtr <nsIMsgFolder> parent;
+ if (folder && NS_SUCCEEDED(rv))
+ {
+ rv = folder->GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent)
+ {
+ bool archiveEnabled;
+ thisIdentity->GetArchiveEnabled(&archiveEnabled);
+ if (archiveEnabled)
+ rv = folder->SetFlag(nsMsgFolderFlags::Archive);
+ else
+ rv = folder->ClearFlag(nsMsgFolderFlags::Archive);
+ }
+ }
+ }
+ thisIdentity->GetStationeryFolder(folderUri);
+ if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res))))
+ {
+ folder = do_QueryInterface(res, &rv);
+ if (folder && NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIMsgFolder> parent;
+ rv = folder->GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent) // only set flag if folder is real
+ rv = folder->SetFlag(nsMsgFolderFlags::Templates);
+ }
+ }
+ }
+ }
+
+ // XXX todo
+ // get all servers
+ // get all spam settings for each server
+ // set the JUNK folder flag on the spam folders, right?
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::UnloadAccounts()
+{
+ // release the default account
+ kDefaultServerAtom = nullptr;
+ mFolderFlagAtom = nullptr;
+
+ m_defaultAccount=nullptr;
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ if (!server)
+ continue;
+ nsresult rv;
+ NotifyServerUnloaded(server);
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv)) {
+ removeListenersFromFolder(rootFolder);
+
+ rootFolder->Shutdown(true);
+ }
+ }
+
+ m_accounts.Clear(); // will release all elements
+ m_identities.Clear();
+ m_incomingServers.Clear();
+ mAccountKeyList.Truncate();
+ SetLastServerFound(nullptr, EmptyCString(), EmptyCString(), 0, EmptyCString());
+
+ if (m_accountsLoaded)
+ {
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID);
+ if (mailSession)
+ mailSession->RemoveFolderListener(this);
+ m_accountsLoaded = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::ShutdownServers()
+{
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ if (server)
+ server->Shutdown();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::CloseCachedConnections()
+{
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ if (server)
+ server->CloseCachedConnections();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::CleanupOnExit()
+{
+ // This can get called multiple times, and potentially re-entrantly.
+ // So add some protection against that.
+ if (m_shutdownInProgress)
+ return NS_OK;
+
+ m_shutdownInProgress = true;
+
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+
+ bool emptyTrashOnExit = false;
+ bool cleanupInboxOnExit = false;
+ nsresult rv;
+
+ if (WeAreOffline())
+ break;
+
+ if (!server)
+ continue;
+
+ server->GetEmptyTrashOnExit(&emptyTrashOnExit);
+ nsCOMPtr <nsIImapIncomingServer> imapserver = do_QueryInterface(server);
+ if (imapserver)
+ {
+ imapserver->GetCleanupInboxOnExit(&cleanupInboxOnExit);
+ imapserver->SetShuttingDown(true);
+ }
+ if (emptyTrashOnExit || cleanupInboxOnExit)
+ {
+ nsCOMPtr<nsIMsgFolder> root;
+ server->GetRootFolder(getter_AddRefs(root));
+ nsCString type;
+ server->GetType(type);
+ if (root)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ folder = do_QueryInterface(root);
+ if (folder)
+ {
+ nsCString passwd;
+ bool serverRequiresPasswordForAuthentication = true;
+ bool isImap = type.EqualsLiteral("imap");
+ if (isImap)
+ {
+ server->GetServerRequiresPasswordForBiff(&serverRequiresPasswordForAuthentication);
+ server->GetPassword(passwd);
+ }
+ if (!isImap || (isImap && (!serverRequiresPasswordForAuthentication || !passwd.IsEmpty())))
+ {
+ nsCOMPtr<nsIUrlListener> urlListener;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ if (isImap)
+ urlListener = do_QueryInterface(accountManager, &rv);
+
+ if (isImap && cleanupInboxOnExit)
+ {
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = folder->GetSubFolders(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> inboxFolder(do_QueryInterface(item));
+ if (!inboxFolder)
+ continue;
+
+ uint32_t flags;
+ inboxFolder->GetFlags(&flags);
+ if (flags & nsMsgFolderFlags::Inbox)
+ {
+ rv = inboxFolder->Compact(urlListener, nullptr /* msgwindow */);
+ if (NS_SUCCEEDED(rv))
+ accountManager->SetFolderDoingCleanupInbox(inboxFolder);
+ break;
+ }
+ }
+ }
+ }
+
+ if (emptyTrashOnExit)
+ {
+ rv = folder->EmptyTrash(nullptr, urlListener);
+ if (isImap && NS_SUCCEEDED(rv))
+ accountManager->SetFolderDoingEmptyTrash(folder);
+ }
+
+ if (isImap && urlListener)
+ {
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+
+ bool inProgress = false;
+ if (cleanupInboxOnExit)
+ {
+ int32_t loopCount = 0; // used to break out after 5 seconds
+ accountManager->GetCleanupInboxInProgress(&inProgress);
+ while (inProgress && loopCount++ < 5000)
+ {
+ accountManager->GetCleanupInboxInProgress(&inProgress);
+ PR_CEnterMonitor(folder);
+ PR_CWait(folder, PR_MicrosecondsToInterval(1000UL));
+ PR_CExitMonitor(folder);
+ NS_ProcessPendingEvents(thread, PR_MicrosecondsToInterval(1000UL));
+ }
+ }
+ if (emptyTrashOnExit)
+ {
+ accountManager->GetEmptyTrashInProgress(&inProgress);
+ int32_t loopCount = 0;
+ while (inProgress && loopCount++ < 5000)
+ {
+ accountManager->GetEmptyTrashInProgress(&inProgress);
+ PR_CEnterMonitor(folder);
+ PR_CWait(folder, PR_MicrosecondsToInterval(1000UL));
+ PR_CExitMonitor(folder);
+ NS_ProcessPendingEvents(thread, PR_MicrosecondsToInterval(1000UL));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Try to do this early on in the shutdown process before
+ // necko shuts itself down.
+ CloseCachedConnections();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::WriteToFolderCache(nsIMsgFolderCache *folderCache)
+{
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->WriteToFolderCache(folderCache);
+ }
+ return folderCache ? folderCache->Close() : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsMsgAccountManager::createKeyedAccount(const nsCString& key,
+ nsIMsgAccount ** aAccount)
+{
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccount> account = do_CreateInstance(kMsgAccountCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ account->SetKey(key);
+
+ m_accounts.AppendElement(account);
+
+ // add to string list
+ if (mAccountKeyList.IsEmpty())
+ mAccountKeyList = key;
+ else {
+ mAccountKeyList.Append(',');
+ mAccountKeyList.Append(key);
+ }
+
+ m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, mAccountKeyList.get());
+ account.swap(*aAccount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::CreateAccount(nsIMsgAccount **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsAutoCString key;
+ getUniqueAccountKey(key);
+
+ return createKeyedAccount(key, _retval);
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetAccount(const nsACString& aKey, nsIMsgAccount **aAccount)
+{
+ NS_ENSURE_ARG_POINTER(aAccount);
+ *aAccount = nullptr;
+
+ for (uint32_t i = 0; i < m_accounts.Length(); ++i)
+ {
+ nsCOMPtr<nsIMsgAccount> account(m_accounts[i]);
+ nsCString key;
+ account->GetKey(key);
+ if (key.Equals(aKey))
+ {
+ account.swap(*aAccount);
+ break;
+ }
+ }
+
+ // If not found, create on demand.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::FindServerIndex(nsIMsgIncomingServer* server, int32_t* result)
+{
+ NS_ENSURE_ARG_POINTER(server);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCString key;
+ nsresult rv = server->GetKey(key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // do this by account because the account list is in order
+ uint32_t i;
+ for (i = 0; i < m_accounts.Length(); ++i)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = m_accounts[i]->GetIncomingServer(getter_AddRefs(server));
+ if (!server || NS_FAILED(rv))
+ continue;
+
+ nsCString serverKey;
+ rv = server->GetKey(serverKey);
+ if (NS_FAILED(rv))
+ continue;
+
+ // stop when found,
+ // index will be set to the current index
+ if (serverKey.Equals(key))
+ break;
+ }
+
+ // Even if the search failed, we can return index.
+ // This means that all servers not in the array return an index higher
+ // than all "registered" servers.
+ *result = i;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::AddIncomingServerListener(nsIIncomingServerListener *serverListener)
+{
+ m_incomingServerListeners.AppendObject(serverListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::RemoveIncomingServerListener(nsIIncomingServerListener *serverListener)
+{
+ m_incomingServerListeners.RemoveObject(serverListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::NotifyServerLoaded(nsIMsgIncomingServer *server)
+{
+ int32_t count = m_incomingServerListeners.Count();
+ for(int32_t i = 0; i < count; i++)
+ {
+ nsIIncomingServerListener* listener = m_incomingServerListeners[i];
+ listener->OnServerLoaded(server);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::NotifyServerUnloaded(nsIMsgIncomingServer *server)
+{
+ NS_ENSURE_ARG_POINTER(server);
+
+ int32_t count = m_incomingServerListeners.Count();
+ server->SetFilterList(nullptr); // clear this to cut shutdown leaks. we are always passing valid non-null server here.
+
+ for(int32_t i = 0; i < count; i++)
+ {
+ nsIIncomingServerListener* listener = m_incomingServerListeners[i];
+ listener->OnServerUnloaded(server);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::NotifyServerChanged(nsIMsgIncomingServer *server)
+{
+ int32_t count = m_incomingServerListeners.Count();
+ for(int32_t i = 0; i < count; i++)
+ {
+ nsIIncomingServerListener* listener = m_incomingServerListeners[i];
+ listener->OnServerChanged(server);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::FindServerByURI(nsIURI *aURI, bool aRealFlag,
+ nsIMsgIncomingServer** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get username and hostname and port so we can get the server
+ nsAutoCString username;
+ nsAutoCString escapedUsername;
+ rv = aURI->GetUserPass(escapedUsername);
+ if (NS_SUCCEEDED(rv) && !escapedUsername.IsEmpty())
+ MsgUnescapeString(escapedUsername, 0, username);
+
+ nsAutoCString hostname;
+ nsAutoCString escapedHostname;
+ rv = aURI->GetHost(escapedHostname);
+ if (NS_SUCCEEDED(rv) && !escapedHostname.IsEmpty())
+ MsgUnescapeString(escapedHostname, 0, hostname);
+
+ nsAutoCString type;
+ rv = aURI->GetScheme(type);
+ if (NS_SUCCEEDED(rv) && !type.IsEmpty())
+ {
+ // now modify type if pop or news
+ if (type.EqualsLiteral("pop"))
+ type.AssignLiteral("pop3");
+ // we use "nntp" in the server list so translate it here.
+ else if (type.EqualsLiteral("news"))
+ type.AssignLiteral("nntp");
+ // we use "any" as the wildcard type.
+ else if (type.EqualsLiteral("any"))
+ type.Truncate();
+ }
+
+ int32_t port = 0;
+ // check the port of the scheme is not 'none' or blank
+ if (!(type.EqualsLiteral("none") || type.IsEmpty()))
+ {
+ rv = aURI->GetPort(&port);
+ // Set the port to zero if we got a -1 (use default)
+ if (NS_SUCCEEDED(rv) && (port == -1))
+ port = 0;
+ }
+
+ return findServerInternal(username, hostname, type, port, aRealFlag, aResult);
+}
+
+nsresult
+nsMsgAccountManager::findServerInternal(const nsACString& username,
+ const nsACString& hostname,
+ const nsACString& type,
+ int32_t port,
+ bool aRealFlag,
+ nsIMsgIncomingServer** aResult)
+{
+ // If 'aRealFlag' is set then we want to scan all existing accounts
+ // to make sure there's no duplicate including those whose host and/or
+ // user names have been changed.
+ if (!aRealFlag &&
+ (m_lastFindServerUserName.Equals(username)) &&
+ (m_lastFindServerHostName.Equals(hostname)) &&
+ (m_lastFindServerType.Equals(type)) &&
+ (m_lastFindServerPort == port) &&
+ m_lastFindServerResult)
+ {
+ NS_ADDREF(*aResult = m_lastFindServerResult);
+ return NS_OK;
+ }
+
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ // Find matching server by user+host+type+port.
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+
+ if (!server)
+ continue;
+
+ nsresult rv;
+ nsCString thisHostname;
+ if (aRealFlag)
+ rv = server->GetRealHostName(thisHostname);
+ else
+ rv = server->GetHostName(thisHostname);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCString thisUsername;
+ if (aRealFlag)
+ rv = server->GetRealUsername(thisUsername);
+ else
+ rv = server->GetUsername(thisUsername);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCString thisType;
+ rv = server->GetType(thisType);
+ if (NS_FAILED(rv))
+ continue;
+
+ int32_t thisPort = -1; // use the default port identifier
+ // Don't try and get a port for the 'none' scheme
+ if (!thisType.EqualsLiteral("none"))
+ {
+ rv = server->GetPort(&thisPort);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ }
+
+ // treat "" as a wild card, so if the caller passed in "" for the desired attribute
+ // treat it as a match
+ if ((type.IsEmpty() || thisType.Equals(type)) &&
+ (hostname.IsEmpty() || thisHostname.Equals(hostname, nsCaseInsensitiveCStringComparator())) &&
+ (!(port != 0) || (port == thisPort)) &&
+ (username.IsEmpty() || thisUsername.Equals(username)))
+ {
+ // stop on first find; cache for next time
+ if (!aRealFlag)
+ SetLastServerFound(server, hostname, username, port, type);
+
+ NS_ADDREF(*aResult = server);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::FindServer(const nsACString& username,
+ const nsACString& hostname,
+ const nsACString& type,
+ nsIMsgIncomingServer** aResult)
+{
+ return findServerInternal(username, hostname, type, 0, false, aResult);
+}
+
+// Interface called by UI js only (always return true).
+NS_IMETHODIMP
+nsMsgAccountManager::FindRealServer(const nsACString& username,
+ const nsACString& hostname,
+ const nsACString& type,
+ int32_t port,
+ nsIMsgIncomingServer** aResult)
+{
+ *aResult = nullptr;
+ findServerInternal(username, hostname, type, port, true, aResult);
+ return NS_OK;
+}
+
+void
+nsMsgAccountManager::findAccountByServerKey(const nsCString &aKey,
+ nsIMsgAccount **aResult)
+{
+ *aResult = nullptr;
+
+ for (uint32_t i = 0; i < m_accounts.Length(); ++i)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_accounts[i]->GetIncomingServer(getter_AddRefs(server));
+ if (!server || NS_FAILED(rv))
+ continue;
+
+ nsCString key;
+ rv = server->GetKey(key);
+ if (NS_FAILED(rv))
+ continue;
+
+ // if the keys are equal, the servers are equal
+ if (key.Equals(aKey))
+ {
+ NS_ADDREF(*aResult = m_accounts[i]);
+ break; // stop on first found account
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::FindAccountForServer(nsIMsgIncomingServer *server,
+ nsIMsgAccount **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!server)
+ {
+ (*aResult) = nullptr;
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ nsCString key;
+ rv = server->GetKey(key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ findAccountByServerKey(key, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetFirstIdentityForServer(nsIMsgIncomingServer *aServer, nsIMsgIdentity **aIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+ NS_ENSURE_ARG_POINTER(aIdentity);
+
+ nsCOMPtr<nsIArray> identities;
+ nsresult rv = GetIdentitiesForServer(aServer, getter_AddRefs(identities));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // not all servers have identities
+ // for example, Local Folders
+ uint32_t numIdentities;
+ rv = identities->GetLength(&numIdentities);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (numIdentities > 0)
+ {
+ nsCOMPtr<nsIMsgIdentity> identity(do_QueryElementAt(identities, 0, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ identity.swap(*aIdentity);
+ }
+ else
+ *aIdentity = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetIdentitiesForServer(nsIMsgIncomingServer *server,
+ nsIArray **_retval)
+{
+ NS_ENSURE_ARG_POINTER(server);
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> identities(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString serverKey;
+ rv = server->GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < m_accounts.Length(); ++i)
+ {
+ nsCOMPtr<nsIMsgAccount> account(m_accounts[i]);
+
+ nsCOMPtr<nsIMsgIncomingServer> thisServer;
+ rv = account->GetIncomingServer(getter_AddRefs(thisServer));
+ if (NS_FAILED(rv) || !thisServer)
+ continue;
+
+ nsAutoCString thisServerKey;
+ rv = thisServer->GetKey(thisServerKey);
+ if (serverKey.Equals(thisServerKey))
+ {
+ nsCOMPtr<nsIArray> theseIdentities;
+ rv = account->GetIdentities(getter_AddRefs(theseIdentities));
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t theseLength;
+ rv = theseIdentities->GetLength(&theseLength);
+ if (NS_SUCCEEDED(rv))
+ {
+ for (uint32_t j = 0; j < theseLength; ++j)
+ {
+ nsCOMPtr<nsISupports> id(do_QueryElementAt(theseIdentities, j, &rv));
+ if (NS_SUCCEEDED(rv))
+ identities->AppendElement(id, false);
+ }
+ }
+ }
+ }
+ }
+
+ identities.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetServersForIdentity(nsIMsgIdentity *aIdentity,
+ nsIArray **_retval)
+{
+ NS_ENSURE_ARG_POINTER(aIdentity);
+
+ nsresult rv;
+ rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> servers(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < m_accounts.Length(); ++i)
+ {
+ nsCOMPtr<nsIArray> identities;
+ if (NS_FAILED(m_accounts[i]->GetIdentities(getter_AddRefs(identities))))
+ continue;
+
+ uint32_t idCount = 0;
+ if (NS_FAILED(identities->GetLength(&idCount)))
+ continue;
+
+ uint32_t id;
+ nsCString identityKey;
+ rv = aIdentity->GetKey(identityKey);
+ for (id = 0; id < idCount; id++)
+ {
+ nsCOMPtr<nsIMsgIdentity> thisIdentity(do_QueryElementAt(identities, id, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString thisIdentityKey;
+ rv = thisIdentity->GetKey(thisIdentityKey);
+
+ if (NS_SUCCEEDED(rv) && identityKey.Equals(thisIdentityKey))
+ {
+ nsCOMPtr<nsIMsgIncomingServer> thisServer;
+ rv = m_accounts[i]->GetIncomingServer(getter_AddRefs(thisServer));
+ if (thisServer && NS_SUCCEEDED(rv))
+ {
+ servers->AppendElement(thisServer, false);
+ break;
+ }
+ }
+ }
+ }
+ }
+ servers.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::AddRootFolderListener(nsIFolderListener *aListener)
+{
+ NS_ENSURE_TRUE(aListener, NS_OK);
+ mFolderListeners.AppendElement(aListener);
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = iter.Data()->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ rv = rootFolder->AddFolderListener(aListener);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::RemoveRootFolderListener(nsIFolderListener *aListener)
+{
+ NS_ENSURE_TRUE(aListener, NS_OK);
+ mFolderListeners.RemoveElement(aListener);
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = iter.Data()->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ rv = rootFolder->RemoveFolderListener(aListener);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::SetLocalFoldersServer(nsIMsgIncomingServer *aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+ nsCString key;
+ nsresult rv = aServer->GetKey(key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, key.get());
+}
+
+NS_IMETHODIMP nsMsgAccountManager::GetLocalFoldersServer(nsIMsgIncomingServer **aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ nsCString serverKey;
+
+ nsresult rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, getter_Copies(serverKey));
+
+ if (NS_SUCCEEDED(rv) && !serverKey.IsEmpty())
+ {
+ rv = GetIncomingServer(serverKey, aServer);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ // otherwise, we're going to fall through to looking for an existing local
+ // folders account, because now we fail creating one if one already exists.
+ }
+
+ // try ("nobody","Local Folders","none"), and work down to any "none" server.
+ rv = FindServer(NS_LITERAL_CSTRING("nobody"), NS_LITERAL_CSTRING("Local Folders"),
+ NS_LITERAL_CSTRING("none"), aServer);
+ if (NS_FAILED(rv) || !*aServer)
+ {
+ rv = FindServer(NS_LITERAL_CSTRING("nobody"), EmptyCString(), NS_LITERAL_CSTRING("none"), aServer);
+ if (NS_FAILED(rv) || !*aServer)
+ {
+ rv = FindServer(EmptyCString(), NS_LITERAL_CSTRING("Local Folders"),
+ NS_LITERAL_CSTRING("none"), aServer);
+ if (NS_FAILED(rv) || !*aServer)
+ rv = FindServer(EmptyCString(), EmptyCString(), NS_LITERAL_CSTRING("none"), aServer);
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!*aServer)
+ return NS_ERROR_FAILURE;
+
+ // we don't want the Smart Mailboxes server to be the local server.
+ bool hidden;
+ (*aServer)->GetHidden(&hidden);
+ if (hidden)
+ return NS_ERROR_FAILURE;
+
+ rv = SetLocalFoldersServer(*aServer);
+ return rv;
+}
+
+nsresult nsMsgAccountManager::GetLocalFoldersPrettyName(nsString &localFoldersName)
+{
+ // we don't want "nobody at Local Folders" to show up in the
+ // folder pane, so we set the pretty name to a localized "Local Folders"
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+
+ rv = sBundleService->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return bundle->GetStringFromName(u"localFolders", getter_Copies(localFoldersName));
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::CreateLocalMailAccount()
+{
+ // create the server
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = CreateIncomingServer(NS_LITERAL_CSTRING("nobody"),
+ NS_LITERAL_CSTRING("Local Folders"),
+ NS_LITERAL_CSTRING("none"), getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString localFoldersName;
+ rv = GetLocalFoldersPrettyName(localFoldersName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ server->SetPrettyName(localFoldersName);
+
+ nsCOMPtr<nsINoIncomingServer> noServer;
+ noServer = do_QueryInterface(server, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // create the directory structure for old 4.x "Local Mail"
+ // under <profile dir>/Mail/Local Folders or
+ // <"mail.directory" pref>/Local Folders
+ nsCOMPtr <nsIFile> mailDir;
+ nsCOMPtr <nsIFile> localFile;
+ bool dirExists;
+
+ // we want <profile>/Mail
+ rv = NS_GetSpecialDirectory(NS_APP_MAIL_50_DIR, getter_AddRefs(mailDir));
+ if (NS_FAILED(rv)) return rv;
+ localFile = do_QueryInterface(mailDir);
+
+ rv = mailDir->Exists(&dirExists);
+ if (NS_SUCCEEDED(rv) && !dirExists)
+ rv = mailDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ if (NS_FAILED(rv)) return rv;
+
+ // set the default local path for "none"
+ rv = server->SetDefaultLocalPath(localFile);
+ if (NS_FAILED(rv)) return rv;
+
+ // Create an account when valid server values are established.
+ // This will keep the status of accounts sane by avoiding the addition of incomplete accounts.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = CreateAccount(getter_AddRefs(account));
+ if (NS_FAILED(rv)) return rv;
+
+ // notice, no identity for local mail
+ // hook the server to the account
+ // after we set the server's local path
+ // (see bug #66018)
+ account->SetIncomingServer(server);
+
+ // remember this as the local folders server
+ return SetLocalFoldersServer(server);
+}
+
+ // nsIUrlListener methods
+
+NS_IMETHODIMP
+nsMsgAccountManager::OnStartRunningUrl(nsIURI * aUrl)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode)
+{
+ if (aUrl)
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl);
+ if (imapUrl)
+ {
+ nsImapAction imapAction = nsIImapUrl::nsImapTest;
+ imapUrl->GetImapAction(&imapAction);
+ switch(imapAction)
+ {
+ case nsIImapUrl::nsImapExpungeFolder:
+ if (m_folderDoingCleanupInbox)
+ {
+ PR_CEnterMonitor(m_folderDoingCleanupInbox);
+ PR_CNotifyAll(m_folderDoingCleanupInbox);
+ m_cleanupInboxInProgress = false;
+ PR_CExitMonitor(m_folderDoingCleanupInbox);
+ m_folderDoingCleanupInbox=nullptr; //reset to nullptr
+ }
+ break;
+ case nsIImapUrl::nsImapDeleteAllMsgs:
+ if (m_folderDoingEmptyTrash)
+ {
+ PR_CEnterMonitor(m_folderDoingEmptyTrash);
+ PR_CNotifyAll(m_folderDoingEmptyTrash);
+ m_emptyTrashInProgress = false;
+ PR_CExitMonitor(m_folderDoingEmptyTrash);
+ m_folderDoingEmptyTrash = nullptr; //reset to nullptr;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SetFolderDoingEmptyTrash(nsIMsgFolder *folder)
+{
+ m_folderDoingEmptyTrash = folder;
+ m_emptyTrashInProgress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetEmptyTrashInProgress(bool *bVal)
+{
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_emptyTrashInProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SetFolderDoingCleanupInbox(nsIMsgFolder *folder)
+{
+ m_folderDoingCleanupInbox = folder;
+ m_cleanupInboxInProgress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetCleanupInboxInProgress(bool *bVal)
+{
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_cleanupInboxInProgress;
+ return NS_OK;
+}
+
+void
+nsMsgAccountManager::SetLastServerFound(nsIMsgIncomingServer *server, const nsACString& hostname,
+ const nsACString& username, const int32_t port, const nsACString& type)
+{
+ m_lastFindServerResult = server;
+ m_lastFindServerHostName = hostname;
+ m_lastFindServerUserName = username;
+ m_lastFindServerPort = port;
+ m_lastFindServerType = type;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SaveAccountInfo()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return pref->SavePrefFile(nullptr);
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetChromePackageName(const nsACString& aExtensionName, nsACString& aChromePackageName)
+{
+ nsresult rv;
+ nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsISimpleEnumerator> e;
+ rv = catman->EnumerateCategory(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, getter_AddRefs(e));
+ if(NS_SUCCEEDED(rv) && e) {
+ while (true) {
+ nsCOMPtr<nsISupports> supports;
+ rv = e->GetNext(getter_AddRefs(supports));
+ nsCOMPtr<nsISupportsCString> catEntry = do_QueryInterface(supports);
+ if (NS_FAILED(rv) || !catEntry)
+ break;
+
+ nsAutoCString entryString;
+ rv = catEntry->GetData(entryString);
+ if (NS_FAILED(rv))
+ break;
+
+ nsCString contractidString;
+ rv = catman->GetCategoryEntry(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, entryString.get(),
+ getter_Copies(contractidString));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr <nsIMsgAccountManagerExtension> extension = do_GetService(contractidString.get(), &rv);
+ if (NS_FAILED(rv) || !extension)
+ break;
+
+ nsCString name;
+ rv = extension->GetName(name);
+ if (NS_FAILED(rv))
+ break;
+
+ if (name.Equals(aExtensionName))
+ return extension->GetChromePackageName(aChromePackageName);
+ }
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+class VFChangeListenerEvent : public mozilla::Runnable
+{
+public:
+ VFChangeListenerEvent(VirtualFolderChangeListener *vfChangeListener,
+ nsIMsgFolder *virtFolder, nsIMsgDatabase *virtDB)
+ : mVFChangeListener(vfChangeListener), mFolder(virtFolder), mDB(virtDB)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mVFChangeListener)
+ mVFChangeListener->ProcessUpdateEvent(mFolder, mDB);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<VirtualFolderChangeListener> mVFChangeListener;
+ nsCOMPtr<nsIMsgFolder> mFolder;
+ nsCOMPtr<nsIMsgDatabase> mDB;
+};
+
+NS_IMPL_ISUPPORTS(VirtualFolderChangeListener, nsIDBChangeListener)
+
+VirtualFolderChangeListener::VirtualFolderChangeListener() :
+ m_searchOnMsgStatus(false), m_batchingEvents(false)
+{}
+
+nsresult VirtualFolderChangeListener::Init()
+{
+ nsCOMPtr <nsIMsgDatabase> msgDB;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+
+ nsresult rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(msgDB));
+ if (NS_SUCCEEDED(rv) && msgDB)
+ {
+ nsCString searchTermString;
+ dbFolderInfo->GetCharProperty("searchStr", searchTermString);
+ nsCOMPtr<nsIMsgFilterService> filterService = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ rv = filterService->GetTempFilterList(m_virtualFolder, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgFilter> tempFilter;
+ filterList->CreateFilter(NS_LITERAL_STRING("temp"), getter_AddRefs(tempFilter));
+ NS_ENSURE_SUCCESS(rv, rv);
+ filterList->ParseCondition(tempFilter, searchTermString.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsArray> searchTerms;
+ rv = tempFilter->GetSearchTerms(getter_AddRefs(searchTerms));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we add the search scope right before we match the header,
+ // because we don't want the search scope caching the body input
+ // stream, because that holds onto the mailbox file, breaking
+ // compaction.
+
+ // add each item in termsArray to the search session
+ uint32_t numTerms;
+ searchTerms->Count(&numTerms);
+ for (uint32_t i = 0; i < numTerms; i++)
+ {
+ nsCOMPtr <nsIMsgSearchTerm> searchTerm (do_QueryElementAt(searchTerms, i));
+ nsMsgSearchAttribValue attrib;
+ searchTerm->GetAttrib(&attrib);
+ if (attrib == nsMsgSearchAttrib::MsgStatus)
+ m_searchOnMsgStatus = true;
+ m_searchSession->AppendTerm(searchTerm);
+ }
+ }
+ return rv;
+}
+
+ /**
+ * nsIDBChangeListener
+ */
+
+NS_IMETHODIMP
+VirtualFolderChangeListener::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrChanged, bool aPreChange, uint32_t *aStatus,
+ nsIDBChangeListener *aInstigator)
+{
+ const uint32_t kMatch = 0x1;
+ const uint32_t kRead = 0x2;
+ const uint32_t kNew = 0x4;
+ NS_ENSURE_ARG_POINTER(aHdrChanged);
+ NS_ENSURE_ARG_POINTER(aStatus);
+
+ uint32_t flags;
+ bool match;
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // we don't want any early returns from this function, until we've
+ // called ClearScopes on the search session.
+ m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching);
+ rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &match);
+ m_searchSession->ClearScopes();
+ NS_ENSURE_SUCCESS(rv, rv);
+ aHdrChanged->GetFlags(&flags);
+
+ if (aPreChange) // We're looking at the old header, save status
+ {
+ *aStatus = 0;
+ if (match)
+ *aStatus |= kMatch;
+ if (flags & nsMsgMessageFlags::Read)
+ *aStatus |= kRead;
+ if (flags & nsMsgMessageFlags::New)
+ *aStatus |= kNew;
+ return NS_OK;
+ }
+
+ // This is the post change section where changes are detected
+
+ bool wasMatch = *aStatus & kMatch;
+ if (!match && !wasMatch) // header not in virtual folder
+ return NS_OK;
+
+ int32_t totalDelta = 0, unreadDelta = 0, newDelta = 0;
+
+ if (match) {
+ totalDelta++;
+ if (!(flags & nsMsgMessageFlags::Read))
+ unreadDelta++;
+ if (flags & nsMsgMessageFlags::New)
+ newDelta++;
+ }
+
+ if (wasMatch) {
+ totalDelta--;
+ if (!(*aStatus & kRead)) unreadDelta--;
+ if (*aStatus & kNew) newDelta--;
+ }
+
+ if ( !(unreadDelta || totalDelta || newDelta) )
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (unreadDelta)
+ dbFolderInfo->ChangeNumUnreadMessages(unreadDelta);
+
+ if (newDelta)
+ {
+ int32_t numNewMessages;
+ m_virtualFolder->GetNumNewMessages(false, &numNewMessages);
+ m_virtualFolder->SetNumNewMessages(numNewMessages + newDelta);
+ m_virtualFolder->SetHasNewMessages(numNewMessages + newDelta > 0);
+ }
+
+ if (totalDelta)
+ {
+ dbFolderInfo->ChangeNumMessages(totalDelta);
+ nsCString searchUri;
+ m_virtualFolder->GetURI(searchUri);
+ msgDB->UpdateHdrInCache(searchUri.get(), aHdrChanged, totalDelta == 1);
+ }
+
+ PostUpdateEvent(m_virtualFolder, virtDatabase);
+
+ return NS_OK;
+}
+
+void VirtualFolderChangeListener::DecrementNewMsgCount()
+{
+ int32_t numNewMessages;
+ m_virtualFolder->GetNumNewMessages(false, &numNewMessages);
+ if (numNewMessages > 0)
+ numNewMessages--;
+ m_virtualFolder->SetNumNewMessages(numNewMessages);
+ if (!numNewMessages)
+ m_virtualFolder->SetHasNewMessages(false);
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags, nsIDBChangeListener *aInstigator)
+{
+ nsCOMPtr <nsIMsgDatabase> msgDB;
+
+ nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB));
+ bool oldMatch = false, newMatch = false;
+ // we don't want any early returns from this function, until we've
+ // called ClearScopes 0n the search session.
+ m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching);
+ rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &newMatch);
+ if (NS_SUCCEEDED(rv) && m_searchOnMsgStatus)
+ {
+ // if status is a search criteria, check if the header matched before
+ // it changed, in order to determine if we need to bump the counts.
+ aHdrChanged->SetFlags(aOldFlags);
+ rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &oldMatch);
+ aHdrChanged->SetFlags(aNewFlags); // restore new flags even on match failure.
+ }
+ else
+ oldMatch = newMatch;
+ m_searchSession->ClearScopes();
+ NS_ENSURE_SUCCESS(rv, rv);
+ // we don't want to change the total counts if this virtual folder is open in a view,
+ // because we won't remove the header from view while it's open. On the other hand,
+ // it's hard to fix the count when the user clicks away to another folder, w/o re-running
+ // the search, or setting some sort of pending count change.
+ // Maybe this needs to be handled in the view code...the view could do the same calculation
+ // and also keep track of the counts changed. Then, when the view was closed, if it's a virtual
+ // folder, it could update the counts for the db.
+ if (oldMatch != newMatch || (oldMatch && (aOldFlags & nsMsgMessageFlags::Read) != (aNewFlags & nsMsgMessageFlags::Read)))
+ {
+ nsCOMPtr <nsIMsgDatabase> virtDatabase;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+
+ rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t totalDelta = 0, unreadDelta = 0;
+ if (oldMatch != newMatch)
+ {
+ // bool isOpen = false;
+// nsCOMPtr <nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID);
+// if (mailSession && aFolder)
+// mailSession->IsFolderOpenInWindow(m_virtualFolder, &isOpen);
+ // we can't remove headers that no longer match - but we might add headers that newly match, someday.
+// if (!isOpen)
+ totalDelta = (oldMatch) ? -1 : 1;
+ }
+ bool msgHdrIsRead;
+ aHdrChanged->GetIsRead(&msgHdrIsRead);
+ if (oldMatch == newMatch) // read flag changed state
+ unreadDelta = (msgHdrIsRead) ? -1 : 1;
+ else if (oldMatch) // else header should removed
+ unreadDelta = (aOldFlags & nsMsgMessageFlags::Read) ? 0 : -1;
+ else // header should be added
+ unreadDelta = (aNewFlags & nsMsgMessageFlags::Read) ? 0 : 1;
+ if (unreadDelta)
+ dbFolderInfo->ChangeNumUnreadMessages(unreadDelta);
+ if (totalDelta)
+ dbFolderInfo->ChangeNumMessages(totalDelta);
+ if (unreadDelta == -1 && aOldFlags & nsMsgMessageFlags::New)
+ DecrementNewMsgCount();
+
+ if (totalDelta)
+ {
+ nsCString searchUri;
+ m_virtualFolder->GetURI(searchUri);
+ msgDB->UpdateHdrInCache(searchUri.get(), aHdrChanged, totalDelta == 1);
+ }
+
+ PostUpdateEvent(m_virtualFolder, virtDatabase);
+ }
+ else if (oldMatch && (aOldFlags & nsMsgMessageFlags::New) &&
+ !(aNewFlags & nsMsgMessageFlags::New))
+ DecrementNewMsgCount();
+
+ return rv;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ nsCOMPtr <nsIMsgDatabase> msgDB;
+
+ nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool match = false;
+ m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching);
+ // Since the notifier went to the trouble of passing in the msg flags,
+ // we should use them when doing the match.
+ uint32_t msgFlags;
+ aHdrDeleted->GetFlags(&msgFlags);
+ aHdrDeleted->SetFlags(aFlags);
+ rv = m_searchSession->MatchHdr(aHdrDeleted, msgDB, &match);
+ aHdrDeleted->SetFlags(msgFlags);
+ m_searchSession->ClearScopes();
+ if (match)
+ {
+ nsCOMPtr <nsIMsgDatabase> virtDatabase;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+
+ rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool msgHdrIsRead;
+ aHdrDeleted->GetIsRead(&msgHdrIsRead);
+ if (!msgHdrIsRead)
+ dbFolderInfo->ChangeNumUnreadMessages(-1);
+ dbFolderInfo->ChangeNumMessages(-1);
+ if (aFlags & nsMsgMessageFlags::New)
+ {
+ int32_t numNewMessages;
+ m_virtualFolder->GetNumNewMessages(false, &numNewMessages);
+ m_virtualFolder->SetNumNewMessages(numNewMessages - 1);
+ if (numNewMessages == 1)
+ m_virtualFolder->SetHasNewMessages(false);
+ }
+
+ nsCString searchUri;
+ m_virtualFolder->GetURI(searchUri);
+ msgDB->UpdateHdrInCache(searchUri.get(), aHdrDeleted, false);
+
+ PostUpdateEvent(m_virtualFolder, virtDatabase);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnHdrAdded(nsIMsgDBHdr *aNewHdr, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+
+ nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool match = false;
+ if (!m_searchSession)
+ return NS_ERROR_NULL_POINTER;
+
+ m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching);
+ rv = m_searchSession->MatchHdr(aNewHdr, msgDB, &match);
+ m_searchSession->ClearScopes();
+ if (match)
+ {
+ nsCOMPtr <nsIMsgDatabase> virtDatabase;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+
+ rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool msgHdrIsRead;
+ uint32_t msgFlags;
+ aNewHdr->GetIsRead(&msgHdrIsRead);
+ aNewHdr->GetFlags(&msgFlags);
+ if (!msgHdrIsRead)
+ dbFolderInfo->ChangeNumUnreadMessages(1);
+ if (msgFlags & nsMsgMessageFlags::New)
+ {
+ int32_t numNewMessages;
+ m_virtualFolder->GetNumNewMessages(false, &numNewMessages);
+ m_virtualFolder->SetHasNewMessages(true);
+ m_virtualFolder->SetNumNewMessages(numNewMessages + 1);
+ }
+ nsCString searchUri;
+ m_virtualFolder->GetURI(searchUri);
+ msgDB->UpdateHdrInCache(searchUri.get(), aNewHdr, true);
+ dbFolderInfo->ChangeNumMessages(1);
+ PostUpdateEvent(m_virtualFolder, virtDatabase);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
+{
+ nsCOMPtr <nsIMsgDatabase> msgDB = do_QueryInterface(instigator);
+ if (msgDB)
+ msgDB->RemoveListener(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+VirtualFolderChangeListener::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnReadChanged(nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnJunkScoreChanged(nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+nsresult VirtualFolderChangeListener::PostUpdateEvent(nsIMsgFolder *virtualFolder,
+ nsIMsgDatabase *virtDatabase)
+{
+ if (m_batchingEvents)
+ return NS_OK;
+ m_batchingEvents = true;
+ nsCOMPtr<nsIRunnable> event = new VFChangeListenerEvent(this, virtualFolder,
+ virtDatabase);
+ return NS_DispatchToCurrentThread(event);
+}
+
+void VirtualFolderChangeListener::ProcessUpdateEvent(nsIMsgFolder *virtFolder,
+ nsIMsgDatabase *virtDB)
+{
+ m_batchingEvents = false;
+ virtFolder->UpdateSummaryTotals(true); // force update from db.
+ virtDB->Commit(nsMsgDBCommitType::kLargeCommit);
+}
+
+nsresult nsMsgAccountManager::GetVirtualFoldersFile(nsCOMPtr<nsIFile>& file)
+{
+ nsCOMPtr<nsIFile> profileDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = profileDir->AppendNative(nsDependentCString("virtualFolders.dat"));
+ if (NS_SUCCEEDED(rv))
+ file = do_QueryInterface(profileDir, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::LoadVirtualFolders()
+{
+ nsCOMPtr <nsIFile> file;
+ GetVirtualFoldersFile(file);
+ if (!file)
+ return NS_ERROR_FAILURE;
+
+ if (m_virtualFoldersLoaded)
+ return NS_OK;
+
+ m_loadingVirtualFolders = true;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ if (msgDBService)
+ {
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFileInputStream> fileStream = do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = fileStream->Init(file, PR_RDONLY, 0664, false);
+ nsCOMPtr <nsILineInputStream> lineInputStream(do_QueryInterface(fileStream));
+
+ bool isMore = true;
+ nsAutoCString buffer;
+ int32_t version = -1;
+ nsCOMPtr <nsIMsgFolder> virtualFolder;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr<nsIRDFResource> resource;
+ nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIArray> allFolders;
+
+ while (isMore &&
+ NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore)))
+ {
+ if (!buffer.IsEmpty())
+ {
+ if (version == -1)
+ {
+ buffer.Cut(0, 8);
+ nsresult irv;
+ version = buffer.ToInteger(&irv);
+ continue;
+ }
+ if (Substring(buffer, 0, 4).Equals("uri="))
+ {
+ buffer.Cut(0, 4);
+ dbFolderInfo = nullptr;
+
+ rv = rdf->GetResource(buffer, getter_AddRefs(resource));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ virtualFolder = do_QueryInterface(resource);
+ if (!virtualFolder)
+ NS_WARNING("Failed to QI virtual folder, is this leftover from an optional account type?");
+ else
+ {
+ nsCOMPtr <nsIMsgFolder> grandParent;
+ nsCOMPtr <nsIMsgFolder> oldParent;
+ nsCOMPtr <nsIMsgFolder> parentFolder;
+ bool isServer;
+ do
+ {
+ // need to add the folder as a sub-folder of its parent.
+ int32_t lastSlash = buffer.RFindChar('/');
+ if (lastSlash == kNotFound)
+ break;
+ nsDependentCSubstring parentUri(buffer, 0, lastSlash);
+ // hold a reference so it won't get deleted before it's parented.
+ oldParent = parentFolder;
+
+ rdf->GetResource(parentUri, getter_AddRefs(resource));
+ parentFolder = do_QueryInterface(resource);
+ if (parentFolder)
+ {
+ nsAutoString currentFolderNameStr;
+ nsAutoCString currentFolderNameCStr;
+ MsgUnescapeString(nsCString(Substring(buffer, lastSlash + 1, buffer.Length())), 0, currentFolderNameCStr);
+ CopyUTF8toUTF16(currentFolderNameCStr, currentFolderNameStr);
+ nsCOMPtr <nsIMsgFolder> childFolder;
+ nsCOMPtr <nsIMsgDatabase> db;
+ // force db to get created.
+ virtualFolder->SetParent(parentFolder);
+ rv = virtualFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ msgDBService->CreateNewDB(virtualFolder, getter_AddRefs(db));
+ if (db)
+ rv = db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ else
+ break;
+
+ parentFolder->AddSubfolder(currentFolderNameStr, getter_AddRefs(childFolder));
+ virtualFolder->SetFlag(nsMsgFolderFlags::Virtual);
+ if (childFolder)
+ parentFolder->NotifyItemAdded(childFolder);
+ // here we make sure if our parent is rooted - if not, we're
+ // going to loop and add our parent as a child of its grandparent
+ // and repeat until we get to the server, or a folder that
+ // has its parent set.
+ parentFolder->GetParent(getter_AddRefs(grandParent));
+ parentFolder->GetIsServer(&isServer);
+ buffer.SetLength(lastSlash);
+ }
+ else
+ break;
+ } while (!grandParent && !isServer);
+ }
+ }
+ else if (dbFolderInfo && Substring(buffer, 0, 6).Equals("scope="))
+ {
+ buffer.Cut(0, 6);
+ // if this is a cross folder virtual folder, we have a list of folders uris,
+ // and we have to add a pending listener for each of them.
+ if (!buffer.IsEmpty())
+ {
+ ParseAndVerifyVirtualFolderScope(buffer, rdf);
+ dbFolderInfo->SetCharProperty(kSearchFolderUriProp, buffer);
+ AddVFListenersForVF(virtualFolder, buffer, rdf, msgDBService);
+ }
+ }
+ else if (dbFolderInfo && Substring(buffer, 0, 6).Equals("terms="))
+ {
+ buffer.Cut(0, 6);
+ dbFolderInfo->SetCharProperty("searchStr", buffer);
+ }
+ else if (dbFolderInfo && Substring(buffer, 0, 13).Equals("searchOnline="))
+ {
+ buffer.Cut(0, 13);
+ dbFolderInfo->SetBooleanProperty("searchOnline", buffer.Equals("true"));
+ }
+ else if (dbFolderInfo &&
+ Substring(buffer, 0, SEARCH_FOLDER_FLAG_LEN + 1)
+ .Equals(SEARCH_FOLDER_FLAG"="))
+ {
+ buffer.Cut(0, SEARCH_FOLDER_FLAG_LEN + 1);
+ dbFolderInfo->SetCharProperty(SEARCH_FOLDER_FLAG, buffer);
+ }
+ }
+ }
+ }
+
+ m_loadingVirtualFolders = false;
+ m_virtualFoldersLoaded = true;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::SaveVirtualFolders()
+{
+ if (!m_virtualFoldersLoaded)
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ GetVirtualFoldersFile(file);
+
+ // Open a buffered, safe output stream
+ nsCOMPtr<nsIOutputStream> outStream;
+ nsresult rv = MsgNewSafeBufferedFileOutputStream(getter_AddRefs(outStream),
+ file,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ WriteLineToOutputStream("version=", "1", outStream);
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ if (server)
+ {
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ {
+ nsCOMPtr <nsIArray> virtualFolders;
+ nsresult rv = rootFolder->GetFoldersWithFlags(nsMsgFolderFlags::Virtual,
+ getter_AddRefs(virtualFolders));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ uint32_t vfCount;
+ virtualFolders->GetLength(&vfCount);
+ for (uint32_t folderIndex = 0; folderIndex < vfCount; folderIndex++)
+ {
+ nsCOMPtr <nsIRDFResource> folderRes (do_QueryElementAt(virtualFolders, folderIndex));
+ nsCOMPtr <nsIMsgFolder> msgFolder = do_QueryInterface(folderRes);
+ const char *uri;
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ rv = msgFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db)); // force db to get created.
+ if (dbFolderInfo)
+ {
+ nsCString srchFolderUri;
+ nsCString searchTerms;
+ nsCString regexScope;
+ nsCString vfFolderFlag;
+ bool searchOnline = false;
+ dbFolderInfo->GetBooleanProperty("searchOnline", false, &searchOnline);
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, srchFolderUri);
+ dbFolderInfo->GetCharProperty("searchStr", searchTerms);
+ // logically searchFolderFlag is an int, but since we want to
+ // write out a string, get it as a string.
+ dbFolderInfo->GetCharProperty(SEARCH_FOLDER_FLAG, vfFolderFlag);
+ folderRes->GetValueConst(&uri);
+ if (!srchFolderUri.IsEmpty() && !searchTerms.IsEmpty())
+ {
+ WriteLineToOutputStream("uri=", uri, outStream);
+ if (!vfFolderFlag.IsEmpty())
+ WriteLineToOutputStream(SEARCH_FOLDER_FLAG"=", vfFolderFlag.get(), outStream);
+ WriteLineToOutputStream("scope=", srchFolderUri.get(), outStream);
+ WriteLineToOutputStream("terms=", searchTerms.get(), outStream);
+ WriteLineToOutputStream("searchOnline=", searchOnline ? "true" : "false", outStream);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream, &rv);
+ NS_ASSERTION(safeStream, "expected a safe output stream!");
+ if (safeStream) {
+ rv = safeStream->Finish();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to save personal dictionary file! possible data loss");
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgAccountManager::WriteLineToOutputStream(const char *prefix, const char * line, nsIOutputStream *outputStream)
+{
+ uint32_t writeCount;
+ outputStream->Write(prefix, strlen(prefix), &writeCount);
+ outputStream->Write(line, strlen(line), &writeCount);
+ outputStream->Write("\n", 1, &writeCount);
+ return NS_OK;
+}
+
+/**
+ * Parse the '|' separated folder uri string into individual folders, verify
+ * that the folders are real. If we were to add things like wildcards, we
+ * could implement the expansion into real folders here.
+ *
+ * @param buffer On input, list of folder uri's, on output, verified list.
+ * @param rdf rdf service
+ */
+void nsMsgAccountManager::ParseAndVerifyVirtualFolderScope(nsCString &buffer,
+ nsIRDFService *rdf)
+{
+ nsCString verifiedFolders;
+ nsTArray<nsCString> folderUris;
+ ParseString(buffer, '|', folderUris);
+ nsCOMPtr <nsIRDFResource> resource;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsIMsgFolder> parent;
+
+ for (uint32_t i = 0; i < folderUris.Length(); i++)
+ {
+ rdf->GetResource(folderUris[i], getter_AddRefs(resource));
+ nsCOMPtr <nsIMsgFolder> realFolder = do_QueryInterface(resource);
+ if (!realFolder)
+ continue;
+ realFolder->GetParent(getter_AddRefs(parent));
+ if (!parent)
+ continue;
+ realFolder->GetServer(getter_AddRefs(server));
+ if (!server)
+ continue;
+ if (!verifiedFolders.IsEmpty())
+ verifiedFolders.Append('|');
+ verifiedFolders.Append(folderUris[i]);
+ }
+ buffer.Assign(verifiedFolders);
+}
+
+// This conveniently works to add a single folder as well.
+nsresult nsMsgAccountManager::AddVFListenersForVF(nsIMsgFolder *virtualFolder,
+ const nsCString& srchFolderUris,
+ nsIRDFService *rdf,
+ nsIMsgDBService *msgDBService)
+{
+ nsTArray<nsCString> folderUris;
+ ParseString(srchFolderUris, '|', folderUris);
+ nsCOMPtr <nsIRDFResource> resource;
+
+ for (uint32_t i = 0; i < folderUris.Length(); i++)
+ {
+ rdf->GetResource(folderUris[i], getter_AddRefs(resource));
+ nsCOMPtr <nsIMsgFolder> realFolder = do_QueryInterface(resource);
+ if (!realFolder)
+ continue;
+ RefPtr<VirtualFolderChangeListener> dbListener = new VirtualFolderChangeListener();
+ NS_ENSURE_TRUE(dbListener, NS_ERROR_OUT_OF_MEMORY);
+ dbListener->m_virtualFolder = virtualFolder;
+ dbListener->m_folderWatching = realFolder;
+ if (NS_FAILED(dbListener->Init()))
+ {
+ dbListener = nullptr;
+ continue;
+ }
+ m_virtualFolderListeners.AppendElement(dbListener);
+ msgDBService->RegisterPendingListener(realFolder, dbListener);
+ }
+ return NS_OK;
+}
+
+// This is called if a folder that's part of the scope of a saved search
+// has gone away.
+nsresult nsMsgAccountManager::RemoveVFListenerForVF(nsIMsgFolder *virtualFolder,
+ nsIMsgFolder *folder)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBService> msgDBService(do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener> >::ForwardIterator iter(m_virtualFolderListeners);
+ RefPtr<VirtualFolderChangeListener> listener;
+
+ while (iter.HasMore())
+ {
+ listener = iter.GetNext();
+ if (listener->m_folderWatching == folder &&
+ listener->m_virtualFolder == virtualFolder)
+ {
+ msgDBService->UnregisterPendingListener(listener);
+ m_virtualFolderListeners.RemoveElement(listener);
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::GetAllFolders(nsIArray **aAllFolders)
+{
+ NS_ENSURE_ARG_POINTER(aAllFolders);
+
+ nsCOMPtr<nsIArray> servers;
+ nsresult rv = GetAllServers(getter_AddRefs(servers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numServers = 0;
+ rv = servers->GetLength(&numServers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> allFolders(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i;
+ for (i = 0; i < numServers; i++)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(servers, i);
+ if (server)
+ {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ rootFolder->ListDescendants(allFolders);
+ }
+ }
+
+ allFolders.forget(aAllFolders);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(item);
+ // just kick out with a success code if the item in question is not a folder
+ if (!folder)
+ return NS_OK;
+
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ bool addToSmartFolders = false;
+ folder->IsSpecialFolder(nsMsgFolderFlags::Inbox |
+ nsMsgFolderFlags::Templates |
+ nsMsgFolderFlags::Trash |
+ nsMsgFolderFlags::Drafts, false,
+ &addToSmartFolders);
+ // For Sent/Archives/Trash, we treat sub-folders of those folders as
+ // "special", and want to add them the smart folders search scope.
+ // So we check if this is a sub-folder of one of those special folders
+ // and set the corresponding folderFlag if so.
+ if (!addToSmartFolders)
+ {
+ bool isSpecial = false;
+ folder->IsSpecialFolder(nsMsgFolderFlags::SentMail, true, &isSpecial);
+ if (isSpecial)
+ {
+ addToSmartFolders = true;
+ folderFlags |= nsMsgFolderFlags::SentMail;
+ }
+ folder->IsSpecialFolder(nsMsgFolderFlags::Archive, true, &isSpecial);
+ if (isSpecial)
+ {
+ addToSmartFolders = true;
+ folderFlags |= nsMsgFolderFlags::Archive;
+ }
+ folder->IsSpecialFolder(nsMsgFolderFlags::Trash, true, &isSpecial);
+ if (isSpecial)
+ {
+ addToSmartFolders = true;
+ folderFlags |= nsMsgFolderFlags::Trash;
+ }
+ }
+ nsresult rv = NS_OK;
+ // if this is a special folder, check if we have a saved search over
+ // folders with this flag, and if so, add this folder to the scope.
+ if (addToSmartFolders)
+ {
+ // quick way to enumerate the saved searches.
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener> >::ForwardIterator iter(m_virtualFolderListeners);
+ RefPtr<VirtualFolderChangeListener> listener;
+
+ while (iter.HasMore())
+ {
+ listener = iter.GetNext();
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ listener->m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db));
+ if (dbFolderInfo)
+ {
+ uint32_t vfFolderFlag;
+ dbFolderInfo->GetUint32Property("searchFolderFlag", 0, & vfFolderFlag);
+ // found a saved search over folders w/ the same flag as the new folder.
+ if (vfFolderFlag & folderFlags)
+ {
+ nsCString searchURI;
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI);
+
+ // "normalize" searchURI so we can search for |folderURI|.
+ if (!searchURI.IsEmpty())
+ {
+ searchURI.Insert('|', 0);
+ searchURI.Append('|');
+ }
+ nsCString folderURI;
+ folder->GetURI(folderURI);
+
+ int32_t index = searchURI.Find(folderURI);
+ if (index == kNotFound)
+ {
+ searchURI.Cut(0, 1);
+ searchURI.Append(folderURI);
+ dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI);
+ break;
+ }
+ // New sent or archive folder, need to add sub-folders to smart folder.
+ if (vfFolderFlag & (nsMsgFolderFlags::Archive | nsMsgFolderFlags::SentMail))
+ {
+ nsCOMPtr<nsIArray> allDescendants;
+ rv = folder->GetDescendants(getter_AddRefs(allDescendants));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cnt = 0;
+ rv = allDescendants->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> parent;
+ for (uint32_t j = 0; j < cnt; j++)
+ {
+ nsCOMPtr<nsIMsgFolder> subFolder = do_QueryElementAt(allDescendants, j);
+ if (subFolder)
+ {
+ subFolder->GetParent(getter_AddRefs(parent));
+ OnItemAdded(parent, subFolder);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // need to make sure this isn't happening during loading of virtualfolders.dat
+ if (folderFlags & nsMsgFolderFlags::Virtual && !m_loadingVirtualFolders)
+ {
+ // When a new virtual folder is added, need to create a db Listener for it.
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ if (msgDBService)
+ {
+ nsCOMPtr <nsIMsgDatabase> virtDatabase;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString srchFolderUri;
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, srchFolderUri);
+ nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
+ AddVFListenersForVF(folder, srchFolderUri, rdf, msgDBService);
+ }
+ rv = SaveVirtualFolders();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(item);
+ // just kick out with a success code if the item in question is not a folder
+ if (!folder)
+ return NS_OK;
+ nsresult rv = NS_OK;
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Virtual) // if we removed a VF, flush VF list to disk.
+ {
+ rv = SaveVirtualFolders();
+ // clear flags on deleted folder if it's a virtual folder, so that creating a new folder
+ // with the same name doesn't cause confusion.
+ folder->SetFlags(0);
+ return rv;
+ }
+ // need to update the saved searches to check for a few things:
+ // 1. Folder removed was in the scope of a saved search - if so, remove the
+ // uri from the scope of the saved search.
+ // 2. If the scope is now empty, remove the saved search.
+
+ // build a "normalized" uri that we can do a find on.
+ nsCString removedFolderURI;
+ folder->GetURI(removedFolderURI);
+ removedFolderURI.Insert('|', 0);
+ removedFolderURI.Append('|');
+
+ // Enumerate the saved searches.
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener> >::ForwardIterator iter(m_virtualFolderListeners);
+ RefPtr<VirtualFolderChangeListener> listener;
+
+ while (iter.HasMore())
+ {
+ listener = iter.GetNext();
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr<nsIMsgFolder> savedSearch = listener->m_virtualFolder;
+ savedSearch->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db));
+ if (dbFolderInfo)
+ {
+ nsCString searchURI;
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI);
+ // "normalize" searchURI so we can search for |folderURI|.
+ searchURI.Insert('|', 0);
+ searchURI.Append('|');
+ int32_t index = searchURI.Find(removedFolderURI);
+ if (index != kNotFound)
+ {
+ RemoveVFListenerForVF(savedSearch, folder);
+
+ // remove |folderURI
+ searchURI.Cut(index, removedFolderURI.Length() - 1);
+ // remove last '|' we added
+ searchURI.SetLength(searchURI.Length() - 1);
+
+ // if saved search is empty now, delete it.
+ if (searchURI.IsEmpty())
+ {
+ db = nullptr;
+ dbFolderInfo = nullptr;
+
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = savedSearch->GetParent(getter_AddRefs(parent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!parent)
+ continue;
+ parent->PropagateDelete(savedSearch, true, nullptr);
+ }
+ else
+ {
+ // remove leading '|' we added (or one after |folderURI, if first URI)
+ searchURI.Cut(0, 1);
+ dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnItemPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char *oldValue, const char *newValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::OnItemIntPropertyChanged(nsIMsgFolder *aFolder,
+ nsIAtom *aProperty,
+ int64_t oldValue,
+ int64_t newValue)
+{
+ if (aProperty == mFolderFlagAtom)
+ {
+ uint32_t smartFlagsChanged = (oldValue ^ newValue) &
+ (nsMsgFolderFlags::SpecialUse & ~nsMsgFolderFlags::Queue);
+ if (smartFlagsChanged)
+ {
+ if (smartFlagsChanged & newValue)
+ {
+ // if the smart folder flag was set, calling OnItemAdded will
+ // do the right thing.
+ nsCOMPtr<nsIMsgFolder> parent;
+ aFolder->GetParent(getter_AddRefs(parent));
+ return OnItemAdded(parent, aFolder);
+ }
+ RemoveFolderFromSmartFolder(aFolder, smartFlagsChanged);
+ // sent|archive flag removed, remove sub-folders from smart folder.
+ if (smartFlagsChanged & (nsMsgFolderFlags::Archive | nsMsgFolderFlags::SentMail))
+ {
+ nsCOMPtr<nsIArray> allDescendants;
+ nsresult rv = aFolder->GetDescendants(getter_AddRefs(allDescendants));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cnt = 0;
+ rv = allDescendants->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> parent;
+ for (uint32_t j = 0; j < cnt; j++)
+ {
+ nsCOMPtr<nsIMsgFolder> subFolder = do_QueryElementAt(allDescendants, j);
+ if (subFolder)
+ RemoveFolderFromSmartFolder(subFolder, smartFlagsChanged);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManager::RemoveFolderFromSmartFolder(nsIMsgFolder *aFolder,
+ uint32_t flagsChanged)
+{
+ nsCString removedFolderURI;
+ aFolder->GetURI(removedFolderURI);
+ removedFolderURI.Insert('|', 0);
+ removedFolderURI.Append('|');
+ uint32_t flags;
+ aFolder->GetFlags(&flags);
+ NS_ASSERTION(!(flags & flagsChanged), "smart folder flag should not be set");
+ // Flag was removed. Look for smart folder based on that flag,
+ // and remove this folder from its scope.
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener> >::ForwardIterator iter(m_virtualFolderListeners);
+ RefPtr<VirtualFolderChangeListener> listener;
+
+ while (iter.HasMore())
+ {
+ listener = iter.GetNext();
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ listener->m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db));
+ if (dbFolderInfo)
+ {
+ uint32_t vfFolderFlag;
+ dbFolderInfo->GetUint32Property("searchFolderFlag", 0, & vfFolderFlag);
+ // found a smart folder over the removed flag
+ if (vfFolderFlag & flagsChanged)
+ {
+ nsCString searchURI;
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI);
+ // "normalize" searchURI so we can search for |folderURI|.
+ searchURI.Insert('|', 0);
+ searchURI.Append('|');
+ int32_t index = searchURI.Find(removedFolderURI);
+ if (index != kNotFound)
+ {
+ RemoveVFListenerForVF(listener->m_virtualFolder, aFolder);
+
+ // remove |folderURI
+ searchURI.Cut(index, removedFolderURI.Length() - 1);
+ // remove last '|' we added
+ searchURI.SetLength(searchURI.Length() - 1);
+
+ // remove leading '|' we added (or one after |folderURI, if first URI)
+ searchURI.Cut(0, 1);
+ dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnItemBoolPropertyChanged(nsIMsgFolder *item, nsIAtom *property, bool oldValue, bool newValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnItemUnicharPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char16_t *oldValue, const char16_t *newValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP nsMsgAccountManager::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnItemEvent(nsIMsgFolder *aFolder, nsIAtom *aEvent)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::FolderUriForPath(nsIFile *aLocalPath,
+ nsACString &aMailboxUri)
+{
+ NS_ENSURE_ARG_POINTER(aLocalPath);
+ bool equals;
+ if (m_lastPathLookedUp &&
+ NS_SUCCEEDED(aLocalPath->Equals(m_lastPathLookedUp, &equals)) && equals)
+ {
+ aMailboxUri = m_lastFolderURIForPath;
+ return NS_OK;
+ }
+ nsCOMPtr<nsIArray> folderArray;
+ nsresult rv = GetAllFolders(getter_AddRefs(folderArray));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ rv = folderArray->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryElementAt(folderArray, i, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> folderPath;
+ rv = folder->GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if we're equal
+ rv = folderPath->Equals(aLocalPath, &equals);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (equals)
+ {
+ rv = folder->GetURI(aMailboxUri);
+ m_lastFolderURIForPath = aMailboxUri;
+ aLocalPath->Clone(getter_AddRefs(m_lastPathLookedUp));
+ return rv;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetSortOrder(nsIMsgIncomingServer* aServer, int32_t* aSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+
+ // If the passed in server is the default, return its sort order as 0 regardless
+ // of its server sort order.
+
+ nsCOMPtr<nsIMsgAccount> defaultAccount;
+ nsresult rv = GetDefaultAccount(getter_AddRefs(defaultAccount));
+ if (NS_SUCCEEDED(rv) && defaultAccount) {
+ nsCOMPtr<nsIMsgIncomingServer> defaultServer;
+ rv = m_defaultAccount->GetIncomingServer(getter_AddRefs(defaultServer));
+ if (NS_SUCCEEDED(rv) && (aServer == defaultServer)) {
+ *aSortOrder = 0;
+ return NS_OK;
+ }
+ // It is OK if there is no default account.
+ }
+
+ // This function returns the sort order by querying the server object for its
+ // sort order value and then incrementing it by the position of the server in
+ // the accounts list. This ensures that even when several accounts have the
+ // same sort order value, the returned value is not the same and keeps
+ // their relative order in the account list when and unstable sort is run
+ // on the returned sort order values.
+ int32_t sortOrder;
+ int32_t serverIndex;
+
+ rv = aServer->GetSortOrder(&sortOrder);
+ if (NS_SUCCEEDED(rv))
+ rv = FindServerIndex(aServer, &serverIndex);
+
+ if (NS_FAILED(rv)) {
+ *aSortOrder = 999999999;
+ } else {
+ *aSortOrder = sortOrder + serverIndex;
+ }
+
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMsgAccountManager.h b/mailnews/base/src/nsMsgAccountManager.h
new file mode 100644
index 000000000..11d19590b
--- /dev/null
+++ b/mailnews/base/src/nsMsgAccountManager.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+#include "nscore.h"
+#include "nsIMsgAccountManager.h"
+#include "nsCOMPtr.h"
+#include "nsISmtpServer.h"
+#include "nsIPrefBranch.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgFolder.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsIUrlListener.h"
+#include "nsCOMArray.h"
+#include "nsIMsgSearchSession.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIMsgDatabase.h"
+#include "nsIDBChangeListener.h"
+#include "nsAutoPtr.h"
+#include "nsTObserverArray.h"
+
+class nsIRDFService;
+
+class VirtualFolderChangeListener final : public nsIDBChangeListener
+{
+public:
+ VirtualFolderChangeListener();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDBCHANGELISTENER
+
+ nsresult Init();
+ /**
+ * Posts an event to update the summary totals and commit the db.
+ * We post the event to avoid committing each time we're called
+ * in a synchronous loop.
+ */
+ nsresult PostUpdateEvent(nsIMsgFolder *folder, nsIMsgDatabase *db);
+ /// Handles event posted to event queue to batch notifications.
+ void ProcessUpdateEvent(nsIMsgFolder *folder, nsIMsgDatabase *db);
+
+ void DecrementNewMsgCount();
+
+ nsCOMPtr <nsIMsgFolder> m_virtualFolder; // folder we're listening to db changes on behalf of.
+ nsCOMPtr <nsIMsgFolder> m_folderWatching; // folder whose db we're listening to.
+ nsCOMPtr <nsIMsgSearchSession> m_searchSession;
+ bool m_searchOnMsgStatus;
+ bool m_batchingEvents;
+
+private:
+ ~VirtualFolderChangeListener() {}
+};
+
+
+class nsMsgAccountManager: public nsIMsgAccountManager,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsIUrlListener,
+ public nsIFolderListener
+{
+public:
+
+ nsMsgAccountManager();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /* nsIMsgAccountManager methods */
+
+ NS_DECL_NSIMSGACCOUNTMANAGER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIFOLDERLISTENER
+
+ nsresult Init();
+ nsresult Shutdown();
+ void LogoutOfServer(nsIMsgIncomingServer *aServer);
+
+private:
+ virtual ~nsMsgAccountManager();
+
+ bool m_accountsLoaded;
+ nsCOMPtr <nsIMsgFolderCache> m_msgFolderCache;
+ nsCOMPtr<nsIAtom> kDefaultServerAtom;
+ nsCOMPtr<nsIAtom> mFolderFlagAtom;
+ nsTArray<nsCOMPtr<nsIMsgAccount> > m_accounts;
+ nsInterfaceHashtable<nsCStringHashKey, nsIMsgIdentity> m_identities;
+ nsInterfaceHashtable<nsCStringHashKey, nsIMsgIncomingServer> m_incomingServers;
+ nsCOMPtr<nsIMsgAccount> m_defaultAccount;
+ nsCOMArray<nsIIncomingServerListener> m_incomingServerListeners;
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener> > m_virtualFolderListeners;
+ nsCOMPtr<nsIMsgFolder> m_folderDoingEmptyTrash;
+ nsCOMPtr<nsIMsgFolder> m_folderDoingCleanupInbox;
+ bool m_emptyTrashInProgress;
+ bool m_cleanupInboxInProgress;
+
+ nsCString mAccountKeyList;
+
+ // These are static because the account manager may go away during
+ // shutdown, and get recreated.
+ static bool m_haveShutdown;
+ static bool m_shutdownInProgress;
+
+ bool m_userAuthenticated;
+ bool m_loadingVirtualFolders;
+ bool m_virtualFoldersLoaded;
+
+ /* we call FindServer() a lot. so cache the last server found */
+ nsCOMPtr <nsIMsgIncomingServer> m_lastFindServerResult;
+ nsCString m_lastFindServerHostName;
+ nsCString m_lastFindServerUserName;
+ int32_t m_lastFindServerPort;
+ nsCString m_lastFindServerType;
+
+ void SetLastServerFound(nsIMsgIncomingServer *server, const nsACString& hostname,
+ const nsACString& username, const int32_t port, const nsACString& type);
+
+ // Cache the results of the last call to FolderUriFromDirInProfile
+ nsCOMPtr<nsIFile> m_lastPathLookedUp;
+ nsCString m_lastFolderURIForPath;
+
+ /* internal creation routines - updates m_identities and m_incomingServers */
+ nsresult createKeyedAccount(const nsCString& key,
+ nsIMsgAccount **_retval);
+ nsresult createKeyedServer(const nsACString& key,
+ const nsACString& username,
+ const nsACString& password,
+ const nsACString& type,
+ nsIMsgIncomingServer **_retval);
+
+ nsresult createKeyedIdentity(const nsACString& key,
+ nsIMsgIdentity **_retval);
+
+ nsresult GetLocalFoldersPrettyName(nsString &localFoldersName);
+
+ /**
+ * Check if the given account can be the set as the default account.
+ */
+ nsresult CheckDefaultAccount(nsIMsgAccount* aAccount, bool &aCanBeDefault);
+
+ /**
+ * Find a new account that can serve as default.
+ */
+ nsresult AutosetDefaultAccount();
+
+ // sets the pref for the default server
+ nsresult setDefaultAccountPref(nsIMsgAccount *aDefaultAccount);
+
+ // Write out the accounts pref from the m_accounts list of accounts.
+ nsresult OutputAccountsPref();
+
+ // fires notifications to the appropriate root folders
+ nsresult notifyDefaultServerChange(nsIMsgAccount *aOldAccount,
+ nsIMsgAccount *aNewAccount);
+
+ //
+ // account enumerators
+ // ("element" is always an account)
+ //
+
+ // find the servers that correspond to the given identity
+ static bool findServersForIdentity (nsISupports *element, void *aData);
+
+ void findAccountByServerKey(const nsCString &aKey,
+ nsIMsgAccount **aResult);
+
+ //
+ // server enumerators
+ // ("element" is always a server)
+ //
+
+ nsresult findServerInternal(const nsACString& username,
+ const nsACString& hostname,
+ const nsACString& type,
+ int32_t port,
+ bool aRealFlag,
+ nsIMsgIncomingServer** aResult);
+
+ // handle virtual folders
+ static nsresult GetVirtualFoldersFile(nsCOMPtr<nsIFile>& file);
+ static nsresult WriteLineToOutputStream(const char *prefix, const char * line, nsIOutputStream *outputStream);
+ void ParseAndVerifyVirtualFolderScope(nsCString &buffer,
+ nsIRDFService *rdf);
+ nsresult AddVFListenersForVF(nsIMsgFolder *virtualFolder,
+ const nsCString& srchFolderUris,
+ nsIRDFService *rdf,
+ nsIMsgDBService *msgDBService);
+
+ nsresult RemoveVFListenerForVF(nsIMsgFolder *virtualFolder,
+ nsIMsgFolder *folder);
+
+ void getUniqueAccountKey(nsCString& aResult);
+
+ // Scan the preferences to find a unique server key
+ void GetUniqueServerKey(nsACString& aResult);
+
+ nsresult RemoveFolderFromSmartFolder(nsIMsgFolder *aFolder,
+ uint32_t flagsChanged);
+
+ nsresult SetSendLaterUriPref(nsIMsgIncomingServer *server);
+
+ nsCOMPtr<nsIPrefBranch> m_prefs;
+
+ //
+ // root folder listener stuff
+ //
+
+ // this array is for folder listeners that are supposed to be listening
+ // on the root folders.
+ // When a new server is created, all of the the folder listeners
+ // should be added to the new server
+ // When a new listener is added, it should be added to all root folders.
+ // similar for when servers are deleted or listeners removed
+ nsTObserverArray<nsCOMPtr<nsIFolderListener> > mFolderListeners;
+
+ void removeListenersFromFolder(nsIMsgFolder *aFolder);
+};
diff --git a/mailnews/base/src/nsMsgAccountManagerDS.cpp b/mailnews/base/src/nsMsgAccountManagerDS.cpp
new file mode 100644
index 000000000..728aaabce
--- /dev/null
+++ b/mailnews/base/src/nsMsgAccountManagerDS.cpp
@@ -0,0 +1,1183 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * RDF datasource for the account manager
+ */
+
+#include "nsMsgAccountManagerDS.h"
+#include "rdf.h"
+#include "nsRDFCID.h"
+#include "nsIRDFDataSource.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIServiceManager.h"
+#include "nsMsgRDFUtils.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgBaseCID.h"
+
+#include "nsICategoryManager.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsArrayEnumerator.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+#include "nsArrayUtils.h"
+
+// turn this on to see useful output
+#undef DEBUG_amds
+
+#define NC_RDF_PAGETITLE_PREFIX NC_NAMESPACE_URI "PageTitle"
+#define NC_RDF_PAGETITLE_MAIN NC_RDF_PAGETITLE_PREFIX "Main"
+#define NC_RDF_PAGETITLE_SERVER NC_RDF_PAGETITLE_PREFIX "Server"
+#define NC_RDF_PAGETITLE_COPIES NC_RDF_PAGETITLE_PREFIX "Copies"
+#define NC_RDF_PAGETITLE_SYNCHRONIZATION NC_RDF_PAGETITLE_PREFIX "Synchronization"
+#define NC_RDF_PAGETITLE_DISKSPACE NC_RDF_PAGETITLE_PREFIX "DiskSpace"
+#define NC_RDF_PAGETITLE_ADDRESSING NC_RDF_PAGETITLE_PREFIX "Addressing"
+#define NC_RDF_PAGETITLE_SMTP NC_RDF_PAGETITLE_PREFIX "SMTP"
+#define NC_RDF_PAGETITLE_JUNK NC_RDF_PAGETITLE_PREFIX "Junk"
+#define NC_RDF_PAGETAG NC_NAMESPACE_URI "PageTag"
+
+
+#define NC_RDF_ACCOUNTROOT "msgaccounts:/"
+
+// the root resource (msgaccounts:/)
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_AccountRoot=nullptr;
+
+// attributes of accounts
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Name=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_FolderTreeName=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_FolderTreeSimpleName=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_NameSort=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_FolderTreeNameSort=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTag=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_IsDefaultServer=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_SupportsFilters=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_CanGetMessages=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_CanGetIncomingMessages=nullptr;
+
+// containment
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Child=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Settings=nullptr;
+
+
+// properties corresponding to interfaces
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Account=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Server=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Identity=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Junk=nullptr;
+
+// individual pages
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleMain=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleServer=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleCopies=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleSynchronization=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleDiskSpace=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleAddressing=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleSMTP=nullptr;
+nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleJunk=nullptr;
+
+// common literals
+nsIRDFLiteral* nsMsgAccountManagerDataSource::kTrueLiteral = nullptr;
+
+nsIAtom* nsMsgAccountManagerDataSource::kDefaultServerAtom = nullptr;
+
+nsrefcnt nsMsgAccountManagerDataSource::gAccountManagerResourceRefCnt = 0;
+
+// shared arc lists
+nsCOMPtr<nsIMutableArray> nsMsgAccountManagerDataSource::mAccountArcsOut;
+nsCOMPtr<nsIMutableArray> nsMsgAccountManagerDataSource::mAccountRootArcsOut;
+
+
+// RDF to match
+#define NC_RDF_ACCOUNT NC_NAMESPACE_URI "Account"
+#define NC_RDF_SERVER NC_NAMESPACE_URI "Server"
+#define NC_RDF_IDENTITY NC_NAMESPACE_URI "Identity"
+#define NC_RDF_SETTINGS NC_NAMESPACE_URI "Settings"
+#define NC_RDF_JUNK NC_NAMESPACE_URI "Junk"
+#define NC_RDF_ISDEFAULTSERVER NC_NAMESPACE_URI "IsDefaultServer"
+#define NC_RDF_SUPPORTSFILTERS NC_NAMESPACE_URI "SupportsFilters"
+#define NC_RDF_CANGETMESSAGES NC_NAMESPACE_URI "CanGetMessages"
+#define NC_RDF_CANGETINCOMINGMESSAGES NC_NAMESPACE_URI "CanGetIncomingMessages"
+
+nsMsgAccountManagerDataSource::nsMsgAccountManagerDataSource()
+{
+#ifdef DEBUG_amds
+ printf("nsMsgAccountManagerDataSource() being created\n");
+#endif
+
+ // do per-class initialization here
+ if (gAccountManagerResourceRefCnt++ == 0) {
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_CHILD), &kNC_Child);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_NAME), &kNC_Name);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREENAME),
+ &kNC_FolderTreeName);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREESIMPLENAME),
+ &kNC_FolderTreeSimpleName);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_NAME_SORT), &kNC_NameSort);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREENAME_SORT),
+ &kNC_FolderTreeNameSort);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETAG), &kNC_PageTag);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_ISDEFAULTSERVER),
+ &kNC_IsDefaultServer);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_SUPPORTSFILTERS),
+ &kNC_SupportsFilters);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANGETMESSAGES),
+ &kNC_CanGetMessages);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANGETINCOMINGMESSAGES),
+ &kNC_CanGetIncomingMessages);
+
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_ACCOUNT), &kNC_Account);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_SERVER), &kNC_Server);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_IDENTITY), &kNC_Identity);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_JUNK), &kNC_Junk);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_MAIN),
+ &kNC_PageTitleMain);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_SERVER),
+ &kNC_PageTitleServer);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_COPIES),
+ &kNC_PageTitleCopies);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_SYNCHRONIZATION),
+ &kNC_PageTitleSynchronization);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_DISKSPACE),
+ &kNC_PageTitleDiskSpace);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_ADDRESSING),
+ &kNC_PageTitleAddressing);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_SMTP),
+ &kNC_PageTitleSMTP);
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_JUNK),
+ &kNC_PageTitleJunk);
+
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_ACCOUNTROOT),
+ &kNC_AccountRoot);
+
+ getRDFService()->GetLiteral(u"true",
+ &kTrueLiteral);
+
+ // eventually these need to exist in some kind of array
+ // that's easily extensible
+ getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_SETTINGS), &kNC_Settings);
+
+ kDefaultServerAtom = MsgNewAtom("DefaultServer").take();
+ }
+}
+
+nsMsgAccountManagerDataSource::~nsMsgAccountManagerDataSource()
+{
+ nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager);
+ if (am)
+ am->RemoveIncomingServerListener(this);
+
+ if (--gAccountManagerResourceRefCnt == 0) {
+ NS_IF_RELEASE(kNC_Child);
+ NS_IF_RELEASE(kNC_Name);
+ NS_IF_RELEASE(kNC_FolderTreeName);
+ NS_IF_RELEASE(kNC_FolderTreeSimpleName);
+ NS_IF_RELEASE(kNC_NameSort);
+ NS_IF_RELEASE(kNC_FolderTreeNameSort);
+ NS_IF_RELEASE(kNC_PageTag);
+ NS_IF_RELEASE(kNC_IsDefaultServer);
+ NS_IF_RELEASE(kNC_SupportsFilters);
+ NS_IF_RELEASE(kNC_CanGetMessages);
+ NS_IF_RELEASE(kNC_CanGetIncomingMessages);
+ NS_IF_RELEASE(kNC_Account);
+ NS_IF_RELEASE(kNC_Server);
+ NS_IF_RELEASE(kNC_Identity);
+ NS_IF_RELEASE(kNC_Junk);
+ NS_IF_RELEASE(kNC_PageTitleMain);
+ NS_IF_RELEASE(kNC_PageTitleServer);
+ NS_IF_RELEASE(kNC_PageTitleCopies);
+ NS_IF_RELEASE(kNC_PageTitleSynchronization);
+ NS_IF_RELEASE(kNC_PageTitleDiskSpace);
+ NS_IF_RELEASE(kNC_PageTitleAddressing);
+ NS_IF_RELEASE(kNC_PageTitleSMTP);
+ NS_IF_RELEASE(kNC_PageTitleJunk);
+ NS_IF_RELEASE(kTrueLiteral);
+
+ NS_IF_RELEASE(kNC_AccountRoot);
+
+ // eventually these need to exist in some kind of array
+ // that's easily extensible
+ NS_IF_RELEASE(kNC_Settings);
+
+
+ NS_IF_RELEASE(kDefaultServerAtom);
+ mAccountArcsOut = nullptr;
+ mAccountRootArcsOut = nullptr;
+ }
+
+}
+
+NS_IMPL_ADDREF_INHERITED(nsMsgAccountManagerDataSource, nsMsgRDFDataSource)
+NS_IMPL_RELEASE_INHERITED(nsMsgAccountManagerDataSource, nsMsgRDFDataSource)
+NS_INTERFACE_MAP_BEGIN(nsMsgAccountManagerDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIIncomingServerListener)
+ NS_INTERFACE_MAP_ENTRY(nsIFolderListener)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgRDFDataSource)
+
+nsresult
+nsMsgAccountManagerDataSource::Init()
+{
+ nsresult rv;
+
+ rv = nsMsgRDFDataSource::Init();
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgAccountManager> am;
+
+ // get a weak ref to the account manager
+ if (!mAccountManager)
+ {
+ am = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ mAccountManager = do_GetWeakReference(am);
+ }
+ else
+ am = do_QueryReferent(mAccountManager);
+
+ if (am)
+ {
+ am->AddIncomingServerListener(this);
+ am->AddRootFolderListener(this);
+ }
+
+
+ return NS_OK;
+}
+
+void nsMsgAccountManagerDataSource::Cleanup()
+{
+ nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager);
+
+ if (am)
+ {
+ am->RemoveIncomingServerListener(this);
+ am->RemoveRootFolderListener(this);
+ }
+
+ nsMsgRDFDataSource::Cleanup();
+}
+
+/* nsIRDFNode GetTarget (in nsIRDFResource aSource, in nsIRDFResource property, in boolean aTruthValue); */
+NS_IMETHODIMP
+nsMsgAccountManagerDataSource::GetTarget(nsIRDFResource *source,
+ nsIRDFResource *property,
+ bool aTruthValue,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+
+
+ rv = NS_RDF_NO_VALUE;
+
+ nsAutoString str;
+ if (property == kNC_Name || property == kNC_FolderTreeName ||
+ property == kNC_FolderTreeSimpleName)
+ {
+ rv = getStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString pageTitle;
+ if (source == kNC_PageTitleServer)
+ mStringBundle->GetStringFromName(u"prefPanel-server",
+ getter_Copies(pageTitle));
+
+ else if (source == kNC_PageTitleCopies)
+ mStringBundle->GetStringFromName(u"prefPanel-copies",
+ getter_Copies(pageTitle));
+ else if (source == kNC_PageTitleSynchronization)
+ mStringBundle->GetStringFromName(u"prefPanel-synchronization",
+ getter_Copies(pageTitle));
+ else if (source == kNC_PageTitleDiskSpace)
+ mStringBundle->GetStringFromName(u"prefPanel-diskspace",
+ getter_Copies(pageTitle));
+ else if (source == kNC_PageTitleAddressing)
+ mStringBundle->GetStringFromName(u"prefPanel-addressing",
+ getter_Copies(pageTitle));
+ else if (source == kNC_PageTitleSMTP)
+ mStringBundle->GetStringFromName(u"prefPanel-smtp",
+ getter_Copies(pageTitle));
+ else if (source == kNC_PageTitleJunk)
+ mStringBundle->GetStringFromName(u"prefPanel-junk",
+ getter_Copies(pageTitle));
+
+ else {
+ // if it's a server, use the pretty name
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(source, &rv);
+ if (NS_SUCCEEDED(rv) && folder) {
+ bool isServer;
+ rv = folder->GetIsServer(&isServer);
+ if (NS_SUCCEEDED(rv) && isServer)
+ rv = folder->GetPrettyName(pageTitle);
+ }
+ else {
+ // allow for the accountmanager to be dynamically extended.
+
+ nsCOMPtr<nsIStringBundleService> strBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(strBundleService, NS_ERROR_UNEXPECTED);
+
+ const char *sourceValue;
+ rv = source->GetValueConst(&sourceValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // make sure the pointer math we're about to do is safe.
+ NS_ENSURE_TRUE(sourceValue && (strlen(sourceValue) > strlen(NC_RDF_PAGETITLE_PREFIX)), NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // turn NC#PageTitlefoobar into foobar, so we can get the am-foobar.properties bundle
+ nsCString chromePackageName;
+ rv = am->GetChromePackageName(nsCString(sourceValue + strlen(NC_RDF_PAGETITLE_PREFIX)), chromePackageName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString bundleURL;
+ bundleURL = "chrome://";
+ bundleURL += chromePackageName;
+ bundleURL += "/locale/am-";
+ bundleURL += (sourceValue + strlen(NC_RDF_PAGETITLE_PREFIX));
+ bundleURL += ".properties";
+
+ nsCOMPtr <nsIStringBundle> bundle;
+ rv = strBundleService->CreateBundle(bundleURL.get(), getter_AddRefs(bundle));
+
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoString panelTitleName;
+ panelTitleName.AssignLiteral("prefPanel-");
+ panelTitleName.Append(NS_ConvertASCIItoUTF16(sourceValue + strlen(NC_RDF_PAGETITLE_PREFIX)));
+ bundle->GetStringFromName(panelTitleName.get(), getter_Copies(pageTitle));
+ }
+ }
+ str = pageTitle.get();
+ }
+ else if (property == kNC_PageTag) {
+ // do NOT localize these strings. these are the urls of the XUL files
+ if (source == kNC_PageTitleServer)
+ str.AssignLiteral("am-server.xul");
+ else if (source == kNC_PageTitleCopies)
+ str.AssignLiteral("am-copies.xul");
+ else if ((source == kNC_PageTitleSynchronization) ||
+ (source == kNC_PageTitleDiskSpace))
+ str.AssignLiteral("am-offline.xul");
+ else if (source == kNC_PageTitleAddressing)
+ str.AssignLiteral("am-addressing.xul");
+ else if (source == kNC_PageTitleSMTP)
+ str.AssignLiteral("am-smtp.xul");
+ else if (source == kNC_PageTitleJunk)
+ str.AssignLiteral("am-junk.xul");
+ else {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(source, &rv);
+ if (NS_SUCCEEDED(rv) && folder) {
+ /* if this is a server, with no identities, then we show a special panel */
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = getServerForFolderNode(source, getter_AddRefs(server));
+ if (server)
+ server->GetAccountManagerChrome(str);
+ else
+ str.AssignLiteral("am-main.xul");
+ }
+ else {
+ // allow for the accountmanager to be dynamically extended
+ const char *sourceValue;
+ rv = source->GetValueConst(&sourceValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // make sure the pointer math we're about to do is safe.
+ NS_ENSURE_TRUE(sourceValue && (strlen(sourceValue) > strlen(NC_RDF_PAGETITLE_PREFIX)), NS_ERROR_UNEXPECTED);
+
+ // turn NC#PageTitlefoobar into foobar, so we can get the am-foobar.xul file
+ str.AssignLiteral("am-");
+ str.Append(NS_ConvertASCIItoUTF16(sourceValue + strlen(NC_RDF_PAGETITLE_PREFIX)));
+ str.AppendLiteral(".xul");
+ }
+ }
+ }
+
+ // handle sorting of servers
+ else if ((property == kNC_NameSort) ||
+ (property == kNC_FolderTreeNameSort)) {
+
+ // order for the folder pane
+ // and for the account manager tree is:
+ //
+ // - default mail account
+ // - <other mail accounts>
+ // - "Local Folders" account
+ // - news accounts
+ // - smtp settings (note, this is only in account manager tree)
+
+ // make sure we're handling a root folder that is a server
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = getServerForFolderNode(source, getter_AddRefs(server));
+
+ if (NS_SUCCEEDED(rv) && server) {
+ int32_t accountNum;
+ nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager);
+
+ if (isDefaultServer(server))
+ str.AssignLiteral("000000000");
+ else {
+ rv = am->GetSortOrder(server, &accountNum);
+ NS_ENSURE_SUCCESS(rv, rv);
+ str.AppendInt(accountNum);
+ }
+ }
+ else {
+ const char *sourceValue;
+ rv = source->GetValueConst(&sourceValue);
+ NS_ENSURE_SUCCESS(rv, NS_RDF_NO_VALUE);
+
+ // if this is a page (which we determine by the prefix of the URI)
+ // we want to generate a sort value
+ // so that we can sort the categories in the account manager tree
+ // (or the folder pane)
+ //
+ // otherwise, return NS_RDF_NO_VALUE
+ // so that the folder data source will take care of it.
+ if (sourceValue && (strncmp(sourceValue, NC_RDF_PAGETITLE_PREFIX, strlen(NC_RDF_PAGETITLE_PREFIX)) == 0)) {
+ if (source == kNC_PageTitleSMTP)
+ str.AssignLiteral("900000000");
+ else if (source == kNC_PageTitleServer)
+ str.AssignLiteral("1");
+ else if (source == kNC_PageTitleCopies)
+ str.AssignLiteral("2");
+ else if (source == kNC_PageTitleAddressing)
+ str.AssignLiteral("3");
+ else if (source == kNC_PageTitleSynchronization)
+ str.AssignLiteral("4");
+ else if (source == kNC_PageTitleDiskSpace)
+ str.AssignLiteral("4");
+ else if (source == kNC_PageTitleJunk)
+ str.AssignLiteral("5");
+ else {
+ // allow for the accountmanager to be dynamically extended
+ // all the other pages come after the standard ones
+ // server, copies, addressing, disk space (or offline & disk space)
+ CopyASCIItoUTF16(nsDependentCString(sourceValue), str);
+ }
+ }
+ else {
+ return NS_RDF_NO_VALUE;
+ }
+ }
+ }
+
+ // GetTargets() stuff - need to return a valid answer so that
+ // twisties will appear
+ else if (property == kNC_Settings) {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(source,&rv);
+ if (NS_FAILED(rv))
+ return NS_RDF_NO_VALUE;
+
+ bool isServer=false;
+ folder->GetIsServer(&isServer);
+ // no need to localize this!
+ if (isServer)
+ str.AssignLiteral("ServerSettings");
+ }
+
+ else if (property == kNC_IsDefaultServer) {
+ nsCOMPtr<nsIMsgIncomingServer> thisServer;
+ rv = getServerForFolderNode(source, getter_AddRefs(thisServer));
+ if (NS_FAILED(rv) || !thisServer) return NS_RDF_NO_VALUE;
+
+ if (isDefaultServer(thisServer))
+ str.AssignLiteral("true");
+ }
+
+ else if (property == kNC_SupportsFilters) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = getServerForFolderNode(source, getter_AddRefs(server));
+ if (NS_FAILED(rv) || !server) return NS_RDF_NO_VALUE;
+
+ if (supportsFilters(server))
+ str.AssignLiteral("true");
+ }
+ else if (property == kNC_CanGetMessages) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = getServerForFolderNode(source, getter_AddRefs(server));
+ if (NS_FAILED(rv) || !server) return NS_RDF_NO_VALUE;
+
+ if (canGetMessages(server))
+ str.AssignLiteral("true");
+ }
+ else if (property == kNC_CanGetIncomingMessages) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = getServerForFolderNode(source, getter_AddRefs(server));
+ if (NS_FAILED(rv) || !server) return NS_RDF_NO_VALUE;
+
+ if (canGetIncomingMessages(server))
+ str.AssignLiteral("true");
+ }
+
+ if (!str.IsEmpty())
+ rv = createNode(str.get(), target, getRDFService());
+
+ //if we have an empty string and we don't have an error value, then
+ //we don't have a value for RDF.
+ else if(NS_SUCCEEDED(rv))
+ rv = NS_RDF_NO_VALUE;
+
+ return rv;
+}
+
+
+
+/* nsISimpleEnumerator GetTargets (in nsIRDFResource aSource, in nsIRDFResource property, in boolean aTruthValue); */
+NS_IMETHODIMP
+nsMsgAccountManagerDataSource::GetTargets(nsIRDFResource *source,
+ nsIRDFResource *property,
+ bool aTruthValue,
+ nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_RDF_NO_VALUE;
+
+ // create array and enumerator
+ // even if we're not handling this we need to return something empty?
+ nsCOMArray<nsIRDFResource> nodes;
+ if (NS_FAILED(rv)) return rv;
+ if (source == kNC_AccountRoot)
+ rv = createRootResources(property, &nodes);
+ else if (property == kNC_Settings)
+ rv = createSettingsResources(source, &nodes);
+
+ if (NS_FAILED(rv))
+ return NS_RDF_NO_VALUE;
+ return NS_NewArrayEnumerator(_retval, nodes);
+}
+
+// end of all arcs coming out of msgaccounts:/
+nsresult
+nsMsgAccountManagerDataSource::createRootResources(nsIRDFResource *property,
+ nsCOMArray<nsIRDFResource> *aNodeArray)
+{
+ nsresult rv = NS_OK;
+ if (isContainment(property)) {
+
+ nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager);
+ if (!am) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIArray> servers;
+ rv = am->GetAllServers(getter_AddRefs(servers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t length;
+ rv = servers->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server(do_QueryElementAt(servers, i, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIMsgFolder> serverFolder;
+ rv = server->GetRootFolder(getter_AddRefs(serverFolder));
+ if (NS_FAILED(rv))
+ continue;
+
+ // add the resource to the array
+ nsCOMPtr<nsIRDFResource> serverResource = do_QueryInterface(serverFolder);
+ if (serverResource)
+ (void) aNodeArray->AppendObject(serverResource);
+ }
+
+#ifdef DEBUG_amds
+ uint32_t nodecount;
+ aNodeArray->GetLength(&nodecount);
+ printf("GetTargets(): added %d servers on %s\n", nodecount,
+ (const char*)property_arc);
+#endif
+ // For the "settings" arc, we also want to add SMTP setting.
+ if (property == kNC_Settings) {
+ aNodeArray->AppendObject(kNC_PageTitleSMTP);
+ }
+ }
+
+#ifdef DEBUG_amds
+ else {
+ printf("unknown arc %s on msgaccounts:/\n", (const char*)property_arc);
+ }
+#endif
+
+ return rv;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::appendGenericSettingsResources(nsIMsgIncomingServer *server, nsCOMArray<nsIRDFResource> *aNodeArray)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsISimpleEnumerator> e;
+ rv = catman->EnumerateCategory(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, getter_AddRefs(e));
+ if(NS_SUCCEEDED(rv) && e) {
+ while (true) {
+ nsCOMPtr<nsISupports> supports;
+ rv = e->GetNext(getter_AddRefs(supports));
+ nsCOMPtr<nsISupportsCString> catEntry = do_QueryInterface(supports);
+ if (NS_FAILED(rv) || !catEntry)
+ break;
+
+ nsAutoCString entryString;
+ rv = catEntry->GetData(entryString);
+ if (NS_FAILED(rv))
+ break;
+
+ nsCString contractidString;
+ rv = catman->GetCategoryEntry(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, entryString.get(), getter_Copies(contractidString));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr <nsIMsgAccountManagerExtension> extension =
+ do_GetService(contractidString.get(), &rv);
+ if (NS_FAILED(rv) || !extension)
+ break;
+
+ bool showPanel;
+ rv = extension->ShowPanel(server, &showPanel);
+ if (NS_FAILED(rv))
+ break;
+
+ if (showPanel) {
+ nsCString name;
+ rv = extension->GetName(name);
+ if (NS_FAILED(rv))
+ break;
+
+ rv = appendGenericSetting(name.get(), aNodeArray);
+ if (NS_FAILED(rv))
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::appendGenericSetting(const char *name,
+ nsCOMArray<nsIRDFResource> *aNodeArray)
+{
+ NS_ENSURE_ARG_POINTER(name);
+ NS_ENSURE_ARG_POINTER(aNodeArray);
+
+ nsCOMPtr <nsIRDFResource> resource;
+
+ nsAutoCString resourceStr;
+ resourceStr = NC_RDF_PAGETITLE_PREFIX;
+ resourceStr += name;
+
+ nsresult rv = getRDFService()->GetResource(resourceStr, getter_AddRefs(resource));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // AppendElement will addref.
+ aNodeArray->AppendObject(resource);
+ return NS_OK;
+}
+
+// end of all #Settings arcs
+nsresult
+nsMsgAccountManagerDataSource::createSettingsResources(nsIRDFResource *aSource,
+ nsCOMArray<nsIRDFResource> *aNodeArray)
+{
+ // If this isn't a server, just return.
+ if (aSource == kNC_PageTitleSMTP)
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ getServerForFolderNode(aSource, getter_AddRefs(server));
+ if (server) {
+ bool hasIdentities;
+ nsresult rv = serverHasIdentities(server, &hasIdentities);
+
+ if (hasIdentities) {
+ aNodeArray->AppendObject(kNC_PageTitleServer);
+ aNodeArray->AppendObject(kNC_PageTitleCopies);
+ aNodeArray->AppendObject(kNC_PageTitleAddressing);
+ }
+
+ // Junk settings apply for all server types except for news and rss.
+ nsCString serverType;
+ server->GetType(serverType);
+ if (!MsgLowerCaseEqualsLiteral(serverType, "nntp") &&
+ !MsgLowerCaseEqualsLiteral(serverType, "rss"))
+ aNodeArray->AppendObject(kNC_PageTitleJunk);
+
+ // Check the offline capability before adding
+ // offline item
+ int32_t offlineSupportLevel = 0;
+ rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool supportsDiskSpace;
+ rv = server->GetSupportsDiskSpace(&supportsDiskSpace);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // currently there is no offline without diskspace
+ if (offlineSupportLevel >= OFFLINE_SUPPORT_LEVEL_REGULAR)
+ aNodeArray->AppendObject(kNC_PageTitleSynchronization);
+ else if (supportsDiskSpace)
+ aNodeArray->AppendObject(kNC_PageTitleDiskSpace);
+
+ if (hasIdentities) {
+ // extensions come after the default panels
+ rv = appendGenericSettingsResources(server, aNodeArray);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to add generic panels");
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::serverHasIdentities(nsIMsgIncomingServer* aServer,
+ bool *aResult)
+{
+ nsresult rv;
+ *aResult = false;
+
+ nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager, &rv);
+
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIArray> identities;
+ rv = am->GetIdentitiesForServer(aServer, getter_AddRefs(identities));
+
+ // no identities just means no arcs
+ if (NS_FAILED(rv)) return NS_OK;
+
+ uint32_t count;
+ rv = identities->GetLength(&count);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ if (count >0)
+ *aResult = true;
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::getAccountArcs(nsIMutableArray **aResult)
+{
+ nsresult rv;
+ if (!mAccountArcsOut) {
+ mAccountArcsOut = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mAccountArcsOut->AppendElement(kNC_Settings, false);
+ mAccountArcsOut->AppendElement(kNC_Name, false);
+ mAccountArcsOut->AppendElement(kNC_FolderTreeName, false);
+ mAccountArcsOut->AppendElement(kNC_FolderTreeSimpleName, false);
+ mAccountArcsOut->AppendElement(kNC_NameSort, false);
+ mAccountArcsOut->AppendElement(kNC_FolderTreeNameSort, false);
+ mAccountArcsOut->AppendElement(kNC_PageTag, false);
+ }
+
+ *aResult = mAccountArcsOut;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::getAccountRootArcs(nsIMutableArray **aResult)
+{
+ nsresult rv;
+ if (!mAccountRootArcsOut) {
+ mAccountRootArcsOut = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mAccountRootArcsOut->AppendElement(kNC_Server, false);
+ mAccountRootArcsOut->AppendElement(kNC_Child, false);
+
+ mAccountRootArcsOut->AppendElement(kNC_Settings, false);
+ mAccountRootArcsOut->AppendElement(kNC_Name, false);
+ mAccountRootArcsOut->AppendElement(kNC_FolderTreeName, false);
+ mAccountRootArcsOut->AppendElement(kNC_FolderTreeSimpleName, false);
+ mAccountRootArcsOut->AppendElement(kNC_NameSort, false);
+ mAccountRootArcsOut->AppendElement(kNC_FolderTreeNameSort, false);
+ mAccountRootArcsOut->AppendElement(kNC_PageTag, false);
+ }
+
+ *aResult = mAccountRootArcsOut;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManagerDataSource::HasArcOut(nsIRDFResource *source, nsIRDFResource *aArc, bool *result)
+{
+ if (aArc == kNC_Settings) {
+ // based on createSettingsResources()
+ // we only have settings for local folders and servers with identities
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = getServerForFolderNode(source, getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) {
+ // Check the offline capability before adding arc
+ int32_t offlineSupportLevel = 0;
+ (void) server->GetOfflineSupportLevel(&offlineSupportLevel);
+ if (offlineSupportLevel >= OFFLINE_SUPPORT_LEVEL_REGULAR) {
+ *result = true;
+ return NS_OK;
+ }
+
+ bool supportsDiskSpace;
+ (void) server->GetSupportsDiskSpace(&supportsDiskSpace);
+ if (supportsDiskSpace) {
+ *result = true;
+ return NS_OK;
+ }
+ return serverHasIdentities(server, result);
+ }
+ }
+
+ *result = false;
+ return NS_OK;
+}
+
+/* nsISimpleEnumerator ArcLabelsOut (in nsIRDFResource aSource); */
+NS_IMETHODIMP
+nsMsgAccountManagerDataSource::ArcLabelsOut(nsIRDFResource *source,
+ nsISimpleEnumerator **_retval)
+{
+ nsresult rv;
+
+ // we have to return something, so always create the array/enumerators
+ nsCOMPtr<nsIMutableArray> arcs;
+ if (source == kNC_AccountRoot)
+ rv = getAccountRootArcs(getter_AddRefs(arcs));
+ else
+ rv = getAccountArcs(getter_AddRefs(arcs));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewArrayEnumerator(_retval, arcs);
+ if (NS_FAILED(rv)) return rv;
+
+#ifdef DEBUG_amds_
+ printf("GetArcLabelsOut(%s): Adding child, settings, and name arclabels\n", value);
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManagerDataSource::HasAssertion(nsIRDFResource *aSource,
+ nsIRDFResource *aProperty,
+ nsIRDFNode *aTarget,
+ bool aTruthValue,
+ bool *_retval)
+{
+ nsresult rv=NS_ERROR_FAILURE;
+
+ //
+ // msgaccounts:/ properties
+ //
+ if (aSource == kNC_AccountRoot) {
+ rv = HasAssertionAccountRoot(aProperty, aTarget, aTruthValue, _retval);
+ }
+ //
+ // server properties
+ // try to convert the resource to a folder, and then only
+ // answer if it's a server.. any failure falls through to the default case
+ //
+ // short-circuit on property, so objects like filters, etc, don't get queried
+ else if (aProperty == kNC_IsDefaultServer || aProperty == kNC_CanGetMessages ||
+ aProperty == kNC_CanGetIncomingMessages || aProperty == kNC_SupportsFilters) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = getServerForFolderNode(aSource, getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ rv = HasAssertionServer(server, aProperty, aTarget,
+ aTruthValue, _retval);
+ }
+
+ // any failures above fallthrough to the parent class
+ if (NS_FAILED(rv))
+ return nsMsgRDFDataSource::HasAssertion(aSource, aProperty,
+ aTarget, aTruthValue, _retval);
+ return NS_OK;
+}
+
+
+
+nsresult
+nsMsgAccountManagerDataSource::HasAssertionServer(nsIMsgIncomingServer *aServer,
+ nsIRDFResource *aProperty,
+ nsIRDFNode *aTarget,
+ bool aTruthValue,
+ bool *_retval)
+{
+ if (aProperty == kNC_IsDefaultServer)
+ *_retval = (aTarget == kTrueLiteral) ? isDefaultServer(aServer) : !isDefaultServer(aServer);
+ else if (aProperty == kNC_SupportsFilters)
+ *_retval = (aTarget == kTrueLiteral) ? supportsFilters(aServer) : !supportsFilters(aServer);
+ else if (aProperty == kNC_CanGetMessages)
+ *_retval = (aTarget == kTrueLiteral) ? canGetMessages(aServer) : !canGetMessages(aServer);
+ else if (aProperty == kNC_CanGetIncomingMessages)
+ *_retval = (aTarget == kTrueLiteral) ? canGetIncomingMessages(aServer) : !canGetIncomingMessages(aServer);
+ else
+ *_retval = false;
+ return NS_OK;
+}
+
+bool
+nsMsgAccountManagerDataSource::isDefaultServer(nsIMsgIncomingServer *aServer)
+{
+ nsresult rv;
+ if (!aServer) return false;
+
+ nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIMsgAccount> defaultAccount;
+ rv = am->GetDefaultAccount(getter_AddRefs(defaultAccount));
+ NS_ENSURE_SUCCESS(rv, false);
+ if (!defaultAccount) return false;
+
+ // in some weird case that there is no default and they asked
+ // for the default
+ nsCOMPtr<nsIMsgIncomingServer> defaultServer;
+ rv = defaultAccount->GetIncomingServer(getter_AddRefs(defaultServer));
+ NS_ENSURE_SUCCESS(rv, false);
+ if (!defaultServer) return false;
+
+ bool isEqual;
+ rv = defaultServer->Equals(aServer, &isEqual);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return isEqual;
+}
+
+
+bool
+nsMsgAccountManagerDataSource::supportsFilters(nsIMsgIncomingServer *aServer)
+{
+ bool supportsFilters;
+ nsresult rv = aServer->GetCanHaveFilters(&supportsFilters);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return supportsFilters;
+}
+
+bool
+nsMsgAccountManagerDataSource::canGetMessages(nsIMsgIncomingServer *aServer)
+{
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ nsresult rv = aServer->GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool canGetMessages;
+ rv = protocolInfo->GetCanGetMessages(&canGetMessages);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return canGetMessages;
+}
+
+bool
+nsMsgAccountManagerDataSource::canGetIncomingMessages(nsIMsgIncomingServer *aServer)
+{
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ nsresult rv = aServer->GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool canGetIncomingMessages;
+ rv = protocolInfo->GetCanGetIncomingMessages(&canGetIncomingMessages);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return canGetIncomingMessages;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::HasAssertionAccountRoot(nsIRDFResource *aProperty,
+ nsIRDFNode *aTarget,
+ bool aTruthValue,
+ bool *_retval)
+{
+ // set up default
+ *_retval = false;
+
+ // for child and settings arcs, just make sure it's a valid server:
+ if (isContainment(aProperty)) {
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = getServerForFolderNode(aTarget, getter_AddRefs(server));
+ if (NS_FAILED(rv) || !server) return rv;
+
+ nsCString serverKey;
+ server->GetKey(serverKey);
+
+ nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIArray> serverArray;
+ rv = am->GetAllServers(getter_AddRefs(serverArray));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t length;
+ rv = serverArray->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server(do_QueryElementAt(serverArray, i, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCString key;
+ server->GetKey(key);
+ if (key.Equals(serverKey))
+ {
+ *_retval = true;
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+nsMsgAccountManagerDataSource::isContainment(nsIRDFResource *aProperty)
+{
+
+ if (aProperty == kNC_Child ||
+ aProperty == kNC_Settings)
+ return true;
+ return false;
+}
+
+// returns failure if the object is not a root server
+nsresult
+nsMsgAccountManagerDataSource::getServerForFolderNode(nsIRDFNode *aResource,
+ nsIMsgIncomingServer **aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(aResource, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ bool isServer;
+ rv = folder->GetIsServer(&isServer);
+ if (NS_SUCCEEDED(rv) && isServer)
+ return folder->GetServer(aResult);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::getStringBundle()
+{
+ if (mStringBundle) return NS_OK;
+
+ nsCOMPtr<nsIStringBundleService> strBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(strBundleService, NS_ERROR_UNEXPECTED);
+
+ return strBundleService->CreateBundle("chrome://messenger/locale/prefs.properties",
+ getter_AddRefs(mStringBundle));
+}
+
+NS_IMETHODIMP
+nsMsgAccountManagerDataSource::OnServerLoaded(nsIMsgIncomingServer* aServer)
+{
+ nsCOMPtr<nsIMsgFolder> serverFolder;
+ nsresult rv = aServer->GetRootFolder(getter_AddRefs(serverFolder));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFResource> serverResource = do_QueryInterface(serverFolder,&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ NotifyObservers(kNC_AccountRoot, kNC_Child, serverResource, nullptr, true, false);
+ NotifyObservers(kNC_AccountRoot, kNC_Settings, serverResource, nullptr, true, false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManagerDataSource::OnServerUnloaded(nsIMsgIncomingServer* aServer)
+{
+ nsCOMPtr<nsIMsgFolder> serverFolder;
+ nsresult rv = aServer->GetRootFolder(getter_AddRefs(serverFolder));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFResource> serverResource = do_QueryInterface(serverFolder,&rv);
+ if (NS_FAILED(rv)) return rv;
+
+
+ NotifyObservers(kNC_AccountRoot, kNC_Child, serverResource, nullptr, false, false);
+ NotifyObservers(kNC_AccountRoot, kNC_Settings, serverResource, nullptr, false, false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManagerDataSource::OnServerChanged(nsIMsgIncomingServer *server)
+{
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *)
+{
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::OnItemUnicharPropertyChanged(nsIMsgFolder *, nsIAtom *, const char16_t *, const char16_t *)
+{
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::OnItemRemoved(nsIMsgFolder *, nsISupports *)
+{
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::OnItemPropertyFlagChanged(nsIMsgDBHdr *, nsIAtom *, uint32_t, uint32_t)
+{
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::OnItemAdded(nsIMsgFolder *, nsISupports *)
+{
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgAccountManagerDataSource::OnItemBoolPropertyChanged(nsIMsgFolder *aItem,
+ nsIAtom *aProperty,
+ bool aOldValue,
+ bool aNewValue)
+{
+ if (aProperty == kDefaultServerAtom) {
+ nsCOMPtr<nsIRDFResource> resource(do_QueryInterface(aItem));
+ NotifyObservers(resource, kNC_IsDefaultServer, kTrueLiteral, nullptr, aNewValue, false);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::OnItemEvent(nsIMsgFolder *, nsIAtom *)
+{
+ return NS_OK;
+}
+
+nsresult
+nsMsgAccountManagerDataSource::OnItemIntPropertyChanged(nsIMsgFolder *, nsIAtom *, int64_t, int64_t)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManagerDataSource::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
+{
+ nsMsgRDFDataSource::Observe(aSubject, aTopic, aData);
+
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMsgAccountManagerDS.h b/mailnews/base/src/nsMsgAccountManagerDS.h
new file mode 100644
index 000000000..0a8d4cf42
--- /dev/null
+++ b/mailnews/base/src/nsMsgAccountManagerDS.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMsgAccountManagerDS_h
+#define __nsMsgAccountManagerDS_h
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsError.h"
+#include "nsIID.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+
+#include "nsMsgRDFDataSource.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIIncomingServerListener.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsWeakPtr.h"
+#include "nsIMutableArray.h"
+#include "nsCOMArray.h"
+
+/* {3f989ca4-f77a-11d2-969d-006008948010} */
+#define NS_MSGACCOUNTMANAGERDATASOURCE_CID \
+ {0x3f989ca4, 0xf77a, 0x11d2, \
+ {0x96, 0x9d, 0x00, 0x60, 0x08, 0x94, 0x80, 0x10}}
+
+class nsMsgAccountManagerDataSource : public nsMsgRDFDataSource,
+ public nsIFolderListener,
+ public nsIIncomingServerListener
+{
+
+public:
+
+ nsMsgAccountManagerDataSource();
+ virtual nsresult Init() override;
+
+ virtual void Cleanup() override;
+ // service manager shutdown method
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFOLDERLISTENER
+ NS_DECL_NSIINCOMINGSERVERLISTENER
+ NS_DECL_NSIOBSERVER
+ // RDF datasource methods
+ NS_IMETHOD GetTarget(nsIRDFResource *source,
+ nsIRDFResource *property,
+ bool aTruthValue,
+ nsIRDFNode **_retval) override;
+ NS_IMETHOD GetTargets(nsIRDFResource *source,
+ nsIRDFResource *property,
+ bool aTruthValue,
+ nsISimpleEnumerator **_retval) override;
+ NS_IMETHOD ArcLabelsOut(nsIRDFResource *source,
+ nsISimpleEnumerator **_retval) override;
+
+ NS_IMETHOD HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty,
+ nsIRDFNode *aTarget, bool aTruthValue,
+ bool *_retval) override;
+ NS_IMETHOD HasArcOut(nsIRDFResource *source, nsIRDFResource *aArc,
+ bool *result) override;
+
+protected:
+ virtual ~nsMsgAccountManagerDataSource();
+
+ nsresult HasAssertionServer(nsIMsgIncomingServer *aServer,
+ nsIRDFResource *aProperty,
+ nsIRDFNode *aTarget,
+ bool aTruthValue, bool *_retval);
+
+ nsresult HasAssertionAccountRoot(nsIRDFResource *aProperty,
+ nsIRDFNode *aTarget,
+ bool aTruthValue, bool *_retval);
+
+ bool isDefaultServer(nsIMsgIncomingServer *aServer);
+ bool supportsFilters(nsIMsgIncomingServer *aServer);
+ bool canGetMessages(nsIMsgIncomingServer *aServer);
+ bool canGetIncomingMessages(nsIMsgIncomingServer *aServer);
+
+ static bool isContainment(nsIRDFResource *aProperty);
+ nsresult getServerForFolderNode(nsIRDFNode *aResource,
+ nsIMsgIncomingServer **aResult);
+
+ nsresult createRootResources(nsIRDFResource *aProperty,
+ nsCOMArray<nsIRDFResource> *aNodeArray);
+ nsresult createSettingsResources(nsIRDFResource *aSource,
+ nsCOMArray<nsIRDFResource> *aNodeArray);
+ nsresult appendGenericSettingsResources(nsIMsgIncomingServer *server,\
+ nsCOMArray<nsIRDFResource> *aNodeArray);
+ nsresult appendGenericSetting(const char *name,
+ nsCOMArray<nsIRDFResource> *aNodeArray);
+
+ static nsIRDFResource* kNC_Name;
+ static nsIRDFResource* kNC_FolderTreeName;
+ static nsIRDFResource* kNC_FolderTreeSimpleName;
+ static nsIRDFResource* kNC_NameSort;
+ static nsIRDFResource* kNC_FolderTreeNameSort;
+ static nsIRDFResource* kNC_PageTag;
+ static nsIRDFResource* kNC_IsDefaultServer;
+ static nsIRDFResource* kNC_SupportsFilters;
+ static nsIRDFResource* kNC_CanGetMessages;
+ static nsIRDFResource* kNC_CanGetIncomingMessages;
+
+ static nsIRDFResource* kNC_Child;
+ static nsIRDFResource* kNC_AccountRoot;
+
+ static nsIRDFResource* kNC_Account;
+ static nsIRDFResource* kNC_Server;
+ static nsIRDFResource* kNC_Identity;
+ static nsIRDFResource* kNC_Settings;
+ static nsIRDFResource* kNC_Junk;
+
+ static nsIRDFResource* kNC_PageTitleMain;
+ static nsIRDFResource* kNC_PageTitleServer;
+ static nsIRDFResource* kNC_PageTitleCopies;
+ static nsIRDFResource* kNC_PageTitleSynchronization;
+ static nsIRDFResource* kNC_PageTitleDiskSpace;
+ static nsIRDFResource* kNC_PageTitleAddressing;
+ static nsIRDFResource* kNC_PageTitleSMTP;
+ static nsIRDFResource* kNC_PageTitleJunk;
+
+ static nsIRDFLiteral* kTrueLiteral;
+
+ static nsIAtom* kDefaultServerAtom;
+
+ static nsrefcnt gAccountManagerResourceRefCnt;
+
+ static nsresult getAccountArcs(nsIMutableArray **aResult);
+ static nsresult getAccountRootArcs(nsIMutableArray **aResult);
+
+private:
+ nsresult serverHasIdentities(nsIMsgIncomingServer *aServer, bool *aResult);
+ nsresult getStringBundle();
+
+ static nsCOMPtr<nsIMutableArray> mAccountArcsOut;
+ static nsCOMPtr<nsIMutableArray> mAccountRootArcsOut;
+ nsWeakPtr mAccountManager;
+ nsCOMPtr<nsIStringBundle> mStringBundle;
+};
+
+#endif /* __nsMsgAccountManagerDS_h */
diff --git a/mailnews/base/src/nsMsgBiffManager.cpp b/mailnews/base/src/nsMsgBiffManager.cpp
new file mode 100644
index 000000000..d0fc8aba4
--- /dev/null
+++ b/mailnews/base/src/nsMsgBiffManager.cpp
@@ -0,0 +1,372 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgBiffManager.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsStatusBarBiffManager.h"
+#include "nsCOMArray.h"
+#include "mozilla/Logging.h"
+#include "nspr.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIObserverService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+#define PREF_BIFF_JITTER "mail.biff.add_interval_jitter"
+
+static NS_DEFINE_CID(kStatusBarBiffManagerCID, NS_STATUSBARBIFFMANAGER_CID);
+
+static PRLogModuleInfo *MsgBiffLogModule = nullptr;
+
+NS_IMPL_ISUPPORTS(nsMsgBiffManager, nsIMsgBiffManager,
+ nsIIncomingServerListener, nsIObserver,
+ nsISupportsWeakReference)
+
+void OnBiffTimer(nsITimer *timer, void *aBiffManager)
+{
+ nsMsgBiffManager *biffManager = (nsMsgBiffManager*)aBiffManager;
+ biffManager->PerformBiff();
+}
+
+nsMsgBiffManager::nsMsgBiffManager()
+{
+ mHaveShutdown = false;
+ mInited = false;
+}
+
+nsMsgBiffManager::~nsMsgBiffManager()
+{
+ if (mBiffTimer)
+ mBiffTimer->Cancel();
+
+ if (!mHaveShutdown)
+ Shutdown();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ {
+ observerService->RemoveObserver(this, "wake_notification");
+ observerService->RemoveObserver(this, "sleep_notification");
+ }
+}
+
+NS_IMETHODIMP nsMsgBiffManager::Init()
+{
+ if (mInited)
+ return NS_OK;
+
+ mInited = true;
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ accountManager->AddIncomingServerListener(this);
+
+ // in turbo mode on profile change we don't need to do anything below this
+ if (mHaveShutdown)
+ {
+ mHaveShutdown = false;
+ return NS_OK;
+ }
+
+ // Ensure status bar biff service has started
+ nsCOMPtr<nsIFolderListener> statusBarBiffService =
+ do_GetService(kStatusBarBiffManagerCID, &rv);
+
+ if (!MsgBiffLogModule)
+ MsgBiffLogModule = PR_NewLogModule("MsgBiff");
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ {
+ observerService->AddObserver(this, "sleep_notification", true);
+ observerService->AddObserver(this, "wake_notification", true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::Shutdown()
+{
+ if (mBiffTimer)
+ {
+ mBiffTimer->Cancel();
+ mBiffTimer = nullptr;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ accountManager->RemoveIncomingServerListener(this);
+
+ mHaveShutdown = true;
+ mInited = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ if (!strcmp(aTopic, "sleep_notification") && mBiffTimer)
+ {
+ mBiffTimer->Cancel();
+ mBiffTimer = nullptr;
+ }
+ else if (!strcmp(aTopic, "wake_notification"))
+ {
+ // wait 10 seconds after waking up to start biffing again.
+ mBiffTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mBiffTimer->InitWithFuncCallback(OnBiffTimer, (void*)this, 10000,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::AddServerBiff(nsIMsgIncomingServer *server)
+{
+ NS_ENSURE_ARG_POINTER(server);
+
+ int32_t biffMinutes;
+
+ nsresult rv = server->GetBiffMinutes(&biffMinutes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't add if biffMinutes isn't > 0
+ if (biffMinutes > 0)
+ {
+ int32_t serverIndex = FindServer(server);
+ // Only add it if it hasn't been added already.
+ if (serverIndex == -1)
+ {
+ nsBiffEntry biffEntry;
+ biffEntry.server = server;
+ rv = SetNextBiffTime(biffEntry, PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddBiffEntry(biffEntry);
+ SetupNextBiff();
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::RemoveServerBiff(nsIMsgIncomingServer *server)
+{
+ int32_t pos = FindServer(server);
+ if (pos != -1)
+ mBiffArray.RemoveElementAt(pos);
+
+ // Should probably reset biff time if this was the server that gets biffed
+ // next.
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgBiffManager::ForceBiff(nsIMsgIncomingServer *server)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::ForceBiffAll()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::OnServerLoaded(nsIMsgIncomingServer *server)
+{
+ NS_ENSURE_ARG_POINTER(server);
+
+ bool doBiff = false;
+ nsresult rv = server->GetDoBiff(&doBiff);
+
+ if (NS_SUCCEEDED(rv) && doBiff)
+ rv = AddServerBiff(server);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::OnServerUnloaded(nsIMsgIncomingServer *server)
+{
+ return RemoveServerBiff(server);
+}
+
+NS_IMETHODIMP nsMsgBiffManager::OnServerChanged(nsIMsgIncomingServer *server)
+{
+ // nothing required. If the hostname or username changed
+ // the next time biff fires, we'll ping the right server
+ return NS_OK;
+}
+
+int32_t nsMsgBiffManager::FindServer(nsIMsgIncomingServer *server)
+{
+ uint32_t count = mBiffArray.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ if (server == mBiffArray[i].server.get())
+ return i;
+ }
+ return -1;
+}
+
+nsresult nsMsgBiffManager::AddBiffEntry(nsBiffEntry &biffEntry)
+{
+ uint32_t i;
+ uint32_t count = mBiffArray.Length();
+ for (i = 0; i < count; i++)
+ {
+ if (biffEntry.nextBiffTime < mBiffArray[i].nextBiffTime)
+ break;
+ }
+ MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("inserting biff entry at %d\n", i));
+ mBiffArray.InsertElementAt(i, biffEntry);
+ return NS_OK;
+}
+
+nsresult nsMsgBiffManager::SetNextBiffTime(nsBiffEntry &biffEntry, PRTime currentTime)
+{
+ nsIMsgIncomingServer *server = biffEntry.server;
+ NS_ENSURE_TRUE(server, NS_ERROR_FAILURE);
+
+ int32_t biffInterval;
+ nsresult rv = server->GetBiffMinutes(&biffInterval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add biffInterval, converted in microseconds, to current time.
+ // Force 64-bit multiplication.
+ PRTime chosenTimeInterval = biffInterval * 60000000LL;
+ biffEntry.nextBiffTime = currentTime + chosenTimeInterval;
+
+ // Check if we should jitter.
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs)
+ {
+ bool shouldUseBiffJitter = false;
+ prefs->GetBoolPref(PREF_BIFF_JITTER, &shouldUseBiffJitter);
+ if (shouldUseBiffJitter)
+ {
+ // Calculate a jitter of +/-5% on chosenTimeInterval
+ // - minimum 1 second (to avoid a modulo with 0)
+ // - maximum 30 seconds (to avoid problems when biffInterval is very large)
+ int64_t jitter = (int64_t)(0.05 * (int64_t)chosenTimeInterval);
+ jitter = std::max<int64_t>(1000000LL, std::min<int64_t>(jitter, 30000000LL));
+ jitter = ((rand() % 2) ? 1 : -1) * (rand() % jitter);
+
+ biffEntry.nextBiffTime += jitter;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgBiffManager::SetupNextBiff()
+{
+ if (mBiffArray.Length() > 0)
+ {
+ // Get the next biff entry
+ const nsBiffEntry &biffEntry = mBiffArray[0];
+ PRTime currentTime = PR_Now();
+ int64_t biffDelay;
+ int64_t ms(1000);
+
+ if (currentTime > biffEntry.nextBiffTime)
+ {
+ // Let's wait 30 seconds before firing biff again
+ biffDelay = 30 * PR_USEC_PER_SEC;
+ }
+ else
+ biffDelay = biffEntry.nextBiffTime - currentTime;
+
+ // Convert biffDelay into milliseconds
+ int64_t timeInMS = biffDelay / ms;
+ uint32_t timeInMSUint32 = (uint32_t)timeInMS;
+
+ // Can't currently reset a timer when it's in the process of
+ // calling Notify. So, just release the timer here and create a new one.
+ if (mBiffTimer)
+ mBiffTimer->Cancel();
+
+ MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("setting %d timer\n", timeInMSUint32));
+ mBiffTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mBiffTimer->InitWithFuncCallback(OnBiffTimer, (void*)this, timeInMSUint32,
+ nsITimer::TYPE_ONE_SHOT);
+
+ }
+ return NS_OK;
+}
+
+//This is the function that does a biff on all of the servers whose time it is to biff.
+nsresult nsMsgBiffManager::PerformBiff()
+{
+ PRTime currentTime = PR_Now();
+ nsCOMArray<nsIMsgFolder> targetFolders;
+ MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("performing biffs\n"));
+
+ uint32_t count = mBiffArray.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ // Take a copy of the entry rather than the a reference so that we can
+ // remove and add if necessary, but keep the references and memory alive.
+ nsBiffEntry current = mBiffArray[i];
+ if (current.nextBiffTime < currentTime)
+ {
+ bool serverBusy = false;
+ bool serverRequiresPassword = true;
+ bool passwordPromptRequired;
+
+ current.server->GetPasswordPromptRequired(&passwordPromptRequired);
+ current.server->GetServerBusy(&serverBusy);
+ current.server->GetServerRequiresPasswordForBiff(&serverRequiresPassword);
+ // find the dest folder we're actually downloading to...
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ current.server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ int32_t targetFolderIndex = targetFolders.IndexOfObject(rootMsgFolder);
+ if (targetFolderIndex == kNotFound)
+ targetFolders.AppendObject(rootMsgFolder);
+
+ // so if we need to be authenticated to biff, check that we are
+ // (since we don't want to prompt the user for password UI)
+ // and make sure the server isn't already in the middle of downloading
+ // new messages
+ if (!serverBusy &&
+ (!serverRequiresPassword || !passwordPromptRequired) &&
+ targetFolderIndex == kNotFound)
+ {
+ nsCString serverKey;
+ current.server->GetKey(serverKey);
+ nsresult rv = current.server->PerformBiff(nullptr);
+ MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("biffing server %s rv = %x\n", serverKey.get(), rv));
+ }
+ else
+ {
+ MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("not biffing server serverBusy = %d requirespassword = %d password prompt required = %d targetFolderIndex = %d\n",
+ serverBusy, serverRequiresPassword, passwordPromptRequired, targetFolderIndex));
+ }
+ // if we didn't do this server because the destination server was already being
+ // biffed into, leave this server in the biff array so it will fire next.
+ if (targetFolderIndex == kNotFound)
+ {
+ mBiffArray.RemoveElementAt(i);
+ i--; //Because we removed it we need to look at the one that just moved up.
+ SetNextBiffTime(current, currentTime);
+ AddBiffEntry(current);
+ }
+#ifdef DEBUG_David_Bienvenu
+ else
+ printf("dest account performing biff\n");
+#endif
+ }
+ else
+ //since we're in biff order, there's no reason to keep checking
+ break;
+ }
+ SetupNextBiff();
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMsgBiffManager.h b/mailnews/base/src/nsMsgBiffManager.h
new file mode 100644
index 000000000..909c2b493
--- /dev/null
+++ b/mailnews/base/src/nsMsgBiffManager.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSMSGBIFFMANAGER_H
+#define NSMSGBIFFMANAGER_H
+
+#include "msgCore.h"
+#include "nsIMsgBiffManager.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsIIncomingServerListener.h"
+#include "nsWeakReference.h"
+#include "nsIObserver.h"
+
+typedef struct {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ PRTime nextBiffTime;
+} nsBiffEntry;
+
+
+class nsMsgBiffManager
+ : public nsIMsgBiffManager,
+ public nsIIncomingServerListener,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ nsMsgBiffManager();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGBIFFMANAGER
+ NS_DECL_NSIINCOMINGSERVERLISTENER
+ NS_DECL_NSIOBSERVER
+
+ nsresult PerformBiff();
+
+protected:
+ virtual ~nsMsgBiffManager();
+
+ int32_t FindServer(nsIMsgIncomingServer *server);
+ nsresult SetNextBiffTime(nsBiffEntry &biffEntry, PRTime currentTime);
+ nsresult SetupNextBiff();
+ nsresult AddBiffEntry(nsBiffEntry &biffEntry);
+
+protected:
+ nsCOMPtr<nsITimer> mBiffTimer;
+ nsTArray<nsBiffEntry> mBiffArray;
+ bool mHaveShutdown;
+ bool mInited;
+};
+
+#endif // NSMSGBIFFMANAGER_H
diff --git a/mailnews/base/src/nsMsgContentPolicy.cpp b/mailnews/base/src/nsMsgContentPolicy.cpp
new file mode 100644
index 000000000..ebc02635f
--- /dev/null
+++ b/mailnews/base/src/nsMsgContentPolicy.cpp
@@ -0,0 +1,1076 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgContentPolicy.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIAbManager.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbCard.h"
+#include "nsIMsgWindow.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsIMsgHdr.h"
+#include "nsIEncryptedSMIMEURIsSrvc.h"
+#include "nsNetUtil.h"
+#include "nsIMsgComposeService.h"
+#include "nsMsgCompCID.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIWebNavigation.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIFrameLoader.h"
+#include "nsIWebProgress.h"
+#include "nsMsgUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsINntpUrl.h"
+#include "nsSandboxFlags.h"
+
+static const char kBlockRemoteImages[] = "mailnews.message_display.disable_remote_image";
+static const char kAllowPlugins[] = "mailnews.message_display.allow_plugins";
+static const char kTrustedDomains[] = "mail.trusteddomains";
+
+using namespace mozilla::mailnews;
+
+// Per message headder flags to keep track of whether the user is allowing remote
+// content for a particular message.
+// if you change or add more values to these constants, be sure to modify
+// the corresponding definitions in mailWindowOverlay.js
+#define kNoRemoteContentPolicy 0
+#define kBlockRemoteContent 1
+#define kAllowRemoteContent 2
+
+NS_IMPL_ISUPPORTS(nsMsgContentPolicy,
+ nsIContentPolicy,
+ nsIWebProgressListener,
+ nsIMsgContentPolicy,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+nsMsgContentPolicy::nsMsgContentPolicy()
+{
+ mAllowPlugins = false;
+ mBlockRemoteImages = true;
+}
+
+nsMsgContentPolicy::~nsMsgContentPolicy()
+{
+ // hey, we are going away...clean up after ourself....unregister our observer
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefInternal = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ prefInternal->RemoveObserver(kBlockRemoteImages, this);
+ prefInternal->RemoveObserver(kAllowPlugins, this);
+ }
+}
+
+nsresult nsMsgContentPolicy::Init()
+{
+ nsresult rv;
+
+ // register ourself as an observer on the mail preference to block remote images
+ nsCOMPtr<nsIPrefBranch> prefInternal = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ prefInternal->AddObserver(kBlockRemoteImages, this, true);
+ prefInternal->AddObserver(kAllowPlugins, this, true);
+
+ prefInternal->GetBoolPref(kAllowPlugins, &mAllowPlugins);
+ prefInternal->GetCharPref(kTrustedDomains, getter_Copies(mTrustedMailDomains));
+ prefInternal->GetBoolPref(kBlockRemoteImages, &mBlockRemoteImages);
+
+ // Grab a handle on the PermissionManager service for managing allowed remote
+ // content senders.
+ mPermissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+/**
+ * @returns true if the sender referenced by aMsgHdr is explicitly allowed to
+ * load remote images according to the PermissionManager
+ */
+bool
+nsMsgContentPolicy::ShouldAcceptRemoteContentForSender(nsIMsgDBHdr *aMsgHdr)
+{
+ if (!aMsgHdr)
+ return false;
+
+ // extract the e-mail address from the msg hdr
+ nsCString author;
+ nsresult rv = aMsgHdr->GetAuthor(getter_Copies(author));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCString emailAddress;
+ ExtractEmail(EncodedHeader(author), emailAddress);
+ if (emailAddress.IsEmpty())
+ return false;
+
+ nsCOMPtr<nsIIOService> ios = do_GetService("@mozilla.org/network/io-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIURI> mailURI;
+ emailAddress.Insert("chrome://messenger/content/email=", 0);
+ rv = ios->NewURI(emailAddress, nullptr, nullptr, getter_AddRefs(mailURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // check with permission manager
+ uint32_t permission = 0;
+ rv = mPermissionManager->TestPermission(mailURI, "image", &permission);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Only return true if the permission manager has an explicit allow
+ return (permission == nsIPermissionManager::ALLOW_ACTION);
+}
+
+/**
+ * Extract the host name from aContentLocation, and look it up in our list
+ * of trusted domains.
+ */
+bool nsMsgContentPolicy::IsTrustedDomain(nsIURI * aContentLocation)
+{
+ bool trustedDomain = false;
+ // get the host name of the server hosting the remote image
+ nsAutoCString host;
+ nsresult rv = aContentLocation->GetHost(host);
+
+ if (NS_SUCCEEDED(rv) && !mTrustedMailDomains.IsEmpty())
+ trustedDomain = MsgHostDomainIsTrusted(host, mTrustedMailDomains);
+
+ return trustedDomain;
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::ShouldLoad(uint32_t aContentType,
+ nsIURI *aContentLocation,
+ nsIURI *aRequestingLocation,
+ nsISupports *aRequestingContext,
+ const nsACString &aMimeGuess,
+ nsISupports *aExtra,
+ nsIPrincipal *aRequestPrincipal,
+ int16_t *aDecision)
+{
+ nsresult rv = NS_OK;
+ // The default decision at the start of the function is to accept the load.
+ // Once we have checked the content type and the requesting location, then
+ // we switch it to reject.
+ //
+ // Be very careful about returning error codes - if this method returns an
+ // NS_ERROR_*, any decision made here will be ignored, and the document could
+ // be accepted when we don't want it to be.
+ //
+ // In most cases if an error occurs, its something we didn't expect so we
+ // should be rejecting the document anyway.
+ *aDecision = nsIContentPolicy::ACCEPT;
+
+ NS_ENSURE_ARG_POINTER(aContentLocation);
+
+#ifdef DEBUG_MsgContentPolicy
+ fprintf(stderr, "aContentType: %d\naContentLocation = %s\n",
+ aContentType,
+ aContentLocation->GetSpecOrDefault().get());
+#endif
+
+#ifndef MOZ_THUNDERBIRD
+ // Go find out if we are dealing with mailnews. Anything else
+ // isn't our concern and we accept content.
+ nsCOMPtr<nsIDocShell> rootDocShell;
+ rv = GetRootDocShellForContext(aRequestingContext,
+ getter_AddRefs(rootDocShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t appType;
+ rv = rootDocShell->GetAppType(&appType);
+ // We only want to deal with mailnews
+ if (NS_FAILED(rv) || appType != nsIDocShell::APP_TYPE_MAIL)
+ return NS_OK;
+#endif
+
+ switch(aContentType) {
+ // Plugins (nsIContentPolicy::TYPE_OBJECT) are blocked on document load.
+ case nsIContentPolicy::TYPE_DOCUMENT:
+ // At this point, we have no intention of supporting a different JS
+ // setting on a subdocument, so we don't worry about TYPE_SUBDOCUMENT here.
+
+ // If the timing were right, we'd enable JavaScript on the docshell
+ // for non mailnews URIs here. However, at this point, the
+ // old document may still be around, so we can't do any enabling just yet.
+ // Instead, we apply the policy in nsIWebProgressListener::OnLocationChange.
+ // For now, we explicitly disable JavaScript in order to be safe rather than
+ // sorry, because OnLocationChange isn't guaranteed to necessarily be called
+ // soon enough to disable it in time (though bz says it _should_ be called
+ // soon enough "in all sane cases").
+ rv = SetDisableItemsOnMailNewsUrlDocshells(aContentLocation,
+ aRequestingContext);
+ // if something went wrong during the tweaking, reject this content
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to set disable items on docShells");
+ *aDecision = nsIContentPolicy::REJECT_TYPE;
+ return NS_OK;
+ }
+ break;
+
+ case nsIContentPolicy::TYPE_CSP_REPORT:
+ // We cannot block CSP reports.
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ break;
+
+ default:
+ break;
+ }
+
+ // NOTE: Not using NS_ENSURE_ARG_POINTER because this is a legitimate case
+ // that can happen. Also keep in mind that the default policy used for a
+ // failure code is ACCEPT.
+ if (!aRequestingLocation)
+ return NS_ERROR_INVALID_POINTER;
+
+#ifdef DEBUG_MsgContentPolicy
+ fprintf(stderr, "aRequestingLocation = %s\n", aRequestingLocation->GetSpecOrDefault().get());
+#endif
+
+ // If the requesting location is safe, accept the content location request.
+ if (IsSafeRequestingLocation(aRequestingLocation))
+ return rv;
+
+ // Now default to reject so early returns via NS_ENSURE_SUCCESS
+ // cause content to be rejected.
+ *aDecision = nsIContentPolicy::REJECT_REQUEST;
+
+ // We want to establish the following:
+ // \--------\ requester | | |
+ // content \------------\ | | |
+ // requested \| mail message | news message | http(s)/data etc.
+ // -------------------------+---------------+--------------+------------------
+ // mail message content | load if same | don't load | don't load
+ // mailbox, imap, JsAccount | message (1) | (2) | (3)
+ // -------------------------+---------------+--------------+------------------
+ // news message | don't load (4)| load (5) | load (6)
+ // -------------------------+---------------+--------------+------------------
+ // http(s)/data, etc. | (default) | (default) | (default)
+ // -------------------------+---------------+--------------+------------------
+ nsCOMPtr<nsIMsgMessageUrl> contentURL(do_QueryInterface(aContentLocation));
+ if (contentURL) {
+ nsCOMPtr<nsINntpUrl> contentNntpURL(do_QueryInterface(aContentLocation));
+ if (!contentNntpURL) {
+ // Mail message (mailbox, imap or JsAccount) content requested, for example
+ // a message part, like an image:
+ // To load mail message content the requester must have the same
+ // "normalised" principal. This is basically a "same origin" test, it
+ // protects against cross-loading of mail message content from
+ // other mail or news messages.
+ nsCOMPtr<nsIMsgMessageUrl> requestURL(do_QueryInterface(aRequestingLocation));
+ // If the request URL is not also a message URL, then we don't accept.
+ if (requestURL) {
+ nsCString contentPrincipalSpec, requestPrincipalSpec;
+ nsresult rv1 = contentURL->GetPrincipalSpec(contentPrincipalSpec);
+ nsresult rv2 = requestURL->GetPrincipalSpec(requestPrincipalSpec);
+ if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2) &&
+ contentPrincipalSpec.Equals(requestPrincipalSpec))
+ *aDecision = nsIContentPolicy::ACCEPT; // (1)
+ }
+ return NS_OK; // (2) and (3)
+ }
+
+ // News message content requested. Don't accept request coming
+ // from a mail message since it would access the news server.
+ nsCOMPtr<nsIMsgMessageUrl> requestURL(do_QueryInterface(aRequestingLocation));
+ if (requestURL) {
+ nsCOMPtr<nsINntpUrl> requestNntpURL(do_QueryInterface(aRequestingLocation));
+ if (!requestNntpURL)
+ return NS_OK; // (4)
+ }
+ *aDecision = nsIContentPolicy::ACCEPT; // (5) and (6)
+ return NS_OK;
+ }
+
+ // If exposed protocol not covered by the test above or protocol that has been
+ // specifically exposed by an add-on, or is a chrome url, then allow the load.
+ if (IsExposedProtocol(aContentLocation))
+ {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+
+ // never load unexposed protocols except for http, https and file.
+ // Protocols like ftp are always blocked.
+ if (ShouldBlockUnexposedProtocol(aContentLocation))
+ return NS_OK;
+
+
+ // Find out the URI that originally initiated the set of requests for this
+ // context.
+ nsCOMPtr<nsIURI> originatorLocation;
+ if (!aRequestingContext && aRequestPrincipal)
+ {
+ // Can get the URI directly from the principal.
+ rv = aRequestPrincipal->GetURI(getter_AddRefs(originatorLocation));
+ }
+ else
+ {
+ rv = GetOriginatingURIForContext(aRequestingContext,
+ getter_AddRefs(originatorLocation));
+ }
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+#ifdef DEBUG_MsgContentPolicy
+ fprintf(stderr, "originatorLocation = %s\n", originatorLocation->GetSpecOrDefault().get());
+#endif
+
+ // Don't load remote content for encrypted messages.
+ nsCOMPtr<nsIEncryptedSMIMEURIsService> encryptedURIService =
+ do_GetService("@mozilla.org/messenger-smime/smime-encrypted-uris-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isEncrypted;
+ rv = encryptedURIService->IsEncrypted(aRequestingLocation->GetSpecOrDefault(), &isEncrypted);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isEncrypted)
+ {
+ *aDecision = nsIContentPolicy::REJECT_REQUEST;
+ NotifyContentWasBlocked(originatorLocation, aContentLocation, false);
+ return NS_OK;
+ }
+
+ // If we are allowing all remote content...
+ if (!mBlockRemoteImages)
+ {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+
+ // Extract the windowtype to handle compose windows separately from mail
+ if (aRequestingContext)
+ {
+ nsCOMPtr<nsIMsgCompose> msgCompose =
+ GetMsgComposeForContext(aRequestingContext);
+ // Work out if we're in a compose window or not.
+ if (msgCompose)
+ {
+ ComposeShouldLoad(msgCompose, aRequestingContext, aContentLocation,
+ aDecision);
+ return NS_OK;
+ }
+ }
+
+ // Allow content when using a remote page.
+ bool isHttp;
+ bool isHttps;
+ rv = originatorLocation->SchemeIs("http", &isHttp);
+ nsresult rv2 = originatorLocation->SchemeIs("https", &isHttps);
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2) && (isHttp || isHttps))
+ {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+
+ uint32_t permission;
+ mPermissionManager->TestPermission(aContentLocation, "image", &permission);
+ switch (permission) {
+ case nsIPermissionManager::UNKNOWN_ACTION:
+ {
+ // No exception was found for this location.
+ break;
+ }
+ case nsIPermissionManager::ALLOW_ACTION:
+ {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+ case nsIPermissionManager::DENY_ACTION:
+ {
+ *aDecision = nsIContentPolicy::REJECT_REQUEST;
+ return NS_OK;
+ }
+ }
+
+ // The default decision is still to reject.
+ ShouldAcceptContentForPotentialMsg(originatorLocation, aContentLocation,
+ aDecision);
+ return NS_OK;
+}
+
+/**
+ * Determines if the requesting location is a safe one, i.e. its under the
+ * app/user's control - so file, about, chrome etc.
+ */
+bool
+nsMsgContentPolicy::IsSafeRequestingLocation(nsIURI *aRequestingLocation)
+{
+ if (!aRequestingLocation)
+ return false;
+
+ // If aRequestingLocation is one of chrome, resource, file or view-source,
+ // allow aContentLocation to load.
+ bool isChrome;
+ bool isRes;
+ bool isFile;
+ bool isViewSource;
+
+ nsresult rv = aRequestingLocation->SchemeIs("chrome", &isChrome);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = aRequestingLocation->SchemeIs("resource", &isRes);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = aRequestingLocation->SchemeIs("file", &isFile);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = aRequestingLocation->SchemeIs("view-source", &isViewSource);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (isChrome || isRes || isFile || isViewSource)
+ return true;
+
+ // Only allow about: to load anything if the requesting location is not the
+ // special about:blank one.
+ bool isAbout;
+ rv = aRequestingLocation->SchemeIs("about", &isAbout);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (!isAbout)
+ return false;
+
+ nsCString fullSpec;
+ rv = aRequestingLocation->GetSpec(fullSpec);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return !fullSpec.EqualsLiteral("about:blank");
+}
+
+/**
+ * Determines if the content location is a scheme that we're willing to expose
+ * for unlimited loading of content.
+ */
+bool
+nsMsgContentPolicy::IsExposedProtocol(nsIURI *aContentLocation)
+{
+ nsAutoCString contentScheme;
+ nsresult rv = aContentLocation->GetScheme(contentScheme);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Check some exposed protocols. Not all protocols in the list of
+ // network.protocol-handler.expose.* prefs in all-thunderbird.js are
+ // admitted purely based on their scheme.
+ // news, snews, nntp, imap and mailbox are checked before the call
+ // to this function by matching content location and requesting location.
+ if (MsgLowerCaseEqualsLiteral(contentScheme, "mailto") ||
+ MsgLowerCaseEqualsLiteral(contentScheme, "addbook") ||
+ MsgLowerCaseEqualsLiteral(contentScheme, "about"))
+ return true;
+
+ // check if customized exposed scheme
+ if (mCustomExposedProtocols.Contains(contentScheme))
+ return true;
+
+ bool isData;
+ bool isChrome;
+ bool isRes;
+ rv = aContentLocation->SchemeIs("chrome", &isChrome);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = aContentLocation->SchemeIs("resource", &isRes);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = aContentLocation->SchemeIs("data", &isData);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return isChrome || isRes || isData;
+}
+
+/**
+ * We block most unexposed protocols - apart from http(s) and file.
+ */
+bool
+nsMsgContentPolicy::ShouldBlockUnexposedProtocol(nsIURI *aContentLocation)
+{
+ bool isHttp;
+ bool isHttps;
+ bool isFile;
+ // Error condition - we must return true so that we block.
+ nsresult rv = aContentLocation->SchemeIs("http", &isHttp);
+ NS_ENSURE_SUCCESS(rv, true);
+ rv = aContentLocation->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv, true);
+ rv = aContentLocation->SchemeIs("file", &isFile);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ return !isHttp && !isHttps && !isFile;
+}
+
+/**
+ * The default for this function will be to reject the content request.
+ * When determining if to allow the request for a given msg hdr, the function
+ * will go through the list of remote content blocking criteria:
+ *
+ * #1 Allow if there is a db header for a manual override.
+ * #2 Allow if the message is in an RSS folder.
+ * #3 Allow if the domain for the remote image in our white list.
+ * #4 Allow if the author has been specifically white listed.
+ */
+int16_t
+nsMsgContentPolicy::ShouldAcceptRemoteContentForMsgHdr(nsIMsgDBHdr *aMsgHdr,
+ nsIURI *aRequestingLocation,
+ nsIURI *aContentLocation)
+{
+ if (!aMsgHdr)
+ return static_cast<int16_t>(nsIContentPolicy::REJECT_REQUEST);
+
+ // Case #1, check the db hdr for the remote content policy on this particular
+ // message.
+ uint32_t remoteContentPolicy = kNoRemoteContentPolicy;
+ aMsgHdr->GetUint32Property("remoteContentPolicy", &remoteContentPolicy);
+
+ // Case #2, check if the message is in an RSS folder
+ bool isRSS = false;
+ IsRSSArticle(aRequestingLocation, &isRSS);
+
+ // Case #3, the domain for the remote image is in our white list
+ bool trustedDomain = IsTrustedDomain(aContentLocation);
+
+ // Case 4 means looking up items in the permissions database. So if
+ // either of the two previous items means we load the data, just do it.
+ if (isRSS || remoteContentPolicy == kAllowRemoteContent || trustedDomain)
+ return nsIContentPolicy::ACCEPT;
+
+ // Case #4, author is in our white list..
+ bool allowForSender = ShouldAcceptRemoteContentForSender(aMsgHdr);
+
+ int16_t result = allowForSender ?
+ static_cast<int16_t>(nsIContentPolicy::ACCEPT) :
+ static_cast<int16_t>(nsIContentPolicy::REJECT_REQUEST);
+
+ // kNoRemoteContentPolicy means we have never set a value on the message
+ if (result == nsIContentPolicy::REJECT_REQUEST && !remoteContentPolicy)
+ aMsgHdr->SetUint32Property("remoteContentPolicy", kBlockRemoteContent);
+
+ return result;
+}
+
+class RemoteContentNotifierEvent : public mozilla::Runnable
+{
+public:
+ RemoteContentNotifierEvent(nsIMsgWindow *aMsgWindow, nsIMsgDBHdr *aMsgHdr,
+ nsIURI *aContentURI, bool aCanOverride = true)
+ : mMsgWindow(aMsgWindow), mMsgHdr(aMsgHdr), mContentURI(aContentURI),
+ mCanOverride(aCanOverride)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mMsgWindow)
+ {
+ nsCOMPtr<nsIMsgHeaderSink> msgHdrSink;
+ (void)mMsgWindow->GetMsgHeaderSink(getter_AddRefs(msgHdrSink));
+ if (msgHdrSink)
+ msgHdrSink->OnMsgHasRemoteContent(mMsgHdr, mContentURI, mCanOverride);
+ }
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIMsgWindow> mMsgWindow;
+ nsCOMPtr<nsIMsgDBHdr> mMsgHdr;
+ nsCOMPtr<nsIURI> mContentURI;
+ bool mCanOverride;
+};
+
+/**
+ * This function is used to show a blocked remote content notification.
+ */
+void
+nsMsgContentPolicy::NotifyContentWasBlocked(nsIURI *aOriginatorLocation,
+ nsIURI *aContentLocation,
+ bool aCanOverride)
+{
+ // Is it a mailnews url?
+ nsresult rv;
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(aOriginatorLocation,
+ &rv));
+ if (NS_FAILED(rv))
+ {
+ return;
+ }
+
+ nsCString resourceURI;
+ rv = msgUrl->GetUri(getter_Copies(resourceURI));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(aOriginatorLocation, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(resourceURI.get(), getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv))
+ {
+ // Maybe we can get a dummy header.
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIMsgHeaderSink> msgHdrSink;
+ rv = msgWindow->GetMsgHeaderSink(getter_AddRefs(msgHdrSink));
+ if (msgHdrSink)
+ rv = msgHdrSink->GetDummyMsgHeader(getter_AddRefs(msgHdr));
+ }
+ }
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIRunnable> event =
+ new RemoteContentNotifierEvent(msgWindow, msgHdr, aContentLocation, aCanOverride);
+ // Post this as an event because it can cause dom mutations, and we
+ // get called at a bad time to be causing dom mutations.
+ if (event)
+ NS_DispatchToCurrentThread(event);
+ }
+}
+
+/**
+ * This function is used to determine if we allow content for a remote message.
+ * If we reject loading remote content, then we'll inform the message window
+ * that this message has remote content (and hence we are not loading it).
+ *
+ * See ShouldAcceptRemoteContentForMsgHdr for the actual decisions that
+ * determine if we are going to allow remote content.
+ */
+void
+nsMsgContentPolicy::ShouldAcceptContentForPotentialMsg(nsIURI *aOriginatorLocation,
+ nsIURI *aContentLocation,
+ int16_t *aDecision)
+{
+ NS_PRECONDITION(*aDecision == nsIContentPolicy::REJECT_REQUEST,
+ "AllowContentForPotentialMessage expects default decision to be reject!");
+
+ // Is it a mailnews url?
+ nsresult rv;
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(aOriginatorLocation,
+ &rv));
+ if (NS_FAILED(rv))
+ {
+ // It isn't a mailnews url - so we accept the load here, and let other
+ // content policies make the decision if we should be loading it or not.
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return;
+ }
+
+ nsCString resourceURI;
+ rv = msgUrl->GetUri(getter_Copies(resourceURI));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(aOriginatorLocation, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(resourceURI.get(), getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv))
+ {
+ // Maybe we can get a dummy header.
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIMsgHeaderSink> msgHdrSink;
+ rv = msgWindow->GetMsgHeaderSink(getter_AddRefs(msgHdrSink));
+ if (msgHdrSink)
+ rv = msgHdrSink->GetDummyMsgHeader(getter_AddRefs(msgHdr));
+ }
+ }
+
+ // Get a decision on whether or not to allow remote content for this message
+ // header.
+ *aDecision = ShouldAcceptRemoteContentForMsgHdr(msgHdr, aOriginatorLocation,
+ aContentLocation);
+
+ // If we're not allowing the remote content, tell the nsIMsgWindow loading
+ // this url that this is the case, so that the UI knows to show the remote
+ // content header bar, so the user can override if they wish.
+ if (*aDecision == nsIContentPolicy::REJECT_REQUEST)
+ {
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIRunnable> event =
+ new RemoteContentNotifierEvent(msgWindow, msgHdr, aContentLocation);
+ // Post this as an event because it can cause dom mutations, and we
+ // get called at a bad time to be causing dom mutations.
+ if (event)
+ NS_DispatchToCurrentThread(event);
+ }
+ }
+}
+
+/**
+ * Content policy logic for compose windows
+ *
+ */
+void nsMsgContentPolicy::ComposeShouldLoad(nsIMsgCompose *aMsgCompose,
+ nsISupports *aRequestingContext,
+ nsIURI *aContentLocation,
+ int16_t *aDecision)
+{
+ NS_PRECONDITION(*aDecision == nsIContentPolicy::REJECT_REQUEST,
+ "ComposeShouldLoad expects default decision to be reject!");
+
+ nsCString originalMsgURI;
+ nsresult rv = aMsgCompose->GetOriginalMsgURI(getter_Copies(originalMsgURI));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ MSG_ComposeType composeType;
+ rv = aMsgCompose->GetType(&composeType);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Only allow remote content for new mail compositions or mailto
+ // Block remote content for all other types (drafts, templates, forwards, replies, etc)
+ // unless there is an associated msgHdr which allows the load, or unless the image is being
+ // added by the user and not the quoted message content...
+ if (composeType == nsIMsgCompType::New ||
+ composeType == nsIMsgCompType::MailToUrl)
+ *aDecision = nsIContentPolicy::ACCEPT;
+ else if (!originalMsgURI.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(originalMsgURI.get(), getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ *aDecision = ShouldAcceptRemoteContentForMsgHdr(msgHdr, nullptr,
+ aContentLocation);
+
+ // Special case image elements. When replying to a message, we want to allow
+ // the user to add remote images to the message. But we don't want remote
+ // images that are a part of the quoted content to load. Hence we block them
+ // while the reply is created (insertingQuotedContent==true), but allow them
+ // later when the user inserts them.
+ if (*aDecision == nsIContentPolicy::REJECT_REQUEST)
+ {
+ bool insertingQuotedContent = true;
+ aMsgCompose->GetInsertingQuotedContent(&insertingQuotedContent);
+ nsCOMPtr<nsIDOMHTMLImageElement> imageElement(do_QueryInterface(aRequestingContext));
+ if (imageElement)
+ {
+ if (!insertingQuotedContent)
+ {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return;
+ }
+
+ // Test whitelist.
+ uint32_t permission;
+ mPermissionManager->TestPermission(aContentLocation, "image", &permission);
+ if (permission == nsIPermissionManager::ALLOW_ACTION)
+ *aDecision = nsIContentPolicy::ACCEPT;
+ }
+ }
+ }
+}
+
+already_AddRefed<nsIMsgCompose> nsMsgContentPolicy::GetMsgComposeForContext(nsISupports *aRequestingContext)
+{
+ nsresult rv;
+
+ nsIDocShell *shell = NS_CP_GetDocShellFromContext(aRequestingContext);
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(do_QueryInterface(shell, &rv));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ rv = docShellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(rootItem));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(rootItem, &rv));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIMsgComposeService> composeService(do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIMsgCompose> msgCompose;
+ // Don't bother checking rv, as GetMsgComposeForDocShell returns NS_ERROR_FAILURE
+ // for not found.
+ composeService->GetMsgComposeForDocShell(docShell,
+ getter_AddRefs(msgCompose));
+ return msgCompose.forget();
+}
+
+nsresult nsMsgContentPolicy::SetDisableItemsOnMailNewsUrlDocshells(
+ nsIURI *aContentLocation, nsISupports *aRequestingContext)
+{
+ // XXX if this class changes so that this method can be called from
+ // ShouldProcess, and if it's possible for this to be null when called from
+ // ShouldLoad, but not in the corresponding ShouldProcess call,
+ // we need to re-think the assumptions underlying this code.
+
+ // If there's no docshell to get to, there's nowhere for the JavaScript to
+ // run, so we're already safe and don't need to disable anything.
+ if (!aRequestingContext) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ bool isAllowedContent = !ShouldBlockUnexposedProtocol(aContentLocation);
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(aContentLocation, &rv);
+ if (NS_FAILED(rv) && !isAllowedContent) {
+ // If it's not a mailnews url or allowed content url (http[s]|file) then
+ // bail; otherwise set whether js and plugins are allowed.
+ return NS_OK;
+ }
+
+ // since NS_CP_GetDocShellFromContext returns the containing docshell rather
+ // than the contained one we need, we can't use that here, so...
+ nsCOMPtr<nsIFrameLoaderOwner> flOwner = do_QueryInterface(aRequestingContext,
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFrameLoader> frameLoader;
+ rv = flOwner->GetFrameLoaderXPCOM(getter_AddRefs(frameLoader));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(frameLoader, NS_ERROR_INVALID_POINTER);
+
+ nsCOMPtr<nsIDocShell> docShell;
+ rv = frameLoader->GetDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(do_QueryInterface(docShell, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // what sort of docshell is this?
+ int32_t itemType;
+ rv = docshellTreeItem->GetItemType(&itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we're only worried about policy settings in content docshells
+ if (itemType != nsIDocShellTreeItem::typeContent) {
+ return NS_OK;
+ }
+
+ if (!isAllowedContent) {
+ // Disable JavaScript on message URLs.
+ rv = docShell->SetAllowJavascript(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = docShell->SetAllowContentRetargetingOnChildren(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = docShell->SetAllowPlugins(mAllowPlugins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t sandboxFlags;
+ rv = docShell->GetSandboxFlags(&sandboxFlags);
+ sandboxFlags |= SANDBOXED_FORMS;
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = docShell->SetSandboxFlags(sandboxFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // JavaScript and plugins are allowed on non-message URLs.
+ rv = docShell->SetAllowJavascript(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = docShell->SetAllowContentRetargetingOnChildren(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = docShell->SetAllowPlugins(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Gets the root docshell from a requesting context.
+ */
+nsresult
+nsMsgContentPolicy::GetRootDocShellForContext(nsISupports *aRequestingContext,
+ nsIDocShell **aDocShell)
+{
+ NS_ENSURE_ARG_POINTER(aRequestingContext);
+ nsresult rv;
+
+ nsIDocShell *shell = NS_CP_GetDocShellFromContext(aRequestingContext);
+ nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(do_QueryInterface(shell, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ rv = docshellTreeItem->GetRootTreeItem(getter_AddRefs(rootItem));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(rootItem, aDocShell);
+}
+
+/**
+ * Gets the originating URI that started off a set of requests, accounting
+ * for multiple iframes.
+ *
+ * Navigates up the docshell tree from aRequestingContext and finds the
+ * highest parent with the same type docshell as aRequestingContext, then
+ * returns the URI associated with that docshell.
+ */
+nsresult
+nsMsgContentPolicy::GetOriginatingURIForContext(nsISupports *aRequestingContext,
+ nsIURI **aURI)
+{
+ NS_ENSURE_ARG_POINTER(aRequestingContext);
+ nsresult rv;
+
+ nsIDocShell *shell = NS_CP_GetDocShellFromContext(aRequestingContext);
+ nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(do_QueryInterface(shell, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ rv = docshellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(rootItem));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIWebNavigation> webNavigation(do_QueryInterface(rootItem, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return webNavigation->GetCurrentURI(aURI);
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::ShouldProcess(uint32_t aContentType,
+ nsIURI *aContentLocation,
+ nsIURI *aRequestingLocation,
+ nsISupports *aRequestingContext,
+ const nsACString &aMimeGuess,
+ nsISupports *aExtra,
+ nsIPrincipal *aRequestPrincipal,
+ int16_t *aDecision)
+{
+ // XXX Returning ACCEPT is presumably only a reasonable thing to do if we
+ // think that ShouldLoad is going to catch all possible cases (i.e. that
+ // everything we use to make decisions is going to be available at
+ // ShouldLoad time, and not only become available in time for ShouldProcess).
+ // Do we think that's actually the case?
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgContentPolicy::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
+{
+ if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic))
+ {
+ NS_LossyConvertUTF16toASCII pref(aData);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIPrefBranch> prefBranchInt = do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (pref.Equals(kBlockRemoteImages))
+ prefBranchInt->GetBoolPref(kBlockRemoteImages, &mBlockRemoteImages);
+ if (pref.Equals(kAllowPlugins))
+ prefBranchInt->GetBoolPref(kAllowPlugins, &mAllowPlugins);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * We implement the nsIWebProgressListener interface in order to enforce
+ * settings at onLocationChange time.
+ */
+NS_IMETHODIMP
+nsMsgContentPolicy::OnStateChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::OnProgressChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::OnLocationChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, nsIURI *aLocation,
+ uint32_t aFlags)
+{
+ nsresult rv;
+
+ // If anything goes wrong and/or there's no docshell associated with this
+ // request, just give up. The behavior ends up being "don't consider
+ // re-enabling JS on the docshell", which is the safe thing to do (and if
+ // the problem was that there's no docshell, that means that there was
+ // nowhere for any JavaScript to run, so we're already safe
+
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress, &rv);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+#ifdef DEBUG
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIDocShell> docShell2;
+ NS_QueryNotificationCallbacks(channel, docShell2);
+ NS_ASSERTION(docShell == docShell2, "aWebProgress and channel callbacks"
+ " do not point to the same docshell");
+ }
+#endif
+
+ nsCOMPtr<nsIMsgMessageUrl> messageUrl = do_QueryInterface(aLocation, &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ // Disable javascript on message URLs.
+ rv = docShell->SetAllowJavascript(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to set javascript disabled on docShell");
+ // Also disable plugins if the preference requires it.
+ rv = docShell->SetAllowPlugins(mAllowPlugins);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to set plugins disabled on docShell");
+ }
+ else {
+ // Disable javascript and plugins are allowed on non-message URLs.
+ rv = docShell->SetAllowJavascript(true);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to set javascript allowed on docShell");
+ rv = docShell->SetAllowPlugins(true);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to set plugins allowed on docShell");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::OnStatusChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, nsresult aStatus,
+ const char16_t *aMessage)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, uint32_t aState)
+{
+ return NS_OK;
+}
+
+/**
+ * Implementation of nsIMsgContentPolicy
+ *
+ */
+NS_IMETHODIMP
+nsMsgContentPolicy::AddExposedProtocol(const nsACString &aScheme)
+{
+ if (mCustomExposedProtocols.Contains(nsCString(aScheme)))
+ return NS_OK;
+
+ mCustomExposedProtocols.AppendElement(aScheme);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::RemoveExposedProtocol(const nsACString &aScheme)
+{
+ mCustomExposedProtocols.RemoveElement(nsCString(aScheme));
+
+ return NS_OK;
+}
+
diff --git a/mailnews/base/src/nsMsgContentPolicy.h b/mailnews/base/src/nsMsgContentPolicy.h
new file mode 100644
index 000000000..745708683
--- /dev/null
+++ b/mailnews/base/src/nsMsgContentPolicy.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**********************************************************************************
+ * nsMsgContentPolicy enforces the specified content policy on images, js, plugins, etc.
+ * This is the class used to determine what elements in a message should be loaded.
+ *
+ * nsMsgCookiePolicy enforces our cookie policy for mail and RSS messages.
+ ***********************************************************************************/
+
+#ifndef _nsMsgContentPolicy_H_
+#define _nsMsgContentPolicy_H_
+
+#include "nsIContentPolicy.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsStringGlue.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIWebProgressListener.h"
+#include "nsIMsgCompose.h"
+#include "nsIDocShell.h"
+#include "nsIPermissionManager.h"
+#include "nsIMsgContentPolicy.h"
+#include "nsTArray.h"
+
+/* DBFCFDF0-4489-4faa-8122-190FD1EFA16C */
+#define NS_MSGCONTENTPOLICY_CID \
+{ 0xdbfcfdf0, 0x4489, 0x4faa, { 0x81, 0x22, 0x19, 0xf, 0xd1, 0xef, 0xa1, 0x6c } }
+
+#define NS_MSGCONTENTPOLICY_CONTRACTID "@mozilla.org/messenger/content-policy;1"
+
+class nsIMsgDBHdr;
+class nsIDocShell;
+
+class nsMsgContentPolicy : public nsIContentPolicy,
+ public nsIObserver,
+ public nsIWebProgressListener,
+ public nsIMsgContentPolicy,
+ public nsSupportsWeakReference
+{
+public:
+ nsMsgContentPolicy();
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIMSGCONTENTPOLICY
+
+protected:
+ virtual ~nsMsgContentPolicy();
+
+ bool mBlockRemoteImages;
+ bool mAllowPlugins;
+ nsCString mTrustedMailDomains;
+ nsCOMPtr<nsIPermissionManager> mPermissionManager;
+
+ bool IsTrustedDomain(nsIURI * aContentLocation);
+ bool IsSafeRequestingLocation(nsIURI *aRequestingLocation);
+ bool IsExposedProtocol(nsIURI *aContentLocation);
+ bool IsExposedChromeProtocol(nsIURI *aContentLocation);
+ bool ShouldBlockUnexposedProtocol(nsIURI *aContentLocation);
+
+ bool ShouldAcceptRemoteContentForSender(nsIMsgDBHdr *aMsgHdr);
+ int16_t ShouldAcceptRemoteContentForMsgHdr(nsIMsgDBHdr *aMsgHdr,
+ nsIURI *aRequestingLocation,
+ nsIURI *aContentLocation);
+ void NotifyContentWasBlocked(nsIURI *aOriginatorLocation,
+ nsIURI *aContentLocation,
+ bool aCanOverride);
+ void ShouldAcceptContentForPotentialMsg(nsIURI *aOriginatorLocation,
+ nsIURI *aContentLocation,
+ int16_t *aDecision);
+ void ComposeShouldLoad(nsIMsgCompose *aMsgCompose,
+ nsISupports *aRequestingContext,
+ nsIURI *aContentLocation, int16_t *aDecision);
+ already_AddRefed<nsIMsgCompose> GetMsgComposeForContext(nsISupports *aRequestingContext);
+
+ nsresult GetRootDocShellForContext(nsISupports *aRequestingContext,
+ nsIDocShell **aDocShell);
+ nsresult GetOriginatingURIForContext(nsISupports *aRequestingContext,
+ nsIURI **aURI);
+ nsresult SetDisableItemsOnMailNewsUrlDocshells(nsIURI *aContentLocation,
+ nsISupports *aRequestingContext);
+
+ nsTArray<nsCString> mCustomExposedProtocols;
+};
+
+#endif // _nsMsgContentPolicy_H_
diff --git a/mailnews/base/src/nsMsgCopyService.cpp b/mailnews/base/src/nsMsgCopyService.cpp
new file mode 100644
index 000000000..e7b79bd56
--- /dev/null
+++ b/mailnews/base/src/nsMsgCopyService.cpp
@@ -0,0 +1,708 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgCopyService.h"
+#include "nsCOMArray.h"
+#include "nspr.h"
+#include "nsIFile.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Logging.h"
+
+static PRLogModuleInfo *gCopyServiceLog;
+
+// ******************** nsCopySource ******************
+//
+
+nsCopySource::nsCopySource() : m_processed(false)
+{
+ MOZ_COUNT_CTOR(nsCopySource);
+ m_messageArray = do_CreateInstance(NS_ARRAY_CONTRACTID);
+}
+
+nsCopySource::nsCopySource(nsIMsgFolder* srcFolder) :
+ m_processed(false)
+{
+ MOZ_COUNT_CTOR(nsCopySource);
+ m_messageArray = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ m_msgFolder = srcFolder;
+}
+
+nsCopySource::~nsCopySource()
+{
+ MOZ_COUNT_DTOR(nsCopySource);
+}
+
+void nsCopySource::AddMessage(nsIMsgDBHdr* aMsg)
+{
+ m_messageArray->AppendElement(aMsg, false);
+}
+
+// ************ nsCopyRequest *****************
+//
+
+nsCopyRequest::nsCopyRequest() :
+ m_requestType(nsCopyMessagesType),
+ m_isMoveOrDraftOrTemplate(false),
+ m_processed(false),
+ m_newMsgFlags(0)
+{
+ MOZ_COUNT_CTOR(nsCopyRequest);
+}
+
+nsCopyRequest::~nsCopyRequest()
+{
+ MOZ_COUNT_DTOR(nsCopyRequest);
+
+ int32_t j = m_copySourceArray.Length();
+ while(j-- > 0)
+ delete m_copySourceArray.ElementAt(j);
+}
+
+nsresult
+nsCopyRequest::Init(nsCopyRequestType type, nsISupports* aSupport,
+ nsIMsgFolder* dstFolder,
+ bool bVal, uint32_t newMsgFlags,
+ const nsACString &newMsgKeywords,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* msgWindow, bool allowUndo)
+{
+ nsresult rv = NS_OK;
+ m_requestType = type;
+ m_srcSupport = aSupport;
+ m_dstFolder = dstFolder;
+ m_isMoveOrDraftOrTemplate = bVal;
+ m_allowUndo = allowUndo;
+ m_newMsgFlags = newMsgFlags;
+ m_newMsgKeywords = newMsgKeywords;
+
+ if (listener)
+ m_listener = listener;
+ if (msgWindow)
+ {
+ m_msgWindow = msgWindow;
+ if (m_allowUndo)
+ msgWindow->GetTransactionManager(getter_AddRefs(m_txnMgr));
+ }
+ if (type == nsCopyFoldersType)
+ {
+ // To support multiple copy folder operations to the same destination, we
+ // need to save the leaf name of the src file spec so that FindRequest() is
+ // able to find the right request when copy finishes.
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(aSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString folderName;
+ rv = srcFolder->GetName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_dstFolderName = folderName;
+ }
+
+ return rv;
+}
+
+nsCopySource*
+nsCopyRequest::AddNewCopySource(nsIMsgFolder* srcFolder)
+{
+ nsCopySource* newSrc = new nsCopySource(srcFolder);
+ if (newSrc)
+ {
+ m_copySourceArray.AppendElement(newSrc);
+ if (srcFolder == m_dstFolder)
+ newSrc->m_processed = true;
+ }
+ return newSrc;
+}
+
+// ************* nsMsgCopyService ****************
+//
+
+
+nsMsgCopyService::nsMsgCopyService()
+{
+ gCopyServiceLog = PR_NewLogModule("MsgCopyService");
+}
+
+nsMsgCopyService::~nsMsgCopyService()
+{
+ int32_t i = m_copyRequests.Length();
+
+ while (i-- > 0)
+ ClearRequest(m_copyRequests.ElementAt(i), NS_ERROR_FAILURE);
+}
+
+void nsMsgCopyService::LogCopyCompletion(nsISupports *aSrc, nsIMsgFolder *aDest)
+{
+ nsCString srcFolderUri, destFolderUri;
+ nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(aSrc));
+ if (srcFolder)
+ srcFolder->GetURI(srcFolderUri);
+ aDest->GetURI(destFolderUri);
+ MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Info,
+ ("NotifyCompletion - src %s dest %s\n",
+ srcFolderUri.get(), destFolderUri.get()));
+}
+
+void nsMsgCopyService::LogCopyRequest(const char *logMsg, nsCopyRequest* aRequest)
+{
+ nsCString srcFolderUri, destFolderUri;
+ nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(aRequest->m_srcSupport));
+ if (srcFolder)
+ srcFolder->GetURI(srcFolderUri);
+ aRequest->m_dstFolder->GetURI(destFolderUri);
+ uint32_t numMsgs = 0;
+ if (aRequest->m_requestType == nsCopyMessagesType &&
+ aRequest->m_copySourceArray.Length() > 0 &&
+ aRequest->m_copySourceArray[0]->m_messageArray)
+ aRequest->m_copySourceArray[0]->m_messageArray->GetLength(&numMsgs);
+ MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Info,
+ ("request %lx %s - src %s dest %s numItems %d type=%d",
+ aRequest, logMsg, srcFolderUri.get(),
+ destFolderUri.get(), numMsgs, aRequest->m_requestType));
+}
+
+nsresult
+nsMsgCopyService::ClearRequest(nsCopyRequest* aRequest, nsresult rv)
+{
+ if (aRequest)
+ {
+ if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
+ LogCopyRequest(NS_SUCCEEDED(rv) ? "Clearing OK request"
+ : "Clearing failed request", aRequest);
+
+ // Send notifications to nsIMsgFolderListeners
+ if (NS_SUCCEEDED(rv) && aRequest->m_requestType == nsCopyFoldersType)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ {
+ bool hasListeners;
+ notifier->GetHasListeners(&hasListeners);
+ if (hasListeners)
+ {
+ // Iterate over the copy sources and append their message arrays to this mutable array
+ // or in the case of folders, the source folder.
+ int32_t cnt, i;
+ cnt = aRequest->m_copySourceArray.Length();
+ for (i = 0; i < cnt; i++)
+ {
+ nsCopySource *copySource = aRequest->m_copySourceArray.ElementAt(i);
+ notifier->NotifyFolderMoveCopyCompleted(aRequest->m_isMoveOrDraftOrTemplate, copySource->m_msgFolder, aRequest->m_dstFolder);
+ }
+ }
+ }
+ }
+
+ // undo stuff
+ if (aRequest->m_allowUndo &&
+ aRequest->m_copySourceArray.Length() > 1 &&
+ aRequest->m_txnMgr)
+ aRequest->m_txnMgr->EndBatch(false);
+
+ m_copyRequests.RemoveElement(aRequest);
+ if (aRequest->m_listener)
+ aRequest->m_listener->OnStopCopy(rv);
+ delete aRequest;
+ }
+
+ return rv;
+}
+
+nsresult
+nsMsgCopyService::QueueRequest(nsCopyRequest* aRequest, bool *aCopyImmediately)
+{
+ NS_ENSURE_ARG_POINTER(aRequest);
+ NS_ENSURE_ARG_POINTER(aCopyImmediately);
+ *aCopyImmediately = true;
+ nsCopyRequest* copyRequest;
+
+ uint32_t cnt = m_copyRequests.Length();
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ copyRequest = m_copyRequests.ElementAt(i);
+ if (aRequest->m_requestType == nsCopyFoldersType)
+ {
+ // For copy folder, see if both destination folder (root)
+ // (ie, Local Folder) and folder name (ie, abc) are the same.
+ if (copyRequest->m_dstFolderName == aRequest->m_dstFolderName &&
+ copyRequest->m_dstFolder.get() == aRequest->m_dstFolder.get())
+ {
+ *aCopyImmediately = false;
+ break;
+ }
+ }
+ else if (copyRequest->m_dstFolder.get() == aRequest->m_dstFolder.get()) //if dst are same and we already have a request, we cannot copy immediately
+ {
+ *aCopyImmediately = false;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgCopyService::DoCopy(nsCopyRequest* aRequest)
+{
+ NS_ENSURE_ARG(aRequest);
+ bool copyImmediately;
+ QueueRequest(aRequest, &copyImmediately);
+ m_copyRequests.AppendElement(aRequest);
+ if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
+ LogCopyRequest(copyImmediately ? "DoCopy" : "QueueRequest", aRequest);
+
+ // if no active request for this dest folder then we can copy immediately
+ if (copyImmediately)
+ return DoNextCopy();
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgCopyService::DoNextCopy()
+{
+ nsresult rv = NS_OK;
+ nsCopyRequest* copyRequest = nullptr;
+ nsCopySource* copySource = nullptr;
+ uint32_t i, j, scnt;
+
+ uint32_t cnt = m_copyRequests.Length();
+ if (cnt > 0)
+ {
+ nsCOMArray<nsIMsgFolder> activeTargets;
+
+ // ** jt -- always FIFO
+ for (i = 0; i < cnt; i++)
+ {
+ copyRequest = m_copyRequests.ElementAt(i);
+ copySource = nullptr;
+ scnt = copyRequest->m_copySourceArray.Length();
+ if (!copyRequest->m_processed)
+ {
+ // if the target folder of this request already has an active
+ // copy request, skip this request for now.
+ if (activeTargets.ContainsObject(copyRequest->m_dstFolder))
+ {
+ copyRequest = nullptr;
+ continue;
+ }
+ if (scnt <= 0)
+ goto found; // must be CopyFileMessage
+ for (j = 0; j < scnt; j++)
+ {
+ copySource = copyRequest->m_copySourceArray.ElementAt(j);
+ if (!copySource->m_processed)
+ goto found;
+ }
+ if (j >= scnt) // all processed set the value
+ copyRequest->m_processed = true;
+ }
+ if (copyRequest->m_processed) // keep track of folders actively getting copied to.
+ activeTargets.AppendObject(copyRequest->m_dstFolder);
+ }
+ found:
+ if (copyRequest && !copyRequest->m_processed)
+ {
+ if (copyRequest->m_listener)
+ copyRequest->m_listener->OnStartCopy();
+ if (copyRequest->m_requestType == nsCopyMessagesType &&
+ copySource)
+ {
+ copySource->m_processed = true;
+ rv = copyRequest->m_dstFolder->CopyMessages
+ (copySource->m_msgFolder, copySource->m_messageArray,
+ copyRequest->m_isMoveOrDraftOrTemplate,
+ copyRequest->m_msgWindow, copyRequest->m_listener, false, copyRequest->m_allowUndo); //isFolder operation false
+
+ }
+ else if (copyRequest->m_requestType == nsCopyFoldersType)
+ {
+ NS_ENSURE_STATE(copySource);
+ copySource->m_processed = true;
+ rv = copyRequest->m_dstFolder->CopyFolder
+ (copySource->m_msgFolder,
+ copyRequest->m_isMoveOrDraftOrTemplate,
+ copyRequest->m_msgWindow, copyRequest->m_listener);
+ // If it's a copy folder operation and the destination
+ // folder already exists, CopyFolder() returns an error w/o sending
+ // a completion notification, so clear it here.
+ if (NS_FAILED(rv))
+ ClearRequest(copyRequest, rv);
+
+ }
+ else if (copyRequest->m_requestType == nsCopyFileMessageType)
+ {
+ nsCOMPtr<nsIFile> aFile(do_QueryInterface(copyRequest->m_srcSupport, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ // ** in case of saving draft/template; the very first
+ // time we may not have the original message to replace
+ // with; if we do we shall have an instance of copySource
+ nsCOMPtr<nsIMsgDBHdr> aMessage;
+ if (copySource)
+ {
+ aMessage = do_QueryElementAt(copySource->m_messageArray,
+ 0, &rv);
+ copySource->m_processed = true;
+ }
+ copyRequest->m_processed = true;
+ rv = copyRequest->m_dstFolder->CopyFileMessage
+ (aFile, aMessage,
+ copyRequest->m_isMoveOrDraftOrTemplate,
+ copyRequest->m_newMsgFlags,
+ copyRequest->m_newMsgKeywords,
+ copyRequest->m_msgWindow,
+ copyRequest->m_listener);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+/**
+ * Find a request in m_copyRequests which matches the passed in source
+ * and destination folders.
+ *
+ * @param aSupport the iSupports of the source folder.
+ * @param dstFolder the destination folder of the copy request.
+ */
+nsCopyRequest*
+nsMsgCopyService::FindRequest(nsISupports* aSupport,
+ nsIMsgFolder* dstFolder)
+{
+ nsCopyRequest* copyRequest = nullptr;
+ uint32_t cnt = m_copyRequests.Length();
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ copyRequest = m_copyRequests.ElementAt(i);
+ if (copyRequest->m_requestType == nsCopyFoldersType)
+ {
+ // If the src is different then check next request.
+ if (copyRequest->m_srcSupport.get() != aSupport)
+ {
+ copyRequest = nullptr;
+ continue;
+ }
+
+ // See if the parent of the copied folder is the same as the one when the request was made.
+ // Note if the destination folder is already a server folder then no need to get parent.
+ nsCOMPtr <nsIMsgFolder> parentMsgFolder;
+ nsresult rv = NS_OK;
+ bool isServer=false;
+ dstFolder->GetIsServer(&isServer);
+ if (!isServer)
+ rv = dstFolder->GetParent(getter_AddRefs(parentMsgFolder));
+ if ((NS_FAILED(rv)) || (!parentMsgFolder && !isServer) || (copyRequest->m_dstFolder.get() != parentMsgFolder))
+ {
+ copyRequest = nullptr;
+ continue;
+ }
+
+ // Now checks if the folder name is the same.
+ nsString folderName;
+ rv = dstFolder->GetName(folderName);
+ if (NS_FAILED(rv))
+ {
+ copyRequest = nullptr;
+ continue;
+ }
+
+ if (copyRequest->m_dstFolderName == folderName)
+ break;
+ }
+ else if (copyRequest->m_srcSupport.get() == aSupport &&
+ copyRequest->m_dstFolder.get() == dstFolder)
+ break;
+ else
+ copyRequest = nullptr;
+ }
+
+ return copyRequest;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgCopyService, nsIMsgCopyService)
+
+NS_IMETHODIMP
+nsMsgCopyService::CopyMessages(nsIMsgFolder* srcFolder, /* UI src folder */
+ nsIArray* messages,
+ nsIMsgFolder* dstFolder,
+ bool isMove,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* window,
+ bool allowUndo)
+{
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ NS_ENSURE_ARG_POINTER(messages);
+ NS_ENSURE_ARG_POINTER(dstFolder);
+
+ MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Debug, ("CopyMessages"));
+
+ if (srcFolder == dstFolder)
+ {
+ NS_ERROR("src and dest folders for msg copy can't be the same");
+ return NS_ERROR_FAILURE;
+ }
+ nsCopyRequest* copyRequest;
+ nsCopySource* copySource = nullptr;
+ nsCOMArray<nsIMsgDBHdr> msgArray;
+ uint32_t cnt;
+ nsCOMPtr<nsIMsgDBHdr> msg;
+ nsCOMPtr<nsIMsgFolder> curFolder;
+ nsCOMPtr<nsISupports> aSupport;
+ nsresult rv;
+
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // make sure dest folder exists
+ // and has proper flags, before we start copying?
+
+ // bail early if nothing to do
+ messages->GetLength(&cnt);
+ if (!cnt)
+ {
+ if (listener)
+ {
+ listener->OnStartCopy();
+ listener->OnStopCopy(NS_OK);
+ }
+ return NS_OK;
+ }
+
+ copyRequest = new nsCopyRequest();
+ if (!copyRequest)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ aSupport = do_QueryInterface(srcFolder, &rv);
+
+ rv = copyRequest->Init(nsCopyMessagesType, aSupport, dstFolder, isMove,
+ 0 /* new msg flags, not used */, EmptyCString(),
+ listener, window, allowUndo);
+ if (NS_FAILED(rv))
+ goto done;
+
+ if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
+ LogCopyRequest("CopyMessages request", copyRequest);
+
+ // duplicate the message array so we could sort the messages by it's
+ // folder easily
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> currMsg = do_QueryElementAt(messages, i);
+ msgArray.AppendObject(currMsg);
+ }
+
+ cnt = msgArray.Count();
+
+ while (cnt-- > 0)
+ {
+ msg = msgArray[cnt];
+ rv = msg->GetFolder(getter_AddRefs(curFolder));
+
+ if (NS_FAILED(rv))
+ goto done;
+ if (!copySource)
+ {
+ copySource = copyRequest->AddNewCopySource(curFolder);
+ if (!copySource)
+ {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ }
+
+ if (curFolder == copySource->m_msgFolder)
+ {
+ copySource->AddMessage(msg);
+ msgArray.RemoveObjectAt(cnt);
+ }
+
+ if (cnt == 0)
+ {
+ cnt = msgArray.Count();
+ if (cnt > 0)
+ copySource = nullptr; // * force to create a new one and
+ // * continue grouping the messages
+ }
+ }
+
+ // undo stuff
+ if (NS_SUCCEEDED(rv) && copyRequest->m_allowUndo && copyRequest->m_copySourceArray.Length() > 1 &&
+ copyRequest->m_txnMgr)
+ copyRequest->m_txnMgr->BeginBatch(nullptr);
+
+done:
+
+ if (NS_FAILED(rv))
+ delete copyRequest;
+ else
+ rv = DoCopy(copyRequest);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCopyService::CopyFolders(nsIArray* folders,
+ nsIMsgFolder* dstFolder,
+ bool isMove,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* window)
+{
+ NS_ENSURE_ARG_POINTER(folders);
+ NS_ENSURE_ARG_POINTER(dstFolder);
+ nsCopyRequest* copyRequest;
+ nsCopySource* copySource = nullptr;
+ nsresult rv;
+ uint32_t cnt;
+ nsCOMPtr<nsIMsgFolder> curFolder;
+ nsCOMPtr<nsISupports> support;
+
+ rv = folders->GetLength(&cnt); //if cnt is zero it cannot to get this point, will be detected earlier
+ if (cnt > 1)
+ NS_ASSERTION((NS_SUCCEEDED(rv)),"More than one folders to copy");
+
+ support = do_QueryElementAt(folders, 0);
+
+ copyRequest = new nsCopyRequest();
+ if (!copyRequest) return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = copyRequest->Init(nsCopyFoldersType, support, dstFolder,
+ isMove, 0 /* new msg flags, not used */ , EmptyCString(), listener, window, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ curFolder = do_QueryInterface(support, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ copySource = copyRequest->AddNewCopySource(curFolder);
+ if (!copySource)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+
+ if (NS_FAILED(rv))
+ {
+ delete copyRequest;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ rv = DoCopy(copyRequest);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCopyService::CopyFileMessage(nsIFile* file,
+ nsIMsgFolder* dstFolder,
+ nsIMsgDBHdr* msgToReplace,
+ bool isDraft,
+ uint32_t aMsgFlags,
+ const nsACString &aNewMsgKeywords,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* window)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ nsCopyRequest* copyRequest;
+ nsCopySource* copySource = nullptr;
+ nsCOMPtr<nsISupports> fileSupport;
+ nsCOMPtr<nsITransactionManager> txnMgr;
+
+ NS_ENSURE_ARG_POINTER(file);
+ NS_ENSURE_ARG_POINTER(dstFolder);
+
+ if (window)
+ window->GetTransactionManager(getter_AddRefs(txnMgr));
+ copyRequest = new nsCopyRequest();
+ if (!copyRequest) return rv;
+ fileSupport = do_QueryInterface(file, &rv);
+ if (NS_FAILED(rv)) goto done;
+
+ rv = copyRequest->Init(nsCopyFileMessageType, fileSupport, dstFolder,
+ isDraft, aMsgFlags, aNewMsgKeywords, listener, window, false);
+ if (NS_FAILED(rv)) goto done;
+
+ if (msgToReplace)
+ {
+ // The actual source of the message is a file not a folder, but
+ // we still need an nsCopySource to reference the old message header
+ // which will be used to recover message metadata.
+ copySource = copyRequest->AddNewCopySource(nullptr);
+ if (!copySource)
+ {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ copySource->AddMessage(msgToReplace);
+ }
+
+done:
+ if (NS_FAILED(rv))
+ {
+ delete copyRequest;
+ }
+ else
+ {
+ rv = DoCopy(copyRequest);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCopyService::NotifyCompletion(nsISupports* aSupport,
+ nsIMsgFolder* dstFolder,
+ nsresult result)
+{
+ if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
+ LogCopyCompletion(aSupport, dstFolder);
+ nsCopyRequest* copyRequest = nullptr;
+ uint32_t numOrigRequests = m_copyRequests.Length();
+ do
+ {
+ // loop for copy requests, because if we do a cross server folder copy,
+ // we'll have a copy request for the folder copy, which will in turn
+ // generate a copy request for the messages in the folder, which
+ // will have the same src support.
+ copyRequest = FindRequest(aSupport, dstFolder);
+
+ if (copyRequest)
+ {
+ // ClearRequest can cause a new request to get added to m_copyRequests
+ // with matching source and dest folders if the copy listener starts
+ // a new copy. We want to ignore any such request here, because it wasn't
+ // the one that was completed. So we keep track of how many original
+ // requests there were.
+ if (m_copyRequests.IndexOf(copyRequest) >= numOrigRequests)
+ break;
+ // check if this copy request is done by making sure all the
+ // sources have been processed.
+ int32_t sourceIndex, sourceCount;
+ sourceCount = copyRequest->m_copySourceArray.Length();
+ for (sourceIndex = 0; sourceIndex < sourceCount;)
+ {
+ if (!(copyRequest->m_copySourceArray.ElementAt(sourceIndex))->m_processed)
+ break;
+ sourceIndex++;
+ }
+ // if all sources processed, mark the request as processed
+ if (sourceIndex >= sourceCount)
+ copyRequest->m_processed = true;
+ // if this request is done, or failed, clear it.
+ if (copyRequest->m_processed || NS_FAILED(result))
+ {
+ ClearRequest(copyRequest, result);
+ numOrigRequests--;
+ }
+ else
+ break;
+ }
+ else
+ break;
+ }
+ while (copyRequest);
+
+ return DoNextCopy();
+}
+
diff --git a/mailnews/base/src/nsMsgCopyService.h b/mailnews/base/src/nsMsgCopyService.h
new file mode 100644
index 000000000..8df9ccff5
--- /dev/null
+++ b/mailnews/base/src/nsMsgCopyService.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgCopyService_h__
+#define nsMsgCopyService_h__
+
+#include "nscore.h"
+#include "nsIMsgCopyService.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgWindow.h"
+#include "nsIMutableArray.h"
+#include "nsITransactionManager.h"
+#include "nsTArray.h"
+
+typedef enum _nsCopyRequestType
+{
+ nsCopyMessagesType = 0x0,
+ nsCopyFileMessageType = 0x1,
+ nsCopyFoldersType = 0x2
+} nsCopyRequestType;
+
+class nsCopyRequest;
+
+class nsCopySource
+{
+public:
+ nsCopySource();
+ explicit nsCopySource(nsIMsgFolder* srcFolder);
+ ~nsCopySource();
+ void AddMessage(nsIMsgDBHdr* aMsg);
+
+ nsCOMPtr<nsIMsgFolder> m_msgFolder;
+ nsCOMPtr<nsIMutableArray> m_messageArray;
+ bool m_processed;
+};
+
+class nsCopyRequest
+{
+public:
+ nsCopyRequest();
+ ~nsCopyRequest();
+
+ nsresult Init(nsCopyRequestType type, nsISupports* aSupport,
+ nsIMsgFolder* dstFolder,
+ bool bVal, uint32_t newMsgFlags,
+ const nsACString &newMsgKeywords,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow *msgWindow, bool allowUndo);
+ nsCopySource* AddNewCopySource(nsIMsgFolder* srcFolder);
+
+ nsCOMPtr<nsISupports> m_srcSupport; // ui source folder or file spec
+ nsCOMPtr<nsIMsgFolder> m_dstFolder;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ nsCOMPtr<nsIMsgCopyServiceListener> m_listener;
+ nsCOMPtr<nsITransactionManager> m_txnMgr;
+ nsCopyRequestType m_requestType;
+ bool m_isMoveOrDraftOrTemplate;
+ bool m_allowUndo;
+ bool m_processed;
+ uint32_t m_newMsgFlags;
+ nsCString m_newMsgKeywords;
+ nsString m_dstFolderName; // used for copy folder.
+ nsTArray<nsCopySource*> m_copySourceArray; // array of nsCopySource
+};
+
+class nsMsgCopyService : public nsIMsgCopyService
+{
+public:
+ nsMsgCopyService();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIMSGCOPYSERVICE
+
+private:
+ virtual ~nsMsgCopyService();
+
+ nsresult ClearRequest(nsCopyRequest* aRequest, nsresult rv);
+ nsresult DoCopy(nsCopyRequest* aRequest);
+ nsresult DoNextCopy();
+ nsCopyRequest* FindRequest(nsISupports* aSupport, nsIMsgFolder* dstFolder);
+ nsresult QueueRequest(nsCopyRequest* aRequest, bool *aCopyImmediately);
+ void LogCopyCompletion(nsISupports *aSrc, nsIMsgFolder *aDest);
+ void LogCopyRequest(const char *logMsg, nsCopyRequest* aRequest);
+
+ nsTArray<nsCopyRequest*> m_copyRequests;
+};
+
+
+#endif
diff --git a/mailnews/base/src/nsMsgDBView.cpp b/mailnews/base/src/nsMsgDBView.cpp
new file mode 100644
index 000000000..6793ba95a
--- /dev/null
+++ b/mailnews/base/src/nsMsgDBView.cpp
@@ -0,0 +1,8095 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "prmem.h"
+#include "nsArrayUtils.h"
+#include "nsIMsgCustomColumnHandler.h"
+#include "nsMsgDBView.h"
+#include "nsISupports.h"
+#include "nsIMsgFolder.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgFolder.h"
+#include "MailNewsTypes2.h"
+#include "nsMsgUtils.h"
+#include "nsQuickSort.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsImapCore.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIDOMElement.h"
+#include "nsDateTimeFormatCID.h"
+#include "nsMsgMimeCID.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgCopyService.h"
+#include "nsMsgBaseCID.h"
+#include "nsISpamSettings.h"
+#include "nsIMsgAccountManager.h"
+#include "nsITreeColumns.h"
+#include "nsTextFormatter.h"
+#include "nsIMutableArray.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+#include "nsMsgDBCID.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "nsIAbManager.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbCard.h"
+#include "mozilla/Services.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsTArray.h"
+
+using namespace mozilla::mailnews;
+nsrefcnt nsMsgDBView::gInstanceCount = 0;
+
+nsIAtom * nsMsgDBView::kJunkMsgAtom = nullptr;
+nsIAtom * nsMsgDBView::kNotJunkMsgAtom = nullptr;
+
+char16_t * nsMsgDBView::kHighestPriorityString = nullptr;
+char16_t * nsMsgDBView::kHighPriorityString = nullptr;
+char16_t * nsMsgDBView::kLowestPriorityString = nullptr;
+char16_t * nsMsgDBView::kLowPriorityString = nullptr;
+char16_t * nsMsgDBView::kNormalPriorityString = nullptr;
+char16_t * nsMsgDBView::kReadString = nullptr;
+char16_t * nsMsgDBView::kRepliedString = nullptr;
+char16_t * nsMsgDBView::kForwardedString = nullptr;
+char16_t * nsMsgDBView::kNewString = nullptr;
+
+nsDateFormatSelector nsMsgDBView::m_dateFormatDefault = kDateFormatShort;
+nsDateFormatSelector nsMsgDBView::m_dateFormatThisWeek = kDateFormatShort;
+nsDateFormatSelector nsMsgDBView::m_dateFormatToday = kDateFormatNone;
+
+static const uint32_t kMaxNumSortColumns = 2;
+
+static void GetCachedName(const nsCString& unparsedString,
+ int32_t displayVersion, nsACString& cachedName);
+
+static void UpdateCachedName(nsIMsgDBHdr *aHdr, const char *header_field,
+ const nsAString& newName);
+
+// this is passed into NS_QuickSort as custom data.
+class viewSortInfo
+{
+public:
+ nsMsgDBView *view;
+ nsIMsgDatabase *db;
+ bool isSecondarySort;
+ bool ascendingSort;
+};
+
+
+NS_IMPL_ADDREF(nsMsgDBView)
+NS_IMPL_RELEASE(nsMsgDBView)
+
+NS_INTERFACE_MAP_BEGIN(nsMsgDBView)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgDBView)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgDBView)
+ NS_INTERFACE_MAP_ENTRY(nsIDBChangeListener)
+ NS_INTERFACE_MAP_ENTRY(nsITreeView)
+ NS_INTERFACE_MAP_ENTRY(nsIJunkMailClassificationListener)
+NS_INTERFACE_MAP_END
+
+nsMsgDBView::nsMsgDBView()
+{
+ /* member initializers and constructor code */
+ m_sortValid = false;
+ m_checkedCustomColumns = false;
+ m_sortOrder = nsMsgViewSortOrder::none;
+ m_viewFlags = nsMsgViewFlagsType::kNone;
+ m_secondarySort = nsMsgViewSortType::byId;
+ m_secondarySortOrder = nsMsgViewSortOrder::ascending;
+ m_cachedMsgKey = nsMsgKey_None;
+ m_currentlyDisplayedMsgKey = nsMsgKey_None;
+ m_currentlyDisplayedViewIndex = nsMsgViewIndex_None;
+ mNumSelectedRows = 0;
+ mSuppressMsgDisplay = false;
+ mSuppressCommandUpdating = false;
+ mSuppressChangeNotification = false;
+ mSummarizeFailed = false;
+ mSelectionSummarized = false;
+ mGoForwardEnabled = false;
+ mGoBackEnabled = false;
+
+ mIsNews = false;
+ mIsRss = false;
+ mIsXFVirtual = false;
+ mDeleteModel = nsMsgImapDeleteModels::MoveToTrash;
+ m_deletingRows = false;
+ mNumMessagesRemainingInBatch = 0;
+ mShowSizeInLines = false;
+ mSortThreadsByRoot = false;
+
+ /* mCommandsNeedDisablingBecauseOfSelection - A boolean that tell us if we needed to disable commands because of what's selected.
+ If we're offline w/o a downloaded msg selected, or a dummy message was selected.
+ */
+
+ mCommandsNeedDisablingBecauseOfSelection = false;
+ mRemovingRow = false;
+ m_saveRestoreSelectionDepth = 0;
+ mRecentlyDeletedArrayIndex = 0;
+ // initialize any static atoms or unicode strings
+ if (gInstanceCount == 0)
+ {
+ InitializeAtomsAndLiterals();
+ InitDisplayFormats();
+ }
+
+ InitLabelStrings();
+ gInstanceCount++;
+}
+
+void nsMsgDBView::InitializeAtomsAndLiterals()
+{
+ kJunkMsgAtom = MsgNewAtom("junk").take();
+ kNotJunkMsgAtom = MsgNewAtom("notjunk").take();
+
+ // priority strings
+ kHighestPriorityString = GetString(u"priorityHighest");
+ kHighPriorityString = GetString(u"priorityHigh");
+ kLowestPriorityString = GetString(u"priorityLowest");
+ kLowPriorityString = GetString(u"priorityLow");
+ kNormalPriorityString = GetString(u"priorityNormal");
+
+ kReadString = GetString(u"read");
+ kRepliedString = GetString(u"replied");
+ kForwardedString = GetString(u"forwarded");
+ kNewString = GetString(u"new");
+}
+
+nsMsgDBView::~nsMsgDBView()
+{
+ if (m_db)
+ m_db->RemoveListener(this);
+
+ gInstanceCount--;
+ if (gInstanceCount <= 0)
+ {
+ NS_IF_RELEASE(kJunkMsgAtom);
+ NS_IF_RELEASE(kNotJunkMsgAtom);
+
+ NS_Free(kHighestPriorityString);
+ NS_Free(kHighPriorityString);
+ NS_Free(kLowestPriorityString);
+ NS_Free(kLowPriorityString);
+ NS_Free(kNormalPriorityString);
+
+ NS_Free(kReadString);
+ NS_Free(kRepliedString);
+ NS_Free(kForwardedString);
+ NS_Free(kNewString);
+ }
+}
+
+nsresult nsMsgDBView::InitLabelStrings()
+{
+ nsresult rv = NS_OK;
+ nsCString prefString;
+
+ for(int32_t i = 0; i < PREF_LABELS_MAX; i++)
+ {
+ prefString.Assign(PREF_LABELS_DESCRIPTION);
+ prefString.AppendInt(i + 1);
+ rv = GetPrefLocalizedString(prefString.get(), mLabelPrefDescriptions[i]);
+ }
+ return rv;
+}
+
+// helper function used to fetch strings from the messenger string bundle
+char16_t * nsMsgDBView::GetString(const char16_t *aStringName)
+{
+ nsresult res = NS_ERROR_UNEXPECTED;
+ char16_t *ptrv = nullptr;
+
+ if (!mMessengerStringBundle)
+ {
+ static const char propertyURL[] = MESSENGER_STRING_URL;
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ if (sBundleService)
+ res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(mMessengerStringBundle));
+ }
+
+ if (mMessengerStringBundle)
+ res = mMessengerStringBundle->GetStringFromName(aStringName, &ptrv);
+
+ if ( NS_SUCCEEDED(res) && (ptrv) )
+ return ptrv;
+ else
+ return NS_strdup(aStringName);
+}
+
+// helper function used to fetch localized strings from the prefs
+nsresult nsMsgDBView::GetPrefLocalizedString(const char *aPrefName, nsString& aResult)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ nsString ucsval;
+
+ prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = prefBranch->GetComplexValue(aPrefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pls->ToString(getter_Copies(ucsval));
+ aResult = ucsval.get();
+ return rv;
+}
+
+nsresult nsMsgDBView::AppendKeywordProperties(const nsACString& keywords, nsAString& properties, bool addSelectedTextProperty)
+{
+ // get the top most keyword's color and append that as a property.
+ nsresult rv;
+ if (!mTagService)
+ {
+ mTagService = do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString topKey;
+ rv = mTagService->GetTopKey(keywords, topKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (topKey.IsEmpty())
+ return NS_OK;
+
+ nsCString color;
+ rv = mTagService->GetColorForKey(topKey, color);
+ if (NS_SUCCEEDED(rv) && !color.IsEmpty())
+ {
+ if (addSelectedTextProperty)
+ {
+ if (color.EqualsLiteral(LABEL_COLOR_WHITE_STRING))
+ properties.AppendLiteral(" lc-black");
+ else
+ properties.AppendLiteral(" lc-white");
+ }
+ color.Replace(0, 1, NS_LITERAL_CSTRING(LABEL_COLOR_STRING));
+ properties.AppendASCII(color.get());
+ }
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// nsITreeView Implementation Methods (and helper methods)
+///////////////////////////////////////////////////////////////////////////
+
+static nsresult GetDisplayNameInAddressBook(const nsACString& emailAddress,
+ nsAString& displayName)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(do_GetService("@mozilla.org/abmanager;1",
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = abManager->GetDirectories(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIAbDirectory> directory;
+ nsCOMPtr<nsIAbCard> cardForAddress;
+ bool hasMore;
+
+ // Scan the addressbook to find out the card of the email address
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) &&
+ hasMore && !cardForAddress)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+ directory = do_QueryInterface(supports);
+ if (directory)
+ {
+ rv = directory->CardForEmailAddress(emailAddress,
+ getter_AddRefs(cardForAddress));
+
+ if (NS_SUCCEEDED(rv) && cardForAddress)
+ break; // the card is found,so stop looping
+ }
+ }
+
+ if (cardForAddress)
+ {
+ bool preferDisplayName = true;
+ cardForAddress->GetPropertyAsBool("PreferDisplayName",&preferDisplayName);
+
+ if (preferDisplayName)
+ rv = cardForAddress->GetDisplayName(displayName);
+ }
+
+ return rv;
+}
+
+/* The unparsedString has following format
+ * "version|displayname"
+ */
+static void GetCachedName(const nsCString& unparsedString,
+ int32_t displayVersion, nsACString& cachedName)
+{
+ nsresult err;
+
+ //get verion #
+ int32_t cachedVersion = unparsedString.ToInteger(&err);
+ if (cachedVersion != displayVersion)
+ return;
+
+ //get cached name
+ int32_t pos = unparsedString.FindChar('|');
+ if (pos != kNotFound)
+ cachedName = Substring(unparsedString, pos + 1);
+}
+
+static void UpdateCachedName(nsIMsgDBHdr *aHdr, const char *header_field,
+ const nsAString& newName)
+{
+ nsCString newCachedName;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ int32_t currentDisplayNameVersion = 0;
+
+ prefs->GetIntPref("mail.displayname.version", &currentDisplayNameVersion);
+
+ // save version number
+ newCachedName.AppendInt(currentDisplayNameVersion);
+ newCachedName.Append("|");
+
+ // save name
+ newCachedName.Append(NS_ConvertUTF16toUTF8(newName));
+
+ aHdr->SetStringProperty(header_field,newCachedName.get());
+}
+
+nsresult nsMsgDBView::FetchAuthor(nsIMsgDBHdr * aHdr, nsAString &aSenderString)
+{
+ nsCString unparsedAuthor;
+ bool showCondensedAddresses = false;
+ int32_t currentDisplayNameVersion = 0;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ prefs->GetIntPref("mail.displayname.version", &currentDisplayNameVersion);
+ prefs->GetBoolPref("mail.showCondensedAddresses", &showCondensedAddresses);
+
+ aHdr->GetStringProperty("sender_name", getter_Copies(unparsedAuthor));
+
+ // if the author is already computed, use it
+ if (!unparsedAuthor.IsEmpty())
+ {
+ nsCString cachedDisplayName;
+
+ GetCachedName(unparsedAuthor, currentDisplayNameVersion, cachedDisplayName);
+ if (!cachedDisplayName.IsEmpty())
+ {
+ CopyUTF8toUTF16(cachedDisplayName, aSenderString);
+ return NS_OK;
+ }
+ }
+
+ nsCString author;
+ (void) aHdr->GetAuthor(getter_Copies(author));
+
+ nsCString headerCharset;
+ aHdr->GetEffectiveCharset(headerCharset);
+
+ nsString name;
+ nsCString emailAddress;
+ nsCOMArray<msgIAddressObject> addresses = EncodedHeader(author, headerCharset.get());
+ bool multipleAuthors = addresses.Length() > 1;
+
+ ExtractFirstAddress(addresses, name, emailAddress);
+
+ if (showCondensedAddresses)
+ GetDisplayNameInAddressBook(emailAddress, aSenderString);
+
+ if (aSenderString.IsEmpty())
+ {
+ // We can't use the display name in the card; use the name contained in
+ // the header or email address.
+ if (name.IsEmpty()) {
+ CopyUTF8toUTF16(emailAddress, aSenderString);
+ } else {
+ int32_t atPos;
+ if ((atPos = name.FindChar('@')) == kNotFound ||
+ name.FindChar('.', atPos) == kNotFound) {
+ aSenderString = name;
+ } else {
+ // Found @ followed by a dot, so this looks like a spoofing case.
+ aSenderString = name;
+ aSenderString.AppendLiteral(" <");
+ AppendUTF8toUTF16(emailAddress, aSenderString);
+ aSenderString.Append('>');
+ }
+ }
+ }
+
+ if (multipleAuthors)
+ {
+ aSenderString.AppendLiteral(" ");
+ aSenderString.Append(GetString(u"andOthers"));
+ }
+
+ UpdateCachedName(aHdr, "sender_name", aSenderString);
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchAccount(nsIMsgDBHdr * aHdr, nsAString& aAccount)
+{
+ nsCString accountKey;
+
+ nsresult rv = aHdr->GetAccountKey(getter_Copies(accountKey));
+
+ // Cache the account manager?
+ nsCOMPtr<nsIMsgAccountManager> accountManager(
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAccount> account;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (!accountKey.IsEmpty())
+ rv = accountManager->GetAccount(accountKey, getter_AddRefs(account));
+
+ if (account)
+ {
+ account->GetIncomingServer(getter_AddRefs(server));
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ aHdr->GetFolder(getter_AddRefs(folder));
+ if (folder)
+ folder->GetServer(getter_AddRefs(server));
+ }
+ if (server)
+ server->GetPrettyName(aAccount);
+ else
+ CopyASCIItoUTF16(accountKey, aAccount);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchRecipients(nsIMsgDBHdr * aHdr, nsAString &aRecipientsString)
+{
+ nsCString recipients;
+ int32_t currentDisplayNameVersion = 0;
+ bool showCondensedAddresses = false;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ prefs->GetIntPref("mail.displayname.version", &currentDisplayNameVersion);
+ prefs->GetBoolPref("mail.showCondensedAddresses", &showCondensedAddresses);
+
+ aHdr->GetStringProperty("recipient_names", getter_Copies(recipients));
+
+ if (!recipients.IsEmpty())
+ {
+ nsCString cachedRecipients;
+
+ GetCachedName(recipients, currentDisplayNameVersion, cachedRecipients);
+
+ // recipients have already been cached,check if the addressbook
+ // was changed after cache.
+ if (!cachedRecipients.IsEmpty())
+ {
+ CopyUTF8toUTF16(cachedRecipients, aRecipientsString);
+ return NS_OK;
+ }
+ }
+
+ nsCString unparsedRecipients;
+ nsresult rv = aHdr->GetRecipients(getter_Copies(unparsedRecipients));
+
+ nsCString headerCharset;
+ aHdr->GetEffectiveCharset(headerCharset);
+
+ nsTArray<nsString> names;
+ nsTArray<nsCString> emails;
+ ExtractAllAddresses(EncodedHeader(unparsedRecipients, headerCharset.get()),
+ names, UTF16ArrayAdapter<>(emails));
+
+ uint32_t numAddresses = names.Length();
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsCOMPtr<nsIAbManager>
+ abManager(do_GetService("@mozilla.org/abmanager;1", &rv));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ // go through each email address in the recipients and
+ // compute its display name.
+ for (uint32_t i = 0; i < numAddresses; i++)
+ {
+ nsString recipient;
+ nsCString &curAddress = emails[i];
+ nsString &curName = names[i];
+
+ if (showCondensedAddresses)
+ GetDisplayNameInAddressBook(curAddress, recipient);
+
+ if (recipient.IsEmpty())
+ {
+ // we can't use the display name in the card,
+ // use the name contained in the header or email address.
+ if (curName.IsEmpty()) {
+ CopyUTF8toUTF16(curAddress, recipient);
+ } else {
+ int32_t atPos;
+ if ((atPos = curName.FindChar('@')) == kNotFound ||
+ curName.FindChar('.', atPos) == kNotFound) {
+ recipient = curName;
+ } else {
+ // Found @ followed by a dot, so this looks like a spoofing case.
+ recipient = curName;
+ recipient.AppendLiteral(" <");
+ AppendUTF8toUTF16(curAddress, recipient);
+ recipient.Append('>');
+ }
+ }
+ }
+
+ // add ', ' between each recipient
+ if (i != 0)
+ aRecipientsString.Append(NS_LITERAL_STRING(", "));
+
+ aRecipientsString.Append(recipient);
+ }
+
+ if (numAddresses == 0 && unparsedRecipients.FindChar(':') != kNotFound) {
+ // No addresses and a colon, so an empty group like "undisclosed-recipients: ;".
+ // Add group name so at least something displays.
+ nsString group;
+ CopyUTF8toUTF16(unparsedRecipients, group);
+ aRecipientsString.Assign(group);
+ }
+
+ UpdateCachedName(aHdr, "recipient_names", aRecipientsString);
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchSubject(nsIMsgDBHdr * aMsgHdr, uint32_t aFlags, nsAString &aValue)
+{
+ if (aFlags & nsMsgMessageFlags::HasRe)
+ {
+ nsString subject;
+ aMsgHdr->GetMime2DecodedSubject(subject);
+ aValue.AssignLiteral("Re: ");
+ aValue.Append(subject);
+ }
+ else
+ aMsgHdr->GetMime2DecodedSubject(aValue);
+ return NS_OK;
+}
+
+// in case we want to play around with the date string, I've broken it out into
+// a separate routine. Set rcvDate to true to get the Received: date instead
+// of the Date: date.
+nsresult nsMsgDBView::FetchDate(nsIMsgDBHdr * aHdr, nsAString &aDateString, bool rcvDate)
+{
+ PRTime dateOfMsg;
+ PRTime dateOfMsgLocal;
+ uint32_t rcvDateSecs;
+ nsresult rv;
+
+ if (!mDateFormatter)
+ mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID);
+
+ NS_ENSURE_TRUE(mDateFormatter, NS_ERROR_FAILURE);
+ // Silently return Date: instead if Received: is unavailable
+ if (rcvDate)
+ {
+ rv = aHdr->GetUint32Property("dateReceived", &rcvDateSecs);
+ if (rcvDateSecs != 0)
+ Seconds2PRTime(rcvDateSecs, &dateOfMsg);
+ }
+ if (!rcvDate || rcvDateSecs == 0)
+ rv = aHdr->GetDate(&dateOfMsg);
+
+ PRTime currentTime = PR_Now();
+ PRExplodedTime explodedCurrentTime;
+ PR_ExplodeTime(currentTime, PR_LocalTimeParameters, &explodedCurrentTime);
+ PRExplodedTime explodedMsgTime;
+ PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime);
+
+ // if the message is from today, don't show the date, only the time. (i.e. 3:15 pm)
+ // if the message is from the last week, show the day of the week. (i.e. Mon 3:15 pm)
+ // in all other cases, show the full date (03/19/01 3:15 pm)
+
+ nsDateFormatSelector dateFormat = m_dateFormatDefault;
+ if (explodedCurrentTime.tm_year == explodedMsgTime.tm_year &&
+ explodedCurrentTime.tm_month == explodedMsgTime.tm_month &&
+ explodedCurrentTime.tm_mday == explodedMsgTime.tm_mday)
+ {
+ // same day...
+ dateFormat = m_dateFormatToday;
+ }
+ // the following chunk of code allows us to show a day instead of a number if the message was received
+ // within the last 7 days. i.e. Mon 5:10pm. (depending on the mail.ui.display.dateformat.thisweek pref)
+ // The concrete format used is dependent on a preference setting (see InitDisplayFormats).
+ else if (currentTime > dateOfMsg)
+ {
+ // Convert the times from GMT to local time
+ int64_t GMTLocalTimeShift = PR_USEC_PER_SEC *
+ int64_t(explodedCurrentTime.tm_params.tp_gmt_offset +
+ explodedCurrentTime.tm_params.tp_dst_offset);
+ currentTime += GMTLocalTimeShift;
+ dateOfMsgLocal = dateOfMsg + GMTLocalTimeShift;
+
+ // Find the most recent midnight
+ int64_t todaysMicroSeconds = currentTime % PR_USEC_PER_DAY;
+ int64_t mostRecentMidnight = currentTime - todaysMicroSeconds;
+
+ // most recent midnight minus 6 days
+ int64_t mostRecentWeek = mostRecentMidnight - (PR_USEC_PER_DAY * 6);
+
+ // was the message sent during the last week?
+ if (dateOfMsgLocal >= mostRecentWeek)
+ { // yes ....
+ dateFormat = m_dateFormatThisWeek;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv))
+ rv = mDateFormatter->FormatPRTime(nullptr /* nsILocale* locale */,
+ dateFormat,
+ kTimeFormatNoSeconds,
+ dateOfMsg,
+ aDateString);
+
+ return rv;
+}
+
+nsresult nsMsgDBView::FetchStatus(uint32_t aFlags, nsAString &aStatusString)
+{
+ if (aFlags & nsMsgMessageFlags::Replied)
+ aStatusString = kRepliedString;
+ else if (aFlags & nsMsgMessageFlags::Forwarded)
+ aStatusString = kForwardedString;
+ else if (aFlags & nsMsgMessageFlags::New)
+ aStatusString = kNewString;
+ else if (aFlags & nsMsgMessageFlags::Read)
+ aStatusString = kReadString;
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchSize(nsIMsgDBHdr * aHdr, nsAString &aSizeString)
+{
+ nsresult rv;
+ nsAutoString formattedSizeString;
+ uint32_t msgSize = 0;
+
+ // for news, show the line count, not the size if the user wants so
+ if (mShowSizeInLines)
+ {
+ aHdr->GetLineCount(&msgSize);
+ formattedSizeString.AppendInt(msgSize);
+ }
+ else
+ {
+ uint32_t flags = 0;
+
+ aHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial)
+ aHdr->GetUint32Property("onlineSize", &msgSize);
+
+ if (msgSize == 0)
+ aHdr->GetMessageSize(&msgSize);
+
+ rv = FormatFileSize(msgSize, true, formattedSizeString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aSizeString = formattedSizeString;
+ // the formattingString Length includes the null terminator byte!
+ if (!formattedSizeString.Last())
+ aSizeString.SetLength(formattedSizeString.Length() - 1);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchPriority(nsIMsgDBHdr *aHdr, nsAString & aPriorityString)
+{
+ nsMsgPriorityValue priority = nsMsgPriority::notSet;
+ aHdr->GetPriority(&priority);
+
+ switch (priority)
+ {
+ case nsMsgPriority::highest:
+ aPriorityString = kHighestPriorityString;
+ break;
+ case nsMsgPriority::high:
+ aPriorityString = kHighPriorityString;
+ break;
+ case nsMsgPriority::low:
+ aPriorityString = kLowPriorityString;
+ break;
+ case nsMsgPriority::lowest:
+ aPriorityString = kLowestPriorityString;
+ break;
+ case nsMsgPriority::normal:
+ aPriorityString = kNormalPriorityString;
+ break;
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchKeywords(nsIMsgDBHdr *aHdr, nsACString &keywordString)
+{
+ NS_ENSURE_ARG_POINTER(aHdr);
+ nsresult rv = NS_OK;
+ if (!mTagService)
+ {
+ mTagService = do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsMsgLabelValue label = 0;
+
+ rv = aHdr->GetLabel(&label);
+ nsCString keywords;
+ aHdr->GetStringProperty("keywords", getter_Copies(keywords));
+ if (label > 0)
+ {
+ nsAutoCString labelStr("$label");
+ labelStr.Append((char) (label + '0'));
+ if (keywords.Find(labelStr, CaseInsensitiveCompare) == -1)
+ {
+ if (!keywords.IsEmpty())
+ keywords.Append(' ');
+ keywords.Append(labelStr);
+ }
+ }
+ keywordString = keywords;
+ return NS_OK;
+}
+
+// If the row is a collapsed thread, we optionally roll-up the keywords in all
+// the messages in the thread, otherwise, return just the keywords for the row.
+nsresult nsMsgDBView::FetchRowKeywords(nsMsgViewIndex aRow, nsIMsgDBHdr *aHdr,
+ nsACString &keywordString)
+{
+ nsresult rv = FetchKeywords(aHdr,keywordString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool cascadeKeywordsUp = true;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ prefs->GetBoolPref("mailnews.display_reply_tag_colors_for_collapsed_threads",
+ &cascadeKeywordsUp);
+
+ if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) &&
+ cascadeKeywordsUp)
+ {
+ if ((m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD)
+ && (m_flags[aRow] & nsMsgMessageFlags::Elided))
+ {
+ nsCOMPtr<nsIMsgThread> thread;
+ rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv) && thread)
+ {
+ uint32_t numChildren;
+ thread->GetNumChildren(&numChildren);
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCString moreKeywords;
+ for (uint32_t index = 0; index < numChildren; index++)
+ {
+ thread->GetChildHdrAt(index, getter_AddRefs(msgHdr));
+ rv = FetchKeywords(msgHdr, moreKeywords);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!keywordString.IsEmpty() && !moreKeywords.IsEmpty())
+ keywordString.Append(' ');
+ keywordString.Append(moreKeywords);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgDBView::FetchTags(nsIMsgDBHdr *aHdr, nsAString &aTagString)
+{
+ NS_ENSURE_ARG_POINTER(aHdr);
+ nsresult rv = NS_OK;
+ if (!mTagService)
+ {
+ mTagService = do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsString tags;
+ nsCString keywords;
+ aHdr->GetStringProperty("keywords", getter_Copies(keywords));
+
+ nsMsgLabelValue label = 0;
+ rv = aHdr->GetLabel(&label);
+ if (label > 0)
+ {
+ nsAutoCString labelStr("$label");
+ labelStr.Append((char) (label + '0'));
+ if (keywords.Find(labelStr, CaseInsensitiveCompare) == -1)
+ FetchLabel(aHdr, tags);
+ }
+
+ nsTArray<nsCString> keywordsArray;
+ ParseString(keywords, ' ', keywordsArray);
+ nsAutoString tag;
+
+ for (uint32_t i = 0; i < keywordsArray.Length(); i++)
+ {
+ rv = mTagService->GetTagForKey(keywordsArray[i], tag);
+ if (NS_SUCCEEDED(rv) && !tag.IsEmpty())
+ {
+ if (!tags.IsEmpty())
+ tags.Append((char16_t) ' ');
+ tags.Append(tag);
+ }
+ }
+
+ aTagString = tags;
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchLabel(nsIMsgDBHdr *aHdr, nsAString &aLabelString)
+{
+ nsresult rv = NS_OK;
+ nsMsgLabelValue label = 0;
+
+ NS_ENSURE_ARG_POINTER(aHdr);
+
+ rv = aHdr->GetLabel(&label);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we don't care if label is not between 1 and PREF_LABELS_MAX inclusive.
+ if ((label < 1) || (label > PREF_LABELS_MAX))
+ {
+ aLabelString.Truncate();
+ return NS_OK;
+ }
+
+ // We need to subtract 1 because mLabelPrefDescriptions is 0 based.
+ aLabelString = mLabelPrefDescriptions[label - 1];
+ return NS_OK;
+}
+
+/**
+ * Lowercase the email and remove a possible plus addressing part.
+ * E.g. John+test@example.com -> john@example.com.
+ */
+static void
+ToLowerCaseDropPlusAddessing(nsCString& aEmail)
+{
+ ToLowerCase(aEmail);
+ int32_t indPlus;
+ if ((indPlus = aEmail.FindChar('+')) == kNotFound)
+ return;
+ int32_t indAt;
+ indAt = aEmail.FindChar('@', indPlus);
+ if (indAt == kNotFound)
+ return;
+ aEmail.ReplaceLiteral(indPlus, indAt - indPlus, "");
+}
+
+bool nsMsgDBView::IsOutgoingMsg(nsIMsgDBHdr* aHdr)
+{
+ nsString author;
+ aHdr->GetMime2DecodedAuthor(author);
+
+ nsCString emailAddress;
+ nsString name;
+ ExtractFirstAddress(DecodedHeader(author), name, emailAddress);
+ ToLowerCaseDropPlusAddessing(emailAddress);
+ return mEmails.Contains(emailAddress);
+}
+
+
+/*if you call SaveAndClearSelection make sure to call RestoreSelection otherwise
+m_saveRestoreSelectionDepth will be incorrect and will lead to selection msg problems*/
+
+nsresult nsMsgDBView::SaveAndClearSelection(nsMsgKey *aCurrentMsgKey, nsTArray<nsMsgKey> &aMsgKeyArray)
+{
+ // we don't do anything on nested Save / Restore calls.
+ m_saveRestoreSelectionDepth++;
+ if (m_saveRestoreSelectionDepth != 1)
+ return NS_OK;
+
+ if (!mTreeSelection || !mTree)
+ return NS_OK;
+
+ // first, freeze selection.
+ mTreeSelection->SetSelectEventsSuppressed(true);
+
+ // second, save the current index.
+ if (aCurrentMsgKey)
+ {
+ int32_t currentIndex;
+ if (NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
+ currentIndex >= 0 && uint32_t(currentIndex) < GetSize())
+ *aCurrentMsgKey = m_keys[currentIndex];
+ else
+ *aCurrentMsgKey = nsMsgKey_None;
+ }
+
+ // third, get an array of view indices for the selection.
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ int32_t numIndices = selection.Length();
+ aMsgKeyArray.SetLength(numIndices);
+
+ // now store the msg key for each selected item.
+ nsMsgKey msgKey;
+ for (int32_t index = 0; index < numIndices; index++)
+ {
+ msgKey = m_keys[selection[index]];
+ aMsgKeyArray[index] = msgKey;
+ }
+
+ // clear the selection, we'll manually restore it later.
+ if (mTreeSelection)
+ mTreeSelection->ClearSelection();
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::RestoreSelection(nsMsgKey aCurrentMsgKey, nsTArray<nsMsgKey> &aMsgKeyArray)
+{
+ // we don't do anything on nested Save / Restore calls.
+ m_saveRestoreSelectionDepth--;
+ if (m_saveRestoreSelectionDepth)
+ return NS_OK;
+
+ if (!mTreeSelection) // don't assert.
+ return NS_OK;
+
+ // turn our message keys into corresponding view indices
+ int32_t arraySize = aMsgKeyArray.Length();
+ nsMsgViewIndex currentViewPosition = nsMsgViewIndex_None;
+ nsMsgViewIndex newViewPosition = nsMsgViewIndex_None;
+
+ // if we are threaded, we need to do a little more work
+ // we need to find (and expand) all the threads that contain messages
+ // that we had selected before.
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ for (int32_t index = 0; index < arraySize; index ++)
+ {
+ FindKey(aMsgKeyArray[index], true /* expand */);
+ }
+ }
+
+ for (int32_t index = 0; index < arraySize; index ++)
+ {
+ newViewPosition = FindKey(aMsgKeyArray[index], false);
+ // add the index back to the selection.
+ if (newViewPosition != nsMsgViewIndex_None)
+ mTreeSelection->ToggleSelect(newViewPosition);
+ }
+
+ // make sure the currentView was preserved....
+ if (aCurrentMsgKey != nsMsgKey_None)
+ currentViewPosition = FindKey(aCurrentMsgKey, true);
+
+ if (mTree)
+ mTreeSelection->SetCurrentIndex(currentViewPosition);
+
+ // make sure the current message is once again visible in the thread pane
+ // so we don't have to go search for it in the thread pane
+ if (mTree && currentViewPosition != nsMsgViewIndex_None)
+ mTree->EnsureRowIsVisible(currentViewPosition);
+
+ // unfreeze selection.
+ mTreeSelection->SetSelectEventsSuppressed(false);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GenerateURIForMsgKey(nsMsgKey aMsgKey, nsIMsgFolder *folder, nsACString & aURI)
+{
+ NS_ENSURE_ARG(folder);
+ return folder->GenerateMessageURI(aMsgKey, aURI);
+}
+
+nsresult nsMsgDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator)
+{
+ return m_db->EnumerateMessages(enumerator);
+}
+
+nsresult nsMsgDBView::CycleThreadedColumn(nsIDOMElement * aElement)
+{
+ nsAutoString currentView;
+
+ // toggle threaded/unthreaded mode
+ aElement->GetAttribute(NS_LITERAL_STRING("currentView"), currentView);
+ if (currentView.EqualsLiteral("threaded"))
+ aElement->SetAttribute(NS_LITERAL_STRING("currentView"), NS_LITERAL_STRING("unthreaded"));
+ else
+ aElement->SetAttribute(NS_LITERAL_STRING("currentView"), NS_LITERAL_STRING("threaded"));
+
+ // i think we need to create a new view and switch it in this circumstance since
+ // we are toggline between threaded and non threaded mode.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::IsEditable(int32_t row, nsITreeColumn* col, bool* _retval)
+{
+ NS_ENSURE_ARG_POINTER(col);
+ NS_ENSURE_ARG_POINTER(_retval);
+ //attempt to retreive a custom column handler. If it exists call it and return
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+
+ nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
+
+ if (colHandler)
+ {
+ colHandler->IsEditable(row, col, _retval);
+ return NS_OK;
+ }
+
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::IsSelectable(int32_t row, nsITreeColumn* col, bool* _retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetRowCount(int32_t *aRowCount)
+{
+ *aRowCount = GetSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetSelection(nsITreeSelection * *aSelection)
+{
+ *aSelection = mTreeSelection;
+ NS_IF_ADDREF(*aSelection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetSelection(nsITreeSelection * aSelection)
+{
+ mTreeSelection = aSelection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::ReloadMessageWithAllParts()
+{
+ if (m_currentlyDisplayedMsgUri.IsEmpty() || mSuppressMsgDisplay)
+ return NS_OK;
+
+ nsAutoCString forceAllParts(m_currentlyDisplayedMsgUri);
+ forceAllParts += (forceAllParts.FindChar('?') == kNotFound) ? '?' : '&';
+ forceAllParts.AppendLiteral("fetchCompleteMessage=true");
+ nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak));
+ NS_ENSURE_TRUE(messenger, NS_ERROR_FAILURE);
+
+ nsresult rv = messenger->OpenURL(forceAllParts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UpdateDisplayMessage(m_currentlyDisplayedViewIndex);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::ReloadMessage()
+{
+ if (m_currentlyDisplayedMsgUri.IsEmpty() || mSuppressMsgDisplay)
+ return NS_OK;
+ nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak));
+ NS_ENSURE_TRUE(messenger, NS_ERROR_FAILURE);
+
+ nsresult rv = messenger->OpenURL(m_currentlyDisplayedMsgUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UpdateDisplayMessage(m_currentlyDisplayedViewIndex);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::UpdateDisplayMessage(nsMsgViewIndex viewPosition)
+{
+ nsresult rv;
+ if (mCommandUpdater)
+ {
+ // get the subject and the folder for the message and inform the front end that
+ // we changed the message we are currently displaying.
+ if (viewPosition != nsMsgViewIndex_None)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgHdrForViewIndex(viewPosition, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString subject;
+ FetchSubject(msgHdr, m_flags[viewPosition], subject);
+
+ nsCString keywords;
+ rv = msgHdr->GetStringProperty("keywords", getter_Copies(keywords));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgFolder> folder = m_viewFolder ? m_viewFolder : m_folder;
+
+ mCommandUpdater->DisplayMessageChanged(folder, subject, keywords);
+
+ if (folder)
+ {
+ rv = folder->SetLastMessageLoaded(m_keys[viewPosition]);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ } // if view position is valid
+ } // if we have an updater
+ return NS_OK;
+}
+
+// given a msg key, we will load the message for it.
+NS_IMETHODIMP nsMsgDBView::LoadMessageByMsgKey(nsMsgKey aMsgKey)
+{
+ return LoadMessageByViewIndex(FindKey(aMsgKey, false));
+}
+
+NS_IMETHODIMP nsMsgDBView::LoadMessageByViewIndex(nsMsgViewIndex aViewIndex)
+{
+ NS_ASSERTION(aViewIndex != nsMsgViewIndex_None,"trying to load nsMsgViewIndex_None");
+ if (aViewIndex == nsMsgViewIndex_None) return NS_ERROR_UNEXPECTED;
+
+ nsCString uri;
+ nsresult rv = GetURIForViewIndex(aViewIndex, uri);
+ if (!mSuppressMsgDisplay && !m_currentlyDisplayedMsgUri.Equals(uri))
+ {
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak));
+ NS_ENSURE_TRUE(messenger, NS_ERROR_FAILURE);
+ messenger->OpenURL(uri);
+ m_currentlyDisplayedMsgKey = m_keys[aViewIndex];
+ m_currentlyDisplayedMsgUri = uri;
+ m_currentlyDisplayedViewIndex = aViewIndex;
+ UpdateDisplayMessage(m_currentlyDisplayedViewIndex);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::LoadMessageByUrl(const char *aUrl)
+{
+ NS_ASSERTION(aUrl, "trying to load a null url");
+ if (!mSuppressMsgDisplay)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ messenger->LoadURL(NULL, nsDependentCString(aUrl));
+ m_currentlyDisplayedMsgKey = nsMsgKey_None;
+ m_currentlyDisplayedMsgUri = aUrl;
+ m_currentlyDisplayedViewIndex = nsMsgViewIndex_None;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SelectionChanged()
+{
+ // if the currentSelection changed then we have a message to display - not if we are in the middle of deleting rows
+ if (m_deletingRows)
+ return NS_OK;
+
+ uint32_t numSelected = 0;
+
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ nsMsgViewIndex *indices = selection.Elements();
+ numSelected = selection.Length();
+
+ bool commandsNeedDisablingBecauseOfSelection = false;
+
+ if(indices)
+ {
+ if (WeAreOffline())
+ commandsNeedDisablingBecauseOfSelection = !OfflineMsgSelected(indices, numSelected);
+ if (!NonDummyMsgSelected(indices, numSelected))
+ commandsNeedDisablingBecauseOfSelection = true;
+ }
+ bool selectionSummarized = false;
+ mSummarizeFailed = false;
+ // let the front-end adjust the message pane appropriately with either
+ // the message body, or a summary of the selection
+ if (mCommandUpdater)
+ {
+ mCommandUpdater->SummarizeSelection(&selectionSummarized);
+ // check if the selection was not summarized, but we expected it to be,
+ // and if so, remember it so GetHeadersFromSelection won't include
+ // the messages in collapsed threads.
+ if (!selectionSummarized &&
+ (numSelected > 1 || (numSelected == 1 &&
+ m_flags[indices[0]] & nsMsgMessageFlags::Elided &&
+ OperateOnMsgsInCollapsedThreads())))
+ mSummarizeFailed = true;
+ }
+
+ bool summaryStateChanged = selectionSummarized != mSelectionSummarized;
+
+ mSelectionSummarized = selectionSummarized;
+ // if only one item is selected then we want to display a message
+ if (mTreeSelection && numSelected == 1 && !selectionSummarized)
+ {
+ int32_t startRange;
+ int32_t endRange;
+ nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange);
+ NS_ENSURE_SUCCESS(rv, NS_OK); // tree doesn't care if we failed
+
+ if (startRange >= 0 && startRange == endRange &&
+ uint32_t(startRange) < GetSize())
+ {
+ if (!mRemovingRow)
+ {
+ if (!mSuppressMsgDisplay)
+ LoadMessageByViewIndex(startRange);
+ else
+ UpdateDisplayMessage(startRange);
+ }
+ }
+ else
+ numSelected = 0; // selection seems bogus, so set to 0.
+ }
+ else {
+ // if we have zero or multiple items selected, we shouldn't be displaying any message
+ m_currentlyDisplayedMsgKey = nsMsgKey_None;
+ m_currentlyDisplayedMsgUri.Truncate();
+ m_currentlyDisplayedViewIndex = nsMsgViewIndex_None;
+ }
+
+ // determine if we need to push command update notifications out to the UI or not.
+
+ // we need to push a command update notification iff, one of the following conditions are met
+ // (1) the selection went from 0 to 1
+ // (2) it went from 1 to 0
+ // (3) it went from 1 to many
+ // (4) it went from many to 1 or 0
+ // (5) a different msg was selected - perhaps it was offline or not...matters only when we are offline
+ // (6) we did a forward/back, or went from having no history to having history - not sure how to tell this.
+ // (7) whether the selection was summarized or not changed.
+
+ // I think we're going to need to keep track of whether forward/back were enabled/should be enabled,
+ // and when this changes, force a command update.
+
+ bool enableGoForward = false;
+ bool enableGoBack = false;
+
+ NavigateStatus(nsMsgNavigationType::forward, &enableGoForward);
+ NavigateStatus(nsMsgNavigationType::back, &enableGoBack);
+ if (!summaryStateChanged &&
+ (numSelected == mNumSelectedRows ||
+ (numSelected > 1 && mNumSelectedRows > 1)) && (commandsNeedDisablingBecauseOfSelection == mCommandsNeedDisablingBecauseOfSelection)
+ && enableGoForward == mGoForwardEnabled && enableGoBack == mGoBackEnabled)
+ {
+ }
+ // don't update commands if we're suppressing them, or if we're removing rows, unless it was the last row.
+ else if (!mSuppressCommandUpdating && mCommandUpdater && (!mRemovingRow || GetSize() == 0)) // o.t. push an updated
+ {
+ mCommandUpdater->UpdateCommandStatus();
+ }
+
+ mCommandsNeedDisablingBecauseOfSelection = commandsNeedDisablingBecauseOfSelection;
+ mGoForwardEnabled = enableGoForward;
+ mGoBackEnabled = enableGoBack;
+ mNumSelectedRows = numSelected;
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GetSelectedIndices(nsMsgViewIndexArray& selection)
+{
+ if (mTreeSelection)
+ {
+ int32_t viewSize = GetSize();
+ int32_t count;
+ mTreeSelection->GetCount(&count);
+ selection.SetLength(count);
+ count = 0;
+ int32_t selectionCount;
+ mTreeSelection->GetRangeCount(&selectionCount);
+ for (int32_t i = 0; i < selectionCount; i++)
+ {
+ int32_t startRange = -1;
+ int32_t endRange = -1;
+ mTreeSelection->GetRangeAt(i, &startRange, &endRange);
+ if (startRange >= 0 && startRange < viewSize)
+ {
+ for (int32_t rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < viewSize; rangeIndex++)
+ selection[count++] = rangeIndex;
+ }
+ }
+ NS_ASSERTION(selection.Length() == uint32_t(count), "selection count is wrong");
+ selection.SetLength(count);
+ }
+ else
+ {
+ // if there is no tree selection object then we must be in stand alone message mode.
+ // in that case the selected indices are really just the current message key.
+ nsMsgViewIndex viewIndex = FindViewIndex(m_currentlyDisplayedMsgKey);
+ if (viewIndex != nsMsgViewIndex_None)
+ selection.AppendElement(viewIndex);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetRowProperties(int32_t index, nsAString& properties)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ // this is where we tell the tree to apply styles to a particular row
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = NS_OK;
+
+ rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+
+ if (NS_FAILED(rv) || !msgHdr) {
+ ClearHdrCache();
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ nsCString keywordProperty;
+ FetchRowKeywords(index, msgHdr, keywordProperty);
+ if (!keywordProperty.IsEmpty())
+ AppendKeywordProperties(keywordProperty, properties, false);
+
+ // give the custom column handlers a chance to style the row.
+ for (int i = 0; i < m_customColumnHandlers.Count(); i++)
+ {
+ nsString extra;
+ m_customColumnHandlers[i]->GetRowProperties(index, extra);
+ if (!extra.IsEmpty())
+ {
+ properties.Append(' ');
+ properties.Append(extra);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetColumnProperties(nsITreeColumn* col, nsAString& properties)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetCellProperties(int32_t aRow, nsITreeColumn *col, nsAString& properties)
+{
+ if (!IsValidIndex(aRow))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ // this is where we tell the tree to apply styles to a particular row
+ // i.e. if the row is an unread message...
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = NS_OK;
+
+ rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
+
+ if (NS_FAILED(rv) || !msgHdr)
+ {
+ ClearHdrCache();
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+ nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
+ if (colHandler != nullptr)
+ colHandler->GetCellProperties(aRow, col, properties);
+ else if (colID[0] == 'c') // correspondent
+ {
+ if (IsOutgoingMsg(msgHdr))
+ properties.AssignLiteral("outgoing");
+ else
+ properties.AssignLiteral("incoming");
+ }
+
+ if (!properties.IsEmpty())
+ properties.Append(' ');
+
+ properties.Append(mMessageType);
+
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+
+ if (!(flags & nsMsgMessageFlags::Read))
+ properties.AppendLiteral(" unread");
+ else
+ properties.AppendLiteral(" read");
+
+ if (flags & nsMsgMessageFlags::Replied)
+ properties.AppendLiteral(" replied");
+
+ if (flags & nsMsgMessageFlags::Forwarded)
+ properties.AppendLiteral(" forwarded");
+
+ if (flags & nsMsgMessageFlags::New)
+ properties.AppendLiteral(" new");
+
+ if (m_flags[aRow] & nsMsgMessageFlags::Marked)
+ properties.AppendLiteral(" flagged");
+
+ // For threaded display add the ignoreSubthread property to the
+ // subthread top row (this row). For non-threaded add it to all rows.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) &&
+ (flags & nsMsgMessageFlags::Ignored)) {
+ properties.AppendLiteral(" ignoreSubthread");
+ }
+ else {
+ bool ignored;
+ msgHdr->GetIsKilled(&ignored);
+ if (ignored)
+ properties.AppendLiteral(" ignoreSubthread");
+ }
+
+ nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+
+ if ((flags & nsMsgMessageFlags::Offline) || (localFolder && !(flags & nsMsgMessageFlags::Partial)))
+ properties.AppendLiteral(" offline");
+
+ if (flags & nsMsgMessageFlags::Attachment)
+ properties.AppendLiteral(" attach");
+
+ if ((mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) && (flags & nsMsgMessageFlags::IMAPDeleted))
+ properties.AppendLiteral(" imapdeleted");
+
+ nsCString imageSize;
+ msgHdr->GetStringProperty("imageSize", getter_Copies(imageSize));
+ if (!imageSize.IsEmpty())
+ properties.AppendLiteral(" hasimage");
+
+ nsCString junkScoreStr;
+ msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ if (!junkScoreStr.IsEmpty()) {
+ if (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_SPAM_SCORE)
+ properties.AppendLiteral(" junk");
+ else
+ properties.AppendLiteral(" notjunk");
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed.");
+ }
+
+ nsCString keywords;
+ FetchRowKeywords(aRow, msgHdr, keywords);
+ if (!keywords.IsEmpty())
+ AppendKeywordProperties(keywords, properties, true);
+
+ // this is a double fetch of the keywords property since we also fetch
+ // it for the tags - do we want to do this?
+ // I'm not sure anyone uses the kw- property, though it could be nice
+ // for people wanting to extend the thread pane.
+ nsCString keywordProperty;
+ msgHdr->GetStringProperty("keywords", getter_Copies(keywordProperty));
+ if (!keywordProperty.IsEmpty())
+ {
+ NS_ConvertUTF8toUTF16 keywords(keywordProperty);
+ int32_t spaceIndex = 0;
+ do
+ {
+ spaceIndex = keywords.FindChar(' ');
+ int32_t endOfKeyword = (spaceIndex == -1) ? keywords.Length() : spaceIndex;
+ properties.AppendLiteral(" kw-");
+ properties.Append(StringHead(keywords, endOfKeyword));
+ if (spaceIndex > 0)
+ keywords.Cut(0, endOfKeyword + 1);
+ }
+ while (spaceIndex > 0);
+ }
+
+#ifdef SUPPORT_PRIORITY_COLORS
+ // add special styles for priority
+ nsMsgPriorityValue priority;
+ msgHdr->GetPriority(&priority);
+ switch (priority)
+ {
+ case nsMsgPriority::highest:
+ properties.append(" priority-highest");
+ break;
+ case nsMsgPriority::high:
+ properties.append(" priority-high");
+ break;
+ case nsMsgPriority::low:
+ properties.append(" priority-low");
+ break;
+ case nsMsgPriority::lowest:
+ properties.append(" priority-lowest");
+ break;
+ default:
+ break;
+ }
+#endif
+
+
+ nsCOMPtr <nsIMsgThread> thread;
+ rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv) && thread)
+ {
+ uint32_t numUnreadChildren;
+ thread->GetNumUnreadChildren(&numUnreadChildren);
+ if (numUnreadChildren > 0)
+ properties.AppendLiteral(" hasUnread");
+
+ // For threaded display add the ignore/watch properties to the
+ // thread top row. For non-threaded add it to all rows.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) ||
+ ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) &&
+ (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD))) {
+ thread->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Watched)
+ properties.AppendLiteral(" watch");
+ if (flags & nsMsgMessageFlags::Ignored)
+ properties.AppendLiteral(" ignore");
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::IsContainer(int32_t index, bool *_retval)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ uint32_t flags = m_flags[index];
+ *_retval = !!(flags & MSG_VIEW_FLAG_HASCHILDREN);
+ }
+ else
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::IsContainerOpen(int32_t index, bool *_retval)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ uint32_t flags = m_flags[index];
+ *_retval = (flags & MSG_VIEW_FLAG_HASCHILDREN) && !(flags & nsMsgMessageFlags::Elided);
+ }
+ else
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::IsContainerEmpty(int32_t index, bool *_retval)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ uint32_t flags = m_flags[index];
+ *_retval = !(flags & MSG_VIEW_FLAG_HASCHILDREN);
+ }
+ else
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::IsSeparator(int32_t index, bool *_retval)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ *_retval = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetParentIndex(int32_t rowIndex, int32_t *_retval)
+{
+ *_retval = -1;
+
+ int32_t rowIndexLevel;
+ nsresult rv = GetLevel(rowIndex, &rowIndexLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t i;
+ for(i = rowIndex; i >= 0; i--)
+ {
+ int32_t l;
+ GetLevel(i, &l);
+ if (l < rowIndexLevel)
+ {
+ *_retval = i;
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval)
+{
+ *_retval = false;
+
+ int32_t rowIndexLevel;
+ GetLevel(rowIndex, &rowIndexLevel);
+
+ int32_t i;
+ int32_t count;
+ GetRowCount(&count);
+ for(i = afterIndex + 1; i < count; i++)
+ {
+ int32_t l;
+ GetLevel(i, &l);
+ if (l < rowIndexLevel)
+ break;
+ if (l == rowIndexLevel)
+ {
+ *_retval = true;
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetLevel(int32_t index, int32_t *_retval)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ *_retval = m_levels[index];
+ else
+ *_retval = 0;
+ return NS_OK;
+}
+
+// search view will override this since headers can span db's
+nsresult nsMsgDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr)
+{
+ nsresult rv = NS_OK;
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ nsMsgKey key = m_keys[index];
+ if (key == nsMsgKey_None || !m_db)
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (key == m_cachedMsgKey)
+ {
+ *msgHdr = m_cachedHdr;
+ NS_IF_ADDREF(*msgHdr);
+ }
+ else
+ {
+ rv = m_db->GetMsgHdrForKey(key, msgHdr);
+ if (NS_SUCCEEDED(rv))
+ {
+ m_cachedHdr = *msgHdr;
+ m_cachedMsgKey = key;
+ }
+ }
+
+ return rv;
+}
+
+void nsMsgDBView::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level)
+{
+ if ((int32_t) index < 0 || index > m_keys.Length())
+ {
+ // Something's gone wrong in a caller, but we have no clue why
+ // Return without adding the header to the view
+ NS_ERROR("Index for message header insertion out of array range!");
+ return;
+ }
+ m_keys.InsertElementAt(index, msgKey);
+ m_flags.InsertElementAt(index, flags);
+ m_levels.InsertElementAt(index, level);
+}
+
+void nsMsgDBView::SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level)
+{
+ m_keys[index] = msgKey;
+ m_flags[index] = flags;
+ m_levels[index] = level;
+}
+
+nsresult nsMsgDBView::GetFolderForViewIndex(nsMsgViewIndex index, nsIMsgFolder **aFolder)
+{
+ NS_IF_ADDREF(*aFolder = m_folder);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db)
+{
+ NS_IF_ADDREF(*db = m_db);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetImageSrc(int32_t aRow, nsITreeColumn* aCol, nsAString& aValue)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ //attempt to retreive a custom column handler. If it exists call it and return
+ const char16_t* colID;
+ aCol->GetIdConst(&colID);
+
+ nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
+
+ if (colHandler)
+ {
+ colHandler->GetImageSrc(aRow, aCol, aValue);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetProgressMode(int32_t aRow, nsITreeColumn* aCol, int32_t* _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetCellValue(int32_t aRow, nsITreeColumn* aCol, nsAString& aValue)
+{
+ if (!IsValidIndex(aRow))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
+
+ if (NS_FAILED(rv) || !msgHdr)
+ {
+ ClearHdrCache();
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ const char16_t* colID;
+ aCol->GetIdConst(&colID);
+
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+
+ aValue.Truncate();
+ // provide a string "value" for cells that do not normally have text.
+ // use empty string for the normal states "Read", "Not Starred", "No Attachment" and "Not Junk"
+ switch (colID[0])
+ {
+ case 'a': // attachment column
+ if (flags & nsMsgMessageFlags::Attachment) {
+ nsString tmp_str;
+ tmp_str.Adopt(GetString(u"messageHasAttachment"));
+ aValue.Assign(tmp_str);
+ }
+ break;
+ case 'f': // flagged (starred) column
+ if (flags & nsMsgMessageFlags::Marked) {
+ nsString tmp_str;
+ tmp_str.Adopt(GetString(u"messageHasFlag"));
+ aValue.Assign(tmp_str);
+ }
+ break;
+ case 'j': // junk column
+ if (JunkControlsEnabled(aRow))
+ {
+ nsCString junkScoreStr;
+ msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ // Only need to assing a real value for junk, it's empty already
+ // as it should be for non-junk.
+ if (!junkScoreStr.IsEmpty() &&
+ (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_SPAM_SCORE))
+ aValue.AssignLiteral("messageJunk");
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed.");
+ }
+ break;
+ case 't':
+ if (colID[1] == 'h' && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ { // thread column
+ bool isContainer, isContainerEmpty, isContainerOpen;
+ IsContainer(aRow, &isContainer);
+ if (isContainer)
+ {
+ IsContainerEmpty(aRow, &isContainerEmpty);
+ if (!isContainerEmpty)
+ {
+ nsString tmp_str;
+
+ IsContainerOpen(aRow, &isContainerOpen);
+ tmp_str.Adopt(GetString(isContainerOpen ?
+ u"messageExpanded" :
+ u"messageCollapsed"));
+ aValue.Assign(tmp_str);
+ }
+ }
+ }
+ break;
+ case 'u': // read/unread column
+ if (!(flags & nsMsgMessageFlags::Read)) {
+ nsString tmp_str;
+ tmp_str.Adopt(GetString(u"messageUnread"));
+ aValue.Assign(tmp_str);
+ }
+ break;
+ default:
+ aValue.Assign(colID);
+ break;
+ }
+ return rv;
+}
+
+void nsMsgDBView::RememberDeletedMsgHdr(nsIMsgDBHdr *msgHdr)
+{
+ nsCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ if (mRecentlyDeletedArrayIndex >= mRecentlyDeletedMsgIds.Length())
+ mRecentlyDeletedMsgIds.AppendElement(messageId);
+ else
+ mRecentlyDeletedMsgIds[mRecentlyDeletedArrayIndex] = messageId;
+ // only remember last 20 deleted msgs.
+ mRecentlyDeletedArrayIndex = (mRecentlyDeletedArrayIndex + 1) % 20;
+}
+
+bool nsMsgDBView::WasHdrRecentlyDeleted(nsIMsgDBHdr *msgHdr)
+{
+ nsCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ return mRecentlyDeletedMsgIds.Contains(messageId);
+}
+
+//add a custom column handler
+NS_IMETHODIMP nsMsgDBView::AddColumnHandler(const nsAString& column, nsIMsgCustomColumnHandler* handler)
+{
+ bool custColInSort = false;
+ size_t index = m_customColumnHandlerIDs.IndexOf(column);
+
+ nsAutoString strColID(column);
+
+ //does not exist
+ if (index == m_customColumnHandlerIDs.NoIndex)
+ {
+ m_customColumnHandlerIDs.AppendElement(strColID);
+ m_customColumnHandlers.AppendObject(handler);
+ }
+ else
+ {
+ //insert new handler into the appropriate place in the COMPtr array
+ //no need to replace the column ID (it's the same)
+ m_customColumnHandlers.ReplaceObjectAt(handler, index);
+
+ }
+ // Check if the column name matches any of the columns in
+ // m_sortColumns, and if so, set m_sortColumns[i].mColHandler
+ for (uint32_t i = 0; i < m_sortColumns.Length(); i++)
+ {
+ MsgViewSortColumnInfo &sortInfo = m_sortColumns[i];
+ if (sortInfo.mSortType == nsMsgViewSortType::byCustom &&
+ sortInfo.mCustomColumnName.Equals(column))
+ {
+ custColInSort = true;
+ sortInfo.mColHandler = handler;
+ }
+ }
+
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ // Grouped view has its own ways.
+ return NS_OK;
+
+ // This cust col is in sort columns, and all are now registered, so sort.
+ if (custColInSort && !CustomColumnsInSortAndNotRegistered())
+ Sort(m_sortType, m_sortOrder);
+
+ return NS_OK;
+}
+
+//remove a custom column handler
+NS_IMETHODIMP nsMsgDBView::RemoveColumnHandler(const nsAString& aColID)
+{
+
+ // here we should check if the column name matches any of the columns in
+ // m_sortColumns, and if so, clear m_sortColumns[i].mColHandler
+ size_t index = m_customColumnHandlerIDs.IndexOf(aColID);
+
+ if (index != m_customColumnHandlerIDs.NoIndex)
+ {
+ m_customColumnHandlerIDs.RemoveElementAt(index);
+ m_customColumnHandlers.RemoveObjectAt(index);
+ // Check if the column name matches any of the columns in
+ // m_sortColumns, and if so, clear m_sortColumns[i].mColHandler
+ for (uint32_t i = 0; i < m_sortColumns.Length(); i++)
+ {
+ MsgViewSortColumnInfo &sortInfo = m_sortColumns[i];
+ if (sortInfo.mSortType == nsMsgViewSortType::byCustom &&
+ sortInfo.mCustomColumnName.Equals(aColID))
+ sortInfo.mColHandler = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE; //can't remove a column that isn't currently custom handled
+}
+
+//TODO: NS_ENSURE_SUCCESS
+nsIMsgCustomColumnHandler* nsMsgDBView::GetCurColumnHandler()
+{
+ return GetColumnHandler(m_curCustomColumn.get());
+}
+
+NS_IMETHODIMP nsMsgDBView::SetCurCustomColumn(const nsAString& aColID)
+{
+ m_curCustomColumn = aColID;
+
+ if (m_viewFolder)
+ {
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv,rv);
+ folderInfo->SetProperty("customSortCol", aColID);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetCurCustomColumn(nsAString &result)
+{
+ result = m_curCustomColumn;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetSecondaryCustomColumn(nsAString &result)
+{
+ result = m_secondaryCustomColumn;
+ return NS_OK;
+}
+
+nsIMsgCustomColumnHandler* nsMsgDBView::GetColumnHandler(const char16_t *colID)
+{
+ size_t index = m_customColumnHandlerIDs.IndexOf(nsDependentString(colID));
+ return (index != m_customColumnHandlerIDs.NoIndex) ?
+ m_customColumnHandlers[index] : nullptr;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetColumnHandler(const nsAString& aColID, nsIMsgCustomColumnHandler** aHandler)
+{
+ NS_ENSURE_ARG_POINTER(aHandler);
+ nsAutoString column(aColID);
+ NS_IF_ADDREF(*aHandler = GetColumnHandler(column.get()));
+ return (*aHandler) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// Check if any active sort columns are custom. If none are custom, return false
+// and go on as always. If any are custom, and all are not registered yet,
+// return true (so that the caller can postpone sort). When the custom column
+// observer is notified with MsgCreateDBView and registers the handler,
+// AddColumnHandler will sort once all required handlers are set.
+bool nsMsgDBView::CustomColumnsInSortAndNotRegistered()
+{
+ // The initial sort on view open has been started, subsequent user initiated
+ // sort callers can ignore verifying cust col registration.
+ m_checkedCustomColumns = true;
+
+ // DecodeColumnSort must have already created m_sortColumns, otherwise we
+ // can't know, but go on anyway.
+ if (!m_sortColumns.Length())
+ return false;
+
+ bool custColNotRegistered = false;
+ for (uint32_t i = 0; i < m_sortColumns.Length() && !custColNotRegistered; i++)
+ {
+ if (m_sortColumns[i].mSortType == nsMsgViewSortType::byCustom &&
+ m_sortColumns[i].mColHandler == nullptr)
+ custColNotRegistered = true;
+ }
+
+ return custColNotRegistered;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& aValue)
+{
+ const char16_t* colID;
+ aCol->GetIdConst(&colID);
+
+ if (!IsValidIndex(aRow))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ aValue.Truncate();
+
+ //attempt to retreive a custom column handler. If it exists call it and return
+ nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
+
+ if (colHandler)
+ {
+ colHandler->GetCellText(aRow, aCol, aValue);
+ return NS_OK;
+ }
+
+ return CellTextForColumn(aRow, colID, aValue);
+}
+
+NS_IMETHODIMP nsMsgDBView::CellTextForColumn(int32_t aRow,
+ const char16_t *aColumnName,
+ nsAString &aValue)
+{
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
+
+ if (NS_FAILED(rv) || !msgHdr)
+ {
+ ClearHdrCache();
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ nsCOMPtr<nsIMsgThread> thread;
+
+ switch (aColumnName[0])
+ {
+ case 's':
+ if (aColumnName[1] == 'u') // subject
+ rv = FetchSubject(msgHdr, m_flags[aRow], aValue);
+ else if (aColumnName[1] == 'e') // sender
+ rv = FetchAuthor(msgHdr, aValue);
+ else if (aColumnName[1] == 'i') // size
+ rv = FetchSize(msgHdr, aValue);
+ else if (aColumnName[1] == 't') // status
+ {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ rv = FetchStatus(flags, aValue);
+ }
+ break;
+ case 'r':
+ if (aColumnName[3] == 'i') // recipient
+ rv = FetchRecipients(msgHdr, aValue);
+ else if (aColumnName[3] == 'e') // received
+ rv = FetchDate(msgHdr, aValue, true);
+ break;
+ case 'd': // date
+ rv = FetchDate(msgHdr, aValue);
+ break;
+ case 'c': // correspondent
+ if (IsOutgoingMsg(msgHdr))
+ rv = FetchRecipients(msgHdr, aValue);
+ else
+ rv = FetchAuthor(msgHdr, aValue);
+ break;
+ case 'p': // priority
+ rv = FetchPriority(msgHdr, aValue);
+ break;
+ case 'a': // account
+ if (aColumnName[1] == 'c') // account
+ rv = FetchAccount(msgHdr, aValue);
+ break;
+ case 't':
+ // total msgs in thread column
+ if (aColumnName[1] == 'o' && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ {
+ if (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD)
+ {
+ rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv) && thread)
+ {
+ nsAutoString formattedCountString;
+ uint32_t numChildren;
+ thread->GetNumChildren(&numChildren);
+ formattedCountString.AppendInt(numChildren);
+ aValue.Assign(formattedCountString);
+ }
+ }
+ }
+ else if (aColumnName[1] == 'a') // tags
+ {
+ rv = FetchTags(msgHdr, aValue);
+ }
+ break;
+ case 'u':
+ // unread msgs in thread col
+ if (aColumnName[6] == 'C' && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ {
+ if (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD)
+ {
+ rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv) && thread)
+ {
+ nsAutoString formattedCountString;
+ uint32_t numUnreadChildren;
+ thread->GetNumUnreadChildren(&numUnreadChildren);
+ if (numUnreadChildren > 0)
+ {
+ formattedCountString.AppendInt(numUnreadChildren);
+ aValue.Assign(formattedCountString);
+ }
+ }
+ }
+ }
+ break;
+ case 'j':
+ {
+ nsCString junkScoreStr;
+ msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ CopyASCIItoUTF16(junkScoreStr, aValue);
+ }
+ break;
+ case 'i': // id
+ {
+ nsAutoString keyString;
+ nsMsgKey key;
+ msgHdr->GetMessageKey(&key);
+ keyString.AppendInt((int64_t)key);
+ aValue.Assign(keyString);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetTree(nsITreeBoxObject *tree)
+{
+ mTree = tree;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::ToggleOpenState(int32_t index)
+{
+ uint32_t numChanged;
+ nsresult rv = ToggleExpansion(index, &numChanged);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::CycleHeader(nsITreeColumn* aCol)
+{
+ // let HandleColumnClick() in threadPane.js handle it
+ // since it will set / clear the sort indicators.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::CycleCell(int32_t row, nsITreeColumn* col)
+{
+ if (!IsValidIndex(row))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+
+ //attempt to retreive a custom column handler. If it exists call it and return
+ nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
+
+ if (colHandler)
+ {
+ colHandler->CycleCell(row, col);
+ return NS_OK;
+ }
+
+ // The cyclers below don't work for the grouped header dummy row, currently.
+ // A future implementation should consider both collapsed and expanded state.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort &&
+ m_flags[row] & MSG_VIEW_FLAG_DUMMY)
+ return NS_OK;
+
+ switch (colID[0])
+ {
+ case 'u': // unreadButtonColHeader
+ if (colID[6] == 'B')
+ ApplyCommandToIndices(nsMsgViewCommandType::toggleMessageRead, (nsMsgViewIndex *) &row, 1);
+ break;
+ case 't': // tag cell, threaded cell or total cell
+ if (colID[1] == 'h')
+ {
+ ExpandAndSelectThreadByIndex(row, false);
+ }
+ else if (colID[1] == 'a')
+ {
+ // ### Do we want to keep this behaviour but switch it to tags?
+ // We could enumerate over the tags and go to the next one - it looks
+ // to me like this wasn't working before tags landed, so maybe not
+ // worth bothering with.
+ }
+ break;
+ case 'f': // flagged column
+ // toggle the flagged status of the element at row.
+ if (m_flags[row] & nsMsgMessageFlags::Marked)
+ ApplyCommandToIndices(nsMsgViewCommandType::unflagMessages, (nsMsgViewIndex *) &row, 1);
+ else
+ ApplyCommandToIndices(nsMsgViewCommandType::flagMessages, (nsMsgViewIndex *) &row, 1);
+ break;
+ case 'j': // junkStatus column
+ {
+ if (!JunkControlsEnabled(row))
+ return NS_OK;
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForViewIndex(row, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr)
+ {
+ nsCString junkScoreStr;
+ rv = msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ if (junkScoreStr.IsEmpty() || (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_HAM_SCORE))
+ ApplyCommandToIndices(nsMsgViewCommandType::junk, (nsMsgViewIndex *) &row, 1);
+ else
+ ApplyCommandToIndices(nsMsgViewCommandType::unjunk, (nsMsgViewIndex *) &row, 1);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed.");
+ }
+ }
+ break;
+ default:
+ break;
+
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::PerformAction(const char16_t *action)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::PerformActionOnRow(const char16_t *action, int32_t row)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::PerformActionOnCell(const char16_t *action, int32_t row, nsITreeColumn* col)
+{
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// end nsITreeView Implementation Methods
+///////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMsgDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount)
+{
+ m_viewFlags = viewFlags;
+ m_sortOrder = sortOrder;
+ m_sortType = sortType;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool userNeedsToAuthenticate = false;
+ // if we're PasswordProtectLocalCache, then we need to find out if the server is authenticated.
+ (void) accountManager->GetUserNeedsToAuthenticate(&userNeedsToAuthenticate);
+ if (userNeedsToAuthenticate)
+ return NS_MSG_USER_NOT_AUTHENTICATED;
+
+ if (folder) // search view will have a null folder
+ {
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(m_db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgDBService->RegisterPendingListener(folder, this);
+ m_folder = folder;
+
+ if (!m_viewFolder)
+ // There is never a viewFolder already set except for the single folder
+ // saved search case, where the backing folder m_folder is different from
+ // the m_viewFolder with its own dbFolderInfo state.
+ m_viewFolder = folder;
+
+ SetMRUTimeForFolder(m_viewFolder);
+
+ RestoreSortInfo();
+
+ // determine if we are in a news folder or not.
+ // if yes, we'll show lines instead of size, and special icons in the thread pane
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCString type;
+ rv = server->GetType(type);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // I'm not sure this is correct, because XF virtual folders with mixed news
+ // and mail can have this set.
+ mIsNews = MsgLowerCaseEqualsLiteral(type, "nntp");
+
+ // Default to a virtual folder if folder not set, since synthetic search
+ // views may not have a folder.
+ uint32_t folderFlags = nsMsgFolderFlags::Virtual;
+ if (folder)
+ folder->GetFlags(&folderFlags);
+ mIsXFVirtual = folderFlags & nsMsgFolderFlags::Virtual;
+
+ if (!mIsXFVirtual && MsgLowerCaseEqualsLiteral(type, "rss"))
+ mIsRss = true;
+
+ // special case nntp --> news since we'll break themes if we try to be consistent
+ if (mIsNews)
+ mMessageType.AssignLiteral("news");
+ else
+ CopyUTF8toUTF16(type, mMessageType);
+
+ GetImapDeleteModel(nullptr);
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ {
+ prefs->GetBoolPref("mailnews.sort_threads_by_root", &mSortThreadsByRoot);
+
+ if (mIsNews)
+ prefs->GetBoolPref("news.show_size_in_lines", &mShowSizeInLines);
+ }
+ }
+
+ nsCOMPtr<nsIArray> identities;
+ rv = accountManager->GetAllIdentities(getter_AddRefs(identities));
+ if (!identities)
+ return rv;
+
+ uint32_t count;
+ identities->GetLength(&count);
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgIdentity> identity(do_QueryElementAt(identities, i));
+ if (!identity)
+ continue;
+
+ nsCString email;
+ identity->GetEmail(email);
+ if (!email.IsEmpty()) {
+ ToLowerCaseDropPlusAddessing(email);
+ mEmails.PutEntry(email);
+ }
+
+ identity->GetReplyTo(email);
+ if (!email.IsEmpty()) {
+ ToLowerCaseDropPlusAddessing(email);
+ mEmails.PutEntry(email);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::Close()
+{
+ int32_t oldSize = GetSize();
+ // this is important, because the tree will ask us for our
+ // row count, which get determine from the number of keys.
+ m_keys.Clear();
+ // be consistent
+ m_flags.Clear();
+ m_levels.Clear();
+
+ // clear these out since they no longer apply if we're switching a folder
+ if (mJunkHdrs)
+ mJunkHdrs->Clear();
+
+ // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount()
+ if (mTree)
+ mTree->RowCountChanged(0, -oldSize);
+
+ ClearHdrCache();
+ if (m_db)
+ {
+ m_db->RemoveListener(this);
+ m_db = nullptr;
+ }
+ if (m_folder)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgDBService->UnregisterPendingListener(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t *aCount)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBView::Init(nsIMessenger * aMessengerInstance, nsIMsgWindow * aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater)
+{
+ mMessengerWeak = do_GetWeakReference(aMessengerInstance);
+ mMsgWindowWeak = do_GetWeakReference(aMsgWindow);
+ mCommandUpdater = aCmdUpdater;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetSuppressCommandUpdating(bool aSuppressCommandUpdating)
+{
+ mSuppressCommandUpdating = aSuppressCommandUpdating;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetSuppressCommandUpdating(bool * aSuppressCommandUpdating)
+{
+ *aSuppressCommandUpdating = mSuppressCommandUpdating;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetSuppressMsgDisplay(bool aSuppressDisplay)
+{
+ uint32_t numSelected = 0;
+ GetNumSelected(&numSelected);
+
+ bool forceDisplay = false;
+ if (mSuppressMsgDisplay && !aSuppressDisplay && numSelected == 1)
+ forceDisplay = true;
+
+ mSuppressMsgDisplay = aSuppressDisplay;
+ if (forceDisplay)
+ {
+ // get the view indexfor the currently selected message
+ nsMsgViewIndex viewIndex;
+ nsresult rv = GetViewIndexForFirstSelectedMsg(&viewIndex);
+ if (NS_SUCCEEDED(rv) && viewIndex != nsMsgViewIndex_None)
+ LoadMessageByViewIndex(viewIndex);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetSuppressMsgDisplay(bool * aSuppressDisplay)
+{
+ *aSuppressDisplay = mSuppressMsgDisplay;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetUsingLines(bool * aUsingLines)
+{
+ *aUsingLines = mShowSizeInLines;
+ return NS_OK;
+}
+
+int CompareViewIndices (const void *v1, const void *v2, void *)
+{
+ nsMsgViewIndex i1 = *(nsMsgViewIndex*) v1;
+ nsMsgViewIndex i2 = *(nsMsgViewIndex*) v2;
+ return i1 - i2;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetIndicesForSelection(uint32_t *length, nsMsgViewIndex **indices)
+{
+ NS_ENSURE_ARG_POINTER(length);
+ *length = 0;
+ NS_ENSURE_ARG_POINTER(indices);
+ *indices = nullptr;
+
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ uint32_t numIndices = selection.Length();
+ if (!numIndices) return NS_OK;
+ *length = numIndices;
+
+ uint32_t datalen = numIndices * sizeof(nsMsgViewIndex);
+ *indices = (nsMsgViewIndex *)NS_Alloc(datalen);
+ if (!*indices) return NS_ERROR_OUT_OF_MEMORY;
+ memcpy(*indices, selection.Elements(), datalen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetSelectedMsgHdrs(uint32_t *aLength, nsIMsgDBHdr ***aResult)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aLength);
+ *aLength = 0;
+
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ uint32_t numIndices = selection.Length();
+ if (!numIndices) return NS_OK;
+
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetHeadersFromSelection(selection.Elements(), numIndices, messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numMsgsSelected;
+ messages->GetLength(&numMsgsSelected);
+
+ nsIMsgDBHdr **headers = static_cast<nsIMsgDBHdr**>(NS_Alloc(
+ sizeof(nsIMsgDBHdr*) * numMsgsSelected));
+ for (uint32_t i = 0; i < numMsgsSelected; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgHdr.forget(&headers[i]); // Already AddRefed
+ }
+
+ *aLength = numMsgsSelected;
+ *aResult = headers;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetMsgHdrsForSelection(nsIMutableArray **aResult)
+{
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ uint32_t numIndices = selection.Length();
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetHeadersFromSelection(selection.Elements(), numIndices, messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ messages.forget(aResult);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetURIsForSelection(uint32_t *length, char ***uris)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG_POINTER(length);
+ *length = 0;
+
+ NS_ENSURE_ARG_POINTER(uris);
+ *uris = nullptr;
+
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ uint32_t numIndices = selection.Length();
+ if (!numIndices) return NS_OK;
+
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetHeadersFromSelection(selection.Elements(), numIndices, messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ messages->GetLength(length);
+ uint32_t numMsgsSelected = *length;
+
+ char **outArray, **next;
+ next = outArray = (char **)moz_xmalloc(numMsgsSelected * sizeof(char *));
+ if (!outArray) return NS_ERROR_OUT_OF_MEMORY;
+ for (uint32_t i = 0; i < numMsgsSelected; i++)
+ {
+ nsCString tmpUri;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ rv = GenerateURIForMsgKey(msgKey, folder, tmpUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+ *next = ToNewCString(tmpUri);
+ if (!*next)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ next++;
+ }
+
+ *uris = outArray;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetURIForViewIndex(nsMsgViewIndex index, nsACString &result)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> folder = m_folder;
+ if (!folder)
+ {
+ rv = GetFolderForViewIndex(index, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ if (index == nsMsgViewIndex_None || index >= m_flags.Length() ||
+ m_flags[index] & MSG_VIEW_FLAG_DUMMY)
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ return GenerateURIForMsgKey(m_keys[index], folder, result);
+}
+
+NS_IMETHODIMP nsMsgDBView::DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder *destFolder)
+{
+ NS_ENSURE_ARG_POINTER(destFolder);
+
+ nsMsgViewIndexArray selection;
+
+ GetSelectedIndices(selection);
+
+ nsMsgViewIndex *indices = selection.Elements();
+ int32_t numIndices = selection.Length();
+
+ nsresult rv = NS_OK;
+ switch (command) {
+ case nsMsgViewCommandType::copyMessages:
+ case nsMsgViewCommandType::moveMessages:
+ NoteStartChange(nsMsgViewNotificationCode::none, 0, 0);
+ rv = ApplyCommandToIndicesWithFolder(command, indices, numIndices, destFolder);
+ NoteEndChange(nsMsgViewNotificationCode::none, 0, 0);
+ break;
+ default:
+ NS_ASSERTION(false, "invalid command type");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ return rv;
+
+}
+
+NS_IMETHODIMP nsMsgDBView::DoCommand(nsMsgViewCommandTypeValue command)
+{
+ nsMsgViewIndexArray selection;
+
+ GetSelectedIndices(selection);
+
+ nsMsgViewIndex *indices = selection.Elements();
+ int32_t numIndices = selection.Length();
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+
+ nsresult rv = NS_OK;
+ switch (command)
+ {
+ case nsMsgViewCommandType::downloadSelectedForOffline:
+ return DownloadForOffline(msgWindow, indices, numIndices);
+ case nsMsgViewCommandType::downloadFlaggedForOffline:
+ return DownloadFlaggedForOffline(msgWindow);
+ case nsMsgViewCommandType::markMessagesRead:
+ case nsMsgViewCommandType::markMessagesUnread:
+ case nsMsgViewCommandType::toggleMessageRead:
+ case nsMsgViewCommandType::flagMessages:
+ case nsMsgViewCommandType::unflagMessages:
+ case nsMsgViewCommandType::deleteMsg:
+ case nsMsgViewCommandType::undeleteMsg:
+ case nsMsgViewCommandType::deleteNoTrash:
+ case nsMsgViewCommandType::markThreadRead:
+ case nsMsgViewCommandType::junk:
+ case nsMsgViewCommandType::unjunk:
+ NoteStartChange(nsMsgViewNotificationCode::none, 0, 0);
+ rv = ApplyCommandToIndices(command, indices, numIndices);
+ NoteEndChange(nsMsgViewNotificationCode::none, 0, 0);
+ break;
+ case nsMsgViewCommandType::selectAll:
+ if (mTreeSelection)
+ {
+ // if in threaded mode, we need to expand all before selecting
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ rv = ExpandAll();
+ mTreeSelection->SelectAll();
+ if (mTree)
+ mTree->Invalidate();
+ }
+ break;
+ case nsMsgViewCommandType::selectThread:
+ rv = ExpandAndSelectThread();
+ break;
+ case nsMsgViewCommandType::selectFlagged:
+ if (!mTreeSelection)
+ rv = NS_ERROR_UNEXPECTED;
+ else
+ {
+ mTreeSelection->SetSelectEventsSuppressed(true);
+ mTreeSelection->ClearSelection();
+ // XXX ExpandAll?
+ nsMsgViewIndex numIndices = GetSize();
+ for (nsMsgViewIndex curIndex = 0; curIndex < numIndices; curIndex++)
+ {
+ if (m_flags[curIndex] & nsMsgMessageFlags::Marked)
+ mTreeSelection->ToggleSelect(curIndex);
+ }
+ mTreeSelection->SetSelectEventsSuppressed(false);
+ }
+ break;
+ case nsMsgViewCommandType::markAllRead:
+ if (m_folder)
+ {
+ SetSuppressChangeNotifications(true);
+ rv = m_folder->MarkAllMessagesRead(msgWindow);
+ SetSuppressChangeNotifications(false);
+ if (mTree)
+ mTree->Invalidate();
+ }
+ break;
+ case nsMsgViewCommandType::toggleThreadWatched:
+ rv = ToggleWatched(indices, numIndices);
+ break;
+ case nsMsgViewCommandType::expandAll:
+ rv = ExpandAll();
+ m_viewFlags |= nsMsgViewFlagsType::kExpandAll;
+ SetViewFlags(m_viewFlags);
+ if (mTree)
+ mTree->Invalidate();
+ break;
+ case nsMsgViewCommandType::collapseAll:
+ rv = CollapseAll();
+ m_viewFlags &= ~nsMsgViewFlagsType::kExpandAll;
+ SetViewFlags(m_viewFlags);
+ if(mTree)
+ mTree->Invalidate();
+ break;
+ default:
+ NS_ASSERTION(false, "invalid command type");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ return rv;
+}
+
+bool nsMsgDBView::ServerSupportsFilterAfterTheFact()
+{
+ if (!m_folder) // cross folder virtual folders might not have a folder set.
+ return false;
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv))
+ return false; // unexpected
+
+ // filter after the fact is implement using search
+ // so if you can't search, you can't filter after the fact
+ bool canSearch;
+ rv = server->GetCanSearchMessages(&canSearch);
+ if (NS_FAILED(rv))
+ return false; // unexpected
+
+ return canSearch;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetCommandStatus(nsMsgViewCommandTypeValue command, bool *selectable_p, nsMsgViewCommandCheckStateValue *selected_p)
+{
+ nsresult rv = NS_OK;
+
+ bool haveSelection;
+ int32_t rangeCount;
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ int32_t numIndices = selection.Length();
+ nsMsgViewIndex *indices = selection.Elements();
+ // if range count is non-zero, we have at least one item selected, so we have a selection
+ if (mTreeSelection && NS_SUCCEEDED(mTreeSelection->GetRangeCount(&rangeCount)) && rangeCount > 0)
+ haveSelection = NonDummyMsgSelected(indices, numIndices);
+ else
+ // If we don't have a tree selection we must be in stand alone mode.
+ haveSelection = IsValidIndex(m_currentlyDisplayedViewIndex);
+
+ switch (command)
+ {
+ case nsMsgViewCommandType::deleteMsg:
+ case nsMsgViewCommandType::deleteNoTrash:
+ {
+ bool canDelete;
+ if (m_folder && NS_SUCCEEDED(m_folder->GetCanDeleteMessages(&canDelete)) && !canDelete)
+ *selectable_p = false;
+ else
+ *selectable_p = haveSelection;
+ }
+ break;
+ case nsMsgViewCommandType::applyFilters:
+ // disable if no messages
+ // XXX todo, check that we have filters, and at least one is enabled
+ *selectable_p = GetSize();
+ if (*selectable_p)
+ *selectable_p = ServerSupportsFilterAfterTheFact();
+ break;
+ case nsMsgViewCommandType::runJunkControls:
+ // disable if no messages
+ // XXX todo, check that we have JMC enabled?
+ *selectable_p = GetSize() && JunkControlsEnabled(nsMsgViewIndex_None);
+ break;
+ case nsMsgViewCommandType::deleteJunk:
+ {
+ // disable if no messages, or if we can't delete (like news and certain imap folders)
+ bool canDelete;
+ *selectable_p = GetSize() && (m_folder && NS_SUCCEEDED(m_folder->GetCanDeleteMessages(&canDelete)) && canDelete);
+ }
+ break;
+ case nsMsgViewCommandType::markMessagesRead:
+ case nsMsgViewCommandType::markMessagesUnread:
+ case nsMsgViewCommandType::toggleMessageRead:
+ case nsMsgViewCommandType::flagMessages:
+ case nsMsgViewCommandType::unflagMessages:
+ case nsMsgViewCommandType::toggleThreadWatched:
+ case nsMsgViewCommandType::markThreadRead:
+ case nsMsgViewCommandType::downloadSelectedForOffline:
+ *selectable_p = haveSelection;
+ break;
+ case nsMsgViewCommandType::junk:
+ case nsMsgViewCommandType::unjunk:
+ *selectable_p = haveSelection && numIndices && JunkControlsEnabled(selection[0]);
+ break;
+ case nsMsgViewCommandType::cmdRequiringMsgBody:
+ *selectable_p = haveSelection && (!WeAreOffline() || OfflineMsgSelected(indices, numIndices));
+ break;
+ case nsMsgViewCommandType::downloadFlaggedForOffline:
+ case nsMsgViewCommandType::markAllRead:
+ *selectable_p = true;
+ break;
+ default:
+ NS_ASSERTION(false, "invalid command type");
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+// This method needs to be overridden by the various view classes
+// that have different kinds of threads. For example, in a
+// threaded quick search db view, we'd only want to include children
+// of the thread that fit the view (IMO). And when we have threaded
+// cross folder views, we would include all the children of the
+// cross-folder thread.
+nsresult nsMsgDBView::ListCollapsedChildren(nsMsgViewIndex viewIndex,
+ nsIMutableArray *messageArray)
+{
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgThread> thread;
+ GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(msgHdr));
+ if (!msgHdr)
+ {
+ NS_ASSERTION(false, "couldn't find message to expand");
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ }
+ nsresult rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numChildren;
+ thread->GetNumChildren(&numChildren);
+ for (uint32_t i = 1; i < numChildren && NS_SUCCEEDED(rv); i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = thread->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+ if (!msgHdr)
+ continue;
+ rv = messageArray->AppendElement(msgHdr, false);
+ }
+ return rv;
+}
+
+bool nsMsgDBView::OperateOnMsgsInCollapsedThreads()
+{
+ if (mTreeSelection)
+ {
+ nsCOMPtr<nsITreeBoxObject> selTree;
+ mTreeSelection->GetTree(getter_AddRefs(selTree));
+ // no tree means stand-alone message window
+ if (!selTree)
+ return false;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool includeCollapsedMsgs = false;
+ prefBranch->GetBoolPref("mail.operate_on_msgs_in_collapsed_threads", &includeCollapsedMsgs);
+ return includeCollapsedMsgs;
+}
+
+nsresult nsMsgDBView::GetHeadersFromSelection(uint32_t *indices,
+ uint32_t numIndices,
+ nsIMutableArray *messageArray)
+{
+ nsresult rv = NS_OK;
+
+ // Don't include collapsed messages if the front end failed to summarize
+ // the selection.
+ bool includeCollapsedMsgs = OperateOnMsgsInCollapsedThreads() &&
+ !mSummarizeFailed;
+
+ for (uint32_t index = 0;
+ index < (nsMsgViewIndex) numIndices && NS_SUCCEEDED(rv); index++)
+ {
+ nsMsgViewIndex viewIndex = indices[index];
+ if (viewIndex == nsMsgViewIndex_None)
+ continue;
+ uint32_t viewIndexFlags = m_flags[viewIndex];
+ if (viewIndexFlags & MSG_VIEW_FLAG_DUMMY)
+ {
+ // if collapsed dummy header selected, list its children
+ if (includeCollapsedMsgs && viewIndexFlags & nsMsgMessageFlags::Elided &&
+ m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ rv = ListCollapsedChildren(viewIndex, messageArray);
+ continue;
+ }
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr)
+ {
+ rv = messageArray->AppendElement(msgHdr, false);
+ if (NS_SUCCEEDED(rv) && includeCollapsedMsgs &&
+ viewIndexFlags & nsMsgMessageFlags::Elided &&
+ viewIndexFlags & MSG_VIEW_FLAG_HASCHILDREN &&
+ m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ rv = ListCollapsedChildren(viewIndex, messageArray);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsMsgDBView::CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool isMove, nsIMsgFolder *destFolder)
+{
+ if (m_deletingRows)
+ {
+ NS_ASSERTION(false, "Last move did not complete");
+ return NS_OK;
+ }
+
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(destFolder);
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetHeadersFromSelection(indices, numIndices, messageArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_deletingRows = isMove && mDeleteModel != nsMsgImapDeleteModels::IMAPDelete;
+ if (m_deletingRows)
+ mIndicesToNoteChange.AppendElements(indices, numIndices);
+
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return copyService->CopyMessages(m_folder /* source folder */, messageArray, destFolder, isMove, nullptr /* listener */, window, true /*allowUndo*/);
+}
+
+nsresult
+nsMsgDBView::ApplyCommandToIndicesWithFolder(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices,
+ int32_t numIndices, nsIMsgFolder *destFolder)
+{
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG_POINTER(destFolder);
+
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ switch (command) {
+ case nsMsgViewCommandType::copyMessages:
+ NS_ASSERTION(!(m_folder == destFolder), "The source folder and the destination folder are the same");
+ if (m_folder != destFolder)
+ rv = CopyMessages(msgWindow, indices, numIndices, false /* isMove */, destFolder);
+ break;
+ case nsMsgViewCommandType::moveMessages:
+ NS_ASSERTION(!(m_folder == destFolder), "The source folder and the destination folder are the same");
+ if (m_folder != destFolder)
+ rv = CopyMessages(msgWindow, indices, numIndices, true /* isMove */, destFolder);
+ break;
+ default:
+ NS_ASSERTION(false, "unhandled command");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ return rv;
+}
+
+nsresult
+nsMsgDBView::ApplyCommandToIndices(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices,
+ int32_t numIndices)
+{
+ NS_ASSERTION(numIndices >= 0, "nsMsgDBView::ApplyCommandToIndices(): "
+ "numIndices is negative!");
+
+ if (numIndices == 0)
+ return NS_OK; // return quietly, just in case
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetFolderForViewIndex(indices[0], getter_AddRefs(folder));
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ if (command == nsMsgViewCommandType::deleteMsg)
+ return DeleteMessages(msgWindow, indices, numIndices, false);
+ if (command == nsMsgViewCommandType::deleteNoTrash)
+ return DeleteMessages(msgWindow, indices, numIndices, true);
+
+ nsTArray<nsMsgKey> imapUids;
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
+ bool thisIsImapFolder = (imapFolder != nullptr);
+ nsCOMPtr<nsIJunkMailPlugin> junkPlugin;
+
+ // if this is a junk command, get the junk plugin.
+ if (command == nsMsgViewCommandType::junk ||
+ command == nsMsgViewCommandType::unjunk)
+ {
+ // get the folder from the first item; we assume that
+ // all messages in the view are from the same folder (no
+ // more junk status column in the 'search messages' dialog
+ // like in earlier versions...)
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFilterPlugin> filterPlugin;
+ rv = server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ junkPlugin = do_QueryInterface(filterPlugin, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mJunkHdrs)
+ {
+ mJunkHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+
+ folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false, true /*dbBatching*/);
+
+ // no sense going through the code that handles messages in collasped threads
+ // for mark thread read.
+ if (command == nsMsgViewCommandType::markThreadRead)
+ {
+ for (int32_t index = 0; index < numIndices; index++)
+ SetThreadOfMsgReadByIndex(indices[index], imapUids, true);
+ }
+ else
+ {
+ // Turn the selection into an array of msg hdrs. This may include messages
+ // in collapsed threads
+ uint32_t length;
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetHeadersFromSelection(indices, numIndices, messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ messages->GetLength(&length);
+
+ if (thisIsImapFolder)
+ imapUids.SetLength(length);
+
+ for (uint32_t i = 0; i < length; i++)
+ {
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i);
+ msgHdr->GetMessageKey(&msgKey);
+ if (thisIsImapFolder)
+ imapUids[i] = msgKey;
+
+ switch (command)
+ {
+ case nsMsgViewCommandType::junk:
+ mNumMessagesRemainingInBatch++;
+ mJunkHdrs->AppendElement(msgHdr, false);
+ rv = SetMsgHdrJunkStatus(junkPlugin.get(), msgHdr,
+ nsIJunkMailPlugin::JUNK);
+ break;
+ case nsMsgViewCommandType::unjunk:
+ mNumMessagesRemainingInBatch++;
+ mJunkHdrs->AppendElement(msgHdr, false);
+ rv = SetMsgHdrJunkStatus(junkPlugin.get(), msgHdr,
+ nsIJunkMailPlugin::GOOD);
+ break;
+ case nsMsgViewCommandType::toggleMessageRead:
+ case nsMsgViewCommandType::undeleteMsg:
+ case nsMsgViewCommandType::markMessagesRead:
+ case nsMsgViewCommandType::markMessagesUnread:
+ case nsMsgViewCommandType::unflagMessages:
+ case nsMsgViewCommandType::flagMessages:
+ break; // this is completely handled in the code below.
+ default:
+ NS_ERROR("unhandled command");
+ break;
+ }
+ }
+
+ switch (command)
+ {
+ case nsMsgViewCommandType::toggleMessageRead:
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, 0);
+ if (!msgHdr)
+ break;
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ folder->MarkMessagesRead(messages,
+ !(msgFlags & nsMsgMessageFlags::Read));
+ }
+ break;
+ case nsMsgViewCommandType::markMessagesRead:
+ case nsMsgViewCommandType::markMessagesUnread:
+ folder->MarkMessagesRead(messages,
+ command == nsMsgViewCommandType::markMessagesRead);
+ break;
+ case nsMsgViewCommandType::unflagMessages:
+ case nsMsgViewCommandType::flagMessages:
+ folder->MarkMessagesFlagged(messages,
+ command == nsMsgViewCommandType::flagMessages);
+ break;
+ default:
+ break;
+
+ }
+ // Provide junk-related batch notifications
+ if ((command == nsMsgViewCommandType::junk) ||
+ (command == nsMsgViewCommandType::unjunk)) {
+ nsCOMPtr<nsIMsgFolderNotificationService>
+ notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyItemEvent(messages,
+ NS_LITERAL_CSTRING("JunkStatusChanged"),
+ (command == nsMsgViewCommandType::junk) ?
+ kJunkMsgAtom : kNotJunkMsgAtom);
+ }
+ }
+
+ folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true, true /*dbBatching*/);
+
+ if (thisIsImapFolder)
+ {
+ imapMessageFlagsType flags = kNoImapMsgFlag;
+ bool addFlags = false;
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ switch (command)
+ {
+ case nsMsgViewCommandType::markThreadRead:
+ flags |= kImapMsgSeenFlag;
+ addFlags = true;
+ break;
+ case nsMsgViewCommandType::undeleteMsg:
+ flags = kImapMsgDeletedFlag;
+ addFlags = false;
+ break;
+ case nsMsgViewCommandType::junk:
+ return imapFolder->StoreCustomKeywords(msgWindow,
+ NS_LITERAL_CSTRING("Junk"),
+ NS_LITERAL_CSTRING("NonJunk"),
+ imapUids.Elements(), imapUids.Length(),
+ nullptr);
+ case nsMsgViewCommandType::unjunk:
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ GetHdrForFirstSelectedMessage(getter_AddRefs(msgHdr));
+ uint32_t msgFlags = 0;
+ if (msgHdr)
+ msgHdr->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::IMAPDeleted)
+ imapFolder->StoreImapFlags(kImapMsgDeletedFlag, false,
+ imapUids.Elements(),
+ imapUids.Length(), nullptr);
+ return imapFolder->StoreCustomKeywords(msgWindow,
+ NS_LITERAL_CSTRING("NonJunk"),
+ NS_LITERAL_CSTRING("Junk"),
+ imapUids.Elements(), imapUids.Length(),
+ nullptr);
+ }
+
+ default:
+ break;
+ }
+
+ if (flags != kNoImapMsgFlag) // can't get here without thisIsImapThreadPane == TRUE
+ imapFolder->StoreImapFlags(flags, addFlags, imapUids.Elements(), imapUids.Length(), nullptr);
+
+ }
+
+ return rv;
+}
+
+// view modifications methods by index
+
+// This method just removes the specified line from the view. It does
+// NOT delete it from the database.
+nsresult nsMsgDBView::RemoveByIndex(nsMsgViewIndex index)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ m_keys.RemoveElementAt(index);
+ m_flags.RemoveElementAt(index);
+ m_levels.RemoveElementAt(index);
+
+ // the call to NoteChange() has to happen after we remove the key
+ // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
+ if (!m_deletingRows)
+ NoteChange(index, -1, nsMsgViewNotificationCode::insertOrDelete); // an example where view is not the listener - D&D messages
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage)
+{
+ if (m_deletingRows)
+ {
+ NS_WARNING("Last delete did not complete");
+ return NS_OK;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetHeadersFromSelection(indices, numIndices, messageArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numMsgs;
+ messageArray->GetLength(&numMsgs);
+
+ const char *warnCollapsedPref = "mail.warn_on_collapsed_thread_operation";
+ const char *warnShiftDelPref = "mail.warn_on_shift_delete";
+ const char *warnNewsPref = "news.warn_on_delete";
+ const char *warnTrashDelPref = "mail.warn_on_delete_from_trash";
+ const char *activePref = nullptr;
+ nsString warningName;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool trashFolder = false;
+ rv = m_folder->GetFlag(nsMsgFolderFlags::Trash, &trashFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (trashFolder)
+ {
+ bool pref = false;
+ prefBranch->GetBoolPref(warnTrashDelPref, &pref);
+ if (pref)
+ {
+ activePref = warnTrashDelPref;
+ warningName.AssignLiteral("confirmMsgDelete.deleteFromTrash.desc");
+ }
+ }
+
+ if (!activePref && (uint32_t(numIndices) != numMsgs))
+ {
+ bool pref = false;
+ prefBranch->GetBoolPref(warnCollapsedPref, &pref);
+ if (pref)
+ {
+ activePref = warnCollapsedPref;
+ warningName.AssignLiteral("confirmMsgDelete.collapsed.desc");
+ }
+ }
+
+ if (!activePref && deleteStorage && !trashFolder)
+ {
+ bool pref = false;
+ prefBranch->GetBoolPref(warnShiftDelPref, &pref);
+ if (pref)
+ {
+ activePref = warnShiftDelPref;
+ warningName.AssignLiteral("confirmMsgDelete.deleteNoTrash.desc");
+ }
+ }
+
+ if (!activePref && mIsNews)
+ {
+ bool pref = false;
+ prefBranch->GetBoolPref(warnNewsPref, &pref);
+ if (pref)
+ {
+ activePref = warnNewsPref;
+ warningName.AssignLiteral("confirmMsgDelete.deleteNoTrash.desc");
+ }
+ }
+
+ if (activePref)
+ {
+ nsCOMPtr<nsIPrompt> dialog;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = wwatch->GetNewPrompter(0, getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool dontAsk = false; // "Don't ask..." - unchecked by default.
+ int32_t buttonPressed = 0;
+
+ nsString dialogTitle;
+ nsString confirmString;
+ nsString checkboxText;
+ nsString buttonApplyNowText;
+ dialogTitle.Adopt(GetString(u"confirmMsgDelete.title"));
+ checkboxText.Adopt(GetString(u"confirmMsgDelete.dontAsk.label"));
+ buttonApplyNowText.Adopt(GetString(u"confirmMsgDelete.delete.label"));
+
+ confirmString.Adopt(GetString(warningName.get()));
+
+ const uint32_t buttonFlags =
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);
+ rv = dialog->ConfirmEx(dialogTitle.get(), confirmString.get(), buttonFlags,
+ buttonApplyNowText.get(), nullptr, nullptr,
+ checkboxText.get(), &dontAsk, &buttonPressed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (buttonPressed)
+ return NS_ERROR_FAILURE;
+ if (dontAsk)
+ prefBranch->SetBoolPref(activePref, false);
+ }
+
+ if (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete)
+ m_deletingRows = true;
+
+ if (m_deletingRows)
+ mIndicesToNoteChange.AppendElements(indices, numIndices);
+
+ rv = m_folder->DeleteMessages(messageArray, window, deleteStorage, false, nullptr, true /*allow Undo*/ );
+ if (NS_FAILED(rv))
+ m_deletingRows = false;
+ return rv;
+}
+
+nsresult nsMsgDBView::DownloadForOffline(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++)
+ {
+ nsMsgKey key = m_keys[indices[index]];
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (msgHdr)
+ {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ if (!(flags & nsMsgMessageFlags::Offline))
+ messageArray->AppendElement(msgHdr, false);
+ }
+ }
+ m_folder->DownloadMessagesForOffline(messageArray, window);
+ return rv;
+}
+
+nsresult nsMsgDBView::DownloadFlaggedForOffline(nsIMsgWindow *window)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ rv = GetMessageEnumerator(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ bool hasMore;
+
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports);
+ if (pHeader && NS_SUCCEEDED(rv))
+ {
+ uint32_t flags;
+ pHeader->GetFlags(&flags);
+ if ((flags & nsMsgMessageFlags::Marked) && !(flags & nsMsgMessageFlags::Offline))
+ messageArray->AppendElement(pHeader, false);
+ }
+ }
+ }
+ m_folder->DownloadMessagesForOffline(messageArray, window);
+ return rv;
+}
+
+// read/unread handling.
+nsresult nsMsgDBView::ToggleReadByIndex(nsMsgViewIndex index)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ return SetReadByIndex(index, !(m_flags[index] & nsMsgMessageFlags::Read));
+}
+
+nsresult nsMsgDBView::SetReadByIndex(nsMsgViewIndex index, bool read)
+{
+ nsresult rv;
+
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ if (read)
+ {
+ OrExtraFlag(index, nsMsgMessageFlags::Read);
+ // MarkRead() will clear this flag in the db
+ // and then call OnKeyChange(), but
+ // because we are the instigator of the change
+ // we'll ignore the change.
+ //
+ // so we need to clear it in m_flags
+ // to keep the db and m_flags in sync
+ AndExtraFlag(index, ~nsMsgMessageFlags::New);
+ }
+ else
+ {
+ AndExtraFlag(index, ~nsMsgMessageFlags::Read);
+ }
+
+ nsCOMPtr <nsIMsgDatabase> dbToUse;
+ rv = GetDBForViewIndex(index, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dbToUse->MarkRead(m_keys[index], read, this);
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ nsMsgViewIndex threadIndex = GetThreadIndex(index);
+ if (threadIndex != index)
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ return rv;
+}
+
+nsresult nsMsgDBView::SetThreadOfMsgReadByIndex(nsMsgViewIndex index, nsTArray<nsMsgKey> &keysMarkedRead, bool /*read*/)
+{
+ nsresult rv;
+
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ rv = MarkThreadOfMsgRead(m_keys[index], index, keysMarkedRead, true);
+ return rv;
+}
+
+nsresult nsMsgDBView::SetFlaggedByIndex(nsMsgViewIndex index, bool mark)
+{
+ nsresult rv;
+
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ nsCOMPtr <nsIMsgDatabase> dbToUse;
+ rv = GetDBForViewIndex(index, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mark)
+ OrExtraFlag(index, nsMsgMessageFlags::Marked);
+ else
+ AndExtraFlag(index, ~nsMsgMessageFlags::Marked);
+
+ rv = dbToUse->MarkMarked(m_keys[index], mark, this);
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ return rv;
+}
+
+nsresult nsMsgDBView::SetMsgHdrJunkStatus(nsIJunkMailPlugin *aJunkPlugin,
+ nsIMsgDBHdr *aMsgHdr,
+ nsMsgJunkStatus aNewClassification)
+{
+ // get the old junk score
+ //
+ nsCString junkScoreStr;
+ nsresult rv = aMsgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+
+ // and the old origin
+ //
+ nsCString oldOriginStr;
+ rv = aMsgHdr->GetStringProperty("junkscoreorigin",
+ getter_Copies(oldOriginStr));
+
+ // if this was not classified by the user, say so
+ //
+ nsMsgJunkStatus oldUserClassification;
+ if (oldOriginStr.get()[0] != 'u') {
+ oldUserClassification = nsIJunkMailPlugin::UNCLASSIFIED;
+ }
+ else {
+ // otherwise, pass the actual user classification
+ if (junkScoreStr.IsEmpty())
+ oldUserClassification = nsIJunkMailPlugin::UNCLASSIFIED;
+ else if (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_SPAM_SCORE)
+ oldUserClassification = nsIJunkMailPlugin::JUNK;
+ else
+ oldUserClassification = nsIJunkMailPlugin::GOOD;
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed.");
+ }
+
+ // get the URI for this message so we can pass it to the plugin
+ //
+ nsCString uri;
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIMsgDatabase> db;
+ aMsgHdr->GetMessageKey(&msgKey);
+ rv = aMsgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ GenerateURIForMsgKey(msgKey, folder, uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // tell the plugin about this change, so that it can (potentially)
+ // adjust its database appropriately
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ rv = aJunkPlugin->SetMessageClassification(
+ uri.get(), oldUserClassification, aNewClassification, msgWindow, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // this routine is only reached if the user someone touched the UI
+ // and told us the junk status of this message.
+ // Set origin first so that listeners on the junkscore will
+ // know the correct origin.
+ rv = db->SetStringProperty(msgKey, "junkscoreorigin", "user");
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetStringPropertyByIndex failed");
+
+ // set the junk score on the message itself
+
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(aNewClassification == nsIJunkMailPlugin::JUNK ?
+ nsIJunkMailPlugin::IS_SPAM_SCORE:
+ nsIJunkMailPlugin::IS_HAM_SCORE);
+ db->SetStringProperty(msgKey, "junkscore", msgJunkScore.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+nsresult
+nsMsgDBView::GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder)
+{
+ NS_IF_ADDREF(*aFolder = m_folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnMessageClassified(const char *aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent)
+
+{
+ // Note: we know all messages in a batch have the same
+ // classification, since unlike OnMessageClassified
+ // methods in other classes (such as nsLocalMailFolder
+ // and nsImapMailFolder), this class, nsMsgDBView, currently
+ // only triggers message classifications due to a command to
+ // mark some of the messages in the view as junk, or as not
+ // junk - so the classification is dictated to the filter,
+ // not suggested by it.
+ //
+ // for this reason the only thing we (may) have to do is
+ // perform the action on all of the junk messages
+ //
+
+ uint32_t numJunk;
+ mJunkHdrs->GetLength(&numJunk);
+ NS_ASSERTION((aClassification == nsIJunkMailPlugin::GOOD) ||
+ numJunk,
+ "the classification of a manually-marked junk message has"
+ "been classified as junk, yet there seem to be no such outstanding messages");
+
+ // is this the last message in the batch?
+
+ if (--mNumMessagesRemainingInBatch == 0 && numJunk > 0)
+ {
+ PerformActionsOnJunkMsgs(aClassification == nsIJunkMailPlugin::JUNK);
+ mJunkHdrs->Clear();
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBView::PerformActionsOnJunkMsgs(bool msgsAreJunk)
+{
+ uint32_t numJunkHdrs;
+ mJunkHdrs->GetLength(&numJunkHdrs);
+ if (!numJunkHdrs)
+ {
+ NS_ERROR("no indices of marked-as-junk messages to act on");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ nsCOMPtr<nsIMsgDBHdr> firstHdr(do_QueryElementAt(mJunkHdrs, 0));
+ firstHdr->GetFolder(getter_AddRefs(srcFolder));
+
+ bool moveMessages, changeReadState;
+ nsCOMPtr<nsIMsgFolder> targetFolder;
+
+ nsresult rv = DetermineActionsForJunkChange(msgsAreJunk, srcFolder,
+ moveMessages, changeReadState,
+ getter_AddRefs(targetFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // nothing to do, bail out
+ if (!(moveMessages || changeReadState))
+ return NS_OK;
+ if (changeReadState)
+ {
+ // notes on marking junk as read:
+ // 1. there are 2 occasions on which junk messages are marked as
+ // read: after a manual marking (here and in the front end) and after
+ // automatic classification by the bayesian filter (see code for local
+ // mail folders and for imap mail folders). The server-specific
+ // markAsReadOnSpam pref only applies to the latter, the former is
+ // controlled by "mailnews.ui.junk.manualMarkAsJunkMarksRead".
+ // 2. even though move/delete on manual mark may be
+ // turned off, we might still need to mark as read
+
+ NoteStartChange(nsMsgViewNotificationCode::none, 0, 0);
+ rv = srcFolder->MarkMessagesRead(mJunkHdrs, msgsAreJunk);
+ NoteEndChange(nsMsgViewNotificationCode::none, 0, 0);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "marking marked-as-junk messages as read failed");
+ }
+ if (moveMessages)
+ {
+ // check if one of the messages to be junked is actually selected
+ // if more than one message being junked, one must be selected.
+ // if no tree selection at all, must be in stand-alone message window.
+ bool junkedMsgSelected = numJunkHdrs > 1 || !mTreeSelection;
+ for (nsMsgViewIndex junkIndex = 0; !junkedMsgSelected && junkIndex < numJunkHdrs; junkIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> junkHdr(do_QueryElementAt(mJunkHdrs, junkIndex, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgViewIndex hdrIndex = FindHdr(junkHdr);
+ if (hdrIndex != nsMsgViewIndex_None)
+ mTreeSelection->IsSelected(hdrIndex, &junkedMsgSelected);
+ }
+
+ // if a junked msg is selected, tell the FE to call SetNextMessageAfterDelete() because a delete is coming
+ if (junkedMsgSelected && mCommandUpdater)
+ {
+ rv = mCommandUpdater->UpdateNextMessageAfterDelete();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ NoteStartChange(nsMsgViewNotificationCode::none, 0, 0);
+ if (targetFolder)
+ {
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = copyService->CopyMessages(srcFolder , mJunkHdrs, targetFolder,
+ true, nullptr, msgWindow, true);
+ }
+ else if (msgsAreJunk)
+ {
+ if (mDeleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ {
+ // Unfortunately the DeleteMessages in this case is interpreted by IMAP as
+ // a delete toggle. So what we have to do is to assemble a new delete
+ // array, keeping only those that are not deleted.
+ //
+ nsCOMPtr<nsIMutableArray> hdrsToDelete = do_CreateInstance("@mozilla.org/array;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t cnt;
+ rv = mJunkHdrs->GetLength(&cnt);
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(mJunkHdrs, i);
+ if (msgHdr)
+ {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ if (!(flags & nsMsgMessageFlags::IMAPDeleted))
+ hdrsToDelete->AppendElement(msgHdr, false);
+ }
+ }
+ hdrsToDelete->GetLength(&cnt);
+ if (cnt)
+ rv = srcFolder->DeleteMessages(hdrsToDelete, msgWindow, false, false,
+ nullptr, true);
+ }
+ else
+ rv = srcFolder->DeleteMessages(mJunkHdrs, msgWindow, false, false,
+ nullptr, true);
+
+ }
+ else if (mDeleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(srcFolder));
+ nsTArray<nsMsgKey> imapUids;
+ imapUids.SetLength(numJunkHdrs);
+ for (uint32_t i = 0; i < numJunkHdrs; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(mJunkHdrs, i);
+ msgHdr->GetMessageKey(&imapUids[i]);
+ }
+
+ imapFolder->StoreImapFlags(kImapMsgDeletedFlag, false, imapUids.Elements(),
+ imapUids.Length(), nullptr);
+ }
+ NoteEndChange(nsMsgViewNotificationCode::none, 0, 0);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "move or deletion of message marked-as-junk/non junk failed");
+ }
+ return rv;
+}
+
+nsresult
+nsMsgDBView::DetermineActionsForJunkChange(bool msgsAreJunk,
+ nsIMsgFolder *srcFolder,
+ bool &moveMessages,
+ bool &changeReadState,
+ nsIMsgFolder** targetFolder)
+{
+ // there are two possible actions which may be performed
+ // on messages marked as spam: marking as read and moving
+ // somewhere...When a message is marked as non junk,
+ // it may be moved to the inbox, and marked unread.
+
+ moveMessages = false;
+ changeReadState = false;
+
+ // ... the 'somewhere', junkTargetFolder, can be a folder,
+ // but if it remains null we'll delete the messages
+
+ *targetFolder = nullptr;
+
+ uint32_t folderFlags;
+ srcFolder->GetFlags(&folderFlags);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = srcFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // handle the easy case of marking a junk message as good first...
+ // set the move target folder to the inbox, if any.
+ if (!msgsAreJunk)
+ {
+ if (folderFlags & nsMsgFolderFlags::Junk)
+ {
+ prefBranch->GetBoolPref("mail.spam.markAsNotJunkMarksUnRead",
+ &changeReadState);
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, targetFolder);
+ moveMessages = targetFolder != nullptr;
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr <nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // When the user explicitly marks a message as junk, we can mark it as read,
+ // too. This is independent of the "markAsReadOnSpam" pref, which applies
+ // only to automatically-classified messages.
+ // Note that this behaviour should match the one in the front end for marking
+ // as junk via toolbar/context menu.
+ prefBranch->GetBoolPref("mailnews.ui.junk.manualMarkAsJunkMarksRead",
+ &changeReadState);
+
+ // now let's determine whether we'll be taking the second action,
+ // the move / deletion (and also determine which of these two)
+
+ bool manualMark;
+ (void)spamSettings->GetManualMark(&manualMark);
+ if (!manualMark)
+ return NS_OK;
+
+ int32_t manualMarkMode;
+ (void)spamSettings->GetManualMarkMode(&manualMarkMode);
+ NS_ASSERTION(manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE
+ || manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE,
+ "bad manual mark mode");
+
+ if (manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE)
+ {
+ // if this is a junk folder
+ // (not only "the" junk folder for this account)
+ // don't do the move
+ if (folderFlags & nsMsgFolderFlags::Junk)
+ return NS_OK;
+
+ nsCString spamFolderURI;
+ rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(!spamFolderURI.IsEmpty(), "spam folder URI is empty, can't move");
+ if (!spamFolderURI.IsEmpty())
+ {
+ rv = GetExistingFolder(spamFolderURI, targetFolder);
+ if (NS_SUCCEEDED(rv) && *targetFolder)
+ {
+ moveMessages = true;
+ }
+ else
+ {
+ // XXX ToDo: GetOrCreateFolder will only create a folder with localized
+ // name "Junk" regardless of spamFolderURI. So if someone
+ // sets the junk folder to an existing folder of a different
+ // name, then deletes that folder, this will fail to create
+ // the correct folder.
+ rv = GetOrCreateFolder(spamFolderURI, nullptr /* aListener */);
+ if (NS_SUCCEEDED(rv))
+ rv = GetExistingFolder(spamFolderURI, targetFolder);
+ NS_ASSERTION(NS_SUCCEEDED(rv) && *targetFolder, "GetOrCreateFolder failed");
+ }
+ }
+ return NS_OK;
+ }
+
+ // at this point manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE)
+
+ // if this is in the trash, let's not delete
+ if (folderFlags & nsMsgFolderFlags::Trash)
+ return NS_OK;
+
+ return srcFolder->GetCanDeleteMessages(&moveMessages);
+}
+
+// reversing threads involves reversing the threads but leaving the
+// expanded messages ordered relative to the thread, so we
+// make a copy of each array and copy them over.
+void nsMsgDBView::ReverseThreads()
+{
+ nsTArray<uint32_t> newFlagArray;
+ nsTArray<nsMsgKey> newKeyArray;
+ nsTArray<uint8_t> newLevelArray;
+
+ uint32_t viewSize = GetSize();
+ uint32_t startThread = viewSize;
+ uint32_t nextThread = viewSize;
+ uint32_t destIndex = 0;
+
+ newKeyArray.SetLength(m_keys.Length());
+ newFlagArray.SetLength(m_flags.Length());
+ newLevelArray.SetLength(m_levels.Length());
+
+ while (startThread)
+ {
+ startThread--;
+
+ if (m_flags[startThread] & MSG_VIEW_FLAG_ISTHREAD)
+ {
+ for (uint32_t sourceIndex = startThread; sourceIndex < nextThread; sourceIndex++)
+ {
+ newKeyArray[destIndex] = m_keys[sourceIndex];
+ newFlagArray[destIndex] = m_flags[sourceIndex];
+ newLevelArray[destIndex] = m_levels[sourceIndex];
+ destIndex++;
+ }
+ nextThread = startThread; // because we're copying in reverse order
+ }
+ }
+ m_keys.SwapElements(newKeyArray);
+ m_flags.SwapElements(newFlagArray);
+ m_levels.SwapElements(newLevelArray);
+}
+
+void nsMsgDBView::ReverseSort()
+{
+ uint32_t topIndex = GetSize();
+
+ nsCOMArray<nsIMsgFolder> *folders = GetFolders();
+
+ // go up half the array swapping values
+ for (uint32_t bottomIndex = 0; bottomIndex < --topIndex; bottomIndex++)
+ {
+ // swap flags
+ uint32_t tempFlags = m_flags[bottomIndex];
+ m_flags[bottomIndex] = m_flags[topIndex];
+ m_flags[topIndex] = tempFlags;
+
+ // swap keys
+ nsMsgKey tempKey = m_keys[bottomIndex];
+ m_keys[bottomIndex] = m_keys[topIndex];
+ m_keys[topIndex] = tempKey;
+
+ if (folders)
+ {
+ // swap folders --
+ // needed when search is done across multiple folders
+ nsIMsgFolder *bottomFolder = folders->ObjectAt(bottomIndex);
+ nsIMsgFolder *topFolder = folders->ObjectAt(topIndex);
+ folders->ReplaceObjectAt(topFolder, bottomIndex);
+ folders->ReplaceObjectAt(bottomFolder, topIndex);
+ }
+ // no need to swap elements in m_levels; since we only call
+ // ReverseSort in non-threaded mode, m_levels are all the same.
+ }
+}
+int
+nsMsgDBView::FnSortIdKey(const void *pItem1, const void *pItem2, void *privateData)
+{
+ int32_t retVal = 0;
+
+ IdKey** p1 = (IdKey**)pItem1;
+ IdKey** p2 = (IdKey**)pItem2;
+ viewSortInfo* sortInfo = (viewSortInfo *) privateData;
+
+ nsIMsgDatabase *db = sortInfo->db;
+
+ mozilla::DebugOnly<nsresult> rv = db->CompareCollationKeys((*p1)->dword, (*p1)->key,
+ (*p2)->dword, (*p2)->key,
+ &retVal);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "compare failed");
+
+ if (retVal)
+ return sortInfo->ascendingSort ? retVal : -retVal;
+ if (sortInfo->view->m_secondarySort == nsMsgViewSortType::byId)
+ return (sortInfo->view->m_secondarySortOrder == nsMsgViewSortOrder::ascending &&
+ (*p1)->id >= (*p2)->id) ? 1 : -1;
+ else
+ return sortInfo->view->SecondarySort((*p1)->id, (*p1)->folder, (*p2)->id, (*p2)->folder, sortInfo);
+ // here we'd use the secondary sort
+}
+
+int
+nsMsgDBView::FnSortIdKeyPtr(const void *pItem1, const void *pItem2, void *privateData)
+{
+ int32_t retVal = 0;
+
+ IdKeyPtr** p1 = (IdKeyPtr**)pItem1;
+ IdKeyPtr** p2 = (IdKeyPtr**)pItem2;
+ viewSortInfo* sortInfo = (viewSortInfo *) privateData;
+
+ nsIMsgDatabase *db = sortInfo->db;
+
+ mozilla::DebugOnly<nsresult> rv = db->CompareCollationKeys((*p1)->dword, (*p1)->key,
+ (*p2)->dword, (*p2)->key,
+ &retVal);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "compare failed");
+
+ if (retVal)
+ return sortInfo->ascendingSort ? retVal : -retVal;
+
+ if (sortInfo->view->m_secondarySort == nsMsgViewSortType::byId)
+ return (sortInfo->view->m_secondarySortOrder == nsMsgViewSortOrder::ascending &&
+ (*p1)->id >= (*p2)->id) ? 1 : -1;
+ else
+ return sortInfo->view->SecondarySort((*p1)->id, (*p1)->folder, (*p2)->id, (*p2)->folder, sortInfo);
+}
+
+int
+nsMsgDBView::FnSortIdUint32(const void *pItem1, const void *pItem2, void *privateData)
+{
+ IdUint32** p1 = (IdUint32**)pItem1;
+ IdUint32** p2 = (IdUint32**)pItem2;
+ viewSortInfo* sortInfo = (viewSortInfo *) privateData;
+
+ if ((*p1)->dword > (*p2)->dword)
+ return (sortInfo->ascendingSort) ? 1 : -1;
+ else if ((*p1)->dword < (*p2)->dword)
+ return (sortInfo->ascendingSort) ? -1 : 1;
+ if (sortInfo->view->m_secondarySort == nsMsgViewSortType::byId)
+ return (sortInfo->view->m_secondarySortOrder == nsMsgViewSortOrder::ascending &&
+ (*p1)->id >= (*p2)->id) ? 1 : -1;
+ else
+ return sortInfo->view->SecondarySort((*p1)->id, (*p1)->folder, (*p2)->id, (*p2)->folder, sortInfo);
+}
+
+
+// XXX are these still correct?
+//To compensate for memory alignment required for
+//systems such as HP-UX these values must be 4 bytes
+//aligned. Don't break this when modify the constants
+const int kMaxSubjectKey = 160;
+const int kMaxLocationKey = 160; // also used for account
+const int kMaxAuthorKey = 160;
+const int kMaxRecipientKey = 80;
+
+//
+// There are cases when pFieldType is not set:
+// one case returns NS_ERROR_UNEXPECTED;
+// the other case now return NS_ERROR_NULL_POINTER (this is only when
+// colHandler below is null, but is very unlikely).
+// The latter case used to return NS_OK, which was incorrect.
+//
+nsresult nsMsgDBView::GetFieldTypeAndLenForSort(nsMsgViewSortTypeValue sortType,
+ uint16_t *pMaxLen,
+ eFieldType *pFieldType,
+ nsIMsgCustomColumnHandler* colHandler)
+{
+ NS_ENSURE_ARG_POINTER(pMaxLen);
+ NS_ENSURE_ARG_POINTER(pFieldType);
+
+ switch (sortType)
+ {
+ case nsMsgViewSortType::bySubject:
+ *pFieldType = kCollationKey;
+ *pMaxLen = kMaxSubjectKey;
+ break;
+ case nsMsgViewSortType::byAccount:
+ case nsMsgViewSortType::byTags:
+ case nsMsgViewSortType::byLocation:
+ *pFieldType = kCollationKey;
+ *pMaxLen = kMaxLocationKey;
+ break;
+ case nsMsgViewSortType::byRecipient:
+ case nsMsgViewSortType::byCorrespondent:
+ *pFieldType = kCollationKey;
+ *pMaxLen = kMaxRecipientKey;
+ break;
+ case nsMsgViewSortType::byAuthor:
+ *pFieldType = kCollationKey;
+ *pMaxLen = kMaxAuthorKey;
+ break;
+ case nsMsgViewSortType::byDate:
+ case nsMsgViewSortType::byReceived:
+ case nsMsgViewSortType::byPriority:
+ case nsMsgViewSortType::byThread:
+ case nsMsgViewSortType::byId:
+ case nsMsgViewSortType::bySize:
+ case nsMsgViewSortType::byFlagged:
+ case nsMsgViewSortType::byUnread:
+ case nsMsgViewSortType::byStatus:
+ case nsMsgViewSortType::byJunkStatus:
+ case nsMsgViewSortType::byAttachments:
+ *pFieldType = kU32;
+ *pMaxLen = 0;
+ break;
+ case nsMsgViewSortType::byCustom:
+ {
+ if (colHandler == nullptr)
+ {
+ NS_WARNING("colHandler is null. *pFieldType is not set.");
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ bool isString;
+ colHandler->IsString(&isString);
+
+ if (isString)
+ {
+ *pFieldType = kCollationKey;
+ *pMaxLen = kMaxRecipientKey; //80 - do we need a seperate k?
+ }
+ else
+ {
+ *pFieldType = kU32;
+ *pMaxLen = 0;
+ }
+ break;
+ }
+ case nsMsgViewSortType::byNone: // bug 901948
+ return NS_ERROR_INVALID_ARG;
+ default:
+ {
+ nsAutoCString message("unexpected switch value: sortType=");
+ message.AppendInt(sortType);
+ NS_WARNING(message.get());
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+#define MSG_STATUS_MASK (nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded)
+
+nsresult nsMsgDBView::GetStatusSortValue(nsIMsgDBHdr *msgHdr, uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(result);
+
+ uint32_t messageFlags;
+ nsresult rv = msgHdr->GetFlags(&messageFlags);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (messageFlags & nsMsgMessageFlags::New)
+ {
+ // happily, new by definition stands alone
+ *result = 0;
+ return NS_OK;
+ }
+
+ switch (messageFlags & MSG_STATUS_MASK)
+ {
+ case nsMsgMessageFlags::Replied:
+ *result = 2;
+ break;
+ case nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied:
+ *result = 1;
+ break;
+ case nsMsgMessageFlags::Forwarded:
+ *result = 3;
+ break;
+ default:
+ *result = (messageFlags & nsMsgMessageFlags::Read) ? 4 : 5;
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GetLongField(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, uint32_t *result, nsIMsgCustomColumnHandler* colHandler)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(result);
+
+ bool isRead;
+ uint32_t bits;
+
+ switch (sortType)
+ {
+ case nsMsgViewSortType::bySize:
+ rv = (mShowSizeInLines) ? msgHdr->GetLineCount(result) : msgHdr->GetMessageSize(result);
+ break;
+ case nsMsgViewSortType::byPriority:
+ nsMsgPriorityValue priority;
+ rv = msgHdr->GetPriority(&priority);
+
+ // treat "none" as "normal" when sorting.
+ if (priority == nsMsgPriority::none)
+ priority = nsMsgPriority::normal;
+
+ // we want highest priority to have lowest value
+ // so ascending sort will have highest priority first.
+ *result = nsMsgPriority::highest - priority;
+ break;
+ case nsMsgViewSortType::byStatus:
+ rv = GetStatusSortValue(msgHdr,result);
+ break;
+ case nsMsgViewSortType::byFlagged:
+ bits = 0;
+ rv = msgHdr->GetFlags(&bits);
+ *result = !(bits & nsMsgMessageFlags::Marked); //make flagged come out on top.
+ break;
+ case nsMsgViewSortType::byUnread:
+ rv = msgHdr->GetIsRead(&isRead);
+ if (NS_SUCCEEDED(rv))
+ *result = !isRead;
+ break;
+ case nsMsgViewSortType::byJunkStatus:
+ {
+ nsCString junkScoreStr;
+ rv = msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ // unscored messages should come before messages that are scored
+ // junkScoreStr is "", and "0" - "100"
+ // normalize to 0 - 101
+ *result = junkScoreStr.IsEmpty() ? (0) : atoi(junkScoreStr.get()) + 1;
+ }
+ break;
+ case nsMsgViewSortType::byAttachments:
+ bits = 0;
+ rv = msgHdr->GetFlags(&bits);
+ *result = !(bits & nsMsgMessageFlags::Attachment);
+ break;
+ case nsMsgViewSortType::byDate:
+ // when sorting threads by date, we may want the date of the newest msg
+ // in the thread
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay
+ && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ && ! mSortThreadsByRoot)
+ {
+ nsCOMPtr <nsIMsgThread> thread;
+ rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv))
+ {
+ thread->GetNewestMsgDate(result);
+ break;
+ }
+ }
+ rv = msgHdr->GetDateInSeconds(result);
+ break;
+ case nsMsgViewSortType::byReceived:
+ // when sorting threads by received date, we may want the received date
+ // of the newest msg in the thread
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay
+ && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ && ! mSortThreadsByRoot)
+ {
+ nsCOMPtr <nsIMsgThread> thread;
+ rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ thread->GetNewestMsgDate(result);
+ }
+ else
+ {
+ rv = msgHdr->GetUint32Property("dateReceived", result); // Already in seconds...
+ if (*result == 0) // Use Date instead, we have no Received property
+ rv = msgHdr->GetDateInSeconds(result);
+ }
+ break;
+ case nsMsgViewSortType::byCustom:
+ if (colHandler != nullptr)
+ {
+ colHandler->GetSortLongForRow(msgHdr, result);
+ rv = NS_OK;
+ }
+ else
+ {
+ NS_ASSERTION(false, "should not be here (Sort Type: byCustom (Long), but no custom handler)");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ break;
+ case nsMsgViewSortType::byNone: // Bug 901948
+ return NS_ERROR_INVALID_ARG;
+
+ case nsMsgViewSortType::byId:
+ // handled by caller, since caller knows the key
+ default:
+ NS_ERROR("should not be here");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+MsgViewSortColumnInfo::MsgViewSortColumnInfo(const MsgViewSortColumnInfo &other)
+{
+ mSortType = other.mSortType;
+ mSortOrder = other.mSortOrder;
+ mCustomColumnName = other.mCustomColumnName;
+ mColHandler = other.mColHandler;
+}
+
+bool MsgViewSortColumnInfo::operator== (const MsgViewSortColumnInfo& other) const
+{
+ return (mSortType == nsMsgViewSortType::byCustom) ?
+ mCustomColumnName.Equals(other.mCustomColumnName) : mSortType == other.mSortType;
+}
+
+nsresult nsMsgDBView::EncodeColumnSort(nsString &columnSortString)
+{
+ for (uint32_t i = 0; i < m_sortColumns.Length(); i++)
+ {
+ MsgViewSortColumnInfo &sortInfo = m_sortColumns[i];
+ columnSortString.Append((char) sortInfo.mSortType);
+ columnSortString.Append((char) sortInfo.mSortOrder + '0');
+ if (sortInfo.mSortType == nsMsgViewSortType::byCustom)
+ {
+ columnSortString.Append(sortInfo.mCustomColumnName);
+ columnSortString.Append((char16_t) '\r');
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::DecodeColumnSort(nsString &columnSortString)
+{
+ const char16_t *stringPtr = columnSortString.BeginReading();
+ while (*stringPtr)
+ {
+ MsgViewSortColumnInfo sortColumnInfo;
+ sortColumnInfo.mSortType = (nsMsgViewSortTypeValue) *stringPtr++;
+ sortColumnInfo.mSortOrder = (nsMsgViewSortOrderValue) (*stringPtr++) - '0';
+ if (sortColumnInfo.mSortType == nsMsgViewSortType::byCustom)
+ {
+ while (*stringPtr && *stringPtr != '\r')
+ sortColumnInfo.mCustomColumnName.Append(*stringPtr++);
+ sortColumnInfo.mColHandler = GetColumnHandler(sortColumnInfo.mCustomColumnName.get());
+ if (*stringPtr) // advance past '\r'
+ stringPtr++;
+ }
+ m_sortColumns.AppendElement(sortColumnInfo);
+ }
+ return NS_OK;
+}
+
+// cf. Secondary Sort Key: when you select a column to sort, that
+// becomes the new Primary sort key, and all previous sort keys
+// become secondary. For example, if you first click on Date,
+// the messages are sorted by Date; then click on From, and now the
+// messages are sorted by From, and for each value of From the
+// messages are in Date order.
+
+void nsMsgDBView::PushSort(const MsgViewSortColumnInfo &newSort)
+{
+ // DONE
+ // Handle byNone (bug 901948) ala a mail/base/modules/dbViewerWrapper.js
+ // where we don't push the secondary sort type if it's ::byNone;
+ // (and secondary sort type is NOT the same as the first sort type
+ // there.). This code should behave the same way.
+
+ // We don't expect to be passed sort type ::byNone,
+ // but if we are it's safe to ignore it.
+ if (newSort.mSortType == nsMsgViewSortType::byNone)
+ return;
+
+ // Date and ID are unique keys, so if we are sorting by them we don't
+ // need to keep any secondary sort keys
+ if (newSort.mSortType == nsMsgViewSortType::byDate ||
+ newSort.mSortType == nsMsgViewSortType::byId )
+ m_sortColumns.Clear();
+ m_sortColumns.RemoveElement(newSort);
+ m_sortColumns.InsertElementAt(0, newSort);
+ if (m_sortColumns.Length() > kMaxNumSortColumns)
+ m_sortColumns.RemoveElementAt(kMaxNumSortColumns);
+}
+
+nsresult
+nsMsgDBView::GetCollationKey(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, uint8_t **result, uint32_t *len, nsIMsgCustomColumnHandler* colHandler)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(result);
+
+ switch (sortType)
+ {
+ case nsMsgViewSortType::bySubject:
+ rv = msgHdr->GetSubjectCollationKey(len, result);
+ break;
+ case nsMsgViewSortType::byLocation:
+ rv = GetLocationCollationKey(msgHdr, result, len);
+ break;
+ case nsMsgViewSortType::byRecipient:
+ {
+ nsString recipients;
+ rv = FetchRecipients(msgHdr, recipients);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIMsgDatabase> dbToUse = m_db;
+ if (!dbToUse) // probably search view
+ {
+ rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ rv = dbToUse->CreateCollationKey(recipients, len, result);
+ }
+ }
+ break;
+ case nsMsgViewSortType::byAuthor:
+ {
+ rv = msgHdr->GetAuthorCollationKey(len, result);
+ nsString author;
+ rv = FetchAuthor(msgHdr, author);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIMsgDatabase> dbToUse = m_db;
+ if (!dbToUse) // probably search view
+ {
+ rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ rv = dbToUse->CreateCollationKey(author, len, result);
+ }
+ }
+ break;
+ case nsMsgViewSortType::byAccount:
+ case nsMsgViewSortType::byTags:
+ {
+ nsString str;
+ nsCOMPtr <nsIMsgDatabase> dbToUse = m_db;
+
+ if (!dbToUse) // probably search view
+ GetDBForViewIndex(0, getter_AddRefs(dbToUse));
+
+ rv = (sortType == nsMsgViewSortType::byAccount)
+ ? FetchAccount(msgHdr, str)
+ : FetchTags(msgHdr, str);
+
+ if (NS_SUCCEEDED(rv) && dbToUse)
+ rv = dbToUse->CreateCollationKey(str, len, result);
+ }
+ break;
+ case nsMsgViewSortType::byCustom:
+ if (colHandler != nullptr)
+ {
+ nsAutoString strKey;
+ rv = colHandler->GetSortStringForRow(msgHdr, strKey);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get sort string for custom row");
+ nsAutoString strTemp(strKey);
+
+ nsCOMPtr <nsIMsgDatabase> dbToUse = m_db;
+ if (!dbToUse) // probably search view
+ {
+ rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ rv = dbToUse->CreateCollationKey(strKey, len, result);
+ }
+ else
+ {
+ NS_ERROR("should not be here (Sort Type: byCustom (String), but no custom handler)");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ break;
+ case nsMsgViewSortType::byCorrespondent:
+ {
+ nsString value;
+ if (IsOutgoingMsg(msgHdr))
+ rv = FetchRecipients(msgHdr, value);
+ else
+ rv = FetchAuthor(msgHdr, value);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIMsgDatabase> dbToUse = m_db;
+ if (!dbToUse) // probably search view
+ {
+ rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ rv = dbToUse->CreateCollationKey(value, len, result);
+ }
+ }
+ break;
+ default:
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ // bailing out with failure will stop the sort and leave us in
+ // a bad state. try to continue on, instead
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get the collation key");
+ if (NS_FAILED(rv))
+ {
+ *result = nullptr;
+ *len = 0;
+ }
+ return NS_OK;
+}
+
+// As the location collation key is created getting folder from the msgHdr,
+// it is defined in this file and not from the db.
+nsresult
+nsMsgDBView::GetLocationCollationKey(nsIMsgDBHdr *msgHdr, uint8_t **result, uint32_t *len)
+{
+ nsCOMPtr <nsIMsgFolder> folder;
+
+ nsresult rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr <nsIMsgDatabase> dbToUse;
+ rv = folder->GetMsgDatabase(getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString locationString;
+ rv = folder->GetPrettiestName(locationString);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return dbToUse->CreateCollationKey(locationString, len, result);
+}
+
+nsresult nsMsgDBView::SaveSortInfo(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
+{
+ if (m_viewFolder)
+ {
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo)
+ {
+ // save off sort type and order, view type and flags
+ folderInfo->SetSortType(sortType);
+ folderInfo->SetSortOrder(sortOrder);
+
+ nsString sortColumnsString;
+ rv = EncodeColumnSort(sortColumnsString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ folderInfo->SetProperty("sortColumns", sortColumnsString);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::RestoreSortInfo()
+{
+ if (!m_viewFolder)
+ return NS_OK;
+
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo)
+ {
+ // Restore m_sortColumns from db.
+ nsString sortColumnsString;
+ folderInfo->GetProperty("sortColumns", sortColumnsString);
+ DecodeColumnSort(sortColumnsString);
+ if (m_sortColumns.Length() > 1)
+ {
+ m_secondarySort = m_sortColumns[1].mSortType;
+ m_secondarySortOrder = m_sortColumns[1].mSortOrder;
+ m_secondaryCustomColumn = m_sortColumns[1].mCustomColumnName;
+ }
+
+ // Restore curCustomColumn from db.
+ folderInfo->GetProperty("customSortCol", m_curCustomColumn);
+ }
+
+ return NS_OK;
+}
+
+// Called by msgDBView::Sort, at which point any persisted active custom
+// columns must be registered. If not, reset their m_sortColumns entries
+// to byDate; Sort will fill in values if necessary based on new user sort.
+void nsMsgDBView::EnsureCustomColumnsValid()
+{
+ if (!m_sortColumns.Length())
+ return;
+
+ for (uint32_t i = 0; i < m_sortColumns.Length(); i++)
+ {
+ if (m_sortColumns[i].mSortType == nsMsgViewSortType::byCustom &&
+ m_sortColumns[i].mColHandler == nullptr)
+ {
+ m_sortColumns[i].mSortType = nsMsgViewSortType::byDate;
+ m_sortColumns[i].mCustomColumnName.Truncate();
+ // There are only two...
+ if (i == 0 && m_sortType != nsMsgViewSortType::byCustom)
+ SetCurCustomColumn(EmptyString());
+ if (i == 1)
+ m_secondaryCustomColumn.Truncate();
+ }
+ }
+}
+
+int32_t nsMsgDBView::SecondarySort(nsMsgKey key1, nsISupports *supports1,
+ nsMsgKey key2, nsISupports *supports2,
+ viewSortInfo *comparisonContext)
+{
+ // We need to make sure that in the case of the secondary sort field also
+ // matching, we don't recurse.
+ if (comparisonContext->isSecondarySort)
+ return key1 > key2;
+
+ nsCOMPtr<nsIMsgFolder> folder1, folder2;
+ nsCOMPtr <nsIMsgDBHdr> hdr1, hdr2;
+ folder1 = do_QueryInterface(supports1);
+ folder2 = do_QueryInterface(supports2);
+ nsresult rv = folder1->GetMessageHeader(key1, getter_AddRefs(hdr1));
+ NS_ENSURE_SUCCESS(rv, 0);
+ rv = folder2->GetMessageHeader(key2, getter_AddRefs(hdr2));
+ NS_ENSURE_SUCCESS(rv, 0);
+ IdKeyPtr EntryInfo1, EntryInfo2;
+ EntryInfo1.key = nullptr;
+ EntryInfo2.key = nullptr;
+
+ uint16_t maxLen;
+ eFieldType fieldType;
+ nsMsgViewSortTypeValue sortType = comparisonContext->view->m_secondarySort;
+ nsMsgViewSortOrderValue sortOrder = comparisonContext->view->m_secondarySortOrder;
+
+ // Get the custom column handler for the *secondary* sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = nullptr;
+ if (sortType == nsMsgViewSortType::byCustom &&
+ comparisonContext->view->m_sortColumns.Length() > 1)
+ colHandler = comparisonContext->view->m_sortColumns[1].mColHandler;
+
+ // The following may leave fieldType undefined.
+ // In this case, we can return 0 right away since
+ // it is the value returned in the default case of
+ // switch (fieldType) statement below.
+ rv = GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType, colHandler);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2;
+
+ int (* comparisonFun) (const void *pItem1, const void *pItem2, void *privateData) = nullptr;
+ int retStatus = 0;
+ hdr1->GetMessageKey(&EntryInfo1.id);
+ hdr2->GetMessageKey(&EntryInfo2.id);
+
+ switch (fieldType)
+ {
+ case kCollationKey:
+ rv = GetCollationKey(hdr1, sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+ comparisonFun = FnSortIdKeyPtr;
+ break;
+ case kU32:
+ if (sortType == nsMsgViewSortType::byId)
+ EntryInfo1.dword = EntryInfo1.id;
+ else
+ GetLongField(hdr1, sortType, &EntryInfo1.dword, colHandler);
+ comparisonFun = FnSortIdUint32;
+ break;
+ default:
+ return 0;
+ }
+ bool saveAscendingSort = comparisonContext->ascendingSort;
+ comparisonContext->isSecondarySort = true;
+ comparisonContext->ascendingSort = (sortOrder == nsMsgViewSortOrder::ascending);
+ if (fieldType == kCollationKey)
+ {
+ PR_FREEIF(EntryInfo2.key);
+ rv = GetCollationKey(hdr2, sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+ }
+ else if (fieldType == kU32)
+ {
+ if (sortType == nsMsgViewSortType::byId)
+ EntryInfo2.dword = EntryInfo2.id;
+ else
+ GetLongField(hdr2, sortType, &EntryInfo2.dword, colHandler);
+ }
+ retStatus = (*comparisonFun)(&pValue1, &pValue2, comparisonContext);
+
+ comparisonContext->isSecondarySort = false;
+ comparisonContext->ascendingSort = saveAscendingSort;
+
+ return retStatus;
+}
+
+
+NS_IMETHODIMP nsMsgDBView::Sort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder)
+{
+ EnsureCustomColumnsValid();
+
+ // If we're doing a stable sort, we can't just reverse the messages.
+ // Check also that the custom column we're sorting on hasn't changed.
+ // Otherwise, to be on the safe side, resort.
+ // Note: m_curCustomColumn is the desired (possibly new) custom column name,
+ // while m_sortColumns[0].mCustomColumnName is the name for the last completed
+ // sort, since these are persisted after each sort.
+ if (m_sortType == sortType && m_sortValid &&
+ (sortType != nsMsgViewSortType::byCustom ||
+ (sortType == nsMsgViewSortType::byCustom && m_sortColumns.Length() &&
+ m_sortColumns[0].mCustomColumnName.Equals(m_curCustomColumn))) &&
+ m_sortColumns.Length() < 2)
+ {
+ // same as it ever was. do nothing
+ if (m_sortOrder == sortOrder)
+ return NS_OK;
+
+ // for secondary sort, remember the sort order on a per column basis.
+ if (m_sortColumns.Length())
+ m_sortColumns[0].mSortOrder = sortOrder;
+ SaveSortInfo(sortType, sortOrder);
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ ReverseThreads();
+ else
+ ReverseSort();
+
+ m_sortOrder = sortOrder;
+ // we just reversed the sort order...we still need to invalidate the view
+ return NS_OK;
+ }
+
+ if (sortType == nsMsgViewSortType::byThread)
+ return NS_OK;
+
+ // If a sortType has changed, or the sortType is byCustom and a column has
+ // changed, this is the new primary sortColumnInfo.
+ // Note: m_curCustomColumn is the desired (possibly new) custom column name,
+ // while m_sortColumns[0].mCustomColumnName is the name for the last completed
+ // sort, since these are persisted after each sort.
+ if (m_sortType != sortType ||
+ (sortType == nsMsgViewSortType::byCustom && m_sortColumns.Length() &&
+ !m_sortColumns[0].mCustomColumnName.Equals(m_curCustomColumn)))
+ {
+ // For secondary sort, remember the sort order of the original primary sort!
+ if (m_sortColumns.Length())
+ m_sortColumns[0].mSortOrder = m_sortOrder;
+
+ MsgViewSortColumnInfo sortColumnInfo;
+ sortColumnInfo.mSortType = sortType;
+ sortColumnInfo.mSortOrder = sortOrder;
+ if (sortType == nsMsgViewSortType::byCustom)
+ {
+ GetCurCustomColumn(sortColumnInfo.mCustomColumnName);
+ sortColumnInfo.mColHandler = GetCurColumnHandler();
+ }
+
+ PushSort(sortColumnInfo);
+ }
+ else
+ {
+ // For primary sort, remember the sort order on a per column basis.
+ if (m_sortColumns.Length())
+ m_sortColumns[0].mSortOrder = sortOrder;
+ }
+
+ if (m_sortColumns.Length() > 1)
+ {
+ m_secondarySort = m_sortColumns[1].mSortType;
+ m_secondarySortOrder = m_sortColumns[1].mSortOrder;
+ m_secondaryCustomColumn = m_sortColumns[1].mCustomColumnName;
+ }
+ SaveSortInfo(sortType, sortOrder);
+ // figure out how much memory we'll need, and the malloc it
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // If we did not obtain proper fieldType, it needs to be checked
+ // because the subsequent code does not handle it very well.
+ nsresult rv = GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType, colHandler);
+
+ // Don't sort if the field type is not supported: Bug 901948
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ nsTArray<void*> ptrs;
+ uint32_t arraySize = GetSize();
+
+ if (!arraySize)
+ return NS_OK;
+
+ nsCOMArray<nsIMsgFolder> *folders = GetFolders();
+
+ IdKey** pPtrBase = (IdKey**)PR_Malloc(arraySize * sizeof(IdKey*));
+ NS_ASSERTION(pPtrBase, "out of memory, can't sort");
+ if (!pPtrBase) return NS_ERROR_OUT_OF_MEMORY;
+ ptrs.AppendElement((void *)pPtrBase); // remember this pointer so we can free it later
+
+ // build up the beast, so we can sort it.
+ uint32_t numSoFar = 0;
+ const uint32_t keyOffset = offsetof(IdKey, key);
+ // calc max possible size needed for all the rest
+ uint32_t maxSize = (keyOffset + maxLen) * (arraySize - numSoFar);
+
+ const uint32_t maxBlockSize = (uint32_t) 0xf000L;
+ uint32_t allocSize = std::min(maxBlockSize, maxSize);
+ char *pTemp = (char *) PR_Malloc(allocSize);
+ NS_ASSERTION(pTemp, "out of memory, can't sort");
+ if (!pTemp)
+ {
+ FreeAll(&ptrs);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ ptrs.AppendElement(pTemp); // remember this pointer so we can free it later
+
+ char *pBase = pTemp;
+ bool more = true;
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ uint8_t *keyValue = nullptr;
+ uint32_t longValue;
+ while (more && numSoFar < arraySize)
+ {
+ nsMsgKey thisKey = m_keys[numSoFar];
+ if (sortType != nsMsgViewSortType::byId)
+ {
+ rv = GetMsgHdrForViewIndex(numSoFar, getter_AddRefs(msgHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && msgHdr, "header not found");
+ if (NS_FAILED(rv) || !msgHdr)
+ {
+ FreeAll(&ptrs);
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ else
+ {
+ msgHdr = nullptr;
+ }
+
+ // Could be a problem here if the ones that appear here are different than
+ // the ones already in the array.
+ uint32_t actualFieldLen = 0;
+
+ if (fieldType == kCollationKey)
+ {
+ rv = GetCollationKey(msgHdr, sortType, &keyValue, &actualFieldLen, colHandler);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ longValue = actualFieldLen;
+ }
+ else
+ {
+ if (sortType == nsMsgViewSortType::byId)
+ {
+ longValue = thisKey;
+ }
+ else
+ {
+ rv = GetLongField(msgHdr, sortType, &longValue, colHandler);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+
+ // check to see if this entry fits into the block we have allocated so far
+ // pTemp - pBase = the space we have used so far
+ // sizeof(EntryInfo) + fieldLen = space we need for this entry
+ // allocSize = size of the current block
+ if ((uint32_t)(pTemp - pBase) + (keyOffset + actualFieldLen) >= allocSize)
+ {
+ maxSize = (keyOffset + maxLen) * (arraySize - numSoFar);
+ allocSize = std::min(maxBlockSize, maxSize);
+ // make sure allocSize is big enough for the current value
+ allocSize = std::max(allocSize, keyOffset + actualFieldLen);
+ pTemp = (char *) PR_Malloc(allocSize);
+ NS_ASSERTION(pTemp, "out of memory, can't sort");
+ if (!pTemp)
+ {
+ FreeAll(&ptrs);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ pBase = pTemp;
+ ptrs.AppendElement(pTemp); // remember this pointer so we can free it later
+ }
+
+ // now store this entry away in the allocated memory
+ IdKey *info = (IdKey*)pTemp;
+ pPtrBase[numSoFar] = info;
+ info->id = thisKey;
+ info->bits = m_flags[numSoFar];
+ info->dword = longValue;
+ //info->pad = 0;
+ info->folder = folders ? folders->ObjectAt(numSoFar) : m_folder.get();
+
+ memcpy(info->key, keyValue, actualFieldLen);
+ //In order to align memory for systems that require it, such as HP-UX
+ //calculate the correct value to pad the actualFieldLen value
+ const uint32_t align = sizeof(IdKey) - sizeof(IdUint32) - 1;
+ actualFieldLen = (actualFieldLen + align) & ~align;
+
+ pTemp += keyOffset + actualFieldLen;
+ ++numSoFar;
+ PR_Free(keyValue);
+ }
+
+ viewSortInfo qsPrivateData;
+ qsPrivateData.view = this;
+ qsPrivateData.isSecondarySort = false;
+ qsPrivateData.ascendingSort = (sortOrder == nsMsgViewSortOrder::ascending);
+
+ nsCOMPtr <nsIMsgDatabase> dbToUse = m_db;
+
+ if (!dbToUse) // probably search view
+ GetDBForViewIndex(0, getter_AddRefs(dbToUse));
+ qsPrivateData.db = dbToUse;
+ if (dbToUse)
+ {
+ // do the sort
+ switch (fieldType)
+ {
+ case kCollationKey:
+ NS_QuickSort(pPtrBase, numSoFar, sizeof(IdKey*), FnSortIdKey, &qsPrivateData);
+ break;
+ case kU32:
+ NS_QuickSort(pPtrBase, numSoFar, sizeof(IdKey*), FnSortIdUint32, &qsPrivateData);
+ break;
+ default:
+ NS_ERROR("not supposed to get here");
+ break;
+ }
+ }
+
+ // now put the IDs into the array in proper order
+ for (uint32_t i = 0; i < numSoFar; i++)
+ {
+ m_keys[i] = pPtrBase[i]->id;
+ m_flags[i] = pPtrBase[i]->bits;
+
+ if (folders)
+ folders->ReplaceObjectAt(pPtrBase[i]->folder, i);
+ }
+
+ m_sortType = sortType;
+ m_sortOrder = sortOrder;
+
+ // free all the memory we allocated
+ FreeAll(&ptrs);
+
+ m_sortValid = true;
+ //m_db->SetSortInfo(sortType, sortOrder);
+
+ return NS_OK;
+}
+
+void nsMsgDBView::FreeAll(nsTArray<void*> *ptrs)
+{
+ int32_t i;
+ int32_t count = (int32_t) ptrs->Length();
+ if (count == 0)
+ return;
+
+ for (i=(count - 1);i>=0;i--)
+ PR_Free((void *) ptrs->ElementAt(i));
+ ptrs->Clear();
+}
+
+nsMsgViewIndex nsMsgDBView::GetIndexOfFirstDisplayedKeyInThread(
+ nsIMsgThread *threadHdr, bool allowDummy)
+{
+ nsMsgViewIndex retIndex = nsMsgViewIndex_None;
+ uint32_t childIndex = 0;
+ // We could speed up the unreadOnly view by starting our search with the first
+ // unread message in the thread. Sometimes, that will be wrong, however, so
+ // let's skip it until we're sure it's necessary.
+ // (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
+ // ? threadHdr->GetFirstUnreadKey(m_db) : threadHdr->GetChildAt(0);
+ uint32_t numThreadChildren;
+ threadHdr->GetNumChildren(&numThreadChildren);
+ while (retIndex == nsMsgViewIndex_None && childIndex < numThreadChildren)
+ {
+ nsCOMPtr<nsIMsgDBHdr> childHdr;
+ threadHdr->GetChildHdrAt(childIndex++, getter_AddRefs(childHdr));
+ if (childHdr)
+ retIndex = FindHdr(childHdr, 0, allowDummy);
+ }
+ return retIndex;
+}
+
+nsresult nsMsgDBView::GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result)
+{
+ nsresult rv;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
+ rv = threadHdr->GetFirstUnreadChild(result);
+ else
+ rv = threadHdr->GetChildHdrAt(0, result);
+ return rv;
+}
+
+// Find the view index of the thread containing the passed msgKey, if
+// the thread is in the view. MsgIndex is passed in as a shortcut if
+// it turns out the msgKey is the first message in the thread,
+// then we can avoid looking for the msgKey.
+nsMsgViewIndex nsMsgDBView::ThreadIndexOfMsg(nsMsgKey msgKey,
+ nsMsgViewIndex msgIndex /* = nsMsgViewIndex_None */,
+ int32_t *pThreadCount /* = NULL */,
+ uint32_t *pFlags /* = NULL */)
+{
+ if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return nsMsgViewIndex_None;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = m_db->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None);
+ return ThreadIndexOfMsgHdr(msgHdr, msgIndex, pThreadCount, pFlags);
+}
+
+nsMsgViewIndex nsMsgDBView::GetThreadIndex(nsMsgViewIndex msgIndex)
+{
+ if (!IsValidIndex(msgIndex))
+ return nsMsgViewIndex_None;
+
+ // scan up looking for level 0 message.
+ while (m_levels[msgIndex] && msgIndex)
+ --msgIndex;
+ return msgIndex;
+}
+
+nsMsgViewIndex
+nsMsgDBView::ThreadIndexOfMsgHdr(nsIMsgDBHdr *msgHdr,
+ nsMsgViewIndex msgIndex,
+ int32_t *pThreadCount,
+ uint32_t *pFlags)
+{
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ nsresult rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None);
+
+ nsMsgViewIndex retIndex = nsMsgViewIndex_None;
+
+ if (threadHdr != nullptr)
+ {
+ if (msgIndex == nsMsgViewIndex_None)
+ msgIndex = FindHdr(msgHdr, 0, true);
+
+ if (msgIndex == nsMsgViewIndex_None) // hdr is not in view, need to find by thread
+ {
+ msgIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr, true);
+ //nsMsgKey threadKey = (msgIndex == nsMsgViewIndex_None) ? nsMsgKey_None : GetAt(msgIndex);
+ if (pFlags)
+ threadHdr->GetFlags(pFlags);
+ }
+ nsMsgViewIndex startOfThread = msgIndex;
+ while ((int32_t) startOfThread >= 0 && m_levels[startOfThread] != 0)
+ startOfThread--;
+ retIndex = startOfThread;
+ if (pThreadCount)
+ {
+ int32_t numChildren = 0;
+ nsMsgViewIndex threadIndex = startOfThread;
+ do
+ {
+ threadIndex++;
+ numChildren++;
+ }
+ while (threadIndex < m_levels.Length() && m_levels[threadIndex] != 0);
+ *pThreadCount = numChildren;
+ }
+ }
+ return retIndex;
+}
+
+nsMsgKey nsMsgDBView::GetKeyOfFirstMsgInThread(nsMsgKey key)
+{
+ // Just report no key for any failure. This can occur when a
+ // message is deleted from a threaded view
+ nsCOMPtr <nsIMsgThread> pThread;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv))
+ return nsMsgKey_None;
+ rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
+ if (NS_FAILED(rv))
+ return nsMsgKey_None;
+ nsMsgKey firstKeyInThread = nsMsgKey_None;
+
+ if (!pThread)
+ return firstKeyInThread;
+
+ // ### dmb UnreadOnly - this is wrong. But didn't seem to matter in 4.x
+ pThread->GetChildKeyAt(0, &firstKeyInThread);
+ return firstKeyInThread;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetKeyAt(nsMsgViewIndex index, nsMsgKey *result)
+{
+ NS_ENSURE_ARG(result);
+ *result = GetAt(index);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetFlagsAt(nsMsgViewIndex aIndex, uint32_t *aResult)
+{
+ NS_ENSURE_ARG(aResult);
+ if (!IsValidIndex(aIndex))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ *aResult = m_flags[aIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetMsgHdrAt(nsMsgViewIndex aIndex, nsIMsgDBHdr **aResult)
+{
+ NS_ENSURE_ARG(aResult);
+ if (!IsValidIndex(aIndex))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ return GetMsgHdrForViewIndex(aIndex, aResult);
+}
+
+nsMsgViewIndex nsMsgDBView::FindHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startIndex,
+ bool allowDummy)
+{
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ nsMsgViewIndex viewIndex = m_keys.IndexOf(msgKey, startIndex);
+ if (viewIndex == nsMsgViewIndex_None)
+ return viewIndex;
+ // if we're supposed to allow dummies, and the previous index is a dummy that
+ // is not elided, then it must be the dummy corresponding to our node and
+ // we should return that instead.
+ if (allowDummy && viewIndex
+ && (m_flags[viewIndex-1] & MSG_VIEW_FLAG_DUMMY)
+ && !(m_flags[viewIndex-1] & nsMsgMessageFlags::Elided))
+ viewIndex--;
+ // else if we're not allowing dummies, and we found a dummy, look again
+ // one past the dummy.
+ else if (!allowDummy && m_flags[viewIndex] & MSG_VIEW_FLAG_DUMMY)
+ return m_keys.IndexOf(msgKey, viewIndex + 1);
+ return viewIndex;
+}
+
+nsMsgViewIndex nsMsgDBView::FindKey(nsMsgKey key, bool expand)
+{
+ nsMsgViewIndex retIndex = nsMsgViewIndex_None;
+ retIndex = (nsMsgViewIndex) (m_keys.IndexOf(key));
+ // for dummy headers, try to expand if the caller says so. And if the thread is
+ // expanded, ignore the dummy header and return the real header index.
+ if (retIndex != nsMsgViewIndex_None && m_flags[retIndex] & MSG_VIEW_FLAG_DUMMY && !(m_flags[retIndex] & nsMsgMessageFlags::Elided))
+ return (nsMsgViewIndex) m_keys.IndexOf(key, retIndex + 1);
+ if (key != nsMsgKey_None && (retIndex == nsMsgViewIndex_None || m_flags[retIndex] & MSG_VIEW_FLAG_DUMMY)
+ && expand && m_db)
+ {
+ nsMsgKey threadKey = GetKeyOfFirstMsgInThread(key);
+ if (threadKey != nsMsgKey_None)
+ {
+ nsMsgViewIndex threadIndex = FindKey(threadKey, false);
+ if (threadIndex != nsMsgViewIndex_None)
+ {
+ uint32_t flags = m_flags[threadIndex];
+ if (((flags & nsMsgMessageFlags::Elided) &&
+ NS_SUCCEEDED(ExpandByIndex(threadIndex, nullptr)))
+ || (flags & MSG_VIEW_FLAG_DUMMY))
+ retIndex = (nsMsgViewIndex) m_keys.IndexOf(key, threadIndex + 1);
+ }
+ }
+ }
+ return retIndex;
+}
+
+nsresult nsMsgDBView::GetThreadCount(nsMsgViewIndex index, uint32_t *pThreadCount)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgThread> pThread;
+ rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
+ if (NS_SUCCEEDED(rv) && pThread != nullptr)
+ rv = pThread->GetNumChildren(pThreadCount);
+ return rv;
+}
+
+// This counts the number of messages in an expanded thread, given the
+// index of the first message in the thread.
+int32_t nsMsgDBView::CountExpandedThread(nsMsgViewIndex index)
+{
+ int32_t numInThread = 0;
+ nsMsgViewIndex startOfThread = index;
+ while ((int32_t) startOfThread >= 0 && m_levels[startOfThread] != 0)
+ startOfThread--;
+ nsMsgViewIndex threadIndex = startOfThread;
+ do
+ {
+ threadIndex++;
+ numInThread++;
+ }
+ while (threadIndex < m_levels.Length() && m_levels[threadIndex] != 0);
+
+ return numInThread;
+}
+
+// returns the number of lines that would be added (> 0) or removed (< 0)
+// if we were to try to expand/collapse the passed index.
+nsresult nsMsgDBView::ExpansionDelta(nsMsgViewIndex index, int32_t *expansionDelta)
+{
+ uint32_t numChildren;
+ nsresult rv;
+
+ *expansionDelta = 0;
+ if (index >= ((nsMsgViewIndex) m_keys.Length()))
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ char flags = m_flags[index];
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return NS_OK;
+
+ // The client can pass in the key of any message
+ // in a thread and get the expansion delta for the thread.
+
+ if (flags & nsMsgMessageFlags::Elided)
+ {
+ rv = GetThreadCount(index, &numChildren);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *expansionDelta = numChildren - 1;
+ }
+ else
+ {
+ numChildren = CountExpandedThread(index);
+ *expansionDelta = - (int32_t) (numChildren - 1);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ToggleExpansion(nsMsgViewIndex index, uint32_t *numChanged)
+{
+ nsresult rv;
+ NS_ENSURE_ARG(numChanged);
+ *numChanged = 0;
+ nsMsgViewIndex threadIndex = GetThreadIndex(index);
+ if (threadIndex == nsMsgViewIndex_None)
+ {
+ NS_ASSERTION(false, "couldn't find thread");
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ }
+ int32_t flags = m_flags[threadIndex];
+
+ // if not a thread, or doesn't have children, no expand/collapse
+ // If we add sub-thread expand collapse, this will need to be relaxed
+ if (!(flags & MSG_VIEW_FLAG_ISTHREAD) || !(flags & MSG_VIEW_FLAG_HASCHILDREN))
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ if (flags & nsMsgMessageFlags::Elided)
+ rv = ExpandByIndex(threadIndex, numChanged);
+ else
+ rv = CollapseByIndex(threadIndex, numChanged);
+
+ // if we collaps/uncollapse a thread, this changes the selected URIs
+ SelectionChanged();
+ return rv;
+}
+
+nsresult nsMsgDBView::ExpandAndSelectThread()
+{
+ nsresult rv;
+
+ NS_ASSERTION(mTreeSelection, "no tree selection");
+ if (!mTreeSelection) return NS_ERROR_UNEXPECTED;
+
+ int32_t index;
+ rv = mTreeSelection->GetCurrentIndex(&index);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = ExpandAndSelectThreadByIndex(index, false);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ExpandAndSelectThreadByIndex(nsMsgViewIndex index, bool augment)
+{
+ nsresult rv;
+
+ nsMsgViewIndex threadIndex;
+ bool inThreadedMode = (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay);
+
+ if (inThreadedMode)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+ threadIndex = ThreadIndexOfMsgHdr(msgHdr, index);
+ if (threadIndex == nsMsgViewIndex_None)
+ {
+ NS_ASSERTION(false, "couldn't find thread");
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ }
+ }
+ else
+ {
+ threadIndex = index;
+ }
+
+ int32_t flags = m_flags[threadIndex];
+ int32_t count = 0;
+
+ if (inThreadedMode && (flags & MSG_VIEW_FLAG_ISTHREAD) && (flags & MSG_VIEW_FLAG_HASCHILDREN))
+ {
+ // if closed, expand this thread.
+ if (flags & nsMsgMessageFlags::Elided)
+ {
+ uint32_t numExpanded;
+ rv = ExpandByIndex(threadIndex, &numExpanded);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // get the number of messages in the expanded thread
+ // so we know how many to select
+ count = CountExpandedThread(threadIndex);
+ }
+ else
+ {
+ count = 1;
+ }
+ NS_ASSERTION(count > 0, "bad count");
+
+ // update the selection
+
+ NS_ASSERTION(mTreeSelection, "no tree selection");
+ if (!mTreeSelection) return NS_ERROR_UNEXPECTED;
+
+ // the count should be 1 or greater. if there was only one message in the thread, we just select it.
+ // if more, we select all of them.
+ mTreeSelection->RangedSelect(threadIndex + count - 1, threadIndex, augment);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ExpandAll()
+{
+ if (mTree)
+ mTree->BeginUpdateBatch();
+ for (int32_t i = GetSize() - 1; i >= 0; i--)
+ {
+ uint32_t numExpanded;
+ uint32_t flags = m_flags[i];
+ if (flags & nsMsgMessageFlags::Elided)
+ ExpandByIndex(i, &numExpanded);
+ }
+ if (mTree)
+ mTree->EndUpdateBatch();
+ SelectionChanged();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread)
+{
+ if (!m_db) return NS_ERROR_FAILURE;
+ return m_db->GetThreadContainingMsgHdr(msgHdr, pThread);
+}
+
+nsresult nsMsgDBView::ExpandByIndex(nsMsgViewIndex index, uint32_t *pNumExpanded)
+{
+ if ((uint32_t) index >= m_keys.Length())
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ uint32_t flags = m_flags[index];
+ uint32_t numExpanded = 0;
+
+ NS_ASSERTION(flags & nsMsgMessageFlags::Elided, "can't expand an already expanded thread");
+ flags &= ~nsMsgMessageFlags::Elided;
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsCOMPtr <nsIMsgThread> pThread;
+ nsresult rv = GetThreadContainingIndex(index, getter_AddRefs(pThread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
+ {
+ if (flags & nsMsgMessageFlags::Read)
+ m_levels.AppendElement(0); // keep top level hdr in thread, even though read.
+ rv = ListUnreadIdsInThread(pThread, index, &numExpanded);
+ }
+ else
+ rv = ListIdsInThread(pThread, index, &numExpanded);
+
+ if (numExpanded > 0)
+ {
+ m_flags[index] = flags;
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ }
+ NoteStartChange(index + 1, numExpanded, nsMsgViewNotificationCode::insertOrDelete);
+ NoteEndChange(index + 1, numExpanded, nsMsgViewNotificationCode::insertOrDelete);
+ if (pNumExpanded != nullptr)
+ *pNumExpanded = numExpanded;
+ return rv;
+}
+
+nsresult nsMsgDBView::CollapseAll()
+{
+ for (uint32_t i = 0; i < GetSize(); i++)
+ {
+ uint32_t numExpanded;
+ uint32_t flags = m_flags[i];
+ if (!(flags & nsMsgMessageFlags::Elided) && (flags & MSG_VIEW_FLAG_HASCHILDREN))
+ CollapseByIndex(i, &numExpanded);
+ }
+ SelectionChanged();
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::CollapseByIndex(nsMsgViewIndex index, uint32_t *pNumCollapsed)
+{
+ nsresult rv;
+ int32_t flags = m_flags[index];
+ int32_t rowDelta = 0;
+
+ if (flags & nsMsgMessageFlags::Elided || !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) || !(flags & MSG_VIEW_FLAG_HASCHILDREN))
+ return NS_OK;
+
+ if (index > m_keys.Length())
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ rv = ExpansionDelta(index, &rowDelta);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ flags |= nsMsgMessageFlags::Elided;
+
+ m_flags[index] = flags;
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+
+ int32_t numRemoved = -rowDelta; // don't count first header in thread
+ if (index + 1 + numRemoved > m_keys.Length())
+ {
+ NS_ERROR("trying to remove too many rows");
+ numRemoved -= (index + 1 + numRemoved) - m_keys.Length();
+ if (numRemoved <= 0)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ }
+ NoteStartChange(index + 1, rowDelta, nsMsgViewNotificationCode::insertOrDelete);
+ // start at first id after thread.
+ RemoveRows(index + 1, numRemoved);
+ if (pNumCollapsed != nullptr)
+ *pNumCollapsed = numRemoved;
+ NoteEndChange(index + 1, rowDelta, nsMsgViewNotificationCode::insertOrDelete);
+
+ return rv;
+}
+
+nsresult nsMsgDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool /*ensureListed*/)
+{
+ nsresult rv = NS_OK;
+ // views can override this behaviour, which is to append to view.
+ // This is the mail behaviour, but threaded views will want
+ // to insert in order...
+ if (newHdr)
+ rv = AddHdr(newHdr);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetThreadContainingIndex(nsMsgViewIndex index, nsIMsgThread **resultThread)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return GetThreadContainingMsgHdr(msgHdr, resultThread);
+}
+
+nsMsgViewIndex
+nsMsgDBView::GetIndexForThread(nsIMsgDBHdr *msgHdr)
+{
+ // Take advantage of the fact that we're already sorted
+ // and find the insert index via a binary search, though expanded threads
+ // make that tricky.
+
+ nsMsgViewIndex highIndex = m_keys.Length();
+ nsMsgViewIndex lowIndex = 0;
+ IdKeyPtr EntryInfo1, EntryInfo2;
+ EntryInfo1.key = nullptr;
+ EntryInfo2.key = nullptr;
+
+ nsresult rv;
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // The following may leave fieldType undefined.
+ // In this case, we can return highIndex right away since
+ // it is the value returned in the default case of
+ // switch (fieldType) statement below.
+ rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler);
+ NS_ENSURE_SUCCESS(rv, highIndex);
+
+ const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2;
+
+ int retStatus = 0;
+ msgHdr->GetMessageKey(&EntryInfo1.id);
+ msgHdr->GetFolder(&EntryInfo1.folder);
+ EntryInfo1.folder->Release();
+
+ viewSortInfo comparisonContext;
+ comparisonContext.view = this;
+ comparisonContext.isSecondarySort = false;
+ comparisonContext.ascendingSort = (m_sortOrder == nsMsgViewSortOrder::ascending);
+ nsCOMPtr <nsIMsgDatabase> hdrDB;
+ EntryInfo1.folder->GetMsgDatabase(getter_AddRefs(hdrDB));
+ comparisonContext.db = hdrDB.get();
+ switch (fieldType)
+ {
+ case kCollationKey:
+ rv = GetCollationKey(msgHdr, m_sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+ break;
+ case kU32:
+ if (m_sortType == nsMsgViewSortType::byId)
+ EntryInfo1.dword = EntryInfo1.id;
+ else
+ GetLongField(msgHdr, m_sortType, &EntryInfo1.dword, colHandler);
+ break;
+ default:
+ return highIndex;
+ }
+ while (highIndex > lowIndex)
+ {
+ nsMsgViewIndex tryIndex = (lowIndex + highIndex) / 2;
+ // need to adjust tryIndex if it's not a thread.
+ while (m_levels[tryIndex] && tryIndex)
+ tryIndex--;
+
+ if (tryIndex < lowIndex)
+ {
+ NS_ERROR("try index shouldn't be less than low index");
+ break;
+ }
+ EntryInfo2.id = m_keys[tryIndex];
+ GetFolderForViewIndex(tryIndex, &EntryInfo2.folder);
+ EntryInfo2.folder->Release();
+
+ nsCOMPtr <nsIMsgDBHdr> tryHdr;
+ nsCOMPtr <nsIMsgDatabase> db;
+ // ### this should get the db from the folder...
+ GetDBForViewIndex(tryIndex, getter_AddRefs(db));
+ if (db)
+ rv = db->GetMsgHdrForKey(EntryInfo2.id, getter_AddRefs(tryHdr));
+ if (!tryHdr)
+ break;
+ if (tryHdr == msgHdr)
+ {
+ NS_WARNING("didn't expect header to already be in view");
+ highIndex = tryIndex;
+ break;
+ }
+ if (fieldType == kCollationKey)
+ {
+ PR_FREEIF(EntryInfo2.key);
+ rv = GetCollationKey(tryHdr, m_sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+ retStatus = FnSortIdKeyPtr(&pValue1, &pValue2, &comparisonContext);
+ }
+ else if (fieldType == kU32)
+ {
+ if (m_sortType == nsMsgViewSortType::byId)
+ EntryInfo2.dword = EntryInfo2.id;
+ else
+ GetLongField(tryHdr, m_sortType, &EntryInfo2.dword, colHandler);
+ retStatus = FnSortIdUint32(&pValue1, &pValue2, &comparisonContext);
+ }
+ if (retStatus == 0)
+ {
+ highIndex = tryIndex;
+ break;
+ }
+
+ if (retStatus < 0)
+ {
+ highIndex = tryIndex;
+ // we already made sure tryIndex was at a thread at the top of the loop.
+ }
+ else
+ {
+ lowIndex = tryIndex + 1;
+ while (lowIndex < GetSize() && m_levels[lowIndex])
+ lowIndex++;
+ }
+ }
+
+ PR_Free(EntryInfo1.key);
+ PR_Free(EntryInfo2.key);
+ return highIndex;
+}
+
+nsMsgViewIndex nsMsgDBView::GetInsertIndexHelper(nsIMsgDBHdr *msgHdr, nsTArray<nsMsgKey> &keys,
+ nsCOMArray<nsIMsgFolder> *folders,
+ nsMsgViewSortOrderValue sortOrder, nsMsgViewSortTypeValue sortType)
+{
+ nsMsgViewIndex highIndex = keys.Length();
+ nsMsgViewIndex lowIndex = 0;
+ IdKeyPtr EntryInfo1, EntryInfo2;
+ EntryInfo1.key = nullptr;
+ EntryInfo2.key = nullptr;
+
+ nsresult rv;
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // The following may leave fieldType undefined.
+ // In this case, we can return highIndex right away since
+ // it is the value returned in the default case of
+ // switch (fieldType) statement below.
+ rv = GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType, colHandler);
+ NS_ENSURE_SUCCESS(rv, highIndex);
+
+ const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2;
+
+ int (* comparisonFun) (const void *pItem1, const void *pItem2, void *privateData) = nullptr;
+ int retStatus = 0;
+ msgHdr->GetMessageKey(&EntryInfo1.id);
+ msgHdr->GetFolder(&EntryInfo1.folder);
+ EntryInfo1.folder->Release();
+
+ viewSortInfo comparisonContext;
+ comparisonContext.view = this;
+ comparisonContext.isSecondarySort = false;
+ comparisonContext.ascendingSort = (sortOrder == nsMsgViewSortOrder::ascending);
+ rv = EntryInfo1.folder->GetMsgDatabase(&comparisonContext.db);
+ NS_ENSURE_SUCCESS(rv, highIndex);
+ comparisonContext.db->Release();
+ switch (fieldType)
+ {
+ case kCollationKey:
+ rv = GetCollationKey(msgHdr, sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+ comparisonFun = FnSortIdKeyPtr;
+ break;
+ case kU32:
+ if (sortType == nsMsgViewSortType::byId)
+ EntryInfo1.dword = EntryInfo1.id;
+ else
+ GetLongField(msgHdr, sortType, &EntryInfo1.dword, colHandler);
+ comparisonFun = FnSortIdUint32;
+ break;
+ default:
+ return highIndex;
+ }
+ while (highIndex > lowIndex)
+ {
+ nsMsgViewIndex tryIndex = (lowIndex + highIndex - 1) / 2;
+ EntryInfo2.id = keys[tryIndex];
+ EntryInfo2.folder = folders ? folders->ObjectAt(tryIndex) : m_folder.get();
+
+ nsCOMPtr <nsIMsgDBHdr> tryHdr;
+ EntryInfo2.folder->GetMessageHeader(EntryInfo2.id, getter_AddRefs(tryHdr));
+ if (!tryHdr)
+ break;
+ if (fieldType == kCollationKey)
+ {
+ PR_FREEIF(EntryInfo2.key);
+ rv = GetCollationKey(tryHdr, sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+ }
+ else if (fieldType == kU32)
+ {
+ if (sortType == nsMsgViewSortType::byId) {
+ EntryInfo2.dword = EntryInfo2.id;
+ }
+ else {
+ GetLongField(tryHdr, sortType, &EntryInfo2.dword, colHandler);
+ }
+ }
+ retStatus = (*comparisonFun)(&pValue1, &pValue2, &comparisonContext);
+ if (retStatus == 0)
+ {
+ highIndex = tryIndex;
+ break;
+ }
+
+ if (retStatus < 0)
+ {
+ highIndex = tryIndex;
+ }
+ else
+ {
+ lowIndex = tryIndex + 1;
+ }
+ }
+
+ PR_Free(EntryInfo1.key);
+ PR_Free(EntryInfo2.key);
+ return highIndex;
+}
+
+nsMsgViewIndex nsMsgDBView::GetInsertIndex(nsIMsgDBHdr *msgHdr)
+{
+ if (!GetSize())
+ return 0;
+
+ if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) != 0
+ && !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ && m_sortOrder != nsMsgViewSortType::byId)
+ return GetIndexForThread(msgHdr);
+
+ return GetInsertIndexHelper(msgHdr, m_keys, GetFolders(), m_sortOrder, m_sortType);
+}
+
+nsresult nsMsgDBView::AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex)
+{
+ uint32_t flags = 0;
+#ifdef DEBUG_bienvenu
+ NS_ASSERTION(m_keys.Length() == m_flags.Length() && (int) m_keys.Length() == m_levels.Length(), "view arrays out of sync!");
+#endif
+
+ if (resultIndex)
+ *resultIndex = nsMsgViewIndex_None;
+
+ if (!GetShowingIgnored())
+ {
+ nsCOMPtr <nsIMsgThread> thread;
+ GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+ if (thread)
+ {
+ thread->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Ignored)
+ return NS_OK;
+ }
+
+ bool ignored;
+ msgHdr->GetIsKilled(&ignored);
+ if (ignored)
+ return NS_OK;
+ }
+
+ nsMsgKey msgKey, threadId;
+ nsMsgKey threadParent;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetThreadId(&threadId);
+ msgHdr->GetThreadParent(&threadParent);
+
+ msgHdr->GetFlags(&flags);
+ // ### this isn't quite right, is it? Should be checking that our thread parent key is none?
+ if (threadParent == nsMsgKey_None)
+ flags |= MSG_VIEW_FLAG_ISTHREAD;
+ nsMsgViewIndex insertIndex = GetInsertIndex(msgHdr);
+ if (insertIndex == nsMsgViewIndex_None)
+ {
+ // if unreadonly, level is 0 because we must be the only msg in the thread.
+ int32_t levelToAdd = 0;
+
+ if (m_sortOrder == nsMsgViewSortOrder::ascending)
+ {
+ InsertMsgHdrAt(GetSize(), msgHdr, msgKey, flags, levelToAdd);
+ if (resultIndex)
+ *resultIndex = GetSize() - 1;
+
+ // the call to NoteChange() has to happen after we add the key
+ // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
+ NoteChange(GetSize() - 1, 1, nsMsgViewNotificationCode::insertOrDelete);
+ }
+ else
+ {
+ InsertMsgHdrAt(0, msgHdr, msgKey, flags, levelToAdd);
+ if (resultIndex)
+ *resultIndex = 0;
+
+ // the call to NoteChange() has to happen after we insert the key
+ // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
+ NoteChange(0, 1, nsMsgViewNotificationCode::insertOrDelete);
+ }
+ m_sortValid = false;
+ }
+ else
+ {
+ InsertMsgHdrAt(insertIndex, msgHdr, msgKey, flags, 0);
+ if (resultIndex)
+ *resultIndex = insertIndex;
+ // the call to NoteChange() has to happen after we add the key
+ // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
+ NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+ }
+ OnHeaderAddedOrDeleted();
+ return NS_OK;
+}
+
+bool nsMsgDBView::WantsThisThread(nsIMsgThread * /*threadHdr*/)
+{
+ return true; // default is to want all threads.
+}
+
+nsMsgViewIndex nsMsgDBView::FindParentInThread(nsMsgKey parentKey, nsMsgViewIndex startOfThreadViewIndex)
+{
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ while (parentKey != nsMsgKey_None)
+ {
+ nsMsgViewIndex parentIndex = m_keys.IndexOf(parentKey, startOfThreadViewIndex);
+ if (parentIndex != nsMsgViewIndex_None)
+ return parentIndex;
+
+ if (NS_FAILED(m_db->GetMsgHdrForKey(parentKey, getter_AddRefs(msgHdr))))
+ break;
+
+ msgHdr->GetThreadParent(&parentKey);
+ }
+
+ return startOfThreadViewIndex;
+}
+
+nsresult nsMsgDBView::ListIdsInThreadOrder(nsIMsgThread *threadHdr, nsMsgKey parentKey,
+ uint32_t level, nsMsgViewIndex *viewIndex,
+ uint32_t *pNumListed)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsISimpleEnumerator> msgEnumerator;
+ threadHdr->EnumerateMessages(parentKey, getter_AddRefs(msgEnumerator));
+ uint32_t numChildren;
+ (void) threadHdr->GetNumChildren(&numChildren);
+ NS_ASSERTION(numChildren, "Empty thread in view/db");
+ if (!numChildren)
+ return NS_OK; // bogus, but harmless.
+
+ numChildren--; // account for the existing thread root
+
+ // skip the first one.
+ bool hasMore;
+ nsCOMPtr <nsISupports> supports;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = msgEnumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = msgEnumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv) && supports)
+ {
+ if (*pNumListed == numChildren)
+ {
+ NS_NOTREACHED("thread corrupt in db");
+ // if we've listed more messages than are in the thread, then the db
+ // is corrupt, and we should invalidate it.
+ // we'll use this rv to indicate there's something wrong with the db
+ // though for now it probably won't get paid attention to.
+ m_db->SetSummaryValid(false);
+ rv = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ break;
+ }
+
+ msgHdr = do_QueryInterface(supports);
+ if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored))
+ {
+ bool ignored;
+ msgHdr->GetIsKilled(&ignored);
+ // We are not going to process subthreads, horribly invalidating the
+ // numChildren characteristic
+ if (ignored)
+ continue;
+ }
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags, newFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ AdjustReadFlag(msgHdr, &msgFlags);
+ SetMsgHdrAt(msgHdr, *viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS, level);
+ // turn off thread or elided bit if they got turned on (maybe from new only view?)
+ msgHdr->AndFlags(~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided), &newFlags);
+ (*pNumListed)++;
+ (*viewIndex)++;
+ rv = ListIdsInThreadOrder(threadHdr, msgKey, level + 1, viewIndex, pNumListed);
+ }
+ }
+ return rv; // we don't want to return the rv from the enumerator when it reaches the end, do we?
+}
+
+bool nsMsgDBView::InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows)
+{
+ return m_keys.InsertElementsAt(viewIndex, numRows, 0) &&
+ m_flags.InsertElementsAt(viewIndex, numRows, 0) &&
+ m_levels.InsertElementsAt(viewIndex, numRows, 1);
+}
+
+void nsMsgDBView::RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows)
+{
+ m_keys.RemoveElementsAt(viewIndex, numRows);
+ m_flags.RemoveElementsAt(viewIndex, numRows);
+ m_levels.RemoveElementsAt(viewIndex, numRows);
+}
+
+NS_IMETHODIMP nsMsgDBView::InsertTreeRows(nsMsgViewIndex aIndex,
+ uint32_t aNumRows,
+ nsMsgKey aKey,
+ nsMsgViewFlagsTypeValue aFlags,
+ uint32_t aLevel,
+ nsIMsgFolder *aFolder)
+{
+ if (GetSize() < aIndex)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMArray<nsIMsgFolder> *folders = GetFolders();
+ if (folders)
+ {
+ // In a search/xfvf view only, a folder is required.
+ NS_ENSURE_ARG_POINTER(aFolder);
+ for (size_t i = 0; i < aNumRows; i++)
+ // Insert into m_folders.
+ if (!folders->InsertObjectAt(aFolder, aIndex + i))
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (m_keys.InsertElementsAt(aIndex, aNumRows, aKey) &&
+ m_flags.InsertElementsAt(aIndex, aNumRows, aFlags) &&
+ m_levels.InsertElementsAt(aIndex, aNumRows, aLevel))
+ return NS_OK;
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsMsgDBView::RemoveTreeRows(nsMsgViewIndex aIndex,
+ uint32_t aNumRows)
+{
+ // Prevent a crash if attempting to remove rows which don't exist.
+ if (GetSize() < aIndex + aNumRows)
+ return NS_ERROR_UNEXPECTED;
+
+ nsMsgDBView::RemoveRows(aIndex, aNumRows);
+
+ nsCOMArray<nsIMsgFolder> *folders = GetFolders();
+ if (folders)
+ // In a search/xfvf view only, remove from m_folders.
+ if (!folders->RemoveObjectsAt(aIndex, aNumRows))
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed)
+{
+ NS_ENSURE_ARG(threadHdr);
+ // these children ids should be in thread order.
+ nsresult rv = NS_OK;
+ uint32_t i;
+ nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1;
+ *pNumListed = 0;
+
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ NS_ASSERTION(numChildren, "Empty thread in view/db");
+ if (!numChildren)
+ return NS_OK;
+
+ numChildren--; // account for the existing thread root
+ if (!InsertEmptyRows(viewIndex, numChildren))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // ### need to rework this when we implemented threading in group views.
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ {
+ nsMsgKey parentKey = m_keys[startOfThreadViewIndex];
+ // If the thread is bigger than the hdr cache, expanding the thread
+ // can be slow. Increasing the hdr cache size will help a fair amount.
+ uint32_t hdrCacheSize;
+ m_db->GetMsgHdrCacheSize(&hdrCacheSize);
+ if (numChildren > hdrCacheSize)
+ m_db->SetMsgHdrCacheSize(numChildren);
+ // If this fails, *pNumListed will be 0, and we'll fall back to just
+ // enumerating the messages in the thread below.
+ rv = ListIdsInThreadOrder(threadHdr, parentKey, 1, &viewIndex, pNumListed);
+ if (numChildren > hdrCacheSize)
+ m_db->SetMsgHdrCacheSize(hdrCacheSize);
+ }
+ if (! *pNumListed)
+ {
+ uint32_t ignoredHeaders = 0;
+ // if we're not threaded, just list em out in db order
+ for (i = 1; i <= numChildren; i++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+
+ if (msgHdr != nullptr)
+ {
+ if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored))
+ {
+ bool killed;
+ msgHdr->GetIsKilled(&killed);
+ if (killed)
+ {
+ ignoredHeaders++;
+ continue;
+ }
+ }
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags, newFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ AdjustReadFlag(msgHdr, &msgFlags);
+ SetMsgHdrAt(msgHdr, viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS, 1);
+ // here, we're either flat, or we're grouped - in either case, level is 1
+ // turn off thread or elided bit if they got turned on (maybe from new only view?)
+ if (i > 0)
+ msgHdr->AndFlags(~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided), &newFlags);
+ (*pNumListed)++;
+ viewIndex++;
+ }
+ }
+ if (ignoredHeaders + *pNumListed < numChildren)
+ {
+ NS_NOTREACHED("thread corrupt in db");
+ // if we've listed fewer messages than are in the thread, then the db
+ // is corrupt, and we should invalidate it.
+ // we'll use this rv to indicate there's something wrong with the db
+ // though for now it probably won't get paid attention to.
+ m_db->SetSummaryValid(false);
+ rv = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ }
+
+ // We may have added too many elements (i.e., subthreads were cut)
+ // ### fix for cross folder view case.
+ if (*pNumListed < numChildren)
+ RemoveRows(viewIndex, numChildren - *pNumListed);
+ return rv;
+}
+
+int32_t nsMsgDBView::FindLevelInThread(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex)
+{
+ nsCOMPtr <nsIMsgDBHdr> curMsgHdr = msgHdr;
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+
+ // look through the ancestors of the passed in msgHdr in turn, looking for them in the view, up to the start of
+ // the thread. If we find an ancestor, then our level is one greater than the level of the ancestor.
+ while (curMsgHdr)
+ {
+ nsMsgKey parentKey;
+ curMsgHdr->GetThreadParent(&parentKey);
+ if (parentKey == nsMsgKey_None)
+ break;
+
+ // scan up to find view index of ancestor, if any
+ for (nsMsgViewIndex indexToTry = viewIndex; indexToTry && indexToTry-- >= startOfThread;)
+ {
+ if (m_keys[indexToTry] == parentKey)
+ return m_levels[indexToTry] + 1;
+ }
+
+ // if msgHdr's key is its parentKey, we'll loop forever, so protect
+ // against that corruption.
+ if (msgKey == parentKey || NS_FAILED(m_db->GetMsgHdrForKey(parentKey, getter_AddRefs(curMsgHdr))))
+ {
+ NS_ERROR("msgKey == parentKey, or GetMsgHdrForKey failed, this used to be an infinte loop condition");
+ curMsgHdr = nullptr;
+ }
+ else
+ {
+ // need to update msgKey so the check for a msgHdr with matching
+ // key+parentKey will work after first time through loop
+ curMsgHdr->GetMessageKey(&msgKey);
+ }
+ }
+ return 1;
+}
+
+// ### Can this be combined with GetIndexForThread??
+nsMsgViewIndex
+nsMsgDBView::GetThreadRootIndex(nsIMsgDBHdr *msgHdr)
+{
+ if (!msgHdr)
+ {
+ NS_WARNING("null msgHdr parameter");
+ return nsMsgViewIndex_None;
+ }
+
+ // Take advantage of the fact that we're already sorted
+ // and find the thread root via a binary search.
+
+ nsMsgViewIndex highIndex = m_keys.Length();
+ nsMsgViewIndex lowIndex = 0;
+ IdKeyPtr EntryInfo1, EntryInfo2;
+ EntryInfo1.key = nullptr;
+ EntryInfo2.key = nullptr;
+
+ nsresult rv;
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // The following may leave fieldType undefined.
+ // In this case, we can return highIndex right away since
+ // it is the value returned in the default case of
+ // switch (fieldType) statement below.
+ rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler);
+ NS_ENSURE_SUCCESS(rv, highIndex);
+
+ const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2;
+
+ int retStatus = 0;
+ msgHdr->GetMessageKey(&EntryInfo1.id);
+ msgHdr->GetFolder(&EntryInfo1.folder);
+ EntryInfo1.folder->Release();
+
+ viewSortInfo comparisonContext;
+ comparisonContext.view = this;
+ comparisonContext.isSecondarySort = false;
+ comparisonContext.ascendingSort = (m_sortOrder == nsMsgViewSortOrder::ascending);
+ nsCOMPtr<nsIMsgDatabase> hdrDB;
+ EntryInfo1.folder->GetMsgDatabase(getter_AddRefs(hdrDB));
+ comparisonContext.db = hdrDB.get();
+ switch (fieldType)
+ {
+ case kCollationKey:
+ rv = GetCollationKey(msgHdr, m_sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+ break;
+ case kU32:
+ if (m_sortType == nsMsgViewSortType::byId)
+ EntryInfo1.dword = EntryInfo1.id;
+ else
+ GetLongField(msgHdr, m_sortType, &EntryInfo1.dword, colHandler);
+ break;
+ default:
+ return highIndex;
+ }
+ while (highIndex > lowIndex)
+ {
+ nsMsgViewIndex tryIndex = (lowIndex + highIndex) / 2;
+ // need to adjust tryIndex if it's not a thread.
+ while (m_levels[tryIndex] && tryIndex)
+ tryIndex--;
+
+ if (tryIndex < lowIndex)
+ {
+ NS_ERROR("try index shouldn't be less than low index");
+ break;
+ }
+ EntryInfo2.id = m_keys[tryIndex];
+ GetFolderForViewIndex(tryIndex, &EntryInfo2.folder);
+ EntryInfo2.folder->Release();
+
+ nsCOMPtr<nsIMsgDBHdr> tryHdr;
+ nsCOMPtr<nsIMsgDatabase> db;
+ // ### this should get the db from the folder...
+ GetDBForViewIndex(tryIndex, getter_AddRefs(db));
+ if (db)
+ rv = db->GetMsgHdrForKey(EntryInfo2.id, getter_AddRefs(tryHdr));
+ if (!tryHdr)
+ break;
+ if (tryHdr == msgHdr)
+ {
+ highIndex = tryIndex;
+ break;
+ }
+ if (fieldType == kCollationKey)
+ {
+ PR_FREEIF(EntryInfo2.key);
+ rv = GetCollationKey(tryHdr, m_sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+ retStatus = FnSortIdKeyPtr(&pValue1, &pValue2, &comparisonContext);
+ }
+ else if (fieldType == kU32)
+ {
+ if (m_sortType == nsMsgViewSortType::byId)
+ EntryInfo2.dword = EntryInfo2.id;
+ else
+ GetLongField(tryHdr, m_sortType, &EntryInfo2.dword, colHandler);
+ retStatus = FnSortIdUint32(&pValue1, &pValue2, &comparisonContext);
+ }
+ if (retStatus == 0)
+ {
+ highIndex = tryIndex;
+ break;
+ }
+
+ if (retStatus < 0)
+ {
+ highIndex = tryIndex;
+ // we already made sure tryIndex was at a thread at the top of the loop.
+ }
+ else
+ {
+ lowIndex = tryIndex + 1;
+ while (lowIndex < GetSize() && m_levels[lowIndex])
+ lowIndex++;
+ }
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> resultHdr;
+ GetMsgHdrForViewIndex(highIndex, getter_AddRefs(resultHdr));
+
+ if (resultHdr != msgHdr)
+ {
+ NS_WARNING("didn't find hdr");
+ highIndex = FindHdr(msgHdr);
+#ifdef DEBUG_David_Bienvenu
+ if (highIndex != nsMsgViewIndex_None)
+ {
+ NS_WARNING("but find hdr did");
+ printf("level of found hdr = %d\n", m_levels[highIndex]);
+ ValidateSort();
+ }
+#endif
+ return highIndex;
+ }
+ PR_Free(EntryInfo1.key);
+ PR_Free(EntryInfo2.key);
+ return msgHdr == resultHdr ? highIndex : nsMsgViewIndex_None;
+}
+
+#ifdef DEBUG_David_Bienvenu
+
+void nsMsgDBView::InitEntryInfoForIndex(nsMsgViewIndex i, IdKeyPtr &EntryInfo)
+{
+ EntryInfo.key = nullptr;
+
+ nsresult rv;
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // The following may leave fieldType undefined.
+ rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to obtain fieldType");
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
+
+ msgHdr->GetMessageKey(&EntryInfo.id);
+ msgHdr->GetFolder(&EntryInfo.folder);
+ EntryInfo.folder->Release();
+
+ nsCOMPtr<nsIMsgDatabase> hdrDB;
+ EntryInfo.folder->GetMsgDatabase(getter_AddRefs(hdrDB));
+ switch (fieldType)
+ {
+ case kCollationKey:
+ PR_FREEIF(EntryInfo.key);
+ rv = GetCollationKey(msgHdr, m_sortType, &EntryInfo.key, &EntryInfo.dword, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key");
+ break;
+ case kU32:
+ if (m_sortType == nsMsgViewSortType::byId)
+ EntryInfo.dword = EntryInfo.id;
+ else
+ GetLongField(msgHdr, m_sortType, &EntryInfo.dword, colHandler);
+ break;
+ default:
+ NS_ERROR("invalid field type");
+ }
+}
+
+void nsMsgDBView::ValidateSort()
+{
+ IdKeyPtr EntryInfo1, EntryInfo2;
+ nsCOMPtr<nsIMsgDBHdr> hdr1, hdr2;
+
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // It is not entirely clear what we should do since,
+ // if fieldType is not available, there is no way to know
+ // how to compare the field to check for sorting.
+ // So we bomb out here. It is OK since this is debug code
+ // inside #ifdef DEBUG_David_Bienvenu
+ rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to obtain fieldType");
+
+ viewSortInfo comparisonContext;
+ comparisonContext.view = this;
+ comparisonContext.isSecondarySort = false;
+ comparisonContext.ascendingSort = (m_sortOrder == nsMsgViewSortOrder::ascending);
+ nsCOMPtr<nsIMsgDatabase> db;
+ GetDBForViewIndex(0, getter_AddRefs(db));
+ // this is only for comparing collation keys - it could be any db.
+ comparisonContext.db = db.get();
+
+ for (nsMsgViewIndex i = 0; i < m_keys.Length();)
+ {
+ // ignore non threads
+ if (m_levels[i])
+ {
+ i++;
+ continue;
+ }
+
+ // find next header.
+ nsMsgViewIndex j = i + 1;
+ while (j < m_keys.Length() && m_levels[j])
+ j++;
+ if (j == m_keys.Length())
+ break;
+
+ InitEntryInfoForIndex(i, EntryInfo1);
+ InitEntryInfoForIndex(j, EntryInfo2);
+ const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2;
+ int retStatus = 0;
+ if (fieldType == kCollationKey)
+ retStatus = FnSortIdKeyPtr(&pValue1, &pValue2, &comparisonContext);
+ else if (fieldType == kU32)
+ retStatus = FnSortIdUint32(&pValue1, &pValue2, &comparisonContext);
+
+ if (retStatus && (retStatus < 0) == (m_sortOrder == nsMsgViewSortOrder::ascending))
+ {
+ NS_ERROR("view not sorted correctly");
+ break;
+ }
+ // j is the new i.
+ i = j;
+ }
+}
+
+#endif
+
+nsresult nsMsgDBView::ListUnreadIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed)
+{
+ NS_ENSURE_ARG(threadHdr);
+ // these children ids should be in thread order.
+ nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1;
+ *pNumListed = 0;
+ nsMsgKey topLevelMsgKey = m_keys[startOfThreadViewIndex];
+
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ uint32_t i;
+ for (i = 0; i < numChildren; i++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+ if (msgHdr != nullptr)
+ {
+ if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored))
+ {
+ bool killed;
+ msgHdr->GetIsKilled(&killed);
+ if (killed)
+ continue;
+ }
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ bool isRead = AdjustReadFlag(msgHdr, &msgFlags);
+ if (!isRead)
+ {
+ // just make sure flag is right in db.
+ m_db->MarkHdrRead(msgHdr, false, nullptr);
+ if (msgKey != topLevelMsgKey)
+ {
+ InsertMsgHdrAt(viewIndex, msgHdr, msgKey, msgFlags,
+ FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex));
+ viewIndex++;
+ (*pNumListed)++;
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags, nsIDBChangeListener *aInstigator)
+{
+ // if we're not the instigator, update flags if this key is in our view
+ if (aInstigator != this)
+ {
+ NS_ENSURE_ARG_POINTER(aHdrChanged);
+ nsMsgKey msgKey;
+ aHdrChanged->GetMessageKey(&msgKey);
+ nsMsgViewIndex index = FindHdr(aHdrChanged);
+ if (index != nsMsgViewIndex_None)
+ {
+ uint32_t viewOnlyFlags = m_flags[index] & (MSG_VIEW_FLAGS | nsMsgMessageFlags::Elided);
+
+ // ### what about saving the old view only flags, like IsThread and HasChildren?
+ // I think we'll want to save those away.
+ m_flags[index] = aNewFlags | viewOnlyFlags;
+ // tell the view the extra flag changed, so it can
+ // update the previous view, if any.
+ OnExtraFlagChanged(index, aNewFlags);
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ }
+
+ uint32_t deltaFlags = (aOldFlags ^ aNewFlags);
+ if (deltaFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::New))
+ {
+ nsMsgViewIndex threadIndex =
+ ThreadIndexOfMsgHdr(aHdrChanged, index, nullptr, nullptr);
+ // may need to fix thread counts
+ if (threadIndex != nsMsgViewIndex_None && threadIndex != index)
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+ // don't need to propagate notifications, right?
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged,
+ nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener *aInstigator)
+{
+ nsMsgViewIndex deletedIndex = FindHdr(aHdrChanged);
+ if (IsValidIndex(deletedIndex))
+ {
+ // Check if this message is currently selected. If it is, tell the frontend
+ // to be prepared for a delete.
+ if (mTreeSelection && mCommandUpdater)
+ {
+ bool isMsgSelected = false;
+ mTreeSelection->IsSelected(deletedIndex, &isMsgSelected);
+ if (isMsgSelected)
+ mCommandUpdater->UpdateNextMessageAfterDelete();
+ }
+
+ RemoveByIndex(deletedIndex);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::OnHdrAdded(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener *aInstigator)
+{
+ return OnNewHeader(aHdrChanged, aParentKey, false);
+ // probably also want to pass that parent key in, since we went to the trouble
+ // of figuring out what it is.
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus,
+ nsIDBChangeListener * aInstigator)
+{
+ if (aPreChange)
+ return NS_OK;
+
+ if (aHdrToChange)
+ {
+ nsMsgViewIndex index = FindHdr(aHdrToChange);
+ if (index != nsMsgViewIndex_None)
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::OnParentChanged (nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
+{
+ if (m_db)
+ {
+ m_db->RemoveListener(this);
+ m_db = nullptr;
+ }
+
+ int32_t saveSize = GetSize();
+ ClearHdrCache();
+
+ // this is important, because the tree will ask us for our
+ // row count, which get determine from the number of keys.
+ m_keys.Clear();
+ // be consistent
+ m_flags.Clear();
+ m_levels.Clear();
+
+ // tell the tree all the rows have gone away
+ if (mTree)
+ mTree->RowCountChanged(0, -saveSize);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ if (!strcmp(aEvent, "DBOpened"))
+ m_db = aDB;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBView::OnReadChanged(nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::OnJunkScoreChanged(nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+void nsMsgDBView::ClearHdrCache()
+{
+ m_cachedHdr = nullptr;
+ m_cachedMsgKey = nsMsgKey_None;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetSuppressChangeNotifications(bool aSuppressChangeNotifications)
+{
+ mSuppressChangeNotification = aSuppressChangeNotifications;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetSuppressChangeNotifications(bool * aSuppressChangeNotifications)
+{
+ NS_ENSURE_ARG_POINTER(aSuppressChangeNotifications);
+ *aSuppressChangeNotifications = mSuppressChangeNotification;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::NoteChange(nsMsgViewIndex firstLineChanged,
+ int32_t numChanged,
+ nsMsgViewNotificationCodeValue changeType)
+{
+ if (mTree && !mSuppressChangeNotification)
+ {
+ switch (changeType)
+ {
+ case nsMsgViewNotificationCode::changed:
+ mTree->InvalidateRange(firstLineChanged, firstLineChanged + numChanged - 1);
+ break;
+ case nsMsgViewNotificationCode::insertOrDelete:
+ if (numChanged < 0)
+ mRemovingRow = true;
+ // the caller needs to have adjusted m_keys before getting here, since
+ // RowCountChanged() will call our GetRowCount()
+ mTree->RowCountChanged(firstLineChanged, numChanged);
+ mRemovingRow = false;
+ MOZ_FALLTHROUGH;
+ case nsMsgViewNotificationCode::all:
+ ClearHdrCache();
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+void nsMsgDBView::NoteStartChange(nsMsgViewIndex firstlineChanged, int32_t numChanged,
+ nsMsgViewNotificationCodeValue changeType)
+{
+}
+void nsMsgDBView::NoteEndChange(nsMsgViewIndex firstlineChanged, int32_t numChanged,
+ nsMsgViewNotificationCodeValue changeType)
+{
+ // send the notification now.
+ NoteChange(firstlineChanged, numChanged, changeType);
+}
+
+NS_IMETHODIMP nsMsgDBView::GetSortOrder(nsMsgViewSortOrderValue *aSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = m_sortOrder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetSortType(nsMsgViewSortTypeValue *aSortType)
+{
+ NS_ENSURE_ARG_POINTER(aSortType);
+ *aSortType = m_sortType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetSortType(nsMsgViewSortTypeValue aSortType)
+{
+ m_sortType = aSortType;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBView::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+ NS_ERROR("you should be overriding this");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetSecondarySortOrder(nsMsgViewSortOrderValue *aSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = m_secondarySortOrder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetSecondarySortOrder(nsMsgViewSortOrderValue aSortOrder)
+{
+ m_secondarySortOrder = aSortOrder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetSecondarySortType(nsMsgViewSortTypeValue *aSortType)
+{
+ NS_ENSURE_ARG_POINTER(aSortType);
+ *aSortType = m_secondarySort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetSecondarySortType(nsMsgViewSortTypeValue aSortType)
+{
+ m_secondarySort = aSortType;
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::PersistFolderInfo(nsIDBFolderInfo **dbFolderInfo)
+{
+ nsresult rv = m_db->GetDBFolderInfo(dbFolderInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // save off sort type and order, view type and flags
+ (*dbFolderInfo)->SetSortType(m_sortType);
+ (*dbFolderInfo)->SetSortOrder(m_sortOrder);
+ (*dbFolderInfo)->SetViewFlags(m_viewFlags);
+ nsMsgViewTypeValue viewType;
+ GetViewType(&viewType);
+ (*dbFolderInfo)->SetViewType(viewType);
+ return rv;
+}
+
+
+NS_IMETHODIMP nsMsgDBView::GetViewFlags(nsMsgViewFlagsTypeValue *aViewFlags)
+{
+ NS_ENSURE_ARG_POINTER(aViewFlags);
+ *aViewFlags = m_viewFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags)
+{
+ // if we're turning off threaded display, we need to expand all so that all
+ // messages will be displayed.
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && ! (aViewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ {
+ ExpandAll();
+ m_sortValid = false; // invalidate the sort so sorting will do something
+ }
+ m_viewFlags = aViewFlags;
+
+ if (m_viewFolder)
+ {
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return folderInfo->SetViewFlags(aViewFlags);
+ }
+ else
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::MarkThreadOfMsgRead(nsMsgKey msgId, nsMsgViewIndex msgIndex, nsTArray<nsMsgKey> &idsMarkedRead, bool bRead)
+{
+ nsCOMPtr <nsIMsgThread> threadHdr;
+ nsresult rv = GetThreadContainingIndex(msgIndex, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgViewIndex threadIndex;
+
+ NS_ASSERTION(threadHdr, "threadHdr is null");
+ if (!threadHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsCOMPtr<nsIMsgDBHdr> firstHdr;
+ rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(firstHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey firstHdrId;
+ firstHdr->GetMessageKey(&firstHdrId);
+ if (msgId != firstHdrId)
+ threadIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr);
+ else
+ threadIndex = msgIndex;
+ return MarkThreadRead(threadHdr, threadIndex, idsMarkedRead, bRead);
+}
+
+nsresult nsMsgDBView::MarkThreadRead(nsIMsgThread *threadHdr, nsMsgViewIndex threadIndex, nsTArray<nsMsgKey> &idsMarkedRead, bool bRead)
+{
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ idsMarkedRead.SetCapacity(numChildren);
+ for (int32_t childIndex = 0; childIndex < (int32_t) numChildren ; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(childIndex, getter_AddRefs(msgHdr));
+ NS_ASSERTION(msgHdr, "msgHdr is null");
+ if (!msgHdr)
+ continue;
+
+ bool isRead;
+
+ nsMsgKey hdrMsgId;
+ msgHdr->GetMessageKey(&hdrMsgId);
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = GetDBForHeader(msgHdr, getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ db->IsRead(hdrMsgId, &isRead);
+
+ if (isRead != bRead)
+ {
+ // MarkHdrRead will change the unread count on the thread
+ db->MarkHdrRead(msgHdr, bRead, nullptr);
+ // insert at the front. should we insert at the end?
+ idsMarkedRead.InsertElementAt(0, hdrMsgId);
+ }
+ }
+
+ return NS_OK;
+}
+
+bool nsMsgDBView::AdjustReadFlag(nsIMsgDBHdr *msgHdr, uint32_t *msgFlags)
+{
+ // if we're a cross-folder view, just bail on this.
+ if (GetFolders())
+ return *msgFlags & nsMsgMessageFlags::Read;
+ bool isRead = false;
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ m_db->IsRead(msgKey, &isRead);
+ // just make sure flag is right in db.
+#ifdef DEBUG_David_Bienvenu
+ NS_ASSERTION(isRead == ((*msgFlags & nsMsgMessageFlags::Read) != 0), "msgFlags out of sync");
+#endif
+ if (isRead)
+ *msgFlags |= nsMsgMessageFlags::Read;
+ else
+ *msgFlags &= ~nsMsgMessageFlags::Read;
+ m_db->MarkHdrRead(msgHdr, isRead, nullptr);
+ return isRead;
+}
+
+// Starting from startIndex, performs the passed in navigation, including
+// any marking read needed, and returns the resultId and resultIndex of the
+// destination of the navigation. If no message is found in the view,
+// it returns a resultId of nsMsgKey_None and an resultIndex of nsMsgViewIndex_None.
+NS_IMETHODIMP nsMsgDBView::ViewNavigate(nsMsgNavigationTypeValue motion, nsMsgKey *pResultKey, nsMsgViewIndex *pResultIndex, nsMsgViewIndex *pThreadIndex, bool wrap)
+{
+ NS_ENSURE_ARG_POINTER(pResultKey);
+ NS_ENSURE_ARG_POINTER(pResultIndex);
+ NS_ENSURE_ARG_POINTER(pThreadIndex);
+
+ int32_t currentIndex;
+ nsMsgViewIndex startIndex;
+
+ if (!mTreeSelection) // we must be in stand alone message mode
+ {
+ currentIndex = FindViewIndex(m_currentlyDisplayedMsgKey);
+ }
+ else
+ {
+ nsresult rv = mTreeSelection->GetCurrentIndex(&currentIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ startIndex = currentIndex;
+ return nsMsgDBView::NavigateFromPos(motion, startIndex, pResultKey, pResultIndex, pThreadIndex, wrap);
+}
+
+nsresult nsMsgDBView::NavigateFromPos(nsMsgNavigationTypeValue motion, nsMsgViewIndex startIndex, nsMsgKey *pResultKey, nsMsgViewIndex *pResultIndex, nsMsgViewIndex *pThreadIndex, bool wrap)
+{
+ nsresult rv = NS_OK;
+ nsMsgKey resultThreadKey;
+ nsMsgViewIndex curIndex;
+ nsMsgViewIndex lastIndex = (GetSize() > 0) ? (nsMsgViewIndex) GetSize() - 1 : nsMsgViewIndex_None;
+ nsMsgViewIndex threadIndex = nsMsgViewIndex_None;
+
+ // if there aren't any messages in the view, bail out.
+ if (GetSize() <= 0)
+ {
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ return NS_OK;
+ }
+
+ switch (motion)
+ {
+ case nsMsgNavigationType::firstMessage:
+ *pResultIndex = 0;
+ *pResultKey = m_keys[0];
+ break;
+ case nsMsgNavigationType::nextMessage:
+ // return same index and id on next on last message
+ *pResultIndex = std::min(startIndex + 1, lastIndex);
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::previousMessage:
+ *pResultIndex = (startIndex != nsMsgViewIndex_None && startIndex > 0) ? startIndex - 1 : 0;
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::lastMessage:
+ *pResultIndex = lastIndex;
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::firstFlagged:
+ rv = FindFirstFlagged(pResultIndex);
+ if (IsValidIndex(*pResultIndex))
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::nextFlagged:
+ rv = FindNextFlagged(startIndex + 1, pResultIndex);
+ if (IsValidIndex(*pResultIndex))
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::previousFlagged:
+ rv = FindPrevFlagged(startIndex, pResultIndex);
+ if (IsValidIndex(*pResultIndex))
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::firstNew:
+ rv = FindFirstNew(pResultIndex);
+ if (IsValidIndex(*pResultIndex))
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::firstUnreadMessage:
+ startIndex = nsMsgViewIndex_None; // note fall thru - is this motion ever used?
+ MOZ_FALLTHROUGH;
+ case nsMsgNavigationType::nextUnreadMessage:
+ for (curIndex = (startIndex == nsMsgViewIndex_None) ? 0 : startIndex; curIndex <= lastIndex && lastIndex != nsMsgViewIndex_None; curIndex++) {
+ uint32_t flags = m_flags[curIndex];
+
+ // don't return start index since navigate should move
+ if (!(flags & (nsMsgMessageFlags::Read | MSG_VIEW_FLAG_DUMMY)) && (curIndex != startIndex))
+ {
+ *pResultIndex = curIndex;
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ }
+ // check for collapsed thread with new children
+ if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) && flags & MSG_VIEW_FLAG_ISTHREAD && flags & nsMsgMessageFlags::Elided) {
+ nsCOMPtr <nsIMsgThread> threadHdr;
+ GetThreadContainingIndex(curIndex, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(threadHdr, "threadHdr is null");
+ if (!threadHdr)
+ continue;
+ uint32_t numUnreadChildren;
+ threadHdr->GetNumUnreadChildren(&numUnreadChildren);
+ if (numUnreadChildren > 0)
+ {
+ uint32_t numExpanded;
+ ExpandByIndex(curIndex, &numExpanded);
+ lastIndex += numExpanded;
+ if (pThreadIndex)
+ *pThreadIndex = curIndex;
+ }
+ }
+ }
+ if (curIndex > lastIndex)
+ {
+ // wrap around by starting at index 0.
+ if (wrap)
+ {
+ nsMsgKey startKey = GetAt(startIndex);
+
+ rv = NavigateFromPos(nsMsgNavigationType::nextUnreadMessage, nsMsgViewIndex_None, pResultKey, pResultIndex, pThreadIndex, false);
+
+ if (*pResultKey == startKey)
+ {
+ // wrapped around and found start message!
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ }
+ }
+ else
+ {
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ }
+ }
+ break;
+ case nsMsgNavigationType::previousUnreadMessage:
+ if (startIndex == nsMsgViewIndex_None)
+ break;
+ rv = FindPrevUnread(m_keys[startIndex], pResultKey,
+ &resultThreadKey);
+ if (NS_SUCCEEDED(rv))
+ {
+ *pResultIndex = FindViewIndex(*pResultKey);
+ if (*pResultKey != resultThreadKey && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ {
+ threadIndex = GetThreadIndex(*pResultIndex);
+ if (*pResultIndex == nsMsgViewIndex_None)
+ {
+ nsCOMPtr <nsIMsgThread> threadHdr;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = m_db->GetMsgHdrForKey(*pResultKey, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(threadHdr, "threadHdr is null");
+ if (threadHdr)
+ break;
+ uint32_t numUnreadChildren;
+ threadHdr->GetNumUnreadChildren(&numUnreadChildren);
+ if (numUnreadChildren > 0)
+ {
+ uint32_t numExpanded;
+ ExpandByIndex(threadIndex, &numExpanded);
+ }
+ *pResultIndex = FindViewIndex(*pResultKey);
+ }
+ }
+ if (pThreadIndex)
+ *pThreadIndex = threadIndex;
+ }
+ break;
+ case nsMsgNavigationType::lastUnreadMessage:
+ break;
+ case nsMsgNavigationType::nextUnreadThread:
+ if (startIndex != nsMsgViewIndex_None)
+ ApplyCommandToIndices(nsMsgViewCommandType::markThreadRead, &startIndex, 1);
+
+ return NavigateFromPos(nsMsgNavigationType::nextUnreadMessage, startIndex, pResultKey, pResultIndex, pThreadIndex, true);
+ case nsMsgNavigationType::toggleThreadKilled:
+ {
+ bool resultKilled;
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ ToggleIgnored(selection.Elements(), selection.Length(), &threadIndex, &resultKilled);
+ if (resultKilled)
+ {
+ return NavigateFromPos(nsMsgNavigationType::nextUnreadThread, threadIndex, pResultKey, pResultIndex, pThreadIndex, true);
+ }
+ else
+ {
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ return NS_OK;
+ }
+ }
+ case nsMsgNavigationType::toggleSubthreadKilled:
+ {
+ bool resultKilled;
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ ToggleMessageKilled(selection.Elements(), selection.Length(),
+ &threadIndex, &resultKilled);
+ if (resultKilled)
+ {
+ return NavigateFromPos(nsMsgNavigationType::nextUnreadMessage, threadIndex, pResultKey, pResultIndex, pThreadIndex, true);
+ }
+ else
+ {
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ return NS_OK;
+ }
+ }
+ // check where navigate says this will take us. If we have the message in the view,
+ // return it. Otherwise, return an error.
+ case nsMsgNavigationType::back:
+ case nsMsgNavigationType::forward:
+ {
+ nsCString folderUri, msgUri;
+ nsCString viewFolderUri;
+ nsCOMPtr<nsIMsgFolder> curFolder = m_viewFolder ? m_viewFolder : m_folder;
+ if (curFolder)
+ curFolder->GetURI(viewFolderUri);
+ int32_t relPos = (motion == nsMsgNavigationType::forward)
+ ? 1 : (m_currentlyDisplayedMsgKey != nsMsgKey_None) ? -1 : 0;
+ int32_t curPos;
+ nsresult rv;
+ nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = messenger->GetFolderUriAtNavigatePos(relPos, folderUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Empty viewFolderUri means we're in a search results view.
+ if (viewFolderUri.IsEmpty() || folderUri.Equals(viewFolderUri))
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = messenger->GetMsgUriAtNavigatePos(relPos, msgUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ messenger->MsgHdrFromURI(msgUri, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ messenger->GetNavigatePos(&curPos);
+ curPos += relPos;
+ *pResultIndex = FindHdr(msgHdr);
+ messenger->SetNavigatePos(curPos);
+ msgHdr->GetMessageKey(pResultKey);
+ return NS_OK;
+ }
+ }
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ break;
+
+ }
+ default:
+ NS_ERROR("unsupported motion");
+ break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::NavigateStatus(nsMsgNavigationTypeValue motion, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ bool enable = false;
+ nsresult rv = NS_ERROR_FAILURE;
+ nsMsgKey resultKey = nsMsgKey_None;
+ int32_t index = nsMsgKey_None;
+ nsMsgViewIndex resultIndex = nsMsgViewIndex_None;
+ if (mTreeSelection)
+ (void) mTreeSelection->GetCurrentIndex(&index);
+ else
+ index = FindViewIndex(m_currentlyDisplayedMsgKey);
+ nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak));
+ // warning - we no longer validate index up front because fe passes in -1 for no
+ // selection, so if you use index, be sure to validate it before using it
+ // as an array index.
+ switch (motion)
+ {
+ case nsMsgNavigationType::firstMessage:
+ case nsMsgNavigationType::lastMessage:
+ if (GetSize() > 0)
+ enable = true;
+ break;
+ case nsMsgNavigationType::nextMessage:
+ if (IsValidIndex(index) && uint32_t(index) < GetSize() - 1)
+ enable = true;
+ break;
+ case nsMsgNavigationType::previousMessage:
+ if (IsValidIndex(index) && index != 0 && GetSize() > 1)
+ enable = true;
+ break;
+ case nsMsgNavigationType::firstFlagged:
+ rv = FindFirstFlagged(&resultIndex);
+ enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None);
+ break;
+ case nsMsgNavigationType::nextFlagged:
+ rv = FindNextFlagged(index + 1, &resultIndex);
+ enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None);
+ break;
+ case nsMsgNavigationType::previousFlagged:
+ if (IsValidIndex(index) && index != 0)
+ rv = FindPrevFlagged(index, &resultIndex);
+ enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None);
+ break;
+ case nsMsgNavigationType::firstNew:
+ rv = FindFirstNew(&resultIndex);
+ enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None);
+ break;
+ case nsMsgNavigationType::readMore:
+ enable = true; // for now, always true.
+ break;
+ case nsMsgNavigationType::nextFolder:
+ case nsMsgNavigationType::nextUnreadThread:
+ case nsMsgNavigationType::nextUnreadMessage:
+ case nsMsgNavigationType::toggleThreadKilled:
+ enable = true; // always enabled
+ break;
+ case nsMsgNavigationType::previousUnreadMessage:
+ if (IsValidIndex(index))
+ {
+ nsMsgKey threadId;
+ rv = FindPrevUnread(m_keys[index], &resultKey, &threadId);
+ enable = (resultKey != nsMsgKey_None);
+ }
+ break;
+ case nsMsgNavigationType::forward:
+ case nsMsgNavigationType::back:
+ {
+ uint32_t curPos;
+ uint32_t historyCount;
+
+ if (messenger)
+ {
+ messenger->GetNavigateHistory(&curPos, &historyCount, nullptr);
+ int32_t desiredPos = (int32_t) curPos;
+ if (motion == nsMsgNavigationType::forward)
+ desiredPos++;
+ else
+ desiredPos--; //? operator code didn't work for me
+ enable = (desiredPos >= 0 && desiredPos < (int32_t) historyCount / 2);
+ }
+ }
+ break;
+
+ default:
+ NS_ERROR("unexpected");
+ break;
+ }
+
+ *_retval = enable;
+ return NS_OK;
+}
+
+// Note that these routines do NOT expand collapsed threads! This mimics the old behaviour,
+// but it's also because we don't remember whether a thread contains a flagged message the
+// same way we remember if a thread contains new messages. It would be painful to dive down
+// into each collapsed thread to update navigate status.
+// We could cache this info, but it would still be expensive the first time this status needs
+// to get updated.
+nsresult nsMsgDBView::FindNextFlagged(nsMsgViewIndex startIndex, nsMsgViewIndex *pResultIndex)
+{
+ nsMsgViewIndex lastIndex = (nsMsgViewIndex) GetSize() - 1;
+ nsMsgViewIndex curIndex;
+
+ *pResultIndex = nsMsgViewIndex_None;
+
+ if (GetSize() > 0)
+ {
+ for (curIndex = startIndex; curIndex <= lastIndex; curIndex++)
+ {
+ uint32_t flags = m_flags[curIndex];
+ if (flags & nsMsgMessageFlags::Marked)
+ {
+ *pResultIndex = curIndex;
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FindFirstNew(nsMsgViewIndex *pResultIndex)
+{
+ if (m_db)
+ {
+ nsMsgKey firstNewKey = nsMsgKey_None;
+ m_db->GetFirstNew(&firstNewKey);
+ *pResultIndex = (firstNewKey != nsMsgKey_None)
+ ? FindKey(firstNewKey, true) : nsMsgViewIndex_None;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FindPrevUnread(nsMsgKey startKey, nsMsgKey *pResultKey,
+ nsMsgKey *resultThreadId)
+{
+ nsMsgViewIndex startIndex = FindViewIndex(startKey);
+ nsMsgViewIndex curIndex = startIndex;
+ nsresult rv = NS_MSG_MESSAGE_NOT_FOUND;
+
+ if (startIndex == nsMsgViewIndex_None)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ *pResultKey = nsMsgKey_None;
+ if (resultThreadId)
+ *resultThreadId = nsMsgKey_None;
+
+ for (; (int) curIndex >= 0 && (*pResultKey == nsMsgKey_None); curIndex--)
+ {
+ uint32_t flags = m_flags[curIndex];
+
+ if (curIndex != startIndex && flags & MSG_VIEW_FLAG_ISTHREAD && flags & nsMsgMessageFlags::Elided)
+ {
+ NS_ERROR("fix this");
+ //nsMsgKey threadId = m_keys[curIndex];
+ //rv = m_db->GetUnreadKeyInThread(threadId, pResultKey, resultThreadId);
+ if (NS_SUCCEEDED(rv) && (*pResultKey != nsMsgKey_None))
+ break;
+ }
+ if (!(flags & (nsMsgMessageFlags::Read | MSG_VIEW_FLAG_DUMMY)) && (curIndex != startIndex))
+ {
+ *pResultKey = m_keys[curIndex];
+ rv = NS_OK;
+ break;
+ }
+ }
+ // found unread message but we don't know the thread
+ NS_ASSERTION(!(*pResultKey != nsMsgKey_None && resultThreadId && *resultThreadId == nsMsgKey_None),
+ "fix this");
+ return rv;
+}
+
+nsresult nsMsgDBView::FindFirstFlagged(nsMsgViewIndex *pResultIndex)
+{
+ return FindNextFlagged(0, pResultIndex);
+}
+
+nsresult nsMsgDBView::FindPrevFlagged(nsMsgViewIndex startIndex, nsMsgViewIndex *pResultIndex)
+{
+ nsMsgViewIndex curIndex;
+
+ *pResultIndex = nsMsgViewIndex_None;
+
+ if (GetSize() > 0 && IsValidIndex(startIndex))
+ {
+ curIndex = startIndex;
+ do
+ {
+ if (curIndex != 0)
+ curIndex--;
+
+ uint32_t flags = m_flags[curIndex];
+ if (flags & nsMsgMessageFlags::Marked)
+ {
+ *pResultIndex = curIndex;
+ break;
+ }
+ }
+ while (curIndex != 0);
+ }
+ return NS_OK;
+}
+
+bool nsMsgDBView::IsValidIndex(nsMsgViewIndex index)
+{
+ return index != nsMsgViewIndex_None &&
+ (index < (nsMsgViewIndex) m_keys.Length());
+}
+
+nsresult nsMsgDBView::OrExtraFlag(nsMsgViewIndex index, uint32_t orflag)
+{
+ uint32_t flag;
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ flag = m_flags[index];
+ flag |= orflag;
+ m_flags[index] = flag;
+ OnExtraFlagChanged(index, flag);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::AndExtraFlag(nsMsgViewIndex index, uint32_t andflag)
+{
+ uint32_t flag;
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ flag = m_flags[index];
+ flag &= andflag;
+ m_flags[index] = flag;
+ OnExtraFlagChanged(index, flag);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::SetExtraFlag(nsMsgViewIndex index, uint32_t extraflag)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ m_flags[index] = extraflag;
+ OnExtraFlagChanged(index, extraflag);
+ return NS_OK;
+}
+
+
+nsresult nsMsgDBView::ToggleIgnored(nsMsgViewIndex * indices, int32_t numIndices, nsMsgViewIndex *resultIndex, bool *resultToggleState)
+{
+ nsCOMPtr <nsIMsgThread> thread;
+
+ // Ignored state is toggled based on the first selected thread
+ nsMsgViewIndex threadIndex = GetThreadFromMsgIndex(indices[0], getter_AddRefs(thread));
+ uint32_t threadFlags;
+ thread->GetFlags(&threadFlags);
+ uint32_t ignored = threadFlags & nsMsgMessageFlags::Ignored;
+
+ // Process threads in reverse order
+ // Otherwise collapsing the threads will invalidate the indices
+ threadIndex = nsMsgViewIndex_None;
+ while (numIndices)
+ {
+ numIndices--;
+ if (indices[numIndices] < threadIndex)
+ {
+ threadIndex = GetThreadFromMsgIndex(indices[numIndices], getter_AddRefs(thread));
+ thread->GetFlags(&threadFlags);
+ if ((threadFlags & nsMsgMessageFlags::Ignored) == ignored)
+ SetThreadIgnored(thread, threadIndex, !ignored);
+ }
+ }
+
+ if (resultIndex)
+ *resultIndex = threadIndex;
+ if (resultToggleState)
+ *resultToggleState = !ignored;
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ToggleMessageKilled(nsMsgViewIndex * indices, int32_t numIndices, nsMsgViewIndex *resultIndex, bool *resultToggleState)
+{
+ NS_ENSURE_ARG_POINTER(resultToggleState);
+
+ nsCOMPtr <nsIMsgDBHdr> header;
+ // Ignored state is toggled based on the first selected message
+ nsresult rv = GetMsgHdrForViewIndex(indices[0], getter_AddRefs(header));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t msgFlags;
+ header->GetFlags(&msgFlags);
+ uint32_t ignored = msgFlags & nsMsgMessageFlags::Ignored;
+
+ // Process messages in reverse order
+ // Otherwise the indices may be invalidated...
+ nsMsgViewIndex msgIndex = nsMsgViewIndex_None;
+ while (numIndices)
+ {
+ numIndices--;
+ if (indices[numIndices] < msgIndex)
+ {
+ msgIndex = indices[numIndices];
+ rv = GetMsgHdrForViewIndex(msgIndex, getter_AddRefs(header));
+ NS_ENSURE_SUCCESS(rv, rv);
+ header->GetFlags(&msgFlags);
+ if ((msgFlags & nsMsgMessageFlags::Ignored) == ignored)
+ SetSubthreadKilled(header, msgIndex, !ignored);
+ }
+ }
+
+ if (resultIndex)
+ *resultIndex = msgIndex;
+ if (resultToggleState)
+ *resultToggleState = !ignored;
+
+ return NS_OK;
+}
+
+nsMsgViewIndex nsMsgDBView::GetThreadFromMsgIndex(nsMsgViewIndex index,
+ nsIMsgThread **threadHdr)
+{
+ nsMsgKey msgKey = GetAt(index);
+ nsMsgViewIndex threadIndex;
+
+ if (threadHdr == nullptr)
+ return nsMsgViewIndex_None;
+
+ nsresult rv = GetThreadContainingIndex(index, threadHdr);
+ NS_ENSURE_SUCCESS(rv,nsMsgViewIndex_None);
+
+ if (*threadHdr == nullptr)
+ return nsMsgViewIndex_None;
+
+ nsMsgKey threadKey;
+ (*threadHdr)->GetThreadKey(&threadKey);
+ if (msgKey !=threadKey)
+ threadIndex = GetIndexOfFirstDisplayedKeyInThread(*threadHdr);
+ else
+ threadIndex = index;
+ return threadIndex;
+}
+
+nsresult nsMsgDBView::ToggleWatched( nsMsgViewIndex* indices, int32_t numIndices)
+{
+ nsCOMPtr <nsIMsgThread> thread;
+
+ // Watched state is toggled based on the first selected thread
+ nsMsgViewIndex threadIndex = GetThreadFromMsgIndex(indices[0], getter_AddRefs(thread));
+ uint32_t threadFlags;
+ thread->GetFlags(&threadFlags);
+ uint32_t watched = threadFlags & nsMsgMessageFlags::Watched;
+
+ // Process threads in reverse order
+ // for consistency with ToggleIgnored
+ threadIndex = nsMsgViewIndex_None;
+ while (numIndices)
+ {
+ numIndices--;
+ if (indices[numIndices] < threadIndex)
+ {
+ threadIndex = GetThreadFromMsgIndex(indices[numIndices], getter_AddRefs(thread));
+ thread->GetFlags(&threadFlags);
+ if ((threadFlags & nsMsgMessageFlags::Watched) == watched)
+ SetThreadWatched(thread, threadIndex, !watched);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::SetThreadIgnored(nsIMsgThread *thread, nsMsgViewIndex threadIndex, bool ignored)
+{
+ if (!IsValidIndex(threadIndex))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ if (ignored)
+ {
+ nsTArray<nsMsgKey> idsMarkedRead;
+
+ MarkThreadRead(thread, threadIndex, idsMarkedRead, true);
+ CollapseByIndex(threadIndex, nullptr);
+ }
+
+ if (!m_db)
+ return NS_ERROR_FAILURE;
+ return m_db->MarkThreadIgnored(thread, m_keys[threadIndex], ignored, this);
+}
+
+nsresult nsMsgDBView::SetSubthreadKilled(nsIMsgDBHdr *header, nsMsgViewIndex msgIndex, bool ignored)
+{
+ if (!IsValidIndex(msgIndex))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ NoteChange(msgIndex, 1, nsMsgViewNotificationCode::changed);
+
+ if (!m_db)
+ return NS_ERROR_FAILURE;
+ nsresult rv = m_db->MarkHeaderKilled(header, ignored, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (ignored)
+ {
+ nsCOMPtr <nsIMsgThread> thread;
+ nsresult rv;
+ rv = GetThreadContainingMsgHdr(header, getter_AddRefs(thread));
+ if (NS_FAILED(rv))
+ return NS_OK; // So we didn't mark threads read
+
+ uint32_t children, current;
+ thread->GetNumChildren(&children);
+
+ nsMsgKey headKey;
+ header->GetMessageKey(&headKey);
+
+ for (current = 0; current < children; current++)
+ {
+ nsMsgKey newKey;
+ thread->GetChildKeyAt(current, &newKey);
+ if (newKey == headKey)
+ break;
+ }
+
+ // Process all messages, starting with this message.
+ for (; current < children; current++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> nextHdr;
+ bool isKilled;
+
+ thread->GetChildHdrAt(current, getter_AddRefs(nextHdr));
+ nextHdr->GetIsKilled(&isKilled);
+
+ // Ideally, the messages should stop processing here.
+ // However, the children are ordered not by thread...
+ if (isKilled)
+ nextHdr->MarkRead(true);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::SetThreadWatched(nsIMsgThread *thread, nsMsgViewIndex index, bool watched)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ return m_db->MarkThreadWatched(thread, m_keys[index], watched, this);
+}
+
+NS_IMETHODIMP nsMsgDBView::GetMsgFolder(nsIMsgFolder **aMsgFolder)
+{
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_IF_ADDREF(*aMsgFolder = m_folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SetViewFolder(nsIMsgFolder *aMsgFolder)
+{
+ m_viewFolder = aMsgFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetViewFolder(nsIMsgFolder **aMsgFolder)
+{
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_IF_ADDREF(*aMsgFolder = m_viewFolder);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMsgDBView::GetNumSelected(uint32_t *aNumSelected)
+{
+ NS_ENSURE_ARG_POINTER(aNumSelected);
+
+ if (!mTreeSelection)
+ {
+ // No tree selection can mean we're in the stand alone mode.
+ *aNumSelected = (m_currentlyDisplayedMsgKey != nsMsgKey_None) ? 1 : 0;
+ return NS_OK;
+ }
+
+ bool includeCollapsedMsgs = OperateOnMsgsInCollapsedThreads();
+
+ // We call this a lot from the front end JS, so make it fast.
+ nsresult rv = mTreeSelection->GetCount((int32_t*)aNumSelected);
+ if (!*aNumSelected || !includeCollapsedMsgs ||
+ !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return rv;
+
+ int32_t numSelectedIncludingCollapsed = *aNumSelected;
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ int32_t numIndices = selection.Length();
+ // iterate over the selection, counting up the messages in collapsed
+ // threads.
+ for (int32_t i = 0; i < numIndices; i++)
+ {
+ if (m_flags[selection[i]] & nsMsgMessageFlags::Elided)
+ {
+ int32_t collapsedCount;
+ ExpansionDelta(selection[i], &collapsedCount);
+ numSelectedIncludingCollapsed += collapsedCount;
+ }
+ }
+ *aNumSelected = numSelectedIncludingCollapsed;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetNumMsgsInView(int32_t *aNumMsgs)
+{
+ NS_ENSURE_ARG_POINTER(aNumMsgs);
+ return (m_folder) ? m_folder->GetTotalMessages(false, aNumMsgs) :
+ NS_ERROR_FAILURE;
+}
+/**
+ * @note For the IMAP delete model, this applies to both deleting and
+ * undeleting a message.
+ */
+NS_IMETHODIMP
+nsMsgDBView::GetMsgToSelectAfterDelete(nsMsgViewIndex *msgToSelectAfterDelete)
+{
+ NS_ENSURE_ARG_POINTER(msgToSelectAfterDelete);
+ *msgToSelectAfterDelete = nsMsgViewIndex_None;
+
+ bool isMultiSelect = false;
+ int32_t startFirstRange = nsMsgViewIndex_None;
+ int32_t endFirstRange = nsMsgViewIndex_None;
+ if (!mTreeSelection)
+ {
+ // If we don't have a tree selection then we must be in stand alone mode.
+ // return the index of the current message key as the first selected index.
+ *msgToSelectAfterDelete = FindViewIndex(m_currentlyDisplayedMsgKey);
+ }
+ else
+ {
+ int32_t selectionCount;
+ int32_t startRange;
+ int32_t endRange;
+ nsresult rv = mTreeSelection->GetRangeCount(&selectionCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (int32_t i = 0; i < selectionCount; i++)
+ {
+ rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // save off the first range in case we need it later
+ if (i == 0) {
+ startFirstRange = startRange;
+ endFirstRange = endRange;
+ } else {
+ // If the tree selection is goofy (eg adjacent or overlapping ranges),
+ // complain about it, but don't try and cope. Just live with the fact
+ // that one of the deleted messages is going to end up selected.
+ NS_WARNING_ASSERTION(endFirstRange != startRange,
+ "goofy tree selection state: two ranges are adjacent!");
+ }
+ *msgToSelectAfterDelete = std::min(*msgToSelectAfterDelete,
+ (nsMsgViewIndex)startRange);
+ }
+
+ // Multiple selection either using Ctrl, Shift, or one of the affordances
+ // to select an entire thread.
+ isMultiSelect = (selectionCount > 1 || (endRange-startRange) > 0);
+ }
+
+ if (*msgToSelectAfterDelete == nsMsgViewIndex_None)
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ GetMsgFolder(getter_AddRefs(folder));
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
+ bool thisIsImapFolder = (imapFolder != nullptr);
+ // Need to update the imap-delete model, can change more than once in a session.
+ if (thisIsImapFolder)
+ GetImapDeleteModel(nullptr);
+
+ // If mail.delete_matches_sort_order is true,
+ // for views sorted in descending order (newest at the top), make msgToSelectAfterDelete
+ // advance in the same direction as the sort order.
+ bool deleteMatchesSort = false;
+ if (m_sortOrder == nsMsgViewSortOrder::descending && *msgToSelectAfterDelete)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetBoolPref("mail.delete_matches_sort_order", &deleteMatchesSort);
+ }
+
+ if (mDeleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ {
+ if (isMultiSelect)
+ {
+ if (deleteMatchesSort)
+ *msgToSelectAfterDelete = startFirstRange - 1;
+ else
+ *msgToSelectAfterDelete = endFirstRange + 1;
+ }
+ else
+ {
+ if (deleteMatchesSort)
+ *msgToSelectAfterDelete -= 1;
+ else
+ *msgToSelectAfterDelete += 1;
+ }
+ }
+ else if (deleteMatchesSort)
+ {
+ *msgToSelectAfterDelete -= 1;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetRemoveRowOnMoveOrDelete(bool *aRemoveRowOnMoveOrDelete)
+{
+ NS_ENSURE_ARG_POINTER(aRemoveRowOnMoveOrDelete);
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder);
+ if (!imapFolder)
+ {
+ *aRemoveRowOnMoveOrDelete = true;
+ return NS_OK;
+ }
+
+ // need to update the imap-delete model, can change more than once in a session.
+ GetImapDeleteModel(nullptr);
+
+ // unlike the other imap delete models, "mark as deleted" does not remove rows on delete (or move)
+ *aRemoveRowOnMoveOrDelete = (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMsgDBView::GetCurrentlyDisplayedMessage(nsMsgViewIndex *currentlyDisplayedMessage)
+{
+ NS_ENSURE_ARG_POINTER(currentlyDisplayedMessage);
+ *currentlyDisplayedMessage = FindViewIndex(m_currentlyDisplayedMsgKey);
+ return NS_OK;
+}
+
+// if nothing selected, return an NS_ERROR
+NS_IMETHODIMP
+nsMsgDBView::GetHdrForFirstSelectedMessage(nsIMsgDBHdr **hdr)
+{
+ NS_ENSURE_ARG_POINTER(hdr);
+
+ nsresult rv;
+ nsMsgKey key;
+ rv = GetKeyForFirstSelectedMessage(&key);
+ // don't assert, it is legal for nothing to be selected
+ if (NS_FAILED(rv)) return rv;
+
+ if (!m_db)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ rv = m_db->GetMsgHdrForKey(key, hdr);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+// if nothing selected, return an NS_ERROR
+NS_IMETHODIMP
+nsMsgDBView::GetURIForFirstSelectedMessage(nsACString &uri)
+{
+ nsresult rv;
+ nsMsgViewIndex viewIndex;
+ rv = GetViewIndexForFirstSelectedMsg(&viewIndex);
+ // don't assert, it is legal for nothing to be selected
+ if (NS_FAILED(rv)) return rv;
+
+ return GetURIForViewIndex(viewIndex, uri);
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnDeleteCompleted(bool aSucceeded)
+{
+ if (m_deletingRows && aSucceeded)
+ {
+ uint32_t numIndices = mIndicesToNoteChange.Length();
+ if (numIndices && mTree)
+ {
+ if (numIndices > 1)
+ mIndicesToNoteChange.Sort();
+
+ // the call to NoteChange() has to happen after we are done removing the keys
+ // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
+ if (numIndices > 1)
+ mTree->BeginUpdateBatch();
+ for (uint32_t i = 0; i < numIndices; i++)
+ NoteChange(mIndicesToNoteChange[i], -1, nsMsgViewNotificationCode::insertOrDelete);
+ if (numIndices > 1)
+ mTree->EndUpdateBatch();
+ }
+ mIndicesToNoteChange.Clear();
+ }
+
+ m_deletingRows = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetDb(nsIMsgDatabase **aDB)
+{
+ NS_ENSURE_ARG_POINTER(aDB);
+ NS_IF_ADDREF(*aDB = m_db);
+ return NS_OK;
+}
+
+bool nsMsgDBView::OfflineMsgSelected(nsMsgViewIndex * indices, int32_t numIndices)
+{
+ nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+ if (localFolder)
+ return true;
+
+ for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++)
+ {
+ // For cross-folder saved searches, we need to check if any message
+ // is in a local folder.
+ if (!m_folder)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ GetFolderForViewIndex(indices[index], getter_AddRefs(folder));
+ nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder);
+ if (localFolder)
+ return true;
+ }
+
+ uint32_t flags = m_flags[indices[index]];
+ if ((flags & nsMsgMessageFlags::Offline))
+ return true;
+ }
+ return false;
+}
+
+bool nsMsgDBView::NonDummyMsgSelected(nsMsgViewIndex * indices, int32_t numIndices)
+{
+ bool includeCollapsedMsgs = OperateOnMsgsInCollapsedThreads();
+
+ for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++)
+ {
+ uint32_t flags = m_flags[indices[index]];
+ // We now treat having a collapsed dummy message selected as if
+ // the whole group was selected so we can apply commands to the group.
+ if (!(flags & MSG_VIEW_FLAG_DUMMY) ||
+ (flags & nsMsgMessageFlags::Elided && includeCollapsedMsgs))
+ return true;
+ }
+ return false;
+}
+
+NS_IMETHODIMP nsMsgDBView::GetViewIndexForFirstSelectedMsg(nsMsgViewIndex *aViewIndex)
+{
+ NS_ENSURE_ARG_POINTER(aViewIndex);
+ // If we don't have a tree selection we must be in stand alone mode...
+ if (!mTreeSelection)
+ {
+ *aViewIndex = m_currentlyDisplayedViewIndex;
+ return NS_OK;
+ }
+
+ int32_t startRange;
+ int32_t endRange;
+ nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange);
+ // don't assert, it is legal for nothing to be selected
+ if (NS_FAILED(rv))
+ return rv;
+
+ // check that the first index is valid, it may not be if nothing is selected
+ if (startRange < 0 || uint32_t(startRange) >= GetSize())
+ return NS_ERROR_UNEXPECTED;
+
+ *aViewIndex = startRange;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetKeyForFirstSelectedMessage(nsMsgKey *key)
+{
+ NS_ENSURE_ARG_POINTER(key);
+ // If we don't have a tree selection we must be in stand alone mode...
+ if (!mTreeSelection)
+ {
+ *key = m_currentlyDisplayedMsgKey;
+ return NS_OK;
+ }
+
+ int32_t startRange;
+ int32_t endRange;
+ nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange);
+ // don't assert, it is legal for nothing to be selected
+ if (NS_FAILED(rv))
+ return rv;
+
+ // check that the first index is valid, it may not be if nothing is selected
+ if (startRange < 0 || uint32_t(startRange) >= GetSize())
+ return NS_ERROR_UNEXPECTED;
+
+ if (m_flags[startRange] & MSG_VIEW_FLAG_DUMMY)
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ *key = m_keys[startRange];
+ return NS_OK;
+}
+
+nsCOMArray<nsIMsgFolder>* nsMsgDBView::GetFolders()
+{
+ return nullptr;
+}
+
+nsresult nsMsgDBView::AdjustRowCount(int32_t rowCountBeforeSort, int32_t rowCountAfterSort)
+{
+ int32_t rowChange = rowCountAfterSort - rowCountBeforeSort;
+
+ if (rowChange)
+ {
+ // this is not safe to use when you have a selection
+ // RowCountChanged() will call AdjustSelection()
+ uint32_t numSelected = 0;
+ GetNumSelected(&numSelected);
+ NS_ASSERTION(numSelected == 0, "it is not save to call AdjustRowCount() when you have a selection");
+
+ if (mTree)
+ mTree->RowCountChanged(0, rowChange);
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GetImapDeleteModel(nsIMsgFolder *folder)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ if (folder) //for the search view
+ folder->GetServer(getter_AddRefs(server));
+ else if (m_folder)
+ m_folder->GetServer(getter_AddRefs(server));
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server, &rv);
+ if (NS_SUCCEEDED(rv) && imapServer )
+ imapServer->GetDeleteModel(&mDeleteModel);
+ return rv;
+}
+
+
+//
+// CanDrop
+//
+// Can't drop on the thread pane.
+//
+NS_IMETHODIMP nsMsgDBView::CanDrop(int32_t index,
+ int32_t orient,
+ nsIDOMDataTransfer *dataTransfer,
+ bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = false;
+
+ return NS_OK;
+}
+
+
+//
+// Drop
+//
+// Can't drop on the thread pane.
+//
+NS_IMETHODIMP nsMsgDBView::Drop(int32_t row,
+ int32_t orient,
+ nsIDOMDataTransfer *dataTransfer)
+{
+ return NS_OK;
+}
+
+
+//
+// IsSorted
+//
+// ...
+//
+NS_IMETHODIMP nsMsgDBView::IsSorted(bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SelectFolderMsgByKey(nsIMsgFolder *aFolder, nsMsgKey aKey)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ if (aKey == nsMsgKey_None)
+ return NS_ERROR_FAILURE;
+
+ // this is OK for non search views.
+
+ nsMsgViewIndex viewIndex = FindKey(aKey, true /* expand */);
+
+ if (mTree)
+ mTreeSelection->SetCurrentIndex(viewIndex);
+
+ // make sure the current message is once again visible in the thread pane
+ // so we don't have to go search for it in the thread pane
+ if (mTree && viewIndex != nsMsgViewIndex_None)
+ {
+ mTreeSelection->Select(viewIndex);
+ mTree->EnsureRowIsVisible(viewIndex);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBView::SelectMsgByKey(nsMsgKey aKey)
+{
+ NS_ASSERTION(aKey != nsMsgKey_None, "bad key");
+ if (aKey == nsMsgKey_None)
+ return NS_OK;
+
+ // use SaveAndClearSelection()
+ // and RestoreSelection() so that we'll clear the current selection
+ // but pass in a different key array so that we'll
+ // select (and load) the desired message
+
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ nsresult rv = SaveAndClearSelection(nullptr, preservedSelection);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // now, restore our desired selection
+ AutoTArray<nsMsgKey, 1> keyArray;
+ keyArray.AppendElement(aKey);
+
+ // if the key was not found
+ // (this can happen with "remember last selected message")
+ // nothing will be selected
+ rv = RestoreSelection(aKey, keyArray);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval)
+{
+ nsMsgDBView* newMsgDBView = new nsMsgDBView();
+
+ if (!newMsgDBView)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater)
+{
+ NS_ENSURE_ARG_POINTER(aNewMsgDBView);
+ if (aMsgWindow)
+ {
+ aNewMsgDBView->mMsgWindowWeak = do_GetWeakReference(aMsgWindow);
+ aMsgWindow->SetOpenFolder(m_viewFolder? m_viewFolder : m_folder);
+ }
+ aNewMsgDBView->mMessengerWeak = do_GetWeakReference(aMessengerInstance);
+ aNewMsgDBView->mCommandUpdater = aCmdUpdater;
+ aNewMsgDBView->m_folder = m_folder;
+ aNewMsgDBView->m_viewFlags = m_viewFlags;
+ aNewMsgDBView->m_sortOrder = m_sortOrder;
+ aNewMsgDBView->m_sortType = m_sortType;
+ aNewMsgDBView->m_curCustomColumn = m_curCustomColumn;
+ aNewMsgDBView->m_secondarySort = m_secondarySort;
+ aNewMsgDBView->m_secondarySortOrder = m_secondarySortOrder;
+ aNewMsgDBView->m_secondaryCustomColumn = m_secondaryCustomColumn;
+ aNewMsgDBView->m_db = m_db;
+ aNewMsgDBView->mDateFormatter = mDateFormatter;
+ if (m_db)
+ aNewMsgDBView->m_db->AddListener(aNewMsgDBView);
+ aNewMsgDBView->mIsNews = mIsNews;
+ aNewMsgDBView->mIsRss = mIsRss;
+ aNewMsgDBView->mIsXFVirtual = mIsXFVirtual;
+ aNewMsgDBView->mShowSizeInLines = mShowSizeInLines;
+ aNewMsgDBView->mDeleteModel = mDeleteModel;
+ aNewMsgDBView->m_flags = m_flags;
+ aNewMsgDBView->m_levels = m_levels;
+ aNewMsgDBView->m_keys = m_keys;
+
+ aNewMsgDBView->m_customColumnHandlerIDs = m_customColumnHandlerIDs;
+ aNewMsgDBView->m_customColumnHandlers.AppendObjects(m_customColumnHandlers);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSearchSession(nsIMsgSearchSession* *aSession)
+{
+ NS_ASSERTION(false, "should be overriden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetSearchSession(nsIMsgSearchSession *aSession)
+{
+ NS_ASSERTION(false, "should be overriden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSupportsThreading(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::FindIndexFromKey(nsMsgKey aMsgKey, bool aExpand, nsMsgViewIndex *aIndex)
+{
+ NS_ENSURE_ARG_POINTER(aIndex);
+
+ *aIndex = FindKey(aMsgKey, aExpand);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::FindIndexOfMsgHdr(nsIMsgDBHdr *aMsgHdr, bool aExpand, nsMsgViewIndex *aIndex)
+{
+ NS_ENSURE_ARG(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aIndex);
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ nsMsgViewIndex threadIndex = ThreadIndexOfMsgHdr(aMsgHdr);
+ if (threadIndex != nsMsgViewIndex_None)
+ {
+ if (aExpand && (m_flags[threadIndex] & nsMsgMessageFlags::Elided))
+ ExpandByIndex(threadIndex, nullptr);
+ *aIndex = FindHdr(aMsgHdr, threadIndex);
+ }
+ else
+ *aIndex = nsMsgViewIndex_None;
+ }
+ else
+ *aIndex = FindHdr(aMsgHdr);
+
+ return NS_OK;
+}
+
+static void getDateFormatPref( nsIPrefBranch* _prefBranch, const char* _prefLocalName, nsDateFormatSelector& _format )
+{
+ // read
+ int32_t nFormatSetting( 0 );
+ nsresult result = _prefBranch->GetIntPref( _prefLocalName, &nFormatSetting );
+ if ( NS_SUCCEEDED( result ) )
+ {
+ // translate
+ nsDateFormatSelector res( nFormatSetting );
+ // transfer if valid
+ if ( ( res >= kDateFormatNone ) && ( res <= kDateFormatWeekday ) )
+ _format = res;
+ }
+}
+
+nsresult nsMsgDBView::InitDisplayFormats()
+{
+ m_dateFormatDefault = kDateFormatShort;
+ m_dateFormatThisWeek = kDateFormatShort;
+ m_dateFormatToday = kDateFormatNone;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIPrefBranch> dateFormatPrefs;
+ rv = prefs->GetBranch("mail.ui.display.dateformat.", getter_AddRefs(dateFormatPrefs));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ getDateFormatPref( dateFormatPrefs, "default", m_dateFormatDefault );
+ getDateFormatPref( dateFormatPrefs, "thisweek", m_dateFormatThisWeek );
+ getDateFormatPref( dateFormatPrefs, "today", m_dateFormatToday );
+ return rv;
+}
+
+void nsMsgDBView::SetMRUTimeForFolder(nsIMsgFolder *folder)
+{
+ uint32_t seconds;
+ PRTime2Seconds(PR_Now(), &seconds);
+ nsAutoCString nowStr;
+ nowStr.AppendInt(seconds);
+ folder->SetStringProperty(MRU_TIME_PROPERTY, nowStr);
+}
+
+
+NS_IMPL_ISUPPORTS(nsMsgDBView::nsMsgViewHdrEnumerator, nsISimpleEnumerator)
+
+nsMsgDBView::nsMsgViewHdrEnumerator::nsMsgViewHdrEnumerator(nsMsgDBView *view)
+{
+ // we need to clone the view because the caller may clear the
+ // current view immediately. It also makes it easier to expand all
+ // if we're working on a copy.
+ nsCOMPtr<nsIMsgDBView> clonedView;
+ view->CloneDBView(nullptr, nullptr, nullptr, getter_AddRefs(clonedView));
+ m_view = static_cast<nsMsgDBView*>(clonedView.get());
+ // make sure we enumerate over collapsed threads by expanding all.
+ m_view->ExpandAll();
+ m_curHdrIndex = 0;
+}
+
+nsMsgDBView::nsMsgViewHdrEnumerator::~nsMsgViewHdrEnumerator()
+{
+ if (m_view)
+ m_view->Close();
+}
+
+NS_IMETHODIMP nsMsgDBView::nsMsgViewHdrEnumerator::GetNext(nsISupports **aItem)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ if (m_curHdrIndex >= m_view->GetSize())
+ return NS_ERROR_FAILURE;
+
+ // Ignore dummy header. We won't have empty groups, so
+ // we know the view index is good.
+ if (m_view->m_flags[m_curHdrIndex] & MSG_VIEW_FLAG_DUMMY)
+ ++m_curHdrIndex;
+
+ nsCOMPtr<nsIMsgDBHdr> nextHdr;
+
+ nsresult rv = m_view->GetMsgHdrForViewIndex(m_curHdrIndex++, getter_AddRefs(nextHdr));
+ NS_IF_ADDREF(*aItem = nextHdr);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBView::nsMsgViewHdrEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_curHdrIndex < m_view->GetSize();
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GetViewEnumerator(nsISimpleEnumerator **enumerator)
+{
+ NS_IF_ADDREF(*enumerator = new nsMsgViewHdrEnumerator(this));
+ return (*enumerator) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult nsMsgDBView::GetDBForHeader(nsIMsgDBHdr *msgHdr, nsIMsgDatabase **db)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folder->GetMsgDatabase(db);
+}
+
+/**
+ * Determine whether junk commands should be enabled on this view.
+ * Junk commands are always enabled for mail. For nntp and rss, they
+ * may be selectively enabled using an inherited folder property.
+ *
+ * @param aViewIndex view index of the message to check
+ * @return true if junk controls should be enabled
+ */
+bool nsMsgDBView::JunkControlsEnabled(nsMsgViewIndex aViewIndex)
+{
+ // For normal mail, junk commands are always enabled.
+ if (!(mIsNews || mIsRss || mIsXFVirtual))
+ return true;
+
+ // we need to check per message or folder
+ nsCOMPtr <nsIMsgFolder> folder = m_folder;
+ if (!folder && IsValidIndex(aViewIndex))
+ GetFolderForViewIndex(aViewIndex, getter_AddRefs(folder));
+ if (folder)
+ {
+ // Check if this is a mail message in search folders.
+ if (mIsXFVirtual)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ folder->GetServer(getter_AddRefs(server));
+ nsAutoCString type;
+ if (server)
+ server->GetType(type);
+ if (!(MsgLowerCaseEqualsLiteral(type, "nntp") || MsgLowerCaseEqualsLiteral(type, "rss")))
+ return true;
+ }
+
+ // For rss and news, check the inherited folder property.
+ nsAutoCString junkEnableOverride;
+ folder->GetInheritedStringProperty("dobayes.mailnews@mozilla.org#junk",
+ junkEnableOverride);
+ if (junkEnableOverride.EqualsLiteral("true"))
+ return true;
+ }
+
+ return false;
+}
diff --git a/mailnews/base/src/nsMsgDBView.h b/mailnews/base/src/nsMsgDBView.h
new file mode 100644
index 000000000..feba5f14f
--- /dev/null
+++ b/mailnews/base/src/nsMsgDBView.h
@@ -0,0 +1,513 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgDBView_H_
+#define _nsMsgDBView_H_
+
+#include "nsIMsgDBView.h"
+#include "nsIMsgWindow.h"
+#include "nsIMessenger.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "MailNewsTypes.h"
+#include "nsIDBChangeListener.h"
+#include "nsITreeView.h"
+#include "nsITreeBoxObject.h"
+#include "nsITreeSelection.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgThread.h"
+#include "nsIDateTimeFormat.h"
+#include "nsIDOMElement.h"
+#include "nsIAtom.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIWeakReference.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIStringBundle.h"
+#include "nsMsgTagService.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIMsgCustomColumnHandler.h"
+#include "nsAutoPtr.h"
+#include "nsIWeakReferenceUtils.h"
+#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
+
+typedef AutoTArray<nsMsgViewIndex, 1> nsMsgViewIndexArray;
+static_assert(nsMsgViewIndex(nsMsgViewIndexArray::NoIndex) ==
+ nsMsgViewIndex_None, "These need to be the same value.");
+
+enum eFieldType {
+ kCollationKey,
+ kU32
+};
+
+// this is used in an nsTArray<> to keep track of a multi-column sort
+class MsgViewSortColumnInfo
+{
+public:
+ MsgViewSortColumnInfo(const MsgViewSortColumnInfo &other);
+ MsgViewSortColumnInfo() {}
+ bool operator == (const MsgViewSortColumnInfo &other) const;
+ nsMsgViewSortTypeValue mSortType;
+ nsMsgViewSortOrderValue mSortOrder;
+ // if mSortType == byCustom, info about the custom column sort
+ nsString mCustomColumnName;
+ nsCOMPtr <nsIMsgCustomColumnHandler> mColHandler;
+} ;
+
+// reserve the top 8 bits in the msg flags for the view-only flags.
+#define MSG_VIEW_FLAGS 0xEE000000
+#define MSG_VIEW_FLAG_HASCHILDREN 0x40000000
+#define MSG_VIEW_FLAG_DUMMY 0x20000000
+#define MSG_VIEW_FLAG_ISTHREAD 0x8000000
+#define MSG_VIEW_FLAG_OUTGOING 0x2000000
+#define MSG_VIEW_FLAG_INCOMING 0x1000000
+
+/* There currently only 5 labels defined */
+#define PREF_LABELS_MAX 5
+#define PREF_LABELS_DESCRIPTION "mailnews.labels.description."
+#define PREF_LABELS_COLOR "mailnews.labels.color."
+
+#define LABEL_COLOR_STRING " lc-"
+#define LABEL_COLOR_WHITE_STRING "#FFFFFF"
+
+struct IdUint32
+{
+ nsMsgKey id;
+ uint32_t bits;
+ uint32_t dword;
+ nsIMsgFolder* folder;
+};
+
+struct IdKey : public IdUint32
+{
+ // actually a variable length array, whose actual size is determined
+ // when the struct is allocated.
+ uint8_t key[1];
+};
+
+struct IdKeyPtr : public IdUint32
+{
+ uint8_t *key;
+};
+
+// This is an abstract implementation class.
+// The actual view objects will be instances of sub-classes of this class
+class nsMsgDBView : public nsIMsgDBView, public nsIDBChangeListener,
+ public nsITreeView,
+ public nsIJunkMailClassificationListener
+{
+public:
+ nsMsgDBView();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGDBVIEW
+ NS_DECL_NSIDBCHANGELISTENER
+ NS_DECL_NSITREEVIEW
+ NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER
+
+ nsMsgViewIndex GetInsertIndexHelper(nsIMsgDBHdr *msgHdr, nsTArray<nsMsgKey> &keys,
+ nsCOMArray<nsIMsgFolder> *folders,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewSortTypeValue sortType);
+ int32_t SecondarySort(nsMsgKey key1, nsISupports *folder1, nsMsgKey key2, nsISupports *folder2,
+ class viewSortInfo *comparisonContext);
+
+protected:
+ virtual ~nsMsgDBView();
+
+ static nsrefcnt gInstanceCount;
+
+ static char16_t* kHighestPriorityString;
+ static char16_t* kHighPriorityString;
+ static char16_t* kLowestPriorityString;
+ static char16_t* kLowPriorityString;
+ static char16_t* kNormalPriorityString;
+
+ static nsIAtom* kJunkMsgAtom;
+ static nsIAtom* kNotJunkMsgAtom;
+
+ static char16_t* kReadString;
+ static char16_t* kRepliedString;
+ static char16_t* kForwardedString;
+ static char16_t* kNewString;
+
+ nsCOMPtr<nsITreeBoxObject> mTree;
+ nsCOMPtr<nsITreeSelection> mTreeSelection;
+ uint32_t mNumSelectedRows; // we cache this to determine when to push command status notifications.
+ bool mSuppressMsgDisplay; // set when the message pane is collapsed
+ bool mSuppressCommandUpdating;
+ bool mRemovingRow; // set when we're telling the outline a row is being removed. used to suppress msg loading.
+ // during delete/move operations.
+ bool mCommandsNeedDisablingBecauseOfSelection;
+ bool mSuppressChangeNotification;
+ bool mGoForwardEnabled;
+ bool mGoBackEnabled;
+
+ virtual const char * GetViewName(void) {return "MsgDBView"; }
+ nsresult FetchAuthor(nsIMsgDBHdr * aHdr, nsAString &aAuthorString);
+ nsresult FetchRecipients(nsIMsgDBHdr * aHdr, nsAString &aRecipientsString);
+ nsresult FetchSubject(nsIMsgDBHdr * aMsgHdr, uint32_t aFlags, nsAString &aValue);
+ nsresult FetchDate(nsIMsgDBHdr * aHdr, nsAString & aDateString, bool rcvDate = false);
+ nsresult FetchStatus(uint32_t aFlags, nsAString &aStatusString);
+ nsresult FetchSize(nsIMsgDBHdr * aHdr, nsAString & aSizeString);
+ nsresult FetchPriority(nsIMsgDBHdr *aHdr, nsAString & aPriorityString);
+ nsresult FetchLabel(nsIMsgDBHdr *aHdr, nsAString & aLabelString);
+ nsresult FetchTags(nsIMsgDBHdr *aHdr, nsAString & aTagString);
+ nsresult FetchKeywords(nsIMsgDBHdr *aHdr, nsACString & keywordString);
+ nsresult FetchRowKeywords(nsMsgViewIndex aRow, nsIMsgDBHdr *aHdr,
+ nsACString & keywordString);
+ nsresult FetchAccount(nsIMsgDBHdr * aHdr, nsAString& aAccount);
+ bool IsOutgoingMsg(nsIMsgDBHdr * aHdr);
+ nsresult CycleThreadedColumn(nsIDOMElement * aElement);
+
+ // The default enumerator is over the db, but things like
+ // quick search views will enumerate just the displayed messages.
+ virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator);
+ // this is a message enumerator that enumerates based on the view contents
+ virtual nsresult GetViewEnumerator(nsISimpleEnumerator **enumerator);
+
+ // Save and Restore Selection are a pair of routines you should
+ // use when performing an operation which is going to change the view
+ // and you want to remember the selection. (i.e. for sorting).
+ // Call SaveAndClearSelection and we'll give you an array of msg keys for
+ // the current selection. We also freeze and clear the selection.
+ // When you are done changing the view,
+ // call RestoreSelection passing in the same array
+ // and we'll restore the selection AND unfreeze selection in the UI.
+ nsresult SaveAndClearSelection(nsMsgKey *aCurrentMsgKey, nsTArray<nsMsgKey> &aMsgKeyArray);
+ nsresult RestoreSelection(nsMsgKey aCurrentmsgKey, nsTArray<nsMsgKey> &aMsgKeyArray);
+
+ // this is not safe to use when you have a selection
+ // RowCountChanged() will call AdjustSelection()
+ // it should be called after SaveAndClearSelection() and before
+ // RestoreSelection()
+ nsresult AdjustRowCount(int32_t rowCountBeforeSort, int32_t rowCountAfterSort);
+
+ nsresult GetSelectedIndices(nsMsgViewIndexArray& selection);
+ nsresult GenerateURIForMsgKey(nsMsgKey aMsgKey, nsIMsgFolder *folder, nsACString &aURI);
+// routines used in building up view
+ virtual bool WantsThisThread(nsIMsgThread * thread);
+ virtual nsresult AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex = nullptr);
+ bool GetShowingIgnored() {return (m_viewFlags & nsMsgViewFlagsType::kShowIgnored) != 0;}
+ bool OperateOnMsgsInCollapsedThreads();
+
+ virtual nsresult OnNewHeader(nsIMsgDBHdr *aNewHdr, nsMsgKey parentKey, bool ensureListed);
+ virtual nsMsgViewIndex GetInsertIndex(nsIMsgDBHdr *msgHdr);
+ nsMsgViewIndex GetIndexForThread(nsIMsgDBHdr *hdr);
+ nsMsgViewIndex GetThreadRootIndex(nsIMsgDBHdr *msgHdr);
+ virtual nsresult GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr);
+ // given a view index, return the index of the top-level msg in the thread.
+ nsMsgViewIndex GetThreadIndex(nsMsgViewIndex msgIndex);
+
+ virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level);
+ virtual void SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level);
+ virtual bool InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows);
+ virtual void RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows);
+ nsresult ToggleExpansion(nsMsgViewIndex index, uint32_t *numChanged);
+ nsresult ExpandByIndex(nsMsgViewIndex index, uint32_t *pNumExpanded);
+ nsresult CollapseByIndex(nsMsgViewIndex index, uint32_t *pNumCollapsed);
+ nsresult ExpandAll();
+ nsresult CollapseAll();
+ nsresult ExpandAndSelectThread();
+
+ // helper routines for thread expanding and collapsing.
+ nsresult GetThreadCount(nsMsgViewIndex viewIndex, uint32_t *pThreadCount);
+ /**
+ * Retrieve the view index of the first displayed message in the given thread.
+ * @param threadHdr The thread you care about.
+ * @param allowDummy Should dummy headers be returned when the non-dummy
+ * header is available? If the root node of the thread is a dummy header
+ * and you pass false, then we will return the first child of the thread
+ * unless the thread is elided, in which case we will return the root.
+ * If you pass true, we will always return the root.
+ * @return the view index of the first message in the thread, if any.
+ */
+ nsMsgViewIndex GetIndexOfFirstDisplayedKeyInThread(nsIMsgThread *threadHdr,
+ bool allowDummy=false);
+ virtual nsresult GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result);
+ virtual nsMsgViewIndex ThreadIndexOfMsg(nsMsgKey msgKey,
+ nsMsgViewIndex msgIndex = nsMsgViewIndex_None,
+ int32_t *pThreadCount = nullptr,
+ uint32_t *pFlags = nullptr);
+ nsMsgViewIndex ThreadIndexOfMsgHdr(nsIMsgDBHdr *msgHdr,
+ nsMsgViewIndex msgIndex = nsMsgViewIndex_None,
+ int32_t *pThreadCount = nullptr,
+ uint32_t *pFlags = nullptr);
+ nsMsgKey GetKeyOfFirstMsgInThread(nsMsgKey key);
+ int32_t CountExpandedThread(nsMsgViewIndex index);
+ virtual nsresult ExpansionDelta(nsMsgViewIndex index, int32_t *expansionDelta);
+ void ReverseSort();
+ void ReverseThreads();
+ nsresult SaveSortInfo(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder);
+ nsresult RestoreSortInfo();
+ nsresult PersistFolderInfo(nsIDBFolderInfo **dbFolderInfo);
+ void SetMRUTimeForFolder(nsIMsgFolder *folder);
+
+ nsMsgKey GetAt(nsMsgViewIndex index)
+ {return m_keys.SafeElementAt(index, nsMsgKey_None);}
+ nsMsgViewIndex FindViewIndex(nsMsgKey key)
+ {return FindKey(key, false);}
+ /**
+ * Find the message header if it is visible in this view. (Messages in
+ * threads/groups that are elided will not be
+ * @param msgHdr Message header to look for.
+ * @param startIndex The index to start looking from.
+ * @param allowDummy Are dummy headers acceptable? If yes, then for a group
+ * with a dummy header, we return the root of the thread (the dummy
+ * header), otherwise we return the actual "content" header for the
+ * message.
+ * @return The view index of the header found, if any.
+ */
+ virtual nsMsgViewIndex FindHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startIndex = 0,
+ bool allowDummy=false);
+ virtual nsMsgViewIndex FindKey(nsMsgKey key, bool expand);
+ virtual nsresult GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db);
+ virtual nsCOMArray<nsIMsgFolder>* GetFolders();
+ virtual nsresult GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder);
+
+ virtual nsresult ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex viewIndex, uint32_t *pNumListed);
+ nsresult ListUnreadIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed);
+ nsMsgViewIndex FindParentInThread(nsMsgKey parentKey, nsMsgViewIndex startOfThreadViewIndex);
+ virtual nsresult ListIdsInThreadOrder(nsIMsgThread *threadHdr,
+ nsMsgKey parentKey, uint32_t level,
+ nsMsgViewIndex *viewIndex,
+ uint32_t *pNumListed);
+ uint32_t GetSize(void) {return(m_keys.Length());}
+
+ // notification api's
+ void NoteStartChange(nsMsgViewIndex firstlineChanged, int32_t numChanged,
+ nsMsgViewNotificationCodeValue changeType);
+ void NoteEndChange(nsMsgViewIndex firstlineChanged, int32_t numChanged,
+ nsMsgViewNotificationCodeValue changeType);
+
+ // for commands
+ virtual nsresult ApplyCommandToIndices(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices,
+ int32_t numIndices);
+ virtual nsresult ApplyCommandToIndicesWithFolder(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices,
+ int32_t numIndices, nsIMsgFolder *destFolder);
+ virtual nsresult CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool isMove, nsIMsgFolder *destFolder);
+ virtual nsresult DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage);
+ nsresult GetHeadersFromSelection(uint32_t *indices, uint32_t numIndices, nsIMutableArray *messageArray);
+ virtual nsresult ListCollapsedChildren(nsMsgViewIndex viewIndex,
+ nsIMutableArray *messageArray);
+
+ nsresult SetMsgHdrJunkStatus(nsIJunkMailPlugin *aJunkPlugin,
+ nsIMsgDBHdr *aMsgHdr,
+ nsMsgJunkStatus aNewClassification);
+ nsresult ToggleReadByIndex(nsMsgViewIndex index);
+ nsresult SetReadByIndex(nsMsgViewIndex index, bool read);
+ nsresult SetThreadOfMsgReadByIndex(nsMsgViewIndex index, nsTArray<nsMsgKey> &keysMarkedRead, bool read);
+ nsresult SetFlaggedByIndex(nsMsgViewIndex index, bool mark);
+ nsresult SetLabelByIndex(nsMsgViewIndex index, nsMsgLabelValue label);
+ nsresult OrExtraFlag(nsMsgViewIndex index, uint32_t orflag);
+ nsresult AndExtraFlag(nsMsgViewIndex index, uint32_t andflag);
+ nsresult SetExtraFlag(nsMsgViewIndex index, uint32_t extraflag);
+ virtual nsresult RemoveByIndex(nsMsgViewIndex index);
+ virtual void OnExtraFlagChanged(nsMsgViewIndex /*index*/, uint32_t /*extraFlag*/) {}
+ virtual void OnHeaderAddedOrDeleted() {}
+ nsresult ToggleWatched( nsMsgViewIndex* indices, int32_t numIndices);
+ nsresult SetThreadWatched(nsIMsgThread *thread, nsMsgViewIndex index, bool watched);
+ nsresult SetThreadIgnored(nsIMsgThread *thread, nsMsgViewIndex threadIndex, bool ignored);
+ nsresult SetSubthreadKilled(nsIMsgDBHdr *header, nsMsgViewIndex msgIndex, bool ignored);
+ nsresult DownloadForOffline(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices);
+ nsresult DownloadFlaggedForOffline(nsIMsgWindow *window);
+ nsMsgViewIndex GetThreadFromMsgIndex(nsMsgViewIndex index, nsIMsgThread **threadHdr);
+ /// Should junk commands be enabled for the current message in the view?
+ bool JunkControlsEnabled(nsMsgViewIndex aViewIndex);
+
+ // for sorting
+ nsresult GetFieldTypeAndLenForSort(nsMsgViewSortTypeValue sortType,
+ uint16_t *pMaxLen,
+ eFieldType *pFieldType,
+ nsIMsgCustomColumnHandler* colHandler = nullptr);
+ nsresult GetCollationKey(nsIMsgDBHdr *msgHdr,
+ nsMsgViewSortTypeValue sortType,
+ uint8_t **result,
+ uint32_t *len,
+ nsIMsgCustomColumnHandler* colHandler = nullptr);
+ nsresult GetLongField(nsIMsgDBHdr *msgHdr,
+ nsMsgViewSortTypeValue sortType,
+ uint32_t *result,
+ nsIMsgCustomColumnHandler* colHandler = nullptr);
+
+ static int FnSortIdKey(const void *pItem1, const void *pItem2, void *privateData);
+ static int FnSortIdKeyPtr(const void *pItem1, const void *pItem2, void *privateData);
+ static int FnSortIdUint32(const void *pItem1, const void *pItem2, void *privateData);
+
+ nsresult GetStatusSortValue(nsIMsgDBHdr *msgHdr, uint32_t *result);
+ nsresult GetLocationCollationKey(nsIMsgDBHdr *msgHdr, uint8_t **result, uint32_t *len);
+ void PushSort(const MsgViewSortColumnInfo &newSort);
+ nsresult EncodeColumnSort(nsString &columnSortString);
+ nsresult DecodeColumnSort(nsString &columnSortString);
+ // for view navigation
+ nsresult NavigateFromPos(nsMsgNavigationTypeValue motion, nsMsgViewIndex startIndex, nsMsgKey *pResultKey,
+ nsMsgViewIndex *pResultIndex, nsMsgViewIndex *pThreadIndex, bool wrap);
+ nsresult FindNextFlagged(nsMsgViewIndex startIndex, nsMsgViewIndex *pResultIndex);
+ nsresult FindFirstNew(nsMsgViewIndex *pResultIndex);
+ nsresult FindPrevUnread(nsMsgKey startKey, nsMsgKey *pResultKey, nsMsgKey *resultThreadId);
+ nsresult FindFirstFlagged(nsMsgViewIndex *pResultIndex);
+ nsresult FindPrevFlagged(nsMsgViewIndex startIndex, nsMsgViewIndex *pResultIndex);
+ nsresult MarkThreadOfMsgRead(nsMsgKey msgId, nsMsgViewIndex msgIndex, nsTArray<nsMsgKey> &idsMarkedRead, bool bRead);
+ nsresult MarkThreadRead(nsIMsgThread *threadHdr, nsMsgViewIndex threadIndex, nsTArray<nsMsgKey> &idsMarkedRead, bool bRead);
+ bool IsValidIndex(nsMsgViewIndex index);
+ nsresult ToggleIgnored(nsMsgViewIndex * indices, int32_t numIndices, nsMsgViewIndex *resultIndex, bool *resultToggleState);
+ nsresult ToggleMessageKilled(nsMsgViewIndex * indices, int32_t numIndices, nsMsgViewIndex *resultIndex, bool *resultToggleState);
+ bool OfflineMsgSelected(nsMsgViewIndex * indices, int32_t numIndices);
+ bool NonDummyMsgSelected(nsMsgViewIndex * indices, int32_t numIndices);
+ char16_t * GetString(const char16_t *aStringName);
+ nsresult GetPrefLocalizedString(const char *aPrefName, nsString& aResult);
+ nsresult GetLabelPrefStringAndAtom(const char *aPrefName, nsString& aColor, nsIAtom** aColorAtom);
+ nsresult AppendKeywordProperties(const nsACString& keywords, nsAString& properties, bool addSelectedTextProperty);
+ nsresult InitLabelStrings(void);
+ nsresult CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater);
+ void InitializeAtomsAndLiterals();
+ virtual int32_t FindLevelInThread(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex);
+ nsresult GetImapDeleteModel(nsIMsgFolder *folder);
+ nsresult UpdateDisplayMessage(nsMsgViewIndex viewPosition);
+ nsresult GetDBForHeader(nsIMsgDBHdr *msgHdr, nsIMsgDatabase **db);
+
+ bool AdjustReadFlag(nsIMsgDBHdr *msgHdr, uint32_t *msgFlags);
+ void FreeAll(nsTArray<void*> *ptrs);
+ void ClearHdrCache();
+ nsTArray<nsMsgKey> m_keys;
+ nsTArray<uint32_t> m_flags;
+ nsTArray<uint8_t> m_levels;
+ nsMsgImapDeleteModel mDeleteModel;
+
+ // cache the most recently asked for header and corresponding msgKey.
+ nsCOMPtr <nsIMsgDBHdr> m_cachedHdr;
+ nsMsgKey m_cachedMsgKey;
+
+ // we need to store the message key for the message we are currenty displaying to ensure we
+ // don't try to redisplay the same message just because the selection changed (i.e. after a sort)
+ nsMsgKey m_currentlyDisplayedMsgKey;
+ nsCString m_currentlyDisplayedMsgUri;
+ nsMsgViewIndex m_currentlyDisplayedViewIndex;
+ // if we're deleting messages, we want to hold off loading messages on selection changed until the delete is done
+ // and we want to batch notifications.
+ bool m_deletingRows;
+ // for certain special folders
+ // and decendents of those folders
+ // (like the "Sent" folder, "Sent/Old Sent")
+ // the Sender column really shows recipients.
+
+ // Server types for this view's folder
+ bool mIsNews; // we have special icons for news
+ bool mIsRss; // rss affects enabling of junk commands
+ bool mIsXFVirtual; // a virtual folder with multiple folders
+
+ bool mShowSizeInLines; // for news we show lines instead of size when true
+ bool mSortThreadsByRoot; // as opposed to by the newest message
+ bool m_sortValid;
+ bool m_checkedCustomColumns;
+ bool mSelectionSummarized;
+ // we asked the front end to summarize the selection and it did not.
+ bool mSummarizeFailed;
+ uint8_t m_saveRestoreSelectionDepth;
+
+ nsCOMPtr <nsIMsgDatabase> m_db;
+ nsCOMPtr <nsIMsgFolder> m_folder;
+ nsCOMPtr <nsIMsgFolder> m_viewFolder; // for virtual folders, the VF db.
+ nsString mMessageType;
+ nsTArray <MsgViewSortColumnInfo> m_sortColumns;
+ nsMsgViewSortTypeValue m_sortType;
+ nsMsgViewSortOrderValue m_sortOrder;
+ nsString m_curCustomColumn;
+ nsMsgViewSortTypeValue m_secondarySort;
+ nsMsgViewSortOrderValue m_secondarySortOrder;
+ nsString m_secondaryCustomColumn;
+ nsMsgViewFlagsTypeValue m_viewFlags;
+
+ // I18N date formatter service which we'll want to cache locally.
+ nsCOMPtr<nsIDateTimeFormat> mDateFormatter;
+ nsCOMPtr<nsIMsgTagService> mTagService;
+ nsWeakPtr mMessengerWeak;
+ nsWeakPtr mMsgWindowWeak;
+ nsCOMPtr<nsIMsgDBViewCommandUpdater> mCommandUpdater; // we push command update notifications to the UI from this.
+ nsCOMPtr<nsIStringBundle> mMessengerStringBundle;
+
+ // used for the preference labels
+ nsString mLabelPrefDescriptions[PREF_LABELS_MAX];
+ nsString mLabelPrefColors[PREF_LABELS_MAX];
+ // used to cache the atoms created for each color to be displayed
+ static nsIAtom* mLabelPrefColorAtoms[PREF_LABELS_MAX];
+
+ // used to determine when to start and end
+ // junk plugin batches
+ uint32_t mNumMessagesRemainingInBatch;
+
+ // these are the headers of the messages in the current
+ // batch/series of batches of messages manually marked
+ // as junk
+ nsCOMPtr<nsIMutableArray> mJunkHdrs;
+
+ nsTArray<uint32_t> mIndicesToNoteChange;
+
+ nsTHashtable<nsCStringHashKey> mEmails;
+
+ // the saved search views keep track of the XX most recently deleted msg ids, so that if the
+ // delete is undone, we can add the msg back to the search results, even if it no longer
+ // matches the search criteria (e.g., a saved search over unread messages).
+ // We use mRecentlyDeletedArrayIndex to treat the array as a list of the XX
+ // most recently deleted msgs.
+ nsTArray<nsCString> mRecentlyDeletedMsgIds;
+ uint32_t mRecentlyDeletedArrayIndex;
+ void RememberDeletedMsgHdr(nsIMsgDBHdr *msgHdr);
+ bool WasHdrRecentlyDeleted(nsIMsgDBHdr *msgHdr);
+
+ //these hold pointers (and IDs) for the nsIMsgCustomColumnHandler object that constitutes the custom column handler
+ nsCOMArray <nsIMsgCustomColumnHandler> m_customColumnHandlers;
+ nsTArray<nsString> m_customColumnHandlerIDs;
+
+ nsIMsgCustomColumnHandler* GetColumnHandler(const char16_t*);
+ nsIMsgCustomColumnHandler* GetCurColumnHandler();
+ bool CustomColumnsInSortAndNotRegistered();
+ void EnsureCustomColumnsValid();
+
+#ifdef DEBUG_David_Bienvenu
+void InitEntryInfoForIndex(nsMsgViewIndex i, IdKeyPtr &EntryInfo);
+void ValidateSort();
+#endif
+
+protected:
+ static nsresult InitDisplayFormats();
+
+private:
+ static nsDateFormatSelector m_dateFormatDefault;
+ static nsDateFormatSelector m_dateFormatThisWeek;
+ static nsDateFormatSelector m_dateFormatToday;
+ bool ServerSupportsFilterAfterTheFact();
+
+ nsresult PerformActionsOnJunkMsgs(bool msgsAreJunk);
+ nsresult DetermineActionsForJunkChange(bool msgsAreJunk,
+ nsIMsgFolder *srcFolder,
+ bool &moveMessages,
+ bool &changeReadState,
+ nsIMsgFolder** targetFolder);
+
+ class nsMsgViewHdrEnumerator final : public nsISimpleEnumerator
+ {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsMsgThreadEnumerator methods:
+ explicit nsMsgViewHdrEnumerator(nsMsgDBView *view);
+
+ RefPtr<nsMsgDBView> m_view;
+ nsMsgViewIndex m_curHdrIndex;
+
+ private:
+ ~nsMsgViewHdrEnumerator();
+ };
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgFolderCache.cpp b/mailnews/base/src/nsMsgFolderCache.cpp
new file mode 100644
index 000000000..9510a6e3d
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderCache.cpp
@@ -0,0 +1,376 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgFolderCacheElement.h"
+#include "nsMsgFolderCache.h"
+#include "nsMorkCID.h"
+#include "nsIMdbFactoryFactory.h"
+#include "nsMsgBaseCID.h"
+#include "nsServiceManagerUtils.h"
+
+const char *kFoldersScope = "ns:msg:db:row:scope:folders:all"; // scope for all folders table
+const char *kFoldersTableKind = "ns:msg:db:table:kind:folders";
+
+nsMsgFolderCache::nsMsgFolderCache()
+{
+ m_mdbEnv = nullptr;
+ m_mdbStore = nullptr;
+ m_mdbAllFoldersTable = nullptr;
+}
+
+// should this, could this be an nsCOMPtr ?
+static nsIMdbFactory *gMDBFactory = nullptr;
+
+nsMsgFolderCache::~nsMsgFolderCache()
+{
+ m_cacheElements.Clear(); // make sure the folder cache elements are released before we release our m_mdb objects...
+ if (m_mdbAllFoldersTable)
+ m_mdbAllFoldersTable->Release();
+ if (m_mdbStore)
+ m_mdbStore->Release();
+ NS_IF_RELEASE(gMDBFactory);
+ if (m_mdbEnv)
+ m_mdbEnv->Release();
+}
+
+
+NS_IMPL_ISUPPORTS(nsMsgFolderCache, nsIMsgFolderCache)
+
+void nsMsgFolderCache::GetMDBFactory(nsIMdbFactory ** aMdbFactory)
+{
+ if (!mMdbFactory)
+ {
+ nsresult rv;
+ nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && mdbFactoryService)
+ rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory));
+ }
+ NS_IF_ADDREF(*aMdbFactory = mMdbFactory);
+}
+
+// initialize the various tokens and tables in our db's env
+nsresult nsMsgFolderCache::InitMDBInfo()
+{
+ nsresult err = NS_OK;
+ if (GetStore())
+ {
+ err = GetStore()->StringToToken(GetEnv(), kFoldersScope, &m_folderRowScopeToken);
+ if (NS_SUCCEEDED(err))
+ {
+ err = GetStore()->StringToToken(GetEnv(), kFoldersTableKind, &m_folderTableKindToken);
+ if (NS_SUCCEEDED(err))
+ {
+ // The table of all message hdrs will have table id 1.
+ m_allFoldersTableOID.mOid_Scope = m_folderRowScopeToken;
+ m_allFoldersTableOID.mOid_Id = 1;
+ }
+ }
+ }
+ return err;
+}
+
+// set up empty tables, dbFolderInfo, etc.
+nsresult nsMsgFolderCache::InitNewDB()
+{
+ nsresult err = InitMDBInfo();
+ if (NS_SUCCEEDED(err))
+ {
+ nsIMdbStore *store = GetStore();
+ // create the unique table for the dbFolderInfo.
+ // TODO: this error assignment is suspicious and never checked.
+ (void) store->NewTable(GetEnv(), m_folderRowScopeToken, m_folderTableKindToken,
+ false, nullptr, &m_mdbAllFoldersTable);
+ }
+ return err;
+}
+
+nsresult nsMsgFolderCache::InitExistingDB()
+{
+ nsresult err = InitMDBInfo();
+ if (NS_FAILED(err))
+ return err;
+
+ err = GetStore()->GetTable(GetEnv(), &m_allFoldersTableOID, &m_mdbAllFoldersTable);
+ if (NS_SUCCEEDED(err) && m_mdbAllFoldersTable)
+ {
+ nsIMdbTableRowCursor* rowCursor = nullptr;
+ err = m_mdbAllFoldersTable->GetTableRowCursor(GetEnv(), -1, &rowCursor);
+ if (NS_SUCCEEDED(err) && rowCursor)
+ {
+ // iterate over the table rows and create nsMsgFolderCacheElements for each.
+ while (true)
+ {
+ nsresult rv;
+ nsIMdbRow* hdrRow;
+ mdb_pos rowPos;
+
+ rv = rowCursor->NextRow(GetEnv(), &hdrRow, &rowPos);
+ if (NS_FAILED(rv) || !hdrRow)
+ break;
+
+ rv = AddCacheElement(EmptyCString(), hdrRow, nullptr);
+ hdrRow->Release();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ rowCursor->Release();
+ }
+ }
+ else
+ err = NS_ERROR_FAILURE;
+
+ return err;
+}
+
+nsresult nsMsgFolderCache::OpenMDB(const nsACString& dbName, bool exists)
+{
+ nsresult ret=NS_OK;
+ nsCOMPtr<nsIMdbFactory> mdbFactory;
+ GetMDBFactory(getter_AddRefs(mdbFactory));
+ if (mdbFactory)
+ {
+ ret = mdbFactory->MakeEnv(nullptr, &m_mdbEnv);
+ if (NS_SUCCEEDED(ret))
+ {
+ nsIMdbThumb *thumb = nullptr;
+ nsIMdbHeap* dbHeap = nullptr;
+
+ if (m_mdbEnv)
+ m_mdbEnv->SetAutoClear(true);
+ if (exists)
+ {
+ mdbOpenPolicy inOpenPolicy;
+ mdb_bool canOpen;
+ mdbYarn outFormatVersion;
+
+ nsIMdbFile* oldFile = nullptr;
+ ret = mdbFactory->OpenOldFile(m_mdbEnv, dbHeap, nsCString(dbName).get(),
+ mdbBool_kFalse, // not readonly, we want modifiable
+ &oldFile);
+ if ( oldFile )
+ {
+ if (NS_SUCCEEDED(ret))
+ {
+ ret = mdbFactory->CanOpenFilePort(m_mdbEnv, oldFile, // file to investigate
+ &canOpen, &outFormatVersion);
+ if (NS_SUCCEEDED(ret) && canOpen)
+ {
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->OpenFileStore(m_mdbEnv, NULL, oldFile, &inOpenPolicy,
+ &thumb);
+ }
+ else
+ ret = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ NS_RELEASE(oldFile); // always release our file ref, store has own
+ }
+ }
+ if (NS_SUCCEEDED(ret) && thumb)
+ {
+ mdb_count outTotal; // total somethings to do in operation
+ mdb_count outCurrent; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken; // is operation irreparably dead and broken?
+ do
+ {
+ ret = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken);
+ if (NS_FAILED(ret))
+ {
+ outDone = true;
+ break;
+ }
+ }
+ while (NS_SUCCEEDED(ret) && !outBroken && !outDone);
+ // m_mdbEnv->ClearErrors(); // ### temporary...
+ if (NS_SUCCEEDED(ret) && outDone)
+ {
+ ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore);
+ if (NS_SUCCEEDED(ret) && m_mdbStore)
+ ret = InitExistingDB();
+ }
+#ifdef DEBUG_bienvenu1
+ DumpContents();
+#endif
+ }
+ else // ### need error code saying why open file store failed
+ {
+ nsIMdbFile* newFile = 0;
+ ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, nsCString(dbName).get(), &newFile);
+ if ( newFile )
+ {
+ if (NS_SUCCEEDED(ret))
+ {
+ mdbOpenPolicy inOpenPolicy;
+
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->CreateNewFileStore(m_mdbEnv, dbHeap,
+ newFile, &inOpenPolicy, &m_mdbStore);
+ if (NS_SUCCEEDED(ret))
+ ret = InitNewDB();
+ }
+ NS_RELEASE(newFile); // always release our file ref, store has own
+ }
+
+ }
+ NS_IF_RELEASE(thumb);
+ }
+ }
+ return ret;
+}
+
+NS_IMETHODIMP nsMsgFolderCache::Init(nsIFile *aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ bool exists;
+ aFile->Exists(&exists);
+
+ nsAutoCString dbPath;
+ aFile->GetNativePath(dbPath);
+ // ### evil cast until MDB supports file streams.
+ nsresult rv = OpenMDB(dbPath, exists);
+ // if this fails and panacea.dat exists, try blowing away the db and recreating it
+ if (NS_FAILED(rv) && exists)
+ {
+ if (m_mdbStore)
+ m_mdbStore->Release();
+ aFile->Remove(false);
+ rv = OpenMDB(dbPath, false);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgFolderCache::GetCacheElement(const nsACString& pathKey, bool createIfMissing,
+ nsIMsgFolderCacheElement **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ NS_ENSURE_TRUE(!pathKey.IsEmpty(), NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgFolderCacheElement> folderCacheEl;
+ m_cacheElements.Get(pathKey, getter_AddRefs(folderCacheEl));
+ folderCacheEl.swap(*result);
+
+ if (*result)
+ return NS_OK;
+ else if (createIfMissing)
+ {
+ nsIMdbRow* hdrRow;
+
+ if (GetStore())
+ {
+ nsresult err = GetStore()->NewRow(GetEnv(), m_folderRowScopeToken, // row scope for row ids
+ &hdrRow);
+ if (NS_SUCCEEDED(err) && hdrRow)
+ {
+ m_mdbAllFoldersTable->AddRow(GetEnv(), hdrRow);
+ nsresult ret = AddCacheElement(pathKey, hdrRow, result);
+ if (*result)
+ (*result)->SetStringProperty("key", pathKey);
+ hdrRow->Release();
+ return ret;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgFolderCache::RemoveElement(const nsACString& key)
+{
+ nsCOMPtr<nsIMsgFolderCacheElement> folderCacheEl;
+ m_cacheElements.Get(key, getter_AddRefs(folderCacheEl));
+ if (!folderCacheEl)
+ return NS_ERROR_FAILURE;
+ nsMsgFolderCacheElement *element = static_cast<nsMsgFolderCacheElement *>(static_cast<nsISupports *>(folderCacheEl.get())); // why the double cast??
+ m_mdbAllFoldersTable->CutRow(GetEnv(), element->m_mdbRow);
+ m_cacheElements.Remove(key);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderCache::Clear()
+{
+ m_cacheElements.Clear();
+ if (m_mdbAllFoldersTable)
+ m_mdbAllFoldersTable->CutAllRows(GetEnv());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderCache::Close()
+{
+ return Commit(true);
+}
+
+NS_IMETHODIMP nsMsgFolderCache::Commit(bool compress)
+{
+ nsresult ret = NS_OK;
+ nsIMdbThumb *commitThumb = nullptr;
+ if (m_mdbStore)
+ {
+ if (compress)
+ ret = m_mdbStore->CompressCommit(GetEnv(), &commitThumb);
+ else
+ ret = m_mdbStore->LargeCommit(GetEnv(), &commitThumb);
+ }
+
+ if (commitThumb)
+ {
+ mdb_count outTotal = 0; // total somethings to do in operation
+ mdb_count outCurrent = 0; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken = false; // is operation irreparably dead and broken?
+ while (!outDone && !outBroken && NS_SUCCEEDED(ret))
+ ret = commitThumb->DoMore(GetEnv(), &outTotal, &outCurrent, &outDone, &outBroken);
+ NS_IF_RELEASE(commitThumb);
+ }
+ // ### do something with error, but clear it now because mork errors out on commits.
+ if (GetEnv())
+ GetEnv()->ClearErrors();
+ return ret;
+}
+
+nsresult nsMsgFolderCache::AddCacheElement(const nsACString& key, nsIMdbRow *row, nsIMsgFolderCacheElement **result)
+{
+ nsMsgFolderCacheElement *cacheElement = new nsMsgFolderCacheElement;
+ NS_ENSURE_TRUE(cacheElement, NS_ERROR_OUT_OF_MEMORY);
+ nsCOMPtr<nsIMsgFolderCacheElement> folderCacheEl(do_QueryInterface(cacheElement));
+
+ cacheElement->SetMDBRow(row);
+ cacheElement->SetOwningCache(this);
+ nsCString hashStrKey(key);
+ // if caller didn't pass in key, try to get it from row.
+ if (key.IsEmpty())
+ folderCacheEl->GetStringProperty("key", hashStrKey);
+ folderCacheEl->SetKey(hashStrKey);
+ m_cacheElements.Put(hashStrKey, folderCacheEl);
+ if (result)
+ folderCacheEl.swap(*result);
+ return NS_OK;
+}
+
+nsresult nsMsgFolderCache::RowCellColumnToCharPtr(nsIMdbRow *hdrRow, mdb_token columnToken, nsACString& resultStr)
+{
+ nsresult err = NS_OK;
+ nsIMdbCell *hdrCell;
+ if (hdrRow) // ### probably should be an error if hdrRow is NULL...
+ {
+ err = hdrRow->GetCell(GetEnv(), columnToken, &hdrCell);
+ if (NS_SUCCEEDED(err) && hdrCell)
+ {
+ struct mdbYarn yarn;
+ hdrCell->AliasYarn(GetEnv(), &yarn);
+ resultStr.Assign((const char *)yarn.mYarn_Buf, yarn.mYarn_Fill);
+ resultStr.SetLength(yarn.mYarn_Fill); // ensure the string is null terminated.
+ hdrCell->Release(); // always release ref
+ }
+ }
+ return err;
+}
diff --git a/mailnews/base/src/nsMsgFolderCache.h b/mailnews/base/src/nsMsgFolderCache.h
new file mode 100644
index 000000000..88536084b
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderCache.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgFolderCache_H
+#define nsMsgFolderCache_H
+
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsInterfaceHashtable.h"
+#include "nsCOMPtr.h"
+#include "mdb.h"
+
+class nsMsgFolderCache : public nsIMsgFolderCache
+{
+
+public:
+ friend class nsMsgFolderCacheElement;
+
+ nsMsgFolderCache();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGFOLDERCACHE
+
+protected:
+ virtual ~nsMsgFolderCache();
+
+ void GetMDBFactory(nsIMdbFactory ** aMdbFactory);
+ nsresult AddCacheElement(const nsACString& key, nsIMdbRow *row, nsIMsgFolderCacheElement **result);
+ nsresult RowCellColumnToCharPtr(nsIMdbRow *hdrRow, mdb_token columnToken, nsACString& resultPtr);
+ nsresult InitMDBInfo();
+ nsresult InitNewDB();
+ nsresult InitExistingDB();
+ nsresult OpenMDB(const nsACString& dbName, bool create);
+ nsIMdbEnv *GetEnv() {return m_mdbEnv;}
+ nsIMdbStore *GetStore() {return m_mdbStore;}
+ nsInterfaceHashtable<nsCStringHashKey, nsIMsgFolderCacheElement> m_cacheElements;
+ // mdb stuff
+ nsIMdbEnv *m_mdbEnv; // to be used in all the db calls.
+ nsIMdbStore *m_mdbStore;
+ nsIMdbTable *m_mdbAllFoldersTable;
+ mdb_token m_folderRowScopeToken;
+ mdb_token m_folderTableKindToken;
+ nsCOMPtr<nsIMdbFactory> mMdbFactory;
+
+ struct mdbOid m_allFoldersTableOID;
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgFolderCacheElement.cpp b/mailnews/base/src/nsMsgFolderCacheElement.cpp
new file mode 100644
index 000000000..e1197c7ed
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderCacheElement.cpp
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgFolderCacheElement.h"
+#include "prmem.h"
+#include "nsMsgUtils.h"
+
+nsMsgFolderCacheElement::nsMsgFolderCacheElement()
+{
+ m_mdbRow = nullptr;
+ m_owningCache = nullptr;
+}
+
+nsMsgFolderCacheElement::~nsMsgFolderCacheElement()
+{
+ NS_IF_RELEASE(m_mdbRow);
+ // circular reference, don't do it.
+ // NS_IF_RELEASE(m_owningCache);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgFolderCacheElement, nsIMsgFolderCacheElement)
+
+NS_IMETHODIMP nsMsgFolderCacheElement::GetKey(nsACString& aFolderKey)
+{
+ aFolderKey = m_folderKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderCacheElement::SetKey(const nsACString& aFolderKey)
+{
+ m_folderKey = aFolderKey;
+ return NS_OK;
+}
+
+void nsMsgFolderCacheElement::SetOwningCache(nsMsgFolderCache *owningCache)
+{
+ m_owningCache = owningCache;
+ // circular reference, don't do it.
+ // if (owningCache)
+ // NS_ADDREF(owningCache);
+}
+
+NS_IMETHODIMP nsMsgFolderCacheElement::GetStringProperty(const char *propertyName, nsACString& result)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ NS_ENSURE_TRUE(m_mdbRow && m_owningCache, NS_ERROR_FAILURE);
+
+ mdb_token property_token;
+ nsresult ret = m_owningCache->GetStore()->StringToToken(m_owningCache->GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(ret))
+ ret = m_owningCache->RowCellColumnToCharPtr(m_mdbRow, property_token, result);
+ return ret;
+}
+
+NS_IMETHODIMP nsMsgFolderCacheElement::GetInt32Property(const char *propertyName, int32_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_TRUE(m_mdbRow, NS_ERROR_FAILURE);
+
+ nsCString resultStr;
+ GetStringProperty(propertyName, resultStr);
+ if (resultStr.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ // This must be an inverse function to nsCString.AppentInt(),
+ // which uses snprintf("%x") internally, so that the wrapped negative numbers
+ // are decoded properly.
+ if (PR_sscanf(resultStr.get(), "%x", aResult) != 1)
+ {
+ NS_WARNING("Unexpected failure to decode hex string.");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderCacheElement::GetInt64Property(const char *propertyName, int64_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_TRUE(m_mdbRow, NS_ERROR_FAILURE);
+
+ nsCString resultStr;
+ GetStringProperty(propertyName, resultStr);
+ if (resultStr.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ // This must be an inverse function to nsCString.AppentInt(),
+ // which uses snprintf("%x") internally, so that the wrapped negative numbers
+ // are decoded properly.
+ if (PR_sscanf(resultStr.get(), "%llx", aResult) != 1)
+ {
+ NS_WARNING("Unexpected failure to decode hex string.");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderCacheElement::SetStringProperty(const char *propertyName, const nsACString& propertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ NS_ENSURE_TRUE(m_mdbRow, NS_ERROR_FAILURE);
+ nsresult rv = NS_OK;
+ mdb_token property_token;
+
+ if (m_owningCache)
+ {
+ rv = m_owningCache->GetStore()->StringToToken(m_owningCache->GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(rv))
+ {
+ struct mdbYarn yarn;
+
+ yarn.mYarn_Grow = NULL;
+ if (m_mdbRow)
+ {
+ nsCString propertyVal (propertyValue);
+ yarn.mYarn_Buf = (void *) propertyVal.get();
+ yarn.mYarn_Size = strlen((const char *) yarn.mYarn_Buf) + 1;
+ yarn.mYarn_Fill = yarn.mYarn_Size - 1;
+ yarn.mYarn_Form = 0; // what to do with this? we're storing csid in the msg hdr...
+ rv = m_mdbRow->AddColumn(m_owningCache->GetEnv(), property_token, &yarn);
+ return rv;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgFolderCacheElement::SetInt32Property(const char *propertyName, int32_t propertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ NS_ENSURE_TRUE(m_mdbRow, NS_ERROR_FAILURE);
+
+ // This also supports encoding negative numbers into hex
+ // by integer wrapping them (e.g. -1 -> "ffffffff").
+ nsAutoCString propertyStr;
+ propertyStr.AppendInt(propertyValue, 16);
+ return SetStringProperty(propertyName, propertyStr);
+}
+
+NS_IMETHODIMP nsMsgFolderCacheElement::SetInt64Property(const char *propertyName, int64_t propertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ NS_ENSURE_TRUE(m_mdbRow, NS_ERROR_FAILURE);
+
+ // This also supports encoding negative numbers into hex
+ // by integer wrapping them (e.g. -1 -> "ffffffffffffffff").
+ nsAutoCString propertyStr;
+ propertyStr.AppendInt(propertyValue, 16);
+ return SetStringProperty(propertyName, propertyStr);
+}
+
+void nsMsgFolderCacheElement::SetMDBRow(nsIMdbRow *row)
+{
+ if (m_mdbRow)
+ NS_RELEASE(m_mdbRow);
+ NS_IF_ADDREF(m_mdbRow = row);
+}
diff --git a/mailnews/base/src/nsMsgFolderCacheElement.h b/mailnews/base/src/nsMsgFolderCacheElement.h
new file mode 100644
index 000000000..4abf0f08f
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderCacheElement.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgFolderCacheElement_H
+#define nsMsgFolderCacheElement_H
+
+#include "nsIMsgFolderCacheElement.h"
+#include "nsMsgFolderCache.h"
+#include "mdb.h"
+
+class nsMsgFolderCacheElement : public nsIMsgFolderCacheElement
+{
+public:
+ nsMsgFolderCacheElement();
+ friend class nsMsgFolderCache;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGFOLDERCACHEELEMENT
+
+ void SetMDBRow(nsIMdbRow *row);
+ void SetOwningCache(nsMsgFolderCache *owningCache);
+protected:
+ virtual ~nsMsgFolderCacheElement();
+
+ nsIMdbRow *m_mdbRow;
+
+ nsMsgFolderCache *m_owningCache; // this will be ref-counted. Is this going to be a problem?
+ // I want to avoid circular references, but since this is
+ // scriptable, I think I have to ref-count it.
+ nsCString m_folderKey;
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgFolderCompactor.cpp b/mailnews/base/src/nsMsgFolderCompactor.cpp
new file mode 100644
index 000000000..c20d54b0f
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderCompactor.cpp
@@ -0,0 +1,1352 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nsCOMPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsAutoPtr.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsIMsgHdr.h"
+#include "nsIStreamListener.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgUtils.h"
+#include "nsISeekableStream.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIDocShell.h"
+#include "nsIPrompt.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsMailHeaders.h"
+#include "nsMsgI18N.h"
+#include "prprf.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsIMsgDatabase.h"
+#include "nsArrayUtils.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsMsgFolderCompactor.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsPrintfCString.h"
+
+
+//////////////////////////////////////////////////////////////////////////////
+// nsFolderCompactState
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsFolderCompactState, nsIMsgFolderCompactor, nsIRequestObserver, nsIStreamListener, nsICopyMessageStreamListener, nsIUrlListener)
+
+nsFolderCompactState::nsFolderCompactState()
+{
+ m_fileStream = nullptr;
+ m_size = 0;
+ m_curIndex = 0;
+ m_status = NS_OK;
+ m_compactAll = false;
+ m_compactOfflineAlso = false;
+ m_compactingOfflineFolders = false;
+ m_parsingFolder=false;
+ m_folderIndex = 0;
+ m_startOfMsg = true;
+ m_needStatusLine = false;
+ m_totalExpungedBytes = 0;
+ m_alreadyWarnedDiskSpace = false;
+}
+
+nsFolderCompactState::~nsFolderCompactState()
+{
+ CloseOutputStream();
+ if (NS_FAILED(m_status))
+ {
+ CleanupTempFilesAfterError();
+ // if for some reason we failed remove the temp folder and database
+ }
+}
+
+void nsFolderCompactState::CloseOutputStream()
+{
+ if (m_fileStream)
+ {
+ m_fileStream->Close();
+ m_fileStream = nullptr;
+ }
+
+}
+
+void nsFolderCompactState::CleanupTempFilesAfterError()
+{
+ CloseOutputStream();
+ if (m_db)
+ m_db->ForceClosed();
+ nsCOMPtr <nsIFile> summaryFile;
+ GetSummaryFileLocation(m_file, getter_AddRefs(summaryFile));
+ m_file->Remove(false);
+ summaryFile->Remove(false);
+}
+
+nsresult nsFolderCompactState::BuildMessageURI(const char *baseURI, nsMsgKey key, nsCString& uri)
+{
+ uri.Append(baseURI);
+ uri.Append('#');
+ uri.AppendInt(key);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsFolderCompactState::InitDB(nsIMsgDatabase *db)
+{
+ nsCOMPtr<nsIMsgDatabase> mailDBFactory;
+ nsresult rv = db->ListAllKeys(m_keyArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_size = m_keyArray->m_keys.Length();
+
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenMailDBFromFile(m_file, m_folder, true, false,
+ getter_AddRefs(m_db));
+
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE ||
+ rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ // if it's out of date then reopen with upgrade.
+ return msgDBService->OpenMailDBFromFile(m_file,
+ m_folder, true, true,
+ getter_AddRefs(m_db));
+ return rv;
+}
+
+NS_IMETHODIMP nsFolderCompactState::CompactFolders(nsIArray *aArrayOfFoldersToCompact,
+ nsIArray *aOfflineFolderArray,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow)
+{
+ m_window = aMsgWindow;
+ m_listener = aUrlListener;
+ if (aArrayOfFoldersToCompact)
+ m_folderArray = aArrayOfFoldersToCompact;
+ else if (aOfflineFolderArray)
+ {
+ m_folderArray = aOfflineFolderArray;
+ m_compactingOfflineFolders = true;
+ aOfflineFolderArray = nullptr;
+ }
+ if (!m_folderArray)
+ return NS_OK;
+
+ m_compactAll = true;
+ m_compactOfflineAlso = aOfflineFolderArray != nullptr;
+ if (m_compactOfflineAlso)
+ m_offlineFolderArray = aOfflineFolderArray;
+
+ m_folderIndex = 0;
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgFolder> firstFolder = do_QueryElementAt(m_folderArray,
+ m_folderIndex, &rv);
+
+ if (NS_SUCCEEDED(rv) && firstFolder)
+ Compact(firstFolder, m_compactingOfflineFolders, aUrlListener,
+ aMsgWindow); //start with first folder from here.
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFolderCompactState::Compact(nsIMsgFolder *folder, bool aOfflineStore,
+ nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(folder);
+ m_listener = aListener;
+ if (!m_compactingOfflineFolders && !aOfflineStore)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
+ if (imapFolder)
+ return imapFolder->Expunge(this, aMsgWindow);
+ }
+
+ m_window = aMsgWindow;
+ nsresult rv;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIFile> path;
+ nsCString baseMessageURI;
+
+ nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder, &rv);
+ if (NS_SUCCEEDED(rv) && localFolder)
+ {
+ rv=localFolder->GetDatabaseWOReparse(getter_AddRefs(db));
+ if (NS_FAILED(rv) || !db)
+ {
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
+ rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ {
+ m_folder = folder; //will be used to compact
+ m_parsingFolder = true;
+ rv = localFolder->ParseFolder(m_window, this);
+ }
+ return rv;
+ }
+ else
+ {
+ bool valid;
+ rv = db->GetSummaryValid(&valid);
+ if (!valid) //we are probably parsing the folder because we selected it.
+ {
+ folder->NotifyCompactCompleted();
+ if (m_compactAll)
+ return CompactNextFolder();
+ else
+ return NS_OK;
+ }
+ }
+ }
+ else
+ {
+ rv = folder->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = folder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ do {
+ bool exists = false;
+ rv = path->Exists(&exists);
+ if (!exists) {
+ // No need to compact if the local file does not exist.
+ // Can happen e.g. on IMAP when the folder is not marked for offline use.
+ break;
+ }
+
+ int64_t expunged = 0;
+ folder->GetExpungedBytes(&expunged);
+ if (expunged == 0) {
+ // No need to compact if nothing would be expunged.
+ break;
+ }
+
+ int64_t diskSize;
+ rv = folder->GetSizeOnDisk(&diskSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t diskFree;
+ rv = path->GetDiskSpaceAvailable(&diskFree);
+ if (NS_FAILED(rv)) {
+ // If GetDiskSpaceAvailable() failed, better bail out fast.
+ if (rv != NS_ERROR_NOT_IMPLEMENTED)
+ return rv;
+ // Some platforms do not have GetDiskSpaceAvailable implemented.
+ // In that case skip the preventive free space analysis and let it
+ // fail in compact later if space actually wasn't available.
+ } else {
+ // Let's try to not even start compact if there is really low free space.
+ // It may still fail later as we do not know how big exactly the folder DB will
+ // end up being.
+ // The DB already doesn't contain references to messages that are already deleted.
+ // So theoretically it shouldn't shrink with compact. But in practice,
+ // the automatic shrinking of the DB may still have not yet happened.
+ // So we cap the final size at 1KB per message.
+ db->Commit(nsMsgDBCommitType::kCompressCommit);
+
+ int64_t dbSize;
+ rv = db->GetDatabaseSize(&dbSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t totalMsgs;
+ rv = folder->GetTotalMessages(false, &totalMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t expectedDBSize = std::min<int64_t>(dbSize, ((int64_t)totalMsgs) * 1024);
+ if (diskFree < diskSize - expunged + expectedDBSize)
+ {
+ if (!m_alreadyWarnedDiskSpace)
+ {
+ folder->ThrowAlertMsg("compactFolderInsufficientSpace", m_window);
+ m_alreadyWarnedDiskSpace = true;
+ }
+ break;
+ }
+ }
+
+ rv = folder->GetBaseMessageURI(baseMessageURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = Init(folder, baseMessageURI.get(), db, path, m_window);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isLocked = true;
+ m_folder->GetLocked(&isLocked);
+ if (isLocked)
+ {
+ CleanupTempFilesAfterError();
+ m_folder->ThrowAlertMsg("compactFolderDeniedLock", m_window);
+ break;
+ }
+
+ // If we got here start the real compacting.
+ nsCOMPtr<nsISupports> supports = do_QueryInterface(static_cast<nsIMsgFolderCompactor*>(this));
+ m_folder->AcquireSemaphore(supports);
+ m_totalExpungedBytes += expunged;
+ return StartCompacting();
+
+ } while(false); // block for easy skipping the compaction using 'break'
+
+ folder->NotifyCompactCompleted();
+ if (m_compactAll)
+ return CompactNextFolder();
+ else
+ return NS_OK;
+}
+
+nsresult nsFolderCompactState::ShowStatusMsg(const nsString& aMsg)
+{
+ nsCOMPtr <nsIMsgStatusFeedback> statusFeedback;
+ if (m_window)
+ {
+ m_window->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ if (statusFeedback && !aMsg.IsEmpty())
+ return statusFeedback->SetStatusString(aMsg);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsFolderCompactState::Init(nsIMsgFolder *folder, const char *baseMsgUri, nsIMsgDatabase *db,
+ nsIFile *path, nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+
+ m_folder = folder;
+ m_baseMessageUri = baseMsgUri;
+ m_file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_file->InitWithFile(path);
+ // need to make sure the temp file goes in the same real directory
+ // as the original file, so resolve sym links.
+ m_file->SetFollowLinks(true);
+
+ m_file->SetNativeLeafName(NS_LITERAL_CSTRING("nstmp"));
+ rv = m_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); //make sure we are not crunching existing nstmp file
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_window = aMsgWindow;
+ m_keyArray = new nsMsgKeyArray;
+ m_size = 0;
+ m_totalMsgSize = 0;
+ rv = InitDB(db);
+ if (NS_FAILED(rv))
+ {
+ CleanupTempFilesAfterError();
+ return rv;
+ }
+
+ m_curIndex = 0;
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_fileStream), m_file, -1, 00600);
+ if (NS_FAILED(rv))
+ m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window);
+ else
+ rv = GetMessageServiceFromURI(nsDependentCString(baseMsgUri),
+ getter_AddRefs(m_messageService));
+ if (NS_FAILED(rv))
+ {
+ m_status = rv;
+ }
+ return rv;
+}
+
+void nsFolderCompactState::ShowCompactingStatusMsg()
+{
+ nsString statusString;
+ nsresult rv = m_folder->GetStringWithFolderNameFromBundle("compactingFolder", statusString);
+ if (!statusString.IsEmpty() && NS_SUCCEEDED(rv))
+ ShowStatusMsg(statusString);
+}
+
+NS_IMETHODIMP nsFolderCompactState::OnStartRunningUrl(nsIURI *url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFolderCompactState::OnStopRunningUrl(nsIURI *url, nsresult status)
+{
+ if (m_parsingFolder)
+ {
+ m_parsingFolder = false;
+ if (NS_SUCCEEDED(status))
+ status = Compact(m_folder, m_compactingOfflineFolders, m_listener, m_window);
+ else if (m_compactAll)
+ CompactNextFolder();
+ }
+ else if (m_compactAll) // this should be the imap case only
+ {
+ nsCOMPtr <nsIMsgFolder> prevFolder = do_QueryElementAt(m_folderArray,
+ m_folderIndex);
+ if (prevFolder)
+ prevFolder->SetMsgDatabase(nullptr);
+ CompactNextFolder();
+ }
+ else if (m_listener)
+ {
+ CompactCompleted(status);
+ }
+ return NS_OK;
+}
+
+nsresult nsFolderCompactState::StartCompacting()
+{
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = server->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Notify that compaction is beginning. We do this even if there are no
+ // messages to be copied because the summary database still gets blown away
+ // which is still pretty interesting. (And we like consistency.)
+ nsCOMPtr<nsIMsgFolderNotificationService>
+ notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyItemEvent(m_folder,
+ NS_LITERAL_CSTRING("FolderCompactStart"),
+ nullptr);
+
+ // TODO: test whether sorting the messages (m_keyArray) by messageOffset
+ // would improve performance on large files (less seeks).
+ // The m_keyArray is in the order as stored in DB and on IMAP or News
+ // the messages stored on the mbox file are not necessarily in the same order.
+ if (m_size > 0)
+ {
+ nsCOMPtr<nsIURI> notUsed;
+ ShowCompactingStatusMsg();
+ AddRef();
+ rv = m_messageService->CopyMessages(m_size, m_keyArray->m_keys.Elements(),
+ m_folder, this,
+ false, nullptr, m_window,
+ getter_AddRefs(notUsed));
+ }
+ else
+ { // no messages to copy with
+ FinishCompact();
+// Release(); // we don't "own" ourselves yet.
+ }
+ return rv;
+}
+
+nsresult
+nsFolderCompactState::FinishCompact()
+{
+ NS_ENSURE_TRUE(m_folder, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(m_file, NS_ERROR_NOT_INITIALIZED);
+
+ // All okay time to finish up the compact process
+ nsCOMPtr<nsIFile> path;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+
+ // get leaf name and database name of the folder
+ nsresult rv = m_folder->GetFilePath(getter_AddRefs(path));
+ nsCOMPtr <nsIFile> folderPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folderPath->InitWithFile(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // need to make sure we put the .msf file in the same directory
+ // as the original mailbox, so resolve symlinks.
+ folderPath->SetFollowLinks(true);
+
+ nsCOMPtr <nsIFile> oldSummaryFile;
+ rv = GetSummaryFileLocation(folderPath, getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString dbName;
+ oldSummaryFile->GetNativeLeafName(dbName);
+ nsAutoCString folderName;
+ path->GetNativeLeafName(folderName);
+
+ // close down the temp file stream; preparing for deleting the old folder
+ // and its database; then rename the temp folder and database
+ if (m_fileStream)
+ {
+ m_fileStream->Flush();
+ m_fileStream->Close();
+ m_fileStream = nullptr;
+ }
+
+ // make sure the new database is valid.
+ // Close it so we can rename the .msf file.
+ if (m_db)
+ {
+ m_db->ForceClosed();
+ m_db = nullptr;
+ }
+
+ nsCOMPtr <nsIFile> newSummaryFile;
+ rv = GetSummaryFileLocation(m_file, getter_AddRefs(newSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIDBFolderInfo> transferInfo;
+ m_folder->GetDBTransferInfo(getter_AddRefs(transferInfo));
+
+ // close down database of the original folder
+ m_folder->ForceDBClosed();
+
+ nsCOMPtr<nsIFile> cloneFile;
+ int64_t fileSize = 0;
+ rv = m_file->Clone(getter_AddRefs(cloneFile));
+ if (NS_SUCCEEDED(rv))
+ rv = cloneFile->GetFileSize(&fileSize);
+ bool tempFileRightSize = ((uint64_t)fileSize == m_totalMsgSize);
+ NS_WARNING_ASSERTION(tempFileRightSize, "temp file not of expected size in compact");
+
+ bool folderRenameSucceeded = false;
+ bool msfRenameSucceeded = false;
+ if (NS_SUCCEEDED(rv) && tempFileRightSize)
+ {
+ // First we're going to try and move the old summary file out the way.
+ // We don't delete it yet, as we want to keep the files in sync.
+ nsCOMPtr<nsIFile> tempSummaryFile;
+ rv = oldSummaryFile->Clone(getter_AddRefs(tempSummaryFile));
+ if (NS_SUCCEEDED(rv))
+ rv = tempSummaryFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+
+ nsAutoCString tempSummaryFileName;
+ if (NS_SUCCEEDED(rv))
+ rv = tempSummaryFile->GetNativeLeafName(tempSummaryFileName);
+
+ if (NS_SUCCEEDED(rv))
+ rv = oldSummaryFile->MoveToNative((nsIFile*) nullptr, tempSummaryFileName);
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "error moving compacted folder's db out of the way");
+ if (NS_SUCCEEDED(rv))
+ {
+ // Now we've successfully moved the summary file out the way, try moving
+ // the newly compacted message file over the old one.
+ rv = m_file->MoveToNative((nsIFile *) nullptr, folderName);
+ folderRenameSucceeded = NS_SUCCEEDED(rv);
+ NS_WARNING_ASSERTION(folderRenameSucceeded, "error renaming compacted folder");
+ if (folderRenameSucceeded)
+ {
+ // That worked, so land the new summary file in the right place.
+ nsCOMPtr<nsIFile> renamedCompactedSummaryFile;
+ newSummaryFile->Clone(getter_AddRefs(renamedCompactedSummaryFile));
+ if (renamedCompactedSummaryFile)
+ {
+ rv = renamedCompactedSummaryFile->MoveToNative((nsIFile *) nullptr, dbName);
+ msfRenameSucceeded = NS_SUCCEEDED(rv);
+ }
+ NS_WARNING_ASSERTION(msfRenameSucceeded, "error renaming compacted folder's db");
+ }
+
+ if (!msfRenameSucceeded)
+ {
+ // Do our best to put the summary file back to where it was
+ rv = tempSummaryFile->MoveToNative((nsIFile*) nullptr, dbName);
+ if (NS_SUCCEEDED(rv))
+ tempSummaryFile = nullptr; // flagging that a renamed db no longer exists
+ else
+ NS_WARNING("error restoring uncompacted folder's db");
+ }
+ }
+ // We don't want any temporarily renamed summary file to lie around
+ if (tempSummaryFile)
+ tempSummaryFile->Remove(false);
+ }
+
+ NS_WARNING_ASSERTION(msfRenameSucceeded, "compact failed");
+ nsresult rvReleaseFolderLock = ReleaseFolderLock();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rvReleaseFolderLock),"folder lock not released successfully");
+ rv = NS_FAILED(rv) ? rv : rvReleaseFolderLock;
+
+ // Cleanup of nstmp-named compacted files if failure
+ if (!folderRenameSucceeded)
+ {
+ // remove the abandoned compacted version with the wrong name
+ m_file->Remove(false);
+ }
+ if (!msfRenameSucceeded)
+ {
+ // remove the abandoned compacted summary file
+ newSummaryFile->Remove(false);
+ }
+
+ if (msfRenameSucceeded)
+ {
+ // Transfer local db information from transferInfo
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenFolderDB(m_folder, true, getter_AddRefs(m_db));
+ NS_ENSURE_TRUE(m_db, NS_FAILED(rv) ? rv : NS_ERROR_FAILURE);
+ // These errors are expected.
+ rv = (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
+ rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) ? NS_OK : rv;
+ m_db->SetSummaryValid(true);
+ m_folder->SetDBTransferInfo(transferInfo);
+
+ // since we're transferring info from the old db, we need to reset the expunged bytes
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if(dbFolderInfo)
+ dbFolderInfo->SetExpungedBytes(0);
+ }
+ if (m_db)
+ m_db->Close(true);
+ m_db = nullptr;
+
+ // Notify that compaction of the folder is completed.
+ nsCOMPtr<nsIMsgFolderNotificationService>
+ notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyItemEvent(m_folder,
+ NS_LITERAL_CSTRING("FolderCompactFinish"),
+ nullptr);
+ m_folder->NotifyCompactCompleted();
+
+ if (m_compactAll)
+ rv = CompactNextFolder();
+ else
+ CompactCompleted(rv);
+
+ return rv;
+}
+
+nsresult
+GetBaseStringBundle(nsIStringBundle **aBundle)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ return bundleService->CreateBundle(
+ "chrome://messenger/locale/messenger.properties", aBundle);
+}
+
+void nsFolderCompactState::CompactCompleted(nsresult exitCode)
+{
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(exitCode),
+ "nsFolderCompactState::CompactCompleted failed");
+ if (m_listener)
+ m_listener->OnStopRunningUrl(nullptr, exitCode);
+ ShowDoneStatus();
+}
+
+nsresult
+nsFolderCompactState::ReleaseFolderLock()
+{
+ nsresult result = NS_OK;
+ if (!m_folder) return result;
+ bool haveSemaphore;
+ nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMsgFolderCompactor*>(this));
+ result = m_folder->TestSemaphore(supports, &haveSemaphore);
+ if(NS_SUCCEEDED(result) && haveSemaphore)
+ result = m_folder->ReleaseSemaphore(supports);
+ return result;
+}
+
+void nsFolderCompactState::ShowDoneStatus()
+{
+ if (m_folder)
+ {
+ nsString statusString;
+ nsCOMPtr <nsIStringBundle> bundle;
+ nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ nsAutoString expungedAmount;
+ FormatFileSize(m_totalExpungedBytes, true, expungedAmount);
+ const char16_t* params[] = { expungedAmount.get() };
+ rv = bundle->FormatStringFromName(u"compactingDone",
+ params, 1, getter_Copies(statusString));
+
+ if (!statusString.IsEmpty() && NS_SUCCEEDED(rv))
+ ShowStatusMsg(statusString);
+ }
+}
+
+nsresult
+nsFolderCompactState::CompactNextFolder()
+{
+ m_folderIndex++;
+ uint32_t cnt = 0;
+ nsresult rv = m_folderArray->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // m_folderIndex might be > cnt if we compact offline stores,
+ // and get back here from OnStopRunningUrl.
+ if (m_folderIndex >= cnt)
+ {
+ if (!m_compactOfflineAlso || m_compactingOfflineFolders)
+ {
+ CompactCompleted(NS_OK);
+ return rv;
+ }
+ m_compactingOfflineFolders = true;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(m_folderArray,
+ m_folderIndex-1, &rv);
+ if (NS_SUCCEEDED(rv) && folder)
+ return folder->CompactAllOfflineStores(this, m_window, m_offlineFolderArray);
+ else
+ NS_WARNING("couldn't get folder to compact offline stores");
+
+ }
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(m_folderArray,
+ m_folderIndex, &rv);
+
+ if (NS_SUCCEEDED(rv) && folder)
+ rv = Compact(folder, m_compactingOfflineFolders, m_listener, m_window);
+ else
+ CompactCompleted(rv);
+ return rv;
+}
+
+nsresult
+nsFolderCompactState::GetMessage(nsIMsgDBHdr **message)
+{
+ return GetMsgDBHdrFromURI(m_messageUri.get(), message);
+}
+
+
+NS_IMETHODIMP
+nsFolderCompactState::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ return StartMessage();
+}
+
+NS_IMETHODIMP
+nsFolderCompactState::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
+ nsresult status)
+{
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ if (NS_FAILED(status))
+ {
+ m_status = status; // set the m_status to status so the destructor can remove the
+ // temp folder and database
+ CleanupTempFilesAfterError();
+ m_folder->NotifyCompactCompleted();
+ ReleaseFolderLock();
+ m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window);
+ }
+ else
+ {
+ // XXX TODO: Error checking and handling missing here.
+ EndCopy(nullptr, status);
+ if (m_curIndex >= m_size)
+ {
+ msgHdr = nullptr;
+ // no more to copy finish it up
+ FinishCompact();
+ }
+ else
+ {
+ // in case we're not getting an error, we still need to pretend we did get an error,
+ // because the compact did not successfully complete.
+ m_folder->NotifyCompactCompleted();
+ CleanupTempFilesAfterError();
+ ReleaseFolderLock();
+ }
+ }
+ Release(); // kill self
+ return status;
+}
+
+// XXX TODO: This function is sadly lacking all status checking, it always
+// returns NS_OK and moves onto the next message.
+NS_IMETHODIMP
+nsFolderCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ if (!m_fileStream || !inStr)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+ uint32_t msgFlags;
+ bool checkForKeyword = m_startOfMsg;
+ bool addKeywordHdr = false;
+ uint32_t needToGrowKeywords = 0;
+ uint32_t statusOffset;
+ nsCString msgHdrKeywords;
+
+ if (m_startOfMsg)
+ {
+ m_statusOffset = 0;
+ m_addedHeaderSize = 0;
+ m_messageUri.Truncate(); // clear the previous message uri
+ if (NS_SUCCEEDED(BuildMessageURI(m_baseMessageUri.get(), m_keyArray->m_keys[m_curIndex],
+ m_messageUri)))
+ {
+ rv = GetMessage(getter_AddRefs(m_curSrcHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (m_curSrcHdr)
+ {
+ (void) m_curSrcHdr->GetFlags(&msgFlags);
+ (void) m_curSrcHdr->GetStatusOffset(&statusOffset);
+
+ if (statusOffset == 0)
+ m_needStatusLine = true;
+ // x-mozilla-status lines should be at the start of the headers, and the code
+ // below assumes everything will fit in m_dataBuffer - if there's not
+ // room, skip the keyword stuff.
+ if (statusOffset > sizeof(m_dataBuffer) - 1024)
+ {
+ checkForKeyword = false;
+ NS_ASSERTION(false, "status offset past end of read buffer size");
+
+ }
+ }
+ }
+ m_startOfMsg = false;
+ }
+ uint32_t maxReadCount, readCount, writeCount;
+ uint32_t bytesWritten;
+
+ while (NS_SUCCEEDED(rv) && (int32_t) count > 0)
+ {
+ maxReadCount = count > sizeof(m_dataBuffer) - 1 ? sizeof(m_dataBuffer) - 1 : count;
+ writeCount = 0;
+ rv = inStr->Read(m_dataBuffer, maxReadCount, &readCount);
+
+ // if status offset is past the number of bytes we read, it's probably bogus,
+ // and we shouldn't do any of the keyword stuff.
+ if (statusOffset + X_MOZILLA_STATUS_LEN > readCount)
+ checkForKeyword = false;
+
+ if (NS_SUCCEEDED(rv))
+ {
+ if (checkForKeyword)
+ {
+ // make sure that status offset really points to x-mozilla-status line
+ if (!strncmp(m_dataBuffer + statusOffset, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN))
+ {
+ const char *keywordHdr = PL_strnrstr(m_dataBuffer, HEADER_X_MOZILLA_KEYWORDS, readCount);
+ if (keywordHdr)
+ m_curSrcHdr->GetUint32Property("growKeywords", &needToGrowKeywords);
+ else
+ addKeywordHdr = true;
+ m_curSrcHdr->GetStringProperty("keywords", getter_Copies(msgHdrKeywords));
+ }
+ checkForKeyword = false;
+ }
+ uint32_t blockOffset = 0;
+ if (m_needStatusLine)
+ {
+ m_needStatusLine = false;
+ // we need to parse out the "From " header, write it out, then
+ // write out the x-mozilla-status headers, and set the
+ // status offset of the dest hdr for later use
+ // in OnEndCopy).
+ if (!strncmp(m_dataBuffer, "From ", 5))
+ {
+ blockOffset = 5;
+ // skip from line
+ MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
+ char statusLine[50];
+ m_fileStream->Write(m_dataBuffer, blockOffset, &writeCount);
+ m_statusOffset = blockOffset;
+ PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF);
+ m_fileStream->Write(statusLine, strlen(statusLine), &m_addedHeaderSize);
+ PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000);
+ m_fileStream->Write(statusLine, strlen(statusLine), &bytesWritten);
+ m_addedHeaderSize += bytesWritten;
+ }
+ else
+ {
+ NS_ASSERTION(false, "not an envelope");
+ // try to mark the db as invalid so it will be reparsed.
+ nsCOMPtr <nsIMsgDatabase> srcDB;
+ m_folder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (srcDB)
+ {
+ srcDB->SetSummaryValid(false);
+ srcDB->ForceClosed();
+ }
+ }
+ }
+#define EXTRA_KEYWORD_HDR " " MSG_LINEBREAK
+
+ // if status offset isn't in the first block, this code won't work. There's no good reason
+ // for the status offset not to be at the beginning of the message anyway.
+ if (addKeywordHdr)
+ {
+ // if blockOffset is set, we added x-mozilla-status headers so
+ // file pointer is already past them.
+ if (!blockOffset)
+ {
+ blockOffset = statusOffset;
+ // skip x-mozilla-status and status2 lines.
+ MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
+ MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
+ // need to rewrite the headers up to and including the x-mozilla-status2 header
+ m_fileStream->Write(m_dataBuffer, blockOffset, &writeCount);
+ }
+ // we should write out the existing keywords from the msg hdr, if any.
+ if (msgHdrKeywords.IsEmpty())
+ { // no keywords, so write blank header
+ m_fileStream->Write(X_MOZILLA_KEYWORDS, sizeof(X_MOZILLA_KEYWORDS) - 1, &bytesWritten);
+ m_addedHeaderSize += bytesWritten;
+ }
+ else
+ {
+ if (msgHdrKeywords.Length() < sizeof(X_MOZILLA_KEYWORDS) - sizeof(HEADER_X_MOZILLA_KEYWORDS) + 10 /* allow some slop */)
+ { // keywords fit in normal blank header, so replace blanks in keyword hdr with keywords
+ nsAutoCString keywordsHdr(X_MOZILLA_KEYWORDS);
+ keywordsHdr.Replace(sizeof(HEADER_X_MOZILLA_KEYWORDS) + 1, msgHdrKeywords.Length(), msgHdrKeywords);
+ m_fileStream->Write(keywordsHdr.get(), keywordsHdr.Length(), &bytesWritten);
+ m_addedHeaderSize += bytesWritten;
+ }
+ else
+ { // keywords don't fit, so write out keywords on one line and an extra blank line
+ nsCString newKeywordHeader(HEADER_X_MOZILLA_KEYWORDS ": ");
+ newKeywordHeader.Append(msgHdrKeywords);
+ newKeywordHeader.Append(MSG_LINEBREAK EXTRA_KEYWORD_HDR);
+ m_fileStream->Write(newKeywordHeader.get(), newKeywordHeader.Length(), &bytesWritten);
+ m_addedHeaderSize += bytesWritten;
+ }
+ }
+ addKeywordHdr = false;
+ }
+ else if (needToGrowKeywords)
+ {
+ blockOffset = statusOffset;
+ if (!strncmp(m_dataBuffer + blockOffset, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN))
+ MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); // skip x-mozilla-status hdr
+ if (!strncmp(m_dataBuffer + blockOffset, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN))
+ MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); // skip x-mozilla-status2 hdr
+ uint32_t preKeywordBlockOffset = blockOffset;
+ if (!strncmp(m_dataBuffer + blockOffset, HEADER_X_MOZILLA_KEYWORDS, sizeof(HEADER_X_MOZILLA_KEYWORDS) - 1))
+ {
+ do
+ {
+ // skip x-mozilla-keywords hdr and any existing continuation headers
+ MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
+ }
+ while (m_dataBuffer[blockOffset] == ' ');
+ }
+ int32_t oldKeywordSize = blockOffset - preKeywordBlockOffset;
+
+ // rewrite the headers up to and including the x-mozilla-status2 header
+ m_fileStream->Write(m_dataBuffer, preKeywordBlockOffset, &writeCount);
+ // let's just rewrite all the keywords on several lines and add a blank line,
+ // instead of worrying about which are missing.
+ bool done = false;
+ nsAutoCString keywordHdr(HEADER_X_MOZILLA_KEYWORDS ": ");
+ int32_t nextBlankOffset = 0;
+ int32_t curHdrLineStart = 0;
+ int32_t newKeywordSize = 0;
+ while (!done)
+ {
+ nextBlankOffset = msgHdrKeywords.FindChar(' ', nextBlankOffset);
+ if (nextBlankOffset == kNotFound)
+ {
+ nextBlankOffset = msgHdrKeywords.Length();
+ done = true;
+ }
+ if (nextBlankOffset - curHdrLineStart > 90 || done)
+ {
+ keywordHdr.Append(nsDependentCSubstring(msgHdrKeywords, curHdrLineStart, msgHdrKeywords.Length() - curHdrLineStart));
+ keywordHdr.Append(MSG_LINEBREAK);
+ m_fileStream->Write(keywordHdr.get(), keywordHdr.Length(), &bytesWritten);
+ newKeywordSize += bytesWritten;
+ curHdrLineStart = nextBlankOffset;
+ keywordHdr.Assign(' ');
+ }
+ nextBlankOffset++;
+ }
+ m_fileStream->Write(EXTRA_KEYWORD_HDR, sizeof(EXTRA_KEYWORD_HDR) - 1, &bytesWritten);
+ newKeywordSize += bytesWritten;
+ m_addedHeaderSize += newKeywordSize - oldKeywordSize;
+ m_curSrcHdr->SetUint32Property("growKeywords", 0);
+ needToGrowKeywords = false;
+ writeCount += blockOffset - preKeywordBlockOffset; // fudge writeCount
+
+ }
+ if (readCount <= blockOffset)
+ {
+ NS_ASSERTION(false, "bad block offset");
+ // not sure what to do to handle this.
+
+ }
+ m_fileStream->Write(m_dataBuffer + blockOffset, readCount - blockOffset, &bytesWritten);
+ writeCount += bytesWritten;
+ count -= readCount;
+ if (writeCount != readCount)
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+ }
+ return rv;
+}
+
+nsOfflineStoreCompactState::nsOfflineStoreCompactState()
+{
+}
+
+nsOfflineStoreCompactState::~nsOfflineStoreCompactState()
+{
+}
+
+
+nsresult
+nsOfflineStoreCompactState::InitDB(nsIMsgDatabase *db)
+{
+ // Start with the list of messages we have offline as the possible
+ // message to keep when compacting the offline store.
+ db->ListAllOfflineMsgs(m_keyArray);
+ m_size = m_keyArray->m_keys.Length();
+ m_db = db;
+ return NS_OK;
+}
+
+/**
+ * This will copy one message to the offline store, but if it fails to
+ * copy the next message, it will keep trying messages until it finds one
+ * it can copy, or it runs out of messages.
+ */
+nsresult nsOfflineStoreCompactState::CopyNextMessage(bool &done)
+{
+ while (m_curIndex < m_size)
+ {
+ // Filter out msgs that have the "pendingRemoval" attribute set.
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ nsString pendingRemoval;
+ nsresult rv = m_db->GetMsgHdrForKey(m_keyArray->m_keys[m_curIndex], getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ hdr->GetProperty("pendingRemoval", pendingRemoval);
+ if (!pendingRemoval.IsEmpty())
+ {
+ m_curIndex++;
+ // Turn off offline flag for message, since after the compact is completed;
+ // we won't have the message in the offline store.
+ uint32_t resultFlags;
+ hdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags);
+ // We need to clear this in case the user changes the offline retention
+ // settings.
+ hdr->SetStringProperty("pendingRemoval", "");
+ continue;
+ }
+ m_messageUri.Truncate(); // clear the previous message uri
+ rv = BuildMessageURI(m_baseMessageUri.get(), m_keyArray->m_keys[m_curIndex],
+ m_messageUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_startOfMsg = true;
+ nsCOMPtr<nsISupports> thisSupports;
+ QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(thisSupports));
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = m_messageService->StreamMessage(m_messageUri.get(), thisSupports, m_window, nullptr,
+ false, EmptyCString(), true, getter_AddRefs(dummyNull));
+ // if copy fails, we clear the offline flag on the source message.
+ if (NS_FAILED(rv))
+ {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ GetMessage(getter_AddRefs(hdr));
+ if (hdr)
+ {
+ uint32_t resultFlags;
+ hdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags);
+ }
+ m_curIndex++;
+ continue;
+ }
+ else
+ break;
+ }
+ done = m_curIndex >= m_size;
+ // In theory, we might be able to stream the next message, so
+ // return NS_OK, not rv.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineStoreCompactState::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
+ nsresult status)
+{
+ nsresult rv = status;
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr <nsIMsgStatusFeedback> statusFeedback;
+ bool done = false;
+
+ // The NS_MSG_ERROR_MSG_NOT_OFFLINE error should allow us to continue, so we
+ // check for it specifically and don't terminate the compaction.
+ if (NS_FAILED(rv) && rv != NS_MSG_ERROR_MSG_NOT_OFFLINE)
+ goto done;
+ uri = do_QueryInterface(ctxt, &rv);
+ if (NS_FAILED(rv)) goto done;
+ rv = GetMessage(getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv)) goto done;
+
+ // This is however an unexpected condition, so let's print a warning.
+ if (rv == NS_MSG_ERROR_MSG_NOT_OFFLINE) {
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ nsPrintfCString msg("Message expectedly not available offline: %s", spec.get());
+ NS_WARNING(msg.get());
+ }
+
+ if (msgHdr)
+ {
+ if (NS_SUCCEEDED(status))
+ {
+ msgHdr->SetMessageOffset(m_startOfNewMsg);
+ char storeToken[100];
+ PR_snprintf(storeToken, sizeof(storeToken), "%lld", m_startOfNewMsg);
+ msgHdr->SetStringProperty("storeToken", storeToken);
+ msgHdr->SetOfflineMessageSize(m_offlineMsgSize);
+ }
+ else
+ {
+ uint32_t resultFlags;
+ msgHdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags);
+ }
+ }
+
+ if (m_window)
+ {
+ m_window->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ if (statusFeedback)
+ statusFeedback->ShowProgress(100 * m_curIndex / m_size);
+ }
+ // advance to next message
+ m_curIndex++;
+ rv = CopyNextMessage(done);
+ if (done)
+ {
+ m_db->Commit(nsMsgDBCommitType::kCompressCommit);
+ msgHdr = nullptr;
+ // no more to copy finish it up
+ ReleaseFolderLock();
+ FinishCompact();
+ Release(); // kill self
+ }
+
+done:
+ if (NS_FAILED(rv)) {
+ m_status = rv; // set the status to rv so the destructor can remove the
+ // temp folder and database
+ ReleaseFolderLock();
+ Release(); // kill self
+ return rv;
+ }
+ return rv;
+}
+
+nsresult
+nsOfflineStoreCompactState::FinishCompact()
+{
+ // All okay time to finish up the compact process
+ nsCOMPtr<nsIFile> path;
+ uint32_t flags;
+
+ // get leaf name and database name of the folder
+ m_folder->GetFlags(&flags);
+ nsresult rv = m_folder->GetFilePath(getter_AddRefs(path));
+
+ nsCString leafName;
+ path->GetNativeLeafName(leafName);
+
+ if (m_fileStream)
+ {
+ // close down the temp file stream; preparing for deleting the old folder
+ // and its database; then rename the temp folder and database
+ m_fileStream->Flush();
+ m_fileStream->Close();
+ m_fileStream = nullptr;
+ }
+
+ // make sure the new database is valid
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo)
+ dbFolderInfo->SetExpungedBytes(0);
+ // this forces the m_folder to update mExpungedBytes from the db folder info.
+ int64_t expungedBytes;
+ m_folder->GetExpungedBytes(&expungedBytes);
+ m_folder->UpdateSummaryTotals(true);
+ m_db->SetSummaryValid(true);
+
+ // remove the old folder
+ path->Remove(false);
+
+ // rename the copied folder to be the original folder
+ m_file->MoveToNative((nsIFile *) nullptr, leafName);
+
+ ShowStatusMsg(EmptyString());
+ m_folder->NotifyCompactCompleted();
+ if (m_compactAll)
+ rv = CompactNextFolder();
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsFolderCompactState::Init(nsIMsgFolder *srcFolder, nsICopyMessageListener *destination, nsISupports *listenerData)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFolderCompactState::StartMessage()
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ NS_ASSERTION(m_fileStream, "Fatal, null m_fileStream...\n");
+ if (m_fileStream)
+ {
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(m_fileStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // this will force an internal flush, but not a sync. Tell should really do an internal flush,
+ // but it doesn't, and I'm afraid to change that nsIFileStream.cpp code anymore.
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, 0);
+ // record the new message key for the message
+ int64_t curStreamPos;
+ seekableStream->Tell(&curStreamPos);
+ m_startOfNewMsg = curStreamPos;
+ rv = NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFolderCompactState::EndMessage(nsMsgKey key)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFolderCompactState::EndCopy(nsISupports *url, nsresult aStatus)
+{
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
+
+ if (m_curIndex >= m_size)
+ {
+ NS_ASSERTION(false, "m_curIndex out of bounds");
+ return NS_OK;
+ }
+
+ /* Messages need to have trailing blank lines */
+ uint32_t bytesWritten;
+ (void) m_fileStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten);
+
+ /**
+ * Done with the current message; copying the existing message header
+ * to the new database.
+ */
+ if (m_curSrcHdr)
+ {
+ nsMsgKey key;
+ m_curSrcHdr->GetMessageKey(&key);
+ m_db->CopyHdrFromExistingHdr(key, m_curSrcHdr, true,
+ getter_AddRefs(newMsgHdr));
+ }
+ m_curSrcHdr = nullptr;
+ if (newMsgHdr)
+ {
+ if (m_statusOffset != 0)
+ newMsgHdr->SetStatusOffset(m_statusOffset);
+
+ char storeToken[100];
+ PR_snprintf(storeToken, sizeof(storeToken), "%lld", m_startOfNewMsg);
+ newMsgHdr->SetStringProperty("storeToken", storeToken);
+ newMsgHdr->SetMessageOffset(m_startOfNewMsg);
+
+ uint32_t msgSize;
+ (void) newMsgHdr->GetMessageSize(&msgSize);
+ if (m_addedHeaderSize)
+ {
+ msgSize += m_addedHeaderSize;
+ newMsgHdr->SetMessageSize(msgSize);
+ }
+ m_totalMsgSize += msgSize + MSG_LINEBREAK_LEN;
+ }
+
+// m_db->Commit(nsMsgDBCommitType::kLargeCommit); // no sense commiting until the end
+ // advance to next message
+ m_curIndex ++;
+ m_startOfMsg = true;
+ nsCOMPtr <nsIMsgStatusFeedback> statusFeedback;
+ if (m_window)
+ {
+ m_window->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ if (statusFeedback)
+ statusFeedback->ShowProgress(100 * m_curIndex / m_size);
+ }
+ return NS_OK;
+}
+
+nsresult nsOfflineStoreCompactState::StartCompacting()
+{
+ nsresult rv = NS_OK;
+ if (m_size > 0 && m_curIndex == 0)
+ {
+ AddRef(); // we own ourselves, until we're done, anyway.
+ ShowCompactingStatusMsg();
+ bool done = false;
+ rv = CopyNextMessage(done);
+ if (!done)
+ return rv;
+ }
+ ReleaseFolderLock();
+ FinishCompact();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsOfflineStoreCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ if (!m_fileStream || !inStr)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+
+ if (m_startOfMsg)
+ {
+ m_statusOffset = 0;
+ m_offlineMsgSize = 0;
+ m_messageUri.Truncate(); // clear the previous message uri
+ if (NS_SUCCEEDED(BuildMessageURI(m_baseMessageUri.get(), m_keyArray->m_keys[m_curIndex],
+ m_messageUri)))
+ {
+ rv = GetMessage(getter_AddRefs(m_curSrcHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ uint32_t maxReadCount, readCount, writeCount;
+ uint32_t bytesWritten;
+
+ while (NS_SUCCEEDED(rv) && (int32_t) count > 0)
+ {
+ maxReadCount = count > sizeof(m_dataBuffer) - 1 ? sizeof(m_dataBuffer) - 1 : count;
+ writeCount = 0;
+ rv = inStr->Read(m_dataBuffer, maxReadCount, &readCount);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ if (m_startOfMsg)
+ {
+ m_startOfMsg = false;
+ // check if there's an envelope header; if not, write one.
+ if (strncmp(m_dataBuffer, "From ", 5))
+ {
+ m_fileStream->Write("From " CRLF, 7, &bytesWritten);
+ m_offlineMsgSize += bytesWritten;
+ }
+ }
+ m_fileStream->Write(m_dataBuffer, readCount, &bytesWritten);
+ m_offlineMsgSize += bytesWritten;
+ writeCount += bytesWritten;
+ count -= readCount;
+ if (writeCount != readCount)
+ {
+ m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window);
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+ }
+ }
+ return rv;
+}
diff --git a/mailnews/base/src/nsMsgFolderCompactor.h b/mailnews/base/src/nsMsgFolderCompactor.h
new file mode 100644
index 000000000..50a69df81
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderCompactor.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgFolderCompactor_h
+#define _nsMsgFolderCompactor_h
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsIStreamListener.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsMsgKeyArray.h"
+#include "nsIMsgWindow.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgMessageService.h"
+
+#define COMPACTOR_READ_BUFF_SIZE 16384
+
+class nsFolderCompactState : public nsIMsgFolderCompactor,
+ public nsIStreamListener,
+ public nsICopyMessageStreamListener,
+ public nsIUrlListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICOPYMESSAGESTREAMLISTENER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGFOLDERCOMPACTOR
+
+ nsFolderCompactState(void);
+protected:
+ virtual ~nsFolderCompactState(void);
+
+ virtual nsresult InitDB(nsIMsgDatabase *db);
+ virtual nsresult StartCompacting();
+ virtual nsresult FinishCompact();
+ void CloseOutputStream();
+ void CleanupTempFilesAfterError();
+
+ nsresult Init(nsIMsgFolder *aFolder, const char* aBaseMsgUri, nsIMsgDatabase *aDb,
+ nsIFile *aPath, nsIMsgWindow *aMsgWindow);
+ nsresult GetMessage(nsIMsgDBHdr **message);
+ nsresult BuildMessageURI(const char *baseURI, nsMsgKey key, nsCString& uri);
+ nsresult ShowStatusMsg(const nsString& aMsg);
+ nsresult ReleaseFolderLock();
+ void ShowCompactingStatusMsg();
+ void CompactCompleted(nsresult exitCode);
+ void ShowDoneStatus();
+ nsresult CompactNextFolder();
+
+ nsCString m_baseMessageUri; // base message uri
+ nsCString m_messageUri; // current message uri being copy
+ nsCOMPtr<nsIMsgFolder> m_folder; // current folder being compact
+ nsCOMPtr<nsIMsgDatabase> m_db; // new database for the compact folder
+ nsCOMPtr <nsIFile> m_file; // new mailbox for the compact folder
+ nsCOMPtr <nsIOutputStream> m_fileStream; // output file stream for writing
+ // all message keys that need to be copied over
+ RefPtr<nsMsgKeyArray> m_keyArray;
+ uint32_t m_size;
+
+ // sum of the sizes of the messages, accumulated as we visit each msg.
+ uint64_t m_totalMsgSize;
+ // number of bytes that can be expunged while compacting.
+ uint64_t m_totalExpungedBytes;
+
+ uint32_t m_curIndex; // index of the current copied message key in key array
+ uint64_t m_startOfNewMsg; // offset in mailbox of new message
+ char m_dataBuffer[COMPACTOR_READ_BUFF_SIZE + 1]; // temp data buffer for copying message
+ nsresult m_status; // the status of the copying operation
+ nsCOMPtr <nsIMsgMessageService> m_messageService; // message service for copying
+ nsCOMPtr<nsIArray> m_folderArray; // folders we are compacting, if compacting multiple.
+ nsCOMPtr <nsIMsgWindow> m_window;
+ nsCOMPtr <nsIMsgDBHdr> m_curSrcHdr;
+ uint32_t m_folderIndex; // tells which folder to compact in case of compact all
+ bool m_compactAll; //flag for compact all
+ bool m_compactOfflineAlso; //whether to compact offline also
+ bool m_compactingOfflineFolders; // are we in the offline folder compact phase
+ bool m_parsingFolder; //flag for parsing local folders;
+ // these members are used to add missing status lines to compacted messages.
+ bool m_needStatusLine;
+ bool m_startOfMsg;
+ int32_t m_statusOffset;
+ uint32_t m_addedHeaderSize;
+ nsCOMPtr<nsIArray> m_offlineFolderArray;
+ nsCOMPtr<nsIUrlListener> m_listener;
+ bool m_alreadyWarnedDiskSpace;
+};
+
+class nsOfflineStoreCompactState : public nsFolderCompactState
+{
+public:
+
+ nsOfflineStoreCompactState(void);
+ virtual ~nsOfflineStoreCompactState(void);
+ NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports *ctxt,
+ nsresult status) override;
+ NS_IMETHODIMP OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count) override;
+
+protected:
+ nsresult CopyNextMessage(bool &done);
+ virtual nsresult InitDB(nsIMsgDatabase *db) override;
+ virtual nsresult StartCompacting() override;
+ virtual nsresult FinishCompact() override;
+
+ uint32_t m_offlineMsgSize;
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgFolderDataSource.cpp b/mailnews/base/src/nsMsgFolderDataSource.cpp
new file mode 100644
index 000000000..391596ab4
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderDataSource.cpp
@@ -0,0 +1,2475 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "prlog.h"
+#include "prmem.h"
+#include "nsMsgFolderDataSource.h"
+#include "nsMsgFolderFlags.h"
+
+#include "nsMsgUtils.h"
+#include "nsMsgRDFUtils.h"
+
+#include "rdf.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsIRDFNode.h"
+#include "nsEnumeratorUtils.h"
+
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+
+#include "nsIMsgMailSession.h"
+#include "nsIMsgCopyService.h"
+#include "nsMsgBaseCID.h"
+#include "nsIInputStream.h"
+#include "nsIMsgHdr.h"
+#include "nsTraceRefcnt.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsIMutableArray.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsINntpIncomingServer.h"
+#include "nsTextFormatter.h"
+#include "nsIStringBundle.h"
+#include "nsIPrompt.h"
+#include "nsIMsgAccountManager.h"
+#include "nsArrayEnumerator.h"
+#include "nsArrayUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
+
+nsIRDFResource* nsMsgFolderDataSource::kNC_Child = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_Folder= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_Name= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_Open = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_FolderTreeName= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_FolderTreeSimpleName= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_NameSort= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_FolderTreeNameSort= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_SpecialFolder= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_ServerType = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_IsDeferred = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_CanCreateFoldersOnServer = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_CanFileMessagesOnServer = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_IsServer = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_IsSecure = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_CanSubscribe = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_SupportsOffline = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_CanFileMessages = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_CanCreateSubfolders = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_CanRename = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_CanCompact = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_TotalMessages= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_TotalUnreadMessages= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_FolderSize = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_Charset = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_BiffState = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_HasUnreadMessages = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_NewMessages = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_SubfoldersHaveUnreadMessages = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_NoSelect = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_VirtualFolder = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_InVFEditSearchScope = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_ImapShared = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_Synchronize = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_SyncDisabled = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_CanSearchMessages = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_UnreadFolders = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_FavoriteFolders = nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_RecentFolders = nullptr;
+
+// commands
+nsIRDFResource* nsMsgFolderDataSource::kNC_Delete= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_ReallyDelete= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_NewFolder= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_GetNewMessages= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_Copy= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_Move= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_CopyFolder= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_MoveFolder= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_MarkAllMessagesRead= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_Compact= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_CompactAll= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_Rename= nullptr;
+nsIRDFResource* nsMsgFolderDataSource::kNC_EmptyTrash= nullptr;
+
+nsrefcnt nsMsgFolderDataSource::gFolderResourceRefCnt = 0;
+
+nsIAtom * nsMsgFolderDataSource::kBiffStateAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kSortOrderAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kNewMessagesAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kTotalMessagesAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kTotalUnreadMessagesAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kFolderSizeAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kNameAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kSynchronizeAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kOpenAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kIsDeferredAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kIsSecureAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kCanFileMessagesAtom = nullptr;
+nsIAtom * nsMsgFolderDataSource::kInVFEditSearchScopeAtom = nullptr;
+
+static const uint32_t kDisplayBlankCount = 0xFFFFFFFE;
+static const uint32_t kDisplayQuestionCount = 0xFFFFFFFF;
+static const int64_t kDisplayBlankCount64 = -2;
+static const int64_t kDisplayQuestionCount64 = -1;
+
+nsMsgFolderDataSource::nsMsgFolderDataSource()
+{
+ // one-time initialization here
+ nsIRDFService* rdf = getRDFService();
+
+ if (gFolderResourceRefCnt++ == 0) {
+ nsCOMPtr<nsIStringBundle> sMessengerStringBundle;
+
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CHILD), &kNC_Child);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDER), &kNC_Folder);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_NAME), &kNC_Name);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_OPEN), &kNC_Open);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREENAME), &kNC_FolderTreeName);
+ rdf->GetResource(NS_LITERAL_CSTRING("mailnewsunreadfolders:/"), &kNC_UnreadFolders);
+ rdf->GetResource(NS_LITERAL_CSTRING("mailnewsfavefolders:/"), &kNC_FavoriteFolders);
+ rdf->GetResource(NS_LITERAL_CSTRING("mailnewsrecentfolders:/"), &kNC_RecentFolders);
+
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREESIMPLENAME), &kNC_FolderTreeSimpleName);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_NAME_SORT), &kNC_NameSort);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREENAME_SORT), &kNC_FolderTreeNameSort);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SPECIALFOLDER), &kNC_SpecialFolder);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SERVERTYPE), &kNC_ServerType);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_ISDEFERRED),&kNC_IsDeferred);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANCREATEFOLDERSONSERVER), &kNC_CanCreateFoldersOnServer);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANFILEMESSAGESONSERVER), &kNC_CanFileMessagesOnServer);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_ISSERVER), &kNC_IsServer);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_ISSECURE), &kNC_IsSecure);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANSUBSCRIBE), &kNC_CanSubscribe);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SUPPORTSOFFLINE), &kNC_SupportsOffline);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANFILEMESSAGES), &kNC_CanFileMessages);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANCREATESUBFOLDERS), &kNC_CanCreateSubfolders);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANRENAME), &kNC_CanRename);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANCOMPACT), &kNC_CanCompact);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_TOTALMESSAGES), &kNC_TotalMessages);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_TOTALUNREADMESSAGES), &kNC_TotalUnreadMessages);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERSIZE), &kNC_FolderSize);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CHARSET), &kNC_Charset);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_BIFFSTATE), &kNC_BiffState);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_HASUNREADMESSAGES), &kNC_HasUnreadMessages);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_NEWMESSAGES), &kNC_NewMessages);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SUBFOLDERSHAVEUNREADMESSAGES), &kNC_SubfoldersHaveUnreadMessages);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_NOSELECT), &kNC_NoSelect);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_VIRTUALFOLDER), &kNC_VirtualFolder);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_INVFEDITSEARCHSCOPE), &kNC_InVFEditSearchScope);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_IMAPSHARED), &kNC_ImapShared);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SYNCHRONIZE), &kNC_Synchronize);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SYNCDISABLED), &kNC_SyncDisabled);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANSEARCHMESSAGES), &kNC_CanSearchMessages);
+
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_DELETE), &kNC_Delete);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_REALLY_DELETE), &kNC_ReallyDelete);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_NEWFOLDER), &kNC_NewFolder);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_GETNEWMESSAGES), &kNC_GetNewMessages);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_COPY), &kNC_Copy);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_MOVE), &kNC_Move);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_COPYFOLDER), &kNC_CopyFolder);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_MOVEFOLDER), &kNC_MoveFolder);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_MARKALLMESSAGESREAD),
+ &kNC_MarkAllMessagesRead);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_COMPACT), &kNC_Compact);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_COMPACTALL), &kNC_CompactAll);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_RENAME), &kNC_Rename);
+ rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_EMPTYTRASH), &kNC_EmptyTrash);
+
+ kTotalMessagesAtom = MsgNewAtom("TotalMessages").take();
+ kTotalUnreadMessagesAtom = MsgNewAtom("TotalUnreadMessages").take();
+ kFolderSizeAtom = MsgNewAtom("FolderSize").take();
+ kBiffStateAtom = MsgNewAtom("BiffState").take();
+ kSortOrderAtom = MsgNewAtom("SortOrder").take();
+ kNewMessagesAtom = MsgNewAtom("NewMessages").take();
+ kNameAtom = MsgNewAtom("Name").take();
+ kSynchronizeAtom = MsgNewAtom("Synchronize").take();
+ kOpenAtom = MsgNewAtom("open").take();
+ kIsDeferredAtom = MsgNewAtom("isDeferred").take();
+ kIsSecureAtom = MsgNewAtom("isSecure").take();
+ kCanFileMessagesAtom = MsgNewAtom("canFileMessages").take();
+ kInVFEditSearchScopeAtom = MsgNewAtom("inVFEditSearchScope").take();
+ }
+
+ CreateLiterals(rdf);
+ CreateArcsOutEnumerator();
+}
+
+nsMsgFolderDataSource::~nsMsgFolderDataSource (void)
+{
+ if (--gFolderResourceRefCnt == 0)
+ {
+ nsrefcnt refcnt;
+ NS_RELEASE2(kNC_Child, refcnt);
+ NS_RELEASE2(kNC_Folder, refcnt);
+ NS_RELEASE2(kNC_Name, refcnt);
+ NS_RELEASE2(kNC_Open, refcnt);
+ NS_RELEASE2(kNC_FolderTreeName, refcnt);
+ NS_RELEASE2(kNC_FolderTreeSimpleName, refcnt);
+ NS_RELEASE2(kNC_NameSort, refcnt);
+ NS_RELEASE2(kNC_FolderTreeNameSort, refcnt);
+ NS_RELEASE2(kNC_SpecialFolder, refcnt);
+ NS_RELEASE2(kNC_ServerType, refcnt);
+ NS_RELEASE2(kNC_IsDeferred, refcnt);
+ NS_RELEASE2(kNC_CanCreateFoldersOnServer, refcnt);
+ NS_RELEASE2(kNC_CanFileMessagesOnServer, refcnt);
+ NS_RELEASE2(kNC_IsServer, refcnt);
+ NS_RELEASE2(kNC_IsSecure, refcnt);
+ NS_RELEASE2(kNC_CanSubscribe, refcnt);
+ NS_RELEASE2(kNC_SupportsOffline, refcnt);
+ NS_RELEASE2(kNC_CanFileMessages, refcnt);
+ NS_RELEASE2(kNC_CanCreateSubfolders, refcnt);
+ NS_RELEASE2(kNC_CanRename, refcnt);
+ NS_RELEASE2(kNC_CanCompact, refcnt);
+ NS_RELEASE2(kNC_TotalMessages, refcnt);
+ NS_RELEASE2(kNC_TotalUnreadMessages, refcnt);
+ NS_RELEASE2(kNC_FolderSize, refcnt);
+ NS_RELEASE2(kNC_Charset, refcnt);
+ NS_RELEASE2(kNC_BiffState, refcnt);
+ NS_RELEASE2(kNC_HasUnreadMessages, refcnt);
+ NS_RELEASE2(kNC_NewMessages, refcnt);
+ NS_RELEASE2(kNC_SubfoldersHaveUnreadMessages, refcnt);
+ NS_RELEASE2(kNC_NoSelect, refcnt);
+ NS_RELEASE2(kNC_VirtualFolder, refcnt);
+ NS_RELEASE2(kNC_InVFEditSearchScope, refcnt);
+ NS_RELEASE2(kNC_ImapShared, refcnt);
+ NS_RELEASE2(kNC_Synchronize, refcnt);
+ NS_RELEASE2(kNC_SyncDisabled, refcnt);
+ NS_RELEASE2(kNC_CanSearchMessages, refcnt);
+
+ NS_RELEASE2(kNC_Delete, refcnt);
+ NS_RELEASE2(kNC_ReallyDelete, refcnt);
+ NS_RELEASE2(kNC_NewFolder, refcnt);
+ NS_RELEASE2(kNC_GetNewMessages, refcnt);
+ NS_RELEASE2(kNC_Copy, refcnt);
+ NS_RELEASE2(kNC_Move, refcnt);
+ NS_RELEASE2(kNC_CopyFolder, refcnt);
+ NS_RELEASE2(kNC_MoveFolder, refcnt);
+ NS_RELEASE2(kNC_MarkAllMessagesRead, refcnt);
+ NS_RELEASE2(kNC_Compact, refcnt);
+ NS_RELEASE2(kNC_CompactAll, refcnt);
+ NS_RELEASE2(kNC_Rename, refcnt);
+ NS_RELEASE2(kNC_EmptyTrash, refcnt);
+ NS_RELEASE2(kNC_UnreadFolders, refcnt);
+ NS_RELEASE2(kNC_FavoriteFolders, refcnt);
+ NS_RELEASE2(kNC_RecentFolders, refcnt);
+
+ NS_RELEASE(kTotalMessagesAtom);
+ NS_RELEASE(kTotalUnreadMessagesAtom);
+ NS_RELEASE(kFolderSizeAtom);
+ NS_RELEASE(kBiffStateAtom);
+ NS_RELEASE(kSortOrderAtom);
+ NS_RELEASE(kNewMessagesAtom);
+ NS_RELEASE(kNameAtom);
+ NS_RELEASE(kSynchronizeAtom);
+ NS_RELEASE(kOpenAtom);
+ NS_RELEASE(kIsDeferredAtom);
+ NS_RELEASE(kIsSecureAtom);
+ NS_RELEASE(kCanFileMessagesAtom);
+ NS_RELEASE(kInVFEditSearchScopeAtom);
+ }
+}
+
+nsresult nsMsgFolderDataSource::CreateLiterals(nsIRDFService *rdf)
+{
+ createNode(u"true",
+ getter_AddRefs(kTrueLiteral), rdf);
+ createNode(u"false",
+ getter_AddRefs(kFalseLiteral), rdf);
+
+ return NS_OK;
+}
+
+nsresult nsMsgFolderDataSource::Init()
+{
+ nsresult rv;
+
+ rv = nsMsgRDFDataSource::Init();
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+
+ if(NS_SUCCEEDED(rv))
+ mailSession->AddFolderListener(this,
+ nsIFolderListener::added |
+ nsIFolderListener::removed |
+ nsIFolderListener::intPropertyChanged |
+ nsIFolderListener::boolPropertyChanged |
+ nsIFolderListener::unicharPropertyChanged);
+
+ return NS_OK;
+}
+
+void nsMsgFolderDataSource::Cleanup()
+{
+ nsresult rv;
+ if (!m_shuttingDown)
+ {
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+
+ if(NS_SUCCEEDED(rv))
+ mailSession->RemoveFolderListener(this);
+ }
+
+ nsMsgRDFDataSource::Cleanup();
+}
+
+nsresult nsMsgFolderDataSource::CreateArcsOutEnumerator()
+{
+ nsresult rv;
+
+ rv = getFolderArcLabelsOut(kFolderArcsOutArray);
+ if(NS_FAILED(rv)) return rv;
+
+ return rv;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsMsgFolderDataSource, nsMsgRDFDataSource)
+NS_IMPL_RELEASE_INHERITED(nsMsgFolderDataSource, nsMsgRDFDataSource)
+
+NS_IMPL_QUERY_INTERFACE_INHERITED(nsMsgFolderDataSource, nsMsgRDFDataSource, nsIFolderListener)
+
+ // nsIRDFDataSource methods
+NS_IMETHODIMP nsMsgFolderDataSource::GetURI(char* *uri)
+{
+ if ((*uri = strdup("rdf:mailnewsfolders")) == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ else
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderDataSource::GetSource(nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ nsIRDFResource** source /* out */)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgFolderDataSource::GetTarget(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsIRDFNode** target)
+{
+ nsresult rv = NS_RDF_NO_VALUE;
+
+ // we only have positive assertions in the mail data source.
+ if (! tv)
+ return NS_RDF_NO_VALUE;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source));
+ if (folder)
+ rv = createFolderNode(folder, property, target);
+ else
+ return NS_RDF_NO_VALUE;
+ return rv;
+}
+
+
+NS_IMETHODIMP nsMsgFolderDataSource::GetSources(nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ nsISimpleEnumerator** sources)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+NS_IMETHODIMP nsMsgFolderDataSource::GetTargets(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsISimpleEnumerator** targets)
+{
+ nsresult rv = NS_RDF_NO_VALUE;
+ if(!targets)
+ return NS_ERROR_NULL_POINTER;
+
+ *targets = nullptr;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ if (kNC_Child == property)
+ {
+ rv = folder->GetSubFolders(targets);
+ }
+ else if ((kNC_Name == property) ||
+ (kNC_Open == property) ||
+ (kNC_FolderTreeName == property) ||
+ (kNC_FolderTreeSimpleName == property) ||
+ (kNC_SpecialFolder == property) ||
+ (kNC_IsServer == property) ||
+ (kNC_IsSecure == property) ||
+ (kNC_CanSubscribe == property) ||
+ (kNC_SupportsOffline == property) ||
+ (kNC_CanFileMessages == property) ||
+ (kNC_CanCreateSubfolders == property) ||
+ (kNC_CanRename == property) ||
+ (kNC_CanCompact == property) ||
+ (kNC_ServerType == property) ||
+ (kNC_IsDeferred == property) ||
+ (kNC_CanCreateFoldersOnServer == property) ||
+ (kNC_CanFileMessagesOnServer == property) ||
+ (kNC_NoSelect == property) ||
+ (kNC_VirtualFolder == property) ||
+ (kNC_InVFEditSearchScope == property) ||
+ (kNC_ImapShared == property) ||
+ (kNC_Synchronize == property) ||
+ (kNC_SyncDisabled == property) ||
+ (kNC_CanSearchMessages == property))
+ {
+ return NS_NewSingletonEnumerator(targets, property);
+ }
+ }
+ if(!*targets)
+ {
+ //create empty cursor
+ rv = NS_NewEmptyEnumerator(targets);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgFolderDataSource::Assert(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv));
+ //We don't handle tv = false at the moment.
+ if(NS_SUCCEEDED(rv) && tv)
+ return DoFolderAssert(folder, property, target);
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgFolderDataSource::Unassert(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return DoFolderUnassert(folder, property, target);
+}
+
+
+NS_IMETHODIMP nsMsgFolderDataSource::HasAssertion(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ bool* hasAssertion)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv));
+ if(NS_SUCCEEDED(rv))
+ return DoFolderHasAssertion(folder, property, target, tv, hasAssertion);
+ else
+ *hasAssertion = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(aSource, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ *result = (aArc == kNC_Name ||
+ aArc == kNC_Open ||
+ aArc == kNC_FolderTreeName ||
+ aArc == kNC_FolderTreeSimpleName ||
+ aArc == kNC_SpecialFolder ||
+ aArc == kNC_ServerType ||
+ aArc == kNC_IsDeferred ||
+ aArc == kNC_CanCreateFoldersOnServer ||
+ aArc == kNC_CanFileMessagesOnServer ||
+ aArc == kNC_IsServer ||
+ aArc == kNC_IsSecure ||
+ aArc == kNC_CanSubscribe ||
+ aArc == kNC_SupportsOffline ||
+ aArc == kNC_CanFileMessages ||
+ aArc == kNC_CanCreateSubfolders ||
+ aArc == kNC_CanRename ||
+ aArc == kNC_CanCompact ||
+ aArc == kNC_TotalMessages ||
+ aArc == kNC_TotalUnreadMessages ||
+ aArc == kNC_FolderSize ||
+ aArc == kNC_Charset ||
+ aArc == kNC_BiffState ||
+ aArc == kNC_Child ||
+ aArc == kNC_NoSelect ||
+ aArc == kNC_VirtualFolder ||
+ aArc == kNC_InVFEditSearchScope ||
+ aArc == kNC_ImapShared ||
+ aArc == kNC_Synchronize ||
+ aArc == kNC_SyncDisabled ||
+ aArc == kNC_CanSearchMessages);
+ }
+ else
+ {
+ *result = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderDataSource::ArcLabelsIn(nsIRDFNode* node,
+ nsISimpleEnumerator** labels)
+{
+ return nsMsgRDFDataSource::ArcLabelsIn(node, labels);
+}
+
+NS_IMETHODIMP nsMsgFolderDataSource::ArcLabelsOut(nsIRDFResource* source,
+ nsISimpleEnumerator** labels)
+{
+ nsresult rv = NS_RDF_NO_VALUE;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = NS_NewArrayEnumerator(labels, kFolderArcsOutArray);
+ }
+ else
+ {
+ rv = NS_NewEmptyEnumerator(labels);
+ }
+
+ return rv;
+}
+
+nsresult
+nsMsgFolderDataSource::getFolderArcLabelsOut(nsCOMArray<nsIRDFResource> &aArcs)
+{
+ aArcs.AppendObject(kNC_Name);
+ aArcs.AppendObject(kNC_Open);
+ aArcs.AppendObject(kNC_FolderTreeName);
+ aArcs.AppendObject(kNC_FolderTreeSimpleName);
+ aArcs.AppendObject(kNC_SpecialFolder);
+ aArcs.AppendObject(kNC_ServerType);
+ aArcs.AppendObject(kNC_IsDeferred);
+ aArcs.AppendObject(kNC_CanCreateFoldersOnServer);
+ aArcs.AppendObject(kNC_CanFileMessagesOnServer);
+ aArcs.AppendObject(kNC_IsServer);
+ aArcs.AppendObject(kNC_IsSecure);
+ aArcs.AppendObject(kNC_CanSubscribe);
+ aArcs.AppendObject(kNC_SupportsOffline);
+ aArcs.AppendObject(kNC_CanFileMessages);
+ aArcs.AppendObject(kNC_CanCreateSubfolders);
+ aArcs.AppendObject(kNC_CanRename);
+ aArcs.AppendObject(kNC_CanCompact);
+ aArcs.AppendObject(kNC_TotalMessages);
+ aArcs.AppendObject(kNC_TotalUnreadMessages);
+ aArcs.AppendObject(kNC_FolderSize);
+ aArcs.AppendObject(kNC_Charset);
+ aArcs.AppendObject(kNC_BiffState);
+ aArcs.AppendObject(kNC_Child);
+ aArcs.AppendObject(kNC_NoSelect);
+ aArcs.AppendObject(kNC_VirtualFolder);
+ aArcs.AppendObject(kNC_InVFEditSearchScope);
+ aArcs.AppendObject(kNC_ImapShared);
+ aArcs.AppendObject(kNC_Synchronize);
+ aArcs.AppendObject(kNC_SyncDisabled);
+ aArcs.AppendObject(kNC_CanSearchMessages);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::GetAllResources(nsISimpleEnumerator** aCursor)
+{
+ NS_NOTYETIMPLEMENTED("sorry!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::GetAllCmds(nsIRDFResource* source,
+ nsISimpleEnumerator/*<nsIRDFResource>*/** commands)
+{
+ NS_NOTYETIMPLEMENTED("no one actually uses me");
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMutableArray> cmds =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+ NS_ENSURE_STATE(cmds);
+
+ cmds->AppendElement(kNC_Delete, false);
+ cmds->AppendElement(kNC_ReallyDelete, false);
+ cmds->AppendElement(kNC_NewFolder, false);
+ cmds->AppendElement(kNC_GetNewMessages, false);
+ cmds->AppendElement(kNC_Copy, false);
+ cmds->AppendElement(kNC_Move, false);
+ cmds->AppendElement(kNC_CopyFolder, false);
+ cmds->AppendElement(kNC_MoveFolder, false);
+ cmds->AppendElement(kNC_MarkAllMessagesRead, false);
+ cmds->AppendElement(kNC_Compact, false);
+ cmds->AppendElement(kNC_CompactAll, false);
+ cmds->AppendElement(kNC_Rename, false);
+ cmds->AppendElement(kNC_EmptyTrash, false);
+
+ return cmds->Enumerate(commands);
+}
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::IsCommandEnabled(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments,
+ bool* aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ nsCOMPtr<nsISupportsArray> sources = do_QueryInterface(aSources);
+ NS_ENSURE_STATE(sources);
+
+ uint32_t cnt;
+ rv = sources->Count(&cnt);
+ if (NS_FAILED(rv)) return rv;
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ folder = do_QueryElementAt(sources, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ // we don't care about the arguments -- folder commands are always enabled
+ if (!((aCommand == kNC_Delete) ||
+ (aCommand == kNC_ReallyDelete) ||
+ (aCommand == kNC_NewFolder) ||
+ (aCommand == kNC_Copy) ||
+ (aCommand == kNC_Move) ||
+ (aCommand == kNC_CopyFolder) ||
+ (aCommand == kNC_MoveFolder) ||
+ (aCommand == kNC_GetNewMessages) ||
+ (aCommand == kNC_MarkAllMessagesRead) ||
+ (aCommand == kNC_Compact) ||
+ (aCommand == kNC_CompactAll) ||
+ (aCommand == kNC_Rename) ||
+ (aCommand == kNC_EmptyTrash)))
+ {
+ *aResult = false;
+ return NS_OK;
+ }
+ }
+ }
+ *aResult = true;
+ return NS_OK; // succeeded for all sources
+}
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::DoCommand(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIMsgWindow> window;
+
+ nsCOMPtr<nsISupportsArray> sources = do_QueryInterface(aSources);
+ NS_ENSURE_STATE(sources);
+ nsCOMPtr<nsISupportsArray> arguments = do_QueryInterface(aArguments);
+
+ // callers can pass in the msgWindow as the last element of the arguments
+ // array. If they do, we'll use that as the msg window for progress, etc.
+ if (arguments)
+ {
+ uint32_t numArgs;
+ arguments->Count(&numArgs);
+ if (numArgs > 1)
+ window = do_QueryElementAt(arguments, numArgs - 1);
+ }
+ if (!window)
+ window = mWindow;
+
+ // XXX need to handle batching of command applied to all sources
+
+ uint32_t cnt = 0;
+ uint32_t i = 0;
+
+ rv = sources->Count(&cnt);
+ if (NS_FAILED(rv)) return rv;
+
+ for ( ; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(sources, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (aCommand == kNC_Delete)
+ {
+ rv = DoDeleteFromFolder(folder, arguments, window, false);
+ }
+ if (aCommand == kNC_ReallyDelete)
+ {
+ rv = DoDeleteFromFolder(folder, arguments, window, true);
+ }
+ else if (aCommand == kNC_NewFolder)
+ {
+ rv = DoNewFolder(folder, arguments, window);
+ }
+ else if (aCommand == kNC_GetNewMessages)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(arguments, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = server->GetNewMessages(folder, window, nullptr);
+ }
+ else if (aCommand == kNC_Copy)
+ {
+ rv = DoCopyToFolder(folder, arguments, window, false);
+ }
+ else if (aCommand == kNC_Move)
+ {
+ rv = DoCopyToFolder(folder, arguments, window, true);
+ }
+ else if (aCommand == kNC_CopyFolder)
+ {
+ rv = DoFolderCopyToFolder(folder, arguments, window, false);
+ }
+ else if (aCommand == kNC_MoveFolder)
+ {
+ rv = DoFolderCopyToFolder(folder, arguments, window, true);
+ }
+ else if (aCommand == kNC_MarkAllMessagesRead)
+ {
+ rv = folder->MarkAllMessagesRead(window);
+ }
+ else if (aCommand == kNC_Compact)
+ {
+ rv = folder->Compact(nullptr, window);
+ }
+ else if (aCommand == kNC_CompactAll)
+ {
+ // this will also compact offline stores for IMAP
+ rv = folder->CompactAll(nullptr, window, true);
+ }
+ else if (aCommand == kNC_EmptyTrash)
+ {
+ rv = folder->EmptyTrash(window, nullptr);
+ }
+ else if (aCommand == kNC_Rename)
+ {
+ nsCOMPtr<nsIRDFLiteral> literal = do_QueryElementAt(arguments, 0, &rv);
+ if(NS_SUCCEEDED(rv))
+ {
+ nsString name;
+ literal->GetValue(getter_Copies(name));
+ rv = folder->Rename(name, window);
+ }
+ }
+ }
+ else
+ {
+ rv = NS_ERROR_NOT_IMPLEMENTED;
+ }
+ }
+ //for the moment return NS_OK, because failure stops entire DoCommand process.
+ return rv;
+ //return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderDataSource::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ return OnItemAddedOrRemoved(parentItem, item, true);
+}
+
+NS_IMETHODIMP nsMsgFolderDataSource::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ return OnItemAddedOrRemoved(parentItem, item, false);
+}
+
+nsresult nsMsgFolderDataSource::OnItemAddedOrRemoved(nsIMsgFolder *parentItem, nsISupports *item, bool added)
+{
+ nsCOMPtr<nsIRDFNode> itemNode(do_QueryInterface(item));
+ if (itemNode)
+ {
+ nsCOMPtr<nsIRDFResource> parentResource(do_QueryInterface(parentItem));
+ if (parentResource) // RDF is not happy about a null parent resource.
+ NotifyObservers(parentResource, kNC_Child, itemNode, nullptr, added, false);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::OnItemPropertyChanged(nsIMsgFolder *resource,
+ nsIAtom *property,
+ const char *oldValue,
+ const char *newValue)
+
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::OnItemIntPropertyChanged(nsIMsgFolder *folder,
+ nsIAtom *property,
+ int64_t oldValue,
+ int64_t newValue)
+{
+ nsCOMPtr<nsIRDFResource> resource(do_QueryInterface(folder));
+ if (kTotalMessagesAtom == property)
+ OnTotalMessagePropertyChanged(resource, oldValue, newValue);
+ else if (kTotalUnreadMessagesAtom == property)
+ OnUnreadMessagePropertyChanged(resource, oldValue, newValue);
+ else if (kFolderSizeAtom == property)
+ OnFolderSizePropertyChanged(resource, oldValue, newValue);
+ else if (kSortOrderAtom == property)
+ OnFolderSortOrderPropertyChanged(resource, oldValue, newValue);
+ else if (kBiffStateAtom == property) {
+ // be careful about skipping if oldValue == newValue
+ // see the comment in nsMsgFolder::SetBiffState() about filters
+
+ nsCOMPtr<nsIRDFNode> biffNode;
+ nsresult rv = createBiffStateNodeFromFlag(newValue, getter_AddRefs(biffNode));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NotifyPropertyChanged(resource, kNC_BiffState, biffNode);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::OnItemUnicharPropertyChanged(nsIMsgFolder *folder,
+ nsIAtom *property,
+ const char16_t *oldValue,
+ const char16_t *newValue)
+{
+ nsCOMPtr<nsIRDFResource> resource(do_QueryInterface(folder));
+ if (kNameAtom == property)
+ {
+ int32_t numUnread;
+ folder->GetNumUnread(false, &numUnread);
+ NotifyFolderTreeNameChanged(folder, resource, numUnread);
+ NotifyFolderTreeSimpleNameChanged(folder, resource);
+ NotifyFolderNameChanged(folder, resource);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::OnItemBoolPropertyChanged(nsIMsgFolder *folder,
+ nsIAtom *property,
+ bool oldValue,
+ bool newValue)
+{
+ nsCOMPtr<nsIRDFResource> resource(do_QueryInterface(folder));
+ if (newValue != oldValue) {
+ nsIRDFNode* literalNode = newValue?kTrueLiteral:kFalseLiteral;
+ nsIRDFNode* oldLiteralNode = oldValue?kTrueLiteral:kFalseLiteral;
+ if (kNewMessagesAtom == property)
+ NotifyPropertyChanged(resource, kNC_NewMessages, literalNode);
+ else if (kSynchronizeAtom == property)
+ NotifyPropertyChanged(resource, kNC_Synchronize, literalNode);
+ else if (kOpenAtom == property)
+ NotifyPropertyChanged(resource, kNC_Open, literalNode);
+ else if (kIsDeferredAtom == property)
+ NotifyPropertyChanged(resource, kNC_IsDeferred, literalNode, oldLiteralNode);
+ else if (kIsSecureAtom == property)
+ NotifyPropertyChanged(resource, kNC_IsSecure, literalNode, oldLiteralNode);
+ else if (kCanFileMessagesAtom == property)
+ NotifyPropertyChanged(resource, kNC_CanFileMessages, literalNode, oldLiteralNode);
+ else if (kInVFEditSearchScopeAtom == property)
+ NotifyPropertyChanged(resource, kNC_InVFEditSearchScope, literalNode);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::OnItemPropertyFlagChanged(nsIMsgDBHdr *item,
+ nsIAtom *property,
+ uint32_t oldFlag,
+ uint32_t newFlag)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFolderDataSource::OnItemEvent(nsIMsgFolder *aFolder, nsIAtom *aEvent)
+{
+ return NS_OK;
+}
+
+
+nsresult nsMsgFolderDataSource::createFolderNode(nsIMsgFolder* folder,
+ nsIRDFResource* property,
+ nsIRDFNode** target)
+{
+ nsresult rv = NS_RDF_NO_VALUE;
+
+ if (kNC_NameSort == property)
+ rv = createFolderNameNode(folder, target, true);
+ else if(kNC_FolderTreeNameSort == property)
+ rv = createFolderNameNode(folder, target, true);
+ else if (kNC_Name == property)
+ rv = createFolderNameNode(folder, target, false);
+ else if(kNC_Open == property)
+ rv = createFolderOpenNode(folder, target);
+ else if (kNC_FolderTreeName == property)
+ rv = createFolderTreeNameNode(folder, target);
+ else if (kNC_FolderTreeSimpleName == property)
+ rv = createFolderTreeSimpleNameNode(folder, target);
+ else if (kNC_SpecialFolder == property)
+ rv = createFolderSpecialNode(folder,target);
+ else if (kNC_ServerType == property)
+ rv = createFolderServerTypeNode(folder, target);
+ else if (kNC_IsDeferred == property)
+ rv = createServerIsDeferredNode(folder, target);
+ else if (kNC_CanCreateFoldersOnServer == property)
+ rv = createFolderCanCreateFoldersOnServerNode(folder, target);
+ else if (kNC_CanFileMessagesOnServer == property)
+ rv = createFolderCanFileMessagesOnServerNode(folder, target);
+ else if (kNC_IsServer == property)
+ rv = createFolderIsServerNode(folder, target);
+ else if (kNC_IsSecure == property)
+ rv = createFolderIsSecureNode(folder, target);
+ else if (kNC_CanSubscribe == property)
+ rv = createFolderCanSubscribeNode(folder, target);
+ else if (kNC_SupportsOffline == property)
+ rv = createFolderSupportsOfflineNode(folder, target);
+ else if (kNC_CanFileMessages == property)
+ rv = createFolderCanFileMessagesNode(folder, target);
+ else if (kNC_CanCreateSubfolders == property)
+ rv = createFolderCanCreateSubfoldersNode(folder, target);
+ else if (kNC_CanRename == property)
+ rv = createFolderCanRenameNode(folder, target);
+ else if (kNC_CanCompact == property)
+ rv = createFolderCanCompactNode(folder, target);
+ else if (kNC_TotalMessages == property)
+ rv = createTotalMessagesNode(folder, target);
+ else if (kNC_TotalUnreadMessages == property)
+ rv = createUnreadMessagesNode(folder, target);
+ else if (kNC_FolderSize == property)
+ rv = createFolderSizeNode(folder, target);
+ else if (kNC_Charset == property)
+ rv = createCharsetNode(folder, target);
+ else if (kNC_BiffState == property)
+ rv = createBiffStateNodeFromFolder(folder, target);
+ else if (kNC_HasUnreadMessages == property)
+ rv = createHasUnreadMessagesNode(folder, false, target);
+ else if (kNC_NewMessages == property)
+ rv = createNewMessagesNode(folder, target);
+ else if (kNC_SubfoldersHaveUnreadMessages == property)
+ rv = createHasUnreadMessagesNode(folder, true, target);
+ else if (kNC_Child == property)
+ rv = createFolderChildNode(folder, target);
+ else if (kNC_NoSelect == property)
+ rv = createFolderNoSelectNode(folder, target);
+ else if (kNC_VirtualFolder == property)
+ rv = createFolderVirtualNode(folder, target);
+ else if (kNC_InVFEditSearchScope == property)
+ rv = createInVFEditSearchScopeNode(folder, target);
+ else if (kNC_ImapShared == property)
+ rv = createFolderImapSharedNode(folder, target);
+ else if (kNC_Synchronize == property)
+ rv = createFolderSynchronizeNode(folder, target);
+ else if (kNC_SyncDisabled == property)
+ rv = createFolderSyncDisabledNode(folder, target);
+ else if (kNC_CanSearchMessages == property)
+ rv = createCanSearchMessages(folder, target);
+ return NS_FAILED(rv) ? NS_RDF_NO_VALUE : rv;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderNameNode(nsIMsgFolder *folder,
+ nsIRDFNode **target, bool sort)
+{
+ nsresult rv;
+ if (sort)
+ {
+ uint8_t *sortKey=nullptr;
+ uint32_t sortKeyLength;
+ rv = folder->GetSortKey(&sortKeyLength, &sortKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ createBlobNode(sortKey, sortKeyLength, target, getRDFService());
+ PR_Free(sortKey);
+ }
+ else
+ {
+ nsString name;
+ rv = folder->GetName(name);
+ if (NS_FAILED(rv))
+ return rv;
+ createNode(name.get(), target, getRDFService());
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgFolderDataSource::GetFolderDisplayName(nsIMsgFolder *folder, nsString& folderName)
+{
+ return folder->GetAbbreviatedName(folderName);
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderTreeNameNode(nsIMsgFolder *folder,
+ nsIRDFNode **target)
+{
+ nsString name;
+ nsresult rv = GetFolderDisplayName(folder, name);
+ if (NS_FAILED(rv)) return rv;
+ nsAutoString nameString(name);
+ int32_t unreadMessages;
+
+ rv = folder->GetNumUnread(false, &unreadMessages);
+ if(NS_SUCCEEDED(rv))
+ CreateUnreadMessagesNameString(unreadMessages, nameString);
+
+ createNode(nameString.get(), target, getRDFService());
+ return NS_OK;
+}
+
+nsresult nsMsgFolderDataSource::createFolderTreeSimpleNameNode(nsIMsgFolder * folder, nsIRDFNode **target)
+{
+ nsString name;
+ nsresult rv = GetFolderDisplayName(folder, name);
+ if (NS_FAILED(rv)) return rv;
+
+ createNode(name.get(), target, getRDFService());
+ return NS_OK;
+}
+
+nsresult nsMsgFolderDataSource::CreateUnreadMessagesNameString(int32_t unreadMessages, nsAutoString &nameString)
+{
+ //Only do this if unread messages are positive
+ if(unreadMessages > 0)
+ {
+ nameString.Append(NS_LITERAL_STRING(" ("));
+ nameString.AppendInt(unreadMessages);
+ nameString.Append(u')');
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderSpecialNode(nsIMsgFolder *folder,
+ nsIRDFNode **target)
+{
+ uint32_t flags;
+ nsresult rv = folder->GetFlags(&flags);
+ if(NS_FAILED(rv))
+ return rv;
+
+ nsAutoString specialFolderString;
+ if (flags & nsMsgFolderFlags::Inbox)
+ specialFolderString.AssignLiteral("Inbox");
+ else if (flags & nsMsgFolderFlags::Trash)
+ specialFolderString.AssignLiteral("Trash");
+ else if (flags & nsMsgFolderFlags::Queue)
+ specialFolderString.AssignLiteral("Outbox");
+ else if (flags & nsMsgFolderFlags::SentMail)
+ specialFolderString.AssignLiteral("Sent");
+ else if (flags & nsMsgFolderFlags::Drafts)
+ specialFolderString.AssignLiteral("Drafts");
+ else if (flags & nsMsgFolderFlags::Templates)
+ specialFolderString.AssignLiteral("Templates");
+ else if (flags & nsMsgFolderFlags::Junk)
+ specialFolderString.AssignLiteral("Junk");
+ else if (flags & nsMsgFolderFlags::Virtual)
+ specialFolderString.AssignLiteral("Virtual");
+ else if (flags & nsMsgFolderFlags::Archive)
+ specialFolderString.AssignLiteral("Archives");
+ else {
+ // XXX why do this at all? or just ""
+ specialFolderString.AssignLiteral("none");
+ }
+
+ createNode(specialFolderString.get(), target, getRDFService());
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderServerTypeNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv) || !server) return NS_ERROR_FAILURE;
+
+ nsCString serverType;
+ rv = server->GetType(serverType);
+ if (NS_FAILED(rv)) return rv;
+
+ createNode(NS_ConvertASCIItoUTF16(serverType).get(), target, getRDFService());
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createServerIsDeferredNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ bool isDeferred = false;
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ folder->GetServer(getter_AddRefs(incomingServer));
+ if (incomingServer)
+ {
+ nsCOMPtr <nsIPop3IncomingServer> pop3Server = do_QueryInterface(incomingServer);
+ if (pop3Server)
+ {
+ nsCString deferredToServer;
+ pop3Server->GetDeferredToAccount(deferredToServer);
+ isDeferred = !deferredToServer.IsEmpty();
+ }
+ }
+ *target = (isDeferred) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderCanCreateFoldersOnServerNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv) || !server) return NS_ERROR_FAILURE;
+
+ bool canCreateFoldersOnServer;
+ rv = server->GetCanCreateFoldersOnServer(&canCreateFoldersOnServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (canCreateFoldersOnServer)
+ *target = kTrueLiteral;
+ else
+ *target = kFalseLiteral;
+ NS_IF_ADDREF(*target);
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderCanFileMessagesOnServerNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv) || !server) return NS_ERROR_FAILURE;
+
+ bool canFileMessagesOnServer;
+ rv = server->GetCanFileMessagesOnServer(&canFileMessagesOnServer);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = (canFileMessagesOnServer) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgFolderDataSource::createFolderIsServerNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+ bool isServer;
+ rv = folder->GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = nullptr;
+
+ if (isServer)
+ *target = kTrueLiteral;
+ else
+ *target = kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderNoSelectNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+ bool noSelect;
+ rv = folder->GetNoSelect(&noSelect);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = (noSelect) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createInVFEditSearchScopeNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ bool inVFEditSearchScope = false;
+ folder->GetInVFEditSearchScope(&inVFEditSearchScope);
+
+ *target = inVFEditSearchScope ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderVirtualNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+
+ *target = (folderFlags & nsMsgFolderFlags::Virtual) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgFolderDataSource::createFolderImapSharedNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+ bool imapShared;
+ rv = folder->GetImapShared(&imapShared);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = (imapShared) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgFolderDataSource::createFolderSynchronizeNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+ bool sync;
+ rv = folder->GetFlag(nsMsgFolderFlags::Offline, &sync);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = nullptr;
+
+ *target = (sync) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderSyncDisabledNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+
+ nsresult rv;
+ bool isServer;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ rv = folder->GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = folder->GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv) || !server) return NS_ERROR_FAILURE;
+
+ nsCString serverType;
+ rv = server->GetType(serverType);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = isServer || MsgLowerCaseEqualsLiteral(serverType, "none") || MsgLowerCaseEqualsLiteral(serverType, "pop3") ?
+ kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createCanSearchMessages(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv) || !server) return NS_ERROR_FAILURE;
+
+ bool canSearchMessages;
+ rv = server->GetCanSearchMessages(&canSearchMessages);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = (canSearchMessages) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderOpenNode(nsIMsgFolder *folder, nsIRDFNode **target)
+{
+ NS_ENSURE_ARG_POINTER(target);
+
+ // call GetSubFolders() to ensure mFlags is set correctly
+ // from the folder cache on startup
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsresult rv = folder->GetSubFolders(getter_AddRefs(subFolders));
+ if (NS_FAILED(rv))
+ return NS_RDF_NO_VALUE;
+
+ bool closed;
+ rv = folder->GetFlag(nsMsgFolderFlags::Elided, &closed);
+ if (NS_FAILED(rv))
+ return rv;
+
+ *target = (closed) ? kFalseLiteral : kTrueLiteral;
+
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderIsSecureNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+ bool isSecure = false;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+
+ if (NS_SUCCEEDED(rv) && server) {
+ rv = server->GetIsSecure(&isSecure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *target = (isSecure) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgFolderDataSource::createFolderCanSubscribeNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+ bool canSubscribe;
+ rv = folder->GetCanSubscribe(&canSubscribe);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = (canSubscribe) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderSupportsOfflineNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+ bool supportsOffline;
+ rv = folder->GetSupportsOffline(&supportsOffline);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ *target = (supportsOffline) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderCanFileMessagesNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+ bool canFileMessages;
+ rv = folder->GetCanFileMessages(&canFileMessages);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = (canFileMessages) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderCanCreateSubfoldersNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ nsresult rv;
+ bool canCreateSubfolders;
+ rv = folder->GetCanCreateSubfolders(&canCreateSubfolders);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = (canCreateSubfolders) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderCanRenameNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ bool canRename;
+ nsresult rv = folder->GetCanRename(&canRename);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = (canRename) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderCanCompactNode(nsIMsgFolder* folder,
+ nsIRDFNode **target)
+{
+ bool canCompact;
+ nsresult rv = folder->GetCanCompact(&canCompact);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = (canCompact) ? kTrueLiteral : kFalseLiteral;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgFolderDataSource::createTotalMessagesNode(nsIMsgFolder *folder,
+ nsIRDFNode **target)
+{
+
+ bool isServer;
+ nsresult rv = folder->GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t totalMessages;
+ if(isServer)
+ totalMessages = kDisplayBlankCount;
+ else
+ {
+ rv = folder->GetTotalMessages(false, &totalMessages);
+ if(NS_FAILED(rv)) return rv;
+ }
+ GetNumMessagesNode(totalMessages, target);
+
+ return rv;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderSizeNode(nsIMsgFolder *folder, nsIRDFNode **target)
+{
+ bool isServer;
+ nsresult rv = folder->GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t folderSize;
+ if (isServer) {
+ folderSize = kDisplayBlankCount64;
+ }
+ else
+ {
+ // XXX todo, we are asserting here for news
+ // for offline news, we'd know the size on disk, right? Yes, bug 851275.
+ rv = folder->GetSizeOnDisk(&folderSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return GetFolderSizeNode(folderSize, target);
+}
+
+nsresult
+nsMsgFolderDataSource::createCharsetNode(nsIMsgFolder *folder, nsIRDFNode **target)
+{
+ nsCString charset;
+ nsresult rv = folder->GetCharset(charset);
+ if (NS_SUCCEEDED(rv))
+ createNode(NS_ConvertASCIItoUTF16(charset).get(), target, getRDFService());
+ else
+ createNode(EmptyString().get(), target, getRDFService());
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createBiffStateNodeFromFolder(nsIMsgFolder *folder, nsIRDFNode **target)
+{
+ uint32_t biffState;
+ nsresult rv = folder->GetBiffState(&biffState);
+ if(NS_FAILED(rv)) return rv;
+
+ rv = createBiffStateNodeFromFlag(biffState, target);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createBiffStateNodeFromFlag(uint32_t flag, nsIRDFNode **target)
+{
+ const char16_t *biffStateStr;
+
+ switch (flag) {
+ case nsIMsgFolder::nsMsgBiffState_NewMail:
+ biffStateStr = u"NewMail";
+ break;
+ case nsIMsgFolder::nsMsgBiffState_NoMail:
+ biffStateStr = u"NoMail";
+ break;
+ default:
+ biffStateStr = u"UnknownMail";
+ break;
+ }
+
+ createNode(biffStateStr, target, getRDFService());
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createUnreadMessagesNode(nsIMsgFolder *folder,
+ nsIRDFNode **target)
+{
+ bool isServer;
+ nsresult rv = folder->GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t totalUnreadMessages;
+ if(isServer)
+ totalUnreadMessages = kDisplayBlankCount;
+ else
+ {
+ rv = folder->GetNumUnread(false, &totalUnreadMessages);
+ if(NS_FAILED(rv)) return rv;
+ }
+ GetNumMessagesNode(totalUnreadMessages, target);
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createHasUnreadMessagesNode(nsIMsgFolder *folder, bool aIncludeSubfolders, nsIRDFNode **target)
+{
+ bool isServer;
+ nsresult rv = folder->GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = kFalseLiteral;
+
+ int32_t totalUnreadMessages;
+ if(!isServer)
+ {
+ rv = folder->GetNumUnread(aIncludeSubfolders, &totalUnreadMessages);
+ if(NS_FAILED(rv)) return rv;
+ // if we're including sub-folders, we're trying to find out if child folders
+ // have unread. If so, we subtract the unread msgs in the current folder.
+ if (aIncludeSubfolders)
+ {
+ int32_t numUnreadInFolder;
+ rv = folder->GetNumUnread(false, &numUnreadInFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // don't subtract if numUnread is negative (which means we don't know the unread count)
+ if (numUnreadInFolder > 0)
+ totalUnreadMessages -= numUnreadInFolder;
+ }
+ *target = (totalUnreadMessages > 0) ? kTrueLiteral : kFalseLiteral;
+ }
+
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::OnUnreadMessagePropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue)
+{
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(folderResource);
+ if(folder)
+ {
+ //First send a regular unread message changed notification
+ nsCOMPtr<nsIRDFNode> newNode;
+
+ GetNumMessagesNode(newValue, getter_AddRefs(newNode));
+ NotifyPropertyChanged(folderResource, kNC_TotalUnreadMessages, newNode);
+
+ //Now see if hasUnreadMessages has changed
+ if(oldValue <=0 && newValue >0)
+ {
+ NotifyPropertyChanged(folderResource, kNC_HasUnreadMessages, kTrueLiteral);
+ NotifyAncestors(folder, kNC_SubfoldersHaveUnreadMessages, kTrueLiteral);
+ }
+ else if(oldValue > 0 && newValue <= 0)
+ {
+ NotifyPropertyChanged(folderResource, kNC_HasUnreadMessages, kFalseLiteral);
+ // this isn't quite right - parents could still have other children with
+ // unread messages. NotifyAncestors will have to figure that out...
+ NotifyAncestors(folder, kNC_SubfoldersHaveUnreadMessages, kFalseLiteral);
+ }
+
+ //We will have to change the folderTreeName if the unread column is hidden
+ NotifyFolderTreeNameChanged(folder, folderResource, newValue);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::NotifyFolderNameChanged(nsIMsgFolder* aFolder, nsIRDFResource *folderResource)
+{
+ nsString name;
+ nsresult rv = aFolder->GetName(name);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIRDFNode> newNameNode;
+ createNode(name.get(), getter_AddRefs(newNameNode), getRDFService());
+ NotifyPropertyChanged(folderResource, kNC_Name, newNameNode);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::NotifyFolderTreeSimpleNameChanged(nsIMsgFolder* aFolder, nsIRDFResource *folderResource)
+{
+ nsString abbreviatedName;
+ nsresult rv = GetFolderDisplayName(aFolder, abbreviatedName);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIRDFNode> newNameNode;
+ createNode(abbreviatedName.get(), getter_AddRefs(newNameNode), getRDFService());
+ NotifyPropertyChanged(folderResource, kNC_FolderTreeSimpleName, newNameNode);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::NotifyFolderTreeNameChanged(nsIMsgFolder* aFolder,
+ nsIRDFResource* aFolderResource,
+ int32_t aUnreadMessages)
+{
+ nsString name;
+ nsresult rv = GetFolderDisplayName(aFolder, name);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString newNameString(name);
+ CreateUnreadMessagesNameString(aUnreadMessages, newNameString);
+ nsCOMPtr<nsIRDFNode> newNameNode;
+ createNode(newNameString.get(), getter_AddRefs(newNameNode), getRDFService());
+ NotifyPropertyChanged(aFolderResource, kNC_FolderTreeName, newNameNode);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::NotifyAncestors(nsIMsgFolder *aFolder, nsIRDFResource *aPropertyResource, nsIRDFNode *aNode)
+{
+ bool isServer = false;
+ nsresult rv = aFolder->GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (isServer)
+ // done, stop
+ return NS_OK;
+
+ nsCOMPtr <nsIMsgFolder> parentMsgFolder;
+ rv = aFolder->GetParent(getter_AddRefs(parentMsgFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!parentMsgFolder)
+ return NS_OK;
+
+ rv = parentMsgFolder->GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // don't need to notify servers either.
+ if (isServer)
+ // done, stop
+ return NS_OK;
+
+ nsCOMPtr<nsIRDFResource> parentFolderResource = do_QueryInterface(parentMsgFolder,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if we're setting the subFoldersHaveUnreadMessages property to false, check
+ // if the folder really doesn't have subfolders with unread messages.
+ if (aPropertyResource == kNC_SubfoldersHaveUnreadMessages && aNode == kFalseLiteral)
+ {
+ nsCOMPtr <nsIRDFNode> unreadMsgsNode;
+ createHasUnreadMessagesNode(parentMsgFolder, true, getter_AddRefs(unreadMsgsNode));
+ aNode = unreadMsgsNode;
+ }
+ NotifyPropertyChanged(parentFolderResource, aPropertyResource, aNode);
+
+ return NotifyAncestors(parentMsgFolder, aPropertyResource, aNode);
+}
+
+// New Messages
+
+nsresult
+nsMsgFolderDataSource::createNewMessagesNode(nsIMsgFolder *folder, nsIRDFNode **target)
+{
+
+ nsresult rv;
+
+ bool isServer;
+ rv = folder->GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+
+ *target = kFalseLiteral;
+
+ //int32_t totalNewMessages;
+ bool isNewMessages;
+ if(!isServer)
+ {
+ rv = folder->GetHasNewMessages(&isNewMessages);
+ if(NS_FAILED(rv)) return rv;
+ *target = (isNewMessages) ? kTrueLiteral : kFalseLiteral;
+ }
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+}
+
+/**
+nsresult
+nsMsgFolderDataSource::OnUnreadMessagePropertyChanged(nsIMsgFolder *folder, int32_t oldValue, int32_t newValue)
+{
+ nsCOMPtr<nsIRDFResource> folderResource = do_QueryInterface(folder);
+ if(folderResource)
+ {
+ //First send a regular unread message changed notification
+ nsCOMPtr<nsIRDFNode> newNode;
+
+ GetNumMessagesNode(newValue, getter_AddRefs(newNode));
+ NotifyPropertyChanged(folderResource, kNC_TotalUnreadMessages, newNode);
+
+ //Now see if hasUnreadMessages has changed
+ nsCOMPtr<nsIRDFNode> oldHasUnreadMessages;
+ nsCOMPtr<nsIRDFNode> newHasUnreadMessages;
+ if(oldValue <=0 && newValue >0)
+ {
+ oldHasUnreadMessages = kFalseLiteral;
+ newHasUnreadMessages = kTrueLiteral;
+ NotifyPropertyChanged(folderResource, kNC_HasUnreadMessages, newHasUnreadMessages);
+ }
+ else if(oldValue > 0 && newValue <= 0)
+ {
+ newHasUnreadMessages = kFalseLiteral;
+ NotifyPropertyChanged(folderResource, kNC_HasUnreadMessages, newHasUnreadMessages);
+ }
+ }
+ return NS_OK;
+}
+
+**/
+nsresult
+nsMsgFolderDataSource::OnFolderSortOrderPropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue)
+{
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(folderResource));
+ if (folder)
+ {
+ nsCOMPtr<nsIRDFNode> newNode;
+ createFolderNameNode(folder, getter_AddRefs(newNode), true);
+ NotifyPropertyChanged(folderResource, kNC_FolderTreeNameSort, newNode);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::OnFolderSizePropertyChanged(nsIRDFResource *folderResource, int64_t oldValue, int64_t newValue)
+{
+ nsCOMPtr<nsIRDFNode> newNode;
+ GetFolderSizeNode(newValue, getter_AddRefs(newNode));
+ NotifyPropertyChanged(folderResource, kNC_FolderSize, newNode);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::OnTotalMessagePropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue)
+{
+ nsCOMPtr<nsIRDFNode> newNode;
+ GetNumMessagesNode(newValue, getter_AddRefs(newNode));
+ NotifyPropertyChanged(folderResource, kNC_TotalMessages, newNode);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::GetNumMessagesNode(int32_t aNumMessages, nsIRDFNode **node)
+{
+ uint32_t numMessages = aNumMessages;
+ if(numMessages == kDisplayQuestionCount)
+ createNode(u"???", node, getRDFService());
+ else if (numMessages == kDisplayBlankCount || numMessages == 0)
+ createNode(EmptyString().get(), node, getRDFService());
+ else
+ createIntNode(numMessages, node, getRDFService());
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::GetFolderSizeNode(int64_t aFolderSize, nsIRDFNode **aNode)
+{
+ nsresult rv;
+ if (aFolderSize == kDisplayBlankCount64 || aFolderSize == 0)
+ createNode(EmptyString().get(), aNode, getRDFService());
+ else if (aFolderSize == kDisplayQuestionCount64)
+ createNode(u"???", aNode, getRDFService());
+ else
+ {
+ nsAutoString sizeString;
+ rv = FormatFileSize(aFolderSize, true, sizeString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ createNode(sizeString.get(), aNode, getRDFService());
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgFolderDataSource::createFolderChildNode(nsIMsgFolder *folder,
+ nsIRDFNode **target)
+{
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsresult rv = folder->GetSubFolders(getter_AddRefs(subFolders));
+ if (NS_FAILED(rv))
+ return NS_RDF_NO_VALUE;
+
+ bool hasMore;
+ rv = subFolders->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv) || !hasMore)
+ return NS_RDF_NO_VALUE;
+
+ nsCOMPtr<nsISupports> firstFolder;
+ rv = subFolders->GetNext(getter_AddRefs(firstFolder));
+ if (NS_FAILED(rv))
+ return NS_RDF_NO_VALUE;
+
+ return CallQueryInterface(firstFolder, target);
+}
+
+
+nsresult nsMsgFolderDataSource::DoCopyToFolder(nsIMsgFolder *dstFolder, nsISupportsArray *arguments,
+ nsIMsgWindow *msgWindow, bool isMove)
+{
+ nsresult rv;
+ uint32_t itemCount;
+ rv = arguments->Count(&itemCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ //need source folder and at least one item to copy
+ if(itemCount < 2)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryElementAt(arguments, 0));
+ if(!srcFolder)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+
+ // Remove first element
+ for(uint32_t i = 1; i < itemCount; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message(do_QueryElementAt(arguments, i));
+ if (message)
+ {
+ messageArray->AppendElement(message, false);
+ }
+ }
+
+ //Call copyservice with dstFolder, srcFolder, messages, isMove, and txnManager
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return copyService->CopyMessages(srcFolder, messageArray, dstFolder, isMove,
+ nullptr, msgWindow, true/* allowUndo */);
+}
+
+nsresult nsMsgFolderDataSource::DoFolderCopyToFolder(nsIMsgFolder *dstFolder, nsISupportsArray *arguments,
+ nsIMsgWindow *msgWindow, bool isMoveFolder)
+{
+ nsresult rv;
+ uint32_t itemCount;
+ rv = arguments->Count(&itemCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ //need at least one item to copy
+ if(itemCount < 1)
+ return NS_ERROR_FAILURE;
+
+ if (!isMoveFolder) // copy folder not on the same server
+ {
+ // Create an nsIMutableArray from the nsISupportsArray
+ nsCOMPtr<nsIMutableArray> folderArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ for (uint32_t i = 0; i < itemCount; i++)
+ {
+ nsCOMPtr<nsISupports> element(do_QueryElementAt(arguments, i, &rv));
+ if (NS_SUCCEEDED(rv))
+ folderArray->AppendElement(element, false);
+ }
+
+ //Call copyservice with dstFolder, srcFolder, folders and isMoveFolder
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ if(NS_SUCCEEDED(rv))
+ {
+ rv = copyService->CopyFolders(folderArray, dstFolder, isMoveFolder,
+ nullptr, msgWindow);
+
+ }
+ }
+ else //within the same server therefore no need for copy service
+ {
+
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ for (uint32_t i=0;i< itemCount; i++)
+ {
+ msgFolder = do_QueryElementAt(arguments, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = dstFolder->CopyFolder(msgFolder, isMoveFolder , msgWindow, nullptr);
+ NS_ASSERTION((NS_SUCCEEDED(rv)),"Copy folder failed.");
+ }
+ }
+ }
+
+ return rv;
+ //return NS_OK;
+}
+
+nsresult nsMsgFolderDataSource::DoDeleteFromFolder(nsIMsgFolder *folder, nsISupportsArray *arguments,
+ nsIMsgWindow *msgWindow, bool reallyDelete)
+{
+ nsresult rv = NS_OK;
+ uint32_t itemCount;
+ rv = arguments->Count(&itemCount);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ nsCOMPtr<nsIMutableArray> folderArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+
+ //Split up deleted items into different type arrays to be passed to the folder
+ //for deletion.
+ for(uint32_t item = 0; item < itemCount; item++)
+ {
+ nsCOMPtr<nsISupports> supports(do_QueryElementAt(arguments, item));
+ nsCOMPtr<nsIMsgDBHdr> deletedMessage(do_QueryInterface(supports));
+ nsCOMPtr<nsIMsgFolder> deletedFolder(do_QueryInterface(supports));
+ if (deletedMessage)
+ {
+ messageArray->AppendElement(supports, false);
+ }
+ else if(deletedFolder)
+ {
+ folderArray->AppendElement(supports, false);
+ }
+ }
+ uint32_t cnt;
+ rv = messageArray->GetLength(&cnt);
+ if (NS_FAILED(rv)) return rv;
+ if (cnt > 0)
+ rv = folder->DeleteMessages(messageArray, msgWindow, reallyDelete, false, nullptr, true /*allowUndo*/);
+
+ rv = folderArray->GetLength(&cnt);
+ if (NS_FAILED(rv)) return rv;
+ if (cnt > 0)
+ {
+ nsCOMPtr<nsIMsgFolder> folderToDelete = do_QueryElementAt(folderArray, 0);
+ uint32_t folderFlags = 0;
+ if (folderToDelete)
+ {
+ folderToDelete->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Virtual)
+ {
+ NS_ENSURE_ARG_POINTER(msgWindow);
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> sMessengerStringBundle;
+ nsString confirmMsg;
+ rv = sBundleService->CreateBundle(MESSENGER_STRING_URL, getter_AddRefs(sMessengerStringBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ sMessengerStringBundle->GetStringFromName(u"confirmSavedSearchDeleteMessage", getter_Copies(confirmMsg));
+
+ nsCOMPtr<nsIPrompt> dialog;
+ rv = msgWindow->GetPromptDialog(getter_AddRefs(dialog));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool dialogResult;
+ rv = dialog->Confirm(nullptr, confirmMsg.get(), &dialogResult);
+ if (!dialogResult)
+ return NS_OK;
+ }
+ }
+ }
+ rv = folder->DeleteSubFolders(folderArray, msgWindow);
+ }
+ return rv;
+}
+
+nsresult nsMsgFolderDataSource::DoNewFolder(nsIMsgFolder *folder, nsISupportsArray *arguments, nsIMsgWindow *window)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIRDFLiteral> literal = do_QueryElementAt(arguments, 0, &rv);
+ if(NS_SUCCEEDED(rv))
+ {
+ nsString name;
+ literal->GetValue(getter_Copies(name));
+ rv = folder->CreateSubfolder(name, window);
+ }
+ return rv;
+}
+
+nsresult nsMsgFolderDataSource::DoFolderAssert(nsIMsgFolder *folder, nsIRDFResource *property, nsIRDFNode *target)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (kNC_Charset == property)
+ {
+ nsCOMPtr<nsIRDFLiteral> literal(do_QueryInterface(target));
+ if(literal)
+ {
+ const char16_t* value;
+ rv = literal->GetValueConst(&value);
+ if(NS_SUCCEEDED(rv))
+ rv = folder->SetCharset(NS_LossyConvertUTF16toASCII(value));
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+ }
+ else if (kNC_Open == property && target == kTrueLiteral)
+ rv = folder->ClearFlag(nsMsgFolderFlags::Elided);
+
+ return rv;
+}
+
+nsresult nsMsgFolderDataSource::DoFolderUnassert(nsIMsgFolder *folder, nsIRDFResource *property, nsIRDFNode *target)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if((kNC_Open == property) && target == kTrueLiteral)
+ rv = folder->SetFlag(nsMsgFolderFlags::Elided);
+
+ return rv;
+}
+
+nsresult nsMsgFolderDataSource::DoFolderHasAssertion(nsIMsgFolder *folder,
+ nsIRDFResource *property,
+ nsIRDFNode *target,
+ bool tv,
+ bool *hasAssertion)
+{
+ nsresult rv = NS_OK;
+ if(!hasAssertion)
+ return NS_ERROR_NULL_POINTER;
+
+ //We're not keeping track of negative assertions on folders.
+ if(!tv)
+ {
+ *hasAssertion = false;
+ return NS_OK;
+ }
+
+ if (kNC_Child == property)
+ {
+ nsCOMPtr<nsIMsgFolder> childFolder(do_QueryInterface(target, &rv));
+ if(NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> childsParent;
+ rv = childFolder->GetParent(getter_AddRefs(childsParent));
+ *hasAssertion = (NS_SUCCEEDED(rv) && childsParent && folder
+ && (childsParent.get() == folder));
+ }
+ }
+ else if ((kNC_Name == property) ||
+ (kNC_Open == property) ||
+ (kNC_FolderTreeName == property) ||
+ (kNC_FolderTreeSimpleName == property) ||
+ (kNC_SpecialFolder == property) ||
+ (kNC_ServerType == property) ||
+ (kNC_IsDeferred == property) ||
+ (kNC_CanCreateFoldersOnServer == property) ||
+ (kNC_CanFileMessagesOnServer == property) ||
+ (kNC_IsServer == property) ||
+ (kNC_IsSecure == property) ||
+ (kNC_CanSubscribe == property) ||
+ (kNC_SupportsOffline == property) ||
+ (kNC_CanFileMessages == property) ||
+ (kNC_CanCreateSubfolders == property) ||
+ (kNC_CanRename == property) ||
+ (kNC_CanCompact == property) ||
+ (kNC_TotalMessages == property) ||
+ (kNC_TotalUnreadMessages == property) ||
+ (kNC_FolderSize == property) ||
+ (kNC_Charset == property) ||
+ (kNC_BiffState == property) ||
+ (kNC_HasUnreadMessages == property) ||
+ (kNC_NoSelect == property) ||
+ (kNC_Synchronize == property) ||
+ (kNC_SyncDisabled == property) ||
+ (kNC_VirtualFolder == property) ||
+ (kNC_CanSearchMessages == property))
+ {
+ nsCOMPtr<nsIRDFResource> folderResource(do_QueryInterface(folder, &rv));
+
+ if(NS_FAILED(rv))
+ return rv;
+
+ rv = GetTargetHasAssertion(this, folderResource, property, tv, target, hasAssertion);
+ }
+ else
+ *hasAssertion = false;
+ return rv;
+}
+
+nsMsgFlatFolderDataSource::nsMsgFlatFolderDataSource()
+{
+ m_builtFolders = false;
+}
+
+nsMsgFlatFolderDataSource::~nsMsgFlatFolderDataSource()
+{
+}
+
+nsresult nsMsgFlatFolderDataSource::Init()
+{
+ nsIRDFService* rdf = getRDFService();
+ NS_ENSURE_TRUE(rdf, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIRDFResource> source;
+ nsAutoCString dsUri(m_dsName);
+ dsUri.Append(":/");
+ rdf->GetResource(dsUri, getter_AddRefs(m_rootResource));
+
+ return nsMsgFolderDataSource::Init();
+}
+
+void nsMsgFlatFolderDataSource::Cleanup()
+{
+ m_folders.Clear();
+ m_builtFolders = false;
+ nsMsgFolderDataSource::Cleanup();
+}
+
+NS_IMETHODIMP nsMsgFlatFolderDataSource::GetTarget(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsIRDFNode** target)
+{
+ return (property == kNC_Child)
+ ? NS_RDF_NO_VALUE
+ : nsMsgFolderDataSource::GetTarget(source, property, tv, target);
+}
+
+
+NS_IMETHODIMP nsMsgFlatFolderDataSource::GetTargets(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsISimpleEnumerator** targets)
+{
+ if (kNC_Child != property)
+ return nsMsgFolderDataSource::GetTargets(source, property, tv, targets);
+
+ if(!targets)
+ return NS_ERROR_NULL_POINTER;
+
+ if (ResourceIsOurRoot(source))
+ {
+ EnsureFolders();
+ return NS_NewArrayEnumerator(targets, m_folders);
+ }
+ return NS_NewSingletonEnumerator(targets, property);
+}
+
+void nsMsgFlatFolderDataSource::EnsureFolders()
+{
+ if (m_builtFolders)
+ return;
+
+ m_builtFolders = true; // in case something goes wrong
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIArray> allFolders;
+ rv = accountManager->GetAllFolders(getter_AddRefs(allFolders));
+ if (NS_FAILED(rv) || !allFolders)
+ return;
+
+ uint32_t count;
+ rv = allFolders->GetLength(&count);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> curFolder = do_QueryElementAt(allFolders, i);
+ if (WantsThisFolder(curFolder))
+ m_folders.AppendObject(curFolder);
+ }
+}
+
+
+NS_IMETHODIMP nsMsgFlatFolderDataSource::GetURI(char* *aUri)
+{
+ nsAutoCString uri("rdf:");
+ uri.Append(m_dsName);
+ return (*aUri = ToNewCString(uri))
+ ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgFlatFolderDataSource::HasAssertion(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ bool* hasAssertion)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv));
+ // we need to check if the folder belongs in this datasource.
+ if (NS_SUCCEEDED(rv) && property != kNC_Open && property != kNC_Child)
+ {
+ if (WantsThisFolder(folder) && (kNC_Child != property))
+ return DoFolderHasAssertion(folder, property, target, tv, hasAssertion);
+ }
+ else if (property == kNC_Child && ResourceIsOurRoot(source)) // check if source is us
+ {
+ folder = do_QueryInterface(target);
+ if (folder)
+ {
+ nsCOMPtr<nsIMsgFolder> parentMsgFolder;
+ folder->GetParent(getter_AddRefs(parentMsgFolder));
+ // a folder without a parent must be getting deleted as part of
+ // the rename operation and is thus a folder we are
+ // no longer interested in
+ if (parentMsgFolder && WantsThisFolder(folder))
+ {
+ *hasAssertion = true;
+ return NS_OK;
+ }
+ }
+ }
+ *hasAssertion = false;
+ return NS_OK;
+}
+
+nsresult nsMsgFlatFolderDataSource::OnItemAddedOrRemoved(nsIMsgFolder *parentItem, nsISupports *item, bool added)
+{
+ // When a folder is added or removed, parentItem is the parent folder and item is the folder being
+ // added or removed. In a flat data source, there is no arc in the graph between the parent folder
+ // and the folder being added or removed. Our flat data source root (i.e. mailnewsunreadfolders:/) has
+ // an arc with the child property to every folder in the data source. We must change parentItem
+ // to be our data source root before calling nsMsgFolderDataSource::OnItemAddedOrRemoved. This ensures
+ // that datasource listeners such as the template builder properly handle add and remove
+ // notifications on the flat datasource.
+ nsCOMPtr<nsIRDFNode> itemNode(do_QueryInterface(item));
+ if (itemNode)
+ NotifyObservers(m_rootResource, kNC_Child, itemNode, nullptr, added, false);
+ return NS_OK;
+}
+
+bool nsMsgFlatFolderDataSource::ResourceIsOurRoot(nsIRDFResource *resource)
+{
+ return m_rootResource.get() == resource;
+}
+
+bool nsMsgFlatFolderDataSource::WantsThisFolder(nsIMsgFolder *folder)
+{
+ EnsureFolders();
+ return m_folders.Contains(folder);
+}
+
+nsresult nsMsgFlatFolderDataSource::GetFolderDisplayName(nsIMsgFolder *folder, nsString& folderName)
+{
+ folder->GetName(folderName);
+ uint32_t foldersCount = m_folders.Count();
+ nsString otherFolderName;
+ for (uint32_t index = 0; index < foldersCount; index++)
+ {
+ if (folder == m_folders[index]) // ignore ourselves.
+ continue;
+ m_folders[index]->GetName(otherFolderName);
+ if (otherFolderName.Equals(folderName))
+ {
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ folder->GetServer(getter_AddRefs(server));
+ if (server)
+ {
+ nsString serverName;
+ server->GetPrettyName(serverName);
+ folderName.AppendLiteral(" - ");
+ folderName.Append(serverName);
+ return NS_OK;
+ }
+ }
+ }
+ // check if folder name is unique - if not, append account name
+ return folder->GetAbbreviatedName(folderName);
+}
+
+
+bool nsMsgUnreadFoldersDataSource::WantsThisFolder(nsIMsgFolder *folder)
+{
+ int32_t numUnread;
+ folder->GetNumUnread(false, &numUnread);
+ return numUnread > 0;
+}
+
+nsresult nsMsgUnreadFoldersDataSource::NotifyPropertyChanged(nsIRDFResource *resource,
+ nsIRDFResource *property, nsIRDFNode *newNode,
+ nsIRDFNode *oldNode)
+{
+ // check if it's the has unread property that's changed; if so, see if we need
+ // to add this folder to the view.
+ // Then, call base class.
+ if (kNC_HasUnreadMessages == property)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(resource));
+ if (folder)
+ {
+ int32_t numUnread;
+ folder->GetNumUnread(false, &numUnread);
+ if (numUnread > 0)
+ {
+ if (!m_folders.Contains(folder))
+ m_folders.AppendObject(folder);
+ NotifyObservers(kNC_UnreadFolders, kNC_Child, resource, nullptr, true, false);
+ }
+ }
+ }
+ return nsMsgFolderDataSource::NotifyPropertyChanged(resource, property,
+ newNode, oldNode);
+}
+
+bool nsMsgFavoriteFoldersDataSource::WantsThisFolder(nsIMsgFolder *folder)
+{
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ return folderFlags & nsMsgFolderFlags::Favorite;
+}
+
+
+void nsMsgRecentFoldersDataSource::Cleanup()
+{
+ m_cutOffDate = 0;
+ nsMsgFlatFolderDataSource::Cleanup();
+}
+
+
+void nsMsgRecentFoldersDataSource::EnsureFolders()
+{
+ if (m_builtFolders)
+ return;
+
+ m_builtFolders = true; // in case something goes wrong
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIArray> allFolders;
+ rv = accountManager->GetAllFolders(getter_AddRefs(allFolders));
+ if (NS_FAILED(rv) || !allFolders)
+ return;
+
+ uint32_t count;
+ rv = allFolders->GetLength(&count);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> curFolder = do_QueryElementAt(allFolders, i);
+ nsCString dateStr;
+ curFolder->GetStringProperty(MRU_TIME_PROPERTY, dateStr);
+ uint32_t curFolderDate = (uint32_t) dateStr.ToInteger(&rv);
+ if (NS_FAILED(rv))
+ curFolderDate = 0;
+
+ if (curFolderDate > m_cutOffDate)
+ {
+ // if m_folders is "full", replace oldest folder with this folder,
+ // and adjust m_cutOffDate so that it's the mrutime
+ // of the "new" oldest folder.
+ uint32_t curFaveFoldersCount = m_folders.Count();
+ if (curFaveFoldersCount > m_maxNumFolders)
+ {
+ uint32_t indexOfOldestFolder = 0;
+ uint32_t oldestFaveDate = 0;
+ uint32_t newOldestFaveDate = 0;
+ for (uint32_t index = 0; index < curFaveFoldersCount; )
+ {
+ nsCString curFaveFolderDateStr;
+ m_folders[index]->GetStringProperty(MRU_TIME_PROPERTY, curFaveFolderDateStr);
+ uint32_t curFaveFolderDate = (uint32_t) curFaveFolderDateStr.ToInteger(&rv);
+ if (!oldestFaveDate || curFaveFolderDate < oldestFaveDate)
+ {
+ indexOfOldestFolder = index;
+ newOldestFaveDate = oldestFaveDate;
+ oldestFaveDate = curFaveFolderDate;
+ }
+ if (!newOldestFaveDate || (index != indexOfOldestFolder
+ && curFaveFolderDate < newOldestFaveDate)) {
+ newOldestFaveDate = curFaveFolderDate;
+ }
+ index++;
+ }
+ if (curFolderDate > oldestFaveDate && !m_folders.Contains(curFolder))
+ m_folders.ReplaceObjectAt(curFolder, indexOfOldestFolder);
+
+ NS_ASSERTION(newOldestFaveDate >= m_cutOffDate, "cutoff date should be getting bigger");
+ m_cutOffDate = newOldestFaveDate;
+ }
+ else if (!m_folders.Contains(curFolder))
+ m_folders.AppendObject(curFolder);
+ }
+#ifdef DEBUG_David_Bienvenu
+ else
+ {
+ for (uint32_t index = 0; index < m_folders.Count(); index++)
+ {
+ nsCString curFaveFolderDateStr;
+ m_folders[index]->GetStringProperty(MRU_TIME_PROPERTY, curFaveFolderDateStr);
+ uint32_t curFaveFolderDate = (uint32_t) curFaveFolderDateStr.ToInteger(&rv);
+ NS_ASSERTION(curFaveFolderDate > curFolderDate, "folder newer then faves but not added");
+ }
+ }
+#endif
+ }
+}
+
+NS_IMETHODIMP nsMsgRecentFoldersDataSource::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ // if we've already built the recent folder array, we should add this item to the array
+ // since just added items are by definition new.
+ // I think this means newly discovered imap folders (ones w/o msf files) will
+ // get added, but maybe that's OK.
+ if (m_builtFolders)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item));
+ if (folder && !m_folders.Contains(folder))
+ {
+ m_folders.AppendObject(folder);
+ nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(item);
+ NotifyObservers(kNC_RecentFolders, kNC_Child, resource, nullptr, true, false);
+ }
+ }
+ return nsMsgFlatFolderDataSource::OnItemAdded(parentItem, item);
+}
+
+nsresult nsMsgRecentFoldersDataSource::NotifyPropertyChanged(nsIRDFResource *resource,
+ nsIRDFResource *property, nsIRDFNode *newNode,
+ nsIRDFNode *oldNode)
+{
+ // check if it's the has new property that's changed; if so, see if we need
+ // to add this folder to the view.
+ // Then, call base class.
+ if (kNC_NewMessages == property)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(resource));
+ if (folder)
+ {
+ bool hasNewMessages;
+ folder->GetHasNewMessages(&hasNewMessages);
+ if (hasNewMessages)
+ {
+ if (!m_folders.Contains(folder))
+ {
+ m_folders.AppendObject(folder);
+ NotifyObservers(kNC_RecentFolders, kNC_Child, resource, nullptr, true, false);
+ }
+ }
+ }
+ }
+ return nsMsgFolderDataSource::NotifyPropertyChanged(resource, property,
+ newNode, oldNode);
+}
diff --git a/mailnews/base/src/nsMsgFolderDataSource.h b/mailnews/base/src/nsMsgFolderDataSource.h
new file mode 100644
index 000000000..c683274ac
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderDataSource.h
@@ -0,0 +1,356 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFService.h"
+
+#include "nsIFolderListener.h"
+#include "nsMsgRDFDataSource.h"
+
+#include "nsITransactionManager.h"
+#include "nsCOMArray.h"
+#include "nsIMutableArray.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+/**
+ * The mail data source.
+ */
+class nsMsgFolderDataSource : public nsMsgRDFDataSource,
+ public nsIFolderListener
+{
+public:
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFOLDERLISTENER
+
+ nsMsgFolderDataSource(void);
+ virtual nsresult Init() override;
+ virtual void Cleanup() override;
+
+ // nsIRDFDataSource methods
+ NS_IMETHOD GetURI(char* *uri) override;
+
+ NS_IMETHOD GetSource(nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ nsIRDFResource** source /* out */) override;
+
+ NS_IMETHOD GetTarget(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsIRDFNode** target) override;
+
+ NS_IMETHOD GetSources(nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ nsISimpleEnumerator** sources) override;
+
+ NS_IMETHOD GetTargets(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsISimpleEnumerator** targets) override;
+
+ NS_IMETHOD Assert(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv) override;
+
+ NS_IMETHOD Unassert(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target) override;
+
+ NS_IMETHOD HasAssertion(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ bool* hasAssertion) override;
+
+ NS_IMETHOD HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc,
+ bool *result) override;
+
+ NS_IMETHOD ArcLabelsIn(nsIRDFNode* node,
+ nsISimpleEnumerator** labels) override;
+
+ NS_IMETHOD ArcLabelsOut(nsIRDFResource* source,
+ nsISimpleEnumerator** labels) override;
+
+ NS_IMETHOD GetAllResources(nsISimpleEnumerator** aResult) override;
+
+ NS_IMETHOD GetAllCmds(nsIRDFResource* source,
+ nsISimpleEnumerator/*<nsIRDFResource>*/** commands
+ ) override;
+
+ NS_IMETHOD IsCommandEnabled(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments,
+ bool* aResult) override;
+
+ NS_IMETHOD DoCommand(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments) override;
+protected:
+ virtual ~nsMsgFolderDataSource();
+
+ nsresult GetSenderName(nsAutoString& sender, nsAutoString *senderUserName);
+
+ nsresult createFolderNode(nsIMsgFolder *folder, nsIRDFResource* property,
+ nsIRDFNode **target);
+ nsresult createFolderNameNode(nsIMsgFolder *folder, nsIRDFNode **target, bool sort);
+ nsresult createFolderOpenNode(nsIMsgFolder *folder,nsIRDFNode **target);
+ nsresult createFolderTreeNameNode(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createFolderTreeSimpleNameNode(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createFolderSpecialNode(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createFolderServerTypeNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createServerIsDeferredNode(nsIMsgFolder* folder,
+ nsIRDFNode **target);
+ nsresult createFolderCanCreateFoldersOnServerNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderCanFileMessagesOnServerNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderIsServerNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderIsSecureNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderCanSubscribeNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderSupportsOfflineNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderCanFileMessagesNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderCanCreateSubfoldersNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderCanRenameNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderCanCompactNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createTotalMessagesNode(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createUnreadMessagesNode(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createFolderSizeNode(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createCharsetNode(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createBiffStateNodeFromFolder(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createBiffStateNodeFromFlag(uint32_t flag, nsIRDFNode **target);
+ nsresult createHasUnreadMessagesNode(nsIMsgFolder *folder, bool aIncludeSubfolders, nsIRDFNode **target);
+ nsresult createNewMessagesNode(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createFolderNoSelectNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderVirtualNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createInVFEditSearchScopeNode(nsIMsgFolder* folder,
+ nsIRDFNode **target);
+ nsresult createFolderImapSharedNode(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderSynchronizeNode(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createFolderSyncDisabledNode(nsIMsgFolder *folder, nsIRDFNode **target);
+ nsresult createCanSearchMessages(nsIMsgFolder *folder,
+ nsIRDFNode **target);
+ nsresult createFolderChildNode(nsIMsgFolder *folder, nsIRDFNode **target);
+
+ nsresult getFolderArcLabelsOut(nsCOMArray<nsIRDFResource> &aArcs);
+
+ nsresult DoDeleteFromFolder(nsIMsgFolder *folder,
+ nsISupportsArray *arguments, nsIMsgWindow *msgWindow, bool reallyDelete);
+
+ nsresult DoCopyToFolder(nsIMsgFolder *dstFolder, nsISupportsArray *arguments,
+ nsIMsgWindow *msgWindow, bool isMove);
+
+ nsresult DoFolderCopyToFolder(nsIMsgFolder *dstFolder, nsISupportsArray *arguments,
+ nsIMsgWindow *msgWindow, bool isMoveFolder);
+
+ nsresult DoNewFolder(nsIMsgFolder *folder, nsISupportsArray *arguments,
+ nsIMsgWindow *window);
+
+ nsresult DoFolderAssert(nsIMsgFolder *folder, nsIRDFResource *property, nsIRDFNode *target);
+ nsresult DoFolderUnassert(nsIMsgFolder *folder, nsIRDFResource *property, nsIRDFNode *target);
+
+ nsresult DoFolderHasAssertion(nsIMsgFolder *folder, nsIRDFResource *property, nsIRDFNode *target,
+ bool tv, bool *hasAssertion);
+
+ nsresult GetBiffStateString(uint32_t biffState, nsAutoCString & biffStateStr);
+
+ nsresult CreateUnreadMessagesNameString(int32_t unreadMessages, nsAutoString &nameString);
+ nsresult CreateArcsOutEnumerator();
+
+ virtual nsresult OnItemAddedOrRemoved(nsIMsgFolder *parentItem, nsISupports *item, bool added);
+
+ nsresult OnUnreadMessagePropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue);
+ nsresult OnTotalMessagePropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue);
+ nsresult OnFolderSizePropertyChanged(nsIRDFResource *folderResource, int64_t oldValue, int64_t newValue);
+ nsresult OnFolderSortOrderPropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue);
+ nsresult NotifyFolderTreeNameChanged(nsIMsgFolder *folder, nsIRDFResource *folderResource, int32_t aUnreadMessages);
+ nsresult NotifyFolderTreeSimpleNameChanged(nsIMsgFolder *folder, nsIRDFResource *folderResource);
+ nsresult NotifyFolderNameChanged(nsIMsgFolder *folder, nsIRDFResource *folderResource);
+ nsresult NotifyAncestors(nsIMsgFolder *aFolder, nsIRDFResource *aPropertyResource, nsIRDFNode *aNode);
+ nsresult GetNumMessagesNode(int32_t numMessages, nsIRDFNode **node);
+ nsresult GetFolderSizeNode(int64_t folderSize, nsIRDFNode **node);
+ nsresult CreateLiterals(nsIRDFService *rdf);
+
+ virtual nsresult GetFolderDisplayName(nsIMsgFolder *folder, nsString& folderName);
+
+ static nsIRDFResource* kNC_Child;
+ static nsIRDFResource* kNC_Folder;
+ static nsIRDFResource* kNC_Name;
+ static nsIRDFResource* kNC_Open;
+ static nsIRDFResource* kNC_FolderTreeName;
+ static nsIRDFResource* kNC_FolderTreeSimpleName;
+ static nsIRDFResource* kNC_NameSort;
+ static nsIRDFResource* kNC_FolderTreeNameSort;
+ static nsIRDFResource* kNC_Columns;
+ static nsIRDFResource* kNC_MSGFolderRoot;
+ static nsIRDFResource* kNC_SpecialFolder;
+ static nsIRDFResource* kNC_ServerType;
+ static nsIRDFResource* kNC_IsDeferred;
+ static nsIRDFResource* kNC_CanCreateFoldersOnServer;
+ static nsIRDFResource* kNC_CanFileMessagesOnServer;
+ static nsIRDFResource* kNC_IsServer;
+ static nsIRDFResource* kNC_IsSecure;
+ static nsIRDFResource* kNC_CanSubscribe;
+ static nsIRDFResource* kNC_SupportsOffline;
+ static nsIRDFResource* kNC_CanFileMessages;
+ static nsIRDFResource* kNC_CanCreateSubfolders;
+ static nsIRDFResource* kNC_CanRename;
+ static nsIRDFResource* kNC_CanCompact;
+ static nsIRDFResource* kNC_TotalMessages;
+ static nsIRDFResource* kNC_TotalUnreadMessages;
+ static nsIRDFResource* kNC_FolderSize;
+ static nsIRDFResource* kNC_Charset;
+ static nsIRDFResource* kNC_BiffState;
+ static nsIRDFResource* kNC_HasUnreadMessages;
+ static nsIRDFResource* kNC_NewMessages;
+ static nsIRDFResource* kNC_SubfoldersHaveUnreadMessages;
+ static nsIRDFResource* kNC_NoSelect;
+ static nsIRDFResource* kNC_ImapShared;
+ static nsIRDFResource* kNC_Synchronize;
+ static nsIRDFResource* kNC_SyncDisabled;
+ static nsIRDFResource* kNC_CanSearchMessages;
+ static nsIRDFResource* kNC_VirtualFolder;
+ static nsIRDFResource* kNC_InVFEditSearchScope;
+ static nsIRDFResource* kNC_UnreadFolders; // maybe should be in nsMsgFlatFolderDataSource?
+ static nsIRDFResource* kNC_FavoriteFolders; // maybe should be in nsMsgFlatFolderDataSource?
+ static nsIRDFResource* kNC_RecentFolders; // maybe should be in nsMsgFlatFolderDataSource?
+ // commands
+ static nsIRDFResource* kNC_Delete;
+ static nsIRDFResource* kNC_ReallyDelete;
+ static nsIRDFResource* kNC_NewFolder;
+ static nsIRDFResource* kNC_GetNewMessages;
+ static nsIRDFResource* kNC_Copy;
+ static nsIRDFResource* kNC_Move;
+ static nsIRDFResource* kNC_CopyFolder;
+ static nsIRDFResource* kNC_MoveFolder;
+ static nsIRDFResource* kNC_MarkAllMessagesRead;
+ static nsIRDFResource* kNC_Compact;
+ static nsIRDFResource* kNC_CompactAll;
+ static nsIRDFResource* kNC_Rename;
+ static nsIRDFResource* kNC_EmptyTrash;
+ static nsIRDFResource* kNC_DownloadFlagged;
+ //Cached literals
+ nsCOMPtr<nsIRDFNode> kTrueLiteral;
+ nsCOMPtr<nsIRDFNode> kFalseLiteral;
+
+ // property atoms
+ static nsIAtom* kTotalMessagesAtom;
+ static nsIAtom* kTotalUnreadMessagesAtom;
+ static nsIAtom* kFolderSizeAtom;
+ static nsIAtom* kBiffStateAtom;
+ static nsIAtom* kSortOrderAtom;
+ static nsIAtom* kNewMessagesAtom;
+ static nsIAtom* kNameAtom;
+ static nsIAtom* kSynchronizeAtom;
+ static nsIAtom* kOpenAtom;
+ static nsIAtom* kIsDeferredAtom;
+ static nsIAtom* kIsSecureAtom;
+ static nsrefcnt gFolderResourceRefCnt;
+ static nsIAtom* kCanFileMessagesAtom;
+ static nsIAtom* kInVFEditSearchScopeAtom;
+
+ nsCOMArray<nsIRDFResource> kFolderArcsOutArray;
+
+};
+
+
+class nsMsgFlatFolderDataSource : public nsMsgFolderDataSource
+{
+public:
+ // constructor could take a filter to filter out folders.
+ nsMsgFlatFolderDataSource();
+ virtual ~nsMsgFlatFolderDataSource();
+ virtual nsresult Init() override;
+ virtual void Cleanup() override;
+
+ NS_IMETHOD GetURI(char* *uri) override;
+ NS_IMETHOD GetTargets(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsISimpleEnumerator** targets) override;
+ NS_IMETHOD GetTarget(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsIRDFNode** target) override;
+
+ NS_IMETHOD HasAssertion(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ bool* hasAssertion) override;
+protected:
+ virtual nsresult GetFolderDisplayName(nsIMsgFolder *folder,
+ nsString& folderName) override;
+ virtual void EnsureFolders();
+ virtual bool WantsThisFolder(nsIMsgFolder *folder);
+ bool ResourceIsOurRoot(nsIRDFResource *resource);
+ virtual nsresult OnItemAddedOrRemoved(nsIMsgFolder *parentItem, nsISupports *item,
+ bool added) override;
+
+ nsCOMArray <nsIMsgFolder> m_folders;
+ nsCOMPtr<nsIRDFResource> m_rootResource; // the resource for our root
+ nsCString m_dsName;
+ bool m_builtFolders;
+};
+
+
+class nsMsgUnreadFoldersDataSource : public nsMsgFlatFolderDataSource
+{
+public:
+ nsMsgUnreadFoldersDataSource() {m_dsName = "mailnewsunreadfolders";}
+ virtual ~nsMsgUnreadFoldersDataSource() {}
+ virtual nsresult NotifyPropertyChanged(nsIRDFResource *resource,
+ nsIRDFResource *propertyResource, nsIRDFNode *newNode,
+ nsIRDFNode *oldNode = nullptr) override;
+protected:
+ virtual bool WantsThisFolder(nsIMsgFolder *folder) override;
+};
+
+class nsMsgFavoriteFoldersDataSource : public nsMsgFlatFolderDataSource
+{
+public:
+ nsMsgFavoriteFoldersDataSource() {m_dsName = "mailnewsfavefolders";}
+ virtual ~nsMsgFavoriteFoldersDataSource() {}
+protected:
+ virtual bool WantsThisFolder(nsIMsgFolder *folder) override;
+};
+
+class nsMsgRecentFoldersDataSource : public nsMsgFlatFolderDataSource
+{
+public:
+ nsMsgRecentFoldersDataSource() {m_dsName = "mailnewsrecentfolders";
+ m_cutOffDate = 0; m_maxNumFolders = 15;}
+ virtual ~nsMsgRecentFoldersDataSource() {}
+ virtual nsresult NotifyPropertyChanged(nsIRDFResource *resource,
+ nsIRDFResource *property, nsIRDFNode *newNode,
+ nsIRDFNode *oldNode) override;
+ NS_IMETHOD OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item) override;
+ virtual void Cleanup() override;
+protected:
+ virtual void EnsureFolders() override;
+ uint32_t m_cutOffDate;
+ uint32_t m_maxNumFolders;
+};
diff --git a/mailnews/base/src/nsMsgFolderNotificationService.cpp b/mailnews/base/src/nsMsgFolderNotificationService.cpp
new file mode 100644
index 000000000..ebf88275a
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderNotificationService.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgFolderNotificationService.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIImapIncomingServer.h"
+
+//
+// nsMsgFolderNotificationService
+//
+NS_IMPL_ISUPPORTS(nsMsgFolderNotificationService, nsIMsgFolderNotificationService)
+
+nsMsgFolderNotificationService::nsMsgFolderNotificationService()
+{
+}
+
+nsMsgFolderNotificationService::~nsMsgFolderNotificationService()
+{
+ /* destructor code */
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::GetHasListeners(bool *aHasListeners)
+{
+ NS_ENSURE_ARG_POINTER(aHasListeners);
+ *aHasListeners = mListeners.Length() > 0;
+ return NS_OK;
+}
+
+
+/* void addListener (in nsIMsgFolderListener aListener, in msgFolderListenerFlag flags); */
+NS_IMETHODIMP nsMsgFolderNotificationService::AddListener(nsIMsgFolderListener *aListener,
+ msgFolderListenerFlag aFlags)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ MsgFolderListener listener(aListener, aFlags);
+ mListeners.AppendElementUnlessExists(listener);
+ return NS_OK;
+}
+
+/* void removeListener (in nsIMsgFolderListener aListener); */
+NS_IMETHODIMP nsMsgFolderNotificationService::RemoveListener(nsIMsgFolderListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+#define NOTIFY_MSGFOLDER_LISTENERS(propertyflag_, propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<MsgFolderListener>::ForwardIterator iter(mListeners); \
+ while (iter.HasMore()) { \
+ const MsgFolderListener &listener = iter.GetNext(); \
+ if (listener.mFlags & propertyflag_) \
+ listener.mListener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+/* void notifyMsgAdded (in nsIMsgDBHdr aMsg); */
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgAdded(nsIMsgDBHdr *aMsg)
+{
+ NOTIFY_MSGFOLDER_LISTENERS(msgAdded, MsgAdded, (aMsg));
+ return NS_OK;
+}
+
+/* void notifyMsgsClassified (in */
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgsClassified(
+ nsIArray *aMsgs, bool aJunkProcessed, bool aTraitProcessed)
+{
+ NOTIFY_MSGFOLDER_LISTENERS(msgsClassified, MsgsClassified,
+ (aMsgs, aJunkProcessed, aTraitProcessed));
+ return NS_OK;
+}
+
+/* void notifyMsgsDeleted (in nsIArray aMsgs); */
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgsDeleted(nsIArray *aMsgs)
+{
+ NOTIFY_MSGFOLDER_LISTENERS(msgsDeleted, MsgsDeleted, (aMsgs));
+ return NS_OK;
+}
+
+/* void notifyMsgsMoveCopyCompleted (in boolean aMove, in nsIArray aSrcMsgs,
+ in nsIMsgFolder aDestFolder); */
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgsMoveCopyCompleted(
+ bool aMove, nsIArray *aSrcMsgs, nsIMsgFolder *aDestFolder,
+ nsIArray *aDestMsgs)
+{
+ uint32_t count = mListeners.Length();
+
+ // IMAP delete model means that a "move" isn't really a move, it is a copy,
+ // followed by storing the IMAP deleted flag on the message.
+ bool isReallyMove = aMove;
+ if (count > 0 && aMove)
+ {
+ nsresult rv;
+ // Assume that all the source messages are from the same server.
+ nsCOMPtr<nsIMsgDBHdr> message(do_QueryElementAt(aSrcMsgs, 0, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = message->GetFolder(getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(msgFolder));
+ if (imapFolder)
+ {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ imapFolder->GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer)
+ {
+ nsMsgImapDeleteModel deleteModel;
+ imapServer->GetDeleteModel(&deleteModel);
+ if (deleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ isReallyMove = false;
+ }
+ }
+ }
+
+ NOTIFY_MSGFOLDER_LISTENERS(msgsMoveCopyCompleted, MsgsMoveCopyCompleted,
+ (isReallyMove, aSrcMsgs, aDestFolder, aDestMsgs));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFolderNotificationService::NotifyMsgKeyChanged(nsMsgKey aOldKey,
+ nsIMsgDBHdr *aNewHdr)
+{
+ NOTIFY_MSGFOLDER_LISTENERS(msgKeyChanged, MsgKeyChanged, (aOldKey, aNewHdr));
+ return NS_OK;
+}
+
+/* void notifyFolderAdded(in nsIMsgFolder aFolder); */
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderAdded(nsIMsgFolder *aFolder)
+{
+ NOTIFY_MSGFOLDER_LISTENERS(folderAdded, FolderAdded, (aFolder));
+ return NS_OK;
+}
+
+/* void notifyFolderDeleted(in nsIMsgFolder aFolder); */
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderDeleted(nsIMsgFolder *aFolder)
+{
+ NOTIFY_MSGFOLDER_LISTENERS(folderDeleted, FolderDeleted, (aFolder));
+ return NS_OK;
+}
+
+/* void notifyFolderMoveCopyCompleted(in boolean aMove, in nsIMsgFolder aSrcFolder, in nsIMsgFolder aDestFolder); */
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderMoveCopyCompleted(bool aMove, nsIMsgFolder *aSrcFolder, nsIMsgFolder *aDestFolder)
+{
+ NOTIFY_MSGFOLDER_LISTENERS(folderMoveCopyCompleted, FolderMoveCopyCompleted,
+ (aMove, aSrcFolder, aDestFolder));
+ return NS_OK;
+}
+
+/* void notifyFolderRenamed (in nsIMsgFolder aOrigFolder, in nsIMsgFolder aNewFolder); */
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderRenamed(nsIMsgFolder *aOrigFolder, nsIMsgFolder *aNewFolder)
+{
+ NOTIFY_MSGFOLDER_LISTENERS(folderRenamed, FolderRenamed, (aOrigFolder, aNewFolder));
+ return NS_OK;
+}
+
+/* void notifyItemEvent (in nsISupports aItem, in string aEvent, in nsISupports aData); */
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyItemEvent(nsISupports *aItem, const nsACString &aEvent, nsISupports *aData)
+{
+ NOTIFY_MSGFOLDER_LISTENERS(itemEvent, ItemEvent, (aItem, aEvent, aData));
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMsgFolderNotificationService.h b/mailnews/base/src/nsMsgFolderNotificationService.h
new file mode 100644
index 000000000..61fdd842b
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderNotificationService.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgFolderNotificationService_h__
+#define nsMsgFolderNotificationService_h__
+
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMsgFolderListener.h"
+#include "nsTObserverArray.h"
+#include "nsCOMPtr.h"
+
+class nsMsgFolderNotificationService final : public nsIMsgFolderNotificationService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGFOLDERNOTIFICATIONSERVICE
+
+ nsMsgFolderNotificationService();
+
+private:
+ ~nsMsgFolderNotificationService();
+ struct MsgFolderListener
+ {
+ nsCOMPtr<nsIMsgFolderListener> mListener;
+ msgFolderListenerFlag mFlags;
+
+ MsgFolderListener(nsIMsgFolderListener *aListener, msgFolderListenerFlag aFlags)
+ : mListener(aListener), mFlags(aFlags) {}
+ MsgFolderListener(const MsgFolderListener &aListener)
+ : mListener(aListener.mListener), mFlags(aListener.mFlags) {}
+ ~MsgFolderListener() {}
+
+ int operator==(nsIMsgFolderListener* aListener) const {
+ return mListener == aListener;
+ }
+ int operator==(const MsgFolderListener &aListener) const {
+ return mListener == aListener.mListener;
+ }
+ };
+
+ nsTObserverArray<MsgFolderListener> mListeners;
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgGroupThread.cpp b/mailnews/base/src/nsMsgGroupThread.cpp
new file mode 100644
index 000000000..c68a6329d
--- /dev/null
+++ b/mailnews/base/src/nsMsgGroupThread.cpp
@@ -0,0 +1,856 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgGroupThread.h"
+#include "nsMsgDBView.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgUtils.h"
+
+NS_IMPL_ISUPPORTS(nsMsgGroupThread, nsIMsgThread)
+
+nsMsgGroupThread::nsMsgGroupThread()
+{
+ Init();
+}
+nsMsgGroupThread::nsMsgGroupThread(nsIMsgDatabase *db)
+{
+ m_db = db;
+ Init();
+}
+
+void nsMsgGroupThread::Init()
+{
+ m_threadKey = nsMsgKey_None;
+ m_threadRootKey = nsMsgKey_None;
+ m_numUnreadChildren = 0;
+ m_flags = 0;
+ m_newestMsgDate = 0;
+ m_dummy = false;
+}
+
+nsMsgGroupThread::~nsMsgGroupThread()
+{
+}
+
+NS_IMETHODIMP nsMsgGroupThread::SetThreadKey(nsMsgKey threadKey)
+{
+ m_threadKey = threadKey;
+ // by definition, the initial thread key is also the thread root key.
+ m_threadRootKey = threadKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetThreadKey(nsMsgKey *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_threadKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetFlags(uint32_t *aFlags)
+{
+ NS_ENSURE_ARG_POINTER(aFlags);
+ *aFlags = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::SetFlags(uint32_t aFlags)
+{
+ m_flags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::SetSubject(const nsACString& aSubject)
+{
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetSubject(nsACString& result)
+{
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetNumChildren(uint32_t *aNumChildren)
+{
+ NS_ENSURE_ARG_POINTER(aNumChildren);
+ *aNumChildren = m_keys.Length(); // - ((m_dummy) ? 1 : 0);
+ return NS_OK;
+}
+
+uint32_t nsMsgGroupThread::NumRealChildren()
+{
+ return m_keys.Length() - ((m_dummy) ? 1 : 0);
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetNumUnreadChildren (uint32_t *aNumUnreadChildren)
+{
+ NS_ENSURE_ARG_POINTER(aNumUnreadChildren);
+ *aNumUnreadChildren = m_numUnreadChildren;
+ return NS_OK;
+}
+
+void nsMsgGroupThread::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr)
+{
+ nsMsgKey msgKey;
+ hdr->GetMessageKey(&msgKey);
+ m_keys.InsertElementAt(index, msgKey);
+}
+
+void nsMsgGroupThread::SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr)
+{
+ nsMsgKey msgKey;
+ hdr->GetMessageKey(&msgKey);
+ m_keys[index] = msgKey;
+}
+
+nsMsgViewIndex nsMsgGroupThread::FindMsgHdr(nsIMsgDBHdr *hdr)
+{
+ nsMsgKey msgKey;
+ hdr->GetMessageKey(&msgKey);
+ return (nsMsgViewIndex)m_keys.IndexOf(msgKey);
+}
+
+NS_IMETHODIMP nsMsgGroupThread::AddChild(nsIMsgDBHdr *child, nsIMsgDBHdr *inReplyTo, bool threadInThread,
+ nsIDBChangeAnnouncer *announcer)
+{
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsMsgViewIndex nsMsgGroupThread::AddMsgHdrInDateOrder(nsIMsgDBHdr *child, nsMsgDBView *view)
+{
+ nsMsgKey newHdrKey;
+ child->GetMessageKey(&newHdrKey);
+ uint32_t insertIndex = 0;
+ // since we're sorted by date, we could do a binary search for the
+ // insert point. Or, we could start at the end...
+ if (m_keys.Length() > 0)
+ {
+ nsMsgViewSortTypeValue sortType;
+ nsMsgViewSortOrderValue sortOrder;
+ (void) view->GetSortType(&sortType);
+ (void) view->GetSortOrder(&sortOrder);
+ // historical behavior is ascending date order unless our primary sort is
+ // on date
+ nsMsgViewSortOrderValue threadSortOrder =
+ (sortType == nsMsgViewSortType::byDate
+ && sortOrder == nsMsgViewSortOrder::descending) ?
+ nsMsgViewSortOrder::descending : nsMsgViewSortOrder::ascending;
+ // new behavior is tricky and uses the secondary sort order if the secondary
+ // sort is on the date
+ nsMsgViewSortTypeValue secondarySortType;
+ nsMsgViewSortOrderValue secondarySortOrder;
+ (void) view->GetSecondarySortType(&secondarySortType);
+ (void) view->GetSecondarySortOrder(&secondarySortOrder);
+ if (secondarySortType == nsMsgViewSortType::byDate)
+ threadSortOrder = secondarySortOrder;
+ // sort by date within group.
+ insertIndex = GetInsertIndexFromView(view, child, threadSortOrder);
+ }
+ m_keys.InsertElementAt(insertIndex, newHdrKey);
+ if (!insertIndex)
+ m_threadRootKey = newHdrKey;
+ return insertIndex;
+}
+
+nsMsgViewIndex
+nsMsgGroupThread::GetInsertIndexFromView(nsMsgDBView *view,
+ nsIMsgDBHdr *child,
+ nsMsgViewSortOrderValue threadSortOrder)
+{
+ return view->GetInsertIndexHelper(child, m_keys, nullptr, threadSortOrder, nsMsgViewSortType::byDate);
+}
+
+nsMsgViewIndex nsMsgGroupThread::AddChildFromGroupView(nsIMsgDBHdr *child, nsMsgDBView *view)
+{
+ uint32_t newHdrFlags = 0;
+ uint32_t msgDate;
+ nsMsgKey newHdrKey = 0;
+
+ child->GetFlags(&newHdrFlags);
+ child->GetMessageKey(&newHdrKey);
+ child->GetDateInSeconds(&msgDate);
+ if (msgDate > m_newestMsgDate)
+ SetNewestMsgDate(msgDate);
+
+ child->AndFlags(~(nsMsgMessageFlags::Watched), &newHdrFlags);
+ uint32_t numChildren;
+
+ // get the num children before we add the new header.
+ GetNumChildren(&numChildren);
+
+ // if this is an empty thread, set the root key to this header's key
+ if (numChildren == 0)
+ m_threadRootKey = newHdrKey;
+
+ if (! (newHdrFlags & nsMsgMessageFlags::Read))
+ ChangeUnreadChildCount(1);
+
+ return AddMsgHdrInDateOrder(child, view);
+}
+
+nsresult nsMsgGroupThread::ReparentNonReferenceChildrenOf(nsIMsgDBHdr *topLevelHdr, nsMsgKey newParentKey,
+ nsIDBChangeAnnouncer *announcer)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (aIndex >= m_keys.Length())
+ return NS_ERROR_INVALID_ARG;
+ *aResult = m_keys[aIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **aResult)
+{
+ if (aIndex >= m_keys.Length())
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ return m_db->GetMsgHdrForKey(m_keys[aIndex], aResult);
+}
+
+
+NS_IMETHODIMP nsMsgGroupThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr **aResult)
+{
+ return GetChildHdrAt(m_keys.IndexOf(msgKey), aResult);
+}
+
+NS_IMETHODIMP nsMsgGroupThread::RemoveChildAt(uint32_t aIndex)
+{
+ NS_ENSURE_TRUE(aIndex < m_keys.Length(), NS_MSG_MESSAGE_NOT_FOUND);
+
+ m_keys.RemoveElementAt(aIndex);
+ return NS_OK;
+}
+
+nsresult nsMsgGroupThread::RemoveChild(nsMsgKey msgKey)
+{
+ m_keys.RemoveElement(msgKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer)
+{
+ NS_ENSURE_ARG_POINTER(child);
+
+ uint32_t flags;
+ nsMsgKey key;
+
+ child->GetFlags(&flags);
+ child->GetMessageKey(&key);
+
+ // if this was the newest msg, clear the newest msg date so we'll recalc.
+ uint32_t date;
+ child->GetDateInSeconds(&date);
+ if (date == m_newestMsgDate)
+ SetNewestMsgDate(0);
+
+ if (!(flags & nsMsgMessageFlags::Read))
+ ChangeUnreadChildCount(-1);
+ nsMsgViewIndex threadIndex = FindMsgHdr(child);
+ bool wasFirstChild = threadIndex == 0;
+ nsresult rv = RemoveChildAt(threadIndex);
+ // if we're deleting the root of a dummy thread, need to update the threadKey
+ // and the dummy header at position 0
+ if (m_dummy && wasFirstChild && m_keys.Length() > 1)
+ {
+ nsIMsgDBHdr *newRootChild;
+ rv = GetChildHdrAt(1, &newRootChild);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetMsgHdrAt(0, newRootChild);
+ }
+
+ return rv;
+}
+
+nsresult nsMsgGroupThread::ReparentChildrenOf(nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeAnnouncer *announcer)
+{
+ nsresult rv = NS_OK;
+
+ uint32_t numChildren;
+ uint32_t childIndex = 0;
+
+ GetNumChildren(&numChildren);
+
+ nsCOMPtr <nsIMsgDBHdr> curHdr;
+ if (numChildren > 0)
+ {
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr)
+ {
+ nsMsgKey threadParent;
+
+ curHdr->GetThreadParent(&threadParent);
+ if (threadParent == oldParent)
+ {
+ nsMsgKey curKey;
+
+ curHdr->SetThreadParent(newParent);
+ curHdr->GetMessageKey(&curKey);
+ if (announcer)
+ announcer->NotifyParentChangedAll(curKey, oldParent, newParent, nullptr);
+ // if the old parent was the root of the thread, then only the first child gets
+ // promoted to root, and other children become children of the new root.
+ if (newParent == nsMsgKey_None)
+ {
+ m_threadRootKey = curKey;
+ newParent = curKey;
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::MarkChildRead(bool bRead)
+{
+ ChangeUnreadChildCount(bRead ? -1 : 1);
+ return NS_OK;
+}
+
+// this could be moved into utils, because I think it's the same as the db impl.
+class nsMsgGroupThreadEnumerator : public nsISimpleEnumerator {
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsMsgGroupThreadEnumerator methods:
+ typedef nsresult (*nsMsgGroupThreadEnumeratorFilter)(nsIMsgDBHdr* hdr, void* closure);
+
+ nsMsgGroupThreadEnumerator(nsMsgGroupThread *thread, nsMsgKey startKey,
+ nsMsgGroupThreadEnumeratorFilter filter, void* closure);
+ int32_t MsgKeyFirstChildIndex(nsMsgKey inMsgKey);
+
+protected:
+ virtual ~nsMsgGroupThreadEnumerator();
+
+ nsresult Prefetch();
+
+ nsCOMPtr <nsIMsgDBHdr> mResultHdr;
+ nsMsgGroupThread* mThread;
+ nsMsgKey mThreadParentKey;
+ nsMsgKey mFirstMsgKey;
+ int32_t mChildIndex;
+ bool mDone;
+ bool mNeedToPrefetch;
+ nsMsgGroupThreadEnumeratorFilter mFilter;
+ void* mClosure;
+ bool mFoundChildren;
+};
+
+nsMsgGroupThreadEnumerator::nsMsgGroupThreadEnumerator(nsMsgGroupThread *thread, nsMsgKey startKey,
+ nsMsgGroupThreadEnumeratorFilter filter, void* closure)
+ : mDone(false),
+ mFilter(filter), mClosure(closure), mFoundChildren(false)
+{
+ mThreadParentKey = startKey;
+ mChildIndex = 0;
+ mThread = thread;
+ mNeedToPrefetch = true;
+ mFirstMsgKey = nsMsgKey_None;
+
+ nsresult rv = mThread->GetRootHdr(nullptr, getter_AddRefs(mResultHdr));
+
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ mResultHdr->GetMessageKey(&mFirstMsgKey);
+
+ uint32_t numChildren;
+ mThread->GetNumChildren(&numChildren);
+
+ if (mThreadParentKey != nsMsgKey_None)
+ {
+ nsMsgKey msgKey = nsMsgKey_None;
+ uint32_t childIndex = 0;
+
+
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ {
+ mResultHdr->GetMessageKey(&msgKey);
+
+ if (msgKey == startKey)
+ {
+ mChildIndex = MsgKeyFirstChildIndex(msgKey);
+ mDone = (mChildIndex < 0);
+ break;
+ }
+
+ if (mDone)
+ break;
+
+ }
+ else
+ NS_ASSERTION(false, "couldn't get child from thread");
+ }
+ }
+
+#ifdef DEBUG_bienvenu1
+ nsCOMPtr <nsIMsgDBHdr> child;
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ nsMsgKey threadParent;
+ nsMsgKey msgKey;
+ // we're only doing one level of threading, so check if caller is
+ // asking for children of the first message in the thread or not.
+ // if not, we will tell him there are no children.
+ child->GetMessageKey(&msgKey);
+ child->GetThreadParent(&threadParent);
+
+ printf("index = %ld key = %ld parent = %lx\n", childIndex, msgKey, threadParent);
+ }
+ }
+#endif
+ NS_ADDREF(thread);
+}
+
+nsMsgGroupThreadEnumerator::~nsMsgGroupThreadEnumerator()
+{
+ NS_RELEASE(mThread);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgGroupThreadEnumerator, nsISimpleEnumerator)
+
+
+int32_t nsMsgGroupThreadEnumerator::MsgKeyFirstChildIndex(nsMsgKey inMsgKey)
+{
+ // if (msgKey != mThreadParentKey)
+ // mDone = true;
+ // look through rest of thread looking for a child of this message.
+ // If the inMsgKey is the first message in the thread, then all children
+ // without parents are considered to be children of inMsgKey.
+ // Otherwise, only true children qualify.
+ uint32_t numChildren;
+ nsCOMPtr <nsIMsgDBHdr> curHdr;
+ int32_t firstChildIndex = -1;
+
+ mThread->GetNumChildren(&numChildren);
+
+ // if this is the first message in the thread, just check if there's more than
+ // one message in the thread.
+ // if (inMsgKey == mThread->m_threadRootKey)
+ // return (numChildren > 1) ? 1 : -1;
+
+ for (uint32_t curChildIndex = 0; curChildIndex < numChildren; curChildIndex++)
+ {
+ nsresult rv = mThread->GetChildHdrAt(curChildIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr)
+ {
+ nsMsgKey parentKey;
+
+ curHdr->GetThreadParent(&parentKey);
+ if (parentKey == inMsgKey)
+ {
+ firstChildIndex = curChildIndex;
+ break;
+ }
+ }
+ }
+#ifdef DEBUG_bienvenu1
+ printf("first child index of %ld = %ld\n", inMsgKey, firstChildIndex);
+#endif
+ return firstChildIndex;
+}
+
+NS_IMETHODIMP nsMsgGroupThreadEnumerator::GetNext(nsISupports **aItem)
+{
+ if (!aItem)
+ return NS_ERROR_NULL_POINTER;
+ nsresult rv = NS_OK;
+
+ if (mNeedToPrefetch)
+ rv = Prefetch();
+
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ {
+ *aItem = mResultHdr;
+ NS_ADDREF(*aItem);
+ mNeedToPrefetch = true;
+ }
+ return rv;
+}
+
+nsresult nsMsgGroupThreadEnumerator::Prefetch()
+{
+ nsresult rv=NS_OK; // XXX or should this default to an error?
+ mResultHdr = nullptr;
+ if (mThreadParentKey == nsMsgKey_None)
+ {
+ rv = mThread->GetRootHdr(&mChildIndex, getter_AddRefs(mResultHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && mResultHdr, "better be able to get root hdr");
+ mChildIndex = 0; // since root can be anywhere, set mChildIndex to 0.
+ }
+ else if (!mDone)
+ {
+ uint32_t numChildren;
+ mThread->GetNumChildren(&numChildren);
+
+ while ((uint32_t)mChildIndex < numChildren)
+ {
+ rv = mThread->GetChildHdrAt(mChildIndex++, getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ {
+ nsMsgKey parentKey;
+ nsMsgKey curKey;
+
+ if (mFilter && NS_FAILED(mFilter(mResultHdr, mClosure))) {
+ mResultHdr = nullptr;
+ continue;
+ }
+
+ mResultHdr->GetThreadParent(&parentKey);
+ mResultHdr->GetMessageKey(&curKey);
+ // if the parent is the same as the msg we're enumerating over,
+ // or the parentKey isn't set, and we're iterating over the top
+ // level message in the thread, then leave mResultHdr set to cur msg.
+ if (parentKey == mThreadParentKey ||
+ (parentKey == nsMsgKey_None
+ && mThreadParentKey == mFirstMsgKey && curKey != mThreadParentKey))
+ break;
+ mResultHdr = nullptr;
+ }
+ else
+ NS_ASSERTION(false, "better be able to get child");
+ }
+ if (!mResultHdr && mThreadParentKey == mFirstMsgKey && !mFoundChildren && numChildren > 1)
+ {
+// mThread->ReparentMsgsWithInvalidParent(numChildren, mThreadParentKey);
+ }
+ }
+ if (!mResultHdr)
+ {
+ mDone = true;
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv))
+ {
+ mDone = true;
+ return rv;
+ }
+ else
+ mNeedToPrefetch = false;
+ mFoundChildren = true;
+
+#ifdef DEBUG_bienvenu1
+ nsMsgKey debugMsgKey;
+ mResultHdr->GetMessageKey(&debugMsgKey);
+ printf("next for %ld = %ld\n", mThreadParentKey, debugMsgKey);
+#endif
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgGroupThreadEnumerator::HasMoreElements(bool *aResult)
+{
+ if (!aResult)
+ return NS_ERROR_NULL_POINTER;
+ if (mNeedToPrefetch)
+ Prefetch();
+ *aResult = !mDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::EnumerateMessages(nsMsgKey parentKey, nsISimpleEnumerator* *result)
+{
+ nsMsgGroupThreadEnumerator* e = new nsMsgGroupThreadEnumerator(this, parentKey, nullptr, nullptr);
+ if (e == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(e);
+ *result = e;
+
+ return NS_OK;
+}
+#if 0
+nsresult nsMsgGroupThread::ReparentMsgsWithInvalidParent(uint32_t numChildren, nsMsgKey threadParentKey)
+{
+ nsresult ret = NS_OK;
+ // run through looking for messages that don't have a correct parent,
+ // i.e., a parent that's in the thread!
+ for (int32_t childIndex = 0; childIndex < (int32_t) numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> curChild;
+ ret = GetChildHdrAt(childIndex, getter_AddRefs(curChild));
+ if (NS_SUCCEEDED(ret) && curChild)
+ {
+ nsMsgKey parentKey;
+ nsCOMPtr <nsIMsgDBHdr> parent;
+
+ curChild->GetThreadParent(&parentKey);
+
+ if (parentKey != nsMsgKey_None)
+ {
+ GetChild(parentKey, getter_AddRefs(parent));
+ if (!parent)
+ curChild->SetThreadParent(threadParentKey);
+ }
+ }
+ }
+ return ret;
+}
+#endif
+NS_IMETHODIMP nsMsgGroupThread::GetRootHdr(int32_t *resultIndex, nsIMsgDBHdr **result)
+{
+ if (!result)
+ return NS_ERROR_NULL_POINTER;
+
+ *result = nullptr;
+
+ if (m_threadRootKey != nsMsgKey_None)
+ {
+ nsresult ret = GetChildHdrForKey(m_threadRootKey, result, resultIndex);
+ if (NS_SUCCEEDED(ret) && *result)
+ return ret;
+ else
+ {
+ printf("need to reset thread root key\n");
+ uint32_t numChildren;
+ nsMsgKey threadParentKey = nsMsgKey_None;
+ GetNumChildren(&numChildren);
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> curChild;
+ ret = GetChildHdrAt(childIndex, getter_AddRefs(curChild));
+ if (NS_SUCCEEDED(ret) && curChild)
+ {
+ nsMsgKey parentKey;
+
+ curChild->GetThreadParent(&parentKey);
+ if (parentKey == nsMsgKey_None)
+ {
+ NS_ASSERTION(!(*result), "two top level msgs, not good");
+ curChild->GetMessageKey(&threadParentKey);
+ m_threadRootKey = threadParentKey;
+ if (resultIndex)
+ *resultIndex = childIndex;
+ *result = curChild;
+ NS_ADDREF(*result);
+// ReparentMsgsWithInvalidParent(numChildren, threadParentKey);
+ // return NS_OK;
+ }
+ }
+ }
+ if (*result)
+ {
+ return NS_OK;
+ }
+ }
+ // if we can't get the thread root key, we'll just get the first hdr.
+ // there's a bug where sometimes we weren't resetting the thread root key
+ // when removing the thread root key.
+ }
+ if (resultIndex)
+ *resultIndex = 0;
+ return GetChildHdrAt(0, result);
+}
+
+nsresult nsMsgGroupThread::ChangeUnreadChildCount(int32_t delta)
+{
+ m_numUnreadChildren += delta;
+ return NS_OK;
+}
+
+nsresult nsMsgGroupThread::GetChildHdrForKey(nsMsgKey desiredKey, nsIMsgDBHdr **result, int32_t *resultIndex)
+{
+ uint32_t numChildren;
+ uint32_t childIndex = 0;
+ nsresult rv = NS_OK; // XXX or should this default to an error?
+
+ if (!result)
+ return NS_ERROR_NULL_POINTER;
+
+ GetNumChildren(&numChildren);
+
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = GetChildHdrAt(childIndex, result);
+ if (NS_SUCCEEDED(rv) && *result)
+ {
+ nsMsgKey msgKey;
+ // we're only doing one level of threading, so check if caller is
+ // asking for children of the first message in the thread or not.
+ // if not, we will tell him there are no children.
+ (*result)->GetMessageKey(&msgKey);
+
+ if (msgKey == desiredKey)
+ break;
+ NS_RELEASE(*result);
+ }
+ }
+ if (resultIndex)
+ *resultIndex = (int32_t) childIndex;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetFirstUnreadChild(nsIMsgDBHdr **result)
+{
+ NS_ENSURE_ARG(result);
+ uint32_t numChildren;
+ nsresult rv = NS_OK;
+
+ GetNumChildren(&numChildren);
+
+ if ((int32_t) numChildren < 0)
+ numChildren = 0;
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ nsMsgKey msgKey;
+ child->GetMessageKey(&msgKey);
+
+ bool isRead;
+ rv = m_db->IsRead(msgKey, &isRead);
+ if (NS_SUCCEEDED(rv) && !isRead)
+ {
+ *result = child;
+ NS_ADDREF(*result);
+ break;
+ }
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetNewestMsgDate(uint32_t *aResult)
+{
+ // if this hasn't been set, figure it out by enumerating the msgs in the thread.
+ if (!m_newestMsgDate)
+ {
+ uint32_t numChildren;
+ nsresult rv = NS_OK;
+
+ GetNumChildren(&numChildren);
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ uint32_t msgDate;
+ child->GetDateInSeconds(&msgDate);
+ if (msgDate > m_newestMsgDate)
+ m_newestMsgDate = msgDate;
+ }
+ }
+
+ }
+ *aResult = m_newestMsgDate;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgGroupThread::SetNewestMsgDate(uint32_t aNewestMsgDate)
+{
+ m_newestMsgDate = aNewestMsgDate;
+ return NS_OK;
+}
+
+nsMsgXFGroupThread::nsMsgXFGroupThread()
+{
+}
+
+nsMsgXFGroupThread::~nsMsgXFGroupThread()
+{
+}
+
+NS_IMETHODIMP nsMsgXFGroupThread::GetNumChildren(uint32_t *aNumChildren)
+{
+ NS_ENSURE_ARG_POINTER(aNumChildren);
+ *aNumChildren = m_folders.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFGroupThread::GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **aResult)
+{
+ if (aIndex >= m_folders.Length())
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ return m_folders.ObjectAt(aIndex)->GetMessageHeader(m_keys[aIndex], aResult);
+}
+
+NS_IMETHODIMP nsMsgXFGroupThread::GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult)
+{
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgXFGroupThread::RemoveChildAt(uint32_t aIndex)
+{
+ NS_ENSURE_TRUE(aIndex < m_folders.Length(), NS_MSG_MESSAGE_NOT_FOUND);
+
+ nsresult rv = nsMsgGroupThread::RemoveChildAt(aIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_folders.RemoveElementAt(aIndex);
+ return NS_OK;
+}
+
+void nsMsgXFGroupThread::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ m_folders.InsertObjectAt(folder, index);
+ nsMsgGroupThread::InsertMsgHdrAt(index, hdr);
+}
+
+void nsMsgXFGroupThread::SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ m_folders.ReplaceObjectAt(folder, index);
+ nsMsgGroupThread::SetMsgHdrAt(index, hdr);
+}
+
+nsMsgViewIndex nsMsgXFGroupThread::FindMsgHdr(nsIMsgDBHdr *hdr)
+{
+ nsMsgKey msgKey;
+ hdr->GetMessageKey(&msgKey);
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ size_t index = 0;
+ while (true) {
+ index = m_keys.IndexOf(msgKey, index);
+ if (index == m_keys.NoIndex || m_folders[index] == folder)
+ break;
+ index++;
+ }
+ return (nsMsgViewIndex)index;
+}
+
+nsMsgViewIndex nsMsgXFGroupThread::AddMsgHdrInDateOrder(nsIMsgDBHdr *child, nsMsgDBView *view)
+{
+ nsMsgViewIndex insertIndex = nsMsgGroupThread::AddMsgHdrInDateOrder(child, view);
+ nsCOMPtr<nsIMsgFolder> folder;
+ child->GetFolder(getter_AddRefs(folder));
+ m_folders.InsertObjectAt(folder, insertIndex);
+ return insertIndex;
+}
+nsMsgViewIndex
+nsMsgXFGroupThread::GetInsertIndexFromView(nsMsgDBView *view,
+ nsIMsgDBHdr *child,
+ nsMsgViewSortOrderValue threadSortOrder)
+{
+ return view->GetInsertIndexHelper(child, m_keys, &m_folders, threadSortOrder, nsMsgViewSortType::byDate);
+}
+
diff --git a/mailnews/base/src/nsMsgGroupThread.h b/mailnews/base/src/nsMsgGroupThread.h
new file mode 100644
index 000000000..076d7432a
--- /dev/null
+++ b/mailnews/base/src/nsMsgGroupThread.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsCOMArray.h"
+#include "nsIMsgThread.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgDBView.h"
+
+class nsMsgGroupView;
+
+class nsMsgGroupThread : public nsIMsgThread
+{
+public:
+ friend class nsMsgGroupView;
+
+ nsMsgGroupThread();
+ explicit nsMsgGroupThread(nsIMsgDatabase *db);
+
+ NS_DECL_NSIMSGTHREAD
+ NS_DECL_ISUPPORTS
+
+protected:
+ virtual ~nsMsgGroupThread();
+
+ void Init();
+ nsMsgViewIndex AddChildFromGroupView(nsIMsgDBHdr *child, nsMsgDBView *view);
+ nsresult RemoveChild(nsMsgKey msgKey);
+ nsresult RerootThread(nsIMsgDBHdr *newParentOfOldRoot, nsIMsgDBHdr *oldRoot, nsIDBChangeAnnouncer *announcer);
+
+ virtual nsMsgViewIndex AddMsgHdrInDateOrder(nsIMsgDBHdr *child, nsMsgDBView *view);
+ virtual nsMsgViewIndex GetInsertIndexFromView(nsMsgDBView *view,
+ nsIMsgDBHdr *child,
+ nsMsgViewSortOrderValue threadSortOrder);
+ nsresult ReparentNonReferenceChildrenOf(nsIMsgDBHdr *topLevelHdr, nsMsgKey newParentKey,
+ nsIDBChangeAnnouncer *announcer);
+
+ nsresult ReparentChildrenOf(nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeAnnouncer *announcer);
+ nsresult ChangeUnreadChildCount(int32_t delta);
+ nsresult GetChildHdrForKey(nsMsgKey desiredKey, nsIMsgDBHdr **result, int32_t *resultIndex);
+ uint32_t NumRealChildren();
+ virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr);
+ virtual void SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr);
+ virtual nsMsgViewIndex FindMsgHdr(nsIMsgDBHdr *hdr);
+
+ nsMsgKey m_threadKey;
+ uint32_t m_numUnreadChildren;
+ uint32_t m_flags;
+ nsMsgKey m_threadRootKey;
+ uint32_t m_newestMsgDate;
+ nsTArray<nsMsgKey> m_keys;
+ bool m_dummy; // top level msg is a dummy, e.g., grouped by age.
+ nsCOMPtr <nsIMsgDatabase> m_db; // should we make a weak ref or just a ptr?
+};
+
+class nsMsgXFGroupThread : public nsMsgGroupThread
+{
+public:
+ nsMsgXFGroupThread();
+
+ NS_IMETHOD GetNumChildren(uint32_t *aNumChildren) override;
+ NS_IMETHOD GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult) override;
+ NS_IMETHOD GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **aResult) override;
+ NS_IMETHOD RemoveChildAt(uint32_t aIndex) override;
+protected:
+ virtual ~nsMsgXFGroupThread();
+
+ virtual void InsertMsgHdrAt(nsMsgViewIndex index,
+ nsIMsgDBHdr *hdr) override;
+ virtual void SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr) override;
+ virtual nsMsgViewIndex FindMsgHdr(nsIMsgDBHdr *hdr) override;
+ virtual nsMsgViewIndex AddMsgHdrInDateOrder(nsIMsgDBHdr *child,
+ nsMsgDBView *view) override;
+ virtual nsMsgViewIndex GetInsertIndexFromView(nsMsgDBView *view,
+ nsIMsgDBHdr *child,
+ nsMsgViewSortOrderValue threadSortOrder
+ ) override;
+
+ nsCOMArray<nsIMsgFolder> m_folders;
+};
+
diff --git a/mailnews/base/src/nsMsgGroupView.cpp b/mailnews/base/src/nsMsgGroupView.cpp
new file mode 100644
index 000000000..f3d9a2704
--- /dev/null
+++ b/mailnews/base/src/nsMsgGroupView.cpp
@@ -0,0 +1,1024 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgUtils.h"
+#include "nsMsgGroupView.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgThread.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgSearchSession.h"
+#include "nsMsgGroupThread.h"
+#include "nsITreeColumns.h"
+#include "nsMsgMessageFlags.h"
+#include <plhash.h>
+#include "mozilla/Attributes.h"
+
+#define MSGHDR_CACHE_LOOK_AHEAD_SIZE 25 // Allocate this more to avoid reallocation on new mail.
+#define MSGHDR_CACHE_MAX_SIZE 8192 // Max msghdr cache entries.
+#define MSGHDR_CACHE_DEFAULT_SIZE 100
+
+nsMsgGroupView::nsMsgGroupView()
+{
+ m_dayChanged = false;
+ m_lastCurExplodedTime.tm_mday = 0;
+}
+
+nsMsgGroupView::~nsMsgGroupView()
+{
+}
+
+NS_IMETHODIMP nsMsgGroupView::Open(nsIMsgFolder *aFolder, nsMsgViewSortTypeValue aSortType, nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, int32_t *aCount)
+{
+ nsresult rv = nsMsgDBView::Open(aFolder, aSortType, aSortOrder, aViewFlags, aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ PersistFolderInfo(getter_AddRefs(dbFolderInfo));
+
+ nsCOMPtr <nsISimpleEnumerator> headers;
+ rv = m_db->EnumerateMessages(getter_AddRefs(headers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return OpenWithHdrs(headers, aSortType, aSortOrder, aViewFlags, aCount);
+}
+
+void nsMsgGroupView::InternalClose()
+{
+ m_groupsTable.Clear();
+ // nothing to do if we're not grouped.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return;
+
+ bool rcvDate = false;
+
+ if (m_sortType == nsMsgViewSortType::byReceived)
+ rcvDate = true;
+ if (m_db &&
+ ((m_sortType == nsMsgViewSortType::byDate) ||
+ (m_sortType == nsMsgViewSortType::byReceived)))
+ {
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo)
+ {
+ uint32_t expandFlags = 0;
+ uint32_t num = GetSize();
+
+ for (uint32_t i = 0; i < num; i++)
+ {
+ if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD && ! (m_flags[i] & nsMsgMessageFlags::Elided))
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ uint32_t ageBucket;
+ nsresult rv = GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
+ if (NS_SUCCEEDED(rv))
+ expandFlags |= 1 << ageBucket;
+ }
+ }
+ }
+ dbFolderInfo->SetUint32Property("dateGroupFlags", expandFlags);
+ }
+ }
+}
+
+NS_IMETHODIMP nsMsgGroupView::Close()
+{
+ InternalClose();
+ return nsMsgDBView::Close();
+}
+
+// Set rcvDate to true to get the Received: date instead of the Date: date.
+nsresult nsMsgGroupView::GetAgeBucketValue(nsIMsgDBHdr *aMsgHdr, uint32_t * aAgeBucket, bool rcvDate)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aAgeBucket);
+
+ PRTime dateOfMsg;
+ nsresult rv;
+ if (!rcvDate)
+ rv = aMsgHdr->GetDate(&dateOfMsg);
+ else
+ {
+ uint32_t rcvDateSecs;
+ rv = aMsgHdr->GetUint32Property("dateReceived", &rcvDateSecs);
+ Seconds2PRTime(rcvDateSecs, &dateOfMsg);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRTime currentTime = PR_Now();
+ PRExplodedTime currentExplodedTime;
+ PR_ExplodeTime(currentTime, PR_LocalTimeParameters, &currentExplodedTime);
+ PRExplodedTime explodedMsgTime;
+ PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime);
+
+ if (m_lastCurExplodedTime.tm_mday &&
+ m_lastCurExplodedTime.tm_mday != currentExplodedTime.tm_mday)
+ m_dayChanged = true; // this will cause us to rebuild the view.
+
+ m_lastCurExplodedTime = currentExplodedTime;
+ if (currentExplodedTime.tm_year == explodedMsgTime.tm_year &&
+ currentExplodedTime.tm_month == explodedMsgTime.tm_month &&
+ currentExplodedTime.tm_mday == explodedMsgTime.tm_mday)
+ {
+ // same day...
+ *aAgeBucket = 1;
+ }
+ // figure out how many days ago this msg arrived
+ else if (currentTime > dateOfMsg)
+ {
+ // setting the time variables to local time
+ int64_t GMTLocalTimeShift = currentExplodedTime.tm_params.tp_gmt_offset +
+ currentExplodedTime.tm_params.tp_dst_offset;
+ GMTLocalTimeShift *= PR_USEC_PER_SEC;
+ currentTime += GMTLocalTimeShift;
+ dateOfMsg += GMTLocalTimeShift;
+
+ // the most recent midnight, counting from current time
+ int64_t mostRecentMidnight = currentTime - currentTime % PR_USEC_PER_DAY;
+ int64_t yesterday = mostRecentMidnight - PR_USEC_PER_DAY;
+ // most recent midnight minus 6 days
+ int64_t mostRecentWeek = mostRecentMidnight - (PR_USEC_PER_DAY * 6);
+
+ // was the message sent yesterday?
+ if (dateOfMsg >= yesterday) // yes ....
+ *aAgeBucket = 2;
+ else if (dateOfMsg >= mostRecentWeek)
+ *aAgeBucket = 3;
+ else
+ {
+ int64_t lastTwoWeeks = mostRecentMidnight - PR_USEC_PER_DAY * 13;
+ *aAgeBucket = (dateOfMsg >= lastTwoWeeks) ? 4 : 5;
+ }
+ }
+ else
+ {
+ // All that remains is a future date.
+ *aAgeBucket = 6;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgGroupView::HashHdr(nsIMsgDBHdr *msgHdr, nsString& aHashKey)
+{
+ nsCString cStringKey;
+ aHashKey.Truncate();
+ nsresult rv = NS_OK;
+ bool rcvDate = false;
+
+ switch (m_sortType)
+ {
+ case nsMsgViewSortType::bySubject:
+ (void) msgHdr->GetSubject(getter_Copies(cStringKey));
+ CopyASCIItoUTF16(cStringKey, aHashKey);
+ break;
+ case nsMsgViewSortType::byAuthor:
+ rv = nsMsgDBView::FetchAuthor(msgHdr, aHashKey);
+ break;
+ case nsMsgViewSortType::byRecipient:
+ (void) msgHdr->GetRecipients(getter_Copies(cStringKey));
+ CopyASCIItoUTF16(cStringKey, aHashKey);
+ break;
+ case nsMsgViewSortType::byAccount:
+ case nsMsgViewSortType::byTags:
+ {
+ nsCOMPtr <nsIMsgDatabase> dbToUse = m_db;
+
+ if (!dbToUse) // probably search view
+ GetDBForViewIndex(0, getter_AddRefs(dbToUse));
+
+ rv = (m_sortType == nsMsgViewSortType::byAccount)
+ ? FetchAccount(msgHdr, aHashKey)
+ : FetchTags(msgHdr, aHashKey);
+ }
+ break;
+ case nsMsgViewSortType::byAttachments:
+ {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ aHashKey.Assign(flags & nsMsgMessageFlags::Attachment ? '1' : '0');
+ break;
+ }
+ case nsMsgViewSortType::byFlagged:
+ {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ aHashKey.Assign(flags & nsMsgMessageFlags::Marked ? '1' : '0');
+ break;
+ }
+ case nsMsgViewSortType::byPriority:
+ {
+ nsMsgPriorityValue priority;
+ msgHdr->GetPriority(&priority);
+ aHashKey.AppendInt(priority);
+ }
+ break;
+ case nsMsgViewSortType::byStatus:
+ {
+ uint32_t status = 0;
+ GetStatusSortValue(msgHdr, &status);
+ aHashKey.AppendInt(status);
+ }
+ break;
+ case nsMsgViewSortType::byReceived:
+ rcvDate = true;
+ MOZ_FALLTHROUGH;
+ case nsMsgViewSortType::byDate:
+ {
+ uint32_t ageBucket;
+ rv = GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
+ if (NS_SUCCEEDED(rv))
+ aHashKey.AppendInt(ageBucket);
+ break;
+ }
+ case nsMsgViewSortType::byCustom:
+ {
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+ if (colHandler)
+ {
+ bool isString;
+ colHandler->IsString(&isString);
+ if (isString)
+ rv = colHandler->GetSortStringForRow(msgHdr, aHashKey);
+ else
+ {
+ uint32_t intKey;
+ rv = colHandler->GetSortLongForRow(msgHdr, &intKey);
+ aHashKey.AppendInt(intKey);
+ }
+ }
+ break;
+ }
+ case nsMsgViewSortType::byCorrespondent:
+ if (IsOutgoingMsg(msgHdr))
+ rv = FetchRecipients(msgHdr, aHashKey);
+ else
+ rv = FetchAuthor(msgHdr, aHashKey);
+ break;
+ default:
+ NS_ASSERTION(false, "no hash key for this type");
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+nsMsgGroupThread *nsMsgGroupView::CreateGroupThread(nsIMsgDatabase *db)
+{
+ return new nsMsgGroupThread(db);
+}
+
+nsMsgGroupThread *nsMsgGroupView::AddHdrToThread(nsIMsgDBHdr *msgHdr, bool *pNewThread)
+{
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ nsString hashKey;
+ nsresult rv = HashHdr(msgHdr, hashKey);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+// if (m_sortType == nsMsgViewSortType::byDate)
+// msgKey = ((nsPRUint32Key *) hashKey)->GetValue();
+ nsCOMPtr<nsIMsgThread> msgThread;
+ m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
+ bool newThread = !msgThread;
+ *pNewThread = newThread;
+ nsMsgViewIndex viewIndexOfThread; // index of first message in thread in view
+ nsMsgViewIndex threadInsertIndex; // index of newly added header in thread
+
+ nsMsgGroupThread *foundThread = static_cast<nsMsgGroupThread *>(msgThread.get());
+ if (foundThread)
+ {
+ // find the view index of the root node of the thread in the view
+ viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(foundThread,
+ true);
+ if (viewIndexOfThread == nsMsgViewIndex_None)
+ {
+ // Something is wrong with the group table. Remove the old group and
+ // insert a new one.
+ m_groupsTable.Remove(hashKey);
+ foundThread = nullptr;
+ *pNewThread = newThread = true;
+ }
+ }
+ // If the thread does not already exist, create one
+ if (!foundThread)
+ {
+ foundThread = CreateGroupThread(m_db);
+ msgThread = do_QueryInterface(foundThread);
+ m_groupsTable.Put(hashKey, msgThread);
+ if (GroupViewUsesDummyRow())
+ {
+ foundThread->m_dummy = true;
+ msgFlags |= MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_HASCHILDREN;
+ }
+
+ viewIndexOfThread = GetInsertIndex(msgHdr);
+ if (viewIndexOfThread == nsMsgViewIndex_None)
+ viewIndexOfThread = m_keys.Length();
+
+ // add the thread root node to the view
+ InsertMsgHdrAt(viewIndexOfThread, msgHdr, msgKey,
+ msgFlags | MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided, 0);
+
+ // For dummy rows, Have the header serve as the dummy node (it will be added
+ // again for its actual content later.)
+ if (GroupViewUsesDummyRow())
+ foundThread->InsertMsgHdrAt(0, msgHdr);
+
+ // Calculate the (integer thread key); this really only needs to be done for
+ // the byDate case where the expanded state of the groups can be easily
+ // persisted and restored because of the bounded, consecutive value space
+ // occupied. We calculate an integer value in all cases mainly because
+ // it's the sanest choice available...
+ // (The thread key needs to be an integer, so parse hash keys that are
+ // stringified integers to real integers, and hash actual strings into
+ // integers.)
+ if ((m_sortType == nsMsgViewSortType::byAttachments) ||
+ (m_sortType == nsMsgViewSortType::byFlagged) ||
+ (m_sortType == nsMsgViewSortType::byPriority) ||
+ (m_sortType == nsMsgViewSortType::byStatus) ||
+ (m_sortType == nsMsgViewSortType::byReceived) ||
+ (m_sortType == nsMsgViewSortType::byDate))
+ foundThread->m_threadKey =
+ atoi(NS_LossyConvertUTF16toASCII(hashKey).get());
+ else
+ foundThread->m_threadKey = (nsMsgKey)
+ PL_HashString(NS_LossyConvertUTF16toASCII(hashKey).get());
+ }
+ // Add the message to the thread as an actual content-bearing header.
+ // (If we use dummy rows, it was already added to the thread during creation.)
+ threadInsertIndex = foundThread->AddChildFromGroupView(msgHdr, this);
+ // check if new hdr became thread root
+ if (!newThread && threadInsertIndex == 0)
+ {
+ // update the root node's header (in the view) to be the same as the root
+ // node in the thread.
+ SetMsgHdrAt(msgHdr, viewIndexOfThread, msgKey,
+ (msgFlags & ~(nsMsgMessageFlags::Elided)) |
+ // maintain elided flag and dummy flag
+ (m_flags[viewIndexOfThread] & (nsMsgMessageFlags::Elided
+ | MSG_VIEW_FLAG_DUMMY))
+ // ensure thread and has-children flags are set
+ | MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN, 0);
+ // update the content-bearing copy in the thread to match. (the root and
+ // first nodes in the thread should always be the same header.)
+ // note: the guy who used to be the root will still exist. If our list of
+ // nodes was [A A], a new node B is introduced which sorts to be the first
+ // node, giving us [B A A], our copy makes that [B B A], and things are
+ // right in the world (since we want the first two headers to be the same
+ // since one is our dummy and one is real.)
+ if (GroupViewUsesDummyRow())
+ foundThread->SetMsgHdrAt(1, msgHdr); // replace the old duplicate dummy header.
+ // we do not update the content-bearing copy in the view to match; we leave
+ // that up to OnNewHeader, which is the piece of code who gets to care
+ // about whether the thread's children are shown or not (elided)
+ }
+
+ return foundThread;
+}
+
+NS_IMETHODIMP nsMsgGroupView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t *aCount)
+{
+ nsresult rv = NS_OK;
+
+ m_groupsTable.Clear();
+ if (aSortType == nsMsgViewSortType::byThread || aSortType == nsMsgViewSortType::byId
+ || aSortType == nsMsgViewSortType::byNone || aSortType == nsMsgViewSortType::bySize)
+ return NS_ERROR_INVALID_ARG;
+
+ m_sortType = aSortType;
+ m_sortOrder = aSortOrder;
+ m_viewFlags = aViewFlags | nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort;
+ SaveSortInfo(m_sortType, m_sortOrder);
+
+ if (m_sortType == nsMsgViewSortType::byCustom)
+ {
+ // If the desired sort is a custom column and there is no handler found,
+ // it hasn't been registered yet; after the custom column observer is
+ // notified with MsgCreateDBView and registers the handler, it will come
+ // back and build the view.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+ if (!colHandler)
+ return rv;
+ }
+
+ bool hasMore;
+ nsCOMPtr <nsISupports> supports;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = aHeaders->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv) && supports)
+ {
+ bool notUsed;
+ msgHdr = do_QueryInterface(supports);
+ AddHdrToThread(msgHdr, &notUsed);
+ }
+ }
+ uint32_t expandFlags = 0;
+ bool expandAll = m_viewFlags & nsMsgViewFlagsType::kExpandAll;
+ uint32_t viewFlag = (m_sortType == nsMsgViewSortType::byDate) ? MSG_VIEW_FLAG_DUMMY : 0;
+ if (viewFlag && m_db)
+ {
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ nsresult rv = m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (dbFolderInfo)
+ dbFolderInfo->GetUint32Property("dateGroupFlags", 0, &expandFlags);
+ }
+ // go through the view updating the flags for threads with more than one message...
+ // and if grouped by date, expanding threads that were expanded before.
+ for (uint32_t viewIndex = 0; viewIndex < m_keys.Length(); viewIndex++)
+ {
+ nsCOMPtr <nsIMsgThread> thread;
+ GetThreadContainingIndex(viewIndex, getter_AddRefs(thread));
+ if (thread)
+ {
+ uint32_t numChildren;
+ thread->GetNumChildren(&numChildren);
+ if (numChildren > 1 || viewFlag)
+ OrExtraFlag(viewIndex, viewFlag | MSG_VIEW_FLAG_HASCHILDREN);
+ if (expandAll || expandFlags)
+ {
+ nsMsgGroupThread *groupThread = static_cast<nsMsgGroupThread *>((nsIMsgThread *) thread);
+ if (expandAll || expandFlags & (1 << groupThread->m_threadKey))
+ {
+ uint32_t numExpanded;
+ ExpandByIndex(viewIndex, &numExpanded);
+ viewIndex += numExpanded;
+ }
+ }
+ }
+ }
+ *aCount = m_keys.Length();
+ return rv;
+}
+
+// we wouldn't need this if we never instantiated this directly,
+// but instead used nsMsgThreadedDBView with the grouping flag set.
+// Or, we could get rid of the nsMsgThreadedDBView impl of this method.
+NS_IMETHODIMP nsMsgGroupView::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowAllThreads;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance,
+ nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater)
+{
+ nsMsgDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ nsMsgGroupView* newMsgDBView = (nsMsgGroupView *) aNewMsgDBView;
+
+ // If grouped, we need to clone the group thread hash table.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) {
+ for (auto iter = m_groupsTable.Iter(); !iter.Done(); iter.Next()) {
+ newMsgDBView->m_groupsTable.Put(iter.Key(), iter.UserData());
+ }
+ }
+ return NS_OK;
+}
+
+// E.g., if the day has changed, we need to close and re-open the view.
+// Or, if we're switching between grouping and threading in a cross-folder
+// saved search. In that case, we needed to build an enumerator based on the
+// old view type, and internally close the view based on its old type, but
+// rebuild the new view based on the new view type. So we pass the new
+// view flags to OpenWithHdrs.
+nsresult nsMsgGroupView::RebuildView(nsMsgViewFlagsTypeValue newFlags)
+{
+ nsCOMPtr <nsISimpleEnumerator> headers;
+ if (NS_SUCCEEDED(GetMessageEnumerator(getter_AddRefs(headers))))
+ {
+ int32_t count;
+ m_dayChanged = false;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ nsMsgKey curSelectedKey;
+ SaveAndClearSelection(&curSelectedKey, preservedSelection);
+ InternalClose();
+ int32_t oldSize = GetSize();
+ // this is important, because the tree will ask us for our
+ // row count, which get determine from the number of keys.
+ m_keys.Clear();
+ // be consistent
+ m_flags.Clear();
+ m_levels.Clear();
+
+ // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount()
+ if (mTree)
+ mTree->RowCountChanged(0, -oldSize);
+ SetSuppressChangeNotifications(true);
+ nsresult rv = OpenWithHdrs(headers, m_sortType, m_sortOrder, newFlags, &count);
+ SetSuppressChangeNotifications(false);
+ if (mTree)
+ mTree->RowCountChanged(0, GetSize());
+
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // now, restore our desired selection
+ AutoTArray<nsMsgKey, 1> keyArray;
+ keyArray.AppendElement(curSelectedKey);
+
+ return RestoreSelection(curSelectedKey, keyArray);
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgGroupView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed)
+{
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return nsMsgDBView::OnNewHeader(newHdr, aParentKey, ensureListed);
+
+ // check if we're adding a header, and the current day has changed. If it has, we're just going to
+ // close and re-open the view so things will be correctly categorized.
+ if (m_dayChanged)
+ return RebuildView(m_viewFlags);
+
+ bool newThread;
+ nsMsgGroupThread *thread = AddHdrToThread(newHdr, &newThread);
+ if (thread)
+ {
+ // find the view index of (the root node of) the thread
+ nsMsgViewIndex threadIndex = ThreadIndexOfMsgHdr(newHdr);
+ // may need to fix thread counts
+ if (threadIndex != nsMsgViewIndex_None)
+ {
+ if (newThread)
+ {
+ // AddHdrToThread creates the header elided, so we need to un-elide it
+ // if we want it expanded.
+ if(m_viewFlags & nsMsgViewFlagsType::kExpandAll)
+ m_flags[threadIndex] &= ~nsMsgMessageFlags::Elided;
+ }
+ else
+ {
+ m_flags[threadIndex] |= MSG_VIEW_FLAG_HASCHILDREN
+ | MSG_VIEW_FLAG_ISTHREAD;
+ }
+
+ int32_t numRowsToInvalidate = 1;
+ // if the thread is expanded (not elided), we should add the header to
+ // the view.
+ if (! (m_flags[threadIndex] & nsMsgMessageFlags::Elided))
+ {
+ uint32_t msgIndexInThread = thread->FindMsgHdr(newHdr);
+ bool insertedAtThreadRoot = !msgIndexInThread;
+ // Add any new display node and potentially fix-up changes in the root.
+ // (If this is a new thread and we are not using a dummy row, the only
+ // node to display is the root node which has already been added by
+ // AddHdrToThread. And since there is just the one, no change in root
+ // could have occurred, so we have nothing to do.)
+ if (!newThread || GroupViewUsesDummyRow())
+ {
+ // we never want to insert/update the root node, because
+ // AddHdrToThread has already done that for us (in all cases).
+ if (insertedAtThreadRoot)
+ msgIndexInThread++;
+ // If this header is the new parent of the thread... AND
+ // If we are not using a dummy row, this means we need to append our
+ // old node as the first child of the new root.
+ // (If we are using a dummy row, the old node's "content" node already
+ // exists (at position threadIndex + 1) and we need to insert the
+ // "content" copy of the new root node there, pushing our old
+ // "content" node down.)
+ // Example mini-diagrams, wrapping the to-add thing with ()
+ // No dummy row; we had: [A], now we have [B], we want [B (A)].
+ // Dummy row; we had: [A A], now we have [B A], we want [B (B) A].
+ // (Coming into this we're adding 'B')
+ if (!newThread && insertedAtThreadRoot && !GroupViewUsesDummyRow())
+ {
+ // grab a copy of the old root node ('A') from the thread so we can
+ // insert it. (offset msgIndexInThread=1 is the right thing; we are
+ // non-dummy.)
+ thread->GetChildHdrAt(msgIndexInThread, &newHdr);
+ } // nothing to do for dummy case, we're already inserting 'B'.
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ newHdr->GetMessageKey(&msgKey);
+ newHdr->GetFlags(&msgFlags);
+ InsertMsgHdrAt(threadIndex + msgIndexInThread, newHdr, msgKey,
+ msgFlags, 1);
+ }
+ // the call to NoteChange() has to happen after we add the key
+ // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
+ // (msgIndexInThread states. new thread: 0, old thread at root: 1)
+ if (newThread && GroupViewUsesDummyRow())
+ NoteChange(threadIndex, 2, nsMsgViewNotificationCode::insertOrDelete);
+ else
+ NoteChange(threadIndex + msgIndexInThread, 1,
+ nsMsgViewNotificationCode::insertOrDelete);
+ numRowsToInvalidate = msgIndexInThread;
+ }
+ // we still need the addition notification for new threads when elided
+ else if (newThread)
+ {
+ NoteChange(threadIndex, 1,
+ nsMsgViewNotificationCode::insertOrDelete);
+ }
+ NoteChange(threadIndex, numRowsToInvalidate, nsMsgViewNotificationCode::changed);
+ }
+ }
+ // if thread is expanded, we need to add hdr to view...
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupView::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags, nsIDBChangeListener *aInstigator)
+{
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags,
+ aInstigator);
+
+ nsCOMPtr <nsIMsgThread> thread;
+
+ // check if we're adding a header, and the current day has changed. If it has, we're just going to
+ // close and re-open the view so things will be correctly categorized.
+ if (m_dayChanged)
+ return RebuildView(m_viewFlags);
+
+ nsresult rv = GetThreadContainingMsgHdr(aHdrChanged, getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t deltaFlags = (aOldFlags ^ aNewFlags);
+ if (deltaFlags & nsMsgMessageFlags::Read)
+ thread->MarkChildRead(aNewFlags & nsMsgMessageFlags::Read);
+
+ return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator);
+}
+
+NS_IMETHODIMP nsMsgGroupView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener *aInstigator)
+{
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator);
+
+ // check if we're adding a header, and the current day has changed. If it has, we're just going to
+ // close and re-open the view so things will be correctly categorized.
+ if (m_dayChanged)
+ return RebuildView(m_viewFlags);
+
+ nsCOMPtr <nsIMsgThread> thread;
+ nsMsgKey keyDeleted;
+ aHdrDeleted->GetMessageKey(&keyDeleted);
+
+ nsresult rv = GetThreadContainingMsgHdr(aHdrDeleted, getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgViewIndex viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(
+ thread, true); // yes to dummy node
+ thread->RemoveChildHdr(aHdrDeleted, nullptr);
+
+ nsMsgGroupThread *groupThread = static_cast<nsMsgGroupThread *>((nsIMsgThread *) thread);
+
+ bool rootDeleted = viewIndexOfThread != nsMsgKey_None &&
+ m_keys[viewIndexOfThread] == keyDeleted;
+ rv = nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator);
+ if (groupThread->m_dummy)
+ {
+ if (!groupThread->NumRealChildren())
+ {
+ thread->RemoveChildAt(0); // get rid of dummy
+ if (viewIndexOfThread != nsMsgKey_None)
+ {
+ RemoveByIndex(viewIndexOfThread);
+ if (m_deletingRows)
+ mIndicesToNoteChange.AppendElement(viewIndexOfThread);
+ }
+ }
+ else if (rootDeleted)
+ {
+ // reflect new thread root into view.dummy row.
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ thread->GetChildHdrAt(0, getter_AddRefs(hdr));
+ if (hdr)
+ {
+ nsMsgKey msgKey;
+ hdr->GetMessageKey(&msgKey);
+ SetMsgHdrAt(hdr, viewIndexOfThread, msgKey, m_flags[viewIndexOfThread], 0);
+ }
+ }
+ }
+ if (!groupThread->m_keys.Length())
+ {
+ nsString hashKey;
+ rv = HashHdr(aHdrDeleted, hashKey);
+ if (NS_SUCCEEDED(rv))
+ m_groupsTable.Remove(hashKey);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgGroupView::GetRowProperties(int32_t aRow, nsAString& aProperties)
+{
+ if (!IsValidIndex(aRow))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY)
+ {
+ aProperties.AssignLiteral("dummy");
+ return NS_OK;
+ }
+
+ return nsMsgDBView::GetRowProperties(aRow, aProperties);
+}
+
+NS_IMETHODIMP nsMsgGroupView::GetCellProperties(int32_t aRow,
+ nsITreeColumn *aCol,
+ nsAString& aProperties)
+{
+ if (!IsValidIndex(aRow))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY)
+ {
+ aProperties.AssignLiteral("dummy read");
+
+ if (!(m_flags[aRow] & nsMsgMessageFlags::Elided))
+ return NS_OK;
+
+ // Set unread property if a collapsed group thread has unread.
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString hashKey;
+ rv = HashHdr(msgHdr, hashKey);
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgThread> msgThread;
+ m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
+ nsMsgGroupThread *groupThread = static_cast<nsMsgGroupThread *>(msgThread.get());
+ if (!groupThread)
+ return NS_OK;
+
+ uint32_t numUnrMsg = 0;
+ groupThread->GetNumUnreadChildren(&numUnrMsg);
+ if (numUnrMsg > 0)
+ aProperties.AppendLiteral(" hasUnread");
+
+ return NS_OK;
+ }
+
+ return nsMsgDBView::GetCellProperties(aRow, aCol, aProperties);
+}
+
+NS_IMETHODIMP nsMsgGroupView::CellTextForColumn(int32_t aRow,
+ const char16_t *aColumnName,
+ nsAString &aValue) {
+ if (!IsValidIndex(aRow))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY && aColumnName[0] != 'u')
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString hashKey;
+ rv = HashHdr(msgHdr, hashKey);
+ if (NS_FAILED(rv))
+ return NS_OK;
+ nsCOMPtr<nsIMsgThread> msgThread;
+ m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
+ nsMsgGroupThread * groupThread = static_cast<nsMsgGroupThread *>(msgThread.get());
+ if (aColumnName[0] == 's' && aColumnName[1] == 'u' )
+ {
+ uint32_t flags;
+ bool rcvDate = false;
+ msgHdr->GetFlags(&flags);
+ aValue.Truncate();
+ nsString tmp_str;
+ switch (m_sortType)
+ {
+ case nsMsgViewSortType::byReceived:
+ rcvDate = true;
+ MOZ_FALLTHROUGH;
+ case nsMsgViewSortType::byDate:
+ {
+ uint32_t ageBucket = 0;
+ GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
+ switch (ageBucket)
+ {
+ case 1:
+ if (m_kTodayString.IsEmpty())
+ m_kTodayString.Adopt(GetString(u"today"));
+ aValue.Assign(m_kTodayString);
+ break;
+ case 2:
+ if (m_kYesterdayString.IsEmpty())
+ m_kYesterdayString.Adopt(GetString(u"yesterday"));
+ aValue.Assign(m_kYesterdayString);
+ break;
+ case 3:
+ if (m_kLastWeekString.IsEmpty())
+ m_kLastWeekString.Adopt(GetString(u"last7Days"));
+ aValue.Assign(m_kLastWeekString);
+ break;
+ case 4:
+ if (m_kTwoWeeksAgoString.IsEmpty())
+ m_kTwoWeeksAgoString.Adopt(GetString(u"last14Days"));
+ aValue.Assign(m_kTwoWeeksAgoString);
+ break;
+ case 5:
+ if (m_kOldMailString.IsEmpty())
+ m_kOldMailString.Adopt(GetString(u"older"));
+ aValue.Assign(m_kOldMailString);
+ break;
+ default:
+ // Future date, error/spoofed.
+ if (m_kFutureDateString.IsEmpty())
+ m_kFutureDateString.Adopt(GetString(u"futureDate"));
+ aValue.Assign(m_kFutureDateString);
+ break;
+ }
+ break;
+ }
+ case nsMsgViewSortType::bySubject:
+ FetchSubject(msgHdr, m_flags[aRow], aValue);
+ break;
+ case nsMsgViewSortType::byAuthor:
+ FetchAuthor(msgHdr, aValue);
+ break;
+ case nsMsgViewSortType::byStatus:
+ rv = FetchStatus(m_flags[aRow], aValue);
+ if (aValue.IsEmpty()) {
+ tmp_str.Adopt(GetString(u"messagesWithNoStatus"));
+ aValue.Assign(tmp_str);
+ }
+ break;
+ case nsMsgViewSortType::byTags:
+ rv = FetchTags(msgHdr, aValue);
+ if (aValue.IsEmpty()) {
+ tmp_str.Adopt(GetString(u"untaggedMessages"));
+ aValue.Assign(tmp_str);
+ }
+ break;
+ case nsMsgViewSortType::byPriority:
+ FetchPriority(msgHdr, aValue);
+ if (aValue.IsEmpty()) {
+ tmp_str.Adopt(GetString(u"noPriority"));
+ aValue.Assign(tmp_str);
+ }
+ break;
+ case nsMsgViewSortType::byAccount:
+ FetchAccount(msgHdr, aValue);
+ break;
+ case nsMsgViewSortType::byRecipient:
+ FetchRecipients(msgHdr, aValue);
+ break;
+ case nsMsgViewSortType::byAttachments:
+ tmp_str.Adopt(GetString(flags & nsMsgMessageFlags::Attachment ?
+ u"attachments" : u"noAttachments"));
+ aValue.Assign(tmp_str);
+ break;
+ case nsMsgViewSortType::byFlagged:
+ tmp_str.Adopt(GetString(flags & nsMsgMessageFlags::Marked ?
+ u"groupFlagged" : u"notFlagged"));
+ aValue.Assign(tmp_str);
+ break;
+ // byLocation is a special case; we don't want to have duplicate
+ // all this logic in nsMsgSearchDBView, and its hash key is what we
+ // want anyways, so just copy it across.
+ case nsMsgViewSortType::byLocation:
+ case nsMsgViewSortType::byCorrespondent:
+ aValue = hashKey;
+ break;
+ case nsMsgViewSortType::byCustom:
+ {
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+ if (colHandler)
+ {
+ bool isString;
+ colHandler->IsString(&isString);
+ if (isString)
+ rv = colHandler->GetSortStringForRow(msgHdr.get(), aValue);
+ else
+ {
+ uint32_t intKey;
+ rv = colHandler->GetSortLongForRow(msgHdr.get(), &intKey);
+ aValue.AppendInt(intKey);
+ }
+ }
+ if (aValue.IsEmpty())
+ aValue.AssignLiteral("*");
+ break;
+ }
+
+ default:
+ NS_ASSERTION(false, "we don't sort by group for this type");
+ break;
+ }
+
+ if (groupThread)
+ {
+ // Get number of messages in group
+ nsAutoString formattedCountMsg;
+ uint32_t numMsg = groupThread->NumRealChildren();
+ formattedCountMsg.AppendInt(numMsg);
+
+ // Get number of unread messages
+ nsAutoString formattedCountUnrMsg;
+ uint32_t numUnrMsg = 0;
+ groupThread->GetNumUnreadChildren(&numUnrMsg);
+ formattedCountUnrMsg.AppendInt(numUnrMsg);
+
+ // Add text to header
+ aValue.Append(NS_LITERAL_STRING(" ("));
+ if (numUnrMsg)
+ {
+ aValue.Append(formattedCountUnrMsg);
+ aValue.Append(NS_LITERAL_STRING("/"));
+ }
+
+ aValue.Append(formattedCountMsg);
+ aValue.Append(NS_LITERAL_STRING(")"));
+ }
+ }
+ else if (aColumnName[0] == 't' && aColumnName[1] == 'o')
+ {
+ nsAutoString formattedCountString;
+ uint32_t numChildren = (groupThread) ? groupThread->NumRealChildren() : 0;
+ formattedCountString.AppendInt(numChildren);
+ aValue.Assign(formattedCountString);
+ }
+ return NS_OK;
+ }
+ return nsMsgDBView::CellTextForColumn(aRow, aColumnName, aValue);
+}
+
+NS_IMETHODIMP nsMsgGroupView::LoadMessageByViewIndex(nsMsgViewIndex aViewIndex)
+{
+ if (!IsValidIndex(aViewIndex))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_flags[aViewIndex] & MSG_VIEW_FLAG_DUMMY)
+ {
+ // if we used to have one item selected, and now we have more than one, we should clear the message pane.
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ nsCOMPtr <nsIMsgWindowCommands> windowCommands;
+ if (msgWindow && NS_SUCCEEDED(msgWindow->GetWindowCommands(getter_AddRefs(windowCommands))) && windowCommands)
+ windowCommands->ClearMsgPane();
+ // since we are selecting a dummy row, we should also clear out m_currentlyDisplayedMsgUri
+ m_currentlyDisplayedMsgUri.Truncate();
+ return NS_OK;
+ }
+ else
+ return nsMsgDBView::LoadMessageByViewIndex(aViewIndex);
+}
+
+NS_IMETHODIMP nsMsgGroupView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread)
+{
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return nsMsgDBView::GetThreadContainingMsgHdr(msgHdr, pThread);
+
+ nsString hashKey;
+ nsresult rv = HashHdr(msgHdr, hashKey);
+ *pThread = nullptr;
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgThread> thread;
+ m_groupsTable.Get(hashKey, getter_AddRefs(thread));
+ thread.swap(*pThread);
+ }
+ return (*pThread) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+int32_t nsMsgGroupView::FindLevelInThread(nsIMsgDBHdr *msgHdr,
+ nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex)
+{
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return nsMsgDBView::FindLevelInThread(msgHdr, startOfThread, viewIndex);
+ return (startOfThread == viewIndex) ? 0 : 1;
+}
+
+bool nsMsgGroupView::GroupViewUsesDummyRow()
+{
+ // Return true to always use a header row as root grouped parent row.
+ return true;
+}
+
+NS_IMETHODIMP nsMsgGroupView::AddColumnHandler(const nsAString& column,
+ nsIMsgCustomColumnHandler* handler)
+{
+ nsMsgDBView::AddColumnHandler(column, handler);
+
+ // If the sortType is byCustom and the desired custom column is the one just
+ // registered, build the view.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort &&
+ m_sortType == nsMsgViewSortType::byCustom)
+ {
+ nsAutoString curCustomColumn;
+ GetCurCustomColumn(curCustomColumn);
+ if (curCustomColumn == column)
+ RebuildView(m_viewFlags);
+ }
+
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMsgGroupView.h b/mailnews/base/src/nsMsgGroupView.h
new file mode 100644
index 000000000..6a2d526da
--- /dev/null
+++ b/mailnews/base/src/nsMsgGroupView.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgGroupView_H_
+#define _nsMsgGroupView_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgDBView.h"
+#include "nsInterfaceHashtable.h"
+
+class nsIMsgThread;
+class nsMsgGroupThread;
+
+// Please note that if you override a method of nsMsgDBView,
+// you will most likely want to check the m_viewFlags to see if
+// we're grouping, and if not, call the base class implementation.
+class nsMsgGroupView : public nsMsgDBView
+{
+public:
+ nsMsgGroupView();
+ virtual ~nsMsgGroupView();
+
+ NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags,
+ int32_t *pCount) override;
+ NS_IMETHOD OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t *aCount) override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override;
+ NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance,
+ nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater);
+ NS_IMETHOD Close() override;
+ NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener *aInstigator) override;
+ NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags, nsIDBChangeListener *aInstigator) override;
+
+ NS_IMETHOD LoadMessageByViewIndex(nsMsgViewIndex aViewIndex) override;
+ NS_IMETHOD GetCellProperties(int32_t aRow, nsITreeColumn *aCol, nsAString& aProperties) override;
+ NS_IMETHOD GetRowProperties(int32_t aRow, nsAString& aProperties) override;
+ NS_IMETHOD CellTextForColumn(int32_t aRow, const char16_t *aColumnName,
+ nsAString &aValue) override;
+ NS_IMETHOD GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread) override;
+ NS_IMETHOD AddColumnHandler(const nsAString& column, nsIMsgCustomColumnHandler* handler) override;
+
+protected:
+ virtual void InternalClose();
+ nsMsgGroupThread *AddHdrToThread(nsIMsgDBHdr *msgHdr, bool *pNewThread);
+ virtual nsresult HashHdr(nsIMsgDBHdr *msgHdr, nsString& aHashKey);
+ nsresult GetAgeBucketValue(nsIMsgDBHdr *aMsgHdr, uint32_t * aAgeBucket, bool rcvDate = false); // helper function to get the age bucket for a hdr, useful when grouped by date
+ nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool /*ensureListed*/) override;
+ virtual int32_t FindLevelInThread(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex) override;
+
+ // Returns true if we are grouped by a sort attribute that uses a dummy row.
+ bool GroupViewUsesDummyRow();
+ nsresult RebuildView(nsMsgViewFlagsTypeValue viewFlags);
+ virtual nsMsgGroupThread *CreateGroupThread(nsIMsgDatabase *db);
+
+ nsInterfaceHashtable <nsStringHashKey, nsIMsgThread> m_groupsTable;
+ PRExplodedTime m_lastCurExplodedTime;
+ bool m_dayChanged;
+
+private:
+ nsString m_kTodayString;
+ nsString m_kYesterdayString;
+ nsString m_kLastWeekString;
+ nsString m_kTwoWeeksAgoString;
+ nsString m_kOldMailString;
+ nsString m_kFutureDateString;
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgMailSession.cpp b/mailnews/base/src/nsMsgMailSession.cpp
new file mode 100644
index 000000000..f9e396988
--- /dev/null
+++ b/mailnews/base/src/nsMsgMailSession.cpp
@@ -0,0 +1,761 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // for pre-compiled headers
+#include "nsMsgBaseCID.h"
+#include "nsMsgMailSession.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIChromeRegistry.h"
+#include "nsIDirectoryService.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIObserverService.h"
+#include "nsIAppStartup.h"
+#include "nsToolkitCompsCID.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+#include "nsIWindowMediator.h"
+#include "nsIWindowWatcher.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "prcmon.h"
+#include "nsThreadUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIProperties.h"
+#include "mozilla/Services.h"
+
+NS_IMPL_ISUPPORTS(nsMsgMailSession, nsIMsgMailSession, nsIFolderListener)
+
+nsMsgMailSession::nsMsgMailSession()
+{
+}
+
+
+nsMsgMailSession::~nsMsgMailSession()
+{
+ Shutdown();
+}
+
+nsresult nsMsgMailSession::Init()
+{
+ // Ensures the shutdown service is initialised
+ nsresult rv;
+ nsCOMPtr<nsIMsgShutdownService> shutdownService =
+ do_GetService(NS_MSGSHUTDOWNSERVICE_CONTRACTID, &rv);
+ return rv;
+}
+
+nsresult nsMsgMailSession::Shutdown()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::AddFolderListener(nsIFolderListener *aListener,
+ uint32_t aNotifyFlags)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ // we don't care about the notification flags for equivalence purposes
+ size_t index = mListeners.IndexOf(aListener);
+ NS_ASSERTION(index == size_t(-1), "tried to add duplicate listener");
+ if (index == size_t(-1))
+ {
+ folderListener newListener(aListener, aNotifyFlags);
+ mListeners.AppendElement(newListener);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::RemoveFolderListener(nsIFolderListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+#define NOTIFY_FOLDER_LISTENERS(propertyflag_, propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<folderListener>::ForwardIterator iter(mListeners); \
+ while (iter.HasMore()) { \
+ const folderListener &fL = iter.GetNext(); \
+ if (fL.mNotifyFlags & nsIFolderListener::propertyflag_) \
+ fL.mListener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsMsgMailSession::OnItemPropertyChanged(nsIMsgFolder *aItem,
+ nsIAtom *aProperty,
+ const char* aOldValue,
+ const char* aNewValue)
+{
+ NOTIFY_FOLDER_LISTENERS(propertyChanged, OnItemPropertyChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::OnItemUnicharPropertyChanged(nsIMsgFolder *aItem,
+ nsIAtom *aProperty,
+ const char16_t* aOldValue,
+ const char16_t* aNewValue)
+{
+ NOTIFY_FOLDER_LISTENERS(unicharPropertyChanged, OnItemUnicharPropertyChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::OnItemIntPropertyChanged(nsIMsgFolder *aItem,
+ nsIAtom *aProperty,
+ int64_t aOldValue,
+ int64_t aNewValue)
+{
+ NOTIFY_FOLDER_LISTENERS(intPropertyChanged, OnItemIntPropertyChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::OnItemBoolPropertyChanged(nsIMsgFolder *aItem,
+ nsIAtom *aProperty,
+ bool aOldValue,
+ bool aNewValue)
+{
+ NOTIFY_FOLDER_LISTENERS(boolPropertyChanged, OnItemBoolPropertyChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::OnItemPropertyFlagChanged(nsIMsgDBHdr *aItem,
+ nsIAtom *aProperty,
+ uint32_t aOldValue,
+ uint32_t aNewValue)
+{
+ NOTIFY_FOLDER_LISTENERS(propertyFlagChanged, OnItemPropertyFlagChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::OnItemAdded(nsIMsgFolder *aParentItem,
+ nsISupports *aItem)
+{
+ NOTIFY_FOLDER_LISTENERS(added, OnItemAdded, (aParentItem, aItem));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::OnItemRemoved(nsIMsgFolder *aParentItem,
+ nsISupports *aItem)
+{
+ NOTIFY_FOLDER_LISTENERS(removed, OnItemRemoved, (aParentItem, aItem));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::OnItemEvent(nsIMsgFolder *aFolder,
+ nsIAtom *aEvent)
+{
+ NOTIFY_FOLDER_LISTENERS(event, OnItemEvent, (aFolder, aEvent));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::AddUserFeedbackListener(nsIMsgUserFeedbackListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ size_t index = mFeedbackListeners.IndexOf(aListener);
+ NS_ASSERTION(index == size_t(-1), "tried to add duplicate listener");
+ if (index == size_t(-1))
+ mFeedbackListeners.AppendElement(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::RemoveUserFeedbackListener(nsIMsgUserFeedbackListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mFeedbackListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::AlertUser(const nsAString &aMessage, nsIMsgMailNewsUrl *aUrl)
+{
+ bool listenersNotified = false;
+ nsTObserverArray<nsCOMPtr<nsIMsgUserFeedbackListener> >::ForwardIterator iter(mFeedbackListeners);
+ nsCOMPtr<nsIMsgUserFeedbackListener> listener;
+
+ while (iter.HasMore())
+ {
+ bool notified = false;
+ listener = iter.GetNext();
+ listener->OnAlert(aMessage, aUrl, &notified);
+ listenersNotified = listenersNotified || notified;
+ }
+
+ // If the listeners notified the user, then we don't need to. Also exit if
+ // aUrl is null because we won't have a nsIMsgWindow in that case.
+ if (listenersNotified || !aUrl)
+ return NS_OK;
+
+ // If the url hasn't got a message window, then the error was a generated as a
+ // result of background activity (e.g. autosync, biff, etc), and hence we
+ // shouldn't prompt either.
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ aUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+
+ if (!msgWindow)
+ return NS_OK;
+
+ nsCOMPtr<nsIPrompt> dialog;
+ msgWindow->GetPromptDialog(getter_AddRefs(dialog));
+
+ if (!dialog) // if we didn't get one, use the default....
+ {
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ wwatch->GetNewPrompter(0, getter_AddRefs(dialog));
+ }
+
+ if (dialog)
+ return dialog->Alert(nullptr, PromiseFlatString(aMessage).get());
+
+ return NS_OK;
+}
+
+nsresult nsMsgMailSession::GetTopmostMsgWindow(nsIMsgWindow **aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ *aMsgWindow = nullptr;
+
+ uint32_t count = mWindows.Count();
+
+ if (count == 1)
+ {
+ NS_ADDREF(*aMsgWindow = mWindows[0]);
+ return (*aMsgWindow) ? NS_OK : NS_ERROR_FAILURE;
+ }
+ else if (count > 1)
+ {
+ // If multiple message windows then we have lots more work.
+ nsresult rv;
+
+ // The msgWindows array does not hold z-order info. Use mediator to get
+ // the top most window then match that with the msgWindows array.
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> windowEnum;
+
+#if defined (XP_UNIX)
+ // The window managers under Unix/X11 do not support ZOrder information,
+ // so we have to use the normal enumeration call here.
+ rv = windowMediator->GetEnumerator(nullptr, getter_AddRefs(windowEnum));
+#else
+ rv = windowMediator->GetZOrderDOMWindowEnumerator(nullptr, true,
+ getter_AddRefs(windowEnum));
+#endif
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> windowSupports;
+ nsCOMPtr<nsPIDOMWindowOuter> topMostWindow;
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ nsCOMPtr<nsIDOMElement> domElement;
+ nsAutoString windowType;
+ bool more;
+
+ // loop to get the top most with attibute "mail:3pane" or "mail:messageWindow"
+ windowEnum->HasMoreElements(&more);
+ while (more)
+ {
+ rv = windowEnum->GetNext(getter_AddRefs(windowSupports));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(windowSupports, NS_ERROR_FAILURE);
+
+ topMostWindow = do_QueryInterface(windowSupports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(topMostWindow, NS_ERROR_FAILURE);
+
+ domDocument = do_QueryInterface(topMostWindow->GetDoc());
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(domDocument, NS_ERROR_FAILURE);
+
+ rv = domDocument->GetDocumentElement(getter_AddRefs(domElement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(domElement, NS_ERROR_FAILURE);
+
+ rv = domElement->GetAttribute(NS_LITERAL_STRING("windowtype"), windowType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (windowType.EqualsLiteral("mail:3pane") ||
+ windowType.EqualsLiteral("mail:messageWindow"))
+ break;
+
+ windowEnum->HasMoreElements(&more);
+ }
+
+ // identified the top most window
+ if (more)
+ {
+ // use this for the match
+ nsIDocShell *topDocShell = topMostWindow->GetDocShell();
+
+ // loop for the msgWindow array to find the match
+ nsCOMPtr<nsIDocShell> docShell;
+
+ while (count)
+ {
+ nsIMsgWindow *msgWindow = mWindows[--count];
+
+ rv = msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (topDocShell == docShell)
+ {
+ NS_IF_ADDREF(*aMsgWindow = msgWindow);
+ break;
+ }
+ }
+ }
+ }
+
+ return (*aMsgWindow) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgMailSession::AddMsgWindow(nsIMsgWindow *msgWindow)
+{
+ NS_ENSURE_ARG_POINTER(msgWindow);
+
+ mWindows.AppendObject(msgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::RemoveMsgWindow(nsIMsgWindow *msgWindow)
+{
+ mWindows.RemoveObject(msgWindow);
+ // Mac keeps a hidden window open so the app doesn't shut down when
+ // the last window is closed. So don't shutdown the account manager in that
+ // case. Similarly, for suite, we don't want to disable mailnews when the
+ // last mail window is closed.
+#if !defined(XP_MACOSX) && !defined(MOZ_SUITE)
+ if (!mWindows.Count())
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ accountManager->CleanupOnExit();
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::IsFolderOpenInWindow(nsIMsgFolder *folder, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = false;
+
+ uint32_t count = mWindows.Count();
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> openFolder;
+ mWindows[i]->GetOpenFolder(getter_AddRefs(openFolder));
+ if (folder == openFolder.get())
+ {
+ *aResult = true;
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::ConvertMsgURIToMsgURL(const char *aURI, nsIMsgWindow *aMsgWindow, char **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aURL);
+
+ // convert the rdf msg uri into a url that represents the message...
+ nsCOMPtr <nsIMsgMessageService> msgService;
+ nsresult rv = GetMessageServiceFromURI(nsDependentCString(aURI), getter_AddRefs(msgService));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NULL_POINTER);
+
+ nsCOMPtr<nsIURI> tURI;
+ rv = msgService->GetUrlForUri(aURI, getter_AddRefs(tURI), aMsgWindow);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NULL_POINTER);
+
+ nsAutoCString urlString;
+ if (NS_SUCCEEDED(tURI->GetSpec(urlString)))
+ {
+ *aURL = ToNewCString(urlString);
+ NS_ENSURE_ARG_POINTER(aURL);
+ }
+ return rv;
+}
+
+//----------------------------------------------------------------------------------------
+// GetSelectedLocaleDataDir - If a locale is selected, appends the selected locale to the
+// defaults data dir and returns that new defaults data dir
+//----------------------------------------------------------------------------------------
+nsresult
+nsMsgMailSession::GetSelectedLocaleDataDir(nsIFile *defaultsDir)
+{
+ NS_ENSURE_ARG_POINTER(defaultsDir);
+
+ bool baseDirExists = false;
+ nsresult rv = defaultsDir->Exists(&baseDirExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (baseDirExists) {
+ nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
+ mozilla::services::GetXULChromeRegistryService();
+ if (packageRegistry) {
+ nsAutoCString localeName;
+ rv = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global-region"), false, localeName);
+
+ if (NS_SUCCEEDED(rv) && !localeName.IsEmpty()) {
+ bool localeDirExists = false;
+ nsCOMPtr<nsIFile> localeDataDir;
+
+ rv = defaultsDir->Clone(getter_AddRefs(localeDataDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localeDataDir->AppendNative(localeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localeDataDir->Exists(&localeDirExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (localeDirExists) {
+ // use locale provider instead
+ rv = defaultsDir->AppendNative(localeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------------------
+// GetDataFilesDir - Gets the application's default folder and then appends the
+// subdirectory named passed in as param dirName. If there is a selected
+// locale, will append that to the dir path before returning the value
+//----------------------------------------------------------------------------------------
+NS_IMETHODIMP
+nsMsgMailSession::GetDataFilesDir(const char* dirName, nsIFile **dataFilesDir)
+{
+
+ NS_ENSURE_ARG_POINTER(dirName);
+ NS_ENSURE_ARG_POINTER(dataFilesDir);
+
+ nsresult rv;
+ nsCOMPtr<nsIProperties> directoryService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> defaultsDir;
+ rv = directoryService->Get(NS_APP_DEFAULTS_50_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(defaultsDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = defaultsDir->AppendNative(nsDependentCString(dirName));
+ if (NS_SUCCEEDED(rv))
+ rv = GetSelectedLocaleDataDir(defaultsDir);
+
+ NS_IF_ADDREF(*dataFilesDir = defaultsDir);
+
+ return rv;
+}
+
+/********************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsMsgShutdownService, nsIMsgShutdownService, nsIUrlListener, nsIObserver)
+
+nsMsgShutdownService::nsMsgShutdownService()
+: mQuitMode(nsIAppStartup::eAttemptQuit),
+ mProcessedShutdown(false),
+ mQuitForced(false),
+ mReadyToQuit(false)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ {
+ observerService->AddObserver(this, "quit-application-requested", false);
+ observerService->AddObserver(this, "quit-application-granted", false);
+ observerService->AddObserver(this, "quit-application", false);
+ }
+}
+
+nsMsgShutdownService::~nsMsgShutdownService()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ {
+ observerService->RemoveObserver(this, "quit-application-requested");
+ observerService->RemoveObserver(this, "quit-application-granted");
+ observerService->RemoveObserver(this, "quit-application");
+ }
+}
+
+nsresult nsMsgShutdownService::ProcessNextTask()
+{
+ bool shutdownTasksDone = true;
+
+ uint32_t count = mShutdownTasks.Length();
+ if (mTaskIndex < count)
+ {
+ shutdownTasksDone = false;
+
+ nsCOMPtr<nsIMsgShutdownTask> curTask = mShutdownTasks[mTaskIndex];
+ nsString taskName;
+ curTask->GetCurrentTaskName(taskName);
+ SetStatusText(taskName);
+
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID);
+ NS_ENSURE_TRUE(mailSession, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgWindow> topMsgWindow;
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(topMsgWindow));
+
+ bool taskIsRunning = true;
+ nsresult rv = curTask->DoShutdownTask(this, topMsgWindow, &taskIsRunning);
+ if (NS_FAILED(rv) || !taskIsRunning)
+ {
+ // We have failed, let's go on to the next task.
+ mTaskIndex++;
+ mMsgProgress->OnProgressChange(nullptr, nullptr, 0, 0, (int32_t)mTaskIndex, count);
+ ProcessNextTask();
+ }
+ }
+
+ if (shutdownTasksDone)
+ {
+ if (mMsgProgress)
+ mMsgProgress->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK);
+ AttemptShutdown();
+ }
+
+ return NS_OK;
+}
+
+void nsMsgShutdownService::AttemptShutdown()
+{
+ if (mQuitForced)
+ {
+ PR_CEnterMonitor(this);
+ mReadyToQuit = true;
+ PR_CNotifyAll(this);
+ PR_CExitMonitor(this);
+ }
+ else
+ {
+ nsCOMPtr<nsIAppStartup> appStartup =
+ do_GetService(NS_APPSTARTUP_CONTRACTID);
+ NS_ENSURE_TRUE_VOID(appStartup);
+ NS_ENSURE_SUCCESS_VOID(appStartup->Quit(mQuitMode));
+ }
+}
+
+NS_IMETHODIMP nsMsgShutdownService::SetShutdownListener(nsIWebProgressListener *inListener)
+{
+ NS_ENSURE_TRUE(mMsgProgress, NS_ERROR_FAILURE);
+ mMsgProgress->RegisterListener(inListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ // Due to bug 459376 we don't always get quit-application-requested and
+ // quit-application-granted. quit-application-requested is preferred, but if
+ // we don't then we have to hook onto quit-application, but we don't want
+ // to do the checking twice so we set some flags to prevent that.
+ if (!strcmp(aTopic, "quit-application-granted"))
+ {
+ // Quit application has been requested and granted, therefore we will shut
+ // down.
+ mProcessedShutdown = true;
+ return NS_OK;
+ }
+
+ // If we've already processed a shutdown notification, no need to do it again.
+ if (!strcmp(aTopic, "quit-application"))
+ {
+ if (mProcessedShutdown)
+ return NS_OK;
+ else
+ mQuitForced = true;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(observerService);
+
+ nsCOMPtr<nsISimpleEnumerator> listenerEnum;
+ nsresult rv = observerService->EnumerateObservers("msg-shutdown", getter_AddRefs(listenerEnum));
+ if (NS_SUCCEEDED(rv) && listenerEnum)
+ {
+ bool hasMore;
+ listenerEnum->HasMoreElements(&hasMore);
+ if (!hasMore)
+ return NS_OK;
+
+ while (hasMore)
+ {
+ nsCOMPtr<nsISupports> curObject;
+ listenerEnum->GetNext(getter_AddRefs(curObject));
+
+ nsCOMPtr<nsIMsgShutdownTask> curTask = do_QueryInterface(curObject);
+ if (curTask)
+ {
+ bool shouldRunTask;
+ curTask->GetNeedsToRunTask(&shouldRunTask);
+ if (shouldRunTask)
+ mShutdownTasks.AppendObject(curTask);
+ }
+
+ listenerEnum->HasMoreElements(&hasMore);
+ }
+
+ if (mShutdownTasks.Count() < 1)
+ return NS_ERROR_FAILURE;
+
+ mTaskIndex = 0;
+
+ mMsgProgress = do_CreateInstance(NS_MSGPROGRESS_CONTRACTID);
+ NS_ENSURE_TRUE(mMsgProgress, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID);
+ NS_ENSURE_TRUE(mailSession, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgWindow> topMsgWindow;
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(topMsgWindow));
+
+ nsCOMPtr<mozIDOMWindowProxy> internalDomWin;
+ if (topMsgWindow)
+ topMsgWindow->GetDomWindow(getter_AddRefs(internalDomWin));
+
+ if (!internalDomWin)
+ {
+ // First see if there is a window open.
+ nsCOMPtr<nsIWindowMediator> winMed = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+ winMed->GetMostRecentWindow(nullptr, getter_AddRefs(internalDomWin));
+
+ //If not use the hidden window.
+ if (!internalDomWin)
+ {
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ appShell->GetHiddenDOMWindow(getter_AddRefs(internalDomWin));
+ NS_ENSURE_TRUE(internalDomWin, NS_ERROR_FAILURE); // bail if we don't get a window.
+ }
+ }
+
+ if (!mQuitForced)
+ {
+ nsCOMPtr<nsISupportsPRBool> stopShutdown = do_QueryInterface(aSubject);
+ stopShutdown->SetData(true);
+
+ // If the attempted quit was a restart, be sure to restart the app once
+ // the tasks have been run. This is usually the case when addons or
+ // updates are going to be installed.
+ if (aData && nsDependentString(aData).EqualsLiteral("restart"))
+ mQuitMode |= nsIAppStartup::eRestart;
+ }
+
+ mMsgProgress->OpenProgressDialog(internalDomWin, topMsgWindow,
+ "chrome://messenger/content/shutdownWindow.xul",
+ false, nullptr);
+
+ if (mQuitForced)
+ {
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+
+ mReadyToQuit = false;
+ while (!mReadyToQuit)
+ {
+ PR_CEnterMonitor(this);
+ // Waiting for 50 milliseconds
+ PR_CWait(this, PR_MicrosecondsToInterval(50000UL));
+ PR_CExitMonitor(this);
+ NS_ProcessPendingEvents(thread);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsIUrlListener
+NS_IMETHODIMP nsMsgShutdownService::OnStartRunningUrl(nsIURI *url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::OnStopRunningUrl(nsIURI *url, nsresult aExitCode)
+{
+ mTaskIndex++;
+
+ if (mMsgProgress)
+ {
+ int32_t numTasks = mShutdownTasks.Count();
+ mMsgProgress->OnProgressChange(nullptr, nullptr, 0, 0, (int32_t)mTaskIndex, numTasks);
+ }
+
+ ProcessNextTask();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::GetNumTasks(int32_t *inNumTasks)
+{
+ *inNumTasks = mShutdownTasks.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::StartShutdownTasks()
+{
+ ProcessNextTask();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::CancelShutdownTasks()
+{
+ AttemptShutdown();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::SetStatusText(const nsAString & inStatusString)
+{
+ nsString statusString(inStatusString);
+ if (mMsgProgress)
+ mMsgProgress->OnStatusChange(nullptr, nullptr, NS_OK, nsString(statusString).get());
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMsgMailSession.h b/mailnews/base/src/nsMsgMailSession.h
new file mode 100644
index 000000000..6e4f57411
--- /dev/null
+++ b/mailnews/base/src/nsMsgMailSession.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgMailSession_h___
+#define nsMsgMailSession_h___
+
+#include "nsIMsgMailSession.h"
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIMsgWindow.h"
+#include "nsCOMArray.h"
+#include "nsIMsgShutdown.h"
+#include "nsIObserver.h"
+#include "nsIMutableArray.h"
+#include "nsIMsgProgress.h"
+#include "nsTArray.h"
+#include "nsTObserverArray.h"
+#include "nsIMsgUserFeedbackListener.h"
+#include "nsIUrlListener.h"
+
+///////////////////////////////////////////////////////////////////////////////////
+// The mail session is a replacement for the old 4.x MSG_Master object. It contains
+// mail session generic information such as the user's current mail identity, ....
+// I'm starting this off as an empty interface and as people feel they need to
+// add more information to it, they can. I think this is a better approach than
+// trying to port over the old MSG_Master in its entirety as that had a lot of
+// cruft in it....
+//////////////////////////////////////////////////////////////////////////////////
+
+class nsMsgMailSession : public nsIMsgMailSession,
+ public nsIFolderListener
+{
+public:
+ nsMsgMailSession();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGMAILSESSION
+ NS_DECL_NSIFOLDERLISTENER
+
+ nsresult Init();
+ nsresult GetSelectedLocaleDataDir(nsIFile *defaultsDir);
+
+protected:
+ virtual ~nsMsgMailSession();
+
+ struct folderListener {
+ nsCOMPtr<nsIFolderListener> mListener;
+ uint32_t mNotifyFlags;
+
+ folderListener(nsIFolderListener *aListener, uint32_t aNotifyFlags)
+ : mListener(aListener), mNotifyFlags(aNotifyFlags) {}
+ folderListener(const folderListener &aListener)
+ : mListener(aListener.mListener), mNotifyFlags(aListener.mNotifyFlags) {}
+ ~folderListener() {}
+
+ int operator==(nsIFolderListener* aListener) const {
+ return mListener == aListener;
+ }
+ int operator==(const folderListener &aListener) const {
+ return mListener == aListener.mListener &&
+ mNotifyFlags == aListener.mNotifyFlags;
+ }
+ };
+
+ nsTObserverArray<folderListener> mListeners;
+ nsTObserverArray<nsCOMPtr<nsIMsgUserFeedbackListener> > mFeedbackListeners;
+
+ nsCOMArray<nsIMsgWindow> mWindows;
+ // stick this here temporarily
+ nsCOMPtr <nsIMsgWindow> m_temporaryMsgWindow;
+};
+
+/********************************************************************************/
+
+class nsMsgShutdownService : public nsIMsgShutdownService,
+ public nsIUrlListener,
+ public nsIObserver
+{
+public:
+ nsMsgShutdownService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSHUTDOWNSERVICE
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIOBSERVER
+
+protected:
+ nsresult ProcessNextTask();
+ void AttemptShutdown();
+
+private:
+ virtual ~nsMsgShutdownService();
+
+ nsCOMArray<nsIMsgShutdownTask> mShutdownTasks;
+ nsCOMPtr<nsIMsgProgress> mMsgProgress;
+ uint32_t mTaskIndex;
+ uint32_t mQuitMode;
+ bool mProcessedShutdown;
+ bool mQuitForced;
+ bool mReadyToQuit;
+};
+
+#endif /* nsMsgMailSession_h__ */
diff --git a/mailnews/base/src/nsMsgOfflineManager.cpp b/mailnews/base/src/nsMsgOfflineManager.cpp
new file mode 100644
index 000000000..be1fac4cb
--- /dev/null
+++ b/mailnews/base/src/nsMsgOfflineManager.cpp
@@ -0,0 +1,399 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * The offline manager service - manages going online and offline, and synchronization
+ */
+#include "msgCore.h"
+#include "netCore.h"
+#include "nsMsgOfflineManager.h"
+#include "nsIServiceManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsIImapService.h"
+#include "nsMsgImapCID.h"
+#include "nsIMsgSendLater.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgCompCID.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsMsgNewsCID.h"
+#include "nsINntpService.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+
+static NS_DEFINE_CID(kMsgSendLaterCID, NS_MSGSENDLATER_CID);
+
+NS_IMPL_ISUPPORTS(nsMsgOfflineManager,
+ nsIMsgOfflineManager,
+ nsIMsgSendLaterListener,
+ nsIObserver,
+ nsISupportsWeakReference,
+ nsIUrlListener)
+
+nsMsgOfflineManager::nsMsgOfflineManager() :
+ m_inProgress (false),
+ m_sendUnsentMessages(false),
+ m_downloadNews(false),
+ m_downloadMail(false),
+ m_playbackOfflineImapOps(false),
+ m_goOfflineWhenDone(false),
+ m_curState(eNoState),
+ m_curOperation(eNoOp)
+{
+}
+
+nsMsgOfflineManager::~nsMsgOfflineManager()
+{
+}
+
+/* attribute nsIMsgWindow window; */
+NS_IMETHODIMP nsMsgOfflineManager::GetWindow(nsIMsgWindow * *aWindow)
+{
+ NS_ENSURE_ARG(aWindow);
+ *aWindow = m_window;
+ NS_IF_ADDREF(*aWindow);
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgOfflineManager::SetWindow(nsIMsgWindow * aWindow)
+{
+ m_window = aWindow;
+ if (m_window)
+ m_window->GetStatusFeedback(getter_AddRefs(m_statusFeedback));
+ else
+ m_statusFeedback = nullptr;
+ return NS_OK;
+}
+
+/* attribute boolean inProgress; */
+NS_IMETHODIMP nsMsgOfflineManager::GetInProgress(bool *aInProgress)
+{
+ NS_ENSURE_ARG(aInProgress);
+ *aInProgress = m_inProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineManager::SetInProgress(bool aInProgress)
+{
+ m_inProgress = aInProgress;
+ return NS_OK;
+}
+
+nsresult nsMsgOfflineManager::StopRunning(nsresult exitStatus)
+{
+ m_inProgress = false;
+ return exitStatus;
+}
+
+nsresult nsMsgOfflineManager::AdvanceToNextState(nsresult exitStatus)
+{
+ // NS_BINDING_ABORTED is used for the user pressing stop, which
+ // should cause us to abort the offline process. Other errors
+ // should allow us to continue.
+ if (exitStatus == NS_BINDING_ABORTED)
+ {
+ return StopRunning(exitStatus);
+ }
+ if (m_curOperation == eGoingOnline)
+ {
+ switch (m_curState)
+ {
+ case eNoState:
+
+ m_curState = eSendingUnsent;
+ if (m_sendUnsentMessages)
+ {
+ SendUnsentMessages();
+ }
+ else
+ AdvanceToNextState(NS_OK);
+ break;
+ case eSendingUnsent:
+
+ m_curState = eSynchronizingOfflineImapChanges;
+ if (m_playbackOfflineImapOps)
+ return SynchronizeOfflineImapChanges();
+ else
+ AdvanceToNextState(NS_OK); // recurse to next state.
+ break;
+ case eSynchronizingOfflineImapChanges:
+ m_curState = eDone;
+ return StopRunning(exitStatus);
+ default:
+ NS_ASSERTION(false, "unhandled current state when going online");
+ }
+ }
+ else if (m_curOperation == eDownloadingForOffline)
+ {
+ switch (m_curState)
+ {
+ case eNoState:
+ m_curState = eDownloadingNews;
+ if (m_downloadNews)
+ DownloadOfflineNewsgroups();
+ else
+ AdvanceToNextState(NS_OK);
+ break;
+ case eSendingUnsent:
+ if (m_goOfflineWhenDone)
+ {
+ SetOnlineState(false);
+ }
+ break;
+ case eDownloadingNews:
+ m_curState = eDownloadingMail;
+ if (m_downloadMail)
+ DownloadMail();
+ else
+ AdvanceToNextState(NS_OK);
+ break;
+ case eDownloadingMail:
+ m_curState = eSendingUnsent;
+ if (m_sendUnsentMessages)
+ SendUnsentMessages();
+ else
+ AdvanceToNextState(NS_OK);
+ break;
+ default:
+ NS_ASSERTION(false, "unhandled current state when downloading for offline");
+ }
+
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgOfflineManager::SynchronizeOfflineImapChanges()
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->PlaybackAllOfflineOperations(m_window, this, getter_AddRefs(mOfflineImapSync));
+}
+
+nsresult nsMsgOfflineManager::SendUnsentMessages()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendLater> pMsgSendLater(do_GetService(kMsgSendLaterCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // now we have to iterate over the identities, finding the *unique* unsent messages folder
+ // for each one, determine if they have unsent messages, and if so, add them to the list
+ // of identities to send unsent messages from.
+ // However, I think there's only ever one unsent messages folder at the moment,
+ // so I think we'll go with that for now.
+ nsCOMPtr<nsIArray> identities;
+
+ if (NS_SUCCEEDED(rv) && accountManager)
+ {
+ rv = accountManager->GetAllIdentities(getter_AddRefs(identities));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCOMPtr <nsIMsgIdentity> identityToUse;
+ uint32_t numIndentities;
+ identities->GetLength(&numIndentities);
+ for (uint32_t i = 0; i < numIndentities; i++)
+ {
+ nsCOMPtr<nsIMsgIdentity> thisIdentity(do_QueryElementAt(identities, i, &rv));
+ if (NS_SUCCEEDED(rv) && thisIdentity)
+ {
+ nsCOMPtr <nsIMsgFolder> outboxFolder;
+ pMsgSendLater->GetUnsentMessagesFolder(thisIdentity, getter_AddRefs(outboxFolder));
+ if (outboxFolder)
+ {
+ int32_t numMessages;
+ outboxFolder->GetTotalMessages(false, &numMessages);
+ if (numMessages > 0)
+ {
+ identityToUse = thisIdentity;
+ break;
+ }
+ }
+ }
+ }
+ if (identityToUse)
+ {
+#ifdef MOZ_SUITE
+ if (m_statusFeedback)
+ pMsgSendLater->SetStatusFeedback(m_statusFeedback);
+#endif
+
+ pMsgSendLater->AddListener(this);
+ rv = pMsgSendLater->SendUnsentMessages(identityToUse);
+ ShowStatus("sendingUnsent");
+ // if we succeeded, return - we'll run the next operation when the
+ // send finishes. Otherwise, advance to the next state.
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+ return AdvanceToNextState(rv);
+
+}
+
+#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
+
+nsresult nsMsgOfflineManager::ShowStatus(const char *statusMsgName)
+{
+ if (!mStringBundle)
+ {
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+ sBundleService->CreateBundle(MESSENGER_STRING_URL, getter_AddRefs(mStringBundle));
+ return NS_OK;
+ }
+
+ nsString statusString;
+ nsresult res = mStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(statusMsgName).get(),
+ getter_Copies(statusString));
+
+ if (NS_SUCCEEDED(res) && m_statusFeedback)
+ m_statusFeedback->ShowStatusString(statusString);
+
+ return res;
+}
+
+nsresult nsMsgOfflineManager::DownloadOfflineNewsgroups()
+{
+ nsresult rv;
+ ShowStatus("downloadingNewsgroups");
+ nsCOMPtr<nsINntpService> nntpService(do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && nntpService)
+ rv = nntpService->DownloadNewsgroupsForOffline(m_window, this);
+
+ if (NS_FAILED(rv))
+ return AdvanceToNextState(rv);
+ return rv;
+}
+
+nsresult nsMsgOfflineManager::DownloadMail()
+{
+ nsresult rv = NS_OK;
+ ShowStatus("downloadingMail");
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->DownloadAllOffineImapFolders(m_window, this);
+ // ### we should do get new mail on pop servers, and download imap messages for offline use.
+}
+
+/* void goOnline (in boolean sendUnsentMessages, in boolean playbackOfflineImapOperations, in nsIMsgWindow aMsgWindow); */
+NS_IMETHODIMP nsMsgOfflineManager::GoOnline(bool sendUnsentMessages, bool playbackOfflineImapOperations, nsIMsgWindow *aMsgWindow)
+{
+ m_sendUnsentMessages = sendUnsentMessages;
+ m_playbackOfflineImapOps = playbackOfflineImapOperations;
+ m_curOperation = eGoingOnline;
+ m_curState = eNoState;
+ SetWindow(aMsgWindow);
+ SetOnlineState(true);
+ if (!m_sendUnsentMessages && !playbackOfflineImapOperations)
+ return NS_OK;
+ else
+ AdvanceToNextState(NS_OK);
+ return NS_OK;
+}
+
+/* void synchronizeForOffline (in boolean downloadNews, in boolean downloadMail, in boolean sendUnsentMessages, in boolean goOfflineWhenDone, in nsIMsgWindow aMsgWindow); */
+NS_IMETHODIMP nsMsgOfflineManager::SynchronizeForOffline(bool downloadNews, bool downloadMail, bool sendUnsentMessages, bool goOfflineWhenDone, nsIMsgWindow *aMsgWindow)
+{
+ m_curOperation = eDownloadingForOffline;
+ m_downloadNews = downloadNews;
+ m_downloadMail = downloadMail;
+ m_sendUnsentMessages = sendUnsentMessages;
+ SetWindow(aMsgWindow);
+ m_goOfflineWhenDone = goOfflineWhenDone;
+ m_curState = eNoState;
+ if (!downloadNews && !downloadMail && !sendUnsentMessages)
+ {
+ if (goOfflineWhenDone)
+ return SetOnlineState(false);
+ }
+ else
+ return AdvanceToNextState(NS_OK);
+ return NS_OK;
+}
+
+nsresult nsMsgOfflineManager::SetOnlineState(bool online)
+{
+ nsCOMPtr<nsIIOService> netService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(netService, NS_ERROR_UNEXPECTED);
+ return netService->SetOffline(!online);
+}
+
+ // nsIUrlListener methods
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnStartRunningUrl(nsIURI * aUrl)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode)
+{
+ mOfflineImapSync = nullptr;
+
+ AdvanceToNextState(aExitCode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ return NS_OK;
+}
+
+// nsIMsgSendLaterListener implementation
+NS_IMETHODIMP
+nsMsgOfflineManager::OnStartSending(uint32_t aTotalMessageCount)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnMessageStartSending(uint32_t aCurrentMessage,
+ uint32_t aTotalMessageCount,
+ nsIMsgDBHdr *aMessageHeader,
+ nsIMsgIdentity *aIdentity)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnMessageSendProgress(uint32_t aCurrentMessage,
+ uint32_t aTotalMessageCount,
+ uint32_t aMessageSendPercent,
+ uint32_t aMessageCopyPercent)
+{
+ if (m_statusFeedback && aTotalMessageCount)
+ return m_statusFeedback->ShowProgress((100 * aCurrentMessage) /
+ aTotalMessageCount);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnMessageSendError(uint32_t aCurrentMessage,
+ nsIMsgDBHdr *aMessageHeader,
+ nsresult aStatus,
+ const char16_t *aMsg)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnStopSending(nsresult aStatus,
+ const char16_t *aMsg, uint32_t aTotalTried,
+ uint32_t aSuccessful)
+{
+#ifdef NS_DEBUG
+ if (NS_SUCCEEDED(aStatus))
+ printf("SendLaterListener::OnStopSending: Tried to send %d messages. %d successful.\n",
+ aTotalTried, aSuccessful);
+#endif
+ return AdvanceToNextState(aStatus);
+}
diff --git a/mailnews/base/src/nsMsgOfflineManager.h b/mailnews/base/src/nsMsgOfflineManager.h
new file mode 100644
index 000000000..07e6b347a
--- /dev/null
+++ b/mailnews/base/src/nsMsgOfflineManager.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgOfflineManager_h__
+#define nsMsgOfflineManager_h__
+
+#include "nscore.h"
+#include "nsIMsgOfflineManager.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgSendLaterListener.h"
+#include "nsIStringBundle.h"
+
+class nsMsgOfflineManager
+ : public nsIMsgOfflineManager,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsIMsgSendLaterListener,
+ public nsIUrlListener
+{
+public:
+
+ nsMsgOfflineManager();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /* nsIMsgOfflineManager methods */
+
+ NS_DECL_NSIMSGOFFLINEMANAGER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGSENDLATERLISTENER
+
+ typedef enum
+ {
+ eStarting = 0,
+ eSynchronizingOfflineImapChanges = 1,
+ eDownloadingNews = 2,
+ eDownloadingMail = 3,
+ eSendingUnsent = 4,
+ eDone = 5,
+ eNoState = 6 // we're not doing anything
+ } offlineManagerState;
+
+ typedef enum
+ {
+ eGoingOnline = 0,
+ eDownloadingForOffline = 1,
+ eNoOp = 2 // no operation in progress
+ } offlineManagerOperation;
+
+private:
+ virtual ~nsMsgOfflineManager();
+
+ nsresult AdvanceToNextState(nsresult exitStatus);
+ nsresult SynchronizeOfflineImapChanges();
+ nsresult StopRunning(nsresult exitStatus);
+ nsresult SendUnsentMessages();
+ nsresult DownloadOfflineNewsgroups();
+ nsresult DownloadMail();
+
+ nsresult SetOnlineState(bool online);
+ nsresult ShowStatus(const char *statusMsgName);
+
+ bool m_inProgress;
+ bool m_sendUnsentMessages;
+ bool m_downloadNews;
+ bool m_downloadMail;
+ bool m_playbackOfflineImapOps;
+ bool m_goOfflineWhenDone;
+ offlineManagerState m_curState;
+ offlineManagerOperation m_curOperation;
+ nsCOMPtr <nsIMsgWindow> m_window;
+ nsCOMPtr <nsIMsgStatusFeedback> m_statusFeedback;
+ nsCOMPtr<nsIStringBundle> mStringBundle;
+ nsCOMPtr<nsISupports> mOfflineImapSync;
+
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgPrintEngine.cpp b/mailnews/base/src/nsMsgPrintEngine.cpp
new file mode 100644
index 000000000..2a509f380
--- /dev/null
+++ b/mailnews/base/src/nsMsgPrintEngine.cpp
@@ -0,0 +1,741 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * nsMsgPrintEngine.cpp provides a DocShell container for use in printing.
+ */
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+
+#include "nsIComponentManager.h"
+
+#include "nsISupports.h"
+
+#include "nsIURI.h"
+
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsIContentViewer.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#include "nsIWebProgress.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsMsgPrintEngine.h"
+#include "nsIDocumentLoader.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsThreadUtils.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Services.h"
+
+// Interfaces Needed
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIWebNavigation.h"
+#include "nsIChannel.h"
+#include "nsIContentViewerFile.h"
+#include "nsServiceManagerUtils.h"
+
+static const char* kPrintingPromptService = "@mozilla.org/embedcomp/printingprompt-service;1";
+
+/////////////////////////////////////////////////////////////////////////
+// nsMsgPrintEngine implementation
+/////////////////////////////////////////////////////////////////////////
+
+nsMsgPrintEngine::nsMsgPrintEngine() :
+ mIsDoingPrintPreview(false),
+ mMsgInx(nsIMsgPrintEngine::MNAB_START)
+{
+ mCurrentlyPrintingURI = -1;
+}
+
+
+nsMsgPrintEngine::~nsMsgPrintEngine()
+{
+}
+
+// Implement AddRef and Release
+NS_IMPL_ISUPPORTS(nsMsgPrintEngine,
+ nsIMsgPrintEngine,
+ nsIWebProgressListener,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+// nsIWebProgressListener implementation
+NS_IMETHODIMP
+nsMsgPrintEngine::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t progressStateFlags,
+ nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+
+ // top-level document load data
+ if (progressStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) {
+ if (progressStateFlags & nsIWebProgressListener::STATE_START) {
+ // Tell the user we are loading...
+ nsString msg;
+ GetString(u"LoadingMessageToPrint", msg);
+ SetStatusMessage(msg);
+ }
+
+ if (progressStateFlags & nsIWebProgressListener::STATE_STOP) {
+ nsCOMPtr<nsIDocumentLoader> docLoader(do_QueryInterface(aWebProgress));
+ if (docLoader)
+ {
+ // Check to see if the document DOMWin that is finished loading is the same
+ // one as the mail msg that we started to load.
+ // We only want to print when the entire msg and all of its attachments
+ // have finished loading.
+ // The mail msg doc is the last one to receive the STATE_STOP notification
+ nsCOMPtr<nsISupports> container;
+ docLoader->GetContainer(getter_AddRefs(container));
+ nsCOMPtr<mozIDOMWindowProxy> domWindow(do_GetInterface(container));
+ if (domWindow.get() != mMsgDOMWin.get()) {
+ return NS_OK;
+ }
+ }
+ nsCOMPtr<nsIWebProgressListener> wpl(do_QueryInterface(mPrintPromptService));
+ if (wpl) {
+ wpl->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP|nsIWebProgressListener::STATE_IS_DOCUMENT, NS_OK);
+ mPrintProgressListener = nullptr;
+ mPrintProgress = nullptr;
+ mPrintProgressParams = nullptr;
+ }
+
+ bool isPrintingCancelled = false;
+ if (mPrintSettings)
+ {
+ mPrintSettings->GetIsCancelled(&isPrintingCancelled);
+ }
+ if (!isPrintingCancelled) {
+ // if aWebProgress is a documentloader than the notification from
+ // loading the documents. If it is NULL (or not a DocLoader) then it
+ // it coming from Printing
+ if (docLoader) {
+ // Now, fire off the print operation!
+ rv = NS_ERROR_FAILURE;
+
+ // Tell the user the message is loaded...
+ nsString msg;
+ GetString(u"MessageLoaded", msg);
+ SetStatusMessage(msg);
+
+ NS_ASSERTION(mDocShell,"can't print, there is no docshell");
+ if ( (!mDocShell) || (!aRequest) )
+ {
+ return StartNextPrintOperation();
+ }
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(aRequest);
+ if (!aChannel) return NS_ERROR_FAILURE;
+
+ // Make sure this isn't just "about:blank" finishing....
+ nsCOMPtr<nsIURI> originalURI = nullptr;
+ if (NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(originalURI))) && originalURI)
+ {
+ nsAutoCString spec;
+
+ if (NS_SUCCEEDED(originalURI->GetSpec(spec)))
+ {
+ if (spec.Equals("about:blank"))
+ {
+ return StartNextPrintOperation();
+ }
+ }
+ }
+
+ // If something bad happens here (menaing we can fire the PLEvent, highly unlikely)
+ // we will still ask the msg to print, but if the user "cancels" out of the
+ // print dialog the hidden print window will not be "closed"
+ if (!FirePrintEvent())
+ {
+ PrintMsgWindow();
+ }
+ } else {
+ FireStartNextEvent();
+ rv = NS_OK;
+ }
+ }
+ else
+ {
+ if (mWindow) {
+ nsPIDOMWindowOuter::From(mWindow)->Close();
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgPrintEngine::OnProgressChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgPrintEngine::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI *location,
+ uint32_t aFlags)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMsgPrintEngine::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMsgPrintEngine::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgPrintEngine::SetWindow(mozIDOMWindowProxy *aWin)
+{
+ if (!aWin)
+ {
+ // It isn't an error to pass in null for aWin, in fact it means we are shutting
+ // down and we should start cleaning things up...
+ return NS_OK;
+ }
+
+ mWindow = aWin;
+
+ NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(mWindow);
+
+ window->GetDocShell()->SetAppType(nsIDocShell::APP_TYPE_MAIL);
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem =
+ do_QueryInterface(window->GetDocShell());
+ NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocShellTreeItem> rootAsItem;
+ docShellAsItem->GetSameTypeRootTreeItem(getter_AddRefs(rootAsItem));
+
+ nsCOMPtr<nsIDocShellTreeItem> childItem;
+ rootAsItem->FindChildWithName(NS_LITERAL_STRING("content"), true,
+ false, nullptr, nullptr,
+ getter_AddRefs(childItem));
+
+ mDocShell = do_QueryInterface(childItem);
+
+ if(mDocShell)
+ SetupObserver();
+
+ return NS_OK;
+}
+
+/* void setParentWindow (in mozIDOMWindowProxy ptr); */
+NS_IMETHODIMP nsMsgPrintEngine::SetParentWindow(mozIDOMWindowProxy *ptr)
+{
+ mParentWindow = ptr;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMsgPrintEngine::ShowWindow(bool aShow)
+{
+ nsresult rv;
+
+ NS_ENSURE_TRUE(mWindow, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(mWindow);
+ nsCOMPtr <nsIDocShellTreeItem> treeItem =
+ do_QueryInterface(window->GetDocShell(), &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIDocShellTreeOwner> treeOwner;
+ rv = treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (treeOwner) {
+ // disable (enable) the window
+ nsCOMPtr<nsIBaseWindow> baseWindow;
+ baseWindow = do_QueryInterface(treeOwner, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = baseWindow->SetEnabled(aShow);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // hide or show the window
+ baseWindow->SetVisibility(aShow);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgPrintEngine::AddPrintURI(const char16_t *aMsgURI)
+{
+ NS_ENSURE_ARG_POINTER(aMsgURI);
+
+ mURIArray.AppendElement(nsDependentString(aMsgURI));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgPrintEngine::SetPrintURICount(int32_t aCount)
+{
+ mURICount = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgPrintEngine::StartPrintOperation(nsIPrintSettings* aPS)
+{
+ NS_ENSURE_ARG_POINTER(aPS);
+ mPrintSettings = aPS;
+
+ // Load the about:blank on the tail end...
+ nsresult rv = AddPrintURI(u"about:blank");
+ if (NS_FAILED(rv)) return rv;
+ return StartNextPrintOperation();
+}
+
+//----------------------------------------------------------------------
+// Set up to use the "pluggable" Print Progress Dialog
+nsresult
+nsMsgPrintEngine::ShowProgressDialog(bool aIsForPrinting, bool& aDoNotify)
+{
+ nsresult rv;
+
+ // default to not notifying, that if something here goes wrong
+ // or we aren't going to show the progress dialog we can straight into
+ // reflowing the doc for printing.
+ aDoNotify = false;
+
+ // Assume we can't do progress and then see if we can
+ bool showProgressDialog = false;
+
+ // if it is already being shown then don't bother to find out if it should be
+ // so skip this and leave mShowProgressDialog set to FALSE
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ prefBranch->GetBoolPref("print.show_print_progress", &showProgressDialog);
+ }
+
+ // Turning off the showing of Print Progress in Prefs overrides
+ // whether the calling PS desire to have it on or off, so only check PS if
+ // prefs says it's ok to be on.
+ if (showProgressDialog)
+ {
+ mPrintSettings->GetShowPrintProgress(&showProgressDialog);
+ }
+
+ // Now open the service to get the progress dialog
+ // If we don't get a service, that's ok, then just don't show progress
+ if (showProgressDialog) {
+ if (!mPrintPromptService)
+ {
+ mPrintPromptService = do_GetService(kPrintingPromptService);
+ }
+ if (mPrintPromptService)
+ {
+ nsCOMPtr<mozIDOMWindowProxy> domWin(do_QueryInterface(mParentWindow));
+ if (!domWin)
+ {
+ domWin = mWindow;
+ }
+
+ rv = mPrintPromptService->ShowProgress(domWin, mWebBrowserPrint, mPrintSettings, this, aIsForPrinting,
+ getter_AddRefs(mPrintProgressListener),
+ getter_AddRefs(mPrintProgressParams),
+ &aDoNotify);
+ if (NS_SUCCEEDED(rv)) {
+
+ showProgressDialog = mPrintProgressListener != nullptr && mPrintProgressParams != nullptr;
+
+ if (showProgressDialog)
+ {
+ nsIWebProgressListener* wpl = static_cast<nsIWebProgressListener*>(mPrintProgressListener.get());
+ NS_ASSERTION(wpl, "nsIWebProgressListener is NULL!");
+ NS_ADDREF(wpl);
+ nsString msg;
+ if (mIsDoingPrintPreview) {
+ GetString(u"LoadingMailMsgForPrintPreview", msg);
+ } else {
+ GetString(u"LoadingMailMsgForPrint", msg);
+ }
+ if (!msg.IsEmpty())
+ mPrintProgressParams->SetDocTitle(msg.get());
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsMsgPrintEngine::StartNextPrintOperation()
+{
+ nsresult rv;
+
+ // Only do this the first time through...
+ if (mCurrentlyPrintingURI == -1)
+ InitializeDisplayCharset();
+
+ mCurrentlyPrintingURI++;
+
+ // First, check if we are at the end of this stuff!
+ if (mCurrentlyPrintingURI >= (int32_t)mURIArray.Length())
+ {
+ // This is the end...dum, dum, dum....my only friend...the end
+ NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
+ nsPIDOMWindowOuter::From(mWindow)->Close();
+
+ // Tell the user we are done...
+ nsString msg;
+ GetString(u"PrintingComplete", msg);
+ SetStatusMessage(msg);
+ return NS_OK;
+ }
+
+ if (!mDocShell)
+ return StartNextPrintOperation();
+
+ const nsString &uri = mURIArray[mCurrentlyPrintingURI];
+ rv = FireThatLoadOperationStartup(uri);
+ if (NS_FAILED(rv))
+ return StartNextPrintOperation();
+ else
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgPrintEngine::SetStatusFeedback(nsIMsgStatusFeedback *aFeedback)
+{
+ mFeedback = aFeedback;
+ return NS_OK;
+}
+
+#define DATA_URL_PREFIX "data:"
+#define ADDBOOK_URL_PREFIX "addbook:"
+
+nsresult
+nsMsgPrintEngine::FireThatLoadOperationStartup(const nsString& uri)
+{
+ if (!uri.IsEmpty())
+ mLoadURI = uri;
+ else
+ mLoadURI.Truncate();
+
+ bool notify = false;
+ nsresult rv = NS_ERROR_FAILURE;
+ // Don't show dialog if we are out of URLs
+ //if ( mCurrentlyPrintingURI < mURIArray.Length() && !mIsDoingPrintPreview)
+ if ( mCurrentlyPrintingURI < (int32_t)mURIArray.Length())
+ rv = ShowProgressDialog(!mIsDoingPrintPreview, notify);
+ if (NS_FAILED(rv) || !notify)
+ return FireThatLoadOperation(uri);
+ return NS_OK;
+}
+
+nsresult
+nsMsgPrintEngine::FireThatLoadOperation(const nsString& uri)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCString uriCStr;
+ LossyCopyUTF16toASCII(uri, uriCStr);
+
+ nsCOMPtr <nsIMsgMessageService> messageService;
+ // if this is a data: url, skip it, because
+ // we've already got something we can print
+ // and we know it is not a message.
+ //
+ // if this an about:blank url, skip it, because
+ // ...
+ //
+ // if this is an addbook: url, skip it, because
+ // we know that isn't a message.
+ //
+ // if this is a message part (or .eml file on disk)
+ // skip it, because we don't want to print the parent message
+ // we want to print the part.
+ // example: imap://sspitzer@nsmail-1:143/fetch%3EUID%3E/INBOX%3E180958?part=1.1.2&type=application/x-message-display&filename=test"
+ if (!StringBeginsWith(uriCStr, NS_LITERAL_CSTRING(DATA_URL_PREFIX)) &&
+ !StringBeginsWith(uriCStr, NS_LITERAL_CSTRING(ADDBOOK_URL_PREFIX)) &&
+ !uriCStr.EqualsLiteral("about:blank") &&
+ uriCStr.Find(NS_LITERAL_CSTRING("type=application/x-message-display")) == -1) {
+ rv = GetMessageServiceFromURI(uriCStr, getter_AddRefs(messageService));
+ }
+
+ if (NS_SUCCEEDED(rv) && messageService) {
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = messageService->DisplayMessageForPrinting(uriCStr.get(), mDocShell, nullptr, nullptr,
+ getter_AddRefs(dummyNull));
+ }
+ //If it's not something we know about, then just load try loading it directly.
+ else
+ {
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ if (webNav)
+ rv = webNav->LoadURI(uri.get(), // URI string
+ nsIWebNavigation::LOAD_FLAGS_NONE, // Load flags
+ nullptr, // Referring URI
+ nullptr, // Post data
+ nullptr); // Extra headers
+ }
+ return rv;
+}
+
+void
+nsMsgPrintEngine::InitializeDisplayCharset()
+{
+ // libmime always converts to UTF-8 (both HTML and XML)
+ if (mDocShell)
+ {
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv)
+ {
+ cv->SetForceCharacterSet(NS_LITERAL_CSTRING("UTF-8"));
+ }
+ }
+}
+
+void
+nsMsgPrintEngine::SetupObserver()
+{
+ if (!mDocShell)
+ return;
+
+ if (mDocShell)
+ {
+ nsCOMPtr<nsIWebProgress> progress(do_GetInterface(mDocShell));
+ NS_ASSERTION(progress, "we were expecting a nsIWebProgress");
+ if (progress)
+ {
+ (void) progress->AddProgressListener((nsIWebProgressListener *)this,
+ nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+ }
+
+ // Cache a pointer to the mail message's DOMWindow
+ // so later we know when we can print when the
+ // document "loaded" msgs com thru via the Progress listener
+ mMsgDOMWin = do_GetInterface(mDocShell);
+ }
+}
+
+nsresult
+nsMsgPrintEngine::SetStatusMessage(const nsString& aMsgString)
+{
+ if ( (!mFeedback) || (aMsgString.IsEmpty()) )
+ return NS_OK;
+
+ mFeedback->ShowStatusString(aMsgString);
+ return NS_OK;
+}
+
+#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
+
+void
+nsMsgPrintEngine::GetString(const char16_t *aStringName, nsString& outStr)
+{
+ outStr.Truncate();
+
+ if (!mStringBundle)
+ {
+ static const char propertyURL[] = MESSENGER_STRING_URL;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ if (sBundleService)
+ sBundleService->CreateBundle(propertyURL, getter_AddRefs(mStringBundle));
+ }
+
+ if (mStringBundle)
+ mStringBundle->GetStringFromName(aStringName, getter_Copies(outStr));
+ return;
+}
+
+//-----------------------------------------------------------
+void
+nsMsgPrintEngine::PrintMsgWindow()
+{
+ const char* kMsgKeys[] = {"PrintingMessage", "PrintPreviewMessage",
+ "PrintingContact", "PrintPreviewContact",
+ "PrintingAddrBook", "PrintPreviewAddrBook"};
+
+ mDocShell->GetContentViewer(getter_AddRefs(mContentViewer));
+ if (mContentViewer)
+ {
+ mWebBrowserPrint = do_QueryInterface(mContentViewer);
+ if (mWebBrowserPrint)
+ {
+ if (!mPrintSettings)
+ {
+ mWebBrowserPrint->GetGlobalPrintSettings(getter_AddRefs(mPrintSettings));
+ }
+
+ // fix for bug #118887 and bug #176016
+ // don't show the actual url when printing mail messages or addressbook cards.
+ // for mail, it can review the salt. for addrbook, it's a data:// url, which
+ // means nothing to the end user.
+ // needs to be " " and not "" or nullptr, otherwise, we'll still print the url
+ mPrintSettings->SetDocURL(u" ");
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (mIsDoingPrintPreview)
+ {
+ if (mStartupPPObs) {
+ rv = mStartupPPObs->Observe(nullptr, nullptr, nullptr);
+ }
+ }
+ else
+ {
+ mPrintSettings->SetPrintSilent(mCurrentlyPrintingURI != 0);
+ rv = mWebBrowserPrint->Print(mPrintSettings, (nsIWebProgressListener *)this);
+ }
+
+ if (NS_FAILED(rv))
+ {
+ mWebBrowserPrint = nullptr;
+ mContentViewer = nullptr;
+ bool isPrintingCancelled = false;
+ if (mPrintSettings)
+ {
+ mPrintSettings->GetIsCancelled(&isPrintingCancelled);
+ }
+ if (!isPrintingCancelled)
+ {
+ StartNextPrintOperation();
+ }
+ else
+ {
+ if (mWindow) {
+ nsPIDOMWindowOuter::From(mWindow)->Close();
+ }
+ }
+ }
+ else
+ {
+ // Tell the user we started printing...
+ nsString msg;
+ GetString(NS_ConvertASCIItoUTF16(kMsgKeys[mMsgInx]).get(), msg);
+ SetStatusMessage(msg);
+ }
+ }
+ }
+}
+
+//---------------------------------------------------------------
+//-- Event Notification
+//---------------------------------------------------------------
+
+//---------------------------------------------------------------
+class nsPrintMsgWindowEvent : public mozilla::Runnable
+{
+public:
+ explicit nsPrintMsgWindowEvent(nsMsgPrintEngine *mpe)
+ : mMsgPrintEngine(mpe)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mMsgPrintEngine)
+ mMsgPrintEngine->PrintMsgWindow();
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsMsgPrintEngine> mMsgPrintEngine;
+};
+
+//-----------------------------------------------------------
+class nsStartNextPrintOpEvent : public mozilla::Runnable
+{
+public:
+ explicit nsStartNextPrintOpEvent(nsMsgPrintEngine *mpe)
+ : mMsgPrintEngine(mpe)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mMsgPrintEngine)
+ mMsgPrintEngine->StartNextPrintOperation();
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsMsgPrintEngine> mMsgPrintEngine;
+};
+
+//-----------------------------------------------------------
+bool
+nsMsgPrintEngine::FirePrintEvent()
+{
+ nsCOMPtr<nsIRunnable> event = new nsPrintMsgWindowEvent(this);
+ return NS_SUCCEEDED(NS_DispatchToCurrentThread(event));
+}
+
+//-----------------------------------------------------------
+nsresult
+nsMsgPrintEngine::FireStartNextEvent()
+{
+ nsCOMPtr<nsIRunnable> event = new nsStartNextPrintOpEvent(this);
+ return NS_DispatchToCurrentThread(event);
+}
+
+/* void setStartupPPObserver (in nsIObserver startupPPObs); */
+NS_IMETHODIMP nsMsgPrintEngine::SetStartupPPObserver(nsIObserver *startupPPObs)
+{
+ mStartupPPObs = startupPPObs;
+ return NS_OK;
+}
+
+/* attribute boolean doPrintPreview; */
+NS_IMETHODIMP nsMsgPrintEngine::GetDoPrintPreview(bool *aDoPrintPreview)
+{
+ NS_ENSURE_ARG_POINTER(aDoPrintPreview);
+ *aDoPrintPreview = mIsDoingPrintPreview;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgPrintEngine::SetDoPrintPreview(bool aDoPrintPreview)
+{
+ mIsDoingPrintPreview = aDoPrintPreview;
+ return NS_OK;
+}
+
+/* void setMsgType (in long aMsgType); */
+NS_IMETHODIMP nsMsgPrintEngine::SetMsgType(int32_t aMsgType)
+{
+ if (mMsgInx >= nsIMsgPrintEngine::MNAB_START && mMsgInx < nsIMsgPrintEngine::MNAB_END)
+ {
+ mMsgInx = aMsgType;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+/*=============== nsIObserver Interface ======================*/
+NS_IMETHODIMP nsMsgPrintEngine::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
+{
+ return FireThatLoadOperation(mLoadURI);
+}
diff --git a/mailnews/base/src/nsMsgPrintEngine.h b/mailnews/base/src/nsMsgPrintEngine.h
new file mode 100644
index 000000000..4f8a9cad0
--- /dev/null
+++ b/mailnews/base/src/nsMsgPrintEngine.h
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// nsMsgPrintEngine.h: declaration of nsMsgPrintEngine class
+// implementing mozISimpleContainer,
+// which provides a DocShell container for use in simple programs
+// using the layout engine
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+
+#include "nsIDocShell.h"
+#include "nsIDocShell.h"
+#include "nsIMsgPrintEngine.h"
+#include "nsIStreamListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIStringBundle.h"
+#include "nsIWebBrowserPrint.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+#include "nsIPrintSettings.h"
+#include "nsIObserver.h"
+
+// Progress Dialog
+#include "nsIPrintProgress.h"
+#include "nsIPrintProgressParams.h"
+#include "nsIPrintingPromptService.h"
+
+class nsMsgPrintEngine : public nsIMsgPrintEngine,
+ public nsIWebProgressListener,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+
+public:
+ nsMsgPrintEngine();
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIMsgPrintEngine interface
+ NS_DECL_NSIMSGPRINTENGINE
+
+ // For nsIWebProgressListener
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ // For nsIObserver
+ NS_DECL_NSIOBSERVER
+
+ void PrintMsgWindow();
+ NS_IMETHOD StartNextPrintOperation();
+
+protected:
+ virtual ~nsMsgPrintEngine();
+
+ bool FirePrintEvent();
+ nsresult FireStartNextEvent();
+ nsresult FireThatLoadOperationStartup(const nsString& uri);
+ nsresult FireThatLoadOperation(const nsString& uri);
+ void InitializeDisplayCharset();
+ void SetupObserver();
+ nsresult SetStatusMessage(const nsString& aMsgString);
+ void GetString(const char16_t *aStringName, nsString& aOutString);
+ nsresult ShowProgressDialog(bool aIsForPrinting, bool& aDoNotify);
+
+ nsCOMPtr<nsIDocShell> mDocShell;
+ nsCOMPtr<mozIDOMWindowProxy> mWindow;
+ nsCOMPtr<mozIDOMWindowProxy> mParentWindow;
+ int32_t mURICount;
+ nsTArray<nsString> mURIArray;
+ int32_t mCurrentlyPrintingURI;
+
+ nsCOMPtr<nsIContentViewer> mContentViewer;
+ nsCOMPtr<nsIStringBundle> mStringBundle; // String bundles...
+ nsCOMPtr<nsIMsgStatusFeedback> mFeedback; // Tell the user something why don't ya'
+ nsCOMPtr<nsIWebBrowserPrint> mWebBrowserPrint;
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+ nsCOMPtr<mozIDOMWindowProxy> mMsgDOMWin;
+ bool mIsDoingPrintPreview;
+ nsCOMPtr<nsIObserver> mStartupPPObs;
+ int32_t mMsgInx;
+
+ // Progress Dialog
+
+ nsCOMPtr<nsIPrintingPromptService> mPrintPromptService;
+ nsCOMPtr<nsIWebProgressListener> mPrintProgressListener;
+ nsCOMPtr<nsIPrintProgress> mPrintProgress;
+ nsCOMPtr<nsIPrintProgressParams> mPrintProgressParams;
+ nsString mLoadURI;
+};
diff --git a/mailnews/base/src/nsMsgProgress.cpp b/mailnews/base/src/nsMsgProgress.cpp
new file mode 100644
index 000000000..02f26edbf
--- /dev/null
+++ b/mailnews/base/src/nsMsgProgress.cpp
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgProgress.h"
+
+#include "nsIBaseWindow.h"
+#include "nsXPCOM.h"
+#include "nsIMutableArray.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIComponentManager.h"
+#include "nsError.h"
+#include "nsIWindowWatcher.h"
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+NS_IMPL_ISUPPORTS(nsMsgProgress, nsIMsgStatusFeedback, nsIMsgProgress,
+ nsIWebProgressListener, nsIProgressEventSink, nsISupportsWeakReference)
+
+
+nsMsgProgress::nsMsgProgress()
+{
+ m_closeProgress = false;
+ m_processCanceled = false;
+ m_pendingStateFlags = -1;
+ m_pendingStateValue = NS_OK;
+}
+
+nsMsgProgress::~nsMsgProgress()
+{
+ (void)ReleaseListeners();
+}
+
+NS_IMETHODIMP nsMsgProgress::OpenProgressDialog(mozIDOMWindowProxy *parentDOMWindow,
+ nsIMsgWindow *aMsgWindow,
+ const char *dialogURL,
+ bool inDisplayModal,
+ nsISupports *parameters)
+{
+ nsresult rv;
+
+ if (aMsgWindow)
+ {
+ SetMsgWindow(aMsgWindow);
+ aMsgWindow->SetStatusFeedback(this);
+ }
+
+ NS_ENSURE_ARG_POINTER(dialogURL);
+ NS_ENSURE_ARG_POINTER(parentDOMWindow);
+ nsCOMPtr<nsPIDOMWindowOuter> parent = nsPIDOMWindowOuter::From(parentDOMWindow);
+ parent = parent->GetOuterWindow();
+ NS_ENSURE_ARG_POINTER(parent);
+
+ // Set up window.arguments[0]...
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ifptr->SetData(static_cast<nsIMsgProgress*>(this));
+ ifptr->SetDataIID(&NS_GET_IID(nsIMsgProgress));
+
+ array->AppendElement(ifptr, false);
+ array->AppendElement(parameters, false);
+
+ // Open the dialog.
+ nsCOMPtr<nsPIDOMWindowOuter> newWindow;
+
+ nsString chromeOptions(NS_LITERAL_STRING("chrome,dependent,centerscreen"));
+ if (inDisplayModal)
+ chromeOptions.AppendLiteral(",modal");
+
+ return parent->OpenDialog(NS_ConvertASCIItoUTF16(dialogURL),
+ NS_LITERAL_STRING("_blank"),
+ chromeOptions,
+ array, getter_AddRefs(newWindow));
+}
+
+/* void closeProgressDialog (in boolean forceClose); */
+NS_IMETHODIMP nsMsgProgress::CloseProgressDialog(bool forceClose)
+{
+ m_closeProgress = true;
+ return OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, forceClose ? NS_ERROR_FAILURE : NS_OK);
+}
+
+/* attribute boolean processCanceledByUser; */
+NS_IMETHODIMP nsMsgProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser)
+{
+ NS_ENSURE_ARG_POINTER(aProcessCanceledByUser);
+ *aProcessCanceledByUser = m_processCanceled;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser)
+{
+ m_processCanceled = aProcessCanceledByUser;
+ OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_BINDING_ABORTED);
+ return NS_OK;
+}
+
+/* void RegisterListener (in nsIWebProgressListener listener); */
+NS_IMETHODIMP nsMsgProgress::RegisterListener(nsIWebProgressListener * listener)
+{
+ if (!listener) //Nothing to do with a null listener!
+ return NS_OK;
+
+ NS_ENSURE_ARG(this != listener); //Check for self-reference (see bug 271700)
+
+ m_listenerList.AppendObject(listener);
+ if (m_closeProgress || m_processCanceled)
+ listener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK);
+ else
+ {
+ listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get());
+ if (m_pendingStateFlags != -1)
+ listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue);
+ }
+
+ return NS_OK;
+}
+
+/* void UnregisterListener (in nsIWebProgressListener listener); */
+NS_IMETHODIMP nsMsgProgress::UnregisterListener(nsIWebProgressListener *listener)
+{
+ if (listener)
+ m_listenerList.RemoveObject(listener);
+ return NS_OK;
+}
+
+/* void onStateChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long aStateFlags, in nsresult aStatus); */
+NS_IMETHODIMP nsMsgProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus)
+{
+ m_pendingStateFlags = aStateFlags;
+ m_pendingStateValue = aStatus;
+
+ nsCOMPtr<nsIMsgWindow> msgWindow (do_QueryReferent(m_msgWindow));
+ if (aStateFlags == nsIWebProgressListener::STATE_STOP && msgWindow && NS_FAILED(aStatus))
+ {
+ msgWindow->StopUrls();
+ msgWindow->SetStatusFeedback(nullptr);
+ }
+
+ for (int32_t i = m_listenerList.Count() - 1; i >= 0; i --)
+ m_listenerList[i]->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
+
+ return NS_OK;
+}
+
+/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */
+NS_IMETHODIMP nsMsgProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress)
+{
+ for (int32_t i = m_listenerList.Count() - 1; i >= 0; i --)
+ m_listenerList[i]->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress);
+ return NS_OK;
+}
+
+/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location, in unsigned long aFlags); */
+NS_IMETHODIMP nsMsgProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */
+NS_IMETHODIMP nsMsgProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage)
+{
+ if (aMessage && *aMessage)
+ m_pendingStatus = aMessage;
+ for (int32_t i = m_listenerList.Count() - 1; i >= 0; i --)
+ m_listenerList[i]->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ return NS_OK;
+}
+
+/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */
+NS_IMETHODIMP nsMsgProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state)
+{
+ return NS_OK;
+}
+
+nsresult nsMsgProgress::ReleaseListeners()
+{
+ m_listenerList.Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::ShowStatusString(const nsAString& aStatus)
+{
+ return OnStatusChange(nullptr, nullptr, NS_OK, PromiseFlatString(aStatus).get());
+}
+
+NS_IMETHODIMP nsMsgProgress::SetStatusString(const nsAString& aStatus)
+{
+ return OnStatusChange(nullptr, nullptr, NS_OK, PromiseFlatString(aStatus).get());
+}
+
+/* void startMeteors (); */
+NS_IMETHODIMP nsMsgProgress::StartMeteors()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void stopMeteors (); */
+NS_IMETHODIMP nsMsgProgress::StopMeteors()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void showProgress (in long percent); */
+NS_IMETHODIMP nsMsgProgress::ShowProgress(int32_t percent)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgProgress::SetWrappedStatusFeedback(nsIMsgStatusFeedback * aJSStatusFeedback)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgProgress::SetMsgWindow(nsIMsgWindow *aMsgWindow)
+{
+ m_msgWindow = do_GetWeakReference(aMsgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::GetMsgWindow(nsIMsgWindow **aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ if (m_msgWindow)
+ CallQueryReferent(m_msgWindow.get(), aMsgWindow);
+ else
+ *aMsgWindow = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::OnProgress(nsIRequest *request, nsISupports* ctxt,
+ int64_t aProgress, int64_t aProgressMax)
+{
+ // XXX: What should the nsIWebProgress be?
+ // XXX: This truncates 64-bit to 32-bit
+ return OnProgressChange(nullptr, request, int32_t(aProgress), int32_t(aProgressMax),
+ int32_t(aProgress) /* current total progress */, int32_t(aProgressMax) /* max total progress */);
+}
+
+NS_IMETHODIMP nsMsgProgress::OnStatus(nsIRequest *request, nsISupports* ctxt,
+ nsresult aStatus, const char16_t* aStatusArg)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> sbs =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(sbs, NS_ERROR_UNEXPECTED);
+ nsString str;
+ rv = sbs->FormatStatusMessage(aStatus, aStatusArg, getter_Copies(str));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowStatusString(str);
+}
diff --git a/mailnews/base/src/nsMsgProgress.h b/mailnews/base/src/nsMsgProgress.h
new file mode 100644
index 000000000..4450c6cce
--- /dev/null
+++ b/mailnews/base/src/nsMsgProgress.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgProgress_h_
+#define nsMsgProgress_h_
+
+#include "nsIMsgProgress.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIDOMWindow.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsStringGlue.h"
+#include "nsIMsgWindow.h"
+#include "nsIProgressEventSink.h"
+#include "nsIStringBundle.h"
+#include "nsWeakReference.h"
+
+class nsMsgProgress : public nsIMsgProgress,
+ public nsIMsgStatusFeedback,
+ public nsIProgressEventSink,
+ public nsSupportsWeakReference
+{
+public:
+ nsMsgProgress();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGPROGRESS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIMSGSTATUSFEEDBACK
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+private:
+ virtual ~nsMsgProgress();
+ nsresult ReleaseListeners(void);
+
+ bool m_closeProgress;
+ bool m_processCanceled;
+ nsString m_pendingStatus;
+ int32_t m_pendingStateFlags;
+ nsresult m_pendingStateValue;
+ nsWeakPtr m_msgWindow;
+ nsCOMArray<nsIWebProgressListener> m_listenerList;
+};
+
+#endif // nsMsgProgress_h_
diff --git a/mailnews/base/src/nsMsgPurgeService.cpp b/mailnews/base/src/nsMsgPurgeService.cpp
new file mode 100644
index 000000000..98d9e7cce
--- /dev/null
+++ b/mailnews/base/src/nsMsgPurgeService.cpp
@@ -0,0 +1,486 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgPurgeService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgUtils.h"
+#include "nsMsgSearchCore.h"
+#include "msgCore.h"
+#include "nsISpamSettings.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "mozilla/Logging.h"
+#include "nsMsgFolderFlags.h"
+#include <stdlib.h>
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsArrayUtils.h"
+
+static PRLogModuleInfo *MsgPurgeLogModule = nullptr;
+
+NS_IMPL_ISUPPORTS(nsMsgPurgeService, nsIMsgPurgeService, nsIMsgSearchNotify)
+
+void OnPurgeTimer(nsITimer *timer, void *aPurgeService)
+{
+ nsMsgPurgeService *purgeService = (nsMsgPurgeService*)aPurgeService;
+ purgeService->PerformPurge();
+}
+
+nsMsgPurgeService::nsMsgPurgeService()
+{
+ mHaveShutdown = false;
+ mMinDelayBetweenPurges = 480; // never purge a folder more than once every 8 hours (60 min/hour * 8 hours)
+ mPurgeTimerInterval = 5; // fire the purge timer every 5 minutes, starting 5 minutes after the service is created (when we load accounts)
+}
+
+nsMsgPurgeService::~nsMsgPurgeService()
+{
+ if (mPurgeTimer)
+ mPurgeTimer->Cancel();
+
+ if(!mHaveShutdown)
+ Shutdown();
+}
+
+NS_IMETHODIMP nsMsgPurgeService::Init()
+{
+ nsresult rv;
+
+ if (!MsgPurgeLogModule)
+ MsgPurgeLogModule = PR_NewLogModule("MsgPurge");
+
+ // these prefs are here to help QA test this feature
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ int32_t min_delay;
+ rv = prefBranch->GetIntPref("mail.purge.min_delay", &min_delay);
+ if (NS_SUCCEEDED(rv) && min_delay)
+ mMinDelayBetweenPurges = min_delay;
+
+ int32_t purge_timer_interval;
+ rv = prefBranch->GetIntPref("mail.purge.timer_interval", &purge_timer_interval);
+ if (NS_SUCCEEDED(rv) && purge_timer_interval)
+ mPurgeTimerInterval = purge_timer_interval;
+ }
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("mail.purge.min_delay=%d minutes",mMinDelayBetweenPurges));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("mail.purge.timer_interval=%d minutes",mPurgeTimerInterval));
+
+ // don't start purging right away.
+ // because the accounts aren't loaded and because the user might be trying to sign in
+ // or startup, etc.
+ SetupNextPurge();
+
+ mHaveShutdown = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgPurgeService::Shutdown()
+{
+ if (mPurgeTimer)
+ {
+ mPurgeTimer->Cancel();
+ mPurgeTimer = nullptr;
+ }
+
+ mHaveShutdown = true;
+ return NS_OK;
+}
+
+nsresult nsMsgPurgeService::SetupNextPurge()
+{
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("setting to check again in %d minutes",mPurgeTimerInterval));
+
+ // Convert mPurgeTimerInterval into milliseconds
+ uint32_t timeInMSUint32 = mPurgeTimerInterval * 60000;
+
+ // Can't currently reset a timer when it's in the process of
+ // calling Notify. So, just release the timer here and create a new one.
+ if(mPurgeTimer)
+ mPurgeTimer->Cancel();
+
+ mPurgeTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mPurgeTimer->InitWithFuncCallback(OnPurgeTimer, (void*)this, timeInMSUint32,
+ nsITimer::TYPE_ONE_SHOT);
+
+ return NS_OK;
+}
+
+// This is the function that looks for the first folder to purge. It also
+// applies retention settings to any folder that hasn't had retention settings
+// applied in mMinDelayBetweenPurges minutes (default, 8 hours).
+// However, if we've spent more than .5 seconds in this loop, don't
+// apply any more retention settings because it might lock up the UI.
+// This might starve folders later on in the hierarchy, since we always
+// start at the top, but since we also apply retention settings when you
+// open a folder, or when you compact all folders, I think this will do
+// for now, until we have a cleanup on shutdown architecture.
+nsresult nsMsgPurgeService::PerformPurge()
+{
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("performing purge"));
+
+ nsresult rv;
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ bool keepApplyingRetentionSettings = true;
+
+ nsCOMPtr<nsIArray> allServers;
+ rv = accountManager->GetAllServers(getter_AddRefs(allServers));
+ if (NS_SUCCEEDED(rv) && allServers)
+ {
+ uint32_t numServers;
+ rv = allServers->GetLength(&numServers);
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("%d servers", numServers));
+ nsCOMPtr<nsIMsgFolder> folderToPurge;
+ PRIntervalTime startTime = PR_IntervalNow();
+ int32_t purgeIntervalToUse = 0;
+ PRTime oldestPurgeTime = 0; // we're going to pick the least-recently purged folder
+
+ // apply retention settings to folders that haven't had retention settings
+ // applied in mMinDelayBetweenPurges minutes (default 8 hours)
+ // Because we get last purge time from the folder cache,
+ // this code won't open db's for folders until it decides it needs
+ // to apply retention settings, and since nsIMsgFolder::ApplyRetentionSettings
+ // will close any db's it opens, this code won't leave db's open.
+ for (uint32_t serverIndex=0; serverIndex < numServers; serverIndex++)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> server =
+ do_QueryElementAt(allServers, serverIndex, &rv);
+ if (NS_SUCCEEDED(rv) && server)
+ {
+ if (keepApplyingRetentionSettings)
+ {
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIArray> childFolders;
+ rv = rootFolder->GetDescendants(getter_AddRefs(childFolders));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cnt = 0;
+ childFolders->GetLength(&cnt);
+
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIUrlListener> urlListener;
+ nsCOMPtr<nsIMsgFolder> childFolder;
+
+ for (uint32_t index = 0; index < cnt; index++)
+ {
+ childFolder = do_QueryElementAt(childFolders, index);
+ if (childFolder)
+ {
+ uint32_t folderFlags;
+ (void) childFolder->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Virtual)
+ continue;
+ PRTime curFolderLastPurgeTime = 0;
+ nsCString curFolderLastPurgeTimeString, curFolderUri;
+ rv = childFolder->GetStringProperty("LastPurgeTime", curFolderLastPurgeTimeString);
+ if (NS_FAILED(rv))
+ continue; // it is ok to fail, go on to next folder
+
+ if (!curFolderLastPurgeTimeString.IsEmpty())
+ {
+ PRTime theTime;
+ PR_ParseTimeString(curFolderLastPurgeTimeString.get(), false, &theTime);
+ curFolderLastPurgeTime = theTime;
+ }
+
+ childFolder->GetURI(curFolderUri);
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("%s curFolderLastPurgeTime=%s (if blank, then never)", curFolderUri.get(), curFolderLastPurgeTimeString.get()));
+
+ // check if this folder is due to purge
+ // has to have been purged at least mMinDelayBetweenPurges minutes ago
+ // we don't want to purge the folders all the time - once a day is good enough
+ int64_t minDelayBetweenPurges(mMinDelayBetweenPurges);
+ int64_t microSecondsPerMinute(60000000);
+ PRTime nextPurgeTime = curFolderLastPurgeTime + (minDelayBetweenPurges * microSecondsPerMinute);
+ if (nextPurgeTime < PR_Now())
+ {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("purging %s", curFolderUri.get()));
+ childFolder->ApplyRetentionSettings();
+ }
+ PRIntervalTime elapsedTime = PR_IntervalNow() - startTime;
+ // check if more than 500 milliseconds have elapsed in this purge process
+ if (PR_IntervalToMilliseconds(elapsedTime) > 500)
+ {
+ keepApplyingRetentionSettings = false;
+ break;
+ }
+ }
+ }
+ }
+ nsCString type;
+ nsresult rv = server->GetType(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString realHostName;
+ server->GetRealHostName(realHostName);
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] %s (%s)", serverIndex, realHostName.get(), type.get()));
+
+ nsCOMPtr <nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t spamLevel;
+ spamSettings->GetLevel(&spamLevel);
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] spamLevel=%d (if 0, don't purge)", serverIndex, spamLevel));
+ if (!spamLevel)
+ continue;
+
+ // check if we are set up to purge for this server
+ // if not, skip it.
+ bool purgeSpam;
+ spamSettings->GetPurge(&purgeSpam);
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] purgeSpam=%s (if false, don't purge)", serverIndex, purgeSpam ? "true" : "false"));
+ if (!purgeSpam)
+ continue;
+
+ // check if the spam folder uri is set for this server
+ // if not skip it.
+ nsCString junkFolderURI;
+ rv = spamSettings->GetSpamFolderURI(getter_Copies(junkFolderURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] junkFolderURI=%s (if empty, don't purge)", serverIndex, junkFolderURI.get()));
+ if (junkFolderURI.IsEmpty())
+ continue;
+
+ // if the junk folder doesn't exist
+ // because the folder pane isn't built yet, for example
+ // skip this account
+ nsCOMPtr<nsIMsgFolder> junkFolder;
+ GetExistingFolder(junkFolderURI, getter_AddRefs(junkFolder));
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] %s exists? %s (if doesn't exist, don't purge)", serverIndex, junkFolderURI.get(), junkFolder ? "true" : "false"));
+ if (!junkFolder)
+ continue;
+
+ PRTime curJunkFolderLastPurgeTime = 0;
+ nsCString curJunkFolderLastPurgeTimeString;
+ rv = junkFolder->GetStringProperty("curJunkFolderLastPurgeTime", curJunkFolderLastPurgeTimeString);
+ if (NS_FAILED(rv))
+ continue; // it is ok to fail, junk folder may not exist
+
+ if (!curJunkFolderLastPurgeTimeString.IsEmpty())
+ {
+ PRTime theTime;
+ PR_ParseTimeString(curJunkFolderLastPurgeTimeString.get(), false, &theTime);
+ curJunkFolderLastPurgeTime = theTime;
+ }
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] %s curJunkFolderLastPurgeTime=%s (if blank, then never)", serverIndex, junkFolderURI.get(), curJunkFolderLastPurgeTimeString.get()));
+
+ // check if this account is due to purge
+ // has to have been purged at least mMinDelayBetweenPurges minutes ago
+ // we don't want to purge the folders all the time
+ PRTime nextPurgeTime = curJunkFolderLastPurgeTime + mMinDelayBetweenPurges * 60000000 /* convert mMinDelayBetweenPurges to into microseconds */;
+ if (nextPurgeTime < PR_Now())
+ {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] last purge greater than min delay", serverIndex));
+
+ nsCOMPtr <nsIMsgIncomingServer> junkFolderServer;
+ rv = junkFolder->GetServer(getter_AddRefs(junkFolderServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool serverBusy = false;
+ bool serverRequiresPassword = true;
+ bool passwordPromptRequired;
+ bool canSearchMessages = false;
+ junkFolderServer->GetPasswordPromptRequired(&passwordPromptRequired);
+ junkFolderServer->GetServerBusy(&serverBusy);
+ junkFolderServer->GetServerRequiresPasswordForBiff(&serverRequiresPassword);
+ junkFolderServer->GetCanSearchMessages(&canSearchMessages);
+ // Make sure we're logged on before doing the search (assuming we need to be)
+ // and make sure the server isn't already in the middle of downloading new messages
+ // and make sure a search isn't already going on
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] (search in progress? %s)", serverIndex, mSearchSession ? "true" : "false"));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] (server busy? %s)", serverIndex, serverBusy ? "true" : "false"));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] (serverRequiresPassword? %s)", serverIndex, serverRequiresPassword ? "true" : "false"));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] (passwordPromptRequired? %s)", serverIndex, passwordPromptRequired ? "true" : "false"));
+ if (canSearchMessages && !mSearchSession && !serverBusy && (!serverRequiresPassword || !passwordPromptRequired))
+ {
+ int32_t purgeInterval;
+ spamSettings->GetPurgeInterval(&purgeInterval);
+
+ if ((oldestPurgeTime == 0) || (curJunkFolderLastPurgeTime < oldestPurgeTime))
+ {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] purging! searching for messages older than %d days", serverIndex, purgeInterval));
+ oldestPurgeTime = curJunkFolderLastPurgeTime;
+ purgeIntervalToUse = purgeInterval;
+ folderToPurge = junkFolder;
+ // if we've never purged this folder, do it...
+ if (curJunkFolderLastPurgeTime == 0)
+ break;
+ }
+ }
+ else {
+ NS_ASSERTION(canSearchMessages, "unexpected, you should be able to search");
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] not a good time for this server, try again later", serverIndex));
+ }
+ }
+ else {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] last purge too recent", serverIndex));
+ }
+ }
+ }
+ if (folderToPurge && purgeIntervalToUse != 0)
+ rv = SearchFolderToPurge(folderToPurge, purgeIntervalToUse);
+ }
+
+ // set up timer to check accounts again
+ SetupNextPurge();
+ return rv;
+}
+
+nsresult nsMsgPurgeService::SearchFolderToPurge(nsIMsgFolder *folder, int32_t purgeInterval)
+{
+ nsresult rv;
+ mSearchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSearchSession->RegisterListener(this,
+ nsIMsgSearchSession::allNotifications);
+
+ // update the time we attempted to purge this folder
+ char dateBuf[100];
+ dateBuf[0] = '\0';
+ PRExplodedTime exploded;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y", &exploded);
+ folder->SetStringProperty("curJunkFolderLastPurgeTime", nsDependentCString(dateBuf));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("curJunkFolderLastPurgeTime is now %s", dateBuf));
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server)); //we need to get the folder's server scope because imap can have local junk folder
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgSearchScopeValue searchScope;
+ server->GetSearchScope(&searchScope);
+
+ mSearchSession->AddScopeTerm(searchScope, folder);
+
+ // look for messages older than the cutoff
+ // you can't also search by junk status, see
+ // nsMsgPurgeService::OnSearchHit()
+ nsCOMPtr <nsIMsgSearchTerm> searchTerm;
+ mSearchSession->CreateTerm(getter_AddRefs(searchTerm));
+ if (searchTerm)
+ {
+ searchTerm->SetAttrib(nsMsgSearchAttrib::AgeInDays);
+ searchTerm->SetOp(nsMsgSearchOp::IsGreaterThan);
+ nsCOMPtr<nsIMsgSearchValue> searchValue;
+ searchTerm->GetValue(getter_AddRefs(searchValue));
+ if (searchValue)
+ {
+ searchValue->SetAttrib(nsMsgSearchAttrib::AgeInDays);
+ searchValue->SetAge((uint32_t) purgeInterval);
+ searchTerm->SetValue(searchValue);
+ }
+ searchTerm->SetBooleanAnd(false);
+ mSearchSession->AppendTerm(searchTerm);
+ }
+
+ // we are about to search
+ // create mHdrsToDelete array (if not previously created)
+ if (!mHdrsToDelete)
+ {
+ mHdrsToDelete = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ uint32_t count;
+ mHdrsToDelete->GetLength(&count);
+ NS_ASSERTION(count == 0, "mHdrsToDelete is not empty");
+ if (count > 0)
+ mHdrsToDelete->Clear(); // this shouldn't happen
+ }
+
+ mSearchFolder = folder;
+ return mSearchSession->Search(nullptr);
+}
+
+NS_IMETHODIMP nsMsgPurgeService::OnNewSearch()
+{
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("on new search"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgPurgeService::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+
+ nsCString messageId;
+ nsCString author;
+ nsCString subject;
+
+ aMsgHdr->GetMessageId(getter_Copies(messageId));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("messageId=%s", messageId.get()));
+ aMsgHdr->GetSubject(getter_Copies(subject));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("subject=%s",subject.get()));
+ aMsgHdr->GetAuthor(getter_Copies(author));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("author=%s",author.get()));
+
+ // double check that the message is junk before adding to
+ // the list of messages to delete
+ //
+ // note, we can't just search for messages that are junk
+ // because not all imap server support keywords
+ // (which we use for the junk score)
+ // so the junk status would be in the message db.
+ //
+ // see bug #194090
+ nsCString junkScoreStr;
+ nsresult rv = aMsgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("junkScore=%s (if empty or != nsIJunkMailPlugin::IS_SPAM_SCORE, don't add to list delete)", junkScoreStr.get()));
+
+ // if "junkscore" is not set, don't delete the message
+ if (junkScoreStr.IsEmpty())
+ return NS_OK;
+
+ if (atoi(junkScoreStr.get()) == nsIJunkMailPlugin::IS_SPAM_SCORE) {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("added message to delete"));
+ return mHdrsToDelete->AppendElement(aMsgHdr, false);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgPurgeService::OnSearchDone(nsresult status)
+{
+ if (NS_SUCCEEDED(status))
+ {
+ uint32_t count = 0;
+ if (mHdrsToDelete)
+ mHdrsToDelete->GetLength(&count);
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("%d messages to delete", count));
+
+ if (count > 0) {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("delete messages"));
+ if (mSearchFolder)
+ mSearchFolder->DeleteMessages(mHdrsToDelete, nullptr, false /*delete storage*/, false /*isMove*/, nullptr, false /*allowUndo*/);
+ }
+ }
+ if (mHdrsToDelete)
+ mHdrsToDelete->Clear();
+ if (mSearchSession)
+ mSearchSession->UnregisterListener(this);
+ // don't cache the session
+ // just create another search session next time we search, rather than clearing scopes, terms etc.
+ // we also use mSearchSession to determine if the purge service is "busy"
+ mSearchSession = nullptr;
+ mSearchFolder = nullptr;
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMsgPurgeService.h b/mailnews/base/src/nsMsgPurgeService.h
new file mode 100644
index 000000000..d9757d65a
--- /dev/null
+++ b/mailnews/base/src/nsMsgPurgeService.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSMSGPURGESERVICE_H
+#define NSMSGPURGESERVICE_H
+
+#include "msgCore.h"
+#include "nsIMsgPurgeService.h"
+#include "nsIMsgSearchSession.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsIMutableArray.h"
+
+class nsMsgPurgeService
+ : public nsIMsgPurgeService,
+ public nsIMsgSearchNotify
+{
+public:
+ nsMsgPurgeService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPURGESERVICE
+ NS_DECL_NSIMSGSEARCHNOTIFY
+
+ nsresult PerformPurge();
+
+protected:
+ virtual ~nsMsgPurgeService();
+ int32_t FindServer(nsIMsgIncomingServer *server);
+ nsresult SetupNextPurge();
+ nsresult PurgeSurver(nsIMsgIncomingServer *server);
+ nsresult SearchFolderToPurge(nsIMsgFolder *folder, int32_t purgeInterval);
+
+protected:
+ nsCOMPtr<nsITimer> mPurgeTimer;
+ nsCOMPtr<nsIMsgSearchSession> mSearchSession;
+ nsCOMPtr<nsIMsgFolder> mSearchFolder;
+ nsCOMPtr<nsIMutableArray> mHdrsToDelete;
+ bool mHaveShutdown;
+
+private:
+ int32_t mMinDelayBetweenPurges; // in minutes, how long must pass between two consecutive purges on the same junk folder?
+ int32_t mPurgeTimerInterval; // in minutes, how often to check if we need to purge one of the junk folders?
+};
+
+
+
+#endif
+
diff --git a/mailnews/base/src/nsMsgQuickSearchDBView.cpp b/mailnews/base/src/nsMsgQuickSearchDBView.cpp
new file mode 100644
index 000000000..3f8c53d2d
--- /dev/null
+++ b/mailnews/base/src/nsMsgQuickSearchDBView.cpp
@@ -0,0 +1,882 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgQuickSearchDBView.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsImapCore.h"
+#include "nsIMsgHdr.h"
+#include "nsIDBFolderInfo.h"
+#include "nsArrayEnumerator.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMutableArray.h"
+#include "nsMsgUtils.h"
+
+nsMsgQuickSearchDBView::nsMsgQuickSearchDBView()
+{
+ m_usingCachedHits = false;
+ m_cacheEmpty = true;
+}
+
+nsMsgQuickSearchDBView::~nsMsgQuickSearchDBView()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgQuickSearchDBView, nsMsgDBView, nsIMsgDBView, nsIMsgSearchNotify)
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount)
+{
+ nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_db)
+ return NS_ERROR_NULL_POINTER;
+ if (pCount)
+ *pCount = 0;
+ m_viewFolder = nullptr;
+ return InitThreadedView(pCount);
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::CloneDBView(nsIMessenger *aMessengerInstance,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgDBViewCommandUpdater *aCmdUpdater,
+ nsIMsgDBView **_retval)
+{
+ nsMsgQuickSearchDBView* newMsgDBView = new nsMsgQuickSearchDBView();
+
+ if (!newMsgDBView)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::CopyDBView(nsMsgDBView *aNewMsgDBView,
+ nsIMessenger *aMessengerInstance,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgDBViewCommandUpdater *aCmdUpdater)
+{
+ nsMsgThreadedDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ nsMsgQuickSearchDBView* newMsgDBView = (nsMsgQuickSearchDBView *) aNewMsgDBView;
+
+ // now copy all of our private member data
+ newMsgDBView->m_origKeys = m_origKeys;
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage)
+{
+ for (nsMsgViewIndex i = 0; i < (nsMsgViewIndex) numIndices; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ (void) GetMsgHdrForViewIndex(indices[i],getter_AddRefs(msgHdr));
+ if (msgHdr)
+ RememberDeletedMsgHdr(msgHdr);
+ }
+
+ return nsMsgDBView::DeleteMessages(window, indices, numIndices, deleteStorage);
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::DoCommand(nsMsgViewCommandTypeValue aCommand)
+{
+ if (aCommand == nsMsgViewCommandType::markAllRead)
+ {
+ nsresult rv = NS_OK;
+ m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false, true /*dbBatching*/);
+
+ for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < GetSize(); i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ m_db->GetMsgHdrForKey(m_keys[i],getter_AddRefs(msgHdr));
+ rv = m_db->MarkHdrRead(msgHdr, true, nullptr);
+ }
+
+ m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true, true /*dbBatching*/);
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder);
+ if (NS_SUCCEEDED(rv) && imapFolder)
+ rv = imapFolder->StoreImapFlags(kImapMsgSeenFlag, true, m_keys.Elements(),
+ m_keys.Length(), nullptr);
+
+ m_db->SetSummaryValid(true);
+ return rv;
+ }
+ else
+ return nsMsgDBView::DoCommand(aCommand);
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowQuickSearchResults;
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex)
+{
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ // protect against duplication.
+ if (m_origKeys.BinaryIndexOf(msgKey) == m_origKeys.NoIndex)
+ {
+ nsMsgViewIndex insertIndex = GetInsertIndexHelper(msgHdr, m_origKeys, nullptr,
+ nsMsgViewSortOrder::ascending, nsMsgViewSortType::byId);
+ m_origKeys.InsertElementAt(insertIndex, msgKey);
+ }
+ if (m_viewFlags & (nsMsgViewFlagsType::kGroupBySort|
+ nsMsgViewFlagsType::kThreadedDisplay))
+ {
+ nsMsgKey parentKey;
+ msgHdr->GetThreadParent(&parentKey);
+ return nsMsgThreadedDBView::OnNewHeader(msgHdr, parentKey, true);
+ }
+ else
+ return nsMsgDBView::AddHdr(msgHdr, resultIndex);
+}
+
+nsresult nsMsgQuickSearchDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed)
+{
+ if (newHdr)
+ {
+ bool match=false;
+ nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
+ if (searchSession)
+ searchSession->MatchHdr(newHdr, m_db, &match);
+ if (match)
+ {
+ // put the new header in m_origKeys, so that expanding a thread will
+ // show the newly added header.
+ nsMsgKey newKey;
+ (void) newHdr->GetMessageKey(&newKey);
+ nsMsgViewIndex insertIndex = GetInsertIndexHelper(newHdr, m_origKeys, nullptr,
+ nsMsgViewSortOrder::ascending, nsMsgViewSortType::byId);
+ m_origKeys.InsertElementAt(insertIndex, newKey);
+ nsMsgThreadedDBView::OnNewHeader(newHdr, aParentKey, ensureListed); // do not add a new message if there isn't a match.
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags, nsIDBChangeListener *aInstigator)
+{
+ nsresult rv = nsMsgGroupView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator);
+
+ if (m_viewFolder &&
+ (m_viewFolder != m_folder) &&
+ (aOldFlags & nsMsgMessageFlags::Read) != (aNewFlags & nsMsgMessageFlags::Read))
+ {
+ // if we're displaying a single folder virtual folder for an imap folder,
+ // the search criteria might be on message body, and we might not have the
+ // message body offline, in which case we can't tell if the message
+ // matched or not. But if the unread flag changed, we need to update the
+ // unread counts. Normally, VirtualFolderChangeListener::OnHdrFlagsChanged will
+ // handle this, but it won't work for body criteria when we don't have the
+ // body offline.
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_viewFolder);
+ if (imapFolder)
+ {
+ nsMsgViewIndex hdrIndex = FindHdr(aHdrChanged);
+ if (hdrIndex != nsMsgViewIndex_None)
+ {
+ nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
+ if (searchSession)
+ {
+ bool oldMatch, newMatch;
+ rv = searchSession->MatchHdr(aHdrChanged, m_db, &newMatch);
+ aHdrChanged->SetFlags(aOldFlags);
+ rv = searchSession->MatchHdr(aHdrChanged, m_db, &oldMatch);
+ aHdrChanged->SetFlags(aNewFlags);
+ // if it doesn't match the criteria, VirtualFolderChangeListener::OnHdrFlagsChanged
+ // won't tweak the read/unread counts. So do it here:
+ if (!oldMatch && !newMatch)
+ {
+ nsCOMPtr <nsIMsgDatabase> virtDatabase;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+
+ rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbFolderInfo->ChangeNumUnreadMessages((aOldFlags & nsMsgMessageFlags::Read) ? 1 : -1);
+ m_viewFolder->UpdateSummaryTotals(true); // force update from db.
+ virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrChanged, bool aPreChange,
+ uint32_t *aStatus, nsIDBChangeListener *aInstigator)
+{
+ // If the junk mail plugin just activated on a message, then
+ // we'll allow filters to remove from view.
+ // Otherwise, just update the view line.
+ //
+ // Note this will not add newly matched headers to the view. This is
+ // probably a bug that needs fixing.
+
+ NS_ENSURE_ARG_POINTER(aStatus);
+ NS_ENSURE_ARG_POINTER(aHdrChanged);
+
+ nsMsgViewIndex index = FindHdr(aHdrChanged);
+ if (index == nsMsgViewIndex_None) // message does not appear in view
+ return NS_OK;
+
+ nsCString originStr;
+ (void) aHdrChanged->GetStringProperty("junkscoreorigin", getter_Copies(originStr));
+ // check for "plugin" with only first character for performance
+ bool plugin = (originStr.get()[0] == 'p');
+
+ if (aPreChange)
+ {
+ // first call, done prior to the change
+ *aStatus = plugin;
+ return NS_OK;
+ }
+
+ // second call, done after the change
+ bool wasPlugin = *aStatus;
+
+ bool match = true;
+ nsCOMPtr<nsIMsgSearchSession> searchSession(do_QueryReferent(m_searchSession));
+ if (searchSession)
+ searchSession->MatchHdr(aHdrChanged, m_db, &match);
+
+ if (!match && plugin && !wasPlugin)
+ RemoveByIndex(index); // remove hdr from view
+ else
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::GetSearchSession(nsIMsgSearchSession* *aSession)
+{
+ NS_ASSERTION(false, "GetSearchSession method is not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::SetSearchSession(nsIMsgSearchSession *aSession)
+{
+ m_searchSession = do_GetWeakReference(aSession);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder)
+{
+ NS_ENSURE_ARG(aMsgHdr);
+ if (!m_db)
+ return NS_ERROR_NULL_POINTER;
+ // remember search hit and when search is done, reconcile cache
+ // with new hits;
+ m_hdrHits.AppendObject(aMsgHdr);
+ nsMsgKey key;
+ aMsgHdr->GetMessageKey(&key);
+ // Is FindKey going to be expensive here? A lot of hits could make
+ // it a little bit slow to search through the view for every hit.
+ if (m_cacheEmpty || FindKey(key, false) == nsMsgViewIndex_None)
+ return AddHdr(aMsgHdr);
+ else
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OnSearchDone(nsresult status)
+{
+ // We're a single-folder virtual folder if viewFolder != folder, and that is
+ // the only case in which we want to be messing about with a results cache
+ // or unread counts.
+ if (m_db && m_viewFolder && m_viewFolder != m_folder)
+ {
+ nsTArray<nsMsgKey> keyArray;
+ nsCString searchUri;
+ m_viewFolder->GetURI(searchUri);
+ uint32_t count = m_hdrHits.Count();
+ // Build up message keys. The hits are in descending order but the cache
+ // expects them to be in ascending key order.
+ for (uint32_t i = count; i > 0; i--)
+ {
+ nsMsgKey key;
+ m_hdrHits[i-1]->GetMessageKey(&key);
+ keyArray.AppendElement(key);
+ }
+ nsMsgKey *staleHits;
+ uint32_t numBadHits;
+ if (m_db)
+ {
+ nsresult rv = m_db->RefreshCache(searchUri.get(), m_hdrHits.Count(),
+ keyArray.Elements(), &numBadHits, &staleHits);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBHdr> hdrDeleted;
+
+ for (uint32_t i = 0; i < numBadHits; i++)
+ {
+ m_db->GetMsgHdrForKey(staleHits[i], getter_AddRefs(hdrDeleted));
+ if (hdrDeleted)
+ OnHdrDeleted(hdrDeleted, nsMsgKey_None, 0, this);
+ }
+ delete [] staleHits;
+ }
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numUnread = 0;
+ size_t numTotal = m_origKeys.Length();
+
+ for (size_t i = 0; i < m_origKeys.Length(); i++)
+ {
+ bool isRead;
+ m_db->IsRead(m_origKeys[i], &isRead);
+ if (!isRead)
+ numUnread++;
+ }
+ dbFolderInfo->SetNumUnreadMessages(numUnread);
+ dbFolderInfo->SetNumMessages(numTotal);
+ m_viewFolder->UpdateSummaryTotals(true); // force update from db.
+ virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ if (m_sortType != nsMsgViewSortType::byThread)//we do not find levels for the results.
+ {
+ m_sortValid = false; //sort the results
+ Sort(m_sortType, m_sortOrder);
+ }
+ if (m_viewFolder && (m_viewFolder != m_folder))
+ SetMRUTimeForFolder(m_viewFolder);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OnNewSearch()
+{
+ int32_t oldSize = GetSize();
+
+ m_keys.Clear();
+ m_levels.Clear();
+ m_flags.Clear();
+ m_hdrHits.Clear();
+ // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount()
+ if (mTree)
+ mTree->RowCountChanged(0, -oldSize);
+ uint32_t folderFlags = 0;
+ if (m_viewFolder)
+ m_viewFolder->GetFlags(&folderFlags);
+ // check if it's a virtual folder - if so, we should get the cached hits
+ // from the db, and set a flag saying that we're using cached values.
+ if (folderFlags & nsMsgFolderFlags::Virtual)
+ {
+ nsCOMPtr<nsISimpleEnumerator> cachedHits;
+ nsCString searchUri;
+ m_viewFolder->GetURI(searchUri);
+ m_db->GetCachedHits(searchUri.get(), getter_AddRefs(cachedHits));
+ if (cachedHits)
+ {
+ bool hasMore;
+
+ m_usingCachedHits = true;
+ cachedHits->HasMoreElements(&hasMore);
+ m_cacheEmpty = !hasMore;
+ if (mTree)
+ mTree->BeginUpdateBatch();
+ while (hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ nsresult rv = cachedHits->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports);
+ if (pHeader && NS_SUCCEEDED(rv))
+ AddHdr(pHeader);
+ else
+ break;
+ cachedHits->HasMoreElements(&hasMore);
+ }
+ if (mTree)
+ mTree->EndUpdateBatch();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result)
+{
+ uint32_t numChildren;
+ nsresult rv = NS_OK;
+ uint8_t minLevel = 0xff;
+ threadHdr->GetNumChildren(&numChildren);
+ nsMsgKey threadRootKey;
+ nsCOMPtr<nsIMsgDBHdr> rootParent;
+ int32_t rootIndex;
+ threadHdr->GetRootHdr(&rootIndex, getter_AddRefs(rootParent));
+ if (rootParent)
+ rootParent->GetMessageKey(&threadRootKey);
+ else
+ threadHdr->GetThreadKey(&threadRootKey);
+
+ nsCOMPtr <nsIMsgDBHdr> retHdr;
+
+ // iterate over thread, finding mgsHdr in view with the lowest level.
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> child;
+ rv = threadHdr->GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ nsMsgKey msgKey;
+ child->GetMessageKey(&msgKey);
+
+ // this works because we've already sorted m_keys by id.
+ nsMsgViewIndex keyIndex = m_origKeys.BinaryIndexOf(msgKey);
+ if (keyIndex != nsMsgViewIndex_None)
+ {
+ // this is the root, so it's the best we're going to do.
+ if (msgKey == threadRootKey)
+ {
+ retHdr = child;
+ break;
+ }
+ uint8_t level = 0;
+ nsMsgKey parentId;
+ child->GetThreadParent(&parentId);
+ nsCOMPtr <nsIMsgDBHdr> parent;
+ // count number of ancestors - that's our level
+ while (parentId != nsMsgKey_None)
+ {
+ rv = m_db->GetMsgHdrForKey(parentId, getter_AddRefs(parent));
+ if (parent)
+ {
+ nsMsgKey saveParentId = parentId;
+ parent->GetThreadParent(&parentId);
+ // message is it's own parent - bad, let's break out of here.
+ // Or we've got some circular ancestry.
+ if (parentId == saveParentId || level > numChildren)
+ break;
+ level++;
+ }
+ else // if we can't find the parent, don't loop forever.
+ break;
+ }
+ if (level < minLevel)
+ {
+ minLevel = level;
+ retHdr = child;
+ }
+ }
+ }
+ }
+ NS_IF_ADDREF(*result = retHdr);
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
+{
+ // don't need to sort by threads for group view.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return NS_OK;
+ // iterate over the messages in the view, getting the thread id's
+ // sort m_keys so we can quickly find if a key is in the view.
+ m_keys.Sort();
+ // array of the threads' root hdr keys.
+ nsTArray<nsMsgKey> threadRootIds;
+ nsCOMPtr <nsIMsgDBHdr> rootHdr;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsCOMPtr <nsIMsgThread> threadHdr;
+ for (uint32_t i = 0; i < m_keys.Length(); i++)
+ {
+ GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
+ m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
+ if (threadHdr)
+ {
+ nsMsgKey rootKey;
+ threadHdr->GetChildKeyAt(0, &rootKey);
+ nsMsgViewIndex threadRootIndex = threadRootIds.BinaryIndexOf(rootKey);
+ // if we already have that id in top level threads, ignore this msg.
+ if (threadRootIndex != nsMsgViewIndex_None)
+ continue;
+ // it would be nice if GetInsertIndexHelper always found the hdr, but it doesn't.
+ threadHdr->GetChildHdrAt(0, getter_AddRefs(rootHdr));
+ if (!rootHdr)
+ continue;
+ threadRootIndex = GetInsertIndexHelper(rootHdr, threadRootIds, nullptr,
+ nsMsgViewSortOrder::ascending,
+ nsMsgViewSortType::byId);
+ threadRootIds.InsertElementAt(threadRootIndex, rootKey);
+ }
+ }
+
+ m_sortType = nsMsgViewSortType::byNone; // sort from scratch
+ // need to sort the top level threads now by sort order, if it's not by id
+ // and ascending (which is the order per above).
+ if (!(sortType == nsMsgViewSortType::byId &&
+ sortOrder == nsMsgViewSortOrder::ascending))
+ {
+ m_keys.SwapElements(threadRootIds);
+ nsMsgDBView::Sort(sortType, sortOrder);
+ threadRootIds.SwapElements(m_keys);
+ }
+ m_keys.Clear();
+ m_levels.Clear();
+ m_flags.Clear();
+ // now we've build up the list of thread ids - need to build the view
+ // from that. So for each thread id, we need to list the messages in the thread.
+ uint32_t numThreads = threadRootIds.Length();
+ for (uint32_t threadIndex = 0; threadIndex < numThreads; threadIndex++)
+ {
+ m_db->GetMsgHdrForKey(threadRootIds[threadIndex], getter_AddRefs(rootHdr));
+ if (rootHdr)
+ {
+ nsCOMPtr <nsIMsgDBHdr> displayRootHdr;
+ m_db->GetThreadContainingMsgHdr(rootHdr, getter_AddRefs(threadHdr));
+ if (threadHdr)
+ {
+ nsMsgKey rootKey;
+ uint32_t rootFlags;
+ GetFirstMessageHdrToDisplayInThread(threadHdr, getter_AddRefs(displayRootHdr));
+ if (!displayRootHdr)
+ continue;
+ displayRootHdr->GetMessageKey(&rootKey);
+ displayRootHdr->GetFlags(&rootFlags);
+ rootFlags |= MSG_VIEW_FLAG_ISTHREAD;
+ m_keys.AppendElement(rootKey);
+ m_flags.AppendElement(rootFlags);
+ m_levels.AppendElement(0);
+
+ nsMsgViewIndex startOfThreadViewIndex = m_keys.Length();
+ nsMsgViewIndex rootIndex = startOfThreadViewIndex - 1;
+ uint32_t numListed = 0;
+ ListIdsInThreadOrder(threadHdr, rootKey, 1, &startOfThreadViewIndex, &numListed);
+ if (numListed > 0)
+ m_flags[rootIndex] = rootFlags | MSG_VIEW_FLAG_HASCHILDREN;
+ }
+ }
+ }
+
+ // The thread state is left expanded (despite viewFlags) so at least reflect
+ // the correct state.
+ m_viewFlags |= nsMsgViewFlagsType::kExpandAll;
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgQuickSearchDBView::ListCollapsedChildren(nsMsgViewIndex viewIndex,
+ nsIMutableArray *messageArray)
+{
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ nsresult rv = GetThreadContainingIndex(viewIndex, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ nsMsgKey rootKey;
+ GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(rootHdr));
+ rootHdr->GetMessageKey(&rootKey);
+ // group threads can have the root key twice, one for the dummy row.
+ bool rootKeySkipped = false;
+ for (uint32_t i = 0; i < numChildren; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped))
+ {
+ // if this hdr is in the original view, add it to new view.
+ if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex)
+ messageArray->AppendElement(msgHdr, false);
+ }
+ else
+ {
+ rootKeySkipped = true;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed)
+{
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ {
+ nsMsgKey parentKey = m_keys[startOfThreadViewIndex++];
+ return ListIdsInThreadOrder(threadHdr, parentKey, 1, &startOfThreadViewIndex, pNumListed);
+ }
+
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ uint32_t i;
+ uint32_t viewIndex = startOfThreadViewIndex + 1;
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ nsMsgKey rootKey;
+ uint32_t rootFlags = m_flags[startOfThreadViewIndex];
+ *pNumListed = 0;
+ GetMsgHdrForViewIndex(startOfThreadViewIndex, getter_AddRefs(rootHdr));
+ rootHdr->GetMessageKey(&rootKey);
+ // group threads can have the root key twice, one for the dummy row.
+ bool rootKeySkipped = false;
+ for (i = 0; i < numChildren; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+ if (msgHdr != nullptr)
+ {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped))
+ {
+ nsMsgViewIndex threadRootIndex = m_origKeys.BinaryIndexOf(msgKey);
+ // if this hdr is in the original view, add it to new view.
+ if (threadRootIndex != nsMsgViewIndex_None)
+ {
+ uint32_t childFlags;
+ msgHdr->GetFlags(&childFlags);
+ InsertMsgHdrAt(viewIndex, msgHdr, msgKey, childFlags,
+ FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex));
+ if (! (rootFlags & MSG_VIEW_FLAG_HASCHILDREN))
+ m_flags[startOfThreadViewIndex] = rootFlags | MSG_VIEW_FLAG_HASCHILDREN;
+
+ viewIndex++;
+ (*pNumListed)++;
+ }
+ }
+ else
+ {
+ rootKeySkipped = true;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgQuickSearchDBView::ListIdsInThreadOrder(nsIMsgThread *threadHdr,
+ nsMsgKey parentKey, uint32_t level,
+ uint32_t callLevel,
+ nsMsgKey keyToSkip,
+ nsMsgViewIndex *viewIndex,
+ uint32_t *pNumListed)
+{
+ nsCOMPtr <nsISimpleEnumerator> msgEnumerator;
+ nsresult rv = threadHdr->EnumerateMessages(parentKey, getter_AddRefs(msgEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use the numChildren as a sanity check on the thread structure.
+ uint32_t numChildren;
+ (void) threadHdr->GetNumChildren(&numChildren);
+ bool hasMore;
+ nsCOMPtr <nsISupports> supports;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = msgEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ rv = msgEnumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv) && supports)
+ {
+ msgHdr = do_QueryInterface(supports);
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (msgKey == keyToSkip)
+ continue;
+
+ // If we discover depths of more than numChildren, it means we have
+ // some sort of circular thread relationship and we bail out of the
+ // while loop before overflowing the stack with recursive calls.
+ // Technically, this is an error, but forcing a database rebuild
+ // is too destructive so we just return.
+ if (*pNumListed > numChildren || callLevel > numChildren)
+ {
+ NS_ERROR("loop in message threading while listing children");
+ return NS_OK;
+ }
+
+ int32_t childLevel = level;
+ if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex)
+ {
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ InsertMsgHdrAt(*viewIndex, msgHdr, msgKey, msgFlags & ~MSG_VIEW_FLAGS, level);
+ (*pNumListed)++;
+ (*viewIndex)++;
+ childLevel++;
+ }
+ rv = ListIdsInThreadOrder(threadHdr, msgKey, childLevel, callLevel + 1,
+ keyToSkip, viewIndex, pNumListed);
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsMsgQuickSearchDBView::ListIdsInThreadOrder(nsIMsgThread *threadHdr,
+ nsMsgKey parentKey, uint32_t level,
+ nsMsgViewIndex *viewIndex,
+ uint32_t *pNumListed)
+{
+ nsresult rv = ListIdsInThreadOrder(threadHdr, parentKey, level, level,
+ nsMsgKey_None, viewIndex, pNumListed);
+ // Because a quick search view might not have the actual thread root
+ // as its root, and thus might have a message that potentially has siblings
+ // as its root, and the enumerator will miss the siblings, we might need to
+ // make a pass looking for the siblings of the non-root root. We'll put
+ // those after the potential children of the root. So we will list the children
+ // of the faux root's parent, ignoring the faux root.
+ if (level == 1)
+ {
+ nsCOMPtr<nsIMsgDBHdr> root;
+ nsCOMPtr<nsIMsgDBHdr> rootParent;
+ nsMsgKey rootKey;
+ int32_t rootIndex;
+ threadHdr->GetRootHdr(&rootIndex, getter_AddRefs(rootParent));
+ if (rootParent)
+ {
+ rootParent->GetMessageKey(&rootKey);
+ if (rootKey != parentKey)
+ rv = ListIdsInThreadOrder(threadHdr, rootKey, level, level, parentKey,
+ viewIndex, pNumListed);
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgQuickSearchDBView::ExpansionDelta(nsMsgViewIndex index, int32_t *expansionDelta)
+{
+ *expansionDelta = 0;
+ if (index >= ((nsMsgViewIndex) m_keys.Length()))
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ char flags = m_flags[index];
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ nsresult rv = GetThreadContainingIndex(index, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ nsMsgKey rootKey;
+ GetMsgHdrForViewIndex(index, getter_AddRefs(rootHdr));
+ rootHdr->GetMessageKey(&rootKey);
+ // group threads can have the root key twice, one for the dummy row.
+ bool rootKeySkipped = false;
+ for (uint32_t i = 0; i < numChildren; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped))
+ {
+ // if this hdr is in the original view, add it to new view.
+ if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex)
+ (*expansionDelta)++;
+ }
+ else
+ {
+ rootKeySkipped = true;
+ }
+ }
+ }
+ if (! (flags & nsMsgMessageFlags::Elided))
+ *expansionDelta = - (*expansionDelta);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OpenWithHdrs(nsISimpleEnumerator *aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t *aCount)
+{
+ if (aViewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OpenWithHdrs(aHeaders, aSortType, aSortOrder,
+ aViewFlags, aCount);
+
+ m_sortType = aSortType;
+ m_sortOrder = aSortOrder;
+ m_viewFlags = aViewFlags;
+
+ bool hasMore;
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = NS_OK;
+ while (NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = aHeaders->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv) && supports)
+ {
+ msgHdr = do_QueryInterface(supports);
+ AddHdr(msgHdr);
+ }
+ else
+ break;
+ }
+ *aCount = m_keys.Length();
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags)
+{
+ nsresult rv = NS_OK;
+ // if the grouping has changed, rebuild the view
+ if ((m_viewFlags & nsMsgViewFlagsType::kGroupBySort) ^
+ (aViewFlags & nsMsgViewFlagsType::kGroupBySort))
+ rv = RebuildView(aViewFlags);
+ nsMsgDBView::SetViewFlags(aViewFlags);
+
+ return rv;
+}
+
+nsresult
+nsMsgQuickSearchDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator)
+{
+ return GetViewEnumerator(enumerator);
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted,
+ nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener *aInstigator)
+{
+ NS_ENSURE_ARG_POINTER(aHdrDeleted);
+ nsMsgKey msgKey;
+ aHdrDeleted->GetMessageKey(&msgKey);
+ size_t keyIndex = m_origKeys.BinaryIndexOf(msgKey);
+ if (keyIndex != m_origKeys.NoIndex)
+ m_origKeys.RemoveElementAt(keyIndex);
+ return nsMsgThreadedDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags,
+ aInstigator);
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::GetNumMsgsInView(int32_t *aNumMsgs)
+{
+ NS_ENSURE_ARG_POINTER(aNumMsgs);
+ *aNumMsgs = m_origKeys.Length();
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMsgQuickSearchDBView.h b/mailnews/base/src/nsMsgQuickSearchDBView.h
new file mode 100644
index 000000000..1db04a627
--- /dev/null
+++ b/mailnews/base/src/nsMsgQuickSearchDBView.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgQuickSearchDBView_H_
+#define _nsMsgQuickSearchDBView_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgThreadedDBView.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsIMsgSearchSession.h"
+#include "nsCOMArray.h"
+#include "nsIMsgHdr.h"
+
+
+class nsMsgQuickSearchDBView : public nsMsgThreadedDBView, public nsIMsgSearchNotify
+{
+public:
+ nsMsgQuickSearchDBView();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMSGSEARCHNOTIFY
+
+ virtual const char * GetViewName(void) override {return "QuickSearchView"; }
+ NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) override;
+ NS_IMETHOD OpenWithHdrs(nsISimpleEnumerator *aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t *aCount) override;
+ NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgDBViewCommandUpdater *aCommandUpdater,
+ nsIMsgDBView **_retval) override;
+ NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView,
+ nsIMessenger *aMessengerInstance,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgDBViewCommandUpdater *aCmdUpdater) override;
+ NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue aCommand) override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override;
+ NS_IMETHOD SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) override;
+ NS_IMETHOD SetSearchSession(nsIMsgSearchSession *aSearchSession) override;
+ NS_IMETHOD GetSearchSession(nsIMsgSearchSession* *aSearchSession) override;
+ NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags, nsIDBChangeListener *aInstigator) override;
+ NS_IMETHOD OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus,
+ nsIDBChangeListener * aInstigator) override;
+ NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey,
+ int32_t aFlags, nsIDBChangeListener *aInstigator) override;
+ NS_IMETHOD GetNumMsgsInView(int32_t *aNumMsgs) override;
+
+protected:
+ virtual ~nsMsgQuickSearchDBView();
+ nsWeakPtr m_searchSession;
+ nsTArray<nsMsgKey> m_origKeys;
+ bool m_usingCachedHits;
+ bool m_cacheEmpty;
+ nsCOMArray <nsIMsgDBHdr> m_hdrHits;
+ virtual nsresult AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex = nullptr) override;
+ virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed) override;
+ virtual nsresult DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage) override;
+ virtual nsresult SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) override;
+ virtual nsresult GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result) override;
+ virtual nsresult ExpansionDelta(nsMsgViewIndex index, int32_t *expansionDelta) override;
+ virtual nsresult ListCollapsedChildren(nsMsgViewIndex viewIndex,
+ nsIMutableArray *messageArray) override;
+ virtual nsresult ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed) override;
+ virtual nsresult ListIdsInThreadOrder(nsIMsgThread *threadHdr,
+ nsMsgKey parentKey, uint32_t level,
+ nsMsgViewIndex *viewIndex,
+ uint32_t *pNumListed) override;
+ virtual nsresult ListIdsInThreadOrder(nsIMsgThread *threadHdr,
+ nsMsgKey parentKey, uint32_t level,
+ uint32_t callLevel,
+ nsMsgKey keyToSkip,
+ nsMsgViewIndex *viewIndex,
+ uint32_t *pNumListed);
+ virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator) override;
+ void SavePreSearchInfo();
+ void ClearPreSearchInfo();
+
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgRDFDataSource.cpp b/mailnews/base/src/nsMsgRDFDataSource.cpp
new file mode 100644
index 000000000..5cdbf5cc6
--- /dev/null
+++ b/mailnews/base/src/nsMsgRDFDataSource.cpp
@@ -0,0 +1,371 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgRDFDataSource.h"
+#include "nsRDFCID.h"
+#include "rdf.h"
+#include "plstr.h"
+#include "nsMsgRDFUtils.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+nsMsgRDFDataSource::nsMsgRDFDataSource():
+ m_shuttingDown(false),
+ mInitialized(false)
+{
+}
+
+nsMsgRDFDataSource::~nsMsgRDFDataSource()
+{
+ // final shutdown happens here
+ NS_ASSERTION(!mInitialized, "Object going away without cleanup, possibly dangerous!");
+ if (mInitialized) Cleanup();
+}
+
+/* initialization happens here - object is constructed,
+ but possibly partially shut down
+*/
+nsresult
+nsMsgRDFDataSource::Init()
+{
+ NS_ENSURE_TRUE(!mInitialized, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsresult rv;
+ /* Add an observer to XPCOM shutdown */
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_UNEXPECTED);
+ rv = obs->AddObserver(static_cast<nsIObserver*>(this), NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ getRDFService();
+
+ mInitialized=true;
+ return rv;
+}
+
+void nsMsgRDFDataSource::Cleanup()
+{
+ mRDFService = nullptr;
+
+ // release the window
+ mWindow = nullptr;
+
+ mInitialized = false;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsMsgRDFDataSource)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsMsgRDFDataSource)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRDFService)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsMsgRDFDataSource)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRDFService)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMsgRDFDataSource)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMsgRDFDataSource)
+
+NS_INTERFACE_MAP_BEGIN(nsMsgRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRDFDataSource)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsMsgRDFDataSource)
+NS_INTERFACE_MAP_END
+
+/* readonly attribute string URI; */
+NS_IMETHODIMP
+nsMsgRDFDataSource::GetURI(char * *aURI)
+{
+ NS_NOTREACHED("should be implemented by a subclass");
+ return NS_ERROR_UNEXPECTED;
+}
+
+
+/* nsIRDFResource GetSource (in nsIRDFResource aProperty, in nsIRDFNode aTarget, in boolean aTruthValue); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::GetSource(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsIRDFResource **_retval)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+/* nsISimpleEnumerator GetSources (in nsIRDFResource aProperty, in nsIRDFNode aTarget, in boolean aTruthValue); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsISimpleEnumerator **_retval)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+/* nsIRDFNode GetTarget (in nsIRDFResource aSource, in nsIRDFResource aProperty, in boolean aTruthValue); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::GetTarget(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsIRDFNode **_retval)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+/* nsISimpleEnumerator GetTargets (in nsIRDFResource aSource, in nsIRDFResource aProperty, in boolean aTruthValue); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::GetTargets(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsISimpleEnumerator **_retval)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+/* void Assert (in nsIRDFResource aSource, in nsIRDFResource aProperty, in nsIRDFNode aTarget, in boolean aTruthValue); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::Assert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+/* void Unassert (in nsIRDFResource aSource, in nsIRDFResource aProperty, in nsIRDFNode aTarget); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::Unassert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+NS_IMETHODIMP
+nsMsgRDFDataSource::Change(nsIRDFResource *aSource,
+ nsIRDFResource *aProperty,
+ nsIRDFNode *aOldTarget,
+ nsIRDFNode *aNewTarget)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+NS_IMETHODIMP
+nsMsgRDFDataSource::Move(nsIRDFResource *aOldSource,
+ nsIRDFResource *aNewSource,
+ nsIRDFResource *aProperty,
+ nsIRDFNode *aTarget)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+/* boolean HasAssertion (in nsIRDFResource aSource, in nsIRDFResource aProperty, in nsIRDFNode aTarget, in boolean aTruthValue); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+
+/* void AddObserver (in nsIRDFObserver aObserver); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::AddObserver(nsIRDFObserver *aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+ if (!mInitialized)
+ Init();
+ mObservers.AppendObject(aObserver);
+ return NS_OK;
+}
+
+/* void RemoveObserver (in nsIRDFObserver aObserver); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::RemoveObserver(nsIRDFObserver *aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+ mObservers.RemoveObject(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRDFDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result)
+{
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRDFDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result)
+{
+ *result = false;
+ return NS_OK;
+}
+
+/* nsISimpleEnumerator ArcLabelsIn (in nsIRDFNode aNode); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::ArcLabelsIn(nsIRDFNode *aNode, nsISimpleEnumerator **_retval)
+{
+ return NS_NewEmptyEnumerator(_retval);
+}
+
+
+/* nsISimpleEnumerator ArcLabelsOut (in nsIRDFResource aSource); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::ArcLabelsOut(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+/* nsISimpleEnumerator GetAllResources (); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::GetAllResources(nsISimpleEnumerator **_retval)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+/* nsISimpleEnumerator GetAllCmds (in nsIRDFResource aSource); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::GetAllCmds(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+/* boolean IsCommandEnabled (in nsISupports aSources, in nsIRDFResource aCommand, in nsISupports aArguments); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::IsCommandEnabled(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments, bool *_retval)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+
+/* void DoCommand (in nsISupports aSources, in nsIRDFResource aCommand, in nsISupports aArguments); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::DoCommand(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments)
+{
+ return NS_RDF_NO_VALUE;
+}
+
+/* void BeginUpdateBatch (); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::BeginUpdateBatch()
+{
+ return NS_OK;
+}
+
+/* void EndUpdateBatch (); */
+NS_IMETHODIMP
+nsMsgRDFDataSource::EndUpdateBatch()
+{
+ return NS_OK;
+}
+
+
+/* XPCOM Shutdown observer */
+NS_IMETHODIMP
+nsMsgRDFDataSource::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData )
+{
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ m_shuttingDown = true;
+ Cleanup();
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgRDFDataSource::GetWindow(nsIMsgWindow * *aWindow)
+{
+ if(!aWindow)
+ return NS_ERROR_NULL_POINTER;
+
+ *aWindow = mWindow;
+ NS_IF_ADDREF(*aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgRDFDataSource::SetWindow(nsIMsgWindow * aWindow)
+{
+ mWindow = aWindow;
+ return NS_OK;
+}
+
+
+nsIRDFService *
+nsMsgRDFDataSource::getRDFService()
+{
+ if (!mRDFService && !m_shuttingDown) {
+ nsresult rv;
+ mRDFService = do_GetService(kRDFServiceCID, &rv);
+ if (NS_FAILED(rv)) return nullptr;
+ }
+
+ return mRDFService;
+}
+
+nsresult nsMsgRDFDataSource::NotifyPropertyChanged(nsIRDFResource *resource,
+ nsIRDFResource *propertyResource,
+ nsIRDFNode *newNode,
+ nsIRDFNode *oldNode /* = nullptr */)
+{
+
+ NotifyObservers(resource, propertyResource, newNode, oldNode, false, true);
+ return NS_OK;
+
+}
+
+nsresult nsMsgRDFDataSource::NotifyObservers(nsIRDFResource *subject,
+ nsIRDFResource *property,
+ nsIRDFNode *newObject,
+ nsIRDFNode *oldObject,
+ bool assert, bool change)
+{
+ NS_ASSERTION(!(change && assert),
+ "Can't change and assert at the same time!\n");
+ nsMsgRDFNotification note = { this, subject, property, newObject, oldObject };
+ if(change)
+ mObservers.EnumerateForwards(changeEnumFunc, &note);
+ else if (assert)
+ mObservers.EnumerateForwards(assertEnumFunc, &note);
+ else
+ mObservers.EnumerateForwards(unassertEnumFunc, &note);
+ return NS_OK;
+}
+
+bool
+nsMsgRDFDataSource::assertEnumFunc(nsIRDFObserver *aObserver, void *aData)
+{
+ nsMsgRDFNotification *note = (nsMsgRDFNotification *)aData;
+ aObserver->OnAssert(note->datasource,
+ note->subject,
+ note->property,
+ note->newObject);
+ return true;
+}
+
+bool
+nsMsgRDFDataSource::unassertEnumFunc(nsIRDFObserver *aObserver, void *aData)
+{
+ nsMsgRDFNotification* note = (nsMsgRDFNotification *)aData;
+ aObserver->OnUnassert(note->datasource,
+ note->subject,
+ note->property,
+ note->newObject);
+ return true;
+}
+
+bool
+nsMsgRDFDataSource::changeEnumFunc(nsIRDFObserver *aObserver, void *aData)
+{
+ nsMsgRDFNotification* note = (nsMsgRDFNotification *)aData;
+ aObserver->OnChange(note->datasource,
+ note->subject,
+ note->property,
+ note->oldObject, note->newObject);
+ return true;
+}
diff --git a/mailnews/base/src/nsMsgRDFDataSource.h b/mailnews/base/src/nsMsgRDFDataSource.h
new file mode 100644
index 000000000..636ebb60a
--- /dev/null
+++ b/mailnews/base/src/nsMsgRDFDataSource.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef __nsMsgRDFDataSource_h
+#define __nsMsgRDFDataSource_h
+
+#include "nsCOMPtr.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFService.h"
+#include "nsIServiceManager.h"
+#include "nsCOMArray.h"
+#include "nsIObserver.h"
+#include "nsITransactionManager.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgRDFDataSource.h"
+#include "nsWeakReference.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsMsgRDFDataSource : public nsIRDFDataSource,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsIMsgRDFDataSource
+{
+ public:
+ nsMsgRDFDataSource();
+ virtual nsresult Init();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsMsgRDFDataSource,
+ nsIRDFDataSource)
+ NS_DECL_NSIMSGRDFDATASOURCE
+ NS_DECL_NSIRDFDATASOURCE
+ NS_DECL_NSIOBSERVER
+
+ // called to reset the datasource to an empty state
+ // if you need to release yourself as an observer/listener, do it here
+ virtual void Cleanup();
+
+ protected:
+ virtual ~nsMsgRDFDataSource();
+ nsIRDFService *getRDFService();
+ static bool assertEnumFunc(nsIRDFObserver *aObserver, void *aData);
+ static bool unassertEnumFunc(nsIRDFObserver *aObserver, void *aData);
+ static bool changeEnumFunc(nsIRDFObserver *aObserver, void *aData);
+ nsresult NotifyObservers(nsIRDFResource *subject, nsIRDFResource *property,
+ nsIRDFNode *newObject, nsIRDFNode *oldObject,
+ bool assert, bool change);
+
+ virtual nsresult NotifyPropertyChanged(nsIRDFResource *resource,
+ nsIRDFResource *propertyResource, nsIRDFNode *newNode,
+ nsIRDFNode *oldNode = nullptr);
+
+ nsCOMPtr<nsIMsgWindow> mWindow;
+
+ bool m_shuttingDown;
+ bool mInitialized;
+
+ private:
+ nsCOMPtr<nsIRDFService> mRDFService;
+ nsCOMArray<nsIRDFObserver> mObservers;
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgRDFUtils.cpp b/mailnews/base/src/nsMsgRDFUtils.cpp
new file mode 100644
index 000000000..3202c73c9
--- /dev/null
+++ b/mailnews/base/src/nsMsgRDFUtils.cpp
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsMsgRDFUtils.h"
+#include "nsIServiceManager.h"
+#include "prprf.h"
+#include "nsCOMPtr.h"
+#include "nsMemory.h"
+
+nsresult createNode(const char16_t *str, nsIRDFNode **node, nsIRDFService *rdfService)
+{
+ nsresult rv;
+ nsCOMPtr<nsIRDFLiteral> value;
+
+ NS_ASSERTION(rdfService, "rdfService is null");
+ if (!rdfService) return NS_OK;
+
+ if (str) {
+ rv = rdfService->GetLiteral(str, getter_AddRefs(value));
+ }
+ else {
+ rv = rdfService->GetLiteral(EmptyString().get(), getter_AddRefs(value));
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ *node = value;
+ NS_IF_ADDREF(*node);
+ }
+ return rv;
+}
+
+nsresult createIntNode(int32_t value, nsIRDFNode **node, nsIRDFService *rdfService)
+{
+ *node = nullptr;
+ nsresult rv;
+ if (!rdfService) return NS_ERROR_NULL_POINTER;
+ nsCOMPtr<nsIRDFInt> num;
+ rv = rdfService->GetIntLiteral(value, getter_AddRefs(num));
+ if(NS_SUCCEEDED(rv)) {
+ *node = num;
+ NS_IF_ADDREF(*node);
+ }
+ return rv;
+}
+
+nsresult createBlobNode(uint8_t *value, uint32_t &length, nsIRDFNode **node, nsIRDFService *rdfService)
+{
+ NS_ENSURE_ARG_POINTER(node);
+ NS_ENSURE_ARG_POINTER(rdfService);
+
+ *node = nullptr;
+ nsCOMPtr<nsIRDFBlob> blob;
+ nsresult rv = rdfService->GetBlobLiteral(value, length, getter_AddRefs(blob));
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_IF_ADDREF(*node = blob);
+ return rv;
+}
+
+nsresult GetTargetHasAssertion(nsIRDFDataSource *dataSource, nsIRDFResource* folderResource,
+ nsIRDFResource *property,bool tv, nsIRDFNode *target,bool* hasAssertion)
+{
+ NS_ENSURE_ARG_POINTER(hasAssertion);
+
+ nsCOMPtr<nsIRDFNode> currentTarget;
+
+ nsresult rv = dataSource->GetTarget(folderResource, property,tv, getter_AddRefs(currentTarget));
+ if(NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIRDFLiteral> value1(do_QueryInterface(target));
+ nsCOMPtr<nsIRDFLiteral> value2(do_QueryInterface(currentTarget));
+ if(value1 && value2)
+ //If the two values are equal then it has this assertion
+ *hasAssertion = (value1 == value2);
+ }
+ else
+ rv = NS_NOINTERFACE;
+
+ return rv;
+
+}
+
diff --git a/mailnews/base/src/nsMsgRDFUtils.h b/mailnews/base/src/nsMsgRDFUtils.h
new file mode 100644
index 000000000..3694ccc3b
--- /dev/null
+++ b/mailnews/base/src/nsMsgRDFUtils.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//This file holds some useful utility functions and declarations used by our datasources.
+
+#include "rdf.h"
+#include "nsIRDFResource.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFService.h"
+#include "nsStringGlue.h"
+
+// this is used for notification of observers using nsVoidArray
+typedef struct _nsMsgRDFNotification {
+ nsIRDFDataSource *datasource;
+ nsIRDFResource *subject;
+ nsIRDFResource *property;
+ nsIRDFNode *newObject;
+ nsIRDFNode *oldObject;
+} nsMsgRDFNotification;
+
+//Some property declarations
+
+#define NC_RDF_CHILD NC_NAMESPACE_URI "child"
+#define NC_RDF_NAME NC_NAMESPACE_URI "Name"
+#define NC_RDF_OPEN NC_NAMESPACE_URI "open"
+#define NC_RDF_FOLDERTREENAME NC_NAMESPACE_URI "FolderTreeName"
+#define NC_RDF_FOLDERTREESIMPLENAME NC_NAMESPACE_URI "FolderTreeSimpleName"
+#define NC_RDF_FOLDER NC_NAMESPACE_URI "Folder"
+#define NC_RDF_SPECIALFOLDER NC_NAMESPACE_URI "SpecialFolder"
+#define NC_RDF_SERVERTYPE NC_NAMESPACE_URI "ServerType"
+#define NC_RDF_CANCREATEFOLDERSONSERVER NC_NAMESPACE_URI "CanCreateFoldersOnServer"
+#define NC_RDF_CANFILEMESSAGESONSERVER NC_NAMESPACE_URI "CanFileMessagesOnServer"
+#define NC_RDF_ISSERVER NC_NAMESPACE_URI "IsServer"
+#define NC_RDF_ISSECURE NC_NAMESPACE_URI "IsSecure"
+#define NC_RDF_CANSUBSCRIBE NC_NAMESPACE_URI "CanSubscribe"
+#define NC_RDF_SUPPORTSOFFLINE NC_NAMESPACE_URI "SupportsOffline"
+#define NC_RDF_CANFILEMESSAGES NC_NAMESPACE_URI "CanFileMessages"
+#define NC_RDF_CANCREATESUBFOLDERS NC_NAMESPACE_URI "CanCreateSubfolders"
+#define NC_RDF_CANRENAME NC_NAMESPACE_URI "CanRename"
+#define NC_RDF_CANCOMPACT NC_NAMESPACE_URI "CanCompact"
+#define NC_RDF_TOTALMESSAGES NC_NAMESPACE_URI "TotalMessages"
+#define NC_RDF_TOTALUNREADMESSAGES NC_NAMESPACE_URI "TotalUnreadMessages"
+#define NC_RDF_FOLDERSIZE NC_NAMESPACE_URI "FolderSize"
+#define NC_RDF_CHARSET NC_NAMESPACE_URI "Charset"
+#define NC_RDF_BIFFSTATE NC_NAMESPACE_URI "BiffState"
+#define NC_RDF_HASUNREADMESSAGES NC_NAMESPACE_URI "HasUnreadMessages"
+#define NC_RDF_SUBFOLDERSHAVEUNREADMESSAGES NC_NAMESPACE_URI "SubfoldersHaveUnreadMessages"
+#define NC_RDF_NOSELECT NC_NAMESPACE_URI "NoSelect"
+#define NC_RDF_VIRTUALFOLDER NC_NAMESPACE_URI "Virtual"
+#define NC_RDF_INVFEDITSEARCHSCOPE NC_NAMESPACE_URI "InVFEditSearchScope"
+#define NC_RDF_IMAPSHARED NC_NAMESPACE_URI "ImapShared"
+#define NC_RDF_NEWMESSAGES NC_NAMESPACE_URI "NewMessages"
+#define NC_RDF_SYNCHRONIZE NC_NAMESPACE_URI "Synchronize"
+#define NC_RDF_SYNCDISABLED NC_NAMESPACE_URI "SyncDisabled"
+#define NC_RDF_KEY NC_NAMESPACE_URI "Key"
+#define NC_RDF_CANSEARCHMESSAGES NC_NAMESPACE_URI "CanSearchMessages"
+#define NC_RDF_ISDEFERRED NC_NAMESPACE_URI "IsDeferred"
+
+//Sort Properties
+#define NC_RDF_SUBJECT_COLLATION_SORT NC_NAMESPACE_URI "Subject?collation=true"
+#define NC_RDF_SENDER_COLLATION_SORT NC_NAMESPACE_URI "Sender?collation=true"
+#define NC_RDF_RECIPIENT_COLLATION_SORT NC_NAMESPACE_URI "Recipient?collation=true"
+#define NC_RDF_ORDERRECEIVED_SORT NC_NAMESPACE_URI "OrderReceived?sort=true"
+#define NC_RDF_PRIORITY_SORT NC_NAMESPACE_URI "Priority?sort=true"
+#define NC_RDF_DATE_SORT NC_NAMESPACE_URI "Date?sort=true"
+#define NC_RDF_SIZE_SORT NC_NAMESPACE_URI "Size?sort=true"
+#define NC_RDF_ISUNREAD_SORT NC_NAMESPACE_URI "IsUnread?sort=true"
+#define NC_RDF_FLAGGED_SORT NC_NAMESPACE_URI "Flagged?sort=true"
+
+#define NC_RDF_NAME_SORT NC_NAMESPACE_URI "Name?sort=true"
+#define NC_RDF_FOLDERTREENAME_SORT NC_NAMESPACE_URI "FolderTreeName?sort=true"
+
+//Folder Commands
+#define NC_RDF_DELETE NC_NAMESPACE_URI "Delete"
+#define NC_RDF_REALLY_DELETE NC_NAMESPACE_URI "ReallyDelete"
+#define NC_RDF_NEWFOLDER NC_NAMESPACE_URI "NewFolder"
+#define NC_RDF_GETNEWMESSAGES NC_NAMESPACE_URI "GetNewMessages"
+#define NC_RDF_COPY NC_NAMESPACE_URI "Copy"
+#define NC_RDF_MOVE NC_NAMESPACE_URI "Move"
+#define NC_RDF_COPYFOLDER NC_NAMESPACE_URI "CopyFolder"
+#define NC_RDF_MOVEFOLDER NC_NAMESPACE_URI "MoveFolder"
+#define NC_RDF_MARKALLMESSAGESREAD NC_NAMESPACE_URI "MarkAllMessagesRead"
+#define NC_RDF_COMPACT NC_NAMESPACE_URI "Compact"
+#define NC_RDF_COMPACTALL NC_NAMESPACE_URI "CompactAll"
+#define NC_RDF_RENAME NC_NAMESPACE_URI "Rename"
+#define NC_RDF_EMPTYTRASH NC_NAMESPACE_URI "EmptyTrash"
+
+
+nsresult createNode(const char16_t *str, nsIRDFNode **, nsIRDFService *rdfService);
+
+//Given an int32_t creates an nsIRDFNode that is really an int literal.
+nsresult createIntNode(int32_t value, nsIRDFNode **node, nsIRDFService *rdfService);
+
+//Given an nsIRDFBlob creates an nsIRDFNode that is really an blob literal.
+nsresult createBlobNode(uint8_t *value, uint32_t &length, nsIRDFNode **node, nsIRDFService *rdfService);
+
+//s Assertion for a datasource that will just call GetTarget on property. When all of our
+//datasource derive from our datasource baseclass, this should be moved there and the first
+//parameter will no longer be needed.
+nsresult GetTargetHasAssertion(nsIRDFDataSource *dataSource, nsIRDFResource* folderResource,
+ nsIRDFResource *property,bool tv, nsIRDFNode *target,bool* hasAssertion);
diff --git a/mailnews/base/src/nsMsgSearchDBView.cpp b/mailnews/base/src/nsMsgSearchDBView.cpp
new file mode 100644
index 000000000..02cdb6ff6
--- /dev/null
+++ b/mailnews/base/src/nsMsgSearchDBView.cpp
@@ -0,0 +1,1433 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgSearchDBView.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgThread.h"
+#include "nsQuickSort.h"
+#include "nsIDBFolderInfo.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgCopyService.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsMsgUtils.h"
+#include "nsITreeColumns.h"
+#include "nsIMsgMessageService.h"
+#include "nsArrayUtils.h"
+#include "nsIMutableArray.h"
+#include "nsMsgGroupThread.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgSearchSession.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+static bool gReferenceOnlyThreading;
+
+nsMsgSearchDBView::nsMsgSearchDBView()
+{
+ // don't try to display messages for the search pane.
+ mSuppressMsgDisplay = true;
+ m_totalMessagesInView = 0;
+ m_nextThreadId = 1;
+}
+
+nsMsgSearchDBView::~nsMsgSearchDBView()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgSearchDBView, nsMsgDBView, nsIMsgDBView,
+ nsIMsgCopyServiceListener, nsIMsgSearchNotify)
+
+NS_IMETHODIMP nsMsgSearchDBView::Open(nsIMsgFolder *folder,
+ nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags,
+ int32_t *pCount)
+{
+ // dbViewWrapper.js likes to create search views with a sort order
+ // of byNone, in order to have the order be the order the search results
+ // are returned. But this doesn't work with threaded view, so make the
+ // sort order be byDate if we're threaded.
+
+ if (viewFlags & nsMsgViewFlagsType::kThreadedDisplay &&
+ sortType == nsMsgViewSortType::byNone)
+ sortType = nsMsgViewSortType::byDate;
+
+ nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder,
+ viewFlags, pCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetBoolPref("mail.strict_threading", &gReferenceOnlyThreading);
+
+ // our sort is automatically valid because we have no contents at this point!
+ m_sortValid = true;
+
+ if (pCount)
+ *pCount = 0;
+ m_folder = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval)
+{
+ nsMsgSearchDBView* newMsgDBView = new nsMsgSearchDBView();
+
+ if (!newMsgDBView)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance,
+ nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater)
+{
+ nsMsgGroupView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ nsMsgSearchDBView* newMsgDBView = (nsMsgSearchDBView *) aNewMsgDBView;
+
+ // now copy all of our private member data
+ newMsgDBView->mDestFolder = mDestFolder;
+ newMsgDBView->mCommand = mCommand;
+ newMsgDBView->mTotalIndices = mTotalIndices;
+ newMsgDBView->mCurIndex = mCurIndex;
+ newMsgDBView->m_folders.InsertObjectsAt(m_folders, 0);
+ newMsgDBView->m_curCustomColumn = m_curCustomColumn;
+ newMsgDBView->m_hdrsForEachFolder.InsertObjectsAt(m_hdrsForEachFolder, 0);
+ newMsgDBView->m_uniqueFoldersSelected.InsertObjectsAt(m_uniqueFoldersSelected, 0);
+
+ int32_t count = m_dbToUseList.Count();
+ for(int32_t i = 0; i < count; i++)
+ {
+ newMsgDBView->m_dbToUseList.AppendObject(m_dbToUseList[i]);
+ // register the new view with the database so it gets notifications
+ m_dbToUseList[i]->AddListener(newMsgDBView);
+ }
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ // We need to clone the thread and msg hdr hash tables.
+ for (auto iter = m_threadsTable.Iter(); !iter.Done(); iter.Next()) {
+ newMsgDBView->m_threadsTable.Put(iter.Key(), iter.UserData());
+ }
+ for (auto iter = m_hdrsTable.Iter(); !iter.Done(); iter.Next()) {
+ newMsgDBView->m_hdrsTable.Put(iter.Key(), iter.UserData());
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::Close()
+{
+ int32_t count = m_dbToUseList.Count();
+
+ for(int32_t i = 0; i < count; i++)
+ m_dbToUseList[i]->RemoveListener(this);
+
+ m_dbToUseList.Clear();
+
+ return nsMsgGroupView::Close();
+}
+
+void nsMsgSearchDBView::InternalClose()
+{
+ m_threadsTable.Clear();
+ m_hdrsTable.Clear();
+ nsMsgGroupView::InternalClose();
+ m_folders.Clear();
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& aValue)
+{
+ NS_ENSURE_TRUE(IsValidIndex(aRow), NS_MSG_INVALID_DBVIEW_INDEX);
+ NS_ENSURE_ARG_POINTER(aCol);
+
+ const char16_t* colID;
+ aCol->GetIdConst(&colID);
+ // the only thing we contribute is location; dummy rows have no location, so
+ // bail in that case. otherwise, check if we are dealing with 'location'.
+ // location, need to check for "lo" not just "l" to avoid "label" column
+ if (!(m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) &&
+ colID[0] == 'l' && colID[1] == 'o')
+ return FetchLocation(aRow, aValue);
+ else
+ return nsMsgGroupView::GetCellText(aRow, aCol, aValue);
+}
+
+nsresult nsMsgSearchDBView::HashHdr(nsIMsgDBHdr *msgHdr, nsString& aHashKey)
+{
+ if (m_sortType == nsMsgViewSortType::byLocation)
+ {
+ aHashKey.Truncate();
+ nsCOMPtr<nsIMsgFolder> folder;
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ return folder->GetPrettiestName(aHashKey);
+ }
+ return nsMsgGroupView::HashHdr(msgHdr, aHashKey);
+}
+
+nsresult nsMsgSearchDBView::FetchLocation(int32_t aRow, nsAString& aLocationString)
+{
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsresult rv = GetFolderForViewIndex(aRow, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return folder->GetPrettiestName(aLocationString);
+}
+
+nsresult nsMsgSearchDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey,
+ bool /*ensureListed*/)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey,
+ int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OnHdrDeleted(aHdrDeleted, aParentKey,
+ aFlags, aInstigator);
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ nsMsgViewIndex deletedIndex = FindHdr(aHdrDeleted);
+ uint32_t savedFlags = 0;
+ if (deletedIndex != nsMsgViewIndex_None)
+ {
+ savedFlags = m_flags[deletedIndex];
+ RemoveByIndex(deletedIndex);
+ }
+
+ nsCOMPtr<nsIMsgThread> thread;
+ GetXFThreadFromMsgHdr(aHdrDeleted, getter_AddRefs(thread));
+ if (thread)
+ {
+ nsMsgXFViewThread *viewThread = static_cast<nsMsgXFViewThread*>(thread.get());
+ viewThread->RemoveChildHdr(aHdrDeleted, nullptr);
+ if (deletedIndex == nsMsgViewIndex_None && viewThread->MsgCount() == 1)
+ {
+ // remove the last child of a collapsed thread. Need to find the root,
+ // and remove the thread flags on it.
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ thread->GetRootHdr(nullptr, getter_AddRefs(rootHdr));
+ if (rootHdr)
+ {
+ nsMsgViewIndex threadIndex = GetThreadRootIndex(rootHdr);
+ if (threadIndex != nsMsgViewIndex_None)
+ AndExtraFlag(threadIndex, ~(MSG_VIEW_FLAG_ISTHREAD |
+ nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN));
+ }
+ }
+ else if (savedFlags & MSG_VIEW_FLAG_HASCHILDREN)
+{
+ if (savedFlags & nsMsgMessageFlags::Elided)
+ {
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ nsresult rv = thread->GetRootHdr(nullptr, getter_AddRefs(rootHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ rootHdr->GetMessageKey(&msgKey);
+ rootHdr->GetFlags(&msgFlags);
+ // promote the new thread root
+ if (viewThread->MsgCount() > 1)
+ msgFlags |= MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN;
+ InsertMsgHdrAt(deletedIndex, rootHdr, msgKey, msgFlags, 0);
+ if (!m_deletingRows)
+ NoteChange(deletedIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+ }
+ else if (viewThread->MsgCount() > 1)
+ {
+ OrExtraFlag(deletedIndex, MSG_VIEW_FLAG_ISTHREAD |
+ MSG_VIEW_FLAG_HASCHILDREN);
+ }
+ }
+ }
+ }
+ else
+ {
+ return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey,
+ aFlags, aInstigator);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags, nsIDBChangeListener *aInstigator)
+{
+ // defer to base class if we're grouped or not threaded at all
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort ||
+ !(m_viewFlags && nsMsgViewFlagsType::kThreadedDisplay))
+ return nsMsgGroupView::OnHdrFlagsChanged(aHdrChanged, aOldFlags,
+ aNewFlags, aInstigator);
+
+ nsCOMPtr <nsIMsgThread> thread;
+ bool foundMessageId;
+ // check if the hdr that changed is in a xf thread, and if the read flag
+ // changed, update the thread unread count. GetXFThreadFromMsgHdr returns
+ // the thread the header does or would belong to, so we need to also
+ // check that the header is actually in the thread.
+ GetXFThreadFromMsgHdr(aHdrChanged, getter_AddRefs(thread), &foundMessageId);
+ if (foundMessageId)
+ {
+ nsMsgXFViewThread *viewThread = static_cast<nsMsgXFViewThread*>(thread.get());
+ if (viewThread->HdrIndex(aHdrChanged) != -1)
+ {
+ uint32_t deltaFlags = (aOldFlags ^ aNewFlags);
+ if (deltaFlags & nsMsgMessageFlags::Read)
+ thread->MarkChildRead(aNewFlags & nsMsgMessageFlags::Read);
+ }
+ }
+ return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags,
+ aNewFlags, aInstigator);
+}
+
+void nsMsgSearchDBView::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level)
+{
+ if ((int32_t) index < 0)
+ {
+ NS_ERROR("invalid insert index");
+ index = 0;
+ level = 0;
+ }
+ else if (index > m_keys.Length())
+ {
+ NS_ERROR("inserting past end of array");
+ index = m_keys.Length();
+ }
+ m_keys.InsertElementAt(index, msgKey);
+ m_flags.InsertElementAt(index, flags);
+ m_levels.InsertElementAt(index, level);
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ m_folders.InsertObjectAt(folder, index);
+}
+
+void nsMsgSearchDBView::SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level)
+{
+ m_keys[index] = msgKey;
+ m_flags[index] = flags;
+ m_levels[index] = level;
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ m_folders.ReplaceObjectAt(folder, index);
+}
+
+bool nsMsgSearchDBView::InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows)
+{
+ for (int32_t i = 0; i < numRows; i++)
+ if (!m_folders.InsertObjectAt(nullptr, viewIndex + i))
+ return false;
+ return nsMsgDBView::InsertEmptyRows(viewIndex, numRows);
+}
+
+void nsMsgSearchDBView::RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows)
+{
+ nsMsgDBView::RemoveRows(viewIndex, numRows);
+ for (int32_t i = 0; i < numRows; i++)
+ m_folders.RemoveObjectAt(viewIndex);
+}
+
+nsresult nsMsgSearchDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index,
+ nsIMsgDBHdr **msgHdr)
+{
+ nsresult rv = NS_MSG_INVALID_DBVIEW_INDEX;
+ if (index == nsMsgViewIndex_None || index >= (uint32_t) m_folders.Count())
+ return rv;
+ nsIMsgFolder *folder = m_folders[index];
+ if (folder)
+ {
+ nsCOMPtr <nsIMsgDatabase> db;
+ rv = folder->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (db)
+ rv = db->GetMsgHdrForKey(m_keys[index], msgHdr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::GetFolderForViewIndex(nsMsgViewIndex index, nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ if (index == nsMsgViewIndex_None || index >= (uint32_t) m_folders.Count())
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ NS_IF_ADDREF(*aFolder = m_folders[index]);
+ return *aFolder ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+nsresult nsMsgSearchDBView::GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db)
+{
+ nsCOMPtr <nsIMsgFolder> aFolder;
+ nsresult rv = GetFolderForViewIndex(index, getter_AddRefs(aFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return aFolder->GetMsgDatabase(db);
+}
+
+nsresult nsMsgSearchDBView::AddHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder)
+{
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OnNewHeader(msgHdr, nsMsgKey_None, true);
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ nsCOMPtr<nsIMsgThread> thread;
+ nsCOMPtr<nsIMsgDBHdr> threadRoot;
+ // if we find an xf thread in the hash table corresponding to the new msg's
+ // message id, a previous header must be a reference child of the new
+ // message, which means we need to reparent later.
+ bool msgIsReferredTo;
+ GetXFThreadFromMsgHdr(msgHdr, getter_AddRefs(thread), &msgIsReferredTo);
+ bool newThread = !thread;
+ nsMsgXFViewThread *viewThread;
+ if (!thread)
+ {
+ viewThread = new nsMsgXFViewThread(this, m_nextThreadId++);
+ if (!viewThread)
+ return NS_ERROR_OUT_OF_MEMORY;
+ thread = do_QueryInterface(viewThread);
+ }
+ else
+ {
+ viewThread = static_cast<nsMsgXFViewThread*>(thread.get());
+ thread->GetChildHdrAt(0, getter_AddRefs(threadRoot));
+ }
+
+ AddMsgToHashTables(msgHdr, thread);
+ nsCOMPtr<nsIMsgDBHdr> parent;
+ uint32_t posInThread;
+ // We need to move threads in order to keep ourselves sorted
+ // correctly. We want the index of the original thread...we can do this by
+ // getting the root header before we add the new header, and finding that.
+ if (newThread || !viewThread->MsgCount())
+ {
+ viewThread->AddHdr(msgHdr, false, posInThread,
+ getter_AddRefs(parent));
+ nsMsgViewIndex insertIndex = GetIndexForThread(msgHdr);
+ NS_ASSERTION(insertIndex == m_levels.Length() || !m_levels[insertIndex],
+ "inserting into middle of thread");
+ if (insertIndex == nsMsgViewIndex_None)
+ return NS_ERROR_FAILURE;
+ if (!(m_viewFlags & nsMsgViewFlagsType::kExpandAll))
+ msgFlags |= nsMsgMessageFlags::Elided;
+ InsertMsgHdrAt(insertIndex, msgHdr, msgKey, msgFlags, 0);
+ NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+ }
+ else
+ {
+ // get the thread root index before we add the header, because adding
+ // the header can change the sort position.
+ nsMsgViewIndex threadIndex = GetThreadRootIndex(threadRoot);
+ viewThread->AddHdr(msgHdr, msgIsReferredTo, posInThread,
+ getter_AddRefs(parent));
+ if (threadIndex == nsMsgViewIndex_None)
+ {
+ NS_ERROR("couldn't find thread index for newly inserted header");
+ return NS_OK; // not really OK, but not failure exactly.
+ }
+ NS_ASSERTION(!m_levels[threadIndex], "threadRoot incorrect, or level incorrect");
+
+ bool moveThread = false;
+ if (m_sortType == nsMsgViewSortType::byDate)
+ {
+ uint32_t newestMsgInThread = 0, msgDate = 0;
+ viewThread->GetNewestMsgDate(&newestMsgInThread);
+ msgHdr->GetDateInSeconds(&msgDate);
+ moveThread = (msgDate == newestMsgInThread);
+ }
+ OrExtraFlag(threadIndex, MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD);
+ if (!(m_flags[threadIndex] & nsMsgMessageFlags::Elided))
+ {
+ if (parent)
+ {
+ // since we know posInThread, we just want to insert the new hdr
+ // at threadIndex + posInThread, and then rebuild the view until we
+ // get to a sibling of the new hdr.
+ uint8_t newMsgLevel = viewThread->ChildLevelAt(posInThread);
+ InsertMsgHdrAt(threadIndex + posInThread, msgHdr, msgKey, msgFlags,
+ newMsgLevel);
+
+ NoteChange(threadIndex + posInThread, 1, nsMsgViewNotificationCode::insertOrDelete);
+ for (nsMsgViewIndex viewIndex = threadIndex + ++posInThread;
+ posInThread < viewThread->MsgCount() &&
+ viewThread->ChildLevelAt(posInThread) > newMsgLevel; viewIndex++)
+ {
+ m_levels[viewIndex] = viewThread->ChildLevelAt(posInThread++);
+ }
+
+ }
+ else // The new header is the root, so we need to adjust
+ // all the children.
+ {
+ InsertMsgHdrAt(threadIndex, msgHdr, msgKey, msgFlags, 0);
+
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+ nsMsgViewIndex i;
+ for (i = threadIndex + 1;
+ i < m_keys.Length() && (i == threadIndex + 1 || m_levels[i]); i++)
+ m_levels[i] = m_levels[i] + 1;
+ // turn off thread flags on old root.
+ AndExtraFlag(threadIndex + 1, ~(MSG_VIEW_FLAG_ISTHREAD |
+ nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN));
+
+ NoteChange(threadIndex + 1, i - threadIndex + 1,
+ nsMsgViewNotificationCode::changed);
+ }
+ }
+ else if (!parent)
+ {
+ // new parent came into collapsed thread
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ msgHdr->GetFolder(getter_AddRefs(msgFolder));
+ m_keys[threadIndex] = msgKey;
+ m_folders.ReplaceObjectAt(msgFolder, threadIndex);
+ m_flags[threadIndex] = msgFlags | MSG_VIEW_FLAG_ISTHREAD |
+ nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN;
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+
+ }
+ if (moveThread)
+ MoveThreadAt(threadIndex);
+ }
+ }
+ else
+ {
+ m_folders.AppendObject(folder);
+ // nsMsgKey_None means it's not a valid hdr.
+ if (msgKey != nsMsgKey_None)
+ {
+ msgHdr->GetFlags(&msgFlags);
+ m_keys.AppendElement(msgKey);
+ m_levels.AppendElement(0);
+ m_flags.AppendElement(msgFlags);
+ NoteChange(GetSize() - 1, 1, nsMsgViewNotificationCode::insertOrDelete);
+ }
+ }
+ return NS_OK;
+ }
+
+// This method removes the thread at threadIndex from the view
+// and puts it back in its new position, determined by the sort order.
+// And, if the selection is affected, save and restore the selection.
+void nsMsgSearchDBView::MoveThreadAt(nsMsgViewIndex threadIndex)
+{
+ bool updatesSuppressed = mSuppressChangeNotification;
+ // Turn off tree notifications so that we don't reload the current message.
+ if (!updatesSuppressed)
+ SetSuppressChangeNotifications(true);
+
+ nsCOMPtr<nsIMsgDBHdr> threadHdr;
+ GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr));
+
+ uint32_t saveFlags = m_flags[threadIndex];
+ bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided);
+ int32_t childCount = 0;
+ nsMsgKey preservedKey;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ int32_t selectionCount;
+ int32_t currentIndex;
+ bool hasSelection = mTree && mTreeSelection &&
+ ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
+ currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) ||
+ (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) &&
+ selectionCount > 0));
+
+
+ if (hasSelection)
+ SaveAndClearSelection(&preservedKey, preservedSelection);
+
+ if (threadIsExpanded)
+ {
+ ExpansionDelta(threadIndex, &childCount);
+ childCount = -childCount;
+ }
+ nsTArray<nsMsgKey> threadKeys;
+ nsTArray<uint32_t> threadFlags;
+ nsTArray<uint8_t> threadLevels;
+ nsCOMArray<nsIMsgFolder> threadFolders;
+
+ if (threadIsExpanded)
+ {
+ threadKeys.SetCapacity(childCount);
+ threadFlags.SetCapacity(childCount);
+ threadLevels.SetCapacity(childCount);
+ threadFolders.SetCapacity(childCount);
+ for (nsMsgViewIndex index = threadIndex + 1;
+ index < (nsMsgViewIndex) GetSize() && m_levels[index]; index++)
+ {
+ threadKeys.AppendElement(m_keys[index]);
+ threadFlags.AppendElement(m_flags[index]);
+ threadLevels.AppendElement(m_levels[index]);
+ threadFolders.AppendObject(m_folders[index]);
+ }
+ uint32_t collapseCount;
+ CollapseByIndex(threadIndex, &collapseCount);
+ }
+ nsMsgDBView::RemoveByIndex(threadIndex);
+ m_folders.RemoveObjectAt(threadIndex);
+ nsMsgViewIndex newIndex = GetIndexForThread(threadHdr);
+ NS_ASSERTION(newIndex == m_levels.Length() || !m_levels[newIndex],
+ "inserting into middle of thread");
+ if (newIndex == nsMsgViewIndex_None)
+ newIndex = 0;
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ threadHdr->GetMessageKey(&msgKey);
+ threadHdr->GetFlags(&msgFlags);
+ InsertMsgHdrAt(newIndex, threadHdr, msgKey, msgFlags, 0);
+
+ if (threadIsExpanded)
+ {
+ m_keys.InsertElementsAt(newIndex + 1, threadKeys);
+ m_flags.InsertElementsAt(newIndex + 1, threadFlags);
+ m_levels.InsertElementsAt(newIndex + 1, threadLevels);
+ m_folders.InsertObjectsAt(threadFolders, newIndex + 1);
+ }
+ m_flags[newIndex] = saveFlags;
+ // unfreeze selection.
+ if (hasSelection)
+ RestoreSelection(preservedKey, preservedSelection);
+
+ if (!updatesSuppressed)
+ SetSuppressChangeNotifications(false);
+ nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex;
+ nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex;
+ NoteChange(lowIndex, highIndex - lowIndex + childCount + 1,
+ nsMsgViewNotificationCode::changed);
+}
+
+nsresult
+nsMsgSearchDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator)
+{
+ // We do not have an m_db, so the default behavior (in nsMsgDBView) is not
+ // what we want (it will crash). We just want someone to enumerate the
+ // headers that we already have. Conveniently, nsMsgDBView already knows
+ // how to do this with its view enumerator, so we just use that.
+ return nsMsgDBView::GetViewEnumerator(enumerator);
+}
+
+nsresult nsMsgSearchDBView::InsertHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder)
+{
+ nsMsgViewIndex insertIndex = nsMsgViewIndex_None;
+ // Threaded view always needs to go through AddHdrFromFolder since
+ // it handles the xf view thread object creation.
+ if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ insertIndex = GetInsertIndex(msgHdr);
+
+ if (insertIndex == nsMsgViewIndex_None)
+ return AddHdrFromFolder(msgHdr, folder);
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ InsertMsgHdrAt(insertIndex, msgHdr, msgKey, msgFlags, 0);
+
+ // the call to NoteChange() has to happen after we add the key
+ // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
+ NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder)
+{
+ NS_ENSURE_ARG(aMsgHdr);
+ NS_ENSURE_ARG(folder);
+
+ if (m_folders.IndexOf(folder) < 0 ) //do this just for new folder
+ {
+ nsCOMPtr<nsIMsgDatabase> dbToUse;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(dbToUse));
+ if (dbToUse)
+ {
+ dbToUse->AddListener(this);
+ m_dbToUseList.AppendObject(dbToUse);
+ }
+ }
+ m_totalMessagesInView++;
+ if (m_sortValid)
+ return InsertHdrFromFolder(aMsgHdr, folder);
+ else
+ return AddHdrFromFolder(aMsgHdr, folder);
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnSearchDone(nsresult status)
+{
+ //we want to set imap delete model once the search is over because setting next
+ //message after deletion will happen before deleting the message and search scope
+ //can change with every search.
+ mDeleteModel = nsMsgImapDeleteModels::MoveToTrash; //set to default in case it is non-imap folder
+ nsIMsgFolder *curFolder = m_folders.SafeObjectAt(0);
+ if (curFolder)
+ GetImapDeleteModel(curFolder);
+ return NS_OK;
+}
+
+// for now also acts as a way of resetting the search datasource
+NS_IMETHODIMP
+nsMsgSearchDBView::OnNewSearch()
+{
+ int32_t oldSize = GetSize();
+
+ int32_t count = m_dbToUseList.Count();
+ for(int32_t j = 0; j < count; j++)
+ m_dbToUseList[j]->RemoveListener(this);
+
+ m_dbToUseList.Clear();
+ m_folders.Clear();
+ m_keys.Clear();
+ m_levels.Clear();
+ m_flags.Clear();
+ m_totalMessagesInView = 0;
+
+ // needs to happen after we remove the keys, since RowCountChanged() will call our GetRowCount()
+ if (mTree)
+ mTree->RowCountChanged(0, -oldSize);
+
+// mSearchResults->Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowSearch;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::SetSearchSession(nsIMsgSearchSession *aSession)
+{
+ m_searchSession = do_GetWeakReference(aSession);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
+{
+ nsIMsgDatabase *db = static_cast<nsIMsgDatabase *>(instigator);
+ if (db)
+ {
+ db->RemoveListener(this);
+ m_dbToUseList.RemoveObject(db);
+ }
+ return NS_OK;
+}
+
+nsCOMArray<nsIMsgFolder>* nsMsgSearchDBView::GetFolders()
+{
+ return &m_folders;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::GetCommandStatus(nsMsgViewCommandTypeValue command, bool *selectable_p, nsMsgViewCommandCheckStateValue *selected_p)
+{
+ if (command != nsMsgViewCommandType::runJunkControls)
+ return nsMsgDBView::GetCommandStatus(command, selectable_p, selected_p);
+
+ *selectable_p = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder *destFolder)
+{
+ mCommand = command;
+ mDestFolder = destFolder;
+ return nsMsgDBView::DoCommandWithFolder(command, destFolder);
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::DoCommand(nsMsgViewCommandTypeValue command)
+{
+ mCommand = command;
+ if (command == nsMsgViewCommandType::deleteMsg ||
+ command == nsMsgViewCommandType::deleteNoTrash ||
+ command == nsMsgViewCommandType::selectAll ||
+ command == nsMsgViewCommandType::selectThread ||
+ command == nsMsgViewCommandType::selectFlagged ||
+ command == nsMsgViewCommandType::expandAll ||
+ command == nsMsgViewCommandType::collapseAll)
+ return nsMsgDBView::DoCommand(command);
+ nsresult rv = NS_OK;
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+
+ nsMsgViewIndex *indices = selection.Elements();
+ int32_t numIndices = selection.Length();
+
+ // we need to break apart the selection by folders, and then call
+ // ApplyCommandToIndices with the command and the indices in the
+ // selection that are from that folder.
+
+ mozilla::UniquePtr<nsTArray<uint32_t>[]> indexArrays;
+ int32_t numArrays;
+ rv = PartitionSelectionByFolder(indices, numIndices, indexArrays, &numArrays);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (int32_t folderIndex = 0; folderIndex < numArrays; folderIndex++)
+ {
+ rv = ApplyCommandToIndices(command, (indexArrays.get())[folderIndex].Elements(), indexArrays[folderIndex].Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+// This method removes the specified line from the view, and adjusts the
+// various flags and levels of affected messages.
+nsresult nsMsgSearchDBView::RemoveByIndex(nsMsgViewIndex index)
+{
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgThread> thread;
+ nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ GetXFThreadFromMsgHdr(msgHdr, getter_AddRefs(thread));
+ if (thread)
+ {
+ nsMsgXFViewThread *viewThread = static_cast<nsMsgXFViewThread*>(thread.get());
+ if (viewThread->MsgCount() == 2)
+ {
+ // if we removed the next to last message in the thread,
+ // we need to adjust the flags on the first message in the thread.
+ nsMsgViewIndex threadIndex = m_levels[index] ? index -1 : index;
+ if (threadIndex != nsMsgViewIndex_None)
+ {
+ AndExtraFlag(threadIndex, ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN));
+ m_levels[threadIndex] = 0;
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+ // Bump up the level of all the descendents of the message
+ // that was removed, if the thread was expanded.
+ uint8_t removedLevel = m_levels[index];
+ nsMsgViewIndex i = index + 1;
+ if (i < m_levels.Length() && m_levels[i] > removedLevel)
+ {
+ // promote the child of the removed message.
+ uint8_t promotedLevel = m_levels[i];
+ m_levels[i] = promotedLevel - 1;
+ i++;
+ // now promote all the children of the promoted message.
+ for (; i < m_levels.Length() &&
+ m_levels[i] > promotedLevel; i++)
+ m_levels[i] = m_levels[i] - 1;
+ }
+ }
+ }
+ m_folders.RemoveObjectAt(index);
+ return nsMsgDBView::RemoveByIndex(index);
+}
+
+nsresult nsMsgSearchDBView::DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage)
+{
+ nsresult rv = GetFoldersAndHdrsForSelection(indices, numIndices);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mDeleteModel != nsMsgImapDeleteModels::MoveToTrash)
+ deleteStorage = true;
+ if (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete)
+ m_deletingRows = true;
+
+ // remember the deleted messages in case the user undoes the delete,
+ // and we want to restore the hdr to the view, even if it no
+ // longer matches the search criteria.
+ for (nsMsgViewIndex i = 0; i < (nsMsgViewIndex) numIndices; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ (void) GetMsgHdrForViewIndex(indices[i], getter_AddRefs(msgHdr));
+ if (msgHdr)
+ RememberDeletedMsgHdr(msgHdr);
+ // if we are deleting rows, save off the view indices
+ if (m_deletingRows)
+ mIndicesToNoteChange.AppendElement(indices[i]);
+
+ }
+ rv = deleteStorage ? ProcessRequestsInAllFolders(window)
+ : ProcessRequestsInOneFolder(window);
+ if (NS_FAILED(rv))
+ m_deletingRows = false;
+ return rv;
+}
+
+nsresult
+nsMsgSearchDBView::CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool isMove, nsIMsgFolder *destFolder)
+{
+ GetFoldersAndHdrsForSelection(indices, numIndices);
+ return ProcessRequestsInOneFolder(window);
+}
+
+nsresult
+nsMsgSearchDBView::PartitionSelectionByFolder(nsMsgViewIndex *indices,
+ int32_t numIndices,
+ mozilla::UniquePtr<nsTArray<uint32_t>[]> &indexArrays,
+ int32_t *numArrays)
+{
+ nsMsgViewIndex i;
+ int32_t folderIndex;
+ nsCOMArray<nsIMsgFolder> uniqueFoldersSelected;
+ nsTArray<uint32_t> numIndicesSelected;
+ mCurIndex = 0;
+
+ //Build unique folder list based on headers selected by the user
+ for (i = 0; i < (nsMsgViewIndex) numIndices; i++)
+ {
+ nsIMsgFolder *curFolder = m_folders[indices[i]];
+ folderIndex = uniqueFoldersSelected.IndexOf(curFolder);
+ if (folderIndex < 0)
+ {
+ uniqueFoldersSelected.AppendObject(curFolder);
+ numIndicesSelected.AppendElement(1);
+ }
+ else
+ {
+ numIndicesSelected[folderIndex]++;
+ }
+ }
+
+ int32_t numFolders = uniqueFoldersSelected.Count();
+ indexArrays = mozilla::MakeUnique<nsTArray<uint32_t>[]>(numFolders);
+ *numArrays = numFolders;
+ NS_ENSURE_TRUE(indexArrays, NS_ERROR_OUT_OF_MEMORY);
+ for (folderIndex = 0; folderIndex < numFolders; folderIndex++)
+ {
+ (indexArrays.get())[folderIndex].SetCapacity(numIndicesSelected[folderIndex]);
+ }
+ for (i = 0; i < (nsMsgViewIndex) numIndices; i++)
+ {
+ nsIMsgFolder *curFolder = m_folders[indices[i]];
+ int32_t folderIndex = uniqueFoldersSelected.IndexOf(curFolder);
+ (indexArrays.get())[folderIndex].AppendElement(indices[i]);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgSearchDBView::GetFoldersAndHdrsForSelection(nsMsgViewIndex *indices, int32_t numIndices)
+{
+ nsresult rv = NS_OK;
+ mCurIndex = 0;
+ m_uniqueFoldersSelected.Clear();
+ m_hdrsForEachFolder.Clear();
+
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetHeadersFromSelection(indices, numIndices, messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numMsgs;
+ messages->GetLength(&numMsgs);
+
+ uint32_t i;
+ // Build unique folder list based on headers selected by the user
+ for (i = 0; i < numMsgs; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> hdr = do_QueryElementAt(messages, i, &rv);
+ if (hdr)
+ {
+ nsCOMPtr<nsIMsgFolder> curFolder;
+ hdr->GetFolder(getter_AddRefs(curFolder));
+ if (m_uniqueFoldersSelected.IndexOf(curFolder) < 0)
+ m_uniqueFoldersSelected.AppendObject(curFolder);
+ }
+ }
+
+ // Group the headers selected by each folder
+ uint32_t numFolders = m_uniqueFoldersSelected.Count();
+ for (uint32_t folderIndex = 0; folderIndex < numFolders; folderIndex++)
+ {
+ nsIMsgFolder *curFolder = m_uniqueFoldersSelected[folderIndex];
+ nsCOMPtr<nsIMutableArray> msgHdrsForOneFolder(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (i = 0; i < numMsgs; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> hdr = do_QueryElementAt(messages, i, &rv);
+ if (hdr)
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ hdr->GetFolder(getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder && msgFolder == curFolder)
+ {
+ nsCOMPtr<nsISupports> hdrSupports = do_QueryInterface(hdr);
+ msgHdrsForOneFolder->AppendElement(hdrSupports, false);
+ }
+ }
+ }
+ m_hdrsForEachFolder.AppendElement(msgHdrsForOneFolder);
+ }
+ return rv;
+}
+
+nsresult
+nsMsgSearchDBView::ApplyCommandToIndicesWithFolder(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices,
+ int32_t numIndices, nsIMsgFolder *destFolder)
+{
+ mCommand = command;
+ mDestFolder = destFolder;
+ return nsMsgDBView::ApplyCommandToIndicesWithFolder(command, indices, numIndices, destFolder);
+}
+
+// nsIMsgCopyServiceListener methods
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnStartCopy()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
+{
+ return NS_OK;
+}
+
+// believe it or not, these next two are msgcopyservice listener methods!
+NS_IMETHODIMP
+nsMsgSearchDBView::SetMessageKey(nsMsgKey aMessageKey)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::GetMessageId(nsACString& messageId)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnStopCopy(nsresult aStatus)
+{
+ if (NS_SUCCEEDED(aStatus))
+ {
+ mCurIndex++;
+ if ((int32_t) mCurIndex < m_uniqueFoldersSelected.Count())
+ {
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ ProcessRequestsInOneFolder(msgWindow);
+ }
+ }
+ return NS_OK;
+}
+
+// end nsIMsgCopyServiceListener methods
+
+nsresult nsMsgSearchDBView::ProcessRequestsInOneFolder(nsIMsgWindow *window)
+{
+ nsresult rv = NS_OK;
+
+ // Folder operations like copy/move are not implemented for .eml files.
+ if (m_uniqueFoldersSelected.Count() == 0)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsIMsgFolder *curFolder = m_uniqueFoldersSelected[mCurIndex];
+ NS_ASSERTION(curFolder, "curFolder is null");
+ nsCOMPtr<nsIMutableArray> messageArray = m_hdrsForEachFolder[mCurIndex];
+ NS_ASSERTION(messageArray, "messageArray is null");
+
+ // called for delete with trash, copy and move
+ if (mCommand == nsMsgViewCommandType::deleteMsg)
+ curFolder->DeleteMessages(messageArray, window, false /* delete storage */, false /* is move*/, this, true /*allowUndo*/);
+ else
+ {
+ NS_ASSERTION(!(curFolder == mDestFolder), "The source folder and the destination folder are the same");
+ if (NS_SUCCEEDED(rv) && curFolder != mDestFolder)
+ {
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mCommand == nsMsgViewCommandType::moveMessages)
+ copyService->CopyMessages(curFolder, messageArray, mDestFolder, true /* isMove */, this, window, true /*allowUndo*/);
+ else if (mCommand == nsMsgViewCommandType::copyMessages)
+ copyService->CopyMessages(curFolder, messageArray, mDestFolder, false /* isMove */, this, window, true /*allowUndo*/);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgSearchDBView::ProcessRequestsInAllFolders(nsIMsgWindow *window)
+{
+ uint32_t numFolders = m_uniqueFoldersSelected.Count();
+ for (uint32_t folderIndex = 0; folderIndex < numFolders; folderIndex++)
+ {
+ nsIMsgFolder *curFolder = m_uniqueFoldersSelected[folderIndex];
+ NS_ASSERTION (curFolder, "curFolder is null");
+
+ nsCOMPtr<nsIMutableArray> messageArray = m_hdrsForEachFolder[folderIndex];
+ NS_ASSERTION(messageArray, "messageArray is null");
+
+ curFolder->DeleteMessages(messageArray, window, true /* delete storage */, false /* is move*/, nullptr/*copyServListener*/, false /*allowUndo*/ );
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
+{
+ if (!m_checkedCustomColumns && CustomColumnsInSortAndNotRegistered())
+ return NS_OK;
+
+ int32_t rowCountBeforeSort = GetSize();
+
+ if (!rowCountBeforeSort)
+ return NS_OK;
+
+ if (m_viewFlags & (nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kGroupBySort))
+ {
+ // ### This forgets which threads were expanded, and is sub-optimal
+ // since it rebuilds the thread objects.
+ m_sortType = sortType;
+ m_sortOrder = sortOrder;
+ return RebuildView(m_viewFlags);
+ }
+
+ nsMsgKey preservedKey;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ SaveAndClearSelection(&preservedKey, preservedSelection);
+
+ nsresult rv = nsMsgDBView::Sort(sortType,sortOrder);
+ // the sort may have changed the number of rows
+ // before we restore the selection, tell the tree
+ // do this before we call restore selection
+ // this is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+// if nothing selected, return an NS_ERROR
+NS_IMETHODIMP
+nsMsgSearchDBView::GetHdrForFirstSelectedMessage(nsIMsgDBHdr **hdr)
+{
+ NS_ENSURE_ARG_POINTER(hdr);
+ int32_t index;
+
+ if (!mTreeSelection)
+ {
+ // We're in standalone mode, so use the message view index to get the header
+ // We can't use the key here because we don't have an m_db
+ index = m_currentlyDisplayedViewIndex;
+ }
+ else
+ {
+ nsresult rv = mTreeSelection->GetCurrentIndex(&index);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return GetMsgHdrForViewIndex(index, hdr);
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OpenWithHdrs(nsISimpleEnumerator *aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t *aCount)
+{
+ if (aViewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OpenWithHdrs(aHeaders, aSortType, aSortOrder,
+ aViewFlags, aCount);
+
+ m_sortType = aSortType;
+ m_sortOrder = aSortOrder;
+ m_viewFlags = aViewFlags;
+ SaveSortInfo(m_sortType, m_sortOrder);
+
+ bool hasMore;
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = NS_OK;
+ while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = aHeaders->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv) && supports)
+ {
+ msgHdr = do_QueryInterface(supports);
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ AddHdrFromFolder(msgHdr, folder);
+ }
+ }
+ *aCount = m_keys.Length();
+ return rv;
+}
+
+nsresult
+nsMsgSearchDBView::GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder)
+{
+ nsCOMPtr <nsIMsgMessageService> msgMessageService;
+ nsresult rv = GetMessageServiceFromURI(nsDependentCString(aMsgURI), getter_AddRefs(msgMessageService));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = msgMessageService->MessageURIToMsgHdr(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return msgHdr->GetFolder(aFolder);
+}
+
+nsMsgViewIndex nsMsgSearchDBView::FindHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startIndex,
+ bool allowDummy)
+{
+ nsCOMPtr<nsIMsgDBHdr> curHdr;
+ uint32_t index;
+ // it would be nice to take advantage of sorted views when possible.
+ for (index = startIndex; index < GetSize(); index++)
+ {
+ GetMsgHdrForViewIndex(index, getter_AddRefs(curHdr));
+ if (curHdr == msgHdr &&
+ (allowDummy ||
+ !(m_flags[index] & MSG_VIEW_FLAG_DUMMY) ||
+ (m_flags[index] & nsMsgMessageFlags::Elided)))
+ break;
+ }
+ return index < GetSize() ? index : nsMsgViewIndex_None;
+}
+
+// This method looks for the XF thread that corresponds to this message hdr,
+// first by looking up the message id, then references, and finally, if subject
+// threading is turned on, the subject.
+nsresult nsMsgSearchDBView::GetXFThreadFromMsgHdr(nsIMsgDBHdr *msgHdr,
+ nsIMsgThread **pThread,
+ bool *foundByMessageId)
+{
+ NS_ENSURE_ARG_POINTER(pThread);
+
+ nsAutoCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ *pThread = nullptr;
+ m_threadsTable.Get(messageId, pThread);
+ // The caller may want to know if we found the thread by the msgHdr's
+ // messageId
+ if (foundByMessageId)
+ *foundByMessageId = *pThread != nullptr;
+ if (!*pThread)
+ {
+ uint16_t numReferences = 0;
+ msgHdr->GetNumReferences(&numReferences);
+ for (int32_t i = numReferences - 1; i >= 0 && !*pThread; i--)
+ {
+ nsAutoCString reference;
+
+ msgHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty())
+ break;
+
+ m_threadsTable.Get(reference, pThread);
+ }
+ }
+ // if we're threading by subject, and we couldn't find the thread by ref,
+ // just treat subject as an other ref.
+ if (!*pThread && !gReferenceOnlyThreading)
+ {
+ nsCString subject;
+ msgHdr->GetSubject(getter_Copies(subject));
+ // this is the raw rfc822 subject header, so this is OK
+ m_threadsTable.Get(subject, pThread);
+ }
+ return (*pThread) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+bool nsMsgSearchDBView::GetMsgHdrFromHash(nsCString &reference, nsIMsgDBHdr **hdr)
+{
+ return m_hdrsTable.Get(reference, hdr);
+}
+
+bool nsMsgSearchDBView::GetThreadFromHash(nsCString &reference,
+ nsIMsgThread **thread)
+{
+ return m_threadsTable.Get(reference, thread);
+}
+
+nsresult nsMsgSearchDBView::AddRefToHash(nsCString &reference,
+ nsIMsgThread *thread)
+{
+ // Check if this reference is already is associated with a thread;
+ // If so, don't overwrite that association.
+ nsCOMPtr<nsIMsgThread> oldThread;
+ m_threadsTable.Get(reference, getter_AddRefs(oldThread));
+ if (oldThread)
+ return NS_OK;
+
+ m_threadsTable.Put(reference, thread);
+ return NS_OK;
+}
+
+nsresult nsMsgSearchDBView::AddMsgToHashTables(nsIMsgDBHdr *msgHdr,
+ nsIMsgThread *thread)
+{
+ NS_ENSURE_ARG_POINTER(msgHdr);
+
+ uint16_t numReferences = 0;
+ nsresult rv;
+
+ msgHdr->GetNumReferences(&numReferences);
+ for (int32_t i = 0; i < numReferences; i++)
+ {
+ nsAutoCString reference;
+
+ msgHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty())
+ break;
+
+ rv = AddRefToHash(reference, thread);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ m_hdrsTable.Put(messageId, msgHdr);
+ if (!gReferenceOnlyThreading)
+ {
+ nsCString subject;
+ msgHdr->GetSubject(getter_Copies(subject));
+ // if we're threading by subject, just treat subject as an other ref.
+ AddRefToHash(subject, thread);
+ }
+ return AddRefToHash(messageId, thread);
+}
+
+nsresult nsMsgSearchDBView::RemoveRefFromHash(nsCString &reference)
+{
+ m_threadsTable.Remove(reference);
+ return NS_OK;
+}
+
+nsresult nsMsgSearchDBView::RemoveMsgFromHashTables(nsIMsgDBHdr *msgHdr)
+{
+ NS_ENSURE_ARG_POINTER(msgHdr);
+
+ uint16_t numReferences = 0;
+ nsresult rv = NS_OK;
+
+ msgHdr->GetNumReferences(&numReferences);
+
+ for (int32_t i = 0; i < numReferences; i++)
+ {
+ nsAutoCString reference;
+ msgHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty())
+ break;
+
+ rv = RemoveRefFromHash(reference);
+ if (NS_FAILED(rv))
+ break;
+ }
+ nsCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ m_hdrsTable.Remove(messageId);
+ RemoveRefFromHash(messageId);
+ if (!gReferenceOnlyThreading)
+ {
+ nsCString subject;
+ msgHdr->GetSubject(getter_Copies(subject));
+ // if we're threading by subject, just treat subject as an other ref.
+ RemoveRefFromHash(subject);
+ }
+ return rv;
+}
+
+nsMsgGroupThread *nsMsgSearchDBView::CreateGroupThread(nsIMsgDatabase * /* db */)
+{
+ return new nsMsgXFGroupThread();
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr,
+ nsIMsgThread **pThread)
+{
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::GetThreadContainingMsgHdr(msgHdr, pThread);
+ else if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ return GetXFThreadFromMsgHdr(msgHdr, pThread);
+
+ // if not threaded, use the real thread.
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ nsresult rv = GetDBForHeader(msgHdr, getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgDB->GetThreadContainingMsgHdr(msgHdr, pThread);
+}
+
+nsresult
+nsMsgSearchDBView::ListIdsInThread(nsIMsgThread *threadHdr,
+ nsMsgViewIndex startOfThreadViewIndex,
+ uint32_t *pNumListed)
+{
+ NS_ENSURE_ARG_POINTER(threadHdr);
+ NS_ENSURE_ARG_POINTER(pNumListed);
+
+ // these children ids should be in thread order.
+ uint32_t i;
+ nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1;
+ *pNumListed = 0;
+
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ NS_ASSERTION(numChildren, "Empty thread in view/db");
+ if (!numChildren)
+ return NS_OK;
+
+ numChildren--; // account for the existing thread root
+ if (!InsertEmptyRows(viewIndex, numChildren))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ bool threadedView = m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay &&
+ !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort);
+ nsMsgXFViewThread *viewThread;
+ if (threadedView)
+ viewThread = static_cast<nsMsgXFViewThread*>(threadHdr);
+
+ for (i = 1; i <= numChildren; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+
+ if (msgHdr)
+ {
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ uint8_t level = (threadedView) ? viewThread->ChildLevelAt(i) : 1;
+ SetMsgHdrAt(msgHdr, viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS,
+ level);
+ (*pNumListed)++;
+ viewIndex++;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::GetNumMsgsInView(int32_t *aNumMsgs)
+{
+ NS_ENSURE_ARG_POINTER(aNumMsgs);
+ *aNumMsgs = m_totalMessagesInView;
+ return NS_OK;
+}
+
diff --git a/mailnews/base/src/nsMsgSearchDBView.h b/mailnews/base/src/nsMsgSearchDBView.h
new file mode 100644
index 000000000..ff89f1d75
--- /dev/null
+++ b/mailnews/base/src/nsMsgSearchDBView.h
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgSearchDBViews_H_
+#define _nsMsgSearchDBViews_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgGroupView.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsMsgXFViewThread.h"
+#include "nsCOMArray.h"
+#include "mozilla/UniquePtr.h"
+
+class nsMsgSearchDBView : public nsMsgGroupView, public nsIMsgCopyServiceListener, public nsIMsgSearchNotify
+{
+public:
+ nsMsgSearchDBView();
+
+ // these are tied together pretty intimately
+ friend class nsMsgXFViewThread;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMSGSEARCHNOTIFY
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ NS_IMETHOD SetSearchSession(nsIMsgSearchSession *aSearchSession) override;
+
+ virtual const char *GetViewName(void) override { return "SearchView"; }
+ NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) override;
+ NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow,
+ nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) override;
+ NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance,
+ nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) override;
+ NS_IMETHOD Close() override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override;
+ NS_IMETHOD Sort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) override;
+ NS_IMETHOD GetCommandStatus(nsMsgViewCommandTypeValue command,
+ bool *selectable_p,
+ nsMsgViewCommandCheckStateValue *selected_p) override;
+ NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue command) override;
+ NS_IMETHOD DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder *destFolder) override;
+ NS_IMETHOD GetHdrForFirstSelectedMessage(nsIMsgDBHdr **hdr) override;
+ NS_IMETHOD OpenWithHdrs(nsISimpleEnumerator *aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t *aCount) override;
+ NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey,
+ int32_t aFlags, nsIDBChangeListener *aInstigator) override;
+ NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags, nsIDBChangeListener *aInstigator) override;
+ NS_IMETHOD GetNumMsgsInView(int32_t *aNumMsgs) override;
+ // override to get location
+ NS_IMETHOD GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& aValue) override;
+ virtual nsresult GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr) override;
+ virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey parentKey, bool ensureListed) override;
+ NS_IMETHOD GetFolderForViewIndex(nsMsgViewIndex index, nsIMsgFolder **folder) override;
+
+ NS_IMETHOD OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) override;
+
+ virtual nsCOMArray<nsIMsgFolder>* GetFolders() override;
+ virtual nsresult GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder) override;
+
+ NS_IMETHOD GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread) override;
+
+protected:
+ virtual ~nsMsgSearchDBView();
+ virtual void InternalClose() override;
+ virtual nsresult HashHdr(nsIMsgDBHdr *msgHdr, nsString& aHashKey) override;
+ virtual nsresult ListIdsInThread(nsIMsgThread *threadHdr,
+ nsMsgViewIndex startOfThreadViewIndex,
+ uint32_t *pNumListed) override;
+ nsresult FetchLocation(int32_t aRow, nsAString& aLocationString);
+ virtual nsresult AddHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder);
+ virtual nsresult GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db) override;
+ virtual nsresult RemoveByIndex(nsMsgViewIndex index) override;
+ virtual nsresult CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool isMove, nsIMsgFolder *destFolder) override;
+ virtual nsresult DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage) override;
+ virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level) override;
+ virtual void SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level) override;
+ virtual bool InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows) override;
+ virtual void RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows) override;
+ virtual nsMsgViewIndex FindHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startIndex = 0,
+ bool allowDummy=false) override;
+ nsresult GetFoldersAndHdrsForSelection(nsMsgViewIndex *indices, int32_t numIndices);
+ nsresult GroupSearchResultsByFolder();
+ nsresult PartitionSelectionByFolder(nsMsgViewIndex *indices,
+ int32_t numIndices,
+ mozilla::UniquePtr<nsTArray<uint32_t>[]> &indexArrays,
+ int32_t *numArrays);
+
+ virtual nsresult ApplyCommandToIndicesWithFolder(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices,
+ int32_t numIndices, nsIMsgFolder *destFolder) override;
+ void MoveThreadAt(nsMsgViewIndex threadIndex);
+
+ virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator) override;
+ virtual nsresult InsertHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder);
+
+ nsCOMArray<nsIMsgFolder> m_folders;
+ nsCOMArray<nsIMutableArray> m_hdrsForEachFolder;
+ nsCOMArray<nsIMsgFolder> m_uniqueFoldersSelected;
+ uint32_t mCurIndex;
+
+ nsMsgViewIndex* mIndicesForChainedDeleteAndFile;
+ int32_t mTotalIndices;
+ nsCOMArray<nsIMsgDatabase> m_dbToUseList;
+ nsMsgViewCommandTypeValue mCommand;
+ nsCOMPtr <nsIMsgFolder> mDestFolder;
+ nsWeakPtr m_searchSession;
+
+ nsresult ProcessRequestsInOneFolder(nsIMsgWindow *window);
+ nsresult ProcessRequestsInAllFolders(nsIMsgWindow *window);
+ // these are for doing threading of the search hits
+
+ // used for assigning thread id's to xfview threads.
+ nsMsgKey m_nextThreadId;
+ // this maps message-ids and reference message ids to
+ // the corresponding nsMsgXFViewThread object. If we're
+ // doing subject threading, we would throw subjects
+ // into the same table.
+ nsInterfaceHashtable <nsCStringHashKey, nsIMsgThread> m_threadsTable;
+
+ // map message-ids to msg hdrs in the view, used for threading.
+ nsInterfaceHashtable <nsCStringHashKey, nsIMsgDBHdr> m_hdrsTable;
+ uint32_t m_totalMessagesInView;
+
+ virtual nsMsgGroupThread *CreateGroupThread(nsIMsgDatabase *db) override;
+ nsresult GetXFThreadFromMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread,
+ bool *foundByMessageId = nullptr);
+ bool GetThreadFromHash(nsCString &reference, nsIMsgThread **thread);
+ bool GetMsgHdrFromHash(nsCString &reference, nsIMsgDBHdr **hdr);
+ nsresult AddRefToHash(nsCString &reference, nsIMsgThread *thread);
+ nsresult AddMsgToHashTables(nsIMsgDBHdr *msgHdr, nsIMsgThread *thread);
+ nsresult RemoveRefFromHash(nsCString &reference);
+ nsresult RemoveMsgFromHashTables(nsIMsgDBHdr *msgHdr);
+ nsresult InitRefHash();
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgServiceProvider.cpp b/mailnews/base/src/nsMsgServiceProvider.cpp
new file mode 100644
index 000000000..224ae799d
--- /dev/null
+++ b/mailnews/base/src/nsMsgServiceProvider.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgServiceProvider.h"
+#include "nsIServiceManager.h"
+
+#include "nsRDFCID.h"
+#include "nsIRDFService.h"
+#include "nsIRDFRemoteDataSource.h"
+
+#include "nsIFileChannel.h"
+#include "nsNetUtil.h"
+#include "nsMsgBaseCID.h"
+
+#include "nsMailDirServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIDirectoryEnumerator.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+static NS_DEFINE_CID(kRDFCompositeDataSourceCID, NS_RDFCOMPOSITEDATASOURCE_CID);
+static NS_DEFINE_CID(kRDFXMLDataSourceCID, NS_RDFXMLDATASOURCE_CID);
+
+nsMsgServiceProviderService::nsMsgServiceProviderService()
+{}
+
+nsMsgServiceProviderService::~nsMsgServiceProviderService()
+{}
+
+NS_IMPL_ISUPPORTS(nsMsgServiceProviderService, nsIRDFDataSource)
+
+nsresult
+nsMsgServiceProviderService::Init()
+{
+ nsresult rv;
+ nsCOMPtr<nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInnerDataSource = do_CreateInstance(kRDFCompositeDataSourceCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LoadISPFiles();
+ return NS_OK;
+}
+
+/**
+ * Looks for ISP configuration files in <.exe>\isp and any sub directories called isp
+ * located in the user's extensions directory.
+ */
+void nsMsgServiceProviderService::LoadISPFiles()
+{
+ nsresult rv;
+ nsCOMPtr<nsIProperties> dirSvc = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return;
+
+ // Walk through the list of isp directories
+ nsCOMPtr<nsISimpleEnumerator> ispDirectories;
+ rv = dirSvc->Get(ISP_DIRECTORY_LIST,
+ NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(ispDirectories));
+ if (NS_FAILED(rv))
+ return;
+
+ bool hasMore;
+ nsCOMPtr<nsIFile> ispDirectory;
+ while (NS_SUCCEEDED(ispDirectories->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> elem;
+ ispDirectories->GetNext(getter_AddRefs(elem));
+
+ ispDirectory = do_QueryInterface(elem);
+ if (ispDirectory)
+ LoadISPFilesFromDir(ispDirectory);
+ }
+}
+
+void nsMsgServiceProviderService::LoadISPFilesFromDir(nsIFile* aDir)
+{
+ nsresult rv;
+
+ bool check = false;
+ rv = aDir->Exists(&check);
+ if (NS_FAILED(rv) || !check)
+ return;
+
+ rv = aDir->IsDirectory(&check);
+ if (NS_FAILED(rv) || !check)
+ return;
+
+ nsCOMPtr<nsISimpleEnumerator> e;
+ rv = aDir->GetDirectoryEntries(getter_AddRefs(e));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIDirectoryEnumerator> files(do_QueryInterface(e));
+ if (!files)
+ return;
+
+ // we only care about the .rdf files in this directory
+ nsCOMPtr<nsIFile> file;
+ while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) {
+ nsAutoString leafName;
+ file->GetLeafName(leafName);
+ if (!StringEndsWith(leafName, NS_LITERAL_STRING(".rdf")))
+ continue;
+
+ nsAutoCString urlSpec;
+ rv = NS_GetURLSpecFromFile(file, urlSpec);
+ if (NS_SUCCEEDED(rv))
+ LoadDataSource(urlSpec.get());
+ }
+}
+
+nsresult
+nsMsgServiceProviderService::LoadDataSource(const char *aURI)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIRDFDataSource> ds =
+ do_CreateInstance(kRDFXMLDataSourceCID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIRDFRemoteDataSource> remote =
+ do_QueryInterface(ds, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = remote->Init(aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // for now load synchronously (async seems to be busted)
+ rv = remote->Refresh(true);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed refresh?\n");
+
+ rv = mInnerDataSource->AddDataSource(ds);
+
+ return rv;
+}
diff --git a/mailnews/base/src/nsMsgServiceProvider.h b/mailnews/base/src/nsMsgServiceProvider.h
new file mode 100644
index 000000000..6697ebf83
--- /dev/null
+++ b/mailnews/base/src/nsMsgServiceProvider.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMsgServiceProvider_h
+#define __nsMsgServiceProvider_h
+
+#include "nsIRDFDataSource.h"
+#include "nsIRDFRemoteDataSource.h"
+#include "nsIRDFCompositeDataSource.h"
+#include "nsCOMPtr.h"
+
+class nsMsgServiceProviderService : public nsIRDFDataSource
+{
+
+ public:
+ nsMsgServiceProviderService();
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIRDFDATASOURCE(mInnerDataSource->)
+
+ private:
+ virtual ~nsMsgServiceProviderService();
+
+ nsCOMPtr<nsIRDFCompositeDataSource> mInnerDataSource;
+ nsresult LoadDataSource(const char *aURL);
+
+ void LoadISPFilesFromDir(nsIFile* aDir);
+ void LoadISPFiles();
+};
+#endif
diff --git a/mailnews/base/src/nsMsgSpecialViews.cpp b/mailnews/base/src/nsMsgSpecialViews.cpp
new file mode 100644
index 000000000..3943a5ca2
--- /dev/null
+++ b/mailnews/base/src/nsMsgSpecialViews.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgSpecialViews.h"
+#include "nsIMsgThread.h"
+#include "nsMsgMessageFlags.h"
+
+nsMsgThreadsWithUnreadDBView::nsMsgThreadsWithUnreadDBView()
+: m_totalUnwantedMessagesInView(0)
+{
+
+}
+
+nsMsgThreadsWithUnreadDBView::~nsMsgThreadsWithUnreadDBView()
+{
+}
+
+NS_IMETHODIMP nsMsgThreadsWithUnreadDBView::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowThreadsWithUnread;
+ return NS_OK;
+}
+
+bool nsMsgThreadsWithUnreadDBView::WantsThisThread(nsIMsgThread *threadHdr)
+{
+ if (threadHdr)
+ {
+ uint32_t numNewChildren;
+
+ threadHdr->GetNumUnreadChildren(&numNewChildren);
+ if (numNewChildren > 0)
+ return true;
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ m_totalUnwantedMessagesInView += numChildren;
+ }
+ return false;
+}
+
+nsresult nsMsgThreadsWithUnreadDBView::AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsIMsgDBHdr> parentHdr;
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ GetFirstMessageHdrToDisplayInThread(threadHdr, getter_AddRefs(parentHdr));
+ if (parentHdr && (ensureListed || !(msgFlags & nsMsgMessageFlags::Read)))
+ {
+ nsMsgKey key;
+ uint32_t numMsgsInThread;
+ rv = AddHdr(parentHdr);
+ threadHdr->GetNumChildren(&numMsgsInThread);
+ if (numMsgsInThread > 1)
+ {
+ parentHdr->GetMessageKey(&key);
+ nsMsgViewIndex viewIndex = FindViewIndex(key);
+ if (viewIndex != nsMsgViewIndex_None)
+ OrExtraFlag(viewIndex, nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_HASCHILDREN);
+ }
+ m_totalUnwantedMessagesInView -= numMsgsInThread;
+ }
+ else
+ m_totalUnwantedMessagesInView++;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgThreadsWithUnreadDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval)
+{
+ nsMsgThreadsWithUnreadDBView* newMsgDBView = new nsMsgThreadsWithUnreadDBView();
+
+ if (!newMsgDBView)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgThreadsWithUnreadDBView::GetNumMsgsInView(int32_t *aNumMsgs)
+{
+ nsresult rv = nsMsgDBView::GetNumMsgsInView(aNumMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aNumMsgs = *aNumMsgs - m_totalUnwantedMessagesInView;
+ return rv;
+}
+
+nsMsgWatchedThreadsWithUnreadDBView::nsMsgWatchedThreadsWithUnreadDBView()
+: m_totalUnwantedMessagesInView(0)
+{
+}
+
+NS_IMETHODIMP nsMsgWatchedThreadsWithUnreadDBView::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowWatchedThreadsWithUnread;
+ return NS_OK;
+}
+
+bool nsMsgWatchedThreadsWithUnreadDBView::WantsThisThread(nsIMsgThread *threadHdr)
+{
+ if (threadHdr)
+ {
+ uint32_t numNewChildren;
+ uint32_t threadFlags;
+
+ threadHdr->GetNumUnreadChildren(&numNewChildren);
+ threadHdr->GetFlags(&threadFlags);
+ if (numNewChildren > 0 && (threadFlags & nsMsgMessageFlags::Watched) != 0)
+ return true;
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ m_totalUnwantedMessagesInView += numChildren;
+ }
+ return false;
+}
+
+nsresult nsMsgWatchedThreadsWithUnreadDBView::AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed)
+{
+ nsresult rv = NS_OK;
+ uint32_t threadFlags;
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ threadHdr->GetFlags(&threadFlags);
+ if (threadFlags & nsMsgMessageFlags::Watched)
+ {
+ nsCOMPtr <nsIMsgDBHdr> parentHdr;
+ GetFirstMessageHdrToDisplayInThread(threadHdr, getter_AddRefs(parentHdr));
+ if (parentHdr && (ensureListed || !(msgFlags & nsMsgMessageFlags::Read)))
+ {
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ rv = AddHdr(parentHdr);
+ if (numChildren > 1)
+ {
+ nsMsgKey key;
+ parentHdr->GetMessageKey(&key);
+ nsMsgViewIndex viewIndex = FindViewIndex(key);
+ if (viewIndex != nsMsgViewIndex_None)
+ OrExtraFlag(viewIndex, nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Watched);
+ }
+ m_totalUnwantedMessagesInView -= numChildren;
+ return rv;
+ }
+ }
+ m_totalUnwantedMessagesInView++;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgWatchedThreadsWithUnreadDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval)
+{
+ nsMsgWatchedThreadsWithUnreadDBView* newMsgDBView = new nsMsgWatchedThreadsWithUnreadDBView();
+
+ if (!newMsgDBView)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgWatchedThreadsWithUnreadDBView::GetNumMsgsInView(int32_t *aNumMsgs)
+{
+ nsresult rv = nsMsgDBView::GetNumMsgsInView(aNumMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aNumMsgs = *aNumMsgs - m_totalUnwantedMessagesInView;
+ return rv;
+}
diff --git a/mailnews/base/src/nsMsgSpecialViews.h b/mailnews/base/src/nsMsgSpecialViews.h
new file mode 100644
index 000000000..8f3757a35
--- /dev/null
+++ b/mailnews/base/src/nsMsgSpecialViews.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgSpecialViews_H_
+#define _nsMsgSpecialViews_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgThreadedDBView.h"
+
+class nsMsgThreadsWithUnreadDBView : public nsMsgThreadedDBView
+{
+public:
+ nsMsgThreadsWithUnreadDBView();
+ virtual ~nsMsgThreadsWithUnreadDBView();
+ virtual const char * GetViewName(void) override {return "ThreadsWithUnreadView"; }
+ NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCommandUpdater, nsIMsgDBView **_retval) override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override;
+ NS_IMETHOD GetNumMsgsInView(int32_t *aNumMsgs) override;
+
+virtual bool WantsThisThread(nsIMsgThread *threadHdr) override;
+protected:
+ virtual nsresult AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed) override;
+ uint32_t m_totalUnwantedMessagesInView;
+};
+
+class nsMsgWatchedThreadsWithUnreadDBView : public nsMsgThreadedDBView
+{
+public:
+ nsMsgWatchedThreadsWithUnreadDBView ();
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override;
+ NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow,
+ nsIMsgDBViewCommandUpdater *aCommandUpdater,
+ nsIMsgDBView **_retval) override;
+ NS_IMETHOD GetNumMsgsInView(int32_t *aNumMsgs) override;
+ virtual const char *GetViewName(void) override { return "WatchedThreadsWithUnreadView"; }
+ virtual bool WantsThisThread(nsIMsgThread *threadHdr) override;
+protected:
+ virtual nsresult AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed) override;
+ uint32_t m_totalUnwantedMessagesInView;
+
+};
+#ifdef DOING_CACHELESS_VIEW
+// This view will initially be used for cacheless IMAP.
+class nsMsgCachelessView : public nsMsgDBView
+{
+public:
+ nsMsgCachelessView();
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType);
+ virtual ~nsMsgCachelessView();
+ virtual const char * GetViewName(void) {return "nsMsgCachelessView"; }
+ NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue viewType, int32_t *count);
+ nsresult SetViewSize(int32_t setSize); // Override
+ virtual nsresult AddNewMessages() ;
+ virtual nsresult AddHdr(nsIMsgDBHdr *msgHdr);
+ // for news, xover line, potentially, for IMAP, imap line...
+ virtual nsresult AddHdrFromServerLine(char *line, nsMsgKey *msgId) ;
+ virtual void SetInitialSortState(void);
+ virtual nsresult Init(uint32_t *pCount);
+protected:
+ void ClearPendingIds();
+
+ nsIMsgFolder *m_folder;
+ nsMsgViewIndex m_curStartSeq;
+ nsMsgViewIndex m_curEndSeq;
+ bool m_sizeInitialized;
+};
+
+#endif /* DOING_CACHELESS_VIEW */
+#endif
diff --git a/mailnews/base/src/nsMsgStatusFeedback.cpp b/mailnews/base/src/nsMsgStatusFeedback.cpp
new file mode 100644
index 000000000..7843886ed
--- /dev/null
+++ b/mailnews/base/src/nsMsgStatusFeedback.cpp
@@ -0,0 +1,303 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+
+#include "nsIWebProgress.h"
+#include "nsIXULBrowserWindow.h"
+#include "nsMsgStatusFeedback.h"
+#include "nsIDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIChannel.h"
+#include "prinrval.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsIMsgWindow.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFolder.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsMsgUtils.h"
+
+#define MSGFEEDBACK_TIMER_INTERVAL 500
+
+nsMsgStatusFeedback::nsMsgStatusFeedback() :
+ m_lastPercent(0),
+ m_lastProgressTime(0)
+{
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+
+ if (bundleService)
+ bundleService->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(mBundle));
+
+ m_msgLoadedAtom = MsgGetAtom("msgLoaded");
+}
+
+nsMsgStatusFeedback::~nsMsgStatusFeedback()
+{
+ mBundle = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgStatusFeedback, nsIMsgStatusFeedback,
+ nsIProgressEventSink, nsIWebProgressListener, nsISupportsWeakReference)
+
+//////////////////////////////////////////////////////////////////////////////////
+// nsMsgStatusFeedback::nsIWebProgressListener
+//////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ int32_t percentage = 0;
+ if (aMaxTotalProgress > 0)
+ {
+ percentage = (aCurTotalProgress * 100) / aMaxTotalProgress;
+ if (percentage)
+ ShowProgress(percentage);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aProgressStateFlags,
+ nsresult aStatus)
+{
+ nsresult rv;
+
+ NS_ENSURE_TRUE(mBundle, NS_ERROR_NULL_POINTER);
+ if (aProgressStateFlags & STATE_IS_NETWORK)
+ {
+ if (aProgressStateFlags & STATE_START)
+ {
+ m_lastPercent = 0;
+ StartMeteors();
+ nsString loadingDocument;
+ rv = mBundle->GetStringFromName(u"documentLoading",
+ getter_Copies(loadingDocument));
+ if (NS_SUCCEEDED(rv))
+ ShowStatusString(loadingDocument);
+ }
+ else if (aProgressStateFlags & STATE_STOP)
+ {
+ // if we are loading message for display purposes, this STATE_STOP notification is
+ // the only notification we get when layout is actually done rendering the message. We need
+ // to fire the appropriate msgHdrSink notification in this particular case.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel)
+ {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl (do_QueryInterface(uri));
+ if (mailnewsUrl)
+ {
+ // get the url type
+ bool messageDisplayUrl;
+ mailnewsUrl->IsUrlType(nsIMsgMailNewsUrl::eDisplay, &messageDisplayUrl);
+
+ if (messageDisplayUrl)
+ {
+ // get the header sink
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIMsgHeaderSink> hdrSink;
+ msgWindow->GetMsgHeaderSink(getter_AddRefs(hdrSink));
+ if (hdrSink)
+ hdrSink->OnEndMsgDownload(mailnewsUrl);
+ }
+ // get the folder and notify that the msg has been loaded. We're
+ // using NotifyPropertyFlagChanged. To be completely consistent,
+ // we'd send a similar notification that the old message was
+ // unloaded.
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+ mailnewsUrl->GetFolder(getter_AddRefs(msgFolder));
+ nsCOMPtr <nsIMsgMessageUrl> msgUrl = do_QueryInterface(mailnewsUrl);
+ if (msgUrl)
+ {
+ // not sending this notification is not a fatal error...
+ (void) msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgFolder && msgHdr)
+ msgFolder->NotifyPropertyFlagChanged(msgHdr, m_msgLoadedAtom, 0, 1);
+ }
+ }
+ }
+ }
+ StopMeteors();
+ nsString documentDone;
+ rv = mBundle->GetStringFromName(u"documentDone",
+ getter_Copies(documentDone));
+ if (NS_SUCCEEDED(rv))
+ ShowStatusString(documentDone);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgStatusFeedback::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI* aLocation,
+ uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::ShowStatusString(const nsAString& aStatus)
+{
+ nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(do_QueryReferent(mJSStatusFeedbackWeak));
+ if (jsStatusFeedback)
+ jsStatusFeedback->ShowStatusString(aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::SetStatusString(const nsAString& aStatus)
+{
+ nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(do_QueryReferent(mJSStatusFeedbackWeak));
+ if (jsStatusFeedback)
+ jsStatusFeedback->SetStatusString(aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::ShowProgress(int32_t aPercentage)
+{
+ // if the percentage hasn't changed...OR if we are going from 0 to 100% in one step
+ // then don't bother....just fall out....
+ if (aPercentage == m_lastPercent || (m_lastPercent == 0 && aPercentage >= 100))
+ return NS_OK;
+
+ m_lastPercent = aPercentage;
+
+ int64_t nowMS = 0;
+ if (aPercentage < 100) // always need to do 100%
+ {
+ nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
+ if (nowMS < m_lastProgressTime + 250)
+ return NS_OK;
+ }
+
+ m_lastProgressTime = nowMS;
+ nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(do_QueryReferent(mJSStatusFeedbackWeak));
+ if (jsStatusFeedback)
+ jsStatusFeedback->ShowProgress(aPercentage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::StartMeteors()
+{
+ nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(do_QueryReferent(mJSStatusFeedbackWeak));
+ if (jsStatusFeedback)
+ jsStatusFeedback->StartMeteors();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::StopMeteors()
+{
+ nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(do_QueryReferent(mJSStatusFeedbackWeak));
+ if (jsStatusFeedback)
+ jsStatusFeedback->StopMeteors();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgStatusFeedback::SetWrappedStatusFeedback(nsIMsgStatusFeedback * aJSStatusFeedback)
+{
+ NS_ENSURE_ARG_POINTER(aJSStatusFeedback);
+ mJSStatusFeedbackWeak = do_GetWeakReference(aJSStatusFeedback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgStatusFeedback::OnProgress(nsIRequest *request, nsISupports* ctxt,
+ int64_t aProgress, int64_t aProgressMax)
+{
+ // XXX: What should the nsIWebProgress be?
+ // XXX: this truncates 64-bit to 32-bit
+ return OnProgressChange(nullptr, request, int32_t(aProgress), int32_t(aProgressMax),
+ int32_t(aProgress) /* current total progress */, int32_t(aProgressMax) /* max total progress */);
+}
+
+NS_IMETHODIMP nsMsgStatusFeedback::OnStatus(nsIRequest *request, nsISupports* ctxt,
+ nsresult aStatus, const char16_t* aStatusArg)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ nsString accountName;
+ // fetching account name from nsIRequest
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> url(do_QueryInterface(uri));
+ if (url)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ url->GetServer(getter_AddRefs(server));
+ if (server)
+ server->GetPrettyName(accountName);
+ }
+
+ // forming the status message
+ nsCOMPtr<nsIStringBundleService> sbs =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(sbs, NS_ERROR_UNEXPECTED);
+ nsString str;
+ rv = sbs->FormatStatusMessage(aStatus, aStatusArg, getter_Copies(str));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // prefixing the account name to the status message if status message isn't blank
+ // and doesn't already contain the account name.
+ nsString statusMessage;
+ if (!str.IsEmpty() && str.Find(accountName) == kNotFound)
+ {
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = sbs->CreateBundle(MSGS_URL, getter_AddRefs(bundle));
+ const char16_t *params[] = { accountName.get(),
+ str.get() };
+ rv = bundle->FormatStringFromName(
+ u"statusMessage",
+ params, 2, getter_Copies(statusMessage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ statusMessage.Assign(str);
+ }
+ return ShowStatusString(statusMessage);
+}
diff --git a/mailnews/base/src/nsMsgStatusFeedback.h b/mailnews/base/src/nsMsgStatusFeedback.h
new file mode 100644
index 000000000..b7565377a
--- /dev/null
+++ b/mailnews/base/src/nsMsgStatusFeedback.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgStatusFeedback_h
+#define _nsMsgStatusFeedback_h
+
+#include "nsIWebProgressListener.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIProgressEventSink.h"
+#include "nsIStringBundle.h"
+#include "nsWeakReference.h"
+#include "nsIAtom.h"
+
+class nsMsgStatusFeedback : public nsIMsgStatusFeedback,
+ public nsIProgressEventSink,
+ public nsIWebProgressListener,
+ public nsSupportsWeakReference
+{
+public:
+ nsMsgStatusFeedback();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGSTATUSFEEDBACK
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+protected:
+ virtual ~nsMsgStatusFeedback();
+
+ bool m_meteorsSpinning;
+ int32_t m_lastPercent;
+ int64_t m_lastProgressTime;
+
+ void BeginObserving();
+ void EndObserving();
+
+ // the JS status feedback implementation object...eventually this object
+ // will replace this very C++ class you are looking at.
+ nsWeakPtr mJSStatusFeedbackWeak;
+
+ nsCOMPtr<nsIStringBundle> mBundle;
+ nsCOMPtr <nsIAtom> m_msgLoadedAtom;
+};
+
+#endif // _nsMsgStatusFeedback_h
diff --git a/mailnews/base/src/nsMsgTagService.cpp b/mailnews/base/src/nsMsgTagService.cpp
new file mode 100644
index 000000000..75480c21a
--- /dev/null
+++ b/mailnews/base/src/nsMsgTagService.cpp
@@ -0,0 +1,560 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgTagService.h"
+#include "nsMsgBaseCID.h"
+#include "nsIPrefService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsMsgI18N.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsMsgDBView.h" // for labels migration
+#include "nsQuickSort.h"
+#include "nsMsgUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+
+#define STRLEN(s) (sizeof(s) - 1)
+
+#define TAG_PREF_VERSION "version"
+#define TAG_PREF_SUFFIX_TAG ".tag"
+#define TAG_PREF_SUFFIX_COLOR ".color"
+#define TAG_PREF_SUFFIX_ORDINAL ".ordinal"
+#define TAG_CMP_LESSER -1
+#define TAG_CMP_EQUAL 0
+#define TAG_CMP_GREATER 1
+
+static bool gMigratingKeys = false;
+
+// comparison functions for nsQuickSort
+static int
+CompareMsgTagKeys(const void* aTagPref1, const void* aTagPref2, void* aData)
+{
+ return strcmp(*static_cast<const char* const*>(aTagPref1),
+ *static_cast<const char* const*>(aTagPref2));
+}
+
+static int
+CompareMsgTags(const void* aTagPref1, const void* aTagPref2, void* aData)
+{
+ // Sort nsMsgTag objects by ascending order, using their ordinal or key.
+ // The "smallest" value will be first in the sorted array,
+ // thus being the most important element.
+ nsMsgTag *element1 = *(nsMsgTag**) aTagPref1;
+ nsMsgTag *element2 = *(nsMsgTag**) aTagPref2;
+
+ // if we have only one element, it wins
+ if (!element1 && !element2)
+ return TAG_CMP_EQUAL;
+ if (!element2)
+ return TAG_CMP_LESSER;
+ if (!element1)
+ return TAG_CMP_GREATER;
+
+ // only use the key if the ordinal is not defined or empty
+ nsAutoCString value1, value2;
+ element1->GetOrdinal(value1);
+ if (value1.IsEmpty())
+ element1->GetKey(value1);
+ element2->GetOrdinal(value2);
+ if (value2.IsEmpty())
+ element2->GetKey(value2);
+
+ return strcmp(value1.get(), value2.get());
+}
+
+
+//
+// nsMsgTag
+//
+NS_IMPL_ISUPPORTS(nsMsgTag, nsIMsgTag)
+
+nsMsgTag::nsMsgTag(const nsACString &aKey,
+ const nsAString &aTag,
+ const nsACString &aColor,
+ const nsACString &aOrdinal)
+: mTag(aTag),
+ mKey(aKey),
+ mColor(aColor),
+ mOrdinal(aOrdinal)
+{
+}
+
+nsMsgTag::~nsMsgTag()
+{
+}
+
+/* readonly attribute ACString key; */
+NS_IMETHODIMP nsMsgTag::GetKey(nsACString & aKey)
+{
+ aKey = mKey;
+ return NS_OK;
+}
+
+/* readonly attribute AString tag; */
+NS_IMETHODIMP nsMsgTag::GetTag(nsAString & aTag)
+{
+ aTag = mTag;
+ return NS_OK;
+}
+
+/* readonly attribute ACString color; */
+NS_IMETHODIMP nsMsgTag::GetColor(nsACString & aColor)
+{
+ aColor = mColor;
+ return NS_OK;
+}
+
+/* readonly attribute ACString ordinal; */
+NS_IMETHODIMP nsMsgTag::GetOrdinal(nsACString & aOrdinal)
+{
+ aOrdinal = mOrdinal;
+ return NS_OK;
+}
+
+
+//
+// nsMsgTagService
+//
+NS_IMPL_ISUPPORTS(nsMsgTagService, nsIMsgTagService)
+
+nsMsgTagService::nsMsgTagService()
+{
+ m_tagPrefBranch = nullptr;
+ nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefService)
+ prefService->GetBranch("mailnews.tags.", getter_AddRefs(m_tagPrefBranch));
+ // need to figure out how to migrate the tags only once.
+ MigrateLabelsToTags();
+ RefreshKeyCache();
+}
+
+nsMsgTagService::~nsMsgTagService()
+{
+ /* destructor code */
+}
+
+/* wstring getTagForKey (in string key); */
+NS_IMETHODIMP nsMsgTagService::GetTagForKey(const nsACString &key, nsAString &_retval)
+{
+ nsAutoCString prefName(key);
+ if (!gMigratingKeys)
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG);
+ return GetUnicharPref(prefName.get(), _retval);
+}
+
+/* void setTagForKey (in string key); */
+NS_IMETHODIMP nsMsgTagService::SetTagForKey(const nsACString &key, const nsAString &tag )
+{
+ nsAutoCString prefName(key);
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG);
+ return SetUnicharPref(prefName.get(), tag);
+}
+
+/* void getKeyForTag (in wstring tag); */
+NS_IMETHODIMP nsMsgTagService::GetKeyForTag(const nsAString &aTag, nsACString &aKey)
+{
+ uint32_t count;
+ char **prefList;
+ nsresult rv = m_tagPrefBranch->GetChildList("", &count, &prefList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // traverse the list, and look for a pref with the desired tag value.
+ for (uint32_t i = count; i--;)
+ {
+ // We are returned the tag prefs in the form "<key>.<tag_data_type>", but
+ // since we only want the tags, just check that the string ends with "tag".
+ nsDependentCString prefName(prefList[i]);
+ if (StringEndsWith(prefName, NS_LITERAL_CSTRING(TAG_PREF_SUFFIX_TAG)))
+ {
+ nsAutoString curTag;
+ GetUnicharPref(prefList[i], curTag);
+ if (aTag.Equals(curTag))
+ {
+ aKey = Substring(prefName, 0, prefName.Length() - STRLEN(TAG_PREF_SUFFIX_TAG));
+ break;
+ }
+ }
+ }
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, prefList);
+ ToLowerCase(aKey);
+ return NS_OK;
+}
+
+/* ACString getTopKey (in ACString keylist); */
+NS_IMETHODIMP nsMsgTagService::GetTopKey(const nsACString & keyList, nsACString & _retval)
+{
+ _retval.Truncate();
+ // find the most important key
+ nsTArray<nsCString> keyArray;
+ ParseString(keyList, ' ', keyArray);
+ uint32_t keyCount = keyArray.Length();
+ nsCString *topKey = nullptr, *key, topOrdinal, ordinal;
+ for (uint32_t i = 0; i < keyCount; ++i)
+ {
+ key = &keyArray[i];
+ if (key->IsEmpty())
+ continue;
+
+ // ignore unknown keywords
+ nsAutoString tagValue;
+ nsresult rv = GetTagForKey(*key, tagValue);
+ if (NS_FAILED(rv) || tagValue.IsEmpty())
+ continue;
+
+ // new top key, judged by ordinal order?
+ rv = GetOrdinalForKey(*key, ordinal);
+ if (NS_FAILED(rv) || ordinal.IsEmpty())
+ ordinal = *key;
+ if ((ordinal < topOrdinal) || topOrdinal.IsEmpty())
+ {
+ topOrdinal = ordinal;
+ topKey = key; // copy actual result key only once - later
+ }
+ }
+ // return the most important key - if any
+ if (topKey)
+ _retval = *topKey;
+ return NS_OK;
+}
+
+/* void addTagForKey (in string key, in wstring tag, in string color, in string ordinal); */
+NS_IMETHODIMP nsMsgTagService::AddTagForKey(const nsACString &key,
+ const nsAString &tag,
+ const nsACString &color,
+ const nsACString &ordinal)
+{
+ nsAutoCString prefName(key);
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG);
+ nsresult rv = SetUnicharPref(prefName.get(), tag);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetColorForKey(key, color);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = RefreshKeyCache();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetOrdinalForKey(key, ordinal);
+}
+
+/* void addTag (in wstring tag, in long color); */
+NS_IMETHODIMP nsMsgTagService::AddTag(const nsAString &tag,
+ const nsACString &color,
+ const nsACString &ordinal)
+{
+ // figure out key from tag. Apply transformation stripping out
+ // illegal characters like <SP> and then convert to imap mod utf7.
+ // Then, check if we have a tag with that key yet, and if so,
+ // make it unique by appending A, AA, etc.
+ // Should we use an iterator?
+ nsAutoString transformedTag(tag);
+ MsgReplaceChar(transformedTag, " ()/{%*<>\\\"", '_');
+ nsAutoCString key;
+ CopyUTF16toMUTF7(transformedTag, key);
+ // We have an imap server that converts keys to upper case so we're going
+ // to normalize all keys to lower case (upper case looks ugly in prefs.js)
+ ToLowerCase(key);
+ nsAutoCString prefName(key);
+ while (true)
+ {
+ nsAutoString tagValue;
+ nsresult rv = GetTagForKey(prefName, tagValue);
+ if (NS_FAILED(rv) || tagValue.IsEmpty() || tagValue.Equals(tag))
+ return AddTagForKey(prefName, tag, color, ordinal);
+ prefName.Append('A');
+ }
+ NS_ASSERTION(false, "can't get here");
+ return NS_ERROR_FAILURE;
+}
+
+/* long getColorForKey (in string key); */
+NS_IMETHODIMP nsMsgTagService::GetColorForKey(const nsACString &key, nsACString &_retval)
+{
+ nsAutoCString prefName(key);
+ if (!gMigratingKeys)
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_COLOR);
+ nsCString color;
+ nsresult rv = m_tagPrefBranch->GetCharPref(prefName.get(), getter_Copies(color));
+ if (NS_SUCCEEDED(rv))
+ _retval = color;
+ return NS_OK;
+}
+
+/* void setColorForKey (in ACString key, in ACString color); */
+NS_IMETHODIMP nsMsgTagService::SetColorForKey(const nsACString & key, const nsACString & color)
+{
+ nsAutoCString prefName(key);
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_COLOR);
+ if (color.IsEmpty())
+ {
+ m_tagPrefBranch->ClearUserPref(prefName.get());
+ return NS_OK;
+ }
+ return m_tagPrefBranch->SetCharPref(prefName.get(), nsCString(color).get());
+}
+
+/* ACString getOrdinalForKey (in ACString key); */
+NS_IMETHODIMP nsMsgTagService::GetOrdinalForKey(const nsACString & key, nsACString & _retval)
+{
+ nsAutoCString prefName(key);
+ if (!gMigratingKeys)
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_ORDINAL);
+ nsCString ordinal;
+ nsresult rv = m_tagPrefBranch->GetCharPref(prefName.get(), getter_Copies(ordinal));
+ _retval = ordinal;
+ return rv;
+}
+
+/* void setOrdinalForKey (in ACString key, in ACString ordinal); */
+NS_IMETHODIMP nsMsgTagService::SetOrdinalForKey(const nsACString & key, const nsACString & ordinal)
+{
+ nsAutoCString prefName(key);
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_ORDINAL);
+ if (ordinal.IsEmpty())
+ {
+ m_tagPrefBranch->ClearUserPref(prefName.get());
+ return NS_OK;
+ }
+ return m_tagPrefBranch->SetCharPref(prefName.get(), nsCString(ordinal).get());
+}
+
+/* void deleteTag (in wstring tag); */
+NS_IMETHODIMP nsMsgTagService::DeleteKey(const nsACString &key)
+{
+ // clear the associated prefs
+ nsAutoCString prefName(key);
+ if (!gMigratingKeys)
+ ToLowerCase(prefName);
+ nsresult rv = m_tagPrefBranch->DeleteBranch(prefName.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ return RefreshKeyCache();
+}
+
+/* void getAllTags (out unsigned long count, [array, size_is (count), retval] out nsIMsgTag tagArray); */
+NS_IMETHODIMP nsMsgTagService::GetAllTags(uint32_t *aCount, nsIMsgTag ***aTagArray)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+ NS_ENSURE_ARG_POINTER(aTagArray);
+
+ // preset harmless default values
+ *aCount = 0;
+ *aTagArray = nullptr;
+
+ // get the actual tag definitions
+ nsresult rv;
+ uint32_t prefCount;
+ char **prefList;
+ rv = m_tagPrefBranch->GetChildList("", &prefCount, &prefList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // sort them by key for ease of processing
+ NS_QuickSort(prefList, prefCount, sizeof(char*), CompareMsgTagKeys, nullptr);
+
+ // build an array of nsIMsgTag elements from the orderered list
+ // it's at max the same size as the preflist, but usually only about half
+ nsIMsgTag** tagArray = (nsIMsgTag**) NS_Alloc(sizeof(nsIMsgTag*) * prefCount);
+
+ if (!tagArray)
+ {
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ uint32_t currentTagIndex = 0;
+ nsMsgTag *newMsgTag;
+ nsString tag;
+ nsCString lastKey, color, ordinal;
+ for (uint32_t i = prefCount; i--;)
+ {
+ // extract just the key from <key>.<info=tag|color|ordinal>
+ char *info = strrchr(prefList[i], '.');
+ if (info)
+ {
+ nsAutoCString key(Substring(prefList[i], info));
+ if (key != lastKey)
+ {
+ if (!key.IsEmpty())
+ {
+ // .tag MUST exist (but may be empty)
+ rv = GetTagForKey(key, tag);
+ if (NS_SUCCEEDED(rv))
+ {
+ // .color MAY exist
+ color.Truncate();
+ GetColorForKey(key, color);
+ // .ordinal MAY exist
+ rv = GetOrdinalForKey(key, ordinal);
+ if (NS_FAILED(rv))
+ ordinal.Truncate();
+ // store the tag info in our array
+ newMsgTag = new nsMsgTag(key, tag, color, ordinal);
+ if (!newMsgTag)
+ {
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(currentTagIndex, tagArray);
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_ADDREF(tagArray[currentTagIndex++] = newMsgTag);
+ }
+ }
+ lastKey = key;
+ }
+ }
+ }
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList);
+
+ // sort the non-null entries by ordinal
+ NS_QuickSort(tagArray, currentTagIndex, sizeof(nsMsgTag*), CompareMsgTags,
+ nullptr);
+
+ // All done, now return the values (the idl's size_is(count) parameter
+ // ensures that the array is cut accordingly).
+ *aCount = currentTagIndex;
+ *aTagArray = tagArray;
+
+ return NS_OK;
+}
+
+nsresult nsMsgTagService::SetUnicharPref(const char *prefName,
+ const nsAString &val)
+{
+ nsresult rv = NS_OK;
+ if (!val.IsEmpty())
+ {
+ nsCOMPtr<nsISupportsString> supportsString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ if (supportsString)
+ {
+ supportsString->SetData(val);
+ rv = m_tagPrefBranch->SetComplexValue(prefName,
+ NS_GET_IID(nsISupportsString),
+ supportsString);
+ }
+ }
+ else
+ {
+ m_tagPrefBranch->ClearUserPref(prefName);
+ }
+ return rv;
+}
+
+nsresult nsMsgTagService::GetUnicharPref(const char *prefName,
+ nsAString &prefValue)
+{
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> supportsString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ if (supportsString)
+ {
+ rv = m_tagPrefBranch->GetComplexValue(prefName,
+ NS_GET_IID(nsISupportsString),
+ getter_AddRefs(supportsString));
+ if (supportsString)
+ rv = supportsString->GetData(prefValue);
+ else
+ prefValue.Truncate();
+ }
+ return rv;
+}
+
+
+nsresult nsMsgTagService::MigrateLabelsToTags()
+{
+ nsCString prefString;
+
+ int32_t prefVersion = 0;
+ nsresult rv = m_tagPrefBranch->GetIntPref(TAG_PREF_VERSION, &prefVersion);
+ if (NS_SUCCEEDED(rv) && prefVersion > 1)
+ return rv;
+ else if (prefVersion == 1)
+ {
+ gMigratingKeys = true;
+ // need to convert the keys to lower case
+ nsIMsgTag **tagArray;
+ uint32_t numTags;
+ GetAllTags(&numTags, &tagArray);
+ for (uint32_t tagIndex = 0; tagIndex < numTags; tagIndex++)
+ {
+ nsAutoCString key, color, ordinal;
+ nsAutoString tagStr;
+ nsIMsgTag *tag = tagArray[tagIndex];
+ tag->GetKey(key);
+ tag->GetTag(tagStr);
+ tag->GetOrdinal(ordinal);
+ tag->GetColor(color);
+ DeleteKey(key);
+ ToLowerCase(key);
+ AddTagForKey(key, tagStr, color, ordinal);
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numTags, tagArray);
+ gMigratingKeys = false;
+ }
+ else
+ {
+ nsCOMPtr<nsIPrefBranch> prefRoot(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ nsString ucsval;
+ nsAutoCString labelKey("$label1");
+ for(int32_t i = 0; i < PREF_LABELS_MAX; )
+ {
+ prefString.Assign(PREF_LABELS_DESCRIPTION);
+ prefString.AppendInt(i + 1);
+ rv = prefRoot->GetComplexValue(prefString.get(),
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(pls));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pls->ToString(getter_Copies(ucsval));
+
+ prefString.Assign(PREF_LABELS_COLOR);
+ prefString.AppendInt(i + 1);
+ nsCString csval;
+ rv = prefRoot->GetCharPref(prefString.get(), getter_Copies(csval));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddTagForKey(labelKey, ucsval, csval, EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+ labelKey.SetCharAt(++i + '1', 6);
+ }
+ }
+ m_tagPrefBranch->SetIntPref(TAG_PREF_VERSION, 2);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgTagService::IsValidKey(const nsACString &aKey, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_keys.Contains(aKey);
+ return NS_OK;
+}
+
+// refresh the local tag key array m_keys from preferences
+nsresult nsMsgTagService::RefreshKeyCache()
+{
+ nsIMsgTag **tagArray;
+ uint32_t numTags;
+ nsresult rv = GetAllTags(&numTags, &tagArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_keys.Clear();
+
+ for (uint32_t tagIndex = 0; tagIndex < numTags; tagIndex++)
+ {
+ nsIMsgTag *tag = tagArray[tagIndex];
+ if (!tag) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ nsAutoCString key;
+ tag->GetKey(key);
+ if (!m_keys.InsertElementAt(tagIndex, key)) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numTags, tagArray);
+ return rv;
+}
diff --git a/mailnews/base/src/nsMsgTagService.h b/mailnews/base/src/nsMsgTagService.h
new file mode 100644
index 000000000..d31a81478
--- /dev/null
+++ b/mailnews/base/src/nsMsgTagService.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgTagService_h__
+#define nsMsgTagService_h__
+
+#include "nsIMsgTagService.h"
+#include "nsIPrefBranch.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsTArray.h"
+
+class nsMsgTag final : public nsIMsgTag
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGTAG
+
+ nsMsgTag(const nsACString &aKey,
+ const nsAString &aTag,
+ const nsACString &aColor,
+ const nsACString &aOrdinal);
+
+protected:
+ ~nsMsgTag();
+
+ nsString mTag;
+ nsCString mKey, mColor, mOrdinal;
+};
+
+
+class nsMsgTagService final : public nsIMsgTagService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGTAGSERVICE
+
+ nsMsgTagService();
+
+private:
+ ~nsMsgTagService();
+
+protected:
+ nsresult SetUnicharPref(const char *prefName,
+ const nsAString &prefValue);
+ nsresult GetUnicharPref(const char *prefName,
+ nsAString &prefValue);
+ nsresult MigrateLabelsToTags();
+ nsresult RefreshKeyCache();
+
+ nsCOMPtr<nsIPrefBranch> m_tagPrefBranch;
+ nsTArray<nsCString> m_keys;
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgThreadedDBView.cpp b/mailnews/base/src/nsMsgThreadedDBView.cpp
new file mode 100644
index 000000000..0a2a18fbc
--- /dev/null
+++ b/mailnews/base/src/nsMsgThreadedDBView.cpp
@@ -0,0 +1,983 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgThreadedDBView.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgThread.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgSearchSession.h"
+#include "nsMsgMessageFlags.h"
+
+#define MSGHDR_CACHE_LOOK_AHEAD_SIZE 25 // Allocate this more to avoid reallocation on new mail.
+#define MSGHDR_CACHE_MAX_SIZE 8192 // Max msghdr cache entries.
+#define MSGHDR_CACHE_DEFAULT_SIZE 100
+
+nsMsgThreadedDBView::nsMsgThreadedDBView()
+{
+ /* member initializers and constructor code */
+ m_havePrevView = false;
+}
+
+nsMsgThreadedDBView::~nsMsgThreadedDBView()
+{
+ /* destructor code */
+}
+
+NS_IMETHODIMP nsMsgThreadedDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount)
+{
+ nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_db)
+ return NS_ERROR_NULL_POINTER;
+ // Preset msg hdr cache size for performance reason.
+ int32_t totalMessages, unreadMessages;
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ PersistFolderInfo(getter_AddRefs(dbFolderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // save off sort type and order, view type and flags
+ dbFolderInfo->GetNumUnreadMessages(&unreadMessages);
+ dbFolderInfo->GetNumMessages(&totalMessages);
+ if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
+ {
+ // Set unread msg size + extra entries to avoid reallocation on new mail.
+ totalMessages = (uint32_t)unreadMessages+MSGHDR_CACHE_LOOK_AHEAD_SIZE;
+ }
+ else
+ {
+ if (totalMessages > MSGHDR_CACHE_MAX_SIZE)
+ totalMessages = MSGHDR_CACHE_MAX_SIZE; // use max default
+ else if (totalMessages > 0)
+ totalMessages += MSGHDR_CACHE_LOOK_AHEAD_SIZE;// allocate extra entries to avoid reallocation on new mail.
+ }
+ // if total messages is 0, then we probably don't have any idea how many headers are in the db
+ // so we have no business setting the cache size.
+ if (totalMessages > 0)
+ m_db->SetMsgHdrCacheSize((uint32_t)totalMessages);
+
+ if (pCount)
+ *pCount = 0;
+ rv = InitThreadedView(pCount);
+
+ // this is a hack, but we're trying to find a way to correct
+ // incorrect total and unread msg counts w/o paying a big
+ // performance penalty. So, if we're not threaded, just add
+ // up the total and unread messages in the view and see if that
+ // matches what the db totals say. Except ignored threads are
+ // going to throw us off...hmm. Unless we just look at the
+ // unread counts which is what mostly tweaks people anyway...
+ int32_t unreadMsgsInView = 0;
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ {
+ for (uint32_t i = m_flags.Length(); i > 0; )
+ {
+ if (!(m_flags[--i] & nsMsgMessageFlags::Read))
+ ++unreadMsgsInView;
+ }
+
+ if (unreadMessages != unreadMsgsInView)
+ m_db->SyncCounts();
+ }
+ m_db->SetMsgHdrCacheSize(MSGHDR_CACHE_DEFAULT_SIZE);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThreadedDBView::Close()
+{
+ return nsMsgDBView::Close();
+}
+
+nsresult nsMsgThreadedDBView::InitThreadedView(int32_t *pCount)
+{
+ nsresult rv;
+
+ m_keys.Clear();
+ m_flags.Clear();
+ m_levels.Clear();
+ m_prevKeys.Clear();
+ m_prevFlags.Clear();
+ m_prevLevels.Clear();
+ m_havePrevView = false;
+ nsresult getSortrv = NS_OK; // ### TODO m_db->GetSortInfo(&sortType, &sortOrder);
+
+ // list all the ids into m_keys.
+ nsMsgKey startMsg = 0;
+ do
+ {
+ const int32_t kIdChunkSize = 400;
+ int32_t numListed = 0;
+ nsMsgKey idArray[kIdChunkSize];
+ int32_t flagArray[kIdChunkSize];
+ char levelArray[kIdChunkSize];
+
+ rv = ListThreadIds(&startMsg, (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) != 0, idArray, flagArray,
+ levelArray, kIdChunkSize, &numListed, nullptr);
+ if (NS_SUCCEEDED(rv))
+ {
+ int32_t numAdded = AddKeys(idArray, flagArray, levelArray, m_sortType, numListed);
+ if (pCount)
+ *pCount += numAdded;
+ }
+
+ } while (NS_SUCCEEDED(rv) && startMsg != nsMsgKey_None);
+
+ if (NS_SUCCEEDED(getSortrv))
+ {
+ rv = InitSort(m_sortType, m_sortOrder);
+ SaveSortInfo(m_sortType, m_sortOrder);
+
+ }
+ return rv;
+}
+
+nsresult nsMsgThreadedDBView::SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
+{
+ NS_PRECONDITION(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay, "trying to sort unthreaded threads");
+
+ uint32_t numThreads = 0;
+ // the idea here is that copy the current view, then build up an m_keys and m_flags array of just the top level
+ // messages in the view, and then call nsMsgDBView::Sort(sortType, sortOrder).
+ // Then, we expand the threads in the result array that were expanded in the original view (perhaps by copying
+ // from the original view, but more likely just be calling expand).
+ for (uint32_t i = 0; i < m_keys.Length(); i++)
+ {
+ if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD)
+ {
+ if (numThreads < i)
+ {
+ m_keys[numThreads] = m_keys[i];
+ m_flags[numThreads] = m_flags[i];
+ }
+ m_levels[numThreads] = 0;
+ numThreads++;
+ }
+ }
+ m_keys.SetLength(numThreads);
+ m_flags.SetLength(numThreads);
+ m_levels.SetLength(numThreads);
+ //m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay;
+ m_sortType = nsMsgViewSortType::byNone; // sort from scratch
+ nsMsgDBView::Sort(sortType, sortOrder);
+ m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
+ SetSuppressChangeNotifications(true);
+ // Loop through the original array, for each thread that's expanded, find it in the new array
+ // and expand the thread. We have to update MSG_VIEW_FLAG_HAS_CHILDREN because
+ // we may be going from a flat sort, which doesn't maintain that flag,
+ // to a threaded sort, which requires that flag.
+ for (uint32_t j = 0; j < m_keys.Length(); j++)
+ {
+ uint32_t flags = m_flags[j];
+ if ((flags & (MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided)) == MSG_VIEW_FLAG_HASCHILDREN)
+ {
+ uint32_t numExpanded;
+ m_flags[j] = flags | nsMsgMessageFlags::Elided;
+ ExpandByIndex(j, &numExpanded);
+ j += numExpanded;
+ if (numExpanded > 0)
+ m_flags[j - numExpanded] = flags | MSG_VIEW_FLAG_HASCHILDREN;
+ }
+ else if (flags & MSG_VIEW_FLAG_ISTHREAD && ! (flags & MSG_VIEW_FLAG_HASCHILDREN))
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsCOMPtr <nsIMsgThread> pThread;
+ m_db->GetMsgHdrForKey(m_keys[j], getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
+ if (pThread)
+ {
+ uint32_t numChildren;
+ pThread->GetNumChildren(&numChildren);
+ if (numChildren > 1)
+ m_flags[j] = flags | MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided;
+ }
+ }
+ }
+ }
+ SetSuppressChangeNotifications(false);
+
+ return NS_OK;
+}
+
+int32_t nsMsgThreadedDBView::AddKeys(nsMsgKey *pKeys, int32_t *pFlags, const char *pLevels, nsMsgViewSortTypeValue sortType, int32_t numKeysToAdd)
+
+{
+ int32_t numAdded = 0;
+ // Allocate enough space first to avoid memory allocation/deallocation.
+ m_keys.SetCapacity(m_keys.Length() + numKeysToAdd);
+ m_flags.SetCapacity(m_flags.Length() + numKeysToAdd);
+ m_levels.SetCapacity(m_levels.Length() + numKeysToAdd);
+ for (int32_t i = 0; i < numKeysToAdd; i++)
+ {
+ int32_t threadFlag = pFlags[i];
+ int32_t flag = threadFlag;
+
+ // skip ignored threads.
+ if ((threadFlag & nsMsgMessageFlags::Ignored) && !(m_viewFlags & nsMsgViewFlagsType::kShowIgnored))
+ continue;
+
+ // skip ignored subthreads
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_db->GetMsgHdrForKey(pKeys[i], getter_AddRefs(msgHdr));
+ if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored))
+ {
+ bool killed;
+ msgHdr->GetIsKilled(&killed);
+ if (killed)
+ continue;
+ }
+
+ // by default, make threads collapsed, unless we're in only viewing new msgs
+
+ if (flag & MSG_VIEW_FLAG_HASCHILDREN)
+ flag |= nsMsgMessageFlags::Elided;
+ // should this be persistent? Doesn't seem to need to be.
+ flag |= MSG_VIEW_FLAG_ISTHREAD;
+ m_keys.AppendElement(pKeys[i]);
+ m_flags.AppendElement(flag);
+ m_levels.AppendElement(pLevels[i]);
+ numAdded++;
+ // we expand as we build the view, which allows us to insert at the end of the key array,
+ // instead of the middle, and is much faster.
+ if ((!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) || m_viewFlags & nsMsgViewFlagsType::kExpandAll) && flag & nsMsgMessageFlags::Elided)
+ ExpandByIndex(m_keys.Length() - 1, NULL);
+ }
+ return numAdded;
+}
+
+NS_IMETHODIMP nsMsgThreadedDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
+{
+ nsresult rv;
+
+ int32_t rowCountBeforeSort = GetSize();
+
+ if (!rowCountBeforeSort)
+ {
+ // still need to setup our flags even when no articles - bug 98183.
+ m_sortType = sortType;
+ if (sortType == nsMsgViewSortType::byThread && ! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ SetViewFlags(m_viewFlags | nsMsgViewFlagsType::kThreadedDisplay);
+ SaveSortInfo(sortType, sortOrder);
+ return NS_OK;
+ }
+
+ if (!m_checkedCustomColumns && CustomColumnsInSortAndNotRegistered())
+ return NS_OK;
+
+ // sort threads by sort order
+ bool sortThreads = m_viewFlags & (nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort);
+
+ // if sort type is by thread, and we're already threaded, change sort type to byId
+ if (sortType == nsMsgViewSortType::byThread && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) != 0)
+ sortType = nsMsgViewSortType::byId;
+
+ nsMsgKey preservedKey;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ SaveAndClearSelection(&preservedKey, preservedSelection);
+ // if the client wants us to forget our cached id arrays, they
+ // should build a new view. If this isn't good enough, we
+ // need a method to do that.
+ if (sortType != m_sortType || !m_sortValid || sortThreads)
+ {
+ SaveSortInfo(sortType, sortOrder);
+ if (sortType == nsMsgViewSortType::byThread)
+ {
+ m_sortType = sortType;
+ m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
+ m_viewFlags &= ~nsMsgViewFlagsType::kGroupBySort;
+ if ( m_havePrevView)
+ {
+ // restore saved id array and flags array
+ m_keys = m_prevKeys;
+ m_flags = m_prevFlags;
+ m_levels = m_prevLevels;
+ m_sortValid = true;
+
+ // the sort may have changed the number of rows
+ // before we restore the selection, tell the tree
+ // do this before we call restore selection
+ // this is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+ return NS_OK;
+ }
+ else
+ {
+ // set sort info in anticipation of what Init will do.
+ InitThreadedView(nullptr); // build up thread list.
+ if (sortOrder != nsMsgViewSortOrder::ascending)
+ Sort(sortType, sortOrder);
+
+ // the sort may have changed the number of rows
+ // before we update the selection, tell the tree
+ // do this before we call restore selection
+ // this is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+ return NS_OK;
+ }
+ }
+ else if (sortType != nsMsgViewSortType::byThread && (m_sortType == nsMsgViewSortType::byThread || sortThreads)/* && !m_havePrevView*/)
+ {
+ if (sortThreads)
+ {
+ SortThreads(sortType, sortOrder);
+ sortType = nsMsgViewSortType::byThread; // hack so base class won't do anything
+ }
+ else
+ {
+ // going from SortByThread to non-thread sort - must build new key, level,and flags arrays
+ m_prevKeys = m_keys;
+ m_prevFlags = m_flags;
+ m_prevLevels = m_levels;
+ // do this before we sort, so that we'll use the cheap method
+ // of expanding.
+ m_viewFlags &= ~(nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort);
+ ExpandAll();
+ // m_idArray.RemoveAll();
+ // m_flags.Clear();
+ m_havePrevView = true;
+ }
+ }
+ }
+ else if (m_sortOrder != sortOrder)// check for toggling the sort
+ {
+ nsMsgDBView::Sort(sortType, sortOrder);
+ }
+ if (!sortThreads)
+ {
+ // call the base class in case we're not sorting by thread
+ rv = nsMsgDBView::Sort(sortType, sortOrder);
+ SaveSortInfo(sortType, sortOrder);
+ }
+ // the sort may have changed the number of rows
+ // before we restore the selection, tell the tree
+ // do this before we call restore selection
+ // this is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+// list the ids of the top-level thread ids starting at id == startMsg. This actually returns
+// the ids of the first message in each thread.
+nsresult nsMsgThreadedDBView::ListThreadIds(nsMsgKey *startMsg, bool unreadOnly, nsMsgKey *pOutput, int32_t *pFlags, char *pLevels,
+ int32_t numToList, int32_t *pNumListed, int32_t *pTotalHeaders)
+{
+ nsresult rv = NS_OK;
+ // N.B..don't ret before assigning numListed to *pNumListed
+ int32_t numListed = 0;
+
+ if (*startMsg > 0)
+ {
+ NS_ASSERTION(m_threadEnumerator != nullptr, "where's our iterator?"); // for now, we'll just have to rely on the caller leaving
+ // the iterator in the right place.
+ }
+ else
+ {
+ NS_ASSERTION(m_db, "no db");
+ if (!m_db) return NS_ERROR_UNEXPECTED;
+ rv = m_db->EnumerateThreads(getter_AddRefs(m_threadEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool hasMore = false;
+
+ nsCOMPtr <nsIMsgThread> threadHdr ;
+ int32_t threadsRemoved = 0;
+ while (numListed < numToList &&
+ NS_SUCCEEDED(rv = m_threadEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = m_threadEnumerator->GetNext(getter_AddRefs(supports));
+ if (NS_FAILED(rv))
+ {
+ threadHdr = nullptr;
+ break;
+ }
+ threadHdr = do_QueryInterface(supports);
+ if (!threadHdr)
+ break;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ uint32_t numChildren;
+ if (unreadOnly)
+ threadHdr->GetNumUnreadChildren(&numChildren);
+ else
+ threadHdr->GetNumChildren(&numChildren);
+ uint32_t threadFlags;
+ threadHdr->GetFlags(&threadFlags);
+ if (numChildren != 0) // not empty thread
+ {
+ int32_t unusedRootIndex;
+ if (pTotalHeaders)
+ *pTotalHeaders += numChildren;
+ if (unreadOnly)
+ rv = threadHdr->GetFirstUnreadChild(getter_AddRefs(msgHdr));
+ else
+ rv = threadHdr->GetRootHdr(&unusedRootIndex, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr != nullptr && WantsThisThread(threadHdr))
+ {
+ uint32_t msgFlags;
+ uint32_t newMsgFlags;
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ // turn off high byte of msg flags - used for view flags.
+ msgFlags &= ~MSG_VIEW_FLAGS;
+ pOutput[numListed] = msgKey;
+ pLevels[numListed] = 0;
+ // turn off these flags on msg hdr - they belong in thread
+ msgHdr->AndFlags(~(nsMsgMessageFlags::Watched), &newMsgFlags);
+ AdjustReadFlag(msgHdr, &msgFlags);
+ // try adding in MSG_VIEW_FLAG_ISTHREAD flag for unreadonly view.
+ pFlags[numListed] = msgFlags | MSG_VIEW_FLAG_ISTHREAD | threadFlags;
+ if (numChildren > 1)
+ pFlags[numListed] |= MSG_VIEW_FLAG_HASCHILDREN;
+
+ numListed++;
+ }
+ else
+ NS_ASSERTION(NS_SUCCEEDED(rv) && msgHdr, "couldn't get header for some reason");
+ }
+ else if (threadsRemoved < 10 && !(threadFlags & (nsMsgMessageFlags::Watched | nsMsgMessageFlags::Ignored)))
+ {
+ // ### remove thread.
+ threadsRemoved++; // don't want to remove all empty threads first time
+ // around as it will choke preformance for upgrade.
+#ifdef DEBUG_bienvenu
+ printf("removing empty non-ignored non-watched thread\n");
+#endif
+ }
+ }
+
+ if (hasMore && threadHdr)
+ {
+ threadHdr->GetThreadKey(startMsg);
+ }
+ else
+ {
+ *startMsg = nsMsgKey_None;
+ nsCOMPtr <nsIDBChangeListener> dbListener = do_QueryInterface(m_threadEnumerator);
+ // this is needed to make the thread enumerator release its reference to the db.
+ if (dbListener)
+ dbListener->OnAnnouncerGoingAway(nullptr);
+ m_threadEnumerator = nullptr;
+ }
+ *pNumListed = numListed;
+ return rv;
+}
+
+void nsMsgThreadedDBView::OnExtraFlagChanged(nsMsgViewIndex index, uint32_t extraFlag)
+{
+ if (IsValidIndex(index))
+ {
+ if (m_havePrevView)
+ {
+ nsMsgKey keyChanged = m_keys[index];
+ nsMsgViewIndex prevViewIndex = m_prevKeys.IndexOf(keyChanged);
+ if (prevViewIndex != nsMsgViewIndex_None)
+ {
+ uint32_t prevFlag = m_prevFlags[prevViewIndex];
+ // don't want to change the elided bit, or has children or is thread
+ if (prevFlag & nsMsgMessageFlags::Elided)
+ extraFlag |= nsMsgMessageFlags::Elided;
+ else
+ extraFlag &= ~nsMsgMessageFlags::Elided;
+ if (prevFlag & MSG_VIEW_FLAG_ISTHREAD)
+ extraFlag |= MSG_VIEW_FLAG_ISTHREAD;
+ else
+ extraFlag &= ~MSG_VIEW_FLAG_ISTHREAD;
+ if (prevFlag & MSG_VIEW_FLAG_HASCHILDREN)
+ extraFlag |= MSG_VIEW_FLAG_HASCHILDREN;
+ else
+ extraFlag &= ~MSG_VIEW_FLAG_HASCHILDREN;
+ m_prevFlags[prevViewIndex] = extraFlag; // will this be right?
+ }
+ }
+ }
+ // we don't really know what's changed, but to be on the safe side, set the sort invalid
+ // so that reverse sort will pick it up.
+ if (m_sortType == nsMsgViewSortType::byStatus || m_sortType == nsMsgViewSortType::byFlagged ||
+ m_sortType == nsMsgViewSortType::byUnread || m_sortType == nsMsgViewSortType::byPriority)
+ m_sortValid = false;
+}
+
+void nsMsgThreadedDBView::OnHeaderAddedOrDeleted()
+{
+ ClearPrevIdArray();
+}
+
+void nsMsgThreadedDBView::ClearPrevIdArray()
+{
+ m_prevKeys.Clear();
+ m_prevLevels.Clear();
+ m_prevFlags.Clear();
+ m_havePrevView = false;
+}
+
+nsresult nsMsgThreadedDBView::InitSort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder)
+{
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return NS_OK; // nothing to do.
+
+ if (sortType == nsMsgViewSortType::byThread)
+ {
+ nsMsgDBView::Sort(nsMsgViewSortType::byId, sortOrder); // sort top level threads by id.
+ m_sortType = nsMsgViewSortType::byThread;
+ m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
+ m_viewFlags &= ~nsMsgViewFlagsType::kGroupBySort;
+ SetViewFlags(m_viewFlags); // persist the view flags.
+ // m_db->SetSortInfo(m_sortType, sortOrder);
+ }
+// else
+// m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay;
+
+ // by default, the unread only view should have all threads expanded.
+ if ((m_viewFlags & (nsMsgViewFlagsType::kUnreadOnly|nsMsgViewFlagsType::kExpandAll))
+ && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ ExpandAll();
+ if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ ExpandAll(); // for now, expand all and do a flat sort.
+
+ Sort(sortType, sortOrder);
+ if (sortType != nsMsgViewSortType::byThread) // forget prev view, since it has everything expanded.
+ ClearPrevIdArray();
+ return NS_OK;
+}
+
+nsresult nsMsgThreadedDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed)
+{
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OnNewHeader(newHdr, aParentKey, ensureListed);
+
+ NS_ENSURE_TRUE(newHdr, NS_MSG_MESSAGE_NOT_FOUND);
+
+ nsMsgKey newKey;
+ newHdr->GetMessageKey(&newKey);
+
+ // views can override this behaviour, which is to append to view.
+ // This is the mail behaviour, but threaded views want
+ // to insert in order...
+ uint32_t msgFlags;
+ newHdr->GetFlags(&msgFlags);
+ if ((m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) && !ensureListed &&
+ (msgFlags & nsMsgMessageFlags::Read))
+ return NS_OK;
+ // Currently, we only add the header in a threaded view if it's a thread.
+ // We used to check if this was the first header in the thread, but that's
+ // a bit harder in the unreadOnly view. But we'll catch it below.
+
+ // if not threaded display just add it to the view.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return AddHdr(newHdr);
+
+ // need to find the thread we added this to so we can change the hasnew flag
+ // added message to existing thread, but not to view
+ // Fix flags on thread header.
+ int32_t threadCount;
+ uint32_t threadFlags;
+ bool moveThread = false;
+ nsMsgViewIndex threadIndex = ThreadIndexOfMsg(newKey, nsMsgViewIndex_None, &threadCount, &threadFlags);
+ bool threadRootIsDisplayed = false;
+
+ nsCOMPtr <nsIMsgThread> threadHdr;
+ m_db->GetThreadContainingMsgHdr(newHdr, getter_AddRefs(threadHdr));
+ if (threadHdr && m_sortType == nsMsgViewSortType::byDate)
+ {
+ uint32_t newestMsgInThread = 0, msgDate = 0;
+ threadHdr->GetNewestMsgDate(&newestMsgInThread);
+ newHdr->GetDateInSeconds(&msgDate);
+ moveThread = (msgDate == newestMsgInThread);
+ }
+
+ if (threadIndex != nsMsgViewIndex_None)
+ {
+ threadRootIsDisplayed = (m_currentlyDisplayedViewIndex == threadIndex);
+ uint32_t flags = m_flags[threadIndex];
+ if (!(flags & MSG_VIEW_FLAG_HASCHILDREN))
+ {
+ flags |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
+ if (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly))
+ flags |= nsMsgMessageFlags::Elided;
+ m_flags[threadIndex] = flags;
+ }
+
+ if (!(flags & nsMsgMessageFlags::Elided))
+ { // thread is expanded
+ // insert child into thread
+ // levels of other hdrs may have changed!
+ uint32_t newFlags = msgFlags;
+ int32_t level = 0;
+ nsMsgViewIndex insertIndex = threadIndex;
+ if (aParentKey == nsMsgKey_None)
+ {
+ newFlags |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
+ }
+ else
+ {
+ nsMsgViewIndex parentIndex = FindParentInThread(aParentKey, threadIndex);
+ level = m_levels[parentIndex] + 1;
+ insertIndex = GetInsertInfoForNewHdr(newHdr, parentIndex, level);
+ }
+ InsertMsgHdrAt(insertIndex, newHdr, newKey, newFlags, level);
+ // the call to NoteChange() has to happen after we add the key
+ // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
+ NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+
+ if (aParentKey == nsMsgKey_None)
+ {
+ // this header is the new king! try collapsing the existing thread,
+ // removing it, installing this header as king, and expanding it.
+ CollapseByIndex(threadIndex, nullptr);
+ // call base class, so child won't get promoted.
+ // nsMsgDBView::RemoveByIndex(threadIndex);
+ ExpandByIndex(threadIndex, nullptr);
+ }
+ }
+ else if (aParentKey == nsMsgKey_None)
+ {
+ // if we have a collapsed thread which just got a new
+ // top of thread, change the keys array.
+ m_keys[threadIndex] = newKey;
+ }
+
+ // If this message is new, the thread is collapsed, it is the
+ // root and it was displayed, expand it so that the user does
+ // not find that their message has magically turned into a summary.
+ if (msgFlags & nsMsgMessageFlags::New &&
+ m_flags[threadIndex] & nsMsgMessageFlags::Elided &&
+ threadRootIsDisplayed)
+ ExpandByIndex(threadIndex, nullptr);
+
+ if (moveThread)
+ MoveThreadAt(threadIndex);
+ else
+ // note change, to update the parent thread's unread and total counts
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ else if (threadHdr)
+ // adding msg to thread that's not in view.
+ AddMsgToThreadNotInView(threadHdr, newHdr, ensureListed);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgThreadedDBView::OnParentChanged (nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator)
+{
+ // we need to adjust the level of the hdr whose parent changed, and invalidate that row,
+ // iff we're in threaded mode.
+#if 0
+ // This code never runs due to the if (false) and Clang complains about it
+ // so it is ifdefed out for now.
+ if (false && m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ nsMsgViewIndex childIndex = FindViewIndex(aKeyChanged);
+ if (childIndex != nsMsgViewIndex_None)
+ {
+ nsMsgViewIndex parentIndex = FindViewIndex(newParent);
+ int32_t newParentLevel = (parentIndex == nsMsgViewIndex_None) ? -1 : m_levels[parentIndex];
+ nsMsgViewIndex oldParentIndex = FindViewIndex(oldParent);
+ int32_t oldParentLevel = (oldParentIndex != nsMsgViewIndex_None || newParent == nsMsgKey_None)
+ ? m_levels[oldParentIndex] : -1 ;
+ int32_t levelChanged = m_levels[childIndex];
+ int32_t parentDelta = oldParentLevel - newParentLevel;
+ m_levels[childIndex] = (newParent == nsMsgKey_None) ? 0 : newParentLevel + 1;
+ if (parentDelta > 0)
+ {
+ for (nsMsgViewIndex viewIndex = childIndex + 1; viewIndex < GetSize() && m_levels[viewIndex] > levelChanged; viewIndex++)
+ {
+ m_levels[viewIndex] = m_levels[viewIndex] - parentDelta;
+ NoteChange(viewIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+ NoteChange(childIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+#endif
+ return NS_OK;
+}
+
+
+nsMsgViewIndex nsMsgThreadedDBView::GetInsertInfoForNewHdr(nsIMsgDBHdr *newHdr, nsMsgViewIndex parentIndex, int32_t targetLevel)
+{
+ uint32_t viewSize = GetSize();
+ while (++parentIndex < viewSize)
+ {
+ // loop until we find a message at a level less than or equal to the parent level
+ if (m_levels[parentIndex] < targetLevel)
+ break;
+ }
+ return parentIndex;
+}
+
+// This method removes the thread at threadIndex from the view
+// and puts it back in its new position, determined by the sort order.
+// And, if the selection is affected, save and restore the selection.
+void nsMsgThreadedDBView::MoveThreadAt(nsMsgViewIndex threadIndex)
+{
+ // we need to check if the thread is collapsed or not...
+ // We want to turn off tree notifications so that we don't
+ // reload the current message.
+ // We also need to invalidate the range between where the thread was
+ // and where it ended up.
+ bool changesDisabled = mSuppressChangeNotification;
+ if (!changesDisabled)
+ SetSuppressChangeNotifications(true);
+
+ nsCOMPtr <nsIMsgDBHdr> threadHdr;
+
+ GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr));
+ int32_t childCount = 0;
+
+ nsMsgKey preservedKey;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ int32_t selectionCount;
+ int32_t currentIndex;
+ bool hasSelection = mTree && mTreeSelection &&
+ ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
+ currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) ||
+ (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) &&
+ selectionCount > 0));
+ if (hasSelection)
+ SaveAndClearSelection(&preservedKey, preservedSelection);
+ uint32_t saveFlags = m_flags[threadIndex];
+ bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided);
+
+ if (threadIsExpanded)
+ {
+ ExpansionDelta(threadIndex, &childCount);
+ childCount = -childCount;
+ }
+ nsTArray<nsMsgKey> threadKeys;
+ nsTArray<uint32_t> threadFlags;
+ nsTArray<uint8_t> threadLevels;
+
+ if (threadIsExpanded)
+ {
+ threadKeys.SetCapacity(childCount);
+ threadFlags.SetCapacity(childCount);
+ threadLevels.SetCapacity(childCount);
+ for (nsMsgViewIndex index = threadIndex + 1;
+ index < GetSize() && m_levels[index]; index++)
+ {
+ threadKeys.AppendElement(m_keys[index]);
+ threadFlags.AppendElement(m_flags[index]);
+ threadLevels.AppendElement(m_levels[index]);
+ }
+ uint32_t collapseCount;
+ CollapseByIndex(threadIndex, &collapseCount);
+ }
+ nsMsgDBView::RemoveByIndex(threadIndex);
+ nsMsgViewIndex newIndex = nsMsgViewIndex_None;
+ AddHdr(threadHdr, &newIndex);
+
+ // AddHdr doesn't always set newIndex, and getting it to do so
+ // is going to require some refactoring.
+ if (newIndex == nsMsgViewIndex_None)
+ newIndex = FindHdr(threadHdr);
+
+ if (threadIsExpanded)
+ {
+ m_keys.InsertElementsAt(newIndex + 1, threadKeys);
+ m_flags.InsertElementsAt(newIndex + 1, threadFlags);
+ m_levels.InsertElementsAt(newIndex + 1, threadLevels);
+ }
+ if (newIndex == nsMsgViewIndex_None)
+ {
+ NS_WARNING("newIndex=-1 in MoveThreadAt");
+ newIndex = 0;
+ }
+ m_flags[newIndex] = saveFlags;
+ // unfreeze selection.
+ if (hasSelection)
+ RestoreSelection(preservedKey, preservedSelection);
+
+ if (!changesDisabled)
+ SetSuppressChangeNotifications(false);
+ nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex;
+ nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex;
+ NoteChange(lowIndex, highIndex - lowIndex + childCount + 1,
+ nsMsgViewNotificationCode::changed);
+}
+nsresult nsMsgThreadedDBView::AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed)
+{
+ nsresult rv = NS_OK;
+ uint32_t threadFlags;
+ threadHdr->GetFlags(&threadFlags);
+ if (!(threadFlags & nsMsgMessageFlags::Ignored))
+ {
+ bool msgKilled;
+ msgHdr->GetIsKilled(&msgKilled);
+ if (!msgKilled)
+ rv = nsMsgDBView::AddHdr(msgHdr);
+ }
+ return rv;
+}
+
+// This method just removes the specified line from the view. It does
+// NOT delete it from the database.
+nsresult nsMsgThreadedDBView::RemoveByIndex(nsMsgViewIndex index)
+{
+ nsresult rv = NS_OK;
+ int32_t flags;
+
+ if (!IsValidIndex(index))
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ OnHeaderAddedOrDeleted();
+
+ flags = m_flags[index];
+
+ if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return nsMsgDBView::RemoveByIndex(index);
+
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ GetThreadContainingIndex(index, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numThreadChildren = 0;
+ // If we can't get a thread, it's already deleted and thus has 0 children.
+ if (threadHdr)
+ threadHdr->GetNumChildren(&numThreadChildren);
+
+ // Check if we're the top level msg in the thread, and we're not collapsed.
+ if ((flags & MSG_VIEW_FLAG_ISTHREAD) &&
+ !(flags & nsMsgMessageFlags::Elided) &&
+ (flags & MSG_VIEW_FLAG_HASCHILDREN))
+ {
+ // Fix flags on thread header - newly promoted message should have
+ // flags set correctly.
+ if (threadHdr)
+ {
+ nsMsgDBView::RemoveByIndex(index);
+ nsCOMPtr<nsIMsgThread> nextThreadHdr;
+ // Above RemoveByIndex may now make index out of bounds.
+ if (IsValidIndex(index) && numThreadChildren > 0)
+ {
+ // unreadOnly
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr));
+ if (msgHdr != nullptr)
+ {
+ uint32_t flag = 0;
+ msgHdr->GetFlags(&flag);
+ if (numThreadChildren > 1)
+ flag |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
+
+ m_flags[index] = flag;
+ m_levels[index] = 0;
+ }
+ }
+ }
+ return rv;
+ }
+ else if (!(flags & MSG_VIEW_FLAG_ISTHREAD))
+ {
+ // We're not deleting the top level msg, but top level msg might be the
+ // only msg in thread now.
+ if (threadHdr && numThreadChildren == 1)
+ {
+ nsMsgKey msgKey;
+ rv = threadHdr->GetChildKeyAt(0, &msgKey);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsMsgViewIndex threadIndex = FindViewIndex(msgKey);
+ if (IsValidIndex(threadIndex))
+ {
+ uint32_t flags = m_flags[threadIndex];
+ flags &= ~(MSG_VIEW_FLAG_ISTHREAD |
+ nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN);
+ m_flags[threadIndex] = flags;
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+
+ }
+ return nsMsgDBView::RemoveByIndex(index);
+ }
+ // Deleting collapsed thread header is special case. Child will be promoted,
+ // so just tell FE that line changed, not that it was deleted.
+ // Header has aleady been deleted from thread.
+ if (threadHdr && numThreadChildren > 0)
+ {
+ // change the id array and flags array to reflect the child header.
+ // If we're not deleting the header, we want the second header,
+ // Otherwise, the first one (which just got promoted).
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr));
+ if (msgHdr != nullptr)
+ {
+ msgHdr->GetMessageKey(&m_keys[index]);
+
+ uint32_t flag = 0;
+ msgHdr->GetFlags(&flag);
+ flag |= MSG_VIEW_FLAG_ISTHREAD;
+
+ // if only hdr in thread (with one about to be deleted)
+ if (numThreadChildren == 1)
+ {
+ // adjust flags.
+ flag &= ~MSG_VIEW_FLAG_HASCHILDREN;
+ flag &= ~nsMsgMessageFlags::Elided;
+ // tell FE that thread header needs to be repainted.
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ }
+ else
+ {
+ flag |= MSG_VIEW_FLAG_HASCHILDREN;
+ flag |= nsMsgMessageFlags::Elided;
+ }
+ m_flags[index] = flag;
+ mIndicesToNoteChange.RemoveElement(index);
+ }
+ else
+ NS_ASSERTION(false, "couldn't find thread child");
+
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ }
+ else
+ {
+ // we may have deleted a whole, collapsed thread - if so,
+ // ensure that the current index will be noted as changed.
+ if (!mIndicesToNoteChange.Contains(index))
+ mIndicesToNoteChange.AppendElement(index);
+
+ rv = nsMsgDBView::RemoveByIndex(index);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThreadedDBView::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowAllThreads;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval)
+{
+ nsMsgThreadedDBView* newMsgDBView = new nsMsgThreadedDBView();
+
+ if (!newMsgDBView)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMsgThreadedDBView.h b/mailnews/base/src/nsMsgThreadedDBView.h
new file mode 100644
index 000000000..948260c12
--- /dev/null
+++ b/mailnews/base/src/nsMsgThreadedDBView.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgThreadedDBView_H_
+#define _nsMsgThreadedDBView_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgGroupView.h"
+
+class nsMsgThreadedDBView : public nsMsgGroupView
+{
+public:
+ nsMsgThreadedDBView();
+ virtual ~nsMsgThreadedDBView();
+
+ NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) override;
+ NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCommandUpdater, nsIMsgDBView **_retval) override;
+ NS_IMETHOD Close() override;
+ int32_t AddKeys(nsMsgKey *pKeys, int32_t *pFlags, const char *pLevels, nsMsgViewSortTypeValue sortType, int32_t numKeysToAdd);
+ NS_IMETHOD Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override;
+ NS_IMETHOD OnParentChanged (nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator) override;
+
+protected:
+ virtual const char *GetViewName(void) override { return "ThreadedDBView"; }
+ nsresult InitThreadedView(int32_t *pCount);
+ virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed) override;
+ virtual nsresult AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed);
+ nsresult ListThreadIds(nsMsgKey *startMsg, bool unreadOnly, nsMsgKey *pOutput, int32_t *pFlags, char *pLevels,
+ int32_t numToList, int32_t *pNumListed, int32_t *pTotalHeaders);
+ nsresult InitSort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder);
+ virtual nsresult SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder);
+ virtual void OnExtraFlagChanged(nsMsgViewIndex index, uint32_t extraFlag) override;
+ virtual void OnHeaderAddedOrDeleted() override;
+ void ClearPrevIdArray();
+ virtual nsresult RemoveByIndex(nsMsgViewIndex index) override;
+ nsMsgViewIndex GetInsertInfoForNewHdr(nsIMsgDBHdr *newHdr, nsMsgViewIndex threadIndex, int32_t targetLevel);
+ void MoveThreadAt(nsMsgViewIndex threadIndex);
+
+ // these are used to save off the previous view so that bopping back and forth
+ // between two views is quick (e.g., threaded and flat sorted by date).
+ bool m_havePrevView;
+ nsTArray<nsMsgKey> m_prevKeys; //this is used for caching non-threaded view.
+ nsTArray<uint32_t> m_prevFlags;
+ nsTArray<uint8_t> m_prevLevels;
+ nsCOMPtr <nsISimpleEnumerator> m_threadEnumerator;
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgWindow.cpp b/mailnews/base/src/nsMsgWindow.cpp
new file mode 100644
index 000000000..b94cbc0e1
--- /dev/null
+++ b/mailnews/base/src/nsMsgWindow.cpp
@@ -0,0 +1,534 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgWindow.h"
+#include "nsIURILoader.h"
+#include "nsCURILoader.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDOMElement.h"
+#include "mozIDOMWindow.h"
+#include "nsTransactionManagerCID.h"
+#include "nsIComponentManager.h"
+#include "nsILoadGroup.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsPIDOMWindow.h"
+#include "nsIPrompt.h"
+#include "nsICharsetConverterManager.h"
+#include "nsIChannel.h"
+#include "nsIRequestObserver.h"
+#include "netCore.h"
+#include "prmem.h"
+#include "plbase64.h"
+#include "nsMsgI18N.h"
+#include "nsIWebNavigation.h"
+#include "nsMsgContentPolicy.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIAuthPrompt.h"
+#include "nsMsgUtils.h"
+
+// used to dispatch urls to default protocol handlers
+#include "nsCExternalHandlerService.h"
+#include "nsIExternalProtocolService.h"
+
+static NS_DEFINE_CID(kTransactionManagerCID, NS_TRANSACTIONMANAGER_CID);
+
+NS_IMPL_ISUPPORTS(nsMsgWindow,
+ nsIMsgWindow,
+ nsIURIContentListener,
+ nsISupportsWeakReference,
+ nsIMsgWindowTest)
+
+nsMsgWindow::nsMsgWindow()
+{
+ mCharsetOverride = false;
+ m_stopped = false;
+}
+
+nsMsgWindow::~nsMsgWindow()
+{
+ CloseWindow();
+}
+
+nsresult nsMsgWindow::Init()
+{
+ // register ourselves as a content listener with the uri dispatcher service
+ nsresult rv;
+ nsCOMPtr<nsIURILoader> dispatcher =
+ do_GetService(NS_URI_LOADER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dispatcher->RegisterContentListener(this);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // create Undo/Redo Transaction Manager
+ mTransactionManager = do_CreateInstance(kTransactionManagerCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mTransactionManager->SetMaxTransactionCount(-1);
+}
+
+NS_IMETHODIMP nsMsgWindow::GetMessageWindowDocShell(nsIDocShell ** aDocShell)
+{
+ *aDocShell = nullptr;
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mMessageWindowDocShellWeak));
+ if (!docShell)
+ {
+ // if we don't have a docshell, then we need to look up the message pane docshell
+ nsCOMPtr<nsIDocShell> rootShell(do_QueryReferent(mRootDocShellWeak));
+ if (rootShell)
+ {
+ nsCOMPtr<nsIDocShellTreeItem> msgDocShellItem;
+ if(rootShell)
+ rootShell->FindChildWithName(NS_LITERAL_STRING("messagepane"),
+ true, false, nullptr, nullptr,
+ getter_AddRefs(msgDocShellItem));
+ NS_ENSURE_TRUE(msgDocShellItem, NS_ERROR_FAILURE);
+ docShell = do_QueryInterface(msgDocShellItem);
+ // we don't own mMessageWindowDocShell so don't try to keep a reference to it!
+ mMessageWindowDocShellWeak = do_GetWeakReference(docShell);
+ }
+ }
+ docShell.swap(*aDocShell);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::CloseWindow()
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURILoader> dispatcher = do_GetService(NS_URI_LOADER_CONTRACTID, &rv);
+ if (dispatcher) // on shut down it's possible dispatcher will be null.
+ rv = dispatcher->UnRegisterContentListener(this);
+
+ mMsgWindowCommands = nullptr;
+ mStatusFeedback = nullptr;
+
+ StopUrls();
+
+ nsCOMPtr<nsIDocShell> messagePaneDocShell(do_QueryReferent(mMessageWindowDocShellWeak));
+ if (messagePaneDocShell)
+ {
+ nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(messagePaneDocShell));
+ if (listener)
+ listener->SetParentContentListener(nullptr);
+ SetRootDocShell(nullptr);
+ mMessageWindowDocShellWeak = nullptr;
+ }
+
+ // in case nsMsgWindow leaks, make sure other stuff doesn't leak.
+ mTransactionManager = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetStatusFeedback(nsIMsgStatusFeedback **aStatusFeedback)
+{
+ NS_ENSURE_ARG_POINTER(aStatusFeedback);
+ NS_IF_ADDREF(*aStatusFeedback = mStatusFeedback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetStatusFeedback(nsIMsgStatusFeedback * aStatusFeedback)
+{
+ mStatusFeedback = aStatusFeedback;
+ nsCOMPtr<nsIDocShell> messageWindowDocShell;
+ GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell));
+
+ // register our status feedback object as a web progress listener
+ nsCOMPtr<nsIWebProgress> webProgress(do_GetInterface(messageWindowDocShell));
+ if (webProgress && mStatusFeedback && messageWindowDocShell)
+ {
+ nsCOMPtr<nsIWebProgressListener> webProgressListener = do_QueryInterface(mStatusFeedback);
+ webProgress->AddProgressListener(webProgressListener, nsIWebProgress::NOTIFY_ALL);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetWindowCommands(nsIMsgWindowCommands * aMsgWindowCommands)
+{
+ mMsgWindowCommands = aMsgWindowCommands;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetWindowCommands(nsIMsgWindowCommands **aMsgWindowCommands)
+{
+ NS_ENSURE_ARG_POINTER(aMsgWindowCommands);
+ NS_IF_ADDREF(*aMsgWindowCommands = mMsgWindowCommands);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetMsgHeaderSink(nsIMsgHeaderSink * *aMsgHdrSink)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdrSink);
+ NS_IF_ADDREF(*aMsgHdrSink = mMsgHeaderSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetMsgHeaderSink(nsIMsgHeaderSink * aMsgHdrSink)
+{
+ mMsgHeaderSink = aMsgHdrSink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetTransactionManager(nsITransactionManager * *aTransactionManager)
+{
+ NS_ENSURE_ARG_POINTER(aTransactionManager);
+ NS_IF_ADDREF(*aTransactionManager = mTransactionManager);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetTransactionManager(nsITransactionManager * aTransactionManager)
+{
+ mTransactionManager = aTransactionManager;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetOpenFolder(nsIMsgFolder * *aOpenFolder)
+{
+ NS_ENSURE_ARG_POINTER(aOpenFolder);
+ NS_IF_ADDREF(*aOpenFolder = mOpenFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetOpenFolder(nsIMsgFolder * aOpenFolder)
+{
+ mOpenFolder = aOpenFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetRootDocShell(nsIDocShell * *aDocShell)
+{
+ if (mRootDocShellWeak)
+ CallQueryReferent(mRootDocShellWeak.get(), aDocShell);
+ else
+ *aDocShell = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetAuthPrompt(nsIAuthPrompt * *aAuthPrompt)
+{
+ NS_ENSURE_ARG_POINTER(aAuthPrompt);
+
+ // testing only
+ if (mAuthPrompt)
+ {
+ NS_ADDREF(*aAuthPrompt = mAuthPrompt);
+ return NS_OK;
+ }
+
+ if (!mRootDocShellWeak)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mRootDocShellWeak.get(), &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAuthPrompt> prompt = do_GetInterface(docShell, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ prompt.swap(*aAuthPrompt);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetAuthPrompt(nsIAuthPrompt* aAuthPrompt)
+{
+ mAuthPrompt = aAuthPrompt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetRootDocShell(nsIDocShell * aDocShell)
+{
+ nsresult rv;
+ nsCOMPtr<nsIWebProgressListener> contentPolicyListener =
+ do_GetService(NS_MSGCONTENTPOLICY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // remove the content policy webProgressListener from the root doc shell
+ // we're currently holding, so we don't keep listening for loads that
+ // we don't care about
+ if (mRootDocShellWeak) {
+ nsCOMPtr<nsIWebProgress> oldWebProgress =
+ do_QueryReferent(mRootDocShellWeak, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = oldWebProgress->RemoveProgressListener(contentPolicyListener);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove old progress listener");
+ }
+ }
+
+ // Query for the doc shell and release it
+ mRootDocShellWeak = nullptr;
+ if (aDocShell)
+ {
+ mRootDocShellWeak = do_GetWeakReference(aDocShell);
+
+ nsCOMPtr<nsIDocShell> messagePaneDocShell;
+ GetMessageWindowDocShell(getter_AddRefs(messagePaneDocShell));
+ nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(messagePaneDocShell));
+ if (listener)
+ listener->SetParentContentListener(this);
+
+ // set the contentPolicy webProgressListener on the root docshell for this
+ // window so that it can allow JavaScript for non-message content
+ nsCOMPtr<nsIWebProgress> docShellProgress =
+ do_QueryInterface(aDocShell, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docShellProgress->AddProgressListener(contentPolicyListener,
+ nsIWebProgress::NOTIFY_LOCATION);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetMailCharacterSet(nsACString& aMailCharacterSet)
+{
+ aMailCharacterSet = mMailCharacterSet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetMailCharacterSet(const nsACString& aMailCharacterSet)
+{
+ mMailCharacterSet.Assign(aMailCharacterSet);
+
+ // Convert to a canonical charset name instead of using the charset name from the message header as is.
+ // This is needed for charset menu item to have a check mark correctly.
+ nsresult rv;
+ nsCOMPtr <nsICharsetConverterManager> ccm =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return ccm->GetCharsetAlias(PromiseFlatCString(aMailCharacterSet).get(),
+ mMailCharacterSet);
+}
+
+NS_IMETHODIMP nsMsgWindow::GetCharsetOverride(bool *aCharsetOverride)
+{
+ NS_ENSURE_ARG_POINTER(aCharsetOverride);
+ *aCharsetOverride = mCharsetOverride;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetCharsetOverride(bool aCharsetOverride)
+{
+ mCharsetOverride = aCharsetOverride;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetDomWindow(mozIDOMWindowProxy **aWindow)
+{
+ NS_ENSURE_ARG_POINTER(aWindow);
+ if (mDomWindow)
+ CallQueryReferent(mDomWindow.get(), aWindow);
+ else
+ *aWindow = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetDomWindow(mozIDOMWindowProxy * aWindow)
+{
+ NS_ENSURE_ARG_POINTER(aWindow);
+ mDomWindow = do_GetWeakReference(aWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(aWindow);
+ nsIDocShell *docShell = nullptr;
+ if (win)
+ docShell = win->GetDocShell();
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(docShell));
+
+ if(docShellAsItem)
+ {
+ nsCOMPtr<nsIDocShellTreeItem> rootAsItem;
+ docShellAsItem->GetSameTypeRootTreeItem(getter_AddRefs(rootAsItem));
+
+ nsCOMPtr<nsIDocShell> rootAsShell(do_QueryInterface(rootAsItem));
+ SetRootDocShell(rootAsShell);
+
+ // force ourselves to figure out the message pane
+ nsCOMPtr<nsIDocShell> messageWindowDocShell;
+ GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetNotificationCallbacks(nsIInterfaceRequestor * aNotificationCallbacks)
+{
+ mNotificationCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetNotificationCallbacks(nsIInterfaceRequestor **aNotificationCallbacks)
+{
+ NS_ENSURE_ARG_POINTER(aNotificationCallbacks);
+ NS_IF_ADDREF(*aNotificationCallbacks = mNotificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::StopUrls()
+{
+ m_stopped = true;
+ nsCOMPtr<nsIWebNavigation> webnav(do_QueryReferent(mRootDocShellWeak));
+ return webnav ? webnav->Stop(nsIWebNavigation::STOP_NETWORK) : NS_ERROR_FAILURE;
+}
+
+// nsIURIContentListener support
+NS_IMETHODIMP nsMsgWindow::OnStartURIOpen(nsIURI* aURI, bool* aAbortOpen)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::DoContent(const nsACString& aContentType, bool aIsContentPreferred,
+ nsIRequest *request, nsIStreamListener **aContentHandler, bool *aAbortProcess)
+{
+ if (!aContentType.IsEmpty())
+ {
+ // forward the DoContent call to our docshell
+ nsCOMPtr<nsIDocShell> messageWindowDocShell;
+ GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell));
+ nsCOMPtr<nsIURIContentListener> ctnListener = do_QueryInterface(messageWindowDocShell);
+ if (ctnListener)
+ {
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (!aChannel) return NS_ERROR_FAILURE;
+
+ // get the url for the channel...let's hope it is a mailnews url so we can set our msg hdr sink on it..
+ // right now, this is the only way I can think of to force the msg hdr sink into the mime converter so it can
+ // get too it later...
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ if (uri)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(uri));
+ if (mailnewsUrl)
+ mailnewsUrl->SetMsgWindow(this);
+ }
+ return ctnListener->DoContent(aContentType, aIsContentPreferred, request, aContentHandler, aAbortProcess);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgWindow::IsPreferred(const char * aContentType,
+ char ** aDesiredContentType,
+ bool * aCanHandleContent)
+{
+ *aCanHandleContent = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::CanHandleContent(const char * aContentType,
+ bool aIsContentPreferred,
+ char ** aDesiredContentType,
+ bool * aCanHandleContent)
+
+{
+ // the mail window knows nothing about the default content types
+ // its docshell can handle...ask the content area if it can handle
+ // the content type...
+
+ nsCOMPtr<nsIDocShell> messageWindowDocShell;
+ GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell));
+ nsCOMPtr<nsIURIContentListener> ctnListener (do_GetInterface(messageWindowDocShell));
+ if (ctnListener)
+ return ctnListener->CanHandleContent(aContentType, aIsContentPreferred,
+ aDesiredContentType, aCanHandleContent);
+ else
+ *aCanHandleContent = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetParentContentListener(nsIURIContentListener** aParent)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+ *aParent = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetParentContentListener(nsIURIContentListener* aParent)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetLoadCookie(nsISupports ** aLoadCookie)
+{
+ NS_ENSURE_ARG_POINTER(aLoadCookie);
+ *aLoadCookie = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetLoadCookie(nsISupports * aLoadCookie)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetPromptDialog(nsIPrompt **aPrompt)
+{
+ NS_ENSURE_ARG_POINTER(aPrompt);
+
+ // testing only
+ if (mPromptDialog)
+ {
+ NS_ADDREF(*aPrompt = mPromptDialog);
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIDocShell> rootShell(do_QueryReferent(mRootDocShellWeak, &rv));
+ if (rootShell)
+ {
+ nsCOMPtr<nsIPrompt> dialog;
+ dialog = do_GetInterface(rootShell, &rv);
+ dialog.swap(*aPrompt);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetPromptDialog(nsIPrompt* aPromptDialog)
+{
+ mPromptDialog = aPromptDialog;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgWindow::DisplayHTMLInMessagePane(const nsAString& title, const nsAString& body, bool clearMsgHdr)
+{
+ if (clearMsgHdr && mMsgWindowCommands)
+ mMsgWindowCommands->ClearMsgPane();
+
+ nsString htmlStr;
+ htmlStr.Append(NS_LITERAL_STRING("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></head><body>"));
+ htmlStr.Append(body);
+ htmlStr.Append(NS_LITERAL_STRING("</body></html>"));
+
+ char *encodedHtml = PL_Base64Encode(NS_ConvertUTF16toUTF8(htmlStr).get(), 0, nullptr);
+ if (!encodedHtml)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCString dataSpec;
+ dataSpec = "data:text/html;base64,";
+ dataSpec += encodedHtml;
+
+ PR_FREEIF(encodedHtml);
+
+ nsCOMPtr <nsIDocShell> docShell;
+ GetMessageWindowDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
+ NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE);
+
+ return webNav->LoadURI(NS_ConvertASCIItoUTF16(dataSpec).get(),
+ nsIWebNavigation::LOAD_FLAGS_NONE,
+ nullptr, nullptr, nullptr);
+}
+
+NS_IMPL_GETSET(nsMsgWindow, Stopped, bool, m_stopped)
diff --git a/mailnews/base/src/nsMsgWindow.h b/mailnews/base/src/nsMsgWindow.h
new file mode 100644
index 000000000..6dd061990
--- /dev/null
+++ b/mailnews/base/src/nsMsgWindow.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgWindow_h
+#define _nsMsgWindow_h
+
+#include "nsIMsgWindow.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsITransactionManager.h"
+#include "nsIMsgFolder.h"
+#include "nsIDocShell.h"
+#include "nsIURIContentListener.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsWeakReference.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsCOMPtr.h"
+
+class nsMsgWindow : public nsIMsgWindow,
+ public nsIURIContentListener,
+ public nsIMsgWindowTest,
+ public nsSupportsWeakReference
+{
+
+public:
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsMsgWindow();
+ nsresult Init();
+ NS_DECL_NSIMSGWINDOW
+ NS_DECL_NSIURICONTENTLISTENER
+ NS_DECL_NSIMSGWINDOWTEST
+
+protected:
+ virtual ~nsMsgWindow();
+ nsCOMPtr<nsIMsgHeaderSink> mMsgHeaderSink;
+ nsCOMPtr<nsIMsgStatusFeedback> mStatusFeedback;
+ nsCOMPtr<nsITransactionManager> mTransactionManager;
+ nsCOMPtr<nsIMsgFolder> mOpenFolder;
+ nsCOMPtr<nsIMsgWindowCommands> mMsgWindowCommands;
+ // These are used by the backend protocol code to attach
+ // notification callbacks to channels, e.g., nsIBadCertListner2.
+ nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks;
+ // prompt dialog used during testing only
+ nsCOMPtr<nsIPrompt> mPromptDialog;
+ // authorization prompt used during testing only
+ nsCOMPtr<nsIAuthPrompt> mAuthPrompt;
+
+ // let's not make this a strong ref - we don't own it.
+ nsWeakPtr mRootDocShellWeak;
+ nsWeakPtr mMessageWindowDocShellWeak;
+ nsWeakPtr mDomWindow;
+
+ nsCString mMailCharacterSet;
+ bool mCharsetOverride;
+ bool m_stopped;
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgXFViewThread.cpp b/mailnews/base/src/nsMsgXFViewThread.cpp
new file mode 100644
index 000000000..113aeef6f
--- /dev/null
+++ b/mailnews/base/src/nsMsgXFViewThread.cpp
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgXFViewThread.h"
+#include "nsMsgSearchDBView.h"
+#include "nsMsgMessageFlags.h"
+
+NS_IMPL_ISUPPORTS(nsMsgXFViewThread, nsIMsgThread)
+
+nsMsgXFViewThread::nsMsgXFViewThread(nsMsgSearchDBView *view, nsMsgKey threadId)
+{
+ m_numUnreadChildren = 0;
+ m_numChildren = 0;
+ m_flags = 0;
+ m_newestMsgDate = 0;
+ m_view = view;
+ m_threadId = threadId;
+}
+
+nsMsgXFViewThread::~nsMsgXFViewThread()
+{
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::SetThreadKey(nsMsgKey threadKey)
+{
+ m_threadId = threadKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetThreadKey(nsMsgKey *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_threadId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetFlags(uint32_t *aFlags)
+{
+ NS_ENSURE_ARG_POINTER(aFlags);
+ *aFlags = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::SetFlags(uint32_t aFlags)
+{
+ m_flags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::SetSubject(const nsACString& aSubject)
+{
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetSubject(nsACString& result)
+{
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetNumChildren(uint32_t *aNumChildren)
+{
+ NS_ENSURE_ARG_POINTER(aNumChildren);
+ *aNumChildren = m_keys.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetNumUnreadChildren (uint32_t *aNumUnreadChildren)
+{
+ NS_ENSURE_ARG_POINTER(aNumUnreadChildren);
+ *aNumUnreadChildren = m_numUnreadChildren;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::AddChild(nsIMsgDBHdr *aNewHdr, nsIMsgDBHdr *aInReplyTo,
+ bool aThreadInThread, nsIDBChangeAnnouncer *aAnnouncer)
+{
+ uint32_t whereInserted;
+ return AddHdr(aNewHdr, false, whereInserted, nullptr);
+}
+
+// Returns the parent of the newly added header. If reparentChildren
+// is true, we believe that the new header is a parent of an existing
+// header, and we should find it, and reparent it.
+nsresult nsMsgXFViewThread::AddHdr(nsIMsgDBHdr *newHdr,
+ bool reparentChildren,
+ uint32_t &whereInserted,
+ nsIMsgDBHdr **outParent)
+{
+ nsCOMPtr<nsIMsgFolder> newHdrFolder;
+ newHdr->GetFolder(getter_AddRefs(newHdrFolder));
+
+ uint32_t newHdrFlags = 0;
+ uint32_t msgDate;
+ nsMsgKey newHdrKey = 0;
+
+ newHdr->GetMessageKey(&newHdrKey);
+ newHdr->GetDateInSeconds(&msgDate);
+ newHdr->GetFlags(&newHdrFlags);
+ if (msgDate > m_newestMsgDate)
+ SetNewestMsgDate(msgDate);
+
+ if (newHdrFlags & nsMsgMessageFlags::Watched)
+ SetFlags(m_flags | nsMsgMessageFlags::Watched);
+
+ ChangeChildCount(1);
+ if (! (newHdrFlags & nsMsgMessageFlags::Read))
+ ChangeUnreadChildCount(1);
+
+ if (m_numChildren == 1)
+ {
+ m_keys.InsertElementAt(0, newHdrKey);
+ m_levels.InsertElementAt(0, 0);
+ m_folders.InsertObjectAt(newHdrFolder, 0);
+ if (outParent)
+ *outParent = nullptr;
+ whereInserted = 0;
+ return NS_OK;
+ }
+
+ // Find our parent, if any, in the thread. Starting at the newest
+ // reference, and working our way back, see if we've mapped that reference
+ // to this thread.
+ uint16_t numReferences;
+ newHdr->GetNumReferences(&numReferences);
+ nsCOMPtr<nsIMsgDBHdr> parent;
+ int32_t parentIndex = -1;
+
+ for (int32_t i = numReferences - 1; i >= 0; i--)
+ {
+ nsAutoCString reference;
+ newHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty())
+ break;
+
+ // I could look for the thread from the reference, but getting
+ // the header directly should be fine. If it's not, that means
+ // that the parent isn't in this thread, though it should be.
+ m_view->GetMsgHdrFromHash(reference, getter_AddRefs(parent));
+ if (parent)
+ {
+ parentIndex = HdrIndex(parent);
+ if (parentIndex == -1)
+ {
+ NS_ERROR("how did we get in the wrong thread?");
+ parent = nullptr;
+ }
+ break;
+ }
+ }
+ if (parent)
+ {
+ if (outParent)
+ NS_ADDREF(*outParent = parent);
+ uint32_t parentLevel = m_levels[parentIndex];
+ nsMsgKey parentKey;
+ parent->GetMessageKey(&parentKey);
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ parent->GetFolder(getter_AddRefs(parentFolder));
+ // iterate over our parents' children until we find one we're older than,
+ // and insert ourselves before it, or as the last child. In other words,
+ // insert, sorted by date.
+ uint32_t msgDate, childDate;
+ newHdr->GetDateInSeconds(&msgDate);
+ nsCOMPtr<nsIMsgDBHdr> child;
+ nsMsgViewIndex i;
+ nsMsgViewIndex insertIndex = m_keys.Length();
+ uint32_t insertLevel = parentLevel + 1;
+ for (i = parentIndex;
+ i < m_keys.Length() && (i == (nsMsgViewIndex)parentIndex || m_levels[i] >= parentLevel);
+ i++)
+ {
+ GetChildHdrAt(i, getter_AddRefs(child));
+ if (child)
+ {
+ if (reparentChildren && IsHdrParentOf(newHdr, child))
+ {
+ insertIndex = i;
+ // bump all the children of the current child, and the child
+ nsMsgViewIndex j = insertIndex;
+ uint8_t childLevel = m_levels[insertIndex];
+ do
+ {
+ m_levels[j] = m_levels[j] + 1;
+ j++;
+ }
+ while (j < m_keys.Length() && m_levels[j] > childLevel);
+ break;
+ }
+ else if (m_levels[i] == parentLevel + 1) // possible sibling
+ {
+ child->GetDateInSeconds(&childDate);
+ if (msgDate < childDate)
+ {
+ // if we think we need to reparent, remember this
+ // insert index, but keep looking for children.
+ insertIndex = i;
+ insertLevel = m_levels[i];
+ // if the sibling we're inserting after has children, we need
+ // to go after the children.
+ while (insertIndex + 1 < m_keys.Length() && m_levels[insertIndex + 1] > insertLevel)
+ insertIndex++;
+ if (!reparentChildren)
+ break;
+ }
+ }
+ }
+ }
+ m_keys.InsertElementAt(insertIndex, newHdrKey);
+ m_levels.InsertElementAt(insertIndex, insertLevel);
+ m_folders.InsertObjectAt(newHdrFolder, insertIndex);
+ whereInserted = insertIndex;
+ }
+ else
+ {
+ if (outParent)
+ *outParent = nullptr;
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ GetChildHdrAt(0, getter_AddRefs(rootHdr));
+ // If the new header is a parent of the root then it should be promoted.
+ if (rootHdr && IsHdrParentOf(newHdr, rootHdr))
+ {
+ m_keys.InsertElementAt(0, newHdrKey);
+ m_levels.InsertElementAt(0, 0);
+ m_folders.InsertObjectAt(newHdrFolder, 0);
+ whereInserted = 0;
+ // Adjust level of old root hdr and its children
+ for (nsMsgViewIndex i = 1; i < m_keys.Length(); i++)
+ m_levels[i] = m_levels[1] + 1;
+ }
+ else
+ {
+ m_keys.AppendElement(newHdrKey);
+ m_levels.AppendElement(1);
+ m_folders.AppendObject(newHdrFolder);
+ if (outParent)
+ NS_IF_ADDREF(*outParent = rootHdr);
+ whereInserted = m_keys.Length() -1;
+ }
+ }
+
+ // ### TODO handle the case where the root header starts
+ // with Re, and the new one doesn't, and is earlier. In that
+ // case, we want to promote the new header to root.
+
+// PRTime newHdrDate;
+// newHdr->GetDate(&newHdrDate);
+
+// if (numChildren > 0 && !(newHdrFlags & nsMsgMessageFlags::HasRe))
+// {
+// PRTime topLevelHdrDate;
+
+// nsCOMPtr<nsIMsgDBHdr> topLevelHdr;
+// rv = GetRootHdr(nullptr, getter_AddRefs(topLevelHdr));
+// if (NS_SUCCEEDED(rv) && topLevelHdr)
+// {
+// topLevelHdr->GetDate(&topLevelHdrDate);
+// if (newHdrDate < topLevelHdrDate)
+
+// }
+// }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **aResult)
+{
+ if (aIndex >= m_keys.Length())
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = m_folders[aIndex]->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return db->GetMsgHdrForKey(m_keys[aIndex], aResult);
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::RemoveChildAt(uint32_t aIndex)
+{
+ m_keys.RemoveElementAt(aIndex);
+ m_levels.RemoveElementAt(aIndex);
+ m_folders.RemoveObjectAt(aIndex);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer)
+{
+ NS_ENSURE_ARG_POINTER(child);
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ child->GetMessageKey(&msgKey);
+ child->GetFlags(&msgFlags);
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ child->GetFolder(getter_AddRefs(msgFolder));
+ // if this was the newest msg, clear the newest msg date so we'll recalc.
+ uint32_t date;
+ child->GetDateInSeconds(&date);
+ if (date == m_newestMsgDate)
+ SetNewestMsgDate(0);
+
+ for (uint32_t childIndex = 0; childIndex < m_keys.Length(); childIndex++)
+ {
+ if (m_keys[childIndex] == msgKey && m_folders[childIndex] == msgFolder)
+ {
+ uint8_t levelRemoved = m_keys[childIndex];
+ // Adjust the levels of all the children of this header
+ nsMsgViewIndex i;
+ for (i = childIndex + 1;
+ i < m_keys.Length() && m_levels[i] > levelRemoved; i++)
+ m_levels[i] = m_levels[i] - 1;
+
+ m_view->NoteChange(childIndex + 1, i - childIndex + 1,
+ nsMsgViewNotificationCode::changed);
+ m_keys.RemoveElementAt(childIndex);
+ m_levels.RemoveElementAt(childIndex);
+ m_folders.RemoveObjectAt(childIndex);
+ if (!(msgFlags & nsMsgMessageFlags::Read))
+ ChangeUnreadChildCount(-1);
+ ChangeChildCount(-1);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetRootHdr(int32_t *aResultIndex, nsIMsgDBHdr **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (aResultIndex)
+ *aResultIndex = 0;
+ return GetChildHdrAt(0, aResult);
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult)
+{
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr **aResult)
+{
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+int32_t nsMsgXFViewThread::HdrIndex(nsIMsgDBHdr *hdr)
+{
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetMessageKey(&msgKey);
+ hdr->GetFolder(getter_AddRefs(folder));
+ for (uint32_t i = 0; i < m_keys.Length(); i++)
+ {
+ if (m_keys[i] == msgKey && m_folders[i] == folder)
+ return i;
+ }
+ return -1;
+}
+
+void nsMsgXFViewThread::ChangeUnreadChildCount(int32_t delta)
+{
+ m_numUnreadChildren += delta;
+}
+
+void nsMsgXFViewThread::ChangeChildCount(int32_t delta)
+{
+ m_numChildren += delta;
+}
+
+bool nsMsgXFViewThread::IsHdrParentOf(nsIMsgDBHdr *possibleParent,
+ nsIMsgDBHdr *possibleChild)
+{
+ uint16_t referenceToCheck = 0;
+ possibleChild->GetNumReferences(&referenceToCheck);
+ nsAutoCString reference;
+
+ nsCString messageId;
+ possibleParent->GetMessageId(getter_Copies(messageId));
+
+ while (referenceToCheck > 0)
+ {
+ possibleChild->GetStringReference(referenceToCheck - 1, reference);
+
+ if (reference.Equals(messageId))
+ return true;
+ // if reference didn't match, check if this ref is for a non-existent
+ // header. If it is, continue looking at ancestors.
+ nsCOMPtr<nsIMsgDBHdr> refHdr;
+ m_view->GetMsgHdrFromHash(reference, getter_AddRefs(refHdr));
+ if (refHdr)
+ break;
+ referenceToCheck--;
+ }
+ return false;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetNewestMsgDate(uint32_t *aResult)
+{
+ // if this hasn't been set, figure it out by enumerating the msgs in the thread.
+ if (!m_newestMsgDate)
+ {
+ uint32_t numChildren;
+ nsresult rv = NS_OK;
+
+ GetNumChildren(&numChildren);
+
+ if ((int32_t) numChildren < 0)
+ numChildren = 0;
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ uint32_t msgDate;
+ child->GetDateInSeconds(&msgDate);
+ if (msgDate > m_newestMsgDate)
+ m_newestMsgDate = msgDate;
+ }
+ }
+ }
+ *aResult = m_newestMsgDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::SetNewestMsgDate(uint32_t aNewestMsgDate)
+{
+ m_newestMsgDate = aNewestMsgDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::MarkChildRead(bool aRead)
+{
+ ChangeUnreadChildCount(aRead ? -1 : 1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFViewThread::GetFirstUnreadChild(nsIMsgDBHdr **aResult)
+{
+ NS_ENSURE_ARG(aResult);
+ uint32_t numChildren;
+ nsresult rv = NS_OK;
+
+ GetNumChildren(&numChildren);
+
+ if ((int32_t) numChildren < 0)
+ numChildren = 0;
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ nsMsgKey msgKey;
+ child->GetMessageKey(&msgKey);
+
+ bool isRead;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = m_folders[childIndex]->GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv))
+ rv = db->IsRead(msgKey, &isRead);
+ if (NS_SUCCEEDED(rv) && !isRead)
+ {
+ NS_ADDREF(*aResult = child);
+ break;
+ }
+ }
+ }
+ return rv;
+}
+NS_IMETHODIMP nsMsgXFViewThread::EnumerateMessages(nsMsgKey aParentKey,
+ nsISimpleEnumerator **aResult)
+{
+ NS_ERROR("shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/mailnews/base/src/nsMsgXFViewThread.h b/mailnews/base/src/nsMsgXFViewThread.h
new file mode 100644
index 000000000..5c404afa8
--- /dev/null
+++ b/mailnews/base/src/nsMsgXFViewThread.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsMsgXFViewThread_h__
+#define nsMsgXFViewThread_h__
+
+#include "msgCore.h"
+#include "nsCOMArray.h"
+#include "nsIMsgThread.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgDBView.h"
+
+class nsMsgSearchDBView;
+
+class nsMsgXFViewThread : public nsIMsgThread
+{
+public:
+
+ nsMsgXFViewThread(nsMsgSearchDBView *view, nsMsgKey threadId);
+
+ NS_DECL_NSIMSGTHREAD
+ NS_DECL_ISUPPORTS
+
+ bool IsHdrParentOf(nsIMsgDBHdr *possibleParent,
+ nsIMsgDBHdr *possibleChild);
+
+ void ChangeUnreadChildCount(int32_t delta);
+ void ChangeChildCount(int32_t delta);
+
+ nsresult AddHdr(nsIMsgDBHdr *newHdr, bool reparentChildren,
+ uint32_t &whereInserted, nsIMsgDBHdr **outParent);
+ int32_t HdrIndex(nsIMsgDBHdr *hdr);
+ uint32_t ChildLevelAt(uint32_t msgIndex) {return m_levels[msgIndex];}
+ uint32_t MsgCount() {return m_numChildren;};
+
+protected:
+ virtual ~nsMsgXFViewThread();
+
+ nsMsgSearchDBView *m_view;
+ uint32_t m_numUnreadChildren;
+ uint32_t m_numChildren;
+ uint32_t m_flags;
+ uint32_t m_newestMsgDate;
+ nsMsgKey m_threadId;
+ nsTArray<nsMsgKey> m_keys;
+ nsCOMArray<nsIMsgFolder> m_folders;
+ nsTArray<uint8_t> m_levels;
+};
+
+#endif
diff --git a/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp b/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp
new file mode 100644
index 000000000..61dbca56e
--- /dev/null
+++ b/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp
@@ -0,0 +1,515 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgXFVirtualFolderDBView.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgThread.h"
+#include "nsQuickSort.h"
+#include "nsIDBFolderInfo.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgCopyService.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsMsgUtils.h"
+#include "nsITreeColumns.h"
+#include "nsIMsgSearchSession.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgMessageFlags.h"
+#include "nsServiceManagerUtils.h"
+
+nsMsgXFVirtualFolderDBView::nsMsgXFVirtualFolderDBView()
+{
+ mSuppressMsgDisplay = false;
+ m_doingSearch = false;
+ m_doingQuickSearch = false;
+ m_totalMessagesInView = 0;
+}
+
+nsMsgXFVirtualFolderDBView::~nsMsgXFVirtualFolderDBView()
+{
+}
+
+NS_IMETHODIMP nsMsgXFVirtualFolderDBView::Open(nsIMsgFolder *folder,
+ nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags,
+ int32_t *pCount)
+{
+ m_viewFolder = folder;
+ return nsMsgSearchDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
+}
+
+void nsMsgXFVirtualFolderDBView::RemovePendingDBListeners()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ // UnregisterPendingListener will return an error when there are no more instances
+ // of this object registered as pending listeners.
+ while (NS_SUCCEEDED(rv))
+ rv = msgDBService->UnregisterPendingListener(this);
+}
+
+NS_IMETHODIMP nsMsgXFVirtualFolderDBView::Close()
+{
+ RemovePendingDBListeners();
+ return nsMsgSearchDBView::Close();
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow,
+ nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval)
+{
+ nsMsgXFVirtualFolderDBView* newMsgDBView = new nsMsgXFVirtualFolderDBView();
+
+ if (!newMsgDBView)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance,
+ nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater)
+{
+ nsMsgSearchDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+
+ nsMsgXFVirtualFolderDBView* newMsgDBView = (nsMsgXFVirtualFolderDBView *) aNewMsgDBView;
+
+ newMsgDBView->m_viewFolder = m_viewFolder;
+ newMsgDBView->m_searchSession = m_searchSession;
+
+ int32_t scopeCount;
+ nsresult rv;
+ nsCOMPtr <nsIMsgSearchSession> searchSession =
+ do_QueryReferent(m_searchSession, &rv);
+ // It's OK not to have a search session.
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ searchSession->CountSearchScopes(&scopeCount);
+ for (int32_t i = 0; i < scopeCount; i++)
+ {
+ nsMsgSearchScopeValue scopeId;
+ nsCOMPtr<nsIMsgFolder> searchFolder;
+ searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder));
+ if (searchFolder)
+ msgDBService->RegisterPendingListener(searchFolder, newMsgDBView);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowVirtualFolderResults;
+ return NS_OK;
+}
+
+nsresult nsMsgXFVirtualFolderDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool /*ensureListed*/)
+{
+ if (newHdr)
+ {
+ bool match = false;
+ nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
+ if (searchSession)
+ searchSession->MatchHdr(newHdr, m_db, &match);
+ if (!match)
+ match = WasHdrRecentlyDeleted(newHdr);
+ if (match)
+ {
+ nsCOMPtr <nsIMsgFolder> folder;
+ newHdr->GetFolder(getter_AddRefs(folder));
+ bool saveDoingSearch = m_doingSearch;
+ m_doingSearch = false;
+ OnSearchHit(newHdr, folder);
+ m_doingSearch = saveDoingSearch;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFVirtualFolderDBView::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrChanged,
+ bool aPreChange, uint32_t *aStatus, nsIDBChangeListener *aInstigator)
+{
+ // If the junk mail plugin just activated on a message, then
+ // we'll allow filters to remove from view.
+ // Otherwise, just update the view line.
+ //
+ // Note this will not add newly matched headers to the view. This is
+ // probably a bug that needs fixing.
+
+ NS_ENSURE_ARG_POINTER(aStatus);
+ NS_ENSURE_ARG_POINTER(aHdrChanged);
+
+ nsMsgViewIndex index = FindHdr(aHdrChanged);
+ if (index == nsMsgViewIndex_None) // message does not appear in view
+ return NS_OK;
+
+ nsCString originStr;
+ (void) aHdrChanged->GetStringProperty("junkscoreorigin", getter_Copies(originStr));
+ // check for "plugin" with only first character for performance
+ bool plugin = (originStr.get()[0] == 'p');
+
+ if (aPreChange)
+ {
+ // first call, done prior to the change
+ *aStatus = plugin;
+ return NS_OK;
+ }
+
+ // second call, done after the change
+ bool wasPlugin = *aStatus;
+
+ bool match = true;
+ nsCOMPtr<nsIMsgSearchSession> searchSession(do_QueryReferent(m_searchSession));
+ if (searchSession)
+ searchSession->MatchHdr(aHdrChanged, m_db, &match);
+
+ if (!match && plugin && !wasPlugin)
+ RemoveByIndex(index); // remove hdr from view
+ else
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+
+ return NS_OK;
+}
+
+void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForFolder(nsIMsgFolder *folder, nsMsgKey *newHits, uint32_t numNewHits)
+{
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsresult rv = folder->GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db)
+ {
+ nsCString searchUri;
+ m_viewFolder->GetURI(searchUri);
+ uint32_t numBadHits;
+ nsMsgKey *badHits;
+ rv = db->RefreshCache(searchUri.get(), numNewHits, newHits,
+ &numBadHits, &badHits);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgDBHdr> badHdr;
+ for (uint32_t badHitIndex = 0; badHitIndex < numBadHits; badHitIndex++)
+ {
+ // ### of course, this isn't quite right, since we should be
+ // using FindHdr, and we shouldn't be expanding the threads.
+ db->GetMsgHdrForKey(badHits[badHitIndex], getter_AddRefs(badHdr));
+ // let nsMsgSearchDBView decide what to do about this header
+ // getting removed.
+ if (badHdr)
+ OnHdrDeleted(badHdr, nsMsgKey_None, 0, this);
+ }
+ delete [] badHits;
+ }
+ }
+}
+
+void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForPrevSearchedFolders(nsIMsgFolder *curSearchFolder)
+{
+ // Handle the most recent folder with hits, if any.
+ if (m_curFolderGettingHits)
+ {
+ uint32_t count = m_hdrHits.Count();
+ nsTArray<nsMsgKey> newHits;
+ newHits.SetLength(count);
+ for (uint32_t i = 0; i < count; i++)
+ m_hdrHits[i]->GetMessageKey(&newHits[i]);
+
+ newHits.Sort();
+ UpdateCacheAndViewForFolder(m_curFolderGettingHits, newHits.Elements(), newHits.Length());
+ m_foldersSearchingOver.RemoveObject(m_curFolderGettingHits);
+ }
+
+ while (m_foldersSearchingOver.Count() > 0)
+ {
+ // this new folder has cached hits.
+ if (m_foldersSearchingOver[0] == curSearchFolder)
+ {
+ m_curFolderHasCachedHits = true;
+ m_foldersSearchingOver.RemoveObjectAt(0);
+ break;
+ }
+ else
+ {
+ // this must be a folder that had no hits with the current search.
+ // So all cached hits, if any, need to be removed.
+ UpdateCacheAndViewForFolder(m_foldersSearchingOver[0], nullptr, 0);
+ m_foldersSearchingOver.RemoveObjectAt(0);
+ }
+ }
+}
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *aFolder)
+{
+ NS_ENSURE_ARG(aMsgHdr);
+ NS_ENSURE_ARG(aFolder);
+
+ if (m_curFolderGettingHits != aFolder && m_doingSearch && !m_doingQuickSearch)
+ {
+ m_curFolderHasCachedHits = false;
+ // since we've gotten a hit for a new folder, the searches for
+ // any previous folders are done, so deal with stale cached hits
+ // for those folders now.
+ UpdateCacheAndViewForPrevSearchedFolders(aFolder);
+ m_curFolderGettingHits = aFolder;
+ m_hdrHits.Clear();
+ m_curFolderStartKeyIndex = m_keys.Length();
+ }
+ bool hdrInCache = false;
+ if (!m_doingQuickSearch)
+ {
+ nsCOMPtr<nsIMsgDatabase> dbToUse;
+ nsCOMPtr<nsIDBFolderInfo> dummyInfo;
+ nsresult rv = aFolder->GetDBFolderInfoAndDB(getter_AddRefs(dummyInfo), getter_AddRefs(dbToUse));
+ if (NS_SUCCEEDED(rv)) {
+ nsCString searchUri;
+ m_viewFolder->GetURI(searchUri);
+ dbToUse->HdrIsInCache(searchUri.get(), aMsgHdr, &hdrInCache);
+ }
+ }
+ if (!m_doingSearch || !m_curFolderHasCachedHits || !hdrInCache)
+ {
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ nsMsgGroupView::OnNewHeader(aMsgHdr, nsMsgKey_None, true);
+ else if (m_sortValid)
+ InsertHdrFromFolder(aMsgHdr, aFolder);
+ else
+ AddHdrFromFolder(aMsgHdr, aFolder);
+ }
+ m_hdrHits.AppendObject(aMsgHdr);
+ m_totalMessagesInView++;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::OnSearchDone(nsresult status)
+{
+ NS_ENSURE_TRUE(m_viewFolder, NS_ERROR_NOT_INITIALIZED);
+
+ // handle any non verified hits we haven't handled yet.
+ if (NS_SUCCEEDED(status) && !m_doingQuickSearch && status != NS_MSG_SEARCH_INTERRUPTED)
+ UpdateCacheAndViewForPrevSearchedFolders(nullptr);
+
+ m_doingSearch = false;
+ //we want to set imap delete model once the search is over because setting next
+ //message after deletion will happen before deleting the message and search scope
+ //can change with every search.
+ mDeleteModel = nsMsgImapDeleteModels::MoveToTrash; //set to default in case it is non-imap folder
+ nsIMsgFolder *curFolder = m_folders.SafeObjectAt(0);
+ if (curFolder)
+ GetImapDeleteModel(curFolder);
+
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // count up the number of unread and total messages from the view, and set those in the
+ // folder - easier than trying to keep the count up to date in the face of
+ // search hits coming in while the user is reading/deleting messages.
+ uint32_t numUnread = 0;
+ for (uint32_t i = 0; i < m_flags.Length(); i++)
+ if (m_flags[i] & nsMsgMessageFlags::Elided)
+ {
+ nsCOMPtr<nsIMsgThread> thread;
+ GetThreadContainingIndex(i, getter_AddRefs(thread));
+ if (thread)
+ {
+ uint32_t unreadInThread;
+ thread->GetNumUnreadChildren(&unreadInThread);
+ numUnread += unreadInThread;
+ }
+ }
+ else
+ {
+ if (!(m_flags[i] & nsMsgMessageFlags::Read))
+ numUnread++;
+ }
+ dbFolderInfo->SetNumUnreadMessages(numUnread);
+ dbFolderInfo->SetNumMessages(m_totalMessagesInView);
+ m_viewFolder->UpdateSummaryTotals(true); // force update from db.
+ virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ if (!m_sortValid && m_sortType != nsMsgViewSortType::byThread &&
+ !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ {
+ m_sortValid = false; //sort the results
+ Sort(m_sortType, m_sortOrder);
+ }
+ m_foldersSearchingOver.Clear();
+ m_curFolderGettingHits = nullptr;
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::OnNewSearch()
+{
+ int32_t oldSize = GetSize();
+
+ RemovePendingDBListeners();
+ m_doingSearch = true;
+ m_totalMessagesInView = 0;
+ m_folders.Clear();
+ m_keys.Clear();
+ m_levels.Clear();
+ m_flags.Clear();
+
+ // needs to happen after we remove the keys, since RowCountChanged() will call our GetRowCount()
+ if (mTree)
+ mTree->RowCountChanged(0, -oldSize);
+
+ // to use the search results cache, we'll need to iterate over the scopes in the
+ // search session, calling getNthSearchScope for i = 0; i < searchSession.countSearchScopes; i++
+ // and for each folder, then open the db and pull out the cached hits, add them to the view.
+ // For each hit in a new folder, we'll then clean up the stale hits from the previous folder(s).
+
+ int32_t scopeCount;
+ nsCOMPtr<nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
+ NS_ENSURE_TRUE(searchSession, NS_OK); // just ignore
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID);
+ searchSession->CountSearchScopes(&scopeCount);
+
+ // Figure out how many search terms the virtual folder has.
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString terms;
+ dbFolderInfo->GetCharProperty("searchStr", terms);
+ nsCOMPtr<nsISupportsArray> searchTerms;
+ rv = searchSession->GetSearchTerms(getter_AddRefs(searchTerms));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString curSearchAsString;
+
+ rv = MsgTermListToString(searchTerms, curSearchAsString);
+ // Trim off the initial AND/OR, which is irrelevant and inconsistent between
+ // what searchSpec.js generates, and what's in virtualFolders.dat.
+ curSearchAsString.Cut(0, StringBeginsWith(curSearchAsString, NS_LITERAL_CSTRING("AND")) ? 3 : 2);
+ terms.Cut(0, StringBeginsWith(terms, NS_LITERAL_CSTRING("AND")) ? 3 : 2);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ // If the search session search string doesn't match the vf search str, then we're doing
+ // quick search, which means we don't want to invalidate cached results, or
+ // used cached results.
+ m_doingQuickSearch = !curSearchAsString.Equals(terms);
+
+ if (mTree && !m_doingQuickSearch)
+ mTree->BeginUpdateBatch();
+
+ for (int32_t i = 0; i < scopeCount; i++)
+ {
+ nsMsgSearchScopeValue scopeId;
+ nsCOMPtr<nsIMsgFolder> searchFolder;
+ searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder));
+ if (searchFolder)
+ {
+ nsCOMPtr<nsISimpleEnumerator> cachedHits;
+ nsCOMPtr<nsIMsgDatabase> searchDB;
+ nsCString searchUri;
+ m_viewFolder->GetURI(searchUri);
+ nsresult rv = searchFolder->GetMsgDatabase(getter_AddRefs(searchDB));
+ if (NS_SUCCEEDED(rv) && searchDB)
+ {
+ if (msgDBService)
+ msgDBService->RegisterPendingListener(searchFolder, this);
+
+ m_foldersSearchingOver.AppendObject(searchFolder);
+ if (m_doingQuickSearch) // ignore cached hits in quick search case.
+ continue;
+ searchDB->GetCachedHits(searchUri.get(), getter_AddRefs(cachedHits));
+ bool hasMore;
+ if (cachedHits)
+ {
+ cachedHits->HasMoreElements(&hasMore);
+ if (hasMore)
+ {
+ mozilla::DebugOnly<nsMsgKey> prevKey = nsMsgKey_None;
+ while (hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ nsresult rv = cachedHits->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports);
+ if (pHeader && NS_SUCCEEDED(rv))
+ {
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ NS_ASSERTION(prevKey == nsMsgKey_None || msgKey > prevKey,
+ "cached Hits not sorted");
+#ifdef DEBUG
+ prevKey = msgKey;
+#endif
+ AddHdrFromFolder(pHeader, searchFolder);
+ }
+ else
+ break;
+ cachedHits->HasMoreElements(&hasMore);
+ }
+ }
+ }
+ }
+ }
+ }
+ if (mTree && !m_doingQuickSearch)
+ mTree->EndUpdateBatch();
+
+ m_curFolderStartKeyIndex = 0;
+ m_curFolderGettingHits = nullptr;
+ m_curFolderHasCachedHits = false;
+
+ // if we have cached hits, sort them.
+ if (GetSize() > 0)
+ {
+ // currently, we keep threaded views sorted while we build them.
+ if (m_sortType != nsMsgViewSortType::byThread &&
+ !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ {
+ m_sortValid = false; //sort the results
+ Sort(m_sortType, m_sortOrder);
+ }
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgXFVirtualFolderDBView::DoCommand(nsMsgViewCommandTypeValue command)
+{
+ return nsMsgSearchDBView::DoCommand(command);
+}
+
+
+
+NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetMsgFolder(nsIMsgFolder **aMsgFolder)
+{
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_IF_ADDREF(*aMsgFolder = m_viewFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFVirtualFolderDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags)
+{
+ nsresult rv = NS_OK;
+ // if the grouping/threading has changed, rebuild the view
+ if ((m_viewFlags & (nsMsgViewFlagsType::kGroupBySort |
+ nsMsgViewFlagsType::kThreadedDisplay)) !=
+ (aViewFlags & (nsMsgViewFlagsType::kGroupBySort |
+ nsMsgViewFlagsType::kThreadedDisplay)))
+ rv = RebuildView(aViewFlags);
+ nsMsgDBView::SetViewFlags(aViewFlags);
+ return rv;
+}
+
+
+nsresult
+nsMsgXFVirtualFolderDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator)
+{
+ return GetViewEnumerator(enumerator);
+}
diff --git a/mailnews/base/src/nsMsgXFVirtualFolderDBView.h b/mailnews/base/src/nsMsgXFVirtualFolderDBView.h
new file mode 100644
index 000000000..77a2623d4
--- /dev/null
+++ b/mailnews/base/src/nsMsgXFVirtualFolderDBView.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgXFVirtualFolderDBView_H_
+#define _nsMsgXFVirtualFolderDBView_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgSearchDBView.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsCOMArray.h"
+
+class nsMsgGroupThread;
+
+class nsMsgXFVirtualFolderDBView : public nsMsgSearchDBView
+{
+public:
+ nsMsgXFVirtualFolderDBView();
+ virtual ~nsMsgXFVirtualFolderDBView();
+
+ // we override all the methods, currently. Might change...
+ NS_DECL_NSIMSGSEARCHNOTIFY
+
+ virtual const char * GetViewName(void) override {return "XFVirtualFolderView"; }
+ NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) override;
+ NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow,
+ nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) override;
+ NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance,
+ nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) override;
+ NS_IMETHOD Close() override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override;
+ NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue command) override;
+ NS_IMETHOD SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) override;
+ NS_IMETHOD OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus,
+ nsIDBChangeListener * aInstigator) override;
+ NS_IMETHOD GetMsgFolder(nsIMsgFolder **aMsgFolder) override;
+
+ virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey parentKey, bool ensureListed) override;
+ void UpdateCacheAndViewForPrevSearchedFolders(nsIMsgFolder *curSearchFolder);
+ void UpdateCacheAndViewForFolder(nsIMsgFolder *folder, nsMsgKey *newHits, uint32_t numNewHits);
+ void RemovePendingDBListeners();
+
+protected:
+
+ virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator) override;
+
+ uint32_t m_cachedFolderArrayIndex; // array index of next folder with cached hits to deal with.
+ nsCOMArray<nsIMsgFolder> m_foldersSearchingOver;
+ nsCOMArray<nsIMsgDBHdr> m_hdrHits;
+ nsCOMPtr <nsIMsgFolder> m_curFolderGettingHits;
+ uint32_t m_curFolderStartKeyIndex; // keeps track of the index of the first hit from the cur folder
+ bool m_curFolderHasCachedHits;
+ bool m_doingSearch;
+ // Are we doing a quick search on top of the virtual folder search?
+ bool m_doingQuickSearch;
+};
+
+#endif
diff --git a/mailnews/base/src/nsSpamSettings.cpp b/mailnews/base/src/nsSpamSettings.cpp
new file mode 100644
index 000000000..e8318b75a
--- /dev/null
+++ b/mailnews/base/src/nsSpamSettings.cpp
@@ -0,0 +1,892 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSpamSettings.h"
+#include "nsIFile.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "nsIMsgHdr.h"
+#include "nsNetUtil.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgUtils.h"
+#include "nsMsgFolderFlags.h"
+#include "nsImapCore.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIRDFService.h"
+#include "nsIRDFResource.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIStringBundle.h"
+#include "nsDateTimeFormatCID.h"
+#include "mozilla/Services.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbManager.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+
+using namespace mozilla::mailnews;
+
+nsSpamSettings::nsSpamSettings()
+{
+ mLevel = 0;
+ mMoveOnSpam = false;
+ mMoveTargetMode = nsISpamSettings::MOVE_TARGET_MODE_ACCOUNT;
+ mPurge = false;
+ mPurgeInterval = 14; // 14 days
+
+ mServerFilterTrustFlags = 0;
+
+ mUseWhiteList = false;
+ mUseServerFilter = false;
+
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mLogFile));
+ if (NS_SUCCEEDED(rv))
+ mLogFile->Append(NS_LITERAL_STRING("junklog.html"));
+}
+
+nsSpamSettings::~nsSpamSettings()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsSpamSettings, nsISpamSettings, nsIUrlListener)
+
+NS_IMETHODIMP
+nsSpamSettings::GetLevel(int32_t *aLevel)
+{
+ NS_ENSURE_ARG_POINTER(aLevel);
+ *aLevel = mLevel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetLevel(int32_t aLevel)
+{
+ NS_ASSERTION((aLevel >= 0 && aLevel <= 100), "bad level");
+ mLevel = aLevel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSpamSettings::GetMoveTargetMode(int32_t *aMoveTargetMode)
+{
+ NS_ENSURE_ARG_POINTER(aMoveTargetMode);
+ *aMoveTargetMode = mMoveTargetMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetMoveTargetMode(int32_t aMoveTargetMode)
+{
+ NS_ASSERTION((aMoveTargetMode == nsISpamSettings::MOVE_TARGET_MODE_FOLDER || aMoveTargetMode == nsISpamSettings::MOVE_TARGET_MODE_ACCOUNT), "bad move target mode");
+ mMoveTargetMode = aMoveTargetMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetManualMark(bool *aManualMark)
+{
+ NS_ENSURE_ARG_POINTER(aManualMark);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->GetBoolPref("mail.spam.manualMark", aManualMark);
+}
+
+NS_IMETHODIMP nsSpamSettings::GetManualMarkMode(int32_t *aManualMarkMode)
+{
+ NS_ENSURE_ARG_POINTER(aManualMarkMode);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->GetIntPref("mail.spam.manualMarkMode", aManualMarkMode);
+}
+
+NS_IMETHODIMP nsSpamSettings::GetLoggingEnabled(bool *aLoggingEnabled)
+{
+ NS_ENSURE_ARG_POINTER(aLoggingEnabled);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->GetBoolPref("mail.spam.logging.enabled", aLoggingEnabled);
+}
+
+NS_IMETHODIMP nsSpamSettings::GetMarkAsReadOnSpam(bool *aMarkAsReadOnSpam)
+{
+ NS_ENSURE_ARG_POINTER(aMarkAsReadOnSpam);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->GetBoolPref("mail.spam.markAsReadOnSpam", aMarkAsReadOnSpam);
+}
+
+NS_IMPL_GETSET(nsSpamSettings, MoveOnSpam, bool, mMoveOnSpam)
+NS_IMPL_GETSET(nsSpamSettings, Purge, bool, mPurge)
+NS_IMPL_GETSET(nsSpamSettings, UseWhiteList, bool, mUseWhiteList)
+NS_IMPL_GETSET(nsSpamSettings, UseServerFilter, bool, mUseServerFilter)
+
+NS_IMETHODIMP nsSpamSettings::GetWhiteListAbURI(char * *aWhiteListAbURI)
+{
+ NS_ENSURE_ARG_POINTER(aWhiteListAbURI);
+ *aWhiteListAbURI = ToNewCString(mWhiteListAbURI);
+ return NS_OK;
+}
+NS_IMETHODIMP nsSpamSettings::SetWhiteListAbURI(const char * aWhiteListAbURI)
+{
+ mWhiteListAbURI = aWhiteListAbURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetActionTargetAccount(char * *aActionTargetAccount)
+{
+ NS_ENSURE_ARG_POINTER(aActionTargetAccount);
+ *aActionTargetAccount = ToNewCString(mActionTargetAccount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetActionTargetAccount(const char * aActionTargetAccount)
+{
+ mActionTargetAccount = aActionTargetAccount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetActionTargetFolder(char * *aActionTargetFolder)
+{
+ NS_ENSURE_ARG_POINTER(aActionTargetFolder);
+ *aActionTargetFolder = ToNewCString(mActionTargetFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetActionTargetFolder(const char * aActionTargetFolder)
+{
+ mActionTargetFolder = aActionTargetFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetPurgeInterval(int32_t *aPurgeInterval)
+{
+ NS_ENSURE_ARG_POINTER(aPurgeInterval);
+ *aPurgeInterval = mPurgeInterval;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetPurgeInterval(int32_t aPurgeInterval)
+{
+ NS_ASSERTION(aPurgeInterval >= 0, "bad purge interval");
+ mPurgeInterval = aPurgeInterval;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSpamSettings::SetLogStream(nsIOutputStream *aLogStream)
+{
+ // if there is a log stream already, close it
+ if (mLogStream) {
+ // will flush
+ nsresult rv = mLogStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ mLogStream = aLogStream;
+ return NS_OK;
+}
+
+#define LOG_HEADER "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style type=\"text/css\">body{font-family:Consolas,\"Lucida Console\",Monaco,\"Courier New\",Courier,monospace;font-size:small}</style>\n</head>\n<body>\n"
+#define LOG_HEADER_LEN (strlen(LOG_HEADER))
+
+NS_IMETHODIMP
+nsSpamSettings::GetLogStream(nsIOutputStream **aLogStream)
+{
+ NS_ENSURE_ARG_POINTER(aLogStream);
+
+ nsresult rv;
+
+ if (!mLogStream) {
+ // append to the end of the log file
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mLogStream),
+ mLogFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_APPEND,
+ 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t fileSize;
+ rv = mLogFile->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // write the header at the start
+ if (fileSize == 0)
+ {
+ uint32_t writeCount;
+
+ rv = mLogStream->Write(LOG_HEADER, LOG_HEADER_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(writeCount == LOG_HEADER_LEN, "failed to write out log header");
+ }
+ }
+
+ NS_ADDREF(*aLogStream = mLogStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::Initialize(nsIMsgIncomingServer *aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+ nsresult rv;
+ int32_t spamLevel;
+ rv = aServer->GetIntValue("spamLevel", &spamLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetLevel(spamLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool moveOnSpam;
+ rv = aServer->GetBoolValue("moveOnSpam", &moveOnSpam);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetMoveOnSpam(moveOnSpam);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t moveTargetMode;
+ rv = aServer->GetIntValue("moveTargetMode", &moveTargetMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetMoveTargetMode(moveTargetMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString spamActionTargetAccount;
+ rv = aServer->GetCharValue("spamActionTargetAccount", spamActionTargetAccount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetActionTargetAccount(spamActionTargetAccount.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString spamActionTargetFolder;
+ rv = aServer->GetCharValue("spamActionTargetFolder", spamActionTargetFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetActionTargetFolder(spamActionTargetFolder.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool useWhiteList;
+ rv = aServer->GetBoolValue("useWhiteList", &useWhiteList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetUseWhiteList(useWhiteList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString whiteListAbURI;
+ rv = aServer->GetCharValue("whiteListAbURI", whiteListAbURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetWhiteListAbURI(whiteListAbURI.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool purgeSpam;
+ rv = aServer->GetBoolValue("purgeSpam", &purgeSpam);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetPurge(purgeSpam);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t purgeSpamInterval;
+ rv = aServer->GetIntValue("purgeSpamInterval", &purgeSpamInterval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetPurgeInterval(purgeSpamInterval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool useServerFilter;
+ rv = aServer->GetBoolValue("useServerFilter", &useServerFilter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetUseServerFilter(useServerFilter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString serverFilterName;
+ rv = aServer->GetCharValue("serverFilterName", serverFilterName);
+ if (NS_SUCCEEDED(rv))
+ SetServerFilterName(serverFilterName);
+ int32_t serverFilterTrustFlags = 0;
+ rv = aServer->GetIntValue("serverFilterTrustFlags", &serverFilterTrustFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetServerFilterTrustFlags(serverFilterTrustFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (prefBranch)
+ prefBranch->GetCharPref("mail.trusteddomains",
+ getter_Copies(mTrustedMailDomains));
+
+ mWhiteListDirArray.Clear();
+ if (!mWhiteListAbURI.IsEmpty())
+ {
+ nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCString> whiteListArray;
+ ParseString(mWhiteListAbURI, ' ', whiteListArray);
+
+ for (uint32_t index = 0; index < whiteListArray.Length(); index++)
+ {
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(whiteListArray[index],
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (directory)
+ mWhiteListDirArray.AppendObject(directory);
+ }
+ }
+
+ // the next two preferences affect whether we try to whitelist our own
+ // address or domain. Spammers send emails with spoofed from address matching
+ // either the email address of the recipient, or the recipient's domain,
+ // hoping to get whitelisted.
+ //
+ // The terms to describe this get wrapped up in chains of negatives. A full
+ // definition of the boolean inhibitWhiteListingIdentityUser is "Suppress address
+ // book whitelisting if the sender matches an identity's email address"
+
+ rv = aServer->GetBoolValue("inhibitWhiteListingIdentityUser",
+ &mInhibitWhiteListingIdentityUser);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aServer->GetBoolValue("inhibitWhiteListingIdentityDomain",
+ &mInhibitWhiteListingIdentityDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // collect lists of identity users if needed
+ if (mInhibitWhiteListingIdentityDomain || mInhibitWhiteListingIdentityUser)
+ {
+ nsCOMPtr<nsIMsgAccountManager>
+ accountManager(do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString accountKey;
+ if (account)
+ account->GetKey(accountKey);
+
+ // Loop through all accounts, adding emails from this account, as well as
+ // from any accounts that defer to this account.
+ mEmails.Clear();
+ nsCOMPtr<nsIArray> accounts;
+ rv = accountManager->GetAccounts(getter_AddRefs(accounts));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t accountCount = 0;
+ if (account && accounts) // no sense scanning accounts if we've nothing to match
+ accounts->GetLength(&accountCount);
+
+ for (uint32_t i = 0; i < accountCount; i++)
+ {
+ nsCOMPtr<nsIMsgAccount> loopAccount(do_QueryElementAt(accounts, i));
+ if (!loopAccount)
+ continue;
+ nsAutoCString loopAccountKey;
+ loopAccount->GetKey(loopAccountKey);
+ nsCOMPtr<nsIMsgIncomingServer> loopServer;
+ loopAccount->GetIncomingServer(getter_AddRefs(loopServer));
+ nsAutoCString deferredToAccountKey;
+ if (loopServer)
+ loopServer->GetCharValue("deferred_to_account", deferredToAccountKey);
+
+ // Add the emails for any account that defers to this one, or for the
+ // account itself.
+ if (accountKey.Equals(deferredToAccountKey) || accountKey.Equals(loopAccountKey))
+ {
+ nsCOMPtr<nsIArray> identities;
+ loopAccount->GetIdentities(getter_AddRefs(identities));
+ if (!identities)
+ continue;
+ uint32_t identityCount = 0;
+ identities->GetLength(&identityCount);
+ for (uint32_t j = 0; j < identityCount; ++j)
+ {
+ nsCOMPtr<nsIMsgIdentity> identity(do_QueryElementAt(identities, j, &rv));
+ if (NS_FAILED(rv) || !identity)
+ continue;
+ nsAutoCString email;
+ identity->GetEmail(email);
+ if (!email.IsEmpty())
+ mEmails.AppendElement(email);
+ }
+ }
+ }
+ }
+
+ return UpdateJunkFolderState();
+}
+
+nsresult nsSpamSettings::UpdateJunkFolderState()
+{
+ nsresult rv;
+
+ // if the spam folder uri changed on us, we need to unset the junk flag
+ // on the old spam folder
+ nsCString newJunkFolderURI;
+ rv = GetSpamFolderURI(getter_Copies(newJunkFolderURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mCurrentJunkFolderURI.IsEmpty() && !mCurrentJunkFolderURI.Equals(newJunkFolderURI))
+ {
+ nsCOMPtr<nsIMsgFolder> oldJunkFolder;
+ rv = GetExistingFolder(mCurrentJunkFolderURI, getter_AddRefs(oldJunkFolder));
+ if (NS_SUCCEEDED(rv) && oldJunkFolder)
+ {
+ // remove the nsMsgFolderFlags::Junk on the old junk folder
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // (in ClearFlag?) we need to make sure that this folder
+ // is not the junk folder for another account
+ // the same goes for set flag. have fun with all that.
+ oldJunkFolder->ClearFlag(nsMsgFolderFlags::Junk);
+ }
+ }
+
+ mCurrentJunkFolderURI = newJunkFolderURI;
+
+ // only try to create the junk folder if we are moving junk
+ // and we have a non-empty uri
+ if (mMoveOnSpam && !mCurrentJunkFolderURI.IsEmpty()) {
+ // as the url listener, the spam settings will set the nsMsgFolderFlags::Junk folder flag
+ // on the junk mail folder, after it is created
+ rv = GetOrCreateFolder(mCurrentJunkFolderURI, this);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsSpamSettings::Clone(nsISpamSettings *aSpamSettings)
+{
+ NS_ENSURE_ARG_POINTER(aSpamSettings);
+
+ nsresult rv = aSpamSettings->GetUseWhiteList(&mUseWhiteList);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ (void)aSpamSettings->GetMoveOnSpam(&mMoveOnSpam);
+ (void)aSpamSettings->GetPurge(&mPurge);
+ (void)aSpamSettings->GetUseServerFilter(&mUseServerFilter);
+
+ rv = aSpamSettings->GetPurgeInterval(&mPurgeInterval);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aSpamSettings->GetLevel(&mLevel);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aSpamSettings->GetMoveTargetMode(&mMoveTargetMode);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString actionTargetAccount;
+ rv = aSpamSettings->GetActionTargetAccount(getter_Copies(actionTargetAccount));
+ NS_ENSURE_SUCCESS(rv,rv);
+ mActionTargetAccount = actionTargetAccount;
+
+ nsCString actionTargetFolder;
+ rv = aSpamSettings->GetActionTargetFolder(getter_Copies(actionTargetFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ mActionTargetFolder = actionTargetFolder;
+
+ nsCString whiteListAbURI;
+ rv = aSpamSettings->GetWhiteListAbURI(getter_Copies(whiteListAbURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+ mWhiteListAbURI = whiteListAbURI;
+
+ aSpamSettings->GetServerFilterName(mServerFilterName);
+ aSpamSettings->GetServerFilterTrustFlags(&mServerFilterTrustFlags);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetSpamFolderURI(char **aSpamFolderURI)
+{
+ NS_ENSURE_ARG_POINTER(aSpamFolderURI);
+
+ if (mMoveTargetMode == nsISpamSettings::MOVE_TARGET_MODE_FOLDER)
+ return GetActionTargetFolder(aSpamFolderURI);
+
+ // if the mode is nsISpamSettings::MOVE_TARGET_MODE_ACCOUNT
+ // the spam folder URI = account uri + "/Junk"
+ nsCString folderURI;
+ nsresult rv = GetActionTargetAccount(getter_Copies(folderURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // we might be trying to get the old spam folder uri
+ // in order to clear the flag
+ // if we didn't have one, bail out.
+ if (folderURI.IsEmpty())
+ return NS_OK;
+
+ nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRDFResource> folderResource;
+ rv = rdf->GetResource(folderURI, getter_AddRefs(folderResource));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(folderResource);
+ if (!folder)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // see nsMsgFolder::SetPrettyName() for where the pretty name is set.
+
+ // Check for an existing junk folder - this will do a case-insensitive
+ // search by URI - if we find a junk folder, use its URI.
+ nsCOMPtr<nsIMsgFolder> junkFolder;
+ folderURI.Append("/Junk");
+ if (NS_SUCCEEDED(server->GetMsgFolderFromURI(nullptr, folderURI,
+ getter_AddRefs(junkFolder))) &&
+ junkFolder)
+ junkFolder->GetURI(folderURI);
+
+ // XXX todo
+ // better not to make base depend in imap
+ // but doing it here, like in nsMsgCopy.cpp
+ // one day, we'll fix this (and nsMsgCopy.cpp) to use GetMsgFolderFromURI()
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ if (imapServer) {
+ // Make sure an specific IMAP folder has correct personal namespace
+ // see bug #197043
+ nsCString folderUriWithNamespace;
+ (void)imapServer->GetUriWithNamespacePrefixIfNecessary(kPersonalNamespace, folderURI,
+ folderUriWithNamespace);
+ if (!folderUriWithNamespace.IsEmpty())
+ folderURI = folderUriWithNamespace;
+ }
+
+ *aSpamFolderURI = ToNewCString(folderURI);
+ if (!*aSpamFolderURI)
+ return NS_ERROR_OUT_OF_MEMORY;
+ else
+ return rv;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetServerFilterName(nsACString &aFilterName)
+{
+ aFilterName = mServerFilterName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetServerFilterName(const nsACString &aFilterName)
+{
+ mServerFilterName = aFilterName;
+ mServerFilterFile = nullptr; // clear out our stored location value
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetServerFilterFile(nsIFile ** aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ if (!mServerFilterFile)
+ {
+ nsresult rv;
+ nsAutoCString serverFilterFileName;
+ GetServerFilterName(serverFilterFileName);
+ serverFilterFileName.Append(".sfd");
+
+ nsCOMPtr<nsIProperties> dirSvc = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Walk through the list of isp directories
+ nsCOMPtr<nsISimpleEnumerator> ispDirectories;
+ rv = dirSvc->Get(ISP_DIRECTORY_LIST, NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(ispDirectories));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ nsCOMPtr<nsIFile> file;
+ while (NS_SUCCEEDED(ispDirectories->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> elem;
+ ispDirectories->GetNext(getter_AddRefs(elem));
+ file = do_QueryInterface(elem);
+
+ if (file)
+ {
+ // append our desired leaf name then test to see if the file exists. If it does, we've found
+ // mServerFilterFile.
+ file->AppendNative(serverFilterFileName);
+ bool exists;
+ if (NS_SUCCEEDED(file->Exists(&exists)) && exists)
+ {
+ file.swap(mServerFilterFile);
+ break;
+ }
+ } // if file
+ } // until we find the location of mServerFilterName
+ } // if we haven't already stored mServerFilterFile
+
+ NS_IF_ADDREF(*aFile = mServerFilterFile);
+ return NS_OK;
+}
+
+
+NS_IMPL_GETSET(nsSpamSettings, ServerFilterTrustFlags, int32_t, mServerFilterTrustFlags)
+
+#define LOG_ENTRY_START_TAG "<p>\n"
+#define LOG_ENTRY_START_TAG_LEN (strlen(LOG_ENTRY_START_TAG))
+#define LOG_ENTRY_END_TAG "</p>\n"
+#define LOG_ENTRY_END_TAG_LEN (strlen(LOG_ENTRY_END_TAG))
+// Does this need to be localizable?
+#define LOG_ENTRY_TIMESTAMP "[$S] "
+
+NS_IMETHODIMP nsSpamSettings::LogJunkHit(nsIMsgDBHdr *aMsgHdr, bool aMoveMessage)
+{
+ bool loggingEnabled;
+ nsresult rv = GetLoggingEnabled(&loggingEnabled);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!loggingEnabled)
+ return NS_OK;
+
+ PRTime date;
+
+ nsString authorValue;
+ nsString subjectValue;
+ nsString dateValue;
+
+ (void)aMsgHdr->GetDate(&date);
+ PRExplodedTime exploded;
+ PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded);
+
+ if (!mDateFormatter)
+ {
+ mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mDateFormatter)
+ {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort,
+ kTimeFormatSeconds, &exploded,
+ dateValue);
+
+ (void)aMsgHdr->GetMime2DecodedAuthor(authorValue);
+ (void)aMsgHdr->GetMime2DecodedSubject(subjectValue);
+
+ nsCString buffer;
+ // this is big enough to hold a log entry.
+ // do this so we avoid growing and copying as we append to the log.
+#ifdef MOZILLA_INTERNAL_API
+ buffer.SetCapacity(512);
+#endif
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/filter.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char16_t *junkLogDetectFormatStrings[3] = { authorValue.get(), subjectValue.get(), dateValue.get() };
+ nsString junkLogDetectStr;
+ rv = bundle->FormatStringFromName(
+ u"junkLogDetectStr",
+ junkLogDetectFormatStrings, 3,
+ getter_Copies(junkLogDetectStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ buffer += NS_ConvertUTF16toUTF8(junkLogDetectStr);
+ buffer += "\n";
+
+ if (aMoveMessage) {
+ nsCString msgId;
+ aMsgHdr->GetMessageId(getter_Copies(msgId));
+
+ nsCString junkFolderURI;
+ rv = GetSpamFolderURI(getter_Copies(junkFolderURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertASCIItoUTF16 msgIdValue(msgId);
+ NS_ConvertASCIItoUTF16 junkFolderURIValue(junkFolderURI);
+
+ const char16_t *logMoveFormatStrings[2] = { msgIdValue.get(), junkFolderURIValue.get() };
+ nsString logMoveStr;
+ rv = bundle->FormatStringFromName(
+ u"logMoveStr",
+ logMoveFormatStrings, 2,
+ getter_Copies(logMoveStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ buffer += NS_ConvertUTF16toUTF8(logMoveStr);
+ buffer += "\n";
+ }
+
+ return LogJunkString(buffer.get());
+}
+
+NS_IMETHODIMP nsSpamSettings::LogJunkString(const char *string)
+{
+ bool loggingEnabled;
+ nsresult rv = GetLoggingEnabled(&loggingEnabled);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!loggingEnabled)
+ return NS_OK;
+
+ nsString dateValue;
+ PRExplodedTime exploded;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+
+ if (!mDateFormatter)
+ {
+ mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mDateFormatter)
+ {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort,
+ kTimeFormatSeconds, &exploded,
+ dateValue);
+
+ nsCString timestampString(LOG_ENTRY_TIMESTAMP);
+ MsgReplaceSubstring(timestampString, "$S", NS_ConvertUTF16toUTF8(dateValue).get());
+
+ nsCOMPtr <nsIOutputStream> logStream;
+ rv = GetLogStream(getter_AddRefs(logStream));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t writeCount;
+
+ rv = logStream->Write(LOG_ENTRY_START_TAG, LOG_ENTRY_START_TAG_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ASSERTION(writeCount == LOG_ENTRY_START_TAG_LEN, "failed to write out start log tag");
+
+ rv = logStream->Write(timestampString.get(), timestampString.Length(), &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ASSERTION(writeCount == timestampString.Length(), "failed to write out timestamp");
+
+ // HTML-escape the log for security reasons.
+ // We don't want someone to send us a message with a subject with
+ // HTML tags, especially <script>.
+ char *escapedBuffer = MsgEscapeHTML(string);
+ if (!escapedBuffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ uint32_t escapedBufferLen = strlen(escapedBuffer);
+ rv = logStream->Write(escapedBuffer, escapedBufferLen, &writeCount);
+ PR_Free(escapedBuffer);
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ASSERTION(writeCount == escapedBufferLen, "failed to write out log hit");
+
+ rv = logStream->Write(LOG_ENTRY_END_TAG, LOG_ENTRY_END_TAG_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ASSERTION(writeCount == LOG_ENTRY_END_TAG_LEN, "failed to write out end log tag");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::OnStartRunningUrl(nsIURI* aURL)
+{
+ // do nothing
+ // all the action happens in OnStopRunningUrl()
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::OnStopRunningUrl(nsIURI* aURL, nsresult exitCode)
+{
+ nsCString junkFolderURI;
+ nsresult rv = GetSpamFolderURI(getter_Copies(junkFolderURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (junkFolderURI.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ // when we get here, the folder should exist.
+ nsCOMPtr <nsIMsgFolder> junkFolder;
+ rv = GetExistingFolder(junkFolderURI, getter_AddRefs(junkFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!junkFolder)
+ return NS_ERROR_UNEXPECTED;
+
+ rv = junkFolder->SetFlag(nsMsgFolderFlags::Junk);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsSpamSettings::CheckWhiteList(nsIMsgDBHdr *aMsgHdr, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false; // default in case of error or no whitelisting
+
+ if (!mUseWhiteList || (!mWhiteListDirArray.Count() &&
+ mTrustedMailDomains.IsEmpty()))
+ return NS_OK;
+
+ // do per-message processing
+
+ nsCString author;
+ aMsgHdr->GetAuthor(getter_Copies(author));
+
+ nsAutoCString authorEmailAddress;
+ ExtractEmail(EncodedHeader(author), authorEmailAddress);
+
+ if (authorEmailAddress.IsEmpty())
+ return NS_OK;
+
+ // should we skip whitelisting for the identity email?
+ if (mInhibitWhiteListingIdentityUser)
+ {
+ for (uint32_t i = 0; i < mEmails.Length(); ++i)
+ {
+ if (mEmails[i].Equals(authorEmailAddress, nsCaseInsensitiveCStringComparator()))
+ return NS_OK;
+ }
+ }
+
+ if (!mTrustedMailDomains.IsEmpty() || mInhibitWhiteListingIdentityDomain)
+ {
+ nsAutoCString domain;
+ int32_t atPos = authorEmailAddress.FindChar('@');
+ if (atPos >= 0)
+ domain = Substring(authorEmailAddress, atPos + 1);
+ if (!domain.IsEmpty())
+ {
+ if (!mTrustedMailDomains.IsEmpty() &&
+ MsgHostDomainIsTrusted(domain, mTrustedMailDomains))
+ {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ if (mInhibitWhiteListingIdentityDomain)
+ {
+ for (uint32_t i = 0; i < mEmails.Length(); ++i)
+ {
+ nsAutoCString identityDomain;
+ int32_t atPos = mEmails[i].FindChar('@');
+ if (atPos >= 0)
+ {
+ identityDomain = Substring(mEmails[i], atPos + 1);
+ if (identityDomain.Equals(domain, nsCaseInsensitiveCStringComparator()))
+ return NS_OK; // don't whitelist
+ }
+ }
+ }
+ }
+ }
+
+ if (mWhiteListDirArray.Count())
+ {
+ nsCOMPtr<nsIAbCard> cardForAddress;
+ for (int32_t index = 0;
+ index < mWhiteListDirArray.Count() && !cardForAddress;
+ index++)
+ {
+ mWhiteListDirArray[index]->CardForEmailAddress(authorEmailAddress,
+ getter_AddRefs(cardForAddress));
+ }
+ if (cardForAddress)
+ {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+ return NS_OK; // default return is false
+}
diff --git a/mailnews/base/src/nsSpamSettings.h b/mailnews/base/src/nsSpamSettings.h
new file mode 100644
index 000000000..9432a1835
--- /dev/null
+++ b/mailnews/base/src/nsSpamSettings.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSpamSettings_h__
+#define nsSpamSettings_h__
+
+#include "nsCOMPtr.h"
+#include "nsISpamSettings.h"
+#include "nsStringGlue.h"
+#include "nsIOutputStream.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIUrlListener.h"
+#include "nsIDateTimeFormat.h"
+#include "nsCOMArray.h"
+#include "nsIAbDirectory.h"
+#include "nsTArray.h"
+
+class nsSpamSettings : public nsISpamSettings, public nsIUrlListener
+{
+public:
+ nsSpamSettings();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISPAMSETTINGS
+ NS_DECL_NSIURLLISTENER
+
+private:
+ virtual ~nsSpamSettings();
+
+ nsCOMPtr <nsIOutputStream> mLogStream;
+ nsCOMPtr<nsIFile> mLogFile;
+
+ int32_t mLevel;
+ int32_t mPurgeInterval;
+ int32_t mMoveTargetMode;
+
+ bool mPurge;
+ bool mUseWhiteList;
+ bool mMoveOnSpam;
+ bool mUseServerFilter;
+
+ nsCString mActionTargetAccount;
+ nsCString mActionTargetFolder;
+ nsCString mWhiteListAbURI;
+ nsCString mCurrentJunkFolderURI; // used to detect changes to the spam folder in ::initialize
+
+ nsCString mServerFilterName;
+ nsCOMPtr<nsIFile> mServerFilterFile;
+ int32_t mServerFilterTrustFlags;
+
+ nsCOMPtr<nsIDateTimeFormat> mDateFormatter;
+
+ // array of address directories to use in junk whitelisting
+ nsCOMArray<nsIAbDirectory> mWhiteListDirArray;
+ // mail domains to use in junk whitelisting
+ nsCString mTrustedMailDomains;
+ // should we inhibit whitelisting address of identity?
+ bool mInhibitWhiteListingIdentityUser;
+ // should we inhibit whitelisting domain of identity?
+ bool mInhibitWhiteListingIdentityDomain;
+ // email addresses associated with this server
+ nsTArray<nsCString> mEmails;
+
+ // helper routine used by Initialize which unsets the junk flag on the previous junk folder
+ // for this account, and sets it on the new junk folder.
+ nsresult UpdateJunkFolderState();
+};
+
+#endif /* nsSpamSettings_h__ */
diff --git a/mailnews/base/src/nsStatusBarBiffManager.cpp b/mailnews/base/src/nsStatusBarBiffManager.cpp
new file mode 100644
index 000000000..27f393986
--- /dev/null
+++ b/mailnews/base/src/nsStatusBarBiffManager.cpp
@@ -0,0 +1,238 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStatusBarBiffManager.h"
+#include "nsMsgBiffManager.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsIObserverService.h"
+#include "nsIWindowMediator.h"
+#include "nsIMsgMailSession.h"
+#include "MailNewsTypes.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsIFileChannel.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsIFile.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+// QueryInterface, AddRef, and Release
+//
+NS_IMPL_ISUPPORTS(nsStatusBarBiffManager, nsIStatusBarBiffManager, nsIFolderListener, nsIObserver)
+
+nsIAtom * nsStatusBarBiffManager::kBiffStateAtom = nullptr;
+
+nsStatusBarBiffManager::nsStatusBarBiffManager()
+: mInitialized(false), mCurrentBiffState(nsIMsgFolder::nsMsgBiffState_Unknown)
+{
+}
+
+nsStatusBarBiffManager::~nsStatusBarBiffManager()
+{
+ NS_IF_RELEASE(kBiffStateAtom);
+}
+
+#define NEW_MAIL_PREF_BRANCH "mail.biff."
+#define FEED_PREF_BRANCH "mail.feed."
+#define PREF_PLAY_SOUND "play_sound"
+#define PREF_SOUND_URL "play_sound.url"
+#define PREF_SOUND_TYPE "play_sound.type"
+#define SYSTEM_SOUND_TYPE 0
+#define CUSTOM_SOUND_TYPE 1
+
+nsresult nsStatusBarBiffManager::Init()
+{
+ if (mInitialized)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ nsresult rv;
+
+ kBiffStateAtom = MsgNewAtom("BiffState").take();
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ if(NS_SUCCEEDED(rv))
+ mailSession->AddFolderListener(this, nsIFolderListener::intPropertyChanged);
+
+ nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsStatusBarBiffManager::PlayBiffSound(const char *aPrefBranch)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefSvc = (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrefBranch> pref;
+ rv = prefSvc->GetBranch(aPrefBranch, getter_AddRefs(pref));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool playSound;
+ if (mServerType.EqualsLiteral("rss")) {
+ nsCOMPtr<nsIPrefBranch> prefFeed;
+ rv = prefSvc->GetBranch(FEED_PREF_BRANCH, getter_AddRefs(prefFeed));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = prefFeed->GetBoolPref(PREF_PLAY_SOUND, &playSound);
+ }
+ else {
+ rv = pref->GetBoolPref(PREF_PLAY_SOUND, &playSound);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!playSound)
+ return NS_OK;
+
+ // lazily create the sound instance
+ if (!mSound)
+ mSound = do_CreateInstance("@mozilla.org/sound;1");
+
+ int32_t soundType = SYSTEM_SOUND_TYPE;
+ rv = pref->GetIntPref(PREF_SOUND_TYPE, &soundType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool customSoundPlayed = false;
+
+ if (soundType == CUSTOM_SOUND_TYPE) {
+ nsCString soundURLSpec;
+ rv = pref->GetCharPref(PREF_SOUND_URL, getter_Copies(soundURLSpec));
+
+ if (NS_SUCCEEDED(rv) && !soundURLSpec.IsEmpty()) {
+ if (!strncmp(soundURLSpec.get(), "file://", 7)) {
+ nsCOMPtr<nsIURI> fileURI;
+ rv = NS_NewURI(getter_AddRefs(fileURI), soundURLSpec);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIFileURL> soundURL = do_QueryInterface(fileURI,&rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> soundFile;
+ rv = soundURL->GetFile(getter_AddRefs(soundFile));
+ if (NS_SUCCEEDED(rv)) {
+ bool soundFileExists = false;
+ rv = soundFile->Exists(&soundFileExists);
+ if (NS_SUCCEEDED(rv) && soundFileExists) {
+ rv = mSound->Play(soundURL);
+ if (NS_SUCCEEDED(rv))
+ customSoundPlayed = true;
+ }
+ }
+ }
+ }
+ else {
+ // todo, see if we can create a nsIFile using the string as a native path.
+ // if that fails, try playing a system sound
+ NS_ConvertUTF8toUTF16 utf16SoundURLSpec(soundURLSpec);
+ rv = mSound->PlaySystemSound(utf16SoundURLSpec);
+ if (NS_SUCCEEDED(rv))
+ customSoundPlayed = true;
+ }
+ }
+ }
+#ifndef XP_MACOSX
+ // if nothing played, play the default system sound
+ if (!customSoundPlayed) {
+ rv = mSound->PlayEventSound(nsISound::EVENT_NEW_MAIL_RECEIVED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#endif
+ return rv;
+}
+
+// nsIFolderListener methods....
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnItemPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char *oldValue, const char *newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnItemIntPropertyChanged(nsIMsgFolder *item, nsIAtom *property, int64_t oldValue, int64_t newValue)
+{
+ if (kBiffStateAtom == property && mCurrentBiffState != newValue) {
+ // if we got new mail, attempt to play a sound.
+ // if we fail along the way, don't return.
+ // we still need to update the UI.
+ if (newValue == nsIMsgFolder::nsMsgBiffState_NewMail) {
+ // Get the folder's server type.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = item->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ server->GetType(mServerType);
+
+ // if we fail to play the biff sound, keep going.
+ (void)PlayBiffSound(NEW_MAIL_PREF_BRANCH);
+ }
+ mCurrentBiffState = newValue;
+
+ // don't care if notification fails
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService)
+ observerService->NotifyObservers(static_cast<nsIStatusBarBiffManager*>(this), "mail:biff-state-changed", nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnItemBoolPropertyChanged(nsIMsgFolder *item, nsIAtom *property, bool oldValue, bool newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnItemUnicharPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char16_t *oldValue, const char16_t *newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnItemEvent(nsIMsgFolder *item, nsIAtom *event)
+{
+ return NS_OK;
+}
+
+// nsIObserver implementation
+NS_IMETHODIMP
+nsStatusBarBiffManager::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ return NS_OK;
+}
+
+// nsIStatusBarBiffManager method....
+NS_IMETHODIMP
+nsStatusBarBiffManager::GetBiffState(int32_t *aBiffState)
+{
+ NS_ENSURE_ARG_POINTER(aBiffState);
+ *aBiffState = mCurrentBiffState;
+ return NS_OK;
+}
+
diff --git a/mailnews/base/src/nsStatusBarBiffManager.h b/mailnews/base/src/nsStatusBarBiffManager.h
new file mode 100644
index 000000000..cb85742e3
--- /dev/null
+++ b/mailnews/base/src/nsStatusBarBiffManager.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStatusBarBiffManager_h__
+#define nsStatusBarBiffManager_h__
+
+#include "nsIStatusBarBiffManager.h"
+
+#include "msgCore.h"
+#include "nsCOMPtr.h"
+#include "nsISound.h"
+#include "nsIObserver.h"
+
+class nsStatusBarBiffManager : public nsIStatusBarBiffManager,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFOLDERLISTENER
+ NS_DECL_NSISTATUSBARBIFFMANAGER
+ NS_DECL_NSIOBSERVER
+
+ nsStatusBarBiffManager();
+ nsresult Init();
+
+private:
+ virtual ~nsStatusBarBiffManager();
+
+ bool mInitialized;
+ int32_t mCurrentBiffState;
+ nsCString mServerType;
+ nsCOMPtr<nsISound> mSound;
+ nsresult PlayBiffSound(const char *aPrefBranch);
+
+protected:
+ static nsIAtom* kBiffStateAtom;
+};
+
+
+
+#endif // nsStatusBarBiffManager_h__
+
diff --git a/mailnews/base/src/nsSubscribableServer.cpp b/mailnews/base/src/nsSubscribableServer.cpp
new file mode 100644
index 000000000..80f9c9b64
--- /dev/null
+++ b/mailnews/base/src/nsSubscribableServer.cpp
@@ -0,0 +1,805 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSubscribableServer.h"
+#include "nsIMsgIncomingServer.h"
+#include "prmem.h"
+#include "rdf.h"
+#include "nsRDFCID.h"
+#include "nsIServiceManager.h"
+#include "nsMsgI18N.h"
+#include "nsMsgUtils.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsServiceManagerUtils.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+nsSubscribableServer::nsSubscribableServer(void)
+{
+ mDelimiter = '.';
+ mShowFullName = true;
+ mTreeRoot = nullptr;
+ mStopped = false;
+}
+
+nsresult
+nsSubscribableServer::Init()
+{
+ nsresult rv;
+
+ rv = EnsureRDFService();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"),
+ getter_AddRefs(kNC_Child));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Subscribed"),
+ getter_AddRefs(kNC_Subscribed));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetLiteral(u"true", getter_AddRefs(kTrueLiteral));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetLiteral(u"false", getter_AddRefs(kFalseLiteral));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+nsSubscribableServer::~nsSubscribableServer(void)
+{
+ mozilla::DebugOnly<nsresult> rv = FreeSubtree(mTreeRoot);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to free tree");
+}
+
+NS_IMPL_ISUPPORTS(nsSubscribableServer, nsISubscribableServer)
+
+NS_IMETHODIMP
+nsSubscribableServer::SetIncomingServer(nsIMsgIncomingServer *aServer)
+{
+ if (!aServer) {
+ mIncomingServerUri.AssignLiteral("");
+ return NS_OK;
+ }
+
+ // We intentionally do not store a pointer to the aServer here
+ // as it would create reference loops, because nsIImapIncomingServer
+ // and nsINntpIncomingServer keep a reference to an internal
+ // nsISubscribableServer object.
+ // We only need the URI of the server anyway.
+ return aServer->GetServerURI(mIncomingServerUri);
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetDelimiter(char *aDelimiter)
+{
+ if (!aDelimiter) return NS_ERROR_NULL_POINTER;
+ *aDelimiter = mDelimiter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetDelimiter(char aDelimiter)
+{
+ mDelimiter = aDelimiter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetAsSubscribed(const nsACString &path)
+{
+ nsresult rv = NS_OK;
+
+ SubscribeTreeNode *node = nullptr;
+ rv = FindAndCreateNode(path, &node);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(node,"didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ node->isSubscribable = true;
+ node->isSubscribed = true;
+
+ rv = NotifyChange(node, kNC_Subscribed, node->isSubscribed);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::AddTo(const nsACString& aName, bool aAddAsSubscribed,
+ bool aSubscribable, bool aChangeIfExists)
+{
+ nsresult rv = NS_OK;
+
+ if (mStopped) {
+#ifdef DEBUG_seth
+ printf("stopped!\n");
+#endif
+ return NS_ERROR_FAILURE;
+ }
+
+ SubscribeTreeNode *node = nullptr;
+
+ // todo, shouldn't we pass in aAddAsSubscribed, for the
+ // default value if we create it?
+ rv = FindAndCreateNode(aName, &node);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(node,"didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ if (aChangeIfExists) {
+ node->isSubscribed = aAddAsSubscribed;
+ rv = NotifyChange(node, kNC_Subscribed, node->isSubscribed);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ node->isSubscribable = aSubscribable;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetState(const nsACString &aPath, bool aState,
+ bool *aStateChanged)
+{
+ nsresult rv = NS_OK;
+ NS_ASSERTION(!aPath.IsEmpty() && aStateChanged, "no path or stateChanged");
+ if (aPath.IsEmpty() || !aStateChanged) return NS_ERROR_NULL_POINTER;
+
+ NS_ASSERTION(MsgIsUTF8(aPath), "aPath is not in UTF-8");
+
+ *aStateChanged = false;
+
+ SubscribeTreeNode *node = nullptr;
+ rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(node,"didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ NS_ASSERTION(node->isSubscribable, "fix this");
+ if (!node->isSubscribable) {
+ return NS_OK;
+ }
+
+ if (node->isSubscribed == aState) {
+ return NS_OK;
+ }
+ else {
+ node->isSubscribed = aState;
+ *aStateChanged = true;
+ rv = NotifyChange(node, kNC_Subscribed, node->isSubscribed);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ return rv;
+}
+
+void
+nsSubscribableServer::BuildURIFromNode(SubscribeTreeNode *node, nsACString &uri)
+{
+ if (node->parent) {
+ BuildURIFromNode(node->parent, uri);
+ if (node->parent == mTreeRoot) {
+ uri += "/";
+ }
+ else {
+ uri += mDelimiter;
+ }
+ }
+
+ uri += node->name;
+ return;
+}
+
+nsresult
+nsSubscribableServer::NotifyAssert(SubscribeTreeNode *subjectNode, nsIRDFResource *property, SubscribeTreeNode *objectNode)
+{
+ nsresult rv;
+
+ bool hasObservers = true;
+ rv = EnsureSubscribeDS();
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mSubscribeDS->GetHasObservers(&hasObservers);
+ NS_ENSURE_SUCCESS(rv,rv);
+ // no need to do all this work, there are no observers
+ if (!hasObservers) {
+ return NS_OK;
+ }
+
+ nsAutoCString subjectUri;
+ BuildURIFromNode(subjectNode, subjectUri);
+
+ // we could optimize this, since we know that objectUri == subjectUri + mDelimiter + object->name
+ // is it worth it?
+ nsAutoCString objectUri;
+ BuildURIFromNode(objectNode, objectUri);
+
+ nsCOMPtr <nsIRDFResource> subject;
+ nsCOMPtr <nsIRDFResource> object;
+
+ rv = EnsureRDFService();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetResource(subjectUri, getter_AddRefs(subject));
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mRDFService->GetResource(objectUri, getter_AddRefs(object));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = Notify(subject, property, object, true, false);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+nsresult
+nsSubscribableServer::EnsureRDFService()
+{
+ nsresult rv;
+
+ if (!mRDFService) {
+ mRDFService = do_GetService(kRDFServiceCID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv) && mRDFService, "failed to get rdf service");
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!mRDFService) return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsSubscribableServer::NotifyChange(SubscribeTreeNode *subjectNode, nsIRDFResource *property, bool value)
+{
+ nsresult rv;
+ nsCOMPtr <nsIRDFResource> subject;
+
+ bool hasObservers = true;
+ rv = EnsureSubscribeDS();
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mSubscribeDS->GetHasObservers(&hasObservers);
+ NS_ENSURE_SUCCESS(rv,rv);
+ // no need to do all this work, there are no observers
+ if (!hasObservers) {
+ return NS_OK;
+ }
+
+ nsAutoCString subjectUri;
+ BuildURIFromNode(subjectNode, subjectUri);
+
+ rv = EnsureRDFService();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetResource(subjectUri, getter_AddRefs(subject));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (value) {
+ rv = Notify(subject,property,kTrueLiteral,false,true);
+ }
+ else {
+ rv = Notify(subject,property,kFalseLiteral,false,true);
+ }
+
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+nsresult
+nsSubscribableServer::EnsureSubscribeDS()
+{
+ nsresult rv = NS_OK;
+
+ if (!mSubscribeDS) {
+ nsCOMPtr<nsIRDFDataSource> ds;
+
+ rv = EnsureRDFService();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetDataSource("rdf:subscribe", getter_AddRefs(ds));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!ds) return NS_ERROR_FAILURE;
+
+ mSubscribeDS = do_QueryInterface(ds, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!mSubscribeDS) return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsSubscribableServer::Notify(nsIRDFResource *subject, nsIRDFResource *property, nsIRDFNode *object, bool isAssert, bool isChange)
+{
+ nsresult rv = NS_OK;
+
+ rv = EnsureSubscribeDS();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mSubscribeDS->NotifyObservers(subject, property, object, isAssert, isChange);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetSubscribeListener(nsISubscribeListener *aListener)
+{
+ mSubscribeListener = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetSubscribeListener(nsISubscribeListener **aListener)
+{
+ if (!aListener) return NS_ERROR_NULL_POINTER;
+ if (mSubscribeListener) {
+ *aListener = mSubscribeListener;
+ NS_ADDREF(*aListener);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SubscribeCleanup()
+{
+ NS_ASSERTION(false,"override this.");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::StartPopulatingWithUri(nsIMsgWindow *aMsgWindow, bool aForceToServer, const char *uri)
+{
+ mStopped = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::StartPopulating(nsIMsgWindow *aMsgWindow, bool aForceToServer, bool aGetOnlyNew /*ignored*/)
+{
+ nsresult rv = NS_OK;
+
+ mStopped = false;
+
+ rv = FreeSubtree(mTreeRoot);
+ mTreeRoot = nullptr;
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::StopPopulating(nsIMsgWindow *aMsgWindow)
+{
+ mStopped = true;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsSubscribableServer::UpdateSubscribed()
+{
+ NS_ASSERTION(false,"override this.");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::Subscribe(const char16_t *aName)
+{
+ NS_ASSERTION(false,"override this.");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::Unsubscribe(const char16_t *aName)
+{
+ NS_ASSERTION(false,"override this.");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetShowFullName(bool showFullName)
+{
+ mShowFullName = showFullName;
+ return NS_OK;
+}
+
+nsresult
+nsSubscribableServer::FreeSubtree(SubscribeTreeNode *node)
+{
+ nsresult rv = NS_OK;
+
+ if (node) {
+ // recursively free the children
+ if (node->firstChild) {
+ // will free node->firstChild
+ rv = FreeSubtree(node->firstChild);
+ NS_ENSURE_SUCCESS(rv,rv);
+ node->firstChild = nullptr;
+ }
+
+ // recursively free the siblings
+ if (node->nextSibling) {
+ // will free node->nextSibling
+ rv = FreeSubtree(node->nextSibling);
+ NS_ENSURE_SUCCESS(rv,rv);
+ node->nextSibling = nullptr;
+ }
+
+#ifdef HAVE_SUBSCRIBE_DESCRIPTION
+ NS_ASSERTION(node->description == nullptr, "you need to free the description");
+#endif
+ NS_Free(node->name);
+#if 0
+ node->name = nullptr;
+ node->parent = nullptr;
+ node->lastChild = nullptr;
+ node->cachedChild = nullptr;
+#endif
+
+ PR_Free(node);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsSubscribableServer::CreateNode(SubscribeTreeNode *parent, const char *name, SubscribeTreeNode **result)
+{
+ NS_ASSERTION(result && name, "result or name is null");
+ if (!result || !name) return NS_ERROR_NULL_POINTER;
+
+ *result = (SubscribeTreeNode *) PR_Malloc(sizeof(SubscribeTreeNode));
+ if (!*result) return NS_ERROR_OUT_OF_MEMORY;
+
+ (*result)->name = strdup(name);
+ if (!(*result)->name) return NS_ERROR_OUT_OF_MEMORY;
+
+ (*result)->parent = parent;
+ (*result)->prevSibling = nullptr;
+ (*result)->nextSibling = nullptr;
+ (*result)->firstChild = nullptr;
+ (*result)->lastChild = nullptr;
+ (*result)->isSubscribed = false;
+ (*result)->isSubscribable = false;
+#ifdef HAVE_SUBSCRIBE_DESCRIPTION
+ (*result)->description = nullptr;
+#endif
+#ifdef HAVE_SUBSCRIBE_MESSAGES
+ (*result)->messages = 0;
+#endif
+ (*result)->cachedChild = nullptr;
+
+ if (parent) {
+ parent->cachedChild = *result;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsSubscribableServer::AddChildNode(SubscribeTreeNode *parent, const char *name, SubscribeTreeNode **child)
+{
+ nsresult rv = NS_OK;
+ NS_ASSERTION(parent && child && name, "parent, child or name is null");
+ if (!parent || !child || !name) return NS_ERROR_NULL_POINTER;
+
+ if (!parent->firstChild) {
+ // CreateNode will set the parent->cachedChild
+ rv = CreateNode(parent, name, child);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ parent->firstChild = *child;
+ parent->lastChild = *child;
+
+ rv = NotifyAssert(parent, kNC_Child, *child);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_OK;
+ }
+ else {
+ if (parent->cachedChild) {
+ if (strcmp(parent->cachedChild->name,name) == 0) {
+ *child = parent->cachedChild;
+ return NS_OK;
+ }
+ }
+
+ SubscribeTreeNode *current = parent->firstChild;
+
+ /*
+ * insert in reverse alphabetical order
+ * this will reduce the # of strcmps
+ * since this is faster assuming:
+ * 1) the hostinfo.dat feeds us the groups in alphabetical order
+ * since we control the hostinfo.dat file, we can guarantee this.
+ * 2) the server gives us the groups in alphabetical order
+ * we can't guarantee this, but it seems to be a common thing
+ *
+ * because we have firstChild, lastChild, nextSibling, prevSibling
+ * we can efficiently reverse the order when dumping to hostinfo.dat
+ * or to GetTargets()
+ */
+ int32_t compare = strcmp(current->name, name);
+
+ while (current && (compare != 0)) {
+ if (compare < 0) {
+ // CreateNode will set the parent->cachedChild
+ rv = CreateNode(parent, name, child);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ (*child)->nextSibling = current;
+ (*child)->prevSibling = current->prevSibling;
+ current->prevSibling = (*child);
+ if (!(*child)->prevSibling) {
+ parent->firstChild = (*child);
+ }
+ else {
+ (*child)->prevSibling->nextSibling = (*child);
+ }
+
+ rv = NotifyAssert(parent, kNC_Child, *child);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+ }
+ current = current->nextSibling;
+ if (current) {
+ NS_ASSERTION(current->name, "no name!");
+ compare = strcmp(current->name,name);
+ }
+ else {
+ compare = -1; // anything but 0, since that would be a match
+ }
+ }
+
+ if (compare == 0) {
+ // already exists;
+ *child = current;
+
+ // set the cachedChild
+ parent->cachedChild = *child;
+ return NS_OK;
+ }
+
+ // CreateNode will set the parent->cachedChild
+ rv = CreateNode(parent, name, child);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ (*child)->prevSibling = parent->lastChild;
+ (*child)->nextSibling = nullptr;
+ parent->lastChild->nextSibling = *child;
+ parent->lastChild = *child;
+
+ rv = NotifyAssert(parent, kNC_Child, *child);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsSubscribableServer::FindAndCreateNode(const nsACString &aPath,
+ SubscribeTreeNode **aResult)
+{
+ nsresult rv = NS_OK;
+ NS_ASSERTION(aResult, "no result");
+ if (!aResult) return NS_ERROR_NULL_POINTER;
+
+ if (!mTreeRoot) {
+ // the root has no parent, and its name is server uri
+ rv = CreateNode(nullptr, mIncomingServerUri.get(), &mTreeRoot);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (aPath.IsEmpty()) {
+ *aResult = mTreeRoot;
+ return NS_OK;
+ }
+
+ char *token = nullptr;
+ nsCString pathStr(aPath);
+ char *rest = pathStr.BeginWriting();
+
+ // todo do this only once
+ char delimstr[2];
+ delimstr[0] = mDelimiter;
+ delimstr[1] = '\0';
+
+ *aResult = nullptr;
+
+ SubscribeTreeNode *parent = mTreeRoot;
+ SubscribeTreeNode *child = nullptr;
+
+ token = NS_strtok(delimstr, &rest);
+ // special case paths that start with the hierarchy delimiter.
+ // We want to include that delimiter in the first token name.
+ if (token && pathStr[0] == mDelimiter)
+ --token;
+ while (token && *token) {
+ rv = AddChildNode(parent, token, &child);
+ if (NS_FAILED(rv))
+ return rv;
+ token = NS_strtok(delimstr, &rest);
+ parent = child;
+ }
+
+ // the last child we add is the result
+ *aResult = child;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::HasChildren(const nsACString &aPath, bool *aHasChildren)
+{
+ nsresult rv = NS_OK;
+ NS_ASSERTION(aHasChildren, "no hasChildren");
+ if (!aHasChildren) return NS_ERROR_NULL_POINTER;
+
+ *aHasChildren = false;
+
+ SubscribeTreeNode *node = nullptr;
+ rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(node,"didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ *aHasChildren = (node->firstChild != nullptr);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsSubscribableServer::IsSubscribed(const nsACString &aPath,
+ bool *aIsSubscribed)
+{
+ NS_ENSURE_ARG_POINTER(aIsSubscribed);
+
+ *aIsSubscribed = false;
+
+ SubscribeTreeNode *node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(node,"didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ *aIsSubscribed = node->isSubscribed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::IsSubscribable(const nsACString &aPath,
+ bool *aIsSubscribable)
+{
+ NS_ENSURE_ARG_POINTER(aIsSubscribable);
+
+ *aIsSubscribable = false;
+
+ SubscribeTreeNode *node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(node,"didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ *aIsSubscribable = node->isSubscribable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetLeafName(const nsACString &aPath, nsAString &aLeafName)
+{
+ SubscribeTreeNode *node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(node,"didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ // XXX TODO FIXME
+ // I'm assuming that mShowFullName is true for NNTP, false for IMAP.
+ // for imap, the node name is in modified UTF7
+ // for news, the path is escaped UTF8
+ //
+ // when we switch to using the tree, this hack will go away.
+ if (mShowFullName) {
+ return NS_MsgDecodeUnescapeURLPath(aPath, aLeafName);
+ }
+
+ return CopyMUTF7toUTF16(nsDependentCString(node->name), aLeafName);
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetFirstChildURI(const nsACString &aPath,
+ nsACString &aResult)
+{
+ aResult.Truncate();
+
+ SubscribeTreeNode *node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(node,"didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ // no children
+ if (!node->firstChild) return NS_ERROR_FAILURE;
+
+ BuildURIFromNode(node->firstChild, aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetChildren(const nsACString &aPath,
+ nsISimpleEnumerator **aResult)
+{
+ SubscribeTreeNode *node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(node,"didn't find the node");
+ if (!node)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString uriPrefix;
+ NS_ASSERTION(mTreeRoot, "no tree root!");
+ if (!mTreeRoot)
+ return NS_ERROR_UNEXPECTED;
+
+ uriPrefix = mTreeRoot->name; // the root's name is the server uri
+ uriPrefix += "/";
+ if (!aPath.IsEmpty()) {
+ uriPrefix += aPath;
+ uriPrefix += mDelimiter;
+ }
+
+ // we inserted them in reverse alphabetical order.
+ // so pull them out in reverse to get the right order
+ // in the subscribe dialog
+ SubscribeTreeNode *current = node->lastChild;
+ // return failure if there are no children.
+ if (!current)
+ return NS_ERROR_FAILURE;
+
+ nsCOMArray<nsIRDFResource> result;
+
+ while (current) {
+ nsAutoCString uri;
+ uri = uriPrefix;
+ NS_ASSERTION(current->name, "no name");
+ if (!current->name)
+ return NS_ERROR_FAILURE;
+
+ uri += current->name;
+
+ nsCOMPtr <nsIRDFResource> res;
+ rv = EnsureRDFService();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // todo, is this creating nsMsgFolders?
+ mRDFService->GetResource(uri, getter_AddRefs(res));
+ result.AppendObject(res);
+
+ current = current->prevSibling;
+ }
+
+ return NS_NewArrayEnumerator(aResult, result);
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::CommitSubscribeChanges()
+{
+ NS_ASSERTION(false,"override this.");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetSearchValue(const nsAString &aSearchValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetSupportsSubscribeSearch(bool *retVal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/mailnews/base/src/nsSubscribableServer.h b/mailnews/base/src/nsSubscribableServer.h
new file mode 100644
index 000000000..cbbac78e5
--- /dev/null
+++ b/mailnews/base/src/nsSubscribableServer.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSubscribableServer_h__
+#define nsSubscribableServer_h__
+
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsISubscribableServer.h"
+#include "nsIRDFService.h"
+#include "nsSubscribeDataSource.h"
+#include "nsIRDFResource.h"
+
+typedef struct _subscribeTreeNode {
+ char *name;
+ bool isSubscribed;
+ struct _subscribeTreeNode *prevSibling;
+ struct _subscribeTreeNode *nextSibling;
+ struct _subscribeTreeNode *firstChild;
+ struct _subscribeTreeNode *lastChild;
+ struct _subscribeTreeNode *parent;
+ struct _subscribeTreeNode *cachedChild;
+#ifdef HAVE_SUBSCRIBE_DESCRIPTION
+ char16_t *description;
+#endif
+#ifdef HAVE_SUBSCRIBE_MESSAGES
+ uint32_t messages;
+#endif
+ bool isSubscribable;
+} SubscribeTreeNode;
+
+#if defined(DEBUG_sspitzer) || defined(DEBUG_seth)
+#define DEBUG_SUBSCRIBE 1
+#endif
+
+class nsSubscribableServer : public nsISubscribableServer
+{
+ public:
+ nsSubscribableServer();
+
+ nsresult Init();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISUBSCRIBABLESERVER
+
+private:
+ virtual ~nsSubscribableServer();
+
+ nsresult ConvertNameToUnichar(const char *inStr, char16_t **outStr);
+ nsCOMPtr <nsISubscribeListener> mSubscribeListener;
+ nsCString mIncomingServerUri;
+ nsCOMPtr <nsISubscribeDataSource> mSubscribeDS;
+ char mDelimiter;
+ bool mShowFullName;
+ bool mStopped;
+
+ nsCOMPtr <nsIRDFResource> kNC_Child;
+ nsCOMPtr <nsIRDFResource> kNC_Subscribed;
+ nsCOMPtr <nsIRDFLiteral> kTrueLiteral;
+ nsCOMPtr <nsIRDFLiteral> kFalseLiteral;
+
+ nsCOMPtr <nsIRDFService> mRDFService;
+
+ SubscribeTreeNode *mTreeRoot;
+ nsresult FreeSubtree(SubscribeTreeNode *node);
+ nsresult CreateNode(SubscribeTreeNode *parent, const char *name, SubscribeTreeNode **result);
+ nsresult AddChildNode(SubscribeTreeNode *parent, const char *name, SubscribeTreeNode **child);
+ nsresult FindAndCreateNode(const nsACString &aPath,
+ SubscribeTreeNode **aResult);
+ nsresult NotifyAssert(SubscribeTreeNode *subjectNode, nsIRDFResource *property, SubscribeTreeNode *objectNode);
+ nsresult NotifyChange(SubscribeTreeNode *subjectNode, nsIRDFResource *property, bool value);
+ nsresult Notify(nsIRDFResource *subject, nsIRDFResource *property, nsIRDFNode *object, bool isAssert, bool isChange);
+ void BuildURIFromNode(SubscribeTreeNode *node, nsACString &uri);
+ nsresult EnsureSubscribeDS();
+ nsresult EnsureRDFService();
+};
+
+#endif // nsSubscribableServer_h__
diff --git a/mailnews/base/src/nsSubscribeDataSource.cpp b/mailnews/base/src/nsSubscribeDataSource.cpp
new file mode 100644
index 000000000..fe13cffca
--- /dev/null
+++ b/mailnews/base/src/nsSubscribeDataSource.cpp
@@ -0,0 +1,673 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSubscribeDataSource.h"
+
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsIComponentManager.h"
+#include "rdf.h"
+#include "nsIServiceManager.h"
+#include "nsEnumeratorUtils.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+nsSubscribeDataSource::nsSubscribeDataSource()
+{
+}
+
+nsSubscribeDataSource::~nsSubscribeDataSource()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsSubscribeDataSource, nsIRDFDataSource, nsISubscribeDataSource)
+
+nsresult
+nsSubscribeDataSource::Init()
+{
+ nsresult rv;
+
+ mRDFService = do_GetService(kRDFServiceCID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv) && mRDFService, "failed to get rdf service");
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!mRDFService) return NS_ERROR_FAILURE;
+
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"),
+ getter_AddRefs(kNC_Child));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"),
+ getter_AddRefs(kNC_Name));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "LeafName"),
+ getter_AddRefs(kNC_LeafName));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Subscribed"),
+ getter_AddRefs(kNC_Subscribed));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Subscribable"),
+ getter_AddRefs(kNC_Subscribable));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "ServerType"),
+ getter_AddRefs(kNC_ServerType));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetLiteral(u"true", getter_AddRefs(kTrueLiteral));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mRDFService->GetLiteral(u"false", getter_AddRefs(kFalseLiteral));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::GetURI(char * *aURI)
+{
+ if ((*aURI = strdup("rdf:subscribe")) == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ else
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::GetSource(nsIRDFResource *property, nsIRDFNode *target, bool tv, nsIRDFResource **source)
+{
+ NS_PRECONDITION(property != nullptr, "null ptr");
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(target != nullptr, "null ptr");
+ if (! target)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ *source = nullptr;
+ return NS_RDF_NO_VALUE;
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::GetTarget(nsIRDFResource *source,
+ nsIRDFResource *property,
+ bool tv,
+ nsIRDFNode **target /* out */)
+{
+ nsresult rv = NS_RDF_NO_VALUE;
+
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(property != nullptr, "null ptr");
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(target != nullptr, "null ptr");
+ if (! target)
+ return NS_ERROR_NULL_POINTER;
+
+ *target = nullptr;
+
+ // we only have positive assertions in the subscribe data source.
+ if (! tv) return NS_RDF_NO_VALUE;
+
+ nsCOMPtr<nsISubscribableServer> server;
+ nsCString relativePath;
+ rv = GetServerAndRelativePathFromResource(source, getter_AddRefs(server), getter_Copies(relativePath));
+ if (NS_FAILED(rv) || !server)
+ return NS_RDF_NO_VALUE;
+
+ if (property == kNC_Name.get()) {
+ nsCOMPtr<nsIRDFLiteral> name;
+ rv = mRDFService->GetLiteral(NS_ConvertUTF8toUTF16(relativePath).get(),
+ getter_AddRefs(name));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!name) rv = NS_RDF_NO_VALUE;
+ if (rv == NS_RDF_NO_VALUE) return(rv);
+ return name->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+ else if (property == kNC_Child.get()) {
+ nsCString childUri;
+ rv = server->GetFirstChildURI(relativePath, childUri);
+ if (NS_FAILED(rv)) return NS_RDF_NO_VALUE;
+ if (childUri.IsEmpty()) return NS_RDF_NO_VALUE;
+
+ nsCOMPtr <nsIRDFResource> childResource;
+ rv = mRDFService->GetResource(childUri, getter_AddRefs(childResource));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return childResource->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+ else if (property == kNC_Subscribed.get()) {
+ bool isSubscribed;
+ rv = server->IsSubscribed(relativePath, &isSubscribed);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*target = (isSubscribed ? kTrueLiteral : kFalseLiteral));
+ return NS_OK;
+ }
+ else if (property == kNC_Subscribable.get()) {
+ bool isSubscribable;
+ rv = server->IsSubscribable(relativePath, &isSubscribable);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*target = (isSubscribable ? kTrueLiteral : kFalseLiteral));
+ return NS_OK;
+ }
+ else if (property == kNC_ServerType.get()) {
+ nsCString serverTypeStr;
+ rv = GetServerType(server, serverTypeStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIRDFLiteral> serverType;
+ rv = mRDFService->GetLiteral(NS_ConvertASCIItoUTF16(serverTypeStr).get(),
+ getter_AddRefs(serverType));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!serverType)
+ rv = NS_RDF_NO_VALUE;
+ if (rv == NS_RDF_NO_VALUE)
+ return rv;
+ return serverType->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+ else if (property == kNC_LeafName.get()) {
+ nsString leafNameStr;
+ rv = server->GetLeafName(relativePath, leafNameStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIRDFLiteral> leafName;
+ rv = mRDFService->GetLiteral(leafNameStr.get(), getter_AddRefs(leafName));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!leafName)
+ rv = NS_RDF_NO_VALUE;
+ if (rv == NS_RDF_NO_VALUE)
+ return rv;
+ return leafName->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+ else {
+ // do nothing
+ }
+
+ return(NS_RDF_NO_VALUE);
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::GetTargets(nsIRDFResource *source,
+ nsIRDFResource *property,
+ bool tv,
+ nsISimpleEnumerator **targets /* out */)
+{
+ nsresult rv = NS_OK;
+
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(property != nullptr, "null ptr");
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(targets != nullptr, "null ptr");
+ if (! targets)
+ return NS_ERROR_NULL_POINTER;
+
+ *targets = nullptr;
+
+ // we only have positive assertions in the subscribe data source.
+ if (!tv) return NS_RDF_NO_VALUE;
+
+ nsCOMPtr<nsISubscribableServer> server;
+ nsCString relativePath; // UTF-8
+
+ rv = GetServerAndRelativePathFromResource(source, getter_AddRefs(server), getter_Copies(relativePath));
+ if (NS_FAILED(rv) || !server) {
+ return NS_NewEmptyEnumerator(targets);
+ }
+
+ if (property == kNC_Child.get()) {
+ rv = server->GetChildren(relativePath, targets);
+ if (NS_FAILED(rv)) {
+ return NS_NewEmptyEnumerator(targets);
+ }
+ return rv;
+ }
+ else if (property == kNC_LeafName.get()) {
+ nsString leafNameStr;
+ rv = server->GetLeafName(relativePath, leafNameStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIRDFLiteral> leafName;
+ rv = mRDFService->GetLiteral(leafNameStr.get(), getter_AddRefs(leafName));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_NewSingletonEnumerator(targets, leafName);
+ }
+ else if (property == kNC_Subscribed.get()) {
+ bool isSubscribed;
+ rv = server->IsSubscribed(relativePath, &isSubscribed);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_NewSingletonEnumerator(targets,
+ isSubscribed ? kTrueLiteral : kFalseLiteral);
+ }
+ else if (property == kNC_Subscribable.get()) {
+ bool isSubscribable;
+ rv = server->IsSubscribable(relativePath, &isSubscribable);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_NewSingletonEnumerator(targets,
+ isSubscribable ? kTrueLiteral : kFalseLiteral);
+ }
+ else if (property == kNC_Name.get()) {
+ nsCOMPtr<nsIRDFLiteral> name;
+ rv = mRDFService->GetLiteral(NS_ConvertUTF8toUTF16(relativePath).get(),
+ getter_AddRefs(name));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_NewSingletonEnumerator(targets, name);
+ }
+ else if (property == kNC_ServerType.get()) {
+ nsCString serverTypeStr;
+ rv = GetServerType(server, serverTypeStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIRDFLiteral> serverType;
+ rv = mRDFService->GetLiteral(NS_ConvertASCIItoUTF16(serverTypeStr).get(),
+ getter_AddRefs(serverType));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_NewSingletonEnumerator(targets, serverType);
+ }
+ else {
+ // do nothing
+ }
+
+ return NS_NewEmptyEnumerator(targets);
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::Assert(nsIRDFResource *source,
+ nsIRDFResource *property,
+ nsIRDFNode *target,
+ bool tv)
+{
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::Unassert(nsIRDFResource *source,
+ nsIRDFResource *property,
+ nsIRDFNode *target)
+{
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::Change(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget)
+{
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::Move(nsIRDFResource* aOldSource,
+ nsIRDFResource* aNewSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+nsresult
+nsSubscribeDataSource::GetServerType(nsISubscribableServer *server, nsACString& serverType)
+{
+ NS_ENSURE_ARG_POINTER(server);
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer(do_QueryInterface(server, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return incomingServer->GetType(serverType);
+}
+
+nsresult
+nsSubscribeDataSource::GetServerAndRelativePathFromResource(nsIRDFResource *source, nsISubscribableServer **server, char **relativePath)
+{
+ nsresult rv = NS_OK;
+
+ const char *sourceURI = nullptr;
+ rv = source->GetValueConst(&sourceURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv));
+ // we expect this to fail sometimes, so don't assert
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = folder->GetServer(getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = incomingServer->QueryInterface(NS_GET_IID(nsISubscribableServer), (void**)server);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString serverURI;
+ rv = incomingServer->GetServerURI(serverURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t serverURILen = serverURI.Length();
+ if (serverURILen == strlen(sourceURI))
+ *relativePath = nullptr;
+ else {
+ // XXX : perhaps, have to unescape before returning
+ *relativePath = strdup(sourceURI + serverURILen + 1);
+ if (!*relativePath)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::HasAssertion(nsIRDFResource *source,
+ nsIRDFResource *property,
+ nsIRDFNode *target,
+ bool tv,
+ bool *hasAssertion /* out */)
+{
+ nsresult rv = NS_OK;
+
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(property != nullptr, "null ptr");
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(target != nullptr, "null ptr");
+ if (! target)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(hasAssertion != nullptr, "null ptr");
+ if (! hasAssertion)
+ return NS_ERROR_NULL_POINTER;
+
+ *hasAssertion = false;
+
+ // we only have positive assertions in the subscribe data source.
+ if (!tv) return NS_OK;
+
+ if (property == kNC_Child.get()) {
+ nsCOMPtr<nsISubscribableServer> server;
+ nsCString relativePath;
+
+ rv = GetServerAndRelativePathFromResource(source, getter_AddRefs(server), getter_Copies(relativePath));
+ if (NS_FAILED(rv) || !server) {
+ *hasAssertion = false;
+ return NS_OK;
+ }
+
+ // not everything has children
+ rv = server->HasChildren(relativePath, hasAssertion);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else if (property == kNC_Name.get()) {
+ // everything has a name
+ *hasAssertion = true;
+ }
+ else if (property == kNC_LeafName.get()) {
+ // everything has a leaf name
+ *hasAssertion = true;
+ }
+ else if (property == kNC_Subscribed.get()) {
+ // everything is subscribed or not
+ *hasAssertion = true;
+ }
+ else if (property == kNC_Subscribable.get()) {
+ // everything is subscribable or not
+ *hasAssertion = true;
+ }
+ else if (property == kNC_ServerType.get()) {
+ // everything has a server type
+ *hasAssertion = true;
+ }
+ else {
+ // do nothing
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::HasArcOut(nsIRDFResource *source, nsIRDFResource *aArc, bool *result)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsISubscribableServer> server;
+ nsCString relativePath;
+
+ if (aArc == kNC_Child.get()) {
+ rv = GetServerAndRelativePathFromResource(source, getter_AddRefs(server), getter_Copies(relativePath));
+ if (NS_FAILED(rv) || !server) {
+ *result = false;
+ return NS_OK;
+ }
+
+ bool hasChildren = false;
+ rv = server->HasChildren(relativePath, &hasChildren);
+ NS_ENSURE_SUCCESS(rv,rv);
+ *result = hasChildren;
+ return NS_OK;
+ }
+ else if ((aArc == kNC_Subscribed.get()) ||
+ (aArc == kNC_Subscribable.get()) ||
+ (aArc == kNC_LeafName.get()) ||
+ (aArc == kNC_ServerType.get()) ||
+ (aArc == kNC_Name.get())) {
+ *result = true;
+ return NS_OK;
+ }
+
+ *result = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::ArcLabelsIn(nsIRDFNode *node,
+ nsISimpleEnumerator ** labels /* out */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::ArcLabelsOut(nsIRDFResource *source,
+ nsISimpleEnumerator **labels /* out */)
+{
+ nsresult rv = NS_OK;
+
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(labels != nullptr, "null ptr");
+ if (! labels)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsISubscribableServer> server;
+ nsCString relativePath;
+
+ rv = GetServerAndRelativePathFromResource(source, getter_AddRefs(server), getter_Copies(relativePath));
+ if (NS_FAILED(rv) || !server) {
+ return NS_NewEmptyEnumerator(labels);
+ }
+
+ bool hasChildren = false;
+ rv = server->HasChildren(relativePath, &hasChildren);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Initialise with the number of items below, to save reallocating on each
+ // addition.
+ nsCOMArray<nsIRDFResource> array(hasChildren ? 6 : 5);
+
+ array.AppendObject(kNC_Subscribed);
+ array.AppendObject(kNC_Subscribable);
+ array.AppendObject(kNC_Name);
+ array.AppendObject(kNC_ServerType);
+ array.AppendObject(kNC_LeafName);
+
+ if (hasChildren) {
+ array.AppendObject(kNC_Child);
+ }
+
+ return NS_NewArrayEnumerator(labels, array);
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::GetAllResources(nsISimpleEnumerator** aCursor)
+{
+ NS_NOTYETIMPLEMENTED("sorry!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::AddObserver(nsIRDFObserver *n)
+{
+ NS_ENSURE_ARG_POINTER(n);
+ mObservers.AppendElement(n);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::RemoveObserver(nsIRDFObserver *n)
+{
+ NS_ENSURE_ARG_POINTER(n);
+ mObservers.RemoveElement(n);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::GetHasObservers(bool *hasObservers)
+{
+ NS_ENSURE_ARG_POINTER(hasObservers);
+ *hasObservers = !mObservers.IsEmpty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::GetAllCmds(nsIRDFResource* source,
+ nsISimpleEnumerator/*<nsIRDFResource>*/** commands)
+{
+ return(NS_NewEmptyEnumerator(commands));
+}
+
+NS_IMETHODIMP
+nsSubscribeDataSource::IsCommandEnabled(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments,
+ bool* aResult)
+{
+ return(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::DoCommand(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments)
+{
+ return(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::BeginUpdateBatch()
+{
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::EndUpdateBatch()
+{
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+nsSubscribeDataSource::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsISimpleEnumerator **_retval)
+{
+ NS_ASSERTION(false, "Not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#define NOTIFY_SUBSCRIBE_LISTENERS(propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ { \
+ nsTObserverArray<nsCOMPtr<nsIRDFObserver> >::ForwardIterator iter(mObservers); \
+ while (iter.HasMore()) \
+ { \
+ iter.GetNext()->propertyfunc_ params_; \
+ } \
+ } \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsSubscribeDataSource::NotifyObservers(nsIRDFResource *subject,
+ nsIRDFResource *property,
+ nsIRDFNode *object,
+ bool assert, bool change)
+{
+ NS_ASSERTION(!(change && assert),
+ "Can't change and assert at the same time!\n");
+
+ if (change)
+ NOTIFY_SUBSCRIBE_LISTENERS(OnChange, (this, subject, property, nullptr, object));
+ else if (assert)
+ NOTIFY_SUBSCRIBE_LISTENERS(OnAssert, (this, subject, property, object));
+ else
+ NOTIFY_SUBSCRIBE_LISTENERS(OnUnassert, (this, subject, property, object));
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsSubscribeDataSource.h b/mailnews/base/src/nsSubscribeDataSource.h
new file mode 100644
index 000000000..bfe72c4c4
--- /dev/null
+++ b/mailnews/base/src/nsSubscribeDataSource.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSubscribeDataSource_h__
+#define nsSubscribeDataSource_h__
+
+#include "nsIRDFService.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFResource.h"
+#include "nsIRDFLiteral.h"
+#include "nsCOMPtr.h"
+#include "nsISubscribableServer.h"
+#include "nsTObserverArray.h"
+
+/**
+ * The subscribe data source.
+ */
+class nsSubscribeDataSource : public nsIRDFDataSource, public nsISubscribeDataSource
+{
+
+public:
+ nsSubscribeDataSource();
+
+ nsresult Init();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRDFDATASOURCE
+ NS_DECL_NSISUBSCRIBEDATASOURCE
+
+private:
+ virtual ~nsSubscribeDataSource();
+ nsCOMPtr <nsIRDFResource> kNC_Child;
+ nsCOMPtr <nsIRDFResource> kNC_Name;
+ nsCOMPtr <nsIRDFResource> kNC_LeafName;
+ nsCOMPtr <nsIRDFResource> kNC_Subscribed;
+ nsCOMPtr <nsIRDFResource> kNC_Subscribable;
+ nsCOMPtr <nsIRDFResource> kNC_ServerType;
+ nsCOMPtr <nsIRDFLiteral> kTrueLiteral;
+ nsCOMPtr <nsIRDFLiteral> kFalseLiteral;
+
+ nsCOMPtr <nsIRDFService> mRDFService;
+ nsTObserverArray<nsCOMPtr<nsIRDFObserver> > mObservers;
+
+ nsresult GetServerAndRelativePathFromResource(nsIRDFResource *source, nsISubscribableServer **server, char **relativePath);
+ nsresult GetServerType(nsISubscribableServer *server, nsACString& serverType);
+};
+
+#endif /* nsSubscribedDataSource_h__ */
diff --git a/mailnews/base/src/virtualFolderWrapper.js b/mailnews/base/src/virtualFolderWrapper.js
new file mode 100644
index 000000000..efb4ed68a
--- /dev/null
+++ b/mailnews/base/src/virtualFolderWrapper.js
@@ -0,0 +1,255 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Wrap everything about virtual folders.
+ */
+
+this.EXPORTED_SYMBOLS = ['VirtualFolderHelper'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource:///modules/iteratorUtils.jsm");
+
+var VirtualFolderHelper = {
+ /**
+ * Create a new virtual folder (an actual nsIMsgFolder that did not previously
+ * exist), wrapping it in a VirtualFolderWrapper, and returning that wrapper.
+ *
+ * If the call to addSubfolder fails (and therefore throws), we will NOT catch
+ * it.
+ *
+ * @param aFolderName The name of the new folder to create.
+ * @param aParentFolder The folder in which to create the search folder.
+ * @param aSearchFolders A list of nsIMsgFolders that you want to use as the
+ * sources for the virtual folder OR a string that is the already '|'
+ * delimited list of folder URIs to use.
+ * @param aSearchTerms The search terms to use for the virtual folder. This
+ * should be a JS list/nsIMutableArray/nsISupportsArray of
+ * nsIMsgSearchTermbs.
+ * @param aOnlineSearch Should the search attempt to use the server's search
+ * capabilities when possible and appropriate?
+ *
+ * @return The VirtualFolderWrapper wrapping the newly created folder. You
+ * would probably only want this for its virtualFolder attribute which has
+ * the nsIMsgFolder we created. Be careful about accessing any of the
+ * other attributes, as they will bring its message database back to life.
+ */
+ createNewVirtualFolder: function (aFolderName, aParentFolder, aSearchFolders,
+ aSearchTerms, aOnlineSearch) {
+ let msgFolder = aParentFolder.addSubfolder(aFolderName);
+ msgFolder.prettyName = aFolderName;
+ msgFolder.setFlag(Ci.nsMsgFolderFlags.Virtual);
+
+ let wrappedVirt = new VirtualFolderWrapper(msgFolder);
+ wrappedVirt.searchTerms = aSearchTerms;
+ wrappedVirt.searchFolders = aSearchFolders;
+ wrappedVirt.onlineSearch = aOnlineSearch;
+
+ let msgDatabase = msgFolder.msgDatabase;
+ msgDatabase.summaryValid = true;
+ msgDatabase.Close(true);
+
+ aParentFolder.NotifyItemAdded(msgFolder);
+ MailServices.accounts.saveVirtualFolders();
+
+ return wrappedVirt;
+ },
+
+ /**
+ * Given an existing nsIMsgFolder that is a virtual folder, wrap it into a
+ * VirtualFolderWrapper.
+ */
+ wrapVirtualFolder: function (aMsgFolder) {
+ return new VirtualFolderWrapper(aMsgFolder);
+ },
+};
+
+/**
+ * Abstracts dealing with the properties of a virtual folder that differentiate
+ * it from a non-virtual folder. A virtual folder is an odd duck. When
+ * holding an nsIMsgFolder that is a virtual folder, it is distinguished by
+ * the virtual flag and a number of properties that tell us the string
+ * representation of its search, the folders it searches over, and whether we
+ * use online searching or not.
+ * Virtual folders and their defining attributes are loaded from
+ * virtualFolders.dat (in the profile directory) by the account manager at
+ * startup, (re-)creating them if need be. It also saves them back to the
+ * file at shutdown. The most important thing the account manager does is to
+ * create VirtualFolderChangeListener instances that are registered with the
+ * message database service. This means that if one of the databases for the
+ * folders that the virtual folder includes is opened for some reason (for
+ * example, new messages are added to the folder because of a filter or they
+ * are delivered there), the virtual folder gets a chance to know about this
+ * and update the virtual folder's "cache" of information, such as the message
+ * counts or the presence of the message in the folder.
+ * The odd part is that a lot of the virtual folder logic also happens as a
+ * result of the nsMsgDBView subclasses being told the search query and the
+ * underlying folders. This makes for an odd collaboration of UI and backend
+ * logic.
+ *
+ * Justification for this class: Virtual folders aren't all that complex, but
+ * they are complex enough that we don't want to have the same code duplicated
+ * all over the place. We also don't want to have a loose assembly of global
+ * functions for working with them. So here we are.
+ *
+ * Important! Accessing any of our attributes results in the message database
+ * being loaded so that we can access the dBFolderInfo associated with the
+ * database. The message database is not automatically forgotten by the
+ * folder, which can lead to an (effective) memory leak. Please make sure
+ * that you are playing your part in not leaking memory by only using the
+ * wrapper when you have a serious need to access the database, and by
+ * forcing the folder to forget about the database when you are done by
+ * setting the database to null (unless you know with confidence someone else
+ * definitely wants the database around and will clean it up.)
+ */
+function VirtualFolderWrapper(aVirtualFolder) {
+ this.virtualFolder = aVirtualFolder;
+};
+VirtualFolderWrapper.prototype = {
+ /**
+ * @return the list of nsIMsgFolders that this virtual folder is a
+ * search over.
+ */
+ get searchFolders() {
+ let rdfService = Cc['@mozilla.org/rdf/rdf-service;1']
+ .getService(Ci.nsIRDFService);
+ let virtualFolderUris =
+ this.dbFolderInfo.getCharProperty("searchFolderUri").split("|");
+ let folders = [];
+ for (let folderURI of virtualFolderUris) {
+ if (folderURI)
+ folders.push(rdfService.GetResource(folderURI)
+ .QueryInterface(Ci.nsIMsgFolder));
+ }
+ return folders;
+ },
+ /**
+ * Set the search folders that back this virtual folder.
+ *
+ * @param aFolders Either a "|"-delimited string of folder URIs or a list of
+ * nsIMsgFolders that fixIterator can traverse (JS array/nsIMutableArray/
+ * nsISupportsArray).
+ */
+ set searchFolders(aFolders) {
+ if (typeof(aFolders) == "string") {
+ this.dbFolderInfo.setCharProperty("searchFolderUri", aFolders);
+ }
+ else {
+ let uris = Array.from(fixIterator(aFolders, Ci.nsIMsgFolder))
+ .map(folder => folder.URI);
+ this.dbFolderInfo.setCharProperty("searchFolderUri", uris.join("|"));
+ }
+ },
+
+ /**
+ * @return a "|"-delimited string containing the URIs of the folders that back
+ * this virtual folder.
+ */
+ get searchFolderURIs() {
+ return this.dbFolderInfo.getCharProperty("searchFolderUri");
+ },
+
+ /**
+ * @return the list of search terms that define this virtual folder.
+ */
+ get searchTerms() {
+ return this.searchTermsSession.searchTerms;
+ },
+ /**
+ * @return a newly created filter with the search terms loaded into it that
+ * define this virtual folder. The filter is apparently useful as an
+ * nsIMsgSearchSession stand-in to some code.
+ */
+ get searchTermsSession() {
+ // Temporary means it doesn't get exposed to the UI and doesn't get saved to
+ // disk. Which is good, because this is just a trick to parse the string
+ // into search terms.
+ let filterList = MailServices.filters.getTempFilterList(this.virtualFolder);
+ let tempFilter = filterList.createFilter("temp");
+ filterList.parseCondition(tempFilter, this.searchString);
+ return tempFilter;
+ },
+
+ /**
+ * Set the search string for this virtual folder to the stringified version of
+ * the provided list of nsIMsgSearchTerm search terms. If you already have
+ * a strinigified version of the search constraint, just set |searchString|
+ * directly.
+ *
+ * @param aTerms Some collection that fixIterator can traverse. A JS list or
+ * XPCOM array (nsIMutableArray or nsISupportsArray) should work.
+ */
+ set searchTerms(aTerms) {
+ let condition = "";
+ for (let term in fixIterator(aTerms, Ci.nsIMsgSearchTerm)) {
+ if (condition.length)
+ condition += " ";
+ if (term.matchAll) {
+ condition = "ALL";
+ break;
+ }
+ condition += (term.booleanAnd) ? "AND (" : "OR (";
+ condition += term.termAsString + ")";
+ }
+ this.searchString = condition;
+ },
+
+ /**
+ * @return the set of search terms that define this virtual folder as a
+ * string. You may prefer to use |searchTerms| which converts them
+ * into a list of nsIMsgSearchTerms instead.
+ */
+ get searchString() {
+ return this.dbFolderInfo.getCharProperty("searchStr");
+ },
+ /**
+ * Set the search that defines this virtual folder from a string. If you have
+ * a list of nsIMsgSearchTerms, you should use |searchTerms| instead.
+ */
+ set searchString(aSearchString) {
+ this.dbFolderInfo.setCharProperty("searchStr", aSearchString);
+ },
+
+ /**
+ * @return whether the virtual folder is configured for online search.
+ */
+ get onlineSearch() {
+ return this.dbFolderInfo.getBooleanProperty("searchOnline", false);
+ },
+ /**
+ * Set whether the virtual folder is configured for online search.
+ */
+ set onlineSearch(aOnlineSearch) {
+ this.dbFolderInfo.setBooleanProperty("searchOnline", aOnlineSearch);
+ },
+
+ /**
+ * @return the dBFolderInfo associated with the virtual folder directly. May
+ * be null. Will cause the message database to be opened, which may have
+ * memory bloat/leak ramifications, so make sure the folder's database was
+ * already going to be opened anyways or that you call
+ * |cleanUpMessageDatabase|.
+ */
+ get dbFolderInfo() {
+ let msgDatabase = this.virtualFolder.msgDatabase;
+ return (msgDatabase && msgDatabase.dBFolderInfo);
+ },
+
+ /**
+ * Avoid memory bloat by making the virtual folder forget about its database.
+ * If the database is actually in use (read: someone is keeping it alive by
+ * having references to it from places other than the nsIMsgFolder), the
+ * folder will be able to re-establish the reference for minimal cost.
+ */
+ cleanUpMessageDatabase:
+ function VirtualFolderWrapper_cleanUpMessageDatabase() {
+ this.virtualFolder.msgDatabase.Close(true);
+ this.virtualFolder.msgDatabase = null;
+ }
+};
diff --git a/mailnews/base/util/ABQueryUtils.jsm b/mailnews/base/util/ABQueryUtils.jsm
new file mode 100644
index 000000000..d4b694033
--- /dev/null
+++ b/mailnews/base/util/ABQueryUtils.jsm
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains helper methods for dealing with addressbook search URIs.
+ */
+
+this.EXPORTED_SYMBOLS = ["getSearchTokens", "getModelQuery",
+ "modelQueryHasUserValue", "generateQueryURI",
+ "encodeABTermValue"];
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Parse the multiword search string to extract individual search terms
+ * (separated on the basis of spaces) or quoted exact phrases to search
+ * against multiple fields of the addressbook cards.
+ *
+ * @param aSearchString The full search string entered by the user.
+ *
+ * @return an array of separated search terms from the full search string.
+ */
+function getSearchTokens(aSearchString) {
+ let searchString = aSearchString.trim();
+ if (searchString == "")
+ return [];
+
+ let quotedTerms = [];
+
+ // Split up multiple search words to create a *foo* and *bar* search against
+ // search fields, using the OR-search template from modelQuery for each word.
+ // If the search query has quoted terms as "foo bar", extract them as is.
+ let startIndex;
+ while ((startIndex = searchString.indexOf('"')) != -1) {
+ let endIndex = searchString.indexOf('"', startIndex + 1);
+ if (endIndex == -1)
+ endIndex = searchString.length;
+
+ quotedTerms.push(searchString.substring(startIndex + 1, endIndex));
+ let query = searchString.substring(0, startIndex);
+ if (endIndex < searchString.length)
+ query += searchString.substr(endIndex + 1);
+
+ searchString = query.trim();
+ }
+
+ let searchWords = [];
+ if (searchString.length != 0) {
+ searchWords = quotedTerms.concat(searchString.split(/\s+/));
+ } else {
+ searchWords = quotedTerms;
+ }
+
+ return searchWords;
+}
+
+/**
+ * For AB quicksearch or recipient autocomplete, get the normal or phonetic model
+ * query URL part from prefs, allowing users to customize these searches.
+ * @param aBasePrefName the full pref name of default, non-phonetic model query,
+ * e.g. mail.addr_book.quicksearchquery.format
+ * If phonetic search is used, corresponding pref must exist:
+ * e.g. mail.addr_book.quicksearchquery.format.phonetic
+ * @return depending on mail.addr_book.show_phonetic_fields pref,
+ * the value of aBasePrefName or aBasePrefName + ".phonetic"
+ */
+function getModelQuery(aBasePrefName) {
+ let modelQuery = "";
+ if (Services.prefs.getComplexValue("mail.addr_book.show_phonetic_fields",
+ Components.interfaces.nsIPrefLocalizedString).data == "true") {
+ modelQuery = Services.prefs.getCharPref(aBasePrefName + ".phonetic");
+ } else {
+ modelQuery = Services.prefs.getCharPref(aBasePrefName);
+ }
+ // remove leading "?" to migrate existing customized values for mail.addr_book.quicksearchquery.format
+ // todo: could this be done in a once-off migration at install time to avoid repetitive calls?
+ if (modelQuery.startsWith("?"))
+ modelQuery = modelQuery.slice(1);
+ return modelQuery;
+}
+
+/**
+ * Check if the currently used pref with the model query was customized by user.
+ * @param aBasePrefName the full pref name of default, non-phonetic model query,
+ * e.g. mail.addr_book.quicksearchquery.format
+ * If phonetic search is used, corresponding pref must exist:
+ * e.g. mail.addr_book.quicksearchquery.format.phonetic
+ * @return true or false
+ */
+function modelQueryHasUserValue(aBasePrefName) {
+ if (Services.prefs.getComplexValue("mail.addr_book.show_phonetic_fields",
+ Components.interfaces.nsIPrefLocalizedString).data == "true")
+ return Services.prefs.prefHasUserValue(aBasePrefName + ".phonetic");
+ return Services.prefs.prefHasUserValue(aBasePrefName);
+}
+
+/*
+ * Given a database model query and a list of search tokens,
+ * return query URI.
+ *
+ * @param aModelQuery database model query
+ * @param aSearchWords an array of search tokens.
+ *
+ * @return query URI.
+ */
+function generateQueryURI(aModelQuery, aSearchWords) {
+ // If there are no search tokens, we simply return an empty string.
+ if (!aSearchWords || aSearchWords.length == 0)
+ return "";
+
+ let queryURI = "";
+ aSearchWords.forEach(searchWord =>
+ queryURI += aModelQuery.replace(/@V/g, encodeABTermValue(searchWord)));
+
+ // queryURI has all the (or(...)) searches, link them up with (and(...)).
+ queryURI = "?(and" + queryURI + ")";
+
+ return queryURI;
+}
+
+
+/**
+ * Encode the string passed as value into an addressbook search term.
+ * The '(' and ')' characters are special for the addressbook
+ * search query language, but are not escaped in encodeURIComponent()
+ * so must be done manually on top of it.
+ */
+function encodeABTermValue(aString) {
+ return encodeURIComponent(aString).replace(/\(/g, "%28").replace(/\)/g, "%29");
+}
diff --git a/mailnews/base/util/IOUtils.js b/mailnews/base/util/IOUtils.js
new file mode 100644
index 000000000..ff4eddcdf
--- /dev/null
+++ b/mailnews/base/util/IOUtils.js
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["IOUtils"];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var kStringBlockSize = 4096;
+var kStreamBlockSize = 8192;
+
+var IOUtils =
+{
+ /**
+ * Read a file containing ASCII text into a string.
+ *
+ * @param aFile An nsIFile representing the file to read or a string containing
+ * the file name of a file under user's profile.
+ * @returns A string containing the contents of the file, presumed to be ASCII
+ * text. If the file didn't exist, returns null.
+ */
+ loadFileToString: function(aFile) {
+ let file;
+ if (!(aFile instanceof Ci.nsIFile)) {
+ file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append(aFile);
+ } else {
+ file = aFile;
+ }
+
+ if (!file.exists())
+ return null;
+
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ // PR_RDONLY
+ fstream.init(file, 0x01, 0, 0);
+
+ let sstream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sstream.init(fstream);
+
+ let data = "";
+ while (sstream.available()) {
+ data += sstream.read(kStringBlockSize);
+ }
+
+ sstream.close();
+ fstream.close();
+
+ return data;
+ },
+
+ /**
+ * Save a string containing ASCII text into a file. The file will be overwritten
+ * and contain only the given text.
+ *
+ * @param aFile An nsIFile representing the file to write or a string containing
+ * the file name of a file under user's profile.
+ * @param aData The string to write.
+ * @param aPerms The octal file permissions for the created file. If unset
+ * the default of 0o600 is used.
+ */
+ saveStringToFile: function(aFile, aData, aPerms = 0o600) {
+ let file;
+ if (!(aFile instanceof Ci.nsIFile)) {
+ file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append(aFile);
+ } else {
+ file = aFile;
+ }
+
+ let foStream = Cc["@mozilla.org/network/safe-file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+
+ // PR_WRONLY + PR_CREATE_FILE + PR_TRUNCATE
+ foStream.init(file, 0x02 | 0x08 | 0x20, aPerms, 0);
+ // safe-file-output-stream appears to throw an error if it doesn't write everything at once
+ // so we won't worry about looping to deal with partial writes.
+ // In case we try to use this function for big files where buffering
+ // is needed we could use the implementation in saveStreamToFile().
+ foStream.write(aData, aData.length);
+ foStream.QueryInterface(Ci.nsISafeOutputStream).finish();
+ foStream.close();
+ },
+
+ /**
+ * Saves the given input stream to a file.
+ *
+ * @param aIStream The input stream to save.
+ * @param aFile The file to which the stream is saved.
+ * @param aPerms The octal file permissions for the created file. If unset
+ * the default of 0o600 is used.
+ */
+ saveStreamToFile: function(aIStream, aFile, aPerms = 0o600) {
+ if (!(aIStream instanceof Ci.nsIInputStream))
+ throw new Error("Invalid stream passed to saveStreamToFile");
+ if (!(aFile instanceof Ci.nsIFile))
+ throw new Error("Invalid file passed to saveStreamToFile");
+
+ let fstream = Cc["@mozilla.org/network/safe-file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ let buffer = Cc["@mozilla.org/network/buffered-output-stream;1"]
+ .createInstance(Ci.nsIBufferedOutputStream);
+
+ // Write the input stream to the file.
+ // PR_WRITE + PR_CREATE + PR_TRUNCATE
+ fstream.init(aFile, 0x04 | 0x08 | 0x20, aPerms, 0);
+ buffer.init(fstream, kStreamBlockSize);
+
+ buffer.writeFrom(aIStream, aIStream.available());
+
+ // Close the output streams.
+ if (buffer instanceof Components.interfaces.nsISafeOutputStream)
+ buffer.finish();
+ else
+ buffer.close();
+ if (fstream instanceof Components.interfaces.nsISafeOutputStream)
+ fstream.finish();
+ else
+ fstream.close();
+
+ // Close the input stream.
+ aIStream.close();
+ return aFile;
+ },
+
+ /**
+ * Returns size of system memory.
+ */
+ getPhysicalMemorySize: function() {
+ return Services.sysinfo.getPropertyAsInt64("memsize");
+ },
+};
diff --git a/mailnews/base/util/JXON.js b/mailnews/base/util/JXON.js
new file mode 100644
index 000000000..509c9b5d8
--- /dev/null
+++ b/mailnews/base/util/JXON.js
@@ -0,0 +1,180 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This is a modification of the JXON parsers found on the page
+// <https://developer.mozilla.org/en-US/docs/JXON>
+
+var EXPORTED_SYMBOLS = ["JXON"];
+
+var JXON = new (function() {
+ const sValueProp = "value"; /* you can customize these values */
+ const sAttributesProp = "attr";
+ const sAttrPref = "@";
+ const sElementListPrefix = "$";
+ const sConflictSuffix = "_"; // used when there's a name conflict with special JXON properties
+ const aCache = [];
+ const rIsNull = /^\s*$/;
+ const rIsBool = /^(?:true|false)$/i;
+
+ function parseText(sValue) {
+ //if (rIsNull.test(sValue))
+ // return null;
+ if (rIsBool.test(sValue))
+ return sValue.toLowerCase() === "true";
+ if (isFinite(sValue))
+ return parseFloat(sValue);
+ if (isFinite(Date.parse(sValue)))
+ return new Date(sValue);
+ return sValue;
+ };
+
+ function EmptyTree() {
+ }
+ EmptyTree.prototype = {
+ toString : function () {
+ return "null";
+ },
+ valueOf : function () {
+ return null;
+ },
+ };
+
+ function objectify(vValue) {
+ if (vValue === null)
+ return new EmptyTree();
+ else if (vValue instanceof Object)
+ return vValue;
+ else
+ return new vValue.constructor(vValue); // What does this? copy?
+ };
+
+ function createObjTree(oParentNode, nVerb, bFreeze, bNesteAttr) {
+ const nLevelStart = aCache.length;
+ const bChildren = oParentNode.hasChildNodes();
+ const bAttributes = oParentNode.attributes &&
+ oParentNode.attributes.length;
+ const bHighVerb = Boolean(nVerb & 2);
+
+ var sProp = 0;
+ var vContent = 0;
+ var nLength = 0;
+ var sCollectedTxt = "";
+ var vResult = bHighVerb ? {} : /* put here the default value for empty nodes: */ true;
+
+ if (bChildren) {
+ for (var oNode, nItem = 0; nItem < oParentNode.childNodes.length; nItem++) {
+ oNode = oParentNode.childNodes.item(nItem);
+ if (oNode.nodeType === 4) // CDATASection
+ sCollectedTxt += oNode.nodeValue;
+ else if (oNode.nodeType === 3) // Text
+ sCollectedTxt += oNode.nodeValue;
+ else if (oNode.nodeType === 1) // Element
+ aCache.push(oNode);
+ }
+ }
+
+ const nLevelEnd = aCache.length;
+ const vBuiltVal = parseText(sCollectedTxt);
+
+ if (!bHighVerb && (bChildren || bAttributes))
+ vResult = nVerb === 0 ? objectify(vBuiltVal) : {};
+
+ for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) {
+ sProp = aCache[nElId].nodeName;
+ if (sProp == sValueProp || sProp == sAttributesProp)
+ sProp = sProp + sConflictSuffix;
+ vContent = createObjTree(aCache[nElId], nVerb, bFreeze, bNesteAttr);
+ if (!vResult.hasOwnProperty(sProp)) {
+ vResult[sProp] = vContent;
+ vResult[sElementListPrefix + sProp] = [];
+ }
+ vResult[sElementListPrefix + sProp].push(vContent);
+ nLength++;
+ }
+
+ if (bAttributes) {
+ const nAttrLen = oParentNode.attributes.length;
+ const sAPrefix = bNesteAttr ? "" : sAttrPref;
+ const oAttrParent = bNesteAttr ? {} : vResult;
+
+ for (var oAttrib, nAttrib = 0; nAttrib < nAttrLen; nLength++, nAttrib++) {
+ oAttrib = oParentNode.attributes.item(nAttrib);
+ oAttrParent[sAPrefix + oAttrib.name] = parseText(oAttrib.value);
+ }
+
+ if (bNesteAttr) {
+ if (bFreeze)
+ Object.freeze(oAttrParent);
+ vResult[sAttributesProp] = oAttrParent;
+ nLength -= nAttrLen - 1;
+ }
+ }
+
+ if (nVerb === 3 || (nVerb === 2 || nVerb === 1 && nLength > 0) && sCollectedTxt)
+ vResult[sValueProp] = vBuiltVal;
+ else if (!bHighVerb && nLength === 0 && sCollectedTxt)
+ vResult = vBuiltVal;
+
+ if (bFreeze && (bHighVerb || nLength > 0))
+ Object.freeze(vResult);
+
+ aCache.length = nLevelStart;
+
+ return vResult;
+ };
+
+ function loadObjTree(oXMLDoc, oParentEl, oParentObj) {
+ var vValue, oChild;
+
+ if (oParentObj instanceof String || oParentObj instanceof Number ||
+ oParentObj instanceof Boolean)
+ oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toString())); /* verbosity level is 0 */
+ else if (oParentObj.constructor === Date)
+ oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toGMTString()));
+
+ for (var sName in oParentObj) {
+ vValue = oParentObj[sName];
+ if (isFinite(sName) || vValue instanceof Function)
+ continue; /* verbosity level is 0 */
+ if (sName === sValueProp) {
+ if (vValue !== null && vValue !== true) {
+ oParentEl.appendChild(oXMLDoc.createTextNode(
+ vValue.constructor === Date ? vValue.toGMTString() : String(vValue)));
+ }
+ } else if (sName === sAttributesProp) { /* verbosity level is 3 */
+ for (var sAttrib in vValue)
+ oParentEl.setAttribute(sAttrib, vValue[sAttrib]);
+ } else if (sName.charAt(0) === sAttrPref) {
+ oParentEl.setAttribute(sName.slice(1), vValue);
+ } else if (vValue.constructor === Array) {
+ for (var nItem = 0; nItem < vValue.length; nItem++) {
+ oChild = oXMLDoc.createElement(sName);
+ loadObjTree(oXMLDoc, oChild, vValue[nItem]);
+ oParentEl.appendChild(oChild);
+ }
+ } else {
+ oChild = oXMLDoc.createElement(sName);
+ if (vValue instanceof Object)
+ loadObjTree(oXMLDoc, oChild, vValue);
+ else if (vValue !== null && vValue !== true)
+ oChild.appendChild(oXMLDoc.createTextNode(vValue.toString()));
+ oParentEl.appendChild(oChild);
+ }
+ }
+ };
+
+ this.build = function(oXMLParent, nVerbosity /* optional */, bFreeze /* optional */, bNesteAttributes /* optional */) {
+ const _nVerb = arguments.length > 1 &&
+ typeof nVerbosity === "number" ? nVerbosity & 3 :
+ /* put here the default verbosity level: */ 1;
+ return createObjTree(oXMLParent, _nVerb, bFreeze || false,
+ arguments.length > 3 ? bNesteAttributes : _nVerb === 3);
+ };
+
+ this.unbuild = function(oObjTree) {
+ const oNewDoc = document.implementation.createDocument("", "", null);
+ loadObjTree(oNewDoc, oNewDoc, oObjTree);
+ return oNewDoc;
+ };
+})();
diff --git a/mailnews/base/util/OAuth2.jsm b/mailnews/base/util/OAuth2.jsm
new file mode 100644
index 000000000..8e6f9e713
--- /dev/null
+++ b/mailnews/base/util/OAuth2.jsm
@@ -0,0 +1,234 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Provides OAuth 2.0 authentication
+ */
+var EXPORTED_SYMBOLS = ["OAuth2"];
+
+var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Http.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+function parseURLData(aData) {
+ let result = {};
+ aData.split(/[?#]/, 2)[1].split("&").forEach(function (aParam) {
+ let [key, value] = aParam.split("=");
+ result[key] = value;
+ });
+ return result;
+}
+
+// Only allow one connecting window per endpoint.
+var gConnecting = {};
+
+function OAuth2(aBaseURI, aScope, aAppKey, aAppSecret) {
+ this.authURI = aBaseURI + "oauth2/auth";
+ this.tokenURI = aBaseURI + "oauth2/token";
+ this.consumerKey = aAppKey;
+ this.consumerSecret = aAppSecret;
+ this.scope = aScope;
+ this.extraAuthParams = [];
+
+ this.log = Log4Moz.getConfiguredLogger("TBOAuth");
+}
+
+OAuth2.CODE_AUTHORIZATION = "authorization_code";
+OAuth2.CODE_REFRESH = "refresh_token";
+
+OAuth2.prototype = {
+
+ responseType: "code",
+ consumerKey: null,
+ consumerSecret: null,
+ completionURI: "http://localhost",
+ requestWindowURI: "chrome://messenger/content/browserRequest.xul",
+ requestWindowFeatures: "chrome,private,centerscreen,width=980,height=600",
+ requestWindowTitle: "",
+ scope: null,
+
+ accessToken: null,
+ refreshToken: null,
+ tokenExpires: 0,
+
+ connect: function connect(aSuccess, aFailure, aWithUI, aRefresh) {
+
+ this.connectSuccessCallback = aSuccess;
+ this.connectFailureCallback = aFailure;
+
+ if (!aRefresh && this.accessToken) {
+ aSuccess();
+ } else if (this.refreshToken) {
+ this.requestAccessToken(this.refreshToken, OAuth2.CODE_REFRESH);
+ } else {
+ if (!aWithUI) {
+ aFailure('{ "error": "auth_noui" }');
+ return;
+ }
+ if (gConnecting[this.authURI]) {
+ aFailure("Window already open");
+ return;
+ }
+ this.requestAuthorization();
+ }
+ },
+
+ requestAuthorization: function requestAuthorization() {
+ let params = [
+ ["response_type", this.responseType],
+ ["client_id", this.consumerKey],
+ ["redirect_uri", this.completionURI],
+ ];
+ // The scope can be optional.
+ if (this.scope) {
+ params.push(["scope", this.scope]);
+ }
+
+ // Add extra parameters
+ params.push(...this.extraAuthParams);
+
+ // Now map the parameters to a string
+ params = params.map(([k,v]) => k + "=" + encodeURIComponent(v)).join("&");
+
+ this._browserRequest = {
+ account: this,
+ url: this.authURI + "?" + params,
+ _active: true,
+ iconURI: "",
+ cancelled: function() {
+ if (!this._active) {
+ return;
+ }
+
+ this.account.finishAuthorizationRequest();
+ this.account.onAuthorizationFailed(Components.results.NS_ERROR_ABORT, '{ "error": "cancelled"}');
+ },
+
+ loaded: function (aWindow, aWebProgress) {
+ if (!this._active) {
+ return;
+ }
+
+ this._listener = {
+ window: aWindow,
+ webProgress: aWebProgress,
+ _parent: this.account,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+
+ _cleanUp: function() {
+ this.webProgress.removeProgressListener(this);
+ this.window.close();
+ delete this.window;
+ },
+
+ _checkForRedirect: function(aURL) {
+ if (aURL.indexOf(this._parent.completionURI) != 0)
+ return;
+
+ this._parent.finishAuthorizationRequest();
+ this._parent.onAuthorizationReceived(aURL);
+ },
+
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
+ const wpl = Ci.nsIWebProgressListener;
+ if (aStateFlags & (wpl.STATE_START | wpl.STATE_IS_NETWORK))
+ this._checkForRedirect(aRequest.name);
+ },
+ onLocationChange: function(aWebProgress, aRequest, aLocation) {
+ this._checkForRedirect(aLocation.spec);
+ },
+ onProgressChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function() {},
+ };
+ aWebProgress.addProgressListener(this._listener,
+ Ci.nsIWebProgress.NOTIFY_ALL);
+ aWindow.document.title = this.account.requestWindowTitle;
+ }
+ };
+
+ this.wrappedJSObject = this._browserRequest;
+ gConnecting[this.authURI] = true;
+ Services.ww.openWindow(null, this.requestWindowURI, null, this.requestWindowFeatures, this);
+ },
+ finishAuthorizationRequest: function() {
+ gConnecting[this.authURI] = false;
+ if (!("_browserRequest" in this)) {
+ return;
+ }
+
+ this._browserRequest._active = false;
+ if ("_listener" in this._browserRequest) {
+ this._browserRequest._listener._cleanUp();
+ }
+ delete this._browserRequest;
+ },
+
+ onAuthorizationReceived: function(aData) {
+ this.log.info("authorization received" + aData);
+ let results = parseURLData(aData);
+ if (this.responseType == "code" && results.code) {
+ this.requestAccessToken(results.code, OAuth2.CODE_AUTHORIZATION);
+ } else if (this.responseType == "token") {
+ this.onAccessTokenReceived(JSON.stringify(results));
+ }
+ else
+ this.onAuthorizationFailed(null, aData);
+ },
+
+ onAuthorizationFailed: function(aError, aData) {
+ this.connectFailureCallback(aData);
+ },
+
+ requestAccessToken: function requestAccessToken(aCode, aType) {
+ let params = [
+ ["client_id", this.consumerKey],
+ ["client_secret", this.consumerSecret],
+ ["grant_type", aType],
+ ];
+
+ if (aType == OAuth2.CODE_AUTHORIZATION) {
+ params.push(["code", aCode]);
+ params.push(["redirect_uri", this.completionURI]);
+ } else if (aType == OAuth2.CODE_REFRESH) {
+ params.push(["refresh_token", aCode]);
+ }
+
+ let options = {
+ postData: params,
+ onLoad: this.onAccessTokenReceived.bind(this),
+ onError: this.onAccessTokenFailed.bind(this)
+ }
+ httpRequest(this.tokenURI, options);
+ },
+
+ onAccessTokenFailed: function onAccessTokenFailed(aError, aData) {
+ if (aError != "offline") {
+ this.refreshToken = null;
+ }
+ this.connectFailureCallback(aData);
+ },
+
+ onAccessTokenReceived: function onRequestTokenReceived(aData) {
+ let result = JSON.parse(aData);
+
+ this.accessToken = result.access_token;
+ if ("refresh_token" in result) {
+ this.refreshToken = result.refresh_token;
+ }
+ if ("expires_in" in result) {
+ this.tokenExpires = (new Date()).getTime() + (result.expires_in * 1000);
+ } else {
+ this.tokenExpires = Number.MAX_VALUE;
+ }
+ this.tokenType = result.token_type;
+
+ this.connectSuccessCallback();
+ }
+};
diff --git a/mailnews/base/util/OAuth2Providers.jsm b/mailnews/base/util/OAuth2Providers.jsm
new file mode 100644
index 000000000..37a750f46
--- /dev/null
+++ b/mailnews/base/util/OAuth2Providers.jsm
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Details of supported OAuth2 Providers.
+ */
+var EXPORTED_SYMBOLS = ["OAuth2Providers"];
+
+var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+// map of hostnames to [issuer, scope]
+var kHostnames = new Map([
+ ["imap.googlemail.com", ["accounts.google.com", "https://mail.google.com/"]],
+ ["smtp.googlemail.com", ["accounts.google.com", "https://mail.google.com/"]],
+ ["imap.gmail.com", ["accounts.google.com", "https://mail.google.com/"]],
+ ["smtp.gmail.com", ["accounts.google.com", "https://mail.google.com/"]],
+
+ ["imap.mail.ru", ["o2.mail.ru", "mail.imap"]],
+ ["smtp.mail.ru", ["o2.mail.ru", "mail.imap"]],
+]);
+
+// map of issuers to appKey, appSecret, authURI, tokenURI
+
+// For the moment, these details are hard-coded, since Google does not
+// provide dynamic client registration. Don't copy these values for your
+// own application--register it yourself. This code (and possibly even the
+// registration itself) will disappear when this is switched to dynamic
+// client registration.
+var kIssuers = new Map ([
+ ["accounts.google.com", [
+ '406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com',
+ 'kSmqreRr0qwBWJgbf5Y-PjSU',
+ 'https://accounts.google.com/o/oauth2/auth',
+ 'https://www.googleapis.com/oauth2/v3/token'
+ ]],
+ ["o2.mail.ru", [
+ 'thunderbird',
+ 'I0dCAXrcaNFujaaY',
+ 'https://o2.mail.ru/login',
+ 'https://o2.mail.ru/token'
+ ]],
+]);
+
+/**
+ * OAuth2Providers: Methods to lookup OAuth2 parameters for supported
+ * email providers.
+ */
+var OAuth2Providers = {
+
+ /**
+ * Map a hostname to the relevant issuer and scope.
+ *
+ * @param aHostname String representing the url for an imap or smtp
+ * server (example "imap.googlemail.com").
+ *
+ * @returns Array with [issuer, scope] for the hostname if found,
+ * else undefined. issuer is a string representing the
+ * organization, scope is an oauth parameter describing\
+ * the required access level.
+ */
+ getHostnameDetails: function (aHostname) { return kHostnames.get(aHostname);},
+
+ /**
+ * Map an issuer to OAuth2 account details.
+ *
+ * @param aIssuer The organization issuing oauth2 parameters, example
+ * "accounts.google.com".
+ *
+ * @return Array containing [appKey, appSecret, authURI, tokenURI]
+ * where appKey and appDetails are strings representing the
+ * account registered for Thunderbird with the organization,
+ * authURI and tokenURI are url strings representing
+ * endpoints to access OAuth2 authentication.
+ */
+ getIssuerDetails: function (aIssuer) { return kIssuers.get(aIssuer);}
+}
diff --git a/mailnews/base/util/ServiceList.h b/mailnews/base/util/ServiceList.h
new file mode 100644
index 000000000..cc98c13b5
--- /dev/null
+++ b/mailnews/base/util/ServiceList.h
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// IWYU pragma: private, include "mozilla/mailnews/Services.h"
+
+MOZ_SERVICE(AbManager, nsIAbManager,
+ "@mozilla.org/abmanager;1")
+MOZ_SERVICE(AccountManager, nsIMsgAccountManager,
+ "@mozilla.org/messenger/account-manager;1")
+MOZ_SERVICE(ComposeService, nsIMsgComposeService,
+ "@mozilla.org/messengercompose;1")
+MOZ_SERVICE(CopyService, nsIMsgCopyService,
+ "@mozilla.org/messenger/messagecopyservice;1")
+MOZ_SERVICE(DBService, nsIMsgDBService,
+ "@mozilla.org/msgDatabase/msgDBService;1")
+MOZ_SERVICE(FilterService, nsIMsgFilterService,
+ "@mozilla.org/messenger/services/filters;1")
+MOZ_SERVICE(HeaderParser, nsIMsgHeaderParser,
+ "@mozilla.org/messenger/headerparser;1")
+MOZ_SERVICE(ImapService, nsIImapService,
+ "@mozilla.org/messenger/imapservice;1")
+MOZ_SERVICE(ImportService, nsIImportService,
+ "@mozilla.org/import/import-service;1")
+MOZ_SERVICE(MailNotifyService, mozINewMailNotificationService,
+ "@mozilla.org/newMailNotificationService;1")
+MOZ_SERVICE(MailSession, nsIMsgMailSession,
+ "@mozilla.org/messenger/services/session;1")
+MOZ_SERVICE(MimeConverter, nsIMimeConverter,
+ "@mozilla.org/messenger/mimeconverter;1")
+MOZ_SERVICE(MFNService, nsIMsgFolderNotificationService,
+ "@mozilla.org/messenger/msgnotificationservice;1")
+MOZ_SERVICE(NntpService, nsINntpService,
+ "@mozilla.org/messenger/nntpservice;1")
+MOZ_SERVICE(Pop3Service, nsIPop3Service,
+ "@mozilla.org/messenger/popservice;1")
+MOZ_SERVICE(SmtpService, nsISmtpService,
+ "@mozilla.org/messengercompose/smtp;1")
+MOZ_SERVICE(TagService, nsIMsgTagService,
+ "@mozilla.org/messenger/tagservice;1")
diff --git a/mailnews/base/util/Services.cpp b/mailnews/base/util/Services.cpp
new file mode 100644
index 000000000..a10d44aa3
--- /dev/null
+++ b/mailnews/base/util/Services.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/mailnews/Services.h"
+
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsServiceManagerUtils.h"
+
+// All of the includes for the services we initiate here
+#include "mozINewMailNotificationService.h"
+#include "nsIAbManager.h"
+#include "nsIImapService.h"
+#include "nsIImportService.h"
+#include "nsIMimeConverter.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgComposeService.h"
+#include "nsIMsgCopyService.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMsgHeaderParser.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgTagService.h"
+#include "nsINntpService.h"
+#include "nsIPop3Service.h"
+#include "nsISmtpService.h"
+
+namespace mozilla {
+namespace services {
+
+namespace {
+class ShutdownObserver final : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static void EnsureInitialized();
+private:
+ ~ShutdownObserver() {}
+
+ void ShutdownServices();
+ static ShutdownObserver *sShutdownObserver;
+ static bool sShuttingDown;
+};
+
+bool ShutdownObserver::sShuttingDown = false;
+ShutdownObserver *ShutdownObserver::sShutdownObserver = nullptr;
+}
+
+#define MOZ_SERVICE(NAME, TYPE, CONTRACT_ID) \
+ static TYPE *g##NAME = nullptr; \
+ already_AddRefed<TYPE> Get##NAME() \
+ { \
+ ShutdownObserver::EnsureInitialized(); \
+ if (!g##NAME) \
+ { \
+ nsCOMPtr<TYPE> os = do_GetService(CONTRACT_ID); \
+ os.forget(&g##NAME); \
+ MOZ_ASSERT(g##NAME, "This service is unexpectedly missing."); \
+ } \
+ nsCOMPtr<TYPE> ret = g##NAME; \
+ return ret.forget(); \
+ }
+#include "mozilla/mailnews/ServiceList.h"
+#undef MOZ_SERVICE
+
+NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP ShutdownObserver::Observe(nsISupports *aSubject,
+ const char *aTopic, const char16_t *aData)
+{
+ if (!strcmp(aTopic, "xpcom-shutdown-threads"))
+ ShutdownServices();
+ return NS_OK;
+}
+
+void ShutdownObserver::EnsureInitialized()
+{
+ MOZ_ASSERT(!sShuttingDown, "It is illegal to use this code after shutdown!");
+ if (!sShutdownObserver)
+ {
+ sShutdownObserver = new ShutdownObserver;
+ sShutdownObserver->AddRef();
+ nsCOMPtr<nsIObserverService> obs(mozilla::services::GetObserverService());
+ MOZ_ASSERT(obs, "This should never be null");
+ obs->AddObserver(sShutdownObserver, "xpcom-shutdown-threads", false);
+ }
+}
+
+void ShutdownObserver::ShutdownServices()
+{
+ sShuttingDown = true;
+ MOZ_ASSERT(sShutdownObserver, "Shutting down twice?");
+ sShutdownObserver->Release();
+ sShutdownObserver = nullptr;
+#define MOZ_SERVICE(NAME, TYPE, CONTRACT_ID) NS_IF_RELEASE(g##NAME);
+#include "mozilla/mailnews/ServiceList.h"
+#undef MOZ_SERVICE
+}
+
+} // namespace services
+} // namespace mozilla
diff --git a/mailnews/base/util/Services.h b/mailnews/base/util/Services.h
new file mode 100644
index 000000000..217cecf32
--- /dev/null
+++ b/mailnews/base/util/Services.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_mailnews_Services_h
+#define mozilla_mailnews_Services_h
+
+#include "mozilla/Services.h"
+
+#define MOZ_SERVICE(NAME, TYPE, SERVICE_CID) class TYPE;
+#include "mozilla/mailnews/ServiceList.h"
+#undef MOZ_SERVICE
+
+namespace mozilla {
+namespace services {
+
+#define MOZ_SERVICE(NAME, TYPE, SERVICE_CID) \
+ already_AddRefed<TYPE> Get##NAME();
+#include "ServiceList.h"
+#undef MOZ_SERVICE
+
+} // namespace services
+} // namespace mozilla
+
+#endif
diff --git a/mailnews/base/util/StringBundle.js b/mailnews/base/util/StringBundle.js
new file mode 100644
index 000000000..a1e10ec62
--- /dev/null
+++ b/mailnews/base/util/StringBundle.js
@@ -0,0 +1,189 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["StringBundle"];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * A string bundle.
+ *
+ * This object presents two APIs: a deprecated one that is equivalent to the API
+ * for the stringbundle XBL binding, to make it easy to switch from that binding
+ * to this module, and a new one that is simpler and easier to use.
+ *
+ * The benefit of this module over the XBL binding is that it can also be used
+ * in JavaScript modules and components, not only in chrome JS.
+ *
+ * To use this module, import it, create a new instance of StringBundle,
+ * and then use the instance's |get| and |getAll| methods to retrieve strings
+ * (you can get both plain and formatted strings with |get|):
+ *
+ * let strings =
+ * new StringBundle("chrome://example/locale/strings.properties");
+ * let foo = strings.get("foo");
+ * let barFormatted = strings.get("bar", [arg1, arg2]);
+ * for (let string of strings.getAll())
+ * dump (string.key + " = " + string.value + "\n");
+ *
+ * @param url {String}
+ * the URL of the string bundle
+ */
+function StringBundle(url) {
+ this.url = url;
+}
+
+StringBundle.prototype = {
+ /**
+ * the locale associated with the application
+ * @type nsILocale
+ * @private
+ */
+ get _appLocale() {
+ try {
+ return Services.locale.getApplicationLocale();
+ }
+ catch(ex) {
+ return null;
+ }
+ },
+
+ /**
+ * the wrapped nsIStringBundle
+ * @type nsIStringBundle
+ * @private
+ */
+ get _stringBundle() {
+ let stringBundle = Services.strings.createBundle(this.url, this._appLocale);
+ this.__defineGetter__("_stringBundle", () => stringBundle);
+ return this._stringBundle;
+ },
+
+
+ // the new API
+
+ /**
+ * the URL of the string bundle
+ * @type String
+ */
+ _url: null,
+ get url() {
+ return this._url;
+ },
+ set url(newVal) {
+ this._url = newVal;
+ delete this._stringBundle;
+ },
+
+ /**
+ * Get a string from the bundle.
+ *
+ * @param key {String}
+ * the identifier of the string to get
+ * @param args {array} [optional]
+ * an array of arguments that replace occurrences of %S in the string
+ *
+ * @returns {String} the value of the string
+ */
+ get: function(key, args) {
+ if (args)
+ return this.stringBundle.formatStringFromName(key, args, args.length);
+ else
+ return this.stringBundle.GetStringFromName(key);
+ },
+
+ /**
+ * Get all the strings in the bundle.
+ *
+ * @returns {Array}
+ * an array of objects with key and value properties
+ */
+ getAll: function() {
+ let strings = [];
+
+ // FIXME: for performance, return an enumerable array that wraps the string
+ // bundle's nsISimpleEnumerator (does JavaScript already support this?).
+
+ let enumerator = this.stringBundle.getSimpleEnumeration();
+
+ while (enumerator.hasMoreElements()) {
+ // We could simply return the nsIPropertyElement objects, but I think
+ // it's better to return standard JS objects that behave as consumers
+ // expect JS objects to behave (f.e. you can modify them dynamically).
+ let string = enumerator.getNext()
+ .QueryInterface(Components.interfaces.nsIPropertyElement);
+ strings.push({ key: string.key, value: string.value });
+ }
+
+ return strings;
+ },
+
+
+ // the deprecated XBL binding-compatible API
+
+ /**
+ * the URL of the string bundle
+ * @deprecated because its name doesn't make sense outside of an XBL binding
+ * @type String
+ */
+ get src() {
+ return this.url;
+ },
+ set src(newVal) {
+ this.url = newVal;
+ },
+
+ /**
+ * the locale associated with the application
+ * @deprecated because it has never been used outside the XBL binding itself,
+ * and consumers should obtain it directly from the locale service anyway.
+ * @type nsILocale
+ */
+ get appLocale() {
+ return this._appLocale;
+ },
+
+ /**
+ * the wrapped nsIStringBundle
+ * @deprecated because this module should provide all necessary functionality
+ * @type nsIStringBundle
+ *
+ * If you do ever need to use this, let the authors of this module know why
+ * so they can surface functionality for your use case in the module itself
+ * and you don't have to access this underlying XPCOM component.
+ */
+ get stringBundle() {
+ return this._stringBundle;
+ },
+
+ /**
+ * Get a string from the bundle.
+ * @deprecated use |get| instead
+ *
+ * @param key {String}
+ * the identifier of the string to get
+ *
+ * @returns {String}
+ * the value of the string
+ */
+ getString: function(key) {
+ return this.get(key);
+ },
+
+ /**
+ * Get a formatted string from the bundle.
+ * @deprecated use |get| instead
+ *
+ * @param key {string}
+ * the identifier of the string to get
+ * @param args {array}
+ * an array of arguments that replace occurrences of %S in the string
+ *
+ * @returns {String}
+ * the formatted value of the string
+ */
+ getFormattedString: function(key, args) {
+ return this.get(key, args);
+ }
+}
diff --git a/mailnews/base/util/errUtils.js b/mailnews/base/util/errUtils.js
new file mode 100644
index 000000000..21b205b82
--- /dev/null
+++ b/mailnews/base/util/errUtils.js
@@ -0,0 +1,309 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains helper methods for debugging -- things like logging
+ * exception objects, dumping DOM nodes, Events, and generic object dumps.
+ */
+
+this.EXPORTED_SYMBOLS = ["logObject", "logException", "logElement", "logEvent",
+ "errorWithDebug"];
+
+/**
+ * Report on an object to stdout.
+ * @param aObj the object to be dumped
+ * @param aName the name of the object, for informational purposes
+ */
+function logObject(aObj, aName) {
+ dump("Dumping Object: " + aName + "\n");
+ stringifier.dumpObj(aObj, aName);
+}
+
+/**
+ * Log an exception to stdout. This function should not be called in
+ * expected circumstances.
+ * @param aException the exception to log
+ * @param [aRethrow] set to true to rethrow the exception after logging
+ * @param [aMsg] optional message to log
+ */
+function logException(aException, aRethrow, aMsg) {
+ stringifier.dumpException(aException, aMsg);
+
+ if (aMsg)
+ Components.utils.reportError(aMsg);
+ Components.utils.reportError(aException);
+
+ if (aRethrow)
+ throw aException;
+}
+
+/**
+ * Log an DOM element to stdout.
+ * @param aElement the DOM element to dump
+ */
+function logElement(aElement) {
+ stringifier.dumpDOM(aElement);
+}
+
+/**
+ * Log an DOM event to stdout.
+ * @param aEvent the DOM event object to dump
+ */
+function logEvent(aEvent) {
+ stringifier.dumpEvent(aEvent);
+}
+
+/**
+ * Dump the current stack and return an Error suitable for throwing. We return
+ * the new Error so that your code can use a "throw" statement which makes it
+ * obvious to syntactic analysis that there is an exit occuring at that point.
+ *
+ * Example:
+ * throw errorWithDebug("I did not expect this!");
+ *
+ * @param aString The message payload for the exception.
+ */
+function errorWithDebug(aString) {
+ dump("PROBLEM: " + aString + "\n");
+ dump("CURRENT STACK (and throwing):\n");
+ // skip this frame.
+ dump(stringifier.getStack(1));
+ return new Error(aString);
+}
+
+function Stringifier() {};
+
+Stringifier.prototype = {
+ dumpObj: function (o, name) {
+ this._reset();
+ this._append(this.objectTreeAsString(o, true, true, 0));
+ dump(this._asString());
+ },
+
+ dumpDOM: function(node, level, recursive) {
+ this._reset();
+ let s = this.DOMNodeAsString(node, level, recursive);
+ dump(s);
+ },
+
+ dumpEvent: function(event) {
+ dump(this.eventAsString(event));
+ },
+
+ dumpException: function(exc, message) {
+ dump(exc + "\n");
+ this._reset();
+ if (message)
+ this._append("Exception (" + message + ")\n");
+
+ this._append("-- Exception object --\n");
+ this._append(this.objectTreeAsString(exc));
+ if (exc.stack) {
+ this._append("-- Stack Trace --\n");
+ this._append(exc.stack); // skip dumpException and logException
+ }
+ dump(this._asString());
+ },
+
+ _reset: function() {
+ this._buffer = [];
+ },
+
+ _append: function(string) {
+ this._buffer.push(string);
+ },
+
+ _asString: function() {
+ let str = this._buffer.join('');
+ this._reset();
+ return str;
+ },
+
+ getStack: function(skipCount) {
+ if (!((typeof Components == "object") &&
+ (typeof Components.classes == "object")))
+ return "No stack trace available.";
+ if (typeof(skipCount) === undefined)
+ skipCount = 0;
+
+ let frame = Components.stack.caller;
+ let str = "<top>";
+
+ while (frame) {
+ if (skipCount > 0) {
+ // Skip this frame.
+ skipCount -= 1;
+ }
+ else {
+ // Include the data from this frame.
+ let name = frame.name ? frame.name : "[anonymous]";
+ str += "\n" + name + "@" + frame.filename + ':' + frame.lineNumber;
+ }
+ frame = frame.caller;
+ }
+ return str + "\n";
+ },
+
+ objectTreeAsString: function(o, recurse, compress, level) {
+ let s = "";
+ if (recurse === undefined)
+ recurse = 0;
+ if (level === undefined)
+ level = 0;
+ if (compress === undefined)
+ compress = true;
+ let pfx = "";
+
+ for (var junk = 0; junk < level; junk++)
+ pfx += (compress) ? "| " : "| ";
+
+ let tee = (compress) ? "+ " : "+- ";
+
+ if (typeof(o) != "object") {
+ s += pfx + tee + " (" + typeof(o) + ") " + o + "\n";
+ }
+ else {
+ for (let i in o) {
+ try {
+ let t = typeof o[i];
+ switch (t) {
+ case "function":
+ let sfunc = String(o[i]).split("\n");
+ if (sfunc[2] == " [native code]")
+ sfunc = "[native code]";
+ else
+ sfunc = sfunc.length + " lines";
+ s += pfx + tee + i + " (function) " + sfunc + "\n";
+ break;
+ case "object":
+ s += pfx + tee + i + " (object) " + o[i] + "\n";
+ if (!compress)
+ s += pfx + "|\n";
+ if ((i != "parent") && (recurse))
+ s += this.objectTreeAsString(o[i], recurse - 1,
+ compress, level + 1);
+ break;
+ case "string":
+ if (o[i].length > 200)
+ s += pfx + tee + i + " (" + t + ") " + o[i].length + " chars\n";
+ else
+ s += pfx + tee + i + " (" + t + ") '" + o[i] + "'\n";
+ break;
+ default:
+ s += pfx + tee + i + " (" + t + ") " + o[i] + "\n";
+ }
+ } catch (ex) {
+ s += pfx + tee + " (exception) " + ex + "\n";
+ }
+ if (!compress)
+ s += pfx + "|\n";
+ }
+ }
+ s += pfx + "*\n";
+ return s;
+ },
+
+ _repeatStr: function (str, aCount) {
+ let res = "";
+ while (--aCount >= 0)
+ res += str;
+ return res;
+ },
+
+ DOMNodeAsString: function(node, level, recursive) {
+ if (level === undefined)
+ level = 0
+ if (recursive === undefined)
+ recursive = true;
+ this._append(this._repeatStr(" ", 2*level) + "<" + node.nodeName + "\n");
+
+ if (node.nodeType == 3) {
+ this._append(this._repeatStr(" ", (2*level) + 4) + node.nodeValue + "'\n");
+ }
+ else {
+ if (node.attributes) {
+ for (let i = 0; i < node.attributes.length; i++) {
+ this._append(this._repeatStr(
+ " ", (2*level) + 4) + node.attributes[i].nodeName +
+ "='" + node.attributes[i].nodeValue + "'\n");
+ }
+ }
+ if (node.childNodes.length == 0) {
+ this._append(this._repeatStr(" ", (2*level)) + "/>\n");
+ }
+ else if (recursive) {
+ this._append(this._repeatStr(" ", (2*level)) + ">\n");
+ for (let i = 0; i < node.childNodes.length; i++) {
+ this._append(this.DOMNodeAsString(node.childNodes[i], level + 1));
+ }
+ this._append(this._repeatStr(" ", 2*level) + "</" + node.nodeName + ">\n");
+ }
+ }
+ return this._asString();
+ },
+
+ eventAsString: function (event) {
+ this._reset();
+ this._append("-EVENT --------------------------\n");
+ this._append("type: " + event.type + "\n");
+ this._append("eventPhase: " + event.eventPhase + "\n");
+ if ("charCode" in event) {
+ this._append("charCode: " + event.charCode + "\n");
+ if ("name" in event)
+ this._append("str(charCode): '" + String.fromCharCode(event.charCode) + "'\n");
+ }
+ if (("target" in event) && event.target) {
+ this._append("target: " + event.target + "\n");
+ if ("nodeName" in event.target)
+ this._append("target.nodeName: " + event.target.nodeName + "\n");
+ if ("getAttribute" in event.target)
+ this._append("target.id: " + event.target.getAttribute("id") + "\n");
+ }
+ if (("currentTarget" in event) && event.currentTarget) {
+ this._append("currentTarget: " + event.currentTarget + "\n");
+ if ("nodeName" in event.currentTarget)
+ this._append("currentTarget.nodeName: "+ event.currentTarget.nodeName + "\n");
+ if ("getAttribute" in event.currentTarget)
+ this._append("currentTarget.id: "+ event.currentTarget.getAttribute("id") + "\n");
+ }
+ if (("originalTarget" in event) && event.originalTarget) {
+ this._append("originalTarget: " + event.originalTarget + "\n");
+ if ("nodeName" in event.originalTarget)
+ this._append("originalTarget.nodeName: "+ event.originalTarget.nodeName + "\n");
+ if ("getAttribute" in event.originalTarget)
+ this._append("originalTarget.id: "+ event.originalTarget.getAttribute("id") + "\n");
+ }
+ let names = [
+ "bubbles",
+ "cancelable",
+ "detail",
+ "button",
+ "keyCode",
+ "isChar",
+ "shiftKey",
+ "altKey",
+ "ctrlKey",
+ "metaKey",
+ "clientX",
+ "clientY",
+ "screenX",
+ "screenY",
+ "layerX",
+ "layerY",
+ "isTrusted",
+ "timeStamp",
+ "currentTargetXPath",
+ "targetXPath",
+ "originalTargetXPath"
+ ];
+ for (let i in names) {
+ if (names[i] in event)
+ this._append(names[i] + ": " + event[names[i]] + "\n");
+ }
+ this._append("-------------------------------------\n");
+ return this._asString();
+ }
+};
+
+var stringifier = new Stringifier();
diff --git a/mailnews/base/util/folderUtils.jsm b/mailnews/base/util/folderUtils.jsm
new file mode 100644
index 000000000..62fb7700b
--- /dev/null
+++ b/mailnews/base/util/folderUtils.jsm
@@ -0,0 +1,234 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains helper methods for dealing with nsIMsgFolders.
+ */
+
+this.EXPORTED_SYMBOLS = ["getFolderProperties", "getSpecialFolderString",
+ "getFolderFromUri", "allAccountsSorted",
+ "getMostRecentFolders", "folderNameCompare"];
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+
+/**
+ * Returns a string representation of a folder's "special" type.
+ *
+ * @param aFolder the nsIMsgFolder whose special type should be returned
+ */
+function getSpecialFolderString(aFolder) {
+ const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
+ let flags = aFolder.flags;
+ if (flags & nsMsgFolderFlags.Inbox)
+ return "Inbox";
+ if (flags & nsMsgFolderFlags.Trash)
+ return "Trash";
+ if (flags & nsMsgFolderFlags.Queue)
+ return "Outbox";
+ if (flags & nsMsgFolderFlags.SentMail)
+ return "Sent";
+ if (flags & nsMsgFolderFlags.Drafts)
+ return "Drafts";
+ if (flags & nsMsgFolderFlags.Templates)
+ return "Templates";
+ if (flags & nsMsgFolderFlags.Junk)
+ return "Junk";
+ if (flags & nsMsgFolderFlags.Archive)
+ return "Archive";
+ if (flags & nsMsgFolderFlags.Virtual)
+ return "Virtual";
+ return "none";
+}
+
+/**
+ * This function is meant to be used with trees. It returns the property list
+ * for all of the common properties that css styling is based off of.
+ *
+ * @param nsIMsgFolder aFolder the folder whose properties should be returned
+ * as a string
+ * @param bool aOpen true if the folder is open/expanded
+ *
+ * @return A string of the property names, delimited by space.
+ */
+function getFolderProperties(aFolder, aOpen) {
+ const nsIMsgFolder = Components.interfaces.nsIMsgFolder;
+ let properties = [];
+
+ properties.push("folderNameCol");
+
+ properties.push("serverType-" + aFolder.server.type);
+
+ // set the SpecialFolder attribute
+ properties.push("specialFolder-" + getSpecialFolderString(aFolder));
+
+ // Now set the biffState
+ switch (aFolder.biffState) {
+ case nsIMsgFolder.nsMsgBiffState_NewMail:
+ properties.push("biffState-NewMail");
+ break;
+ case nsIMsgFolder.nsMsgBiffState_NoMail:
+ properties.push("biffState-NoMail");
+ break;
+ default:
+ properties.push("biffState-UnknownMail");
+ }
+
+ properties.push("isSecure-" + aFolder.server.isSecure);
+
+ // A folder has new messages, or a closed folder or any subfolder has new messages.
+ if (aFolder.hasNewMessages ||
+ (!aOpen && aFolder.hasSubFolders && aFolder.hasFolderOrSubfolderNewMessages))
+ properties.push("newMessages-true");
+
+ if (aFolder.isServer) {
+ properties.push("isServer-true");
+ }
+ else
+ {
+ // We only set this if we're not a server
+ let shallowUnread = aFolder.getNumUnread(false);
+ if (shallowUnread > 0) {
+ properties.push("hasUnreadMessages-true");
+ }
+ else
+ {
+ // Make sure that shallowUnread isn't negative
+ shallowUnread = 0;
+ }
+ let deepUnread = aFolder.getNumUnread(true);
+ if (deepUnread - shallowUnread > 0)
+ properties.push("subfoldersHaveUnreadMessages-true");
+ }
+
+ properties.push("noSelect-" + aFolder.noSelect);
+ properties.push("imapShared-" + aFolder.imapShared);
+
+ return properties.join(" ");
+}
+
+/**
+ * Returns a folder for a particular uri
+ *
+ * @param aUri the rdf uri of the folder to return
+ */
+function getFolderFromUri(aUri) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ return Cc["@mozilla.org/mail/folder-lookup;1"].
+ getService(Ci.nsIFolderLookupService).getFolderById(aUri);
+}
+
+/**
+ * Returns the sort order value based on the server type to be used for sorting.
+ * The servers (accounts) go in the following order:
+ * (0) default account, (1) other mail accounts, (2) Local Folders,
+ * (3) IM accounts, (4) RSS, (5) News, (9) others (no server)
+ * This ordering is encoded in the .sortOrder property of each server type.
+ *
+ * @param aServer the server object to be tested
+ */
+function getServerSortOrder(aServer) {
+ // If there is no server sort this object to the end.
+ if (!aServer)
+ return 999999999;
+
+ // Otherwise get the server sort order from the Account manager.
+ return MailServices.accounts.getSortOrder(aServer);
+}
+
+/**
+ * Compares the passed in accounts according to their precedence.
+ */
+function compareAccounts(aAccount1, aAccount2) {
+ return getServerSortOrder(aAccount1.incomingServer)
+ - getServerSortOrder(aAccount2.incomingServer);
+}
+
+/**
+ * Returns a list of accounts sorted by server type.
+ *
+ * @param aExcludeIMAccounts Remove IM accounts from the list?
+ */
+function allAccountsSorted(aExcludeIMAccounts) {
+ // Get the account list, and add the proper items.
+ let accountList = toArray(fixIterator(MailServices.accounts.accounts,
+ Components.interfaces.nsIMsgAccount));
+
+ // This is a HACK to work around bug 41133. If we have one of the
+ // dummy "news" accounts there, that account won't have an
+ // incomingServer attached to it, and everything will blow up.
+ accountList = accountList.filter(function hasServer(a) {
+ return a.incomingServer;
+ });
+
+ // Remove IM servers.
+ if (aExcludeIMAccounts) {
+ accountList = accountList.filter(function(a) {
+ return a.incomingServer.type != "im";
+ });
+ }
+
+ return accountList.sort(compareAccounts);
+}
+
+/**
+ * Returns the most recently used/modified folders from the passed in list.
+ *
+ * @param aFolderList The array of nsIMsgFolders to search for recent folders.
+ * @param aMaxHits How many folders to return.
+ * @param aTimeProperty Which folder time property to use.
+ * Use "MRMTime" for most recently modified time.
+ * Use "MRUTime" for most recently used time.
+ */
+function getMostRecentFolders(aFolderList, aMaxHits, aTimeProperty) {
+ let recentFolders = [];
+
+ /**
+ * This sub-function will add a folder to the recentFolders array if it
+ * is among the aMaxHits most recent. If we exceed aMaxHits folders,
+ * it will pop the oldest folder, ensuring that we end up with the
+ * right number.
+ *
+ * @param aFolder The folder to check for recency.
+ */
+ let oldestTime = 0;
+ function addIfRecent(aFolder) {
+ let time = 0;
+ try {
+ time = Number(aFolder.getStringProperty(aTimeProperty)) || 0;
+ } catch(e) {}
+ if (time <= oldestTime)
+ return;
+
+ if (recentFolders.length == aMaxHits) {
+ recentFolders.sort(function sort_folders_by_time(a, b) {
+ return a.time < b.time; });
+ recentFolders.pop();
+ oldestTime = recentFolders[recentFolders.length - 1].time;
+ }
+ recentFolders.push({ folder: aFolder, time: time });
+ }
+
+ for (let folder of aFolderList) {
+ addIfRecent(folder);
+ }
+
+ return recentFolders.map(function (f) { return f.folder; });
+}
+
+/**
+ * A locale dependent comparison function to produce a case-insensitive sort order
+ * used to sort folder names.
+ * Returns positive number if aString1 > aString2, negative number if aString1 > aString2,
+ * otherwise 0.
+ *
+ * @param aString1 first string to compare
+ * @param aString2 second string to compare
+ */
+function folderNameCompare(aString1, aString2) {
+ // TODO: improve this as described in bug 992651.
+ return aString1.toLocaleLowerCase()
+ .localeCompare(aString2.toLocaleLowerCase());
+}
diff --git a/mailnews/base/util/hostnameUtils.jsm b/mailnews/base/util/hostnameUtils.jsm
new file mode 100644
index 000000000..e2d0ee4ff
--- /dev/null
+++ b/mailnews/base/util/hostnameUtils.jsm
@@ -0,0 +1,341 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Generic shared utility code for checking of IP and hostname validity.
+ */
+
+this.EXPORTED_SYMBOLS = [ "isLegalHostNameOrIP",
+ "isLegalHostName",
+ "isLegalIPv4Address",
+ "isLegalIPv6Address",
+ "isLegalIPAddress",
+ "isLegalLocalIPAddress",
+ "cleanUpHostName",
+ "kMinPort",
+ "kMaxPort" ];
+
+var kMinPort = 1;
+var kMaxPort = 65535;
+
+/**
+ * Check if aHostName is an IP address or a valid hostname.
+ *
+ * @param aHostName The string to check for validity.
+ * @param aAllowExtendedIPFormats Allow hex/octal formats in addition to decimal.
+ * @return Unobscured host name if aHostName is valid.
+ * Returns null if it's not.
+ */
+function isLegalHostNameOrIP(aHostName, aAllowExtendedIPFormats)
+{
+ /*
+ RFC 1123:
+ Whenever a user inputs the identity of an Internet host, it SHOULD
+ be possible to enter either (1) a host domain name or (2) an IP
+ address in dotted-decimal ("#.#.#.#") form. The host SHOULD check
+ the string syntactically for a dotted-decimal number before
+ looking it up in the Domain Name System.
+ */
+
+ return isLegalIPAddress(aHostName, aAllowExtendedIPFormats) ||
+ isLegalHostName(aHostName);
+}
+
+/**
+ * Check if aHostName is a valid hostname.
+ *
+ * @return The host name if it is valid.
+ * Returns null if it's not.
+ */
+function isLegalHostName(aHostName)
+{
+ /*
+ RFC 952:
+ A "name" (Net, Host, Gateway, or Domain name) is a text string up
+ to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
+ sign (-), and period (.). Note that periods are only allowed when
+ they serve to delimit components of "domain style names". (See
+ RFC-921, "Domain Name System Implementation Schedule", for
+ background). No blank or space characters are permitted as part of a
+ name. No distinction is made between upper and lower case. The first
+ character must be an alpha character. The last character must not be
+ a minus sign or period.
+
+ RFC 1123:
+ The syntax of a legal Internet host name was specified in RFC-952
+ [DNS:4]. One aspect of host name syntax is hereby changed: the
+ restriction on the first character is relaxed to allow either a
+ letter or a digit. Host software MUST support this more liberal
+ syntax.
+
+ Host software MUST handle host names of up to 63 characters and
+ SHOULD handle host names of up to 255 characters.
+
+ RFC 1034:
+ Relative names are either taken relative to a well known origin, or to a
+ list of domains used as a search list. Relative names appear mostly at
+ the user interface, where their interpretation varies from
+ implementation to implementation, and in master files, where they are
+ relative to a single origin domain name. The most common interpretation
+ uses the root "." as either the single origin or as one of the members
+ of the search list, so a multi-label relative name is often one where
+ the trailing dot has been omitted to save typing.
+
+ Since a complete domain name ends with the root label, this leads to
+ a printed form which ends in a dot.
+ */
+
+ const hostPattern = /^(([a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])\.?$/i;
+ return ((aHostName.length <= 255) && hostPattern.test(aHostName)) ? aHostName : null;
+}
+
+/**
+ * Check if aHostName is a valid IPv4 address.
+ *
+ * @param aHostName The string to check for validity.
+ * @param aAllowExtendedIPFormats If false, only IPv4 addresses in the common
+ decimal format (4 components, each up to 255)
+ * will be accepted, no hex/octal formats.
+ * @return Unobscured canonicalized address if aHostName is an IPv4 address.
+ * Returns null if it's not.
+ */
+function isLegalIPv4Address(aHostName, aAllowExtendedIPFormats)
+{
+ // Scammers frequently obscure the IP address by encoding each component as
+ // decimal, octal, hex or in some cases a mix match of each. There can even
+ // be less than 4 components where the last number covers the missing components.
+ // See the test at mailnews/base/test/unit/test_hostnameUtils.js for possible
+ // combinations.
+
+ if (!aHostName)
+ return null;
+
+ // Break the IP address down into individual components.
+ let ipComponents = aHostName.split(".");
+ let componentCount = ipComponents.length;
+ if (componentCount > 4 || (componentCount < 4 && !aAllowExtendedIPFormats))
+ return null;
+
+ /**
+ * Checks validity of an IP address component.
+ *
+ * @param aValue The component string.
+ * @param aWidth How many components does this string cover.
+ * @return The value of the component in decimal if it is valid.
+ * Returns null if it's not.
+ */
+ const kPowersOf256 = [ 1, 256, 65536, 16777216, 4294967296 ];
+ function isLegalIPv4Component(aValue, aWidth) {
+ let component;
+ // Is the component decimal?
+ if (/^(0|([1-9][0-9]{0,9}))$/.test(aValue)) {
+ component = parseInt(aValue, 10);
+ } else if (aAllowExtendedIPFormats) {
+ // Is the component octal?
+ if (/^(0[0-7]{1,12})$/.test(aValue))
+ component = parseInt(aValue, 8);
+ // Is the component hex?
+ else if (/^(0x[0-9a-f]{1,8})$/i.test(aValue))
+ component = parseInt(aValue, 16);
+ else
+ return null;
+ } else {
+ return null;
+ }
+
+ // Make sure the component in not larger than the expected maximum.
+ if (component >= kPowersOf256[aWidth])
+ return null;
+
+ return component;
+ }
+
+ for (let i = 0; i < componentCount; i++) {
+ // If we are on the last supplied component but we do not have 4,
+ // the last one covers the remaining ones.
+ let componentWidth = (i == componentCount - 1 ? 4 - i : 1);
+ let componentValue = isLegalIPv4Component(ipComponents[i], componentWidth);
+ if (componentValue == null)
+ return null;
+
+ // If we have a component spanning multiple ones, split it.
+ for (let j = 0; j < componentWidth; j++) {
+ ipComponents[i + j] = (componentValue >> ((componentWidth - 1 - j) * 8)) & 255;
+ }
+ }
+
+ // First component of zero is not valid.
+ if (ipComponents[0] == 0)
+ return null;
+
+ return ipComponents.join(".");
+}
+
+/**
+ * Check if aHostName is a valid IPv6 address.
+ *
+ * @param aHostName The string to check for validity.
+ * @return Unobscured canonicalized address if aHostName is an IPv6 address.
+ * Returns null if it's not.
+ */
+function isLegalIPv6Address(aHostName)
+{
+ if (!aHostName)
+ return null;
+
+ // Break the IP address down into individual components.
+ let ipComponents = aHostName.toLowerCase().split(":");
+
+ // Make sure there are at least 3 components.
+ if (ipComponents.length < 3)
+ return null;
+
+ let ipLength = ipComponents.length - 1;
+
+ // Take care if the last part is written in decimal using dots as separators.
+ let lastPart = isLegalIPv4Address(ipComponents[ipLength], false);
+ if (lastPart)
+ {
+ let lastPartComponents = lastPart.split(".");
+ // Convert it into standard IPv6 components.
+ ipComponents[ipLength] =
+ ((lastPartComponents[0] << 8) | lastPartComponents[1]).toString(16);
+ ipComponents[ipLength + 1] =
+ ((lastPartComponents[2] << 8) | lastPartComponents[3]).toString(16);
+ }
+
+ // Make sure that there is only one empty component.
+ let emptyIndex;
+ for (let i = 1; i < ipComponents.length - 1; i++)
+ {
+ if (ipComponents[i] == "")
+ {
+ // If we already found an empty component return null.
+ if (emptyIndex)
+ return null;
+
+ emptyIndex = i;
+ }
+ }
+
+ // If we found an empty component, extend it.
+ if (emptyIndex)
+ {
+ ipComponents[emptyIndex] = 0;
+
+ // Add components so we have a total of 8.
+ for (let count = ipComponents.length; count < 8; count++)
+ ipComponents.splice(emptyIndex, 0, 0);
+ }
+
+ // Make sure there are 8 components.
+ if (ipComponents.length != 8)
+ return null;
+
+ // Format all components to 4 character hex value.
+ for (let i = 0; i < ipComponents.length; i++)
+ {
+ if (ipComponents[i] == "")
+ ipComponents[i] = 0;
+
+ // Make sure the component is a number and it isn't larger than 0xffff.
+ if (/^[0-9a-f]{1,4}$/.test(ipComponents[i])) {
+ ipComponents[i] = parseInt(ipComponents[i], 16);
+ if (isNaN(ipComponents[i]) || ipComponents[i] > 0xffff)
+ return null;
+ }
+ else {
+ return null;
+ }
+
+ // Pad the component with 0:s.
+ ipComponents[i] = ("0000" + ipComponents[i].toString(16)).substr(-4);
+ }
+
+ // TODO: support Zone indices in Link-local addresses? Currently they are rejected.
+ // http://en.wikipedia.org/wiki/IPv6_address#Link-local_addresses_and_zone_indices
+
+ let hostName = ipComponents.join(":");
+ // Treat 0000:0000:0000:0000:0000:0000:0000:0000 as an invalid IPv6 address.
+ return (hostName != "0000:0000:0000:0000:0000:0000:0000:0000") ?
+ hostName : null;
+}
+
+/**
+ * Check if aHostName is a valid IP address (IPv4 or IPv6).
+ *
+ * @param aHostName The string to check for validity.
+ * @param aAllowExtendedIPFormats Allow hex/octal formats in addition to decimal.
+ * @return Unobscured canonicalized IPv4 or IPv6 address if it is valid,
+ * otherwise null.
+ */
+function isLegalIPAddress(aHostName, aAllowExtendedIPFormats)
+{
+ return isLegalIPv4Address(aHostName, aAllowExtendedIPFormats) ||
+ isLegalIPv6Address(aHostName);
+}
+
+/**
+ * Check if aIPAddress is a local or private IP address.
+ *
+ * @param aIPAddress A valid IP address literal in canonical (unobscured) form.
+ * @return True if it is a local/private IPv4 or IPv6 address,
+ * otherwise false.
+ *
+ * Note: if the passed in address is not in canonical (unobscured form),
+ * the result may be wrong.
+ */
+function isLegalLocalIPAddress(aIPAddress)
+{
+ // IPv4 address?
+ let ipComponents = aIPAddress.split(".");
+ if (ipComponents.length == 4)
+ {
+ // Check if it's a local or private IPv4 address.
+ return ipComponents[0] == 10 ||
+ ipComponents[0] == 127 || // loopback address
+ (ipComponents[0] == 192 && ipComponents[1] == 168) ||
+ (ipComponents[0] == 169 && ipComponents[1] == 254) ||
+ (ipComponents[0] == 172 && ipComponents[1] >= 16 && ipComponents[1] < 32);
+ }
+
+ // IPv6 address?
+ ipComponents = aIPAddress.split(":");
+ if (ipComponents.length == 8)
+ {
+ // ::1/128 - localhost
+ if (ipComponents[0] == "0000" && ipComponents[1] == "0000" &&
+ ipComponents[2] == "0000" && ipComponents[3] == "0000" &&
+ ipComponents[4] == "0000" && ipComponents[5] == "0000" &&
+ ipComponents[6] == "0000" && ipComponents[7] == "0001")
+ return true;
+
+ // fe80::/10 - link local addresses
+ if (ipComponents[0] == "fe80")
+ return true;
+
+ // fc00::/7 - unique local addresses
+ if (ipComponents[0].startsWith("fc") || // usage has not been defined yet
+ ipComponents[0].startsWith("fd"))
+ return true;
+
+ return false;
+ }
+
+ return false;
+}
+
+/**
+ * Clean up the hostname or IP. Usually used to sanitize a value input by the user.
+ * It is usually applied before we know if the hostname is even valid.
+ *
+ * @param aHostName The hostname or IP string to clean up.
+ */
+function cleanUpHostName(aHostName)
+{
+ // TODO: Bug 235312: if UTF8 string was input, convert to punycode using convertUTF8toACE()
+ // but bug 563172 needs resolving first.
+ return aHostName.trim();
+}
diff --git a/mailnews/base/util/iteratorUtils.jsm b/mailnews/base/util/iteratorUtils.jsm
new file mode 100644
index 000000000..266c4d7b6
--- /dev/null
+++ b/mailnews/base/util/iteratorUtils.jsm
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains helper methods for dealing with XPCOM iterators (arrays
+ * and enumerators) in JS-friendly ways.
+ */
+
+this.EXPORTED_SYMBOLS = ["fixIterator", "toXPCOMArray", "toArray"];
+
+Components.utils.import("resource://gre/modules/Deprecated.jsm");
+
+var Ci = Components.interfaces;
+
+var JS_HAS_SYMBOLS = typeof Symbol === "function";
+var ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
+
+/**
+ * This function will take a number of objects and convert them to an array.
+ *
+ * Currently, we support the following objects:
+ * Anything you can for (let x of aObj) on
+ * (e.g. toArray(fixIterator(enum))[4],
+ * also a NodeList from element.childNodes)
+ *
+ * @param aObj The object to convert
+ */
+function toArray(aObj) {
+ if (ITERATOR_SYMBOL in aObj) {
+ return Array.from(aObj);
+ }
+
+ // We got something unexpected, notify the caller loudly.
+ throw new Error("An unsupported object sent to toArray: " +
+ (("toString" in aObj) ? aObj.toString() : aObj));
+}
+
+/**
+ * Given a JS array, JS iterator, or one of a variety of XPCOM collections or
+ * iterators, return a JS iterator suitable for use in a for...of expression.
+ *
+ * Currently, we support the following types of XPCOM iterators:
+ * nsIArray
+ * nsISupportsArray
+ * nsISimpleEnumerator
+ *
+ * This intentionally does not support nsIEnumerator as it is obsolete and
+ * no longer used in the base code.
+ *
+ * Note that old-style JS iterators are explicitly not supported in this
+ * method, as they are going away. For a limited time, the resulting iterator
+ * can be used in a for...in loop, but this is a legacy compatibility shim that
+ * will not work forever. See bug 1098412.
+ *
+ * @param aEnum the enumerator to convert
+ * @param aIface (optional) an interface to QI each object to prior to
+ * returning
+ *
+ * @note This returns an object that can be used in 'for...of' loops.
+ * Do not use 'for each...in'. 'for...in' may be used, but only as a
+ * legacy feature.
+ * This does *not* return an Array object. To create such an array, use
+ * let array = toArray(fixIterator(xpcomEnumerator));
+ */
+function fixIterator(aEnum, aIface) {
+ // Minor internal details: to support both for (let x of fixIterator()) and
+ // for (let x in fixIterator()), we need to add in a __iterator__ kludge
+ // property. __iterator__ is to go away in bug 1098412; we could theoretically
+ // make it work beyond that by using Proxies, but that's far to go for
+ // something we want to get rid of anyways.
+ // Note that the new-style iterator uses Symbol.iterator to work, and anything
+ // that has Symbol.iterator works with for-of.
+ function makeDualIterator(newStyle) {
+ newStyle.__iterator__ = function() {
+ for (let item of newStyle)
+ yield item;
+ };
+ return newStyle;
+ }
+
+ // If the input is an array or something that sports Symbol.iterator, then
+ // the original input is sufficient to directly return. However, if we want
+ // to support the aIface parameter, we need to do a lazy version of Array.map.
+ if (Array.isArray(aEnum) || ITERATOR_SYMBOL in aEnum) {
+ if (!aIface) {
+ return makeDualIterator(aEnum);
+ } else {
+ return makeDualIterator((function*() {
+ for (let o of aEnum)
+ yield o.QueryInterface(aIface);
+ })());
+ }
+ }
+
+ let face = aIface || Ci.nsISupports;
+ // Figure out which kind of array object we have.
+ // First try nsIArray (covers nsIMutableArray too).
+ if (aEnum instanceof Ci.nsIArray) {
+ return makeDualIterator((function*() {
+ let count = aEnum.length;
+ for (let i = 0; i < count; i++)
+ yield aEnum.queryElementAt(i, face);
+ })());
+ }
+
+ // Try an nsISupportsArray.
+ // This object is deprecated, but we need to keep supporting it
+ // while anything in the base code (including mozilla-central) produces it.
+ if (aEnum instanceof Ci.nsISupportsArray) {
+ return makeDualIterator((function*() {
+ let count = aEnum.Count();
+ for (let i = 0; i < count; i++)
+ yield aEnum.QueryElementAt(i, face);
+ })());
+ }
+
+ // How about nsISimpleEnumerator? This one is nice and simple.
+ if (aEnum instanceof Ci.nsISimpleEnumerator) {
+ return makeDualIterator((function*() {
+ while (aEnum.hasMoreElements())
+ yield aEnum.getNext().QueryInterface(face);
+ })());
+ }
+
+ // We got something unexpected, notify the caller loudly.
+ throw new Error("An unsupported object sent to fixIterator: " +
+ (("toString" in aEnum) ? aEnum.toString() : aEnum));
+}
+
+/**
+ * This function takes an Array object and returns an XPCOM array
+ * of the desired type. It will *not* work if you extend Array.prototype.
+ *
+ * @param aArray the array (anything fixIterator supports) to convert to an XPCOM array
+ * @param aInterface the type of XPCOM array to convert
+ *
+ * @note The returned array is *not* dynamically updated. Changes made to the
+ * JS array after a call to this function will not be reflected in the
+ * XPCOM array.
+ */
+function toXPCOMArray(aArray, aInterface) {
+ if (aInterface.equals(Ci.nsISupportsArray)) {
+ Deprecated.warning("nsISupportsArray object is deprecated, avoid creating new ones.",
+ "https://developer.mozilla.org/en-US/docs/XPCOM_array_guide");
+ let supportsArray = Components.classes["@mozilla.org/supports-array;1"]
+ .createInstance(Ci.nsISupportsArray);
+ for (let item of fixIterator(aArray)) {
+ supportsArray.AppendElement(item);
+ }
+ return supportsArray;
+ }
+
+ if (aInterface.equals(Ci.nsIMutableArray)) {
+ let mutableArray = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ for (let item of fixIterator(aArray)) {
+ mutableArray.appendElement(item, false);
+ }
+ return mutableArray;
+ }
+
+ // We got something unexpected, notify the caller loudly.
+ throw new Error("An unsupported interface requested from toXPCOMArray: " +
+ aInterface);
+}
diff --git a/mailnews/base/util/jsTreeSelection.js b/mailnews/base/util/jsTreeSelection.js
new file mode 100644
index 000000000..0ba5ea42c
--- /dev/null
+++ b/mailnews/base/util/jsTreeSelection.js
@@ -0,0 +1,654 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['JSTreeSelection'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * Partial nsITreeSelection implementation so that we can have nsMsgDBViews that
+ * exist only for message display but do not need to be backed by a full
+ * tree view widget. This could also hopefully be used for more xpcshell unit
+ * testing of the FolderDisplayWidget. It might also be useful for creating
+ * transient selections when right-click selection happens.
+ *
+ * Our current limitations:
+ * - We do not support any single selection modes. This is mainly because we
+ * need to look at the box object for that and we don't want to do it.
+ * - Timed selection. Our expected consumers don't use it.
+ *
+ * Our current laziness:
+ * - We aren't very precise about invalidation when it would be potentially
+ * complicated. The theory is that if there is a tree box object, it's
+ * probably native and the XPConnect overhead is probably a lot more than
+ * any potential savings, at least for now when the tree display is
+ * generally C++ XPCOM backed rather than JS XPCOM backed. Also, we
+ * aren't intended to actually be used with a real tree display; you should
+ * be using the C++ object in that case!
+ *
+ * If documentation is omitted for something, it is because we have little to
+ * add to the documentation of nsITreeSelection and really hope that our
+ * documentation tool will copy-down that documentation.
+ *
+ * This implementation attempts to mimic the behavior of nsTreeSelection. In
+ * a few cases, this leads to potentially confusing actions. I attempt to note
+ * when we are doing this and why we do it.
+ *
+ * Unit test is in mailnews/base/util/test_jsTreeSelection.js
+ */
+function JSTreeSelection(aTreeBoxObject) {
+ this._treeBoxObject = aTreeBoxObject;
+
+ this._currentIndex = null;
+ this._shiftSelectPivot = null;
+ this._ranges = [];
+ this._count = 0;
+
+ this._selectEventsSuppressed = false;
+}
+JSTreeSelection.prototype = {
+ /**
+ * The current nsITreeBoxObject, appropriately QueryInterfaced. May be null.
+ */
+ _treeBoxObject: null,
+
+ /**
+ * Where the focus rectangle (that little dotted thing) shows up. Just
+ * because something is focused does not mean it is actually selected.
+ */
+ _currentIndex: null,
+ /**
+ * The view index where the shift is anchored when it is not (conceptually)
+ * the same as _currentIndex. This only happens when you perform a ranged
+ * selection. In that case, the start index of the ranged selection becomes
+ * the shift pivot (and the _currentIndex becomes the end of the ranged
+ * selection.)
+ * It gets cleared whenever the selection changes and it's not the result of
+ * a call to rangedSelect.
+ */
+ _shiftSelectPivot: null,
+ /**
+ * A list of [lowIndexInclusive, highIndexInclusive] non-overlapping,
+ * non-adjacent 'tuples' sort in ascending order.
+ */
+ _ranges: [],
+ /**
+ * The number of currently selected rows.
+ */
+ _count: 0,
+
+ // In the case of the stand-alone message window, there's no tree, but
+ // there's a view.
+ _view: null,
+
+ get tree() {
+ return this._treeBoxObject;
+ },
+ set tree(aTreeBoxObject) {
+ this._treeBoxObject = aTreeBoxObject;
+ },
+
+ set view(aView) {
+ this._view = aView;
+ },
+ /**
+ * Although the nsITreeSelection documentation doesn't say, what this method
+ * is supposed to do is check if the seltype attribute on the XUL tree is any
+ * of the following: "single" (only a single row may be selected at a time,
+ * "cell" (a single cell may be selected), or "text" (the row gets selected
+ * but only the primary column shows up as selected.)
+ *
+ * @return false because we don't support single-selection.
+ */
+ get single() {
+ return false;
+ },
+
+ _updateCount: function JSTreeSelection__updateCount() {
+ this._count = 0;
+ for (let [low, high] of this._ranges) {
+ this._count += high - low + 1;
+ }
+ },
+
+ get count() {
+ return this._count;
+ },
+
+ isSelected: function JSTreeSelection_isSelected(aViewIndex) {
+ for (let [low, high] of this._ranges) {
+ if (aViewIndex >= low && aViewIndex <= high)
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Select the given row. It does nothing if that row was already selected.
+ */
+ select: function JSTreeSelection_select(aViewIndex) {
+ // current index will provide our effective shift pivot
+ this._shiftSelectPivot = null;
+ this.currentIndex = aViewIndex;
+
+ if (this._count == 1 && this._ranges[0][0] == aViewIndex)
+ return;
+
+ this._count = 1;
+ this._ranges = [[aViewIndex, aViewIndex]];
+
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+
+ this._fireSelectionChanged();
+ },
+
+ timedSelect: function JSTreeSelection_timedSelect(aIndex, aDelay) {
+ throw new Error("We do not implement timed selection.");
+ },
+
+ toggleSelect: function JSTreeSelection_toggleSelect(aIndex) {
+ this.currentIndex = aIndex;
+ // If nothing's selected, select aIndex
+ if (this._count == 0) {
+ this._count = 1;
+ this._ranges = [[aIndex, aIndex]];
+ }
+ else for (let [iTupe, [low, high]] of this._ranges.entries()) {
+ // below the range? add it to the existing range or create a new one
+ if (aIndex < low) {
+ this._count++;
+ // is it just below an existing range? (range fusion only happens in the
+ // high case, not here.)
+ if (aIndex == low - 1) {
+ this._ranges[iTupe][0] = aIndex;
+ break;
+ }
+ // then it gets its own range
+ this._ranges.splice(iTupe, 0, [aIndex, aIndex]);
+ break;
+ }
+ // in the range? will need to either nuke, shrink, or split the range to
+ // remove it
+ if (aIndex >= low && aIndex <= high) {
+ this._count--;
+ // nuke
+ if (aIndex == low && aIndex == high)
+ this._ranges.splice(iTupe, 1);
+ // lower shrink
+ else if (aIndex == low)
+ this._ranges[iTupe][0] = aIndex + 1;
+ // upper shrink
+ else if (aIndex == high)
+ this._ranges[iTupe][1] = aIndex - 1;
+ // split
+ else
+ this._ranges.splice(iTupe, 1, [low, aIndex - 1], [aIndex + 1, high]);
+ break;
+ }
+ // just above the range? fuse into the range, and possibly the next
+ // range up.
+ if (aIndex == high + 1) {
+ this._count++;
+ // see if there is another range and there was just a gap of one between
+ // the two ranges.
+ if ((iTupe + 1 < this._ranges.length) &&
+ (this._ranges[iTupe+1][0] == aIndex + 1)) {
+ // yes, merge the ranges
+ this._ranges.splice(iTupe, 2, [low, this._ranges[iTupe+1][1]]);
+ break;
+ }
+ // nope, no merge required, just update the range
+ this._ranges[iTupe][1] = aIndex;
+ break;
+ }
+ // otherwise we need to keep going
+ }
+
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidateRow(aIndex);
+ this._fireSelectionChanged();
+ },
+
+ /**
+ * @param aRangeStart If omitted, it implies a shift-selection is happening,
+ * in which case we use _shiftSelectPivot as the start if we have it,
+ * _currentIndex if we don't, and if we somehow didn't have a
+ * _currentIndex, we use the range end.
+ * @param aRangeEnd Just the inclusive end of the range.
+ * @param aAugment Does this set a new selection or should it be merged with
+ * the existing selection?
+ */
+ rangedSelect: function JSTreeSelection_rangedSelect(aRangeStart, aRangeEnd,
+ aAugment) {
+ if (aRangeStart == -1) {
+ if (this._shiftSelectPivot != null)
+ aRangeStart = this._shiftSelectPivot;
+ else if (this._currentIndex != null)
+ aRangeStart = this._currentIndex;
+ else
+ aRangeStart = aRangeEnd;
+ }
+
+ this._shiftSelectPivot = aRangeStart;
+ this.currentIndex = aRangeEnd;
+
+ // enforce our ordering constraint for our ranges
+ if (aRangeStart > aRangeEnd)
+ [aRangeStart, aRangeEnd] = [aRangeEnd, aRangeStart];
+
+ // if we're not augmenting, then this is really easy.
+ if (!aAugment) {
+ this._count = aRangeEnd - aRangeStart + 1;
+ this._ranges = [[aRangeStart, aRangeEnd]];
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this._fireSelectionChanged();
+ return;
+ }
+
+ // Iterate over our existing set of ranges, finding the 'range' of ranges
+ // that our new range overlaps or simply obviates.
+ // Overlap variables track blocks we need to keep some part of, Nuke
+ // variables are for blocks that get spliced out. For our purposes, all
+ // overlap blocks are also nuke blocks.
+ let lowOverlap, lowNuke, highNuke, highOverlap;
+ // in case there is no overlap, also figure an insertionPoint
+ let insertionPoint = this._ranges.length; // default to the end
+ for (let [iTupe, [low, high]] of this._ranges.entries()) {
+ // If it's completely include the range, it should be nuked
+ if (aRangeStart <= low && aRangeEnd >= high) {
+ if (lowNuke == null) // only the first one we see is the low one
+ lowNuke = iTupe;
+ highNuke = iTupe;
+ }
+ // If our new range start is inside a range or is adjacent, it's overlap
+ if (aRangeStart >= low - 1 && aRangeStart <= high + 1 &&
+ lowOverlap == null)
+ lowOverlap = lowNuke = highNuke = iTupe;
+ // If our new range ends inside a range or is adjacent, it's overlap
+ if (aRangeEnd >= low - 1 && aRangeEnd <= high + 1) {
+ highOverlap = highNuke = iTupe;
+ if (lowNuke == null)
+ lowNuke = iTupe;
+ }
+
+ // we're done when no more overlap is possible
+ if (aRangeEnd < low) {
+ insertionPoint = iTupe;
+ break;
+ }
+ }
+
+ if (lowOverlap != null)
+ aRangeStart = Math.min(aRangeStart, this._ranges[lowOverlap][0]);
+ if (highOverlap != null)
+ aRangeEnd = Math.max(aRangeEnd, this._ranges[highOverlap][1]);
+ if (lowNuke != null)
+ this._ranges.splice(lowNuke, highNuke - lowNuke + 1,
+ [aRangeStart, aRangeEnd]);
+ else
+ this._ranges.splice(insertionPoint, 0, [aRangeStart, aRangeEnd]);
+
+ this._updateCount();
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this._fireSelectionChanged();
+ },
+
+ /**
+ * This is basically RangedSelect but without insertion of a new range and we
+ * don't need to worry about adjacency.
+ * Oddly, nsTreeSelection doesn't fire a selection changed event here...
+ */
+ clearRange: function JSTreeSelection_clearRange(aRangeStart, aRangeEnd) {
+ // Iterate over our existing set of ranges, finding the 'range' of ranges
+ // that our clear range overlaps or simply obviates.
+ // Overlap variables track blocks we need to keep some part of, Nuke
+ // variables are for blocks that get spliced out. For our purposes, all
+ // overlap blocks are also nuke blocks.
+ let lowOverlap, lowNuke, highNuke, highOverlap;
+ for (let [iTupe, [low, high]] of this._ranges.entries()) {
+ // If we completely include the range, it should be nuked
+ if (aRangeStart <= low && aRangeEnd >= high) {
+ if (lowNuke == null) // only the first one we see is the low one
+ lowNuke = iTupe;
+ highNuke = iTupe;
+ }
+ // If our new range start is inside a range, it's nuke and maybe overlap
+ if (aRangeStart >= low && aRangeStart <= high && lowNuke == null) {
+ lowNuke = highNuke = iTupe;
+ // it's only overlap if we don't match at the low end
+ if (aRangeStart > low)
+ lowOverlap = iTupe;
+ }
+ // If our new range ends inside a range, it's nuke and maybe overlap
+ if (aRangeEnd >= low && aRangeEnd <= high) {
+ highNuke = iTupe;
+ // it's only overlap if we don't match at the high end
+ if (aRangeEnd < high)
+ highOverlap = iTupe;
+ if (lowNuke == null)
+ lowNuke = iTupe;
+ }
+
+ // we're done when no more overlap is possible
+ if (aRangeEnd < low)
+ break;
+ }
+ // nothing to do since there's nothing to nuke
+ if (lowNuke == null)
+ return;
+ let args = [lowNuke, highNuke - lowNuke + 1];
+ if (lowOverlap != null)
+ args.push([this._ranges[lowOverlap][0], aRangeStart - 1]);
+ if (highOverlap != null)
+ args.push([aRangeEnd + 1, this._ranges[highOverlap][1]]);
+ this._ranges.splice.apply(this._ranges, args);
+
+ this._updateCount();
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ // note! nsTreeSelection doesn't fire a selection changed event, so neither
+ // do we, but it seems like we should
+ },
+
+ /**
+ * nsTreeSelection always fires a select notification when the range is
+ * cleared, even if there is no effective chance in selection.
+ */
+ clearSelection: function JSTreeSelection_clearSelection() {
+ this._shiftSelectPivot = null;
+ this._count = 0;
+ this._ranges = [];
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this._fireSelectionChanged();
+ },
+
+ /**
+ * Not even nsTreeSelection implements this.
+ */
+ invertSelection: function JSTreeSelection_invertSelection() {
+ throw new Error("Who really was going to use this?");
+ },
+
+ /**
+ * Select all with no rows is a no-op, otherwise we select all and notify.
+ */
+ selectAll: function JSTreeSelection_selectAll() {
+ if (!this._view)
+ return;
+
+ let view = this._view;
+ let rowCount = view.rowCount;
+
+ // no-ops-ville
+ if (!rowCount)
+ return;
+
+ this._count = rowCount;
+ this._ranges = [[0, rowCount - 1]];
+
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this._fireSelectionChanged();
+ },
+
+ getRangeCount: function JSTreeSelection_getRangeCount() {
+ return this._ranges.length;
+ },
+ getRangeAt: function JSTreeSelection_getRangeAt(aRangeIndex, aMinObj,
+ aMaxObj) {
+ if (aRangeIndex < 0 || aRangeIndex > this._ranges.length)
+ throw new Exception("Try a real range index next time.");
+ [aMinObj.value, aMaxObj.value] = this._ranges[aRangeIndex];
+ },
+
+ invalidateSelection: function JSTreeSelection_invalidateSelection() {
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ },
+
+ /**
+ * Helper method to adjust points in the face of row additions/removal.
+ * @param aPoint The point, null if there isn't one, or an index otherwise.
+ * @param aDeltaAt The row at which the change is happening.
+ * @param aDelta The number of rows added if positive, or the (negative)
+ * number of rows removed.
+ */
+ _adjustPoint: function JSTreeSelection__adjustPoint(aPoint, aDeltaAt,
+ aDelta) {
+ // if there is no point, no change
+ if (aPoint == null)
+ return aPoint;
+ // if the point is before the change, no change
+ if (aPoint < aDeltaAt)
+ return aPoint;
+ // if it's a deletion and it includes the point, clear it
+ if (aDelta < 0 && aPoint >= aDeltaAt && (aPoint + aDelta < aDeltaAt))
+ return null;
+ // (else) the point is at/after the change, compensate
+ return aPoint + aDelta;
+ },
+ /**
+ * Find the index of the range, if any, that contains the given index, and
+ * the index at which to insert a range if one does not exist.
+ *
+ * @return A tuple containing: 1) the index if there is one, null otherwise,
+ * 2) the index at which to insert a range that would contain the point.
+ */
+ _findRangeContainingRow:
+ function JSTreeSelection__findRangeContainingRow(aIndex) {
+ for (let [iTupe, [low, high]] of this._ranges.entries()) {
+ if (aIndex >= low && aIndex <= high)
+ return [iTupe, iTupe];
+ if (aIndex < low)
+ return [null, iTupe];
+ }
+ return [null, this._ranges.length];
+ },
+
+
+ /**
+ * When present, a list of calls made to adjustSelection. See
+ * |logAdjustSelectionForReplay| and |replayAdjustSelectionLog|.
+ */
+ _adjustSelectionLog: null,
+ /**
+ * Start logging calls to adjustSelection made against this instance. You
+ * would do this because you are replacing an existing selection object
+ * with this instance for the purposes of creating a transient selection.
+ * Of course, you want the original selection object to be up-to-date when
+ * you go to put it back, so then you can call replayAdjustSelectionLog
+ * with that selection object and everything will be peachy.
+ */
+ logAdjustSelectionForReplay:
+ function JSTreeSelection_logAdjustSelectionForReplay() {
+ this._adjustSelectionLog = [];
+ },
+ /**
+ * Stop logging calls to adjustSelection and replay the existing log against
+ * aSelection.
+ *
+ * @param aSelection {nsITreeSelection}.
+ */
+ replayAdjustSelectionLog:
+ function JSTreeSelection_replayAdjustSelectionLog(aSelection) {
+ if (this._adjustSelectionLog.length) {
+ // Temporarily disable selection events because adjustSelection is going
+ // to generate an event each time otherwise, and better 1 event than
+ // many.
+ aSelection.selectEventsSuppressed = true;
+ for (let [index, count] of this._adjustSelectionLog) {
+ aSelection.adjustSelection(index, count);
+ }
+ aSelection.selectEventsSuppressed = false;
+ }
+ this._adjustSelectionLog = null;
+ },
+
+ adjustSelection: function JSTreeSelection_adjustSelection(aIndex, aCount) {
+ // nothing to do if there is no actual change
+ if (!aCount)
+ return;
+
+ if (this._adjustSelectionLog)
+ this._adjustSelectionLog.push([aIndex, aCount]);
+
+ // adjust our points
+ this._shiftSelectPivot = this._adjustPoint(this._shiftSelectPivot,
+ aIndex, aCount);
+ this._currentIndex = this._adjustPoint(this._currentIndex, aIndex, aCount);
+
+ // If we are adding rows, we want to split any range at aIndex and then
+ // translate all of the ranges above that point up.
+ if (aCount > 0) {
+ let [iContain, iInsert] = this._findRangeContainingRow(aIndex);
+ if (iContain != null) {
+ let [low, high] = this._ranges[iContain];
+ // if it is the low value, we just want to shift the range entirely, so
+ // do nothing (and keep iInsert pointing at it for translation)
+ // if it is not the low value, then there must be at least two values so
+ // we should split it and only translate the new/upper block
+ if (aIndex != low) {
+ this._ranges.splice(iContain, 1, [low, aIndex - 1], [aIndex, high]);
+ iInsert++;
+ }
+ }
+ // now translate everything from iInsert on up
+ for (let iTrans = iInsert; iTrans < this._ranges.length; iTrans++) {
+ let [low, high] = this._ranges[iTrans];
+ this._ranges[iTrans] = [low + aCount, high + aCount];
+ }
+ // invalidate and fire selection change notice
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this._fireSelectionChanged();
+ return;
+ }
+
+ // If we are removing rows, we are basically clearing the range that is
+ // getting deleted and translating everyone above the remaining point
+ // downwards. The one trick is we may have to merge the lowest translated
+ // block.
+ let saveSuppress = this.selectEventsSuppressed;
+ this.selectEventsSuppressed = true;
+ this.clearRange(aIndex, aIndex - aCount - 1);
+ // translate
+ let iTrans = this._findRangeContainingRow(aIndex)[1];
+ for (; iTrans < this._ranges.length; iTrans++) {
+ let [low, high] = this._ranges[iTrans];
+ // for the first range, low may be below the index, in which case it
+ // should not get translated
+ this._ranges[iTrans] = [(low >= aIndex) ? low + aCount : low,
+ high + aCount];
+ }
+ // we may have to merge the lowest translated block because it may now be
+ // adjacent to the previous block
+ if (iTrans > 0 && iTrans < this._ranges.length &&
+ this._ranges[iTrans-1][1] == this_ranges[iTrans][0]) {
+ this._ranges[iTrans-1][1] = this._ranges[iTrans][1];
+ this._ranges.splice(iTrans, 1);
+ }
+
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this.selectEventsSuppressed = saveSuppress;
+ },
+
+ get selectEventsSuppressed() {
+ return this._selectEventsSuppressed;
+ },
+ /**
+ * Control whether selection events are suppressed. For consistency with
+ * nsTreeSelection, we always generate a selection event when a value of
+ * false is assigned, even if the value was already false.
+ */
+ set selectEventsSuppressed(aSuppress) {
+ this._selectEventsSuppressed = aSuppress;
+ if (!aSuppress)
+ this._fireSelectionChanged();
+ },
+
+ /**
+ * Note that we bypass any XUL "onselect" handler that may exist and go
+ * straight to the view. If you have a tree, you shouldn't be using us,
+ * so this seems aboot right.
+ */
+ _fireSelectionChanged: function JSTreeSelection__fireSelectionChanged() {
+ // don't fire if we are suppressed; we will fire when un-suppressed
+ if (this.selectEventsSuppressed)
+ return;
+ let view;
+ if (this._treeBoxObject && this._treeBoxObject.view)
+ view = this._treeBoxObject.view;
+ else
+ view = this._view;
+
+ // We might not have a view if we're in the middle of setting up things
+ if (view) {
+ view = view.QueryInterface(Ci.nsITreeView);
+ view.selectionChanged();
+ }
+ },
+
+ get currentIndex() {
+ if (this._currentIndex == null)
+ return -1;
+ return this._currentIndex;
+ },
+ /**
+ * Sets the current index. Other than updating the variable, this just
+ * invalidates the tree row if we have a tree.
+ * The real selection object would send a DOM event we don't care about.
+ */
+ set currentIndex(aIndex) {
+ if (aIndex == this.currentIndex)
+ return;
+
+ this._currentIndex = (aIndex != -1) ? aIndex : null;
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidateRow(aIndex);
+ },
+
+ currentColumn: null,
+
+ get shiftSelectPivot() {
+ return this._shiftSelectPivot != null ? this._shiftSelectPivot : -1;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsITreeSelection]),
+
+ /*
+ * Functions after this aren't part of the nsITreeSelection interface.
+ */
+
+ /**
+ * Duplicate this selection on another nsITreeSelection. This is useful
+ * when you would like to discard this selection for a real tree selection.
+ * We assume that both selections are for the same tree.
+ *
+ * @note We don't transfer the correct shiftSelectPivot over.
+ * @note This will fire a selectionChanged event on the tree view.
+ *
+ * @param aSelection an nsITreeSelection to duplicate this selection onto
+ */
+ duplicateSelection: function JSTreeSelection_duplicateSelection(aSelection) {
+ aSelection.selectEventsSuppressed = true;
+ aSelection.clearSelection();
+ for (let [iTupe, [low, high]] of this._ranges.entries())
+ aSelection.rangedSelect(low, high, iTupe > 0);
+
+ aSelection.currentIndex = this.currentIndex;
+ // This will fire a selectionChanged event
+ aSelection.selectEventsSuppressed = false;
+ },
+};
diff --git a/mailnews/base/util/mailServices.js b/mailnews/base/util/mailServices.js
new file mode 100644
index 000000000..f6bf9ca01
--- /dev/null
+++ b/mailnews/base/util/mailServices.js
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ["MailServices"];
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var MailServices = {};
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "mailSession",
+ "@mozilla.org/messenger/services/session;1",
+ "nsIMsgMailSession");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "accounts",
+ "@mozilla.org/messenger/account-manager;1",
+ "nsIMsgAccountManager");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "pop3",
+ "@mozilla.org/messenger/popservice;1",
+ "nsIPop3Service");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "imap",
+ "@mozilla.org/messenger/imapservice;1",
+ "nsIImapService");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "nntp",
+ "@mozilla.org/messenger/nntpservice;1",
+ "nsINntpService");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "smtp",
+ "@mozilla.org/messengercompose/smtp;1",
+ "nsISmtpService");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "compose",
+ "@mozilla.org/messengercompose;1",
+ "nsIMsgComposeService");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "ab",
+ "@mozilla.org/abmanager;1",
+ "nsIAbManager");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "copy",
+ "@mozilla.org/messenger/messagecopyservice;1",
+ "nsIMsgCopyService");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "mfn",
+ "@mozilla.org/messenger/msgnotificationservice;1",
+ "nsIMsgFolderNotificationService");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "headerParser",
+ "@mozilla.org/messenger/headerparser;1",
+ "nsIMsgHeaderParser");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "mimeConverter",
+ "@mozilla.org/messenger/mimeconverter;1",
+ "nsIMimeConverter");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "tags",
+ "@mozilla.org/messenger/tagservice;1",
+ "nsIMsgTagService");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "filters",
+ "@mozilla.org/messenger/services/filters;1",
+ "nsIMsgFilterService");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "junk",
+ "@mozilla.org/messenger/filter-plugin;1?name=bayesianfilter",
+ "nsIJunkMailPlugin");
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "newMailNotification",
+ "@mozilla.org/newMailNotificationService;1",
+ "mozINewMailNotificationService");
diff --git a/mailnews/base/util/mailnewsMigrator.js b/mailnews/base/util/mailnewsMigrator.js
new file mode 100644
index 000000000..2e3be6906
--- /dev/null
+++ b/mailnews/base/util/mailnewsMigrator.js
@@ -0,0 +1,203 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Migrate profile (prefs and other files) from older versions of Mailnews to
+ * current.
+ * This should be run at startup. It migrates as needed: each migration
+ * function should be written to be a no-op when the value is already migrated
+ * or was never used in the old version.
+ */
+
+this.EXPORTED_SYMBOLS = [ "migrateMailnews" ];
+
+Components.utils.import("resource:///modules/errUtils.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+var Ci = Components.interfaces;
+var kServerPrefVersion = 1;
+var kSmtpPrefVersion = 1;
+var kABRemoteContentPrefVersion = 1;
+var kDefaultCharsetsPrefVersion = 1;
+
+function migrateMailnews()
+{
+ try {
+ MigrateServerAuthPref();
+ } catch (e) { logException(e); }
+
+ try {
+ MigrateABRemoteContentSettings();
+ } catch (e) { logException(e); }
+
+ try {
+ MigrateDefaultCharsets();
+ } catch (e) { logException(e); }
+}
+
+/**
+ * Migrates from pref useSecAuth to pref authMethod
+ */
+function MigrateServerAuthPref()
+{
+ try {
+ // comma-separated list of all accounts.
+ var accounts = Services.prefs.getCharPref("mail.accountmanager.accounts")
+ .split(",");
+ for (let i = 0; i < accounts.length; i++)
+ {
+ let accountKey = accounts[i]; // e.g. "account1"
+ if (!accountKey)
+ continue;
+ let serverKey = Services.prefs.getCharPref("mail.account." + accountKey +
+ ".server");
+ let server = "mail.server." + serverKey + ".";
+ if (Services.prefs.prefHasUserValue(server + "authMethod"))
+ continue;
+ if (!Services.prefs.prefHasUserValue(server + "useSecAuth") &&
+ !Services.prefs.prefHasUserValue(server + "auth_login"))
+ continue;
+ if (Services.prefs.prefHasUserValue(server + "migrated"))
+ continue;
+ // auth_login = false => old-style auth
+ // else: useSecAuth = true => "secure auth"
+ // else: cleartext pw
+ let auth_login = true;
+ let useSecAuth = false; // old default, default pref now removed
+ try {
+ auth_login = Services.prefs.getBoolPref(server + "auth_login");
+ } catch (e) {}
+ try {
+ useSecAuth = Services.prefs.getBoolPref(server + "useSecAuth");
+ } catch (e) {}
+
+ Services.prefs.setIntPref(server + "authMethod",
+ auth_login ? (useSecAuth ?
+ Ci.nsMsgAuthMethod.secure :
+ Ci.nsMsgAuthMethod.passwordCleartext) :
+ Ci.nsMsgAuthMethod.old);
+ Services.prefs.setIntPref(server + "migrated", kServerPrefVersion);
+ }
+
+ // same again for SMTP servers
+ var smtpservers = Services.prefs.getCharPref("mail.smtpservers").split(",");
+ for (let i = 0; i < smtpservers.length; i++)
+ {
+ if (!smtpservers[i])
+ continue;
+ let server = "mail.smtpserver." + smtpservers[i] + ".";
+ if (Services.prefs.prefHasUserValue(server + "authMethod"))
+ continue;
+ if (!Services.prefs.prefHasUserValue(server + "useSecAuth") &&
+ !Services.prefs.prefHasUserValue(server + "auth_method"))
+ continue;
+ if (Services.prefs.prefHasUserValue(server + "migrated"))
+ continue;
+ // auth_method = 0 => no auth
+ // else: useSecAuth = true => "secure auth"
+ // else: cleartext pw
+ let auth_method = 1;
+ let useSecAuth = false;
+ try {
+ auth_method = Services.prefs.getIntPref(server + "auth_method");
+ } catch (e) {}
+ try {
+ useSecAuth = Services.prefs.getBoolPref(server + "useSecAuth");
+ } catch (e) {}
+
+ Services.prefs.setIntPref(server + "authMethod",
+ auth_method ? (useSecAuth ?
+ Ci.nsMsgAuthMethod.secure :
+ Ci.nsMsgAuthMethod.passwordCleartext) :
+ Ci.nsMsgAuthMethod.none);
+ Services.prefs.setIntPref(server + "migrated", kSmtpPrefVersion);
+ }
+ } catch(e) { logException(e); }
+}
+
+/**
+ * The address book used to contain information about wheather to allow remote
+ * content for a given contact. Now we use the permission manager for that.
+ * Do a one-time migration for it.
+ */
+function MigrateABRemoteContentSettings()
+{
+ if (Services.prefs.prefHasUserValue("mail.ab_remote_content.migrated"))
+ return;
+
+ // Search through all of our local address books looking for a match.
+ let enumerator = MailServices.ab.directories;
+ while (enumerator.hasMoreElements())
+ {
+ let migrateAddress = function(aEmail) {
+ let uri = Services.io.newURI(
+ "chrome://messenger/content/email=" + aEmail, null, null);
+ Services.perms.add(uri, "image", Services.perms.ALLOW_ACTION);
+ }
+
+ let addrbook = enumerator.getNext()
+ .QueryInterface(Components.interfaces.nsIAbDirectory);
+ try {
+ // If it's a read-only book, don't try to find a card as we we could never
+ // have set the AllowRemoteContent property.
+ if (addrbook.readOnly)
+ continue;
+
+ let childCards = addrbook.childCards;
+ while (childCards.hasMoreElements())
+ {
+ let card = childCards.getNext()
+ .QueryInterface(Components.interfaces.nsIAbCard);
+
+ if (card.getProperty("AllowRemoteContent", false) == false)
+ continue; // not allowed for this contact
+
+ if (card.primaryEmail)
+ migrateAddress(card.primaryEmail);
+
+ if (card.getProperty("SecondEmail", ""))
+ migrateAddress(card.getProperty("SecondEmail", ""));
+ }
+ } catch (e) { logException(e); }
+ }
+
+ Services.prefs.setIntPref("mail.ab_remote_content.migrated",
+ kABRemoteContentPrefVersion);
+}
+
+/**
+ * If the default sending or viewing charset is one that is no longer available,
+ * change it back to the default.
+ */
+function MigrateDefaultCharsets()
+{
+ if (Services.prefs.prefHasUserValue("mail.default_charsets.migrated"))
+ return;
+
+ let charsetConvertManager = Components.classes['@mozilla.org/charset-converter-manager;1']
+ .getService(Components.interfaces.nsICharsetConverterManager);
+
+ let sendCharsetStr = Services.prefs.getComplexValue(
+ "mailnews.send_default_charset",
+ Components.interfaces.nsIPrefLocalizedString).data;
+
+ try {
+ charsetConvertManager.getCharsetTitle(sendCharsetStr);
+ } catch (e) {
+ Services.prefs.clearUserPref("mailnews.send_default_charset");
+ }
+
+ let viewCharsetStr = Services.prefs.getComplexValue(
+ "mailnews.view_default_charset",
+ Components.interfaces.nsIPrefLocalizedString).data;
+
+ try {
+ charsetConvertManager.getCharsetTitle(viewCharsetStr);
+ } catch (e) {
+ Services.prefs.clearUserPref("mailnews.view_default_charset");
+ }
+
+ Services.prefs.setIntPref("mail.default_charsets.migrated",
+ kDefaultCharsetsPrefVersion);
+}
diff --git a/mailnews/base/util/moz.build b/mailnews/base/util/moz.build
new file mode 100644
index 000000000..0f1295ffe
--- /dev/null
+++ b/mailnews/base/util/moz.build
@@ -0,0 +1,82 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'nsImapMoveCoalescer.h',
+ 'nsMsgCompressIStream.h',
+ 'nsMsgCompressOStream.h',
+ 'nsMsgDBFolder.h',
+ 'nsMsgDBFolderAtomList.h',
+ 'nsMsgI18N.h',
+ 'nsMsgIdentity.h',
+ 'nsMsgIncomingServer.h',
+ 'nsMsgKeyArray.h',
+ 'nsMsgKeySet.h',
+ 'nsMsgLineBuffer.h',
+ 'nsMsgMailNewsUrl.h',
+ 'nsMsgProtocol.h',
+ 'nsMsgReadStateTxn.h',
+ 'nsMsgTxn.h',
+ 'nsMsgUtils.h',
+]
+
+EXPORTS.mozilla.mailnews += [
+ 'ServiceList.h',
+ 'Services.h',
+]
+
+SOURCES += [
+ 'nsImapMoveCoalescer.cpp',
+ 'nsMsgCompressIStream.cpp',
+ 'nsMsgCompressOStream.cpp',
+ 'nsMsgDBFolder.cpp',
+ 'nsMsgFileStream.cpp',
+ 'nsMsgI18N.cpp',
+ 'nsMsgIdentity.cpp',
+ 'nsMsgIncomingServer.cpp',
+ 'nsMsgKeyArray.cpp',
+ 'nsMsgKeySet.cpp',
+ 'nsMsgLineBuffer.cpp',
+ 'nsMsgMailNewsUrl.cpp',
+ 'nsMsgProtocol.cpp',
+ 'nsMsgReadStateTxn.cpp',
+ 'nsMsgTxn.cpp',
+ 'nsMsgUtils.cpp',
+ 'nsStopwatch.cpp',
+ 'Services.cpp',
+]
+
+EXTRA_JS_MODULES += [
+ 'ABQueryUtils.jsm',
+ 'errUtils.js',
+ 'folderUtils.jsm',
+ 'hostnameUtils.jsm',
+ 'IOUtils.js',
+ 'iteratorUtils.jsm',
+ 'jsTreeSelection.js',
+ 'JXON.js',
+ 'mailnewsMigrator.js',
+ 'mailServices.js',
+ 'msgDBCacheManager.js',
+ 'StringBundle.js',
+ 'templateUtils.js',
+ 'traceHelper.js',
+]
+
+if CONFIG['MOZ_MAILNEWS_OAUTH2']:
+ EXTRA_JS_MODULES += [
+ 'OAuth2.jsm',
+ 'OAuth2Providers.jsm',
+ ]
+
+LOCAL_INCLUDES += [
+ '/netwerk/base'
+]
+
+FINAL_LIBRARY = 'mail'
+
+Library('msgbsutl_s')
+
+DEFINES['_IMPL_NS_MSG_BASE'] = True
diff --git a/mailnews/base/util/msgDBCacheManager.js b/mailnews/base/util/msgDBCacheManager.js
new file mode 100644
index 000000000..267962375
--- /dev/null
+++ b/mailnews/base/util/msgDBCacheManager.js
@@ -0,0 +1,175 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Message DB Cache manager
+ */
+
+/* :::::::: Constants and Helpers ::::::::::::::: */
+
+this.EXPORTED_SYMBOLS = ["msgDBCacheManager"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/gloda/log4moz.js");
+var log = Log4Moz.getConfiguredLogger("mailnews.database.dbcache");
+
+/**
+ */
+var DBCACHE_INTERVAL_DEFAULT_MS = 60000; // 1 minute
+
+/* :::::::: The Module ::::::::::::::: */
+
+var msgDBCacheManager =
+{
+ _initialized: false,
+
+ _msgDBCacheTimer: null,
+
+ _msgDBCacheTimerIntervalMS: DBCACHE_INTERVAL_DEFAULT_MS,
+
+ _dbService: null,
+
+ /**
+ * This is called on startup
+ */
+ init: function dbcachemgr_init()
+ {
+ if (this._initialized)
+ return;
+
+ this._dbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"]
+ .getService(Ci.nsIMsgDBService);
+
+ // we listen for "quit-application-granted" instead of
+ // "quit-application-requested" because other observers of the
+ // latter can cancel the shutdown.
+ Services.obs.addObserver(this, "quit-application-granted", false);
+
+ this.startPeriodicCheck();
+
+ this._initialized = true;
+ },
+
+/* ........ Timer Callback ................*/
+
+ _dbCacheCheckTimerCallback: function dbCache_CheckTimerCallback()
+ {
+ msgDBCacheManager.checkCachedDBs();
+ },
+
+/* ........ Observer Notification Handler ................*/
+
+ observe: function dbCache_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ // This is observed before any windows start unloading if something other
+ // than the last 3pane window closing requested the application be
+ // shutdown. For example, when the user quits via the file menu.
+ case "quit-application-granted":
+ Services.obs.removeObserver(this, "quit-application-granted");
+ this.stopPeriodicCheck();
+ break;
+ }
+ },
+
+/* ........ Public API ................*/
+
+ /**
+ * Stops db cache check
+ */
+ stopPeriodicCheck: function dbcache_stopPeriodicCheck()
+ {
+ if (this._dbCacheCheckTimer) {
+ this._dbCacheCheckTimer.cancel();
+
+ delete this._dbCacheCheckTimer;
+ this._dbCacheCheckTimer = null;
+ }
+ },
+
+ /**
+ * Starts periodic db cache check
+ */
+ startPeriodicCheck: function dbcache_startPeriodicCheck()
+ {
+ if (!this._dbCacheCheckTimer) {
+ this._dbCacheCheckTimer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+
+ this._dbCacheCheckTimer.initWithCallback(
+ this._dbCacheCheckTimerCallback,
+ this._msgDBCacheTimerIntervalMS,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+ }
+ },
+
+ /**
+ * Checks if any DBs need to be closed due to inactivity or too many of them open.
+ */
+ checkCachedDBs: function()
+ {
+ let idleLimit = Services.prefs.getIntPref("mail.db.idle_limit");
+ let maxOpenDBs = Services.prefs.getIntPref("mail.db.max_open");
+
+ // db.lastUseTime below is in microseconds while Date.now and idleLimit pref
+ // is in milliseconds.
+ let closeThreshold = (Date.now() - idleLimit) * 1000;
+ let cachedDBs = this._dbService.openDBs;
+ log.info("Periodic check of cached folder databases (DBs), count=" + cachedDBs.length);
+ // Count databases that are already closed or get closed now due to inactivity.
+ let numClosing = 0;
+ // Count databases whose folder is open in a window.
+ let numOpenInWindow = 0;
+ let dbs = [];
+ for (let i = 0; i < cachedDBs.length; i++) {
+ let db = cachedDBs.queryElementAt(i, Ci.nsIMsgDatabase);
+ if (!db.folder.databaseOpen) {
+ // The DB isn't really open anymore.
+ log.debug("Skipping, DB not open for folder: " + db.folder.name);
+ numClosing++;
+ continue;
+ }
+
+ if (MailServices.mailSession.IsFolderOpenInWindow(db.folder)) {
+ // The folder is open in a window so this DB must not be closed.
+ log.debug("Skipping, DB open in window for folder: " + db.folder.name);
+ numOpenInWindow++;
+ continue;
+ }
+
+ if (db.lastUseTime < closeThreshold)
+ {
+ // DB open too log without activity.
+ log.debug("Closing expired DB for folder: " + db.folder.name);
+ db.folder.msgDatabase = null;
+ numClosing++;
+ continue;
+ }
+
+ // Database eligible for closing.
+ dbs.push(db);
+ }
+ log.info("DBs open in a window: " + numOpenInWindow + ", DBs open: " + dbs.length + ", DBs already closing: " + numClosing);
+ let dbsToClose = Math.max(dbs.length - Math.max(maxOpenDBs - numOpenInWindow, 0), 0);
+ if (dbsToClose > 0) {
+ // Close some DBs so that we do not have more than maxOpenDBs.
+ // However, we skipped DBs for folders that are open in a window
+ // so if there are so many windows open, it may be possible for
+ // more than maxOpenDBs folders to stay open after this loop.
+ log.info("Need to close " + dbsToClose + " more DBs");
+ // Order databases by lowest lastUseTime (oldest) at the end.
+ dbs.sort((a, b) => b.lastUseTime - a.lastUseTime);
+ while (dbsToClose > 0) {
+ let db = dbs.pop();
+ log.debug("Closing DB for folder: " + db.folder.name);
+ db.folder.msgDatabase = null;
+ dbsToClose--;
+ }
+ }
+ },
+};
diff --git a/mailnews/base/util/nsImapMoveCoalescer.cpp b/mailnews/base/util/nsImapMoveCoalescer.cpp
new file mode 100644
index 000000000..56958cdc9
--- /dev/null
+++ b/mailnews/base/util/nsImapMoveCoalescer.cpp
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgImapCID.h"
+#include "nsImapMoveCoalescer.h"
+#include "nsIImapService.h"
+#include "nsIMsgCopyService.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/ArrayUtils.h"
+
+NS_IMPL_ISUPPORTS(nsImapMoveCoalescer, nsIUrlListener)
+
+nsImapMoveCoalescer::nsImapMoveCoalescer(nsIMsgFolder *sourceFolder, nsIMsgWindow *msgWindow)
+{
+ m_sourceFolder = sourceFolder;
+ m_msgWindow = msgWindow;
+ m_hasPendingMoves = false;
+}
+
+nsImapMoveCoalescer::~nsImapMoveCoalescer()
+{
+}
+
+nsresult nsImapMoveCoalescer::AddMove(nsIMsgFolder *folder, nsMsgKey key)
+{
+ m_hasPendingMoves = true;
+ int32_t folderIndex = m_destFolders.IndexOf(folder);
+ nsTArray<nsMsgKey> *keysToAdd = nullptr;
+
+ if (folderIndex >= 0)
+ keysToAdd = &(m_sourceKeyArrays[folderIndex]);
+ else
+ {
+ m_destFolders.AppendObject(folder);
+ keysToAdd = m_sourceKeyArrays.AppendElement();
+ if (!keysToAdd)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!keysToAdd->Contains(key))
+ keysToAdd->AppendElement(key);
+
+ return NS_OK;
+}
+
+nsresult nsImapMoveCoalescer::PlaybackMoves(bool doNewMailNotification /* = false */)
+{
+ int32_t numFolders = m_destFolders.Count();
+ // Nothing to do, so don't change the member variables.
+ if (numFolders == 0)
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+ m_hasPendingMoves = false;
+ m_doNewMailNotification = doNewMailNotification;
+ m_outstandingMoves = 0;
+
+ for (int32_t i = 0; i < numFolders; ++i)
+ {
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // is this the right place to make sure dest folder exists
+ // (and has proper flags?), before we start copying?
+ nsCOMPtr <nsIMsgFolder> destFolder(m_destFolders[i]);
+ nsTArray<nsMsgKey>& keysToAdd = m_sourceKeyArrays[i];
+ int32_t numNewMessages = 0;
+ int32_t numKeysToAdd = keysToAdd.Length();
+ if (numKeysToAdd == 0)
+ continue;
+
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ for (uint32_t keyIndex = 0; keyIndex < keysToAdd.Length(); keyIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> mailHdr = nullptr;
+ rv = m_sourceFolder->GetMessageHeader(keysToAdd.ElementAt(keyIndex), getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr)
+ {
+ messages->AppendElement(mailHdr, false);
+ bool isRead = false;
+ mailHdr->GetIsRead(&isRead);
+ if (!isRead)
+ numNewMessages++;
+ }
+ }
+ uint32_t destFlags;
+ destFolder->GetFlags(&destFlags);
+ if (! (destFlags & nsMsgFolderFlags::Junk)) // don't set has new on junk folder
+ {
+ destFolder->SetNumNewMessages(numNewMessages);
+ if (numNewMessages > 0)
+ destFolder->SetHasNewMessages(true);
+ }
+ // adjust the new message count on the source folder
+ int32_t oldNewMessageCount = 0;
+ m_sourceFolder->GetNumNewMessages(false, &oldNewMessageCount);
+ if (oldNewMessageCount >= numKeysToAdd)
+ oldNewMessageCount -= numKeysToAdd;
+ else
+ oldNewMessageCount = 0;
+
+ m_sourceFolder->SetNumNewMessages(oldNewMessageCount);
+
+ nsCOMPtr <nsISupports> sourceSupports = do_QueryInterface(m_sourceFolder, &rv);
+ nsCOMPtr <nsIUrlListener> urlListener(do_QueryInterface(sourceSupports));
+
+ keysToAdd.Clear();
+ nsCOMPtr<nsIMsgCopyService> copySvc = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID);
+ if (copySvc)
+ {
+ nsCOMPtr <nsIMsgCopyServiceListener> listener;
+ if (m_doNewMailNotification)
+ {
+ nsMoveCoalescerCopyListener *copyListener = new nsMoveCoalescerCopyListener(this, destFolder);
+ if (copyListener)
+ listener = do_QueryInterface(copyListener);
+ }
+ rv = copySvc->CopyMessages(m_sourceFolder, messages, destFolder, true,
+ listener, m_msgWindow, false /*allowUndo*/);
+ if (NS_SUCCEEDED(rv))
+ m_outstandingMoves++;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMoveCoalescer::OnStartRunningUrl(nsIURI *aUrl)
+{
+ NS_PRECONDITION(aUrl, "just a sanity check");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMoveCoalescer::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ m_outstandingMoves--;
+ if (m_doNewMailNotification && !m_outstandingMoves)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_sourceFolder);
+ if (imapFolder)
+ imapFolder->NotifyIfNewMail();
+ }
+ return NS_OK;
+}
+
+nsTArray<nsMsgKey> *nsImapMoveCoalescer::GetKeyBucket(uint32_t keyArrayIndex)
+{
+ NS_ASSERTION(keyArrayIndex < MOZ_ARRAY_LENGTH(m_keyBuckets), "invalid index");
+
+ return keyArrayIndex < mozilla::ArrayLength(m_keyBuckets) ?
+ &(m_keyBuckets[keyArrayIndex]) : nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsMoveCoalescerCopyListener, nsIMsgCopyServiceListener)
+
+nsMoveCoalescerCopyListener::nsMoveCoalescerCopyListener(nsImapMoveCoalescer * coalescer,
+ nsIMsgFolder *destFolder)
+{
+ m_destFolder = destFolder;
+ m_coalescer = coalescer;
+}
+
+nsMoveCoalescerCopyListener::~nsMoveCoalescerCopyListener()
+{
+}
+
+NS_IMETHODIMP nsMoveCoalescerCopyListener::OnStartCopy()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
+NS_IMETHODIMP nsMoveCoalescerCopyListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void SetMessageKey (in uint32_t aKey); */
+NS_IMETHODIMP nsMoveCoalescerCopyListener::SetMessageKey(uint32_t aKey)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void GetMessageId (in nsACString aMessageId); */
+NS_IMETHODIMP nsMoveCoalescerCopyListener::GetMessageId(nsACString& messageId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnStopCopy (in nsresult aStatus); */
+NS_IMETHODIMP nsMoveCoalescerCopyListener::OnStopCopy(nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+ if (NS_SUCCEEDED(aStatus))
+ {
+ // if the dest folder is imap, update it.
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_destFolder);
+ if (imapFolder)
+ {
+ uint32_t folderFlags;
+ m_destFolder->GetFlags(&folderFlags);
+ if (!(folderFlags & (nsMsgFolderFlags::Junk | nsMsgFolderFlags::Trash)))
+ {
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIURI> url;
+ nsCOMPtr <nsIUrlListener> listener = do_QueryInterface(m_coalescer);
+ rv = imapService->SelectFolder(m_destFolder, listener, nullptr, getter_AddRefs(url));
+ }
+ }
+ else // give junk filters a chance to run on new msgs in destination local folder
+ {
+ bool filtersRun;
+ m_destFolder->CallFilterPlugins(nullptr, &filtersRun);
+ }
+ }
+ return rv;
+}
+
+
+
diff --git a/mailnews/base/util/nsImapMoveCoalescer.h b/mailnews/base/util/nsImapMoveCoalescer.h
new file mode 100644
index 000000000..2085408fb
--- /dev/null
+++ b/mailnews/base/util/nsImapMoveCoalescer.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsImapMoveCoalescer_H
+#define _nsImapMoveCoalescer_H
+
+#include "msgCore.h"
+#include "nsCOMArray.h"
+#include "nsIMsgWindow.h"
+#include "nsCOMPtr.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgCopyServiceListener.h"
+
+// imap move coalescer class - in order to keep nsImapMailFolder from growing like Topsy
+// Logically, we want to keep track of an nsTArray<nsMsgKey> per nsIMsgFolder, and then
+// be able to retrieve them one by one and play back the moves.
+// This utility class will be used by both the filter code and the offline playback code,
+// to avoid multiple moves to the same folder.
+
+class NS_MSG_BASE nsImapMoveCoalescer : public nsIUrlListener
+{
+public:
+ friend class nsMoveCoalescerCopyListener;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+
+ nsImapMoveCoalescer(nsIMsgFolder *sourceFolder, nsIMsgWindow *msgWindow);
+
+ nsresult AddMove(nsIMsgFolder *folder, nsMsgKey key);
+ nsresult PlaybackMoves(bool doNewMailNotification = false);
+ // this lets the caller store keys in an arbitrary number of buckets. If the bucket
+ // for the passed in index doesn't exist, it will get created.
+ nsTArray<nsMsgKey> *GetKeyBucket(uint32_t keyArrayIndex);
+ nsIMsgWindow *GetMsgWindow() {return m_msgWindow;}
+ bool HasPendingMoves() {return m_hasPendingMoves;}
+protected:
+ virtual ~nsImapMoveCoalescer();
+ // m_sourceKeyArrays and m_destFolders are parallel arrays.
+ nsTArray<nsTArray<nsMsgKey> > m_sourceKeyArrays;
+ nsCOMArray<nsIMsgFolder> m_destFolders;
+ nsCOMPtr <nsIMsgWindow> m_msgWindow;
+ nsCOMPtr <nsIMsgFolder> m_sourceFolder;
+ bool m_doNewMailNotification;
+ bool m_hasPendingMoves;
+ nsTArray<nsMsgKey> m_keyBuckets[2];
+ int32_t m_outstandingMoves;
+};
+
+class nsMoveCoalescerCopyListener final : public nsIMsgCopyServiceListener
+{
+public:
+ nsMoveCoalescerCopyListener(nsImapMoveCoalescer * coalescer, nsIMsgFolder *destFolder);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ nsCOMPtr <nsIMsgFolder> m_destFolder;
+
+ nsImapMoveCoalescer *m_coalescer;
+ // when we get OnStopCopy, update the folder. When we've finished all the copies,
+ // send the biff notification.
+
+private:
+ ~nsMoveCoalescerCopyListener();
+};
+
+
+#endif // _nsImapMoveCoalescer_H
+
diff --git a/mailnews/base/util/nsMsgCompressIStream.cpp b/mailnews/base/util/nsMsgCompressIStream.cpp
new file mode 100644
index 000000000..b2a7013cf
--- /dev/null
+++ b/mailnews/base/util/nsMsgCompressIStream.cpp
@@ -0,0 +1,227 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgCompressIStream.h"
+#include "prio.h"
+#include "prmem.h"
+#include <algorithm>
+
+#define BUFFER_SIZE 16384
+
+nsMsgCompressIStream::nsMsgCompressIStream() :
+ m_dataptr(nullptr),
+ m_dataleft(0),
+ m_inflateAgain(false)
+{
+}
+
+nsMsgCompressIStream::~nsMsgCompressIStream()
+{
+ Close();
+}
+
+NS_IMPL_ISUPPORTS(nsMsgCompressIStream, nsIInputStream,
+ nsIAsyncInputStream)
+
+nsresult nsMsgCompressIStream::InitInputStream(nsIInputStream *rawStream)
+{
+ // protect against repeat calls
+ if (m_iStream)
+ return NS_ERROR_UNEXPECTED;
+
+ // allocate some memory for buffering
+ m_zbuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE);
+ if (!m_zbuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // allocate some memory for buffering
+ m_databuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE);
+ if (!m_databuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // set up zlib object
+ m_zstream.zalloc = Z_NULL;
+ m_zstream.zfree = Z_NULL;
+ m_zstream.opaque = Z_NULL;
+
+ // http://zlib.net/manual.html is rather silent on the topic, but
+ // perl's Compress::Raw::Zlib manual says:
+ // -WindowBits
+ // To compress an RFC 1951 data stream, set WindowBits to -MAX_WBITS.
+ if (inflateInit2(&m_zstream, -MAX_WBITS) != Z_OK)
+ return NS_ERROR_FAILURE;
+
+ m_iStream = rawStream;
+
+ return NS_OK;
+}
+
+nsresult nsMsgCompressIStream::DoInflation()
+{
+ // if there's something in the input buffer of the zstream, process it.
+ m_zstream.next_out = (Bytef *) m_databuf.get();
+ m_zstream.avail_out = BUFFER_SIZE;
+ int zr = inflate(&m_zstream, Z_SYNC_FLUSH);
+
+ // inflate() should normally be called until it returns
+ // Z_STREAM_END or an error, and Z_BUF_ERROR just means
+ // unable to progress any further (possible if we filled
+ // an output buffer exactly)
+ if (zr == Z_BUF_ERROR || zr == Z_STREAM_END)
+ zr = Z_OK;
+
+ // otherwise it's an error
+ if (zr != Z_OK)
+ return NS_ERROR_FAILURE;
+
+ // http://www.zlib.net/manual.html says:
+ // If inflate returns Z_OK and with zero avail_out, it must be called
+ // again after making room in the output buffer because there might be
+ // more output pending.
+ m_inflateAgain = m_zstream.avail_out ? false : true;
+
+ // set the pointer to the start of the buffer, and the count to how
+ // based on how many bytes are left unconsumed.
+ m_dataptr = m_databuf.get();
+ m_dataleft = BUFFER_SIZE - m_zstream.avail_out;
+
+ return NS_OK;
+}
+
+/* void close (); */
+NS_IMETHODIMP nsMsgCompressIStream::Close()
+{
+ return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP nsMsgCompressIStream::CloseWithStatus(nsresult reason)
+{
+ nsresult rv = NS_OK;
+
+ if (m_iStream)
+ {
+ // pass the status through to our wrapped stream
+ nsCOMPtr <nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_iStream);
+ if (asyncInputStream)
+ rv = asyncInputStream->CloseWithStatus(reason);
+
+ // tidy up
+ m_iStream = nullptr;
+ inflateEnd(&m_zstream);
+ }
+
+ // clean up all the buffers
+ m_zbuf = nullptr;
+ m_databuf = nullptr;
+ m_dataptr = nullptr;
+ m_dataleft = 0;
+
+ return rv;
+}
+
+/* unsigned long long available (); */
+NS_IMETHODIMP nsMsgCompressIStream::Available(uint64_t *aResult)
+{
+ if (!m_iStream)
+ return NS_BASE_STREAM_CLOSED;
+
+ // check if there's anything still in flight
+ if (!m_dataleft && m_inflateAgain)
+ {
+ nsresult rv = DoInflation();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // we'll be returning this many to the next read, guaranteed
+ if (m_dataleft)
+ {
+ *aResult = m_dataleft;
+ return NS_OK;
+ }
+
+ // this value isn't accurate, but will give a good true/false
+ // indication for idle purposes, and next read will fill
+ // m_dataleft, so we'll have an accurate count for the next call.
+ return m_iStream->Available(aResult);
+}
+
+/* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */
+NS_IMETHODIMP nsMsgCompressIStream::Read(char * aBuf, uint32_t aCount, uint32_t *aResult)
+{
+ if (!m_iStream)
+ {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ // There are two stages of buffering:
+ // * m_zbuf contains the compressed data from the remote server
+ // * m_databuf contains the uncompressed raw bytes for consumption
+ // by the local client.
+ //
+ // Each buffer will only be filled when the following buffers
+ // have been entirely consumed.
+ //
+ // m_dataptr and m_dataleft are respectively a pointer to the
+ // unconsumed portion of m_databuf and the number of bytes
+ // of uncompressed data remaining in m_databuf.
+ //
+ // both buffers have a maximum size of BUFFER_SIZE, so it is
+ // possible that multiple inflate passes will be required to
+ // consume all of m_zbuf.
+ while (!m_dataleft)
+ {
+ // get some more data if we don't already have any
+ if (!m_inflateAgain)
+ {
+ uint32_t bytesRead;
+ nsresult rv = m_iStream->Read(m_zbuf.get(), (uint32_t)BUFFER_SIZE, &bytesRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bytesRead)
+ return NS_BASE_STREAM_CLOSED;
+ m_zstream.next_in = (Bytef *) m_zbuf.get();
+ m_zstream.avail_in = bytesRead;
+ }
+
+ nsresult rv = DoInflation();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aResult = std::min(m_dataleft, aCount);
+
+ if (*aResult)
+ {
+ memcpy(aBuf, m_dataptr, *aResult);
+ m_dataptr += *aResult;
+ m_dataleft -= *aResult;
+ }
+
+ return NS_OK;
+}
+
+/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */
+NS_IMETHODIMP nsMsgCompressIStream::ReadSegments(nsWriteSegmentFun aWriter, void * aClosure, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgCompressIStream::AsyncWait(nsIInputStreamCallback *callback, uint32_t flags, uint32_t amount, nsIEventTarget *target)
+{
+ if (!m_iStream)
+ return NS_BASE_STREAM_CLOSED;
+
+ nsCOMPtr <nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_iStream);
+ if (asyncInputStream)
+ return asyncInputStream->AsyncWait(callback, flags, amount, target);
+
+ return NS_OK;
+}
+
+/* boolean isNonBlocking (); */
+NS_IMETHODIMP nsMsgCompressIStream::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
diff --git a/mailnews/base/util/nsMsgCompressIStream.h b/mailnews/base/util/nsMsgCompressIStream.h
new file mode 100644
index 000000000..6c91e084d
--- /dev/null
+++ b/mailnews/base/util/nsMsgCompressIStream.h
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIInputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "zlib.h"
+
+class NS_MSG_BASE nsMsgCompressIStream final : public nsIAsyncInputStream
+{
+public:
+ nsMsgCompressIStream();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ nsresult InitInputStream(nsIInputStream *rawStream);
+
+protected:
+ ~nsMsgCompressIStream();
+ nsresult DoInflation();
+ nsCOMPtr<nsIInputStream> m_iStream;
+ mozilla::UniquePtr<char[]> m_zbuf;
+ mozilla::UniquePtr<char[]> m_databuf;
+ char *m_dataptr;
+ uint32_t m_dataleft;
+ bool m_inflateAgain;
+ z_stream m_zstream;
+};
+
diff --git a/mailnews/base/util/nsMsgCompressOStream.cpp b/mailnews/base/util/nsMsgCompressOStream.cpp
new file mode 100644
index 000000000..0a0a69549
--- /dev/null
+++ b/mailnews/base/util/nsMsgCompressOStream.cpp
@@ -0,0 +1,145 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgCompressOStream.h"
+#include "prio.h"
+#include "prmem.h"
+
+#define BUFFER_SIZE 16384
+
+nsMsgCompressOStream::nsMsgCompressOStream() :
+ m_zbuf(nullptr)
+{
+}
+
+nsMsgCompressOStream::~nsMsgCompressOStream()
+{
+ Close();
+}
+
+NS_IMPL_ISUPPORTS(nsMsgCompressOStream, nsIOutputStream)
+
+nsresult nsMsgCompressOStream::InitOutputStream(nsIOutputStream *rawStream)
+{
+ // protect against repeat calls
+ if (m_oStream)
+ return NS_ERROR_UNEXPECTED;
+
+ // allocate some memory for a buffer
+ m_zbuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE);
+ if (!m_zbuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // set up the zlib object
+ m_zstream.zalloc = Z_NULL;
+ m_zstream.zfree = Z_NULL;
+ m_zstream.opaque = Z_NULL;
+
+ // http://zlib.net/manual.html is rather silent on the topic, but
+ // perl's Compress::Raw::Zlib manual says:
+ // -WindowBits [...]
+ // To compress an RFC 1951 data stream, set WindowBits to -MAX_WBITS.
+ if (deflateInit2(&m_zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK)
+ return NS_ERROR_FAILURE;
+
+ m_oStream = rawStream;
+
+ return NS_OK;
+}
+
+/* void close (); */
+NS_IMETHODIMP nsMsgCompressOStream::Close()
+{
+ if (m_oStream)
+ {
+ m_oStream = nullptr;
+ deflateEnd(&m_zstream);
+ }
+ m_zbuf = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompressOStream::Write(const char *buf, uint32_t count, uint32_t *result)
+{
+ if (!m_oStream)
+ return NS_BASE_STREAM_CLOSED;
+
+ m_zstream.next_in = (Bytef *) buf;
+ m_zstream.avail_in = count;
+
+ // keep looping until the buffer doesn't get filled
+ do
+ {
+ m_zstream.next_out = (Bytef *) m_zbuf.get();
+ m_zstream.avail_out = BUFFER_SIZE;
+ // Using "Z_SYNC_FLUSH" may cause excess flushes if the calling
+ // code does a lot of small writes. An option with the IMAP
+ // protocol is to check the buffer for "\n" at the end, but
+ // in the interests of keeping this generic, don't optimise
+ // yet. An alternative is to require ->Flush always, but that
+ // is likely to break callers.
+ int zr = deflate(&m_zstream, Z_SYNC_FLUSH);
+ if (zr == Z_STREAM_END || zr == Z_BUF_ERROR)
+ zr = Z_OK; // not an error for our purposes
+ if (zr != Z_OK)
+ return NS_ERROR_FAILURE;
+
+ uint32_t out_size = BUFFER_SIZE - m_zstream.avail_out;
+ const char *out_buf = m_zbuf.get();
+
+ // push everything in the buffer before repeating
+ while (out_size)
+ {
+ uint32_t out_result;
+ nsresult rv = m_oStream->Write(out_buf, out_size, &out_result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!out_result)
+ return NS_BASE_STREAM_CLOSED;
+ out_size -= out_result;
+ out_buf += out_result;
+ }
+
+ // http://www.zlib.net/manual.html says:
+ // If deflate returns with avail_out == 0, this function must be
+ // called again with the same value of the flush parameter and
+ // more output space (updated avail_out), until the flush is
+ // complete (deflate returns with non-zero avail_out).
+ } while (!m_zstream.avail_out);
+
+ *result = count;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompressOStream::Flush(void)
+{
+ if (!m_oStream)
+ return NS_BASE_STREAM_CLOSED;
+
+ return m_oStream->Flush();
+}
+
+NS_IMETHODIMP
+nsMsgCompressOStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgCompressOStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* boolean isNonBlocking (); */
+NS_IMETHODIMP nsMsgCompressOStream::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
diff --git a/mailnews/base/util/nsMsgCompressOStream.h b/mailnews/base/util/nsMsgCompressOStream.h
new file mode 100644
index 000000000..c706a9617
--- /dev/null
+++ b/mailnews/base/util/nsMsgCompressOStream.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "zlib.h"
+
+class NS_MSG_BASE nsMsgCompressOStream final : public nsIOutputStream
+{
+public:
+ nsMsgCompressOStream();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsresult InitOutputStream(nsIOutputStream *rawStream);
+
+protected:
+ ~nsMsgCompressOStream();
+ nsCOMPtr<nsIOutputStream> m_oStream;
+ mozilla::UniquePtr<char[]> m_zbuf;
+ z_stream m_zstream;
+};
+
diff --git a/mailnews/base/util/nsMsgDBFolder.cpp b/mailnews/base/util/nsMsgDBFolder.cpp
new file mode 100644
index 000000000..c6356f83d
--- /dev/null
+++ b/mailnews/base/util/nsMsgDBFolder.cpp
@@ -0,0 +1,6063 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsUnicharUtils.h"
+#include "nsMsgDBFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsRDFCID.h"
+#include "nsNetUtil.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMsgDatabase.h"
+#include "nsIMsgAccountManager.h"
+#include "nsISeekableStream.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIChannel.h"
+#include "nsITransport.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsIDocShell.h"
+#include "nsIMsgWindow.h"
+#include "nsIPrompt.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILocale.h"
+#include "nsILocaleService.h"
+#include "nsCollationCID.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbCard.h"
+#include "nsIAbDirectory.h"
+#include "nsISpamSettings.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIMsgMailSession.h"
+#include "nsIRDFService.h"
+#include "nsTextFormatter.h"
+#include "nsMsgDBCID.h"
+#include "nsReadLine.h"
+#include "nsLayoutCID.h"
+#include "nsIParserUtils.h"
+#include "nsIDocumentEncoder.h"
+#include "nsMsgI18N.h"
+#include "nsIMIMEHeaderParam.h"
+#include "plbase64.h"
+#include "nsArrayEnumerator.h"
+#include <time.h>
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsIMimeHeaders.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIMsgTraitService.h"
+#include "nsIMessenger.h"
+#include "nsThreadUtils.h"
+#include "nsITransactionManager.h"
+#include "nsMsgReadStateTxn.h"
+#include "nsAutoPtr.h"
+#include "prmem.h"
+#include "nsIPK11TokenDB.h"
+#include "nsIPK11Token.h"
+#include "nsMsgLocalFolderHdrs.h"
+#define oneHour 3600000000U
+#include "nsMsgUtils.h"
+#include "nsIMsgFilterService.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/Services.h"
+#include "nsMimeTypes.h"
+#include "nsIMsgFilter.h"
+
+static PRTime gtimeOfLastPurgeCheck; //variable to know when to check for purge_threshhold
+
+#define PREF_MAIL_PROMPT_PURGE_THRESHOLD "mail.prompt_purge_threshhold"
+#define PREF_MAIL_PURGE_THRESHOLD "mail.purge_threshhold"
+#define PREF_MAIL_PURGE_THRESHOLD_MB "mail.purge_threshhold_mb"
+#define PREF_MAIL_PURGE_MIGRATED "mail.purge_threshold_migrated"
+#define PREF_MAIL_PURGE_ASK "mail.purge.ask"
+#define PREF_MAIL_WARN_FILTER_CHANGED "mail.warn_filter_changed"
+
+const char *kUseServerRetentionProp = "useServerRetention";
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+nsICollation * nsMsgDBFolder::gCollationKeyGenerator = nullptr;
+
+char16_t *nsMsgDBFolder::kLocalizedInboxName;
+char16_t *nsMsgDBFolder::kLocalizedTrashName;
+char16_t *nsMsgDBFolder::kLocalizedSentName;
+char16_t *nsMsgDBFolder::kLocalizedDraftsName;
+char16_t *nsMsgDBFolder::kLocalizedTemplatesName;
+char16_t *nsMsgDBFolder::kLocalizedUnsentName;
+char16_t *nsMsgDBFolder::kLocalizedJunkName;
+char16_t *nsMsgDBFolder::kLocalizedArchivesName;
+
+char16_t *nsMsgDBFolder::kLocalizedBrandShortName;
+
+nsrefcnt nsMsgDBFolder::mInstanceCount=0;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgDBFolder, nsRDFResource,
+ nsISupportsWeakReference, nsIMsgFolder,
+ nsIDBChangeListener, nsIUrlListener,
+ nsIJunkMailClassificationListener,
+ nsIMsgTraitClassificationListener)
+
+#define MSGDBFOLDER_ATOM(name_, value_) nsIAtom* nsMsgDBFolder::name_ = nullptr;
+#include "nsMsgDBFolderAtomList.h"
+#undef MSGDBFOLDER_ATOM
+
+nsMsgDBFolder::nsMsgDBFolder(void)
+: mAddListener(true),
+ mNewMessages(false),
+ mGettingNewMessages(false),
+ mLastMessageLoaded(nsMsgKey_None),
+ mFlags(0),
+ mNumUnreadMessages(-1),
+ mNumTotalMessages(-1),
+ mNotifyCountChanges(true),
+ mExpungedBytes(0),
+ mInitializedFromCache(false),
+ mSemaphoreHolder(nullptr),
+ mNumPendingUnreadMessages(0),
+ mNumPendingTotalMessages(0),
+ mFolderSize(kSizeUnknown),
+ mNumNewBiffMessages(0),
+ mHaveParsedURI(false),
+ mIsServerIsValid(false),
+ mIsServer(false),
+ mInVFEditSearchScope (false)
+{
+ if (mInstanceCount++ <=0) {
+#define MSGDBFOLDER_ATOM(name_, value_) name_ = MsgNewAtom(value_).take();
+#include "nsMsgDBFolderAtomList.h"
+#undef MSGDBFOLDER_ATOM
+ initializeStrings();
+ createCollationKeyGenerator();
+ gtimeOfLastPurgeCheck = 0;
+ }
+
+ mProcessingFlag[0].bit = nsMsgProcessingFlags::ClassifyJunk;
+ mProcessingFlag[1].bit = nsMsgProcessingFlags::ClassifyTraits;
+ mProcessingFlag[2].bit = nsMsgProcessingFlags::TraitsDone;
+ mProcessingFlag[3].bit = nsMsgProcessingFlags::FiltersDone;
+ mProcessingFlag[4].bit = nsMsgProcessingFlags::FilterToMove;
+ mProcessingFlag[5].bit = nsMsgProcessingFlags::NotReportedClassified;
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ mProcessingFlag[i].keys = nsMsgKeySetU::Create();
+}
+
+nsMsgDBFolder::~nsMsgDBFolder(void)
+{
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ delete mProcessingFlag[i].keys;
+
+ if (--mInstanceCount == 0) {
+ NS_IF_RELEASE(gCollationKeyGenerator);
+ NS_Free(kLocalizedInboxName);
+ NS_Free(kLocalizedTrashName);
+ NS_Free(kLocalizedSentName);
+ NS_Free(kLocalizedDraftsName);
+ NS_Free(kLocalizedTemplatesName);
+ NS_Free(kLocalizedUnsentName);
+ NS_Free(kLocalizedJunkName);
+ NS_Free(kLocalizedArchivesName);
+ NS_Free(kLocalizedBrandShortName);
+
+#define MSGDBFOLDER_ATOM(name_, value_) NS_RELEASE(name_);
+#include "nsMsgDBFolderAtomList.h"
+#undef MSGDBFOLDER_ATOM
+ }
+ //shutdown but don't shutdown children.
+ Shutdown(false);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Shutdown(bool shutdownChildren)
+{
+ if(mDatabase)
+ {
+ mDatabase->RemoveListener(this);
+ mDatabase->ForceClosed();
+ mDatabase = nullptr;
+ if (mBackupDatabase)
+ {
+ mBackupDatabase->ForceClosed();
+ mBackupDatabase = nullptr;
+ }
+ }
+
+ if(shutdownChildren)
+ {
+ int32_t count = mSubFolders.Count();
+
+ for (int32_t i = 0; i < count; i++)
+ mSubFolders[i]->Shutdown(true);
+
+ // Reset incoming server pointer and pathname.
+ mServer = nullptr;
+ mPath = nullptr;
+ mHaveParsedURI = false;
+ mName.Truncate();
+ mSubFolders.Clear();
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::ForceDBClosed()
+{
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ mSubFolders[i]->ForceDBClosed();
+
+ if (mDatabase)
+ {
+ mDatabase->ForceClosed();
+ mDatabase = nullptr;
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgDBService> mailDBFactory(do_GetService(NS_MSGDB_SERVICE_CONTRACTID));
+ if (mailDBFactory)
+ mailDBFactory->ForceFolderDBClosed(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CloseAndBackupFolderDB(const nsACString& newName)
+{
+ ForceDBClosed();
+
+ // We only support backup for mail at the moment
+ if ( !(mFlags & nsMsgFolderFlags::Mail))
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> folderPath;
+ nsresult rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dbFile;
+ rv = GetSummaryFileLocation(folderPath, getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDir;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDBFile;
+ rv = GetBackupSummaryFile(getter_AddRefs(backupDBFile), newName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBackupDatabase)
+ {
+ mBackupDatabase->ForceClosed();
+ mBackupDatabase = nullptr;
+ }
+
+ backupDBFile->Remove(false);
+ bool backupExists;
+ backupDBFile->Exists(&backupExists);
+ NS_ASSERTION(!backupExists, "Couldn't delete database backup");
+ if (backupExists)
+ return NS_ERROR_FAILURE;
+
+ if (!newName.IsEmpty())
+ {
+ nsAutoCString backupName;
+ rv = backupDBFile->GetNativeLeafName(backupName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return dbFile->CopyToNative(backupDir, backupName);
+ }
+ else
+ return dbFile->CopyToNative(backupDir, EmptyCString());
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OpenBackupMsgDatabase()
+{
+ if (mBackupDatabase)
+ return NS_OK;
+ nsCOMPtr<nsIFile> folderPath;
+ nsresult rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = folderPath->GetLeafName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDir;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use a dummy message folder file so we can use
+ // GetSummaryFileLocation to get the db file name
+ nsCOMPtr<nsIFile> backupDBDummyFolder;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDBDummyFolder->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenMailDBFromFile(
+ backupDBDummyFolder, this, false, true,
+ getter_AddRefs(mBackupDatabase));
+ // we add a listener so that we can close the db during OnAnnouncerGoingAway. There should
+ // not be any other calls to the listener with the backup database
+ if (NS_SUCCEEDED(rv) && mBackupDatabase)
+ mBackupDatabase->AddListener(this);
+
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ // this is normal in reparsing
+ rv = NS_OK;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RemoveBackupMsgDatabase()
+{
+ nsCOMPtr<nsIFile> folderPath;
+ nsresult rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = folderPath->GetLeafName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDir;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use a dummy message folder file so we can use
+ // GetSummaryFileLocation to get the db file name
+ nsCOMPtr<nsIFile> backupDBDummyFolder;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDBDummyFolder->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDBFile;
+ rv = GetSummaryFileLocation(backupDBDummyFolder, getter_AddRefs(backupDBFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBackupDatabase)
+ {
+ mBackupDatabase->ForceClosed();
+ mBackupDatabase = nullptr;
+ }
+
+ return backupDBFile->Remove(false);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::StartFolderLoading(void)
+{
+ if(mDatabase)
+ mDatabase->RemoveListener(this);
+ mAddListener = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::EndFolderLoading(void)
+{
+ if(mDatabase)
+ mDatabase->AddListener(this);
+ mAddListener = true;
+ UpdateSummaryTotals(true);
+
+ //GGGG check for new mail here and call SetNewMessages...?? -- ONE OF THE 2 PLACES
+ if(mDatabase)
+ m_newMsgs.Clear();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetExpungedBytes(int64_t *count)
+{
+ NS_ENSURE_ARG_POINTER(count);
+
+ if (mDatabase)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (NS_FAILED(rv)) return rv;
+ rv = folderInfo->GetExpungedBytes(count);
+ if (NS_SUCCEEDED(rv))
+ mExpungedBytes = *count; // sync up with the database
+ return rv;
+ }
+ else
+ {
+ ReadDBFolderInfo(false);
+ *count = mExpungedBytes;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::GetCharset(nsACString& aCharset)
+{
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(rv))
+ rv = folderInfo->GetEffectiveCharacterSet(aCharset);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetCharset(const nsACString& aCharset)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(rv))
+ {
+ rv = folderInfo->SetCharacterSet(aCharset);
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+ mCharset = aCharset;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetCharsetOverride(bool *aCharsetOverride)
+{
+ NS_ENSURE_ARG_POINTER(aCharsetOverride);
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(rv))
+ rv = folderInfo->GetCharacterSetOverride(aCharsetOverride);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetCharsetOverride(bool aCharsetOverride)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(rv))
+ {
+ rv = folderInfo->SetCharacterSetOverride(aCharsetOverride);
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+ mCharsetOverride = aCharsetOverride; // synchronize member variable
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetHasNewMessages(bool *hasNewMessages)
+{
+ NS_ENSURE_ARG_POINTER(hasNewMessages);
+ *hasNewMessages = mNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetHasNewMessages(bool curNewMessages)
+{
+ if (curNewMessages != mNewMessages)
+ {
+ // Only change mru time if we're going from doesn't have new to has new.
+ // technically, we should probably update mru time for every new message
+ // but we would pay a performance penalty for that. If the user
+ // opens the folder, the mrutime will get updated anyway.
+ if (curNewMessages)
+ SetMRUTime();
+ bool oldNewMessages = mNewMessages;
+ mNewMessages = curNewMessages;
+ NotifyBoolPropertyChanged(kNewMessagesAtom, oldNewMessages, curNewMessages);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetHasFolderOrSubfolderNewMessages(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ bool hasNewMessages = mNewMessages;
+
+ if (!hasNewMessages)
+ {
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ bool hasNew = false;
+ mSubFolders[i]->GetHasFolderOrSubfolderNewMessages(&hasNew);
+ if (hasNew)
+ {
+ hasNewMessages = true;
+ break;
+ }
+ }
+ }
+
+ *aResult = hasNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetGettingNewMessages(bool *gettingNewMessages)
+{
+ NS_ENSURE_ARG_POINTER(gettingNewMessages);
+ *gettingNewMessages = mGettingNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetGettingNewMessages(bool gettingNewMessages)
+{
+ mGettingNewMessages = gettingNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFirstNewMessage(nsIMsgDBHdr **firstNewMessage)
+{
+ //If there's not a db then there can't be new messages. Return failure since you
+ //should use HasNewMessages first.
+ if(!mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsMsgKey key;
+ rv = mDatabase->GetFirstNew(&key);
+ if(NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(hdr));
+ if(NS_FAILED(rv))
+ return rv;
+
+ return mDatabase->GetMsgHdrForKey(key, firstNewMessage);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ClearNewMessages()
+{
+ nsresult rv = NS_OK;
+ bool dbWasCached = mDatabase != nullptr;
+ if (!dbWasCached)
+ GetDatabase();
+
+ if (mDatabase)
+ {
+ uint32_t numNewKeys;
+ nsMsgKey *newMessageKeys;
+ rv = mDatabase->GetNewList(&numNewKeys, &newMessageKeys);
+ if (NS_SUCCEEDED(rv) && newMessageKeys)
+ {
+ m_saveNewMsgs.Clear();
+ m_saveNewMsgs.AppendElements(newMessageKeys, numNewKeys);
+ NS_Free(newMessageKeys);
+ }
+ mDatabase->ClearNewList(true);
+ }
+ if (!dbWasCached)
+ SetMsgDatabase(nullptr);
+
+ m_newMsgs.Clear();
+ mNumNewBiffMessages = 0;
+ return rv;
+}
+
+void nsMsgDBFolder::UpdateNewMessages()
+{
+ if (! (mFlags & nsMsgFolderFlags::Virtual))
+ {
+ bool hasNewMessages = false;
+ for (uint32_t keyIndex = 0; keyIndex < m_newMsgs.Length(); keyIndex++)
+ {
+ bool containsKey = false;
+ mDatabase->ContainsKey(m_newMsgs[keyIndex], &containsKey);
+ if (!containsKey)
+ continue;
+ bool isRead = false;
+ nsresult rv2 = mDatabase->IsRead(m_newMsgs[keyIndex], &isRead);
+ if (NS_SUCCEEDED(rv2) && !isRead)
+ {
+ hasNewMessages = true;
+ mDatabase->AddToNewList(m_newMsgs[keyIndex]);
+ }
+ }
+ SetHasNewMessages(hasNewMessages);
+ }
+}
+
+// helper function that gets the cache element that corresponds to the passed in file spec.
+// This could be static, or could live in another class - it's not specific to the current
+// nsMsgDBFolder. If it lived at a higher level, we could cache the account manager and folder cache.
+nsresult nsMsgDBFolder::GetFolderCacheElemFromFile(nsIFile *file, nsIMsgFolderCacheElement **cacheElement)
+{
+ nsresult result;
+ NS_ENSURE_ARG_POINTER(file);
+ NS_ENSURE_ARG_POINTER(cacheElement);
+ nsCOMPtr <nsIMsgFolderCache> folderCache;
+#ifdef DEBUG_bienvenu1
+ bool exists;
+ NS_ASSERTION(NS_SUCCEEDED(fileSpec->Exists(&exists)) && exists, "whoops, file doesn't exist, mac will break");
+#endif
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &result);
+ if(NS_SUCCEEDED(result))
+ {
+ result = accountMgr->GetFolderCache(getter_AddRefs(folderCache));
+ if (NS_SUCCEEDED(result) && folderCache)
+ {
+ nsCString persistentPath;
+ result = file->GetPersistentDescriptor(persistentPath);
+ NS_ENSURE_SUCCESS(result, result);
+ result = folderCache->GetCacheElement(persistentPath, false, cacheElement);
+ }
+ }
+ return result;
+}
+
+nsresult nsMsgDBFolder::ReadDBFolderInfo(bool force)
+{
+ // Since it turns out to be pretty expensive to open and close
+ // the DBs all the time, if we have to open it once, get everything
+ // we might need while we're here
+ nsresult result = NS_OK;
+
+ // don't need to reload from cache if we've already read from cache,
+ // and, we might get stale info, so don't do it.
+ if (!mInitializedFromCache)
+ {
+ nsCOMPtr <nsIFile> dbPath;
+ result = GetFolderCacheKey(getter_AddRefs(dbPath), true /* createDBIfMissing */);
+ if (dbPath)
+ {
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ result = GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(result) && cacheElement)
+ result = ReadFromFolderCacheElem(cacheElement);
+ }
+ }
+
+ if (force || !mInitializedFromCache)
+ {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ result = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(result))
+ {
+ if (folderInfo)
+ {
+ if (!mInitializedFromCache)
+ {
+ folderInfo->GetFlags((int32_t *)&mFlags);
+#ifdef DEBUG_bienvenu1
+ nsString name;
+ GetName(name);
+ NS_ASSERTION(Compare(name, kLocalizedTrashName) || (mFlags & nsMsgFolderFlags::Trash), "lost trash flag");
+#endif
+ mInitializedFromCache = true;
+ }
+
+ folderInfo->GetNumMessages(&mNumTotalMessages);
+ folderInfo->GetNumUnreadMessages(&mNumUnreadMessages);
+ folderInfo->GetExpungedBytes(&mExpungedBytes);
+
+ nsCString utf8Name;
+ folderInfo->GetFolderName(utf8Name);
+ if (!utf8Name.IsEmpty())
+ CopyUTF8toUTF16(utf8Name, mName);
+
+ //These should be put in IMAP folder only.
+ //folderInfo->GetImapTotalPendingMessages(&mNumPendingTotalMessages);
+ //folderInfo->GetImapUnreadPendingMessages(&mNumPendingUnreadMessages);
+
+ folderInfo->GetCharacterSet(mCharset);
+ folderInfo->GetCharacterSetOverride(&mCharsetOverride);
+
+ if (db) {
+ bool hasnew;
+ nsresult rv;
+ rv = db->HasNew(&hasnew);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ }
+ else {
+ // we tried to open DB but failed - don't keep trying.
+ // If a DB is created, we will call this method with force == TRUE,
+ // and read from the db that way.
+ mInitializedFromCache = true;
+ }
+ if (db)
+ db->Close(false);
+ }
+ return result;
+}
+
+nsresult nsMsgDBFolder::SendFlagNotifications(nsIMsgDBHdr *item, uint32_t oldFlags, uint32_t newFlags)
+{
+ nsresult rv = NS_OK;
+ uint32_t changedFlags = oldFlags ^ newFlags;
+ if((changedFlags & nsMsgMessageFlags::Read) && (changedFlags & nsMsgMessageFlags::New))
+ {
+ //..so..if the msg is read in the folder and the folder has new msgs clear the account level and status bar biffs.
+ rv = NotifyPropertyFlagChanged(item, kStatusAtom, oldFlags, newFlags);
+ rv = SetBiffState(nsMsgBiffState_NoMail);
+ }
+ else if(changedFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded
+ | nsMsgMessageFlags::IMAPDeleted | nsMsgMessageFlags::New | nsMsgMessageFlags::Offline))
+ rv = NotifyPropertyFlagChanged(item, kStatusAtom, oldFlags, newFlags);
+ else if((changedFlags & nsMsgMessageFlags::Marked))
+ rv = NotifyPropertyFlagChanged(item, kFlaggedAtom, oldFlags, newFlags);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::DownloadMessagesForOffline(nsIArray *messages, nsIMsgWindow *)
+{
+ NS_ASSERTION(false, "imap and news need to override this");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow)
+{
+ NS_ASSERTION(false, "imap and news need to override this");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetMsgStore(nsIMsgPluggableStore **aStore)
+{
+ NS_ENSURE_ARG_POINTER(aStore);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+ return server->GetMsgStore(aStore);
+}
+
+bool nsMsgDBFolder::VerifyOfflineMessage(nsIMsgDBHdr *msgHdr, nsIInputStream *fileStream)
+{
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(fileStream);
+ if (seekableStream)
+ {
+ uint64_t offset;
+ msgHdr->GetMessageOffset(&offset);
+ nsresult rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, offset);
+ char startOfMsg[100];
+ uint32_t bytesRead = 0;
+ uint32_t bytesToRead = sizeof(startOfMsg) - 1;
+ if (NS_SUCCEEDED(rv))
+ rv = fileStream->Read(startOfMsg, bytesToRead, &bytesRead);
+ startOfMsg[bytesRead] = '\0';
+ // check if message starts with From, or is a draft and starts with FCC
+ if (NS_FAILED(rv) || bytesRead != bytesToRead ||
+ (strncmp(startOfMsg, "From ", 5) && (! (mFlags & nsMsgFolderFlags::Drafts) || strncmp(startOfMsg, "FCC", 3))))
+ return false;
+ }
+ return true;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetOfflineFileStream(nsMsgKey msgKey, int64_t *offset, uint32_t *size, nsIInputStream **aFileStream)
+{
+ NS_ENSURE_ARG(aFileStream);
+
+ *offset = *size = 0;
+
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ NS_ENSURE_TRUE(hdr, NS_OK);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hdr)
+ hdr->GetOfflineMessageSize(size);
+
+ bool reusable;
+ rv = GetMsgInputStream(hdr, &reusable, aFileStream);
+ // check if offline store really has the correct offset into the offline
+ // store by reading the first few bytes. If it doesn't, clear the offline
+ // flag on the msg and return false, which will fall back to reading the message
+ // from the server.
+ // We'll also advance the offset past the envelope header and
+ // X-Mozilla-Status lines.
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(*aFileStream);
+ if (seekableStream)
+ {
+ seekableStream->Tell(offset);
+ char startOfMsg[200];
+ uint32_t bytesRead = 0;
+ uint32_t bytesToRead = sizeof(startOfMsg) - 1;
+ if (NS_SUCCEEDED(rv))
+ rv = (*aFileStream)->Read(startOfMsg, bytesToRead, &bytesRead);
+ startOfMsg[bytesRead] = '\0';
+ // check if message starts with From, or is a draft and starts with FCC
+ if (NS_FAILED(rv) || bytesRead != bytesToRead ||
+ (strncmp(startOfMsg, "From ", 5) && (! (mFlags & nsMsgFolderFlags::Drafts) || strncmp(startOfMsg, "FCC", 3))))
+ rv = NS_ERROR_FAILURE;
+ else
+ {
+ uint32_t msgOffset = 0;
+ // skip "From "/FCC line
+ bool foundNextLine = MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1);
+ if (foundNextLine && !strncmp(startOfMsg + msgOffset,
+ X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN))
+ {
+ // skip X-Mozilla-Status line
+ if (MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1))
+ {
+ if (!strncmp(startOfMsg + msgOffset,
+ X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN))
+ MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1);
+ }
+ }
+ int32_t findPos = MsgFindCharInSet(nsDependentCString(startOfMsg),
+ ":\n\r", msgOffset);
+ // Check that the first line is a header line, i.e., with a ':' in it.
+ // Or, the line starts with "From " - I've seen IMAP servers return
+ // a bogus "From " line without a ':'.
+ if (findPos != -1 && (startOfMsg[findPos] == ':' ||
+ !(strncmp(startOfMsg, "From ", 5))))
+ {
+ *offset += msgOffset;
+ *size -= msgOffset;
+ }
+ else
+ {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, *offset);
+ else if (mDatabase)
+ mDatabase->MarkOffline(msgKey, false, nullptr);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetOfflineStoreOutputStream(nsIMsgDBHdr *aHdr,
+ nsIOutputStream **aOutputStream)
+{
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_ENSURE_ARG_POINTER(aHdr);
+
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(offlineStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool reusable;
+ rv = offlineStore->GetNewMsgOutputStream(this, &aHdr, &reusable,
+ aOutputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetMsgInputStream(nsIMsgDBHdr *aMsgHdr, bool *aReusable,
+ nsIInputStream **aInputStream)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aReusable);
+ NS_ENSURE_ARG_POINTER(aInputStream);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString storeToken;
+ rv = aMsgHdr->GetStringProperty("storeToken", getter_Copies(storeToken));
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t offset;
+ rv = msgStore->GetMsgInputStream(this, storeToken, &offset, aMsgHdr, aReusable,
+ aInputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISeekableStream> seekableStream(do_QueryInterface(*aInputStream));
+ if (seekableStream)
+ rv = seekableStream->Seek(PR_SEEK_SET, offset);
+ NS_WARNING_ASSERTION(seekableStream || !offset,
+ "non-zero offset w/ non-seekable stream");
+ return rv;
+}
+
+// path coming in is the root path without the leaf name,
+// on the way out, it's the whole path.
+nsresult nsMsgDBFolder::CreateFileForDB(const nsAString& userLeafName, nsIFile *path, nsIFile **dbFile)
+{
+ NS_ENSURE_ARG_POINTER(dbFile);
+
+ nsAutoString proposedDBName(userLeafName);
+ NS_MsgHashIfNecessary(proposedDBName);
+
+ // (note, the caller of this will be using the dbFile to call db->Open()
+ // will turn the path into summary file path, and append the ".msf" extension)
+ //
+ // we want db->Open() to create a new summary file
+ // so we have to jump through some hoops to make sure the .msf it will
+ // create is unique. now that we've got the "safe" proposedDBName,
+ // we append ".msf" to see if the file exists. if so, we make the name
+ // unique and then string off the ".msf" so that we pass the right thing
+ // into Open(). this isn't ideal, since this is not atomic
+ // but it will make do.
+ nsresult rv;
+ nsCOMPtr <nsIFile> dbPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbPath->InitWithFile(path);
+ proposedDBName.AppendLiteral(SUMMARY_SUFFIX);
+ dbPath->Append(proposedDBName);
+ bool exists;
+ dbPath->Exists(&exists);
+ if (exists)
+ {
+ rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbPath->GetLeafName(proposedDBName);
+ }
+ // now, take the ".msf" off
+ proposedDBName.SetLength(proposedDBName.Length() - NS_LITERAL_CSTRING(SUMMARY_SUFFIX).Length());
+ dbPath->SetLeafName(proposedDBName);
+
+ dbPath.swap(*dbFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase)
+{
+ NS_ENSURE_ARG_POINTER(aMsgDatabase);
+ GetDatabase();
+ if (!mDatabase)
+ return NS_ERROR_FAILURE;
+ NS_ADDREF(*aMsgDatabase = mDatabase);
+ mDatabase->SetLastUseTime(PR_Now());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetMsgDatabase(nsIMsgDatabase *aMsgDatabase)
+{
+ if (mDatabase)
+ {
+ // commit here - db might go away when all these refs are released.
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ mDatabase->RemoveListener(this);
+ mDatabase->ClearCachedHdrs();
+ if (!aMsgDatabase)
+ {
+ uint32_t numNewKeys;
+ nsMsgKey *newMessageKeys;
+ nsresult rv = mDatabase->GetNewList(&numNewKeys, &newMessageKeys);
+ if (NS_SUCCEEDED(rv) && newMessageKeys)
+ {
+ m_newMsgs.Clear();
+ m_newMsgs.AppendElements(newMessageKeys, numNewKeys);
+ }
+ NS_Free(newMessageKeys);
+ }
+ }
+ mDatabase = aMsgDatabase;
+
+ if (aMsgDatabase)
+ aMsgDatabase->AddListener(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetDatabaseOpen(bool *aOpen)
+{
+ NS_ENSURE_ARG_POINTER(aOpen);
+
+ *aOpen = (mDatabase != nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetBackupMsgDatabase(nsIMsgDatabase** aMsgDatabase)
+{
+ NS_ENSURE_ARG_POINTER(aMsgDatabase);
+ nsresult rv = OpenBackupMsgDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mBackupDatabase)
+ return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aMsgDatabase = mBackupDatabase);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **database)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnReadChanged(nsIDBChangeListener * aInstigator)
+{
+ /* do nothing. if you care about this, override it. see nsNewsFolder.cpp */
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnJunkScoreChanged(nsIDBChangeListener * aInstigator)
+{
+ NotifyFolderEvent(mJunkStatusChangedAtom);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus,
+ nsIDBChangeListener *aInstigator)
+{
+ /* do nothing. if you care about this, override it.*/
+ return NS_OK;
+}
+
+// 1. When the status of a message changes.
+NS_IMETHODIMP nsMsgDBFolder::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags,
+ nsIDBChangeListener * aInstigator)
+{
+ if(aHdrChanged)
+ {
+ SendFlagNotifications(aHdrChanged, aOldFlags, aNewFlags);
+ UpdateSummaryTotals(true);
+ }
+
+ // The old state was new message state
+ // We check and see if this state has changed
+ if(aOldFlags & nsMsgMessageFlags::New)
+ {
+ // state changing from new to something else
+ if (!(aNewFlags & nsMsgMessageFlags::New))
+ CheckWithNewMessagesStatus(false);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::CheckWithNewMessagesStatus(bool messageAdded)
+{
+ bool hasNewMessages;
+ if (messageAdded)
+ SetHasNewMessages(true);
+ else // message modified or deleted
+ {
+ if(mDatabase)
+ {
+ nsresult rv = mDatabase->HasNew(&hasNewMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetHasNewMessages(hasNewMessages);
+ }
+ }
+
+ return NS_OK;
+}
+
+// 3. When a message gets deleted, we need to see if it was new
+// When we lose a new message we need to check if there are still new messages
+NS_IMETHODIMP nsMsgDBFolder::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener * aInstigator)
+{
+ // check to see if a new message is being deleted
+ // as in this case, if there is only one new message and it's being deleted
+ // the folder newness has to be cleared.
+ CheckWithNewMessagesStatus(false);
+ // Remove all processing flags. This is generally a good thing although
+ // undo-ing a message back into position will not re-gain the flags.
+ nsMsgKey msgKey;
+ aHdrChanged->GetMessageKey(&msgKey);
+ AndProcessingFlags(msgKey, 0);
+ return OnHdrAddedOrDeleted(aHdrChanged, false);
+}
+
+// 2. When a new messages gets added, we need to see if it's new.
+NS_IMETHODIMP nsMsgDBFolder::OnHdrAdded(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey , int32_t aFlags,
+ nsIDBChangeListener * aInstigator)
+{
+ if(aFlags & nsMsgMessageFlags::New)
+ CheckWithNewMessagesStatus(true);
+ return OnHdrAddedOrDeleted(aHdrChanged, true);
+}
+
+nsresult nsMsgDBFolder::OnHdrAddedOrDeleted(nsIMsgDBHdr *aHdrChanged, bool added)
+{
+ if(added)
+ NotifyItemAdded(aHdrChanged);
+ else
+ NotifyItemRemoved(aHdrChanged);
+ UpdateSummaryTotals(true);
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent,
+ nsIDBChangeListener * aInstigator)
+{
+ nsCOMPtr<nsIMsgDBHdr> hdrChanged;
+ mDatabase->GetMsgHdrForKey(aKeyChanged, getter_AddRefs(hdrChanged));
+ //In reality we probably want to just change the parent because otherwise we will lose things like
+ //selection.
+ if (hdrChanged)
+ {
+ //First delete the child from the old threadParent
+ OnHdrAddedOrDeleted(hdrChanged, false);
+ //Then add it to the new threadParent
+ OnHdrAddedOrDeleted(hdrChanged, true);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
+{
+ if (mBackupDatabase && instigator == mBackupDatabase)
+ {
+ mBackupDatabase->RemoveListener(this);
+ mBackupDatabase = nullptr;
+ }
+ else if (mDatabase)
+ {
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetManyHeadersToDownload(bool *retval)
+{
+ NS_ENSURE_ARG_POINTER(retval);
+ int32_t numTotalMessages;
+
+ // is there any reason to return false?
+ if (!mDatabase)
+ *retval = true;
+ else if (NS_SUCCEEDED(GetTotalMessages(false, &numTotalMessages)) && numTotalMessages <= 0)
+ *retval = true;
+ else
+ *retval = false;
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::MsgFitsDownloadCriteria(nsMsgKey msgKey, bool *result)
+{
+ if(!mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if(NS_FAILED(rv))
+ return rv;
+
+ if (hdr)
+ {
+ uint32_t msgFlags = 0;
+ hdr->GetFlags(&msgFlags);
+ // check if we already have this message body offline
+ if (! (msgFlags & nsMsgMessageFlags::Offline))
+ {
+ *result = true;
+ // check against the server download size limit .
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ {
+ bool limitDownloadSize = false;
+ rv = incomingServer->GetLimitOfflineMessageSize(&limitDownloadSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (limitDownloadSize)
+ {
+ int32_t maxDownloadMsgSize = 0;
+ uint32_t msgSize;
+ hdr->GetMessageSize(&msgSize);
+ rv = incomingServer->GetMaxMessageSize(&maxDownloadMsgSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ maxDownloadMsgSize *= 1024;
+ if (msgSize > (uint32_t) maxDownloadMsgSize)
+ *result = false;
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSupportsOffline(bool *aSupportsOffline)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsOffline);
+ if (mFlags & nsMsgFolderFlags::Virtual)
+ {
+ *aSupportsOffline = false;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!server)
+ return NS_ERROR_FAILURE;
+
+ int32_t offlineSupportLevel;
+ rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ *aSupportsOffline = (offlineSupportLevel >= OFFLINE_SUPPORT_LEVEL_REGULAR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ShouldStoreMsgOffline(nsMsgKey msgKey, bool *result)
+{
+ NS_ENSURE_ARG(result);
+ uint32_t flags = 0;
+ *result = false;
+ GetFlags(&flags);
+ return flags & nsMsgFolderFlags::Offline ? MsgFitsDownloadCriteria(msgKey, result) : NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::HasMsgOffline(nsMsgKey msgKey, bool *result)
+{
+ NS_ENSURE_ARG(result);
+ *result = false;
+ GetDatabase();
+ if(!mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if(NS_FAILED(rv))
+ return rv;
+
+ if (hdr)
+ {
+ uint32_t msgFlags = 0;
+ hdr->GetFlags(&msgFlags);
+ // check if we already have this message body offline
+ if ((msgFlags & nsMsgMessageFlags::Offline))
+ *result = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetOfflineMsgFolder(nsMsgKey msgKey, nsIMsgFolder **aMsgFolder) {
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ nsCOMPtr<nsIMsgFolder> subMsgFolder;
+ GetDatabase();
+ if (!mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ nsresult rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (hdr)
+ {
+ uint32_t msgFlags = 0;
+ hdr->GetFlags(&msgFlags);
+ // Check if we already have this message body offline
+ if ((msgFlags & nsMsgMessageFlags::Offline))
+ {
+ NS_IF_ADDREF(*aMsgFolder = this);
+ return NS_OK;
+ }
+ }
+ // it's okay to not get a folder. Folder is remain unchanged in that case.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFlags(uint32_t *_retval)
+{
+ ReadDBFolderInfo(false);
+ *_retval = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element)
+{
+ nsresult rv = NS_OK;
+ nsCString charset;
+
+ element->GetInt32Property("flags", (int32_t *) &mFlags);
+ element->GetInt32Property("totalMsgs", &mNumTotalMessages);
+ element->GetInt32Property("totalUnreadMsgs", &mNumUnreadMessages);
+ element->GetInt32Property("pendingUnreadMsgs", &mNumPendingUnreadMessages);
+ element->GetInt32Property("pendingMsgs", &mNumPendingTotalMessages);
+ element->GetInt64Property("expungedBytes", &mExpungedBytes);
+ element->GetInt64Property("folderSize", &mFolderSize);
+ element->GetStringProperty("charset", mCharset);
+
+#ifdef DEBUG_bienvenu1
+ nsCString uri;
+ GetURI(uri);
+ printf("read total %ld for %s\n", mNumTotalMessages, uri.get());
+#endif
+ mInitializedFromCache = true;
+ return rv;
+}
+
+nsresult nsMsgDBFolder::GetFolderCacheKey(nsIFile **aFile, bool createDBIfMissing /* = false */)
+{
+ nsresult rv;
+ nsCOMPtr <nsIFile> path;
+ rv = GetFilePath(getter_AddRefs(path));
+
+ // now we put a new file in aFile, because we're going to change it.
+ nsCOMPtr <nsIFile> dbPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dbPath)
+ {
+ dbPath->InitWithFile(path);
+ // if not a server, we need to convert to a db Path with .msf on the end
+ bool isServer = false;
+ GetIsServer(&isServer);
+
+ // if it's a server, we don't need the .msf appended to the name
+ if (!isServer)
+ {
+ nsCOMPtr <nsIFile> summaryName;
+ rv = GetSummaryFileLocation(dbPath, getter_AddRefs(summaryName));
+ dbPath->InitWithFile(summaryName);
+
+ // create the .msf file
+ // see bug #244217 for details
+ bool exists;
+ if (createDBIfMissing && NS_SUCCEEDED(dbPath->Exists(&exists)) && !exists) {
+ rv = dbPath->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ NS_IF_ADDREF(*aFile = dbPath);
+ return rv;
+}
+
+nsresult nsMsgDBFolder::FlushToFolderCache()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && accountManager)
+ {
+ nsCOMPtr<nsIMsgFolderCache> folderCache;
+ rv = accountManager->GetFolderCache(getter_AddRefs(folderCache));
+ if (NS_SUCCEEDED(rv) && folderCache)
+ rv = WriteToFolderCache(folderCache, false);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCache(nsIMsgFolderCache *folderCache, bool deep)
+{
+ nsresult rv = NS_OK;
+
+ if (folderCache)
+ {
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ nsCOMPtr <nsIFile> dbPath;
+ rv = GetFolderCacheKey(getter_AddRefs(dbPath));
+#ifdef DEBUG_bienvenu1
+ bool exists;
+ NS_ASSERTION(NS_SUCCEEDED(dbPath->Exists(&exists)) && exists, "file spec we're adding to cache should exist");
+#endif
+ if (NS_SUCCEEDED(rv) && dbPath)
+ {
+ nsCString persistentPath;
+ rv = dbPath->GetPersistentDescriptor(persistentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folderCache->GetCacheElement(persistentPath, true, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(rv) && cacheElement)
+ rv = WriteToFolderCacheElem(cacheElement);
+ }
+ }
+
+ if (!deep)
+ return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = GetSubFolders(getter_AddRefs(enumerator));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item));
+ if (!msgFolder)
+ continue;
+
+ if (folderCache)
+ {
+ rv = msgFolder->WriteToFolderCache(folderCache, true);
+ if (NS_FAILED(rv))
+ break;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCacheElem(nsIMsgFolderCacheElement *element)
+{
+ nsresult rv = NS_OK;
+
+ element->SetInt32Property("flags", (int32_t) mFlags);
+ element->SetInt32Property("totalMsgs", mNumTotalMessages);
+ element->SetInt32Property("totalUnreadMsgs", mNumUnreadMessages);
+ element->SetInt32Property("pendingUnreadMsgs", mNumPendingUnreadMessages);
+ element->SetInt32Property("pendingMsgs", mNumPendingTotalMessages);
+ element->SetInt64Property("expungedBytes", mExpungedBytes);
+ element->SetInt64Property("folderSize", mFolderSize);
+ element->SetStringProperty("charset", mCharset);
+
+#ifdef DEBUG_bienvenu1
+ nsCString uri;
+ GetURI(uri);
+ printf("writing total %ld for %s\n", mNumTotalMessages, uri.get());
+#endif
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsMsgKey msgKey;
+ aMessage->GetMessageKey(&msgKey);
+
+ if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied)
+ mDatabase->MarkReplied(msgKey, true, nullptr);
+ else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
+ mDatabase->MarkForwarded(msgKey, true, nullptr);
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::AddMarkAllReadUndoAction(nsIMsgWindow *msgWindow,
+ nsMsgKey *thoseMarked,
+ uint32_t numMarked)
+{
+ RefPtr<nsMsgReadStateTxn> readStateTxn = new nsMsgReadStateTxn();
+ if (!readStateTxn)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = readStateTxn->Init(this, numMarked, thoseMarked);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = readStateTxn->SetTransactionType(nsIMessenger::eMarkAllMsg);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ rv = msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = txnMgr->DoTransaction(readStateTxn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::MarkAllMessagesRead(nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = GetDatabase();
+ m_newMsgs.Clear();
+
+ if (NS_SUCCEEDED(rv))
+ {
+ EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/);
+ nsMsgKey *thoseMarked;
+ uint32_t numMarked;
+ rv = mDatabase->MarkAllRead(&numMarked, &thoseMarked);
+ EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Setup a undo-state
+ if (aMsgWindow && numMarked)
+ rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked, numMarked);
+ free(thoseMarked);
+ }
+
+ SetHasNewMessages(false);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::MarkThreadRead(nsIMsgThread *thread)
+{
+ nsresult rv = GetDatabase();
+ if(NS_SUCCEEDED(rv))
+ {
+ nsMsgKey *keys;
+ uint32_t numKeys;
+ rv = mDatabase->MarkThreadRead(thread, nullptr, &numKeys, &keys);
+ free(keys);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnStartRunningUrl(nsIURI *aUrl)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ if (mailUrl)
+ {
+ bool updatingFolder = false;
+ if (NS_SUCCEEDED(mailUrl->GetUpdatingFolder(&updatingFolder)) && updatingFolder)
+ NotifyFolderEvent(mFolderLoadedAtom);
+
+ // be sure to remove ourselves as a url listener
+ mailUrl->UnRegisterListener(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetRetentionSettings(nsIMsgRetentionSettings **settings)
+{
+ NS_ENSURE_ARG_POINTER(settings);
+ *settings = nullptr;
+ nsresult rv = NS_OK;
+ bool useServerDefaults = false;
+ if (!m_retentionSettings)
+ {
+ nsCString useServerRetention;
+ GetStringProperty(kUseServerRetentionProp, useServerRetention);
+ if (useServerRetention.EqualsLiteral("1"))
+ {
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ {
+ rv = incomingServer->GetRetentionSettings(settings);
+ useServerDefaults = true;
+ }
+ }
+ else
+ {
+ GetDatabase();
+ if (mDatabase)
+ {
+ // get the settings from the db - if the settings from the db say the folder
+ // is not overriding the incoming server settings, get the settings from the
+ // server.
+ rv = mDatabase->GetMsgRetentionSettings(settings);
+ if (NS_SUCCEEDED(rv) && *settings)
+ {
+ (*settings)->GetUseServerDefaults(&useServerDefaults);
+ if (useServerDefaults)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ NS_IF_RELEASE(*settings);
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ incomingServer->GetRetentionSettings(settings);
+ }
+ if (useServerRetention.EqualsLiteral("1") != useServerDefaults)
+ {
+ if (useServerDefaults)
+ useServerRetention.AssignLiteral("1");
+ else
+ useServerRetention.AssignLiteral("0");
+ SetStringProperty(kUseServerRetentionProp, useServerRetention);
+ }
+ }
+ }
+ else
+ return NS_ERROR_FAILURE;
+ }
+ // Only cache the retention settings if we've overridden the server
+ // settings (otherwise, we won't notice changes to the server settings).
+ if (!useServerDefaults)
+ m_retentionSettings = *settings;
+ }
+ else
+ NS_IF_ADDREF(*settings = m_retentionSettings);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetRetentionSettings(nsIMsgRetentionSettings *settings)
+{
+ bool useServerDefaults;
+ nsCString useServerRetention;
+
+ settings->GetUseServerDefaults(&useServerDefaults);
+ if (useServerDefaults)
+ {
+ useServerRetention.AssignLiteral("1");
+ m_retentionSettings = nullptr;
+ }
+ else
+ {
+ useServerRetention.AssignLiteral("0");
+ m_retentionSettings = settings;
+ }
+ SetStringProperty(kUseServerRetentionProp, useServerRetention);
+ GetDatabase();
+ if (mDatabase)
+ mDatabase->SetMsgRetentionSettings(settings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDownloadSettings(nsIMsgDownloadSettings **settings)
+{
+ NS_ENSURE_ARG_POINTER(settings);
+ nsresult rv = NS_OK;
+ if (!m_downloadSettings)
+ {
+ GetDatabase();
+ if (mDatabase)
+ {
+ // get the settings from the db - if the settings from the db say the folder
+ // is not overriding the incoming server settings, get the settings from the
+ // server.
+ rv = mDatabase->GetMsgDownloadSettings(getter_AddRefs(m_downloadSettings));
+ if (NS_SUCCEEDED(rv) && m_downloadSettings)
+ {
+ bool useServerDefaults;
+ m_downloadSettings->GetUseServerDefaults(&useServerDefaults);
+ if (useServerDefaults)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ incomingServer->GetDownloadSettings(getter_AddRefs(m_downloadSettings));
+ }
+ }
+ }
+ }
+ NS_IF_ADDREF(*settings = m_downloadSettings);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetDownloadSettings(nsIMsgDownloadSettings *settings)
+{
+ m_downloadSettings = settings;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::IsCommandEnabled(const nsACString& command, bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = true;
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::WriteStartOfNewLocalMessage()
+{
+ nsAutoCString result;
+ uint32_t writeCount;
+ time_t now = time ((time_t*) 0);
+ char *ct = ctime(&now);
+ ct[24] = 0;
+ result = "From - ";
+ result += ct;
+ result += MSG_LINEBREAK;
+ m_bytesAddedToLocalMsg = result.Length();
+
+ nsCOMPtr <nsISeekableStream> seekable;
+
+ if (m_offlineHeader)
+ seekable = do_QueryInterface(m_tempMessageStream);
+
+ m_tempMessageStream->Write(result.get(), result.Length(),
+ &writeCount);
+
+ NS_NAMED_LITERAL_CSTRING(MozillaStatus, "X-Mozilla-Status: 0001" MSG_LINEBREAK);
+ m_tempMessageStream->Write(MozillaStatus.get(), MozillaStatus.Length(),
+ &writeCount);
+ m_bytesAddedToLocalMsg += writeCount;
+ NS_NAMED_LITERAL_CSTRING(MozillaStatus2, "X-Mozilla-Status2: 00000000" MSG_LINEBREAK);
+ m_bytesAddedToLocalMsg += MozillaStatus2.Length();
+ return m_tempMessageStream->Write(MozillaStatus2.get(),
+ MozillaStatus2.Length(), &writeCount);
+}
+
+nsresult nsMsgDBFolder::StartNewOfflineMessage()
+{
+ bool isLocked;
+ GetLocked(&isLocked);
+ bool hasSemaphore = false;
+ if (isLocked)
+ {
+ // it's OK if we, the folder, have the semaphore.
+ TestSemaphore(static_cast<nsIMsgFolder*>(this), &hasSemaphore);
+ if (!hasSemaphore)
+ {
+ NS_WARNING("folder locked trying to download offline");
+ return NS_MSG_FOLDER_BUSY;
+ }
+ }
+ nsresult rv = GetOfflineStoreOutputStream(m_offlineHeader,
+ getter_AddRefs(m_tempMessageStream));
+ if (NS_SUCCEEDED(rv) && !hasSemaphore)
+ AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (NS_SUCCEEDED(rv))
+ WriteStartOfNewLocalMessage();
+ m_numOfflineMsgLines = 0;
+ return rv;
+}
+
+nsresult nsMsgDBFolder::EndNewOfflineMessage()
+{
+ nsCOMPtr <nsISeekableStream> seekable;
+ int64_t curStorePos;
+ uint64_t messageOffset;
+ uint32_t messageSize;
+
+ nsMsgKey messageKey;
+
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_offlineHeader->GetMessageKey(&messageKey);
+ if (m_tempMessageStream)
+ seekable = do_QueryInterface(m_tempMessageStream);
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ GetMsgStore(getter_AddRefs(msgStore));
+
+ if (seekable)
+ {
+ mDatabase->MarkOffline(messageKey, true, nullptr);
+ m_tempMessageStream->Flush();
+ int64_t tellPos;
+ seekable->Tell(&tellPos);
+ curStorePos = tellPos;
+
+ // N.B. This only works if we've set the offline flag for the message,
+ // so be careful about moving the call to MarkOffline above.
+ m_offlineHeader->GetMessageOffset(&messageOffset);
+ curStorePos -= messageOffset;
+ m_offlineHeader->SetOfflineMessageSize(curStorePos);
+ m_offlineHeader->GetMessageSize(&messageSize);
+ messageSize += m_bytesAddedToLocalMsg;
+ // unix/mac has a one byte line ending, but the imap server returns
+ // crlf terminated lines.
+ if (MSG_LINEBREAK_LEN == 1)
+ messageSize -= m_numOfflineMsgLines;
+
+ // We clear the offline flag on the message if the size
+ // looks wrong. Check if we're off by more than one byte per line.
+ if (messageSize > (uint32_t) curStorePos &&
+ (messageSize - (uint32_t) curStorePos) > (uint32_t) m_numOfflineMsgLines)
+ {
+ mDatabase->MarkOffline(messageKey, false, nullptr);
+ // we should truncate the offline store at messageOffset
+ ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (msgStore)
+ // this closes the stream
+ msgStore->DiscardNewMessage(m_tempMessageStream, m_offlineHeader);
+ else
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+#ifdef _DEBUG
+ nsAutoCString message("Offline message too small: messageSize=");
+ message.AppendInt(messageSize);
+ message.Append(" curStorePos=");
+ message.AppendInt(curStorePos);
+ message.Append(" numOfflineMsgLines=");
+ message.AppendInt(m_numOfflineMsgLines);
+ message.Append(" bytesAdded=");
+ message.AppendInt(m_bytesAddedToLocalMsg);
+ NS_ERROR(message.get());
+#endif
+ m_offlineHeader = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ else
+ m_offlineHeader->SetLineCount(m_numOfflineMsgLines);
+ }
+ if (msgStore)
+ msgStore->FinishNewMessage(m_tempMessageStream, m_offlineHeader);
+
+ m_offlineHeader = nullptr;
+ if (m_tempMessageStream)
+ {
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::CompactOfflineStore(nsIMsgWindow *inWindow, nsIUrlListener *aListener)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolderCompactor> folderCompactor = do_CreateInstance(NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderCompactor->Compact(this, true, aListener, inWindow);
+}
+
+class AutoCompactEvent : public mozilla::Runnable
+{
+public:
+ AutoCompactEvent(nsIMsgWindow *aMsgWindow, nsMsgDBFolder *aFolder)
+ : mMsgWindow(aMsgWindow), mFolder(aFolder)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mFolder)
+ mFolder->HandleAutoCompactEvent(mMsgWindow);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIMsgWindow> mMsgWindow;
+ RefPtr<nsMsgDBFolder> mFolder;
+};
+
+nsresult nsMsgDBFolder::HandleAutoCompactEvent(nsIMsgWindow *aWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIArray> allServers;
+ rv = accountMgr->GetAllServers(getter_AddRefs(allServers));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numServers = 0, serverIndex = 0;
+ rv = allServers->GetLength(&numServers);
+ int32_t offlineSupportLevel;
+ if (numServers > 0)
+ {
+ nsCOMPtr<nsIMutableArray> folderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMutableArray> offlineFolderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t totalExpungedBytes = 0;
+ int64_t offlineExpungedBytes = 0;
+ int64_t localExpungedBytes = 0;
+ do
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(allServers, serverIndex, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = server->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!msgStore)
+ continue;
+ bool supportsCompaction;
+ msgStore->GetSupportsCompaction(&supportsCompaction);
+ if (!supportsCompaction)
+ continue;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if(NS_SUCCEEDED(rv) && rootFolder)
+ {
+ rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIArray> allDescendents;
+ rootFolder->GetDescendants(getter_AddRefs(allDescendents));
+ uint32_t cnt = 0;
+ rv = allDescendents->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t expungedBytes = 0;
+ if (offlineSupportLevel > 0)
+ {
+ uint32_t flags;
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i);
+ expungedBytes = 0;
+ folder->GetFlags(&flags);
+ if (flags & nsMsgFolderFlags::Offline)
+ folder->GetExpungedBytes(&expungedBytes);
+ if (expungedBytes > 0 )
+ {
+ offlineFolderArray->AppendElement(folder, false);
+ offlineExpungedBytes += expungedBytes;
+ }
+ }
+ }
+ else //pop or local
+ {
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i);
+ expungedBytes = 0;
+ folder->GetExpungedBytes(&expungedBytes);
+ if (expungedBytes > 0 )
+ {
+ folderArray->AppendElement(folder, false);
+ localExpungedBytes += expungedBytes;
+ }
+ }
+ }
+ }
+ }
+ while (++serverIndex < numServers);
+ totalExpungedBytes = localExpungedBytes + offlineExpungedBytes;
+ int32_t purgeThreshold;
+ rv = GetPurgeThreshold(&purgeThreshold);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (totalExpungedBytes > (purgeThreshold * 1024))
+ {
+ bool okToCompact = false;
+ nsCOMPtr<nsIPrefService> pref = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ nsCOMPtr<nsIPrefBranch> branch;
+ pref->GetBranch("", getter_AddRefs(branch));
+
+ bool askBeforePurge;
+ branch->GetBoolPref(PREF_MAIL_PURGE_ASK, &askBeforePurge);
+ if (askBeforePurge && aWindow)
+ {
+ nsCOMPtr <nsIStringBundle> bundle;
+ rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString dialogTitle;
+ nsString confirmString;
+ nsString checkboxText;
+ nsString buttonCompactNowText;
+ nsAutoString compactSize;
+ FormatFileSize(totalExpungedBytes, true, compactSize);
+ const char16_t* params[] = { compactSize.get() };
+ rv = bundle->GetStringFromName(u"autoCompactAllFoldersTitle", getter_Copies(dialogTitle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->FormatStringFromName(u"autoCompactAllFoldersText",
+ params, 1, getter_Copies(confirmString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->GetStringFromName(u"autoCompactAlwaysAskCheckbox",
+ getter_Copies(checkboxText));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->GetStringFromName(u"compactNowButton",
+ getter_Copies(buttonCompactNowText));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool alwaysAsk = true; // "Always ask..." - checked by default.
+ int32_t buttonPressed = 0;
+
+ nsCOMPtr<nsIPrompt> dialog;
+ rv = aWindow->GetPromptDialog(getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const uint32_t buttonFlags =
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);
+ rv = dialog->ConfirmEx(dialogTitle.get(), confirmString.get(), buttonFlags,
+ buttonCompactNowText.get(), nullptr, nullptr,
+ checkboxText.get(), &alwaysAsk, &buttonPressed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!buttonPressed)
+ {
+ okToCompact = true;
+ if (!alwaysAsk) // [ ] Always ask me before compacting folders automatically
+ branch->SetBoolPref(PREF_MAIL_PURGE_ASK, false);
+ }
+ }
+ else
+ okToCompact = aWindow || !askBeforePurge;
+
+ if (okToCompact)
+ {
+ nsCOMPtr <nsIAtom> aboutToCompactAtom = MsgGetAtom("AboutToCompact");
+ NotifyFolderEvent(aboutToCompactAtom);
+
+ if (localExpungedBytes > 0)
+ {
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (offlineExpungedBytes > 0)
+ folderCompactor->CompactFolders(folderArray, offlineFolderArray, nullptr, aWindow);
+ else
+ folderCompactor->CompactFolders(folderArray, nullptr, nullptr, aWindow);
+ }
+ else if (offlineExpungedBytes > 0)
+ CompactAllOfflineStores(nullptr, aWindow, offlineFolderArray);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsMsgDBFolder::AutoCompact(nsIMsgWindow *aWindow)
+{
+ // we don't check for null aWindow, because this routine can get called
+ // in unit tests where we have no window. Just assume not OK if no window.
+ bool prompt;
+ nsresult rv = GetPromptPurgeThreshold(&prompt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ PRTime timeNow = PR_Now(); //time in microseconds
+ PRTime timeAfterOneHourOfLastPurgeCheck = gtimeOfLastPurgeCheck + oneHour;
+ if (timeAfterOneHourOfLastPurgeCheck < timeNow && prompt)
+ {
+ gtimeOfLastPurgeCheck = timeNow;
+ nsCOMPtr<nsIRunnable> event = new AutoCompactEvent(aWindow, this);
+ // Post this as an event because it can put up an alert, which
+ // might cause issues depending on the stack when we are called.
+ if (event)
+ NS_DispatchToCurrentThread(event);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CompactAllOfflineStores(nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aWindow,
+ nsIArray *aOfflineFolderArray)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor
+ = do_CreateInstance(NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderCompactor->CompactFolders(nullptr, aOfflineFolderArray, aUrlListener, aWindow);
+}
+
+nsresult
+nsMsgDBFolder::GetPromptPurgeThreshold(bool *aPrompt)
+{
+ NS_ENSURE_ARG(aPrompt);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && prefBranch)
+ {
+ rv = prefBranch->GetBoolPref(PREF_MAIL_PROMPT_PURGE_THRESHOLD, aPrompt);
+ if (NS_FAILED(rv))
+ {
+ *aPrompt = false;
+ rv = NS_OK;
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsMsgDBFolder::GetPurgeThreshold(int32_t *aThreshold)
+{
+ NS_ENSURE_ARG(aThreshold);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && prefBranch)
+ {
+ int32_t thresholdMB = 20;
+ bool thresholdMigrated = false;
+ prefBranch->GetIntPref(PREF_MAIL_PURGE_THRESHOLD_MB, &thresholdMB);
+ prefBranch->GetBoolPref(PREF_MAIL_PURGE_MIGRATED, &thresholdMigrated);
+ if (!thresholdMigrated)
+ {
+ *aThreshold = 20480;
+ (void) prefBranch->GetIntPref(PREF_MAIL_PURGE_THRESHOLD, aThreshold);
+ if (*aThreshold/1024 != thresholdMB)
+ {
+ thresholdMB = std::max(1, *aThreshold/1024);
+ prefBranch->SetIntPref(PREF_MAIL_PURGE_THRESHOLD_MB, thresholdMB);
+ }
+ prefBranch->SetBoolPref(PREF_MAIL_PURGE_MIGRATED, true);
+ }
+ *aThreshold = thresholdMB * 1024;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP //called on the folder that is renamed or about to be deleted
+nsMsgDBFolder::MatchOrChangeFilterDestination(nsIMsgFolder *newFolder, bool caseInsensitive, bool *found)
+{
+ NS_ENSURE_ARG_POINTER(found);
+ nsCString oldUri;
+ nsresult rv = GetURI(oldUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString newUri;
+ if (newFolder) //for matching uri's this will be null
+ {
+ rv = newFolder->GetURI(newUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIArray> allServers;
+ rv = accountMgr->GetAllServers(getter_AddRefs(allServers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numServers;
+ rv = allServers->GetLength(&numServers);
+ for (uint32_t serverIndex = 0; serverIndex < numServers; serverIndex++)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> server = do_QueryElementAt(allServers, serverIndex);
+ if (server)
+ {
+ bool canHaveFilters;
+ rv = server->GetCanHaveFilters(&canHaveFilters);
+ if (NS_SUCCEEDED(rv) && canHaveFilters)
+ {
+ // update the filterlist to match the new folder name
+ rv = server->GetFilterList(nullptr, getter_AddRefs(filterList));
+ if (NS_SUCCEEDED(rv) && filterList)
+ {
+ rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri, caseInsensitive, found);
+ if (NS_SUCCEEDED(rv) && *found && newFolder && !newUri.IsEmpty())
+ rv = filterList->SaveToDefaultFile();
+ }
+ // update the editable filterlist to match the new folder name
+ rv = server->GetEditableFilterList(nullptr, getter_AddRefs(filterList));
+ if (NS_SUCCEEDED(rv) && filterList)
+ {
+ rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri, caseInsensitive, found);
+ if (NS_SUCCEEDED(rv) && *found && newFolder && !newUri.IsEmpty())
+ rv = filterList->SaveToDefaultFile();
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetDBTransferInfo(nsIDBFolderInfo **aTransferInfo)
+{
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr <nsIMsgDatabase> db;
+ GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db));
+ if (dbFolderInfo)
+ dbFolderInfo->GetTransferInfo(aTransferInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetDBTransferInfo(nsIDBFolderInfo *aTransferInfo)
+{
+ NS_ENSURE_ARG(aTransferInfo);
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr <nsIMsgDatabase> db;
+ GetMsgDatabase(getter_AddRefs(db));
+ if (db)
+ {
+ db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if(dbFolderInfo)
+ {
+ dbFolderInfo->InitFromTransferInfo(aTransferInfo);
+ dbFolderInfo->SetBooleanProperty("forceReparse", false);
+ }
+ db->SetSummaryValid(true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetStringProperty(const char *propertyName, nsACString& propertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ nsCOMPtr <nsIFile> dbPath;
+ nsresult rv = GetFolderCacheKey(getter_AddRefs(dbPath));
+ if (dbPath)
+ {
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ rv = GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
+ if (cacheElement) //try to get from cache
+ rv = cacheElement->GetStringProperty(propertyName, propertyValue);
+ if (NS_FAILED(rv)) //if failed, then try to get from db
+ {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ bool exists;
+ rv = dbPath->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return NS_MSG_ERROR_FOLDER_MISSING;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv))
+ rv = folderInfo->GetCharProperty(propertyName, propertyValue);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetStringProperty(const char *propertyName, const nsACString& propertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ nsCOMPtr <nsIFile> dbPath;
+ GetFolderCacheKey(getter_AddRefs(dbPath));
+ if (dbPath)
+ {
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
+ if (cacheElement) //try to set in the cache
+ cacheElement->SetStringProperty(propertyName, propertyValue);
+ }
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(rv))
+ {
+ folderInfo->SetCharProperty(propertyName, propertyValue);
+ db->Commit(nsMsgDBCommitType::kLargeCommit); //commiting the db also commits the cache
+ }
+ return NS_OK;
+}
+
+// Get/Set ForcePropertyEmpty is only used with inherited properties
+NS_IMETHODIMP
+nsMsgDBFolder::GetForcePropertyEmpty(const char *aPropertyName, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsAutoCString nameEmpty(aPropertyName);
+ nameEmpty.Append(NS_LITERAL_CSTRING(".empty"));
+ nsCString value;
+ GetStringProperty(nameEmpty.get(), value);
+ *_retval = value.EqualsLiteral("true");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetForcePropertyEmpty(const char *aPropertyName, bool aValue)
+{
+ nsAutoCString nameEmpty(aPropertyName);
+ nameEmpty.Append(NS_LITERAL_CSTRING(".empty"));
+ return SetStringProperty(nameEmpty.get(),
+ aValue ? NS_LITERAL_CSTRING("true") : NS_LITERAL_CSTRING(""));
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetInheritedStringProperty(const char *aPropertyName, nsACString& aPropertyValue)
+{
+ NS_ENSURE_ARG_POINTER(aPropertyName);
+ nsCString value;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ bool forceEmpty = false;
+
+ if (!mIsServer)
+ {
+ GetForcePropertyEmpty(aPropertyName, &forceEmpty);
+ }
+ else
+ {
+ // root folders must get their values from the server
+ GetServer(getter_AddRefs(server));
+ if (server)
+ server->GetForcePropertyEmpty(aPropertyName, &forceEmpty);
+ }
+
+ if (forceEmpty)
+ {
+ aPropertyValue.Truncate();
+ return NS_OK;
+ }
+
+ // servers will automatically inherit from the preference mail.server.default.(propertyName)
+ if (server)
+ return server->GetCharValue(aPropertyName, aPropertyValue);
+
+ GetStringProperty(aPropertyName, value);
+ if (value.IsEmpty())
+ {
+ // inherit from the parent
+ nsCOMPtr<nsIMsgFolder> parent;
+ GetParent(getter_AddRefs(parent));
+ if (parent)
+ return parent->GetInheritedStringProperty(aPropertyName, aPropertyValue);
+ }
+
+ aPropertyValue.Assign(value);
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBFolder::SpamFilterClassifyMessage(const char *aURI, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgTraitService> traitService(do_GetService("@mozilla.org/msg-trait-service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ uint32_t *proIndices;
+ uint32_t *antiIndices;
+ rv = traitService->GetEnabledIndices(&count, &proIndices, &antiIndices);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aJunkMailPlugin->ClassifyTraitsInMessage(aURI, count, proIndices, antiIndices, this, aMsgWindow, this);
+ NS_Free(proIndices);
+ NS_Free(antiIndices);
+ return rv;
+}
+
+nsresult
+nsMsgDBFolder::SpamFilterClassifyMessages(const char **aURIArray, uint32_t aURICount, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin)
+{
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgTraitService> traitService(do_GetService("@mozilla.org/msg-trait-service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ uint32_t *proIndices;
+ uint32_t *antiIndices;
+ rv = traitService->GetEnabledIndices(&count, &proIndices, &antiIndices);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aJunkMailPlugin->ClassifyTraitsInMessages(aURICount, aURIArray, count,
+ proIndices, antiIndices, this, aMsgWindow, this);
+ NS_Free(proIndices);
+ NS_Free(antiIndices);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnMessageClassified(const char *aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent)
+{
+ if (!aMsgURI) // This signifies end of batch.
+ {
+ nsresult rv = NS_OK;
+ // Apply filters if needed.
+ uint32_t length;
+ if (mPostBayesMessagesToFilter &&
+ NS_SUCCEEDED(mPostBayesMessagesToFilter->GetLength(&length)) &&
+ length)
+ {
+ // Apply post-bayes filtering.
+ nsCOMPtr<nsIMsgFilterService>
+ filterService(do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ // We use a null nsIMsgWindow because we don't want some sort of ui
+ // appearing in the middle of automatic filtering (plus I really don't
+ // want to propagate that value.)
+ rv = filterService->ApplyFilters(nsMsgFilterType::PostPlugin,
+ mPostBayesMessagesToFilter,
+ this, nullptr, nullptr);
+ mPostBayesMessagesToFilter->Clear();
+ }
+
+ // Bail if we didn't actually classify any messages.
+ if (mClassifiedMsgKeys.IsEmpty())
+ return rv;
+
+ // Notify that we classified some messages.
+ nsCOMPtr<nsIMsgFolderNotificationService>
+ notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMutableArray> classifiedMsgHdrs =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numKeys = mClassifiedMsgKeys.Length();
+ for (uint32_t i = 0 ; i < numKeys ; ++i)
+ {
+ nsMsgKey msgKey = mClassifiedMsgKeys[i];
+ bool hasKey;
+ // It is very possible for a message header to no longer be around because
+ // a filter moved it.
+ rv = mDatabase->ContainsKey(msgKey, &hasKey);
+ if (!NS_SUCCEEDED(rv) || !hasKey)
+ continue;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
+ if (!NS_SUCCEEDED(rv))
+ continue;
+ classifiedMsgHdrs->AppendElement(msgHdr, false);
+ }
+
+ // only generate the notification if there are some classified messages
+ if (NS_SUCCEEDED(classifiedMsgHdrs->GetLength(&length)) && length)
+ notifier->NotifyMsgsClassified(classifiedMsgHdrs,
+ mBayesJunkClassifying,
+ mBayesTraitClassifying);
+ mClassifiedMsgKeys.Clear();
+
+ return rv;
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ rv = msgHdr->GetMessageKey(&msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if this message needs junk classification
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+
+ if (processingFlags & nsMsgProcessingFlags::ClassifyJunk)
+ {
+ mClassifiedMsgKeys.AppendElement(msgKey);
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::ClassifyJunk);
+
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(aClassification == nsIJunkMailPlugin::JUNK ?
+ nsIJunkMailPlugin::IS_SPAM_SCORE:
+ nsIJunkMailPlugin::IS_HAM_SCORE);
+ mDatabase->SetStringProperty(msgKey, "junkscore", msgJunkScore.get());
+ mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "plugin");
+
+ nsAutoCString strPercent;
+ strPercent.AppendInt(aJunkPercent);
+ mDatabase->SetStringProperty(msgKey, "junkpercent", strPercent.get());
+
+ if (aClassification == nsIJunkMailPlugin::JUNK)
+ {
+ // IMAP has its own way of marking read.
+ if (!(mFlags & nsMsgFolderFlags::ImapBox))
+ {
+ bool markAsReadOnSpam;
+ (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam);
+ if (markAsReadOnSpam)
+ {
+ rv = mDatabase->MarkRead(msgKey, true, this);
+ if (!NS_SUCCEEDED(rv))
+ NS_WARNING("failed marking spam message as read");
+ }
+ }
+ // mail folders will log junk hits with move info. Perhaps we should
+ // add a log here for non-mail folders as well, that don't override
+ // onMessageClassified
+ //rv = spamSettings->LogJunkHit(msgHdr, false);
+ //NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnMessageTraitsClassified(const char *aMsgURI,
+ uint32_t aTraitCount,
+ uint32_t *aTraits,
+ uint32_t *aPercents)
+{
+ if (!aMsgURI) // This signifies end of batch
+ return NS_OK; // We are not handling batching
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ rv = msgHdr->GetMessageKey(&msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+ if (!(processingFlags & nsMsgProcessingFlags::ClassifyTraits))
+ return NS_OK;
+
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::ClassifyTraits);
+
+ nsCOMPtr<nsIMsgTraitService> traitService;
+ traitService = do_GetService("@mozilla.org/msg-trait-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < aTraitCount; i++)
+ {
+ if (aTraits[i] == nsIJunkMailPlugin::JUNK_TRAIT)
+ continue; // junk is processed by the junk listener
+ nsAutoCString traitId;
+ rv = traitService->GetId(aTraits[i], traitId);
+ traitId.Insert(NS_LITERAL_CSTRING("bayespercent/"), 0);
+ nsAutoCString strPercent;
+ strPercent.AppendInt(aPercents[i]);
+ mDatabase->SetStringPropertyByHdr(msgHdr, traitId.get(), strPercent.get());
+ }
+ return NS_OK;
+}
+
+/**
+ * Call the filter plugins (XXX currently just one)
+ */
+NS_IMETHODIMP
+nsMsgDBFolder::CallFilterPlugins(nsIMsgWindow *aMsgWindow, bool *aFiltersRun)
+{
+ NS_ENSURE_ARG_POINTER(aFiltersRun);
+ *aFiltersRun = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ int32_t spamLevel = 0;
+
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString serverType;
+ server->GetType(serverType);
+
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ nsCOMPtr <nsIMsgFilterPlugin> filterPlugin;
+ server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
+ if (!filterPlugin) // it's not an error not to have the filter plugin.
+ return NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIJunkMailPlugin> junkMailPlugin = do_QueryInterface(filterPlugin);
+ if (!junkMailPlugin) // we currently only support the junk mail plugin
+ return NS_OK;
+
+ // if it's a news folder, then we really don't support junk in the ui
+ // yet the legacy spamLevel seems to think we should analyze it.
+ // Maybe we should upgrade that, but for now let's not analyze. We'll
+ // let an extension set an inherited property if they really want us to
+ // analyze this. We need that anyway to allow extension-based overrides.
+ // When we finalize adding junk in news to core, we'll deal with the
+ // spamLevel issue
+
+ // if this is the junk folder, or the trash folder
+ // don't analyze for spam, because we don't care
+ //
+ // if it's the sent, unsent, templates, or drafts,
+ // don't analyze for spam, because the user
+ // created that message
+ //
+ // if it's a public imap folder, or another users
+ // imap folder, don't analyze for spam, because
+ // it's not ours to analyze
+ //
+
+ bool filterForJunk = true;
+ if (serverType.EqualsLiteral("rss") ||
+ (mFlags & (nsMsgFolderFlags::SpecialUse |
+ nsMsgFolderFlags::ImapPublic | nsMsgFolderFlags::Newsgroup |
+ nsMsgFolderFlags::ImapOtherUser) &&
+ !(mFlags & nsMsgFolderFlags::Inbox)))
+ filterForJunk = false;
+
+ spamSettings->GetLevel(&spamLevel);
+ if (!spamLevel)
+ filterForJunk = false;
+
+ /*
+ * We'll use inherited folder properties for the junk trait to override the
+ * standard server-based activation of junk processing. This provides a
+ * hook for extensions to customize the application of junk filtering.
+ * Set inherited property "dobayes.mailnews@mozilla.org#junk" to "true"
+ * to force junk processing, and "false" to skip junk processing.
+ */
+
+ nsAutoCString junkEnableOverride;
+ GetInheritedStringProperty("dobayes.mailnews@mozilla.org#junk", junkEnableOverride);
+ if (junkEnableOverride.EqualsLiteral("true"))
+ filterForJunk = true;
+ else if (junkEnableOverride.EqualsLiteral("false"))
+ filterForJunk = false;
+
+ bool userHasClassified = false;
+ // if the user has not classified any messages yet, then we shouldn't bother
+ // running the junk mail controls. This creates a better first use experience.
+ // See Bug #250084.
+ junkMailPlugin->GetUserHasClassified(&userHasClassified);
+ if (!userHasClassified)
+ filterForJunk = false;
+
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ rv = GetMsgDatabase(getter_AddRefs(database));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if trait processing needed
+
+ nsCOMPtr<nsIMsgTraitService> traitService(
+ do_GetService("@mozilla.org/msg-trait-service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count = 0, *proIndices, *antiIndices;
+ rv = traitService->GetEnabledIndices(&count, &proIndices, &antiIndices);
+ bool filterForOther = false;
+ if (NS_SUCCEEDED(rv)) // We just skip this on failure, since it is rarely used
+ {
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ // The trait service determines which traits are globally enabled or
+ // disabled. If a trait is enabled, it can still be made inactive
+ // on a particular folder using an inherited property. To do that,
+ // set "dobayes." + trait proID as an inherited folder property with
+ // the string value "false"
+ //
+ // If any non-junk traits are active on the folder, then the bayes
+ // processing will calculate probabilities for all enabled traits.
+
+ if (proIndices[i] != nsIJunkMailPlugin::JUNK_TRAIT)
+ {
+ filterForOther = true;
+ nsAutoCString traitId;
+ nsAutoCString property("dobayes.");
+ traitService->GetId(proIndices[i], traitId);
+ property.Append(traitId);
+ nsAutoCString isEnabledOnFolder;
+ GetInheritedStringProperty(property.get(), isEnabledOnFolder);
+ if (isEnabledOnFolder.EqualsLiteral("false"))
+ filterForOther = false;
+ // We might have to allow a "true" override in the future, but
+ // for now there is no way for that to affect the processing
+ break;
+ }
+ }
+ NS_Free(proIndices);
+ NS_Free(antiIndices);
+ }
+
+ // Do we need to apply message filters?
+ bool filterPostPlugin = false; // Do we have a post-analysis filter?
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ GetFilterList(aMsgWindow, getter_AddRefs(filterList));
+ if (filterList)
+ {
+ uint32_t filterCount = 0;
+ filterList->GetFilterCount(&filterCount);
+ for (uint32_t index = 0; index < filterCount && !filterPostPlugin; ++index)
+ {
+ nsCOMPtr<nsIMsgFilter> filter;
+ filterList->GetFilterAt(index, getter_AddRefs(filter));
+ if (!filter)
+ continue;
+ nsMsgFilterTypeType filterType;
+ filter->GetFilterType(&filterType);
+ if (!(filterType & nsMsgFilterType::PostPlugin))
+ continue;
+ bool enabled = false;
+ filter->GetEnabled(&enabled);
+ if (!enabled)
+ continue;
+ filterPostPlugin = true;
+ }
+ }
+
+ // If there is nothing to do, leave now but let NotifyHdrsNotBeingClassified
+ // generate the msgsClassified notification for all newly added messages as
+ // tracked by the NotReportedClassified processing flag.
+ if (!filterForOther && !filterForJunk && !filterPostPlugin)
+ {
+ NotifyHdrsNotBeingClassified();
+ return NS_OK;
+ }
+
+ // get the list of new messages
+ //
+ uint32_t numNewKeys;
+ nsMsgKey *newKeys;
+ rv = database->GetNewList(&numNewKeys, &newKeys);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsMsgKey> newMessageKeys;
+ // Start from m_saveNewMsgs (and clear its current state). m_saveNewMsgs is
+ // where we stash the list of new messages when we are told to clear the list
+ // of new messages by the UI (which purges the list from the nsMsgDatabase).
+ newMessageKeys.SwapElements(m_saveNewMsgs);
+ if (numNewKeys)
+ newMessageKeys.AppendElements(newKeys, numNewKeys);
+
+ NS_Free(newKeys);
+
+ // build up list of keys to classify
+ nsTArray<nsMsgKey> classifyMsgKeys;
+ nsCString uri;
+
+ uint32_t numNewMessages = newMessageKeys.Length();
+ for (uint32_t i = 0 ; i < numNewMessages ; ++i)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsMsgKey msgKey = newMessageKeys[i];
+ rv = database->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
+ if (!NS_SUCCEEDED(rv))
+ continue;
+ // per-message junk tests.
+ bool filterMessageForJunk = false;
+ while (filterForJunk) // we'll break from this at the end
+ {
+ nsCString junkScore;
+ msgHdr->GetStringProperty("junkscore", getter_Copies(junkScore));
+ if (!junkScore.IsEmpty()) // ignore already scored messages.
+ break;
+
+ bool whiteListMessage = false;
+ spamSettings->CheckWhiteList(msgHdr, &whiteListMessage);
+ if (whiteListMessage)
+ {
+ // mark this msg as non-junk, because we whitelisted it.
+
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
+ database->SetStringProperty(msgKey, "junkscore", msgJunkScore.get());
+ database->SetStringProperty(msgKey, "junkscoreorigin", "whitelist");
+ break; // skip this msg since it's in the white list
+ }
+ filterMessageForJunk = true;
+
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::ClassifyJunk);
+ // Since we are junk processing, we want to defer the msgsClassified
+ // notification until the junk classification has occurred. The event
+ // is sufficiently reliable that we know this will be handled in
+ // OnMessageClassified at the end of the batch. We clear the
+ // NotReportedClassified flag since we know the message is in good hands.
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::NotReportedClassified);
+ break;
+ }
+
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+
+ bool filterMessageForOther = false;
+ // trait processing
+ if (!(processingFlags & nsMsgProcessingFlags::TraitsDone))
+ {
+ // don't do trait processing on this message again
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::TraitsDone);
+ if (filterForOther)
+ {
+ filterMessageForOther = true;
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::ClassifyTraits);
+ }
+ }
+
+ if (filterMessageForJunk || filterMessageForOther)
+ classifyMsgKeys.AppendElement(newMessageKeys[i]);
+
+ // Set messages to filter post-bayes.
+ // Have we already filtered this message?
+ if (!(processingFlags & nsMsgProcessingFlags::FiltersDone))
+ {
+ if (filterPostPlugin)
+ {
+ // Don't do filters on this message again.
+ // (Only set this if we are actually filtering since this is
+ // tantamount to a memory leak.)
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::FiltersDone);
+ // Lazily create the array.
+ if (!mPostBayesMessagesToFilter)
+ mPostBayesMessagesToFilter = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ mPostBayesMessagesToFilter->AppendElement(msgHdr, false);
+ }
+ }
+ }
+
+ NotifyHdrsNotBeingClassified();
+ // If there weren't any new messages, just return.
+ if (newMessageKeys.IsEmpty())
+ return NS_OK;
+
+ // If we do not need to do any work, leave.
+ // (We needed to get the list of new messages so we could get their headers so
+ // we can send notifications about them here.)
+
+ if (!classifyMsgKeys.IsEmpty())
+ {
+ // Remember what classifications are the source of this decision for when
+ // we perform the notification in OnMessageClassified at the conclusion of
+ // classification.
+ mBayesJunkClassifying = filterForJunk;
+ mBayesTraitClassifying = filterForOther;
+
+ uint32_t numMessagesToClassify = classifyMsgKeys.Length();
+ char ** messageURIs = (char **) PR_MALLOC(sizeof(const char *) * numMessagesToClassify);
+ if (!messageURIs)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ for (uint32_t msgIndex = 0; msgIndex < numMessagesToClassify ; ++msgIndex )
+ {
+ nsCString tmpStr;
+ rv = GenerateMessageURI(classifyMsgKeys[msgIndex], tmpStr);
+ messageURIs[msgIndex] = ToNewCString(tmpStr);
+ if (NS_FAILED(rv))
+ NS_WARNING("nsMsgDBFolder::CallFilterPlugins(): could not"
+ " generate URI for message");
+ }
+ // filterMsgs
+ *aFiltersRun = true;
+ rv = SpamFilterClassifyMessages((const char **) messageURIs, numMessagesToClassify, aMsgWindow, junkMailPlugin);
+ for ( uint32_t freeIndex=0 ; freeIndex < numMessagesToClassify ; ++freeIndex )
+ PR_Free(messageURIs[freeIndex]);
+ PR_Free(messageURIs);
+ }
+ else if (filterPostPlugin)
+ {
+ // Nothing to classify, so need to end batch ourselves. We do this so that
+ // post analysis filters will run consistently on a folder, even if
+ // disabled junk processing, which could be dynamic through whitelisting,
+ // makes the bayes analysis unnecessary.
+ OnMessageClassified(nullptr, nsIJunkMailPlugin::UNCLASSIFIED, 0);
+ }
+
+ return rv;
+}
+
+/**
+ * Adds the messages in the NotReportedClassified mProcessing set to the
+ * (possibly empty) array of msgHdrsNotBeingClassified, and send the
+ * nsIMsgFolderNotificationService notification.
+ */
+nsresult nsMsgDBFolder::NotifyHdrsNotBeingClassified()
+{
+ nsCOMPtr<nsIMutableArray> msgHdrsNotBeingClassified;
+
+ if (mProcessingFlag[5].keys)
+ {
+ nsTArray<nsMsgKey> keys;
+ mProcessingFlag[5].keys->ToMsgKeyArray(keys);
+ if (keys.Length())
+ {
+ msgHdrsNotBeingClassified = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ if (!msgHdrsNotBeingClassified)
+ return NS_ERROR_OUT_OF_MEMORY;
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ MsgGetHeadersFromKeys(mDatabase, keys, msgHdrsNotBeingClassified);
+
+ // Since we know we've handled all the NotReportedClassified messages,
+ // we clear the set by deleting and recreating it.
+ delete mProcessingFlag[5].keys;
+ mProcessingFlag[5].keys = nsMsgKeySetU::Create();
+ nsCOMPtr<nsIMsgFolderNotificationService>
+ notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsClassified(msgHdrsNotBeingClassified,
+ // no classification is being performed
+ false, false);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetLastMessageLoaded(nsMsgKey *aMsgKey)
+{
+ NS_ENSURE_ARG_POINTER(aMsgKey);
+ *aMsgKey = mLastMessageLoaded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetLastMessageLoaded(nsMsgKey aMsgKey)
+{
+ mLastMessageLoaded = aMsgKey;
+ return NS_OK;
+}
+
+// Returns true if: a) there is no need to prompt or b) the user is already
+// logged in or c) the user logged in successfully.
+bool nsMsgDBFolder::PromptForMasterPasswordIfNecessary()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool userNeedsToAuthenticate = false;
+ // if we're PasswordProtectLocalCache, then we need to find out if the server
+ // is authenticated.
+ (void) accountManager->GetUserNeedsToAuthenticate(&userNeedsToAuthenticate);
+ if (!userNeedsToAuthenticate)
+ return true;
+
+ // Do we have a master password?
+ nsCOMPtr<nsIPK11TokenDB> tokenDB =
+ do_GetService(NS_PK11TOKENDB_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIPK11Token> token;
+ rv = tokenDB->GetInternalKeyToken(getter_AddRefs(token));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool result;
+ rv = token->CheckPassword(EmptyCString(), &result);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (result)
+ {
+ // We don't have a master password, so this function isn't supported,
+ // therefore just tell account manager we've authenticated and return true.
+ accountManager->SetUserNeedsToAuthenticate(false);
+ return true;
+ }
+
+ // We have a master password, so try and login to the slot.
+ rv = token->Login(false);
+ if (NS_FAILED(rv))
+ // Login failed, so we didn't get a password (e.g. prompt cancelled).
+ return false;
+
+ // Double-check that we are now logged in
+ rv = token->IsLoggedIn(&result);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ accountManager->SetUserNeedsToAuthenticate(!result);
+ return result;
+}
+
+// this gets called after the last junk mail classification has run.
+nsresult nsMsgDBFolder::PerformBiffNotifications(void)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t numBiffMsgs = 0;
+ nsCOMPtr<nsIMsgFolder> root;
+ rv = GetRootFolder(getter_AddRefs(root));
+ root->GetNumNewMessages(true, &numBiffMsgs);
+ if (numBiffMsgs > 0)
+ {
+ server->SetPerformingBiff(true);
+ SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
+ server->SetPerformingBiff(false);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBFolder::initializeStrings()
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bundle->GetStringFromName(u"inboxFolderName",
+ &kLocalizedInboxName);
+ bundle->GetStringFromName(u"trashFolderName",
+ &kLocalizedTrashName);
+ bundle->GetStringFromName(u"sentFolderName",
+ &kLocalizedSentName);
+ bundle->GetStringFromName(u"draftsFolderName",
+ &kLocalizedDraftsName);
+ bundle->GetStringFromName(u"templatesFolderName",
+ &kLocalizedTemplatesName);
+ bundle->GetStringFromName(u"junkFolderName",
+ &kLocalizedJunkName);
+ bundle->GetStringFromName(u"outboxFolderName",
+ &kLocalizedUnsentName);
+ bundle->GetStringFromName(u"archivesFolderName",
+ &kLocalizedArchivesName);
+
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle("chrome://branding/locale/brand.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bundle->GetStringFromName(u"brandShortName",
+ &kLocalizedBrandShortName);
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBFolder::createCollationKeyGenerator()
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsILocaleService> localeSvc = do_GetService(NS_LOCALESERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILocale> locale;
+ rv = localeSvc->GetApplicationLocale(getter_AddRefs(locale));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsICollationFactory> factory = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = factory->CreateCollation(locale, &gCollationKeyGenerator);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::Init(const char* aURI)
+{
+ // for now, just initialize everything during Init()
+ nsresult rv;
+ rv = nsRDFResource::Init(aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return CreateBaseMessageURI(nsDependentCString(aURI));
+}
+
+nsresult nsMsgDBFolder::CreateBaseMessageURI(const nsACString& aURI)
+{
+ // Each folder needs to implement this.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetURI(nsACString& name)
+{
+ return nsRDFResource::GetValueUTF8(name);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+#if 0
+typedef bool
+(*nsArrayFilter)(nsISupports* element, void* data);
+#endif
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetSubFolders(nsISimpleEnumerator **aResult)
+{
+ return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::FindSubFolder(const nsACString& aEscapedSubFolderName, nsIMsgFolder **aFolder)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ // XXX use necko here
+ nsAutoCString uri;
+ uri.Append(mURI);
+ uri.Append('/');
+ uri.Append(aEscapedSubFolderName);
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ folder.swap(*aFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetHasSubFolders(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mSubFolders.Count() > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetNumSubFolders(uint32_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mSubFolders.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AddFolderListener(nsIFolderListener * listener)
+{
+ return mListeners.AppendElement(listener) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RemoveFolderListener(nsIFolderListener * listener)
+{
+ mListeners.RemoveElement(listener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetParent(nsIMsgFolder *aParent)
+{
+ mParent = do_GetWeakReference(aParent);
+ if (aParent)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> parentMsgFolder = do_QueryInterface(aParent, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ // servers do not have parents, so we must not be a server
+ mIsServer = false;
+ mIsServerIsValid = true;
+
+ // also set the server itself while we're here.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = parentMsgFolder->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ mServer = do_GetWeakReference(server);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetParent(nsIMsgFolder **aParent)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+ nsCOMPtr<nsIMsgFolder> parent = do_QueryReferent(mParent);
+ parent.swap(*aParent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetMessages(nsISimpleEnumerator **result)
+{
+ // XXX should this return an empty enumeration?
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::UpdateFolder(nsIMsgWindow *)
+{
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMsgDBFolder::GetFolderURL(nsACString& url)
+{
+ url.Assign(EmptyCString());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetServer(nsIMsgIncomingServer ** aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+ nsresult rv;
+ // short circut the server if we have it.
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
+ if (NS_FAILED(rv))
+ {
+ // try again after parsing the URI
+ rv = parseURI(true);
+ server = do_QueryReferent(mServer);
+ }
+ server.swap(*aServer);
+ return *aServer ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsMsgDBFolder::parseURI(bool needServer)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURL> url;
+
+ url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->SetSpec(mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // empty path tells us it's a server.
+ if (!mIsServerIsValid)
+ {
+ nsAutoCString path;
+ rv = url->GetPath(path);
+ if (NS_SUCCEEDED(rv))
+ mIsServer = path.EqualsLiteral("/");
+ mIsServerIsValid = true;
+ }
+
+ // grab the name off the leaf of the server
+ if (mName.IsEmpty())
+ {
+ // mName:
+ // the name is the trailing directory in the path
+ nsAutoCString fileName;
+ nsAutoCString escapedFileName;
+ url->GetFileName(escapedFileName);
+ if (!escapedFileName.IsEmpty())
+ {
+ // XXX conversion to unicode here? is fileName in UTF8?
+ // yes, let's say it is in utf8
+ MsgUnescapeString(escapedFileName, 0, fileName);
+ NS_ASSERTION(MsgIsUTF8(fileName), "fileName is not in UTF-8");
+ CopyUTF8toUTF16(fileName, mName);
+ }
+ }
+
+ // grab the server by parsing the URI and looking it up
+ // in the account manager...
+ // But avoid this extra work by first asking the parent, if any
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
+ if (NS_FAILED(rv))
+ {
+ // first try asking the parent instead of the URI
+ nsCOMPtr<nsIMsgFolder> parentMsgFolder;
+ GetParent(getter_AddRefs(parentMsgFolder));
+
+ if (parentMsgFolder)
+ rv = parentMsgFolder->GetServer(getter_AddRefs(server));
+
+ // no parent. do the extra work of asking
+ if (!server && needServer)
+ {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString serverType;
+ GetIncomingServerType(serverType);
+ if (serverType.IsEmpty())
+ {
+ NS_WARNING("can't determine folder's server type");
+ return NS_ERROR_FAILURE;
+ }
+
+ url->SetScheme(serverType);
+ rv = accountManager->FindServerByURI(url, false,
+ getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mServer = do_GetWeakReference(server);
+ } /* !mServer */
+
+ // now try to find the local path for this folder
+ if (server)
+ {
+ nsAutoCString newPath;
+ nsAutoCString escapedUrlPath;
+ nsAutoCString urlPath;
+ url->GetFilePath(escapedUrlPath);
+ if (!escapedUrlPath.IsEmpty())
+ {
+ MsgUnescapeString(escapedUrlPath, 0, urlPath);
+
+ // transform the filepath from the URI, such as
+ // "/folder1/folder2/foldern"
+ // to
+ // "folder1.sbd/folder2.sbd/foldern"
+ // (remove leading / and add .sbd to first n-1 folders)
+ // to be appended onto the server's path
+ bool isNewsFolder = false;
+ nsAutoCString scheme;
+ if (NS_SUCCEEDED(url->GetScheme(scheme)))
+ {
+ isNewsFolder = scheme.EqualsLiteral("news") ||
+ scheme.EqualsLiteral("snews") ||
+ scheme.EqualsLiteral("nntp");
+ }
+ NS_MsgCreatePathStringFromFolderURI(urlPath.get(), newPath, scheme,
+ isNewsFolder);
+ }
+
+ // now append munged path onto server path
+ nsCOMPtr<nsIFile> serverPath;
+ rv = server->GetLocalPath(getter_AddRefs(serverPath));
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mPath && serverPath)
+ {
+ if (!newPath.IsEmpty())
+ {
+ // I hope this is temporary - Ultimately,
+ // NS_MsgCreatePathStringFromFolderURI will need to be fixed.
+#if defined(XP_WIN)
+ MsgReplaceChar(newPath, '/', '\\');
+#endif
+ rv = serverPath->AppendRelativeNativePath(newPath);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to append to the serverPath");
+ if (NS_FAILED(rv))
+ {
+ mPath = nullptr;
+ return rv;
+ }
+ }
+ mPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mPath->InitWithFile(serverPath);
+ }
+ // URI is completely parsed when we've attempted to get the server
+ mHaveParsedURI=true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetIsServer(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // make sure we've parsed the URI
+ if (!mIsServerIsValid)
+ {
+ nsresult rv = parseURI();
+ if (NS_FAILED(rv) || !mIsServerIsValid)
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = mIsServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetNoSelect(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetImapShared(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ return GetFlag(nsMsgFolderFlags::PersonalShared, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanSubscribe(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // by default, you can't subscribe.
+ // if otherwise, override it.
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanFileMessages(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ //varada - checking folder flag to see if it is the "Unsent Messages"
+ //and if so return FALSE
+ if (mFlags & (nsMsgFolderFlags::Queue | nsMsgFolderFlags::Virtual))
+ {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+
+ // by default, you can't file messages into servers, only to folders
+ // if otherwise, override it.
+ *aResult = !isServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanDeleteMessages(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanCreateSubfolders(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ //Checking folder flag to see if it is the "Unsent Messages"
+ //or a virtual folder, and if so return FALSE
+ if (mFlags & (nsMsgFolderFlags::Queue | nsMsgFolderFlags::Virtual))
+ {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ // by default, you can create subfolders on server and folders
+ // if otherwise, override it.
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanRename(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+ // by default, you can't rename servers, only folders
+ // if otherwise, override it.
+ //
+ // check if the folder is a special folder
+ // (Trash, Drafts, Unsent Messages, Inbox, Sent, Templates, Junk, Archives)
+ // if it is, don't allow the user to rename it
+ // (which includes dnd moving it with in the same server)
+ //
+ // this errors on the side of caution. we'll return false a lot
+ // more often if we use flags,
+ // instead of checking if the folder really is being used as a
+ // special folder by looking at the "copies and folders" prefs on the
+ // identities.
+ *aResult = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanCompact(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv,rv);
+ // servers cannot be compacted --> 4.x
+ // virtual search folders cannot be compacted
+ *aResult = !isServer && !(mFlags & nsMsgFolderFlags::Virtual);
+ // Check if the store supports compaction
+ if (*aResult)
+ {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ GetMsgStore(getter_AddRefs(msgStore));
+ if (msgStore)
+ msgStore->GetSupportsCompaction(aResult);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::GetPrettyName(nsAString& name)
+{
+ return GetName(name);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetPrettyName(const nsAString& name)
+{
+ nsresult rv;
+
+ //Set pretty name only if special flag is set and if it the default folder name
+ if (mFlags & nsMsgFolderFlags::Inbox && name.LowerCaseEqualsLiteral("inbox"))
+ rv = SetName(nsDependentString(kLocalizedInboxName));
+ else if (mFlags & nsMsgFolderFlags::SentMail && name.LowerCaseEqualsLiteral("sent"))
+ rv = SetName(nsDependentString(kLocalizedSentName));
+ else if (mFlags & nsMsgFolderFlags::Drafts && name.LowerCaseEqualsLiteral("drafts"))
+ rv = SetName(nsDependentString(kLocalizedDraftsName));
+ else if (mFlags & nsMsgFolderFlags::Templates && name.LowerCaseEqualsLiteral("templates"))
+ rv = SetName(nsDependentString(kLocalizedTemplatesName));
+ else if (mFlags & nsMsgFolderFlags::Trash && name.LowerCaseEqualsLiteral("trash"))
+ rv = SetName(nsDependentString(kLocalizedTrashName));
+ else if (mFlags & nsMsgFolderFlags::Queue && name.LowerCaseEqualsLiteral("unsent messages"))
+ rv = SetName(nsDependentString(kLocalizedUnsentName));
+ else if (mFlags & nsMsgFolderFlags::Junk && name.LowerCaseEqualsLiteral("junk"))
+ rv = SetName(nsDependentString(kLocalizedJunkName));
+ else if (mFlags & nsMsgFolderFlags::Archive && name.LowerCaseEqualsLiteral("archives"))
+ rv = SetName(nsDependentString(kLocalizedArchivesName));
+ else
+ rv = SetName(name);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetName(nsAString& name)
+{
+ nsresult rv;
+ if (!mHaveParsedURI && mName.IsEmpty())
+ {
+ rv = parseURI();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // if it's a server, just forward the call
+ if (mIsServer)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ return server->GetPrettyName(name);
+ }
+
+ name = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetName(const nsAString& name)
+{
+ // override the URI-generated name
+ if (!mName.Equals(name))
+ {
+ mName = name;
+ // old/new value doesn't matter here
+ NotifyUnicharPropertyChanged(kNameAtom, name, name);
+ }
+ return NS_OK;
+}
+
+//For default, just return name
+NS_IMETHODIMP nsMsgDBFolder::GetAbbreviatedName(nsAString& aAbbreviatedName)
+{
+ return GetName(aAbbreviatedName);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetChildNamed(const nsAString& aName, nsIMsgFolder **aChild)
+{
+ NS_ENSURE_ARG_POINTER(aChild);
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders
+ *aChild = nullptr;
+ int32_t count = mSubFolders.Count();
+
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsString folderName;
+ nsresult rv = mSubFolders[i]->GetName(folderName);
+ // case-insensitive compare is probably LCD across OS filesystems
+ if (NS_SUCCEEDED(rv) &&
+ folderName.Equals(aName, nsCaseInsensitiveStringComparator()))
+ {
+ NS_ADDREF(*aChild = mSubFolders[i]);
+ return NS_OK;
+ }
+ }
+ // don't return NS_OK if we didn't find the folder
+ // see http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c15
+ // and http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c17
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetChildWithURI(const nsACString& uri, bool deep, bool caseInsensitive, nsIMsgFolder ** child)
+{
+ NS_ENSURE_ARG_POINTER(child);
+ // will return nullptr if we can't find it
+ *child = nullptr;
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = GetSubFolders(getter_AddRefs(enumerator));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool hasMore;
+ while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIRDFResource> folderResource(do_QueryInterface(item));
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item));
+ if (folderResource && folder)
+ {
+ const char *folderURI;
+ rv = folderResource->GetValueConst(&folderURI);
+ if (NS_FAILED(rv)) return rv;
+ bool equal = folderURI && (caseInsensitive ? uri.Equals(folderURI, nsCaseInsensitiveCStringComparator())
+ : uri.Equals(folderURI));
+ if (equal)
+ {
+ *child = folder;
+ NS_ADDREF(*child);
+ return NS_OK;
+ }
+ if (deep)
+ {
+ rv = folder->GetChildWithURI(uri, deep, caseInsensitive, child);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (*child)
+ return NS_OK;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetPrettiestName(nsAString& name)
+{
+ if (NS_SUCCEEDED(GetPrettyName(name)))
+ return NS_OK;
+ return GetName(name);
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::GetShowDeletedMessages(bool *showDeletedMessages)
+{
+ NS_ENSURE_ARG_POINTER(showDeletedMessages);
+ *showDeletedMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Delete()
+{
+ ForceDBClosed();
+
+ // Delete the .msf file.
+ // NOTE: this doesn't remove .msf files in subfolders, but
+ // both nsMsgBrkMBoxStore::DeleteFolder() and
+ // nsMsgMaildirStore::DeleteFolder() will remove those .msf files
+ // as a side-effect of deleting the .sbd directory.
+ nsCOMPtr<nsIFile> summaryFile;
+ nsresult rv = GetSummaryFile(getter_AddRefs(summaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists = false;
+ summaryFile->Exists(&exists);
+ if (exists) {
+ rv = summaryFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Ask the msgStore to delete the actual storage (mbox, maildir or whatever
+ // else may be supported in future).
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgStore->DeleteFolder(this);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::DeleteSubFolders(nsIArray *folders,
+ nsIMsgWindow *msgWindow)
+{
+ uint32_t count;
+ nsresult rv = folders->GetLength(&count);
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryElementAt(folders, i, &rv));
+ if (folder)
+ PropagateDelete(folder, true, msgWindow);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CreateStorageIfMissing(nsIUrlListener* /* urlListener */)
+{
+ NS_ASSERTION(false, "needs to be overridden");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::PropagateDelete(nsIMsgFolder *folder, bool deleteStorage, nsIMsgWindow *msgWindow)
+{
+ // first, find the folder we're looking to delete
+ nsresult rv = NS_OK;
+
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> child(mSubFolders[i]);
+ if (folder == child.get())
+ {
+ // Remove self as parent
+ child->SetParent(nullptr);
+ // maybe delete disk storage for it, and its subfolders
+ rv = child->RecursiveDelete(deleteStorage, msgWindow);
+ if (NS_SUCCEEDED(rv))
+ {
+ // Remove from list of subfolders.
+ mSubFolders.RemoveObjectAt(i);
+ NotifyItemRemoved(child);
+ break;
+ }
+ else // setting parent back if we failed
+ child->SetParent(this);
+ }
+ else
+ rv = child->PropagateDelete(folder, deleteStorage, msgWindow);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RecursiveDelete(bool deleteStorage, nsIMsgWindow *msgWindow)
+{
+ // If deleteStorage is true, recursively deletes disk storage for this folder
+ // and all its subfolders.
+ // Regardless of deleteStorage, always unlinks them from the children lists and
+ // frees memory for the subfolders but NOT for _this_
+
+ nsresult status = NS_OK;
+ nsCOMPtr <nsIFile> dbPath;
+
+ // first remove the deleted folder from the folder cache;
+ nsresult result = GetFolderCacheKey(getter_AddRefs(dbPath));
+
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &result);
+ if(NS_SUCCEEDED(result))
+ {
+ nsCOMPtr <nsIMsgFolderCache> folderCache;
+ result = accountMgr->GetFolderCache(getter_AddRefs(folderCache));
+ if (NS_SUCCEEDED(result) && folderCache)
+ {
+ nsCString persistentPath;
+ result = dbPath->GetPersistentDescriptor(persistentPath);
+ if (NS_SUCCEEDED(result))
+ folderCache->RemoveElement(persistentPath);
+ }
+ }
+
+ int32_t count = mSubFolders.Count();
+ while (count > 0)
+ {
+ nsIMsgFolder *child = mSubFolders[0];
+
+ child->SetParent(nullptr);
+ status = child->RecursiveDelete(deleteStorage, msgWindow); // recur
+ if (NS_SUCCEEDED(status))
+ // unlink it from this child's list
+ mSubFolders.RemoveObjectAt(0);
+ else
+ {
+ // setting parent back if we failed for some reason
+ child->SetParent(this);
+ break;
+ }
+
+ count--;
+ }
+
+ // now delete the disk storage for _this_
+ if (deleteStorage && NS_SUCCEEDED(status))
+ {
+ // All delete commands use deleteStorage = true, and local moves use false.
+ // IMAP moves use true, leaving this here in the hope that bug 439108
+ // works out.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderDeleted(this);
+ status = Delete();
+ }
+ return status;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CreateSubfolder(const nsAString& folderName, nsIMsgWindow *msgWindow)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AddSubfolder(const nsAString& name,
+ nsIMsgFolder** child)
+{
+ NS_ENSURE_ARG_POINTER(child);
+
+ int32_t flags = 0;
+ nsresult rv;
+ nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+
+ // URI should use UTF-8
+ // (see RFC2396 Uniform Resource Identifiers (URI): Generic Syntax)
+ nsAutoCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(name, escapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // fix for #192780
+ // if this is the root folder
+ // make sure the the special folders
+ // have the right uri.
+ // on disk, host\INBOX should be a folder with the uri mailbox://user@host/Inbox"
+ // as mailbox://user@host/Inbox != mailbox://user@host/INBOX
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder && (rootFolder.get() == (nsIMsgFolder *)this))
+ {
+ if (MsgLowerCaseEqualsLiteral(escapedName, "inbox"))
+ uri += "Inbox";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "unsent%20messages"))
+ uri += "Unsent%20Messages";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "drafts"))
+ uri += "Drafts";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "trash"))
+ uri += "Trash";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "sent"))
+ uri += "Sent";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "templates"))
+ uri +="Templates";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "archives"))
+ uri += "Archives";
+ else
+ uri += escapedName.get();
+ }
+ else
+ uri += escapedName.get();
+
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+ rv = GetChildWithURI(uri, false/*deep*/, true /*case Insensitive*/, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder)
+ return NS_MSG_FOLDER_EXISTS;
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ folder->GetFlags((uint32_t *)&flags);
+ flags |= nsMsgFolderFlags::Mail;
+ folder->SetParent(this);
+
+ bool isServer;
+ rv = GetIsServer(&isServer);
+
+ //Only set these if these are top level children.
+ if(NS_SUCCEEDED(rv) && isServer)
+ {
+ if(name.LowerCaseEqualsLiteral("inbox"))
+ {
+ flags |= nsMsgFolderFlags::Inbox;
+ SetBiffState(nsIMsgFolder::nsMsgBiffState_Unknown);
+ }
+ else if (name.LowerCaseEqualsLiteral("trash"))
+ flags |= nsMsgFolderFlags::Trash;
+ else if (name.LowerCaseEqualsLiteral("unsent messages") ||
+ name.LowerCaseEqualsLiteral("outbox"))
+ flags |= nsMsgFolderFlags::Queue;
+ }
+
+ folder->SetFlags(flags);
+
+ if (folder)
+ mSubFolders.AppendObject(folder);
+
+ folder.swap(*child);
+ // at this point we must be ok and we don't want to return failure in case
+ // GetIsServer failed.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CompactAll(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow, bool aCompactOfflineAlso)
+{
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::EmptyTrash(nsIMsgWindow *msgWindow, nsIUrlListener *aListener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsMsgDBFolder::CheckIfFolderExists(const nsAString& newFolderName, nsIMsgFolder *parentFolder, nsIMsgWindow *msgWindow)
+{
+ NS_ENSURE_ARG_POINTER(parentFolder);
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsresult rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = subFolders->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item));
+ if (!msgFolder)
+ break;
+
+ nsString folderName;
+
+ msgFolder->GetName(folderName);
+ if (folderName.Equals(newFolderName, nsCaseInsensitiveStringComparator()))
+ {
+ ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ }
+ }
+ return NS_OK;
+}
+
+bool
+nsMsgDBFolder::ConfirmAutoFolderRename(nsIMsgWindow *msgWindow,
+ const nsString& aOldName,
+ const nsString& aNewName)
+{
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsString folderName;
+ GetName(folderName);
+ const char16_t *formatStrings[] =
+ {
+ aOldName.get(),
+ folderName.get(),
+ aNewName.get()
+ };
+
+ nsString confirmString;
+ rv = bundle->FormatStringFromName(u"confirmDuplicateFolderRename",
+ formatStrings, 3, getter_Copies(confirmString));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ bool confirmed = false;
+ rv = ThrowConfirmationPrompt(msgWindow, confirmString, &confirmed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ return confirmed;
+}
+
+nsresult
+nsMsgDBFolder::AddDirectorySeparator(nsIFile *path)
+{
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ return path->SetLeafName(leafName);
+}
+
+/* Finds the directory associated with this folder. That is if the path is
+ c:\Inbox, it will return c:\Inbox.sbd if it succeeds. If that path doesn't
+ currently exist then it will create it. Path is strictly an out parameter.
+ */
+nsresult nsMsgDBFolder::CreateDirectoryForFolder(nsIFile **resultFile)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIFile> path;
+ rv = GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv)) return rv;
+
+ bool pathIsDirectory = false;
+ path->IsDirectory(&pathIsDirectory);
+
+ bool isServer;
+ GetIsServer(&isServer);
+
+ // Make sure this is REALLY the parent for subdirectories
+ if (pathIsDirectory && !isServer)
+ {
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ nsAutoString ext;
+ int32_t idx = leafName.RFindChar('.');
+ if (idx != -1)
+ ext = Substring(leafName, idx);
+ if (!ext.EqualsLiteral(FOLDER_SUFFIX))
+ pathIsDirectory = false;
+ }
+
+ if(!pathIsDirectory)
+ {
+ //If the current path isn't a directory, add directory separator
+ //and test it out.
+ rv = AddDirectorySeparator(path);
+ if(NS_FAILED(rv))
+ return rv;
+
+ //If that doesn't exist, then we have to create this directory
+ pathIsDirectory = false;
+ path->IsDirectory(&pathIsDirectory);
+ if(!pathIsDirectory)
+ {
+ bool pathExists;
+ path->Exists(&pathExists);
+ //If for some reason there's a file with the directory separator
+ //then we are going to fail.
+ rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY : path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ path.swap(*resultFile);
+ return rv;
+}
+
+/* Finds the backup directory associated with this folder, stored on the temp
+ drive. If that path doesn't currently exist then it will create it. Path is
+ strictly an out parameter.
+ */
+nsresult nsMsgDBFolder::CreateBackupDirectory(nsIFile **resultFile)
+{
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
+ getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = path->Append(NS_LITERAL_STRING("MozillaMailnews"));
+ bool pathIsDirectory;
+ path->IsDirectory(&pathIsDirectory);
+
+ // If that doesn't exist, then we have to create this directory
+ if (!pathIsDirectory)
+ {
+ bool pathExists;
+ path->Exists(&pathExists);
+ // If for some reason there's a file with the directory separator
+ // then we are going to fail.
+ rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY :
+ path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ if (NS_SUCCEEDED(rv))
+ path.swap(*resultFile);
+ return rv;
+}
+
+nsresult nsMsgDBFolder::GetBackupSummaryFile(nsIFile **aBackupFile, const nsACString& newName)
+{
+ nsCOMPtr<nsIFile> backupDir;
+ nsresult rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use a dummy message folder file so we can use
+ // GetSummaryFileLocation to get the db file name
+ nsCOMPtr<nsIFile> backupDBDummyFolder;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!newName.IsEmpty())
+ {
+ rv = backupDBDummyFolder->AppendNative(newName);
+ }
+ else // if newName is null, use the folder name
+ {
+ nsCOMPtr<nsIFile> folderPath;
+ rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString folderName;
+ rv = folderPath->GetNativeLeafName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDBDummyFolder->AppendNative(folderName);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDBFile;
+ rv = GetSummaryFileLocation(backupDBDummyFolder, getter_AddRefs(backupDBFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ backupDBFile.swap(*aBackupFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Rename(const nsAString& aNewName, nsIMsgWindow *msgWindow)
+{
+ nsCOMPtr<nsIFile> oldPathFile;
+ nsCOMPtr<nsIAtom> folderRenameAtom;
+ nsresult rv = GetFilePath(getter_AddRefs(oldPathFile));
+ if (NS_FAILED(rv))
+ return rv;
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ rv = GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder)
+ return NS_ERROR_FAILURE;
+ nsCOMPtr<nsISupports> parentSupport = do_QueryInterface(parentFolder);
+ nsCOMPtr<nsIFile> oldSummaryFile;
+ rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dirFile;
+ int32_t count = mSubFolders.Count();
+
+ if (count > 0)
+ rv = CreateDirectoryForFolder(getter_AddRefs(dirFile));
+
+ nsAutoString newDiskName(aNewName);
+ NS_MsgHashIfNecessary(newDiskName);
+
+ if (mName.Equals(aNewName, nsCaseInsensitiveStringComparator()))
+ {
+ rv = ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ }
+ else
+ {
+ nsCOMPtr <nsIFile> parentPathFile;
+ parentFolder->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+ bool isDirectory = false;
+ parentPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory)
+ AddDirectorySeparator(parentPathFile);
+
+ rv = CheckIfFolderExists(aNewName, parentFolder, msgWindow);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ ForceDBClosed();
+
+ // Save of dir name before appending .msf
+ nsAutoString newNameDirStr(newDiskName);
+
+ if (! (mFlags & nsMsgFolderFlags::Virtual))
+ rv = oldPathFile->MoveTo(nullptr, newDiskName);
+ if (NS_SUCCEEDED(rv))
+ {
+ newDiskName.AppendLiteral(SUMMARY_SUFFIX);
+ oldSummaryFile->MoveTo(nullptr, newDiskName);
+ }
+ else
+ {
+ ThrowAlertMsg("folderRenameFailed", msgWindow);
+ return rv;
+ }
+
+ if (NS_SUCCEEDED(rv) && count > 0)
+ {
+ // rename "*.sbd" directory
+ newNameDirStr.AppendLiteral(".sbd");
+ dirFile->MoveTo(nullptr, newNameDirStr);
+ }
+
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ if (parentSupport)
+ {
+ rv = parentFolder->AddSubfolder(aNewName, getter_AddRefs(newFolder));
+ if (newFolder)
+ {
+ newFolder->SetPrettyName(EmptyString());
+ newFolder->SetPrettyName(aNewName);
+ newFolder->SetFlags(mFlags);
+ bool changed = false;
+ MatchOrChangeFilterDestination(newFolder, true /*caseInsenstive*/, &changed);
+ if (changed)
+ AlertFilterChanged(msgWindow);
+
+ if (count > 0)
+ newFolder->RenameSubFolders(msgWindow, this);
+
+ if (parentFolder)
+ {
+ SetParent(nullptr);
+ parentFolder->PropagateDelete(this, false, msgWindow);
+ parentFolder->NotifyItemAdded(newFolder);
+ }
+ folderRenameAtom = MsgGetAtom("RenameCompleted");
+ newFolder->NotifyFolderEvent(folderRenameAtom);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RenameSubFolders(nsIMsgWindow *msgWindow, nsIMsgFolder *oldFolder)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ContainsChildNamed(const nsAString& name, bool* containsChild)
+{
+ NS_ENSURE_ARG_POINTER(containsChild);
+ nsCOMPtr<nsIMsgFolder> child;
+ GetChildNamed(name, getter_AddRefs(child));
+ *containsChild = child != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::IsAncestorOf(nsIMsgFolder *child, bool *isAncestor)
+{
+ NS_ENSURE_ARG_POINTER(isAncestor);
+ nsresult rv = NS_OK;
+
+ int32_t count = mSubFolders.Count();
+
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
+ if (folder.get() == child)
+ *isAncestor = true;
+ else
+ folder->IsAncestorOf(child, isAncestor);
+
+ if (*isAncestor)
+ return NS_OK;
+ }
+ *isAncestor = false;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GenerateUniqueSubfolderName(const nsAString& prefix,
+ nsIMsgFolder *otherFolder,
+ nsAString& name)
+{
+ /* only try 256 times */
+ for (int count = 0; count < 256; count++)
+ {
+ nsAutoString uniqueName;
+ uniqueName.Assign(prefix);
+ uniqueName.AppendInt(count);
+ bool containsChild;
+ bool otherContainsChild = false;
+ ContainsChildNamed(uniqueName, &containsChild);
+ if (otherFolder)
+ otherFolder->ContainsChildNamed(uniqueName, &otherContainsChild);
+
+ if (!containsChild && !otherContainsChild)
+ {
+ name = uniqueName;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::UpdateSummaryTotals(bool force)
+{
+ if (!mNotifyCountChanges)
+ return NS_OK;
+
+ int32_t oldUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ int32_t oldTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+ //We need to read this info from the database
+ nsresult rv = ReadDBFolderInfo(force);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ int32_t newUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ int32_t newTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+
+ //Need to notify listeners that total count changed.
+ if(oldTotalMessages != newTotalMessages)
+ NotifyIntPropertyChanged(kTotalMessagesAtom, oldTotalMessages, newTotalMessages);
+
+ if(oldUnreadMessages != newUnreadMessages)
+ NotifyIntPropertyChanged(kTotalUnreadMessagesAtom, oldUnreadMessages, newUnreadMessages);
+
+ FlushToFolderCache();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SummaryChanged()
+{
+ UpdateSummaryTotals(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumUnread(bool deep, int32_t *numUnread)
+{
+ NS_ENSURE_ARG_POINTER(numUnread);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t total = isServer ? 0 : mNumUnreadMessages + mNumPendingUnreadMessages;
+
+ if (deep)
+ {
+ if (total < 0) // deep search never returns negative counts
+ total = 0;
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
+ int32_t num;
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (!(folderFlags & nsMsgFolderFlags::Virtual))
+ {
+ folder->GetNumUnread(deep, &num);
+ total += num;
+ }
+ }
+ }
+ *numUnread = total;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetTotalMessages(bool deep, int32_t *totalMessages)
+{
+ NS_ENSURE_ARG_POINTER(totalMessages);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t total = isServer ? 0 : mNumTotalMessages + mNumPendingTotalMessages;
+
+ if (deep)
+ {
+ if (total < 0) // deep search never returns negative counts
+ total = 0;
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
+ int32_t num;
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (!(folderFlags & nsMsgFolderFlags::Virtual))
+ {
+ folder->GetTotalMessages(deep, &num);
+ total += num;
+ }
+ }
+ }
+ *totalMessages = total;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumPendingUnread(int32_t *aPendingUnread)
+{
+ *aPendingUnread = mNumPendingUnreadMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumPendingTotalMessages(int32_t *aPendingTotal)
+{
+ *aPendingTotal = mNumPendingTotalMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ChangeNumPendingUnread(int32_t delta)
+{
+ if (delta)
+ {
+ int32_t oldUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ mNumPendingUnreadMessages += delta;
+ int32_t newUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ NS_ASSERTION(newUnreadMessages >= 0, "shouldn't have negative unread message count");
+ if (newUnreadMessages >= 0)
+ {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo)
+ folderInfo->SetImapUnreadPendingMessages(mNumPendingUnreadMessages);
+ NotifyIntPropertyChanged(kTotalUnreadMessagesAtom, oldUnreadMessages, newUnreadMessages);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ChangeNumPendingTotalMessages(int32_t delta)
+{
+ if (delta)
+ {
+ int32_t oldTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+ mNumPendingTotalMessages += delta;
+ int32_t newTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo)
+ folderInfo->SetImapTotalPendingMessages(mNumPendingTotalMessages);
+ NotifyIntPropertyChanged(kTotalMessagesAtom, oldTotalMessages, newTotalMessages);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetFlag(uint32_t flag)
+{
+ // If calling this function causes us to open the db (i.e., it was not
+ // open before), we're going to close the db before returning.
+ bool dbWasOpen = mDatabase != nullptr;
+
+ ReadDBFolderInfo(false);
+ // OnFlagChange can be expensive, so don't call it if we don't need to
+ bool flagSet;
+ nsresult rv;
+
+ if (NS_FAILED(rv = GetFlag(flag, &flagSet)))
+ return rv;
+
+ if (!flagSet)
+ {
+ mFlags |= flag;
+ OnFlagChange(flag);
+ }
+ if (!dbWasOpen && mDatabase)
+ SetMsgDatabase(nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ClearFlag(uint32_t flag)
+{
+ // OnFlagChange can be expensive, so don't call it if we don't need to
+ bool flagSet;
+ nsresult rv;
+
+ if (NS_FAILED(rv = GetFlag(flag, &flagSet)))
+ return rv;
+
+ if (flagSet)
+ {
+ mFlags &= ~flag;
+ OnFlagChange (flag);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFlag(uint32_t flag, bool *_retval)
+{
+ *_retval = ((mFlags & flag) != 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ToggleFlag(uint32_t flag)
+{
+ mFlags ^= flag;
+ OnFlagChange (flag);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OnFlagChange(uint32_t flag)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo)
+ {
+#ifdef DEBUG_bienvenu1
+ nsString name;
+ rv = GetName(name);
+ NS_ASSERTION(Compare(name, kLocalizedTrashName) || (mFlags & nsMsgFolderFlags::Trash), "lost trash flag");
+#endif
+ folderInfo->SetFlags((int32_t) mFlags);
+ if (db)
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+
+ if (mFlags & flag)
+ NotifyIntPropertyChanged(mFolderFlagAtom, mFlags & ~flag, mFlags);
+ else
+ NotifyIntPropertyChanged(mFolderFlagAtom, mFlags | flag, mFlags);
+
+ if (flag & nsMsgFolderFlags::Offline)
+ {
+ bool newValue = mFlags & nsMsgFolderFlags::Offline;
+ rv = NotifyBoolPropertyChanged(kSynchronizeAtom, !newValue, !!newValue);
+ }
+ else if (flag & nsMsgFolderFlags::Elided)
+ {
+ bool newValue = mFlags & nsMsgFolderFlags::Elided;
+ rv = NotifyBoolPropertyChanged(kOpenAtom, !!newValue, !newValue);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetFlags(uint32_t aFlags)
+{
+ if (mFlags != aFlags)
+ {
+ uint32_t changedFlags = aFlags ^ mFlags;
+ mFlags = aFlags;
+ OnFlagChange(changedFlags);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFolderWithFlags(uint32_t aFlags, nsIMsgFolder** aResult)
+{
+ if ((mFlags & aFlags) == aFlags)
+ {
+ NS_ADDREF(*aResult = this);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders
+
+ int32_t count = mSubFolders.Count();
+ *aResult = nullptr;
+ for (int32_t i = 0; !*aResult && i < count; ++i)
+ mSubFolders[i]->GetFolderWithFlags(aFlags, aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFoldersWithFlags(uint32_t aFlags, nsIArray** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ListFoldersWithFlags(aFlags, array);
+ NS_ADDREF(*aResult = array);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ListFoldersWithFlags(uint32_t aFlags, nsIMutableArray* aFolders)
+{
+ NS_ENSURE_ARG_POINTER(aFolders);
+ if ((mFlags & aFlags) == aFlags)
+ aFolders->AppendElement(static_cast<nsRDFResource*>(this), false);
+
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders
+
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; ++i)
+ mSubFolders[i]->ListFoldersWithFlags(aFlags, aFolders);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::IsSpecialFolder(uint32_t aFlags,
+ bool aCheckAncestors,
+ bool *aIsSpecial)
+{
+ NS_ENSURE_ARG_POINTER(aIsSpecial);
+
+ if ((mFlags & aFlags) == 0)
+ {
+ nsCOMPtr<nsIMsgFolder> parentMsgFolder;
+ GetParent(getter_AddRefs(parentMsgFolder));
+
+ if (parentMsgFolder && aCheckAncestors)
+ parentMsgFolder->IsSpecialFolder(aFlags, aCheckAncestors, aIsSpecial);
+ else
+ *aIsSpecial = false;
+ }
+ else
+ {
+ // The user can set their INBOX to be their SENT folder.
+ // in that case, we want this folder to act like an INBOX,
+ // and not a SENT folder
+ *aIsSpecial = !((aFlags & nsMsgFolderFlags::SentMail) &&
+ (mFlags & nsMsgFolderFlags::Inbox));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDeletable(bool *deletable)
+{
+ NS_ENSURE_ARG_POINTER(deletable);
+ *deletable = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDisplayRecipients(bool *displayRecipients)
+{
+ *displayRecipients = false;
+ if (mFlags & nsMsgFolderFlags::SentMail && !(mFlags & nsMsgFolderFlags::Inbox))
+ *displayRecipients = true;
+ else if (mFlags & nsMsgFolderFlags::Queue)
+ *displayRecipients = true;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::AcquireSemaphore(nsISupports *semHolder)
+{
+ nsresult rv = NS_OK;
+ if (mSemaphoreHolder == NULL)
+ mSemaphoreHolder = semHolder; //Don't AddRef due to ownership issues.
+ else
+ rv = NS_MSG_FOLDER_BUSY;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ReleaseSemaphore(nsISupports *semHolder)
+{
+ if (!mSemaphoreHolder || mSemaphoreHolder == semHolder)
+ mSemaphoreHolder = NULL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::TestSemaphore(nsISupports *semHolder, bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = (mSemaphoreHolder == semHolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetLocked(bool *isLocked)
+{
+ *isLocked = mSemaphoreHolder != NULL;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::GetRelativePathName(nsACString& pathName)
+{
+ pathName.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSizeOnDisk(int64_t *size)
+{
+ NS_ENSURE_ARG_POINTER(size);
+ *size = kSizeUnknown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetSizeOnDisk(int64_t aSizeOnDisk)
+{
+ NotifyIntPropertyChanged(kFolderSizeAtom, mFolderSize, aSizeOnDisk);
+ mFolderSize = aSizeOnDisk;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetUsername(nsACString& userName)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetUsername(userName);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetHostname(nsACString& hostName)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetHostName(hostName);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNewMessages(nsIMsgWindow *, nsIUrlListener * /* aListener */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetBiffState(uint32_t *aBiffState)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetBiffState(aBiffState);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetBiffState(uint32_t aBiffState)
+{
+ uint32_t oldBiffState = nsMsgBiffState_Unknown;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ rv = server->GetBiffState(&oldBiffState);
+
+ if (oldBiffState != aBiffState)
+ {
+ // Get the server and notify it and not inbox.
+ if (!mIsServer)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetRootFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder)
+ return folder->SetBiffState(aBiffState);
+ }
+ if (server)
+ server->SetBiffState(aBiffState);
+
+ NotifyIntPropertyChanged(kBiffStateAtom, oldBiffState, aBiffState);
+ }
+ else if (aBiffState == oldBiffState && aBiffState == nsMsgBiffState_NewMail)
+ {
+ // The folder has been updated, so update the MRUTime
+ SetMRUTime();
+ // biff is already set, but notify that there is additional new mail for the folder
+ NotifyIntPropertyChanged(kNewMailReceivedAtom, 0, mNumNewBiffMessages);
+ }
+ else if (aBiffState == nsMsgBiffState_NoMail)
+ {
+ // even if the old biff state equals the new biff state, it is still possible that we've never
+ // cleared the number of new messages for this particular folder. This happens when the new mail state
+ // got cleared by viewing a new message in folder that is different from this one. Biff state is stored per server
+ // the num. of new messages is per folder.
+ SetNumNewMessages(0);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumNewMessages(bool deep, int32_t *aNumNewMessages)
+{
+ NS_ENSURE_ARG_POINTER(aNumNewMessages);
+
+ int32_t numNewMessages = (!deep || ! (mFlags & nsMsgFolderFlags::Virtual))
+ ? mNumNewBiffMessages : 0;
+ if (deep)
+ {
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ int32_t num;
+ mSubFolders[i]->GetNumNewMessages(deep, &num);
+ if (num > 0) // it's legal for counts to be negative if we don't know
+ numNewMessages += num;
+ }
+ }
+ *aNumNewMessages = numNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetNumNewMessages(int32_t aNumNewMessages)
+{
+ if (aNumNewMessages != mNumNewBiffMessages)
+ {
+ int32_t oldNumMessages = mNumNewBiffMessages;
+ mNumNewBiffMessages = aNumNewMessages;
+
+ nsAutoCString oldNumMessagesStr;
+ oldNumMessagesStr.AppendInt(oldNumMessages);
+ nsAutoCString newNumMessagesStr;
+ newNumMessagesStr.AppendInt(aNumNewMessages);
+ NotifyPropertyChanged(kNumNewBiffMessagesAtom, oldNumMessagesStr, newNumMessagesStr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetRootFolder(nsIMsgFolder * *aRootFolder)
+{
+ NS_ENSURE_ARG_POINTER(aRootFolder);
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetRootMsgFolder(aRootFolder);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetFilePath(nsIFile *aFile)
+{
+ mPath = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetFilePath(nsIFile * *aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+ // make a new nsIFile object in case the caller
+ // alters the underlying file object.
+ nsCOMPtr <nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mPath)
+ parseURI(true);
+ rv = file->InitWithFile(mPath);
+ file.swap(*aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSummaryFile(nsIFile **aSummaryFile)
+{
+ NS_ENSURE_ARG_POINTER(aSummaryFile);
+
+ nsresult rv;
+ nsCOMPtr <nsIFile> newSummaryLocation =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newSummaryLocation->InitWithFile(pathFile);
+
+ nsString fileName;
+ rv = newSummaryLocation->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ fileName.Append(NS_LITERAL_STRING(SUMMARY_SUFFIX));
+ rv = newSummaryLocation->SetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newSummaryLocation.forget(aSummaryFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::MarkMessagesRead(nsIArray *messages, bool markRead)
+{
+ uint32_t count;
+ nsresult rv;
+
+ rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(messages, i, &rv);
+ if (message)
+ rv = message->MarkRead(markRead);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::MarkMessagesFlagged(nsIArray *messages, bool markFlagged)
+{
+ uint32_t count;
+ nsresult rv;
+
+ rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(messages, i, &rv);
+ if (message)
+ rv = message->MarkFlagged(markFlagged);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetLabelForMessages(nsIArray *aMessages, nsMsgLabelValue aLabel)
+{
+ NS_ENSURE_ARG(aMessages);
+ GetDatabase();
+ if (mDatabase)
+ {
+ uint32_t count;
+ nsresult rv = aMessages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ (void) message->GetMessageKey(&msgKey);
+ rv = mDatabase->SetLabel(msgKey, aLabel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetJunkScoreForMessages(nsIArray *aMessages, const nsACString& junkScore)
+{
+ NS_ENSURE_ARG(aMessages);
+ nsresult rv = NS_OK;
+ GetDatabase();
+ if (mDatabase)
+ {
+ uint32_t count;
+ nsresult rv = aMessages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ (void) message->GetMessageKey(&msgKey);
+ mDatabase->SetStringProperty(msgKey, "junkscore", nsCString(junkScore).get());
+ mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter");
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::ApplyRetentionSettings()
+{
+ return ApplyRetentionSettings(true);
+}
+
+nsresult nsMsgDBFolder::ApplyRetentionSettings(bool deleteViaFolder)
+{
+ if (mFlags & nsMsgFolderFlags::Virtual) // ignore virtual folders.
+ return NS_OK;
+ bool weOpenedDB = !mDatabase;
+ nsCOMPtr<nsIMsgRetentionSettings> retentionSettings;
+ nsresult rv = GetRetentionSettings(getter_AddRefs(retentionSettings));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsMsgRetainByPreference retainByPreference =
+ nsIMsgRetentionSettings::nsMsgRetainAll;
+
+ retentionSettings->GetRetainByPreference(&retainByPreference);
+ if (retainByPreference != nsIMsgRetentionSettings::nsMsgRetainAll)
+ {
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mDatabase)
+ rv = mDatabase->ApplyRetentionSettings(retentionSettings, deleteViaFolder);
+ }
+ }
+ // we don't want applying retention settings to keep the db open, because
+ // if we try to purge a bunch of folders, that will leave the dbs all open.
+ // So if we opened the db, close it.
+ if (weOpenedDB)
+ CloseDBIfFolderNotOpen();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::DeleteMessages(nsIArray *messages,
+ nsIMsgWindow *msgWindow,
+ bool deleteStorage,
+ bool isMove,
+ nsIMsgCopyServiceListener *listener,
+ bool allowUndo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CopyMessages(nsIMsgFolder* srcFolder,
+ nsIArray *messages,
+ bool isMove,
+ nsIMsgWindow *window,
+ nsIMsgCopyServiceListener* listener,
+ bool isFolder,
+ bool allowUndo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CopyFolder(nsIMsgFolder* srcFolder,
+ bool isMoveFolder,
+ nsIMsgWindow *window,
+ nsIMsgCopyServiceListener* listener)
+{
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CopyFileMessage(nsIFile* aFile,
+ nsIMsgDBHdr* messageToReplace,
+ bool isDraftOrTemplate,
+ uint32_t aNewMsgFlags,
+ const nsACString &aNewMsgKeywords,
+ nsIMsgWindow *window,
+ nsIMsgCopyServiceListener* listener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CopyDataToOutputStreamForAppend(nsIInputStream *aInStream,
+ int32_t aLength, nsIOutputStream *aOutputStream)
+{
+ if (!aInStream)
+ return NS_OK;
+
+ uint32_t uiWritten;
+ return aOutputStream->WriteFrom(aInStream, aLength, &uiWritten);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CopyDataDone()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyPropertyChanged(nsIAtom *aProperty,
+ const nsACString& aOldValue,
+ const nsACString& aNewValue)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemPropertyChanged,
+ (this, aProperty,
+ nsCString(aOldValue).get(),
+ nsCString(aNewValue).get()));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemPropertyChanged(this, aProperty,
+ nsCString(aOldValue).get(),
+ nsCString(aNewValue).get());
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyUnicharPropertyChanged(nsIAtom *aProperty,
+ const nsAString& aOldValue,
+ const nsAString& aNewValue)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemUnicharPropertyChanged,
+ (this, aProperty,
+ nsString(aOldValue).get(),
+ nsString(aNewValue).get()));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemUnicharPropertyChanged(this,
+ aProperty,
+ nsString(aOldValue).get(),
+ nsString(aNewValue).get());
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyIntPropertyChanged(nsIAtom *aProperty, int64_t aOldValue,
+ int64_t aNewValue)
+{
+ // Don't send off count notifications if they are turned off.
+ if (!mNotifyCountChanges &&
+ ((aProperty == kTotalMessagesAtom) ||
+ (aProperty == kTotalUnreadMessagesAtom)))
+ return NS_OK;
+
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemIntPropertyChanged,
+ (this, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemIntPropertyChanged(this, aProperty,
+ aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyBoolPropertyChanged(nsIAtom* aProperty,
+ bool aOldValue, bool aNewValue)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemBoolPropertyChanged,
+ (this, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemBoolPropertyChanged(this, aProperty,
+ aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyPropertyFlagChanged(nsIMsgDBHdr *aItem, nsIAtom *aProperty,
+ uint32_t aOldValue, uint32_t aNewValue)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemPropertyFlagChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemPropertyFlagChanged(aItem, aProperty,
+ aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::NotifyItemAdded(nsISupports *aItem)
+{
+ static bool notify = true;
+
+ if (!notify)
+ return NS_OK;
+
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemAdded,
+ (this, aItem));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemAdded(this, aItem);
+}
+
+nsresult nsMsgDBFolder::NotifyItemRemoved(nsISupports *aItem)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemRemoved,
+ (this, aItem));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemRemoved(this, aItem);
+}
+
+nsresult nsMsgDBFolder::NotifyFolderEvent(nsIAtom* aEvent)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemEvent,
+ (this, aEvent));
+
+ //Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemEvent(this, aEvent);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetFilterList(aMsgWindow, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetFilterList(nsIMsgFilterList *aFilterList)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->SetFilterList(aFilterList);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetEditableFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetEditableFilterList(aMsgWindow, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetEditableFilterList(nsIMsgFilterList *aFilterList)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->SetEditableFilterList(aFilterList);
+}
+
+/* void enableNotifications (in long notificationType, in boolean enable); */
+NS_IMETHODIMP nsMsgDBFolder::EnableNotifications(int32_t notificationType, bool enable, bool dbBatching)
+{
+ if (notificationType == nsIMsgFolder::allMessageCountNotifications)
+ {
+ mNotifyCountChanges = enable;
+ // start and stop db batching here. This is under the theory
+ // that any time we want to enable and disable notifications,
+ // we're probably doing something that should be batched.
+ nsCOMPtr <nsIMsgDatabase> database;
+
+ if (dbBatching) //only if we do dbBatching we need to get db
+ GetMsgDatabase(getter_AddRefs(database));
+
+ if (enable)
+ {
+ if (database)
+ database->EndBatch();
+ UpdateSummaryTotals(true);
+ }
+ else if (database)
+ return database->StartBatch();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetMessageHeader(nsMsgKey msgKey, nsIMsgDBHdr **aMsgHdr)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ nsCOMPtr <nsIMsgDatabase> database;
+ nsresult rv = GetMsgDatabase(getter_AddRefs(database));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return (database) ? database->GetMsgHdrForKey(msgKey, aMsgHdr) : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDescendants(nsIArray** aDescendants)
+{
+ NS_ENSURE_ARG_POINTER(aDescendants);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> allFolders(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ListDescendants(allFolders);
+ allFolders.forget(aDescendants);
+ return NS_OK;
+}
+
+// this gets the deep sub-folders too, e.g., the children of the children
+NS_IMETHODIMP nsMsgDBFolder::ListDescendants(nsIMutableArray *aDescendants)
+{
+ NS_ENSURE_ARG_POINTER(aDescendants);
+
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders
+ uint32_t count = mSubFolders.Count();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> child(mSubFolders[i]);
+ aDescendants->AppendElement(child, false);
+ child->ListDescendants(aDescendants); // recurse
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetBaseMessageURI(nsACString& baseMessageURI)
+{
+ if (mBaseMessageURI.IsEmpty())
+ return NS_ERROR_FAILURE;
+ baseMessageURI = mBaseMessageURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetUriForMsg(nsIMsgDBHdr *msgHdr, nsACString& aURI)
+{
+ NS_ENSURE_ARG(msgHdr);
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ nsAutoCString uri;
+ uri.Assign(mBaseMessageURI);
+
+ // append a "#" followed by the message key.
+ uri.Append('#');
+ uri.AppendInt(msgKey);
+ aURI = uri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GenerateMessageURI(nsMsgKey msgKey, nsACString& aURI)
+{
+ nsCString uri;
+ nsresult rv = GetBaseMessageURI( uri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // append a "#" followed by the message key.
+ uri.Append('#');
+ uri.AppendInt(msgKey);
+ aURI = uri;
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBFolder::GetBaseStringBundle(nsIStringBundle **aBundle)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ bundle.swap(*aBundle);
+ return NS_OK;
+}
+
+nsresult //Do not use this routine if you have to call it very often because it creates a new bundle each time
+nsMsgDBFolder::GetStringFromBundle(const char *msgName, nsString& aResult)
+{
+ nsresult rv;
+ nsCOMPtr <nsIStringBundle> bundle;
+ rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle)
+ rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(msgName).get(), getter_Copies(aResult));
+ return rv;
+}
+
+nsresult
+nsMsgDBFolder::ThrowConfirmationPrompt(nsIMsgWindow *msgWindow, const nsAString& confirmString, bool *confirmed)
+{
+ if (msgWindow)
+ {
+ nsCOMPtr <nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (docShell)
+ {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ if (dialog && !confirmString.IsEmpty())
+ dialog->Confirm(nullptr, nsString(confirmString).get(), confirmed);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetStringWithFolderNameFromBundle(const char * msgName, nsAString& aResult)
+{
+ nsCOMPtr <nsIStringBundle> bundle;
+ nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle)
+ {
+ nsString folderName;
+ GetName(folderName);
+ const char16_t *formatStrings[] =
+ {
+ folderName.get(),
+ kLocalizedBrandShortName
+ };
+
+ nsString resultStr;
+ rv = bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(msgName).get(),
+ formatStrings, 2, getter_Copies(resultStr));
+ if (NS_SUCCEEDED(rv))
+ aResult.Assign(resultStr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ConfirmFolderDeletionForFilter(nsIMsgWindow *msgWindow, bool *confirmed)
+{
+ nsString confirmString;
+ nsresult rv = GetStringWithFolderNameFromBundle("confirmFolderDeletionForFilter", confirmString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ThrowConfirmationPrompt(msgWindow, confirmString, confirmed);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ThrowAlertMsg(const char * msgName, nsIMsgWindow *msgWindow)
+{
+ nsString alertString;
+ nsresult rv = GetStringWithFolderNameFromBundle(msgName, alertString);
+ if (NS_SUCCEEDED(rv) && !alertString.IsEmpty() && msgWindow)
+ {
+ nsCOMPtr<nsIPrompt> dialog;
+ msgWindow->GetPromptDialog(getter_AddRefs(dialog));
+ if (dialog)
+ dialog->Alert(nullptr, alertString.get());
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AlertFilterChanged(nsIMsgWindow *msgWindow)
+{
+ NS_ENSURE_ARG(msgWindow);
+ nsresult rv = NS_OK;
+ bool checkBox=false;
+ GetWarnFilterChanged(&checkBox);
+ if (!checkBox)
+ {
+ nsCOMPtr <nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ nsString alertString;
+ rv = GetStringFromBundle("alertFilterChanged", alertString);
+ nsString alertCheckbox;
+ rv = GetStringFromBundle("alertFilterCheckbox", alertCheckbox);
+ if (!alertString.IsEmpty() && !alertCheckbox.IsEmpty() && docShell)
+ {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ if (dialog)
+ {
+ dialog->AlertCheck(nullptr, alertString.get(), alertCheckbox.get(), &checkBox);
+ SetWarnFilterChanged(checkBox);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsMsgDBFolder::GetWarnFilterChanged(bool *aVal)
+{
+ NS_ENSURE_ARG(aVal);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = prefBranch->GetBoolPref(PREF_MAIL_WARN_FILTER_CHANGED, aVal);
+ if (NS_FAILED(rv))
+ *aVal = false;
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBFolder::SetWarnFilterChanged(bool aVal)
+{
+ nsresult rv=NS_OK;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->SetBoolPref(PREF_MAIL_WARN_FILTER_CHANGED, aVal);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::NotifyCompactCompleted()
+{
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsMsgDBFolder::CloseDBIfFolderNotOpen()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool folderOpen;
+ session->IsFolderOpenInWindow(this, &folderOpen);
+ if (!folderOpen && ! (mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
+ SetMsgDatabase(nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetSortOrder(int32_t order)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSortOrder(int32_t *order)
+{
+ NS_ENSURE_ARG_POINTER(order);
+
+ uint32_t flags;
+ nsresult rv = GetFlags(&flags);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (flags & nsMsgFolderFlags::Inbox)
+ *order = 0;
+ else if (flags & nsMsgFolderFlags::Drafts)
+ *order = 1;
+ else if (flags & nsMsgFolderFlags::Templates)
+ *order = 2;
+ else if (flags & nsMsgFolderFlags::SentMail)
+ *order = 3;
+ else if (flags & nsMsgFolderFlags::Archive)
+ *order = 4;
+ else if (flags & nsMsgFolderFlags::Junk)
+ *order = 5;
+ else if (flags & nsMsgFolderFlags::Trash)
+ *order = 6;
+ else if (flags & nsMsgFolderFlags::Virtual)
+ *order = 7;
+ else if (flags & nsMsgFolderFlags::Queue)
+ *order = 8;
+ else
+ *order = 9;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSortKey(uint32_t *aLength, uint8_t **aKey)
+{
+ NS_ENSURE_ARG(aKey);
+ int32_t order;
+ nsresult rv = GetSortOrder(&order);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsAutoString orderString;
+ orderString.AppendInt(order);
+ nsString folderName;
+ rv = GetName(folderName);
+ NS_ENSURE_SUCCESS(rv,rv);
+ orderString.Append(folderName);
+ return CreateCollationKey(orderString, aKey, aLength);
+}
+
+nsresult
+nsMsgDBFolder::CreateCollationKey(const nsString &aSource, uint8_t **aKey, uint32_t *aLength)
+{
+ NS_ENSURE_TRUE(gCollationKeyGenerator, NS_ERROR_NULL_POINTER);
+ return gCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, aSource,
+ aKey, aLength);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CompareSortKeys(nsIMsgFolder *aFolder, int32_t *sortOrder)
+{
+ uint8_t *sortKey1=nullptr;
+ uint8_t *sortKey2=nullptr;
+ uint32_t sortKey1Length;
+ uint32_t sortKey2Length;
+ nsresult rv = GetSortKey(&sortKey1Length, &sortKey1);
+ NS_ENSURE_SUCCESS(rv,rv);
+ aFolder->GetSortKey(&sortKey2Length, &sortKey2);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = gCollationKeyGenerator->CompareRawSortKey(sortKey1, sortKey1Length, sortKey2, sortKey2Length, sortOrder);
+ PR_Free(sortKey1);
+ PR_Free(sortKey2);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetInVFEditSearchScope (bool *aInVFEditSearchScope)
+{
+ *aInVFEditSearchScope = mInVFEditSearchScope;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetInVFEditSearchScope (bool aInVFEditSearchScope, bool aSetOnSubFolders)
+{
+ bool oldInVFEditSearchScope = mInVFEditSearchScope;
+ mInVFEditSearchScope = aInVFEditSearchScope;
+ NotifyBoolPropertyChanged(kInVFEditSearchScopeAtom, oldInVFEditSearchScope, mInVFEditSearchScope);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::FetchMsgPreviewText(nsMsgKey *aKeysToFetch, uint32_t aNumKeys,
+ bool aLocalOnly, nsIUrlListener *aUrlListener,
+ bool *aAsyncResults)
+{
+ NS_ENSURE_ARG_POINTER(aKeysToFetch);
+ NS_ENSURE_ARG_POINTER(aAsyncResults);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetMsgTextFromStream(nsIInputStream *stream, const nsACString &aCharset,
+ uint32_t bytesToRead, uint32_t aMaxOutputLen,
+ bool aCompressQuotes, bool aStripHTMLTags,
+ nsACString &aContentType, nsACString &aMsgText)
+{
+ /*
+ 1. non mime message - the message body starts after the blank line following the headers.
+ 2. mime message, multipart/alternative - we could simply scan for the boundary line,
+ advance past its headers, and treat the next few lines as the text.
+ 3. mime message, text/plain - body follows headers
+ 4. multipart/mixed - scan past boundary, treat next part as body.
+ */
+
+ nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>);
+ NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ nsAutoCString msgText;
+ nsAutoString contentType;
+ nsAutoString encoding;
+ nsAutoCString curLine;
+ nsAutoCString charset(aCharset);
+
+ // might want to use a state var instead of bools.
+ bool msgBodyIsHtml = false;
+ bool more = true;
+ bool reachedEndBody = false;
+ bool isBase64 = false;
+ bool inMsgBody = false;
+ bool justPassedEndBoundary = false;
+
+ uint32_t bytesRead = 0;
+
+ nsresult rv;
+
+ // Both are used to extract data from the headers
+ nsCOMPtr<nsIMimeHeaders> mimeHeaders(do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMIMEHeaderParam> mimeHdrParam(do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Stack of boundaries, used to figure out where we are
+ nsTArray<nsCString> boundaryStack;
+
+ while (!inMsgBody && bytesRead <= bytesToRead)
+ {
+ nsAutoCString msgHeaders;
+ // We want to NS_ReadLine until we get to a blank line (the end of the headers)
+ while (more)
+ {
+ rv = NS_ReadLine(stream, lineBuffer.get(), curLine, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (curLine.IsEmpty())
+ break;
+ msgHeaders.Append(curLine);
+ msgHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
+ bytesRead += curLine.Length();
+ if (bytesRead > bytesToRead)
+ break;
+ }
+
+ // There's no point in processing if we can't get the body
+ if (bytesRead > bytesToRead)
+ break;
+
+ // Process the headers, looking for things we need
+ rv = mimeHeaders->Initialize(msgHeaders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contentTypeHdr;
+ mimeHeaders->ExtractHeader("Content-Type", false, contentTypeHdr);
+
+ // Get the content type
+ // If we don't have a content type, then we assign text/plain
+ // this is in violation of the RFC for multipart/digest, though
+ // Also, if we've just passed an end boundary, we're going to ignore this.
+ if (!justPassedEndBoundary && contentTypeHdr.IsEmpty())
+ contentType.Assign(NS_LITERAL_STRING("text/plain"));
+ else
+ mimeHdrParam->GetParameter(contentTypeHdr, nullptr, EmptyCString(), false, nullptr, contentType);
+
+ justPassedEndBoundary = false;
+
+ // If we are multipart, then we need to get the boundary
+ if (StringBeginsWith(contentType, NS_LITERAL_STRING("multipart/"), nsCaseInsensitiveStringComparator()))
+ {
+ nsAutoString boundaryParam;
+ mimeHdrParam->GetParameter(contentTypeHdr, "boundary", EmptyCString(), false, nullptr, boundaryParam);
+ if (!boundaryParam.IsEmpty())
+ {
+ nsAutoCString boundary(NS_LITERAL_CSTRING("--"));
+ boundary.Append(NS_ConvertUTF16toUTF8(boundaryParam));
+ boundaryStack.AppendElement(boundary);
+ }
+ }
+
+ // If we are message/rfc822, then there's another header block coming up
+ else if (contentType.LowerCaseEqualsLiteral("message/rfc822"))
+ continue;
+
+ // If we are a text part, then we want it
+ else if (StringBeginsWith(contentType, NS_LITERAL_STRING("text/"), nsCaseInsensitiveStringComparator()))
+ {
+ inMsgBody = true;
+
+ if (contentType.LowerCaseEqualsLiteral("text/html"))
+ msgBodyIsHtml = true;
+
+ // Also get the charset if required
+ if (charset.IsEmpty())
+ {
+ nsAutoString charsetW;
+ mimeHdrParam->GetParameter(contentTypeHdr, "charset", EmptyCString(), false, nullptr, charsetW);
+ charset.Assign(NS_ConvertUTF16toUTF8(charsetW));
+ }
+
+ // Finally, get the encoding
+ nsAutoCString encodingHdr;
+ mimeHeaders->ExtractHeader("Content-Transfer-Encoding", false, encodingHdr);
+ if (!encodingHdr.IsEmpty())
+ mimeHdrParam->GetParameter(encodingHdr, nullptr, EmptyCString(), false, nullptr, encoding);
+
+ if (encoding.LowerCaseEqualsLiteral(ENCODING_BASE64))
+ isBase64 = true;
+ }
+
+ // We need to consume the rest, until the next headers
+ uint32_t count = boundaryStack.Length();
+ nsAutoCString boundary;
+ nsAutoCString endBoundary;
+ if (count)
+ {
+ boundary.Assign(boundaryStack.ElementAt(count - 1));
+ endBoundary.Assign(boundary);
+ endBoundary.Append(NS_LITERAL_CSTRING("--"));
+ }
+ while (more)
+ {
+ rv = NS_ReadLine(stream, lineBuffer.get(), curLine, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count)
+ {
+ // If we've reached a MIME final delimiter, pop and break
+ if (StringBeginsWith(curLine, endBoundary))
+ {
+ if (inMsgBody)
+ reachedEndBody = true;
+ boundaryStack.RemoveElementAt(count - 1);
+ justPassedEndBoundary = true;
+ break;
+ }
+ // If we've reached the end of this MIME part, we can break
+ if (StringBeginsWith(curLine, boundary))
+ {
+ if (inMsgBody)
+ reachedEndBody = true;
+ break;
+ }
+ }
+
+ // Only append the text if we're actually in the message body
+ if (inMsgBody)
+ {
+ msgText.Append(curLine);
+ if (!isBase64)
+ msgText.Append(NS_LITERAL_CSTRING("\r\n"));
+ }
+
+ bytesRead += curLine.Length();
+ if (bytesRead > bytesToRead)
+ break;
+ }
+ }
+ lineBuffer = nullptr;
+
+ // if the snippet is encoded, decode it
+ if (!encoding.IsEmpty())
+ decodeMsgSnippet(NS_ConvertUTF16toUTF8(encoding), !reachedEndBody, msgText);
+
+ // In order to turn our snippet into unicode, we need to convert it from the charset we
+ // detected earlier.
+ nsString unicodeMsgBodyStr;
+ ConvertToUnicode(charset.get(), msgText, unicodeMsgBodyStr);
+
+ // now we've got a msg body. If it's html, convert it to plain text.
+ if (msgBodyIsHtml && aStripHTMLTags)
+ ConvertMsgSnippetToPlainText(unicodeMsgBodyStr, unicodeMsgBodyStr);
+
+ // We want to remove any whitespace from the beginning and end of the string
+ unicodeMsgBodyStr.Trim(" \t\r\n", true, true);
+
+ // step 3, optionally remove quoted text from the snippet
+ nsString compressedQuotesMsgStr;
+ if (aCompressQuotes)
+ compressQuotesInMsgSnippet(unicodeMsgBodyStr, compressedQuotesMsgStr);
+
+ // now convert back to utf-8 which is more convenient for storage
+ CopyUTF16toUTF8(aCompressQuotes ? compressedQuotesMsgStr : unicodeMsgBodyStr, aMsgText);
+
+ // finally, truncate the string based on aMaxOutputLen
+ if (aMsgText.Length() > aMaxOutputLen) {
+ if (NS_IsAscii(aMsgText.BeginReading()))
+ aMsgText.SetLength(aMaxOutputLen);
+ else
+ nsMsgI18NShrinkUTF8Str(nsCString(aMsgText),
+ aMaxOutputLen, aMsgText);
+ }
+
+ // Also assign the content type being returned
+ aContentType.Assign(NS_ConvertUTF16toUTF8(contentType));
+ return rv;
+}
+
+/**
+ * decodeMsgSnippet - helper function which applies the appropriate transfer decoding
+ * to the message snippet based on aEncodingType. Currently handles
+ * base64 and quoted-printable. If aEncodingType refers to an encoding we don't
+ * handle, the message data is passed back unmodified.
+ * @param aEncodingType the encoding type (base64, quoted-printable)
+ * @param aIsComplete the snippet is actually the entire message so the decoder
+ * doesn't have to worry about partial data
+ * @param aMsgSnippet in/out argument. The encoded msg snippet and then the decoded snippet
+ */
+void nsMsgDBFolder::decodeMsgSnippet(const nsACString& aEncodingType, bool aIsComplete, nsCString& aMsgSnippet)
+{
+ if (MsgLowerCaseEqualsLiteral(aEncodingType, ENCODING_BASE64))
+ {
+ int32_t base64Len = aMsgSnippet.Length();
+ if (aIsComplete)
+ base64Len -= base64Len % 4;
+ char *decodedBody = PL_Base64Decode(aMsgSnippet.get(), base64Len, nullptr);
+ if (decodedBody)
+ aMsgSnippet.Adopt(decodedBody);
+ }
+ else if (MsgLowerCaseEqualsLiteral(aEncodingType, ENCODING_QUOTED_PRINTABLE))
+ {
+ // giant hack - decode in place, and truncate string.
+ MsgStripQuotedPrintable((unsigned char *) aMsgSnippet.get());
+ aMsgSnippet.SetLength(strlen(aMsgSnippet.get()));
+ }
+}
+
+/**
+ * stripQuotesFromMsgSnippet - Reduces quoted reply text including the citation (Scott wrote:) from
+ * the message snippet to " ... ". Assumes the snippet has been decoded and converted to
+ * plain text.
+ * @param aMsgSnippet in/out argument. The string to strip quotes from.
+ */
+void nsMsgDBFolder::compressQuotesInMsgSnippet(const nsString& aMsgSnippet, nsAString& aCompressedQuotes)
+{
+ int32_t msgBodyStrLen = aMsgSnippet.Length();
+ bool lastLineWasAQuote = false;
+ int32_t offset = 0;
+ int32_t lineFeedPos = 0;
+ while (offset < msgBodyStrLen)
+ {
+ lineFeedPos = aMsgSnippet.FindChar('\n', offset);
+ if (lineFeedPos != -1)
+ {
+ const nsAString& currentLine = Substring(aMsgSnippet, offset, lineFeedPos - offset);
+ // this catches quoted text ("> "), nested quotes of any level (">> ", ">>> ", ...)
+ // it also catches empty line quoted text (">"). It might be over agressive and require
+ // tweaking later.
+ // Try to strip the citation. If the current line ends with a ':' and the next line
+ // looks like a quoted reply (starts with a ">") skip the current line
+ if (StringBeginsWith(currentLine, NS_LITERAL_STRING(">")) ||
+ (lineFeedPos + 1 < msgBodyStrLen && lineFeedPos
+ && aMsgSnippet[lineFeedPos - 1] == char16_t(':')
+ && aMsgSnippet[lineFeedPos + 1] == char16_t('>')))
+ {
+ lastLineWasAQuote = true;
+ }
+ else if (!currentLine.IsEmpty())
+ {
+ if (lastLineWasAQuote)
+ {
+ aCompressedQuotes += NS_LITERAL_STRING(" ... ");
+ lastLineWasAQuote = false;
+ }
+
+ aCompressedQuotes += currentLine;
+ aCompressedQuotes += char16_t(' '); // don't forget to substitute a space for the line feed
+ }
+
+ offset = lineFeedPos + 1;
+ }
+ else
+ {
+ aCompressedQuotes.Append(Substring(aMsgSnippet, offset, msgBodyStrLen - offset));
+ break;
+ }
+ }
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ConvertMsgSnippetToPlainText(
+ const nsAString& aMessageText, nsAString& aOutText)
+{
+ uint32_t flags = nsIDocumentEncoder::OutputLFLineBreak
+ | nsIDocumentEncoder::OutputNoScriptContent
+ | nsIDocumentEncoder::OutputNoFramesContent
+ | nsIDocumentEncoder::OutputBodyOnly;
+ nsCOMPtr<nsIParserUtils> utils =
+ do_GetService(NS_PARSERUTILS_CONTRACTID);
+ return utils->ConvertToPlainText(aMessageText, flags, 80, aOutText);
+}
+
+nsresult nsMsgDBFolder::GetMsgPreviewTextFromStream(nsIMsgDBHdr *msgHdr, nsIInputStream *stream)
+{
+ nsCString msgBody;
+ nsAutoCString charset;
+ msgHdr->GetCharset(getter_Copies(charset));
+ nsAutoCString contentType;
+ nsresult rv = GetMsgTextFromStream(stream, charset, 4096, 255, true, true, contentType, msgBody);
+ // replaces all tabs and line returns with a space,
+ // then trims off leading and trailing white space
+ MsgCompressWhitespace(msgBody);
+ msgHdr->SetStringProperty("preview", msgBody.get());
+ return rv;
+}
+
+void nsMsgDBFolder::UpdateTimestamps(bool allowUndo)
+{
+ if (!(mFlags & (nsMsgFolderFlags::Trash|nsMsgFolderFlags::Junk)))
+ {
+ SetMRUTime();
+ if (allowUndo) // This is a proxy for a user-initiated act.
+ {
+ bool isArchive;
+ IsSpecialFolder(nsMsgFolderFlags::Archive, true, &isArchive);
+ if (!isArchive)
+ {
+ SetMRMTime();
+ nsCOMPtr<nsIAtom> MRMTimeChangedAtom = MsgGetAtom("MRMTimeChanged");
+ NotifyFolderEvent(MRMTimeChangedAtom);
+ }
+ }
+ }
+}
+
+void nsMsgDBFolder::SetMRUTime()
+{
+ uint32_t seconds;
+ PRTime2Seconds(PR_Now(), &seconds);
+ nsAutoCString nowStr;
+ nowStr.AppendInt(seconds);
+ SetStringProperty(MRU_TIME_PROPERTY, nowStr);
+}
+
+void nsMsgDBFolder::SetMRMTime()
+{
+ uint32_t seconds;
+ PRTime2Seconds(PR_Now(), &seconds);
+ nsAutoCString nowStr;
+ nowStr.AppendInt(seconds);
+ SetStringProperty(MRM_TIME_PROPERTY, nowStr);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords)
+{
+ NS_ENSURE_ARG(aMessages);
+ nsresult rv = NS_OK;
+ GetDatabase();
+ if (mDatabase)
+ {
+ uint32_t count;
+ nsresult rv = aMessages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString keywords;
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ message->GetStringProperty("keywords", getter_Copies(keywords));
+ nsTArray<nsCString> keywordArray;
+ ParseString(aKeywords, ' ', keywordArray);
+ uint32_t addCount = 0;
+ for (uint32_t j = 0; j < keywordArray.Length(); j++)
+ {
+ int32_t start, length;
+ if (!MsgFindKeyword(keywordArray[j], keywords, &start, &length))
+ {
+ if (!keywords.IsEmpty())
+ keywords.Append(' ');
+ keywords.Append(keywordArray[j]);
+ addCount++;
+ }
+ }
+ // avoid using the message key to set the string property, because
+ // in the case of filters running on incoming pop3 mail with quarantining
+ // turned on, the message key is wrong.
+ mDatabase->SetStringPropertyByHdr(message, "keywords", keywords.get());
+
+ if (addCount)
+ NotifyPropertyFlagChanged(message, kKeywords, 0, addCount);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords)
+{
+ NS_ENSURE_ARG(aMessages);
+ nsresult rv = NS_OK;
+ GetDatabase();
+ if (mDatabase)
+ {
+ uint32_t count;
+ nsresult rv = aMessages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsTArray<nsCString> keywordArray;
+ ParseString(aKeywords, ' ', keywordArray);
+ nsCString keywords;
+ // If the tag is also a label, we should remove the label too...
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = message->GetStringProperty("keywords", getter_Copies(keywords));
+ uint32_t removeCount = 0;
+ for (uint32_t j = 0; j < keywordArray.Length(); j++)
+ {
+ bool keywordIsLabel = (StringBeginsWith(keywordArray[j], NS_LITERAL_CSTRING("$label"))
+ && keywordArray[j].CharAt(6) >= '1' && keywordArray[j].CharAt(6) <= '5');
+ if (keywordIsLabel)
+ {
+ nsMsgLabelValue labelValue;
+ message->GetLabel(&labelValue);
+ // if we're removing the keyword that corresponds to a pre 2.0 label,
+ // we need to clear the old label attribute on the message.
+ if (labelValue == (nsMsgLabelValue) (keywordArray[j].CharAt(6) - '0'))
+ message->SetLabel((nsMsgLabelValue) 0);
+ }
+ int32_t startOffset, length;
+ if (MsgFindKeyword(keywordArray[j], keywords, &startOffset, &length))
+ {
+ // delete any leading space delimiters
+ while (startOffset && keywords.CharAt(startOffset - 1) == ' ')
+ {
+ startOffset--;
+ length++;
+ }
+ // but if the keyword is at the start then delete the following space
+ if (!startOffset && length < static_cast<int32_t>(keywords.Length()) &&
+ keywords.CharAt(length) == ' ')
+ length++;
+ keywords.Cut(startOffset, length);
+ removeCount++;
+ }
+ }
+
+ if (removeCount)
+ {
+ mDatabase->SetStringPropertyByHdr(message, "keywords", keywords.get());
+ NotifyPropertyFlagChanged(message, kKeywords, removeCount, 0);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetCustomIdentity(nsIMsgIdentity **aIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ *aIdentity = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetProcessingFlags(nsMsgKey aKey, uint32_t *aFlags)
+{
+ NS_ENSURE_ARG_POINTER(aFlags);
+ *aFlags = 0;
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ if (mProcessingFlag[i].keys && mProcessingFlag[i].keys->IsMember(aKey))
+ *aFlags |= mProcessingFlag[i].bit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OrProcessingFlags(nsMsgKey aKey, uint32_t mask)
+{
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ if (mProcessingFlag[i].bit & mask && mProcessingFlag[i].keys)
+ mProcessingFlag[i].keys->Add(aKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AndProcessingFlags(nsMsgKey aKey, uint32_t mask)
+{
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ if (!(mProcessingFlag[i].bit & mask) && mProcessingFlag[i].keys)
+ mProcessingFlag[i].keys->Remove(aKey);
+ return NS_OK;
+}
+
+// Each implementation must provide an override of this, connecting the folder
+// type to the corresponding incoming server type.
+NS_IMETHODIMP nsMsgDBFolder::GetIncomingServerType(nsACString& aIncomingServerType)
+{
+ NS_ASSERTION(false, "subclasses need to override this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsMsgDBFolder::ClearProcessingFlags()
+{
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ {
+ // There is no clear method so we need to delete and re-create.
+ delete mProcessingFlag[i].keys;
+ mProcessingFlag[i].keys = nsMsgKeySetU::Create();
+ }
+}
+
+nsresult nsMsgDBFolder::MessagesInKeyOrder(nsTArray<nsMsgKey> &aKeyArray,
+ nsIMsgFolder *srcFolder, nsIMutableArray* messages)
+{
+ // XXX: the output messages - should really be and nsCOMArray<nsIMsgDBHdr>
+
+ nsresult rv = NS_OK;
+ uint32_t numMessages = aKeyArray.Length();
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db)
+ {
+ for (uint32_t i = 0; i < numMessages; i++)
+ {
+ rv = db->GetMsgHdrForKey(aKeyArray[i], getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (msgHdr)
+ messages->AppendElement(msgHdr, false);
+ }
+ }
+ return rv;
+}
+
+/* static */ nsMsgKeySetU* nsMsgKeySetU::Create()
+{
+ nsMsgKeySetU* set = new nsMsgKeySetU;
+ if (set)
+ {
+ set->loKeySet = nsMsgKeySet::Create();
+ set->hiKeySet = nsMsgKeySet::Create();
+ if (!(set->loKeySet && set->hiKeySet))
+ {
+ delete set;
+ set = nullptr;
+ }
+ }
+ return set;
+}
+
+nsMsgKeySetU::nsMsgKeySetU()
+{ }
+
+nsMsgKeySetU::~nsMsgKeySetU()
+{
+ delete loKeySet;
+ delete hiKeySet;
+}
+
+const uint32_t kLowerBits = 0x7fffffff;
+
+int nsMsgKeySetU::Add(nsMsgKey aKey)
+{
+ int32_t intKey = static_cast<int32_t>(aKey);
+ if (intKey >= 0)
+ return loKeySet->Add(intKey);
+ return hiKeySet->Add(intKey & kLowerBits);
+}
+
+int nsMsgKeySetU::Remove(nsMsgKey aKey)
+{
+ int32_t intKey = static_cast<int32_t>(aKey);
+ if (intKey >= 0)
+ return loKeySet->Remove(intKey);
+ return hiKeySet->Remove(intKey & kLowerBits);
+}
+
+bool nsMsgKeySetU::IsMember(nsMsgKey aKey)
+{
+ int32_t intKey = static_cast<int32_t>(aKey);
+ if (intKey >= 0)
+ return loKeySet->IsMember(intKey);
+ return hiKeySet->IsMember(intKey & kLowerBits);
+}
+
+nsresult nsMsgKeySetU::ToMsgKeyArray(nsTArray<nsMsgKey> &aArray)
+{
+ nsresult rv = loKeySet->ToMsgKeyArray(aArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return hiKeySet->ToMsgKeyArray(aArray);
+}
+
diff --git a/mailnews/base/util/nsMsgDBFolder.h b/mailnews/base/util/nsMsgDBFolder.h
new file mode 100644
index 000000000..0c96ee8f0
--- /dev/null
+++ b/mailnews/base/util/nsMsgDBFolder.h
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgDBFolder_h__
+#define nsMsgDBFolder_h__
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsIMsgFolder.h"
+#include "nsRDFResource.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsCOMPtr.h"
+#include "nsStaticAtom.h"
+#include "nsIDBChangeListener.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIURL.h"
+#include "nsIFile.h"
+#include "nsWeakReference.h"
+#include "nsIMsgFilterList.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgHdr.h"
+#include "nsIOutputStream.h"
+#include "nsITransport.h"
+#include "nsIStringBundle.h"
+#include "nsTObserverArray.h"
+#include "nsCOMArray.h"
+#include "nsMsgKeySet.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFilterPlugin.h"
+class nsIMsgFolderCacheElement;
+class nsICollation;
+class nsMsgKeySetU;
+
+ /*
+ * nsMsgDBFolder
+ * class derived from nsMsgFolder for those folders that use an nsIMsgDatabase
+ */
+
+class NS_MSG_BASE nsMsgDBFolder: public nsRDFResource,
+ public nsSupportsWeakReference,
+ public nsIMsgFolder,
+ public nsIDBChangeListener,
+ public nsIUrlListener,
+ public nsIJunkMailClassificationListener,
+ public nsIMsgTraitClassificationListener
+{
+public:
+ nsMsgDBFolder(void);
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMSGFOLDER
+ NS_DECL_NSIDBCHANGELISTENER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER
+ NS_DECL_NSIMSGTRAITCLASSIFICATIONLISTENER
+
+ NS_IMETHOD WriteToFolderCacheElem(nsIMsgFolderCacheElement *element);
+ NS_IMETHOD ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element);
+
+ // nsRDFResource overrides
+ NS_IMETHOD Init(const char* aURI) override;
+
+ nsresult CreateDirectoryForFolder(nsIFile **result);
+ nsresult CreateBackupDirectory(nsIFile **result);
+ nsresult GetBackupSummaryFile(nsIFile **result, const nsACString& newName);
+ nsresult GetMsgPreviewTextFromStream(nsIMsgDBHdr *msgHdr, nsIInputStream *stream);
+ nsresult HandleAutoCompactEvent(nsIMsgWindow *aMsgWindow);
+protected:
+ virtual ~nsMsgDBFolder();
+
+ virtual nsresult CreateBaseMessageURI(const nsACString& aURI);
+
+ void compressQuotesInMsgSnippet(const nsString& aMessageText, nsAString& aCompressedQuotesStr);
+ void decodeMsgSnippet(const nsACString& aEncodingType, bool aIsComplete, nsCString& aMsgSnippet);
+
+ // helper routine to parse the URI and update member variables
+ nsresult parseURI(bool needServer=false);
+ nsresult GetBaseStringBundle(nsIStringBundle **aBundle);
+ nsresult GetStringFromBundle(const char* msgName, nsString& aResult);
+ nsresult ThrowConfirmationPrompt(nsIMsgWindow *msgWindow, const nsAString& confirmString, bool *confirmed);
+ nsresult GetWarnFilterChanged(bool *aVal);
+ nsresult SetWarnFilterChanged(bool aVal);
+ nsresult CreateCollationKey(const nsString &aSource, uint8_t **aKey, uint32_t *aLength);
+
+protected:
+ // all children will override this to create the right class of object.
+ virtual nsresult CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder) = 0;
+ virtual nsresult ReadDBFolderInfo(bool force);
+ virtual nsresult FlushToFolderCache();
+ virtual nsresult GetDatabase() = 0;
+ virtual nsresult SendFlagNotifications(nsIMsgDBHdr *item, uint32_t oldFlags, uint32_t newFlags);
+ nsresult CheckWithNewMessagesStatus(bool messageAdded);
+ void UpdateNewMessages();
+ nsresult OnHdrAddedOrDeleted(nsIMsgDBHdr *hdrChanged, bool added);
+ nsresult CreateFileForDB(const nsAString& userLeafName, nsIFile *baseDir,
+ nsIFile **dbFile);
+
+ nsresult GetFolderCacheKey(nsIFile **aFile, bool createDBIfMissing = false);
+ nsresult GetFolderCacheElemFromFile(nsIFile *file, nsIMsgFolderCacheElement **cacheElement);
+ nsresult AddDirectorySeparator(nsIFile *path);
+ nsresult CheckIfFolderExists(const nsAString& newFolderName, nsIMsgFolder *parentFolder, nsIMsgWindow *msgWindow);
+ bool ConfirmAutoFolderRename(nsIMsgWindow *aMsgWindow,
+ const nsString& aOldName,
+ const nsString& aNewName);
+
+ // Returns true if: a) there is no need to prompt or b) the user is already
+ // logged in or c) the user logged in successfully.
+ static bool PromptForMasterPasswordIfNecessary();
+
+ // offline support methods.
+ nsresult StartNewOfflineMessage();
+ nsresult WriteStartOfNewLocalMessage();
+ nsresult EndNewOfflineMessage();
+ nsresult CompactOfflineStore(nsIMsgWindow *inWindow, nsIUrlListener *aUrlListener);
+ nsresult AutoCompact(nsIMsgWindow *aWindow);
+ // this is a helper routine that ignores whether nsMsgMessageFlags::Offline is set for the folder
+ nsresult MsgFitsDownloadCriteria(nsMsgKey msgKey, bool *result);
+ nsresult GetPromptPurgeThreshold(bool *aPrompt);
+ nsresult GetPurgeThreshold(int32_t *aThreshold);
+ nsresult ApplyRetentionSettings(bool deleteViaFolder);
+ bool VerifyOfflineMessage(nsIMsgDBHdr *msgHdr, nsIInputStream *fileStream);
+ nsresult AddMarkAllReadUndoAction(nsIMsgWindow *msgWindow,
+ nsMsgKey *thoseMarked, uint32_t numMarked);
+
+ nsresult PerformBiffNotifications(void); // if there are new, non spam messages, do biff
+ nsresult CloseDBIfFolderNotOpen();
+
+ virtual nsresult SpamFilterClassifyMessage(const char *aURI, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin);
+ virtual nsresult SpamFilterClassifyMessages(const char **aURIArray, uint32_t aURICount, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin);
+ // Helper function for Move code to call to update the MRU and MRM time.
+ void UpdateTimestamps(bool allowUndo);
+ void SetMRUTime();
+ void SetMRMTime();
+ /**
+ * Clear all processing flags, presumably because message keys are no longer
+ * valid.
+ */
+ void ClearProcessingFlags();
+
+ nsresult NotifyHdrsNotBeingClassified();
+
+ /**
+ * Produce an array of messages ordered like the input keys.
+ */
+ nsresult MessagesInKeyOrder(nsTArray<nsMsgKey> &aKeyArray,
+ nsIMsgFolder *srcFolder,
+ nsIMutableArray* messages);
+
+protected:
+ nsCOMPtr<nsIMsgDatabase> mDatabase;
+ nsCOMPtr<nsIMsgDatabase> mBackupDatabase;
+ nsCString mCharset;
+ bool mCharsetOverride;
+ bool mAddListener;
+ bool mNewMessages;
+ bool mGettingNewMessages;
+ nsMsgKey mLastMessageLoaded;
+
+ nsCOMPtr <nsIMsgDBHdr> m_offlineHeader;
+ int32_t m_numOfflineMsgLines;
+ int32_t m_bytesAddedToLocalMsg;
+ // this is currently used when we do a save as of an imap or news message..
+ nsCOMPtr<nsIOutputStream> m_tempMessageStream;
+
+ nsCOMPtr <nsIMsgRetentionSettings> m_retentionSettings;
+ nsCOMPtr <nsIMsgDownloadSettings> m_downloadSettings;
+ static NS_MSG_BASE_STATIC_MEMBER_(nsrefcnt) mInstanceCount;
+
+protected:
+ uint32_t mFlags;
+ nsWeakPtr mParent; //This won't be refcounted for ownership reasons.
+ int32_t mNumUnreadMessages; /* count of unread messages (-1 means unknown; -2 means unknown but we already tried to find out.) */
+ int32_t mNumTotalMessages; /* count of existing messages. */
+ bool mNotifyCountChanges;
+ int64_t mExpungedBytes;
+ nsCOMArray<nsIMsgFolder> mSubFolders;
+ // This can't be refcounted due to ownsership issues
+ nsTObserverArray<nsIFolderListener*> mListeners;
+
+ bool mInitializedFromCache;
+ nsISupports *mSemaphoreHolder; // set when the folder is being written to
+ //Due to ownership issues, this won't be AddRef'd.
+
+ nsWeakPtr mServer;
+
+ // These values are used for tricking the front end into thinking that we have more
+ // messages than are really in the DB. This is usually after and IMAP message copy where
+ // we don't want to do an expensive select until the user actually opens that folder
+ int32_t mNumPendingUnreadMessages;
+ int32_t mNumPendingTotalMessages;
+ int64_t mFolderSize;
+
+ int32_t mNumNewBiffMessages;
+
+ // these are previous set of new msgs, which we might
+ // want to run junk controls on. This is in addition to "new" hdrs
+ // in the db, which might get cleared because the user clicked away
+ // from the folder.
+ nsTArray<nsMsgKey> m_saveNewMsgs;
+
+ // These are the set of new messages for a folder who has had
+ // its db closed, without the user reading the folder. This
+ // happens with pop3 mail filtered to a different local folder.
+ nsTArray<nsMsgKey> m_newMsgs;
+
+ //
+ // stuff from the uri
+ //
+ bool mHaveParsedURI; // is the URI completely parsed?
+ bool mIsServerIsValid;
+ bool mIsServer;
+ nsString mName;
+ nsCOMPtr<nsIFile> mPath;
+ nsCString mBaseMessageURI; //The uri with the message scheme
+
+ bool mInVFEditSearchScope ; // non persistant state used by the virtual folder UI
+
+ // static stuff for cross-instance objects like atoms
+ static NS_MSG_BASE_STATIC_MEMBER_(nsrefcnt) gInstanceCount;
+
+ static nsresult initializeStrings();
+ static nsresult createCollationKeyGenerator();
+
+ static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedInboxName;
+ static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedTrashName;
+ static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedSentName;
+ static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedDraftsName;
+ static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedTemplatesName;
+ static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedUnsentName;
+ static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedJunkName;
+ static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedArchivesName;
+
+ static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedBrandShortName;
+
+#define MSGDBFOLDER_ATOM(name_, value) static NS_MSG_BASE_STATIC_MEMBER_(nsIAtom*) name_;
+#include "nsMsgDBFolderAtomList.h"
+#undef MSGDBFOLDER_ATOM
+
+ static NS_MSG_BASE_STATIC_MEMBER_(nsICollation*) gCollationKeyGenerator;
+
+ // store of keys that have a processing flag set
+ struct
+ {
+ uint32_t bit;
+ nsMsgKeySetU* keys;
+ } mProcessingFlag[nsMsgProcessingFlags::NumberOfFlags];
+
+ // list of nsIMsgDBHdrs for messages to process post-bayes
+ nsCOMPtr<nsIMutableArray> mPostBayesMessagesToFilter;
+
+ /**
+ * The list of message keys that have been classified for msgsClassified
+ * batch notification purposes. We add to this list in OnMessageClassified
+ * when we are told about a classified message (a URI is provided), and we
+ * notify for the list and clear it when we are told all the messages in
+ * the batch were classified (a URI is not provided).
+ */
+ nsTArray<nsMsgKey> mClassifiedMsgKeys;
+ // Is the current bayes filtering doing junk classification?
+ bool mBayesJunkClassifying;
+ // Is the current bayes filtering doing trait classification?
+ bool mBayesTraitClassifying;
+};
+
+// This class is a kludge to allow nsMsgKeySet to be used with uint32_t keys
+class nsMsgKeySetU
+{
+public:
+ // Creates an empty set.
+ static nsMsgKeySetU* Create();
+ ~nsMsgKeySetU();
+ // IsMember() returns whether the given key is a member of this set.
+ bool IsMember(nsMsgKey key);
+ // Add() adds the given key to the set. (Returns 1 if a change was
+ // made, 0 if it was already there, and negative on error.)
+ int Add(nsMsgKey key);
+ // Remove() removes the given article from the set.
+ int Remove(nsMsgKey key);
+ // Add the keys in the set to aArray.
+ nsresult ToMsgKeyArray(nsTArray<nsMsgKey> &aArray);
+
+protected:
+ nsMsgKeySetU();
+ nsMsgKeySet* loKeySet;
+ nsMsgKeySet* hiKeySet;
+};
+
+#endif
diff --git a/mailnews/base/util/nsMsgDBFolderAtomList.h b/mailnews/base/util/nsMsgDBFolderAtomList.h
new file mode 100644
index 000000000..6ec8c0691
--- /dev/null
+++ b/mailnews/base/util/nsMsgDBFolderAtomList.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+MSGDBFOLDER_ATOM(kTotalUnreadMessagesAtom, "TotalUnreadMessages")
+MSGDBFOLDER_ATOM(kBiffStateAtom, "BiffState")
+MSGDBFOLDER_ATOM(kNewMailReceivedAtom, "NewMailReceived")
+MSGDBFOLDER_ATOM(kNewMessagesAtom, "NewMessages")
+MSGDBFOLDER_ATOM(kInVFEditSearchScopeAtom, "inVFEditSearchScope")
+MSGDBFOLDER_ATOM(kNumNewBiffMessagesAtom, "NumNewBiffMessages")
+MSGDBFOLDER_ATOM(kTotalMessagesAtom, "TotalMessages")
+MSGDBFOLDER_ATOM(kFolderSizeAtom, "FolderSize")
+MSGDBFOLDER_ATOM(kStatusAtom, "Status")
+MSGDBFOLDER_ATOM(kFlaggedAtom, "Flagged")
+MSGDBFOLDER_ATOM(kNameAtom, "Name")
+MSGDBFOLDER_ATOM(kSynchronizeAtom, "Synchronize")
+MSGDBFOLDER_ATOM(kOpenAtom, "open")
+MSGDBFOLDER_ATOM(kIsDeferred, "isDeferred")
+MSGDBFOLDER_ATOM(kKeywords, "Keywords")
+MSGDBFOLDER_ATOM(mFolderLoadedAtom, "FolderLoaded")
+MSGDBFOLDER_ATOM(mDeleteOrMoveMsgCompletedAtom, "DeleteOrMoveMsgCompleted")
+MSGDBFOLDER_ATOM(mDeleteOrMoveMsgFailedAtom, "DeleteOrMoveMsgFailed")
+MSGDBFOLDER_ATOM(mJunkStatusChangedAtom, "JunkStatusChanged")
+MSGDBFOLDER_ATOM(mFiltersAppliedAtom, "FiltersApplied")
+MSGDBFOLDER_ATOM(mFolderFlagAtom, "FolderFlag")
diff --git a/mailnews/base/util/nsMsgFileStream.cpp b/mailnews/base/util/nsMsgFileStream.cpp
new file mode 100644
index 000000000..87d11ab3d
--- /dev/null
+++ b/mailnews/base/util/nsMsgFileStream.cpp
@@ -0,0 +1,196 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFile.h"
+#include "nsMsgFileStream.h"
+#include "prerr.h"
+#include "prerror.h"
+
+/* From nsDebugImpl.cpp: */
+static nsresult
+ErrorAccordingToNSPR()
+{
+ PRErrorCode err = PR_GetError();
+ switch (err) {
+ case PR_OUT_OF_MEMORY_ERROR: return NS_ERROR_OUT_OF_MEMORY;
+ case PR_WOULD_BLOCK_ERROR: return NS_BASE_STREAM_WOULD_BLOCK;
+ case PR_FILE_NOT_FOUND_ERROR: return NS_ERROR_FILE_NOT_FOUND;
+ case PR_READ_ONLY_FILESYSTEM_ERROR: return NS_ERROR_FILE_READ_ONLY;
+ case PR_NOT_DIRECTORY_ERROR: return NS_ERROR_FILE_NOT_DIRECTORY;
+ case PR_IS_DIRECTORY_ERROR: return NS_ERROR_FILE_IS_DIRECTORY;
+ case PR_LOOP_ERROR: return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+ case PR_FILE_EXISTS_ERROR: return NS_ERROR_FILE_ALREADY_EXISTS;
+ case PR_FILE_IS_LOCKED_ERROR: return NS_ERROR_FILE_IS_LOCKED;
+ case PR_FILE_TOO_BIG_ERROR: return NS_ERROR_FILE_TOO_BIG;
+ case PR_NO_DEVICE_SPACE_ERROR: return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ case PR_NAME_TOO_LONG_ERROR: return NS_ERROR_FILE_NAME_TOO_LONG;
+ case PR_DIRECTORY_NOT_EMPTY_ERROR: return NS_ERROR_FILE_DIR_NOT_EMPTY;
+ case PR_NO_ACCESS_RIGHTS_ERROR: return NS_ERROR_FILE_ACCESS_DENIED;
+ default: return NS_ERROR_FAILURE;
+ }
+}
+
+nsMsgFileStream::nsMsgFileStream()
+{
+ mFileDesc = nullptr;
+ mSeekedToEnd = false;
+}
+
+nsMsgFileStream::~nsMsgFileStream()
+{
+ if (mFileDesc)
+ PR_Close(mFileDesc);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgFileStream, nsIInputStream, nsIOutputStream, nsISeekableStream)
+
+nsresult nsMsgFileStream::InitWithFile(nsIFile *file)
+{
+ return file->OpenNSPRFileDesc(PR_RDWR|PR_CREATE_FILE, 0664, &mFileDesc);
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::Seek(int32_t whence, int64_t offset)
+{
+ if (mFileDesc == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ bool seekingToEnd = whence == PR_SEEK_END && offset == 0;
+ if (seekingToEnd && mSeekedToEnd)
+ return NS_OK;
+
+ int64_t cnt = PR_Seek64(mFileDesc, offset, (PRSeekWhence)whence);
+ if (cnt == int64_t(-1)) {
+ return ErrorAccordingToNSPR();
+ }
+
+ mSeekedToEnd = seekingToEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::Tell(int64_t *result)
+{
+ if (mFileDesc == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int64_t cnt = PR_Seek64(mFileDesc, 0, PR_SEEK_CUR);
+ if (cnt == int64_t(-1)) {
+ return ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::SetEOF()
+{
+ if (mFileDesc == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void close (); */
+NS_IMETHODIMP nsMsgFileStream::Close()
+{
+ nsresult rv = NS_OK;
+ if (mFileDesc && (PR_Close(mFileDesc) == PR_FAILURE))
+ rv = NS_BASE_STREAM_OSERROR;
+ mFileDesc = nullptr;
+ return rv;
+}
+
+/* unsigned long long available (); */
+NS_IMETHODIMP nsMsgFileStream::Available(uint64_t *aResult)
+{
+ if (!mFileDesc)
+ return NS_BASE_STREAM_CLOSED;
+
+ int64_t avail = PR_Available64(mFileDesc);
+ if (avail == -1)
+ return ErrorAccordingToNSPR();
+
+ *aResult = avail;
+ return NS_OK;
+}
+
+/* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */
+NS_IMETHODIMP nsMsgFileStream::Read(char * aBuf, uint32_t aCount, uint32_t *aResult)
+{
+ if (!mFileDesc)
+ {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ int32_t bytesRead = PR_Read(mFileDesc, aBuf, aCount);
+ if (bytesRead == -1)
+ return ErrorAccordingToNSPR();
+
+ *aResult = bytesRead;
+ return NS_OK;
+}
+
+/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */
+NS_IMETHODIMP nsMsgFileStream::ReadSegments(nsWriteSegmentFun aWriter, void * aClosure, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* boolean isNonBlocking (); */
+NS_IMETHODIMP nsMsgFileStream::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::Write(const char *buf, uint32_t count, uint32_t *result)
+{
+ if (mFileDesc == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int32_t cnt = PR_Write(mFileDesc, buf, count);
+ if (cnt == -1) {
+ return ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::Flush(void)
+{
+ if (mFileDesc == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int32_t cnt = PR_Sync(mFileDesc);
+ if (cnt == -1)
+ return ErrorAccordingToNSPR();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval)
+{
+ NS_NOTREACHED("WriteFrom (see source comment)");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval)
+{
+ NS_NOTREACHED("WriteSegments (see source comment)");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+
+
diff --git a/mailnews/base/util/nsMsgFileStream.h b/mailnews/base/util/nsMsgFileStream.h
new file mode 100644
index 000000000..7c2b0cefb
--- /dev/null
+++ b/mailnews/base/util/nsMsgFileStream.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISeekableStream.h"
+#include "prio.h"
+
+class nsMsgFileStream final : public nsIInputStream,
+ public nsIOutputStream,
+ public nsISeekableStream
+{
+public:
+ nsMsgFileStream();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Available(uint64_t *_retval) override;
+ NS_IMETHOD Read(char * aBuf, uint32_t aCount, uint32_t *_retval) override;
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void * aClosure, uint32_t aCount, uint32_t *_retval) override;
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+
+ nsresult InitWithFile(nsIFile *localFile);
+protected:
+ ~nsMsgFileStream();
+
+ PRFileDesc *mFileDesc;
+ bool mSeekedToEnd;
+};
diff --git a/mailnews/base/util/nsMsgI18N.cpp b/mailnews/base/util/nsMsgI18N.cpp
new file mode 100644
index 000000000..b79a4c196
--- /dev/null
+++ b/mailnews/base/util/nsMsgI18N.cpp
@@ -0,0 +1,479 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// as does this
+#include "nsICharsetConverterManager.h"
+#include "nsIPlatformCharset.h"
+#include "nsIServiceManager.h"
+
+#include "nsISupports.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgUtils.h"
+#include "nsMsgI18N.h"
+#include "nsMsgMimeCID.h"
+#include "nsILineInputStream.h"
+#include "nsMimeTypes.h"
+#include "nsISaveAsCharset.h"
+#include "nsStringGlue.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "nsUTF8Utils.h"
+#include "nsNetUtil.h"
+#include "nsCRTGlue.h"
+#include "nsComponentManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIFileStreams.h"
+//
+// International functions necessary for composition
+//
+
+nsresult nsMsgI18NConvertFromUnicode(const char* aCharset,
+ const nsString& inString,
+ nsACString& outString,
+ bool aIsCharsetCanonical,
+ bool aReportUencNoMapping)
+{
+ if (inString.IsEmpty()) {
+ outString.Truncate();
+ return NS_OK;
+ }
+ // Note: This will hide a possible error if the Unicode contains more than one
+ // charset, e.g. Latin1 + Japanese.
+ else if (!aReportUencNoMapping && (!*aCharset ||
+ !PL_strcasecmp(aCharset, "us-ascii") ||
+ !PL_strcasecmp(aCharset, "ISO-8859-1"))) {
+ LossyCopyUTF16toASCII(inString, outString);
+ return NS_OK;
+ }
+ else if (!PL_strcasecmp(aCharset, "UTF-8")) {
+ CopyUTF16toUTF8(inString, outString);
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr <nsICharsetConverterManager> ccm = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIUnicodeEncoder> encoder;
+
+ // get an unicode converter
+ if (aIsCharsetCanonical) // optimize for modified UTF-7 used by IMAP
+ rv = ccm->GetUnicodeEncoderRaw(aCharset, getter_AddRefs(encoder));
+ else
+ rv = ccm->GetUnicodeEncoder(aCharset, getter_AddRefs(encoder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Must set behavior to kOnError_Signal if we want to receive the
+ // NS_ERROR_UENC_NOMAPPING signal, should it occur.
+ int32_t behavior = aReportUencNoMapping ? nsIUnicodeEncoder::kOnError_Signal:
+ nsIUnicodeEncoder::kOnError_Replace;
+ rv = encoder->SetOutputErrorBehavior(behavior, nullptr, '?');
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char16_t *originalSrcPtr = inString.get();
+ const char16_t *currentSrcPtr = originalSrcPtr;
+ int32_t originalUnicharLength = inString.Length();
+ int32_t srcLength;
+ int32_t dstLength;
+ char localbuf[512+10]; // We have seen cases were the buffer was overrun
+ // by two (!!) bytes (Bug 1255863).
+ // So give it ten bytes more for now to avoid a crash.
+ int32_t consumedLen = 0;
+
+ bool mappingFailure = false;
+ outString.Truncate();
+ // convert
+ while (consumedLen < originalUnicharLength) {
+ srcLength = originalUnicharLength - consumedLen;
+ dstLength = 512;
+ rv = encoder->Convert(currentSrcPtr, &srcLength, localbuf, &dstLength);
+#ifdef DEBUG
+ if (dstLength > 512) {
+ char warning[100];
+ sprintf(warning, "encoder->Convert() returned %d bytes. Limit = 512", dstLength);
+ NS_WARNING(warning);
+ }
+#endif
+ if (rv == NS_ERROR_UENC_NOMAPPING) {
+ mappingFailure = true;
+ }
+ if (NS_FAILED(rv) || dstLength == 0)
+ break;
+ outString.Append(localbuf, dstLength);
+
+ currentSrcPtr += srcLength;
+ consumedLen = currentSrcPtr - originalSrcPtr; // src length used so far
+ }
+ dstLength = 512; // Reset available buffer size.
+ rv = encoder->Finish(localbuf, &dstLength);
+ if (NS_SUCCEEDED(rv)) {
+ if (dstLength)
+ outString.Append(localbuf, dstLength);
+ return !mappingFailure ? rv: NS_ERROR_UENC_NOMAPPING;
+ }
+ return rv;
+}
+
+nsresult nsMsgI18NConvertToUnicode(const char* aCharset,
+ const nsCString& inString,
+ nsAString& outString,
+ bool aIsCharsetCanonical)
+{
+ if (inString.IsEmpty()) {
+ outString.Truncate();
+ return NS_OK;
+ }
+ else if (!*aCharset || !PL_strcasecmp(aCharset, "us-ascii") ||
+ !PL_strcasecmp(aCharset, "ISO-8859-1")) {
+ // Despite its name, it also works for Latin-1.
+ CopyASCIItoUTF16(inString, outString);
+ return NS_OK;
+ }
+ else if (!PL_strcasecmp(aCharset, "UTF-8")) {
+ if (MsgIsUTF8(inString)) {
+ nsAutoString tmp;
+ CopyUTF8toUTF16(inString, tmp);
+ if (!tmp.IsEmpty() && tmp.First() == char16_t(0xFEFF))
+ tmp.Cut(0, 1);
+ outString.Assign(tmp);
+ return NS_OK;
+ }
+ NS_WARNING("Invalid UTF-8 string");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ nsCOMPtr <nsICharsetConverterManager> ccm = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIUnicodeDecoder> decoder;
+
+ // get an unicode converter
+ if (aIsCharsetCanonical) // optimize for modified UTF-7 used by IMAP
+ rv = ccm->GetUnicodeDecoderRaw(aCharset, getter_AddRefs(decoder));
+ else
+ rv = ccm->GetUnicodeDecoderInternal(aCharset, getter_AddRefs(decoder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char *originalSrcPtr = inString.get();
+ const char *currentSrcPtr = originalSrcPtr;
+ int32_t originalLength = inString.Length();
+ int32_t srcLength;
+ int32_t dstLength;
+ char16_t localbuf[512];
+ int32_t consumedLen = 0;
+
+ outString.Truncate();
+
+ // convert
+ while (consumedLen < originalLength) {
+ srcLength = originalLength - consumedLen;
+ dstLength = 512;
+ rv = decoder->Convert(currentSrcPtr, &srcLength, localbuf, &dstLength);
+ if (NS_FAILED(rv) || dstLength == 0)
+ break;
+ outString.Append(localbuf, dstLength);
+
+ currentSrcPtr += srcLength;
+ consumedLen = currentSrcPtr - originalSrcPtr; // src length used so far
+ }
+ return rv;
+}
+
+// Charset used by the file system.
+const char * nsMsgI18NFileSystemCharset()
+{
+ /* Get a charset used for the file. */
+ static nsAutoCString fileSystemCharset;
+
+ if (fileSystemCharset.IsEmpty())
+ {
+ nsresult rv;
+ nsCOMPtr <nsIPlatformCharset> platformCharset = do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = platformCharset->GetCharset(kPlatformCharsetSel_FileName,
+ fileSystemCharset);
+ }
+
+ if (NS_FAILED(rv))
+ fileSystemCharset.Assign("ISO-8859-1");
+ }
+ return fileSystemCharset.get();
+}
+
+// Charset used by the text file.
+void nsMsgI18NTextFileCharset(nsACString& aCharset)
+{
+ nsresult rv;
+ nsCOMPtr <nsIPlatformCharset> platformCharset =
+ do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = platformCharset->GetCharset(kPlatformCharsetSel_PlainTextInFile,
+ aCharset);
+ }
+
+ if (NS_FAILED(rv))
+ aCharset.Assign("ISO-8859-1");
+}
+
+// MIME encoder, output string should be freed by PR_FREE
+// XXX : fix callers later to avoid allocation and copy
+char * nsMsgI18NEncodeMimePartIIStr(const char *header, bool structured, const char *charset, int32_t fieldnamelen, bool usemime)
+{
+ // No MIME, convert to the outgoing mail charset.
+ if (false == usemime) {
+ nsAutoCString convertedStr;
+ if (NS_SUCCEEDED(ConvertFromUnicode(charset, NS_ConvertUTF8toUTF16(header),
+ convertedStr)))
+ return PL_strdup(convertedStr.get());
+ else
+ return PL_strdup(header);
+ }
+
+ nsAutoCString encodedString;
+ nsresult res;
+ nsCOMPtr<nsIMimeConverter> converter = do_GetService(NS_MIME_CONVERTER_CONTRACTID, &res);
+ if (NS_SUCCEEDED(res) && nullptr != converter)
+ res = converter->EncodeMimePartIIStr_UTF8(nsDependentCString(header),
+ structured, "UTF-8", fieldnamelen,
+ nsIMimeConverter::MIME_ENCODED_WORD_SIZE, encodedString);
+
+ return NS_SUCCEEDED(res) ? PL_strdup(encodedString.get()) : nullptr;
+}
+
+// Return True if a charset is stateful (e.g. JIS).
+bool nsMsgI18Nstateful_charset(const char *charset)
+{
+ //TODO: use charset manager's service
+ return (PL_strcasecmp(charset, "ISO-2022-JP") == 0);
+}
+
+bool nsMsgI18Nmultibyte_charset(const char *charset)
+{
+ nsresult res;
+ nsCOMPtr <nsICharsetConverterManager> ccm = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res);
+ bool result = false;
+
+ if (NS_SUCCEEDED(res)) {
+ nsAutoString charsetData;
+ res = ccm->GetCharsetData(charset, u".isMultibyte", charsetData);
+ if (NS_SUCCEEDED(res)) {
+ result = charsetData.LowerCaseEqualsLiteral("true");
+ }
+ }
+
+ return result;
+}
+
+bool nsMsgI18Ncheck_data_in_charset_range(const char *charset, const char16_t* inString, char **fallbackCharset)
+{
+ if (!charset || !*charset || !inString || !*inString)
+ return true;
+
+ nsresult res;
+ bool result = true;
+
+ nsCOMPtr <nsICharsetConverterManager> ccm = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res);
+
+ if (NS_SUCCEEDED(res)) {
+ nsCOMPtr <nsIUnicodeEncoder> encoder;
+
+ // get an unicode converter
+ res = ccm->GetUnicodeEncoderRaw(charset, getter_AddRefs(encoder));
+ if(NS_SUCCEEDED(res)) {
+ const char16_t *originalPtr = inString;
+ int32_t originalLen = NS_strlen(inString);
+ const char16_t *currentSrcPtr = originalPtr;
+ char localBuff[512];
+ int32_t consumedLen = 0;
+ int32_t srcLen;
+ int32_t dstLength;
+
+ // convert from unicode
+ while (consumedLen < originalLen) {
+ srcLen = originalLen - consumedLen;
+ dstLength = 512;
+ res = encoder->Convert(currentSrcPtr, &srcLen, localBuff, &dstLength);
+ if (NS_ERROR_UENC_NOMAPPING == res) {
+ result = false;
+ break;
+ }
+ else if (NS_FAILED(res) || (0 == dstLength))
+ break;
+
+ currentSrcPtr += srcLen;
+ consumedLen = currentSrcPtr - originalPtr; // src length used so far
+ }
+ }
+ }
+
+ // if the conversion was not successful then try fallback to other charsets
+ if (!result && fallbackCharset) {
+ nsCString convertedString;
+ res = nsMsgI18NConvertFromUnicode(*fallbackCharset,
+ nsDependentString(inString), convertedString, false, true);
+ result = (NS_SUCCEEDED(res) && NS_ERROR_UENC_NOMAPPING != res);
+ }
+
+ return result;
+}
+
+// Simple parser to parse META charset.
+// It only supports the case when the description is within one line.
+const char *
+nsMsgI18NParseMetaCharset(nsIFile* file)
+{
+ static char charset[nsIMimeConverter::MAX_CHARSET_NAME_LENGTH+1];
+
+ *charset = '\0';
+
+ bool isDirectory = false;
+ file->IsDirectory(&isDirectory);
+ if (isDirectory) {
+ NS_ERROR("file is a directory");
+ return charset;
+ }
+
+ nsresult rv;
+ nsCOMPtr <nsIFileInputStream> fileStream = do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, charset);
+
+ rv = fileStream->Init(file, PR_RDONLY, 0664, false);
+ nsCOMPtr <nsILineInputStream> lineStream = do_QueryInterface(fileStream, &rv);
+
+ nsCString curLine;
+ bool more = true;
+ while (NS_SUCCEEDED(rv) && more) {
+ rv = lineStream->ReadLine(curLine, &more);
+ if (curLine.IsEmpty())
+ continue;
+
+ ToUpperCase(curLine);
+
+ if (curLine.Find("/HEAD") != -1)
+ break;
+
+ if (curLine.Find("META") != -1 &&
+ curLine.Find("HTTP-EQUIV") != -1 &&
+ curLine.Find("CONTENT-TYPE") != -1 &&
+ curLine.Find("CHARSET") != -1) {
+ char *cp = (char *) PL_strchr(PL_strstr(curLine.get(), "CHARSET"), '=');
+ char *token = nullptr;
+ if (cp)
+ {
+ char *newStr = cp + 1;
+ token = NS_strtok(" \"\'", &newStr);
+ }
+ if (token) {
+ PL_strncpy(charset, token, sizeof(charset));
+ charset[sizeof(charset)-1] = '\0';
+
+ // this function cannot parse a file if it is really
+ // encoded by one of the following charsets
+ // so we can say that the charset label must be incorrect for
+ // the .html if we actually see those charsets parsed
+ // and we should ignore them
+ if (!PL_strncasecmp("UTF-16", charset, sizeof("UTF-16")-1) ||
+ !PL_strncasecmp("UTF-32", charset, sizeof("UTF-32")-1))
+ charset[0] = '\0';
+
+ break;
+ }
+ }
+ }
+
+ return charset;
+}
+
+nsresult nsMsgI18NShrinkUTF8Str(const nsCString &inString,
+ uint32_t aMaxLength,
+ nsACString &outString)
+{
+ if (inString.IsEmpty()) {
+ outString.Truncate();
+ return NS_OK;
+ }
+ if (inString.Length() < aMaxLength) {
+ outString.Assign(inString);
+ return NS_OK;
+ }
+ NS_ASSERTION(MsgIsUTF8(inString), "Invalid UTF-8 string is inputted");
+ const char* start = inString.get();
+ const char* end = start + inString.Length();
+ const char* last = start + aMaxLength;
+ const char* cur = start;
+ const char* prev = nullptr;
+ bool err = false;
+ while (cur < last) {
+ prev = cur;
+ if (!UTF8CharEnumerator::NextChar(&cur, end, &err) || err)
+ break;
+ }
+ if (!prev || err) {
+ outString.Truncate();
+ return NS_OK;
+ }
+ uint32_t len = prev - start;
+ outString.Assign(Substring(inString, 0, len));
+ return NS_OK;
+}
+
+void nsMsgI18NConvertRawBytesToUTF16(const nsCString& inString,
+ const char* charset,
+ nsAString& outString)
+{
+ if (MsgIsUTF8(inString))
+ {
+ CopyUTF8toUTF16(inString, outString);
+ return;
+ }
+
+ nsresult rv = ConvertToUnicode(charset, inString, outString);
+ if (NS_SUCCEEDED(rv))
+ return;
+
+ const char* cur = inString.BeginReading();
+ const char* end = inString.EndReading();
+ outString.Truncate();
+ while (cur < end) {
+ char c = *cur++;
+ if (c & char(0x80))
+ outString.Append(UCS2_REPLACEMENT_CHAR);
+ else
+ outString.Append(c);
+ }
+}
+
+void nsMsgI18NConvertRawBytesToUTF8(const nsCString& inString,
+ const char* charset,
+ nsACString& outString)
+{
+ if (MsgIsUTF8(inString))
+ {
+ outString.Assign(inString);
+ return;
+ }
+
+ nsAutoString utf16Text;
+ nsresult rv = ConvertToUnicode(charset, inString, utf16Text);
+ if (NS_SUCCEEDED(rv))
+ {
+ CopyUTF16toUTF8(utf16Text, outString);
+ return;
+ }
+
+ // EF BF BD (UTF-8 encoding of U+FFFD)
+ NS_NAMED_LITERAL_CSTRING(utf8ReplacementChar, "\357\277\275");
+ const char* cur = inString.BeginReading();
+ const char* end = inString.EndReading();
+ outString.Truncate();
+ while (cur < end) {
+ char c = *cur++;
+ if (c & char(0x80))
+ outString.Append(utf8ReplacementChar);
+ else
+ outString.Append(c);
+ }
+}
diff --git a/mailnews/base/util/nsMsgI18N.h b/mailnews/base/util/nsMsgI18N.h
new file mode 100644
index 000000000..e58ae620a
--- /dev/null
+++ b/mailnews/base/util/nsMsgI18N.h
@@ -0,0 +1,198 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgI18N_H_
+#define _nsMsgI18N_H_
+
+#include "nscore.h"
+#include "msgCore.h"
+#include "nsStringGlue.h"
+class nsIFile;
+
+/**
+ * Encode an input string into RFC 2047 form.
+ *
+ * @param header [IN] A header to encode.
+ * @param structured [IN] Specify the header is structured or non-structured field (See RFC-822).
+ * @param charset [IN] Charset name to convert.
+ * @param fieldnamelen [IN] Header field name length. (e.g. "From: " -> 6)
+ * @param usemime [IN] If false then apply charset conversion only no MIME encoding.
+ * @return Encoded buffer (in C string) or NULL in case of error.
+ */
+NS_MSG_BASE char *nsMsgI18NEncodeMimePartIIStr(const char *header, bool structured, const char *charset, int32_t fieldnamelen, bool usemime);
+
+/**
+ * Check if given charset is stateful (e.g. ISO-2022-JP).
+ *
+ * @param charset [IN] Charset name.
+ * @return True if stateful
+ */
+NS_MSG_BASE bool nsMsgI18Nstateful_charset(const char *charset);
+
+/**
+ * Check if given charset is multibye (e.g. Shift_JIS, Big5).
+ *
+ * @param charset [IN] Charset name.
+ * @return True if multibyte
+ */
+NS_MSG_BASE bool nsMsgI18Nmultibyte_charset(const char *charset);
+
+/**
+ * Check the input (unicode) string is in a range of the given charset after the conversion.
+ * Note, do not use this for large string (e.g. message body) since this actually applies the conversion to the buffer.
+ *
+ * @param charset [IN] Charset to be converted.
+ * @param inString [IN] Input unicode string to be examined.
+ * @param fallbackCharset [OUT]
+ * null if fallback charset is not needed.
+ * Otherwise, a fallback charset name may be set if that was used for the conversion.
+ * Caller is responsible for freeing the memory.
+ * @return True if the string can be converted within the charset range.
+ * False if one or more characters cannot be converted to the target charset.
+ */
+NS_MSG_BASE bool nsMsgI18Ncheck_data_in_charset_range(const char *charset, const char16_t* inString,
+ char **fallbackCharset=nullptr);
+
+/**
+ * Return charset name of file system (OS dependent).
+ *
+ * @return File system charset name.
+ */
+NS_MSG_BASE const char * nsMsgI18NFileSystemCharset(void);
+
+/**
+ * Return charset name of text file (OS dependent).
+ *
+ * @param aCharset [OUT] Text file charset name.
+ */
+NS_MSG_BASE void nsMsgI18NTextFileCharset(nsACString& aCharset);
+
+/**
+ * Convert from unicode to target charset.
+ *
+ * @param charset [IN] Charset name.
+ * @param inString [IN] Unicode string to convert.
+ * @param outString [OUT] Converted output string.
+ * @param aIsCharsetCanonical [IN] Whether the charset is canonical or not.
+ * @param aReportUencNoMapping [IN] Set encoder to report (instead of using
+ * replacement char on errors). Set to true
+ * to receive NS_ERROR_UENC_NOMAPPING when
+ * that happens. Note that
+ * NS_ERROR_UENC_NOMAPPING is a success code!
+ * @return nsresult.
+ */
+NS_MSG_BASE nsresult nsMsgI18NConvertFromUnicode(const char* aCharset,
+ const nsString& inString,
+ nsACString& outString,
+ bool aIsCharsetCanonical =
+ false,
+ bool reportUencNoMapping =
+ false);
+/**
+ * Convert from charset to unicode.
+ *
+ * @param charset [IN] Charset name.
+ * @param inString [IN] Input string to convert.
+ * @param outString [OUT] Output unicode string.
+ * @return nsresult.
+ */
+NS_MSG_BASE nsresult nsMsgI18NConvertToUnicode(const char* aCharset,
+ const nsCString& inString,
+ nsAString& outString,
+ bool aIsCharsetCanonical =
+ false);
+/**
+ * Parse for META charset.
+ *
+ * @param file [IN] A nsIFile.
+ * @return A charset name or empty string if not found.
+ */
+NS_MSG_BASE const char *nsMsgI18NParseMetaCharset(nsIFile* file);
+
+/**
+ * Shrink the aStr to aMaxLength bytes. Note that this doesn't check whether
+ * the aUTF8Str is valid UTF-8 string.
+ *
+ * @param inString [IN] Input UTF-8 string (it must be valid UTF-8 string)
+ * @param aMaxLength [IN] Shrink to this length (it means bytes)
+ * @param outString [OUT] Shrunken UTF-8 string
+ * @return nsresult
+ */
+NS_MSG_BASE nsresult nsMsgI18NShrinkUTF8Str(const nsCString &inString,
+ uint32_t aMaxLength,
+ nsACString &outString);
+
+/*
+ * Convert raw bytes in header to UTF-16
+ *
+ * @param inString [IN] Input raw octets
+ * @param outString [OUT] Output UTF-16 string
+ */
+NS_MSG_BASE void nsMsgI18NConvertRawBytesToUTF16(const nsCString& inString,
+ const char* charset,
+ nsAString& outString);
+
+/*
+ * Convert raw bytes in header to UTF-8
+ *
+ * @param inString [IN] Input raw octets
+ * @param outString [OUT] Output UTF-8 string
+ */
+NS_MSG_BASE void nsMsgI18NConvertRawBytesToUTF8(const nsCString& inString,
+ const char* charset,
+ nsACString& outString);
+
+// inline forwarders to avoid littering with 'x-imap4-.....'
+inline nsresult CopyUTF16toMUTF7(const nsString &aSrc, nsACString& aDest)
+{
+ return nsMsgI18NConvertFromUnicode("x-imap4-modified-utf7", aSrc,
+ aDest, true);
+}
+
+inline nsresult CopyMUTF7toUTF16(const nsCString& aSrc, nsAString& aDest)
+{
+ return nsMsgI18NConvertToUnicode("x-imap4-modified-utf7", aSrc,
+ aDest, true);
+}
+
+inline nsresult ConvertToUnicode(const char* charset,
+ const nsCString &aSrc, nsAString& aDest)
+{
+ return nsMsgI18NConvertToUnicode(charset, aSrc, aDest);
+}
+
+inline nsresult ConvertToUnicode(const char* charset,
+ const char* aSrc, nsAString& aDest)
+{
+ return nsMsgI18NConvertToUnicode(charset, nsDependentCString(aSrc), aDest);
+}
+
+inline nsresult ConvertFromUnicode(const char* charset,
+ const nsString &aSrc, nsACString& aDest)
+{
+ return nsMsgI18NConvertFromUnicode(charset, aSrc, aDest);
+}
+
+inline void ConvertRawBytesToUTF16(const nsCString& inString,
+ const char* charset, nsAString& outString)
+{
+ return nsMsgI18NConvertRawBytesToUTF16(inString, charset, outString);
+}
+
+inline void ConvertRawBytesToUTF16(const char* inString,
+ const char* charset, nsAString& outString)
+{
+ return nsMsgI18NConvertRawBytesToUTF16(nsDependentCString(inString),
+ charset,
+ outString);
+}
+
+inline void ConvertRawBytesToUTF8(const nsCString& inString,
+ const char* charset, nsACString& outString)
+{
+ return nsMsgI18NConvertRawBytesToUTF8(inString, charset, outString);
+}
+
+#endif /* _nsMsgI18N_H_ */
diff --git a/mailnews/base/util/nsMsgIdentity.cpp b/mailnews/base/util/nsMsgIdentity.cpp
new file mode 100644
index 000000000..51fabdee1
--- /dev/null
+++ b/mailnews/base/util/nsMsgIdentity.cpp
@@ -0,0 +1,669 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // for pre-compiled headers
+#include "nsMsgIdentity.h"
+#include "nsIPrefService.h"
+#include "nsStringGlue.h"
+#include "nsMsgCompCID.h"
+#include "nsIRDFService.h"
+#include "nsIRDFResource.h"
+#include "nsRDFCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgAccountManager.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsMsgBaseCID.h"
+#include "prprf.h"
+#include "nsISupportsPrimitives.h"
+#include "nsMsgUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsArrayUtils.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+#define REL_FILE_PREF_SUFFIX "-rel"
+
+NS_IMPL_ISUPPORTS(nsMsgIdentity,
+ nsIMsgIdentity)
+
+/*
+ * accessors for pulling values directly out of preferences
+ * instead of member variables, etc
+ */
+
+NS_IMETHODIMP
+nsMsgIdentity::GetKey(nsACString& aKey)
+{
+ aKey = mKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::SetKey(const nsACString& identityKey)
+{
+ mKey = identityKey;
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString branchName;
+ branchName.AssignLiteral("mail.identity.");
+ branchName += mKey;
+ branchName.Append('.');
+ rv = prefs->GetBranch(branchName.get(), getter_AddRefs(mPrefBranch));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = prefs->GetBranch("mail.identity.default.", getter_AddRefs(mDefPrefBranch));
+ return rv;
+}
+
+nsresult
+nsMsgIdentity::GetIdentityName(nsAString& idName)
+{
+ idName.AssignLiteral("");
+ // Try to use "fullname <email>" as the name.
+ nsresult rv = GetFullAddress(idName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If a non-empty label exists, append it.
+ nsString label;
+ rv = GetLabel(label);
+ if (NS_SUCCEEDED(rv) && !label.IsEmpty())
+ { // TODO: this should be localizable
+ idName.AppendLiteral(" (");
+ idName.Append(label);
+ idName.AppendLiteral(")");
+ }
+
+ if (!idName.IsEmpty())
+ return NS_OK;
+
+ // If we still found nothing to use, use our key.
+ return ToString(idName);
+}
+
+nsresult
+nsMsgIdentity::GetFullAddress(nsAString& fullAddress)
+{
+ nsAutoString fullName;
+ nsresult rv = GetFullName(fullName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString email;
+ rv = GetEmail(email);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fullName.IsEmpty() && email.IsEmpty())
+ fullAddress.Truncate();
+ else
+ mozilla::mailnews::MakeMimeAddress(fullName, NS_ConvertASCIItoUTF16(email), fullAddress);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::ToString(nsAString& aResult)
+{
+ aResult.AssignLiteral("[nsIMsgIdentity: ");
+ aResult.Append(NS_ConvertASCIItoUTF16(mKey));
+ aResult.AppendLiteral("]");
+ return NS_OK;
+}
+
+/* Identity attribute accessors */
+
+NS_IMETHODIMP
+nsMsgIdentity::GetSignature(nsIFile **sig)
+{
+ bool gotRelPref;
+ nsresult rv = NS_GetPersistentFile("sig_file" REL_FILE_PREF_SUFFIX, "sig_file", nullptr, gotRelPref, sig, mPrefBranch);
+ if (NS_SUCCEEDED(rv) && !gotRelPref)
+ {
+ rv = NS_SetPersistentFile("sig_file" REL_FILE_PREF_SUFFIX, "sig_file", *sig, mPrefBranch);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to write signature file pref.");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::SetSignature(nsIFile *sig)
+{
+ nsresult rv = NS_OK;
+ if (sig)
+ rv = NS_SetPersistentFile("sig_file" REL_FILE_PREF_SUFFIX, "sig_file", sig, mPrefBranch);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::ClearAllValues()
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mPrefBranch->DeleteBranch("");
+}
+
+NS_IMPL_IDPREF_STR(EscapedVCard, "escapedVCard")
+NS_IMPL_IDPREF_STR(SmtpServerKey, "smtpServer")
+NS_IMPL_IDPREF_WSTR(FullName, "fullName")
+NS_IMPL_IDPREF_STR(Email, "useremail")
+NS_IMPL_IDPREF_WSTR(Label, "label")
+NS_IMPL_IDPREF_STR(ReplyTo, "reply_to")
+NS_IMPL_IDPREF_WSTR(Organization, "organization")
+NS_IMPL_IDPREF_BOOL(ComposeHtml, "compose_html")
+NS_IMPL_IDPREF_BOOL(AttachVCard, "attach_vcard")
+NS_IMPL_IDPREF_BOOL(AttachSignature, "attach_signature")
+NS_IMPL_IDPREF_WSTR(HtmlSigText, "htmlSigText")
+NS_IMPL_IDPREF_BOOL(HtmlSigFormat, "htmlSigFormat")
+
+NS_IMPL_IDPREF_BOOL(AutoQuote, "auto_quote")
+NS_IMPL_IDPREF_INT(ReplyOnTop, "reply_on_top")
+NS_IMPL_IDPREF_BOOL(SigBottom, "sig_bottom")
+NS_IMPL_IDPREF_BOOL(SigOnForward, "sig_on_fwd")
+NS_IMPL_IDPREF_BOOL(SigOnReply, "sig_on_reply")
+
+NS_IMPL_IDPREF_INT(SignatureDate,"sig_date")
+
+NS_IMPL_IDPREF_BOOL(DoFcc, "fcc")
+
+NS_IMPL_FOLDERPREF_STR(FccFolder, "fcc_folder", "Sent", nsMsgFolderFlags::SentMail)
+NS_IMPL_IDPREF_STR(FccFolderPickerMode, "fcc_folder_picker_mode")
+NS_IMPL_IDPREF_BOOL(FccReplyFollowsParent, "fcc_reply_follows_parent")
+NS_IMPL_IDPREF_STR(DraftsFolderPickerMode, "drafts_folder_picker_mode")
+NS_IMPL_IDPREF_STR(ArchivesFolderPickerMode, "archives_folder_picker_mode")
+NS_IMPL_IDPREF_STR(TmplFolderPickerMode, "tmpl_folder_picker_mode")
+
+NS_IMPL_IDPREF_BOOL(BccSelf, "bcc_self")
+NS_IMPL_IDPREF_BOOL(BccOthers, "bcc_other")
+NS_IMPL_IDPREF_STR (BccList, "bcc_other_list")
+
+NS_IMPL_IDPREF_BOOL(SuppressSigSep, "suppress_signature_separator")
+
+NS_IMPL_IDPREF_BOOL(DoCc, "doCc")
+NS_IMPL_IDPREF_STR (DoCcList, "doCcList")
+
+NS_IMETHODIMP
+nsMsgIdentity::GetDoBcc(bool *aValue)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mPrefBranch->GetBoolPref("doBcc", aValue);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ bool bccSelf = false;
+ GetBccSelf(&bccSelf);
+
+ bool bccOthers = false;
+ GetBccOthers(&bccOthers);
+
+ nsCString others;
+ GetBccList(others);
+
+ *aValue = bccSelf || (bccOthers && !others.IsEmpty());
+
+ return SetDoBcc(*aValue);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::SetDoBcc(bool aValue)
+{
+ return SetBoolAttribute("doBcc", aValue);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::GetDoBccList(nsACString& aValue)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString val;
+ nsresult rv = mPrefBranch->GetCharPref("doBccList", getter_Copies(val));
+ aValue = val;
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ bool bccSelf = false;
+ rv = GetBccSelf(&bccSelf);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (bccSelf)
+ GetEmail(aValue);
+
+ bool bccOthers = false;
+ rv = GetBccOthers(&bccOthers);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString others;
+ rv = GetBccList(others);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (bccOthers && !others.IsEmpty()) {
+ if (bccSelf)
+ aValue.AppendLiteral(",");
+ aValue.Append(others);
+ }
+
+ return SetDoBccList(aValue);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::SetDoBccList(const nsACString& aValue)
+{
+ return SetCharAttribute("doBccList", aValue);
+}
+
+NS_IMPL_FOLDERPREF_STR(DraftFolder, "draft_folder", "Drafts", nsMsgFolderFlags::Drafts)
+NS_IMPL_FOLDERPREF_STR(ArchiveFolder, "archive_folder", "Archives", nsMsgFolderFlags::Archive)
+NS_IMPL_FOLDERPREF_STR(StationeryFolder, "stationery_folder", "Templates", nsMsgFolderFlags::Templates)
+
+NS_IMPL_IDPREF_BOOL(ArchiveEnabled, "archive_enabled")
+NS_IMPL_IDPREF_INT(ArchiveGranularity, "archive_granularity")
+NS_IMPL_IDPREF_BOOL(ArchiveKeepFolderStructure, "archive_keep_folder_structure")
+
+NS_IMPL_IDPREF_BOOL(ShowSaveMsgDlg, "showSaveMsgDlg")
+NS_IMPL_IDPREF_STR (DirectoryServer, "directoryServer")
+NS_IMPL_IDPREF_BOOL(OverrideGlobalPref, "overrideGlobal_Pref")
+NS_IMPL_IDPREF_BOOL(AutocompleteToMyDomain, "autocompleteToMyDomain")
+
+NS_IMPL_IDPREF_BOOL(Valid, "valid")
+
+nsresult
+nsMsgIdentity::getFolderPref(const char *prefname, nsCString& retval,
+ const char *folderName, uint32_t folderflag)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mPrefBranch->GetCharPref(prefname, getter_Copies(retval));
+ if (NS_SUCCEEDED(rv) && !retval.IsEmpty()) {
+ // get the corresponding RDF resource
+ // RDF will create the folder resource if it doesn't already exist
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<nsIRDFResource> resource;
+ rdf->GetResource(retval, getter_AddRefs(resource));
+
+ nsCOMPtr <nsIMsgFolder> folderResource = do_QueryInterface(resource);
+ if (folderResource)
+ {
+ // don't check validity of folder - caller will handle creating it
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ //make sure that folder hierarchy is built so that legitimate parent-child relationship is established
+ folderResource->GetServer(getter_AddRefs(server));
+ if (server)
+ {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIMsgFolder> deferredToRootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ server->GetRootMsgFolder(getter_AddRefs(deferredToRootFolder));
+ // check if we're using a deferred account - if not, use the uri;
+ // otherwise, fall through to code that will fix this pref.
+ if (rootFolder == deferredToRootFolder)
+ {
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+ rv = server->GetMsgFolderFromURI(folderResource, retval, getter_AddRefs(msgFolder));
+ return NS_SUCCEEDED(rv) ? msgFolder->GetURI(retval) : rv;
+ }
+ }
+ }
+ }
+
+ // if the server doesn't exist, fall back to the default pref.
+ rv = mDefPrefBranch->GetCharPref(prefname, getter_Copies(retval));
+ if (NS_SUCCEEDED(rv) && !retval.IsEmpty())
+ return setFolderPref(prefname, retval, folderflag);
+
+ // here I think we need to create a uri for the folder on the
+ // default server for this identity.
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIArray> servers;
+ rv = accountManager->GetServersForIdentity(this, getter_AddRefs(servers));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIMsgIncomingServer> server(do_QueryElementAt(servers, 0, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool defaultToServer;
+ server->GetDefaultCopiesAndFoldersPrefsToServer(&defaultToServer);
+ // if we should default to special folders on the server,
+ // use the local folders server
+ if (!defaultToServer)
+ {
+ rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ // this will get the deferred to server's root folder, if "server"
+ // is deferred, e.g., using the pop3 global inbox.
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (rootFolder)
+ {
+ rv = rootFolder->GetURI(retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ retval.Append('/');
+ retval.Append(folderName);
+ return setFolderPref(prefname, retval, folderflag);
+ }
+ }
+ // if there are no servers for this identity, return generic failure.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+nsMsgIdentity::setFolderPref(const char *prefname, const nsACString& value, uint32_t folderflag)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString oldpref;
+ nsresult rv;
+ nsCOMPtr<nsIRDFResource> res;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+
+ if (folderflag == nsMsgFolderFlags::SentMail)
+ {
+ // Clear the temporary return receipt filter so that the new filter
+ // rule can be recreated (by ConfigureTemporaryFilters()).
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIArray> servers;
+ rv = accountManager->GetServersForIdentity(this, getter_AddRefs(servers));
+ NS_ENSURE_SUCCESS(rv,rv);
+ uint32_t cnt = 0;
+ servers->GetLength(&cnt);
+ if (cnt > 0)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server(do_QueryElementAt(servers, 0, &rv));
+ if (NS_SUCCEEDED(rv))
+ server->ClearTemporaryReturnReceiptsFilter(); // okay to fail; no need to check for return code
+ }
+ }
+
+ // get the old folder, and clear the special folder flag on it
+ rv = mPrefBranch->GetCharPref(prefname, getter_Copies(oldpref));
+ if (NS_SUCCEEDED(rv) && !oldpref.IsEmpty())
+ {
+ rv = rdf->GetResource(oldpref, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv) && res)
+ {
+ folder = do_QueryInterface(res, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = folder->ClearFlag(folderflag);
+ }
+ }
+
+ // set the new folder, and set the special folder flags on it
+ rv = SetCharAttribute(prefname, value);
+ if (NS_SUCCEEDED(rv) && !value.IsEmpty())
+ {
+ rv = rdf->GetResource(value, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv) && res)
+ {
+ folder = do_QueryInterface(res, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = folder->SetFlag(folderflag);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIdentity::SetUnicharAttribute(const char *aName, const nsAString& val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (!val.IsEmpty()) {
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> supportsString(
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ rv = supportsString->SetData(val);
+ if (NS_SUCCEEDED(rv))
+ rv = mPrefBranch->SetComplexValue(aName,
+ NS_GET_IID(nsISupportsString),
+ supportsString);
+ return rv;
+ }
+
+ mPrefBranch->ClearUserPref(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIdentity::GetUnicharAttribute(const char *aName, nsAString& val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsISupportsString> supportsString;
+ if (NS_FAILED(mPrefBranch->GetComplexValue(aName,
+ NS_GET_IID(nsISupportsString),
+ getter_AddRefs(supportsString))))
+ mDefPrefBranch->GetComplexValue(aName,
+ NS_GET_IID(nsISupportsString),
+ getter_AddRefs(supportsString));
+
+ if (supportsString)
+ supportsString->GetData(val);
+ else
+ val.Truncate();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIdentity::SetCharAttribute(const char *aName, const nsACString& val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (!val.IsEmpty())
+ return mPrefBranch->SetCharPref(aName, nsCString(val).get());
+
+ mPrefBranch->ClearUserPref(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIdentity::GetCharAttribute(const char *aName, nsACString& val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString tmpVal;
+ if (NS_FAILED(mPrefBranch->GetCharPref(aName, getter_Copies(tmpVal))))
+ mDefPrefBranch->GetCharPref(aName, getter_Copies(tmpVal));
+ val = tmpVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIdentity::SetBoolAttribute(const char *aName, bool val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mPrefBranch->SetBoolPref(aName, val);
+}
+
+NS_IMETHODIMP nsMsgIdentity::GetBoolAttribute(const char *aName, bool *val)
+{
+ NS_ENSURE_ARG_POINTER(val);
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ *val = false;
+
+ if (NS_FAILED(mPrefBranch->GetBoolPref(aName, val)))
+ mDefPrefBranch->GetBoolPref(aName, val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIdentity::SetIntAttribute(const char *aName, int32_t val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mPrefBranch->SetIntPref(aName, val);
+}
+
+NS_IMETHODIMP nsMsgIdentity::GetIntAttribute(const char *aName, int32_t *val)
+{
+ NS_ENSURE_ARG_POINTER(val);
+
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ *val = 0;
+
+ if (NS_FAILED(mPrefBranch->GetIntPref(aName, val)))
+ mDefPrefBranch->GetIntPref(aName, val);
+
+ return NS_OK;
+}
+
+#define COPY_IDENTITY_FILE_VALUE(SRC_ID,MACRO_GETTER,MACRO_SETTER) \
+ { \
+ nsresult macro_rv; \
+ nsCOMPtr <nsIFile>macro_spec; \
+ macro_rv = SRC_ID->MACRO_GETTER(getter_AddRefs(macro_spec)); \
+ if (NS_SUCCEEDED(macro_rv)) \
+ this->MACRO_SETTER(macro_spec); \
+ }
+
+#define COPY_IDENTITY_INT_VALUE(SRC_ID,MACRO_GETTER,MACRO_SETTER) \
+ { \
+ nsresult macro_rv; \
+ int32_t macro_oldInt; \
+ macro_rv = SRC_ID->MACRO_GETTER(&macro_oldInt); \
+ if (NS_SUCCEEDED(macro_rv)) \
+ this->MACRO_SETTER(macro_oldInt); \
+ }
+
+#define COPY_IDENTITY_BOOL_VALUE(SRC_ID,MACRO_GETTER,MACRO_SETTER) \
+ { \
+ nsresult macro_rv; \
+ bool macro_oldBool; \
+ macro_rv = SRC_ID->MACRO_GETTER(&macro_oldBool); \
+ if (NS_SUCCEEDED(macro_rv)) \
+ this->MACRO_SETTER(macro_oldBool); \
+ }
+
+#define COPY_IDENTITY_STR_VALUE(SRC_ID,MACRO_GETTER,MACRO_SETTER) \
+ { \
+ nsCString macro_oldStr; \
+ nsresult macro_rv; \
+ macro_rv = SRC_ID->MACRO_GETTER(macro_oldStr); \
+ if (NS_SUCCEEDED(macro_rv)) { \
+ this->MACRO_SETTER(macro_oldStr); \
+ } \
+ }
+
+#define COPY_IDENTITY_WSTR_VALUE(SRC_ID,MACRO_GETTER,MACRO_SETTER) \
+ { \
+ nsString macro_oldStr; \
+ nsresult macro_rv; \
+ macro_rv = SRC_ID->MACRO_GETTER(macro_oldStr); \
+ if (NS_SUCCEEDED(macro_rv)) { \
+ this->MACRO_SETTER(macro_oldStr); \
+ } \
+ }
+
+NS_IMETHODIMP
+nsMsgIdentity::Copy(nsIMsgIdentity *identity)
+{
+ NS_ENSURE_ARG_POINTER(identity);
+
+ COPY_IDENTITY_BOOL_VALUE(identity,GetComposeHtml,SetComposeHtml)
+ COPY_IDENTITY_STR_VALUE(identity,GetEmail,SetEmail)
+ COPY_IDENTITY_WSTR_VALUE(identity,GetLabel,SetLabel)
+ COPY_IDENTITY_STR_VALUE(identity,GetReplyTo,SetReplyTo)
+ COPY_IDENTITY_WSTR_VALUE(identity,GetFullName,SetFullName)
+ COPY_IDENTITY_WSTR_VALUE(identity,GetOrganization,SetOrganization)
+ COPY_IDENTITY_STR_VALUE(identity,GetDraftFolder,SetDraftFolder)
+ COPY_IDENTITY_STR_VALUE(identity,GetArchiveFolder,SetArchiveFolder)
+ COPY_IDENTITY_STR_VALUE(identity,GetFccFolder,SetFccFolder)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetFccReplyFollowsParent,
+ SetFccReplyFollowsParent)
+ COPY_IDENTITY_STR_VALUE(identity,GetStationeryFolder,SetStationeryFolder)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetArchiveEnabled,SetArchiveEnabled)
+ COPY_IDENTITY_INT_VALUE(identity,GetArchiveGranularity,
+ SetArchiveGranularity)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetArchiveKeepFolderStructure,
+ SetArchiveKeepFolderStructure)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetAttachSignature,SetAttachSignature)
+ COPY_IDENTITY_FILE_VALUE(identity,GetSignature,SetSignature)
+ COPY_IDENTITY_WSTR_VALUE(identity,GetHtmlSigText,SetHtmlSigText)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetHtmlSigFormat,SetHtmlSigFormat)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetAutoQuote,SetAutoQuote)
+ COPY_IDENTITY_INT_VALUE(identity,GetReplyOnTop,SetReplyOnTop)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetSigBottom,SetSigBottom)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetSigOnForward,SetSigOnForward)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetSigOnReply,SetSigOnReply)
+ COPY_IDENTITY_INT_VALUE(identity,GetSignatureDate,SetSignatureDate)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetAttachVCard,SetAttachVCard)
+ COPY_IDENTITY_STR_VALUE(identity,GetEscapedVCard,SetEscapedVCard)
+ COPY_IDENTITY_STR_VALUE(identity,GetSmtpServerKey,SetSmtpServerKey)
+ COPY_IDENTITY_BOOL_VALUE(identity,GetSuppressSigSep,SetSuppressSigSep)
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::GetRequestReturnReceipt(bool *aVal)
+{
+ NS_ENSURE_ARG_POINTER(aVal);
+
+ bool useCustomPrefs = false;
+ nsresult rv = GetBoolAttribute("use_custom_prefs", &useCustomPrefs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (useCustomPrefs)
+ return GetBoolAttribute("request_return_receipt_on", aVal);
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefs->GetBoolPref("mail.receipt.request_return_receipt_on", aVal);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::GetReceiptHeaderType(int32_t *aType)
+{
+ NS_ENSURE_ARG_POINTER(aType);
+
+ bool useCustomPrefs = false;
+ nsresult rv = GetBoolAttribute("use_custom_prefs", &useCustomPrefs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (useCustomPrefs)
+ return GetIntAttribute("request_receipt_header_type", aType);
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefs->GetIntPref("mail.receipt.request_header_type", aType);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::GetRequestDSN(bool *aVal)
+{
+ NS_ENSURE_ARG_POINTER(aVal);
+
+ bool useCustomPrefs = false;
+ nsresult rv = GetBoolAttribute("dsn_use_custom_prefs", &useCustomPrefs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (useCustomPrefs)
+ return GetBoolAttribute("dsn_always_request_on", aVal);
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefs->GetBoolPref("mail.dsn.always_request_on", aVal);
+}
diff --git a/mailnews/base/util/nsMsgIdentity.h b/mailnews/base/util/nsMsgIdentity.h
new file mode 100644
index 000000000..7b17ae3f6
--- /dev/null
+++ b/mailnews/base/util/nsMsgIdentity.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgIdentity_h___
+#define nsMsgIdentity_h___
+
+#include "nsIMsgIdentity.h"
+#include "nsIPrefBranch.h"
+#include "msgCore.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+
+class NS_MSG_BASE nsMsgIdentity final : public nsIMsgIdentity
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGIDENTITY
+
+private:
+ ~nsMsgIdentity() {}
+ nsCString mKey;
+ nsCOMPtr<nsIPrefBranch> mPrefBranch;
+ nsCOMPtr<nsIPrefBranch> mDefPrefBranch;
+
+protected:
+ nsresult getFolderPref(const char *pref, nsCString&, const char *, uint32_t);
+ nsresult setFolderPref(const char *pref, const nsACString&, uint32_t);
+};
+
+
+#define NS_IMPL_IDPREF_STR(_postfix, _prefname) \
+NS_IMETHODIMP \
+nsMsgIdentity::Get##_postfix(nsACString& retval) \
+{ \
+ return GetCharAttribute(_prefname, retval); \
+} \
+NS_IMETHODIMP \
+nsMsgIdentity::Set##_postfix(const nsACString& value) \
+{ \
+ return SetCharAttribute(_prefname, value); \
+}
+
+#define NS_IMPL_IDPREF_WSTR(_postfix, _prefname) \
+NS_IMETHODIMP \
+nsMsgIdentity::Get##_postfix(nsAString& retval) \
+{ \
+ return GetUnicharAttribute(_prefname, retval); \
+} \
+NS_IMETHODIMP \
+nsMsgIdentity::Set##_postfix(const nsAString& value) \
+{ \
+ return SetUnicharAttribute(_prefname, value); \
+}
+
+#define NS_IMPL_IDPREF_BOOL(_postfix, _prefname) \
+NS_IMETHODIMP \
+nsMsgIdentity::Get##_postfix(bool *retval) \
+{ \
+ return GetBoolAttribute(_prefname, retval); \
+} \
+NS_IMETHODIMP \
+nsMsgIdentity::Set##_postfix(bool value) \
+{ \
+ return mPrefBranch->SetBoolPref(_prefname, value); \
+}
+
+#define NS_IMPL_IDPREF_INT(_postfix, _prefname) \
+NS_IMETHODIMP \
+nsMsgIdentity::Get##_postfix(int32_t *retval) \
+{ \
+ return GetIntAttribute(_prefname, retval); \
+} \
+NS_IMETHODIMP \
+nsMsgIdentity::Set##_postfix(int32_t value) \
+{ \
+ return mPrefBranch->SetIntPref(_prefname, value); \
+}
+
+#define NS_IMPL_FOLDERPREF_STR(_postfix, _prefname, _foldername, _flag) \
+NS_IMETHODIMP \
+nsMsgIdentity::Get##_postfix(nsACString& retval) \
+{ \
+ nsresult rv; \
+ nsCString folderPref; \
+ rv = getFolderPref(_prefname, folderPref, _foldername, _flag); \
+ retval = folderPref; \
+ return rv; \
+} \
+NS_IMETHODIMP \
+nsMsgIdentity::Set##_postfix(const nsACString& value) \
+{ \
+ return setFolderPref(_prefname, value, _flag); \
+}
+
+#endif /* nsMsgIdentity_h___ */
diff --git a/mailnews/base/util/nsMsgIncomingServer.cpp b/mailnews/base/util/nsMsgIncomingServer.cpp
new file mode 100644
index 000000000..2677e149a
--- /dev/null
+++ b/mailnews/base/util/nsMsgIncomingServer.cpp
@@ -0,0 +1,2332 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgIncomingServer.h"
+#include "nscore.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "prprf.h"
+
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsISupportsPrimitives.h"
+
+#include "nsIMsgBiffManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgDBCID.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIPrefService.h"
+#include "nsIRelativeFilePref.h"
+#include "nsIDocShell.h"
+#include "nsIAuthPrompt.h"
+#include "nsNetUtil.h"
+#include "nsIWindowWatcher.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgHdr.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoginInfo.h"
+#include "nsILoginManager.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgMdnGenerator.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgUtils.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "mozilla/Services.h"
+#include "nsIMsgFilter.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+#include "nsIObserverService.h"
+
+#define PORT_NOT_SET -1
+
+nsMsgIncomingServer::nsMsgIncomingServer():
+ m_rootFolder(nullptr),
+ m_downloadedHdrs(50),
+ m_numMsgsDownloaded(0),
+ m_biffState(nsIMsgFolder::nsMsgBiffState_Unknown),
+ m_serverBusy(false),
+ m_canHaveFilters(true),
+ m_displayStartupPage(true),
+ mPerformingBiff(false)
+{
+}
+
+nsresult nsMsgIncomingServer::Init()
+{
+ // We need to know when the password manager changes.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ observerService->AddObserver(this, "passwordmgr-storage-changed", false);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ return NS_OK;
+}
+
+nsMsgIncomingServer::~nsMsgIncomingServer()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsMsgIncomingServer, nsIMsgIncomingServer,
+ nsISupportsWeakReference, nsIObserver)
+
+NS_IMETHODIMP
+nsMsgIncomingServer::Observe(nsISupports *aSubject, const char* aTopic,
+ const char16_t *aData)
+{
+ nsresult rv;
+
+ // When the state of the password manager changes we need to clear the
+ // password from the cache in case the user just removed it.
+ if (strcmp(aTopic, "passwordmgr-storage-changed") == 0)
+ {
+ rv = ForgetSessionPassword();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0)
+ {
+ // Now remove ourselves from the observer service as well.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ observerService->RemoveObserver(this, "passwordmgr-storage-changed");
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetServerBusy(bool aServerBusy)
+{
+ m_serverBusy = aServerBusy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetServerBusy(bool * aServerBusy)
+{
+ NS_ENSURE_ARG_POINTER(aServerBusy);
+ *aServerBusy = m_serverBusy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetKey(nsACString& serverKey)
+{
+ serverKey = m_serverKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetKey(const nsACString& serverKey)
+{
+ m_serverKey.Assign(serverKey);
+
+ // in order to actually make use of the key, we need the prefs
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString branchName;
+ branchName.AssignLiteral("mail.server.");
+ branchName.Append(m_serverKey);
+ branchName.Append('.');
+ rv = prefs->GetBranch(branchName.get(), getter_AddRefs(mPrefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return prefs->GetBranch("mail.server.default.", getter_AddRefs(mDefPrefBranch));
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetRootFolder(nsIMsgFolder * aRootFolder)
+{
+ m_rootFolder = aRootFolder;
+ return NS_OK;
+}
+
+// this will return the root folder of this account,
+// even if this server is deferred.
+NS_IMETHODIMP
+nsMsgIncomingServer::GetRootFolder(nsIMsgFolder * *aRootFolder)
+{
+ NS_ENSURE_ARG_POINTER(aRootFolder);
+ if (!m_rootFolder)
+ {
+ nsresult rv = CreateRootFolder();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*aRootFolder = m_rootFolder);
+ return NS_OK;
+}
+
+// this will return the root folder of the deferred to account,
+// if this server is deferred.
+NS_IMETHODIMP
+nsMsgIncomingServer::GetRootMsgFolder(nsIMsgFolder **aRootMsgFolder)
+{
+ return GetRootFolder(aRootMsgFolder);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::PerformExpand(nsIMsgWindow *aMsgWindow)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::VerifyLogon(nsIUrlListener *aUrlListener, nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::PerformBiff(nsIMsgWindow* aMsgWindow)
+{
+ //This has to be implemented in the derived class, but in case someone doesn't implement it
+ //just return not implemented.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetNewMessages(nsIMsgFolder *aFolder, nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ return aFolder->GetNewMessages(aMsgWindow, aUrlListener);
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetPerformingBiff(bool *aPerformingBiff)
+{
+ NS_ENSURE_ARG_POINTER(aPerformingBiff);
+ *aPerformingBiff = mPerformingBiff;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::SetPerformingBiff(bool aPerformingBiff)
+{
+ mPerformingBiff = aPerformingBiff;
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsMsgIncomingServer, BiffState, uint32_t, m_biffState)
+
+NS_IMETHODIMP nsMsgIncomingServer::WriteToFolderCache(nsIMsgFolderCache *folderCache)
+{
+ nsresult rv = NS_OK;
+ if (m_rootFolder)
+ {
+ nsCOMPtr <nsIMsgFolder> msgFolder = do_QueryInterface(m_rootFolder, &rv);
+ if (NS_SUCCEEDED(rv) && msgFolder)
+ rv = msgFolder->WriteToFolderCache(folderCache, true /* deep */);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::Shutdown()
+{
+ nsresult rv = CloseCachedConnections();
+ mFilterPlugin = nullptr;
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (mFilterList)
+ {
+ // close the filter log stream
+ rv = mFilterList->SetLogStream(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mFilterList = nullptr;
+ }
+
+ if (mSpamSettings)
+ {
+ // close the spam log stream
+ rv = mSpamSettings->SetLogStream(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mSpamSettings = nullptr;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::CloseCachedConnections()
+{
+ // derived class should override if they cache connections.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetDownloadMessagesAtStartup(bool *getMessagesAtStartup)
+{
+ // derived class should override if they need to do this.
+ *getMessagesAtStartup = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanHaveFilters(bool *canHaveFilters)
+{
+ NS_ENSURE_ARG_POINTER(canHaveFilters);
+ *canHaveFilters = m_canHaveFilters;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetCanHaveFilters(bool aCanHaveFilters)
+{
+ m_canHaveFilters = aCanHaveFilters;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanBeDefaultServer(bool *canBeDefaultServer)
+{
+ // derived class should override if they need to do this.
+ *canBeDefaultServer = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanSearchMessages(bool *canSearchMessages)
+{
+ // derived class should override if they need to do this.
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ *canSearchMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanCompactFoldersOnServer(bool *canCompactFoldersOnServer)
+{
+ // derived class should override if they need to do this.
+ NS_ENSURE_ARG_POINTER(canCompactFoldersOnServer);
+ *canCompactFoldersOnServer = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanUndoDeleteOnServer(bool *canUndoDeleteOnServer)
+{
+ // derived class should override if they need to do this.
+ NS_ENSURE_ARG_POINTER(canUndoDeleteOnServer);
+ *canUndoDeleteOnServer = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanEmptyTrashOnExit(bool *canEmptyTrashOnExit)
+{
+ // derived class should override if they need to do this.
+ NS_ENSURE_ARG_POINTER(canEmptyTrashOnExit);
+ *canEmptyTrashOnExit = true;
+ return NS_OK;
+}
+
+// construct <localStoreType>://[<username>@]<hostname
+NS_IMETHODIMP
+nsMsgIncomingServer::GetServerURI(nsACString& aResult)
+{
+ nsresult rv;
+ rv = GetLocalStoreType(aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aResult.AppendLiteral("://");
+
+ nsCString username;
+ rv = GetUsername(username);
+ if (NS_SUCCEEDED(rv) && !username.IsEmpty()) {
+ nsCString escapedUsername;
+ MsgEscapeString(username, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+ // not all servers have a username
+ aResult.Append(escapedUsername);
+ aResult.Append('@');
+ }
+
+ nsCString hostname;
+ rv = GetHostName(hostname);
+ if (NS_SUCCEEDED(rv) && !hostname.IsEmpty()) {
+ nsCString escapedHostname;
+ MsgEscapeString(hostname, nsINetUtil::ESCAPE_URL_PATH, escapedHostname);
+ // not all servers have a hostname
+ aResult.Append(escapedHostname);
+ }
+ return NS_OK;
+}
+
+// helper routine to create local folder on disk, if it doesn't exist.
+nsresult
+nsMsgIncomingServer::CreateLocalFolder(const nsAString& folderName)
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> child;
+ rv = rootFolder->GetChildNamed(folderName, getter_AddRefs(child));
+ if (child)
+ return NS_OK;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->CreateFolder(rootFolder, folderName, getter_AddRefs(child));
+}
+
+nsresult
+nsMsgIncomingServer::CreateRootFolder()
+{
+ nsresult rv;
+ // get the URI from the incoming server
+ nsCString serverUri;
+ rv = GetServerURI(serverUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the corresponding RDF resource
+ // RDF will create the server resource if it doesn't already exist
+ nsCOMPtr<nsIRDFResource> serverResource;
+ rv = rdf->GetResource(serverUri, getter_AddRefs(serverResource));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make incoming server know about its root server folder so we
+ // can find sub-folders given an incoming server.
+ m_rootFolder = do_QueryInterface(serverResource, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetBoolValue(const char *prefname,
+ bool *val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NS_ENSURE_ARG_POINTER(val);
+ *val = false;
+
+ if (NS_FAILED(mPrefBranch->GetBoolPref(prefname, val)))
+ mDefPrefBranch->GetBoolPref(prefname, val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetBoolValue(const char *prefname,
+ bool val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ bool defaultValue;
+ nsresult rv = mDefPrefBranch->GetBoolPref(prefname, &defaultValue);
+
+ if (NS_SUCCEEDED(rv) && val == defaultValue)
+ mPrefBranch->ClearUserPref(prefname);
+ else
+ rv = mPrefBranch->SetBoolPref(prefname, val);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetIntValue(const char *prefname,
+ int32_t *val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NS_ENSURE_ARG_POINTER(val);
+ *val = 0;
+
+ if (NS_FAILED(mPrefBranch->GetIntPref(prefname, val)))
+ mDefPrefBranch->GetIntPref(prefname, val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetFileValue(const char* aRelPrefName,
+ const char* aAbsPrefName,
+ nsIFile** aLocalFile)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Get the relative first
+ nsCOMPtr<nsIRelativeFilePref> relFilePref;
+ nsresult rv = mPrefBranch->GetComplexValue(aRelPrefName,
+ NS_GET_IID(nsIRelativeFilePref),
+ getter_AddRefs(relFilePref));
+ if (relFilePref) {
+ rv = relFilePref->GetFile(aLocalFile);
+ NS_ASSERTION(*aLocalFile, "An nsIRelativeFilePref has no file.");
+ if (NS_SUCCEEDED(rv))
+ (*aLocalFile)->Normalize();
+ } else {
+ rv = mPrefBranch->GetComplexValue(aAbsPrefName,
+ NS_GET_IID(nsIFile),
+ reinterpret_cast<void**>(aLocalFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = NS_NewRelativeFilePref(*aLocalFile,
+ NS_LITERAL_CSTRING(NS_APP_USER_PROFILE_50_DIR),
+ getter_AddRefs(relFilePref));
+ if (relFilePref)
+ rv = mPrefBranch->SetComplexValue(aRelPrefName,
+ NS_GET_IID(nsIRelativeFilePref),
+ relFilePref);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetFileValue(const char* aRelPrefName,
+ const char* aAbsPrefName,
+ nsIFile* aLocalFile)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Write the relative path.
+ nsCOMPtr<nsIRelativeFilePref> relFilePref;
+ NS_NewRelativeFilePref(aLocalFile,
+ NS_LITERAL_CSTRING(NS_APP_USER_PROFILE_50_DIR),
+ getter_AddRefs(relFilePref));
+ if (relFilePref) {
+ nsresult rv = mPrefBranch->SetComplexValue(aRelPrefName,
+ NS_GET_IID(nsIRelativeFilePref),
+ relFilePref);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return mPrefBranch->SetComplexValue(aAbsPrefName, NS_GET_IID(nsIFile), aLocalFile);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetIntValue(const char *prefname,
+ int32_t val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t defaultVal;
+ nsresult rv = mDefPrefBranch->GetIntPref(prefname, &defaultVal);
+
+ if (NS_SUCCEEDED(rv) && defaultVal == val)
+ mPrefBranch->ClearUserPref(prefname);
+ else
+ rv = mPrefBranch->SetIntPref(prefname, val);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCharValue(const char *prefname,
+ nsACString& val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString tmpVal;
+ if (NS_FAILED(mPrefBranch->GetCharPref(prefname, getter_Copies(tmpVal))))
+ mDefPrefBranch->GetCharPref(prefname, getter_Copies(tmpVal));
+ val = tmpVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetUnicharValue(const char *prefname,
+ nsAString& val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsISupportsString> supportsString;
+ if (NS_FAILED(mPrefBranch->GetComplexValue(prefname,
+ NS_GET_IID(nsISupportsString),
+ getter_AddRefs(supportsString))))
+ mDefPrefBranch->GetComplexValue(prefname,
+ NS_GET_IID(nsISupportsString),
+ getter_AddRefs(supportsString));
+
+ if (supportsString)
+ return supportsString->GetData(val);
+ val.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetCharValue(const char *prefname,
+ const nsACString& val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (val.IsEmpty()) {
+ mPrefBranch->ClearUserPref(prefname);
+ return NS_OK;
+ }
+
+ nsCString defaultVal;
+ nsresult rv = mDefPrefBranch->GetCharPref(prefname, getter_Copies(defaultVal));
+
+ if (NS_SUCCEEDED(rv) && defaultVal.Equals(val))
+ mPrefBranch->ClearUserPref(prefname);
+ else
+ rv = mPrefBranch->SetCharPref(prefname, nsCString(val).get());
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetUnicharValue(const char *prefname,
+ const nsAString& val)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (val.IsEmpty()) {
+ mPrefBranch->ClearUserPref(prefname);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupportsString> supportsString;
+ nsresult rv = mDefPrefBranch->GetComplexValue(prefname,
+ NS_GET_IID(nsISupportsString),
+ getter_AddRefs(supportsString));
+ nsString defaultVal;
+ if (NS_SUCCEEDED(rv) &&
+ NS_SUCCEEDED(supportsString->GetData(defaultVal)) &&
+ defaultVal.Equals(val))
+ mPrefBranch->ClearUserPref(prefname);
+ else {
+ supportsString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ if (supportsString) {
+ supportsString->SetData(val);
+ rv = mPrefBranch->SetComplexValue(prefname,
+ NS_GET_IID(nsISupportsString),
+ supportsString);
+ }
+ }
+
+ return rv;
+}
+
+// pretty name is the display name to show to the user
+NS_IMETHODIMP
+nsMsgIncomingServer::GetPrettyName(nsAString& retval)
+{
+ nsresult rv = GetUnicharValue("name", retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if there's no name, then just return the hostname
+ return retval.IsEmpty() ? GetConstructedPrettyName(retval) : rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetPrettyName(const nsAString& value)
+{
+ SetUnicharValue("name", value);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ rootFolder->SetPrettyName(value);
+ return NS_OK;
+}
+
+
+// construct the pretty name to show to the user if they haven't
+// specified one. This should be overridden for news and mail.
+NS_IMETHODIMP
+nsMsgIncomingServer::GetConstructedPrettyName(nsAString& retval)
+{
+ nsCString username;
+ nsresult rv = GetUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!username.IsEmpty()) {
+ CopyASCIItoUTF16(username, retval);
+ retval.AppendLiteral(" on ");
+ }
+
+ nsCString hostname;
+ rv = GetHostName(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ retval.Append(NS_ConvertASCIItoUTF16(hostname));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::ToString(nsAString& aResult)
+{
+ aResult.AssignLiteral("[nsIMsgIncomingServer: ");
+ aResult.Append(NS_ConvertASCIItoUTF16(m_serverKey));
+ aResult.AppendLiteral("]");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::SetPassword(const nsACString& aPassword)
+{
+ m_password = aPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetPassword(nsACString& aPassword)
+{
+ aPassword = m_password;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff)
+{
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ *aServerRequiresPasswordForBiff = true;
+ return NS_OK;
+}
+
+// This sets m_password if we find a password in the pw mgr.
+nsresult nsMsgIncomingServer::GetPasswordWithoutUI()
+{
+ nsresult rv;
+ nsCOMPtr<nsILoginManager> loginMgr(do_GetService(NS_LOGINMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current server URI
+ nsCString currServerUri;
+ rv = GetLocalStoreType(currServerUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ currServerUri.AppendLiteral("://");
+
+ nsCString temp;
+ rv = GetHostName(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ currServerUri.Append(temp);
+
+ NS_ConvertUTF8toUTF16 currServer(currServerUri);
+
+ uint32_t numLogins = 0;
+ nsILoginInfo** logins = nullptr;
+ rv = loginMgr->FindLogins(&numLogins, currServer, EmptyString(),
+ currServer, &logins);
+
+ // Login manager can produce valid fails, e.g. NS_ERROR_ABORT when a user
+ // cancels the master password dialog. Therefore handle that here, but don't
+ // warn about it.
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Don't abort here, if we didn't find any or failed, then we'll just have
+ // to prompt.
+ if (numLogins > 0)
+ {
+ nsCString serverCUsername;
+ rv = GetUsername(serverCUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 serverUsername(serverCUsername);
+
+ nsString username;
+ for (uint32_t i = 0; i < numLogins; ++i)
+ {
+ rv = logins[i]->GetUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (username.Equals(serverUsername))
+ {
+ nsString password;
+ rv = logins[i]->GetPassword(password);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_password = NS_LossyConvertUTF16toASCII(password);
+ break;
+ }
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numLogins, logins);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetPasswordWithUI(const nsAString& aPromptMessage, const
+ nsAString& aPromptTitle,
+ nsIMsgWindow* aMsgWindow,
+ nsACString& aPassword)
+{
+ nsresult rv = NS_OK;
+
+ if (m_password.IsEmpty())
+ {
+ // let's see if we have the password in the password manager and
+ // can avoid this prompting thing. This makes it easier to get embedders
+ // to get up and running w/o a password prompting UI.
+ rv = GetPasswordWithoutUI();
+ // If GetPasswordWithoutUI returns NS_ERROR_ABORT, the most likely case
+ // is the user canceled getting the master password, so just return
+ // straight away, as they won't want to get prompted again.
+ if (rv == NS_ERROR_ABORT)
+ return NS_MSG_PASSWORD_PROMPT_CANCELLED;
+ }
+ if (m_password.IsEmpty())
+ {
+ nsCOMPtr<nsIAuthPrompt> dialog;
+ // aMsgWindow is required if we need to prompt
+ if (aMsgWindow)
+ {
+ rv = aMsgWindow->GetAuthPrompt(getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (dialog)
+ {
+ // prompt the user for the password
+ nsCString serverUri;
+ rv = GetLocalStoreType(serverUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ serverUri.AppendLiteral("://");
+ nsCString temp;
+ rv = GetUsername(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!temp.IsEmpty())
+ {
+ nsCString escapedUsername;
+ MsgEscapeString(temp, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+ serverUri.Append(escapedUsername);
+ serverUri.Append('@');
+ }
+
+ rv = GetHostName(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ serverUri.Append(temp);
+
+ // we pass in the previously used password, if any, into PromptPassword
+ // so that it will appear as ******. This means we can't use an nsString
+ // and getter_Copies.
+ char16_t *uniPassword = nullptr;
+ if (!aPassword.IsEmpty())
+ uniPassword = ToNewUnicode(NS_ConvertASCIItoUTF16(aPassword));
+
+ bool okayValue = true;
+ rv = dialog->PromptPassword(PromiseFlatString(aPromptTitle).get(),
+ PromiseFlatString(aPromptMessage).get(),
+ NS_ConvertASCIItoUTF16(serverUri).get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ &uniPassword, &okayValue);
+ nsAutoString uniPasswordAdopted;
+ uniPasswordAdopted.Adopt(uniPassword);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!okayValue) // if the user pressed cancel, just return an empty string;
+ {
+ aPassword.Truncate();
+ return NS_MSG_PASSWORD_PROMPT_CANCELLED;
+ }
+
+ // we got a password back...so remember it
+ rv = SetPassword(NS_LossyConvertUTF16toASCII(uniPasswordAdopted));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } // if we got a prompt dialog
+ else
+ return NS_ERROR_FAILURE;
+ } // if the password is empty
+ return GetPassword(aPassword);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::ForgetPassword()
+{
+ nsresult rv;
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current server URI
+ nsCString currServerUri;
+ rv = GetLocalStoreType(currServerUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ currServerUri.AppendLiteral("://");
+
+ nsCString temp;
+ rv = GetHostName(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ currServerUri.Append(temp);
+
+ uint32_t count;
+ nsILoginInfo** logins;
+
+ NS_ConvertUTF8toUTF16 currServer(currServerUri);
+
+ nsCString serverCUsername;
+ rv = GetUsername(serverCUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 serverUsername(serverCUsername);
+
+ rv = loginMgr->FindLogins(&count, currServer, EmptyString(),
+ currServer, &logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // There should only be one-login stored for this url, however just in case
+ // there isn't.
+ nsString username;
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ if (NS_SUCCEEDED(logins[i]->GetUsername(username)) &&
+ username.Equals(serverUsername))
+ {
+ // If this fails, just continue, we'll still want to remove the password
+ // from our local cache.
+ loginMgr->RemoveLogin(logins[i]);
+ }
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins);
+
+ return SetPassword(EmptyCString());
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::ForgetSessionPassword()
+{
+ m_password.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetDefaultLocalPath(nsIFile *aDefaultLocalPath)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return protocolInfo->SetDefaultLocalPath(aDefaultLocalPath);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetLocalPath(nsIFile **aLocalPath)
+{
+ nsresult rv;
+
+ // if the local path has already been set, use it
+ rv = GetFileValue("directory-rel", "directory", aLocalPath);
+ if (NS_SUCCEEDED(rv) && *aLocalPath)
+ return rv;
+
+ // otherwise, create the path using the protocol info.
+ // note we are using the
+ // hostname, unless that directory exists.
+// this should prevent all collisions.
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> localPath;
+ rv = protocolInfo->GetDefaultLocalPath(getter_AddRefs(localPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = localPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (rv == NS_ERROR_FILE_ALREADY_EXISTS)
+ rv = NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString hostname;
+ rv = GetHostName(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // set the leaf name to "dummy", and then call MakeUnique with a suggested leaf name
+ rv = localPath->AppendNative(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = localPath->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetLocalPath(localPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ localPath.swap(*aLocalPath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetMsgStore(nsIMsgPluggableStore **aMsgStore)
+{
+ NS_ENSURE_ARG_POINTER(aMsgStore);
+ if (!m_msgStore)
+ {
+ nsCString storeContractID;
+ nsresult rv;
+ // We don't want there to be a default pref, I think, since
+ // we can't change the default. We may want no pref to mean
+ // berkeley store, and then set the store pref off of some sort
+ // of default when creating a server. But we need to make sure
+ // that we do always write a store pref.
+ GetCharValue("storeContractID", storeContractID);
+ if (storeContractID.IsEmpty())
+ {
+ storeContractID.Assign("@mozilla.org/msgstore/berkeleystore;1");
+ SetCharValue("storeContractID", storeContractID);
+ }
+
+ // After someone starts using the pluggable store, we can no longer
+ // change the value.
+ SetBoolValue("canChangeStoreType", false);
+
+ // Right now, we just have one pluggable store per server. If we want
+ // to support multiple, this pref could be a list of pluggable store
+ // contract id's.
+ m_msgStore = do_CreateInstance(storeContractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ NS_IF_ADDREF(*aMsgStore = m_msgStore);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetLocalPath(nsIFile *aLocalPath)
+{
+ NS_ENSURE_ARG_POINTER(aLocalPath);
+ nsresult rv = aLocalPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (rv == NS_ERROR_FILE_ALREADY_EXISTS)
+ rv = NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetFileValue("directory-rel", "directory", aLocalPath);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetLocalStoreType(nsACString& aResult)
+{
+ NS_NOTYETIMPLEMENTED("nsMsgIncomingServer superclass not implementing GetLocalStoreType!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetLocalDatabaseType(nsACString& aResult)
+{
+ NS_NOTYETIMPLEMENTED("nsMsgIncomingServer superclass not implementing GetLocalDatabaseType!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetAccountManagerChrome(nsAString& aResult)
+{
+ aResult.AssignLiteral("am-main.xul");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::Equals(nsIMsgIncomingServer *server, bool *_retval)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(server);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCString key1;
+ nsCString key2;
+
+ rv = GetKey(key1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = server->GetKey(key2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // compare the server keys
+ *_retval = key1.Equals(key2, nsCaseInsensitiveCStringComparator());
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::ClearAllValues()
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mPrefBranch->DeleteBranch("");
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::RemoveFiles()
+{
+ // IMPORTANT, see bug #77652
+ // TODO: Decide what to do for deferred accounts.
+ nsCString deferredToAccount;
+ GetCharValue("deferred_to_account", deferredToAccount);
+ bool isDeferredTo = true;
+ GetIsDeferredTo(&isDeferredTo);
+ if (!deferredToAccount.IsEmpty() || isDeferredTo)
+ {
+ NS_ASSERTION(false, "shouldn't remove files for a deferred account");
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr <nsIFile> localPath;
+ nsresult rv = GetLocalPath(getter_AddRefs(localPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return localPath->Remove(true);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetFilterList(nsIMsgFilterList *aFilterList)
+{
+ mFilterList = aFilterList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (!mFilterList)
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ // use GetRootFolder so for deferred pop3 accounts, we'll get the filters
+ // file from the deferred account, not the deferred to account,
+ // so that filters will still be per-server.
+ nsresult rv = GetRootFolder(getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString filterType;
+ rv = GetCharValue("filter.type", filterType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!filterType.IsEmpty() && !filterType.EqualsLiteral("default"))
+ {
+ nsAutoCString contractID("@mozilla.org/filterlist;1?type=");
+ contractID += filterType;
+ ToLowerCase(contractID);
+ mFilterList = do_CreateInstance(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mFilterList->SetFolder(msgFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = mFilterList);
+ return NS_OK;
+ }
+
+ // The default case, a local folder, is a bit special. It requires
+ // more initialization.
+
+ nsCOMPtr<nsIFile> thisFolder;
+ rv = msgFolder->GetFilePath(getter_AddRefs(thisFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mFilterFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mFilterFile->InitWithFile(thisFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mFilterFile->AppendNative(NS_LITERAL_CSTRING("msgFilterRules.dat"));
+
+ bool fileExists;
+ mFilterFile->Exists(&fileExists);
+ if (!fileExists)
+ {
+ nsCOMPtr<nsIFile> oldFilterFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = oldFilterFile->InitWithFile(thisFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ oldFilterFile->AppendNative(NS_LITERAL_CSTRING("rules.dat"));
+
+ oldFilterFile->Exists(&fileExists);
+ if (fileExists) //copy rules.dat --> msgFilterRules.dat
+ {
+ rv = oldFilterFile->CopyToNative(thisFolder, NS_LITERAL_CSTRING("msgFilterRules.dat"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filterService->OpenFilterList(mFilterFile, msgFolder, aMsgWindow, getter_AddRefs(mFilterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*aResult = mFilterList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetEditableFilterList(nsIMsgFilterList *aEditableFilterList)
+{
+ mEditableFilterList = aEditableFilterList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetEditableFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (!mEditableFilterList)
+ {
+ bool editSeparate;
+ nsresult rv = GetBoolValue("filter.editable.separate", &editSeparate);
+ if (NS_FAILED(rv) || !editSeparate)
+ return GetFilterList(aMsgWindow, aResult);
+
+ nsCString filterType;
+ rv = GetCharValue("filter.editable.type", filterType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contractID("@mozilla.org/filterlist;1?type=");
+ contractID += filterType;
+ ToLowerCase(contractID);
+ mEditableFilterList = do_CreateInstance(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ // use GetRootFolder so for deferred pop3 accounts, we'll get the filters
+ // file from the deferred account, not the deferred to account,
+ // so that filters will still be per-server.
+ rv = GetRootFolder(getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mEditableFilterList->SetFolder(msgFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = mEditableFilterList);
+ return NS_OK;
+ }
+
+ NS_IF_ADDREF(*aResult = mEditableFilterList);
+ return NS_OK;
+}
+
+// If the hostname contains ':' (like hostname:1431)
+// then parse and set the port number.
+nsresult
+nsMsgIncomingServer::InternalSetHostName(const nsACString& aHostname, const char * prefName)
+{
+ nsCString hostname;
+ hostname = aHostname;
+ if (MsgCountChar(hostname, ':') == 1)
+ {
+ int32_t colonPos = hostname.FindChar(':');
+ nsAutoCString portString(Substring(hostname, colonPos));
+ hostname.SetLength(colonPos);
+ nsresult err;
+ int32_t port = portString.ToInteger(&err);
+ if (NS_SUCCEEDED(err))
+ SetPort(port);
+ }
+ return SetCharValue(prefName, hostname);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged)
+{
+ nsresult rv;
+
+ // 1. Reset password so that users are prompted for new password for the new user/host.
+ ForgetPassword();
+
+ // 2. Let the derived class close all cached connection to the old host.
+ CloseCachedConnections();
+
+ // 3. Notify any listeners for account server changes.
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = accountManager->NotifyServerChanged(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 4. Lastly, replace all occurrences of old name in the acct name with the new one.
+ nsString acctName;
+ rv = GetPrettyName(acctName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_FALSE(acctName.IsEmpty(), NS_OK);
+
+ // exit if new name contains @ then better do not update the account name
+ if (!hostnameChanged && (newName.FindChar('@') != kNotFound))
+ return NS_OK;
+
+ int32_t atPos = acctName.FindChar('@');
+
+ // get previous username and hostname
+ nsCString userName, hostName;
+ if (hostnameChanged)
+ {
+ rv = GetRealUsername(userName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ hostName.Assign(oldName);
+ }
+ else
+ {
+ userName.Assign(oldName);
+ rv = GetRealHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // switch corresponding part of the account name to the new name...
+ if (!hostnameChanged && (atPos != kNotFound))
+ {
+ // ...if username changed and the previous username was equal to the part
+ // of the account name before @
+ if (StringHead(acctName, atPos).Equals(NS_ConvertASCIItoUTF16(userName)))
+ acctName.Replace(0, userName.Length(), NS_ConvertASCIItoUTF16(newName));
+ }
+ if (hostnameChanged)
+ {
+ // ...if hostname changed and the previous hostname was equal to the part
+ // of the account name after @, or to the whole account name
+ if (atPos == kNotFound)
+ atPos = 0;
+ else
+ atPos += 1;
+ if (Substring(acctName, atPos).Equals(NS_ConvertASCIItoUTF16(hostName))) {
+ acctName.Replace(atPos, acctName.Length() - atPos,
+ NS_ConvertASCIItoUTF16(newName));
+ }
+ }
+
+ return SetPrettyName(acctName);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetHostName(const nsACString& aHostname)
+{
+ return (InternalSetHostName(aHostname, "hostname"));
+}
+
+// SetRealHostName() is called only when the server name is changed from the
+// UI (Account Settings page). No one should call it in any circumstances.
+NS_IMETHODIMP
+nsMsgIncomingServer::SetRealHostName(const nsACString& aHostname)
+{
+ nsCString oldName;
+ nsresult rv = GetRealHostName(oldName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = InternalSetHostName(aHostname, "realhostname");
+
+ // A few things to take care of if we're changing the hostname.
+ if (!aHostname.Equals(oldName, nsCaseInsensitiveCStringComparator()))
+ rv = OnUserOrHostNameChanged(oldName, aHostname, true);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetHostName(nsACString& aResult)
+{
+ nsresult rv;
+ rv = GetCharValue("hostname", aResult);
+ if (MsgCountChar(aResult, ':') == 1)
+ {
+ // gack, we need to reformat the hostname - SetHostName will do that
+ SetHostName(aResult);
+ rv = GetCharValue("hostname", aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetRealHostName(nsACString& aResult)
+{
+ // If 'realhostname' is set (was changed) then use it, otherwise use 'hostname'
+ nsresult rv;
+ rv = GetCharValue("realhostname", aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aResult.IsEmpty())
+ return GetHostName(aResult);
+
+ if (MsgCountChar(aResult, ':') == 1)
+ {
+ SetRealHostName(aResult);
+ rv = GetCharValue("realhostname", aResult);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetRealUsername(nsACString& aResult)
+{
+ // If 'realuserName' is set (was changed) then use it, otherwise use 'userName'
+ nsresult rv;
+ rv = GetCharValue("realuserName", aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return aResult.IsEmpty() ? GetUsername(aResult) : rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetRealUsername(const nsACString& aUsername)
+{
+ // Need to take care of few things if we're changing the username.
+ nsCString oldName;
+ nsresult rv = GetRealUsername(oldName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetCharValue("realuserName", aUsername);
+ if (!oldName.Equals(aUsername))
+ rv = OnUserOrHostNameChanged(oldName, aUsername, false);
+ return rv;
+}
+
+#define BIFF_PREF_NAME "check_new_mail"
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetDoBiff(bool *aDoBiff)
+{
+ NS_ENSURE_ARG_POINTER(aDoBiff);
+
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ rv = mPrefBranch->GetBoolPref(BIFF_PREF_NAME, aDoBiff);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ // if the pref isn't set, use the default
+ // value based on the protocol
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = protocolInfo->GetDefaultDoBiff(aDoBiff);
+ // note, don't call SetDoBiff()
+ // since we keep changing our minds on
+ // if biff should be on or off, let's keep the ability
+ // to change the default in future builds.
+ // if we call SetDoBiff() here, it will be in the users prefs.
+ // and we can't do anything after that.
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetDoBiff(bool aDoBiff)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Update biffManager immediately, no restart required. Adding/removing
+ // existing/non-existing server is handled without error checking.
+ nsresult rv;
+ nsCOMPtr<nsIMsgBiffManager> biffService =
+ do_GetService(NS_MSGBIFFMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && biffService)
+ {
+ if (aDoBiff)
+ (void) biffService->AddServerBiff(this);
+ else
+ (void) biffService->RemoveServerBiff(this);
+ }
+
+ return mPrefBranch->SetBoolPref(BIFF_PREF_NAME, aDoBiff);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetPort(int32_t *aPort)
+{
+ NS_ENSURE_ARG_POINTER(aPort);
+
+ nsresult rv;
+ rv = GetIntValue("port", aPort);
+ // We can't use a port of 0, because the URI parsing code fails.
+ if (*aPort != PORT_NOT_SET && *aPort)
+ return rv;
+
+ // if the port isn't set, use the default
+ // port based on the protocol
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool useSSLPort = (socketType == nsMsgSocketType::SSL);
+ return protocolInfo->GetDefaultServerPort(useSSLPort, aPort);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetPort(int32_t aPort)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool useSSLPort = (socketType == nsMsgSocketType::SSL);
+
+ int32_t defaultPort;
+ protocolInfo->GetDefaultServerPort(useSSLPort, &defaultPort);
+ return SetIntValue("port", aPort == defaultPort ? PORT_NOT_SET : aPort);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetProtocolInfo(nsIMsgProtocolInfo **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCString type;
+ nsresult rv = GetType(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contractid(NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX);
+ contractid.Append(type);
+
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo = do_GetService(contractid.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ protocolInfo.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetRetentionSettings(nsIMsgRetentionSettings **settings)
+{
+ NS_ENSURE_ARG_POINTER(settings);
+ nsMsgRetainByPreference retainByPreference;
+ int32_t daysToKeepHdrs = 0;
+ int32_t numHeadersToKeep = 0;
+ int32_t daysToKeepBodies = 0;
+ bool cleanupBodiesByDays = false;
+ bool applyToFlaggedMessages = false;
+ nsresult rv = NS_OK;
+ // Create an empty retention settings object,
+ // get the settings from the server prefs, and init the object from the prefs.
+ nsCOMPtr <nsIMsgRetentionSettings> retentionSettings =
+ do_CreateInstance(NS_MSG_RETENTIONSETTINGS_CONTRACTID);
+ if (retentionSettings)
+ {
+ rv = GetIntValue("retainBy", (int32_t*) &retainByPreference);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetIntValue("numHdrsToKeep", &numHeadersToKeep);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetIntValue("daysToKeepHdrs", &daysToKeepHdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetIntValue("daysToKeepBodies", &daysToKeepBodies);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetBoolValue("cleanupBodies", &cleanupBodiesByDays);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetBoolValue("applyToFlaggedMessages", &applyToFlaggedMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ retentionSettings->SetRetainByPreference(retainByPreference);
+ retentionSettings->SetNumHeadersToKeep((uint32_t) numHeadersToKeep);
+ retentionSettings->SetDaysToKeepBodies(daysToKeepBodies);
+ retentionSettings->SetDaysToKeepHdrs(daysToKeepHdrs);
+ retentionSettings->SetCleanupBodiesByDays(cleanupBodiesByDays);
+ retentionSettings->SetApplyToFlaggedMessages(applyToFlaggedMessages);
+ }
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ NS_IF_ADDREF(*settings = retentionSettings);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::SetRetentionSettings(nsIMsgRetentionSettings *settings)
+{
+ nsMsgRetainByPreference retainByPreference;
+ uint32_t daysToKeepHdrs = 0;
+ uint32_t numHeadersToKeep = 0;
+ uint32_t daysToKeepBodies = 0;
+ bool cleanupBodiesByDays = false;
+ bool applyToFlaggedMessages = false;
+ settings->GetRetainByPreference(&retainByPreference);
+ settings->GetNumHeadersToKeep(&numHeadersToKeep);
+ settings->GetDaysToKeepBodies(&daysToKeepBodies);
+ settings->GetDaysToKeepHdrs(&daysToKeepHdrs);
+ settings->GetCleanupBodiesByDays(&cleanupBodiesByDays);
+ settings->GetApplyToFlaggedMessages(&applyToFlaggedMessages);
+ nsresult rv = SetIntValue("retainBy", retainByPreference);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetIntValue("numHdrsToKeep", numHeadersToKeep);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetIntValue("daysToKeepHdrs", daysToKeepHdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetIntValue("daysToKeepBodies", daysToKeepBodies);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetBoolValue("cleanupBodies", cleanupBodiesByDays);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetBoolValue("applyToFlaggedMessages", applyToFlaggedMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetDisplayStartupPage(bool *displayStartupPage)
+{
+ NS_ENSURE_ARG_POINTER(displayStartupPage);
+ *displayStartupPage = m_displayStartupPage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetDisplayStartupPage(bool displayStartupPage)
+{
+ m_displayStartupPage = displayStartupPage;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgIncomingServer::GetDownloadSettings(nsIMsgDownloadSettings **settings)
+{
+ NS_ENSURE_ARG_POINTER(settings);
+ bool downloadUnreadOnly = false;
+ bool downloadByDate = false;
+ uint32_t ageLimitOfMsgsToDownload = 0;
+ nsresult rv = NS_OK;
+ if (!m_downloadSettings)
+ {
+ m_downloadSettings = do_CreateInstance(NS_MSG_DOWNLOADSETTINGS_CONTRACTID);
+ if (m_downloadSettings)
+ {
+ rv = GetBoolValue("downloadUnreadOnly", &downloadUnreadOnly);
+ rv = GetBoolValue("downloadByDate", &downloadByDate);
+ rv = GetIntValue("ageLimit", (int32_t *) &ageLimitOfMsgsToDownload);
+ m_downloadSettings->SetDownloadUnreadOnly(downloadUnreadOnly);
+ m_downloadSettings->SetDownloadByDate(downloadByDate);
+ m_downloadSettings->SetAgeLimitOfMsgsToDownload(ageLimitOfMsgsToDownload);
+ }
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ // Create an empty download settings object,
+ // get the settings from the server prefs, and init the object from the prefs.
+ }
+ NS_IF_ADDREF(*settings = m_downloadSettings);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::SetDownloadSettings(nsIMsgDownloadSettings *settings)
+{
+ m_downloadSettings = settings;
+ bool downloadUnreadOnly = false;
+ bool downloadByDate = false;
+ uint32_t ageLimitOfMsgsToDownload = 0;
+ m_downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly);
+ m_downloadSettings->GetDownloadByDate(&downloadByDate);
+ m_downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload);
+ nsresult rv = SetBoolValue("downloadUnreadOnly", downloadUnreadOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetBoolValue("downloadByDate", downloadByDate);
+ return SetIntValue("ageLimit", ageLimitOfMsgsToDownload);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetSupportsDiskSpace(bool *aSupportsDiskSpace)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsDiskSpace);
+ *aSupportsDiskSpace = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetOfflineSupportLevel(int32_t *aSupportLevel)
+{
+ NS_ENSURE_ARG_POINTER(aSupportLevel);
+
+ nsresult rv = GetIntValue("offline_support_level", aSupportLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*aSupportLevel == OFFLINE_SUPPORT_LEVEL_UNDEFINED)
+ *aSupportLevel = OFFLINE_SUPPORT_LEVEL_NONE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetOfflineSupportLevel(int32_t aSupportLevel)
+{
+ SetIntValue("offline_support_level", aSupportLevel);
+ return NS_OK;
+}
+#define BASE_MSGS_URL "chrome://messenger/locale/messenger.properties"
+
+NS_IMETHODIMP nsMsgIncomingServer::DisplayOfflineMsg(nsIMsgWindow *aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle(BASE_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (bundle)
+ {
+ nsString errorMsgTitle;
+ nsString errorMsgBody;
+ bundle->GetStringFromName(u"nocachedbodybody2", getter_Copies(errorMsgBody));
+ bundle->GetStringFromName(u"nocachedbodytitle", getter_Copies(errorMsgTitle));
+ aMsgWindow->DisplayHTMLInMessagePane(errorMsgTitle, errorMsgBody, true);
+ }
+
+ return NS_OK;
+}
+
+// Called only during the migration process. A unique name is generated for the
+// migrated account.
+NS_IMETHODIMP
+nsMsgIncomingServer::GeneratePrettyNameForMigration(nsAString& aPrettyName)
+{
+/**
+ * 4.x had provisions for multiple imap servers to be maintained under
+ * single identity. So, when migrated each of those server accounts need
+ * to be represented by unique account name. nsImapIncomingServer will
+ * override the implementation for this to do the right thing.
+*/
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetFilterScope(nsMsgSearchScopeValue *filterScope)
+{
+ NS_ENSURE_ARG_POINTER(filterScope);
+ *filterScope = nsMsgSearchScope::offlineMailFilter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetSearchScope(nsMsgSearchScopeValue *searchScope)
+{
+ NS_ENSURE_ARG_POINTER(searchScope);
+ *searchScope = nsMsgSearchScope::offlineMail;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetIsSecure(bool *aIsSecure)
+{
+ NS_ENSURE_ARG_POINTER(aIsSecure);
+ int32_t socketType;
+ nsresult rv = GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv,rv);
+ *aIsSecure = (socketType == nsMsgSocketType::alwaysSTARTTLS ||
+ socketType == nsMsgSocketType::SSL);
+ return NS_OK;
+}
+
+// use the convenience macros to implement the accessors
+NS_IMPL_SERVERPREF_STR(nsMsgIncomingServer, Username, "userName")
+NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, AuthMethod, "authMethod")
+NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, BiffMinutes, "check_time")
+NS_IMPL_SERVERPREF_STR(nsMsgIncomingServer, Type, "type")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, DownloadOnBiff, "download_on_biff")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, Valid, "valid")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, EmptyTrashOnExit,
+ "empty_trash_on_exit")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, CanDelete, "canDelete")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, LoginAtStartUp, "login_at_startup")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer,
+ DefaultCopiesAndFoldersPrefsToServer,
+ "allows_specialfolders_usage")
+
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer,
+ CanCreateFoldersOnServer,
+ "canCreateFolders")
+
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer,
+ CanFileMessagesOnServer,
+ "canFileMessages")
+
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer,
+ LimitOfflineMessageSize,
+ "limit_offline_message_size")
+
+NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, MaxMessageSize, "max_size")
+
+NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, IncomingDuplicateAction, "dup_action")
+
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, Hidden, "hidden")
+
+NS_IMETHODIMP nsMsgIncomingServer::GetSocketType(int32_t *aSocketType)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mPrefBranch->GetIntPref("socketType", aSocketType);
+
+ // socketType is set to default value. Look at isSecure setting
+ if (NS_FAILED(rv))
+ {
+ bool isSecure;
+ rv = mPrefBranch->GetBoolPref("isSecure", &isSecure);
+ if (NS_SUCCEEDED(rv) && isSecure)
+ {
+ *aSocketType = nsMsgSocketType::SSL;
+ // don't call virtual method in case overrides call GetSocketType
+ nsMsgIncomingServer::SetSocketType(*aSocketType);
+ }
+ else
+ {
+ if (!mDefPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+ rv = mDefPrefBranch->GetIntPref("socketType", aSocketType);
+ if (NS_FAILED(rv))
+ *aSocketType = nsMsgSocketType::plain;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::SetSocketType(int32_t aSocketType)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t socketType = nsMsgSocketType::plain;
+ mPrefBranch->GetIntPref("socketType", &socketType);
+
+ nsresult rv = mPrefBranch->SetIntPref("socketType", aSocketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isSecureOld = (socketType == nsMsgSocketType::alwaysSTARTTLS ||
+ socketType == nsMsgSocketType::SSL);
+ bool isSecureNew = (aSocketType == nsMsgSocketType::alwaysSTARTTLS ||
+ aSocketType == nsMsgSocketType::SSL);
+ if ((isSecureOld != isSecureNew) && m_rootFolder) {
+ nsCOMPtr <nsIAtom> isSecureAtom = MsgGetAtom("isSecure");
+ m_rootFolder->NotifyBoolPropertyChanged(isSecureAtom,
+ isSecureOld, isSecureNew);
+ }
+ return NS_OK;
+}
+
+// Check if the password is available and return a boolean indicating whether
+// it is being authenticated or not.
+NS_IMETHODIMP
+nsMsgIncomingServer::GetPasswordPromptRequired(bool *aPasswordIsRequired)
+{
+ NS_ENSURE_ARG_POINTER(aPasswordIsRequired);
+ *aPasswordIsRequired = true;
+
+ // If the password is not even required for biff we don't need to check any further
+ nsresult rv = GetServerRequiresPasswordForBiff(aPasswordIsRequired);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!*aPasswordIsRequired)
+ return NS_OK;
+
+ // If the password is empty, check to see if it is stored and to be retrieved
+ if (m_password.IsEmpty())
+ (void)GetPasswordWithoutUI();
+
+ *aPasswordIsRequired = m_password.IsEmpty();
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::ConfigureTemporaryFilters(nsIMsgFilterList *aFilterList)
+{
+ nsresult rv = ConfigureTemporaryReturnReceiptsFilter(aFilterList);
+ if (NS_FAILED(rv)) // shut up warnings...
+ return rv;
+ return ConfigureTemporaryServerSpamFilters(aFilterList);
+}
+
+nsresult
+nsMsgIncomingServer::ConfigureTemporaryServerSpamFilters(nsIMsgFilterList *filterList)
+{
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ nsresult rv = GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool useServerFilter;
+ rv = spamSettings->GetUseServerFilter(&useServerFilter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we aren't configured to use server filters, then return early.
+ if (!useServerFilter)
+ return NS_OK;
+
+ // For performance reasons, we'll handle clearing of filters if the user turns
+ // off the server-side filters from the junk mail controls, in the junk mail controls.
+ nsAutoCString serverFilterName;
+ spamSettings->GetServerFilterName(serverFilterName);
+ if (serverFilterName.IsEmpty())
+ return NS_OK;
+ int32_t serverFilterTrustFlags = 0;
+ (void) spamSettings->GetServerFilterTrustFlags(&serverFilterTrustFlags);
+ if (!serverFilterTrustFlags)
+ return NS_OK;
+ // check if filters have been setup already.
+ nsAutoString yesFilterName, noFilterName;
+ CopyASCIItoUTF16(serverFilterName, yesFilterName);
+ yesFilterName.AppendLiteral("Yes");
+
+ CopyASCIItoUTF16(serverFilterName, noFilterName);
+ noFilterName.AppendLiteral("No");
+
+ nsCOMPtr<nsIMsgFilter> newFilter;
+ (void) filterList->GetFilterNamed(yesFilterName,
+ getter_AddRefs(newFilter));
+
+ if (!newFilter)
+ (void) filterList->GetFilterNamed(noFilterName,
+ getter_AddRefs(newFilter));
+ if (newFilter)
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ spamSettings->GetServerFilterFile(getter_AddRefs(file));
+
+ // it's possible that we can no longer find the sfd file (i.e. the user disabled an extnsion that
+ // was supplying the .sfd file.
+ if (!file)
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgFilterService> filterService = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ nsCOMPtr<nsIMsgFilterList> serverFilterList;
+
+ rv = filterService->OpenFilterList(file, NULL, NULL, getter_AddRefs(serverFilterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = serverFilterList->GetFilterNamed(yesFilterName,
+ getter_AddRefs(newFilter));
+ if (newFilter && serverFilterTrustFlags & nsISpamSettings::TRUST_POSITIVES)
+ {
+ newFilter->SetTemporary(true);
+ // check if we're supposed to move junk mail to junk folder; if so,
+ // add filter action to do so.
+
+ /*
+ * We don't want this filter to activate on messages that have
+ * been marked by the user as not spam. This occurs when messages that
+ * were marked as good are moved back into the inbox. But to
+ * do this with a filter, we have to add a boolean term. That requires
+ * that we rewrite the existing filter search terms to group them.
+ */
+
+ // get the list of search terms from the filter
+ nsCOMPtr<nsISupportsArray> searchTerms;
+ rv = newFilter->GetSearchTerms(getter_AddRefs(searchTerms));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t count = 0;
+ searchTerms->Count(&count);
+ if (count > 1) // don't need to group a single term
+ {
+ // beginGrouping the first term, and endGrouping the last term
+ nsCOMPtr<nsIMsgSearchTerm> firstTerm(do_QueryElementAt(searchTerms,
+ 0, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+ firstTerm->SetBeginsGrouping(true);
+
+ nsCOMPtr<nsIMsgSearchTerm> lastTerm(do_QueryElementAt(searchTerms,
+ count - 1, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+ lastTerm->SetEndsGrouping(true);
+ }
+
+ // Create a new term, checking if the user set junk status. The term will
+ // search for junkscoreorigin != "user"
+ nsCOMPtr<nsIMsgSearchTerm> searchTerm;
+ rv = newFilter->CreateTerm(getter_AddRefs(searchTerm));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ searchTerm->SetAttrib(nsMsgSearchAttrib::JunkScoreOrigin);
+ searchTerm->SetOp(nsMsgSearchOp::Isnt);
+ searchTerm->SetBooleanAnd(true);
+
+ nsCOMPtr<nsIMsgSearchValue> searchValue;
+ searchTerm->GetValue(getter_AddRefs(searchValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+ searchValue->SetAttrib(nsMsgSearchAttrib::JunkScoreOrigin);
+ searchValue->SetStr(NS_LITERAL_STRING("user"));
+ searchTerm->SetValue(searchValue);
+
+ searchTerms->InsertElementAt(searchTerm, count);
+
+ bool moveOnSpam, markAsReadOnSpam;
+ spamSettings->GetMoveOnSpam(&moveOnSpam);
+ if (moveOnSpam)
+ {
+ nsCString spamFolderURI;
+ rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI));
+ if (NS_SUCCEEDED(rv) && (!spamFolderURI.IsEmpty()))
+ {
+ nsCOMPtr <nsIMsgRuleAction> moveAction;
+ rv = newFilter->CreateAction(getter_AddRefs(moveAction));
+ if (NS_SUCCEEDED(rv))
+ {
+ moveAction->SetType(nsMsgFilterAction::MoveToFolder);
+ moveAction->SetTargetFolderUri(spamFolderURI);
+ newFilter->AppendAction(moveAction);
+ }
+ }
+ }
+ spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam);
+ if (markAsReadOnSpam)
+ {
+ nsCOMPtr <nsIMsgRuleAction> markAsReadAction;
+ rv = newFilter->CreateAction(getter_AddRefs(markAsReadAction));
+ if (NS_SUCCEEDED(rv))
+ {
+ markAsReadAction->SetType(nsMsgFilterAction::MarkRead);
+ newFilter->AppendAction(markAsReadAction);
+ }
+ }
+ filterList->InsertFilterAt(0, newFilter);
+ }
+
+ rv = serverFilterList->GetFilterNamed(noFilterName,
+ getter_AddRefs(newFilter));
+ if (newFilter && serverFilterTrustFlags & nsISpamSettings::TRUST_NEGATIVES)
+ {
+ newFilter->SetTemporary(true);
+ filterList->InsertFilterAt(0, newFilter);
+ }
+
+ return rv;
+}
+
+nsresult
+nsMsgIncomingServer::ConfigureTemporaryReturnReceiptsFilter(nsIMsgFilterList *filterList)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = accountMgr->GetFirstIdentityForServer(this, getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // this can return success and a null identity...
+
+ bool useCustomPrefs = false;
+ int32_t incorp = nsIMsgMdnGenerator::eIncorporateInbox;
+ NS_ENSURE_TRUE(identity, NS_ERROR_NULL_POINTER);
+
+ identity->GetBoolAttribute("use_custom_prefs", &useCustomPrefs);
+ if (useCustomPrefs)
+ rv = GetIntValue("incorporate_return_receipt", &incorp);
+ else
+ {
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ prefs->GetIntPref("mail.incorporate.return_receipt", &incorp);
+ }
+
+ bool enable = (incorp == nsIMsgMdnGenerator::eIncorporateSent);
+
+ // this is a temporary, internal mozilla filter
+ // it will not show up in the UI, it will not be written to disk
+ NS_NAMED_LITERAL_STRING(internalReturnReceiptFilterName, "mozilla-temporary-internal-MDN-receipt-filter");
+
+ nsCOMPtr<nsIMsgFilter> newFilter;
+ rv = filterList->GetFilterNamed(internalReturnReceiptFilterName,
+ getter_AddRefs(newFilter));
+ if (newFilter)
+ newFilter->SetEnabled(enable);
+ else if (enable)
+ {
+ nsCString actionTargetFolderUri;
+ rv = identity->GetFccFolder(actionTargetFolderUri);
+ if (!actionTargetFolderUri.IsEmpty())
+ {
+ filterList->CreateFilter(internalReturnReceiptFilterName,
+ getter_AddRefs(newFilter));
+ if (newFilter)
+ {
+ newFilter->SetEnabled(true);
+ // this internal filter is temporary
+ // and should not show up in the UI or be written to disk
+ newFilter->SetTemporary(true);
+
+ nsCOMPtr<nsIMsgSearchTerm> term;
+ nsCOMPtr<nsIMsgSearchValue> value;
+
+ rv = newFilter->CreateTerm(getter_AddRefs(term));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = term->GetValue(getter_AddRefs(value));
+ if (NS_SUCCEEDED(rv))
+ {
+ // we need to use OtherHeader + 1 so nsMsgFilter::GetTerm will
+ // return our custom header.
+ value->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1);
+ value->SetStr(NS_LITERAL_STRING("multipart/report"));
+ term->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1);
+ term->SetOp(nsMsgSearchOp::Contains);
+ term->SetBooleanAnd(true);
+ term->SetArbitraryHeader(NS_LITERAL_CSTRING("Content-Type"));
+ term->SetValue(value);
+ newFilter->AppendTerm(term);
+ }
+ }
+ rv = newFilter->CreateTerm(getter_AddRefs(term));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = term->GetValue(getter_AddRefs(value));
+ if (NS_SUCCEEDED(rv))
+ {
+ // XXX todo
+ // determine if ::OtherHeader is the best way to do this.
+ // see nsMsgSearchOfflineMail::MatchTerms()
+ value->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1);
+ value->SetStr(NS_LITERAL_STRING("disposition-notification"));
+ term->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1);
+ term->SetOp(nsMsgSearchOp::Contains);
+ term->SetBooleanAnd(true);
+ term->SetArbitraryHeader(NS_LITERAL_CSTRING("Content-Type"));
+ term->SetValue(value);
+ newFilter->AppendTerm(term);
+ }
+ }
+ nsCOMPtr<nsIMsgRuleAction> filterAction;
+ rv = newFilter->CreateAction(getter_AddRefs(filterAction));
+ if (NS_SUCCEEDED(rv))
+ {
+ filterAction->SetType(nsMsgFilterAction::MoveToFolder);
+ filterAction->SetTargetFolderUri(actionTargetFolderUri);
+ newFilter->AppendAction(filterAction);
+ filterList->InsertFilterAt(0, newFilter);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::ClearTemporaryReturnReceiptsFilter()
+{
+ if (mFilterList)
+ {
+ nsCOMPtr<nsIMsgFilter> mdnFilter;
+ nsresult rv = mFilterList->GetFilterNamed(NS_LITERAL_STRING("mozilla-temporary-internal-MDN-receipt-filter"),
+ getter_AddRefs(mdnFilter));
+ if (NS_SUCCEEDED(rv) && mdnFilter)
+ return mFilterList->RemoveFilter(mdnFilter);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetMsgFolderFromURI(nsIMsgFolder *aFolderResource, const nsACString& aURI, nsIMsgFolder **aFolder)
+{
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_TRUE(rootMsgFolder, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+ rv = rootMsgFolder->GetChildWithURI(aURI, true, true /*caseInsensitive*/, getter_AddRefs(msgFolder));
+ if (NS_FAILED(rv) || !msgFolder)
+ msgFolder = aFolderResource;
+ NS_IF_ADDREF(*aFolder = msgFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetSpamSettings(nsISpamSettings **aSpamSettings)
+{
+ NS_ENSURE_ARG_POINTER(aSpamSettings);
+
+ nsAutoCString spamActionTargetAccount;
+ GetCharValue("spamActionTargetAccount", spamActionTargetAccount);
+ if (spamActionTargetAccount.IsEmpty())
+ {
+ GetServerURI(spamActionTargetAccount);
+ SetCharValue("spamActionTargetAccount", spamActionTargetAccount);
+ }
+
+ if (!mSpamSettings) {
+ nsresult rv;
+ mSpamSettings = do_CreateInstance(NS_SPAMSETTINGS_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mSpamSettings->Initialize(this);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ NS_ADDREF(*aSpamSettings = mSpamSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetSpamFilterPlugin(nsIMsgFilterPlugin **aFilterPlugin)
+{
+ NS_ENSURE_ARG_POINTER(aFilterPlugin);
+ if (!mFilterPlugin)
+ {
+ nsresult rv;
+ mFilterPlugin = do_GetService("@mozilla.org/messenger/filter-plugin;1?name=bayesianfilter", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*aFilterPlugin = mFilterPlugin);
+ return NS_OK;
+}
+
+// get all the servers that defer to the account for the passed in server. Note that
+// destServer may not be "this"
+nsresult nsMsgIncomingServer::GetDeferredServers(nsIMsgIncomingServer *destServer, nsCOMArray<nsIPop3IncomingServer>& aServers)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager
+ = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgAccount> thisAccount;
+ accountManager->FindAccountForServer(destServer, getter_AddRefs(thisAccount));
+ if (thisAccount)
+ {
+ nsCOMPtr<nsIArray> allServers;
+ nsCString accountKey;
+ thisAccount->GetKey(accountKey);
+ accountManager->GetAllServers(getter_AddRefs(allServers));
+ if (allServers)
+ {
+ uint32_t serverCount;
+ allServers->GetLength(&serverCount);
+ for (uint32_t i = 0; i < serverCount; i++)
+ {
+ nsCOMPtr<nsIPop3IncomingServer> server(do_QueryElementAt(allServers, i));
+ if (server)
+ {
+ nsCString deferredToAccount;
+ server->GetDeferredToAccount(deferredToAccount);
+ if (deferredToAccount.Equals(accountKey))
+ aServers.AppendElement(server);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetIsDeferredTo(bool *aIsDeferredTo)
+{
+ NS_ENSURE_ARG_POINTER(aIsDeferredTo);
+ nsCOMPtr<nsIMsgAccountManager> accountManager
+ = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID);
+ if (accountManager)
+ {
+ nsCOMPtr <nsIMsgAccount> thisAccount;
+ accountManager->FindAccountForServer(this, getter_AddRefs(thisAccount));
+ if (thisAccount)
+ {
+ nsCOMPtr<nsIArray> allServers;
+ nsCString accountKey;
+ thisAccount->GetKey(accountKey);
+ accountManager->GetAllServers(getter_AddRefs(allServers));
+ if (allServers)
+ {
+ uint32_t serverCount;
+ allServers->GetLength(&serverCount);
+ for (uint32_t i = 0; i < serverCount; i++)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> server (do_QueryElementAt(allServers, i));
+ if (server)
+ {
+ nsCString deferredToAccount;
+ server->GetCharValue("deferred_to_account", deferredToAccount);
+ if (deferredToAccount.Equals(accountKey))
+ {
+ *aIsDeferredTo = true;
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+ }
+ *aIsDeferredTo = false;
+ return NS_OK;
+}
+
+const long kMaxDownloadTableSize = 500;
+
+// hash the concatenation of the message-id and subject as the hash table key,
+// and store the arrival index as the value. To limit the size of the hash table,
+// we just throw out ones with a lower ordinal value than the cut-off point.
+NS_IMETHODIMP nsMsgIncomingServer::IsNewHdrDuplicate(nsIMsgDBHdr *aNewHdr, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+ *aResult = false;
+
+ // If the message has been partially downloaded, the message should not
+ // be considered a duplicated message. See bug 714090.
+ uint32_t flags;
+ aNewHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial)
+ return NS_OK;
+
+ nsAutoCString strHashKey;
+ nsCString messageId, subject;
+ aNewHdr->GetMessageId(getter_Copies(messageId));
+ strHashKey.Append(messageId);
+ aNewHdr->GetSubject(getter_Copies(subject));
+ // err on the side of caution and ignore messages w/o subject or messageid.
+ if (subject.IsEmpty() || messageId.IsEmpty())
+ return NS_OK;
+ strHashKey.Append(subject);
+ int32_t hashValue = 0;
+ m_downloadedHdrs.Get(strHashKey, &hashValue);
+ if (hashValue)
+ *aResult = true;
+ else
+ {
+ // we store the current size of the hash table as the hash
+ // value - this allows us to delete older entries.
+ m_downloadedHdrs.Put(strHashKey, ++m_numMsgsDownloaded);
+ // Check if hash table is larger than some reasonable size
+ // and if is it, iterate over hash table deleting messages
+ // with an arrival index < number of msgs downloaded - half the reasonable size.
+ if (m_downloadedHdrs.Count() >= kMaxDownloadTableSize) {
+ for (auto iter = m_downloadedHdrs.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.Data() < m_numMsgsDownloaded - kMaxDownloadTableSize/2) {
+ iter.Remove();
+ } else if (m_downloadedHdrs.Count() <= kMaxDownloadTableSize/2) {
+ break;
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetForcePropertyEmpty(const char *aPropertyName, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsAutoCString nameEmpty(aPropertyName);
+ nameEmpty.Append(NS_LITERAL_CSTRING(".empty"));
+ nsCString value;
+ GetCharValue(nameEmpty.get(), value);
+ *_retval = value.EqualsLiteral("true");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetForcePropertyEmpty(const char *aPropertyName, bool aValue)
+{
+ nsAutoCString nameEmpty(aPropertyName);
+ nameEmpty.Append(NS_LITERAL_CSTRING(".empty"));
+ return SetCharValue(nameEmpty.get(),
+ aValue ? NS_LITERAL_CSTRING("true") : NS_LITERAL_CSTRING(""));
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetSortOrder(int32_t* aSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = 100000000;
+ return NS_OK;
+}
diff --git a/mailnews/base/util/nsMsgIncomingServer.h b/mailnews/base/util/nsMsgIncomingServer.h
new file mode 100644
index 000000000..58502e224
--- /dev/null
+++ b/mailnews/base/util/nsMsgIncomingServer.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgIncomingServer_h__
+#define nsMsgIncomingServer_h__
+
+#include "nsIMsgIncomingServer.h"
+#include "nsIPrefBranch.h"
+#include "nsIMsgFilterList.h"
+#include "msgCore.h"
+#include "nsIMsgFolder.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsWeakReference.h"
+#include "nsIMsgDatabase.h"
+#include "nsISpamSettings.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsDataHashtable.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIObserver.h"
+
+class nsIMsgFolderCache;
+class nsIMsgProtocolInfo;
+
+/*
+ * base class for nsIMsgIncomingServer - derive your class from here
+ * if you want to get some free implementation
+ *
+ * this particular implementation is not meant to be used directly.
+ */
+
+class NS_MSG_BASE nsMsgIncomingServer : public nsIMsgIncomingServer,
+ public nsSupportsWeakReference,
+ public nsIObserver
+{
+ public:
+ nsMsgIncomingServer();
+ nsresult Init();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGINCOMINGSERVER
+ NS_DECL_NSIOBSERVER
+
+protected:
+ virtual ~nsMsgIncomingServer();
+ nsCString m_serverKey;
+
+ // Sets m_password, if password found. Can return NS_ERROR_ABORT if the
+ // user cancels the master password dialog.
+ nsresult GetPasswordWithoutUI();
+
+ nsresult ConfigureTemporaryReturnReceiptsFilter(nsIMsgFilterList *filterList);
+ nsresult ConfigureTemporaryServerSpamFilters(nsIMsgFilterList *filterList);
+
+ nsCOMPtr <nsIMsgFolder> m_rootFolder;
+ nsCOMPtr <nsIMsgDownloadSettings> m_downloadSettings;
+
+ // For local servers, where we put messages. For imap/pop3, where we store
+ // offline messages.
+ nsCOMPtr <nsIMsgPluggableStore> m_msgStore;
+
+ /// Helper routine to create local folder on disk if it doesn't exist
+ /// under the account's rootFolder.
+ nsresult CreateLocalFolder(const nsAString& folderName);
+
+ static nsresult GetDeferredServers(nsIMsgIncomingServer *destServer, nsCOMArray<nsIPop3IncomingServer>& aServers);
+
+ nsresult CreateRootFolder();
+ virtual nsresult CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder) = 0;
+
+ nsresult InternalSetHostName(const nsACString& aHostname, const char * prefName);
+
+ nsCOMPtr <nsIFile> mFilterFile;
+ nsCOMPtr <nsIMsgFilterList> mFilterList;
+ nsCOMPtr <nsIMsgFilterList> mEditableFilterList;
+ nsCOMPtr<nsIPrefBranch> mPrefBranch;
+ nsCOMPtr<nsIPrefBranch> mDefPrefBranch;
+
+ // these allow us to handle duplicate incoming messages, e.g. delete them.
+ nsDataHashtable<nsCStringHashKey,int32_t> m_downloadedHdrs;
+ int32_t m_numMsgsDownloaded;
+
+private:
+ uint32_t m_biffState;
+ bool m_serverBusy;
+ nsCOMPtr <nsISpamSettings> mSpamSettings;
+ nsCOMPtr<nsIMsgFilterPlugin> mFilterPlugin; // XXX should be a list
+
+protected:
+ nsCString m_password;
+ bool m_canHaveFilters;
+ bool m_displayStartupPage;
+ bool mPerformingBiff;
+};
+
+#endif // nsMsgIncomingServer_h__
diff --git a/mailnews/base/util/nsMsgKeyArray.cpp b/mailnews/base/util/nsMsgKeyArray.cpp
new file mode 100644
index 000000000..e04dc948b
--- /dev/null
+++ b/mailnews/base/util/nsMsgKeyArray.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgKeyArray.h"
+#include "nsMemory.h"
+
+NS_IMPL_ISUPPORTS(nsMsgKeyArray, nsIMsgKeyArray)
+
+nsMsgKeyArray::nsMsgKeyArray()
+{
+#ifdef DEBUG
+ m_sorted = false;
+#endif
+}
+
+nsMsgKeyArray::~nsMsgKeyArray()
+{
+}
+
+NS_IMETHODIMP nsMsgKeyArray::Sort()
+{
+#ifdef DEBUG
+ m_sorted = true;
+#endif
+ m_keys.Sort();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgKeyArray::GetKeyAt(int32_t aIndex, nsMsgKey *aKey)
+{
+ NS_ENSURE_ARG_POINTER(aKey);
+ *aKey = m_keys[aIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgKeyArray::GetLength(uint32_t *aLength)
+{
+ NS_ENSURE_ARG_POINTER(aLength);
+ *aLength = m_keys.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgKeyArray::SetCapacity(uint32_t aCapacity)
+{
+ m_keys.SetCapacity(aCapacity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgKeyArray::AppendElement(nsMsgKey aKey)
+{
+#ifdef DEBUG
+ NS_ASSERTION(!m_sorted || m_keys.Length() == 0 ||
+ aKey > m_keys[m_keys.Length() - 1],
+ "Inserting a new key at wrong position in a sorted key list!");
+#endif
+ m_keys.AppendElement(aKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgKeyArray::InsertElementSorted(nsMsgKey aKey)
+{
+ // Ths function should be removed after interfaces are not frozen for TB38.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgKeyArray::GetArray(uint32_t *aCount, nsMsgKey **aKeys)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+ NS_ENSURE_ARG_POINTER(aKeys);
+ *aCount = m_keys.Length();
+ *aKeys =
+ (nsMsgKey *) nsMemory::Clone(m_keys.begin(),
+ m_keys.Length() * sizeof(nsMsgKey));
+ return (*aKeys) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
diff --git a/mailnews/base/util/nsMsgKeyArray.h b/mailnews/base/util/nsMsgKeyArray.h
new file mode 100644
index 000000000..02c952b77
--- /dev/null
+++ b/mailnews/base/util/nsMsgKeyArray.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgKeyArray_h__
+#define nsMsgKeyArray_h__
+
+#include "nsIMsgKeyArray.h"
+#include "nsTArray.h"
+
+/*
+ * This class is a thin wrapper around an nsTArray<nsMsgKey>
+ */
+class nsMsgKeyArray : public nsIMsgKeyArray
+{
+public:
+ nsMsgKeyArray();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGKEYARRAY
+
+ nsTArray<nsMsgKey> m_keys;
+
+private:
+ virtual ~nsMsgKeyArray();
+
+#ifdef DEBUG
+ bool m_sorted;
+#endif
+};
+
+#endif
diff --git a/mailnews/base/util/nsMsgKeySet.cpp b/mailnews/base/util/nsMsgKeySet.cpp
new file mode 100644
index 000000000..427fb61af
--- /dev/null
+++ b/mailnews/base/util/nsMsgKeySet.cpp
@@ -0,0 +1,1520 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "prlog.h"
+
+#include "MailNewsTypes.h"
+#include "nsMsgKeySet.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsTArray.h"
+#include "nsMemory.h"
+#include <ctype.h>
+
+#if defined(DEBUG_seth_) || defined(DEBUG_sspitzer_)
+#define DEBUG_MSGKEYSET 1
+#endif
+
+/* A compressed encoding for sets of article. This is usually for lines from
+ the newsrc, which have article lists like
+
+ 1-29627,29635,29658,32861-32863
+
+ so the data has these properties:
+
+ - strictly increasing
+ - large subsequences of monotonically increasing ranges
+ - gaps in the set are usually small, but not always
+ - consecutive ranges tend to be large
+
+ The biggest win is to run-length encode the data, storing ranges as two
+ numbers (start+length or start,end). We could also store each number as a
+ delta from the previous number for further compression, but that gets kind
+ of tricky, since there are no guarentees about the sizes of the gaps, and
+ we'd have to store variable-length words.
+
+ Current data format:
+
+ DATA := SIZE [ CHUNK ]*
+ CHUNK := [ RANGE | VALUE ]
+ RANGE := -LENGTH START
+ START := VALUE
+ LENGTH := int32_t
+ VALUE := a literal positive integer, for now
+ it could also be an offset from the previous value.
+ LENGTH could also perhaps be a less-than-32-bit quantity,
+ at least most of the time.
+
+ Lengths of CHUNKs are stored negative to distinguish the beginning of
+ a chunk from a literal: negative means two-word sequence, positive
+ means one-word sequence.
+
+ 0 represents a literal 0, but should not occur, and should never occur
+ except in the first position.
+
+ A length of -1 won't occur either, except temporarily - a sequence of
+ two elements is represented as two literals, since they take up the same
+ space.
+
+ Another optimization we make is to notice that we typically ask the
+ question ``is N a member of the set'' for increasing values of N. So the
+ set holds a cache of the last value asked for, and can simply resume the
+ search from there. */
+
+nsMsgKeySet::nsMsgKeySet(/* MSG_NewsHost* host*/)
+{
+ MOZ_COUNT_CTOR(nsMsgKeySet);
+ m_cached_value = -1;
+ m_cached_value_index = 0;
+ m_length = 0;
+ m_data_size = 10;
+ m_data = (int32_t *) PR_Malloc (sizeof (int32_t) * m_data_size);
+#ifdef NEWSRC_DOES_HOST_STUFF
+ m_host = host;
+#endif
+}
+
+
+nsMsgKeySet::~nsMsgKeySet()
+{
+ MOZ_COUNT_DTOR(nsMsgKeySet);
+ PR_FREEIF(m_data);
+}
+
+
+bool nsMsgKeySet::Grow()
+{
+ int32_t new_size;
+ int32_t *new_data;
+ new_size = m_data_size * 2;
+ new_data = (int32_t *) PR_REALLOC (m_data, sizeof (int32_t) * new_size);
+ if (! new_data)
+ return false;
+ m_data_size = new_size;
+ m_data = new_data;
+ return true;
+}
+
+
+nsMsgKeySet::nsMsgKeySet(const char* numbers /* , MSG_NewsHost* host */)
+{
+ int32_t *head, *tail, *end;
+ MOZ_COUNT_CTOR(nsMsgKeySet);
+
+#ifdef NEWSRC_DOES_HOST_STUFF
+ m_host = host;
+#endif
+ m_cached_value = -1;
+ m_cached_value_index = 0;
+ m_length = 0;
+ m_data_size = 10;
+ m_data = (int32_t *) PR_Malloc (sizeof (int32_t) * m_data_size);
+ if (!m_data) return;
+
+ head = m_data;
+ tail = head;
+ end = head + m_data_size;
+
+ if(!numbers) {
+ return;
+ }
+
+ while (isspace (*numbers)) numbers++;
+ while (*numbers) {
+ int32_t from = 0;
+ int32_t to;
+
+ if (tail >= end - 4) {
+ /* out of room! */
+ int32_t tailo = tail - head;
+ if (!Grow()) {
+ PR_FREEIF(m_data);
+ return;
+ }
+ /* data may have been relocated */
+ head = m_data;
+ tail = head + tailo;
+ end = head + m_data_size;
+ }
+
+ while (isspace(*numbers)) numbers++;
+ if (*numbers && !isdigit(*numbers)) {
+ break; /* illegal character */
+ }
+ while (isdigit (*numbers)) {
+ from = (from * 10) + (*numbers++ - '0');
+ }
+ while (isspace(*numbers)) numbers++;
+ if (*numbers != '-') {
+ to = from;
+ } else {
+ to = 0;
+ numbers++;
+ while (*numbers >= '0' && *numbers <= '9')
+ to = (to * 10) + (*numbers++ - '0');
+ while (isspace(*numbers)) numbers++;
+ }
+
+ if (to < from) to = from; /* illegal */
+
+ /* This is a hack - if the newsrc file specifies a range 1-x as
+ being read, we internally pretend that article 0 is read as well.
+ (But if only 2-x are read, then 0 is not read.) This is needed
+ because some servers think that article 0 is an article (I think)
+ but some news readers (including Netscape 1.1) choke if the .newsrc
+ file has lines beginning with 0... ### */
+ if (from == 1) from = 0;
+
+ if (to == from) {
+ /* Write it as a literal */
+ *tail = from;
+ tail++;
+ } else /* Write it as a range. */ {
+ *tail = -(to - from);
+ tail++;
+ *tail = from;
+ tail++;
+ }
+
+ while (*numbers == ',' || isspace(*numbers)) {
+ numbers++;
+ }
+ }
+
+ m_length = tail - head; /* size of data */
+}
+
+
+
+nsMsgKeySet*
+nsMsgKeySet::Create(/*MSG_NewsHost* host*/)
+{
+ nsMsgKeySet* set = new nsMsgKeySet(/* host */);
+ if (set && set->m_data == NULL) {
+ delete set;
+ set = NULL;
+ }
+ return set;
+}
+
+
+nsMsgKeySet*
+nsMsgKeySet::Create(const char* value /* , MSG_NewsHost* host */)
+{
+#ifdef DEBUG_MSGKEYSET
+ printf("create from %s\n",value);
+#endif
+
+ nsMsgKeySet* set = new nsMsgKeySet(value /* , host */);
+ if (set && set->m_data == NULL) {
+ delete set;
+ set = NULL;
+ }
+ return set;
+}
+
+
+
+/* Returns the lowest non-member of the set greater than 0.
+ */
+int32_t
+nsMsgKeySet::FirstNonMember ()
+{
+ if (m_length <= 0) {
+ return 1;
+ } else if(m_data[0] < 0 && m_data[1] != 1 && m_data[1] != 0) {
+ /* first range not equal to 0 or 1, always return 1 */
+ return 1;
+ } else if (m_data[0] < 0) {
+ /* it's a range */
+ /* If there is a range [N-M] we can presume that M+1 is not in the
+ set. */
+ return (m_data[1] - m_data[0] + 1);
+ } else {
+ /* it's a literal */
+ if (m_data[0] == 1) {
+ /* handle "1,..." */
+ if (m_length > 1 && m_data[1] == 2) {
+ /* This is "1,2,M-N,..." or "1,2,M,..." where M >= 4. Note
+ that M will never be 3, because in that case we would have
+ started with a range: "1-3,..." */
+ return 3;
+ } else {
+ return 2; /* handle "1,M-N,.." or "1,M,..."
+ where M >= 3; */
+ }
+ }
+ else if (m_data[0] == 0) {
+ /* handle "0,..." */
+ if (m_length > 1 && m_data[1] == 1) {
+ /* this is 0,1, (see above) */
+ return 2;
+ }
+ else {
+ return 1;
+ }
+
+ } else {
+ /* handle "M,..." where M >= 2. */
+ return 1;
+ }
+ }
+}
+
+
+nsresult
+nsMsgKeySet::Output(char **outputStr)
+{
+ NS_ENSURE_ARG(outputStr);
+ int32_t size;
+ int32_t *head;
+ int32_t *tail;
+ int32_t *end;
+ int32_t s_size;
+ char *s_head;
+ char *s, *s_end;
+ int32_t last_art = -1;
+
+ *outputStr = nullptr;
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ s_size = (size * 12) + 10; // dmb - try to make this allocation get used at least once.
+ s_head = (char *) moz_xmalloc(s_size);
+ if (! s_head) return NS_ERROR_OUT_OF_MEMORY;
+
+ s_head[0] = '\0'; // otherwise, s_head will contain garbage.
+ s = s_head;
+ s_end = s + s_size;
+
+ while (tail < end) {
+ int32_t from;
+ int32_t to;
+
+ if (s > (s_end - (12 * 2 + 10))) { /* 12 bytes for each number (enough
+ for "2147483647" aka 2^31-1),
+ plus 10 bytes of slop. */
+ int32_t so = s - s_head;
+ s_size += 200;
+ char* tmp = (char *) moz_xmalloc(s_size);
+ if (tmp) PL_strcpy(tmp, s_head);
+ free(s_head);
+ s_head = tmp;
+ if (!s_head) return NS_ERROR_OUT_OF_MEMORY;
+ s = s_head + so;
+ s_end = s_head + s_size;
+ }
+
+ if (*tail < 0) {
+ /* it's a range */
+ from = tail[1];
+ to = from + (-(tail[0]));
+ tail += 2;
+ }
+ else /* it's a literal */
+ {
+ from = *tail;
+ to = from;
+ tail++;
+ }
+ if (from == 0) {
+ from = 1; /* See 'hack' comment above ### */
+ }
+ if (from <= last_art) from = last_art + 1;
+ if (from <= to) {
+ if (from < to) {
+ PR_snprintf(s, s_end - s, "%lu-%lu,", from, to);
+ } else {
+ PR_snprintf(s, s_end - s, "%lu,", from);
+ }
+ s += PL_strlen(s);
+ last_art = to;
+ }
+ }
+ if (last_art >= 0) {
+ s--; /* Strip off the last ',' */
+ }
+
+ *s = 0;
+
+ *outputStr = s_head;
+ return NS_OK;
+}
+
+int32_t
+nsMsgKeySet::GetLastMember()
+{
+ if (m_length > 1)
+ {
+ int32_t nextToLast = m_data[m_length - 2];
+ if (nextToLast < 0) // is range at end?
+ {
+ int32_t last = m_data[m_length - 1];
+ return (-nextToLast + last - 1);
+ }
+ else // no, so last number must be last member
+ {
+ return m_data[m_length - 1];
+ }
+ }
+ else if (m_length == 1)
+ return m_data[0]; // must be only 1 read.
+ else
+ return 0;
+}
+
+void nsMsgKeySet::SetLastMember(int32_t newHighWaterMark)
+{
+ if (newHighWaterMark < GetLastMember())
+ {
+ while (true)
+ {
+ if (m_length > 1)
+ {
+ int32_t nextToLast = m_data[m_length - 2];
+ int32_t curHighWater;
+ if (nextToLast < 0) // is range at end?
+ {
+ int32_t rangeStart = m_data[m_length - 1];
+ int32_t rangeLength = -nextToLast;
+ curHighWater = (rangeLength + rangeStart - 1);
+ if (curHighWater > newHighWaterMark)
+ {
+ if (rangeStart > newHighWaterMark)
+ {
+ m_length -= 2; // throw away whole range
+ }
+ else if (rangeStart == newHighWaterMark)
+ {
+ // turn range into single element.
+ m_data[m_length - 2] = newHighWaterMark;
+ m_length--;
+ break;
+ }
+ else // just shorten range
+ {
+ m_data[m_length - 2] = -(newHighWaterMark - rangeStart);
+ break;
+ }
+ }
+ else {
+ // prevent the infinite loop
+ // see bug #13062
+ break;
+ }
+ }
+ else if (m_data[m_length - 1] > newHighWaterMark) // no, so last number must be last member
+ {
+ m_length--;
+ }
+ else
+ break;
+ }
+ else
+ break;
+ }
+ // well, the whole range is probably invalid, because the server probably re-ordered ids,
+ // but what can you do?
+#ifdef NEWSRC_DOES_HOST_STUFF
+ if (m_host)
+ m_host->MarkDirty();
+#endif
+ }
+}
+
+int32_t
+nsMsgKeySet::GetFirstMember()
+{
+ if (m_length > 1)
+ {
+ int32_t first = m_data[0];
+ if (first < 0) // is range at start?
+ {
+ int32_t second = m_data[1];
+ return (second);
+ }
+ else // no, so first number must be first member
+ {
+ return m_data[0];
+ }
+ }
+ else if (m_length == 1)
+ return m_data[0]; // must be only 1 read.
+ else
+ return 0;
+}
+
+/* Re-compresses a `nsMsgKeySet' object.
+
+ The assumption is made that the `nsMsgKeySet' is syntactically correct
+ (all ranges have a length of at least 1, and all values are non-
+ decreasing) but will optimize the compression, for example, merging
+ consecutive literals or ranges into one range.
+
+ Returns true if successful, false if there wasn't enough memory to
+ allocate scratch space.
+
+ #### This should be changed to modify the buffer in place.
+
+ Also note that we never call Optimize() unless we actually changed
+ something, so it's a great place to tell the MSG_NewsHost* that something
+ changed.
+ */
+bool
+nsMsgKeySet::Optimize()
+{
+ int32_t input_size;
+ int32_t output_size;
+ int32_t *input_tail;
+ int32_t *output_data;
+ int32_t *output_tail;
+ int32_t *input_end;
+ int32_t *output_end;
+
+ input_size = m_length;
+ output_size = input_size + 1;
+ input_tail = m_data;
+ output_data = (int32_t *) PR_Malloc (sizeof (int32_t) * output_size);
+ if (!output_data) return false;
+
+ output_tail = output_data;
+ input_end = input_tail + input_size;
+ output_end = output_data + output_size;
+
+ /* We're going to modify the set, so invalidate the cache. */
+ m_cached_value = -1;
+
+ while (input_tail < input_end) {
+ int32_t from, to;
+ bool range_p = (*input_tail < 0);
+
+ if (range_p) {
+ /* it's a range */
+ from = input_tail[1];
+ to = from + (-(input_tail[0]));
+
+ /* Copy it over */
+ *output_tail++ = *input_tail++;
+ *output_tail++ = *input_tail++;
+ } else {
+ /* it's a literal */
+ from = *input_tail;
+ to = from;
+
+ /* Copy it over */
+ *output_tail++ = *input_tail++;
+ }
+ NS_ASSERTION(output_tail < output_end, "invalid end of output string");
+ if (output_tail >= output_end) {
+ PR_Free(output_data);
+ return false;
+ }
+
+ /* As long as this chunk is followed by consecutive chunks,
+ keep extending it. */
+ while (input_tail < input_end &&
+ ((*input_tail > 0 && /* literal... */
+ *input_tail == to + 1) || /* ...and consecutive, or */
+ (*input_tail <= 0 && /* range... */
+ input_tail[1] == to + 1)) /* ...and consecutive. */
+ ) {
+ if (! range_p) {
+ /* convert the literal to a range. */
+ output_tail++;
+ output_tail [-2] = 0;
+ output_tail [-1] = from;
+ range_p = true;
+ }
+
+ if (*input_tail > 0) { /* literal */
+ output_tail[-2]--; /* increase length by 1 */
+ to++;
+ input_tail++;
+ } else {
+ int32_t L2 = (- *input_tail) + 1;
+ output_tail[-2] -= L2; /* increase length by N */
+ to += L2;
+ input_tail += 2;
+ }
+ }
+ }
+
+ PR_Free (m_data);
+ m_data = output_data;
+ m_data_size = output_size;
+ m_length = output_tail - output_data;
+
+ /* One last pass to turn [N - N+1] into [N, N+1]. */
+ output_tail = output_data;
+ output_end = output_tail + m_length;
+ while (output_tail < output_end) {
+ if (*output_tail < 0) {
+ /* it's a range */
+ if (output_tail[0] == -1) {
+ output_tail[0] = output_tail[1];
+ output_tail[1]++;
+ }
+ output_tail += 2;
+ } else {
+ /* it's a literal */
+ output_tail++;
+ }
+ }
+
+#ifdef NEWSRC_DOES_HOST_STUFF
+ if (m_host) m_host->MarkDirty();
+#endif
+ return true;
+}
+
+
+
+bool
+nsMsgKeySet::IsMember(int32_t number)
+{
+ bool value = false;
+ int32_t size;
+ int32_t *head;
+ int32_t *tail;
+ int32_t *end;
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ /* If there is a value cached, and that value is smaller than the
+ value we're looking for, skip forward that far. */
+ if (m_cached_value > 0 &&
+ m_cached_value < number) {
+ tail += m_cached_value_index;
+ }
+
+ while (tail < end) {
+ if (*tail < 0) {
+ /* it's a range */
+ int32_t from = tail[1];
+ int32_t to = from + (-(tail[0]));
+ if (from > number) {
+ /* This range begins after the number - we've passed it. */
+ value = false;
+ goto DONE;
+ } else if (to >= number) {
+ /* In range. */
+ value = true;
+ goto DONE;
+ } else {
+ tail += 2;
+ }
+ }
+ else {
+ /* it's a literal */
+ if (*tail == number) {
+ /* bang */
+ value = true;
+ goto DONE;
+ } else if (*tail > number) {
+ /* This literal is after the number - we've passed it. */
+ value = false;
+ goto DONE;
+ } else {
+ tail++;
+ }
+ }
+ }
+
+DONE:
+ /* Store the position of this chunk for next time. */
+ m_cached_value = number;
+ m_cached_value_index = tail - head;
+
+ return value;
+}
+
+
+int
+nsMsgKeySet::Add(int32_t number)
+{
+ int32_t size;
+ int32_t *head;
+ int32_t *tail;
+ int32_t *end;
+
+#ifdef DEBUG_MSGKEYSET
+ printf("add %d\n",number);
+#endif
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ NS_ASSERTION (number >= 0, "can't have negative items");
+ if (number < 0)
+ return 0;
+
+ /* We're going to modify the set, so invalidate the cache. */
+ m_cached_value = -1;
+
+ while (tail < end) {
+ if (*tail < 0) {
+ /* it's a range */
+ int32_t from = tail[1];
+ int32_t to = from + (-(tail[0]));
+
+ if (from <= number && to >= number) {
+ /* This number is already present - we don't need to do
+ anything. */
+ return 0;
+ }
+
+ if (to > number) {
+ /* We have found the point before which the new number
+ should be inserted. */
+ break;
+ }
+
+ tail += 2;
+ } else {
+ /* it's a literal */
+ if (*tail == number) {
+ /* This number is already present - we don't need to do
+ anything. */
+ return 0;
+ }
+
+ if (*tail > number) {
+ /* We have found the point before which the new number
+ should be inserted. */
+ break;
+ }
+
+ tail++;
+ }
+ }
+
+ /* At this point, `tail' points to a position in the set which represents
+ a value greater than `new'; or it is at `end'. In the interest of
+ avoiding massive duplication of code, simply insert a literal here and
+ then run the optimizer.
+ */
+ int mid = (tail - head);
+
+ if (m_data_size <= m_length + 1) {
+ int endo = end - head;
+ if (!Grow()) {
+ // out of memory
+ return -1;
+ }
+ head = m_data;
+ end = head + endo;
+ }
+
+ if (tail == end) {
+ /* at the end */
+ /* Add a literal to the end. */
+ m_data[m_length++] = number;
+ } else {
+ /* need to insert (or edit) in the middle */
+ int32_t i;
+ for (i = size; i > mid; i--) {
+ m_data[i] = m_data[i-1];
+ }
+ m_data[i] = number;
+ m_length++;
+ }
+
+ Optimize();
+ return 1;
+}
+
+
+
+int
+nsMsgKeySet::Remove(int32_t number)
+{
+ int32_t size;
+ int32_t *head;
+ int32_t *tail;
+ int32_t *end;
+#ifdef DEBUG_MSGKEYSET
+ printf("remove %d\n",number);
+#endif
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ // **** I am not sure this is a right thing to comment the following
+ // statements out. The reason for this is due to the implementation of
+ // offline save draft and template. We use faked UIDs (negative ids) for
+ // offline draft and template in order to distinguish them from real
+ // UID. David I need your help here. **** jt
+
+ // PR_ASSERT(number >= 0);
+ // if (number < 0) {
+ // return -1;
+ /// }
+
+ /* We're going to modify the set, so invalidate the cache. */
+ m_cached_value = -1;
+
+ while (tail < end) {
+ int32_t mid = (tail - m_data);
+
+ if (*tail < 0) {
+ /* it's a range */
+ int32_t from = tail[1];
+ int32_t to = from + (-(tail[0]));
+
+ if (number < from || number > to) {
+ /* Not this range */
+ tail += 2;
+ continue;
+ }
+
+ if (to == from + 1) {
+ /* If this is a range [N - N+1] and we are removing M
+ (which must be either N or N+1) replace it with a
+ literal. This reduces the length by 1. */
+ m_data[mid] = (number == from ? to : from);
+ while (++mid < m_length) {
+ m_data[mid] = m_data[mid+1];
+ }
+ m_length--;
+ Optimize();
+ return 1;
+ } else if (to == from + 2) {
+ /* If this is a range [N - N+2] and we are removing M,
+ replace it with the literals L,M (that is, either
+ (N, N+1), (N, N+2), or (N+1, N+2). The overall
+ length remains the same. */
+ m_data[mid] = from;
+ m_data[mid+1] = to;
+ if (from == number) {
+ m_data[mid] = from+1;
+ } else if (to == number) {
+ m_data[mid+1] = to-1;
+ }
+ Optimize();
+ return 1;
+ } else if (from == number) {
+ /* This number is at the beginning of a long range (meaning a
+ range which will still be long enough to remain a range.)
+ Increase start and reduce length of the range. */
+ m_data[mid]++;
+ m_data[mid+1]++;
+ Optimize();
+ return 1;
+ } else if (to == number) {
+ /* This number is at the end of a long range (meaning a range
+ which will still be long enough to remain a range.)
+ Just decrease the length of the range. */
+ m_data[mid]++;
+ Optimize();
+ return 1;
+ } else {
+ /* The number being deleted is in the middle of a range which
+ must be split. This increases overall length by 2.
+ */
+ int32_t i;
+ int endo = end - head;
+ if (m_data_size - m_length <= 2) {
+ if (!Grow())
+ // out of memory
+ return -1;
+ }
+ head = m_data;
+ end = head + endo;
+
+ for (i = m_length + 2; i > mid + 2; i--) {
+ m_data[i] = m_data[i-2];
+ }
+
+ m_data[mid] = (- (number - from - 1));
+ m_data[mid+1] = from;
+ m_data[mid+2] = (- (to - number - 1));
+ m_data[mid+3] = number + 1;
+ m_length += 2;
+
+ /* Oops, if we've ended up with a range with a 0 length,
+ which is illegal, convert it to a literal, which reduces
+ the overall length by 1. */
+ if (m_data[mid] == 0) {
+ /* first range */
+ m_data[mid] = m_data[mid+1];
+ for (i = mid + 1; i < m_length; i++) {
+ m_data[i] = m_data[i+1];
+ }
+ m_length--;
+ }
+ if (m_data[mid+2] == 0) {
+ /* second range */
+ m_data[mid+2] = m_data[mid+3];
+ for (i = mid + 3; i < m_length; i++) {
+ m_data[i] = m_data[i+1];
+ }
+ m_length--;
+ }
+ Optimize();
+ return 1;
+ }
+ } else {
+ /* it's a literal */
+ if (*tail != number) {
+ /* Not this literal */
+ tail++;
+ continue;
+ }
+
+ /* Excise this literal. */
+ m_length--;
+ while (mid < m_length) {
+ m_data[mid] = m_data[mid+1];
+ mid++;
+ }
+ Optimize();
+ return 1;
+ }
+ }
+
+ /* It wasn't here at all. */
+ return 0;
+}
+
+
+static int32_t*
+msg_emit_range(int32_t* tmp, int32_t a, int32_t b)
+{
+ if (a == b) {
+ *tmp++ = a;
+ } else {
+ NS_ASSERTION(a < b && a >= 0, "range is out of order");
+ *tmp++ = -(b - a);
+ *tmp++ = a;
+ }
+ return tmp;
+}
+
+
+int
+nsMsgKeySet::AddRange(int32_t start, int32_t end)
+{
+ int32_t tmplength;
+ int32_t* tmp;
+ int32_t* in;
+ int32_t* out;
+ int32_t* tail;
+ int32_t a;
+ int32_t b;
+ bool didit = false;
+
+ /* We're going to modify the set, so invalidate the cache. */
+ m_cached_value = -1;
+
+ NS_ASSERTION(start <= end, "invalid range");
+ if (start > end) return -1;
+
+ if (start == end) {
+ return Add(start);
+ }
+
+ tmplength = m_length + 2;
+ tmp = (int32_t*) PR_Malloc(sizeof(int32_t) * tmplength);
+
+ if (!tmp)
+ // out of memory
+ return -1;
+
+ in = m_data;
+ out = tmp;
+ tail = in + m_length;
+
+#define EMIT(x, y) out = msg_emit_range(out, x, y)
+
+ while (in < tail) {
+ // Set [a,b] to be this range.
+ if (*in < 0) {
+ b = - *in++;
+ a = *in++;
+ b += a;
+ } else {
+ a = b = *in++;
+ }
+
+ if (a <= start && b >= end) {
+ // We already have the entire range marked.
+ PR_Free(tmp);
+ return 0;
+ }
+ if (start > b + 1) {
+ // No overlap yet.
+ EMIT(a, b);
+ } else if (end < a - 1) {
+ // No overlap, and we passed it.
+ EMIT(start, end);
+ EMIT(a, b);
+ didit = true;
+ break;
+ } else {
+ // The ranges overlap. Suck this range into our new range, and
+ // keep looking for other ranges that might overlap.
+ start = start < a ? start : a;
+ end = end > b ? end : b;
+ }
+ }
+ if (!didit) EMIT(start, end);
+ while (in < tail) {
+ *out++ = *in++;
+ }
+
+#undef EMIT
+
+ PR_Free(m_data);
+ m_data = tmp;
+ m_length = out - tmp;
+ m_data_size = tmplength;
+#ifdef NEWSRC_DOES_HOST_STUFF
+ if (m_host) m_host->MarkDirty();
+#endif
+ return 1;
+}
+
+int32_t
+nsMsgKeySet::CountMissingInRange(int32_t range_start, int32_t range_end)
+{
+ int32_t count;
+ int32_t *head;
+ int32_t *tail;
+ int32_t *end;
+
+ NS_ASSERTION (range_start >= 0 && range_end >= 0 && range_end >= range_start, "invalid range");
+ if (range_start < 0 || range_end < 0 || range_end < range_start) return -1;
+
+ head = m_data;
+ tail = head;
+ end = head + m_length;
+
+ count = range_end - range_start + 1;
+
+ while (tail < end) {
+ if (*tail < 0) {
+ /* it's a range */
+ int32_t from = tail[1];
+ int32_t to = from + (-(tail[0]));
+ if (from < range_start) from = range_start;
+ if (to > range_end) to = range_end;
+
+ if (to >= from)
+ count -= (to - from + 1);
+
+ tail += 2;
+ } else {
+ /* it's a literal */
+ if (*tail >= range_start && *tail <= range_end) count--;
+ tail++;
+ }
+ NS_ASSERTION (count >= 0, "invalid count");
+ }
+ return count;
+}
+
+
+int
+nsMsgKeySet::FirstMissingRange(int32_t min, int32_t max,
+ int32_t* first, int32_t* last)
+{
+ int32_t size;
+ int32_t *head;
+ int32_t *tail;
+ int32_t *end;
+ int32_t from = 0;
+ int32_t to = 0;
+ int32_t a;
+ int32_t b;
+
+ NS_ASSERTION(first && last, "invalid parameter");
+ if (!first || !last) return -1;
+
+ *first = *last = 0;
+
+ NS_ASSERTION(min <= max && min > 0, "invalid min or max param");
+ if (min > max || min <= 0) return -1;
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ while (tail < end) {
+ a = to + 1;
+ if (*tail < 0) { /* We got a range. */
+ from = tail[1];
+ to = from + (-(tail[0]));
+ tail += 2;
+ } else {
+ from = to = tail[0];
+ tail++;
+ }
+ b = from - 1;
+ /* At this point, [a,b] is the range of unread articles just before
+ the current range of read articles [from,to]. See if this range
+ intersects the [min,max] range we were given. */
+ if (a > max) return 0; /* It's hopeless; there are none. */
+ if (a <= b && b >= min) {
+ /* Ah-hah! We found an intersection. */
+ *first = a > min ? a : min;
+ *last = b < max ? b : max;
+ return 0;
+ }
+ }
+ /* We found no holes in the newsrc that overlaps the range, nor did we hit
+ something read beyond the end of the range. So, the great infinite
+ range of unread articles at the end of any newsrc line intersects the
+ range we want, and we just need to return that. */
+ a = to + 1;
+ *first = a > min ? a : min;
+ *last = max;
+ return 0;
+}
+
+// I'm guessing we didn't include this because we didn't think we're going
+// to need it. I'm not so sure. I'm putting it in for now.
+int
+nsMsgKeySet::LastMissingRange(int32_t min, int32_t max,
+ int32_t* first, int32_t* last)
+{
+ int32_t size;
+ int32_t *head;
+ int32_t *tail;
+ int32_t *end;
+ int32_t from = 0;
+ int32_t to = 0;
+ int32_t a;
+ int32_t b;
+
+ NS_ASSERTION(first && last, "invalid null param");
+ if (!first || !last) return -1;
+
+ *first = *last = 0;
+
+
+ NS_ASSERTION(min <= max && min > 0, "invalid min or max");
+ if (min > max || min <= 0) return -1;
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ while (tail < end) {
+ a = to + 1;
+ if (*tail < 0) { /* We got a range. */
+ from = tail[1];
+ to = from + (-(tail[0]));
+ tail += 2;
+ } else {
+ from = to = tail[0];
+ tail++;
+ }
+ b = from - 1;
+ /* At this point, [a,b] is the range of unread articles just before
+ the current range of read articles [from,to]. See if this range
+ intersects the [min,max] range we were given. */
+ if (a > max) return 0; /* We're done. If we found something, it's already
+ sitting in [*first,*last]. */
+ if (a <= b && b >= min) {
+ /* Ah-hah! We found an intersection. */
+ *first = a > min ? a : min;
+ *last = b < max ? b : max;
+ /* Continue on, looking for a later range. */
+ }
+ }
+ if (to < max) {
+ /* The great infinite range of unread articles at the end of any newsrc
+ line intersects the range we want, and we just need to return that. */
+ a = to + 1;
+ *first = a > min ? a : min;
+ *last = max;
+ }
+ return 0;
+}
+
+/**
+ * Fill the passed in aArray with the keys in the message key set.
+ */
+nsresult
+nsMsgKeySet::ToMsgKeyArray(nsTArray<nsMsgKey> &aArray)
+{
+ int32_t size;
+ int32_t *head;
+ int32_t *tail;
+ int32_t *end;
+ int32_t last_art = -1;
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ while (tail < end) {
+ int32_t from;
+ int32_t to;
+
+ if (*tail < 0) {
+ /* it's a range */
+ from = tail[1];
+ to = from + (-(tail[0]));
+ tail += 2;
+ }
+ else /* it's a literal */
+ {
+ from = *tail;
+ to = from;
+ tail++;
+ }
+ // The horrible news-hack used to adjust from to 1 if it was zero right
+ // here, but there is no longer a consumer of this method with that
+ // broken use-case.
+ if (from <= last_art) from = last_art + 1;
+ if (from <= to) {
+ if (from < to) {
+ for (int32_t i = from; i <= to ; ++i ) {
+ aArray.AppendElement(i);
+ }
+ } else {
+ aArray.AppendElement(from);
+ }
+ last_art = to;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+#ifdef DEBUG /* A lot of test cases for the above */
+
+#define countof(x) (sizeof(x) / sizeof(*(x)))
+
+void
+nsMsgKeySet::test_decoder (const char *string)
+{
+ nsMsgKeySet set(string /* , NULL */);
+ char* tmp;
+ set.Output(&tmp);
+ printf ("\t\"%s\"\t--> \"%s\"\n", string, tmp);
+ free(tmp);
+}
+
+
+#define START(STRING) \
+ string = STRING; \
+ if (!(set = nsMsgKeySet::Create(string))) abort ()
+
+#define FROB(N,PUSHP) \
+ i = N; \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \
+ printf ("%3lu: %-58s %c %3lu =\n", (unsigned long)set->m_length, s, \
+ (PUSHP ? '+' : '-'), (unsigned long)i); \
+ free(s); \
+ if (PUSHP \
+ ? set->Add(i) < 0 \
+ : set->Remove(i) < 0) \
+ abort (); \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \
+ printf ("%3lu: %-58s optimized =\n", (unsigned long)set->m_length, s); \
+ free(s); \
+
+#define END() \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \
+ printf ("%3lu: %s\n\n", (unsigned long)set->m_length, s); \
+ free(s); \
+ delete set; \
+
+
+
+void
+nsMsgKeySet::test_adder (void)
+{
+ const char *string;
+ nsMsgKeySet *set;
+ char *s;
+ int32_t i;
+
+ START("0-70,72-99,105,107,110-111,117-200");
+
+ FROB(205, true);
+ FROB(206, true);
+ FROB(207, true);
+ FROB(208, true);
+ FROB(208, true);
+ FROB(109, true);
+ FROB(72, true);
+
+ FROB(205, false);
+ FROB(206, false);
+ FROB(207, false);
+ FROB(208, false);
+ FROB(208, false);
+ FROB(109, false);
+ FROB(72, false);
+
+ FROB(72, true);
+ FROB(109, true);
+ FROB(208, true);
+ FROB(208, true);
+ FROB(207, true);
+ FROB(206, true);
+ FROB(205, true);
+
+ FROB(205, false);
+ FROB(206, false);
+ FROB(207, false);
+ FROB(208, false);
+ FROB(208, false);
+ FROB(109, false);
+ FROB(72, false);
+
+ FROB(100, true);
+ FROB(101, true);
+ FROB(102, true);
+ FROB(103, true);
+ FROB(106, true);
+ FROB(104, true);
+ FROB(109, true);
+ FROB(108, true);
+ END();
+
+ START("1-6"); FROB(7, false); END();
+ START("1-6"); FROB(6, false); END();
+ START("1-6"); FROB(5, false); END();
+ START("1-6"); FROB(4, false); END();
+ START("1-6"); FROB(3, false); END();
+ START("1-6"); FROB(2, false); END();
+ START("1-6"); FROB(1, false); END();
+ START("1-6"); FROB(0, false); END();
+
+ START("1-3"); FROB(1, false); END();
+ START("1-3"); FROB(2, false); END();
+ START("1-3"); FROB(3, false); END();
+
+ START("1,3,5-7,9,10"); FROB(5, false); END();
+ START("1,3,5-7,9,10"); FROB(6, false); END();
+ START("1,3,5-7,9,10"); FROB(7, false); FROB(7, true); FROB(8, true);
+ FROB (4, true); FROB (2, false); FROB (2, true);
+
+ FROB (4, false); FROB (5, false); FROB (6, false); FROB (7, false);
+ FROB (8, false); FROB (9, false); FROB (10, false); FROB (3, false);
+ FROB (2, false); FROB (1, false); FROB (1, false); FROB (0, false);
+ END();
+}
+
+#undef START
+#undef FROB
+#undef END
+
+
+
+#define START(STRING) \
+ string = STRING; \
+ if (!(set = nsMsgKeySet::Create(string))) abort ()
+
+#define FROB(N,M) \
+ i = N; \
+ j = M; \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \
+ printf ("%3lu: %-58s + %3lu-%3lu =\n", (unsigned long)set->m_length, s, (unsigned long)i, (unsigned long)j); \
+ free(s); \
+ switch (set->AddRange(i, j)) { \
+ case 0: \
+ printf("(no-op)\n"); \
+ break; \
+ case 1: \
+ break; \
+ default: \
+ abort(); \
+ } \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \
+ printf ("%3lu: %-58s\n", (unsigned long)set->m_length, s); \
+ free(s); \
+
+
+#define END() \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \
+ printf ("%3lu: %s\n\n", (unsigned long)set->m_length, s); \
+ free(s); \
+ delete set;
+
+
+void
+nsMsgKeySet::test_ranges(void)
+{
+ const char *string;
+ nsMsgKeySet *set;
+ char *s;
+ int32_t i;
+ int32_t j;
+
+ START("20-40,72-99,105,107,110-111,117-200");
+
+ FROB(205, 208);
+ FROB(50, 70);
+ FROB(0, 10);
+ FROB(112, 113);
+ FROB(101, 101);
+ FROB(5, 75);
+ FROB(103, 109);
+ FROB(2, 20);
+ FROB(1, 9999);
+
+ END();
+
+
+#undef START
+#undef FROB
+#undef END
+}
+
+
+
+
+#define TEST(N) \
+ if (! with_cache) set->m_cached_value = -1; \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \
+ printf (" %3d = %s\n", N, \
+ (set->IsMember(N) ? "true" : "false")); \
+ free(s);
+
+void
+nsMsgKeySet::test_member(bool with_cache)
+{
+ nsMsgKeySet *set;
+ char *s;
+
+ const char *st1 = "1-70,72-99,105,107,110-111,117-200";
+ printf ("\n\nTesting %s (with%s cache)\n", st1, with_cache ? "" : "out");
+ if (!(set = Create(st1))) {
+ abort();
+ }
+
+ TEST(-1);
+ TEST(0);
+ TEST(1);
+ TEST(20);
+
+ delete set;
+ const char *st2 = "0-70,72-99,105,107,110-111,117-200";
+ printf ("\n\nTesting %s (with%s cache)\n", st2, with_cache ? "" : "out");
+ if (!(set = Create(st2))) {
+ abort();
+ }
+
+ TEST(-1);
+ TEST(0);
+ TEST(1);
+ TEST(20);
+ TEST(69);
+ TEST(70);
+ TEST(71);
+ TEST(72);
+ TEST(73);
+ TEST(74);
+ TEST(104);
+ TEST(105);
+ TEST(106);
+ TEST(107);
+ TEST(108);
+ TEST(109);
+ TEST(110);
+ TEST(111);
+ TEST(112);
+ TEST(116);
+ TEST(117);
+ TEST(118);
+ TEST(119);
+ TEST(200);
+ TEST(201);
+ TEST(65535);
+
+ delete set;
+}
+
+#undef TEST
+
+
+// static void
+// test_newsrc (char *file)
+// {
+// FILE *fp = fopen (file, "r");
+// char buf [1024];
+// if (! fp) abort ();
+// while (fgets (buf, sizeof (buf), fp))
+// {
+// if (!strncmp (buf, "options ", 8))
+// fwrite (buf, 1, strlen (buf), stdout);
+// else
+// {
+// char *sep = buf;
+// while (*sep != 0 && *sep != ':' && *sep != '!')
+// sep++;
+// if (*sep) sep++;
+// while (isspace (*sep)) sep++;
+// fwrite (buf, 1, sep - buf, stdout);
+// if (*sep)
+// {
+// char *s;
+// msg_NewsRCSet *set = msg_parse_newsrc_set (sep, &allocinfo);
+// if (! set)
+// abort ();
+// if (! msg_OptimizeNewsRCSet (set))
+// abort ();
+// if (! ((s = msg_format_newsrc_set (set))))
+// abort ();
+// msg_free_newsrc_set (set, &allocinfo);
+// fwrite (s, 1, strlen (s), stdout);
+// free (s);
+// fwrite ("\n", 1, 1, stdout);
+// }
+// }
+// }
+// fclose (fp);
+// }
+
+void
+nsMsgKeySet::RunTests ()
+{
+
+ test_decoder ("");
+ test_decoder (" ");
+ test_decoder ("0");
+ test_decoder ("1");
+ test_decoder ("123");
+ test_decoder (" 123 ");
+ test_decoder (" 123 4");
+ test_decoder (" 1,2, 3, 4");
+ test_decoder ("0-70,72-99,100,101");
+ test_decoder (" 0-70 , 72 - 99 ,100,101 ");
+ test_decoder ("0 - 268435455");
+ /* This one overflows - we can't help it.
+ test_decoder ("0 - 4294967295"); */
+
+ test_adder ();
+
+ test_ranges();
+
+ test_member (false);
+ test_member (true);
+
+ // test_newsrc ("/u/montulli/.newsrc");
+ /* test_newsrc ("/u/jwz/.newsrc");*/
+}
+
+#endif /* DEBUG */
diff --git a/mailnews/base/util/nsMsgKeySet.h b/mailnews/base/util/nsMsgKeySet.h
new file mode 100644
index 000000000..f38dbd013
--- /dev/null
+++ b/mailnews/base/util/nsMsgKeySet.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgKeySet_H_
+#define _nsMsgKeySet_H_
+
+#include "msgCore.h"
+#include "nsTArray.h"
+
+// nsMsgKeySet represents a set of articles. Typically, it is the set of
+// read articles from a .newsrc file, but it can be used for other purposes
+// too.
+
+#if 0
+// If a MSG_NewsHost* is supplied to the creation routine, then that
+// MSG_NewsHost will be notified whenever a change is made to set.
+class MSG_NewsHost;
+#endif
+
+class NS_MSG_BASE nsMsgKeySet {
+public:
+ // Creates an empty set.
+ static nsMsgKeySet* Create(/* MSG_NewsHost* host = NULL*/);
+
+ // Creates a set from the list of numbers, as might be found in a
+ // newsrc file.
+ static nsMsgKeySet* Create(const char* str/* , MSG_NewsHost* host = NULL*/);
+ ~nsMsgKeySet();
+
+ // FirstNonMember() returns the lowest non-member of the set that is
+ // greater than 0.
+ int32_t FirstNonMember();
+
+ // Output() converts to a string representation suitable for writing to a
+ // .newsrc file.
+ nsresult Output(char **outputStr);
+
+ // IsMember() returns whether the given article is a member of this set.
+ bool IsMember(int32_t art);
+
+ // Add() adds the given article to the set. (Returns 1 if a change was
+ // made, 0 if it was already there, and negative on error.)
+ int Add(int32_t art);
+
+ // Remove() removes the given article from the set.
+ int Remove(int32_t art);
+
+ // AddRange() adds the (inclusive) given range of articles to the set.
+ int AddRange(int32_t first, int32_t last);
+
+ // CountMissingInRange() takes an inclusive range of articles and returns
+ // the number of articles in that range which are not in the set.
+ int32_t CountMissingInRange(int32_t start, int32_t end);
+
+ // FirstMissingRange() takes an inclusive range and finds the first range
+ // of articles that are not in the set. If none, return zeros.
+ int FirstMissingRange(int32_t min, int32_t max, int32_t* first, int32_t* last);
+
+
+ // LastMissingRange() takes an inclusive range and finds the last range
+ // of articles that are not in the set. If none, return zeros.
+ int LastMissingRange(int32_t min, int32_t max, int32_t* first, int32_t* last);
+
+ int32_t GetLastMember();
+ int32_t GetFirstMember();
+ void SetLastMember(int32_t highWaterMark);
+ // For debugging only...
+ int32_t getLength() {return m_length;}
+
+/**
+ * Fill the passed in aArray with the keys in the message key set.
+ */
+ nsresult ToMsgKeyArray(nsTArray<nsMsgKey> &aArray);
+
+#ifdef DEBUG
+ static void RunTests();
+#endif
+
+protected:
+ nsMsgKeySet(/* MSG_NewsHost* host */);
+ explicit nsMsgKeySet(const char* /* , MSG_NewsHost* host */);
+ bool Grow();
+ bool Optimize();
+
+#ifdef DEBUG
+ static void test_decoder(const char*);
+ static void test_adder();
+ static void test_ranges();
+ static void test_member(bool with_cache);
+#endif
+
+ int32_t *m_data; /* the numbers composing the `chunks' */
+ int32_t m_data_size; /* size of that malloc'ed block */
+ int32_t m_length; /* active area */
+
+ int32_t m_cached_value; /* a potential set member, or -1 if unset*/
+ int32_t m_cached_value_index; /* the index into `data' at which a search
+ to determine whether `cached_value' was
+ a member of the set ended. */
+#ifdef NEWSRC_DOES_HOST_STUFF
+ MSG_NewsHost* m_host;
+#endif
+};
+
+
+#endif /* _nsMsgKeySet_H_ */
diff --git a/mailnews/base/util/nsMsgLineBuffer.cpp b/mailnews/base/util/nsMsgLineBuffer.cpp
new file mode 100644
index 000000000..56f8a9504
--- /dev/null
+++ b/mailnews/base/util/nsMsgLineBuffer.cpp
@@ -0,0 +1,439 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "prlog.h"
+#include "prmem.h"
+#include "nsMsgLineBuffer.h"
+#include "nsMsgUtils.h"
+#include "nsIInputStream.h" // used by nsMsgLineStreamBuffer
+
+nsByteArray::nsByteArray()
+{
+ MOZ_COUNT_CTOR(nsByteArray);
+ m_buffer = NULL;
+ m_bufferSize = 0;
+ m_bufferPos = 0;
+}
+
+nsByteArray::~nsByteArray()
+{
+ MOZ_COUNT_DTOR(nsByteArray);
+ PR_FREEIF(m_buffer);
+}
+
+nsresult nsByteArray::GrowBuffer(uint32_t desired_size, uint32_t quantum)
+{
+ if (m_bufferSize < desired_size)
+ {
+ char *new_buf;
+ uint32_t increment = desired_size - m_bufferSize;
+ if (increment < quantum) /* always grow by a minimum of N bytes */
+ increment = quantum;
+
+
+ new_buf = (m_buffer
+ ? (char *) PR_REALLOC (m_buffer, (m_bufferSize + increment))
+ : (char *) PR_MALLOC (m_bufferSize + increment));
+ if (! new_buf)
+ return NS_ERROR_OUT_OF_MEMORY;
+ m_buffer = new_buf;
+ m_bufferSize += increment;
+ }
+ return NS_OK;
+}
+
+nsresult nsByteArray::AppendString(const char *string)
+{
+ uint32_t strLength = (string) ? PL_strlen(string) : 0;
+ return AppendBuffer(string, strLength);
+
+}
+
+nsresult nsByteArray::AppendBuffer(const char *buffer, uint32_t length)
+{
+ nsresult ret = NS_OK;
+ if (m_bufferPos + length > m_bufferSize)
+ ret = GrowBuffer(m_bufferPos + length, 1024);
+ if (NS_SUCCEEDED(ret))
+ {
+ memcpy(m_buffer + m_bufferPos, buffer, length);
+ m_bufferPos += length;
+ }
+ return ret;
+}
+
+nsMsgLineBuffer::nsMsgLineBuffer(nsMsgLineBufferHandler *handler, bool convertNewlinesP)
+{
+ MOZ_COUNT_CTOR(nsMsgLineBuffer);
+ m_handler = handler;
+ m_convertNewlinesP = convertNewlinesP;
+ m_lookingForCRLF = true;
+}
+
+nsMsgLineBuffer::~nsMsgLineBuffer()
+{
+ MOZ_COUNT_DTOR(nsMsgLineBuffer);
+}
+
+void
+nsMsgLineBuffer::SetLookingForCRLF(bool b)
+{
+ m_lookingForCRLF = b;
+}
+
+nsresult nsMsgLineBuffer::BufferInput(const char *net_buffer, int32_t net_buffer_size)
+{
+ nsresult status = NS_OK;
+ if (m_bufferPos > 0 && m_buffer && m_buffer[m_bufferPos - 1] == '\r' &&
+ net_buffer_size > 0 && net_buffer[0] != '\n') {
+ /* The last buffer ended with a CR. The new buffer does not start
+ with a LF. This old buffer should be shipped out and discarded. */
+ PR_ASSERT(m_bufferSize > m_bufferPos);
+ if (m_bufferSize <= m_bufferPos)
+ return NS_ERROR_UNEXPECTED;
+ if (NS_FAILED(ConvertAndSendBuffer()))
+ return NS_ERROR_FAILURE;
+ m_bufferPos = 0;
+ }
+ while (net_buffer_size > 0)
+ {
+ const char *net_buffer_end = net_buffer + net_buffer_size;
+ const char *newline = 0;
+ const char *s;
+
+ for (s = net_buffer; s < net_buffer_end; s++)
+ {
+ if (m_lookingForCRLF) {
+ /* Move forward in the buffer until the first newline.
+ Stop when we see CRLF, CR, or LF, or the end of the buffer.
+ *But*, if we see a lone CR at the *very end* of the buffer,
+ treat this as if we had reached the end of the buffer without
+ seeing a line terminator. This is to catch the case of the
+ buffers splitting a CRLF pair, as in "FOO\r\nBAR\r" "\nBAZ\r\n".
+ */
+ if (*s == '\r' || *s == '\n') {
+ newline = s;
+ if (newline[0] == '\r') {
+ if (s == net_buffer_end - 1) {
+ /* CR at end - wait for the next character. */
+ newline = 0;
+ break;
+ }
+ else if (newline[1] == '\n') {
+ /* CRLF seen; swallow both. */
+ newline++;
+ }
+ }
+ newline++;
+ break;
+ }
+ }
+ else {
+ /* if not looking for a CRLF, stop at CR or LF. (for example, when parsing the newsrc file). this fixes #9896, where we'd lose the last line of anything we'd parse that used CR as the line break. */
+ if (*s == '\r' || *s == '\n') {
+ newline = s;
+ newline++;
+ break;
+ }
+ }
+ }
+
+ /* Ensure room in the net_buffer and append some or all of the current
+ chunk of data to it. */
+ {
+ const char *end = (newline ? newline : net_buffer_end);
+ uint32_t desired_size = (end - net_buffer) + m_bufferPos + 1;
+
+ if (desired_size >= m_bufferSize)
+ {
+ status = GrowBuffer (desired_size, 1024);
+ if (NS_FAILED(status))
+ return status;
+ }
+ memcpy (m_buffer + m_bufferPos, net_buffer, (end - net_buffer));
+ m_bufferPos += (end - net_buffer);
+ }
+
+ /* Now m_buffer contains either a complete line, or as complete
+ a line as we have read so far.
+
+ If we have a line, process it, and then remove it from `m_buffer'.
+ Then go around the loop again, until we drain the incoming data.
+ */
+ if (!newline)
+ return NS_OK;
+
+ if (NS_FAILED(ConvertAndSendBuffer()))
+ return NS_ERROR_FAILURE;
+
+ net_buffer_size -= (newline - net_buffer);
+ net_buffer = newline;
+ m_bufferPos = 0;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgLineBuffer::HandleLine(const char *line, uint32_t line_length)
+{
+ NS_ASSERTION(false, "must override this method if you don't provide a handler");
+ return NS_OK;
+}
+
+nsresult nsMsgLineBuffer::ConvertAndSendBuffer()
+{
+ /* Convert the line terminator to the native form.
+ */
+
+ char *buf = m_buffer;
+ int32_t length = m_bufferPos;
+
+ char* newline;
+
+ PR_ASSERT(buf && length > 0);
+ if (!buf || length <= 0)
+ return NS_ERROR_FAILURE;
+ newline = buf + length;
+
+ PR_ASSERT(newline[-1] == '\r' || newline[-1] == '\n');
+ if (newline[-1] != '\r' && newline[-1] != '\n')
+ return NS_ERROR_FAILURE;
+
+ if (m_convertNewlinesP)
+ {
+#if (MSG_LINEBREAK_LEN == 1)
+ if ((newline - buf) >= 2 &&
+ newline[-2] == '\r' &&
+ newline[-1] == '\n')
+ {
+ /* CRLF -> CR or LF */
+ buf [length - 2] = MSG_LINEBREAK[0];
+ length--;
+ }
+ else if (newline > buf + 1 &&
+ newline[-1] != MSG_LINEBREAK[0])
+ {
+ /* CR -> LF or LF -> CR */
+ buf [length - 1] = MSG_LINEBREAK[0];
+ }
+#else
+ if (((newline - buf) >= 2 && newline[-2] != '\r') ||
+ ((newline - buf) >= 1 && newline[-1] != '\n'))
+ {
+ /* LF -> CRLF or CR -> CRLF */
+ length++;
+ buf[length - 2] = MSG_LINEBREAK[0];
+ buf[length - 1] = MSG_LINEBREAK[1];
+ }
+#endif
+ }
+ return (m_handler) ? m_handler->HandleLine(buf, length) : HandleLine(buf, length);
+}
+
+// If there's still some data (non CRLF terminated) flush it out
+nsresult nsMsgLineBuffer::FlushLastLine()
+{
+ char *buf = m_buffer + m_bufferPos;
+ int32_t length = m_bufferPos - 1;
+ if (length > 0)
+ return (m_handler) ? m_handler->HandleLine(buf, length) : HandleLine(buf, length);
+ else
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// This is a utility class used to efficiently extract lines from an input stream by buffering
+// read but unprocessed stream data in a buffer.
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+nsMsgLineStreamBuffer::nsMsgLineStreamBuffer(uint32_t aBufferSize, bool aAllocateNewLines, bool aEatCRLFs, char aLineToken)
+ : m_eatCRLFs(aEatCRLFs), m_allocateNewLines(aAllocateNewLines), m_lineToken(aLineToken)
+{
+ NS_PRECONDITION(aBufferSize > 0, "invalid buffer size!!!");
+ m_dataBuffer = nullptr;
+ m_startPos = 0;
+ m_numBytesInBuffer = 0;
+
+ // used to buffer incoming data by ReadNextLineFromInput
+ if (aBufferSize > 0)
+ {
+ m_dataBuffer = (char *) PR_CALLOC(sizeof(char) * aBufferSize);
+ }
+
+ m_dataBufferSize = aBufferSize;
+}
+
+nsMsgLineStreamBuffer::~nsMsgLineStreamBuffer()
+{
+ PR_FREEIF(m_dataBuffer); // release our buffer...
+}
+
+
+nsresult nsMsgLineStreamBuffer::GrowBuffer(int32_t desiredSize)
+{
+ char* newBuffer = (char *) PR_REALLOC(m_dataBuffer, desiredSize);
+ NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
+ m_dataBuffer = newBuffer;
+ m_dataBufferSize = desiredSize;
+ return NS_OK;
+}
+
+void nsMsgLineStreamBuffer::ClearBuffer()
+{
+ m_startPos = 0;
+ m_numBytesInBuffer = 0;
+}
+
+// aInputStream - the input stream we want to read a line from
+// aPauseForMoreData is returned as true if the stream does not yet contain a line and we must wait for more
+// data to come into the stream.
+// Note to people wishing to modify this function: Be *VERY CAREFUL* this is a critical function used by all of
+// our mail protocols including imap, nntp, and pop. If you screw it up, you could break a lot of stuff.....
+
+char * nsMsgLineStreamBuffer::ReadNextLine(nsIInputStream * aInputStream, uint32_t &aNumBytesInLine, bool &aPauseForMoreData, nsresult *prv, bool addLineTerminator)
+{
+ // try to extract a line from m_inputBuffer. If we don't have an entire line,
+ // then read more bytes out from the stream. If the stream is empty then wait
+ // on the monitor for more data to come in.
+
+ NS_PRECONDITION(m_dataBuffer && m_dataBufferSize > 0, "invalid input arguments for read next line from input");
+
+ if (prv)
+ *prv = NS_OK;
+ // initialize out values
+ aPauseForMoreData = false;
+ aNumBytesInLine = 0;
+ char * endOfLine = nullptr;
+ char * startOfLine = m_dataBuffer+m_startPos;
+
+ if (m_numBytesInBuffer > 0) // any data in our internal buffer?
+ endOfLine = PL_strchr(startOfLine, m_lineToken); // see if we already have a line ending...
+
+ // it's possible that we got here before the first time we receive data from the server
+ // so aInputStream will be nullptr...
+ if (!endOfLine && aInputStream) // get some more data from the server
+ {
+ nsresult rv;
+ uint64_t numBytesInStream = 0;
+ uint32_t numBytesCopied = 0;
+ bool nonBlockingStream;
+ aInputStream->IsNonBlocking(&nonBlockingStream);
+ rv = aInputStream->Available(&numBytesInStream);
+ if (NS_FAILED(rv))
+ {
+ if (prv)
+ *prv = rv;
+ aNumBytesInLine = -1;
+ return nullptr;
+ }
+ if (!nonBlockingStream && numBytesInStream == 0) // if no data available,
+ numBytesInStream = m_dataBufferSize / 2; // ask for half the data buffer size.
+
+ // if the number of bytes we want to read from the stream, is greater than the number
+ // of bytes left in our buffer, then we need to shift the start pos and its contents
+ // down to the beginning of m_dataBuffer...
+ uint32_t numFreeBytesInBuffer = m_dataBufferSize - m_startPos - m_numBytesInBuffer;
+ if (numBytesInStream >= numFreeBytesInBuffer)
+ {
+ if (m_startPos)
+ {
+ memmove(m_dataBuffer, startOfLine, m_numBytesInBuffer);
+ // make sure the end of the buffer is terminated
+ m_dataBuffer[m_numBytesInBuffer] = '\0';
+ m_startPos = 0;
+ startOfLine = m_dataBuffer;
+ numFreeBytesInBuffer = m_dataBufferSize - m_numBytesInBuffer;
+ //printf("moving data in read line around because buffer filling up\n");
+ }
+ // If we didn't make enough space (or any), grow the buffer
+ if (numBytesInStream >= numFreeBytesInBuffer)
+ {
+ int64_t growBy = (numBytesInStream - numFreeBytesInBuffer) * 2 + 1;
+ // GrowBuffer cannot handles over 4GB size
+ if (m_dataBufferSize + growBy > PR_UINT32_MAX)
+ return nullptr;
+ // try growing buffer by twice as much as we need.
+ nsresult rv = GrowBuffer(m_dataBufferSize + growBy);
+ // if we can't grow the buffer, we have to bail.
+ if (NS_FAILED(rv))
+ return nullptr;
+ startOfLine = m_dataBuffer;
+ numFreeBytesInBuffer += growBy;
+ }
+ NS_ASSERTION(m_startPos == 0, "m_startPos should be 0 .....\n");
+ }
+
+ uint32_t numBytesToCopy = std::min(uint64_t(numFreeBytesInBuffer - 1) /* leave one for a null terminator */, numBytesInStream);
+ if (numBytesToCopy > 0)
+ {
+ // read the data into the end of our data buffer
+ char *startOfNewData = startOfLine + m_numBytesInBuffer;
+ rv = aInputStream->Read(startOfNewData, numBytesToCopy, &numBytesCopied);
+ if (prv)
+ *prv = rv;
+ uint32_t i;
+ for (i = 0; i < numBytesCopied; i++) // replace nulls with spaces
+ {
+ if (!startOfNewData[i])
+ startOfNewData[i] = ' ';
+ }
+ m_numBytesInBuffer += numBytesCopied;
+ m_dataBuffer[m_startPos + m_numBytesInBuffer] = '\0';
+
+ // okay, now that we've tried to read in more data from the stream,
+ // look for another end of line character in the new data
+ endOfLine = PL_strchr(startOfNewData, m_lineToken);
+ }
+ }
+
+ // okay, now check again for endOfLine.
+ if (endOfLine)
+ {
+ if (!m_eatCRLFs)
+ endOfLine += 1; // count for LF or CR
+
+ aNumBytesInLine = endOfLine - startOfLine;
+
+ if (m_eatCRLFs && aNumBytesInLine > 0 && startOfLine[aNumBytesInLine-1] == '\r') // Remove the CR in a CRLF sequence
+ aNumBytesInLine--;
+
+ // PR_CALLOC zeros out the allocated line
+ char* newLine = (char*) PR_CALLOC(aNumBytesInLine + (addLineTerminator ? MSG_LINEBREAK_LEN : 0) + 1);
+ if (!newLine)
+ {
+ aNumBytesInLine = 0;
+ aPauseForMoreData = true;
+ return nullptr;
+ }
+
+ memcpy(newLine, startOfLine, aNumBytesInLine); // copy the string into the new line buffer
+ if (addLineTerminator)
+ {
+ memcpy(newLine + aNumBytesInLine, MSG_LINEBREAK, MSG_LINEBREAK_LEN);
+ aNumBytesInLine += MSG_LINEBREAK_LEN;
+ }
+
+ if (m_eatCRLFs)
+ endOfLine += 1; // advance past LF or CR if we haven't already done so...
+
+ // now we need to update the data buffer to go past the line we just read out.
+ m_numBytesInBuffer -= (endOfLine - startOfLine);
+ if (m_numBytesInBuffer)
+ m_startPos = endOfLine - m_dataBuffer;
+ else
+ m_startPos = 0;
+
+ return newLine;
+ }
+
+ aPauseForMoreData = true;
+ return nullptr; // if we somehow got here. we don't have another line in the buffer yet...need to wait for more data...
+}
+
+bool nsMsgLineStreamBuffer::NextLineAvailable()
+{
+ return (m_numBytesInBuffer > 0 && PL_strchr(m_dataBuffer+m_startPos, m_lineToken));
+}
+
diff --git a/mailnews/base/util/nsMsgLineBuffer.h b/mailnews/base/util/nsMsgLineBuffer.h
new file mode 100644
index 000000000..6bacd45af
--- /dev/null
+++ b/mailnews/base/util/nsMsgLineBuffer.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsMsgLineBuffer_H
+#define _nsMsgLineBuffer_H
+
+#include "msgCore.h" // precompiled header...
+
+// I can't believe I have to have this stupid class, but I can't find
+// anything suitable (nsStrImpl might be, when it's done). nsIByteBuffer
+// would do, if I had a stream for input, which I don't.
+
+class NS_MSG_BASE nsByteArray
+{
+public:
+ nsByteArray();
+ virtual ~nsByteArray();
+ uint32_t GetSize() {return m_bufferSize;}
+ uint32_t GetBufferPos() {return m_bufferPos;}
+ nsresult GrowBuffer(uint32_t desired_size, uint32_t quantum = 1024);
+ nsresult AppendString(const char *string);
+ nsresult AppendBuffer(const char *buffer, uint32_t length);
+ void ResetWritePos() {m_bufferPos = 0;}
+ char *GetBuffer() {return m_buffer;}
+protected:
+ char *m_buffer;
+ uint32_t m_bufferSize;
+ uint32_t m_bufferPos; // write Pos in m_buffer - where the next byte should go.
+};
+
+
+class NS_MSG_BASE nsMsgLineBufferHandler : public nsByteArray
+{
+public:
+ virtual nsresult HandleLine(const char *line, uint32_t line_length) = 0;
+};
+
+class NS_MSG_BASE nsMsgLineBuffer : public nsMsgLineBufferHandler
+{
+public:
+ nsMsgLineBuffer(nsMsgLineBufferHandler *handler, bool convertNewlinesP);
+
+ virtual ~nsMsgLineBuffer();
+ nsresult BufferInput(const char *net_buffer, int32_t net_buffer_size);
+ // Not sure why anyone cares, by NNTPHost seems to want to know the buf pos.
+ uint32_t GetBufferPos() {return m_bufferPos;}
+
+ virtual nsresult HandleLine(const char *line, uint32_t line_length);
+ // flush last line, though it won't be CRLF terminated.
+ virtual nsresult FlushLastLine();
+protected:
+ explicit nsMsgLineBuffer(bool convertNewlinesP);
+
+ nsresult ConvertAndSendBuffer();
+ void SetLookingForCRLF(bool b);
+
+ nsMsgLineBufferHandler *m_handler;
+ bool m_convertNewlinesP;
+ bool m_lookingForCRLF;
+};
+
+// I'm adding this utility class here for lack of a better place. This utility class is similar to nsMsgLineBuffer
+// except it works from an input stream. It is geared towards efficiently parsing new lines out of a stream by storing
+// read but unprocessed bytes in a buffer. I envision the primary use of this to be our mail protocols such as imap, news and
+// pop which need to process line by line data being returned in the form of a proxied stream from the server.
+
+class nsIInputStream;
+
+class NS_MSG_BASE nsMsgLineStreamBuffer
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(nsMsgLineStreamBuffer)
+
+ // aBufferSize -- size of the buffer you want us to use for buffering stream data
+ // aEndOfLinetoken -- The delimiter string to be used for determining the end of line. This
+ // allows us to parse platform specific end of line endings by making it
+ // a parameter.
+ // aAllocateNewLines -- true if you want calls to ReadNextLine to allocate new memory for the line.
+ // if false, the char * returned is just a ptr into the buffer. Subsequent calls to
+ // ReadNextLine will alter the data so your ptr only has a life time of a per call.
+ // aEatCRLFs -- true if you don't want to see the CRLFs on the lines returned by ReadNextLine.
+ // false if you do want to see them.
+ // aLineToken -- Specify the line token to look for, by default is LF ('\n') which cover as well CRLF. If
+ // lines are terminated with a CR only, you need to set aLineToken to CR ('\r')
+ nsMsgLineStreamBuffer(uint32_t aBufferSize, bool aAllocateNewLines,
+ bool aEatCRLFs = true, char aLineToken = '\n'); // specify the size of the buffer you want the class to use....
+
+ // Caller must free the line returned using PR_Free
+ // aEndOfLinetoken -- delimiter used to denote the end of a line.
+ // aNumBytesInLine -- The number of bytes in the line returned
+ // aPauseForMoreData -- There is not enough data in the stream to make a line at this time...
+ char * ReadNextLine(nsIInputStream * aInputStream, uint32_t &anumBytesInLine, bool &aPauseForMoreData, nsresult *rv = nullptr, bool addLineTerminator = false);
+ nsresult GrowBuffer(int32_t desiredSize);
+ void ClearBuffer();
+ bool NextLineAvailable();
+private:
+ virtual ~nsMsgLineStreamBuffer();
+protected:
+ bool m_eatCRLFs;
+ bool m_allocateNewLines;
+ char * m_dataBuffer;
+ uint32_t m_dataBufferSize;
+ uint32_t m_startPos;
+ uint32_t m_numBytesInBuffer;
+ char m_lineToken;
+};
+
+
+#endif
diff --git a/mailnews/base/util/nsMsgMailNewsUrl.cpp b/mailnews/base/util/nsMsgMailNewsUrl.cpp
new file mode 100644
index 000000000..2c80cb692
--- /dev/null
+++ b/mailnews/base/util/nsMsgMailNewsUrl.cpp
@@ -0,0 +1,1060 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsStringGlue.h"
+#include "nsILoadGroup.h"
+#include "nsIDocShell.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsIStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsIFile.h"
+#include "prmem.h"
+#include <time.h>
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+#include "nsProxyRelease.h"
+#include "mozilla/BasePrincipal.h"
+
+nsMsgMailNewsUrl::nsMsgMailNewsUrl()
+{
+ // nsIURI specific state
+ m_errorMessage = nullptr;
+ m_runningUrl = false;
+ m_updatingFolder = false;
+ m_msgIsInLocalCache = false;
+ m_suppressErrorMsgs = false;
+ m_isPrincipalURL = false;
+ mMaxProgress = -1;
+ m_baseURL = do_CreateInstance(NS_STANDARDURL_CONTRACTID);
+}
+
+#define NOTIFY_URL_LISTENERS(propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<nsCOMPtr<nsIUrlListener> >::ForwardIterator iter(mUrlListeners); \
+ while (iter.HasMore()) { \
+ nsCOMPtr<nsIUrlListener> listener = iter.GetNext(); \
+ listener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+nsMsgMailNewsUrl::~nsMsgMailNewsUrl()
+{
+ PR_FREEIF(m_errorMessage);
+
+ // In IMAP this URL is created and destroyed on the imap thread,
+ // so we must ensure that releases of XPCOM objects (which might be
+ // implemented by non-threadsafe JS components) are released on the
+ // main thread.
+ NS_ReleaseOnMainThread(m_baseURL.forget());
+ NS_ReleaseOnMainThread(mMimeHeaders.forget());
+ NS_ReleaseOnMainThread(m_searchSession.forget());
+ NS_ReleaseOnMainThread(mMsgHeaderSink.forget());
+
+ nsTObserverArray<nsCOMPtr<nsIUrlListener>>::ForwardIterator iter(mUrlListeners);
+ while (iter.HasMore()) {
+ nsCOMPtr<nsIUrlListener> listener = iter.GetNext();
+ if (listener)
+ NS_ReleaseOnMainThread(listener.forget());
+ }
+}
+
+NS_IMPL_ADDREF(nsMsgMailNewsUrl)
+NS_IMPL_RELEASE(nsMsgMailNewsUrl)
+
+// We want part URLs to QI to nsIURIWithPrincipal so we can give
+// them a "normalised" origin. URLs that already have a "normalised"
+// origin should not QI to nsIURIWithPrincipal.
+NS_INTERFACE_MAP_BEGIN(nsMsgMailNewsUrl)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgMailNewsUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgMailNewsUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIURIWithPrincipal, !m_isPrincipalURL)
+NS_INTERFACE_MAP_END
+
+// Support for nsIURIWithPrincipal.
+NS_IMETHODIMP nsMsgMailNewsUrl::GetPrincipal(nsIPrincipal **aPrincipal)
+{
+ MOZ_ASSERT(!m_isPrincipalURL,
+ "nsMsgMailNewsUrl::GetPrincipal() can only be called for non-principal URLs");
+
+ if (!m_principal) {
+ nsCOMPtr <nsIMsgMessageUrl> msgUrl;
+ QueryInterface(NS_GET_IID(nsIMsgMessageUrl), getter_AddRefs(msgUrl));
+
+ nsAutoCString spec;
+ if (!msgUrl || NS_FAILED(msgUrl->GetPrincipalSpec(spec))) {
+ MOZ_ASSERT(false, "Can't get principal spec");
+ // just use the normal spec.
+ GetSpec(spec);
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozilla::PrincipalOriginAttributes attrs;
+ m_principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs);
+ }
+
+ NS_IF_ADDREF(*aPrincipal = m_principal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetPrincipalUri(nsIURI **aPrincipalURI)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipalURI);
+ if (!m_principal) {
+ nsCOMPtr<nsIPrincipal> p;
+ GetPrincipal(getter_AddRefs(p));
+ }
+ if (!m_principal)
+ return NS_ERROR_NULL_POINTER;
+ return m_principal->GetURI(aPrincipalURI);
+}
+
+// NS_IMPL_ISUPPORTS(nsMsgMailNewsUrl, nsIMsgMailNewsUrl, nsIURL, nsIURI)
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIMsgMailNewsUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsMsgMailNewsUrl::GetUrlState(bool * aRunningUrl)
+{
+ if (aRunningUrl)
+ *aRunningUrl = m_runningUrl;
+
+ return NS_OK;
+}
+
+nsresult nsMsgMailNewsUrl::SetUrlState(bool aRunningUrl, nsresult aExitCode)
+{
+ // if we already knew this running state, return, unless the url was aborted
+ if (m_runningUrl == aRunningUrl && aExitCode != NS_MSG_ERROR_URL_ABORTED)
+ return NS_OK;
+ m_runningUrl = aRunningUrl;
+ nsCOMPtr <nsIMsgStatusFeedback> statusFeedback;
+
+ // put this back - we need it for urls that don't run through the doc loader
+ if (NS_SUCCEEDED(GetStatusFeedback(getter_AddRefs(statusFeedback))) && statusFeedback)
+ {
+ if (m_runningUrl)
+ statusFeedback->StartMeteors();
+ else
+ {
+ statusFeedback->ShowProgress(0);
+ statusFeedback->StopMeteors();
+ }
+ }
+
+ if (m_runningUrl)
+ {
+ NOTIFY_URL_LISTENERS(OnStartRunningUrl, (this));
+ }
+ else
+ {
+ NOTIFY_URL_LISTENERS(OnStopRunningUrl, (this, aExitCode));
+ mUrlListeners.Clear();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::RegisterListener(nsIUrlListener *aUrlListener)
+{
+ NS_ENSURE_ARG_POINTER(aUrlListener);
+ mUrlListeners.AppendElement(aUrlListener);
+ return NS_OK;
+}
+
+nsresult nsMsgMailNewsUrl::UnRegisterListener(nsIUrlListener *aUrlListener)
+{
+ NS_ENSURE_ARG_POINTER(aUrlListener);
+
+ // Due to the way mailnews is structured, some listeners attempt to remove
+ // themselves twice. This may in fact be an error in the coding, however
+ // if they didn't do it as they do currently, then they could fail to remove
+ // their listeners.
+ mUrlListeners.RemoveElement(aUrlListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetServer(nsIMsgIncomingServer ** aIncomingServer)
+{
+ // mscott --> we could cache a copy of the server here....but if we did, we run
+ // the risk of leaking the server if any single url gets leaked....of course that
+ // shouldn't happen...but it could. so i'm going to look it up every time and
+ // we can look at caching it later.
+
+ nsresult rv;
+ nsAutoCString urlstr;
+ nsAutoCString scheme;
+
+ nsCOMPtr<nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = m_baseURL->GetSpec(urlstr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = url->SetSpec(urlstr);
+ if (NS_FAILED(rv)) return rv;
+ rv = GetScheme(scheme);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (scheme.EqualsLiteral("pop"))
+ scheme.Assign("pop3");
+ // we use "nntp" in the server list so translate it here.
+ if (scheme.EqualsLiteral("news"))
+ scheme.Assign("nntp");
+ url->SetScheme(scheme);
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = accountManager->FindServerByURI(url, false,
+ aIncomingServer);
+ if (!*aIncomingServer && scheme.EqualsLiteral("imap"))
+ {
+ // look for any imap server with this host name so clicking on
+ // other users folder urls will work. We could override this method
+ // for imap urls, or we could make caching of servers work and
+ // just set the server in the imap code for this case.
+ url->SetUserPass(EmptyCString());
+ rv = accountManager->FindServerByURI(url, false,
+ aIncomingServer);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgWindow(nsIMsgWindow **aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+ *aMsgWindow = nullptr;
+
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ msgWindow.swap(*aMsgWindow);
+ return *aMsgWindow ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgWindow(nsIMsgWindow *aMsgWindow)
+{
+#ifdef DEBUG_David_Bienvenu
+ NS_ASSERTION(aMsgWindow || !m_msgWindowWeak, "someone crunching non-null msg window");
+#endif
+ m_msgWindowWeak = do_GetWeakReference(aMsgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetStatusFeedback(nsIMsgStatusFeedback **aMsgFeedback)
+{
+ // note: it is okay to return a null status feedback and not return an error
+ // it's possible the url really doesn't have status feedback
+ *aMsgFeedback = nullptr;
+ if (!m_statusFeedbackWeak)
+ {
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ if (msgWindow)
+ msgWindow->GetStatusFeedback(aMsgFeedback);
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback(do_QueryReferent(m_statusFeedbackWeak));
+ statusFeedback.swap(*aMsgFeedback);
+ }
+ return *aMsgFeedback ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetStatusFeedback(nsIMsgStatusFeedback *aMsgFeedback)
+{
+ if (aMsgFeedback)
+ m_statusFeedbackWeak = do_GetWeakReference(aMsgFeedback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMaxProgress(int64_t *aMaxProgress)
+{
+ *aMaxProgress = mMaxProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMaxProgress(int64_t aMaxProgress)
+{
+ mMaxProgress = aMaxProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ *aLoadGroup = nullptr;
+ // note: it is okay to return a null load group and not return an error
+ // it's possible the url really doesn't have load group
+ nsCOMPtr<nsILoadGroup> loadGroup (do_QueryReferent(m_loadGroupWeak));
+ if (!loadGroup)
+ {
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ if (msgWindow)
+ {
+ // XXXbz This is really weird... why are we getting some
+ // random loadgroup we're not really a part of?
+ nsCOMPtr<nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ loadGroup = do_GetInterface(docShell);
+ m_loadGroupWeak = do_GetWeakReference(loadGroup);
+ }
+ }
+ loadGroup.swap(*aLoadGroup);
+ return *aLoadGroup ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetUpdatingFolder(bool *aResult)
+{
+ NS_ENSURE_ARG(aResult);
+ *aResult = m_updatingFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetUpdatingFolder(bool updatingFolder)
+{
+ m_updatingFolder = updatingFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgIsInLocalCache(bool *aMsgIsInLocalCache)
+{
+ NS_ENSURE_ARG(aMsgIsInLocalCache);
+ *aMsgIsInLocalCache = m_msgIsInLocalCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgIsInLocalCache(bool aMsgIsInLocalCache)
+{
+ m_msgIsInLocalCache = aMsgIsInLocalCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetSuppressErrorMsgs(bool *aSuppressErrorMsgs)
+{
+ NS_ENSURE_ARG(aSuppressErrorMsgs);
+ *aSuppressErrorMsgs = m_suppressErrorMsgs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetSuppressErrorMsgs(bool aSuppressErrorMsgs)
+{
+ m_suppressErrorMsgs = aSuppressErrorMsgs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::IsUrlType(uint32_t type, bool *isType)
+{
+ //base class doesn't know about any specific types
+ NS_ENSURE_ARG(isType);
+ *isType = false;
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetSearchSession(nsIMsgSearchSession *aSearchSession)
+{
+ if (aSearchSession)
+ m_searchSession = do_QueryInterface(aSearchSession);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetSearchSession(nsIMsgSearchSession **aSearchSession)
+{
+ NS_ENSURE_ARG(aSearchSession);
+ *aSearchSession = m_searchSession;
+ NS_IF_ADDREF(*aSearchSession);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// End nsIMsgMailNewsUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIURI support
+////////////////////////////////////////////////////////////////////////////////////
+
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetSpec(nsACString &aSpec)
+{
+ return m_baseURL->GetSpec(aSpec);
+}
+
+#define FILENAME_PART_LEN 10
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetSpec(const nsACString &aSpec)
+{
+ nsAutoCString spec(aSpec);
+ // Parse out "filename" attribute if present.
+ char *start, *end;
+ start = PL_strcasestr(spec.BeginWriting(),"?filename=");
+ if (!start)
+ start = PL_strcasestr(spec.BeginWriting(),"&filename=");
+ if (start)
+ { // Make sure we only get our own value.
+ end = PL_strcasestr((char*)(start+FILENAME_PART_LEN),"&");
+ if (end)
+ {
+ *end = 0;
+ mAttachmentFileName = start+FILENAME_PART_LEN;
+ *end = '&';
+ }
+ else
+ mAttachmentFileName = start+FILENAME_PART_LEN;
+ }
+
+ // Now, set the rest.
+ nsresult rv = m_baseURL->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check whether the URL is in normalised form.
+ nsCOMPtr <nsIMsgMessageUrl> msgUrl;
+ QueryInterface(NS_GET_IID(nsIMsgMessageUrl), getter_AddRefs(msgUrl));
+
+ nsAutoCString principalSpec;
+ if (!msgUrl || NS_FAILED(msgUrl->GetPrincipalSpec(principalSpec))) {
+ // If we can't get the principal spec, never QI this to nsIURIWithPrincipal.
+ m_isPrincipalURL = true;
+ } else {
+ m_isPrincipalURL = spec.Equals(principalSpec);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetPrePath(nsACString &aPrePath)
+{
+ return m_baseURL->GetPrePath(aPrePath);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetScheme(nsACString &aScheme)
+{
+ return m_baseURL->GetScheme(aScheme);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetScheme(const nsACString &aScheme)
+{
+ return m_baseURL->SetScheme(aScheme);
+}
+
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetUserPass(nsACString &aUserPass)
+{
+ return m_baseURL->GetUserPass(aUserPass);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetUserPass(const nsACString &aUserPass)
+{
+ return m_baseURL->SetUserPass(aUserPass);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetUsername(nsACString &aUsername)
+{
+ /* note: this will return an escaped string */
+ return m_baseURL->GetUsername(aUsername);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetUsername(const nsACString &aUsername)
+{
+ return m_baseURL->SetUsername(aUsername);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetPassword(nsACString &aPassword)
+{
+ return m_baseURL->GetPassword(aPassword);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetPassword(const nsACString &aPassword)
+{
+ return m_baseURL->SetPassword(aPassword);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetHostPort(nsACString &aHostPort)
+{
+ return m_baseURL->GetHostPort(aHostPort);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetHostPort(const nsACString &aHostPort)
+{
+ return m_baseURL->SetHostPort(aHostPort);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetHostAndPort(const nsACString &aHostPort)
+{
+ return m_baseURL->SetHostAndPort(aHostPort);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetHost(nsACString &aHost)
+{
+ return m_baseURL->GetHost(aHost);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetHost(const nsACString &aHost)
+{
+ return m_baseURL->SetHost(aHost);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetPort(int32_t *aPort)
+{
+ return m_baseURL->GetPort(aPort);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetPort(int32_t aPort)
+{
+ return m_baseURL->SetPort(aPort);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetPath(nsACString &aPath)
+{
+ return m_baseURL->GetPath(aPath);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetPath(const nsACString &aPath)
+{
+ return m_baseURL->SetPath(aPath);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiHost(nsACString &aHostA)
+{
+ return m_baseURL->GetAsciiHost(aHostA);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiHostPort(nsACString &aHostPortA)
+{
+ return m_baseURL->GetAsciiHostPort(aHostPortA);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiSpec(nsACString &aSpecA)
+{
+ return m_baseURL->GetAsciiSpec(aSpecA);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetOriginCharset(nsACString &aOriginCharset)
+{
+ return m_baseURL->GetOriginCharset(aOriginCharset);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetBaseURI(nsIURI **aBaseURI)
+{
+ NS_ENSURE_ARG_POINTER(aBaseURI);
+ return m_baseURL->QueryInterface(NS_GET_IID(nsIURI), (void**) aBaseURI);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::Equals(nsIURI *other, bool *_retval)
+{
+ // The passed-in URI might be a mail news url. Pass our inner URL to its
+ // Equals method. The other mail news url will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other)
+ return other->Equals(m_baseURL, _retval);
+
+ return m_baseURL->Equals(other, _retval);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::EqualsExceptRef(nsIURI *other, bool *result)
+{
+ // The passed-in URI might be a mail news url. Pass our inner URL to its
+ // Equals method. The other mail news url will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other)
+ return other->EqualsExceptRef(m_baseURL, result);
+
+ return m_baseURL->EqualsExceptRef(other, result);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::GetSpecIgnoringRef(nsACString &result)
+{
+ return m_baseURL->GetSpecIgnoringRef(result);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::GetHasRef(bool *result)
+{
+ return m_baseURL->GetHasRef(result);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SchemeIs(const char *aScheme, bool *_retval)
+{
+ return m_baseURL->SchemeIs(aScheme, _retval);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef, nsIURI** _retval)
+{
+ nsresult rv;
+ nsAutoCString urlSpec;
+ nsCOMPtr<nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+ rv = GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = ioService->NewURI(urlSpec, nullptr, nullptr, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // add the msg window to the cloned url
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgMailNewsUrl = do_QueryInterface(*_retval, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgMailNewsUrl->SetMsgWindow(msgWindow);
+ }
+
+ if (aRefHandlingMode == nsIMsgMailNewsUrl::REPLACE_REF) {
+ rv = (*_retval)->SetRef(newRef);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (aRefHandlingMode == nsIMsgMailNewsUrl::IGNORE_REF) {
+ rv = (*_retval)->SetRef(EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::Clone(nsIURI **_retval)
+{
+ return CloneInternal(nsIMsgMailNewsUrl::HONOR_REF, EmptyCString(), _retval);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::CloneIgnoringRef(nsIURI** _retval)
+{
+ return CloneInternal(nsIMsgMailNewsUrl::IGNORE_REF, EmptyCString(), _retval);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::CloneWithNewRef(const nsACString& newRef, nsIURI** _retval)
+{
+ return CloneInternal(nsIMsgMailNewsUrl::REPLACE_REF, newRef, _retval);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::Resolve(const nsACString &relativePath, nsACString &result)
+{
+ // only resolve anchor urls....i.e. urls which start with '#' against the mailnews url...
+ // everything else shouldn't be resolved against mailnews urls.
+ nsresult rv = NS_OK;
+
+ if (!relativePath.IsEmpty() && relativePath.First() == '#') // an anchor
+ return m_baseURL->Resolve(relativePath, result);
+ else
+ {
+ // if relativePath is a complete url with it's own scheme then allow it...
+ nsCOMPtr<nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+ nsAutoCString scheme;
+
+ rv = ioService->ExtractScheme(relativePath, scheme);
+ // if we have a fully qualified scheme then pass the relative path back as the result
+ if (NS_SUCCEEDED(rv) && !scheme.IsEmpty())
+ {
+ result = relativePath;
+ rv = NS_OK;
+ }
+ else
+ {
+ result.Truncate();
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetDirectory(nsACString &aDirectory)
+{
+ return m_baseURL->GetDirectory(aDirectory);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetDirectory(const nsACString &aDirectory)
+{
+
+ return m_baseURL->SetDirectory(aDirectory);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFileName(nsACString &aFileName)
+{
+ if (!mAttachmentFileName.IsEmpty())
+ {
+ aFileName = mAttachmentFileName;
+ return NS_OK;
+ }
+ return m_baseURL->GetFileName(aFileName);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFileBaseName(nsACString &aFileBaseName)
+{
+ return m_baseURL->GetFileBaseName(aFileBaseName);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetFileBaseName(const nsACString &aFileBaseName)
+{
+ return m_baseURL->SetFileBaseName(aFileBaseName);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFileExtension(nsACString &aFileExtension)
+{
+ if (!mAttachmentFileName.IsEmpty())
+ {
+ int32_t pos = mAttachmentFileName.RFindChar(char16_t('.'));
+ if (pos > 0)
+ aFileExtension = Substring(mAttachmentFileName, pos + 1 /* skip the '.' */);
+ return NS_OK;
+ }
+ return m_baseURL->GetFileExtension(aFileExtension);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetFileExtension(const nsACString &aFileExtension)
+{
+ return m_baseURL->SetFileExtension(aFileExtension);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetFileName(const nsACString &aFileName)
+{
+ mAttachmentFileName = aFileName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetQuery(nsACString &aQuery)
+{
+ return m_baseURL->GetQuery(aQuery);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetQuery(const nsACString &aQuery)
+{
+ return m_baseURL->SetQuery(aQuery);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetRef(nsACString &aRef)
+{
+ return m_baseURL->GetRef(aRef);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetRef(const nsACString &aRef)
+{
+ return m_baseURL->SetRef(aRef);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFilePath(nsACString &o_DirFile)
+{
+ return m_baseURL->GetFilePath(o_DirFile);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetFilePath(const nsACString &i_DirFile)
+{
+ return m_baseURL->SetFilePath(i_DirFile);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetCommonBaseSpec(nsIURI *uri2, nsACString &result)
+{
+ return m_baseURL->GetCommonBaseSpec(uri2, result);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetRelativeSpec(nsIURI *uri2, nsACString &result)
+{
+ return m_baseURL->GetRelativeSpec(uri2, result);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMemCacheEntry(nsICacheEntry *memCacheEntry)
+{
+ m_memCacheEntry = memCacheEntry;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMemCacheEntry(nsICacheEntry **memCacheEntry)
+{
+ NS_ENSURE_ARG(memCacheEntry);
+ nsresult rv = NS_OK;
+
+ if (m_memCacheEntry)
+ {
+ *memCacheEntry = m_memCacheEntry;
+ NS_ADDREF(*memCacheEntry);
+ }
+ else
+ {
+ *memCacheEntry = nullptr;
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMimeHeaders(nsIMimeHeaders * *mimeHeaders)
+{
+ NS_ENSURE_ARG_POINTER(mimeHeaders);
+ NS_IF_ADDREF(*mimeHeaders = mMimeHeaders);
+ return (mMimeHeaders) ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMimeHeaders(nsIMimeHeaders *mimeHeaders)
+{
+ mMimeHeaders = mimeHeaders;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::LoadURI(nsIDocShell* docShell,
+ nsIDocShellLoadInfo* loadInfo,
+ uint32_t aLoadFlags)
+{
+ NS_ENSURE_ARG_POINTER(docShell);
+ return docShell->LoadURI(this, loadInfo, aLoadFlags, false);
+}
+
+#define SAVE_BUF_SIZE FILE_IO_BUFFER_SIZE
+class nsMsgSaveAsListener : public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsMsgSaveAsListener(nsIFile *aFile, bool addDummyEnvelope);
+ nsresult SetupMsgWriteStream(nsIFile *aFile, bool addDummyEnvelope);
+protected:
+ virtual ~nsMsgSaveAsListener();
+ nsCOMPtr<nsIOutputStream> m_outputStream;
+ nsCOMPtr<nsIFile> m_outputFile;
+ bool m_addDummyEnvelope;
+ bool m_writtenData;
+ uint32_t m_leftOver;
+ char m_dataBuffer[SAVE_BUF_SIZE+1]; // temporary buffer for this save operation
+
+};
+
+NS_IMPL_ISUPPORTS(nsMsgSaveAsListener,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+nsMsgSaveAsListener::nsMsgSaveAsListener(nsIFile *aFile, bool addDummyEnvelope)
+{
+ m_outputFile = aFile;
+ m_writtenData = false;
+ m_addDummyEnvelope = addDummyEnvelope;
+ m_leftOver = 0;
+}
+
+nsMsgSaveAsListener::~nsMsgSaveAsListener()
+{
+}
+
+NS_IMETHODIMP nsMsgSaveAsListener::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSaveAsListener::OnStopRequest(nsIRequest *request, nsISupports * aCtxt, nsresult aStatus)
+{
+ if (m_outputStream)
+ {
+ m_outputStream->Flush();
+ m_outputStream->Close();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSaveAsListener::OnDataAvailable(nsIRequest* request,
+ nsISupports* aSupport,
+ nsIInputStream* inStream,
+ uint64_t srcOffset,
+ uint32_t count)
+{
+ nsresult rv;
+ uint64_t available;
+ rv = inStream->Available(&available);
+ if (!m_writtenData)
+ {
+ m_writtenData = true;
+ rv = SetupMsgWriteStream(m_outputFile, m_addDummyEnvelope);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool useCanonicalEnding = false;
+ nsCOMPtr <nsIMsgMessageUrl> msgUrl = do_QueryInterface(aSupport);
+ if (msgUrl)
+ msgUrl->GetCanonicalLineEnding(&useCanonicalEnding);
+
+ const char *lineEnding = (useCanonicalEnding) ? CRLF : MSG_LINEBREAK;
+ uint32_t lineEndingLength = (useCanonicalEnding) ? 2 : MSG_LINEBREAK_LEN;
+
+ uint32_t readCount, maxReadCount = SAVE_BUF_SIZE - m_leftOver;
+ uint32_t writeCount;
+ char *start, *end, lastCharInPrevBuf = '\0';
+ uint32_t linebreak_len = 0;
+
+ while (count > 0)
+ {
+ if (count < maxReadCount)
+ maxReadCount = count;
+ rv = inStream->Read(m_dataBuffer + m_leftOver,
+ maxReadCount,
+ &readCount);
+ if (NS_FAILED(rv)) return rv;
+
+ m_leftOver += readCount;
+ m_dataBuffer[m_leftOver] = '\0';
+
+ start = m_dataBuffer;
+ // make sure we don't insert another LF, accidentally, by ignoring
+ // second half of CRLF spanning blocks.
+ if (lastCharInPrevBuf == '\r' && *start == '\n')
+ start++;
+
+ end = PL_strchr(start, '\r');
+ if (!end)
+ end = PL_strchr(start, '\n');
+ else if (*(end+1) == '\n' && linebreak_len == 0)
+ linebreak_len = 2;
+
+ if (linebreak_len == 0) // not initialize yet
+ linebreak_len = 1;
+
+ count -= readCount;
+ maxReadCount = SAVE_BUF_SIZE - m_leftOver;
+
+ if (!end && count > maxReadCount)
+ // must be a very very long line; sorry cannot handle it
+ return NS_ERROR_FAILURE;
+
+ while (start && end)
+ {
+ if (m_outputStream &&
+ PL_strncasecmp(start, "X-Mozilla-Status:", 17) &&
+ PL_strncasecmp(start, "X-Mozilla-Status2:", 18) &&
+ PL_strncmp(start, "From - ", 7))
+ {
+ rv = m_outputStream->Write(start, end-start, &writeCount);
+ nsresult tmp = m_outputStream->Write(lineEnding, lineEndingLength, &writeCount);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+ start = end+linebreak_len;
+ if (start >= m_dataBuffer + m_leftOver)
+ {
+ maxReadCount = SAVE_BUF_SIZE;
+ m_leftOver = 0;
+ break;
+ }
+ end = PL_strchr(start, '\r');
+ if (!end)
+ end = PL_strchr(start, '\n');
+ if (start && !end)
+ {
+ m_leftOver -= (start - m_dataBuffer);
+ memcpy(m_dataBuffer, start,
+ m_leftOver+1); // including null
+ maxReadCount = SAVE_BUF_SIZE - m_leftOver;
+ }
+ }
+ if (NS_FAILED(rv)) return rv;
+ if (end)
+ lastCharInPrevBuf = *end;
+ }
+ return rv;
+
+ // rv = m_outputStream->WriteFrom(inStream, std::min(available, count), &bytesWritten);
+}
+
+nsresult nsMsgSaveAsListener::SetupMsgWriteStream(nsIFile *aFile, bool addDummyEnvelope)
+{
+ // If the file already exists, delete it, but do this before
+ // getting the outputstream.
+ // Due to bug 328027, the nsSaveMsgListener created in
+ // nsMessenger::SaveAs now opens the stream on the nsIFile
+ // object, thus creating an empty file. Actual save operations for
+ // IMAP and NNTP use this nsMsgSaveAsListener here, though, so we
+ // have to close the stream before deleting the file, else data
+ // would still be written happily into a now non-existing file.
+ // (Windows doesn't care, btw, just unixoids do...)
+ aFile->Remove(false);
+
+ nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream),
+ aFile, -1, 0666);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_outputStream && addDummyEnvelope)
+ {
+ nsAutoCString result;
+ uint32_t writeCount;
+
+ time_t now = time((time_t*) 0);
+ char *ct = ctime(&now);
+ // Remove the ending new-line character.
+ ct[24] = '\0';
+ result = "From - ";
+ result += ct;
+ result += MSG_LINEBREAK;
+ m_outputStream->Write(result.get(), result.Length(), &writeCount);
+
+ result = "X-Mozilla-Status: 0001";
+ result += MSG_LINEBREAK;
+ result += "X-Mozilla-Status2: 00000000";
+ result += MSG_LINEBREAK;
+ m_outputStream->Write(result.get(), result.Length(), &writeCount);
+ }
+
+ return rv;
+}
+
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetSaveAsListener(bool addDummyEnvelope,
+ nsIFile *aFile, nsIStreamListener **aSaveListener)
+{
+ NS_ENSURE_ARG_POINTER(aSaveListener);
+ nsMsgSaveAsListener *saveAsListener = new nsMsgSaveAsListener(aFile, addDummyEnvelope);
+ return saveAsListener->QueryInterface(NS_GET_IID(nsIStreamListener), (void **) aSaveListener);
+}
+
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetFolder(nsIMsgFolder * /* aFolder */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFolder(nsIMsgFolder ** /* aFolder */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgHeaderSink(nsIMsgHeaderSink * *aMsgHdrSink)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdrSink);
+ NS_IF_ADDREF(*aMsgHdrSink = mMsgHeaderSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgHeaderSink(nsIMsgHeaderSink * aMsgHdrSink)
+{
+ mMsgHeaderSink = aMsgHdrSink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetIsMessageUri(bool *aIsMessageUri)
+{
+ NS_ENSURE_ARG(aIsMessageUri);
+ nsAutoCString scheme;
+ m_baseURL->GetScheme(scheme);
+ *aIsMessageUri = StringEndsWith(scheme, NS_LITERAL_CSTRING("-message"));
+ return NS_OK;
+}
diff --git a/mailnews/base/util/nsMsgMailNewsUrl.h b/mailnews/base/util/nsMsgMailNewsUrl.h
new file mode 100644
index 000000000..3cd3b2ef2
--- /dev/null
+++ b/mailnews/base/util/nsMsgMailNewsUrl.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgMailNewsUrl_h___
+#define nsMsgMailNewsUrl_h___
+
+#include "nscore.h"
+#include "nsISupports.h"
+#include "nsIUrlListener.h"
+#include "nsTObserverArray.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIMimeHeaders.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIURL.h"
+#include "nsIURIWithPrincipal.h"
+#include "nsILoadGroup.h"
+#include "nsIMsgSearchSession.h"
+#include "nsICacheEntry.h"
+#include "nsICacheSession.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsWeakReference.h"
+#include "nsStringGlue.h"
+
+///////////////////////////////////////////////////////////////////////////////////
+// Okay, I found that all of the mail and news url interfaces needed to support
+// several common interfaces (in addition to those provided through nsIURI).
+// So I decided to group them all in this implementation so we don't have to
+// duplicate the code.
+//
+//////////////////////////////////////////////////////////////////////////////////
+
+class NS_MSG_BASE nsMsgMailNewsUrl : public nsIMsgMailNewsUrl,
+ public nsIURIWithPrincipal
+{
+public:
+ nsMsgMailNewsUrl();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGMAILNEWSURL
+ NS_DECL_NSIURI
+ NS_DECL_NSIURL
+ NS_DECL_NSIURIWITHPRINCIPAL
+
+protected:
+ virtual ~nsMsgMailNewsUrl();
+
+ nsCOMPtr<nsIURL> m_baseURL;
+ nsCOMPtr<nsIPrincipal> m_principal;
+ nsWeakPtr m_statusFeedbackWeak;
+ nsWeakPtr m_msgWindowWeak;
+ nsWeakPtr m_loadGroupWeak;
+ nsCOMPtr<nsIMimeHeaders> mMimeHeaders;
+ nsCOMPtr<nsIMsgSearchSession> m_searchSession;
+ nsCOMPtr<nsICacheEntry> m_memCacheEntry;
+ nsCOMPtr<nsIMsgHeaderSink> mMsgHeaderSink;
+ char *m_errorMessage;
+ int64_t mMaxProgress;
+ bool m_runningUrl;
+ bool m_updatingFolder;
+ bool m_msgIsInLocalCache;
+ bool m_suppressErrorMsgs;
+ bool m_isPrincipalURL;
+
+ // the following field is really a bit of a hack to make
+ // open attachments work. The external applications code sometimes tries to figure out the right
+ // handler to use by looking at the file extension of the url we are trying to load. Unfortunately,
+ // the attachment file name really isn't part of the url string....so we'll store it here...and if
+ // the url we are running is an attachment url, we'll set it here. Then when the helper apps code
+ // asks us for it, we'll return the right value.
+ nsCString mAttachmentFileName;
+
+ nsTObserverArray<nsCOMPtr<nsIUrlListener> > mUrlListeners;
+};
+
+#endif /* nsMsgMailNewsUrl_h___ */
diff --git a/mailnews/base/util/nsMsgProtocol.cpp b/mailnews/base/util/nsMsgProtocol.cpp
new file mode 100644
index 000000000..c86995189
--- /dev/null
+++ b/mailnews/base/util/nsMsgProtocol.cpp
@@ -0,0 +1,1550 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsStringGlue.h"
+#include "nsMsgProtocol.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgBaseCID.h"
+#include "nsIStreamTransportService.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIIOService.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIWebProgressListener.h"
+#include "nsIPipe.h"
+#include "nsIPrompt.h"
+#include "prprf.h"
+#include "plbase64.h"
+#include "nsIStringBundle.h"
+#include "nsIProxyInfo.h"
+#include "nsThreadUtils.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsMsgUtils.h"
+#include "nsILineInputStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIInputStreamPump.h"
+#include "nsMimeTypes.h"
+#include "mozilla/Services.h"
+#include "nsContentSecurityManager.h"
+
+#undef PostMessage // avoid to collision with WinUser.h
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsMsgProtocol, nsIChannel, nsIStreamListener,
+ nsIRequestObserver, nsIRequest, nsITransportEventSink)
+
+static char16_t *FormatStringWithHostNameByName(const char16_t* stringName, nsIMsgMailNewsUrl *msgUri);
+
+
+nsMsgProtocol::nsMsgProtocol(nsIURI * aURL)
+{
+ m_flags = 0;
+ m_readCount = 0;
+ mLoadFlags = 0;
+ m_socketIsOpen = false;
+ mContentLength = -1;
+
+ GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "tempMessage.eml",
+ getter_AddRefs(m_tempMsgFile));
+
+ mSuppressListenerNotifications = false;
+ InitFromURI(aURL);
+}
+
+nsresult nsMsgProtocol::InitFromURI(nsIURI *aUrl)
+{
+ m_url = aUrl;
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ if (mailUrl)
+ {
+ mailUrl->GetLoadGroup(getter_AddRefs(m_loadGroup));
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ mProgressEventSink = do_QueryInterface(statusFeedback);
+ }
+
+ // Reset channel data in case the object is reused and initialised again.
+ mCharset.Truncate();
+
+ return NS_OK;
+}
+
+nsMsgProtocol::~nsMsgProtocol()
+{}
+
+
+static bool gGotTimeoutPref;
+static int32_t gSocketTimeout = 60;
+
+nsresult
+nsMsgProtocol::GetQoSBits(uint8_t *aQoSBits)
+{
+ NS_ENSURE_ARG_POINTER(aQoSBits);
+ const char* protocol = GetType();
+
+ if (!protocol)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsAutoCString prefName("mail.");
+ prefName.Append(protocol);
+ prefName.Append(".qos");
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t val;
+ rv = prefBranch->GetIntPref(prefName.get(), &val);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aQoSBits = (uint8_t) clamped(val, 0, 0xff);
+ return NS_OK;
+}
+
+nsresult
+nsMsgProtocol::OpenNetworkSocketWithInfo(const char * aHostName,
+ int32_t aGetPort,
+ const char *connectionType,
+ nsIProxyInfo *aProxyInfo,
+ nsIInterfaceRequestor* callbacks)
+{
+ NS_ENSURE_ARG(aHostName);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISocketTransportService> socketService (do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(socketService, NS_ERROR_FAILURE);
+
+ // with socket connections we want to read as much data as arrives
+ m_readCount = -1;
+
+ nsCOMPtr<nsISocketTransport> strans;
+ rv = socketService->CreateTransport(&connectionType, connectionType != nullptr,
+ nsDependentCString(aHostName),
+ aGetPort, aProxyInfo,
+ getter_AddRefs(strans));
+ if (NS_FAILED(rv)) return rv;
+
+ strans->SetSecurityCallbacks(callbacks);
+
+ // creates cyclic reference!
+ nsCOMPtr<nsIThread> currentThread(do_GetCurrentThread());
+ strans->SetEventSink(this, currentThread);
+
+ m_socketIsOpen = false;
+ m_transport = strans;
+
+ if (!gGotTimeoutPref)
+ {
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (prefBranch)
+ {
+ prefBranch->GetIntPref("mailnews.tcptimeout", &gSocketTimeout);
+ gGotTimeoutPref = true;
+ }
+ }
+ strans->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT, gSocketTimeout + 60);
+ strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, gSocketTimeout);
+
+ uint8_t qos;
+ rv = GetQoSBits(&qos);
+ if (NS_SUCCEEDED(rv))
+ strans->SetQoSBits(qos);
+
+ return SetupTransportState();
+}
+
+nsresult nsMsgProtocol::GetFileFromURL(nsIURI * aURL, nsIFile **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aURL);
+ NS_ENSURE_ARG_POINTER(aResult);
+ // extract the file path from the uri...
+ nsAutoCString urlSpec;
+ aURL->GetPath(urlSpec);
+ urlSpec.Insert(NS_LITERAL_CSTRING("file://"), 0);
+ nsresult rv;
+
+// dougt - there should be an easier way!
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), urlSpec.get())))
+ return rv;
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
+ if (!fileURL) return NS_ERROR_FAILURE;
+
+ return fileURL->GetFile(aResult);
+ // dougt
+}
+
+nsresult nsMsgProtocol::OpenFileSocket(nsIURI * aURL, uint32_t aStartPosition, int32_t aReadCount)
+{
+ // mscott - file needs to be encoded directly into aURL. I should be able to get
+ // rid of this method completely.
+
+ nsresult rv = NS_OK;
+ m_readCount = aReadCount;
+ nsCOMPtr <nsIFile> file;
+
+ rv = GetFileFromURL(aURL, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
+ if (NS_FAILED(rv)) return rv;
+
+ // create input stream transport
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = sts->CreateInputTransport(stream, int64_t(aStartPosition),
+ int64_t(aReadCount), true,
+ getter_AddRefs(m_transport));
+
+ m_socketIsOpen = false;
+ return rv;
+}
+
+nsresult nsMsgProtocol::GetTopmostMsgWindow(nsIMsgWindow **aWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> mailSession(do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mailSession->GetTopmostMsgWindow(aWindow);
+}
+
+nsresult nsMsgProtocol::SetupTransportState()
+{
+ if (!m_socketIsOpen && m_transport)
+ {
+ nsresult rv;
+
+ // open buffered, blocking output stream
+ rv = m_transport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(m_outputStream));
+ if (NS_FAILED(rv)) return rv;
+ // we want to open the stream
+ } // if m_transport
+
+ return NS_OK;
+}
+
+nsresult nsMsgProtocol::CloseSocket()
+{
+ nsresult rv = NS_OK;
+ // release all of our socket state
+ m_socketIsOpen = false;
+ m_inputStream = nullptr;
+ m_outputStream = nullptr;
+ if (m_transport) {
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
+ if (strans) {
+ strans->SetEventSink(nullptr, nullptr); // break cyclic reference!
+ }
+ }
+ // we need to call Cancel so that we remove the socket transport from the mActiveTransportList. see bug #30648
+ if (m_request) {
+ rv = m_request->Cancel(NS_BINDING_ABORTED);
+ }
+ m_request = nullptr;
+ if (m_transport) {
+ m_transport->Close(NS_BINDING_ABORTED);
+ m_transport = nullptr;
+ }
+
+ return rv;
+}
+
+/*
+* Writes the data contained in dataBuffer into the current output stream. It also informs
+* the transport layer that this data is now available for transmission.
+* Returns a positive number for success, 0 for failure (not all the bytes were written to the
+* stream, etc). We need to make another pass through this file to install an error system (mscott)
+*
+* No logging is done in the base implementation, so aSuppressLogging is ignored.
+*/
+
+nsresult nsMsgProtocol::SendData(const char * dataBuffer, bool aSuppressLogging)
+{
+ uint32_t writeCount = 0;
+
+ if (dataBuffer && m_outputStream)
+ return m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer), &writeCount);
+ // TODO make sure all the bytes in PL_strlen(dataBuffer) were written
+ else
+ return NS_ERROR_INVALID_ARG;
+}
+
+// Whenever data arrives from the connection, core netlib notifices the protocol by calling
+// OnDataAvailable. We then read and process the incoming data from the input stream.
+NS_IMETHODIMP nsMsgProtocol::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count)
+{
+ // right now, this really just means turn around and churn through the state machine
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(ctxt);
+ return ProcessProtocolState(uri, inStr, sourceOffset, count);
+}
+
+NS_IMETHODIMP nsMsgProtocol::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(ctxt, &rv);
+ if (NS_SUCCEEDED(rv) && aMsgUrl)
+ {
+ rv = aMsgUrl->SetUrlState(true, NS_OK);
+ if (m_loadGroup)
+ m_loadGroup->AddRequest(static_cast<nsIRequest *>(this), nullptr /* context isupports */);
+ }
+
+ // if we are set up as a channel, we should notify our channel listener that we are starting...
+ // so pass in ourself as the channel and not the underlying socket or file channel the protocol
+ // happens to be using
+ if (!mSuppressListenerNotifications && m_channelListener)
+ {
+ if (!m_channelContext)
+ m_channelContext = do_QueryInterface(ctxt);
+ rv = m_channelListener->OnStartRequest(this, m_channelContext);
+ }
+
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
+
+ if (strans)
+ strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, gSocketTimeout);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+void nsMsgProtocol::ShowAlertMessage(nsIMsgMailNewsUrl *aMsgUrl, nsresult aStatus)
+{
+ const char16_t* errorString = nullptr;
+ switch (aStatus)
+ {
+ case NS_ERROR_UNKNOWN_HOST:
+ case NS_ERROR_UNKNOWN_PROXY_HOST:
+ errorString = u"unknownHostError";
+ break;
+ case NS_ERROR_CONNECTION_REFUSED:
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ errorString = u"connectionRefusedError";
+ break;
+ case NS_ERROR_NET_TIMEOUT:
+ errorString = u"netTimeoutError";
+ break;
+ case NS_ERROR_NET_RESET:
+ errorString = u"unknownHostError"; // ESR HACK: Can't use new correct string "netResetError"
+ break;
+ case NS_ERROR_NET_INTERRUPT:
+ errorString = u"unknownHostError"; // ESR HACK: Can't use new correct string u"netInterruptError"
+ break;
+ default:
+ // Leave the string as nullptr.
+ break;
+ }
+
+ NS_ASSERTION(errorString, "unknown error, but don't alert user.");
+ if (errorString)
+ {
+ nsString errorMsg;
+ errorMsg.Adopt(FormatStringWithHostNameByName(errorString, aMsgUrl));
+ if (errorMsg.IsEmpty())
+ {
+ errorMsg.Assign(NS_LITERAL_STRING("[StringID "));
+ errorMsg.Append(errorString);
+ errorMsg.AppendLiteral("?]");
+ }
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID);
+ if (mailSession)
+ mailSession->AlertUser(errorMsg, aMsgUrl);
+ }
+}
+
+// stop binding is a "notification" informing us that the stream associated with aURL is going away.
+NS_IMETHODIMP nsMsgProtocol::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+
+ // if we are set up as a channel, we should notify our channel listener that we are starting...
+ // so pass in ourself as the channel and not the underlying socket or file channel the protocol
+ // happens to be using
+ if (!mSuppressListenerNotifications && m_channelListener)
+ rv = m_channelListener->OnStopRequest(this, m_channelContext, aStatus);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(ctxt, &rv);
+ if (NS_SUCCEEDED(rv) && msgUrl)
+ {
+ rv = msgUrl->SetUrlState(false, aStatus); // Always returns NS_OK.
+ if (m_loadGroup)
+ m_loadGroup->RemoveRequest(static_cast<nsIRequest *>(this), nullptr, aStatus);
+
+ // !m_channelContext because if we're set up as a channel, then the remove
+ // request above will handle alerting the user, so we don't need to.
+ //
+ // !NS_BINDING_ABORTED because we don't want to see an alert if the user
+ // cancelled the operation. also, we'll get here because we call Cancel()
+ // to force removal of the nsSocketTransport. see CloseSocket()
+ // bugs #30775 and #30648 relate to this
+ if (!m_channelContext && NS_FAILED(aStatus) &&
+ (aStatus != NS_BINDING_ABORTED))
+ ShowAlertMessage(msgUrl, aStatus);
+ } // if we have a mailnews url.
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ mProgressEventSink = nullptr;
+ // Call CloseSocket(), in case we got here because the server dropped the
+ // connection while reading, and we never get a chance to get back into
+ // the protocol state machine via OnDataAvailable.
+ if (m_socketIsOpen)
+ CloseSocket();
+
+ return rv;
+}
+
+nsresult nsMsgProtocol::GetPromptDialogFromUrl(nsIMsgMailNewsUrl * aMsgUrl, nsIPrompt ** aPromptDialog)
+{
+ // get the nsIPrompt interface from the message window associated wit this url.
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ aMsgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ NS_ENSURE_TRUE(msgWindow, NS_ERROR_FAILURE);
+
+ msgWindow->GetPromptDialog(aPromptDialog);
+
+ NS_ENSURE_TRUE(*aPromptDialog, NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+nsresult nsMsgProtocol::LoadUrl(nsIURI * aURL, nsISupports * aConsumer)
+{
+ // okay now kick us off to the next state...
+ // our first state is a process state so drive the state machine...
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(aURL, &rv);
+
+ if (NS_SUCCEEDED(rv) && aMsgUrl)
+ {
+ bool msgIsInLocalCache;
+ aMsgUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
+
+ rv = aMsgUrl->SetUrlState(true, NS_OK); // set the url as a url currently being run...
+
+ // if the url is given a stream consumer then we should use it to forward calls to...
+ if (!m_channelListener && aConsumer) // if we don't have a registered listener already
+ {
+ m_channelListener = do_QueryInterface(aConsumer);
+ if (!m_channelContext)
+ m_channelContext = do_QueryInterface(aURL);
+ }
+
+ if (!m_socketIsOpen)
+ {
+ nsCOMPtr<nsISupports> urlSupports = do_QueryInterface(aURL);
+ if (m_transport)
+ {
+ // don't open the input stream more than once
+ if (!m_inputStream)
+ {
+ // open buffered, asynchronous input stream
+ rv = m_transport->OpenInputStream(0, 0, 0, getter_AddRefs(m_inputStream));
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump),
+ m_inputStream, -1, m_readCount);
+ if (NS_FAILED(rv)) return rv;
+
+ m_request = pump; // keep a reference to the pump so we can cancel it
+
+ // put us in a state where we are always notified of incoming data
+ rv = pump->AsyncRead(this, urlSupports);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncRead failed");
+ m_socketIsOpen = true; // mark the channel as open
+ }
+ } // if we got an event queue service
+ else if (!msgIsInLocalCache) // the connection is already open so we should begin processing our new url...
+ rv = ProcessProtocolState(aURL, nullptr, 0, 0);
+ }
+
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////
+// The rest of this file is mostly nsIChannel mumbo jumbo stuff
+///////////////////////////////////////////////////////////////////////
+
+nsresult nsMsgProtocol::SetUrl(nsIURI * aURL)
+{
+ m_url = aURL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+ m_loadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetOriginalURI(nsIURI* *aURI)
+{
+ *aURI = m_originalUrl ? m_originalUrl : m_url;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetOriginalURI(nsIURI* aURI)
+{
+ m_originalUrl = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetURI(nsIURI* *aURI)
+{
+ *aURI = m_url;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::Open(nsIInputStream **_retval)
+{
+ return NS_ImplementChannelOpen(this, _retval);
+}
+
+NS_IMETHODIMP nsMsgProtocol::Open2(nsIInputStream **_retval)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(_retval);
+}
+
+NS_IMETHODIMP nsMsgProtocol::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ int32_t port;
+ nsresult rv = m_url->GetPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString scheme;
+ rv = m_url->GetScheme(scheme);
+ if (NS_FAILED(rv))
+ return rv;
+
+
+ rv = NS_CheckPortSafety(port, scheme.get());
+ if (NS_FAILED(rv))
+ return rv;
+
+ // set the stream listener and then load the url
+ m_channelContext = ctxt;
+ m_channelListener = listener;
+ return LoadUrl(m_url, nullptr);
+}
+
+NS_IMETHODIMP nsMsgProtocol::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK; // don't fail when trying to set this
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetContentType(nsACString &aContentType)
+{
+ // as url dispatching matures, we'll be intelligent and actually start
+ // opening the url before specifying the content type. This will allow
+ // us to optimize the case where the message url actual refers to
+ // a part in the message that has a content type that is not message/rfc822
+
+ if (mContentType.IsEmpty())
+ aContentType.AssignLiteral("message/rfc822");
+ else
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetContentType(const nsACString &aContentType)
+{
+ nsAutoCString charset;
+ nsresult rv = NS_ParseResponseContentType(aContentType, mContentType, charset);
+ if (NS_FAILED(rv) || mContentType.IsEmpty())
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetContentCharset(nsACString &aContentCharset)
+{
+ aContentCharset.Assign(mCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetContentCharset(const nsACString &aContentCharset)
+{
+ mCharset.Assign(aContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetContentLength(int64_t *aContentLength)
+{
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetContentLength(int64_t aContentLength)
+{
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ *aSecurityInfo = nullptr;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetName(nsACString &result)
+{
+ if (m_url)
+ return m_url->GetSpec(result);
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetOwner(nsISupports * *aPrincipal)
+{
+ *aPrincipal = mOwner;
+ NS_IF_ADDREF(*aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetOwner(nsISupports * aPrincipal)
+{
+ mOwner = aPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetLoadGroup(nsILoadGroup * *aLoadGroup)
+{
+ *aLoadGroup = m_loadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ *aLoadInfo = m_loadInfo;
+ NS_IF_ADDREF(*aLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetLoadInfo(nsILoadInfo *aLoadInfo)
+{
+ m_loadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::GetNotificationCallbacks(nsIInterfaceRequestor* *aNotificationCallbacks)
+{
+ *aNotificationCallbacks = mCallbacks.get();
+ NS_IF_ADDREF(*aNotificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
+{
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::OnTransportStatus(nsITransport *transport, nsresult status,
+ int64_t progress, int64_t progressMax)
+{
+ if ((mLoadFlags & LOAD_BACKGROUND) || !m_url)
+ return NS_OK;
+
+ // these transport events should not generate any status messages
+ if (status == NS_NET_STATUS_RECEIVING_FROM ||
+ status == NS_NET_STATUS_SENDING_TO)
+ return NS_OK;
+
+ if (!mProgressEventSink)
+ {
+ NS_QueryNotificationCallbacks(mCallbacks, m_loadGroup, mProgressEventSink);
+ if (!mProgressEventSink)
+ return NS_OK;
+ }
+
+ nsAutoCString host;
+ m_url->GetHost(host);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ mailnewsUrl->GetServer(getter_AddRefs(server));
+ if (server)
+ server->GetRealHostName(host);
+ }
+ mProgressEventSink->OnStatus(this, nullptr, status,
+ NS_ConvertUTF8toUTF16(host).get());
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// From nsIRequest
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMsgProtocol::IsPending(bool *result)
+{
+ *result = m_channelListener != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetStatus(nsresult *status)
+{
+ if (m_request)
+ return m_request->GetStatus(status);
+
+ *status = NS_OK;
+ return *status;
+}
+
+NS_IMETHODIMP nsMsgProtocol::Cancel(nsresult status)
+{
+ NS_ASSERTION(m_request,"no channel");
+ if (!m_request)
+ return NS_ERROR_FAILURE;
+
+ return m_request->Cancel(status);
+}
+
+NS_IMETHODIMP nsMsgProtocol::Suspend()
+{
+ if (m_request)
+ return m_request->Suspend();
+
+ NS_WARNING("no request to suspend");
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsMsgProtocol::Resume()
+{
+ if (m_request)
+ return m_request->Resume();
+
+ NS_WARNING("no request to resume");
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsMsgProtocol::PostMessage(nsIURI* url, nsIFile *postFile)
+{
+ if (!url || !postFile) return NS_ERROR_NULL_POINTER;
+
+#define POST_DATA_BUFFER_SIZE 2048
+
+ // mscott -- this function should be re-written to use the file url code
+ // so it can be asynch
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), postFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(inputStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsCString line;
+ nsCString outputBuffer;
+
+ do
+ {
+ lineInputStream->ReadLine(line, &more);
+
+ /* escape starting periods
+ */
+ if (line.CharAt(0) == '.')
+ line.Insert('.', 0);
+ line.Append(NS_LITERAL_CSTRING(CRLF));
+ outputBuffer.Append(line);
+ // test hack by mscott. If our buffer is almost full, then
+ // send it off & reset ourselves
+ // to make more room.
+ if (outputBuffer.Length() > POST_DATA_BUFFER_SIZE || !more)
+ {
+ rv = SendData(outputBuffer.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ // does this keep the buffer around? That would be best.
+ // Maybe SetLength(0) instead?
+ outputBuffer.Truncate();
+ }
+ } while (more);
+
+ return NS_OK;
+}
+
+nsresult nsMsgProtocol::DoGSSAPIStep1(const char *service, const char *username, nsCString &response)
+{
+ nsresult rv;
+#ifdef DEBUG_BenB
+ printf("GSSAPI step 1 for service %s, username %s\n", service, username);
+#endif
+
+ // if this fails, then it means that we cannot do GSSAPI SASL.
+ m_authModule = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sasl-gssapi", &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ m_authModule->Init(service, nsIAuthModule::REQ_DEFAULT, nullptr, NS_ConvertUTF8toUTF16(username).get(), nullptr);
+
+ void *outBuf;
+ uint32_t outBufLen;
+ rv = m_authModule->GetNextToken((void *)nullptr, 0, &outBuf, &outBufLen);
+ if (NS_SUCCEEDED(rv) && outBuf)
+ {
+ char *base64Str = PL_Base64Encode((char *)outBuf, outBufLen, nullptr);
+ if (base64Str)
+ response.Adopt(base64Str);
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ free(outBuf);
+ }
+
+#ifdef DEBUG_BenB
+ printf("GSSAPI step 1 succeeded\n");
+#endif
+ return rv;
+}
+
+nsresult nsMsgProtocol::DoGSSAPIStep2(nsCString &commandResponse, nsCString &response)
+{
+#ifdef DEBUG_BenB
+ printf("GSSAPI step 2\n");
+#endif
+ nsresult rv;
+ void *inBuf, *outBuf;
+ uint32_t inBufLen, outBufLen;
+ uint32_t len = commandResponse.Length();
+
+ // Cyrus SASL may send us zero length tokens (grrrr)
+ if (len > 0) {
+ // decode into the input secbuffer
+ inBufLen = (len * 3)/4; // sufficient size (see plbase64.h)
+ inBuf = moz_xmalloc(inBufLen);
+ if (!inBuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // strip off any padding (see bug 230351)
+ const char *challenge = commandResponse.get();
+ while (challenge[len - 1] == '=')
+ len--;
+
+ // We need to know the exact length of the decoded string to give to
+ // the GSSAPI libraries. But NSPR's base64 routine doesn't seem capable
+ // of telling us that. So, we figure it out for ourselves.
+
+ // For every 4 characters, add 3 to the destination
+ // If there are 3 remaining, add 2
+ // If there are 2 remaining, add 1
+ // 1 remaining is an error
+ inBufLen = (len / 4)*3 + ((len % 4 == 3)?2:0) + ((len % 4 == 2)?1:0);
+
+ rv = (PL_Base64Decode(challenge, len, (char *)inBuf))
+ ? m_authModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen)
+ : NS_ERROR_FAILURE;
+
+ free(inBuf);
+ }
+ else
+ {
+ rv = m_authModule->GetNextToken(NULL, 0, &outBuf, &outBufLen);
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ // And in return, we may need to send Cyrus zero length tokens back
+ if (outBuf)
+ {
+ char *base64Str = PL_Base64Encode((char *)outBuf, outBufLen, nullptr);
+ if (base64Str)
+ response.Adopt(base64Str);
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ response.Adopt((char *)nsMemory::Clone("",1));
+ }
+
+#ifdef DEBUG_BenB
+ printf(NS_SUCCEEDED(rv) ? "GSSAPI step 2 succeeded\n" : "GSSAPI step 2 failed\n");
+#endif
+ return rv;
+}
+
+nsresult nsMsgProtocol::DoNtlmStep1(const char *username, const char *password, nsCString &response)
+{
+ nsresult rv;
+
+ m_authModule = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "ntlm", &rv);
+ // if this fails, then it means that we cannot do NTLM auth.
+ if (NS_FAILED(rv) || !m_authModule)
+ return rv;
+
+ m_authModule->Init(nullptr, 0, nullptr, NS_ConvertUTF8toUTF16(username).get(),
+ NS_ConvertUTF8toUTF16(password).get());
+
+ void *outBuf;
+ uint32_t outBufLen;
+ rv = m_authModule->GetNextToken((void *)nullptr, 0, &outBuf, &outBufLen);
+ if (NS_SUCCEEDED(rv) && outBuf)
+ {
+ char *base64Str = PL_Base64Encode((char *)outBuf, outBufLen, nullptr);
+ if (base64Str)
+ response.Adopt(base64Str);
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ free(outBuf);
+ }
+
+ return rv;
+}
+
+nsresult nsMsgProtocol::DoNtlmStep2(nsCString &commandResponse, nsCString &response)
+{
+ nsresult rv;
+ void *inBuf, *outBuf;
+ uint32_t inBufLen, outBufLen;
+ uint32_t len = commandResponse.Length();
+
+ // decode into the input secbuffer
+ inBufLen = (len * 3)/4; // sufficient size (see plbase64.h)
+ inBuf = moz_xmalloc(inBufLen);
+ if (!inBuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // strip off any padding (see bug 230351)
+ const char *challenge = commandResponse.get();
+ while (challenge[len - 1] == '=')
+ len--;
+
+ rv = (PL_Base64Decode(challenge, len, (char *)inBuf))
+ ? m_authModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen)
+ : NS_ERROR_FAILURE;
+
+ free(inBuf);
+ if (NS_SUCCEEDED(rv) && outBuf)
+ {
+ char *base64Str = PL_Base64Encode((char *)outBuf, outBufLen, nullptr);
+ if (base64Str)
+ response.Adopt(base64Str);
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (NS_FAILED(rv))
+ response = "*";
+
+ return rv;
+}
+
+/////////////////////////////////////////////////////////////////////
+// nsMsgAsyncWriteProtocol subclass and related helper classes
+/////////////////////////////////////////////////////////////////////
+
+class nsMsgProtocolStreamProvider : public nsIOutputStreamCallback
+{
+public:
+ // XXX this probably doesn't need to be threadsafe
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsMsgProtocolStreamProvider() { }
+
+ void Init(nsMsgAsyncWriteProtocol *aProtInstance, nsIInputStream *aInputStream)
+ {
+ mMsgProtocol = do_GetWeakReference(static_cast<nsIStreamListener*> (aProtInstance));
+ mInStream = aInputStream;
+ }
+
+ //
+ // nsIOutputStreamCallback implementation ...
+ //
+ NS_IMETHODIMP OnOutputStreamReady(nsIAsyncOutputStream *aOutStream) override
+ {
+ NS_ASSERTION(mInStream, "not initialized");
+
+ nsresult rv;
+ uint64_t avail;
+
+ // Write whatever is available in the pipe. If the pipe is empty, then
+ // return NS_BASE_STREAM_WOULD_BLOCK; we will resume the write when there
+ // is more data.
+
+ rv = mInStream->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+
+ nsMsgAsyncWriteProtocol *protInst = nullptr;
+ nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mMsgProtocol);
+ if (!callback)
+ return NS_ERROR_FAILURE;
+ protInst = static_cast<nsMsgAsyncWriteProtocol *>(callback.get());
+
+ if (avail == 0 && !protInst->mAsyncBuffer.Length())
+ {
+ // ok, stop writing...
+ protInst->mSuspendedWrite = true;
+ return NS_OK;
+ }
+ protInst->mSuspendedWrite = false;
+
+ uint32_t bytesWritten;
+
+ if (avail)
+ {
+ rv = aOutStream->WriteFrom(mInStream, std::min(avail, uint64_t(FILE_IO_BUFFER_SIZE)), &bytesWritten);
+ // if were full at the time, the input stream may be backed up and we need to read any remains from the last ODA call
+ // before we'll get more ODA calls
+ if (protInst->mSuspendedRead)
+ protInst->UnblockPostReader();
+ }
+ else
+ {
+ rv = aOutStream->Write(protInst->mAsyncBuffer.get(),
+ protInst->mAsyncBuffer.Length(),
+ &bytesWritten);
+ protInst->mAsyncBuffer.Cut(0, bytesWritten);
+ }
+
+ protInst->UpdateProgress(bytesWritten);
+
+ // try to write again...
+ if (NS_SUCCEEDED(rv))
+ rv = aOutStream->AsyncWait(this, 0, 0, protInst->mProviderThread);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_BINDING_ABORTED, "unexpected error writing stream");
+ return NS_OK;
+ }
+
+
+protected:
+ virtual ~nsMsgProtocolStreamProvider() {}
+
+ nsCOMPtr<nsIWeakReference> mMsgProtocol;
+ nsCOMPtr<nsIInputStream> mInStream;
+};
+
+NS_IMPL_ISUPPORTS(nsMsgProtocolStreamProvider,
+ nsIOutputStreamCallback)
+
+class nsMsgFilePostHelper : public nsIStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsMsgFilePostHelper() { mSuspendedPostFileRead = false;}
+ nsresult Init(nsIOutputStream * aOutStream, nsMsgAsyncWriteProtocol * aProtInstance, nsIFile *aFileToPost);
+ nsCOMPtr<nsIRequest> mPostFileRequest;
+ bool mSuspendedPostFileRead;
+ void CloseSocket() { mProtInstance = nullptr; }
+protected:
+ virtual ~nsMsgFilePostHelper() {}
+ nsCOMPtr<nsIOutputStream> mOutStream;
+ nsCOMPtr<nsIWeakReference> mProtInstance;
+};
+
+NS_IMPL_ISUPPORTS(nsMsgFilePostHelper, nsIStreamListener, nsIRequestObserver)
+
+nsresult nsMsgFilePostHelper::Init(nsIOutputStream * aOutStream, nsMsgAsyncWriteProtocol * aProtInstance, nsIFile *aFileToPost)
+{
+ nsresult rv = NS_OK;
+ mOutStream = aOutStream;
+ mProtInstance = do_GetWeakReference(static_cast<nsIStreamListener*> (aProtInstance));
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFileToPost);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = pump->AsyncRead(this, nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ mPostFileRequest = pump;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilePostHelper::OnStartRequest(nsIRequest * aChannel, nsISupports *ctxt)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilePostHelper::OnStopRequest(nsIRequest * aChannel, nsISupports *ctxt, nsresult aStatus)
+{
+ nsMsgAsyncWriteProtocol *protInst = nullptr;
+ nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mProtInstance);
+ if (!callback)
+ return NS_OK;
+ protInst = static_cast<nsMsgAsyncWriteProtocol *>(callback.get());
+
+ if (!mSuspendedPostFileRead)
+ protInst->PostDataFinished();
+
+ mSuspendedPostFileRead = false;
+ protInst->mFilePostHelper = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilePostHelper::OnDataAvailable(nsIRequest * /* aChannel */, nsISupports *ctxt, nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count)
+{
+ nsMsgAsyncWriteProtocol *protInst = nullptr;
+ nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mProtInstance);
+ if (!callback)
+ return NS_OK;
+
+ protInst = static_cast<nsMsgAsyncWriteProtocol *>(callback.get());
+
+ if (mSuspendedPostFileRead)
+ {
+ protInst->UpdateSuspendedReadBytes(count, protInst->mInsertPeriodRequired);
+ return NS_OK;
+ }
+
+ protInst->ProcessIncomingPostData(inStr, count);
+
+ if (protInst->mSuspendedWrite)
+ {
+ // if we got here then we had suspended the write 'cause we didn't have anymore
+ // data to write (i.e. the pipe went empty). So resume the channel to kick
+ // things off again.
+ protInst->mSuspendedWrite = false;
+ protInst->mAsyncOutStream->AsyncWait(protInst->mProvider, 0, 0,
+ protInst->mProviderThread);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol)
+NS_IMPL_RELEASE_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol)
+
+NS_INTERFACE_MAP_BEGIN(nsMsgAsyncWriteProtocol)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol)
+
+nsMsgAsyncWriteProtocol::nsMsgAsyncWriteProtocol(nsIURI * aURL) : nsMsgProtocol(aURL)
+{
+ mSuspendedWrite = false;
+ mSuspendedReadBytes = 0;
+ mSuspendedRead = false;
+ mInsertPeriodRequired = false;
+ mGenerateProgressNotifications = false;
+ mSuspendedReadBytesPostPeriod = 0;
+ mFilePostHelper = nullptr;
+}
+
+nsMsgAsyncWriteProtocol::~nsMsgAsyncWriteProtocol()
+{}
+
+NS_IMETHODIMP nsMsgAsyncWriteProtocol::Cancel(nsresult status)
+{
+ mGenerateProgressNotifications = false;
+
+ if (m_request)
+ m_request->Cancel(status);
+
+ if (mAsyncOutStream)
+ mAsyncOutStream->CloseWithStatus(status);
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::PostMessage(nsIURI* url, nsIFile *file)
+{
+ nsCOMPtr<nsIStreamListener> listener = new nsMsgFilePostHelper();
+
+ if (!listener) return NS_ERROR_OUT_OF_MEMORY;
+
+ // be sure to initialize some state before posting
+ mSuspendedReadBytes = 0;
+ mNumBytesPosted = 0;
+ file->GetFileSize(&mFilePostSize);
+ mSuspendedRead = false;
+ mInsertPeriodRequired = false;
+ mSuspendedReadBytesPostPeriod = 0;
+ mGenerateProgressNotifications = true;
+
+ mFilePostHelper = static_cast<nsMsgFilePostHelper*>(static_cast<nsIStreamListener*>(listener));
+
+ static_cast<nsMsgFilePostHelper*>(static_cast<nsIStreamListener*>(listener))->Init(m_outputStream, this, file);
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::SuspendPostFileRead()
+{
+ if (mFilePostHelper && !mFilePostHelper->mSuspendedPostFileRead)
+ {
+ // uhoh we need to pause reading in the file until we get unblocked...
+ mFilePostHelper->mPostFileRequest->Suspend();
+ mFilePostHelper->mSuspendedPostFileRead = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::ResumePostFileRead()
+{
+ if (mFilePostHelper)
+ {
+ if (mFilePostHelper->mSuspendedPostFileRead)
+ {
+ mFilePostHelper->mPostFileRequest->Resume();
+ mFilePostHelper->mSuspendedPostFileRead = false;
+ }
+ }
+ else // we must be done with the download so send the '.'
+ {
+ PostDataFinished();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::UpdateSuspendedReadBytes(uint32_t aNewBytes, bool aAddToPostPeriodByteCount)
+{
+ // depending on our current state, we'll either add aNewBytes to mSuspendedReadBytes
+ // or mSuspendedReadBytesAfterPeriod.
+
+ mSuspendedRead = true;
+ if (aAddToPostPeriodByteCount)
+ mSuspendedReadBytesPostPeriod += aNewBytes;
+ else
+ mSuspendedReadBytes += aNewBytes;
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::PostDataFinished()
+{
+ nsresult rv = SendData("." CRLF);
+ if (NS_FAILED(rv))
+ return rv;
+ mGenerateProgressNotifications = false;
+ mPostDataStream = nullptr;
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::ProcessIncomingPostData(nsIInputStream *inStr, uint32_t count)
+{
+ if (!m_socketIsOpen) return NS_OK; // kick out if the socket was canceled
+
+ // We need to quote any '.' that occur at the beginning of a line.
+ // but I don't want to waste time reading out the data into a buffer and searching
+ // let's try to leverage nsIBufferedInputStream and see if we can "peek" into the
+ // current contents for this particular case.
+
+ nsCOMPtr<nsISearchableInputStream> bufferInputStr = do_QueryInterface(inStr);
+ NS_ASSERTION(bufferInputStr, "i made a wrong assumption about the type of stream we are getting");
+ NS_ASSERTION(mSuspendedReadBytes == 0, "oops, I missed something");
+
+ if (!mPostDataStream) mPostDataStream = inStr;
+
+ if (bufferInputStr)
+ {
+ uint32_t amountWritten;
+
+ while (count > 0)
+ {
+ bool found = false;
+ uint32_t offset = 0;
+ bufferInputStr->Search("\012.", true, &found, &offset); // LF.
+
+ if (!found || offset > count)
+ {
+ // push this data into the output stream
+ m_outputStream->WriteFrom(inStr, count, &amountWritten);
+ // store any remains which need read out at a later date
+ if (count > amountWritten) // stream will block
+ {
+ UpdateSuspendedReadBytes(count - amountWritten, false);
+ SuspendPostFileRead();
+ }
+ break;
+ }
+ else
+ {
+ // count points to the LF in a LF followed by a '.'
+ // go ahead and write up to offset..
+ m_outputStream->WriteFrom(inStr, offset + 1, &amountWritten);
+ count -= amountWritten;
+ if (offset+1 > amountWritten)
+ {
+ UpdateSuspendedReadBytes(offset+1 - amountWritten, false);
+ mInsertPeriodRequired = true;
+ UpdateSuspendedReadBytes(count, mInsertPeriodRequired);
+ SuspendPostFileRead();
+ break;
+ }
+
+ // write out the extra '.'
+ m_outputStream->Write(".", 1, &amountWritten);
+ if (amountWritten != 1)
+ {
+ mInsertPeriodRequired = true;
+ // once we do write out the '.', if we are now blocked we need to remember the remaining count that comes
+ // after the '.' so we can perform processing on that once we become unblocked.
+ UpdateSuspendedReadBytes(count, mInsertPeriodRequired);
+ SuspendPostFileRead();
+ break;
+ }
+ }
+ } // while count > 0
+ }
+
+ return NS_OK;
+}
+nsresult nsMsgAsyncWriteProtocol::UnblockPostReader()
+{
+ uint32_t amountWritten = 0;
+
+ if (!m_socketIsOpen) return NS_OK; // kick out if the socket was canceled
+
+ if (mSuspendedRead)
+ {
+ // (1) attempt to write out any remaining read bytes we need in order to unblock the reader
+ if (mSuspendedReadBytes > 0 && mPostDataStream)
+ {
+ uint64_t avail = 0;
+ mPostDataStream->Available(&avail);
+
+ m_outputStream->WriteFrom(mPostDataStream, std::min(avail, uint64_t(mSuspendedReadBytes)), &amountWritten);
+ // hmm sometimes my mSuspendedReadBytes is getting out of whack...so for now, reset it
+ // if necessary.
+ if (mSuspendedReadBytes > avail)
+ mSuspendedReadBytes = avail;
+
+ if (mSuspendedReadBytes > amountWritten)
+ mSuspendedReadBytes -= amountWritten;
+ else
+ mSuspendedReadBytes = 0;
+ }
+
+ // (2) if we are now unblocked, and we need to insert a '.' then do so now...
+ if (mInsertPeriodRequired && mSuspendedReadBytes == 0)
+ {
+ amountWritten = 0;
+ m_outputStream->Write(".", 1, &amountWritten);
+ if (amountWritten == 1) // if we succeeded then clear pending '.' flag
+ mInsertPeriodRequired = false;
+ }
+
+ // (3) if we inserted a '.' and we still have bytes after the '.' which need processed before the stream is unblocked
+ // then fake an ODA call to handle this now...
+ if (!mInsertPeriodRequired && mSuspendedReadBytesPostPeriod > 0)
+ {
+ // these bytes actually need processed for extra '.''s.....
+ uint32_t postbytes = mSuspendedReadBytesPostPeriod;
+ mSuspendedReadBytesPostPeriod = 0;
+ ProcessIncomingPostData(mPostDataStream, postbytes);
+ }
+
+ // (4) determine if we are out of the suspended read state...
+ if (mSuspendedReadBytes == 0 && !mInsertPeriodRequired && mSuspendedReadBytesPostPeriod == 0)
+ {
+ mSuspendedRead = false;
+ ResumePostFileRead();
+ }
+
+ } // if we are in the suspended read state
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::SetupTransportState()
+{
+ nsresult rv = NS_OK;
+
+ if (!m_outputStream && m_transport)
+ {
+ // first create a pipe which we'll use to write the data we want to send
+ // into.
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+ rv = pipe->Init(true, true, 1024, 8);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIAsyncInputStream *inputStream = nullptr;
+ // This always succeeds because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(&inputStream));
+ mInStream = dont_AddRef(static_cast<nsIInputStream *>(inputStream));
+
+ nsIAsyncOutputStream *outputStream = nullptr;
+ // This always succeeds because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(&outputStream));
+ m_outputStream = dont_AddRef(static_cast<nsIOutputStream *>(outputStream));
+
+ mProviderThread = do_GetCurrentThread();
+
+ nsMsgProtocolStreamProvider *provider = new nsMsgProtocolStreamProvider();
+
+ if (!provider) return NS_ERROR_OUT_OF_MEMORY;
+
+ provider->Init(this, mInStream);
+ mProvider = provider; // ADDREF
+
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = m_transport->OpenOutputStream(0, 0, 0, getter_AddRefs(stream));
+ if (NS_FAILED(rv)) return rv;
+
+ mAsyncOutStream = do_QueryInterface(stream, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // wait for the output stream to become writable
+ rv = mAsyncOutStream->AsyncWait(mProvider, 0, 0, mProviderThread);
+ } // if m_transport
+
+ return rv;
+}
+
+nsresult nsMsgAsyncWriteProtocol::CloseSocket()
+{
+ nsresult rv = NS_OK;
+ if (mAsyncOutStream)
+ mAsyncOutStream->CloseWithStatus(NS_BINDING_ABORTED);
+
+ nsMsgProtocol::CloseSocket();
+
+ if (mFilePostHelper)
+ {
+ mFilePostHelper->CloseSocket();
+ mFilePostHelper = nullptr;
+ }
+
+ mAsyncOutStream = nullptr;
+ mProvider = nullptr;
+ mProviderThread = nullptr;
+ mAsyncBuffer.Truncate();
+ return rv;
+}
+
+void nsMsgAsyncWriteProtocol::UpdateProgress(uint32_t aNewBytes)
+{
+ if (!mGenerateProgressNotifications) return;
+
+ mNumBytesPosted += aNewBytes;
+ if (mFilePostSize > 0)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
+ if (!mailUrl) return;
+
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ if (!statusFeedback) return;
+
+ nsCOMPtr<nsIWebProgressListener> webProgressListener (do_QueryInterface(statusFeedback));
+ if (!webProgressListener) return;
+
+ // XXX not sure if m_request is correct here
+ webProgressListener->OnProgressChange(nullptr, m_request, mNumBytesPosted, mFilePostSize, mNumBytesPosted, mFilePostSize);
+ }
+
+ return;
+}
+
+nsresult nsMsgAsyncWriteProtocol::SendData(const char * dataBuffer, bool aSuppressLogging)
+{
+ this->mAsyncBuffer.Append(dataBuffer);
+ if (!mAsyncOutStream)
+ return NS_ERROR_FAILURE;
+ return mAsyncOutStream->AsyncWait(mProvider, 0, 0, mProviderThread);
+}
+
+char16_t *FormatStringWithHostNameByName(const char16_t* stringName, nsIMsgMailNewsUrl *msgUri)
+{
+ if (!msgUri)
+ return nullptr;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(sBundleService, nullptr);
+
+ nsCOMPtr<nsIStringBundle> sBundle;
+ rv = sBundleService->CreateBundle(MSGS_URL, getter_AddRefs(sBundle));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ char16_t *ptrv = nullptr;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = msgUri->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCString hostName;
+ rv = server->GetRealHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ NS_ConvertASCIItoUTF16 hostStr(hostName);
+ const char16_t *params[] = { hostStr.get() };
+ rv = sBundle->FormatStringFromName(stringName, params, 1, &ptrv);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return ptrv;
+}
+
+// vim: ts=2 sw=2
diff --git a/mailnews/base/util/nsMsgProtocol.h b/mailnews/base/util/nsMsgProtocol.h
new file mode 100644
index 000000000..f0675d6ff
--- /dev/null
+++ b/mailnews/base/util/nsMsgProtocol.h
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgProtocol_h__
+#define nsMsgProtocol_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIChannel.h"
+#include "nsIURL.h"
+#include "nsIThread.h"
+#include "nsILoadGroup.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIProgressEventSink.h"
+#include "nsITransport.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIAuthModule.h"
+#include "nsStringGlue.h"
+#include "nsWeakReference.h"
+
+class nsIMsgWindow;
+class nsIPrompt;
+class nsIMsgMailNewsUrl;
+class nsMsgFilePostHelper;
+class nsIProxyInfo;
+
+// This is a helper class used to encapsulate code shared between all of the
+// mailnews protocol objects (imap, news, pop, smtp, etc.) In particular,
+// it unifies the core networking code for the protocols. My hope is that
+// this will make unification with Necko easier as we'll only have to change
+// this class and not all of the mailnews protocols.
+class NS_MSG_BASE nsMsgProtocol : public nsIStreamListener
+ , public nsIChannel
+ , public nsITransportEventSink
+{
+public:
+ nsMsgProtocol(nsIURI * aURL);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ // nsIChannel support
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUEST
+
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSITRANSPORTEVENTSINK
+
+ // LoadUrl -- A protocol typically overrides this function, sets up any local state for the url and
+ // then calls the base class which opens the socket if it needs opened. If the socket is
+ // already opened then we just call ProcessProtocolState to start the churning process.
+ // aConsumer is the consumer for the url. It can be null if this argument is not appropriate
+ virtual nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer = nullptr);
+
+ virtual nsresult SetUrl(nsIURI * aURL); // sometimes we want to set the url before we load it
+ void ShowAlertMessage(nsIMsgMailNewsUrl *aMsgUrl, nsresult aStatus);
+
+ // Flag manipulators
+ virtual bool TestFlag (uint32_t flag) {return flag & m_flags;}
+ virtual void SetFlag (uint32_t flag) { m_flags |= flag; }
+ virtual void ClearFlag (uint32_t flag) { m_flags &= ~flag; }
+
+protected:
+ virtual ~nsMsgProtocol();
+
+ // methods for opening and closing a socket with core netlib....
+ // mscott -okay this is lame. I should break this up into a file protocol and a socket based
+ // protocool class instead of cheating and putting both methods here...
+
+ // open a connection with a specific host and port
+ // aHostName must be UTF-8 encoded.
+ virtual nsresult OpenNetworkSocketWithInfo(const char * aHostName,
+ int32_t aGetPort,
+ const char *connectionType,
+ nsIProxyInfo *aProxyInfo,
+ nsIInterfaceRequestor* callbacks);
+ // helper routine
+ nsresult GetFileFromURL(nsIURI * aURL, nsIFile **aResult);
+ virtual nsresult OpenFileSocket(nsIURI * aURL, uint32_t aStartPosition, int32_t aReadCount); // used to open a file socket connection
+
+ nsresult GetTopmostMsgWindow(nsIMsgWindow **aWindow);
+
+ virtual const char* GetType() {return nullptr;}
+ nsresult GetQoSBits(uint8_t *aQoSBits);
+
+ // a Protocol typically overrides this method. They free any of their own connection state and then
+ // they call up into the base class to free the generic connection objects
+ virtual nsresult CloseSocket();
+
+ virtual nsresult SetupTransportState(); // private method used by OpenNetworkSocket and OpenFileSocket
+
+ // ProcessProtocolState - This is the function that gets churned by calls to OnDataAvailable.
+ // As data arrives on the socket, OnDataAvailable calls ProcessProtocolState.
+
+ virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length) = 0;
+
+ // SendData -- Writes the data contained in dataBuffer into the current output stream.
+ // It also informs the transport layer that this data is now available for transmission.
+ // Returns a positive number for success, 0 for failure (not all the bytes were written to the
+ // stream, etc).
+ // aSuppressLogging is a hint that sensitive data is being sent and should not be logged
+ virtual nsresult SendData(const char * dataBuffer, bool aSuppressLogging = false);
+
+ virtual nsresult PostMessage(nsIURI* url, nsIFile* aPostFile);
+
+ virtual nsresult InitFromURI(nsIURI *aUrl);
+
+ nsresult DoNtlmStep1(const char *username, const char *password, nsCString &response);
+ nsresult DoNtlmStep2(nsCString &commandResponse, nsCString &response);
+
+ nsresult DoGSSAPIStep1(const char *service, const char *username, nsCString &response);
+ nsresult DoGSSAPIStep2(nsCString &commandResponse, nsCString &response);
+ // Ouput stream for writing commands to the socket
+ nsCOMPtr<nsIOutputStream> m_outputStream; // this will be obtained from the transport interface
+ nsCOMPtr<nsIInputStream> m_inputStream;
+
+ // Ouput stream for writing commands to the socket
+ nsCOMPtr<nsITransport> m_transport;
+ nsCOMPtr<nsIRequest> m_request;
+
+ bool m_socketIsOpen; // mscott: we should look into keeping this state in the nsSocketTransport...
+ // I'm using it to make sure I open the socket the first time a URL is loaded into the connection
+ uint32_t m_flags; // used to store flag information
+ //uint32_t m_startPosition;
+ int32_t m_readCount;
+
+ nsCOMPtr<nsIFile> m_tempMsgFile; // we currently have a hack where displaying a msg involves writing it to a temp file first
+
+ // auth module for access to NTLM functions
+ nsCOMPtr<nsIAuthModule> m_authModule;
+
+ // the following is a catch all for nsIChannel related data
+ nsCOMPtr<nsIURI> m_originalUrl; // the original url
+ nsCOMPtr<nsIURI> m_url; // the running url
+ nsCOMPtr<nsIStreamListener> m_channelListener;
+ nsCOMPtr<nsISupports> m_channelContext;
+ nsCOMPtr<nsILoadGroup> m_loadGroup;
+ nsLoadFlags mLoadFlags;
+ nsCOMPtr<nsIProgressEventSink> mProgressEventSink;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCString mContentType;
+ nsCString mCharset;
+ int64_t mContentLength;
+ nsCOMPtr<nsILoadInfo> m_loadInfo;
+
+ nsCString m_lastPasswordSent; // used to prefill the password prompt
+
+ // private helper routine used by subclasses to quickly get a reference to the correct prompt dialog
+ // for a mailnews url.
+ nsresult GetPromptDialogFromUrl(nsIMsgMailNewsUrl * aMsgUrl, nsIPrompt ** aPromptDialog);
+
+ // if a url isn't going to result in any content then we want to suppress calls to
+ // OnStartRequest, OnDataAvailable and OnStopRequest
+ bool mSuppressListenerNotifications;
+};
+
+
+// This is is a subclass of nsMsgProtocol extends the parent class with AsyncWrite support. Protocols like smtp
+// and news want to leverage aysnc write. We don't want everyone who inherits from nsMsgProtocol to have to
+// pick up the extra overhead.
+class NS_MSG_BASE nsMsgAsyncWriteProtocol : public nsMsgProtocol
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD Cancel(nsresult status) override;
+
+ nsMsgAsyncWriteProtocol(nsIURI * aURL);
+
+ // temporary over ride...
+ virtual nsresult PostMessage(nsIURI* url, nsIFile *postFile) override;
+
+ // over ride the following methods from the base class
+ virtual nsresult SetupTransportState() override;
+ virtual nsresult SendData(const char * dataBuffer, bool aSuppressLogging = false) override;
+ nsCString mAsyncBuffer;
+
+ // if we suspended the asynch write while waiting for more data to write then this will be TRUE
+ bool mSuspendedWrite;
+ nsCOMPtr<nsIRequest> m_WriteRequest;
+ nsCOMPtr<nsIAsyncOutputStream> mAsyncOutStream;
+ nsCOMPtr<nsIOutputStreamCallback> mProvider;
+ nsCOMPtr<nsIThread> mProviderThread;
+
+ // because we are reading the post data in asychronously, it's possible that we aren't sending it
+ // out fast enough and the reading gets blocked. The following set of state variables are used to
+ // track this.
+ bool mSuspendedRead;
+ bool mInsertPeriodRequired; // do we need to insert a '.' as part of the unblocking process
+
+ nsresult ProcessIncomingPostData(nsIInputStream *inStr, uint32_t count);
+ nsresult UnblockPostReader();
+ nsresult UpdateSuspendedReadBytes(uint32_t aNewBytes, bool aAddToPostPeriodByteCount);
+ nsresult PostDataFinished(); // this is so we'll send out a closing '.' and release any state related to the post
+
+
+ // these two routines are used to pause and resume our loading of the file containing the contents
+ // we are trying to post. We call these routines when we aren't sending the bits out fast enough
+ // to keep up with the file read.
+ nsresult SuspendPostFileRead();
+ nsresult ResumePostFileRead();
+ nsresult UpdateSuspendedReadBytes(uint32_t aNewBytes);
+ void UpdateProgress(uint32_t aNewBytes);
+ nsMsgFilePostHelper * mFilePostHelper; // needs to be a weak reference
+protected:
+ virtual ~nsMsgAsyncWriteProtocol();
+
+ // the streams for the pipe used to queue up data for the async write calls to the server.
+ // we actually re-use the same mOutStream variable in our parent class for the output
+ // stream to the socket channel. So no need for a new variable here.
+ nsCOMPtr<nsIInputStream> mInStream;
+ nsCOMPtr<nsIInputStream> mPostDataStream;
+ uint32_t mSuspendedReadBytes; // remaining # of bytes we need to read before
+ // the input stream becomes unblocked
+ uint32_t mSuspendedReadBytesPostPeriod; // # of bytes which need processed after we insert a '.' before
+ // the input stream becomes unblocked.
+ int64_t mFilePostSize; // used for determining progress on posting files.
+ uint32_t mNumBytesPosted; // used for deterimining progress on posting files
+ bool mGenerateProgressNotifications; // set during a post operation after we've started sending the post data...
+
+ virtual nsresult CloseSocket() override;
+};
+
+#endif /* nsMsgProtocol_h__ */
diff --git a/mailnews/base/util/nsMsgReadStateTxn.cpp b/mailnews/base/util/nsMsgReadStateTxn.cpp
new file mode 100644
index 000000000..68524e960
--- /dev/null
+++ b/mailnews/base/util/nsMsgReadStateTxn.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgReadStateTxn.h"
+
+#include "nsIMutableArray.h"
+#include "nsIMsgHdr.h"
+#include "nsComponentManagerUtils.h"
+
+
+nsMsgReadStateTxn::nsMsgReadStateTxn()
+{
+}
+
+nsMsgReadStateTxn::~nsMsgReadStateTxn()
+{
+}
+
+nsresult
+nsMsgReadStateTxn::Init(nsIMsgFolder *aParentFolder,
+ uint32_t aNumKeys,
+ nsMsgKey *aMsgKeyArray)
+{
+ NS_ENSURE_ARG_POINTER(aParentFolder);
+
+ mParentFolder = aParentFolder;
+ mMarkedMessages.AppendElements(aMsgKeyArray, aNumKeys);
+
+ return nsMsgTxn::Init();
+}
+
+NS_IMETHODIMP
+nsMsgReadStateTxn::UndoTransaction()
+{
+ return MarkMessages(false);
+}
+
+NS_IMETHODIMP
+nsMsgReadStateTxn::RedoTransaction()
+{
+ return MarkMessages(true);
+}
+
+NS_IMETHODIMP
+nsMsgReadStateTxn::MarkMessages(bool aAsRead)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> messageArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t length = mMarkedMessages.Length();
+ for (uint32_t i = 0; i < length; i++) {
+ nsCOMPtr<nsIMsgDBHdr> curMsgHdr;
+ rv = mParentFolder->GetMessageHeader(mMarkedMessages[i],
+ getter_AddRefs(curMsgHdr));
+ if (NS_SUCCEEDED(rv) && curMsgHdr) {
+ messageArray->AppendElement(curMsgHdr, false);
+ }
+ }
+
+ return mParentFolder->MarkMessagesRead(messageArray, aAsRead);
+}
+
diff --git a/mailnews/base/util/nsMsgReadStateTxn.h b/mailnews/base/util/nsMsgReadStateTxn.h
new file mode 100644
index 000000000..45a15e27c
--- /dev/null
+++ b/mailnews/base/util/nsMsgReadStateTxn.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgBaseUndoTxn_h_
+#define nsMsgBaseUndoTxn_h_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgTxn.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "MailNewsTypes.h"
+#include "nsIMsgFolder.h"
+
+
+#define NS_MSGREADSTATETXN_IID \
+{ /* 121FCE4A-3EA1-455C-8161-839E1557D0CF */ \
+ 0x121FCE4A, 0x3EA1, 0x455C, \
+ { 0x81, 0x61, 0x83, 0x9E, 0x15, 0x57, 0xD0, 0xCF } \
+}
+
+
+//------------------------------------------------------------------------------
+// A mark-all transaction handler. Helper for redo/undo of message read states.
+//------------------------------------------------------------------------------
+class NS_MSG_BASE nsMsgReadStateTxn : public nsMsgTxn
+{
+public:
+ nsMsgReadStateTxn();
+ virtual ~nsMsgReadStateTxn();
+
+ nsresult Init(nsIMsgFolder *aParentFolder,
+ uint32_t aNumKeys,
+ nsMsgKey *aMsgKeyArray);
+ NS_IMETHOD UndoTransaction() override;
+ NS_IMETHOD RedoTransaction() override;
+
+protected:
+ NS_IMETHOD MarkMessages(bool aAsRead);
+
+private:
+ nsCOMPtr<nsIMsgFolder> mParentFolder;
+ nsTArray<nsMsgKey> mMarkedMessages;
+};
+
+#endif // nsMsgBaseUndoTxn_h_
+
diff --git a/mailnews/base/util/nsMsgTxn.cpp b/mailnews/base/util/nsMsgTxn.cpp
new file mode 100644
index 000000000..6d72360c2
--- /dev/null
+++ b/mailnews/base/util/nsMsgTxn.cpp
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgTxn.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgDatabase.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsComponentManagerUtils.h"
+#include "nsVariant.h"
+#include "nsIProperty.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFolder.h"
+
+NS_IMPL_ADDREF(nsMsgTxn)
+NS_IMPL_RELEASE(nsMsgTxn)
+NS_INTERFACE_MAP_BEGIN(nsMsgTxn)
+ NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY(nsITransaction)
+ NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2)
+ NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2)
+NS_INTERFACE_MAP_END
+
+nsMsgTxn::nsMsgTxn()
+{
+ m_txnType = 0;
+}
+
+nsMsgTxn::~nsMsgTxn()
+{
+}
+
+nsresult nsMsgTxn::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTxn::HasKey(const nsAString& name, bool *aResult)
+{
+ *aResult = mPropertyHash.Get(name, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTxn::Get(const nsAString& name, nsIVariant* *_retval)
+{
+ mPropertyHash.Get(name, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTxn::GetProperty(const nsAString& name, nsIVariant* * _retval)
+{
+ return mPropertyHash.Get(name, _retval) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgTxn::SetProperty(const nsAString& name, nsIVariant *value)
+{
+ NS_ENSURE_ARG_POINTER(value);
+ mPropertyHash.Put(name, value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTxn::DeleteProperty(const nsAString& name)
+{
+ if (!mPropertyHash.Get(name, nullptr))
+ return NS_ERROR_FAILURE;
+
+ mPropertyHash.Remove(name);
+ return mPropertyHash.Get(name, nullptr) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+//
+// nsMailSimpleProperty class and impl; used for GetEnumerator
+// This is same as nsSimpleProperty but for external API use.
+//
+
+class nsMailSimpleProperty final : public nsIProperty
+{
+public:
+ nsMailSimpleProperty(const nsAString& aName, nsIVariant* aValue)
+ : mName(aName), mValue(aValue)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROPERTY
+protected:
+ ~nsMailSimpleProperty() {}
+
+ nsString mName;
+ nsCOMPtr<nsIVariant> mValue;
+};
+
+NS_IMPL_ISUPPORTS(nsMailSimpleProperty, nsIProperty)
+
+NS_IMETHODIMP nsMailSimpleProperty::GetName(nsAString& aName)
+{
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailSimpleProperty::GetValue(nsIVariant* *aValue)
+{
+ NS_IF_ADDREF(*aValue = mValue);
+ return NS_OK;
+}
+
+// end nsMailSimpleProperty
+
+NS_IMETHODIMP nsMsgTxn::GetEnumerator(nsISimpleEnumerator* *_retval)
+{
+ nsCOMArray<nsIProperty> propertyArray;
+ for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
+ nsMailSimpleProperty *sprop = new nsMailSimpleProperty(iter.Key(),
+ iter.Data());
+ propertyArray.AppendObject(sprop);
+ }
+ return NS_NewArrayEnumerator(_retval, propertyArray);
+}
+
+#define IMPL_GETSETPROPERTY_AS(Name, Type) \
+NS_IMETHODIMP \
+nsMsgTxn::GetPropertyAs ## Name (const nsAString & prop, Type *_retval) \
+{ \
+ nsIVariant* v = mPropertyHash.GetWeak(prop); \
+ if (!v) \
+ return NS_ERROR_NOT_AVAILABLE; \
+ return v->GetAs ## Name(_retval); \
+} \
+\
+NS_IMETHODIMP \
+nsMsgTxn::SetPropertyAs ## Name (const nsAString & prop, Type value) \
+{ \
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant(); \
+ var->SetAs ## Name(value); \
+ return SetProperty(prop, var); \
+}
+
+IMPL_GETSETPROPERTY_AS(Int32, int32_t)
+IMPL_GETSETPROPERTY_AS(Uint32, uint32_t)
+IMPL_GETSETPROPERTY_AS(Int64, int64_t)
+IMPL_GETSETPROPERTY_AS(Uint64, uint64_t)
+IMPL_GETSETPROPERTY_AS(Double, double)
+IMPL_GETSETPROPERTY_AS(Bool, bool)
+
+NS_IMETHODIMP nsMsgTxn::GetPropertyAsAString(const nsAString & prop,
+ nsAString & _retval)
+{
+ nsIVariant* v = mPropertyHash.GetWeak(prop);
+ if (!v)
+ return NS_ERROR_NOT_AVAILABLE;
+ return v->GetAsAString(_retval);
+}
+
+NS_IMETHODIMP nsMsgTxn::GetPropertyAsACString(const nsAString & prop,
+ nsACString & _retval)
+{
+ nsIVariant* v = mPropertyHash.GetWeak(prop);
+ if (!v)
+ return NS_ERROR_NOT_AVAILABLE;
+ return v->GetAsACString(_retval);
+}
+
+NS_IMETHODIMP nsMsgTxn::GetPropertyAsAUTF8String(const nsAString & prop,
+ nsACString & _retval)
+{
+ nsIVariant* v = mPropertyHash.GetWeak(prop);
+ if (!v)
+ return NS_ERROR_NOT_AVAILABLE;
+ return v->GetAsAUTF8String(_retval);
+}
+
+NS_IMETHODIMP nsMsgTxn::GetPropertyAsInterface(const nsAString & prop,
+ const nsIID & aIID,
+ void** _retval)
+{
+ nsIVariant* v = mPropertyHash.GetWeak(prop);
+ if (!v)
+ return NS_ERROR_NOT_AVAILABLE;
+ nsCOMPtr<nsISupports> val;
+ nsresult rv = v->GetAsISupports(getter_AddRefs(val));
+ if (NS_FAILED(rv))
+ return rv;
+ if (!val) {
+ // We have a value, but it's null
+ *_retval = nullptr;
+ return NS_OK;
+ }
+ return val->QueryInterface(aIID, _retval);
+}
+
+NS_IMETHODIMP nsMsgTxn::SetPropertyAsAString(const nsAString & prop,
+ const nsAString & value)
+{
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsAString(value);
+ return SetProperty(prop, var);
+}
+
+NS_IMETHODIMP nsMsgTxn::SetPropertyAsACString(const nsAString & prop,
+ const nsACString & value)
+{
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsACString(value);
+ return SetProperty(prop, var);
+}
+
+NS_IMETHODIMP nsMsgTxn::SetPropertyAsAUTF8String(const nsAString & prop,
+ const nsACString & value)
+{
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsAUTF8String(value);
+ return SetProperty(prop, var);
+}
+
+NS_IMETHODIMP nsMsgTxn::SetPropertyAsInterface(const nsAString & prop,
+ nsISupports* value)
+{
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsISupports(value);
+ return SetProperty(prop, var);
+}
+
+/////////////////////// Transaction Stuff //////////////////
+NS_IMETHODIMP nsMsgTxn::DoTransaction(void)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTxn::GetIsTransient(bool *aIsTransient)
+{
+ if (nullptr!=aIsTransient)
+ *aIsTransient = false;
+ else
+ return NS_ERROR_NULL_POINTER;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTxn::Merge(nsITransaction *aTransaction, bool *aDidMerge)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+nsresult nsMsgTxn::GetMsgWindow(nsIMsgWindow **msgWindow)
+{
+ if (!msgWindow || !m_msgWindow)
+ return NS_ERROR_NULL_POINTER;
+ *msgWindow = m_msgWindow;
+ NS_ADDREF (*msgWindow);
+ return NS_OK;
+}
+
+nsresult nsMsgTxn::SetMsgWindow(nsIMsgWindow *msgWindow)
+{
+ m_msgWindow = msgWindow;
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgTxn::SetTransactionType(uint32_t txnType)
+{
+ return SetPropertyAsUint32(NS_LITERAL_STRING("type"), txnType);
+}
+
+/*none of the callers pass null aFolder,
+ we always initialize aResult (before we pass in) for the case where the key is not in the db*/
+nsresult
+nsMsgTxn::CheckForToggleDelete(nsIMsgFolder *aFolder, const nsMsgKey &aMsgKey, bool *aResult)
+{
+ NS_ENSURE_ARG(aResult);
+ nsCOMPtr<nsIMsgDBHdr> message;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = aFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (db)
+ {
+ bool containsKey;
+ rv = db->ContainsKey(aMsgKey, &containsKey);
+ if (NS_FAILED(rv) || !containsKey) // the message has been deleted from db, so we cannot do toggle here
+ return NS_OK;
+ rv = db->GetMsgHdrForKey(aMsgKey, getter_AddRefs(message));
+ uint32_t flags;
+ if (NS_SUCCEEDED(rv) && message)
+ {
+ message->GetFlags(&flags);
+ *aResult = (flags & nsMsgMessageFlags::IMAPDeleted) != 0;
+ }
+ }
+ return rv;
+}
diff --git a/mailnews/base/util/nsMsgTxn.h b/mailnews/base/util/nsMsgTxn.h
new file mode 100644
index 000000000..e925b60ea
--- /dev/null
+++ b/mailnews/base/util/nsMsgTxn.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgTxn_h__
+#define nsMsgTxn_h__
+
+#include "mozilla/Attributes.h"
+#include "nsITransaction.h"
+#include "msgCore.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgWindow.h"
+#include "nsInterfaceHashtable.h"
+#include "MailNewsTypes2.h"
+#include "nsIVariant.h"
+#include "nsIWritablePropertyBag.h"
+#include "nsIWritablePropertyBag2.h"
+
+#define NS_MESSAGETRANSACTION_IID \
+{ /* da621b30-1efc-11d3-abe4-00805f8ac968 */ \
+ 0xda621b30, 0x1efc, 0x11d3, \
+ { 0xab, 0xe4, 0x00, 0x80, 0x5f, 0x8a, 0xc9, 0x68 } }
+/**
+ * base class for all message undo/redo transactions.
+ */
+
+class NS_MSG_BASE nsMsgTxn : public nsITransaction,
+ public nsIWritablePropertyBag,
+ public nsIWritablePropertyBag2
+{
+public:
+ nsMsgTxn();
+
+ nsresult Init();
+
+ NS_IMETHOD DoTransaction(void) override;
+
+ NS_IMETHOD UndoTransaction(void) override = 0;
+
+ NS_IMETHOD RedoTransaction(void) override = 0;
+
+ NS_IMETHOD GetIsTransient(bool *aIsTransient) override;
+
+ NS_IMETHOD Merge(nsITransaction *aTransaction, bool *aDidMerge) override;
+
+ nsresult GetMsgWindow(nsIMsgWindow **msgWindow);
+ nsresult SetMsgWindow(nsIMsgWindow *msgWindow);
+ nsresult SetTransactionType(uint32_t txnType);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROPERTYBAG
+ NS_DECL_NSIPROPERTYBAG2
+ NS_DECL_NSIWRITABLEPROPERTYBAG
+ NS_DECL_NSIWRITABLEPROPERTYBAG2
+
+protected:
+ virtual ~nsMsgTxn();
+
+ // a hash table of string -> nsIVariant
+ nsInterfaceHashtable<nsStringHashKey, nsIVariant> mPropertyHash;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ uint32_t m_txnType;
+ nsresult CheckForToggleDelete(nsIMsgFolder *aFolder, const nsMsgKey &aMsgKey, bool *aResult);
+};
+
+#endif
diff --git a/mailnews/base/util/nsMsgUtils.cpp b/mailnews/base/util/nsMsgUtils.cpp
new file mode 100644
index 000000000..44609bea0
--- /dev/null
+++ b/mailnews/base/util/nsMsgUtils.cpp
@@ -0,0 +1,2520 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgUtils.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "nsStringGlue.h"
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "nsIFolderLookupService.h"
+#include "nsIImapUrl.h"
+#include "nsIMailboxUrl.h"
+#include "nsINntpUrl.h"
+#include "nsMsgNewsCID.h"
+#include "nsMsgLocalCID.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgImapCID.h"
+#include "nsMsgI18N.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsCharTraits.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsNetCID.h"
+#include "nsIIOService.h"
+#include "nsIRDFService.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgMimeCID.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIRelativeFilePref.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsISpamSettings.h"
+#include "nsICryptoHash.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIRssIncomingServer.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIOutputStream.h"
+#include "nsMsgFileStream.h"
+#include "nsIFileURL.h"
+#include "nsNetUtil.h"
+#include "nsProtocolProxyService.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMutableArray.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsArrayUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgWindow.h"
+#include "nsIWindowWatcher.h"
+#include "nsIPrompt.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsTextFormatter.h"
+#include "nsIAtomService.h"
+#include "nsIStreamListener.h"
+#include "nsReadLine.h"
+#include "nsICharsetDetectionObserver.h"
+#include "nsICharsetDetector.h"
+#include "nsILineInputStream.h"
+#include "nsIPlatformCharset.h"
+#include "nsIParserUtils.h"
+#include "nsICharsetConverterManager.h"
+#include "nsIDocumentEncoder.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+#include "locale.h"
+#include "nsStringStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsIChannel.h"
+
+/* for logging to Error Console */
+#include "nsIScriptError.h"
+#include "nsIConsoleService.h"
+
+// Log an error string to the error console
+// (adapted from nsContentUtils::LogSimpleConsoleError).
+// Flag can indicate error, warning or info.
+NS_MSG_BASE void MsgLogToConsole4(const nsAString &aErrorText,
+ const nsAString &aFilename,
+ uint32_t aLinenumber,
+ uint32_t aFlag)
+{
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
+ if (NS_WARN_IF(!scriptError))
+ return;
+ nsCOMPtr<nsIConsoleService> console =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (NS_WARN_IF(!console))
+ return;
+ if (NS_FAILED(scriptError->Init(aErrorText,
+ aFilename,
+ EmptyString(),
+ aLinenumber,
+ 0,
+ aFlag,
+ "mailnews")))
+ return;
+ console->LogMessage(scriptError);
+ return;
+}
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+static NS_DEFINE_CID(kImapUrlCID, NS_IMAPURL_CID);
+static NS_DEFINE_CID(kCMailboxUrl, NS_MAILBOXURL_CID);
+static NS_DEFINE_CID(kCNntpUrlCID, NS_NNTPURL_CID);
+
+#define ILLEGAL_FOLDER_CHARS ";#"
+#define ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER "."
+#define ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER ".~ "
+
+nsresult GetMessageServiceContractIDForURI(const char *uri, nsCString &contractID)
+{
+ nsresult rv = NS_OK;
+ //Find protocol
+ nsAutoCString uriStr(uri);
+ int32_t pos = uriStr.FindChar(':');
+ if (pos == -1)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString protocol(StringHead(uriStr, pos));
+
+ if (protocol.Equals("file") && uriStr.Find("application/x-message-display") != -1)
+ protocol.Assign("mailbox");
+ //Build message service contractid
+ contractID = "@mozilla.org/messenger/messageservice;1?type=";
+ contractID += protocol.get();
+
+ return rv;
+}
+
+nsresult GetMessageServiceFromURI(const nsACString& uri, nsIMsgMessageService **aMessageService)
+{
+ nsresult rv;
+
+ nsAutoCString contractID;
+ rv = GetMessageServiceContractIDForURI(PromiseFlatCString(uri).get(), contractID);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgMessageService> msgService = do_GetService(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*aMessageService = msgService);
+ return rv;
+}
+
+nsresult GetMsgDBHdrFromURI(const char *uri, nsIMsgDBHdr **msgHdr)
+{
+ nsCOMPtr <nsIMsgMessageService> msgMessageService;
+ nsresult rv = GetMessageServiceFromURI(nsDependentCString(uri), getter_AddRefs(msgMessageService));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!msgMessageService) return NS_ERROR_FAILURE;
+
+ return msgMessageService->MessageURIToMsgHdr(uri, msgHdr);
+}
+
+nsresult CreateStartupUrl(const char *uri, nsIURI** aUrl)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ if (!uri || !*uri || !aUrl) return rv;
+ *aUrl = nullptr;
+
+ // XXX fix this, so that base doesn't depend on imap, local or news.
+ // we can't do NS_NewURI(uri, aUrl), because these are imap-message://, mailbox-message://, news-message:// uris.
+ // I think we should do something like GetMessageServiceFromURI() to get the service, and then have the service create the
+ // appropriate nsI*Url, and then QI to nsIURI, and return it.
+ // see bug #110689
+ if (PL_strncasecmp(uri, "imap", 4) == 0)
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_CreateInstance(kImapUrlCID, &rv);
+
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ rv = imapUrl->QueryInterface(NS_GET_IID(nsIURI),
+ (void**) aUrl);
+ }
+ else if (PL_strncasecmp(uri, "mailbox", 7) == 0)
+ {
+ nsCOMPtr<nsIMailboxUrl> mailboxUrl = do_CreateInstance(kCMailboxUrl, &rv);
+ if (NS_SUCCEEDED(rv) && mailboxUrl)
+ rv = mailboxUrl->QueryInterface(NS_GET_IID(nsIURI),
+ (void**) aUrl);
+ }
+ else if (PL_strncasecmp(uri, "news", 4) == 0)
+ {
+ nsCOMPtr<nsINntpUrl> nntpUrl = do_CreateInstance(kCNntpUrlCID, &rv);
+ if (NS_SUCCEEDED(rv) && nntpUrl)
+ rv = nntpUrl->QueryInterface(NS_GET_IID(nsIURI),
+ (void**) aUrl);
+ }
+ if (*aUrl) // SetSpec can fail, for mailbox urls, but we still have a url.
+ (void) (*aUrl)->SetSpec(nsDependentCString(uri));
+ return rv;
+}
+
+
+// Where should this live? It's a utility used to convert a string priority,
+// e.g., "High, Low, Normal" to an enum.
+// Perhaps we should have an interface that groups together all these
+// utilities...
+nsresult NS_MsgGetPriorityFromString(
+ const char * const priority,
+ nsMsgPriorityValue & outPriority)
+{
+ if (!priority)
+ return NS_ERROR_NULL_POINTER;
+
+ // Note: Checking the values separately and _before_ the names,
+ // hoping for a much faster match;
+ // Only _drawback_, as "priority" handling is not truly specified:
+ // some softwares may have the number meanings reversed (1=Lowest) !?
+ if (PL_strchr(priority, '1'))
+ outPriority = nsMsgPriority::highest;
+ else if (PL_strchr(priority, '2'))
+ outPriority = nsMsgPriority::high;
+ else if (PL_strchr(priority, '3'))
+ outPriority = nsMsgPriority::normal;
+ else if (PL_strchr(priority, '4'))
+ outPriority = nsMsgPriority::low;
+ else if (PL_strchr(priority, '5'))
+ outPriority = nsMsgPriority::lowest;
+ else if (PL_strcasestr(priority, "Highest"))
+ outPriority = nsMsgPriority::highest;
+ // Important: "High" must be tested after "Highest" !
+ else if (PL_strcasestr(priority, "High") ||
+ PL_strcasestr(priority, "Urgent"))
+ outPriority = nsMsgPriority::high;
+ else if (PL_strcasestr(priority, "Normal"))
+ outPriority = nsMsgPriority::normal;
+ else if (PL_strcasestr(priority, "Lowest"))
+ outPriority = nsMsgPriority::lowest;
+ // Important: "Low" must be tested after "Lowest" !
+ else if (PL_strcasestr(priority, "Low") ||
+ PL_strcasestr(priority, "Non-urgent"))
+ outPriority = nsMsgPriority::low;
+ else
+ // "Default" case gets default value.
+ outPriority = nsMsgPriority::Default;
+
+ return NS_OK;
+}
+
+nsresult NS_MsgGetPriorityValueString(
+ const nsMsgPriorityValue p,
+ nsACString & outValueString)
+{
+ switch (p)
+ {
+ case nsMsgPriority::highest:
+ outValueString.AssignLiteral("1");
+ break;
+ case nsMsgPriority::high:
+ outValueString.AssignLiteral("2");
+ break;
+ case nsMsgPriority::normal:
+ outValueString.AssignLiteral("3");
+ break;
+ case nsMsgPriority::low:
+ outValueString.AssignLiteral("4");
+ break;
+ case nsMsgPriority::lowest:
+ outValueString.AssignLiteral("5");
+ break;
+ case nsMsgPriority::none:
+ case nsMsgPriority::notSet:
+ // Note: '0' is a "fake" value; we expect to never be in this case.
+ outValueString.AssignLiteral("0");
+ break;
+ default:
+ NS_ASSERTION(false, "invalid priority value");
+ }
+
+ return NS_OK;
+}
+
+nsresult NS_MsgGetUntranslatedPriorityName(
+ const nsMsgPriorityValue p,
+ nsACString & outName)
+{
+ switch (p)
+ {
+ case nsMsgPriority::highest:
+ outName.AssignLiteral("Highest");
+ break;
+ case nsMsgPriority::high:
+ outName.AssignLiteral("High");
+ break;
+ case nsMsgPriority::normal:
+ outName.AssignLiteral("Normal");
+ break;
+ case nsMsgPriority::low:
+ outName.AssignLiteral("Low");
+ break;
+ case nsMsgPriority::lowest:
+ outName.AssignLiteral("Lowest");
+ break;
+ case nsMsgPriority::none:
+ case nsMsgPriority::notSet:
+ // Note: 'None' is a "fake" value; we expect to never be in this case.
+ outName.AssignLiteral("None");
+ break;
+ default:
+ NS_ASSERTION(false, "invalid priority value");
+ }
+
+ return NS_OK;
+}
+
+
+/* this used to be XP_StringHash2 from xp_hash.c */
+/* phong's linear congruential hash */
+static uint32_t StringHash(const char *ubuf, int32_t len = -1)
+{
+ unsigned char * buf = (unsigned char*) ubuf;
+ uint32_t h=1;
+ unsigned char *end = buf + (len == -1 ? strlen(ubuf) : len);
+ while(buf < end) {
+ h = 0x63c63cd9*h + 0x9c39c33d + (int32_t)*buf;
+ buf++;
+ }
+ return h;
+}
+
+inline uint32_t StringHash(const nsAutoString& str)
+{
+ const char16_t *strbuf = str.get();
+ return StringHash(reinterpret_cast<const char*>(strbuf),
+ str.Length() * 2);
+}
+
+#ifndef MOZILLA_INTERNAL_API
+static int GetFindInSetFilter(const char* aChars)
+{
+ uint8_t filter = 0;
+ while (*aChars)
+ filter |= *aChars++;
+ return ~filter;
+}
+#endif
+
+/* Utility functions used in a few places in mailnews */
+int32_t
+MsgFindCharInSet(const nsCString &aString,
+ const char* aChars, uint32_t aOffset)
+{
+#ifdef MOZILLA_INTERNAL_API
+ return aString.FindCharInSet(aChars, aOffset);
+#else
+ const char *str;
+ uint32_t len = aString.BeginReading(&str);
+ int filter = GetFindInSetFilter(aChars);
+ for (uint32_t index = aOffset; index < len; index++) {
+ if (!(str[index] & filter) && strchr(aChars, str[index]))
+ return index;
+ }
+ return -1;
+#endif
+}
+
+int32_t
+MsgFindCharInSet(const nsString &aString,
+ const char* aChars, uint32_t aOffset)
+{
+#ifdef MOZILLA_INTERNAL_API
+ return aString.FindCharInSet(aChars, aOffset);
+#else
+ const char16_t *str;
+ uint32_t len = aString.BeginReading(&str);
+ int filter = GetFindInSetFilter(aChars);
+ for (uint32_t index = aOffset; index < len; index++) {
+ if (!(str[index] & filter) && strchr(aChars, str[index]))
+ return index;
+ }
+ return -1;
+#endif
+}
+
+static bool ConvertibleToNative(const nsAutoString& str)
+{
+ nsAutoCString native;
+ nsAutoString roundTripped;
+#ifdef MOZILLA_INTERNAL_API
+ NS_CopyUnicodeToNative(str, native);
+ NS_CopyNativeToUnicode(native, roundTripped);
+#else
+ nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(), str, native);
+ nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), native, roundTripped);
+#endif
+ return str.Equals(roundTripped);
+}
+
+#if defined(XP_UNIX)
+ const static uint32_t MAX_LEN = 55;
+#elif defined(XP_WIN32)
+ const static uint32_t MAX_LEN = 55;
+#else
+ #error need_to_define_your_max_filename_length
+#endif
+
+nsresult NS_MsgHashIfNecessary(nsAutoCString &name)
+{
+ if (name.IsEmpty())
+ return NS_OK; // Nothing to do.
+ nsAutoCString str(name);
+
+ // Given a filename, make it safe for filesystem
+ // certain filenames require hashing because they
+ // are too long or contain illegal characters
+ int32_t illegalCharacterIndex = MsgFindCharInSet(str,
+ FILE_PATH_SEPARATOR
+ FILE_ILLEGAL_CHARACTERS
+ ILLEGAL_FOLDER_CHARS, 0);
+
+ // Need to check the first ('.') and last ('.', '~' and ' ') char
+ if (illegalCharacterIndex == -1)
+ {
+ int32_t lastIndex = str.Length() - 1;
+ if (NS_LITERAL_CSTRING(ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER).FindChar(str[0]) != -1)
+ illegalCharacterIndex = 0;
+ else if (NS_LITERAL_CSTRING(ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER).FindChar(str[lastIndex]) != -1)
+ illegalCharacterIndex = lastIndex;
+ else
+ illegalCharacterIndex = -1;
+ }
+
+ char hashedname[MAX_LEN + 1];
+ if (illegalCharacterIndex == -1)
+ {
+ // no illegal chars, it's just too long
+ // keep the initial part of the string, but hash to make it fit
+ if (str.Length() > MAX_LEN)
+ {
+ PL_strncpy(hashedname, str.get(), MAX_LEN + 1);
+ PR_snprintf(hashedname + MAX_LEN - 8, 9, "%08lx",
+ (unsigned long) StringHash(str.get()));
+ name = hashedname;
+ }
+ }
+ else
+ {
+ // found illegal chars, hash the whole thing
+ // if we do substitution, then hash, two strings
+ // could hash to the same value.
+ // for example, on mac: "foo__bar", "foo:_bar", "foo::bar"
+ // would map to "foo_bar". this way, all three will map to
+ // different values
+ PR_snprintf(hashedname, 9, "%08lx",
+ (unsigned long) StringHash(str.get()));
+ name = hashedname;
+ }
+
+ return NS_OK;
+}
+
+// XXX : The number of UTF-16 2byte code units are half the number of
+// bytes in legacy encodings for CJK strings and non-Latin1 in UTF-8.
+// The ratio can be 1/3 for CJK strings in UTF-8. However, we can
+// get away with using the same MAX_LEN for nsCString and nsString
+// because MAX_LEN is defined rather conservatively in the first place.
+nsresult NS_MsgHashIfNecessary(nsAutoString &name)
+{
+ if (name.IsEmpty())
+ return NS_OK; // Nothing to do.
+ int32_t illegalCharacterIndex = MsgFindCharInSet(name,
+ FILE_PATH_SEPARATOR
+ FILE_ILLEGAL_CHARACTERS
+ ILLEGAL_FOLDER_CHARS, 0);
+
+ // Need to check the first ('.') and last ('.', '~' and ' ') char
+ if (illegalCharacterIndex == -1)
+ {
+ int32_t lastIndex = name.Length() - 1;
+ if (NS_LITERAL_STRING(ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER).FindChar(name[0]) != -1)
+ illegalCharacterIndex = 0;
+ else if (NS_LITERAL_STRING(ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER).FindChar(name[lastIndex]) != -1)
+ illegalCharacterIndex = lastIndex;
+ else
+ illegalCharacterIndex = -1;
+ }
+
+ char hashedname[9];
+ int32_t keptLength = -1;
+ if (illegalCharacterIndex != -1)
+ keptLength = illegalCharacterIndex;
+ else if (!ConvertibleToNative(name))
+ keptLength = 0;
+ else if (name.Length() > MAX_LEN) {
+ keptLength = MAX_LEN-8;
+ // To avoid keeping only the high surrogate of a surrogate pair
+ if (NS_IS_HIGH_SURROGATE(name.CharAt(keptLength-1)))
+ --keptLength;
+ }
+
+ if (keptLength >= 0) {
+ PR_snprintf(hashedname, 9, "%08lx", (unsigned long) StringHash(name));
+ name.SetLength(keptLength);
+ name.Append(NS_ConvertASCIItoUTF16(hashedname));
+ }
+
+ return NS_OK;
+}
+
+nsresult FormatFileSize(int64_t size, bool useKB, nsAString &formattedSize)
+{
+ NS_NAMED_LITERAL_STRING(byteAbbr, "byteAbbreviation2");
+ NS_NAMED_LITERAL_STRING(kbAbbr, "kiloByteAbbreviation2");
+ NS_NAMED_LITERAL_STRING(mbAbbr, "megaByteAbbreviation2");
+ NS_NAMED_LITERAL_STRING(gbAbbr, "gigaByteAbbreviation2");
+
+ const char16_t *sizeAbbrNames[] = {
+ byteAbbr.get(), kbAbbr.get(), mbAbbr.get(), gbAbbr.get()
+ };
+
+ nsresult rv;
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double unitSize = size < 0 ? 0.0 : size;
+ uint32_t unitIndex = 0;
+
+ if (useKB) {
+ // Start by formatting in kilobytes
+ unitSize /= 1024;
+ if (unitSize < 0.1 && unitSize != 0)
+ unitSize = 0.1;
+ unitIndex++;
+ }
+
+ // Convert to next unit if it needs 4 digits (after rounding), but only if
+ // we know the name of the next unit
+ while ((unitSize >= 999.5) && (unitIndex < ArrayLength(sizeAbbrNames) - 1))
+ {
+ unitSize /= 1024;
+ unitIndex++;
+ }
+
+ // Grab the string for the appropriate unit
+ nsString sizeAbbr;
+ rv = bundle->GetStringFromName(sizeAbbrNames[unitIndex],
+ getter_Copies(sizeAbbr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get rid of insignificant bits by truncating to 1 or 0 decimal points
+ // 0.1 -> 0.1; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
+ nsTextFormatter::ssprintf(
+ formattedSize, sizeAbbr.get(),
+ (unitIndex != 0) && (unitSize < 99.95 && unitSize != 0) ? 1 : 0, unitSize);
+
+ int32_t separatorPos = formattedSize.FindChar('.');
+ if (separatorPos != kNotFound) {
+ // The ssprintf returned a decimal number using a dot (.) as the decimal
+ // separator. Now we try to localize the separator.
+ // Try to get the decimal separator from the system's locale.
+ char *decimalPoint;
+#ifdef HAVE_LOCALECONV
+ struct lconv *locale = localeconv();
+ decimalPoint = locale->decimal_point;
+#else
+ decimalPoint = getenv("LOCALE_DECIMAL_POINT");
+#endif
+ NS_ConvertUTF8toUTF16 decimalSeparator(decimalPoint);
+ if (decimalSeparator.IsEmpty())
+ decimalSeparator.AssignLiteral(".");
+
+ formattedSize.Replace(separatorPos, 1, decimalSeparator);
+ }
+
+ return NS_OK;
+}
+
+nsresult NS_MsgCreatePathStringFromFolderURI(const char *aFolderURI,
+ nsCString& aPathCString,
+ const nsCString &aScheme,
+ bool aIsNewsFolder)
+{
+ // A file name has to be in native charset. Here we convert
+ // to UTF-16 and check for 'unsafe' characters before converting
+ // to native charset.
+ NS_ENSURE_TRUE(MsgIsUTF8(nsDependentCString(aFolderURI)), NS_ERROR_UNEXPECTED);
+ NS_ConvertUTF8toUTF16 oldPath(aFolderURI);
+
+ nsAutoString pathPiece, path;
+
+ int32_t startSlashPos = oldPath.FindChar('/');
+ int32_t endSlashPos = (startSlashPos >= 0)
+ ? oldPath.FindChar('/', startSlashPos + 1) - 1 : oldPath.Length() - 1;
+ if (endSlashPos < 0)
+ endSlashPos = oldPath.Length();
+#if defined(XP_UNIX) || defined(XP_MACOSX)
+ bool isLocalUri = aScheme.EqualsLiteral("none") ||
+ aScheme.EqualsLiteral("pop3") ||
+ aScheme.EqualsLiteral("rss");
+#endif
+ // trick to make sure we only add the path to the first n-1 folders
+ bool haveFirst=false;
+ while (startSlashPos != -1) {
+ pathPiece.Assign(Substring(oldPath, startSlashPos + 1, endSlashPos - startSlashPos));
+ // skip leading '/' (and other // style things)
+ if (!pathPiece.IsEmpty())
+ {
+
+ // add .sbd onto the previous path
+ if (haveFirst)
+ {
+ path.AppendLiteral(".sbd/");
+ }
+
+ if (aIsNewsFolder)
+ {
+ nsAutoCString tmp;
+ CopyUTF16toMUTF7(pathPiece, tmp);
+ CopyASCIItoUTF16(tmp, pathPiece);
+ }
+#if defined(XP_UNIX) || defined(XP_MACOSX)
+ // Don't hash path pieces because local mail folder uri's have already
+ // been hashed. We're only doing this on the mac to limit potential
+ // regressions.
+ if (!isLocalUri)
+#endif
+ NS_MsgHashIfNecessary(pathPiece);
+ path += pathPiece;
+ haveFirst=true;
+ }
+ // look for the next slash
+ startSlashPos = endSlashPos + 1;
+
+ endSlashPos = (startSlashPos >= 0)
+ ? oldPath.FindChar('/', startSlashPos + 1) - 1: oldPath.Length() - 1;
+ if (endSlashPos < 0)
+ endSlashPos = oldPath.Length();
+
+ if (startSlashPos >= endSlashPos)
+ break;
+ }
+#ifdef MOZILLA_INTERNAL_API
+ return NS_CopyUnicodeToNative(path, aPathCString);
+#else
+ return nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(), path, aPathCString);
+#endif
+}
+
+bool NS_MsgStripRE(const nsCString& subject, nsCString& modifiedSubject)
+{
+ bool result = false;
+
+ // Get localizedRe pref.
+ nsresult rv;
+ nsString utf16LocalizedRe;
+ NS_GetLocalizedUnicharPreferenceWithDefault(nullptr,
+ "mailnews.localizedRe",
+ EmptyString(),
+ utf16LocalizedRe);
+ NS_ConvertUTF16toUTF8 localizedRe(utf16LocalizedRe);
+
+ // Hardcoded "Re" so that no one can configure Mozilla standards incompatible.
+ nsAutoCString checkString("Re,RE,re,rE");
+ if (!localizedRe.IsEmpty()) {
+ checkString.Append(',');
+ checkString.Append(localizedRe);
+ }
+
+ // Decode the string.
+ nsCString decodedString;
+ nsCOMPtr<nsIMimeConverter> mimeConverter;
+ // We cannot strip "Re:" for RFC2047-encoded subject without modifying the original.
+ if (subject.Find("=?") != kNotFound)
+ {
+ mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = mimeConverter->DecodeMimeHeaderToUTF8(subject,
+ nullptr, false, true, decodedString);
+ }
+
+ const char *s, *s_end;
+ if (decodedString.IsEmpty()) {
+ s = subject.BeginReading();
+ s_end = s + subject.Length();
+ } else {
+ s = decodedString.BeginReading();
+ s_end = s + decodedString.Length();
+ }
+
+AGAIN:
+ while (s < s_end && IS_SPACE(*s))
+ s++;
+
+ const char *tokPtr = checkString.get();
+ while (*tokPtr)
+ {
+ // Tokenize the comma separated list.
+ size_t tokenLength = 0;
+ while (*tokPtr && *tokPtr != ',') {
+ tokenLength++;
+ tokPtr++;
+ }
+ // Check if the beginning of s is the actual token.
+ if (tokenLength && !strncmp(s, tokPtr - tokenLength, tokenLength))
+ {
+ if (s[tokenLength] == ':')
+ {
+ s = s + tokenLength + 1; /* Skip over "Re:" */
+ result = true; /* Yes, we stripped it. */
+ goto AGAIN; /* Skip whitespace and try again. */
+ }
+ else if (s[tokenLength] == '[' || s[tokenLength] == '(')
+ {
+ const char *s2 = s + tokenLength + 1; /* Skip over "Re[" */
+
+ // Skip forward over digits after the "[".
+ while (s2 < (s_end - 2) && isdigit((unsigned char)*s2))
+ s2++;
+
+ // Now ensure that the following thing is "]:".
+ // Only if it is do we alter `s`.
+ if ((s2[0] == ']' || s2[0] == ')') && s2[1] == ':')
+ {
+ s = s2 + 2; /* Skip over "]:" */
+ result = true; /* Yes, we stripped it. */
+ goto AGAIN; /* Skip whitespace and try again. */
+ }
+ }
+ }
+ if (*tokPtr)
+ tokPtr++;
+ }
+
+ // If we didn't strip anything, we can return here.
+ if (!result)
+ return false;
+
+ if (decodedString.IsEmpty()) {
+ // We didn't decode anything, so just return a new string.
+ modifiedSubject.Assign(s);
+ return true;
+ }
+
+ // We decoded the string, so we need to encode it again. We always encode in UTF-8.
+ mimeConverter->EncodeMimePartIIStr_UTF8(nsDependentCString(s),
+ false, "UTF-8", sizeof("Subject:"),
+ nsIMimeConverter::MIME_ENCODED_WORD_SIZE, modifiedSubject);
+ return true;
+}
+
+/* Very similar to strdup except it free's too
+ */
+char * NS_MsgSACopy (char **destination, const char *source)
+{
+ if(*destination)
+ {
+ PR_Free(*destination);
+ *destination = 0;
+ }
+ if (! source)
+ *destination = nullptr;
+ else
+ {
+ *destination = (char *) PR_Malloc (PL_strlen(source) + 1);
+ if (*destination == nullptr)
+ return(nullptr);
+
+ PL_strcpy (*destination, source);
+ }
+ return *destination;
+}
+
+/* Again like strdup but it concatenates and free's and uses Realloc.
+*/
+char * NS_MsgSACat (char **destination, const char *source)
+{
+ if (source && *source)
+ {
+ int destLength = *destination ? PL_strlen(*destination) : 0;
+ char* newDestination = (char*) PR_Realloc(*destination, destLength + PL_strlen(source) + 1);
+ if (newDestination == nullptr)
+ return nullptr;
+
+ *destination = newDestination;
+ PL_strcpy(*destination + destLength, source);
+ }
+ return *destination;
+}
+
+nsresult NS_MsgEscapeEncodeURLPath(const nsAString& aStr, nsCString& aResult)
+{
+ return MsgEscapeString(NS_ConvertUTF16toUTF8(aStr), nsINetUtil::ESCAPE_URL_PATH, aResult);
+}
+
+nsresult NS_MsgDecodeUnescapeURLPath(const nsACString& aPath,
+ nsAString& aResult)
+{
+ nsAutoCString unescapedName;
+ MsgUnescapeString(aPath, nsINetUtil::ESCAPE_URL_FILE_BASENAME |
+ nsINetUtil::ESCAPE_URL_FORCED, unescapedName);
+ CopyUTF8toUTF16(unescapedName, aResult);
+ return NS_OK;
+}
+
+bool WeAreOffline()
+{
+ bool offline = false;
+
+ nsCOMPtr <nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ if (ioService)
+ ioService->GetOffline(&offline);
+
+ return offline;
+}
+
+nsresult GetExistingFolder(const nsCString& aFolderURI, nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ *aFolder = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIFolderLookupService> fls(do_GetService(NSIFLS_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = fls->GetFolderForURL(aFolderURI, aFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return *aFolder ? NS_OK : NS_ERROR_FAILURE;
+}
+
+bool IsAFromSpaceLine(char *start, const char *end)
+{
+ bool rv = false;
+ while ((start < end) && (*start == '>'))
+ start++;
+ // If the leading '>'s are followed by an 'F' then we have a possible case here.
+ if ( (*start == 'F') && (end-start > 4) && !strncmp(start, "From ", 5) )
+ rv = true;
+ return rv;
+}
+
+//
+// This function finds all lines starting with "From " or "From " preceeding
+// with one or more '>' (ie, ">From", ">>From", etc) in the input buffer
+// (between 'start' and 'end') and prefix them with a ">" .
+//
+nsresult EscapeFromSpaceLine(nsIOutputStream *outputStream, char *start, const char *end)
+{
+ nsresult rv;
+ char *pChar;
+ uint32_t written;
+
+ pChar = start;
+ while (start < end)
+ {
+ while ((pChar < end) && (*pChar != '\r') && ((pChar + 1) < end) &&
+ (*(pChar + 1) != '\n'))
+ pChar++;
+ if ((pChar + 1) == end)
+ pChar++;
+
+ if (pChar < end)
+ {
+ // Found a line so check if it's a qualified "From " line.
+ if (IsAFromSpaceLine(start, pChar))
+ rv = outputStream->Write(">", 1, &written);
+ int32_t lineTerminatorCount = (*(pChar + 1) == '\n') ? 2 : 1;
+ rv = outputStream->Write(start, pChar - start + lineTerminatorCount, &written);
+ NS_ENSURE_SUCCESS(rv,rv);
+ pChar += lineTerminatorCount;
+ start = pChar;
+ }
+ else if (start < end)
+ {
+ // Check and flush out the remaining data and we're done.
+ if (IsAFromSpaceLine(start, end))
+ rv = outputStream->Write(">", 1, &written);
+ rv = outputStream->Write(start, end-start, &written);
+ NS_ENSURE_SUCCESS(rv,rv);
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult IsRFC822HeaderFieldName(const char *aHdr, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aHdr);
+ NS_ENSURE_ARG_POINTER(aResult);
+ uint32_t length = strlen(aHdr);
+ for(uint32_t i=0; i<length; i++)
+ {
+ char c = aHdr[i];
+ if ( c < '!' || c == ':' || c > '~')
+ {
+ *aResult = false;
+ return NS_OK;
+ }
+ }
+ *aResult = true;
+ return NS_OK;
+}
+
+// Warning, currently this routine only works for the Junk Folder
+nsresult
+GetOrCreateFolder(const nsACString &aURI, nsIUrlListener *aListener)
+{
+ nsresult rv;
+ nsCOMPtr <nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the corresponding RDF resource
+ // RDF will create the folder resource if it doesn't already exist
+ nsCOMPtr<nsIRDFResource> resource;
+ rv = rdf->GetResource(aURI, getter_AddRefs(resource));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgFolder> folderResource;
+ folderResource = do_QueryInterface(resource, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // don't check validity of folder - caller will handle creating it
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // make sure that folder hierarchy is built so that legitimate parent-child relationship is established
+ rv = folderResource->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!server)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+ rv = server->GetMsgFolderFromURI(folderResource, aURI, getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgFolder> parent;
+ rv = msgFolder->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv) || !parent)
+ {
+ nsCOMPtr <nsIFile> folderPath;
+ // for local folders, path is to the berkeley mailbox.
+ // for imap folders, path needs to have .msf appended to the name
+ msgFolder->GetFilePath(getter_AddRefs(folderPath));
+
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = server->GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isAsyncFolder;
+ rv = protocolInfo->GetFoldersCreatedAsync(&isAsyncFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we can't get the path from the folder, then try to create the storage.
+ // for imap, it doesn't matter if the .msf file exists - it still might not
+ // exist on the server, so we should try to create it
+ bool exists = false;
+ if (!isAsyncFolder && folderPath)
+ folderPath->Exists(&exists);
+ if (!exists)
+ {
+ // Hack to work around a localization bug with the Junk Folder.
+ // Please see Bug #270261 for more information...
+ nsString localizedJunkName;
+ msgFolder->GetName(localizedJunkName);
+
+ // force the junk folder name to be Junk so it gets created on disk correctly...
+ msgFolder->SetName(NS_LITERAL_STRING("Junk"));
+ msgFolder->SetFlag(nsMsgFolderFlags::Junk);
+ rv = msgFolder->CreateStorageIfMissing(aListener);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // now restore the localized folder name...
+ msgFolder->SetName(localizedJunkName);
+
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // ugh, I hate this hack
+ // we have to do this (for now)
+ // because imap and local are different (one creates folder asynch, the other synch)
+ // one will notify the listener, one will not.
+ // I blame nsMsgCopy.
+ // we should look into making it so no matter what the folder type
+ // we always call the listener
+ // this code should move into local folder's version of CreateStorageIfMissing()
+ if (!isAsyncFolder && aListener) {
+ rv = aListener->OnStartRunningUrl(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aListener->OnStopRunningUrl(nullptr, NS_OK);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ }
+ else {
+ // if the folder exists, we should set the junk flag on it
+ // which is what the listener will do
+ if (aListener) {
+ rv = aListener->OnStartRunningUrl(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aListener->OnStopRunningUrl(nullptr, NS_OK);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult IsRSSArticle(nsIURI * aMsgURI, bool *aIsRSSArticle)
+{
+ nsresult rv;
+ *aIsRSSArticle = false;
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(aMsgURI, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString resourceURI;
+ msgUrl->GetUri(getter_Copies(resourceURI));
+
+ // get the msg service for this URI
+ nsCOMPtr<nsIMsgMessageService> msgService;
+ rv = GetMessageServiceFromURI(resourceURI, getter_AddRefs(msgService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if the message is a feed message, regardless of folder.
+ uint32_t flags;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgService->MessageURIToMsgHdr(resourceURI.get(), getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::FeedMsg)
+ {
+ *aIsRSSArticle = true;
+ return rv;
+ }
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aMsgURI, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the folder and the server from the msghdr
+ nsCOMPtr<nsIRssIncomingServer> rssServer;
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ folder->GetServer(getter_AddRefs(server));
+ rssServer = do_QueryInterface(server);
+
+ if (rssServer)
+ *aIsRSSArticle = true;
+ }
+
+ return rv;
+}
+
+
+// digest needs to be a pointer to a DIGEST_LENGTH (16) byte buffer
+nsresult MSGCramMD5(const char *text, int32_t text_len, const char *key, int32_t key_len, unsigned char *digest)
+{
+ nsresult rv;
+
+ nsAutoCString hash;
+ nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+
+ // this code adapted from http://www.cis.ohio-state.edu/cgi-bin/rfc/rfc2104.html
+
+ char innerPad[65]; /* inner padding - key XORd with innerPad */
+ char outerPad[65]; /* outer padding - key XORd with outerPad */
+ int i;
+ /* if key is longer than 64 bytes reset it to key=MD5(key) */
+ if (key_len > 64)
+ {
+
+ rv = hasher->Init(nsICryptoHash::MD5);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Update((const uint8_t*) key, key_len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Finish(false, hash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ key = hash.get();
+ key_len = DIGEST_LENGTH;
+ }
+
+ /*
+ * the HMAC_MD5 transform looks like:
+ *
+ * MD5(K XOR outerPad, MD5(K XOR innerPad, text))
+ *
+ * where K is an n byte key
+ * innerPad is the byte 0x36 repeated 64 times
+ * outerPad is the byte 0x5c repeated 64 times
+ * and text is the data being protected
+ */
+
+ /* start out by storing key in pads */
+ memset(innerPad, 0, sizeof innerPad);
+ memset(outerPad, 0, sizeof outerPad);
+ memcpy(innerPad, key, key_len);
+ memcpy(outerPad, key, key_len);
+
+ /* XOR key with innerPad and outerPad values */
+ for (i=0; i<64; i++)
+ {
+ innerPad[i] ^= 0x36;
+ outerPad[i] ^= 0x5c;
+ }
+ /*
+ * perform inner MD5
+ */
+ nsAutoCString result;
+ rv = hasher->Init(nsICryptoHash::MD5); /* init context for 1st pass */
+ rv = hasher->Update((const uint8_t*)innerPad, 64); /* start with inner pad */
+ rv = hasher->Update((const uint8_t*)text, text_len); /* then text of datagram */
+ rv = hasher->Finish(false, result); /* finish up 1st pass */
+
+ /*
+ * perform outer MD5
+ */
+ hasher->Init(nsICryptoHash::MD5); /* init context for 2nd pass */
+ rv = hasher->Update((const uint8_t*)outerPad, 64); /* start with outer pad */
+ rv = hasher->Update((const uint8_t*)result.get(), 16);/* then results of 1st hash */
+ rv = hasher->Finish(false, result); /* finish up 2nd pass */
+
+ if (result.Length() != DIGEST_LENGTH)
+ return NS_ERROR_UNEXPECTED;
+
+ memcpy(digest, result.get(), DIGEST_LENGTH);
+
+ return rv;
+
+}
+
+
+// digest needs to be a pointer to a DIGEST_LENGTH (16) byte buffer
+nsresult MSGApopMD5(const char *text, int32_t text_len, const char *password, int32_t password_len, unsigned char *digest)
+{
+ nsresult rv;
+ nsAutoCString result;
+
+ nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Init(nsICryptoHash::MD5);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Update((const uint8_t*) text, text_len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Update((const uint8_t*) password, password_len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Finish(false, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (result.Length() != DIGEST_LENGTH)
+ return NS_ERROR_UNEXPECTED;
+
+ memcpy(digest, result.get(), DIGEST_LENGTH);
+ return rv;
+}
+
+NS_MSG_BASE nsresult NS_GetPersistentFile(const char *relPrefName,
+ const char *absPrefName,
+ const char *dirServiceProp,
+ bool& gotRelPref,
+ nsIFile **aFile,
+ nsIPrefBranch *prefBranch)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = nullptr;
+ NS_ENSURE_ARG(relPrefName);
+ NS_ENSURE_ARG(absPrefName);
+ gotRelPref = false;
+
+ nsCOMPtr<nsIPrefBranch> mainBranch;
+ if (!prefBranch) {
+ nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (!prefService) return NS_ERROR_FAILURE;
+ prefService->GetBranch(nullptr, getter_AddRefs(mainBranch));
+ if (!mainBranch) return NS_ERROR_FAILURE;
+ prefBranch = mainBranch;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+
+ // Get the relative first
+ nsCOMPtr<nsIRelativeFilePref> relFilePref;
+ prefBranch->GetComplexValue(relPrefName,
+ NS_GET_IID(nsIRelativeFilePref), getter_AddRefs(relFilePref));
+ if (relFilePref) {
+ relFilePref->GetFile(getter_AddRefs(localFile));
+ NS_ASSERTION(localFile, "An nsIRelativeFilePref has no file.");
+ if (localFile)
+ gotRelPref = true;
+ }
+
+ // If not, get the old absolute
+ if (!localFile) {
+ prefBranch->GetComplexValue(absPrefName,
+ NS_GET_IID(nsIFile), getter_AddRefs(localFile));
+
+ // If not, and given a dirServiceProp, use directory service.
+ if (!localFile && dirServiceProp) {
+ nsCOMPtr<nsIProperties> dirService(do_GetService("@mozilla.org/file/directory_service;1"));
+ if (!dirService) return NS_ERROR_FAILURE;
+ dirService->Get(dirServiceProp, NS_GET_IID(nsIFile), getter_AddRefs(localFile));
+ if (!localFile) return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (localFile) {
+ localFile->Normalize();
+ *aFile = localFile;
+ NS_ADDREF(*aFile);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_MSG_BASE nsresult NS_SetPersistentFile(const char *relPrefName,
+ const char *absPrefName,
+ nsIFile *aFile,
+ nsIPrefBranch *prefBranch)
+{
+ NS_ENSURE_ARG(relPrefName);
+ NS_ENSURE_ARG(absPrefName);
+ NS_ENSURE_ARG(aFile);
+
+ nsCOMPtr<nsIPrefBranch> mainBranch;
+ if (!prefBranch) {
+ nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (!prefService) return NS_ERROR_FAILURE;
+ prefService->GetBranch(nullptr, getter_AddRefs(mainBranch));
+ if (!mainBranch) return NS_ERROR_FAILURE;
+ prefBranch = mainBranch;
+ }
+
+ // Write the absolute for backwards compatibilty's sake.
+ // Or, if aPath is on a different drive than the profile dir.
+ nsresult rv = prefBranch->SetComplexValue(absPrefName, NS_GET_IID(nsIFile), aFile);
+
+ // Write the relative path.
+ nsCOMPtr<nsIRelativeFilePref> relFilePref;
+ NS_NewRelativeFilePref(aFile, nsDependentCString(NS_APP_USER_PROFILE_50_DIR), getter_AddRefs(relFilePref));
+ if (relFilePref) {
+ nsresult rv2 = prefBranch->SetComplexValue(relPrefName, NS_GET_IID(nsIRelativeFilePref), relFilePref);
+ if (NS_FAILED(rv2) && NS_SUCCEEDED(rv))
+ prefBranch->ClearUserPref(relPrefName);
+ }
+
+ return rv;
+}
+
+NS_MSG_BASE nsresult NS_GetUnicharPreferenceWithDefault(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch
+ const char *prefName,
+ const nsAString& defValue,
+ nsAString& prefValue)
+{
+ NS_ENSURE_ARG(prefName);
+
+ nsCOMPtr<nsIPrefBranch> pbr;
+ if(!prefBranch) {
+ pbr = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ prefBranch = pbr;
+ }
+
+ nsCOMPtr<nsISupportsString> str;
+ nsresult rv = prefBranch->GetComplexValue(prefName, NS_GET_IID(nsISupportsString), getter_AddRefs(str));
+ if (NS_SUCCEEDED(rv))
+ str->GetData(prefValue);
+ else
+ prefValue = defValue;
+ return NS_OK;
+}
+
+NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreferenceWithDefault(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch
+ const char *prefName,
+ const nsAString& defValue,
+ nsAString& prefValue)
+{
+ NS_ENSURE_ARG(prefName);
+
+ nsCOMPtr<nsIPrefBranch> pbr;
+ if(!prefBranch) {
+ pbr = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ prefBranch = pbr;
+ }
+
+ nsCOMPtr<nsIPrefLocalizedString> str;
+ nsresult rv = prefBranch->GetComplexValue(prefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString tmpValue;
+ str->ToString(getter_Copies(tmpValue));
+ prefValue.Assign(tmpValue);
+ }
+ else
+ prefValue = defValue;
+ return NS_OK;
+}
+
+NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreference(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch
+ const char *prefName,
+ nsAString& prefValue)
+{
+ NS_ENSURE_ARG_POINTER(prefName);
+
+ nsCOMPtr<nsIPrefBranch> pbr;
+ if (!prefBranch) {
+ pbr = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ prefBranch = pbr;
+ }
+
+ nsCOMPtr<nsIPrefLocalizedString> str;
+ nsresult rv = prefBranch->GetComplexValue(prefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString tmpValue;
+ str->ToString(getter_Copies(tmpValue));
+ prefValue.Assign(tmpValue);
+ return NS_OK;
+}
+
+void PRTime2Seconds(PRTime prTime, uint32_t *seconds)
+{
+ *seconds = (uint32_t)(prTime / PR_USEC_PER_SEC);
+}
+
+void PRTime2Seconds(PRTime prTime, int32_t *seconds)
+{
+ *seconds = (int32_t)(prTime / PR_USEC_PER_SEC);
+}
+
+void Seconds2PRTime(uint32_t seconds, PRTime *prTime)
+{
+ *prTime = (PRTime)seconds * PR_USEC_PER_SEC;
+}
+
+nsresult GetSummaryFileLocation(nsIFile* fileLocation, nsIFile** summaryLocation)
+{
+ nsresult rv;
+ nsCOMPtr <nsIFile> newSummaryLocation = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newSummaryLocation->InitWithFile(fileLocation);
+ nsString fileName;
+
+ rv = newSummaryLocation->GetLeafName(fileName);
+ if (NS_FAILED(rv))
+ return rv;
+
+ fileName.Append(NS_LITERAL_STRING(SUMMARY_SUFFIX));
+ rv = newSummaryLocation->SetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*summaryLocation = newSummaryLocation);
+ return NS_OK;
+}
+
+void MsgGenerateNowStr(nsACString &nowStr)
+{
+ char dateBuf[100];
+ dateBuf[0] = '\0';
+ PRExplodedTime exploded;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y", &exploded);
+ nowStr.Assign(dateBuf);
+}
+
+
+// Gets a special directory and appends the supplied file name onto it.
+nsresult GetSpecialDirectoryWithFileName(const char* specialDirName,
+ const char* fileName,
+ nsIFile** result)
+{
+ nsresult rv = NS_GetSpecialDirectory(specialDirName, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return (*result)->AppendNative(nsDependentCString(fileName));
+}
+
+// Cleans up temp files with matching names
+nsresult MsgCleanupTempFiles(const char *fileName, const char *extension)
+{
+ nsCOMPtr<nsIFile> tmpFile;
+ nsCString rootName(fileName);
+ rootName.Append(".");
+ rootName.Append(extension);
+ nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ rootName.get(),
+ getter_AddRefs(tmpFile));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ int index = 1;
+ bool exists;
+ do
+ {
+ tmpFile->Exists(&exists);
+ if (exists)
+ {
+ tmpFile->Remove(false);
+ nsCString leafName(fileName);
+ leafName.Append("-");
+ leafName.AppendInt(index);
+ leafName.Append(".");
+ leafName.Append(extension);
+ // start with "Picture-1.jpg" after "Picture.jpg" exists
+ tmpFile->SetNativeLeafName(leafName);
+ }
+ }
+ while (exists && ++index < 10000);
+ return NS_OK;
+}
+
+nsresult MsgGetFileStream(nsIFile *file, nsIOutputStream **fileStream)
+{
+ nsMsgFileStream *newFileStream = new nsMsgFileStream;
+ NS_ENSURE_TRUE(newFileStream, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = newFileStream->InitWithFile(file);
+ if (NS_SUCCEEDED(rv))
+ rv = newFileStream->QueryInterface(NS_GET_IID(nsIOutputStream), (void **) fileStream);
+ return rv;
+}
+
+nsresult MsgReopenFileStream(nsIFile *file, nsIInputStream *fileStream)
+{
+ nsMsgFileStream *msgFileStream = static_cast<nsMsgFileStream *>(fileStream);
+ if (msgFileStream)
+ return msgFileStream->InitWithFile(file);
+ else
+ return NS_ERROR_FAILURE;
+}
+
+nsresult MsgNewBufferedFileOutputStream(nsIOutputStream **aResult,
+ nsIFile* aFile,
+ int32_t aIOFlags,
+ int32_t aPerm)
+{
+ nsCOMPtr<nsIOutputStream> stream;
+ nsresult rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile, aIOFlags, aPerm);
+ if (NS_SUCCEEDED(rv))
+ rv = NS_NewBufferedOutputStream(aResult, stream, FILE_IO_BUFFER_SIZE);
+ return rv;
+}
+
+nsresult MsgNewSafeBufferedFileOutputStream(nsIOutputStream **aResult,
+ nsIFile* aFile,
+ int32_t aIOFlags,
+ int32_t aPerm)
+{
+ nsCOMPtr<nsIOutputStream> stream;
+ nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), aFile, aIOFlags, aPerm);
+ if (NS_SUCCEEDED(rv))
+ rv = NS_NewBufferedOutputStream(aResult, stream, FILE_IO_BUFFER_SIZE);
+ return rv;
+}
+
+bool MsgFindKeyword(const nsCString &keyword, nsCString &keywords, int32_t *aStartOfKeyword, int32_t *aLength)
+{
+#ifdef MOZILLA_INTERNAL_API
+// nsTString_CharT::Find(const nsCString& aString,
+// bool aIgnoreCase=false,
+// int32_t aOffset=0,
+// int32_t aCount=-1 ) const;
+#define FIND_KEYWORD(keywords,keyword,offset) ((keywords).Find((keyword), false, (offset)))
+#else
+// nsAString::Find(const self_type& aStr,
+// uint32_t aOffset,
+// ComparatorFunc c = DefaultComparator) const;
+#define FIND_KEYWORD(keywords,keyword,offset) ((keywords).Find((keyword), static_cast<uint32_t>(offset)))
+#endif
+ // 'keyword' is the single keyword we're looking for
+ // 'keywords' is a space delimited list of keywords to be searched,
+ // which may be just a single keyword or even be empty
+ const int32_t kKeywordLen = keyword.Length();
+ const char* start = keywords.BeginReading();
+ const char* end = keywords.EndReading();
+ *aStartOfKeyword = FIND_KEYWORD(keywords, keyword, 0);
+ while (*aStartOfKeyword >= 0)
+ {
+ const char* matchStart = start + *aStartOfKeyword;
+ const char* matchEnd = matchStart + kKeywordLen;
+ // For a real match, matchStart must be the start of keywords or preceded
+ // by a space and matchEnd must be the end of keywords or point to a space.
+ if ((matchStart == start || *(matchStart - 1) == ' ') &&
+ (matchEnd == end || *matchEnd == ' '))
+ {
+ *aLength = kKeywordLen;
+ return true;
+ }
+ *aStartOfKeyword = FIND_KEYWORD(keywords, keyword, *aStartOfKeyword + kKeywordLen);
+ }
+
+ *aLength = 0;
+ return false;
+#undef FIND_KEYWORD
+}
+
+bool MsgHostDomainIsTrusted(nsCString &host, nsCString &trustedMailDomains)
+{
+ const char *end;
+ uint32_t hostLen, domainLen;
+ bool domainIsTrusted = false;
+
+ const char *domain = trustedMailDomains.BeginReading();
+ const char *domainEnd = trustedMailDomains.EndReading();
+ const char *hostStart = host.BeginReading();
+ hostLen = host.Length();
+
+ do {
+ // skip any whitespace
+ while (*domain == ' ' || *domain == '\t')
+ ++domain;
+
+ // find end of this domain in the string
+ end = strchr(domain, ',');
+ if (!end)
+ end = domainEnd;
+
+ // to see if the hostname is in the domain, check if the domain
+ // matches the end of the hostname.
+ domainLen = end - domain;
+ if (domainLen && hostLen >= domainLen) {
+ const char *hostTail = hostStart + hostLen - domainLen;
+ if (PL_strncasecmp(domain, hostTail, domainLen) == 0)
+ {
+ // now, make sure either that the hostname is a direct match or
+ // that the hostname begins with a dot.
+ if (hostLen == domainLen || *hostTail == '.' || *(hostTail - 1) == '.')
+ {
+ domainIsTrusted = true;
+ break;
+ }
+ }
+ }
+
+ domain = end + 1;
+ } while (*end);
+ return domainIsTrusted;
+}
+
+nsresult MsgGetLocalFileFromURI(const nsACString &aUTF8Path, nsIFile **aFile)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURI> argURI;
+ rv = NS_NewURI(getter_AddRefs(argURI), aUTF8Path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFileURL> argFileURL(do_QueryInterface(argURI, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> argFile;
+ rv = argFileURL->GetFile(getter_AddRefs(argFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ argFile.forget(aFile);
+ return NS_OK;
+}
+
+#ifndef MOZILLA_INTERNAL_API
+/*
+ * Function copied from nsReadableUtils.
+ * Migrating to frozen linkage is the only change done
+ */
+NS_MSG_BASE bool MsgIsUTF8(const nsACString& aString)
+{
+ const char *done_reading = aString.EndReading();
+
+ int32_t state = 0;
+ bool overlong = false;
+ bool surrogate = false;
+ bool nonchar = false;
+ uint16_t olupper = 0; // overlong byte upper bound.
+ uint16_t slower = 0; // surrogate byte lower bound.
+
+ const char *ptr = aString.BeginReading();
+
+ while (ptr < done_reading) {
+ uint8_t c;
+
+ if (0 == state) {
+
+ c = *ptr++;
+
+ if ((c & 0x80) == 0x00)
+ continue;
+
+ if ( c <= 0xC1 ) // [80-BF] where not expected, [C0-C1] for overlong.
+ return false;
+ else if ((c & 0xE0) == 0xC0)
+ state = 1;
+ else if ((c & 0xF0) == 0xE0) {
+ state = 2;
+ if ( c == 0xE0 ) { // to exclude E0[80-9F][80-BF]
+ overlong = true;
+ olupper = 0x9F;
+ } else if ( c == 0xED ) { // ED[A0-BF][80-BF] : surrogate codepoint
+ surrogate = true;
+ slower = 0xA0;
+ } else if ( c == 0xEF ) // EF BF [BE-BF] : non-character
+ nonchar = true;
+ } else if ( c <= 0xF4 ) { // XXX replace /w UTF8traits::is4byte when it's updated to exclude [F5-F7].(bug 199090)
+ state = 3;
+ nonchar = true;
+ if ( c == 0xF0 ) { // to exclude F0[80-8F][80-BF]{2}
+ overlong = true;
+ olupper = 0x8F;
+ }
+ else if ( c == 0xF4 ) { // to exclude F4[90-BF][80-BF]
+ // actually not surrogates but codepoints beyond 0x10FFFF
+ surrogate = true;
+ slower = 0x90;
+ }
+ } else
+ return false; // Not UTF-8 string
+ }
+
+ while (ptr < done_reading && state) {
+ c = *ptr++;
+ --state;
+
+ // non-character : EF BF [BE-BF] or F[0-7] [89AB]F BF [BE-BF]
+ if ( nonchar && ( !state && c < 0xBE ||
+ state == 1 && c != 0xBF ||
+ state == 2 && 0x0F != (0x0F & c) ))
+ nonchar = false;
+
+ if ((c & 0xC0) != 0x80 || overlong && c <= olupper ||
+ surrogate && slower <= c || nonchar && !state )
+ return false; // Not UTF-8 string
+ overlong = surrogate = false;
+ }
+ }
+ return !state; // state != 0 at the end indicates an invalid UTF-8 seq.
+}
+
+#endif
+
+NS_MSG_BASE void MsgStripQuotedPrintable (unsigned char *src)
+{
+ // decode quoted printable text in place
+
+ if (!*src)
+ return;
+ unsigned char *dest = src;
+ int srcIdx = 0, destIdx = 0;
+
+ while (src[srcIdx] != 0)
+ {
+ // Decode sequence of '=XY' into a character with code XY.
+ if (src[srcIdx] == '=')
+ {
+ if (MsgIsHex((const char*)src + srcIdx + 1, 2)) {
+ // If we got here, we successfully decoded a quoted printable sequence,
+ // so bump each pointer past it and move on to the next char.
+ dest[destIdx++] = MsgUnhex((const char*)src + srcIdx + 1, 2);
+ srcIdx += 3;
+ }
+ else
+ {
+ // If first char after '=' isn't hex check if it's a normal char
+ // or a soft line break. If it's a soft line break, eat the
+ // CR/LF/CRLF.
+ if (src[srcIdx + 1] == '\r' || src[srcIdx + 1] == '\n')
+ {
+ srcIdx++; // soft line break, ignore the '=';
+ if (src[srcIdx] == '\r' || src[srcIdx] == '\n')
+ {
+ srcIdx++;
+ if (src[srcIdx] == '\n')
+ srcIdx++;
+ }
+ }
+ else // The first or second char after '=' isn't hex, just copy the '='.
+ {
+ dest[destIdx++] = src[srcIdx++];
+ }
+ continue;
+ }
+ }
+ else
+ dest[destIdx++] = src[srcIdx++];
+ }
+
+ dest[destIdx] = src[srcIdx]; // null terminate
+}
+
+NS_MSG_BASE nsresult MsgEscapeString(const nsACString &aStr,
+ uint32_t aType, nsACString &aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nu->EscapeString(aStr, aType, aResult);
+}
+
+NS_MSG_BASE nsresult MsgUnescapeString(const nsACString &aStr, uint32_t aFlags,
+ nsACString &aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nu->UnescapeString(aStr, aFlags, aResult);
+}
+
+NS_MSG_BASE nsresult MsgEscapeURL(const nsACString &aStr, uint32_t aFlags,
+ nsACString &aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nu->EscapeURL(aStr, aFlags, aResult);
+}
+
+#ifndef MOZILLA_INTERNAL_API
+
+NS_MSG_BASE char *MsgEscapeHTML(const char *string)
+{
+ char *rv = nullptr;
+ /* XXX Hardcoded max entity len. The +1 is for the trailing null. */
+ uint32_t len = PL_strlen(string);
+ if (len >= (PR_UINT32_MAX / 6))
+ return nullptr;
+
+ rv = (char *)NS_Alloc( (6 * len) + 1 );
+ char *ptr = rv;
+
+ if (rv)
+ {
+ for(; *string != '\0'; string++)
+ {
+ if (*string == '<')
+ {
+ *ptr++ = '&';
+ *ptr++ = 'l';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ }
+ else if (*string == '>')
+ {
+ *ptr++ = '&';
+ *ptr++ = 'g';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ }
+ else if (*string == '&')
+ {
+ *ptr++ = '&';
+ *ptr++ = 'a';
+ *ptr++ = 'm';
+ *ptr++ = 'p';
+ *ptr++ = ';';
+ }
+ else if (*string == '"')
+ {
+ *ptr++ = '&';
+ *ptr++ = 'q';
+ *ptr++ = 'u';
+ *ptr++ = 'o';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ }
+ else if (*string == '\'')
+ {
+ *ptr++ = '&';
+ *ptr++ = '#';
+ *ptr++ = '3';
+ *ptr++ = '9';
+ *ptr++ = ';';
+ }
+ else
+ {
+ *ptr++ = *string;
+ }
+ }
+ *ptr = '\0';
+ }
+ return(rv);
+}
+
+NS_MSG_BASE char16_t *MsgEscapeHTML2(const char16_t *aSourceBuffer,
+ int32_t aSourceBufferLen)
+{
+ // if the caller didn't calculate the length
+ if (aSourceBufferLen == -1) {
+ aSourceBufferLen = NS_strlen(aSourceBuffer); // ...then I will
+ }
+
+ /* XXX Hardcoded max entity len. */
+ if (aSourceBufferLen >=
+ ((PR_UINT32_MAX - sizeof(char16_t)) / (6 * sizeof(char16_t))) )
+ return nullptr;
+
+ char16_t *resultBuffer = (char16_t *)moz_xmalloc(aSourceBufferLen *
+ 6 * sizeof(char16_t) + sizeof(char16_t('\0')));
+
+ char16_t *ptr = resultBuffer;
+
+ if (resultBuffer) {
+ int32_t i;
+
+ for(i = 0; i < aSourceBufferLen; i++) {
+ if(aSourceBuffer[i] == '<') {
+ *ptr++ = '&';
+ *ptr++ = 'l';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ } else if(aSourceBuffer[i] == '>') {
+ *ptr++ = '&';
+ *ptr++ = 'g';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ } else if(aSourceBuffer[i] == '&') {
+ *ptr++ = '&';
+ *ptr++ = 'a';
+ *ptr++ = 'm';
+ *ptr++ = 'p';
+ *ptr++ = ';';
+ } else if (aSourceBuffer[i] == '"') {
+ *ptr++ = '&';
+ *ptr++ = 'q';
+ *ptr++ = 'u';
+ *ptr++ = 'o';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ } else if (aSourceBuffer[i] == '\'') {
+ *ptr++ = '&';
+ *ptr++ = '#';
+ *ptr++ = '3';
+ *ptr++ = '9';
+ *ptr++ = ';';
+ } else {
+ *ptr++ = aSourceBuffer[i];
+ }
+ }
+ *ptr = 0;
+ }
+
+ return resultBuffer;
+}
+
+NS_MSG_BASE void MsgCompressWhitespace(nsCString& aString)
+{
+ // This code is frozen linkage specific
+ aString.Trim(" \f\n\r\t\v");
+
+ char *start, *end;
+ aString.BeginWriting(&start, &end);
+
+ for (char *cur = start; cur < end; ++cur) {
+ if (!IS_SPACE(*cur))
+ continue;
+
+ *cur = ' ';
+
+ if (!IS_SPACE(*(cur + 1)))
+ continue;
+
+ // Loop through the white space
+ char *wend = cur + 2;
+ while (IS_SPACE(*wend))
+ ++wend;
+
+ uint32_t wlen = wend - cur - 1;
+
+ // fix "end"
+ end -= wlen;
+
+ // move everything forwards a bit
+ for (char *m = cur + 1; m < end; ++m) {
+ *m = *(m + wlen);
+ }
+ }
+
+ // Set the new length.
+ aString.SetLength(end - start);
+}
+
+
+NS_MSG_BASE void MsgReplaceChar(nsString& str, const char *set, const char16_t replacement)
+{
+ char16_t *c_str = str.BeginWriting();
+ while (*set) {
+ int32_t pos = 0;
+ while ((pos = str.FindChar(*set, pos)) != -1) {
+ c_str[pos++] = replacement;
+ }
+ set++;
+ }
+}
+
+NS_MSG_BASE void MsgReplaceChar(nsCString& str, const char needle, const char replacement)
+{
+ char *c_str = str.BeginWriting();
+ while ((c_str = strchr(c_str, needle))) {
+ *c_str = replacement;
+ c_str++;
+ }
+}
+
+NS_MSG_BASE already_AddRefed<nsIAtom> MsgNewAtom(const char* aString)
+{
+ nsCOMPtr<nsIAtomService> atomService(do_GetService("@mozilla.org/atom-service;1"));
+ nsCOMPtr<nsIAtom> atom;
+
+ if (atomService)
+ atomService->GetAtomUTF8(aString, getter_AddRefs(atom));
+ return atom.forget();
+}
+
+NS_MSG_BASE void MsgReplaceSubstring(nsAString &str, const nsAString &what, const nsAString &replacement)
+{
+ const char16_t* replacement_str;
+ uint32_t replacementLength = replacement.BeginReading(&replacement_str);
+ uint32_t whatLength = what.Length();
+ int32_t i = 0;
+
+ while ((i = str.Find(what, i)) != kNotFound)
+ {
+ str.Replace(i, whatLength, replacement_str, replacementLength);
+ i += replacementLength;
+ }
+}
+
+NS_MSG_BASE void MsgReplaceSubstring(nsACString &str, const char *what, const char *replacement)
+{
+ uint32_t replacementLength = strlen(replacement);
+ uint32_t whatLength = strlen(what);
+ int32_t i = 0;
+
+ /* We have to create nsDependentCString from 'what' because there's no
+ * str.Find(char *what, int offset) but there is only
+ * str.Find(char *what, int length) */
+ nsDependentCString what_dependent(what);
+ while ((i = str.Find(what_dependent, i)) != kNotFound)
+ {
+ str.Replace(i, whatLength, replacement, replacementLength);
+ i += replacementLength;
+ }
+}
+
+/* This class is based on nsInterfaceRequestorAgg from nsInterfaceRequestorAgg.h */
+class MsgInterfaceRequestorAgg : public nsIInterfaceRequestor
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ MsgInterfaceRequestorAgg(nsIInterfaceRequestor *aFirst,
+ nsIInterfaceRequestor *aSecond)
+ : mFirst(aFirst)
+ , mSecond(aSecond) {}
+
+ nsCOMPtr<nsIInterfaceRequestor> mFirst, mSecond;
+};
+
+// XXX This needs to support threadsafe refcounting until we fix bug 243591.
+NS_IMPL_ISUPPORTS(MsgInterfaceRequestorAgg, nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+MsgInterfaceRequestorAgg::GetInterface(const nsIID &aIID, void **aResult)
+{
+ nsresult rv = NS_ERROR_NO_INTERFACE;
+ if (mFirst)
+ rv = mFirst->GetInterface(aIID, aResult);
+ if (mSecond && NS_FAILED(rv))
+ rv = mSecond->GetInterface(aIID, aResult);
+ return rv;
+}
+
+/* This function is based on NS_NewInterfaceRequestorAggregation from
+ * nsInterfaceRequestorAgg.h */
+NS_MSG_BASE nsresult
+MsgNewInterfaceRequestorAggregation(nsIInterfaceRequestor *aFirst,
+ nsIInterfaceRequestor *aSecond,
+ nsIInterfaceRequestor **aResult)
+{
+ *aResult = new MsgInterfaceRequestorAgg(aFirst, aSecond);
+ if (!*aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+nsresult NS_FASTCALL MsgQueryElementAt::operator()( const nsIID& aIID, void** aResult ) const
+ {
+ nsresult status = mArray
+ ? mArray->QueryElementAt(mIndex, aIID, aResult)
+ : NS_ERROR_NULL_POINTER;
+
+ if ( mErrorPtr )
+ *mErrorPtr = status;
+
+ return status;
+ }
+
+#endif
+
+NS_MSG_BASE nsresult MsgGetHeadersFromKeys(nsIMsgDatabase *aDB, const nsTArray<nsMsgKey> &aMsgKeys,
+ nsIMutableArray *aHeaders)
+{
+ NS_ENSURE_ARG_POINTER(aDB);
+
+ uint32_t count = aMsgKeys.Length();
+ nsresult rv = NS_OK;
+
+ for (uint32_t kindex = 0; kindex < count; kindex++)
+ {
+ nsMsgKey key = aMsgKeys.ElementAt(kindex);
+
+ bool hasKey;
+ rv = aDB->ContainsKey(key, &hasKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This function silently skips when the key is not found. This is an expected case.
+ if (hasKey)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = aDB->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHeaders->AppendElement(msgHdr, false);
+ }
+ }
+
+ return rv;
+}
+
+NS_MSG_BASE nsresult MsgGetHdrsFromKeys(nsIMsgDatabase *aDB, nsMsgKey *aMsgKeys,
+ uint32_t aNumKeys, nsIMutableArray **aHeaders)
+{
+ NS_ENSURE_ARG_POINTER(aDB);
+ NS_ENSURE_ARG_POINTER(aMsgKeys);
+ NS_ENSURE_ARG_POINTER(aHeaders);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t kindex = 0; kindex < aNumKeys; kindex++) {
+ nsMsgKey key = aMsgKeys[kindex];
+ bool hasKey;
+ rv = aDB->ContainsKey(key, &hasKey);
+ // This function silently skips when the key is not found. This is an expected case.
+ if (NS_SUCCEEDED(rv) && hasKey)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = aDB->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv))
+ messages->AppendElement(msgHdr, false);
+ }
+ }
+
+ messages.forget(aHeaders);
+ return NS_OK;
+}
+
+bool MsgAdvanceToNextLine(const char *buffer, uint32_t &bufferOffset, uint32_t maxBufferOffset)
+{
+ bool result = false;
+ for (; bufferOffset < maxBufferOffset; bufferOffset++)
+ {
+ if (buffer[bufferOffset] == '\r' || buffer[bufferOffset] == '\n')
+ {
+ bufferOffset++;
+ if (buffer[bufferOffset- 1] == '\r' && buffer[bufferOffset] == '\n')
+ bufferOffset++;
+ result = true;
+ break;
+ }
+ }
+ return result;
+}
+
+NS_MSG_BASE nsresult
+MsgExamineForProxy(nsIChannel *channel, nsIProxyInfo **proxyInfo)
+{
+ nsresult rv;
+
+#ifdef DEBUG
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetURI(getter_AddRefs(uri));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && uri,
+ "The URI needs to be set before calling the proxy service");
+#endif
+
+ nsCOMPtr<nsIProtocolProxyService> proxyService =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX: This "interface" ID is exposed, but it's not hooked up to the QI.
+ // Until it is, use a static_cast for now.
+#if 0
+ RefPtr<nsProtocolProxyService> rawProxyService = do_QueryObject(proxyService, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+#else
+ nsProtocolProxyService *rawProxyService = static_cast<nsProtocolProxyService*>(proxyService.get());
+#endif
+
+ return rawProxyService->DeprecatedBlockingResolve(channel, 0, proxyInfo);
+}
+
+NS_MSG_BASE nsresult MsgPromptLoginFailed(nsIMsgWindow *aMsgWindow,
+ const nsCString &aHostname,
+ int32_t *aResult)
+{
+
+ nsCOMPtr<nsIPrompt> dialog;
+ if (aMsgWindow)
+ aMsgWindow->GetPromptDialog(getter_AddRefs(dialog));
+
+ nsresult rv;
+
+ // If we haven't got one, use a default dialog.
+ if (!dialog)
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = wwatch->GetNewPrompter(0, getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString message;
+ NS_ConvertUTF8toUTF16 hostNameUTF16(aHostname);
+ const char16_t *formatStrings[] = { hostNameUTF16.get() };
+
+ rv = bundle->FormatStringFromName(u"mailServerLoginFailed",
+ formatStrings, 1,
+ getter_Copies(message));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString title;
+ rv = bundle->GetStringFromName(
+ u"mailServerLoginFailedTitle", getter_Copies(title));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString button0;
+ rv = bundle->GetStringFromName(
+ u"mailServerLoginFailedRetryButton",
+ getter_Copies(button0));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString button2;
+ rv = bundle->GetStringFromName(
+ u"mailServerLoginFailedEnterNewPasswordButton",
+ getter_Copies(button2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool dummyValue = false;
+ return dialog->ConfirmEx(
+ title.get(), message.get(),
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1) +
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2),
+ button0.get(), nullptr, button2.get(), nullptr, &dummyValue, aResult);
+}
+
+NS_MSG_BASE PRTime MsgConvertAgeInDaysToCutoffDate(int32_t ageInDays)
+{
+ PRTime now = PR_Now();
+
+ return now - PR_USEC_PER_DAY * ageInDays;
+}
+
+NS_MSG_BASE nsresult MsgTermListToString(nsISupportsArray *aTermList, nsCString &aOutString)
+{
+ uint32_t count;
+ aTermList->Count(&count);
+ nsresult rv = NS_OK;
+
+ for (uint32_t searchIndex = 0; searchIndex < count;
+ searchIndex++)
+ {
+ nsAutoCString stream;
+
+ nsCOMPtr<nsIMsgSearchTerm> term;
+ aTermList->QueryElementAt(searchIndex, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(term));
+ if (!term)
+ continue;
+
+ if (aOutString.Length() > 1)
+ aOutString += ' ';
+
+ bool booleanAnd;
+ bool matchAll;
+ term->GetBooleanAnd(&booleanAnd);
+ term->GetMatchAll(&matchAll);
+ if (matchAll)
+ {
+ aOutString += "ALL";
+ continue;
+ }
+ else if (booleanAnd)
+ aOutString += "AND (";
+ else
+ aOutString += "OR (";
+
+ rv = term->GetTermAsString(stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aOutString += stream;
+ aOutString += ')';
+ }
+ return rv;
+}
+
+NS_MSG_BASE uint64_t ParseUint64Str(const char *str)
+{
+#ifdef XP_WIN
+ {
+ char *endPtr;
+ return _strtoui64(str, &endPtr, 10);
+ }
+#else
+ return strtoull(str, nullptr, 10);
+#endif
+}
+
+NS_MSG_BASE uint64_t MsgUnhex(const char *aHexString, size_t aNumChars)
+{
+ // Large numbers will not fit into uint64_t.
+ NS_ASSERTION(aNumChars <= 16, "Hex literal too long to convert!");
+
+ uint64_t result = 0;
+ for (size_t i = 0; i < aNumChars; i++)
+ {
+ unsigned char c = aHexString[i];
+ uint8_t digit;
+ if ((c >= '0') && (c <= '9'))
+ digit = (c - '0');
+ else if ((c >= 'a') && (c <= 'f'))
+ digit = ((c - 'a') + 10);
+ else if ((c >= 'A') && (c <= 'F'))
+ digit = ((c - 'A') + 10);
+ else
+ break;
+
+ result = (result << 4) | digit;
+ }
+
+ return result;
+}
+
+NS_MSG_BASE bool MsgIsHex(const char *aHexString, size_t aNumChars)
+{
+ for (size_t i = 0; i < aNumChars; i++)
+ {
+ if (!isxdigit(aHexString[i]))
+ return false;
+ }
+ return true;
+}
+
+
+NS_MSG_BASE nsresult
+MsgStreamMsgHeaders(nsIInputStream *aInputStream, nsIStreamListener *aConsumer)
+{
+ nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>);
+ NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv;
+
+ nsAutoCString msgHeaders;
+ nsAutoCString curLine;
+
+ bool more = true;
+
+ // We want to NS_ReadLine until we get to a blank line (the end of the headers)
+ while (more)
+ {
+ rv = NS_ReadLine(aInputStream, lineBuffer.get(), curLine, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (curLine.IsEmpty())
+ break;
+ msgHeaders.Append(curLine);
+ msgHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
+ }
+ lineBuffer = nullptr;
+ nsCOMPtr<nsIStringInputStream> hdrsStream =
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ hdrsStream->SetData(msgHeaders.get(), msgHeaders.Length());
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), hdrsStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return pump->AsyncRead(aConsumer, nullptr);
+}
+
+class CharsetDetectionObserver : public nsICharsetDetectionObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ CharsetDetectionObserver() {};
+ NS_IMETHOD Notify(const char* aCharset, nsDetectionConfident aConf) override
+ {
+ mCharset = aCharset;
+ return NS_OK;
+ };
+ const char *GetDetectedCharset() { return mCharset.get(); }
+
+private:
+ virtual ~CharsetDetectionObserver() {}
+ nsCString mCharset;
+};
+
+NS_IMPL_ISUPPORTS(CharsetDetectionObserver, nsICharsetDetectionObserver)
+
+NS_MSG_BASE nsresult
+MsgDetectCharsetFromFile(nsIFile *aFile, nsACString &aCharset)
+{
+ // First try the universal charset detector
+ nsCOMPtr<nsICharsetDetector> detector
+ = do_CreateInstance(NS_CHARSET_DETECTOR_CONTRACTID_BASE
+ "universal_charset_detector");
+ if (!detector) {
+ // No universal charset detector, try the default charset detector
+ nsString detectorName;
+ NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, "intl.charset.detector",
+ EmptyString(), detectorName);
+ if (!detectorName.IsEmpty()) {
+ nsAutoCString detectorContractID;
+ detectorContractID.AssignLiteral(NS_CHARSET_DETECTOR_CONTRACTID_BASE);
+ AppendUTF16toUTF8(detectorName, detectorContractID);
+ detector = do_CreateInstance(detectorContractID.get());
+ }
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (detector) {
+ nsAutoCString buffer;
+
+ RefPtr<CharsetDetectionObserver> observer = new CharsetDetectionObserver();
+
+ rv = detector->Init(observer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILineInputStream> lineInputStream;
+ lineInputStream = do_QueryInterface(inputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isMore = true;
+ bool dontFeed = false;
+ while (isMore &&
+ NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore)) &&
+ buffer.Length() > 0) {
+ detector->DoIt(buffer.get(), buffer.Length(), &dontFeed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (dontFeed)
+ break;
+ }
+ rv = detector->Done();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aCharset = observer->GetDetectedCharset();
+ } else {
+ // no charset detector available, check the BOM
+ char sniffBuf[3];
+ uint32_t numRead;
+ rv = inputStream->Read(sniffBuf, sizeof(sniffBuf), &numRead);
+
+ if (numRead >= 2 &&
+ sniffBuf[0] == (char)0xfe &&
+ sniffBuf[1] == (char)0xff) {
+ aCharset = "UTF-16BE";
+ } else if (numRead >= 2 &&
+ sniffBuf[0] == (char)0xff &&
+ sniffBuf[1] == (char)0xfe) {
+ aCharset = "UTF-16LE";
+ } else if (numRead >= 3 &&
+ sniffBuf[0] == (char)0xef &&
+ sniffBuf[1] == (char)0xbb &&
+ sniffBuf[2] == (char)0xbf) {
+ aCharset = "UTF-8";
+ }
+ }
+
+ if (aCharset.IsEmpty()) { // No sniffed or charset.
+ nsAutoCString buffer;
+ nsCOMPtr<nsILineInputStream> lineInputStream =
+ do_QueryInterface(inputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isMore = true;
+ bool isUTF8Compat = true;
+ while (isMore && isUTF8Compat &&
+ NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
+ isUTF8Compat = MsgIsUTF8(buffer);
+ }
+
+ // If the file content is UTF-8 compatible, use that. Otherwise let's not
+ // make a bad guess.
+ if (isUTF8Compat)
+ aCharset.AssignLiteral("UTF-8");
+ }
+
+ if (aCharset.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+/*
+ * Converts a buffer to plain text. Some conversions may
+ * or may not work with certain end charsets which is why we
+ * need that as an argument to the function. If charset is
+ * unknown or deemed of no importance NULL could be passed.
+ */
+NS_MSG_BASE nsresult
+ConvertBufToPlainText(nsString &aConBuf, bool formatFlowed, bool delsp,
+ bool formatOutput, bool disallowBreaks)
+{
+ if (aConBuf.IsEmpty())
+ return NS_OK;
+
+ int32_t wrapWidth = 72;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ if (pPrefBranch)
+ {
+ pPrefBranch->GetIntPref("mailnews.wraplength", &wrapWidth);
+ // Let sanity reign!
+ if (wrapWidth == 0 || wrapWidth > 990)
+ wrapWidth = 990;
+ else if (wrapWidth < 10)
+ wrapWidth = 10;
+ }
+
+ uint32_t converterFlags = nsIDocumentEncoder::OutputPersistNBSP;
+ if (formatFlowed)
+ converterFlags |= nsIDocumentEncoder::OutputFormatFlowed;
+ if (delsp)
+ converterFlags |= nsIDocumentEncoder::OutputFormatDelSp;
+ if (formatOutput)
+ converterFlags |= nsIDocumentEncoder::OutputFormatted;
+ if (disallowBreaks)
+ converterFlags |= nsIDocumentEncoder::OutputDisallowLineBreaking;
+
+ nsCOMPtr<nsIParserUtils> utils =
+ do_GetService(NS_PARSERUTILS_CONTRACTID);
+ return utils->ConvertToPlainText(aConBuf,
+ converterFlags,
+ wrapWidth,
+ aConBuf);
+}
+
+NS_MSG_BASE nsMsgKey msgKeyFromInt(uint32_t aValue)
+{
+ return aValue;
+}
+
+NS_MSG_BASE nsMsgKey msgKeyFromInt(uint64_t aValue)
+{
+ NS_ASSERTION(aValue <= PR_UINT32_MAX, "Msg key value too big!");
+ return aValue;
+}
+
+// Helper function to extract a query qualifier.
+nsAutoCString MsgExtractQueryPart(nsAutoCString spec, const char* queryToExtract)
+{
+ nsAutoCString queryPart;
+ int32_t queryIndex = spec.Find(queryToExtract);
+ if (queryIndex == kNotFound)
+ return queryPart;
+
+ int32_t queryEnd = spec.FindChar('&', queryIndex + 1);
+ if (queryEnd == kNotFound)
+ queryEnd = spec.FindChar('?', queryIndex + 1);
+ if (queryEnd == kNotFound) {
+ // Nothing follows, so return from where the query qualifier started.
+ queryPart.Assign(Substring(spec, queryIndex));
+ } else {
+ // Return the substring that represents the query qualifier.
+ queryPart.Assign(Substring(spec, queryIndex, queryEnd - queryIndex));
+ }
+ return queryPart;
+}
diff --git a/mailnews/base/util/nsMsgUtils.h b/mailnews/base/util/nsMsgUtils.h
new file mode 100644
index 000000000..97f5010ae
--- /dev/null
+++ b/mailnews/base/util/nsMsgUtils.h
@@ -0,0 +1,589 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _NSMSGUTILS_H
+#define _NSMSGUTILS_H
+
+#include "nsIURL.h"
+#include "nsStringGlue.h"
+#include "msgCore.h"
+#include "nsCOMPtr.h"
+#include "MailNewsTypes2.h"
+#include "nsTArray.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "nsILoadGroup.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsIAtom.h"
+#include "nsINetUtil.h"
+#include "nsIRequest.h"
+#include "nsILoadInfo.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIFile.h"
+
+class nsIChannel;
+class nsIFile;
+class nsIPrefBranch;
+class nsIMsgFolder;
+class nsIMsgMessageService;
+class nsIUrlListener;
+class nsIOutputStream;
+class nsIInputStream;
+class nsIMsgDatabase;
+class nsIMutableArray;
+class nsIProxyInfo;
+class nsIMsgWindow;
+class nsISupportsArray;
+class nsIStreamListener;
+
+#define FILE_IO_BUFFER_SIZE (16*1024)
+#define MSGS_URL "chrome://messenger/locale/messenger.properties"
+
+//These are utility functions that can used throughout the mailnews code
+
+NS_MSG_BASE nsresult GetMessageServiceContractIDForURI(const char *uri, nsCString &contractID);
+
+NS_MSG_BASE nsresult GetMessageServiceFromURI(const nsACString& uri, nsIMsgMessageService **aMessageService);
+
+NS_MSG_BASE nsresult GetMsgDBHdrFromURI(const char *uri, nsIMsgDBHdr **msgHdr);
+
+NS_MSG_BASE nsresult CreateStartupUrl(const char *uri, nsIURI** aUrl);
+
+NS_MSG_BASE nsresult NS_MsgGetPriorityFromString(
+ const char * const priority,
+ nsMsgPriorityValue & outPriority);
+
+NS_MSG_BASE nsresult NS_MsgGetPriorityValueString(
+ const nsMsgPriorityValue p,
+ nsACString & outValueString);
+
+NS_MSG_BASE nsresult NS_MsgGetUntranslatedPriorityName(
+ const nsMsgPriorityValue p,
+ nsACString & outName);
+
+NS_MSG_BASE nsresult NS_MsgHashIfNecessary(nsAutoString &name);
+NS_MSG_BASE nsresult NS_MsgHashIfNecessary(nsAutoCString &name);
+
+NS_MSG_BASE nsresult FormatFileSize(int64_t size, bool useKB, nsAString &formattedSize);
+
+
+/**
+ * given a folder uri, return the path to folder in the user profile directory.
+ *
+ * @param aFolderURI uri of folder we want the path to, without the scheme
+ * @param[out] aPathString result path string
+ * @param aScheme scheme of the uri
+ * @param[optional] aIsNewsFolder is this a news folder?
+ */
+NS_MSG_BASE nsresult
+NS_MsgCreatePathStringFromFolderURI(const char *aFolderURI,
+ nsCString& aPathString,
+ const nsCString &aScheme,
+ bool aIsNewsFolder=false);
+
+/**
+ * Given a string and a length, removes any "Re:" strings from the front.
+ * It also deals with that dumbass "Re[2]:" thing that some losing mailers do.
+ *
+ * If mailnews.localizedRe is set, it will also remove localized "Re:" strings.
+ *
+ * @return true if it made a change (in which case the caller should look to
+ * modifiedSubject for the result) and false otherwise (in which
+ * case the caller should look at subject for the result)
+ */
+NS_MSG_BASE bool NS_MsgStripRE(const nsCString& subject, nsCString& modifiedSubject);
+
+NS_MSG_BASE char * NS_MsgSACopy(char **destination, const char *source);
+
+NS_MSG_BASE char * NS_MsgSACat(char **destination, const char *source);
+
+NS_MSG_BASE nsresult NS_MsgEscapeEncodeURLPath(const nsAString& aStr,
+ nsCString& aResult);
+
+NS_MSG_BASE nsresult NS_MsgDecodeUnescapeURLPath(const nsACString& aPath,
+ nsAString& aResult);
+
+NS_MSG_BASE bool WeAreOffline();
+
+// Check if a folder with aFolderUri exists
+NS_MSG_BASE nsresult GetExistingFolder(const nsCString& aFolderURI, nsIMsgFolder **aFolder);
+
+// Escape lines starting with "From ", ">From ", etc. in a buffer.
+NS_MSG_BASE nsresult EscapeFromSpaceLine(nsIOutputStream *ouputStream, char *start, const char *end);
+NS_MSG_BASE bool IsAFromSpaceLine(char *start, const char *end);
+
+NS_MSG_BASE nsresult NS_GetPersistentFile(const char *relPrefName,
+ const char *absPrefName,
+ const char *dirServiceProp, // Can be NULL
+ bool& gotRelPref,
+ nsIFile **aFile,
+ nsIPrefBranch *prefBranch = nullptr);
+
+NS_MSG_BASE nsresult NS_SetPersistentFile(const char *relPrefName,
+ const char *absPrefName,
+ nsIFile *aFile,
+ nsIPrefBranch *prefBranch = nullptr);
+
+NS_MSG_BASE nsresult IsRFC822HeaderFieldName(const char *aHdr, bool *aResult);
+
+NS_MSG_BASE nsresult NS_GetUnicharPreferenceWithDefault(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch
+ const char *prefName,
+ const nsAString& defValue,
+ nsAString& prefValue);
+
+NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreferenceWithDefault(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch
+ const char *prefName,
+ const nsAString& defValue,
+ nsAString& prefValue);
+
+NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreference(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch
+ const char *prefName,
+ nsAString& prefValue);
+
+ /**
+ * this needs a listener, because we might have to create the folder
+ * on the server, and that is asynchronous
+ */
+NS_MSG_BASE nsresult GetOrCreateFolder(const nsACString & aURI, nsIUrlListener *aListener);
+
+// Returns true if the nsIURI is a message under an RSS account
+NS_MSG_BASE nsresult IsRSSArticle(nsIURI * aMsgURI, bool *aIsRSSArticle);
+
+// digest needs to be a pointer to a 16 byte buffer
+#define DIGEST_LENGTH 16
+
+NS_MSG_BASE nsresult MSGCramMD5(const char *text, int32_t text_len, const char *key, int32_t key_len, unsigned char *digest);
+NS_MSG_BASE nsresult MSGApopMD5(const char *text, int32_t text_len, const char *password, int32_t password_len, unsigned char *digest);
+
+// helper functions to convert a 64bits PRTime into a 32bits value (compatible time_t) and vice versa.
+NS_MSG_BASE void PRTime2Seconds(PRTime prTime, uint32_t *seconds);
+NS_MSG_BASE void PRTime2Seconds(PRTime prTime, int32_t *seconds);
+NS_MSG_BASE void Seconds2PRTime(uint32_t seconds, PRTime *prTime);
+// helper function to generate current date+time as a string
+NS_MSG_BASE void MsgGenerateNowStr(nsACString &nowStr);
+
+// Appends the correct summary file extension onto the supplied fileLocation
+// and returns it in summaryLocation.
+NS_MSG_BASE nsresult GetSummaryFileLocation(nsIFile* fileLocation,
+ nsIFile** summaryLocation);
+
+// Gets a special directory and appends the supplied file name onto it.
+NS_MSG_BASE nsresult GetSpecialDirectoryWithFileName(const char* specialDirName,
+ const char* fileName,
+ nsIFile** result);
+
+// cleanup temp files with the given filename and extension, including
+// the consecutive -NNNN ones that we can find. If there are holes, e.g.,
+// <filename>-1-10,12.<extension> exist, but <filename>-11.<extension> does not
+// we'll clean up 1-10. If the leaks are common, I think the gaps will tend to
+// be filled.
+NS_MSG_BASE nsresult MsgCleanupTempFiles(const char *fileName, const char *extension);
+
+NS_MSG_BASE nsresult MsgGetFileStream(nsIFile *file, nsIOutputStream **fileStream);
+
+NS_MSG_BASE nsresult MsgReopenFileStream(nsIFile *file, nsIInputStream *fileStream);
+
+// Automatically creates an output stream with a suitable buffer
+NS_MSG_BASE nsresult MsgNewBufferedFileOutputStream(nsIOutputStream **aResult, nsIFile *aFile, int32_t aIOFlags = -1, int32_t aPerm = -1);
+
+// Automatically creates an output stream with a suitable buffer, but write to a temporary file first, then rename to aFile
+NS_MSG_BASE nsresult MsgNewSafeBufferedFileOutputStream(nsIOutputStream **aResult, nsIFile *aFile, int32_t aIOFlags = -1, int32_t aPerm = -1);
+
+// fills in the position of the passed in keyword in the passed in keyword list
+// and returns false if the keyword isn't present
+NS_MSG_BASE bool MsgFindKeyword(const nsCString &keyword, nsCString &keywords, int32_t *aStartOfKeyword, int32_t *aLength);
+
+NS_MSG_BASE bool MsgHostDomainIsTrusted(nsCString &host, nsCString &trustedMailDomains);
+
+// gets an nsIFile from a UTF-8 file:// path
+NS_MSG_BASE nsresult MsgGetLocalFileFromURI(const nsACString &aUTF8Path, nsIFile **aFile);
+
+NS_MSG_BASE void MsgStripQuotedPrintable (unsigned char *src);
+
+/*
+ * Utility function copied from nsReadableUtils
+ */
+NS_MSG_BASE bool MsgIsUTF8(const nsACString& aString);
+
+/*
+ * Utility functions that call functions from nsINetUtil
+ */
+
+NS_MSG_BASE nsresult MsgEscapeString(const nsACString &aStr,
+ uint32_t aType, nsACString &aResult);
+
+NS_MSG_BASE nsresult MsgUnescapeString(const nsACString &aStr,
+ uint32_t aFlags, nsACString &aResult);
+
+NS_MSG_BASE nsresult MsgEscapeURL(const nsACString &aStr, uint32_t aFlags,
+ nsACString &aResult);
+
+// Converts an nsTArray of nsMsgKeys plus a database, to an array of nsIMsgDBHdrs.
+NS_MSG_BASE nsresult MsgGetHeadersFromKeys(nsIMsgDatabase *aDB,
+ const nsTArray<nsMsgKey> &aKeys,
+ nsIMutableArray *aHeaders);
+// Converts an array of nsMsgKeys plus a database, to an array of nsIMsgDBHdrs.
+NS_MSG_BASE nsresult MsgGetHdrsFromKeys(nsIMsgDatabase *aDB,
+ nsMsgKey *aKeys,
+ uint32_t aNumKeys,
+ nsIMutableArray **aHeaders);
+
+NS_MSG_BASE nsresult MsgExamineForProxy(nsIChannel *channel,
+ nsIProxyInfo **proxyInfo);
+
+NS_MSG_BASE int32_t MsgFindCharInSet(const nsCString &aString,
+ const char* aChars, uint32_t aOffset = 0);
+NS_MSG_BASE int32_t MsgFindCharInSet(const nsString &aString,
+ const char* aChars, uint32_t aOffset = 0);
+
+
+// advances bufferOffset to the beginning of the next line, if we don't
+// get to maxBufferOffset first. Returns false if we didn't get to the
+// next line.
+NS_MSG_BASE bool MsgAdvanceToNextLine(const char *buffer, uint32_t &bufferOffset,
+ uint32_t maxBufferOffset);
+
+/**
+ * Alerts the user that the login to the server failed. Asks whether the
+ * connection should: retry, cancel, or request a new password.
+ *
+ * @param aMsgWindow The message window associated with this action (cannot
+ * be null).
+ * @param aHostname The hostname of the server for which the login failed.
+ * @param aResult The button pressed. 0 for retry, 1 for cancel,
+ * 2 for enter a new password.
+ * @return NS_OK for success, NS_ERROR_* if there was a failure in
+ * creating the dialog.
+ */
+NS_MSG_BASE nsresult MsgPromptLoginFailed(nsIMsgWindow *aMsgWindow,
+ const nsCString &aHostname,
+ int32_t *aResult);
+
+/**
+ * Calculate a PRTime value used to determine if a date is XX
+ * days ago. This is used by various retention setting algorithms.
+ */
+NS_MSG_BASE PRTime MsgConvertAgeInDaysToCutoffDate(int32_t ageInDays);
+
+/**
+ * Converts the passed in term list to its string representation.
+ *
+ * @param aTermList Array of nsIMsgSearchTerms
+ * @param[out] aOutString result representation of search terms.
+ *
+ */
+NS_MSG_BASE nsresult MsgTermListToString(nsISupportsArray *aTermList, nsCString &aOutString);
+
+NS_MSG_BASE nsresult
+MsgStreamMsgHeaders(nsIInputStream *aInputStream, nsIStreamListener *aConsumer);
+
+/**
+ * convert string to uint64_t
+ *
+ * @param str conveted string
+ * @returns uint64_t vaule for success, 0 for parse failure
+ */
+NS_MSG_BASE uint64_t ParseUint64Str(const char *str);
+
+/**
+ * Detect charset of file
+ *
+ * @param aFile The target of nsIFile
+ * @param[out] aCharset The charset string
+ */
+NS_MSG_BASE nsresult MsgDetectCharsetFromFile(nsIFile *aFile, nsACString &aCharset);
+
+/*
+ * Converts a buffer to plain text. Some conversions may
+ * or may not work with certain end charsets which is why we
+ * need that as an argument to the function. If charset is
+ * unknown or deemed of no importance NULL could be passed.
+ * @param[in/out] aConBuf Variable with the text to convert
+ * @param formatFlowed Use format flowed?
+ * @param delsp Use delsp=yes when flowed
+ * @param formatOutput Reformat the output?
+ & @param disallowBreaks Disallow breaks when formatting
+ */
+NS_MSG_BASE nsresult
+ConvertBufToPlainText(nsString &aConBuf, bool formatFlowed, bool delsp,
+ bool formatOutput, bool disallowBreaks);
+
+/**
+ * The following definitons exist for compatibility between the internal and
+ * external APIs. Where possible they just forward to the existing API.
+ */
+
+#ifdef MOZILLA_INTERNAL_API
+#include "nsEscape.h"
+
+/**
+ * The internal API expects nsCaseInsensitiveC?StringComparator() and true.
+ * Redefine CaseInsensitiveCompare so that Find works.
+ */
+#define CaseInsensitiveCompare true
+/**
+ * The following methods are not exposed to the external API, but when we're
+ * using the internal API we can simply redirect the calls appropriately.
+ */
+#define MsgLowerCaseEqualsLiteral(str, l) \
+ (str).LowerCaseEqualsLiteral(l)
+#define MsgRFindChar(str, ch, len) \
+ (str).RFindChar(ch, len)
+#define MsgCompressWhitespace(str) \
+ (str).CompressWhitespace()
+#define MsgEscapeHTML(str) \
+ nsEscapeHTML(str)
+#define MsgEscapeHTML2(buffer, len) \
+ nsEscapeHTML2(buffer, len)
+#define MsgReplaceSubstring(str, what, replacement) \
+ (str).ReplaceSubstring(what, replacement)
+#define MsgIsUTF8(str) \
+ IsUTF8(str)
+#define MsgNewInterfaceRequestorAggregation(aFirst, aSecond, aResult) \
+ NS_NewInterfaceRequestorAggregation(aFirst, aSecond, aResult)
+#define MsgNewNotificationCallbacksAggregation(aCallbacks, aLoadGroup, aResult) \
+ NS_NewNotificationCallbacksAggregation(aCallbacks, aLoadGroup, aResult)
+#define MsgGetAtom(aString) \
+ NS_Atomize(aString)
+#define MsgNewAtom(aString) \
+ NS_Atomize(aString)
+#define MsgReplaceChar(aString, aNeedle, aReplacement) \
+ (aString).ReplaceChar(aNeedle, aReplacement)
+#define MsgFind(str, what, ignore_case, offset) \
+ (str).Find(what, ignore_case, offset)
+#define MsgCountChar(aString, aChar) \
+ (aString).CountChar(aChar)
+
+#else
+
+/**
+ * The external API expects CaseInsensitiveCompare. Redefine
+ * nsCaseInsensitiveC?StringComparator() so that Equals works.
+ */
+#define nsCaseInsensitiveCStringComparator() \
+ CaseInsensitiveCompare
+#define nsCaseInsensitiveStringComparator() \
+ CaseInsensitiveCompare
+/// The external API does not provide kNotFound.
+#define kNotFound -1
+/**
+ * The external API does not provide the following methods. While we can
+ * reasonably easily define them in terms of existing methods, we only want
+ * to do this when using the external API.
+ */
+#define AppendASCII \
+ AppendLiteral
+#define AppendUTF16toUTF8(source, dest) \
+ (dest).Append(NS_ConvertUTF16toUTF8(source))
+#define AppendUTF8toUTF16(source, dest) \
+ (dest).Append(NS_ConvertUTF8toUTF16(source))
+#define AppendASCIItoUTF16(source, dest) \
+ (dest).Append(NS_ConvertASCIItoUTF16(source))
+#define Compare(str1, str2, comp) \
+ (str1).Compare(str2, comp)
+#define CaseInsensitiveFindInReadable(what, str) \
+ ((str).Find(what, CaseInsensitiveCompare) != kNotFound)
+#define LossyAppendUTF16toASCII(source, dest) \
+ (dest).Append(NS_LossyConvertUTF16toASCII(source))
+#define Last() \
+ EndReading()[-1]
+#define SetCharAt(ch, index) \
+ Replace(index, 1, ch)
+#define NS_NewISupportsArray(result) \
+ CallCreateInstance(NS_SUPPORTSARRAY_CONTRACTID, static_cast<nsISupportsArray**>(result))
+/**
+ * The internal and external methods expect the parameters in a different order.
+ * The internal API also always expects a flag rather than a comparator.
+ */
+inline int32_t MsgFind(nsAString &str, const char *what, bool ignore_case, uint32_t offset)
+{
+ return str.Find(what, offset, ignore_case);
+}
+
+inline int32_t MsgFind(nsACString &str, const char *what, bool ignore_case, int32_t offset)
+{
+ /* See Find_ComputeSearchRange from nsStringObsolete.cpp */
+ if (offset < 0) {
+ offset = 0;
+ }
+ if (ignore_case)
+ return str.Find(nsDependentCString(what), offset, CaseInsensitiveCompare);
+ return str.Find(nsDependentCString(what), offset);
+}
+
+inline int32_t MsgFind(nsACString &str, const nsACString &what, bool ignore_case, int32_t offset)
+{
+ /* See Find_ComputeSearchRange from nsStringObsolete.cpp */
+ if (offset < 0) {
+ offset = 0;
+ }
+ if (ignore_case)
+ return str.Find(what, offset, CaseInsensitiveCompare);
+ return str.Find(what, offset);
+}
+
+/**
+ * The following methods are not exposed to the external API so we define
+ * equivalent versions here.
+ */
+/// Equivalent of LowerCaseEqualsLiteral(literal)
+#define MsgLowerCaseEqualsLiteral(str, literal) \
+ (str).Equals(literal, CaseInsensitiveCompare)
+/// Equivalent of RFindChar(ch, len)
+#define MsgRFindChar(str, ch, len) \
+ StringHead(str, len).RFindChar(ch)
+/// Equivalent of aString.CompressWhitespace()
+NS_MSG_BASE void MsgCompressWhitespace(nsCString& aString);
+/// Equivalent of nsEscapeHTML(aString)
+NS_MSG_BASE char *MsgEscapeHTML(const char *aString);
+/// Equivalent of nsEscapeHTML2(aBuffer, aLen)
+NS_MSG_BASE char16_t *MsgEscapeHTML2(const char16_t *aBuffer, int32_t aLen);
+// Existing replacement for IsUTF8
+NS_MSG_BASE bool MsgIsUTF8(const nsACString& aString);
+/// Equivalent of NS_Atomize(aUTF8String)
+NS_MSG_BASE already_AddRefed<nsIAtom> MsgNewAtom(const char* aString);
+/// Equivalent of NS_Atomize(aUTF8String)
+inline already_AddRefed<nsIAtom> MsgGetAtom(const char* aUTF8String)
+{
+ return MsgNewAtom(aUTF8String);
+}
+/// Equivalent of ns(C)String::ReplaceSubstring(what, replacement)
+NS_MSG_BASE void MsgReplaceSubstring(nsAString &str, const nsAString &what, const nsAString &replacement);
+NS_MSG_BASE void MsgReplaceSubstring(nsACString &str, const char *what, const char *replacement);
+/// Equivalent of ns(C)String::ReplaceChar(what, replacement)
+NS_MSG_BASE void MsgReplaceChar(nsString& str, const char *set, const char16_t replacement);
+NS_MSG_BASE void MsgReplaceChar(nsCString& str, const char needle, const char replacement);
+// Equivalent of NS_NewInterfaceRequestorAggregation(aFirst, aSecond, aResult)
+NS_MSG_BASE nsresult MsgNewInterfaceRequestorAggregation(nsIInterfaceRequestor *aFirst,
+ nsIInterfaceRequestor *aSecond,
+ nsIInterfaceRequestor **aResult);
+
+/**
+ * This function is based on NS_NewNotificationCallbacksAggregation from
+ * nsNetUtil.h
+ *
+ * This function returns a nsIInterfaceRequestor instance that returns the
+ * same result as NS_QueryNotificationCallbacks when queried.
+ */
+inline nsresult
+MsgNewNotificationCallbacksAggregation(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsIInterfaceRequestor **result)
+{
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ if (loadGroup)
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ return MsgNewInterfaceRequestorAggregation(callbacks, cbs, result);
+}
+
+/**
+ * Count occurences of specified character in string.
+ *
+ */
+inline
+uint32_t MsgCountChar(nsACString &aString, char16_t aChar) {
+ const char *begin, *end;
+ uint32_t num_chars = 0;
+ aString.BeginReading(&begin, &end);
+ for (const char *current = begin; current < end; ++current) {
+ if (*current == aChar)
+ ++num_chars;
+ }
+ return num_chars;
+}
+
+inline
+uint32_t MsgCountChar(nsAString &aString, char16_t aChar) {
+ const char16_t *begin, *end;
+ uint32_t num_chars = 0;
+ aString.BeginReading(&begin, &end);
+ for (const char16_t *current = begin; current < end; ++current) {
+ if (*current == aChar)
+ ++num_chars;
+ }
+ return num_chars;
+}
+
+#endif
+
+/**
+ * Converts a hex string into an integer.
+ * Processes up to aNumChars characters or the first non-hex char.
+ * It is not an error if less than aNumChars valid hex digits are found.
+ */
+NS_MSG_BASE uint64_t MsgUnhex(const char *aHexString, size_t aNumChars);
+
+/**
+ * Checks if a string is a valid hex literal containing at least aNumChars digits.
+ */
+NS_MSG_BASE bool MsgIsHex(const char *aHexString, size_t aNumChars);
+
+/**
+ * Convert an uint32_t to a nsMsgKey.
+ * Currently they are mostly the same but we need to preserve the notion that
+ * nsMsgKey is an opaque value that can't be treated as a generic integer
+ * (except when storing it into the database). It enables type safety checks and
+ * may prevent coding errors.
+ */
+NS_MSG_BASE nsMsgKey msgKeyFromInt(uint32_t aValue);
+
+NS_MSG_BASE nsMsgKey msgKeyFromInt(uint64_t aValue);
+
+/**
+ * Helper function to extract query part from URL spec.
+ */
+nsAutoCString MsgExtractQueryPart(nsAutoCString spec, const char* queryToExtract);
+
+/**
+ * Helper macro for defining getter/setters. Ported from nsISupportsObsolete.h
+ */
+#define NS_IMPL_GETSET(clazz, attr, type, member) \
+ NS_IMETHODIMP clazz::Get##attr(type *result) \
+ { \
+ NS_ENSURE_ARG_POINTER(result); \
+ *result = member; \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP clazz::Set##attr(type aValue) \
+ { \
+ member = aValue; \
+ return NS_OK; \
+ }
+
+#endif
+
+ /**
+ * Macro and helper function for reporting an error, warning or
+ * informational message to the Error Console
+ *
+ * This will require the inclusion of the following files in the source file
+ * #include "nsIScriptError.h"
+ * #include "nsIConsoleService.h"
+ *
+ */
+
+NS_MSG_BASE
+void MsgLogToConsole4(const nsAString &aErrorText, const nsAString &aFilename,
+ uint32_t aLine, uint32_t flags);
+
+// Macro with filename and line number
+#define MSG_LOG_TO_CONSOLE(_text, _flag) MsgLogToConsole4(NS_LITERAL_STRING(_text), NS_LITERAL_STRING(__FILE__), __LINE__, _flag)
+#define MSG_LOG_ERR_TO_CONSOLE(_text) MSG_LOG_TO_CONSOLE(_text, nsIScriptError::errorFlag)
+#define MSG_LOG_WARN_TO_CONSOLE(_text) MSG_LOG_TO_CONSOLE(_text, nsIScriptError::warningFlag)
+#define MSG_LOG_INFO_TO_CONSOLE(_text) MSG_LOG_TO_CONSOLE(_text, nsIScriptError::infoFlag)
+
+// Helper macros to cope with shoddy I/O error reporting (or lack thereof)
+#define MSG_NS_ERROR(_txt) do { NS_ERROR(_txt); MSG_LOG_ERR_TO_CONSOLE(_txt); } while(0)
+#define MSG_NS_WARNING(_txt) do { NS_WARNING(_txt); MSG_LOG_WARN_TO_CONSOLE(_txt); } while (0)
+#define MSG_NS_WARN_IF_FALSE(_val, _txt) do { if (!(_val)) { NS_WARNING(_txt); MSG_LOG_WARN_TO_CONSOLE(_txt); } } while (0)
+#define MSG_NS_INFO(_txt) do { MSG_LOCAL_INFO_TO_CONSOLE(_txt); \
+ fprintf(stderr,"(info) %s (%s:%d)\n", _txt, __FILE__, __LINE__); } while(0)
diff --git a/mailnews/base/util/nsStopwatch.cpp b/mailnews/base/util/nsStopwatch.cpp
new file mode 100644
index 000000000..137c456e7
--- /dev/null
+++ b/mailnews/base/util/nsStopwatch.cpp
@@ -0,0 +1,183 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStopwatch.h"
+
+#include <stdio.h>
+#include <time.h>
+#if defined(XP_UNIX)
+#include <unistd.h>
+#include <sys/times.h>
+#include <sys/time.h>
+#include <errno.h>
+#elif defined(XP_WIN)
+#include "windows.h"
+#endif // elif defined(XP_WIN)
+
+#include "nsMemory.h"
+/*
+ * This basis for the logic in this file comes from (will used to come from):
+ * (mozilla/)modules/libutil/public/stopwatch.cpp.
+ *
+ * It was no longer used in the mozilla tree, and is being migrated to
+ * comm-central where we actually have a need for it. ("Being" in the sense
+ * that it will not be removed immediately from mozilla-central.)
+ *
+ * Simplification and general clean-up has been performed and the fix for
+ * bug 96669 has been integrated.
+ */
+
+NS_IMPL_ISUPPORTS(nsStopwatch, nsIStopwatch)
+
+#if defined(XP_UNIX)
+/** the number of ticks per second */
+static double gTicks = 0;
+#define MICRO_SECONDS_TO_SECONDS_MULT static_cast<double>(1.0e-6)
+#elif defined(WIN32)
+#ifdef DEBUG
+#ifdef MOZILLA_INTERNAL_API
+#include "nsPrintfCString.h"
+#endif
+#endif
+// 1 tick per 100ns = 10 per us = 10 * 1,000 per ms = 10 * 1,000 * 1,000 per sec.
+#define WIN32_TICK_RESOLUTION static_cast<double>(1.0e-7)
+// subtract off to get to the unix epoch
+#define UNIX_EPOCH_IN_FILE_TIME 116444736000000000L
+#endif // elif defined(WIN32)
+
+nsStopwatch::nsStopwatch()
+ : fTotalRealTimeSecs(0.0)
+ , fTotalCpuTimeSecs(0.0)
+ , fRunning(false)
+{
+#if defined(XP_UNIX)
+ // idempotent in the event of a race under all coherency models
+ if (!gTicks)
+ {
+ // we need to clear errno because sysconf's spec says it leaves it the same
+ // on success and only sets it on failure.
+ errno = 0;
+ gTicks = (clock_t)sysconf(_SC_CLK_TCK);
+ // in event of failure, pick an arbitrary value so we don't divide by zero.
+ if (errno)
+ gTicks = 1000000L;
+ }
+#endif
+}
+
+nsStopwatch::~nsStopwatch()
+{
+}
+
+NS_IMETHODIMP nsStopwatch::Start()
+{
+ fTotalRealTimeSecs = 0.0;
+ fTotalCpuTimeSecs = 0.0;
+ return Resume();
+}
+
+NS_IMETHODIMP nsStopwatch::Stop()
+{
+ fStopRealTimeSecs = GetRealTime();
+ fStopCpuTimeSecs = GetCPUTime();
+ if (fRunning)
+ {
+ fTotalCpuTimeSecs += fStopCpuTimeSecs - fStartCpuTimeSecs;
+ fTotalRealTimeSecs += fStopRealTimeSecs - fStartRealTimeSecs;
+ }
+ fRunning = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsStopwatch::Resume()
+{
+ if (!fRunning)
+ {
+ fStartRealTimeSecs = GetRealTime();
+ fStartCpuTimeSecs = GetCPUTime();
+ }
+ fRunning = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsStopwatch::GetCpuTimeSeconds(double *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = fTotalCpuTimeSecs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsStopwatch::GetRealTimeSeconds(double *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = fTotalRealTimeSecs;
+ return NS_OK;
+}
+
+double nsStopwatch::GetRealTime()
+{
+#if defined(XP_UNIX)
+ struct timeval t;
+ gettimeofday(&t, NULL);
+ return t.tv_sec + t.tv_usec * MICRO_SECONDS_TO_SECONDS_MULT;
+#elif defined(WIN32)
+ union {FILETIME ftFileTime;
+ __int64 ftInt64;
+ } ftRealTime; // time the process has spent in kernel mode
+ SYSTEMTIME st;
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &ftRealTime.ftFileTime);
+ return (ftRealTime.ftInt64 - UNIX_EPOCH_IN_FILE_TIME) * WIN32_TICK_RESOLUTION;
+#else
+#error "nsStopwatch not supported on this platform."
+#endif
+}
+
+double nsStopwatch::GetCPUTime()
+{
+#if defined(XP_UNIX)
+ struct tms cpt;
+ times(&cpt);
+ return (double)(cpt.tms_utime+cpt.tms_stime) / gTicks;
+#elif defined(WIN32)
+ FILETIME ftCreate, // when the process was created
+ ftExit; // when the process exited
+
+ union {FILETIME ftFileTime;
+ __int64 ftInt64;
+ } ftKernel; // time the process has spent in kernel mode
+
+ union {FILETIME ftFileTime;
+ __int64 ftInt64;
+ } ftUser; // time the process has spent in user mode
+
+ HANDLE hProcess = GetCurrentProcess();
+#ifdef DEBUG
+ BOOL ret =
+#endif
+ GetProcessTimes(hProcess, &ftCreate, &ftExit,
+ &ftKernel.ftFileTime, &ftUser.ftFileTime);
+#ifdef DEBUG
+#ifdef MOZILLA_INTERNAL_API
+ if (!ret)
+ NS_ERROR(nsPrintfCString("GetProcessTimes() failed, error=0x%lx.", GetLastError()).get());
+#else
+ if (!ret) {
+ // nsPrintfCString() is unavailable to report GetLastError().
+ NS_ERROR("GetProcessTimes() failed.");
+ }
+#endif
+#endif
+
+ /*
+ * Process times are returned in a 64-bit structure, as the number of
+ * 100 nanosecond ticks since 1 January 1601. User mode and kernel mode
+ * times for this process are in separate 64-bit structures.
+ * Add them and convert the result to seconds.
+ */
+ return (ftKernel.ftInt64 + ftUser.ftInt64) * WIN32_TICK_RESOLUTION;
+#else
+#error "nsStopwatch not supported on this platform."
+#endif
+}
diff --git a/mailnews/base/util/nsStopwatch.h b/mailnews/base/util/nsStopwatch.h
new file mode 100644
index 000000000..108518870
--- /dev/null
+++ b/mailnews/base/util/nsStopwatch.h
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsStopwatch_h_
+#define _nsStopwatch_h_
+
+#include "nsIStopwatch.h"
+
+#include "msgCore.h"
+
+#define NS_STOPWATCH_CID \
+{0x6ef7eafd, 0x72d0, 0x4c56, {0x94, 0x09, 0x67, 0xe1, 0x6d, 0x0f, 0x25, 0x5b}}
+
+#define NS_STOPWATCH_CONTRACTID "@mozilla.org/stopwatch;1"
+
+class NS_MSG_BASE nsStopwatch : public nsIStopwatch
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTOPWATCH
+
+ nsStopwatch();
+private:
+ virtual ~nsStopwatch();
+
+ /// Wall-clock start time in seconds since unix epoch.
+ double fStartRealTimeSecs;
+ /// Wall-clock stop time in seconds since unix epoch.
+ double fStopRealTimeSecs;
+ /// CPU-clock start time in seconds (of CPU time used since app start)
+ double fStartCpuTimeSecs;
+ /// CPU-clock stop time in seconds (of CPU time used since app start)
+ double fStopCpuTimeSecs;
+ /// Total wall-clock time elapsed in seconds.
+ double fTotalRealTimeSecs;
+ /// Total CPU time elapsed in seconds.
+ double fTotalCpuTimeSecs;
+
+ /// Is the timer running?
+ bool fRunning;
+
+ static double GetRealTime();
+ static double GetCPUTime();
+};
+
+#endif // _nsStopwatch_h_
diff --git a/mailnews/base/util/templateUtils.js b/mailnews/base/util/templateUtils.js
new file mode 100644
index 000000000..d90f16abd
--- /dev/null
+++ b/mailnews/base/util/templateUtils.js
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ["PluralStringFormatter", "makeFriendlyDateAgo"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/PluralForm.jsm");
+Cu.import("resource:///modules/StringBundle.js");
+
+function PluralStringFormatter(aBundleURI) {
+ this._bundle = new StringBundle(aBundleURI);
+}
+
+PluralStringFormatter.prototype = {
+ get: function(aStringName, aReplacements, aPluralCount) {
+ let str = this._bundle.get(aStringName);
+ if (aPluralCount !== undefined)
+ str = PluralForm.get(aPluralCount, str);
+ if (aReplacements !== undefined) {
+ for (let i = 0; i < aReplacements.length; i++)
+ str = str.replace("#" + (i+1), aReplacements[i]);
+ }
+ return str;
+ },
+};
+
+
+var gTemplateUtilsStrings = new PluralStringFormatter(
+ "chrome://messenger/locale/templateUtils.properties"
+);
+
+/**
+ * Helper function to generate a localized "friendly" representation of
+ * time relative to the present. If the time input is "today", it returns
+ * a string corresponding to just the time. If it's yesterday, it returns
+ * "yesterday" (localized). If it's in the last week, it returns the day
+ * of the week. If it's before that, it returns the date.
+ *
+ * @param time
+ * the time (better be in the past!)
+ * @return The string with a "human-friendly" representation of that time
+ * relative to now.
+ */
+function makeFriendlyDateAgo(time)
+{
+ let dts = Cc["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Ci.nsIScriptableDateFormat);
+
+ // Figure out when today begins
+ let now = new Date();
+ let today = new Date(now.getFullYear(), now.getMonth(),
+ now.getDate());
+
+ // Get the end time to display
+ let end = time;
+
+ // Figure out if the end time is from today, yesterday,
+ // this week, etc.
+ let dateTime;
+ let kDayInMsecs = 24 * 60 * 60 * 1000;
+ let k6DaysInMsecs = 6 * kDayInMsecs;
+ if (end >= today) {
+ // activity finished after today started, show the time
+ dateTime = dts.FormatTime("", dts.timeFormatNoSeconds,
+ end.getHours(), end.getMinutes(),0);
+ } else if (today - end < kDayInMsecs) {
+ // activity finished after yesterday started, show yesterday
+ dateTime = gTemplateUtilsStrings.get("yesterday");
+ } else if (today - end < k6DaysInMsecs) {
+ // activity finished after last week started, show day of week
+ dateTime = end.toLocaleFormat("%A");
+ } else if (now.getFullYear() == end.getFullYear()) {
+ // activity must have been from some time ago.. show month/day
+ let month = end.toLocaleFormat("%B");
+ // Remove leading 0 by converting the date string to a number
+ let date = Number(end.toLocaleFormat("%d"));
+ dateTime = gTemplateUtilsStrings.get("monthDate", [month, date]);
+ } else {
+ // not this year, so show full date format
+ dateTime = dts.FormatDate("", dts.dateFormatShort,
+ end.getFullYear(), end.getMonth() + 1,
+ end.getDate());
+ }
+ return dateTime;
+}
diff --git a/mailnews/base/util/traceHelper.js b/mailnews/base/util/traceHelper.js
new file mode 100644
index 000000000..a69c7e83d
--- /dev/null
+++ b/mailnews/base/util/traceHelper.js
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['DebugTraceHelper'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var SPACES = " ";
+var BRIGHT_COLORS = {
+ red: "\x1b[1;31m",
+ green: "\x1b[1;32m",
+ yellow: "\x1b[1;33m",
+ blue: "\x1b[1;34m",
+ magenta: "\x1b[1;35m",
+ cyan: "\x1b[1;36m",
+ white: "\x1b[1;37m",
+};
+var DARK_COLORS = {
+ red: "\x1b[0;31m",
+ green: "\x1b[0;32m",
+ yellow: "\x1b[0;33m",
+ blue: "\x1b[0;34m",
+ magenta: "\x1b[0;35m",
+ cyan: "\x1b[0;36m",
+ white: "\x1b[0;37m",
+};
+var STOP_COLORS = "\x1b[0m";
+
+
+/**
+ * Example usages:
+ *
+ * Components.utils.import("resource:///modules/traceHelper.js");
+ * var debugContext = {color: "cyan"};
+ * DebugTraceHelper.tracify(FolderDisplayWidget.prototype,
+ * "FolderDisplayWidget", /.+/, debugContext);
+ * DebugTraceHelper.tracify(MessageDisplayWidget.prototype,
+ * "MessageDisplayWidget", /.+/, debugContext);
+ * DebugTraceHelper.tracify(StandaloneFolderDisplayWidget.prototype,
+ * "StandaloneFolderDisplayWidget", /.+/, debugContext);
+ * DebugTraceHelper.tracify(StandaloneMessageDisplayWidget.prototype,
+ * "StandaloneMessageDisplayWidget", /.+/, debugContext);
+ * DebugTraceHelper.tracify(DBViewWrapper.prototype,
+ * "DBViewWrapper", /.+/, {color: "green"});
+ * DebugTraceHelper.tracify(JSTreeSelection.prototype,
+ * "JSTreeSelection", /.+/, {color: "yellow"});
+ */
+var DebugTraceHelper = {
+ tracify: function(aObj, aDesc, aPat, aContext, aSettings) {
+ aContext.depth = 0;
+ let color = aSettings.color || "cyan";
+ aSettings.introCode = BRIGHT_COLORS[color];
+ aSettings.outroCode = DARK_COLORS[color];
+ for (let key in aObj) {
+ if (aPat.test(key)) {
+ // ignore properties!
+ if (aObj.__lookupGetter__(key) || aObj.__lookupSetter__(key))
+ continue;
+ // ignore non-functions!
+ if (typeof(aObj[key]) != "function")
+ continue;
+ let name = key;
+ let prev = aObj[name];
+ aObj[name] = function() {
+ let argstr = "";
+ for (let i = 0; i < arguments.length; i++) {
+ let arg = arguments[i];
+ if (arg == null)
+ argstr += " null";
+ else if (typeof(arg) == "function")
+ argstr += " function "+ arg.name;
+ else
+ argstr += " " + arguments[i].toString();
+ }
+
+ let indent = SPACES.substr(0, aContext.depth++ * 2);
+ dump(indent + "--> " + aSettings.introCode + aDesc + "::" + name +
+ ":" + argstr +
+ STOP_COLORS + "\n");
+ let ret;
+ try {
+ ret = prev.apply(this, arguments);
+ }
+ catch (ex) {
+ if (ex.stack) {
+ dump(BRIGHT_COLORS.red + "Exception: " + ex + "\n " +
+ ex.stack.replace("\n", "\n ") + STOP_COLORS + "\n");
+ }
+ else {
+ dump(BRIGHT_COLORS.red + "Exception: " + ex.fileName + ":" +
+ ex.lineNumber + ": " + ex + STOP_COLORS + "\n");
+ }
+ aContext.depth--;
+ dump(indent + "<-- " + aSettings.outroCode + aDesc + "::" + name +
+ STOP_COLORS + "\n");
+ throw ex;
+ }
+ aContext.depth--;
+ dump(indent + "<-- " + aSettings.outroCode + aDesc + "::" + name +
+ ": " + (ret != null ? ret.toString() : "null") +
+ STOP_COLORS + "\n");
+ return ret;
+ };
+ }
+ }
+ }
+};
diff --git a/mailnews/build/moz.build b/mailnews/build/moz.build
new file mode 100644
index 000000000..375a6ca8f
--- /dev/null
+++ b/mailnews/build/moz.build
@@ -0,0 +1,63 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsMailModule.cpp',
+]
+
+USE_LIBS += [
+ 'nspr',
+]
+
+if CONFIG['MOZ_INCOMPLETE_EXTERNAL_LINKAGE']:
+ XPCOMBinaryComponent('mail')
+ USE_LIBS += [
+ 'rdfutil_external_s',
+ 'unicharutil_external_s',
+ 'xpcomglue_s',
+ 'xul',
+ ]
+else:
+ Library('mail')
+ FINAL_LIBRARY = 'xul'
+
+# js needs to come after xul for now, because it is an archive and its content
+# is discarded when it comes first.
+USE_LIBS += [
+ 'js',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ OS_LIBS += [
+ 'shell32',
+ ]
+else:
+ OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ OS_LIBS += CONFIG['TK_LIBS']
+ OS_LIBS += ['-framework Cocoa']
+
+LOCAL_INCLUDES += [
+ '/mailnews/addrbook/src',
+ '/mailnews/base/search/src',
+ '/mailnews/base/src',
+ '/mailnews/base/util',
+ '/mailnews/compose/src',
+ '/mailnews/extensions/bayesian-spam-filter/src',
+ '/mailnews/extensions/fts3/src',
+ '/mailnews/extensions/mailviews/src',
+ '/mailnews/extensions/mdn/src',
+ '/mailnews/extensions/smime/src',
+ '/mailnews/imap/src',
+ '/mailnews/intl',
+ '/mailnews/local/src',
+ '/mailnews/mime/emitters',
+ '/mailnews/mime/src',
+ '/mailnews/news/src',
+]
+
+if CONFIG['MOZ_LDAP_XPCOM']:
+ DEFINES['MOZ_LDAP_XPCOM'] = True
diff --git a/mailnews/build/newmail.ico b/mailnews/build/newmail.ico
new file mode 100644
index 000000000..301c0853e
--- /dev/null
+++ b/mailnews/build/newmail.ico
Binary files differ
diff --git a/mailnews/build/nsMailModule.cpp b/mailnews/build/nsMailModule.cpp
new file mode 100644
index 000000000..5b86b4c3e
--- /dev/null
+++ b/mailnews/build/nsMailModule.cpp
@@ -0,0 +1,1392 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* ****************************************************************************
+ * ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
+ * ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
+ *
+ * ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
+ * ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
+ *
+ * Dear Mortals,
+ *
+ * Please be advised that if you are adding something here, you should also
+ * strongly consider adding it to the other place it goes too! These can be
+ * found in paths like so: mailnews/.../build/WhateverFactory.cpp
+ *
+ * If you do not, your (static) release builds will be quite pleasant, but
+ * (dynamic) debug builds will disappoint you by not having your component in
+ * them.
+ *
+ * Yours truly,
+ * The ghost that haunts the MailNews codebase.
+ *
+ * ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
+ * ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
+ *
+ * ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
+ * ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
+ * ****************************************************************************/
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Core Module Include Files
+////////////////////////////////////////////////////////////////////////////////
+
+#include "mozilla/ModuleUtils.h"
+#include "nsIFactory.h"
+#include "nsISupports.h"
+#include "nsIModule.h"
+#include "nsICategoryManager.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "msgCore.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// mailnews base includes
+////////////////////////////////////////////////////////////////////////////////
+#include "nsMsgBaseCID.h"
+#include "rdf.h"
+#include "nsMessengerBootstrap.h"
+#include "nsMessenger.h"
+#include "nsIContentViewer.h"
+#include "nsMsgMailSession.h"
+#include "nsMsgAccount.h"
+#include "nsMsgAccountManager.h"
+#include "nsMsgIdentity.h"
+#include "nsMsgIncomingServer.h"
+#include "nsMsgFolderDataSource.h"
+#include "nsMsgAccountManagerDS.h"
+#include "nsMsgBiffManager.h"
+#include "nsMsgPurgeService.h"
+#include "nsStatusBarBiffManager.h"
+#include "nsMsgKeyArray.h"
+#include "nsCopyMessageStreamListener.h"
+#include "nsMsgCopyService.h"
+#include "nsMsgFolderCache.h"
+#include "nsMsgStatusFeedback.h"
+#include "nsMsgFilterService.h"
+#include "nsMsgWindow.h"
+#include "nsMsgServiceProvider.h"
+#include "nsSubscribeDataSource.h"
+#include "nsSubscribableServer.h"
+#ifdef NS_PRINTING
+#include "nsMsgPrintEngine.h"
+#endif
+#include "nsMsgSearchSession.h"
+#include "nsMsgSearchTerm.h"
+#include "nsMsgSearchAdapter.h"
+#include "nsMsgFolderCompactor.h"
+#include "nsMsgThreadedDBView.h"
+#include "nsMsgSpecialViews.h"
+#include "nsMsgXFVirtualFolderDBView.h"
+#include "nsMsgQuickSearchDBView.h"
+#include "nsMsgGroupView.h"
+#include "nsMsgOfflineManager.h"
+#include "nsMsgProgress.h"
+#include "nsSpamSettings.h"
+#include "nsMsgContentPolicy.h"
+#include "nsCidProtocolHandler.h"
+#include "nsRssIncomingServer.h"
+#include "nsRssService.h"
+#include "nsMsgBrkMBoxStore.h"
+#include "nsMsgMaildirStore.h"
+#include "nsMsgTagService.h"
+#include "nsMsgFolderNotificationService.h"
+#include "nsMailDirProvider.h"
+
+#ifdef XP_WIN
+#include "nsMessengerWinIntegration.h"
+#endif
+#ifdef XP_MACOSX
+#include "nsMessengerOSXIntegration.h"
+#endif
+#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
+#include "nsMessengerUnixIntegration.h"
+#endif
+#include "nsCURILoader.h"
+#include "nsMessengerContentHandler.h"
+#include "nsStopwatch.h"
+#include "MailNewsDLF.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// addrbook includes
+////////////////////////////////////////////////////////////////////////////////
+#include "nsAbBaseCID.h"
+#include "nsAbBSDirectory.h"
+#include "nsAbMDBDirectory.h"
+#include "nsAbMDBCard.h"
+#include "nsAbDirFactoryService.h"
+#include "nsAbMDBDirFactory.h"
+#include "nsAddrDatabase.h"
+#include "nsAbManager.h"
+#include "nsAbContentHandler.h"
+#include "nsAbDirProperty.h"
+#include "nsAbAddressCollector.h"
+#include "nsAddbookProtocolHandler.h"
+#include "nsAddbookUrl.h"
+
+#include "nsAbDirectoryQuery.h"
+#include "nsAbBooleanExpression.h"
+#include "nsAbDirectoryQueryProxy.h"
+#include "nsAbView.h"
+#include "nsMsgVCardService.h"
+#include "nsAbLDIFService.h"
+
+#if defined(MOZ_LDAP_XPCOM)
+#include "nsAbLDAPDirectory.h"
+#include "nsAbLDAPDirectoryQuery.h"
+#include "nsAbLDAPCard.h"
+#include "nsAbLDAPDirFactory.h"
+#include "nsAbLDAPReplicationService.h"
+#include "nsAbLDAPReplicationQuery.h"
+#include "nsAbLDAPReplicationData.h"
+// XXX These files are not being built as they don't work. Bug 311632 should
+// fix them.
+//#include "nsAbLDAPChangeLogQuery.h"
+//#include "nsAbLDAPChangeLogData.h"
+#endif
+
+
+#if defined(MOZ_MAPI_SUPPORT)
+#include "nsAbOutlookDirFactory.h"
+#include "nsAbOutlookDirectory.h"
+#endif
+
+#ifdef XP_MACOSX
+#include "nsAbOSXDirectory.h"
+#include "nsAbOSXCard.h"
+#include "nsAbOSXDirFactory.h"
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// bayesian spam filter includes
+////////////////////////////////////////////////////////////////////////////////
+#include "nsBayesianFilterCID.h"
+#include "nsBayesianFilter.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// compose includes
+////////////////////////////////////////////////////////////////////////////////
+#include "nsMsgCompCID.h"
+
+#include "nsMsgSendLater.h"
+#include "nsSmtpUrl.h"
+#include "nsISmtpService.h"
+#include "nsSmtpService.h"
+#include "nsMsgComposeService.h"
+#include "nsMsgComposeContentHandler.h"
+#include "nsMsgCompose.h"
+#include "nsMsgComposeParams.h"
+#include "nsMsgComposeProgressParams.h"
+#include "nsMsgAttachment.h"
+#include "nsMsgSend.h"
+#include "nsMsgQuote.h"
+#include "nsURLFetcher.h"
+#include "nsSmtpServer.h"
+#include "nsMsgCompUtils.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// jsAccount includes
+////////////////////////////////////////////////////////////////////////////////
+#include "msgJsAccountCID.h"
+#include "JaAbDirectory.h"
+#include "JaCompose.h"
+#include "JaIncomingServer.h"
+#include "JaMsgFolder.h"
+#include "JaSend.h"
+#include "JaUrl.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// imap includes
+////////////////////////////////////////////////////////////////////////////////
+#include "nsMsgImapCID.h"
+#include "nsIMAPHostSessionList.h"
+#include "nsImapIncomingServer.h"
+#include "nsImapService.h"
+#include "nsImapMailFolder.h"
+#include "nsImapUrl.h"
+#include "nsImapProtocol.h"
+#include "nsAutoSyncManager.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// local includes
+////////////////////////////////////////////////////////////////////////////////
+#include "nsMsgLocalCID.h"
+
+#include "nsMailboxUrl.h"
+#include "nsPop3URL.h"
+#include "nsMailboxService.h"
+#include "nsLocalMailFolder.h"
+#include "nsParseMailbox.h"
+#include "nsPop3Service.h"
+
+#ifdef HAVE_MOVEMAIL
+#include "nsMovemailService.h"
+#include "nsMovemailIncomingServer.h"
+#endif /* HAVE_MOVEMAIL */
+
+#include "nsNoneService.h"
+#include "nsPop3IncomingServer.h"
+#include "nsNoIncomingServer.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// msgdb includes
+///////////////////////////////////////////////////////////////////////////////
+#include "nsMsgDBCID.h"
+#include "nsMailDatabase.h"
+#include "nsNewsDatabase.h"
+#include "nsImapMailDatabase.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// mime includes
+///////////////////////////////////////////////////////////////////////////////
+#include "nsMsgMimeCID.h"
+#include "nsStreamConverter.h"
+#include "nsMimeObjectClassAccess.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// mime emitter includes
+///////////////////////////////////////////////////////////////////////////////
+#include "nsMimeEmitterCID.h"
+#include "nsIMimeEmitter.h"
+#include "nsMimeHtmlEmitter.h"
+#include "nsMimeRawEmitter.h"
+#include "nsMimeXmlEmitter.h"
+#include "nsMimePlainEmitter.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// news includes
+///////////////////////////////////////////////////////////////////////////////
+#include "nsMsgNewsCID.h"
+#include "nsNntpUrl.h"
+#include "nsNntpService.h"
+#include "nsNntpIncomingServer.h"
+#include "nsNNTPNewsgroupPost.h"
+#include "nsNNTPNewsgroupList.h"
+#include "nsNNTPArticleList.h"
+#include "nsNewsDownloadDialogArgs.h"
+#include "nsNewsFolder.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// mail views includes
+///////////////////////////////////////////////////////////////////////////////
+#include "nsMsgMailViewsCID.h"
+#include "nsMsgMailViewList.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// mdn includes
+///////////////////////////////////////////////////////////////////////////////
+#include "nsMsgMdnCID.h"
+#include "nsMsgMdnGenerator.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// smime includes
+///////////////////////////////////////////////////////////////////////////////
+#include "nsCMS.h"
+#include "nsCMSSecureMessage.h"
+#include "nsCertPicker.h"
+#include "nsMsgSMIMECID.h"
+#include "nsMsgComposeSecure.h"
+#include "nsSMimeJSHelper.h"
+#include "nsEncryptedSMIMEURIsService.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// vcard includes
+///////////////////////////////////////////////////////////////////////////////
+#include "nsMimeContentTypeHandler.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// FTS3 Tokenizer
+///////////////////////////////////////////////////////////////////////////////
+#include "nsFts3TokenizerCID.h"
+#include "nsFts3Tokenizer.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// PGP/MIME includes
+////////////////////////////////////////////////////////////////////////////////
+#include "nsPgpMimeProxy.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// i18n includes
+////////////////////////////////////////////////////////////////////////////////
+#include "nsEncoderDecoderUtils.h"
+#include "nsCommUConvCID.h"
+
+#include "nsCharsetConverterManager.h"
+
+#include "nsUTF7ToUnicode.h"
+#include "nsMUTF7ToUnicode.h"
+#include "nsUnicodeToUTF7.h"
+#include "nsUnicodeToMUTF7.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// mailnews base factories
+////////////////////////////////////////////////////////////////////////////////
+using namespace mozilla::mailnews;
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMessengerBootstrap)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgMailSession, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMessenger)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgAccountManager, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgAccount)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgIdentity)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgFolderDataSource, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgUnreadFoldersDataSource, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgFavoriteFoldersDataSource, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgRecentFoldersDataSource, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgAccountManagerDataSource, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgSearchSession)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgSearchTerm)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgSearchValidityManager)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgFilterService)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgBiffManager, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgPurgeService)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsStatusBarBiffManager, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCopyMessageStreamListener)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgCopyService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgFolderCache)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgStatusFeedback)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgKeyArray)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgWindow,Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgServiceProviderService, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSubscribeDataSource, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSubscribableServer, Init)
+#ifdef NS_PRINTING
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgPrintEngine)
+#endif
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFolderCompactState)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsOfflineStoreCompactState)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgThreadedDBView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgThreadsWithUnreadDBView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgWatchedThreadsWithUnreadDBView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgSearchDBView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgXFVirtualFolderDBView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgQuickSearchDBView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgGroupView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgOfflineManager)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgProgress)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSpamSettings)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgTagService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgFolderNotificationService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCidProtocolHandler)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMailDirProvider)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgShutdownService)
+#ifdef XP_WIN
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMessengerWinIntegration, Init)
+#endif
+#ifdef XP_MACOSX
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMessengerOSXIntegration, Init)
+#endif
+#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMessengerUnixIntegration, Init)
+#endif
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMessengerContentHandler)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgContentPolicy, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsStopwatch)
+NS_GENERIC_FACTORY_CONSTRUCTOR(MailNewsDLF)
+
+NS_DEFINE_NAMED_CID(NS_MESSENGERBOOTSTRAP_CID);
+NS_DEFINE_NAMED_CID(NS_MESSENGERWINDOWSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGMAILSESSION_CID);
+NS_DEFINE_NAMED_CID(NS_MESSENGER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGACCOUNTMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGACCOUNT_CID);
+NS_DEFINE_NAMED_CID(NS_MSGIDENTITY_CID);
+NS_DEFINE_NAMED_CID(NS_MAILNEWSFOLDERDATASOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_MAILNEWSUNREADFOLDERDATASOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_MAILNEWSFAVORITEFOLDERDATASOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_MAILNEWSRECENTFOLDERDATASOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGACCOUNTMANAGERDATASOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGFILTERSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGSEARCHSESSION_CID);
+NS_DEFINE_NAMED_CID(NS_MSGSEARCHTERM_CID);
+NS_DEFINE_NAMED_CID(NS_MSGSEARCHVALIDITYMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGBIFFMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGPURGESERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_STATUSBARBIFFMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_COPYMESSAGESTREAMLISTENER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGCOPYSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGFOLDERCACHE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGSTATUSFEEDBACK_CID);
+NS_DEFINE_NAMED_CID(NS_MSGWINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_MSGKEYARRAY_CID);
+#ifdef NS_PRINTING
+NS_DEFINE_NAMED_CID(NS_MSG_PRINTENGINE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_MSGSERVICEPROVIDERSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SUBSCRIBEDATASOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_SUBSCRIBABLESERVER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGLOCALFOLDERCOMPACTOR_CID);
+NS_DEFINE_NAMED_CID(NS_MSG_OFFLINESTORECOMPACTOR_CID);
+NS_DEFINE_NAMED_CID(NS_MSGTHREADEDDBVIEW_CID);
+NS_DEFINE_NAMED_CID(NS_MSGTHREADSWITHUNREADDBVIEW_CID);
+NS_DEFINE_NAMED_CID(NS_MSGWATCHEDTHREADSWITHUNREADDBVIEW_CID);
+NS_DEFINE_NAMED_CID(NS_MSGSEARCHDBVIEW_CID);
+NS_DEFINE_NAMED_CID(NS_MSGQUICKSEARCHDBVIEW_CID);
+NS_DEFINE_NAMED_CID(NS_MSG_XFVFDBVIEW_CID);
+NS_DEFINE_NAMED_CID(NS_MSG_GROUPDBVIEW_CID);
+NS_DEFINE_NAMED_CID(NS_MSGOFFLINEMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGPROGRESS_CID);
+NS_DEFINE_NAMED_CID(NS_SPAMSETTINGS_CID);
+NS_DEFINE_NAMED_CID(NS_CIDPROTOCOL_CID);
+NS_DEFINE_NAMED_CID(NS_MSGTAGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGNOTIFICATIONSERVICE_CID);
+#ifdef XP_WIN
+NS_DEFINE_NAMED_CID(NS_MESSENGERWININTEGRATION_CID);
+#endif
+#ifdef XP_MACOSX
+NS_DEFINE_NAMED_CID(NS_MESSENGEROSXINTEGRATION_CID);
+#endif
+#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
+NS_DEFINE_NAMED_CID(NS_MESSENGERUNIXINTEGRATION_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_MESSENGERCONTENTHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGCONTENTPOLICY_CID);
+NS_DEFINE_NAMED_CID(NS_MSGSHUTDOWNSERVICE_CID);
+NS_DEFINE_NAMED_CID(MAILDIRPROVIDER_CID);
+NS_DEFINE_NAMED_CID(NS_STOPWATCH_CID);
+NS_DEFINE_NAMED_CID(NS_MAILNEWSDLF_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// addrbook factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAbManager,Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbContentHandler)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbDirProperty)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbCardProperty)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbBSDirectory)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbMDBDirectory)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbMDBCard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAddrDatabase)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAbAddressCollector,Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAddbookUrl)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbDirFactoryService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbMDBDirFactory)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAddbookProtocolHandler)
+
+#if defined(MOZ_MAPI_SUPPORT)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbOutlookDirectory)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbOutlookDirFactory)
+#endif
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbDirectoryQueryArguments)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbBooleanConditionString)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbBooleanExpression)
+
+
+#if defined(MOZ_LDAP_XPCOM)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPDirectory)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPDirectoryQuery)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPCard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPDirFactory)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPReplicationService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPReplicationQuery)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPProcessReplicationData)
+// XXX These files are not being built as they don't work. Bug 311632 should
+// fix them.
+//NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPChangeLogQuery)
+//NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPProcessChangeLogData)
+#endif
+
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbDirectoryQueryProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgVCardService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDIFService)
+
+#ifdef XP_MACOSX
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbOSXDirectory)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbOSXCard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbOSXDirFactory)
+#endif
+
+NS_DEFINE_NAMED_CID(NS_ABMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_ABDIRECTORY_CID);
+NS_DEFINE_NAMED_CID(NS_ABMDBDIRECTORY_CID);
+NS_DEFINE_NAMED_CID(NS_ABMDBCARD_CID);
+NS_DEFINE_NAMED_CID(NS_ADDRDATABASE_CID);
+NS_DEFINE_NAMED_CID(NS_ABCARDPROPERTY_CID);
+NS_DEFINE_NAMED_CID(NS_ABDIRPROPERTY_CID);
+NS_DEFINE_NAMED_CID(NS_ABADDRESSCOLLECTOR_CID);
+NS_DEFINE_NAMED_CID(NS_ADDBOOKURL_CID);
+NS_DEFINE_NAMED_CID(NS_ADDBOOK_HANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_ABCONTENTHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_ABDIRFACTORYSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_ABMDBDIRFACTORY_CID);
+NS_DEFINE_NAMED_CID(NS_ABDIRECTORYQUERYARGUMENTS_CID);
+NS_DEFINE_NAMED_CID(NS_BOOLEANCONDITIONSTRING_CID);
+NS_DEFINE_NAMED_CID(NS_BOOLEANEXPRESSION_CID);
+#if defined(MOZ_MAPI_SUPPORT)
+NS_DEFINE_NAMED_CID(NS_ABOUTLOOKDIRECTORY_CID);
+NS_DEFINE_NAMED_CID(NS_ABOUTLOOKDIRFACTORY_CID);
+#endif
+
+#if defined(MOZ_LDAP_XPCOM)
+NS_DEFINE_NAMED_CID(NS_ABLDAPDIRECTORY_CID);
+NS_DEFINE_NAMED_CID(NS_ABLDAPDIRECTORYQUERY_CID);
+NS_DEFINE_NAMED_CID(NS_ABLDAPCARD_CID);
+NS_DEFINE_NAMED_CID(NS_ABLDAPDIRFACTORY_CID);
+NS_DEFINE_NAMED_CID(NS_ABLDAP_REPLICATIONSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_ABLDAP_REPLICATIONQUERY_CID);
+NS_DEFINE_NAMED_CID(NS_ABLDAP_PROCESSREPLICATIONDATA_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_ABDIRECTORYQUERYPROXY_CID);
+#ifdef XP_MACOSX
+NS_DEFINE_NAMED_CID(NS_ABOSXDIRECTORY_CID);
+NS_DEFINE_NAMED_CID(NS_ABOSXCARD_CID);
+NS_DEFINE_NAMED_CID(NS_ABOSXDIRFACTORY_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_ABVIEW_CID);
+NS_DEFINE_NAMED_CID(NS_MSGVCARDSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_ABLDIFSERVICE_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// bayesian spam filter factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsBayesianFilter, Init)
+
+NS_DEFINE_NAMED_CID(NS_BAYESIANFILTER_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// compose factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSmtpService)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSmtpServer, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgCompose)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgComposeParams)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgComposeSendListener)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgComposeProgressParams)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgCompFields)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgAttachment)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgAttachmentData)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgAttachedFile)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgComposeAndSend)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgSendLater, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMsgComposeService, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgComposeContentHandler)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgQuote)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgQuoteListener)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSmtpUrl)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMailtoUrl)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsURLFetcher)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgCompUtils)
+
+NS_DEFINE_NAMED_CID(NS_MSGCOMPOSE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGCOMPOSESERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGCOMPOSECONTENTHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGCOMPOSEPARAMS_CID);
+NS_DEFINE_NAMED_CID(NS_MSGCOMPOSESENDLISTENER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGCOMPOSEPROGRESSPARAMS_CID);
+NS_DEFINE_NAMED_CID(NS_MSGCOMPFIELDS_CID);
+NS_DEFINE_NAMED_CID(NS_MSGATTACHMENT_CID);
+NS_DEFINE_NAMED_CID(NS_MSGATTACHMENTDATA_CID);
+NS_DEFINE_NAMED_CID(NS_MSGATTACHEDFILE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGSEND_CID);
+NS_DEFINE_NAMED_CID(NS_MSGSENDLATER_CID);
+NS_DEFINE_NAMED_CID(NS_SMTPSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SMTPSERVER_CID);
+NS_DEFINE_NAMED_CID(NS_SMTPURL_CID);
+NS_DEFINE_NAMED_CID(NS_MAILTOURL_CID);
+NS_DEFINE_NAMED_CID(NS_MSGQUOTE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGQUOTELISTENER_CID);
+NS_DEFINE_NAMED_CID(NS_URLFETCHER_CID);
+NS_DEFINE_NAMED_CID(NS_MSGCOMPUTILS_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// jsAccount factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(JaCppAbDirectoryDelegator)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JaCppComposeDelegator)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(JaCppIncomingServerDelegator, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JaCppMsgFolderDelegator)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JaCppSendDelegator)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JaCppUrlDelegator)
+
+NS_DEFINE_NAMED_CID(JACPPABDIRECTORYDELEGATOR_CID);
+NS_DEFINE_NAMED_CID(JACPPCOMPOSEDELEGATOR_CID);
+NS_DEFINE_NAMED_CID(JACPPINCOMINGSERVERDELEGATOR_CID);
+NS_DEFINE_NAMED_CID(JACPPMSGFOLDERDELEGATOR_CID);
+NS_DEFINE_NAMED_CID(JACPPSENDDELEGATOR_CID);
+NS_DEFINE_NAMED_CID(JACPPURLDELEGATOR_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// imap factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapUrl)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapProtocol)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsIMAPHostSessionList, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsImapIncomingServer, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapMailFolder)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapMockChannel)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAutoSyncManager)
+
+NS_DEFINE_NAMED_CID(NS_IMAPURL_CID);
+NS_DEFINE_NAMED_CID(NS_IMAPPROTOCOL_CID);
+NS_DEFINE_NAMED_CID(NS_IMAPMOCKCHANNEL_CID);
+NS_DEFINE_NAMED_CID(NS_IIMAPHOSTSESSIONLIST_CID);
+NS_DEFINE_NAMED_CID(NS_IMAPINCOMINGSERVER_CID);
+NS_DEFINE_NAMED_CID(NS_IMAPRESOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_IMAPSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_AUTOSYNCMANAGER_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// local factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMailboxUrl)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsPop3URL)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgMailboxParser)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMailboxService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsPop3Service)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNoneService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgLocalMailFolder)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsParseMailMessageState)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPop3IncomingServer, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsRssIncomingServer, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsRssService)
+#ifdef HAVE_MOVEMAIL
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMovemailIncomingServer, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMovemailService)
+#endif /* HAVE_MOVEMAIL */
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNoIncomingServer, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgBrkMBoxStore)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgMaildirStore)
+
+NS_DEFINE_NAMED_CID(NS_MAILBOXURL_CID);
+NS_DEFINE_NAMED_CID(NS_MAILBOXSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_MAILBOXPARSER_CID);
+NS_DEFINE_NAMED_CID(NS_POP3URL_CID);
+NS_DEFINE_NAMED_CID(NS_POP3SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_NONESERVICE_CID);
+#ifdef HAVE_MOVEMAIL
+NS_DEFINE_NAMED_CID(NS_MOVEMAILSERVICE_CID);
+#endif /* HAVE_MOVEMAIL */
+NS_DEFINE_NAMED_CID(NS_LOCALMAILFOLDERRESOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_POP3INCOMINGSERVER_CID);
+#ifdef HAVE_MOVEMAIL
+NS_DEFINE_NAMED_CID(NS_MOVEMAILINCOMINGSERVER_CID);
+#endif /* HAVE_MOVEMAIL */
+NS_DEFINE_NAMED_CID(NS_NOINCOMINGSERVER_CID);
+NS_DEFINE_NAMED_CID(NS_PARSEMAILMSGSTATE_CID);
+NS_DEFINE_NAMED_CID(NS_RSSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_RSSINCOMINGSERVER_CID);
+NS_DEFINE_NAMED_CID(NS_BRKMBOXSTORE_CID);
+NS_DEFINE_NAMED_CID(NS_MAILDIRSTORE_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// msgdb factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgDBService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMailDatabase)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNewsDatabase)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapMailDatabase)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgRetentionSettings)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgDownloadSettings)
+
+NS_DEFINE_NAMED_CID(NS_MAILDB_CID);
+NS_DEFINE_NAMED_CID(NS_NEWSDB_CID);
+NS_DEFINE_NAMED_CID(NS_IMAPDB_CID);
+NS_DEFINE_NAMED_CID(NS_MSG_RETENTIONSETTINGS_CID);
+NS_DEFINE_NAMED_CID(NS_MSG_DOWNLOADSETTINGS_CID);
+NS_DEFINE_NAMED_CID(NS_MSGDB_SERVICE_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// mime factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMimeObjectClassAccess)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsStreamConverter)
+
+NS_DEFINE_NAMED_CID(NS_MIME_OBJECT_CLASS_ACCESS_CID);
+NS_DEFINE_NAMED_CID(NS_MAILNEWS_MIME_STREAM_CONVERTER_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// mime emitter factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMimeRawEmitter)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMimeXmlEmitter)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMimePlainEmitter)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMimeHtmlDisplayEmitter, Init)
+
+NS_DEFINE_NAMED_CID(NS_HTML_MIME_EMITTER_CID);
+NS_DEFINE_NAMED_CID(NS_XML_MIME_EMITTER_CID);
+NS_DEFINE_NAMED_CID(NS_PLAIN_MIME_EMITTER_CID);
+NS_DEFINE_NAMED_CID(NS_RAW_MIME_EMITTER_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFts3Tokenizer)
+
+NS_DEFINE_NAMED_CID(NS_FTS3TOKENIZER_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// news factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNntpUrl)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNntpService)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNntpIncomingServer, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNNTPArticleList)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNNTPNewsgroupPost)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNNTPNewsgroupList)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgNewsFolder)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNewsDownloadDialogArgs)
+
+NS_DEFINE_NAMED_CID(NS_NNTPSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_NNTPURL_CID);
+NS_DEFINE_NAMED_CID(NS_NEWSFOLDERRESOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_NNTPINCOMINGSERVER_CID);
+NS_DEFINE_NAMED_CID(NS_NNTPNEWSGROUPPOST_CID);
+NS_DEFINE_NAMED_CID(NS_NNTPNEWSGROUPLIST_CID);
+NS_DEFINE_NAMED_CID(NS_NNTPARTICLELIST_CID);
+NS_DEFINE_NAMED_CID(NS_NEWSDOWNLOADDIALOGARGS_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// mail view factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgMailViewList)
+
+NS_DEFINE_NAMED_CID(NS_MSGMAILVIEWLIST_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// mdn factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgMdnGenerator)
+
+NS_DEFINE_NAMED_CID(NS_MSGMDNGENERATOR_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// smime factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgComposeSecure)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgSMIMEComposeFields)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSMimeJSHelper)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsEncryptedSMIMEURIsService)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsCMSDecoder, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsCMSEncoder, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsCMSMessage, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsCMSSecureMessage, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsCertPicker, Init)
+
+NS_DEFINE_NAMED_CID(NS_MSGCOMPOSESECURE_CID);
+NS_DEFINE_NAMED_CID(NS_MSGSMIMECOMPFIELDS_CID);
+NS_DEFINE_NAMED_CID(NS_SMIMEJSJELPER_CID);
+NS_DEFINE_NAMED_CID(NS_SMIMEENCRYPTURISERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_CMSDECODER_CID);
+NS_DEFINE_NAMED_CID(NS_CMSENCODER_CID);
+NS_DEFINE_NAMED_CID(NS_CMSMESSAGE_CID);
+NS_DEFINE_NAMED_CID(NS_CMSSECUREMESSAGE_CID);
+NS_DEFINE_NAMED_CID(NS_CERT_PICKER_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// vcard factories
+////////////////////////////////////////////////////////////////////////////////
+
+NS_DEFINE_NAMED_CID(NS_VCARD_CONTENT_TYPE_HANDLER_CID);
+
+// XXX this vcard stuff needs cleaned up to use a generic factory constructor
+extern "C" MimeObjectClass *
+MIME_VCardCreateContentTypeHandlerClass(const char *content_type,
+ contentTypeHandlerInitStruct *initStruct);
+
+static nsresult nsVCardMimeContentTypeHandlerConstructor(nsISupports *aOuter,
+ REFNSIID aIID,
+ void **aResult)
+{
+ nsresult rv;
+ nsMimeContentTypeHandler *inst = nullptr;
+
+ if (NULL == aResult)
+ {
+ rv = NS_ERROR_NULL_POINTER;
+ return rv;
+ }
+ *aResult = NULL;
+ if (NULL != aOuter)
+ {
+ rv = NS_ERROR_NO_AGGREGATION;
+ return rv;
+ }
+ inst = new nsMimeContentTypeHandler("text/x-vcard", &MIME_VCardCreateContentTypeHandlerClass);
+ if (inst == NULL)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(inst);
+ rv = inst->QueryInterface(aIID,aResult);
+ NS_RELEASE(inst);
+
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PGP/MIME factories
+////////////////////////////////////////////////////////////////////////////////
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPgpMimeProxy, Init)
+
+NS_DEFINE_NAMED_CID(NS_PGPMIMEPROXY_CID);
+
+NS_DEFINE_NAMED_CID(NS_PGPMIME_CONTENT_TYPE_HANDLER_CID);
+
+extern "C" MimeObjectClass *
+MIME_PgpMimeCreateContentTypeHandlerClass(const char *content_type,
+ contentTypeHandlerInitStruct *initStruct);
+
+static nsresult
+nsPgpMimeMimeContentTypeHandlerConstructor(nsISupports *aOuter,
+ REFNSIID aIID,
+ void **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_FALSE(aOuter, NS_ERROR_NO_AGGREGATION);
+ *aResult = nullptr;
+
+ RefPtr<nsMimeContentTypeHandler> inst(
+ new nsMimeContentTypeHandler("mulitpart/encrypted",
+ &MIME_PgpMimeCreateContentTypeHandlerClass));
+
+ NS_ENSURE_TRUE(inst, NS_ERROR_OUT_OF_MEMORY);
+
+ return inst->QueryInterface(aIID, aResult);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// i18n factories
+////////////////////////////////////////////////////////////////////////////////
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCharsetConverterManager)
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF7ToUnicode)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMUTF7ToUnicode)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToUTF7)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToMUTF7)
+
+NS_DEFINE_NAMED_CID(NS_ICHARSETCONVERTERMANAGER_CID);
+
+NS_DEFINE_NAMED_CID(NS_UTF7TOUNICODE_CID);
+NS_DEFINE_NAMED_CID(NS_MUTF7TOUNICODE_CID);
+NS_DEFINE_NAMED_CID(NS_UNICODETOUTF7_CID);
+NS_DEFINE_NAMED_CID(NS_UNICODETOMUTF7_CID);
+
+const mozilla::Module::CIDEntry kMailNewsCIDs[] = {
+ // MailNews Base Entries
+ { &kNS_MESSENGERBOOTSTRAP_CID, false, NULL, nsMessengerBootstrapConstructor },
+ { &kNS_MESSENGERWINDOWSERVICE_CID, false, NULL, nsMessengerBootstrapConstructor},
+ { &kNS_MSGMAILSESSION_CID, false, NULL, nsMsgMailSessionConstructor},
+ { &kNS_MESSENGER_CID, false, NULL,nsMessengerConstructor},
+ { &kNS_MSGACCOUNTMANAGER_CID, false, NULL, nsMsgAccountManagerConstructor},
+ { &kNS_MSGACCOUNT_CID, false, NULL, nsMsgAccountConstructor},
+ { &kNS_MSGIDENTITY_CID, false, NULL, nsMsgIdentityConstructor},
+ { &kNS_MAILNEWSFOLDERDATASOURCE_CID, false, NULL, nsMsgFolderDataSourceConstructor},
+ { &kNS_MAILNEWSUNREADFOLDERDATASOURCE_CID, false, NULL, nsMsgUnreadFoldersDataSourceConstructor},
+ { &kNS_MAILNEWSFAVORITEFOLDERDATASOURCE_CID, false, NULL, nsMsgFavoriteFoldersDataSourceConstructor},
+ { &kNS_MAILNEWSRECENTFOLDERDATASOURCE_CID, false, NULL, nsMsgRecentFoldersDataSourceConstructor},
+ { &kNS_MSGACCOUNTMANAGERDATASOURCE_CID, false, NULL, nsMsgAccountManagerDataSourceConstructor},
+ { &kNS_MSGFILTERSERVICE_CID, false, NULL, nsMsgFilterServiceConstructor},
+ { &kNS_MSGSEARCHSESSION_CID, false, NULL, nsMsgSearchSessionConstructor},
+ { &kNS_MSGSEARCHTERM_CID, false, NULL, nsMsgSearchTermConstructor},
+ { &kNS_MSGSEARCHVALIDITYMANAGER_CID, false, NULL, nsMsgSearchValidityManagerConstructor},
+ { &kNS_MSGBIFFMANAGER_CID, false, NULL, nsMsgBiffManagerConstructor},
+ { &kNS_MSGPURGESERVICE_CID, false, NULL, nsMsgPurgeServiceConstructor},
+ { &kNS_STATUSBARBIFFMANAGER_CID, false, NULL, nsStatusBarBiffManagerConstructor},
+ { &kNS_COPYMESSAGESTREAMLISTENER_CID, false, NULL, nsCopyMessageStreamListenerConstructor},
+ { &kNS_MSGCOPYSERVICE_CID, false, NULL, nsMsgCopyServiceConstructor},
+ { &kNS_MSGFOLDERCACHE_CID, false, NULL, nsMsgFolderCacheConstructor},
+ { &kNS_MSGSTATUSFEEDBACK_CID, false, NULL, nsMsgStatusFeedbackConstructor},
+ { &kNS_MSGKEYARRAY_CID, false, NULL, nsMsgKeyArrayConstructor},
+ { &kNS_MSGWINDOW_CID, false, NULL, nsMsgWindowConstructor},
+#ifdef NS_PRINTING
+ { &kNS_MSG_PRINTENGINE_CID, false, NULL, nsMsgPrintEngineConstructor},
+#endif
+ { &kNS_MSGSERVICEPROVIDERSERVICE_CID, false, NULL, nsMsgServiceProviderServiceConstructor},
+ { &kNS_SUBSCRIBEDATASOURCE_CID, false, NULL, nsSubscribeDataSourceConstructor},
+ { &kNS_SUBSCRIBABLESERVER_CID, false, NULL, nsSubscribableServerConstructor},
+ { &kNS_MSGLOCALFOLDERCOMPACTOR_CID, false, NULL, nsFolderCompactStateConstructor},
+ { &kNS_MSG_OFFLINESTORECOMPACTOR_CID, false, NULL, nsOfflineStoreCompactStateConstructor},
+ { &kNS_MSGTHREADEDDBVIEW_CID, false, NULL, nsMsgThreadedDBViewConstructor},
+ { &kNS_MSGTHREADSWITHUNREADDBVIEW_CID, false, NULL, nsMsgThreadsWithUnreadDBViewConstructor},
+ { &kNS_MSGWATCHEDTHREADSWITHUNREADDBVIEW_CID, false, NULL, nsMsgWatchedThreadsWithUnreadDBViewConstructor
+},
+ { &kNS_MSGSEARCHDBVIEW_CID, false, NULL, nsMsgSearchDBViewConstructor},
+ { &kNS_MSGQUICKSEARCHDBVIEW_CID, false, NULL, nsMsgQuickSearchDBViewConstructor},
+ { &kNS_MSG_XFVFDBVIEW_CID, false, NULL, nsMsgXFVirtualFolderDBViewConstructor},
+ { &kNS_MSG_GROUPDBVIEW_CID, false, NULL, nsMsgGroupViewConstructor},
+ { &kNS_MSGOFFLINEMANAGER_CID, false, NULL, nsMsgOfflineManagerConstructor},
+ { &kNS_MSGPROGRESS_CID, false, NULL, nsMsgProgressConstructor},
+ { &kNS_SPAMSETTINGS_CID, false, NULL, nsSpamSettingsConstructor},
+ { &kNS_CIDPROTOCOL_CID, false, NULL, nsCidProtocolHandlerConstructor},
+ { &kNS_MSGTAGSERVICE_CID, false, NULL, nsMsgTagServiceConstructor},
+ { &kNS_MSGNOTIFICATIONSERVICE_CID, false, NULL, nsMsgFolderNotificationServiceConstructor},
+#ifdef XP_WIN
+ { &kNS_MESSENGERWININTEGRATION_CID, false, NULL, nsMessengerWinIntegrationConstructor},
+#endif
+#ifdef XP_MACOSX
+ { &kNS_MESSENGEROSXINTEGRATION_CID, false, NULL, nsMessengerOSXIntegrationConstructor},
+#endif
+#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
+ { &kNS_MESSENGERUNIXINTEGRATION_CID, false, NULL, nsMessengerUnixIntegrationConstructor},
+#endif
+ { &kNS_MESSENGERCONTENTHANDLER_CID, false, NULL, nsMessengerContentHandlerConstructor},
+ { &kNS_MSGCONTENTPOLICY_CID, false, NULL, nsMsgContentPolicyConstructor},
+ { &kNS_MSGSHUTDOWNSERVICE_CID, false, NULL, nsMsgShutdownServiceConstructor},
+ { &kMAILDIRPROVIDER_CID, false, NULL, nsMailDirProviderConstructor},
+ { &kNS_STOPWATCH_CID, false, NULL, nsStopwatchConstructor},
+ { &kNS_MAILNEWSDLF_CID, false, NULL, MailNewsDLFConstructor},
+ // Address Book Entries
+ { &kNS_ABMANAGER_CID, false, NULL, nsAbManagerConstructor },
+ { &kNS_ABDIRECTORY_CID, false, NULL, nsAbBSDirectoryConstructor },
+ { &kNS_ABMDBDIRECTORY_CID, false, NULL, nsAbMDBDirectoryConstructor },
+ { &kNS_ABMDBCARD_CID, false, NULL, nsAbMDBCardConstructor },
+ { &kNS_ADDRDATABASE_CID, false, NULL, nsAddrDatabaseConstructor },
+ { &kNS_ABCARDPROPERTY_CID, false, NULL, nsAbCardPropertyConstructor },
+ { &kNS_ABDIRPROPERTY_CID, false, NULL, nsAbDirPropertyConstructor },
+ { &kNS_ABADDRESSCOLLECTOR_CID, false, NULL, nsAbAddressCollectorConstructor },
+ { &kNS_ADDBOOKURL_CID, false, NULL, nsAddbookUrlConstructor },
+ { &kNS_ADDBOOK_HANDLER_CID, false, NULL, nsAddbookProtocolHandlerConstructor }
+,
+ { &kNS_ABCONTENTHANDLER_CID, false, NULL, nsAbContentHandlerConstructor },
+ { &kNS_ABDIRFACTORYSERVICE_CID, false, NULL, nsAbDirFactoryServiceConstructor },
+ { &kNS_ABMDBDIRFACTORY_CID, false, NULL, nsAbMDBDirFactoryConstructor },
+#if defined(MOZ_MAPI_SUPPORT)
+ { &kNS_ABOUTLOOKDIRECTORY_CID, false, NULL, nsAbOutlookDirectoryConstructor },
+ { &kNS_ABOUTLOOKDIRFACTORY_CID, false, NULL, nsAbOutlookDirFactoryConstructor },
+#endif
+ { &kNS_ABDIRECTORYQUERYARGUMENTS_CID, false, NULL, nsAbDirectoryQueryArgumentsConstructor },
+ { &kNS_BOOLEANCONDITIONSTRING_CID, false, NULL, nsAbBooleanConditionStringConstructor },
+ { &kNS_BOOLEANEXPRESSION_CID, false, NULL, nsAbBooleanExpressionConstructor },
+
+#if defined(MOZ_LDAP_XPCOM)
+ { &kNS_ABLDAPDIRECTORY_CID, false, NULL, nsAbLDAPDirectoryConstructor },
+ { &kNS_ABLDAPDIRECTORYQUERY_CID, false, NULL, nsAbLDAPDirectoryQueryConstructor },
+ { &kNS_ABLDAPCARD_CID, false, NULL, nsAbLDAPCardConstructor },
+ { &kNS_ABLDAP_REPLICATIONSERVICE_CID, false, NULL, nsAbLDAPReplicationServiceConstructor },
+ { &kNS_ABLDAP_REPLICATIONQUERY_CID, false, NULL, nsAbLDAPReplicationQueryConstructor },
+ { &kNS_ABLDAP_PROCESSREPLICATIONDATA_CID, false, NULL, nsAbLDAPProcessReplicationDataConstructor },
+ { &kNS_ABLDAPDIRFACTORY_CID, false, NULL, nsAbLDAPDirFactoryConstructor },
+#endif
+ { &kNS_ABDIRECTORYQUERYPROXY_CID, false, NULL, nsAbDirectoryQueryProxyConstructor },
+#ifdef XP_MACOSX
+ { &kNS_ABOSXDIRECTORY_CID, false, NULL, nsAbOSXDirectoryConstructor },
+ { &kNS_ABOSXCARD_CID, false, NULL, nsAbOSXCardConstructor },
+ { &kNS_ABOSXDIRFACTORY_CID, false, NULL, nsAbOSXDirFactoryConstructor },
+#endif
+ { &kNS_ABVIEW_CID, false, NULL, nsAbViewConstructor },
+ { &kNS_MSGVCARDSERVICE_CID, false, NULL, nsMsgVCardServiceConstructor },
+ { &kNS_ABLDIFSERVICE_CID, false, NULL, nsAbLDIFServiceConstructor },
+ // Bayesian Filter Entries
+ { &kNS_BAYESIANFILTER_CID, false, NULL, nsBayesianFilterConstructor },
+ // Compose Entries
+ { &kNS_MSGCOMPOSE_CID, false, NULL, nsMsgComposeConstructor},
+ { &kNS_MSGCOMPOSESERVICE_CID, false, NULL, nsMsgComposeServiceConstructor},
+ { &kNS_MSGCOMPOSECONTENTHANDLER_CID, false, NULL, nsMsgComposeContentHandlerConstructor},
+ { &kNS_MSGCOMPOSEPARAMS_CID, false, NULL, nsMsgComposeParamsConstructor},
+ { &kNS_MSGCOMPOSESENDLISTENER_CID, false, NULL, nsMsgComposeSendListenerConstructor},
+ { &kNS_MSGCOMPOSEPROGRESSPARAMS_CID, false, NULL, nsMsgComposeProgressParamsConstructor},
+ { &kNS_MSGCOMPFIELDS_CID, false, NULL, nsMsgCompFieldsConstructor},
+ { &kNS_MSGATTACHMENT_CID, false, NULL, nsMsgAttachmentConstructor},
+ { &kNS_MSGATTACHMENTDATA_CID, false, NULL, nsMsgAttachmentDataConstructor},
+ { &kNS_MSGATTACHEDFILE_CID, false, NULL, nsMsgAttachedFileConstructor},
+ { &kNS_MSGSEND_CID, false, NULL, nsMsgComposeAndSendConstructor},
+ { &kNS_MSGSENDLATER_CID, false, NULL, nsMsgSendLaterConstructor},
+ { &kNS_SMTPSERVICE_CID, false, NULL, nsSmtpServiceConstructor},
+ { &kNS_SMTPSERVER_CID, false, NULL, nsSmtpServerConstructor},
+ { &kNS_SMTPURL_CID, false, NULL, nsSmtpUrlConstructor},
+ { &kNS_MAILTOURL_CID, false, NULL, nsMailtoUrlConstructor},
+ { &kNS_MSGQUOTE_CID, false, NULL, nsMsgQuoteConstructor},
+ { &kNS_MSGQUOTELISTENER_CID, false, NULL, nsMsgQuoteListenerConstructor},
+ { &kNS_URLFETCHER_CID, false, NULL, nsURLFetcherConstructor},
+ { &kNS_MSGCOMPUTILS_CID, false, NULL, nsMsgCompUtilsConstructor},
+ // JsAccount Entries
+ { &kJACPPABDIRECTORYDELEGATOR_CID, false, nullptr, JaCppAbDirectoryDelegatorConstructor },
+ { &kJACPPCOMPOSEDELEGATOR_CID, false, nullptr, JaCppComposeDelegatorConstructor },
+ { &kJACPPINCOMINGSERVERDELEGATOR_CID, false, nullptr, JaCppIncomingServerDelegatorConstructor },
+ { &kJACPPMSGFOLDERDELEGATOR_CID, false, nullptr, JaCppMsgFolderDelegatorConstructor },
+ { &kJACPPSENDDELEGATOR_CID, false, nullptr, JaCppSendDelegatorConstructor },
+ { &kJACPPURLDELEGATOR_CID, false, nullptr, JaCppUrlDelegatorConstructor },
+ // Imap Entries
+ { &kNS_IMAPURL_CID, false, NULL, nsImapUrlConstructor },
+ { &kNS_IMAPPROTOCOL_CID, false, nullptr, nsImapProtocolConstructor },
+ { &kNS_IMAPMOCKCHANNEL_CID, false, nullptr, nsImapMockChannelConstructor },
+ { &kNS_IIMAPHOSTSESSIONLIST_CID, false, nullptr, nsIMAPHostSessionListConstructor },
+ { &kNS_IMAPINCOMINGSERVER_CID, false, nullptr, nsImapIncomingServerConstructor },
+ { &kNS_IMAPRESOURCE_CID, false, nullptr, nsImapMailFolderConstructor },
+ { &kNS_IMAPSERVICE_CID, false, nullptr, nsImapServiceConstructor },
+ { &kNS_AUTOSYNCMANAGER_CID, false, nullptr, nsAutoSyncManagerConstructor },
+ // Local Entries
+ { &kNS_MAILBOXURL_CID, false, NULL, nsMailboxUrlConstructor },
+ { &kNS_MAILBOXSERVICE_CID, false, NULL, nsMailboxServiceConstructor },
+ { &kNS_MAILBOXPARSER_CID, false, NULL, nsMsgMailboxParserConstructor },
+ { &kNS_POP3URL_CID, false, NULL, nsPop3URLConstructor },
+ { &kNS_POP3SERVICE_CID, false, NULL, nsPop3ServiceConstructor },
+ { &kNS_NONESERVICE_CID, false, NULL, nsNoneServiceConstructor },
+#ifdef HAVE_MOVEMAIL
+ { &kNS_MOVEMAILSERVICE_CID, false, NULL, nsMovemailServiceConstructor },
+#endif /* HAVE_MOVEMAIL */
+ { &kNS_LOCALMAILFOLDERRESOURCE_CID, false, NULL, nsMsgLocalMailFolderConstructor },
+ { &kNS_POP3INCOMINGSERVER_CID, false, NULL, nsPop3IncomingServerConstructor },
+#ifdef HAVE_MOVEMAIL
+ { &kNS_MOVEMAILINCOMINGSERVER_CID, false, NULL, nsMovemailIncomingServerConstructor },
+#endif /* HAVE_MOVEMAIL */
+ { &kNS_NOINCOMINGSERVER_CID, false, NULL, nsNoIncomingServerConstructor },
+ { &kNS_PARSEMAILMSGSTATE_CID, false, NULL, nsParseMailMessageStateConstructor },
+ { &kNS_RSSSERVICE_CID, false, NULL, nsRssServiceConstructor },
+ { &kNS_RSSINCOMINGSERVER_CID, false, NULL, nsRssIncomingServerConstructor },
+ { &kNS_BRKMBOXSTORE_CID, false, NULL, nsMsgBrkMBoxStoreConstructor },
+ { &kNS_MAILDIRSTORE_CID, false, NULL, nsMsgMaildirStoreConstructor },
+ // msgdb Entries
+ { &kNS_MAILDB_CID, false, NULL, nsMailDatabaseConstructor },
+ { &kNS_NEWSDB_CID, false, NULL, nsNewsDatabaseConstructor },
+ { &kNS_IMAPDB_CID, false, NULL, nsImapMailDatabaseConstructor },
+ { &kNS_MSG_RETENTIONSETTINGS_CID, false, NULL, nsMsgRetentionSettingsConstructor },
+ { &kNS_MSG_DOWNLOADSETTINGS_CID, false, NULL, nsMsgDownloadSettingsConstructor },
+ { &kNS_MSGDB_SERVICE_CID, false, NULL, nsMsgDBServiceConstructor },
+ // Mime Entries
+ { &kNS_MIME_OBJECT_CLASS_ACCESS_CID, false, NULL, nsMimeObjectClassAccessConstructor },
+ { &kNS_MAILNEWS_MIME_STREAM_CONVERTER_CID, false, NULL, nsStreamConverterConstructor },
+ { &kNS_HTML_MIME_EMITTER_CID, false, NULL, nsMimeHtmlDisplayEmitterConstructor},
+ { &kNS_XML_MIME_EMITTER_CID, false, NULL, nsMimeXmlEmitterConstructor},
+ { &kNS_PLAIN_MIME_EMITTER_CID, false, NULL, nsMimePlainEmitterConstructor},
+ { &kNS_RAW_MIME_EMITTER_CID, false, NULL, nsMimeRawEmitterConstructor},
+ // Fts 3
+ { &kNS_FTS3TOKENIZER_CID, false, NULL, nsFts3TokenizerConstructor },
+ // News Entries
+ { &kNS_NNTPURL_CID, false, NULL, nsNntpUrlConstructor },
+ { &kNS_NNTPSERVICE_CID, false, NULL, nsNntpServiceConstructor },
+ { &kNS_NEWSFOLDERRESOURCE_CID, false, NULL, nsMsgNewsFolderConstructor },
+ { &kNS_NNTPINCOMINGSERVER_CID, false, NULL, nsNntpIncomingServerConstructor },
+ { &kNS_NNTPNEWSGROUPPOST_CID, false, NULL, nsNNTPNewsgroupPostConstructor },
+ { &kNS_NNTPNEWSGROUPLIST_CID, false, NULL, nsNNTPNewsgroupListConstructor },
+ { &kNS_NNTPARTICLELIST_CID, false, NULL, nsNNTPArticleListConstructor },
+ { &kNS_NEWSDOWNLOADDIALOGARGS_CID, false, NULL, nsNewsDownloadDialogArgsConstructor },
+ // Mail View Entries
+ { &kNS_MSGMAILVIEWLIST_CID, false, NULL, nsMsgMailViewListConstructor },
+ // mdn Entries
+ { &kNS_MSGMDNGENERATOR_CID, false, NULL, nsMsgMdnGeneratorConstructor },
+ // SMime Entries
+ { &kNS_MSGCOMPOSESECURE_CID, false, NULL, nsMsgComposeSecureConstructor },
+ { &kNS_MSGSMIMECOMPFIELDS_CID, false, NULL, nsMsgSMIMEComposeFieldsConstructor },
+ { &kNS_SMIMEJSJELPER_CID, false, NULL, nsSMimeJSHelperConstructor },
+ { &kNS_SMIMEENCRYPTURISERVICE_CID, false, NULL, nsEncryptedSMIMEURIsServiceConstructor },
+ { &kNS_CMSDECODER_CID, false, NULL, nsCMSDecoderConstructor },
+ { &kNS_CMSENCODER_CID, false, NULL, nsCMSEncoderConstructor },
+ { &kNS_CMSMESSAGE_CID, false, NULL, nsCMSMessageConstructor },
+ { &kNS_CMSSECUREMESSAGE_CID, false, NULL, nsCMSSecureMessageConstructor },
+ { &kNS_CERT_PICKER_CID, false, nullptr, nsCertPickerConstructor },
+ // Vcard Entries
+ { &kNS_VCARD_CONTENT_TYPE_HANDLER_CID, false, NULL, nsVCardMimeContentTypeHandlerConstructor},
+ // PGP/MIME Entries
+ { &kNS_PGPMIME_CONTENT_TYPE_HANDLER_CID, false, NULL, nsPgpMimeMimeContentTypeHandlerConstructor },
+ { &kNS_PGPMIMEPROXY_CID, false, NULL, nsPgpMimeProxyConstructor },
+ // i18n Entries
+ { &kNS_ICHARSETCONVERTERMANAGER_CID, false, nullptr, nsCharsetConverterManagerConstructor },
+ { &kNS_UTF7TOUNICODE_CID, false, nullptr, nsUTF7ToUnicodeConstructor },
+ { &kNS_MUTF7TOUNICODE_CID, false, nullptr, nsMUTF7ToUnicodeConstructor },
+ { &kNS_UNICODETOUTF7_CID, false, nullptr, nsUnicodeToUTF7Constructor },
+ { &kNS_UNICODETOMUTF7_CID, false, nullptr, nsUnicodeToMUTF7Constructor },
+ // Tokenizer Entries
+ { NULL }
+};
+
+const mozilla::Module::ContractIDEntry kMailNewsContracts[] = {
+ // MailNews Base Entries
+ { NS_MESSENGERBOOTSTRAP_CONTRACTID, &kNS_MESSENGERBOOTSTRAP_CID },
+ { NS_MESSENGERWINDOWSERVICE_CONTRACTID, &kNS_MESSENGERWINDOWSERVICE_CID },
+ { NS_MSGMAILSESSION_CONTRACTID, &kNS_MSGMAILSESSION_CID },
+ { NS_MESSENGER_CONTRACTID, &kNS_MESSENGER_CID },
+ { NS_MSGACCOUNTMANAGER_CONTRACTID, &kNS_MSGACCOUNTMANAGER_CID },
+ { NS_MSGACCOUNT_CONTRACTID, &kNS_MSGACCOUNT_CID },
+ { NS_MSGIDENTITY_CONTRACTID, &kNS_MSGIDENTITY_CID },
+ { NS_MAILNEWSFOLDERDATASOURCE_CONTRACTID, &kNS_MAILNEWSFOLDERDATASOURCE_CID },
+ { NS_MAILNEWSUNREADFOLDERDATASOURCE_CONTRACTID, &kNS_MAILNEWSUNREADFOLDERDATASOURCE_CID },
+ { NS_MAILNEWSFAVORITEFOLDERDATASOURCE_CONTRACTID, &kNS_MAILNEWSFAVORITEFOLDERDATASOURCE_CID },
+ { NS_MAILNEWSRECENTFOLDERDATASOURCE_CONTRACTID, &kNS_MAILNEWSRECENTFOLDERDATASOURCE_CID },
+ { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "msgaccountmanager", &kNS_MSGACCOUNTMANAGERDATASOURCE_CID },
+ { NS_MSGFILTERSERVICE_CONTRACTID, &kNS_MSGFILTERSERVICE_CID },
+ { NS_MSGSEARCHSESSION_CONTRACTID, &kNS_MSGSEARCHSESSION_CID },
+ { NS_MSGSEARCHTERM_CONTRACTID, &kNS_MSGSEARCHTERM_CID },
+ { NS_MSGSEARCHVALIDITYMANAGER_CONTRACTID, &kNS_MSGSEARCHVALIDITYMANAGER_CID },
+ { NS_MSGBIFFMANAGER_CONTRACTID, &kNS_MSGBIFFMANAGER_CID },
+ { NS_MSGPURGESERVICE_CONTRACTID, &kNS_MSGPURGESERVICE_CID },
+ { NS_STATUSBARBIFFMANAGER_CONTRACTID, &kNS_STATUSBARBIFFMANAGER_CID },
+ { NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &kNS_COPYMESSAGESTREAMLISTENER_CID },
+ { NS_MSGCOPYSERVICE_CONTRACTID, &kNS_MSGCOPYSERVICE_CID },
+ { NS_MSGFOLDERCACHE_CONTRACTID, &kNS_MSGFOLDERCACHE_CID },
+ { NS_MSGSTATUSFEEDBACK_CONTRACTID, &kNS_MSGSTATUSFEEDBACK_CID },
+ { NS_MSGKEYARRAY_CONTRACTID, &kNS_MSGKEYARRAY_CID },
+ { NS_MSGWINDOW_CONTRACTID, &kNS_MSGWINDOW_CID },
+#ifdef NS_PRINTING
+ { NS_MSGPRINTENGINE_CONTRACTID, &kNS_MSG_PRINTENGINE_CID },
+#endif
+ { NS_MSGSERVICEPROVIDERSERVICE_CONTRACTID, &kNS_MSGSERVICEPROVIDERSERVICE_CID },
+ { NS_SUBSCRIBEDATASOURCE_CONTRACTID, &kNS_SUBSCRIBEDATASOURCE_CID },
+ { NS_SUBSCRIBABLESERVER_CONTRACTID, &kNS_SUBSCRIBABLESERVER_CID },
+ { NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &kNS_MSGLOCALFOLDERCOMPACTOR_CID },
+ { NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &kNS_MSG_OFFLINESTORECOMPACTOR_CID },
+ { NS_MSGTHREADEDDBVIEW_CONTRACTID, &kNS_MSGTHREADEDDBVIEW_CID },
+ { NS_MSGTHREADSWITHUNREADDBVIEW_CONTRACTID, &kNS_MSGTHREADSWITHUNREADDBVIEW_CID },
+ { NS_MSGWATCHEDTHREADSWITHUNREADDBVIEW_CONTRACTID, &kNS_MSGWATCHEDTHREADSWITHUNREADDBVIEW_CID },
+ { NS_MSGSEARCHDBVIEW_CONTRACTID, &kNS_MSGSEARCHDBVIEW_CID },
+ { NS_MSGQUICKSEARCHDBVIEW_CONTRACTID, &kNS_MSGQUICKSEARCHDBVIEW_CID },
+ { NS_MSGXFVFDBVIEW_CONTRACTID, &kNS_MSG_XFVFDBVIEW_CID },
+ { NS_MSGGROUPDBVIEW_CONTRACTID, &kNS_MSG_GROUPDBVIEW_CID },
+ { NS_MSGOFFLINEMANAGER_CONTRACTID, &kNS_MSGOFFLINEMANAGER_CID },
+ { NS_MSGPROGRESS_CONTRACTID, &kNS_MSGPROGRESS_CID },
+ { NS_SPAMSETTINGS_CONTRACTID, &kNS_SPAMSETTINGS_CID },
+ { NS_CIDPROTOCOLHANDLER_CONTRACTID, &kNS_CIDPROTOCOL_CID },
+ { NS_MSGTAGSERVICE_CONTRACTID, &kNS_MSGTAGSERVICE_CID },
+ { NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &kNS_MSGNOTIFICATIONSERVICE_CID },
+#ifdef XP_WIN
+ { NS_MESSENGEROSINTEGRATION_CONTRACTID, &kNS_MESSENGERWININTEGRATION_CID },
+#endif
+#ifdef XP_MACOSX
+ { NS_MESSENGEROSINTEGRATION_CONTRACTID, &kNS_MESSENGEROSXINTEGRATION_CID },
+#endif
+#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
+ { NS_MESSENGEROSINTEGRATION_CONTRACTID, &kNS_MESSENGERUNIXINTEGRATION_CID },
+#endif
+ { NS_MESSENGERCONTENTHANDLER_CONTRACTID, &kNS_MESSENGERCONTENTHANDLER_CID },
+ { NS_MSGCONTENTPOLICY_CONTRACTID, &kNS_MSGCONTENTPOLICY_CID },
+ { NS_MSGSHUTDOWNSERVICE_CONTRACTID, &kNS_MSGSHUTDOWNSERVICE_CID },
+ { NS_MAILDIRPROVIDER_CONTRACTID, &kMAILDIRPROVIDER_CID },
+ { NS_STOPWATCH_CONTRACTID, &kNS_STOPWATCH_CID },
+ { NS_MAILNEWSDLF_CONTRACTID, &kNS_MAILNEWSDLF_CID },
+ // Address Book Entries
+ { NS_ABMANAGER_CONTRACTID, &kNS_ABMANAGER_CID },
+ { NS_ABMANAGERSTARTUPHANDLER_CONTRACTID, &kNS_ABMANAGER_CID },
+ { NS_ABDIRECTORY_CONTRACTID, &kNS_ABDIRECTORY_CID },
+ { NS_ABMDBDIRECTORY_CONTRACTID, &kNS_ABMDBDIRECTORY_CID },
+ { NS_ABMDBCARD_CONTRACTID, &kNS_ABMDBCARD_CID },
+ { NS_ADDRDATABASE_CONTRACTID, &kNS_ADDRDATABASE_CID },
+ { NS_ABCARDPROPERTY_CONTRACTID, &kNS_ABCARDPROPERTY_CID },
+ { NS_ABDIRPROPERTY_CONTRACTID, &kNS_ABDIRPROPERTY_CID },
+ { NS_ABADDRESSCOLLECTOR_CONTRACTID, &kNS_ABADDRESSCOLLECTOR_CID },
+ { NS_ADDBOOKURL_CONTRACTID, &kNS_ADDBOOKURL_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "addbook", &kNS_ADDBOOK_HANDLER_CID },
+ { NS_CONTENT_HANDLER_CONTRACTID_PREFIX"application/x-addvcard", &kNS_ABCONTENTHANDLER_CID },
+ { NS_CONTENT_HANDLER_CONTRACTID_PREFIX"text/x-vcard", &kNS_ABCONTENTHANDLER_CID },
+ { NS_ABDIRFACTORYSERVICE_CONTRACTID, &kNS_ABDIRFACTORYSERVICE_CID },
+ { NS_ABMDBDIRFACTORY_CONTRACTID, &kNS_ABMDBDIRFACTORY_CID },
+#if defined(MOZ_MAPI_SUPPORT)
+ { NS_ABOUTLOOKDIRECTORY_CONTRACTID, &kNS_ABOUTLOOKDIRECTORY_CID },
+ { NS_ABOUTLOOKDIRFACTORY_CONTRACTID, &kNS_ABOUTLOOKDIRFACTORY_CID },
+#endif
+ { NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID, &kNS_ABDIRECTORYQUERYARGUMENTS_CID },
+ { NS_BOOLEANCONDITIONSTRING_CONTRACTID, &kNS_BOOLEANCONDITIONSTRING_CID },
+ { NS_BOOLEANEXPRESSION_CONTRACTID, &kNS_BOOLEANEXPRESSION_CID },
+
+#if defined(MOZ_LDAP_XPCOM)
+ { NS_ABLDAPDIRECTORY_CONTRACTID, &kNS_ABLDAPDIRECTORY_CID },
+ { NS_ABLDAPDIRECTORYQUERY_CONTRACTID, &kNS_ABLDAPDIRECTORYQUERY_CID },
+ { NS_ABLDAPCARD_CONTRACTID, &kNS_ABLDAPCARD_CID },
+ { NS_ABLDAPDIRFACTORY_CONTRACTID, &kNS_ABLDAPDIRFACTORY_CID },
+ { NS_ABLDAP_REPLICATIONSERVICE_CONTRACTID, &kNS_ABLDAP_REPLICATIONSERVICE_CID },
+ { NS_ABLDAP_REPLICATIONQUERY_CONTRACTID, &kNS_ABLDAP_REPLICATIONQUERY_CID },
+ { NS_ABLDAP_PROCESSREPLICATIONDATA_CONTRACTID, &kNS_ABLDAP_PROCESSREPLICATIONDATA_CID },
+ { NS_ABLDAPACDIRFACTORY_CONTRACTID, &kNS_ABLDAPDIRFACTORY_CID },
+ { NS_ABLDAPSACDIRFACTORY_CONTRACTID, &kNS_ABLDAPDIRFACTORY_CID },
+#endif
+
+ { NS_ABDIRECTORYQUERYPROXY_CONTRACTID, &kNS_ABDIRECTORYQUERYPROXY_CID },
+#ifdef XP_MACOSX
+ { NS_ABOSXDIRECTORY_CONTRACTID, &kNS_ABOSXDIRECTORY_CID },
+ { NS_ABOSXCARD_CONTRACTID, &kNS_ABOSXCARD_CID },
+ { NS_ABOSXDIRFACTORY_CONTRACTID, &kNS_ABOSXDIRFACTORY_CID },
+#endif
+ { NS_ABVIEW_CONTRACTID, &kNS_ABVIEW_CID },
+ { NS_MSGVCARDSERVICE_CONTRACTID, &kNS_MSGVCARDSERVICE_CID },
+ { NS_ABLDIFSERVICE_CONTRACTID, &kNS_ABLDIFSERVICE_CID },
+ // Bayesian Filter Entries
+ { NS_BAYESIANFILTER_CONTRACTID, &kNS_BAYESIANFILTER_CID },
+ // Compose Entries
+ { NS_MSGCOMPOSE_CONTRACTID, &kNS_MSGCOMPOSE_CID },
+ { NS_MSGCOMPOSESERVICE_CONTRACTID, &kNS_MSGCOMPOSESERVICE_CID },
+ { NS_MSGCOMPOSESTARTUPHANDLER_CONTRACTID, &kNS_MSGCOMPOSESERVICE_CID },
+ { NS_MSGCOMPOSECONTENTHANDLER_CONTRACTID, &kNS_MSGCOMPOSECONTENTHANDLER_CID },
+ { NS_MSGCOMPOSEPARAMS_CONTRACTID, &kNS_MSGCOMPOSEPARAMS_CID },
+ { NS_MSGCOMPOSESENDLISTENER_CONTRACTID, &kNS_MSGCOMPOSESENDLISTENER_CID },
+ { NS_MSGCOMPOSEPROGRESSPARAMS_CONTRACTID, &kNS_MSGCOMPOSEPROGRESSPARAMS_CID },
+ { NS_MSGCOMPFIELDS_CONTRACTID, &kNS_MSGCOMPFIELDS_CID },
+ { NS_MSGATTACHMENT_CONTRACTID, &kNS_MSGATTACHMENT_CID },
+ { NS_MSGATTACHMENTDATA_CONTRACTID, &kNS_MSGATTACHMENTDATA_CID },
+ { NS_MSGATTACHEDFILE_CONTRACTID, &kNS_MSGATTACHEDFILE_CID },
+ { NS_MSGSEND_CONTRACTID, &kNS_MSGSEND_CID },
+ { NS_MSGSENDLATER_CONTRACTID, &kNS_MSGSENDLATER_CID },
+ { NS_SMTPSERVICE_CONTRACTID, &kNS_SMTPSERVICE_CID },
+ { NS_MAILTOHANDLER_CONTRACTID, &kNS_SMTPSERVICE_CID },
+ { NS_SMTPSERVER_CONTRACTID, &kNS_SMTPSERVER_CID },
+ { NS_SMTPURL_CONTRACTID, &kNS_SMTPURL_CID },
+ { NS_MAILTOURL_CONTRACTID, &kNS_MAILTOURL_CID },
+ { NS_MSGQUOTE_CONTRACTID, &kNS_MSGQUOTE_CID },
+ { NS_MSGQUOTELISTENER_CONTRACTID, &kNS_MSGQUOTELISTENER_CID },
+ { NS_URLFETCHER_CONTRACTID, &kNS_URLFETCHER_CID },
+ { NS_MSGCOMPUTILS_CONTRACTID, &kNS_MSGCOMPUTILS_CID },
+ // JsAccount Entries
+ { JACPPABDIRECTORYDELEGATOR_CONTRACTID, &kJACPPABDIRECTORYDELEGATOR_CID },
+ { JACPPCOMPOSEDELEGATOR_CONTRACTID, &kJACPPCOMPOSEDELEGATOR_CID },
+ { JACPPINCOMINGSERVERDELEGATOR_CONTRACTID, &kJACPPINCOMINGSERVERDELEGATOR_CID },
+ { JACPPMSGFOLDERDELEGATOR_CONTRACTID, &kJACPPMSGFOLDERDELEGATOR_CID },
+ { JACPPSENDDELEGATOR_CONTRACTID, &kJACPPSENDDELEGATOR_CID },
+ { JACPPURLDELEGATOR_CONTRACTID, &kJACPPURLDELEGATOR_CID },
+ // Imap Entries
+ { NS_IMAPINCOMINGSERVER_CONTRACTID, &kNS_IMAPINCOMINGSERVER_CID },
+ { NS_RDF_RESOURCE_FACTORY_CONTRACTID_PREFIX "imap", &kNS_IMAPRESOURCE_CID },
+ { "@mozilla.org/messenger/messageservice;1?type=imap-message", &kNS_IMAPSERVICE_CID },
+ { "@mozilla.org/messenger/messageservice;1?type=imap", &kNS_IMAPSERVICE_CID },
+ { NS_IMAPSERVICE_CONTRACTID, &kNS_IMAPSERVICE_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "imap", &kNS_IMAPSERVICE_CID },
+ { NS_IMAPPROTOCOLINFO_CONTRACTID, &kNS_IMAPSERVICE_CID },
+ { NS_CONTENT_HANDLER_CONTRACTID_PREFIX"x-application-imapfolder", &kNS_IMAPSERVICE_CID },
+ { NS_AUTOSYNCMANAGER_CONTRACTID, &kNS_AUTOSYNCMANAGER_CID },
+ // Local Entries
+ { NS_MAILBOXURL_CONTRACTID, &kNS_MAILBOXURL_CID },
+ { NS_MAILBOXSERVICE_CONTRACTID1, &kNS_MAILBOXSERVICE_CID },
+ { NS_MAILBOXSERVICE_CONTRACTID2, &kNS_MAILBOXSERVICE_CID },
+ { NS_MAILBOXSERVICE_CONTRACTID3, &kNS_MAILBOXSERVICE_CID },
+ { NS_MAILBOXSERVICE_CONTRACTID4, &kNS_MAILBOXSERVICE_CID },
+ { NS_MAILBOXPARSER_CONTRACTID, &kNS_MAILBOXPARSER_CID },
+ { NS_POP3URL_CONTRACTID, &kNS_POP3URL_CID },
+ { NS_POP3SERVICE_CONTRACTID1, &kNS_POP3SERVICE_CID },
+ { NS_POP3SERVICE_CONTRACTID2, &kNS_POP3SERVICE_CID },
+ { NS_POP3SERVICE_CONTRACTID3, &kNS_POP3SERVICE_CID },
+ { NS_NONESERVICE_CONTRACTID, &kNS_NONESERVICE_CID },
+#ifdef HAVE_MOVEMAIL
+ { NS_MOVEMAILSERVICE_CONTRACTID, &kNS_MOVEMAILSERVICE_CID },
+#endif /* HAVE_MOVEMAIL */
+ { NS_POP3PROTOCOLINFO_CONTRACTID, &kNS_POP3SERVICE_CID },
+ { NS_NONEPROTOCOLINFO_CONTRACTID, &kNS_NONESERVICE_CID },
+#ifdef HAVE_MOVEMAIL
+ { NS_MOVEMAILPROTOCOLINFO_CONTRACTID, &kNS_MOVEMAILSERVICE_CID },
+#endif /* HAVE_MOVEMAIL */
+ { NS_LOCALMAILFOLDERRESOURCE_CONTRACTID, &kNS_LOCALMAILFOLDERRESOURCE_CID },
+ { NS_POP3INCOMINGSERVER_CONTRACTID, &kNS_POP3INCOMINGSERVER_CID },
+#ifdef HAVE_MOVEMAIL
+ { NS_MOVEMAILINCOMINGSERVER_CONTRACTID, &kNS_MOVEMAILINCOMINGSERVER_CID },
+#endif /* HAVE_MOVEMAIL */
+ { NS_BRKMBOXSTORE_CONTRACTID, &kNS_BRKMBOXSTORE_CID },
+ { NS_MAILDIRSTORE_CONTRACTID, &kNS_MAILDIRSTORE_CID },
+ { NS_NOINCOMINGSERVER_CONTRACTID, &kNS_NOINCOMINGSERVER_CID },
+ { NS_PARSEMAILMSGSTATE_CONTRACTID, &kNS_PARSEMAILMSGSTATE_CID },
+ { NS_RSSSERVICE_CONTRACTID, &kNS_RSSSERVICE_CID },
+ { NS_RSSPROTOCOLINFO_CONTRACTID, &kNS_RSSSERVICE_CID },
+ { NS_RSSINCOMINGSERVER_CONTRACTID, &kNS_RSSINCOMINGSERVER_CID },
+ // msgdb Entries
+ { NS_MAILBOXDB_CONTRACTID, &kNS_MAILDB_CID },
+ { NS_NEWSDB_CONTRACTID, &kNS_NEWSDB_CID },
+ { NS_IMAPDB_CONTRACTID, &kNS_IMAPDB_CID },
+ { NS_MSG_RETENTIONSETTINGS_CONTRACTID, &kNS_MSG_RETENTIONSETTINGS_CID },
+ { NS_MSG_DOWNLOADSETTINGS_CONTRACTID, &kNS_MSG_DOWNLOADSETTINGS_CID },
+ { NS_MSGDB_SERVICE_CONTRACTID, &kNS_MSGDB_SERVICE_CID },
+ // Mime Entries
+ { NS_MIME_OBJECT_CONTRACTID, &kNS_MIME_OBJECT_CLASS_ACCESS_CID },
+ { NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID, &kNS_MAILNEWS_MIME_STREAM_CONVERTER_CID },
+ { NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID1, &kNS_MAILNEWS_MIME_STREAM_CONVERTER_CID },
+ { NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID2, &kNS_MAILNEWS_MIME_STREAM_CONVERTER_CID },
+ { NS_HTML_MIME_EMITTER_CONTRACTID, &kNS_HTML_MIME_EMITTER_CID },
+ { NS_XML_MIME_EMITTER_CONTRACTID, &kNS_XML_MIME_EMITTER_CID },
+ { NS_PLAIN_MIME_EMITTER_CONTRACTID, &kNS_PLAIN_MIME_EMITTER_CID },
+ { NS_RAW_MIME_EMITTER_CONTRACTID, &kNS_RAW_MIME_EMITTER_CID },
+ // FTS3
+ { NS_FTS3TOKENIZER_CONTRACTID, &kNS_FTS3TOKENIZER_CID },
+ // News Entries
+ { NS_NNTPURL_CONTRACTID, &kNS_NNTPURL_CID },
+ { NS_NNTPSERVICE_CONTRACTID, &kNS_NNTPSERVICE_CID },
+ { NS_NEWSSTARTUPHANDLER_CONTRACTID, &kNS_NNTPSERVICE_CID },
+ { NS_NNTPPROTOCOLINFO_CONTRACTID, &kNS_NNTPSERVICE_CID },
+ { NS_NNTPMESSAGESERVICE_CONTRACTID, &kNS_NNTPSERVICE_CID },
+ { NS_NEWSMESSAGESERVICE_CONTRACTID, &kNS_NNTPSERVICE_CID },
+ { NS_NEWSPROTOCOLHANDLER_CONTRACTID, &kNS_NNTPSERVICE_CID },
+ { NS_SNEWSPROTOCOLHANDLER_CONTRACTID, &kNS_NNTPSERVICE_CID },
+ { NS_NNTPPROTOCOLHANDLER_CONTRACTID, &kNS_NNTPSERVICE_CID },
+ { NS_CONTENT_HANDLER_CONTRACTID_PREFIX"x-application-newsgroup", &kNS_NNTPSERVICE_CID },
+ { NS_CONTENT_HANDLER_CONTRACTID_PREFIX"x-application-newsgroup-listids", &kNS_NNTPSERVICE_CID },
+ { NS_NEWSFOLDERRESOURCE_CONTRACTID, &kNS_NEWSFOLDERRESOURCE_CID },
+ { NS_NNTPINCOMINGSERVER_CONTRACTID, &kNS_NNTPINCOMINGSERVER_CID },
+ { NS_NNTPNEWSGROUPPOST_CONTRACTID, &kNS_NNTPNEWSGROUPPOST_CID },
+ { NS_NNTPNEWSGROUPLIST_CONTRACTID, &kNS_NNTPNEWSGROUPLIST_CID },
+ { NS_NNTPARTICLELIST_CONTRACTID, &kNS_NNTPARTICLELIST_CID },
+ { NS_NEWSDOWNLOADDIALOGARGS_CONTRACTID, &kNS_NEWSDOWNLOADDIALOGARGS_CID },
+ // Mail View Entries
+ { NS_MSGMAILVIEWLIST_CONTRACTID, &kNS_MSGMAILVIEWLIST_CID },
+ // mdn Entries
+ { NS_MSGMDNGENERATOR_CONTRACTID, &kNS_MSGMDNGENERATOR_CID },
+ // SMime Entries
+ { NS_MSGCOMPOSESECURE_CONTRACTID, &kNS_MSGCOMPOSESECURE_CID },
+ { NS_MSGSMIMECOMPFIELDS_CONTRACTID, &kNS_MSGSMIMECOMPFIELDS_CID },
+ { NS_SMIMEJSHELPER_CONTRACTID, &kNS_SMIMEJSJELPER_CID },
+ { NS_SMIMEENCRYPTURISERVICE_CONTRACTID, &kNS_SMIMEENCRYPTURISERVICE_CID },
+ { NS_CMSSECUREMESSAGE_CONTRACTID, &kNS_CMSSECUREMESSAGE_CID },
+ { NS_CMSDECODER_CONTRACTID, &kNS_CMSDECODER_CID },
+ { NS_CMSENCODER_CONTRACTID, &kNS_CMSENCODER_CID },
+ { NS_CMSMESSAGE_CONTRACTID, &kNS_CMSMESSAGE_CID },
+ { NS_CERTPICKDIALOGS_CONTRACTID, &kNS_CERT_PICKER_CID },
+ { NS_CERT_PICKER_CONTRACTID, &kNS_CERT_PICKER_CID },
+ // Vcard Entries
+ { "@mozilla.org/mimecth;1?type=text/x-vcard", &kNS_VCARD_CONTENT_TYPE_HANDLER_CID },
+ // PGP/MIME Entries
+ { "@mozilla.org/mimecth;1?type=multipart/encrypted", &kNS_PGPMIME_CONTENT_TYPE_HANDLER_CID },
+ { NS_PGPMIMEPROXY_CONTRACTID, &kNS_PGPMIMEPROXY_CID },
+ // i18n Entries
+ { NS_CHARSETCONVERTERMANAGER_CONTRACTID, &kNS_ICHARSETCONVERTERMANAGER_CID },
+ { NS_UNICODEDECODER_CONTRACTID_BASE "UTF-7", &kNS_UTF7TOUNICODE_CID },
+ { NS_UNICODEDECODER_CONTRACTID_BASE "x-imap4-modified-utf7", &kNS_MUTF7TOUNICODE_CID },
+ { NS_UNICODEENCODER_CONTRACTID_BASE "UTF-7", &kNS_UNICODETOUTF7_CID },
+ { NS_UNICODEENCODER_CONTRACTID_BASE "x-imap4-modified-utf7", &kNS_UNICODETOMUTF7_CID },
+ // Tokenizer Entries
+ { NULL }
+};
+
+static const mozilla::Module::CategoryEntry kMailNewsCategories[] = {
+ // MailNews Base Entries
+ { XPCOM_DIRECTORY_PROVIDER_CATEGORY, "mail-directory-provider", NS_MAILDIRPROVIDER_CONTRACTID },
+ { "content-policy", NS_MSGCONTENTPOLICY_CONTRACTID, NS_MSGCONTENTPOLICY_CONTRACTID },
+ MAILNEWSDLF_CATEGORIES
+#ifdef XP_MACOSX
+ { "app-startup", NS_MESSENGEROSINTEGRATION_CONTRACTID, "service," NS_MESSENGEROSINTEGRATION_CONTRACTID}
+,
+#endif
+ // Address Book Entries
+ { "command-line-handler", "m-addressbook", NS_ABMANAGERSTARTUPHANDLER_CONTRACTID },
+ // Bayesian Filter Entries
+ // Compose Entries
+ { "command-line-handler", "m-compose", NS_MSGCOMPOSESTARTUPHANDLER_CONTRACTID },
+ // JsAccount Entries
+ // Imap Entries
+ // Local Entries
+ // msgdb Entries
+ // Mime Entries
+ { "mime-emitter", NS_HTML_MIME_EMITTER_CONTRACTID, NS_HTML_MIME_EMITTER_CONTRACTID },
+ { "mime-emitter", NS_XML_MIME_EMITTER_CONTRACTID, NS_XML_MIME_EMITTER_CONTRACTID },
+ { "mime-emitter", NS_PLAIN_MIME_EMITTER_CONTRACTID, NS_PLAIN_MIME_EMITTER_CONTRACTID },
+ { "mime-emitter", NS_RAW_MIME_EMITTER_CONTRACTID, NS_RAW_MIME_EMITTER_CONTRACTID },
+ // News Entries
+ { "command-line-handler", "m-news", NS_NEWSSTARTUPHANDLER_CONTRACTID },
+ // Mail View Entries
+ // mdn Entries
+ // i18n Entries
+ NS_UCONV_REG_UNREG("UTF-7", NS_UTF7TOUNICODE_CID, NS_UNICODETOUTF7_CID)
+ NS_UCONV_REG_UNREG("x-imap4-modified-utf7", NS_MUTF7TOUNICODE_CID, NS_UNICODETOMUTF7_CID)
+ // Tokenizer Entries
+ { NULL }
+};
+
+static void
+msgMailNewsModuleDtor()
+{
+ nsAddrDatabase::CleanupCache();
+}
+
+static const mozilla::Module kMailNewsModule = {
+ mozilla::Module::kVersion,
+ kMailNewsCIDs,
+ kMailNewsContracts,
+ kMailNewsCategories,
+ NULL,
+ NULL,
+ msgMailNewsModuleDtor
+};
+
+NSMODULE_DEFN(nsMailModule) = &kMailNewsModule;
+
diff --git a/mailnews/compose/content/askSendFormat.js b/mailnews/compose/content/askSendFormat.js
new file mode 100644
index 000000000..17f4bafe4
--- /dev/null
+++ b/mailnews/compose/content/askSendFormat.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gParam = null;
+
+/**
+ * This dialog should be opened with arguments like e.g.
+ * {action: nsIMsgCompSendFormat.AskUser, convertible: nsIMsgCompConvertible.Yes}
+ */
+function Startup()
+{
+ gParam = window.arguments[0];
+
+ const msgCompSendFormat = Components.interfaces.nsIMsgCompSendFormat;
+ const msgCompConvertible = Components.interfaces.nsIMsgCompConvertible;
+
+ var bundle = document.getElementById("askSendFormatStringBundle");
+
+ // If the user hits the close box, we will abort.
+ gParam.abort = true;
+
+ // Set the question label
+ var mailSendFormatExplanation = document.getElementById("mailSendFormatExplanation");
+ var icon = document.getElementById("convertDefault");
+
+ switch (gParam.convertible)
+ {
+ case msgCompConvertible.Altering:
+ mailSendFormatExplanation.textContent = bundle.getString("convertibleAltering");
+ icon.className = "question-icon";
+ break;
+ case msgCompConvertible.No:
+ mailSendFormatExplanation.textContent = bundle.getString("convertibleNo");
+ icon.className = "alert-icon";
+ break;
+ default: // msgCompConvertible.Yes
+ mailSendFormatExplanation.textContent = bundle.getString("convertibleYes");
+ // XXX change this to use class message-icon once bug 512173 is fixed
+ icon.className = "question-icon";
+ break;
+ }
+
+ // Set the default radio array value and recommendation.
+ var group = document.getElementById("mailDefaultHTMLAction");
+ if (gParam.action != msgCompSendFormat.AskUser)
+ {
+ group.value = gParam.action;
+ group.selectedItem.label += " " + bundle.getString("recommended");
+ }
+}
+
+function Send()
+{
+ // gParam.action should be an integer for when it is returned to MsgComposeCommands.js
+ gParam.action = parseInt(document.getElementById("mailDefaultHTMLAction").value);
+ gParam.abort = false;
+}
diff --git a/mailnews/compose/content/askSendFormat.xul b/mailnews/compose/content/askSendFormat.xul
new file mode 100644
index 000000000..751378bd8
--- /dev/null
+++ b/mailnews/compose/content/askSendFormat.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/messengercompose/askSendFormat.dtd">
+
+<dialog id="askSendFormat"
+ title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttonpack="center"
+ onload="Startup();"
+ ondialogaccept="Send();"
+ buttonlabelaccept="&send.label;"
+ buttonaccesskeyaccept="&send.accesskey;"
+ style="width: 75ch;">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/messengercompose/askSendFormat.js"/>
+
+ <stringbundle id="askSendFormatStringBundle"
+ src="chrome://messenger/locale/messengercompose/askSendFormat.properties"/>
+
+ <separator class="thin"/>
+ <hbox>
+ <separator orient="vertical"/>
+ <vbox id="askImageBox">
+ <image id="convertDefault"/>
+ </vbox>
+ <separator orient="vertical"/>
+ <vbox flex="1">
+ <description>&recipient.label;</description>
+ <description id="mailSendFormatExplanation"/>
+ <description>&question.label;</description>
+ <separator/>
+ <radiogroup id="mailDefaultHTMLAction">
+ <radio value="3" label="&plainTextAndHtml.label;" accesskey="&plainTextAndHtml.accesskey;"/>
+ <radio value="1" selected="true"
+ label="&plainTextOnly.label;" accesskey="&plainTextOnly.accesskey;"/>
+ <radio value="2" label="&htmlOnly.label;" accesskey="&htmlOnly.accesskey;"/>
+ </radiogroup>
+ </vbox>
+ </hbox>
+</dialog>
diff --git a/mailnews/compose/content/mailComposeEditorOverlay.xul b/mailnews/compose/content/mailComposeEditorOverlay.xul
new file mode 100644
index 000000000..4304935e1
--- /dev/null
+++ b/mailnews/compose/content/mailComposeEditorOverlay.xul
@@ -0,0 +1,157 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE window SYSTEM "chrome://messenger/locale/messengercompose/mailComposeEditorOverlay.dtd" >
+
+<overlay id="mailComposeEditorOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript">
+ <![CDATA[
+ Components.utils.import("resource://gre/modules/Services.jsm");
+
+ var gMsgCompProcessLink = false;
+ var gMsgCompInputElement = null;
+ var gMsgCompPrevInputValue = null;
+ var gMsgCompPrevMozDoNotSendAttribute;
+ var gMsgCompAttachSourceElement = null;
+
+ function OnLoadOverlay()
+ {
+ gMsgCompAttachSourceElement = document.getElementById("AttachSourceToMail");
+ var editor = GetCurrentEditor();
+ if (gMsgCompAttachSourceElement && editor &&
+ (editor.flags & Components.interfaces.nsIPlaintextEditor.eEditorMailMask))
+ {
+ SetRelativeCheckbox = function() { SetAttachCheckbox();};
+ //initialize the AttachSourceToMail checkbox
+ gMsgCompAttachSourceElement.hidden = false;
+
+ switch (document.documentElement.id)
+ {
+ case "imageDlg":
+ gMsgCompInputElement = gDialog.srcInput;
+ gMsgCompProcessLink = false;
+ break;
+ case "linkDlg" :
+ gMsgCompInputElement = gDialog.hrefInput;
+ gMsgCompProcessLink = true;
+ break;
+ }
+ if (gMsgCompInputElement)
+ {
+ SetAttachCheckbox();
+ gMsgCompPrevMozDoNotSendAttribute = globalElement.getAttribute("moz-do-not-send")
+ }
+ }
+ }
+ addEventListener("load", OnLoadOverlay, false);
+
+ function OnAcceptOverlay()
+ {
+ // Auto-convert file URLs to data URLs. If we're in the link properties
+ // dialog convert only when requested - for the image dialog do it always.
+ if (/^file:/i.test(gMsgCompInputElement.value.trim()) &&
+ (gMsgCompAttachSourceElement.checked || !gMsgCompProcessLink)) {
+ var dataURI = GenerateDataURL(gMsgCompInputElement.value.trim());
+ gMsgCompInputElement.value = dataURI;
+ gMsgCompAttachSourceElement.checked = true;
+ }
+ DoAttachSourceCheckbox();
+ }
+ addEventListener("dialogaccept", OnAcceptOverlay, false);
+
+ function SetAttachCheckbox()
+ {
+ var resetCheckbox = false;
+ var mozDoNotSend = globalElement.getAttribute("moz-do-not-send");
+
+ //In case somebody played with the advanced property and changed the moz-do-not-send attribute
+ if (mozDoNotSend != gMsgCompPrevMozDoNotSendAttribute)
+ {
+ gMsgCompPrevMozDoNotSendAttribute = mozDoNotSend;
+ resetCheckbox = true;
+ }
+
+ // Has the URL changed
+ if (gMsgCompInputElement && gMsgCompInputElement.value != gMsgCompPrevInputValue)
+ {
+ gMsgCompPrevInputValue = gMsgCompInputElement.value;
+ resetCheckbox = true;
+ }
+
+ if (gMsgCompInputElement && resetCheckbox)
+ {
+ // Here is the rule about how to set the checkbox Attach Source To Message:
+ // If the attribute "moz-do-not-send" has not been set, we look at the scheme of the URL
+ // and at some preference to decide what is the best for the user.
+ // If it is set to "false", the checkbox is checked, otherwise unchecked.
+ var attach = false;
+ if (mozDoNotSend == null)
+ {
+ // We haven't yet set the "moz-do-not-send" attribute.
+ var inputValue = gMsgCompInputElement.value.trim();
+ if (/^(file|data):/i.test(inputValue)) {
+ // For files or data URLs, default to attach them.
+ attach = true;
+ } else if (!gMsgCompProcessLink && // Implies image dialogue.
+ /^https?:/i.test(inputValue)) {
+ // For images loaded via http(s) we default to the preference value.
+ attach = Services.prefs.getBoolPref("mail.compose.attach_http_images");
+ }
+ }
+ else
+ {
+ attach = (mozDoNotSend == "false");
+ }
+
+ gMsgCompAttachSourceElement.checked = attach;
+ }
+ }
+
+ function DoAttachSourceCheckbox()
+ {
+ gMsgCompPrevMozDoNotSendAttribute = (!gMsgCompAttachSourceElement.checked).toString();
+ globalElement.setAttribute("moz-do-not-send", gMsgCompPrevMozDoNotSendAttribute);
+ }
+
+ function GenerateDataURL(url) {
+ var file = Services.io.newURI(url, null, null)
+ .QueryInterface(Components.interfaces.nsIFileURL).file;
+ var contentType = Components.classes["@mozilla.org/mime;1"]
+ .getService(Components.interfaces.nsIMIMEService)
+ .getTypeFromFile(file);
+ var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ inputStream.init(file, 0x01, 0o600, 0);
+ var stream = Components.classes["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryInputStream);
+ stream.setInputStream(inputStream);
+ let data = "";
+ while (stream.available() > 0) {
+ data += stream.readBytes(stream.available());
+ }
+ let encoded = btoa(data);
+ stream.close();
+ return "data:" + contentType +
+ ";filename=" + encodeURIComponent(file.leafName) +
+ ";base64," + encoded;
+ }
+ ]]>
+ </script>
+
+ <hbox id="MakeRelativeHbox">
+ <checkbox id="AttachSourceToMail" hidden="true"
+ label="&attachImageSource.label;" accesskey="&attachImageSource.accesskey;"
+ insertafter="MakeRelativeCheckbox" oncommand="DoAttachSourceCheckbox()"/>
+ </hbox>
+
+ <groupbox id="LinkURLBox">
+ <checkbox id="AttachSourceToMail" hidden="true"
+ label="&attachLinkSource.label;" accesskey="&attachLinkSource.accesskey;"
+ insertafter="LinkLocationBox" oncommand="DoAttachSourceCheckbox()"/>
+ </groupbox>
+
+</overlay>
diff --git a/mailnews/compose/content/menulistCompactBindings.xml b/mailnews/compose/content/menulistCompactBindings.xml
new file mode 100644
index 000000000..5ca277347
--- /dev/null
+++ b/mailnews/compose/content/menulistCompactBindings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="menulistCompactBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="menulist-compact" display="xul:menu"
+ extends="chrome://global/content/bindings/menulist.xml#menulist">
+ <content sizetopopup="false">
+ <xul:hbox class="menulist-label-box" flex="1">
+ <xul:image class="menulist-icon" xbl:inherits="src"/>
+ <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
+ </xul:hbox>
+ <children includes="menupopup"/>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/mailnews/compose/content/sendProgress.js b/mailnews/compose/content/sendProgress.js
new file mode 100644
index 000000000..8354cf953
--- /dev/null
+++ b/mailnews/compose/content/sendProgress.js
@@ -0,0 +1,171 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var nsIMsgCompDeliverMode = Components.interfaces.nsIMsgCompDeliverMode;
+
+// dialog is just an array we'll use to store various properties from the dialog document...
+var dialog;
+
+// the msgProgress is a nsIMsgProgress object
+var msgProgress = null;
+
+// random global variables...
+var itsASaveOperation = false;
+var gSendProgressStringBundle;
+
+// all progress notifications are done through the nsIWebProgressListener implementation...
+var progressListener = {
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_START)
+ {
+ // Put progress meter in undetermined mode.
+ dialog.progress.setAttribute("mode", "undetermined");
+ }
+
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
+ {
+ // we are done sending/saving the message...
+ // Indicate completion in status area.
+ var msg;
+ if (itsASaveOperation)
+ msg = gSendProgressStringBundle.getString("messageSaved");
+ else
+ msg = gSendProgressStringBundle.getString("messageSent");
+ dialog.status.setAttribute("value", msg);
+
+ // Put progress meter at 100%.
+ dialog.progress.setAttribute("value", 100);
+ dialog.progress.setAttribute("mode", "normal");
+ var percentMsg = gSendProgressStringBundle.getFormattedString("percentMsg", [100]);
+ dialog.progressText.setAttribute("value", percentMsg);
+
+ window.close();
+ }
+ },
+
+ onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
+ {
+ // Calculate percentage.
+ var percent;
+ if (aMaxTotalProgress > 0)
+ {
+ percent = Math.round(aCurTotalProgress / aMaxTotalProgress * 100);
+ if (percent > 100)
+ percent = 100;
+
+ dialog.progress.removeAttribute("mode");
+
+ // Advance progress meter.
+ dialog.progress.setAttribute("value", percent);
+
+ // Update percentage label on progress meter.
+ var percentMsg = gSendProgressStringBundle.getFormattedString("percentMsg", [percent]);
+ dialog.progressText.setAttribute("value", percentMsg);
+ }
+ else
+ {
+ // Progress meter should be barber-pole in this case.
+ dialog.progress.setAttribute("mode", "undetermined");
+ // Update percentage label on progress meter.
+ dialog.progressText.setAttribute("value", "");
+ }
+ },
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ // we can ignore this notification
+ },
+
+ onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ if (aMessage != "")
+ dialog.status.setAttribute("value", aMessage);
+ },
+
+ onSecurityChange: function(aWebProgress, aRequest, state)
+ {
+ // we can ignore this notification
+ },
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Components.interfaces.nsIWebProgressListener) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw Components.results.NS_NOINTERFACE;
+ }
+};
+
+function onLoad()
+{
+ // Set global variables.
+ let subject = "";
+ gSendProgressStringBundle = document.getElementById("sendProgressStringBundle");
+
+ msgProgress = window.arguments[0];
+ if (!msgProgress)
+ {
+ Components.utils.reportError("Invalid argument to sendProgress.xul.");
+ window.close();
+ return;
+ }
+
+ if (window.arguments[1])
+ {
+ let progressParams = window.arguments[1].QueryInterface(Components.interfaces.nsIMsgComposeProgressParams);
+ if (progressParams)
+ {
+ itsASaveOperation = (progressParams.deliveryMode != nsIMsgCompDeliverMode.Now);
+ subject = progressParams.subject;
+ }
+ }
+
+ if (subject) {
+ let title = itsASaveOperation ? "titleSaveMsgSubject" : "titleSendMsgSubject";
+ document.title = gSendProgressStringBundle.getFormattedString(title, [subject]);
+ } else {
+ let title = itsASaveOperation ? "titleSaveMsg" : "titleSendMsg";
+ document.title = gSendProgressStringBundle.getString(title);
+ }
+
+ dialog = {};
+ dialog.status = document.getElementById("dialog.status");
+ dialog.progress = document.getElementById("dialog.progress");
+ dialog.progressText = document.getElementById("dialog.progressText");
+
+ // set our web progress listener on the helper app launcher
+ msgProgress.registerListener(progressListener);
+}
+
+function onUnload()
+{
+ if (msgProgress)
+ {
+ try
+ {
+ msgProgress.unregisterListener(progressListener);
+ msgProgress = null;
+ } catch (e) {}
+ }
+}
+
+// If the user presses cancel, tell the app launcher and close the dialog...
+function onCancel()
+{
+ // Cancel app launcher.
+ try
+ {
+ msgProgress.processCanceledByUser = true;
+ } catch (e)
+ {
+ return true;
+ }
+
+ // don't Close up dialog by returning false, the backend will close the dialog when everything will be aborted.
+ return false;
+}
diff --git a/mailnews/compose/content/sendProgress.xul b/mailnews/compose/content/sendProgress.xul
new file mode 100644
index 000000000..45508387f
--- /dev/null
+++ b/mailnews/compose/content/sendProgress.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/messengercompose/sendProgress.dtd">
+
+<dialog id="sendProgress"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&sendDialog.title;"
+ style="width: 56ch;"
+ onload="onLoad();"
+ onunload="onUnload();"
+ buttons="cancel"
+ ondialogcancel="return onCancel();">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/messengercompose/sendProgress.js"/>
+ <stringbundle id="sendProgressStringBundle"
+ src="chrome://messenger/locale/messengercompose/sendProgress.properties"/>
+
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row>
+ <hbox pack="end">
+ <label value="&status.label;"/>
+ </hbox>
+ <label id="dialog.status" crop="center"/>
+ </row>
+ <row class="thin-separator">
+ <hbox pack="end">
+ <label value="&progress.label;"/>
+ </hbox>
+ <progressmeter id="dialog.progress" mode="normal" value="0"/>
+ <hbox pack="end">
+ <label id="dialog.progressText"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+
+</dialog>
diff --git a/mailnews/compose/moz.build b/mailnews/compose/moz.build
new file mode 100644
index 000000000..023ca2fba
--- /dev/null
+++ b/mailnews/compose/moz.build
@@ -0,0 +1,10 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'public',
+ 'src',
+]
+
diff --git a/mailnews/compose/public/moz.build b/mailnews/compose/public/moz.build
new file mode 100644
index 000000000..038ebfd60
--- /dev/null
+++ b/mailnews/compose/public/moz.build
@@ -0,0 +1,35 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIMsgAttachment.idl',
+ 'nsIMsgAttachmentHandler.idl',
+ 'nsIMsgCompFields.idl',
+ 'nsIMsgCompose.idl',
+ 'nsIMsgComposeParams.idl',
+ 'nsIMsgComposeProgressParams.idl',
+ 'nsIMsgComposeSecure.idl',
+ 'nsIMsgComposeService.idl',
+ 'nsIMsgCompUtils.idl',
+ 'nsIMsgQuote.idl',
+ 'nsIMsgQuotingOutputStreamListener.idl',
+ 'nsIMsgSend.idl',
+ 'nsIMsgSendLater.idl',
+ 'nsIMsgSendLaterListener.idl',
+ 'nsIMsgSendListener.idl',
+ 'nsIMsgSendReport.idl',
+ 'nsISmtpServer.idl',
+ 'nsISmtpService.idl',
+ 'nsISmtpUrl.idl',
+ 'nsIURLFetcher.idl',
+]
+
+XPIDL_MODULE = 'msgcompose'
+
+EXPORTS += [
+ 'nsMsgAttachmentData.h',
+ 'nsMsgCompCID.h',
+]
+
diff --git a/mailnews/compose/public/nsIMsgAttachment.idl b/mailnews/compose/public/nsIMsgAttachment.idl
new file mode 100644
index 000000000..9a9c74859
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgAttachment.idl
@@ -0,0 +1,124 @@
+/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(d17d2d60-ec3a-46de-8bd1-24c77dd9b87b)]
+interface nsIMsgAttachment : nsISupports {
+
+ /**
+ * name attribute
+ *
+ * @Attachment real name, will be sent with the attachment's header.
+ * @If no name has been provided, a name will be generated using the url.
+ */
+ attribute AString name;
+
+ /**
+ * url attribute
+ *
+ * @specify where the attachment live (localy or remotely)
+ */
+ attribute AUTF8String url;
+
+ /**
+ * urlCharset attribute
+ *
+ * @specify the Charset of url (used to convert url to Unicode after
+ * unescaping)
+ */
+ attribute ACString urlCharset;
+
+
+ /**
+ * temporary attribute
+ *
+ * @If set to true, the file pointed by the url will be destroyed when this object is destroyed.
+ * @This is only for local attachment.
+ */
+ attribute boolean temporary;
+
+ /**
+ * Are we storing this attachment via a cloud provider and linking to it?
+ */
+ attribute boolean sendViaCloud;
+
+ /**
+ * Cloud provider account key for this attachment, if any.
+ */
+ attribute ACString cloudProviderKey;
+
+ /**
+ * This allows the compose front end code to put whatever html annotation
+ * it wants for the cloud part, e.g., with expiration time, etc.
+ */
+ attribute AString htmlAnnotation;
+
+ /**
+ * contentLocation attribute
+ *
+ * @Specify the origin url of the attachment, used normally when attaching
+ * a locally saved html document, but also used for cloud files.
+ */
+ attribute ACString contentLocation;
+
+ /**
+ * contentType attribute
+ *
+ * @Specify the content-type of the attachment, this does not include extra content-type parameters. If
+ * @you need to specify extra information, use contentTypeParam, charset, macType or macCreator.
+ * @If ommitted, it will be determined base on either the name, the url or the content of the file.
+ */
+ attribute string contentType;
+
+ /**
+ * contentTypeParam attribute
+ *
+ * @Specify the any content-type parameter (other than the content-type itself, charset, macType or macCreator).
+ * @It will be added to the content-type during the send/save operation.
+ */
+ attribute string contentTypeParam;
+
+ /**
+ * charset attribute
+ *
+ * @Specify the charset of the attachment. It will be added to the content-type during the
+ * @send/save operation
+ * @If omitted, will be determined automatically (if possible).
+ */
+ attribute string charset;
+
+ /**
+ * size attribute
+ *
+ * @Specify the size of the attachment.
+ */
+ attribute int64_t size;
+
+ /**
+ * macType attribute
+ *
+ * @Specify the Mac file type of the attachment. It will be added to the content-type during the
+ * @send/save operation
+ * @If omitted, will be determined automatically on Macintosh OS.
+ */
+ attribute string macType;
+
+ /**
+ * macCreator attribute
+ *
+ * @Specify the Mac file creator of the attachment. It will be added to the content-type during the
+ * @send/save operation
+ * @If omitted, will be determined automatically on Macintosh OS.
+ */
+ attribute string macCreator;
+
+ /**
+ * equalsUrl
+ *
+ * @ determines if both attachments have the same url.
+ */
+ boolean equalsUrl(in nsIMsgAttachment attachment);
+};
diff --git a/mailnews/compose/public/nsIMsgAttachmentHandler.idl b/mailnews/compose/public/nsIMsgAttachmentHandler.idl
new file mode 100644
index 000000000..ac7c4f825
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgAttachmentHandler.idl
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIFile;
+
+// This interface provides minimal XPCONNECT access to objects of type
+// nsMsgAttachmentHandler. This is primarily for use with new account
+// types and JsAccount, so this is probably not the interface that you
+// want if you are working with standard account types.
+
+[scriptable, uuid(1731283c-60fe-4102-a804-622a84cc1a08)]
+interface nsIMsgAttachmentHandler : nsISupports
+{
+ /// The real type, once we know it.
+ readonly attribute ACString type;
+
+ /// URI with link to the attachment.
+ readonly attribute ACString uri;
+
+ /// The temp file to which we save it.
+ readonly attribute nsIFile tmpFile;
+
+ /// The name for the headers, if different from the URL.
+ readonly attribute AUTF8String name;
+
+ /// Size of the attachment, in bytes.
+ readonly attribute unsigned long size;
+
+ /// This is for multipart/related Content-ID's.
+ readonly attribute ACString contentId;
+
+ /// True if this should be sent as a link to a file.
+ readonly attribute boolean sendViaCloud;
+
+ /// Name of the character set for the attachment.
+ readonly attribute ACString charset;
+
+ /// The encoding, once we've decided.
+ readonly attribute ACString encoding;
+
+ /// Whether the attachment has been encoded, for example to base64.
+ readonly attribute boolean alreadyEncoded;
+};
diff --git a/mailnews/compose/public/nsIMsgCompFields.idl b/mailnews/compose/public/nsIMsgCompFields.idl
new file mode 100644
index 000000000..5562457d9
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgCompFields.idl
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgIStructuredHeaders.idl"
+
+interface nsIMsgAttachment;
+interface nsISimpleEnumerator;
+
+/**
+ * A collection of headers and other attributes for building a mail message.
+ */
+[scriptable, uuid(10928477-4F24-4357-9397-FBD847F46F0A)]
+interface nsIMsgCompFields : msgIWritableStructuredHeaders {
+
+ attribute AString from;
+ attribute AString replyTo;
+ attribute AString to;
+ attribute AString cc;
+ attribute AString bcc;
+ readonly attribute bool hasRecipients;
+
+ attribute AString fcc;
+ attribute AString fcc2;
+
+ attribute AString newsgroups;
+ attribute string newspostUrl;
+ attribute AString followupTo;
+
+ attribute AString subject;
+
+ attribute AString organization;
+ attribute string references;
+ attribute string priority;
+ attribute string messageId;
+ attribute string characterSet;
+ readonly attribute string defaultCharacterSet;
+
+ attribute AString templateName;
+ attribute string draftId;
+
+ attribute boolean returnReceipt;
+ attribute long receiptHeaderType;
+ attribute boolean DSN;
+ attribute boolean attachVCard;
+ attribute boolean forcePlainText;
+ attribute boolean useMultipartAlternative;
+ attribute boolean bodyIsAsciiOnly;
+ attribute boolean forceMsgEncoding;
+ /// Status of manually-activated attachment reminder.
+ attribute boolean attachmentReminder;
+ /// Delivery format for the mail being composed
+ /// (auto = 4, text = 1, html = 2, text and html = 3).
+ attribute long deliveryFormat;
+ attribute string contentLanguage;
+ /// This is populated with the key of the identity which created the draft or template.
+ attribute string creatorIdentityKey;
+
+ /**
+ * Beware that when setting this property, your body must be properly wrapped,
+ * and the line endings must match MSG_LINEBREAK, namely "\r\n" on Windows
+ * and "\n" on Linux and OSX.
+ */
+ attribute AString body;
+
+ readonly attribute nsISimpleEnumerator attachments;
+ void addAttachment(in nsIMsgAttachment attachment);
+ void removeAttachment(in nsIMsgAttachment attachment);
+ void removeAttachments();
+
+ /**
+ * This function will split the recipients into an array.
+ *
+ * @param aRecipients The recipients list to split.
+ * @param aEmailAddressOnly Set to true to drop display names from the results
+ * array.
+ * @param aLength The length of the aResult array.
+ * @param aResult An array of the recipients.
+ */
+ void splitRecipients(in AString aRecipients, in boolean aEmailAddressOnly,
+ out unsigned long aLength,
+ [array, size_is(aLength), retval] out wstring aResult);
+
+ void ConvertBodyToPlainText();
+
+ /**
+ * Indicates whether we need to check if the current |DocumentCharset|
+ * can represent all the characters in the message body. It should be
+ * initialized to true and set to false when 'Send Anyway' is selected
+ * by a user. (bug 249530)
+ */
+ attribute boolean needToCheckCharset;
+
+ attribute nsISupports securityInfo;
+};
+
diff --git a/mailnews/compose/public/nsIMsgCompUtils.idl b/mailnews/compose/public/nsIMsgCompUtils.idl
new file mode 100644
index 000000000..6d37a8474
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgCompUtils.idl
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgIdentity.idl"
+
+[scriptable, uuid(00b4569a-077e-4236-b993-980fd82bb948)]
+interface nsIMsgCompUtils : nsISupports {
+ string mimeMakeSeparator(in string prefix);
+ string msgGenerateMessageId(in nsIMsgIdentity identity);
+ readonly attribute boolean msgMimeConformToStandard;
+};
diff --git a/mailnews/compose/public/nsIMsgCompose.idl b/mailnews/compose/public/nsIMsgCompose.idl
new file mode 100644
index 000000000..cc98d2460
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgCompose.idl
@@ -0,0 +1,307 @@
+/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgCompFields.idl"
+#include "nsIMsgComposeParams.idl"
+#include "nsIMsgSendListener.idl"
+
+%{C++
+#include "nsStringGlue.h"
+%}
+
+interface nsIMsgSend;
+interface nsIMsgIdentity;
+interface nsIMsgProgress;
+interface nsIDocShell;
+interface mozIDOMWindowProxy;
+interface nsIEditor;
+interface nsIMsgWindow;
+
+typedef long MSG_ComposeSaveType;
+
+[scriptable, uuid(6953e50a-7531-11d3-85fe-006008948010)]
+interface nsIMsgCompSaveType {
+ const long File = 0;
+ const long Template = 1;
+ const long Draft = 2;
+};
+
+typedef long MSG_DeliverMode;
+
+[scriptable, uuid(a9f27dd7-8f89-4de3-8fbf-41b789c16ee5)]
+interface nsIMsgCompDeliverMode {
+ const long Now = 0;
+ const long Later = 1;
+ const long Save = 2;
+ const long SaveAs = 3;
+ const long SaveAsDraft = 4;
+ const long SaveAsTemplate = 5;
+ const long SendUnsent = 6;
+ const long AutoSaveAsDraft = 7;
+ const long Background = 8;
+};
+
+[scriptable, uuid(f38ea280-e090-11d3-a449-e3153319347c)]
+interface nsIMsgCompSendFormat {
+ const long AskUser = 4; /* Hack: Bug 44512. If this is 0 and passed
+ as results.action to the askSendFormat
+ dialog, the args object gets destroyed.*/
+ const long PlainText = 1;
+ const long HTML = 2;
+ const long Both = 3;
+};
+
+[scriptable, uuid(9638af92-1dd1-11b2-bef1-ca5fee0abc62)]
+interface nsIMsgCompConvertible/*ToTXT*/ {
+ const long Plain = 1; // Like 4.x: Only <html>, <p>, <br>, ...
+ const long Yes = 2; // *Minor* alterations of the look: <ol>, <dd>, ...
+ const long Altering = 3; /* Look altered: <strong>, <i>, <h1>, ...
+ Can be expressed in plaintext, but not in
+ the way it looked in the HTML composer. */
+ const long No = 4; /* Will lose data: <font>, ...
+ Really *requires* visual formatting or
+ is not supported by our HTML->TXT converter. */
+ /* The values here have meaning, they are "levels":
+ convertible({a; b}) == max(convertible({a}), convertible({b}))
+ must be true, i.e. the higher value counts. */
+};
+
+[scriptable, uuid(6ce49b2a-07dc-4783-b307-9a355423163f)]
+interface nsIMsgComposeStateListener : nsISupports
+{
+ /* ... */
+ void NotifyComposeFieldsReady();
+ void ComposeProcessDone(in nsresult aResult);
+ void SaveInFolderDone(in string folderName);
+ void NotifyComposeBodyReady();
+};
+
+[scriptable, uuid(061aae23-7e0a-4818-9a15-1b5db3ceb7f4)]
+interface nsIMsgComposeNotificationType
+{
+ const long ComposeFieldsReady = 0;
+ const long ComposeProcessDone = 1;
+ const long SaveInFolderDone = 2;
+ const long ComposeBodyReady = 3;
+};
+
+native nsString(nsString);
+[ref] native nsStringRef(nsString);
+
+[scriptable, uuid(c6544b6b-06dd-43ac-89b5-949d7c81bb7b)]
+interface nsIMsgCompose : nsIMsgSendListener {
+
+ /**
+ * Initializes the msg compose object.
+ *
+ * @param aParams An nsIMsgComposeParams object containing the initial
+ * details for the compose.
+ * @param aWindow The optional window associated with this compose object.
+ * @param aDocShell The optional docShell of the editor element that is used
+ * for composing.
+ */
+ void initialize(in nsIMsgComposeParams aParams,
+ [optional] in mozIDOMWindowProxy aWindow,
+ [optional] in nsIDocShell aDocShell);
+
+ /* ... */
+ void SetDocumentCharset(in string charset);
+
+ /* ... */
+ void RegisterStateListener(in nsIMsgComposeStateListener stateListener);
+
+ /* ... */
+ void UnregisterStateListener(in nsIMsgComposeStateListener stateListener);
+
+ /* ... */
+ void SendMsg(in MSG_DeliverMode deliverMode, in nsIMsgIdentity identity, in string accountKey, in nsIMsgWindow aMsgWindow, in nsIMsgProgress progress);
+
+ /**
+ * After all Compose preparations are complete, send the prepared message to
+ * the server. This exists primarily to allow an override of the sending to
+ * use a non-SMTP method for send.
+ *
+ * @param deliverMode One of the nsIMsgCompDeliverMode values.
+ * @param identity The message identity.
+ * @param accountKey The message account key.
+ */
+ void sendMsgToServer(in MSG_DeliverMode deliverMode,
+ in nsIMsgIdentity identity,
+ in string accountKey);
+
+ /* ... */
+ void CloseWindow();
+
+ /* ... */
+ void abort();
+
+ /* ... */
+ void quoteMessage(in string msgURI);
+
+ /*
+ AttachmentPrettyName will return only the leafName if the it's a file URL.
+ It will also convert the filename to Unicode assuming it's in the file system
+ charset. In case of URL, |charset| parameter will be used in the conversion.
+ This UI utility function should probably go into it's own class
+ */
+ AUTF8String AttachmentPrettyName(in AUTF8String url, in string charset);
+
+ /**
+ * Expand all mailing lists in the relevant compose fields to include the
+ * members of their output. This method will additionally update the
+ * popularity field of cards in the addressing header.
+ */
+ void expandMailingLists();
+
+ /**
+ * Returns how we should send this message in terms of HTML, plaintext, or
+ * both. Note that this method should not be called until after mailing lists
+ * have been expanded if correct results are desired.
+ *
+ * @param aConvertible The convertability of the body (from
+ * nsIMsgCompConvertible).
+ * @return The HTML action to use (from nsIMsgCompSendFormat).
+ */
+ long determineHTMLAction(in long aConvertible);
+
+ /**
+ * The level of "convertibility" of the message body (whole HTML document)
+ * to plaintext.
+ *
+ * @return a value from nsIMsgCompConvertible.
+ */
+ long bodyConvertible();
+
+ /**
+ * The identity currently selected for the message compose object. When set
+ * this may change the signature on a message being composed. Note that
+ * typically SendMsg will be called with the same identity as is set here, but
+ * if it is different the SendMsg version will overwrite this identity.
+ */
+ attribute nsIMsgIdentity identity;
+
+ /* Check if the composing mail headers (and identity) can be converted to a mail charset.
+ */
+ boolean checkCharsetConversion(in nsIMsgIdentity identity, out string fallbackCharset);
+
+ /* The message send object. This is created by default to be the SMTP server
+ * in sendMsgToServer, but if that method is overridden, set the actual
+ * value used here.
+ */
+ attribute nsIMsgSend messageSend;
+
+ /*
+ * Clear the messageSend object to break any circular references
+ */
+ void clearMessageSend();
+
+ /* ... */
+ attribute nsIEditor editor;
+
+ /* ... */
+ readonly attribute mozIDOMWindowProxy domWindow;
+
+ /* ... */
+ readonly attribute nsIMsgCompFields compFields;
+
+ /* ... */
+ readonly attribute boolean composeHTML;
+
+ /* ... */
+ attribute MSG_ComposeType type;
+
+ /* ... */
+ readonly attribute long wrapLength;
+
+ /* by reading this value, you can determine if yes or not the message has been mofified
+ by the user. When you set this value to false, you reset the modification count
+ of the body to 0 (clean).
+ */
+ attribute boolean bodyModified;
+
+ /* The body string is stored as a byte string in comp fields, but is converted to
+ * UTF16 when fetched by GetBody(). Fetch the body without conversion.
+ */
+ readonly attribute ACString bodyRaw;
+
+ /**
+ * Init the editor THIS USED TO BE [noscript]
+ * Now, this is called after editor is created,
+ * which is triggered by loading startup url from JS.
+ * The completion of document loading is detected by observing
+ * the "obs_documentCreated" command
+ */
+ void initEditor(in nsIEditor editor, in mozIDOMWindowProxy contentWindow);
+
+ /* The following functions are for internal use, essentially for the listener */
+
+ /* ... */
+ [noscript] void setCiteReference(in nsString citeReference);
+
+ /* Set the URI of the folder where the message has been saved */
+ attribute string savedFolderURI;
+
+ /* Append the signature defined in the identity to the msgBody */
+ [noscript] void processSignature(in nsIMsgIdentity identity,
+ in boolean aQuoted,
+ inout nsString aMsgBody);
+
+ /* set any reply flags on the original message's folder */
+ [noscript] void processReplyFlags();
+ [noscript] void rememberQueuedDisposition();
+
+ /* ... */
+ [noscript] void convertAndLoadComposeWindow(in nsStringRef aPrefix,
+ in nsStringRef aBuf,
+ in nsStringRef aSignature,
+ in boolean aQuoted,
+ in boolean aHTMLEditor);
+
+ /* Tell the doc state listeners that the doc state has changed
+ * aNotificationType is from nsIMsgComposeNotificationType
+ */
+ [noscript] void notifyStateListeners(in long aNotificationType, in nsresult aResult);
+
+ /* Retreive the progress object */
+ readonly attribute nsIMsgProgress progress;
+
+ /* ... */
+ [noscript] void buildBodyMessageAndSignature();
+
+ /* ... */
+ [noscript] void buildQuotedMessageAndSignature();
+
+ /* ... */
+ [noscript] void getQuotingToFollow(out boolean quotingToFollow);
+
+ readonly attribute string originalMsgURI;
+
+ attribute boolean deleteDraft;
+
+ /* true when the compose window is in the process of inserting quoted content
+ (i.e. via reply, forward inline or a quoting operation) into the document
+ */
+ attribute boolean insertingQuotedContent;
+
+ /* for easier use of nsIMsgSendListener */
+ void addMsgSendListener(in nsIMsgSendListener sendListener);
+
+ /* for easier use of nsIMsgSendListener */
+ void removeMsgSendListener(in nsIMsgSendListener sendListener);
+
+ /// Access during mail-set-sender observer if needed, see nsIMsgCompDeliverMode.
+ readonly attribute MSG_DeliverMode deliverMode;
+
+};
+
+/* send listener interface */
+[scriptable, uuid(ad6ee068-b225-47f9-a50e-8e48440282ca)]
+interface nsIMsgComposeSendListener : nsISupports {
+
+ void setMsgCompose(in nsIMsgCompose msgCompose);
+ void setDeliverMode(in MSG_DeliverMode deliverMode);
+
+};
diff --git a/mailnews/compose/public/nsIMsgComposeParams.idl b/mailnews/compose/public/nsIMsgComposeParams.idl
new file mode 100644
index 000000000..87c15eac3
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgComposeParams.idl
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+#include "nsIMsgIdentity.idl"
+#include "nsIMsgCompFields.idl"
+#include "nsIMsgSendListener.idl"
+
+interface nsIMsgDBHdr;
+typedef long MSG_ComposeType;
+
+[scriptable, uuid(c7035852-7531-11d3-9a73-006008948010)]
+interface nsIMsgCompType {
+ const long New = 0;
+ const long Reply = 1;
+ const long ReplyAll = 2;
+ const long ForwardAsAttachment = 3;
+ const long ForwardInline = 4;
+ const long NewsPost = 5;
+ const long ReplyToSender = 6;
+ const long ReplyToGroup = 7;
+ const long ReplyToSenderAndGroup = 8;
+ const long Draft = 9;
+ const long Template = 10;
+ const long MailToUrl = 11;
+ const long ReplyWithTemplate = 12;
+ const long ReplyToList = 13;
+
+ /**
+ * Will resend the original message keeping the Subject and the body the
+ * same, and will set the Reply-To: header to the sender of the original
+ * message. This gets the redirector "out of the loop" because replies
+ * to the message will go to the original sender. This is not the same
+ * as the Resent mechanism described in section 3.6.6 of RFC 2822, and
+ * so therefore does not use Resent-* headers.
+ */
+ const long Redirect = 14;
+
+ /**
+ * Add this value to a reply type to suppress quoting the current selection
+ * which may not belong to the message being replied to.
+ */
+ const long ReplyIgnoreQuote = 100;
+};
+
+
+typedef long MSG_ComposeFormat;
+
+[scriptable, uuid(a28325e8-7531-11d3-8f1c-006008948010)]
+interface nsIMsgCompFormat {
+ const long Default = 0;
+ const long HTML = 1;
+ const long PlainText = 2;
+ const long OppositeOfDefault = 3;
+};
+
+
+[scriptable, uuid(930895f2-d610-43f4-9e3c-25e1d1fe4143)]
+interface nsIMsgComposeParams : nsISupports {
+ attribute MSG_ComposeType type;
+ attribute MSG_ComposeFormat format;
+ attribute string originalMsgURI;
+ attribute nsIMsgIdentity identity;
+
+ attribute nsIMsgCompFields composeFields;
+ attribute boolean bodyIsLink;
+
+ attribute nsIMsgSendListener sendListener;
+ attribute string smtpPassword;
+ attribute nsIMsgDBHdr origMsgHdr;
+
+ /**
+ * HTML-formatted content to quote in the body of the message.
+ * Set this to get different content than what would normally
+ * appear in the body, e.g. the original message body in a reply.
+ */
+ attribute AUTF8String htmlToQuote;
+};
diff --git a/mailnews/compose/public/nsIMsgComposeProgressParams.idl b/mailnews/compose/public/nsIMsgComposeProgressParams.idl
new file mode 100644
index 000000000..f3bb301bd
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgComposeProgressParams.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+#include "nsIMsgCompose.idl"
+
+[scriptable, uuid(1e0e7c00-3e4c-11d5-9daa-f88d288130fc)]
+interface nsIMsgComposeProgressParams: nsISupports {
+
+ /* message subject */
+ attribute wstring subject;
+
+ /* delivery mode */
+ attribute MSG_DeliverMode deliveryMode;
+};
diff --git a/mailnews/compose/public/nsIMsgComposeSecure.idl b/mailnews/compose/public/nsIMsgComposeSecure.idl
new file mode 100644
index 000000000..20981bea4
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgComposeSecure.idl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMsgSendReport.idl"
+#include "nsISupports.idl"
+
+interface nsIMsgCompFields;
+interface nsIMsgIdentity;
+interface nsIOutputStream;
+
+/* Security interface */
+[scriptable, uuid(245f2adc-410e-4bdb-91e2-a7bb42d61787)]
+interface nsIMsgComposeSecure : nsISupports
+{
+ // requiresCryptoEncapsulation --> returns true if the current message send requires us to go through
+ // some encryption work. In the case of false, you can disregard the compose secure object.
+ boolean requiresCryptoEncapsulation(in nsIMsgIdentity aIdentity, in nsIMsgCompFields aCompFields);
+
+ void beginCryptoEncapsulation(in nsIOutputStream aStream, in string aRecipients, in nsIMsgCompFields aCompFields, in nsIMsgIdentity aIdentity, in nsIMsgSendReport sendReport, in boolean aIsDraft);
+ void finishCryptoEncapsulation(in boolean aAbort, in nsIMsgSendReport sendReport);
+ void mimeCryptoWriteBlock(in string aBuf, in long aLen);
+};
diff --git a/mailnews/compose/public/nsIMsgComposeService.idl b/mailnews/compose/public/nsIMsgComposeService.idl
new file mode 100644
index 000000000..ce8cf4a1b
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgComposeService.idl
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+#include "nsIMsgCompose.idl"
+#include "nsIMsgComposeParams.idl"
+
+interface nsIURI;
+interface nsIDocShell;
+interface nsIMsgWindow;
+interface nsIMsgIdentity;
+interface nsIMsgIncomingServer;
+interface nsIMsgDBHdr;
+
+[scriptable, uuid(041782bf-e523-444b-a268-d90868fd2b50)]
+interface nsIMsgComposeService : nsISupports {
+
+ /* we need a msg window because when we forward inline we may need progress */
+ void OpenComposeWindow(in string msgComposeWindowURL,
+ in nsIMsgDBHdr msgHdr,
+ in string originalMsgURI,
+ in MSG_ComposeType type,
+ in MSG_ComposeFormat format,
+ in nsIMsgIdentity identity,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Open a compose window given a mailto url and (optionally) an identity.
+ *
+ * @param aMsgComposeWindowURL Can be null in most cases. If you have your
+ * own chrome url you want to use in bringing up a
+ * compose window, pass it in here.
+ * @param aURI The mailto url you want to use as the
+ * foundation for the data inside the compose
+ * window.
+ * @param aIdentity An optional identity to send the message from.
+ */
+ void OpenComposeWindowWithURI(in string msgComposeWindowURL,
+ in nsIURI aURI,
+ [optional] in nsIMsgIdentity aIdentity);
+
+ /* ... */
+ void OpenComposeWindowWithParams(in string msgComposeWindowURL, in nsIMsgComposeParams params);
+
+ /**
+ * Creates an nsIMsgCompose instance and initalizes it.
+ *
+ * @param aParams An nsIMsgComposeParams object containing the initial
+ * details for the compose.
+ * @param aWindow The optional window associated with this compose object.
+ * @param aDocShell The optional docShell of the editor element that is used
+ * for composing.
+ */
+ nsIMsgCompose initCompose(in nsIMsgComposeParams aParams,
+ [optional] in mozIDOMWindowProxy aWindow,
+ [optional] in nsIDocShell aDocShell);
+
+ /**
+ * defaultIdentity
+ *
+ * @return the default identity, in case no identity has been setup yet, will return null
+ */
+ readonly attribute nsIMsgIdentity defaultIdentity;
+
+ /* This function is use for debugging purpose only and may go away at anytime without warning */
+ void TimeStamp(in string label, in boolean resetTime);
+
+ /* This attribute is use for debugging purposes for determining whether to PR_LOG or not */
+ readonly attribute boolean logComposePerformance;
+
+ [noscript] boolean determineComposeHTML(in nsIMsgIdentity aIdentity, in MSG_ComposeFormat aFormat);
+
+ /**
+ * given a mailto url, parse the attributes and turn them into a nsIMsgComposeParams object
+ * @return nsIMsgComposeParams which corresponds to the passed in mailto url
+ */
+ nsIMsgComposeParams getParamsForMailto(in nsIURI aURI);
+
+ /**
+ * @{
+ * These constants control how to forward messages in forwardMessage.
+ * kForwardAsDefault uses value of pref "mail.forward_message_mode".
+ */
+ const unsigned long kForwardAsDefault = 0;
+ const unsigned long kForwardAsAttachment = 1;
+ const unsigned long kForwardInline = 2;
+ /** @} */
+
+ /**
+ * Allow filters to automatically forward a message to the given address(es).
+ * @param forwardTo the address(es) to forward to
+ * @param msgHdr the header of the message being replied to
+ * @param msgWindow message window to use
+ * @param server server to use for determining which account to send from
+ * @param aForwardType - How to forward the message one of 3 values:
+ * kForwardAsDefault, kForwardInline, or
+ * kForwardAsAttachment.
+ */
+ void forwardMessage(in AString forwardTo, in nsIMsgDBHdr msgHdr,
+ in nsIMsgWindow msgWindow, in nsIMsgIncomingServer server,
+ in unsigned long aForwardType);
+
+ /**
+ * Allow filters to automatically reply to a message. The reply message is
+ * based on the given template.
+ * @param msgHdr the header of the message being replied to
+ * @param templateUri uri of the template to base ther reply on
+ * @param msgWindow message window to use
+ * @param server server to use for determining which account to send from
+ */
+ void replyWithTemplate(in nsIMsgDBHdr msgHdr, in string templateUri,
+ in nsIMsgWindow msgWindow, in nsIMsgIncomingServer server);
+
+ /**
+ * The docShell of each editor element used for composing should be registered
+ * with this service. docShells passed to initCompose get registered
+ * automatically. The registrations are typically used to get the msgCompose
+ * window when determining what remote content to allow to be displayed.
+ *
+ * @param aDocShell The nsIDocShell of the editor element.
+ * @param aMsgCompose The compose object associated with the compose window
+ */
+ void registerComposeDocShell(in nsIDocShell aDocShell,
+ in nsIMsgCompose aMsgCompose);
+
+ /**
+ * When an editor docShell is being closed, you should
+ * unregister it from this service. nsIMsgCompose normally calls this
+ * automatically for items passed to initCompose.
+ *
+ * @param aDocShell The nsIDocShell of the editor element.
+ */
+ void unregisterComposeDocShell(in nsIDocShell aDocShell);
+
+ /**
+ * For a given docShell, returns the nsIMsgCompose object associated with it.
+ *
+ * @param aDocShell The nsIDocShell of the editor element.
+ *
+ * @return NS_ERROR_FAILURE if we could not find a nsIMsgCompose for
+ * the passed in docShell.
+ */
+ nsIMsgCompose getMsgComposeForDocShell(in nsIDocShell aDocShell);
+};
diff --git a/mailnews/compose/public/nsIMsgQuote.idl b/mailnews/compose/public/nsIMsgQuote.idl
new file mode 100644
index 000000000..9ec330d8e
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgQuote.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+#include "nsIMsgQuotingOutputStreamListener.idl"
+#include "nsIChannel.idl"
+#include "nsIMimeStreamConverter.idl"
+
+interface nsIMsgDBHdr;
+
+[scriptable, uuid(f79b1d55-f546-4ed5-9f75-9428e35c4eff)]
+interface nsIMsgQuote : nsISupports {
+
+ /**
+ * Quote a particular message specified by its URI.
+ *
+ * @param charset optional parameter - if set, force the message to be
+ * quoted using this particular charset
+ */
+ void quoteMessage(in string msgURI, in boolean quoteHeaders,
+ in nsIMsgQuotingOutputStreamListener streamListener,
+ in string charset, in boolean headersOnly,
+ in nsIMsgDBHdr aOrigHdr);
+
+ readonly attribute nsIMimeStreamConverterListener quoteListener;
+ readonly attribute nsIChannel quoteChannel;
+ readonly attribute nsIMsgQuotingOutputStreamListener streamListener;
+};
+
+[scriptable, uuid(1EC75AD9-88DE-11d3-989D-001083010E9B)]
+interface nsIMsgQuoteListener : nsIMimeStreamConverterListener
+{
+ attribute nsIMsgQuote msgQuote;
+};
diff --git a/mailnews/compose/public/nsIMsgQuotingOutputStreamListener.idl b/mailnews/compose/public/nsIMsgQuotingOutputStreamListener.idl
new file mode 100644
index 000000000..ac86361ab
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgQuotingOutputStreamListener.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIMimeHeaders;
+
+[scriptable, uuid(1fe345e6-2428-4a43-a0c6-d2acea0d4da4)]
+interface nsIMsgQuotingOutputStreamListener : nsIStreamListener {
+
+ // The headers are used to fill in the reply's compose fields
+ void setMimeHeaders(in nsIMimeHeaders headers);
+
+};
diff --git a/mailnews/compose/public/nsIMsgSend.idl b/mailnews/compose/public/nsIMsgSend.idl
new file mode 100644
index 000000000..d1bda4abf
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgSend.idl
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * The nsIMsgSend method will create an RFC822 message and send it all in one operation
+ * as well as providing the ability to save disk files for later use. The mode of delivery
+ * can also be specified for the "Send Later", "Drafts" and "Templates" operations. (NOTE:
+ * This method could easily be broken in to a few different calls. Currently, this method
+ * does several functions depending on the arguments passed in, but this could easily lead
+ * to confusion. This is something that very well may change as time allows).
+ */
+#include "nsISupports.idl"
+#include "nsrootidl.idl"
+#include "nsIMsgIdentity.idl"
+#include "nsIMsgCompFields.idl"
+#include "nsIMsgSendListener.idl"
+#include "nsIMsgSendReport.idl"
+#include "domstubs.idl"
+#include "nsIPrompt.idl"
+#include "MailNewsTypes2.idl"
+#include "nsIMsgComposeParams.idl"
+
+interface nsIMsgProgress;
+interface nsIURI;
+interface nsIRequest;
+interface nsIMsgDBHdr;
+interface nsIMsgHdr;
+interface nsIFile;
+interface nsIOutputStream;
+interface nsIMsgComposeSecure;
+interface nsIMsgStatusFeedback;
+interface nsIEditor;
+interface nsIArray;
+interface nsIMsgAttachmentHandler;
+interface mozIDOMWindowProxy;
+
+typedef long nsMsgDeliverMode;
+
+[scriptable, uuid(c658cd1f-dc4a-43c0-911c-c6d3e569ca7e)]
+interface nsIMsgAttachmentData : nsISupports
+{
+ /// The URL to attach.
+ attribute nsIURI url;
+
+ /**
+ * The type to which this document should be
+ * converted. Legal values are NULL, TEXT_PLAIN
+ * and APPLICATION_POSTSCRIPT (which are macros
+ * defined in net.h); other values are ignored.
+ */
+ attribute ACString desiredType;
+
+ /**
+ * The type of the URL if known, otherwise empty. For example, if
+ * you were attaching a temp file which was known to contain HTML data,
+ * you would pass in TEXT_HTML as the realType, to override whatever type
+ * the name of the tmp file might otherwise indicate.
+ */
+ attribute ACString realType;
+
+ /// Goes along with real_type.
+ attribute ACString realEncoding;
+
+ /**
+ * The original name of this document, which will eventually show up in the
+ * Content-Disposition header. For example, if you had copied a document to a
+ * tmp file, this would be the original, human-readable name of the document.
+ */
+ attribute ACString realName;
+ /**
+ * If you put a string here, it will show up as the Content-Description
+ * header. This can be any explanatory text; it's not a file name.
+ */
+ attribute ACString description;
+
+ /// mac-specific info
+ attribute ACString xMacType;
+
+ /// mac-specific info
+ attribute ACString xMacCreator;
+};
+
+/**
+ * When we have downloaded a URL to a tmp file for attaching, this
+ * represents everything we learned about it (and did to it) in the
+ * process.
+ */
+[scriptable, uuid(c552345d-c74b-40b0-a673-79bb461e920b)]
+interface nsIMsgAttachedFile : nsISupports
+{
+ /// Where it came from on the network (or even elsewhere on the local disk.)
+ attribute nsIURI origUrl;
+
+ /// The tmp file in which the (possibly converted) data now resides.
+ attribute nsIFile tmpFile;
+
+ /// The type of the data in file_name (not necessarily the same as the type of orig_url.)
+ attribute ACString type;
+
+ /**
+ * The encoding of the tmp file. This will be set only if the original
+ * document had an encoding already; we don't do base64 encoding and so forth
+ * until it's time to assemble a full MIME message of all parts.
+ */
+ attribute ACString encoding;
+ /// For Content-Description header.
+ attribute ACString description;
+
+ /// X-Mozilla-Cloud-Part, if any.
+ attribute ACString cloudPartInfo;
+
+ attribute ACString xMacType; // mac-specific info
+ attribute ACString xMacCreator; // mac-specific info
+ attribute ACString realName; // The real name of the file.
+
+ /**
+ * Some statistics about the data that was written to the file, so that when
+ * it comes time to compose a MIME message, we can make an informed decision
+ * about what Content-Transfer-Encoding would be best for this attachment.
+ * (If it's encoded already, we ignore this information and ship it as-is.)
+ */
+ attribute unsigned long size;
+ attribute unsigned long unprintableCount;
+ attribute unsigned long highbitCount;
+ attribute unsigned long ctlCount;
+ attribute unsigned long nullCount;
+ attribute unsigned long maxLineLength;
+};
+
+/**
+ * This interface is used by Outlook import to shuttle embedded
+ * image information over to nsIMsgSend's createRFC822Message method via
+ * the aEmbbeddedObjects parameter.
+ */
+[scriptable, uuid(5d2c6554-b4c8-4d68-b864-50e0df929707)]
+interface nsIMsgEmbeddedImageData : nsISupports
+{
+ attribute nsIURI uri;
+ attribute ACString cid;
+ attribute ACString name;
+};
+
+%{ C++
+// Forward declaration
+class nsMsgAttachmentHandler;
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+%}
+
+[ptr] native nsMsgAttachedFile(nsMsgAttachedFile);
+[ptr] native nsMsgAttachmentHandlerArray(nsTArray<RefPtr<nsMsgAttachmentHandler>>);
+
+[scriptable, uuid(747fdfa2-1754-4282-ab26-1e55fd8de13c)]
+interface nsIMsgSend : nsISupports
+{
+ //
+ // This is the primary interface for creating and sending RFC822 messages
+ // in the new architecture. Currently, this method supports many arguments
+ // that change the behavior of the operation. This will change in time to
+ // be separate calls that will be more singluar in nature.
+ //
+ // NOTE: when aEditor is non-null, a multipart related MHTML message will
+ // be created
+ //
+
+ /// Send the message straight away.
+ const nsMsgDeliverMode nsMsgDeliverNow = 0;
+ /**
+ * Queue the message for sending later, but then wait for the user to
+ * request to send it.
+ */
+ const nsMsgDeliverMode nsMsgQueueForLater = 1;
+ const nsMsgDeliverMode nsMsgSave = 2;
+ const nsMsgDeliverMode nsMsgSaveAs = 3;
+ const nsMsgDeliverMode nsMsgSaveAsDraft = 4;
+ const nsMsgDeliverMode nsMsgSaveAsTemplate = 5;
+ const nsMsgDeliverMode nsMsgSendUnsent = 6;
+
+ /// Queue the message in the unsent folder and send it in the background.
+ const nsMsgDeliverMode nsMsgDeliverBackground = 8;
+
+ /**
+ * Create an rfc822 message and send it.
+ * @param aEditor nsIEditor instance that contains message. May be a dummy,
+ * especially in the case of import.
+ * @param aUserIdentity identity to send from.
+ * @param aAccountKey account we're sending message from. May be null.
+ * @param aFields composition fields from addressing widget
+ * @param aIsDigest is this a digest message?
+ * @param aDontDeliver Set to false by the import code - used when we're
+ * trying to create a message from parts.
+ * @param aMode delivery mode
+ * @param aMsgToReplace e.g., when saving a draft over an old draft. May be 0
+ * @param aBodyType content type of message body
+ * @param aBody message body text (should have native line endings)
+ * @param aAttachments Array of nsIMsgAttachmentData
+ * @param aPreloadedAttachments Array of nsIMsgAttachedFile
+ * @param aParentWindow compose window; may be null.
+ * @param aProgress where to send progress info; may be null.
+ * @param aListener optional listener for send progress
+ * @param aPassword optional smtp server password
+ * @param aOriginalMsgURI may be null.
+ * @param aType see nsIMsgComposeParams.idl
+ */
+ void createAndSendMessage(in nsIEditor aEditor,
+ in nsIMsgIdentity aUserIdentity,
+ in string aAccountKey,
+ in nsIMsgCompFields aFields,
+ in boolean aIsDigest,
+ in boolean aDontDeliver,
+ in nsMsgDeliverMode aMode,
+ in nsIMsgDBHdr aMsgToReplace,
+ in string aBodyType,
+ in ACString aBody,
+ in nsIArray aAttachments,
+ in nsIArray aPreloadedAttachments,
+ in mozIDOMWindowProxy aParentWindow,
+ in nsIMsgProgress aProgress,
+ in nsIMsgSendListener aListener,
+ in string aPassword,
+ in AUTF8String aOriginalMsgURI,
+ in MSG_ComposeType aType);
+
+ /**
+ * Creates a file containing an rfc822 message, using the passed information.
+ * aListener's OnStopSending method will get called with the file the message
+ * was stored in. OnStopSending may be called sync or async, depending on
+ * content, so you need to handle both cases.
+ *
+ * @param aUserIdentity The user identity to use for sending this email.
+ * @param aFields An nsIMsgCompFields object containing information
+ * on who to send the message to.
+ * @param aBodyType content type of message body
+ * @param aBody message body text (should have native line endings)
+ * @param aCreateAsDraft If true, this message will be put in a drafts folder
+ * @param aAttachments Array of nsIMsgAttachmentData
+ * @param aEmbeddedObjects Array of nsIDomNode objects for MHTML messages.
+ * Primarily used for embedded images.
+ * Almost all methods can be noops, but
+ * GetNodeValue should return the
+ * cid of the embedded object.
+ * @param aListener listener for msg creation progress and resulting file.
+ */
+ void createRFC822Message(in nsIMsgIdentity aUserIdentity,
+ in nsIMsgCompFields aFields,
+ in string aBodyType,
+ in ACString aBody,
+ in boolean aCreateAsDraft,
+ in nsIArray aAttachments,
+ in nsIArray aEmbeddedObjects,
+ in nsIMsgSendListener aListener);
+
+ /**
+ * Sends a file to the specified composition fields, via the user identity
+ * provided.
+ *
+ * @param aUserIdentity The user identity to use for sending this email.
+ * @param aAccountKey The key of the account that this message relates
+ * to.
+ * @param aFields An nsIMsgCompFields object containing information
+ * on who to send the message to.
+ * @param aSendIFile A reference to the file to send.
+ * @param aDeleteSendFileOnCompletion
+ * Set to true if you want the send file deleted once
+ * the message has been sent.
+ * @param aDigest_p If this is a multipart message, this param
+ * specifies whether the message is in digest or mixed
+ * format.
+ * @param aMode The delivery mode for sending the message (see
+ * above for values).
+ * @param aMsgToReplace A message header representing a message to be
+ * replaced by the one sent, this param may be null.
+ * @param aListener An nsIMsgSendListener to receive feedback on the
+ * current send status. This parameter can also
+ * support the nsIMsgCopyServiceListener interface to
+ * receive notifications of copy finishing e.g. after
+ * saving a message to the sent mail folder.
+ * This param may be null.
+ * @param aStatusFeedback A feedback listener for slightly different feedback
+ * on the message send status. This param may be null.
+ * @param aPassword Pass this in to prevent a dialog if the password
+ * is needed for secure transmission.
+ */
+ void sendMessageFile(in nsIMsgIdentity aUserIdentity,
+ in string aAccountKey,
+ in nsIMsgCompFields aFields,
+ in nsIFile aSendIFile,
+ in boolean aDeleteSendFileOnCompletion,
+ in boolean aDigest_p,
+ in nsMsgDeliverMode aMode,
+ in nsIMsgDBHdr aMsgToReplace,
+ in nsIMsgSendListener aListener,
+ in nsIMsgStatusFeedback aStatusFeedback,
+ in string aPassword
+ );
+
+ /* Abort current send/save operation */
+ void abort();
+
+ /**
+ * Report a send failure.
+ *
+ * @param aFailureCode The failure code of the send operation. See
+ * nsComposeStrings.h for possible values. NS_OK is a possible
+ * value as well; if passed, the function won't prompt the user * but will still about the session.
+ * @param aErrorMsg The appropriate error string for the failure.
+ * @result A modified result value in the case a user action results in
+ * a different way to handle the failure.
+ */
+ nsresult fail(in nsresult aFailureCode, in wstring aErrorMsg);
+
+ /* Disable UI notification (alert message) */
+ void setGUINotificationState(in boolean aEnableFlag);
+
+ /* Crypto */
+ void BeginCryptoEncapsulation();
+
+ /* retreive the last send process report*/
+ readonly attribute nsIMsgSendReport sendReport;
+
+ /* methods for send listener ... */
+ void notifyListenerOnStartSending(in string aMsgID, in unsigned long aMsgSize);
+ void notifyListenerOnProgress(in string aMsgID, in unsigned long aProgress, in unsigned long aProgressMax);
+ void notifyListenerOnStatus(in string aMsgID, in wstring aMsg);
+ void notifyListenerOnStopSending(in string aMsgID, in nsresult aStatus, in wstring aMsg, in nsIFile returnFile);
+ void deliverAsMailExit(in nsIURI aUrl, in nsresult aExitCode);
+ void deliverAsNewsExit(in nsIURI aUrl, in nsresult aExitCode);
+
+ void sendDeliveryCallback(in nsIURI aUrl, in boolean inIsNewsDelivery, in nsresult aExitCode);
+
+ /* methods for copy listener ... */
+ void notifyListenerOnStartCopy();
+ void notifyListenerOnProgressCopy(in unsigned long aProgress, in unsigned long aProgressMax);
+ void notifyListenerOnStopCopy(in nsresult aStatus);
+ void getMessageId(out ACString messageID);
+ /// When saving as draft, the folder uri we saved to.
+ readonly attribute ACString folderUri;
+
+ /**
+ * After a draft is saved, use this to get the mime part number for the dom
+ * node in the editor embedded object list with the passed in index.
+ *
+ * @param aDomIndex - index in the editor dom embedded object list of
+ * the part we're interested in. These are generally images.
+ *
+ * @return the mime part number for that object.
+ */
+ ACString getPartForDomIndex(in long aDomIndex);
+
+ attribute nsMsgKey messageKey;
+
+ nsIPrompt getDefaultPrompt();
+
+ /* process attachment */
+ void gatherMimeAttachments();
+ readonly attribute boolean processAttachmentsSynchronously;
+ [noscript] nsMsgAttachmentHandlerArray getAttachmentHandlers();
+ readonly attribute unsigned long attachmentCount;
+ attribute unsigned long pendingAttachmentCount;
+ readonly attribute nsMsgDeliverMode deliveryMode;
+
+ nsIMsgProgress getProgress();
+
+ nsIOutputStream getOutputStream();
+
+ attribute nsIRequest runningRequest;
+
+ attribute nsresult status;
+
+ attribute nsIMsgComposeSecure cryptoclosure;
+
+ /// Access the local copy of the composition fields.
+ readonly attribute nsIMsgCompFields sendCompFields;
+
+ /// The message body.
+ readonly attribute AString sendBody;
+
+ /// The type of the message body (typically text/plain or text/html).
+ readonly attribute ACString sendBodyType;
+
+ /// The identity to use to send the message.
+ readonly attribute nsIMsgIdentity identity;
+
+ /**
+ * Get a handler for an attachment by its index.
+ * The lifetime of the attachment is dependent on the existence
+ * of the underlying send object, so do not hold onto these
+ * attachment handlers.
+ *
+ * @param index Index used to specify a particular attachment.
+ *
+ * @return Attachment handler with information about the attachment.
+ */
+ nsIMsgAttachmentHandler getAttachment(in unsigned long index);
+
+ /// The folder name to which the message will be saved,
+ /// used by error reporting.
+ attribute AString savedToFolderName;
+
+ /// Should we deliver this message (versus saving as a file)?
+ attribute boolean dontDeliver;
+
+};
diff --git a/mailnews/compose/public/nsIMsgSendLater.idl b/mailnews/compose/public/nsIMsgSendLater.idl
new file mode 100644
index 000000000..524bb867f
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgSendLater.idl
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIMsgStatusFeedback;
+interface nsIMsgIdentity;
+interface nsIMsgSendLaterListener;
+interface nsIMsgFolder;
+
+/**
+ * nsIMsgSendLater is a service used for sending messages in the background.
+ * Messages should be saved to an identity's unsent messages folder, and then
+ * can be sent by calling sendUnsentMessages.
+ *
+ * Although the service supports passing identities as parameters, until bug
+ * 317803 is fixed, all identities use the same folder, and hence the option
+ * currently doesn't work.
+ */
+[scriptable, uuid(fa324a4b-4b87-4e9a-a3c0-af9071a358df)]
+interface nsIMsgSendLater : nsIStreamListener
+{
+ /// Used to obtain status feedback for when messages are sent.
+ attribute nsIMsgStatusFeedback statusFeedback;
+
+ /**
+ * Sends any unsent messages in the identity's unsent messages folder.
+ *
+ * @param aIdentity The identity to send messages for.
+ */
+ void sendUnsentMessages(in nsIMsgIdentity aIdentity);
+
+ /**
+ * Adds an listener to the service to receive notifications.
+ *
+ * @param aListener The listener to add.
+ */
+ void addListener(in nsIMsgSendLaterListener aListener);
+
+ /**
+ * Removes a listener from the service.
+ *
+ * @param aListener The listener to remove.
+ * @exception NS_ERROR_INVALID_ARG If the listener was not already added to
+ * the service.
+ */
+ void removeListener(in nsIMsgSendLaterListener aListener);
+
+ /**
+ * Returns the unsent messages folder for the identity.
+ */
+ nsIMsgFolder getUnsentMessagesFolder(in nsIMsgIdentity userIdentity);
+
+ /**
+ * Returns true if there are any unsent messages to send.
+ *
+ * @param aIdentity The identity whose folder to check for unsent messages.
+ * If not specified, all unsent message folders are checked.
+ */
+ boolean hasUnsentMessages([optional] in nsIMsgIdentity aIdentity);
+
+ /// Returns true if the service is currently sending messages.
+ readonly attribute boolean sendingMessages;
+};
diff --git a/mailnews/compose/public/nsIMsgSendLaterListener.idl b/mailnews/compose/public/nsIMsgSendLaterListener.idl
new file mode 100644
index 000000000..6e6e29a79
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgSendLaterListener.idl
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgDBHdr;
+interface nsIMsgIdentity;
+
+/**
+ * Implement this interface and add to nsIMsgSendLater to receive notifications
+ * of send later actions.
+ */
+[scriptable, uuid(a7bc603b-d0da-4959-a82f-4b99c138b9f4)]
+interface nsIMsgSendLaterListener : nsISupports {
+ /**
+ * Notify the observer that the operation of sending messages later has
+ * started.
+ *
+ * @param aTotalMessageCount Number of messages to be sent. This will not
+ * change over the time we are doing this sequence.
+ */
+ void onStartSending(in unsigned long aTotalMessageCount);
+
+ /**
+ * Notify the observer that the next message send/copy is starting and
+ * provide details about the message.
+ *
+ * @param aCurrentMessage The current message number that is being sent.
+ * @param aTotalMessageCount The total number of messages that we are
+ * trying to send.
+ * @param aMessageHeader The header information for the message that is
+ * being sent.
+ * @param aMessageIdentity The identity being used to send the message.
+ */
+ void onMessageStartSending(in unsigned long aCurrentMessage,
+ in unsigned long aTotalMessageCount,
+ in nsIMsgDBHdr aMessageHeader,
+ in nsIMsgIdentity aIdentity);
+
+ /**
+ * Notify the observer of the current progress of sending a message. The one
+ * function covers sending the message over the network and copying to the
+ * appropriate sent folder.
+ *
+ * @param aCurrentMessage The current message number that is being sent.
+ * @param aTotalMessageCount The total number of messages that we are
+ * trying to send.
+ * @param aMessageSendPercent The percentage of the message sent (0 to 100)
+ * @param aMessageCopyPercent The percentage of the copy completed (0 to
+ * 100). If there is no copy for this message,
+ * this may be set to 100 at the same time as
+ * aMessageSendPercent.
+ */
+ void onMessageSendProgress(in unsigned long aCurrentMessage,
+ in unsigned long aTotalMessageCount,
+ in unsigned long aMessageSendPercent,
+ in unsigned long aMessageCopyPercent);
+
+ /**
+ * Notify the observer of an error in the send message later function.
+ *
+ * @param aCurrentMessage The current message number that is being sent.
+ * @param aMessageHeader The header information for the message that is
+ * being sent.
+ * @param aStatus The error status code.
+ * @param aMsg A text string describing the error.
+ */
+ void onMessageSendError(in unsigned long aCurrentMessage,
+ in nsIMsgDBHdr aMessageHeader,
+ in nsresult aStatus,
+ in wstring aMsg);
+
+ /**
+ * Notify the observer that the send unsent messages operation has finished.
+ * This is called regardless of the success/failure of the operation.
+ *
+ * @param aStatus Status code for the message send.
+ * @param aMsg A text string describing the error.
+ * @param aTotalTried Total number of messages that were attempted to be sent.
+ * @param aSuccessful How many messages were successfully sent.
+ */
+ void onStopSending(in nsresult aStatus, in wstring aMsg,
+ in unsigned long aTotalTried, in unsigned long aSuccessful);
+};
diff --git a/mailnews/compose/public/nsIMsgSendListener.idl b/mailnews/compose/public/nsIMsgSendListener.idl
new file mode 100644
index 000000000..4facebc05
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgSendListener.idl
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(D34DC178-5E78-45E8-8658-A8F52D9CCF5F)]
+interface nsIMsgSendListener : nsISupports {
+
+ /**
+ * Notify the observer that the message has started to be delivered. This method is
+ * called only once, at the beginning of a message send operation.
+ *
+ * @return The return value is currently ignored. In the future it may be
+ * used to cancel the URL load..
+ */
+ void onStartSending(in string aMsgID, in uint32_t aMsgSize);
+
+ /**
+ * Notify the observer that progress as occurred for the message send
+ */
+ void onProgress(in string aMsgID, in uint32_t aProgress, in uint32_t aProgressMax);
+
+ /**
+ * Notify the observer with a status message for the message send
+ */
+ void onStatus(in string aMsgID, in wstring aMsg);
+
+ /**
+ * Notify the observer that the message has been sent. This method is
+ * called once when the networking library has finished processing the
+ * message.
+ *
+ * This method is called regardless of whether the the operation was successful.
+ * aMsgID The message id for the mail message
+ * status Status code for the message send.
+ * msg A text string describing the error.
+ * returnFileSpec The returned file spec for save to file operations.
+ */
+ void onStopSending(in string aMsgID, in nsresult aStatus, in wstring aMsg,
+ in nsIFile aReturnFile);
+
+ /**
+ * Notify the observer with the folder uri before the draft is copied.
+ */
+ void onGetDraftFolderURI(in string aFolderURI);
+
+ /**
+ * Notify the observer when the user aborts the send without actually doing the send
+ * eg : by closing the compose window without Send.
+ */
+ void onSendNotPerformed(in string aMsgID, in nsresult aStatus);
+
+};
diff --git a/mailnews/compose/public/nsIMsgSendReport.idl b/mailnews/compose/public/nsIMsgSendReport.idl
new file mode 100644
index 000000000..65507a511
--- /dev/null
+++ b/mailnews/compose/public/nsIMsgSendReport.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: idl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIPrompt.idl"
+
+
+[scriptable, uuid(2ec81175-bc65-44b9-ba87-462bc3f938db)]
+interface nsIMsgProcessReport : nsISupports {
+
+ attribute boolean proceeded;
+ attribute nsresult error;
+ attribute wstring message;
+
+ void reset();
+};
+
+[scriptable, uuid(428c5bde-29f5-4bfe-830a-ec795a1c2975)]
+interface nsIMsgSendReport : nsISupports {
+
+ const long process_Current = -1;
+ const long process_BuildMessage = 0;
+ const long process_NNTP = 1;
+ const long process_SMTP = 2;
+ const long process_Copy = 3;
+ const long process_Filter = 4;
+ const long process_FCC = 5;
+
+ attribute long deliveryMode; /* see nsMsgDeliverMode in nsIMsgSend.idl for valid value */
+ attribute long currentProcess;
+
+ void reset();
+
+ void setProceeded(in long process, in boolean proceeded);
+ void setError(in long process, in nsresult error, in boolean overwriteError);
+ void setMessage(in long process, in wstring message, in boolean overwriteMessage);
+
+ nsIMsgProcessReport getProcessReport(in long process);
+
+ /* Display Report will ananlyze data collected during the send and will show the most appropriate error.
+ Also it will return the error code. In case of no error or if the error has been canceld, it will return
+ NS_OK.
+ */
+ nsresult displayReport(in nsIPrompt prompt, in boolean showErrorOnly, in boolean dontShowReportTwice);
+};
diff --git a/mailnews/compose/public/nsISmtpServer.idl b/mailnews/compose/public/nsISmtpServer.idl
new file mode 100644
index 000000000..8ff3d1aa9
--- /dev/null
+++ b/mailnews/compose/public/nsISmtpServer.idl
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIAuthPrompt;
+interface nsIUrlListener;
+interface nsIURI;
+interface nsIMsgWindow;
+
+/**
+ * This interface represents a single SMTP Server. A SMTP server instance may be
+ * created/obtained from nsIMsgAccountManager.
+ *
+ * Most of the attributes will set/get preferences from the main preferences
+ * file.
+ */
+[scriptable, uuid(a53dce6c-cd81-495c-83bc-45a65df1f08e)]
+interface nsISmtpServer : nsISupports {
+
+ /// A unique identifier for the server.
+ attribute string key;
+
+ /// A user supplied description for the server.
+ attribute AUTF8String description;
+
+ /// The server's hostname.
+ attribute ACString hostname;
+
+ /// The server's port.
+ attribute int32_t port;
+
+ /// The username to access the server with (if required)
+ attribute ACString username;
+
+ /**
+ * The password to access the server with (if required).
+ *
+ * @note this is stored within the server instance but not within preferences.
+ * It can be specified/saved here to avoid prompting the user constantly for
+ * the sending password.
+ */
+ attribute ACString password;
+
+ /// Returns a displayname of the format hostname:port or just hostname
+ readonly attribute string displayname;
+
+ /**
+ * Authentication mechanism.
+ *
+ * @see nsMsgAuthMethod (in MailNewsTypes2.idl)
+ * Same as "mail.smtpserver...authMethod" pref
+ *
+ * Compatibility note: This attribute had a different meaning in TB < 3.1
+ */
+ attribute nsMsgAuthMethodValue authMethod;
+
+ /**
+ * Whether to SSL or STARTTLS or not
+ *
+ * @see nsMsgSocketType (in MailNewsTypes2.idl)
+ * Same as "mail.smtpserver...try_ssl" pref
+ */
+ attribute nsMsgSocketTypeValue socketType;
+
+ /**
+ * May contain an alternative argument to EHLO or HELO to provide to the
+ * server. Reflects the value of the mail.smtpserver.*.hello_argument pref.
+ * This is mainly useful where ISPs don't bother providing PTR records for
+ * their servers and therefore users get an error on sending. See bug 244030
+ * for more discussion.
+ */
+ readonly attribute string helloArgument;
+
+ /// Returns the URI of the server (smtp:///)
+ readonly attribute ACString serverURI;
+
+ /**
+ * Gets a password for this server, using a UI prompt if necessary.
+ *
+ * @param promptString The string to prompt the user with when asking for
+ * the password.
+ * @param promptTitle The title of the prompt.
+ * @param netPrompt An nsIAuthPrompt instance to use for the password
+ * prompt.
+ * @return The password to use (may be null if no password was
+ * obtained).
+ */
+ ACString getPasswordWithUI(in wstring promptString, in wstring promptTitle,
+ in nsIAuthPrompt netPrompt);
+
+ /**
+ * Gets a username and password for this server, using a UI prompt if
+ * necessary.
+ *
+ * @param promptString The string to prompt the user with when asking for
+ * the password.
+ * @param promptTitle The title of the prompt.
+ * @param netPrompt An nsIAuthPrompt instance to use for the password
+ * prompt.
+ * @param userid The username to use (may be null if no password was
+ * obtained).
+ * @param password The password to use (may be empty if no password was
+ * obtained).
+ */
+ void getUsernamePasswordWithUI(in wstring promptString, in wstring promptTitle,
+ in nsIAuthPrompt netPrompt, out ACString userid,
+ out ACString password);
+
+ /**
+ * Calling this will *remove* the saved password for this server from the
+ * password manager and from the stored value.
+ */
+ void forgetPassword();
+
+ /**
+ * Verify that we can logon
+ *
+ * @param aPassword - password to use
+ * @param aUrlListener - gets called back with success or failure.
+ * @return - the url that we run.
+ *
+ */
+ nsIURI verifyLogon(in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow);
+
+ /// Call this to clear all preference values for this server.
+ void clearAllValues();
+};
diff --git a/mailnews/compose/public/nsISmtpService.idl b/mailnews/compose/public/nsISmtpService.idl
new file mode 100644
index 000000000..fcd6602de
--- /dev/null
+++ b/mailnews/compose/public/nsISmtpService.idl
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsISmtpServer;
+interface nsIURI;
+interface nsIUrlListener;
+interface nsIMsgIdentity;
+interface nsIInterfaceRequestor;
+interface nsIFile;
+interface nsIMsgStatusFeedback;
+interface nsIRequest;
+interface nsISimpleEnumerator;
+interface nsIMsgWindow;
+
+[scriptable, uuid(1b11b532-1527-4fc0-a00f-4ce7e6886419)]
+interface nsISmtpService : nsISupports {
+ /**
+ * Sends a mail message via the given parameters. This function builds an
+ * SMTP URL and makes an SMTP connection, and then runs the url.
+ * The SMTP server defined
+ * in the aSenderIdentity object (see nsIMsgIdentity) will be used to send
+ * the message. If there is no SMTP server defined in aSenderIdentity, the
+ * default SMTP server will be used.
+ *
+ * @note The file to send must be in the format specified by RFC 2822 for
+ * sending data. This includes having the correct CRLF line endings
+ * throughout the file, and the <CRLF>.<CRLF> at the end of the file.
+ * sendMailMessage does no processing/additions on the file.
+ *
+ * @param aFilePath The file to send.
+ * @param aRecipients A comma delimited list of recipients.
+ * @param aSenderIdentity The identity of the sender.
+ * @param aPassword Pass this in to prevent a dialog if the
+ * password is needed for secure transmission.
+ * @param aUrlListener A listener to listen to the URL being run,
+ * this parameter may be null.
+ * @param aStatusListener A feedback listener for slightly different
+ * feedback on the message send status. This
+ * parameter may be null.
+ * @param aNotificationCallbacks More notification callbacks
+ * @param aRequestDSN Pass true to request Delivery Status
+ * Notification.
+ * @param aURL Provides a handle on the running url. You
+ * can later interrupt the action by asking the
+ * netlib service manager to interrupt the url
+ * you are given back. This parameter may be
+ * null.
+ * @param aRequest Provides a handle to the running request.
+ * This parameter may be null.
+ */
+ void sendMailMessage(in nsIFile aFilePath, in string aRecipients,
+ in nsIMsgIdentity aSenderIdentity,
+ in string aPassword,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgStatusFeedback aStatusListener,
+ in nsIInterfaceRequestor aNotificationCallbacks,
+ in boolean aRequestDSN,
+ out nsIURI aURL,
+ out nsIRequest aRequest);
+
+ /**
+ * Verifies that we can logon to the server with given password
+ *
+ * @param aSmtpServer Server to try to logon to.
+ * @param aUrlListener Listener that will get notified whether logon
+ * was successful or not.
+ * @param aMsgWindow nsIMsgWindow to use for notification callbacks.
+ * @return - the url that we run.
+ */
+ nsIURI verifyLogon(in nsISmtpServer aServer, in nsIUrlListener aListener,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Return the SMTP server that is associated with an identity.
+ * @param aSenderIdentity the identity
+ * @param aServer the SMTP server
+ */
+ void getServerByIdentity(in nsIMsgIdentity aSenderIdentity,
+ out nsISmtpServer aServer);
+
+ /**
+ * A copy of the array of SMTP servers, as stored in the preferences
+ */
+ readonly attribute nsISimpleEnumerator servers;
+
+ /**
+ * The default server, across sessions of the app
+ * (eventually there will be a session default which does not
+ * persist past shutdown)
+ */
+ attribute nsISmtpServer defaultServer;
+
+ /**
+ * The "session default" server - this is never saved, and only used
+ * for the current session. Always falls back to the default server
+ * unless explicitly set.
+ */
+ attribute nsISmtpServer sessionDefaultServer;
+
+ /**
+ * Create a new SMTP server.
+ * Use this instead of createInstance(), so that the SMTP Service can
+ * be aware of this server
+ */
+ nsISmtpServer createServer();
+
+ /**
+ * Find the first server with the given hostname and/or username.
+ * Note: if either username or hostname is empty, then that parameter will
+ * not be used in the matching process.
+ * @param username the username for the server
+ * @param hostname the hostname of the server
+ * @returns null if no server is found
+ */
+ nsISmtpServer findServer(in string username, in string hostname);
+
+ /**
+ * Look up the server with the given key
+ * If the server does not exist, create it and add it to our list
+ */
+ nsISmtpServer getServerByKey(in string key);
+
+ /**
+ * Delete the given server from the server list - does nothing if the server
+ * does not exist
+ * @param server the server to delete. Use findServer() if you only know
+ * the hostname
+ */
+ void deleteServer(in nsISmtpServer server);
+};
diff --git a/mailnews/compose/public/nsISmtpUrl.idl b/mailnews/compose/public/nsISmtpUrl.idl
new file mode 100644
index 000000000..be2af7cb7
--- /dev/null
+++ b/mailnews/compose/public/nsISmtpUrl.idl
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgComposeParams.idl"
+
+interface nsIMsgIdentity;
+interface nsIPrompt;
+interface nsIAuthPrompt;
+interface nsISmtpServer;
+interface nsIInterfaceRequestor;
+interface nsIFile;
+
+[scriptable, uuid(da22b8ac-059d-4f82-bf99-f5f3d3c8202d)]
+interface nsISmtpUrl : nsISupports {
+ /**
+ * SMTP Parse specific getters.
+ * These retrieve various parts from the url.
+ */
+
+ /**
+ * This list is a list of all recipients to send the email to.
+ * Each name is NULL terminated.
+ */
+ attribute string recipients;
+
+ attribute boolean PostMessage;
+
+ /**
+ * The message can be stored in a file, to allow accessors for getting and
+ * setting the file name to post.
+ */
+ attribute nsIFile postMessageFile;
+
+ attribute boolean requestDSN;
+
+ /**
+ * The envid which is used in the DSN.
+ */
+ attribute ACString dsnEnvid;
+
+ /**
+ * SMTP Url instance specific getters and setters
+ * Information the protocol needs to know in order to run the url.
+ * These are NOT event sinks which are things the caller needs to know.
+ */
+
+ /**
+ * By default the url is really a bring up the compose window mailto url.
+ * You need to call this function if you want to force the message to be
+ * posted to the mailserver.
+ */
+
+ /**
+ * The user's full name and user's email address are encapsulated in the
+ * senderIdentity.
+ * (the user's domain name can be glopped from the user's email address)
+ *
+ * NOTE: the SMTP username and SMTP server are in the smtp url
+ * smtp://sspitzer@tintin/...
+ */
+ attribute nsIMsgIdentity senderIdentity;
+ attribute nsIPrompt prompt;
+ attribute nsIAuthPrompt authPrompt;
+ attribute nsIInterfaceRequestor notificationCallbacks;
+ attribute nsISmtpServer smtpServer;
+
+ attribute boolean verifyLogon; // we're just verifying the ability to logon
+
+ /// Constant for the default SMTP port number
+ const int32_t DEFAULT_SMTP_PORT = 25;
+
+ /// Constant for the default SMTP over ssl port number
+ const int32_t DEFAULT_SMTPS_PORT = 465;
+};
+
+[scriptable, uuid(87c36c23-4bc2-4992-b338-69f88f6ed0a1)]
+interface nsIMailtoUrl : nsISupports {
+ /**
+ * mailto: parse specific getters
+ *
+ * All of these fields are things we can effectively extract from a
+ * mailto url if it contains all of these values
+ *
+ * Note: Attachments aren't available because that would expose a potential
+ * security hole (see bug 99055).
+ *
+ * These items are in one function as we only ever get them from the one
+ * place and all at the same time.
+ */
+ void getMessageContents(out AUTF8String aToPart, out AUTF8String aCcPart,
+ out AUTF8String aBccPart, out AUTF8String aSubjectPart,
+ out AUTF8String aBodyPart, out AUTF8String aHtmlPart,
+ out ACString aReferencePart,
+ out AUTF8String aNewsgroupPart,
+ out MSG_ComposeFormat aFormat);
+
+ /**
+ * These attributes are available should mailnews or extensions want them
+ * but aren't used by standard in mailnews.
+ */
+ readonly attribute AUTF8String fromPart;
+ readonly attribute AUTF8String followUpToPart;
+ readonly attribute AUTF8String organizationPart;
+ readonly attribute AUTF8String replyToPart;
+ readonly attribute AUTF8String priorityPart;
+ readonly attribute AUTF8String newsHostPart;
+};
diff --git a/mailnews/compose/public/nsIURLFetcher.idl b/mailnews/compose/public/nsIURLFetcher.idl
new file mode 100644
index 000000000..fadb3abbc
--- /dev/null
+++ b/mailnews/compose/public/nsIURLFetcher.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIURI.idl"
+#include "nsIFileStreams.idl"
+#include "nsIFile.idl"
+
+%{ C++
+
+//
+// Callback declarations for URL completion
+//
+// For completion of send/message creation operations...
+typedef nsresult (*nsAttachSaveCompletionCallback) (nsresult aStatus,
+ const nsACString &aContentType,
+ const nsACString &aCharset,
+ int32_t totalSize, const char16_t* aMsg,
+ void *tagData);
+
+class nsMsgAttachmentHandler;
+%}
+
+native nsAttachSaveCompletionCallback(nsAttachSaveCompletionCallback);
+[ptr] native nsMsgAttachmentHandler(nsMsgAttachmentHandler);
+
+
+[noscript, uuid(7316af6b-050d-4697-9d39-b0b358514f5c)]
+interface nsIURLFetcher : nsISupports
+{
+ boolean stillRunning();
+
+ void fireURLRequest(in nsIURI aURL, in nsIFile localFile, in nsIOutputStream fileStream, in nsAttachSaveCompletionCallback cb, in nsMsgAttachmentHandler tagData);
+
+ void initialize(in nsIFile localFile, in nsIOutputStream fileStream, in nsAttachSaveCompletionCallback cb, in nsMsgAttachmentHandler tagData);
+};
diff --git a/mailnews/compose/public/nsMsgAttachmentData.h b/mailnews/compose/public/nsMsgAttachmentData.h
new file mode 100644
index 000000000..0f15d3764
--- /dev/null
+++ b/mailnews/compose/public/nsMsgAttachmentData.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __MSGATTACHMENTDATA_H__
+#define __MSGATTACHMENTDATA_H__
+
+#include "nsIURL.h"
+#include "nsStringGlue.h"
+#include "nsIMsgSend.h"
+
+// Attachment file/URL structures - we're letting libmime use this directly
+class nsMsgAttachmentData final : public nsIMsgAttachmentData
+{
+public:
+ NS_DECL_NSIMSGATTACHMENTDATA
+ NS_DECL_ISUPPORTS
+
+ nsMsgAttachmentData();
+ virtual ~nsMsgAttachmentData();
+
+ nsCOMPtr<nsIURI> m_url; // The URL to attach.
+
+ nsCString m_desiredType; // The type to which this document should be
+ // converted. Legal values are NULL, TEXT_PLAIN
+ // and APPLICATION_POSTSCRIPT (which are macros
+ // defined in net.h); other values are ignored.
+
+ nsCString m_realType; // The type of the URL if known, otherwise NULL. For example, if
+ // you were attaching a temp file which was known to contain HTML data,
+ // you would pass in TEXT_HTML as the real_type, to override whatever type
+ // the name of the tmp file might otherwise indicate.
+
+ nsCString m_realEncoding; // Goes along with real_type
+
+ nsCString m_realName; // The original name of this document, which will eventually show up in the
+ // Content-Disposition header. For example, if you had copied a document to a
+ // tmp file, this would be the original, human-readable name of the document.
+
+ nsCString m_description; // If you put a string here, it will show up as the Content-Description header.
+ // This can be any explanatory text; it's not a file name.
+
+ nsCString m_disposition; // The Content-Disposition header (if any). a
+ // nsMsgAttachmentData can very well have
+ // Content-Disposition: inline value, instead of
+ // "attachment".
+ nsCString m_cloudPartInfo; // For X-Mozilla-Cloud-Part header, if any
+
+ // Mac-specific data that should show up as optional parameters
+ // to the content-type header.
+ nsCString m_xMacType;
+ nsCString m_xMacCreator;
+
+ int32_t m_size; // The size of the attachment. May be 0.
+ nsCString m_sizeExternalStr; // The reported size of an external attachment. Originally set at "-1" to mean an unknown value.
+ bool m_isExternalAttachment; // Flag for determining if the attachment is external
+ bool m_isExternalLinkAttachment; // Flag for determining if the attachment is external and an http link.
+ bool m_isDownloaded; // Flag for determining if the attachment has already been downloaded
+ bool m_hasFilename; // Tells whether the name is provided by us or if it's a Part 1.2-like attachment
+ bool m_displayableInline; // Tells whether the attachment could be displayed inline
+};
+
+class nsMsgAttachedFile final : public nsIMsgAttachedFile
+{
+public:
+ NS_DECL_NSIMSGATTACHEDFILE
+ NS_DECL_ISUPPORTS
+
+ nsMsgAttachedFile();
+ virtual ~nsMsgAttachedFile();
+
+ nsCOMPtr<nsIURI> m_origUrl; // Where it came from on the network (or even elsewhere on the local disk.)
+
+ nsCOMPtr<nsIFile> m_tmpFile; // The tmp file in which the (possibly converted) data now resides.
+
+ nsCString m_type; // The type of the data in file_name (not necessarily the same as the type of orig_url.)
+
+ nsCString m_encoding; // Likewise, the encoding of the tmp file. This will be set only if the original
+ // document had an encoding already; we don't do base64 encoding and so forth until
+ // it's time to assemble a full MIME message of all parts.
+
+
+ nsCString m_description; // For Content-Description header
+ nsCString m_cloudPartInfo; // For X-Mozilla-Cloud-Part header, if any
+ nsCString m_xMacType; // mac-specific info
+ nsCString m_xMacCreator; // mac-specific info
+ nsCString m_realName; // The real name of the file.
+
+ // Some statistics about the data that was written to the file, so that when
+ // it comes time to compose a MIME message, we can make an informed decision
+ // about what Content-Transfer-Encoding would be best for this attachment.
+ // (If it's encoded already, we ignore this information and ship it as-is.)
+ uint32_t m_size;
+ uint32_t m_unprintableCount;
+ uint32_t m_highbitCount;
+ uint32_t m_ctlCount;
+ uint32_t m_nullCount;
+ uint32_t m_maxLineLength;
+};
+
+#undef MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING
+#ifdef MOZ_IS_DESTRUCTIBLE
+#define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X) \
+ static_assert(!MOZ_IS_DESTRUCTIBLE(X) || \
+ mozilla::IsSame<X, nsMsgAttachmentData>::value || \
+ mozilla::IsSame<X, nsMsgAttachedFile>::value, \
+ "Reference-counted class " #X " should not have a public destructor. " \
+ "Try to make this class's destructor non-public. If that is really " \
+ "not possible, you can whitelist this class by providing a " \
+ "HasDangerousPublicDestructor specialization for it.");
+#else
+#define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X)
+#endif
+#endif
diff --git a/mailnews/compose/public/nsMsgCompCID.h b/mailnews/compose/public/nsMsgCompCID.h
new file mode 100644
index 000000000..9fad52638
--- /dev/null
+++ b/mailnews/compose/public/nsMsgCompCID.h
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMessageCompCID_h__
+#define nsMessageCompCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+//
+// nsMsgComposeService
+//
+#define NS_MSGCOMPOSESERVICE_CID \
+{ /* 588595FE-1ADA-11d3-A715-0060B0EB39B5 */ \
+ 0x588595fe, 0x1ada, 0x11d3, \
+ {0xa7, 0x15, 0x0, 0x60, 0xb0, 0xeb, 0x39, 0xb5}}
+
+#define NS_MSGCOMPOSESERVICE_CONTRACTID \
+ "@mozilla.org/messengercompose;1"
+#define NS_MSGCOMPOSESTARTUPHANDLER_CONTRACTID \
+ "@mozilla.org/commandlinehandler/general-startup;1?type=compose"
+
+//
+// nsMsgComposeContentHandler
+//
+#define NS_MSGCOMPOSECONTENTHANDLER_CID \
+{ /* 0B63FB80-BBBA-11D4-9DAA-91B657EB313C */ \
+0x0b63fb80, 0xbbba, 0x11d4, \
+ {0x9d, 0xaa, 0x91, 0xb6, 0x57, 0xeb, 0x31, 0x3c}}
+
+#define NS_MSGCOMPOSECONTENTHANDLER_CONTRACTID \
+ NS_CONTENT_HANDLER_CONTRACTID_PREFIX"application/x-mailto"
+
+//
+// nsMsgCompose
+//
+#define NS_MSGCOMPOSE_CONTRACTID \
+ "@mozilla.org/messengercompose/compose;1"
+
+#define NS_MSGCOMPOSE_CID \
+{ /* EB5BDAF8-BBC6-11d2-A6EC-0060B0EB39B5 */ \
+ 0xeb5bdaf8, 0xbbc6, 0x11d2, \
+ {0xa6, 0xec, 0x0, 0x60, 0xb0, 0xeb, 0x39, 0xb5}}
+
+//
+// nsMsgComposeSecure
+//
+#define NS_MSGCOMPOSESECURE_CONTRACTID \
+ "@mozilla.org/messengercompose/composesecure;1"
+
+//
+// nsMsgComposeParams
+//
+#define NS_MSGCOMPOSEPARAMS_CONTRACTID \
+ "@mozilla.org/messengercompose/composeparams;1"
+
+#define NS_MSGCOMPOSEPARAMS_CID \
+{ /* CB998A00-C079-11D4-9DAA-8DF64BAB2EFC */ \
+ 0xcb998a00, 0xc079, 0x11d4, \
+ {0x9d, 0xaa, 0x8d, 0xf6, 0x4b, 0xab, 0x2e, 0xfc}}
+
+//
+// nsMsgComposeSendListener
+//
+#define NS_MSGCOMPOSESENDLISTENER_CONTRACTID \
+ "@mozilla.org/messengercompose/composesendlistener;1"
+
+#define NS_MSGCOMPOSESENDLISTENER_CID \
+{ /* acc72781-2cea-11d5-9daa-bacdeac1eefc */ \
+ 0xacc72781, 0x2cea, 0x11d5, \
+ {0x9d, 0xaa, 0xba, 0xcd, 0xea, 0xc1, 0xee, 0xfc}}
+
+//
+// nsMsgComposeProgressParams
+//
+#define NS_MSGCOMPOSEPROGRESSPARAMS_CONTRACTID \
+ "@mozilla.org/messengercompose/composeprogressparameters;1"
+
+#define NS_MSGCOMPOSEPROGRESSPARAMS_CID \
+{ /* 1e0e7c01-3e4c-11d5-9daa-f88d288130fc */ \
+ 0x1e0e7c01, 0x3e4c, 0x11d5, \
+ {0x9d, 0xaa, 0xf8, 0x8d, 0x28, 0x81, 0x30, 0xfc}}
+
+//
+// nsMsgCompFields
+//
+#define NS_MSGCOMPFIELDS_CONTRACTID \
+ "@mozilla.org/messengercompose/composefields;1"
+
+#define NS_MSGCOMPFIELDS_CID \
+{ /* 6D222BA0-BD46-11d2-8293-000000000000 */ \
+ 0x6d222ba0, 0xbd46, 0x11d2, \
+ {0x82, 0x93, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}
+
+//
+// nsMsgAttachment
+//
+#define NS_MSGATTACHMENT_CONTRACTID \
+ "@mozilla.org/messengercompose/attachment;1"
+
+#define NS_MSGATTACHMENT_CID \
+{ /* 27B8D045-8D9F-4fa8-BFB6-8A0F8D09CE89 */ \
+ 0x27b8d045, 0x8d9f, 0x4fa8, \
+ {0xbf, 0xb6, 0x8a, 0xf, 0x8d, 0x9, 0xce, 0x89}}
+
+//
+// nsMsgAttachmentData
+//
+#define NS_MSGATTACHMENTDATA_CONTRACTID \
+ "@mozilla.org/messengercompose/attachmentdata;1"
+
+#define NS_MSGATTACHMENTDATA_CID \
+{ /* 9e16958d-d9e9-4cae-b723-a5bccf104998 */ \
+ 0x9e16958d, 0xd9e9, 0x4cae, \
+ {0xb7, 0x23, 0xa5, 0xbc, 0xcf, 0x10, 0x49, 0x98}}
+
+//
+// nsMsgAttachedFile
+//
+#define NS_MSGATTACHEDFILE_CONTRACTID \
+ "@mozilla.org/messengercompose/attachedfile;1"
+
+#define NS_MSGATTACHEDFILE_CID \
+{ /* ef173501-4e14-42b9-ae1f-7770de235c29 */ \
+ 0xef173501, 0x4e14, 0x42b9, \
+ {0xae, 0x1f, 0x77, 0x70, 0xde, 0x23, 0x5c, 0x29}}
+
+//
+// nsMsgSend
+//
+#define NS_MSGSEND_CONTRACTID \
+ "@mozilla.org/messengercompose/send;1"
+
+#define NS_MSGSEND_CID \
+{ /* 935284E0-C5D8-11d2-8297-000000000000 */ \
+ 0x935284e0, 0xc5d8, 0x11d2, \
+ {0x82, 0x97, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}
+
+//
+// nsMsgSendLater
+//
+#define NS_MSGSENDLATER_CONTRACTID \
+ "@mozilla.org/messengercompose/sendlater;1"
+
+#define NS_MSGSENDLATER_CID \
+{ /* E15C83F1-1CF4-11d3-8EF0-00A024A7D144 */ \
+ 0xe15c83f1, 0x1cf4, 0x11d3, \
+ {0x8e, 0xf0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 }}
+
+//
+// nsSmtpUrl
+//
+#define NS_SMTPURL_CONTRACTID \
+ "@mozilla.org/messengercompose/smtpurl;1"
+
+#define NS_SMTPURL_CID \
+{ /* BE59DBF0-2812-11d3-80A3-006008128C4E} */ \
+ 0xbe59dbf0, 0x2812, 0x11d3, \
+ {0x80, 0xa3, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e}}
+
+//
+// nsMailtoUrl
+//
+#define NS_MAILTOURL_CONTRACTID \
+ "@mozilla.org/messengercompose/mailtourl;1"
+
+#define NS_MAILTOURL_CID \
+{ /* 05BAB5E7-9C7D-11d3-98A3-001083010E9B} */ \
+ 0x5bab5e7, 0x9c7d, 0x11d3, \
+ {0x98, 0xa3, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b}}
+
+//
+// nsSmtpServer
+//
+#define NS_SMTPSERVER_CONTRACTID \
+ "@mozilla.org/messenger/smtp/server;1"
+
+#define NS_SMTPSERVER_CID \
+{ /* 60dc861a-56ce-11d3-9118-00a0c900d445 */ \
+ 0x60dc861a,0x56ce,0x11d3, \
+ {0x91,0x18, 0x0, 0xa0, 0xc9, 0x0, 0xd4, 0x45 }}
+
+//
+// nsSmtpService
+//
+#define NS_SMTPSERVICE_CONTRACTID \
+ "@mozilla.org/messengercompose/smtp;1"
+
+#define NS_MAILTOHANDLER_CONTRACTID \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "mailto"
+
+#define NS_SMTPSERVICE_CID \
+{ /* 5B6419F1-CA9B-11d2-8063-006008128C4E */ \
+ 0x5b6419f1, 0xca9b, 0x11d2, \
+ {0x80, 0x63, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e}}
+
+//
+// nsMsgQuote
+//
+#define NS_MSGQUOTE_CONTRACTID \
+ "@mozilla.org/messengercompose/quoting;1"
+#define NS_MSGQUOTE_CID \
+ {0x1C7ABF0C, 0x21E5, 0x11d3, \
+ { 0x8E, 0xF1, 0x00, 0xA0, 0x24, 0xA7, 0xD1, 0x44 }}
+
+#define NS_MSGQUOTELISTENER_CONTRACTID \
+ "@mozilla.org/messengercompose/quotinglistener;1"
+#define NS_MSGQUOTELISTENER_CID \
+ {0x683728ac, 0x88df, 0x11d3, \
+ { 0x98, 0x9d, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b }}
+
+//
+// nsMsgDraft
+//
+#define NS_MSGDRAFT_CONTRACTID \
+ "@mozilla.org/messengercompose/drafts;1"
+#define NS_MSGDRAFT_CID \
+ { 0xa623746c, 0x453b, 0x11d3, \
+ { 0x8f, 0xf, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } }
+
+//
+// nsURLFetcher
+//
+#define NS_URLFETCHER_CONTRACTID \
+ "@mozilla.org/messengercompose/urlfetcher;1"
+
+// {01B8A701-2F52-11D5-9DAA-F78DA781A1FC}
+#define NS_URLFETCHER_CID \
+{ 0x01b8a701, 0x2f52, 0x11d5, \
+ { 0x9d, 0xaa, 0xf7, 0x8d, 0xa7, 0x81, 0xa1, 0xfc } }
+
+//
+// nsMsgCompUtils
+//
+#define NS_MSGCOMPUTILS_CONTRACTID \
+ "@mozilla.org/messengercompose/computils;1"
+
+// {ceb0dca2-5e7d-4204-94d4-2ab925921fae}
+#define NS_MSGCOMPUTILS_CID \
+{ 0xceb0dca2, 0x5e7d, 0x4204, \
+ { 0x94, 0xd4, 0x2a, 0xb9, 0x25, 0x92, 0x1f, 0xae } }
+
+
+#endif // nsMessageCompCID_h__
diff --git a/mailnews/compose/src/moz.build b/mailnews/compose/src/moz.build
new file mode 100644
index 000000000..33b62c444
--- /dev/null
+++ b/mailnews/compose/src/moz.build
@@ -0,0 +1,55 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'nsComposeStrings.h',
+ 'nsMsgAttachmentHandler.h',
+ 'nsMsgCompFields.h',
+ 'nsMsgCompose.h',
+ 'nsMsgSend.h',
+]
+
+SOURCES += [
+ 'nsComposeStrings.cpp',
+ 'nsMsgAttachment.cpp',
+ 'nsMsgAttachmentHandler.cpp',
+ 'nsMsgCompFields.cpp',
+ 'nsMsgCompose.cpp',
+ 'nsMsgComposeContentHandler.cpp',
+ 'nsMsgComposeParams.cpp',
+ 'nsMsgComposeProgressParams.cpp',
+ 'nsMsgComposeService.cpp',
+ 'nsMsgCompUtils.cpp',
+ 'nsMsgCopy.cpp',
+ 'nsMsgPrompts.cpp',
+ 'nsMsgQuote.cpp',
+ 'nsMsgSend.cpp',
+ 'nsMsgSendLater.cpp',
+ 'nsMsgSendPart.cpp',
+ 'nsMsgSendReport.cpp',
+ 'nsSmtpProtocol.cpp',
+ 'nsSmtpServer.cpp',
+ 'nsSmtpService.cpp',
+ 'nsSmtpUrl.cpp',
+ 'nsURLFetcher.cpp',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'nsMsgAppleDoubleEncode.cpp',
+ 'nsMsgAppleEncode.cpp',
+ ]
+ EXPORTS += [
+ 'nsMsgAppleCodes.h',
+ 'nsMsgAppleDouble.h',
+ ]
+
+EXTRA_COMPONENTS += [
+ 'nsSMTPProtocolHandler.js',
+ 'nsSMTPProtocolHandler.manifest',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/compose/src/nsComposeStrings.cpp b/mailnews/compose/src/nsComposeStrings.cpp
new file mode 100644
index 000000000..5c8565805
--- /dev/null
+++ b/mailnews/compose/src/nsComposeStrings.cpp
@@ -0,0 +1,116 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsComposeStrings.h"
+
+const char16_t* errorStringNameForErrorCode(nsresult aCode)
+{
+#ifdef __GNUC__
+// Temporary workaroung until bug 783526 is fixed.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wswitch"
+#endif
+ switch(aCode)
+ {
+ case NS_MSG_UNABLE_TO_OPEN_FILE:
+ return u"unableToOpenFile";
+ case NS_MSG_UNABLE_TO_OPEN_TMP_FILE:
+ return u"unableToOpenTmpFile";
+ case NS_MSG_UNABLE_TO_SAVE_TEMPLATE:
+ return u"unableToSaveTemplate";
+ case NS_MSG_UNABLE_TO_SAVE_DRAFT:
+ return u"unableToSaveDraft";
+ case NS_MSG_COULDNT_OPEN_FCC_FOLDER:
+ return u"couldntOpenFccFolder";
+ case NS_MSG_NO_SENDER:
+ return u"noSender";
+ case NS_MSG_NO_RECIPIENTS:
+ return u"noRecipients";
+ case NS_MSG_ERROR_WRITING_FILE:
+ return u"errorWritingFile";
+ case NS_ERROR_SENDING_FROM_COMMAND:
+ return u"errorSendingFromCommand";
+ case NS_ERROR_SENDING_DATA_COMMAND:
+ return u"errorSendingDataCommand";
+ case NS_ERROR_SENDING_MESSAGE:
+ return u"errorSendingMessage";
+ case NS_ERROR_POST_FAILED:
+ return u"postFailed";
+ case NS_ERROR_QUEUED_DELIVERY_FAILED:
+ return u"errorQueuedDeliveryFailed";
+ case NS_ERROR_SEND_FAILED:
+ return u"sendFailed";
+ case NS_ERROR_SMTP_SERVER_ERROR:
+ return u"smtpServerError";
+ case NS_MSG_UNABLE_TO_SEND_LATER:
+ return u"unableToSendLater";
+ case NS_ERROR_COMMUNICATIONS_ERROR:
+ return u"communicationsError";
+ case NS_ERROR_BUT_DONT_SHOW_ALERT:
+ return u"dontShowAlert";
+ case NS_ERROR_TCP_READ_ERROR:
+ return u"tcpReadError";
+ case NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS:
+ return u"couldNotGetUsersMailAddress";
+ case NS_ERROR_MIME_MPART_ATTACHMENT_ERROR:
+ return u"mimeMpartAttachmentError";
+ case NS_MSG_FAILED_COPY_OPERATION:
+ return u"failedCopyOperation";
+ case NS_ERROR_NNTP_NO_CROSS_POSTING:
+ return u"nntpNoCrossPosting";
+ case NS_MSG_CANCELLING:
+ return u"msgCancelling";
+ case NS_ERROR_SEND_FAILED_BUT_NNTP_OK:
+ return u"sendFailedButNntpOk";
+ case NS_MSG_ERROR_READING_FILE:
+ return u"errorReadingFile";
+ case NS_MSG_ERROR_ATTACHING_FILE:
+ return u"errorAttachingFile";
+ case NS_ERROR_SMTP_GREETING:
+ return u"incorrectSmtpGreeting";
+ case NS_ERROR_SENDING_RCPT_COMMAND:
+ return u"errorSendingRcptCommand";
+ case NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS:
+ return u"startTlsFailed";
+ case NS_ERROR_SMTP_PASSWORD_UNDEFINED:
+ return u"smtpPasswordUndefined";
+ case NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED:
+ return u"smtpTempSizeExceeded";
+ case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1:
+ return u"smtpPermSizeExceeded1";
+ case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2:
+ return u"smtpPermSizeExceeded2";
+ case NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER:
+ return u"smtpSendFailedUnknownServer";
+ case NS_ERROR_SMTP_SEND_FAILED_REFUSED:
+ return u"smtpSendRequestRefused";
+ case NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED:
+ return u"smtpSendInterrupted";
+ case NS_ERROR_SMTP_SEND_FAILED_TIMEOUT:
+ return u"smtpSendTimeout";
+ case NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON:
+ return u"smtpSendFailedUnknownReason";
+ case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL:
+ return u"smtpHintAuthEncryptToPlainNoSsl";
+ case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL:
+ return u"smtpHintAuthEncryptToPlainSsl";
+ case NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT:
+ return u"smtpHintAuthPlainToEncrypt";
+ case NS_ERROR_SMTP_AUTH_FAILURE:
+ return u"smtpAuthFailure";
+ case NS_ERROR_SMTP_AUTH_GSSAPI:
+ return u"smtpAuthGssapi";
+ case NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED:
+ return u"smtpAuthMechNotSupported";
+ case NS_ERROR_SMTP_AUTH_NOT_SUPPORTED:
+ return u"smtpAuthenticationNotSupported";
+ case NS_ERROR_ILLEGAL_LOCALPART:
+ return u"illegalLocalPart";
+ default:
+ return u"sendFailed";
+ }
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+}
diff --git a/mailnews/compose/src/nsComposeStrings.h b/mailnews/compose/src/nsComposeStrings.h
new file mode 100644
index 000000000..2a8754493
--- /dev/null
+++ b/mailnews/compose/src/nsComposeStrings.h
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ String Ids used by mailnews\compose
+ To Do: Convert the callers to use names instead of ids and then make this file obsolete.
+ */
+
+#ifndef _nsComposeStrings_H__
+#define _nsComposeStrings_H__
+
+#include "msgCore.h"
+
+#define NS_MSG_UNABLE_TO_OPEN_FILE NS_MSG_GENERATE_FAILURE(12500)
+#define NS_MSG_UNABLE_TO_OPEN_TMP_FILE NS_MSG_GENERATE_FAILURE(12501)
+#define NS_MSG_UNABLE_TO_SAVE_TEMPLATE NS_MSG_GENERATE_FAILURE(12502)
+#define NS_MSG_UNABLE_TO_SAVE_DRAFT NS_MSG_GENERATE_FAILURE(12503)
+#define NS_MSG_COULDNT_OPEN_FCC_FOLDER NS_MSG_GENERATE_FAILURE(12506)
+#define NS_MSG_NO_SENDER NS_MSG_GENERATE_FAILURE(12510)
+#define NS_MSG_NO_RECIPIENTS NS_MSG_GENERATE_FAILURE(12511)
+#define NS_MSG_ERROR_WRITING_FILE NS_MSG_GENERATE_FAILURE(12512)
+#define NS_ERROR_SENDING_FROM_COMMAND NS_MSG_GENERATE_FAILURE(12514)
+#define NS_ERROR_SENDING_DATA_COMMAND NS_MSG_GENERATE_FAILURE(12516)
+#define NS_ERROR_SENDING_MESSAGE NS_MSG_GENERATE_FAILURE(12517)
+#define NS_ERROR_POST_FAILED NS_MSG_GENERATE_FAILURE(12518)
+#define NS_ERROR_QUEUED_DELIVERY_FAILED NS_MSG_GENERATE_FAILURE(12519)
+#define NS_ERROR_SEND_FAILED NS_MSG_GENERATE_FAILURE(12520)
+#define NS_ERROR_SMTP_SERVER_ERROR NS_MSG_GENERATE_FAILURE(12524)
+#define NS_MSG_UNABLE_TO_SEND_LATER NS_MSG_GENERATE_FAILURE(12525)
+#define NS_ERROR_COMMUNICATIONS_ERROR NS_MSG_GENERATE_FAILURE(12526)
+#define NS_ERROR_BUT_DONT_SHOW_ALERT NS_MSG_GENERATE_FAILURE(12527)
+#define NS_ERROR_TCP_READ_ERROR NS_MSG_GENERATE_FAILURE(12528)
+#define NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS NS_MSG_GENERATE_FAILURE(12529)
+#define NS_ERROR_MIME_MPART_ATTACHMENT_ERROR NS_MSG_GENERATE_FAILURE(12531)
+#define NS_MSG_FAILED_COPY_OPERATION NS_MSG_GENERATE_FAILURE(12532)
+
+/* 12554 is taken by NS_ERROR_NNTP_NO_CROSS_POSTING. use 12555 as the next one */
+
+#define NS_MSG_CANCELLING NS_MSG_GENERATE_SUCCESS(12555)
+
+// For message sending report
+#define NS_ERROR_SEND_FAILED_BUT_NNTP_OK NS_MSG_GENERATE_FAILURE(12560)
+#define NS_MSG_ERROR_READING_FILE NS_MSG_GENERATE_FAILURE(12563)
+
+#define NS_MSG_ERROR_ATTACHING_FILE NS_MSG_GENERATE_FAILURE(12570)
+
+#define NS_ERROR_SMTP_GREETING NS_MSG_GENERATE_FAILURE(12572)
+
+#define NS_ERROR_SENDING_RCPT_COMMAND NS_MSG_GENERATE_FAILURE(12575)
+
+#define NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS NS_MSG_GENERATE_FAILURE(12582)
+
+#define NS_ERROR_SMTP_PASSWORD_UNDEFINED NS_MSG_GENERATE_FAILURE(12584)
+#define NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED NS_MSG_GENERATE_FAILURE(12586)
+#define NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1 NS_MSG_GENERATE_FAILURE(12587)
+#define NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2 NS_MSG_GENERATE_FAILURE(12588)
+
+#define NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER NS_MSG_GENERATE_FAILURE(12589)
+#define NS_ERROR_SMTP_SEND_FAILED_REFUSED NS_MSG_GENERATE_FAILURE(12590)
+#define NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED NS_MSG_GENERATE_FAILURE(12591)
+#define NS_ERROR_SMTP_SEND_FAILED_TIMEOUT NS_MSG_GENERATE_FAILURE(12592)
+#define NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON NS_MSG_GENERATE_FAILURE(12593)
+
+#define NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL NS_MSG_GENERATE_FAILURE(12594)
+#define NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL NS_MSG_GENERATE_FAILURE(12595)
+#define NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT NS_MSG_GENERATE_FAILURE(12596)
+#define NS_ERROR_SMTP_AUTH_FAILURE NS_MSG_GENERATE_FAILURE(12597)
+#define NS_ERROR_SMTP_AUTH_GSSAPI NS_MSG_GENERATE_FAILURE(12598)
+#define NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED NS_MSG_GENERATE_FAILURE(12599)
+#define NS_ERROR_SMTP_AUTH_NOT_SUPPORTED NS_MSG_GENERATE_FAILURE(12600)
+
+#define NS_ERROR_ILLEGAL_LOCALPART NS_MSG_GENERATE_FAILURE(12601)
+
+const char16_t* errorStringNameForErrorCode(nsresult aCode);
+
+#endif /* _nsComposeStrings_H__ */
diff --git a/mailnews/compose/src/nsMsgAppleCodes.h b/mailnews/compose/src/nsMsgAppleCodes.h
new file mode 100644
index 000000000..d8ca2f327
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAppleCodes.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+** AD_Codes.h
+**
+** ---------------
+**
+** Head file for Apple Decode/Encode essential codes.
+**
+**
+*/
+
+#ifndef ad_codes_h
+#define ad_codes_h
+
+/*
+** applefile definitions used
+*/
+#if PRAGMA_STRUCT_ALIGN
+ #pragma options align=mac68k
+#endif
+
+#define APPLESINGLE_MAGIC 0x00051600L
+#define APPLEDOUBLE_MAGIC 0x00051607L
+#define VERSION 0x00020000
+
+#define NUM_ENTRIES 6
+
+#define ENT_DFORK 1L
+#define ENT_RFORK 2L
+#define ENT_NAME 3L
+#define ENT_COMMENT 4L
+#define ENT_DATES 8L
+#define ENT_FINFO 9L
+#define CONVERT_TIME 1265437696L
+
+/*
+** data type used in the encoder/decoder.
+*/
+typedef struct ap_header
+{
+ int32_t magic;
+ int32_t version;
+ char fill[16];
+ int16_t entries;
+
+} ap_header;
+
+typedef struct ap_entry
+{
+ int32_t id;
+ int32_t offset;
+ int32_t length;
+
+} ap_entry;
+
+typedef struct ap_dates
+{
+ int32_t create, modify, backup, access;
+
+} ap_dates;
+
+typedef struct myFInfo /* the mac FInfo structure for the cross platform. */
+{
+ int32_t fdType, fdCreator;
+ int16_t fdFlags;
+ int32_t fdLocation; /* it really should be a pointer, but just a place-holder */
+ int16_t fdFldr;
+
+} myFInfo;
+
+PR_BEGIN_EXTERN_C
+/*
+** string utils.
+*/
+int write_stream(appledouble_encode_object *p_ap_encode_obj, const char *s,int len);
+
+int fill_apple_mime_header(appledouble_encode_object *p_ap_encode_obj);
+int ap_encode_file_infor(appledouble_encode_object *p_ap_encode_obj);
+int ap_encode_header(appledouble_encode_object* p_ap_encode_obj, bool firstTime);
+int ap_encode_data( appledouble_encode_object* p_ap_encode_obj, bool firstTime);
+
+/*
+** the prototypes for the ap_decoder.
+*/
+int fetch_a_line(appledouble_decode_object* p_ap_decode_obj, char *buff);
+int ParseFileHeader(appledouble_decode_object* p_ap_decode_obj);
+int ap_seek_part_start(appledouble_decode_object* p_ap_decode_obj);
+void parse_param(char *p, char **param, char**define, char **np);
+int ap_seek_to_boundary(appledouble_decode_object* p_ap_decode_obj, bool firstime);
+int ap_parse_header(appledouble_decode_object* p_ap_decode_obj,bool firstime);
+int ap_decode_file_infor(appledouble_decode_object* p_ap_decode_obj);
+int ap_decode_process_header(appledouble_decode_object* p_ap_decode_obj, bool firstime);
+int ap_decode_process_data( appledouble_decode_object* p_ap_decode_obj, bool firstime);
+
+PR_END_EXTERN_C
+
+#if PRAGMA_STRUCT_ALIGN
+ #pragma options align=reset
+#endif
+
+#endif /* ad_codes_h */
diff --git a/mailnews/compose/src/nsMsgAppleDouble.h b/mailnews/compose/src/nsMsgAppleDouble.h
new file mode 100644
index 000000000..f4ae934ad
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAppleDouble.h
@@ -0,0 +1,207 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+* AppleDouble.h
+* -------------
+*
+* The header file for a stream based apple single/double encodor/decodor.
+*
+* 2aug95 mym
+*
+*/
+
+
+#ifndef AppleDouble_h
+#define AppleDouble_h
+
+#include "msgCore.h"
+#include "nsComposeStrings.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+
+#include <CoreServices/CoreServices.h>
+
+#define NOERR 0
+#define errDone 1
+ /* Done with current operation. */
+#define errEOB 2
+ /* End of a buffer. */
+#define errEOP 3
+ /* End of a Part. */
+
+
+#define errFileOpen NS_ERROR_GET_CODE(NS_MSG_UNABLE_TO_OPEN_TMP_FILE)
+#define errFileWrite -202 /*Error writing temporary file.*/
+#define errUsrCancel -2 /*MK_INTERRUPTED */
+#define errDecoding -1
+
+/*
+** The envirment block data type.
+*/
+enum
+{
+ kInit,
+ kDoingHeaderPortion,
+ kDoneHeaderPortion,
+ kDoingDataPortion,
+ kDoneDataPortion
+};
+
+typedef struct _appledouble_encode_object
+{
+ char fname[256];
+ FSIORefNum fileId; /* the id for the open file (data/resource fork) */
+
+ int state;
+ int text_file_type; /* if the file has a text file type with it. */
+ char *boundary; /* the boundary string. */
+
+ int status; /* the error code if anyerror happens. */
+ char b_overflow[200];
+ int s_overflow;
+
+ int state64; /* the left over state of base64 enocding */
+ int ct; /* the character count of base64 encoding */
+ int c1, c2; /* the left of the last base64 encoding */
+
+ char *outbuff; /* the outbuff by the caller. */
+ int s_outbuff; /* the size of the buffer. */
+ int pos_outbuff; /* the offset in the current buffer. */
+
+} appledouble_encode_object;
+
+/* The possible content transfer encodings */
+
+enum
+{
+ kEncodeNone,
+ kEncodeQP,
+ kEncodeBase64,
+ kEncodeUU
+};
+
+enum
+{
+ kGeneralMine,
+ kAppleDouble,
+ kAppleSingle
+};
+
+enum
+{
+ kInline,
+ kDontCare
+};
+
+enum
+{
+ kHeaderPortion,
+ kDataPortion
+};
+
+/* the decode states. */
+enum
+{
+ kBeginParseHeader = 3,
+ kParsingHeader,
+ kBeginSeekBoundary,
+ kSeekingBoundary,
+ kBeginHeaderPortion,
+ kProcessingHeaderPortion,
+ kBeginDataPortion,
+ kProcessingDataPortion,
+ kFinishing
+};
+
+/* uuencode states */
+enum
+{
+ kWaitingForBegin = (int) 0,
+ kBegin,
+ kMainBody,
+ kEnd
+};
+
+typedef struct _appledouble_decode_object
+{
+ int is_binary;
+ int is_apple_single; /* if the object encoded is in apple single */
+ int write_as_binhex;
+
+ int messagetype;
+ char* boundary0; /* the boundary for the enclosure. */
+ int deposition; /* the deposition. */
+ int encoding; /* the encoding method. */
+ int which_part;
+
+ char fname[256];
+ // nsIOFileStream *fileSpec; /* the stream for data fork work. */
+
+ int state;
+
+ int rksize; /* the resource fork size count. */
+ int dksize; /* the data fork size count. */
+
+ int status; /* the error code if anyerror happens. */
+ char b_leftover[256];
+ int s_leftover;
+
+ int encode; /* the encode type of the message. */
+ int state64; /* the left over state of base64 enocding */
+ int left; /* the character count of base64 encoding */
+ int c[4]; /* the left of the last base64 encoding */
+ int uu_starts_line; /* is decoder at the start of a line? (uuencode) */
+ int uu_state; /* state w/r/t the uuencode body */
+ int uu_bytes_written; /* bytes written from the current tuple (uuencode) */
+ int uu_line_bytes; /* encoded bytes remaining in the current line (uuencode) */
+
+ char *inbuff; /* the outbuff by the caller. */
+ int s_inbuff; /* the size of the buffer. */
+ int pos_inbuff; /* the offset in the current buffer. */
+
+
+ nsCOMPtr <nsIFile> tmpFile; /* the temp file to hold the decode data fork */
+ /* when doing the binhex exporting. */
+ nsCOMPtr <nsIOutputStream> tmpFileStream; /* The output File Stream */
+ int32_t data_size; /* the size of the data in the tmp file. */
+
+} appledouble_decode_object;
+
+
+/*
+** The protypes.
+*/
+
+PR_BEGIN_EXTERN_C
+
+int ap_encode_init(appledouble_encode_object *p_ap_encode_obj,
+ const char* fname,
+ char* separator);
+
+int ap_encode_next(appledouble_encode_object* p_ap_encode_obj,
+ char *to_buff,
+ int32_t buff_size,
+ int32_t* real_size);
+
+int ap_encode_end(appledouble_encode_object* p_ap_encode_obj,
+ bool is_aborting);
+
+int ap_decode_init(appledouble_decode_object* p_ap_decode_obj,
+ bool is_apple_single,
+ bool write_as_bin_hex,
+ void *closure);
+
+int ap_decode_next(appledouble_decode_object* p_ap_decode_obj,
+ char *in_buff,
+ int32_t buff_size);
+
+int ap_decode_end(appledouble_decode_object* p_ap_decode_obj,
+ bool is_aborting);
+
+PR_END_EXTERN_C
+
+#endif
diff --git a/mailnews/compose/src/nsMsgAppleDoubleEncode.cpp b/mailnews/compose/src/nsMsgAppleDoubleEncode.cpp
new file mode 100644
index 000000000..4d7178123
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAppleDoubleEncode.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+*
+* apple-double.c
+* --------------
+*
+* The codes to do apple double encoding/decoding.
+*
+* 02aug95 mym created.
+*
+*/
+#include "nsID.h"
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsMsgAppleDouble.h"
+#include "nsMsgAppleCodes.h"
+#include "nsMsgCompUtils.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIMIMEService.h"
+#include "nsMimeTypes.h"
+#include "prmem.h"
+#include "nsNetUtil.h"
+
+
+void
+MacGetFileType(nsIFile *fs,
+ bool *useDefault,
+ char **fileType,
+ char **encoding)
+{
+ if ((fs == NULL) || (fileType == NULL) || (encoding == NULL))
+ return;
+
+ bool exists = false;
+ fs->Exists(&exists);
+ if (!exists)
+ return;
+
+ *useDefault = TRUE;
+ *fileType = NULL;
+ *encoding = NULL;
+
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(fs);
+ FSRef fsRef;
+ FSCatalogInfo catalogInfo;
+ OSErr err = errFileOpen;
+ if (NS_SUCCEEDED(macFile->GetFSRef(&fsRef)))
+ err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo, nullptr, nullptr, nullptr);
+
+ if ( (err != noErr) || (((FileInfo*)(&catalogInfo.finderInfo))->fileType == 'TEXT') )
+ *fileType = strdup(APPLICATION_OCTET_STREAM);
+ else
+ {
+ // At this point, we should call the mime service and
+ // see what we can find out?
+ nsresult rv;
+ nsCOMPtr <nsIURI> tURI;
+ if (NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(tURI), fs)) && tURI)
+ {
+ nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && mimeFinder)
+ {
+ nsAutoCString mimeType;
+ rv = mimeFinder->GetTypeFromURI(tURI, mimeType);
+ if (NS_SUCCEEDED(rv))
+ {
+ *fileType = ToNewCString(mimeType);
+ return;
+ }
+ }
+ }
+
+ // If we hit here, return something...default to this...
+ *fileType = strdup(APPLICATION_OCTET_STREAM);
+ }
+}
+
+//#pragma cplusplus reset
+
+/*
+* ap_encode_init
+* --------------
+*
+* Setup the encode envirment
+*/
+
+int ap_encode_init( appledouble_encode_object *p_ap_encode_obj,
+ const char *fname,
+ char *separator)
+{
+ nsCOMPtr <nsIFile> myFile;
+ NS_NewNativeLocalFile(nsDependentCString(fname), true, getter_AddRefs(myFile));
+ bool exists;
+ if (myFile && NS_SUCCEEDED(myFile->Exists(&exists)) && !exists)
+ return -1;
+
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(myFile);
+ nsAutoCString path;
+ macFile->GetNativePath(path);
+
+ memset(p_ap_encode_obj, 0, sizeof(appledouble_encode_object));
+
+ /*
+ ** Fill out the source file inforamtion.
+ */
+ memcpy(p_ap_encode_obj->fname, path.get(), path.Length());
+ p_ap_encode_obj->fname[path.Length()] = '\0';
+
+ p_ap_encode_obj->boundary = strdup(separator);
+ return noErr;
+}
+
+/*
+** ap_encode_next
+** --------------
+**
+** return :
+** noErr : everything is ok
+** errDone : when encoding is done.
+** errors : otherwise.
+*/
+int ap_encode_next(
+ appledouble_encode_object* p_ap_encode_obj,
+ char *to_buff,
+ int32_t buff_size,
+ int32_t* real_size)
+{
+ int status;
+
+ /*
+ ** install the out buff now.
+ */
+ p_ap_encode_obj->outbuff = to_buff;
+ p_ap_encode_obj->s_outbuff = buff_size;
+ p_ap_encode_obj->pos_outbuff = 0;
+
+ /*
+ ** first copy the outstandind data in the overflow buff to the out buffer.
+ */
+ if (p_ap_encode_obj->s_overflow)
+ {
+ status = write_stream(p_ap_encode_obj,
+ (const char*)(p_ap_encode_obj->b_overflow),
+ p_ap_encode_obj->s_overflow);
+ if (status != noErr)
+ return status;
+
+ p_ap_encode_obj->s_overflow = 0;
+ }
+
+ /*
+ ** go the next processing stage based on the current state.
+ */
+ switch (p_ap_encode_obj->state)
+ {
+ case kInit:
+ /*
+ ** We are in the starting position, fill out the header.
+ */
+ status = fill_apple_mime_header(p_ap_encode_obj);
+ if (status != noErr)
+ break; /* some error happens */
+
+ p_ap_encode_obj->state = kDoingHeaderPortion;
+ status = ap_encode_header(p_ap_encode_obj, true);
+ /* it is the first time to calling */
+ if (status == errDone)
+ {
+ p_ap_encode_obj->state = kDoneHeaderPortion;
+ }
+ else
+ {
+ break; /* we need more work on header portion. */
+ }
+
+ /*
+ ** we are done with the header, so let's go to the data port.
+ */
+ p_ap_encode_obj->state = kDoingDataPortion;
+ status = ap_encode_data(p_ap_encode_obj, true);
+ /* it is first time call do data portion */
+
+ if (status == errDone)
+ {
+ p_ap_encode_obj->state = kDoneDataPortion;
+ status = noErr;
+ }
+ break;
+
+ case kDoingHeaderPortion:
+
+ status = ap_encode_header(p_ap_encode_obj, false);
+ /* continue with the header portion. */
+ if (status == errDone)
+ {
+ p_ap_encode_obj->state = kDoneHeaderPortion;
+ }
+ else
+ {
+ break; /* we need more work on header portion. */
+ }
+
+ /*
+ ** start the data portion.
+ */
+ p_ap_encode_obj->state = kDoingDataPortion;
+ status = ap_encode_data(p_ap_encode_obj, true);
+ /* it is the first time calling */
+ if (status == errDone)
+ {
+ p_ap_encode_obj->state = kDoneDataPortion;
+ status = noErr;
+ }
+ break;
+
+ case kDoingDataPortion:
+
+ status = ap_encode_data(p_ap_encode_obj, false);
+ /* it is not the first time */
+
+ if (status == errDone)
+ {
+ p_ap_encode_obj->state = kDoneDataPortion;
+ status = noErr;
+ }
+ break;
+
+ case kDoneDataPortion:
+ status = errDone; /* we are really done. */
+
+ break;
+ }
+
+ *real_size = p_ap_encode_obj->pos_outbuff;
+ return status;
+}
+
+/*
+** ap_encode_end
+** -------------
+**
+** clear the apple encoding.
+*/
+
+int ap_encode_end(
+ appledouble_encode_object *p_ap_encode_obj,
+ bool is_aborting)
+{
+ /*
+ ** clear up the apple doubler.
+ */
+ if (p_ap_encode_obj == NULL)
+ return noErr;
+
+ if (p_ap_encode_obj->fileId) /* close the file if it is open. */
+ ::FSCloseFork(p_ap_encode_obj->fileId);
+
+ PR_FREEIF(p_ap_encode_obj->boundary); /* the boundary string. */
+
+ return noErr;
+}
diff --git a/mailnews/compose/src/nsMsgAppleEncode.cpp b/mailnews/compose/src/nsMsgAppleEncode.cpp
new file mode 100644
index 000000000..27e39a8cd
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAppleEncode.cpp
@@ -0,0 +1,703 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ *
+ * apple_double_encode.c
+ * ---------------------
+ *
+ * The routines doing the Apple Double Encoding.
+ *
+ * 2aug95 mym Created.
+ *
+ */
+
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsMimeTypes.h"
+#include "prprf.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgAppleDouble.h"
+#include "nsMsgAppleCodes.h"
+#include "nsILocalFileMac.h"
+
+/*
+** Local Functions prototypes.
+*/
+static int output64chunk( appledouble_encode_object* p_ap_encode_obj,
+ int c1, int c2, int c3, int pads);
+
+static int to64(appledouble_encode_object* p_ap_encode_obj,
+ char *p,
+ int in_size);
+
+static int finish64(appledouble_encode_object* p_ap_encode_obj);
+
+
+#define BUFF_LEFT(p) ((p)->s_outbuff - (p)->pos_outbuff)
+
+/*
+** write_stream.
+*/
+int write_stream(
+ appledouble_encode_object *p_ap_encode_obj,
+ const char *out_string,
+ int len)
+{
+ if (p_ap_encode_obj->pos_outbuff + len < p_ap_encode_obj->s_outbuff)
+ {
+ memcpy(p_ap_encode_obj->outbuff + p_ap_encode_obj->pos_outbuff,
+ out_string,
+ len);
+ p_ap_encode_obj->pos_outbuff += len;
+ return noErr;
+ }
+ else
+ {
+ /*
+ ** If the buff doesn't have enough space, use the overflow buffer then.
+ */
+ int s_len = p_ap_encode_obj->s_outbuff - p_ap_encode_obj->pos_outbuff;
+
+ memcpy(p_ap_encode_obj->outbuff + p_ap_encode_obj->pos_outbuff,
+ out_string,
+ s_len);
+ memcpy(p_ap_encode_obj->b_overflow + p_ap_encode_obj->s_overflow,
+ out_string + s_len,
+ p_ap_encode_obj->s_overflow += (len - s_len));
+ p_ap_encode_obj->pos_outbuff += s_len;
+ return errEOB;
+ }
+}
+
+int fill_apple_mime_header(
+ appledouble_encode_object *p_ap_encode_obj)
+{
+ int status;
+
+ char tmpstr[266];
+
+#if 0
+// strcpy(tmpstr, "Content-Type: multipart/mixed; boundary=\"-\"\n\n---\n");
+// status = write_stream(p_ap_encode_env,
+// tmpstr,
+// strlen(tmpstr));
+// if (status != noErr)
+// return status;
+
+ PR_snprintf(tmpstr, sizeof(tmpstr),
+ "Content-Type: multipart/appledouble; boundary=\"=\"; name=\"");
+ status = write_stream(p_ap_encode_obj, (const char*)tmpstr, strlen(tmpstr));
+ if (status != noErr)
+ return status;
+
+ status = write_stream(p_ap_encode_obj,
+ p_ap_encode_obj->fname,
+ strlen(p_ap_encode_obj->fname));
+ if (status != noErr)
+ return status;
+
+ PR_snprintf(tmpstr, sizeof(tmpstr),
+ "\"\r\nContent-Disposition: inline; filename=\"%s\"\r\n\r\n\r\n--=\r\n",
+ p_ap_encode_obj->fname);
+#endif /* 0 */
+ PR_snprintf(tmpstr, sizeof(tmpstr), "--%s" CRLF, p_ap_encode_obj->boundary);
+ status = write_stream(p_ap_encode_obj, (const char*)tmpstr, strlen(tmpstr));
+ return status;
+}
+
+int ap_encode_file_infor(
+ appledouble_encode_object *p_ap_encode_obj)
+{
+ ap_header head;
+ ap_entry entries[NUM_ENTRIES];
+ ap_dates dates;
+ short i;
+ long comlen;
+ char comment[256];
+ int status;
+
+ nsCOMPtr <nsIFile> resFile;
+ NS_NewNativeLocalFile(nsDependentCString(p_ap_encode_obj->fname), true,
+ getter_AddRefs(resFile));
+ if (!resFile)
+ return errFileOpen;
+
+ FSRef ref;
+ nsCOMPtr <nsILocalFileMac> macFile = do_QueryInterface(resFile);
+ if (NS_FAILED(macFile->GetFSRef(&ref)))
+ return errFileOpen;
+
+ FSCatalogInfo catalogInfo;
+ if (::FSGetCatalogInfo(&ref, kFSCatInfoFinderInfo, &catalogInfo, nullptr, nullptr, nullptr) != noErr)
+ {
+ return errFileOpen;
+ }
+
+ /* get a file comment, if possible */
+#if 1
+ // Carbon doesn't support GetWDInfo(). (Bug 555684)
+
+ // not sure why working directories are needed here...
+ comlen = 0;
+#else
+ long procID;
+ procID = 0;
+ GetWDInfo(p_ap_encode_obj->vRefNum, &fpb->ioVRefNum, &fpb->ioDirID, &procID);
+ IOParam vinfo;
+ memset((void *) &vinfo, '\0', sizeof (vinfo));
+ GetVolParmsInfoBuffer vp;
+ vinfo.ioCompletion = nil;
+ vinfo.ioVRefNum = fpb->ioVRefNum;
+ vinfo.ioBuffer = (Ptr) &vp;
+ vinfo.ioReqCount = sizeof (vp);
+ comlen = 0;
+ if (PBHGetVolParmsSync((HParmBlkPtr) &vinfo) == noErr &&
+ ((vp.vMAttrib >> bHasDesktopMgr) & 1))
+ {
+ DTPBRec dtp;
+ memset((void *) &dtp, '\0', sizeof (dtp));
+ dtp.ioVRefNum = fpb->ioVRefNum;
+ if (PBDTGetPath(&dtp) == noErr)
+ {
+ dtp.ioCompletion = nil;
+ dtp.ioDTBuffer = (Ptr) comment;
+ dtp.ioNamePtr = fpb->ioNamePtr;
+ dtp.ioDirID = fpb->ioFlParID;
+ if (PBDTGetCommentSync(&dtp) == noErr)
+ comlen = dtp.ioDTActCount;
+ }
+ }
+#endif /* ! 1 */
+
+ /* write header */
+// head.magic = dfork ? APPLESINGLE_MAGIC : APPLEDOUBLE_MAGIC;
+ head.magic = APPLEDOUBLE_MAGIC; /* always do apple double */
+ head.version = VERSION;
+ memset(head.fill, '\0', sizeof (head.fill));
+ head.entries = NUM_ENTRIES - 1;
+ status = to64(p_ap_encode_obj,
+ (char *) &head,
+ sizeof (head));
+ if (status != noErr)
+ return status;
+
+ /* write entry descriptors */
+ nsAutoCString leafname;
+ macFile->GetNativeLeafName(leafname);
+ entries[0].offset = sizeof (head) + sizeof (ap_entry) * head.entries;
+ entries[0].id = ENT_NAME;
+ entries[0].length = leafname.Length();
+ entries[1].id = ENT_FINFO;
+ entries[1].length = sizeof (FInfo) + sizeof (FXInfo);
+ entries[2].id = ENT_DATES;
+ entries[2].length = sizeof (ap_dates);
+ entries[3].id = ENT_COMMENT;
+ entries[3].length = comlen;
+ entries[4].id = ENT_RFORK;
+ entries[4].length = catalogInfo.rsrcLogicalSize;
+ entries[5].id = ENT_DFORK;
+ entries[5].length = catalogInfo.dataLogicalSize;
+
+ /* correct the link in the entries. */
+ for (i = 1; i < NUM_ENTRIES; ++i)
+ {
+ entries[i].offset = entries[i-1].offset + entries[i-1].length;
+ }
+ status = to64(p_ap_encode_obj,
+ (char *) entries,
+ sizeof (ap_entry) * head.entries);
+ if (status != noErr)
+ return status;
+
+ /* write name */
+ status = to64(p_ap_encode_obj,
+ (char *) leafname.get(),
+ leafname.Length());
+ if (status != noErr)
+ return status;
+
+ /* write finder info */
+ status = to64(p_ap_encode_obj,
+ (char *) &catalogInfo.finderInfo,
+ sizeof (FInfo));
+ if (status != noErr)
+ return status;
+
+ status = to64(p_ap_encode_obj,
+ (char *) &catalogInfo.extFinderInfo,
+ sizeof (FXInfo));
+ if (status != noErr)
+ return status;
+
+ /* write dates */
+ dates.create = catalogInfo.createDate.lowSeconds + CONVERT_TIME;
+ dates.modify = catalogInfo.contentModDate.lowSeconds + CONVERT_TIME;
+ dates.backup = catalogInfo.backupDate.lowSeconds + CONVERT_TIME;
+ dates.access = catalogInfo.accessDate.lowSeconds + CONVERT_TIME;
+ status = to64(p_ap_encode_obj,
+ (char *) &dates,
+ sizeof (ap_dates));
+ if (status != noErr)
+ return status;
+
+ /* write comment */
+ if (comlen)
+ {
+ status = to64(p_ap_encode_obj,
+ comment,
+ comlen * sizeof(char));
+ }
+ /*
+ ** Get some help information on deciding the file type.
+ */
+ if (((FileInfo*)(&catalogInfo.finderInfo))->fileType == 'TEXT' ||
+ ((FileInfo*)(&catalogInfo.finderInfo))->fileType == 'text')
+ {
+ p_ap_encode_obj->text_file_type = true;
+ }
+
+ return status;
+}
+/*
+** ap_encode_header
+**
+** encode the file header and the resource fork.
+**
+*/
+int ap_encode_header(
+ appledouble_encode_object* p_ap_encode_obj,
+ bool firstime)
+{
+ char rd_buff[256];
+ FSIORefNum fileId;
+ OSErr retval = noErr;
+ int status;
+ ByteCount inCount;
+
+ if (firstime)
+ {
+ PL_strcpy(rd_buff,
+ "Content-Type: application/applefile\r\nContent-Transfer-Encoding: base64\r\n\r\n");
+ status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff));
+ if (status != noErr)
+ return status;
+
+ status = ap_encode_file_infor(p_ap_encode_obj);
+ if (status != noErr)
+ return status;
+
+ /*
+ ** preparing to encode the resource fork.
+ */
+ nsCOMPtr <nsIFile> myFile;
+ NS_NewNativeLocalFile(nsDependentCString(p_ap_encode_obj->fname), true, getter_AddRefs(myFile));
+ if (!myFile)
+ return errFileOpen;
+
+ FSRef ref;
+ nsCOMPtr <nsILocalFileMac> macFile = do_QueryInterface(myFile);
+ if (NS_FAILED(macFile->GetFSRef(&ref)))
+ return errFileOpen;
+
+ HFSUniStr255 forkName;
+ ::FSGetResourceForkName(&forkName);
+ retval = ::FSOpenFork(&ref, forkName.length, forkName.unicode, fsRdPerm, &p_ap_encode_obj->fileId);
+ if (retval != noErr)
+ return retval;
+ }
+
+ fileId = p_ap_encode_obj->fileId;
+ while (retval == noErr)
+ {
+ if (BUFF_LEFT(p_ap_encode_obj) < 400)
+ break;
+
+ inCount = 0;
+ retval = ::FSReadFork(fileId, fsAtMark, 0, 256, rd_buff, &inCount);
+ if (inCount)
+ {
+ status = to64(p_ap_encode_obj,
+ rd_buff,
+ inCount);
+ if (status != noErr)
+ return status;
+ }
+ }
+
+ if (retval == eofErr)
+ {
+ ::FSCloseFork(fileId);
+ p_ap_encode_obj->fileId = 0;
+
+ status = finish64(p_ap_encode_obj);
+ if (status != noErr)
+ return status;
+
+ /*
+ ** write out the boundary
+ */
+ PR_snprintf(rd_buff, sizeof(rd_buff),
+ CRLF "--%s" CRLF,
+ p_ap_encode_obj->boundary);
+
+ status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff));
+ if (status == noErr)
+ status = errDone;
+ }
+ return status;
+}
+
+#if 0
+// This is unused for now and Clang complains about that is it is ifdefed out
+static void replace(char *p, int len, char frm, char to)
+{
+ for (; len > 0; len--, p++)
+ if (*p == frm) *p = to;
+}
+#endif
+
+/* Description of the various file formats and their magic numbers */
+struct magic
+{
+ const char *name; /* Name of the file format */
+ const char *num; /* The magic number */
+ int len; /* Length (0 means strlen(magicnum)) */
+};
+
+/* The magic numbers of the file formats we know about */
+static struct magic magic[] =
+{
+ { "image/gif", "GIF", 0 },
+ { "image/jpeg", "\377\330\377", 0 },
+ { "video/mpeg", "\0\0\001\263", 4 },
+ { "application/postscript", "%!", 0 },
+};
+static int num_magic = MOZ_ARRAY_LENGTH(magic);
+
+static const char *text_type = TEXT_PLAIN; /* the text file type. */
+static const char *default_type = APPLICATION_OCTET_STREAM;
+
+
+/*
+ * Determins the format of the file "inputf". The name
+ * of the file format (or NULL on error) is returned.
+ */
+static const char *magic_look(char *inbuff, int numread)
+{
+ int i, j;
+
+ for (i=0; i<num_magic; i++)
+ {
+ if (magic[i].len == 0)
+ magic[i].len = strlen(magic[i].num);
+ }
+
+ for (i=0; i<num_magic; i++)
+ {
+ if (numread >= magic[i].len)
+ {
+ for (j=0; j<magic[i].len; j++)
+ {
+ if (inbuff[j] != magic[i].num[j]) break;
+ }
+
+ if (j == magic[i].len)
+ return magic[i].name;
+ }
+ }
+
+ return default_type;
+}
+/*
+** ap_encode_data
+**
+** ---------------
+**
+** encode on the data fork.
+**
+*/
+int ap_encode_data(
+ appledouble_encode_object* p_ap_encode_obj,
+ bool firstime)
+{
+ char rd_buff[256];
+ FSIORefNum fileId;
+ OSErr retval = noErr;
+ ByteCount in_count;
+ int status;
+
+ if (firstime)
+ {
+ const char* magic_type;
+
+ /*
+ ** preparing to encode the data fork.
+ */
+ nsCOMPtr <nsIFile> resFile;
+ NS_NewNativeLocalFile(nsDependentCString(p_ap_encode_obj->fname), true,
+ getter_AddRefs(resFile));
+ if (!resFile)
+ return errFileOpen;
+
+ FSRef ref;
+ nsCOMPtr <nsILocalFileMac> macFile = do_QueryInterface(resFile);
+ if (NS_FAILED(macFile->GetFSRef(&ref)))
+ return errFileOpen;
+
+ HFSUniStr255 forkName;
+ ::FSGetDataForkName(&forkName);
+ retval = ::FSOpenFork(&ref, forkName.length, forkName.unicode, fsRdPerm, &fileId);
+ if (retval != noErr)
+ return retval;
+
+ p_ap_encode_obj->fileId = fileId;
+
+
+ if (!p_ap_encode_obj->text_file_type)
+ {
+ /*
+ ** do a smart check for the file type.
+ */
+ in_count = 0;
+ retval = ::FSReadFork(fileId, fsFromStart, 0, 256, rd_buff, &in_count);
+ magic_type = magic_look(rd_buff, in_count);
+
+ /* don't forget to rewind the index to start point. */
+ ::FSSetForkPosition(fileId, fsFromStart, 0);
+ /* and reset retVal just in case... */
+ if (retval == eofErr)
+ retval = noErr;
+ }
+ else
+ {
+ magic_type = text_type; /* we already know it is a text type. */
+ }
+
+ /*
+ ** the data portion header information.
+ */
+ nsAutoCString leafName;
+ resFile->GetNativeLeafName(leafName);
+ PR_snprintf(rd_buff, sizeof(rd_buff),
+ "Content-Type: %s; name=\"%s\"" CRLF "Content-Transfer-Encoding: base64" CRLF "Content-Disposition: inline; filename=\"%s\"" CRLF CRLF,
+ magic_type,
+ leafName.get(),
+ leafName.get());
+
+ status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff));
+ if (status != noErr)
+ return status;
+ }
+
+ while (retval == noErr)
+ {
+ if (BUFF_LEFT(p_ap_encode_obj) < 400)
+ break;
+
+ in_count = 0;
+ retval = ::FSReadFork(p_ap_encode_obj->fileId, fsAtMark, 0, 256, rd_buff, &in_count);
+ if (in_count)
+ {
+#if 0
+/* replace(rd_buff, in_count, '\r', '\n'); */
+#endif
+/* ** may be need to do character set conversion here for localization. ** */
+ status = to64(p_ap_encode_obj,
+ rd_buff,
+ in_count);
+ if (status != noErr)
+ return status;
+ }
+ }
+
+ if (retval == eofErr)
+ {
+ ::FSCloseFork(p_ap_encode_obj->fileId);
+ p_ap_encode_obj->fileId = 0;
+
+ status = finish64(p_ap_encode_obj);
+ if (status != noErr)
+ return status;
+
+ /* write out the boundary */
+
+ PR_snprintf(rd_buff, sizeof(rd_buff),
+ CRLF "--%s--" CRLF CRLF,
+ p_ap_encode_obj->boundary);
+
+ status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff));
+
+ if (status == noErr)
+ status = errDone;
+ }
+ return status;
+}
+
+static char basis_64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*
+** convert the stream in the inbuff to 64 format and put it in the out buff.
+** To make the life easier, the caller will responcable of the cheking of the outbuff's bundary.
+*/
+static int
+to64(appledouble_encode_object* p_ap_encode_obj,
+ char *p,
+ int in_size)
+{
+ int status;
+ int c1, c2, c3, ct;
+ unsigned char *inbuff = (unsigned char*)p;
+
+ ct = p_ap_encode_obj->ct; /* the char count left last time. */
+
+ /*
+ ** resume the left state of the last conversion.
+ */
+ switch (p_ap_encode_obj->state64)
+ {
+ case 0:
+ p_ap_encode_obj->c1 = c1 = *inbuff ++;
+ if (--in_size <= 0)
+ {
+ p_ap_encode_obj->state64 = 1;
+ return noErr;
+ }
+ p_ap_encode_obj->c2 = c2 = *inbuff ++;
+ if (--in_size <= 0)
+ {
+ p_ap_encode_obj->state64 = 2;
+ return noErr;
+ }
+ c3 = *inbuff ++; --in_size;
+ break;
+ case 1:
+ c1 = p_ap_encode_obj->c1;
+ p_ap_encode_obj->c2 = c2 = *inbuff ++;
+ if (--in_size <= 0)
+ {
+ p_ap_encode_obj->state64 = 2;
+ return noErr;
+ }
+ c3 = *inbuff ++; --in_size;
+ break;
+ case 2:
+ c1 = p_ap_encode_obj->c1;
+ c2 = p_ap_encode_obj->c2;
+ c3 = *inbuff ++; --in_size;
+ break;
+ }
+
+ while (in_size >= 0)
+ {
+ status = output64chunk(p_ap_encode_obj,
+ c1,
+ c2,
+ c3,
+ 0);
+ if (status != noErr)
+ return status;
+
+ ct += 4;
+ if (ct > 71)
+ {
+ status = write_stream(p_ap_encode_obj,
+ CRLF,
+ 2);
+ if (status != noErr)
+ return status;
+
+ ct = 0;
+ }
+
+ if (in_size <= 0)
+ {
+ p_ap_encode_obj->state64 = 0;
+ break;
+ }
+
+ c1 = (int)*inbuff++;
+ if (--in_size <= 0)
+ {
+ p_ap_encode_obj->c1 = c1;
+ p_ap_encode_obj->state64 = 1;
+ break;
+ }
+ c2 = *inbuff++;
+ if (--in_size <= 0)
+ {
+ p_ap_encode_obj->c1 = c1;
+ p_ap_encode_obj->c2 = c2;
+ p_ap_encode_obj->state64 = 2;
+ break;
+ }
+ c3 = *inbuff++;
+ in_size--;
+ }
+ p_ap_encode_obj->ct = ct;
+ return status;
+}
+
+/*
+** clear the left base64 encodes.
+*/
+static int
+finish64(appledouble_encode_object* p_ap_encode_obj)
+{
+ int status;
+
+ switch (p_ap_encode_obj->state64)
+ {
+ case 0:
+ break;
+ case 1:
+ status = output64chunk(p_ap_encode_obj,
+ p_ap_encode_obj->c1,
+ 0,
+ 0,
+ 2);
+ break;
+ case 2:
+ status = output64chunk(p_ap_encode_obj,
+ p_ap_encode_obj->c1,
+ p_ap_encode_obj->c2,
+ 0,
+ 1);
+ break;
+ }
+ status = write_stream(p_ap_encode_obj, CRLF, 2);
+ p_ap_encode_obj->state64 = 0;
+ p_ap_encode_obj->ct = 0;
+ return status;
+}
+
+static int output64chunk(
+ appledouble_encode_object* p_ap_encode_obj,
+ int c1, int c2, int c3, int pads)
+{
+ char tmpstr[32];
+ char *p = tmpstr;
+
+ *p++ = basis_64[c1>>2];
+ *p++ = basis_64[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)];
+ if (pads == 2)
+ {
+ *p++ = '=';
+ *p++ = '=';
+ }
+ else if (pads)
+ {
+ *p++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)];
+ *p++ = '=';
+ }
+ else
+ {
+ *p++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)];
+ *p++ = basis_64[c3 & 0x3F];
+ }
+ return write_stream(p_ap_encode_obj, (const char*) tmpstr, p-tmpstr);
+}
diff --git a/mailnews/compose/src/nsMsgAttachment.cpp b/mailnews/compose/src/nsMsgAttachment.cpp
new file mode 100644
index 000000000..3a828c3b0
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAttachment.cpp
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgAttachment.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+
+NS_IMPL_ISUPPORTS(nsMsgAttachment, nsIMsgAttachment)
+
+nsMsgAttachment::nsMsgAttachment()
+{
+ mTemporary = false;
+ mSendViaCloud = false;
+ mSize = -1;
+}
+
+nsMsgAttachment::~nsMsgAttachment()
+{
+ if (mTemporary && !mSendViaCloud)
+ (void)DeleteAttachment();
+}
+
+/* attribute wstring name; */
+NS_IMETHODIMP nsMsgAttachment::GetName(nsAString & aName)
+{
+ aName = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetName(const nsAString & aName)
+{
+ mName = aName;
+ return NS_OK;
+}
+
+/* attribute string url; */
+NS_IMETHODIMP nsMsgAttachment::GetUrl(nsACString & aUrl)
+{
+ aUrl = mUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetUrl(const nsACString & aUrl)
+{
+ mUrl = aUrl;
+ return NS_OK;
+}
+
+/* attribute string urlCharset; */
+NS_IMETHODIMP nsMsgAttachment::GetUrlCharset(nsACString & aUrlCharset)
+{
+ aUrlCharset = mUrlCharset;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgAttachment::SetUrlCharset(const nsACString & aUrlCharset)
+{
+ mUrlCharset = aUrlCharset;
+ return NS_OK;
+}
+
+/* attribute boolean temporary; */
+NS_IMETHODIMP nsMsgAttachment::GetTemporary(bool *aTemporary)
+{
+ NS_ENSURE_ARG_POINTER(aTemporary);
+
+ *aTemporary = mTemporary;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgAttachment::SetTemporary(bool aTemporary)
+{
+ mTemporary = aTemporary;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetSendViaCloud(bool *aSendViaCloud)
+{
+ NS_ENSURE_ARG_POINTER(aSendViaCloud);
+
+ *aSendViaCloud = mSendViaCloud;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgAttachment::SetSendViaCloud(bool aSendViaCloud)
+{
+ mSendViaCloud = aSendViaCloud;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetHtmlAnnotation(const nsAString &aAnnotation)
+{
+ mHtmlAnnotation = aAnnotation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetHtmlAnnotation(nsAString &aAnnotation)
+{
+ aAnnotation = mHtmlAnnotation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAttachment::SetCloudProviderKey(const nsACString &aCloudProviderKey)
+{
+ mCloudProviderKey = aCloudProviderKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAttachment::GetCloudProviderKey(nsACString &aCloudProviderKey)
+{
+ aCloudProviderKey = mCloudProviderKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetContentLocation(nsACString &aContentLocation)
+{
+ aContentLocation = mContentLocation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetContentLocation(const nsACString &aContentLocation)
+{
+ mContentLocation = aContentLocation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetContentType(char * *aContentType)
+{
+ NS_ENSURE_ARG_POINTER(aContentType);
+
+ *aContentType = ToNewCString(mContentType);
+ return (*aContentType ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetContentType(const char * aContentType)
+{
+ mContentType = aContentType;
+ /* a full content type could also contains parameters but we need to
+ keep only the content type alone. Therefore we need to cleanup it.
+ */
+ int32_t offset = mContentType.FindChar(';');
+ if (offset >= 0)
+ mContentType.SetLength(offset);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetContentTypeParam(char * *aContentTypeParam)
+{
+ NS_ENSURE_ARG_POINTER(aContentTypeParam);
+
+ *aContentTypeParam = ToNewCString(mContentTypeParam);
+ return (*aContentTypeParam ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetContentTypeParam(const char * aContentTypeParam)
+{
+ if (aContentTypeParam)
+ while (*aContentTypeParam == ';' || *aContentTypeParam == ' ')
+ aContentTypeParam ++;
+ mContentTypeParam = aContentTypeParam;
+
+ return NS_OK;
+}
+
+/* attribute string charset; */
+NS_IMETHODIMP nsMsgAttachment::GetCharset(char * *aCharset)
+{
+ NS_ENSURE_ARG_POINTER(aCharset);
+
+ *aCharset = ToNewCString(mCharset);
+ return (*aCharset ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+}
+NS_IMETHODIMP nsMsgAttachment::SetCharset(const char * aCharset)
+{
+ mCharset = aCharset;
+ return NS_OK;
+}
+
+/* attribute string macType; */
+NS_IMETHODIMP nsMsgAttachment::GetMacType(char * *aMacType)
+{
+ NS_ENSURE_ARG_POINTER(aMacType);
+
+ *aMacType = ToNewCString(mMacType);
+ return (*aMacType ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+}
+NS_IMETHODIMP nsMsgAttachment::SetMacType(const char * aMacType)
+{
+ mMacType = aMacType;
+ return NS_OK;
+}
+
+/* attribute string macCreator; */
+NS_IMETHODIMP nsMsgAttachment::GetMacCreator(char * *aMacCreator)
+{
+ NS_ENSURE_ARG_POINTER(aMacCreator);
+
+ *aMacCreator = ToNewCString(mMacCreator);
+ return (*aMacCreator ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+}
+NS_IMETHODIMP nsMsgAttachment::SetMacCreator(const char * aMacCreator)
+{
+ mMacCreator = aMacCreator;
+ return NS_OK;
+}
+
+/* attribute int64_t size; */
+NS_IMETHODIMP nsMsgAttachment::GetSize(int64_t *aSize)
+{
+ NS_ENSURE_ARG_POINTER(aSize);
+
+ *aSize = mSize;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgAttachment::SetSize(int64_t aSize)
+{
+ mSize = aSize;
+ return NS_OK;
+}
+
+/* boolean equalsUrl (in nsIMsgAttachment attachment); */
+NS_IMETHODIMP nsMsgAttachment::EqualsUrl(nsIMsgAttachment *attachment, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(attachment);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsAutoCString url;
+ attachment->GetUrl(url);
+
+ *_retval = mUrl.Equals(url);
+ return NS_OK;
+}
+
+
+nsresult nsMsgAttachment::DeleteAttachment()
+{
+ nsresult rv;
+ bool isAFile = false;
+
+ nsCOMPtr<nsIFile> urlFile;
+ rv = NS_GetFileFromURLSpec(mUrl, getter_AddRefs(urlFile));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't nsIFile from URL string");
+ if (NS_SUCCEEDED(rv))
+ {
+ bool bExists = false;
+ rv = urlFile->Exists(&bExists);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Exists() call failed!");
+ if (NS_SUCCEEDED(rv) && bExists)
+ {
+ rv = urlFile->IsFile(&isAFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "IsFile() call failed!");
+ }
+ }
+
+ // remove it if it's a valid file
+ if (isAFile)
+ rv = urlFile->Remove(false);
+
+ return rv;
+}
diff --git a/mailnews/compose/src/nsMsgAttachment.h b/mailnews/compose/src/nsMsgAttachment.h
new file mode 100644
index 000000000..9f0d0a3b0
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAttachment.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgAttachment_H_
+#define _nsMsgAttachment_H_
+
+#include "nsIMsgAttachment.h"
+#include "nsStringGlue.h"
+
+class nsMsgAttachment : public nsIMsgAttachment
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGATTACHMENT
+
+ nsMsgAttachment();
+
+private:
+ virtual ~nsMsgAttachment();
+ nsresult DeleteAttachment();
+
+ nsString mName;
+ nsCString mUrl;
+ nsCString mUrlCharset;
+ bool mTemporary;
+ bool mSendViaCloud;
+ nsCString mCloudProviderKey;
+ nsCString mContentLocation;
+ nsCString mContentType;
+ nsCString mContentTypeParam;
+ nsCString mCharset;
+ nsCString mMacType;
+ nsCString mMacCreator;
+ nsString mHtmlAnnotation;
+ int64_t mSize;
+};
+
+
+#endif /* _nsMsgAttachment_H_ */
diff --git a/mailnews/compose/src/nsMsgAttachmentHandler.cpp b/mailnews/compose/src/nsMsgAttachmentHandler.cpp
new file mode 100644
index 000000000..4994c3fe6
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAttachmentHandler.cpp
@@ -0,0 +1,1384 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIPrefLocalizedString.h"
+#include "nsILineInputStream.h"
+#include "nsMsgAttachmentHandler.h"
+#include "prmem.h"
+#include "nsMsgCopy.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsMsgSend.h"
+#include "nsMsgCompUtils.h"
+#include "nsMsgI18N.h"
+#include "nsURLFetcher.h"
+#include "nsMimeTypes.h"
+#include "nsMsgCompCID.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#include "nsMsgPrompts.h"
+#include "nsTextFormatter.h"
+#include "nsIPrompt.h"
+#include "nsITextToSubURI.h"
+#include "nsIURL.h"
+#include "nsIFileURL.h"
+#include "nsNetCID.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsMsgMimeCID.h"
+#include "nsNetUtil.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsComposeStrings.h"
+#include "nsIZipWriter.h"
+#include "nsIDirectoryEnumerator.h"
+#include "mozilla/Services.h"
+#include "mozilla/mailnews/MimeEncoder.h"
+#include "nsIPrincipal.h"
+
+///////////////////////////////////////////////////////////////////////////
+// Mac Specific Attachment Handling for AppleDouble Encoded Files
+///////////////////////////////////////////////////////////////////////////
+#ifdef XP_MACOSX
+
+#define AD_WORKING_BUFF_SIZE FILE_IO_BUFFER_SIZE
+
+extern void MacGetFileType(nsIFile *fs, bool *useDefault, char **type, char **encoding);
+
+#include "nsILocalFileMac.h"
+
+/* static */
+nsresult nsSimpleZipper::Zip(nsIFile *aInputFile, nsIFile *aOutputFile)
+{
+ // create zipwriter object
+ nsresult rv;
+ nsCOMPtr<nsIZipWriter> zipWriter = do_CreateInstance("@mozilla.org/zipwriter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = zipWriter->Open(aOutputFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddToZip(zipWriter, aInputFile, EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we're done.
+ zipWriter->Close();
+ return rv;
+}
+
+/* static */
+nsresult nsSimpleZipper::AddToZip(nsIZipWriter *aZipWriter,
+ nsIFile *aFile,
+ const nsACString &aPath)
+{
+ // find out the path this file/dir should have in the zip
+ nsCString leafName;
+ aFile->GetNativeLeafName(leafName);
+ nsCString currentPath(aPath);
+ currentPath += leafName;
+
+ bool isDirectory;
+ aFile->IsDirectory(&isDirectory);
+ // append slash for a directory entry
+ if (isDirectory)
+ currentPath.Append('/');
+
+ // add the file or directory entry to the zip
+ nsresult rv = aZipWriter->AddEntryFile(currentPath, nsIZipWriter::COMPRESSION_DEFAULT, aFile, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if it's a directory, add all its contents too
+ if (isDirectory) {
+ nsCOMPtr<nsISimpleEnumerator> e;
+ nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(e));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator = do_QueryInterface(e, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> currentFile;
+ while (NS_SUCCEEDED(dirEnumerator->GetNextFile(getter_AddRefs(currentFile))) && currentFile) {
+ rv = AddToZip(aZipWriter, currentFile, currentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+#endif // XP_MACOSX
+
+//
+// Class implementation...
+//
+nsMsgAttachmentHandler::nsMsgAttachmentHandler() :
+ mRequest(nullptr),
+ mCompFields(nullptr), // Message composition fields for the sender
+ m_bogus_attachment(false),
+ m_done(false),
+ m_already_encoded_p(false),
+ mDeleteFile(false),
+ mMHTMLPart(false),
+ mPartUserOmissionOverride(false),
+ mMainBody(false),
+ mSendViaCloud(false),
+ mNodeIndex(-1),
+ // For analyzing the attachment file...
+ m_size(0),
+ m_unprintable_count(0),
+ m_highbit_count(0),
+ m_ctl_count(0),
+ m_null_count(0),
+ m_have_cr(0),
+ m_have_lf(0),
+ m_have_crlf(0),
+ m_prev_char_was_cr(false),
+ m_current_column(0),
+ m_max_column(0),
+ m_lines(0),
+ m_file_analyzed(false),
+
+ // Mime
+ m_encoder(nullptr)
+{
+}
+
+nsMsgAttachmentHandler::~nsMsgAttachmentHandler()
+{
+ if (mTmpFile && mDeleteFile)
+ mTmpFile->Remove(false);
+
+ if (mOutFile)
+ mOutFile->Close();
+
+ CleanupTempFile();
+}
+
+NS_IMPL_ISUPPORTS(nsMsgAttachmentHandler, nsIMsgAttachmentHandler)
+
+// nsIMsgAttachmentHandler implementation.
+
+NS_IMETHODIMP nsMsgAttachmentHandler::GetType(nsACString& aType)
+{
+ aType.Assign(m_type);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentHandler::GetUri(nsACString& aUri)
+{
+ nsAutoCString turl;
+ if (!mURL)
+ {
+ if (!m_uri.IsEmpty())
+ turl = m_uri;
+ }
+ else
+ {
+ nsresult rv = mURL->GetSpec(turl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ aUri.Assign(turl);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentHandler::GetTmpFile(nsIFile **aTmpFile)
+{
+ NS_ENSURE_ARG_POINTER(aTmpFile);
+ if (!mTmpFile)
+ return NS_ERROR_FAILURE;
+ NS_ADDREF(*aTmpFile = mTmpFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentHandler::GetName(nsACString& aName)
+{
+ aName.Assign(m_realName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentHandler::GetSize(uint32_t *aSize)
+{
+ NS_ENSURE_ARG_POINTER(aSize);
+ *aSize = m_size;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentHandler::GetContentId(nsACString& aContentId)
+{
+ aContentId.Assign(m_contentId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentHandler::GetSendViaCloud(bool* aSendViaCloud)
+{
+ NS_ENSURE_ARG_POINTER(aSendViaCloud);
+ *aSendViaCloud = mSendViaCloud;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentHandler::GetCharset(nsACString& aCharset)
+{
+ aCharset.Assign(m_charset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentHandler::GetEncoding(nsACString& aEncoding)
+{
+ aEncoding.Assign(m_encoding);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentHandler::GetAlreadyEncoded(bool* aAlreadyEncoded)
+{
+ NS_ENSURE_ARG_POINTER(aAlreadyEncoded);
+ *aAlreadyEncoded = m_already_encoded_p;
+ return NS_OK;
+}
+
+// Local methods.
+
+void
+nsMsgAttachmentHandler::CleanupTempFile()
+{
+#ifdef XP_MACOSX
+ if (mEncodedWorkingFile) {
+ mEncodedWorkingFile->Remove(false);
+ mEncodedWorkingFile = nullptr;
+ }
+#endif // XP_MACOSX
+}
+
+void
+nsMsgAttachmentHandler::AnalyzeDataChunk(const char *chunk, int32_t length)
+{
+ unsigned char *s = (unsigned char *) chunk;
+ unsigned char *end = s + length;
+ for (; s < end; s++)
+ {
+ if (*s > 126)
+ {
+ m_highbit_count++;
+ m_unprintable_count++;
+ }
+ else if (*s < ' ' && *s != '\t' && *s != '\r' && *s != '\n')
+ {
+ m_unprintable_count++;
+ m_ctl_count++;
+ if (*s == 0)
+ m_null_count++;
+ }
+
+ if (*s == '\r' || *s == '\n')
+ {
+ if (*s == '\r')
+ {
+ if (m_prev_char_was_cr)
+ m_have_cr = 1;
+ else
+ m_prev_char_was_cr = true;
+ }
+ else
+ {
+ if (m_prev_char_was_cr)
+ {
+ if (m_current_column == 0)
+ {
+ m_have_crlf = 1;
+ m_lines--;
+ }
+ else
+ m_have_cr = m_have_lf = 1;
+ m_prev_char_was_cr = false;
+ }
+ else
+ m_have_lf = 1;
+ }
+ if (m_max_column < m_current_column)
+ m_max_column = m_current_column;
+ m_current_column = 0;
+ m_lines++;
+ }
+ else
+ {
+ m_current_column++;
+ }
+ }
+ // Check one last time for the last line. This is also important if there
+ // is only one line that doesn't terminate in \n.
+ if (m_max_column < m_current_column)
+ m_max_column = m_current_column;
+}
+
+void
+nsMsgAttachmentHandler::AnalyzeSnarfedFile(void)
+{
+ char chunk[1024];
+ uint32_t numRead = 0;
+
+ if (m_file_analyzed)
+ return;
+
+ if (mTmpFile)
+ {
+ int64_t fileSize;
+ mTmpFile->GetFileSize(&fileSize);
+ m_size = (uint32_t) fileSize;
+ nsCOMPtr <nsIInputStream> inputFile;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), mTmpFile);
+ if (NS_FAILED(rv))
+ return;
+ {
+ do
+ {
+ rv = inputFile->Read(chunk, sizeof(chunk), &numRead);
+ if (numRead)
+ AnalyzeDataChunk(chunk, numRead);
+ }
+ while (numRead && NS_SUCCEEDED(rv));
+ if (m_prev_char_was_cr)
+ m_have_cr = 1;
+
+ inputFile->Close();
+ m_file_analyzed = true;
+ }
+ }
+}
+
+//
+// Given a content-type and some info about the contents of the document,
+// decide what encoding it should have.
+//
+nsresult
+nsMsgAttachmentHandler::PickEncoding(const char *charset, nsIMsgSend *mime_delivery_state)
+{
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ bool needsB64 = false;
+ bool forceB64 = false;
+ bool isUsingQP = false;
+
+ if (mSendViaCloud)
+ {
+ m_encoding = ENCODING_7BIT;
+ return NS_OK;
+ }
+ if (m_already_encoded_p)
+ goto DONE;
+
+ AnalyzeSnarfedFile();
+
+ // Allow users to override our percentage-wise guess on whether
+ // the file is text or binary.
+ if (pPrefBranch)
+ pPrefBranch->GetBoolPref ("mail.file_attach_binary", &forceB64);
+
+ // If the content-type is "image/" or something else known to be binary or
+ // several flavors of newlines are present, use base64 unless we're attaching
+ // a message (so that we don't get confused by newline conversions).
+ if (!mMainBody &&
+ (forceB64 || mime_type_requires_b64_p(m_type.get()) ||
+ m_have_cr + m_have_lf + m_have_crlf != 1) &&
+ !m_type.LowerCaseEqualsLiteral(MESSAGE_RFC822))
+ {
+ needsB64 = true;
+ }
+ else
+ {
+ // Otherwise, we need to pick an encoding based on the contents of the
+ // document.
+ bool encode_p;
+ bool force_p = false;
+
+ // Force quoted-printable if the sender does not allow conversion to 7bit.
+ if (mCompFields) {
+ if (mCompFields->GetForceMsgEncoding())
+ force_p = true;
+ } else if (mime_delivery_state) {
+ if (((nsMsgComposeAndSend *)mime_delivery_state)->mCompFields->GetForceMsgEncoding()) {
+ force_p = true;
+ }
+ }
+
+ if (force_p || (m_max_column > LINELENGTH_ENCODING_THRESHOLD)) {
+ encode_p = true;
+ } else if (UseQuotedPrintable() && m_unprintable_count) {
+ encode_p = true;
+ } else if (m_null_count) {
+ // If there are nulls, we must always encode, because sendmail will
+ // blow up.
+ encode_p = true;
+ } else {
+ encode_p = false;
+ }
+
+ // MIME requires a special case that these types never be encoded.
+ if (StringBeginsWith(m_type, NS_LITERAL_CSTRING("message"),
+ nsCaseInsensitiveCStringComparator()) ||
+ StringBeginsWith(m_type, NS_LITERAL_CSTRING("multipart"),
+ nsCaseInsensitiveCStringComparator()))
+ {
+ encode_p = false;
+ if (m_desiredType.LowerCaseEqualsLiteral(TEXT_PLAIN))
+ m_desiredType.Truncate();
+ }
+
+ // If the Mail charset is multibyte, we force it to use Base64 for attachments.
+ if ((!mMainBody && charset && nsMsgI18Nmultibyte_charset(charset)) &&
+ (m_type.LowerCaseEqualsLiteral(TEXT_HTML) ||
+ m_type.LowerCaseEqualsLiteral(TEXT_MDL) ||
+ m_type.LowerCaseEqualsLiteral(TEXT_PLAIN) ||
+ m_type.LowerCaseEqualsLiteral(TEXT_RICHTEXT) ||
+ m_type.LowerCaseEqualsLiteral(TEXT_ENRICHED) ||
+ m_type.LowerCaseEqualsLiteral(TEXT_VCARD) ||
+ m_type.LowerCaseEqualsLiteral(APPLICATION_DIRECTORY) || /* text/x-vcard synonym */
+ m_type.LowerCaseEqualsLiteral(TEXT_CSS) ||
+ m_type.LowerCaseEqualsLiteral(TEXT_JSSS))) {
+ needsB64 = true;
+ } else if (charset && nsMsgI18Nstateful_charset(charset)) {
+ m_encoding = ENCODING_7BIT;
+ } else if (encode_p &&
+ m_unprintable_count > (m_size / 10)) {
+ // If the document contains more than 10% unprintable characters,
+ // then that seems like a good candidate for base64 instead of
+ // quoted-printable.
+ needsB64 = true;
+ } else if (encode_p) {
+ m_encoding = ENCODING_QUOTED_PRINTABLE;
+ isUsingQP = true;
+ } else if (m_highbit_count > 0) {
+ m_encoding = ENCODING_8BIT;
+ } else {
+ m_encoding = ENCODING_7BIT;
+ }
+ }
+
+ // Always base64 binary data.
+ if (needsB64)
+ m_encoding = ENCODING_BASE64;
+
+ // According to RFC 821 we must always have lines shorter than 998 bytes.
+ // To encode "long lines" use a CTE that will transmit shorter lines.
+ // Switch to base64 if we are not already using "quoted printable".
+
+ // We don't do this for message/rfc822 attachments, since we can't
+ // change the original Content-Transfer-Encoding of the message we're
+ // attaching. We rely on the original message complying with RFC 821,
+ // if it doesn't we won't either. Not ideal.
+ if (!m_type.LowerCaseEqualsLiteral(MESSAGE_RFC822) &&
+ m_max_column > LINELENGTH_ENCODING_THRESHOLD && !isUsingQP)
+ m_encoding = ENCODING_BASE64;
+
+ // Now that we've picked an encoding, initialize the filter.
+ NS_ASSERTION(!m_encoder, "not-null m_encoder");
+ if (m_encoding.LowerCaseEqualsLiteral(ENCODING_BASE64))
+ {
+ m_encoder = MimeEncoder::GetBase64Encoder(mime_encoder_output_fn,
+ mime_delivery_state);
+ }
+ else if (m_encoding.LowerCaseEqualsLiteral(ENCODING_QUOTED_PRINTABLE))
+ {
+ m_encoder = MimeEncoder::GetQPEncoder(mime_encoder_output_fn,
+ mime_delivery_state);
+ }
+ else
+ {
+ m_encoder = nullptr;
+ }
+
+ /* Do some cleanup for documents with unknown content type.
+ There are two issues: how they look to MIME users, and how they look to
+ non-MIME users.
+
+ If the user attaches a "README" file, which has unknown type because it
+ has no extension, we still need to send it with no encoding, so that it
+ is readable to non-MIME users.
+
+ But if the user attaches some random binary file, then base64 encoding
+ will have been chosen for it (above), and in this case, it won't be
+ immediately readable by non-MIME users. However, if we type it as
+ text/plain instead of application/octet-stream, it will show up inline
+ in a MIME viewer, which will probably be ugly, and may possibly have
+ bad charset things happen as well.
+
+ So, the heuristic we use is, if the type is unknown, then the type is
+ set to application/octet-stream for data which needs base64 (binary data)
+ and is set to text/plain for data which didn't need base64 (unencoded or
+ lightly encoded data.)
+ */
+DONE:
+ if (m_type.IsEmpty() || m_type.LowerCaseEqualsLiteral(UNKNOWN_CONTENT_TYPE))
+ {
+ if (m_already_encoded_p)
+ m_type = APPLICATION_OCTET_STREAM;
+ else if (m_encoding.LowerCaseEqualsLiteral(ENCODING_BASE64) ||
+ m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE))
+ m_type = APPLICATION_OCTET_STREAM;
+ else
+ m_type = TEXT_PLAIN;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgAttachmentHandler::PickCharset()
+{
+ if (!m_charset.IsEmpty() ||
+ !StringBeginsWith(m_type, NS_LITERAL_CSTRING("text/"),
+ nsCaseInsensitiveCStringComparator()))
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> tmpFile =
+ do_QueryInterface(mTmpFile);
+ if (!tmpFile)
+ return NS_OK;
+
+ return MsgDetectCharsetFromFile(tmpFile, m_charset);
+}
+
+static nsresult
+FetcherURLDoneCallback(nsresult aStatus,
+ const nsACString &aContentType,
+ const nsACString &aCharset,
+ int32_t totalSize,
+ const char16_t* aMsg, void *tagData)
+{
+ nsMsgAttachmentHandler *ma = (nsMsgAttachmentHandler *) tagData;
+ NS_ASSERTION(ma != nullptr, "not-null mime attachment");
+
+ if (ma != nullptr)
+ {
+ ma->m_size = totalSize;
+ if (!aContentType.IsEmpty())
+ {
+#ifdef XP_MACOSX
+ //Do not change the type if we are dealing with an encoded (e.g., appledouble or zip) file
+ if (!ma->mEncodedWorkingFile)
+#else
+ // can't send appledouble on non-macs
+ if (!aContentType.EqualsLiteral("multipart/appledouble"))
+#endif
+ ma->m_type = aContentType;
+ }
+
+ if (!aCharset.IsEmpty())
+ ma->m_charset = aCharset;
+
+ return ma->UrlExit(aStatus, aMsg);
+ }
+ else
+ return NS_OK;
+}
+
+nsresult
+nsMsgAttachmentHandler::SnarfMsgAttachment(nsMsgCompFields *compFields)
+{
+ nsresult rv = NS_ERROR_INVALID_ARG;
+ nsCOMPtr <nsIMsgMessageService> messageService;
+
+ if (m_uri.Find("-message:", CaseInsensitiveCompare) != -1)
+ {
+ nsCOMPtr <nsIFile> tmpFile;
+ rv = nsMsgCreateTempFile("nsmail.tmp", getter_AddRefs(tmpFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mTmpFile = do_QueryInterface(tmpFile);
+ mDeleteFile = true;
+ mCompFields = compFields;
+ m_type = MESSAGE_RFC822;
+ m_overrideType = MESSAGE_RFC822;
+ if (!mTmpFile)
+ {
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mOutFile), mTmpFile, -1, 00600);
+ if (NS_FAILED(rv) || !mOutFile)
+ {
+ if (m_mime_delivery_state)
+ {
+ nsCOMPtr<nsIMsgSendReport> sendReport;
+ m_mime_delivery_state->GetSendReport(getter_AddRefs(sendReport));
+ if (sendReport)
+ {
+ nsAutoString error_msg;
+ nsMsgBuildMessageWithTmpFile(mTmpFile, error_msg);
+ sendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false);
+ }
+ }
+ rv = NS_MSG_UNABLE_TO_OPEN_TMP_FILE;
+ goto done;
+ }
+
+ nsCOMPtr<nsIURLFetcher> fetcher = do_CreateInstance(NS_URLFETCHER_CONTRACTID, &rv);
+ if (NS_FAILED(rv) || !fetcher)
+ {
+ if (NS_SUCCEEDED(rv))
+ rv = NS_ERROR_UNEXPECTED;
+ goto done;
+ }
+
+ rv = fetcher->Initialize(mTmpFile, mOutFile, FetcherURLDoneCallback, this);
+ rv = GetMessageServiceFromURI(m_uri, getter_AddRefs(messageService));
+ if (NS_SUCCEEDED(rv) && messageService)
+ {
+ nsAutoCString uri(m_uri);
+ uri += (uri.FindChar('?') == kNotFound) ? '?' : '&';
+ uri.Append("fetchCompleteMessage=true");
+ nsCOMPtr<nsIStreamListener> strListener;
+ fetcher->QueryInterface(NS_GET_IID(nsIStreamListener), getter_AddRefs(strListener));
+
+ // initialize a new stream converter, that uses the strListener as its input
+ // obtain the input stream listener from the new converter,
+ // and pass the converter's input stream listener to DisplayMessage
+
+ m_mime_parser = do_CreateInstance(NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ goto done;
+
+ // Set us as the output stream for HTML data from libmime...
+ nsCOMPtr<nsIMimeStreamConverter> mimeConverter = do_QueryInterface(m_mime_parser);
+ if (mimeConverter)
+ {
+ mimeConverter->SetMimeOutputType(nsMimeOutput::nsMimeMessageDecrypt);
+ mimeConverter->SetForwardInline(false);
+ mimeConverter->SetIdentity(nullptr);
+ mimeConverter->SetOriginalMsgURI(nullptr);
+ }
+
+ nsCOMPtr<nsIStreamListener> convertedListener = do_QueryInterface(m_mime_parser, &rv);
+ if (NS_FAILED(rv))
+ goto done;
+
+ nsCOMPtr<nsIURI> aURL;
+ rv = messageService->GetUrlForUri(uri.get(), getter_AddRefs(aURL), nullptr);
+ if (NS_FAILED(rv))
+ goto done;
+
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed.");
+ if (NS_FAILED(rv))
+ goto done;
+
+ rv = NS_NewInputStreamChannel(getter_AddRefs(m_converter_channel),
+ aURL,
+ nullptr,
+ nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv))
+ goto done;
+
+ rv = m_mime_parser->AsyncConvertData("message/rfc822", "message/rfc822",
+ strListener, m_converter_channel);
+ if (NS_FAILED(rv))
+ goto done;
+
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = messageService->DisplayMessage(uri.get(), convertedListener, nullptr, nullptr, nullptr,
+ getter_AddRefs(dummyNull));
+ }
+ }
+done:
+ if (NS_FAILED(rv))
+ {
+ if (mOutFile)
+ {
+ mOutFile->Close();
+ mOutFile = nullptr;
+ }
+
+ if (mTmpFile)
+ {
+ mTmpFile->Remove(false);
+ mTmpFile = nullptr;
+ }
+ }
+
+ return rv;
+}
+
+#ifdef XP_MACOSX
+bool nsMsgAttachmentHandler::HasResourceFork(FSRef *fsRef)
+{
+ FSCatalogInfo catalogInfo;
+ OSErr err = FSGetCatalogInfo(fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes, &catalogInfo, nullptr, nullptr, nullptr);
+ return (err == noErr && catalogInfo.rsrcLogicalSize != 0);
+}
+#endif
+
+nsresult
+nsMsgAttachmentHandler::SnarfAttachment(nsMsgCompFields *compFields)
+{
+ NS_ASSERTION (! m_done, "Already done");
+
+ if (!mURL)
+ return SnarfMsgAttachment(compFields);
+
+ mCompFields = compFields;
+
+ // First, get as file spec and create the stream for the
+ // temp file where we will save this data
+ nsCOMPtr <nsIFile> tmpFile;
+ nsresult rv = nsMsgCreateTempFile("nsmail.tmp", getter_AddRefs(tmpFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mTmpFile = do_QueryInterface(tmpFile);
+ mDeleteFile = true;
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mOutFile), mTmpFile, -1, 00600);
+ if (NS_FAILED(rv) || !mOutFile)
+ {
+ if (m_mime_delivery_state)
+ {
+ nsCOMPtr<nsIMsgSendReport> sendReport;
+ m_mime_delivery_state->GetSendReport(getter_AddRefs(sendReport));
+ if (sendReport)
+ {
+ nsAutoString error_msg;
+ nsMsgBuildMessageWithTmpFile(mTmpFile, error_msg);
+ sendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false);
+ }
+ }
+ mTmpFile->Remove(false);
+ mTmpFile = nullptr;
+ return NS_MSG_UNABLE_TO_OPEN_TMP_FILE;
+ }
+
+ nsCString sourceURISpec;
+ rv = mURL->GetSpec(sourceURISpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+#ifdef XP_MACOSX
+ if (!m_bogus_attachment && StringBeginsWith(sourceURISpec, NS_LITERAL_CSTRING("file://")))
+ {
+ // Unescape the path (i.e. un-URLify it) before making a FSSpec
+ nsAutoCString filePath;
+ filePath.Adopt(nsMsgGetLocalFileFromURL(sourceURISpec.get()));
+ nsAutoCString unescapedFilePath;
+ MsgUnescapeString(filePath, 0, unescapedFilePath);
+
+ nsCOMPtr<nsIFile> sourceFile;
+ NS_NewNativeLocalFile(unescapedFilePath, true, getter_AddRefs(sourceFile));
+ if (!sourceFile)
+ return NS_ERROR_FAILURE;
+
+ // check if it is a bundle. if it is, we'll zip it.
+ // if not, we'll apple encode it (applesingle or appledouble)
+ nsCOMPtr<nsILocalFileMac> macFile(do_QueryInterface(sourceFile));
+ bool isPackage;
+ macFile->IsPackage(&isPackage);
+ if (isPackage)
+ rv = ConvertToZipFile(macFile);
+ else
+ rv = ConvertToAppleEncoding(sourceURISpec, unescapedFilePath, macFile);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#endif /* XP_MACOSX */
+
+ //
+ // Ok, here we are, we need to fire the URL off and get the data
+ // in the temp file
+ //
+ // Create a fetcher for the URL attachment...
+
+ nsCOMPtr<nsIURLFetcher> fetcher = do_CreateInstance(NS_URLFETCHER_CONTRACTID, &rv);
+ if (NS_FAILED(rv) || !fetcher)
+ {
+ if (NS_SUCCEEDED(rv))
+ return NS_ERROR_UNEXPECTED;
+ else
+ return rv;
+ }
+
+ return fetcher->FireURLRequest(mURL, mTmpFile, mOutFile, FetcherURLDoneCallback, this);
+}
+
+#ifdef XP_MACOSX
+nsresult
+nsMsgAttachmentHandler::ConvertToZipFile(nsILocalFileMac *aSourceFile)
+{
+ // append ".zip" to the real file name
+ nsAutoCString zippedName;
+ nsresult rv = aSourceFile->GetNativeLeafName(zippedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ zippedName.AppendLiteral(".zip");
+
+ // create a temporary file that we'll work on
+ nsCOMPtr <nsIFile> tmpFile;
+ rv = nsMsgCreateTempFile(zippedName.get(), getter_AddRefs(tmpFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mEncodedWorkingFile = do_QueryInterface(tmpFile);
+
+ // point our URL at the zipped temp file
+ NS_NewFileURI(getter_AddRefs(mURL), mEncodedWorkingFile);
+
+ // zip it!
+ rv = nsSimpleZipper::Zip(aSourceFile, mEncodedWorkingFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // set some metadata for this attachment, that will affect the MIME headers.
+ m_type = APPLICATION_ZIP;
+ m_realName = zippedName.get();
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgAttachmentHandler::ConvertToAppleEncoding(const nsCString &aFileURI,
+ const nsCString &aFilePath,
+ nsILocalFileMac *aSourceFile)
+{
+ // convert the apple file to AppleDouble first, and then patch the
+ // address in the url.
+
+ //We need to retrieve the file type and creator...
+
+ char fileInfo[32];
+ OSType type, creator;
+
+ nsresult rv = aSourceFile->GetFileType(&type);
+ if (NS_FAILED(rv))
+ return rv;
+ PR_snprintf(fileInfo, sizeof(fileInfo), "%X", type);
+ m_xMacType = fileInfo;
+
+ rv = aSourceFile->GetFileCreator(&creator);
+ if (NS_FAILED(rv))
+ return rv;
+ PR_snprintf(fileInfo, sizeof(fileInfo), "%X", creator);
+ m_xMacCreator = fileInfo;
+
+ FSRef fsRef;
+ aSourceFile->GetFSRef(&fsRef);
+ bool sendResourceFork = HasResourceFork(&fsRef);
+
+ // if we have a resource fork, check the filename extension, maybe we don't need the resource fork!
+ if (sendResourceFork)
+ {
+ nsCOMPtr<nsIURL> fileUrl(do_CreateInstance(NS_STANDARDURL_CONTRACTID));
+ if (fileUrl)
+ {
+ rv = fileUrl->SetSpec(aFileURI);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString ext;
+ rv = fileUrl->GetFileExtension(ext);
+ if (NS_SUCCEEDED(rv) && !ext.IsEmpty())
+ {
+ sendResourceFork =
+ PL_strcasecmp(ext.get(), "TXT") &&
+ PL_strcasecmp(ext.get(), "JPG") &&
+ PL_strcasecmp(ext.get(), "GIF") &&
+ PL_strcasecmp(ext.get(), "TIF") &&
+ PL_strcasecmp(ext.get(), "HTM") &&
+ PL_strcasecmp(ext.get(), "HTML") &&
+ PL_strcasecmp(ext.get(), "ART") &&
+ PL_strcasecmp(ext.get(), "XUL") &&
+ PL_strcasecmp(ext.get(), "XML") &&
+ PL_strcasecmp(ext.get(), "CSS") &&
+ PL_strcasecmp(ext.get(), "JS");
+ }
+ }
+ }
+ }
+
+ // Only use appledouble if we aren't uuencoding.
+ if( sendResourceFork )
+ {
+ char *separator;
+
+ separator = mime_make_separator("ad");
+ if (!separator)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr <nsIFile> tmpFile;
+ nsresult rv = nsMsgCreateTempFile("appledouble", getter_AddRefs(tmpFile));
+ if (NS_SUCCEEDED(rv))
+ mEncodedWorkingFile = do_QueryInterface(tmpFile);
+ if (!mEncodedWorkingFile)
+ {
+ PR_FREEIF(separator);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ //
+ // RICHIE_MAC - ok, here's the deal, we have a file that we need
+ // to encode in appledouble encoding for the resource fork and put that
+ // into the mEncodedWorkingFile location. Then, we need to patch the new file
+ // spec into the array and send this as part of the 2 part appledouble/mime
+ // encoded mime part.
+ //
+ AppleDoubleEncodeObject *obj = new (AppleDoubleEncodeObject);
+ if (obj == NULL)
+ {
+ mEncodedWorkingFile = nullptr;
+ PR_FREEIF(separator);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = MsgGetFileStream(mEncodedWorkingFile, getter_AddRefs(obj->fileStream));
+ if (NS_FAILED(rv) || !obj->fileStream)
+ {
+ PR_FREEIF(separator);
+ delete obj;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char *working_buff = (char *) PR_Malloc(AD_WORKING_BUFF_SIZE);
+ if (!working_buff)
+ {
+ PR_FREEIF(separator);
+ delete obj;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ obj->buff = working_buff;
+ obj->s_buff = AD_WORKING_BUFF_SIZE;
+
+ //
+ // Setup all the need information on the apple double encoder.
+ //
+ ap_encode_init(&(obj->ap_encode_obj), aFilePath.get(), separator);
+
+ int32_t count;
+
+ OSErr status = noErr;
+ m_size = 0;
+ while (status == noErr)
+ {
+ status = ap_encode_next(&(obj->ap_encode_obj), obj->buff, obj->s_buff, &count);
+ if (status == noErr || status == errDone)
+ {
+ //
+ // we got the encode data, so call the next stream to write it to the disk.
+ //
+ uint32_t bytesWritten;
+ obj->fileStream->Write(obj->buff, count, &bytesWritten);
+ if (bytesWritten != (uint32_t) count)
+ status = errFileWrite;
+ }
+ }
+
+ ap_encode_end(&(obj->ap_encode_obj), (status >= 0)); // if this is true, ok, false abort
+ if (obj->fileStream)
+ obj->fileStream->Close();
+
+ PR_FREEIF(obj->buff); /* free the working buff. */
+ PR_FREEIF(obj);
+
+ nsCOMPtr <nsIURI> fileURI;
+ NS_NewFileURI(getter_AddRefs(fileURI), mEncodedWorkingFile);
+
+ nsCOMPtr<nsIFileURL> theFileURL = do_QueryInterface(fileURI, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString newURLSpec;
+ rv = fileURI->GetSpec(newURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (newURLSpec.IsEmpty())
+ {
+ PR_FREEIF(separator);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (NS_FAILED(nsMsgNewURL(getter_AddRefs(mURL), newURLSpec.get())))
+ {
+ PR_FREEIF(separator);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Now after conversion, also patch the types.
+ char tmp[128];
+ PR_snprintf(tmp, sizeof(tmp), MULTIPART_APPLEDOUBLE ";\r\n boundary=\"%s\"", separator);
+ PR_FREEIF(separator);
+ m_type = tmp;
+ }
+ else
+ {
+ if ( sendResourceFork )
+ {
+ // For now, just do the encoding, but in the old world we would ask the
+ // user about doing this conversion
+ printf("...we could ask the user about this conversion, but for now, nahh..\n");
+ }
+
+ bool useDefault;
+ char *macType, *macEncoding;
+ if (m_type.IsEmpty() || m_type.LowerCaseEqualsLiteral(TEXT_PLAIN))
+ {
+# define TEXT_TYPE 0x54455854 /* the characters 'T' 'E' 'X' 'T' */
+# define text_TYPE 0x74657874 /* the characters 't' 'e' 'x' 't' */
+
+ if (type != TEXT_TYPE && type != text_TYPE)
+ {
+ MacGetFileType(aSourceFile, &useDefault, &macType, &macEncoding);
+ m_type = macType;
+ }
+ }
+ // don't bother to set the types if we failed in getting the file info.
+ }
+
+ return NS_OK;
+}
+#endif // XP_MACOSX
+
+nsresult
+nsMsgAttachmentHandler::LoadDataFromFile(nsIFile *file, nsString &sigData, bool charsetConversion)
+{
+ int32_t readSize;
+ char *readBuf;
+
+ nsCOMPtr <nsIInputStream> inputFile;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), file);
+ if (NS_FAILED(rv))
+ return NS_MSG_ERROR_WRITING_FILE;
+
+ int64_t fileSize;
+ file->GetFileSize(&fileSize);
+ readSize = (uint32_t) fileSize;
+
+ readBuf = (char *)PR_Malloc(readSize + 1);
+ if (!readBuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+ memset(readBuf, 0, readSize + 1);
+
+ uint32_t bytesRead;
+ inputFile->Read(readBuf, readSize, &bytesRead);
+ inputFile->Close();
+
+ nsDependentCString cstringReadBuf(readBuf, bytesRead);
+ if (charsetConversion)
+ {
+ if (NS_FAILED(ConvertToUnicode(m_charset.get(), cstringReadBuf, sigData)))
+ CopyASCIItoUTF16(cstringReadBuf, sigData);
+ }
+ else
+ CopyASCIItoUTF16(cstringReadBuf, sigData);
+
+ PR_FREEIF(readBuf);
+ return NS_OK;
+}
+
+nsresult
+nsMsgAttachmentHandler::Abort()
+{
+ nsCOMPtr<nsIRequest> saveRequest;
+ saveRequest.swap(mRequest);
+
+ if (mTmpFile)
+ {
+ if (mDeleteFile)
+ mTmpFile->Remove(false);
+ mTmpFile = nullptr;
+ }
+
+ NS_ASSERTION(m_mime_delivery_state != nullptr, "not-null m_mime_delivery_state");
+
+ if (m_done)
+ return NS_OK;
+
+ if (saveRequest)
+ return saveRequest->Cancel(NS_ERROR_ABORT);
+ else
+ if (m_mime_delivery_state)
+ {
+ m_mime_delivery_state->SetStatus(NS_ERROR_ABORT);
+ m_mime_delivery_state->NotifyListenerOnStopSending(nullptr, NS_ERROR_ABORT, 0, nullptr);
+ }
+ return NS_OK;
+
+}
+
+nsresult
+nsMsgAttachmentHandler::UrlExit(nsresult status, const char16_t* aMsg)
+{
+ NS_ASSERTION(m_mime_delivery_state != nullptr, "not-null m_mime_delivery_state");
+
+ // Close the file, but don't delete the disk file (or the file spec.)
+ if (mOutFile)
+ {
+ mOutFile->Close();
+ mOutFile = nullptr;
+ }
+ // this silliness is because Windows nsIFile caches its file size
+ // so if an output stream writes to it, it will still return the original
+ // cached size.
+ if (mTmpFile)
+ {
+ nsCOMPtr <nsIFile> tmpFile;
+ mTmpFile->Clone(getter_AddRefs(tmpFile));
+ mTmpFile = do_QueryInterface(tmpFile);
+ }
+ mRequest = nullptr;
+
+ // First things first, we are now going to see if this is an HTML
+ // Doc and if it is, we need to see if we can determine the charset
+ // for this part by sniffing the HTML file.
+ // This is needed only when the charset is not set already.
+ // (e.g. a charset may be specified in HTTP header)
+ //
+ if (!m_type.IsEmpty() && m_charset.IsEmpty() &&
+ m_type.LowerCaseEqualsLiteral(TEXT_HTML))
+ m_charset = nsMsgI18NParseMetaCharset(mTmpFile);
+
+ nsresult mimeDeliveryStatus;
+ m_mime_delivery_state->GetStatus(&mimeDeliveryStatus);
+
+ if (mimeDeliveryStatus == NS_ERROR_ABORT)
+ status = NS_ERROR_ABORT;
+
+ // If the attachment is empty, let's call that a failure.
+ if (!m_size)
+ status = NS_ERROR_FAILURE;
+
+ if (NS_FAILED(status) && status != NS_ERROR_ABORT && NS_SUCCEEDED(mimeDeliveryStatus))
+ {
+ // At this point, we should probably ask a question to the user
+ // if we should continue without this attachment.
+ //
+ bool keepOnGoing = true;
+ nsCString turl;
+ nsString msg;
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgDeliverMode mode = nsIMsgSend::nsMsgDeliverNow;
+ m_mime_delivery_state->GetDeliveryMode(&mode);
+ nsCString params;
+ if (!m_realName.IsEmpty())
+ params = m_realName;
+ else if (NS_SUCCEEDED(mURL->GetSpec(turl)) && !turl.IsEmpty())
+ {
+ nsAutoCString unescapedUrl;
+ MsgUnescapeString(turl, 0, unescapedUrl);
+ if (unescapedUrl.IsEmpty())
+ params = turl;
+ else
+ params = unescapedUrl;
+ }
+ else
+ params.AssignLiteral("?");
+
+ NS_ConvertUTF8toUTF16 UTF16params(params);
+ const char16_t* formatParams[] = { UTF16params.get() };
+ if (mode == nsIMsgSend::nsMsgSaveAsDraft || mode == nsIMsgSend::nsMsgSaveAsTemplate)
+ bundle->FormatStringFromName(u"failureOnObjectEmbeddingWhileSaving",
+ formatParams, 1, getter_Copies(msg));
+ else
+ bundle->FormatStringFromName(u"failureOnObjectEmbeddingWhileSending",
+ formatParams, 1, getter_Copies(msg));
+
+ nsCOMPtr<nsIPrompt> aPrompt;
+ if (m_mime_delivery_state)
+ m_mime_delivery_state->GetDefaultPrompt(getter_AddRefs(aPrompt));
+ nsMsgAskBooleanQuestionByString(aPrompt, msg.get(), &keepOnGoing);
+
+ if (keepOnGoing)
+ {
+ status = NS_OK;
+ m_bogus_attachment = true; //That will cause this attachment to be ignored.
+ }
+ else
+ {
+ status = NS_ERROR_ABORT;
+ m_mime_delivery_state->SetStatus(status);
+ nsresult ignoreMe;
+ m_mime_delivery_state->Fail(status, nullptr, &ignoreMe);
+ m_mime_delivery_state->NotifyListenerOnStopSending(nullptr, status, 0, nullptr);
+ SetMimeDeliveryState(nullptr);
+ return status;
+ }
+ }
+
+ m_done = true;
+
+ //
+ // Ok, now that we have the file here on disk, we need to see if there was
+ // a need to do conversion to plain text...if so, the magic happens here,
+ // otherwise, just move on to other attachments...
+ //
+ if (NS_SUCCEEDED(status) && !m_type.LowerCaseEqualsLiteral(TEXT_PLAIN) &&
+ m_desiredType.LowerCaseEqualsLiteral(TEXT_PLAIN))
+ {
+ //
+ // Conversion to plain text desired.
+ // Now use the converter service here to do the right
+ // thing and convert this data to plain text for us!
+ //
+ nsAutoString conData;
+
+ if (NS_SUCCEEDED(LoadDataFromFile(mTmpFile, conData, true)))
+ {
+ bool flowed, delsp, formatted, disallowBreaks;
+ GetSerialiserFlags(m_charset.get(), &flowed, &delsp, &formatted, &disallowBreaks);
+
+ if (NS_SUCCEEDED(ConvertBufToPlainText(conData, flowed, delsp, formatted, disallowBreaks)))
+ {
+ if (mDeleteFile)
+ mTmpFile->Remove(false);
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), mTmpFile,
+ PR_WRONLY | PR_CREATE_FILE, 00600);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString tData;
+ if (NS_FAILED(ConvertFromUnicode(m_charset.get(), conData, tData)))
+ LossyCopyUTF16toASCII(conData, tData);
+ if (!tData.IsEmpty())
+ {
+ uint32_t bytesWritten;
+ (void) outputStream->Write(tData.get(), tData.Length(), &bytesWritten);
+ }
+ outputStream->Close();
+ // this silliness is because Windows nsIFile caches its file size
+ // so if an output stream writes to it, it will still return the original
+ // cached size.
+ if (mTmpFile)
+ {
+ nsCOMPtr <nsIFile> tmpFile;
+ mTmpFile->Clone(getter_AddRefs(tmpFile));
+ mTmpFile = do_QueryInterface(tmpFile);
+ }
+
+ }
+ }
+ }
+
+ m_type = m_desiredType;
+ m_desiredType.Truncate();
+ m_encoding.Truncate();
+ }
+
+ uint32_t pendingAttachmentCount = 0;
+ m_mime_delivery_state->GetPendingAttachmentCount(&pendingAttachmentCount);
+ NS_ASSERTION (pendingAttachmentCount > 0, "no more pending attachment");
+
+ m_mime_delivery_state->SetPendingAttachmentCount(pendingAttachmentCount - 1);
+
+ bool processAttachmentsSynchronously = false;
+ m_mime_delivery_state->GetProcessAttachmentsSynchronously(&processAttachmentsSynchronously);
+ if (NS_SUCCEEDED(status) && processAttachmentsSynchronously)
+ {
+ /* Find the next attachment which has not yet been loaded,
+ if any, and start it going.
+ */
+ uint32_t i;
+ nsMsgAttachmentHandler *next = 0;
+ nsTArray<RefPtr<nsMsgAttachmentHandler>> *attachments;
+
+ m_mime_delivery_state->GetAttachmentHandlers(&attachments);
+
+ for (i = 0; i < attachments->Length(); i++)
+ {
+ if (!(*attachments)[i]->m_done)
+ {
+ next = (*attachments)[i];
+ //
+ // rhp: We need to get a little more understanding to failed URL
+ // requests. So, at this point if most of next is NULL, then we
+ // should just mark it fetched and move on! We probably ignored
+ // this earlier on in the send process.
+ //
+ if ( (!next->mURL) && (next->m_uri.IsEmpty()) )
+ {
+ (*attachments)[i]->m_done = true;
+ (*attachments)[i]->SetMimeDeliveryState(nullptr);
+ m_mime_delivery_state->GetPendingAttachmentCount(&pendingAttachmentCount);
+ m_mime_delivery_state->SetPendingAttachmentCount(pendingAttachmentCount - 1);
+ next->mPartUserOmissionOverride = true;
+ next = nullptr;
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if (next)
+ {
+ nsresult status = next->SnarfAttachment(mCompFields);
+ if (NS_FAILED(status))
+ {
+ nsresult ignoreMe;
+ m_mime_delivery_state->Fail(status, nullptr, &ignoreMe);
+ m_mime_delivery_state->NotifyListenerOnStopSending(nullptr, status, 0, nullptr);
+ SetMimeDeliveryState(nullptr);
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+
+ m_mime_delivery_state->GetPendingAttachmentCount(&pendingAttachmentCount);
+ if (pendingAttachmentCount == 0)
+ {
+ // If this is the last attachment, then either complete the
+ // delivery (if successful) or report the error by calling
+ // the exit routine and terminating the delivery.
+ if (NS_FAILED(status))
+ {
+ nsresult ignoreMe;
+ m_mime_delivery_state->Fail(status, aMsg, &ignoreMe);
+ m_mime_delivery_state->NotifyListenerOnStopSending(nullptr, status, aMsg, nullptr);
+ SetMimeDeliveryState(nullptr);
+ return NS_ERROR_UNEXPECTED;
+ }
+ else
+ {
+ status = m_mime_delivery_state->GatherMimeAttachments ();
+ if (NS_FAILED(status))
+ {
+ nsresult ignoreMe;
+ m_mime_delivery_state->Fail(status, aMsg, &ignoreMe);
+ m_mime_delivery_state->NotifyListenerOnStopSending(nullptr, status, aMsg, nullptr);
+ SetMimeDeliveryState(nullptr);
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+ else
+ {
+ // If this is not the last attachment, but it got an error,
+ // then report that error and continue
+ if (NS_FAILED(status))
+ {
+ nsresult ignoreMe;
+ m_mime_delivery_state->Fail(status, aMsg, &ignoreMe);
+ }
+ }
+
+ SetMimeDeliveryState(nullptr);
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgAttachmentHandler::GetMimeDeliveryState(nsIMsgSend** _retval)
+{
+ NS_ENSURE_ARG(_retval);
+ *_retval = m_mime_delivery_state;
+ NS_IF_ADDREF(*_retval);
+ return NS_OK;
+}
+
+nsresult
+nsMsgAttachmentHandler::SetMimeDeliveryState(nsIMsgSend* mime_delivery_state)
+{
+ /*
+ Because setting m_mime_delivery_state to null could destroy ourself as
+ m_mime_delivery_state it's our parent, we need to protect ourself against
+ that!
+
+ This extra comptr is necessary,
+ see bug http://bugzilla.mozilla.org/show_bug.cgi?id=78967
+ */
+ nsCOMPtr<nsIMsgSend> temp = m_mime_delivery_state; /* Should lock our parent until the end of the function */
+ m_mime_delivery_state = mime_delivery_state;
+ return NS_OK;
+}
diff --git a/mailnews/compose/src/nsMsgAttachmentHandler.h b/mailnews/compose/src/nsMsgAttachmentHandler.h
new file mode 100644
index 000000000..2034cba03
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAttachmentHandler.h
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgAttachmentHandler_H_
+#define _nsMsgAttachmentHandler_H_
+
+#include "nsIURL.h"
+#include "nsMsgCompFields.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIChannel.h"
+#include "nsIMsgSend.h"
+#include "nsIFileStreams.h"
+#include "nsIStreamConverter.h"
+#include "nsAutoPtr.h"
+#include "nsIMsgAttachmentHandler.h"
+
+#ifdef XP_MACOSX
+
+#include "nsMsgAppleDouble.h"
+#include "nsILocalFileMac.h"
+
+class AppleDoubleEncodeObject
+{
+public:
+ appledouble_encode_object ap_encode_obj;
+ char *buff; // the working buff
+ int32_t s_buff; // the working buff size
+ nsCOMPtr <nsIOutputStream> fileStream; // file to hold the encoding
+};
+
+class nsILocalFileMac;
+class nsIZipWriter;
+
+/* Simple utility class that will synchronously zip any file
+ (or folder hierarchy) you give it. */
+class nsSimpleZipper
+{
+ public:
+
+ // Synchronously zips the input file/folder and writes all
+ // data to the output file.
+ static nsresult Zip(nsIFile *aInputFile, nsIFile *aOutputFile);
+
+ private:
+
+ // Recursively adds the file or folder to aZipWriter.
+ static nsresult AddToZip(nsIZipWriter *aZipWriter,
+ nsIFile *aFile,
+ const nsACString &aPath);
+};
+#endif // XP_MACOSX
+
+namespace mozilla {
+namespace mailnews {
+class MimeEncoder;
+}
+}
+
+//
+// This is a class that deals with processing remote attachments. It implements
+// an nsIStreamListener interface to deal with incoming data
+//
+class nsMsgAttachmentHandler : public nsIMsgAttachmentHandler
+{
+
+ typedef mozilla::mailnews::MimeEncoder MimeEncoder;
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGATTACHMENTHANDLER
+
+ nsMsgAttachmentHandler();
+public:
+ nsresult SnarfAttachment(nsMsgCompFields *compFields);
+ nsresult PickEncoding(const char *charset, nsIMsgSend* mime_delivery_state);
+ nsresult PickCharset();
+ void AnalyzeSnarfedFile (); // Analyze a previously-snarfed file.
+ // (Currently only used for plaintext
+ // converted from HTML.)
+ nsresult Abort();
+ nsresult UrlExit(nsresult status, const char16_t* aMsg);
+
+ // if there's an intermediate temp file left, takes care to remove it from disk.
+ //
+ // NOTE: this takes care of the mEncodedWorkingFile temp file, but not mTmpFile which seems
+ // to be used by lots of other classes at the moment.
+ void CleanupTempFile();
+
+private:
+ virtual ~nsMsgAttachmentHandler();
+
+ // use when a message (e.g. original message in a reply) is attached as a rfc822 attachment.
+ nsresult SnarfMsgAttachment(nsMsgCompFields *compFields);
+ bool UseUUEncode_p(void);
+ void AnalyzeDataChunk (const char *chunk, int32_t chunkSize);
+ nsresult LoadDataFromFile(nsIFile *file, nsString &sigData, bool charsetConversion); //A similar function already exist in nsMsgCompose!
+#ifdef XP_MACOSX
+ nsresult ConvertToAppleEncoding(const nsCString &aFileSpecURI,
+ const nsCString &aFilePath,
+ nsILocalFileMac *aSourceFile);
+ // zips this attachment and does the work to make this attachment handler handle it properly.
+ nsresult ConvertToZipFile(nsILocalFileMac *aSourceFile);
+ bool HasResourceFork(FSRef *fsRef);
+#endif
+
+ //
+public:
+ nsCOMPtr<nsIURI> mURL;
+ nsCOMPtr<nsIFile> mTmpFile; // The temp file to which we save it
+ nsCOMPtr<nsIOutputStream> mOutFile;
+ nsCOMPtr<nsIRequest> mRequest; // The live request used while fetching an attachment
+ nsMsgCompFields *mCompFields; // Message composition fields for the sender
+ bool m_bogus_attachment; // This is to catch problem children...
+
+#ifdef XP_MACOSX
+ // if we need to encode this file into for example an appledouble, or zip file,
+ // this file is our working file. currently only needed on mac.
+ nsCOMPtr<nsIFile> mEncodedWorkingFile;
+#endif
+
+ nsCString m_xMacType; // Mac file type
+ nsCString m_xMacCreator; // Mac file creator
+
+ bool m_done;
+ nsCString m_charset; // charset name
+ nsCString m_contentId; // This is for mutipart/related Content-ID's
+ nsCString m_type; // The real type, once we know it.
+ nsCString m_typeParam; // Any addition parameters to add to the content-type (other than charset, macType and maccreator)
+ nsCString m_overrideType; // The type we should assume it to be
+ // or 0, if we should get it from the
+ // server)
+ nsCString m_overrideEncoding; // Goes along with override_type
+
+ nsCString m_desiredType; // The type it should be converted to.
+ nsCString m_description; // For Content-Description header
+ nsCString m_realName; // The name for the headers, if different
+ // from the URL.
+ nsCString m_encoding; // The encoding, once we've decided. */
+ bool m_already_encoded_p; // If we attach a document that is already
+ // encoded, we just pass it through.
+
+ bool mDeleteFile; // If this is true, Delete the file...its
+ // NOT the original file!
+
+ bool mMHTMLPart; // This is true if its an MHTML part, otherwise, false
+ bool mPartUserOmissionOverride; // This is true if the user send send the email without this part
+ bool mMainBody; // True if this is a main body.
+ // true if this should be sent as a link to a file.
+ bool mSendViaCloud;
+ nsString mHtmlAnnotation;
+ nsCString mCloudProviderKey;
+ nsCString mCloudUrl;
+ int32_t mNodeIndex; //If this is an embedded image, this is the index of the
+ // corresponding domNode in the editor's
+ //GetEmbeddedObjects. Otherwise, it will be -1.
+ //
+ // Vars for analyzing file data...
+ //
+ uint32_t m_size; /* Some state used while filtering it */
+ uint32_t m_unprintable_count;
+ uint32_t m_highbit_count;
+ uint32_t m_ctl_count;
+ uint32_t m_null_count;
+ uint8_t m_have_cr, m_have_lf, m_have_crlf;
+ bool m_prev_char_was_cr;
+ uint32_t m_current_column;
+ uint32_t m_max_column;
+ uint32_t m_lines;
+ bool m_file_analyzed;
+
+ nsAutoPtr<MimeEncoder> m_encoder;
+ nsCString m_uri; // original uri string
+
+ nsresult GetMimeDeliveryState(nsIMsgSend** _retval);
+ nsresult SetMimeDeliveryState(nsIMsgSend* mime_delivery_state);
+private:
+ nsCOMPtr<nsIMsgSend> m_mime_delivery_state;
+ nsCOMPtr<nsIStreamConverter> m_mime_parser;
+ nsCOMPtr<nsIChannel> m_converter_channel;
+};
+
+
+#endif /* _nsMsgAttachmentHandler_H_ */
diff --git a/mailnews/compose/src/nsMsgCompFields.cpp b/mailnews/compose/src/nsMsgCompFields.cpp
new file mode 100644
index 000000000..24192f594
--- /dev/null
+++ b/mailnews/compose/src/nsMsgCompFields.cpp
@@ -0,0 +1,692 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgCompose.h"
+#include "nsMsgCompFields.h"
+#include "nsMsgI18N.h"
+#include "nsMsgCompUtils.h"
+#include "nsMsgUtils.h"
+#include "prmem.h"
+#include "nsIFileChannel.h"
+#include "nsIMsgAttachment.h"
+#include "nsIMsgMdnGenerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgMimeCID.h"
+#include "nsArrayEnumerator.h"
+#include "nsMemory.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+using namespace mozilla::mailnews;
+
+struct HeaderInfo {
+ /// Header name
+ const char *mName;
+ /// If true, nsMsgCompFields should reflect the raw header value instead of
+ /// the unstructured header value.
+ bool mStructured;
+};
+
+// This is a mapping of the m_headers local set to the actual header name we
+// store on the structured header object.
+static HeaderInfo kHeaders[] = {
+ { "From", true },
+ { "Reply-To", true },
+ { "To", true },
+ { "Cc", true },
+ { "Bcc", true },
+ { nullptr, false }, // FCC
+ { nullptr, false }, // FCC2
+ { "Newsgroups", true },
+ { "Followup-To", true },
+ { "Subject", false },
+ { "Organization", false },
+ { "References", true },
+ { "X-Mozilla-News-Host", false },
+ { "X-Priority", false },
+ { nullptr, false }, // CHARACTER_SET
+ { "Message-Id", true },
+ { "X-Template", true },
+ { nullptr, false }, // DRAFT_ID
+ { "Content-Language", true },
+ { nullptr, false } // CREATOR IDENTITY KEY
+};
+
+static_assert(MOZ_ARRAY_LENGTH(kHeaders) ==
+ nsMsgCompFields::MSG_MAX_HEADERS,
+ "These two arrays need to be kept in sync or bad things will happen!");
+
+NS_IMPL_ISUPPORTS(nsMsgCompFields, nsIMsgCompFields, msgIStructuredHeaders,
+ msgIWritableStructuredHeaders)
+
+nsMsgCompFields::nsMsgCompFields()
+: mStructuredHeaders(do_CreateInstance(NS_ISTRUCTUREDHEADERS_CONTRACTID))
+{
+ m_body.Truncate();
+
+ m_attachVCard = false;
+ m_forcePlainText = false;
+ m_useMultipartAlternative = false;
+ m_returnReceipt = false;
+ m_receiptHeaderType = nsIMsgMdnGenerator::eDntType;
+ m_DSN = false;
+ m_bodyIsAsciiOnly = false;
+ m_forceMsgEncoding = false;
+ m_needToCheckCharset = true;
+ m_attachmentReminder = false;
+ m_deliveryFormat = nsIMsgCompSendFormat::AskUser;
+
+ // Get the default charset from pref, use this as a mail charset.
+ nsString charset;
+ NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, "mailnews.send_default_charset",
+ NS_LITERAL_STRING("UTF-8"), charset);
+
+ LossyCopyUTF16toASCII(charset, m_DefaultCharacterSet); // Charsets better be ASCII
+ SetCharacterSet(m_DefaultCharacterSet.get());
+}
+
+nsMsgCompFields::~nsMsgCompFields()
+{
+}
+
+nsresult nsMsgCompFields::SetAsciiHeader(MsgHeaderID header, const char *value)
+{
+ NS_ASSERTION(header >= 0 && header < MSG_MAX_HEADERS,
+ "Invalid message header index!");
+
+ // If we are storing this on the structured header object, we need to set the
+ // value on that object as well. Note that the value may be null, which we'll
+ // take as an attempt to delete the header.
+ const char *headerName = kHeaders[header].mName;
+ if (headerName)
+ {
+ if (!value || !*value)
+ return mStructuredHeaders->DeleteHeader(headerName);
+
+ return mStructuredHeaders->SetRawHeader(headerName,
+ nsDependentCString(value), "UTF-8");
+ }
+
+ // Not on the structurd header object, so save it locally.
+ m_headers[header] = value;
+
+ return NS_OK;
+}
+
+const char* nsMsgCompFields::GetAsciiHeader(MsgHeaderID header)
+{
+ NS_ASSERTION(header >= 0 && header < MSG_MAX_HEADERS,
+ "Invalid message header index!");
+
+ const char *headerName = kHeaders[header].mName;
+ if (headerName)
+ {
+ // We may be out of sync with the structured header object. Retrieve the
+ // header value.
+ if (kHeaders[header].mStructured)
+ {
+ mStructuredHeaders->GetRawHeader(headerName, m_headers[header]);
+ }
+ else
+ {
+ nsString value;
+ mStructuredHeaders->GetUnstructuredHeader(headerName, value);
+ CopyUTF16toUTF8(value, m_headers[header]);
+ }
+ }
+
+ return m_headers[header].get();
+}
+
+nsresult nsMsgCompFields::SetUnicodeHeader(MsgHeaderID header, const nsAString& value)
+{
+ return SetAsciiHeader(header, NS_ConvertUTF16toUTF8(value).get());
+}
+
+nsresult nsMsgCompFields::GetUnicodeHeader(MsgHeaderID header, nsAString& aResult)
+{
+ CopyUTF8toUTF16(nsDependentCString(GetAsciiHeader(header)), aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetFrom(const nsAString &value)
+{
+ return SetUnicodeHeader(MSG_FROM_HEADER_ID, value);
+}
+
+
+NS_IMETHODIMP nsMsgCompFields::GetFrom(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_FROM_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetReplyTo(const nsAString &value)
+{
+ return SetUnicodeHeader(MSG_REPLY_TO_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetReplyTo(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_REPLY_TO_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetTo(const nsAString &value)
+{
+ return SetUnicodeHeader(MSG_TO_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetTo(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_TO_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetCc(const nsAString &value)
+{
+ return SetUnicodeHeader(MSG_CC_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetCc(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_CC_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetBcc(const nsAString &value)
+{
+ return SetUnicodeHeader(MSG_BCC_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetBcc(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_BCC_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetFcc(const nsAString &value)
+{
+ return SetUnicodeHeader(MSG_FCC_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetFcc(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_FCC_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetFcc2(const nsAString &value)
+{
+ return SetUnicodeHeader(MSG_FCC2_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetFcc2(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_FCC2_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetNewsgroups(const nsAString &aValue)
+{
+ return SetUnicodeHeader(MSG_NEWSGROUPS_HEADER_ID, aValue);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetNewsgroups(nsAString &aGroup)
+{
+ return GetUnicodeHeader(MSG_NEWSGROUPS_HEADER_ID, aGroup);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetFollowupTo(const nsAString &aValue)
+{
+ return SetUnicodeHeader(MSG_FOLLOWUP_TO_HEADER_ID, aValue);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetFollowupTo(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_FOLLOWUP_TO_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetHasRecipients(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = NS_SUCCEEDED(mime_sanity_check_fields_recipients(
+ GetTo(), GetCc(), GetBcc(), GetNewsgroups()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetCreatorIdentityKey(const char *value)
+{
+ return SetAsciiHeader(MSG_CREATOR_IDENTITY_KEY_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetCreatorIdentityKey(char **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = strdup(GetAsciiHeader(MSG_CREATOR_IDENTITY_KEY_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetSubject(const nsAString &value)
+{
+ return SetUnicodeHeader(MSG_SUBJECT_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetSubject(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_SUBJECT_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetOrganization(const nsAString &value)
+{
+ return SetUnicodeHeader(MSG_ORGANIZATION_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetOrganization(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_ORGANIZATION_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetReferences(const char *value)
+{
+ return SetAsciiHeader(MSG_REFERENCES_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetReferences(char **_retval)
+{
+ *_retval = strdup(GetAsciiHeader(MSG_REFERENCES_HEADER_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetNewspostUrl(const char *value)
+{
+ return SetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetNewspostUrl(char **_retval)
+{
+ *_retval = strdup(GetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetPriority(const char *value)
+{
+ return SetAsciiHeader(MSG_PRIORITY_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetPriority(char **_retval)
+{
+ *_retval = strdup(GetAsciiHeader(MSG_PRIORITY_HEADER_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetCharacterSet(const char *value)
+{
+ return SetAsciiHeader(MSG_CHARACTER_SET_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetCharacterSet(char **_retval)
+{
+ *_retval = strdup(GetAsciiHeader(MSG_CHARACTER_SET_HEADER_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetMessageId(const char *value)
+{
+ return SetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetMessageId(char **_retval)
+{
+ *_retval = strdup(GetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetTemplateName(const nsAString &value)
+{
+ return SetUnicodeHeader(MSG_X_TEMPLATE_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetTemplateName(nsAString &_retval)
+{
+ return GetUnicodeHeader(MSG_X_TEMPLATE_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetDraftId(const char *value)
+{
+ return SetAsciiHeader(MSG_DRAFT_ID_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetDraftId(char **_retval)
+{
+ *_retval = strdup(GetAsciiHeader(MSG_DRAFT_ID_HEADER_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetReturnReceipt(bool value)
+{
+ m_returnReceipt = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetReturnReceipt(bool *_retval)
+{
+ *_retval = m_returnReceipt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetReceiptHeaderType(int32_t value)
+{
+ m_receiptHeaderType = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetReceiptHeaderType(int32_t *_retval)
+{
+ *_retval = m_receiptHeaderType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetDSN(bool value)
+{
+ m_DSN = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetDSN(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_DSN;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetAttachVCard(bool value)
+{
+ m_attachVCard = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetAttachVCard(bool *_retval)
+{
+ *_retval = m_attachVCard;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetAttachmentReminder(bool *_retval)
+{
+ *_retval = m_attachmentReminder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetAttachmentReminder(bool value)
+{
+ m_attachmentReminder = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetDeliveryFormat(int32_t value)
+{
+ switch (value) {
+ case nsIMsgCompSendFormat::AskUser:
+ case nsIMsgCompSendFormat::PlainText:
+ case nsIMsgCompSendFormat::HTML:
+ case nsIMsgCompSendFormat::Both:
+ m_deliveryFormat = value;
+ break;
+ default:
+ m_deliveryFormat = nsIMsgCompSendFormat::AskUser;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetDeliveryFormat(int32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_deliveryFormat;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetContentLanguage(const char *value)
+{
+ return SetAsciiHeader(MSG_CONTENT_LANGUAGE_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetContentLanguage(char **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = strdup(GetAsciiHeader(MSG_CONTENT_LANGUAGE_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetForcePlainText(bool value)
+{
+ m_forcePlainText = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetForcePlainText(bool *_retval)
+{
+ *_retval = m_forcePlainText;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetForceMsgEncoding(bool value)
+{
+ m_forceMsgEncoding = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetForceMsgEncoding(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_forceMsgEncoding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetUseMultipartAlternative(bool value)
+{
+ m_useMultipartAlternative = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetUseMultipartAlternative(bool *_retval)
+{
+ *_retval = m_useMultipartAlternative;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetBodyIsAsciiOnly(bool value)
+{
+ m_bodyIsAsciiOnly = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetBodyIsAsciiOnly(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = m_bodyIsAsciiOnly;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetBody(const nsAString &value)
+{
+ CopyUTF16toUTF8(value, m_body);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetBody(nsAString &_retval)
+{
+ CopyUTF8toUTF16(m_body, _retval);
+ return NS_OK;
+}
+
+nsresult nsMsgCompFields::SetBody(const char *value)
+{
+ if (value)
+ m_body = value;
+ else
+ m_body.Truncate();
+ return NS_OK;
+}
+
+const char* nsMsgCompFields::GetBody()
+{
+ return m_body.get();
+}
+
+/* readonly attribute nsISimpleEnumerator attachmentsArray; */
+NS_IMETHODIMP nsMsgCompFields::GetAttachments(nsISimpleEnumerator * *aAttachmentsEnum)
+{
+ return aAttachmentsEnum ? NS_NewArrayEnumerator(aAttachmentsEnum, m_attachments) : NS_ERROR_NULL_POINTER;
+}
+
+/* void addAttachment (in nsIMsgAttachment attachment); */
+NS_IMETHODIMP nsMsgCompFields::AddAttachment(nsIMsgAttachment *attachment)
+{
+ int32_t attachmentCount = m_attachments.Count();
+
+ //Don't add twice the same attachment.
+ nsCOMPtr<nsIMsgAttachment> element;
+ bool sameUrl;
+ for (int32_t i = 0; i < attachmentCount; i ++)
+ {
+ m_attachments[i]->EqualsUrl(attachment, &sameUrl);
+ if (sameUrl)
+ return NS_OK;
+ }
+ m_attachments.AppendObject(attachment);
+
+ return NS_OK;
+}
+
+/* void removeAttachment (in nsIMsgAttachment attachment); */
+NS_IMETHODIMP nsMsgCompFields::RemoveAttachment(nsIMsgAttachment *attachment)
+{
+ int32_t attachmentCount = m_attachments.Count();
+
+ nsCOMPtr<nsIMsgAttachment> element;
+ bool sameUrl;
+ for (int32_t i = 0; i < attachmentCount; i ++)
+ {
+ m_attachments[i]->EqualsUrl(attachment, &sameUrl);
+ if (sameUrl)
+ {
+ m_attachments.RemoveObjectAt(i);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+/* void removeAttachments (); */
+NS_IMETHODIMP nsMsgCompFields::RemoveAttachments()
+{
+ m_attachments.Clear();
+
+ return NS_OK;
+}
+
+
+// This method is called during the creation of a new window.
+NS_IMETHODIMP
+nsMsgCompFields::SplitRecipients(const nsAString &aRecipients,
+ bool aEmailAddressOnly,
+ uint32_t *aLength,
+ char16_t*** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aLength);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aLength = 0;
+ *aResult = nullptr;
+
+ nsCOMArray<msgIAddressObject> header(EncodedHeaderW(aRecipients));
+ nsTArray<nsString> results;
+ if (aEmailAddressOnly)
+ ExtractEmails(header, results);
+ else
+ ExtractDisplayAddresses(header, results);
+
+ uint32_t count = results.Length();
+ char16_t **result = (char16_t **)NS_Alloc(sizeof(char16_t *) * count);
+ for (uint32_t i = 0; i < count; ++i)
+ result[i] = ToNewUnicode(results[i]);
+
+ *aResult = result;
+ *aLength = count;
+ return NS_OK;
+}
+
+
+// This method is called during the sending of message from nsMsgCompose::CheckAndPopulateRecipients()
+nsresult nsMsgCompFields::SplitRecipientsEx(const nsAString &recipients,
+ nsTArray<nsMsgRecipient> &aResult)
+{
+ nsTArray<nsString> names, addresses;
+ ExtractAllAddresses(EncodedHeaderW(recipients), names, addresses);
+
+ uint32_t numAddresses = names.Length();
+ for (uint32_t i = 0; i < numAddresses; ++i)
+ {
+ nsMsgRecipient msgRecipient;
+ msgRecipient.mEmail = addresses[i];
+ msgRecipient.mName = names[i];
+ aResult.AppendElement(msgRecipient);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::ConvertBodyToPlainText()
+{
+ nsresult rv = NS_OK;
+
+ if (!m_body.IsEmpty())
+ {
+ nsAutoString body;
+ rv = GetBody(body);
+ if (NS_SUCCEEDED(rv))
+ {
+ bool flowed, delsp, formatted, disallowBreaks;
+ GetSerialiserFlags(GetCharacterSet(), &flowed, &delsp, &formatted, &disallowBreaks);
+ rv = ConvertBufToPlainText(body, flowed, delsp, formatted, disallowBreaks);
+ if (NS_SUCCEEDED(rv))
+ rv = SetBody(body);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetSecurityInfo(nsISupports ** aSecurityInfo)
+{
+ NS_ENSURE_ARG_POINTER(aSecurityInfo);
+ *aSecurityInfo = mSecureCompFields;
+ NS_IF_ADDREF(*aSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetSecurityInfo(nsISupports * aSecurityInfo)
+{
+ mSecureCompFields = aSecurityInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetDefaultCharacterSet(char * *aDefaultCharacterSet)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultCharacterSet);
+ *aDefaultCharacterSet = ToNewCString(m_DefaultCharacterSet);
+ return *aDefaultCharacterSet ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetNeedToCheckCharset(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_needToCheckCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetNeedToCheckCharset(bool aCheck)
+{
+ m_needToCheckCharset = aCheck;
+ return NS_OK;
+}
diff --git a/mailnews/compose/src/nsMsgCompFields.h b/mailnews/compose/src/nsMsgCompFields.h
new file mode 100644
index 000000000..ecc562499
--- /dev/null
+++ b/mailnews/compose/src/nsMsgCompFields.h
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MsgCompFields_H_
+#define _MsgCompFields_H_
+
+#include "nsIMsgCompFields.h"
+#include "msgCore.h"
+#include "nsIAbCard.h"
+#include "nsIAbDirectory.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+
+struct nsMsgRecipient
+{
+ nsString mName;
+ nsString mEmail;
+ nsCOMPtr<nsIAbCard> mCard;
+ nsCOMPtr<nsIAbDirectory> mDirectory;
+};
+
+/* Note that all the "Get" methods never return NULL (except in case of serious
+ error, like an illegal parameter); rather, they return "" if things were set
+ to NULL. This makes it real handy for the callers. */
+
+class nsMsgCompFields : public nsIMsgCompFields {
+public:
+ nsMsgCompFields();
+
+ /* this macro defines QueryInterface, AddRef and Release for this class */
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_MSGISTRUCTUREDHEADERS(mStructuredHeaders->)
+ NS_FORWARD_MSGIWRITABLESTRUCTUREDHEADERS(mStructuredHeaders->)
+ NS_DECL_NSIMSGCOMPFIELDS
+
+ // Allow the C++ utility methods for people who use a concrete class instead
+ // of the interfaces.
+ using msgIStructuredHeaders::GetAddressingHeader;
+ using msgIWritableStructuredHeaders::SetAddressingHeader;
+
+ typedef enum MsgHeaderID
+ {
+ MSG_FROM_HEADER_ID = 0,
+ MSG_REPLY_TO_HEADER_ID,
+ MSG_TO_HEADER_ID,
+ MSG_CC_HEADER_ID,
+ MSG_BCC_HEADER_ID,
+ MSG_FCC_HEADER_ID,
+ MSG_FCC2_HEADER_ID,
+ MSG_NEWSGROUPS_HEADER_ID,
+ MSG_FOLLOWUP_TO_HEADER_ID,
+ MSG_SUBJECT_HEADER_ID,
+ MSG_ORGANIZATION_HEADER_ID,
+ MSG_REFERENCES_HEADER_ID,
+ MSG_NEWSPOSTURL_HEADER_ID,
+ MSG_PRIORITY_HEADER_ID,
+ MSG_CHARACTER_SET_HEADER_ID,
+ MSG_MESSAGE_ID_HEADER_ID,
+ MSG_X_TEMPLATE_HEADER_ID,
+ MSG_DRAFT_ID_HEADER_ID,
+ MSG_CONTENT_LANGUAGE_ID,
+ MSG_CREATOR_IDENTITY_KEY_ID,
+
+ MSG_MAX_HEADERS //Must be the last one.
+ } MsgHeaderID;
+
+ nsresult SetAsciiHeader(MsgHeaderID header, const char *value);
+ const char* GetAsciiHeader(MsgHeaderID header); //just return the address of the internal header variable, don't dispose it
+
+ nsresult SetUnicodeHeader(MsgHeaderID header, const nsAString &value);
+ nsresult GetUnicodeHeader(MsgHeaderID header, nsAString &_retval);
+
+ /* Convenience routines to get and set header's value...
+
+ IMPORTANT:
+ all routines const char* GetXxx(void) will return a pointer to the header, please don't free it.
+ */
+
+ nsresult SetFrom(const char *value) {return SetAsciiHeader(MSG_FROM_HEADER_ID, value);}
+ const char* GetFrom(void) {return GetAsciiHeader(MSG_FROM_HEADER_ID);}
+
+ nsresult SetReplyTo(const char *value) {return SetAsciiHeader(MSG_REPLY_TO_HEADER_ID, value);}
+ const char* GetReplyTo() {return GetAsciiHeader(MSG_REPLY_TO_HEADER_ID);}
+
+ nsresult SetTo(const char *value) {return SetAsciiHeader(MSG_TO_HEADER_ID, value);}
+ const char* GetTo() {return GetAsciiHeader(MSG_TO_HEADER_ID);}
+
+ nsresult SetCc(const char *value) {return SetAsciiHeader(MSG_CC_HEADER_ID, value);}
+ const char* GetCc() {return GetAsciiHeader(MSG_CC_HEADER_ID);}
+
+ nsresult SetBcc(const char *value) {return SetAsciiHeader(MSG_BCC_HEADER_ID, value);}
+ const char* GetBcc() {return GetAsciiHeader(MSG_BCC_HEADER_ID);}
+
+ nsresult SetFcc(const char *value) {return SetAsciiHeader(MSG_FCC_HEADER_ID, value);}
+ const char* GetFcc() {return GetAsciiHeader(MSG_FCC_HEADER_ID);}
+
+ nsresult SetFcc2(const char *value) {return SetAsciiHeader(MSG_FCC2_HEADER_ID, value);}
+ const char* GetFcc2() {return GetAsciiHeader(MSG_FCC2_HEADER_ID);}
+
+ nsresult SetNewsgroups(const char *aValue) {return SetAsciiHeader(MSG_NEWSGROUPS_HEADER_ID, aValue);}
+ const char* GetNewsgroups() {return GetAsciiHeader(MSG_NEWSGROUPS_HEADER_ID);}
+
+ nsresult SetFollowupTo(const char *aValue) {return SetAsciiHeader(MSG_FOLLOWUP_TO_HEADER_ID, aValue);}
+ const char* GetFollowupTo() {return GetAsciiHeader(MSG_FOLLOWUP_TO_HEADER_ID);}
+
+ nsresult SetSubject(const char *value) {return SetAsciiHeader(MSG_SUBJECT_HEADER_ID, value);}
+ const char* GetSubject() {return GetAsciiHeader(MSG_SUBJECT_HEADER_ID);}
+
+ nsresult SetOrganization(const char *value) {return SetAsciiHeader(MSG_ORGANIZATION_HEADER_ID, value);}
+ const char* GetOrganization() {return GetAsciiHeader(MSG_ORGANIZATION_HEADER_ID);}
+
+ const char* GetReferences() {return GetAsciiHeader(MSG_REFERENCES_HEADER_ID);}
+
+ const char* GetNewspostUrl() {return GetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID);}
+
+ const char* GetPriority() {return GetAsciiHeader(MSG_PRIORITY_HEADER_ID);}
+
+ const char* GetCharacterSet() {return GetAsciiHeader(MSG_CHARACTER_SET_HEADER_ID);}
+
+ const char* GetMessageId() {return GetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID);}
+
+ nsresult SetTemplateName(const char *value) {return SetAsciiHeader(MSG_X_TEMPLATE_HEADER_ID, value);}
+ const char* GetTemplateName() {return GetAsciiHeader(MSG_X_TEMPLATE_HEADER_ID);}
+
+ const char* GetDraftId() {return GetAsciiHeader(MSG_DRAFT_ID_HEADER_ID);}
+
+ const char* GetContentLanguage() {return GetAsciiHeader(MSG_CONTENT_LANGUAGE_ID);}
+
+ bool GetReturnReceipt() {return m_returnReceipt;}
+ bool GetDSN() {return m_DSN;}
+ bool GetAttachVCard() {return m_attachVCard;}
+ bool GetAttachmentReminder() {return m_attachmentReminder;}
+ int32_t GetDeliveryFormat() {return m_deliveryFormat;}
+ bool GetForcePlainText() {return m_forcePlainText;}
+ bool GetUseMultipartAlternative() {return m_useMultipartAlternative;}
+ bool GetBodyIsAsciiOnly() {return m_bodyIsAsciiOnly;}
+ bool GetForceMsgEncoding() {return m_forceMsgEncoding;}
+
+ nsresult SetBody(const char *value);
+ const char* GetBody();
+
+ nsresult SplitRecipientsEx(const nsAString &recipients,
+ nsTArray<nsMsgRecipient> &aResult);
+
+protected:
+ virtual ~nsMsgCompFields();
+ nsCString m_headers[MSG_MAX_HEADERS];
+ nsCString m_body;
+ nsCOMArray<nsIMsgAttachment> m_attachments;
+ bool m_attachVCard;
+ bool m_attachmentReminder;
+ int32_t m_deliveryFormat;
+ bool m_forcePlainText;
+ bool m_useMultipartAlternative;
+ bool m_returnReceipt;
+ bool m_DSN;
+ bool m_bodyIsAsciiOnly;
+ bool m_forceMsgEncoding;
+ int32_t m_receiptHeaderType; /* receipt header type */
+ nsCString m_DefaultCharacterSet;
+ bool m_needToCheckCharset;
+
+ nsCOMPtr<nsISupports> mSecureCompFields;
+ nsCOMPtr<msgIWritableStructuredHeaders> mStructuredHeaders;
+};
+
+
+#endif /* _MsgCompFields_H_ */
diff --git a/mailnews/compose/src/nsMsgCompUtils.cpp b/mailnews/compose/src/nsMsgCompUtils.cpp
new file mode 100644
index 000000000..6632e3257
--- /dev/null
+++ b/mailnews/compose/src/nsMsgCompUtils.cpp
@@ -0,0 +1,1803 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "nsMsgCompUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "prmem.h"
+#include "nsMsgSend.h"
+#include "nsIIOService.h"
+#include "nsIHttpProtocolHandler.h"
+#include "nsMailHeaders.h"
+#include "nsMsgI18N.h"
+#include "nsINntpService.h"
+#include "nsMimeTypes.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsMsgPrompts.h"
+#include "nsMsgUtils.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIMIMEService.h"
+#include "nsComposeStrings.h"
+#include "nsIMsgCompUtils.h"
+#include "nsIMsgMdnGenerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "nsCRTGlue.h"
+#include <ctype.h>
+#include "mozilla/mailnews/Services.h"
+#include "mozilla/Services.h"
+#include "nsIMIMEInfo.h"
+#include "nsIMsgHeaderParser.h"
+#include "nsIRandomGenerator.h"
+#include "nsID.h"
+
+NS_IMPL_ISUPPORTS(nsMsgCompUtils, nsIMsgCompUtils)
+
+nsMsgCompUtils::nsMsgCompUtils()
+{
+}
+
+nsMsgCompUtils::~nsMsgCompUtils()
+{
+}
+
+NS_IMETHODIMP nsMsgCompUtils::MimeMakeSeparator(const char *prefix,
+ char **_retval)
+{
+ NS_ENSURE_ARG_POINTER(prefix);
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mime_make_separator(prefix);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompUtils::MsgGenerateMessageId(nsIMsgIdentity *identity,
+ char **_retval)
+{
+ NS_ENSURE_ARG_POINTER(identity);
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = msg_generate_message_id(identity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompUtils::GetMsgMimeConformToStandard(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nsMsgMIMEGetConformToStandard();
+ return NS_OK;
+}
+
+//
+// Create a file for the a unique temp file
+// on the local machine. Caller must free memory
+//
+nsresult
+nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile)
+{
+ if ((!tFileName) || (!*tFileName))
+ tFileName = "nsmail.tmp";
+
+ nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ tFileName,
+ tFile);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = (*tFile)->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv))
+ NS_RELEASE(*tFile);
+
+ return rv;
+}
+
+//
+// Create a file spec for the a unique temp file
+// on the local machine. Caller must free memory
+// returned
+//
+char *
+nsMsgCreateTempFileName(const char *tFileName)
+{
+ if ((!tFileName) || (!*tFileName))
+ tFileName = "nsmail.tmp";
+
+ nsCOMPtr<nsIFile> tmpFile;
+
+ nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ tFileName,
+ getter_AddRefs(tmpFile));
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ nsCString tempString;
+ rv = tmpFile->GetNativePath(tempString);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ char *tString = ToNewCString(tempString);
+ if (!tString)
+ return PL_strdup("mozmail.tmp"); // No need to I18N
+
+ return tString;
+}
+
+// This is the value a caller will Get if they don't Set first (like MDN
+// sending a return receipt), so init to the default value of the
+// mail.strictly_mime_headers preference.
+static bool mime_headers_use_quoted_printable_p = true;
+
+bool
+nsMsgMIMEGetConformToStandard (void)
+{
+ return mime_headers_use_quoted_printable_p;
+}
+
+void
+nsMsgMIMESetConformToStandard (bool conform_p)
+{
+ /*
+ * If we are conforming to mime standard no matter what we set
+ * for the headers preference when generating mime headers we should
+ * also conform to the standard. Otherwise, depends the preference
+ * we set. For now, the headers preference is not accessible from UI.
+ */
+ if (conform_p)
+ mime_headers_use_quoted_printable_p = true;
+ else {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ prefs->GetBoolPref("mail.strictly_mime_headers", &mime_headers_use_quoted_printable_p);
+ }
+ }
+}
+
+/**
+ * Checks if the recipient fields have sane values for message send.
+ */
+nsresult mime_sanity_check_fields_recipients (
+ const char *to,
+ const char *cc,
+ const char *bcc,
+ const char *newsgroups)
+{
+ if (to)
+ while (IS_SPACE(*to))
+ to++;
+ if (cc)
+ while (IS_SPACE(*cc))
+ cc++;
+ if (bcc)
+ while (IS_SPACE(*bcc))
+ bcc++;
+ if (newsgroups)
+ while (IS_SPACE(*newsgroups))
+ newsgroups++;
+
+ if ((!to || !*to) && (!cc || !*cc) &&
+ (!bcc || !*bcc) && (!newsgroups || !*newsgroups))
+ return NS_MSG_NO_RECIPIENTS;
+
+ return NS_OK;
+}
+
+/**
+ * Checks if the compose fields have sane values for message send.
+ */
+nsresult mime_sanity_check_fields (
+ const char *from,
+ const char *reply_to,
+ const char *to,
+ const char *cc,
+ const char *bcc,
+ const char *fcc,
+ const char *newsgroups,
+ const char *followup_to,
+ const char * /*subject*/,
+ const char * /*references*/,
+ const char * /*organization*/,
+ const char * /*other_random_headers*/)
+{
+ if (from)
+ while (IS_SPACE(*from))
+ from++;
+ if (reply_to)
+ while (IS_SPACE(*reply_to))
+ reply_to++;
+ if (fcc)
+ while (IS_SPACE(*fcc))
+ fcc++;
+ if (followup_to)
+ while (IS_SPACE(*followup_to))
+ followup_to++;
+
+ // TODO: sanity check other_random_headers for newline conventions
+ if (!from || !*from)
+ return NS_MSG_NO_SENDER;
+
+ return mime_sanity_check_fields_recipients(to, cc, bcc, newsgroups);
+}
+
+//
+// Generate the message headers for the new RFC822 message
+//
+#define UA_PREF_PREFIX "general.useragent."
+
+// Helper macro for generating the X-Mozilla-Draft-Info header.
+#define APPEND_BOOL(method, param) \
+ do { \
+ bool val = false; \
+ fields->Get##method(&val); \
+ if (val) \
+ draftInfo.AppendLiteral(param "=1"); \
+ else \
+ draftInfo.AppendLiteral(param "=0"); \
+ } while (false)
+
+nsresult mime_generate_headers(nsIMsgCompFields *fields,
+ nsMsgDeliverMode deliver_mode,
+ msgIWritableStructuredHeaders *finalHeaders)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDraft =
+ deliver_mode == nsIMsgSend::nsMsgSaveAsDraft ||
+ deliver_mode == nsIMsgSend::nsMsgSaveAsTemplate ||
+ deliver_mode == nsIMsgSend::nsMsgQueueForLater ||
+ deliver_mode == nsIMsgSend::nsMsgDeliverBackground;
+
+ bool hasDisclosedRecipient = false;
+
+ MOZ_ASSERT(fields, "null fields");
+ NS_ENSURE_ARG_POINTER(fields);
+
+ nsCOMArray<msgIAddressObject> from;
+ fields->GetAddressingHeader("From", from, true);
+
+ // Copy all headers from the original compose field.
+ rv = finalHeaders->AddAllHeaders(fields);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMessageId = false;
+ if (NS_SUCCEEDED(fields->HasHeader("Message-ID", &hasMessageId)) &&
+ hasMessageId)
+ {
+ /* MDN request header requires to have MessageID header presented
+ * in the message in order to
+ * coorelate the MDN reports to the original message. Here will be
+ * the right place
+ */
+
+ bool returnReceipt = false;
+ fields->GetReturnReceipt(&returnReceipt);
+ if (returnReceipt &&
+ (deliver_mode != nsIMsgSend::nsMsgSaveAsDraft &&
+ deliver_mode != nsIMsgSend::nsMsgSaveAsTemplate))
+ {
+ int32_t receipt_header_type = nsIMsgMdnGenerator::eDntType;
+ fields->GetReceiptHeaderType(&receipt_header_type);
+
+ // nsIMsgMdnGenerator::eDntType = MDN Disposition-Notification-To: ;
+ // nsIMsgMdnGenerator::eRrtType = Return-Receipt-To: ;
+ // nsIMsgMdnGenerator::eDntRrtType = both MDN DNT and RRT headers .
+ if (receipt_header_type != nsIMsgMdnGenerator::eRrtType)
+ finalHeaders->SetAddressingHeader("Disposition-Notification-To", from);
+ if (receipt_header_type != nsIMsgMdnGenerator::eDntType)
+ finalHeaders->SetAddressingHeader("Return-Receipt-To", from);
+ }
+ }
+
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
+ int gmtoffset = (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60;
+
+ /* Use PR_FormatTimeUSEnglish() to format the date in US English format,
+ then figure out what our local GMT offset is, and append it (since
+ PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as
+ per RFC 1123 (superceding RFC 822.)
+ */
+ char dateString[130];
+ PR_FormatTimeUSEnglish(dateString, sizeof(dateString),
+ "%a, %d %b %Y %H:%M:%S ",
+ &now);
+
+ char *entryPoint = dateString + strlen(dateString);
+ PR_snprintf(entryPoint, sizeof(dateString) - (entryPoint - dateString),
+ "%c%02d%02d" CRLF,
+ (gmtoffset >= 0 ? '+' : '-'),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60));
+ finalHeaders->SetRawHeader("Date", nsDependentCString(dateString), nullptr);
+
+ // X-Mozilla-Draft-Info
+ if (isDraft)
+ {
+ nsAutoCString draftInfo;
+ draftInfo.AppendLiteral("internal/draft; ");
+ APPEND_BOOL(AttachVCard, "vcard");
+ draftInfo.AppendLiteral("; ");
+ bool hasReturnReceipt = false;
+ fields->GetReturnReceipt(&hasReturnReceipt);
+ if (hasReturnReceipt)
+ {
+ // slight change compared to 4.x; we used to use receipt= to tell
+ // whether the draft/template has request for either MDN or DNS or both
+ // return receipt; since the DNS is out of the picture we now use the
+ // header type + 1 to tell whether user has requested the return receipt
+ int32_t headerType = 0;
+ fields->GetReceiptHeaderType(&headerType);
+ draftInfo.AppendLiteral("receipt=");
+ draftInfo.AppendInt(headerType + 1);
+ }
+ else
+ draftInfo.AppendLiteral("receipt=0");
+ draftInfo.AppendLiteral("; ");
+ APPEND_BOOL(DSN, "DSN");
+ draftInfo.AppendLiteral("; ");
+ draftInfo.AppendLiteral("uuencode=0");
+ draftInfo.AppendLiteral("; ");
+ APPEND_BOOL(AttachmentReminder, "attachmentreminder");
+ draftInfo.AppendLiteral("; ");
+ int32_t deliveryFormat;
+ fields->GetDeliveryFormat(&deliveryFormat);
+ draftInfo.AppendLiteral("deliveryformat=");
+ draftInfo.AppendInt(deliveryFormat);
+
+ finalHeaders->SetRawHeader(HEADER_X_MOZILLA_DRAFT_INFO, draftInfo, nullptr);
+ }
+
+ nsCOMPtr<nsIHttpProtocolHandler> pHTTPHandler = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv);
+ if (NS_SUCCEEDED(rv) && pHTTPHandler)
+ {
+ nsAutoCString userAgentString;
+ pHTTPHandler->GetUserAgent(userAgentString);
+
+ if (!userAgentString.IsEmpty())
+ finalHeaders->SetUnstructuredHeader("User-Agent",
+ NS_ConvertUTF8toUTF16(userAgentString));
+ }
+
+ finalHeaders->SetUnstructuredHeader("MIME-Version", NS_LITERAL_STRING("1.0"));
+
+ nsAutoCString newsgroups;
+ finalHeaders->GetRawHeader("Newsgroups", newsgroups);
+ if (!newsgroups.IsEmpty())
+ {
+ // Since the newsgroup header can contain data in the form of:
+ // "news://news.mozilla.org/netscape.test,news://news.mozilla.org/netscape.junk"
+ // we need to turn that into: "netscape.test,netscape.junk"
+ // (XXX: can it really?)
+ nsCOMPtr<nsINntpService> nntpService =
+ do_GetService("@mozilla.org/messenger/nntpservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString newsgroupsHeaderVal;
+ nsCString newshostHeaderVal;
+ rv = nntpService->GenerateNewsHeaderValsForPosting(newsgroups,
+ getter_Copies(newsgroupsHeaderVal), getter_Copies(newshostHeaderVal));
+ NS_ENSURE_SUCCESS(rv, rv);
+ finalHeaders->SetRawHeader("Newsgroups", newsgroupsHeaderVal, nullptr);
+
+ // If we are here, we are NOT going to send this now. (i.e. it is a Draft,
+ // Send Later file, etc...). Because of that, we need to store what the user
+ // typed in on the original composition window for use later when rebuilding
+ // the headers
+ if (deliver_mode != nsIMsgSend::nsMsgDeliverNow &&
+ deliver_mode != nsIMsgSend::nsMsgSendUnsent)
+ {
+ // This is going to be saved for later, that means we should just store
+ // what the user typed into the "Newsgroup" line in the HEADER_X_MOZILLA_NEWSHOST
+ // header for later use by "Send Unsent Messages", "Drafts" or "Templates"
+ finalHeaders->SetRawHeader(HEADER_X_MOZILLA_NEWSHOST, newshostHeaderVal,
+ nullptr);
+ }
+
+ // Newsgroups are a recipient...
+ hasDisclosedRecipient = true;
+ }
+
+ nsCOMArray<msgIAddressObject> recipients;
+ finalHeaders->GetAddressingHeader("To", recipients);
+ hasDisclosedRecipient |= !recipients.IsEmpty();
+ finalHeaders->GetAddressingHeader("Cc", recipients);
+ hasDisclosedRecipient |= !recipients.IsEmpty();
+
+ // If we don't have disclosed recipient (only Bcc), address the message to
+ // undisclosed-recipients to prevent problem with some servers
+
+ // If we are saving the message as a draft, don't bother inserting the undisclosed recipients field. We'll take care of that when we
+ // really send the message.
+ if (!hasDisclosedRecipient && !isDraft)
+ {
+ bool bAddUndisclosedRecipients = true;
+ prefs->GetBoolPref("mail.compose.add_undisclosed_recipients", &bAddUndisclosedRecipients);
+ if (bAddUndisclosedRecipients)
+ {
+ bool hasBcc = false;
+ fields->HasHeader("Bcc", &hasBcc);
+ if (hasBcc)
+ {
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (stringService)
+ {
+ nsCOMPtr<nsIStringBundle> composeStringBundle;
+ rv = stringService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(composeStringBundle));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString undisclosedRecipients;
+ rv = composeStringBundle->GetStringFromName(u"undisclosedRecipients",
+ getter_Copies(undisclosedRecipients));
+ if (NS_SUCCEEDED(rv) && !undisclosedRecipients.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(
+ mozilla::services::GetHeaderParser());
+ nsCOMPtr<msgIAddressObject> group;
+ headerParser->MakeGroupObject(undisclosedRecipients,
+ nullptr, 0, getter_AddRefs(group));
+ recipients.AppendElement(group);
+ finalHeaders->SetAddressingHeader("To", recipients);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // We don't want to emit a Bcc header to the output. If we are saving this to
+ // Drafts/Sent, this is readded later in nsMsgSend.cpp.
+ finalHeaders->DeleteHeader("bcc");
+
+ // Skip no or empty priority.
+ nsAutoCString priority;
+ rv = fields->GetRawHeader("X-Priority", priority);
+ if (NS_SUCCEEDED(rv) && !priority.IsEmpty())
+ {
+ nsMsgPriorityValue priorityValue;
+
+ NS_MsgGetPriorityFromString(priority.get(), priorityValue);
+
+ // Skip default priority.
+ if (priorityValue != nsMsgPriority::Default) {
+ nsAutoCString priorityName;
+ nsAutoCString priorityValueString;
+
+ NS_MsgGetPriorityValueString(priorityValue, priorityValueString);
+ NS_MsgGetUntranslatedPriorityName(priorityValue, priorityName);
+
+ // Output format: [X-Priority: <pValue> (<pName>)]
+ priorityValueString.AppendLiteral(" (");
+ priorityValueString += priorityName;
+ priorityValueString.AppendLiteral(")");
+ finalHeaders->SetRawHeader("X-Priority", priorityValueString, nullptr);
+ }
+ }
+
+ nsAutoCString references;
+ finalHeaders->GetRawHeader("References", references);
+ if (!references.IsEmpty())
+ {
+ // The References header should be kept under 998 characters: if it's too
+ // long, trim out the earliest references to make it smaller.
+ if (references.Length() > 986)
+ {
+ int32_t firstRef = references.FindChar('<');
+ int32_t secondRef = references.FindChar('<', firstRef + 1);
+ if (secondRef > 0)
+ {
+ nsAutoCString newReferences(StringHead(references, secondRef));
+ int32_t bracket = references.FindChar('<',
+ references.Length() + newReferences.Length() - 986);
+ if (bracket > 0)
+ {
+ newReferences.Append(Substring(references, bracket));
+ finalHeaders->SetRawHeader("References", newReferences, nullptr);
+ }
+ }
+ }
+ // The In-Reply-To header is the last entry in the references header...
+ int32_t bracket = references.RFind("<");
+ if (bracket >= 0)
+ finalHeaders->SetRawHeader("In-Reply-To", Substring(references, bracket),
+ nullptr);
+ }
+
+ return NS_OK;
+}
+
+#undef APPEND_BOOL // X-Mozilla-Draft-Info helper macro
+
+static void
+GenerateGlobalRandomBytes(unsigned char *buf, int32_t len)
+{
+ // Attempt to generate bytes from system entropy-based RNG.
+ nsCOMPtr<nsIRandomGenerator> randomGenerator(do_GetService("@mozilla.org/security/random-generator;1"));
+ MOZ_ASSERT(randomGenerator, "nsIRandomGenerator service not retrievable");
+ uint8_t *tempBuffer;
+ nsresult rv = randomGenerator->GenerateRandomBytes(len, &tempBuffer);
+ if (NS_SUCCEEDED(rv))
+ {
+ memcpy(buf, tempBuffer, len);
+ free(tempBuffer);
+ return;
+ }
+ // nsIRandomGenerator failed -- fall back to low entropy PRNG.
+ static bool firstTime = true;
+ if (firstTime)
+ {
+ // Seed the random-number generator with current time so that
+ // the numbers will be different every time we run.
+ srand( (unsigned)PR_Now() );
+ firstTime = false;
+ }
+
+ for( int32_t i = 0; i < len; i++ )
+ buf[i] = rand() % 256;
+}
+
+char
+*mime_make_separator(const char *prefix)
+{
+ unsigned char rand_buf[13];
+ GenerateGlobalRandomBytes(rand_buf, 12);
+
+ return PR_smprintf("------------%s"
+ "%02X%02X%02X%02X"
+ "%02X%02X%02X%02X"
+ "%02X%02X%02X%02X",
+ prefix,
+ rand_buf[0], rand_buf[1], rand_buf[2], rand_buf[3],
+ rand_buf[4], rand_buf[5], rand_buf[6], rand_buf[7],
+ rand_buf[8], rand_buf[9], rand_buf[10], rand_buf[11]);
+}
+
+static char *
+RFC2231ParmFolding(const char *parmName, const nsCString& charset,
+ const char *language, const nsString& parmValue);
+
+static char *
+LegacyParmFolding(const nsCString& aCharset,
+ const nsCString& aFileName, int32_t aParmFolding);
+
+char *
+mime_generate_attachment_headers (const char *type,
+ const char *type_param,
+ const char *encoding,
+ const char *description,
+ const char *x_mac_type,
+ const char *x_mac_creator,
+ const char *real_name,
+ const char *base_url,
+ bool /*digest_p*/,
+ nsMsgAttachmentHandler * /*ma*/,
+ const char *attachmentCharset,
+ const char *bodyCharset,
+ bool bodyIsAsciiOnly,
+ const char *content_id,
+ bool aBodyDocument)
+{
+ NS_ASSERTION (encoding, "null encoding");
+
+ int32_t parmFolding = 2; // RFC 2231-compliant
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ prefs->GetIntPref("mail.strictly_mime.parm_folding", &parmFolding);
+
+ /* Let's encode the real name */
+ char *encodedRealName = nullptr;
+ nsCString charset; // actual charset used for MIME encode
+ nsAutoString realName;
+ if (real_name)
+ {
+ // first try main body's charset to encode the file name,
+ // then try local file system charset if fails
+ CopyUTF8toUTF16(nsDependentCString(real_name), realName);
+ if (bodyCharset && *bodyCharset &&
+ nsMsgI18Ncheck_data_in_charset_range(bodyCharset, realName.get()))
+ charset.Assign(bodyCharset);
+ else
+ {
+ charset.Assign(nsMsgI18NFileSystemCharset());
+ if (!nsMsgI18Ncheck_data_in_charset_range(charset.get(), realName.get()))
+ charset.Assign("UTF-8"); // set to UTF-8 if fails again
+ }
+
+ encodedRealName = RFC2231ParmFolding("filename", charset, nullptr,
+ realName);
+ // somehow RFC2231ParamFolding failed. fall back to legacy method
+ if (!encodedRealName || !*encodedRealName) {
+ PR_FREEIF(encodedRealName);
+ parmFolding = 0;
+ // Not RFC 2231 style encoding (it's not standard-compliant)
+ encodedRealName =
+ LegacyParmFolding(charset, nsDependentCString(real_name), parmFolding);
+ }
+ }
+
+ nsCString buf; // very likely to be longer than 64 characters
+ buf.Append("Content-Type: ");
+ buf.Append(type);
+ if (type_param && *type_param)
+ {
+ if (*type_param != ';')
+ buf.Append("; ");
+ buf.Append(type_param);
+ }
+
+ if (mime_type_needs_charset (type))
+ {
+
+ char charset_label[65] = ""; // Content-Type: charset
+ if (attachmentCharset)
+ {
+ PL_strncpy(charset_label, attachmentCharset, sizeof(charset_label)-1);
+ charset_label[sizeof(charset_label)-1] = '\0';
+ }
+
+ /* If the characters are all 7bit, arguably it's better to
+ claim the charset to be US-ASCII. However, it causes
+ a major 'interoperability problem' with MS OE, which makes it hard
+ to sell Mozilla/TB to people most of whose correspondents use
+ MS OE. MS OE turns all non-ASCII characters to question marks
+ in replies to messages labeled as US-ASCII if users select 'send as is'
+ with MIME turned on. (with MIME turned off, this happens without
+ any warning.) To avoid this, we use the label 'US-ASCII' only when
+ it's explicitly requested by setting the hidden pref.
+ 'mail.label_ascii_only_mail_as_us_ascii'. (bug 247958) */
+ bool labelAsciiAsAscii = false;
+ if (prefs)
+ prefs->GetBoolPref("mail.label_ascii_only_mail_as_us_ascii",
+ &labelAsciiAsAscii);
+ if (labelAsciiAsAscii && encoding &&
+ !PL_strcasecmp (encoding, "7bit") && bodyIsAsciiOnly)
+ PL_strcpy (charset_label, "us-ascii");
+
+ // If charset is multibyte then no charset to be specified (apply base64 instead).
+ // The list of file types match with PickEncoding() where we put base64 label.
+ if ( ((attachmentCharset && !nsMsgI18Nmultibyte_charset(attachmentCharset)) ||
+ ((PL_strcasecmp(type, TEXT_HTML) == 0) ||
+ (PL_strcasecmp(type, TEXT_MDL) == 0) ||
+ (PL_strcasecmp(type, TEXT_PLAIN) == 0) ||
+ (PL_strcasecmp(type, TEXT_RICHTEXT) == 0) ||
+ (PL_strcasecmp(type, TEXT_ENRICHED) == 0) ||
+ (PL_strcasecmp(type, TEXT_VCARD) == 0) ||
+ (PL_strcasecmp(type, APPLICATION_DIRECTORY) == 0) || /* text/x-vcard synonym */
+ (PL_strcasecmp(type, TEXT_CSS) == 0) ||
+ (PL_strcasecmp(type, TEXT_JSSS) == 0)) ||
+ (PL_strcasecmp(encoding, ENCODING_BASE64) != 0)) &&
+ (*charset_label))
+ {
+ buf.Append("; charset=");
+ buf.Append(charset_label);
+ }
+ }
+
+ // Only do this if we are in the body of a message
+ if (aBodyDocument)
+ {
+ // Add format=flowed as in RFC 2646 if we are using that
+ if(type && !PL_strcasecmp(type, "text/plain"))
+ {
+ bool flowed, delsp, formatted, disallowBreaks;
+ GetSerialiserFlags(bodyCharset, &flowed, &delsp, &formatted, &disallowBreaks);
+ if(flowed)
+ buf.Append("; format=flowed");
+ if (delsp)
+ buf.Append("; delsp=yes");
+ // else
+ // {
+ // Don't add a markup. Could use
+ // PUSH_STRING ("; format=fixed");
+ // but it is equivalent to nothing at all and we do want
+ // to save bandwidth. Don't we?
+ // }
+ }
+ }
+
+ if (x_mac_type && *x_mac_type) {
+ buf.Append("; x-mac-type=\"");
+ buf.Append(x_mac_type);
+ buf.Append("\"");
+ }
+
+ if (x_mac_creator && *x_mac_creator) {
+ buf.Append("; x-mac-creator=\"");
+ buf.Append(x_mac_creator);
+ buf.Append("\"");
+ }
+
+#ifdef EMIT_NAME_IN_CONTENT_TYPE
+ if (encodedRealName && *encodedRealName) {
+ // Note that we don't need to output the name field if the name encoding is
+ // RFC 2231. If the MUA knows the RFC 2231, it should know the RFC 2183 too.
+ if (parmFolding != 2) {
+ // The underlying JS MIME code will only handle UTF-8 here.
+ char *nameValue = LegacyParmFolding(NS_LITERAL_CSTRING("UTF-8"),
+ nsDependentCString(real_name),
+ parmFolding);
+ if (!nameValue || !*nameValue) {
+ PR_FREEIF(nameValue);
+ nameValue = encodedRealName;
+ }
+ buf.Append(";\r\n name=\"");
+ buf.Append(nameValue);
+ buf.Append("\"");
+ if (nameValue != encodedRealName)
+ PR_FREEIF(nameValue);
+ }
+ }
+#endif /* EMIT_NAME_IN_CONTENT_TYPE */
+ buf.Append(CRLF);
+
+ buf.Append("Content-Transfer-Encoding: ");
+ buf.Append(encoding);
+ buf.Append(CRLF);
+
+ if (description && *description) {
+ char *s = mime_fix_header (description);
+ if (s) {
+ buf.Append("Content-Description: ");
+ buf.Append(s);
+ buf.Append(CRLF);
+ PR_Free(s);
+ }
+ }
+
+ if ( (content_id) && (*content_id) )
+ {
+ buf.Append("Content-ID: <");
+ buf.Append(content_id);
+ buf.Append(">");
+ buf.Append(CRLF);
+ }
+
+ if (encodedRealName && *encodedRealName) {
+ char *period = PL_strrchr(encodedRealName, '.');
+ int32_t pref_content_disposition = 0;
+
+ if (prefs) {
+ mozilla::DebugOnly<nsresult> rv = prefs->GetIntPref("mail.content_disposition_type",
+ &pref_content_disposition);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get mail.content_disposition_type");
+ }
+
+ buf.Append("Content-Disposition: ");
+
+ // If this is an attachment which is part of the message body and therefore has a
+ // Content-ID (e.g, image in HTML msg), then Content-Disposition has to be inline
+ if (content_id && *content_id)
+ buf.Append("inline");
+ else if (pref_content_disposition == 1)
+ buf.Append("attachment");
+ else
+ if (pref_content_disposition == 2 &&
+ (!PL_strcasecmp(type, TEXT_PLAIN) ||
+ (period && !PL_strcasecmp(period, ".txt"))))
+ buf.Append("attachment");
+
+ /* If this document is an anonymous binary file or a vcard,
+ then always show it as an attachment, never inline. */
+ else
+ if (!PL_strcasecmp(type, APPLICATION_OCTET_STREAM) ||
+ !PL_strcasecmp(type, TEXT_VCARD) ||
+ !PL_strcasecmp(type, APPLICATION_DIRECTORY)) /* text/x-vcard synonym */
+ buf.Append("attachment");
+ else
+ buf.Append("inline");
+
+ buf.Append(";\r\n ");
+ buf.Append(encodedRealName);
+ buf.Append(CRLF);
+ }
+ else
+ if (type &&
+ (!PL_strcasecmp (type, MESSAGE_RFC822) ||
+ !PL_strcasecmp (type, MESSAGE_NEWS)))
+ buf.Append("Content-Disposition: inline" CRLF);
+
+#ifdef GENERATE_CONTENT_BASE
+ /* If this is an HTML document, and we know the URL it originally
+ came from, write out a Content-Base header. */
+ if (type &&
+ (!PL_strcasecmp (type, TEXT_HTML) ||
+ !PL_strcasecmp (type, TEXT_MDL)) &&
+ base_url && *base_url)
+ {
+ int32_t col = 0;
+ const char *s = base_url;
+ const char *colon = PL_strchr (s, ':');
+ bool useContentLocation = false; /* rhp - add this */
+
+ if (!colon)
+ goto GIVE_UP_ON_CONTENT_BASE; /* malformed URL? */
+
+ /* Don't emit a content-base that points to (or into) a news or
+ mail message. */
+ if (!PL_strncasecmp (s, "news:", 5) ||
+ !PL_strncasecmp (s, "snews:", 6) ||
+ !PL_strncasecmp (s, "IMAP:", 5) ||
+ !PL_strncasecmp (s, "file:", 5) || /* rhp: fix targets from included HTML files */
+ !PL_strncasecmp (s, "mailbox:", 8))
+ goto GIVE_UP_ON_CONTENT_BASE;
+
+ /* rhp - Put in a pref for using Content-Location instead of Content-Base.
+ This will get tweaked to default to true in 5.0
+ */
+ if (prefs)
+ prefs->GetBoolPref("mail.use_content_location_on_send", &useContentLocation);
+
+ if (useContentLocation)
+ buf.Append("Content-Location: \"");
+ else
+ buf.Append("Content-Base: \"");
+ /* rhp - Pref for Content-Location usage */
+
+/* rhp: this is to work with the Content-Location stuff */
+CONTENT_LOC_HACK:
+
+ while (*s != 0 && *s != '#')
+ {
+ uint32_t ot=buf.Length();
+ char tmp[]="\x00\x00";
+ /* URLs must be wrapped at 40 characters or less. */
+ if (col >= 38) {
+ buf.Append(CRLF "\t");
+ col = 0;
+ }
+
+ if (*s == ' ')
+ buf.Append("%20");
+ else if (*s == '\t')
+ buf.Append("%09");
+ else if (*s == '\n')
+ buf.Append("%0A");
+ else if (*s == '\r')
+ buf.Append("%0D");
+ else {
+ tmp[0]=*s;
+ buf.Append(tmp);
+ }
+ s++;
+ col += (buf.Length() - ot);
+ }
+ buf.Append("\"" CRLF);
+
+ /* rhp: this is to try to get around this fun problem with Content-Location */
+ if (!useContentLocation) {
+ buf.Append("Content-Location: \"");
+ s = base_url;
+ col = 0;
+ useContentLocation = true;
+ goto CONTENT_LOC_HACK;
+ }
+ /* rhp: this is to try to get around this fun problem with Content-Location */
+
+GIVE_UP_ON_CONTENT_BASE:
+ ;
+ }
+#endif /* GENERATE_CONTENT_BASE */
+
+ /* realloc it smaller... */
+
+#ifdef DEBUG_jungshik
+ printf ("header=%s\n", buf.get());
+#endif
+ PR_Free(encodedRealName);
+ return PL_strdup(buf.get());
+}
+
+static bool isValidHost( const char* host )
+{
+ if ( host )
+ for (const char *s = host; *s; ++s)
+ if ( !isalpha(*s)
+ && !isdigit(*s)
+ && *s != '-'
+ && *s != '_'
+ && *s != '.'
+ )
+ {
+ host = nullptr;
+ break;
+ }
+
+ return nullptr != host;
+}
+
+char *
+msg_generate_message_id (nsIMsgIdentity *identity)
+{
+ const char *host = 0;
+
+ nsCString forcedFQDN;
+ nsCString from;
+ nsresult rv = NS_OK;
+
+ rv = identity->GetCharAttribute("FQDN", forcedFQDN);
+
+ if (NS_SUCCEEDED(rv) && !forcedFQDN.IsEmpty())
+ host = forcedFQDN.get();
+
+ if (!isValidHost(host))
+ {
+ nsresult rv = identity->GetEmail(from);
+ if (NS_SUCCEEDED(rv) && !from.IsEmpty())
+ host = strchr(from.get(),'@');
+
+ // No '@'? Munged address, anti-spam?
+ // see bug #197203
+ if (host)
+ ++host;
+ }
+
+ if (!isValidHost(host))
+ /* If we couldn't find a valid host name to use, we can't generate a
+ valid message ID, so bail, and let NNTP and SMTP generate them. */
+ return 0;
+
+ // Generate 128-bit UUID for the local part. We use the high-entropy
+ // GenerateGlobalRandomBytes to make tracking more difficult.
+ nsID uuid;
+ GenerateGlobalRandomBytes((unsigned char*) &uuid, sizeof(nsID));
+ char uuidString[NSID_LENGTH];
+ uuid.ToProvidedString(uuidString);
+ // Drop first and last characters (curly braces).
+ uuidString[NSID_LENGTH - 2] = 0;
+ return PR_smprintf("<%s@%s>", uuidString + 1, host);
+}
+
+
+inline static bool is7bitCharset(const nsCString& charset)
+{
+ // charset name is canonical (no worry about case-sensitivity)
+ return Substring(charset, 0, 8).EqualsLiteral("ISO-2022-");
+}
+
+#define PR_MAX_FOLDING_LEN 75 // this is to gurantee the folded line will
+ // never be greater than 78 = 75 + CRLFLWSP
+/*static */ char *
+RFC2231ParmFolding(const char *parmName, const nsCString& charset,
+ const char *language, const nsString& parmValue)
+{
+ NS_ENSURE_TRUE(parmName && *parmName && !parmValue.IsEmpty(), nullptr);
+
+ bool needEscape;
+ nsCString dupParm;
+
+ if (!NS_IsAscii(static_cast<const char16_t*>(parmValue.get())) || is7bitCharset(charset)) {
+ needEscape = true;
+ nsAutoCString nativeParmValue;
+ ConvertFromUnicode(charset.get(), parmValue, nativeParmValue);
+ MsgEscapeString(nativeParmValue, nsINetUtil::ESCAPE_ALL, dupParm);
+ }
+ else {
+ needEscape = false;
+ dupParm.Adopt(
+ msg_make_filename_qtext(NS_LossyConvertUTF16toASCII(parmValue).get(),
+ true));
+ }
+
+ if (dupParm.IsEmpty())
+ return nullptr;
+
+ int32_t parmNameLen = PL_strlen(parmName);
+ int32_t parmValueLen = dupParm.Length();
+
+ parmNameLen += 5; // *=__'__'___ or *[0]*=__'__'__ or *[1]*=___ or *[0]="___"
+
+ int32_t languageLen = language ? PL_strlen(language) : 0;
+ int32_t charsetLen = charset.Length();
+ char *foldedParm = nullptr;
+
+ if ((parmValueLen + parmNameLen + charsetLen + languageLen) <
+ PR_MAX_FOLDING_LEN)
+ {
+ foldedParm = PL_strdup(parmName);
+ if (needEscape)
+ {
+ NS_MsgSACat(&foldedParm, "*=");
+ if (charsetLen)
+ NS_MsgSACat(&foldedParm, charset.get());
+ NS_MsgSACat(&foldedParm, "'");
+ if (languageLen)
+ NS_MsgSACat(&foldedParm, language);
+ NS_MsgSACat(&foldedParm, "'");
+ }
+ else
+ NS_MsgSACat(&foldedParm, "=\"");
+ NS_MsgSACat(&foldedParm, dupParm.get());
+ if (!needEscape)
+ NS_MsgSACat(&foldedParm, "\"");
+ }
+ else
+ {
+ int curLineLen = 0;
+ int counter = 0;
+ char digits[32];
+ char *start = dupParm.BeginWriting();
+ char *end = NULL;
+ char tmp = 0;
+
+ while (parmValueLen > 0)
+ {
+ curLineLen = 0;
+ if (counter == 0) {
+ PR_FREEIF(foldedParm)
+ foldedParm = PL_strdup(parmName);
+ }
+ else {
+ NS_MsgSACat(&foldedParm, ";\r\n ");
+ NS_MsgSACat(&foldedParm, parmName);
+ }
+ PR_snprintf(digits, sizeof(digits), "*%d", counter);
+ NS_MsgSACat(&foldedParm, digits);
+ curLineLen += PL_strlen(digits);
+ if (needEscape)
+ {
+ NS_MsgSACat(&foldedParm, "*=");
+ if (counter == 0)
+ {
+ if (charsetLen)
+ NS_MsgSACat(&foldedParm, charset.get());
+ NS_MsgSACat(&foldedParm, "'");
+ if (languageLen)
+ NS_MsgSACat(&foldedParm, language);
+ NS_MsgSACat(&foldedParm, "'");
+ curLineLen += charsetLen;
+ curLineLen += languageLen;
+ }
+ }
+ else
+ {
+ NS_MsgSACat(&foldedParm, "=\"");
+ }
+ counter++;
+ curLineLen += parmNameLen;
+ if (parmValueLen <= PR_MAX_FOLDING_LEN - curLineLen)
+ end = start + parmValueLen;
+ else
+ end = start + (PR_MAX_FOLDING_LEN - curLineLen);
+
+ tmp = 0;
+ if (*end && needEscape)
+ {
+ // check to see if we are in the middle of escaped char
+ if (*end == '%')
+ {
+ tmp = '%'; *end = 0;
+ }
+ else if (end-1 > start && *(end-1) == '%')
+ {
+ end -= 1; tmp = '%'; *end = 0;
+ }
+ else if (end-2 > start && *(end-2) == '%')
+ {
+ end -= 2; tmp = '%'; *end = 0;
+ }
+ else
+ {
+ tmp = *end; *end = 0;
+ }
+ }
+ else
+ {
+ // XXX should check if we are in the middle of escaped char (RFC 822)
+ tmp = *end; *end = 0;
+ }
+ NS_MsgSACat(&foldedParm, start);
+ if (!needEscape)
+ NS_MsgSACat(&foldedParm, "\"");
+
+ parmValueLen -= (end-start);
+ if (tmp)
+ *end = tmp;
+ start = end;
+ }
+ }
+
+ return foldedParm;
+}
+
+/*static */ char *
+LegacyParmFolding(const nsCString& aCharset,
+ const nsCString& aFileName, int32_t aParmFolding)
+{
+ bool usemime = nsMsgMIMEGetConformToStandard();
+ char *encodedRealName =
+ nsMsgI18NEncodeMimePartIIStr(aFileName.get(), false, aCharset.get(),
+ 0, usemime);
+
+ if (!encodedRealName || !*encodedRealName) {
+ PR_FREEIF(encodedRealName);
+ encodedRealName = (char *) PR_Malloc(aFileName.Length() + 1);
+ if (encodedRealName)
+ PL_strcpy(encodedRealName, aFileName.get());
+ }
+
+ // Now put backslashes before special characters per RFC 822
+ char *qtextName =
+ msg_make_filename_qtext(encodedRealName, aParmFolding == 0);
+ if (qtextName) {
+ PR_FREEIF(encodedRealName);
+ encodedRealName = qtextName;
+ }
+ return encodedRealName;
+}
+
+bool
+mime_7bit_data_p (const char *string, uint32_t size)
+{
+ if ((!string) || (!*string))
+ return true;
+
+ char *ptr = (char *)string;
+ for (uint32_t i=0; i<size; i++)
+ {
+ if ((unsigned char) ptr[i] > 0x7F)
+ return false;
+ }
+ return true;
+}
+
+/* Strips whitespace, and expands newlines into newline-tab for use in
+ mail headers. Returns a new string or 0 (if it would have been empty.)
+ If addr_p is true, the addresses will be parsed and reemitted as
+ rfc822 mailboxes.
+ */
+char *
+mime_fix_header_1 (const char *string, bool addr_p, bool news_p)
+{
+ char *new_string;
+ const char *in;
+ char *out;
+ int32_t i, old_size, new_size;
+
+ if (!string || !*string)
+ return 0;
+
+ if (addr_p) {
+ return strdup(string);
+ }
+
+ old_size = PL_strlen (string);
+ new_size = old_size;
+ for (i = 0; i < old_size; i++)
+ if (string[i] == '\r' || string[i] == '\n')
+ new_size += 2;
+
+ new_string = (char *) PR_Malloc (new_size + 1);
+ if (! new_string)
+ return 0;
+
+ in = string;
+ out = new_string;
+
+ /* strip leading whitespace. */
+ while (IS_SPACE (*in))
+ in++;
+
+ /* replace CR, LF, or CRLF with CRLF-TAB. */
+ while (*in) {
+ if (*in == '\r' || *in == '\n') {
+ if (*in == '\r' && in[1] == '\n')
+ in++;
+ in++;
+ *out++ = '\r';
+ *out++ = '\n';
+ *out++ = '\t';
+ }
+ else
+ if (news_p && *in == ',') {
+ *out++ = *in++;
+ /* skip over all whitespace after a comma. */
+ while (IS_SPACE (*in))
+ in++;
+ }
+ else
+ *out++ = *in++;
+ }
+ *out = 0;
+
+ /* strip trailing whitespace. */
+ while (out > in && IS_SPACE (out[-1]))
+ *out-- = 0;
+
+ /* If we ended up throwing it all away, use 0 instead of "". */
+ if (!*new_string) {
+ PR_Free (new_string);
+ new_string = 0;
+ }
+
+ return new_string;
+}
+
+char *
+mime_fix_header (const char *string)
+{
+ return mime_fix_header_1 (string, false, false);
+}
+
+char *
+mime_fix_addr_header (const char *string)
+{
+ return mime_fix_header_1 (string, true, false);
+}
+
+char *
+mime_fix_news_header (const char *string)
+{
+ return mime_fix_header_1 (string, false, true);
+}
+
+bool
+mime_type_requires_b64_p (const char *type)
+{
+ if (!type || !PL_strcasecmp (type, UNKNOWN_CONTENT_TYPE))
+ /* Unknown types don't necessarily require encoding. (Note that
+ "unknown" and "application/octet-stream" aren't the same.) */
+ return false;
+
+ else if (!PL_strncasecmp (type, "image/", 6) ||
+ !PL_strncasecmp (type, "audio/", 6) ||
+ !PL_strncasecmp (type, "video/", 6) ||
+ !PL_strncasecmp (type, "application/", 12))
+ {
+ /* The following types are application/ or image/ types that are actually
+ known to contain textual data (meaning line-based, not binary, where
+ CRLF conversion is desired rather than disasterous.) So, if the type
+ is any of these, it does not *require* base64, and if we do need to
+ encode it for other reasons, we'll probably use quoted-printable.
+ But, if it's not one of these types, then we assume that any subtypes
+ of the non-"text/" types are binary data, where CRLF conversion would
+ corrupt it, so we use base64 right off the bat.
+
+ The reason it's desirable to ship these as text instead of just using
+ base64 all the time is mainly to preserve the readability of them for
+ non-MIME users: if I mail a /bin/sh script to someone, it might not
+ need to be encoded at all, so we should leave it readable if we can.
+
+ This list of types was derived from the comp.mail.mime FAQ, section
+ 10.2.2, "List of known unregistered MIME types" on 2-Feb-96.
+ */
+ static const char *app_and_image_types_which_are_really_text[] = {
+ "application/mac-binhex40", /* APPLICATION_BINHEX */
+ "application/pgp", /* APPLICATION_PGP */
+ "application/pgp-keys",
+ "application/x-pgp-message", /* APPLICATION_PGP2 */
+ "application/postscript", /* APPLICATION_POSTSCRIPT */
+ "application/x-uuencode", /* APPLICATION_UUENCODE */
+ "application/x-uue", /* APPLICATION_UUENCODE2 */
+ "application/uue", /* APPLICATION_UUENCODE4 */
+ "application/uuencode", /* APPLICATION_UUENCODE3 */
+ "application/sgml",
+ "application/x-csh",
+ "application/javascript",
+ "application/ecmascript",
+ "application/x-javascript",
+ "application/x-latex",
+ "application/x-macbinhex40",
+ "application/x-ns-proxy-autoconfig",
+ "application/x-www-form-urlencoded",
+ "application/x-perl",
+ "application/x-sh",
+ "application/x-shar",
+ "application/x-tcl",
+ "application/x-tex",
+ "application/x-texinfo",
+ "application/x-troff",
+ "application/x-troff-man",
+ "application/x-troff-me",
+ "application/x-troff-ms",
+ "application/x-troff-ms",
+ "application/x-wais-source",
+ "image/x-bitmap",
+ "image/x-pbm",
+ "image/x-pgm",
+ "image/x-portable-anymap",
+ "image/x-portable-bitmap",
+ "image/x-portable-graymap",
+ "image/x-portable-pixmap", /* IMAGE_PPM */
+ "image/x-ppm",
+ "image/x-xbitmap", /* IMAGE_XBM */
+ "image/x-xbm", /* IMAGE_XBM2 */
+ "image/xbm", /* IMAGE_XBM3 */
+ "image/x-xpixmap",
+ "image/x-xpm",
+ 0 };
+ const char **s;
+ for (s = app_and_image_types_which_are_really_text; *s; s++)
+ if (!PL_strcasecmp (type, *s))
+ return false;
+
+ /* All others must be assumed to be binary formats, and need Base64. */
+ return true;
+ }
+
+ else
+ return false;
+}
+
+//
+// Some types should have a "charset=" parameter, and some shouldn't.
+// This is what decides.
+//
+bool
+mime_type_needs_charset (const char *type)
+{
+ /* Only text types should have charset. */
+ if (!type || !*type)
+ return false;
+ else
+ if (!PL_strncasecmp (type, "text", 4))
+ return true;
+ else
+ return false;
+}
+
+/* Given a string, convert it to 'qtext' (quoted text) for RFC822 header purposes. */
+char *
+msg_make_filename_qtext(const char *srcText, bool stripCRLFs)
+{
+ /* newString can be at most twice the original string (every char quoted). */
+ char *newString = (char *) PR_Malloc(PL_strlen(srcText)*2 + 1);
+ if (!newString) return NULL;
+
+ const char *s = srcText;
+ const char *end = srcText + PL_strlen(srcText);
+ char *d = newString;
+
+ while(*s)
+ {
+ /* Put backslashes in front of existing backslashes, or double quote
+ characters.
+ If stripCRLFs is true, don't write out CRs or LFs. Otherwise,
+ write out a backslash followed by the CR but not
+ linear-white-space.
+ We might already have quoted pair of "\ " or "\\t" skip it.
+ */
+ if (*s == '\\' || *s == '"' ||
+ (!stripCRLFs &&
+ (*s == '\r' && (s[1] != '\n' ||
+ (s[1] == '\n' && (s+2) < end && !IS_SPACE(s[2]))))))
+ *d++ = '\\';
+
+ if (stripCRLFs && *s == '\r' && s[1] == '\n' && (s+2) < end && IS_SPACE(s[2]))
+ {
+ s += 3; // skip CRLFLWSP
+ }
+ else
+ {
+ *d++ = *s++;
+ }
+ }
+ *d = 0;
+
+ return newString;
+}
+
+/* Rip apart the URL and extract a reasonable value for the `real_name' slot.
+ */
+void
+msg_pick_real_name (nsMsgAttachmentHandler *attachment, const char16_t *proposedName, const char *charset)
+{
+ const char *s, *s2;
+
+ if (!attachment->m_realName.IsEmpty())
+ return;
+
+ if (proposedName && *proposedName)
+ {
+ attachment->m_realName.Adopt(ToNewUTF8String(nsAutoString(proposedName)));
+ }
+ else //Let's extract the name from the URL
+ {
+ nsCString url;
+ nsresult rv = attachment->mURL->GetSpec(url);
+ if (NS_FAILED(rv))
+ return;
+
+ s = url.get();
+ s2 = PL_strchr (s, ':');
+ if (s2)
+ s = s2 + 1;
+ /* If we know the URL doesn't have a sensible file name in it,
+ don't bother emitting a content-disposition. */
+ if (StringBeginsWith (url, NS_LITERAL_CSTRING("news:"), nsCaseInsensitiveCStringComparator()) ||
+ StringBeginsWith (url, NS_LITERAL_CSTRING("snews:"), nsCaseInsensitiveCStringComparator()) ||
+ StringBeginsWith (url, NS_LITERAL_CSTRING("IMAP:"), nsCaseInsensitiveCStringComparator()) ||
+ StringBeginsWith (url, NS_LITERAL_CSTRING("mailbox:"), nsCaseInsensitiveCStringComparator()))
+ return;
+
+ if (StringBeginsWith(url, NS_LITERAL_CSTRING("data:"),
+ nsCaseInsensitiveCStringComparator()))
+ {
+ int32_t endNonData = url.FindChar(',');
+ if (endNonData == -1)
+ return;
+ nsCString nonDataPart(Substring(url, 5, endNonData - 5));
+ int32_t filenamePos = nonDataPart.Find("filename=");
+ if (filenamePos != -1)
+ {
+ filenamePos += 9;
+ int32_t endFilename = nonDataPart.FindChar(';', filenamePos);
+ if (endFilename == -1)
+ endFilename = endNonData;
+ attachment->m_realName = Substring(nonDataPart, filenamePos,
+ endFilename - filenamePos);
+ }
+ else
+ {
+ // no filename; need to construct one based on the content type.
+ nsCOMPtr<nsIMIMEService> mimeService(do_GetService(NS_MIMESERVICE_CONTRACTID));
+ if (!mimeService)
+ return;
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ nsCString mediaType(Substring(nonDataPart, 0, nonDataPart.FindChar(';')));
+ mimeService->GetFromTypeAndExtension(mediaType, EmptyCString(), getter_AddRefs(mimeInfo));
+ if (!mimeInfo)
+ return;
+ nsCString filename;
+ nsCString extension;
+ mimeInfo->GetPrimaryExtension(extension);
+ unsigned char filePrefixBytes[8];
+ GenerateGlobalRandomBytes(filePrefixBytes, 8);
+ // Create a filename prefix with 16 lowercase letters,
+ // representing 8 bytes.
+ for (int32_t i = 0; i < 8; i++)
+ {
+ // A pair of letters, each any of (a-p).
+ filename.Append((filePrefixBytes[i] & 0xF) + 'a');
+ filename.Append((filePrefixBytes[i] >> 4) + 'a');
+ }
+ filename.Append('.');
+ filename.Append(extension);
+ attachment->m_realName = filename;
+ }
+ }
+ else
+ {
+ /* Take the part of the file name after the last / or \ */
+ s2 = PL_strrchr (s, '/');
+ if (s2) s = s2+1;
+ s2 = PL_strrchr (s, '\\');
+
+ if (s2) s = s2+1;
+ /* Copy it into the attachment struct. */
+ attachment->m_realName = s;
+ int32_t charPos = attachment->m_realName.FindChar('?');
+ if (charPos != -1)
+ attachment->m_realName.SetLength(charPos);
+ /* Now trim off any named anchors or search data. */
+ charPos = attachment->m_realName.FindChar('#');
+ if (charPos != -1)
+ attachment->m_realName.SetLength(charPos);
+ }
+ /* Now lose the %XX crap. */
+ nsCString unescaped_real_name;
+ MsgUnescapeString(attachment->m_realName, 0, unescaped_real_name);
+ attachment->m_realName = unescaped_real_name;
+ }
+
+ /* Now a special case for attaching uuencoded files...
+
+ If we attach a file "foo.txt.uu", we will send it out with
+ Content-Type: text/plain; Content-Transfer-Encoding: x-uuencode.
+ When saving such a file, a mail reader will generally decode it first
+ (thus removing the uuencoding.) So, let's make life a little easier by
+ removing the indication of uuencoding from the file name itself. (This
+ will presumably make the file name in the Content-Disposition header be
+ the same as the file name in the "begin" line of the uuencoded data.)
+
+ However, since there are mailers out there (including earlier versions of
+ Mozilla) that will use "foo.txt.uu" as the file name, we still need to
+ cope with that; the code which copes with that is in the MIME parser, in
+ libmime/mimei.c.
+ */
+ if (attachment->m_already_encoded_p && !attachment->m_encoding.IsEmpty())
+ {
+ /* #### TOTAL KLUDGE.
+ I'd like to ask the mime.types file, "what extensions correspond
+ to obj->encoding (which happens to be "x-uuencode") but doing that
+ in a non-sphagetti way would require brain surgery. So, since
+ currently uuencode is the only content-transfer-encoding which we
+ understand which traditionally has an extension, we just special-
+ case it here!
+
+ Note that it's special-cased in a similar way in libmime/mimei.c.
+ */
+ if (attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE) ||
+ attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE2) ||
+ attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE3) ||
+ attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE4))
+ {
+ if (StringEndsWith(attachment->m_realName, NS_LITERAL_CSTRING(".uu")))
+ attachment->m_realName.Cut(attachment->m_realName.Length() - 3, 3);
+ else if (StringEndsWith(attachment->m_realName, NS_LITERAL_CSTRING(".uue")))
+ attachment->m_realName.Cut(attachment->m_realName.Length() - 4, 4);
+ }
+ }
+}
+
+// Utility to create a nsIURI object...
+nsresult
+nsMsgNewURL(nsIURI** aInstancePtrResult, const char * aSpec)
+{
+ nsresult rv = NS_OK;
+ if (nullptr == aInstancePtrResult)
+ return NS_ERROR_NULL_POINTER;
+ nsCOMPtr<nsIIOService> pNetService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(pNetService, NS_ERROR_UNEXPECTED);
+ if (PL_strstr(aSpec, "://") == nullptr && strncmp(aSpec, "data:", 5))
+ {
+ //XXXjag Temporary fix for bug 139362 until the real problem(bug 70083) get fixed
+ nsAutoCString uri(NS_LITERAL_CSTRING("http://"));
+ uri.Append(aSpec);
+ rv = pNetService->NewURI(uri, nullptr, nullptr, aInstancePtrResult);
+ }
+ else
+ rv = pNetService->NewURI(nsDependentCString(aSpec), nullptr, nullptr, aInstancePtrResult);
+ return rv;
+}
+
+bool
+nsMsgIsLocalFile(const char *url)
+{
+ /*
+ A url is considered as a local file if it's start with file://
+ But on Window, we need to filter UNC file url because there
+ are not really local file. Those start with file:////
+ */
+ if (PL_strncasecmp(url, "file://", 7) == 0)
+ {
+#ifdef XP_WIN
+ if (PL_strncasecmp(url, "file:////", 9) == 0)
+ return false;
+#endif
+ return true;
+ }
+ else
+ return false;
+}
+
+char
+*nsMsgGetLocalFileFromURL(const char *url)
+{
+ char * finalPath;
+ NS_ASSERTION(PL_strncasecmp(url, "file://", 7) == 0, "invalid url");
+ finalPath = (char*)PR_Malloc(strlen(url));
+ if (finalPath == NULL)
+ return NULL;
+ strcpy(finalPath, url+6+1);
+ return finalPath;
+}
+
+char *
+nsMsgParseURLHost(const char *url)
+{
+ nsIURI *workURI = nullptr;
+ nsresult rv;
+
+ rv = nsMsgNewURL(&workURI, url);
+ if (NS_FAILED(rv) || !workURI)
+ return nullptr;
+
+ nsAutoCString host;
+ rv = workURI->GetHost(host);
+ NS_IF_RELEASE(workURI);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ return ToNewCString(host);
+}
+
+char *
+GenerateFileNameFromURI(nsIURI *aURL)
+{
+ nsresult rv;
+ nsCString file;
+ nsCString spec;
+ char *returnString;
+ char *cp = nullptr;
+ char *cp1 = nullptr;
+
+ rv = aURL->GetPath(file);
+ if ( NS_SUCCEEDED(rv) && !file.IsEmpty())
+ {
+ char *newFile = ToNewCString(file);
+ if (!newFile)
+ return nullptr;
+
+ // strip '/'
+ cp = PL_strrchr(newFile, '/');
+ if (cp)
+ ++cp;
+ else
+ cp = newFile;
+
+ if (*cp)
+ {
+ if ((cp1 = PL_strchr(cp, '/'))) *cp1 = 0;
+ if ((cp1 = PL_strchr(cp, '?'))) *cp1 = 0;
+ if ((cp1 = PL_strchr(cp, '>'))) *cp1 = 0;
+ if (*cp != '\0')
+ {
+ returnString = PL_strdup(cp);
+ PR_FREEIF(newFile);
+ return returnString;
+ }
+ }
+ else
+ return nullptr;
+ }
+
+ cp = nullptr;
+ cp1 = nullptr;
+
+
+ rv = aURL->GetSpec(spec);
+ if ( NS_SUCCEEDED(rv) && !spec.IsEmpty())
+ {
+ char *newSpec = ToNewCString(spec);
+ if (!newSpec)
+ return nullptr;
+
+ char *cp2 = NULL, *cp3=NULL ;
+
+ // strip '"'
+ cp2 = newSpec;
+ while (*cp2 == '"')
+ cp2++;
+ if ((cp3 = PL_strchr(cp2, '"')))
+ *cp3 = 0;
+
+ char *hostStr = nsMsgParseURLHost(cp2);
+ if (!hostStr)
+ hostStr = PL_strdup(cp2);
+
+ bool isHTTP = false;
+ if (NS_SUCCEEDED(aURL->SchemeIs("http", &isHTTP)) && isHTTP)
+ {
+ returnString = PR_smprintf("%s.html", hostStr);
+ PR_FREEIF(hostStr);
+ }
+ else
+ returnString = hostStr;
+
+ PR_FREEIF(newSpec);
+ return returnString;
+ }
+
+ return nullptr;
+}
+
+//
+// This routine will generate a content id for use in a mail part.
+// It will take the part number passed in as well as the email
+// address. If the email address is null or invalid, we will simply
+// use netscape.com for the interesting part. The content ID's will
+// look like the following:
+//
+// Content-ID: <part1.36DF1DCE.73B5A330@netscape.com>
+//
+char *
+mime_gen_content_id(uint32_t aPartNum, const char *aEmailAddress)
+{
+ int32_t randLen = 5;
+ unsigned char rand_buf1[5];
+ unsigned char rand_buf2[5];
+ const char *domain = nullptr;
+ const char *defaultDomain = "@netscape.com";
+
+ memset(rand_buf1, 0, randLen-1);
+ memset(rand_buf2, 0, randLen-1);
+
+ GenerateGlobalRandomBytes(rand_buf1, randLen);
+ GenerateGlobalRandomBytes(rand_buf2, randLen);
+
+ // Find the @domain.com string...
+ if (aEmailAddress && *aEmailAddress)
+ domain = const_cast<const char*>(PL_strchr(aEmailAddress, '@'));
+
+ if (!domain)
+ domain = defaultDomain;
+
+ char *retVal = PR_smprintf("part%d."
+ "%02X%02X%02X%02X"
+ "."
+ "%02X%02X%02X%02X"
+ "%s",
+ aPartNum,
+ rand_buf1[0], rand_buf1[1], rand_buf1[2], rand_buf1[3],
+ rand_buf2[0], rand_buf2[1], rand_buf2[2], rand_buf2[3],
+ domain);
+
+ return retVal;
+}
+
+void
+GetFolderURIFromUserPrefs(nsMsgDeliverMode aMode, nsIMsgIdentity* identity, nsCString& uri)
+{
+ nsresult rv;
+ uri.Truncate();
+
+ // QueueForLater (Outbox)
+ if (aMode == nsIMsgSend::nsMsgQueueForLater ||
+ aMode == nsIMsgSend::nsMsgDeliverBackground)
+ {
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+ rv = prefs->GetCharPref("mail.default_sendlater_uri", getter_Copies(uri));
+ if (NS_FAILED(rv) || uri.IsEmpty())
+ uri.AssignLiteral(ANY_SERVER);
+ else
+ {
+ // check if uri is unescaped, and if so, escape it and reset the pef.
+ if (uri.FindChar(' ') != kNotFound)
+ {
+ MsgReplaceSubstring(uri, " ", "%20");
+ prefs->SetCharPref("mail.default_sendlater_uri", uri.get());
+ }
+ }
+ return;
+ }
+
+ if (!identity)
+ return;
+
+ if (aMode == nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts)
+ rv = identity->GetDraftFolder(uri);
+ else if (aMode == nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates)
+ rv = identity->GetStationeryFolder(uri);
+ else
+ {
+ bool doFcc = false;
+ rv = identity->GetDoFcc(&doFcc);
+ if (doFcc)
+ rv = identity->GetFccFolder(uri);
+ }
+ return;
+}
+
+/**
+ * Check if we should use format=flowed (RFC 2646) for a mail.
+ * We will use format=flowed unless the preference tells us not to do so.
+ * In this function we set all the serialiser flags.
+ * 'formatted' is always 'true'.
+ */
+void GetSerialiserFlags(const char* charset, bool* flowed, bool* delsp, bool* formatted, bool* disallowBreaks)
+{
+ *flowed = false;
+ *delsp = false;
+ *formatted = true;
+ *disallowBreaks = true;
+
+ // Set format=flowed as in RFC 2646 according to the preference.
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ prefs->GetBoolPref("mailnews.send_plaintext_flowed", flowed);
+ }
+
+ // We could test statefulCharset(charset) here, but since ISO-2022-JP is the
+ // only one left we support, we might as well check for it directly.
+ if (PL_strcasecmp(charset, "ISO-2022-JP") == 0) {
+ // Make sure we honour RFC 1468. For encoding in ISO-2022-JP we need to
+ // send short lines to allow 7bit transfer encoding.
+ *disallowBreaks = false;
+ if (*flowed)
+ *delsp = true;
+ }
+}
diff --git a/mailnews/compose/src/nsMsgCompUtils.h b/mailnews/compose/src/nsMsgCompUtils.h
new file mode 100644
index 000000000..13d6ddd83
--- /dev/null
+++ b/mailnews/compose/src/nsMsgCompUtils.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgCompUtils_H_
+#define _nsMsgCompUtils_H_
+
+#include "nscore.h"
+#include "nsMsgSend.h"
+#include "nsMsgCompFields.h"
+#include "nsIMsgSend.h"
+#include "nsIMsgCompUtils.h"
+
+class nsIPrompt;
+
+#define ANY_SERVER "anyfolder://"
+
+// these are msg hdr property names for storing the original
+// msg uri's and disposition(replied/forwarded) when queuing
+// messages to send later.
+#define ORIG_URI_PROPERTY "origURIs"
+#define QUEUED_DISPOSITION_PROPERTY "queuedDisposition"
+
+class nsMsgCompUtils : public nsIMsgCompUtils
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOMPUTILS
+
+ nsMsgCompUtils();
+
+private:
+ virtual ~nsMsgCompUtils();
+};
+
+PR_BEGIN_EXTERN_C
+
+//
+// Create a file spec or file name using the name passed
+// in as a template
+//
+nsresult nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile);
+char *nsMsgCreateTempFileName(const char *tFileName);
+
+
+//
+// Various utilities for building parts of MIME encoded
+// messages during message composition
+//
+
+nsresult mime_sanity_check_fields_recipients (
+ const char *to,
+ const char *cc,
+ const char *bcc,
+ const char *newsgroups);
+
+nsresult mime_sanity_check_fields (
+ const char *from,
+ const char *reply_to,
+ const char *to,
+ const char *cc,
+ const char *bcc,
+ const char *fcc,
+ const char *newsgroups,
+ const char *followup_to,
+ const char * /*subject*/,
+ const char * /*references*/,
+ const char * /*organization*/,
+ const char * /*other_random_headers*/);
+
+nsresult mime_generate_headers(nsIMsgCompFields *fields,
+ nsMsgDeliverMode deliver_mode,
+ msgIWritableStructuredHeaders *headers);
+
+char *mime_make_separator(const char *prefix);
+char *mime_gen_content_id(uint32_t aPartNum, const char *aEmailAddress);
+
+char *mime_generate_attachment_headers (
+ const char *type,
+ const char *type_param,
+ const char *encoding,
+ const char *description,
+ const char *x_mac_type,
+ const char *x_mac_creator,
+ const char *real_name,
+ const char *base_url,
+ bool digest_p,
+ nsMsgAttachmentHandler *ma,
+ const char *attachmentCharset, // charset of the attachment (can be null)
+ const char *bodyCharset, // charset of the main body
+ bool bodyIsAsciiOnly,
+ const char *content_id,
+ bool aBodyDocument);
+
+char *msg_generate_message_id (nsIMsgIdentity*);
+
+bool mime_7bit_data_p (const char *string, uint32_t size);
+
+char *mime_fix_header_1 (const char *string, bool addr_p, bool news_p);
+char *mime_fix_header (const char *string);
+char *mime_fix_addr_header (const char *string);
+char *mime_fix_news_header (const char *string);
+
+bool mime_type_requires_b64_p (const char *type);
+bool mime_type_needs_charset (const char *type);
+
+char *msg_make_filename_qtext(const char *srcText, bool stripCRLFs);
+
+// Rip apart the URL and extract a reasonable value for the `real_name' slot.
+void msg_pick_real_name (nsMsgAttachmentHandler *attachment, const char16_t *proposedName, const char *charset);
+
+//
+// Informational calls...
+//
+void nsMsgMIMESetConformToStandard (bool conform_p);
+bool nsMsgMIMEGetConformToStandard (void);
+
+//
+// network service type calls...
+//
+nsresult nsMsgNewURL(nsIURI** aInstancePtrResult, const char * aSpec);
+bool nsMsgIsLocalFile(const char *url);
+char *nsMsgGetLocalFileFromURL(const char *url);
+
+char *nsMsgParseURLHost(const char *url);
+
+char *GenerateFileNameFromURI(nsIURI *aURL);
+
+//
+// Folder calls...
+//
+void GetFolderURIFromUserPrefs(nsMsgDeliverMode aMode, nsIMsgIdentity *identity, nsCString& uri);
+
+// Check if we should use format=flowed
+void GetSerialiserFlags(const char *charset, bool *flowed, bool *delsp, bool *formatted, bool *disallowBreaks);
+
+
+PR_END_EXTERN_C
+
+
+#endif /* _nsMsgCompUtils_H_ */
+
diff --git a/mailnews/compose/src/nsMsgCompose.cpp b/mailnews/compose/src/nsMsgCompose.cpp
new file mode 100644
index 000000000..a7c2140ad
--- /dev/null
+++ b/mailnews/compose/src/nsMsgCompose.cpp
@@ -0,0 +1,6057 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgCompose.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMText.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIDOMHTMLLinkElement.h"
+#include "nsIDOMHTMLAnchorElement.h"
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsISelectionController.h"
+#include "nsMsgI18N.h"
+#include "nsMsgCompCID.h"
+#include "nsMsgQuote.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIDocumentEncoder.h" // for editor output flags
+#include "nsMsgCompUtils.h"
+#include "nsComposeStrings.h"
+#include "nsIMsgSend.h"
+#include "nsMailHeaders.h"
+#include "nsMsgPrompts.h"
+#include "nsMimeTypes.h"
+#include "nsICharsetConverterManager.h"
+#include "nsTextFormatter.h"
+#include "nsIPlaintextEditor.h"
+#include "nsIHTMLEditor.h"
+#include "nsIEditorMailSupport.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "nsIDocShell.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIMIMEService.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIWindowMediator.h"
+#include "nsIURL.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgMimeCID.h"
+#include "nsDateTimeFormatCID.h"
+#include "nsIDateTimeFormat.h"
+#include "nsILocaleService.h"
+#include "nsILocale.h"
+#include "nsIMsgComposeService.h"
+#include "nsIMsgComposeProgressParams.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsImapCore.h"
+#include "nsUnicharUtils.h"
+#include "nsNetUtil.h"
+#include "nsIContentViewer.h"
+#include "nsIMsgMdnGenerator.h"
+#include "plbase64.h"
+#include "nsUConvCID.h"
+#include "nsIUnicodeNormalizer.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgAttachment.h"
+#include "nsIMsgProgress.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgDatabase.h"
+#include "nsStringStream.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsIMsgWindow.h"
+#include "nsITextToSubURI.h"
+#include "nsIAbManager.h"
+#include "nsCRT.h"
+#include "mozilla/Services.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "mozilla/Preferences.h"
+#include "nsStreamConverter.h"
+#include "nsISelection.h"
+#include "nsJSEnvironment.h"
+#include "nsIObserverService.h"
+#include "nsIProtocolHandler.h"
+#include "nsContentUtils.h"
+#include "nsIFileURL.h"
+
+using namespace mozilla;
+using namespace mozilla::mailnews;
+
+static nsresult GetReplyHeaderInfo(int32_t* reply_header_type,
+ nsString& reply_header_locale,
+ nsString& reply_header_authorwrote,
+ nsString& reply_header_ondateauthorwrote,
+ nsString& reply_header_authorwroteondate,
+ nsString& reply_header_originalmessage)
+{
+ nsresult rv;
+ *reply_header_type = 0;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If fetching any of the preferences fails,
+ // we return early with header_type = 0 meaning "no header".
+ rv = NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.reply_header_locale", EmptyString(), reply_header_locale);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetLocalizedUnicharPreference(prefBranch, "mailnews.reply_header_authorwrotesingle",
+ reply_header_authorwrote);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetLocalizedUnicharPreference(prefBranch, "mailnews.reply_header_ondateauthorwrote",
+ reply_header_ondateauthorwrote);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetLocalizedUnicharPreference(prefBranch, "mailnews.reply_header_authorwroteondate",
+ reply_header_authorwroteondate);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetLocalizedUnicharPreference(prefBranch, "mailnews.reply_header_originalmessage",
+ reply_header_originalmessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return prefBranch->GetIntPref("mailnews.reply_header_type", reply_header_type);
+}
+
+static void TranslateLineEnding(nsString& data)
+{
+ char16_t* rPtr; //Read pointer
+ char16_t* wPtr; //Write pointer
+ char16_t* sPtr; //Start data pointer
+ char16_t* ePtr; //End data pointer
+
+ rPtr = wPtr = sPtr = data.BeginWriting();
+ ePtr = rPtr + data.Length();
+
+ while (rPtr < ePtr)
+ {
+ if (*rPtr == nsCRT::CR) {
+ *wPtr = nsCRT::LF;
+ if (rPtr + 1 < ePtr && *(rPtr + 1) == nsCRT::LF)
+ rPtr ++;
+ }
+ else
+ *wPtr = *rPtr;
+
+ rPtr ++;
+ wPtr ++;
+ }
+
+ data.SetLength(wPtr - sPtr);
+}
+
+static void GetTopmostMsgWindowCharacterSet(nsCString& charset, bool* charsetOverride)
+{
+ // HACK: if we are replying to a message and that message used a charset over ride
+ // (as specified in the top most window (assuming the reply originated from that window)
+ // then use that over ride charset instead of the charset specified in the message
+ nsCOMPtr <nsIMsgMailSession> mailSession (do_GetService(NS_MSGMAILSESSION_CONTRACTID));
+ if (mailSession)
+ {
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ msgWindow->GetMailCharacterSet(charset);
+ msgWindow->GetCharsetOverride(charsetOverride);
+ }
+ }
+}
+
+nsMsgCompose::nsMsgCompose()
+{
+
+ mQuotingToFollow = false;
+ mInsertingQuotedContent = false;
+ mWhatHolder = 1;
+ m_window = nullptr;
+ m_editor = nullptr;
+ mQuoteStreamListener=nullptr;
+ mCharsetOverride = false;
+ mAnswerDefaultCharset = false;
+ mDeleteDraft = false;
+ m_compFields = nullptr; //m_compFields will be set during nsMsgCompose::Initialize
+ mType = nsIMsgCompType::New;
+
+ // For TagConvertible
+ // Read and cache pref
+ mConvertStructs = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ prefBranch->GetBoolPref("converter.html2txt.structs", &mConvertStructs);
+
+ m_composeHTML = false;
+}
+
+
+nsMsgCompose::~nsMsgCompose()
+{
+ NS_IF_RELEASE(m_compFields);
+ NS_IF_RELEASE(mQuoteStreamListener);
+}
+
+/* the following macro actually implement addref, release and query interface for our component. */
+NS_IMPL_ISUPPORTS(nsMsgCompose, nsIMsgCompose, nsIMsgSendListener,
+ nsISupportsWeakReference)
+
+//
+// Once we are here, convert the data which we know to be UTF-8 to UTF-16
+// for insertion into the editor
+//
+nsresult
+GetChildOffset(nsIDOMNode *aChild, nsIDOMNode *aParent, int32_t &aOffset)
+{
+ NS_ASSERTION((aChild && aParent), "bad args");
+ nsresult result = NS_ERROR_NULL_POINTER;
+ if (aChild && aParent)
+ {
+ nsCOMPtr<nsIDOMNodeList> childNodes;
+ result = aParent->GetChildNodes(getter_AddRefs(childNodes));
+ if ((NS_SUCCEEDED(result)) && (childNodes))
+ {
+ int32_t i=0;
+ for ( ; NS_SUCCEEDED(result); i++)
+ {
+ nsCOMPtr<nsIDOMNode> childNode;
+ result = childNodes->Item(i, getter_AddRefs(childNode));
+ if ((NS_SUCCEEDED(result)) && (childNode))
+ {
+ if (childNode.get()==aChild)
+ {
+ aOffset = i;
+ break;
+ }
+ }
+ else if (!childNode)
+ result = NS_ERROR_NULL_POINTER;
+ }
+ }
+ else if (!childNodes)
+ result = NS_ERROR_NULL_POINTER;
+ }
+ return result;
+}
+
+nsresult
+GetNodeLocation(nsIDOMNode *inChild, nsCOMPtr<nsIDOMNode> *outParent, int32_t *outOffset)
+{
+ NS_ASSERTION((outParent && outOffset), "bad args");
+ nsresult result = NS_ERROR_NULL_POINTER;
+ if (inChild && outParent && outOffset)
+ {
+ result = inChild->GetParentNode(getter_AddRefs(*outParent));
+ if ( (NS_SUCCEEDED(result)) && (*outParent) )
+ {
+ result = GetChildOffset(inChild, *outParent, *outOffset);
+ }
+ }
+
+ return result;
+}
+
+bool nsMsgCompose::IsEmbeddedObjectSafe(const char * originalScheme,
+ const char * originalHost,
+ const char * originalPath,
+ nsIDOMNode * object)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIDOMHTMLImageElement> image;
+ nsCOMPtr<nsIDOMHTMLLinkElement> link;
+ nsCOMPtr<nsIDOMHTMLAnchorElement> anchor;
+ nsAutoString objURL;
+
+ if (!object || !originalScheme || !originalPath) //having a null host is ok...
+ return false;
+
+ if ((image = do_QueryInterface(object)))
+ {
+ if (NS_FAILED(image->GetSrc(objURL)))
+ return false;
+ }
+ else if ((link = do_QueryInterface(object)))
+ {
+ if (NS_FAILED(link->GetHref(objURL)))
+ return false;
+ }
+ else if ((anchor = do_QueryInterface(object)))
+ {
+ if (NS_FAILED(anchor->GetHref(objURL)))
+ return false;
+ }
+ else
+ return false;
+
+ if (!objURL.IsEmpty())
+ {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), objURL);
+ if (NS_SUCCEEDED(rv) && uri)
+ {
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ if (NS_SUCCEEDED(rv) && scheme.Equals(originalScheme, nsCaseInsensitiveCStringComparator()))
+ {
+ nsAutoCString host;
+ rv = uri->GetAsciiHost(host);
+ // mailbox url don't have a host therefore don't be too strict.
+ if (NS_SUCCEEDED(rv) && (host.IsEmpty() || originalHost || host.Equals(originalHost, nsCaseInsensitiveCStringComparator())))
+ {
+ nsAutoCString path;
+ rv = uri->GetPath(path);
+ if (NS_SUCCEEDED(rv))
+ {
+ const char * query = strrchr(path.get(), '?');
+ if (query && PL_strncasecmp(path.get(), originalPath, query - path.get()) == 0)
+ return true; //This object is a part of the original message, we can send it safely.
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/* Reset the uri's of embedded objects because we've saved the draft message, and the
+ original message doesn't exist anymore.
+ */
+nsresult nsMsgCompose::ResetUrisForEmbeddedObjects()
+{
+ nsCOMPtr<nsIArray> aNodeList;
+ uint32_t numNodes;
+ uint32_t i;
+
+ nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(m_editor));
+ if (!mailEditor)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = mailEditor->GetEmbeddedObjects(getter_AddRefs(aNodeList));
+ if (NS_FAILED(rv) || !aNodeList)
+ return NS_ERROR_FAILURE;
+
+ if (NS_FAILED(aNodeList->GetLength(&numNodes)))
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDOMNode> node;
+ nsCString curDraftIdURL;
+
+ rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL));
+
+ // Skip if no draft id (probably a new draft msg).
+ if (NS_SUCCEEDED(rv) && mMsgSend && !curDraftIdURL.IsEmpty())
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr;
+ rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(msgDBHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg header DB interface pointer.");
+ if (NS_SUCCEEDED(rv) && msgDBHdr)
+ {
+ // build up the old and new ?number= parts. This code assumes it is
+ // called *before* RemoveCurrentDraftMessage, so that curDraftIdURL
+ // is the previous draft.
+ // This code works for both imap and local messages.
+ nsMsgKey newMsgKey;
+ nsCString folderUri;
+ nsCString baseMsgUri;
+ mMsgSend->GetMessageKey(&newMsgKey);
+ mMsgSend->GetFolderUri(folderUri);
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetExistingFolder(folderUri, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ folder->GetBaseMessageURI(baseMsgUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDOMElement> domElement;
+ for (i = 0; i < numNodes; i ++)
+ {
+ domElement = do_QueryElementAt(aNodeList, i);
+ if (!domElement)
+ continue;
+
+ nsCOMPtr<nsIDOMHTMLImageElement> image = do_QueryInterface(domElement);
+ if (!image)
+ continue;
+ nsCString partNum;
+ mMsgSend->GetPartForDomIndex(i, partNum);
+ // do we care about anything besides images?
+ nsAutoString objURL;
+ image->GetSrc(objURL);
+
+ // First we need to make sure that the URL is associated with a message
+ // protocol so we don't accidentally manipulate a URL like:
+ // http://www.site.com/retrieve.html?C=image.jpg.
+ nsCOMPtr<nsIIOService> ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString scheme;
+ ioService->ExtractScheme(NS_ConvertUTF16toUTF8(objURL), scheme);
+
+ // Detect message protocols where attachments can occur.
+ nsCOMPtr<nsIProtocolHandler> handler;
+ ioService->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (!handler)
+ continue;
+ nsCOMPtr<nsIMsgMessageFetchPartService> mailHandler = do_QueryInterface(handler);
+ if (!mailHandler)
+ continue;
+
+ // the objURL is the full path to the embedded content. We need
+ // to update it with uri for the folder we just saved to, and the new
+ // msg key.
+ int32_t restOfUrlIndex = objURL.Find("?number=");
+ if (restOfUrlIndex == kNotFound)
+ restOfUrlIndex = objURL.FindChar('?');
+ else
+ restOfUrlIndex = objURL.FindChar('&', restOfUrlIndex);
+
+ if (restOfUrlIndex == kNotFound)
+ continue;
+
+ nsCString newURI(baseMsgUri);
+ newURI.Append('#');
+ newURI.AppendInt(newMsgKey);
+ nsString restOfUrl(Substring(objURL, restOfUrlIndex, objURL.Length() - restOfUrlIndex));
+ int32_t partIndex = restOfUrl.Find("part=");
+ if (partIndex != kNotFound)
+ {
+ partIndex += 5;
+ int32_t endPart = restOfUrl.FindChar('&', partIndex);
+ int32_t existingPartLen = (endPart == kNotFound) ? -1 : endPart - partIndex;
+ restOfUrl.Replace(partIndex, existingPartLen, NS_ConvertASCIItoUTF16(partNum));
+ }
+
+ nsCOMPtr<nsIMsgMessageService> msgService;
+ rv = GetMessageServiceFromURI(newURI, getter_AddRefs(msgService));
+ if (NS_FAILED(rv))
+ continue;
+ nsCOMPtr<nsIURI> newUrl;
+ rv = msgService->GetUrlForUri(newURI.get(), getter_AddRefs(newUrl), nullptr);
+ if (!newUrl)
+ continue;
+ nsCString spec;
+ rv = newUrl->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString newSrc;
+ // mailbox urls will have ?number=xxx; imap urls won't. We need to
+ // handle both cases because we may be going from a mailbox url to
+ // and imap url, or vice versa, depending on the original folder,
+ // and the destination drafts folder.
+ bool specHasQ = (spec.FindChar('?') != kNotFound);
+ if (specHasQ && restOfUrl.CharAt(0) == '?')
+ restOfUrl.SetCharAt('&', 0);
+ else if (!specHasQ && restOfUrl.CharAt(0) == '&')
+ restOfUrl.SetCharAt('?', 0);
+ AppendUTF8toUTF16(spec, newSrc);
+ newSrc.Append(restOfUrl);
+ image->SetSrc(newSrc);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+/* The purpose of this function is to mark any embedded object that wasn't a RFC822 part
+ of the original message as moz-do-not-send.
+ That will prevent us to attach data not specified by the user or not present in the
+ original message.
+*/
+nsresult nsMsgCompose::TagEmbeddedObjects(nsIEditorMailSupport *aEditor)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIArray> aNodeList;
+ uint32_t count;
+ uint32_t i;
+
+ if (!aEditor)
+ return NS_ERROR_FAILURE;
+
+ rv = aEditor->GetEmbeddedObjects(getter_AddRefs(aNodeList));
+ if (NS_FAILED(rv) || !aNodeList)
+ return NS_ERROR_FAILURE;
+
+ if (NS_FAILED(aNodeList->GetLength(&count)))
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIURI> originalUrl;
+ nsCString originalScheme;
+ nsCString originalHost;
+ nsCString originalPath;
+
+ // first, convert the rdf original msg uri into a url that represents the message...
+ nsCOMPtr <nsIMsgMessageService> msgService;
+ rv = GetMessageServiceFromURI(mOriginalMsgURI, getter_AddRefs(msgService));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = msgService->GetUrlForUri(mOriginalMsgURI.get(), getter_AddRefs(originalUrl), nullptr);
+ if (NS_SUCCEEDED(rv) && originalUrl)
+ {
+ originalUrl->GetScheme(originalScheme);
+ originalUrl->GetAsciiHost(originalHost);
+ originalUrl->GetPath(originalPath);
+ }
+ }
+
+ // Then compare the url of each embedded objects with the original message.
+ // If they a not coming from the original message, they should not be sent
+ // with the message.
+ for (i = 0; i < count; i ++)
+ {
+ nsCOMPtr<nsIDOMNode> node = do_QueryElementAt(aNodeList, i);
+ if (!node)
+ continue;
+ if (IsEmbeddedObjectSafe(originalScheme.get(), originalHost.get(),
+ originalPath.get(), node))
+ continue; //Don't need to tag this object, it safe to send it.
+
+ //The source of this object should not be sent with the message
+ nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(node);
+ if (domElement)
+ domElement->SetAttribute(NS_LITERAL_STRING("moz-do-not-send"), NS_LITERAL_STRING("true"));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::GetInsertingQuotedContent(bool * aInsertingQuotedText)
+{
+ NS_ENSURE_ARG_POINTER(aInsertingQuotedText);
+ *aInsertingQuotedText = mInsertingQuotedContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::SetInsertingQuotedContent(bool aInsertingQuotedText)
+{
+ mInsertingQuotedContent = aInsertingQuotedText;
+ return NS_OK;
+}
+
+void
+nsMsgCompose::InsertDivWrappedTextAtSelection(const nsAString &aText,
+ const nsAString &classStr)
+{
+ NS_ASSERTION(m_editor, "InsertDivWrappedTextAtSelection called, but no editor exists\n");
+ if (!m_editor)
+ return;
+
+ nsCOMPtr<nsIDOMElement> divElem;
+ nsCOMPtr<nsIHTMLEditor> htmlEditor(do_QueryInterface(m_editor));
+
+ nsresult rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("div"),
+ getter_AddRefs(divElem));
+
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIDOMNode> divNode (do_QueryInterface(divElem));
+
+ // We need the document
+ nsCOMPtr<nsIDOMDocument> doc;
+ rv = m_editor->GetDocument(getter_AddRefs(doc));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Break up the text by newlines, and then insert text nodes followed
+ // by <br> nodes.
+ int32_t start = 0;
+ int32_t end = aText.Length();
+
+ for (;;)
+ {
+ int32_t delimiter = aText.FindChar('\n', start);
+ if (delimiter == kNotFound)
+ delimiter = end;
+
+ nsCOMPtr<nsIDOMText> textNode;
+ rv = doc->CreateTextNode(Substring(aText, start, delimiter - start), getter_AddRefs(textNode));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIDOMNode> newTextNode = do_QueryInterface(textNode);
+ nsCOMPtr<nsIDOMNode> resultNode;
+ rv = divElem->AppendChild(newTextNode, getter_AddRefs(resultNode));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Now create and insert a BR
+ nsCOMPtr<nsIDOMElement> brElem;
+ rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("br"),
+ getter_AddRefs(brElem));
+ rv = divElem->AppendChild(brElem, getter_AddRefs(resultNode));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (delimiter == end)
+ break;
+ start = ++delimiter;
+ if (start == end)
+ break;
+ }
+
+ htmlEditor->InsertElementAtSelection(divElem, true);
+ nsCOMPtr<nsIDOMNode> parent;
+ int32_t offset;
+
+ rv = GetNodeLocation(divNode, address_of(parent), &offset);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsISelection> selection;
+ m_editor->GetSelection(getter_AddRefs(selection));
+
+ if (selection)
+ selection->Collapse(parent, offset + 1);
+ }
+ if (divElem)
+ divElem->SetAttribute(NS_LITERAL_STRING("class"), classStr);
+}
+
+/*
+ * The following function replaces <plaintext> tags with <x-plaintext>.
+ * <plaintext> is a funny beast: It leads to everything following it
+ * being displayed verbatim, even a </plaintext> tag is ignored.
+ */
+static void
+remove_plaintext_tag(nsString &body)
+{
+ // Replace all <plaintext> and </plaintext> tags.
+ int32_t index = 0;
+ bool replaced = false;
+ while ((index = body.Find("<plaintext", /* ignoreCase = */ true, index)) != kNotFound) {
+ body.Insert(u"x-", index+1);
+ index += 12;
+ replaced = true;
+ }
+ if (replaced) {
+ index = 0;
+ while ((index = body.Find("</plaintext", /* ignoreCase = */ true, index)) != kNotFound) {
+ body.Insert(u"x-", index+2);
+ index += 13;
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsMsgCompose::ConvertAndLoadComposeWindow(nsString& aPrefix,
+ nsString& aBuf,
+ nsString& aSignature,
+ bool aQuoted,
+ bool aHTMLEditor)
+{
+ NS_ASSERTION(m_editor, "ConvertAndLoadComposeWindow but no editor\n");
+ NS_ENSURE_TRUE(m_editor && m_identity, NS_ERROR_NOT_INITIALIZED);
+
+ // First, get the nsIEditor interface for future use
+ nsCOMPtr<nsIDOMNode> nodeInserted;
+
+ TranslateLineEnding(aPrefix);
+ TranslateLineEnding(aBuf);
+ TranslateLineEnding(aSignature);
+
+ m_editor->EnableUndo(false);
+
+ // Ok - now we need to figure out the charset of the aBuf we are going to send
+ // into the editor shell. There are I18N calls to sniff the data and then we need
+ // to call the new routine in the editor that will allow us to send in the charset
+ //
+
+ // Now, insert it into the editor...
+ nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor));
+ nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(m_editor));
+ nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(m_editor));
+ int32_t reply_on_top = 0;
+ bool sig_bottom = true;
+ m_identity->GetReplyOnTop(&reply_on_top);
+ m_identity->GetSigBottom(&sig_bottom);
+ bool sigOnTop = (reply_on_top == 1 && !sig_bottom);
+ bool isForwarded = (mType == nsIMsgCompType::ForwardInline);
+
+ if (aQuoted)
+ {
+ mInsertingQuotedContent = true;
+ if (!aPrefix.IsEmpty())
+ {
+ if (!aHTMLEditor)
+ aPrefix.AppendLiteral("\n");
+
+ int32_t reply_on_top = 0;
+ m_identity->GetReplyOnTop(&reply_on_top);
+ if (reply_on_top == 1)
+ {
+ // HTML editor eats one line break
+ if (aHTMLEditor)
+ textEditor->InsertLineBreak();
+
+ // add one newline if a signature comes before the quote, two otherwise
+ bool includeSignature = true;
+ bool sig_bottom = true;
+ bool attachFile = false;
+ nsString prefSigText;
+
+ m_identity->GetSigOnReply(&includeSignature);
+ m_identity->GetSigBottom(&sig_bottom);
+ m_identity->GetHtmlSigText(prefSigText);
+ nsresult rv = m_identity->GetAttachSignature(&attachFile);
+ if (includeSignature && !sig_bottom &&
+ ((NS_SUCCEEDED(rv) && attachFile) || !prefSigText.IsEmpty()))
+ textEditor->InsertLineBreak();
+ else {
+ textEditor->InsertLineBreak();
+ textEditor->InsertLineBreak();
+ }
+ }
+
+ InsertDivWrappedTextAtSelection(aPrefix,
+ NS_LITERAL_STRING("moz-cite-prefix"));
+ }
+
+ if (!aBuf.IsEmpty() && mailEditor)
+ {
+ // This leaves the caret at the right place to insert a bottom signature.
+ if (aHTMLEditor) {
+ nsAutoString body(aBuf);
+ remove_plaintext_tag(body);
+ mailEditor->InsertAsCitedQuotation(body,
+ mCiteReference,
+ true,
+ getter_AddRefs(nodeInserted));
+ } else {
+ mailEditor->InsertAsQuotation(aBuf,
+ getter_AddRefs(nodeInserted));
+ }
+ }
+
+ mInsertingQuotedContent = false;
+
+ (void)TagEmbeddedObjects(mailEditor);
+
+ if (!aSignature.IsEmpty())
+ {
+ //we cannot add it on top earlier, because TagEmbeddedObjects will mark all images in the signature as "moz-do-not-send"
+ if( sigOnTop )
+ m_editor->BeginningOfDocument();
+
+ if (aHTMLEditor && htmlEditor)
+ htmlEditor->InsertHTML(aSignature);
+ else if (htmlEditor)
+ {
+ textEditor->InsertLineBreak();
+ InsertDivWrappedTextAtSelection(aSignature,
+ NS_LITERAL_STRING("moz-signature"));
+ }
+
+ if( sigOnTop )
+ m_editor->EndOfDocument();
+ }
+ }
+ else
+ {
+ if (aHTMLEditor && htmlEditor)
+ {
+ mInsertingQuotedContent = true;
+ if (isForwarded && Substring(aBuf, 0, sizeof(MIME_FORWARD_HTML_PREFIX)-1)
+ .EqualsLiteral(MIME_FORWARD_HTML_PREFIX)) {
+ // We assign the opening tag inside "<HTML><BODY><BR><BR>" before the
+ // two <br> elements.
+ // This is a bit hacky but we know that the MIME code prepares the
+ // forwarded content like this:
+ // <HTML><BODY><BR><BR> + forwarded header + header table.
+ // Note: We only do this when we prepare the message to be forwarded,
+ // a re-opened saved draft of a forwarded message does not repeat this.
+ nsString newBody(aBuf);
+ nsString divTag;
+ divTag.AssignLiteral("<div class=\"moz-forward-container\">");
+ newBody.Insert(divTag, sizeof(MIME_FORWARD_HTML_PREFIX)-1-8);
+ remove_plaintext_tag(newBody);
+ htmlEditor->RebuildDocumentFromSource(newBody);
+ } else {
+ htmlEditor->RebuildDocumentFromSource(aBuf);
+ }
+ mInsertingQuotedContent = false;
+
+ // when forwarding a message as inline, tag any embedded objects
+ // which refer to local images or files so we know not to include
+ // send them
+ if (isForwarded)
+ (void)TagEmbeddedObjects(mailEditor);
+
+ if (!aSignature.IsEmpty())
+ {
+ if (isForwarded && sigOnTop) {
+ // Use our own function, nsEditor::BeginningOfDocument() would position
+ // into the <div class="moz-forward-container"> we've just created.
+ MoveToBeginningOfDocument();
+ } else {
+ // Use our own function, nsEditor::EndOfDocument() would position
+ // into the <div class="moz-forward-container"> we've just created.
+ MoveToEndOfDocument();
+ }
+ htmlEditor->InsertHTML(aSignature);
+ if (isForwarded && sigOnTop)
+ m_editor->EndOfDocument();
+ }
+ else
+ m_editor->EndOfDocument();
+ }
+ else if (htmlEditor)
+ {
+ bool sigOnTopInserted = false;
+ if (isForwarded && sigOnTop && !aSignature.IsEmpty())
+ {
+ textEditor->InsertLineBreak();
+ InsertDivWrappedTextAtSelection(aSignature,
+ NS_LITERAL_STRING("moz-signature"));
+ m_editor->EndOfDocument();
+ sigOnTopInserted = true;
+ }
+
+ if (!aBuf.IsEmpty())
+ {
+ nsresult rv;
+ nsCOMPtr<nsIDOMElement> divElem;
+ nsCOMPtr<nsIDOMNode> extraBr;
+
+ if (isForwarded) {
+ // Special treatment for forwarded messages: Part 1.
+ // Create a <div> of the required class.
+ rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("div"),
+ getter_AddRefs(divElem));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString attributeName;
+ nsAutoString attributeValue;
+ attributeName.AssignLiteral("class");
+ attributeValue.AssignLiteral("moz-forward-container");
+ divElem->SetAttribute(attributeName, attributeValue);
+
+ // We can't insert an empty <div>, so fill it with something.
+ nsCOMPtr<nsIDOMElement> brElem;
+ rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("br"),
+ getter_AddRefs(brElem));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = divElem->AppendChild(brElem, getter_AddRefs(extraBr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Insert the non-empty <div> into the DOM.
+ rv = htmlEditor->InsertElementAtSelection(divElem, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Position into the div, so out content goes there.
+ nsCOMPtr<nsISelection> selection;
+ m_editor->GetSelection(getter_AddRefs(selection));
+ rv = selection->Collapse(divElem, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mailEditor) {
+ rv = mailEditor->InsertTextWithQuotations(aBuf);
+ } else {
+ // Will we ever get here?
+ rv = textEditor->InsertText(aBuf);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isForwarded) {
+ // Special treatment for forwarded messages: Part 2.
+ if (sigOnTopInserted) {
+ // Sadly the M-C editor inserts a <br> between the <div> for the signature
+ // and this <div>, so remove the <br> we don't want.
+ nsCOMPtr<nsIDOMNode> brBeforeDiv;
+ nsAutoString tagLocalName;
+ rv = divElem->GetPreviousSibling(getter_AddRefs(brBeforeDiv));
+ if (NS_SUCCEEDED(rv) && brBeforeDiv) {
+ brBeforeDiv->GetLocalName(tagLocalName);
+ if (tagLocalName.EqualsLiteral("br")) {
+ rv = m_editor->DeleteNode(brBeforeDiv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ // Clean up the <br> we inserted.
+ rv = m_editor->DeleteNode(extraBr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Use our own function instead of nsEditor::EndOfDocument() because
+ // we don't want to position at the end of the div we've just created.
+ // It's OK to use, even if we're not forwarding and didn't create a
+ // <div>.
+ rv = MoveToEndOfDocument();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if ((!isForwarded || !sigOnTop) && !aSignature.IsEmpty()) {
+ textEditor->InsertLineBreak();
+ InsertDivWrappedTextAtSelection(aSignature,
+ NS_LITERAL_STRING("moz-signature"));
+ }
+ }
+ }
+
+ if (aBuf.IsEmpty())
+ m_editor->BeginningOfDocument();
+ else
+ {
+ switch (reply_on_top)
+ {
+ // This should set the cursor after the body but before the sig
+ case 0:
+ {
+ if (!textEditor)
+ {
+ m_editor->BeginningOfDocument();
+ break;
+ }
+
+ nsCOMPtr<nsISelection> selection = nullptr;
+ nsCOMPtr<nsIDOMNode> parent = nullptr;
+ int32_t offset;
+ nsresult rv;
+
+ // get parent and offset of mailcite
+ rv = GetNodeLocation(nodeInserted, address_of(parent), &offset);
+ if (NS_FAILED(rv) || (!parent))
+ {
+ m_editor->BeginningOfDocument();
+ break;
+ }
+
+ // get selection
+ m_editor->GetSelection(getter_AddRefs(selection));
+ if (!selection)
+ {
+ m_editor->BeginningOfDocument();
+ break;
+ }
+
+ // place selection after mailcite
+ selection->Collapse(parent, offset+1);
+
+ // insert a break at current selection
+ textEditor->InsertLineBreak();
+
+ // i'm not sure if you need to move the selection back to before the
+ // break. expirement.
+ selection->Collapse(parent, offset+1);
+
+ break;
+ }
+
+ case 2:
+ {
+ m_editor->SelectAll();
+ break;
+ }
+
+ // This should set the cursor to the top!
+ default:
+ {
+ m_editor->BeginningOfDocument();
+ break;
+ }
+ }
+ }
+
+ nsCOMPtr<nsISelectionController> selCon;
+ m_editor->GetSelectionController(getter_AddRefs(selCon));
+
+ if (selCon)
+ selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_ANCHOR_REGION, true);
+
+ m_editor->EnableUndo(true);
+ SetBodyModified(false);
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ nsCOMPtr<nsIMsgComposeService> composeService (do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID));
+ composeService->TimeStamp("Finished inserting data into the editor. The window is finally ready!", false);
+#endif
+ return NS_OK;
+}
+
+/**
+ * Check the identity pref to include signature on replies and forwards.
+ */
+bool nsMsgCompose::CheckIncludeSignaturePrefs(nsIMsgIdentity *identity)
+{
+ bool includeSignature = true;
+ switch (mType)
+ {
+ case nsIMsgCompType::ForwardInline:
+ case nsIMsgCompType::ForwardAsAttachment:
+ identity->GetSigOnForward(&includeSignature);
+ break;
+ case nsIMsgCompType::Reply:
+ case nsIMsgCompType::ReplyAll:
+ case nsIMsgCompType::ReplyToList:
+ case nsIMsgCompType::ReplyToGroup:
+ case nsIMsgCompType::ReplyToSender:
+ case nsIMsgCompType::ReplyToSenderAndGroup:
+ identity->GetSigOnReply(&includeSignature);
+ break;
+ }
+ return includeSignature;
+}
+
+nsresult
+nsMsgCompose::SetQuotingToFollow(bool aVal)
+{
+ mQuotingToFollow = aVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::GetQuotingToFollow(bool* quotingToFollow)
+{
+ NS_ENSURE_ARG(quotingToFollow);
+ *quotingToFollow = mQuotingToFollow;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::Initialize(nsIMsgComposeParams *aParams,
+ mozIDOMWindowProxy *aWindow,
+ nsIDocShell *aDocShell)
+{
+ NS_ENSURE_ARG_POINTER(aParams);
+ nsresult rv;
+
+ aParams->GetIdentity(getter_AddRefs(m_identity));
+
+ if (aWindow)
+ {
+ m_window = aWindow;
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocShellTreeItem> treeItem =
+ do_QueryInterface(window->GetDocShell());
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ rv = treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (NS_FAILED(rv)) return rv;
+
+ m_baseWindow = do_QueryInterface(treeOwner);
+ }
+
+ MSG_ComposeFormat format;
+ aParams->GetFormat(&format);
+
+ MSG_ComposeType type;
+ aParams->GetType(&type);
+
+ nsCString originalMsgURI;
+ aParams->GetOriginalMsgURI(getter_Copies(originalMsgURI));
+ aParams->GetOrigMsgHdr(getter_AddRefs(mOrigMsgHdr));
+
+ nsCOMPtr<nsIMsgCompFields> composeFields;
+ aParams->GetComposeFields(getter_AddRefs(composeFields));
+
+ nsCOMPtr<nsIMsgComposeService> composeService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = composeService->DetermineComposeHTML(m_identity, format, &m_composeHTML);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (composeFields)
+ {
+ nsAutoCString draftId; // will get set for drafts and templates
+ rv = composeFields->GetDraftId(getter_Copies(draftId));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Set return receipt flag and type, and if we should attach a vCard
+ // by checking the identity prefs - but don't clobber the values for
+ // drafts and templates as they were set up already by mime when
+ // initializing the message.
+ if (m_identity && draftId.IsEmpty() && type != nsIMsgCompType::Template)
+ {
+ bool requestReturnReceipt = false;
+ rv = m_identity->GetRequestReturnReceipt(&requestReturnReceipt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = composeFields->SetReturnReceipt(requestReturnReceipt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t receiptType = nsIMsgMdnGenerator::eDntType;
+ rv = m_identity->GetReceiptHeaderType(&receiptType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = composeFields->SetReceiptHeaderType(receiptType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool requestDSN = false;
+ rv = m_identity->GetRequestDSN(&requestDSN);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = composeFields->SetDSN(requestDSN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool attachVCard;
+ rv = m_identity->GetAttachVCard(&attachVCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = composeFields->SetAttachVCard(attachVCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+ aParams->GetSendListener(getter_AddRefs(externalSendListener));
+ if(externalSendListener)
+ AddMsgSendListener( externalSendListener );
+
+ nsCString smtpPassword;
+ aParams->GetSmtpPassword(getter_Copies(smtpPassword));
+ mSmtpPassword = smtpPassword;
+
+ aParams->GetHtmlToQuote(mHtmlToQuote);
+
+ if (aDocShell)
+ {
+ mDocShell = aDocShell;
+ // register the compose object with the compose service
+ rv = composeService->RegisterComposeDocShell(aDocShell, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return CreateMessage(originalMsgURI.get(), type, composeFields);
+}
+
+nsresult nsMsgCompose::SetDocumentCharset(const char *aCharset)
+{
+ NS_ENSURE_TRUE(m_compFields && m_editor, NS_ERROR_NOT_INITIALIZED);
+
+ // Set charset, this will be used for the MIME charset labeling.
+ m_compFields->SetCharacterSet(aCharset);
+
+ // notify the change to editor
+ nsCString charset;
+ if (aCharset)
+ charset = nsDependentCString(aCharset);
+ if (m_editor)
+ m_editor->SetDocumentCharacterSet(charset);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::RegisterStateListener(nsIMsgComposeStateListener *aStateListener)
+{
+ NS_ENSURE_ARG_POINTER(aStateListener);
+
+ return mStateListeners.AppendElement(aStateListener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::UnregisterStateListener(nsIMsgComposeStateListener *aStateListener)
+{
+ NS_ENSURE_ARG_POINTER(aStateListener);
+
+ return mStateListeners.RemoveElement(aStateListener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// Added to allow easier use of the nsIMsgSendListener
+NS_IMETHODIMP nsMsgCompose::AddMsgSendListener( nsIMsgSendListener *aMsgSendListener )
+{
+ NS_ENSURE_ARG_POINTER(aMsgSendListener);
+ return mExternalSendListeners.AppendElement(aMsgSendListener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgCompose::RemoveMsgSendListener( nsIMsgSendListener *aMsgSendListener )
+{
+ NS_ENSURE_ARG_POINTER(aMsgSendListener);
+ return mExternalSendListeners.RemoveElement(aMsgSendListener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::SendMsgToServer(MSG_DeliverMode deliverMode, nsIMsgIdentity *identity,
+ const char *accountKey)
+{
+ nsresult rv = NS_OK;
+
+ // clear saved message id if sending, so we don't send out the same message-id.
+ if (deliverMode == nsIMsgCompDeliverMode::Now ||
+ deliverMode == nsIMsgCompDeliverMode::Later ||
+ deliverMode == nsIMsgCompDeliverMode::Background)
+ m_compFields->SetMessageId("");
+
+ if (m_compFields && identity)
+ {
+ // Pref values are supposed to be stored as UTF-8, so no conversion
+ nsCString email;
+ nsString fullName;
+ nsString organization;
+
+ identity->GetEmail(email);
+ identity->GetFullName(fullName);
+ identity->GetOrganization(organization);
+
+ const char* pFrom = m_compFields->GetFrom();
+ if (!pFrom || !*pFrom)
+ {
+ nsCString sender;
+ MakeMimeAddress(NS_ConvertUTF16toUTF8(fullName), email, sender);
+ m_compFields->SetFrom(sender.IsEmpty() ? email.get() : sender.get());
+ }
+
+ m_compFields->SetOrganization(organization);
+
+ // We need an nsIMsgSend instance to send the message. Allow extensions
+ // to override the default SMTP sender by observing mail-set-sender.
+ mMsgSend = nullptr;
+ mDeliverMode = deliverMode; // save for possible access by observer.
+
+ // Allow extensions to specify an outgoing server.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(observerService);
+
+ // Assemble a string with sending parameters.
+ nsAutoString sendParms;
+
+ // First parameter: account key. This may be null.
+ sendParms.AppendASCII(accountKey && *accountKey ? accountKey : "");
+ sendParms.AppendLiteral(",");
+
+ // Second parameter: deliverMode.
+ sendParms.AppendInt(deliverMode);
+ sendParms.AppendLiteral(",");
+
+ // Third parameter: identity (as identity key).
+ nsAutoCString identityKey;
+ identity->GetKey(identityKey);
+ sendParms.AppendASCII(identityKey.get());
+
+ observerService->NotifyObservers(
+ NS_ISUPPORTS_CAST(nsIMsgCompose*, this),
+ "mail-set-sender",
+ sendParms.get());
+
+ if (!mMsgSend)
+ mMsgSend = do_CreateInstance(NS_MSGSEND_CONTRACTID);
+
+ if (mMsgSend)
+ {
+ nsCString bodyString(m_compFields->GetBody());
+
+ // Create the listener for the send operation...
+ nsCOMPtr<nsIMsgComposeSendListener> composeSendListener = do_CreateInstance(NS_MSGCOMPOSESENDLISTENER_CONTRACTID);
+ if (!composeSendListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // right now, AutoSaveAsDraft is identical to SaveAsDraft as
+ // far as the msg send code is concerned. This way, we don't have
+ // to add an nsMsgDeliverMode for autosaveasdraft, and add cases for
+ // it in the msg send code.
+ if (deliverMode == nsIMsgCompDeliverMode::AutoSaveAsDraft)
+ deliverMode = nsIMsgCompDeliverMode::SaveAsDraft;
+
+ RefPtr<nsIMsgCompose> msgCompose(this);
+ composeSendListener->SetMsgCompose(msgCompose);
+ composeSendListener->SetDeliverMode(deliverMode);
+
+ if (mProgress)
+ {
+ nsCOMPtr<nsIWebProgressListener> progressListener = do_QueryInterface(composeSendListener);
+ mProgress->RegisterListener(progressListener);
+ }
+
+ // If we are composing HTML, then this should be sent as
+ // multipart/related which means we pass the editor into the
+ // backend...if not, just pass nullptr
+ //
+ nsCOMPtr<nsIMsgSendListener> sendListener = do_QueryInterface(composeSendListener);
+ rv = mMsgSend->CreateAndSendMessage(
+ m_composeHTML ? m_editor.get() : nullptr,
+ identity,
+ accountKey,
+ m_compFields,
+ false,
+ false,
+ (nsMsgDeliverMode)deliverMode,
+ nullptr,
+ m_composeHTML ? TEXT_HTML : TEXT_PLAIN,
+ bodyString,
+ nullptr,
+ nullptr,
+ m_window,
+ mProgress,
+ sendListener,
+ mSmtpPassword.get(),
+ mOriginalMsgURI,
+ mType);
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+ }
+ else
+ rv = NS_ERROR_NOT_INITIALIZED;
+
+ if (NS_FAILED(rv))
+ NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgCompose::SendMsg(MSG_DeliverMode deliverMode, nsIMsgIdentity *identity, const char *accountKey, nsIMsgWindow *aMsgWindow, nsIMsgProgress *progress)
+{
+ NS_ENSURE_TRUE(m_compFields, NS_ERROR_NOT_INITIALIZED);
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrompt> prompt;
+
+ // i'm assuming the compose window is still up at this point...
+ if (m_window) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(m_window);
+ window->GetPrompter(getter_AddRefs(prompt));
+ }
+
+ // Set content type based on which type of compose window we had.
+ nsString contentType = (m_composeHTML) ? NS_LITERAL_STRING("text/html"):
+ NS_LITERAL_STRING("text/plain");
+ nsString msgBody;
+ if (m_editor)
+ {
+ // Reset message body previously stored in the compose fields
+ // There is 2 nsIMsgCompFields::SetBody() functions using a pointer as argument,
+ // therefore a casting is required.
+ m_compFields->SetBody((const char *)nullptr);
+
+ const char *charset = m_compFields->GetCharacterSet();
+
+ uint32_t flags = nsIDocumentEncoder::OutputCRLineBreak |
+ nsIDocumentEncoder::OutputLFLineBreak;
+
+ if (m_composeHTML) {
+ flags |= nsIDocumentEncoder::OutputFormatted |
+ nsIDocumentEncoder::OutputDisallowLineBreaking;
+ } else {
+ bool flowed, delsp, formatted, disallowBreaks;
+ GetSerialiserFlags(charset, &flowed, &delsp, &formatted, &disallowBreaks);
+ if (flowed)
+ flags |= nsIDocumentEncoder::OutputFormatFlowed;
+ if (delsp)
+ flags |= nsIDocumentEncoder::OutputFormatDelSp;
+ if (formatted)
+ flags |= nsIDocumentEncoder::OutputFormatted;
+ if (disallowBreaks)
+ flags |= nsIDocumentEncoder::OutputDisallowLineBreaking;
+ // Don't lose NBSP in the plain text encoder.
+ flags |= nsIDocumentEncoder::OutputPersistNBSP;
+ }
+ rv = m_editor->OutputToString(contentType, flags, msgBody);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ m_compFields->GetBody(msgBody);
+ }
+ if (!msgBody.IsEmpty())
+ {
+ // Convert body to mail charset
+ nsCString outCString;
+ rv = nsMsgI18NConvertFromUnicode(m_compFields->GetCharacterSet(),
+ msgBody, outCString, false, true);
+ bool isAsciiOnly = NS_IsAscii(outCString.get()) &&
+ !nsMsgI18Nstateful_charset(m_compFields->GetCharacterSet());
+ if (m_compFields->GetForceMsgEncoding())
+ isAsciiOnly = false;
+ if (NS_SUCCEEDED(rv) && !outCString.IsEmpty())
+ {
+ // If the body contains characters outside the repertoire of the current
+ // charset, just convert to UTF-8 and be done with it
+ // unless disable_fallback_to_utf8 is set for this charset.
+ if (NS_ERROR_UENC_NOMAPPING == rv)
+ {
+ bool needToCheckCharset;
+ m_compFields->GetNeedToCheckCharset(&needToCheckCharset);
+ if (needToCheckCharset)
+ {
+ bool disableFallback = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (prefBranch)
+ {
+ nsCString prefName("mailnews.disable_fallback_to_utf8.");
+ prefName.Append(m_compFields->GetCharacterSet());
+ prefBranch->GetBoolPref(prefName.get(), &disableFallback);
+ }
+ if (!disableFallback)
+ {
+ CopyUTF16toUTF8(msgBody, outCString);
+ m_compFields->SetCharacterSet("UTF-8");
+ SetDocumentCharset("UTF-8");
+ }
+ }
+ }
+ m_compFields->SetBodyIsAsciiOnly(isAsciiOnly);
+ m_compFields->SetBody(outCString.get());
+ }
+ else
+ {
+ m_compFields->SetBody(NS_ConvertUTF16toUTF8(msgBody).get());
+ m_compFields->SetCharacterSet("UTF-8");
+ SetDocumentCharset("UTF-8");
+ }
+ }
+
+ // Let's open the progress dialog
+ if (progress)
+ {
+ mProgress = progress;
+
+ if (deliverMode != nsIMsgCompDeliverMode::AutoSaveAsDraft)
+ {
+ nsAutoString msgSubject;
+ m_compFields->GetSubject(msgSubject);
+
+ bool showProgress = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ {
+ prefBranch->GetBoolPref("mailnews.show_send_progress", &showProgress);
+ if (showProgress)
+ {
+ nsCOMPtr<nsIMsgComposeProgressParams> params = do_CreateInstance(NS_MSGCOMPOSEPROGRESSPARAMS_CONTRACTID, &rv);
+ if (NS_FAILED(rv) || !params)
+ return NS_ERROR_FAILURE;
+
+ params->SetSubject(msgSubject.get());
+ params->SetDeliveryMode(deliverMode);
+
+ mProgress->OpenProgressDialog(m_window, aMsgWindow,
+ "chrome://messenger/content/messengercompose/sendProgress.xul",
+ false, params);
+ }
+ }
+ }
+
+ mProgress->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, NS_OK);
+ }
+
+ bool attachVCard = false;
+ m_compFields->GetAttachVCard(&attachVCard);
+
+ if (attachVCard && identity &&
+ (deliverMode == nsIMsgCompDeliverMode::Now ||
+ deliverMode == nsIMsgCompDeliverMode::Later ||
+ deliverMode == nsIMsgCompDeliverMode::Background))
+ {
+ nsCString escapedVCard;
+ // make sure, if there is no card, this returns an empty string, or NS_ERROR_FAILURE
+ rv = identity->GetEscapedVCard(escapedVCard);
+
+ if (NS_SUCCEEDED(rv) && !escapedVCard.IsEmpty())
+ {
+ nsCString vCardUrl;
+ vCardUrl = "data:text/x-vcard;charset=utf-8;base64,";
+ nsCString unescapedData;
+ MsgUnescapeString(escapedVCard, 0, unescapedData);
+ char *result = PL_Base64Encode(unescapedData.get(), 0, nullptr);
+ vCardUrl += result;
+ PR_Free(result);
+
+ nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && attachment)
+ {
+ // [comment from 4.x]
+ // Send the vCard out with a filename which distinguishes this user. e.g. jsmith.vcf
+ // The main reason to do this is for interop with Eudora, which saves off
+ // the attachments separately from the message body
+ nsCString userid;
+ (void)identity->GetEmail(userid);
+ int32_t index = userid.FindChar('@');
+ if (index != kNotFound)
+ userid.SetLength(index);
+
+ if (userid.IsEmpty())
+ attachment->SetName(NS_LITERAL_STRING("vcard.vcf"));
+ else
+ {
+ // Replace any dot with underscore to stop vCards
+ // generating false positives with some heuristic scanners
+ MsgReplaceChar(userid, '.', '_');
+ userid.AppendLiteral(".vcf");
+ attachment->SetName(NS_ConvertASCIItoUTF16(userid));
+ }
+
+ attachment->SetUrl(vCardUrl);
+ m_compFields->AddAttachment(attachment);
+ }
+ }
+ }
+
+ // Save the identity being sent for later use.
+ m_identity = identity;
+
+ rv = SendMsgToServer(deliverMode, identity, accountKey);
+ if (NS_FAILED(rv))
+ {
+ nsCOMPtr<nsIMsgSendReport> sendReport;
+ if (mMsgSend)
+ mMsgSend->GetSendReport(getter_AddRefs(sendReport));
+ if (sendReport)
+ {
+ nsresult theError;
+ sendReport->DisplayReport(prompt, true, true, &theError);
+ }
+ else
+ {
+ /* If we come here it's because we got an error before we could intialize a
+ send report! Let's try our best...
+ */
+ switch (deliverMode)
+ {
+ case nsIMsgCompDeliverMode::Later:
+ nsMsgDisplayMessageByName(prompt, u"unableToSendLater");
+ break;
+ case nsIMsgCompDeliverMode::AutoSaveAsDraft:
+ case nsIMsgCompDeliverMode::SaveAsDraft:
+ nsMsgDisplayMessageByName(prompt, u"unableToSaveDraft");
+ break;
+ case nsIMsgCompDeliverMode::SaveAsTemplate:
+ nsMsgDisplayMessageByName(prompt, u"unableToSaveTemplate");
+ break;
+
+ default:
+ nsMsgDisplayMessageByName(prompt, u"sendFailed");
+ break;
+ }
+ }
+
+ if (progress)
+ progress->CloseProgressDialog(true);
+ }
+
+ return rv;
+}
+
+/* attribute boolean deleteDraft */
+NS_IMETHODIMP nsMsgCompose::GetDeleteDraft(bool *aDeleteDraft)
+{
+ NS_ENSURE_ARG_POINTER(aDeleteDraft);
+ *aDeleteDraft = mDeleteDraft;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::SetDeleteDraft(bool aDeleteDraft)
+{
+ mDeleteDraft = aDeleteDraft;
+ return NS_OK;
+}
+
+bool nsMsgCompose::IsLastWindow()
+{
+ nsresult rv;
+ bool more;
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+ rv = windowMediator->GetEnumerator(nullptr,
+ getter_AddRefs(windowEnumerator));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsISupports> isupports;
+
+ if (NS_SUCCEEDED(windowEnumerator->GetNext(getter_AddRefs(isupports))))
+ if (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)))
+ return !more;
+ }
+ }
+ return true;
+}
+
+NS_IMETHODIMP nsMsgCompose::CloseWindow(void)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgComposeService> composeService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // unregister the compose object with the compose service
+ rv = composeService->UnregisterComposeDocShell(mDocShell);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mDocShell = nullptr;
+
+ // ensure that the destructor of nsMsgSend is invoked to remove
+ // temporary files.
+ mMsgSend = nullptr;
+
+ //We are going away for real, we need to do some clean up first
+ if (m_baseWindow)
+ {
+ if (m_editor)
+ {
+ // The editor will be destroyed during the close window.
+ // Set it to null to be sure we won't use it anymore.
+ m_editor = nullptr;
+ }
+ nsIBaseWindow * window = m_baseWindow;
+ m_baseWindow = nullptr;
+ rv = window->Destroy();
+ }
+
+ m_window = nullptr;
+ return rv;
+}
+
+nsresult nsMsgCompose::Abort()
+{
+ if (mMsgSend)
+ mMsgSend->Abort();
+
+ if (mProgress)
+ mProgress->CloseProgressDialog(true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetEditor(nsIEditor * *aEditor)
+{
+ NS_IF_ADDREF(*aEditor = m_editor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::SetEditor(nsIEditor *aEditor)
+{
+ m_editor = aEditor;
+ return NS_OK;
+}
+
+static nsresult fixCharset(nsCString &aCharset)
+{
+ // No matter what, we should block x-windows-949 (our internal name)
+ // from being used for outgoing emails (bug 234958).
+ if (aCharset.Equals("x-windows-949", nsCaseInsensitiveCStringComparator()))
+ aCharset = "EUC-KR";
+
+ // Convert to a canonical charset name.
+ // Bug 1297118 will revisit this call site.
+ nsresult rv;
+ nsCOMPtr<nsICharsetConverterManager> ccm =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString charset(aCharset);
+ rv = ccm->GetCharsetAlias(charset.get(), aCharset);
+
+ // Don't accept UTF-16 ever. UTF-16 should never be selected as an
+ // outgoing encoding for e-mail. MIME can't handle those messages
+ // encoded in ASCII-incompatible encodings.
+ if (NS_FAILED(rv) ||
+ StringBeginsWith(aCharset, NS_LITERAL_CSTRING("UTF-16"))) {
+ aCharset.AssignLiteral("UTF-8");
+ }
+ return NS_OK;
+}
+
+// This used to be called BEFORE editor was created
+// (it did the loadUrl that triggered editor creation)
+// It is called from JS after editor creation
+// (loadUrl is done in JS)
+NS_IMETHODIMP nsMsgCompose::InitEditor(nsIEditor* aEditor, mozIDOMWindowProxy* aContentWindow)
+{
+ NS_ENSURE_ARG_POINTER(aEditor);
+ NS_ENSURE_ARG_POINTER(aContentWindow);
+ nsresult rv;
+
+ m_editor = aEditor;
+
+ nsAutoCString msgCharSet(m_compFields->GetCharacterSet());
+ rv = fixCharset(msgCharSet);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_compFields->SetCharacterSet(msgCharSet.get());
+ m_editor->SetDocumentCharacterSet(msgCharSet);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aContentWindow);
+
+ nsIDocShell *docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIContentViewer> childCV;
+ NS_ENSURE_SUCCESS(docShell->GetContentViewer(getter_AddRefs(childCV)), NS_ERROR_FAILURE);
+ if (childCV)
+ {
+ // SetForceCharacterSet will complain about "UTF-7" or "x-mac-croatian"
+ // (see test-charset-edit.js), but we deal with this elsewhere.
+ rv = childCV->SetForceCharacterSet(msgCharSet);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetForceCharacterSet() failed");
+ }
+
+ // This is what used to be done in mDocumentListener,
+ // nsMsgDocumentStateListener::NotifyDocumentCreated()
+ bool quotingToFollow = false;
+ GetQuotingToFollow(&quotingToFollow);
+ if (quotingToFollow)
+ return BuildQuotedMessageAndSignature();
+ else
+ {
+ NotifyStateListeners(nsIMsgComposeNotificationType::ComposeFieldsReady, NS_OK);
+ rv = BuildBodyMessageAndSignature();
+ NotifyStateListeners(nsIMsgComposeNotificationType::ComposeBodyReady, NS_OK);
+ return rv;
+ }
+}
+
+NS_IMETHODIMP nsMsgCompose::GetBodyRaw(nsACString& aBodyRaw)
+{
+ aBodyRaw.Assign((char *)m_compFields->GetBody());
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::GetBodyModified(bool * modified)
+{
+ nsresult rv;
+
+ if (! modified)
+ return NS_ERROR_NULL_POINTER;
+
+ *modified = true;
+
+ if (m_editor)
+ {
+ rv = m_editor->GetDocumentModified(modified);
+ if (NS_FAILED(rv))
+ *modified = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::SetBodyModified(bool modified)
+{
+ nsresult rv = NS_OK;
+
+ if (m_editor)
+ {
+ if (modified)
+ {
+ int32_t modCount = 0;
+ m_editor->GetModificationCount(&modCount);
+ if (modCount == 0)
+ m_editor->IncrementModificationCount(1);
+ }
+ else
+ m_editor->ResetModificationCount();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::GetDomWindow(mozIDOMWindowProxy * *aDomWindow)
+{
+ NS_IF_ADDREF(*aDomWindow = m_window);
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::GetCompFields(nsIMsgCompFields * *aCompFields)
+{
+ *aCompFields = (nsIMsgCompFields*)m_compFields;
+ NS_IF_ADDREF(*aCompFields);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetComposeHTML(bool *aComposeHTML)
+{
+ *aComposeHTML = m_composeHTML;
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::GetWrapLength(int32_t *aWrapLength)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ return prefBranch->GetIntPref("mailnews.wraplength", aWrapLength);
+}
+
+nsresult nsMsgCompose::CreateMessage(const char * originalMsgURI,
+ MSG_ComposeType type,
+ nsIMsgCompFields * compFields)
+{
+ nsresult rv = NS_OK;
+ mType = type;
+ mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_None;
+
+ mDeleteDraft = (type == nsIMsgCompType::Draft);
+ nsAutoCString msgUri(originalMsgURI);
+ bool fileUrl = StringBeginsWith(msgUri, NS_LITERAL_CSTRING("file:"));
+ int32_t typeIndex = msgUri.Find("type=application/x-message-display");
+ if (typeIndex != kNotFound && typeIndex > 0)
+ {
+ // Strip out type=application/x-message-display because it confuses libmime.
+ msgUri.Cut(typeIndex, sizeof("type=application/x-message-display"));
+ if (fileUrl) // we're dealing with an .eml file msg
+ {
+ // We have now removed the type from the uri. Make sure we don't have
+ // an uri with "&&" now. If we do, remove the second '&'.
+ if (msgUri.CharAt(typeIndex) == '&')
+ msgUri.Cut(typeIndex, 1);
+ // Remove possible trailing '?'.
+ if (msgUri.CharAt(msgUri.Length() - 1) == '?')
+ msgUri.Cut(msgUri.Length() - 1, 1);
+ }
+ else // we're dealing with a message/rfc822 attachment
+ {
+ // nsURLFetcher will check for "realtype=message/rfc822" and will set the
+ // content type to message/rfc822 in the forwarded message.
+ msgUri.Append("&realtype=message/rfc822");
+ }
+ originalMsgURI = msgUri.get();
+ }
+
+ if (compFields)
+ {
+ NS_IF_RELEASE(m_compFields);
+ m_compFields = reinterpret_cast<nsMsgCompFields*>(compFields);
+ NS_ADDREF(m_compFields);
+ }
+ else
+ {
+ m_compFields = new nsMsgCompFields();
+ if (m_compFields)
+ NS_ADDREF(m_compFields);
+ else
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (m_identity && mType != nsIMsgCompType::Draft)
+ {
+ // Setup reply-to field.
+ nsCString replyTo;
+ m_identity->GetReplyTo(replyTo);
+ if (!replyTo.IsEmpty())
+ {
+ nsCString resultStr;
+ RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetReplyTo()),
+ replyTo, resultStr);
+ if (!resultStr.IsEmpty())
+ {
+ replyTo.Append(',');
+ replyTo.Append(resultStr);
+ }
+ m_compFields->SetReplyTo(replyTo.get());
+ }
+
+ // Setup auto-Cc field.
+ bool doCc;
+ m_identity->GetDoCc(&doCc);
+ if (doCc)
+ {
+ nsCString ccList;
+ m_identity->GetDoCcList(ccList);
+
+ nsCString resultStr;
+ RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetCc()),
+ ccList, resultStr);
+ if (!resultStr.IsEmpty())
+ {
+ ccList.Append(',');
+ ccList.Append(resultStr);
+ }
+ m_compFields->SetCc(ccList.get());
+ }
+
+ // Setup auto-Bcc field.
+ bool doBcc;
+ m_identity->GetDoBcc(&doBcc);
+ if (doBcc)
+ {
+ nsCString bccList;
+ m_identity->GetDoBccList(bccList);
+
+ nsCString resultStr;
+ RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetBcc()),
+ bccList, resultStr);
+ if (!resultStr.IsEmpty())
+ {
+ bccList.Append(',');
+ bccList.Append(resultStr);
+ }
+ m_compFields->SetBcc(bccList.get());
+ }
+ }
+
+ if (mType == nsIMsgCompType::Draft)
+ {
+ nsCString curDraftIdURL;
+ rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty(), "CreateMessage can't get draft id");
+
+ // Skip if no draft id (probably a new draft msg).
+ if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty())
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr;
+ rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(msgDBHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CreateMessage can't get msg header DB interface pointer.");
+ if (msgDBHdr)
+ {
+ nsCString queuedDisposition;
+ msgDBHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, getter_Copies(queuedDisposition));
+ // We need to retrieve the original URI from the database so we can
+ // set the disposition flags correctly if the draft is a reply or forwarded message.
+ nsCString originalMsgURIfromDB;
+ msgDBHdr->GetStringProperty(ORIG_URI_PROPERTY, getter_Copies(originalMsgURIfromDB));
+ mOriginalMsgURI = originalMsgURIfromDB;
+ if (!queuedDisposition.IsEmpty())
+ {
+ if (queuedDisposition.Equals("replied"))
+ mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Replied;
+ else if (queuedDisposition.Equals("forward"))
+ mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Forwarded;
+ }
+ }
+ }
+ }
+
+ // If we don't have an original message URI, nothing else to do...
+ if (!originalMsgURI || *originalMsgURI == 0)
+ return NS_OK;
+
+ // store the original message URI so we can extract it after we send the message to properly
+ // mark any disposition flags like replied or forwarded on the message.
+ if (mOriginalMsgURI.IsEmpty())
+ mOriginalMsgURI = originalMsgURI;
+
+ nsCOMPtr<nsIPrefBranch> prefs (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // "Forward inline" and "Reply with template" processing.
+ // Note the early return at the end of the block.
+ if (type == nsIMsgCompType::ForwardInline ||
+ type == nsIMsgCompType::ReplyWithTemplate)
+ {
+ // Use charset set up in the compose fields by MIME unless we should
+ // use the default charset.
+ bool replyInDefault = false;
+ prefs->GetBoolPref("mailnews.reply_in_default_charset",
+ &replyInDefault);
+ // Use send_default_charset if reply_in_default_charset is on.
+ if (replyInDefault)
+ {
+ nsString str;
+ nsCString charset;
+ NS_GetLocalizedUnicharPreferenceWithDefault(prefs, "mailnews.send_default_charset",
+ EmptyString(), str);
+ if (!str.IsEmpty())
+ {
+ LossyCopyUTF16toASCII(str, charset);
+ m_compFields->SetCharacterSet(charset.get());
+ mAnswerDefaultCharset = true;
+ }
+ }
+
+ // We want to treat this message as a reference too
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(originalMsgURI, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+
+ nsAutoCString reference;
+ // When forwarding we only use the original message for "References:" -
+ // recipients don't have the other messages anyway.
+ // For reply with template we want to preserve all the references.
+ if (type == nsIMsgCompType::ReplyWithTemplate)
+ {
+ uint16_t numReferences = 0;
+ msgHdr->GetNumReferences(&numReferences);
+ for (int32_t i = 0; i < numReferences; i++)
+ {
+ nsAutoCString ref;
+ msgHdr->GetStringReference(i, ref);
+ if (!ref.IsEmpty())
+ {
+ reference.AppendLiteral("<");
+ reference.Append(ref);
+ reference.AppendLiteral("> ");
+ }
+ }
+ reference.Trim(" ", false, true);
+ }
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ reference.AppendLiteral("<");
+ reference.Append(messageId);
+ reference.AppendLiteral(">");
+ m_compFields->SetReferences(reference.get());
+ }
+
+ // Early return for "Forward inline" and "Reply with template" processing.
+ return NS_OK;
+ }
+
+ // All other processing.
+ char *uriList = PL_strdup(originalMsgURI);
+ if (!uriList)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Resulting charset for this message.
+ nsCString charset;
+
+ // Check for the charset of the last displayed message, it
+ // will be used for quoting and as override.
+ nsCString windowCharset;
+ mCharsetOverride = false;
+ mAnswerDefaultCharset = false;
+ GetTopmostMsgWindowCharacterSet(windowCharset, &mCharsetOverride);
+ if (!windowCharset.IsEmpty()) {
+ // Although the charset in which to send the message might change,
+ // the original message will be parsed for quoting using the charset it is
+ // now displayed with.
+ mQuoteCharset = windowCharset;
+
+ if (mCharsetOverride) {
+ // Use override charset.
+ charset = windowCharset;
+ }
+ }
+
+ // Note the following:
+ // LoadDraftOrTemplate() is run in nsMsgComposeService::OpenComposeWindow()
+ // for five compose types: ForwardInline, ReplyWithTemplate (both covered
+ // in the code block above) and Draft, Template and Redirect. For these
+ // compose types, the charset is already correct (incl. MIME-applied override)
+ // unless the default charset should be used.
+
+ bool isFirstPass = true;
+ char *uri = uriList;
+ char *nextUri;
+ do
+ {
+ nextUri = strstr(uri, "://");
+ if (nextUri)
+ {
+ // look for next ://, and then back up to previous ','
+ nextUri = strstr(nextUri + 1, "://");
+ if (nextUri)
+ {
+ *nextUri = '\0';
+ char *saveNextUri = nextUri;
+ nextUri = strrchr(uri, ',');
+ if (nextUri)
+ *nextUri = '\0';
+ *saveNextUri = ':';
+ }
+ }
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ if (mOrigMsgHdr)
+ msgHdr = mOrigMsgHdr;
+ else
+ {
+ rv = GetMsgDBHdrFromURI(uri, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ if (msgHdr)
+ {
+ nsCString decodedCString;
+
+ bool replyInDefault = false;
+ prefs->GetBoolPref("mailnews.reply_in_default_charset",
+ &replyInDefault);
+ // Use send_default_charset if reply_in_default_charset is on.
+ if (replyInDefault)
+ {
+ nsString str;
+ NS_GetLocalizedUnicharPreferenceWithDefault(prefs, "mailnews.send_default_charset",
+ EmptyString(), str);
+ if (!str.IsEmpty()) {
+ LossyCopyUTF16toASCII(str, charset);
+ mAnswerDefaultCharset = true;
+ }
+ }
+
+ // Set the charset we determined, if any, in the comp fields.
+ // For replies, the charset will be set after processing the message
+ // through MIME in QuotingOutputStreamListener::OnStopRequest().
+ if (isFirstPass && !charset.IsEmpty())
+ m_compFields->SetCharacterSet(charset.get());
+
+ nsString subject;
+ rv = msgHdr->GetMime2DecodedSubject(subject);
+ if (NS_FAILED(rv)) return rv;
+
+ // Check if (was: is present in the subject
+ int32_t wasOffset = subject.RFind(NS_LITERAL_STRING(" (was:"));
+ bool strip = true;
+
+ if (wasOffset >= 0) {
+ // Check the number of references, to check if was: should be stripped
+ // First, assume that it should be stripped; the variable will be set to
+ // false later if stripping should not happen.
+ uint16_t numRef;
+ msgHdr->GetNumReferences(&numRef);
+ if (numRef) {
+ // If there are references, look for the first message in the thread
+ // firstly, get the database via the folder
+ nsCOMPtr<nsIMsgFolder> folder;
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ if (folder) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ folder->GetMsgDatabase(getter_AddRefs(db));
+
+ if (db) {
+ nsAutoCString reference;
+ msgHdr->GetStringReference(0, reference);
+
+ nsCOMPtr<nsIMsgDBHdr> refHdr;
+ db->GetMsgHdrForMessageID(reference.get(), getter_AddRefs(refHdr));
+
+ if (refHdr) {
+ nsCString refSubject;
+ rv = refHdr->GetSubject(getter_Copies(refSubject));
+ if (NS_SUCCEEDED(rv)) {
+ if (refSubject.Find(" (was:") >= 0)
+ strip = false;
+ }
+ }
+ }
+ }
+ }
+ else
+ strip = false;
+ }
+
+ if (strip && wasOffset >= 0) {
+ // Strip off the "(was: old subject)" part
+ subject.Assign(Substring(subject, 0, wasOffset));
+ }
+
+ switch (type)
+ {
+ default: break;
+ case nsIMsgCompType::Reply :
+ case nsIMsgCompType::ReplyAll:
+ case nsIMsgCompType::ReplyToList:
+ case nsIMsgCompType::ReplyToGroup:
+ case nsIMsgCompType::ReplyToSender:
+ case nsIMsgCompType::ReplyToSenderAndGroup:
+ {
+ if (!isFirstPass) // safeguard, just in case...
+ {
+ PR_Free(uriList);
+ return rv;
+ }
+ mQuotingToFollow = true;
+
+ subject.Insert(NS_LITERAL_STRING("Re: "), 0);
+ m_compFields->SetSubject(subject);
+
+ // Setup quoting callbacks for later...
+ mWhatHolder = 1;
+ break;
+ }
+ case nsIMsgCompType::ForwardAsAttachment:
+ {
+ // Add the forwarded message in the references, first
+ nsAutoCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ if (isFirstPass)
+ {
+ nsAutoCString reference;
+ reference.Append(NS_LITERAL_CSTRING("<"));
+ reference.Append(messageId);
+ reference.Append(NS_LITERAL_CSTRING(">"));
+ m_compFields->SetReferences(reference.get());
+ }
+ else
+ {
+ nsAutoCString references;
+ m_compFields->GetReferences(getter_Copies(references));
+ references.Append(NS_LITERAL_CSTRING(" <"));
+ references.Append(messageId);
+ references.Append(NS_LITERAL_CSTRING(">"));
+ m_compFields->SetReferences(references.get());
+ }
+
+ uint32_t flags;
+
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::HasRe)
+ subject.Insert(NS_LITERAL_STRING("Re: "), 0);
+
+ // Setup quoting callbacks for later...
+ mQuotingToFollow = false; //We don't need to quote the original message.
+ nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && attachment)
+ {
+ bool addExtension = true;
+ nsString sanitizedSubj;
+ prefs->GetBoolPref("mail.forward_add_extension", &addExtension);
+
+ // copy subject string to sanitizedSubj, use default if empty
+ if (subject.IsEmpty())
+ {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> composeBundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties",
+ getter_AddRefs(composeBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ composeBundle->GetStringFromName(u"messageAttachmentSafeName",
+ getter_Copies(sanitizedSubj));
+ }
+ else
+ sanitizedSubj.Assign(subject);
+
+ // set the file size
+ uint32_t messageSize;
+ msgHdr->GetMessageSize(&messageSize);
+ attachment->SetSize(messageSize);
+
+ // change all '.' to '_' see bug #271211
+ MsgReplaceChar(sanitizedSubj, ".", '_');
+ if (addExtension)
+ sanitizedSubj.AppendLiteral(".eml");
+ attachment->SetName(sanitizedSubj);
+ attachment->SetUrl(nsDependentCString(uri));
+ m_compFields->AddAttachment(attachment);
+ }
+
+ if (isFirstPass)
+ {
+ nsCString fwdPrefix;
+ prefs->GetCharPref("mail.forward_subject_prefix", getter_Copies(fwdPrefix));
+ if (!fwdPrefix.IsEmpty())
+ {
+ nsString unicodeFwdPrefix;
+ CopyUTF8toUTF16(fwdPrefix, unicodeFwdPrefix);
+ unicodeFwdPrefix.AppendLiteral(": ");
+ subject.Insert(unicodeFwdPrefix, 0);
+ }
+ else
+ {
+ subject.Insert(NS_LITERAL_STRING("Fwd: "), 0);
+ }
+ m_compFields->SetSubject(subject);
+ }
+ break;
+ }
+ case nsIMsgCompType::Redirect:
+ {
+ // For a redirect, set the Reply-To: header to what was in the original From: header...
+ nsAutoCString author;
+ msgHdr->GetAuthor(getter_Copies(author));
+ m_compFields->SetReplyTo(author.get());
+
+ // ... and empty out the various recipient headers
+ nsAutoString empty;
+ m_compFields->SetTo(empty);
+ m_compFields->SetCc(empty);
+ m_compFields->SetBcc(empty);
+ m_compFields->SetNewsgroups(empty);
+ m_compFields->SetFollowupTo(empty);
+ break;
+ }
+ }
+ }
+ isFirstPass = false;
+ uri = nextUri + 1;
+ }
+ while (nextUri);
+ PR_Free(uriList);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetProgress(nsIMsgProgress **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mProgress;
+ NS_IF_ADDREF(*_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetMessageSend(nsIMsgSend **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mMsgSend;
+ NS_IF_ADDREF(*_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::SetMessageSend(nsIMsgSend* aMsgSend)
+{
+ mMsgSend = aMsgSend;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::ClearMessageSend()
+{
+ mMsgSend = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::SetCiteReference(nsString citeReference)
+{
+ mCiteReference = citeReference;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::SetSavedFolderURI(const char *folderURI)
+{
+ m_folderName = folderURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetSavedFolderURI(char ** folderURI)
+{
+ NS_ENSURE_ARG_POINTER(folderURI);
+ *folderURI = ToNewCString(m_folderName);
+ return (*folderURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetOriginalMsgURI(char ** originalMsgURI)
+{
+ NS_ENSURE_ARG_POINTER(originalMsgURI);
+ *originalMsgURI = ToNewCString(mOriginalMsgURI);
+ return (*originalMsgURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// THIS IS THE CLASS THAT IS THE STREAM CONSUMER OF THE HTML OUPUT
+// FROM LIBMIME. THIS IS FOR QUOTING
+////////////////////////////////////////////////////////////////////////////////////
+QuotingOutputStreamListener::~QuotingOutputStreamListener()
+{
+ if (mUnicodeConversionBuffer)
+ free(mUnicodeConversionBuffer);
+}
+
+QuotingOutputStreamListener::QuotingOutputStreamListener(const char * originalMsgURI,
+ nsIMsgDBHdr *originalMsgHdr,
+ bool quoteHeaders,
+ bool headersOnly,
+ nsIMsgIdentity *identity,
+ nsIMsgQuote* msgQuote,
+ bool charsetFixed,
+ bool quoteOriginal,
+ const nsACString& htmlToQuote)
+{
+ nsresult rv;
+ mQuoteHeaders = quoteHeaders;
+ mHeadersOnly = headersOnly;
+ mIdentity = identity;
+ mOrigMsgHdr = originalMsgHdr;
+ mUnicodeBufferCharacterLength = 0;
+ mUnicodeConversionBuffer = nullptr;
+ mQuoteOriginal = quoteOriginal;
+ mHtmlToQuote = htmlToQuote;
+ mQuote = msgQuote;
+ mCharsetFixed = charsetFixed;
+
+ if (!mHeadersOnly || !mHtmlToQuote.IsEmpty())
+ {
+ // Get header type, locale and strings from pref.
+ int32_t replyHeaderType;
+ nsAutoString replyHeaderLocale;
+ nsString replyHeaderAuthorWrote;
+ nsString replyHeaderOnDateAuthorWrote;
+ nsString replyHeaderAuthorWroteOnDate;
+ nsString replyHeaderOriginalmessage;
+ GetReplyHeaderInfo(&replyHeaderType,
+ replyHeaderLocale,
+ replyHeaderAuthorWrote,
+ replyHeaderOnDateAuthorWrote,
+ replyHeaderAuthorWroteOnDate,
+ replyHeaderOriginalmessage);
+
+ // For the built message body...
+ if (originalMsgHdr && !quoteHeaders)
+ {
+ // Setup the cite information....
+ nsCString myGetter;
+ if (NS_SUCCEEDED(originalMsgHdr->GetMessageId(getter_Copies(myGetter))))
+ {
+ if (!myGetter.IsEmpty())
+ {
+ nsAutoCString buf;
+ mCiteReference.AssignLiteral("mid:");
+ MsgEscapeURL(myGetter,
+ nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED,
+ buf);
+ mCiteReference.Append(NS_ConvertASCIItoUTF16(buf));
+ }
+ }
+
+ bool citingHeader; //Do we have a header needing to cite any info from original message?
+ bool headerDate; //Do we have a header needing to cite date/time from original message?
+ switch (replyHeaderType)
+ {
+ case 0: // No reply header at all (actually the "---- original message ----" string,
+ // which is kinda misleading. TODO: Should there be a "really no header" option?
+ mCitePrefix.Assign(replyHeaderOriginalmessage);
+ citingHeader = false;
+ headerDate = false;
+ break;
+
+ case 2: // Insert both the original author and date in the reply header (date followed by author)
+ mCitePrefix.Assign(replyHeaderOnDateAuthorWrote);
+ citingHeader = true;
+ headerDate = true;
+ break;
+
+ case 3: // Insert both the original author and date in the reply header (author followed by date)
+ mCitePrefix.Assign(replyHeaderAuthorWroteOnDate);
+ citingHeader = true;
+ headerDate = true;
+ break;
+
+ case 4: // TODO bug 107884: implement a more featureful user specified header
+ case 1:
+ default: // Default is to only show the author.
+ mCitePrefix.Assign(replyHeaderAuthorWrote);
+ citingHeader = true;
+ headerDate = false;
+ break;
+ }
+
+ if (citingHeader)
+ {
+ int32_t placeholderIndex = kNotFound;
+
+ if (headerDate)
+ {
+ nsCOMPtr<nsIDateTimeFormat> dateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ PRTime originalMsgDate;
+ rv = originalMsgHdr->GetDate(&originalMsgDate);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsILocale> locale;
+ nsCOMPtr<nsILocaleService> localeService(do_GetService(NS_LOCALESERVICE_CONTRACTID));
+
+ // Format date using "mailnews.reply_header_locale", if empty then use application default locale.
+ if (!replyHeaderLocale.IsEmpty())
+ rv = localeService->NewLocale(replyHeaderLocale, getter_AddRefs(locale));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoString citeDatePart;
+ if ((placeholderIndex = mCitePrefix.Find("#2")) != kNotFound)
+ {
+ rv = dateFormatter->FormatPRTime(locale,
+ kDateFormatShort,
+ kTimeFormatNone,
+ originalMsgDate,
+ citeDatePart);
+ if (NS_SUCCEEDED(rv))
+ mCitePrefix.Replace(placeholderIndex, 2, citeDatePart);
+ }
+ if ((placeholderIndex = mCitePrefix.Find("#3")) != kNotFound)
+ {
+ rv = dateFormatter->FormatPRTime(locale,
+ kDateFormatNone,
+ kTimeFormatNoSeconds,
+ originalMsgDate,
+ citeDatePart);
+ if (NS_SUCCEEDED(rv))
+ mCitePrefix.Replace(placeholderIndex, 2, citeDatePart);
+ }
+ }
+ }
+ }
+ }
+
+ if ((placeholderIndex = mCitePrefix.Find("#1")) != kNotFound)
+ {
+ nsAutoCString author;
+ rv = originalMsgHdr->GetAuthor(getter_Copies(author));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoString citeAuthor;
+ ExtractName(EncodedHeader(author), citeAuthor);
+ mCitePrefix.Replace(placeholderIndex, 2, citeAuthor);
+ }
+ }
+ }
+ }
+
+ // This should not happen, but just in case.
+ if (mCitePrefix.IsEmpty())
+ {
+ mCitePrefix.AppendLiteral("\n\n");
+ mCitePrefix.Append(replyHeaderOriginalmessage);
+ mCitePrefix.AppendLiteral("\n");
+ }
+ }
+}
+
+/**
+ * The formatflowed parameter directs if formatflowed should be used in the conversion.
+ * format=flowed (RFC 2646) is a way to represent flow in a plain text mail, without
+ * disturbing the plain text.
+ */
+nsresult
+QuotingOutputStreamListener::ConvertToPlainText(bool formatflowed,
+ bool delsp,
+ bool formatted,
+ bool disallowBreaks)
+{
+ nsresult rv = ConvertBufToPlainText(mMsgBody, formatflowed,
+ delsp,
+ formatted,
+ disallowBreaks);
+ NS_ENSURE_SUCCESS (rv, rv);
+ return ConvertBufToPlainText(mSignature, formatflowed,
+ delsp,
+ formatted,
+ disallowBreaks);
+}
+
+NS_IMETHODIMP QuotingOutputStreamListener::OnStartRequest(nsIRequest *request, nsISupports * /* ctxt */)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP QuotingOutputStreamListener::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
+{
+ nsresult rv = NS_OK;
+
+ if (!mHtmlToQuote.IsEmpty())
+ {
+ // If we had a selection in the original message to quote, we can add
+ // it now that we are done ignoring the original body of the message
+ mHeadersOnly = false;
+ rv = AppendToMsgBody(mHtmlToQuote);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj);
+ NS_ENSURE_TRUE(compose, NS_ERROR_NULL_POINTER);
+
+ MSG_ComposeType type;
+ compose->GetType(&type);
+
+ // Assign cite information if available...
+ if (!mCiteReference.IsEmpty())
+ compose->SetCiteReference(mCiteReference);
+
+ bool overrideReplyTo =
+ mozilla::Preferences::GetBool("mail.override_list_reply_to", true);
+
+ if (mHeaders && (type == nsIMsgCompType::Reply ||
+ type == nsIMsgCompType::ReplyAll ||
+ type == nsIMsgCompType::ReplyToList ||
+ type == nsIMsgCompType::ReplyToSender ||
+ type == nsIMsgCompType::ReplyToGroup ||
+ type == nsIMsgCompType::ReplyToSenderAndGroup) &&
+ mQuoteOriginal)
+ {
+ nsCOMPtr<nsIMsgCompFields> compFields;
+ compose->GetCompFields(getter_AddRefs(compFields));
+ if (compFields)
+ {
+ nsAutoString from;
+ nsAutoString to;
+ nsAutoString cc;
+ nsAutoString bcc;
+ nsAutoString replyTo;
+ nsAutoString mailReplyTo;
+ nsAutoString mailFollowupTo;
+ nsAutoString newgroups;
+ nsAutoString followUpTo;
+ nsAutoString messageId;
+ nsAutoString references;
+ nsAutoString listPost;
+
+ nsCString outCString; // Temp helper string.
+
+ bool needToRemoveDup = false;
+ if (!mMimeConverter)
+ {
+ mMimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCString charset;
+ compFields->GetCharacterSet(getter_Copies(charset));
+
+ if (!mCharsetFixed) {
+ // Get the charset from the channel where MIME left it.
+ if (mQuote) {
+ nsCOMPtr<nsIChannel> quoteChannel;
+ mQuote->GetQuoteChannel(getter_AddRefs(quoteChannel));
+ if (quoteChannel) {
+ quoteChannel->GetContentCharset(charset);
+ if (!charset.IsEmpty()) {
+ rv = fixCharset(charset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ compFields->SetCharacterSet(charset.get());
+ }
+ }
+ }
+ }
+
+ mHeaders->ExtractHeader(HEADER_FROM, true, outCString);
+ ConvertRawBytesToUTF16(outCString, charset.get(), from);
+
+ mHeaders->ExtractHeader(HEADER_TO, true, outCString);
+ ConvertRawBytesToUTF16(outCString, charset.get(), to);
+
+ mHeaders->ExtractHeader(HEADER_CC, true, outCString);
+ ConvertRawBytesToUTF16(outCString, charset.get(), cc);
+
+ mHeaders->ExtractHeader(HEADER_BCC, true, outCString);
+ ConvertRawBytesToUTF16(outCString, charset.get(), bcc);
+
+ mHeaders->ExtractHeader(HEADER_MAIL_FOLLOWUP_TO, true, outCString);
+ ConvertRawBytesToUTF16(outCString, charset.get(), mailFollowupTo);
+
+ mHeaders->ExtractHeader(HEADER_REPLY_TO, false, outCString);
+ ConvertRawBytesToUTF16(outCString, charset.get(), replyTo);
+
+ mHeaders->ExtractHeader(HEADER_MAIL_REPLY_TO, true, outCString);
+ ConvertRawBytesToUTF16(outCString, charset.get(), mailReplyTo);
+
+ mHeaders->ExtractHeader(HEADER_NEWSGROUPS, false, outCString);
+ if (!outCString.IsEmpty())
+ mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(),
+ false, true, newgroups);
+
+ mHeaders->ExtractHeader(HEADER_FOLLOWUP_TO, false, outCString);
+ if (!outCString.IsEmpty())
+ mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(),
+ false, true, followUpTo);
+
+ mHeaders->ExtractHeader(HEADER_MESSAGE_ID, false, outCString);
+ if (!outCString.IsEmpty())
+ mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(),
+ false, true, messageId);
+
+ mHeaders->ExtractHeader(HEADER_REFERENCES, false, outCString);
+ if (!outCString.IsEmpty())
+ mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(),
+ false, true, references);
+
+ mHeaders->ExtractHeader(HEADER_LIST_POST, true, outCString);
+ if (!outCString.IsEmpty())
+ mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(),
+ false, true, listPost);
+ if (!listPost.IsEmpty())
+ {
+ int32_t startPos = listPost.Find("<mailto:");
+ int32_t endPos = listPost.FindChar('>', startPos);
+ // Extract the e-mail address.
+ if (endPos > startPos)
+ {
+ const uint32_t mailtoLen = strlen("<mailto:");
+ listPost = Substring(listPost, startPos + mailtoLen, endPos - (startPos + mailtoLen));
+ }
+ }
+
+ nsCString fromEmailAddress;
+ ExtractEmail(EncodedHeaderW(from), fromEmailAddress);
+
+ nsTArray<nsCString> toEmailAddresses;
+ ExtractEmails(EncodedHeaderW(to), UTF16ArrayAdapter<>(toEmailAddresses));
+
+ nsTArray<nsCString> ccEmailAddresses;
+ ExtractEmails(EncodedHeaderW(cc), UTF16ArrayAdapter<>(ccEmailAddresses));
+
+ nsCOMPtr<nsIPrefBranch> prefs (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool replyToSelfCheckAll = false;
+ prefs->GetBoolPref("mailnews.reply_to_self_check_all_ident",
+ &replyToSelfCheckAll);
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIArray> identities;
+ nsCString accountKey;
+ mOrigMsgHdr->GetAccountKey(getter_Copies(accountKey));
+ if (replyToSelfCheckAll)
+ {
+ // Check all avaliable identities if the pref was set.
+ accountManager->GetAllIdentities(getter_AddRefs(identities));
+ }
+ else if (!accountKey.IsEmpty())
+ {
+ // Check headers to see which account the message came in from
+ // (only works for pop3).
+ nsCOMPtr<nsIMsgAccount> account;
+ accountManager->GetAccount(accountKey, getter_AddRefs(account));
+
+ if (account)
+ account->GetIdentities(getter_AddRefs(identities));
+ }
+ else
+ {
+ // Check identities only for the server of the folder that the message
+ // is in.
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+ rv = mOrigMsgHdr->GetFolder(getter_AddRefs(msgFolder));
+
+ if (NS_SUCCEEDED(rv) && msgFolder){
+ nsCOMPtr<nsIMsgIncomingServer> nsIMsgIncomingServer;
+ rv = msgFolder->GetServer(getter_AddRefs(nsIMsgIncomingServer));
+
+ if (NS_SUCCEEDED(rv) && nsIMsgIncomingServer)
+ accountManager->GetIdentitiesForServer(nsIMsgIncomingServer, getter_AddRefs(identities));
+ }
+ }
+
+ bool isReplyToSelf = false;
+ nsCOMPtr<nsIMsgIdentity> selfIdentity;
+ if (identities)
+ {
+ // Go through the identities to see if any of them is the author of
+ // the email.
+ nsCOMPtr<nsIMsgIdentity> lookupIdentity;
+
+ uint32_t count = 0;
+ identities->GetLength(&count);
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ lookupIdentity = do_QueryElementAt(identities, i, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ selfIdentity = lookupIdentity;
+
+ nsCString curIdentityEmail;
+ lookupIdentity->GetEmail(curIdentityEmail);
+
+ // See if it's a reply to own message, but not a reply between identities.
+ if (curIdentityEmail.Equals(fromEmailAddress))
+ {
+ isReplyToSelf = true;
+ // For a true reply-to-self, none of your identities are normally in
+ // To or Cc. We need to avoid doing a reply-to-self for people that
+ // have multiple identities set and sometimes *uses* the other
+ // identity and sometimes *mails* the other identity.
+ // E.g. husband+wife or own-email+company-role-mail.
+ for (uint32_t j = 0; j < count; j++)
+ {
+ nsCOMPtr<nsIMsgIdentity> lookupIdentity2;
+ rv = identities->QueryElementAt(j, NS_GET_IID(nsIMsgIdentity),
+ getter_AddRefs(lookupIdentity2));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCString curIdentityEmail2;
+ lookupIdentity2->GetEmail(curIdentityEmail2);
+ if (toEmailAddresses.Contains(curIdentityEmail2))
+ {
+ // However, "From:me To:me" should be treated as
+ // reply-to-self if we have a Bcc. If we don't have a Bcc we
+ // might have the case of a generated mail of the style
+ // "From:me To:me Reply-To:customer". Then we need to to do a
+ // normal reply to the customer.
+ isReplyToSelf = !bcc.IsEmpty(); // true if bcc is set
+ break;
+ }
+ else if (ccEmailAddresses.Contains(curIdentityEmail2))
+ {
+ // If you auto-Cc yourself your email would be in Cc - but we
+ // can't detect why it is in Cc so lets just treat it like a
+ // normal reply.
+ isReplyToSelf = false;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ if (type == nsIMsgCompType::ReplyToSender || type == nsIMsgCompType::Reply)
+ {
+ if (isReplyToSelf)
+ {
+ // Cast to concrete class. We *only* what to change m_identity, not
+ // all the things compose->SetIdentity would do.
+ nsMsgCompose* _compose = static_cast<nsMsgCompose*>(compose.get());
+ _compose->m_identity = selfIdentity;
+ compFields->SetFrom(from);
+ compFields->SetTo(to);
+ compFields->SetReplyTo(replyTo);
+ }
+ else if (!mailReplyTo.IsEmpty())
+ {
+ // handle Mail-Reply-To (http://cr.yp.to/proto/replyto.html)
+ compFields->SetTo(mailReplyTo);
+ needToRemoveDup = true;
+ }
+ else if (!replyTo.IsEmpty())
+ {
+ // default reply behaviour then
+
+ if (overrideReplyTo &&
+ !listPost.IsEmpty() && replyTo.Find(listPost) != kNotFound)
+ {
+ // Reply-To munging in this list post. Reply to From instead,
+ // as the user can choose Reply List if that's what he wants.
+ compFields->SetTo(from);
+ }
+ else
+ {
+ compFields->SetTo(replyTo);
+ }
+ needToRemoveDup = true;
+ }
+ else {
+ compFields->SetTo(from);
+ }
+ }
+ else if (type == nsIMsgCompType::ReplyAll)
+ {
+ if (isReplyToSelf)
+ {
+ // Cast to concrete class. We *only* what to change m_identity, not
+ // all the things compose->SetIdentity would do.
+ nsMsgCompose* _compose = static_cast<nsMsgCompose*>(compose.get());
+ _compose->m_identity = selfIdentity;
+ compFields->SetFrom(from);
+ compFields->SetTo(to);
+ compFields->SetCc(cc);
+ // In case it's a reply to self, but it's not the actual source of the
+ // sent message, then we won't know the Bcc header. So set it only if
+ // it's not empty. If you have auto-bcc and removed the auto-bcc for
+ // the original mail, you will have to do it manually for this reply
+ // too.
+ if (!bcc.IsEmpty())
+ compFields->SetBcc(bcc);
+ compFields->SetReplyTo(replyTo);
+ needToRemoveDup = true;
+ }
+ else if (mailFollowupTo.IsEmpty()) {
+ // default reply-all behaviour then
+
+ nsAutoString allTo;
+ if (!replyTo.IsEmpty())
+ {
+ allTo.Assign(replyTo);
+ needToRemoveDup = true;
+ if (overrideReplyTo &&
+ !listPost.IsEmpty() && replyTo.Find(listPost) != kNotFound)
+ {
+ // Reply-To munging in this list. Add From to recipients, it's the
+ // lesser evil...
+ allTo.AppendLiteral(", ");
+ allTo.Append(from);
+ }
+ }
+ else
+ {
+ allTo.Assign(from);
+ }
+
+ allTo.AppendLiteral(", ");
+ allTo.Append(to);
+ compFields->SetTo(allTo);
+
+ nsAutoString allCc;
+ compFields->GetCc(allCc); // auto-cc
+ if (!allCc.IsEmpty())
+ allCc.AppendLiteral(", ");
+ allCc.Append(cc);
+ compFields->SetCc(allCc);
+
+ needToRemoveDup = true;
+ }
+ else
+ {
+ // Handle Mail-Followup-To (http://cr.yp.to/proto/replyto.html)
+ compFields->SetTo(mailFollowupTo);
+ needToRemoveDup = true; // To remove possible self from To.
+
+ // If Cc is set a this point it's auto-Ccs, so we'll just keep those.
+ }
+ }
+ else if (type == nsIMsgCompType::ReplyToList)
+ {
+ compFields->SetTo(listPost);
+ }
+
+ if (!newgroups.IsEmpty())
+ {
+ if ((type != nsIMsgCompType::Reply) && (type != nsIMsgCompType::ReplyToSender))
+ compFields->SetNewsgroups(newgroups);
+ if (type == nsIMsgCompType::ReplyToGroup)
+ compFields->SetTo(EmptyString());
+ }
+
+ if (!followUpTo.IsEmpty())
+ {
+ // Handle "followup-to: poster" magic keyword here
+ if (followUpTo.EqualsLiteral("poster"))
+ {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ nsCOMPtr<nsIPrompt> prompt;
+ compose->GetDomWindow(getter_AddRefs(domWindow));
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> composeWindow = nsPIDOMWindowOuter::From(domWindow);
+ if (composeWindow)
+ composeWindow->GetPrompter(getter_AddRefs(prompt));
+ nsMsgDisplayMessageByName(prompt, u"followupToSenderMessage");
+
+ if (!replyTo.IsEmpty())
+ {
+ compFields->SetTo(replyTo);
+ }
+ else
+ {
+ // If reply-to is empty, use the From header to fetch the original
+ // sender's email.
+ compFields->SetTo(from);
+ }
+
+ // Clear the newsgroup: header field, because followup-to: poster
+ // only follows up to the original sender
+ if (!newgroups.IsEmpty())
+ compFields->SetNewsgroups(EmptyString());
+ }
+ else // Process "followup-to: newsgroup-content" here
+ {
+ if (type != nsIMsgCompType::ReplyToSender)
+ compFields->SetNewsgroups(followUpTo);
+ if (type == nsIMsgCompType::Reply)
+ {
+ compFields->SetTo(EmptyString());
+ }
+ }
+ }
+
+ if (!references.IsEmpty())
+ references.Append(char16_t(' '));
+ references += messageId;
+ compFields->SetReferences(NS_LossyConvertUTF16toASCII(references).get());
+
+ nsAutoCString resultStr;
+
+ // Cast interface to concrete class that has direct field getters etc.
+ nsMsgCompFields* _compFields = static_cast<nsMsgCompFields*>(compFields.get());
+
+ // Remove duplicate addresses between To && Cc.
+ if (needToRemoveDup)
+ {
+ nsCString addressesToRemoveFromCc;
+ if (mIdentity)
+ {
+ bool removeMyEmailInCc = true;
+ nsCString myEmail;
+ mIdentity->GetEmail(myEmail);
+
+ // Remove my own address from To, unless it's a reply to self.
+ if (!isReplyToSelf) {
+ RemoveDuplicateAddresses(nsDependentCString(_compFields->GetTo()),
+ myEmail, resultStr);
+ _compFields->SetTo(resultStr.get());
+ }
+ addressesToRemoveFromCc.Assign(_compFields->GetTo());
+
+ // Remove own address from CC unless we want it in there
+ // through the automatic-CC-to-self (see bug 584962). There are
+ // three cases:
+ // - user has no automatic CC
+ // - user has automatic CC but own email is not in it
+ // - user has automatic CC and own email in it
+ // Only in the last case do we want our own email address to stay
+ // in the CC list.
+ bool automaticCc;
+ mIdentity->GetDoCc(&automaticCc);
+ if (automaticCc)
+ {
+ nsCString autoCcList;
+ mIdentity->GetDoCcList(autoCcList);
+ nsTArray<nsCString> autoCcEmailAddresses;
+ ExtractEmails(EncodedHeader(autoCcList),
+ UTF16ArrayAdapter<>(autoCcEmailAddresses));
+ if (autoCcEmailAddresses.Contains(myEmail))
+ {
+ removeMyEmailInCc = false;
+ }
+ }
+
+ if (removeMyEmailInCc)
+ {
+ addressesToRemoveFromCc.AppendLiteral(", ");
+ addressesToRemoveFromCc.Append(myEmail);
+ }
+ }
+ RemoveDuplicateAddresses(nsDependentCString(_compFields->GetCc()),
+ addressesToRemoveFromCc, resultStr);
+ _compFields->SetCc(resultStr.get());
+ if (_compFields->GetBcc())
+ {
+ // Remove addresses already in Cc from Bcc.
+ RemoveDuplicateAddresses(nsDependentCString(_compFields->GetBcc()),
+ nsDependentCString(_compFields->GetCc()),
+ resultStr);
+ if (!resultStr.IsEmpty())
+ {
+ // Remove addresses already in To from Bcc.
+ RemoveDuplicateAddresses(resultStr,
+ nsDependentCString(_compFields->GetTo()),
+ resultStr);
+ }
+ _compFields->SetBcc(resultStr.get());
+ }
+ }
+ }
+ }
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ nsCOMPtr<nsIMsgComposeService> composeService (do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID));
+ composeService->TimeStamp("Done with MIME. Now we're updating the UI elements", false);
+#endif
+
+ if (mQuoteOriginal)
+ compose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeFieldsReady, NS_OK);
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ composeService->TimeStamp("Addressing widget, window title and focus are now set, time to insert the body", false);
+#endif
+
+ if (! mHeadersOnly)
+ mMsgBody.AppendLiteral("</html>");
+
+ // Now we have an HTML representation of the quoted message.
+ // If we are in plain text mode, we need to convert this to plain
+ // text before we try to insert it into the editor. If we don't, we
+ // just get lots of HTML text in the message...not good.
+ //
+ // XXX not m_composeHTML? /BenB
+ bool composeHTML = true;
+ compose->GetComposeHTML(&composeHTML);
+ if (!composeHTML)
+ {
+ // Downsampling.
+
+ // In plain text quotes we always allow line breaking to not end up with
+ // long lines. The quote is inserted into a span with style
+ // "white-space: pre;" which isn't be wrapped.
+ // Update: Bug 387687 changed this to "white-space: pre-wrap;".
+ // Note that the body of the plain text message is wrapped since it uses
+ // "white-space: pre-wrap; width: 72ch;".
+ // Look at it in the DOM Inspector to see it.
+ //
+ // If we're using format flowed, we need to pass it so the encoder
+ // can add a space at the end.
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ bool flowed = false;
+ if (pPrefBranch) {
+ pPrefBranch->GetBoolPref("mailnews.send_plaintext_flowed", &flowed);
+ }
+
+ rv = ConvertToPlainText(flowed,
+ false, // delsp makes no sense in this context
+ true, // formatted
+ false); // allow line breaks
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ compose->ProcessSignature(mIdentity, true, &mSignature);
+
+ nsCOMPtr<nsIEditor> editor;
+ if (NS_SUCCEEDED(compose->GetEditor(getter_AddRefs(editor))) && editor)
+ {
+ if (mQuoteOriginal)
+ compose->ConvertAndLoadComposeWindow(mCitePrefix,
+ mMsgBody, mSignature,
+ true, composeHTML);
+ else
+ InsertToCompose(editor, composeHTML);
+ }
+
+ if (mQuoteOriginal)
+ compose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeBodyReady, NS_OK);
+ return rv;
+}
+
+NS_IMETHODIMP QuotingOutputStreamListener::OnDataAvailable(nsIRequest *request,
+ nsISupports *ctxt, nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG(inStr);
+
+ if (mHeadersOnly)
+ return rv;
+
+ char *newBuf = (char *)PR_Malloc(count + 1);
+ if (!newBuf)
+ return NS_ERROR_FAILURE;
+
+ uint32_t numWritten = 0;
+ rv = inStr->Read(newBuf, count, &numWritten);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+ rv = NS_OK;
+ newBuf[numWritten] = '\0';
+ if (NS_SUCCEEDED(rv) && numWritten > 0)
+ {
+ rv = AppendToMsgBody(nsDependentCString(newBuf, numWritten));
+ }
+
+ PR_FREEIF(newBuf);
+ return rv;
+}
+
+NS_IMETHODIMP QuotingOutputStreamListener::AppendToMsgBody(const nsCString &inStr)
+{
+ nsresult rv = NS_OK;
+
+ if (!inStr.IsEmpty())
+ {
+ // Create unicode decoder.
+ if (!mUnicodeDecoder)
+ {
+ nsCOMPtr<nsICharsetConverterManager> ccm =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = ccm->GetUnicodeDecoderRaw("UTF-8",
+ getter_AddRefs(mUnicodeDecoder));
+ }
+ }
+
+ if (NS_SUCCEEDED(rv))
+ {
+ int32_t unicharLength;
+ int32_t inputLength = inStr.Length();
+ rv = mUnicodeDecoder->GetMaxLength(inStr.get(), inStr.Length(), &unicharLength);
+ if (NS_SUCCEEDED(rv))
+ {
+ // Use this local buffer if possible.
+ const int32_t kLocalBufSize = 4096;
+ char16_t localBuf[kLocalBufSize];
+ char16_t *unichars = localBuf;
+
+ if (unicharLength > kLocalBufSize)
+ {
+ // Otherwise, use the buffer of the class.
+ if (!mUnicodeConversionBuffer ||
+ unicharLength > mUnicodeBufferCharacterLength)
+ {
+ if (mUnicodeConversionBuffer)
+ free(mUnicodeConversionBuffer);
+ mUnicodeConversionBuffer = (char16_t *) moz_xmalloc(unicharLength * sizeof(char16_t));
+ if (!mUnicodeConversionBuffer)
+ {
+ mUnicodeBufferCharacterLength = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mUnicodeBufferCharacterLength = unicharLength;
+ }
+ unichars = mUnicodeConversionBuffer;
+ }
+
+ int32_t consumedInputLength = 0;
+ int32_t originalInputLength = inputLength;
+ const char *inputBuffer = inStr.get();
+ int32_t convertedOutputLength = 0;
+ int32_t outputBufferLength = unicharLength;
+ char16_t *originalOutputBuffer = unichars;
+ do
+ {
+ rv = mUnicodeDecoder->Convert(inputBuffer, &inputLength, unichars, &unicharLength);
+ if (NS_SUCCEEDED(rv))
+ {
+ convertedOutputLength += unicharLength;
+ break;
+ }
+
+ // if we failed, we consume one byte, replace it with a question mark
+ // and try the conversion again.
+ unichars += unicharLength;
+ *unichars = (char16_t)'?';
+ unichars++;
+ unicharLength++;
+
+ mUnicodeDecoder->Reset();
+
+ inputBuffer += ++inputLength;
+ consumedInputLength += inputLength;
+ inputLength = originalInputLength - consumedInputLength; // update input length to convert
+ convertedOutputLength += unicharLength;
+ unicharLength = outputBufferLength - unicharLength; // update output length
+
+ } while (NS_FAILED(rv) &&
+ (originalInputLength > consumedInputLength) &&
+ (outputBufferLength > convertedOutputLength));
+
+ if (convertedOutputLength > 0)
+ mMsgBody.Append(originalOutputBuffer, convertedOutputLength);
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+QuotingOutputStreamListener::SetComposeObj(nsIMsgCompose *obj)
+{
+ mWeakComposeObj = do_GetWeakReference(obj);
+ return NS_OK;
+}
+
+nsresult
+QuotingOutputStreamListener::SetMimeHeaders(nsIMimeHeaders * headers)
+{
+ mHeaders = headers;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuotingOutputStreamListener::InsertToCompose(nsIEditor *aEditor,
+ bool aHTMLEditor)
+{
+ // First, get the nsIEditor interface for future use
+ nsCOMPtr<nsIDOMNode> nodeInserted;
+
+ TranslateLineEnding(mMsgBody);
+
+ // Now, insert it into the editor...
+ if (aEditor)
+ aEditor->EnableUndo(true);
+
+ nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj);
+ if (!mMsgBody.IsEmpty() && compose)
+ {
+ compose->SetInsertingQuotedContent(true);
+ if (!mCitePrefix.IsEmpty())
+ {
+ if (!aHTMLEditor)
+ mCitePrefix.AppendLiteral("\n");
+ nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(aEditor));
+ if (textEditor)
+ textEditor->InsertText(mCitePrefix);
+ }
+
+ nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(aEditor));
+ if (mailEditor)
+ {
+ if (aHTMLEditor) {
+ nsAutoString body(mMsgBody);
+ remove_plaintext_tag(body);
+ mailEditor->InsertAsCitedQuotation(body, EmptyString(), true,
+ getter_AddRefs(nodeInserted));
+ } else {
+ mailEditor->InsertAsQuotation(mMsgBody, getter_AddRefs(nodeInserted));
+ }
+ }
+ compose->SetInsertingQuotedContent(false);
+ }
+
+ if (aEditor)
+ {
+ nsCOMPtr<nsIPlaintextEditor> textEditor = do_QueryInterface(aEditor);
+ if (textEditor)
+ {
+ nsCOMPtr<nsISelection> selection;
+ nsCOMPtr<nsIDOMNode> parent;
+ int32_t offset;
+ nsresult rv;
+
+ // get parent and offset of mailcite
+ rv = GetNodeLocation(nodeInserted, address_of(parent), &offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get selection
+ aEditor->GetSelection(getter_AddRefs(selection));
+ if (selection)
+ {
+ // place selection after mailcite
+ selection->Collapse(parent, offset+1);
+ // insert a break at current selection
+ textEditor->InsertLineBreak();
+ selection->Collapse(parent, offset+1);
+ }
+ nsCOMPtr<nsISelectionController> selCon;
+ aEditor->GetSelectionController(getter_AddRefs(selCon));
+
+ if (selCon)
+ // After ScrollSelectionIntoView(), the pending notifications might be
+ // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+ selCon->ScrollSelectionIntoView(
+ nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_ANCHOR_REGION,
+ true);
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Returns true if the domain is a match for the given the domain list.
+ * Subdomains are also considered to match.
+ * @param aDomain - the domain name to check
+ * @param aDomainList - a comman separated string of domain names
+ */
+bool IsInDomainList(const nsAString &aDomain, const nsAString &aDomainList)
+{
+ if (aDomain.IsEmpty() || aDomainList.IsEmpty())
+ return false;
+
+ // Check plain text domains.
+ int32_t left = 0;
+ int32_t right = 0;
+ while (right != (int32_t)aDomainList.Length())
+ {
+ right = aDomainList.FindChar(',', left);
+ if (right == kNotFound)
+ right = aDomainList.Length();
+ nsDependentSubstring domain = Substring(aDomainList, left, right);
+
+ if (aDomain.Equals(domain, nsCaseInsensitiveStringComparator()))
+ return true;
+
+ nsAutoString dotDomain;
+ dotDomain.Assign(NS_LITERAL_STRING("."));
+ dotDomain.Append(domain);
+ if (StringEndsWith(aDomain, dotDomain, nsCaseInsensitiveStringComparator()))
+ return true;
+
+ left = right + 1;
+ }
+ return false;
+}
+
+NS_IMPL_ISUPPORTS(QuotingOutputStreamListener,
+ nsIMsgQuotingOutputStreamListener,
+ nsIRequestObserver,
+ nsIStreamListener)
+
+////////////////////////////////////////////////////////////////////////////////////
+// END OF QUOTING LISTENER
+////////////////////////////////////////////////////////////////////////////////////
+
+/* attribute MSG_ComposeType type; */
+NS_IMETHODIMP nsMsgCompose::SetType(MSG_ComposeType aType)
+{
+
+ mType = aType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetType(MSG_ComposeType *aType)
+{
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aType = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::QuoteMessage(const char *msgURI)
+{
+ NS_ENSURE_ARG_POINTER(msgURI);
+
+ nsresult rv;
+ mQuotingToFollow = false;
+
+ // Create a mime parser (nsIStreamConverter)!
+ mQuote = do_CreateInstance(NS_MSGQUOTE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(msgURI, getter_AddRefs(msgHdr));
+
+ // Create the consumer output stream.. this will receive all the HTML from libmime
+ mQuoteStreamListener =
+ new QuotingOutputStreamListener(msgURI,
+ msgHdr,
+ false,
+ !mHtmlToQuote.IsEmpty(),
+ m_identity,
+ mQuote,
+ mCharsetOverride || mAnswerDefaultCharset,
+ false,
+ mHtmlToQuote);
+
+ if (!mQuoteStreamListener)
+ return NS_ERROR_FAILURE;
+ NS_ADDREF(mQuoteStreamListener);
+
+ mQuoteStreamListener->SetComposeObj(this);
+
+ rv = mQuote->QuoteMessage(msgURI, false, mQuoteStreamListener,
+ mCharsetOverride ? m_compFields->GetCharacterSet() : "",
+ false, msgHdr);
+ return rv;
+}
+
+nsresult
+nsMsgCompose::QuoteOriginalMessage() // New template
+{
+ nsresult rv;
+
+ mQuotingToFollow = false;
+
+ // Create a mime parser (nsIStreamConverter)!
+ mQuote = do_CreateInstance(NS_MSGQUOTE_CONTRACTID, &rv);
+ if (NS_FAILED(rv) || !mQuote)
+ return NS_ERROR_FAILURE;
+
+ bool bAutoQuote = true;
+ m_identity->GetAutoQuote(&bAutoQuote);
+
+ nsCOMPtr <nsIMsgDBHdr> originalMsgHdr = mOrigMsgHdr;
+ if (!originalMsgHdr)
+ {
+ rv = GetMsgDBHdrFromURI(mOriginalMsgURI.get(), getter_AddRefs(originalMsgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool fileUrl = StringBeginsWith(mOriginalMsgURI, NS_LITERAL_CSTRING("file:"));
+ if (fileUrl)
+ {
+ mOriginalMsgURI.Replace(0, 5, NS_LITERAL_CSTRING("mailbox:"));
+ mOriginalMsgURI.AppendLiteral("?number=0");
+ }
+
+ // Create the consumer output stream.. this will receive all the HTML from libmime
+ mQuoteStreamListener =
+ new QuotingOutputStreamListener(mOriginalMsgURI.get(),
+ originalMsgHdr,
+ mWhatHolder != 1,
+ !bAutoQuote || !mHtmlToQuote.IsEmpty(),
+ m_identity,
+ mQuote,
+ mCharsetOverride || mAnswerDefaultCharset,
+ true,
+ mHtmlToQuote);
+
+ if (!mQuoteStreamListener)
+ return NS_ERROR_FAILURE;
+ NS_ADDREF(mQuoteStreamListener);
+
+ mQuoteStreamListener->SetComposeObj(this);
+
+ rv = mQuote->QuoteMessage(mOriginalMsgURI.get(), mWhatHolder != 1, mQuoteStreamListener,
+ mCharsetOverride ? mQuoteCharset.get() : "",
+ !bAutoQuote, originalMsgHdr);
+ return rv;
+}
+
+//CleanUpRecipient will remove un-necessary "<>" when a recipient as an address without name
+void nsMsgCompose::CleanUpRecipients(nsString& recipients)
+{
+ uint16_t i;
+ bool startANewRecipient = true;
+ bool removeBracket = false;
+ nsAutoString newRecipient;
+ char16_t aChar;
+
+ for (i = 0; i < recipients.Length(); i ++)
+ {
+ aChar = recipients[i];
+ switch (aChar)
+ {
+ case '<' :
+ if (startANewRecipient)
+ removeBracket = true;
+ else
+ newRecipient += aChar;
+ startANewRecipient = false;
+ break;
+
+ case '>' :
+ if (removeBracket)
+ removeBracket = false;
+ else
+ newRecipient += aChar;
+ break;
+
+ case ' ' :
+ newRecipient += aChar;
+ break;
+
+ case ',' :
+ newRecipient += aChar;
+ startANewRecipient = true;
+ removeBracket = false;
+ break;
+
+ default :
+ newRecipient += aChar;
+ startANewRecipient = false;
+ break;
+ }
+ }
+ recipients = newRecipient;
+}
+
+NS_IMETHODIMP nsMsgCompose::RememberQueuedDisposition()
+{
+ // need to find the msg hdr in the saved folder and then set a property on
+ // the header that we then look at when we actually send the message.
+ nsresult rv;
+ nsAutoCString dispositionSetting;
+
+ if (mType == nsIMsgCompType::Reply ||
+ mType == nsIMsgCompType::ReplyAll ||
+ mType == nsIMsgCompType::ReplyToList ||
+ mType == nsIMsgCompType::ReplyToGroup ||
+ mType == nsIMsgCompType::ReplyToSender ||
+ mType == nsIMsgCompType::ReplyToSenderAndGroup)
+ {
+ dispositionSetting.AssignLiteral("replied");
+ }
+ else if (mType == nsIMsgCompType::ForwardAsAttachment ||
+ mType == nsIMsgCompType::ForwardInline)
+ {
+ dispositionSetting.AssignLiteral("forwarded");
+ }
+ else if (mType == nsIMsgCompType::Draft)
+ {
+ nsAutoCString curDraftIdURL;
+ rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!curDraftIdURL.IsEmpty()) {
+ nsCOMPtr <nsIMsgDBHdr> draftHdr;
+ rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(draftHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ draftHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, getter_Copies(dispositionSetting));
+ }
+ }
+
+ nsMsgKey msgKey;
+ if (mMsgSend)
+ {
+ mMsgSend->GetMessageKey(&msgKey);
+ nsAutoCString msgUri(m_folderName);
+ nsCString identityKey;
+
+ m_identity->GetKey(identityKey);
+
+ int32_t insertIndex = StringBeginsWith(msgUri, NS_LITERAL_CSTRING("mailbox")) ? 7 : 4;
+ msgUri.Insert("-message", insertIndex); // "mailbox/imap: -> "mailbox/imap-message:"
+ msgUri.Append('#');
+ msgUri.AppendInt(msgKey);
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(msgUri.get(), getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t pseudoHdrProp = 0;
+ msgHdr->GetUint32Property("pseudoHdr", &pseudoHdrProp);
+ if (pseudoHdrProp)
+ {
+ // Use SetAttributeOnPendingHdr for IMAP pseudo headers, as those
+ // will get deleted (and properties set using SetStringProperty lost.)
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ rv = folder->GetMsgDatabase(getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageId;
+ mMsgSend->GetMessageId(messageId);
+ msgHdr->SetMessageId(messageId.get());
+ if (!mOriginalMsgURI.IsEmpty())
+ {
+ msgDB->SetAttributeOnPendingHdr(msgHdr, ORIG_URI_PROPERTY, mOriginalMsgURI.get());
+ if (!dispositionSetting.IsEmpty())
+ msgDB->SetAttributeOnPendingHdr(msgHdr, QUEUED_DISPOSITION_PROPERTY,
+ dispositionSetting.get());
+ }
+ msgDB->SetAttributeOnPendingHdr(msgHdr, HEADER_X_MOZILLA_IDENTITY_KEY, identityKey.get());
+ }
+ else if (msgHdr)
+ {
+ if (!mOriginalMsgURI.IsEmpty())
+ {
+ msgHdr->SetStringProperty(ORIG_URI_PROPERTY, mOriginalMsgURI.get());
+ if (!dispositionSetting.IsEmpty())
+ msgHdr->SetStringProperty(QUEUED_DISPOSITION_PROPERTY, dispositionSetting.get());
+ }
+ msgHdr->SetStringProperty(HEADER_X_MOZILLA_IDENTITY_KEY, identityKey.get());
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::ProcessReplyFlags()
+{
+ nsresult rv;
+ // check to see if we were doing a reply or a forward, if we were, set the answered field flag on the message folder
+ // for this URI.
+ if (mType == nsIMsgCompType::Reply ||
+ mType == nsIMsgCompType::ReplyAll ||
+ mType == nsIMsgCompType::ReplyToList ||
+ mType == nsIMsgCompType::ReplyToGroup ||
+ mType == nsIMsgCompType::ReplyToSender ||
+ mType == nsIMsgCompType::ReplyToSenderAndGroup ||
+ mType == nsIMsgCompType::ForwardAsAttachment ||
+ mType == nsIMsgCompType::ForwardInline ||
+ mDraftDisposition != nsIMsgFolder::nsMsgDispositionState_None)
+ {
+ if (!mOriginalMsgURI.IsEmpty())
+ {
+ nsCString msgUri (mOriginalMsgURI);
+ char *newStr = msgUri.BeginWriting();
+ char *uri;
+ while (nullptr != (uri = NS_strtok(",", &newStr)))
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(uri, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (msgHdr)
+ {
+ // get the folder for the message resource
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ msgHdr->GetFolder(getter_AddRefs(msgFolder));
+ if (msgFolder)
+ {
+ // If it's a draft with disposition, default to replied, otherwise,
+ // check if it's a forward.
+ nsMsgDispositionState dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Replied;
+ if (mDraftDisposition != nsIMsgFolder::nsMsgDispositionState_None)
+ dispositionSetting = mDraftDisposition;
+ else if (mType == nsIMsgCompType::ForwardAsAttachment ||
+ mType == nsIMsgCompType::ForwardInline)
+ dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Forwarded;
+
+ msgFolder->AddMessageDispositionState(msgHdr, dispositionSetting);
+ if (mType != nsIMsgCompType::ForwardAsAttachment)
+ break; // just safeguard
+ }
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgCompose::OnStartSending(const char *aMsgID, uint32_t aMsgSize)
+{
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore())
+ {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnStartSending(aMsgID, aMsgSize);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::OnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax)
+{
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore())
+ {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnProgress(aMsgID, aProgress, aProgressMax);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::OnStatus(const char *aMsgID, const char16_t *aMsg)
+{
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore())
+ {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnStatus(aMsgID, aMsg);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::OnStopSending(const char *aMsgID, nsresult aStatus, const char16_t *aMsg,
+ nsIFile *returnFile)
+{
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore())
+ {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::OnSendNotPerformed(const char *aMsgID, nsresult aStatus)
+{
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore())
+ {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnSendNotPerformed(aMsgID, aStatus);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::OnGetDraftFolderURI(const char *aFolderURI)
+{
+ m_folderName = aFolderURI;
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore())
+ {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnGetDraftFolderURI(aFolderURI);
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for both the send operation and the copy operation.
+// We have to create this class to listen for message send completion and deal with
+// failures in both send and copy operations
+////////////////////////////////////////////////////////////////////////////////////
+NS_IMPL_ADDREF(nsMsgComposeSendListener)
+NS_IMPL_RELEASE(nsMsgComposeSendListener)
+
+/*
+NS_IMPL_QUERY_INTERFACE(nsMsgComposeSendListener,
+ nsIMsgComposeSendListener,
+ nsIMsgSendListener,
+ nsIMsgCopyServiceListener,
+ nsIWebProgressListener)
+*/
+NS_INTERFACE_MAP_BEGIN(nsMsgComposeSendListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgComposeSendListener)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgComposeSendListener)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgSendListener)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgCopyServiceListener)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+NS_INTERFACE_MAP_END
+
+
+nsMsgComposeSendListener::nsMsgComposeSendListener(void)
+{
+ mDeliverMode = 0;
+}
+
+nsMsgComposeSendListener::~nsMsgComposeSendListener(void)
+{
+}
+
+NS_IMETHODIMP nsMsgComposeSendListener::SetMsgCompose(nsIMsgCompose *obj)
+{
+ mWeakComposeObj = do_GetWeakReference(obj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSendListener::SetDeliverMode(MSG_DeliverMode deliverMode)
+{
+ mDeliverMode = deliverMode;
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeSendListener::OnStartSending(const char *aMsgID, uint32_t aMsgSize)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnStartSending(aMsgID, aMsgSize);
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeSendListener::OnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnProgress(aMsgID, aProgress, aProgressMax);
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeSendListener::OnStatus(const char *aMsgID, const char16_t *aMsg)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnStatus(aMsgID, aMsg);
+ return NS_OK;
+}
+
+nsresult nsMsgComposeSendListener::OnSendNotPerformed(const char *aMsgID, nsresult aStatus)
+{
+ // since OnSendNotPerformed is called in the case where the user aborts the operation
+ // by closing the compose window, we need not do the stuff required
+ // for closing the windows. However we would need to do the other operations as below.
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv);
+ if (msgCompose)
+ msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, aStatus);
+
+ nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnSendNotPerformed(aMsgID, aStatus);
+
+ return rv;
+}
+
+nsresult nsMsgComposeSendListener::OnStopSending(const char *aMsgID, nsresult aStatus,
+ const char16_t *aMsg, nsIFile *returnFile)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv);
+ if (msgCompose)
+ {
+ nsCOMPtr<nsIMsgProgress> progress;
+ msgCompose->GetProgress(getter_AddRefs(progress));
+
+ if (NS_SUCCEEDED(aStatus))
+ {
+ nsCOMPtr<nsIMsgCompFields> compFields;
+ msgCompose->GetCompFields(getter_AddRefs(compFields));
+
+ // only process the reply flags if we successfully sent the message
+ msgCompose->ProcessReplyFlags();
+
+ // See if there is a composer window
+ bool hasDomWindow = true;
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ rv = msgCompose->GetDomWindow(getter_AddRefs(domWindow));
+ if (NS_FAILED(rv) || !domWindow)
+ hasDomWindow = false;
+
+ // Close the window ONLY if we are not going to do a save operation
+ nsAutoString fieldsFCC;
+ if (NS_SUCCEEDED(compFields->GetFcc(fieldsFCC)))
+ {
+ if (!fieldsFCC.IsEmpty())
+ {
+ if (fieldsFCC.LowerCaseEqualsLiteral("nocopy://"))
+ {
+ msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, NS_OK);
+ if (progress)
+ {
+ progress->UnregisterListener(this);
+ progress->CloseProgressDialog(false);
+ }
+ if (hasDomWindow)
+ msgCompose->CloseWindow();
+ }
+ }
+ }
+ else
+ {
+ msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, NS_OK);
+ if (progress)
+ {
+ progress->UnregisterListener(this);
+ progress->CloseProgressDialog(false);
+ }
+ if (hasDomWindow)
+ msgCompose->CloseWindow(); // if we fail on the simple GetFcc call, close the window to be safe and avoid
+ // windows hanging around to prevent the app from exiting.
+ }
+
+ // Remove the current draft msg when sending draft is done.
+ bool deleteDraft;
+ msgCompose->GetDeleteDraft(&deleteDraft);
+ if (deleteDraft)
+ RemoveCurrentDraftMessage(msgCompose, false);
+ }
+ else
+ {
+ msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, aStatus);
+ if (progress)
+ {
+ progress->CloseProgressDialog(true);
+ progress->UnregisterListener(this);
+ }
+ }
+
+ }
+
+ nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile);
+
+ return rv;
+}
+
+nsresult
+nsMsgComposeSendListener::OnGetDraftFolderURI(const char *aFolderURI)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnGetDraftFolderURI(aFolderURI);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgComposeSendListener::OnStartCopy()
+{
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeSendListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
+{
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeSendListener::OnStopCopy(nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv);
+ if (msgCompose)
+ {
+ if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater ||
+ mDeliverMode == nsIMsgSend::nsMsgDeliverBackground ||
+ mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft)
+ {
+ msgCompose->RememberQueuedDisposition();
+ }
+
+ // Ok, if we are here, we are done with the send/copy operation so
+ // we have to do something with the window....SHOW if failed, Close
+ // if succeeded
+
+ nsCOMPtr<nsIMsgProgress> progress;
+ msgCompose->GetProgress(getter_AddRefs(progress));
+ if (progress)
+ {
+ // Unregister ourself from msg compose progress
+ progress->UnregisterListener(this);
+ progress->CloseProgressDialog(NS_FAILED(aStatus));
+ }
+
+ msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, aStatus);
+
+ if (NS_SUCCEEDED(aStatus))
+ {
+ // We should only close the window if we are done. Things like templates
+ // and drafts aren't done so their windows should stay open
+ if (mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft ||
+ mDeliverMode == nsIMsgSend::nsMsgSaveAsTemplate)
+ {
+ msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::SaveInFolderDone, aStatus);
+ // Remove the current draft msg when saving as draft/template is done.
+ msgCompose->SetDeleteDraft(true);
+ RemoveCurrentDraftMessage(msgCompose, true);
+ }
+ else
+ {
+ // Remove (possible) draft if we're in send later mode
+ if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater ||
+ mDeliverMode == nsIMsgSend::nsMsgDeliverBackground)
+ {
+ msgCompose->SetDeleteDraft(true);
+ RemoveCurrentDraftMessage(msgCompose, true);
+ }
+ msgCompose->CloseWindow();
+ }
+ }
+ msgCompose->ClearMessageSend();
+ }
+
+ return rv;
+}
+
+nsresult
+nsMsgComposeSendListener::GetMsgFolder(nsIMsgCompose *compObj, nsIMsgFolder **msgFolder)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> aMsgFolder;
+ nsCString folderUri;
+
+ rv = compObj->GetSavedFolderURI(getter_Copies(folderUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRDFService> rdfService (do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIRDFResource> resource;
+ rv = rdfService->GetResource(folderUri, getter_AddRefs(resource));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMsgFolder = do_QueryInterface(resource, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_IF_ADDREF(*msgFolder = aMsgFolder);
+ return rv;
+}
+
+nsresult
+nsMsgComposeSendListener::RemoveCurrentDraftMessage(nsIMsgCompose *compObj, bool calledByCopy)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgCompFields> compFields = nullptr;
+
+ rv = compObj->GetCompFields(getter_AddRefs(compFields));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get compose fields");
+ if (NS_FAILED(rv) || !compFields)
+ return rv;
+
+ nsCString curDraftIdURL;
+ nsMsgKey newUid = 0;
+ nsCString newDraftIdURL;
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+
+ rv = compFields->GetDraftId(getter_Copies(curDraftIdURL));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get draft id");
+
+ // Skip if no draft id (probably a new draft msg).
+ if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty())
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr;
+ rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(msgDBHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg header DB interface pointer.");
+ if (NS_SUCCEEDED(rv) && msgDBHdr)
+ {
+ do { // Break on failure or removal not needed.
+ // Get the folder for the message resource.
+ rv = msgDBHdr->GetFolder(getter_AddRefs(msgFolder));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg folder interface pointer.");
+ if (NS_FAILED(rv) || !msgFolder)
+ break;
+
+ // Only do this if it's a drafts folder.
+ bool isDraft;
+ msgFolder->GetFlag(nsMsgFolderFlags::Drafts, &isDraft);
+ if (!isDraft)
+ break;
+
+ // Only remove if the message is actually in the db. It might have only
+ // been in the use cache.
+ nsMsgKey key;
+ rv = msgDBHdr->GetMessageKey(&key);
+ if (NS_FAILED(rv))
+ break;
+ nsCOMPtr<nsIMsgDatabase> db;
+ msgFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (!db)
+ break;
+ bool containsKey = false;
+ db->ContainsKey(key, &containsKey);
+ if (!containsKey)
+ break;
+
+ // Build the msg array.
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't allocate array.");
+ if (NS_FAILED(rv) || !messageArray)
+ break;
+ rv = messageArray->AppendElement(msgDBHdr, false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't append msg header to array.");
+ if (NS_FAILED(rv))
+ break;
+
+ // Ready to delete the msg.
+ rv = msgFolder->DeleteMessages(messageArray, nullptr, true, false, nullptr, false /*allowUndo*/);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't delete message.");
+ } while(false);
+ }
+ else
+ {
+ // If we get here we have the case where the draft folder
+ // is on the server and
+ // it's not currently open (in thread pane), so draft
+ // msgs are saved to the server
+ // but they're not in our local DB. In this case,
+ // GetMsgDBHdrFromURI() will never
+ // find the msg. If the draft folder is a local one
+ // then we'll not get here because
+ // the draft msgs are saved to the local folder and
+ // are in local DB. Make sure the
+ // msg folder is imap. Even if we get here due to
+ // DB errors (worst case), we should
+ // still try to delete msg on the server because
+ // that's where the master copy of the
+ // msgs are stored, if draft folder is on the server.
+ // For local case, since DB is bad
+ // we can't do anything with it anyway so it'll be
+ // noop in this case.
+ rv = GetMsgFolder(compObj, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
+ NS_ASSERTION(imapFolder, "The draft folder MUST be an imap folder in order to mark the msg delete!");
+ if (NS_SUCCEEDED(rv) && imapFolder)
+ {
+ const char * str = PL_strchr(curDraftIdURL.get(), '#');
+ NS_ASSERTION(str, "Failed to get current draft id url");
+ if (str)
+ {
+ nsAutoCString srcStr(str+1);
+ nsresult err;
+ nsMsgKey messageID = srcStr.ToInteger(&err);
+ if (messageID != nsMsgKey_None)
+ {
+ rv = imapFolder->StoreImapFlags(kImapMsgDeletedFlag, true,
+ &messageID, 1, nullptr);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Now get the new uid so that next save will remove the right msg
+ // regardless whether or not the exiting msg can be deleted.
+ if (calledByCopy)
+ {
+ nsCOMPtr<nsIMsgFolder> savedToFolder;
+ nsCOMPtr<nsIMsgSend> msgSend;
+ rv = compObj->GetMessageSend(getter_AddRefs(msgSend));
+ NS_ASSERTION(msgSend, "RemoveCurrentDraftMessage msgSend is null.");
+ if (NS_FAILED(rv) || !msgSend)
+ return rv;
+
+ rv = msgSend->GetMessageKey(&newUid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure we have a folder interface pointer
+ rv = GetMsgFolder(compObj, getter_AddRefs(savedToFolder));
+
+ // Reset draft (uid) url with the new uid.
+ if (savedToFolder && newUid != nsMsgKey_None)
+ {
+ uint32_t folderFlags;
+ savedToFolder->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Drafts)
+ {
+ rv = savedToFolder->GenerateMessageURI(newUid, newDraftIdURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ compFields->SetDraftId(newDraftIdURL.get());
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsMsgComposeSendListener::SetMessageKey(nsMsgKey aMessageKey)
+{
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeSendListener::GetMessageId(nsACString& messageId)
+{
+ return NS_OK;
+}
+
+/* void onStateChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long aStateFlags, in nsresult aStatus); */
+NS_IMETHODIMP nsMsgComposeSendListener::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus)
+{
+ if (aStateFlags == nsIWebProgressListener::STATE_STOP)
+ {
+ nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj);
+ if (msgCompose)
+ {
+ nsCOMPtr<nsIMsgProgress> progress;
+ msgCompose->GetProgress(getter_AddRefs(progress));
+
+ // Time to stop any pending operation...
+ if (progress)
+ {
+ // Unregister ourself from msg compose progress
+ progress->UnregisterListener(this);
+
+ bool bCanceled = false;
+ progress->GetProcessCanceledByUser(&bCanceled);
+ if (bCanceled)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString msg;
+ bundle->GetStringFromName(u"msgCancelling", getter_Copies(msg));
+ progress->OnStatusChange(nullptr, nullptr, NS_OK, msg.get());
+ }
+ }
+
+ nsCOMPtr<nsIMsgSend> msgSend;
+ msgCompose->GetMessageSend(getter_AddRefs(msgSend));
+ if (msgSend)
+ msgSend->Abort();
+ }
+ }
+ return NS_OK;
+}
+
+/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */
+NS_IMETHODIMP nsMsgComposeSendListener::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress)
+{
+ /* Ignore this call */
+ return NS_OK;
+}
+
+/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location, in unsigned long aFlags); */
+NS_IMETHODIMP nsMsgComposeSendListener::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags)
+{
+ /* Ignore this call */
+ return NS_OK;
+}
+
+/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */
+NS_IMETHODIMP nsMsgComposeSendListener::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage)
+{
+ /* Ignore this call */
+ return NS_OK;
+}
+
+/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */
+NS_IMETHODIMP nsMsgComposeSendListener::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state)
+{
+ /* Ignore this call */
+ return NS_OK;
+}
+
+nsresult
+nsMsgCompose::ConvertHTMLToText(nsIFile *aSigFile, nsString &aSigData)
+{
+ nsAutoString origBuf;
+
+ nsresult rv = LoadDataFromFile(aSigFile, origBuf);
+ NS_ENSURE_SUCCESS (rv, rv);
+
+ ConvertBufToPlainText(origBuf, false, false, true, true);
+ aSigData = origBuf;
+ return NS_OK;
+}
+
+nsresult
+nsMsgCompose::ConvertTextToHTML(nsIFile *aSigFile, nsString &aSigData)
+{
+ nsresult rv;
+ nsAutoString origBuf;
+
+ rv = LoadDataFromFile(aSigFile, origBuf);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Ok, once we are here, we need to escape the data to make sure that
+ // we don't do HTML stuff with plain text sigs.
+ //
+ char16_t *escaped = MsgEscapeHTML2(origBuf.get(), origBuf.Length());
+ if (escaped)
+ {
+ aSigData.Append(escaped);
+ NS_Free(escaped);
+ }
+ else
+ aSigData.Append(origBuf);
+ return NS_OK;
+}
+
+nsresult
+nsMsgCompose::LoadDataFromFile(nsIFile *file, nsString &sigData,
+ bool aAllowUTF8, bool aAllowUTF16)
+{
+ int32_t readSize;
+ uint32_t nGot;
+ char *readBuf;
+ char *ptr;
+
+ bool isDirectory = false;
+ file->IsDirectory(&isDirectory);
+ if (isDirectory) {
+ NS_ERROR("file is a directory");
+ return NS_MSG_ERROR_READING_FILE;
+ }
+
+
+ nsCOMPtr <nsIInputStream> inputFile;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), file);
+ if (NS_FAILED(rv))
+ return NS_MSG_ERROR_READING_FILE;
+
+ int64_t fileSize;
+ file->GetFileSize(&fileSize);
+ readSize = (uint32_t) fileSize;
+
+
+ ptr = readBuf = (char *)PR_Malloc(readSize + 1); if (!readBuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+ memset(readBuf, 0, readSize + 1);
+
+ while (readSize) {
+ inputFile->Read(ptr, readSize, &nGot);
+ if (nGot) {
+ readSize -= nGot;
+ ptr += nGot;
+ }
+ else {
+ readSize = 0;
+ }
+ }
+ inputFile->Close();
+
+ readSize = (uint32_t) fileSize;
+
+ nsAutoCString sigEncoding(nsMsgI18NParseMetaCharset(file));
+ bool removeSigCharset = !sigEncoding.IsEmpty() && m_composeHTML;
+
+ if (sigEncoding.IsEmpty()) {
+ if (aAllowUTF8 && MsgIsUTF8(nsDependentCString(readBuf))) {
+ sigEncoding.Assign("UTF-8");
+ }
+ else if (sigEncoding.IsEmpty() && aAllowUTF16 &&
+ readSize % 2 == 0 && readSize >= 2 &&
+ ((readBuf[0] == char(0xFE) && readBuf[1] == char(0xFF)) ||
+ (readBuf[0] == char(0xFF) && readBuf[1] == char(0xFE)))) {
+ sigEncoding.Assign("UTF-16");
+ }
+ else {
+ //default to platform encoding for plain text files w/o meta charset
+ nsAutoCString textFileCharset;
+ nsMsgI18NTextFileCharset(textFileCharset);
+ sigEncoding.Assign(textFileCharset);
+ }
+ }
+
+ nsAutoCString readStr(readBuf, (int32_t) fileSize);
+ PR_FREEIF(readBuf);
+
+ // XXX: ^^^ could really use nsContentUtils::SlurpFileToString instead!
+
+ if (NS_FAILED(ConvertToUnicode(sigEncoding.get(), readStr, sigData)))
+ CopyASCIItoUTF16(readStr, sigData);
+
+ //remove sig meta charset to allow user charset override during composition
+ if (removeSigCharset)
+ {
+ nsAutoCString metaCharset("charset=");
+ metaCharset.Append(sigEncoding);
+ int32_t pos = sigData.Find(metaCharset.BeginReading(), true);
+ if (pos != kNotFound)
+ sigData.Cut(pos, metaCharset.Length());
+ }
+ return NS_OK;
+}
+
+/**
+ * If the data contains file URLs, convert them to data URLs instead.
+ * This is intended to be used in for signature files, so that we can make sure
+ * images loaded into the editor are available on send.
+ */
+nsresult
+nsMsgCompose::ReplaceFileURLs(nsString &aData)
+{
+ int32_t fPos;
+ int32_t offset = -1; // We're using RFind(), so offset -1 is from the very right.
+
+ // XXX This code is rather incomplete since it looks for "file://" even
+ // outside tags.
+ while ((fPos = aData.RFind("file://", true, offset)) != kNotFound) {
+ bool quoted = false;
+ char16_t q = 'x'; // initialise to anything to keep compilers happy.
+ if (fPos > 0) {
+ q = aData.CharAt(fPos - 1);
+ quoted = (q == '"' || q == '\'');
+ }
+ int32_t end = kNotFound;
+ if (quoted) {
+ end = aData.FindChar(q, fPos);
+ }
+ else {
+ int32_t spacePos = aData.FindChar(' ', fPos);
+ int32_t gtPos = aData.FindChar('>', fPos);
+ if (gtPos != kNotFound && spacePos != kNotFound) {
+ end = (spacePos < gtPos) ? spacePos : gtPos;
+ }
+ else if (gtPos == kNotFound && spacePos != kNotFound) {
+ end = spacePos;
+ }
+ else if (gtPos != kNotFound && spacePos == kNotFound) {
+ end = gtPos;
+ }
+ }
+ if (end == kNotFound) {
+ break;
+ }
+ nsString fileURL;
+ fileURL = Substring(aData, fPos, end - fPos);
+ nsString dataURL;
+ nsresult rv = DataURLForFileURL(fileURL, dataURL);
+ // If this one failed, maybe because the file wasn't found,
+ // continue to process the next one.
+ if (NS_SUCCEEDED(rv)) {
+ aData.Replace(fPos, end - fPos, dataURL);
+ }
+ if (fPos == 0)
+ break;
+ offset = fPos - 1;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgCompose::DataURLForFileURL(const nsAString &aFileURL, nsAString &aDataURL)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> fileUri;
+ rv = NS_NewURI(getter_AddRefs(fileUri), NS_ConvertUTF16toUTF8(aFileURL).get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(fileUri, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString type;
+ rv = mime->GetTypeFromFile(file, type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString data;
+ rv = nsContentUtils::SlurpFileToString(file, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aDataURL.AssignLiteral("data:");
+ AppendUTF8toUTF16(type, aDataURL);
+
+ nsAutoString filename;
+ rv = file->GetLeafName(filename);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString fn;
+ MsgEscapeURL(NS_ConvertUTF16toUTF8(filename),
+ nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED, fn);
+ if (!fn.IsEmpty()) {
+ aDataURL.AppendLiteral(";filename=");
+ aDataURL.Append(NS_ConvertUTF8toUTF16(fn));
+ }
+ }
+
+ aDataURL.AppendLiteral(";base64,");
+ char *result = PL_Base64Encode(data.get(), data.Length(), nullptr);
+ nsDependentCString base64data(result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AppendUTF8toUTF16(base64data, aDataURL);
+ return NS_OK;
+}
+
+nsresult
+nsMsgCompose::BuildQuotedMessageAndSignature(void)
+{
+ //
+ // This should never happen...if it does, just bail out...
+ //
+ NS_ASSERTION(m_editor, "BuildQuotedMessageAndSignature but no editor!\n");
+ if (!m_editor)
+ return NS_ERROR_FAILURE;
+
+ // We will fire off the quote operation and wait for it to
+ // finish before we actually do anything with Ender...
+ return QuoteOriginalMessage();
+}
+
+//
+// This will process the signature file for the user. This method
+// will always append the results to the mMsgBody member variable.
+//
+nsresult
+nsMsgCompose::ProcessSignature(nsIMsgIdentity *identity, bool aQuoted, nsString *aMsgBody)
+{
+ nsresult rv = NS_OK;
+
+ // Now, we can get sort of fancy. This is the time we need to check
+ // for all sorts of user defined stuff, like signatures and editor
+ // types and the like!
+ //
+ // user_pref(".....sig_file", "y:\\sig.html");
+ // user_pref(".....attach_signature", true);
+ // user_pref(".....htmlSigText", "unicode sig");
+ //
+ // Note: We will have intelligent signature behavior in that we
+ // look at the signature file first...if the extension is .htm or
+ // .html, we assume its HTML, otherwise, we assume it is plain text
+ //
+ // ...and that's not all! What we will also do now is look and see if
+ // the file is an image file. If it is an image file, then we should
+ // insert the correct HTML into the composer to have it work, but if we
+ // are doing plain text compose, we should insert some sort of message
+ // saying "Image Signature Omitted" or something (not done yet).
+ //
+ // If there's a sig pref, it will only be used if there is no sig file defined,
+ // thus if attach_signature is checked, htmlSigText is ignored (bug 324495).
+ // Plain-text signatures may or may not have a trailing line break (bug 428040).
+
+ nsAutoCString sigNativePath;
+ bool attachFile = false;
+ bool useSigFile = false;
+ bool htmlSig = false;
+ bool imageSig = false;
+ nsAutoString sigData;
+ nsAutoString sigOutput;
+ int32_t reply_on_top = 0;
+ bool sig_bottom = true;
+ bool suppressSigSep = false;
+
+ nsCOMPtr<nsIFile> sigFile;
+ if (identity)
+ {
+ if (!CheckIncludeSignaturePrefs(identity))
+ return NS_OK;
+
+ identity->GetReplyOnTop(&reply_on_top);
+ identity->GetSigBottom(&sig_bottom);
+ identity->GetSuppressSigSep(&suppressSigSep);
+
+ rv = identity->GetAttachSignature(&attachFile);
+ if (NS_SUCCEEDED(rv) && attachFile)
+ {
+ rv = identity->GetSignature(getter_AddRefs(sigFile));
+ if (NS_SUCCEEDED(rv) && sigFile) {
+ rv = sigFile->GetNativePath(sigNativePath);
+ if (NS_SUCCEEDED(rv) && !sigNativePath.IsEmpty()) {
+ bool exists = false;
+ sigFile->Exists(&exists);
+ if (exists) {
+ useSigFile = true; // ok, there's a signature file
+
+ // Now, most importantly, we need to figure out what the content type is for
+ // this signature...if we can't, we assume text
+ nsAutoCString sigContentType;
+ nsresult rv2; // don't want to clobber the other rv
+ nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv2));
+ if (NS_SUCCEEDED(rv2)) {
+ rv2 = mimeFinder->GetTypeFromFile(sigFile, sigContentType);
+ if (NS_SUCCEEDED(rv2)) {
+ if (StringBeginsWith(sigContentType, NS_LITERAL_CSTRING("image/"), nsCaseInsensitiveCStringComparator()))
+ imageSig = true;
+ else if (sigContentType.Equals(TEXT_HTML, nsCaseInsensitiveCStringComparator()))
+ htmlSig = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Unless signature to be attached from file, use preference value;
+ // the htmlSigText value is always going to be treated as html if
+ // the htmlSigFormat pref is true, otherwise it is considered text
+ nsAutoString prefSigText;
+ if (identity && !attachFile)
+ identity->GetHtmlSigText(prefSigText);
+ // Now, if they didn't even want to use a signature, we should
+ // just return nicely.
+ //
+ if ((!useSigFile && prefSigText.IsEmpty()) || NS_FAILED(rv))
+ return NS_OK;
+
+ static const char htmlBreak[] = "<br>";
+ static const char dashes[] = "-- ";
+ static const char htmlsigopen[] = "<div class=\"moz-signature\">";
+ static const char htmlsigclose[] = "</div>"; /* XXX: Due to a bug in
+ 4.x' HTML editor, it will not be able to
+ break this HTML sig, if quoted (for the user to
+ interleave a comment). */
+ static const char _preopen[] = "<pre class=\"moz-signature\" cols=%d>";
+ char* preopen;
+ static const char preclose[] = "</pre>";
+
+ int32_t wrapLength = 72; // setup default value in case GetWrapLength failed
+ GetWrapLength(&wrapLength);
+ preopen = PR_smprintf(_preopen, wrapLength);
+ if (!preopen)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ bool paragraphMode =
+ mozilla::Preferences::GetBool("mail.compose.default_to_paragraph", false);
+
+ if (imageSig)
+ {
+ // We have an image signature. If we're using the in HTML composer, we
+ // should put in the appropriate HTML for inclusion, otherwise, do nothing.
+ if (m_composeHTML)
+ {
+ if (!paragraphMode)
+ sigOutput.AppendLiteral(htmlBreak);
+ sigOutput.AppendLiteral(htmlsigopen);
+ if ((mType == nsIMsgCompType::NewsPost || !suppressSigSep) &&
+ (reply_on_top != 1 || sig_bottom || !aQuoted)) {
+ sigOutput.AppendLiteral(dashes);
+ }
+
+ sigOutput.AppendLiteral(htmlBreak);
+ sigOutput.AppendLiteral("<img src='");
+
+ nsCOMPtr<nsIURI> fileURI;
+ nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), sigFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString fileURL;
+ fileURI->GetSpec(fileURL);
+
+ nsString dataURL;
+ rv = DataURLForFileURL(NS_ConvertUTF8toUTF16(fileURL), dataURL);
+ if (NS_SUCCEEDED(rv)) {
+ sigOutput.Append(dataURL);
+ }
+ sigOutput.AppendLiteral("' border=0>");
+ sigOutput.AppendLiteral(htmlsigclose);
+ }
+ }
+ else if (useSigFile)
+ {
+ // is this a text sig with an HTML editor?
+ if ( (m_composeHTML) && (!htmlSig) ) {
+ ConvertTextToHTML(sigFile, sigData);
+ }
+ // is this a HTML sig with a text window?
+ else if ( (!m_composeHTML) && (htmlSig) ) {
+ ConvertHTMLToText(sigFile, sigData);
+ }
+ else { // We have a match...
+ LoadDataFromFile(sigFile, sigData); // Get the data!
+ ReplaceFileURLs(sigData);
+ }
+ }
+
+ // if we have a prefSigText, append it to sigData.
+ if (!prefSigText.IsEmpty())
+ {
+ // set htmlSig if the pref is supposed to contain HTML code, defaults to false
+ rv = identity->GetHtmlSigFormat(&htmlSig);
+ if (NS_FAILED(rv))
+ htmlSig = false;
+
+ if (!m_composeHTML)
+ {
+ if (htmlSig)
+ ConvertBufToPlainText(prefSigText, false, false, true, true);
+ sigData.Append(prefSigText);
+ }
+ else
+ {
+ if (!htmlSig)
+ {
+ char16_t* escaped = MsgEscapeHTML2(prefSigText.get(), prefSigText.Length());
+ if (escaped)
+ {
+ sigData.Append(escaped);
+ NS_Free(escaped);
+ }
+ else
+ sigData.Append(prefSigText);
+ }
+ else {
+ ReplaceFileURLs(prefSigText);
+ sigData.Append(prefSigText);
+ }
+ }
+ }
+
+ // post-processing for plain-text signatures to ensure we end in CR, LF, or CRLF
+ if (!htmlSig && !m_composeHTML)
+ {
+ int32_t sigLength = sigData.Length();
+ if (sigLength > 0 && !(sigData.CharAt(sigLength - 1) == '\r')
+ && !(sigData.CharAt(sigLength - 1) == '\n'))
+ sigData.AppendLiteral(CRLF);
+ }
+
+ // Now that sigData holds data...if any, append it to the body in a nice
+ // looking manner
+ if (!sigData.IsEmpty())
+ {
+ if (m_composeHTML)
+ {
+ if (!paragraphMode)
+ sigOutput.AppendLiteral(htmlBreak);
+
+ if (htmlSig)
+ sigOutput.AppendLiteral(htmlsigopen);
+ else
+ sigOutput.Append(NS_ConvertASCIItoUTF16(preopen));
+ }
+
+ if ((reply_on_top != 1 || sig_bottom || !aQuoted) &&
+ sigData.Find("\r-- \r", true) < 0 &&
+ sigData.Find("\n-- \n", true) < 0 &&
+ sigData.Find("\n-- \r", true) < 0)
+ {
+ nsDependentSubstring firstFourChars(sigData, 0, 4);
+
+ if ((mType == nsIMsgCompType::NewsPost || !suppressSigSep) &&
+ !(firstFourChars.EqualsLiteral("-- \n") ||
+ firstFourChars.EqualsLiteral("-- \r")))
+ {
+ sigOutput.AppendLiteral(dashes);
+
+ if (!m_composeHTML || !htmlSig)
+ sigOutput.AppendLiteral(CRLF);
+ else if (m_composeHTML)
+ sigOutput.AppendLiteral(htmlBreak);
+ }
+ }
+
+ // add CRLF before signature for plain-text mode if signature comes before quote
+ if (!m_composeHTML && reply_on_top == 1 && !sig_bottom && aQuoted)
+ sigOutput.AppendLiteral(CRLF);
+
+ sigOutput.Append(sigData);
+
+ if (m_composeHTML)
+ {
+ if (htmlSig)
+ sigOutput.AppendLiteral(htmlsigclose);
+ else
+ sigOutput.AppendLiteral(preclose);
+ }
+ }
+
+ aMsgBody->Append(sigOutput);
+ PR_Free(preopen);
+ return NS_OK;
+}
+
+nsresult
+nsMsgCompose::BuildBodyMessageAndSignature()
+{
+ nsresult rv = NS_OK;
+
+ //
+ // This should never happen...if it does, just bail out...
+ //
+ if (!m_editor)
+ return NS_ERROR_FAILURE;
+
+ //
+ // Now, we have the body so we can just blast it into the
+ // composition editor window.
+ //
+ nsAutoString body;
+ m_compFields->GetBody(body);
+
+ /* Some time we want to add a signature and sometime we wont. Let's figure that now...*/
+ bool addSignature;
+ bool isQuoted = false;
+ switch (mType)
+ {
+ case nsIMsgCompType::ForwardInline :
+ addSignature = true;
+ isQuoted = true;
+ break;
+ case nsIMsgCompType::New :
+ case nsIMsgCompType::MailToUrl : /* same as New */
+ case nsIMsgCompType::Reply : /* should not happen! but just in case */
+ case nsIMsgCompType::ReplyAll : /* should not happen! but just in case */
+ case nsIMsgCompType::ReplyToList : /* should not happen! but just in case */
+ case nsIMsgCompType::ForwardAsAttachment : /* should not happen! but just in case */
+ case nsIMsgCompType::NewsPost :
+ case nsIMsgCompType::ReplyToGroup :
+ case nsIMsgCompType::ReplyToSender :
+ case nsIMsgCompType::ReplyToSenderAndGroup :
+ addSignature = true;
+ break;
+
+ case nsIMsgCompType::Draft :
+ case nsIMsgCompType::Template :
+ case nsIMsgCompType::Redirect :
+ addSignature = false;
+ break;
+
+ default :
+ addSignature = false;
+ break;
+ }
+
+ nsAutoString tSignature;
+ if (addSignature)
+ ProcessSignature(m_identity, isQuoted, &tSignature);
+
+ // if type is new, but we have body, this is probably a mapi send, so we need to
+ // replace '\n' with <br> so that the line breaks won't be lost by html.
+ // if mailtourl, do the same.
+ if (m_composeHTML && (mType == nsIMsgCompType::New || mType == nsIMsgCompType::MailToUrl))
+ MsgReplaceSubstring(body, NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("<br>"));
+
+ // Restore flowed text wrapping for Drafts/Templates.
+ // Look for unquoted lines - if we have an unquoted line
+ // that ends in a space, join this line with the next one
+ // by removing the end of line char(s).
+ int32_t wrapping_enabled = 0;
+ GetWrapLength(&wrapping_enabled);
+ if (!m_composeHTML && wrapping_enabled)
+ {
+ bool quote = false;
+ for (uint32_t i = 0; i < body.Length(); i ++)
+ {
+ if (i == 0 || body[i - 1] == '\n') // newline
+ {
+ if (body[i] == '>')
+ {
+ quote = true;
+ continue;
+ }
+ nsString s(Substring(body, i, 10));
+ if (StringBeginsWith(s, NS_LITERAL_STRING("-- \r")) ||
+ StringBeginsWith(s, NS_LITERAL_STRING("-- \n")))
+ {
+ i += 4;
+ continue;
+ }
+ if (StringBeginsWith(s, NS_LITERAL_STRING("- -- \r")) ||
+ StringBeginsWith(s, NS_LITERAL_STRING("- -- \n")))
+ {
+ i += 6;
+ continue;
+ }
+ }
+ if (body[i] == '\n' && i > 1)
+ {
+ if (quote)
+ {
+ quote = false;
+ continue; // skip quoted lines
+ }
+ uint32_t j = i - 1; // look backward for space
+ if (body[j] == '\r')
+ j --;
+ if (body[j] == ' ') // join this line with next one
+ body.Cut(j + 1, i - j); // remove CRLF
+ }
+ }
+ }
+
+ nsString empty;
+ rv = ConvertAndLoadComposeWindow(empty, body, tSignature,
+ false, m_composeHTML);
+
+ return rv;
+}
+
+nsresult nsMsgCompose::NotifyStateListeners(int32_t aNotificationType, nsresult aResult)
+{
+
+ if (aNotificationType == nsIMsgComposeNotificationType::SaveInFolderDone)
+ ResetUrisForEmbeddedObjects();
+
+ nsTObserverArray<nsCOMPtr<nsIMsgComposeStateListener> >::ForwardIterator iter(mStateListeners);
+ nsCOMPtr<nsIMsgComposeStateListener> thisListener;
+
+ while (iter.HasMore())
+ {
+ thisListener = iter.GetNext();
+
+ switch (aNotificationType)
+ {
+ case nsIMsgComposeNotificationType::ComposeFieldsReady:
+ thisListener->NotifyComposeFieldsReady();
+ break;
+
+ case nsIMsgComposeNotificationType::ComposeProcessDone:
+ thisListener->ComposeProcessDone(aResult);
+ break;
+
+ case nsIMsgComposeNotificationType::SaveInFolderDone:
+ thisListener->SaveInFolderDone(m_folderName.get());
+ break;
+
+ case nsIMsgComposeNotificationType::ComposeBodyReady:
+ thisListener->NotifyComposeBodyReady();
+ break;
+
+ default:
+ NS_NOTREACHED("Unknown notification");
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::AttachmentPrettyName(const nsACString & scheme, const char* charset, nsACString& _retval)
+{
+ nsresult rv;
+
+ if (MsgLowerCaseEqualsLiteral(StringHead(scheme, 5), "file:"))
+ {
+ nsCOMPtr<nsIFile> file;
+ rv = NS_GetFileFromURLSpec(scheme,
+ getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString leafName;
+ rv = file->GetLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF16toUTF8(leafName, _retval);
+ return rv;
+ }
+
+ // To work around a mysterious bug in VC++ 6.
+ const char* cset = (!charset || !*charset) ? "UTF-8" : charset;
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString retUrl;
+ rv = textToSubURI->UnEscapeURIForUI(nsDependentCString(cset), scheme, retUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF16toUTF8(retUrl, _retval);
+ } else {
+ _retval.Assign(scheme);
+ }
+ if (MsgLowerCaseEqualsLiteral(StringHead(scheme, 5), "http:"))
+ _retval.Cut(0, 7);
+
+ return NS_OK;
+}
+
+/**
+ * Retrieve address book directories and mailing lists.
+ *
+ * @param aDirUri directory URI
+ * @param allDirectoriesArray retrieved directories and sub-directories
+ * @param allMailListArray retrieved maillists
+ */
+nsresult
+nsMsgCompose::GetABDirAndMailLists(const nsACString& aDirUri,
+ nsCOMArray<nsIAbDirectory> &aDirArray,
+ nsTArray<nsMsgMailList> &aMailListArray)
+{
+ static bool collectedAddressbookFound;
+ if (aDirUri.EqualsLiteral(kMDBDirectoryRoot))
+ collectedAddressbookFound = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(aDirUri, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> subDirectories;
+ if (NS_SUCCEEDED(directory->GetChildNodes(getter_AddRefs(subDirectories))) && subDirectories)
+ {
+ nsCOMPtr<nsISupports> item;
+ bool hasMore;
+ while (NS_SUCCEEDED(rv = subDirectories->HasMoreElements(&hasMore)) && hasMore)
+ {
+ if (NS_SUCCEEDED(subDirectories->GetNext(getter_AddRefs(item))))
+ {
+ directory = do_QueryInterface(item, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ bool bIsMailList;
+
+ if (NS_SUCCEEDED(directory->GetIsMailList(&bIsMailList)) && bIsMailList)
+ {
+ aMailListArray.AppendElement(directory);
+ continue;
+ }
+
+ nsCString uri;
+ rv = directory->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t pos;
+ if (uri.EqualsLiteral(kPersonalAddressbookUri))
+ pos = 0;
+ else
+ {
+ uint32_t count = aDirArray.Count();
+
+ if (uri.EqualsLiteral(kCollectedAddressbookUri))
+ {
+ collectedAddressbookFound = true;
+ pos = count;
+ }
+ else
+ {
+ if (collectedAddressbookFound && count > 1)
+ pos = count - 1;
+ else
+ pos = count;
+ }
+ }
+
+ aDirArray.InsertObjectAt(directory, pos);
+ rv = GetABDirAndMailLists(uri, aDirArray, aMailListArray);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+/**
+ * Comparator for use with nsTArray::IndexOf to find a recipient.
+ * This comparator will check if an "address" is a mail list or not.
+ */
+struct nsMsgMailListComparator
+{
+ // A mail list will have one of the formats
+ // 1) "mName <mDescription>" when the list has a description
+ // 2) "mName <mName>" when the list lacks description
+ // A recipient is of the form "mName <mEmail>" - for equality the list
+ // name must be the same. The recipient "email" must match the list name for
+ // case 1, and the list description for case 2.
+ bool Equals(const nsMsgMailList &mailList,
+ const nsMsgRecipient &recipient) const {
+ if (!mailList.mName.Equals(recipient.mName,
+ nsCaseInsensitiveStringComparator()))
+ return false;
+ return mailList.mDescription.IsEmpty() ?
+ mailList.mName.Equals(recipient.mEmail, nsCaseInsensitiveStringComparator()) :
+ mailList.mDescription.Equals(recipient.mEmail, nsCaseInsensitiveStringComparator());
+ }
+};
+
+/**
+ * Comparator for use with nsTArray::IndexOf to find a recipient.
+ */
+struct nsMsgRecipientComparator
+{
+ bool Equals(const nsMsgRecipient &recipient,
+ const nsMsgRecipient &recipientToFind) const {
+ if (!recipient.mEmail.Equals(recipientToFind.mEmail,
+ nsCaseInsensitiveStringComparator()))
+ return false;
+
+ if (!recipient.mName.Equals(recipientToFind.mName,
+ nsCaseInsensitiveStringComparator()))
+ return false;
+
+ return true;
+ }
+};
+
+/**
+ * This function recursively resolves a mailing list and returns individual
+ * email addresses. Nested lists are supported. It maintains an array of
+ * already visited mailing lists to avoid endless recursion.
+ *
+ * @param aMailList the list
+ * @param allDirectoriesArray all directories
+ * @param allMailListArray all maillists
+ * @param mailListProcessed maillists processed (to avoid recursive lists)
+ * @param aListMembers list members
+ */
+nsresult
+nsMsgCompose::ResolveMailList(nsIAbDirectory* aMailList,
+ nsCOMArray<nsIAbDirectory> &allDirectoriesArray,
+ nsTArray<nsMsgMailList> &allMailListArray,
+ nsTArray<nsMsgMailList> &mailListProcessed,
+ nsTArray<nsMsgRecipient> &aListMembers)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMutableArray> mailListAddresses;
+ rv = aMailList->GetAddressLists(getter_AddRefs(mailListAddresses));
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t nbrAddresses = 0;
+ mailListAddresses->GetLength(&nbrAddresses);
+ for (uint32_t i = 0; i < nbrAddresses; i++)
+ {
+ nsCOMPtr<nsIAbCard> existingCard(do_QueryElementAt(mailListAddresses, i, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgRecipient newRecipient;
+
+ rv = existingCard->GetDisplayName(newRecipient.mName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = existingCard->GetPrimaryEmail(newRecipient.mEmail);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (newRecipient.mName.IsEmpty() && newRecipient.mEmail.IsEmpty()) {
+ continue;
+ }
+
+ // First check if it's a mailing list.
+ size_t index = allMailListArray.IndexOf(newRecipient, 0, nsMsgMailListComparator());
+ if (index != allMailListArray.NoIndex && allMailListArray[index].mDirectory)
+ {
+ // Check if maillist processed.
+ if (mailListProcessed.Contains(newRecipient, nsMsgMailListComparator())) {
+ continue;
+ }
+
+ nsCOMPtr<nsIAbDirectory> directory2(allMailListArray[index].mDirectory);
+
+ // Add mailList to mailListProcessed.
+ mailListProcessed.AppendElement(directory2);
+
+ // Resolve mailList members.
+ rv = ResolveMailList(directory2,
+ allDirectoriesArray,
+ allMailListArray,
+ mailListProcessed,
+ aListMembers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ continue;
+ }
+
+ // Check if recipient is in aListMembers.
+ if (aListMembers.Contains(newRecipient, nsMsgRecipientComparator())) {
+ continue;
+ }
+
+ // Now we need to insert the new address into the list of recipients.
+ newRecipient.mCard = existingCard;
+ newRecipient.mDirectory = aMailList;
+
+ aListMembers.AppendElement(newRecipient);
+ }
+
+ return rv;
+}
+
+/**
+ * Lookup the recipients as specified in the compose fields (To, Cc, Bcc)
+ * in the address books and return an array of individual recipients.
+ * Mailing lists are replaced by the cards they contain, nested and recursive
+ * lists are taken care of, recipients contained in multiple lists are only
+ * added once.
+ *
+ * @param recipientsList (out) recipient array
+ */
+nsresult
+nsMsgCompose::LookupAddressBook(RecipientsArray &recipientsList)
+{
+ nsresult rv = NS_OK;
+
+ // First, build some arrays with the original recipients.
+
+ nsAutoString originalRecipients[MAX_OF_RECIPIENT_ARRAY];
+ m_compFields->GetTo(originalRecipients[0]);
+ m_compFields->GetCc(originalRecipients[1]);
+ m_compFields->GetBcc(originalRecipients[2]);
+
+ for (uint32_t i = 0; i < MAX_OF_RECIPIENT_ARRAY; ++i)
+ {
+ if (originalRecipients[i].IsEmpty())
+ continue;
+
+ rv = m_compFields->SplitRecipientsEx(originalRecipients[i],
+ recipientsList[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Then look them up in the Addressbooks
+ bool stillNeedToSearch = true;
+ nsCOMPtr<nsIAbDirectory> abDirectory;
+ nsCOMPtr<nsIAbCard> existingCard;
+ nsTArray<nsMsgMailList> mailListArray;
+ nsTArray<nsMsgMailList> mailListProcessed;
+
+ nsCOMArray<nsIAbDirectory> addrbookDirArray;
+ rv = GetABDirAndMailLists(NS_LITERAL_CSTRING(kAllDirectoryRoot),
+ addrbookDirArray, mailListArray);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsString dirPath;
+ uint32_t nbrAddressbook = addrbookDirArray.Count();
+
+ for (uint32_t k = 0; k < nbrAddressbook && stillNeedToSearch; ++k)
+ {
+ // Avoid recursive mailing lists.
+ if (abDirectory && (addrbookDirArray[k] == abDirectory))
+ {
+ stillNeedToSearch = false;
+ break;
+ }
+
+ abDirectory = addrbookDirArray[k];
+ if (!abDirectory)
+ continue;
+
+ stillNeedToSearch = false;
+ for (uint32_t i = 0; i < MAX_OF_RECIPIENT_ARRAY; i ++)
+ {
+ mailListProcessed.Clear();
+
+ // Note: We check this each time to allow for length changes.
+ for (uint32_t j = 0; j < recipientsList[i].Length(); j++)
+ {
+ nsMsgRecipient &recipient = recipientsList[i][j];
+ if (!recipient.mDirectory)
+ {
+ // First check if it's a mailing list.
+ size_t index = mailListArray.IndexOf(recipient, 0, nsMsgMailListComparator());
+ if (index != mailListArray.NoIndex && mailListArray[index].mDirectory)
+ {
+ // Check mailList Processed.
+ if (mailListProcessed.Contains(recipient, nsMsgMailListComparator())) {
+ // Remove from recipientsList.
+ recipientsList[i].RemoveElementAt(j--);
+ continue;
+ }
+
+ nsCOMPtr<nsIAbDirectory> directory(mailListArray[index].mDirectory);
+
+ // Add mailList to mailListProcessed.
+ mailListProcessed.AppendElement(directory);
+
+ // Resolve mailList members.
+ nsTArray<nsMsgRecipient> members;
+ rv = ResolveMailList(directory,
+ addrbookDirArray,
+ mailListArray,
+ mailListProcessed,
+ members);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove mailList from recipientsList.
+ recipientsList[i].RemoveElementAt(j);
+
+ // Merge members into recipientsList[i].
+ uint32_t pos = 0;
+ for (uint32_t c = 0; c < members.Length(); c++)
+ {
+ nsMsgRecipient &member = members[c];
+ if (!recipientsList[i].Contains(member, nsMsgRecipientComparator())) {
+ recipientsList[i].InsertElementAt(j + pos, member);
+ pos++;
+ }
+ }
+ }
+ else
+ {
+ // Find a card that contains this e-mail address.
+ rv = abDirectory->CardForEmailAddress(NS_ConvertUTF16toUTF8(recipient.mEmail),
+ getter_AddRefs(existingCard));
+ if (NS_SUCCEEDED(rv) && existingCard)
+ {
+ recipient.mCard = existingCard;
+ recipient.mDirectory = abDirectory;
+ }
+ else
+ {
+ stillNeedToSearch = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::ExpandMailingLists()
+{
+ RecipientsArray recipientsList;
+ nsresult rv = LookupAddressBook(recipientsList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Reset the final headers with the expanded mailing lists.
+ nsAutoString recipientsStr;
+
+ for (int i = 0; i < MAX_OF_RECIPIENT_ARRAY; ++i)
+ {
+ uint32_t nbrRecipients = recipientsList[i].Length();
+ if (nbrRecipients == 0)
+ continue;
+ recipientsStr.Truncate();
+
+ // Note: We check this each time to allow for length changes.
+ for (uint32_t j = 0; j < recipientsList[i].Length(); ++j)
+ {
+ nsMsgRecipient &recipient = recipientsList[i][j];
+
+ if (!recipientsStr.IsEmpty())
+ recipientsStr.Append(char16_t(','));
+ nsAutoString address;
+ MakeMimeAddress(recipient.mName, recipient.mEmail, address);
+ recipientsStr.Append(address);
+
+ if (recipient.mCard)
+ {
+ bool readOnly;
+ rv = recipient.mDirectory->GetReadOnly(&readOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bump the popularity index for this card since we are about to send
+ // e-mail to it.
+ if (!readOnly)
+ {
+ uint32_t popularityIndex = 0;
+ if (NS_FAILED(recipient.mCard->GetPropertyAsUint32(
+ kPopularityIndexProperty, &popularityIndex)))
+ {
+ // TB 2 wrote the popularity value as hex, so if we get here,
+ // then we've probably got a hex value. We'll convert it back
+ // to decimal, as that's the best we can do.
+
+ nsCString hexPopularity;
+ if (NS_SUCCEEDED(recipient.mCard->GetPropertyAsAUTF8String(
+ kPopularityIndexProperty, hexPopularity)))
+ {
+ nsresult errorCode = NS_OK;
+ popularityIndex = hexPopularity.ToInteger(&errorCode, 16);
+ if (NS_FAILED(errorCode))
+ // We failed, just set it to zero.
+ popularityIndex = 0;
+ }
+ else
+ // We couldn't get it as a string either, so just reset to zero.
+ popularityIndex = 0;
+ }
+
+ recipient.mCard->SetPropertyAsUint32(kPopularityIndexProperty,
+ ++popularityIndex);
+ recipient.mDirectory->ModifyCard(recipient.mCard);
+ }
+ }
+ }
+
+ switch (i)
+ {
+ case 0: m_compFields->SetTo(recipientsStr); break;
+ case 1: m_compFields->SetCc(recipientsStr); break;
+ case 2: m_compFields->SetBcc(recipientsStr); break;
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * This function implements the decision logic for delivery format 'Auto-Detect',
+ * including optional 'Auto-Downgrade' behaviour for HTML messages considered
+ * convertible (silent, "lossless" conversion to plain text).
+ * @param aConvertible the result of analysing message body convertibility:
+ * nsIMsgCompConvertible::Plain | Yes | Altering | No
+ * @return nsIMsgCompSendFormat::AskUser | PlainText | HTML | Both
+ */
+NS_IMETHODIMP
+nsMsgCompose::DetermineHTMLAction(int32_t aConvertible, int32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsresult rv;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // *** Message-centric Auto-Downgrade ***
+ // If the message has practically no HTML formatting,
+ // AND if user accepts auto-downgrading (send options pref),
+ // bypass auto-detection of recipients' preferences and just
+ // send the message as plain text (silent, "lossless" conversion);
+ // which will also avoid asking for newsgroups for this typical scenario.
+ bool autoDowngrade = true;
+ rv = prefBranch->GetBoolPref("mailnews.sendformat.auto_downgrade", &autoDowngrade);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (autoDowngrade && (aConvertible == nsIMsgCompConvertible::Plain))
+ {
+ *result = nsIMsgCompSendFormat::PlainText;
+ return NS_OK;
+ }
+
+ // *** Newsgroups ***
+ // Right now, we don't have logic for newsgroups for intelligent send
+ // preferences. Therefore, bail out early and save us a lot of work if there
+ // are newsgroups.
+
+ nsAutoString newsgroups;
+ m_compFields->GetNewsgroups(newsgroups);
+
+ if (!newsgroups.IsEmpty())
+ {
+ *result = nsIMsgCompSendFormat::AskUser;
+ return NS_OK;
+ }
+
+ // *** Recipient-Centric Auto-Detect ***
+
+ RecipientsArray recipientsList;
+ rv = LookupAddressBook(recipientsList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally return the list of non-HTML recipients if requested and/or rebuilt
+ // the recipient field. Also, check for domain preference when preferFormat
+ // is unknown.
+ nsString plaintextDomains;
+ nsString htmlDomains;
+
+ if (prefBranch)
+ {
+ NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.plaintext_domains",
+ EmptyString(), plaintextDomains);
+ NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.html_domains",
+ EmptyString(), htmlDomains);
+ }
+
+ // allHTML and allPlain are summary recipient scopes of format preference
+ // according to address book and send options for recipient-centric Auto-Detect,
+ // used by Auto-Detect to determine the appropriate message delivery format.
+
+ // allHtml: All recipients prefer HTML.
+ bool allHtml = true;
+
+ // allPlain: All recipients prefer Plain Text.
+ bool allPlain = true;
+
+ // Exit the loop early if allHtml and allPlain both decay to false to save us
+ // some work.
+ for (int i = 0; i < MAX_OF_RECIPIENT_ARRAY && (allHtml || allPlain); ++i)
+ {
+ uint32_t nbrRecipients = recipientsList[i].Length();
+ for (uint32_t j = 0; j < nbrRecipients && (allHtml || allPlain); ++j)
+ {
+ nsMsgRecipient &recipient = recipientsList[i][j];
+ uint32_t preferFormat = nsIAbPreferMailFormat::unknown;
+ if (recipient.mCard)
+ {
+ recipient.mCard->GetPropertyAsUint32(kPreferMailFormatProperty,
+ &preferFormat);
+ }
+
+ // if we don't have a prefer format for a recipient, check the domain in
+ // case we have a format defined for it
+ if (preferFormat == nsIAbPreferMailFormat::unknown &&
+ (!plaintextDomains.IsEmpty() || !htmlDomains.IsEmpty()))
+ {
+ int32_t atPos = recipient.mEmail.FindChar('@');
+ if (atPos < 0)
+ continue;
+
+ nsDependentSubstring emailDomain = Substring(recipient.mEmail,
+ atPos + 1);
+ if (IsInDomainList(emailDomain, plaintextDomains))
+ preferFormat = nsIAbPreferMailFormat::plaintext;
+ else if (IsInDomainList(emailDomain, htmlDomains))
+ preferFormat = nsIAbPreferMailFormat::html;
+ }
+
+ // Determine the delivery format preference of this recipient and adjust
+ // the summary recipient scopes of the message accordingly.
+ switch (preferFormat)
+ {
+ case nsIAbPreferMailFormat::html:
+ allPlain = false;
+ break;
+
+ case nsIAbPreferMailFormat::plaintext:
+ allHtml = false;
+ break;
+
+ default: // nsIAbPreferMailFormat::unknown
+ allHtml = false;
+ allPlain = false;
+ break;
+ }
+ }
+ }
+
+ // Here's the final part of recipient-centric Auto-Detect logic where we set
+ // the actual send format (aka delivery format) after analysing recipients'
+ // format preferences above.
+
+ // If all recipients prefer HTML, then return HTML.
+ if (allHtml)
+ {
+ *result = nsIMsgCompSendFormat::HTML;
+ return NS_OK;
+ }
+
+ // If all recipients prefer plaintext, silently strip *all* HTML formatting,
+ // regardless of (non-)convertibility, and send the message as plaintext.
+ // **ToDo: UX-error-prevention, UX-wysiwyg: warn against dataloss potential.**
+ if (allPlain)
+ {
+ *result = nsIMsgCompSendFormat::PlainText;
+ return NS_OK;
+ }
+
+ // Otherwise, check the preference to see what action we should default to.
+ // This pref covers all recipient scopes involving prefers-plain (except allplain)
+ // and prefers-unknown. So we are mixing format conflict resolution options for
+ // prefers-plain with default format setting for prefers-unknown; not ideal.
+ int32_t action = nsIMsgCompSendFormat::AskUser;
+ rv = prefBranch->GetIntPref("mail.default_html_action", &action);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the action is a known send format, return the value to send in that format.
+ // Otherwise, ask the user.
+ // Note that the preference may default to 0 (Ask), which is not a valid value
+ // for the following enum.
+ if (action == nsIMsgCompSendFormat::PlainText ||
+ action == nsIMsgCompSendFormat::HTML ||
+ action == nsIMsgCompSendFormat::Both)
+ {
+ *result = action;
+ return NS_OK;
+ }
+
+ // At this point, ask the user.
+ *result = nsIMsgCompSendFormat::AskUser;
+ return NS_OK;
+}
+
+/* Decides which tags trigger which convertible mode, i.e. here is the logic
+ for BodyConvertible */
+// Helper function. Parameters are not checked.
+nsresult nsMsgCompose::TagConvertible(nsIDOMElement *node, int32_t *_retval)
+{
+ nsresult rv;
+
+ *_retval = nsIMsgCompConvertible::No;
+
+ uint16_t nodeType;
+ rv = node->GetNodeType(&nodeType);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoString element;
+ rv = node->GetNodeName(element);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIDOMNode> pItem;
+
+ // style attribute on any element can change layout in any way, so that is not convertible.
+ nsAutoString attribValue;
+ if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("style"), attribValue)) &&
+ !attribValue.IsEmpty())
+ {
+ *_retval = nsIMsgCompConvertible::No;
+ return NS_OK;
+ }
+
+ // moz-* classes are used internally by the editor and mail composition
+ // (like moz-cite or moz-signature). Those can be discarded.
+ // But any other ones are unconvertible. Style can be attached to them or any
+ // other context (e.g. in microformats).
+ if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("class"), attribValue)) &&
+ !attribValue.IsEmpty() &&
+ !StringBeginsWith(attribValue, NS_LITERAL_STRING("moz-"), nsCaseInsensitiveStringComparator()))
+ {
+ *_retval = nsIMsgCompConvertible::No;
+ return NS_OK;
+ }
+ // ID attributes can contain attached style/context or be target of links
+ // so we should preserve them.
+ if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("id"), attribValue)) &&
+ !attribValue.IsEmpty())
+ {
+ *_retval = nsIMsgCompConvertible::No;
+ return NS_OK;
+ }
+ if ( // some "simple" elements without "style" attribute
+ element.LowerCaseEqualsLiteral("br") ||
+ element.LowerCaseEqualsLiteral("p") ||
+ element.LowerCaseEqualsLiteral("pre") ||
+ element.LowerCaseEqualsLiteral("tt") ||
+ element.LowerCaseEqualsLiteral("html") ||
+ element.LowerCaseEqualsLiteral("head") ||
+ element.LowerCaseEqualsLiteral("meta") ||
+ element.LowerCaseEqualsLiteral("title")
+ )
+ {
+ *_retval = nsIMsgCompConvertible::Plain;
+ }
+ else if (
+ //element.LowerCaseEqualsLiteral("blockquote") || // see below
+ element.LowerCaseEqualsLiteral("ul") ||
+ element.LowerCaseEqualsLiteral("ol") ||
+ element.LowerCaseEqualsLiteral("li") ||
+ element.LowerCaseEqualsLiteral("dl") ||
+ element.LowerCaseEqualsLiteral("dt") ||
+ element.LowerCaseEqualsLiteral("dd")
+ )
+ {
+ *_retval = nsIMsgCompConvertible::Yes;
+ }
+ else if (
+ //element.LowerCaseEqualsLiteral("a") || // see below
+ element.LowerCaseEqualsLiteral("h1") ||
+ element.LowerCaseEqualsLiteral("h2") ||
+ element.LowerCaseEqualsLiteral("h3") ||
+ element.LowerCaseEqualsLiteral("h4") ||
+ element.LowerCaseEqualsLiteral("h5") ||
+ element.LowerCaseEqualsLiteral("h6") ||
+ element.LowerCaseEqualsLiteral("hr") ||
+ (
+ mConvertStructs
+ &&
+ (
+ element.LowerCaseEqualsLiteral("em") ||
+ element.LowerCaseEqualsLiteral("strong") ||
+ element.LowerCaseEqualsLiteral("code") ||
+ element.LowerCaseEqualsLiteral("b") ||
+ element.LowerCaseEqualsLiteral("i") ||
+ element.LowerCaseEqualsLiteral("u")
+ )
+ )
+ )
+ {
+ *_retval = nsIMsgCompConvertible::Altering;
+ }
+ else if (element.LowerCaseEqualsLiteral("body"))
+ {
+ *_retval = nsIMsgCompConvertible::Plain;
+
+ bool hasAttribute;
+ nsAutoString color;
+ if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("background"), &hasAttribute))
+ && hasAttribute) // There is a background image
+ *_retval = nsIMsgCompConvertible::No;
+ else if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("text"), &hasAttribute)) &&
+ hasAttribute &&
+ NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("text"), color)) &&
+ !color.EqualsLiteral("#000000")) {
+ *_retval = nsIMsgCompConvertible::Altering;
+ }
+ else if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("bgcolor"), &hasAttribute)) &&
+ hasAttribute &&
+ NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("bgcolor"), color)) &&
+ !color.LowerCaseEqualsLiteral("#ffffff")) {
+ *_retval = nsIMsgCompConvertible::Altering;
+ }
+ else if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("dir"), &hasAttribute))
+ && hasAttribute) // dir=rtl attributes should not downconvert
+ *_retval = nsIMsgCompConvertible::No;
+
+ //ignore special color setting for link, vlink and alink at this point.
+ }
+ else if (element.LowerCaseEqualsLiteral("blockquote"))
+ {
+ // Skip <blockquote type="cite">
+ *_retval = nsIMsgCompConvertible::Yes;
+
+ if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("type"), attribValue)) &&
+ attribValue.LowerCaseEqualsLiteral("cite"))
+ {
+ *_retval = nsIMsgCompConvertible::Plain;
+ }
+ }
+ else if (
+ element.LowerCaseEqualsLiteral("div") ||
+ element.LowerCaseEqualsLiteral("span") ||
+ element.LowerCaseEqualsLiteral("a")
+ )
+ {
+ /* Do some special checks for these tags. They are inside this |else if|
+ for performance reasons */
+
+ // Maybe, it's an <a> element inserted by another recognizer (e.g. 4.x')
+ if (element.LowerCaseEqualsLiteral("a"))
+ {
+ /* Ignore anchor tag, if the URI is the same as the text
+ (as inserted by recognizers) */
+ *_retval = nsIMsgCompConvertible::Altering;
+
+ nsAutoString hrefValue;
+ bool hasChild;
+ if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("href"), hrefValue)) &&
+ NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild)
+ {
+ nsCOMPtr<nsIDOMNodeList> children;
+ if (NS_SUCCEEDED(node->GetChildNodes(getter_AddRefs(children))) &&
+ children &&
+ NS_SUCCEEDED(children->Item(0, getter_AddRefs(pItem))) &&
+ pItem)
+ {
+ nsAutoString textValue;
+ if (NS_SUCCEEDED(pItem->GetNodeValue(textValue)) &&
+ textValue == hrefValue)
+ *_retval = nsIMsgCompConvertible::Plain;
+ }
+ }
+ }
+
+ // Lastly, test, if it is just a "simple" <div> or <span>
+ else if (
+ element.LowerCaseEqualsLiteral("div") ||
+ element.LowerCaseEqualsLiteral("span")
+ )
+ {
+ *_retval = nsIMsgCompConvertible::Plain;
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsMsgCompose::_NodeTreeConvertible(nsIDOMElement *node, int32_t *_retval)
+{
+ NS_ENSURE_TRUE(node && _retval, NS_ERROR_NULL_POINTER);
+
+ nsresult rv;
+ int32_t result;
+
+ // Check this node
+ rv = TagConvertible(node, &result);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Walk tree recursively to check the children
+ bool hasChild;
+ if (NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild)
+ {
+ nsCOMPtr<nsIDOMNodeList> children;
+ if (NS_SUCCEEDED(node->GetChildNodes(getter_AddRefs(children)))
+ && children)
+ {
+ uint32_t nbrOfElements;
+ rv = children->GetLength(&nbrOfElements);
+ for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < nbrOfElements; i++)
+ {
+ nsCOMPtr<nsIDOMNode> pItem;
+ if (NS_SUCCEEDED(children->Item(i, getter_AddRefs(pItem)))
+ && pItem)
+ {
+ // We assume all nodes that are not elements are convertible,
+ // so only test elements.
+ nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(pItem);
+ if (domElement) {
+ int32_t curresult;
+ rv = _NodeTreeConvertible(domElement, &curresult);
+
+ if (NS_SUCCEEDED(rv) && curresult > result)
+ result = curresult;
+ }
+ }
+ }
+ }
+ }
+
+ *_retval = result;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::BodyConvertible(int32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_ENSURE_STATE(m_editor);
+
+ nsCOMPtr<nsIDOMDocument> rootDocument;
+ nsresult rv = m_editor->GetDocument(getter_AddRefs(rootDocument));
+ if (NS_FAILED(rv) || !rootDocument)
+ return rv;
+
+ // get the top level element, which contains <html>
+ nsCOMPtr<nsIDOMElement> rootElement;
+ rv = rootDocument->GetDocumentElement(getter_AddRefs(rootElement));
+ if (NS_FAILED(rv) || !rootElement)
+ return rv;
+
+ return _NodeTreeConvertible(rootElement, _retval);
+}
+
+NS_IMETHODIMP
+nsMsgCompose::GetIdentity(nsIMsgIdentity **aIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ NS_IF_ADDREF(*aIdentity = m_identity);
+ return NS_OK;
+}
+
+/**
+ * Position above the quote, that is either <blockquote> or
+ * <div class="moz-cite-prefix"> or <div class="moz-forward-container">
+ * in an inline-forwarded message.
+ */
+nsresult
+nsMsgCompose::MoveToAboveQuote(void)
+{
+ nsCOMPtr<nsIDOMElement> rootElement;
+ nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+ if (NS_FAILED(rv) || !rootElement) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMNode> node;
+ nsAutoString attributeName;
+ nsAutoString attributeValue;
+ nsAutoString tagLocalName;
+ attributeName.AssignLiteral("class");
+
+ rv = rootElement->GetFirstChild(getter_AddRefs(node));
+ while (NS_SUCCEEDED(rv) && node) {
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(node);
+ if (element) {
+ // First check for <blockquote>. This will most likely not trigger
+ // since well-behaved quotes are preceded by a cite prefix.
+ node->GetLocalName(tagLocalName);
+ if (tagLocalName.EqualsLiteral("blockquote")) {
+ break;
+ }
+
+ // Get the class value.
+ element->GetAttribute(attributeName, attributeValue);
+
+ // Now check for the cite prefix, so an element with
+ // class="moz-cite-prefix".
+ if (attributeValue.Find("moz-cite-prefix", true) != kNotFound) {
+ break;
+ }
+
+ // Next check for forwarded content.
+ // The forwarded part is inside an element with
+ // class="moz-forward-container".
+ if (attributeValue.Find("moz-forward-container", true) != kNotFound) {
+ break;
+ }
+ }
+
+ rv = node->GetNextSibling(getter_AddRefs(node));
+ if (NS_FAILED(rv) || !node) {
+ // No further siblings found, so we didn't find what we were looking for.
+ rv = NS_OK;
+ node = nullptr;
+ break;
+ }
+ }
+
+ // Now position. If no quote was found, we position to the very front.
+ int32_t offset = 0;
+ if (node) {
+ rv = GetChildOffset(node, rootElement, offset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ nsCOMPtr<nsISelection> selection;
+ m_editor->GetSelection(getter_AddRefs(selection));
+ if (selection)
+ rv = selection->Collapse(rootElement, offset);
+
+ return rv;
+}
+
+/**
+ * nsEditor::BeginningOfDocument() will position to the beginning of the document
+ * before the first editable element. It will position into a container.
+ * We need to be at the very front.
+ */
+nsresult
+nsMsgCompose::MoveToBeginningOfDocument(void)
+{
+ nsCOMPtr<nsIDOMElement> rootElement;
+ nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+ if (NS_FAILED(rv) || !rootElement) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISelection> selection;
+ m_editor->GetSelection(getter_AddRefs(selection));
+ if (selection)
+ rv = selection->Collapse(rootElement, 0);
+
+ return rv;
+}
+
+/**
+ * M-C's nsEditor::EndOfDocument() will position to the end of the document
+ * but it will position into a container. We really need to position
+ * after the last container so we don't accidentally position into a
+ * <blockquote>. That's why we use our own function.
+ */
+nsresult
+nsMsgCompose::MoveToEndOfDocument(void)
+{
+ int32_t offset;
+ nsCOMPtr<nsIDOMElement> rootElement;
+ nsCOMPtr<nsIDOMNode> lastNode;
+ nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+ if (NS_FAILED(rv) || !rootElement) {
+ return rv;
+ }
+
+ rv = rootElement->GetLastChild(getter_AddRefs(lastNode));
+ if (NS_FAILED(rv) || !lastNode) {
+ return rv;
+ }
+
+ rv = GetChildOffset(lastNode, rootElement, offset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISelection> selection;
+ m_editor->GetSelection(getter_AddRefs(selection));
+ if (selection)
+ rv = selection->Collapse(rootElement, offset + 1);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::SetIdentity(nsIMsgIdentity *aIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aIdentity);
+
+ m_identity = aIdentity;
+
+ nsresult rv;
+
+ if (! m_editor)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDOMElement> rootElement;
+ rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+ if (NS_FAILED(rv) || !rootElement)
+ return rv;
+
+ //First look for the current signature, if we have one
+ nsCOMPtr<nsIDOMNode> lastNode;
+ nsCOMPtr<nsIDOMNode> node;
+ nsCOMPtr<nsIDOMNode> tempNode;
+ nsAutoString tagLocalName;
+
+ rv = rootElement->GetLastChild(getter_AddRefs(lastNode));
+ if (NS_SUCCEEDED(rv) && lastNode)
+ {
+ node = lastNode;
+ // In html, the signature is inside an element with
+ // class="moz-signature"
+ bool signatureFound = false;
+ nsAutoString attributeName;
+ attributeName.AssignLiteral("class");
+
+ do
+ {
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(node);
+ if (element)
+ {
+ nsAutoString attributeValue;
+
+ rv = element->GetAttribute(attributeName, attributeValue);
+
+ if (attributeValue.Find("moz-signature", true) != kNotFound) {
+ signatureFound = true;
+ break;
+ }
+ }
+ } while (!signatureFound &&
+ node &&
+ NS_SUCCEEDED(node->GetPreviousSibling(getter_AddRefs(node))));
+
+ if (signatureFound)
+ {
+ m_editor->BeginTransaction();
+ node->GetPreviousSibling(getter_AddRefs(tempNode));
+ rv = m_editor->DeleteNode(node);
+ if (NS_FAILED(rv))
+ {
+ m_editor->EndTransaction();
+ return rv;
+ }
+
+ // Also, remove the <br> right before the signature.
+ if (tempNode)
+ {
+ tempNode->GetLocalName(tagLocalName);
+ if (tagLocalName.EqualsLiteral("br"))
+ m_editor->DeleteNode(tempNode);
+ }
+ m_editor->EndTransaction();
+ }
+ }
+
+ if (!CheckIncludeSignaturePrefs(aIdentity))
+ return NS_OK;
+
+ // Then add the new one if needed
+ nsAutoString aSignature;
+
+ // No delimiter needed if not a compose window
+ bool isQuoted;
+ switch (mType)
+ {
+ case nsIMsgCompType::New :
+ case nsIMsgCompType::NewsPost :
+ case nsIMsgCompType::MailToUrl :
+ case nsIMsgCompType::ForwardAsAttachment :
+ isQuoted = false;
+ break;
+ default :
+ isQuoted = true;
+ break;
+ }
+
+ ProcessSignature(aIdentity, isQuoted, &aSignature);
+
+ if (!aSignature.IsEmpty())
+ {
+ TranslateLineEnding(aSignature);
+
+ m_editor->BeginTransaction();
+ int32_t reply_on_top = 0;
+ bool sig_bottom = true;
+ aIdentity->GetReplyOnTop(&reply_on_top);
+ aIdentity->GetSigBottom(&sig_bottom);
+ bool sigOnTop = (reply_on_top == 1 && !sig_bottom);
+ if (sigOnTop && isQuoted) {
+ rv = MoveToAboveQuote();
+ } else {
+ // Note: New messages aren't quoted so we always move to the end.
+ rv = MoveToEndOfDocument();
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ if (m_composeHTML) {
+ nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor));
+ rv = htmlEditor->InsertHTML(aSignature);
+ } else {
+ nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(m_editor));
+ rv = textEditor->InsertLineBreak();
+ InsertDivWrappedTextAtSelection(aSignature, NS_LITERAL_STRING("moz-signature"));
+ }
+ }
+ m_editor->EndTransaction();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgCompose::CheckCharsetConversion(nsIMsgIdentity *identity, char **fallbackCharset, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(identity);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // Kept around for legacy reasons. This method is supposed to check that the
+ // headers can be converted to the appropriate charset, but we don't support
+ // encoding headers to non-UTF-8, so this is now moot.
+ if (fallbackCharset)
+ *fallbackCharset = nullptr;
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetDeliverMode(MSG_DeliverMode* aDeliverMode)
+{
+ NS_ENSURE_ARG_POINTER(aDeliverMode);
+ *aDeliverMode = mDeliverMode;
+ return NS_OK;
+}
+
+nsMsgMailList::nsMsgMailList(nsIAbDirectory* directory) :
+ mDirectory(directory)
+{
+ mDirectory->GetDirName(mName);
+ mDirectory->GetDescription(mDescription);
+
+ if (mDescription.IsEmpty())
+ mDescription = mName;
+
+ mDirectory = directory;
+}
diff --git a/mailnews/compose/src/nsMsgCompose.h b/mailnews/compose/src/nsMsgCompose.h
new file mode 100644
index 000000000..fb2f5a594
--- /dev/null
+++ b/mailnews/compose/src/nsMsgCompose.h
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgCompose_H_
+#define _nsMsgCompose_H_
+
+#include "nsIMsgCompose.h"
+#include "nsCOMArray.h"
+#include "nsTObserverArray.h"
+#include "nsWeakReference.h"
+#include "nsMsgCompFields.h"
+#include "nsIOutputStream.h"
+#include "nsIMsgQuote.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIBaseWindow.h"
+#include "nsIAbDirectory.h"
+#include "nsIWebProgressListener.h"
+#include "nsIMimeConverter.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsIMsgFolder.h"
+#include "nsIDOMNode.h"
+#include "mozIDOMWindow.h"
+
+// Forward declares
+class QuotingOutputStreamListener;
+class nsMsgComposeSendListener;
+class nsIEditorMailSupport;
+class nsIRDFService;
+class nsIArray;
+struct nsMsgMailList;
+
+class nsMsgCompose : public nsIMsgCompose, public nsSupportsWeakReference
+{
+ public:
+
+ nsMsgCompose();
+
+ /* this macro defines QueryInterface, AddRef and Release for this class */
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /*** nsIMsgCompose pure virtual functions */
+ NS_DECL_NSIMSGCOMPOSE
+
+ /* nsIMsgSendListener interface */
+ NS_DECL_NSIMSGSENDLISTENER
+
+protected:
+ virtual ~nsMsgCompose();
+
+ // Deal with quoting issues...
+ nsresult QuoteOriginalMessage(); // New template
+ nsresult SetQuotingToFollow(bool aVal);
+ nsresult ConvertHTMLToText(nsIFile *aSigFile, nsString &aSigData);
+ nsresult ConvertTextToHTML(nsIFile *aSigFile, nsString &aSigData);
+ bool IsEmbeddedObjectSafe(const char * originalScheme,
+ const char * originalHost,
+ const char * originalPath,
+ nsIDOMNode * object);
+ nsresult ResetUrisForEmbeddedObjects();
+ nsresult TagEmbeddedObjects(nsIEditorMailSupport *aMailEditor);
+
+ nsCString mQuoteCharset;
+ nsCString mOriginalMsgURI; // used so we can mark message disposition flags after we send the message
+
+ int32_t mWhatHolder;
+
+ nsresult LoadDataFromFile(nsIFile *file,
+ nsString &sigData,
+ bool aAllowUTF8 = true,
+ bool aAllowUTF16 = true);
+
+ bool CheckIncludeSignaturePrefs(nsIMsgIdentity *identity);
+ //m_folderName to store the value of the saved drafts folder.
+ nsCString m_folderName;
+ void InsertDivWrappedTextAtSelection(const nsAString &aText,
+ const nsAString &classStr);
+
+ protected:
+ nsresult CreateMessage(const char * originalMsgURI, MSG_ComposeType type, nsIMsgCompFields* compFields);
+ void CleanUpRecipients(nsString& recipients);
+ nsresult GetABDirAndMailLists(const nsACString& aDirUri,
+ nsCOMArray<nsIAbDirectory>& aDirArray,
+ nsTArray<nsMsgMailList>& aMailListArray);
+ nsresult ResolveMailList(nsIAbDirectory* aMailList,
+ nsCOMArray<nsIAbDirectory>& allDirectoriesArray,
+ nsTArray<nsMsgMailList>& allMailListArray,
+ nsTArray<nsMsgMailList>& mailListResolved,
+ nsTArray<nsMsgRecipient>& aListMembers);
+ nsresult TagConvertible(nsIDOMElement *node, int32_t *_retval);
+ nsresult _NodeTreeConvertible(nsIDOMElement *node, int32_t *_retval);
+ nsresult MoveToAboveQuote(void);
+ nsresult MoveToBeginningOfDocument(void);
+ nsresult MoveToEndOfDocument(void);
+ nsresult ReplaceFileURLs(nsString &sigData);
+ nsresult DataURLForFileURL(const nsAString &aFileURL, nsAString &aDataURL);
+
+// 3 = To, Cc, Bcc
+#define MAX_OF_RECIPIENT_ARRAY 3
+ typedef nsTArray<nsMsgRecipient> RecipientsArray[MAX_OF_RECIPIENT_ARRAY];
+ /**
+ * This method parses the compose fields and associates email addresses with
+ * the relevant cards from the address books.
+ */
+ nsresult LookupAddressBook(RecipientsArray &recipientList);
+ bool IsLastWindow();
+
+ // Helper function. Parameters are not checked.
+ bool mConvertStructs; // for TagConvertible
+
+ nsCOMPtr<nsIEditor> m_editor;
+ mozIDOMWindowProxy *m_window;
+ nsCOMPtr<nsIDocShell> mDocShell;
+ nsCOMPtr<nsIBaseWindow> m_baseWindow;
+ nsMsgCompFields *m_compFields;
+ nsCOMPtr<nsIMsgIdentity> m_identity;
+ bool m_composeHTML;
+ QuotingOutputStreamListener *mQuoteStreamListener;
+ nsCOMPtr<nsIOutputStream> mBaseStream;
+
+ nsCOMPtr<nsIMsgSend> mMsgSend; // for composition back end
+ nsCOMPtr<nsIMsgProgress> mProgress; // use by the back end to report progress to the front end
+
+ // Deal with quoting issues...
+ nsString mCiteReference;
+ nsCOMPtr<nsIMsgQuote> mQuote;
+ bool mQuotingToFollow; // Quoting indicator
+ MSG_ComposeType mType; // Message type
+ bool mCharsetOverride;
+ bool mAnswerDefaultCharset;
+ bool mDeleteDraft;
+ nsMsgDispositionState mDraftDisposition;
+ nsCOMPtr <nsIMsgDBHdr> mOrigMsgHdr;
+
+ nsCString mSmtpPassword;
+ nsCString mHtmlToQuote;
+
+ nsTObserverArray<nsCOMPtr<nsIMsgComposeStateListener> > mStateListeners;
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener> > mExternalSendListeners;
+
+ bool mInsertingQuotedContent;
+ MSG_DeliverMode mDeliverMode; // nsIMsgCompDeliverMode long.
+
+ friend class QuotingOutputStreamListener;
+ friend class nsMsgComposeSendListener;
+};
+
+////////////////////////////////////////////////////////////////////////////////////
+// THIS IS THE CLASS THAT IS THE STREAM Listener OF THE HTML OUTPUT
+// FROM LIBMIME. THIS IS FOR QUOTING
+////////////////////////////////////////////////////////////////////////////////////
+class QuotingOutputStreamListener : public nsIMsgQuotingOutputStreamListener
+{
+public:
+ QuotingOutputStreamListener(const char *originalMsgURI,
+ nsIMsgDBHdr *origMsgHdr,
+ bool quoteHeaders,
+ bool headersOnly,
+ nsIMsgIdentity *identity,
+ nsIMsgQuote* msgQuote,
+ bool charsetFixed,
+ bool quoteOriginal,
+ const nsACString& htmlToQuote);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIMSGQUOTINGOUTPUTSTREAMLISTENER
+
+ NS_IMETHOD SetComposeObj(nsIMsgCompose *obj);
+ NS_IMETHOD ConvertToPlainText(bool formatflowed,
+ bool delsp,
+ bool formatted,
+ bool disallowBreaks);
+ NS_IMETHOD InsertToCompose(nsIEditor *aEditor, bool aHTMLEditor);
+ NS_IMETHOD AppendToMsgBody(const nsCString &inStr);
+
+private:
+ virtual ~QuotingOutputStreamListener();
+ nsWeakPtr mWeakComposeObj;
+ nsString mMsgBody;
+ nsString mCitePrefix;
+ nsString mSignature;
+ bool mQuoteHeaders;
+ bool mHeadersOnly;
+ bool mCharsetFixed;
+ nsCOMPtr<nsIMsgQuote> mQuote;
+ nsCOMPtr<nsIMimeHeaders> mHeaders;
+ nsCOMPtr<nsIMsgIdentity> mIdentity;
+ nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr;
+ nsString mCiteReference;
+ nsCOMPtr<nsIMimeConverter> mMimeConverter;
+ nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
+ int32_t mUnicodeBufferCharacterLength;
+ char16_t* mUnicodeConversionBuffer;
+ bool mQuoteOriginal;
+ nsCString mHtmlToQuote;
+};
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for the send operation. We have to create this class
+// to listen for message send completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+class nsMsgComposeSendListener : public nsIMsgComposeSendListener, public nsIMsgSendListener, public nsIMsgCopyServiceListener, public nsIWebProgressListener
+{
+public:
+ nsMsgComposeSendListener(void);
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsIMsgComposeSendListener interface
+ NS_DECL_NSIMSGCOMPOSESENDLISTENER
+
+ // nsIMsgSendListener interface
+ NS_DECL_NSIMSGSENDLISTENER
+
+ // nsIMsgCopyServiceListener interface
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ // nsIWebProgressListener interface
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ nsresult RemoveCurrentDraftMessage(nsIMsgCompose *compObj, bool calledByCopy);
+ nsresult GetMsgFolder(nsIMsgCompose *compObj, nsIMsgFolder **msgFolder);
+
+private:
+ virtual ~nsMsgComposeSendListener();
+ nsWeakPtr mWeakComposeObj;
+ MSG_DeliverMode mDeliverMode;
+};
+
+/******************************************************************************
+ * nsMsgMailList
+ ******************************************************************************/
+struct nsMsgMailList
+{
+ explicit nsMsgMailList(nsIAbDirectory* directory);
+
+ nsString mName;
+ nsString mDescription;
+ nsCOMPtr<nsIAbDirectory> mDirectory;
+};
+
+#endif /* _nsMsgCompose_H_ */
diff --git a/mailnews/compose/src/nsMsgComposeContentHandler.cpp b/mailnews/compose/src/nsMsgComposeContentHandler.cpp
new file mode 100644
index 000000000..cc52bcb67
--- /dev/null
+++ b/mailnews/compose/src/nsMsgComposeContentHandler.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgComposeContentHandler.h"
+#include "nsMsgComposeService.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgCompCID.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "plstr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsNetUtil.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgAccountManager.h"
+
+static NS_DEFINE_CID(kMsgComposeServiceCID, NS_MSGCOMPOSESERVICE_CID);
+
+nsMsgComposeContentHandler::nsMsgComposeContentHandler()
+{
+}
+
+// The following macro actually implement addref, release and query interface
+// for our component.
+NS_IMPL_ISUPPORTS(nsMsgComposeContentHandler, nsIContentHandler)
+
+nsMsgComposeContentHandler::~nsMsgComposeContentHandler()
+{
+}
+
+// Try to get an appropriate nsIMsgIdentity by going through the window, getting
+// the document's URI, then the corresponding nsIMsgDBHdr. Then find the server
+// associated with that header and get the first identity for it.
+nsresult nsMsgComposeContentHandler::GetBestIdentity(
+ nsIInterfaceRequestor* aWindowContext, nsIMsgIdentity **aIdentity)
+{
+ nsresult rv;
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow = do_GetInterface(aWindowContext);
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(domWindow);
+
+ nsAutoString documentURIString;
+ rv = window->GetDoc()->GetDocumentURI(documentURIString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> documentURI;
+ rv = NS_NewURI(getter_AddRefs(documentURI), documentURIString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMessageUrl> msgURI = do_QueryInterface(documentURI);
+ if (!msgURI)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgURI->GetMessageHeader(getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // nsIMsgDBHdrs from .eml messages have a null folder, so bail out if that's
+ // the case.
+ if (!folder)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(
+ NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = accountManager->GetFirstIdentityForServer(server, aIdentity);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgComposeContentHandler::HandleContent(const char * aContentType,
+ nsIInterfaceRequestor* aWindowContext, nsIRequest *request)
+{
+ nsresult rv = NS_OK;
+ if (!request)
+ return NS_ERROR_NULL_POINTER;
+
+ // First of all, get the content type and make sure it is a content type we
+ // know how to handle!
+ if (PL_strcasecmp(aContentType, "application/x-mailto") == 0) {
+ nsCOMPtr<nsIMsgIdentity> identity;
+
+ if (aWindowContext)
+ GetBestIdentity(aWindowContext, getter_AddRefs(identity));
+
+ nsCOMPtr<nsIURI> aUri;
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if(!aChannel) return NS_ERROR_FAILURE;
+
+ rv = aChannel->GetURI(getter_AddRefs(aUri));
+ if (aUri)
+ {
+ nsCOMPtr<nsIMsgComposeService> composeService =
+ do_GetService(kMsgComposeServiceCID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = composeService->OpenComposeWindowWithURI(nullptr, aUri, identity);
+ }
+ } else {
+ // The content-type was not application/x-mailto...
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+
+ return rv;
+}
diff --git a/mailnews/compose/src/nsMsgComposeContentHandler.h b/mailnews/compose/src/nsMsgComposeContentHandler.h
new file mode 100644
index 000000000..360a83608
--- /dev/null
+++ b/mailnews/compose/src/nsMsgComposeContentHandler.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIContentHandler.h"
+#include "nsIMsgIdentity.h"
+
+class nsMsgComposeContentHandler : public nsIContentHandler
+{
+public:
+ nsMsgComposeContentHandler();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTHANDLER
+private:
+ virtual ~nsMsgComposeContentHandler();
+ nsresult GetBestIdentity(nsIInterfaceRequestor* aWindowContext,
+ nsIMsgIdentity **identity);
+};
diff --git a/mailnews/compose/src/nsMsgComposeParams.cpp b/mailnews/compose/src/nsMsgComposeParams.cpp
new file mode 100644
index 000000000..46f964cee
--- /dev/null
+++ b/mailnews/compose/src/nsMsgComposeParams.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgComposeParams.h"
+
+nsMsgComposeParams::nsMsgComposeParams() :
+ mType(nsIMsgCompType::New),
+ mFormat(nsIMsgCompFormat::Default),
+ mBodyIsLink(false)
+{
+}
+
+/* the following macro actually implement addref, release and query interface for our component. */
+NS_IMPL_ISUPPORTS(nsMsgComposeParams, nsIMsgComposeParams)
+
+nsMsgComposeParams::~nsMsgComposeParams()
+{
+}
+
+/* attribute MSG_ComposeType type; */
+NS_IMETHODIMP nsMsgComposeParams::GetType(MSG_ComposeType *aType)
+{
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aType = mType;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetType(MSG_ComposeType aType)
+{
+ mType = aType;
+ return NS_OK;
+}
+
+/* attribute MSG_ComposeFormat format; */
+NS_IMETHODIMP nsMsgComposeParams::GetFormat(MSG_ComposeFormat *aFormat)
+{
+ NS_ENSURE_ARG_POINTER(aFormat);
+
+ *aFormat = mFormat;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetFormat(MSG_ComposeFormat aFormat)
+{
+ mFormat = aFormat;
+ return NS_OK;
+}
+
+/* attribute string originalMsgURI; */
+NS_IMETHODIMP nsMsgComposeParams::GetOriginalMsgURI(char * *aOriginalMsgURI)
+{
+ NS_ENSURE_ARG_POINTER(aOriginalMsgURI);
+
+ *aOriginalMsgURI = ToNewCString(mOriginalMsgUri);
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetOriginalMsgURI(const char * aOriginalMsgURI)
+{
+ mOriginalMsgUri = aOriginalMsgURI;
+ return NS_OK;
+}
+
+/* attribute nsIMsgIdentity identity; */
+NS_IMETHODIMP nsMsgComposeParams::GetIdentity(nsIMsgIdentity * *aIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ NS_IF_ADDREF(*aIdentity = mIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeParams::SetIdentity(nsIMsgIdentity * aIdentity)
+{
+ mIdentity = aIdentity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeParams::SetOrigMsgHdr(nsIMsgDBHdr *aMsgHdr)
+{
+ mOrigMsgHdr = aMsgHdr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeParams::GetOrigMsgHdr(nsIMsgDBHdr * *aMsgHdr)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_IF_ADDREF(*aMsgHdr = mOrigMsgHdr);
+ return NS_OK;
+}
+
+/* attribute ACString htmlToQuote; */
+NS_IMETHODIMP nsMsgComposeParams::GetHtmlToQuote(nsACString& aHtmlToQuote)
+{
+ aHtmlToQuote = mHtmlToQuote;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetHtmlToQuote(const nsACString& aHtmlToQuote)
+{
+ mHtmlToQuote = aHtmlToQuote;
+ return NS_OK;
+}
+
+/* attribute nsIMsgCompFields composeFields; */
+NS_IMETHODIMP nsMsgComposeParams::GetComposeFields(nsIMsgCompFields * *aComposeFields)
+{
+ NS_ENSURE_ARG_POINTER(aComposeFields);
+
+ if (mComposeFields)
+ {
+ *aComposeFields = mComposeFields;
+ NS_ADDREF(*aComposeFields);
+ }
+ else
+ *aComposeFields = nullptr;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetComposeFields(nsIMsgCompFields * aComposeFields)
+{
+ mComposeFields = aComposeFields;
+ return NS_OK;
+}
+
+/* attribute boolean bodyIsLink; */
+NS_IMETHODIMP nsMsgComposeParams::GetBodyIsLink(bool *aBodyIsLink)
+{
+ NS_ENSURE_ARG_POINTER(aBodyIsLink);
+
+ *aBodyIsLink = mBodyIsLink;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetBodyIsLink(bool aBodyIsLink)
+{
+ mBodyIsLink = aBodyIsLink;
+ return NS_OK;
+}
+
+/* attribute nsIMsgSendLisneter sendListener; */
+NS_IMETHODIMP nsMsgComposeParams::GetSendListener(nsIMsgSendListener * *aSendListener)
+{
+ NS_ENSURE_ARG_POINTER(aSendListener);
+
+ if (mSendListener)
+ {
+ *aSendListener = mSendListener;
+ NS_ADDREF(*aSendListener);
+ }
+ else
+ *aSendListener = nullptr;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetSendListener(nsIMsgSendListener * aSendListener)
+{
+ mSendListener = aSendListener;
+ return NS_OK;
+}
+
+/* attribute string smtpPassword; */
+NS_IMETHODIMP nsMsgComposeParams::GetSmtpPassword(char * *aSmtpPassword)
+{
+ NS_ENSURE_ARG_POINTER(aSmtpPassword);
+
+ *aSmtpPassword = ToNewCString(mSMTPPassword);
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetSmtpPassword(const char * aSmtpPassword)
+{
+ mSMTPPassword = aSmtpPassword;
+ return NS_OK;
+}
+
diff --git a/mailnews/compose/src/nsMsgComposeParams.h b/mailnews/compose/src/nsMsgComposeParams.h
new file mode 100644
index 000000000..00eaaa335
--- /dev/null
+++ b/mailnews/compose/src/nsMsgComposeParams.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMsgComposeParams.h"
+#include "nsStringGlue.h"
+#include "nsIMsgHdr.h"
+#include "nsCOMPtr.h"
+class nsMsgComposeParams : public nsIMsgComposeParams
+{
+public:
+ nsMsgComposeParams();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOMPOSEPARAMS
+
+private:
+ virtual ~nsMsgComposeParams();
+ MSG_ComposeType mType;
+ MSG_ComposeFormat mFormat;
+ nsCString mOriginalMsgUri;
+ nsCOMPtr<nsIMsgIdentity> mIdentity;
+ nsCOMPtr<nsIMsgCompFields> mComposeFields;
+ bool mBodyIsLink;
+ nsCOMPtr<nsIMsgSendListener> mSendListener;
+ nsCString mSMTPPassword;
+ nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr;
+ nsCString mHtmlToQuote;
+};
diff --git a/mailnews/compose/src/nsMsgComposeProgressParams.cpp b/mailnews/compose/src/nsMsgComposeProgressParams.cpp
new file mode 100644
index 000000000..23c5507c5
--- /dev/null
+++ b/mailnews/compose/src/nsMsgComposeProgressParams.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgComposeProgressParams.h"
+#include "nsServiceManagerUtils.h"
+
+NS_IMPL_ISUPPORTS(nsMsgComposeProgressParams, nsIMsgComposeProgressParams)
+
+nsMsgComposeProgressParams::nsMsgComposeProgressParams() :
+ m_deliveryMode(nsIMsgCompDeliverMode::Now)
+{
+}
+
+nsMsgComposeProgressParams::~nsMsgComposeProgressParams()
+{
+}
+
+/* attribute wstring subject; */
+NS_IMETHODIMP nsMsgComposeProgressParams::GetSubject(char16_t * *aSubject)
+{
+ NS_ENSURE_ARG(aSubject);
+
+ *aSubject = ToNewUnicode(m_subject);
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeProgressParams::SetSubject(const char16_t * aSubject)
+{
+ m_subject = aSubject;
+ return NS_OK;
+}
+
+/* attribute MSG_DeliverMode deliveryMode; */
+NS_IMETHODIMP nsMsgComposeProgressParams::GetDeliveryMode(MSG_DeliverMode *aDeliveryMode)
+{
+ NS_ENSURE_ARG(aDeliveryMode);
+
+ *aDeliveryMode = m_deliveryMode;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeProgressParams::SetDeliveryMode(MSG_DeliverMode aDeliveryMode)
+{
+ m_deliveryMode = aDeliveryMode;
+ return NS_OK;
+}
diff --git a/mailnews/compose/src/nsMsgComposeProgressParams.h b/mailnews/compose/src/nsMsgComposeProgressParams.h
new file mode 100644
index 000000000..6d9598f16
--- /dev/null
+++ b/mailnews/compose/src/nsMsgComposeProgressParams.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMsgComposeProgressParams.h"
+
+class nsMsgComposeProgressParams : public nsIMsgComposeProgressParams
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOMPOSEPROGRESSPARAMS
+
+ nsMsgComposeProgressParams();
+
+private:
+ virtual ~nsMsgComposeProgressParams();
+ nsString m_subject;
+ MSG_DeliverMode m_deliveryMode;
+};
diff --git a/mailnews/compose/src/nsMsgComposeService.cpp b/mailnews/compose/src/nsMsgComposeService.cpp
new file mode 100644
index 000000000..bec88f8fe
--- /dev/null
+++ b/mailnews/compose/src/nsMsgComposeService.cpp
@@ -0,0 +1,1479 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgComposeService.h"
+#include "nsMsgCompCID.h"
+#include "nsIMsgSend.h"
+#include "nsIServiceManager.h"
+#include "nsIObserverService.h"
+#include "nsIMsgIdentity.h"
+#include "nsISmtpUrl.h"
+#include "nsIURI.h"
+#include "nsMsgI18N.h"
+#include "nsIMsgComposeParams.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIWindowWatcher.h"
+#include "mozIDOMWindow.h"
+#include "nsIContentViewer.h"
+#include "nsIMsgWindow.h"
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIXULWindow.h"
+#include "nsIWindowMediator.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIBaseWindow.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsIStreamConverter.h"
+#include "nsMsgMimeCID.h"
+#include "nsToolkitCompsCID.h"
+#include "nsNetUtil.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIMsgDatabase.h"
+#include "nsIDocumentEncoder.h"
+#include "nsContentCID.h"
+#include "nsISelection.h"
+#include "nsUTF8Utils.h"
+#include "nsILineBreaker.h"
+#include "nsLWBrkCIID.h"
+#include "mozilla/Services.h"
+#include "mimemoz2.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+#include "mozilla/Logging.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#endif
+
+#include "nsICommandLine.h"
+#include "nsIAppStartup.h"
+#include "nsMsgUtils.h"
+#include "nsIPrincipal.h"
+
+#ifdef XP_WIN32
+#include <windows.h>
+#include <shellapi.h>
+#include "nsIWidget.h"
+#endif
+
+#define DEFAULT_CHROME "chrome://messenger/content/messengercompose/messengercompose.xul"
+
+#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION "mailnews.reply_quoting_selection"
+#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION_MULTI_WORD "mailnews.reply_quoting_selection.multi_word"
+#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION_ONLY_IF "mailnews.reply_quoting_selection.only_if_chars"
+
+#define MAIL_ROOT_PREF "mail."
+#define MAILNEWS_ROOT_PREF "mailnews."
+#define HTMLDOMAINUPDATE_VERSION_PREF_NAME "global_html_domains.version"
+#define HTMLDOMAINUPDATE_DOMAINLIST_PREF_NAME "global_html_domains"
+#define USER_CURRENT_HTMLDOMAINLIST_PREF_NAME "html_domains"
+#define USER_CURRENT_PLAINTEXTDOMAINLIST_PREF_NAME "plaintext_domains"
+#define DOMAIN_DELIMITER ','
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+static PRLogModuleInfo *MsgComposeLogModule = nullptr;
+
+static uint32_t GetMessageSizeFromURI(const char * originalMsgURI)
+{
+ uint32_t msgSize = 0;
+
+ if (originalMsgURI && *originalMsgURI)
+ {
+ nsCOMPtr <nsIMsgDBHdr> originalMsgHdr;
+ GetMsgDBHdrFromURI(originalMsgURI, getter_AddRefs(originalMsgHdr));
+ if (originalMsgHdr)
+ originalMsgHdr->GetMessageSize(&msgSize);
+ }
+
+ return msgSize;
+}
+#endif
+
+nsMsgComposeService::nsMsgComposeService()
+{
+
+// Defaulting the value of mLogComposePerformance to FALSE to prevent logging.
+ mLogComposePerformance = false;
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ if (!MsgComposeLogModule)
+ MsgComposeLogModule = PR_NewLogModule("msgcompose");
+
+ mStartTime = PR_IntervalNow();
+ mPreviousTime = mStartTime;
+#endif
+
+}
+
+NS_IMPL_ISUPPORTS(nsMsgComposeService,
+ nsIMsgComposeService,
+ ICOMMANDLINEHANDLER,
+ nsISupportsWeakReference)
+
+nsMsgComposeService::~nsMsgComposeService()
+{
+ mOpenComposeWindows.Clear();
+}
+
+nsresult nsMsgComposeService::Init()
+{
+ nsresult rv = NS_OK;
+
+ Reset();
+
+ AddGlobalHtmlDomains();
+ // Since the compose service should only be initialized once, we can
+ // be pretty sure there aren't any existing compose windows open.
+ MsgCleanupTempFiles("nsmail", "tmp");
+ MsgCleanupTempFiles("nsemail", "html");
+ MsgCleanupTempFiles("nscopy", "tmp");
+ return rv;
+}
+
+void nsMsgComposeService::Reset()
+{
+ mOpenComposeWindows.Clear();
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ prefs->GetBoolPref("mailnews.logComposePerformance", &mLogComposePerformance);
+}
+
+// Function to open a message compose window and pass an nsIMsgComposeParams
+// parameter to it.
+NS_IMETHODIMP
+nsMsgComposeService::OpenComposeWindowWithParams(const char *chrome,
+ nsIMsgComposeParams *params)
+{
+ NS_ENSURE_ARG_POINTER(params);
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ if(mLogComposePerformance)
+ {
+ TimeStamp("Start opening the window", true);
+ }
+#endif
+
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(params);
+
+ //Use default identity if no identity has been specified
+ nsCOMPtr<nsIMsgIdentity> identity;
+ params->GetIdentity(getter_AddRefs(identity));
+ if (!identity)
+ {
+ GetDefaultIdentity(getter_AddRefs(identity));
+ params->SetIdentity(identity);
+ }
+
+ // Create a new window.
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (!wwatch)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISupportsInterfacePointer> msgParamsWrapper =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgParamsWrapper->SetData(params);
+ msgParamsWrapper->SetDataIID(&NS_GET_IID(nsIMsgComposeParams));
+
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ rv = wwatch->OpenWindow(0, chrome && *chrome ? chrome : DEFAULT_CHROME,
+ "_blank", "all,chrome,dialog=no,status,toolbar", msgParamsWrapper,
+ getter_AddRefs(newWindow));
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::DetermineComposeHTML(nsIMsgIdentity *aIdentity, MSG_ComposeFormat aFormat, bool *aComposeHTML)
+{
+ NS_ENSURE_ARG_POINTER(aComposeHTML);
+
+ *aComposeHTML = true;
+ switch (aFormat)
+ {
+ case nsIMsgCompFormat::HTML:
+ *aComposeHTML = true;
+ break;
+ case nsIMsgCompFormat::PlainText:
+ *aComposeHTML = false;
+ break;
+
+ default:
+ nsCOMPtr<nsIMsgIdentity> identity = aIdentity;
+ if (!identity)
+ GetDefaultIdentity(getter_AddRefs(identity));
+
+ if (identity)
+ {
+ identity->GetComposeHtml(aComposeHTML);
+ if (aFormat == nsIMsgCompFormat::OppositeOfDefault)
+ *aComposeHTML = !*aComposeHTML;
+ }
+ else
+ {
+ // default identity not found. Use the mail.html_compose pref to determine
+ // message compose type (HTML or PlainText).
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ {
+ nsresult rv;
+ bool useHTMLCompose;
+ rv = prefs->GetBoolPref(MAIL_ROOT_PREF "html_compose", &useHTMLCompose);
+ if (NS_SUCCEEDED(rv))
+ *aComposeHTML = useHTMLCompose;
+ }
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeService::GetOrigWindowSelection(MSG_ComposeType type, nsIMsgWindow *aMsgWindow, nsACString& aSelHTML)
+{
+ nsresult rv;
+
+ // Good hygiene
+ aSelHTML.Truncate();
+
+ // Get the pref to see if we even should do reply quoting selection
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool replyQuotingSelection;
+ rv = prefs->GetBoolPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION, &replyQuotingSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!replyQuotingSelection)
+ return NS_ERROR_ABORT;
+
+ // Now delve down in to the message to get the HTML representation of the selection
+ nsCOMPtr<nsIDocShell> rootDocShell;
+ rv = aMsgWindow->GetRootDocShell(getter_AddRefs(rootDocShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocShellTreeItem> childAsItem;
+ rv = rootDocShell->FindChildWithName(NS_LITERAL_STRING("messagepane"),
+ true, false, nullptr, nullptr, getter_AddRefs(childAsItem));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(childAsItem, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow(do_GetInterface(childAsItem));
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(domWindow);
+ nsCOMPtr<nsISelection> sel = privateWindow->GetSelection();
+ NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE);
+
+ bool requireMultipleWords = true;
+ nsAutoCString charsOnlyIf;
+ prefs->GetBoolPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION_MULTI_WORD, &requireMultipleWords);
+ prefs->GetCharPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION_ONLY_IF, getter_Copies(charsOnlyIf));
+ if (sel && (requireMultipleWords || !charsOnlyIf.IsEmpty()))
+ {
+ nsAutoString selPlain;
+ rv = sel->ToString(selPlain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If "mailnews.reply_quoting_selection.multi_word" is on, then there must be at least
+ // two words selected in order to quote just the selected text
+ if (requireMultipleWords)
+ {
+ if (selPlain.IsEmpty())
+ return NS_ERROR_ABORT;
+
+ nsCOMPtr<nsILineBreaker> lineBreaker = do_GetService(NS_LBRK_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ const uint32_t length = selPlain.Length();
+ const char16_t* unicodeStr = selPlain.get();
+ int32_t endWordPos = lineBreaker->Next(unicodeStr, length, 0);
+
+ // If there's not even one word, then there's not multiple words
+ if (endWordPos == NS_LINEBREAKER_NEED_MORE_TEXT)
+ return NS_ERROR_ABORT;
+
+ // If after the first word is only space, then there's not multiple words
+ const char16_t* end;
+ for (end = unicodeStr + endWordPos; NS_IsSpace(*end); end++)
+ ;
+ if (!*end)
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ if (!charsOnlyIf.IsEmpty())
+ {
+ if (MsgFindCharInSet(selPlain, charsOnlyIf.get()) < 0)
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ nsCOMPtr<nsIContentViewer> contentViewer;
+ rv = docShell->GetContentViewer(getter_AddRefs(contentViewer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ rv = contentViewer->GetDOMDocument(getter_AddRefs(domDocument));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocumentEncoder> docEncoder(do_CreateInstance(NS_HTMLCOPY_ENCODER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docEncoder->Init(domDocument, NS_LITERAL_STRING("text/html"), 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docEncoder->SetSelection(sel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString selHTML;
+ rv = docEncoder->EncodeToString(selHTML);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now remove <span class="moz-txt-citetags">&gt; </span>.
+ nsAutoCString html(NS_ConvertUTF16toUTF8(selHTML).get());
+ int32_t spanInd = html.Find("<span class=\"moz-txt-citetags\">");
+ while (spanInd != kNotFound) {
+ nsAutoCString right0(Substring(html, spanInd));
+ int32_t endInd = right0.Find("</span>");
+ if (endInd == kNotFound)
+ break; // oops, where is the closing tag gone?
+ nsAutoCString right1(Substring(html, spanInd + endInd + 7));
+ html.SetLength(spanInd);
+ html.Append(right1);
+ spanInd = html.Find("<span class=\"moz-txt-citetags\">");
+ }
+
+ aSelHTML.Assign(html);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::OpenComposeWindow(const char *msgComposeWindowURL, nsIMsgDBHdr *origMsgHdr, const char *originalMsgURI,
+ MSG_ComposeType type, MSG_ComposeFormat format, nsIMsgIdentity * aIdentity, nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+
+ // Check for any reply type that wants to ignore the quote.
+ bool ignoreQuote = false;
+ if (type >= nsIMsgCompType::ReplyIgnoreQuote) {
+ type -= nsIMsgCompType::ReplyIgnoreQuote;
+ ignoreQuote = true;
+ }
+
+ nsCOMPtr<nsIMsgIdentity> identity = aIdentity;
+ if (!identity)
+ GetDefaultIdentity(getter_AddRefs(identity));
+
+ /* Actually, the only way to implement forward inline is to simulate a template message.
+ Maybe one day when we will have more time we can change that
+ */
+ if (type == nsIMsgCompType::ForwardInline || type == nsIMsgCompType::Draft || type == nsIMsgCompType::Template
+ || type == nsIMsgCompType::ReplyWithTemplate || type == nsIMsgCompType::Redirect)
+ {
+ nsAutoCString uriToOpen(originalMsgURI);
+ uriToOpen += (uriToOpen.FindChar('?') == kNotFound) ? '?' : '&';
+ uriToOpen.Append("fetchCompleteMessage=true");
+ if (type == nsIMsgCompType::Redirect)
+ uriToOpen.Append("&redirect=true");
+
+ return LoadDraftOrTemplate(uriToOpen, type == nsIMsgCompType::ForwardInline || type == nsIMsgCompType::Draft ?
+ nsMimeOutput::nsMimeMessageDraftOrTemplate : nsMimeOutput::nsMimeMessageEditorTemplate,
+ identity, originalMsgURI, origMsgHdr, type == nsIMsgCompType::ForwardInline,
+ format == nsIMsgCompFormat::OppositeOfDefault, aMsgWindow);
+ }
+
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && pMsgComposeParams)
+ {
+ nsCOMPtr<nsIMsgCompFields> pMsgCompFields (do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && pMsgCompFields)
+ {
+ pMsgComposeParams->SetType(type);
+ pMsgComposeParams->SetFormat(format);
+ pMsgComposeParams->SetIdentity(identity);
+
+ // When doing a reply (except with a template) see if there's a selection that we should quote
+ if (!ignoreQuote &&
+ (type == nsIMsgCompType::Reply ||
+ type == nsIMsgCompType::ReplyAll ||
+ type == nsIMsgCompType::ReplyToSender ||
+ type == nsIMsgCompType::ReplyToGroup ||
+ type == nsIMsgCompType::ReplyToSenderAndGroup ||
+ type == nsIMsgCompType::ReplyToList))
+ {
+ nsAutoCString selHTML;
+ if (NS_SUCCEEDED(GetOrigWindowSelection(type, aMsgWindow, selHTML)))
+ pMsgComposeParams->SetHtmlToQuote(selHTML);
+ }
+
+ if (originalMsgURI && *originalMsgURI)
+ {
+ if (type == nsIMsgCompType::NewsPost)
+ {
+ nsAutoCString newsURI(originalMsgURI);
+ nsAutoCString group;
+ nsAutoCString host;
+
+ int32_t slashpos = newsURI.RFindChar('/');
+ if (slashpos > 0 )
+ {
+ // uri is "[s]news://host[:port]/group"
+ host = StringHead(newsURI, slashpos);
+ group = Substring(newsURI, slashpos + 1);
+
+ }
+ else
+ group = originalMsgURI;
+
+ nsAutoCString unescapedName;
+ MsgUnescapeString(group,
+ nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED,
+ unescapedName);
+ pMsgCompFields->SetNewsgroups(NS_ConvertUTF8toUTF16(unescapedName));
+ pMsgCompFields->SetNewspostUrl(host.get());
+ }
+ else
+ {
+ pMsgComposeParams->SetOriginalMsgURI(originalMsgURI);
+ pMsgComposeParams->SetOrigMsgHdr(origMsgHdr);
+ }
+ }
+
+ pMsgComposeParams->SetComposeFields(pMsgCompFields);
+
+ if(mLogComposePerformance)
+ {
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ // ducarroz, properly fix this in the case of new message (not a reply)
+ if (type != nsIMsgCompType::NewsPost) {
+ char buff[256];
+ sprintf(buff, "Start opening the window, message size = %d", GetMessageSizeFromURI(originalMsgURI));
+ TimeStamp(buff, true);
+ }
+#endif
+ }//end if(mLogComposePerformance)
+
+ rv = OpenComposeWindowWithParams(msgComposeWindowURL, pMsgComposeParams);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgComposeService::GetParamsForMailto(nsIURI * aURI, nsIMsgComposeParams ** aParams)
+{
+ nsresult rv = NS_OK;
+ if (aURI)
+ {
+ nsCOMPtr<nsIMailtoUrl> aMailtoUrl;
+ rv = aURI->QueryInterface(NS_GET_IID(nsIMailtoUrl), getter_AddRefs(aMailtoUrl));
+ if (NS_SUCCEEDED(rv))
+ {
+ MSG_ComposeFormat requestedComposeFormat = nsIMsgCompFormat::Default;
+ nsCString toPart;
+ nsCString ccPart;
+ nsCString bccPart;
+ nsCString subjectPart;
+ nsCString bodyPart;
+ nsCString newsgroup;
+ nsCString refPart;
+ nsCString HTMLBodyPart;
+
+ aMailtoUrl->GetMessageContents(toPart, ccPart, bccPart, subjectPart,
+ bodyPart, HTMLBodyPart, refPart,
+ newsgroup, &requestedComposeFormat);
+
+ nsAutoString sanitizedBody;
+
+ bool composeHTMLFormat;
+ DetermineComposeHTML(NULL, requestedComposeFormat, &composeHTMLFormat);
+
+ // If there was an 'html-body' param, finding it will have requested
+ // HTML format in GetMessageContents, so we try to use it first. If it's
+ // empty, but we are composing in HTML because of the user's prefs, the
+ // 'body' param needs to be escaped, since it's supposed to be plain
+ // text, but it then doesn't need to sanitized.
+ nsString rawBody;
+ if (HTMLBodyPart.IsEmpty())
+ {
+ if (composeHTMLFormat)
+ {
+ char *escaped = MsgEscapeHTML(bodyPart.get());
+ if (!escaped)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ CopyUTF8toUTF16(nsDependentCString(escaped), sanitizedBody);
+ free(escaped);
+ }
+ else
+ CopyUTF8toUTF16(bodyPart, rawBody);
+ }
+ else
+ CopyUTF8toUTF16(HTMLBodyPart, rawBody);
+
+ if (!rawBody.IsEmpty() && composeHTMLFormat)
+ {
+ //For security reason, we must sanitize the message body before accepting any html...
+
+ rv = HTMLSanitize(rawBody, sanitizedBody); // from mimemoz2.h
+
+ if (NS_FAILED(rv))
+ {
+ // Something went horribly wrong with parsing for html format
+ // in the body. Set composeHTMLFormat to false so we show the
+ // plain text mail compose.
+ composeHTMLFormat = false;
+ }
+ }
+
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && pMsgComposeParams)
+ {
+ pMsgComposeParams->SetType(nsIMsgCompType::MailToUrl);
+ pMsgComposeParams->SetFormat(composeHTMLFormat ? nsIMsgCompFormat::HTML : nsIMsgCompFormat::PlainText);
+
+
+ nsCOMPtr<nsIMsgCompFields> pMsgCompFields (do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv));
+ if (pMsgCompFields)
+ {
+ //ugghh more conversion work!!!!
+ pMsgCompFields->SetTo(NS_ConvertUTF8toUTF16(toPart));
+ pMsgCompFields->SetCc(NS_ConvertUTF8toUTF16(ccPart));
+ pMsgCompFields->SetBcc(NS_ConvertUTF8toUTF16(bccPart));
+ pMsgCompFields->SetNewsgroups(NS_ConvertUTF8toUTF16(newsgroup));
+ pMsgCompFields->SetReferences(refPart.get());
+ pMsgCompFields->SetSubject(NS_ConvertUTF8toUTF16(subjectPart));
+ pMsgCompFields->SetBody(composeHTMLFormat ? sanitizedBody : rawBody);
+ pMsgComposeParams->SetComposeFields(pMsgCompFields);
+
+ NS_ADDREF(*aParams = pMsgComposeParams);
+ return NS_OK;
+ }
+ } // if we created msg compose params....
+ } // if we had a mailto url
+ } // if we had a url...
+
+ // if we got here we must have encountered an error
+ *aParams = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgComposeService::OpenComposeWindowWithURI(const char * aMsgComposeWindowURL, nsIURI * aURI, nsIMsgIdentity *identity)
+{
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams;
+ nsresult rv = GetParamsForMailto(aURI, getter_AddRefs(pMsgComposeParams));
+ if (NS_SUCCEEDED(rv)) {
+ pMsgComposeParams->SetIdentity(identity);
+ rv = OpenComposeWindowWithParams(aMsgComposeWindowURL, pMsgComposeParams);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgComposeService::InitCompose(nsIMsgComposeParams *aParams,
+ mozIDOMWindowProxy *aWindow,
+ nsIDocShell *aDocShell,
+ nsIMsgCompose **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgCompose> msgCompose =
+ do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = msgCompose->Initialize(aParams, aWindow, aDocShell);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*_retval = msgCompose);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::GetDefaultIdentity(nsIMsgIdentity **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> defaultAccount;
+ rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return defaultAccount ? defaultAccount->GetDefaultIdentity(_retval) : NS_OK;
+}
+
+/* readonly attribute boolean logComposePerformance; */
+NS_IMETHODIMP nsMsgComposeService::GetLogComposePerformance(bool *aLogComposePerformance)
+{
+ *aLogComposePerformance = mLogComposePerformance;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeService::TimeStamp(const char * label, bool resetTime)
+{
+ if (!mLogComposePerformance)
+ return NS_OK;
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+
+ PRIntervalTime now;
+
+ if (resetTime)
+ {
+ MOZ_LOG(MsgComposeLogModule, mozilla::LogLevel::Info, ("\n[process]: [totalTime][deltaTime]\n--------------------\n"));
+
+ mStartTime = PR_IntervalNow();
+ mPreviousTime = mStartTime;
+ now = mStartTime;
+ }
+ else
+ now = PR_IntervalNow();
+
+ PRIntervalTime totalTime = PR_IntervalToMilliseconds(now - mStartTime);
+ PRIntervalTime deltaTime = PR_IntervalToMilliseconds(now - mPreviousTime);
+
+ MOZ_LOG(MsgComposeLogModule, mozilla::LogLevel::Info, ("[%3.2f][%3.2f] - %s\n",
+((double)totalTime/1000.0) + 0.005, ((double)deltaTime/1000.0) + 0.005, label));
+
+ mPreviousTime = now;
+#endif
+ return NS_OK;
+}
+
+class nsMsgTemplateReplyHelper final: public nsIStreamListener,
+ public nsIUrlListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ nsMsgTemplateReplyHelper();
+
+ nsCOMPtr<nsIMsgDBHdr> mHdrToReplyTo;
+ nsCOMPtr<nsIMsgDBHdr> mTemplateHdr;
+ nsCOMPtr<nsIMsgWindow> mMsgWindow;
+ nsCOMPtr<nsIMsgIdentity> mIdentity;
+ nsCString mTemplateBody;
+ bool mInMsgBody;
+ char mLastBlockChars[3];
+
+private:
+ ~nsMsgTemplateReplyHelper();
+};
+
+NS_IMPL_ISUPPORTS(nsMsgTemplateReplyHelper,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIUrlListener)
+
+nsMsgTemplateReplyHelper::nsMsgTemplateReplyHelper()
+{
+ mInMsgBody = false;
+ memset(mLastBlockChars, 0, sizeof(mLastBlockChars));
+}
+
+nsMsgTemplateReplyHelper::~nsMsgTemplateReplyHelper()
+{
+}
+
+
+NS_IMETHODIMP nsMsgTemplateReplyHelper::OnStartRunningUrl(nsIURI *aUrl)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTemplateReplyHelper::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ NS_ENSURE_SUCCESS(aExitCode, aExitCode);
+ nsresult rv;
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow;
+ if (mMsgWindow)
+ {
+ nsCOMPtr<nsIDocShell> docShell;
+ rv = mMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+ parentWindow = do_GetInterface(docShell);
+ NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE);
+ }
+
+ // create the compose params object
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv));
+ if (NS_FAILED(rv) || (!pMsgComposeParams) ) return rv ;
+ nsCOMPtr<nsIMsgCompFields> compFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv) ;
+
+ nsCString replyTo;
+ mHdrToReplyTo->GetStringProperty("replyTo", getter_Copies(replyTo));
+ if (replyTo.IsEmpty())
+ mHdrToReplyTo->GetAuthor(getter_Copies(replyTo));
+ compFields->SetTo(NS_ConvertUTF8toUTF16(replyTo));
+
+ nsString body;
+ nsString templateSubject, replySubject;
+
+ mHdrToReplyTo->GetMime2DecodedSubject(replySubject);
+ mTemplateHdr->GetMime2DecodedSubject(templateSubject);
+ nsString subject(NS_LITERAL_STRING("Auto: ")); // RFC 3834 3.1.5.
+ subject.Append(templateSubject);
+ if (!replySubject.IsEmpty())
+ {
+ subject.Append(NS_LITERAL_STRING(" (was: "));
+ subject.Append(replySubject);
+ subject.Append(NS_LITERAL_STRING(")"));
+ }
+
+ compFields->SetSubject(subject);
+ compFields->SetRawHeader("Auto-Submitted", NS_LITERAL_CSTRING("auto-replied"), nullptr);
+
+ nsCString charset;
+ rv = mTemplateHdr->GetCharset(getter_Copies(charset));
+ NS_ENSURE_SUCCESS(rv, rv);
+ compFields->SetCharacterSet(charset.get());
+ rv = nsMsgI18NConvertToUnicode(charset.get(), mTemplateBody, body);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "couldn't convert templ body to unicode");
+ compFields->SetBody(body);
+
+ nsCString msgUri;
+ nsCOMPtr <nsIMsgFolder> folder;
+ mHdrToReplyTo->GetFolder(getter_AddRefs(folder));
+ folder->GetUriForMsg(mHdrToReplyTo, msgUri);
+ // populate the compose params
+ pMsgComposeParams->SetType(nsIMsgCompType::ReplyWithTemplate);
+ pMsgComposeParams->SetFormat(nsIMsgCompFormat::Default);
+ pMsgComposeParams->SetIdentity(mIdentity);
+ pMsgComposeParams->SetComposeFields(compFields);
+ pMsgComposeParams->SetOriginalMsgURI(msgUri.get());
+
+ // create the nsIMsgCompose object to send the object
+ nsCOMPtr<nsIMsgCompose> pMsgCompose (do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /** initialize nsIMsgCompose, Send the message, wait for send completion response **/
+
+ rv = pMsgCompose->Initialize(pMsgComposeParams, parentWindow, nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, mIdentity, nullptr, nullptr, nullptr) ;
+}
+
+NS_IMETHODIMP
+nsMsgTemplateReplyHelper::OnStartRequest(nsIRequest* request, nsISupports* aSupport)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgTemplateReplyHelper::OnStopRequest(nsIRequest* request, nsISupports* aSupport,
+ nsresult status)
+{
+ if (NS_SUCCEEDED(status))
+ {
+ // now we've got the message body in mTemplateBody -
+ // need to set body in compose params and send the reply.
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgTemplateReplyHelper::OnDataAvailable(nsIRequest* request,
+ nsISupports* aSupport,
+ nsIInputStream* inStream,
+ uint64_t srcOffset,
+ uint32_t count)
+{
+ nsresult rv = NS_OK;
+
+ char readBuf[1024];
+
+ uint64_t available;
+ uint32_t readCount;
+ uint32_t maxReadCount = sizeof(readBuf) - 1;
+
+ rv = inStream->Available(&available);
+ while (NS_SUCCEEDED(rv) && available > 0)
+ {
+ uint32_t bodyOffset = 0, readOffset = 0;
+ if (!mInMsgBody && mLastBlockChars[0])
+ {
+ memcpy(readBuf, mLastBlockChars, 3);
+ readOffset = 3;
+ maxReadCount -= 3;
+ }
+ if (maxReadCount > available)
+ maxReadCount = (uint32_t)available;
+ memset(readBuf, 0, sizeof(readBuf));
+ rv = inStream->Read(readBuf + readOffset, maxReadCount, &readCount);
+ available -= readCount;
+ readCount += readOffset;
+ // we're mainly interested in the msg body, so we need to
+ // find the header/body delimiter of a blank line. A blank line
+ // looks like <CR><CR>, <LF><LF>, or <CRLF><CRLF>
+ if (!mInMsgBody)
+ {
+ for (uint32_t charIndex = 0; charIndex < readCount && !bodyOffset; charIndex++)
+ {
+ if (readBuf[charIndex] == '\r' || readBuf[charIndex] == '\n')
+ {
+ if (charIndex + 1 < readCount)
+ {
+ if (readBuf[charIndex] == readBuf[charIndex + 1])
+ {
+ // got header+body separator
+ bodyOffset = charIndex + 2;
+ break;
+ }
+ else if ((charIndex + 3 < readCount) && !strncmp(readBuf + charIndex, "\r\n\r\n", 4))
+ {
+ bodyOffset = charIndex + 4;
+ break;
+ }
+ }
+ }
+ }
+ mInMsgBody = bodyOffset != 0;
+ if (!mInMsgBody && readCount > 3) // still in msg hdrs
+ strncpy(mLastBlockChars, readBuf + readCount - 3, 3);
+ }
+ mTemplateBody.Append(readBuf + bodyOffset);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgComposeService::ReplyWithTemplate(nsIMsgDBHdr *aMsgHdr, const char *templateUri,
+ nsIMsgWindow *aMsgWindow, nsIMsgIncomingServer *aServer)
+{
+ // To reply with template, we need the message body of the template.
+ // I think we're going to need to stream the template message to ourselves,
+ // and construct the body, and call setBody on the compFields.
+ nsresult rv;
+ nsCOMPtr <nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIArray> identities;
+ rv = account->GetIdentities(getter_AddRefs(identities));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString recipients;
+ aMsgHdr->GetRecipients(getter_Copies(recipients));
+
+ nsAutoCString ccList;
+ aMsgHdr->GetCcList(getter_Copies(ccList));
+
+ // Go through the identities to see to whom this was addressed.
+ // In case we get no match, this is likely a list/bulk/bcc/spam mail and we
+ // shouldn't reply. RFC 3834 2.
+ nsCOMPtr<nsIMsgIdentity> identity; // identity to reply from
+ uint32_t count = 0;
+ identities->GetLength(&count);
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgIdentity> anIdentity(do_QueryElementAt(identities, i, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString identityEmail;
+ anIdentity->GetEmail(identityEmail);
+
+ if (recipients.Find(identityEmail, CaseInsensitiveCompare) != kNotFound ||
+ ccList.Find(identityEmail, CaseInsensitiveCompare) != kNotFound)
+ {
+ identity = anIdentity;
+ break;
+ }
+ }
+ if (!identity) // Found no match -> don't reply.
+ return NS_ERROR_ABORT;
+
+ RefPtr<nsMsgTemplateReplyHelper> helper = new nsMsgTemplateReplyHelper;
+
+ helper->mHdrToReplyTo = aMsgHdr;
+ helper->mMsgWindow = aMsgWindow;
+ helper->mIdentity = identity;
+
+ nsAutoCString replyTo;
+ aMsgHdr->GetStringProperty("replyTo", getter_Copies(replyTo));
+ if (replyTo.IsEmpty())
+ aMsgHdr->GetAuthor(getter_Copies(replyTo));
+ if (replyTo.IsEmpty())
+ return NS_ERROR_FAILURE; // nowhere to send the reply
+
+ nsCOMPtr <nsIMsgFolder> templateFolder;
+ nsCOMPtr <nsIMsgDatabase> templateDB;
+ nsCString templateMsgHdrUri;
+ const char * query = PL_strstr(templateUri, "?messageId=");
+ if (!query)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString folderUri(Substring(templateUri, query));
+ rv = GetExistingFolder(folderUri, getter_AddRefs(templateFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = templateFolder->GetMsgDatabase(getter_AddRefs(templateDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char *subject = PL_strstr(templateUri, "&subject=");
+ if (subject)
+ {
+ const char *subjectEnd = subject + strlen(subject);
+ nsAutoCString messageId(Substring(query + 11, subject));
+ nsAutoCString subjectString(Substring(subject + 9, subjectEnd));
+ templateDB->GetMsgHdrForMessageID(messageId.get(), getter_AddRefs(helper->mTemplateHdr));
+ if (helper->mTemplateHdr)
+ templateFolder->GetUriForMsg(helper->mTemplateHdr, templateMsgHdrUri);
+ // to use the subject, we'd need to expose a method to find a message by subject,
+ // or painfully iterate through messages...We'll try to make the message-id
+ // not change when saving a template first.
+ }
+ if (templateMsgHdrUri.IsEmpty())
+ {
+ // ### probably want to return a specific error and
+ // have the calling code disable the filter.
+ NS_ASSERTION(false, "failed to get msg hdr");
+ return NS_ERROR_FAILURE;
+ }
+ // we need to convert the template uri, which is of the form
+ // <folder uri>?messageId=<messageId>&subject=<subject>
+ nsCOMPtr <nsIMsgMessageService> msgService;
+ rv = GetMessageServiceFromURI(templateMsgHdrUri, getter_AddRefs(msgService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> listenerSupports;
+ helper->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(listenerSupports));
+
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = msgService->StreamMessage(templateMsgHdrUri.get(), listenerSupports,
+ aMsgWindow, helper,
+ false, // convert data
+ EmptyCString(), false, getter_AddRefs(dummyNull));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aMsgHdr->GetFolder(getter_AddRefs(folder));
+ if (!folder)
+ return NS_ERROR_NULL_POINTER;
+
+ // We're sending a new message. Conceptually it's a reply though, so mark the
+ // original message as replied.
+ return folder->AddMessageDispositionState(aMsgHdr, nsIMsgFolder::nsMsgDispositionState_Replied);
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::ForwardMessage(const nsAString &forwardTo,
+ nsIMsgDBHdr *aMsgHdr,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgIncomingServer *aServer,
+ uint32_t aForwardType)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+
+ nsresult rv;
+ if (aForwardType == nsIMsgComposeService::kForwardAsDefault)
+ {
+ int32_t forwardPref = 0;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetIntPref("mail.forward_message_mode", &forwardPref);
+ // 0=default as attachment 2=forward as inline with attachments,
+ // (obsolete 4.x value)1=forward as quoted (mapped to 2 in mozilla)
+ aForwardType = forwardPref == 0 ? nsIMsgComposeService::kForwardAsAttachment :
+ nsIMsgComposeService::kForwardInline;
+ }
+ nsCString msgUri;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aMsgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_TRUE(folder, NS_ERROR_NULL_POINTER);
+
+ folder->GetUriForMsg(aMsgHdr, msgUri);
+
+ nsAutoCString uriToOpen(msgUri);
+ uriToOpen += (uriToOpen.FindChar('?') == kNotFound) ? '?' : '&';
+ uriToOpen.Append("fetchCompleteMessage=true");
+
+ // get the MsgIdentity for the above key using AccountManager
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService (NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> account;
+ nsCOMPtr<nsIMsgIdentity> identity;
+
+ rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = account->GetDefaultIdentity(getter_AddRefs(identity));
+ // Use default identity if no identity has been found on this account
+ if (NS_FAILED(rv) || !identity)
+ {
+ rv = GetDefaultIdentity(getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aForwardType == nsIMsgComposeService::kForwardInline)
+ return RunMessageThroughMimeDraft(uriToOpen,
+ nsMimeOutput::nsMimeMessageDraftOrTemplate,
+ identity,
+ uriToOpen.get(), aMsgHdr,
+ true, forwardTo,
+ false, aMsgWindow);
+
+ nsCOMPtr<mozIDOMWindowProxy> parentWindow;
+ if (aMsgWindow)
+ {
+ nsCOMPtr<nsIDocShell> docShell;
+ rv = aMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+ parentWindow = do_GetInterface(docShell);
+ NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE);
+ }
+ // create the compose params object
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgCompFields> compFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv);
+
+ compFields->SetTo(forwardTo);
+ // populate the compose params
+ pMsgComposeParams->SetType(nsIMsgCompType::ForwardAsAttachment);
+ pMsgComposeParams->SetFormat(nsIMsgCompFormat::Default);
+ pMsgComposeParams->SetIdentity(identity);
+ pMsgComposeParams->SetComposeFields(compFields);
+ pMsgComposeParams->SetOriginalMsgURI(uriToOpen.get());
+ // create the nsIMsgCompose object to send the object
+ nsCOMPtr<nsIMsgCompose> pMsgCompose (do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /** initialize nsIMsgCompose, Send the message, wait for send completion response **/
+ rv = pMsgCompose->Initialize(pMsgComposeParams, parentWindow, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, identity, nullptr, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // nsMsgCompose::ProcessReplyFlags usually takes care of marking messages
+ // as forwarded. ProcessReplyFlags is normally called from
+ // nsMsgComposeSendListener::OnStopSending but for this case the msgCompose
+ // object is not set so ProcessReplyFlags won't get called.
+ // Therefore, let's just mark it here instead.
+ return folder->AddMessageDispositionState(aMsgHdr, nsIMsgFolder::nsMsgDispositionState_Forwarded);
+}
+
+nsresult nsMsgComposeService::AddGlobalHtmlDomains()
+{
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefs->GetBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrefBranch> defaultsPrefBranch;
+ rv = prefs->GetDefaultBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(defaultsPrefBranch));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ /**
+ * Check to see if we need to add any global domains.
+ * If so, make sure the following prefs are added to mailnews.js
+ *
+ * 1. pref("mailnews.global_html_domains.version", version number);
+ * This pref registers the current version in the user prefs file. A default value is stored
+ * in mailnews file. Depending the changes we plan to make we can move the default version number.
+ * Comparing version number from user's prefs file and the default one from mailnews.js, we
+ * can effect ppropriate changes.
+ *
+ * 2. pref("mailnews.global_html_domains", <comma separated domain list>);
+ * This pref contains the list of html domains that ISP can add to make that user's contain all
+ * of these under the HTML domains in the Mail&NewsGrpus|Send Format under global preferences.
+ */
+ int32_t htmlDomainListCurrentVersion, htmlDomainListDefaultVersion;
+ rv = prefBranch->GetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, &htmlDomainListCurrentVersion);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = defaultsPrefBranch->GetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, &htmlDomainListDefaultVersion);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Update the list as needed
+ if (htmlDomainListCurrentVersion <= htmlDomainListDefaultVersion) {
+ // Get list of global domains need to be added
+ nsCString globalHtmlDomainList;
+ rv = prefBranch->GetCharPref(HTMLDOMAINUPDATE_DOMAINLIST_PREF_NAME, getter_Copies(globalHtmlDomainList));
+
+ if (NS_SUCCEEDED(rv) && !globalHtmlDomainList.IsEmpty()) {
+ nsTArray<nsCString> domainArray;
+
+ // Get user's current HTML domain set for send format
+ nsCString currentHtmlDomainList;
+ rv = prefBranch->GetCharPref(USER_CURRENT_HTMLDOMAINLIST_PREF_NAME, getter_Copies(currentHtmlDomainList));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString newHtmlDomainList(currentHtmlDomainList);
+ // Get the current html domain list into new list var
+ ParseString(currentHtmlDomainList, DOMAIN_DELIMITER, domainArray);
+
+ // Get user's current Plaintext domain set for send format
+ nsCString currentPlaintextDomainList;
+ rv = prefBranch->GetCharPref(USER_CURRENT_PLAINTEXTDOMAINLIST_PREF_NAME, getter_Copies(currentPlaintextDomainList));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Get the current plaintext domain list into new list var
+ ParseString(currentPlaintextDomainList, DOMAIN_DELIMITER, domainArray);
+
+ size_t i = domainArray.Length();
+ if (i > 0) {
+ // Append each domain in the preconfigured html domain list
+ globalHtmlDomainList.StripWhitespace();
+ ParseString(globalHtmlDomainList, DOMAIN_DELIMITER, domainArray);
+
+ // Now add each domain that does not already appear in
+ // the user's current html or plaintext domain lists
+ for (; i < domainArray.Length(); i++) {
+ if (domainArray.IndexOf(domainArray[i]) == i) {
+ if (!newHtmlDomainList.IsEmpty())
+ newHtmlDomainList += DOMAIN_DELIMITER;
+ newHtmlDomainList += domainArray[i];
+ }
+ }
+ }
+ else
+ {
+ // User has no domains listed either in html or plain text category.
+ // Assign the global list to be the user's current html domain list
+ newHtmlDomainList = globalHtmlDomainList;
+ }
+
+ // Set user's html domain pref with the updated list
+ rv = prefBranch->SetCharPref(USER_CURRENT_HTMLDOMAINLIST_PREF_NAME, newHtmlDomainList.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Increase the version to avoid running the update code unless needed (based on default version)
+ rv = prefBranch->SetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, htmlDomainListCurrentVersion + 1);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::RegisterComposeDocShell(nsIDocShell *aDocShell,
+ nsIMsgCompose *aComposeObject)
+{
+ NS_ENSURE_ARG_POINTER(aDocShell);
+ NS_ENSURE_ARG_POINTER(aComposeObject);
+
+ nsresult rv;
+
+ // add the msg compose / dom window mapping to our hash table
+ nsCOMPtr<nsIWeakReference> weakDocShell = do_GetWeakReference(aDocShell, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIWeakReference> weakMsgComposePtr = do_GetWeakReference(aComposeObject);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mOpenComposeWindows.Put(weakDocShell, weakMsgComposePtr);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::UnregisterComposeDocShell(nsIDocShell *aDocShell)
+{
+ NS_ENSURE_ARG_POINTER(aDocShell);
+
+ nsresult rv;
+ nsCOMPtr<nsIWeakReference> weakDocShell = do_GetWeakReference(aDocShell, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ mOpenComposeWindows.Remove(weakDocShell);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::GetMsgComposeForDocShell(nsIDocShell *aDocShell,
+ nsIMsgCompose **aComposeObject)
+{
+ NS_ENSURE_ARG_POINTER(aDocShell);
+ NS_ENSURE_ARG_POINTER(aComposeObject);
+
+ if (!mOpenComposeWindows.Count())
+ return NS_ERROR_FAILURE;
+
+ // get the weak reference for our dom window
+ nsresult rv;
+ nsCOMPtr<nsIWeakReference> weakDocShell = do_GetWeakReference(aDocShell, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIWeakReference> weakMsgComposePtr;
+
+ if (!mOpenComposeWindows.Get(weakDocShell,
+ getter_AddRefs(weakMsgComposePtr)))
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(weakMsgComposePtr, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aComposeObject = msgCompose);
+ return rv;
+}
+
+/**
+ * LoadDraftOrTemplate
+ * Helper routine used to run msgURI through libmime in order to fetch the contents for a
+ * draft or template.
+ */
+nsresult
+nsMsgComposeService::LoadDraftOrTemplate(const nsACString& aMsgURI, nsMimeOutputType aOutType,
+ nsIMsgIdentity * aIdentity, const char * aOriginalMsgURI,
+ nsIMsgDBHdr * aOrigMsgHdr,
+ bool aForwardInline,
+ bool overrideComposeFormat,
+ nsIMsgWindow *aMsgWindow)
+{
+ return RunMessageThroughMimeDraft(aMsgURI, aOutType, aIdentity,
+ aOriginalMsgURI, aOrigMsgHdr,
+ aForwardInline, EmptyString(),
+ overrideComposeFormat, aMsgWindow);
+}
+
+/**
+ * Run the aMsgURI message through libmime. We set various attributes of the
+ * nsIMimeStreamConverter so mimedrft.cpp will know what to do with the message
+ * when its done streaming. Usually that will be opening a compose window
+ * with the contents of the message, but if forwardTo is non-empty, mimedrft.cpp
+ * will forward the contents directly.
+ *
+ * @param aMsgURI URI to stream, which is the msgUri + any extra terms, e.g.,
+ * "redirect=true".
+ * @param aOutType nsMimeOutput::nsMimeMessageDraftOrTemplate or
+ * nsMimeOutput::nsMimeMessageEditorTemplate
+ * @param aIdentity identity to use for the new message
+ * @param aOriginalMsgURI msgURI w/o any extra terms
+ * @param aOrigMsgHdr nsIMsgDBHdr corresponding to aOriginalMsgURI
+ * @param aForwardInline true if doing a forward inline
+ * @param aForwardTo e-mail address to forward msg to. This is used for
+ * forward inline message filter actions.
+ * @param aOverrideComposeFormat True if the user had shift key down when
+ doing a command that opens the compose window,
+ * which means we switch the compose window used
+ * from the default.
+ * @param aMsgWindow msgWindow to pass into DisplayMessage.
+ */
+nsresult
+nsMsgComposeService::RunMessageThroughMimeDraft(
+ const nsACString& aMsgURI, nsMimeOutputType aOutType,
+ nsIMsgIdentity * aIdentity, const char * aOriginalMsgURI,
+ nsIMsgDBHdr * aOrigMsgHdr,
+ bool aForwardInline,
+ const nsAString &aForwardTo,
+ bool aOverrideComposeFormat,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsCOMPtr <nsIMsgMessageService> messageService;
+ nsresult rv = GetMessageServiceFromURI(aMsgURI, getter_AddRefs(messageService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a mime parser (nsIMimeStreamConverter)to do the conversion.
+ nsCOMPtr<nsIMimeStreamConverter> mimeConverter =
+ do_CreateInstance(NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mimeConverter->SetMimeOutputType(aOutType); // Set the type of output for libmime
+ mimeConverter->SetForwardInline(aForwardInline);
+ if (!aForwardTo.IsEmpty())
+ {
+ mimeConverter->SetForwardInlineFilter(true);
+ mimeConverter->SetForwardToAddress(aForwardTo);
+ }
+ mimeConverter->SetOverrideComposeFormat(aOverrideComposeFormat);
+ mimeConverter->SetIdentity(aIdentity);
+ mimeConverter->SetOriginalMsgURI(aOriginalMsgURI);
+ mimeConverter->SetOrigMsgHdr(aOrigMsgHdr);
+
+ nsCOMPtr<nsIURI> url;
+ bool fileUrl = StringBeginsWith(aMsgURI, NS_LITERAL_CSTRING("file:"));
+ nsCString mailboxUri(aMsgURI);
+ if (fileUrl)
+ {
+ // We loaded a .eml file from a file: url. Construct equivalent mailbox url.
+ mailboxUri.Replace(0, 5, NS_LITERAL_CSTRING("mailbox:"));
+ mailboxUri.Append(NS_LITERAL_CSTRING("&number=0"));
+ // Need this to prevent nsMsgCompose::TagEmbeddedObjects from setting
+ // inline images as moz-do-not-send.
+ mimeConverter->SetOriginalMsgURI(mailboxUri.get());
+ }
+ if (fileUrl || PromiseFlatCString(aMsgURI).Find("&type=application/x-message-display") >= 0)
+ rv = NS_NewURI(getter_AddRefs(url), mailboxUri);
+ else
+ rv = messageService->GetUrlForUri(PromiseFlatCString(aMsgURI).get(), getter_AddRefs(url), aMsgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ignore errors here - it's not fatal, and in the case of mailbox messages,
+ // we're always passing in an invalid spec...
+ (void )url->SetSpec(mailboxUri);
+
+ // if we are forwarding a message and that message used a charset over ride
+ // then use that over ride charset instead of the charset specified in the message
+ nsCString mailCharset;
+ if (aMsgWindow)
+ {
+ bool charsetOverride;
+ if (NS_SUCCEEDED(aMsgWindow->GetCharsetOverride(&charsetOverride)) && charsetOverride)
+ {
+ if (NS_SUCCEEDED(aMsgWindow->GetMailCharacterSet(mailCharset)))
+ {
+ nsCOMPtr<nsIMsgI18NUrl> i18nUrl(do_QueryInterface(url));
+ if (i18nUrl)
+ (void) i18nUrl->SetCharsetOverRide(mailCharset.get());
+ }
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed");
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
+ url,
+ nullptr,
+ nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewChannel failed.");
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIStreamConverter> converter = do_QueryInterface(mimeConverter);
+ rv = converter->AsyncConvertData(nullptr, nullptr, nullptr, channel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now, just plug the two together and get the hell out of the way!
+ nsCOMPtr<nsIStreamListener> streamListener = do_QueryInterface(mimeConverter);
+ nsCOMPtr<nsIURI> dummyNull;
+ return messageService->DisplayMessage(PromiseFlatCString(aMsgURI).get(), streamListener,
+ aMsgWindow, nullptr, mailCharset.get(),
+ getter_AddRefs(dummyNull));
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::Handle(nsICommandLine* aCmdLine)
+{
+ NS_ENSURE_ARG_POINTER(aCmdLine);
+
+ nsresult rv;
+ int32_t found, end, count;
+ nsAutoString uristr;
+ bool composeShouldHandle = true;
+
+ rv = aCmdLine->FindFlag(NS_LITERAL_STRING("compose"), false, &found);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifndef MOZ_SUITE
+ // MAC OS X passes in -url mailto:mscott@mozilla.org into the command line
+ // instead of -compose.
+ if (found == -1)
+ {
+ rv = aCmdLine->FindFlag(NS_LITERAL_STRING("url"), false, &found);
+ // we don't want to consume the argument for -url unless we're sure it is a mailto url and we'll
+ // figure that out shortly.
+ composeShouldHandle = false;
+ }
+#endif
+
+ if (found == -1)
+ return NS_OK;
+
+ end = found;
+
+ rv = aCmdLine->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count > found + 1) {
+ aCmdLine->GetArgument(found + 1, uristr);
+ if (StringBeginsWith(uristr, NS_LITERAL_STRING("mailto:")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("preselectid=")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("to=")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("cc=")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("bcc=")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("newsgroups=")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("subject=")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("format=")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("body=")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("attachment=")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("message=")) ||
+ StringBeginsWith(uristr, NS_LITERAL_STRING("from="))) {
+ composeShouldHandle = true; // the -url argument looks like mailto
+ end++;
+ // mailto: URIs are frequently passed with spaces in them. They should be
+ // escaped with %20, but we hack around broken clients. See bug 231032.
+ while (end + 1 < count) {
+ nsAutoString curarg;
+ aCmdLine->GetArgument(end + 1, curarg);
+ if (curarg.First() == '-')
+ break;
+
+ uristr.Append(' ');
+ uristr.Append(curarg);
+ ++end;
+ }
+ }
+ else {
+ uristr.Truncate();
+ }
+ }
+ if (composeShouldHandle)
+ {
+ aCmdLine->RemoveArguments(found, end);
+
+ nsCOMPtr<nsIWindowWatcher> wwatch (do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsISupportsString> arg(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ if (arg)
+ arg->SetData(uristr);
+
+ nsCOMPtr<mozIDOMWindowProxy> opened;
+ wwatch->OpenWindow(nullptr, DEFAULT_CHROME, "_blank",
+ "chrome,dialog=no,all", arg, getter_AddRefs(opened));
+
+ aCmdLine->SetPreventDefault(true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::GetHelpInfo(nsACString& aResult)
+{
+ aResult.AssignLiteral(
+ " -compose [ <options> ] Compose a mail or news message. Options are specified\n"
+ " as string \"option='value,...',option=value,...\" and\n"
+ " include: from, to, cc, bcc, newsgroups, subject, body,\n"
+ " message (file), attachment (file), format (html | text).\n"
+ " Example: \"to=john@example.com,subject='Dinner tonight?'\"\n");
+ return NS_OK;
+}
diff --git a/mailnews/compose/src/nsMsgComposeService.h b/mailnews/compose/src/nsMsgComposeService.h
new file mode 100644
index 000000000..6ba029050
--- /dev/null
+++ b/mailnews/compose/src/nsMsgComposeService.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define MSGCOMP_TRACE_PERFORMANCE 1
+
+#include "nsIMsgComposeService.h"
+#include "nsCOMPtr.h"
+#include "mozIDOMWindow.h"
+#include "nsIXULWindow.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsInterfaceHashtable.h"
+
+#include "nsICommandLineHandler.h"
+#define ICOMMANDLINEHANDLER nsICommandLineHandler
+
+class nsMsgComposeService :
+ public nsIMsgComposeService,
+ public ICOMMANDLINEHANDLER,
+ public nsSupportsWeakReference
+{
+public:
+ nsMsgComposeService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOMPOSESERVICE
+ NS_DECL_NSICOMMANDLINEHANDLER
+
+ nsresult Init();
+ void Reset();
+ void DeleteCachedWindows();
+ nsresult AddGlobalHtmlDomains();
+
+private:
+ virtual ~nsMsgComposeService();
+ bool mLogComposePerformance;
+
+ nsresult LoadDraftOrTemplate(const nsACString& aMsgURI, nsMimeOutputType aOutType,
+ nsIMsgIdentity * aIdentity, const char * aOriginalMsgURI,
+ nsIMsgDBHdr * aOrigMsgHdr, bool aForwardInline,
+ bool overrideComposeFormat,
+ nsIMsgWindow *aMsgWindow);
+
+ nsresult RunMessageThroughMimeDraft(const nsACString& aMsgURI,
+ nsMimeOutputType aOutType,
+ nsIMsgIdentity * aIdentity,
+ const char * aOriginalMsgURI,
+ nsIMsgDBHdr * aOrigMsgHdr,
+ bool aForwardInline,
+ const nsAString &forwardTo,
+ bool overrideComposeFormat,
+ nsIMsgWindow *aMsgWindow);
+
+ // hash table mapping dom windows to nsIMsgCompose objects
+ nsInterfaceHashtable<nsISupportsHashKey, nsIWeakReference> mOpenComposeWindows;
+
+ // When doing a reply and the settings are enabled, get the HTML of the selected text
+ // in the original message window so that it can be quoted instead of the entire message.
+ nsresult GetOrigWindowSelection(MSG_ComposeType type, nsIMsgWindow *aMsgWindow, nsACString& aSelHTML);
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ PRIntervalTime mStartTime;
+ PRIntervalTime mPreviousTime;
+#endif
+};
diff --git a/mailnews/compose/src/nsMsgCopy.cpp b/mailnews/compose/src/nsMsgCopy.cpp
new file mode 100644
index 000000000..5c441b0c1
--- /dev/null
+++ b/mailnews/compose/src/nsMsgCopy.cpp
@@ -0,0 +1,553 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsMsgCopy.h"
+
+#include "nsCOMPtr.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsISupports.h"
+#include "nsIRDFService.h"
+#include "nsIRDFResource.h"
+#include "nsRDFCID.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsMsgCompUtils.h"
+#include "prcmon.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsThreadUtils.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgProgress.h"
+#include "nsComposeStrings.h"
+#include "prmem.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "nsArrayUtils.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for the copy operation. We have to create this class
+// to listen for message copy completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+NS_IMPL_ISUPPORTS(CopyListener, nsIMsgCopyServiceListener)
+
+CopyListener::CopyListener(void)
+{
+ mCopyInProgress = false;
+}
+
+CopyListener::~CopyListener(void)
+{
+}
+
+nsresult
+CopyListener::OnStartCopy()
+{
+#ifdef NS_DEBUG
+ printf("CopyListener::OnStartCopy()\n");
+#endif
+
+ if (mComposeAndSend)
+ mComposeAndSend->NotifyListenerOnStartCopy();
+ return NS_OK;
+}
+
+nsresult
+CopyListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
+{
+#ifdef NS_DEBUG
+ printf("CopyListener::OnProgress() %d of %d\n", aProgress, aProgressMax);
+#endif
+
+ if (mComposeAndSend)
+ mComposeAndSend->NotifyListenerOnProgressCopy(aProgress, aProgressMax);
+
+ return NS_OK;
+}
+
+nsresult
+CopyListener::SetMessageKey(nsMsgKey aMessageKey)
+{
+ if (mComposeAndSend)
+ mComposeAndSend->SetMessageKey(aMessageKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CopyListener::GetMessageId(nsACString& aMessageId)
+{
+ if (mComposeAndSend)
+ mComposeAndSend->GetMessageId(aMessageId);
+ return NS_OK;
+}
+
+nsresult
+CopyListener::OnStopCopy(nsresult aStatus)
+{
+ if (NS_SUCCEEDED(aStatus))
+ {
+#ifdef NS_DEBUG
+ printf("CopyListener: SUCCESSFUL ON THE COPY OPERATION!\n");
+#endif
+ }
+ else
+ {
+#ifdef NS_DEBUG
+ printf("CopyListener: COPY OPERATION FAILED!\n");
+#endif
+ }
+
+ if (mCopyInProgress)
+ {
+ PR_CEnterMonitor(this);
+ PR_CNotifyAll(this);
+ mCopyInProgress = false;
+ PR_CExitMonitor(this);
+ }
+ if (mComposeAndSend)
+ mComposeAndSend->NotifyListenerOnStopCopy(aStatus);
+
+ return NS_OK;
+}
+
+nsresult
+CopyListener::SetMsgComposeAndSendObject(nsIMsgSend *obj)
+{
+ if (obj)
+ mComposeAndSend = obj;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// END END END END END END END END END END END END END END END
+// This is the listener class for the copy operation. We have to create this class
+// to listen for message copy completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsMsgCopy, nsIUrlListener)
+
+nsMsgCopy::nsMsgCopy()
+{
+ mFile = nullptr;
+ mMode = nsIMsgSend::nsMsgDeliverNow;
+ mSavePref = nullptr;
+}
+
+nsMsgCopy::~nsMsgCopy()
+{
+ PR_Free(mSavePref);
+}
+
+nsresult
+nsMsgCopy::StartCopyOperation(nsIMsgIdentity *aUserIdentity,
+ nsIFile *aFile,
+ nsMsgDeliverMode aMode,
+ nsIMsgSend *aMsgSendObj,
+ const char *aSavePref,
+ nsIMsgDBHdr *aMsgToReplace)
+{
+ nsCOMPtr<nsIMsgFolder> dstFolder;
+ bool isDraft = false;
+ bool waitForUrl = false;
+ nsresult rv;
+
+ if (!aMsgSendObj)
+ return NS_ERROR_INVALID_ARG;
+
+ // Store away the server location...
+ if (aSavePref)
+ mSavePref = PL_strdup(aSavePref);
+
+ //
+ // Vars for implementation...
+ //
+
+ // QueueForLater (Outbox)
+ if (aMode == nsIMsgSend::nsMsgQueueForLater ||
+ aMode == nsIMsgSend::nsMsgDeliverBackground)
+ {
+ rv = GetUnsentMessagesFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl);
+ isDraft = false;
+ if (!dstFolder || NS_FAILED(rv)) {
+ return NS_MSG_UNABLE_TO_SEND_LATER;
+ }
+ }
+ else if (aMode == nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts)
+ {
+ rv = GetDraftsFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl);
+ isDraft = true;
+ if (!dstFolder || NS_FAILED(rv))
+ return NS_MSG_UNABLE_TO_SAVE_DRAFT;
+ }
+ else if (aMode == nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates)
+ {
+ rv = GetTemplatesFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl);
+ isDraft = false;
+ if (!dstFolder || NS_FAILED(rv))
+ return NS_MSG_UNABLE_TO_SAVE_TEMPLATE;
+ }
+ else // SaveInSentFolder (Sent) - nsMsgDeliverNow or nsMsgSendUnsent
+ {
+ rv = GetSentFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl);
+ isDraft = false;
+ if (!dstFolder || NS_FAILED(rv))
+ return NS_MSG_COULDNT_OPEN_FCC_FOLDER;
+ }
+
+ nsCOMPtr <nsIMsgWindow> msgWindow;
+
+ if (aMsgSendObj)
+ {
+ nsCOMPtr <nsIMsgProgress> progress;
+ aMsgSendObj->GetProgress(getter_AddRefs(progress));
+ if (progress)
+ progress->GetMsgWindow(getter_AddRefs(msgWindow));
+ }
+
+ mMode = aMode;
+ mFile = aFile;
+ mDstFolder = dstFolder;
+ mMsgToReplace = aMsgToReplace;
+ mIsDraft = isDraft;
+ mMsgSendObj = aMsgSendObj;
+ if (!waitForUrl)
+ {
+ // cache info needed for DoCopy and call DoCopy when OnStopUrl is called.
+ rv = DoCopy(aFile, dstFolder, aMsgToReplace, isDraft, msgWindow, aMsgSendObj);
+ // N.B. "this" may be deleted when this call returns.
+ }
+ return rv;
+}
+
+nsresult
+nsMsgCopy::DoCopy(nsIFile *aDiskFile, nsIMsgFolder *dstFolder,
+ nsIMsgDBHdr *aMsgToReplace, bool aIsDraft,
+ nsIMsgWindow *msgWindow,
+ nsIMsgSend *aMsgSendObj)
+{
+ nsresult rv = NS_OK;
+
+ // Check sanity
+ if ((!aDiskFile) || (!dstFolder))
+ return NS_ERROR_INVALID_ARG;
+
+ //Call copyservice with dstFolder, disk file, and txnManager
+ if(NS_SUCCEEDED(rv))
+ {
+ RefPtr<CopyListener> copyListener = new CopyListener();
+ if (!copyListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ copyListener->SetMsgComposeAndSendObject(aMsgSendObj);
+ nsCOMPtr<nsIThread> thread;
+
+ if (aIsDraft)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(dstFolder);
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ bool shutdownInProgress = false;
+ rv = accountManager->GetShutdownInProgress(&shutdownInProgress);
+
+ if (NS_SUCCEEDED(rv) && shutdownInProgress && imapFolder)
+ {
+ // set the following only when we were in the middle of shutdown
+ // process
+ copyListener->mCopyInProgress = true;
+ thread = do_GetCurrentThread();
+ }
+ }
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = copyService->CopyFileMessage(aDiskFile, dstFolder, aMsgToReplace,
+ aIsDraft,
+ aIsDraft ? 0 : nsMsgMessageFlags::Read,
+ EmptyCString(), copyListener, msgWindow);
+ // copyListener->mCopyInProgress can only be set when we are in the
+ // middle of the shutdown process
+ while (copyListener->mCopyInProgress)
+ {
+ PR_CEnterMonitor(copyListener);
+ PR_CWait(copyListener, PR_MicrosecondsToInterval(1000UL));
+ PR_CExitMonitor(copyListener);
+ if (thread)
+ NS_ProcessPendingEvents(thread);
+ }
+ }
+
+ return rv;
+}
+
+// nsIUrlListener methods
+NS_IMETHODIMP
+nsMsgCopy::OnStartRunningUrl(nsIURI * aUrl)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCopy::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode)
+{
+ nsresult rv = aExitCode;
+ if (NS_SUCCEEDED(aExitCode))
+ {
+ rv = DoCopy(mFile, mDstFolder, mMsgToReplace, mIsDraft, nullptr, mMsgSendObj);
+ }
+ return rv;
+}
+
+nsresult
+nsMsgCopy::GetUnsentMessagesFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **folder, bool *waitForUrl)
+{
+ nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgQueueForLater, mSavePref, folder);
+ if (*folder)
+ (*folder)->SetFlag(nsMsgFolderFlags::Queue);
+ CreateIfMissing(folder, waitForUrl);
+ return ret;
+}
+
+nsresult
+nsMsgCopy::GetDraftsFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **folder, bool *waitForUrl)
+{
+ nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgSaveAsDraft, mSavePref, folder);
+ if (*folder)
+ (*folder)->SetFlag(nsMsgFolderFlags::Drafts);
+ CreateIfMissing(folder, waitForUrl);
+ return ret;
+}
+
+nsresult
+nsMsgCopy::GetTemplatesFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **folder, bool *waitForUrl)
+{
+ nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgSaveAsTemplate, mSavePref, folder);
+ if (*folder)
+ (*folder)->SetFlag(nsMsgFolderFlags::Templates);
+ CreateIfMissing(folder, waitForUrl);
+ return ret;
+}
+
+nsresult
+nsMsgCopy::GetSentFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **folder, bool *waitForUrl)
+{
+ nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgDeliverNow, mSavePref, folder);
+ if (*folder)
+ {
+ // If mSavePref is the same as the identity's fcc folder, set the sent flag.
+ nsCString identityFccUri;
+ userIdentity->GetFccFolder(identityFccUri);
+ if (identityFccUri.Equals(mSavePref))
+ (*folder)->SetFlag(nsMsgFolderFlags::SentMail);
+ }
+ CreateIfMissing(folder, waitForUrl);
+ return ret;
+}
+
+nsresult
+nsMsgCopy::CreateIfMissing(nsIMsgFolder **folder, bool *waitForUrl)
+{
+ nsresult rv = NS_OK;
+ if (folder && *folder)
+ {
+ nsCOMPtr<nsIMsgFolder> parent;
+ (*folder)->GetParent(getter_AddRefs(parent));
+ if (!parent)
+ {
+ nsCOMPtr<nsIFile> folderPath;
+ // for local folders, path is to the berkeley mailbox.
+ // for imap folders, path needs to have .msf appended to the name
+ (*folder)->GetFilePath(getter_AddRefs(folderPath));
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = (*folder)->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = server->GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isAsyncFolder;
+ rv = protocolInfo->GetFoldersCreatedAsync(&isAsyncFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we can't get the path from the folder, then try to create the storage.
+ // for imap, it doesn't matter if the .msf file exists - it still might not
+ // exist on the server, so we should try to create it
+ bool exists = false;
+ if (!isAsyncFolder && folderPath)
+ folderPath->Exists(&exists);
+ if (!exists)
+ {
+ (*folder)->CreateStorageIfMissing(this);
+ if (isAsyncFolder)
+ *waitForUrl = true;
+
+ rv = NS_OK;
+ }
+ }
+ }
+ return rv;
+}
+////////////////////////////////////////////////////////////////////////////////////
+// Utility Functions for MsgFolders
+////////////////////////////////////////////////////////////////////////////////////
+nsresult
+LocateMessageFolder(nsIMsgIdentity *userIdentity,
+ nsMsgDeliverMode aFolderType,
+ const char *aFolderURI,
+ nsIMsgFolder **msgFolder)
+{
+ nsresult rv = NS_OK;
+
+ if (!msgFolder) return NS_ERROR_NULL_POINTER;
+ *msgFolder = nullptr;
+
+ if (!aFolderURI || !*aFolderURI)
+ return NS_ERROR_INVALID_ARG;
+
+ // as long as it doesn't start with anyfolder://
+ if (PL_strncasecmp(ANY_SERVER, aFolderURI, strlen(aFolderURI)) != 0)
+ {
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ // get the corresponding RDF resource
+ // RDF will create the folder resource if it doesn't already exist
+ nsCOMPtr<nsIRDFResource> resource;
+ rv = rdf->GetResource(nsDependentCString(aFolderURI), getter_AddRefs(resource));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIMsgFolder> folderResource;
+ folderResource = do_QueryInterface(resource, &rv);
+ if (NS_SUCCEEDED(rv) && folderResource)
+ {
+ // don't check validity of folder - caller will handle creating it
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ //make sure that folder hierarchy is built so that legitimate parent-child relationship is established
+ rv = folderResource->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return server->GetMsgFolderFromURI(folderResource, nsDependentCString(aFolderURI), msgFolder);
+ }
+ else
+ {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ else
+ {
+ uint32_t cnt = 0;
+ uint32_t i;
+
+ if (!userIdentity)
+ return NS_ERROR_INVALID_ARG;
+
+ // get the account manager
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // If any folder will do, go look for one.
+ nsCOMPtr<nsIArray> retval;
+ accountManager->GetServersForIdentity(userIdentity, getter_AddRefs(retval));
+ if (!retval) return NS_ERROR_FAILURE;
+
+ // Ok, we have to look through the servers and try to find the server that
+ // has a valid folder of the type that interests us...
+ rv = retval->GetLength(&cnt);
+ if (NS_FAILED(rv)) return rv;
+
+ for (i=0; i<cnt; i++) {
+ // Now that we have the server...we need to get the named message folder
+ nsCOMPtr<nsIMsgIncomingServer> inServer;
+
+ inServer = do_QueryElementAt(retval, i, &rv);
+ if(NS_FAILED(rv) || (!inServer))
+ continue;
+
+ //
+ // If aFolderURI is passed in, then the user has chosen a specific
+ // mail folder to save the message, but if it is null, just find the
+ // first one and make that work. The folder is specified as a URI, like
+ // the following:
+ //
+ // mailbox://nobody@Local Folders/Sent
+ // imap://rhp@nsmail-2/Drafts
+ // newsgroup://news.mozilla.org/netscape.test
+ //
+ nsCString serverURI;
+ rv = inServer->GetServerURI(serverURI);
+ if (NS_FAILED(rv) || serverURI.IsEmpty())
+ continue;
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = inServer->GetRootFolder(getter_AddRefs(rootFolder));
+
+ if(NS_FAILED(rv) || (!rootFolder))
+ continue;
+
+ // use the defaults by getting the folder by flags
+ if (aFolderType == nsIMsgSend::nsMsgQueueForLater ||
+ aFolderType == nsIMsgSend::nsMsgDeliverBackground)
+ {
+ // QueueForLater (Outbox)
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Queue, msgFolder);
+ }
+ else if (aFolderType == nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts)
+ {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts, msgFolder);
+ }
+ else if (aFolderType == nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates)
+ {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Templates, msgFolder);
+ }
+ else // SaveInSentFolder (Sent) - nsMsgDeliverNow or nsMsgSendUnsent
+ {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail, msgFolder);
+ }
+
+ if (*msgFolder)
+ {
+ return NS_OK;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+//
+// Figure out if a folder is local or not and return a boolean to
+// say so.
+//
+nsresult
+MessageFolderIsLocal(nsIMsgIdentity *userIdentity,
+ nsMsgDeliverMode aFolderType,
+ const char *aFolderURI,
+ bool *aResult)
+{
+ nsresult rv;
+
+ if (!aFolderURI) return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr <nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->SetSpec(nsDependentCString(aFolderURI));
+ if (NS_FAILED(rv)) return rv;
+
+ /* mailbox:/ means its local (on disk) */
+ rv = url->SchemeIs("mailbox", aResult);
+ if (NS_FAILED(rv)) return rv;
+ return NS_OK;
+}
+
diff --git a/mailnews/compose/src/nsMsgCopy.h b/mailnews/compose/src/nsMsgCopy.h
new file mode 100644
index 000000000..7b559d754
--- /dev/null
+++ b/mailnews/compose/src/nsMsgCopy.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgCopy_H_
+#define _nsMsgCopy_H_
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsIFile.h"
+#include "nsMsgSend.h"
+#include "nsIMsgFolder.h"
+#include "nsITransactionManager.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIMsgCopyService.h"
+
+// {0874C3B5-317D-11d3-8EFB-00A024A7D144}
+#define NS_IMSGCOPY_IID \
+{ 0x874c3b5, 0x317d, 0x11d3, \
+{ 0x8e, 0xfb, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } };
+
+// Forward declarations...
+class nsMsgCopy;
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for the copy operation. We have to create this class
+// to listen for message copy completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+class CopyListener : public nsIMsgCopyServiceListener
+{
+public:
+ CopyListener(void);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD OnStartCopy() override;
+
+ NS_IMETHOD OnProgress(uint32_t aProgress, uint32_t aProgressMax) override;
+
+ NS_IMETHOD SetMessageKey(nsMsgKey aMessageKey) override;
+
+ NS_IMETHOD GetMessageId(nsACString& aMessageId) override;
+
+ NS_IMETHOD OnStopCopy(nsresult aStatus) override;
+
+ NS_IMETHOD SetMsgComposeAndSendObject(nsIMsgSend *obj);
+
+ bool mCopyInProgress;
+
+private:
+ virtual ~CopyListener();
+ nsCOMPtr<nsIMsgSend> mComposeAndSend;
+};
+
+//
+// This is a class that deals with processing remote attachments. It implements
+// an nsIStreamListener interface to deal with incoming data
+//
+class nsMsgCopy : public nsIUrlListener
+{
+public:
+ nsMsgCopy();
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+
+
+ //////////////////////////////////////////////////////////////////////
+ // Object methods...
+ //////////////////////////////////////////////////////////////////////
+ //
+ nsresult StartCopyOperation(nsIMsgIdentity *aUserIdentity,
+ nsIFile *aFile,
+ nsMsgDeliverMode aMode,
+ nsIMsgSend *aMsgSendObj,
+ const char *aSavePref,
+ nsIMsgDBHdr *aMsgToReplace);
+
+ nsresult DoCopy(nsIFile *aDiskFile, nsIMsgFolder *dstFolder,
+ nsIMsgDBHdr *aMsgToReplace, bool aIsDraft,
+ nsIMsgWindow *msgWindow,
+ nsIMsgSend *aMsgSendObj);
+
+ nsresult GetUnsentMessagesFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **msgFolder, bool *waitForUrl);
+ nsresult GetDraftsFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **msgFolder, bool *waitForUrl);
+ nsresult GetTemplatesFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **msgFolder, bool *waitForUrl);
+ nsresult GetSentFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **msgFolder, bool *waitForUrl);
+ nsresult CreateIfMissing(nsIMsgFolder **folder, bool *waitForUrl);
+
+
+ //
+ // Vars for implementation...
+ //
+ nsIFile *mFile; // the file we are sending...
+ nsMsgDeliverMode mMode;
+ nsCOMPtr<nsIMsgFolder> mDstFolder;
+ nsCOMPtr<nsIMsgDBHdr> mMsgToReplace;
+ bool mIsDraft;
+ nsCOMPtr<nsIMsgSend> mMsgSendObj;
+ char *mSavePref;
+
+private:
+ virtual ~nsMsgCopy();
+};
+
+// Useful function for the back end...
+nsresult LocateMessageFolder(nsIMsgIdentity *userIdentity,
+ nsMsgDeliverMode aFolderType,
+ const char *aSaveURI,
+ nsIMsgFolder **msgFolder);
+
+nsresult MessageFolderIsLocal(nsIMsgIdentity *userIdentity,
+ nsMsgDeliverMode aFolderType,
+ const char *aSaveURI,
+ bool *aResult);
+
+#endif /* _nsMsgCopy_H_ */
diff --git a/mailnews/compose/src/nsMsgPrompts.cpp b/mailnews/compose/src/nsMsgPrompts.cpp
new file mode 100644
index 000000000..473469f16
--- /dev/null
+++ b/mailnews/compose/src/nsMsgPrompts.cpp
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsMsgPrompts.h"
+
+#include "nsMsgCopy.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+#include "nsMsgCompCID.h"
+#include "nsComposeStrings.h"
+#include "nsIStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+nsresult
+nsMsgGetMessageByName(const char16_t* aName, nsString& aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return bundle->GetStringFromName(aName, getter_Copies(aResult));
+}
+
+static nsresult
+nsMsgBuildMessageByName(const char16_t *aName, nsIFile *aFile, nsString& aResult)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString path;
+ aFile->GetPath(path);
+
+ const char16_t *params[1] = {path.get()};
+ return bundle->FormatStringFromName(aName, params, 1, getter_Copies(aResult));
+}
+
+nsresult
+nsMsgBuildMessageWithFile(nsIFile *aFile, nsString& aResult)
+{
+ return nsMsgBuildMessageByName(u"unableToOpenFile", aFile, aResult);
+}
+
+nsresult
+nsMsgBuildMessageWithTmpFile(nsIFile *aFile, nsString& aResult)
+{
+ return nsMsgBuildMessageByName(u"unableToOpenTmpFile", aFile, aResult);
+}
+
+nsresult
+nsMsgDisplayMessageByName(nsIPrompt *aPrompt, const char16_t* aName, const char16_t *windowTitle)
+{
+ nsString msg;
+ nsMsgGetMessageByName(aName, msg);
+ return nsMsgDisplayMessageByString(aPrompt, msg.get(), windowTitle);
+}
+
+nsresult
+nsMsgDisplayMessageByString(nsIPrompt * aPrompt, const char16_t * msg, const char16_t * windowTitle)
+{
+ NS_ENSURE_ARG_POINTER(msg);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrompt> prompt = aPrompt;
+
+ if (!prompt)
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch)
+ wwatch->GetNewPrompter(0, getter_AddRefs(prompt));
+ }
+
+ if (prompt)
+ rv = prompt->Alert(windowTitle, msg);
+
+ return rv;
+}
+
+nsresult
+nsMsgAskBooleanQuestionByString(nsIPrompt * aPrompt, const char16_t * msg, bool *answer, const char16_t * windowTitle)
+{
+ NS_ENSURE_TRUE(msg && *msg, NS_ERROR_INVALID_ARG);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrompt> dialog = aPrompt;
+
+ if (!dialog)
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch)
+ wwatch->GetNewPrompter(0, getter_AddRefs(dialog));
+ }
+
+ if (dialog) {
+ rv = dialog->Confirm(windowTitle, msg, answer);
+ }
+
+ return rv;
+}
diff --git a/mailnews/compose/src/nsMsgPrompts.h b/mailnews/compose/src/nsMsgPrompts.h
new file mode 100644
index 000000000..9aed5252e
--- /dev/null
+++ b/mailnews/compose/src/nsMsgPrompts.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgPrompts_H_
+#define _nsMsgPrompts_H_
+
+#include "nscore.h"
+#include "nsError.h"
+#include "nsStringGlue.h"
+
+class nsIPrompt;
+
+nsresult nsMsgGetMessageByName(const char16_t* aName, nsString& aResult);
+nsresult nsMsgBuildMessageWithFile(nsIFile * aFile, nsString& aResult);
+nsresult nsMsgBuildMessageWithTmpFile(nsIFile * aFile, nsString& aResult);
+nsresult nsMsgDisplayMessageByName(nsIPrompt *aPrompt, const char16_t *aName, const char16_t *windowTitle = nullptr);
+nsresult nsMsgDisplayMessageByString(nsIPrompt * aPrompt, const char16_t * msg, const char16_t * windowTitle = nullptr);
+nsresult nsMsgAskBooleanQuestionByString(nsIPrompt * aPrompt, const char16_t * msg, bool *answer, const char16_t * windowTitle = nullptr);
+
+#endif /* _nsMsgPrompts_H_ */
diff --git a/mailnews/compose/src/nsMsgQuote.cpp b/mailnews/compose/src/nsMsgQuote.cpp
new file mode 100644
index 000000000..2bddae520
--- /dev/null
+++ b/mailnews/compose/src/nsMsgQuote.cpp
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURL.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIServiceManager.h"
+#include "nsIStreamListener.h"
+#include "nsIStreamConverter.h"
+#include "nsIStreamConverterService.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsMimeTypes.h"
+#include "nsICharsetConverterManager.h"
+#include "prprf.h"
+#include "nsMsgQuote.h"
+#include "nsMsgCompUtils.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#include "nsNetUtil.h"
+#include "nsMsgMimeCID.h"
+#include "nsMsgCompCID.h"
+#include "nsMsgCompose.h"
+#include "nsMsgMailNewsUrl.h"
+#include "mozilla/Services.h"
+#include "nsIScriptSecurityManager.h"
+
+NS_IMPL_ISUPPORTS(nsMsgQuoteListener, nsIMsgQuoteListener,
+ nsIMimeStreamConverterListener)
+
+nsMsgQuoteListener::nsMsgQuoteListener()
+{
+}
+
+nsMsgQuoteListener::~nsMsgQuoteListener()
+{
+}
+
+NS_IMETHODIMP nsMsgQuoteListener::SetMsgQuote(nsIMsgQuote * msgQuote)
+{
+ mMsgQuote = do_GetWeakReference(msgQuote);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuoteListener::GetMsgQuote(nsIMsgQuote ** aMsgQuote)
+{
+ nsresult rv = NS_OK;
+ if (aMsgQuote)
+ {
+ nsCOMPtr<nsIMsgQuote> msgQuote = do_QueryReferent(mMsgQuote);
+ *aMsgQuote = msgQuote;
+ NS_IF_ADDREF(*aMsgQuote);
+ }
+ else
+ rv = NS_ERROR_NULL_POINTER;
+
+ return rv;
+}
+
+nsresult nsMsgQuoteListener::OnHeadersReady(nsIMimeHeaders * headers)
+{
+ nsCOMPtr<nsIMsgQuotingOutputStreamListener> quotingOutputStreamListener;
+ nsCOMPtr<nsIMsgQuote> msgQuote = do_QueryReferent(mMsgQuote);
+
+ if (msgQuote)
+ msgQuote->GetStreamListener(getter_AddRefs(quotingOutputStreamListener));
+
+ if (quotingOutputStreamListener)
+ quotingOutputStreamListener->SetMimeHeaders(headers);
+ return NS_OK;
+}
+
+//
+// Implementation...
+//
+nsMsgQuote::nsMsgQuote()
+{
+ mQuoteHeaders = false;
+ mQuoteListener = nullptr;
+}
+
+nsMsgQuote::~nsMsgQuote()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsMsgQuote, nsIMsgQuote, nsISupportsWeakReference)
+
+NS_IMETHODIMP nsMsgQuote::GetStreamListener(nsIMsgQuotingOutputStreamListener ** aStreamListener)
+{
+ nsresult rv = NS_OK;
+ if (aStreamListener)
+ {
+ *aStreamListener = mStreamListener;
+ NS_IF_ADDREF(*aStreamListener);
+ }
+ else
+ rv = NS_ERROR_NULL_POINTER;
+
+ return rv;
+}
+
+nsresult
+nsMsgQuote::QuoteMessage(const char *msgURI, bool quoteHeaders,
+ nsIMsgQuotingOutputStreamListener * aQuoteMsgStreamListener,
+ const char * aMsgCharSet, bool headersOnly,
+ nsIMsgDBHdr *aMsgHdr)
+{
+ nsresult rv;
+ if (!msgURI)
+ return NS_ERROR_INVALID_ARG;
+
+ mQuoteHeaders = quoteHeaders;
+ mStreamListener = aQuoteMsgStreamListener;
+
+ nsAutoCString msgUri(msgURI);
+ bool fileUrl = !strncmp(msgURI, "file:", 5);
+ bool forwardedMessage = PL_strstr(msgURI, "&realtype=message/rfc822") != nullptr;
+ nsCOMPtr<nsIURI> aURL;
+ if (fileUrl)
+ {
+ msgUri.Replace(0, 5, NS_LITERAL_CSTRING("mailbox:"));
+ msgUri.AppendLiteral("?number=0");
+ rv = NS_NewURI(getter_AddRefs(aURL), msgUri);
+ nsCOMPtr<nsIMsgMessageUrl> mailUrl(do_QueryInterface(aURL));
+ if (mailUrl)
+ mailUrl->SetMessageHeader(aMsgHdr);
+ }
+ else if (forwardedMessage)
+ rv = NS_NewURI(getter_AddRefs(aURL), msgURI);
+ else
+ {
+ nsCOMPtr <nsIMsgMessageService> msgService;
+ rv = GetMessageServiceFromURI(nsDependentCString(msgURI), getter_AddRefs(msgService));
+ if (NS_FAILED(rv)) return rv;
+ rv = msgService->GetUrlForUri(msgURI, getter_AddRefs(aURL), nullptr);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIURL> mailNewsUrl = do_QueryInterface(aURL, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString queryPart;
+ rv = mailNewsUrl->GetQuery(queryPart);
+ if (!queryPart.IsEmpty())
+ queryPart.Append('&');
+
+ if (headersOnly) /* We don't need to quote the message body but we still need to extract the headers */
+ queryPart.Append("header=only");
+ else if (quoteHeaders)
+ queryPart.Append("header=quote");
+ else
+ queryPart.Append("header=quotebody");
+ rv = mailNewsUrl->SetQuery(queryPart);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if we were given a non empty charset, then use it
+ if (aMsgCharSet && *aMsgCharSet)
+ {
+ nsCOMPtr<nsIMsgI18NUrl> i18nUrl (do_QueryInterface(aURL));
+ if (i18nUrl)
+ i18nUrl->SetCharsetOverRide(aMsgCharSet);
+ }
+
+ mQuoteListener = do_CreateInstance(NS_MSGQUOTELISTENER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ mQuoteListener->SetMsgQuote(this);
+
+ // funky magic go get the isupports for this class which inherits from multiple interfaces.
+ nsISupports * supports;
+ QueryInterface(NS_GET_IID(nsISupports), (void **) &supports);
+ nsCOMPtr<nsISupports> quoteSupport = supports;
+ NS_IF_RELEASE(supports);
+
+ // now we want to create a necko channel for this url and we want to open it
+ nsCOMPtr<nsIScriptSecurityManager> secMan(
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secMan->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ mQuoteChannel = nullptr;
+ nsCOMPtr<nsIIOService> netService = mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(netService, NS_ERROR_UNEXPECTED);
+ rv = netService->NewChannelFromURI2(aURL,
+ nullptr,
+ systemPrincipal,
+ nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ getter_AddRefs(mQuoteChannel));
+
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<nsISupports> ctxt = do_QueryInterface(aURL);
+
+ nsCOMPtr<nsIStreamConverterService> streamConverterService =
+ do_GetService("@mozilla.org/streamConverters;1", &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIStreamListener> convertedListener;
+ rv = streamConverterService->AsyncConvertData("message/rfc822",
+ "application/vnd.mozilla.xul+xml",
+ mStreamListener,
+ quoteSupport,
+ getter_AddRefs(convertedListener));
+ if (NS_FAILED(rv)) return rv;
+
+ // now try to open the channel passing in our display consumer as the listener
+ rv = mQuoteChannel->AsyncOpen(convertedListener, ctxt);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgQuote::GetQuoteListener(nsIMimeStreamConverterListener** aQuoteListener)
+{
+ if (!aQuoteListener || !mQuoteListener)
+ return NS_ERROR_NULL_POINTER;
+ *aQuoteListener = mQuoteListener;
+ NS_ADDREF(*aQuoteListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuote::GetQuoteChannel(nsIChannel** aQuoteChannel)
+{
+ if (!aQuoteChannel || !mQuoteChannel)
+ return NS_ERROR_NULL_POINTER;
+ *aQuoteChannel = mQuoteChannel;
+ NS_ADDREF(*aQuoteChannel);
+ return NS_OK;
+}
diff --git a/mailnews/compose/src/nsMsgQuote.h b/mailnews/compose/src/nsMsgQuote.h
new file mode 100644
index 000000000..2151828ef
--- /dev/null
+++ b/mailnews/compose/src/nsMsgQuote.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef __nsMsgQuote_h__
+#define __nsMsgQuote_h__
+
+#include "nsIMsgQuote.h"
+#include "nsIMsgMessageService.h"
+#include "nsIStreamListener.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsIChannel.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+
+class nsMsgQuote;
+
+class nsMsgQuoteListener: public nsIMsgQuoteListener
+{
+public:
+ nsMsgQuoteListener();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIMimeStreamConverterListener support
+ NS_DECL_NSIMIMESTREAMCONVERTERLISTENER
+ NS_DECL_NSIMSGQUOTELISTENER
+
+private:
+ virtual ~nsMsgQuoteListener();
+ nsWeakPtr mMsgQuote;
+};
+
+class nsMsgQuote: public nsIMsgQuote, public nsSupportsWeakReference {
+public:
+ nsMsgQuote();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGQUOTE
+
+private:
+ virtual ~nsMsgQuote();
+ //
+ // Implementation data...
+ //
+ nsCOMPtr<nsIMsgQuotingOutputStreamListener> mStreamListener;
+ bool mQuoteHeaders;
+ nsCOMPtr<nsIMsgQuoteListener> mQuoteListener;
+ nsCOMPtr<nsIChannel> mQuoteChannel;
+};
+
+#endif /* __nsMsgQuote_h__ */
diff --git a/mailnews/compose/src/nsMsgSend.cpp b/mailnews/compose/src/nsMsgSend.cpp
new file mode 100644
index 000000000..919d9bfc5
--- /dev/null
+++ b/mailnews/compose/src/nsMsgSend.cpp
@@ -0,0 +1,5219 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsMsgSend.h"
+#include "prmem.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMsgSendPart.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgNewsCID.h"
+#include "nsISmtpService.h" // for actually sending the message...
+#include "nsINntpService.h" // for actually posting the message...
+#include "nsIMsgMailSession.h"
+#include "nsIMsgIdentity.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMsgCompUtils.h"
+#include "nsMsgI18N.h"
+#include "nsICharsetConverterManager.h"
+#include "nsIMsgSendListener.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIFile.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsMsgCopy.h"
+#include "nsUnicharUtils.h"
+#include "nsMsgPrompts.h"
+#include "nsIDOMHTMLBodyElement.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIDOMHTMLLinkElement.h"
+#include "nsIDOMHTMLAnchorElement.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIMIMEService.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsMsgCompCID.h"
+#include "nsIAbAddressCollector.h"
+#include "nsAbBaseCID.h"
+#include "nsCOMPtr.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIMsgWindow.h"
+#include "nsTextFormatter.h"
+#include "nsIPrompt.h"
+#include "nsMailHeaders.h"
+#include "nsIDocShell.h"
+#include "nsMimeTypes.h"
+#include "nsISmtpUrl.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIEditorMailSupport.h"
+#include "nsIDocumentEncoder.h" // for editor output flags
+#include "nsILoadGroup.h"
+#include "nsMsgSendReport.h"
+#include "nsNetCID.h"
+#include "nsError.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgMdnGenerator.h"
+#include "nsISmtpServer.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIAbCard.h"
+#include "nsIMsgAttachment.h"
+#include "nsIMsgProgress.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFolder.h"
+#include "nsComposeStrings.h"
+#include "nsStringGlue.h"
+#include "nsMsgUtils.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/mailnews/MimeEncoder.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsIMutableArray.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgProtocolInfo.h"
+#include "mozIDOMWindow.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::mailnews;
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+#define PREF_MAIL_SEND_STRUCT "mail.send_struct"
+#define PREF_MAIL_STRICTLY_MIME "mail.strictly_mime"
+#define PREF_MAIL_MESSAGE_WARNING_SIZE "mailnews.message_warning_size"
+#define PREF_MAIL_COLLECT_EMAIL_ADDRESS_OUTGOING "mail.collect_email_address_outgoing"
+
+#define ATTR_MOZ_DO_NOT_SEND "moz-do-not-send"
+
+enum { kDefaultMode = (PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE) };
+
+static bool mime_use_quoted_printable_p = false;
+
+//
+// Ugh, we need to do this currently to access this boolean.
+//
+bool
+UseQuotedPrintable(void)
+{
+ return mime_use_quoted_printable_p;
+}
+
+/* This function will parse a list of email addresses and groups and just
+ * return a list of email addresses (recipient)
+ *
+ * The input could be:
+ * [recipient | group] *[,recipient | group]
+ *
+ * The group syntax is:
+ * group-name:[recipient *[,recipient]];
+ *
+ * the output will be:
+ * recipient *[, recipient]
+ *
+ * As the result will always be equal or smaller than the input string,
+ * the extraction will be made in place. Don't need to create a new buffer.
+ */
+static nsresult StripOutGroupNames(char * addresses)
+{
+ char aChar;
+ char * readPtr = addresses; // current read position
+ char * writePtr = addresses; // current write position
+ char * previousSeparator = addresses; // remember last time we wrote a recipient separator
+ char * endPtr = addresses + PL_strlen(addresses);
+
+ bool quoted = false; // indicate if we are between double quote
+ bool group = false; // indicate if we found a group prefix
+ bool atFound = false; // indicate if we found an @ in the current recipient. group name should not have an @
+
+ while (readPtr < endPtr)
+ {
+ aChar = *readPtr;
+ readPtr ++;
+ switch(aChar)
+ {
+ case '\\':
+ if (*readPtr == '"') //ignore escaped quote
+ readPtr ++;
+ continue;
+
+ case '"':
+ quoted = !quoted;
+ break;
+
+ case '@':
+ if (!quoted)
+ atFound = true;
+ break;
+
+ case ':':
+ if (!quoted && !atFound)
+ {
+ // ok, we found a group name
+ // let's backup the write cursor to remove the group name
+ writePtr = previousSeparator + 1;
+ group = true;
+ continue;
+ }
+ break;
+
+ case ';':
+ if (quoted || !group)
+ break;
+ else
+ group = false;
+ //end of the group, act like a recipient separator now...
+ /* NO BREAK */
+ MOZ_FALLTHROUGH;
+ case ',':
+ if (!quoted)
+ {
+ atFound = false;
+ //let check if we already have a comma separator in the output string
+ if (writePtr > addresses && *(writePtr - 1) == ',')
+ writePtr --;
+ *writePtr = ',';
+ previousSeparator = writePtr;
+ writePtr ++;
+ continue;
+ }
+ break;
+ }
+ *writePtr = aChar;
+ writePtr ++;
+ }
+
+ if (writePtr > addresses && *(writePtr - 1) == ',')
+ writePtr --;
+ *writePtr = '\0';
+
+ return NS_OK;
+}
+
+
+// This private class just provides us an external URL listener, with callback functionality.
+
+class MsgDeliveryListener : public nsIUrlListener
+{
+public:
+ MsgDeliveryListener(nsIMsgSend *aMsgSend, bool inIsNewsDelivery);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+
+private:
+ virtual ~MsgDeliveryListener();
+ nsCOMPtr<nsIMsgSend> mMsgSend;
+ bool mIsNewsDelivery;
+};
+
+NS_IMPL_ISUPPORTS(MsgDeliveryListener, nsIUrlListener)
+
+MsgDeliveryListener::MsgDeliveryListener(nsIMsgSend *aMsgSend, bool inIsNewsDelivery)
+{
+ mMsgSend = aMsgSend;
+ mIsNewsDelivery = inIsNewsDelivery;
+}
+
+MsgDeliveryListener::~MsgDeliveryListener()
+{
+}
+
+NS_IMETHODIMP MsgDeliveryListener::OnStartRunningUrl(nsIURI *url)
+{
+ if (mMsgSend)
+ mMsgSend->NotifyListenerOnStartSending(nullptr, 0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP MsgDeliveryListener::OnStopRunningUrl(nsIURI *url, nsresult aExitCode)
+{
+ if (url)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(url);
+ if (mailUrl)
+ mailUrl->UnRegisterListener(this);
+ }
+
+ // Let mMsgSend sort out the OnStopSending notification - it knows more about
+ // the messages than we do.
+ if (mMsgSend)
+ mMsgSend->SendDeliveryCallback(url, mIsNewsDelivery, aExitCode);
+
+ return NS_OK;
+}
+
+
+/* the following macro actually implement addref, release and query interface for our component. */
+NS_IMPL_ISUPPORTS(nsMsgComposeAndSend, nsIMsgSend, nsIMsgOperationListener,
+ nsISupportsWeakReference)
+
+nsMsgComposeAndSend::nsMsgComposeAndSend() :
+ m_messageKey(nsMsgKey_None)
+{
+ mGUINotificationEnabled = true;
+ mAbortInProcess = false;
+ mMultipartRelatedAttachmentCount = -1;
+ mSendMailAlso = false;
+
+ m_dont_deliver_p = false;
+ m_deliver_mode = nsMsgDeliverNow;
+
+ m_pre_snarfed_attachments_p = false;
+ m_digest_p = false;
+ m_be_synchronous_p = false;
+ m_attachment1_type = 0;
+ m_attachment1_encoding = 0;
+ m_attachment1_body = 0;
+ m_attachment1_body_length = 0;
+ m_attachment_count = 0;
+ m_attachment_pending_count = 0;
+ m_status = NS_OK;
+ m_plaintext = nullptr;
+ m_related_part = nullptr;
+ m_related_body_part = nullptr;
+ mOriginalHTMLBody = nullptr;
+
+ mNeedToPerformSecondFCC = false;
+ mPerformingSecondFCC = false;
+
+ mPreloadedAttachmentCount = 0;
+ mRemoteAttachmentCount = 0;
+ mCompFieldLocalAttachments = 0;
+ mCompFieldRemoteAttachments = 0;
+ mMessageWarningSize = 0;
+
+ mSendReport = new nsMsgSendReport();
+}
+
+nsMsgComposeAndSend::~nsMsgComposeAndSend()
+{
+ PR_Free(m_attachment1_type);
+ PR_Free(m_attachment1_encoding);
+ PR_Free(m_attachment1_body);
+ PR_Free(mOriginalHTMLBody);
+
+ if (m_plaintext)
+ {
+ if (m_plaintext->mTmpFile)
+ m_plaintext->mTmpFile->Remove(false);
+
+ m_plaintext = nullptr;
+ }
+
+ if (mHTMLFile)
+ mHTMLFile->Remove(false);
+
+ if (mCopyFile)
+ mCopyFile->Remove(false);
+
+ if (mCopyFile2)
+ mCopyFile2->Remove(false);
+
+ if (mTempFile && !mReturnFile)
+ mTempFile->Remove(false);
+
+ m_attachments.Clear();
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::GetDefaultPrompt(nsIPrompt ** aPrompt)
+{
+ NS_ENSURE_ARG(aPrompt);
+ *aPrompt = nullptr;
+
+ nsresult rv = NS_OK;
+
+ if (mParentWindow)
+ {
+ rv = mParentWindow->GetPrompter(aPrompt);
+ if (NS_SUCCEEDED(rv) && *aPrompt)
+ return NS_OK;
+ }
+
+ /* If we cannot find a prompter, try the mail3Pane window */
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr <nsIMsgMailSession> mailSession (do_GetService(NS_MSGMAILSESSION_CONTRACTID));
+ if (mailSession)
+ {
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ rv = msgWindow->GetPromptDialog(aPrompt);
+ }
+
+ return rv;
+}
+
+nsresult nsMsgComposeAndSend::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks)
+{
+// TODO: stop using mail3pane window!
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIMsgMailSession> mailSession(do_GetService(NS_MSGMAILSESSION_CONTRACTID));
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow) {
+ nsCOMPtr<nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(docShell));
+ nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
+ msgWindow->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks));
+ if (notificationCallbacks) {
+ nsCOMPtr<nsIInterfaceRequestor> aggregrateIR;
+ MsgNewInterfaceRequestorAggregation(notificationCallbacks, ir, getter_AddRefs(aggregrateIR));
+ ir = aggregrateIR;
+ }
+ if (ir) {
+ NS_ADDREF(*aCallbacks = ir);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+
+static char *mime_mailto_stream_read_buffer = 0;
+static char *mime_mailto_stream_write_buffer = 0;
+
+
+char * mime_get_stream_write_buffer(void)
+{
+ if (!mime_mailto_stream_write_buffer)
+ mime_mailto_stream_write_buffer = (char *) PR_Malloc(MIME_BUFFER_SIZE);
+ return mime_mailto_stream_write_buffer;
+}
+
+static bool isEmpty(const char* aString)
+{
+ return (!aString) || (!*aString);
+}
+
+void nsMsgComposeAndSend::GenerateMessageId()
+{
+ if (isEmpty(mCompFields->GetMessageId()))
+ {
+ if (isEmpty(mCompFields->GetTo()) &&
+ isEmpty(mCompFields->GetCc()) &&
+ isEmpty(mCompFields->GetBcc()) &&
+ !isEmpty(mCompFields->GetNewsgroups()))
+ {
+ bool generateNewsMessageId = false;
+ mUserIdentity->GetBoolAttribute("generate_news_message_id", &generateNewsMessageId);
+ if (!generateNewsMessageId)
+ return;
+ }
+
+ char* msgID = msg_generate_message_id(mUserIdentity);
+ mCompFields->SetMessageId(msgID);
+ PR_Free(msgID);
+ }
+}
+
+// Don't I18N this line...this is per the spec!
+#define MIME_MULTIPART_BLURB "This is a multi-part message in MIME format."
+
+/* All of the desired attachments have been written to individual temp files,
+ and we know what's in them. Now we need to make a final temp file of the
+ actual mail message, containing all of the other files after having been
+ encoded as appropriate.
+ */
+NS_IMETHODIMP
+nsMsgComposeAndSend::GatherMimeAttachments()
+{
+ bool shouldDeleteDeliveryState = true;
+ nsresult status;
+ uint32_t i;
+ PRFileDesc *in_file = 0;
+ char *buffer = 0;
+ nsString msg;
+ bool body_is_us_ascii = true;
+ bool isUsingQP = false;
+
+ nsMsgSendPart* toppart = nullptr; // The very top most container of the message
+ // that we are going to send.
+
+ nsMsgSendPart* mainbody = nullptr; // The leaf node that contains the text of the
+ // message we're going to contain.
+
+ nsMsgSendPart* maincontainer = nullptr; // The direct child of toppart that will
+ // contain the mainbody. If mainbody is
+ // the same as toppart, then this is
+ // also the same. But if mainbody is
+ // to end up somewhere inside of a
+ // multipart/alternative or a
+ // multipart/related, then this is that
+ // multipart object.
+
+ nsMsgSendPart* plainpart = nullptr; // If we converted HTML into plaintext,
+ // the message or child containing the plaintext
+ // goes here. (Need to use this to determine
+ // what headers to append/set to the main
+ // message body.)
+
+ uint32_t multipartRelatedCount = GetMultipartRelatedCount(); // The number of related part we will have to generate
+
+ nsCOMPtr<nsIPrompt> promptObject; // only used if we have to show an alert here....
+ GetDefaultPrompt(getter_AddRefs(promptObject));
+
+ char *hdrs = 0;
+ bool maincontainerISrelatedpart = false;
+ const char * toppart_type = nullptr;
+
+ status = m_status;
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ if (!m_attachment1_type) {
+ m_attachment1_type = PL_strdup(TEXT_PLAIN);
+ if (!m_attachment1_type)
+ goto FAILMEM;
+ }
+
+ nsresult rv;
+
+ // If we have a text/html main part, and we need a plaintext attachment, then
+ // we'll do so now. This is an asynchronous thing, so we'll kick it off and
+ // count on getting back here when it finishes.
+
+ if (m_plaintext == nullptr &&
+ (mCompFields->GetForcePlainText() ||
+ mCompFields->GetUseMultipartAlternative()) &&
+ m_attachment1_body && PL_strcmp(m_attachment1_type, TEXT_HTML) == 0)
+ {
+ //
+ // If we get here, we have an HTML body, but we really need to send
+ // a text/plain message, so we will write the HTML out to a disk file,
+ // fire off another URL request for this local disk file and that will
+ // take care of the conversion...
+ //
+ rv = nsMsgCreateTempFile("nsemail.html", getter_AddRefs(mHTMLFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> tempfile;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(tempfile), mHTMLFile, -1, 00600);
+ if (NS_FAILED(rv))
+ {
+ if (mSendReport)
+ {
+ nsAutoString error_msg;
+ nsMsgBuildMessageWithTmpFile(mHTMLFile, error_msg);
+ mSendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false);
+ }
+ status = NS_MSG_UNABLE_TO_OPEN_TMP_FILE;
+ goto FAIL;
+ }
+
+ if (mOriginalHTMLBody)
+ {
+ uint32_t origLen = strlen(mOriginalHTMLBody);
+ uint32_t n;
+ nsresult rv = tempfile->Write(mOriginalHTMLBody, origLen, &n);
+ if (NS_FAILED(rv) || n != origLen)
+ {
+ status = NS_MSG_ERROR_WRITING_FILE;
+ goto FAIL;
+ }
+ }
+
+ if (NS_FAILED(tempfile->Flush()))
+ {
+ status = NS_MSG_ERROR_WRITING_FILE;
+ goto FAIL;
+ }
+
+ tempfile->Close();
+
+ m_plaintext = new nsMsgAttachmentHandler;
+ if (!m_plaintext)
+ goto FAILMEM;
+ m_plaintext->SetMimeDeliveryState(this);
+ m_plaintext->m_bogus_attachment = true;
+
+ nsAutoCString tempURL;
+ rv = NS_GetURLSpecFromFile(mHTMLFile, tempURL);
+ if (NS_FAILED(rv) || NS_FAILED(nsMsgNewURL(getter_AddRefs(m_plaintext->mURL), tempURL.get())))
+ {
+ m_plaintext = nullptr;
+ goto FAILMEM;
+ }
+
+ m_plaintext->m_type = TEXT_HTML;
+ m_plaintext->m_charset = mCompFields->GetCharacterSet();
+ m_plaintext->m_desiredType = TEXT_PLAIN;
+ m_attachment_pending_count ++;
+ status = m_plaintext->SnarfAttachment(mCompFields);
+ if (NS_FAILED(status))
+ goto FAIL;
+ if (m_attachment_pending_count > 0)
+ return NS_OK;
+ }
+
+ /* Kludge to avoid having to allocate memory on the toy computers... */
+ buffer = mime_get_stream_write_buffer();
+ if (! buffer)
+ goto FAILMEM;
+
+ NS_ASSERTION (m_attachment_pending_count == 0, "m_attachment_pending_count != 0");
+
+ mComposeBundle->GetStringFromName(u"assemblingMessage",
+ getter_Copies(msg));
+ SetStatusMessage( msg );
+
+ /* First, open the message file.
+ */
+ rv = nsMsgCreateTempFile("nsemail.eml", getter_AddRefs(mTempFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mOutputFile), mTempFile, -1, 00600);
+ if (NS_FAILED(rv))
+ {
+ status = NS_MSG_UNABLE_TO_OPEN_TMP_FILE;
+ if (mSendReport)
+ {
+ nsAutoString error_msg;
+ nsMsgBuildMessageWithTmpFile(mTempFile, error_msg);
+ mSendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false);
+ }
+ goto FAIL;
+ }
+
+ // generate a message id, if necessary
+ GenerateMessageId( );
+
+ mainbody = new nsMsgSendPart(this, mCompFields->GetCharacterSet());
+ if (!mainbody)
+ goto FAILMEM;
+
+ mainbody->SetMainPart(true);
+ mainbody->SetType(m_attachment1_type ? m_attachment1_type : TEXT_PLAIN);
+
+ NS_ASSERTION(mainbody->GetBuffer() == nullptr, "not-null buffer");
+ status = mainbody->SetBuffer(m_attachment1_body ? m_attachment1_body : "");
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ // Determine the encoding of the main message body before we free it.
+ PR_FREEIF(m_attachment1_encoding);
+ if (m_attachment1_body)
+ mCompFields->GetBodyIsAsciiOnly(&body_is_us_ascii);
+
+ if (!mCompFields->GetForceMsgEncoding() && (body_is_us_ascii ||
+ nsMsgI18Nstateful_charset(mCompFields->GetCharacterSet()))) {
+ m_attachment1_encoding = PL_strdup (ENCODING_7BIT);
+ } else if (mime_use_quoted_printable_p) {
+ m_attachment1_encoding = PL_strdup (ENCODING_QUOTED_PRINTABLE);
+ isUsingQP = true;
+ } else {
+ m_attachment1_encoding = PL_strdup (ENCODING_8BIT);
+ }
+
+ // Make sure the lines don't get to long.
+ if (m_attachment1_body) {
+ uint32_t max_column = 0;
+ uint32_t cur_column = 0;
+ for (char* c = m_attachment1_body; *c; c++) {
+ if (*c == '\n') {
+ if (cur_column > max_column)
+ max_column = cur_column;
+ cur_column = 0;
+ } else if (*c != '\r') {
+ cur_column++;
+ }
+ }
+ // Check one last time for the last line.
+ if (cur_column > max_column)
+ max_column = cur_column;
+ if (max_column > LINELENGTH_ENCODING_THRESHOLD && !isUsingQP) {
+ // To encode "long lines" use a CTE that will transmit shorter lines.
+ // Switch to base64 if we are not already using "quoted printable".
+ PR_FREEIF(m_attachment1_encoding);
+ m_attachment1_encoding = PL_strdup (ENCODING_BASE64);
+ }
+ }
+ PR_FREEIF (m_attachment1_body);
+
+ maincontainer = mainbody;
+
+ // If we were given a pre-saved collection of HTML and contained images,
+ // then we want mainbody to point to the HTML lump therein.
+ if (m_related_part)
+ {
+ // If m_related_part is of type text/html, set both maincontainer
+ // and mainbody to point to it. If m_related_part is multipart/related,
+ // however, set mainbody to be the first child within m_related_part.
+ delete mainbody;
+
+ // No matter what, maincontainer points to the outermost related part.
+ maincontainer = m_related_part;
+ maincontainerISrelatedpart = true;
+
+ mainbody = m_related_part->GetChild(0);
+ mainbody->SetMainPart(true);
+ }
+ if (m_plaintext)
+ {
+ //
+ // OK. We have a plaintext version of the main body that we want to
+ // send instead of or with the text/html. Shove it in.
+ //
+ plainpart = new nsMsgSendPart(this, mCompFields->GetCharacterSet());
+ if (!plainpart)
+ goto FAILMEM;
+ status = plainpart->SetType(TEXT_PLAIN);
+ if (NS_FAILED(status))
+ goto FAIL;
+ status = plainpart->SetFile(m_plaintext->mTmpFile);
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ m_plaintext->mMainBody = true;
+
+ // Determine Content-Transfer-Encoding for the attachments.
+ m_plaintext->PickEncoding(mCompFields->GetCharacterSet(), this);
+ const char *charset = mCompFields->GetCharacterSet();
+ hdrs = mime_generate_attachment_headers(m_plaintext->m_type.get(),
+ nullptr,
+ m_plaintext->m_encoding.get(),
+ m_plaintext->m_description.get(),
+ m_plaintext->m_xMacType.get(),
+ m_plaintext->m_xMacCreator.get(),
+ nullptr, 0,
+ m_digest_p,
+ m_plaintext,
+ charset,
+ charset,
+ body_is_us_ascii,
+ nullptr,
+ true);
+ if (!hdrs)
+ goto FAILMEM;
+ status = plainpart->SetOtherHeaders(hdrs);
+ PR_Free(hdrs);
+ hdrs = nullptr;
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ if (mCompFields->GetUseMultipartAlternative())
+ {
+ nsMsgSendPart* htmlpart = maincontainer;
+ maincontainer = new nsMsgSendPart(this);
+ if (!maincontainer)
+ goto FAILMEM;
+
+ // Setup the maincontainer stuff...
+ status = maincontainer->SetType(MULTIPART_ALTERNATIVE);
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ status = maincontainer->AddChild(plainpart);
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ status = maincontainer->AddChild(htmlpart);
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ // Create the encoder for the plaintext part here,
+ // because we aren't the main part (attachment1).
+ // (This, along with the rest of the routine, should really
+ // be restructured so that no special treatment is given to
+ // the main body text that came in. Best to put attachment1_text
+ // etc. into a nsMsgSendPart, then reshuffle the parts. Sigh.)
+ if (m_plaintext->m_encoding.LowerCaseEqualsLiteral(ENCODING_QUOTED_PRINTABLE))
+ {
+ plainpart->SetEncoder(MimeEncoder::GetQPEncoder(
+ mime_encoder_output_fn, this));
+ }
+ else if (m_plaintext->m_encoding.LowerCaseEqualsLiteral(ENCODING_BASE64))
+ {
+ plainpart->SetEncoder(MimeEncoder::GetBase64Encoder(
+ mime_encoder_output_fn, this));
+ }
+ }
+ else
+ {
+ delete maincontainer;
+ if (maincontainerISrelatedpart)
+ m_related_part = nullptr; // in that case, m_related_part == maincontainer which we have just deleted!
+ maincontainer = plainpart;
+ mainbody = maincontainer;
+ PR_FREEIF(m_attachment1_type);
+ m_attachment1_type = PL_strdup(TEXT_PLAIN);
+ if (!m_attachment1_type)
+ goto FAILMEM;
+
+ // Override attachment1_encoding here. We do this blindly since we are
+ // sending plaintext only at this point.
+ PR_FREEIF(m_attachment1_encoding);
+ m_attachment1_encoding = ToNewCString(m_plaintext->m_encoding);
+ }
+ }
+
+ // check if we need to encapsulate the message in a multipart/mixed or multipart/digest
+ if (m_attachment_count > multipartRelatedCount)
+ {
+ toppart = new nsMsgSendPart(this);
+ if (!toppart)
+ goto FAILMEM;
+
+ status = toppart->SetType(m_digest_p ? MULTIPART_DIGEST : MULTIPART_MIXED);
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ status = toppart->AddChild(maincontainer);
+ if (NS_FAILED(status))
+ goto FAIL;
+ }
+ else
+ toppart = maincontainer;
+
+ // Is the top part a multipart container?
+ // can't use m_attachment_count because it's not reliable for that
+ // instead use type of main part. See bug #174396
+ toppart_type = toppart->GetType(); // GetType return directly the member variable, don't free it!
+ if (!m_crypto_closure && toppart_type && !PL_strncasecmp(toppart_type, "multipart/", 10))
+ {
+ status = toppart->SetBuffer(MIME_MULTIPART_BLURB);
+ if (NS_FAILED(status))
+ goto FAIL;
+ }
+
+ {
+ nsCOMPtr<msgIWritableStructuredHeaders> outputHeaders =
+ do_CreateInstance(NS_ISTRUCTUREDHEADERS_CONTRACTID);
+ status = mime_generate_headers(mCompFields, m_deliver_mode, outputHeaders);
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ // Convert the blocks of headers into a single string for emission.
+ nsAutoCString headers;
+ outputHeaders->BuildMimeText(headers);
+
+ // If we converted HTML into plaintext, the plaintext part (plainpart)
+ // already has its content-type and content-transfer-encoding ("other")
+ // headers set.
+ //
+ // In the specific case where such a plaintext part is the top-level message
+ // part (iff an HTML message is being sent as text only and no other
+ // attachments exist) we want to preserve the original plainpart headers,
+ // since they contain accurate transfer encoding and Mac type/creator
+ // information.
+ //
+ // So, in the above case we append the main message headers, otherwise we
+ // overwrite whatever headers may have existed.
+ if (plainpart && plainpart == toppart)
+ status = toppart->AppendOtherHeaders(headers.get());
+ else
+ status = toppart->SetOtherHeaders(headers.get());
+ }
+
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ // Set up the first part (user-typed.) For now, do it even if the first
+ // part is empty; we need to add things to skip it if this part is empty.
+
+ // Set up encoder for the first part (message body.)
+ //
+ NS_ASSERTION(!m_attachment1_encoder, "not-null m_attachment1_encoder");
+ if (!PL_strcasecmp(m_attachment1_encoding, ENCODING_BASE64))
+ {
+ m_attachment1_encoder = MimeEncoder::GetBase64Encoder(
+ mime_encoder_output_fn, this);
+ }
+ else if (!PL_strcasecmp(m_attachment1_encoding, ENCODING_QUOTED_PRINTABLE))
+ {
+ m_attachment1_encoder = MimeEncoder::GetQPEncoder(mime_encoder_output_fn,
+ this);
+ }
+
+ // If we converted HTML into plaintext, the plaintext part
+ // already has its type/encoding headers set. So, in the specific
+ // case where such a plaintext part is the main message body
+ // (iff an HTML message is being sent as text only)
+ // we want to avoid generating type/encoding/digest headers;
+ // in all other cases, generate such headers here.
+ //
+ // We really want to set up headers as a dictionary of some sort
+ // so that we need not worry about duplicate header lines.
+ //
+ if ((!plainpart) || (plainpart != mainbody))
+ {
+ const char *charset = mCompFields->GetCharacterSet();
+ hdrs = mime_generate_attachment_headers (m_attachment1_type,
+ nullptr,
+ m_attachment1_encoding,
+ 0, 0, 0, 0, 0,
+ m_digest_p,
+ nullptr, /* no "ma"! */
+ charset,
+ charset,
+ mCompFields->GetBodyIsAsciiOnly(),
+ nullptr,
+ true);
+ if (!hdrs)
+ goto FAILMEM;
+ status = mainbody->AppendOtherHeaders(hdrs);
+ if (NS_FAILED(status))
+ goto FAIL;
+ }
+
+ PR_FREEIF(hdrs);
+
+ mainbody->SetEncoder(m_attachment1_encoder.forget());
+
+ //
+ // Now we need to process attachments and slot them in the
+ // correct hierarchy.
+ //
+ if (m_attachment_count > 0)
+ {
+ // Kludge to avoid having to allocate memory on the toy computers...
+ if (! mime_mailto_stream_read_buffer)
+ mime_mailto_stream_read_buffer = (char *) PR_Malloc (MIME_BUFFER_SIZE);
+ buffer = mime_mailto_stream_read_buffer;
+ if (! buffer)
+ goto FAILMEM;
+
+ // Gather all of the attachments for this message that are NOT
+ // part of an enclosed MHTML message!
+ for (i = 0; i < m_attachment_count; i++)
+ {
+ nsMsgAttachmentHandler *ma = m_attachments[i];
+ if (!ma->mMHTMLPart)
+ PreProcessPart(ma, toppart);
+ }
+
+ //
+ // If we have a m_related_part as a container for children, then we have to
+ // tack on these children for the part
+ //
+ if (m_related_part)
+ {
+ for (i = 0; i < m_attachment_count; i++)
+ {
+ //
+ // look for earlier part with the same content id. If we find it,
+ // need to remember the mapping between our node index and the
+ // part num of the earlier part.
+ int32_t nodeIndex = m_attachments[i]->mNodeIndex;
+ if (nodeIndex != -1)
+ {
+ for (uint32_t j = 0; j < i; j++)
+ {
+ if (m_attachments[j]->mNodeIndex != -1 &&
+ m_attachments[j]->m_contentId.Equals(m_attachments[i]->m_contentId))
+ m_partNumbers[nodeIndex] = m_partNumbers[m_attachments[j]->mNodeIndex];
+ }
+ }
+ // rhp: This is here because we could get here after saying OK
+ // to a lot of prompts about not being able to fetch this part!
+ //
+ if (m_attachments[i]->mPartUserOmissionOverride)
+ continue;
+
+ // Now, we need to add this part to the m_related_part member so the
+ // message will be generated correctly.
+ if (m_attachments[i]->mMHTMLPart)
+ PreProcessPart(m_attachments[i], m_related_part);
+ }
+ }
+
+ }
+
+ // Tell the user we are creating the message...
+ mComposeBundle->GetStringFromName(u"creatingMailMessage",
+ getter_Copies(msg));
+ SetStatusMessage( msg );
+
+ // OK, now actually write the structure we've carefully built up.
+ status = toppart->Write();
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ /* Close down encryption stream */
+ if (m_crypto_closure)
+ {
+ status = m_crypto_closure->FinishCryptoEncapsulation(false, mSendReport);
+ m_crypto_closure = nullptr;
+ if (NS_FAILED(status)) goto FAIL;
+ }
+
+ if (mOutputFile)
+ {
+ if (NS_FAILED(mOutputFile->Flush()))
+ {
+ status = NS_MSG_ERROR_WRITING_FILE;
+ goto FAIL;
+ }
+
+ mOutputFile->Close();
+ mOutputFile = nullptr;
+
+ // mTempFile is stale because we wrote to it. Get another copy to refresh.
+ nsCOMPtr<nsIFile> tempFileCopy;
+ mTempFile->Clone(getter_AddRefs(tempFileCopy));
+ mTempFile = tempFileCopy;
+ tempFileCopy = nullptr;
+ /* If we don't do this check...ZERO length files can be sent */
+ int64_t fileSize;
+ rv = mTempFile->GetFileSize(&fileSize);
+ if (NS_FAILED(rv) || fileSize == 0)
+ {
+ status = NS_MSG_ERROR_WRITING_FILE;
+ goto FAIL;
+ }
+ }
+
+ mComposeBundle->GetStringFromName(u"assemblingMessageDone",
+ getter_Copies(msg));
+ SetStatusMessage(msg);
+
+ if (m_dont_deliver_p && mListener)
+ {
+ //
+ // Need to ditch the file spec here so that we don't delete the
+ // file, since in this case, the caller wants the file
+ //
+ mReturnFile = mTempFile;
+ mTempFile = nullptr;
+ if (!mReturnFile)
+ NotifyListenerOnStopSending(nullptr, NS_ERROR_OUT_OF_MEMORY, nullptr, nullptr);
+ else
+ {
+ NotifyListenerOnStopSending(nullptr, NS_OK, nullptr, mReturnFile);
+ }
+ }
+ else
+ {
+ status = DeliverMessage();
+ if (NS_SUCCEEDED(status))
+ shouldDeleteDeliveryState = false;
+ }
+ goto FAIL;
+
+FAILMEM:
+ status = NS_ERROR_OUT_OF_MEMORY;
+
+FAIL:
+ if (toppart)
+ delete toppart;
+ toppart = nullptr;
+ mainbody = nullptr;
+ maincontainer = nullptr;
+
+ if (in_file)
+ {
+ PR_Close (in_file);
+ in_file = nullptr;
+ }
+
+ if (shouldDeleteDeliveryState)
+ {
+ if (NS_FAILED(status))
+ {
+ m_status = status;
+ nsresult ignoreMe;
+ Fail(status, nullptr, &ignoreMe);
+ }
+ }
+
+ return status;
+}
+
+int32_t
+nsMsgComposeAndSend::PreProcessPart(nsMsgAttachmentHandler *ma,
+ nsMsgSendPart *toppart) // The very top most container of the message
+{
+ nsresult status;
+ char *hdrs = 0;
+ nsMsgSendPart *part = nullptr;
+
+ // If this was one of those dead parts from a quoted web page,
+ // then just return safely.
+ //
+ if (ma->m_bogus_attachment)
+ return 0;
+
+ // If at this point we *still* don't have a content-type, then
+ // we're never going to get one.
+ if (ma->m_type.IsEmpty())
+ ma->m_type = UNKNOWN_CONTENT_TYPE;
+
+ ma->PickEncoding(mCompFields->GetCharacterSet(), this);
+ ma->PickCharset();
+
+ part = new nsMsgSendPart(this);
+ if (!part)
+ return 0;
+ status = toppart->AddChild(part);
+ // Remember the part number if it has a node index.
+ if (ma->mNodeIndex != -1)
+ m_partNumbers[ma->mNodeIndex] = part->m_partNum;
+
+ if (NS_FAILED(status))
+ return 0;
+ status = part->SetType(ma->m_type.get());
+ if (NS_FAILED(status))
+ return 0;
+
+ if (ma->mSendViaCloud)
+ ma->m_encoding = ENCODING_7BIT;
+
+ nsCString turl;
+ if (!ma->mURL)
+ {
+ if (!ma->m_uri.IsEmpty())
+ turl = ma->m_uri;
+ }
+ else {
+ status = ma->mURL->GetSpec(turl);
+ if (NS_FAILED(status))
+ return 0;
+ }
+
+ nsCString type(ma->m_type);
+ nsCString realName(ma->m_realName);
+
+ // for cloud attachments, make the part an html part with no name,
+ // so we don't show it as an attachment.
+ if (ma->mSendViaCloud)
+ {
+ type.Assign("application/octet-stream");
+ realName.Truncate();
+ }
+ hdrs = mime_generate_attachment_headers (type.get(),
+ ma->m_typeParam.get(),
+ ma->m_encoding.get(),
+ ma->m_description.get(),
+ ma->m_xMacType.get(),
+ ma->m_xMacCreator.get(),
+ realName.get(),
+ turl.get(),
+ m_digest_p,
+ ma,
+ ma->m_charset.get(), // rhp - this needs
+ // to be the charset
+ // we determine from
+ // the file or none
+ // at all!
+ mCompFields->GetCharacterSet(),
+ false, // bodyIsAsciiOnly to false
+ // for attachments
+ ma->m_contentId.get(),
+ false);
+ if (!hdrs)
+ return 0;
+
+ status = part->SetOtherHeaders(hdrs);
+ PR_FREEIF(hdrs);
+ if (ma->mSendViaCloud)
+ {
+ nsCString urlSpec;
+ status = ma->mURL->GetSpec(urlSpec);
+ if (NS_FAILED(status))
+ return 0;
+
+ // Need to add some headers so that libmime can restore the cloud info
+ // when loading a draft message.
+ nsCString draftInfo(HEADER_X_MOZILLA_CLOUD_PART": cloudFile; url=");
+ draftInfo.Append(ma->mCloudUrl.get());
+ // don't leak user file paths or account keys to recipients.
+ if (m_deliver_mode == nsMsgSaveAsDraft)
+ {
+ draftInfo.Append("; provider=");
+ draftInfo.Append(ma->mCloudProviderKey.get());
+ draftInfo.Append("; file=");
+ draftInfo.Append(urlSpec.get());
+ }
+ draftInfo.Append("; name=");
+ draftInfo.Append(ma->m_realName.get());
+ draftInfo.Append(CRLF);
+ part->AppendOtherHeaders(draftInfo.get());
+ part->SetType("application/octet-stream");
+ part->SetBuffer("");
+ }
+ if (NS_FAILED(status))
+ return 0;
+ status = part->SetFile(ma->mTmpFile);
+ if (NS_FAILED(status))
+ return 0;
+ if (ma->m_encoder)
+ {
+ part->SetEncoder(ma->m_encoder.forget());
+ }
+
+ ma->m_current_column = 0;
+
+ if (ma->m_type.LowerCaseEqualsLiteral(MESSAGE_RFC822) ||
+ ma->m_type.LowerCaseEqualsLiteral(MESSAGE_NEWS)) {
+ part->SetStripSensitiveHeaders(true);
+ }
+
+ return 1;
+}
+
+# define FROB(X) \
+ if (X && *X) \
+ { \
+ if (*recipients) PL_strcat(recipients, ","); \
+ PL_strcat(recipients, X); \
+ }
+
+nsresult nsMsgComposeAndSend::BeginCryptoEncapsulation ()
+{
+ // Try to create a secure compose object. If we can create it, then query to see
+ // if we need to use it for this send transaction.
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgComposeSecure> secureCompose;
+ secureCompose = do_CreateInstance(NS_MSGCOMPOSESECURE_CONTRACTID, &rv);
+ // it's not an error scenario of there is secure compose
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ if (secureCompose)
+ {
+ bool requiresEncryptionWork = false;
+ secureCompose->RequiresCryptoEncapsulation(mUserIdentity, mCompFields, &requiresEncryptionWork);
+ if (requiresEncryptionWork)
+ {
+ m_crypto_closure = secureCompose;
+ // bah i'd like to move the following blurb into the implementation of BeginCryptoEncapsulation; however
+ // the apis for nsIMsgComposeField just aren't rich enough. It requires the implementor to jump through way
+ // too many string conversions....
+ char * recipients = (char *)
+ PR_MALLOC((mCompFields->GetTo() ? strlen(mCompFields->GetTo()) : 0) +
+ (mCompFields->GetCc() ? strlen(mCompFields->GetCc()) : 0) +
+ (mCompFields->GetBcc() ? strlen(mCompFields->GetBcc()) : 0) +
+ (mCompFields->GetNewsgroups() ? strlen(mCompFields->GetNewsgroups()) : 0) + 20);
+ if (!recipients) return NS_ERROR_OUT_OF_MEMORY;
+
+ *recipients = 0;
+
+ FROB(mCompFields->GetTo())
+ FROB(mCompFields->GetCc())
+ FROB(mCompFields->GetBcc())
+ FROB(mCompFields->GetNewsgroups())
+
+ // end section of code I'd like to move to the implementor.....
+ rv = m_crypto_closure->BeginCryptoEncapsulation(mOutputFile,
+ recipients,
+ mCompFields,
+ mUserIdentity,
+ mSendReport,
+ (m_deliver_mode == nsMsgSaveAsDraft));
+
+ PR_FREEIF(recipients);
+ }
+
+ }
+
+ return rv;
+}
+
+nsresult
+mime_write_message_body(nsIMsgSend *state, const char *buf, uint32_t size)
+{
+ NS_ENSURE_ARG_POINTER(state);
+
+ nsCOMPtr<nsIOutputStream> output;
+ nsCOMPtr<nsIMsgComposeSecure> crypto_closure;
+
+ state->GetOutputStream(getter_AddRefs(output));
+ if (!output)
+ return NS_MSG_ERROR_WRITING_FILE;
+
+ state->GetCryptoclosure(getter_AddRefs(crypto_closure));
+ if (crypto_closure)
+ {
+ // Copy to new null-terminated string so JS glue doesn't crash when
+ // MimeCryptoWriteBlock() is implemented in JS.
+ nsCString bufWithNull;
+ bufWithNull.Assign(buf, size);
+ return crypto_closure->MimeCryptoWriteBlock(bufWithNull.get(), size);
+ }
+
+ uint32_t n;
+ nsresult rv = output->Write(buf, size, &n);
+ if (NS_FAILED(rv) || n != size)
+ {
+ return NS_MSG_ERROR_WRITING_FILE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+mime_encoder_output_fn(const char *buf, int32_t size, void *closure)
+{
+ nsMsgComposeAndSend *state = (nsMsgComposeAndSend *) closure;
+ return mime_write_message_body (state, (char *) buf, (uint32_t)size);
+}
+
+nsresult
+nsMsgComposeAndSend::GetEmbeddedObjectInfo(nsIDOMNode *node, nsMsgAttachmentData *attachment, bool *acceptObject)
+{
+ NS_ENSURE_ARG_POINTER(node);
+ NS_ENSURE_ARG_POINTER(attachment);
+ NS_ENSURE_ARG_POINTER(acceptObject);
+
+ // GetEmbeddedObjectInfo will determine if we need to attach the source of the
+ // embedded object with the message. The decision is made automatically unless
+ // the attribute moz-do-not-send has been set to true or false.
+ nsresult rv = NS_OK;
+
+ // Reset this structure to null!
+ *acceptObject = false;
+
+ // We're only interested in body, image, link and anchors which are all
+ // elements.
+ nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(node);
+ if (!domElement)
+ return NS_OK;
+
+ bool isImage = false;
+ nsAutoString mozDoNotSendAttr;
+ domElement->GetAttribute(NS_LITERAL_STRING(ATTR_MOZ_DO_NOT_SEND), mozDoNotSendAttr);
+
+ // Only empty or moz-do-not-send="false" may be accepted later.
+ if (!(mozDoNotSendAttr.IsEmpty() || mozDoNotSendAttr.LowerCaseEqualsLiteral("false")))
+ return NS_OK;
+
+ // Now, we know the types of objects this node can be, so we will do
+ // our query interface here and see what we come up with
+ nsCOMPtr<nsIDOMHTMLBodyElement> body = (do_QueryInterface(node));
+ // XXX convert to use nsIImageLoadingContent?
+ nsCOMPtr<nsIDOMHTMLImageElement> image = (do_QueryInterface(node));
+ nsCOMPtr<nsIDOMHTMLLinkElement> link = (do_QueryInterface(node));
+ nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = (do_QueryInterface(node));
+
+ // First, try to see if the body as a background image
+ if (body)
+ {
+ nsAutoString tUrl;
+ if (NS_SUCCEEDED(body->GetBackground(tUrl)))
+ {
+ nsAutoCString turlC;
+ CopyUTF16toUTF8(tUrl, turlC);
+ if (NS_FAILED(nsMsgNewURL(getter_AddRefs(attachment->m_url), turlC.get())))
+ return NS_OK;
+ }
+ isImage = true;
+ }
+ else if (image) // Is this an image?
+ {
+ nsString tUrl;
+ nsString tName;
+ nsString tDesc;
+
+ // Create the URI
+ if (NS_FAILED(image->GetSrc(tUrl)))
+ return NS_ERROR_FAILURE;
+ if (tUrl.IsEmpty())
+ return NS_OK;
+
+ nsAutoCString turlC;
+ CopyUTF16toUTF8(tUrl, turlC);
+ if (NS_FAILED(nsMsgNewURL(getter_AddRefs(attachment->m_url), turlC.get())))
+ {
+ // Well, the first time failed...which means we probably didn't get
+ // the full path name...
+ //
+ nsIDOMDocument *ownerDocument = nullptr;
+ node->GetOwnerDocument(&ownerDocument);
+ if (ownerDocument)
+ {
+ nsIDocument *doc = nullptr;
+ if (NS_FAILED(ownerDocument->QueryInterface(NS_GET_IID(nsIDocument),(void**)&doc)) || !doc)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsAutoCString spec;
+ nsIURI *uri = doc->GetDocumentURI();
+
+ if (!uri)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ok, now get the path to the root doc and tack on the name we
+ // got from the GetSrc() call....
+ NS_ConvertUTF8toUTF16 workURL(spec);
+
+ int32_t loc = workURL.RFindChar('/');
+ if (loc >= 0)
+ workURL.SetLength(loc+1);
+ workURL.Append(tUrl);
+ NS_ConvertUTF16toUTF8 workurlC(workURL);
+ if (NS_FAILED(nsMsgNewURL(getter_AddRefs(attachment->m_url), workurlC.get())))
+ return NS_OK; // Continue and send it without this image.
+ }
+ }
+ isImage = true;
+
+ rv = image->GetName(tName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LossyCopyUTF16toASCII(tName, attachment->m_realName);
+ rv = image->GetLongDesc(tDesc);
+ NS_ENSURE_SUCCESS(rv, rv);
+ attachment->m_description = NS_LossyConvertUTF16toASCII(tDesc); // XXX i18n
+ }
+ else if (link) // Is this a link?
+ {
+ nsString tUrl;
+
+ // Create the URI
+ rv = link->GetHref(tUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (tUrl.IsEmpty())
+ return NS_OK;
+ nsAutoCString turlC;
+ CopyUTF16toUTF8(tUrl, turlC);
+ if (NS_FAILED(nsMsgNewURL(getter_AddRefs(attachment->m_url), turlC.get())))
+ return NS_OK;
+ }
+ else if (anchor)
+ {
+ nsString tUrl;
+ nsString tName;
+
+ // Create the URI
+ rv = anchor->GetHref(tUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (tUrl.IsEmpty())
+ return NS_OK;
+ nsAutoCString turlC;
+ CopyUTF16toUTF8(tUrl, turlC);
+ // This can fail since the URL might not be recognised, for example:
+ // <a href="skype:some-name?call" title="Skype">Some Name</a>
+ if (NS_FAILED(nsMsgNewURL(getter_AddRefs(attachment->m_url), turlC.get())))
+ return NS_OK;
+ rv = anchor->GetName(tName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LossyCopyUTF16toASCII(tName, attachment->m_realName);
+ }
+ else
+ {
+ // If we get here, we got something we didn't expect!
+ // Just try to continue and send it without this thing.
+ return NS_OK;
+ }
+
+ // Before going further, check what scheme we're dealing with. Files need to
+ // be converted to data URLs during composition. "Attaching" means
+ // sending as a cid: part instead of original URL.
+ bool isHttp =
+ (NS_SUCCEEDED(attachment->m_url->SchemeIs("http", &isHttp)) && isHttp) ||
+ (NS_SUCCEEDED(attachment->m_url->SchemeIs("https", &isHttp)) && isHttp);
+ // Attach (= create cid: part) http resources if moz-do-not-send is set to
+ // "false". Special processing for images: We attach if the preference says so.
+ // Note that moz-do-not-send="true" is already processed above so the preference
+ // doesn't override this.
+ if (isHttp)
+ {
+ *acceptObject =
+ (isImage && Preferences::GetBool("mail.compose.attach_http_images", false)) ||
+ mozDoNotSendAttr.LowerCaseEqualsLiteral("false");
+ return NS_OK;
+ }
+
+ bool isData =
+ (NS_SUCCEEDED(attachment->m_url->SchemeIs("data", &isData)) && isData);
+ bool isNews =
+ (NS_SUCCEEDED(attachment->m_url->SchemeIs("news", &isNews)) && isNews) ||
+ (NS_SUCCEEDED(attachment->m_url->SchemeIs("snews", &isNews)) && isNews) ||
+ (NS_SUCCEEDED(attachment->m_url->SchemeIs("nntp", &isNews)) && isNews);
+ // Attach (= create cid: part) data resources if moz-do-not-send is not
+ // specified or set to "false".
+ if (isData || isNews)
+ {
+ *acceptObject = mozDoNotSendAttr.IsEmpty() ||
+ mozDoNotSendAttr.LowerCaseEqualsLiteral("false");
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+
+uint32_t
+nsMsgComposeAndSend::GetMultipartRelatedCount(bool forceToBeCalculated /*=false*/)
+{
+ nsresult rv = NS_OK;
+ uint32_t count;
+
+ if (mMultipartRelatedAttachmentCount != -1 && !forceToBeCalculated)
+ return (uint32_t)mMultipartRelatedAttachmentCount;
+
+ //First time here, let's calculate the correct number of related part we need to generate
+ mMultipartRelatedAttachmentCount = 0;
+ if (mEditor)
+ {
+ nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(mEditor));
+ if (!mailEditor)
+ return 0;
+
+ rv = mailEditor->GetEmbeddedObjects(getter_AddRefs(mEmbeddedObjectList));
+ if (NS_FAILED(rv))
+ return 0;
+ }
+ if (!mEmbeddedObjectList)
+ return 0;
+
+ if (NS_SUCCEEDED(mEmbeddedObjectList->GetLength(&count)))
+ {
+ if (count > 0)
+ {
+ // preallocate space for part numbers
+ m_partNumbers.SetLength(count);
+ // Let parse the list to count the number of valid objects. BTW, we can remove the others from the list
+ RefPtr<nsMsgAttachmentData> attachment(new nsMsgAttachmentData);
+
+ int32_t i;
+ nsCOMPtr<nsIDOMNode> node;
+
+ for (i = count - 1, count = 0; i >= 0; i --)
+ {
+ // Reset this structure to null!
+
+ // now we need to get the element in the array and do the magic
+ // to process this element.
+ //
+ node = do_QueryElementAt(mEmbeddedObjectList, i, &rv);
+ bool acceptObject = false;
+ if (node)
+ {
+ rv = GetEmbeddedObjectInfo(node, attachment, &acceptObject);
+ }
+ else // outlook import case
+ {
+ nsCOMPtr<nsIMsgEmbeddedImageData> imageData =
+ do_QueryElementAt(mEmbeddedObjectList, i, &rv);
+ if (!imageData)
+ continue;
+ acceptObject = true;
+ }
+ if (NS_SUCCEEDED(rv) && acceptObject)
+ count ++;
+ }
+ }
+ mMultipartRelatedAttachmentCount = (int32_t)count;
+ return count;
+ }
+ else
+ return 0;
+}
+
+nsresult
+nsMsgComposeAndSend::GetBodyFromEditor()
+{
+ //
+ // Now we have to fix up and get the HTML from the editor. After we
+ // get the HTML data, we need to store it in the m_attachment_1_body
+ // member variable after doing the necessary charset conversion.
+ //
+
+ //
+ // Query the editor, get the body of HTML!
+ //
+ uint32_t flags = nsIDocumentEncoder::OutputFormatted |
+ nsIDocumentEncoder::OutputNoFormattingInPre |
+ nsIDocumentEncoder::OutputDisallowLineBreaking;
+ nsAutoString bodyStr;
+ char16_t* bodyText = nullptr;
+ nsresult rv;
+ char16_t *origHTMLBody = nullptr;
+
+ // Ok, get the body...the DOM should have been whacked with
+ // Content ID's already
+ if (mEditor)
+ mEditor->OutputToString(NS_LITERAL_STRING(TEXT_HTML), flags, bodyStr);
+ else
+ bodyStr = NS_ConvertASCIItoUTF16(m_attachment1_body);
+
+ // If we really didn't get a body, just return NS_OK
+ if (bodyStr.IsEmpty())
+ return NS_OK;
+ bodyText = ToNewUnicode(bodyStr);
+ if (!bodyText)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // If we are forcing this to be plain text, we should not be
+ // doing this conversion.
+ bool doConversion = true;
+
+ if ( (mCompFields) && mCompFields->GetForcePlainText() )
+ doConversion = false;
+
+ if (doConversion)
+ {
+ nsCOMPtr<mozITXTToHTMLConv> conv = do_CreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t whattodo = mozITXTToHTMLConv::kURLs;
+ bool enable_structs = false;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (pPrefBranch)
+ {
+ rv = pPrefBranch->GetBoolPref(PREF_MAIL_SEND_STRUCT, &enable_structs);
+ if (enable_structs)
+ whattodo = whattodo | mozITXTToHTMLConv::kStructPhrase;
+ }
+
+ char16_t* wresult;
+ rv = conv->ScanHTML(bodyText, whattodo, &wresult);
+ if (NS_SUCCEEDED(rv))
+ {
+ // Save the original body for possible attachment as plain text
+ // We should have what the user typed in stored in mOriginalHTMLBody
+ origHTMLBody = bodyText;
+ bodyText = wresult;
+ }
+ }
+ }
+
+ nsCString attachment1_body;
+
+ // Convert body to mail charset
+ nsCString outCString;
+ const char *aCharset = mCompFields->GetCharacterSet();
+
+ if (aCharset && *aCharset)
+ {
+ rv = nsMsgI18NConvertFromUnicode(aCharset, nsDependentString(bodyText), outCString, false, true);
+ bool isAsciiOnly = NS_IsAscii(outCString.get()) &&
+ !nsMsgI18Nstateful_charset(mCompFields->GetCharacterSet());
+ if (mCompFields->GetForceMsgEncoding())
+ isAsciiOnly = false;
+ mCompFields->SetBodyIsAsciiOnly(isAsciiOnly);
+
+ // If the body contains characters outside the current mail charset,
+ // convert to UTF-8.
+ if (NS_ERROR_UENC_NOMAPPING == rv)
+ {
+ bool needToCheckCharset;
+ mCompFields->GetNeedToCheckCharset(&needToCheckCharset);
+ if (needToCheckCharset)
+ {
+ // Just use UTF-8 and be done with it
+ // unless disable_fallback_to_utf8 is set for this charset.
+ bool disableFallback = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (prefBranch)
+ {
+ nsCString prefName("mailnews.disable_fallback_to_utf8.");
+ prefName.Append(aCharset);
+ prefBranch->GetBoolPref(prefName.get(), &disableFallback);
+ }
+ if (!disableFallback)
+ {
+ CopyUTF16toUTF8(nsDependentString(bodyText), outCString);
+ mCompFields->SetCharacterSet("UTF-8");
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv))
+ attachment1_body = outCString;
+
+ // If we have an origHTMLBody that is not null, this means that it is
+ // different than the bodyText because of formatting conversions. Because of
+ // this we need to do the charset conversion on this part separately
+ if (origHTMLBody)
+ {
+ nsCString newBody;
+ rv = nsMsgI18NConvertFromUnicode(aCharset,
+ nsDependentString(origHTMLBody), newBody, false, true);
+ if (NS_SUCCEEDED(rv))
+ {
+ mOriginalHTMLBody = ToNewCString(newBody);
+ }
+ }
+ else {
+ mOriginalHTMLBody = ToNewCString(attachment1_body);
+ }
+
+ NS_Free(bodyText); //Don't need it anymore
+ }
+ else
+ return NS_ERROR_FAILURE;
+
+ rv = SnarfAndCopyBody(attachment1_body, TEXT_HTML);
+
+ return rv;
+}
+
+//
+// This is the routine that does the magic of generating the body and the
+// attachments for the multipart/related email message.
+//
+typedef struct
+{
+ nsIDOMNode *node;
+ char *url;
+} domSaveStruct;
+
+nsresult
+nsMsgComposeAndSend::ProcessMultipartRelated(int32_t *aMailboxCount, int32_t *aNewsCount)
+{
+ uint32_t multipartCount = GetMultipartRelatedCount();
+ nsresult rv = NS_OK;
+ uint32_t i;
+ int32_t j = -1;
+ uint32_t k;
+ int32_t duplicateOf;
+ domSaveStruct *domSaveArray = nullptr;
+
+ if (!mEmbeddedObjectList)
+ return NS_ERROR_MIME_MPART_ATTACHMENT_ERROR;
+
+ RefPtr<nsMsgAttachmentData> attachment(new nsMsgAttachmentData);
+ int32_t locCount = -1;
+
+ if (multipartCount > 0)
+ {
+ domSaveArray = (domSaveStruct *)PR_MALLOC(sizeof(domSaveStruct) * multipartCount);
+ if (!domSaveArray)
+ return NS_ERROR_MIME_MPART_ATTACHMENT_ERROR;
+ memset(domSaveArray, 0, sizeof(domSaveStruct) * multipartCount);
+ }
+
+ nsCOMPtr<nsIDOMNode> node;
+ for (i = mPreloadedAttachmentCount; i < (mPreloadedAttachmentCount + multipartCount);)
+ {
+ // Ok, now we need to get the element in the array and do the magic
+ // to process this element.
+ //
+
+ locCount++;
+ mEmbeddedObjectList->QueryElementAt(locCount, NS_GET_IID(nsIDOMNode), getter_AddRefs(node));
+ if (node)
+ {
+ bool acceptObject = false;
+ rv = GetEmbeddedObjectInfo(node, attachment, &acceptObject);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MIME_MPART_ATTACHMENT_ERROR);
+ if (!acceptObject)
+ continue;
+ nsString nodeValue;
+ node->GetNodeValue(nodeValue);
+ LossyCopyUTF16toASCII(nodeValue, m_attachments[i]->m_contentId);
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgEmbeddedImageData> imageData = do_QueryElementAt(mEmbeddedObjectList, locCount, &rv);
+ if (!imageData)
+ return NS_ERROR_MIME_MPART_ATTACHMENT_ERROR;
+ imageData->GetUri(getter_AddRefs(attachment->m_url));
+ if (!attachment->m_url)
+ return NS_ERROR_MIME_MPART_ATTACHMENT_ERROR;
+ imageData->GetCid(m_attachments[i]->m_contentId);
+ imageData->GetName(attachment->m_realName);
+ }
+
+
+ // MUST set this to get placed in the correct part of the message
+ m_attachments[i]->mMHTMLPart = true;
+
+ m_attachments[i]->mDeleteFile = true;
+ m_attachments[i]->m_done = false;
+ m_attachments[i]->SetMimeDeliveryState(this);
+ m_attachments[i]->mNodeIndex = locCount;
+
+ j++;
+ domSaveArray[j].node = node;
+
+ // check if we have alreay attached this object, don't need to attach it twice
+ duplicateOf = -1;
+ for (k = mPreloadedAttachmentCount; k < i; k++)
+ {
+ bool isEqual = false;
+ NS_ASSERTION(attachment->m_url, "null attachment url!");
+ if (attachment->m_url)
+ (void)attachment->m_url->Equals(m_attachments[k]->mURL, &isEqual);
+ if (isEqual)
+ {
+ duplicateOf = k;
+ break;
+ }
+ }
+
+ if (duplicateOf == -1)
+ {
+ //
+ // Now we have to get all of the interesting information from
+ // the nsIDOMNode we have in hand...
+ m_attachments[i]->mURL = attachment->m_url;
+
+ m_attachments[i]->m_overrideType = attachment->m_realType;
+ m_attachments[i]->m_overrideEncoding = attachment->m_realEncoding;
+ m_attachments[i]->m_desiredType = attachment->m_desiredType;
+ m_attachments[i]->m_description = attachment->m_description;
+ m_attachments[i]->m_realName = attachment->m_realName;
+ m_attachments[i]->m_xMacType = attachment->m_xMacType;
+ m_attachments[i]->m_xMacCreator = attachment->m_xMacCreator;
+
+ m_attachments[i]->m_charset = mCompFields->GetCharacterSet();
+ m_attachments[i]->m_encoding = ENCODING_7BIT;
+
+ if (m_attachments[i]->mURL)
+ msg_pick_real_name(m_attachments[i], nullptr, mCompFields->GetCharacterSet());
+
+ if (m_attachments[i]->m_contentId.IsEmpty())
+ {
+ //
+ // Next, generate a content id for use with this part
+ //
+ nsCString email;
+ mUserIdentity->GetEmail(email);
+ m_attachments[i]->m_contentId = mime_gen_content_id(locCount+1, email.get());
+ }
+
+ //
+ // Start counting the attachments which are going to come from mail folders
+ // and from NNTP servers.
+ //
+ if (m_attachments[i]->mURL)
+ {
+ nsIURI *uri = m_attachments[i]->mURL;
+ bool match = false;
+ if ((NS_SUCCEEDED(uri->SchemeIs("mailbox", &match)) && match) ||
+ (NS_SUCCEEDED(uri->SchemeIs("imap", &match)) && match))
+ (*aMailboxCount)++;
+ else if ((NS_SUCCEEDED(uri->SchemeIs("news", &match)) && match) ||
+ (NS_SUCCEEDED(uri->SchemeIs("snews", &match)) && match))
+ (*aNewsCount)++;
+ else
+ {
+ // Additional account types need a mechanism to report that they are
+ // message protocols. If there is an nsIMsgProtocolInfo component
+ // registered for this scheme, we'll consider it a mailbox
+ // attachment.
+ nsAutoCString contractID;
+ contractID.Assign(
+ NS_LITERAL_CSTRING("@mozilla.org/messenger/protocol/info;1"));
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ contractID.Append(scheme);
+ nsCOMPtr<nsIMsgProtocolInfo> msgProtocolInfo =
+ do_CreateInstance(contractID.get());
+ if (msgProtocolInfo)
+ (*aMailboxCount)++;
+ }
+
+ }
+ }
+ else
+ {
+ m_attachments[i]->m_contentId = m_attachments[duplicateOf]->m_contentId;
+ m_attachments[i]->SetMimeDeliveryState(nullptr);
+ }
+
+ //
+ // Ok, while we are here, we should whack the DOM with the generated
+ // Content-ID for this object. This will be necessary for generating
+ // the HTML we need.
+ //
+ nsString domURL;
+ if (!m_attachments[duplicateOf == -1 ? i : duplicateOf]->m_contentId.IsEmpty())
+ {
+ nsString newSpec(NS_LITERAL_STRING("cid:"));
+ newSpec.AppendASCII(m_attachments[duplicateOf == -1 ? i : duplicateOf]->m_contentId.get());
+
+ // Now, we know the types of objects this node can be, so we will do
+ // our query interface here and see what we come up with
+ nsCOMPtr<nsIDOMHTMLBodyElement> body = (do_QueryInterface(domSaveArray[j].node));
+ nsCOMPtr<nsIDOMHTMLImageElement> image = (do_QueryInterface(domSaveArray[j].node));
+ nsCOMPtr<nsIDOMHTMLLinkElement> link = (do_QueryInterface(domSaveArray[j].node));
+ nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = (do_QueryInterface(domSaveArray[j].node));
+
+ if (anchor)
+ {
+ anchor->GetHref(domURL);
+ anchor->SetHref(newSpec);
+ }
+ else if (link)
+ {
+ link->GetHref(domURL);
+ link->SetHref(newSpec);
+ }
+ else if (image)
+ {
+ image->GetSrc(domURL);
+ image->SetSrc(newSpec);
+ }
+ else if (body)
+ {
+ body->GetBackground(domURL);
+ body->SetBackground(newSpec);
+ }
+
+ if (!domURL.IsEmpty())
+ domSaveArray[j].url = ToNewCString(NS_LossyConvertUTF16toASCII(domURL));
+ }
+ i++;
+ }
+
+ rv = GetBodyFromEditor();
+
+ //
+ // Ok, now we need to un-whack the DOM or we have a screwed up document on
+ // Send failure.
+ //
+ for (i = 0; i < multipartCount; i++)
+ {
+ if ( (!domSaveArray[i].node) || (!domSaveArray[i].url) )
+ continue;
+
+ // Now, we know the types of objects this node can be, so we will do
+ // our query interface here and see what we come up with
+ nsCOMPtr<nsIDOMHTMLBodyElement> body = (do_QueryInterface(domSaveArray[i].node));
+ nsCOMPtr<nsIDOMHTMLImageElement> image = (do_QueryInterface(domSaveArray[i].node));
+ nsCOMPtr<nsIDOMHTMLLinkElement> link = (do_QueryInterface(domSaveArray[i].node));
+ nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = (do_QueryInterface(domSaveArray[i].node));
+
+ // STRING USE WARNING: hoisting the following conversion might save code-space, since it happens along every path
+
+ if (anchor)
+ anchor->SetHref(NS_ConvertASCIItoUTF16(domSaveArray[i].url));
+ else if (link)
+ link->SetHref(NS_ConvertASCIItoUTF16(domSaveArray[i].url));
+ else if (image)
+ image->SetSrc(NS_ConvertASCIItoUTF16(domSaveArray[i].url));
+ else if (body)
+ body->SetBackground(NS_ConvertASCIItoUTF16(domSaveArray[i].url));
+
+ free(domSaveArray[i].url);
+ }
+
+ PR_FREEIF(domSaveArray);
+
+ //
+ // Now, we have to create that first child node for the multipart
+ // message that holds the body as well as the attachment handler
+ // for this body part.
+ //
+ // If we ONLY have multipart objects, then we don't need the container
+ // for the multipart section...
+ //
+ m_related_part = new nsMsgSendPart(this);
+ if (!m_related_part)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ m_related_part->SetMimeDeliveryState(this);
+ m_related_part->SetType(MULTIPART_RELATED);
+ // We are now going to use the m_related_part as a way to store the
+ // MHTML message for this email.
+ //
+ m_related_body_part = new nsMsgSendPart(this);
+ if (!m_related_body_part)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Set the body contents...
+ m_related_body_part->SetBuffer(m_attachment1_body);
+ m_related_body_part->SetType(m_attachment1_type);
+
+ m_related_part->AddChild(m_related_body_part);
+
+ return rv;
+}
+
+nsresult
+nsMsgComposeAndSend::CountCompFieldAttachments()
+{
+ //Reset the counters
+ mCompFieldLocalAttachments = 0;
+ mCompFieldRemoteAttachments = 0;
+
+ //Get the attachments array
+ nsCOMPtr<nsISimpleEnumerator> attachments;
+ mCompFields->GetAttachments(getter_AddRefs(attachments));
+ if (!attachments)
+ return NS_OK;
+
+ nsresult rv;
+
+ // Parse the attachments array
+ bool moreAttachments;
+ nsCString url;
+ nsCOMPtr<nsISupports> element;
+ while (NS_SUCCEEDED(attachments->HasMoreElements(&moreAttachments)) && moreAttachments) {
+ rv = attachments->GetNext(getter_AddRefs(element));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAttachment> attachment = do_QueryInterface(element, &rv);
+ if (NS_SUCCEEDED(rv) && attachment)
+ {
+ attachment->GetUrl(url);
+ if (!url.IsEmpty())
+ {
+ // Check to see if this is a file URL, if so, don't retrieve
+ // like a remote URL...
+ if (nsMsgIsLocalFile(url.get()))
+ mCompFieldLocalAttachments++;
+ else // This is a remote URL...
+ mCompFieldRemoteAttachments++;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//
+// Since we are at the head of the list, we start from ZERO.
+//
+nsresult
+nsMsgComposeAndSend::AddCompFieldLocalAttachments()
+{
+ // If none, just return...
+ if (mCompFieldLocalAttachments <= 0)
+ return NS_OK;
+
+ //Get the attachments array
+ nsCOMPtr<nsISimpleEnumerator> attachments;
+ mCompFields->GetAttachments(getter_AddRefs(attachments));
+ if (!attachments)
+ return NS_OK;
+
+ uint32_t newLoc = 0;
+ nsresult rv;
+ nsCString url;
+
+ //Parse the attachments array
+ bool moreAttachments;
+ nsCOMPtr<nsISupports> element;
+ while (NS_SUCCEEDED(attachments->HasMoreElements(&moreAttachments)) && moreAttachments) {
+ rv = attachments->GetNext(getter_AddRefs(element));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAttachment> attachment = do_QueryInterface(element, &rv);
+ if (NS_SUCCEEDED(rv) && attachment)
+ {
+ bool sendViaCloud = false;
+ attachment->GetSendViaCloud(&sendViaCloud);
+ m_attachments[newLoc]->mSendViaCloud = sendViaCloud;
+ attachment->GetUrl(url);
+ if (!url.IsEmpty())
+ {
+ bool sendViaCloud;
+ attachment->GetSendViaCloud(&sendViaCloud);
+ if (sendViaCloud)
+ {
+ nsCString cloudProviderKey;
+ // We'd like to output a part for the attachment, just an html part
+ // with information about how to download the attachment.
+ // m_attachments[newLoc]->m_done = true;
+ attachment->GetHtmlAnnotation(m_attachments[newLoc]->mHtmlAnnotation);
+ m_attachments[newLoc]->m_type.AssignLiteral("text/html");
+ attachment->GetCloudProviderKey(m_attachments[newLoc]->mCloudProviderKey);
+ attachment->GetContentLocation(m_attachments[newLoc]->mCloudUrl);
+ }
+ // Just look for local file:// attachments and do the right thing.
+ if (nsMsgIsLocalFile(url.get()))
+ {
+ //
+ // Now we have to setup the m_attachments entry for the file://
+ // URL that is passed in...
+ //
+ m_attachments[newLoc]->mDeleteFile = false;
+
+ nsMsgNewURL(getter_AddRefs(m_attachments[newLoc]->mURL), url.get());
+
+ if (m_attachments[newLoc]->mTmpFile)
+ {
+ if (m_attachments[newLoc]->mDeleteFile)
+ m_attachments[newLoc]->mTmpFile->Remove(false);
+ m_attachments[newLoc]->mTmpFile =nullptr;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr <nsIURI> uri;
+ rv = ioService->NewURI(url, nullptr, nullptr, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFileURL> fileURL = do_QueryInterface(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFile> fileURLFile;
+ fileURL->GetFile(getter_AddRefs(fileURLFile));
+ m_attachments[newLoc]->mTmpFile = do_QueryInterface(fileURLFile);
+ m_attachments[newLoc]->mDeleteFile = false;
+ if (m_attachments[newLoc]->mURL)
+ {
+ nsAutoString proposedName;
+ attachment->GetName(proposedName);
+ msg_pick_real_name(m_attachments[newLoc], proposedName.get(), mCompFields->GetCharacterSet());
+ }
+
+ // Now, most importantly, we need to figure out what the content type is for
+ // this attachment...If we can't, then just make it application/octet-stream
+
+ #ifdef MAC_OSX
+ //Mac always need to snarf the file to figure out how to send it, maybe we need to use apple double...
+ // unless caller has already set the content type, in which case, trust them.
+ bool mustSnarfAttachment = true;
+ #else
+ bool mustSnarfAttachment = false;
+ #endif
+ if (sendViaCloud)
+ mustSnarfAttachment = false;
+
+ attachment->GetContentType(getter_Copies(m_attachments[newLoc]->m_type));
+ if (m_attachments[newLoc]->m_type.IsEmpty())
+ {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && mimeFinder)
+ {
+ nsCOMPtr<nsIURL> fileUrl(do_CreateInstance(NS_STANDARDURL_CONTRACTID));
+ if (fileUrl)
+ {
+ nsAutoCString fileExt;
+ //First try using the real file name
+ rv = fileUrl->SetFileName(m_attachments[newLoc]->m_realName);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = fileUrl->GetFileExtension(fileExt);
+ if (NS_SUCCEEDED(rv) && !fileExt.IsEmpty()) {
+ nsAutoCString type;
+ mimeFinder->GetTypeFromExtension(fileExt, type);
+ #ifndef XP_MACOSX
+ if (!type.Equals("multipart/appledouble")) // can't do apple double on non-macs
+ #endif
+ m_attachments[newLoc]->m_type = type;
+ }
+ }
+
+ //Then try using the url if we still haven't figured out the content type
+ if (m_attachments[newLoc]->m_type.IsEmpty())
+ {
+ rv = fileUrl->SetSpec(url);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = fileUrl->GetFileExtension(fileExt);
+ if (NS_SUCCEEDED(rv) && !fileExt.IsEmpty()) {
+ nsAutoCString type;
+ mimeFinder->GetTypeFromExtension(fileExt, type);
+ #ifndef XP_MACOSX
+ if (!type.Equals("multipart/appledouble")) // can't do apple double on non-macs
+ #endif
+ m_attachments[newLoc]->m_type = type;
+ // rtf and vcs files may look like text to sniffers,
+ // but they're not human readable.
+ if (type.IsEmpty() && !fileExt.IsEmpty() &&
+ (MsgLowerCaseEqualsLiteral(fileExt, "rtf") ||
+ MsgLowerCaseEqualsLiteral(fileExt, "vcs")))
+ m_attachments[newLoc]->m_type = APPLICATION_OCTET_STREAM;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ attachment->GetContentTypeParam(getter_Copies(m_attachments[newLoc]->m_typeParam));
+ mustSnarfAttachment = false;
+ }
+
+ //We need to snarf the file to figure out how to send it only if we don't have a content type...
+ if (mustSnarfAttachment || m_attachments[newLoc]->m_type.IsEmpty())
+ {
+ m_attachments[newLoc]->m_done = false;
+ m_attachments[newLoc]->SetMimeDeliveryState(this);
+ }
+ else
+ {
+ m_attachments[newLoc]->m_done = true;
+ m_attachments[newLoc]->SetMimeDeliveryState(nullptr);
+ }
+ // For local files, if they are HTML docs and we don't have a charset, we should
+ // sniff the file and see if we can figure it out.
+ if (!m_attachments[newLoc]->m_type.IsEmpty())
+ {
+ if (m_attachments[newLoc]->m_type.LowerCaseEqualsLiteral(TEXT_HTML))
+ {
+ char *tmpCharset = (char *)nsMsgI18NParseMetaCharset(m_attachments[newLoc]->mTmpFile);
+ if (tmpCharset[0] != '\0')
+ m_attachments[newLoc]->m_charset = tmpCharset;
+ }
+ }
+
+ attachment->GetMacType(getter_Copies(m_attachments[newLoc]->m_xMacType));
+ attachment->GetMacCreator(getter_Copies(m_attachments[newLoc]->m_xMacCreator));
+
+ ++newLoc;
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeAndSend::AddCompFieldRemoteAttachments(uint32_t aStartLocation,
+ int32_t *aMailboxCount,
+ int32_t *aNewsCount)
+{
+ // If none, just return...
+ if (mCompFieldRemoteAttachments <= 0)
+ return NS_OK;
+
+ //Get the attachments array
+ nsCOMPtr<nsISimpleEnumerator> attachments;
+ mCompFields->GetAttachments(getter_AddRefs(attachments));
+ if (!attachments)
+ return NS_OK;
+
+ uint32_t newLoc = aStartLocation;
+
+ nsresult rv;
+ bool moreAttachments;
+ nsCString url;
+ nsCOMPtr<nsISupports> element;
+ while (NS_SUCCEEDED(attachments->HasMoreElements(&moreAttachments)) && moreAttachments) {
+ rv = attachments->GetNext(getter_AddRefs(element));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAttachment> attachment = do_QueryInterface(element, &rv);
+ if (NS_SUCCEEDED(rv) && attachment)
+ {
+ attachment->GetUrl(url);
+ if (!url.IsEmpty())
+ {
+ // Just look for files that are NOT local file attachments and do
+ // the right thing.
+ if (! nsMsgIsLocalFile(url.get()))
+ {
+ // Check for message attachment, see nsMsgMailNewsUrl::GetIsMessageUri.
+ nsCOMPtr<nsIURI> nsiuri = do_CreateInstance(NS_STANDARDURL_CONTRACTID);
+ NS_ENSURE_STATE(nsiuri);
+ nsiuri->SetSpec(url);
+ nsAutoCString scheme;
+ nsiuri->GetScheme(scheme);
+ bool isAMessageAttachment =
+ StringEndsWith(scheme, NS_LITERAL_CSTRING("-message"));
+
+ m_attachments[newLoc]->mDeleteFile = true;
+ m_attachments[newLoc]->m_done = false;
+ m_attachments[newLoc]->SetMimeDeliveryState(this);
+
+ if (!isAMessageAttachment)
+ nsMsgNewURL(getter_AddRefs(m_attachments[newLoc]->mURL), url.get());
+
+ m_attachments[newLoc]->m_encoding = ENCODING_7BIT;
+
+ attachment->GetMacType(getter_Copies(m_attachments[newLoc]->m_xMacType));
+ attachment->GetMacCreator(getter_Copies(m_attachments[newLoc]->m_xMacCreator));
+
+ /* Count up attachments which are going to come from mail folders
+ and from NNTP servers. */
+ bool do_add_attachment = false;
+ if (isAMessageAttachment)
+ {
+ do_add_attachment = true;
+ if (!PL_strncasecmp(url.get(), "news-message://", 15))
+ (*aNewsCount)++;
+ else
+ (*aMailboxCount)++;
+
+ m_attachments[newLoc]->m_uri = url;
+ m_attachments[newLoc]->mURL = nullptr;
+ }
+ else
+ do_add_attachment = (nullptr != m_attachments[newLoc]->mURL);
+ m_attachments[newLoc]->mSendViaCloud = false;
+ if (do_add_attachment)
+ {
+ nsAutoString proposedName;
+ attachment->GetName(proposedName);
+ msg_pick_real_name(m_attachments[newLoc], proposedName.get(), mCompFields->GetCharacterSet());
+ ++newLoc;
+ }
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeAndSend::HackAttachments(nsIArray *attachments,
+ nsIArray *preloadedAttachments)
+{
+ //
+ // First, count the total number of attachments we are going to process
+ // for this operation! This is a little more complicated than you might
+ // think because we have a few ways to specify attachments. Via the nsMsgAttachmentData
+ // as well as the composition fields.
+ //
+ CountCompFieldAttachments();
+
+ // Count the preloaded attachments!
+ mPreloadedAttachmentCount = 0;
+
+ // For now, manually add the local attachments in the comp field!
+ mPreloadedAttachmentCount += mCompFieldLocalAttachments;
+ uint32_t numAttachments = 0, numPreloadedAttachments = 0;
+ if (attachments)
+ attachments->GetLength(&numAttachments);
+ if (preloadedAttachments)
+ preloadedAttachments->GetLength(&numPreloadedAttachments);
+ mPreloadedAttachmentCount += numPreloadedAttachments;
+
+ // Count the attachments we have to go retrieve! Keep in mind, that these
+ // will be APPENDED to the current list of URL's that we have gathered if
+ // this is a multpart/related send operation
+ mRemoteAttachmentCount = GetMultipartRelatedCount();
+
+ // For now, manually add the remote attachments in the comp field!
+ mRemoteAttachmentCount += mCompFieldRemoteAttachments;
+
+ mRemoteAttachmentCount += numAttachments;
+
+ m_attachment_count = mPreloadedAttachmentCount + mRemoteAttachmentCount;
+
+ uint32_t i; // counter for location in attachment array...
+ // Now create the array of attachment handlers...
+ for (i = 0; i < m_attachment_count; i++) {
+ RefPtr<nsMsgAttachmentHandler> handler = new nsMsgAttachmentHandler;
+ m_attachments.AppendElement(handler);
+ }
+
+ //
+ // First, we need to attach the files that are defined in the comp fields...
+ if (NS_FAILED(AddCompFieldLocalAttachments()))
+ return NS_ERROR_INVALID_ARG;
+
+ // Now handle the preloaded attachments...
+ if (numPreloadedAttachments > 0)
+ {
+ // These are attachments which have already been downloaded to tmp files.
+ // We merely need to point the internal attachment data at those tmp
+ // files.
+ m_pre_snarfed_attachments_p = true;
+
+ for (i = mCompFieldLocalAttachments; i < mPreloadedAttachmentCount; i++)
+ {
+ nsCOMPtr<nsIMsgAttachedFile> attachedFile = do_QueryElementAt(preloadedAttachments, i);
+ if (!attachedFile)
+ continue;
+
+ /* These attachments are already "snarfed". */
+ m_attachments[i]->mDeleteFile = false;
+ m_attachments[i]->SetMimeDeliveryState(nullptr);
+ m_attachments[i]->m_done = true;
+
+ attachedFile->GetOrigUrl(getter_AddRefs(m_attachments[i]->mURL));
+
+ attachedFile->GetType(m_attachments[i]->m_type);
+
+ // Set it to the compose fields for a default...
+ m_attachments[i]->m_charset = mCompFields->GetCharacterSet();
+
+ // If we still don't have a content type, we should really try sniff one out!
+ if (m_attachments[i]->m_type.IsEmpty())
+ m_attachments[i]->PickEncoding(mCompFields->GetCharacterSet(), this);
+
+ // For local files, if they are HTML docs and we don't have a charset, we should
+ // sniff the file and see if we can figure it out.
+ if (!m_attachments[i]->m_type.IsEmpty())
+ {
+ nsCOMPtr<nsIFile> tmpFile;
+ attachedFile->GetTmpFile(getter_AddRefs(tmpFile));
+ if (m_attachments[i]->m_type.LowerCaseEqualsLiteral(TEXT_HTML) && tmpFile)
+ {
+ char *tmpCharset = (char *)nsMsgI18NParseMetaCharset(tmpFile);
+ if (tmpCharset[0] != '\0')
+ m_attachments[i]->m_charset = tmpCharset;
+ }
+ }
+
+ attachedFile->GetDescription(m_attachments[i]->m_description);
+ attachedFile->GetRealName(m_attachments[i]->m_realName);
+ attachedFile->GetXMacType(m_attachments[i]->m_xMacType);
+ attachedFile->GetXMacCreator(m_attachments[i]->m_xMacCreator);
+ attachedFile->GetEncoding(m_attachments[i]->m_encoding);
+
+ if (m_attachments[i]->mTmpFile)
+ {
+ if (m_attachments[i]->mDeleteFile)
+ m_attachments[i]->mTmpFile->Remove(false);
+ m_attachments[i]->mTmpFile = nullptr;
+ }
+ attachedFile->GetTmpFile(getter_AddRefs(m_attachments[i]->mTmpFile));
+
+ attachedFile->GetSize(&m_attachments[i]->m_size);
+ attachedFile->GetUnprintableCount(&m_attachments[i]->m_unprintable_count);
+ attachedFile->GetHighbitCount(&m_attachments[i]->m_highbit_count);
+ attachedFile->GetCtlCount(&m_attachments[i]->m_ctl_count);
+ attachedFile->GetNullCount(&m_attachments[i]->m_null_count);
+ attachedFile->GetMaxLineLength(&m_attachments[i]->m_max_column);
+
+ /* If the attachment has an encoding, and it's not one of
+ the "null" encodings, then keep it. */
+ if (!m_attachments[i]->m_encoding.IsEmpty() &&
+ !m_attachments[i]->m_encoding.LowerCaseEqualsLiteral(ENCODING_7BIT) &&
+ !m_attachments[i]->m_encoding.LowerCaseEqualsLiteral(ENCODING_8BIT) &&
+ !m_attachments[i]->m_encoding.LowerCaseEqualsLiteral(ENCODING_BINARY))
+ m_attachments[i]->m_already_encoded_p = true;
+
+ if (m_attachments[i]->mURL)
+ msg_pick_real_name(m_attachments[i], nullptr, mCompFields->GetCharacterSet());
+ }
+ }
+
+ // First, handle the multipart related attachments if any...
+ //
+ int32_t mailbox_count = 0, news_count = 0;
+ int32_t multipartRelatedCount = GetMultipartRelatedCount();
+
+ if (multipartRelatedCount > 0)
+ {
+ nsresult rv = ProcessMultipartRelated(&mailbox_count, &news_count);
+ if (NS_FAILED(rv))
+ {
+ // The destructor will take care of the m_attachment array
+ return rv;
+ }
+ }
+
+ //
+ // Now add the comp field remote attachments...
+ //
+ if (NS_FAILED( AddCompFieldRemoteAttachments( (mPreloadedAttachmentCount + multipartRelatedCount),
+ &mailbox_count, &news_count) ))
+ return NS_ERROR_INVALID_ARG;
+
+ //
+ // Now deal remote attachments and attach multipart/related attachments (url's and such..)
+ // first!
+ //
+ if (attachments)
+ {
+ int32_t locCount = -1;
+
+ for (i = (mPreloadedAttachmentCount + GetMultipartRelatedCount() + mCompFieldRemoteAttachments); i < m_attachment_count; i++)
+ {
+ locCount++;
+ nsCOMPtr<nsIMsgAttachmentData> attachment(do_QueryElementAt(attachments, i));
+ if (!attachment)
+ continue;
+ m_attachments[i]->mDeleteFile = true;
+ m_attachments[i]->m_done = false;
+ m_attachments[i]->SetMimeDeliveryState(this);
+
+ attachment->GetUrl(getter_AddRefs(m_attachments[i]->mURL));
+
+ attachment->GetRealType(m_attachments[i]->m_overrideType);
+ m_attachments[i]->m_charset = mCompFields->GetCharacterSet();
+ attachment->GetRealEncoding(m_attachments[i]->m_overrideEncoding);
+ attachment->GetDesiredType(m_attachments[i]->m_desiredType);
+ attachment->GetDescription(m_attachments[i]->m_description);
+ attachment->GetRealName(m_attachments[i]->m_realName);
+ attachment->GetXMacType(m_attachments[i]->m_xMacType);
+ attachment->GetXMacCreator(m_attachments[i]->m_xMacCreator);
+ m_attachments[i]->m_encoding = ENCODING_7BIT;
+
+ // real name is set in the case of vcard so don't change it. XXX STILL NEEDED?
+ // m_attachments[i]->m_real_name = 0;
+
+ /* Count up attachments which are going to come from mail folders
+ and from NNTP servers. */
+ if (m_attachments[i]->mURL)
+ {
+ nsIURI *uri = m_attachments[i]->mURL;
+ bool match = false;
+ if ((NS_SUCCEEDED(uri->SchemeIs("mailbox", &match)) && match) ||
+ (NS_SUCCEEDED(uri->SchemeIs("imap", &match)) && match))
+ mailbox_count++;
+ else if ((NS_SUCCEEDED(uri->SchemeIs("news", &match)) && match) ||
+ (NS_SUCCEEDED(uri->SchemeIs("snews", &match)) && match))
+ news_count++;
+ else
+ {
+ // Additional account types need a mechanism to report that they are
+ // message protocols. If there is an nsIMsgProtocolInfo component
+ // registered for this scheme, we'll consider it a mailbox
+ // attachment.
+ nsAutoCString contractID;
+ contractID.Assign(
+ NS_LITERAL_CSTRING("@mozilla.org/messenger/protocol/info;1"));
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ contractID.Append(scheme);
+ nsCOMPtr<nsIMsgProtocolInfo> msgProtocolInfo =
+ do_CreateInstance(contractID.get());
+ if (msgProtocolInfo)
+ mailbox_count++;
+ }
+ if (uri)
+ msg_pick_real_name(m_attachments[i], nullptr, mCompFields->GetCharacterSet());
+ }
+ }
+ }
+
+ bool needToCallGatherMimeAttachments = true;
+
+ if (m_attachment_count > 0)
+ {
+ // If there is more than one mailbox URL, or more than one NNTP url,
+ // do the load in serial rather than parallel, for efficiency.
+ if (mailbox_count > 1 || news_count > 1)
+ m_be_synchronous_p = true;
+
+ m_attachment_pending_count = m_attachment_count;
+
+ // Start the URL attachments loading (eventually, an exit routine will
+ // call the done_callback).
+
+ for (i = 0; i < m_attachment_count; i++)
+ {
+ if (m_attachments[i]->m_done || m_attachments[i]->mSendViaCloud)
+ {
+ m_attachment_pending_count--;
+ continue;
+ }
+
+ //
+ // IF we get here and the URL is NULL, just dec the pending count and move on!!!
+ //
+ if ( (!m_attachments[i]->mURL) && (!m_attachments[i]->m_uri.Length()) )
+ {
+ m_attachments[i]->m_bogus_attachment = true;
+ m_attachments[i]->m_done = true;
+ m_attachments[i]->SetMimeDeliveryState(nullptr);
+ m_attachment_pending_count--;
+ continue;
+ }
+
+ //
+ // This only returns a failure code if NET_GetURL was not called
+ // (and thus no exit routine was or will be called.)
+ //
+
+ // Display some feedback to user...
+ nsString msg;
+ nsAutoString attachmentFileName;
+ NS_ConvertUTF8toUTF16 params(m_attachments[i]->m_realName);
+ const char16_t *formatParams[1];
+ if (!params.IsEmpty()) {
+ formatParams[0] = params.get();
+ } else if (m_attachments[i]->mURL) {
+ nsCString asciiSpec;
+ m_attachments[i]->mURL->GetAsciiSpec(asciiSpec);
+ attachmentFileName.AssignASCII(asciiSpec.get());
+ formatParams[0] = attachmentFileName.get();
+ }
+ mComposeBundle->FormatStringFromName(u"gatheringAttachment",
+ formatParams, 1, getter_Copies(msg));
+
+ if (!msg.IsEmpty())
+ {
+ SetStatusMessage(msg);
+ }
+
+ /* As SnarfAttachment will call GatherMimeAttachments when it will be done (this is an async process),
+ we need to avoid to call it ourself.
+ */
+ needToCallGatherMimeAttachments = false;
+
+ nsresult status = m_attachments[i]->SnarfAttachment(mCompFields);
+ if (NS_FAILED(status))
+ {
+ nsString errorMsg;
+ nsresult rv = ConvertToUnicode(nsMsgI18NFileSystemCharset(), m_attachments[i]->m_realName, attachmentFileName);
+ if (attachmentFileName.IsEmpty() && m_attachments[i]->mURL) {
+ nsCString asciiSpec;
+ m_attachments[i]->mURL->GetAsciiSpec(asciiSpec);
+ attachmentFileName.AssignASCII(asciiSpec.get());
+ rv = NS_OK;
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIStringBundle> bundle;
+ const char16_t *params[] = { attachmentFileName.get() };
+ mComposeBundle->FormatStringFromName(u"errorAttachingFile",
+ params, 1,
+ getter_Copies(errorMsg));
+ mSendReport->SetMessage(nsIMsgSendReport::process_Current, errorMsg.get(), false);
+ mSendReport->SetError(nsIMsgSendReport::process_Current,
+ NS_MSG_ERROR_ATTACHING_FILE,
+ false);
+ }
+ return NS_MSG_ERROR_ATTACHING_FILE;
+ }
+ if (m_be_synchronous_p)
+ break;
+ }
+ }
+
+ // If no attachments - finish now (this will call the done_callback).
+ if (needToCallGatherMimeAttachments)
+ return GatherMimeAttachments();
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeAndSend::InitCompositionFields(nsMsgCompFields *fields,
+ const nsACString &aOriginalMsgURI,
+ MSG_ComposeType aType)
+{
+ nsresult rv = NS_OK;
+ const char *pStr = nullptr;
+
+ mCompFields = new nsMsgCompFields();
+ if (!mCompFields)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ const char *cset = fields->GetCharacterSet();
+ // Make sure charset is sane...
+ if (!cset || !*cset)
+ {
+ mCompFields->SetCharacterSet("UTF-8");
+ }
+ else
+ {
+ mCompFields->SetCharacterSet(fields->GetCharacterSet());
+ }
+
+ // Now, we will look for a URI defined as the default FCC pref. If this is set,
+ // then SetFcc will use this value. The FCC field is a URI for the server that
+ // will hold the "Sent" folder...the
+ //
+ // First, look at what was passed in via the "fields" structure...if that was
+ // set then use it, otherwise, fall back to what is set in the prefs...
+ //
+ // But even before that, pay attention to the new OVERRIDE pref that will cancel
+ // any and all copy operations!
+ //
+ bool doFcc = true;
+ rv = mUserIdentity->GetDoFcc(&doFcc);
+ if (!doFcc)
+ {
+ // If the identity pref "fcc" is set to false, then we will not do
+ // any FCC operation!
+ mCompFields->SetFcc("");
+ }
+ else
+ {
+ bool useDefaultFCC = true;
+ const char *fieldsFCC = fields->GetFcc();
+ if (fieldsFCC && *fieldsFCC)
+ {
+ if (PL_strcasecmp(fieldsFCC, "nocopy://") == 0)
+ {
+ useDefaultFCC = false;
+ mCompFields->SetFcc("");
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ GetExistingFolder(nsDependentCString(fieldsFCC), getter_AddRefs(folder));
+ if (folder)
+ {
+ useDefaultFCC = false;
+ mCompFields->SetFcc(mime_fix_header(fieldsFCC));
+ }
+ }
+ }
+
+ // We use default FCC setting if it's not set or was set to an invalid folder.
+ if (useDefaultFCC)
+ {
+ // Only check whether the user wants the message in the original message
+ // folder if the msgcomptype is some kind of a reply.
+ if (!aOriginalMsgURI.IsEmpty() && (
+ aType == nsIMsgCompType::Reply ||
+ aType == nsIMsgCompType::ReplyAll ||
+ aType == nsIMsgCompType::ReplyToGroup ||
+ aType == nsIMsgCompType::ReplyToList ||
+ aType == nsIMsgCompType::ReplyToSender ||
+ aType == nsIMsgCompType::ReplyToSenderAndGroup ||
+ aType == nsIMsgCompType::ReplyWithTemplate )
+ )
+ {
+ nsCOMPtr <nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(PromiseFlatCString(aOriginalMsgURI).get(),
+ getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIMsgFolder> folder;
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool canFileMessages;
+ rv = folder->GetCanFileMessages(&canFileMessages);
+ if (NS_SUCCEEDED(rv) && canFileMessages)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = folder->GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString incomingServerType;
+ rv = incomingServer->GetCharValue("type", incomingServerType);
+ // Exclude RSS accounts, as they falsely report
+ // 'canFileMessages' = true
+ if (NS_SUCCEEDED(rv) && !incomingServerType.Equals("rss"))
+ {
+ bool fccReplyFollowsParent;
+ rv = mUserIdentity->GetFccReplyFollowsParent(
+ &fccReplyFollowsParent);
+ if (NS_SUCCEEDED(rv) && fccReplyFollowsParent)
+ {
+ nsCString folderURI;
+ rv = folder->GetURI(folderURI);
+ if (NS_SUCCEEDED(rv))
+ {
+ mCompFields->SetFcc(folderURI.get());
+ useDefaultFCC = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (useDefaultFCC)
+ {
+ nsCString uri;
+ GetFolderURIFromUserPrefs(nsMsgDeliverNow, mUserIdentity, uri);
+ mCompFields->SetFcc(MsgLowerCaseEqualsLiteral(uri, "nocopy://") ? "" : uri.get());
+ }
+ }
+ }
+
+ //
+ // Deal with an additional FCC operation for this email.
+ //
+ const char *fieldsFCC2 = fields->GetFcc2();
+ if ( (fieldsFCC2) && (*fieldsFCC2) )
+ {
+ if (PL_strcasecmp(fieldsFCC2, "nocopy://") == 0)
+ {
+ mCompFields->SetFcc2("");
+ mNeedToPerformSecondFCC = false;
+ }
+ else
+ {
+ mCompFields->SetFcc2(fieldsFCC2);
+ mNeedToPerformSecondFCC = true;
+ }
+ }
+
+ // Copy the main bodies of headers over.
+ rv = mCompFields->AddAllHeaders(fields);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> srcAttachments;
+ fields->GetAttachments(getter_AddRefs(srcAttachments));
+ if (srcAttachments)
+ {
+ bool moreAttachments;
+ nsCOMPtr<nsISupports> element;
+ while (NS_SUCCEEDED(srcAttachments->HasMoreElements(&moreAttachments)) && moreAttachments) {
+ rv = srcAttachments->GetNext(getter_AddRefs(element));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAttachment> attachment = do_QueryInterface(element, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCompFields->AddAttachment(attachment);
+ }
+ }
+
+ AddDefaultCustomHeaders();
+
+ AddMailFollowupToHeader();
+ AddMailReplyToHeader();
+
+ if (aType == nsIMsgCompType::ForwardInline ||
+ aType == nsIMsgCompType::ForwardAsAttachment)
+ AddXForwardedMessageIdHeader();
+
+ pStr = fields->GetPriority();
+ if (pStr)
+ mCompFields->SetPriority((char *) pStr);
+
+ mCompFields->SetAttachVCard(fields->GetAttachVCard());
+ mCompFields->SetForcePlainText(fields->GetForcePlainText());
+ mCompFields->SetUseMultipartAlternative(fields->GetUseMultipartAlternative());
+ int32_t receiptType = nsIMsgMdnGenerator::eDntType;
+ fields->GetReceiptHeaderType(&receiptType);
+
+ mCompFields->SetReturnReceipt(fields->GetReturnReceipt());
+ mCompFields->SetAttachmentReminder(fields->GetAttachmentReminder());
+ mCompFields->SetDeliveryFormat(fields->GetDeliveryFormat());
+ mCompFields->SetContentLanguage(fields->GetContentLanguage());
+ mCompFields->SetReceiptHeaderType(receiptType);
+
+ mCompFields->SetDSN(fields->GetDSN());
+
+ mCompFields->SetBodyIsAsciiOnly(fields->GetBodyIsAsciiOnly());
+ mCompFields->SetForceMsgEncoding(fields->GetForceMsgEncoding());
+
+ nsCOMPtr<nsISupports> secInfo;
+ fields->GetSecurityInfo(getter_AddRefs(secInfo));
+
+ mCompFields->SetSecurityInfo(secInfo);
+
+ bool needToCheckCharset;
+ fields->GetNeedToCheckCharset(&needToCheckCharset);
+ mCompFields->SetNeedToCheckCharset(needToCheckCharset);
+
+ if ( m_deliver_mode != nsMsgSaveAsDraft && m_deliver_mode != nsMsgSaveAsTemplate )
+ {
+ // Check the fields for legitimacy...
+ return mime_sanity_check_fields (
+ mCompFields->GetFrom(), mCompFields->GetReplyTo(),
+ mCompFields->GetTo(), mCompFields->GetCc(),
+ mCompFields->GetBcc(), mCompFields->GetFcc(),
+ mCompFields->GetNewsgroups(), mCompFields->GetFollowupTo(),
+ mCompFields->GetSubject(), mCompFields->GetReferences(),
+ mCompFields->GetOrganization(), "");
+ }
+ return NS_OK;
+}
+
+// Add default headers to outgoing messages see Bug #61520
+// mail.identity.<id#>.headers pref is a comma separated value of pref names
+// containging headers to add headers are stored in
+// mail.identity.<id#>.header.<header name> grab all the headers, mime encode
+// them and add them to the other custom headers.
+nsresult
+nsMsgComposeAndSend::AddDefaultCustomHeaders() {
+ nsCString headersList;
+ // get names of prefs containing headers to add
+ nsresult rv = mUserIdentity->GetCharAttribute("headers", headersList);
+ if (NS_SUCCEEDED(rv) && !headersList.IsEmpty()) {
+ int32_t start = 0;
+ int32_t end = 0;
+ int32_t len = 0;
+ while (end != -1) {
+ end = headersList.FindChar(',', start);
+ if (end == -1) {
+ len = headersList.Length() - start;
+ } else {
+ len = end - start;
+ }
+ // grab the name of the current header pref
+ nsAutoCString headerName("header.");
+ headerName.Append(Substring(headersList, start, len));
+ start = end + 1;
+
+ nsCString headerVal;
+ rv = mUserIdentity->GetCharAttribute(headerName.get(), headerVal);
+ if (NS_SUCCEEDED(rv)) {
+ int32_t colonIdx = headerVal.FindChar(':');
+ if (colonIdx > 0) { // check that the header is *most likely* valid.
+ nsCString name(Substring(headerVal, 0, colonIdx));
+ mCompFields->SetRawHeader(name.get(),
+ Substring(headerVal, colonIdx + 1), nullptr);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+// Add Mail-Followup-To header
+// See bug #204339 and http://cr.yp.to/proto/replyto.html for details
+nsresult
+nsMsgComposeAndSend::AddMailFollowupToHeader() {
+ nsresult rv;
+
+ // If there's already a Mail-Followup-To header, don't need to do anything.
+ nsAutoCString mftHeader;
+ mCompFields->GetRawHeader(HEADER_MAIL_FOLLOWUP_TO, mftHeader);
+ if (!mftHeader.IsEmpty())
+ {
+ return NS_OK;
+ }
+
+ // Get list of subscribed mailing lists
+ nsAutoCString mailing_lists;
+ rv = mUserIdentity->GetCharAttribute("subscribed_mailing_lists", mailing_lists);
+ // Stop here if this list is missing or empty
+ if (NS_FAILED(rv) || mailing_lists.IsEmpty())
+ return NS_OK;
+
+ // Get a list of all recipients excluding bcc
+ nsDependentCString to(mCompFields->GetTo());
+ nsDependentCString cc(mCompFields->GetCc());
+ nsAutoCString recipients;
+
+ if (to.IsEmpty() && cc.IsEmpty())
+ // We have bcc recipients only, so we don't add the Mail-Followup-To header
+ return NS_OK;
+
+ if (!to.IsEmpty() && cc.IsEmpty())
+ recipients = to;
+ else if (to.IsEmpty() && !cc.IsEmpty())
+ recipients = cc;
+ else
+ {
+ recipients.Assign(to);
+ recipients.AppendLiteral(", ");
+ recipients.Append(cc);
+ }
+
+ // Remove duplicate addresses in recipients
+ nsAutoCString recipients_no_dups;
+ RemoveDuplicateAddresses(recipients, EmptyCString(), recipients_no_dups);
+
+ // Remove subscribed mailing lists from recipients...
+ nsAutoCString recipients_without_mailing_lists;
+ RemoveDuplicateAddresses(recipients_no_dups, mailing_lists,
+ recipients_without_mailing_lists);
+
+ // ... If the result is equal to the input, we don't write to a subscribed
+ // mailing list and therefore we don't add Mail-Followup-To
+ if (recipients_no_dups == recipients_without_mailing_lists)
+ return NS_OK;
+
+ // Set Mail-Followup-To
+ return mCompFields->SetRawHeader(HEADER_MAIL_FOLLOWUP_TO, recipients,
+ mCompFields->GetCharacterSet());
+}
+
+// Add Mail-Reply-To header
+// See bug #204339 and http://cr.yp.to/proto/replyto.html for details
+nsresult
+nsMsgComposeAndSend::AddMailReplyToHeader() {
+ nsresult rv;
+
+ // If there's already a Mail-Reply-To header, don't need to do anything.
+ nsAutoCString mrtHeader;
+ mCompFields->GetRawHeader(HEADER_MAIL_REPLY_TO, mrtHeader);
+ if (!mrtHeader.IsEmpty())
+ return NS_OK;
+
+ // Get list of reply-to mangling mailing lists
+ nsAutoCString mailing_lists;
+ rv = mUserIdentity->GetCharAttribute("replyto_mangling_mailing_lists", mailing_lists);
+ // Stop here if this list is missing or empty
+ if (NS_FAILED(rv) || mailing_lists.IsEmpty())
+ return NS_OK;
+
+ // MRT will be set if the recipients of the message contains at least one
+ // of the addresses in mailing_lists or if mailing_lists has '*' as first
+ // character. The latter case gives the user an easy way to always set
+ // the MRT header. Notice that this behaviour wouldn't make sense for MFT
+ // in AddMailFollowupToHeader() above.
+
+ if (mailing_lists[0] != '*') {
+ // Get a list of all recipients excluding bcc
+ nsDependentCString to(mCompFields->GetTo());
+ nsDependentCString cc(mCompFields->GetCc());
+ nsAutoCString recipients;
+
+ if (to.IsEmpty() && cc.IsEmpty())
+ // We have bcc recipients only, so we don't add the Mail-Reply-To header
+ return NS_OK;
+
+ if (!to.IsEmpty() && cc.IsEmpty())
+ recipients = to;
+ else if (to.IsEmpty() && !cc.IsEmpty())
+ recipients = cc;
+ else
+ {
+ recipients.Assign(to);
+ recipients.AppendLiteral(", ");
+ recipients.Append(cc);
+ }
+
+ // Remove duplicate addresses in recipients
+ nsAutoCString recipients_no_dups;
+ RemoveDuplicateAddresses(recipients, EmptyCString(), recipients_no_dups);
+
+ // Remove reply-to mangling mailing lists from recipients...
+ nsAutoCString recipients_without_mailing_lists;
+ RemoveDuplicateAddresses(recipients_no_dups, mailing_lists,
+ recipients_without_mailing_lists);
+
+ // ... If the result is equal to the input, none of the recipients
+ // occure in the MRT addresses and therefore we stop here.
+ if (recipients_no_dups == recipients_without_mailing_lists)
+ return NS_OK;
+ }
+
+ // Set Mail-Reply-To
+ nsAutoCString replyTo, mailReplyTo;
+ replyTo = mCompFields->GetReplyTo();
+ if (replyTo.IsEmpty())
+ mailReplyTo = mCompFields->GetFrom();
+ else
+ mailReplyTo = replyTo;
+
+ mCompFields->SetRawHeader(HEADER_MAIL_REPLY_TO, mailReplyTo,
+ mCompFields->GetCharacterSet());
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeAndSend::AddXForwardedMessageIdHeader() {
+ return mCompFields->SetRawHeader("X-Forwarded-Message-Id",
+ nsDependentCString(mCompFields->GetReferences()), nullptr);
+}
+
+nsresult
+nsMsgComposeAndSend::SnarfAndCopyBody(const nsACString &attachment1_body,
+ const char *attachment1_type)
+{
+ //
+ // If we are here, then just process the body from what was
+ // passed in the attachment1_body field.
+ //
+ // strip out whitespaces from the end of body ONLY.
+ nsAutoCString body(attachment1_body);
+ body.Trim(" ", false, true);
+
+ if (body.Length() > 0)
+ {
+ m_attachment1_body = ToNewCString(body);
+ if (!m_attachment1_body) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ m_attachment1_body_length = body.Length();
+ }
+
+ PR_FREEIF(m_attachment1_type);
+ m_attachment1_type = PL_strdup (attachment1_type);
+ PR_FREEIF(m_attachment1_encoding);
+ m_attachment1_encoding = PL_strdup (ENCODING_8BIT);
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeAndSend::Init(
+ nsIMsgIdentity *aUserIdentity,
+ const char *aAccountKey,
+ nsMsgCompFields *fields,
+ nsIFile *sendFile,
+ bool digest_p,
+ bool dont_deliver_p,
+ nsMsgDeliverMode mode,
+ nsIMsgDBHdr *msgToReplace,
+ const char *attachment1_type,
+ const nsACString &attachment1_body,
+ nsIArray *attachments,
+ nsIArray *preloaded_attachments,
+ const char *password,
+ const nsACString &aOriginalMsgURI,
+ MSG_ComposeType aType)
+{
+ nsresult rv = NS_OK;
+
+ //Let make sure we retreive the correct number of related parts. It may have changed since last time
+ GetMultipartRelatedCount(true);
+
+ nsString msg;
+ if (!mComposeBundle)
+ {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(mComposeBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Tell the user we are assembling the message...
+ mComposeBundle->GetStringFromName(u"assemblingMailInformation", getter_Copies(msg));
+ SetStatusMessage(msg);
+ if (mSendReport)
+ mSendReport->SetCurrentProcess(nsIMsgSendReport::process_BuildMessage);
+
+ //
+ // The Init() method should initialize a send operation for full
+ // blown create and send operations as well as just the "send a file"
+ // operations.
+ //
+ m_dont_deliver_p = dont_deliver_p;
+ m_deliver_mode = mode;
+ mMsgToReplace = msgToReplace;
+
+ mUserIdentity = aUserIdentity;
+ mAccountKey = aAccountKey;
+ NS_ASSERTION(mUserIdentity, "Got null identity!\n");
+ if (!mUserIdentity) return NS_ERROR_UNEXPECTED;
+
+ //
+ // First sanity check the composition fields parameter and
+ // see if we should continue
+ //
+ if (!fields)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ m_digest_p = digest_p;
+
+ //
+ // Needed for mime encoding!
+ //
+ bool strictly_mime = true;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (pPrefBranch)
+ {
+ rv = pPrefBranch->GetBoolPref(PREF_MAIL_STRICTLY_MIME, &strictly_mime);
+ rv = pPrefBranch->GetIntPref(PREF_MAIL_MESSAGE_WARNING_SIZE, (int32_t *) &mMessageWarningSize);
+ }
+
+ nsCOMPtr<nsIMsgComposeSecure> secureCompose
+ = do_CreateInstance(NS_MSGCOMPOSESECURE_CONTRACTID, &rv);
+ // It's not an error scenario if there is no secure compose.
+ // The S/MIME extension may be unavailable.
+ if (NS_SUCCEEDED(rv) && secureCompose)
+ {
+ bool requiresEncryptionWork = false;
+ rv = secureCompose->RequiresCryptoEncapsulation(aUserIdentity, fields,
+ &requiresEncryptionWork);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (requiresEncryptionWork)
+ {
+ strictly_mime = true;
+ // RFC2633 3.1.3 doesn't require multipart/signed entities to have
+ // transfer encoding applied for ascii, but do it anyway to make sure
+ // the content (e.g. line endings) isn't mangled along the way.
+ fields->SetForceMsgEncoding(true);
+ }
+ }
+
+ nsMsgMIMESetConformToStandard(strictly_mime);
+ mime_use_quoted_printable_p = strictly_mime;
+
+ rv = InitCompositionFields(fields, aOriginalMsgURI, aType);
+ if (NS_FAILED(rv))
+ return rv;
+
+ //
+ // At this point, if we are only creating this object to do
+ // send operations on externally created RFC822 disk files,
+ // make sure we have setup the appropriate nsIFile and
+ // move on with life.
+ //
+ //
+ // First check to see if we are doing a send operation on an external file
+ // or creating the file itself.
+ //
+ if (sendFile)
+ {
+ mTempFile = sendFile;
+ return NS_OK;
+ }
+
+ // Ok, now watch me pull a rabbit out of my hat....what we need
+ // to do here is figure out what the body will be. If this is a
+ // MHTML request, then we need to do some processing of the document
+ // and figure out what we need to package along with this message
+ // to send. See ProcessMultipartRelated() for further details.
+ //
+
+ //
+ // If we don't have an editor, then we won't be doing multipart related processing
+ // for the body, so make a copy of the one passed in.
+ //
+ if (!mEditor)
+ {
+ SnarfAndCopyBody(attachment1_body, attachment1_type);
+ mOriginalHTMLBody = ToNewCString(attachment1_body);
+ }
+ else if (GetMultipartRelatedCount() == 0) // Only do this if there are not embedded objects
+ {
+ rv = GetBodyFromEditor();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ mSmtpPassword = password;
+
+ return HackAttachments(attachments, preloaded_attachments);
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::SendDeliveryCallback(nsIURI *aUrl, bool inIsNewsDelivery, nsresult aExitCode)
+{
+ if (inIsNewsDelivery)
+ {
+ if (NS_FAILED(aExitCode))
+ if (aExitCode != NS_ERROR_ABORT && !NS_IS_MSG_ERROR(aExitCode))
+ aExitCode = NS_ERROR_POST_FAILED;
+
+ DeliverAsNewsExit(aUrl, aExitCode);
+ }
+ else
+ {
+ if (NS_FAILED(aExitCode))
+ {
+#ifdef __GNUC__
+// Temporary workaroung until bug 783526 is fixed.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wswitch"
+#endif
+ switch (aExitCode)
+ {
+ case NS_ERROR_UNKNOWN_HOST:
+ case NS_ERROR_UNKNOWN_PROXY_HOST:
+ aExitCode = NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER;
+ break;
+ case NS_ERROR_CONNECTION_REFUSED:
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ aExitCode = NS_ERROR_SMTP_SEND_FAILED_REFUSED;
+ break;
+ case NS_ERROR_NET_INTERRUPT:
+ aExitCode = NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED;
+ break;
+ case NS_ERROR_NET_TIMEOUT:
+ case NS_ERROR_NET_RESET:
+ aExitCode = NS_ERROR_SMTP_SEND_FAILED_TIMEOUT;
+ break;
+ case NS_ERROR_SMTP_PASSWORD_UNDEFINED:
+ // nothing to do, just keep the code
+ break;
+ default:
+ if (aExitCode != NS_ERROR_ABORT && !NS_IS_MSG_ERROR(aExitCode))
+ aExitCode = NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON;
+ break;
+ }
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ }
+ DeliverAsMailExit(aUrl, aExitCode);
+ }
+
+ return aExitCode;
+}
+
+nsresult
+nsMsgComposeAndSend::DeliverMessage()
+{
+ if (mSendProgress)
+ {
+ bool canceled = false;
+ mSendProgress->GetProcessCanceledByUser(&canceled);
+ if (canceled)
+ return NS_ERROR_ABORT;
+ }
+
+ bool mail_p = ((mCompFields->GetTo() && *mCompFields->GetTo()) ||
+ (mCompFields->GetCc() && *mCompFields->GetCc()) ||
+ (mCompFields->GetBcc() && *mCompFields->GetBcc()));
+ bool news_p = mCompFields->GetNewsgroups() && *(mCompFields->GetNewsgroups());
+ NS_ASSERTION(!( m_deliver_mode != nsMsgSaveAsDraft && m_deliver_mode != nsMsgSaveAsTemplate) || (mail_p || news_p), "message without destination");
+ if (m_deliver_mode == nsMsgQueueForLater ||
+ m_deliver_mode == nsMsgDeliverBackground ||
+ m_deliver_mode == nsMsgSaveAsDraft ||
+ m_deliver_mode == nsMsgSaveAsTemplate)
+ return SendToMagicFolder(m_deliver_mode);
+
+ //
+ // Ok, we are about to send the file that we have built up...but what
+ // if this is a mongo email...we should have a way to warn the user that
+ // they are about to do something they may not want to do.
+ //
+ int64_t fileSize;
+ nsresult rv = mTempFile->GetFileSize(&fileSize);
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ if ((mMessageWarningSize > 0) && (fileSize > mMessageWarningSize) && (mGUINotificationEnabled))
+ {
+ bool abortTheSend = false;
+ nsString msg;
+ nsAutoString formattedFileSize;
+ FormatFileSize(fileSize, true, formattedFileSize);
+ const char16_t* params[] = { formattedFileSize.get() };
+ mComposeBundle->FormatStringFromName(u"largeMessageSendWarning",
+ params, 1, getter_Copies(msg));
+
+ if (!msg.IsEmpty())
+ {
+ nsCOMPtr<nsIPrompt> prompt;
+ GetDefaultPrompt(getter_AddRefs(prompt));
+ nsMsgAskBooleanQuestionByString(prompt, msg.get(), &abortTheSend);
+ if (!abortTheSend)
+ {
+ nsresult ignoreMe;
+ Fail(NS_ERROR_BUT_DONT_SHOW_ALERT, msg.get(), &ignoreMe);
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ if (news_p)
+ {
+ if (mail_p)
+ mSendMailAlso = true;
+
+ return DeliverFileAsNews(); /* will call DeliverFileAsMail if it needs to */
+ }
+ else if (mail_p)
+ return DeliverFileAsMail();
+ else
+ return NS_ERROR_UNEXPECTED;
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgComposeAndSend::DeliverFileAsMail()
+{
+ char *buf, *buf2;
+ buf = (char *) PR_Malloc ((mCompFields->GetTo() ? PL_strlen (mCompFields->GetTo()) + 10 : 0) +
+ (mCompFields->GetCc() ? PL_strlen (mCompFields->GetCc()) + 10 : 0) +
+ (mCompFields->GetBcc() ? PL_strlen (mCompFields->GetBcc()) + 10 : 0) +
+ 10);
+
+ if (mSendReport)
+ mSendReport->SetCurrentProcess(nsIMsgSendReport::process_SMTP);
+
+ nsCOMPtr<nsIPrompt> promptObject;
+ GetDefaultPrompt(getter_AddRefs(promptObject));
+
+ if (!buf)
+ {
+ nsresult ignoreMe;
+ Fail(NS_ERROR_OUT_OF_MEMORY, nullptr, &ignoreMe);
+ NotifyListenerOnStopSending(nullptr, NS_ERROR_OUT_OF_MEMORY, nullptr, nullptr);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ bool collectOutgoingAddresses = true;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (pPrefBranch)
+ pPrefBranch->GetBoolPref(PREF_MAIL_COLLECT_EMAIL_ADDRESS_OUTGOING, &collectOutgoingAddresses);
+
+ nsCOMPtr<nsIAbAddressCollector> addressCollector =
+ do_GetService(NS_ABADDRESSCOLLECTOR_CONTRACTID);
+
+ bool collectAddresses = (collectOutgoingAddresses && addressCollector);
+ uint32_t sendFormat = nsIAbPreferMailFormat::unknown;
+
+ // this code is not ready yet
+ // see bug #44494 for more details
+ // so for now, just pass in nsIAbPreferMailFormat::unknown
+ // which will have no effect on the "prefers" attribute in the ab
+#if 0
+ bool forcePlainText = mCompFields->GetForcePlainText();
+ bool useMultipartAlternative = mCompFields->GetUseMultipartAlternative();
+ // see GenericSendMessage() in MsgComposeCommands.js for the reverse logic
+ // if we choose to send both (html and plain) remember html.
+ if (forcePlainText && !useMultipartAlternative)
+ {
+ // for now, don't remember the "plaintext" decision.
+ // we could get in here because while sending html mail
+ // the body was "convertible", but that doesn't mean
+ // we intended to force plain text here.
+ // so for now, use "unknown" which will have no effect on the
+ // "prefers" attribute in the ab.
+ // see bug #245520 for more details
+ // sendFormat = nsIAbPreferMailFormat::plaintext;
+ sendFormat = nsIAbPreferMailFormat::unknown;
+ }
+ else if (!forcePlainText)
+ sendFormat = nsIAbPreferMailFormat::html;
+ else
+ NS_ERROR("unknown send format, should not happen");
+#endif
+
+ PL_strcpy (buf, "");
+ buf2 = buf + PL_strlen (buf);
+ if (mCompFields->GetTo() && *mCompFields->GetTo())
+ {
+ PL_strcat (buf2, mCompFields->GetTo());
+ if (addressCollector)
+ addressCollector->CollectAddress(nsCString(mCompFields->GetTo()),
+ collectAddresses /* create card if one doesn't exist */, sendFormat);
+ }
+ if (mCompFields->GetCc() && *mCompFields->GetCc()) {
+ if (*buf2) PL_strcat (buf2, ",");
+ PL_strcat (buf2, mCompFields->GetCc());
+ if (addressCollector)
+ addressCollector->CollectAddress(nsCString(mCompFields->GetCc()),
+ collectAddresses /* create card if one doesn't exist */, sendFormat);
+ }
+ if (mCompFields->GetBcc() && *mCompFields->GetBcc()) {
+ if (*buf2) PL_strcat (buf2, ",");
+ PL_strcat (buf2, mCompFields->GetBcc());
+ if (addressCollector)
+ addressCollector->CollectAddress(nsCString(mCompFields->GetBcc()),
+ collectAddresses /* create card if one doesn't exist */, sendFormat);
+ }
+
+ // We need undo groups to keep only the addresses
+ nsresult rv = StripOutGroupNames(buf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ok, now MIME II encode this to prevent 8bit problems...
+ char *convbuf = nsMsgI18NEncodeMimePartIIStr(buf, true,
+ mCompFields->GetCharacterSet(), 0, nsMsgMIMEGetConformToStandard());
+ if (convbuf)
+ {
+ // MIME-PartII conversion
+ PR_FREEIF(buf);
+ buf = convbuf;
+ }
+
+ nsCString escaped_buf;
+ MsgEscapeString(nsDependentCString(buf), nsINetUtil::ESCAPE_URL_PATH, escaped_buf);
+
+ if (!escaped_buf.IsEmpty())
+ {
+ NS_Free(buf);
+ buf = ToNewCString(escaped_buf);
+ }
+
+ nsCOMPtr<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && smtpService)
+ {
+ MsgDeliveryListener *deliveryListener = new MsgDeliveryListener(this, false);
+ if (!deliveryListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // we used to get the prompt from the compose window and we'd pass that in
+ // to the smtp protocol as the prompt to use. But when you send a message,
+ // we dismiss the compose window.....so you are parenting off of a window that
+ // isn't there. To have it work correctly I think we want the alert dialogs to be modal
+ // to the top most mail window...after all, that's where we are going to be sending status
+ // update information too....
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ GetNotificationCallbacks(getter_AddRefs(callbacks));
+
+ // Tell the user we are sending the message!
+ nsString msg;
+ mComposeBundle->GetStringFromName(u"sendingMessage", getter_Copies(msg));
+ SetStatusMessage(msg);
+ nsCOMPtr<nsIMsgStatusFeedback> msgStatus (do_QueryInterface(mSendProgress));
+ // if the sendProgress isn't set, let's use the member variable.
+ if (!msgStatus)
+ msgStatus = do_QueryInterface(mStatusFeedback);
+
+ nsCOMPtr<nsIURI> runningUrl;
+ rv = smtpService->SendMailMessage(mTempFile, buf, mUserIdentity,
+ mSmtpPassword.get(), deliveryListener, msgStatus,
+ callbacks, mCompFields->GetDSN(),
+ getter_AddRefs(runningUrl),
+ getter_AddRefs(mRunningRequest));
+ // set envid on the returned URL
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsISmtpUrl> smtpUrl(do_QueryInterface(runningUrl, &rv));
+ if (NS_SUCCEEDED(rv))
+ smtpUrl->SetDsnEnvid(nsDependentCString(mCompFields->GetMessageId()));
+ }
+ }
+
+ PR_FREEIF(buf); // free the buf because we are done with it....
+ return rv;
+}
+
+nsresult
+nsMsgComposeAndSend::DeliverFileAsNews()
+{
+ nsresult rv = NS_OK;
+ if (!(mCompFields->GetNewsgroups()))
+ return rv;
+
+ if (mSendReport)
+ mSendReport->SetCurrentProcess(nsIMsgSendReport::process_NNTP);
+
+ nsCOMPtr<nsIPrompt> promptObject;
+ GetDefaultPrompt(getter_AddRefs(promptObject));
+
+ nsCOMPtr<nsINntpService> nntpService(do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv) && nntpService)
+ {
+ MsgDeliveryListener *deliveryListener = new MsgDeliveryListener(this, true);
+ if (!deliveryListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Tell the user we are posting the message!
+ nsString msg;
+ mComposeBundle->GetStringFromName(u"postingMessage",
+ getter_Copies(msg));
+ SetStatusMessage(msg);
+
+ nsCOMPtr <nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // JFD TODO: we should use GetDefaultPrompt instead
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ // see bug #163139
+ // we might not have a msg window if only the compose window is open.
+ if(NS_FAILED(rv))
+ msgWindow = nullptr;
+
+ rv = nntpService->PostMessage(mTempFile, mCompFields->GetNewsgroups(), mAccountKey.get(),
+ deliveryListener, msgWindow, nullptr);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::Fail(nsresult aFailureCode, const char16_t *aErrorMsg,
+ nsresult *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = aFailureCode;
+
+ if (NS_FAILED(aFailureCode))
+ {
+ nsCOMPtr<nsIPrompt> prompt;
+ GetDefaultPrompt(getter_AddRefs(prompt));
+
+ if (mSendReport)
+ {
+ int32_t process;
+ if (NS_SUCCEEDED(mSendReport->GetCurrentProcess(&process)) && process == nsIMsgSendReport::process_Current)
+ {
+ // currentProcess isn't set yet, so we need another value.
+ mSendReport->SetCurrentProcess(nsIMsgSendReport::process_BuildMessage);
+ }
+ mSendReport->SetError(nsIMsgSendReport::process_Current, aFailureCode, false);
+ mSendReport->SetMessage(nsIMsgSendReport::process_Current, aErrorMsg, false);
+ mSendReport->DisplayReport(prompt, true, true, aResult);
+ }
+ else
+ {
+ if (aFailureCode != NS_ERROR_BUT_DONT_SHOW_ALERT)
+ nsMsgDisplayMessageByName(prompt, u"sendFailed");
+ }
+ }
+
+ if (NS_SUCCEEDED(m_status))
+ m_status = NS_ERROR_BUT_DONT_SHOW_ALERT;
+
+ //Stop any pending process...
+ Abort();
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgComposeAndSend::FormatStringWithSMTPHostNameByName(const char16_t* aMsgName, char16_t **aString)
+{
+ NS_ENSURE_ARG(aString);
+
+ nsresult rv;
+ nsCOMPtr<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Get the smtp hostname and format the string.
+ nsCString smtpHostName;
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = smtpService->GetServerByIdentity(mUserIdentity, getter_AddRefs(smtpServer));
+ if (NS_SUCCEEDED(rv))
+ smtpServer->GetHostname(smtpHostName);
+
+ nsAutoString hostStr;
+ CopyASCIItoUTF16(smtpHostName, hostStr);
+ const char16_t *params[] = { hostStr.get() };
+ if (NS_SUCCEEDED(rv))
+ mComposeBundle->FormatStringFromName(aMsgName, params, 1, aString);
+ return rv;
+}
+
+void
+nsMsgComposeAndSend::DoDeliveryExitProcessing(nsIURI * aUri, nsresult aExitCode, bool aCheckForMail)
+{
+ // If we fail on the news delivery, no sense in going on so just notify
+ // the user and exit.
+ if (NS_FAILED(aExitCode))
+ {
+ const char16_t* exitString = errorStringNameForErrorCode(aExitCode);
+ nsString eMsg;
+ if (aExitCode == NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER ||
+ aExitCode == NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON ||
+ aExitCode == NS_ERROR_SMTP_SEND_FAILED_REFUSED ||
+ aExitCode == NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED ||
+ aExitCode == NS_ERROR_SMTP_SEND_FAILED_TIMEOUT ||
+ aExitCode == NS_ERROR_SMTP_PASSWORD_UNDEFINED ||
+ aExitCode == NS_ERROR_SMTP_AUTH_FAILURE ||
+ aExitCode == NS_ERROR_SMTP_AUTH_GSSAPI ||
+ aExitCode == NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED ||
+ aExitCode == NS_ERROR_SMTP_AUTH_NOT_SUPPORTED ||
+ aExitCode == NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL ||
+ aExitCode == NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL ||
+ aExitCode == NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT ||
+ aExitCode == NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS) {
+ FormatStringWithSMTPHostNameByName(exitString, getter_Copies(eMsg));
+ } else {
+ mComposeBundle->GetStringFromName(exitString, getter_Copies(eMsg));
+ }
+
+ Fail(aExitCode, eMsg.get(), &aExitCode);
+ NotifyListenerOnStopSending(nullptr, aExitCode, nullptr, nullptr);
+ return;
+ }
+
+ if (aCheckForMail)
+ {
+ if ((mCompFields->GetTo() && *mCompFields->GetTo()) ||
+ (mCompFields->GetCc() && *mCompFields->GetCc()) ||
+ (mCompFields->GetBcc() && *mCompFields->GetBcc()))
+ {
+ // If we're sending this news message to mail as well, start it now.
+ // Completion and further errors will be handled there.
+ DeliverFileAsMail();
+ return;
+ }
+ }
+
+ //
+ // Tell the listeners that we are done with the sending operation...
+ //
+ NotifyListenerOnStopSending(mCompFields->GetMessageId(),
+ aExitCode,
+ nullptr,
+ nullptr);
+
+ // If we hit here, we are done with delivery!
+ //
+ // Just call the DoFCC() method and if that fails, then we should just
+ // cleanup and get out. If DoFCC "succeeds", then all that means is the
+ // async copy operation has been started and we will be notified later
+ // when it is done. DON'T cleanup until the copy is complete and don't
+ // notify the listeners with OnStop() until we are done.
+ //
+ // For now, we don't need to do anything here, but the code will stay this
+ // way until later...
+ //
+
+ DoFcc();
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::DeliverAsMailExit(nsIURI *aUrl, nsresult aExitCode)
+{
+ DoDeliveryExitProcessing(aUrl, aExitCode, false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::DeliverAsNewsExit(nsIURI *aUrl, nsresult aExitCode)
+{
+ DoDeliveryExitProcessing(aUrl, aExitCode, mSendMailAlso);
+ return NS_OK;
+}
+
+bool nsMsgComposeAndSend::CanSaveMessagesToFolder(const char *folderURL)
+{
+ nsresult rv;
+ nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
+ if (NS_FAILED(rv))
+ return false;
+
+ nsCOMPtr<nsIRDFResource> resource;
+ rv = rdf->GetResource(nsDependentCString(folderURL), getter_AddRefs(resource));
+ if (NS_FAILED(rv))
+ return false;
+
+ nsCOMPtr <nsIMsgFolder> thisFolder;
+ thisFolder = do_QueryInterface(resource, &rv);
+ if (NS_FAILED(rv) || !thisFolder)
+ return false;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = thisFolder->GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv) || !server)
+ return false;
+
+ // See if we are allowed to save/file msgs to this folder.
+ bool canSave;
+ rv = server->GetCanFileMessagesOnServer(&canSave);
+ return canSave;
+}
+
+//
+// Now, start the appropriate copy operation.
+//
+nsresult
+nsMsgComposeAndSend::DoFcc()
+{
+ //
+ // Just cleanup and return success if we're not allowed to save msgs to FCC folder.
+ //
+ const char* fcc = mCompFields->GetFcc();
+ if (!fcc || !*fcc || !CanSaveMessagesToFolder(fcc))
+ {
+
+ // It is the caller's responsibility to say we've stopped sending, so just
+ // let the listeners know we're not doing a copy.
+ NotifyListenerOnStopCopy(NS_OK); // For closure of compose window...
+ return NS_OK;
+ }
+
+ if (mSendReport)
+ mSendReport->SetCurrentProcess(nsIMsgSendReport::process_Copy);
+
+ //
+ // If we are here, then we need to save off the FCC file to save and
+ // start the copy operation. MimeDoFCC() will take care of all of this
+ // for us.
+ //
+ nsresult rv = MimeDoFCC(mTempFile,
+ nsMsgDeliverNow,
+ mCompFields->GetBcc(),
+ mCompFields->GetFcc(),
+ mCompFields->GetNewspostUrl());
+ if (NS_FAILED(rv))
+ {
+ //
+ // If we hit here, the copy operation FAILED and we should at least tell the
+ // user that it did fail but the send operation has already succeeded.
+ //
+ NotifyListenerOnStopCopy(rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::NotifyListenerOnStartSending(const char *aMsgID, uint32_t aMsgSize)
+{
+ if (mListener)
+ mListener->OnStartSending(aMsgID, aMsgSize);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::NotifyListenerOnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax)
+{
+ if (mListener)
+ mListener->OnProgress(aMsgID, aProgress, aProgressMax);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::NotifyListenerOnStatus(const char *aMsgID, const char16_t *aMsg)
+{
+ if (mListener)
+ mListener->OnStatus(aMsgID, aMsg);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::NotifyListenerOnStopSending(const char *aMsgID, nsresult aStatus, const char16_t *aMsg,
+ nsIFile *returnFile)
+{
+ if (mListener != nullptr)
+ mListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::NotifyListenerOnStartCopy()
+{
+ nsCOMPtr<nsIMsgCopyServiceListener> copyListener;
+
+ if (mListener)
+ {
+ copyListener = do_QueryInterface(mListener);
+ if (copyListener)
+ copyListener->OnStartCopy();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::NotifyListenerOnProgressCopy(uint32_t aProgress,
+ uint32_t aProgressMax)
+{
+ nsCOMPtr<nsIMsgCopyServiceListener> copyListener;
+
+ if (mListener)
+ {
+ copyListener = do_QueryInterface(mListener);
+ if (copyListener)
+ copyListener->OnProgress(aProgress, aProgressMax);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::SetMessageKey(nsMsgKey aMessageKey)
+{
+ m_messageKey = aMessageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetMessageKey(nsMsgKey *aMessageKey)
+{
+ *aMessageKey = m_messageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetFolderUri(nsACString &aFolderUri)
+{
+ aFolderUri = m_folderName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetPartForDomIndex(int32_t aDomIndex, nsACString &aPartNum)
+{
+ aPartNum = m_partNumbers.SafeElementAt(aDomIndex, EmptyCString());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetMessageId(nsACString& aMessageId)
+{
+ nsresult rv = NS_OK;
+ if (mCompFields)
+ aMessageId = mCompFields->GetMessageId();
+ else
+ rv = NS_ERROR_NULL_POINTER;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::NotifyListenerOnStopCopy(nsresult aStatus)
+{
+ // This is one per copy so make sure we clean this up first.
+ mCopyObj = nullptr;
+
+ // Set a status message...
+ nsString msg;
+ if (NS_SUCCEEDED(aStatus))
+ mComposeBundle->GetStringFromName(u"copyMessageComplete", getter_Copies(msg));
+ else
+ mComposeBundle->GetStringFromName(u"copyMessageFailed", getter_Copies(msg));
+
+ SetStatusMessage(msg);
+ nsCOMPtr<nsIPrompt> prompt;
+ GetDefaultPrompt(getter_AddRefs(prompt));
+
+ if (NS_FAILED(aStatus))
+ {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString msg;
+ const char16_t *formatStrings[] = { mSavedToFolderName.get() };
+
+ rv = bundle->FormatStringFromName(u"errorSavingMsg",
+ formatStrings, 1,
+ getter_Copies(msg));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool retry = false;
+ nsMsgAskBooleanQuestionByString(prompt, msg.get(), &retry, nullptr);
+ if (retry)
+ {
+ mSendProgress = nullptr; // this was cancelled, so we need to clear it.
+ return SendToMagicFolder(m_deliver_mode);
+ }
+ }
+
+ // We failed, and the user decided not to retry. So we're just going to
+ // fail out. However, give Fail a success code so that it doesn't prompt
+ // the user a second time as they already know about the failure.
+ Fail(NS_OK, nullptr, &aStatus);
+ }
+
+ if (NS_SUCCEEDED(aStatus) &&
+ !mPerformingSecondFCC && m_messageKey != nsMsgKey_None &&
+ (m_deliver_mode == nsMsgDeliverNow || m_deliver_mode == nsMsgSendUnsent))
+ {
+ nsresult rv = FilterSentMessage();
+ if (NS_FAILED(rv))
+ OnStopOperation(rv);
+ return rv;
+ }
+
+ return MaybePerformSecondFCC(aStatus);
+}
+
+nsresult
+nsMsgComposeAndSend::FilterSentMessage()
+{
+ if (mSendReport)
+ mSendReport->SetCurrentProcess(nsIMsgSendReport::process_Filter);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetExistingFolder(m_folderName, getter_AddRefs(folder));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = folder->GetMessageHeader(m_messageKey, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMutableArray> msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFilterService> filterSvc = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = msgArray->AppendElement(msgHdr, false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ if (mSendProgress)
+ mSendProgress->GetMsgWindow(getter_AddRefs(msgWindow));
+
+ return filterSvc->ApplyFilters(nsMsgFilterType::PostOutgoing, msgArray, folder, msgWindow, this);
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::OnStopOperation(nsresult aStatus)
+{
+ // Set a status message...
+ nsString msg;
+ if (NS_SUCCEEDED(aStatus))
+ mComposeBundle->GetStringFromName(u"filterMessageComplete", getter_Copies(msg));
+ else
+ mComposeBundle->GetStringFromName(u"filterMessageFailed", getter_Copies(msg));
+
+ SetStatusMessage(msg);
+
+ if (NS_FAILED(aStatus))
+ {
+ nsresult rv = mComposeBundle->GetStringFromName(u"errorFilteringMsg", getter_Copies(msg));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIPrompt> prompt;
+ GetDefaultPrompt(getter_AddRefs(prompt));
+ nsMsgDisplayMessageByString(prompt, msg.get(), nullptr);
+ }
+
+ // We failed, however, give Fail a success code so that it doesn't prompt
+ // the user a second time as they already know about the failure.
+ Fail(NS_OK, nullptr, &aStatus);
+ }
+
+ return MaybePerformSecondFCC(aStatus);
+}
+
+nsresult
+nsMsgComposeAndSend::MaybePerformSecondFCC(nsresult aStatus)
+{
+ // Ok, now to support a second copy operation, we need to figure
+ // out which copy request just finished. If the user has requested
+ // a second copy operation, then we need to fire that off, but if they
+ // just wanted a single copy operation, we can tell everyone we are done
+ // and move on with life. Only do the second copy if the first one worked.
+ //
+ if ( NS_SUCCEEDED(aStatus) && (mNeedToPerformSecondFCC) )
+ {
+ if (mSendReport)
+ mSendReport->SetCurrentProcess(nsIMsgSendReport::process_FCC);
+
+ mNeedToPerformSecondFCC = false;
+ mPerformingSecondFCC = true;
+
+ const char *fcc2 = mCompFields->GetFcc2();
+ if (fcc2 && *fcc2)
+ {
+ nsresult rv = MimeDoFCC(mTempFile,
+ nsMsgDeliverNow,
+ mCompFields->GetBcc(),
+ fcc2,
+ mCompFields->GetNewspostUrl());
+ if (NS_FAILED(rv))
+ Fail(rv, nullptr, &aStatus);
+ else
+ return NS_OK;
+ }
+ }
+
+ // If we are here, its real cleanup time!
+ if (mListener)
+ {
+ nsCOMPtr<nsIMsgCopyServiceListener> copyListener =
+ do_QueryInterface(mListener);
+ if (copyListener)
+ copyListener->OnStopCopy(aStatus);
+ }
+
+ return aStatus;
+}
+
+/* This is the main driving function of this module. It generates a
+ document of type message/rfc822, which contains the stuff provided.
+ The first few arguments are the standard header fields that the
+ generated document should have.
+
+ `other_random_headers' is a string of additional headers that should
+ be inserted beyond the standard ones. If provided, it is just tacked
+ on to the end of the header block, so it should have newlines at the
+ end of each line, shouldn't have blank lines, multi-line headers
+ should be properly continued, etc.
+
+ `digest_p' says that most of the documents we are attaching are
+ themselves messages, and so we should generate a multipart/digest
+ container instead of multipart/mixed. (It's a minor difference.)
+
+ The full text of the first attachment is provided via `attachment1_type'
+ and `attachment1_body'. These may all be 0 if all attachments are
+ provided externally.
+
+ Subsequent attachments are provided as URLs to load, described in the
+ nsMsgAttachmentData structures.
+
+ If `dont_deliver_p' is false, then we actually deliver the message to the
+ SMTP and/or NNTP server, and the message_delivery_done_callback will be
+ invoked with the status.
+
+ If `dont_deliver_p' is true, then we just generate the message, we don't
+ actually deliver it, and the message_delivery_done_callback will be called
+ with the name of the generated file. The callback is responsible for both
+ freeing the file name string, and deleting the file when it is done with
+ it. If an error occurred, then `status' will be negative and
+ `error_message' may be an error message to display. If status is non-
+ negative, then `error_message' contains the file name (this is kind of
+ a kludge...)
+ */
+NS_IMETHODIMP
+nsMsgComposeAndSend::CreateAndSendMessage(
+ nsIEditor *aEditor,
+ nsIMsgIdentity *aUserIdentity,
+ const char *aAccountKey,
+ nsIMsgCompFields *fields,
+ bool digest_p,
+ bool dont_deliver_p,
+ nsMsgDeliverMode mode,
+ nsIMsgDBHdr *msgToReplace,
+ const char *attachment1_type,
+ const nsACString &attachment1_body,
+ nsIArray *attachments,
+ nsIArray *preloaded_attachments,
+ mozIDOMWindowProxy *parentWindow,
+ nsIMsgProgress *progress,
+ nsIMsgSendListener *aListener,
+ const char *password,
+ const nsACString &aOriginalMsgURI,
+ MSG_ComposeType aType
+ )
+{
+ nsresult rv;
+ /* First thing to do is to reset the send errors report */
+ mSendReport->Reset();
+ mSendReport->SetDeliveryMode(mode);
+
+ mParentWindow = do_QueryInterface(parentWindow);
+ mSendProgress = progress;
+ mListener = aListener;
+
+ // Set the editor for MHTML operations if necessary
+ if (aEditor)
+ mEditor = aEditor;
+
+ rv = Init(aUserIdentity, aAccountKey, (nsMsgCompFields *)fields, nullptr,
+ digest_p, dont_deliver_p, mode, msgToReplace,
+ attachment1_type, attachment1_body,
+ attachments, preloaded_attachments,
+ password, aOriginalMsgURI, aType);
+
+ if (NS_FAILED(rv) && mSendReport)
+ mSendReport->SetError(nsIMsgSendReport::process_Current, rv, false);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::CreateRFC822Message(
+ nsIMsgIdentity *aUserIdentity,
+ nsIMsgCompFields *aFields,
+ const char *aMsgType,
+ const nsACString &aMsgBody,
+ bool aIsDraft,
+ nsIArray *aAttachments,
+ nsIArray *aEmbeddedObjects,
+ nsIMsgSendListener *aListener
+ )
+{
+ nsresult rv;
+ nsMsgDeliverMode mode = aIsDraft ? nsIMsgSend::nsMsgSaveAsDraft :
+ nsIMsgSend::nsMsgDeliverNow;
+
+ /* First thing to do is to reset the send errors report */
+ mSendReport->Reset();
+ mSendReport->SetDeliveryMode(mode);
+
+ mParentWindow = nullptr;
+ mSendProgress = nullptr;
+ mListener = aListener;
+ mEmbeddedObjectList = aEmbeddedObjects;
+
+ rv = Init(aUserIdentity, nullptr, (nsMsgCompFields *)aFields, nullptr,
+ false, true, mode, nullptr,
+ aMsgType,
+ aMsgBody,
+ nullptr, aAttachments,
+ nullptr, EmptyCString(), nsIMsgCompType::New);
+
+ if (NS_FAILED(rv) && mSendReport)
+ mSendReport->SetError(nsIMsgSendReport::process_Current, rv, false);
+
+ return rv;
+}
+
+nsresult
+nsMsgComposeAndSend::SendMessageFile(
+ nsIMsgIdentity *aUserIndentity,
+ const char *aAccountKey,
+ nsIMsgCompFields *fields,
+ nsIFile *sendIFile,
+ bool deleteSendFileOnCompletion,
+ bool digest_p,
+ nsMsgDeliverMode mode,
+ nsIMsgDBHdr *msgToReplace,
+ nsIMsgSendListener *aListener,
+ nsIMsgStatusFeedback *aStatusFeedback,
+ const char *password
+ )
+{
+ NS_ENSURE_ARG_POINTER(fields);
+ NS_ENSURE_ARG_POINTER(sendIFile);
+
+ nsresult rv;
+
+ /* First thing to do is to reset the send errors report */
+ mSendReport->Reset();
+ mSendReport->SetDeliveryMode(mode);
+
+ mStatusFeedback = aStatusFeedback;
+ //
+ // First check to see if the external file we are sending is a valid file.
+ //
+ bool exists;
+ if (NS_FAILED(sendIFile->Exists(&exists)))
+ return NS_ERROR_INVALID_ARG;
+
+ if (!exists)
+ return NS_ERROR_INVALID_ARG;
+
+ // Setup the listener...
+ mListener = aListener;
+
+ // Should we delete the temp file when done?
+ if (!deleteSendFileOnCompletion)
+ mReturnFile = sendIFile;
+
+ rv = Init(aUserIndentity, aAccountKey, (nsMsgCompFields *)fields, sendIFile,
+ digest_p, false, mode, msgToReplace,
+ nullptr, EmptyCString(),
+ nullptr, nullptr,
+ password, EmptyCString(), nsIMsgCompType::New);
+
+ if (NS_SUCCEEDED(rv))
+ rv = DeliverMessage();
+
+ if (NS_FAILED(rv) && mSendReport)
+ mSendReport->SetError(nsIMsgSendReport::process_Current, rv, false);
+
+ return rv;
+}
+
+//
+// Send the message to the magic folder, and runs the completion/failure
+// callback.
+//
+nsresult
+nsMsgComposeAndSend::SendToMagicFolder(nsMsgDeliverMode mode)
+{
+ nsresult rv = MimeDoFCC(mTempFile,
+ mode,
+ mCompFields->GetBcc(),
+ mCompFields->GetFcc(),
+ mCompFields->GetNewspostUrl());
+ //
+ // The caller of MimeDoFCC needs to deal with failure.
+ //
+ if (NS_FAILED(rv))
+ rv = NotifyListenerOnStopCopy(rv);
+
+ return rv;
+}
+
+char*
+nsMsgGetEnvelopeLine(void)
+{
+ static char result[75] = "";
+ PRExplodedTime now;
+ char buffer[128] = "";
+
+ // Generate envelope line in format of: From - Sat Apr 18 20:01:49 1998
+ //
+ // Use PR_FormatTimeUSEnglish() to format the date in US English format,
+ // then figure out what our local GMT offset is, and append it (since
+ // PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as
+ // per RFC 1123 (superceding RFC 822.)
+ //
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
+ PR_FormatTimeUSEnglish(buffer, sizeof(buffer),
+ "%a %b %d %H:%M:%S %Y",
+ &now);
+
+ // This value must be in ctime() format, with English abbreviations.
+ // PL_strftime("... %c ...") is no good, because it is localized.
+ //
+ PL_strcpy(result, "From - ");
+ PL_strcpy(result + 7, buffer);
+ PL_strcpy(result + 7 + 24, CRLF);
+ return result;
+}
+
+#define ibuffer_size FILE_IO_BUFFER_SIZE
+nsresult
+nsMsgComposeAndSend::MimeDoFCC(nsIFile *input_file,
+ nsMsgDeliverMode mode,
+ const char *bcc_header,
+ const char *fcc_header,
+ const char *news_url)
+{
+ nsresult status = NS_OK;
+ char *ibuffer = nullptr;
+ uint32_t n;
+ bool folderIsLocal = true;
+ nsCString turi;
+ char16_t *printfString = nullptr;
+ nsString msg;
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ // Before continuing, just check the user has not cancel the operation
+ if (mSendProgress)
+ {
+ bool canceled = false;
+ mSendProgress->GetProcessCanceledByUser(&canceled);
+ if (canceled)
+ return NS_ERROR_ABORT;
+ else
+ mSendProgress->OnProgressChange(nullptr, nullptr, 0, 0, 0, -1);
+ }
+
+ //
+ // Ok, this is here to keep track of this for 2 copy operations...
+ //
+ if (mCopyFile)
+ {
+ mCopyFile2 = mCopyFile;
+ mCopyFile = nullptr;
+ }
+
+ //
+ // Create the file that will be used for the copy service!
+ //
+ nsresult rv = nsMsgCreateTempFile("nscopy.tmp", getter_AddRefs(mCopyFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> tempOutfile;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(tempOutfile), mCopyFile, -1, 00600);
+ if (NS_FAILED(rv))
+ {
+ if (mSendReport)
+ {
+ nsAutoString error_msg;
+ nsMsgBuildMessageWithTmpFile(mCopyFile, error_msg);
+ mSendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false);
+ }
+ status = NS_MSG_UNABLE_TO_OPEN_TMP_FILE;
+
+ mCopyFile = nullptr;
+ return status;
+ }
+
+ //
+ // Get our files ready...
+ //
+ nsCOMPtr<nsIInputStream> inputFile;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), input_file);
+ if (NS_FAILED(rv))
+ {
+ if (mSendReport)
+ {
+ nsAutoString error_msg;
+ nsMsgBuildMessageWithFile(input_file, error_msg);
+ mSendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false);
+ }
+ status = NS_MSG_UNABLE_TO_OPEN_FILE;
+ goto FAIL;
+ }
+
+ // now the buffers...
+ ibuffer = (char *) PR_Malloc(ibuffer_size);
+ if (!ibuffer)
+ {
+ status = NS_ERROR_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+
+ //
+ // First, we we need to put a Berkeley "From - " delimiter at the head of
+ // the file for parsing...
+ //
+
+ if (fcc_header && *fcc_header)
+ GetExistingFolder(nsDependentCString(fcc_header), getter_AddRefs(folder));
+
+ if ((mode == nsMsgDeliverNow || mode == nsMsgSendUnsent) && folder)
+ turi = fcc_header;
+ else
+ GetFolderURIFromUserPrefs(mode, mUserIdentity, turi);
+ status = MessageFolderIsLocal(mUserIdentity, mode, turi.get(), &folderIsLocal);
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ // Tell the user we are copying the message...
+ mComposeBundle->GetStringFromName(u"copyMessageStart",
+ getter_Copies(msg));
+ if (!msg.IsEmpty())
+ {
+ nsCOMPtr<nsIRDFService> rdfService = do_GetService(kRDFServiceCID);
+ if (rdfService)
+ {
+ nsCOMPtr<nsIRDFResource> res;
+ rdfService->GetResource(turi, getter_AddRefs(res));
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(res);
+ if (folder)
+ folder->GetName(mSavedToFolderName);
+ }
+ if (!mSavedToFolderName.IsEmpty())
+ printfString = nsTextFormatter::smprintf(msg.get(), mSavedToFolderName.get());
+ else
+ printfString = nsTextFormatter::smprintf(msg.get(), "?");
+ if (printfString)
+ {
+ SetStatusMessage(nsDependentString(printfString));
+ PR_Free(printfString);
+ }
+ }
+
+ if (folderIsLocal)
+ {
+ char *envelopeLine = nsMsgGetEnvelopeLine();
+ uint32_t len = PL_strlen(envelopeLine);
+
+ rv = tempOutfile->Write(envelopeLine, len, &n);
+ if (NS_FAILED(rv) || n != len)
+ {
+ status = NS_ERROR_FAILURE;
+ goto FAIL;
+ }
+ }
+
+ //
+ // Write out an X-Mozilla-Status header.
+ //
+ // This is required for the queue file, so that we can overwrite it once
+ // the messages have been delivered, and so that the nsMsgMessageFlags::Queued bit
+ // is set.
+ //
+ // For FCC files, we don't necessarily need one, but we might as well put
+ // one in so that it's marked as read already.
+ //
+ //
+ // Need to add these lines for POP3 ONLY! IMAP servers will handle
+ // this status information for summary file regeneration for us.
+ if ((mode == nsMsgQueueForLater || mode == nsMsgSaveAsDraft ||
+ mode == nsMsgSaveAsTemplate || mode == nsMsgDeliverNow ||
+ mode == nsMsgSendUnsent || mode == nsMsgDeliverBackground) &&
+ folderIsLocal)
+ {
+ char *buf = 0;
+ uint16_t flags = 0;
+
+ // for save as draft and send later, we want to leave the message as unread.
+ // See Bug #198087
+ // Messages sent with mode nsMsgDeliverBackground must not have the Queued
+ // flag sent so that they get picked up by the background send function.
+ if (mode == nsMsgQueueForLater)
+ flags |= nsMsgMessageFlags::Queued;
+ else if (mode != nsMsgSaveAsDraft && mode != nsMsgDeliverBackground)
+ flags |= nsMsgMessageFlags::Read;
+ buf = PR_smprintf(X_MOZILLA_STATUS_FORMAT CRLF, flags);
+ if (buf)
+ {
+ uint32_t len = PL_strlen(buf);
+ rv = tempOutfile->Write(buf, len, &n);
+ PR_Free(buf);
+ if (NS_FAILED(rv) || n != len)
+ {
+ status = NS_ERROR_FAILURE;
+ goto FAIL;
+ }
+ }
+
+ uint32_t flags2 = 0;
+ if (mode == nsMsgSaveAsTemplate)
+ flags2 |= nsMsgMessageFlags::Template;
+ if (mode == nsMsgDeliverNow || mode == nsMsgSendUnsent)
+ {
+ flags2 &= ~nsMsgMessageFlags::MDNReportNeeded;
+ flags2 |= nsMsgMessageFlags::MDNReportSent;
+ }
+ buf = PR_smprintf(X_MOZILLA_STATUS2_FORMAT CRLF, flags2);
+ if (buf)
+ {
+ uint32_t len = PL_strlen(buf);
+ rv = tempOutfile->Write(buf, len, &n);
+ PR_Free(buf);
+ if (NS_FAILED(rv) || n != len)
+ {
+ status = NS_ERROR_FAILURE;
+ goto FAIL;
+ }
+ }
+ tempOutfile->Write(X_MOZILLA_KEYWORDS, sizeof(X_MOZILLA_KEYWORDS) - 1, &n);
+ }
+
+ // Write out the FCC and BCC headers.
+ // When writing to the Queue file, we *must* write the FCC and BCC
+ // headers, or else that information would be lost. Because, when actually
+ // delivering the message (with "deliver now") we do FCC/BCC right away;
+ // but when queueing for later delivery, we do FCC/BCC at delivery-time.
+ //
+ // The question remains of whether FCC and BCC should be written into normal
+ // BCC folders (like the Sent Mail folder.)
+ //
+ // For FCC, there seems no point to do that; it's not information that one
+ // would want to refer back to.
+ //
+ // For BCC, the question isn't as clear. On the one hand, if I send someone
+ // a BCC'ed copy of the message, and save a copy of it for myself (with FCC)
+ // I might want to be able to look at that message later and see the list of
+ // people to whom I had BCC'ed it.
+ //
+ // On the other hand, the contents of the BCC header is sensitive
+ // information, and should perhaps not be stored at all.
+ //
+ // Thus the consultation of the #define SAVE_BCC_IN_FCC_FILE.
+ //
+ // (Note that, if there is a BCC header present in a message in some random
+ // folder, and that message is forwarded to someone, then the attachment
+ // code will strip out the BCC header before forwarding it.)
+ //
+ if ((mode == nsMsgQueueForLater || mode == nsMsgDeliverBackground ||
+ mode == nsMsgSaveAsDraft || mode == nsMsgSaveAsTemplate) &&
+ fcc_header && *fcc_header)
+ {
+ int32_t L = PL_strlen(fcc_header) + 20;
+ char *buf = (char *) PR_Malloc (L);
+ if (!buf)
+ {
+ status = NS_ERROR_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+
+ PR_snprintf(buf, L-1, "FCC: %s" CRLF, fcc_header);
+
+ uint32_t len = PL_strlen(buf);
+ rv = tempOutfile->Write(buf, len, &n);
+ if (NS_FAILED(rv) || n != len)
+ {
+ status = NS_ERROR_FAILURE;
+ goto FAIL;
+ }
+ }
+
+ //
+ // Ok, now I want to get the identity key and write it out if this is for a
+ // nsMsgQueueForLater operation!
+ //
+ if ((nsMsgQueueForLater == mode || nsMsgSaveAsDraft == mode ||
+ nsMsgDeliverBackground == mode || nsMsgSaveAsTemplate == mode) &&
+ mUserIdentity)
+ {
+ char *buf = nullptr;
+ nsCString key;
+
+ if (NS_SUCCEEDED(mUserIdentity->GetKey(key)) && !key.IsEmpty())
+ {
+ buf = PR_smprintf(HEADER_X_MOZILLA_IDENTITY_KEY ": %s" CRLF, key.get());
+ if (buf)
+ {
+ uint32_t len = strlen(buf);
+ rv = tempOutfile->Write(buf, len, &n);
+ PR_Free(buf);
+ if (NS_FAILED(rv) || n != len)
+ {
+ status = NS_ERROR_FAILURE;
+ goto FAIL;
+ }
+ }
+ }
+
+ if (!mAccountKey.IsEmpty())
+ {
+ buf = PR_smprintf(HEADER_X_MOZILLA_ACCOUNT_KEY ": %s" CRLF, mAccountKey.get());
+ if (buf)
+ {
+ uint32_t len = strlen(buf);
+ rv = tempOutfile->Write(buf, len, &n);
+ PR_Free(buf);
+ if (NS_FAILED(rv) || n != len)
+ {
+ status = NS_ERROR_FAILURE;
+ goto FAIL;
+ }
+ }
+ }
+ }
+
+ if (bcc_header && *bcc_header
+#ifndef SAVE_BCC_IN_FCC_FILE
+ && (mode == MSG_QueueForLater || mode == MSG_SaveAsDraft ||
+ mode == MSG_SaveAsTemplate)
+#endif
+ )
+ {
+ char *convBcc;
+ convBcc = nsMsgI18NEncodeMimePartIIStr(bcc_header, true,
+ mCompFields->GetCharacterSet(), sizeof("BCC: "),
+ nsMsgMIMEGetConformToStandard());
+
+ int32_t L = strlen(convBcc ? convBcc : bcc_header) + 20;
+ char *buf = (char *) PR_Malloc (L);
+ if (!buf)
+ {
+ status = NS_ERROR_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+
+ PR_snprintf(buf, L-1, "BCC: %s" CRLF, convBcc ? convBcc : bcc_header);
+ uint32_t len = strlen(buf);
+ rv = tempOutfile->Write(buf, len, &n);
+ PR_Free(buf);
+ PR_Free(convBcc);
+ if (NS_FAILED(rv) || n != len)
+ {
+ status = NS_ERROR_FAILURE;
+ goto FAIL;
+ }
+ }
+
+ //
+ // Write out the X-Mozilla-News-Host header.
+ // This is done only when writing to the queue file, not the FCC file.
+ // We need this to complement the "Newsgroups" header for the case of
+ // queueing a message for a non-default news host.
+ //
+ // Convert a URL like "snews://host:123/" to the form "host:123/secure"
+ // or "news://user@host:222" to simply "host:222".
+ //
+ if ((mode == nsMsgQueueForLater || mode == nsMsgSaveAsDraft ||
+ mode == nsMsgSaveAsTemplate || mode == nsMsgDeliverBackground) &&
+ news_url && *news_url)
+ {
+ bool secure_p = (news_url[0] == 's' || news_url[0] == 'S');
+ char *orig_hap = nsMsgParseURLHost (news_url);
+ char *host_and_port = orig_hap;
+ if (host_and_port)
+ {
+ // There may be authinfo at the front of the host - it could be of
+ // the form "user:password@host:port", so take off everything before
+ // the first at-sign. We don't want to store authinfo in the queue
+ // folder, I guess, but would want it to be re-prompted-for at
+ // delivery-time.
+ //
+ char *at = PL_strchr (host_and_port, '@');
+ if (at)
+ host_and_port = at + 1;
+ }
+
+ if ((host_and_port && *host_and_port) || !secure_p)
+ {
+ char *line = PR_smprintf(X_MOZILLA_NEWSHOST ": %s%s" CRLF,
+ host_and_port ? host_and_port : "",
+ secure_p ? "/secure" : "");
+ PR_FREEIF(orig_hap);
+ if (!line)
+ {
+ status = NS_ERROR_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+
+ uint32_t len = PL_strlen(line);
+ rv = tempOutfile->Write(line, len, &n);
+ PR_Free(line);
+ if (NS_FAILED(rv) || n != len)
+ {
+ status = NS_ERROR_FAILURE;
+ goto FAIL;
+ }
+ }
+
+ PR_Free(orig_hap);
+ }
+
+ //
+ // Read from the message file, and write to the FCC or Queue file.
+ // There are two tricky parts: the first is that the message file
+ // uses CRLF, and the FCC file should use LINEBREAK. The second
+ // is that the message file may have lines beginning with "From "
+ // but the FCC file must have those lines mangled.
+ //
+ // It's unfortunate that we end up writing the FCC file a line
+ // at a time, but it's the easiest way...
+ //
+ uint64_t available;
+ rv = inputFile->Available(&available);
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (available > 0)
+ {
+ // check *ibuffer in case that ibuffer isn't big enough
+ uint32_t readCount;
+ rv = inputFile->Read(ibuffer, ibuffer_size, &readCount);
+ if (NS_FAILED(rv) || readCount == 0 || *ibuffer == 0)
+ {
+ status = NS_ERROR_FAILURE;
+ goto FAIL;
+ }
+
+ rv = tempOutfile->Write(ibuffer, readCount, &n);
+ if (NS_FAILED(rv) || n != readCount) // write failed
+ {
+ status = NS_MSG_ERROR_WRITING_FILE;
+ goto FAIL;
+ }
+
+ rv = inputFile->Available(&available);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+FAIL:
+ PR_Free(ibuffer);
+
+ if (NS_FAILED(tempOutfile->Flush()))
+ status = NS_MSG_ERROR_WRITING_FILE;
+
+ tempOutfile->Close();
+
+ if (inputFile)
+ inputFile->Close();
+
+
+ // here we should clone mCopyFile, since it has changed on disk.
+ nsCOMPtr <nsIFile> clonedFile;
+ mCopyFile->Clone(getter_AddRefs(clonedFile));
+ mCopyFile = clonedFile;
+
+ // When we get here, we have to see if we have been successful so far.
+ // If we have, then we should start up the async copy service operation.
+ // If we weren't successful, then we should just return the error and
+ // bail out.
+ if (NS_SUCCEEDED(status))
+ {
+ // If we are here, time to start the async copy service operation!
+ status = StartMessageCopyOperation(mCopyFile, mode, turi);
+ }
+ return status;
+}
+
+//
+// This is pretty much a wrapper to the functionality that lives in the
+// nsMsgCopy class
+//
+nsresult
+nsMsgComposeAndSend::StartMessageCopyOperation(nsIFile *aFile,
+ nsMsgDeliverMode mode,
+ const nsCString& dest_uri)
+{
+ mCopyObj = new nsMsgCopy();
+ if (!mCopyObj)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ //
+ // Actually, we need to pick up the proper folder from the prefs and not
+ // default to the default "Flagged" folder choices
+ //
+ nsresult rv;
+ if (!dest_uri.IsEmpty())
+ m_folderName = dest_uri;
+ else
+ GetFolderURIFromUserPrefs(mode, mUserIdentity, m_folderName);
+
+ if (mListener)
+ mListener->OnGetDraftFolderURI(m_folderName.get());
+
+ rv = mCopyObj->StartCopyOperation(mUserIdentity, aFile, mode,
+ this, m_folderName.get(), mMsgToReplace);
+ return rv;
+}
+
+//I'm getting this each time without holding onto the feedback so that 3 pane windows can be closed
+//without any chance of crashing due to holding onto a deleted feedback.
+nsresult
+nsMsgComposeAndSend::SetStatusMessage(const nsString &aMsgString)
+{
+ if (mSendProgress)
+ mSendProgress->OnStatusChange(nullptr, nullptr, NS_OK, aMsgString.get());
+ return NS_OK;
+}
+
+// For GUI notification...
+nsresult
+nsMsgComposeAndSend::SetGUINotificationState(bool aEnableFlag)
+{
+ mGUINotificationEnabled = aEnableFlag;
+ return NS_OK;
+}
+
+/* readonly attribute nsIMsgSendReport sendReport; */
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetSendReport(nsIMsgSendReport * *aSendReport)
+{
+ NS_ENSURE_ARG_POINTER(aSendReport);
+ NS_IF_ADDREF(*aSendReport = mSendReport);
+ return NS_OK;
+}
+
+nsresult nsMsgComposeAndSend::Abort()
+{
+ uint32_t i;
+ nsresult rv;
+
+ if (mAbortInProcess)
+ return NS_OK;
+
+ mAbortInProcess = true;
+
+ if (m_plaintext)
+ rv = m_plaintext->Abort();
+
+ for (i = 0; i < m_attachment_count; i ++)
+ {
+ nsMsgAttachmentHandler *ma = m_attachments[i];
+ if (ma)
+ rv = ma->Abort();
+ }
+
+ /* stop the current running url */
+ if (mRunningRequest)
+ {
+ mRunningRequest->Cancel(NS_ERROR_ABORT);
+ mRunningRequest = nullptr;
+ }
+
+ if (mCopyObj)
+ {
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ copyService->NotifyCompletion(mCopyFile, mCopyObj->mDstFolder, NS_ERROR_ABORT);
+ }
+ mAbortInProcess = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::GetProcessAttachmentsSynchronously(bool *_retval)
+{
+ NS_ENSURE_ARG(_retval);
+ *_retval = m_be_synchronous_p;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::GetAttachmentHandlers(nsTArray<RefPtr<nsMsgAttachmentHandler>> **_retval)
+{
+ NS_ENSURE_ARG(_retval);
+ *_retval = &m_attachments;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::GetAttachmentCount(uint32_t *aAttachmentCount)
+{
+ NS_ENSURE_ARG(aAttachmentCount);
+ *aAttachmentCount = m_attachment_count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::GetPendingAttachmentCount(uint32_t *aPendingAttachmentCount)
+{
+ NS_ENSURE_ARG(aPendingAttachmentCount);
+ *aPendingAttachmentCount = m_attachment_pending_count;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeAndSend::SetPendingAttachmentCount(uint32_t aPendingAttachmentCount)
+{
+ m_attachment_pending_count = aPendingAttachmentCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::GetDeliveryMode(nsMsgDeliverMode *aDeliveryMode)
+{
+ NS_ENSURE_ARG(aDeliveryMode);
+ *aDeliveryMode = m_deliver_mode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::GetProgress(nsIMsgProgress **_retval)
+{
+ NS_ENSURE_ARG(_retval);
+ NS_IF_ADDREF(*_retval = mSendProgress);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::GetOutputStream(nsIOutputStream **_retval)
+{
+ NS_ENSURE_ARG(_retval);
+ NS_IF_ADDREF(*_retval = mOutputFile);
+ return NS_OK;
+}
+
+
+/* [noscript] attribute nsIURI runningURL; */
+NS_IMETHODIMP nsMsgComposeAndSend::GetRunningRequest(nsIRequest **request)
+{
+ NS_ENSURE_ARG(request);
+ NS_IF_ADDREF(*request = mRunningRequest);
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeAndSend::SetRunningRequest(nsIRequest *request)
+{
+ mRunningRequest = request;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::GetStatus(nsresult *aStatus)
+{
+ NS_ENSURE_ARG(aStatus);
+ *aStatus = m_status;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::SetStatus(nsresult aStatus)
+{
+ m_status = aStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::GetCryptoclosure(nsIMsgComposeSecure ** aCryptoclosure)
+{
+ NS_ENSURE_ARG(aCryptoclosure);
+ NS_IF_ADDREF(*aCryptoclosure = m_crypto_closure);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeAndSend::SetCryptoclosure(nsIMsgComposeSecure * aCryptoclosure)
+{
+ m_crypto_closure = aCryptoclosure;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetSendCompFields(nsIMsgCompFields** aCompFields)
+{
+ NS_ENSURE_ARG_POINTER(aCompFields);
+ nsCOMPtr<nsIMsgCompFields> qiCompFields(mCompFields);
+ NS_ENSURE_STATE(qiCompFields);
+ qiCompFields.forget(aCompFields);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetSendBody(nsAString& aBody)
+{
+ nsCString charSet;
+ if (mCompFields)
+ mCompFields->GetCharacterSet(getter_Copies(charSet));
+ if (!m_attachment1_body) {
+ aBody.Truncate();
+ return NS_OK;
+ }
+ return ConvertToUnicode(charSet.get(), m_attachment1_body, aBody);
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetSendBodyType(nsACString& aBodyType)
+{
+ if (m_attachment1_type && *m_attachment1_type)
+ aBodyType.Assign(nsDependentCString(m_attachment1_type));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetIdentity(nsIMsgIdentity **aIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ *aIdentity = mUserIdentity;
+ NS_IF_ADDREF(*aIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetAttachment(uint32_t aIndex,
+ nsIMsgAttachmentHandler **aAttachment)
+{
+ NS_ENSURE_ARG_POINTER(aAttachment);
+ if (aIndex >= m_attachment_count)
+ return NS_ERROR_ILLEGAL_VALUE;
+ *aAttachment = m_attachments[aIndex];
+ NS_IF_ADDREF(*aAttachment);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::SetSavedToFolderName(const nsAString& aName)
+{
+ mSavedToFolderName.Assign(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetSavedToFolderName(nsAString& aName)
+{
+ aName.Assign(mSavedToFolderName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::SetDontDeliver(bool aDontDeliver)
+{
+ m_dont_deliver_p = aDontDeliver;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsMsgComposeAndSend::GetDontDeliver(bool *aDontDeliver)
+{
+ NS_ENSURE_ARG_POINTER(aDontDeliver);
+ *aDontDeliver = m_dont_deliver_p;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgAttachmentData, nsIMsgAttachmentData)
+
+nsMsgAttachmentData::nsMsgAttachmentData() : m_size(0), m_sizeExternalStr("-1"),
+ m_isExternalAttachment(false), m_isExternalLinkAttachment(false),
+ m_isDownloaded(false), m_hasFilename(false), m_displayableInline(false)
+{
+}
+
+nsMsgAttachmentData::~nsMsgAttachmentData()
+{
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetUrl(nsIURI **aUrl)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ NS_IF_ADDREF(*aUrl = m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetUrl(nsIURI *aUrl)
+{
+ m_url = aUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetDesiredType(nsACString &aDesiredType)
+{
+ aDesiredType = m_desiredType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetDesiredType(const nsACString &aDesiredType)
+{
+ m_desiredType = aDesiredType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetRealType(nsACString &aRealType)
+{
+ aRealType = m_realType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetRealType(const nsACString &aRealType)
+{
+ m_realType = aRealType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetRealEncoding(nsACString &aRealEncoding)
+{
+ aRealEncoding = m_realEncoding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetRealEncoding(const nsACString &aRealEncoding)
+{
+ m_realEncoding = aRealEncoding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetRealName(nsACString &aRealName)
+{
+ aRealName = m_realName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetRealName(const nsACString &aRealName)
+{
+ m_realName = aRealName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetDescription(nsACString &aDescription)
+{
+ aDescription = m_description;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetDescription(const nsACString &aDescription)
+{
+ m_description = aDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetXMacType(nsACString & aXMacType)
+{
+ aXMacType = m_xMacType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetXMacType(const nsACString & aXMacType)
+{
+ m_xMacType = aXMacType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetXMacCreator(nsACString & aXMacCreator)
+{
+ aXMacCreator = m_xMacCreator;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetXMacCreator(const nsACString & aXMacCreator)
+{
+ m_xMacCreator = aXMacCreator;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgAttachedFile, nsIMsgAttachedFile)
+
+nsMsgAttachedFile::nsMsgAttachedFile() : m_size(0), m_unprintableCount(0),
+ m_highbitCount(0), m_ctlCount(0), m_nullCount(0), m_maxLineLength(0)
+{
+}
+
+nsMsgAttachedFile::~nsMsgAttachedFile()
+{
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetOrigUrl(nsIURI **aOrigUrl)
+{
+ NS_ENSURE_ARG_POINTER(aOrigUrl);
+ NS_IF_ADDREF(*aOrigUrl = m_origUrl);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetOrigUrl(nsIURI *aOrigUrl)
+{
+ m_origUrl = aOrigUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetTmpFile(nsIFile **aTmpFile)
+{
+ NS_ENSURE_ARG_POINTER(aTmpFile);
+ NS_IF_ADDREF(*aTmpFile = m_tmpFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetTmpFile(nsIFile *aTmpFile)
+{
+ m_tmpFile = aTmpFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetType(nsACString &aType)
+{
+ aType = m_type;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetType(const nsACString &aType)
+{
+ m_type = aType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetEncoding(nsACString &aEncoding)
+{
+ aEncoding = m_encoding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetEncoding(const nsACString &aEncoding)
+{
+ m_encoding = aEncoding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetDescription(nsACString &aDescription)
+{
+ aDescription = m_description;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetDescription(const nsACString &aDescription)
+{
+ m_description = aDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetCloudPartInfo(nsACString &aCloudPartInfo)
+{
+ aCloudPartInfo = m_cloudPartInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetCloudPartInfo(const nsACString &aCloudPartInfo)
+{
+ m_cloudPartInfo = aCloudPartInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetXMacType(nsACString & aXMacType)
+{
+ aXMacType = m_xMacType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetXMacType(const nsACString & aXMacType)
+{
+ m_xMacType = aXMacType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetXMacCreator(nsACString & aXMacCreator)
+{
+ aXMacCreator = m_xMacCreator;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetXMacCreator(const nsACString & aXMacCreator)
+{
+ m_xMacCreator = aXMacCreator;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetRealName(nsACString & aRealName)
+{
+ aRealName = m_realName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetRealName(const nsACString & aRealName)
+{
+ m_realName = aRealName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetSize(uint32_t *aSize)
+{
+ NS_ENSURE_ARG_POINTER(aSize);
+ *aSize = m_size;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetSize(uint32_t aSize)
+{
+ m_size = aSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetUnprintableCount(uint32_t *aUnprintableCount)
+{
+ NS_ENSURE_ARG_POINTER(aUnprintableCount);
+ *aUnprintableCount = m_unprintableCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetUnprintableCount(uint32_t aUnprintableCount)
+{
+ m_unprintableCount = aUnprintableCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetHighbitCount(uint32_t *aHighbitCount)
+{
+ NS_ENSURE_ARG_POINTER(aHighbitCount);
+ *aHighbitCount = m_highbitCount;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgAttachedFile::SetHighbitCount(uint32_t aHighbitCount)
+{
+ m_highbitCount = aHighbitCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetCtlCount(uint32_t *aCtlCount)
+{
+ NS_ENSURE_ARG_POINTER(aCtlCount);
+ *aCtlCount = m_ctlCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetCtlCount(uint32_t aCtlCount)
+{
+ m_ctlCount = aCtlCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetNullCount(uint32_t *aNullCount)
+{
+ NS_ENSURE_ARG_POINTER(aNullCount);
+ *aNullCount = m_nullCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetNullCount(uint32_t aNullCount)
+{
+ m_nullCount = aNullCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetMaxLineLength(uint32_t *aMaxLineLength)
+{
+ NS_ENSURE_ARG_POINTER(aMaxLineLength);
+ *aMaxLineLength = m_maxLineLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetMaxLineLength(uint32_t aMaxLineLength)
+{
+ m_maxLineLength = aMaxLineLength;
+ return NS_OK;
+}
diff --git a/mailnews/compose/src/nsMsgSend.h b/mailnews/compose/src/nsMsgSend.h
new file mode 100644
index 000000000..558c8e53e
--- /dev/null
+++ b/mailnews/compose/src/nsMsgSend.h
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __MSGSEND_H__
+#define __MSGSEND_H__
+
+/* Asynchronous mailing of messages with attached URLs.
+
+ - If there are any attachments, start their URLs going, and write each
+ of them to a temp file.
+
+ - While writing to their files, examine the data going by and decide
+ what kind of encoding, if any, they need. Also remember their content
+ types.
+
+ - Once that URLs has been saved to a temp file (or, if there were no
+ attachments) generate a final temp file, of the actual message:
+
+ - Generate a string of the headers.
+ - Open the final temp file.
+ - Write the headers.
+ - Examine the first part, and decide whether to encode it.
+ - Write the first part to the file, possibly encoded.
+ - Write the second and subsequent parts to the file, possibly encoded.
+ (Open the first temp file and copy it to the final temp file, and so
+ on, through an encoding filter.)
+
+ - Delete the attachment temp file(s) as we finish with them.
+ - Close the final temp file.
+ - Open the news: url.
+ - Send the final temp file to NNTP.
+ If there's an error, run the callback with "failure" status.
+ - If mail succeeded, open the mailto: url.
+ - Send the final temp file to SMTP.
+ If there's an error, run the callback with "failure" status.
+ - Otherwise, run the callback with "success" status.
+ - Free everything, delete the final temp file.
+
+ The theory behind the encoding logic:
+ =====================================
+
+ If the document is of type text/html, and the user has asked to attach it
+ as source or postscript, it will be run through the appropriate converter
+ (which will result in a document of type text/plain.)
+
+ An attachment will be encoded if:
+
+ - it is of a non-text type (in which case we will use base64); or
+ - The "use QP" option has been selected and high-bit characters exist; or
+ - any NULLs exist in the document; or
+ - any line is longer than 990 bytes (see LINELENGTH_ENCODING_THRESHOLD below)
+ and it is not of type message/rfc822.
+
+ - If we are encoding, and more than 10% of the document consists of
+ non-ASCII characters, then we always use base64 instead of QP.
+
+ We eschew quoted-printable in favor of base64 for documents which are likely
+ to always be binary (images, sound) because, on the off chance that a GIF
+ file (for example) might contain primarily bytes in the ASCII range, using
+ the quoted-printable representation might cause corruption due to the
+ translation of CR or LF to CRLF. So, when we don't know that the document
+ has "lines", we don't use quoted-printable.
+ */
+
+/* For maximal compatibility, it helps to emit both
+ Content-Type: <type>; name="<original-file-name>"
+ as well as
+ Content-Disposition: inline; filename="<original-file-name>"
+
+ The lossage here is, RFC1341 defined the "name" parameter to Content-Type,
+ but then RFC1521 deprecated it in anticipation of RFC1806, which defines
+ Content-Type and the "filename" parameter. But, RFC1521 is "Standards Track"
+ while RFC1806 is still "Experimental." So, it's probably best to just
+ implement both.
+ */
+#define EMIT_NAME_IN_CONTENT_TYPE
+
+/* Whether the contents of the BCC header should be preserved in the FCC'ed
+ copy of a message. See comments below, in mime_do_fcc_1().
+ */
+#define SAVE_BCC_IN_FCC_FILE
+
+/* When attaching an HTML document, one must indicate the original URL of
+ that document, if the receiver is to have any chance of being able to
+ retreive and display the inline images, or to click on any links in the
+ HTML.
+
+ The way we have done this in the past is by inserting a <BASE> tag as the
+ first line of all HTML documents we attach. (This is kind of bad in that
+ we're actually modifying the document, and it really isn't our place to
+ do that.)
+
+ The sanctioned *new* way of doing this is to insert a Content-Base header
+ field on the attachment. This is (will be) a part of the forthcoming MHTML
+ spec.
+
+ If GENERATE_CONTENT_BASE, we generate a Content-Base header.
+
+ We used to have a MANGLE_HTML_ATTACHMENTS_WITH_BASE_TAG symbol that we
+ defined, which added a BASE tag to the bodies. We stopped doing this in
+ 4.0. */
+#define GENERATE_CONTENT_BASE
+
+
+//
+// Necessary includes
+//
+#include "nsCOMPtr.h"
+#include "nsIMsgSend.h"
+#include "nsIStringBundle.h"
+#include "msgCore.h"
+#include "prprf.h"
+#include "nsIOutputStream.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIURL.h"
+#include "nsMsgAttachmentHandler.h"
+#include "nsMsgCompFields.h"
+#include "nsIMsgSendListener.h"
+#include "nsIDOMNode.h"
+#include "nsIEditor.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIMsgIdentity.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgIdentity.h"
+#include "nsWeakReference.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMWindow.h"
+#include "nsIMsgComposeSecure.h"
+#include "nsAutoPtr.h"
+#include "nsMsgAttachmentData.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgOperationListener.h"
+
+//
+// Some necessary defines...
+//
+#define MIME_BUFFER_SIZE 4096 // must be greater than 1000
+ // SMTP (RFC821) limit
+// Maximum number of bytes we allow in a line before we force
+// encoding to base64 if not already QR-encoded or of type message/rfc822.
+#define LINELENGTH_ENCODING_THRESHOLD 990
+
+//
+// Utilities for string handling
+//
+#define PUSH_STRING(S) \
+ do { PL_strcpy (buffer_tail, S); buffer_tail += PL_strlen (S); } while(0)
+#define PUSH_STRINGN(S,N) \
+ do { memcpy(buffer_tail, (S), (N)); buffer_tail += (N); } while(0)
+#define PUSH_NEWLINE() \
+ do { *buffer_tail++ = '\r'; *buffer_tail++ = '\n'; *buffer_tail = '\0'; } while(0)
+
+//
+// Forward declarations...
+//
+class nsMsgSendPart;
+class nsMsgCopy;
+class nsIPrompt;
+class nsIInterfaceRequestor;
+
+namespace mozilla {
+namespace mailnews {
+class MimeEncoder;
+}
+}
+
+class nsMsgComposeAndSend : public nsIMsgSend,
+ public nsIMsgOperationListener,
+ public nsSupportsWeakReference
+{
+ typedef mozilla::mailnews::MimeEncoder MimeEncoder;
+public:
+ //
+ // Define QueryInterface, AddRef and Release for this class
+ //
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGSEND
+ NS_DECL_NSIMSGOPERATIONLISTENER
+
+ nsMsgComposeAndSend();
+
+
+ // Delivery and completion callback routines...
+ NS_IMETHOD DeliverMessage();
+ NS_IMETHOD DeliverFileAsMail();
+ NS_IMETHOD DeliverFileAsNews();
+ void DoDeliveryExitProcessing(nsIURI * aUrl, nsresult aExitCode, bool aCheckForMail);
+ nsresult FormatStringWithSMTPHostNameByName(const char16_t* aMsgName, char16_t **aString);
+
+ nsresult DoFcc();
+ nsresult StartMessageCopyOperation(nsIFile *aFileSpec,
+ nsMsgDeliverMode mode,
+ const nsCString& dest_uri);
+
+
+ nsresult SendToMagicFolder(nsMsgDeliverMode flag);
+
+ // Check to see if it's ok to save msgs to the configured folder.
+ bool CanSaveMessagesToFolder(const char *folderURL);
+
+ //
+ // FCC operations...
+ //
+ nsresult MimeDoFCC (nsIFile *input_file,
+ nsMsgDeliverMode mode,
+ const char *bcc_header,
+ const char *fcc_header,
+ const char *news_url);
+
+ // Init() will allow for either message creation without delivery or full
+ // message creation and send operations
+ //
+ nsresult Init(
+ nsIMsgIdentity *aUserIdentity,
+ const char *aAccountKey,
+ nsMsgCompFields *fields,
+ nsIFile *sendFile,
+ bool digest_p,
+ bool dont_deliver_p,
+ nsMsgDeliverMode mode,
+ nsIMsgDBHdr *msgToReplace,
+ const char *attachment1_type,
+ const nsACString &attachment1_body,
+ nsIArray *attachments,
+ nsIArray *preloaded_attachments,
+ const char *password,
+ const nsACString &aOriginalMsgURI,
+ MSG_ComposeType aType);
+
+ //
+ // Setup the composition fields
+ //
+ nsresult InitCompositionFields(nsMsgCompFields *fields,
+ const nsACString &aOriginalMsgURI,
+ MSG_ComposeType aType);
+
+ NS_IMETHOD GetBodyFromEditor();
+
+
+ //
+ // Attachment processing...
+ //
+ nsresult HackAttachments(nsIArray *attachments,
+ nsIArray *preloaded_attachments);
+ nsresult CountCompFieldAttachments();
+ nsresult AddCompFieldLocalAttachments();
+ nsresult AddCompFieldRemoteAttachments(uint32_t aStartLocation, int32_t *aMailboxCount, int32_t *aNewsCount);
+
+ // Deal with multipart related data
+ nsresult ProcessMultipartRelated(int32_t *aMailboxCount, int32_t *aNewsCount);
+ nsresult GetEmbeddedObjectInfo(nsIDOMNode *node, nsMsgAttachmentData *attachment, bool *acceptObject);
+ uint32_t GetMultipartRelatedCount(bool forceToBeCalculated = false);
+ nsCOMPtr<nsIArray> mEmbeddedObjectList; // it's initialized when calling GetMultipartRelatedCount
+
+ // Body processing
+ nsresult SnarfAndCopyBody(const nsACString &attachment1_body,
+ const char *attachment1_type);
+
+ int32_t PreProcessPart(nsMsgAttachmentHandler *ma,
+ nsMsgSendPart *toppart); // The very top most container of the message
+ // For part processing
+
+ nsresult SetStatusMessage(const nsString &aMsgString); // Status message method
+
+ //
+ // All vars necessary for this implementation
+ //
+ nsMsgKey m_messageKey; // jt -- Draft/Template support; newly created key
+ nsCOMPtr<nsIMsgIdentity> mUserIdentity;
+ nsCString mAccountKey;
+ RefPtr<nsMsgCompFields> mCompFields; // All needed composition fields (header, etc...)
+ nsCOMPtr<nsIFile> mTempFile; // our temporary file
+
+ nsCOMPtr<nsIOutputStream> mOutputFile; // the actual output file stream
+ uint32_t mMessageWarningSize; // Warn if a message is over this size!
+
+ bool m_dont_deliver_p; // If set, we just return the nsIFile of the file
+ // created, instead of actually delivering message.
+ nsMsgDeliverMode m_deliver_mode; // nsMsgDeliverNow, nsMsgQueueForLater, nsMsgSaveAsDraft,
+ // nsMsgSaveAsTemplate and nsMsgSendUnsent
+ nsCOMPtr<nsIMsgDBHdr> mMsgToReplace; // If the mode is nsMsgSaveAsDraft, this is the message it will
+ // replace
+ nsString mSavedToFolderName; // Name of folder we're saving to, used when
+ // displaying error on save.
+ // These are needed for callbacks to the FE...
+ nsCOMPtr<nsPIDOMWindowOuter> mParentWindow;
+ nsCOMPtr<nsIMsgProgress> mSendProgress;
+ nsCOMPtr<nsIMsgSendListener> mListener;
+ nsCOMPtr<nsIMsgStatusFeedback> mStatusFeedback;
+ nsCOMPtr<nsIRequest> mRunningRequest;
+ bool mSendMailAlso;
+ nsCOMPtr<nsIFile> mReturnFile; // a holder for file spec's to be returned to caller
+
+ // File where we stored our HTML so that we could make the plaintext form.
+ nsCOMPtr<nsIFile> mHTMLFile;
+
+ // Variable for storing the draft name;
+ nsCString m_folderName;
+
+ // mapping between editor dom node indexes and saved mime part numbers.
+ nsTArray<nsCString> m_partNumbers;
+ //
+ // These variables are needed for message Copy operations!
+ //
+ nsCOMPtr<nsIFile> mCopyFile;
+ nsCOMPtr<nsIFile> mCopyFile2;
+ RefPtr<nsMsgCopy> mCopyObj;
+ bool mNeedToPerformSecondFCC;
+ bool mPerformingSecondFCC;
+
+ // For MHTML message creation
+ nsCOMPtr<nsIEditor> mEditor;
+
+ //
+ // The first attachment, if any (typed in by the user.)
+ //
+ char *m_attachment1_type;
+ char *m_attachment1_encoding;
+ nsAutoPtr<MimeEncoder> m_attachment1_encoder;
+ char *m_attachment1_body;
+ uint32_t m_attachment1_body_length;
+ char *mOriginalHTMLBody;
+
+ // The plaintext form of the first attachment, if needed.
+ RefPtr<nsMsgAttachmentHandler> m_plaintext;
+
+ // The multipart/related save object for HTML text.
+ nsMsgSendPart *m_related_part;
+ nsMsgSendPart *m_related_body_part;
+
+ //
+ // Subsequent attachments, if any.
+ //
+ uint32_t m_attachment_count;
+ uint32_t m_attachment_pending_count;
+ nsTArray< RefPtr<nsMsgAttachmentHandler> > m_attachments;
+ nsresult m_status; // in case some attachments fail but not all
+
+ uint32_t mPreloadedAttachmentCount;
+ uint32_t mRemoteAttachmentCount;
+ int32_t mMultipartRelatedAttachmentCount; // the number of mpart related attachments, -1 means it has not been yet initialized
+
+ uint32_t mCompFieldLocalAttachments; // the number of file:// attachments in the comp fields
+ uint32_t mCompFieldRemoteAttachments; // the number of remote attachments in the comp fields
+
+ //
+ // attachment states and other info...
+ //
+ bool m_pre_snarfed_attachments_p; // If true, then the attachments were
+ // loaded by in the background and therefore
+ // we shouldn't delete the tmp files (but should
+ // leave that to the caller.)
+
+ bool m_digest_p; // Whether to be multipart/digest instead of
+ // multipart/mixed.
+
+ bool m_be_synchronous_p; // If true, we will load one URL after another,
+ // rather than starting all URLs going at once
+ // and letting them load in parallel. This is
+ // more efficient if (for example) all URLs are
+ // known to be coming from the same news server
+ // or mailbox: loading them in parallel would
+ // cause multiple connections to the news
+ // server to be opened, or would cause much seek()ing.
+
+ bool mGUINotificationEnabled; // Should we throw up the GUI alerts on errors?
+ bool mAbortInProcess; // Used by Abort to avoid reentrance.
+
+ nsCOMPtr<nsIMsgComposeSecure> m_crypto_closure;
+
+protected:
+ nsCOMPtr<nsIStringBundle> mComposeBundle;
+ nsresult GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks);
+
+ virtual ~nsMsgComposeAndSend();
+ nsresult FilterSentMessage();
+ nsresult MaybePerformSecondFCC(nsresult aStatus);
+
+ // generates a message id for our message, if necessary
+ void GenerateMessageId( );
+
+ // add default custom headers to the message
+ nsresult AddDefaultCustomHeaders();
+
+ // add Mail-Followup-To and Mail-Reply-To header
+ nsresult AddMailFollowupToHeader();
+ nsresult AddMailReplyToHeader();
+ nsresult AddXForwardedMessageIdHeader();
+
+ nsCOMPtr<nsIMsgSendReport> mSendReport;
+ nsCString mSmtpPassword; // store the smtp Password use during a send
+};
+
+//
+// These C routines should only be used by the nsMsgSendPart class.
+//
+extern nsresult mime_write_message_body(nsIMsgSend *state, const char *buf, uint32_t size);
+extern char *mime_get_stream_write_buffer(void);
+extern nsresult mime_encoder_output_fn (const char *buf, int32_t size, void *closure);
+extern bool UseQuotedPrintable(void);
+
+#endif /* __MSGSEND_H__ */
diff --git a/mailnews/compose/src/nsMsgSendLater.cpp b/mailnews/compose/src/nsMsgSendLater.cpp
new file mode 100644
index 000000000..ad0fb2031
--- /dev/null
+++ b/mailnews/compose/src/nsMsgSendLater.cpp
@@ -0,0 +1,1559 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsMsgSendLater.h"
+#include "nsMsgCopy.h"
+#include "nsIMsgSend.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgCompCID.h"
+#include "nsMsgCompUtils.h"
+#include "nsMsgUtils.h"
+#include "nsMailHeaders.h"
+#include "nsMsgPrompts.h"
+#include "nsISmtpUrl.h"
+#include "nsIChannel.h"
+#include "nsNetUtil.h"
+#include "prlog.h"
+#include "prmem.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgMimeCID.h"
+#include "nsComposeStrings.h"
+#include "nsIMutableArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsIObserverService.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgDatabase.h"
+#include "mozilla/Services.h"
+#include "nsArrayUtils.h"
+
+// Consts for checking and sending mail in milliseconds
+
+// 1 second from mail into the unsent messages folder to initially trying to
+// send it.
+const uint32_t kInitialMessageSendTime = 1000;
+
+NS_IMPL_ISUPPORTS(nsMsgSendLater,
+ nsIMsgSendLater,
+ nsIFolderListener,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIObserver,
+ nsIUrlListener,
+ nsIMsgShutdownTask)
+
+nsMsgSendLater::nsMsgSendLater()
+{
+ mSendingMessages = false;
+ mTimerSet = false;
+ mTotalSentSuccessfully = 0;
+ mTotalSendCount = 0;
+ mLeftoverBuffer = nullptr;
+
+ m_to = nullptr;
+ m_bcc = nullptr;
+ m_fcc = nullptr;
+ m_newsgroups = nullptr;
+ m_newshost = nullptr;
+ m_headers = nullptr;
+ m_flags = 0;
+ m_headersFP = 0;
+ m_inhead = true;
+ m_headersPosition = 0;
+
+ m_bytesRead = 0;
+ m_position = 0;
+ m_flagsPosition = 0;
+ m_headersSize = 0;
+
+ mIdentityKey = nullptr;
+ mAccountKey = nullptr;
+}
+
+nsMsgSendLater::~nsMsgSendLater()
+{
+ PR_Free(m_to);
+ PR_Free(m_fcc);
+ PR_Free(m_bcc);
+ PR_Free(m_newsgroups);
+ PR_Free(m_newshost);
+ PR_Free(m_headers);
+ PR_Free(mLeftoverBuffer);
+ PR_Free(mIdentityKey);
+ PR_Free(mAccountKey);
+}
+
+nsresult
+nsMsgSendLater::Init()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool sendInBackground;
+ rv = prefs->GetBoolPref("mailnews.sendInBackground", &sendInBackground);
+ // If we're not sending in the background, don't do anything else
+ if (NS_FAILED(rv) || !sendInBackground)
+ return NS_OK;
+
+ // We need to know when we're shutting down.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ rv = observerService->AddObserver(this, "xpcom-shutdown", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->AddObserver(this, "quit-application", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->AddObserver(this, "msg-shutdown", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Subscribe to the unsent messages folder
+ // XXX This code should be set up for multiple unsent folders, however we
+ // don't support that at the moment, so for now just assume one folder.
+ rv = GetUnsentMessagesFolder(nullptr, getter_AddRefs(mMessageFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mMessageFolder->AddFolderListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX may want to send messages X seconds after startup if there are any.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::Observe(nsISupports *aSubject, const char* aTopic,
+ const char16_t *aData)
+{
+ if (aSubject == mTimer && !strcmp(aTopic, "timer-callback"))
+ {
+ if (mTimer)
+ mTimer->Cancel();
+ else
+ NS_ERROR("mTimer was null in nsMsgSendLater::Observe");
+
+ mTimerSet = false;
+ // If we've already started a send since the timer fired, don't start
+ // another
+ if (!mSendingMessages)
+ InternalSendMessages(false, nullptr);
+ }
+ else if (!strcmp(aTopic, "quit-application"))
+ {
+ // If the timer is set, cancel it - we're quitting, the shutdown service
+ // interfaces will sort out sending etc.
+ if (mTimer)
+ mTimer->Cancel();
+
+ mTimerSet = false;
+ }
+ else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID))
+ {
+ // We're shutting down. Unsubscribe from the unsentFolder notifications
+ // they aren't any use to us now, we don't want to start sending more
+ // messages.
+ nsresult rv;
+ if (mMessageFolder)
+ {
+ rv = mMessageFolder->RemoveFolderListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Now remove ourselves from the observer service as well.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ rv = observerService->RemoveObserver(this, "xpcom-shutdown");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->RemoveObserver(this, "quit-application");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->RemoveObserver(this, "msg-shutdown");
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::SetStatusFeedback(nsIMsgStatusFeedback *aFeedback)
+{
+ mFeedback = aFeedback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::GetStatusFeedback(nsIMsgStatusFeedback **aFeedback)
+{
+ NS_ENSURE_ARG_POINTER(aFeedback);
+ NS_IF_ADDREF(*aFeedback = mFeedback);
+ return NS_OK;
+}
+
+// Stream is done...drive on!
+NS_IMETHODIMP
+nsMsgSendLater::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
+{
+ nsresult rv;
+
+ // First, this shouldn't happen, but if
+ // it does, flush the buffer and move on.
+ if (mLeftoverBuffer)
+ {
+ DeliverQueuedLine(mLeftoverBuffer, PL_strlen(mLeftoverBuffer));
+ }
+
+ if (mOutFile)
+ mOutFile->Close();
+
+ // See if we succeeded on reading the message from the message store?
+ //
+ if (NS_SUCCEEDED(status))
+ {
+ // Message is done...send it!
+ rv = CompleteMailFileSend();
+
+#ifdef NS_DEBUG
+ printf("nsMsgSendLater: Success on getting message...\n");
+#endif
+
+ // If the send operation failed..try the next one...
+ if (NS_FAILED(rv))
+ {
+ rv = StartNextMailFileSend(rv);
+ if (NS_FAILED(rv))
+ EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully);
+ }
+ }
+ else
+ {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if(!channel) return NS_ERROR_FAILURE;
+
+ // extract the prompt object to use for the alert from the url....
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIPrompt> promptObject;
+ if (channel)
+ {
+ channel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsISmtpUrl> smtpUrl (do_QueryInterface(uri));
+ if (smtpUrl)
+ smtpUrl->GetPrompt(getter_AddRefs(promptObject));
+ }
+ nsMsgDisplayMessageByName(promptObject, u"errorQueuedDeliveryFailed");
+
+ // Getting the data failed, but we will still keep trying to send the rest...
+ rv = StartNextMailFileSend(status);
+ if (NS_FAILED(rv))
+ EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully);
+ }
+
+ return rv;
+}
+
+char *
+FindEOL(char *inBuf, char *buf_end)
+{
+ char *buf = inBuf;
+ char *findLoc = nullptr;
+
+ while (buf <= buf_end)
+ if (*buf == 0)
+ return buf;
+ else if ( (*buf == '\n') || (*buf == '\r') )
+ {
+ findLoc = buf;
+ break;
+ }
+ else
+ ++buf;
+
+ if (!findLoc)
+ return nullptr;
+ else if ((findLoc + 1) > buf_end)
+ return buf;
+
+ if ( (*findLoc == '\n' && *(findLoc+1) == '\r') ||
+ (*findLoc == '\r' && *(findLoc+1) == '\n'))
+ findLoc++; // possibly a pair.
+ return findLoc;
+}
+
+nsresult
+nsMsgSendLater::RebufferLeftovers(char *startBuf, uint32_t aLen)
+{
+ PR_FREEIF(mLeftoverBuffer);
+ mLeftoverBuffer = (char *)PR_Malloc(aLen + 1);
+ if (!mLeftoverBuffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ memcpy(mLeftoverBuffer, startBuf, aLen);
+ mLeftoverBuffer[aLen] = '\0';
+ return NS_OK;
+}
+
+nsresult
+nsMsgSendLater::BuildNewBuffer(const char* aBuf, uint32_t aCount, uint32_t *totalBufSize)
+{
+ // Only build a buffer when there are leftovers...
+ NS_ENSURE_TRUE(mLeftoverBuffer, NS_ERROR_FAILURE);
+
+ int32_t leftoverSize = PL_strlen(mLeftoverBuffer);
+ char* newBuffer = (char *) PR_Realloc(mLeftoverBuffer, aCount + leftoverSize);
+ NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
+ mLeftoverBuffer = newBuffer;
+
+ memcpy(mLeftoverBuffer + leftoverSize, aBuf, aCount);
+ *totalBufSize = aCount + leftoverSize;
+ return NS_OK;
+}
+
+// Got data?
+NS_IMETHODIMP
+nsMsgSendLater::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count)
+{
+ NS_ENSURE_ARG_POINTER(inStr);
+
+ // This is a little bit tricky since we have to chop random
+ // buffers into lines and deliver the lines...plus keeping the
+ // leftovers for next time...some fun, eh?
+ //
+ nsresult rv = NS_OK;
+ char *startBuf;
+ char *endBuf;
+ char *lineEnd;
+ char *newbuf = nullptr;
+ uint32_t size;
+
+ uint32_t aCount = count;
+ char *aBuf = (char *)PR_Malloc(aCount + 1);
+
+ inStr->Read(aBuf, count, &aCount);
+
+ // First, create a new work buffer that will
+ if (NS_FAILED(BuildNewBuffer(aBuf, aCount, &size))) // no leftovers...
+ {
+ startBuf = (char *)aBuf;
+ endBuf = (char *)(aBuf + aCount - 1);
+ }
+ else // yum, leftovers...new buffer created...sitting in mLeftoverBuffer
+ {
+ newbuf = mLeftoverBuffer;
+ startBuf = newbuf;
+ endBuf = startBuf + size - 1;
+ mLeftoverBuffer = nullptr; // null out this
+ }
+
+ while (startBuf <= endBuf)
+ {
+ lineEnd = FindEOL(startBuf, endBuf);
+ if (!lineEnd)
+ {
+ rv = RebufferLeftovers(startBuf, (endBuf - startBuf) + 1);
+ break;
+ }
+
+ rv = DeliverQueuedLine(startBuf, (lineEnd - startBuf) + 1);
+ if (NS_FAILED(rv))
+ break;
+
+ startBuf = lineEnd+1;
+ }
+
+ PR_Free(newbuf);
+ PR_Free(aBuf);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnStartRunningUrl(nsIURI *url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnStopRunningUrl(nsIURI *url, nsresult aExitCode)
+{
+ if (NS_SUCCEEDED(aExitCode))
+ InternalSendMessages(mUserInitiated, mIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for the send operation. We have to create this class
+// to listen for message send completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+NS_IMPL_ISUPPORTS(SendOperationListener, nsIMsgSendListener,
+ nsIMsgCopyServiceListener)
+
+SendOperationListener::SendOperationListener(nsMsgSendLater *aSendLater)
+: mSendLater(aSendLater)
+{
+}
+
+SendOperationListener::~SendOperationListener(void)
+{
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnGetDraftFolderURI(const char *aFolderURI)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnStartSending(const char *aMsgID, uint32_t aMsgSize)
+{
+#ifdef NS_DEBUG
+ printf("SendOperationListener::OnStartSending()\n");
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax)
+{
+#ifdef NS_DEBUG
+ printf("SendOperationListener::OnProgress()\n");
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnStatus(const char *aMsgID, const char16_t *aMsg)
+{
+#ifdef NS_DEBUG
+ printf("SendOperationListener::OnStatus()\n");
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnSendNotPerformed(const char *aMsgID, nsresult aStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnStopSending(const char *aMsgID, nsresult aStatus, const char16_t *aMsg,
+ nsIFile *returnFile)
+{
+ if (mSendLater && !mSendLater->OnSendStepFinished(aStatus))
+ NS_RELEASE(mSendLater);
+
+ return NS_OK;
+}
+
+// nsIMsgCopyServiceListener
+
+NS_IMETHODIMP
+SendOperationListener::OnStartCopy(void)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::SetMessageKey(nsMsgKey aKey)
+{
+ NS_NOTREACHED("SendOperationListener::SetMessageKey()");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SendOperationListener::GetMessageId(nsACString& messageId)
+{
+ NS_NOTREACHED("SendOperationListener::GetMessageId()\n");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnStopCopy(nsresult aStatus)
+{
+ if (mSendLater)
+ {
+ mSendLater->OnCopyStepFinished(aStatus);
+ NS_RELEASE(mSendLater);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgSendLater::CompleteMailFileSend()
+{
+ // get the identity from the key
+ // if no key, or we fail to find the identity
+ // use the default identity on the default account
+ nsCOMPtr<nsIMsgIdentity> identity;
+ nsresult rv = GetIdentityFromKey(mIdentityKey, getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!identity)
+ return NS_ERROR_UNEXPECTED;
+
+ // If for some reason the tmp file didn't get created, we've failed here
+ bool created;
+ mTempFile->Exists(&created);
+ if (!created)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgCompFields> compFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgSend> pMsgSend = do_CreateInstance(NS_MSGSEND_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Since we have already parsed all of the headers, we are simply going to
+ // set the composition fields and move on.
+ nsCString author;
+ mMessage->GetAuthor(getter_Copies(author));
+
+ nsMsgCompFields * fields = (nsMsgCompFields *)compFields.get();
+
+ fields->SetFrom(author.get());
+
+ if (m_to)
+ {
+ fields->SetTo(m_to);
+ }
+
+ if (m_bcc)
+ {
+ fields->SetBcc(m_bcc);
+ }
+
+ if (m_fcc)
+ {
+ fields->SetFcc(m_fcc);
+ }
+
+ if (m_newsgroups)
+ fields->SetNewsgroups(m_newsgroups);
+
+#if 0
+ // needs cleanup. Is this needed?
+ if (m_newshost)
+ fields->SetNewspostUrl(m_newshost);
+#endif
+
+ // Create the listener for the send operation...
+ SendOperationListener *sendListener = new SendOperationListener(this);
+ if (!sendListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(sendListener);
+
+ NS_ADDREF(this); //TODO: We should remove this!!!
+ rv = pMsgSend->SendMessageFile(identity,
+ mAccountKey,
+ compFields, // nsIMsgCompFields *fields,
+ mTempFile, // nsIFile *sendFile,
+ true, // bool deleteSendFileOnCompletion,
+ false, // bool digest_p,
+ nsIMsgSend::nsMsgSendUnsent, // nsMsgDeliverMode mode,
+ nullptr, // nsIMsgDBHdr *msgToReplace,
+ sendListener,
+ mFeedback,
+ nullptr);
+ NS_RELEASE(sendListener);
+ return rv;
+}
+
+nsresult
+nsMsgSendLater::StartNextMailFileSend(nsresult prevStatus)
+{
+ bool hasMoreElements = false;
+ if ((!mEnumerator) ||
+ NS_FAILED(mEnumerator->HasMoreElements(&hasMoreElements)) ||
+ !hasMoreElements)
+ {
+ // Notify that this message has finished being sent.
+ NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 100);
+
+ // EndSendMessages resets everything for us
+ EndSendMessages(prevStatus, nullptr, mTotalSendCount, mTotalSentSuccessfully);
+
+ // XXX Should we be releasing references so that we don't hold onto items
+ // unnecessarily.
+ return NS_OK;
+ }
+
+ // If we've already sent a message, and are sending more, send out a progress
+ // update with 100% for both send and copy as we must have finished by now.
+ if (mTotalSendCount)
+ NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 100);
+
+ nsCOMPtr<nsISupports> currentItem;
+ nsresult rv = mEnumerator->GetNext(getter_AddRefs(currentItem));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mMessage = do_QueryInterface(currentItem);
+ if (!mMessage)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mMessageFolder)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCString messageURI;
+ mMessageFolder->GetUriForMsg(mMessage, messageURI);
+
+ rv = nsMsgCreateTempFile("nsqmail.tmp", getter_AddRefs(mTempFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMessageService> messageService;
+ rv = GetMessageServiceFromURI(messageURI, getter_AddRefs(messageService));
+ if (NS_FAILED(rv) && !messageService)
+ return NS_ERROR_FACTORY_NOT_LOADED;
+
+ ++mTotalSendCount;
+
+ nsCString identityKey;
+ rv = mMessage->GetStringProperty(HEADER_X_MOZILLA_IDENTITY_KEY,
+ getter_Copies(identityKey));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = GetIdentityFromKey(identityKey.get(), getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!identity)
+ return NS_ERROR_UNEXPECTED;
+
+ // Notify that we're just about to start sending this message
+ NotifyListenersOnMessageStartSending(mTotalSendCount, mMessagesToSend.Count(),
+ identity);
+
+ // Setup what we need to parse the data stream correctly
+ m_inhead = true;
+ m_headersFP = 0;
+ m_headersPosition = 0;
+ m_bytesRead = 0;
+ m_position = 0;
+ m_flagsPosition = 0;
+ m_headersSize = 0;
+ PR_FREEIF(mLeftoverBuffer);
+
+ // Now, get our stream listener interface and plug it into the DisplayMessage
+ // operation
+ AddRef();
+
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = messageService->DisplayMessage(messageURI.get(),
+ static_cast<nsIStreamListener*>(this),
+ nullptr, nullptr, nullptr,
+ getter_AddRefs(dummyNull));
+
+ Release();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::GetUnsentMessagesFolder(nsIMsgIdentity *aIdentity, nsIMsgFolder **folder)
+{
+ nsCString uri;
+ GetFolderURIFromUserPrefs(nsIMsgSend::nsMsgQueueForLater, aIdentity, uri);
+ return LocateMessageFolder(aIdentity, nsIMsgSend::nsMsgQueueForLater,
+ uri.get(), folder);
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::HasUnsentMessages(nsIMsgIdentity *aIdentity, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIArray> accounts;
+ accountManager->GetAccounts(getter_AddRefs(accounts));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cnt = 0;
+ rv = accounts->GetLength(&cnt);
+ if (cnt == 0) {
+ *aResult = false;
+ return NS_OK; // no account set up -> no unsent messages
+ }
+
+ // XXX This code should be set up for multiple unsent folders, however we
+ // don't support that at the moment, so for now just assume one folder.
+ if (!mMessageFolder)
+ {
+ rv = GetUnsentMessagesFolder(nullptr,
+ getter_AddRefs(mMessageFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = ReparseDBIfNeeded(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t totalMessages;
+ rv = mMessageFolder->GetTotalMessages(false, &totalMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = totalMessages > 0;
+ return NS_OK;
+}
+
+//
+// To really finalize this capability, we need to have the ability to get
+// the message from the mail store in a stream for processing. The flow
+// would be something like this:
+//
+// foreach (message in Outbox folder)
+// get stream of Nth message
+// if (not done with headers)
+// Tack on to current buffer of headers
+// when done with headers
+// BuildHeaders()
+// Write Headers to Temp File
+// after done with headers
+// write rest of message body to temp file
+//
+// when done with the message
+// do send operation
+//
+// when send is complete
+// Copy from Outbox to FCC folder
+// Delete from Outbox folder
+//
+//
+NS_IMETHODIMP
+nsMsgSendLater::SendUnsentMessages(nsIMsgIdentity *aIdentity)
+{
+ return InternalSendMessages(true, aIdentity);
+}
+
+// Returns NS_OK if the db is OK, an error otherwise, e.g., we had to reparse.
+nsresult nsMsgSendLater::ReparseDBIfNeeded(nsIUrlListener *aListener)
+{
+ // This will kick off a reparse, if needed. So the next time we check if
+ // there are unsent messages, the db will be up to date.
+ nsCOMPtr<nsIMsgDatabase> unsentDB;
+ nsresult rv;
+ nsCOMPtr<nsIMsgLocalMailFolder> locFolder(do_QueryInterface(mMessageFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return locFolder->GetDatabaseWithReparse(aListener, nullptr,
+ getter_AddRefs(unsentDB));
+}
+
+nsresult
+nsMsgSendLater::InternalSendMessages(bool aUserInitiated,
+ nsIMsgIdentity *aIdentity)
+{
+ if (WeAreOffline())
+ return NS_MSG_ERROR_OFFLINE;
+
+ // Protect against being called whilst we're already sending.
+ if (mSendingMessages)
+ {
+ NS_ERROR("nsMsgSendLater is already sending messages\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ // XXX This code should be set up for multiple unsent folders, however we
+ // don't support that at the moment, so for now just assume one folder.
+ if (!mMessageFolder)
+ {
+ rv = GetUnsentMessagesFolder(nullptr,
+ getter_AddRefs(mMessageFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCOMPtr<nsIMsgDatabase> unsentDB;
+ // Remember these in case we need to reparse the db.
+ mUserInitiated = aUserInitiated;
+ mIdentity = aIdentity;
+ rv = ReparseDBIfNeeded(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mIdentity = nullptr; // don't hold onto the identity since we're a service.
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = mMessageFolder->GetMessages(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // copy all the elements in the enumerator into our isupports array....
+
+ nsCOMPtr<nsISupports> currentItem;
+ nsCOMPtr<nsIMsgDBHdr> messageHeader;
+ bool hasMoreElements = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) && hasMoreElements)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(currentItem));
+ if (NS_SUCCEEDED(rv))
+ {
+ messageHeader = do_QueryInterface(currentItem, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (aUserInitiated)
+ // If the user initiated the send, add all messages
+ mMessagesToSend.AppendObject(messageHeader);
+ else
+ {
+ // Else just send those that are NOT marked as Queued.
+ uint32_t flags;
+ rv = messageHeader->GetFlags(&flags);
+ if (NS_SUCCEEDED(rv) && !(flags & nsMsgMessageFlags::Queued))
+ mMessagesToSend.AppendObject(messageHeader);
+ }
+ }
+ }
+ }
+
+ // Now get an enumerator for our array.
+ rv = NS_NewArrayEnumerator(getter_AddRefs(mEnumerator), mMessagesToSend);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We're now sending messages so its time to signal that and reset our counts.
+ mSendingMessages = true;
+ mTotalSentSuccessfully = 0;
+ mTotalSendCount = 0;
+
+ // Notify the listeners that we are starting a send.
+ NotifyListenersOnStartSending(mMessagesToSend.Count());
+
+ return StartNextMailFileSend(NS_OK);
+}
+
+nsresult nsMsgSendLater::SetOrigMsgDisposition()
+{
+ if (!mMessage)
+ return NS_ERROR_NULL_POINTER;
+
+ // We're finished sending a queued message. We need to look at mMessage
+ // and see if we need to set replied/forwarded
+ // flags for the original message that this message might be a reply to
+ // or forward of.
+ nsCString originalMsgURIs;
+ nsCString queuedDisposition;
+ mMessage->GetStringProperty(ORIG_URI_PROPERTY, getter_Copies(originalMsgURIs));
+ mMessage->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, getter_Copies(queuedDisposition));
+ if (!queuedDisposition.IsEmpty())
+ {
+ nsTArray<nsCString> uriArray;
+ ParseString(originalMsgURIs, ',', uriArray);
+ for (uint32_t i = 0; i < uriArray.Length(); i++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgDBHdrFromURI(uriArray[i].get(), getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (msgHdr)
+ {
+ // get the folder for the message resource
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ msgHdr->GetFolder(getter_AddRefs(msgFolder));
+ if (msgFolder)
+ {
+ nsMsgDispositionState dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Replied;
+ if (queuedDisposition.Equals("forwarded"))
+ dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Forwarded;
+
+ msgFolder->AddMessageDispositionState(msgHdr, dispositionSetting);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgSendLater::DeleteCurrentMessage()
+{
+ if (!mMessage)
+ {
+ NS_ERROR("nsMsgSendLater: Attempt to delete an already deleted message");
+ return NS_OK;
+ }
+
+ // Get the composition fields interface
+ nsCOMPtr<nsIMutableArray> msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ if (!msgArray)
+ return NS_ERROR_FACTORY_NOT_LOADED;
+
+ if (!mMessageFolder)
+ return NS_ERROR_UNEXPECTED;
+
+ msgArray->InsertElementAt(mMessage, 0, false);
+
+ nsresult res = mMessageFolder->DeleteMessages(msgArray, nullptr, true, false, nullptr, false /*allowUndo*/);
+ if (NS_FAILED(res))
+ return NS_ERROR_FAILURE;
+
+ // Null out the message so we don't try and delete it again.
+ mMessage = nullptr;
+
+ return NS_OK;
+}
+
+//
+// This function parses the headers, and also deletes from the header block
+// any headers which should not be delivered in mail, regardless of whether
+// they were present in the queue file. Such headers include: BCC, FCC,
+// Sender, X-Mozilla-Status, X-Mozilla-News-Host, and Content-Length.
+// (Content-Length is for the disk file only, and must not be allowed to
+// escape onto the network, since it depends on the local linebreak
+// representation. Arguably, we could allow Lines to escape, but it's not
+// required by NNTP.)
+//
+nsresult
+nsMsgSendLater::BuildHeaders()
+{
+ char *buf = m_headers;
+ char *buf_end = buf + m_headersFP;
+
+ PR_FREEIF(m_to);
+ PR_FREEIF(m_bcc);
+ PR_FREEIF(m_newsgroups);
+ PR_FREEIF(m_newshost);
+ PR_FREEIF(m_fcc);
+ PR_FREEIF(mIdentityKey);
+ PR_FREEIF(mAccountKey);
+ m_flags = 0;
+
+ while (buf < buf_end)
+ {
+ bool prune_p = false;
+ bool do_flags_p = false;
+ char *colon = PL_strchr(buf, ':');
+ char *end;
+ char *value = 0;
+ char **header = 0;
+ char *header_start = buf;
+
+ if (! colon)
+ break;
+
+ end = colon;
+ while (end > buf && (*end == ' ' || *end == '\t'))
+ end--;
+
+ switch (buf [0])
+ {
+ case 'B': case 'b':
+ if (!PL_strncasecmp ("BCC", buf, end - buf))
+ {
+ header = &m_bcc;
+ prune_p = true;
+ }
+ break;
+ case 'C': case 'c':
+ if (!PL_strncasecmp ("CC", buf, end - buf))
+ header = &m_to;
+ else if (!PL_strncasecmp (HEADER_CONTENT_LENGTH, buf, end - buf))
+ prune_p = true;
+ break;
+ case 'F': case 'f':
+ if (!PL_strncasecmp ("FCC", buf, end - buf))
+ {
+ header = &m_fcc;
+ prune_p = true;
+ }
+ break;
+ case 'L': case 'l':
+ if (!PL_strncasecmp ("Lines", buf, end - buf))
+ prune_p = true;
+ break;
+ case 'N': case 'n':
+ if (!PL_strncasecmp ("Newsgroups", buf, end - buf))
+ header = &m_newsgroups;
+ break;
+ case 'S': case 's':
+ if (!PL_strncasecmp ("Sender", buf, end - buf))
+ prune_p = true;
+ break;
+ case 'T': case 't':
+ if (!PL_strncasecmp ("To", buf, end - buf))
+ header = &m_to;
+ break;
+ case 'X': case 'x':
+ {
+ if (buf + strlen(HEADER_X_MOZILLA_STATUS2) == end &&
+ !PL_strncasecmp(HEADER_X_MOZILLA_STATUS2, buf, end - buf))
+ prune_p = true;
+ else if (buf + strlen(HEADER_X_MOZILLA_STATUS) == end &&
+ !PL_strncasecmp(HEADER_X_MOZILLA_STATUS, buf, end - buf))
+ prune_p = do_flags_p = true;
+ else if (!PL_strncasecmp(HEADER_X_MOZILLA_DRAFT_INFO, buf, end - buf))
+ prune_p = true;
+ else if (!PL_strncasecmp(HEADER_X_MOZILLA_KEYWORDS, buf, end - buf))
+ prune_p = true;
+ else if (!PL_strncasecmp(HEADER_X_MOZILLA_NEWSHOST, buf, end - buf))
+ {
+ prune_p = true;
+ header = &m_newshost;
+ }
+ else if (!PL_strncasecmp(HEADER_X_MOZILLA_IDENTITY_KEY, buf, end - buf))
+ {
+ prune_p = true;
+ header = &mIdentityKey;
+ }
+ else if (!PL_strncasecmp(HEADER_X_MOZILLA_ACCOUNT_KEY, buf, end - buf))
+ {
+ prune_p = true;
+ header = &mAccountKey;
+ }
+ break;
+ }
+ }
+
+ buf = colon + 1;
+ while (*buf == ' ' || *buf == '\t')
+ buf++;
+
+ value = buf;
+
+SEARCH_NEWLINE:
+ while (*buf != 0 && *buf != '\r' && *buf != '\n')
+ buf++;
+
+ if (buf+1 >= buf_end)
+ ;
+ // If "\r\n " or "\r\n\t" is next, that doesn't terminate the header.
+ else if (buf+2 < buf_end &&
+ (buf[0] == '\r' && buf[1] == '\n') &&
+ (buf[2] == ' ' || buf[2] == '\t'))
+ {
+ buf += 3;
+ goto SEARCH_NEWLINE;
+ }
+ // If "\r " or "\r\t" or "\n " or "\n\t" is next, that doesn't terminate
+ // the header either.
+ else if ((buf[0] == '\r' || buf[0] == '\n') &&
+ (buf[1] == ' ' || buf[1] == '\t'))
+ {
+ buf += 2;
+ goto SEARCH_NEWLINE;
+ }
+
+ if (header)
+ {
+ int L = buf - value;
+ if (*header)
+ {
+ char *newh = (char*) PR_Realloc ((*header),
+ PL_strlen(*header) + L + 10);
+ if (!newh) return NS_ERROR_OUT_OF_MEMORY;
+ *header = newh;
+ newh = (*header) + PL_strlen (*header);
+ *newh++ = ',';
+ *newh++ = ' ';
+ memcpy(newh, value, L);
+ newh [L] = 0;
+ }
+ else
+ {
+ *header = (char *) PR_Malloc(L+1);
+ if (!*header) return NS_ERROR_OUT_OF_MEMORY;
+ memcpy((*header), value, L);
+ (*header)[L] = 0;
+ }
+ }
+ else if (do_flags_p)
+ {
+ char *s = value;
+ PR_ASSERT(*s != ' ' && *s != '\t');
+ NS_ASSERTION(MsgIsHex(s, 4), "Expected 4 hex digits for flags.");
+ m_flags = MsgUnhex(s, 4);
+ }
+
+ if (*buf == '\r' || *buf == '\n')
+ {
+ if (*buf == '\r' && buf[1] == '\n')
+ buf++;
+ buf++;
+ }
+
+ if (prune_p)
+ {
+ char *to = header_start;
+ char *from = buf;
+ while (from < buf_end)
+ *to++ = *from++;
+ buf = header_start;
+ buf_end = to;
+ m_headersFP = buf_end - m_headers;
+ }
+ }
+
+ m_headers[m_headersFP++] = '\r';
+ m_headers[m_headersFP++] = '\n';
+
+ // Now we have parsed out all of the headers we need and we
+ // can proceed.
+ return NS_OK;
+}
+
+nsresult
+DoGrowBuffer(int32_t desired_size, int32_t element_size, int32_t quantum,
+ char **buffer, int32_t *size)
+{
+ if (*size <= desired_size)
+ {
+ char *new_buf;
+ int32_t increment = desired_size - *size;
+ if (increment < quantum) // always grow by a minimum of N bytes
+ increment = quantum;
+
+ new_buf = (*buffer
+ ? (char *) PR_Realloc (*buffer, (*size + increment)
+ * (element_size / sizeof(char)))
+ : (char *) PR_Malloc ((*size + increment)
+ * (element_size / sizeof(char))));
+ if (! new_buf)
+ return NS_ERROR_OUT_OF_MEMORY;
+ *buffer = new_buf;
+ *size += increment;
+ }
+ return NS_OK;
+}
+
+#define do_grow_headers(desired_size) \
+ (((desired_size) >= m_headersSize) ? \
+ DoGrowBuffer ((desired_size), sizeof(char), 1024, \
+ &m_headers, &m_headersSize) \
+ : NS_OK)
+
+nsresult
+nsMsgSendLater::DeliverQueuedLine(char *line, int32_t length)
+{
+ int32_t flength = length;
+
+ m_bytesRead += length;
+
+// convert existing newline to CRLF
+// Don't need this because the calling routine is taking care of it.
+// if (length > 0 && (line[length-1] == '\r' ||
+// (line[length-1] == '\n' && (length < 2 || line[length-2] != '\r'))))
+// {
+// line[length-1] = '\r';
+// line[length++] = '\n';
+// }
+//
+ //
+ // We are going to check if we are looking at a "From - " line. If so,
+ // then just eat it and return NS_OK
+ //
+ if (!PL_strncasecmp(line, "From - ", 7))
+ return NS_OK;
+
+ if (m_inhead)
+ {
+ if (m_headersPosition == 0)
+ {
+ // This line is the first line in a header block.
+ // Remember its position.
+ m_headersPosition = m_position;
+
+ // Also, since we're now processing the headers, clear out the
+ // slots which we will parse data into, so that the values that
+ // were used the last time around do not persist.
+
+ // We must do that here, and not in the previous clause of this
+ // `else' (the "I've just seen a `From ' line clause") because
+ // that clause happens before delivery of the previous message is
+ // complete, whereas this clause happens after the previous msg
+ // has been delivered. If we did this up there, then only the
+ // last message in the folder would ever be able to be both
+ // mailed and posted (or fcc'ed.)
+ PR_FREEIF(m_to);
+ PR_FREEIF(m_bcc);
+ PR_FREEIF(m_newsgroups);
+ PR_FREEIF(m_newshost);
+ PR_FREEIF(m_fcc);
+ PR_FREEIF(mIdentityKey);
+ }
+
+ if (line[0] == '\r' || line[0] == '\n' || line[0] == 0)
+ {
+ // End of headers. Now parse them; open the temp file;
+ // and write the appropriate subset of the headers out.
+ m_inhead = false;
+
+ nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mOutFile), mTempFile, -1, 00600);
+ if (NS_FAILED(rv))
+ return NS_MSG_ERROR_WRITING_FILE;
+
+ nsresult status = BuildHeaders();
+ if (NS_FAILED(status))
+ return status;
+
+ uint32_t n;
+ rv = mOutFile->Write(m_headers, m_headersFP, &n);
+ if (NS_FAILED(rv) || n != (uint32_t)m_headersFP)
+ return NS_MSG_ERROR_WRITING_FILE;
+ }
+ else
+ {
+ // Otherwise, this line belongs to a header. So append it to the
+ // header data.
+
+ if (!PL_strncasecmp (line, HEADER_X_MOZILLA_STATUS, PL_strlen(HEADER_X_MOZILLA_STATUS)))
+ // Notice the position of the flags.
+ m_flagsPosition = m_position;
+ else if (m_headersFP == 0)
+ m_flagsPosition = 0;
+
+ nsresult status = do_grow_headers (length + m_headersFP + 10);
+ if (NS_FAILED(status))
+ return status;
+
+ memcpy(m_headers + m_headersFP, line, length);
+ m_headersFP += length;
+ }
+ }
+ else
+ {
+ // This is a body line. Write it to the file.
+ PR_ASSERT(mOutFile);
+ if (mOutFile)
+ {
+ uint32_t wrote;
+ nsresult rv = mOutFile->Write(line, length, &wrote);
+ if (NS_FAILED(rv) || wrote < (uint32_t) length)
+ return NS_MSG_ERROR_WRITING_FILE;
+ }
+ }
+
+ m_position += flength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::AddListener(nsIMsgSendLaterListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ mListenerArray.AppendElement(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::RemoveListener(nsIMsgSendLaterListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ return mListenerArray.RemoveElement(aListener) ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::GetSendingMessages(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mSendingMessages;
+ return NS_OK;
+}
+
+#define NOTIFY_LISTENERS(propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<nsCOMPtr<nsIMsgSendLaterListener> >::ForwardIterator iter(mListenerArray); \
+ nsCOMPtr<nsIMsgSendLaterListener> listener; \
+ while (iter.HasMore()) { \
+ listener = iter.GetNext(); \
+ listener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+void
+nsMsgSendLater::NotifyListenersOnStartSending(uint32_t aTotalMessageCount)
+{
+ NOTIFY_LISTENERS(OnStartSending, (aTotalMessageCount));
+}
+
+void
+nsMsgSendLater::NotifyListenersOnMessageStartSending(uint32_t aCurrentMessage,
+ uint32_t aTotalMessage,
+ nsIMsgIdentity *aIdentity)
+{
+ NOTIFY_LISTENERS(OnMessageStartSending, (aCurrentMessage, aTotalMessage,
+ mMessage, aIdentity));
+}
+
+void
+nsMsgSendLater::NotifyListenersOnProgress(uint32_t aCurrentMessage,
+ uint32_t aTotalMessage,
+ uint32_t aSendPercent,
+ uint32_t aCopyPercent)
+{
+ NOTIFY_LISTENERS(OnMessageSendProgress, (aCurrentMessage, aTotalMessage,
+ aSendPercent, aCopyPercent));
+}
+
+void
+nsMsgSendLater::NotifyListenersOnMessageSendError(uint32_t aCurrentMessage,
+ nsresult aStatus,
+ const char16_t *aMsg)
+{
+ NOTIFY_LISTENERS(OnMessageSendError, (aCurrentMessage, mMessage,
+ aStatus, aMsg));
+}
+
+/**
+ * This function is called to end sending of messages, it resets the send later
+ * system and notifies the relevant parties that we have finished.
+ */
+void
+nsMsgSendLater::EndSendMessages(nsresult aStatus, const char16_t *aMsg,
+ uint32_t aTotalTried, uint32_t aSuccessful)
+{
+ // Catch-all, we may have had an issue sending, so we may not be calling
+ // StartNextMailFileSend to fully finish the sending. Therefore set
+ // mSendingMessages to false here so that we don't think we're still trying
+ // to send messages
+ mSendingMessages = false;
+
+ // Clear out our array of messages.
+ mMessagesToSend.Clear();
+
+ // We don't need to keep hold of the database now we've finished sending.
+ (void)mMessageFolder->SetMsgDatabase(nullptr);
+
+ // or the enumerator, temp file or output stream
+ mEnumerator = nullptr;
+ mTempFile = nullptr;
+ mOutFile = nullptr;
+
+ NOTIFY_LISTENERS(OnStopSending, (aStatus, aMsg, aTotalTried, aSuccessful));
+
+ // If we've got a shutdown listener, notify it that we've finished.
+ if (mShutdownListener)
+ {
+ mShutdownListener->OnStopRunningUrl(nullptr, NS_OK);
+ mShutdownListener = nullptr;
+ }
+}
+
+/**
+ * Called when the send part of sending a message is finished. This will set up
+ * for the next step or "end" depending on the status.
+ *
+ * @param aStatus The success or fail result of the send step.
+ * @return True if the copy process will continue, false otherwise.
+ */
+bool
+nsMsgSendLater::OnSendStepFinished(nsresult aStatus)
+{
+ if (NS_SUCCEEDED(aStatus))
+ {
+ SetOrigMsgDisposition();
+ DeleteCurrentMessage();
+
+ // Send finished, so that is now 100%, copy to proceed...
+ NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 0);
+
+ ++mTotalSentSuccessfully;
+ return true;
+ }
+ else
+ {
+ // XXX we don't currently get a message string from the send service.
+ NotifyListenersOnMessageSendError(mTotalSendCount, aStatus, nullptr);
+ nsresult rv = StartNextMailFileSend(aStatus);
+ // if this is the last message we're sending, we should report
+ // the status failure.
+ if (NS_FAILED(rv))
+ EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully);
+ }
+ return false;
+}
+
+/**
+ * Called when the copy part of sending a message is finished. This will send
+ * the next message or handle failure as appropriate.
+ *
+ * @param aStatus The success or fail result of the copy step.
+ */
+void
+nsMsgSendLater::OnCopyStepFinished(nsresult aStatus)
+{
+ // Regardless of the success of the copy we will still keep trying
+ // to send the rest...
+ nsresult rv = StartNextMailFileSend(aStatus);
+ if (NS_FAILED(rv))
+ EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully);
+}
+
+// XXX todo
+// maybe this should just live in the account manager?
+nsresult
+nsMsgSendLater::GetIdentityFromKey(const char *aKey, nsIMsgIdentity **aIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aIdentity);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aKey)
+ {
+ nsCOMPtr<nsIArray> identities;
+ if (NS_SUCCEEDED(accountManager->GetAllIdentities(getter_AddRefs(identities))))
+ {
+ nsCOMPtr<nsIMsgIdentity> lookupIdentity;
+ uint32_t count = 0;
+
+ identities->GetLength(&count);
+ for (uint32_t i = 0; i < count; i++)
+ {
+ lookupIdentity = do_QueryElementAt(identities, i, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCString key;
+ lookupIdentity->GetKey(key);
+ if (key.Equals(aKey))
+ {
+ NS_IF_ADDREF(*aIdentity = lookupIdentity);
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ // If no aKey, or we failed to find the identity from the key
+ // use the identity from the default account.
+ nsCOMPtr<nsIMsgAccount> defaultAccount;
+ rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (defaultAccount)
+ rv = defaultAccount->GetDefaultIdentity(aIdentity);
+ else
+ *aIdentity = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnItemAdded(nsIMsgFolder *aParentItem, nsISupports *aItem)
+{
+ // No need to trigger if timer is already set
+ if (mTimerSet)
+ return NS_OK;
+
+ // XXX only trigger for non-queued headers
+
+ // Items from this function return NS_OK because the callee won't care about
+ // the result anyway.
+ nsresult rv;
+ if (!mTimer)
+ {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ }
+
+ rv = mTimer->Init(static_cast<nsIObserver*>(this), kInitialMessageSendTime,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ mTimerSet = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnItemRemoved(nsIMsgFolder *aParentItem, nsISupports *aItem)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnItemPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty,
+ const char* aOldValue,
+ const char* aNewValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnItemIntPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty,
+ int64_t aOldValue, int64_t aNewValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnItemBoolPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty,
+ bool aOldValue, bool aNewValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnItemUnicharPropertyChanged(nsIMsgFolder *aItem,
+ nsIAtom *aProperty,
+ const char16_t* aOldValue,
+ const char16_t* aNewValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnItemPropertyFlagChanged(nsIMsgDBHdr *aItem, nsIAtom *aProperty,
+ uint32_t aOldValue, uint32_t aNewValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnItemEvent(nsIMsgFolder* aItem, nsIAtom *aEvent)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::GetNeedsToRunTask(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mSendingMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::DoShutdownTask(nsIUrlListener *aListener, nsIMsgWindow *aWindow,
+ bool *aResult)
+{
+ if (mTimer)
+ mTimer->Cancel();
+ // If we're already sending messages, nothing to do, but save the shutdown
+ // listener until we've finished.
+ if (mSendingMessages)
+ {
+ mShutdownListener = aListener;
+ return NS_OK;
+ }
+ // Else we have pending messages, we need to throw up a dialog to find out
+ // if to send them or not.
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::GetCurrentTaskName(nsAString &aResult)
+{
+ // XXX Bug 440794 will localize this, left as non-localized whilst we decide
+ // on the actual strings and try out the UI.
+ aResult = NS_LITERAL_STRING("Sending Messages");
+ return NS_OK;
+}
diff --git a/mailnews/compose/src/nsMsgSendLater.h b/mailnews/compose/src/nsMsgSendLater.h
new file mode 100644
index 000000000..7b6cdf8f7
--- /dev/null
+++ b/mailnews/compose/src/nsMsgSendLater.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgSendLater_H_
+#define _nsMsgSendLater_H_
+
+#include "nsCOMArray.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgSendListener.h"
+#include "nsIMsgSendLaterListener.h"
+#include "nsIMsgSendLater.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsTObserverArray.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsIMsgShutdown.h"
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for the send operation. We have to create this class
+// to listen for message send completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+class nsMsgSendLater;
+
+class SendOperationListener : public nsIMsgSendListener,
+ public nsIMsgCopyServiceListener
+{
+public:
+ explicit SendOperationListener(nsMsgSendLater *aSendLater);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSENDLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+private:
+ virtual ~SendOperationListener();
+ nsMsgSendLater *mSendLater;
+};
+
+class nsMsgSendLater: public nsIMsgSendLater,
+ public nsIFolderListener,
+ public nsIObserver,
+ public nsIUrlListener,
+ public nsIMsgShutdownTask
+
+{
+public:
+ nsMsgSendLater();
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSENDLATER
+ NS_DECL_NSIFOLDERLISTENER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGSHUTDOWNTASK
+
+ // Methods needed for implementing interface...
+ nsresult StartNextMailFileSend(nsresult prevStatus);
+ nsresult CompleteMailFileSend();
+
+ nsresult DeleteCurrentMessage();
+ nsresult SetOrigMsgDisposition();
+ // Necessary for creating a valid list of recipients
+ nsresult BuildHeaders();
+ nsresult DeliverQueuedLine(char *line, int32_t length);
+ nsresult RebufferLeftovers(char *startBuf, uint32_t aLen);
+ nsresult BuildNewBuffer(const char* aBuf, uint32_t aCount, uint32_t *totalBufSize);
+
+ // methods for listener array processing...
+ void NotifyListenersOnStartSending(uint32_t aTotalMessageCount);
+ void NotifyListenersOnMessageStartSending(uint32_t aCurrentMessage,
+ uint32_t aTotalMessage,
+ nsIMsgIdentity *aIdentity);
+ void NotifyListenersOnProgress(uint32_t aCurrentMessage,
+ uint32_t aTotalMessage,
+ uint32_t aSendPercent,
+ uint32_t aCopyPercent);
+ void NotifyListenersOnMessageSendError(uint32_t aCurrentMessage,
+ nsresult aStatus,
+ const char16_t *aMsg);
+ void EndSendMessages(nsresult aStatus, const char16_t *aMsg,
+ uint32_t aTotalTried, uint32_t aSuccessful);
+
+ bool OnSendStepFinished(nsresult aStatus);
+ void OnCopyStepFinished(nsresult aStatus);
+
+ // counters and things for enumeration
+ uint32_t mTotalSentSuccessfully;
+ uint32_t mTotalSendCount;
+ nsCOMArray<nsIMsgDBHdr> mMessagesToSend;
+ nsCOMPtr<nsISimpleEnumerator> mEnumerator;
+ nsCOMPtr<nsIMsgFolder> mMessageFolder;
+ nsCOMPtr<nsIMsgStatusFeedback> mFeedback;
+
+ // Private Information
+private:
+ virtual ~nsMsgSendLater();
+ nsresult GetIdentityFromKey(const char *aKey, nsIMsgIdentity **aIdentity);
+ nsresult ReparseDBIfNeeded(nsIUrlListener *aListener);
+ nsresult InternalSendMessages(bool aUserInitiated,
+ nsIMsgIdentity *aIdentity);
+
+ nsTObserverArray<nsCOMPtr<nsIMsgSendLaterListener> > mListenerArray;
+ nsCOMPtr<nsIMsgDBHdr> mMessage;
+ nsCOMPtr<nsITimer> mTimer;
+ bool mTimerSet;
+ nsCOMPtr<nsIUrlListener> mShutdownListener;
+
+ //
+ // File output stuff...
+ //
+ nsCOMPtr<nsIFile> mTempFile;
+ nsCOMPtr<nsIOutputStream> mOutFile;
+
+ // For building headers and stream parsing...
+ char *m_to;
+ char *m_bcc;
+ char *m_fcc;
+ char *m_newsgroups;
+ char *m_newshost;
+ char *m_headers;
+ int32_t m_flags;
+ int32_t m_headersFP;
+ bool m_inhead;
+ int32_t m_headersPosition;
+ int32_t m_bytesRead;
+ int32_t m_position;
+ int32_t m_flagsPosition;
+ int32_t m_headersSize;
+ char *mLeftoverBuffer;
+ char *mIdentityKey;
+ char *mAccountKey;
+
+ bool mSendingMessages;
+ bool mUserInitiated;
+ nsCOMPtr<nsIMsgIdentity> mIdentity;
+};
+
+
+#endif /* _nsMsgSendLater_H_ */
diff --git a/mailnews/compose/src/nsMsgSendPart.cpp b/mailnews/compose/src/nsMsgSendPart.cpp
new file mode 100644
index 000000000..a33dabf33
--- /dev/null
+++ b/mailnews/compose/src/nsMsgSendPart.cpp
@@ -0,0 +1,779 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMsgSend.h"
+#include "nsMsgSendPart.h"
+#include "nsIMimeConverter.h"
+#include "nsCOMPtr.h"
+#include "nsIComponentManager.h"
+#include "nsMsgI18N.h"
+#include "nsMsgCompUtils.h"
+#include "nsMsgMimeCID.h"
+#include "nsMimeTypes.h"
+#include "prmem.h"
+#include "nsMsgPrompts.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsNetUtil.h"
+#include "nsISeekableStream.h"
+#include "nsReadLine.h"
+#include "nsILineInputStream.h"
+#include "nsComposeStrings.h"
+#include "mozilla/mailnews/MimeEncoder.h"
+
+static char *mime_mailto_stream_read_buffer = 0;
+
+int32_t nsMsgSendPart::M_counter = 0;
+
+nsMsgSendPart::nsMsgSendPart(nsIMsgSend* state, const char *part_charset)
+{
+ PL_strncpy(m_charset_name, (part_charset ? part_charset : "UTF-8"), sizeof(m_charset_name)-1);
+ m_charset_name[sizeof(m_charset_name)-1] = '\0';
+ m_children = nullptr;
+ m_numchildren = 0;
+ // if we're not added as a child, the default part number will be "1".
+ m_partNum = "1";
+ SetMimeDeliveryState(state);
+
+ m_parent = nullptr;
+ m_buffer = nullptr;
+ m_type = nullptr;
+ m_other = nullptr;
+ m_strip_sensitive_headers = false;
+
+ m_firstBlock = false;
+ m_needIntlConversion = false;
+
+ m_mainpart = false;
+ m_just_hit_CR = false;
+}
+
+
+nsMsgSendPart::~nsMsgSendPart()
+{
+ for (int i=0 ; i < m_numchildren; i++)
+ delete m_children[i];
+
+ delete [] m_children;
+ PR_FREEIF(m_buffer);
+ PR_FREEIF(m_other);
+ PR_FREEIF(m_type);
+}
+
+nsresult nsMsgSendPart::CopyString(char** dest, const char* src)
+{
+ NS_ASSERTION(src, "src null");
+
+ PR_FREEIF(*dest);
+ if (!src)
+ *dest = PL_strdup("");
+ else
+ *dest = PL_strdup(src);
+
+ return *dest? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+
+nsresult nsMsgSendPart::SetFile(nsIFile *file)
+{
+ m_file = file;
+ return NS_OK;
+}
+
+
+nsresult nsMsgSendPart::SetBuffer(const char* buffer)
+{
+ PR_FREEIF(m_buffer);
+ return CopyString(&m_buffer, buffer);
+}
+
+
+nsresult nsMsgSendPart::SetType(const char* type)
+{
+ PR_FREEIF(m_type);
+ m_type = PL_strdup(type);
+ return m_type ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+
+nsresult nsMsgSendPart::SetOtherHeaders(const char* other)
+{
+ return CopyString(&m_other, other);
+}
+
+nsresult nsMsgSendPart::SetMimeDeliveryState(nsIMsgSend *state)
+{
+ m_state = state;
+ if (GetNumChildren() > 0)
+ {
+ for (int i = 0; i < GetNumChildren(); i++)
+ {
+ nsMsgSendPart *part = GetChild(i);
+ if (part)
+ part->SetMimeDeliveryState(state);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgSendPart::AppendOtherHeaders(const char* more)
+{
+ if (!m_other)
+ return SetOtherHeaders(more);
+
+ if (!more || !*more)
+ return NS_OK;
+
+ char* tmp = (char *) PR_Malloc(sizeof(char) * (PL_strlen(m_other) + PL_strlen(more) + 2));
+ if (!tmp)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ PL_strcpy(tmp, m_other);
+ PL_strcat(tmp, more);
+ PR_FREEIF(m_other);
+ m_other = tmp;
+
+ return NS_OK;
+}
+
+
+nsresult nsMsgSendPart::SetMainPart(bool value)
+{
+ m_mainpart = value;
+ return NS_OK;
+}
+
+nsresult nsMsgSendPart::AddChild(nsMsgSendPart* child)
+{
+ m_numchildren++;
+ nsMsgSendPart** tmp = new nsMsgSendPart* [m_numchildren];
+ if (tmp == nullptr) return NS_ERROR_OUT_OF_MEMORY;
+ for (int i=0 ; i<m_numchildren-1 ; i++) {
+ tmp[i] = m_children[i];
+ }
+ delete [] m_children;
+ m_children = tmp;
+ m_children[m_numchildren - 1] = child;
+ child->m_parent = this;
+ nsCString partNum(m_partNum);
+ partNum.Append(".");
+ partNum.AppendInt(m_numchildren);
+ child->m_partNum = partNum;
+ return NS_OK;
+}
+
+nsMsgSendPart * nsMsgSendPart::DetachChild(int32_t whichOne)
+{
+ nsMsgSendPart *returnValue = nullptr;
+
+ NS_ASSERTION(whichOne >= 0 && whichOne < m_numchildren, "parameter out of range");
+ if (whichOne >= 0 && whichOne < m_numchildren)
+ {
+ returnValue = m_children[whichOne];
+
+ if (m_numchildren > 1)
+ {
+ nsMsgSendPart** tmp = new nsMsgSendPart* [m_numchildren-1];
+ if (tmp != nullptr)
+ {
+ // move all the other kids over
+ for (int i=0 ; i<m_numchildren-1 ; i++)
+ {
+ if (i >= whichOne)
+ tmp[i] = m_children[i+1];
+ else
+ tmp[i] = m_children[i];
+ }
+ delete [] m_children;
+ m_children = tmp;
+ m_numchildren--;
+ }
+ }
+ else
+ {
+ delete [] m_children;
+ m_children = nullptr;
+ m_numchildren = 0;
+ }
+ }
+
+ if (returnValue)
+ returnValue->m_parent = nullptr;
+
+ return returnValue;
+}
+
+nsMsgSendPart* nsMsgSendPart::GetChild(int32_t which)
+{
+ NS_ASSERTION(which >= 0 && which < m_numchildren, "parameter out of range");
+ if (which >= 0 && which < m_numchildren) {
+ return m_children[which];
+ }
+ return nullptr;
+}
+
+
+
+nsresult nsMsgSendPart::PushBody(const char* buffer, int32_t length)
+{
+ nsresult status = NS_OK;
+ const char* encoded_data = buffer;
+
+ if (m_encoder)
+ {
+ status = m_encoder->Write(encoded_data, length);
+ }
+ else
+ {
+ // Merely translate all linebreaks to CRLF.
+ const char *in = encoded_data;
+ const char *end = in + length;
+ char *buffer, *out;
+
+
+ buffer = mime_get_stream_write_buffer();
+ // XXX -1 is not a valid nsresult
+ NS_ENSURE_TRUE(buffer, static_cast<nsresult>(-1));
+
+ NS_ASSERTION(encoded_data != buffer, "encoded_data == buffer");
+ out = buffer;
+
+ for (; in < end; in++) {
+ if (m_just_hit_CR) {
+ m_just_hit_CR = false;
+ if (*in == '\n') {
+ // The last thing we wrote was a CRLF from hitting a CR.
+ // So, we don't want to do anything from a following LF;
+ // we want to ignore it.
+ continue;
+ }
+ }
+ if (*in == '\r' || *in == '\n') {
+ /* Write out the newline. */
+ *out++ = '\r';
+ *out++ = '\n';
+
+ status = mime_write_message_body(m_state, buffer,
+ out - buffer);
+ if (NS_FAILED(status)) return status;
+ out = buffer;
+
+ if (*in == '\r') {
+ m_just_hit_CR = true;
+ }
+
+ out = buffer;
+ } else {
+
+ /* Fix for bug #95985. We can't assume that all lines are shorter
+ than 4096 chars (MIME_BUFFER_SIZE), so we need to test
+ for this here. sfraser.
+ */
+ if (out - buffer >= MIME_BUFFER_SIZE)
+ {
+ status = mime_write_message_body(m_state, buffer, out - buffer);
+ if (NS_FAILED(status)) return status;
+
+ out = buffer;
+ }
+
+ *out++ = *in;
+ }
+ }
+
+ /* Flush the last line. */
+ if (out > buffer) {
+ status = mime_write_message_body(m_state, buffer, out - buffer);
+ if (NS_FAILED(status)) return status;
+ out = buffer;
+ }
+ }
+
+ if (encoded_data && encoded_data != buffer) {
+ PR_Free((char *) encoded_data);
+ }
+
+ return status;
+}
+
+
+/* Partition the headers into those which apply to the message as a whole;
+those which apply to the message's contents; and the Content-Type header
+itself. (This relies on the fact that all body-related headers begin with
+"Content-".)
+
+ (How many header parsers are in this program now?)
+ */
+static nsresult
+divide_content_headers(const char *headers,
+ char **message_headers,
+ char **content_headers,
+ char **content_type_header)
+{
+ const char *tail;
+ char *message_tail, *content_tail, *type_tail;
+ int L = 0;
+ if (headers)
+ L = PL_strlen(headers);
+
+ if (L == 0)
+ return NS_OK;
+
+ *message_headers = (char *)PR_Malloc(L+1);
+ if (!*message_headers)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ *content_headers = (char *)PR_Malloc(L+1);
+ if (!*content_headers) {
+ PR_Free(*message_headers);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *content_type_header = (char *)PR_Malloc(L+1);
+ if (!*content_type_header) {
+ PR_Free(*message_headers);
+ PR_Free(*content_headers);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ message_tail = *message_headers;
+ content_tail = *content_headers;
+ type_tail = *content_type_header;
+ tail = headers;
+
+ while (*tail)
+ {
+ const char *head = tail;
+ char **out;
+ while(true) {
+ /* Loop until we reach a newline that is not followed by whitespace.
+ */
+ if (tail[0] == 0 ||
+ ((tail[0] == '\r' || tail[0] == '\n') &&
+ !(tail[1] == ' ' || tail[1] == '\t' || tail[1] == '\n')))
+ {
+ /* Swallow the whole newline. */
+ if (tail[0] == '\r' && tail[1] == '\n')
+ tail++;
+ if (*tail)
+ tail++;
+ break;
+ }
+ tail++;
+ }
+
+ /* Decide which block this header goes into.
+ */
+ if (!PL_strncasecmp(head, "Content-Type:", 13))
+ out = &type_tail;
+ else
+ if (!PL_strncasecmp(head, "Content-", 8))
+ out = &content_tail;
+ else
+ out = &message_tail;
+
+ memcpy(*out, head, (tail-head));
+ *out += (tail-head);
+ }
+
+ *message_tail = 0;
+ *content_tail = 0;
+ *type_tail = 0;
+
+ if (!**message_headers) {
+ PR_Free(*message_headers);
+ *message_headers = 0;
+ }
+
+ if (!**content_headers) {
+ PR_Free(*content_headers);
+ *content_headers = 0;
+ }
+
+ if (!**content_type_header) {
+ PR_Free(*content_type_header);
+ *content_type_header = 0;
+ }
+
+#ifdef DEBUG
+ // ### mwelch Because of the extreme difficulty we've had with
+ // duplicate part headers, I'm going to put in an
+ // ASSERT here which makes sure that no duplicate
+ // Content-Type or Content-Transfer-Encoding headers
+ // leave here undetected.
+ const char* tmp;
+ if (*content_type_header) {
+ tmp = PL_strstr(*content_type_header, "Content-Type");
+ if (tmp) {
+ tmp++; // get past the first occurrence
+ NS_ASSERTION(!PL_strstr(tmp, "Content-Type"), "Content-part already present");
+ }
+ }
+
+ if (*content_headers) {
+ tmp = PL_strstr(*content_headers, "Content-Transfer-Encoding");
+ if (tmp) {
+ tmp++; // get past the first occurrence
+ NS_ASSERTION(!PL_strstr(tmp, "Content-Transfer-Encoding"), "Content-Transfert already present");
+ }
+ }
+#endif // DEBUG
+
+ return NS_OK;
+}
+
+#define SKIP_EMPTY_PART 1966
+
+nsresult
+nsMsgSendPart::Write()
+{
+ nsresult status = NS_OK;
+ char *separator = nullptr;
+ bool needToWriteCRLFAfterEncodedBody = false;
+
+#define PUSHLEN(str, length) \
+ do { \
+ status = mime_write_message_body(m_state, str, length); \
+ if (NS_FAILED(status)) goto FAIL; \
+ } while (0) \
+
+#define PUSH(str) PUSHLEN(str, PL_strlen(str))
+
+ // rhp: Suppress the output of parts that are empty!
+ if ( (m_parent) &&
+ (m_numchildren == 0) &&
+ ( (!m_buffer) || (!*m_buffer) ) &&
+ (!m_file) &&
+ (!m_mainpart) )
+ // XXX SKIP_EMPTY_PART (= 1966) is not a valid nsresult
+ return static_cast<nsresult>(SKIP_EMPTY_PART);
+
+ if (m_mainpart && m_type && PL_strcmp(m_type, TEXT_HTML) == 0)
+ {
+ if (m_file)
+ {
+ // The "insert HTML links" code requires a memory buffer,
+ // so read the file into memory.
+ NS_ASSERTION(m_buffer == nullptr, "not-null buffer");
+ int32_t length = 0;
+ int64_t fileSize;
+ if (NS_SUCCEEDED(m_file->GetFileSize(&fileSize)))
+ length = fileSize;
+
+ m_buffer = (char *) PR_Malloc(sizeof(char) * (length + 1));
+ if (m_buffer)
+ {
+ nsCOMPtr<nsIInputStream> inputFile;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), m_file);
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t bytesRead;
+ rv = inputFile->Read(m_buffer, length, &bytesRead);
+ inputFile->Close();
+ m_buffer[length] = '\0';
+ }
+ else
+ PR_Free(m_buffer);
+ }
+ }
+ }
+
+ if (m_parent && m_parent->m_type &&
+ !PL_strcasecmp(m_parent->m_type, MULTIPART_DIGEST) &&
+ m_type &&
+ (!PL_strcasecmp(m_type, MESSAGE_RFC822) ||
+ !PL_strcasecmp(m_type, MESSAGE_NEWS)))
+ {
+ // If we're in a multipart/digest, and this document is of type
+ // message/rfc822, then it's appropriate to emit no headers.
+ //
+ }
+ else
+ {
+ char *message_headers = 0;
+ char *content_headers = 0;
+ char *content_type_header = 0;
+ status = divide_content_headers(m_other,
+ &message_headers,
+ &content_headers,
+ &content_type_header);
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ /* First, write out all of the headers that refer to the message
+ itself (From, Subject, MIME-Version, etc.)
+ */
+ if (message_headers)
+ {
+ PUSH(message_headers);
+ PR_Free(message_headers);
+ message_headers = 0;
+ }
+
+ /* Now allow the crypto library to (potentially) insert some text
+ (it may want to wrap the body in an envelope.) */
+ if (!m_parent) {
+ status = m_state->BeginCryptoEncapsulation();
+ if (NS_FAILED(status)) goto FAIL;
+ }
+
+ /* Now make sure there's a Content-Type header.
+ */
+ if (!content_type_header)
+ {
+ NS_ASSERTION(m_type && *m_type, "null ptr");
+ bool needsCharset = mime_type_needs_charset(m_type ? m_type : TEXT_PLAIN);
+ if (needsCharset)
+ {
+ content_type_header = PR_smprintf("Content-Type: %s; charset=%s" CRLF,
+ (m_type ? m_type : TEXT_PLAIN), m_charset_name);
+ }
+ else
+ content_type_header = PR_smprintf("Content-Type: %s" CRLF,
+ (m_type ? m_type : TEXT_PLAIN));
+ if (!content_type_header)
+ {
+ if (content_headers)
+ PR_Free(content_headers);
+ status = NS_ERROR_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+ }
+
+ /* If this is a compound object, tack a boundary string onto the
+ Content-Type header. this
+ */
+ if (m_numchildren > 0)
+ {
+ int L;
+ char *ct2;
+ NS_ASSERTION(m_type, "null ptr");
+
+ if (!separator)
+ {
+ separator = mime_make_separator("");
+ if (!separator)
+ {
+ status = NS_ERROR_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+ }
+
+ L = PL_strlen(content_type_header);
+
+ if (content_type_header[L-1] == '\n')
+ content_type_header[--L] = 0;
+ if (content_type_header[L-1] == '\r')
+ content_type_header[--L] = 0;
+
+ ct2 = PR_smprintf("%s;\r\n boundary=\"%s\"" CRLF, content_type_header, separator);
+ PR_Free(content_type_header);
+ if (!ct2)
+ {
+ if (content_headers)
+ PR_Free(content_headers);
+ status = NS_ERROR_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+
+ content_type_header = ct2;
+ }
+
+ // Now write out the Content-Type header...
+ NS_ASSERTION(content_type_header && *content_type_header, "null ptr");
+ PUSH(content_type_header);
+ PR_Free(content_type_header);
+ content_type_header = 0;
+
+ /* ...followed by all of the other headers that refer to the body of
+ the message (Content-Transfer-Encoding, Content-Dispositon, etc.)
+ */
+ if (content_headers)
+ {
+ PUSH(content_headers);
+ PR_Free(content_headers);
+ content_headers = 0;
+ }
+ }
+
+ PUSH(CRLF); // A blank line, to mark the end of headers.
+
+ m_firstBlock = true;
+ /* only convert if we need to tag charset */
+ m_needIntlConversion = mime_type_needs_charset(m_type);
+
+ if (m_buffer)
+ {
+ status = PushBody(m_buffer, PL_strlen(m_buffer));
+ if (NS_FAILED(status))
+ goto FAIL;
+ }
+ else if (m_file)
+ {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), m_file);
+ if (NS_FAILED(rv))
+ {
+ // mysteriously disappearing?
+ nsCOMPtr<nsIMsgSendReport> sendReport;
+ m_state->GetSendReport(getter_AddRefs(sendReport));
+ if (sendReport)
+ {
+ nsAutoString error_msg;
+ nsMsgBuildMessageWithTmpFile(m_file, error_msg);
+ sendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false);
+ }
+ status = NS_MSG_UNABLE_TO_OPEN_TMP_FILE;
+ goto FAIL;
+ }
+
+ nsCString curLine;
+ bool more = true;
+
+ /* Kludge to avoid having to allocate memory on the toy computers... */
+ if (!mime_mailto_stream_read_buffer)
+ {
+ mime_mailto_stream_read_buffer = (char *) PR_Malloc(MIME_BUFFER_SIZE);
+ if (!mime_mailto_stream_read_buffer)
+ {
+ status = NS_ERROR_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+ }
+
+ char *buffer = mime_mailto_stream_read_buffer;
+ if (m_strip_sensitive_headers)
+ {
+ // We are attaching a message, so we should be careful to
+ // strip out certain sensitive internal header fields.
+ bool skipping = false;
+ nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>);
+ NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ while (more)
+ {
+ // NS_ReadLine doesn't return line termination chars.
+ rv = NS_ReadLine(inputStream.get(), lineBuffer.get(), curLine, &more);
+
+ curLine.Append(CRLF);
+
+ char *line = (char *) curLine.get();
+ if (skipping) {
+ if (*line == ' ' || *line == '\t')
+ continue;
+ else
+ skipping = false;
+ }
+
+ if (!PL_strncasecmp(line, "From -", 6) ||
+ !PL_strncasecmp(line, "BCC:", 4) ||
+ !PL_strncasecmp(line, "FCC:", 4) ||
+ !PL_strncasecmp(line, CONTENT_LENGTH ":", CONTENT_LENGTH_LEN+1) ||
+ !PL_strncasecmp(line, "Lines:", 6) ||
+ !PL_strncasecmp(line, "Status:", 7) ||
+ !PL_strncasecmp(line, X_MOZILLA_STATUS ":", X_MOZILLA_STATUS_LEN+1) ||
+ !PL_strncasecmp(line, X_MOZILLA_STATUS2 ":", X_MOZILLA_STATUS2_LEN+1) ||
+ !PL_strncasecmp(line, X_MOZILLA_DRAFT_INFO ":", X_MOZILLA_DRAFT_INFO_LEN+1) ||
+ !PL_strncasecmp(line, X_MOZILLA_NEWSHOST ":", X_MOZILLA_NEWSHOST_LEN+1) ||
+ !PL_strncasecmp(line, X_UIDL ":", X_UIDL_LEN+1) ||
+ !PL_strncasecmp(line, "X-VM-", 5)) /* hi Kyle */
+ {
+ skipping = true;
+ continue;
+ }
+
+ PUSH(line);
+
+ if (curLine.Length() == 2) {
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(inputStream);
+ // seek back the amount of data left in the line buffer...
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, lineBuffer->start - lineBuffer->end);
+ break; // Now can do normal reads for the body.
+ }
+ }
+ lineBuffer = nullptr;
+ }
+
+ while (NS_SUCCEEDED(status))
+ {
+ uint32_t bytesRead;
+ nsresult rv = inputStream->Read(buffer, MIME_BUFFER_SIZE, &bytesRead);
+ if (NS_FAILED(rv))
+ {
+ nsCOMPtr<nsIMsgSendReport> sendReport;
+ m_state->GetSendReport(getter_AddRefs(sendReport));
+ if (sendReport)
+ {
+ nsAutoString error_msg;
+ nsMsgBuildMessageWithFile(m_file, error_msg);
+ sendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false);
+ status = NS_MSG_UNABLE_TO_OPEN_FILE;
+ goto FAIL;
+ }
+ }
+ status = PushBody(buffer, bytesRead);
+ if (NS_FAILED(status))
+ goto FAIL;
+ if (bytesRead < MIME_BUFFER_SIZE)
+ break;
+ }
+ }
+
+ if (m_encoder)
+ {
+ nsresult rv = m_encoder->Flush();
+ m_encoder = nullptr;
+ needToWriteCRLFAfterEncodedBody = !m_parent;
+ if (NS_FAILED(rv))
+ {
+ // XXX -1 is not a valid nsresult
+ status = static_cast<nsresult>(-1);
+ goto FAIL;
+ }
+ }
+
+ //
+ // Ok, from here we loop and drive the the output of all children
+ // for this message.
+ //
+ if (m_numchildren > 0)
+ {
+ bool writeSeparator = true;
+
+ for (int i = 0 ; i < m_numchildren ; i ++)
+ {
+ if (writeSeparator)
+ {
+ PUSH(CRLF);
+ PUSH("--");
+
+ PUSH(separator);
+ PUSH(CRLF);
+ }
+
+ status = m_children[i]->Write();
+ if (NS_FAILED(status))
+ goto FAIL;
+
+ // XXX SKIP_EMPTY_PART (= 1966) is not a valid nsresult
+ if (status == static_cast<nsresult>(SKIP_EMPTY_PART))
+ writeSeparator = false;
+ else
+ writeSeparator = true;
+ }
+
+ PUSH(CRLF);
+ PUSH("--");
+ PUSH(separator);
+ PUSH("--");
+ PUSH(CRLF);
+ }
+ else if (needToWriteCRLFAfterEncodedBody)
+ PUSH(CRLF);
+
+FAIL:
+ PR_FREEIF(separator);
+ return status;
+}
+
diff --git a/mailnews/compose/src/nsMsgSendPart.h b/mailnews/compose/src/nsMsgSendPart.h
new file mode 100644
index 000000000..84d12bbd7
--- /dev/null
+++ b/mailnews/compose/src/nsMsgSendPart.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MsgSendPart_H_
+#define _MsgSendPart_H_
+
+#include "msgCore.h"
+#include "prprf.h" /* should be defined into msgCore.h? */
+#include "nsMsgSend.h"
+
+namespace mozilla {
+namespace mailnews {
+class MimeEncoder;
+}
+}
+
+typedef int (*MSG_SendPartWriteFunc)(const char* line, int32_t size,
+ bool isheader, void* closure);
+
+class nsMsgSendPart {
+ typedef mozilla::mailnews::MimeEncoder MimeEncoder;
+public:
+ explicit nsMsgSendPart(nsIMsgSend* state, const char *part_charset = NULL);
+ virtual ~nsMsgSendPart(); // Note that the destructor also destroys
+ // any children that were added.
+
+ virtual nsresult Write();
+
+ virtual nsresult SetFile(nsIFile *filename);
+ const nsIFile *GetFile() {return m_file;}
+
+ virtual nsresult SetBuffer(const char* buffer);
+ const char *GetBuffer() {return m_buffer;}
+
+ virtual nsresult SetType(const char* type);
+ const char *GetType() {return m_type;}
+
+ const char *GetCharsetName() {return m_charset_name;}
+
+ virtual nsresult SetOtherHeaders(const char* other);
+ const char *SetOtherHeaders() {return m_other;}
+ virtual nsresult AppendOtherHeaders(const char* moreother);
+
+ virtual nsresult SetMimeDeliveryState(nsIMsgSend* state);
+
+ // Note that the nsMsgSendPart class will take over ownership of the
+ // MimeEncoderData* object, deleting it when it chooses. (This is
+ // necessary because deleting these objects is the only current way to
+ // flush out the data in them.)
+ void SetEncoder(MimeEncoder* encoder) {m_encoder = encoder;}
+ MimeEncoder *GetEncoder() {return m_encoder;}
+
+ void SetStripSensitiveHeaders(bool value)
+ {
+ m_strip_sensitive_headers = value;
+ }
+ bool GetStripSensitiveHeaders() {return m_strip_sensitive_headers;}
+
+ virtual nsresult AddChild(nsMsgSendPart* child);
+
+ int32_t GetNumChildren() {return m_numchildren;}
+ nsMsgSendPart *GetChild(int32_t which);
+ nsMsgSendPart *DetachChild(int32_t which);
+
+ virtual nsresult SetMainPart(bool value);
+ bool IsMainPart()
+ {
+ return m_mainpart;
+ }
+ nsCString m_partNum;
+protected:
+ nsresult CopyString(char** dest, const char* src);
+ nsresult PushBody(const char* buffer, int32_t length);
+
+ nsCOMPtr<nsIMsgSend> m_state;
+ nsMsgSendPart *m_parent;
+ nsCOMPtr <nsIFile> m_file;
+ char *m_buffer;
+ char *m_type;
+ char *m_other;
+ char m_charset_name[64+1]; // charset name associated with this part
+ bool m_strip_sensitive_headers;
+ nsAutoPtr<MimeEncoder> m_encoder;
+
+ nsMsgSendPart **m_children;
+ int32_t m_numchildren;
+
+ // Data used while actually writing.
+ bool m_firstBlock;
+ bool m_needIntlConversion;
+
+ bool m_mainpart;
+
+ bool m_just_hit_CR;
+
+ static int32_t M_counter;
+};
+
+#endif /* _MsgSendPart_H_ */
diff --git a/mailnews/compose/src/nsMsgSendReport.cpp b/mailnews/compose/src/nsMsgSendReport.cpp
new file mode 100644
index 000000000..1bc26ca8a
--- /dev/null
+++ b/mailnews/compose/src/nsMsgSendReport.cpp
@@ -0,0 +1,437 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgSendReport.h"
+
+#include "msgCore.h"
+#include "nsIMsgCompose.h"
+#include "nsMsgCompCID.h"
+#include "nsMsgPrompts.h"
+#include "nsError.h"
+#include "nsComposeStrings.h"
+#include "nsIStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+
+NS_IMPL_ISUPPORTS(nsMsgProcessReport, nsIMsgProcessReport)
+
+nsMsgProcessReport::nsMsgProcessReport()
+{
+ Reset();
+}
+
+nsMsgProcessReport::~nsMsgProcessReport()
+{
+}
+
+/* attribute boolean proceeded; */
+NS_IMETHODIMP nsMsgProcessReport::GetProceeded(bool *aProceeded)
+{
+ NS_ENSURE_ARG_POINTER(aProceeded);
+ *aProceeded = mProceeded;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgProcessReport::SetProceeded(bool aProceeded)
+{
+ mProceeded = aProceeded;
+ return NS_OK;
+}
+
+/* attribute nsresult error; */
+NS_IMETHODIMP nsMsgProcessReport::GetError(nsresult *aError)
+{
+ NS_ENSURE_ARG_POINTER(aError);
+ *aError = mError;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgProcessReport::SetError(nsresult aError)
+{
+ mError = aError;
+ return NS_OK;
+}
+
+/* attribute wstring message; */
+NS_IMETHODIMP nsMsgProcessReport::GetMessage(char16_t * *aMessage)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+ *aMessage = ToNewUnicode(mMessage);
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgProcessReport::SetMessage(const char16_t * aMessage)
+{
+ mMessage = aMessage;
+ return NS_OK;
+}
+
+/* void Reset (); */
+NS_IMETHODIMP nsMsgProcessReport::Reset()
+{
+ mProceeded = false;
+ mError = NS_OK;
+ mMessage.Truncate();
+
+ return NS_OK;
+}
+
+
+NS_IMPL_ISUPPORTS(nsMsgSendReport, nsIMsgSendReport)
+
+nsMsgSendReport::nsMsgSendReport()
+{
+ uint32_t i;
+ for (i = 0; i <= SEND_LAST_PROCESS; i ++)
+ mProcessReport[i] = new nsMsgProcessReport();
+
+ Reset();
+}
+
+nsMsgSendReport::~nsMsgSendReport()
+{
+ uint32_t i;
+ for (i = 0; i <= SEND_LAST_PROCESS; i ++)
+ mProcessReport[i] = nullptr;
+}
+
+/* attribute long currentProcess; */
+NS_IMETHODIMP nsMsgSendReport::GetCurrentProcess(int32_t *aCurrentProcess)
+{
+ NS_ENSURE_ARG_POINTER(aCurrentProcess);
+ *aCurrentProcess = mCurrentProcess;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgSendReport::SetCurrentProcess(int32_t aCurrentProcess)
+{
+ if (aCurrentProcess < 0 || aCurrentProcess > SEND_LAST_PROCESS)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ mCurrentProcess = aCurrentProcess;
+ if (mProcessReport[mCurrentProcess])
+ mProcessReport[mCurrentProcess]->SetProceeded(true);
+
+ return NS_OK;
+}
+
+/* attribute long deliveryMode; */
+NS_IMETHODIMP nsMsgSendReport::GetDeliveryMode(int32_t *aDeliveryMode)
+{
+ NS_ENSURE_ARG_POINTER(aDeliveryMode);
+ *aDeliveryMode = mDeliveryMode;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgSendReport::SetDeliveryMode(int32_t aDeliveryMode)
+{
+ mDeliveryMode = aDeliveryMode;
+ return NS_OK;
+}
+
+/* void Reset (); */
+NS_IMETHODIMP nsMsgSendReport::Reset()
+{
+ uint32_t i;
+ for (i = 0; i <= SEND_LAST_PROCESS; i ++)
+ if (mProcessReport[i])
+ mProcessReport[i]->Reset();
+
+ mCurrentProcess = 0;
+ mDeliveryMode = 0;
+ mAlreadyDisplayReport = false;
+
+ return NS_OK;
+}
+
+/* void setProceeded (in long process, in boolean proceeded); */
+NS_IMETHODIMP nsMsgSendReport::SetProceeded(int32_t process, bool proceeded)
+{
+ if (process < process_Current || process > SEND_LAST_PROCESS)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (process == process_Current)
+ process = mCurrentProcess;
+
+ if (!mProcessReport[process])
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mProcessReport[process]->SetProceeded(proceeded);
+}
+
+/* void setError (in long process, in nsresult error, in boolean overwriteError); */
+NS_IMETHODIMP nsMsgSendReport::SetError(int32_t process, nsresult newError, bool overwriteError)
+{
+ if (process < process_Current || process > SEND_LAST_PROCESS)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (process == process_Current)
+ {
+ if (mCurrentProcess == process_Current)
+ // We don't know what we're currently trying to do
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ process = mCurrentProcess;
+ }
+
+ if (!mProcessReport[process])
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult currError = NS_OK;
+ mProcessReport[process]->GetError(&currError);
+ if (overwriteError || NS_SUCCEEDED(currError))
+ return mProcessReport[process]->SetError(newError);
+ else
+ return NS_OK;
+}
+
+/* void setMessage (in long process, in wstring message, in boolean overwriteMessage); */
+NS_IMETHODIMP nsMsgSendReport::SetMessage(int32_t process, const char16_t *message, bool overwriteMessage)
+{
+ if (process < process_Current || process > SEND_LAST_PROCESS)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (process == process_Current)
+ {
+ if (mCurrentProcess == process_Current)
+ // We don't know what we're currently trying to do
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ process = mCurrentProcess;
+ }
+
+ if (!mProcessReport[process])
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsString currMessage;
+ mProcessReport[process]->GetMessage(getter_Copies(currMessage));
+ if (overwriteMessage || currMessage.IsEmpty())
+ return mProcessReport[process]->SetMessage(message);
+ else
+ return NS_OK;
+}
+
+/* nsIMsgProcessReport getProcessReport (in long process); */
+NS_IMETHODIMP nsMsgSendReport::GetProcessReport(int32_t process, nsIMsgProcessReport **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ if (process < process_Current || process > SEND_LAST_PROCESS)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (process == process_Current)
+ {
+ if (mCurrentProcess == process_Current)
+ // We don't know what we're currently trying to do
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ process = mCurrentProcess;
+ }
+
+ NS_IF_ADDREF(*_retval = mProcessReport[process]);
+ return NS_OK;
+}
+
+/* nsresult displayReport (in nsIPrompt prompt, in boolean showErrorOnly, in boolean dontShowReportTwice); */
+NS_IMETHODIMP nsMsgSendReport::DisplayReport(nsIPrompt *prompt, bool showErrorOnly, bool dontShowReportTwice, nsresult *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ NS_ENSURE_TRUE(mCurrentProcess >= 0 && mCurrentProcess <= SEND_LAST_PROCESS,
+ NS_ERROR_NOT_INITIALIZED);
+
+ nsresult currError = NS_OK;
+ mProcessReport[mCurrentProcess]->GetError(&currError);
+ *_retval = currError;
+
+ if (dontShowReportTwice && mAlreadyDisplayReport)
+ return NS_OK;
+
+ if (showErrorOnly && NS_SUCCEEDED(currError))
+ return NS_OK;
+
+ nsString currMessage;
+ mProcessReport[mCurrentProcess]->GetMessage(getter_Copies(currMessage));
+
+ nsresult rv; // don't step on currError.
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle));
+ if (NS_FAILED(rv))
+ {
+ //TODO need to display a generic hardcoded message
+ mAlreadyDisplayReport = true;
+ return NS_OK;
+ }
+
+ nsString dialogTitle;
+ nsString dialogMessage;
+
+ if (NS_SUCCEEDED(currError))
+ {
+ //TODO display a success error message
+ return NS_OK;
+ }
+
+ //Do we have an explanation of the error? if no, try to build one...
+ if (currMessage.IsEmpty())
+ {
+#ifdef __GNUC__
+// Temporary workaroung until bug 783526 is fixed.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wswitch"
+#endif
+ switch (currError)
+ {
+ case NS_BINDING_ABORTED:
+ case NS_ERROR_SEND_FAILED:
+ case NS_ERROR_SEND_FAILED_BUT_NNTP_OK:
+ case NS_MSG_FAILED_COPY_OPERATION:
+ case NS_MSG_UNABLE_TO_SEND_LATER:
+ case NS_MSG_UNABLE_TO_SAVE_DRAFT:
+ case NS_MSG_UNABLE_TO_SAVE_TEMPLATE:
+ //Ignore, don't need to repeat ourself.
+ break;
+ default:
+ const char16_t* errorString = errorStringNameForErrorCode(currError);
+ nsMsgGetMessageByName(errorString, currMessage);
+ break;
+ }
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ }
+
+ if (mDeliveryMode == nsIMsgCompDeliverMode::Now || mDeliveryMode == nsIMsgCompDeliverMode::SendUnsent)
+ {
+ // SMTP is taking care of it's own error message and will return NS_ERROR_BUT_DONT_SHOW_ALERT as error code.
+ // In that case, we must not show an alert ourself.
+ if (currError == NS_ERROR_BUT_DONT_SHOW_ALERT)
+ {
+ mAlreadyDisplayReport = true;
+ return NS_OK;
+ }
+
+ bundle->GetStringFromName(u"sendMessageErrorTitle",
+ getter_Copies(dialogTitle));
+
+ const char16_t* preStrName = u"sendFailed";
+ bool askToGoBackToCompose = false;
+ switch (mCurrentProcess)
+ {
+ case process_BuildMessage :
+ preStrName = u"sendFailed";
+ askToGoBackToCompose = false;
+ break;
+ case process_NNTP :
+ preStrName = u"sendFailed";
+ askToGoBackToCompose = false;
+ break;
+ case process_SMTP :
+ bool nntpProceeded;
+ mProcessReport[process_NNTP]->GetProceeded(&nntpProceeded);
+ if (nntpProceeded)
+ preStrName = u"sendFailedButNntpOk";
+ else
+ preStrName = u"sendFailed";
+ askToGoBackToCompose = false;
+ break;
+ case process_Copy:
+ preStrName = u"failedCopyOperation";
+ askToGoBackToCompose = (mDeliveryMode == nsIMsgCompDeliverMode::Now);
+ break;
+ case process_FCC:
+ preStrName = u"failedCopyOperation";
+ askToGoBackToCompose = (mDeliveryMode == nsIMsgCompDeliverMode::Now);
+ break;
+ }
+ bundle->GetStringFromName(preStrName, getter_Copies(dialogMessage));
+
+ //Do we already have an error message?
+ if (!askToGoBackToCompose && currMessage.IsEmpty())
+ {
+ //we don't have an error description but we can put a generic explanation
+ bundle->GetStringFromName(u"genericFailureExplanation",
+ getter_Copies(currMessage));
+ }
+
+ if (!currMessage.IsEmpty())
+ {
+ //Don't need to repeat ourself!
+ if (!currMessage.Equals(dialogMessage))
+ {
+ if (!dialogMessage.IsEmpty())
+ dialogMessage.Append(char16_t('\n'));
+ dialogMessage.Append(currMessage);
+ }
+ }
+
+ if (askToGoBackToCompose)
+ {
+ bool oopsGiveMeBackTheComposeWindow = true;
+ nsString text1;
+ bundle->GetStringFromName(u"returnToComposeWindowQuestion",
+ getter_Copies(text1));
+ if (!dialogMessage.IsEmpty())
+ dialogMessage.AppendLiteral("\n");
+ dialogMessage.Append(text1);
+ nsMsgAskBooleanQuestionByString(prompt, dialogMessage.get(), &oopsGiveMeBackTheComposeWindow, dialogTitle.get());
+ if (!oopsGiveMeBackTheComposeWindow)
+ *_retval = NS_OK;
+ }
+ else
+ nsMsgDisplayMessageByString(prompt, dialogMessage.get(), dialogTitle.get());
+ }
+ else
+ {
+ const char16_t* title;
+ const char16_t* messageName;
+
+ switch (mDeliveryMode)
+ {
+ case nsIMsgCompDeliverMode::Later:
+ title = u"sendLaterErrorTitle";
+ messageName = u"unableToSendLater";
+ break;
+
+ case nsIMsgCompDeliverMode::AutoSaveAsDraft:
+ case nsIMsgCompDeliverMode::SaveAsDraft:
+ title = u"saveDraftErrorTitle";
+ messageName = u"unableToSaveDraft";
+ break;
+
+ case nsIMsgCompDeliverMode::SaveAsTemplate:
+ title = u"saveTemplateErrorTitle";
+ messageName = u"unableToSaveTemplate";
+ break;
+
+ default:
+ /* This should never happen! */
+ title = u"sendMessageErrorTitle";
+ messageName = u"sendFailed";
+ break;
+ }
+
+ bundle->GetStringFromName(title, getter_Copies(dialogTitle));
+ bundle->GetStringFromName(messageName,
+ getter_Copies(dialogMessage));
+
+ //Do we have an error message...
+ if (currMessage.IsEmpty())
+ {
+ //we don't have an error description but we can put a generic explanation
+ bundle->GetStringFromName(u"genericFailureExplanation",
+ getter_Copies(currMessage));
+ }
+
+ if (!currMessage.IsEmpty())
+ {
+ if (!dialogMessage.IsEmpty())
+ dialogMessage.Append(char16_t('\n'));
+ dialogMessage.Append(currMessage);
+ }
+ nsMsgDisplayMessageByString(prompt, dialogMessage.get(), dialogTitle.get());
+ }
+
+ mAlreadyDisplayReport = true;
+ return NS_OK;
+}
+
diff --git a/mailnews/compose/src/nsMsgSendReport.h b/mailnews/compose/src/nsMsgSendReport.h
new file mode 100644
index 000000000..3e344f628
--- /dev/null
+++ b/mailnews/compose/src/nsMsgSendReport.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMsgSendReport_h__
+#define __nsMsgSendReport_h__
+
+#include "nsIMsgSendReport.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+
+class nsMsgProcessReport : public nsIMsgProcessReport
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPROCESSREPORT
+
+ nsMsgProcessReport();
+
+private:
+ virtual ~nsMsgProcessReport();
+ bool mProceeded;
+ nsresult mError;
+ nsString mMessage;
+};
+
+
+class nsMsgSendReport : public nsIMsgSendReport
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSENDREPORT
+
+ nsMsgSendReport();
+
+private:
+ virtual ~nsMsgSendReport();
+ #define SEND_LAST_PROCESS process_FCC
+ nsCOMPtr<nsIMsgProcessReport> mProcessReport[SEND_LAST_PROCESS + 1];
+ int32_t mDeliveryMode;
+ int32_t mCurrentProcess;
+ bool mAlreadyDisplayReport;
+};
+
+#endif
diff --git a/mailnews/compose/src/nsSMTPProtocolHandler.js b/mailnews/compose/src/nsSMTPProtocolHandler.js
new file mode 100644
index 000000000..9b9e3ead0
--- /dev/null
+++ b/mailnews/compose/src/nsSMTPProtocolHandler.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var kNetworkProtocolCIDPrefix = "@mozilla.org/network/protocol;1?name=";
+var nsIProtocolHandler = Components.interfaces.nsIProtocolHandler;
+
+function makeProtocolHandler(aProtocol, aDefaultPort, aClassID) {
+ return {
+ classID: Components.ID(aClassID),
+ QueryInterface: XPCOMUtils.generateQI([nsIProtocolHandler]),
+
+ scheme: aProtocol,
+ defaultPort: aDefaultPort,
+ protocolFlags: nsIProtocolHandler.URI_NORELATIVE |
+ nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+ nsIProtocolHandler.URI_NON_PERSISTABLE |
+ nsIProtocolHandler.ALLOWS_PROXY |
+ nsIProtocolHandler.URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT,
+
+ newURI: function (aSpec, aOriginCharset, aBaseURI) {
+ var url = Components.classes["@mozilla.org/messengercompose/smtpurl;1"]
+ .createInstance(Components.interfaces.nsIURI);
+
+ url.spec = aSpec;
+
+ return url;
+ },
+
+ newChannel: function (aURI) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ newChannel2: function(aURI, aLoadInfo)
+ {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ allowPort: function (port, scheme) {
+ return port == aDefaultPort;
+ }
+ };
+}
+
+function nsSMTPProtocolHandler() {}
+
+nsSMTPProtocolHandler.prototype =
+ makeProtocolHandler("smtp",
+ Components.interfaces.nsISmtpUrl.DEFAULT_SMTP_PORT,
+ "b14c2b67-8680-4c11-8d63-9403c7d4f757");
+
+function nsSMTPSProtocolHandler() {}
+
+nsSMTPSProtocolHandler.prototype =
+ makeProtocolHandler("smtps",
+ Components.interfaces.nsISmtpUrl.DEFAULT_SMTPS_PORT,
+ "057d0997-9e3a-411e-b4ee-2602f53fe05f");
+
+var components = [nsSMTPProtocolHandler, nsSMTPSProtocolHandler];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/compose/src/nsSMTPProtocolHandler.manifest b/mailnews/compose/src/nsSMTPProtocolHandler.manifest
new file mode 100644
index 000000000..4d371163f
--- /dev/null
+++ b/mailnews/compose/src/nsSMTPProtocolHandler.manifest
@@ -0,0 +1,4 @@
+component {b14c2b67-8680-4c11-8d63-9403c7d4f757} nsSMTPProtocolHandler.js
+contract @mozilla.org/network/protocol;1?name=smtp {b14c2b67-8680-4c11-8d63-9403c7d4f757}
+component {057d0997-9e3a-411e-b4ee-2602f53fe05f} nsSMTPProtocolHandler.js
+contract @mozilla.org/network/protocol;1?name=smtps {057d0997-9e3a-411e-b4ee-2602f53fe05f}
diff --git a/mailnews/compose/src/nsSmtpProtocol.cpp b/mailnews/compose/src/nsSmtpProtocol.cpp
new file mode 100644
index 000000000..89607224e
--- /dev/null
+++ b/mailnews/compose/src/nsSmtpProtocol.cpp
@@ -0,0 +1,2277 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsSmtpProtocol.h"
+#include "nscore.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISocketTransport.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgCompCID.h"
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsStringGlue.h"
+#include "nsTextFormatter.h"
+#include "nsIMsgIdentity.h"
+#include "nsISmtpServer.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "prerror.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "plbase64.h"
+#include "prnetdb.h"
+#include "prsystem.h"
+#include "nsMsgUtils.h"
+#include "nsIPipe.h"
+#include "nsNetUtil.h"
+#include "nsIPrefService.h"
+#include "nsISSLSocketControl.h"
+#include "nsComposeStrings.h"
+#include "nsIStringBundle.h"
+#include "nsMsgCompUtils.h"
+#include "nsIMsgWindow.h"
+#include "MailNewsTypes2.h" // for nsMsgSocketType and nsMsgAuthMethod
+#include "nsIIDNService.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "mozilla/Services.h"
+#include "mozilla/Attributes.h"
+#include "nsINetAddr.h"
+#include "nsIProxyInfo.h"
+
+#ifndef XP_UNIX
+#include <stdarg.h>
+#endif /* !XP_UNIX */
+
+#undef PostMessage // avoid to collision with WinUser.h
+
+static PRLogModuleInfo *SMTPLogModule = nullptr;
+
+using namespace mozilla::mailnews;
+
+/* the output_buffer_size must be larger than the largest possible line
+ * 2000 seems good for news
+ *
+ * jwz: I increased this to 4k since it must be big enough to hold the
+ * entire button-bar HTML, and with the new "mailto" format, that can
+ * contain arbitrarily long header fields like "references".
+ *
+ * fortezza: proxy auth is huge, buffer increased to 8k (sigh).
+ */
+#define OUTPUT_BUFFER_SIZE (4096*2)
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// TEMPORARY HARD CODED FUNCTIONS
+///////////////////////////////////////////////////////////////////////////////////////////
+
+/* based on in NET_ExplainErrorDetails in mkmessag.c */
+nsresult nsExplainErrorDetails(nsISmtpUrl * aSmtpUrl, nsresult aCode, ...)
+{
+ NS_ENSURE_ARG(aSmtpUrl);
+
+ va_list args;
+
+ nsCOMPtr<nsIPrompt> dialog;
+ aSmtpUrl->GetPrompt(getter_AddRefs(dialog));
+ NS_ENSURE_TRUE(dialog, NS_ERROR_FAILURE);
+
+ char16_t * msg;
+ nsString eMsg;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ va_start (args, aCode);
+
+ const char16_t* exitString;
+#ifdef __GNUC__
+// Temporary workaroung until bug 783526 is fixed.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wswitch"
+#endif
+ switch (aCode)
+ {
+ case NS_ERROR_ILLEGAL_LOCALPART:
+ bundle->GetStringFromName(u"errorIllegalLocalPart",
+ getter_Copies(eMsg));
+ msg = nsTextFormatter::vsmprintf(eMsg.get(), args);
+ break;
+ case NS_ERROR_SMTP_SERVER_ERROR:
+ case NS_ERROR_TCP_READ_ERROR:
+ case NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED:
+ case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1:
+ case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2:
+ case NS_ERROR_SENDING_FROM_COMMAND:
+ case NS_ERROR_SENDING_RCPT_COMMAND:
+ case NS_ERROR_SENDING_DATA_COMMAND:
+ case NS_ERROR_SENDING_MESSAGE:
+ case NS_ERROR_SMTP_GREETING:
+ exitString = errorStringNameForErrorCode(aCode);
+ bundle->GetStringFromName(exitString, getter_Copies(eMsg));
+ msg = nsTextFormatter::vsmprintf(eMsg.get(), args);
+ break;
+ default:
+ NS_WARNING("falling to default error code");
+ bundle->GetStringFromName(u"communicationsError", getter_Copies(eMsg));
+ msg = nsTextFormatter::smprintf(eMsg.get(), aCode);
+ break;
+ }
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+ if (msg)
+ {
+ rv = dialog->Alert(nullptr, msg);
+ nsTextFormatter::smprintf_free(msg);
+ }
+
+ va_end (args);
+
+ return rv;
+}
+
+/* RFC 1891 -- extended smtp value encoding scheme
+
+ 5. Additional parameters for RCPT and MAIL commands
+
+ The extended RCPT and MAIL commands are issued by a client when it wishes to request a DSN from the
+ server, under certain conditions, for a particular recipient. The extended RCPT and MAIL commands are
+ identical to the RCPT and MAIL commands defined in [1], except that one or more of the following parameters
+ appear after the sender or recipient address, respectively. The general syntax for extended SMTP commands is
+ defined in [4].
+
+ NOTE: Although RFC 822 ABNF is used to describe the syntax of these parameters, they are not, in the
+ language of that document, "structured field bodies". Therefore, while parentheses MAY appear within an
+ emstp-value, they are not recognized as comment delimiters.
+
+ The syntax for "esmtp-value" in [4] does not allow SP, "=", control characters, or characters outside the
+ traditional ASCII range of 1- 127 decimal to be transmitted in an esmtp-value. Because the ENVID and
+ ORCPT parameters may need to convey values outside this range, the esmtp-values for these parameters are
+ encoded as "xtext". "xtext" is formally defined as follows:
+
+ xtext = *( xchar / hexchar )
+
+ xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive, except for "+" and "=".
+
+ ; "hexchar"s are intended to encode octets that cannot appear
+ ; as ASCII characters within an esmtp-value.
+
+ hexchar = ASCII "+" immediately followed by two upper case hexadecimal digits
+
+ When encoding an octet sequence as xtext:
+
+ + Any ASCII CHAR between "!" and "~" inclusive, except for "+" and "=",
+ MAY be encoded as itself. (A CHAR in this range MAY instead be encoded as a "hexchar", at the
+ implementor's discretion.)
+
+ + ASCII CHARs that fall outside the range above must be encoded as
+ "hexchar".
+
+ */
+/* caller must free the return buffer */
+static char *
+esmtp_value_encode(const char *addr)
+{
+ char *buffer = (char *) PR_Malloc(512); /* esmtp ORCPT allow up to 500 chars encoded addresses */
+ char *bp = buffer, *bpEnd = buffer+500;
+ int len, i;
+
+ if (!buffer) return NULL;
+
+ *bp=0;
+ if (! addr || *addr == 0) /* this will never happen */
+ return buffer;
+
+ for (i=0, len=PL_strlen(addr); i < len && bp < bpEnd; i++)
+ {
+ if (*addr >= 0x21 &&
+ *addr <= 0x7E &&
+ *addr != '+' &&
+ *addr != '=')
+ {
+ *bp++ = *addr++;
+ }
+ else
+ {
+ PR_snprintf(bp, bpEnd-bp, "+%.2X", ((int)*addr++));
+ bp += PL_strlen(bp);
+ }
+ }
+ *bp=0;
+ return buffer;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// END OF TEMPORARY HARD CODED FUNCTIONS
+///////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+NS_IMPL_ISUPPORTS_INHERITED(nsSmtpProtocol, nsMsgAsyncWriteProtocol,
+ msgIOAuth2ModuleListener)
+#else
+NS_IMPL_ADDREF_INHERITED(nsSmtpProtocol, nsMsgAsyncWriteProtocol)
+NS_IMPL_RELEASE_INHERITED(nsSmtpProtocol, nsMsgAsyncWriteProtocol)
+
+NS_INTERFACE_MAP_BEGIN(nsSmtpProtocol)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgAsyncWriteProtocol)
+// TODO: See if we can use NS_IMPL_ISUPPORTS_INHERITED: https://hg.mozilla.org/comm-central/diff/eae1195fde6d/mailnews/compose/src/nsSmtpProtocol.cpp
+#endif
+nsSmtpProtocol::nsSmtpProtocol(nsIURI * aURL)
+ : nsMsgAsyncWriteProtocol(aURL)
+ , m_dataBuf(nullptr)
+{
+}
+
+nsSmtpProtocol::~nsSmtpProtocol()
+{
+ // free our local state
+ PR_FREEIF(m_dataBuf);
+}
+
+void nsSmtpProtocol::Initialize(nsIURI * aURL)
+{
+ NS_PRECONDITION(aURL, "invalid URL passed into Smtp Protocol");
+ nsresult rv = NS_OK;
+
+ m_flags = 0;
+ m_prefAuthMethods = 0;
+ m_failedAuthMethods = 0;
+ m_currentAuthMethod = 0;
+ m_usernamePrompted = false;
+ m_prefSocketType = nsMsgSocketType::trySTARTTLS;
+ m_tlsInitiated = false;
+
+ m_urlErrorState = NS_ERROR_FAILURE;
+
+ if (!SMTPLogModule)
+ SMTPLogModule = PR_NewLogModule("SMTP");
+
+ if (aURL)
+ m_runningURL = do_QueryInterface(aURL);
+
+ // extract out message feedback if there is any.
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL);
+ if (mailnewsUrl)
+ mailnewsUrl->GetStatusFeedback(getter_AddRefs(m_statusFeedback));
+
+ m_dataBuf = (char *) PR_Malloc(sizeof(char) * OUTPUT_BUFFER_SIZE);
+ m_dataBufSize = OUTPUT_BUFFER_SIZE;
+
+ m_nextState = SMTP_START_CONNECT;
+ m_nextStateAfterResponse = SMTP_START_CONNECT;
+ m_responseCode = 0;
+ m_previousResponseCode = 0;
+ m_continuationResponse = -1;
+ m_tlsEnabled = false;
+ m_addressesLeft = 0;
+
+ m_sendDone = false;
+
+ m_sizelimit = 0;
+ m_totalMessageSize = 0;
+ nsCOMPtr<nsIFile> file;
+ m_runningURL->GetPostMessageFile(getter_AddRefs(file));
+ if (file)
+ file->GetFileSize(&m_totalMessageSize);
+
+ m_originalContentLength = 0;
+ m_totalAmountRead = 0;
+
+ m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true);
+ // ** may want to consider caching the server capability to save lots of
+ // round trip communication between the client and server
+ int32_t authMethod = 0;
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer));
+ if (smtpServer) {
+ smtpServer->GetAuthMethod(&authMethod);
+ smtpServer->GetSocketType(&m_prefSocketType);
+ smtpServer->GetHelloArgument(getter_Copies(m_helloArgument));
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ // Query for OAuth2 support. If the SMTP server preferences don't allow
+ // for OAuth2, then don't carry around the OAuth2 module any longer
+ // since we won't need it.
+ mOAuth2Support = do_CreateInstance(MSGIOAUTH2MODULE_CONTRACTID);
+ if (mOAuth2Support)
+ {
+ bool supportsOAuth = false;
+ mOAuth2Support->InitFromSmtp(smtpServer, &supportsOAuth);
+ if (!supportsOAuth)
+ mOAuth2Support = nullptr;
+ }
+#endif
+ }
+ InitPrefAuthMethods(authMethod);
+
+ nsAutoCString hostName;
+ int32_t port = 0;
+
+ aURL->GetPort(&port);
+ aURL->GetAsciiHost(hostName);
+
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP Connecting to: %s", hostName.get()));
+
+ // When we are making a secure connection, we need to make sure that we
+ // pass an interface requestor down to the socket transport so that PSM can
+ // retrieve a nsIPrompt instance if needed.
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ nsCOMPtr<nsISmtpUrl> smtpUrl(do_QueryInterface(aURL));
+ if (smtpUrl)
+ smtpUrl->GetNotificationCallbacks(getter_AddRefs(callbacks));
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ rv = MsgExamineForProxy(this, getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) proxyInfo = nullptr;
+
+ if (m_prefSocketType == nsMsgSocketType::SSL)
+ rv = OpenNetworkSocketWithInfo(hostName.get(), port, "ssl", proxyInfo,
+ callbacks);
+ else if (m_prefSocketType != nsMsgSocketType::plain)
+ {
+ rv = OpenNetworkSocketWithInfo(hostName.get(), port, "starttls",
+ proxyInfo, callbacks);
+ if (NS_FAILED(rv) && m_prefSocketType == nsMsgSocketType::trySTARTTLS)
+ {
+ m_prefSocketType = nsMsgSocketType::plain;
+ rv = OpenNetworkSocketWithInfo(hostName.get(), port, nullptr,
+ proxyInfo, callbacks);
+ }
+ }
+ else
+ rv = OpenNetworkSocketWithInfo(hostName.get(), port, nullptr, proxyInfo,
+ callbacks);
+}
+
+void nsSmtpProtocol::AppendHelloArgument(nsACString& aResult)
+{
+ nsresult rv;
+
+ // is a custom EHLO/HELO argument configured for the transport to be used?
+ if (!m_helloArgument.IsEmpty())
+ {
+ aResult += m_helloArgument;
+ }
+ else
+ {
+ // is a FQDN known for this system?
+ char hostName[256];
+ PR_GetSystemInfo(PR_SI_HOSTNAME_UNTRUNCATED, hostName, sizeof hostName);
+ if ((hostName[0] != '\0') && (strchr(hostName, '.') != NULL))
+ {
+ nsDependentCString cleanedHostName(hostName);
+ // avoid problems with hostnames containing newlines/whitespace
+ cleanedHostName.StripWhitespace();
+ aResult += cleanedHostName;
+ }
+ else
+ {
+ nsCOMPtr<nsINetAddr> iaddr; // IP address for this connection
+ // our transport is always a nsISocketTransport
+ nsCOMPtr<nsISocketTransport> socketTransport = do_QueryInterface(m_transport);
+ // should return the interface ip of the SMTP connection
+ // minimum case - see bug 68877 and RFC 2821, chapter 4.1.1.1
+ rv = socketTransport->GetScriptableSelfAddr(getter_AddRefs(iaddr));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ // turn it into a string
+ nsCString ipAddressString;
+ rv = iaddr->GetAddress(ipAddressString);
+ if (NS_SUCCEEDED(rv))
+ {
+#ifdef DEBUG
+ bool v4mapped = false;
+ iaddr->GetIsV4Mapped(&v4mapped);
+ NS_ASSERTION(!v4mapped,
+ "unexpected IPv4-mapped IPv6 address");
+#endif
+
+ uint16_t family = nsINetAddr::FAMILY_INET;
+ iaddr->GetFamily(&family);
+
+ if (family == nsINetAddr::FAMILY_INET6) // IPv6 style address?
+ aResult.AppendLiteral("[IPv6:");
+ else
+ aResult.AppendLiteral("[");
+
+ aResult.Append(ipAddressString);
+ aResult.Append(']');
+ }
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// we suppport the nsIStreamListener interface
+////////////////////////////////////////////////////////////////////////////////////////////
+
+// stop binding is a "notification" informing us that the stream
+// associated with aURL is going away.
+NS_IMETHODIMP nsSmtpProtocol::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
+ nsresult aStatus)
+{
+ bool connDroppedDuringAuth = NS_SUCCEEDED(aStatus) && !m_sendDone &&
+ (m_nextStateAfterResponse == SMTP_AUTH_LOGIN_STEP0_RESPONSE ||
+ m_nextStateAfterResponse == SMTP_AUTH_LOGIN_RESPONSE);
+ // ignore errors handling the QUIT command so fcc can continue.
+ if (m_sendDone && NS_FAILED(aStatus))
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info,
+ ("SMTP connection error quitting %lx, ignoring ", aStatus));
+ aStatus = NS_OK;
+ }
+ if (NS_SUCCEEDED(aStatus) && !m_sendDone) {
+ // if we are getting OnStopRequest() with NS_OK,
+ // but we haven't finished clean, that's spells trouble.
+ // it means that the server has dropped us before we could send the whole mail
+ // for example, see bug #200647
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info,
+ ("SMTP connection dropped after %ld total bytes read", m_totalAmountRead));
+ if (!connDroppedDuringAuth)
+ nsMsgAsyncWriteProtocol::OnStopRequest(nullptr, ctxt, NS_ERROR_NET_INTERRUPT);
+ }
+ else
+ nsMsgAsyncWriteProtocol::OnStopRequest(nullptr, ctxt, aStatus);
+
+ // okay, we've been told that the send is done and the connection is going away. So
+ // we need to release all of our state
+ nsresult rv = nsMsgAsyncWriteProtocol::CloseSocket();
+ // If the server dropped the connection when we were expecting
+ // a login response, reprompt for password, and if the user asks,
+ // retry the url.
+ if (connDroppedDuringAuth)
+ {
+ nsCOMPtr<nsIURI> runningURI = do_QueryInterface(m_runningURL);
+ nsresult rv = AuthLoginResponse(nullptr, 0);
+ if (NS_FAILED(rv))
+ return rv;
+ return LoadUrl(runningURI, ctxt);
+ }
+
+ return rv;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// End of nsIStreamListenerSupport
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+void nsSmtpProtocol::UpdateStatus(const char16_t* aStatusName)
+{
+ if (m_statusFeedback)
+ {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService) return;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle));
+ if (NS_FAILED(rv)) return;
+ nsString msg;
+ bundle->GetStringFromName(aStatusName, getter_Copies(msg));
+ UpdateStatusWithString(msg.get());
+ }
+}
+
+void nsSmtpProtocol::UpdateStatusWithString(const char16_t * aStatusString)
+{
+ if (m_statusFeedback && aStatusString)
+ m_statusFeedback->ShowStatusString(nsDependentString(aStatusString));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Begin protocol state machine functions...
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+/*
+ * gets the response code from the SMTP server and the
+ * response line
+ */
+nsresult nsSmtpProtocol::SmtpResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ char * line = nullptr;
+ char cont_char;
+ uint32_t ln = 0;
+ bool pauseForMoreData = false;
+
+ if (!m_lineStreamBuffer)
+ // this will force an error and at least we won't crash
+ return NS_ERROR_NULL_POINTER;
+
+ line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData);
+
+ if (pauseForMoreData || !line)
+ {
+ SetFlag(SMTP_PAUSE_FOR_READ); /* pause */
+ PR_Free(line);
+ return NS_OK;
+ }
+
+ m_totalAmountRead += ln;
+
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP Response: %s", line));
+ cont_char = ' '; /* default */
+ // sscanf() doesn't update m_responseCode if line doesn't start
+ // with a number. That can be dangerous. So be sure to set
+ // m_responseCode to 0 if no items read.
+ if (PR_sscanf(line, "%d%c", &m_responseCode, &cont_char) <= 0)
+ m_responseCode = 0;
+
+ if (m_continuationResponse == -1)
+ {
+ if (cont_char == '-') /* begin continuation */
+ m_continuationResponse = m_responseCode;
+
+ // display the whole message if no valid response code or
+ // message shorter than 4 chars
+ m_responseText = (m_responseCode >= 100 && PL_strlen(line) > 3) ? line + 4 : line;
+ }
+ else
+ { /* have to continue */
+ if (m_continuationResponse == m_responseCode && cont_char == ' ')
+ m_continuationResponse = -1; /* ended */
+
+ if (m_responseText.IsEmpty() || m_responseText.Last() != '\n')
+ m_responseText += "\n";
+
+ m_responseText += (PL_strlen(line) > 3) ? line + 4 : line;
+ }
+
+ if (m_responseCode == 220 && m_responseText.Length() && !m_tlsInitiated &&
+ !m_sendDone)
+ m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE;
+
+ if (m_continuationResponse == -1) /* all done with this response? */
+ {
+ m_nextState = m_nextStateAfterResponse;
+ ClearFlag(SMTP_PAUSE_FOR_READ); /* don't pause */
+ }
+
+ PR_Free(line);
+ return NS_OK;
+}
+
+nsresult nsSmtpProtocol::ExtensionLoginResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ nsresult status = NS_OK;
+
+ if (m_responseCode != 220)
+ {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ nsExplainErrorDetails(m_runningURL, NS_ERROR_SMTP_GREETING,
+ m_responseText.get());
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error");
+
+ m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT;
+ return NS_ERROR_SMTP_AUTH_FAILURE;
+ }
+
+ nsAutoCString buffer("EHLO ");
+ AppendHelloArgument(buffer);
+ buffer += CRLF;
+
+ status = SendData(buffer.get());
+
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_SEND_EHLO_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+
+ return(status);
+}
+
+nsresult nsSmtpProtocol::SendHeloResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ nsresult status = NS_OK;
+ nsAutoCString buffer;
+ nsresult rv;
+
+ if (m_responseCode != 250)
+ {
+#ifdef DEBUG
+ rv =
+#endif
+ nsExplainErrorDetails(m_runningURL, NS_ERROR_SMTP_SERVER_ERROR,
+ m_responseText.get());
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error");
+
+ m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT;
+ return NS_ERROR_SMTP_AUTH_FAILURE;
+ }
+
+ // check if we're just verifying the ability to logon
+ nsCOMPtr<nsISmtpUrl> smtpUrl = do_QueryInterface(m_runningURL, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool verifyingLogon = false;
+ smtpUrl->GetVerifyLogon(&verifyingLogon);
+ if (verifyingLogon)
+ return SendQuit();
+
+ // extract the email address from the identity
+ nsCString emailAddress;
+ nsCOMPtr <nsIMsgIdentity> senderIdentity;
+ rv = m_runningURL->GetSenderIdentity(getter_AddRefs(senderIdentity));
+ if (NS_FAILED(rv) || !senderIdentity)
+ {
+ m_urlErrorState = NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS;
+ return(NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS);
+ }
+ senderIdentity->GetEmail(emailAddress);
+
+ if (emailAddress.IsEmpty())
+ {
+ m_urlErrorState = NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS;
+ return(NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS);
+ }
+
+ nsCString fullAddress;
+ // Quote the email address before passing it to the SMTP server.
+ MakeMimeAddress(EmptyCString(), emailAddress, fullAddress);
+
+ buffer = "MAIL FROM:<";
+ buffer += fullAddress;
+ buffer += ">";
+
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (TestFlag(SMTP_EHLO_DSN_ENABLED))
+ {
+ bool requestDSN = false;
+ rv = m_runningURL->GetRequestDSN(&requestDSN);
+
+ if (requestDSN)
+ {
+ bool requestRetFull = false;
+ rv = prefBranch->GetBoolPref("mail.dsn.ret_full_on", &requestRetFull);
+
+ buffer += requestRetFull ? " RET=FULL" : " RET=HDRS";
+
+ nsCString dsnEnvid;
+
+ // get the envid from the smtpUrl
+ rv = m_runningURL->GetDsnEnvid(dsnEnvid);
+
+ if (dsnEnvid.IsEmpty())
+ dsnEnvid.Adopt(msg_generate_message_id(senderIdentity));
+
+ buffer += " ENVID=";
+ buffer += dsnEnvid;
+ }
+ }
+
+ if (TestFlag(SMTP_EHLO_8BIT_ENABLED))
+ {
+ bool strictlyMime = false;
+ rv = prefBranch->GetBoolPref("mail.strictly_mime", &strictlyMime);
+
+ if (!strictlyMime)
+ buffer.Append(" BODY=8BITMIME");
+ }
+
+ if (TestFlag(SMTP_EHLO_SIZE_ENABLED))
+ {
+ buffer.Append(" SIZE=");
+ buffer.AppendInt(m_totalMessageSize);
+ }
+ buffer += CRLF;
+
+ status = SendData(buffer.get());
+
+ m_nextState = SMTP_RESPONSE;
+
+
+ m_nextStateAfterResponse = SMTP_SEND_MAIL_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+
+ return(status);
+}
+
+nsresult nsSmtpProtocol::SendEhloResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ nsresult status = NS_OK;
+
+ if (m_responseCode != 250)
+ {
+ /* EHLO must not be implemented by the server, so fall back to the HELO case
+ * if command is unrecognized or unimplemented.
+ */
+ if (m_responseCode == 500 || m_responseCode == 502)
+ {
+ /* If STARTTLS is requested by the user, EHLO is required to advertise it.
+ * But only if TLS handshake is not already accomplished.
+ */
+ if (m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS &&
+ !m_tlsEnabled)
+ {
+ m_nextState = SMTP_ERROR_DONE;
+ m_urlErrorState = NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS;
+ return(NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS);
+ }
+
+ nsAutoCString buffer("HELO ");
+ AppendHelloArgument(buffer);
+ buffer += CRLF;
+
+ status = SendData(buffer.get());
+
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_SEND_HELO_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ return (status);
+ }
+ /* e.g. getting 421 "Server says unauthorized, bye" or
+ * 501 "Syntax error in EHLOs parameters or arguments"
+ */
+ else
+ {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ nsExplainErrorDetails(m_runningURL, NS_ERROR_SMTP_SERVER_ERROR,
+ m_responseText.get());
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error");
+
+ m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT;
+ return NS_ERROR_SMTP_AUTH_FAILURE;
+ }
+ }
+
+ int32_t responseLength = m_responseText.Length();
+ int32_t startPos = 0;
+ int32_t endPos;
+ do
+ {
+ endPos = m_responseText.FindChar('\n', startPos + 1);
+ nsAutoCString responseLine;
+ responseLine.Assign(Substring(m_responseText, startPos,
+ (endPos >= 0 ? endPos : responseLength) - startPos));
+
+ MsgCompressWhitespace(responseLine);
+ if (responseLine.LowerCaseEqualsLiteral("starttls"))
+ {
+ SetFlag(SMTP_EHLO_STARTTLS_ENABLED);
+ }
+ else if (responseLine.LowerCaseEqualsLiteral("dsn"))
+ {
+ SetFlag(SMTP_EHLO_DSN_ENABLED);
+ }
+ else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("AUTH"), nsCaseInsensitiveCStringComparator()))
+ {
+ SetFlag(SMTP_AUTH);
+
+ if (responseLine.Find(NS_LITERAL_CSTRING("GSSAPI"),
+ CaseInsensitiveCompare) >= 0)
+ SetFlag(SMTP_AUTH_GSSAPI_ENABLED);
+
+ if (responseLine.Find(NS_LITERAL_CSTRING("CRAM-MD5"),
+ CaseInsensitiveCompare) >= 0)
+ SetFlag(SMTP_AUTH_CRAM_MD5_ENABLED);
+
+ if (responseLine.Find(NS_LITERAL_CSTRING("NTLM"),
+ CaseInsensitiveCompare) >= 0)
+ SetFlag(SMTP_AUTH_NTLM_ENABLED);
+
+ if (responseLine.Find(NS_LITERAL_CSTRING("MSN"),
+ CaseInsensitiveCompare) >= 0)
+ SetFlag(SMTP_AUTH_MSN_ENABLED);
+
+ if (responseLine.Find(NS_LITERAL_CSTRING("PLAIN"),
+ CaseInsensitiveCompare) >= 0)
+ SetFlag(SMTP_AUTH_PLAIN_ENABLED);
+
+ if (responseLine.Find(NS_LITERAL_CSTRING("LOGIN"),
+ CaseInsensitiveCompare) >= 0)
+ SetFlag(SMTP_AUTH_LOGIN_ENABLED);
+
+ if (responseLine.Find(NS_LITERAL_CSTRING("EXTERNAL"),
+ CaseInsensitiveCompare) >= 0)
+ SetFlag(SMTP_AUTH_EXTERNAL_ENABLED);
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ if (responseLine.Find(NS_LITERAL_CSTRING("XOAUTH2"),
+ CaseInsensitiveCompare) >= 0)
+ SetFlag(SMTP_AUTH_OAUTH2_ENABLED);
+#endif
+ }
+ else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("SIZE"), nsCaseInsensitiveCStringComparator()))
+ {
+ SetFlag(SMTP_EHLO_SIZE_ENABLED);
+
+ m_sizelimit = atol((responseLine.get()) + 4);
+ }
+ else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("8BITMIME"), nsCaseInsensitiveCStringComparator()))
+ {
+ SetFlag(SMTP_EHLO_8BIT_ENABLED);
+ }
+
+ startPos = endPos + 1;
+ } while (endPos >= 0);
+
+ if (TestFlag(SMTP_EHLO_SIZE_ENABLED) &&
+ m_sizelimit > 0 && (int32_t)m_totalMessageSize > m_sizelimit)
+ {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ nsExplainErrorDetails(m_runningURL,
+ NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1, m_sizelimit);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error");
+
+ m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT;
+ return(NS_ERROR_SENDING_FROM_COMMAND);
+ }
+
+ m_nextState = SMTP_AUTH_PROCESS_STATE;
+ return status;
+}
+
+
+nsresult nsSmtpProtocol::SendTLSResponse()
+{
+ // only tear down our existing connection and open a new one if we received a 220 response
+ // from the smtp server after we issued the STARTTLS
+ nsresult rv = NS_OK;
+ if (m_responseCode == 220)
+ {
+ nsCOMPtr<nsISupports> secInfo;
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = strans->GetSecurityInfo(getter_AddRefs(secInfo));
+
+ if (NS_SUCCEEDED(rv) && secInfo) {
+ nsCOMPtr<nsISSLSocketControl> sslControl = do_QueryInterface(secInfo, &rv);
+
+ if (NS_SUCCEEDED(rv) && sslControl)
+ rv = sslControl->StartTLS();
+ }
+
+ if (NS_SUCCEEDED(rv))
+ {
+ m_nextState = SMTP_EXTN_LOGIN_RESPONSE;
+ m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE;
+ m_tlsEnabled = true;
+ m_flags = 0; // resetting the flags
+ return rv;
+ }
+ }
+
+ ClearFlag(SMTP_EHLO_STARTTLS_ENABLED);
+ m_tlsInitiated = false;
+ m_nextState = SMTP_AUTH_PROCESS_STATE;
+
+ return rv;
+}
+
+void nsSmtpProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue)
+{
+ // for m_prefAuthMethods, using the same flags as server capablities.
+ switch (authMethodPrefValue)
+ {
+ case nsMsgAuthMethod::none:
+ m_prefAuthMethods = SMTP_AUTH_NONE_ENABLED;
+ break;
+ //case nsMsgAuthMethod::old -- no such thing for SMTP
+ case nsMsgAuthMethod::passwordCleartext:
+ m_prefAuthMethods = SMTP_AUTH_LOGIN_ENABLED |
+ SMTP_AUTH_PLAIN_ENABLED;
+ break;
+ case nsMsgAuthMethod::passwordEncrypted:
+ m_prefAuthMethods = SMTP_AUTH_CRAM_MD5_ENABLED;
+ break;
+ case nsMsgAuthMethod::NTLM:
+ m_prefAuthMethods = SMTP_AUTH_NTLM_ENABLED |
+ SMTP_AUTH_MSN_ENABLED;
+ break;
+ case nsMsgAuthMethod::GSSAPI:
+ m_prefAuthMethods = SMTP_AUTH_GSSAPI_ENABLED;
+ break;
+#ifdef MOZ_MAILNEWS_OAUTH2
+ case nsMsgAuthMethod::OAuth2:
+ m_prefAuthMethods = SMTP_AUTH_OAUTH2_ENABLED;
+ break;
+#endif
+ case nsMsgAuthMethod::secure:
+ m_prefAuthMethods = SMTP_AUTH_CRAM_MD5_ENABLED |
+ SMTP_AUTH_GSSAPI_ENABLED |
+ SMTP_AUTH_NTLM_ENABLED | SMTP_AUTH_MSN_ENABLED |
+ SMTP_AUTH_EXTERNAL_ENABLED; // TODO: Expose EXTERNAL? How?
+ break;
+ default:
+ NS_ASSERTION(false, "SMTP: authMethod pref invalid");
+ // TODO log to error console
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error,
+ ("SMTP: bad pref authMethod = %d\n", authMethodPrefValue));
+ // fall to any
+ MOZ_FALLTHROUGH;
+ case nsMsgAuthMethod::anything:
+ m_prefAuthMethods =
+ SMTP_AUTH_LOGIN_ENABLED | SMTP_AUTH_PLAIN_ENABLED |
+ SMTP_AUTH_CRAM_MD5_ENABLED | SMTP_AUTH_GSSAPI_ENABLED |
+ SMTP_AUTH_NTLM_ENABLED | SMTP_AUTH_MSN_ENABLED |
+#ifdef MOZ_MAILNEWS_OAUTH2
+ SMTP_AUTH_OAUTH2_ENABLED |
+#endif
+ SMTP_AUTH_EXTERNAL_ENABLED;
+ break;
+ }
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ // Only enable OAuth2 support if we can do the lookup.
+ if ((m_prefAuthMethods & SMTP_AUTH_OAUTH2_ENABLED) && !mOAuth2Support)
+ m_prefAuthMethods &= ~SMTP_AUTH_OAUTH2_ENABLED;
+#endif
+
+ NS_ASSERTION(m_prefAuthMethods != 0, "SMTP:InitPrefAuthMethods() failed");
+}
+
+/**
+ * Changes m_currentAuthMethod to pick the next-best one
+ * which is allowed by server and prefs and not marked failed.
+ * The order of preference and trying of auth methods is encoded here.
+ */
+nsresult nsSmtpProtocol::ChooseAuthMethod()
+{
+ int32_t serverCaps = m_flags; // from nsMsgProtocol::TestFlag()
+ int32_t availCaps = serverCaps & m_prefAuthMethods & ~m_failedAuthMethods;
+
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug,
+ ("SMTP auth: server caps 0x%X, pref 0x%X, failed 0x%X, avail caps 0x%X",
+ serverCaps, m_prefAuthMethods, m_failedAuthMethods, availCaps));
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel:: Debug,
+ ("(GSSAPI = 0x%X, CRAM = 0x%X, NTLM = 0x%X, "
+ "MSN = 0x%X, PLAIN = 0x%X, LOGIN = 0x%X, EXTERNAL = 0x%X)",
+ SMTP_AUTH_GSSAPI_ENABLED, SMTP_AUTH_CRAM_MD5_ENABLED,
+ SMTP_AUTH_NTLM_ENABLED, SMTP_AUTH_MSN_ENABLED, SMTP_AUTH_PLAIN_ENABLED,
+ SMTP_AUTH_LOGIN_ENABLED, SMTP_AUTH_EXTERNAL_ENABLED));
+
+ if (SMTP_AUTH_GSSAPI_ENABLED & availCaps)
+ m_currentAuthMethod = SMTP_AUTH_GSSAPI_ENABLED;
+ else if (SMTP_AUTH_CRAM_MD5_ENABLED & availCaps)
+ m_currentAuthMethod = SMTP_AUTH_CRAM_MD5_ENABLED;
+ else if (SMTP_AUTH_NTLM_ENABLED & availCaps)
+ m_currentAuthMethod = SMTP_AUTH_NTLM_ENABLED;
+ else if (SMTP_AUTH_MSN_ENABLED & availCaps)
+ m_currentAuthMethod = SMTP_AUTH_MSN_ENABLED;
+#ifdef MOZ_MAILNEWS_OAUTH2
+ else if (SMTP_AUTH_OAUTH2_ENABLED & availCaps)
+ m_currentAuthMethod = SMTP_AUTH_OAUTH2_ENABLED;
+#endif
+ else if (SMTP_AUTH_PLAIN_ENABLED & availCaps)
+ m_currentAuthMethod = SMTP_AUTH_PLAIN_ENABLED;
+ else if (SMTP_AUTH_LOGIN_ENABLED & availCaps)
+ m_currentAuthMethod = SMTP_AUTH_LOGIN_ENABLED;
+ else if (SMTP_AUTH_EXTERNAL_ENABLED & availCaps)
+ m_currentAuthMethod = SMTP_AUTH_EXTERNAL_ENABLED;
+ else
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("no auth method remaining"));
+ m_currentAuthMethod = 0;
+ return NS_ERROR_SMTP_AUTH_FAILURE;
+ }
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("trying auth method 0x%X", m_currentAuthMethod));
+ return NS_OK;
+}
+
+void nsSmtpProtocol::MarkAuthMethodAsFailed(int32_t failedAuthMethod)
+{
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug,
+ ("marking auth method 0x%X failed", failedAuthMethod));
+ m_failedAuthMethods |= failedAuthMethod;
+}
+
+/**
+ * Start over, trying all auth methods again
+ */
+void nsSmtpProtocol::ResetAuthMethods()
+{
+ m_currentAuthMethod = 0;
+ m_failedAuthMethods = 0;
+}
+
+nsresult nsSmtpProtocol::ProcessAuth()
+{
+ nsresult status = NS_OK;
+ nsAutoCString buffer;
+
+ if (!m_tlsEnabled)
+ {
+ if (TestFlag(SMTP_EHLO_STARTTLS_ENABLED))
+ {
+ // Do not try to combine SMTPS with STARTTLS.
+ // If nsMsgSocketType::SSL is set,
+ // we are alrady using a secure connection.
+ // Do not attempt to do STARTTLS,
+ // even if server offers it.
+ if (m_prefSocketType == nsMsgSocketType::trySTARTTLS ||
+ m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ buffer = "STARTTLS";
+ buffer += CRLF;
+
+ status = SendData(buffer.get());
+
+ m_tlsInitiated = true;
+
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_TLS_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ return status;
+ }
+ }
+ else if (m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ m_nextState = SMTP_ERROR_DONE;
+ m_urlErrorState = NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS;
+ return NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS;
+ }
+ }
+ // (wrong indention until here)
+
+ (void) ChooseAuthMethod(); // advance m_currentAuthMethod
+
+ // We don't need to auth, per pref, or the server doesn't advertise AUTH,
+ // so skip auth and try to send message.
+ if (m_prefAuthMethods == SMTP_AUTH_NONE_ENABLED || !TestFlag(SMTP_AUTH))
+ {
+ m_nextState = SMTP_SEND_HELO_RESPONSE;
+ // fake to 250 because SendHeloResponse() tests for this
+ m_responseCode = 250;
+ }
+ else if (m_currentAuthMethod == SMTP_AUTH_EXTERNAL_ENABLED)
+ {
+ buffer = "AUTH EXTERNAL =";
+ buffer += CRLF;
+ SendData(buffer.get());
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_AUTH_EXTERNAL_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ else if (m_currentAuthMethod == SMTP_AUTH_GSSAPI_ENABLED)
+ {
+ m_nextState = SMTP_SEND_AUTH_GSSAPI_FIRST;
+ }
+ else if (m_currentAuthMethod == SMTP_AUTH_CRAM_MD5_ENABLED ||
+ m_currentAuthMethod == SMTP_AUTH_PLAIN_ENABLED ||
+ m_currentAuthMethod == SMTP_AUTH_NTLM_ENABLED)
+ {
+ m_nextState = SMTP_SEND_AUTH_LOGIN_STEP1;
+ }
+ else if (m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED ||
+ m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED)
+ {
+ m_nextState = SMTP_SEND_AUTH_LOGIN_STEP0;
+ }
+#ifdef MOZ_MAILNEWS_OAUTH2
+ else if (m_currentAuthMethod == SMTP_AUTH_OAUTH2_ENABLED)
+ {
+ m_nextState = SMTP_AUTH_OAUTH2_STEP;
+ }
+#endif
+ else // All auth methods failed
+ {
+ // show an appropriate error msg
+ if (m_failedAuthMethods == 0)
+ {
+ // we didn't even try anything, so we had a non-working config:
+ // pref doesn't match server
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error,
+ ("no working auth mech - pref doesn't match server capas"));
+
+ // pref has encrypted pw & server claims to support plaintext pw
+ if (m_prefAuthMethods == SMTP_AUTH_CRAM_MD5_ENABLED &&
+ m_flags & (SMTP_AUTH_LOGIN_ENABLED | SMTP_AUTH_PLAIN_ENABLED))
+ {
+ // have SSL
+ if (m_prefSocketType == nsMsgSocketType::SSL ||
+ m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS)
+ // tell user to change to plaintext pw
+ m_urlErrorState = NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL;
+ else
+ // tell user to change to plaintext pw, with big warning
+ m_urlErrorState = NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL;
+ }
+ // pref has plaintext pw & server claims to support encrypted pw
+ else if (m_prefAuthMethods == (SMTP_AUTH_LOGIN_ENABLED |
+ SMTP_AUTH_PLAIN_ENABLED) &&
+ m_flags & SMTP_AUTH_CRAM_MD5_ENABLED)
+ // tell user to change to encrypted pw
+ m_urlErrorState = NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT;
+ else
+ {
+ // just "change auth method"
+ m_urlErrorState = NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED;
+ }
+ }
+ else if (m_failedAuthMethods == SMTP_AUTH_GSSAPI_ENABLED)
+ {
+ // We have only GSSAPI, and it failed, so nothing left to do.
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("GSSAPI only and it failed"));
+ m_urlErrorState = NS_ERROR_SMTP_AUTH_GSSAPI;
+ }
+ else
+ {
+ // we tried to login, but it all failed
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("All auth attempts failed"));
+ m_urlErrorState = NS_ERROR_SMTP_AUTH_FAILURE;
+ }
+ m_nextState = SMTP_ERROR_DONE;
+ return NS_ERROR_SMTP_AUTH_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+
+
+nsresult nsSmtpProtocol::AuthLoginResponse(nsIInputStream * stream, uint32_t length)
+{
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP Login response, code %d", m_responseCode));
+ nsresult status = NS_OK;
+
+ switch (m_responseCode/100)
+ {
+ case 2:
+ m_nextState = SMTP_SEND_HELO_RESPONSE;
+ // fake to 250 because SendHeloResponse() tests for this
+ m_responseCode = 250;
+ break;
+ case 3:
+ m_nextState = SMTP_SEND_AUTH_LOGIN_STEP2;
+ break;
+ case 5:
+ default:
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer));
+ if (smtpServer)
+ {
+ // If one authentication failed, mark it failed, so that we're going to
+ // fall back on a less secure login method.
+ MarkAuthMethodAsFailed(m_currentAuthMethod);
+
+ bool allFailed = NS_FAILED(ChooseAuthMethod());
+ if (allFailed && m_failedAuthMethods > 0 &&
+ m_failedAuthMethods != SMTP_AUTH_GSSAPI_ENABLED &&
+ m_failedAuthMethods != SMTP_AUTH_EXTERNAL_ENABLED)
+ {
+ // We've tried all avail. methods, and they all failed, and we have no mechanism left.
+ // Ask user to try with a new password.
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning,
+ ("SMTP: ask user what to do (after login failed): new password, retry or cancel"));
+
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ nsresult rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString hostname;
+ rv = smtpServer->GetHostname(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t buttonPressed = 1;
+ if (NS_SUCCEEDED(MsgPromptLoginFailed(nullptr, hostname,
+ &buttonPressed)))
+ {
+ if (buttonPressed == 1) // Cancel button
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, ("cancel button pressed"));
+ // abort and get out of here
+ status = NS_ERROR_ABORT;
+ break;
+ }
+ else if (buttonPressed == 2) // 'New password' button
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, ("new password button pressed"));
+ // Change password was pressed. For now, forget the stored
+ // password and we'll prompt for a new one next time around.
+ smtpServer->ForgetPassword();
+ if (m_usernamePrompted)
+ smtpServer->SetUsername(EmptyCString());
+
+ // Let's restore the original auth flags from SendEhloResponse
+ // so we can try them again with new password and username
+ ResetAuthMethods();
+ // except for GSSAPI and EXTERNAL, which don't care about passwords.
+ MarkAuthMethodAsFailed(SMTP_AUTH_GSSAPI_ENABLED);
+ MarkAuthMethodAsFailed(SMTP_AUTH_EXTERNAL_ENABLED);
+ }
+ else if (buttonPressed == 0) // Retry button
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, ("retry button pressed"));
+ // try all again, including GSSAPI
+ ResetAuthMethods();
+ }
+ }
+ }
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error,
+ ("SMTP: login failed: failed %X, current %X", m_failedAuthMethods, m_currentAuthMethod));
+
+ m_nextState = SMTP_AUTH_PROCESS_STATE; // try auth (ProcessAuth()) again, with other method
+ }
+ else
+ status = NS_ERROR_SMTP_PASSWORD_UNDEFINED;
+ break;
+ }
+
+ return (status);
+}
+
+nsresult nsSmtpProtocol::AuthGSSAPIFirst()
+{
+ NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_GSSAPI_ENABLED, "called in invalid state");
+ nsAutoCString command("AUTH GSSAPI ");
+ nsAutoCString resp;
+ nsAutoCString service("smtp@");
+ nsCString hostName;
+ nsCString userName;
+ nsresult rv;
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer));
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ rv = smtpServer->GetUsername(userName);
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ rv = smtpServer->GetHostname(hostName);
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+ service.Append(hostName);
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP: GSSAPI step 1 for user %s at server %s, service %s",
+ userName.get(), hostName.get(), service.get()));
+
+ rv = DoGSSAPIStep1(service.get(), userName.get(), resp);
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("SMTP: GSSAPI step 1 failed early"));
+ MarkAuthMethodAsFailed(SMTP_AUTH_GSSAPI_ENABLED);
+ m_nextState = SMTP_AUTH_PROCESS_STATE;
+ return NS_OK;
+ }
+ else
+ command.Append(resp);
+ command.Append(CRLF);
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_SEND_AUTH_GSSAPI_STEP;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ return SendData(command.get());
+}
+
+// GSSAPI may consist of multiple round trips
+
+nsresult nsSmtpProtocol::AuthGSSAPIStep()
+{
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP: GSSAPI auth step 2"));
+ NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_GSSAPI_ENABLED, "called in invalid state");
+ nsresult rv;
+ nsAutoCString cmd;
+
+ // Check to see what the server said
+ if (m_responseCode / 100 != 3) {
+ m_nextState = SMTP_AUTH_LOGIN_RESPONSE;
+ return NS_OK;
+ }
+
+ rv = DoGSSAPIStep2(m_responseText, cmd);
+ if (NS_FAILED(rv))
+ cmd = "*";
+ cmd += CRLF;
+
+ m_nextStateAfterResponse = (rv == NS_SUCCESS_AUTH_FINISHED)?SMTP_AUTH_LOGIN_RESPONSE:SMTP_SEND_AUTH_GSSAPI_STEP;
+ m_nextState = SMTP_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+
+ return SendData(cmd.get());
+}
+
+
+// LOGIN and MSN consist of three steps (MSN not through the mechanism
+// but by non-RFC2821 compliant implementation in MS servers) not two as
+// PLAIN or CRAM-MD5, so we've to start here and continue with AuthStep1
+// if the server responds with with a 3xx code to "AUTH LOGIN" or "AUTH MSN"
+nsresult nsSmtpProtocol::AuthLoginStep0()
+{
+ NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED ||
+ m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED,
+ "called in invalid state");
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP: MSN or LOGIN auth, step 0"));
+ nsAutoCString command(m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED
+ ? "AUTH MSN" CRLF : "AUTH LOGIN" CRLF);
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_AUTH_LOGIN_STEP0_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+
+ return SendData(command.get());
+}
+
+void nsSmtpProtocol::AuthLoginStep0Response()
+{
+ NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED ||
+ m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED,
+ "called in invalid state");
+ // need the test to be here instead in AuthLoginResponse() to
+ // continue with step 1 instead of 2 in case of a code 3xx
+ m_nextState = (m_responseCode/100 == 3) ?
+ SMTP_SEND_AUTH_LOGIN_STEP1 : SMTP_AUTH_LOGIN_RESPONSE;
+}
+
+nsresult nsSmtpProtocol::AuthLoginStep1()
+{
+ char buffer[512]; // TODO nsAutoCString
+ nsresult rv;
+ nsresult status = NS_OK;
+ nsCString username;
+ char *base64Str = nullptr;
+ nsAutoCString password;
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer));
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ rv = smtpServer->GetUsername(username);
+ if (username.IsEmpty())
+ {
+ rv = GetUsernamePassword(username, password);
+ m_usernamePrompted = true;
+ if (username.IsEmpty() || password.IsEmpty())
+ return NS_ERROR_SMTP_PASSWORD_UNDEFINED;
+ }
+
+ nsCString hostname;
+ smtpServer->GetHostname(hostname);
+
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP AuthLoginStep1() for %s@%s",
+ username.get(), hostname.get()));
+
+ GetPassword(password);
+ if (password.IsEmpty())
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("SMTP: password undefined"));
+ m_urlErrorState = NS_ERROR_SMTP_PASSWORD_UNDEFINED;
+ return NS_ERROR_SMTP_PASSWORD_UNDEFINED;
+ }
+
+ if (m_currentAuthMethod == SMTP_AUTH_CRAM_MD5_ENABLED)
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("CRAM auth, step 1"));
+ PR_snprintf(buffer, sizeof(buffer), "AUTH CRAM-MD5" CRLF);
+ }
+ else if (m_currentAuthMethod == SMTP_AUTH_NTLM_ENABLED ||
+ m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED)
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("NTLM/MSN auth, step 1"));
+ nsAutoCString response;
+ rv = DoNtlmStep1(username.get(), password.get(), response);
+ PR_snprintf(buffer, sizeof(buffer), TestFlag(SMTP_AUTH_NTLM_ENABLED) ?
+ "AUTH NTLM %.256s" CRLF :
+ "%.256s" CRLF, response.get());
+ }
+ else if (m_currentAuthMethod == SMTP_AUTH_PLAIN_ENABLED)
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("PLAIN auth"));
+ char plain_string[512];
+ int len = 1; /* first <NUL> char */
+
+ memset(plain_string, 0, 512);
+ PR_snprintf(&plain_string[1], 510, "%s", username.get());
+ len += username.Length();
+ len++; /* second <NUL> char */
+ PR_snprintf(&plain_string[len], 511-len, "%s", password.get());
+ len += password.Length();
+
+ base64Str = PL_Base64Encode(plain_string, len, nullptr);
+ PR_snprintf(buffer, sizeof(buffer), "AUTH PLAIN %.256s" CRLF, base64Str);
+ }
+ else if (m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED)
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("LOGIN auth"));
+ base64Str = PL_Base64Encode(username.get(),
+ username.Length(), nullptr);
+ PR_snprintf(buffer, sizeof(buffer), "%.256s" CRLF, base64Str);
+ }
+ else
+ return (NS_ERROR_COMMUNICATIONS_ERROR);
+
+ status = SendData(buffer, true);
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_AUTH_LOGIN_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ NS_Free(base64Str);
+
+ return (status);
+}
+
+nsresult nsSmtpProtocol::AuthLoginStep2()
+{
+ /* use cached smtp password first
+ * if not then use cached pop password
+ * if pop password undefined
+ * sync with smtp password
+ */
+ nsresult status = NS_OK;
+ nsresult rv;
+ nsAutoCString password;
+
+ GetPassword(password);
+ if (password.IsEmpty())
+ {
+ m_urlErrorState = NS_ERROR_SMTP_PASSWORD_UNDEFINED;
+ return NS_ERROR_SMTP_PASSWORD_UNDEFINED;
+ }
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP AuthLoginStep2"));
+
+ if (!password.IsEmpty())
+ {
+ char buffer[512];
+ if (m_currentAuthMethod == SMTP_AUTH_CRAM_MD5_ENABLED)
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("CRAM auth, step 2"));
+ unsigned char digest[DIGEST_LENGTH];
+ char * decodedChallenge = PL_Base64Decode(m_responseText.get(),
+ m_responseText.Length(), nullptr);
+
+ if (decodedChallenge)
+ rv = MSGCramMD5(decodedChallenge, strlen(decodedChallenge), password.get(), password.Length(), digest);
+ else
+ rv = NS_ERROR_FAILURE;
+
+ PR_Free(decodedChallenge);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString encodedDigest;
+ char hexVal[8];
+
+ for (uint32_t j=0; j<16; j++)
+ {
+ PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)digest[j]);
+ encodedDigest.Append(hexVal);
+ }
+
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer));
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ nsCString userName;
+ rv = smtpServer->GetUsername(userName);
+
+ PR_snprintf(buffer, sizeof(buffer), "%s %s", userName.get(), encodedDigest.get());
+ char *base64Str = PL_Base64Encode(buffer, strlen(buffer), nullptr);
+ PR_snprintf(buffer, sizeof(buffer), "%s" CRLF, base64Str);
+ NS_Free(base64Str);
+ }
+ if (NS_FAILED(rv))
+ PR_snprintf(buffer, sizeof(buffer), "*" CRLF);
+ }
+ else if (m_currentAuthMethod == SMTP_AUTH_NTLM_ENABLED ||
+ m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED)
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("NTLM/MSN auth, step 2"));
+ nsAutoCString response;
+ rv = DoNtlmStep2(m_responseText, response);
+ PR_snprintf(buffer, sizeof(buffer), "%.509s" CRLF, response.get());
+ }
+ else if (m_currentAuthMethod == SMTP_AUTH_PLAIN_ENABLED ||
+ m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED)
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("PLAIN/LOGIN auth, step 2"));
+ char *base64Str = PL_Base64Encode(password.get(), password.Length(), nullptr);
+ PR_snprintf(buffer, sizeof(buffer), "%.256s" CRLF, base64Str);
+ NS_Free(base64Str);
+ }
+ else
+ return NS_ERROR_COMMUNICATIONS_ERROR;
+
+ status = SendData(buffer, true);
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_AUTH_LOGIN_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ return (status);
+ }
+
+ // XXX -1 is not a valid nsresult
+ return static_cast<nsresult>(-1);
+}
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+nsresult nsSmtpProtocol::AuthOAuth2Step1()
+{
+ MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support");
+
+ nsresult rv = mOAuth2Support->Connect(true, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_nextState = SMTP_SUSPENDED;
+ return NS_OK;
+}
+
+nsresult nsSmtpProtocol::OnSuccess(const nsACString &aAccessToken)
+{
+ MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support");
+
+ nsCString base64Str;
+ mOAuth2Support->BuildXOAuth2String(base64Str);
+
+ // Send the AUTH XOAUTH2 command, and then siphon us back to the regular
+ // authentication login stream.
+ nsAutoCString buffer;
+ buffer.AppendLiteral("AUTH XOAUTH2 ");
+ buffer += base64Str;
+ buffer += CRLF;
+ nsresult rv = SendData(buffer.get(), true);
+ if (NS_FAILED(rv))
+ {
+ m_nextState = SMTP_ERROR_DONE;
+ }
+ else
+ {
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_AUTH_LOGIN_RESPONSE;
+ }
+
+ SetFlag(SMTP_PAUSE_FOR_READ);
+
+ ProcessProtocolState(nullptr, nullptr, 0, 0);
+ return NS_OK;
+}
+
+nsresult nsSmtpProtocol::OnFailure(nsresult aError)
+{
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("OAuth2 login error %08x",
+ (uint32_t)aError));
+ m_urlErrorState = aError;
+ m_nextState = SMTP_ERROR_DONE;
+ return ProcessProtocolState(nullptr, nullptr, 0, 0);
+}
+#endif
+
+nsresult nsSmtpProtocol::SendMailResponse()
+{
+ nsresult status = NS_OK;
+ nsAutoCString buffer;
+ nsresult rv;
+
+ if (m_responseCode/10 != 25)
+ {
+ nsresult errorcode;
+ if (TestFlag(SMTP_EHLO_SIZE_ENABLED))
+ errorcode = (m_responseCode == 452) ? NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED :
+ (m_responseCode == 552) ? NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2 :
+ NS_ERROR_SENDING_FROM_COMMAND;
+ else
+ errorcode = NS_ERROR_SENDING_FROM_COMMAND;
+
+ rv = nsExplainErrorDetails(m_runningURL, errorcode, m_responseText.get());
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error");
+
+ m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT;
+ return(NS_ERROR_SENDING_FROM_COMMAND);
+ }
+
+ /* Send the RCPT TO: command */
+ bool requestDSN = false;
+ rv = m_runningURL->GetRequestDSN(&requestDSN);
+
+ nsCOMPtr <nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool requestOnSuccess = false;
+ rv = prefBranch->GetBoolPref("mail.dsn.request_on_success_on", &requestOnSuccess);
+
+ bool requestOnFailure = false;
+ rv = prefBranch->GetBoolPref("mail.dsn.request_on_failure_on", &requestOnFailure);
+
+ bool requestOnDelay = false;
+ rv = prefBranch->GetBoolPref("mail.dsn.request_on_delay_on", &requestOnDelay);
+
+ bool requestOnNever = false;
+ rv = prefBranch->GetBoolPref("mail.dsn.request_never_on", &requestOnNever);
+
+ nsCString &address = m_addresses[m_addressesLeft - 1];
+ if (TestFlag(SMTP_EHLO_DSN_ENABLED) && requestDSN && (requestOnSuccess || requestOnFailure || requestOnDelay || requestOnNever))
+ {
+ char *encodedAddress = esmtp_value_encode(address.get());
+ nsAutoCString dsnBuffer;
+
+ if (encodedAddress)
+ {
+ buffer = "RCPT TO:<";
+ buffer += address;
+ buffer += "> NOTIFY=";
+
+ if (requestOnNever)
+ dsnBuffer += "NEVER";
+ else
+ {
+ if (requestOnSuccess)
+ dsnBuffer += "SUCCESS";
+
+ if (requestOnFailure)
+ dsnBuffer += dsnBuffer.IsEmpty() ? "FAILURE" : ",FAILURE";
+
+ if (requestOnDelay)
+ dsnBuffer += dsnBuffer.IsEmpty() ? "DELAY" : ",DELAY";
+ }
+
+ buffer += dsnBuffer;
+ buffer += " ORCPT=rfc822;";
+ buffer += encodedAddress;
+ buffer += CRLF;
+ PR_FREEIF(encodedAddress);
+ }
+ else
+ {
+ m_urlErrorState = NS_ERROR_OUT_OF_MEMORY;
+ return (NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ else
+ {
+ buffer = "RCPT TO:<";
+ buffer += address;
+ buffer += ">";
+ buffer += CRLF;
+ }
+ status = SendData(buffer.get());
+
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_SEND_RCPT_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+
+ return(status);
+}
+
+nsresult nsSmtpProtocol::SendRecipientResponse()
+{
+ nsresult status = NS_OK;
+ nsAutoCString buffer;
+ nsresult rv;
+
+ if (m_responseCode / 10 != 25)
+ {
+ nsresult errorcode;
+ if (TestFlag(SMTP_EHLO_SIZE_ENABLED))
+ errorcode = (m_responseCode == 452) ? NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED :
+ (m_responseCode == 552) ? NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2 :
+ NS_ERROR_SENDING_RCPT_COMMAND;
+ else
+ errorcode = NS_ERROR_SENDING_RCPT_COMMAND;
+
+ if (errorcode == NS_ERROR_SENDING_RCPT_COMMAND) {
+ rv = nsExplainErrorDetails(
+ m_runningURL, errorcode, NS_ConvertUTF8toUTF16(m_responseText).get(),
+ NS_ConvertUTF8toUTF16(m_addresses[m_addressesLeft - 1]).get());
+ } else {
+ rv = nsExplainErrorDetails(m_runningURL, errorcode,
+ m_responseText.get(),
+ m_addresses[m_addressesLeft - 1].get());
+ }
+
+ if (!NS_SUCCEEDED(rv))
+ NS_ASSERTION(false, "failed to explain SMTP error");
+
+ m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT;
+ return(NS_ERROR_SENDING_RCPT_COMMAND);
+ }
+
+ if (--m_addressesLeft > 0)
+ {
+ // more senders to RCPT to
+ // fake to 250 because SendMailResponse() can't handle 251
+ m_responseCode = 250;
+ m_nextState = SMTP_SEND_MAIL_RESPONSE;
+ return NS_OK;
+ }
+
+ /* else send the DATA command */
+ buffer = "DATA";
+ buffer += CRLF;
+ status = SendData(buffer.get());
+
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_SEND_DATA_RESPONSE;
+ SetFlag(SMTP_PAUSE_FOR_READ);
+
+ return(status);
+}
+
+
+nsresult nsSmtpProtocol::SendData(const char *dataBuffer, bool aSuppressLogging)
+{
+ // XXX -1 is not a valid nsresult
+ if (!dataBuffer) return static_cast<nsresult>(-1);
+
+ if (!aSuppressLogging) {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP Send: %s", dataBuffer));
+ } else {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("Logging suppressed for this command (it probably contained authentication information)"));
+ }
+ return nsMsgAsyncWriteProtocol::SendData(dataBuffer);
+}
+
+
+nsresult nsSmtpProtocol::SendDataResponse()
+{
+ nsresult status = NS_OK;
+
+ if (m_responseCode != 354)
+ {
+ mozilla::DebugOnly<nsresult> rv = nsExplainErrorDetails(m_runningURL,
+ NS_ERROR_SENDING_DATA_COMMAND,
+ m_responseText.get());
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error");
+
+ m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT;
+ return(NS_ERROR_SENDING_DATA_COMMAND);
+ }
+
+ m_nextState = SMTP_SEND_POST_DATA;
+ ClearFlag(SMTP_PAUSE_FOR_READ); /* send data directly */
+
+ UpdateStatus(u"smtpDeliveringMail");
+
+ {
+// m_runningURL->GetBodySize(&m_totalMessageSize);
+ }
+ return(status);
+}
+
+void nsSmtpProtocol::SendMessageInFile()
+{
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningURL);
+ m_runningURL->GetPostMessageFile(getter_AddRefs(file));
+ if (url && file)
+ // need to fully qualify to avoid getting overwritten by a #define
+ // in some windows header file
+ nsMsgAsyncWriteProtocol::PostMessage(url, file);
+
+ SetFlag(SMTP_PAUSE_FOR_READ);
+
+ // for now, we are always done at this point..we aren't making multiple calls
+ // to post data...
+
+ UpdateStatus(u"smtpDeliveringMail");
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_SEND_MESSAGE_RESPONSE;
+}
+
+void nsSmtpProtocol::SendPostData()
+{
+ // mscott: as a first pass, I'm writing everything at once and am not
+ // doing it in chunks...
+
+ /* returns 0 on done and negative on error
+ * positive if it needs to continue.
+ */
+
+ // check to see if url is a file..if it is...call our file handler...
+ bool postMessageInFile = true;
+ m_runningURL->GetPostMessage(&postMessageInFile);
+ if (postMessageInFile)
+ {
+ SendMessageInFile();
+ }
+
+ /* Update the thermo and the status bar. This is done by hand, rather
+ than using the FE_GraphProgress* functions, because there seems to be
+ no way to make FE_GraphProgress shut up and not display anything more
+ when all the data has arrived. At the end, we want to show the
+ "message sent; waiting for reply" status; FE_GraphProgress gets in
+ the way of that. See bug #23414. */
+}
+
+
+
+nsresult nsSmtpProtocol::SendMessageResponse()
+{
+ if((m_responseCode/10 != 25))
+ {
+ mozilla::DebugOnly<nsresult> rv = nsExplainErrorDetails(m_runningURL,
+ NS_ERROR_SENDING_MESSAGE,
+ m_responseText.get());
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error");
+
+ m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT;
+ return(NS_ERROR_SENDING_MESSAGE);
+ }
+
+ UpdateStatus(u"smtpMailSent");
+
+ /* else */
+ return SendQuit();
+}
+
+nsresult nsSmtpProtocol::SendQuit(SmtpState aNextStateAfterResponse)
+{
+ m_sendDone = true;
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = aNextStateAfterResponse;
+
+ return SendData("QUIT" CRLF); // send a quit command to close the connection with the server.
+}
+
+nsresult nsSmtpProtocol::LoadUrl(nsIURI * aURL, nsISupports * aConsumer )
+{
+ if (!aURL)
+ return NS_OK;
+
+ Initialize(aURL);
+
+ m_continuationResponse = -1; /* init */
+ m_runningURL = do_QueryInterface(aURL);
+ if (!m_runningURL)
+ return NS_ERROR_FAILURE;
+
+ // we had a bug where we failed to bring up an alert if the host
+ // name was empty....so throw up an alert saying we don't have
+ // a host name and inform the caller that we are not going to
+ // run the url...
+ nsAutoCString hostName;
+ aURL->GetHost(hostName);
+ if (hostName.IsEmpty())
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(aURL);
+ if (aMsgUrl)
+ {
+ aMsgUrl->SetUrlState(true, NS_OK);
+ // set the url as a url currently being run...
+ aMsgUrl->SetUrlState(false /* we aren't running the url */,
+ NS_ERROR_SMTP_AUTH_FAILURE);
+ }
+ return NS_ERROR_BUT_DONT_SHOW_ALERT;
+ }
+
+ bool postMessage = false;
+ m_runningURL->GetPostMessage(&postMessage);
+
+ if (postMessage)
+ {
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE;
+
+ // compile a minimal list of valid target addresses by
+ // - looking only at mailboxes
+ // - dropping addresses with invalid localparts (until we implement RFC 6532)
+ // - using ACE for IDN domainparts
+ // - stripping duplicates
+ nsCString addresses;
+ m_runningURL->GetRecipients(getter_Copies(addresses));
+
+ ExtractEmails(EncodedHeader(addresses), UTF16ArrayAdapter<>(m_addresses));
+
+ nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ addresses.Truncate();
+ uint32_t count = m_addresses.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ const char *start = m_addresses[i].get();
+ // Location of the @ character
+ const char *lastAt = nullptr;
+ const char *ch = start;
+ for (; *ch; ch++)
+ {
+ if (*ch == '@')
+ lastAt = ch;
+ // Check for first illegal character (outside 0x09,0x20-0x7e)
+ else if ((*ch < ' ' || *ch > '~') && (*ch != '\t'))
+ {
+ break;
+ }
+ }
+ // validate the just parsed address
+ if (*ch || m_addresses[i].IsEmpty())
+ {
+ // Fortunately, we will always have an @ in each mailbox address.
+ // We try to fix illegal character in the domain part by converting
+ // that to ACE. Illegal characters in the local part are not fixable
+ // (which charset would it be anyway?), hence we error out in that
+ // case as well.
+ nsresult rv = NS_ERROR_FAILURE; // anything but NS_OK
+ if (lastAt)
+ {
+ // Illegal char in the domain part, hence use ACE
+ nsAutoCString domain;
+ domain.Assign(lastAt + 1);
+ rv = converter->ConvertUTF8toACE(domain, domain);
+ if (NS_SUCCEEDED(rv))
+ {
+ m_addresses[i].SetLength(lastAt - start + 1);
+ m_addresses[i] += domain;
+ }
+ }
+ if (NS_FAILED(rv))
+ {
+ // Throw an error, including the broken address
+ m_nextState = SMTP_ERROR_DONE;
+ ClearFlag(SMTP_PAUSE_FOR_READ);
+ // Unfortunately, nsExplainErrorDetails will show the error above
+ // the mailnews main window, because we don't necessarily get
+ // passed down a compose window - we might be sending in the
+ // background!
+ rv = nsExplainErrorDetails(m_runningURL,
+ NS_ERROR_ILLEGAL_LOCALPART, start);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain illegal localpart");
+ m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT;
+ return NS_ERROR_BUT_DONT_SHOW_ALERT;
+ }
+ }
+ }
+
+ // final cleanup
+ m_addressesLeft = m_addresses.Length();
+
+ // hmm no addresses to send message to...
+ if (m_addressesLeft == 0)
+ {
+ m_nextState = SMTP_ERROR_DONE;
+ ClearFlag(SMTP_PAUSE_FOR_READ);
+ m_urlErrorState = NS_MSG_NO_RECIPIENTS;
+ return NS_MSG_NO_RECIPIENTS;
+ }
+ } // if post message
+
+ return nsMsgProtocol::LoadUrl(aURL, aConsumer);
+}
+
+/*
+ * returns negative if the transfer is finished or error'd out
+ *
+ * returns zero or more if the transfer needs to be continued.
+ */
+nsresult nsSmtpProtocol::ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length)
+ {
+ nsresult status = NS_OK;
+ ClearFlag(SMTP_PAUSE_FOR_READ); /* already paused; reset */
+
+ while(!TestFlag(SMTP_PAUSE_FOR_READ))
+ {
+ MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP entering state: %d",
+ m_nextState));
+ switch(m_nextState)
+ {
+ case SMTP_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ else
+ status = SmtpResponse(inputStream, length);
+ break;
+
+ case SMTP_START_CONNECT:
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ m_nextState = SMTP_RESPONSE;
+ m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE;
+ break;
+ case SMTP_FINISH_CONNECT:
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ break;
+ case SMTP_TLS_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ else
+ status = SendTLSResponse();
+ break;
+ case SMTP_EXTN_LOGIN_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ else
+ status = ExtensionLoginResponse(inputStream, length);
+ break;
+
+ case SMTP_SEND_HELO_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ else
+ status = SendHeloResponse(inputStream, length);
+ break;
+ case SMTP_SEND_EHLO_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ else
+ status = SendEhloResponse(inputStream, length);
+ break;
+ case SMTP_AUTH_PROCESS_STATE:
+ status = ProcessAuth();
+ break;
+
+ case SMTP_SEND_AUTH_GSSAPI_FIRST:
+ status = AuthGSSAPIFirst();
+ break;
+
+ case SMTP_SEND_AUTH_GSSAPI_STEP:
+ status = AuthGSSAPIStep();
+ break;
+
+ case SMTP_SEND_AUTH_LOGIN_STEP0:
+ status = AuthLoginStep0();
+ break;
+
+ case SMTP_AUTH_LOGIN_STEP0_RESPONSE:
+ AuthLoginStep0Response();
+ status = NS_OK;
+ break;
+
+ case SMTP_AUTH_EXTERNAL_RESPONSE:
+ case SMTP_AUTH_LOGIN_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ else
+ status = AuthLoginResponse(inputStream, length);
+ break;
+
+ case SMTP_SEND_AUTH_LOGIN_STEP1:
+ status = AuthLoginStep1();
+ break;
+
+ case SMTP_SEND_AUTH_LOGIN_STEP2:
+ status = AuthLoginStep2();
+ break;
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ case SMTP_AUTH_OAUTH2_STEP:
+ status = AuthOAuth2Step1();
+ break;
+#endif
+
+ case SMTP_SEND_MAIL_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ else
+ status = SendMailResponse();
+ break;
+
+ case SMTP_SEND_RCPT_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ else
+ status = SendRecipientResponse();
+ break;
+
+ case SMTP_SEND_DATA_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ else
+ status = SendDataResponse();
+ break;
+
+ case SMTP_SEND_POST_DATA:
+ SendPostData();
+ status = NS_OK;
+ break;
+
+ case SMTP_SEND_MESSAGE_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(SMTP_PAUSE_FOR_READ);
+ else
+ status = SendMessageResponse();
+ break;
+ case SMTP_DONE:
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(m_runningURL);
+ mailNewsUrl->SetUrlState(false, NS_OK);
+ }
+
+ m_nextState = SMTP_FREE;
+ break;
+
+ case SMTP_ERROR_DONE:
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(m_runningURL);
+ // propagate the right error code
+ mailNewsUrl->SetUrlState(false, m_urlErrorState);
+ }
+
+ m_nextState = SMTP_FREE;
+ break;
+
+ case SMTP_FREE:
+ // smtp is a one time use connection so kill it if we get here...
+ nsMsgAsyncWriteProtocol::CloseSocket();
+ return NS_OK; /* final end */
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ // This state means we're going into an async loop and waiting for
+ // something (say auth) to happen. ProcessProtocolState will be
+ // retriggered when necessary.
+ case SMTP_SUSPENDED:
+ return NS_OK;
+#endif
+
+ default: /* should never happen !!! */
+ m_nextState = SMTP_ERROR_DONE;
+ break;
+ }
+
+ /* check for errors during load and call error
+ * state if found
+ */
+ if (NS_FAILED(status) && m_nextState != SMTP_FREE) {
+ // send a quit command to close the connection with the server.
+ if (NS_FAILED(SendQuit(SMTP_ERROR_DONE)))
+ {
+ m_nextState = SMTP_ERROR_DONE;
+ // Don't exit - loop around again and do the free case
+ ClearFlag(SMTP_PAUSE_FOR_READ);
+ }
+ }
+ } /* while(!SMTP_PAUSE_FOR_READ) */
+
+ return NS_OK;
+}
+
+nsresult
+nsSmtpProtocol::GetPassword(nsCString &aPassword)
+{
+ nsresult rv;
+ nsCOMPtr<nsISmtpUrl> smtpUrl = do_QueryInterface(m_runningURL, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = smtpUrl->GetSmtpServer(getter_AddRefs(smtpServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = smtpServer->GetPassword(aPassword);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!aPassword.IsEmpty())
+ return rv;
+ // empty password
+
+ nsCOMPtr <nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString username;
+ rv = smtpServer->GetUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertASCIItoUTF16 usernameUTF16(username);
+
+ nsCString hostname;
+ rv = smtpServer->GetHostname(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString hostnameUTF16;
+ CopyASCIItoUTF16(hostname, hostnameUTF16);
+
+ const char16_t *formatStrings[] =
+ {
+ hostnameUTF16.get(),
+ usernameUTF16.get()
+ };
+
+ rv = PromptForPassword(smtpServer, smtpUrl, formatStrings, aPassword);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+/**
+ * formatStrings is an array for the prompts, item 0 is the hostname, item 1
+ * is the username.
+ */
+nsresult
+nsSmtpProtocol::PromptForPassword(nsISmtpServer *aSmtpServer, nsISmtpUrl *aSmtpUrl, const char16_t **formatStrings, nsACString &aPassword)
+{
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(stringService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> composeStringBundle;
+ nsresult rv = stringService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(composeStringBundle));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString passwordPromptString;
+ if(formatStrings[1])
+ rv = composeStringBundle->FormatStringFromName(
+ u"smtpEnterPasswordPromptWithUsername",
+ formatStrings, 2, getter_Copies(passwordPromptString));
+ else
+ rv = composeStringBundle->FormatStringFromName(
+ u"smtpEnterPasswordPrompt",
+ formatStrings, 1, getter_Copies(passwordPromptString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAuthPrompt> netPrompt;
+ rv = aSmtpUrl->GetAuthPrompt(getter_AddRefs(netPrompt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString passwordTitle;
+ rv = composeStringBundle->GetStringFromName(
+ u"smtpEnterPasswordPromptTitle",
+ getter_Copies(passwordTitle));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aSmtpServer->GetPasswordWithUI(passwordPromptString.get(), passwordTitle.get(),
+ netPrompt, aPassword);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult
+nsSmtpProtocol::GetUsernamePassword(nsACString &aUsername,
+ nsACString &aPassword)
+{
+ nsresult rv;
+ nsCOMPtr<nsISmtpUrl> smtpUrl = do_QueryInterface(m_runningURL, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = smtpUrl->GetSmtpServer(getter_AddRefs(smtpServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = smtpServer->GetPassword(aPassword);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!aPassword.IsEmpty())
+ {
+ rv = smtpServer->GetUsername(aUsername);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!aUsername.IsEmpty())
+ return rv;
+ }
+ // empty password
+
+ aPassword.Truncate();
+
+ nsCString hostname;
+ rv = smtpServer->GetHostname(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString hostnameUTF16;
+ CopyASCIItoUTF16(hostname, hostnameUTF16);
+
+ const char16_t *formatStrings[] =
+ {
+ hostnameUTF16.get(),
+ nullptr
+ };
+
+ rv = PromptForPassword(smtpServer, smtpUrl, formatStrings, aPassword);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
diff --git a/mailnews/compose/src/nsSmtpProtocol.h b/mailnews/compose/src/nsSmtpProtocol.h
new file mode 100644
index 000000000..b1370d4be
--- /dev/null
+++ b/mailnews/compose/src/nsSmtpProtocol.h
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSmtpProtocol_h___
+#define nsSmtpProtocol_h___
+
+#include "mozilla/Attributes.h"
+#ifdef MOZ_MAILNEWS_OAUTH2
+#include "msgIOAuth2Module.h"
+#endif
+#include "nsMsgProtocol.h"
+#include "nsIStreamListener.h"
+#include "nsISmtpUrl.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsMsgLineBuffer.h"
+#include "nsIAuthModule.h"
+#include "MailNewsTypes2.h" // for nsMsgSocketType
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+class nsIVariant;
+class nsIWritableVariant;
+#endif
+
+ /* states of the machine
+ */
+typedef enum _SmtpState {
+SMTP_RESPONSE = 0, // 0
+SMTP_START_CONNECT, // 1
+SMTP_FINISH_CONNECT, // 2
+SMTP_SEND_HELO_RESPONSE, // 3
+SMTP_SEND_EHLO_RESPONSE, // 4
+SMTP_SEND_MAIL_RESPONSE, // 5
+SMTP_SEND_RCPT_RESPONSE, // 6
+SMTP_SEND_DATA_RESPONSE, // 7
+SMTP_SEND_POST_DATA, // 8
+SMTP_SEND_MESSAGE_RESPONSE, // 9
+SMTP_DONE, // 10
+SMTP_ERROR_DONE, // 11
+SMTP_FREE, // 12
+SMTP_AUTH_LOGIN_STEP0_RESPONSE, // 13
+SMTP_EXTN_LOGIN_RESPONSE, // 14
+SMTP_SEND_AUTH_LOGIN_STEP0, // 15
+SMTP_SEND_AUTH_LOGIN_STEP1, // 16
+SMTP_SEND_AUTH_LOGIN_STEP2, // 17
+SMTP_AUTH_LOGIN_RESPONSE, // 18
+SMTP_TLS_RESPONSE, // 19
+SMTP_AUTH_EXTERNAL_RESPONSE, // 20
+SMTP_AUTH_PROCESS_STATE, // 21
+SMTP_AUTH_CRAM_MD5_CHALLENGE_RESPONSE, // 22
+SMTP_SEND_AUTH_GSSAPI_FIRST, // 23
+SMTP_SEND_AUTH_GSSAPI_STEP, // 24
+#ifdef MOZ_MAILNEWS_OAUTH2
+SMTP_SUSPENDED, // 25
+SMTP_AUTH_OAUTH2_STEP, // 26
+SMTP_AUTH_OAUTH2_RESPONSE, // 27
+#endif
+} SmtpState;
+
+// State Flags (Note, I use the word state in terms of storing
+// state information about the connection (authentication, have we sent
+// commands, etc. I do not intend it to refer to protocol state)
+#define SMTP_PAUSE_FOR_READ 0x00000001 /* should we pause for the next read */
+#define SMTP_ESMTP_SERVER 0x00000002
+#define SMTP_EHLO_DSN_ENABLED 0x00000004
+#define SMTP_EHLO_STARTTLS_ENABLED 0x00000008
+#define SMTP_EHLO_SIZE_ENABLED 0x00000010
+#define SMTP_EHLO_8BIT_ENABLED 0x00000020
+
+// insecure mechanisms follow
+#define SMTP_AUTH_LOGIN_ENABLED 0x00000100
+#define SMTP_AUTH_PLAIN_ENABLED 0x00000200
+#define SMTP_AUTH_EXTERNAL_ENABLED 0x00000400
+// secure mechanisms follow
+#define SMTP_AUTH_GSSAPI_ENABLED 0x00000800
+#define SMTP_AUTH_DIGEST_MD5_ENABLED 0x00001000
+#define SMTP_AUTH_CRAM_MD5_ENABLED 0x00002000
+#define SMTP_AUTH_NTLM_ENABLED 0x00004000
+#define SMTP_AUTH_MSN_ENABLED 0x00008000
+#ifdef MOZ_MAILNEWS_OAUTH2
+#define SMTP_AUTH_OAUTH2_ENABLED 0x00010000
+// sum of all above auth mechanisms
+#define SMTP_AUTH_ANY 0x0001FF00
+// indicates that AUTH has been advertised
+#define SMTP_AUTH 0x00020000
+// No login necessary (pref)
+#define SMTP_AUTH_NONE_ENABLED 0x00040000
+#else
+#define SMTP_AUTH_ANY 0x0000FF00
+#define SMTP_AUTH 0x00010000
+#define SMTP_AUTH_NONE_ENABLED 0x00020000
+#endif
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+class nsSmtpProtocol : public nsMsgAsyncWriteProtocol,
+ public msgIOAuth2ModuleListener
+#else
+class nsSmtpProtocol : public nsMsgAsyncWriteProtocol
+#endif
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+#ifdef MOZ_MAILNEWS_OAUTH2
+ NS_DECL_MSGIOAUTH2MODULELISTENER
+#endif
+
+ // Creating a protocol instance requires the URL which needs to be run.
+ explicit nsSmtpProtocol(nsIURI * aURL);
+
+ virtual nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer = nullptr) override;
+ virtual nsresult SendData(const char * dataBuffer, bool aSuppressLogging = false) override;
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we suppport the nsIStreamListener interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ // stop binding is a "notification" informing us that the stream associated with aURL is going away.
+ NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) override;
+
+private:
+ virtual ~nsSmtpProtocol();
+ // if we are asked to load a url while we are blocked waiting for redirection information,
+ // then we'll store the url consumer in mPendingConsumer until we can actually load
+ // the url.
+ nsCOMPtr<nsISupports> mPendingConsumer;
+
+ // the nsISmtpURL that is currently running
+ nsCOMPtr<nsISmtpUrl> m_runningURL;
+
+ // the error state we want to set on the url
+ nsresult m_urlErrorState;
+ nsCOMPtr<nsIMsgStatusFeedback> m_statusFeedback;
+
+ // Generic state information -- What state are we in? What state do we want to go to
+ // after the next response? What was the last response code? etc.
+ SmtpState m_nextState;
+ SmtpState m_nextStateAfterResponse;
+ int32_t m_responseCode; /* code returned from Smtp server */
+ int32_t m_previousResponseCode;
+ int32_t m_continuationResponse;
+ nsCString m_responseText; /* text returned from Smtp server */
+ RefPtr<nsMsgLineStreamBuffer> m_lineStreamBuffer; // used to efficiently extract lines from the incoming data stream
+
+ nsTArray<nsCString> m_addresses;
+ uint32_t m_addressesLeft;
+ nsCString m_mailAddr;
+ nsCString m_helloArgument;
+ int32_t m_sizelimit;
+
+ // *** the following should move to the smtp server when we support
+ // multiple smtp servers
+ bool m_usernamePrompted;
+ int32_t m_prefSocketType;
+ bool m_tlsEnabled;
+
+ bool m_tlsInitiated;
+
+ bool m_sendDone;
+
+ int32_t m_totalAmountRead;
+ int64_t m_totalMessageSize;
+
+ char *m_dataBuf;
+ uint32_t m_dataBufSize;
+
+ int32_t m_originalContentLength; /* the content length at the time of calling graph progress */
+
+ // initialization function given a new url and transport layer
+ void Initialize(nsIURI * aURL);
+ virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length) override;
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Communication methods --> Reading and writing protocol
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ void UpdateStatus(const char16_t* aStatusName);
+ void UpdateStatusWithString(const char16_t * aStatusString);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Protocol Methods --> This protocol is state driven so each protocol method is
+ // designed to re-act to the current "state". I've attempted to
+ // group them together based on functionality.
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ nsresult SmtpResponse(nsIInputStream * inputStream, uint32_t length);
+ nsresult ExtensionLoginResponse(nsIInputStream * inputStream, uint32_t length);
+ nsresult SendHeloResponse(nsIInputStream * inputStream, uint32_t length);
+ nsresult SendEhloResponse(nsIInputStream * inputStream, uint32_t length);
+ nsresult SendQuit(SmtpState aNextStateAfterResponse = SMTP_DONE);
+
+ nsresult AuthGSSAPIFirst();
+ nsresult AuthGSSAPIStep();
+ nsresult AuthLoginStep0();
+ void AuthLoginStep0Response();
+ nsresult AuthLoginStep1();
+ nsresult AuthLoginStep2();
+ nsresult AuthLoginResponse(nsIInputStream * stream, uint32_t length);
+#ifdef MOZ_MAILNEWS_OAUTH2
+ nsresult AuthOAuth2Step1();
+#endif
+
+ nsresult SendTLSResponse();
+ nsresult SendMailResponse();
+ nsresult SendRecipientResponse();
+ nsresult SendDataResponse();
+ void SendPostData();
+ nsresult SendMessageResponse();
+ nsresult ProcessAuth();
+
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // End of Protocol Methods
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ void SendMessageInFile();
+
+ void AppendHelloArgument(nsACString& aResult);
+ nsresult GetPassword(nsCString &aPassword);
+ nsresult GetUsernamePassword(nsACString &aUsername, nsACString &aPassword);
+ nsresult PromptForPassword(nsISmtpServer *aSmtpServer, nsISmtpUrl *aSmtpUrl,
+ const char16_t **formatStrings,
+ nsACString &aPassword);
+
+ void InitPrefAuthMethods(int32_t authMethodPrefValue);
+ nsresult ChooseAuthMethod();
+ void MarkAuthMethodAsFailed(int32_t failedAuthMethod);
+ void ResetAuthMethods();
+
+ virtual const char* GetType() override {return "smtp";}
+
+ int32_t m_prefAuthMethods; // set of capability flags for auth methods
+ int32_t m_failedAuthMethods; // ditto
+ int32_t m_currentAuthMethod; // exactly one capability flag, or 0
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ // The support module for OAuth2 logon, only present if OAuth2 is enabled
+ // and working.
+ nsCOMPtr<msgIOAuth2Module> mOAuth2Support;
+#endif
+};
+
+#endif // nsSmtpProtocol_h___
diff --git a/mailnews/compose/src/nsSmtpServer.cpp b/mailnews/compose/src/nsSmtpServer.cpp
new file mode 100644
index 000000000..555993ad0
--- /dev/null
+++ b/mailnews/compose/src/nsSmtpServer.cpp
@@ -0,0 +1,689 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsSmtpServer.h"
+#include "nsNetUtil.h"
+#include "nsIAuthPrompt.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsISmtpService.h"
+#include "nsMsgCompCID.h"
+#include "nsILoginInfo.h"
+#include "nsILoginManager.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+#include "nsIObserverService.h"
+
+NS_IMPL_ADDREF(nsSmtpServer)
+NS_IMPL_RELEASE(nsSmtpServer)
+NS_INTERFACE_MAP_BEGIN(nsSmtpServer)
+ NS_INTERFACE_MAP_ENTRY(nsISmtpServer)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISmtpServer)
+NS_INTERFACE_MAP_END
+
+nsSmtpServer::nsSmtpServer():
+ mKey("")
+{
+ m_logonFailed = false;
+ getPrefs();
+}
+
+nsSmtpServer::~nsSmtpServer()
+{
+}
+
+nsresult nsSmtpServer::Init()
+{
+ // We need to know when the password manager changes.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ observerService->AddObserver(this, "passwordmgr-storage-changed", false);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::Observe(nsISupports *aSubject, const char* aTopic,
+ const char16_t *aData)
+{
+ // When the state of the password manager changes we need to clear the
+ // password from the cache in case the user just removed it.
+ if (strcmp(aTopic, "passwordmgr-storage-changed") == 0)
+ {
+ m_password.Truncate();
+ }
+ else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0)
+ {
+ // Now remove ourselves from the observer service as well.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ observerService->RemoveObserver(this, "passwordmgr-storage-changed");
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsSmtpServer::GetKey(char * *aKey)
+{
+ if (!aKey) return NS_ERROR_NULL_POINTER;
+ if (mKey.IsEmpty())
+ *aKey = nullptr;
+ else
+ *aKey = ToNewCString(mKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::SetKey(const char * aKey)
+{
+ NS_ASSERTION(aKey, "Bad key pointer");
+ mKey = aKey;
+ return getPrefs();
+}
+
+nsresult nsSmtpServer::getPrefs()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString branchName;
+ branchName.AssignLiteral("mail.smtpserver.");
+ branchName += mKey;
+ branchName.Append('.');
+ rv = prefs->GetBranch(branchName.get(), getter_AddRefs(mPrefBranch));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if(!mDefPrefBranch) {
+ branchName.AssignLiteral("mail.smtpserver.default.");
+ rv = prefs->GetBranch(branchName.get(), getter_AddRefs(mDefPrefBranch));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// This function is intentionally called the same as in nsIMsgIncomingServer.
+nsresult
+nsSmtpServer::OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged)
+{
+ // Reset password so that users are prompted for new password for the new user/host.
+ (void)ForgetPassword();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetHostname(nsACString &aHostname)
+{
+ nsCString result;
+ nsresult rv = mPrefBranch->GetCharPref("hostname", getter_Copies(result));
+ if (NS_FAILED(rv))
+ aHostname.Truncate();
+ else
+ aHostname = result;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::SetHostname(const nsACString &aHostname)
+{
+ nsCString oldName;
+ nsresult rv = GetHostname(oldName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // A few things to take care of if we're changing the hostname.
+ if (!oldName.Equals(aHostname, nsCaseInsensitiveCStringComparator())) {
+ rv = OnUserOrHostNameChanged(oldName, aHostname, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aHostname.IsEmpty())
+ return mPrefBranch->SetCharPref("hostname", PromiseFlatCString(aHostname).get());
+
+ // If the pref value is already empty, ClearUserPref will return
+ // NS_ERROR_UNEXPECTED, so don't check the rv here.
+ (void)mPrefBranch->ClearUserPref("hostname");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetDescription(nsACString &aDescription)
+{
+ nsCString temp;
+ mPrefBranch->GetCharPref("description", getter_Copies(temp));
+ aDescription.Assign(temp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::SetDescription(const nsACString &aDescription)
+{
+ if (!aDescription.IsEmpty())
+ return mPrefBranch->SetCharPref("description", PromiseFlatCString(aDescription).get());
+ else
+ mPrefBranch->ClearUserPref("description");
+ return NS_OK;
+}
+
+// if GetPort returns 0, it means default port
+NS_IMETHODIMP
+nsSmtpServer::GetPort(int32_t *aPort)
+{
+ NS_ENSURE_ARG_POINTER(aPort);
+ if (NS_FAILED(mPrefBranch->GetIntPref("port", aPort)))
+ *aPort = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::SetPort(int32_t aPort)
+{
+ if (aPort)
+ return mPrefBranch->SetIntPref("port", aPort);
+
+ mPrefBranch->ClearUserPref("port");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetDisplayname(char * *aDisplayname)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aDisplayname);
+
+ nsCString hostname;
+ rv = mPrefBranch->GetCharPref("hostname", getter_Copies(hostname));
+ if (NS_FAILED(rv)) {
+ *aDisplayname=nullptr;
+ return NS_OK;
+ }
+ int32_t port;
+ rv = mPrefBranch->GetIntPref("port", &port);
+ if (NS_FAILED(rv))
+ port = 0;
+
+ if (port) {
+ hostname.Append(':');
+ hostname.AppendInt(port);
+ }
+
+ *aDisplayname = ToNewCString(hostname);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetSocketType(int32_t *socketType)
+{
+ NS_ENSURE_ARG_POINTER(socketType);
+ getIntPrefWithDefault("try_ssl", socketType, 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::SetSocketType(int32_t socketType)
+{
+ return mPrefBranch->SetIntPref("try_ssl", socketType);
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetHelloArgument(char * *aHelloArgument)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aHelloArgument);
+ rv = mPrefBranch->GetCharPref("hello_argument", aHelloArgument);
+ if (NS_FAILED(rv))
+ {
+ rv = mDefPrefBranch->GetCharPref("hello_argument", aHelloArgument);
+ if (NS_FAILED(rv))
+ *aHelloArgument = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetAuthMethod(int32_t *authMethod)
+{
+ NS_ENSURE_ARG_POINTER(authMethod);
+ getIntPrefWithDefault("authMethod", authMethod, 3);
+ return NS_OK;
+}
+
+void
+nsSmtpServer::getIntPrefWithDefault(const char *prefName,
+ int32_t *val,
+ int32_t defVal)
+{
+ nsresult rv = mPrefBranch->GetIntPref(prefName, val);
+ if (NS_SUCCEEDED(rv))
+ return;
+
+ rv = mDefPrefBranch->GetIntPref(prefName, val);
+ if (NS_FAILED(rv))
+ // last resort
+ *val = defVal;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::SetAuthMethod(int32_t authMethod)
+{
+ return mPrefBranch->SetIntPref("authMethod", authMethod);
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetUsername(nsACString &aUsername)
+{
+ nsCString result;
+ nsresult rv = mPrefBranch->GetCharPref("username", getter_Copies(result));
+ if (NS_FAILED(rv))
+ aUsername.Truncate();
+ else
+ aUsername = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::SetUsername(const nsACString &aUsername)
+{
+ // Need to take care of few things if we're changing the username.
+ nsCString oldName;
+ nsresult rv = GetUsername(oldName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!oldName.Equals(aUsername)) {
+ rv = OnUserOrHostNameChanged(oldName, aUsername, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aUsername.IsEmpty())
+ return mPrefBranch->SetCharPref("username", PromiseFlatCString(aUsername).get());
+
+ // If the pref value is already empty, ClearUserPref will return
+ // NS_ERROR_UNEXPECTED, so don't check the rv here.
+ (void)mPrefBranch->ClearUserPref("username");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetPassword(nsACString& aPassword)
+{
+ if (m_password.IsEmpty() && !m_logonFailed)
+ {
+ // try to avoid prompting the user for another password. If the user has set
+ // the appropriate pref, we'll use the password from an incoming server, if
+ // the user has already logged onto that server.
+
+ // if this is set, we'll only use this, and not the other prefs
+ // user_pref("mail.smtpserver.smtp1.incomingAccount", "server1");
+
+ // if this is set, we'll accept an exact match of user name and server
+ // user_pref("mail.smtp.useMatchingHostNameServer", true);
+
+ // if this is set, and we don't find an exact match of user and host name,
+ // we'll accept a match of username and domain, where domain
+ // is everything after the first '.'
+ // user_pref("mail.smtp.useMatchingDomainServer", true);
+
+ nsCString accountKey;
+ bool useMatchingHostNameServer = false;
+ bool useMatchingDomainServer = false;
+ mPrefBranch->GetCharPref("incomingAccount", getter_Copies(accountKey));
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID);
+ nsCOMPtr<nsIMsgIncomingServer> incomingServerToUse;
+ if (accountManager)
+ {
+ if (!accountKey.IsEmpty())
+ accountManager->GetIncomingServer(accountKey, getter_AddRefs(incomingServerToUse));
+ else
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+ prefBranch->GetBoolPref("mail.smtp.useMatchingHostNameServer", &useMatchingHostNameServer);
+ prefBranch->GetBoolPref("mail.smtp.useMatchingDomainServer", &useMatchingDomainServer);
+ if (useMatchingHostNameServer || useMatchingDomainServer)
+ {
+ nsCString userName;
+ nsCString hostName;
+ GetHostname(hostName);
+ GetUsername(userName);
+ if (useMatchingHostNameServer)
+ // pass in empty type and port=0, to match imap and pop3.
+ accountManager->FindRealServer(userName, hostName, EmptyCString(), 0, getter_AddRefs(incomingServerToUse));
+ int32_t dotPos = -1;
+ if (!incomingServerToUse && useMatchingDomainServer
+ && (dotPos = hostName.FindChar('.')) != kNotFound)
+ {
+ hostName.Cut(0, dotPos);
+ nsCOMPtr<nsIArray> allServers;
+ accountManager->GetAllServers(getter_AddRefs(allServers));
+ if (allServers)
+ {
+ uint32_t count = 0;
+ allServers->GetLength(&count);
+ uint32_t i;
+ for (i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(allServers, i);
+ if (server)
+ {
+ nsCString serverUserName;
+ nsCString serverHostName;
+ server->GetRealUsername(serverUserName);
+ server->GetRealHostName(serverHostName);
+ if (serverUserName.Equals(userName))
+ {
+ int32_t serverDotPos = serverHostName.FindChar('.');
+ if (serverDotPos != kNotFound)
+ {
+ serverHostName.Cut(0, serverDotPos);
+ if (serverHostName.Equals(hostName))
+ {
+ incomingServerToUse = server;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (incomingServerToUse)
+ return incomingServerToUse->GetPassword(aPassword);
+ }
+ aPassword = m_password;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::VerifyLogon(nsIUrlListener *aUrlListener, nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ nsresult rv;
+ nsCOMPtr<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return smtpService->VerifyLogon(this, aUrlListener, aMsgWindow, aURL);
+}
+
+
+NS_IMETHODIMP
+nsSmtpServer::SetPassword(const nsACString& aPassword)
+{
+ m_password = aPassword;
+ return NS_OK;
+}
+
+nsresult
+nsSmtpServer::GetPasswordWithoutUI()
+{
+ nsresult rv;
+ nsCOMPtr<nsILoginManager> loginMgr(do_GetService(NS_LOGINMANAGER_CONTRACTID,
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertASCIItoUTF16 serverUri(GetServerURIInternal(false));
+
+ uint32_t numLogins = 0;
+ nsILoginInfo** logins = nullptr;
+ rv = loginMgr->FindLogins(&numLogins, serverUri, EmptyString(),
+ serverUri, &logins);
+ // Login manager can produce valid fails, e.g. NS_ERROR_ABORT when a user
+ // cancels the master password dialog. Therefore handle that here, but don't
+ // warn about it.
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Don't abort here, if we didn't find any or failed, then we'll just have
+ // to prompt.
+ if (numLogins > 0)
+ {
+ nsCString serverCUsername;
+ rv = GetUsername(serverCUsername);
+ NS_ConvertASCIItoUTF16 serverUsername(serverCUsername);
+
+ nsString username;
+ for (uint32_t i = 0; i < numLogins; ++i)
+ {
+ rv = logins[i]->GetUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (username.Equals(serverUsername))
+ {
+ nsString password;
+ rv = logins[i]->GetPassword(password);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LossyCopyUTF16toASCII(password, m_password);
+ break;
+ }
+ }
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numLogins, logins);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetPasswordWithUI(const char16_t *aPromptMessage,
+ const char16_t *aPromptTitle,
+ nsIAuthPrompt* aDialog,
+ nsACString &aPassword)
+{
+ if (!m_password.IsEmpty())
+ return GetPassword(aPassword);
+
+ // We need to get a password, but see if we can get it from the password
+ // manager without requiring a prompt.
+ nsresult rv = GetPasswordWithoutUI();
+ if (rv == NS_ERROR_ABORT)
+ return NS_MSG_PASSWORD_PROMPT_CANCELLED;
+
+ // Now re-check if we've got a password or not, if we have, then we
+ // don't need to prompt the user.
+ if (!m_password.IsEmpty())
+ {
+ aPassword = m_password;
+ return NS_OK;
+ }
+
+ NS_ENSURE_ARG_POINTER(aDialog);
+
+ // PromptPassword needs the username as well.
+ nsCString serverUri(GetServerURIInternal(true));
+
+ bool okayValue = true;
+ nsString uniPassword;
+
+ rv = aDialog->PromptPassword(aPromptTitle, aPromptMessage,
+ NS_ConvertASCIItoUTF16(serverUri).get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ getter_Copies(uniPassword), &okayValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the user pressed cancel, just return an empty string.
+ if (!okayValue)
+ {
+ aPassword.Truncate();
+ return NS_MSG_PASSWORD_PROMPT_CANCELLED;
+ }
+
+ NS_LossyConvertUTF16toASCII password(uniPassword);
+
+ rv = SetPassword(password);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPassword = password;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetUsernamePasswordWithUI(const char16_t * aPromptMessage, const
+ char16_t *aPromptTitle,
+ nsIAuthPrompt* aDialog,
+ nsACString &aUsername,
+ nsACString &aPassword)
+{
+ nsresult rv;
+ if (!m_password.IsEmpty())
+ {
+ rv = GetUsername(aUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetPassword(aPassword);
+ }
+
+ NS_ENSURE_ARG_POINTER(aDialog);
+
+ nsCString serverUri;
+ rv = GetServerURI(serverUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString uniUsername;
+ nsString uniPassword;
+ bool okayValue = true;
+
+ rv = aDialog->PromptUsernameAndPassword(aPromptTitle, aPromptMessage,
+ NS_ConvertASCIItoUTF16(serverUri).get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ getter_Copies(uniUsername),
+ getter_Copies(uniPassword),
+ &okayValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the user pressed cancel, just return emtpy strings.
+ if (!okayValue)
+ {
+ aUsername.Truncate();
+ aPassword.Truncate();
+ return rv;
+ }
+
+ // We got a username and password back...so remember them.
+ NS_LossyConvertUTF16toASCII username(uniUsername);
+
+ rv = SetUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_LossyConvertUTF16toASCII password(uniPassword);
+
+ rv = SetPassword(password);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aUsername = username;
+ aPassword = password;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::ForgetPassword()
+{
+ nsresult rv;
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current server URI without the username
+ uint32_t count;
+ nsILoginInfo** logins;
+
+ NS_ConvertASCIItoUTF16 serverUri(GetServerURIInternal(false));
+
+ nsCString serverCUsername;
+ rv = GetUsername(serverCUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 serverUsername(serverCUsername);
+
+ rv = loginMgr->FindLogins(&count, serverUri, EmptyString(),
+ serverUri, &logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // There should only be one-login stored for this url, however just in case
+ // there isn't.
+ nsString username;
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ if (NS_SUCCEEDED(logins[i]->GetUsername(username)) &&
+ username.Equals(serverUsername))
+ {
+ // If this fails, just continue, we'll still want to remove the password
+ // from our local cache.
+ loginMgr->RemoveLogin(logins[i]);
+ }
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins);
+
+ rv = SetPassword(EmptyCString());
+ m_logonFailed = true;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::GetServerURI(nsACString &aResult)
+{
+ aResult = GetServerURIInternal(true);
+ return NS_OK;
+}
+
+nsCString
+nsSmtpServer::GetServerURIInternal(const bool aIncludeUsername)
+{
+ nsCString uri(NS_LITERAL_CSTRING("smtp://"));
+ nsresult rv;
+
+ if (aIncludeUsername)
+ {
+ nsCString username;
+ rv = GetUsername(username);
+
+ if (NS_SUCCEEDED(rv) && !username.IsEmpty()) {
+ nsCString escapedUsername;
+ MsgEscapeString(username, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+ // not all servers have a username
+ uri.Append(escapedUsername);
+ uri.AppendLiteral("@");
+ }
+ }
+
+ nsCString hostname;
+ rv = GetHostname(hostname);
+
+ if (NS_SUCCEEDED(rv) && !hostname.IsEmpty()) {
+ nsCString escapedHostname;
+ MsgEscapeString(hostname, nsINetUtil::ESCAPE_URL_PATH, escapedHostname);
+ // not all servers have a hostname
+ uri.Append(escapedHostname);
+ }
+
+ return uri;
+}
+
+NS_IMETHODIMP
+nsSmtpServer::ClearAllValues()
+{
+ return mPrefBranch->DeleteBranch("");
+}
diff --git a/mailnews/compose/src/nsSmtpServer.h b/mailnews/compose/src/nsSmtpServer.h
new file mode 100644
index 000000000..9dbbe54b4
--- /dev/null
+++ b/mailnews/compose/src/nsSmtpServer.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsSmtpServer_h_
+#define __nsSmtpServer_h_
+
+
+#include "nsStringGlue.h"
+#include "nsISmtpServer.h"
+#include "nsIPrefBranch.h"
+#include "nsWeakReference.h"
+#include "nsIObserver.h"
+
+class nsSmtpServer : public nsISmtpServer,
+ public nsSupportsWeakReference,
+ public nsIObserver
+{
+public:
+ nsSmtpServer();
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISMTPSERVER
+ NS_DECL_NSIOBSERVER
+
+private:
+ virtual ~nsSmtpServer();
+ nsCString mKey;
+ nsCOMPtr<nsIPrefBranch> mPrefBranch;
+ nsCOMPtr<nsIPrefBranch> mDefPrefBranch;
+
+ nsresult getPrefs();
+ void getIntPrefWithDefault(const char *prefName, int32_t *val,
+ int32_t defval);
+ nsresult GetPasswordWithoutUI();
+ nsCString GetServerURIInternal(const bool aIncludeUsername);
+
+ nsresult OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged);
+
+ nsCString m_password;
+ bool m_logonFailed;
+};
+
+#endif
diff --git a/mailnews/compose/src/nsSmtpService.cpp b/mailnews/compose/src/nsSmtpService.cpp
new file mode 100644
index 000000000..9409811ff
--- /dev/null
+++ b/mailnews/compose/src/nsSmtpService.cpp
@@ -0,0 +1,775 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIIOService.h"
+#include "nsIPipe.h"
+#include "nsNetCID.h"
+#include "nsMsgUtils.h"
+#include "nsNetUtil.h"
+#include "nsSmtpService.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgCompCID.h"
+#include "nsArrayEnumerator.h"
+#include "nsSmtpUrl.h"
+#include "nsSmtpProtocol.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgIdentity.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+#include "nsIUTF8ConverterService.h"
+#include "nsUConvCID.h"
+#include "nsAutoPtr.h"
+#include "nsComposeStrings.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIPrincipal.h"
+
+#define SERVER_DELIMITER ','
+#define APPEND_SERVERS_VERSION_PREF_NAME "append_preconfig_smtpservers.version"
+#define MAIL_ROOT_PREF "mail."
+#define PREF_MAIL_SMTPSERVERS "mail.smtpservers"
+#define PREF_MAIL_SMTPSERVERS_APPEND_SERVERS "mail.smtpservers.appendsmtpservers"
+#define PREF_MAIL_SMTP_DEFAULTSERVER "mail.smtp.defaultserver"
+
+typedef struct _findServerByKeyEntry {
+ const char *key;
+ nsISmtpServer *server;
+} findServerByKeyEntry;
+
+typedef struct _findServerByHostnameEntry {
+ nsCString hostname;
+ nsCString username;
+ nsISmtpServer *server;
+} findServerByHostnameEntry;
+
+static NS_DEFINE_CID(kCSmtpUrlCID, NS_SMTPURL_CID);
+static NS_DEFINE_CID(kCMailtoUrlCID, NS_MAILTOURL_CID);
+
+// foward declarations...
+nsresult
+NS_MsgBuildSmtpUrl(nsIFile * aFilePath,
+ nsISmtpServer *aServer,
+ const char* aRecipients,
+ nsIMsgIdentity * aSenderIdentity,
+ nsIUrlListener * aUrlListener,
+ nsIMsgStatusFeedback *aStatusFeedback,
+ nsIInterfaceRequestor* aNotificationCallbacks,
+ nsIURI ** aUrl,
+ bool aRequestDSN);
+
+nsresult NS_MsgLoadSmtpUrl(nsIURI * aUrl, nsISupports * aConsumer, nsIRequest ** aRequest);
+
+nsSmtpService::nsSmtpService() :
+ mSmtpServersLoaded(false)
+{
+}
+
+nsSmtpService::~nsSmtpService()
+{
+ // save the SMTP servers to disk
+
+}
+
+NS_IMPL_ISUPPORTS(nsSmtpService, nsISmtpService, nsIProtocolHandler)
+
+
+NS_IMETHODIMP nsSmtpService::SendMailMessage(nsIFile * aFilePath,
+ const char * aRecipients,
+ nsIMsgIdentity * aSenderIdentity,
+ const char * aPassword,
+ nsIUrlListener * aUrlListener,
+ nsIMsgStatusFeedback *aStatusFeedback,
+ nsIInterfaceRequestor* aNotificationCallbacks,
+ bool aRequestDSN,
+ nsIURI ** aURL,
+ nsIRequest ** aRequest)
+{
+ nsIURI * urlToRun = nullptr;
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = GetServerByIdentity(aSenderIdentity, getter_AddRefs(smtpServer));
+
+ if (NS_SUCCEEDED(rv) && smtpServer)
+ {
+ if (aPassword && *aPassword)
+ smtpServer->SetPassword(nsDependentCString(aPassword));
+
+ // this ref counts urlToRun
+ rv = NS_MsgBuildSmtpUrl(aFilePath, smtpServer, aRecipients, aSenderIdentity,
+ aUrlListener, aStatusFeedback,
+ aNotificationCallbacks, &urlToRun, aRequestDSN);
+ if (NS_SUCCEEDED(rv) && urlToRun)
+ rv = NS_MsgLoadSmtpUrl(urlToRun, nullptr, aRequest);
+
+ if (aURL) // does the caller want a handle on the url?
+ *aURL = urlToRun; // transfer our ref count to the caller....
+ else
+ NS_IF_RELEASE(urlToRun);
+ }
+
+ return rv;
+}
+
+
+// The following are two convience functions I'm using to help expedite building and running a mail to url...
+
+// short cut function for creating a mailto url...
+nsresult NS_MsgBuildSmtpUrl(nsIFile * aFilePath,
+ nsISmtpServer *aSmtpServer,
+ const char * aRecipients,
+ nsIMsgIdentity * aSenderIdentity,
+ nsIUrlListener * aUrlListener,
+ nsIMsgStatusFeedback *aStatusFeedback,
+ nsIInterfaceRequestor* aNotificationCallbacks,
+ nsIURI ** aUrl,
+ bool aRequestDSN)
+{
+ // mscott: this function is a convience hack until netlib actually dispatches
+ // smtp urls. in addition until we have a session to get a password, host and
+ // other stuff from, we need to use default values....
+ // ..for testing purposes....
+
+ nsCString smtpHostName;
+ nsCString smtpUserName;
+ int32_t smtpPort;
+ int32_t socketType;
+
+ aSmtpServer->GetHostname(smtpHostName);
+ aSmtpServer->GetUsername(smtpUserName);
+ aSmtpServer->GetPort(&smtpPort);
+ aSmtpServer->GetSocketType(&socketType);
+
+ if (!smtpPort)
+ smtpPort = (socketType == nsMsgSocketType::SSL) ?
+ nsISmtpUrl::DEFAULT_SMTPS_PORT : nsISmtpUrl::DEFAULT_SMTP_PORT;
+
+ nsresult rv;
+ nsCOMPtr<nsISmtpUrl> smtpUrl(do_CreateInstance(kCSmtpUrlCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString urlSpec("smtp://");
+
+ if (!smtpUserName.IsEmpty())
+ {
+ nsCString escapedUsername;
+ MsgEscapeString(smtpUserName, nsINetUtil::ESCAPE_XALPHAS,
+ escapedUsername);
+ urlSpec.Append(escapedUsername);
+ urlSpec.Append('@');
+ }
+
+ urlSpec.Append(smtpHostName);
+ if (smtpHostName.FindChar(':') == -1)
+ {
+ urlSpec.Append(':');
+ urlSpec.AppendInt(smtpPort);
+ }
+
+ nsCOMPtr<nsIMsgMailNewsUrl> url(do_QueryInterface(smtpUrl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->SetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ smtpUrl->SetRecipients(aRecipients);
+ smtpUrl->SetRequestDSN(aRequestDSN);
+ smtpUrl->SetPostMessageFile(aFilePath);
+ smtpUrl->SetSenderIdentity(aSenderIdentity);
+ if (aNotificationCallbacks)
+ smtpUrl->SetNotificationCallbacks(aNotificationCallbacks);
+ smtpUrl->SetSmtpServer(aSmtpServer);
+
+ nsCOMPtr<nsIPrompt> smtpPrompt(do_GetInterface(aNotificationCallbacks));
+ nsCOMPtr<nsIAuthPrompt> smtpAuthPrompt(do_GetInterface(aNotificationCallbacks));
+ if (!smtpPrompt || !smtpAuthPrompt)
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!smtpPrompt)
+ wwatch->GetNewPrompter(0, getter_AddRefs(smtpPrompt));
+ if (!smtpAuthPrompt)
+ wwatch->GetNewAuthPrompter(0, getter_AddRefs(smtpAuthPrompt));
+ }
+
+ smtpUrl->SetPrompt(smtpPrompt);
+ smtpUrl->SetAuthPrompt(smtpAuthPrompt);
+
+ if (aUrlListener)
+ url->RegisterListener(aUrlListener);
+ if (aStatusFeedback)
+ url->SetStatusFeedback(aStatusFeedback);
+
+ return CallQueryInterface(smtpUrl, aUrl);
+}
+
+nsresult NS_MsgLoadSmtpUrl(nsIURI * aUrl, nsISupports * aConsumer, nsIRequest ** aRequest)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+
+ // For now, assume the url is an smtp url and load it.
+ nsresult rv;
+ nsCOMPtr<nsISmtpUrl> smtpUrl(do_QueryInterface(aUrl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a smtp protocol instance to run the url in.
+ RefPtr<nsSmtpProtocol> smtpProtocol = new nsSmtpProtocol(aUrl);
+ if (!smtpProtocol)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Protocol will get destroyed when url is completed.
+ rv = smtpProtocol->LoadUrl(aUrl, aConsumer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(smtpProtocol.get(), aRequest);
+}
+
+NS_IMETHODIMP nsSmtpService::VerifyLogon(nsISmtpServer *aServer,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+ nsCString popHost;
+ nsCString popUser;
+ nsCOMPtr <nsIURI> urlToRun;
+
+ nsresult rv = NS_MsgBuildSmtpUrl(nullptr, aServer,
+ nullptr, nullptr, aUrlListener, nullptr,
+ nullptr , getter_AddRefs(urlToRun), false);
+ if (NS_SUCCEEDED(rv) && urlToRun)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> url(do_QueryInterface(urlToRun, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ url->SetMsgWindow(aMsgWindow);
+ rv = NS_MsgLoadSmtpUrl(urlToRun, nullptr, nullptr /* aRequest */);
+ if (aURL)
+ urlToRun.forget(aURL);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsSmtpService::GetScheme(nsACString &aScheme)
+{
+ aScheme = "mailto";
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSmtpService::GetDefaultPort(int32_t *aDefaultPort)
+{
+ nsresult rv = NS_OK;
+ if (aDefaultPort)
+ *aDefaultPort = nsISmtpUrl::DEFAULT_SMTP_PORT;
+ else
+ rv = NS_ERROR_NULL_POINTER;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSmtpService::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // allow smtp to run on any port
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSmtpService::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_NORELATIVE | ALLOWS_PROXY | URI_LOADABLE_BY_ANYONE |
+ URI_NON_PERSISTABLE | URI_DOES_NOT_RETURN_DATA |
+ URI_FORBIDS_COOKIE_ACCESS;
+ return NS_OK;
+}
+
+// the smtp service is also the protocol handler for mailto urls....
+
+NS_IMETHODIMP nsSmtpService::NewURI(const nsACString &aSpec,
+ const char *aOriginCharset,
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ // get a new smtp url
+ nsresult rv;
+ nsCOMPtr<nsIURI> mailtoUrl = do_CreateInstance(kCMailtoUrlCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString utf8Spec;
+ if (aOriginCharset)
+ {
+ nsCOMPtr<nsIUTF8ConverterService>
+ utf8Converter(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ rv = utf8Converter->ConvertURISpecToUTF8(aSpec, aOriginCharset, utf8Spec);
+ }
+
+ // utf8Spec is filled up only when aOriginCharset is specified and
+ // the conversion is successful. Otherwise, fall back to aSpec.
+ if (aOriginCharset && NS_SUCCEEDED(rv))
+ rv = mailtoUrl->SetSpec(utf8Spec);
+ else
+ rv = mailtoUrl->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mailtoUrl.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSmtpService::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+NS_IMETHODIMP nsSmtpService::NewChannel2(nsIURI *aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel **_retval)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ // create an empty pipe for use with the input stream channel.
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+ nsresult rv = pipe->Init(false, false, 0, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // These always succeed because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(pipeIn)));
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(pipeOut)));
+
+ pipeOut->Close();
+
+ if (aLoadInfo) {
+ return NS_NewInputStreamChannelInternal(_retval,
+ aURI,
+ pipeIn,
+ NS_LITERAL_CSTRING("application/x-mailto"),
+ EmptyCString(),
+ aLoadInfo);
+ }
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed.");
+ if (NS_FAILED(rv))
+ return rv;
+
+ return NS_NewInputStreamChannel(_retval,
+ aURI,
+ pipeIn,
+ nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ NS_LITERAL_CSTRING("application/x-mailto"));
+}
+
+NS_IMETHODIMP
+nsSmtpService::GetServers(nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // now read in the servers from prefs if necessary
+ uint32_t serverCount = mSmtpServers.Count();
+
+ if (serverCount <= 0)
+ loadSmtpServers();
+
+ return NS_NewArrayEnumerator(aResult, mSmtpServers);
+}
+
+nsresult
+nsSmtpService::loadSmtpServers()
+{
+ if (mSmtpServersLoaded)
+ return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ nsCOMPtr<nsIPrefBranch> prefRootBranch;
+ prefService->GetBranch(nullptr, getter_AddRefs(prefRootBranch));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCString serverList;
+ rv = prefRootBranch->GetCharPref(PREF_MAIL_SMTPSERVERS, getter_Copies(serverList));
+ serverList.StripWhitespace();
+
+ nsTArray<nsCString> servers;
+ ParseString(serverList, SERVER_DELIMITER, servers);
+
+ /**
+ * Check to see if we need to add pre-configured smtp servers.
+ * Following prefs are important to note in understanding the procedure here.
+ *
+ * 1. pref("mailnews.append_preconfig_smtpservers.version", version number);
+ * This pref registers the current version in the user prefs file. A default value
+ * is stored in mailnews.js file. If a given vendor needs to add more preconfigured
+ * smtp servers, the default version number can be increased. Comparing version
+ * number from user's prefs file and the default one from mailnews.js, we
+ * can add new smtp servers and any other version level changes that need to be done.
+ *
+ * 2. pref("mail.smtpservers.appendsmtpservers", <comma separated servers list>);
+ * This pref contains the list of pre-configured smp servers that ISP/Vendor wants to
+ * to add to the existing servers list.
+ */
+ nsCOMPtr<nsIPrefBranch> defaultsPrefBranch;
+ rv = prefService->GetDefaultBranch(MAIL_ROOT_PREF, getter_AddRefs(defaultsPrefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefService->GetBranch(MAIL_ROOT_PREF, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t appendSmtpServersCurrentVersion = 0;
+ int32_t appendSmtpServersDefaultVersion = 0;
+ rv = prefBranch->GetIntPref(APPEND_SERVERS_VERSION_PREF_NAME, &appendSmtpServersCurrentVersion);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = defaultsPrefBranch->GetIntPref(APPEND_SERVERS_VERSION_PREF_NAME, &appendSmtpServersDefaultVersion);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Update the smtp server list if needed
+ if (appendSmtpServersCurrentVersion <= appendSmtpServersDefaultVersion) {
+ // If there are pre-configured servers, add them to the existing server list
+ nsCString appendServerList;
+ rv = prefRootBranch->GetCharPref(PREF_MAIL_SMTPSERVERS_APPEND_SERVERS, getter_Copies(appendServerList));
+ appendServerList.StripWhitespace();
+ ParseString(appendServerList, SERVER_DELIMITER, servers);
+
+ // Increase the version number so that updates will happen as and when needed
+ prefBranch->SetIntPref(APPEND_SERVERS_VERSION_PREF_NAME, appendSmtpServersCurrentVersion + 1);
+ }
+
+ // use GetServerByKey to check if the key (pref) is already in
+ // in the list. If not it calls createKeyedServer directly.
+
+ for (uint32_t i = 0; i < servers.Length(); i++) {
+ nsCOMPtr<nsISmtpServer> server;
+ GetServerByKey(servers[i].get(), getter_AddRefs(server));
+ }
+
+ saveKeyList();
+
+ mSmtpServersLoaded = true;
+ return NS_OK;
+}
+
+// save the list of keys
+nsresult
+nsSmtpService::saveKeyList()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ return prefBranch->SetCharPref(PREF_MAIL_SMTPSERVERS, mServerKeyList.get());
+}
+
+nsresult
+nsSmtpService::createKeyedServer(const char *key, nsISmtpServer** aResult)
+{
+ if (!key) return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ nsCOMPtr<nsISmtpServer> server = do_CreateInstance(NS_SMTPSERVER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ server->SetKey(key);
+ mSmtpServers.AppendObject(server);
+
+ if (mServerKeyList.IsEmpty())
+ mServerKeyList = key;
+ else {
+ mServerKeyList.Append(',');
+ mServerKeyList += key;
+ }
+
+ if (aResult)
+ server.swap(*aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpService::GetSessionDefaultServer(nsISmtpServer **aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ if (!mSessionDefaultServer)
+ return GetDefaultServer(aServer);
+
+ NS_ADDREF(*aServer = mSessionDefaultServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpService::SetSessionDefaultServer(nsISmtpServer *aServer)
+{
+ mSessionDefaultServer = aServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpService::GetDefaultServer(nsISmtpServer **aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ loadSmtpServers();
+
+ *aServer = nullptr;
+ // always returns NS_OK, just leaving *aServer at nullptr
+ if (!mDefaultSmtpServer) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ // try to get it from the prefs
+ nsCString defaultServerKey;
+ rv = prefBranch->GetCharPref(PREF_MAIL_SMTP_DEFAULTSERVER, getter_Copies(defaultServerKey));
+ if (NS_SUCCEEDED(rv) &&
+ !defaultServerKey.IsEmpty()) {
+
+ nsCOMPtr<nsISmtpServer> server;
+ rv = GetServerByKey(defaultServerKey.get(),
+ getter_AddRefs(mDefaultSmtpServer));
+ } else {
+ // no pref set, so just return the first one, and set the pref
+
+ // Ensure the list of servers is loaded
+ loadSmtpServers();
+
+ // nothing in the array, we had better create a new server
+ // (which will add it to the array & prefs anyway)
+ if (mSmtpServers.Count() == 0)
+ // if there are no smtp servers then don't create one for the default.
+ return NS_OK;
+
+ mDefaultSmtpServer = mSmtpServers[0];
+ NS_ENSURE_TRUE(mDefaultSmtpServer, NS_ERROR_NULL_POINTER);
+
+ // now we have a default server, set the prefs correctly
+ nsCString serverKey;
+ mDefaultSmtpServer->GetKey(getter_Copies(serverKey));
+ if (NS_SUCCEEDED(rv))
+ prefBranch->SetCharPref(PREF_MAIL_SMTP_DEFAULTSERVER, serverKey.get());
+ }
+ }
+
+ // at this point:
+ // * mDefaultSmtpServer has a valid server
+ // * the key has been set in the prefs
+
+ NS_IF_ADDREF(*aServer = mDefaultSmtpServer);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpService::SetDefaultServer(nsISmtpServer *aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ mDefaultSmtpServer = aServer;
+
+ nsCString serverKey;
+ nsresult rv = aServer->GetKey(getter_Copies(serverKey));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+ prefBranch->SetCharPref(PREF_MAIL_SMTP_DEFAULTSERVER, serverKey.get());
+ return NS_OK;
+}
+
+bool
+nsSmtpService::findServerByKey(nsISmtpServer *aServer, void *aData)
+{
+ findServerByKeyEntry *entry = (findServerByKeyEntry*) aData;
+
+ nsCString key;
+ nsresult rv = aServer->GetKey(getter_Copies(key));
+ if (NS_FAILED(rv))
+ return true;
+
+ if (key.Equals(entry->key))
+ {
+ entry->server = aServer;
+ return false;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsSmtpService::CreateServer(nsISmtpServer **aResult)
+{
+ if (!aResult) return NS_ERROR_NULL_POINTER;
+
+ loadSmtpServers();
+ nsresult rv;
+ int32_t i = 0;
+ bool unique = false;
+
+ findServerByKeyEntry entry;
+ nsAutoCString key;
+
+ do {
+ key = "smtp";
+ key.AppendInt(++i);
+ entry.key = key.get();
+ entry.server = nullptr;
+
+ mSmtpServers.EnumerateForwards(findServerByKey, (void *)&entry);
+ if (!entry.server) unique=true;
+
+ } while (!unique);
+
+ rv = createKeyedServer(key.get(), aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return saveKeyList();
+}
+
+
+nsresult
+nsSmtpService::GetServerByKey(const char* aKey, nsISmtpServer **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!aKey || !*aKey)
+ {
+ NS_ASSERTION(false, "bad key");
+ return NS_ERROR_FAILURE;
+ }
+ findServerByKeyEntry entry;
+ entry.key = aKey;
+ entry.server = nullptr;
+ mSmtpServers.EnumerateForwards(findServerByKey, (void *)&entry);
+
+ if (entry.server) {
+ NS_ADDREF(*aResult = entry.server);
+ return NS_OK;
+ }
+
+ // not found in array, I guess we load it
+ return createKeyedServer(aKey, aResult);
+}
+
+NS_IMETHODIMP
+nsSmtpService::DeleteServer(nsISmtpServer *aServer)
+{
+ if (!aServer) return NS_OK;
+
+ int32_t idx = mSmtpServers.IndexOf(aServer);
+ if (idx == -1)
+ return NS_OK;
+
+ nsCString serverKey;
+ aServer->GetKey(getter_Copies(serverKey));
+
+ mSmtpServers.RemoveObjectAt(idx);
+
+ if (mDefaultSmtpServer.get() == aServer)
+ mDefaultSmtpServer = nullptr;
+ if (mSessionDefaultServer.get() == aServer)
+ mSessionDefaultServer = nullptr;
+
+ nsAutoCString newServerList;
+ nsCString tmpStr = mServerKeyList;
+ char *newStr = tmpStr.BeginWriting();
+ char *token = NS_strtok(",", &newStr);
+ while (token) {
+ // only re-add the string if it's not the key
+ if (strcmp(token, serverKey.get()) != 0) {
+ if (newServerList.IsEmpty())
+ newServerList = token;
+ else {
+ newServerList += ',';
+ newServerList += token;
+ }
+ }
+ token = NS_strtok(",", &newStr);
+ }
+
+ // make sure the server clears out it's values....
+ aServer->ClearAllValues();
+
+ mServerKeyList = newServerList;
+ saveKeyList();
+ return NS_OK;
+}
+
+bool
+nsSmtpService::findServerByHostname(nsISmtpServer *aServer, void *aData)
+{
+ findServerByHostnameEntry *entry = (findServerByHostnameEntry*)aData;
+
+ nsCString hostname;
+ nsresult rv = aServer->GetHostname(hostname);
+ if (NS_FAILED(rv))
+ return true;
+
+ nsCString username;
+ rv = aServer->GetUsername(username);
+ if (NS_FAILED(rv))
+ return true;
+
+ bool checkHostname = !entry->hostname.IsEmpty();
+ bool checkUsername = !entry->username.IsEmpty();
+
+ if ((!checkHostname ||
+ (entry->hostname.Equals(hostname, nsCaseInsensitiveCStringComparator()))) &&
+ (!checkUsername ||
+ entry->username.Equals(username, nsCaseInsensitiveCStringComparator())))
+ {
+ entry->server = aServer;
+ return false; // stop when found
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+nsSmtpService::FindServer(const char *aUsername,
+ const char *aHostname, nsISmtpServer ** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ findServerByHostnameEntry entry;
+ entry.server = nullptr;
+ entry.hostname = aHostname;
+ entry.username = aUsername;
+
+ mSmtpServers.EnumerateForwards(findServerByHostname, (void *)&entry);
+
+ // entry.server may be null, but that's ok.
+ // just return null if no server is found
+ NS_IF_ADDREF(*aResult = entry.server);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpService::GetServerByIdentity(nsIMsgIdentity *aSenderIdentity,
+ nsISmtpServer **aSmtpServer)
+{
+ NS_ENSURE_ARG_POINTER(aSmtpServer);
+ nsresult rv = NS_ERROR_FAILURE;
+
+ // First try the identity's preferred server
+ if (aSenderIdentity)
+ {
+ nsCString smtpServerKey;
+ rv = aSenderIdentity->GetSmtpServerKey(smtpServerKey);
+ if (NS_SUCCEEDED(rv) && !(smtpServerKey.IsEmpty()))
+ rv = GetServerByKey(smtpServerKey.get(), aSmtpServer);
+ }
+
+ // Fallback to the default
+ if (NS_FAILED(rv) || !(*aSmtpServer))
+ rv = GetDefaultServer(aSmtpServer);
+ return rv;
+}
diff --git a/mailnews/compose/src/nsSmtpService.h b/mailnews/compose/src/nsSmtpService.h
new file mode 100644
index 000000000..8aca6e7ef
--- /dev/null
+++ b/mailnews/compose/src/nsSmtpService.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSmtpService_h___
+#define nsSmtpService_h___
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsISmtpService.h"
+#include "nsISmtpServer.h"
+#include "nsIProtocolHandler.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// The Smtp Service is an interfaced designed to make building and running mail to urls
+// easier. I'm not sure if this service will go away when the new networking model comes
+// on line (as part of the N2 project). So I reserve the right to change my mind and take
+// this service away =).
+////////////////////////////////////////////////////////////////////////////////////////
+
+class nsSmtpService : public nsISmtpService, public nsIProtocolHandler
+{
+public:
+
+ nsSmtpService();
+
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////
+ // we suppport the nsISmtpService interface
+ ////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSISMTPSERVICE
+
+ //////////////////////////////////////////////////////////////////////////
+ // we suppport the nsIProtocolHandler interface
+ //////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIPROTOCOLHANDLER
+
+protected:
+ nsresult loadSmtpServers();
+
+
+private:
+ virtual ~nsSmtpService();
+ static bool findServerByKey(nsISmtpServer *aServer, void *aData);
+ static bool findServerByHostname(nsISmtpServer *aServer, void *aData);
+
+ nsresult createKeyedServer(const char* key,
+ nsISmtpServer **aResult = nullptr);
+ nsresult saveKeyList();
+
+ nsCOMArray<nsISmtpServer> mSmtpServers;
+ nsCOMPtr<nsISmtpServer> mDefaultSmtpServer;
+ nsCOMPtr<nsISmtpServer> mSessionDefaultServer;
+
+ nsCString mServerKeyList;
+
+ bool mSmtpServersLoaded;
+};
+
+#endif /* nsSmtpService_h___ */
diff --git a/mailnews/compose/src/nsSmtpUrl.cpp b/mailnews/compose/src/nsSmtpUrl.cpp
new file mode 100644
index 000000000..6d130449f
--- /dev/null
+++ b/mailnews/compose/src/nsSmtpUrl.cpp
@@ -0,0 +1,802 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsSmtpUrl.h"
+#include "nsStringGlue.h"
+#include "nsMsgUtils.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgMimeCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCRT.h"
+#include "nsAutoPtr.h"
+
+/////////////////////////////////////////////////////////////////////////////////////
+// mailto url definition
+/////////////////////////////////////////////////////////////////////////////////////
+nsMailtoUrl::nsMailtoUrl()
+{
+ mFormat = nsIMsgCompFormat::Default;
+ m_baseURL = do_CreateInstance(NS_SIMPLEURI_CONTRACTID);
+}
+
+nsMailtoUrl::~nsMailtoUrl()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsMailtoUrl, nsIMailtoUrl, nsIURI)
+
+static void UnescapeAndConvert(nsIMimeConverter *mimeConverter,
+ const nsACString &escaped, nsACString &out)
+{
+ NS_ASSERTION(mimeConverter, "Set mimeConverter before calling!");
+ // If the string is empty, do absolutely nothing.
+ if (escaped.IsEmpty())
+ return;
+
+ MsgUnescapeString(escaped, 0, out);
+ nsAutoCString decodedString;
+ nsresult rv = mimeConverter->DecodeMimeHeaderToUTF8(out, "UTF_8", false,
+ true, decodedString);
+ if (NS_SUCCEEDED(rv) && !decodedString.IsEmpty())
+ out = decodedString;
+}
+
+nsresult nsMailtoUrl::ParseMailtoUrl(char * searchPart)
+{
+ char *rest = searchPart;
+ nsCString escapedInReplyToPart;
+ nsCString escapedToPart;
+ nsCString escapedCcPart;
+ nsCString escapedSubjectPart;
+ nsCString escapedNewsgroupPart;
+ nsCString escapedNewsHostPart;
+ nsCString escapedReferencePart;
+ nsCString escapedBodyPart;
+ nsCString escapedBccPart;
+ nsCString escapedFollowUpToPart;
+ nsCString escapedFromPart;
+ nsCString escapedHtmlPart;
+ nsCString escapedOrganizationPart;
+ nsCString escapedReplyToPart;
+ nsCString escapedPriorityPart;
+
+ // okay, first, free up all of our old search part state.....
+ CleanupMailtoState();
+ // m_toPart has the escaped address from before the query string, copy it
+ // over so we can add on any additional to= addresses and unescape them all.
+ escapedToPart = m_toPart;
+
+ if (rest && *rest == '?')
+ {
+ /* start past the '?' */
+ rest++;
+ }
+
+ if (rest)
+ {
+ char *token = NS_strtok("&", &rest);
+ while (token && *token)
+ {
+ char *value = 0;
+ char *eq = PL_strchr(token, '=');
+ if (eq)
+ {
+ value = eq+1;
+ *eq = 0;
+ }
+
+ nsCString decodedName;
+ MsgUnescapeString(nsDependentCString(token), 0, decodedName);
+
+ if (decodedName.IsEmpty())
+ break;
+
+ switch (NS_ToUpper(decodedName.First()))
+ {
+ /* DO NOT support attachment= in mailto urls. This poses a security fire hole!!!
+ case 'A':
+ if (!PL_strcasecmp (token, "attachment"))
+ m_attachmentPart = value;
+ break;
+ */
+ case 'B':
+ if (decodedName.LowerCaseEqualsLiteral("bcc"))
+ {
+ if (!escapedBccPart.IsEmpty())
+ {
+ escapedBccPart += ", ";
+ escapedBccPart += value;
+ }
+ else
+ escapedBccPart = value;
+ }
+ else if (decodedName.LowerCaseEqualsLiteral("body"))
+ {
+ if (!escapedBodyPart.IsEmpty())
+ {
+ escapedBodyPart +="\n";
+ escapedBodyPart += value;
+ }
+ else
+ escapedBodyPart = value;
+ }
+ break;
+ case 'C':
+ if (decodedName.LowerCaseEqualsLiteral("cc"))
+ {
+ if (!escapedCcPart.IsEmpty())
+ {
+ escapedCcPart += ", ";
+ escapedCcPart += value;
+ }
+ else
+ escapedCcPart = value;
+ }
+ break;
+ case 'F':
+ if (decodedName.LowerCaseEqualsLiteral("followup-to"))
+ escapedFollowUpToPart = value;
+ else if (decodedName.LowerCaseEqualsLiteral("from"))
+ escapedFromPart = value;
+ break;
+ case 'H':
+ if (decodedName.LowerCaseEqualsLiteral("html-part") ||
+ decodedName.LowerCaseEqualsLiteral("html-body"))
+ {
+ // escapedHtmlPart holds the body for both html-part and html-body.
+ escapedHtmlPart = value;
+ mFormat = nsIMsgCompFormat::HTML;
+ }
+ break;
+ case 'I':
+ if (decodedName.LowerCaseEqualsLiteral("in-reply-to"))
+ escapedInReplyToPart = value;
+ break;
+
+ case 'N':
+ if (decodedName.LowerCaseEqualsLiteral("newsgroups"))
+ escapedNewsgroupPart = value;
+ else if (decodedName.LowerCaseEqualsLiteral("newshost"))
+ escapedNewsHostPart = value;
+ break;
+ case 'O':
+ if (decodedName.LowerCaseEqualsLiteral("organization"))
+ escapedOrganizationPart = value;
+ break;
+ case 'R':
+ if (decodedName.LowerCaseEqualsLiteral("references"))
+ escapedReferencePart = value;
+ else if (decodedName.LowerCaseEqualsLiteral("reply-to"))
+ escapedReplyToPart = value;
+ break;
+ case 'S':
+ if(decodedName.LowerCaseEqualsLiteral("subject"))
+ escapedSubjectPart = value;
+ break;
+ case 'P':
+ if (decodedName.LowerCaseEqualsLiteral("priority"))
+ escapedPriorityPart = PL_strdup(value);
+ break;
+ case 'T':
+ if (decodedName.LowerCaseEqualsLiteral("to"))
+ {
+ if (!escapedToPart.IsEmpty())
+ {
+ escapedToPart += ", ";
+ escapedToPart += value;
+ }
+ else
+ escapedToPart = value;
+ }
+ break;
+ default:
+ break;
+ } // end of switch statement...
+
+ if (eq)
+ *eq = '='; /* put it back */
+ token = NS_strtok("&", &rest);
+ } // while we still have part of the url to parse...
+ } // if rest && *rest
+
+ // Get a global converter
+ nsCOMPtr<nsIMimeConverter> mimeConverter =
+ do_GetService(NS_MIME_CONVERTER_CONTRACTID);
+
+ // Now unescape everything, and mime-decode the things that can be encoded.
+ UnescapeAndConvert(mimeConverter, escapedToPart, m_toPart);
+ UnescapeAndConvert(mimeConverter, escapedCcPart, m_ccPart);
+ UnescapeAndConvert(mimeConverter, escapedBccPart, m_bccPart);
+ UnescapeAndConvert(mimeConverter, escapedSubjectPart, m_subjectPart);
+ UnescapeAndConvert(mimeConverter, escapedNewsgroupPart, m_newsgroupPart);
+ UnescapeAndConvert(mimeConverter, escapedReferencePart, m_referencePart);
+ if (!escapedBodyPart.IsEmpty())
+ MsgUnescapeString(escapedBodyPart, 0, m_bodyPart);
+ if (!escapedHtmlPart.IsEmpty())
+ MsgUnescapeString(escapedHtmlPart, 0, m_htmlPart);
+ UnescapeAndConvert(mimeConverter, escapedNewsHostPart, m_newsHostPart);
+ UnescapeAndConvert(mimeConverter, escapedFollowUpToPart, m_followUpToPart);
+ UnescapeAndConvert(mimeConverter, escapedFromPart, m_fromPart);
+ UnescapeAndConvert(mimeConverter, escapedOrganizationPart, m_organizationPart);
+ UnescapeAndConvert(mimeConverter, escapedReplyToPart, m_replyToPart);
+ UnescapeAndConvert(mimeConverter, escapedPriorityPart, m_priorityPart);
+
+ nsCString inReplyToPart; // Not a member like the others...
+ UnescapeAndConvert(mimeConverter, escapedInReplyToPart, inReplyToPart);
+
+ if (!inReplyToPart.IsEmpty())
+ {
+ // Ensure that References and In-Reply-To are consistent... The last
+ // reference will be used as In-Reply-To header.
+ if (m_referencePart.IsEmpty())
+ {
+ // If References is not set, set it to be the In-Reply-To.
+ m_referencePart = inReplyToPart;
+ }
+ else
+ {
+ // References is set. Add the In-Reply-To as last header unless it's
+ // set as last reference already.
+ int32_t lastRefStart = m_referencePart.RFindChar('<');
+ nsAutoCString lastReference;
+ if (lastRefStart != -1)
+ lastReference = StringTail(m_referencePart, lastRefStart);
+ else
+ lastReference = m_referencePart;
+
+ if (lastReference != inReplyToPart)
+ {
+ m_referencePart += " ";
+ m_referencePart += inReplyToPart;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetSpec(const nsACString &aSpec)
+{
+ nsresult rv = m_baseURL->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+nsresult nsMailtoUrl::CleanupMailtoState()
+{
+ m_ccPart = "";
+ m_subjectPart = "";
+ m_newsgroupPart = "";
+ m_newsHostPart = "";
+ m_referencePart = "";
+ m_bodyPart = "";
+ m_bccPart = "";
+ m_followUpToPart = "";
+ m_fromPart = "";
+ m_htmlPart = "";
+ m_organizationPart = "";
+ m_replyToPart = "";
+ m_priorityPart = "";
+ return NS_OK;
+}
+
+nsresult nsMailtoUrl::ParseUrl()
+{
+ // we can get the path from the simple url.....
+ nsCString escapedPath;
+ m_baseURL->GetPath(escapedPath);
+
+ int32_t startOfSearchPart = escapedPath.FindChar('?');
+ if (startOfSearchPart >= 0)
+ {
+ // now parse out the search field...
+ nsAutoCString searchPart(Substring(escapedPath, startOfSearchPart));
+
+ if (!searchPart.IsEmpty())
+ {
+ // now we need to strip off the search part from the
+ // to part....
+ escapedPath.SetLength(startOfSearchPart);
+ MsgUnescapeString(escapedPath, 0, m_toPart);
+ ParseMailtoUrl(searchPart.BeginWriting());
+ }
+ }
+ else if (!escapedPath.IsEmpty())
+ {
+ MsgUnescapeString(escapedPath, 0, m_toPart);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetMessageContents(nsACString &aToPart, nsACString &aCcPart,
+ nsACString &aBccPart, nsACString &aSubjectPart,
+ nsACString &aBodyPart, nsACString &aHtmlPart,
+ nsACString &aReferencePart,
+ nsACString &aNewsgroupPart,
+ MSG_ComposeFormat *aFormat)
+{
+ NS_ENSURE_ARG_POINTER(aFormat);
+
+ aToPart = m_toPart;
+ aCcPart = m_ccPart;
+ aBccPart = m_bccPart;
+ aSubjectPart = m_subjectPart;
+ aBodyPart = m_bodyPart;
+ aHtmlPart = m_htmlPart;
+ aReferencePart = m_referencePart;
+ aNewsgroupPart = m_newsgroupPart;
+ *aFormat = mFormat;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetFromPart(nsACString &aResult)
+{
+ aResult = m_fromPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetFollowUpToPart(nsACString &aResult)
+{
+ aResult = m_followUpToPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetOrganizationPart(nsACString &aResult)
+{
+ aResult = m_organizationPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetReplyToPart(nsACString &aResult)
+{
+ aResult = m_replyToPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetPriorityPart(nsACString &aResult)
+{
+ aResult = m_priorityPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetNewsHostPart(nsACString &aResult)
+{
+ aResult = m_newsHostPart;
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Begin nsIURI support
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMailtoUrl::GetSpec(nsACString &aSpec)
+{
+ return m_baseURL->GetSpec(aSpec);
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetPrePath(nsACString &aPrePath)
+{
+ return m_baseURL->GetPrePath(aPrePath);
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetScheme(nsACString &aScheme)
+{
+ return m_baseURL->GetScheme(aScheme);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetScheme(const nsACString &aScheme)
+{
+ m_baseURL->SetScheme(aScheme);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetUserPass(nsACString &aUserPass)
+{
+ return m_baseURL->GetUserPass(aUserPass);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetUserPass(const nsACString &aUserPass)
+{
+ m_baseURL->SetUserPass(aUserPass);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetUsername(nsACString &aUsername)
+{
+ return m_baseURL->GetUsername(aUsername);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetUsername(const nsACString &aUsername)
+{
+ m_baseURL->SetUsername(aUsername);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetPassword(nsACString &aPassword)
+{
+ return m_baseURL->GetPassword(aPassword);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetPassword(const nsACString &aPassword)
+{
+ m_baseURL->SetPassword(aPassword);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetHostPort(nsACString &aHostPort)
+{
+ return m_baseURL->GetHost(aHostPort);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetHostPort(const nsACString &aHostPort)
+{
+ m_baseURL->SetHost(aHostPort);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetHostAndPort(const nsACString &aHostPort)
+{
+ m_baseURL->SetHostAndPort(aHostPort);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetHost(nsACString &aHost)
+{
+ return m_baseURL->GetHost(aHost);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetHost(const nsACString &aHost)
+{
+ m_baseURL->SetHost(aHost);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetPort(int32_t *aPort)
+{
+ return m_baseURL->GetPort(aPort);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetPort(int32_t aPort)
+{
+ m_baseURL->SetPort(aPort);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetPath(nsACString &aPath)
+{
+ return m_baseURL->GetPath(aPath);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetPath(const nsACString &aPath)
+{
+ m_baseURL->SetPath(aPath);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetAsciiHost(nsACString &aHostA)
+{
+ return m_baseURL->GetAsciiHost(aHostA);
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetAsciiHostPort(nsACString &aHostPortA)
+{
+ return m_baseURL->GetAsciiHostPort(aHostPortA);
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetAsciiSpec(nsACString &aSpecA)
+{
+ return m_baseURL->GetAsciiSpec(aSpecA);
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetOriginCharset(nsACString &aOriginCharset)
+{
+ return m_baseURL->GetOriginCharset(aOriginCharset);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SchemeIs(const char *aScheme, bool *_retval)
+{
+ return m_baseURL->SchemeIs(aScheme, _retval);
+}
+
+NS_IMETHODIMP nsMailtoUrl::Equals(nsIURI *other, bool *_retval)
+{
+ // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its
+ // Equals method. The other nsMailtoUrl will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other)
+ return other->Equals(m_baseURL, _retval);
+
+ return m_baseURL->Equals(other, _retval);
+}
+
+nsresult
+nsMailtoUrl::CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef, nsIURI** _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ RefPtr<nsMailtoUrl> clone = new nsMailtoUrl();
+
+ NS_ENSURE_TRUE(clone, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv;
+ if (aRefHandlingMode == eHonorRef) {
+ rv = m_baseURL->Clone(getter_AddRefs(clone->m_baseURL));
+ } else if (aRefHandlingMode == eReplaceRef) {
+ rv = m_baseURL->CloneWithNewRef(newRef, getter_AddRefs(clone->m_baseURL));
+ } else {
+ rv = m_baseURL->CloneIgnoringRef(getter_AddRefs(clone->m_baseURL));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ clone->ParseUrl();
+ clone.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::Clone(nsIURI **_retval)
+{
+ return CloneInternal(eHonorRef, EmptyCString(), _retval);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::CloneIgnoringRef(nsIURI** _retval)
+{
+ return CloneInternal(eIgnoreRef, EmptyCString(), _retval);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::CloneWithNewRef(const nsACString& newRef, nsIURI** _retval)
+{
+ return CloneInternal(eReplaceRef, newRef, _retval);
+}
+
+NS_IMETHODIMP nsMailtoUrl::Resolve(const nsACString &relativePath, nsACString &result)
+{
+ return m_baseURL->Resolve(relativePath, result);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SetRef(const nsACString &aRef)
+{
+ return m_baseURL->SetRef(aRef);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetRef(nsACString &result)
+{
+ return m_baseURL->GetRef(result);
+}
+
+NS_IMETHODIMP nsMailtoUrl::EqualsExceptRef(nsIURI *other, bool *result)
+{
+ // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its
+ // Equals method. The other nsMailtoUrl will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other)
+ return other->EqualsExceptRef(m_baseURL, result);
+
+ return m_baseURL->EqualsExceptRef(other, result);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetSpecIgnoringRef(nsACString &result)
+{
+ return m_baseURL->GetSpecIgnoringRef(result);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetHasRef(bool *result)
+{
+ return m_baseURL->GetHasRef(result);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetFilePath(nsACString &aFilePath)
+{
+ return m_baseURL->GetFilePath(aFilePath);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::SetFilePath(const nsACString &aFilePath)
+{
+ return m_baseURL->SetFilePath(aFilePath);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetQuery(nsACString &aQuery)
+{
+ return m_baseURL->GetQuery(aQuery);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::SetQuery(const nsACString &aQuery)
+{
+ return m_baseURL->SetQuery(aQuery);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// smtp url definition
+/////////////////////////////////////////////////////////////////////////////////////
+
+nsSmtpUrl::nsSmtpUrl() : nsMsgMailNewsUrl()
+{
+ // nsISmtpUrl specific state...
+
+ m_isPostMessage = true;
+ m_requestDSN = false;
+ m_verifyLogon = false;
+}
+
+nsSmtpUrl::~nsSmtpUrl()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsSmtpUrl, nsMsgMailNewsUrl, nsISmtpUrl)
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsISmtpUrl specific support
+
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsSmtpUrl::SetRecipients(const char * aRecipientsList)
+{
+ NS_ENSURE_ARG(aRecipientsList);
+ MsgUnescapeString(nsDependentCString(aRecipientsList), 0, m_toPart);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetRecipients(char ** aRecipientsList)
+{
+ NS_ENSURE_ARG_POINTER(aRecipientsList);
+ if (aRecipientsList)
+ *aRecipientsList = ToNewCString(m_toPart);
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsSmtpUrl, PostMessage, bool, m_isPostMessage)
+
+NS_IMPL_GETSET(nsSmtpUrl, VerifyLogon, bool, m_verifyLogon)
+
+// the message can be stored in a file....allow accessors for getting and setting
+// the file name to post...
+NS_IMETHODIMP nsSmtpUrl::SetPostMessageFile(nsIFile * aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ m_fileName = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSmtpUrl::GetPostMessageFile(nsIFile ** aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ if (m_fileName)
+ {
+ // Clone the file so nsLocalFile stat caching doesn't make the caller get
+ // the wrong file size.
+ m_fileName->Clone(aFile);
+ return *aFile ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMPL_GETSET(nsSmtpUrl, RequestDSN, bool, m_requestDSN)
+
+NS_IMETHODIMP
+nsSmtpUrl::SetDsnEnvid(const nsACString &aDsnEnvid)
+{
+ m_dsnEnvid = aDsnEnvid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetDsnEnvid(nsACString &aDsnEnvid)
+{
+ aDsnEnvid = m_dsnEnvid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetSenderIdentity(nsIMsgIdentity **aSenderIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aSenderIdentity);
+ *aSenderIdentity = m_senderIdentity;
+ NS_ADDREF(*aSenderIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetSenderIdentity(nsIMsgIdentity *aSenderIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aSenderIdentity);
+ m_senderIdentity = aSenderIdentity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetPrompt(nsIPrompt *aNetPrompt)
+{
+ NS_ENSURE_ARG_POINTER(aNetPrompt);
+ m_netPrompt = aNetPrompt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetPrompt(nsIPrompt **aNetPrompt)
+{
+ NS_ENSURE_ARG_POINTER(aNetPrompt);
+ NS_ENSURE_TRUE(m_netPrompt, NS_ERROR_NULL_POINTER);
+ *aNetPrompt = m_netPrompt;
+ NS_ADDREF(*aNetPrompt);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetAuthPrompt(nsIAuthPrompt *aNetAuthPrompt)
+{
+ NS_ENSURE_ARG_POINTER(aNetAuthPrompt);
+ m_netAuthPrompt = aNetAuthPrompt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetAuthPrompt(nsIAuthPrompt **aNetAuthPrompt)
+{
+ NS_ENSURE_ARG_POINTER(aNetAuthPrompt);
+ NS_ENSURE_TRUE(m_netAuthPrompt, NS_ERROR_NULL_POINTER);
+ *aNetAuthPrompt = m_netAuthPrompt;
+ NS_ADDREF(*aNetAuthPrompt);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
+{
+ NS_ENSURE_ARG_POINTER(aCallbacks);
+ m_callbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks)
+{
+ NS_ENSURE_ARG_POINTER(aCallbacks);
+ NS_ENSURE_TRUE(m_callbacks, NS_ERROR_NULL_POINTER);
+ *aCallbacks = m_callbacks;
+ NS_ADDREF(*aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetSmtpServer(nsISmtpServer * aSmtpServer)
+{
+ NS_ENSURE_ARG_POINTER(aSmtpServer);
+ m_smtpServer = aSmtpServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetSmtpServer(nsISmtpServer ** aSmtpServer)
+{
+ NS_ENSURE_ARG_POINTER(aSmtpServer);
+ NS_ENSURE_TRUE(m_smtpServer, NS_ERROR_NULL_POINTER);
+ *aSmtpServer = m_smtpServer;
+ NS_ADDREF(*aSmtpServer);
+ return NS_OK;
+}
diff --git a/mailnews/compose/src/nsSmtpUrl.h b/mailnews/compose/src/nsSmtpUrl.h
new file mode 100644
index 000000000..d603fa365
--- /dev/null
+++ b/mailnews/compose/src/nsSmtpUrl.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSmtpUrl_h__
+#define nsSmtpUrl_h__
+
+#include "nsISmtpUrl.h"
+#include "nsIURI.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsIMsgIdentity.h"
+#include "nsCOMPtr.h"
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsISmtpServer.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+
+class nsMailtoUrl : public nsIMailtoUrl, public nsIURI
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIMAILTOURL
+
+ nsMailtoUrl();
+
+protected:
+ enum RefHandlingEnum {
+ eIgnoreRef,
+ eHonorRef,
+ eReplaceRef
+ };
+ virtual ~nsMailtoUrl();
+ nsresult ParseUrl();
+ nsresult CleanupMailtoState();
+ nsresult ParseMailtoUrl(char * searchPart);
+ nsresult CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef, nsIURI** _retval);
+
+ nsCOMPtr<nsIURI> m_baseURL;
+
+ // data retrieved from parsing the url: (Note the url could be a post from file or it could be in the url)
+ nsCString m_toPart;
+ nsCString m_ccPart;
+ nsCString m_subjectPart;
+ nsCString m_newsgroupPart;
+ nsCString m_newsHostPart;
+ nsCString m_referencePart;
+ nsCString m_bodyPart;
+ nsCString m_bccPart;
+ nsCString m_followUpToPart;
+ nsCString m_fromPart;
+ nsCString m_htmlPart;
+ nsCString m_organizationPart;
+ nsCString m_replyToPart;
+ nsCString m_priorityPart;
+
+ MSG_ComposeFormat mFormat;
+};
+
+class nsSmtpUrl : public nsISmtpUrl, public nsMsgMailNewsUrl
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // From nsISmtpUrl
+ NS_DECL_NSISMTPURL
+
+ // nsSmtpUrl
+ nsSmtpUrl();
+
+protected:
+ virtual ~nsSmtpUrl();
+
+ // data retrieved from parsing the url: (Note the url could be a post from
+ // file or it could be in the url)
+ nsCString m_toPart;
+
+ bool m_isPostMessage;
+ bool m_requestDSN;
+ nsCString m_dsnEnvid;
+ bool m_verifyLogon;
+
+ // Smtp specific event sinks
+ nsCOMPtr<nsIFile> m_fileName;
+ nsCOMPtr<nsIMsgIdentity> m_senderIdentity;
+ nsCOMPtr<nsIPrompt> m_netPrompt;
+ nsCOMPtr<nsIAuthPrompt> m_netAuthPrompt;
+ nsCOMPtr<nsIInterfaceRequestor> m_callbacks;
+ nsCOMPtr<nsISmtpServer> m_smtpServer;
+
+ // it is possible to encode the message to parse in the form of a url.
+ // This function is used to decompose the search and path part into the bare
+ // message components (to, fcc, bcc, etc.)
+ nsresult ParseMessageToPost(char * searchPart);
+};
+
+#endif // nsSmtpUrl_h__
diff --git a/mailnews/compose/src/nsURLFetcher.cpp b/mailnews/compose/src/nsURLFetcher.cpp
new file mode 100644
index 000000000..ca2dcd7c6
--- /dev/null
+++ b/mailnews/compose/src/nsURLFetcher.cpp
@@ -0,0 +1,530 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsURLFetcher.h"
+
+#include "msgCore.h" // for pre-compiled headers
+#include "nsCOMPtr.h"
+#include "nsIScriptSecurityManager.h"
+#include <stdio.h>
+#include "nscore.h"
+#include "nsIFactory.h"
+#include "nsISupports.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "nsIComponentManager.h"
+#include "nsStringGlue.h"
+#include "nsIIOService.h"
+#include "nsIChannel.h"
+#include "nsNetUtil.h"
+#include "nsMimeTypes.h"
+#include "nsIHttpChannel.h"
+#include "nsIWebProgress.h"
+#include "nsMsgAttachmentHandler.h"
+#include "nsMsgSend.h"
+#include "nsISeekableStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsIMsgProgress.h"
+#include "nsMsgUtils.h"
+
+NS_IMPL_ISUPPORTS(nsURLFetcher,
+ nsIURLFetcher,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIURIContentListener,
+ nsIInterfaceRequestor,
+ nsIWebProgressListener,
+ nsISupportsWeakReference)
+
+
+/*
+ * Inherited methods for nsMimeConverter
+ */
+nsURLFetcher::nsURLFetcher()
+{
+ // Init member variables...
+ mTotalWritten = 0;
+ mBuffer = nullptr;
+ mBufferSize = 0;
+ mStillRunning = true;
+ mCallback = nullptr;
+ mOnStopRequestProcessed = false;
+ mIsFile=false;
+ nsURLFetcherStreamConsumer *consumer = new nsURLFetcherStreamConsumer(this);
+ mConverter = do_QueryInterface(consumer);
+}
+
+nsURLFetcher::~nsURLFetcher()
+{
+ mStillRunning = false;
+
+ PR_FREEIF(mBuffer);
+ // Remove the DocShell as a listener of the old WebProgress...
+ if (mLoadCookie)
+ {
+ nsCOMPtr<nsIWebProgress> webProgress(do_QueryInterface(mLoadCookie));
+
+ if (webProgress)
+ webProgress->RemoveProgressListener(this);
+ }
+}
+
+NS_IMETHODIMP nsURLFetcher::GetInterface(const nsIID & aIID, void * *aInstancePtr)
+{
+ NS_ENSURE_ARG_POINTER(aInstancePtr);
+ return QueryInterface(aIID, aInstancePtr);
+}
+
+// nsIURIContentListener support
+NS_IMETHODIMP
+nsURLFetcher::OnStartURIOpen(nsIURI* aURI, bool* aAbortOpen)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::IsPreferred(const char * aContentType,
+ char ** aDesiredContentType,
+ bool * aCanHandleContent)
+
+{
+ return CanHandleContent(aContentType, true, aDesiredContentType,
+ aCanHandleContent);
+}
+
+NS_IMETHODIMP
+nsURLFetcher::CanHandleContent(const char * aContentType,
+ bool aIsContentPreferred,
+ char ** aDesiredContentType,
+ bool * aCanHandleContent)
+
+{
+ if (!mIsFile && PL_strcasecmp(aContentType, MESSAGE_RFC822) == 0)
+ *aDesiredContentType = strdup("text/html");
+
+ // since we explicilty loaded the url, we always want to handle it!
+ *aCanHandleContent = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::DoContent(const nsACString& aContentType,
+ bool aIsContentPreferred,
+ nsIRequest *request,
+ nsIStreamListener ** aContentHandler,
+ bool * aAbortProcess)
+{
+ nsresult rv = NS_OK;
+
+ if (aAbortProcess)
+ *aAbortProcess = false;
+ QueryInterface(NS_GET_IID(nsIStreamListener), (void **) aContentHandler);
+
+ /*
+ Check the content-type to see if we need to insert a converter
+ */
+ if (PL_strcasecmp(PromiseFlatCString(aContentType).get(), UNKNOWN_CONTENT_TYPE) == 0 ||
+ PL_strcasecmp(PromiseFlatCString(aContentType).get(), MULTIPART_MIXED_REPLACE) == 0 ||
+ PL_strcasecmp(PromiseFlatCString(aContentType).get(), MULTIPART_MIXED) == 0 ||
+ PL_strcasecmp(PromiseFlatCString(aContentType).get(), MULTIPART_BYTERANGES) == 0)
+ {
+ rv = InsertConverter(PromiseFlatCString(aContentType).get());
+ if (NS_SUCCEEDED(rv))
+ mConverterContentType = PromiseFlatCString(aContentType).get();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::GetParentContentListener(nsIURIContentListener** aParent)
+{
+ *aParent = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::SetParentContentListener(nsIURIContentListener* aParent)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::GetLoadCookie(nsISupports ** aLoadCookie)
+{
+ *aLoadCookie = mLoadCookie;
+ NS_IF_ADDREF(*aLoadCookie);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::SetLoadCookie(nsISupports * aLoadCookie)
+{
+ // Remove the DocShell as a listener of the old WebProgress...
+ if (mLoadCookie)
+ {
+ nsCOMPtr<nsIWebProgress> webProgress(do_QueryInterface(mLoadCookie));
+
+ if (webProgress)
+ webProgress->RemoveProgressListener(this);
+ }
+
+ mLoadCookie = aLoadCookie;
+
+ // Add the DocShell as a listener to the new WebProgress...
+ if (mLoadCookie)
+ {
+ nsCOMPtr<nsIWebProgress> webProgress(do_QueryInterface(mLoadCookie));
+
+ if (webProgress)
+ webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_ALL);
+ }
+ return NS_OK;
+
+}
+
+nsresult
+nsURLFetcher::StillRunning(bool *running)
+{
+ *running = mStillRunning;
+ return NS_OK;
+}
+
+
+// Methods for nsIStreamListener...
+nsresult
+nsURLFetcher::OnDataAvailable(nsIRequest *request, nsISupports * ctxt, nsIInputStream *aIStream,
+ uint64_t sourceOffset, uint32_t aLength)
+{
+ /* let our converter or consumer process the data */
+ if (!mConverter)
+ return NS_ERROR_FAILURE;
+
+ return mConverter->OnDataAvailable(request, ctxt, aIStream, sourceOffset, aLength);
+}
+
+
+// Methods for nsIStreamObserver
+nsresult
+nsURLFetcher::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ /* check if the user has canceld the operation */
+ if (mTagData)
+ {
+ nsCOMPtr<nsIMsgSend> sendPtr;
+ mTagData->GetMimeDeliveryState(getter_AddRefs(sendPtr));
+ if (sendPtr)
+ {
+ nsCOMPtr<nsIMsgProgress> progress;
+ sendPtr->GetProgress(getter_AddRefs(progress));
+ if (progress)
+ {
+ bool cancel = false;
+ progress->GetProcessCanceledByUser(&cancel);
+ if (cancel)
+ return request->Cancel(NS_ERROR_ABORT);
+ }
+ }
+ mTagData->mRequest = request;
+ }
+
+ /* call our converter or consumer */
+ if (mConverter)
+ return mConverter->OnStartRequest(request, ctxt);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::OnStopRequest(nsIRequest *request, nsISupports * ctxt, nsresult aStatus)
+{
+ // it's possible we could get in here from the channel calling us with an OnStopRequest and from our
+ // onStatusChange method (in the case of an error). So we should protect against this to make sure we
+ // don't process the on stop request twice...
+
+ if (mOnStopRequestProcessed)
+ return NS_OK;
+
+ mOnStopRequestProcessed = true;
+
+ /* first, call our converter or consumer */
+ if (mConverter)
+ (void) mConverter->OnStopRequest(request, ctxt, aStatus);
+
+ if (mTagData)
+ mTagData->mRequest = nullptr;
+
+ //
+ // Now complete the stream!
+ //
+ mStillRunning = false;
+
+ // time to close the output stream...
+ if (mOutStream)
+ {
+ mOutStream->Close();
+ mOutStream = nullptr;
+
+ /* In case of multipart/x-mixed-replace, we need to truncate the file to the current part size */
+ if (MsgLowerCaseEqualsLiteral(mConverterContentType, MULTIPART_MIXED_REPLACE))
+ {
+ mLocalFile->SetFileSize(mTotalWritten);
+ }
+ }
+
+ // Now if there is a callback, we need to call it...
+ if (mCallback)
+ mCallback (aStatus, mContentType, mCharset, mTotalWritten, nullptr, mTagData);
+
+ // Time to return...
+ return NS_OK;
+}
+
+nsresult
+nsURLFetcher::Initialize(nsIFile *localFile,
+ nsIOutputStream *outputStream,
+ nsAttachSaveCompletionCallback cb,
+ nsMsgAttachmentHandler *tagData)
+{
+ if (!outputStream || !localFile)
+ return NS_ERROR_INVALID_ARG;
+
+ mOutStream = outputStream;
+ mLocalFile = localFile;
+ mCallback = cb; //JFD: Please, no more callback, use a listener...
+ mTagData = tagData;
+ return NS_OK;
+}
+
+nsresult
+nsURLFetcher::FireURLRequest(nsIURI *aURL, nsIFile *localFile, nsIOutputStream *outputStream,
+ nsAttachSaveCompletionCallback cb, nsMsgAttachmentHandler *tagData)
+{
+ nsresult rv;
+
+ rv = Initialize(localFile, outputStream, cb, tagData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ //check to see if aURL is a local file or not
+ aURL->SchemeIs("file", &mIsFile);
+
+ // we're about to fire a new url request so make sure the on stop request flag is cleared...
+ mOnStopRequestProcessed = false;
+
+ // let's try uri dispatching...
+ nsCOMPtr<nsIURILoader> pURILoader (do_GetService(NS_URI_LOADER_CONTRACTID));
+ NS_ENSURE_TRUE(pURILoader, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIScriptSecurityManager>
+ secMan(do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secMan->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ aURL,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aLoadGroup
+ this); // aCallbacks
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return pURILoader->OpenURI(channel, false, this);
+}
+
+nsresult
+nsURLFetcher::InsertConverter(const char * aContentType)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamConverterService> convServ(do_GetService("@mozilla.org/streamConverters;1", &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIStreamListener> toListener(mConverter);
+ nsCOMPtr<nsIStreamListener> fromListener;
+
+ rv = convServ->AsyncConvertData(aContentType,
+ "*/*",
+ toListener,
+ nullptr,
+ getter_AddRefs(fromListener));
+ if (NS_SUCCEEDED(rv))
+ mConverter = fromListener;
+ }
+
+ return rv;
+}
+
+// web progress listener implementation
+
+NS_IMETHODIMP
+nsURLFetcher::OnProgressChange(nsIWebProgress *aProgress, nsIRequest *aRequest,
+ int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress, int32_t aMaxTotalProgress)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::OnStateChange(nsIWebProgress *aProgress, nsIRequest *aRequest,
+ uint32_t aStateFlags, nsresult aStatus)
+{
+ // all we care about is the case where an error occurred (as in we were unable to locate the
+ // the url....
+
+ if (NS_FAILED(aStatus))
+ OnStopRequest(aRequest, nullptr, aStatus);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI *aURI,
+ uint32_t aFlags)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsURLFetcher::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+
+/**
+ * Stream consumer used for handling special content type like multipart/x-mixed-replace
+ */
+
+NS_IMPL_ISUPPORTS(nsURLFetcherStreamConsumer, nsIStreamListener, nsIRequestObserver)
+
+nsURLFetcherStreamConsumer::nsURLFetcherStreamConsumer(nsURLFetcher* urlFetcher) :
+ mURLFetcher(urlFetcher)
+{
+}
+
+nsURLFetcherStreamConsumer::~nsURLFetcherStreamConsumer()
+{
+}
+
+/** nsIRequestObserver methods **/
+
+/* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
+NS_IMETHODIMP nsURLFetcherStreamConsumer::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
+{
+ if (!mURLFetcher || !mURLFetcher->mOutStream)
+ return NS_ERROR_FAILURE;
+
+ /* In case of multipart/x-mixed-replace, we need to erase the output file content */
+ if (MsgLowerCaseEqualsLiteral(mURLFetcher->mConverterContentType, MULTIPART_MIXED_REPLACE))
+ {
+ nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(mURLFetcher->mOutStream);
+ if (seekStream)
+ seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ mURLFetcher->mTotalWritten = 0;
+ }
+
+ return NS_OK;
+}
+
+/* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
+NS_IMETHODIMP nsURLFetcherStreamConsumer::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
+{
+ if (!mURLFetcher)
+ return NS_ERROR_FAILURE;
+
+ // Check the content type!
+ nsAutoCString contentType;
+ nsAutoCString charset;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if(!channel) return NS_ERROR_FAILURE;
+
+ if (NS_SUCCEEDED(channel->GetContentType(contentType)) &&
+ !contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE))
+ {
+ nsAutoCString uriSpec;
+ nsCOMPtr <nsIURI> channelURI;
+ channel->GetURI(getter_AddRefs(channelURI));
+ nsresult rv = channelURI->GetSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (uriSpec.Find("&realtype=message/rfc822") >= 0)
+ mURLFetcher->mContentType = MESSAGE_RFC822;
+ else
+ mURLFetcher->mContentType = contentType;
+ }
+
+ if (NS_SUCCEEDED(channel->GetContentCharset(charset)) && !charset.IsEmpty())
+ {
+ mURLFetcher->mCharset = charset;
+ }
+
+ return NS_OK;
+}
+
+/** nsIStreamListener methods **/
+
+/* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */
+NS_IMETHODIMP nsURLFetcherStreamConsumer::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count)
+{
+ uint32_t readLen = count;
+ uint32_t wroteIt;
+
+ if (!mURLFetcher)
+ return NS_ERROR_FAILURE;
+
+ if (!mURLFetcher->mOutStream)
+ return NS_ERROR_INVALID_ARG;
+
+ if (mURLFetcher->mBufferSize < count)
+ {
+ PR_FREEIF(mURLFetcher->mBuffer);
+
+ if (count > 0x1000)
+ mURLFetcher->mBufferSize = count;
+ else
+ mURLFetcher->mBufferSize = 0x1000;
+
+ mURLFetcher->mBuffer = (char *)PR_Malloc(mURLFetcher->mBufferSize);
+ if (!mURLFetcher->mBuffer)
+ return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */
+ }
+
+ // read the data from the input stram...
+ nsresult rv = inStr->Read(mURLFetcher->mBuffer, count, &readLen);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // write to the output file...
+ mURLFetcher->mOutStream->Write(mURLFetcher->mBuffer, readLen, &wroteIt);
+
+ if (wroteIt != readLen)
+ return NS_ERROR_FAILURE;
+ else
+ {
+ mURLFetcher->mTotalWritten += wroteIt;
+ return NS_OK;
+ }
+}
diff --git a/mailnews/compose/src/nsURLFetcher.h b/mailnews/compose/src/nsURLFetcher.h
new file mode 100644
index 000000000..b23a66385
--- /dev/null
+++ b/mailnews/compose/src/nsURLFetcher.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsURLFetcher_h_
+#define nsURLFetcher_h_
+
+#include "nsIURLFetcher.h"
+
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsIInputStream.h"
+#include "nsIStreamListener.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsCURILoader.h"
+#include "nsIURIContentListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+#include "nsStringGlue.h"
+
+class nsMsgAttachmentHandler;
+
+class nsURLFetcher : public nsIURLFetcher,
+ public nsIStreamListener,
+ public nsIURIContentListener,
+ public nsIInterfaceRequestor,
+ public nsIWebProgressListener,
+ public nsSupportsWeakReference
+{
+public:
+ nsURLFetcher();
+
+ /* this macro defines QueryInterface, AddRef and Release for this class */
+ NS_DECL_ISUPPORTS
+
+ // Methods for nsIURLFetcher
+ NS_DECL_NSIURLFETCHER
+
+ // Methods for nsIStreamListener
+ NS_DECL_NSISTREAMLISTENER
+
+ // Methods for nsIRequestObserver
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // Methods for nsIURICOntentListener
+ NS_DECL_NSIURICONTENTLISTENER
+
+ // Methods for nsIInterfaceRequestor
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ // Methods for nsIWebProgressListener
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+protected:
+ nsresult InsertConverter(const char * aContentType);
+
+private:
+ virtual ~nsURLFetcher();
+ nsCOMPtr<nsIOutputStream> mOutStream; // the output file stream
+ nsCOMPtr<nsIFile> mLocalFile; // the output file itself
+ nsCOMPtr<nsIStreamListener> mConverter; // the stream converter, if needed
+ nsCString mConverterContentType; // The content type of the converter
+ bool mStillRunning; // Are we still running?
+ int32_t mTotalWritten; // Size counter variable
+ char *mBuffer; // Buffer used for reading the data
+ uint32_t mBufferSize; // Buffer size;
+ nsCString mContentType; // The content type retrieved from the server
+ nsCString mCharset; // The charset retrieved from the server
+ RefPtr<nsMsgAttachmentHandler> mTagData; // Tag data for callback...
+ nsAttachSaveCompletionCallback mCallback; // Callback to call once the file is saved...
+ nsCOMPtr<nsISupports> mLoadCookie; // load cookie used by the uri loader when we fetch the url
+ bool mOnStopRequestProcessed; // used to prevent calling OnStopRequest multiple times
+ bool mIsFile; // This is used to check whether the URI is a local file.
+
+ friend class nsURLFetcherStreamConsumer;
+};
+
+
+/**
+ * Stream consumer used for handling special content type like multipart/x-mixed-replace
+ */
+
+class nsURLFetcherStreamConsumer : public nsIStreamListener
+{
+public:
+ explicit nsURLFetcherStreamConsumer(nsURLFetcher* urlFetcher);
+
+ /* additional members */
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+private:
+ virtual ~nsURLFetcherStreamConsumer();
+ nsURLFetcher* mURLFetcher;
+};
+
+
+#endif /* nsURLFetcher_h_ */
diff --git a/mailnews/db/gloda/components/glautocomp.js b/mailnews/db/gloda/components/glautocomp.js
new file mode 100644
index 000000000..67c245a93
--- /dev/null
+++ b/mailnews/db/gloda/components/glautocomp.js
@@ -0,0 +1,544 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/errUtils.js");
+
+var Gloda = null;
+var GlodaUtils = null;
+var MultiSuffixTree = null;
+var TagNoun = null;
+var FreeTagNoun = null;
+
+function ResultRowFullText(aItem, words, typeForStyle) {
+ this.item = aItem;
+ this.words = words;
+ this.typeForStyle = "gloda-fulltext-" + typeForStyle;
+}
+ResultRowFullText.prototype = {
+ multi: false,
+ fullText: true
+};
+
+function ResultRowSingle(aItem, aCriteriaType, aCriteria, aExplicitNounID) {
+ this.nounID = aExplicitNounID || aItem.NOUN_ID;
+ this.nounDef = Gloda._nounIDToDef[this.nounID];
+ this.criteriaType = aCriteriaType;
+ this.criteria = aCriteria;
+ this.item = aItem;
+ this.typeForStyle = "gloda-single-" + this.nounDef.name;
+}
+ResultRowSingle.prototype = {
+ multi: false,
+ fullText: false
+};
+
+function ResultRowMulti(aNounID, aCriteriaType, aCriteria, aQuery) {
+ this.nounID = aNounID;
+ this.nounDef = Gloda._nounIDToDef[aNounID];
+ this.criteriaType = aCriteriaType;
+ this.criteria = aCriteria;
+ this.collection = aQuery.getCollection(this);
+ this.collection.becomeExplicit();
+ this.renderer = null;
+}
+ResultRowMulti.prototype = {
+ multi: true,
+ typeForStyle: "gloda-multi",
+ fullText: false,
+ onItemsAdded: function(aItems) {
+ if (this.renderer) {
+ for (let [iItem, item] of aItems.entries()) {
+ this.renderer.renderItem(item);
+ }
+ }
+ },
+ onItemsModified: function(aItems) {
+ },
+ onItemsRemoved: function(aItems) {
+ },
+ onQueryCompleted: function() {
+ }
+};
+
+function nsAutoCompleteGlodaResult(aListener, aCompleter, aString) {
+ this.listener = aListener;
+ this.completer = aCompleter;
+ this.searchString = aString;
+ this._results = [];
+ this._pendingCount = 0;
+ this._problem = false;
+ // Track whether we have reported anything to the complete controller so
+ // that we know not to send notifications to it during calls to addRows
+ // prior to that point.
+ this._initiallyReported = false;
+
+ this.wrappedJSObject = this;
+}
+nsAutoCompleteGlodaResult.prototype = {
+ getObjectAt: function(aIndex) {
+ return this._results[aIndex] || null;
+ },
+ markPending: function ACGR_markPending(aCompleter) {
+ this._pendingCount++;
+ },
+ markCompleted: function ACGR_markCompleted(aCompleter) {
+ if (--this._pendingCount == 0 && this.active) {
+ this.listener.onSearchResult(this.completer, this);
+ }
+ },
+ announceYourself: function ACGR_announceYourself() {
+ this._initiallyReported = true;
+ this.listener.onSearchResult(this.completer, this);
+ },
+ addRows: function ACGR_addRows(aRows) {
+ if (!aRows.length)
+ return;
+ this._results.push.apply(this._results, aRows);
+ if (this._initiallyReported && this.active) {
+ this.listener.onSearchResult(this.completer, this);
+ }
+ },
+ // ==== nsIAutoCompleteResult
+ searchString: null,
+ get searchResult() {
+ if (this._problem)
+ return Ci.nsIAutoCompleteResult.RESULT_FAILURE;
+ if (this._results.length)
+ return (!this._pendingCount) ? Ci.nsIAutoCompleteResult.RESULT_SUCCESS
+ : Ci.nsIAutoCompleteResult.RESULT_SUCCESS_ONGOING;
+ else
+ return (!this._pendingCount) ? Ci.nsIAutoCompleteResult.RESULT_NOMATCH
+ : Ci.nsIAutoCompleteResult.RESULT_NOMATCH_ONGOING;
+ },
+ active: false,
+ defaultIndex: -1,
+ errorDescription: null,
+ get matchCount() {
+ return (this._results === null) ? 0 : this._results.length;
+ },
+ // this is the lower text, (shows the url in firefox)
+ // we try and show the contact's name here.
+ getValueAt: function(aIndex) {
+ let thing = this._results[aIndex];
+ return thing.name || thing.value || thing.subject || null;
+ },
+ getLabelAt: function(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+ // rich uses this to be the "title". it is the upper text
+ // we try and show the identity here.
+ getCommentAt: function(aIndex) {
+ let thing = this._results[aIndex];
+ if (thing.value) // identity
+ return thing.contact.name;
+ else
+ return thing.name || thing.subject;
+ },
+ // rich uses this to be the "type"
+ getStyleAt: function(aIndex) {
+ let row = this._results[aIndex];
+ return row.typeForStyle;
+ },
+ // rich uses this to be the icon
+ getImageAt: function(aIndex) {
+ let thing = this._results[aIndex];
+ if (!thing.value)
+ return null;
+
+ return ""; // we don't want to use gravatars as is.
+ /*
+ let md5hash = GlodaUtils.md5HashString(thing.value);
+ let gravURL = "http://www.gravatar.com/avatar/" + md5hash +
+ "?d=identicon&s=32&r=g";
+ return gravURL;
+ */
+ },
+ getFinalCompleteValueAt: function(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+ removeValueAt: function() {},
+
+ _stop: function() {
+ }
+};
+
+var MAX_POPULAR_CONTACTS = 200;
+
+/**
+ * Complete contacts/identities based on name/email. Instant phase is based on
+ * a suffix-tree built of popular contacts/identities. Delayed phase relies
+ * on a LIKE search of all known contacts.
+ */
+function ContactIdentityCompleter() {
+ // get all the contacts
+ let contactQuery = Gloda.newQuery(Gloda.NOUN_CONTACT);
+ contactQuery.orderBy("-popularity").limit(MAX_POPULAR_CONTACTS);
+ this.contactCollection = contactQuery.getCollection(this, null);
+ this.contactCollection.becomeExplicit();
+}
+ContactIdentityCompleter.prototype = {
+ _popularitySorter: function(a, b){ return b.popularity - a.popularity; },
+ complete: function ContactIdentityCompleter_complete(aResult, aString) {
+ if (aString.length < 3) {
+ // In CJK, first name or last name is sometime used as 1 character only.
+ // So we allow autocompleted search even if 1 character.
+ //
+ // [U+3041 - U+9FFF ... Full-width Katakana, Hiragana
+ // and CJK Ideograph
+ // [U+AC00 - U+D7FF ... Hangul
+ // [U+F900 - U+FFDC ... CJK compatibility ideograph
+ if (!aString.match(/[\u3041-\u9fff\uac00-\ud7ff\uf900-\uffdc]/))
+ return false;
+ }
+
+ let matches;
+ if (this.suffixTree) {
+ matches = this.suffixTree.findMatches(aString.toLowerCase());
+ }
+ else
+ matches = [];
+
+ // let's filter out duplicates due to identity/contact double-hits by
+ // establishing a map based on the contact id for these guys.
+ // let's also favor identities as we do it, because that gets us the
+ // most accurate gravat, potentially
+ let contactToThing = {};
+ for (let iMatch = 0; iMatch < matches.length; iMatch++) {
+ let thing = matches[iMatch];
+ if (thing.NOUN_ID == Gloda.NOUN_CONTACT && !(thing.id in contactToThing))
+ contactToThing[thing.id] = thing;
+ else if (thing.NOUN_ID == Gloda.NOUN_IDENTITY)
+ contactToThing[thing.contactID] = thing;
+ }
+ // and since we can now map from contacts down to identities, map contacts
+ // to the first identity for them that we find...
+ matches = Object.keys(contactToThing).map(id => contactToThing[id]).
+ map(val => val.NOUN_ID == Gloda.NOUN_IDENTITY ? val : val.identities[0]);
+
+ let rows = matches.
+ map(match => new ResultRowSingle(match, "text", aResult.searchString));
+ aResult.addRows(rows);
+
+ // - match against database contacts / identities
+ let pending = {contactToThing: contactToThing, pendingCount: 2};
+
+ let contactQuery = Gloda.newQuery(Gloda.NOUN_CONTACT);
+ contactQuery.nameLike(contactQuery.WILDCARD, aString,
+ contactQuery.WILDCARD);
+ pending.contactColl = contactQuery.getCollection(this, aResult);
+ pending.contactColl.becomeExplicit();
+
+ let identityQuery = Gloda.newQuery(Gloda.NOUN_IDENTITY);
+ identityQuery.kind("email").valueLike(identityQuery.WILDCARD, aString,
+ identityQuery.WILDCARD);
+ pending.identityColl = identityQuery.getCollection(this, aResult);
+ pending.identityColl.becomeExplicit();
+
+ aResult._contactCompleterPending = pending;
+
+ return true;
+ },
+ onItemsAdded: function(aItems, aCollection) {
+ },
+ onItemsModified: function(aItems, aCollection) {
+ },
+ onItemsRemoved: function(aItems, aCollection) {
+ },
+ onQueryCompleted: function(aCollection) {
+ // handle the initial setup case...
+ if (aCollection.data == null) {
+ // cheat and explicitly add our own contact...
+ if (Gloda.myContact &&
+ !(Gloda.myContact.id in this.contactCollection._idMap))
+ this.contactCollection._onItemsAdded([Gloda.myContact]);
+
+ // the set of identities owned by the contacts is automatically loaded as part
+ // of the contact loading...
+ // (but only if we actually have any contacts)
+ this.identityCollection =
+ this.contactCollection.subCollections[Gloda.NOUN_IDENTITY];
+
+ let contactNames = this.contactCollection.items.
+ map(c => c.name.replace(" ", "").toLowerCase() || "x");
+ // if we had no contacts, we will have no identity collection!
+ let identityMails;
+ if (this.identityCollection)
+ identityMails = this.identityCollection.items.
+ map(i => i.value.toLowerCase());
+
+ // The suffix tree takes two parallel lists; the first contains strings
+ // while the second contains objects that correspond to those strings.
+ // In the degenerate case where identityCollection does not exist, it will
+ // be undefined. Calling concat with an argument of undefined simply
+ // duplicates the list we called concat on, and is thus harmless. Our
+ // use of && on identityCollection allows its undefined value to be
+ // passed through to concat. identityMails will likewise be undefined.
+ this.suffixTree = new MultiSuffixTree(contactNames.concat(identityMails),
+ this.contactCollection.items.concat(this.identityCollection &&
+ this.identityCollection.items));
+
+ return;
+ }
+
+ // handle the completion case
+ let result = aCollection.data;
+ let pending = result._contactCompleterPending;
+
+ if (--pending.pendingCount == 0) {
+ let possibleDudes = [];
+
+ let contactToThing = pending.contactToThing;
+
+ let items;
+
+ // check identities first because they are better than contacts in terms
+ // of display
+ items = pending.identityColl.items;
+ for (let iIdentity = 0; iIdentity < items.length; iIdentity++){
+ let identity = items[iIdentity];
+ if (!(identity.contactID in contactToThing)) {
+ contactToThing[identity.contactID] = identity;
+ possibleDudes.push(identity);
+ // augment the identity with its contact's popularity
+ identity.popularity = identity.contact.popularity;
+ }
+ }
+ items = pending.contactColl.items;
+ for (let iContact = 0; iContact < items.length; iContact++) {
+ let contact = items[iContact];
+ if (!(contact.id in contactToThing)) {
+ contactToThing[contact.id] = contact;
+ possibleDudes.push(contact.identities[0]);
+ }
+ }
+
+ // sort in order of descending popularity
+ possibleDudes.sort(this._popularitySorter);
+ let rows = possibleDudes.
+ map(dude => new ResultRowSingle(dude, "text", result.searchString));
+ result.addRows(rows);
+ result.markCompleted(this);
+
+ // the collections no longer care about the result, make it clear.
+ delete pending.identityColl.data;
+ delete pending.contactColl.data;
+ // the result object no longer needs us or our data
+ delete result._contactCompleterPending;
+ }
+ }
+};
+
+/**
+ * Complete tags that are used on contacts.
+ */
+function ContactTagCompleter() {
+ FreeTagNoun.populateKnownFreeTags();
+ this._buildSuffixTree();
+ FreeTagNoun.addListener(this);
+}
+ContactTagCompleter.prototype = {
+ _buildSuffixTree: function() {
+ let tagNames = [], tags = [];
+ for (let [tagName, tag] in Iterator(FreeTagNoun.knownFreeTags)) {
+ tagNames.push(tagName.toLowerCase());
+ tags.push(tag);
+ }
+ this._suffixTree = new MultiSuffixTree(tagNames, tags);
+ this._suffixTreeDirty = false;
+ },
+ onFreeTagAdded: function(aTag) {
+ this._suffixTreeDirty = true;
+ },
+ complete: function ContactTagCompleter_complete(aResult, aString) {
+ // now is not the best time to do this; have onFreeTagAdded use a timer.
+ if (this._suffixTreeDirty)
+ this._buildSuffixTree();
+
+ if (aString.length < 2)
+ return false; // no async mechanism that will add new rows
+
+ let tags = this._suffixTree.findMatches(aString.toLowerCase());
+ let rows = [];
+ for (let tag of tags) {
+ let query = Gloda.newQuery(Gloda.NOUN_CONTACT);
+ query.freeTags(tag);
+ let resRow = new ResultRowMulti(Gloda.NOUN_CONTACT, "tag", tag.name,
+ query);
+ rows.push(resRow);
+ }
+ aResult.addRows(rows);
+
+ return false; // no async mechanism that will add new rows
+ }
+};
+
+/**
+ * Complete tags that are used on messages
+ */
+function MessageTagCompleter() {
+ this._buildSuffixTree();
+}
+MessageTagCompleter.prototype = {
+ _buildSuffixTree: function MessageTagCompleter__buildSufficeTree() {
+ let tagNames = [], tags = [];
+ let tagArray = TagNoun.getAllTags();
+ for (let iTag = 0; iTag < tagArray.length; iTag++) {
+ let tag = tagArray[iTag];
+ tagNames.push(tag.tag.toLowerCase());
+ tags.push(tag);
+ }
+ this._suffixTree = new MultiSuffixTree(tagNames, tags);
+ this._suffixTreeDirty = false;
+ },
+ complete: function MessageTagCompleter_complete(aResult, aString) {
+ if (aString.length < 2)
+ return false;
+
+ let tags = this._suffixTree.findMatches(aString.toLowerCase());
+ let rows = [];
+ for (let tag of tags) {
+ let resRow = new ResultRowSingle(tag, "tag", tag.tag, TagNoun.id);
+ rows.push(resRow);
+ }
+ aResult.addRows(rows);
+
+ return false; // no async mechanism that will add new rows
+ }
+};
+
+/**
+ * Complete with helpful hints about full-text search
+ */
+function FullTextCompleter() {
+}
+FullTextCompleter.prototype = {
+ complete: function FullTextCompleter_complete(aResult, aSearchString) {
+ if (aSearchString.length < 4)
+ return false;
+ // We use code very similar to that in msg_search.js, except that we
+ // need to detect when we found phrases, as well as strip commas.
+ aSearchString = aSearchString.trim();
+ let terms = [];
+ let phraseFound = false;
+ while (aSearchString) {
+ let term = "";
+ if (aSearchString.startsWith('"')) {
+ let endIndex = aSearchString.indexOf(aSearchString[0], 1);
+ // eat the quote if it has no friend
+ if (endIndex == -1) {
+ aSearchString = aSearchString.substring(1);
+ continue;
+ }
+ phraseFound = true;
+ term = aSearchString.substring(1, endIndex).trim();
+ if (term)
+ terms.push(term);
+ aSearchString = aSearchString.substring(endIndex + 1);
+ continue;
+ }
+
+ let spaceIndex = aSearchString.indexOf(" ");
+ if (spaceIndex == -1) {
+ terms.push(aSearchString.replace(/,/g, ""));
+ break;
+ }
+
+ term = aSearchString.substring(0, spaceIndex).replace(/,/g, "");
+ if (term)
+ terms.push(term);
+ aSearchString = aSearchString.substring(spaceIndex+1);
+ }
+
+ if (terms.length == 1 && !phraseFound)
+ aResult.addRows([new ResultRowFullText(aSearchString, terms, "single")]);
+ else
+ aResult.addRows([new ResultRowFullText(aSearchString, terms, "all")]);
+
+ return false; // no async mechanism that will add new rows
+ }
+};
+
+var LOG;
+
+function nsAutoCompleteGloda() {
+ this.wrappedJSObject = this;
+ try {
+ // set up our awesome globals!
+ if (Gloda === null) {
+ let loadNS = {};
+ Cu.import("resource:///modules/gloda/public.js", loadNS);
+ Gloda = loadNS.Gloda;
+
+ Cu.import("resource:///modules/gloda/utils.js", loadNS);
+ GlodaUtils = loadNS.GlodaUtils;
+ Cu.import("resource:///modules/gloda/suffixtree.js", loadNS);
+ MultiSuffixTree = loadNS.MultiSuffixTree;
+ Cu.import("resource:///modules/gloda/noun_tag.js", loadNS);
+ TagNoun = loadNS.TagNoun;
+ Cu.import("resource:///modules/gloda/noun_freetag.js", loadNS);
+ FreeTagNoun = loadNS.FreeTagNoun;
+
+ Cu.import("resource:///modules/gloda/log4moz.js", loadNS);
+ LOG = loadNS["Log4Moz"].repository.getLogger("gloda.autocomp");
+ }
+
+ this.completers = [];
+ this.curResult = null;
+
+ this.completers.push(new FullTextCompleter()); // not async.
+ this.completers.push(new ContactIdentityCompleter()); // potentially async.
+ this.completers.push(new ContactTagCompleter()); // not async.
+ this.completers.push(new MessageTagCompleter()); // not async.
+ } catch (e) {
+ logException(e);
+ }
+}
+
+nsAutoCompleteGloda.prototype = {
+ classID: Components.ID("{3bbe4d77-3f70-4252-9500-bc00c26f476d}"),
+ QueryInterface: XPCOMUtils.generateQI([
+ Components.interfaces.nsIAutoCompleteSearch]),
+
+ startSearch: function(aString, aParam, aResult, aListener) {
+ try {
+ let result = new nsAutoCompleteGlodaResult(aListener, this, aString);
+ // save this for hacky access to the search. I somewhat suspect we simply
+ // should not be using the formal autocomplete mechanism at all.
+ // Used in glodacomplete.xml.
+ this.curResult = result;
+
+ // Guard against late async results being sent.
+ this.curResult.active = true;
+
+ if (aParam == "global") {
+ for (let completer of this.completers) {
+ // they will return true if they have something pending.
+ if (completer.complete(result, aString))
+ result.markPending(completer);
+ }
+ //} else {
+ // It'd be nice to do autocomplete in the quicksearch modes based
+ // on the specific values for that mode in the current view.
+ // But we don't do that yet.
+ }
+
+ result.announceYourself();
+ } catch (e) {
+ logException(e);
+ }
+ },
+
+ stopSearch: function() {
+ this.curResult.active = false;
+ }
+};
+
+var components = [nsAutoCompleteGloda];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/db/gloda/components/gloda.manifest b/mailnews/db/gloda/components/gloda.manifest
new file mode 100644
index 000000000..0156e94f4
--- /dev/null
+++ b/mailnews/db/gloda/components/gloda.manifest
@@ -0,0 +1,5 @@
+component {3bbe4d77-3f70-4252-9500-bc00c26f476d} glautocomp.js
+contract @mozilla.org/autocomplete/search;1?name=gloda {3bbe4d77-3f70-4252-9500-bc00c26f476d}
+component {8cddbbbc-7ced-46b0-a936-8cddd1928c24} jsmimeemitter.js
+contract @mozilla.org/gloda/jsmimeemitter;1 {8cddbbbc-7ced-46b0-a936-8cddd1928c24}
+category mime-emitter @mozilla.org/messenger/mimeemitter;1?type=application/x-js-mime-message @mozilla.org/gloda/jsmimeemitter;1
diff --git a/mailnews/db/gloda/components/jsmimeemitter.js b/mailnews/db/gloda/components/jsmimeemitter.js
new file mode 100644
index 000000000..ed86415dd
--- /dev/null
+++ b/mailnews/db/gloda/components/jsmimeemitter.js
@@ -0,0 +1,493 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var kStateUnknown = 0;
+var kStateInHeaders = 1;
+var kStateInBody = 2;
+var kStateInAttachment = 3;
+
+/**
+ * When the saneBodySize flag is active, limit body parts to at most this many
+ * bytes. See |MsgHdrToMimeMessage| for more information on the flag.
+ *
+ * The choice of 20k was made on the very scientific basis of running a query
+ * against my indexed e-mail and finding the point where these things taper
+ * off. I chose 20 because things had tapered off pretty firmly by 16, so
+ * 20 gave it some space and it was also the end of a mini-plateau.
+ */
+var MAX_SANE_BODY_PART_SIZE = 20 * 1024;
+
+/**
+ * Custom nsIMimeEmitter to build a sub-optimal javascript representation of a
+ * MIME message. The intent is that a better mechanism than is evolved to
+ * provide a javascript-accessible representation of the message.
+ *
+ * Processing occurs in two passes. During the first pass, libmime is parsing
+ * the stream it is receiving, and generating header and body events for all
+ * MimeMessage instances it encounters. This provides us with the knowledge
+ * of each nested message in addition to the top level message, their headers
+ * and sort-of their bodies. The sort-of is that we may get more than
+ * would normally be displayed in cases involving multipart/alternatives.
+ * We have augmented libmime to have a notify_nested_options parameter which
+ * is enabled when we are the consumer. This option causes MimeMultipart to
+ * always emit a content-type header (via addHeaderField), defaulting to
+ * text/plain when an explicit value is not present. Additionally,
+ * addHeaderField is called with a custom "x-jsemitter-part-path" header with
+ * the value being the part path (ex: 1.2.2). Having the part path greatly
+ * simplifies our life for building the part hierarchy.
+ * During the second pass, the libmime object model is traversed, generating
+ * attachment notifications for all leaf nodes. From our perspective, this
+ * means file attachments and embedded messages (message/rfc822). We use this
+ * pass to create the attachment objects proper, which we then substitute into
+ * the part tree we have already built.
+ */
+function MimeMessageEmitter() {
+ this._mimeMsg = {};
+ Cu.import("resource:///modules/gloda/mimemsg.js", this._mimeMsg);
+ this._utils = {};
+ Cu.import("resource:///modules/gloda/utils.js", this._utils);
+
+ this._url = null;
+ this._partRE = this._utils.GlodaUtils.PART_RE;
+
+ this._outputListener = null;
+
+ this._curPart = null;
+ this._curAttachment = null;
+ this._partMap = {};
+ this._bogusPartTranslation = {};
+
+ this._state = kStateUnknown;
+
+ this._writeBody = false;
+}
+
+var deathToNewlines = /\n/g;
+
+MimeMessageEmitter.prototype = {
+ classID: Components.ID("{8cddbbbc-7ced-46b0-a936-8cddd1928c24}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIMimeEmitter]),
+
+ initialize: function mime_emitter_initialize(aUrl, aChannel, aFormat) {
+ this._url = aUrl;
+ this._curPart = new this._mimeMsg.MimeMessage();
+ // the partName is intentionally ""! not a place-holder!
+ this._curPart.partName = "";
+ this._curAttachment = "";
+ this._partMap[""] = this._curPart;
+
+ // pull options across...
+ let options = this._mimeMsg.MsgHdrToMimeMessage.OPTION_TUNNEL;
+ this._saneBodySize = (options && ("saneBodySize" in options)) ?
+ options.saneBodySize : false;
+
+ this._mimeMsg.MsgHdrToMimeMessage.RESULT_RENDEVOUZ[aUrl.spec] =
+ this._curPart;
+ },
+
+ complete: function mime_emitter_complete() {
+ this._url = null;
+
+ this._outputListener = null;
+
+ this._curPart = null;
+ this._curAttachment = null;
+ this._partMap = null;
+ this._bogusPartTranslation = null;
+ },
+
+ setPipe: function mime_emitter_setPipe(aInputStream, aOutputStream) {
+ // we do not care about these
+ },
+ set outputListener(aListener) {
+ this._outputListener = aListener;
+ },
+ get outputListener() {
+ return this._outputListener;
+ },
+
+ _stripParams: function mime_emitter__stripParams(aValue) {
+ let indexSemi = aValue.indexOf(";");
+ if (indexSemi >= 0)
+ aValue = aValue.substring(0, indexSemi);
+ return aValue;
+ },
+
+ _beginPayload: function mime_emitter__beginPayload(aContentType) {
+ let contentTypeNoParams = this._stripParams(aContentType).toLowerCase();
+ if (contentTypeNoParams == "text/plain" ||
+ contentTypeNoParams == "text/html" ||
+ contentTypeNoParams == "text/enriched") {
+ this._curPart = new this._mimeMsg.MimeBody(contentTypeNoParams);
+ this._writeBody = true;
+ }
+ else if (contentTypeNoParams == "message/rfc822") {
+ // startHeader will take care of this
+ this._curPart = new this._mimeMsg.MimeMessage();
+ // do not fall through into the content-type setting case; this
+ // content-type needs to get clobbered by the actual content-type of
+ // the enclosed message.
+ this._writeBody = false;
+ return;
+ }
+ // this is going to fall-down with TNEF encapsulation and such, we really
+ // need to just be consuming the object model.
+ else if (contentTypeNoParams.startsWith("multipart/")) {
+ this._curPart = new this._mimeMsg.MimeContainer(contentTypeNoParams);
+ this._writeBody = false;
+ }
+ else {
+ this._curPart = new this._mimeMsg.MimeUnknown(contentTypeNoParams);
+ this._writeBody = false;
+ }
+ // put the full content-type in the headers and normalize out any newlines
+ this._curPart.headers["content-type"] =
+ [aContentType.replace(deathToNewlines, "")];
+ },
+
+ // ----- Header Routines
+ /**
+ * StartHeader provides the base case for our processing. It is the first
+ * notification we receive when processing begins on the outer rfc822
+ * message. We do not receive an x-jsemitter-part-path notification for the
+ * message, but the aIsRootMailHeader tells us everything we need to know.
+ * (Or it would if we hadn't already set everything up in initialize.)
+ *
+ * When dealing with nested RFC822 messages, we will receive the
+ * addHeaderFields for the content-type and the x-jsemitter-part-path
+ * prior to the startHeader call. This is because the MIME multipart
+ * container that holds the message is the one generating the notification.
+ * For that reason, we do not process them here, but instead in
+ * addHeaderField and _beginPayload.
+ *
+ * We do need to track our state for addHeaderField's benefit though.
+ */
+ startHeader: function mime_emitter_startHeader(aIsRootMailHeader,
+ aIsHeaderOnly, aMsgID, aOutputCharset) {
+ this._state = kStateInHeaders;
+ },
+ /**
+ * Receives a header field name and value for the current MIME part, which
+ * can be an rfc822/message or one of its sub-parts.
+ *
+ * The emitter architecture treats rfc822/messages as special because it was
+ * architected around presentation. In that case, the organizing concept
+ * is the single top-level rfc822/message. (It did not 'look into' nested
+ * messages in most cases.)
+ * As a result the interface is biased towards being 'in the headers' or
+ * 'in the body', corresponding to calls to startHeader and startBody,
+ * respectively.
+ * This information is interesting to us because the message itself is an
+ * odd pseudo-mime-part. Because it has only one child, its headers are,
+ * in a way, its payload, but they also serve as the description of its
+ * MIME child part. This introduces a complication in that we see the
+ * content-type for the message's "body" part before we actually see any
+ * of the headers. To deal with this, we punt on the construction of the
+ * body part to the call to startBody() and predicate our logic on the
+ * _state field.
+ */
+ addHeaderField: function mime_emitter_addHeaderField(aField, aValue) {
+ if (this._state == kStateInBody) {
+ aField = aField.toLowerCase();
+ if (aField == "content-type")
+ this._beginPayload(aValue, true);
+ else if (aField == "x-jsemitter-part-path") {
+ // This is either naming the current part, or referring to an already
+ // existing part (in the case of multipart/related on its second pass).
+ // As such, check if the name already exists in our part map.
+ let partName = this._stripParams(aValue);
+ // if it does, then make the already-existing part at that path current
+ if (partName in this._partMap) {
+ this._curPart = this._partMap[partName];
+ this._writeBody = "body" in this._curPart;
+ }
+ // otherwise, name the part we are holding onto and place it.
+ else {
+ this._curPart.partName = partName;
+ this._placePart(this._curPart);
+ }
+ }
+ else if (aField == "x-jsemitter-encrypted" && aValue == "1") {
+ this._curPart.isEncrypted = true;
+ }
+ // There is no other field to be emitted in the body case other than the
+ // ones we just handled. (They were explicitly added for the js
+ // emitter.)
+ }
+ else if (this._state == kStateInHeaders) {
+ let lowerField = aField.toLowerCase();
+ if (lowerField in this._curPart.headers)
+ this._curPart.headers[lowerField].push(aValue);
+ else
+ this._curPart.headers[lowerField] = [aValue];
+ }
+ },
+ addAllHeaders: function mime_emitter_addAllHeaders(aAllHeaders, aHeaderSize) {
+ // This is called by the parsing code after the calls to AddHeaderField (or
+ // AddAttachmentField if the part is an attachment), and seems to serve
+ // a specialized, quasi-redundant purpose. (nsMimeBaseEmitter creates a
+ // nsIMimeHeaders instance and hands it to the nsIMsgMailNewsUrl.)
+ // nop
+ },
+ writeHTMLHeaders: function mime_emitter_writeHTMLHeaders(aName) {
+ // It doesn't look like this should even be part of the interface; I think
+ // only the nsMimeHtmlDisplayEmitter::EndHeader call calls this signature.
+ // nop
+ },
+ endHeader: function mime_emitter_endHeader(aName) {
+ },
+ updateCharacterSet: function mime_emitter_updateCharacterSet(aCharset) {
+ // we do not need to worry about this. it turns out this notification is
+ // exclusively for the benefit of the UI. libmime, believe it or not,
+ // is actually doing the right thing under the hood and handles all the
+ // encoding issues for us.
+ // so, get ready for the only time you will ever hear this:
+ // three cheers for libmime!
+ },
+
+ /**
+ * Place a part in its proper location; requires the parent to be present.
+ * However, we no longer require in-order addition of children. (This is
+ * currently a hedge against extension code doing wacky things. Our
+ * motivating use-case is multipart/related which actually does generate
+ * everything in order on its first pass, but has a wacky second pass. It
+ * does not actually trigger the out-of-order code because we have
+ * augmented the libmime code to generate its x-jsemitter-part-path info
+ * a second time, in which case we reuse the part we already created.)
+ *
+ * @param aPart Part to place.
+ */
+ _placePart: function(aPart) {
+ let partName = aPart.partName;
+ this._partMap[partName] = aPart;
+
+ let [storagePartName, parentName, parentPart] = this._findOrCreateParent(partName);
+ let lastDotIndex = storagePartName.lastIndexOf(".");
+ if (parentPart !== undefined) {
+ let indexInParent = parseInt(storagePartName.substring(lastDotIndex+1)) - 1;
+ // handle out-of-order notification...
+ if (indexInParent < parentPart.parts.length)
+ parentPart.parts[indexInParent] = aPart;
+ else {
+ while (indexInParent > parentPart.parts.length)
+ parentPart.parts.push(null);
+ parentPart.parts.push(aPart);
+ }
+ }
+ },
+
+ /**
+ * In case the MIME structure is wrong, (i.e. we have no parent to add the
+ * current part to), this function recursively makes sure we create the
+ * missing bits in the hierarchy.
+ * What happens in the case of encrypted emails (mimecryp.cpp):
+ * 1. is the message
+ * 1.1 doesn't exist
+ * 1.1.1 is the multipart/alternative that holds the text/plain and text/html
+ * 1.1.1.1 is text/plain
+ * 1.1.1.2 is text/html
+ * This function fills the missing bits.
+ */
+ _findOrCreateParent: function (aPartName) {
+ let partName = aPartName + "";
+ let parentName = partName.substring(0, partName.lastIndexOf("."));
+ let parentPart;
+ if (parentName in this._partMap) {
+ parentPart = this._partMap[parentName]
+ let lastDotIndex = partName.lastIndexOf(".");
+ let indexInParent = parseInt(partName.substring(lastDotIndex+1)) - 1;
+ if ("parts" in parentPart && indexInParent == parentPart.parts.length - 1)
+ return [partName, parentName, parentPart];
+ else
+ return this._findAnotherContainer(aPartName);
+ } else {
+ // Find the grandparent
+ let [, grandParentName, grandParentPart] = this._findOrCreateParent(parentName);
+ // Create the missing part.
+ let parentPart = new this._mimeMsg.MimeContainer("multipart/fake-container");
+ // Add it to the grandparent, remember we added it in the hierarchy.
+ grandParentPart.parts.push(parentPart);
+ this._partMap[parentName] = parentPart;
+ return [partName, parentName, parentPart];
+ }
+ },
+
+ /**
+ * In the case of UUEncoded attachments, libmime tells us about the attachment
+ * as a child of a MimeBody. This obviously doesn't make us happy, so in case
+ * libmime wants us to attach an attachment to something that's not a
+ * container, we walk up the mime tree to find a suitable container to hold
+ * the attachment.
+ * The results are cached so that they're consistent accross calls — this
+ * ensures the call to _replacePart works fine.
+ */
+ _findAnotherContainer: function(aPartName) {
+ if (aPartName in this._bogusPartTranslation)
+ return this._bogusPartTranslation[aPartName];
+
+ let parentName = aPartName + "";
+ let parentPart;
+ while (!(parentPart && "parts" in parentPart) && parentName.length) {
+ parentName = parentName.substring(0, parentName.lastIndexOf("."));
+ parentPart = this._partMap[parentName];
+ }
+ let childIndex = parentPart.parts.length;
+ let fallbackPartName = (parentName ? parentName +"." : "")+(childIndex+1);
+ return (this._bogusPartTranslation[aPartName] = [fallbackPartName, parentName, parentPart]);
+ },
+
+ /**
+ * In the case of attachments, we need to replace an existing part with a
+ * more representative part...
+ *
+ * @param aPart Part to place.
+ */
+ _replacePart: function(aPart) {
+ // _partMap always maps the libmime names to parts
+ let partName = aPart.partName;
+ this._partMap[partName] = aPart;
+
+ let [storagePartName, parentName, parentPart] = this._findOrCreateParent(partName);
+
+ let childNamePart = storagePartName.substring(storagePartName.lastIndexOf(".")+1);
+ let childIndex = parseInt(childNamePart) - 1;
+
+ // The attachment has been encapsulated properly in a MIME part (most of
+ // the cases). This does not hold for UUencoded-parts for instance (see
+ // test_mime_attachments_size.js for instance).
+ if (childIndex < parentPart.parts.length) {
+ let oldPart = parentPart.parts[childIndex];
+ parentPart.parts[childIndex] = aPart;
+ // copy over information from the original part
+ aPart.parts = oldPart.parts;
+ aPart.headers = oldPart.headers;
+ aPart.isEncrypted = oldPart.isEncrypted;
+ } else {
+ parentPart.parts[childIndex] = aPart;
+ }
+ },
+
+ // ----- Attachment Routines
+ // The attachment processing happens after the initial streaming phase (during
+ // which time we receive the messages, both bodies and headers). Our caller
+ // traverses the libmime child object hierarchy, emitting an attachment for
+ // each leaf object or sub-message.
+ startAttachment: function mime_emitter_startAttachment(aName, aContentType,
+ aUrl, aIsExternalAttachment) {
+ this._state = kStateInAttachment;
+
+ // we need to strip our magic flags from the URL; this regexp matches all
+ // the specific flags that the jsmimeemitter understands (we abuse the URL
+ // parameters to pass information all the way to here)
+ aUrl = aUrl.replace(/((header=filter|emitter=js|fetchCompleteMessage=(true|false)|examineEncryptedParts=(true|false)))&?/g, "");
+ // the url should contain a part= piece that tells us the part name, which
+ // we then use to figure out where to place that part if it's a real
+ // attachment.
+ let partMatch, partName;
+ if (aUrl.startsWith("http") || aUrl.startsWith("file")) {
+ // if we have a remote url, unlike non external mail part urls, it may also
+ // contain query strings starting with ?; PART_RE does not handle this.
+ partMatch = aUrl.match(/[?&]part=[^&]+$/);
+ partMatch = partMatch && partMatch[0];
+ partName = partMatch && partMatch.split("part=")[1];
+ }
+ else {
+ partMatch = this._partRE.exec(aUrl);
+ partName = partMatch && partMatch[1];
+ }
+ this._curAttachment = partName;
+
+ if (aContentType == "message/rfc822") {
+ // we want to offer extension authors a way to see attachments as the
+ // message readers sees them, which means attaching an extra url property
+ // to the part that was already created before
+ if (partName) {
+ // we disguise this MimeMessage into something that can be used as a
+ // MimeAttachment so that it is transparent for the user code
+ this._partMap[partName].url = aUrl;
+ this._partMap[partName].isExternal = aIsExternalAttachment;
+ this._partMap[partName].name = aName;
+ this._partMap[partName].isRealAttachment = true;
+ }
+ }
+ else if (partName) {
+ let part = new this._mimeMsg.MimeMessageAttachment(partName,
+ aName, aContentType, aUrl, aIsExternalAttachment);
+ // replace the existing part with the attachment...
+ this._replacePart(part);
+ }
+ },
+ addAttachmentField: function mime_emitter_addAttachmentField(aField, aValue) {
+ // What gets passed in here is X-Mozilla-PartURL with a value that
+ // is completely identical to aUrl from the call to startAttachment.
+ // (it's the same variable they use in each case). As such, there is
+ // no reason to handle that here.
+ // However, we also pass information about the size of the attachment, and
+ // that we want to handle
+ if (aField == "X-Mozilla-PartSize" && (this._curAttachment in this._partMap))
+ this._partMap[this._curAttachment].size = parseInt(aValue);
+ },
+ endAttachment: function mime_emitter_endAttachment() {
+ // don't need to do anything here, since we don't care about the headers.
+ },
+ endAllAttachments: function mime_emitter_endAllAttachments() {
+ // nop
+ },
+
+ // ----- Body Routines
+ /**
+ * We don't get an x-jsemitter-part-path for the message body, and we ignored
+ * our body part's content-type in addHeaderField, so this serves as our
+ * notice to set up the part (giving it a name).
+ */
+ startBody: function mime_emitter_startBody(aIsBodyOnly, aMsgID, aOutCharset) {
+ this._state = kStateInBody;
+
+ let subPartName = (this._curPart.partName == "") ?
+ "1" :
+ this._curPart.partName + ".1";
+ this._beginPayload(this._curPart.get("content-type", "text/plain"));
+ this._curPart.partName = subPartName;
+ this._placePart(this._curPart);
+ },
+
+ /**
+ * Write to the body. When saneBodySize is active, we stop adding if we are
+ * already at the limit for this body part.
+ */
+ writeBody: function mime_emitter_writeBody(aBuf, aSize, aOutAmountWritten) {
+ if (this._writeBody &&
+ (!this._saneBodySize ||
+ this._curPart.size < MAX_SANE_BODY_PART_SIZE))
+ this._curPart.appendBody(aBuf);
+ },
+
+ endBody: function mime_emitter_endBody() {
+ },
+
+ // ----- Generic Write (confusing)
+ // (binary data writing...)
+ write: function mime_emitter_write(aBuf, aSize, aOutAmountWritten) {
+ // we don't actually ever get called because we don't have the attachment
+ // binary payloads pass through us, but we do the following just in case
+ // we did get called (otherwise the caller gets mad and throws exceptions).
+ aOutAmountWritten.value = aSize;
+ },
+
+ // (string writing)
+ utilityWrite: function mime_emitter_utilityWrite(aBuf) {
+ this.write(aBuf, aBuf.length, {});
+ },
+};
+
+var components = [MimeMessageEmitter];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/db/gloda/components/moz.build b/mailnews/db/gloda/components/moz.build
new file mode 100644
index 000000000..0252cce7d
--- /dev/null
+++ b/mailnews/db/gloda/components/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXTRA_COMPONENTS += [
+ 'glautocomp.js',
+ 'gloda.manifest',
+ 'jsmimeemitter.js',
+]
+
diff --git a/mailnews/db/gloda/content/glodacomplete.css b/mailnews/db/gloda/content/glodacomplete.css
new file mode 100644
index 000000000..4e52bff21
--- /dev/null
+++ b/mailnews/db/gloda/content/glodacomplete.css
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+textbox[type="glodacomplete"] {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete");
+}
+
+panel[type="glodacomplete-richlistbox"] {
+ -moz-binding: url("chrome://gloda/content/glodacomplete.xml#glodacomplete-rich-result-popup");
+}
+
+.autocomplete-richlistbox {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistbox");
+ -moz-user-focus: ignore;
+ -moz-appearance: none;
+}
+
+.autocomplete-richlistbox > scrollbox {
+ overflow-x: hidden !important;
+}
+
+.explanation, .gloda-single-identity {
+ margin-inline-start: 1em;
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+.ac-comment {
+ font-size: 1.1em;
+ margin-inline-start: 0;
+}
+
+.ac-url-text {
+ color: -moz-nativehyperlinktext;
+ font-size: 0.95em;
+}
+
+span.ac-emphasize-text {
+ font-weight: bold;
+}
+
+.ac-url-text[selected="true"] {
+ color: inherit !important;
+}
+
+.gloda-single-identity[selected="true"] .ac-url{
+ color: white;
+}
+
+.parameters {
+ font-style: italic;
+ margin-inline-start: 1em;
+}
+
+.autocomplete-richlistitem[type="gloda-single-tag"] {
+ -moz-binding: url("chrome://gloda/content/glodacomplete.xml#gloda-single-tag-item");
+ overflow: -moz-hidden-unscrollable;
+}
+
+.autocomplete-richlistitem[type="gloda-single-identity"] {
+ -moz-binding: url("chrome://gloda/content/glodacomplete.xml#gloda-single-identity-item");
+ -moz-box-orient: vertical;
+ overflow: -moz-hidden-unscrollable;
+}
+
+.autocomplete-richlistitem[type="gloda-fulltext-single"] {
+ -moz-binding: url("chrome://gloda/content/glodacomplete.xml#gloda-fulltext-single-item");
+ overflow: -moz-hidden-unscrollable;
+}
+
+.autocomplete-richlistitem[type="gloda-fulltext-any"] {
+ -moz-binding: url("chrome://gloda/content/glodacomplete.xml#gloda-fulltext-any-item");
+ overflow: -moz-hidden-unscrollable;
+}
+
+.autocomplete-richlistitem[type="gloda-fulltext-all"] {
+ -moz-binding: url("chrome://gloda/content/glodacomplete.xml#gloda-fulltext-all-item");
+ overflow: -moz-hidden-unscrollable;
+}
+
+richlistitem[type="gloda-contact-chunk"] {
+ -moz-binding: url("chrome://gloda/content/glodacomplete.xml#gloda-contact-chunk");
+ -moz-box-orient: vertical;
+ overflow: -moz-hidden-unscrollable;
+}
+
+.autocomplete-richlistitem[type="gloda-multi"] {
+ -moz-binding: url("chrome://gloda/content/glodacomplete.xml#gloda-multi-item");
+ -moz-box-orient: vertical;
+ overflow: -moz-hidden-unscrollable;
+}
+
+/* .autocomplete-history-dropmarker wants to be optional, but we don't care */ \ No newline at end of file
diff --git a/mailnews/db/gloda/content/glodacomplete.xml b/mailnews/db/gloda/content/glodacomplete.xml
new file mode 100644
index 000000000..6c6b2f59d
--- /dev/null
+++ b/mailnews/db/gloda/content/glodacomplete.xml
@@ -0,0 +1,644 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="autocompleteBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="glodacomplete-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
+ <implementation implements="nsIAutoCompletePopup">
+ <method name="_appendCurrentResult">
+ <body>
+ <![CDATA[
+ var controller = this.mInput.controller;
+
+ // Process maxRows per chunk to improve performance and user experience
+ for (let i = 0; i < this.maxRows; i++) {
+ if (this._currentIndex >= this._matchCount)
+ return;
+
+ var existingItemsCount = this.richlistbox.childNodes.length;
+ var item;
+
+ // trim the leading/trailing whitespace
+ var trimmedSearchString = controller.searchString.trim();
+
+ // Unescape the URI spec for showing as an entry in the popup
+ let url = Components.classes["@mozilla.org/intl/texttosuburi;1"].
+ getService(Components.interfaces.nsITextToSubURI).
+ unEscapeURIForUI("UTF-8", controller.getValueAt(this._currentIndex));
+
+ // Unlike our superclass, we create nodes every time because we have
+ // heterogeneous results and we cannot rely on the XBL bindings to
+ // to change fast enough.
+ item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
+
+ var glodaCompleter = Components.
+ classes["@mozilla.org/autocomplete/search;1?name=gloda"].
+ getService(). //Components.interfaces.nsIAutoCompleteSearch)
+ wrappedJSObject;
+ var result = glodaCompleter.curResult;
+
+ // set these attributes before we set the class
+ // so that we can use them from the contructor
+ var row = result.getObjectAt(this._currentIndex);
+ var obj = row.item;
+ item.setAttribute("text", trimmedSearchString);
+ item.setAttribute("type", result.getStyleAt(this._currentIndex));
+
+ item.row = row;
+
+ // set the class at the end so we can use the attributes
+ // in the xbl constructor
+ item.className = "autocomplete-richlistitem";
+ this.richlistbox.appendChild(item);
+
+ this._currentIndex++;
+ }
+
+ // yield after each batch of items so that typing the url bar is responsive
+ setTimeout(() => this._appendCurrentResult(), 0);
+ ]]>
+ </body>
+ </method>
+ <method name="_invalidate">
+ <body>
+ <![CDATA[
+ setTimeout(() => this.adjustHeight(), 0);
+
+ // remove all child nodes because we never want to reuse them.
+ while (this.richlistbox.hasChildNodes())
+ this.richlistbox.lastChild.remove();
+
+ this._currentIndex = 0;
+ this._appendCurrentResult();
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <!-- This is autocomplete.xml's autocomplete-richlistitem duplicated and
+ modified to include its useful helper functions, but eliminating anything
+ that assumes specific content sub-items. Namely, url/title/etc. -->
+ <binding id="glodacomplete-base-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <field name="_boundaryCutoff">null</field>
+
+ <property name="boundaryCutoff" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._boundaryCutoff) {
+ this._boundaryCutoff =
+ Services.prefs
+ .getIntPref("toolkit.autocomplete.richBoundaryCutoff");
+ }
+ return this._boundaryCutoff;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="_getBoundaryIndices">
+ <parameter name="aText"/>
+ <parameter name="aSearchTokens"/>
+ <body>
+ <![CDATA[
+ // Short circuit for empty search ([""] == "")
+ if (aSearchTokens == "")
+ return [0, aText.length];
+
+ // Find which regions of text match the search terms
+ let regions = [];
+ for (let search of aSearchTokens) {
+ let matchIndex;
+ let startIndex = 0;
+ let searchLen = search.length;
+
+ // Find all matches of the search terms, but stop early for perf
+ let lowerText = aText.toLowerCase().substr(0, this.boundaryCutoff);
+ while ((matchIndex = lowerText.indexOf(search, startIndex)) >= 0) {
+ // Start the next search from where this one finished
+ startIndex = matchIndex + searchLen;
+ regions.push([matchIndex, startIndex]);
+ }
+ }
+
+ // Sort the regions by start position then end position
+ regions = regions.sort(function(a, b) {
+ let start = a[0] - b[0];
+ return (start == 0) ? a[1] - b[1] : start;
+ });
+
+ // Generate the boundary indices from each region
+ let start = 0;
+ let end = 0;
+ let boundaries = [];
+ let len = regions.length;
+ for (let i = 0; i < len; i++) {
+ // We have a new boundary if the start of the next is past the end
+ let region = regions[i];
+ if (region[0] > end) {
+ // First index is the beginning of match
+ boundaries.push(start);
+ // Second index is the beginning of non-match
+ boundaries.push(end);
+
+ // Track the new region now that we've stored the previous one
+ start = region[0];
+ }
+
+ // Push back the end index for the current or new region
+ end = Math.max(end, region[1]);
+ }
+
+ // Add the last region
+ boundaries.push(start);
+ boundaries.push(end);
+
+ // Put on the end boundary if necessary
+ if (end < aText.length)
+ boundaries.push(aText.length);
+
+ // Skip the first item because it's always 0
+ return boundaries.slice(1);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getSearchTokens">
+ <parameter name="aSearch"/>
+ <body>
+ <![CDATA[
+ let search = aSearch.toLowerCase();
+ return search.split(/\s+/);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_needsAlternateEmphasis">
+ <parameter name="aText"/>
+ <body>
+ <![CDATA[
+ for (let i = aText.length; --i >= 0; ) {
+ let charCode = aText.charCodeAt(i);
+ // Arabic, Syriac, Indic languages are likely to have ligatures
+ // that are broken when using the main emphasis styling
+ if (0x0600 <= charCode && charCode <= 0x109F)
+ return true;
+ }
+
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_setUpDescription">
+ <parameter name="aDescriptionElement"/>
+ <parameter name="aText"/>
+ <body>
+ <![CDATA[
+ // Get rid of all previous text
+ while (aDescriptionElement.hasChildNodes())
+ aDescriptionElement.lastChild.remove();
+
+ // Get the indices that separate match and non-match text
+ let search = this.getAttribute("text");
+ let tokens = this._getSearchTokens(search);
+ let indices = this._getBoundaryIndices(aText, tokens);
+
+ // If we're searching for something that needs alternate emphasis,
+ // we'll need to check the text that we match
+ let checkAlt = this._needsAlternateEmphasis(search);
+
+ let next;
+ let start = 0;
+ let len = indices.length;
+ // Even indexed boundaries are matches, so skip the 0th if it's empty
+ for (let i = indices[0] == 0 ? 1 : 0; i < len; i++) {
+ next = indices[i];
+ let text = aText.substr(start, next - start);
+ start = next;
+
+ if (i % 2 == 0) {
+ // Emphasize the text for even indices
+ let span = aDescriptionElement.appendChild(
+ document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
+ span.className = checkAlt && this._needsAlternateEmphasis(text) ?
+ "ac-emphasize-alt" : "ac-emphasize-text";
+ span.textContent = text;
+ } else {
+ // Otherwise, it's plain text
+ aDescriptionElement.appendChild(document.createTextNode(text));
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_setUpOverflow">
+ <parameter name="aParentBox"/>
+ <parameter name="aEllipsis"/>
+ <body>
+ <![CDATA[
+ // Hide the ellipsis incase there's just enough to not underflow
+ aEllipsis.hidden = true;
+
+ // Start with the parent's width and subtract off its children
+ let tooltip = [];
+ let children = aParentBox.childNodes;
+ let widthDiff = aParentBox.boxObject.width;
+
+ for (let i = 0; i < children.length; i++) {
+ // Only consider a child if it actually takes up space
+ let childWidth = children[i].boxObject.width;
+ if (childWidth > 0) {
+ // Subtract a little less to account for subpixel rounding
+ widthDiff -= childWidth - .5;
+
+ // Add to the tooltip if it's not hidden and has text
+ let childText = children[i].textContent;
+ if (childText)
+ tooltip.push(childText);
+ }
+ }
+
+ // If the children take up more space than the parent.. overflow!
+ if (widthDiff < 0) {
+ // Re-show the ellipsis now that we know it's needed
+ aEllipsis.hidden = false;
+
+ // Separate text components with a ndash --
+ aParentBox.tooltipText = tooltip.join(" \u2013 ");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_doUnderflow">
+ <parameter name="aName"/>
+ <body>
+ <![CDATA[
+ // Hide the ellipsis right when we know we're underflowing instead of
+ // waiting for the timeout to trigger the _setUpOverflow calculations
+ this[aName + "Box"].tooltipText = "";
+ this[aName + "OverflowEllipsis"].hidden = true;
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="gloda-single-tag-item" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
+ <content orient="vertical">
+ <xul:description anonid="explanation" class="explanation gloda-single"/>
+ </content>
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <constructor>
+ <![CDATA[
+ this._explanation = document.getAnonymousElementByAttribute(this, "anonid", "explanation");
+
+ this._adjustAcItem();
+ ]]>
+ </constructor>
+
+ <property name="label" readonly="true">
+ <getter>
+ <![CDATA[
+ return "tag " + this.row.item.tag;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="_adjustAcItem">
+ <body>
+ <![CDATA[
+ let label = gGlodaCompleteStrings.get("glodaComplete.messagesTagged.label");
+ this._explanation.value = label.replace("#1", this.row.item.tag);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <binding id="gloda-fulltext-single-item" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
+ <content orient="vertical">
+ <xul:description anonid="explanation" class="explanation gloda-fulltext-single"/>
+ <xul:description anonid="parameters"/>
+ </content>
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <constructor>
+ <![CDATA[
+ this._explanation = document.getAnonymousElementByAttribute(this, "anonid", "explanation");
+
+ this._adjustAcItem();
+ ]]>
+ </constructor>
+
+ <property name="label" readonly="true">
+ <getter>
+ <![CDATA[
+ return "full text search: " + this.row.item;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="_adjustAcItem">
+ <body>
+ <![CDATA[
+ let label = gGlodaCompleteStrings.get("glodaComplete.messagesMentioning.label");
+ this._explanation.value = label.replace("#1", this.row.item);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="gloda-fulltext-all-item" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
+ <content orient="vertical">
+ <xul:description anonid="explanation" class="explanation"/>
+ </content>
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <constructor>
+ <![CDATA[
+ this._explanation = document.getAnonymousElementByAttribute(this, "anonid", "explanation");
+ this._adjustAcItem();
+ ]]>
+ </constructor>
+
+ <property name="label" readonly="true">
+ <getter>
+ <![CDATA[
+ return "full text search: " + this.row.item; // what is this for? l10n?
+ ]]>
+ </getter>
+ </property>
+
+ <method name="_adjustAcItem">
+ <body>
+ <![CDATA[
+ let label = gGlodaCompleteStrings.get("glodaComplete.messagesMentioningMany.label");
+ this._explanation.value = label.replace("#1", this.row.words.join(", "));
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="gloda-single-identity-item" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
+ <content>
+ <xul:hbox class="gloda-single-identity">
+ <xul:image anonid="picture" class="picture"/>
+ <xul:vbox>
+ <xul:hbox>
+ <xul:hbox anonid="name-box" class="ac-title" flex="1"
+ onunderflow="_doUnderflow('_name');">
+ <xul:description anonid="name" class="ac-normal-text ac-comment"
+ xbl:inherits="selected"/>
+ </xul:hbox>
+ <xul:label anonid="name-overflow-ellipsis" xbl:inherits="selected"
+ class="ac-ellipsis-after ac-comment" hidden="true"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:hbox anonid="identity-box" class="ac-url" flex="1"
+ onunderflow="_doUnderflow('_identity');">
+ <xul:description anonid="identity" class="ac-normal-text ac-url-text"
+ xbl:inherits="selected"/>
+ </xul:hbox>
+ <xul:label anonid="identity-overflow-ellipsis" xbl:inherits="selected"
+ class="ac-ellipsis-after ac-url-text" hidden="true"/>
+ <xul:image anonid="type-image" class="ac-type-icon"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <constructor>
+ <![CDATA[
+ let ellipsis = "\u2026";
+ try {
+ ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (ex) {
+ // Do nothing.. we already have a default
+ }
+
+ this._identityOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "identity-overflow-ellipsis");
+ this._nameOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "name-overflow-ellipsis");
+
+ this._identityOverflowEllipsis.value = ellipsis;
+ this._nameOverflowEllipsis.value = ellipsis;
+
+ this._typeImage = document.getAnonymousElementByAttribute(this, "anonid", "type-image");
+
+ this._identityBox = document.getAnonymousElementByAttribute(this, "anonid", "identity-box");
+ this._identity = document.getAnonymousElementByAttribute(this, "anonid", "identity");
+
+ this._nameBox = document.getAnonymousElementByAttribute(this, "anonid", "name-box");
+ this._name = document.getAnonymousElementByAttribute(this, "anonid", "name");
+
+ this._picture = document.getAnonymousElementByAttribute(this, "anonid", "picture");
+
+ this._adjustAcItem();
+ ]]>
+ </constructor>
+
+ <property name="label" readonly="true">
+ <getter>
+ <![CDATA[
+ var identity = this.row.item;
+ return identity.accessibleLabel;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="_adjustAcItem">
+ <body>
+ <![CDATA[
+ var identity = this.row.item;
+
+ if (identity == null)
+ return;
+
+ // I guess we should get the picture size from CSS or something?
+ this._picture.src = identity.pictureURL(32);
+
+ // Emphasize the matching search terms for the description
+ this._setUpDescription(this._name, identity.contact.name);
+ this._setUpDescription(this._identity, identity.value);
+
+ // Set up overflow on a timeout because the contents of the box
+ // might not have a width yet even though we just changed them
+ setTimeout(this._setUpOverflow, 0, this._nameBox, this._nameOverflowEllipsis);
+ setTimeout(this._setUpOverflow, 0, this._identityBox, this._identityOverflowEllipsis);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="gloda-contact-chunk" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
+ <content orient="horizontal">
+ <xul:image anonid="picture"/>
+ <xul:vbox>
+ <xul:hbox>
+ <xul:hbox anonid="name-box" class="ac-title" flex="1"
+ onunderflow="_doUnderflow('_name');">
+ <xul:description anonid="name" class="ac-normal-text ac-comment"
+ xbl:inherits="selected"/>
+ </xul:hbox>
+ <xul:label anonid="name-overflow-ellipsis" xbl:inherits="selected"
+ class="ac-ellipsis-after ac-comment" hidden="true"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:hbox anonid="identity-box" class="ac-url" flex="1"
+ onunderflow="_doUnderflow('_identity');">
+ <xul:description anonid="identity" class="ac-normal-text ac-url-text"
+ xbl:inherits="selected"/>
+ </xul:hbox>
+ <xul:label anonid="identity-overflow-ellipsis" xbl:inherits="selected"
+ class="ac-ellipsis-after ac-url-text" hidden="true"/>
+ <xul:image anonid="type-image" class="ac-type-icon"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <constructor>
+ <![CDATA[
+ let ellipsis = "\u2026";
+ try {
+ ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (ex) {
+ // Do nothing.. we already have a default
+ }
+
+ this._identityOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "identity-overflow-ellipsis");
+ this._nameOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "name-overflow-ellipsis");
+
+ this._identityOverflowEllipsis.value = ellipsis;
+ this._nameOverflowEllipsis.value = ellipsis;
+
+ this._typeImage = document.getAnonymousElementByAttribute(this, "anonid", "type-image");
+
+ this._identityBox = document.getAnonymousElementByAttribute(this, "anonid", "identity-box");
+ this._identity = document.getAnonymousElementByAttribute(this, "anonid", "identity");
+
+ this._nameBox = document.getAnonymousElementByAttribute(this, "anonid", "name-box");
+ this._name = document.getAnonymousElementByAttribute(this, "anonid", "name");
+
+ this._picture = document.getAnonymousElementByAttribute(this, "anonid", "picture");
+
+ this._adjustAcItem();
+ ]]>
+ </constructor>
+
+ <property name="label" readonly="true">
+ <getter>
+ <![CDATA[
+ var identity = this.obj;
+ return identity.accessibleLabel;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="_adjustAcItem">
+ <body>
+ <![CDATA[
+ var contact = this.obj;
+
+ if (contact == null)
+ return;
+
+ var identity = contact.identities[0];
+
+ // I guess we should get the picture size from CSS or something?
+ this._picture.src = identity.pictureURL(32);
+
+ // Emphasize the matching search terms for the description
+ this._setUpDescription(this._name, contact.name);
+ this._setUpDescription(this._identity, identity.value);
+
+ // Set up overflow on a timeout because the contents of the box
+ // might not have a width yet even though we just changed them
+ setTimeout(this._setUpOverflow, 0, this._nameBox, this._nameOverflowEllipsis);
+ setTimeout(this._setUpOverflow, 0, this._identityBox, this._identityOverflowEllipsis);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="gloda-multi-item" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
+ <content orient="vertical">
+ <xul:description anonid="explanation"/>
+ <xul:hbox anonid="identity-holder" flex="1">
+ </xul:hbox>
+ </content>
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <constructor>
+ <![CDATA[
+ this._explanation = document.getAnonymousElementByAttribute(this, "anonid", "explanation");
+ this._identityHolder = document.getAnonymousElementByAttribute(this, "anonid", "identity-holder");
+
+ this._adjustAcItem();
+ ]]>
+ </constructor>
+
+ <property name="label" readonly="true">
+ <getter>
+ <![CDATA[
+ return this._explanation.value;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="renderItem">
+ <parameter name="aObj"/>
+ <body>
+ var node = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "richlistitem");
+
+ node.obj = aObj;
+ node.setAttribute("type",
+ "gloda-" + this.row.nounDef.name + "-chunk");
+
+ this._identityHolder.appendChild(node);
+ </body>
+ </method>
+
+ <method name="_adjustAcItem">
+ <body>
+ <![CDATA[
+ // clear out any lingering children
+ while (this._identityHolder.hasChildNodes())
+ this._identityHolder.lastChild.remove();
+
+ var row = this.row;
+ if (row == null)
+ return;
+
+ this._explanation.value = row.nounDef.name + "s " +
+ row.criteriaType + "ed " + row.criteria;
+
+ // render anyone already in there
+ for (let item of row.collection.items) {
+ this.renderItem(item);
+ }
+ // listen up, yo.
+ row.renderer = this;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+
+</bindings>
diff --git a/mailnews/db/gloda/content/overlay.js b/mailnews/db/gloda/content/overlay.js
new file mode 100644
index 000000000..a4220be90
--- /dev/null
+++ b/mailnews/db/gloda/content/overlay.js
@@ -0,0 +1,6 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// get the core
+Components.utils.import("resource:///modules/gloda/public.js");
diff --git a/mailnews/db/gloda/content/thunderbirdOverlay.xul b/mailnews/db/gloda/content/thunderbirdOverlay.xul
new file mode 100644
index 000000000..1f2d65a30
--- /dev/null
+++ b/mailnews/db/gloda/content/thunderbirdOverlay.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<overlay id="gloda-overlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://gloda/content/overlay.js"/>
+</overlay>
diff --git a/mailnews/db/gloda/jar.mn b/mailnews/db/gloda/jar.mn
new file mode 100644
index 000000000..8e5cfe127
--- /dev/null
+++ b/mailnews/db/gloda/jar.mn
@@ -0,0 +1,11 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+gloda.jar:
+% content gloda %content/
+% overlay chrome://messenger/content/messenger.xul chrome://gloda/content/thunderbirdOverlay.xul application={3550f703-e582-4d05-9a08-453d09bdfdc6}
+ content/overlay.js (content/overlay.js)
+ content/thunderbirdOverlay.xul (content/thunderbirdOverlay.xul)
+ content/glodacomplete.css (content/glodacomplete.css)
+ content/glodacomplete.xml (content/glodacomplete.xml)
diff --git a/mailnews/db/gloda/modules/collection.js b/mailnews/db/gloda/modules/collection.js
new file mode 100644
index 000000000..466d7df1a
--- /dev/null
+++ b/mailnews/db/gloda/modules/collection.js
@@ -0,0 +1,772 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['GlodaCollection', 'GlodaCollectionManager'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+var LOG = Log4Moz.repository.getLogger("gloda.collection");
+
+/**
+ * @namespace Central registry and logic for all collections.
+ *
+ * The collection manager is a singleton that has the following tasks:
+ * - Let views of objects (nouns) know when their objects have changed. For
+ * example, an attribute has changed due to user action.
+ * - Let views of objects based on queries know when new objects match their
+ * query, or when their existing objects no longer match due to changes.
+ * - Caching/object-identity maintenance. It is ideal if we only ever have
+ * one instance of an object at a time. (More specifically, only one instance
+ * per database row 'id'.) The collection mechanism lets us find existing
+ * instances to this end. Caching can be directly integrated by being treated
+ * as a special collection.
+ */
+var GlodaCollectionManager = {
+ _collectionsByNoun: {},
+ _cachesByNoun: {},
+
+ /**
+ * Registers the existence of a collection with the collection manager. This
+ * is done using a weak reference so that the collection can go away if it
+ * wants to.
+ */
+ registerCollection: function gloda_colm_registerCollection(aCollection) {
+ let collections;
+ let nounID = aCollection.query._nounDef.id;
+ if (!(nounID in this._collectionsByNoun))
+ collections = this._collectionsByNoun[nounID] = [];
+ else {
+ // purge dead weak references while we're at it
+ collections = this._collectionsByNoun[nounID].filter(function (aRef) {
+ return aRef.get(); });
+ this._collectionsByNoun[nounID] = collections;
+ }
+ collections.push(Cu.getWeakReference(aCollection));
+ },
+
+ getCollectionsForNounID: function gloda_colm_getCollectionsForNounID(aNounID){
+ if (!(aNounID in this._collectionsByNoun))
+ return [];
+
+ // generator would be nice, but I suspect get() is too expensive to use
+ // twice (guard/predicate and value)
+ let weakCollections = this._collectionsByNoun[aNounID];
+ let collections = [];
+ for (let iColl = 0; iColl < weakCollections.length; iColl++) {
+ let collection = weakCollections[iColl].get();
+ if (collection)
+ collections.push(collection);
+ }
+ return collections;
+ },
+
+ defineCache: function gloda_colm_defineCache(aNounDef, aCacheSize) {
+ this._cachesByNoun[aNounDef.id] = new GlodaLRUCacheCollection(aNounDef,
+ aCacheSize);
+ },
+
+ /**
+ * Attempt to locate an instance of the object of the given noun type with the
+ * given id. Counts as a cache hit if found. (And if it was't in a cache,
+ * but rather a collection, it is added to the cache.)
+ */
+ cacheLookupOne: function gloda_colm_cacheLookupOne(aNounID, aID, aDoCache) {
+ let cache = this._cachesByNoun[aNounID];
+
+ if (cache) {
+ if (aID in cache._idMap) {
+ let item = cache._idMap[aID];
+ return cache.hit(item);
+ }
+ }
+
+ if (aDoCache === false)
+ cache = null;
+
+ for (let collection of this.getCollectionsForNounID(aNounID)) {
+ if (aID in collection._idMap) {
+ let item = collection._idMap[aID];
+ if (cache)
+ cache.add([item]);
+ return item;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Lookup multiple nouns by ID from the cache/existing collections.
+ *
+ * @param aNounID The kind of noun identified by its ID.
+ * @param aIDMap A dictionary/map whose keys must be gloda noun ids for the
+ * given noun type and whose values are ignored.
+ * @param aTargetMap An object to hold the noun id's (key) and noun instances
+ * (value) for the noun instances that were found available in memory
+ * because they were cached or in existing query collections.
+ * @param [aDoCache=true] Should we add any items to the cache that we found
+ * in collections that were in memory but not in the cache? You would
+ * likely want to pass false if you are only updating in-memory
+ * representations rather than performing a new query.
+ *
+ * @return [The number that were found, the number that were not found,
+ * a dictionary whose keys are the ids of noun instances that
+ * were not found.]
+ */
+ cacheLookupMany: function gloda_colm_cacheLookupMany(aNounID, aIDMap,
+ aTargetMap, aDoCache) {
+ let foundCount = 0, notFoundCount = 0, notFound = {};
+
+ let cache = this._cachesByNoun[aNounID];
+
+ if (cache) {
+ for (let key in aIDMap) {
+ let cacheValue = cache._idMap[key];
+ if (cacheValue === undefined) {
+ notFoundCount++;
+ notFound[key] = null;
+ }
+ else {
+ foundCount++;
+ aTargetMap[key] = cacheValue;
+ cache.hit(cacheValue);
+ }
+ }
+ }
+
+ if (aDoCache === false)
+ cache = null;
+
+ for (let collection of this.getCollectionsForNounID(aNounID)) {
+ for (let key in notFound) {
+ let collValue = collection._idMap[key];
+ if (collValue !== undefined) {
+ aTargetMap[key] = collValue;
+ delete notFound[key];
+ foundCount++;
+ notFoundCount--;
+ if (cache)
+ cache.add([collValue]);
+ }
+ }
+ }
+
+ return [foundCount, notFoundCount, notFound];
+ },
+
+ /**
+ * Friendlier version of |cacheLookupMany|; takes a list of ids and returns
+ * an object whose keys and values are the gloda id's and instances of the
+ * instances that were found. We don't tell you who we didn't find. The
+ * assumption is this is being used for in-memory updates where we only need
+ * to tweak what is in memory.
+ */
+ cacheLookupManyList: function gloda_colm_cacheLookupManyList(aNounID, aIds) {
+ let checkMap = {}, targetMap = {};
+ for (let id of aIds) {
+ checkMap[id] = null;
+ }
+ // do not promote found items into the cache
+ this.cacheLookupMany(aNounID, checkMap, targetMap, false);
+ return targetMap;
+ },
+
+ /**
+ * Attempt to locate an instance of the object of the given noun type with the
+ * given id. Counts as a cache hit if found. (And if it was't in a cache,
+ * but rather a collection, it is added to the cache.)
+ */
+ cacheLookupOneByUniqueValue:
+ function gloda_colm_cacheLookupOneByUniqueValue(aNounID, aUniqueValue,
+ aDoCache) {
+ let cache = this._cachesByNoun[aNounID];
+
+ if (cache) {
+ if (aUniqueValue in cache._uniqueValueMap) {
+ let item = cache._uniqueValueMap[aUniqueValue];
+ return cache.hit(item);
+ }
+ }
+
+ if (aDoCache === false)
+ cache = null;
+
+ for (let collection of this.getCollectionsForNounID(aNounID)) {
+ if (aUniqueValue in collection._uniqueValueMap) {
+ let item = collection._uniqueValueMap[aUniqueValue];
+ if (cache)
+ cache.add([item]);
+ return item;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Checks whether the provided item with the given id is actually a duplicate
+ * of an instance that already exists in the cache/a collection. If it is,
+ * the pre-existing instance is returned and counts as a cache hit. If it
+ * is not, the passed-in instance is added to the cache and returned.
+ */
+ cacheLoadUnifyOne: function gloda_colm_cacheLoadUnifyOne(aItem) {
+ let items = [aItem];
+ this.cacheLoadUnify(aItem.NOUN_ID, items);
+ return items[0];
+ },
+
+ /**
+ * Given a list of items, check if any of them already have duplicate,
+ * canonical, instances in the cache or collections. Items with pre-existing
+ * instances are replaced by those instances in the provided list, and each
+ * counts as a cache hit. Items without pre-existing instances are added
+ * to the cache and left intact.
+ */
+ cacheLoadUnify: function gloda_colm_cacheLoadUnify(aNounID, aItems,
+ aCacheIfMissing) {
+ let cache = this._cachesByNoun[aNounID];
+ if (aCacheIfMissing === undefined)
+ aCacheIfMissing = true;
+
+ // track the items we haven't yet found in a cache/collection (value) and
+ // their index in aItems (key). We're somewhat abusing the dictionary
+ // metaphor with the intent of storing tuples here. We also do it because
+ // it allows random-access deletion theoretically without cost. (Since
+ // we delete during iteration, that may be wrong, but it sounds like the
+ // semantics still work?)
+ let unresolvedIndexToItem = {};
+ let numUnresolved = 0;
+
+ if (cache) {
+ for (let iItem = 0; iItem < aItems.length; iItem++) {
+ let item = aItems[iItem];
+
+ if (item.id in cache._idMap) {
+ let realItem = cache._idMap[item.id];
+ // update the caller's array with the reference to the 'real' item
+ aItems[iItem] = realItem;
+ cache.hit(realItem);
+ }
+ else {
+ unresolvedIndexToItem[iItem] = item;
+ numUnresolved++;
+ }
+ }
+
+ // we're done if everyone was a hit.
+ if (numUnresolved == 0)
+ return;
+ }
+ else {
+ for (let iItem = 0; iItem < aItems.length; iItem++) {
+ unresolvedIndexToItem[iItem] = aItems[iItem];
+ }
+ numUnresolved = aItems.length;
+ }
+
+ let needToCache = [];
+ // next, let's fall back to our collections
+ for (let collection of this.getCollectionsForNounID(aNounID)) {
+ for (let [iItem, item] in Iterator(unresolvedIndexToItem)) {
+ if (item.id in collection._idMap) {
+ let realItem = collection._idMap[item.id];
+ // update the caller's array to now have the 'real' object
+ aItems[iItem] = realItem;
+ // flag that we need to cache this guy (we use an inclusive cache)
+ needToCache.push(realItem);
+ // we no longer need to resolve this item...
+ delete unresolvedIndexToItem[iItem];
+ // stop checking collections if we got everybody
+ if (--numUnresolved == 0)
+ break;
+ }
+ }
+ }
+
+ // anything left in unresolvedIndexToItem should be added to the cache
+ // unless !aCacheIfMissing. plus, we already have 'needToCache'
+ if (cache && aCacheIfMissing) {
+ cache.add(needToCache.concat(Object.keys(unresolvedIndexToItem).
+ map(key => unresolvedIndexToItem[key])));
+ }
+
+ return aItems;
+ },
+
+ cacheCommitDirty: function glod_colm_cacheCommitDirty() {
+ for (let id in this._cachesByNoun) {
+ let cache = this._cachesByNoun[id];
+ cache.commitDirty();
+ }
+ },
+
+ /**
+ * Notifies the collection manager that an item has been loaded and should
+ * be cached, assuming caching is active.
+ */
+ itemLoaded: function gloda_colm_itemsLoaded(aItem) {
+ let cache = this._cachesByNoun[aItem.NOUN_ID];
+ if (cache) {
+ cache.add([aItem]);
+ }
+ },
+
+ /**
+ * Notifies the collection manager that multiple items has been loaded and
+ * should be cached, assuming caching is active.
+ */
+ itemsLoaded: function gloda_colm_itemsLoaded(aNounID, aItems) {
+ let cache = this._cachesByNoun[aNounID];
+ if (cache) {
+ cache.add(aItems);
+ }
+ },
+
+ /**
+ * This should be called when items are added to the global database. This
+ * should generally mean during indexing by indexers or an attribute
+ * provider.
+ * We walk all existing collections for the given noun type and add the items
+ * to the collection if the item meets the query that defines the collection.
+ */
+ itemsAdded: function gloda_colm_itemsAdded(aNounID, aItems) {
+ let cache = this._cachesByNoun[aNounID];
+ if (cache) {
+ cache.add(aItems);
+ }
+
+ for (let collection of this.getCollectionsForNounID(aNounID)) {
+ let addItems = aItems.filter(item => collection.query.test(item));
+ if (addItems.length)
+ collection._onItemsAdded(addItems);
+ }
+ },
+ /**
+ * This should be called when items in the global database are modified. For
+ * example, as a result of indexing. This should generally only be called
+ * by indexers or by attribute providers.
+ * We walk all existing collections for the given noun type. For items
+ * currently included in each collection but should no longer be (per the
+ * collection's defining query) we generate onItemsRemoved events. For items
+ * not currently included in the collection but should now be, we generate
+ * onItemsAdded events. For items included that still match the query, we
+ * generate onItemsModified events.
+ */
+ itemsModified: function gloda_colm_itemsModified(aNounID, aItems) {
+ for (let collection of this.getCollectionsForNounID(aNounID)) {
+ let added = [], modified = [], removed = [];
+ for (let item of aItems) {
+ if (item.id in collection._idMap) {
+ // currently in... but should it still be there?
+ if (collection.query.test(item))
+ modified.push(item); // yes, keep it
+ // oy, so null queries really don't want any notifications, and they
+ // sorta fit into our existing model, except for the removal bit.
+ // so we need a specialized check for them, and we're using the
+ // frozen attribute to this end.
+ else if (!collection.query.frozen)
+ removed.push(item); // no, bin it
+ }
+ else if (collection.query.test(item)) // not in, should it be?
+ added.push(item); // yep, add it
+ }
+ if (added.length)
+ collection._onItemsAdded(added);
+ if (modified.length)
+ collection._onItemsModified(modified);
+ if (removed.length)
+ collection._onItemsRemoved(removed);
+ }
+ },
+ /**
+ * This should be called when items in the global database are permanently-ish
+ * deleted. (This is distinct from concepts like message deletion which may
+ * involved trash folders or other modified forms of existence. Deleted
+ * means the data is gone and if it were to come back, it would come back
+ * via an itemsAdded event.)
+ * We walk all existing collections for the given noun type. For items
+ * currently in the collection, we generate onItemsRemoved events.
+ *
+ * @param aItemIds A list of item ids that are being deleted.
+ */
+ itemsDeleted: function gloda_colm_itemsDeleted(aNounID, aItemIds) {
+ // cache
+ let cache = this._cachesByNoun[aNounID];
+ if (cache) {
+ for (let itemId of aItemIds) {
+ if (itemId in cache._idMap)
+ cache.deleted(cache._idMap[itemId]);
+ }
+ }
+
+ // collections
+ for (let collection of this.getCollectionsForNounID(aNounID)) {
+ let removeItems = aItemIds.filter(itemId => itemId in collection._idMap).
+ map(itemId => collection._idMap[itemId]);
+ if (removeItems.length)
+ collection._onItemsRemoved(removeItems);
+ }
+ },
+ /**
+ * Like |itemsDeleted| but for the case where the deletion is based on an
+ * attribute that SQLite can more efficiently check than we can and where the
+ * cost of scanning the in-memory items is presumably much cheaper than
+ * trying to figure out what actually got deleted.
+ *
+ * Since we are doing an in-memory walk, this is obviously O(n) where n is the
+ * number of noun instances of a given type in-memory. We are assuming this
+ * is a reasonable number of things and that this type of deletion call is
+ * not going to happen all that frequently. If these assumptions are wrong,
+ * callers are advised to re-think the whole situation.
+ *
+ * @param aNounID Type of noun we are talking about here.
+ * @param aFilter A filter function that returns true when the item should be
+ * thought of as deleted, or false if the item is still good. Screw this
+ * up and you will get some seriously wacky bugs, yo.
+ */
+ itemsDeletedByAttribute: function gloda_colm_itemsDeletedByAttribute(
+ aNounID, aFilter) {
+ // cache
+ let cache = this._cachesByNoun[aNounID];
+ if (cache) {
+ for (let id in cache._idMap) {
+ let item = cache._idMap[id];
+ if (aFilter(item))
+ cache.deleted(item);
+ }
+ }
+
+ // collections
+ for (let collection of this.getCollectionsForNounID(aNounID)) {
+ let removeItems = collection.items.filter(aFilter);
+ if (removeItems.length)
+ collection._onItemsRemoved(removeItems);
+ }
+ },
+};
+
+/**
+ * @class A current view of the set of first-class nouns meeting a given query.
+ * Assuming a listener is present, events are
+ * generated when new objects meet the query, existing objects no longer meet
+ * the query, or existing objects have experienced a change in attributes that
+ * does not affect their ability to be present (but the listener may care about
+ * because it is exposing those attributes).
+ * @constructor
+ */
+function GlodaCollection(aNounDef, aItems, aQuery, aListener,
+ aMasterCollection) {
+ // if aNounDef is null, we are just being invoked for subclassing
+ if (aNounDef === undefined)
+ return;
+
+ this._nounDef = aNounDef;
+ // should we also maintain a unique value mapping...
+ if (this._nounDef.usesUniqueValue)
+ this._uniqueValueMap = {};
+
+ this.pendingItems = [];
+ this._pendingIdMap = {};
+ this.items = [];
+ this._idMap = {};
+
+ // force the listener to null for our call to _onItemsAdded; no events for
+ // the initial load-out.
+ this._listener = null;
+ if (aItems && aItems.length)
+ this._onItemsAdded(aItems);
+
+ this.query = aQuery || null;
+ if (this.query) {
+ this.query.collection = this;
+ if (this.query.options.stashColumns)
+ this.stashedColumns = {};
+ }
+ this._listener = aListener || null;
+
+ this.deferredCount = 0;
+ this.resolvedCount = 0;
+
+ if (aMasterCollection) {
+ this.masterCollection = aMasterCollection.masterCollection;
+ }
+ else {
+ this.masterCollection = this;
+ /** a dictionary of dictionaries. at the top level, the keys are noun IDs.
+ * each of these sub-dictionaries maps the IDs of desired noun instances to
+ * the actual instance, or null if it has not yet been loaded.
+ */
+ this.referencesByNounID = {};
+ /**
+ * a dictionary of dictionaries. at the top level, the keys are noun IDs.
+ * each of the sub-dictionaries maps the IDs of the _recognized parent
+ * noun_ to the list of children, or null if the list has not yet been
+ * populated.
+ *
+ * So if we have a noun definition A with ID 1 who is the recognized parent
+ * noun of noun definition B with ID 2, AND we have an instance A(1) with
+ * two children B(10), B(11), then an example might be: {2: {1: [10, 11]}}.
+ */
+ this.inverseReferencesByNounID = {};
+ this.subCollections = {};
+ }
+}
+
+GlodaCollection.prototype = {
+ get listener() { return this._listener; },
+ set listener(aListener) { this._listener = aListener; },
+
+ /**
+ * If this collection still has a query associated with it, drop the query
+ * and replace it with an 'explicit query'. This means that the Collection
+ * Manager will not attempt to match new items indexed to the system against
+ * our query criteria.
+ * Once you call this method, your collection's listener will no longer
+ * receive onItemsAdded notifications that are not the result of your
+ * initial database query. It will, however, receive onItemsModified
+ * notifications if items in the collection are re-indexed.
+ */
+ becomeExplicit: function gloda_coll_becomeExplicit() {
+ if (!(this.query instanceof this._nounDef.explicitQueryClass)) {
+ this.query = new this._nounDef.explicitQueryClass(this);
+ }
+ },
+
+ /**
+ * Clear the contents of this collection. This only makes sense for explicit
+ * collections or wildcard collections. (Actual query-based collections
+ * should represent the state of the query, so unless we're going to delete
+ * all the items, clearing the collection would violate that constraint.)
+ */
+ clear: function gloda_coll_clear() {
+ this._idMap = {};
+ if (this._uniqueValueMap)
+ this._uniqueValueMap = {};
+ this.items = [];
+ },
+
+ _onItemsAdded: function gloda_coll_onItemsAdded(aItems) {
+ this.items.push.apply(this.items, aItems);
+ if (this._uniqueValueMap) {
+ for (let item of this.items) {
+ this._idMap[item.id] = item;
+ this._uniqueValueMap[item.uniqueValue] = item;
+ }
+ }
+ else {
+ for (let item of this.items) {
+ this._idMap[item.id] = item;
+ }
+ }
+ if (this._listener) {
+ try {
+ this._listener.onItemsAdded(aItems, this);
+ }
+ catch (ex) {
+ LOG.error("caught exception from listener in onItemsAdded: " +
+ ex.fileName + ":" + ex.lineNumber + ": " + ex);
+ }
+ }
+ },
+
+ _onItemsModified: function gloda_coll_onItemsModified(aItems) {
+ if (this._listener) {
+ try {
+ this._listener.onItemsModified(aItems, this);
+ }
+ catch (ex) {
+ LOG.error("caught exception from listener in onItemsModified: " +
+ ex.fileName + ":" + ex.lineNumber + ": " + ex);
+ }
+ }
+ },
+
+ /**
+ * Given a list of items that definitely no longer belong in this collection,
+ * remove them from the collection and notify the listener. The 'tricky'
+ * part is that we need to remove the deleted items from our list of items.
+ */
+ _onItemsRemoved: function gloda_coll_onItemsRemoved(aItems) {
+ // we want to avoid the O(n^2) deletion performance case, and deletion
+ // should be rare enough that the extra cost of building the deletion map
+ // should never be a real problem.
+ let deleteMap = {};
+ // build the delete map while also nuking from our id map/unique value map
+ for (let item of aItems) {
+ deleteMap[item.id] = true;
+ delete this._idMap[item.id];
+ if (this._uniqueValueMap)
+ delete this._uniqueValueMap[item.uniqueValue];
+ }
+ let items = this.items;
+ // in-place filter. probably needless optimization.
+ let iWrite=0;
+ for (let iRead = 0; iRead < items.length; iRead++) {
+ let item = items[iRead];
+ if (!(item.id in deleteMap))
+ items[iWrite++] = item;
+ }
+ items.splice(iWrite);
+
+ if (this._listener) {
+ try {
+ this._listener.onItemsRemoved(aItems, this);
+ }
+ catch (ex) {
+ LOG.error("caught exception from listener in onItemsRemoved: " +
+ ex.fileName + ":" + ex.lineNumber + ": " + ex);
+ }
+ }
+ },
+
+ _onQueryCompleted: function gloda_coll_onQueryCompleted() {
+ this.query.completed = true;
+ if (this._listener && this._listener.onQueryCompleted)
+ this._listener.onQueryCompleted(this);
+ }
+};
+
+/**
+ * Create an LRU cache collection for the given noun with the given size.
+ * @constructor
+ */
+function GlodaLRUCacheCollection(aNounDef, aCacheSize) {
+ GlodaCollection.call(this, aNounDef, null, null, null);
+
+ this._head = null; // aka oldest!
+ this._tail = null; // aka newest!
+ this._size = 0;
+ // let's keep things sane, and simplify our logic a little...
+ if (aCacheSize < 32)
+ aCacheSize = 32;
+ this._maxCacheSize = aCacheSize;
+}
+/**
+ * @class A LRU-discard cache. We use a doubly linked-list for the eviction
+ * tracking. Since we require that there is at most one LRU-discard cache per
+ * noun class, we simplify our lives by adding our own attributes to the
+ * cached objects.
+ * @augments GlodaCollection
+ */
+GlodaLRUCacheCollection.prototype = new GlodaCollection;
+GlodaLRUCacheCollection.prototype.add = function cache_add(aItems) {
+ for (let item of aItems) {
+ if (item.id in this._idMap) {
+ // DEBUGME so, we're dealing with this, but it shouldn't happen. need
+ // trace-debuggage.
+ continue;
+ }
+ this._idMap[item.id] = item;
+ if (this._uniqueValueMap)
+ this._uniqueValueMap[item.uniqueValue] = item;
+
+ item._lruPrev = this._tail;
+ // we do have to make sure that we will set _head the first time we insert
+ // something
+ if (this._tail !== null)
+ this._tail._lruNext = item;
+ else
+ this._head = item;
+ item._lruNext = null;
+ this._tail = item;
+
+ this._size++;
+ }
+
+ while (this._size > this._maxCacheSize) {
+ let item = this._head;
+
+ // we never have to deal with the possibility of needing to make _head/_tail
+ // null.
+ this._head = item._lruNext;
+ this._head._lruPrev = null;
+ // (because we are nice, we will delete the properties...)
+ delete item._lruNext;
+ delete item._lruPrev;
+
+ // nuke from our id map
+ delete this._idMap[item.id];
+ if (this._uniqueValueMap)
+ delete this._uniqueValueMap[item.uniqueValue];
+
+ // flush dirty items to disk (they may not have this attribute, in which
+ // case, this returns false, which is fine.)
+ if (item.dirty) {
+ this._nounDef.objUpdate.call(this._nounDef.datastore, item);
+ delete item.dirty;
+ }
+
+ this._size--;
+ }
+};
+
+GlodaLRUCacheCollection.prototype.hit = function cache_hit(aItem) {
+ // don't do anything in the 0 or 1 items case, or if we're already
+ // the last item
+ if ((this._head === this._tail) || (this._tail === aItem))
+ return aItem;
+
+ // - unlink the item
+ if (aItem._lruPrev !== null)
+ aItem._lruPrev._lruNext = aItem._lruNext;
+ else
+ this._head = aItem._lruNext;
+ // (_lruNext cannot be null)
+ aItem._lruNext._lruPrev = aItem._lruPrev;
+ // - link it in to the end
+ this._tail._lruNext = aItem;
+ aItem._lruPrev = this._tail;
+ aItem._lruNext = null;
+ // update tail tracking
+ this._tail = aItem;
+
+ return aItem;
+};
+
+GlodaLRUCacheCollection.prototype.deleted = function cache_deleted(aItem) {
+ // unlink the item
+ if (aItem._lruPrev !== null)
+ aItem._lruPrev._lruNext = aItem._lruNext;
+ else
+ this._head = aItem._lruNext;
+ if (aItem._lruNext !== null)
+ aItem._lruNext._lruPrev = aItem._lruPrev;
+ else
+ this._tail = aItem._lruPrev;
+
+ // (because we are nice, we will delete the properties...)
+ delete aItem._lruNext;
+ delete aItem._lruPrev;
+
+ // nuke from our id map
+ delete this._idMap[aItem.id];
+ if (this._uniqueValueMap)
+ delete this._uniqueValueMap[aItem.uniqueValue];
+
+ this._size--;
+};
+
+/**
+ * If any of the cached items are dirty, commit them, and make them no longer
+ * dirty.
+ */
+GlodaLRUCacheCollection.prototype.commitDirty = function cache_commitDirty() {
+ // we can only do this if there is an update method available...
+ if (!this._nounDef.objUpdate)
+ return;
+
+ for (let iItem in this._idMap) {
+ let item = this._idMap[iItem];
+ if (item.dirty) {
+ LOG.debug("flushing dirty: " + item);
+ this._nounDef.objUpdate.call(this._nounDef.datastore, item);
+ delete item.dirty;
+ }
+ }
+};
diff --git a/mailnews/db/gloda/modules/connotent.js b/mailnews/db/gloda/modules/connotent.js
new file mode 100644
index 000000000..4ef424d43
--- /dev/null
+++ b/mailnews/db/gloda/modules/connotent.js
@@ -0,0 +1,273 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['GlodaContent', 'whittlerRegistry',
+ 'mimeMsgToContentAndMeta', 'mimeMsgToContentSnippetAndMeta'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+var LOG = Log4Moz.repository.getLogger("gloda.connotent");
+
+
+
+/**
+ * Given a MimeMsg and the corresponding folder, return the GlodaContent object.
+ *
+ * @param aMimeMsg: the MimeMessage instance
+ * @param folder: the nsIMsgDBFolder
+ * @return an array containing the GlodaContent instance, and the meta dictionary
+ * that the Gloda content providers may have filled with useful data.
+ */
+
+function mimeMsgToContentAndMeta(aMimeMsg, folder) {
+ let content = new GlodaContent();
+ let meta = {subject: aMimeMsg.get("subject")};
+ let bodyLines = aMimeMsg.coerceBodyToPlaintext(folder).split(/\r?\n/);
+
+ for (let whittler of whittlerRegistry.getWhittlers())
+ whittler.contentWhittle(meta, bodyLines, content);
+
+ return [content, meta];
+}
+
+
+/**
+ * Given a MimeMsg, return the whittled content string, suitable for summarizing
+ * a message.
+ *
+ * @param aMimeMsg: the MimeMessage instance
+ * @param folder: the nsIMsgDBFolder
+ * @param length: optional number of characters to trim the whittled content.
+ * If the actual length of the message is greater than |length|, then the return
+ * value is the first (length-1) characters with an ellipsis appended.
+ * @return an array containing the text of the snippet, and the meta dictionary
+ * that the Gloda content providers may have filled with useful data.
+ */
+
+function mimeMsgToContentSnippetAndMeta(aMimeMsg, folder, length) {
+ let [content, meta] = mimeMsgToContentAndMeta(aMimeMsg, folder);
+
+ let text = content.getContentSnippet(length + 1);
+ if (length && text.length > length)
+ text = text.substring(0, length-1) + "\u2026"; // ellipsis
+
+ return [text, meta];
+}
+
+
+/**
+ * A registry of gloda providers that have contentWhittle() functions.
+ * used by mimeMsgToContentSnippet, but populated by the Gloda object as it's
+ * processing providers.
+ */
+function WhittlerRegistry() {
+ this._whittlers = [];
+}
+
+WhittlerRegistry.prototype = {
+ /**
+ * Add a provider as a content whittler.
+ */
+ registerWhittler: function whittler_registry_registerWhittler(provider) {
+ this._whittlers.push(provider);
+ },
+ /**
+ * get the list of content whittlers, sorted from the most specific to
+ * the most generic
+ */
+ getWhittlers: function whittler_registry_getWhittlers() {
+ // Use the concat() trick to avoid mutating the internal object and
+ // leaking an internal representation.
+ return this._whittlers.concat().reverse();
+ }
+}
+
+this.whittlerRegistry = new WhittlerRegistry();
+
+function GlodaContent() {
+ this._contentPriority = null;
+ this._producing = false;
+ this._hunks = [];
+}
+
+GlodaContent.prototype = {
+ kPriorityBase: 0,
+ kPriorityPerfect: 100,
+
+ kHunkMeta: 1,
+ kHunkQuoted: 2,
+ kHunkContent: 3,
+
+ _resetContent: function gloda_content__resetContent() {
+ this._keysAndValues = [];
+ this._keysAndDeltaValues = [];
+ this._hunks = [];
+ this._curHunk = null;
+ },
+
+ /* ===== Consumer API ===== */
+ hasContent: function gloda_content_hasContent() {
+ return (this._contentPriority != null);
+ },
+
+ /**
+ * Return content suitable for snippet display. This means that no quoting
+ * or meta-data should be returned.
+ *
+ * @param aMaxLength The maximum snippet length desired.
+ */
+ getContentSnippet: function gloda_content_getContentSnippet(aMaxLength) {
+ let content = this.getContentString();
+ if (aMaxLength)
+ content = content.substring(0, aMaxLength);
+ return content;
+ },
+
+ getContentString: function gloda_content_getContent(aIndexingPurposes) {
+ let data = "";
+ for (let hunk of this._hunks) {
+ if (hunk.hunkType == this.kHunkContent) {
+ if (data)
+ data += "\n" + hunk.data;
+ else
+ data = hunk.data;
+ }
+ }
+
+ if (aIndexingPurposes) {
+ // append the values for indexing. we assume the keywords are cruft.
+ // this may be crazy, but things that aren't a science aren't an exact
+ // science.
+ for (let kv of this._keysAndValues) {
+ data += "\n" + kv[1];
+ }
+ for (let kon of this._keysAndValues) {
+ data += "\n" + kon[1] + "\n" + kon[2];
+ }
+ }
+
+ return data;
+ },
+
+ /* ===== Producer API ===== */
+ /**
+ * Called by a producer with the priority they believe their interpretation
+ * of the content comes in at.
+ *
+ * @returns true if we believe the producer's interpretation will be
+ * interesting and they should go ahead and generate events. We return
+ * false if we don't think they are interesting, in which case they should
+ * probably not issue calls to us, although we don't care. (We will
+ * ignore their calls if we return false, this allows the simplification
+ * of code that needs to run anyways.)
+ */
+ volunteerContent: function gloda_content_volunteerContent(aPriority) {
+ if (this._contentPriority === null || this._contentPriority < aPriority) {
+ this._contentPriority = aPriority;
+ this._resetContent();
+ this._producing = true;
+ return true;
+ }
+ this._producing = false;
+ return false;
+ },
+
+ keyValue: function gloda_content_keyValue(aKey, aValue) {
+ if (!this._producing)
+ return;
+
+ this._keysAndValues.push([aKey, aValue]);
+ },
+ keyValueDelta: function gloda_content_keyValueDelta (aKey, aOldValue,
+ aNewValue) {
+ if (!this._producing)
+ return;
+
+ this._keysAndDeltaValues.push([aKey, aOldValue, aNewValue]);
+ },
+
+ /**
+ * Meta lines are lines that have to do with the content but are not the
+ * content and can generally be related to an attribute that has been derived
+ * and stored on the item.
+ * For example, a bugzilla bug may note that an attachment was created; this
+ * is not content and wouldn't be desired in a snippet, but is still
+ * potentially interesting meta-data.
+ *
+ * @param aLineOrLines The line or list of lines that are meta-data.
+ * @param aAttr The attribute this meta-data is associated with.
+ * @param aIndex If the attribute is non-singular, indicate the specific
+ * index of the item in the attribute's bound list that the meta-data
+ * is associated with.
+ */
+ meta: function gloda_content_meta(aLineOrLines, aAttr, aIndex) {
+ if (!this._producing)
+ return;
+
+ let data;
+ if (typeof(aLineOrLines) == "string")
+ data = aLineOrLines;
+ else
+ data = aLineOrLines.join("\n");
+
+ this._curHunk = {hunkType: this.kHunkMeta, attr: aAttr, index: aIndex,
+ data: data};
+ this._hunks.push(this._curHunk);
+ },
+ /**
+ * Quoted lines reference previous messages or what not.
+ *
+ * @param aLineOrLiens The line or list of lines that are quoted.
+ * @param aDepth The depth of the quoting.
+ * @param aOrigin The item that originated the original content, if known.
+ * For example, perhaps a GlodaMessage?
+ * @param aTarget A reference to the location in the original content, if
+ * known. For example, the index of a line in a message or something?
+ */
+ quoted: function gloda_content_quoted(aLineOrLines, aDepth, aOrigin,
+ aTarget) {
+ if (!this._producing)
+ return;
+
+ let data;
+ if (typeof(aLineOrLines) == "string")
+ data = aLineOrLines;
+ else
+ data = aLineOrLines.join("\n");
+
+ if (!this._curHunk ||
+ this._curHunk.hunkType != this.kHunkQuoted ||
+ this._curHunk.depth != aDepth ||
+ this._curHunk.origin != aOrigin || this._curHunk.target != aTarget) {
+ this._curHunk = {hunkType: this.kHunkQuoted, data: data,
+ depth: aDepth, origin: aOrigin, target: aTarget};
+ this._hunks.push(this._curHunk);
+ }
+ else
+ this._curHunk.data += "\n" + data;
+ },
+
+ content: function gloda_content_content(aLineOrLines) {
+ if (!this._producing)
+ return;
+
+ let data;
+ if (typeof(aLineOrLines) == "string")
+ data = aLineOrLines;
+ else
+ data = aLineOrLines.join("\n");
+
+ if (!this._curHunk || this._curHunk.hunkType != this.kHunkContent) {
+ this._curHunk = {hunkType: this.kHunkContent, data: data};
+ this._hunks.push(this._curHunk);
+ }
+ else
+ this._curHunk.data += "\n" + data;
+ },
+};
diff --git a/mailnews/db/gloda/modules/databind.js b/mailnews/db/gloda/modules/databind.js
new file mode 100644
index 000000000..a2ecf1773
--- /dev/null
+++ b/mailnews/db/gloda/modules/databind.js
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["GlodaDatabind"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+var DBC_LOG = Log4Moz.repository.getLogger("gloda.ds.dbc");
+
+function GlodaDatabind(aNounDef, aDatastore) {
+ this._nounDef = aNounDef;
+ this._tableName = aNounDef.tableName;
+ this._tableDef = aNounDef.schema;
+ this._datastore = aDatastore;
+ this._log = Log4Moz.repository.getLogger("gloda.databind." + this._tableName);
+
+ // process the column definitions and make sure they have an attribute mapping
+ for (let [iColDef, coldef] of this._tableDef.columns.entries()) {
+ // default to the other dude's thing.
+ if (coldef.length < 3)
+ coldef[2] = coldef[0];
+ if (coldef[0] == "id")
+ this._idAttr = coldef[2];
+ // colDef[3] is the index of us in our SQL bindings, storage-numbering
+ coldef[3] = iColDef;
+ }
+
+ // XXX This is obviously synchronous and not perfectly async. Since we are
+ // doing this, we don't actually need to move to ordinal binding below
+ // since we could just as well compel creation of the name map and thereby
+ // avoid ever acquiring the mutex after bootstrap.
+ // However, this specific check can be cleverly avoided with future work.
+ // Namely, at startup we can scan for extension-defined tables and get their
+ // maximum id so that we don't need to do it here. The table will either
+ // be brand new and thus have a maximum id of 1 or we will already know it
+ // because of that scan.
+ this._nextId = 1;
+ let stmt = this._datastore._createSyncStatement(
+ "SELECT MAX(id) FROM " + this._tableName, true);
+ if (stmt.executeStep()) { // no chance of this SQLITE_BUSY on this call
+ this._nextId = stmt.getInt64(0) + 1;
+ }
+ stmt.finalize();
+
+ let insertColumns = [];
+ let insertValues = [];
+ let updateItems = [];
+ for (let [iColDef, coldef] of this._tableDef.columns.entries()) {
+ let column = coldef[0];
+ let placeholder = "?" + (iColDef + 1);
+ insertColumns.push(column);
+ insertValues.push(placeholder);
+ if (column != "id") {
+ updateItems.push(column + " = " + placeholder);
+ }
+ }
+
+ let insertSql = "INSERT INTO " + this._tableName + " (" +
+ insertColumns.join(", ") + ") VALUES (" + insertValues.join(", ") + ")";
+
+ // For the update, we want the 'id' to be a constraint and not a value
+ // that gets set...
+ let updateSql = "UPDATE " + this._tableName + " SET " +
+ updateItems.join(", ") + " WHERE id = ?1";
+ this._insertStmt = aDatastore._createAsyncStatement(insertSql);
+ this._updateStmt = aDatastore._createAsyncStatement(updateSql);
+
+ if (this._tableDef.fulltextColumns) {
+ for (let [iColDef, coldef] of this._tableDef.fulltextColumns.entries()) {
+ if (coldef.length < 3)
+ coldef[2] = coldef[0];
+ // colDef[3] is the index of us in our SQL bindings, storage-numbering
+ coldef[3] = iColDef + 1;
+ }
+
+ let insertColumns = [];
+ let insertValues = [];
+ let updateItems = [];
+ for (var [iColDef, coldef] of this._tableDef.fulltextColumns.entries()) {
+ let column = coldef[0];
+ // +2 instead of +1 because docid is implied
+ let placeholder = "?" + (iColDef + 2);
+ insertColumns.push(column);
+ insertValues.push(placeholder);
+ if (column != "id") {
+ updateItems.push(column + " = " + placeholder);
+ }
+ }
+
+ let insertFulltextSql = "INSERT INTO " + this._tableName + "Text (docid," +
+ insertColumns.join(", ") + ") VALUES (?1," + insertValues.join(", ") +
+ ")";
+
+ // For the update, we want the 'id' to be a constraint and not a value
+ // that gets set...
+ let updateFulltextSql = "UPDATE " + this._tableName + "Text SET " +
+ updateItems.join(", ") + " WHERE docid = ?1";
+
+ this._insertFulltextStmt =
+ aDatastore._createAsyncStatement(insertFulltextSql);
+ this._updateFulltextStmt =
+ aDatastore._createAsyncStatement(updateFulltextSql);
+ }
+}
+
+GlodaDatabind.prototype = {
+ /**
+ * Perform appropriate binding coercion based on the schema provided to us.
+ * Although we end up effectively coercing JS Date objects to numeric values,
+ * we should not be provided with JS Date objects! There is no way for us
+ * to know to turn them back into JS Date objects on the way out.
+ * Additionally, there is the small matter of storage's bias towards
+ * PRTime representations which may not always be desirable.
+ */
+ bindByType: function(aStmt, aColDef, aValue) {
+ if (aValue == null)
+ aStmt.bindNullParameter(aColDef[3]);
+ else if (aColDef[1] == "STRING" || aColDef[1] == "TEXT")
+ aStmt.bindStringParameter(aColDef[3], aValue);
+ else
+ aStmt.bindInt64Parameter(aColDef[3], aValue);
+ },
+
+ objFromRow: function(aRow) {
+ let getVariant = this._datastore._getVariant;
+ let obj = new this._nounDef.class();
+ for (let [iCol, colDef] of this._tableDef.columns.entries()) {
+ obj[colDef[2]] = getVariant(aRow, iCol);
+ }
+ return obj;
+ },
+
+ objInsert: function(aThing) {
+ let bindByType = this.bindByType;
+ if (!aThing[this._idAttr])
+ aThing[this._idAttr] = this._nextId++;
+
+ let stmt = this._insertStmt;
+ for (let colDef of this._tableDef.columns) {
+ bindByType(stmt, colDef, aThing[colDef[2]]);
+ }
+
+ stmt.executeAsync(this._datastore.trackAsync());
+
+ if (this._insertFulltextStmt) {
+ stmt = this._insertFulltextStmt;
+ stmt.bindInt64Parameter(0, aThing[this._idAttr]);
+ for (let colDef of this._tableDef.fulltextColumns) {
+ bindByType(stmt, colDef, aThing[colDef[2]]);
+ }
+ stmt.executeAsync(this._datastore.trackAsync());
+ }
+ },
+
+ objUpdate: function(aThing) {
+ let bindByType = this.bindByType;
+ let stmt = this._updateStmt;
+ // note, we specially bound the location of 'id' for the insert, but since
+ // we're using named bindings, there is nothing special about setting it
+ for (let colDef of this._tableDef.columns) {
+ bindByType(stmt, colDef, aThing[colDef[2]]);
+ }
+ stmt.executeAsync(this._datastore.trackAsync());
+
+ if (this._updateFulltextStmt) {
+ stmt = this._updateFulltextStmt;
+ // fulltextColumns doesn't include id/docid, need to explicitly set it
+ stmt.bindInt64Parameter(0, aThing[this._idAttr]);
+ for (let colDef of this._tableDef.fulltextColumns) {
+ bindByType(stmt, colDef, aThing[colDef[2]]);
+ }
+ stmt.executeAsync(this._datastore.trackAsync());
+ }
+ },
+
+ adjustAttributes: function() {
+ // just proxy the call over to the datastore... we have to do this for
+ // 'this' reasons. we don't refactor things to avoid this because it does
+ // make some sense to have all the methods exposed from a single object,
+ // even if the implementation does live elsewhere.
+ return this._datastore.adjustAttributes.apply(this._datastore, arguments);
+ },
+
+ // also proxied...
+ queryFromQuery: function() {
+ return this._datastore.queryFromQuery.apply(this._datastore, arguments);
+ }
+};
diff --git a/mailnews/db/gloda/modules/datamodel.js b/mailnews/db/gloda/modules/datamodel.js
new file mode 100644
index 000000000..1bdd6d01d
--- /dev/null
+++ b/mailnews/db/gloda/modules/datamodel.js
@@ -0,0 +1,907 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["GlodaAttributeDBDef", "GlodaAccount",
+ "GlodaConversation", "GlodaFolder", "GlodaMessage",
+ "GlodaContact", "GlodaIdentity", "GlodaAttachment"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/mailServices.js");
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+var LOG = Log4Moz.repository.getLogger("gloda.datamodel");
+
+Cu.import("resource:///modules/gloda/utils.js");
+
+// Make it lazy.
+var gMessenger;
+function getMessenger () {
+ if (!gMessenger)
+ gMessenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+ return gMessenger;
+}
+
+/**
+ * @class Represents a gloda attribute definition's DB form. This class
+ * stores the information in the database relating to this attribute
+ * definition. Access its attrDef attribute to get at the realy juicy data.
+ * This main interesting thing this class does is serve as the keeper of the
+ * mapping from parameters to attribute ids in the database if this is a
+ * parameterized attribute.
+ */
+function GlodaAttributeDBDef(aDatastore, aID, aCompoundName, aAttrType,
+ aPluginName, aAttrName) {
+ // _datastore is now set on the prototype by GlodaDatastore
+ this._id = aID;
+ this._compoundName = aCompoundName;
+ this._attrType = aAttrType;
+ this._pluginName = aPluginName;
+ this._attrName = aAttrName;
+
+ this.attrDef = null;
+
+ /** Map parameter values to the underlying database id. */
+ this._parameterBindings = {};
+}
+
+GlodaAttributeDBDef.prototype = {
+ // set by GlodaDatastore
+ _datastore: null,
+ get id() { return this._id; },
+ get attributeName() { return this._attrName; },
+
+ get parameterBindings() { return this._parameterBindings; },
+
+ /**
+ * Bind a parameter value to the attribute definition, allowing use of the
+ * attribute-parameter as an attribute.
+ *
+ * @return
+ */
+ bindParameter: function gloda_attr_bindParameter(aValue) {
+ // people probably shouldn't call us with null, but handle it
+ if (aValue == null) {
+ return this._id;
+ }
+ if (aValue in this._parameterBindings) {
+ return this._parameterBindings[aValue];
+ }
+ // no database entry exists if we are here, so we must create it...
+ let id = this._datastore._createAttributeDef(this._attrType,
+ this._pluginName, this._attrName, aValue);
+ this._parameterBindings[aValue] = id;
+ this._datastore.reportBinding(id, this, aValue);
+ return id;
+ },
+
+ /**
+ * Given a list of values, return a list (regardless of plurality) of
+ * database-ready [attribute id, value] tuples. This is intended to be used
+ * to directly convert the value of a property on an object that corresponds
+ * to a bound attribute.
+ *
+ * @param {Array} aInstanceValues An array of instance values regardless of
+ * whether or not the attribute is singular.
+ */
+ convertValuesToDBAttributes:
+ function gloda_attr_convertValuesToDBAttributes(aInstanceValues) {
+ let nounDef = this.attrDef.objectNounDef;
+ let dbAttributes = [];
+ if (nounDef.usesParameter) {
+ for (let instanceValue of aInstanceValues) {
+ let [param, dbValue] = nounDef.toParamAndValue(instanceValue);
+ dbAttributes.push([this.bindParameter(param), dbValue]);
+ }
+ }
+ else {
+ // Not generating any attributes is ok. This basically means the noun is
+ // just an informative property on the Gloda Message and has no real
+ // indexing purposes.
+ if ("toParamAndValue" in nounDef) {
+ for (let instanceValue of aInstanceValues) {
+ dbAttributes.push([this._id,
+ nounDef.toParamAndValue(instanceValue)[1]]);
+ }
+ }
+ }
+ return dbAttributes;
+ },
+
+ toString: function() {
+ return this._compoundName;
+ }
+};
+
+var GlodaHasAttributesMixIn = {
+ enumerateAttributes: function* gloda_attrix_enumerateAttributes() {
+ let nounDef = this.NOUN_DEF;
+ for (let key in this) {
+ let value = this[key];
+ let attrDef = nounDef.attribsByBoundName[key];
+ // we expect to not have attributes for underscore prefixed values (those
+ // are managed by the instance's logic. we also want to not explode
+ // should someone crap other values in there, we get both birds with this
+ // one stone.
+ if (attrDef === undefined)
+ continue;
+ if (attrDef.singular) {
+ // ignore attributes with null values
+ if (value != null)
+ yield [attrDef, [value]];
+ }
+ else {
+ // ignore attributes with no values
+ if (value.length)
+ yield [attrDef, value];
+ }
+ }
+ },
+
+ domContribute: function gloda_attrix_domContribute(aDomNode) {
+ let nounDef = this.NOUN_DEF;
+ for (let attrName in nounDef.domExposeAttribsByBoundName) {
+ let attr = nounDef.domExposeAttribsByBoundName[attrName];
+ if (this[attrName])
+ aDomNode.setAttribute(attr.domExpose, this[attrName]);
+ }
+ },
+};
+
+function MixIn(aConstructor, aMixIn) {
+ let proto = aConstructor.prototype;
+ for (let [name, func] in Iterator(aMixIn)) {
+ if (name.startsWith("get_"))
+ proto.__defineGetter__(name.substring(4), func);
+ else
+ proto[name] = func;
+ }
+}
+
+/**
+ * @class A gloda wrapper around nsIMsgIncomingServer.
+ */
+function GlodaAccount(aIncomingServer) {
+ this._incomingServer = aIncomingServer;
+}
+
+GlodaAccount.prototype = {
+ NOUN_ID: 106,
+ get id() { return this._incomingServer.key; },
+ get name() { return this._incomingServer.prettyName; },
+ get incomingServer() { return this._incomingServer; },
+ toString: function gloda_account_toString() {
+ return "Account: " + this.id;
+ },
+
+ toLocaleString: function gloda_account_toLocaleString() {
+ return this.name;
+ }
+};
+
+/**
+ * @class A gloda conversation (thread) exists so that messages can belong.
+ */
+function GlodaConversation(aDatastore, aID, aSubject, aOldestMessageDate,
+ aNewestMessageDate) {
+ // _datastore is now set on the prototype by GlodaDatastore
+ this._id = aID;
+ this._subject = aSubject;
+ this._oldestMessageDate = aOldestMessageDate;
+ this._newestMessageDate = aNewestMessageDate;
+}
+
+GlodaConversation.prototype = {
+ NOUN_ID: 101,
+ // set by GlodaDatastore
+ _datastore: null,
+ get id() { return this._id; },
+ get subject() { return this._subject; },
+ get oldestMessageDate() { return this._oldestMessageDate; },
+ get newestMessageDate() { return this._newestMessageDate; },
+
+ getMessagesCollection: function gloda_conversation_getMessagesCollection(
+ aListener, aData) {
+ let query = new GlodaMessage.prototype.NOUN_DEF.queryClass();
+ query.conversation(this._id).orderBy("date");
+ return query.getCollection(aListener, aData);
+ },
+
+ toString: function gloda_conversation_toString() {
+ return "Conversation:" + this._id;
+ },
+
+ toLocaleString: function gloda_conversation_toLocaleString() {
+ return this._subject;
+ }
+};
+
+function GlodaFolder(aDatastore, aID, aURI, aDirtyStatus, aPrettyName,
+ aIndexingPriority) {
+ // _datastore is now set by GlodaDatastore
+ this._id = aID;
+ this._uri = aURI;
+ this._dirtyStatus = aDirtyStatus;
+ this._prettyName = aPrettyName;
+ this._xpcomFolder = null;
+ this._account = null;
+ this._activeIndexing = false;
+ this._activeHeaderRetrievalLastStamp = 0;
+ this._indexingPriority = aIndexingPriority;
+ this._deleted = false;
+ this._compacting = false;
+}
+
+GlodaFolder.prototype = {
+ NOUN_ID: 100,
+ // set by GlodaDatastore
+ _datastore: null,
+
+ /** The folder is believed to be up-to-date */
+ kFolderClean: 0,
+ /** The folder has some un-indexed or dirty messages */
+ kFolderDirty: 1,
+ /** The folder needs to be entirely re-indexed, regardless of the flags on
+ * the messages in the folder. This state will be downgraded to dirty */
+ kFolderFilthy: 2,
+
+ _kFolderDirtyStatusMask: 0x7,
+ /**
+ * The (local) folder has been compacted and all of its message keys are
+ * potentially incorrect. This is not a possible state for IMAP folders
+ * because their message keys are based on UIDs rather than offsets into
+ * the mbox file.
+ */
+ _kFolderCompactedFlag: 0x8,
+
+ /** The folder should never be indexed. */
+ kIndexingNeverPriority: -1,
+ /** The lowest priority assigned to a folder. */
+ kIndexingLowestPriority: 0,
+ /** The highest priority assigned to a folder. */
+ kIndexingHighestPriority: 100,
+
+ /** The indexing priority for a folder if no other priority is assigned. */
+ kIndexingDefaultPriority: 20,
+ /** Folders marked check new are slightly more important I guess. */
+ kIndexingCheckNewPriority: 30,
+ /** Favorite folders are more interesting to the user, presumably. */
+ kIndexingFavoritePriority: 40,
+ /** The indexing priority for inboxes. */
+ kIndexingInboxPriority: 50,
+ /** The indexing priority for sent mail folders. */
+ kIndexingSentMailPriority: 60,
+
+ get id() { return this._id; },
+ get uri() { return this._uri; },
+ get dirtyStatus() {
+ return this._dirtyStatus & this._kFolderDirtyStatusMask;
+ },
+ /**
+ * Mark a folder as dirty if it was clean. Do nothing if it was already dirty
+ * or filthy. For use by GlodaMsgIndexer only. And maybe rkent and his
+ * marvelous extensions.
+ */
+ _ensureFolderDirty: function gloda_folder__markFolderDirty() {
+ if (this.dirtyStatus == this.kFolderClean) {
+ this._dirtyStatus = (this.kFolderDirty & this._kFolderDirtyStatusMask) |
+ (this._dirtyStatus & ~this._kFolderDirtyStatusMask);
+ this._datastore.updateFolderDirtyStatus(this);
+ }
+ },
+ /**
+ * Definitely for use only by GlodaMsgIndexer to downgrade the dirty status of
+ * a folder.
+ */
+ _downgradeDirtyStatus: function gloda_folder__downgradeDirtyStatus(
+ aNewStatus) {
+ if (this.dirtyStatus != aNewStatus) {
+ this._dirtyStatus = (aNewStatus & this._kFolderDirtyStatusMask) |
+ (this._dirtyStatus & ~this._kFolderDirtyStatusMask);
+ this._datastore.updateFolderDirtyStatus(this);
+ }
+ },
+ /**
+ * Indicate whether this folder is currently being compacted. The
+ * |GlodaMsgIndexer| keeps this in-memory-only value up-to-date.
+ */
+ get compacting() {
+ return this._compacting;
+ },
+ /**
+ * Set whether this folder is currently being compacted. This is really only
+ * for the |GlodaMsgIndexer| to set.
+ */
+ set compacting(aCompacting) {
+ this._compacting = aCompacting;
+ },
+ /**
+ * Indicate whether this folder was compacted and has not yet been
+ * compaction processed.
+ */
+ get compacted() {
+ return Boolean(this._dirtyStatus & this._kFolderCompactedFlag);
+ },
+ /**
+ * For use only by GlodaMsgIndexer to set/clear the compaction state of this
+ * folder.
+ */
+ _setCompactedState: function gloda_folder__clearCompactedState(aCompacted) {
+ if (this.compacted != aCompacted) {
+ if (aCompacted)
+ this._dirtyStatus |= this._kFolderCompactedFlag;
+ else
+ this._dirtyStatus &= ~this._kFolderCompactedFlag;
+ this._datastore.updateFolderDirtyStatus(this);
+ }
+ },
+
+ get name() { return this._prettyName; },
+ toString: function gloda_folder_toString() {
+ return "Folder:" + this._id;
+ },
+
+ toLocaleString: function gloda_folder_toLocaleString() {
+ let xpcomFolder = this.getXPCOMFolder(this.kActivityFolderOnlyNoData);
+ if (!xpcomFolder)
+ return this._prettyName;
+ return xpcomFolder.prettiestName +
+ " (" + xpcomFolder.rootFolder.prettiestName + ")";
+ },
+
+ get indexingPriority() {
+ return this._indexingPriority;
+ },
+
+ /** We are going to index this folder. */
+ kActivityIndexing: 0,
+ /** Asking for the folder to perform header retrievals. */
+ kActivityHeaderRetrieval: 1,
+ /** We only want the folder for its metadata but are not going to open it. */
+ kActivityFolderOnlyNoData: 2,
+
+
+ /** Is this folder known to be actively used for indexing? */
+ _activeIndexing: false,
+ /** Get our indexing status. */
+ get indexing() {
+ return this._activeIndexing;
+ },
+ /**
+ * Set our indexing status. Normally, this will be enabled through passing
+ * an activity type of kActivityIndexing (which will set us), but we will
+ * still need to be explicitly disabled by the indexing code.
+ * When disabling indexing, we will call forgetFolderIfUnused to take care of
+ * shutting things down.
+ * We are not responsible for committing changes to the message database!
+ * That is on you!
+ */
+ set indexing(aIndexing) {
+ this._activeIndexing = aIndexing;
+ if (!aIndexing)
+ this.forgetFolderIfUnused();
+ },
+ /** When was this folder last used for header retrieval purposes? */
+ _activeHeaderRetrievalLastStamp: 0,
+
+ /**
+ * Retrieve the nsIMsgFolder instance corresponding to this folder, providing
+ * an explanation of why you are requesting it for tracking/cleanup purposes.
+ *
+ * @param aActivity One of the kActivity* constants. If you pass
+ * kActivityIndexing, we will set indexing for you, but you will need to
+ * clear it when you are done.
+ * @return The nsIMsgFolder if available, null on failure.
+ */
+ getXPCOMFolder: function gloda_folder_getXPCOMFolder(aActivity) {
+ if (!this._xpcomFolder) {
+ let rdfService = Cc['@mozilla.org/rdf/rdf-service;1']
+ .getService(Ci.nsIRDFService);
+ this._xpcomFolder = rdfService.GetResource(this.uri)
+ .QueryInterface(Ci.nsIMsgFolder);
+ }
+ switch (aActivity) {
+ case this.kActivityIndexing:
+ // mark us as indexing, but don't bother with live tracking. we do
+ // that independently and only for header retrieval.
+ this.indexing = true;
+ break;
+ case this.kActivityHeaderRetrieval:
+ if (this._activeHeaderRetrievalLastStamp === 0)
+ this._datastore.markFolderLive(this);
+ this._activeHeaderRetrievalLastStamp = Date.now();
+ break;
+ case this.kActivityFolderOnlyNoData:
+ // we don't have to do anything here.
+ break;
+ }
+
+ return this._xpcomFolder;
+ },
+
+ /**
+ * Retrieve a GlodaAccount instance corresponding to this folder.
+ *
+ * @return The GlodaAccount instance.
+ */
+ getAccount: function gloda_folder_getAccount() {
+ if (!this._account) {
+ let msgFolder = this.getXPCOMFolder(this.kActivityFolderOnlyNoData);
+ this._account = new GlodaAccount(msgFolder.server);
+ }
+ return this._account;
+ },
+
+ /**
+ * How many milliseconds must a folder have not had any header retrieval
+ * activity before it's okay to lose the database reference?
+ */
+ ACCEPTABLY_OLD_THRESHOLD: 10000,
+
+ /**
+ * Cleans up our nsIMsgFolder reference if we have one and it's not "in use".
+ * In use, from our perspective, means that it is not being used for indexing
+ * and some arbitrary interval of time has elapsed since it was last
+ * retrieved for header retrieval reasons. The time interval is because if
+ * we have one GlodaMessage requesting a header, there's a high probability
+ * that another message will request a header in the near future.
+ * Because setting indexing to false disables us, we are written in an
+ * idempotent fashion. (It is possible for disabling indexing's call to us
+ * to cause us to return true but for the datastore's timer call to have not
+ * yet triggered.)
+ *
+ * @returns true if we are cleaned up and can be considered 'dead', false if
+ * we should still be considered alive and this method should be called
+ * again in the future.
+ */
+ forgetFolderIfUnused: function gloda_folder_forgetFolderIfUnused() {
+ // we are not cleaning/cleaned up if we are indexing
+ if (this._activeIndexing)
+ return false;
+
+ // set a point in the past as the threshold. the timestamp must be older
+ // than this to be eligible for cleanup.
+ let acceptablyOld = Date.now() - this.ACCEPTABLY_OLD_THRESHOLD;
+ // we are not cleaning/cleaned up if we have retrieved a header more
+ // recently than the acceptably old threshold.
+ if (this._activeHeaderRetrievalLastStamp > acceptablyOld)
+ return false;
+
+ if (this._xpcomFolder) {
+ // This is the key action we take; the nsIMsgFolder will continue to
+ // exist, but we want it to forget about its database so that it can
+ // be closed and its memory can be reclaimed.
+ this._xpcomFolder.msgDatabase = null;
+ this._xpcomFolder = null;
+ // since the last retrieval time tracks whether we have marked live or
+ // not, this needs to be reset to 0 too.
+ this._activeHeaderRetrievalLastStamp = 0;
+ }
+
+ return true;
+ }
+};
+
+/**
+ * @class A message representation.
+ */
+function GlodaMessage(aDatastore, aID, aFolderID, aMessageKey,
+ aConversationID, aConversation, aDate,
+ aHeaderMessageID, aDeleted, aJsonText,
+ aNotability,
+ aSubject, aIndexedBodyText, aAttachmentNames) {
+ // _datastore is now set on the prototype by GlodaDatastore
+ this._id = aID;
+ this._folderID = aFolderID;
+ this._messageKey = aMessageKey;
+ this._conversationID = aConversationID;
+ this._conversation = aConversation;
+ this._date = aDate;
+ this._headerMessageID = aHeaderMessageID;
+ this._jsonText = aJsonText;
+ this._notability = aNotability;
+ this._subject = aSubject;
+ this._indexedBodyText = aIndexedBodyText;
+ this._attachmentNames = aAttachmentNames;
+
+ // only set _deleted if we're deleted, otherwise the undefined does our
+ // speaking for us.
+ if (aDeleted)
+ this._deleted = aDeleted;
+}
+
+GlodaMessage.prototype = {
+ NOUN_ID: 102,
+ // set by GlodaDatastore
+ _datastore: null,
+ get id() { return this._id; },
+ get folderID() { return this._folderID; },
+ get messageKey() { return this._messageKey; },
+ get conversationID() { return this._conversationID; },
+ // conversation is special
+ get headerMessageID() { return this._headerMessageID; },
+ get notability() { return this._notability; },
+ set notability(aNotability) { this._notability = aNotability; },
+
+ get subject() { return this._subject; },
+ get indexedBodyText() { return this._indexedBodyText; },
+ get attachmentNames() { return this._attachmentNames; },
+
+ get date() { return this._date; },
+ set date(aNewDate) { this._date = aNewDate; },
+
+ get folder() {
+ // XXX due to a deletion bug it is currently possible to get in a state
+ // where we have an illegal folderID value. This will result in an
+ // exception. As a workaround, let's just return null in that case.
+ try {
+ if (this._folderID != null)
+ return this._datastore._mapFolderID(this._folderID);
+ }
+ catch (ex) {
+ }
+ return null;
+ },
+ get folderURI() {
+ // XXX just like for folder, handle mapping failures and return null
+ try {
+ if (this._folderID != null)
+ return this._datastore._mapFolderID(this._folderID).uri;
+ }
+ catch (ex) {
+ }
+ return null;
+ },
+ get account() {
+ // XXX due to a deletion bug it is currently possible to get in a state
+ // where we have an illegal folderID value. This will result in an
+ // exception. As a workaround, let's just return null in that case.
+ try {
+ if (this._folderID == null)
+ return null;
+ let folder = this._datastore._mapFolderID(this._folderID);
+ return folder.getAccount();
+ }
+ catch (ex) { }
+ return null;
+ },
+ get conversation() {
+ return this._conversation;
+ },
+
+ toString: function gloda_message_toString() {
+ // uh, this is a tough one...
+ return "Message:" + this._id;
+ },
+
+ _clone: function gloda_message_clone() {
+ return new GlodaMessage(/* datastore */ null, this._id, this._folderID,
+ this._messageKey, this._conversationID, this._conversation, this._date,
+ this._headerMessageID, "_deleted" in this ? this._deleted : undefined,
+ "_jsonText" in this ? this._jsonText : undefined, this._notability,
+ this._subject, this._indexedBodyText, this._attachmentNames);
+ },
+
+ /**
+ * Provide a means of propagating changed values on our clone back to
+ * ourselves. This is required because of an object identity trick gloda
+ * does; when indexing an already existing object, all mutations happen on
+ * a clone of the existing object so that
+ */
+ _declone: function gloda_message_declone(aOther) {
+ if ("_content" in aOther)
+ this._content = aOther._content;
+
+ // The _indexedAuthor/_indexedRecipients fields don't get updated on
+ // fulltext update so we don't need to propagate.
+ this._indexedBodyText = aOther._indexedBodyText;
+ this._attachmentNames = aOther._attachmentNames;
+ },
+
+ /**
+ * Mark this message as a ghost. Ghosts are characterized by having no folder
+ * id and no message key. They also are not deleted or they would be of
+ * absolutely no use to us.
+ *
+ * These changes are suitable for persistence.
+ */
+ _ghost: function gloda_message_ghost() {
+ this._folderID = null;
+ this._messageKey = null;
+ if ("_deleted" in this)
+ delete this._deleted;
+ },
+
+ /**
+ * Are we a ghost (which implies not deleted)? We are not a ghost if we have
+ * a definite folder location (we may not know our message key in the case
+ * of IMAP moves not fully completed) and are not deleted.
+ */
+ get _isGhost() {
+ return this._folderID == null && !this._isDeleted;
+ },
+
+ /**
+ * If we were dead, un-dead us.
+ */
+ _ensureNotDeleted: function gloda_message__ensureNotDeleted() {
+ if ("_deleted" in this)
+ delete this._deleted;
+ },
+
+ /**
+ * Are we deleted? This is private because deleted gloda messages are not
+ * visible to non-core-gloda code.
+ */
+ get _isDeleted() {
+ return ("_deleted" in this) && this._deleted;
+ },
+
+ /**
+ * Trash this message's in-memory representation because it should no longer
+ * be reachable by any code. The database record is gone, it's not coming
+ * back.
+ */
+ _objectPurgedMakeYourselfUnpleasant: function gloda_message_nuke() {
+ this._id = null;
+ this._folderID = null;
+ this._messageKey = null;
+ this._conversationID = null;
+ this._conversation = null;
+ this.date = null;
+ this._headerMessageID = null;
+ },
+
+ /**
+ * Return the underlying nsIMsgDBHdr from the folder storage for this, or
+ * null if the message does not exist for one reason or another. We may log
+ * to our logger in the failure cases.
+ *
+ * This method no longer caches the result, so if you need to hold onto it,
+ * hold onto it.
+ *
+ * In the process of retrieving the underlying message header, we may have to
+ * open the message header database associated with the folder. This may
+ * result in blocking while the load happens, so you may want to try and find
+ * an alternate way to initiate the load before calling us.
+ * We provide hinting to the GlodaDatastore via the GlodaFolder so that it
+ * knows when it's a good time for it to go and detach from the database.
+ *
+ * @returns The nsIMsgDBHdr associated with this message if available, null on
+ * failure.
+ */
+ get folderMessage() {
+ if (this._folderID === null || this._messageKey === null)
+ return null;
+
+ // XXX like for folder and folderURI, return null if we can't map the folder
+ let glodaFolder;
+ try {
+ glodaFolder = this._datastore._mapFolderID(this._folderID);
+ }
+ catch (ex) {
+ return null;
+ }
+ let folder = glodaFolder.getXPCOMFolder(
+ glodaFolder.kActivityHeaderRetrieval);
+ if (folder) {
+ let folderMessage;
+ try {
+ folderMessage = folder.GetMessageHeader(this._messageKey);
+ }
+ catch (ex) {
+ folderMessage = null;
+ }
+ if (folderMessage !== null) {
+ // verify the message-id header matches what we expect...
+ if (folderMessage.messageId != this._headerMessageID) {
+ LOG.info("Message with message key " + this._messageKey +
+ " in folder '" + folder.URI + "' does not match expected " +
+ "header! (" + this._headerMessageID + " expected, got " +
+ folderMessage.messageId + ")");
+ folderMessage = null;
+ }
+ }
+ return folderMessage;
+ }
+
+ // this only gets logged if things have gone very wrong. we used to throw
+ // here, but it's unlikely our caller can do anything more meaningful than
+ // treating this as a disappeared message.
+ LOG.info("Unable to locate folder message for: " + this._folderID + ":" +
+ this._messageKey);
+ return null;
+ },
+ get folderMessageURI() {
+ let folderMessage = this.folderMessage;
+ if (folderMessage)
+ return folderMessage.folder.getUriForMsg(folderMessage);
+ else
+ return null;
+ }
+};
+MixIn(GlodaMessage, GlodaHasAttributesMixIn);
+
+/**
+ * @class Contacts correspond to people (one per person), and may own multiple
+ * identities (e-mail address, IM account, etc.)
+ */
+function GlodaContact(aDatastore, aID, aDirectoryUUID, aContactUUID, aName,
+ aPopularity, aFrecency, aJsonText) {
+ // _datastore set on the prototype by GlodaDatastore
+ this._id = aID;
+ this._directoryUUID = aDirectoryUUID;
+ this._contactUUID = aContactUUID;
+ this._name = aName;
+ this._popularity = aPopularity;
+ this._frecency = aFrecency;
+ if (aJsonText)
+ this._jsonText = aJsonText;
+
+ this._identities = null;
+}
+
+GlodaContact.prototype = {
+ NOUN_ID: 103,
+ // set by GlodaDatastore
+ _datastore: null,
+
+ get id() { return this._id; },
+ get directoryUUID() { return this._directoryUUID; },
+ get contactUUID() { return this._contactUUID; },
+ get name() { return this._name; },
+ set name(aName) { this._name = aName; },
+
+ get popularity() { return this._popularity; },
+ set popularity(aPopularity) {
+ this._popularity = aPopularity;
+ this.dirty = true;
+ },
+
+ get frecency() { return this._frecency; },
+ set frecency(aFrecency) {
+ this._frecency = aFrecency;
+ this.dirty = true;
+ },
+
+ get identities() {
+ return this._identities;
+ },
+
+ toString: function gloda_contact_toString() {
+ return "Contact:" + this._id;
+ },
+
+ get accessibleLabel() {
+ return "Contact: " + this._name;
+ },
+
+ _clone: function gloda_contact_clone() {
+ return new GlodaContact(/* datastore */ null, this._id, this._directoryUUID,
+ this._contactUUID, this._name, this._popularity, this._frecency);
+ },
+};
+MixIn(GlodaContact, GlodaHasAttributesMixIn);
+
+
+/**
+ * @class A specific means of communication for a contact.
+ */
+function GlodaIdentity(aDatastore, aID, aContactID, aContact, aKind, aValue,
+ aDescription, aIsRelay) {
+ // _datastore set on the prototype by GlodaDatastore
+ this._id = aID;
+ this._contactID = aContactID;
+ this._contact = aContact;
+ this._kind = aKind;
+ this._value = aValue;
+ this._description = aDescription;
+ this._isRelay = aIsRelay;
+ /// Cached indication of whether there is an address book card for this
+ /// identity. We keep this up-to-date via address book listener
+ /// notifications in |GlodaABIndexer|.
+ this._hasAddressBookCard = undefined;
+}
+
+GlodaIdentity.prototype = {
+ NOUN_ID: 104,
+ // set by GlodaDatastore
+ _datastore: null,
+ get id() { return this._id; },
+ get contactID() { return this._contactID; },
+ get contact() { return this._contact; },
+ get kind() { return this._kind; },
+ get value() { return this._value; },
+ get description() { return this._description; },
+ get isRelay() { return this._isRelay; },
+
+ get uniqueValue() {
+ return this._kind + "@" + this._value;
+ },
+
+ toString: function gloda_identity_toString() {
+ return "Identity:" + this._kind + ":" + this._value;
+ },
+
+ toLocaleString: function gloda_identity_toLocaleString() {
+ if (this.contact.name == this.value)
+ return this.value;
+ return this.contact.name + " : " + this.value;
+ },
+
+ get abCard() {
+ // for our purposes, the address book only speaks email
+ if (this._kind != "email")
+ return false;
+ let card = GlodaUtils.getCardForEmail(this._value);
+ this._hasAddressBookCard = (card != null);
+ return card;
+ },
+
+ /**
+ * Indicates whether we have an address book card for this identity. This
+ * value is cached once looked-up and kept up-to-date by |GlodaABIndexer|
+ * and its notifications.
+ */
+ get inAddressBook() {
+ if (this._hasAddressBookCard !== undefined)
+ return this._hasAddressBookCard;
+ return (this.abCard && true) || false;
+ },
+
+ pictureURL: function(aSize) {
+ if (this.inAddressBook) {
+ // XXX should get the photo if we have it.
+ }
+ return "";
+ }
+};
+
+
+/**
+ * An attachment, with as much information as we can gather on it
+ */
+function GlodaAttachment(aGlodaMessage, aName, aContentType, aSize, aPart, aExternalUrl, aIsExternal) {
+ // _datastore set on the prototype by GlodaDatastore
+ this._glodaMessage = aGlodaMessage;
+ this._name = aName;
+ this._contentType = aContentType;
+ this._size = aSize;
+ this._part = aPart;
+ this._externalUrl = aExternalUrl;
+ this._isExternal = aIsExternal;
+}
+
+GlodaAttachment.prototype = {
+ NOUN_ID: 105,
+ // set by GlodaDatastore
+ get name() { return this._name; },
+ get contentType() { return this._contentType; },
+ get size() { return this._size; },
+ get url() {
+ if (this.isExternal)
+ return this._externalUrl;
+ else {
+ let uri = this._glodaMessage.folderMessageURI;
+ if (!uri)
+ throw new Error("The message doesn't exist anymore, unable to rebuild attachment URL");
+ let neckoURL = {};
+ let msgService = getMessenger().messageServiceFromURI(uri);
+ msgService.GetUrlForUri(uri, neckoURL, null);
+ let url = neckoURL.value.spec;
+ let hasParamAlready = url.match(/\?[a-z]+=[^\/]+$/);
+ let sep = hasParamAlready ? "&" : "?";
+ return url+sep+"part="+this._part+"&filename="+encodeURIComponent(this._name);
+ }
+ },
+ get isExternal() { return this._isExternal; },
+
+ toString: function gloda_attachment_toString() {
+ return "attachment: " + this._name + ":" + this._contentType;
+ },
+
+};
diff --git a/mailnews/db/gloda/modules/datastore.js b/mailnews/db/gloda/modules/datastore.js
new file mode 100644
index 000000000..70bbdc6a7
--- /dev/null
+++ b/mailnews/db/gloda/modules/datastore.js
@@ -0,0 +1,3989 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This file looks to Myk Melez <myk@mozilla.org>'s Mozilla Labs snowl
+ * project's (http://hg.mozilla.org/labs/snowl/) modules/datastore.js
+ * for inspiration and idioms (and also a name :).
+ */
+
+this.EXPORTED_SYMBOLS = ["GlodaDatastore"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/IOUtils.js");
+Cu.import("resource://gre/modules/Services.jsm");
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+Cu.import("resource:///modules/gloda/datamodel.js");
+Cu.import("resource:///modules/gloda/databind.js");
+Cu.import("resource:///modules/gloda/collection.js");
+
+var MIN_CACHE_SIZE = 8 * 1048576;
+var MAX_CACHE_SIZE = 64 * 1048576;
+var MEMSIZE_FALLBACK_BYTES = 256 * 1048576;
+
+var PCH_LOG = Log4Moz.repository.getLogger("gloda.ds.pch");
+
+/**
+ * Commit async handler; hands off the notification to
+ * |GlodaDatastore._asyncCompleted|.
+ */
+function PostCommitHandler(aCallbacks) {
+ this.callbacks = aCallbacks;
+ GlodaDatastore._pendingAsyncStatements++;
+}
+
+PostCommitHandler.prototype = {
+ handleResult: function gloda_ds_pch_handleResult(aResultSet) {
+ },
+
+ handleError: function gloda_ds_pch_handleError(aError) {
+ PCH_LOG.error("database error:" + aError);
+ },
+
+ handleCompletion: function gloda_ds_pch_handleCompletion(aReason) {
+ // just outright bail if we are shutdown
+ if (GlodaDatastore.datastoreIsShutdown)
+ return;
+
+ if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ for (let callback of this.callbacks) {
+ try {
+ callback();
+ }
+ catch (ex) {
+ PCH_LOG.error("PostCommitHandler callback (" + ex.fileName + ":" +
+ ex.lineNumber + ") threw: " + ex);
+ }
+ }
+ }
+ try {
+ GlodaDatastore._asyncCompleted();
+ }
+ catch (e) {
+ PCH_LOG.error("Exception in handleCompletion:", e);
+ }
+
+ }
+};
+
+var QFQ_LOG = Log4Moz.repository.getLogger("gloda.ds.qfq");
+
+/**
+ * Singleton collection listener used by |QueryFromQueryCallback| to assist in
+ * the loading of referenced noun instances. Which is to say, messages have
+ * identities (specific e-mail addresses) associated with them via attributes.
+ * And these identities in turn reference / are referenced by contacts (the
+ * notion of a person).
+ *
+ * This listener is primarily concerned with fixing up the references in each
+ * noun instance to its referenced instances once they have been loaded. It
+ * also deals with caching so that our identity invariant is maintained: user
+ * code should only ever see one distinct instance of a thing at a time.
+ */
+var QueryFromQueryResolver = {
+ onItemsAdded: function(aIgnoredItems, aCollection, aFake) {
+ let originColl = aCollection.dataStack ? aCollection.dataStack.pop()
+ : aCollection.data;
+ //QFQ_LOG.debug("QFQR: originColl: " + originColl);
+ if (aCollection.completionShifter)
+ aCollection.completionShifter.push(originColl);
+ else
+ aCollection.completionShifter = [originColl];
+
+ if (!aFake) {
+ originColl.deferredCount--;
+ originColl.resolvedCount++;
+ }
+
+ // bail if we are still pending on some other load completion
+ if (originColl.deferredCount > 0) {
+ //QFQ_LOG.debug("QFQR: bailing " + originColl._nounDef.name);
+ return;
+ }
+
+ let referencesByNounID = originColl.masterCollection.referencesByNounID;
+ let inverseReferencesByNounID =
+ originColl.masterCollection.inverseReferencesByNounID;
+
+ if (originColl.pendingItems) {
+ for (let [, item] in Iterator(originColl.pendingItems)) {
+ //QFQ_LOG.debug("QFQR: loading deferred " + item.NOUN_ID + ":" + item.id);
+ GlodaDatastore.loadNounDeferredDeps(item, referencesByNounID,
+ inverseReferencesByNounID);
+ }
+
+ // we need to consider the possibility that we are racing a collection very
+ // much like our own. as such, this means we need to perform cache
+ // unification as our last step.
+ GlodaCollectionManager.cacheLoadUnify(originColl._nounDef.id,
+ originColl.pendingItems, false);
+
+ // just directly tell the collection about the items. we know the query
+ // matches (at least until we introduce predicates that we cannot express
+ // in SQL.)
+ //QFQ_LOG.debug(" QFQR: about to trigger listener: " + originColl._listener +
+ // "with collection: " + originColl._nounDef.name);
+ originColl._onItemsAdded(originColl.pendingItems);
+ delete originColl.pendingItems;
+ delete originColl._pendingIdMap;
+ }
+ },
+ onItemsModified: function() {
+ },
+ onItemsRemoved: function() {
+ },
+ onQueryCompleted: function(aCollection) {
+ let originColl = aCollection.completionShifter ?
+ aCollection.completionShifter.shift() : aCollection.data;
+ //QFQ_LOG.debug(" QFQR about to trigger completion with collection: " +
+ // originColl._nounDef.name);
+ if (originColl.deferredCount <= 0) {
+ originColl._onQueryCompleted();
+ }
+ },
+};
+
+/**
+ * Handles the results from a GlodaDatastore.queryFromQuery call in cooperation
+ * with the |QueryFromQueryResolver| collection listener. We do a lot of
+ * legwork related to satisfying references to other noun instances on the
+ * noun instances the user directy queried. Messages reference identities
+ * reference contacts which in turn (implicitly) reference identities again.
+ * We have to spin up those other queries and stitch things together.
+ *
+ * While the code is generally up to the existing set of tasks it is called to
+ * handle, I would not be surprised for it to fall down if things get more
+ * complex. Some of the logic here 'evolved' a bit and could benefit from
+ * additional documentation and a fresh go-through.
+ */
+function QueryFromQueryCallback(aStatement, aNounDef, aCollection) {
+ this.statement = aStatement;
+ this.nounDef = aNounDef;
+ this.collection = aCollection;
+
+ //QFQ_LOG.debug("Creating QFQCallback for noun: " + aNounDef.name);
+
+ // the master collection holds the referencesByNounID
+ this.referencesByNounID = {};
+ this.masterReferencesByNounID =
+ this.collection.masterCollection.referencesByNounID;
+ this.inverseReferencesByNounID = {};
+ this.masterInverseReferencesByNounID =
+ this.collection.masterCollection.inverseReferencesByNounID;
+ // we need to contribute our references as we load things; we need this
+ // because of the potential for circular dependencies and our inability to
+ // put things into the caching layer (or collection's _idMap) until we have
+ // fully resolved things.
+ if (this.nounDef.id in this.masterReferencesByNounID)
+ this.selfReferences = this.masterReferencesByNounID[this.nounDef.id];
+ else
+ this.selfReferences = this.masterReferencesByNounID[this.nounDef.id] = {};
+ if (this.nounDef.parentColumnAttr) {
+ if (this.nounDef.id in this.masterInverseReferencesByNounID)
+ this.selfInverseReferences =
+ this.masterInverseReferencesByNounID[this.nounDef.id];
+ else
+ this.selfInverseReferences =
+ this.masterInverseReferencesByNounID[this.nounDef.id] = {};
+ }
+
+ this.needsLoads = false;
+
+ GlodaDatastore._pendingAsyncStatements++;
+}
+
+QueryFromQueryCallback.prototype = {
+ handleResult: function gloda_ds_qfq_handleResult(aResultSet) {
+ try {
+ // just outright bail if we are shutdown
+ if (GlodaDatastore.datastoreIsShutdown)
+ return;
+
+ let pendingItems = this.collection.pendingItems;
+ let pendingIdMap = this.collection._pendingIdMap;
+ let row;
+ let nounDef = this.nounDef;
+ let nounID = nounDef.id;
+ while ((row = aResultSet.getNextRow())) {
+ let item = nounDef.objFromRow.call(nounDef.datastore, row);
+ if (this.collection.stashedColumns) {
+ let stashed = this.collection.stashedColumns[item.id] = [];
+ for (let [,iCol] in
+ Iterator(this.collection.query.options.stashColumns)) {
+ stashed.push(GlodaDatastore._getVariant(row, iCol));
+ }
+ }
+ // try and replace the item with one from the cache, if we can
+ let cachedItem = GlodaCollectionManager.cacheLookupOne(nounID, item.id,
+ false);
+
+ // if we already have a copy in the pending id map, skip it
+ if (item.id in pendingIdMap)
+ continue;
+
+ //QFQ_LOG.debug("loading item " + nounDef.id + ":" + item.id + " existing: " +
+ // this.selfReferences[item.id] + " cached: " + cachedItem);
+ if (cachedItem)
+ item = cachedItem;
+ // we may already have been loaded by this process
+ else if (this.selfReferences[item.id] != null)
+ item = this.selfReferences[item.id];
+ // perform loading logic which may produce reference dependencies
+ else
+ this.needsLoads =
+ GlodaDatastore.loadNounItem(item, this.referencesByNounID,
+ this.inverseReferencesByNounID) ||
+ this.needsLoads;
+
+ // add ourself to the references by our id
+ // QFQ_LOG.debug("saving item " + nounDef.id + ":" + item.id + " to self-refs");
+ this.selfReferences[item.id] = item;
+
+ // if we're tracking it, add ourselves to our parent's list of children
+ // too
+ if (this.selfInverseReferences) {
+ let parentID = item[nounDef.parentColumnAttr.idStorageAttributeName];
+ let childrenList = this.selfInverseReferences[parentID];
+ if (childrenList === undefined)
+ childrenList = this.selfInverseReferences[parentID] = [];
+ childrenList.push(item);
+ }
+
+ pendingItems.push(item);
+ pendingIdMap[item.id] = item;
+ }
+ }
+ catch (e) {
+ GlodaDatastore._log.error("Exception in handleResult:", e);
+ }
+ },
+
+ handleError: function gloda_ds_qfq_handleError(aError) {
+ GlodaDatastore._log.error("Async queryFromQuery error: " +
+ aError.result + ": " + aError.message);
+ },
+
+ handleCompletion: function gloda_ds_qfq_handleCompletion(aReason) {
+ try {
+ try {
+ this.statement.finalize();
+ this.statement = null;
+
+ // just outright bail if we are shutdown
+ if (GlodaDatastore.datastoreIsShutdown)
+ return;
+
+ //QFQ_LOG.debug("handleCompletion: " + this.collection._nounDef.name);
+
+ if (this.needsLoads) {
+ for (let nounID in this.referencesByNounID) {
+ let references = this.referencesByNounID[nounID];
+ if (nounID == this.nounDef.id)
+ continue;
+ let nounDef = GlodaDatastore._nounIDToDef[nounID];
+ //QFQ_LOG.debug(" have references for noun: " + nounDef.name);
+ // try and load them out of the cache/existing collections. items in the
+ // cache will be fully formed, which is nice for us.
+ // XXX this mechanism will get dubious when we have multiple paths to a
+ // single noun-type. For example, a -> b -> c, a-> c; two paths to c
+ // and we're looking at issuing two requests to c, the latter of which
+ // will be a superset of the first one. This does not currently pose
+ // a problem because we only have a -> b -> c -> b, and sequential
+ // processing means no alarms and no surprises.
+ let masterReferences = this.masterReferencesByNounID[nounID];
+ if (masterReferences === undefined)
+ masterReferences = this.masterReferencesByNounID[nounID] = {};
+ let outReferences;
+ if (nounDef.parentColumnAttr)
+ outReferences = {};
+ else
+ outReferences = masterReferences;
+ let [foundCount, notFoundCount, notFound] =
+ GlodaCollectionManager.cacheLookupMany(nounDef.id, references,
+ outReferences);
+
+ if (nounDef.parentColumnAttr) {
+ let inverseReferences;
+ if (nounDef.id in this.masterInverseReferencesByNounID)
+ inverseReferences =
+ this.masterInverseReferencesByNounID[nounDef.id];
+ else
+ inverseReferences =
+ this.masterInverseReferencesByNounID[nounDef.id] = {};
+
+ for (let key in outReferences) {
+ let item = outReferences[key];
+ masterReferences[item.id] = item;
+ let parentID = item[nounDef.parentColumnAttr.idStorageAttributeName];
+ let childrenList = inverseReferences[parentID];
+ if (childrenList === undefined)
+ childrenList = inverseReferences[parentID] = [];
+ childrenList.push(item);
+ }
+ }
+
+ //QFQ_LOG.debug(" found: " + foundCount + " not found: " + notFoundCount);
+ if (notFoundCount === 0) {
+ this.collection.resolvedCount++;
+ }
+ else {
+ this.collection.deferredCount++;
+ let query = new nounDef.queryClass();
+ query.id.apply(query, Object.keys(notFound));
+
+ this.collection.masterCollection.subCollections[nounDef.id] =
+ GlodaDatastore.queryFromQuery(query, QueryFromQueryResolver,
+ this.collection,
+ // we fully expect/allow for there being no such subcollection yet.
+ this.collection.masterCollection.subCollections[nounDef.id],
+ this.collection.masterCollection,
+ {becomeExplicit: true});
+ }
+ }
+
+ for (let nounID in this.inverseReferencesByNounID) {
+ let inverseReferences = this.inverseReferencesByNounID[nounID];
+ this.collection.deferredCount++;
+ let nounDef = GlodaDatastore._nounIDToDef[nounID];
+
+ //QFQ_LOG.debug("Want to load inverse via " + nounDef.parentColumnAttr.boundName);
+
+ let query = new nounDef.queryClass();
+ // we want to constrain using the parent column
+ let queryConstrainer = query[nounDef.parentColumnAttr.boundName];
+ queryConstrainer.apply(query, Object.keys(inverseReferences));
+ this.collection.masterCollection.subCollections[nounDef.id] =
+ GlodaDatastore.queryFromQuery(query, QueryFromQueryResolver,
+ this.collection,
+ // we fully expect/allow for there being no such subcollection yet.
+ this.collection.masterCollection.subCollections[nounDef.id],
+ this.collection.masterCollection,
+ {becomeExplicit: true});
+ }
+ }
+ else {
+ this.collection.deferredCount--;
+ this.collection.resolvedCount++;
+ }
+
+ //QFQ_LOG.debug(" defer: " + this.collection.deferredCount +
+ // " resolved: " + this.collection.resolvedCount);
+
+ // process immediately and kick-up to the master collection...
+ if (this.collection.deferredCount <= 0) {
+ // this guy will resolve everyone using referencesByNounID and issue the
+ // call to this.collection._onItemsAdded to propagate things to the
+ // next concerned subCollection or the actual listener if this is the
+ // master collection. (Also, call _onQueryCompleted).
+ QueryFromQueryResolver.onItemsAdded(null, {data: this.collection}, true);
+ QueryFromQueryResolver.onQueryCompleted({data: this.collection});
+ }
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ QFQ_LOG.error("Exception:", e);
+ }
+ }
+ finally {
+ GlodaDatastore._asyncCompleted();
+ }
+ }
+};
+
+/**
+ * Used by |GlodaDatastore.folderCompactionPassBlockFetch| to accumulate the
+ * results and pass them back in to the compaction process in
+ * |GlodaMsgIndexer._worker_folderCompactionPass|.
+ */
+function CompactionBlockFetcherHandler(aCallback) {
+ this.callback = aCallback;
+ this.idsAndMessageKeys = [];
+ GlodaDatastore._pendingAsyncStatements++;
+}
+CompactionBlockFetcherHandler.prototype = {
+ handleResult: function gloda_ds_cbfh_handleResult(aResultSet) {
+ let row;
+ while ((row = aResultSet.getNextRow())) {
+ this.idsAndMessageKeys.push([
+ row.getInt64(0), // id
+ row.getInt64(1), // messageKey
+ row.getString(2), // headerMessageID
+ ]);
+ }
+ },
+ handleError: function gloda_ds_cbfh_handleError(aError) {
+ GlodaDatastore._log.error("CompactionBlockFetcherHandler error: " +
+ aError.result + ": " + aError.message);
+ },
+ handleCompletion: function gloda_ds_cbfh_handleCompletion(aReason) {
+ GlodaDatastore._asyncCompleted();
+ this.callback(this.idsAndMessageKeys);
+ }
+};
+
+/**
+ * Use this as the callback handler when you have a SQL query that returns a
+ * single row with a single integer column value, like a COUNT() query.
+ */
+function SingletonResultValueHandler(aCallback) {
+ this.callback = aCallback;
+ this.result = null;
+ GlodaDatastore._pendingAsyncStatements++;
+}
+SingletonResultValueHandler.prototype = {
+ handleResult: function gloda_ds_cbfh_handleResult(aResultSet) {
+ let row;
+ while ((row = aResultSet.getNextRow())) {
+ this.result = row.getInt64(0);
+ }
+ },
+ handleError: function gloda_ds_cbfh_handleError(aError) {
+ GlodaDatastore._log.error("SingletonResultValueHandler error: " +
+ aError.result + ": " + aError.message);
+ },
+ handleCompletion: function gloda_ds_cbfh_handleCompletion(aReason) {
+ GlodaDatastore._asyncCompleted();
+ this.callback(this.result);
+ }
+};
+
+/**
+ * Wrapper that duplicates actions taken on a real statement to an explain
+ * statement. Currently only fires an explain statement once.
+ */
+function ExplainedStatementWrapper(aRealStatement, aExplainStatement,
+ aSQLString, aExplainHandler) {
+ this.real = aRealStatement;
+ this.explain = aExplainStatement;
+ this.sqlString = aSQLString;
+ this.explainHandler = aExplainHandler;
+ this.done = false;
+}
+ExplainedStatementWrapper.prototype = {
+ bindNullParameter: function(aColIndex) {
+ this.real.bindNullParameter(aColIndex);
+ if (!this.done)
+ this.explain.bindNullParameter(aColIndex);
+ },
+ bindStringParameter: function(aColIndex, aValue) {
+ this.real.bindStringParameter(aColIndex, aValue);
+ if (!this.done)
+ this.explain.bindStringParameter(aColIndex, aValue);
+ },
+ bindInt64Parameter: function(aColIndex, aValue) {
+ this.real.bindInt64Parameter(aColIndex, aValue);
+ if (!this.done)
+ this.explain.bindInt64Parameter(aColIndex, aValue);
+ },
+ bindDoubleParameter: function(aColIndex, aValue) {
+ this.real.bindDoubleParameter(aColIndex, aValue);
+ if (!this.done)
+ this.explain.bindDoubleParameter(aColIndex, aValue);
+ },
+ executeAsync: function wrapped_executeAsync(aCallback) {
+ if (!this.done) {
+ this.explainHandler.sqlEnRoute(this.sqlString);
+ this.explain.executeAsync(this.explainHandler);
+ this.explain.finalize();
+ this.done = true;
+ }
+ return this.real.executeAsync(aCallback);
+ },
+ finalize: function wrapped_finalize() {
+ if (!this.done)
+ this.explain.finalize();
+ this.real.finalize();
+ },
+};
+
+/**
+ * Writes a single JSON document to the provide file path in a streaming
+ * fashion. At startup we open an array to place the queries in and at
+ * shutdown we close it.
+ */
+function ExplainedStatementProcessor(aDumpPath) {
+ Services.obs.addObserver(this, "quit-application", false);
+
+ this._sqlStack = [];
+ this._curOps = [];
+ this._objsWritten = 0;
+
+ let filePath = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ filePath.initWithPath(aDumpPath);
+
+ this._ostream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ this._ostream.init(filePath, -1, -1, 0);
+
+ let s = '{"queries": [';
+ this._ostream.write(s, s.length);
+}
+ExplainedStatementProcessor.prototype = {
+ sqlEnRoute: function esp_sqlEnRoute(aSQLString) {
+ this._sqlStack.push(aSQLString);
+ },
+ handleResult: function esp_handleResult(aResultSet) {
+ let row;
+ // addr opcode (s) p1 p2 p3 p4 (s) p5 comment (s)
+ while ((row = aResultSet.getNextRow())) {
+ this._curOps.push([
+ row.getInt64(0), // addr
+ row.getString(1), // opcode
+ row.getInt64(2), // p1
+ row.getInt64(3), // p2
+ row.getInt64(4), // p3
+ row.getString(5), // p4
+ row.getString(6), // p5
+ row.getString(7) // comment
+ ]);
+ }
+ },
+ handleError: function esp_handleError(aError) {
+ Cu.reportError("Unexpected error in EXPLAIN handler: " + aError);
+ },
+ handleCompletion: function esp_handleCompletion(aReason) {
+ let obj = {
+ sql: this._sqlStack.shift(),
+ operations: this._curOps,
+ };
+ let s = (this._objsWritten++ ? ", " : "") + JSON.stringify(obj, null, 2);
+ this._ostream.write(s, s.length);
+
+ this._curOps = [];
+ },
+
+ observe: function esp_observe(aSubject, aTopic, aData) {
+ if (aTopic == "quit-application")
+ this.shutdown();
+ },
+
+ shutdown: function esp_shutdown() {
+ let s = "]}";
+ this._ostream.write(s, s.length);
+ this._ostream.close();
+
+ Services.obs.removeObserver(this, "quit-application");
+ }
+};
+
+// See the documentation on GlodaDatastore._schemaVersion to understand these:
+var DB_SCHEMA_ACCEPT_LEAVE_LOW = 31,
+ DB_SCHEMA_ACCEPT_LEAVE_HIGH = 34,
+ DB_SCHEMA_ACCEPT_DOWNGRADE_LOW = 35,
+ DB_SCHEMA_ACCEPT_DOWNGRADE_HIGH = 39,
+ DB_SCHEMA_DOWNGRADE_DELTA = 5;
+
+/**
+ * Database abstraction layer. Contains explicit SQL schemas for our
+ * fundamental representations (core 'nouns', if you will) as well as
+ * specialized functions for then dealing with each type of object. At the
+ * same time, we are beginning to support extension-provided tables, which
+ * call into question whether we really need our hand-rolled code, or could
+ * simply improve the extension-provided table case to work for most of our
+ * hand-rolled cases.
+ * For now, the argument can probably be made that our explicit schemas and code
+ * is readable/intuitive (not magic) and efficient (although generic stuff
+ * could also be made efficient, if slightly evil through use of eval or some
+ * other code generation mechanism.)
+ *
+ * === Data Model Interaction / Dependencies
+ *
+ * Dependent on and assumes limited knowledge of the datamodel.js
+ * implementations. datamodel.js actually has an implicit dependency on
+ * our implementation, reaching back into the datastore via the _datastore
+ * attribute which we pass into every instance we create.
+ * We pass a reference to ourself as we create the datamodel.js instances (and
+ * they store it as _datastore) because of a half-implemented attempt to make
+ * it possible to live in a world where we have multiple datastores. This
+ * would be desirable in the cases where we are dealing with multiple SQLite
+ * databases. This could be because of per-account global databases or
+ * some other segmentation. This was abandoned when the importance of
+ * per-account databases was diminished following public discussion, at least
+ * for the short-term, but no attempted was made to excise the feature or
+ * preclude it. (Merely a recognition that it's too much to try and implement
+ * correct right now, especially because our solution might just be another
+ * (aggregating) layer on top of things, rather than complicating the lower
+ * levels.)
+ *
+ * === Object Identity / Caching
+ *
+ * The issue of object identity is handled by integration with the collection.js
+ * provided GlodaCollectionManager. By "Object Identity", I mean that we only
+ * should ever have one object instance alive at a time that corresponds to
+ * an underlying database row in the database. Where possible we avoid
+ * performing database look-ups when we can check if the object is already
+ * present in memory; in practice, this means when we are asking for an object
+ * by ID. When we cannot avoid a database query, we attempt to make sure that
+ * we do not return a duplicate object instance, instead replacing it with the
+ * 'live' copy of the object. (Ideally, we would avoid any redundant
+ * construction costs, but that is not currently the case.)
+ * Although you should consult the GlodaCollectionManager for details, the
+ * general idea is that we have 'collections' which represent views of the
+ * database (based on a query) which use a single mechanism for double duty.
+ * The collections are registered with the collection manager via weak
+ * reference. The first 'duty' is that since the collections may be desired
+ * to be 'live views' of the data, we want them to update as changes occur.
+ * The weak reference allows the collection manager to track the 'live'
+ * collections and update them. The second 'duty' is the caching/object
+ * identity duty. In theory, every live item should be referenced by at least
+ * one collection, making it reachable for object identity/caching purposes.
+ * There is also an explicit (inclusive) caching layer present to both try and
+ * avoid poor performance from some of the costs of this strategy, as well as
+ * to try and keep track of objects that are being worked with that are not
+ * (yet) tracked by a collection. Using a size-bounded cache is clearly not
+ * a guarantee of correctness for this, but is suspected will work quite well.
+ * (Well enough to be dangerous because the inevitable failure case will not be
+ * expected.)
+ *
+ * The current strategy may not be the optimal one, feel free to propose and/or
+ * implement better ones, especially if you have numbers.
+ * The current strategy is not fully implemented in this file, but the common
+ * cases are believed to be covered. (Namely, we fail to purge items from the
+ * cache as they are purged from the database.)
+ *
+ * === Things That May Not Be Obvious (Gotchas)
+ *
+ * Although the schema includes "triggers", they are currently not used
+ * and were added when thinking about implementing the feature. We will
+ * probably implement this feature at some point, which is why they are still
+ * in there.
+ *
+ * We, and the layers above us, are not sufficiently thorough at cleaning out
+ * data from the database, and may potentially orphan it _as new functionality
+ * is added in the future at layers above us_. That is, currently we should
+ * not be leaking database rows, but we may in the future. This is because
+ * we/the layers above us lack a mechanism to track dependencies based on
+ * attributes. Say a plugin exists that extracts recipes from messages and
+ * relates them via an attribute. To do so, it must create new recipe rows
+ * in its own table as new recipes are discovered. No automatic mechanism
+ * will purge recipes as their source messages are purged, nor does any
+ * event-driven mechanism explicitly inform the plugin. (It could infer
+ * such an event from the indexing/attribute-providing process, or poll the
+ * states of attributes to accomplish this, but that is not desirable.) This
+ * needs to be addressed, and may be best addressed at layers above
+ * datastore.js.
+ * @namespace
+ */
+var GlodaDatastore = {
+ _log: null,
+
+ /* see Gloda's documentation for these constants */
+ kSpecialNotAtAll: 0,
+ kSpecialColumn: 16,
+ kSpecialColumnChildren: 16|1,
+ kSpecialColumnParent: 16|2,
+ kSpecialString: 32,
+ kSpecialFulltext: 64,
+ IGNORE_FACET: {},
+
+ kConstraintIdIn: 0,
+ kConstraintIn: 1,
+ kConstraintRanges: 2,
+ kConstraintEquals: 3,
+ kConstraintStringLike: 4,
+ kConstraintFulltext: 5,
+
+ /* ******************* SCHEMA ******************* */
+
+ /**
+ * Schema version policy. IMPORTANT! We expect the following potential things
+ * to happen in the life of gloda that can impact our schema and the ability
+ * to move between different versions of Thunderbird:
+ *
+ * - Fundamental changes to the schema so that two versions of Thunderbird
+ * cannot use the same global database. To wit, Thunderbird N+1 needs to
+ * blow away the database of Thunderbird N and reindex from scratch.
+ * Likewise, Thunderbird N will need to blow away Thunderbird N+1's
+ * database because it can't understand it. And we can't simply use a
+ * different file because there would be fatal bookkeeping losses.
+ *
+ * - Bidirectional minor schema changes (rare).
+ * Thunderbird N+1 does something that does not affect Thunderbird N's use
+ * of the database, and a user switching back to Thunderbird N will not be
+ * negatively impacted. It will also be fine when they go back to N+1 and
+ * N+1 will not be missing any vital data. The historic example of this is
+ * when we added a missing index that was important for performance. In
+ * that case, Thunderbird N could have potentially left the schema revision
+ * intact (if there was a safe revision), rather than swapping it on the
+ * downgrade, compelling N+1 to redo the transform on upgrade.
+ *
+ * - Backwards compatible, upgrade-transition minor schema changes.
+ * Thunderbird N+1 does something that does not require nuking the
+ * database / a full re-index, but does require processing on upgrade from
+ * a version of the database previously used by Thunderbird. These changes
+ * do not impact N's ability to use the database. For example, adding a
+ * new indexed attribute that affects a small number of messages could be
+ * handled by issuing a query on upgrade to dirty/index those messages.
+ * However, if the user goes back to N from N+1, when they upgrade to N+1
+ * again, we need to re-index. In this case N would need to have downgrade
+ * the schema revision.
+ *
+ * - Backwards incompatible, minor schema changes.
+ * Thunderbird N+1 does something that does not require nuking the database
+ * but will break Thunderbird N's ability to use the database.
+ *
+ * - Regression fixes. Sometimes we may land something that screws up
+ * databases, or the platform changes in a way that breaks our code and we
+ * had insufficient unit test coverage and so don't detect it until some
+ * databases have gotten messed up.
+ *
+ * Accordingly, every version of Thunderbird has a concept of potential schema
+ * versions with associated semantics to prepare for the minor schema upgrade
+ * cases were inter-op is possible. These ranges and their semantics are:
+ * - accepts and leaves intact. Covers:
+ * - regression fixes that no longer exist with the landing of the upgrade
+ * code as long as users never go back a build in the given channel.
+ * - bidirectional minor schema changes.
+ * - accepts but downgrades version to self. Covers:
+ * - backwards compatible, upgrade-transition minor schema changes.
+ * - nuke range (anything beyond a specific revision needs to be nuked):
+ * - backwards incompatible, minor scheme changes
+ * - fundamental changes
+ *
+ *
+ * SO, YOU WANT TO CHANGE THE SCHEMA?
+ *
+ * Use the ranges below for Thunderbird 11 as a guide, bumping things as little
+ * as possible. If we start to use up the "accepts and leaves intact" range
+ * without majorly changing things up, re-do the numbering acceptance range
+ * to give us additional runway.
+ *
+ * Also, if we keep needing non-nuking upgrades, consider adding an additional
+ * table to the database that can tell older versions of Thunderbird what to
+ * do when confronted with a newer database and where it can set flags to tell
+ * the newer Thunderbird what the older Thunderbird got up to. For example,
+ * it would be much easier if we just tell Thunderbird N what to do when it's
+ * confronted with the database.
+ *
+ *
+ * CURRENT STATE OF THE MIGRATION LOGIC:
+ *
+ * Thunderbird 11: uses 30 (regression fix from 26)
+ * - accepts and leaves intact: 31-34
+ * - accepts and downgrades by 5: 35-39
+ * - nukes: 40+
+ */
+ _schemaVersion: 30,
+ // what is the schema in the database right now?
+ _actualSchemaVersion: 0,
+ _schema: {
+ tables: {
+
+ // ----- Messages
+ folderLocations: {
+ columns: [
+ ["id", "INTEGER PRIMARY KEY"],
+ ["folderURI", "TEXT NOT NULL"],
+ ["dirtyStatus", "INTEGER NOT NULL"],
+ ["name", "TEXT NOT NULL"],
+ ["indexingPriority", "INTEGER NOT NULL"],
+ ],
+
+ triggers: {
+ delete: "DELETE from messages WHERE folderID = OLD.id",
+ },
+ },
+
+ conversations: {
+ columns: [
+ ["id", "INTEGER PRIMARY KEY"],
+ ["subject", "TEXT NOT NULL"],
+ ["oldestMessageDate", "INTEGER"],
+ ["newestMessageDate", "INTEGER"],
+ ],
+
+ indices: {
+ subject: ['subject'],
+ oldestMessageDate: ['oldestMessageDate'],
+ newestMessageDate: ['newestMessageDate'],
+ },
+
+ fulltextColumns: [
+ ["subject", "TEXT"],
+ ],
+
+ triggers: {
+ delete: "DELETE from messages WHERE conversationID = OLD.id",
+ },
+ },
+
+ /**
+ * A message record correspond to an actual message stored in a folder
+ * somewhere, or is a ghost record indicating a message that we know
+ * should exist, but which we have not seen (and which we may never see).
+ * We represent these ghost messages by storing NULL values in the
+ * folderID and messageKey fields; this may need to change to other
+ * sentinel values if this somehow impacts performance.
+ */
+ messages: {
+ columns: [
+ ["id", "INTEGER PRIMARY KEY"],
+ ["folderID", "INTEGER"],
+ ["messageKey", "INTEGER"],
+ // conversationID used to have a REFERENCES but I'm losing it for
+ // presumed performance reasons and it doesn't do anything for us.
+ ["conversationID", "INTEGER NOT NULL"],
+ ["date", "INTEGER"],
+ // we used to have the parentID, but because of the very real
+ // possibility of multiple copies of a message with a given
+ // message-id, the parentID concept is unreliable.
+ ["headerMessageID", "TEXT"],
+ ["deleted", "INTEGER NOT NULL default 0"],
+ ["jsonAttributes", "TEXT"],
+ // Notability attempts to capture the static 'interestingness' of a
+ // message as a result of being starred/flagged, labeled, read
+ // multiple times, authored by someone in your address book or that
+ // you converse with a lot, etc.
+ ["notability", "INTEGER NOT NULL default 0"],
+ ],
+
+ indices: {
+ messageLocation: ['folderID', 'messageKey'],
+ headerMessageID: ['headerMessageID'],
+ conversationID: ['conversationID'],
+ date: ['date'],
+ deleted: ['deleted'],
+ },
+
+ // note: if reordering the columns, you need to change this file's
+ // row-loading logic, msg_search.js's ranking usages and also the
+ // column saturations in nsGlodaRankerFunction
+ fulltextColumns: [
+ ["body", "TEXT"],
+ ["subject", "TEXT"],
+ ["attachmentNames", "TEXT"],
+ ["author", "TEXT"],
+ ["recipients", "TEXT"],
+ ],
+
+ triggers: {
+ delete: "DELETE FROM messageAttributes WHERE messageID = OLD.id",
+ },
+ },
+
+ // ----- Attributes
+ attributeDefinitions: {
+ columns: [
+ ["id", "INTEGER PRIMARY KEY"],
+ ["attributeType", "INTEGER NOT NULL"],
+ ["extensionName", "TEXT NOT NULL"],
+ ["name", "TEXT NOT NULL"],
+ ["parameter", "BLOB"],
+ ],
+
+ triggers: {
+ delete: "DELETE FROM messageAttributes WHERE attributeID = OLD.id",
+ },
+ },
+
+ messageAttributes: {
+ columns: [
+ // conversationID and messageID used to have REFERENCES back to their
+ // appropriate types. I removed it when removing attributeID for
+ // better reasons and because the code is not capable of violating
+ // this constraint, so the check is just added cost. (And we have
+ // unit tests that sanity check my assertions.)
+ ["conversationID", "INTEGER NOT NULL"],
+ ["messageID", "INTEGER NOT NULL"],
+ // This used to be REFERENCES attributeDefinitions(id) but then we
+ // introduced sentinel values and it's hard to justify the effort
+ // to compel injection of the record or the overhead to do the
+ // references checking.
+ ["attributeID", "INTEGER NOT NULL"],
+ ["value", "NUMERIC"],
+ ],
+
+ indices: {
+ attribQuery: [
+ "attributeID", "value",
+ /* covering: */ "conversationID", "messageID"],
+ // This is required for deletion of a message's attributes to be
+ // performant. We could optimize this index away if we changed our
+ // deletion logic to issue specific attribute deletions based on the
+ // information it already has available in the message's JSON blob.
+ // The rub there is that if we screwed up we could end up leaking
+ // attributes and there is a non-trivial performance overhead to
+ // the many requests it would cause (which can also be reduced in
+ // the future by changing our SQL dispatch code.)
+ messageAttribFastDeletion: [
+ "messageID"],
+ },
+ },
+
+ // ----- Contacts / Identities
+
+ /**
+ * Corresponds to a human being and roughly to an address book entry.
+ * Constrast with an identity, which is a specific e-mail address, IRC
+ * nick, etc. Identities belong to contacts, and this relationship is
+ * expressed on the identityAttributes table.
+ */
+ contacts: {
+ columns: [
+ ["id", "INTEGER PRIMARY KEY"],
+ ["directoryUUID", "TEXT"],
+ ["contactUUID", "TEXT"],
+ ["popularity", "INTEGER"],
+ ["frecency", "INTEGER"],
+ ["name", "TEXT"],
+ ["jsonAttributes", "TEXT"],
+ ],
+ indices: {
+ popularity: ["popularity"],
+ frecency: ["frecency"],
+ },
+ },
+
+ contactAttributes: {
+ columns: [
+ ["contactID", "INTEGER NOT NULL"],
+ ["attributeID",
+ "INTEGER NOT NULL"],
+ ["value", "NUMERIC"]
+ ],
+ indices: {
+ contactAttribQuery: [
+ "attributeID", "value",
+ /* covering: */ "contactID"],
+ }
+ },
+
+ /**
+ * Identities correspond to specific e-mail addresses, IRC nicks, etc.
+ */
+ identities: {
+ columns: [
+ ["id", "INTEGER PRIMARY KEY"],
+ ["contactID", "INTEGER NOT NULL"],
+ ["kind", "TEXT NOT NULL"], // ex: email, irc, etc.
+ ["value", "TEXT NOT NULL"], // ex: e-mail address, irc nick/handle...
+ ["description", "NOT NULL"], // what makes this identity different
+ // from the others? (ex: home, work, etc.)
+ ["relay", "INTEGER NOT NULL"], // is the identity just a relay
+ // mechanism? (ex: mailing list, twitter 'bouncer', IRC gateway, etc.)
+ ],
+
+ indices: {
+ contactQuery: ["contactID"],
+ valueQuery: ["kind", "value"]
+ }
+ },
+ },
+ },
+
+
+ /* ******************* LOGIC ******************* */
+ /**
+ * We only have one connection; this name exists for legacy reasons but helps
+ * track when we are intentionally doing synchronous things during startup.
+ * We do nothing synchronous once our setup has completed.
+ */
+ syncConnection: null,
+ /**
+ * We only have one connection and we only do asynchronous things after setup;
+ * this name still exists mainly for legacy reasons.
+ */
+ asyncConnection: null,
+
+ /**
+ * Our "mailnews.database.global.datastore." preferences branch for debug
+ * notification handling. We register as an observer against this.
+ */
+ _prefBranch: null,
+
+ /**
+ * The unique ID assigned to an index when it has been built. This value
+ * changes once the index has been rebuilt.
+ */
+ _datastoreID: null,
+
+ /**
+ * Initialize logging, create the database if it doesn't exist, "upgrade" it
+ * if it does and it's not up-to-date, fill our authoritative folder uri/id
+ * mapping.
+ */
+ _init: function gloda_ds_init(aNounIDToDef) {
+ this._log = Log4Moz.repository.getLogger("gloda.datastore");
+ this._log.debug("Beginning datastore initialization.");
+
+ this._nounIDToDef = aNounIDToDef;
+
+ let branch = Services.prefs.getBranch("mailnews.database.global.datastore.");
+ this._prefBranch = branch;
+
+ // Not sure the weak reference really makes a difference given that we are a
+ // GC root.
+ branch.addObserver("", this, false);
+ // claim the pref changed so we can centralize our logic there.
+ this.observe(null, "nsPref:changed", "explainToPath");
+
+ // Get the path to our global database
+ var dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ dbFile.append("global-messages-db.sqlite");
+
+ var dbConnection;
+
+ // Report about the size of the database through telemetry (if there's a
+ // database, naturally).
+ if (dbFile.exists()) {
+ try {
+ let h = Services.telemetry.getHistogramById("THUNDERBIRD_GLODA_SIZE_MB");
+ h.add(dbFile.fileSize/1048576);
+ } catch (e) {
+ this._log.warn("Couldn't report telemetry", e);
+ }
+ }
+
+ // Create the file if it does not exist
+ if (!dbFile.exists()) {
+ this._log.debug("Creating database because it doesn't exist.");
+ dbConnection = this._createDB(dbFile);
+ }
+ // It does exist, but we (someday) might need to upgrade the schema
+ else {
+ // (Exceptions may be thrown if the database is corrupt)
+ try {
+ dbConnection = Services.storage.openUnsharedDatabase(dbFile);
+ let cacheSize = this._determineCachePages(dbConnection);
+ // see _createDB...
+ dbConnection.executeSimpleSQL("PRAGMA cache_size = "+cacheSize);
+ dbConnection.executeSimpleSQL("PRAGMA synchronous = FULL");
+
+ // Register custom tokenizer to index all language text
+ var tokenizer = Cc["@mozilla.org/messenger/fts3tokenizer;1"].
+ getService(Ci.nsIFts3Tokenizer);
+ tokenizer.registerTokenizer(dbConnection);
+
+ // -- database schema changes
+ let dbSchemaVersion = this._actualSchemaVersion =
+ dbConnection.schemaVersion;
+ // - database from the future!
+ if (dbSchemaVersion > this._schemaVersion) {
+ if (dbSchemaVersion >= DB_SCHEMA_ACCEPT_LEAVE_LOW &&
+ dbSchemaVersion <= DB_SCHEMA_ACCEPT_LEAVE_HIGH) {
+ this._log.debug("db from the future in acceptable range; leaving " +
+ "version at: " + dbSchemaVersion);
+ }
+ else if (dbSchemaVersion >= DB_SCHEMA_ACCEPT_DOWNGRADE_LOW &&
+ dbSchemaVersion <= DB_SCHEMA_ACCEPT_DOWNGRADE_HIGH) {
+ let newVersion = dbSchemaVersion - DB_SCHEMA_DOWNGRADE_DELTA;
+ this._log.debug("db from the future in downgrade range; setting " +
+ "version to " + newVersion + " down from " +
+ dbSchemaVersion);
+ dbConnection.schemaVersion = this._actualSchemaVersion = newVersion;
+ }
+ // too far from the future, nuke it.
+ else {
+ dbConnection = this._nukeMigration(dbFile, dbConnection);
+ }
+ }
+ // - database from the past! migrate it, possibly.
+ else if (dbSchemaVersion < this._schemaVersion) {
+ this._log.debug("Need to migrate database. (DB version: " +
+ this._actualSchemaVersion + " desired version: " +
+ this._schemaVersion);
+ dbConnection = this._migrate(dbFile,
+ dbConnection,
+ this._actualSchemaVersion,
+ this._schemaVersion);
+ this._log.debug("Migration call completed.");
+ }
+ // else: this database is juuust right.
+
+ // If we never had a datastore ID, make sure to create one now.
+ if (!this._prefBranch.prefHasUserValue("id")) {
+ this._datastoreID = this._generateDatastoreID();
+ this._prefBranch.setCharPref("id", this._datastoreID);
+ } else {
+ this._datastoreID = this._prefBranch.getCharPref("id");
+ }
+ }
+ // Handle corrupt databases, other oddities
+ catch (ex) {
+ if (ex.result == Cr.NS_ERROR_FILE_CORRUPTED) {
+ this._log.warn("Database was corrupt, removing the old one.");
+ dbFile.remove(false);
+ this._log.warn("Removed old database, creating a new one.");
+ dbConnection = this._createDB(dbFile);
+ }
+ else {
+ this._log.error("Unexpected error when trying to open the database:",
+ ex);
+ throw ex;
+ }
+ }
+ }
+
+ this.syncConnection = dbConnection;
+ this.asyncConnection = dbConnection;
+
+ this._log.debug("Initializing folder mappings.");
+ this._getAllFolderMappings();
+ // we need to figure out the next id's for all of the tables where we
+ // manage that.
+ this._log.debug("Populating managed id counters.");
+ this._populateAttributeDefManagedId();
+ this._populateConversationManagedId();
+ this._populateMessageManagedId();
+ this._populateContactManagedId();
+ this._populateIdentityManagedId();
+
+ // create the timer we use to periodically drop our references to folders
+ // we no longer need XPCOM references to (or more significantly, their
+ // message databases.)
+ this._folderCleanupTimer =
+ Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ this._log.debug("Completed datastore initialization.");
+ },
+
+ observe: function gloda_ds_observe(aSubject, aTopic, aData) {
+ if(aTopic != "nsPref:changed")
+ return;
+
+ if (aData == "explainToPath") {
+ let explainToPath = null;
+ try {
+ explainToPath = this._prefBranch.getCharPref("explainToPath");
+ if (explainToPath.trim() == "")
+ explainToPath = null;
+ }
+ catch (ex) {
+ // don't care if the pref is not there.
+ }
+
+ // It is conceivable that the name is changing and this isn't a boolean
+ // toggle, so always clean out the explain processor.
+ if (this._explainProcessor) {
+ this._explainProcessor.shutdown();
+ this._explainProcessor = null;
+ }
+
+ if (explainToPath) {
+ this._createAsyncStatement = this._createExplainedAsyncStatement;
+ this._explainProcessor = new ExplainedStatementProcessor(
+ explainToPath);
+ }
+ else {
+ this._createAsyncStatement = this._realCreateAsyncStatement;
+ }
+ }
+ },
+
+ datastoreIsShutdown: false,
+
+ /**
+ * Perform datastore shutdown.
+ */
+ shutdown: function gloda_ds_shutdown() {
+ // Clear out any pending transaction by committing it.
+ // The indexer has been shutdown by this point; it no longer has any active
+ // indexing logic and it no longer has active event listeners capable of
+ // generating new activity.
+ // Semantic consistency of the database is guaranteed by the indexer's
+ // strategy of only yielding control at coherent times. Although it takes
+ // multiple calls and multiple SQL operations to update the state of our
+ // database representations, the generator does not yield until it has
+ // issued all the database statements required for said update. As such,
+ // this commit will leave us in a good way (and the commit will happen
+ // because closing the connection will drain the async execution queue.)
+ while (this._transactionDepth) {
+ this._log.info("Closing pending transaction out for shutdown.");
+ // just schedule this function to be run again once the transaction has
+ // been closed out.
+ this._commitTransaction();
+ }
+
+ this.datastoreIsShutdown = true;
+
+ // shutdown our folder cleanup timer, if active and null it out.
+ if (this._folderCleanupActive)
+ this._folderCleanupTimer.cancel();
+ this._folderCleanupTimer = null;
+
+ this._log.info("Closing db connection");
+
+ // we do not expect exceptions, but it's a good idea to avoid having our
+ // shutdown process explode.
+ try {
+ this._cleanupAsyncStatements();
+ this._cleanupSyncStatements();
+ }
+ catch (ex) {
+ this._log.debug("Unexpected exception during statement cleanup: " + ex);
+ }
+
+ // it's conceivable we might get a spurious exception here, but we really
+ // shouldn't get one. again, we want to ensure shutdown runs to completion
+ // and doesn't break our caller.
+ try {
+ // This currently causes all pending asynchronous operations to be run to
+ // completion. this simplifies things from a correctness perspective,
+ // and, honestly, is a lot easier than us tracking all of the async
+ // event tasks so that we can explicitly cancel them.
+ // This is a reasonable thing to do because we don't actually ever have
+ // a huge number of statements outstanding. The indexing process needs
+ // to issue async requests periodically, so the most we have in-flight
+ // from a write perspective is strictly less than the work required to
+ // update the database state for a single message.
+ // However, the potential for multiple pending expensive queries does
+ // exist, and it may be advisable to attempt to track and cancel those.
+ // For simplicity we don't currently do this, and I expect this should
+ // not pose a major problem, but those are famous last words.
+ // Note: asyncClose does not spin a nested event loop, but the thread
+ // manager shutdown code will spin the async thread's event loop, so it
+ // nets out to be the same.
+ this.asyncConnection.asyncClose();
+ }
+ catch (ex) {
+ this._log.debug("Potentially expected exception during connection " +
+ "closure: " + ex);
+ }
+
+ this.asyncConnection = null;
+ this.syncConnection = null;
+ },
+
+ /**
+ * Generates and returns a UUID.
+ *
+ * @return a UUID as a string, ex: "c4dd0159-9287-480f-a648-a4613e147fdb"
+ */
+ _generateDatastoreID: function gloda_ds_generateDatastoreID() {
+ let uuidGen = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator);
+ let uuid = uuidGen.generateUUID().toString();
+ // We snip off the { and } from each end of the UUID.
+ return uuid.substring(1, uuid.length - 2);
+ },
+
+ _determineCachePages: function gloda_ds_determineCachePages(aDBConn) {
+ try {
+ // For the details of the computations, one should read
+ // nsNavHistory::InitDB. We're slightly diverging from them in the sense
+ // that we won't allow gloda to use insane amounts of memory cache, and
+ // we start with 1% instead of 6% like them.
+ let pageStmt = aDBConn.createStatement("PRAGMA page_size");
+ pageStmt.executeStep();
+ let pageSize = pageStmt.row.page_size;
+ pageStmt.finalize();
+ let cachePermillage = this._prefBranch
+ .getIntPref("cache_to_memory_permillage");
+ cachePermillage = Math.min(cachePermillage, 50);
+ cachePermillage = Math.max(cachePermillage, 0);
+ let physMem = IOUtils.getPhysicalMemorySize();
+ if (physMem == 0)
+ physMem = MEMSIZE_FALLBACK_BYTES;
+ let cacheSize = Math.round(physMem * cachePermillage / 1000);
+ cacheSize = Math.max(cacheSize, MIN_CACHE_SIZE);
+ cacheSize = Math.min(cacheSize, MAX_CACHE_SIZE);
+ let cachePages = Math.round(cacheSize / pageSize);
+ return cachePages;
+ } catch (ex) {
+ this._log.warn("Error determining cache size: " + ex);
+ // A little bit lower than on my personal machine, will result in ~40M.
+ return 1000;
+ }
+ },
+
+ /**
+ * Create our database; basically a wrapper around _createSchema.
+ */
+ _createDB: function gloda_ds_createDB(aDBFile) {
+ var dbConnection = Services.storage.openUnsharedDatabase(aDBFile);
+ // We now follow the Firefox strategy for places, which mainly consists in
+ // picking a default 32k page size, and then figuring out the amount of
+ // cache accordingly. The default 32k come from mozilla/toolkit/storage,
+ // but let's get it directly from sqlite in case they change it.
+ let cachePages = this._determineCachePages(dbConnection);
+ // This is a maximum number of pages to be used. If the database does not
+ // get this large, then the memory does not get used.
+ // Do not forget to update the code in _init if you change this value.
+ dbConnection.executeSimpleSQL("PRAGMA cache_size = "+cachePages);
+ // The mozStorage default is NORMAL which shaves off some fsyncs in the
+ // interest of performance. Since everything we do after bootstrap is
+ // async, we do not care about the performance, but we really want the
+ // correctness. Bug reports and support avenues indicate a non-zero number
+ // of corrupt databases. Note that this may not fix everything; OS X
+ // also supports an F_FULLSYNC flag enabled by PRAGMA fullfsync that we are
+ // not enabling that is much more comprehensive. We can think about
+ // turning that on after we've seen how this reduces our corruption count.
+ dbConnection.executeSimpleSQL("PRAGMA synchronous = FULL");
+ // Register custom tokenizer to index all language text
+ var tokenizer = Cc["@mozilla.org/messenger/fts3tokenizer;1"].
+ getService(Ci.nsIFts3Tokenizer);
+ tokenizer.registerTokenizer(dbConnection);
+
+ // We're creating a new database, so let's generate a new ID for this
+ // version of the datastore. This way, indexers can know when the index
+ // has been rebuilt in the event that they need to rebuild dependent data.
+ this._datastoreID = this._generateDatastoreID();
+ this._prefBranch.setCharPref("id", this._datastoreID);
+
+ dbConnection.beginTransaction();
+ try {
+ this._createSchema(dbConnection);
+ dbConnection.commitTransaction();
+ }
+ catch(ex) {
+ dbConnection.rollbackTransaction();
+ throw ex;
+ }
+
+ return dbConnection;
+ },
+
+ _createTableSchema: function gloda_ds_createTableSchema(aDBConnection,
+ aTableName, aTableDef) {
+ // - Create the table
+ this._log.info("Creating table: " + aTableName);
+ let columnDefs = [];
+ for (let [column, type] of aTableDef.columns) {
+ columnDefs.push(column + " " + type);
+ }
+ aDBConnection.createTable(aTableName, columnDefs.join(", "));
+
+ // - Create the fulltext table if applicable
+ if (aTableDef.fulltextColumns) {
+ let columnDefs = [];
+ for (let [column, type] of aTableDef.fulltextColumns) {
+ columnDefs.push(column + " " + type);
+ }
+ let createFulltextSQL = "CREATE VIRTUAL TABLE " + aTableName + "Text" +
+ " USING fts3(tokenize mozporter, " + columnDefs.join(", ") + ")";
+ this._log.info("Creating fulltext table: " + createFulltextSQL);
+ aDBConnection.executeSimpleSQL(createFulltextSQL);
+ }
+
+ // - Create its indices
+ if (aTableDef.indices) {
+ for (let indexName in aTableDef.indices) {
+ let indexColumns = aTableDef.indices[indexName];
+ aDBConnection.executeSimpleSQL(
+ "CREATE INDEX " + indexName + " ON " + aTableName +
+ "(" + indexColumns.join(", ") + ")");
+ }
+ }
+
+ // - Create the attributes table if applicable
+ if (aTableDef.genericAttributes) {
+ aTableDef.genericAttributes = {
+ columns: [
+ ["nounID", "INTEGER NOT NULL"],
+ ["attributeID", "INTEGER NOT NULL"],
+ ["value", "NUMERIC"]
+ ],
+ indices: {}
+ };
+ aTableDef.genericAttributes.indices[aTableName + "AttribQuery"] =
+ ["attributeID", "value", /* covering: */ "nounID"];
+ // let's use this very function! (since we created genericAttributes,
+ // explodey recursion is avoided.)
+ this._createTableSchema(aDBConnection, aTableName + "Attributes",
+ aTableDef.genericAttributes);
+ }
+ },
+
+ /**
+ * Create our database schema assuming a newly created database. This
+ * comes down to creating normal tables, their full-text variants (if
+ * applicable), and their indices.
+ */
+ _createSchema: function gloda_ds_createSchema(aDBConnection) {
+ // -- For each table...
+ for (let tableName in this._schema.tables) {
+ let tableDef = this._schema.tables[tableName];
+ this._createTableSchema(aDBConnection, tableName, tableDef);
+ }
+
+ aDBConnection.schemaVersion = this._actualSchemaVersion =
+ this._schemaVersion;
+ },
+
+ /**
+ * Create a table for a noun, replete with data binding.
+ */
+ createNounTable: function gloda_ds_createTableIfNotExists(aNounDef) {
+ // give it a _jsonText attribute if appropriate...
+ if (aNounDef.allowsArbitraryAttrs)
+ aNounDef.schema.columns.push(['jsonAttributes', 'STRING', '_jsonText']);
+ // check if the table exists
+ if (!this.asyncConnection.tableExists(aNounDef.tableName)) {
+ // it doesn't! create it (and its potentially many variants)
+ try {
+ this._createTableSchema(this.asyncConnection, aNounDef.tableName,
+ aNounDef.schema);
+ }
+ catch (ex) {
+ this._log.error("Problem creating table " + aNounDef.tableName + " " +
+ "because: " + ex + " at " + ex.fileName + ":" + ex.lineNumber);
+ return;
+ }
+ }
+
+ aNounDef._dataBinder = new GlodaDatabind(aNounDef, this);
+ aNounDef.datastore = aNounDef._dataBinder;
+ aNounDef.objFromRow = aNounDef._dataBinder.objFromRow;
+ aNounDef.objInsert = aNounDef._dataBinder.objInsert;
+ aNounDef.objUpdate = aNounDef._dataBinder.objUpdate;
+ aNounDef.dbAttribAdjuster = aNounDef._dataBinder.adjustAttributes;
+
+ if (aNounDef.schema.genericAttributes) {
+ aNounDef.attrTableName = aNounDef.tableName + "Attributes";
+ aNounDef.attrIDColumnName = "nounID";
+ }
+ },
+
+ _nukeMigration: function gloda_ds_nukeMigration(aDBFile, aDBConnection) {
+ aDBConnection.close();
+ aDBFile.remove(false);
+ this._log.warn("Global database has been purged due to schema change. " +
+ "old version was " + this._actualSchemaVersion +
+ ", new version is: " + this._schemaVersion);
+ return this._createDB(aDBFile);
+ },
+
+ /**
+ * Migrate the database _to the latest version_ from an older version. We
+ * only keep enough logic around to get us to the recent version. This code
+ * is not a time machine! If we need to blow away the database to get to the
+ * most recent version, then that's the sum total of the migration!
+ */
+ _migrate: function gloda_ds_migrate(aDBFile, aDBConnection,
+ aCurVersion, aNewVersion) {
+
+ // version 12:
+ // - notability column added
+ // version 13:
+ // - we are adding a new fulltext index column. blow away!
+ // - note that I screwed up and failed to mark the schema change; apparently
+ // no database will claim to be version 13...
+ // version 14ish, still labeled 13?:
+ // - new attributes: forwarded, repliedTo, bcc, recipients
+ // - altered fromMeTo and fromMeCc to fromMe
+ // - altered toMe and ccMe to just be toMe
+ // - exposes bcc to cc-related attributes
+ // - MIME type DB schema overhaul
+ // version 15ish, still labeled 13:
+ // - change tokenizer to mozporter to support CJK
+ // (We are slip-streaming this so that only people who want to test CJK
+ // have to test it. We will properly bump the schema revision when the
+ // gloda correctness patch lands.)
+ // version 16ish, labeled 14 and now 16
+ // - gloda message id's start from 32 now
+ // - all kinds of correctness changes (blow away)
+ // version 17
+ // - more correctness fixes. (blow away)
+ // version 18
+ // - significant empty set support (blow away)
+ // version 19
+ // - there was a typo that was resulting in deleted getting set to the
+ // numeric value of the javascript undefined value. (migrate-able)
+ // version 20
+ // - tokenizer changes to provide for case/accent-folding. (blow away)
+ // version 21
+ // - add the messagesAttribFastDeletion index we thought was already covered
+ // by an index we removed a while ago (migrate-able)
+ // version 26
+ // - bump page size and also cache size (blow away)
+ // version 30
+ // - recover from bug 732372 that affected TB 11 beta / TB 12 alpha / TB 13
+ // trunk. The fix is bug 734507. The revision bump happens
+ // asynchronously. (migrate-able)
+
+ // nuke if prior to 26
+ if (aCurVersion < 26)
+ return this._nukeMigration(aDBFile, aDBConnection);
+
+ // They must be desiring our "a.contact is undefined" fix!
+ // This fix runs asynchronously as the first indexing job the indexer ever
+ // performs. It is scheduled by the enabling of the message indexer and
+ // it is the one that updates the schema version when done.
+
+ // return the same DB connection since we didn't create a new one or do
+ // anything.
+ return aDBConnection;
+ },
+
+ /**
+ * Asynchronously update the schema version; only for use by in-tree callers
+ * who asynchronously perform migration work triggered by their initial
+ * indexing sweep and who have properly updated the schema version in all
+ * the appropriate locations in this file.
+ *
+ * This is done without doing anything about the current transaction state,
+ * which is desired.
+ */
+ _updateSchemaVersion: function(newSchemaVersion) {
+ this._actualSchemaVersion = newSchemaVersion;
+ let stmt = this._createAsyncStatement(
+ // we need to concat; pragmas don't like "?1" binds
+ "PRAGMA user_version = " + newSchemaVersion, true);
+ stmt.executeAsync(this.trackAsync());
+ stmt.finalize();
+ },
+
+ _outstandingAsyncStatements: [],
+
+ /**
+ * Unless debugging, this is just _realCreateAsyncStatement, but in some
+ * debugging modes this is instead the helpful wrapper
+ * _createExplainedAsyncStatement.
+ */
+ _createAsyncStatement: null,
+
+ _realCreateAsyncStatement: function gloda_ds_createAsyncStatement(aSQLString,
+ aWillFinalize) {
+ let statement = null;
+ try {
+ statement = this.asyncConnection.createAsyncStatement(aSQLString);
+ }
+ catch(ex) {
+ throw new Error("error creating async statement " + aSQLString + " - " +
+ this.asyncConnection.lastError + ": " +
+ this.asyncConnection.lastErrorString + " - " + ex);
+ }
+
+ if (!aWillFinalize)
+ this._outstandingAsyncStatements.push(statement);
+
+ return statement;
+ },
+
+ /**
+ * The ExplainedStatementProcessor instance used by
+ * _createExplainedAsyncStatement. This will be null if
+ * _createExplainedAsyncStatement is not being used as _createAsyncStatement.
+ */
+ _explainProcessor: null,
+
+ /**
+ * Wrapped version of _createAsyncStatement that EXPLAINs the statement. When
+ * used this decorates _createAsyncStatement, in which case we are found at
+ * that name and the original is at _orig_createAsyncStatement. This is
+ * controlled by the explainToPath preference (see |_init|).
+ */
+ _createExplainedAsyncStatement:
+ function gloda_ds__createExplainedAsyncStatement(aSQLString,
+ aWillFinalize) {
+ let realStatement = this._realCreateAsyncStatement(aSQLString,
+ aWillFinalize);
+ // don't wrap transaction control statements.
+ if (aSQLString == "COMMIT" ||
+ aSQLString == "BEGIN TRANSACTION" ||
+ aSQLString == "ROLLBACK")
+ return realStatement;
+
+ let explainSQL = "EXPLAIN " + aSQLString;
+ let explainStatement = this._realCreateAsyncStatement(explainSQL);
+
+ return new ExplainedStatementWrapper(realStatement, explainStatement,
+ aSQLString, this._explainProcessor);
+ },
+
+ _cleanupAsyncStatements: function gloda_ds_cleanupAsyncStatements() {
+ this._outstandingAsyncStatements.forEach(stmt => stmt.finalize());
+ },
+
+ _outstandingSyncStatements: [],
+
+ _createSyncStatement: function gloda_ds_createSyncStatement(aSQLString,
+ aWillFinalize) {
+ let statement = null;
+ try {
+ statement = this.syncConnection.createStatement(aSQLString);
+ }
+ catch(ex) {
+ throw new Error("error creating sync statement " + aSQLString + " - " +
+ this.syncConnection.lastError + ": " +
+ this.syncConnection.lastErrorString + " - " + ex);
+ }
+
+ if (!aWillFinalize)
+ this._outstandingSyncStatements.push(statement);
+
+ return statement;
+ },
+
+ _cleanupSyncStatements: function gloda_ds_cleanupSyncStatements() {
+ this._outstandingSyncStatements.forEach(stmt => stmt.finalize());
+ },
+
+ /**
+ * Perform a synchronous executeStep on the statement, handling any
+ * SQLITE_BUSY fallout that could conceivably happen from a collision on our
+ * read with the async writes.
+ * Basically we keep trying until we succeed or run out of tries.
+ * We believe this to be a reasonable course of action because we don't
+ * expect this to happen much.
+ */
+ _syncStep: function gloda_ds_syncStep(aStatement) {
+ let tries = 0;
+ while (tries < 32000) {
+ try {
+ return aStatement.executeStep();
+ }
+ catch (e) {
+ // SQLITE_BUSY becomes NS_ERROR_FAILURE
+ if (e.result == 0x80004005) {
+ tries++;
+ // we really need to delay here, somehow. unfortunately, we can't
+ // allow event processing to happen, and most of the things we could
+ // do to delay ourselves result in event processing happening. (Use
+ // of a timer, a synchronous dispatch, etc.)
+ // in theory, nsIThreadEventFilter could allow us to stop other events
+ // that aren't our timer from happening, but it seems slightly
+ // dangerous and 'notxpcom' suggests it ain't happening anyways...
+ // so, let's just be dumb and hope that the underlying file I/O going
+ // on makes us more likely to yield to the other thread so it can
+ // finish what it is doing...
+ } else {
+ throw e;
+ }
+ }
+ }
+ this._log.error("Synchronous step gave up after " + tries + " tries.");
+ },
+
+ /**
+ * Helper to bind based on the actual type of the javascript value. Note
+ * that we always use int64 because under the hood sqlite just promotes the
+ * normal 'int' call to 'int64' anyways.
+ */
+ _bindVariant: function gloda_ds_bindBlob(aStatement, aIndex, aVariant) {
+ if (aVariant == null) // catch both null and undefined
+ aStatement.bindNullParameter(aIndex);
+ else if (typeof aVariant == "string")
+ aStatement.bindStringParameter(aIndex, aVariant);
+ else if (typeof aVariant == "number") {
+ // we differentiate for storage representation reasons only.
+ if (Math.floor(aVariant) === aVariant)
+ aStatement.bindInt64Parameter(aIndex, aVariant);
+ else
+ aStatement.bindDoubleParameter(aIndex, aVariant);
+ }
+ else
+ throw new Error("Attempt to bind variant with unsupported type: " +
+ (typeof aVariant));
+ },
+
+ /**
+ * Helper that uses the appropriate getter given the data type; should be
+ * mooted once we move to 1.9.2 and can use built-in variant support.
+ */
+ _getVariant: function gloda_ds_getBlob(aRow, aIndex) {
+ let typeOfIndex = aRow.getTypeOfIndex(aIndex);
+ if (typeOfIndex == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ return null;
+ // XPConnect would just end up going through an intermediary double stage
+ // for the int64 case anyways...
+ else if (typeOfIndex == Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER ||
+ typeOfIndex == Ci.mozIStorageValueArray.VALUE_TYPE_DOUBLE)
+ return aRow.getDouble(aIndex);
+ else // typeOfIndex == Ci.mozIStorageValueArray.VALUE_TYPE_TEXT
+ return aRow.getString(aIndex);
+ },
+
+ /** Simple nested transaction support as a performance optimization. */
+ _transactionDepth: 0,
+ _transactionGood: false,
+
+ /**
+ * Self-memoizing BEGIN TRANSACTION statement.
+ */
+ get _beginTransactionStatement() {
+ let statement = this._createAsyncStatement("BEGIN TRANSACTION");
+ this.__defineGetter__("_beginTransactionStatement", () => statement);
+ return this._beginTransactionStatement;
+ },
+
+ /**
+ * Self-memoizing COMMIT statement.
+ */
+ get _commitTransactionStatement() {
+ let statement = this._createAsyncStatement("COMMIT");
+ this.__defineGetter__("_commitTransactionStatement", () => statement);
+ return this._commitTransactionStatement;
+ },
+
+ /**
+ * Self-memoizing ROLLBACK statement.
+ */
+ get _rollbackTransactionStatement() {
+ let statement = this._createAsyncStatement("ROLLBACK");
+ this.__defineGetter__("_rollbackTransactionStatement", () => statement);
+ return this._rollbackTransactionStatement;
+ },
+
+ _pendingPostCommitCallbacks: null,
+ /**
+ * Register a callback to be invoked when the current transaction's commit
+ * completes.
+ */
+ runPostCommit: function gloda_ds_runPostCommit(aCallback) {
+ this._pendingPostCommitCallbacks.push(aCallback);
+ },
+
+ /**
+ * Begin a potentially nested transaction; only the outermost transaction gets
+ * to be an actual transaction, and the failure of any nested transaction
+ * results in a rollback of the entire outer transaction. If you really
+ * need an atomic transaction
+ */
+ _beginTransaction: function gloda_ds_beginTransaction() {
+ if (this._transactionDepth == 0) {
+ this._pendingPostCommitCallbacks = [];
+ this._beginTransactionStatement.executeAsync(this.trackAsync());
+ this._transactionGood = true;
+ }
+ this._transactionDepth++;
+ },
+ /**
+ * Commit a potentially nested transaction; if we are the outer-most
+ * transaction and no sub-transaction issues a rollback
+ * (via _rollbackTransaction) then we commit, otherwise we rollback.
+ */
+ _commitTransaction: function gloda_ds_commitTransaction() {
+ this._transactionDepth--;
+ if (this._transactionDepth == 0) {
+ try {
+ if (this._transactionGood)
+ this._commitTransactionStatement.executeAsync(
+ new PostCommitHandler(this._pendingPostCommitCallbacks));
+ else
+ this._rollbackTransactionStatement.executeAsync(this.trackAsync());
+ }
+ catch (ex) {
+ this._log.error("Commit problem:", ex);
+ }
+ this._pendingPostCommitCallbacks = [];
+ }
+ },
+ /**
+ * Abort the commit of the potentially nested transaction. If we are not the
+ * outermost transaction, we set a flag that tells the outermost transaction
+ * that it must roll back.
+ */
+ _rollbackTransaction: function gloda_ds_rollbackTransaction() {
+ this._transactionDepth--;
+ this._transactionGood = false;
+ if (this._transactionDepth == 0) {
+ try {
+ this._rollbackTransactionStatement.executeAsync(this.trackAsync());
+ }
+ catch (ex) {
+ this._log.error("Rollback problem:", ex);
+ }
+ }
+ },
+
+ _pendingAsyncStatements: 0,
+ /**
+ * The function to call, if any, when we hit 0 pending async statements.
+ */
+ _pendingAsyncCompletedListener: null,
+ _asyncCompleted: function () {
+ if (--this._pendingAsyncStatements == 0) {
+ if (this._pendingAsyncCompletedListener !== null) {
+ this._pendingAsyncCompletedListener();
+ this._pendingAsyncCompletedListener = null;
+ }
+ }
+ },
+ _asyncTrackerListener: {
+ handleResult: function () {},
+ handleError: function(aError) {
+ GlodaDatastore._log.error("got error in _asyncTrackerListener.handleError(): " +
+ aError.result + ": " + aError.message);
+ },
+ handleCompletion: function () {
+ try {
+ // the helper method exists because the other classes need to call it too
+ GlodaDatastore._asyncCompleted();
+ }
+ catch (e) {
+ this._log.error("Exception in handleCompletion:", e);
+ }
+ }
+ },
+ /**
+ * Increments _pendingAsyncStatements and returns a listener that will
+ * decrement the value when the statement completes.
+ */
+ trackAsync: function() {
+ this._pendingAsyncStatements++;
+ return this._asyncTrackerListener;
+ },
+
+ /* ********** Attribute Definitions ********** */
+ /** Maps (attribute def) compound names to the GlodaAttributeDBDef objects. */
+ _attributeDBDefs: {},
+ /** Map attribute ID to the definition and parameter value that produce it. */
+ _attributeIDToDBDefAndParam: {},
+
+ /**
+ * This attribute id indicates that we are encoding that a non-singular
+ * attribute has an empty set. The value payload that goes with this should
+ * the attribute id of the attribute we are talking about.
+ */
+ kEmptySetAttrId: 1,
+
+ /**
+ * We maintain the attributeDefinitions next id counter mainly because we can.
+ * Since we mediate the access, there's no real risk to doing so, and it
+ * allows us to keep the writes on the async connection without having to
+ * wait for a completion notification.
+ *
+ * Start from 32 so we can have a number of sentinel values.
+ */
+ _nextAttributeId: 32,
+
+ _populateAttributeDefManagedId: function () {
+ let stmt = this._createSyncStatement(
+ "SELECT MAX(id) FROM attributeDefinitions", true);
+ if (stmt.executeStep()) { // no chance of this SQLITE_BUSY on this call
+ // 0 gets returned even if there are no messages...
+ let highestSeen = stmt.getInt64(0);
+ if (highestSeen != 0)
+ this._nextAttributeId = highestSeen + 1;
+ }
+ stmt.finalize();
+ },
+
+ get _insertAttributeDefStatement() {
+ let statement = this._createAsyncStatement(
+ "INSERT INTO attributeDefinitions (id, attributeType, extensionName, \
+ name, parameter) \
+ VALUES (?1, ?2, ?3, ?4, ?5)");
+ this.__defineGetter__("_insertAttributeDefStatement", () => statement);
+ return this._insertAttributeDefStatement;
+ },
+
+ /**
+ * Create an attribute definition and return the row ID. Special/atypical
+ * in that it doesn't directly return a GlodaAttributeDBDef; we leave that up
+ * to the caller since they know much more than actually needs to go in the
+ * database.
+ *
+ * @return The attribute id allocated to this attribute.
+ */
+ _createAttributeDef: function gloda_ds_createAttributeDef(aAttrType,
+ aExtensionName, aAttrName, aParameter) {
+ let attributeId = this._nextAttributeId++;
+
+ let iads = this._insertAttributeDefStatement;
+ iads.bindInt64Parameter(0, attributeId);
+ iads.bindInt64Parameter(1, aAttrType);
+ iads.bindStringParameter(2, aExtensionName);
+ iads.bindStringParameter(3, aAttrName);
+ this._bindVariant(iads, 4, aParameter);
+
+ iads.executeAsync(this.trackAsync());
+
+ return attributeId;
+ },
+
+ /**
+ * Sync-ly look-up all the attribute definitions, populating our authoritative
+ * _attributeDBDefss and _attributeIDToDBDefAndParam maps. (In other words,
+ * once this method is called, those maps should always be in sync with the
+ * underlying database.)
+ */
+ getAllAttributes: function gloda_ds_getAllAttributes() {
+ let stmt = this._createSyncStatement(
+ "SELECT id, attributeType, extensionName, name, parameter \
+ FROM attributeDefinitions", true);
+
+ // map compound name to the attribute
+ let attribs = {};
+ // map the attribute id to [attribute, parameter] where parameter is null
+ // in cases where parameter is unused.
+ let idToAttribAndParam = {};
+
+ this._log.info("loading all attribute defs");
+
+ while (stmt.executeStep()) { // no chance of this SQLITE_BUSY on this call
+ let rowId = stmt.getInt64(0);
+ let rowAttributeType = stmt.getInt64(1);
+ let rowExtensionName = stmt.getString(2);
+ let rowName = stmt.getString(3);
+ let rowParameter = this._getVariant(stmt, 4);
+
+ let compoundName = rowExtensionName + ":" + rowName;
+
+ let attrib;
+ if (compoundName in attribs) {
+ attrib = attribs[compoundName];
+ } else {
+ attrib = new GlodaAttributeDBDef(this, /* aID */ null,
+ compoundName, rowAttributeType, rowExtensionName, rowName);
+ attribs[compoundName] = attrib;
+ }
+ // if the parameter is null, the id goes on the attribute def, otherwise
+ // it is a parameter binding and goes in the binding map.
+ if (rowParameter == null) {
+ this._log.debug(compoundName + " primary: " + rowId);
+ attrib._id = rowId;
+ idToAttribAndParam[rowId] = [attrib, null];
+ } else {
+ this._log.debug(compoundName + " binding: " + rowParameter +
+ " = " + rowId);
+ attrib._parameterBindings[rowParameter] = rowId;
+ idToAttribAndParam[rowId] = [attrib, rowParameter];
+ }
+ }
+ stmt.finalize();
+
+ this._log.info("done loading all attribute defs");
+
+ this._attributeDBDefs = attribs;
+ this._attributeIDToDBDefAndParam = idToAttribAndParam;
+ },
+
+ /**
+ * Helper method for GlodaAttributeDBDef to tell us when their bindParameter
+ * method is called and they have created a new binding (using
+ * GlodaDatastore._createAttributeDef). In theory, that method could take
+ * an additional argument and obviate the need for this method.
+ */
+ reportBinding: function gloda_ds_reportBinding(aID, aAttrDef, aParamValue) {
+ this._attributeIDToDBDefAndParam[aID] = [aAttrDef, aParamValue];
+ },
+
+ /* ********** Folders ********** */
+ /** next folder (row) id to issue, populated by _getAllFolderMappings. */
+ _nextFolderId: 1,
+
+ get _insertFolderLocationStatement() {
+ let statement = this._createAsyncStatement(
+ "INSERT INTO folderLocations (id, folderURI, dirtyStatus, name, \
+ indexingPriority) VALUES \
+ (?1, ?2, ?3, ?4, ?5)");
+ this.__defineGetter__("_insertFolderLocationStatement",
+ () => statement);
+ return this._insertFolderLocationStatement;
+ },
+
+ /**
+ * Authoritative map from folder URI to folder ID. (Authoritative in the
+ * sense that this map exactly represents the state of the underlying
+ * database. If it does not, it's a bug in updating the database.)
+ */
+ _folderByURI: {},
+ /** Authoritative map from folder ID to folder URI */
+ _folderByID: {},
+
+ /** Intialize our _folderByURI/_folderByID mappings, called by _init(). */
+ _getAllFolderMappings: function gloda_ds_getAllFolderMappings() {
+ let stmt = this._createSyncStatement(
+ "SELECT id, folderURI, dirtyStatus, name, indexingPriority \
+ FROM folderLocations", true);
+
+ while (stmt.executeStep()) { // no chance of this SQLITE_BUSY on this call
+ let folderID = stmt.getInt64(0);
+ let folderURI = stmt.getString(1);
+ let dirtyStatus = stmt.getInt32(2);
+ let folderName = stmt.getString(3);
+ let indexingPriority = stmt.getInt32(4);
+
+ let folder = new GlodaFolder(this, folderID, folderURI, dirtyStatus,
+ folderName, indexingPriority);
+
+ this._folderByURI[folderURI] = folder;
+ this._folderByID[folderID] = folder;
+
+ if (folderID >= this._nextFolderId)
+ this._nextFolderId = folderID + 1;
+ }
+ stmt.finalize();
+ },
+
+ _folderKnown: function gloda_ds_folderKnown(aFolder) {
+ let folderURI = aFolder.URI;
+ return folderURI in this._folderByURI;
+ },
+
+ _folderIdKnown: function gloda_ds_folderIdKnown(aFolderID) {
+ return (aFolderID in this._folderByID);
+ },
+
+ /**
+ * Return the default messaging priority for a folder of this type, based
+ * on the folder's flags. If aAllowSpecialFolderIndexing is true, then
+ * folders suchs as Trash and Junk will be indexed.
+ *
+ * @param {nsIMsgFolder} aFolder
+ * @param {boolean} aAllowSpecialFolderIndexing
+ * @returns {Number}
+ */
+ getDefaultIndexingPriority: function gloda_ds_getDefaultIndexingPriority(aFolder, aAllowSpecialFolderIndexing) {
+
+ let indexingPriority = GlodaFolder.prototype.kIndexingDefaultPriority;
+ // Do not walk into trash/junk folders, unless the user is explicitly
+ // telling us to do so.
+ let specialFolderFlags = Ci.nsMsgFolderFlags.Trash | Ci.nsMsgFolderFlags.Junk;
+ if (aFolder.isSpecialFolder(specialFolderFlags, true))
+ indexingPriority = aAllowSpecialFolderIndexing ?
+ GlodaFolder.prototype.kIndexingDefaultPriority :
+ GlodaFolder.prototype.kIndexingNeverPriority;
+ // Queue folders should always be ignored just because messages should not
+ // spend much time in there.
+ // We hate newsgroups, and public IMAP folders are similar.
+ // Other user IMAP folders should be ignored because it's not this user's
+ // mail.
+ else if (aFolder.flags & (Ci.nsMsgFolderFlags.Queue
+ | Ci.nsMsgFolderFlags.Newsgroup
+ // In unit testing at least folders can be
+ // confusingly labeled ImapPublic when they
+ // should not be. Or at least I don't think they
+ // should be. So they're legit for now.
+ //| Ci.nsMsgFolderFlags.ImapPublic
+ //| Ci.nsMsgFolderFlags.ImapOtherUser
+ ))
+ indexingPriority = GlodaFolder.prototype.kIndexingNeverPriority;
+ else if (aFolder.flags & Ci.nsMsgFolderFlags.Inbox)
+ indexingPriority = GlodaFolder.prototype.kIndexingInboxPriority;
+ else if (aFolder.flags & Ci.nsMsgFolderFlags.SentMail)
+ indexingPriority = GlodaFolder.prototype.kIndexingSentMailPriority;
+ else if (aFolder.flags & Ci.nsMsgFolderFlags.Favorite)
+ indexingPriority = GlodaFolder.prototype.kIndexingFavoritePriority;
+ else if (aFolder.flags & Ci.nsMsgFolderFlags.CheckNew)
+ indexingPriority = GlodaFolder.prototype.kIndexingCheckNewPriority;
+
+ return indexingPriority;
+ },
+
+ /**
+ * Map a folder URI to a GlodaFolder instance, creating the mapping if it does
+ * not yet exist.
+ *
+ * @param aFolder The nsIMsgFolder instance you would like the GlodaFolder
+ * instance for.
+ * @returns The existing or newly created GlodaFolder instance.
+ */
+ _mapFolder: function gloda_ds_mapFolderURI(aFolder) {
+ let folderURI = aFolder.URI;
+ if (folderURI in this._folderByURI) {
+ return this._folderByURI[folderURI];
+ }
+
+ let folderID = this._nextFolderId++;
+
+ // if there's an indexingPriority stored on the folder, just use that
+ let indexingPriority;
+ let stringPrio = aFolder.getStringProperty("indexingPriority");
+ if (stringPrio.length)
+ indexingPriority = parseInt(stringPrio);
+ else
+ // otherwise, fall back to the default for folders of this type
+ indexingPriority = this.getDefaultIndexingPriority(aFolder);
+
+ // If there are messages in the folder, it is filthy. If there are no
+ // messages, it can be clean.
+ let dirtyStatus = aFolder.getTotalMessages(false) ?
+ GlodaFolder.prototype.kFolderFilthy :
+ GlodaFolder.prototype.kFolderClean;
+ let folder = new GlodaFolder(this, folderID, folderURI, dirtyStatus,
+ aFolder.prettiestName, indexingPriority);
+
+ this._insertFolderLocationStatement.bindInt64Parameter(0, folder.id);
+ this._insertFolderLocationStatement.bindStringParameter(1, folder.uri);
+ this._insertFolderLocationStatement.bindInt64Parameter(2,
+ folder.dirtyStatus);
+ this._insertFolderLocationStatement.bindStringParameter(3, folder.name);
+ this._insertFolderLocationStatement.bindInt64Parameter(
+ 4, folder.indexingPriority);
+ this._insertFolderLocationStatement.executeAsync(this.trackAsync());
+
+ this._folderByURI[folderURI] = folder;
+ this._folderByID[folderID] = folder;
+ this._log.debug("!! mapped " + folder.id + " from " + folderURI);
+ return folder;
+ },
+
+ /**
+ * Map an integer gloda folder ID to the corresponding GlodaFolder instance.
+ *
+ * @param aFolderID The known valid gloda folder ID for which you would like
+ * a GlodaFolder instance.
+ * @return The GlodaFolder instance with the given id. If no such instance
+ * exists, we will throw an exception.
+ */
+ _mapFolderID: function gloda_ds_mapFolderID(aFolderID) {
+ if (aFolderID === null)
+ return null;
+ if (aFolderID in this._folderByID)
+ return this._folderByID[aFolderID];
+ throw new Error("Got impossible folder ID: " + aFolderID);
+ },
+
+ /**
+ * Mark the gloda folder as deleted for any outstanding references to it and
+ * remove it from our tables so we don't hand out any new references. The
+ * latter is especially important in the case a folder with the same name
+ * is created afterwards; we don't want to confuse the new one with the old
+ * one!
+ */
+ _killGlodaFolderIntoTombstone:
+ function gloda_ds__killGlodaFolderIntoTombstone(aGlodaFolder) {
+ aGlodaFolder._deleted = true;
+ delete this._folderByURI[aGlodaFolder.uri];
+ delete this._folderByID[aGlodaFolder.id];
+ },
+
+ get _updateFolderDirtyStatusStatement() {
+ let statement = this._createAsyncStatement(
+ "UPDATE folderLocations SET dirtyStatus = ?1 \
+ WHERE id = ?2");
+ this.__defineGetter__("_updateFolderDirtyStatusStatement",
+ () => statement);
+ return this._updateFolderDirtyStatusStatement;
+ },
+
+ updateFolderDirtyStatus: function gloda_ds_updateFolderDirtyStatus(aFolder) {
+ let ufds = this._updateFolderDirtyStatusStatement;
+ ufds.bindInt64Parameter(1, aFolder.id);
+ ufds.bindInt64Parameter(0, aFolder.dirtyStatus);
+ ufds.executeAsync(this.trackAsync());
+ },
+
+ get _updateFolderIndexingPriorityStatement() {
+ let statement = this._createAsyncStatement(
+ "UPDATE folderLocations SET indexingPriority = ?1 \
+ WHERE id = ?2");
+ this.__defineGetter__("_updateFolderIndexingPriorityStatement",
+ () => statement);
+ return this._updateFolderIndexingPriorityStatement;
+ },
+
+ updateFolderIndexingPriority: function gloda_ds_updateFolderIndexingPriority(aFolder) {
+ let ufip = this._updateFolderIndexingPriorityStatement;
+ ufip.bindInt64Parameter(1, aFolder.id);
+ ufip.bindInt64Parameter(0, aFolder.indexingPriority);
+ ufip.executeAsync(this.trackAsync());
+ },
+
+ get _updateFolderLocationStatement() {
+ let statement = this._createAsyncStatement(
+ "UPDATE folderLocations SET folderURI = ?1 \
+ WHERE id = ?2");
+ this.__defineGetter__("_updateFolderLocationStatement",
+ () => statement);
+ return this._updateFolderLocationStatement;
+ },
+
+ /**
+ * Non-recursive asynchronous folder renaming based on the URI.
+ *
+ * @TODO provide a mechanism for recursive folder renames or have a higher
+ * layer deal with it and remove this note.
+ */
+ renameFolder: function gloda_ds_renameFolder(aOldFolder, aNewURI) {
+ if (!(aOldFolder.URI in this._folderByURI))
+ return;
+ let folder = this._mapFolder(aOldFolder); // ensure the folder is mapped
+ let oldURI = folder.uri;
+ this._folderByURI[aNewURI] = folder;
+ folder._uri = aNewURI;
+ this._log.info("renaming folder URI " + oldURI + " to " + aNewURI);
+ this._updateFolderLocationStatement.bindStringParameter(1, folder.id);
+ this._updateFolderLocationStatement.bindStringParameter(0, aNewURI);
+ this._updateFolderLocationStatement.executeAsync(this.trackAsync());
+
+ delete this._folderByURI[oldURI];
+ },
+
+ get _deleteFolderByIDStatement() {
+ let statement = this._createAsyncStatement(
+ "DELETE FROM folderLocations WHERE id = ?1");
+ this.__defineGetter__("_deleteFolderByIDStatement",
+ () => statement);
+ return this._deleteFolderByIDStatement;
+ },
+
+ deleteFolderByID: function gloda_ds_deleteFolder(aFolderID) {
+ let dfbis = this._deleteFolderByIDStatement;
+ dfbis.bindInt64Parameter(0, aFolderID);
+ dfbis.executeAsync(this.trackAsync());
+ },
+
+ /**
+ * This timer drives our folder cleanup logic that is in charge of dropping
+ * our folder references and more importantly the folder's msgDatabase
+ * reference, but only if they are no longer in use.
+ * This timer is only active when we have one or more live gloda folders (as
+ * tracked by _liveGlodaFolders). Although we choose our timer interval to
+ * be power-friendly, it doesn't really matter because unless the user or the
+ * indexing process is actively doing things, all of the folders will 'die'
+ * and so we will stop scheduling the timer.
+ */
+ _folderCleanupTimer: null,
+
+ /**
+ * When true, we have a folder cleanup timer event active.
+ */
+ _folderCleanupActive: false,
+
+ /**
+ * Interval at which we call the folder cleanup code, in milliseconds.
+ */
+ _folderCleanupTimerInterval: 2000,
+
+ /**
+ * Maps the id of 'live' GlodaFolders to the instances. If a GlodaFolder is
+ * in here, it means that it has a reference to its nsIMsgDBFolder which
+ * should have an open nsIMsgDatabase that we will need to close. This does
+ * not count folders that are being indexed unless they have also been used
+ * for header retrieval.
+ */
+ _liveGlodaFolders: {},
+
+ /**
+ * Mark a GlodaFolder as having a live reference to its nsIMsgFolder with an
+ * implied opened associated message database. GlodaFolder calls this when
+ * it first acquires its reference. It is removed from the list of live
+ * folders only when our timer check calls the GlodaFolder's
+ * forgetFolderIfUnused method and that method returns true.
+ */
+ markFolderLive: function gloda_ds_markFolderLive(aGlodaFolder) {
+ this._liveGlodaFolders[aGlodaFolder.id] = aGlodaFolder;
+ if (!this._folderCleanupActive) {
+ this._folderCleanupTimer.initWithCallback(this._performFolderCleanup,
+ this._folderCleanupTimerInterval, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ this._folderCleanupActive = true;
+ }
+ },
+
+ /**
+ * Timer-driven folder cleanup logic. For every live folder tracked in
+ * _liveGlodaFolders, we call their forgetFolderIfUnused method each time
+ * until they return true indicating they have cleaned themselves up.
+ * This method is called without a 'this' context!
+ */
+ _performFolderCleanup: function gloda_ds_performFolderCleanup() {
+ // we only need to keep going if there is at least one folder in the table
+ // that is still alive after this pass.
+ let keepGoing = false;
+ for (let id in GlodaDatastore._liveGlodaFolders) {
+ let glodaFolder = GlodaDatastore._liveGlodaFolders[id];
+ // returns true if it is now 'dead' and doesn't need this heartbeat check
+ if (glodaFolder.forgetFolderIfUnused())
+ delete GlodaDatastore._liveGlodaFolders[glodaFolder.id];
+ else
+ keepGoing = true;
+ }
+
+ if (!keepGoing) {
+ GlodaDatastore._folderCleanupTimer.cancel();
+ GlodaDatastore._folderCleanupActive = false;
+ }
+ },
+
+ /* ********** Conversation ********** */
+ /** The next conversation id to allocate. Initialize at startup. */
+ _nextConversationId: 1,
+
+ _populateConversationManagedId: function () {
+ let stmt = this._createSyncStatement(
+ "SELECT MAX(id) FROM conversations", true);
+ if (stmt.executeStep()) { // no chance of this SQLITE_BUSY on this call
+ this._nextConversationId = stmt.getInt64(0) + 1;
+ }
+ stmt.finalize();
+ },
+
+ get _insertConversationStatement() {
+ let statement = this._createAsyncStatement(
+ "INSERT INTO conversations (id, subject, oldestMessageDate, \
+ newestMessageDate) \
+ VALUES (?1, ?2, ?3, ?4)");
+ this.__defineGetter__("_insertConversationStatement", () => statement);
+ return this._insertConversationStatement;
+ },
+
+ get _insertConversationTextStatement() {
+ let statement = this._createAsyncStatement(
+ "INSERT INTO conversationsText (docid, subject) \
+ VALUES (?1, ?2)");
+ this.__defineGetter__("_insertConversationTextStatement",
+ () => statement);
+ return this._insertConversationTextStatement;
+ },
+
+ /**
+ * Asynchronously create a conversation.
+ */
+ createConversation: function gloda_ds_createConversation(aSubject,
+ aOldestMessageDate, aNewestMessageDate) {
+
+ // create the data row
+ let conversationID = this._nextConversationId++;
+ let ics = this._insertConversationStatement;
+ ics.bindInt64Parameter(0, conversationID);
+ ics.bindStringParameter(1, aSubject);
+ if (aOldestMessageDate == null)
+ ics.bindNullParameter(2);
+ else
+ ics.bindInt64Parameter(2, aOldestMessageDate);
+ if (aNewestMessageDate == null)
+ ics.bindNullParameter(3);
+ else
+ ics.bindInt64Parameter(3, aNewestMessageDate);
+ ics.executeAsync(this.trackAsync());
+
+ // create the fulltext row, using the same rowid/docid
+ let icts = this._insertConversationTextStatement;
+ icts.bindInt64Parameter(0, conversationID);
+ icts.bindStringParameter(1, aSubject);
+ icts.executeAsync(this.trackAsync());
+
+ // create it
+ let conversation = new GlodaConversation(this, conversationID,
+ aSubject, aOldestMessageDate,
+ aNewestMessageDate);
+ // it's new! let the collection manager know about it.
+ GlodaCollectionManager.itemsAdded(conversation.NOUN_ID, [conversation]);
+ // return it
+ return conversation;
+ },
+
+ get _deleteConversationByIDStatement() {
+ let statement = this._createAsyncStatement(
+ "DELETE FROM conversations WHERE id = ?1");
+ this.__defineGetter__("_deleteConversationByIDStatement",
+ () => statement);
+ return this._deleteConversationByIDStatement;
+ },
+
+ /**
+ * Asynchronously delete a conversation given its ID.
+ */
+ deleteConversationByID: function gloda_ds_deleteConversationByID(
+ aConversationID) {
+ let dcbids = this._deleteConversationByIDStatement;
+ dcbids.bindInt64Parameter(0, aConversationID);
+ dcbids.executeAsync(this.trackAsync());
+
+ GlodaCollectionManager.itemsDeleted(GlodaConversation.prototype.NOUN_ID,
+ [aConversationID]);
+ },
+
+ _conversationFromRow: function gloda_ds_conversationFromRow(aStmt) {
+ let oldestMessageDate, newestMessageDate;
+ if (aStmt.getTypeOfIndex(2) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ oldestMessageDate = null;
+ else
+ oldestMessageDate = aStmt.getInt64(2);
+ if (aStmt.getTypeOfIndex(3) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ newestMessageDate = null;
+ else
+ newestMessageDate = aStmt.getInt64(3);
+ return new GlodaConversation(this, aStmt.getInt64(0),
+ aStmt.getString(1), oldestMessageDate, newestMessageDate);
+ },
+
+ /* ********** Message ********** */
+ /**
+ * Next message id, managed because of our use of asynchronous inserts.
+ * Initialized by _populateMessageManagedId called by _init.
+ *
+ * Start from 32 to leave us all kinds of magical sentinel values at the
+ * bottom.
+ */
+ _nextMessageId: 32,
+
+ _populateMessageManagedId: function () {
+ let stmt = this._createSyncStatement(
+ "SELECT MAX(id) FROM messages", true);
+ if (stmt.executeStep()) { // no chance of this SQLITE_BUSY on this call
+ // 0 gets returned even if there are no messages...
+ let highestSeen = stmt.getInt64(0);
+ if (highestSeen != 0)
+ this._nextMessageId = highestSeen + 1;
+ }
+ stmt.finalize();
+ },
+
+ get _insertMessageStatement() {
+ let statement = this._createAsyncStatement(
+ "INSERT INTO messages (id, folderID, messageKey, conversationID, date, \
+ headerMessageID, jsonAttributes, notability) \
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)");
+ this.__defineGetter__("_insertMessageStatement", () => statement);
+ return this._insertMessageStatement;
+ },
+
+ get _insertMessageTextStatement() {
+ let statement = this._createAsyncStatement(
+ "INSERT INTO messagesText (docid, subject, body, attachmentNames, \
+ author, recipients) \
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6)");
+ this.__defineGetter__("_insertMessageTextStatement", () => statement);
+ return this._insertMessageTextStatement;
+ },
+
+ /**
+ * Create a GlodaMessage with the given properties. Because this is only half
+ * of the process of creating a message (the attributes still need to be
+ * completed), it's on the caller's head to call GlodaCollectionManager's
+ * itemAdded method once the message is fully created.
+ *
+ * This method uses the async connection, any downstream logic that depends on
+ * this message actually existing in the database must be done using an
+ * async query.
+ */
+ createMessage: function gloda_ds_createMessage(aFolder, aMessageKey,
+ aConversationID, aDatePRTime, aHeaderMessageID) {
+ let folderID;
+ if (aFolder != null) {
+ folderID = this._mapFolder(aFolder).id;
+ }
+ else {
+ folderID = null;
+ }
+
+ let messageID = this._nextMessageId++;
+
+ let message = new GlodaMessage(
+ this, messageID, folderID,
+ aMessageKey,
+ aConversationID, /* conversation */ null,
+ aDatePRTime ? new Date(aDatePRTime / 1000) : null,
+ aHeaderMessageID,
+ /* deleted */ false, /* jsonText */ undefined, /* notability*/ 0);
+
+ // We would love to notify the collection manager about the message at this
+ // point (at least if it's not a ghost), but we can't yet. We need to wait
+ // until the attributes have been indexed, which means it's out of our
+ // hands. (Gloda.processMessage does it.)
+
+ return message;
+ },
+
+ insertMessage: function gloda_ds_insertMessage(aMessage) {
+ let ims = this._insertMessageStatement;
+ ims.bindInt64Parameter(0, aMessage.id);
+ if (aMessage.folderID == null)
+ ims.bindNullParameter(1);
+ else
+ ims.bindInt64Parameter(1, aMessage.folderID);
+ if (aMessage.messageKey == null)
+ ims.bindNullParameter(2);
+ else
+ ims.bindInt64Parameter(2, aMessage.messageKey);
+ ims.bindInt64Parameter(3, aMessage.conversationID);
+ if (aMessage.date == null)
+ ims.bindNullParameter(4);
+ else
+ ims.bindInt64Parameter(4, aMessage.date * 1000);
+ ims.bindStringParameter(5, aMessage.headerMessageID);
+ if (aMessage._jsonText)
+ ims.bindStringParameter(6, aMessage._jsonText);
+ else
+ ims.bindNullParameter(6);
+ ims.bindInt64Parameter(7, aMessage.notability);
+
+ try {
+ ims.executeAsync(this.trackAsync());
+ }
+ catch(ex) {
+ throw new Error("error executing statement... " +
+ this.asyncConnection.lastError + ": " +
+ this.asyncConnection.lastErrorString + " - " + ex);
+ }
+
+ // we create the full-text row for any message that isn't a ghost,
+ // whether we have the body or not
+ if (aMessage.folderID !== null)
+ this._insertMessageText(aMessage);
+ },
+
+ /**
+ * Inserts a full-text row. This should only be called if you're sure you want
+ * to insert a row into the table.
+ */
+ _insertMessageText: function gloda_ds__insertMessageText(aMessage) {
+ if (aMessage._content && aMessage._content.hasContent())
+ aMessage._indexedBodyText = aMessage._content.getContentString(true);
+ else if (aMessage._bodyLines)
+ aMessage._indexedBodyText = aMessage._bodyLines.join("\n");
+ else
+ aMessage._indexedBodyText = null;
+
+ let imts = this._insertMessageTextStatement;
+ imts.bindInt64Parameter(0, aMessage.id);
+ imts.bindStringParameter(1, aMessage._subject);
+ if (aMessage._indexedBodyText == null)
+ imts.bindNullParameter(2);
+ else
+ imts.bindStringParameter(2, aMessage._indexedBodyText);
+ if (aMessage._attachmentNames === null)
+ imts.bindNullParameter(3);
+ else
+ imts.bindStringParameter(3, aMessage._attachmentNames.join("\n"));
+
+ imts.bindStringParameter(4, aMessage._indexAuthor);
+ imts.bindStringParameter(5, aMessage._indexRecipients);
+
+ try {
+ imts.executeAsync(this.trackAsync());
+ }
+ catch(ex) {
+ throw new Error("error executing fulltext statement... " +
+ this.asyncConnection.lastError + ": " +
+ this.asyncConnection.lastErrorString + " - " + ex);
+ }
+ },
+
+ get _updateMessageStatement() {
+ let statement = this._createAsyncStatement(
+ "UPDATE messages SET folderID = ?1, \
+ messageKey = ?2, \
+ conversationID = ?3, \
+ date = ?4, \
+ headerMessageID = ?5, \
+ jsonAttributes = ?6, \
+ notability = ?7, \
+ deleted = ?8 \
+ WHERE id = ?9");
+ this.__defineGetter__("_updateMessageStatement", () => statement);
+ return this._updateMessageStatement;
+ },
+
+ get _updateMessageTextStatement() {
+ let statement = this._createAsyncStatement(
+ "UPDATE messagesText SET body = ?1, \
+ attachmentNames = ?2 \
+ WHERE docid = ?3");
+
+ this.__defineGetter__("_updateMessageTextStatement", () => statement);
+ return this._updateMessageTextStatement;
+ },
+
+ /**
+ * Update the database row associated with the message. If the message is
+ * not a ghost and has _isNew defined, messagesText is affected.
+ *
+ * aMessage._isNew is currently equivalent to the fact that there is no
+ * full-text row associated with this message, and we work with this
+ * assumption here. Note that if aMessage._isNew is not defined, then
+ * we don't do anything.
+ */
+ updateMessage: function gloda_ds_updateMessage(aMessage) {
+ let ums = this._updateMessageStatement;
+ ums.bindInt64Parameter(8, aMessage.id);
+ if (aMessage.folderID === null)
+ ums.bindNullParameter(0);
+ else
+ ums.bindInt64Parameter(0, aMessage.folderID);
+ if (aMessage.messageKey === null)
+ ums.bindNullParameter(1);
+ else
+ ums.bindInt64Parameter(1, aMessage.messageKey);
+ ums.bindInt64Parameter(2, aMessage.conversationID);
+ if (aMessage.date === null)
+ ums.bindNullParameter(3);
+ else
+ ums.bindInt64Parameter(3, aMessage.date * 1000);
+ ums.bindStringParameter(4, aMessage.headerMessageID);
+ if (aMessage._jsonText)
+ ums.bindStringParameter(5, aMessage._jsonText);
+ else
+ ums.bindNullParameter(5);
+ ums.bindInt64Parameter(6, aMessage.notability);
+ ums.bindInt64Parameter(7, aMessage._isDeleted ? 1 : 0);
+
+ ums.executeAsync(this.trackAsync());
+
+ if (aMessage.folderID !== null) {
+ if (aMessage._isNew === true)
+ this._insertMessageText(aMessage);
+ else
+ this._updateMessageText(aMessage);
+ }
+ },
+
+ /**
+ * Updates the full-text row associated with this message. This only performs
+ * the UPDATE query if the indexed body text has changed, which means that if
+ * the body hasn't changed but the attachments have, we don't update.
+ */
+ _updateMessageText: function gloda_ds__updateMessageText(aMessage) {
+ let newIndexedBodyText;
+ if (aMessage._content && aMessage._content.hasContent())
+ newIndexedBodyText = aMessage._content.getContentString(true);
+ else if (aMessage._bodyLines)
+ newIndexedBodyText = aMessage._bodyLines.join("\n");
+ else
+ newIndexedBodyText = null;
+
+ // If the body text matches, don't perform an update
+ if (newIndexedBodyText == aMessage._indexedBodyText) {
+ this._log.debug("in _updateMessageText, skipping update because body matches");
+ return;
+ }
+
+ aMessage._indexedBodyText = newIndexedBodyText;
+ let umts = this._updateMessageTextStatement;
+ umts.bindInt64Parameter(2, aMessage.id);
+
+ if (aMessage._indexedBodyText == null)
+ umts.bindNullParameter(0);
+ else
+ umts.bindStringParameter(0, aMessage._indexedBodyText);
+
+ if (aMessage._attachmentNames == null)
+ umts.bindNullParameter(1);
+ else
+ umts.bindStringParameter(1, aMessage._attachmentNames.join("\n"));
+
+ try {
+ umts.executeAsync(this.trackAsync());
+ }
+ catch(ex) {
+ throw new Error("error executing fulltext statement... " +
+ this.asyncConnection.lastError + ": " +
+ this.asyncConnection.lastErrorString + " - " + ex);
+ }
+ },
+
+ get _updateMessageLocationStatement() {
+ let statement = this._createAsyncStatement(
+ "UPDATE messages SET folderID = ?1, messageKey = ?2 WHERE id = ?3");
+ this.__defineGetter__("_updateMessageLocationStatement",
+ () => statement);
+ return this._updateMessageLocationStatement;
+ },
+
+ /**
+ * Given a list of gloda message ids, and a list of their new message keys in
+ * the given new folder location, asynchronously update the message's
+ * database locations. Also, update the in-memory representations.
+ */
+ updateMessageLocations: function gloda_ds_updateMessageLocations(aMessageIds,
+ aNewMessageKeys, aDestFolder, aDoNotNotify) {
+ let statement = this._updateMessageLocationStatement;
+ let destFolderID = (typeof(aDestFolder) == "number") ? aDestFolder :
+ this._mapFolder(aDestFolder).id;
+
+ // map gloda id to the new message key for in-memory rep transform below
+ let cacheLookupMap = {};
+
+ for (let iMsg = 0; iMsg < aMessageIds.length; iMsg++) {
+ let id = aMessageIds[iMsg], msgKey = aNewMessageKeys[iMsg];
+ statement.bindInt64Parameter(0, destFolderID);
+ statement.bindInt64Parameter(1, msgKey);
+ statement.bindInt64Parameter(2, id);
+ statement.executeAsync(this.trackAsync());
+
+ cacheLookupMap[id] = msgKey;
+ }
+
+ // - perform the cache lookup so we can update in-memory representations
+ // found in memory items, and converted to list form for notification
+ let inMemoryItems = {}, modifiedItems = [];
+ GlodaCollectionManager.cacheLookupMany(GlodaMessage.prototype.NOUN_ID,
+ cacheLookupMap,
+ inMemoryItems,
+ /* do not cache */ false);
+ for (let glodaId in inMemoryItems) {
+ let glodaMsg = inMemoryItems[glodaId];
+ glodaMsg._folderID = destFolderID;
+ glodaMsg._messageKey = cacheLookupMap[glodaId];
+ modifiedItems.push(glodaMsg);
+ }
+
+ // tell the collection manager about the modified messages so it can update
+ // any existing views...
+ if (!aDoNotNotify && modifiedItems.length) {
+ GlodaCollectionManager.itemsModified(GlodaMessage.prototype.NOUN_ID,
+ modifiedItems);
+ }
+ },
+
+ get _updateMessageKeyStatement() {
+ let statement = this._createAsyncStatement(
+ "UPDATE messages SET messageKey = ?1 WHERE id = ?2");
+ this.__defineGetter__("_updateMessageKeyStatement",
+ () => statement);
+ return this._updateMessageKeyStatement;
+ },
+
+ /**
+ * Update the message keys for the gloda messages with the given id's. This
+ * is to be used in response to msgKeyChanged notifications and is similar to
+ * `updateMessageLocations` except that we do not update the folder and we
+ * do not perform itemsModified notifications (because message keys are not
+ * intended to be relevant to the gloda message abstraction).
+ */
+ updateMessageKeys: function(aMessageIds, aNewMessageKeys) {
+ let statement = this._updateMessageKeyStatement;
+
+ // map gloda id to the new message key for in-memory rep transform below
+ let cacheLookupMap = {};
+
+ for (let iMsg = 0; iMsg < aMessageIds.length; iMsg++) {
+ let id = aMessageIds[iMsg], msgKey = aNewMessageKeys[iMsg];
+ statement.bindInt64Parameter(0, msgKey);
+ statement.bindInt64Parameter(1, id);
+ statement.executeAsync(this.trackAsync());
+
+ cacheLookupMap[id] = msgKey;
+ }
+
+ // - perform the cache lookup so we can update in-memory representations
+ let inMemoryItems = {};
+ GlodaCollectionManager.cacheLookupMany(GlodaMessage.prototype.NOUN_ID,
+ cacheLookupMap,
+ inMemoryItems,
+ /* do not cache */ false);
+ for (let glodaId in inMemoryItems) {
+ let glodaMsg = inMemoryItems[glodaId];
+ glodaMsg._messageKey = cacheLookupMap[glodaId];
+ }
+ },
+
+ /**
+ * Asynchronously mutate message folder id/message keys for the given
+ * messages, indicating that we are moving them to the target folder, but
+ * don't yet know their target message keys.
+ *
+ * Updates in-memory representations too.
+ */
+ updateMessageFoldersByKeyPurging:
+ function gloda_ds_updateMessageFoldersByKeyPurging(aGlodaIds,
+ aDestFolder) {
+ let destFolderID = this._mapFolder(aDestFolder).id;
+
+ let sqlStr = "UPDATE messages SET folderID = ?1, \
+ messageKey = ?2 \
+ WHERE id IN (" + aGlodaIds.join(", ") + ")";
+ let statement = this._createAsyncStatement(sqlStr, true);
+ statement.bindInt64Parameter(0, destFolderID);
+ statement.bindNullParameter(1);
+ statement.executeAsync(this.trackAsync());
+ statement.finalize();
+
+ let cached =
+ GlodaCollectionManager.cacheLookupManyList(GlodaMessage.prototype.NOUN_ID,
+ aGlodaIds);
+ for (let id in cached) {
+ let glodaMsg = cached[id];
+ glodaMsg._folderID = destFolderID;
+ glodaMsg._messageKey = null;
+ }
+ },
+
+ _messageFromRow: function gloda_ds_messageFromRow(aRow) {
+ let folderId, messageKey, date, jsonText, subject, indexedBodyText,
+ attachmentNames;
+ if (aRow.getTypeOfIndex(1) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ folderId = null;
+ else
+ folderId = aRow.getInt64(1);
+ if (aRow.getTypeOfIndex(2) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ messageKey = null;
+ else
+ messageKey = aRow.getInt64(2);
+ if (aRow.getTypeOfIndex(4) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ date = null;
+ else
+ date = new Date(aRow.getInt64(4) / 1000);
+ if (aRow.getTypeOfIndex(7) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ jsonText = undefined;
+ else
+ jsonText = aRow.getString(7);
+ // only queryFromQuery queries will have these columns
+ if (aRow.numEntries >= 14) {
+ if (aRow.getTypeOfIndex(10) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ subject = undefined;
+ else
+ subject = aRow.getString(10);
+ if (aRow.getTypeOfIndex(9) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ indexedBodyText = undefined;
+ else
+ indexedBodyText = aRow.getString(9);
+ if (aRow.getTypeOfIndex(11) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ attachmentNames = null;
+ else {
+ attachmentNames = aRow.getString(11);
+ if (attachmentNames)
+ attachmentNames = attachmentNames.split("\n");
+ else
+ attachmentNames = null;
+ }
+ // we ignore 12, author
+ // we ignore 13, recipients
+ }
+ return new GlodaMessage(this, aRow.getInt64(0), folderId, messageKey,
+ aRow.getInt64(3), null, date, aRow.getString(5),
+ aRow.getInt64(6), jsonText, aRow.getInt64(8),
+ subject, indexedBodyText, attachmentNames);
+ },
+
+ get _updateMessagesMarkDeletedByFolderID() {
+ // When marking deleted clear the folderID and messageKey so that the
+ // indexing process can reuse it without any location constraints.
+ let statement = this._createAsyncStatement(
+ "UPDATE messages SET folderID = NULL, messageKey = NULL, \
+ deleted = 1 WHERE folderID = ?1");
+ this.__defineGetter__("_updateMessagesMarkDeletedByFolderID",
+ () => statement);
+ return this._updateMessagesMarkDeletedByFolderID;
+ },
+
+ /**
+ * Efficiently mark all the messages in a folder as deleted. Unfortunately,
+ * we obviously do not know the id's of the messages affected by this which
+ * complicates in-memory updates. The options are sending out to the SQL
+ * database for a list of the message id's or some form of in-memory
+ * traversal. I/O costs being what they are, users having a propensity to
+ * have folders with tens of thousands of messages, and the unlikeliness
+ * of all of those messages being gloda-memory-resident, we go with the
+ * in-memory traversal.
+ */
+ markMessagesDeletedByFolderID:
+ function gloda_ds_markMessagesDeletedByFolderID(aFolderID) {
+ let statement = this._updateMessagesMarkDeletedByFolderID;
+ statement.bindInt64Parameter(0, aFolderID);
+ statement.executeAsync(this.trackAsync());
+
+ // Have the collection manager generate itemsRemoved events for any
+ // in-memory messages in that folder.
+ GlodaCollectionManager.itemsDeletedByAttribute(
+ GlodaMessage.prototype.NOUN_ID,
+ aMsg => aMsg._folderID == aFolderID);
+ },
+
+ /**
+ * Mark all the gloda messages as deleted blind-fire. Check if any of the
+ * messages are known to the collection manager and update them to be deleted
+ * along with the requisite collection notifications.
+ */
+ markMessagesDeletedByIDs: function gloda_ds_markMessagesDeletedByIDs(
+ aMessageIDs) {
+ // When marking deleted clear the folderID and messageKey so that the
+ // indexing process can reuse it without any location constraints.
+ let sqlString = "UPDATE messages SET folderID = NULL, messageKey = NULL, " +
+ "deleted = 1 WHERE id IN (" +
+ aMessageIDs.join(",") + ")";
+
+ let statement = this._createAsyncStatement(sqlString, true);
+ statement.executeAsync(this.trackAsync());
+ statement.finalize();
+
+ GlodaCollectionManager.itemsDeleted(GlodaMessage.prototype.NOUN_ID,
+ aMessageIDs);
+ },
+
+ get _countDeletedMessagesStatement() {
+ let statement = this._createAsyncStatement(
+ "SELECT COUNT(*) FROM messages WHERE deleted = 1");
+ this.__defineGetter__("_countDeletedMessagesStatement",
+ () => statement);
+ return this._countDeletedMessagesStatement;
+ },
+
+ /**
+ * Count how many messages are currently marked as deleted in the database.
+ */
+ countDeletedMessages: function gloda_ds_countDeletedMessages(aCallback) {
+ let cms = this._countDeletedMessagesStatement;
+ cms.executeAsync(new SingletonResultValueHandler(aCallback));
+ },
+
+ get _deleteMessageByIDStatement() {
+ let statement = this._createAsyncStatement(
+ "DELETE FROM messages WHERE id = ?1");
+ this.__defineGetter__("_deleteMessageByIDStatement",
+ () => statement);
+ return this._deleteMessageByIDStatement;
+ },
+
+ get _deleteMessageTextByIDStatement() {
+ let statement = this._createAsyncStatement(
+ "DELETE FROM messagesText WHERE docid = ?1");
+ this.__defineGetter__("_deleteMessageTextByIDStatement",
+ () => statement);
+ return this._deleteMessageTextByIDStatement;
+ },
+
+ /**
+ * Delete a message and its fulltext from the database. It is assumed that
+ * the message was already marked as deleted and so is not visible to the
+ * collection manager and so nothing needs to be done about that.
+ */
+ deleteMessageByID: function gloda_ds_deleteMessageByID(aMessageID) {
+ let dmbids = this._deleteMessageByIDStatement;
+ dmbids.bindInt64Parameter(0, aMessageID);
+ dmbids.executeAsync(this.trackAsync());
+
+ this.deleteMessageTextByID(aMessageID);
+ },
+
+ deleteMessageTextByID: function gloda_ds_deleteMessageTextByID(aMessageID) {
+ let dmt = this._deleteMessageTextByIDStatement;
+ dmt.bindInt64Parameter(0, aMessageID);
+ dmt.executeAsync(this.trackAsync());
+ },
+
+ get _folderCompactionStatement() {
+ let statement = this._createAsyncStatement(
+ "SELECT id, messageKey, headerMessageID FROM messages \
+ WHERE folderID = ?1 AND \
+ messageKey >= ?2 AND +deleted = 0 ORDER BY messageKey LIMIT ?3");
+ this.__defineGetter__("_folderCompactionStatement",
+ () => statement);
+ return this._folderCompactionStatement;
+ },
+
+ folderCompactionPassBlockFetch:
+ function gloda_ds_folderCompactionPassBlockFetch(
+ aFolderID, aStartingMessageKey, aLimit, aCallback) {
+ let fcs = this._folderCompactionStatement;
+ fcs.bindInt64Parameter(0, aFolderID);
+ fcs.bindInt64Parameter(1, aStartingMessageKey);
+ fcs.bindInt64Parameter(2, aLimit);
+ fcs.executeAsync(new CompactionBlockFetcherHandler(aCallback));
+ },
+
+ /* ********** Message Attributes ********** */
+ get _insertMessageAttributeStatement() {
+ let statement = this._createAsyncStatement(
+ "INSERT INTO messageAttributes (conversationID, messageID, attributeID, \
+ value) \
+ VALUES (?1, ?2, ?3, ?4)");
+ this.__defineGetter__("_insertMessageAttributeStatement",
+ () => statement);
+ return this._insertMessageAttributeStatement;
+ },
+
+ get _deleteMessageAttributeStatement() {
+ let statement = this._createAsyncStatement(
+ "DELETE FROM messageAttributes WHERE attributeID = ?1 AND value = ?2 \
+ AND conversationID = ?3 AND messageID = ?4");
+ this.__defineGetter__("_deleteMessageAttributeStatement",
+ () => statement);
+ return this._deleteMessageAttributeStatement;
+ },
+
+ /**
+ * Insert and remove attributes relating to a GlodaMessage. This is performed
+ * inside a pseudo-transaction (we create one if we aren't in one, using
+ * our _beginTransaction wrapper, but if we are in one, no additional
+ * meaningful semantics are added).
+ * No attempt is made to verify uniqueness of inserted attributes, either
+ * against the current database or within the provided list of attributes.
+ * The caller is responsible for ensuring that unwanted duplicates are
+ * avoided.
+ *
+ * @param aMessage The GlodaMessage the attributes belong to. This is used
+ * to provide the message id and conversation id.
+ * @param aAddDBAttributes A list of attribute tuples to add, where each tuple
+ * contains an attribute ID and a value. Lest you forget, an attribute ID
+ * corresponds to a row in the attribute definition table. The attribute
+ * definition table stores the 'parameter' for the attribute, if any.
+ * (Which is to say, our frequent Attribute-Parameter-Value triple has
+ * the Attribute-Parameter part distilled to a single attribute id.)
+ * @param aRemoveDBAttributes A list of attribute tuples to remove.
+ */
+ adjustMessageAttributes: function gloda_ds_adjustMessageAttributes(aMessage,
+ aAddDBAttributes, aRemoveDBAttributes) {
+ let imas = this._insertMessageAttributeStatement;
+ let dmas = this._deleteMessageAttributeStatement;
+ this._beginTransaction();
+ try {
+ for (let iAttrib = 0; iAttrib < aAddDBAttributes.length; iAttrib++) {
+ let attribValueTuple = aAddDBAttributes[iAttrib];
+
+ imas.bindInt64Parameter(0, aMessage.conversationID);
+ imas.bindInt64Parameter(1, aMessage.id);
+ imas.bindInt64Parameter(2, attribValueTuple[0]);
+ // use 0 instead of null, otherwise the db gets upset. (and we don't
+ // really care anyways.)
+ if (attribValueTuple[1] == null)
+ imas.bindInt64Parameter(3, 0);
+ else if (Math.floor(attribValueTuple[1]) == attribValueTuple[1])
+ imas.bindInt64Parameter(3, attribValueTuple[1]);
+ else
+ imas.bindDoubleParameter(3, attribValueTuple[1]);
+ imas.executeAsync(this.trackAsync());
+ }
+
+ for (let iAttrib = 0; iAttrib < aRemoveDBAttributes.length; iAttrib++) {
+ let attribValueTuple = aRemoveDBAttributes[iAttrib];
+
+ dmas.bindInt64Parameter(0, attribValueTuple[0]);
+ // use 0 instead of null, otherwise the db gets upset. (and we don't
+ // really care anyways.)
+ if (attribValueTuple[1] == null)
+ dmas.bindInt64Parameter(1, 0);
+ else if (Math.floor(attribValueTuple[1]) == attribValueTuple[1])
+ dmas.bindInt64Parameter(1, attribValueTuple[1]);
+ else
+ dmas.bindDoubleParameter(1, attribValueTuple[1]);
+ dmas.bindInt64Parameter(2, aMessage.conversationID);
+ dmas.bindInt64Parameter(3, aMessage.id);
+ dmas.executeAsync(this.trackAsync());
+ }
+
+ this._commitTransaction();
+ }
+ catch (ex) {
+ this._log.error("adjustMessageAttributes:", ex);
+ this._rollbackTransaction();
+ throw ex;
+ }
+ },
+
+ get _deleteMessageAttributesByMessageIDStatement() {
+ let statement = this._createAsyncStatement(
+ "DELETE FROM messageAttributes WHERE messageID = ?1");
+ this.__defineGetter__("_deleteMessageAttributesByMessageIDStatement",
+ () => statement);
+ return this._deleteMessageAttributesByMessageIDStatement;
+ },
+
+ /**
+ * Clear all the message attributes for a given GlodaMessage. No changes
+ * are made to the in-memory representation of the message; it is up to the
+ * caller to ensure that it handles things correctly.
+ *
+ * @param aMessage The GlodaMessage whose database attributes should be
+ * purged.
+ */
+ clearMessageAttributes: function gloda_ds_clearMessageAttributes(aMessage) {
+ if (aMessage.id != null) {
+ this._deleteMessageAttributesByMessageIDStatement.bindInt64Parameter(0,
+ aMessage.id);
+ this._deleteMessageAttributesByMessageIDStatement.executeAsync(
+ this.trackAsync());
+ }
+ },
+
+ _stringSQLQuoter: function(aString) {
+ return "'" + aString.replace(/\'/g, "''") + "'";
+ },
+ _numberQuoter: function(aNum) {
+ return aNum;
+ },
+
+ /* ===== Generic Attribute Support ===== */
+ adjustAttributes: function gloda_ds_adjustAttributes(aItem, aAddDBAttributes,
+ aRemoveDBAttributes) {
+ let nounDef = aItem.NOUN_DEF;
+ let dbMeta = nounDef._dbMeta;
+ if (dbMeta.insertAttrStatement === undefined) {
+ dbMeta.insertAttrStatement = this._createAsyncStatement(
+ "INSERT INTO " + nounDef.attrTableName +
+ " (" + nounDef.attrIDColumnName + ", attributeID, value) " +
+ " VALUES (?1, ?2, ?3)");
+ // we always create this at the same time (right here), no need to check
+ dbMeta.deleteAttrStatement = this._createAsyncStatement(
+ "DELETE FROM " + nounDef.attrTableName + " WHERE " +
+ " attributeID = ?1 AND value = ?2 AND " +
+ nounDef.attrIDColumnName + " = ?3");
+ }
+
+ let ias = dbMeta.insertAttrStatement;
+ let das = dbMeta.deleteAttrStatement;
+ this._beginTransaction();
+ try {
+ for (let iAttr = 0; iAttr < aAddDBAttributes.length; iAttr++) {
+ let attribValueTuple = aAddDBAttributes[iAttr];
+
+ ias.bindInt64Parameter(0, aItem.id);
+ ias.bindInt64Parameter(1, attribValueTuple[0]);
+ // use 0 instead of null, otherwise the db gets upset. (and we don't
+ // really care anyways.)
+ if (attribValueTuple[1] == null)
+ ias.bindInt64Parameter(2, 0);
+ else if (Math.floor(attribValueTuple[1]) == attribValueTuple[1])
+ ias.bindInt64Parameter(2, attribValueTuple[1]);
+ else
+ ias.bindDoubleParameter(2, attribValueTuple[1]);
+ ias.executeAsync(this.trackAsync());
+ }
+
+ for (let iAttr = 0; iAttr < aRemoveDBAttributes.length; iAttr++) {
+ let attribValueTuple = aRemoveDBAttributes[iAttr];
+
+ das.bindInt64Parameter(0, attribValueTuple[0]);
+ // use 0 instead of null, otherwise the db gets upset. (and we don't
+ // really care anyways.)
+ if (attribValueTuple[1] == null)
+ das.bindInt64Parameter(1, 0);
+ else if (Math.floor(attribValueTuple[1]) == attribValueTuple[1])
+ das.bindInt64Parameter(1, attribValueTuple[1]);
+ else
+ das.bindDoubleParameter(1, attribValueTuple[1]);
+ das.bindInt64Parameter(2, aItem.id);
+ das.executeAsync(this.trackAsync());
+ }
+
+ this._commitTransaction();
+ }
+ catch (ex) {
+ this._log.error("adjustAttributes:", ex);
+ this._rollbackTransaction();
+ throw ex;
+ }
+ },
+
+ clearAttributes: function gloda_ds_clearAttributes(aItem) {
+ let nounDef = aItem.NOUN_DEF;
+ let dbMeta = nounMeta._dbMeta;
+ if (dbMeta.clearAttrStatement === undefined) {
+ dbMeta.clearAttrStatement = this._createAsyncStatement(
+ "DELETE FROM " + nounDef.attrTableName + " WHERE " +
+ nounDef.attrIDColumnName + " = ?1");
+ }
+
+ if (aItem.id != null) {
+ dbMeta.clearAttrStatement.bindInt64Parameter(0, aItem.id);
+ dbMeta.clearAttrStatement.executeAsync(this.trackAsync());
+ }
+ },
+
+ /**
+ * escapeStringForLIKE is only available on statements, and sometimes we want
+ * to use it before we create our statement, so we create a statement just
+ * for this reason.
+ */
+ get _escapeLikeStatement() {
+ let statement = this._createAsyncStatement("SELECT 0");
+ this.__defineGetter__("_escapeLikeStatement", () => statement);
+ return this._escapeLikeStatement;
+ },
+
+ _convertToDBValuesAndGroupByAttributeID:
+ function* gloda_ds__convertToDBValuesAndGroupByAttributeID(aAttrDef,
+ aValues) {
+ let objectNounDef = aAttrDef.objectNounDef;
+ if (!objectNounDef.usesParameter) {
+ let dbValues = [];
+ for (let iValue = 0; iValue < aValues.length; iValue++) {
+ let value = aValues[iValue];
+ // If the empty set is significant and it's an empty signifier, emit
+ // the appropriate dbvalue.
+ if (value == null && aAttrDef.emptySetIsSignificant) {
+ yield [this.kEmptySetAttrId, [aAttrDef.id]];
+ // Bail if the only value was us; we don't want to add a
+ // value-posessing wildcard into the mix.
+ if (aValues.length == 1)
+ return;
+ continue;
+ }
+ let dbValue = objectNounDef.toParamAndValue(value)[1];
+ if (dbValue != null)
+ dbValues.push(dbValue);
+ }
+ yield [aAttrDef.special ? undefined : aAttrDef.id, dbValues];
+ return;
+ }
+
+ let curParam, attrID, dbValues;
+ let attrDBDef = aAttrDef.dbDef;
+ for (let iValue = 0; iValue < aValues.length; iValue++) {
+ let value = aValues[iValue];
+ // If the empty set is significant and it's an empty signifier, emit
+ // the appropriate dbvalue.
+ if (value == null && aAttrDef.emptySetIsSignificant) {
+ yield [this.kEmptySetAttrId, [aAttrDef.id]];
+ // Bail if the only value was us; we don't want to add a
+ // value-posessing wildcard into the mix.
+ if (aValues.length == 1)
+ return;
+ continue;
+ }
+ let [dbParam, dbValue] = objectNounDef.toParamAndValue(value);
+ if (curParam === undefined) {
+ curParam = dbParam;
+ attrID = attrDBDef.bindParameter(curParam);
+ if (dbValue != null)
+ dbValues = [dbValue];
+ else
+ dbValues = [];
+ }
+ else if (curParam == dbParam) {
+ if (dbValue != null)
+ dbValues.push(dbValue);
+ }
+ else {
+ yield [attrID, dbValues];
+ curParam = dbParam;
+ attrID = attrDBDef.bindParameter(curParam);
+ if (dbValue != null)
+ dbValues = [dbValue];
+ else
+ dbValues = [];
+ }
+ }
+ if (dbValues !== undefined)
+ yield [attrID, dbValues];
+ },
+
+ _convertRangesToDBStringsAndGroupByAttributeID:
+ function* gloda_ds__convertRangesToDBStringsAndGroupByAttributeID(aAttrDef,
+ aValues, aValueColumnName) {
+ let objectNounDef = aAttrDef.objectNounDef;
+ if (!objectNounDef.usesParameter) {
+ let dbStrings = [];
+ for (let iValue = 0; iValue < aValues.length; iValue++) {
+ let [lowerVal, upperVal] = aValues[iValue];
+ // they both can't be null. that is the law.
+ if (lowerVal == null)
+ dbStrings.push(aValueColumnName + " <= " +
+ objectNounDef.toParamAndValue(upperVal)[1]);
+ else if (upperVal == null)
+ dbStrings.push(aValueColumnName + " >= " +
+ objectNounDef.toParamAndValue(lowerVal)[1]);
+ else // no one is null!
+ dbStrings.push(aValueColumnName + " BETWEEN " +
+ objectNounDef.toParamAndValue(lowerVal)[1] + " AND " +
+ objectNounDef.toParamAndValue(upperVal)[1]);
+ }
+ yield [aAttrDef.special ? undefined : aAttrDef.id, dbStrings];
+ return;
+ }
+
+ let curParam, attrID, dbStrings;
+ let attrDBDef = aAttrDef.dbDef;
+ for (let iValue = 0; iValue < aValues.length; iValue++) {
+ let [lowerVal, upperVal] = aValues[iValue];
+
+ let dbString, dbParam, lowerDBVal, upperDBVal;
+ // they both can't be null. that is the law.
+ if (lowerVal == null) {
+ [dbParam, upperDBVal] = objectNounDef.toParamAndValue(upperVal);
+ dbString = aValueColumnName + " <= " + upperDBVal;
+ }
+ else if (upperVal == null) {
+ [dbParam, lowerDBVal] = objectNounDef.toParamAndValue(lowerVal);
+ dbString = aValueColumnName + " >= " + lowerDBVal;
+ }
+ else { // no one is null!
+ [dbParam, lowerDBVal] = objectNounDef.toParamAndValue(lowerVal);
+ dbString = aValueColumnName + " BETWEEN " + lowerDBVal + " AND " +
+ objectNounDef.toParamAndValue(upperVal)[1];
+ }
+
+ if (curParam === undefined) {
+ curParam = dbParam;
+ attrID = attrDBDef.bindParameter(curParam);
+ dbStrings = [dbString];
+ }
+ else if (curParam === dbParam) {
+ dbStrings.push(dbString);
+ }
+ else {
+ yield [attrID, dbStrings];
+ curParam = dbParam;
+ attrID = attrDBDef.bindParameter(curParam);
+ dbStrings = [dbString];
+ }
+ }
+ if (dbStrings !== undefined)
+ yield [attrID, dbStrings];
+ },
+
+ /**
+ * Perform a database query given a GlodaQueryClass instance that specifies
+ * a set of constraints relating to the noun type associated with the query.
+ * A GlodaCollection is returned containing the results of the look-up.
+ * By default the collection is "live", and will mutate (generating events to
+ * its listener) as the state of the database changes.
+ * This functionality is made user/extension visible by the Query's
+ * getCollection (asynchronous).
+ *
+ * @param [aArgs] See |GlodaQuery.getCollection| for info.
+ */
+ queryFromQuery: function gloda_ds_queryFromQuery(aQuery, aListener,
+ aListenerData, aExistingCollection, aMasterCollection, aArgs) {
+ // when changing this method, be sure that GlodaQuery's testMatch function
+ // likewise has its changes made.
+ let nounDef = aQuery._nounDef;
+
+ let whereClauses = [];
+ let unionQueries = [aQuery].concat(aQuery._unions);
+ let boundArgs = [];
+
+ // Use the dbQueryValidityConstraintSuffix to provide constraints that
+ // filter items down to those that are valid for the query mechanism to
+ // return. For example, in the case of messages, deleted or ghost
+ // messages should not be returned by this query layer. We require
+ // hand-rolled SQL to do that for now.
+ let validityConstraintSuffix;
+ if (nounDef.dbQueryValidityConstraintSuffix &&
+ !aQuery.options.noDbQueryValidityConstraints)
+ validityConstraintSuffix = nounDef.dbQueryValidityConstraintSuffix;
+ else
+ validityConstraintSuffix = "";
+
+ for (let iUnion = 0; iUnion < unionQueries.length; iUnion++) {
+ let curQuery = unionQueries[iUnion];
+ let selects = [];
+
+ let lastConstraintWasSpecial = false;
+ let curConstraintIsSpecial;
+
+ for (let iConstraint = 0; iConstraint < curQuery._constraints.length;
+ iConstraint++) {
+ let constraint = curQuery._constraints[iConstraint];
+ let [constraintType, attrDef] = constraint;
+ let constraintValues = constraint.slice(2);
+
+ let tableName, idColumnName, tableColumnName, valueColumnName;
+ if (constraintType == this.kConstraintIdIn) {
+ // we don't need any of the next cases' setup code, and we especially
+ // would prefer that attrDef isn't accessed since it's null for us.
+ }
+ else if (attrDef.special) {
+ tableName = nounDef.tableName;
+ idColumnName = "id"; // canonical id for a table is "id".
+ valueColumnName = attrDef.specialColumnName;
+ curConstraintIsSpecial = true;
+ }
+ else {
+ tableName = nounDef.attrTableName;
+ idColumnName = nounDef.attrIDColumnName;
+ valueColumnName = "value";
+ curConstraintIsSpecial = false;
+ }
+
+ let select = null, test = null, bindArgs = null;
+ if (constraintType === this.kConstraintIdIn) {
+ // this is somewhat of a trick. this does mean that this can be the
+ // only constraint. Namely, our idiom is:
+ // SELECT * FROM blah WHERE id IN (a INTERSECT b INTERSECT c)
+ // but if we only have 'a', then that becomes "...IN (a)", and if
+ // 'a' is not a select but a list of id's... tricky, no?
+ select = constraintValues.join(",");
+ }
+ // @testpoint gloda.datastore.sqlgen.kConstraintIn
+ else if (constraintType === this.kConstraintIn) {
+ let clauses = [];
+ for (let [attrID, values] of
+ this._convertToDBValuesAndGroupByAttributeID(attrDef,
+ constraintValues)) {
+ let clausePart;
+ if (attrID !== undefined)
+ clausePart = "(attributeID = " + attrID +
+ (values.length ? " AND " : "");
+ else
+ clausePart = "(";
+ if (values.length) {
+ // strings need to be escaped, we would use ? binding, except
+ // that gets mad if we have too many strings... so we use our
+ // own escaping logic. correctly escaping is easy, but it still
+ // feels wrong to do it. (just double the quote character...)
+ if (attrDef.special == this.kSpecialString)
+ clausePart += valueColumnName + " IN (" +
+ values.map(v => "'" + v.replace(/\'/g, "''") + "'").
+ join(",") + "))";
+ else
+ clausePart += valueColumnName + " IN (" + values.join(",") +
+ "))";
+ }
+ else
+ clausePart += ")";
+ clauses.push(clausePart);
+ }
+ test = clauses.join(" OR ");
+ }
+ // @testpoint gloda.datastore.sqlgen.kConstraintRanges
+ else if (constraintType === this.kConstraintRanges) {
+ let clauses = [];
+ for (let [attrID, dbStrings] of
+ this._convertRangesToDBStringsAndGroupByAttributeID(attrDef,
+ constraintValues, valueColumnName)) {
+ if (attrID !== undefined)
+ clauses.push("(attributeID = " + attrID +
+ " AND (" + dbStrings.join(" OR ") + "))");
+ else
+ clauses.push("(" + dbStrings.join(" OR ") + ")");
+ }
+ test = clauses.join(" OR ");
+ }
+ // @testpoint gloda.datastore.sqlgen.kConstraintEquals
+ else if (constraintType === this.kConstraintEquals) {
+ let clauses = [];
+ for (let [attrID, values] of
+ this._convertToDBValuesAndGroupByAttributeID(attrDef,
+ constraintValues)) {
+ if (attrID !== undefined)
+ clauses.push("(attributeID = " + attrID +
+ " AND (" + values.map(_ => valueColumnName + " = ?").
+ join(" OR ") + "))");
+ else
+ clauses.push("(" + values.map(_ => valueColumnName + " = ?").
+ join(" OR ") + ")");
+ boundArgs.push.apply(boundArgs, values);
+ }
+ test = clauses.join(" OR ");
+ }
+ // @testpoint gloda.datastore.sqlgen.kConstraintStringLike
+ else if (constraintType === this.kConstraintStringLike) {
+ let likePayload = '';
+ for (let valuePart of constraintValues) {
+ if (typeof valuePart == "string")
+ likePayload += this._escapeLikeStatement.escapeStringForLIKE(
+ valuePart, "/");
+ else
+ likePayload += "%";
+ }
+ test = valueColumnName + " LIKE ? ESCAPE '/'";
+ boundArgs.push(likePayload);
+ }
+ // @testpoint gloda.datastore.sqlgen.kConstraintFulltext
+ else if (constraintType === this.kConstraintFulltext) {
+ let matchStr = constraintValues[0];
+ select = "SELECT docid FROM " + nounDef.tableName + "Text" +
+ " WHERE " + attrDef.specialColumnName + " MATCH ?";
+ boundArgs.push(matchStr);
+ }
+
+ if (curConstraintIsSpecial && lastConstraintWasSpecial && test) {
+ selects[selects.length-1] += " AND " + test;
+ }
+ else if (select)
+ selects.push(select);
+ else if (test) {
+ select = "SELECT " + idColumnName + " FROM " + tableName + " WHERE " +
+ test;
+ selects.push(select);
+ }
+ else
+ this._log.warn("Unable to translate constraint of type " +
+ constraintType + " on attribute bound as " + nounDef.name);
+
+ lastConstraintWasSpecial = curConstraintIsSpecial;
+ }
+
+ if (selects.length)
+ whereClauses.push("id IN (" + selects.join(" INTERSECT ") + ")" +
+ validityConstraintSuffix);
+ }
+
+ let sqlString = "SELECT * FROM " + nounDef.tableName;
+ if (!aQuery.options.noMagic) {
+ if (aQuery.options.noDbQueryValidityConstraints &&
+ nounDef.dbQueryJoinMagicWithNoValidityConstraints)
+ sqlString += nounDef.dbQueryJoinMagicWithNoValidityConstraints;
+ else if (nounDef.dbQueryJoinMagic)
+ sqlString += nounDef.dbQueryJoinMagic;
+ }
+
+ if (whereClauses.length)
+ sqlString += " WHERE (" + whereClauses.join(") OR (") + ")";
+
+ if (aQuery.options.explicitSQL)
+ sqlString = aQuery.options.explicitSQL;
+
+ if (aQuery.options.outerWrapColumns)
+ sqlString = "SELECT *, " + aQuery.options.outerWrapColumns.join(", ") +
+ " FROM (" + sqlString + ")";
+
+ if (aQuery._order.length) {
+ let orderClauses = [];
+ for (let [, colName] in Iterator(aQuery._order)) {
+ if (colName.startsWith("-"))
+ orderClauses.push(colName.substring(1) + " DESC");
+ else
+ orderClauses.push(colName + " ASC");
+ }
+ sqlString += " ORDER BY " + orderClauses.join(", ");
+ }
+
+ if (aQuery._limit) {
+ if (!("limitClauseAlreadyIncluded" in aQuery.options))
+ sqlString += " LIMIT ?";
+ boundArgs.push(aQuery._limit);
+ }
+
+ this._log.debug("QUERY FROM QUERY: " + sqlString + " ARGS: " + boundArgs);
+
+ // if we want to become explicit, replace the query (which has already
+ // provided our actual SQL query) with an explicit query. This will be
+ // what gets attached to the collection in the event we create a new
+ // collection. If we are reusing one, we assume that the explicitness,
+ // if desired, already happened.
+ // (we do not need to pass an argument to the explicitQueryClass constructor
+ // because it will be passed in to the collection's constructor, which will
+ // ensure that the collection attribute gets set.)
+ if (aArgs && ("becomeExplicit" in aArgs) && aArgs.becomeExplicit)
+ aQuery = new nounDef.explicitQueryClass();
+ else if (aArgs && ("becomeNull" in aArgs) && aArgs.becomeNull)
+ aQuery = new nounDef.nullQueryClass();
+
+ return this._queryFromSQLString(sqlString, boundArgs, nounDef, aQuery,
+ aListener, aListenerData, aExistingCollection, aMasterCollection);
+ },
+
+ _queryFromSQLString: function gloda_ds__queryFromSQLString(aSqlString,
+ aBoundArgs, aNounDef, aQuery, aListener, aListenerData,
+ aExistingCollection, aMasterCollection) {
+ let statement = this._createAsyncStatement(aSqlString, true);
+ for (let [iBinding, bindingValue] in Iterator(aBoundArgs)) {
+ this._bindVariant(statement, iBinding, bindingValue);
+ }
+
+ let collection;
+ if (aExistingCollection)
+ collection = aExistingCollection;
+ else {
+ collection = new GlodaCollection(aNounDef, [], aQuery, aListener,
+ aMasterCollection);
+ GlodaCollectionManager.registerCollection(collection);
+ // we don't want to overwrite the existing listener or its data, but this
+ // does raise the question about what should happen if we get passed in
+ // a different listener and/or data.
+ if (aListenerData !== undefined)
+ collection.data = aListenerData;
+ }
+ if (aListenerData) {
+ if (collection.dataStack)
+ collection.dataStack.push(aListenerData);
+ else
+ collection.dataStack = [aListenerData];
+ }
+
+ statement.executeAsync(new QueryFromQueryCallback(statement, aNounDef,
+ collection));
+ statement.finalize();
+ return collection;
+ },
+
+ /**
+ *
+ *
+ */
+ loadNounItem: function gloda_ds_loadNounItem(aItem, aReferencesByNounID,
+ aInverseReferencesByNounID) {
+ let attribIDToDBDefAndParam = this._attributeIDToDBDefAndParam;
+
+ let hadDeps = aItem._deps != null;
+ let deps = aItem._deps || {};
+ let hasDeps = false;
+
+ //this._log.debug(" hadDeps: " + hadDeps + " deps: " +
+ // Log4Moz.enumerateProperties(deps).join(","));
+
+ for (let attrib of aItem.NOUN_DEF.specialLoadAttribs) {
+ let objectNounDef = attrib.objectNounDef;
+
+ if (attrib.special === this.kSpecialColumnChildren) {
+ let invReferences = aInverseReferencesByNounID[objectNounDef.id];
+ if (invReferences === undefined)
+ invReferences = aInverseReferencesByNounID[objectNounDef.id] = {};
+ // only contribute if it's not already pending or there
+ if (!(attrib.id in deps) && aItem[attrib.storageAttributeName] == null){
+ //this._log.debug(" Adding inv ref for: " + aItem.id);
+ if (!(aItem.id in invReferences))
+ invReferences[aItem.id] = null;
+ deps[attrib.id] = null;
+ hasDeps = true;
+ }
+ }
+ else if (attrib.special === this.kSpecialColumnParent) {
+ let references = aReferencesByNounID[objectNounDef.id];
+ if (references === undefined)
+ references = aReferencesByNounID[objectNounDef.id] = {};
+ // nothing to contribute if it's already there
+ if (!(attrib.id in deps) &&
+ aItem[attrib.valueStorageAttributeName] == null) {
+ let parentID = aItem[attrib.idStorageAttributeName];
+ if (!(parentID in references))
+ references[parentID] = null;
+ //this._log.debug(" Adding parent ref for: " +
+ // aItem[attrib.idStorageAttributeName]);
+ deps[attrib.id] = null;
+ hasDeps = true;
+ }
+ else {
+ this._log.debug(" paranoia value storage: " + aItem[attrib.valueStorageAttributeName]);
+ }
+ }
+ }
+
+ // bail here if arbitrary values are not allowed, there just is no
+ // encoded json, or we already had dependencies for this guy, implying
+ // the json pass has already been performed
+ if (!aItem.NOUN_DEF.allowsArbitraryAttrs || !aItem._jsonText || hadDeps) {
+ if (hasDeps)
+ aItem._deps = deps;
+ return hasDeps;
+ }
+
+ //this._log.debug(" load json: " + aItem._jsonText);
+ let jsonDict = JSON.parse(aItem._jsonText);
+ delete aItem._jsonText;
+
+ // Iterate over the attributes on the item
+ for (let attribId in jsonDict) {
+ let jsonValue = jsonDict[attribId];
+ // It is technically impossible for attribute ids to go away at this
+ // point in time. This would require someone to monkey around with
+ // our schema. But we will introduce this functionality one day, so
+ // prepare for it now.
+ if (!(attribId in attribIDToDBDefAndParam))
+ continue;
+ // find the attribute definition that corresponds to this key
+ let dbAttrib = attribIDToDBDefAndParam[attribId][0];
+
+ let attrib = dbAttrib.attrDef;
+ // The attribute definition will fail to exist if no one defines the
+ // attribute anymore. This can happen for many reasons: an extension
+ // was uninstalled, an extension was changed and no longer defines the
+ // attribute, or patches are being applied/unapplied. Ignore this
+ // attribute if missing.
+ if (attrib == null)
+ continue;
+ let objectNounDef = attrib.objectNounDef;
+
+ // If it has a tableName member but no fromJSON, then it's a persistent
+ // object that needs to be loaded, which also means we need to hold it in
+ // a collection owned by our collection.
+ // (If it has a fromJSON method, then it's a special case like
+ // MimeTypeNoun where it is authoritatively backed by a table but caches
+ // everything into memory. There is no case where fromJSON would be
+ // implemented but we should still be doing database lookups.)
+ if (objectNounDef.tableName && !objectNounDef.fromJSON) {
+ let references = aReferencesByNounID[objectNounDef.id];
+ if (references === undefined)
+ references = aReferencesByNounID[objectNounDef.id] = {};
+
+ if (attrib.singular) {
+ if (!(jsonValue in references))
+ references[jsonValue] = null;
+ }
+ else {
+ for (let key in jsonValue) {
+ let anID = jsonValue[key];
+ if (!(anID in references))
+ references[anID] = null;
+ }
+ }
+
+ deps[attribId] = jsonValue;
+ hasDeps = true;
+ }
+ /* if it has custom contribution logic, use it */
+ else if (objectNounDef.contributeObjDependencies) {
+ if (objectNounDef.contributeObjDependencies(jsonValue,
+ aReferencesByNounID, aInverseReferencesByNounID)) {
+ deps[attribId] = jsonValue;
+ hasDeps = true;
+ }
+ else // just propagate the value, it's some form of simple sentinel
+ aItem[attrib.boundName] = jsonValue;
+ }
+ // otherwise, the value just needs to be de-persisted, or...
+ else if (objectNounDef.fromJSON) {
+ if (attrib.singular) {
+ // For consistency with the non-singular case, we don't assign the
+ // attribute if undefined is returned.
+ let deserialized = objectNounDef.fromJSON(jsonValue, aItem);
+ if (deserialized !== undefined)
+ aItem[attrib.boundName] = deserialized;
+ }
+ else {
+ // Convert all the entries in the list filtering out any undefined
+ // values. (TagNoun will do this if the tag is now dead.)
+ let outList = [];
+ for (let key in jsonValue) {
+ let val = jsonValue[key];
+ let deserialized = objectNounDef.fromJSON(val, aItem);
+ if (deserialized !== undefined)
+ outList.push(deserialized);
+ }
+ // Note: It's possible if we filtered things out that this is an empty
+ // list. This is acceptable because this is somewhat of an unusual
+ // case and I don't think we want to further complicate our
+ // semantics.
+ aItem[attrib.boundName] = outList;
+ }
+ }
+ // it's fine as is
+ else
+ aItem[attrib.boundName] = jsonValue;
+ }
+
+ if (hasDeps)
+ aItem._deps = deps;
+ return hasDeps;
+ },
+
+ loadNounDeferredDeps: function gloda_ds_loadNounDeferredDeps(aItem,
+ aReferencesByNounID, aInverseReferencesByNounID) {
+ if (aItem._deps === undefined)
+ return;
+
+ //this._log.debug(" loading deferred, deps: " +
+ // Log4Moz.enumerateProperties(aItem._deps).join(","));
+
+
+ let attribIDToDBDefAndParam = this._attributeIDToDBDefAndParam;
+
+ for (let [attribId, jsonValue] in Iterator(aItem._deps)) {
+ let dbAttrib = attribIDToDBDefAndParam[attribId][0];
+ let attrib = dbAttrib.attrDef;
+
+ let objectNounDef = attrib.objectNounDef;
+ let references = aReferencesByNounID[objectNounDef.id];
+ if (attrib.special) {
+ if (attrib.special === this.kSpecialColumnChildren) {
+ let inverseReferences = aInverseReferencesByNounID[objectNounDef.id];
+ //this._log.info("inverse assignment: " + objectNounDef.id +
+ // " of " + aItem.id)
+ aItem[attrib.storageAttributeName] = inverseReferences[aItem.id];
+ }
+ else if (attrib.special === this.kSpecialColumnParent) {
+ //this._log.info("parent column load: " + objectNounDef.id +
+ // " storage value: " + aItem[attrib.idStorageAttributeName]);
+ aItem[attrib.valueStorageAttributeName] =
+ references[aItem[attrib.idStorageAttributeName]];
+ }
+ }
+ else if (objectNounDef.tableName) {
+ //this._log.info("trying to load: " + objectNounDef.id + " refs: " +
+ // jsonValue + ": " + Log4Moz.enumerateProperties(jsonValue).join(","));
+ if (attrib.singular)
+ aItem[attrib.boundName] = references[jsonValue];
+ else
+ aItem[attrib.boundName] = Object.keys(jsonValue).
+ map(key => references[jsonValue[key]]);
+ }
+ else if (objectNounDef.contributeObjDependencies) {
+ aItem[attrib.boundName] =
+ objectNounDef.resolveObjDependencies(jsonValue, aReferencesByNounID,
+ aInverseReferencesByNounID);
+ }
+ // there is no other case
+ }
+
+ delete aItem._deps;
+ },
+
+ /* ********** Contact ********** */
+ _nextContactId: 1,
+
+ _populateContactManagedId: function () {
+ let stmt = this._createSyncStatement("SELECT MAX(id) FROM contacts", true);
+ if (stmt.executeStep()) { // no chance of this SQLITE_BUSY on this call
+ this._nextContactId = stmt.getInt64(0) + 1;
+ }
+ stmt.finalize();
+ },
+
+ get _insertContactStatement() {
+ let statement = this._createAsyncStatement(
+ "INSERT INTO contacts (id, directoryUUID, contactUUID, name, popularity,\
+ frecency, jsonAttributes) \
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)");
+ this.__defineGetter__("_insertContactStatement", () => statement);
+ return this._insertContactStatement;
+ },
+
+ createContact: function gloda_ds_createContact(aDirectoryUUID, aContactUUID,
+ aName, aPopularity, aFrecency) {
+ let contactID = this._nextContactId++;
+ let contact = new GlodaContact(this, contactID,
+ aDirectoryUUID, aContactUUID, aName,
+ aPopularity, aFrecency);
+ return contact;
+ },
+
+ insertContact: function gloda_ds_insertContact(aContact) {
+ let ics = this._insertContactStatement;
+ ics.bindInt64Parameter(0, aContact.id);
+ if (aContact.directoryUUID == null)
+ ics.bindNullParameter(1);
+ else
+ ics.bindStringParameter(1, aContact.directoryUUID);
+ if (aContact.contactUUID == null)
+ ics.bindNullParameter(2);
+ else
+ ics.bindStringParameter(2, aContact.contactUUID);
+ ics.bindStringParameter(3, aContact.name);
+ ics.bindInt64Parameter(4, aContact.popularity);
+ ics.bindInt64Parameter(5, aContact.frecency);
+ if (aContact._jsonText)
+ ics.bindStringParameter(6, aContact._jsonText);
+ else
+ ics.bindNullParameter(6);
+
+ ics.executeAsync(this.trackAsync());
+
+ return aContact;
+ },
+
+ get _updateContactStatement() {
+ let statement = this._createAsyncStatement(
+ "UPDATE contacts SET directoryUUID = ?1, \
+ contactUUID = ?2, \
+ name = ?3, \
+ popularity = ?4, \
+ frecency = ?5, \
+ jsonAttributes = ?6 \
+ WHERE id = ?7");
+ this.__defineGetter__("_updateContactStatement", () => statement);
+ return this._updateContactStatement;
+ },
+
+ updateContact: function gloda_ds_updateContact(aContact) {
+ let ucs = this._updateContactStatement;
+ ucs.bindInt64Parameter(6, aContact.id);
+ ucs.bindStringParameter(0, aContact.directoryUUID);
+ ucs.bindStringParameter(1, aContact.contactUUID);
+ ucs.bindStringParameter(2, aContact.name);
+ ucs.bindInt64Parameter(3, aContact.popularity);
+ ucs.bindInt64Parameter(4, aContact.frecency);
+ if (aContact._jsonText)
+ ucs.bindStringParameter(5, aContact._jsonText);
+ else
+ ucs.bindNullParameter(5);
+
+ ucs.executeAsync(this.trackAsync());
+ },
+
+ _contactFromRow: function gloda_ds_contactFromRow(aRow) {
+ let directoryUUID, contactUUID, jsonText;
+ if (aRow.getTypeOfIndex(1) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ directoryUUID = null;
+ else
+ directoryUUID = aRow.getString(1);
+ if (aRow.getTypeOfIndex(2) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ contactUUID = null;
+ else
+ contactUUID = aRow.getString(2);
+ if (aRow.getTypeOfIndex(6) == Ci.mozIStorageValueArray.VALUE_TYPE_NULL)
+ jsonText = undefined;
+ else
+ jsonText = aRow.getString(6);
+
+ return new GlodaContact(this, aRow.getInt64(0), directoryUUID,
+ contactUUID, aRow.getString(5),
+ aRow.getInt64(3), aRow.getInt64(4), jsonText);
+ },
+
+ get _selectContactByIDStatement() {
+ let statement = this._createSyncStatement(
+ "SELECT * FROM contacts WHERE id = ?1");
+ this.__defineGetter__("_selectContactByIDStatement",
+ () => statement);
+ return this._selectContactByIDStatement;
+ },
+
+ /**
+ * Synchronous contact lookup currently only for use by gloda's creation
+ * of the concept of "me". It is okay for it to be doing synchronous work
+ * because it is part of the startup process before any user code could
+ * have gotten a reference to Gloda, but no one else should do this.
+ */
+ getContactByID: function gloda_ds_getContactByID(aContactID) {
+ let contact = GlodaCollectionManager.cacheLookupOne(
+ GlodaContact.prototype.NOUN_ID, aContactID);
+
+ if (contact === null) {
+ let scbi = this._selectContactByIDStatement;
+ scbi.bindInt64Parameter(0, aContactID);
+ if (this._syncStep(scbi)) {
+ contact = this._contactFromRow(scbi);
+ GlodaCollectionManager.itemLoaded(contact);
+ }
+ scbi.reset();
+ }
+
+ return contact;
+ },
+
+ /* ********** Identity ********** */
+ /** next identity id, managed for async use reasons. */
+ _nextIdentityId: 1,
+ _populateIdentityManagedId: function () {
+ let stmt = this._createSyncStatement(
+ "SELECT MAX(id) FROM identities", true);
+ if (stmt.executeStep()) { // no chance of this SQLITE_BUSY on this call
+ this._nextIdentityId = stmt.getInt64(0) + 1;
+ }
+ stmt.finalize();
+ },
+
+ get _insertIdentityStatement() {
+ let statement = this._createAsyncStatement(
+ "INSERT INTO identities (id, contactID, kind, value, description, relay) \
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6)");
+ this.__defineGetter__("_insertIdentityStatement", () => statement);
+ return this._insertIdentityStatement;
+ },
+
+ createIdentity: function gloda_ds_createIdentity(aContactID, aContact, aKind,
+ aValue, aDescription,
+ aIsRelay) {
+ let identityID = this._nextIdentityId++;
+ let iis = this._insertIdentityStatement;
+ iis.bindInt64Parameter(0, identityID);
+ iis.bindInt64Parameter(1, aContactID);
+ iis.bindStringParameter(2, aKind);
+ iis.bindStringParameter(3, aValue);
+ iis.bindStringParameter(4, aDescription);
+ iis.bindInt64Parameter(5, aIsRelay ? 1 : 0);
+ iis.executeAsync(this.trackAsync());
+
+ let identity = new GlodaIdentity(this, identityID,
+ aContactID, aContact, aKind, aValue,
+ aDescription, aIsRelay);
+ GlodaCollectionManager.itemsAdded(identity.NOUN_ID, [identity]);
+ return identity;
+ },
+
+ _identityFromRow: function gloda_ds_identityFromRow(aRow) {
+ return new GlodaIdentity(this, aRow.getInt64(0), aRow.getInt64(1), null,
+ aRow.getString(2), aRow.getString(3),
+ aRow.getString(4),
+ aRow.getInt32(5) ? true : false);
+ },
+
+ get _selectIdentityByKindValueStatement() {
+ let statement = this._createSyncStatement(
+ "SELECT * FROM identities WHERE kind = ?1 AND value = ?2");
+ this.__defineGetter__("_selectIdentityByKindValueStatement",
+ () => statement);
+ return this._selectIdentityByKindValueStatement;
+ },
+
+ /**
+ * Synchronous lookup of an identity by kind and value, only for use by
+ * the legacy gloda core code that creates a concept of "me".
+ * Ex: (email, foo@example.com)
+ */
+ getIdentity: function gloda_ds_getIdentity(aKind, aValue) {
+ let identity = GlodaCollectionManager.cacheLookupOneByUniqueValue(
+ GlodaIdentity.prototype.NOUN_ID, aKind + "@" + aValue);
+
+ let ibkv = this._selectIdentityByKindValueStatement;
+ ibkv.bindStringParameter(0, aKind);
+ ibkv.bindStringParameter(1, aValue);
+ if (this._syncStep(ibkv)) {
+ identity = this._identityFromRow(ibkv);
+ GlodaCollectionManager.itemLoaded(identity);
+ }
+ ibkv.reset();
+
+ return identity;
+ },
+};
+GlodaAttributeDBDef.prototype._datastore = GlodaDatastore;
+GlodaConversation.prototype._datastore = GlodaDatastore;
+GlodaFolder.prototype._datastore = GlodaDatastore;
+GlodaMessage.prototype._datastore = GlodaDatastore;
+GlodaContact.prototype._datastore = GlodaDatastore;
+GlodaIdentity.prototype._datastore = GlodaDatastore;
diff --git a/mailnews/db/gloda/modules/dbview.js b/mailnews/db/gloda/modules/dbview.js
new file mode 100644
index 000000000..4d34cf3af
--- /dev/null
+++ b/mailnews/db/gloda/modules/dbview.js
@@ -0,0 +1,178 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file is charged with providing you a way to have a pretty gloda-backed
+ * nsIMsgDBView.
+ */
+
+this.EXPORTED_SYMBOLS = ["GlodaSyntheticView"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+Cu.import("resource:///modules/gloda/public.js");
+Cu.import("resource:///modules/gloda/msg_search.js");
+
+/**
+ * Create a synthetic view suitable for passing to |FolderDisplayWidget.show|.
+ * You must pass a query, collection, or conversation in.
+ *
+ * @param {GlodaQuery} [aArgs.query] A gloda query to run.
+ * @param {GlodaCollection} [aArgs.collection] An already-populated collection
+ * to display. Do not call getCollection on a query and hand us that. We
+ * will not register ourselves as a listener and things will not work.
+ * @param {GlodaConversation} [aArgs.conversation] A conversation whose messages
+ * you want to display.
+ */
+function GlodaSyntheticView(aArgs) {
+ if ("query" in aArgs) {
+ this.query = aArgs.query;
+ this.collection = this.query.getCollection(this);
+ this.completed = false;
+ this.viewType = "global";
+ }
+ else if ("collection" in aArgs) {
+ this.query = null;
+ this.collection = aArgs.collection;
+ this.completed = true;
+ this.viewType = "global";
+ }
+ else if ("conversation" in aArgs) {
+ this.collection = aArgs.conversation.getMessagesCollection(this);
+ this.query = this.collection.query;
+ this.completed = false;
+ this.viewType = "conversation";
+ }
+ else {
+ throw new Error("You need to pass a query or collection");
+ }
+
+ this.customColumns = [];
+}
+GlodaSyntheticView.prototype = {
+ defaultSort: [[Ci.nsMsgViewSortType.byDate, Ci.nsMsgViewSortOrder.descending]],
+
+ /**
+ * Request the search be performed and notification provided to
+ * aSearchListener. If results are already available, they should
+ * be provided to aSearchListener without re-performing the search.
+ */
+ search: function(aSearchListener, aCompletionCallback) {
+ this.searchListener = aSearchListener;
+ this.completionCallback = aCompletionCallback;
+
+ this.searchListener.onNewSearch();
+ if (this.completed) {
+ this.reportResults(this.collection.items);
+ // we're not really aborting, but it closes things out nicely
+ this.abortSearch();
+ return;
+ }
+ },
+
+ abortSearch: function() {
+ if (this.searchListener)
+ this.searchListener.onSearchDone(Cr.NS_OK);
+ if (this.completionCallback)
+ this.completionCallback();
+ this.searchListener = null;
+ this.completionCallback = null;
+ },
+
+ reportResults: function(aItems) {
+ for (let item of aItems) {
+ let hdr = item.folderMessage;
+ if (hdr)
+ this.searchListener.onSearchHit(hdr, hdr.folder);
+ }
+ },
+
+ /**
+ * Helper function used by |DBViewWrapper.getMsgHdrForMessageID| since there
+ * are no actual backing folders for it to check.
+ */
+ getMsgHdrForMessageID: function(aMessageId) {
+ for (let item of this.collection.items) {
+ if (item.headerMessageID == aMessageId) {
+ let hdr = item.folderMessage;
+ if (hdr)
+ return hdr;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * The default set of columns to show.
+ */
+ DEFAULT_COLUMN_STATES: {
+ threadCol: {
+ visible: true,
+ },
+ flaggedCol: {
+ visible: true,
+ },
+ subjectCol: {
+ visible: true,
+ },
+ correspondentCol: {
+ visible: Services.prefs.getBoolPref("mail.threadpane.use_correspondents"),
+ },
+ senderCol: {
+ visible: !Services.prefs.getBoolPref("mail.threadpane.use_correspondents"),
+ },
+ dateCol: {
+ visible: true,
+ },
+ locationCol: {
+ visible: true,
+ },
+ },
+
+ // --- settings persistence
+ getPersistedSetting: function(aSetting) {
+ try {
+ return JSON.parse(Services.prefs.getCharPref(
+ "mailnews.database.global.views." + this.viewType + "." + aSetting
+ ));
+ }
+ catch (e) {
+ return this.getDefaultSetting(aSetting);
+ }
+ },
+ setPersistedSetting: function(aSetting, aValue) {
+ Services.prefs.setCharPref(
+ "mailnews.database.global.views." + this.viewType + "." + aSetting,
+ JSON.stringify(aValue)
+ );
+ },
+ getDefaultSetting: function(aSetting) {
+ if (aSetting == "columns")
+ return this.DEFAULT_COLUMN_STATES;
+ else
+ return undefined;
+ },
+
+ // --- collection listener
+ onItemsAdded: function(aItems, aCollection) {
+ if (this.searchListener)
+ this.reportResults(aItems);
+ },
+ onItemsModified: function(aItems, aCollection) {
+ },
+ onItemsRemoved: function(aItems, aCollection) {
+ },
+ onQueryCompleted: function(aCollection) {
+ this.completed = true;
+ this.searchListener.onSearchDone(Cr.NS_OK);
+ if (this.completionCallback)
+ this.completionCallback();
+ },
+};
diff --git a/mailnews/db/gloda/modules/everybody.js b/mailnews/db/gloda/modules/everybody.js
new file mode 100644
index 000000000..b3332f473
--- /dev/null
+++ b/mailnews/db/gloda/modules/everybody.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = [];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+var LOG = Log4Moz.repository.getLogger("gloda.everybody");
+
+var importNS = {};
+
+function loadModule(aModuleURI, aNSContrib) {
+ try {
+ LOG.info("... loading " + aModuleURI);
+ Cu.import(aModuleURI, importNS);
+ }
+ catch (ex) {
+ LOG.error("!!! error loading " + aModuleURI);
+ LOG.error("(" + ex.fileName + ":" + ex.lineNumber + ") " + ex);
+ return false;
+ }
+ LOG.info("+++ loaded " + aModuleURI);
+
+ if (aNSContrib) {
+ try {
+ importNS[aNSContrib].init();
+ }
+ catch (ex) {
+ LOG.error("!!! error initializing " + aModuleURI);
+ LOG.error("(" + ex.fileName + ":" + ex.lineNumber + ") " + ex);
+ return false;
+ }
+ LOG.info("+++ inited " + aModuleURI);
+ }
+ return true;
+}
+
+loadModule("resource:///modules/gloda/fundattr.js", "GlodaFundAttr");
+loadModule("resource:///modules/gloda/explattr.js", "GlodaExplicitAttr");
+
+loadModule("resource:///modules/gloda/noun_tag.js");
+loadModule("resource:///modules/gloda/noun_freetag.js");
+loadModule("resource:///modules/gloda/noun_mimetype.js");
+loadModule("resource:///modules/gloda/index_msg.js");
+loadModule("resource:///modules/gloda/index_ab.js", "GlodaABAttrs");
diff --git a/mailnews/db/gloda/modules/explattr.js b/mailnews/db/gloda/modules/explattr.js
new file mode 100644
index 000000000..851f88a11
--- /dev/null
+++ b/mailnews/db/gloda/modules/explattr.js
@@ -0,0 +1,193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file provides the "explicit attribute" provider for messages. It is
+ * concerned with attributes that are the result of user actions. For example,
+ * whether a message is starred (flagged), message tags, whether it is
+ * read/unread, etc.
+ */
+
+this.EXPORTED_SYMBOLS = ['GlodaExplicitAttr'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+Cu.import("resource:///modules/StringBundle.js");
+
+Cu.import("resource:///modules/gloda/utils.js");
+Cu.import("resource:///modules/gloda/gloda.js");
+Cu.import("resource:///modules/gloda/noun_tag.js");
+Cu.import("resource:///modules/mailServices.js");
+
+
+var nsMsgMessageFlags_Replied = Ci.nsMsgMessageFlags.Replied;
+var nsMsgMessageFlags_Forwarded = Ci.nsMsgMessageFlags.Forwarded;
+
+var EXT_BUILTIN = "built-in";
+
+/**
+ * @namespace Explicit attribute provider. Indexes/defines attributes that are
+ * explicitly a result of user action. This dubiously includes marking a
+ * message as read.
+ */
+var GlodaExplicitAttr = {
+ providerName: "gloda.explattr",
+ strings: new StringBundle("chrome://messenger/locale/gloda.properties"),
+ _log: null,
+ _msgTagService: null,
+
+ init: function gloda_explattr_init() {
+ this._log = Log4Moz.repository.getLogger("gloda.explattr");
+
+ this._msgTagService = MailServices.tags;
+
+ try {
+ this.defineAttributes();
+ }
+ catch (ex) {
+ this._log.error("Error in init: " + ex);
+ throw ex;
+ }
+ },
+
+ /** Boost for starred messages. */
+ NOTABILITY_STARRED: 16,
+ /** Boost for tagged messages, first tag. */
+ NOTABILITY_TAGGED_FIRST: 8,
+ /** Boost for tagged messages, each additional tag. */
+ NOTABILITY_TAGGED_ADDL: 1,
+
+ defineAttributes: function() {
+ // Tag
+ this._attrTag = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrExplicit,
+ attributeName: "tag",
+ bindName: "tags",
+ singular: false,
+ emptySetIsSignificant: true,
+ facet: true,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_TAG,
+ parameterNoun: null,
+ // Property change notifications that we care about:
+ propertyChanges: ["keywords"],
+ }); // not-tested
+
+ // Star
+ this._attrStar = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrExplicit,
+ attributeName: "star",
+ bindName: "starred",
+ singular: true,
+ facet: true,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_BOOLEAN,
+ parameterNoun: null,
+ }); // tested-by: test_attributes_explicit
+ // Read/Unread
+ this._attrRead = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrExplicit,
+ attributeName: "read",
+ // Make the message query-able but without using the database.
+ canQuery: "truthy-but-not-true",
+ singular: true,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_BOOLEAN,
+ parameterNoun: null,
+ }); // tested-by: test_attributes_explicit
+
+ /**
+ * Has this message been replied to by the user.
+ */
+ this._attrRepliedTo = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrExplicit,
+ attributeName: "repliedTo",
+ singular: true,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_BOOLEAN,
+ parameterNoun: null,
+ }); // tested-by: test_attributes_explicit
+
+ /**
+ * Has this user forwarded this message to someone.
+ */
+ this._attrForwarded = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrExplicit,
+ attributeName: "forwarded",
+ singular: true,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_BOOLEAN,
+ parameterNoun: null,
+ }); // tested-by: test_attributes_explicit
+ },
+
+ process: function* Gloda_explattr_process(aGlodaMessage, aRawReps, aIsNew,
+ aCallbackHandle) {
+ let aMsgHdr = aRawReps.header;
+
+ aGlodaMessage.starred = aMsgHdr.isFlagged;
+ if (aGlodaMessage.starred)
+ aGlodaMessage.notability += this.NOTABILITY_STARRED;
+
+ aGlodaMessage.read = aMsgHdr.isRead;
+
+ let flags = aMsgHdr.flags;
+ aGlodaMessage.repliedTo = Boolean(flags & nsMsgMessageFlags_Replied);
+ aGlodaMessage.forwarded = Boolean(flags & nsMsgMessageFlags_Forwarded);
+
+ let tags = aGlodaMessage.tags = [];
+
+ // -- Tag
+ // build a map of the keywords
+ let keywords = aMsgHdr.getStringProperty("keywords");
+ let keywordList = keywords.split(' ');
+ let keywordMap = {};
+ for (let iKeyword = 0; iKeyword < keywordList.length; iKeyword++) {
+ let keyword = keywordList[iKeyword];
+ keywordMap[keyword] = true;
+ }
+
+ let tagArray = TagNoun.getAllTags();
+ for (let iTag = 0; iTag < tagArray.length; iTag++) {
+ let tag = tagArray[iTag];
+ if (tag.key in keywordMap)
+ tags.push(tag);
+ }
+
+ if (tags.length)
+ aGlodaMessage.notability += this.NOTABILITY_TAGGED_FIRST +
+ (tags.length - 1) * this.NOTABILITY_TAGGED_ADDL;
+
+ yield Gloda.kWorkDone;
+ },
+
+ /**
+ * Duplicates the notability logic from process(). Arguably process should
+ * be factored to call us, grokNounItem should be factored to call us, or we
+ * should get sufficiently fancy that our code wildly diverges.
+ */
+ score: function Gloda_explattr_score(aMessage, aContext) {
+ let score = 0;
+ if (aMessage.starred)
+ score += this.NOTABILITY_STARRED;
+ if (aMessage.tags.length)
+ score += this.NOTABILITY_TAGGED_FIRST +
+ (aMessage.tags.length - 1) * this.NOTABILITY_TAGGED_ADDL;
+ return score;
+ },
+};
diff --git a/mailnews/db/gloda/modules/facet.js b/mailnews/db/gloda/modules/facet.js
new file mode 100644
index 000000000..0f73d3d5b
--- /dev/null
+++ b/mailnews/db/gloda/modules/facet.js
@@ -0,0 +1,582 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file provides faceting logic.
+ */
+
+var EXPORTED_SYMBOLS = ["FacetDriver", "FacetUtils"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/public.js");
+
+/**
+ * Decides the appropriate faceters for the noun type and drives the faceting
+ * process. This class and the faceters are intended to be reusable so that
+ * you only need one instance per faceting session. (Although each faceting
+ * pass is accordingly destructive to previous results.)
+ *
+ * Our strategy for faceting is to process one attribute at a time across all
+ * the items in the provided set. The alternative would be to iterate over
+ * the items and then iterate over the attributes on each item. While both
+ * approaches have caching downsides
+ */
+function FacetDriver(aNounDef, aWindow) {
+ this.nounDef = aNounDef;
+ this._window = aWindow;
+
+ this._makeFaceters();
+}
+FacetDriver.prototype = {
+ /**
+ * Populate |this.faceters| with a set of faceters appropriate to the noun
+ * definition associated with this instance.
+ */
+ _makeFaceters: function() {
+ let faceters = this.faceters = [];
+
+ function makeFaceter(aAttrDef, aFacetDef) {
+ let facetType = aFacetDef.type;
+
+ if (aAttrDef.singular) {
+ if (facetType == "date")
+ faceters.push(new DateFaceter(aAttrDef, aFacetDef));
+ else
+ faceters.push(new DiscreteFaceter(aAttrDef, aFacetDef));
+ }
+ else {
+ if (facetType == "nonempty?")
+ faceters.push(new NonEmptySetFaceter(aAttrDef, aFacetDef));
+ else
+ faceters.push(new DiscreteSetFaceter(aAttrDef, aFacetDef));
+ }
+ }
+
+ for (let key in this.nounDef.attribsByBoundName) {
+ let attrDef = this.nounDef.attribsByBoundName[key];
+ // ignore attributes that do not want to be faceted
+ if (!attrDef.facet)
+ continue;
+
+ makeFaceter(attrDef, attrDef.facet);
+
+ if ("extraFacets" in attrDef) {
+ for (let facetDef of attrDef.extraFacets) {
+ makeFaceter(attrDef, facetDef);
+ }
+ }
+ }
+ },
+ /**
+ * Asynchronously facet the provided items, calling the provided callback when
+ * completed.
+ */
+ go: function FacetDriver_go(aItems, aCallback, aCallbackThis) {
+ this.items = aItems;
+ this.callback = aCallback;
+ this.callbackThis = aCallbackThis;
+
+ this._nextFaceter = 0;
+ this._drive();
+ },
+
+ _MAX_FACETING_TIMESLICE_MS: 100,
+ _FACETING_YIELD_DURATION_MS: 0,
+ _driveWrapper: function(aThis) {
+ aThis._drive();
+ },
+ _drive: function() {
+ let start = Date.now();
+
+ while (this._nextFaceter < this.faceters.length) {
+ let faceter = this.faceters[this._nextFaceter++];
+ // for now we facet in one go, but the long-term plan allows for them to
+ // be generators.
+ faceter.facetItems(this.items);
+
+ let delta = Date.now() - start;
+ if (delta > this._MAX_FACETING_TIMESLICE_MS) {
+ this._window.setTimeout(this._driveWrapper,
+ this._FACETING_YIELD_DURATION_MS,
+ this);
+ return;
+ }
+ }
+
+ // we only get here once we are done with the faceters
+ this.callback.call(this.callbackThis);
+ }
+};
+
+var FacetUtils = {
+ _groupSizeComparator: function(a, b) {
+ return b[1].length - a[1].length;
+ },
+
+ /**
+ * Given a list where each entry is a tuple of [group object, list of items
+ * belonging to that group], produce a new list of the top grouped items. We
+ * used to also produce an "other" aggregation, but that turned out to be
+ * conceptually difficult to deal with, so that's gone, leaving this method
+ * with much less to do.
+ *
+ * @param aAttrDef The attribute for the facet we are working with.
+ * @param aGroups The list of groups built for the facet.
+ * @param aMaxCount The number of result rows you want back.
+ */
+ makeTopGroups: function FacetUtils_makeTopGroups(aAttrDef, aGroups,
+ aMaxCount) {
+ let nounDef = aAttrDef.objectNounDef;
+ let realGroupsToUse = aMaxCount;
+
+ let orderedBySize = aGroups.concat();
+ orderedBySize.sort(this._groupSizeComparator);
+
+ // - get the real groups to use and order them by the attribute comparator
+ let outGroups = orderedBySize.slice(0, realGroupsToUse);
+ let comparator = nounDef.comparator;
+ function comparatorHelper(a, b) {
+ return comparator(a[0], b[0]);
+ }
+ outGroups.sort(comparatorHelper);
+
+ return outGroups;
+ }
+};
+
+/**
+ * Facet discrete things like message authors, boolean values, etc. Only
+ * appropriate for use on singular values. Use |DiscreteSetFaceter| for
+ * non-singular values.
+ */
+function DiscreteFaceter(aAttrDef, aFacetDef) {
+ this.attrDef = aAttrDef;
+ this.facetDef = aFacetDef;
+}
+DiscreteFaceter.prototype = {
+ type: "discrete",
+ /**
+ * Facet the given set of items, deferring to the appropriate helper method
+ */
+ facetItems: function(aItems) {
+ if (this.attrDef.objectNounDef.isPrimitive)
+ return this.facetPrimitiveItems(aItems);
+ else
+ return this.facetComplexItems(aItems);
+ },
+ /**
+ * Facet an attribute whose value is primitive, meaning that it is a raw
+ * numeric value or string, rather than a complex object.
+ */
+ facetPrimitiveItems: function(aItems) {
+ let attrKey = this.attrDef.boundName;
+ let nounDef = this.attrDef.objectNounDef;
+ let filter = this.facetDef.filter;
+
+ let valStrToVal = {};
+ let groups = this.groups = {};
+ this.groupCount = 0;
+
+ for (let item of aItems) {
+ let val = (attrKey in item) ? item[attrKey] : null;
+ if (val === Gloda.IGNORE_FACET)
+ continue;
+
+ // skip items the filter tells us to ignore
+ if (filter && !filter(val))
+ continue;
+
+ // We need to use hasOwnProperty because we cannot guarantee that the
+ // contents of val won't collide with the attributes in Object.prototype.
+ if (groups.hasOwnProperty(val))
+ groups[val].push(item);
+ else {
+ groups[val] = [item];
+ valStrToVal[val] = val;
+ this.groupCount++;
+ }
+ }
+
+ let orderedGroups = Object.keys(groups).
+ map(key => [valStrToVal[key], groups[key]]);
+ let comparator = this.facetDef.groupComparator;
+ function comparatorHelper(a, b) {
+ return comparator(a[0], b[0]);
+ }
+ orderedGroups.sort(comparatorHelper);
+ this.orderedGroups = orderedGroups;
+ },
+ /**
+ * Facet an attribute whose value is a complex object that can be identified
+ * by its 'id' attribute. This is the case where the value is itself a noun
+ * instance.
+ */
+ facetComplexItems: function(aItems) {
+ let attrKey = this.attrDef.boundName;
+ let nounDef = this.attrDef.objectNounDef;
+ let filter = this.facetDef.filter;
+ let idAttr = this.facetDef.groupIdAttr;
+
+ let groups = this.groups = {};
+ let groupMap = this.groupMap = {};
+ this.groupCount = 0;
+
+ for (let item of aItems) {
+ let val = (attrKey in item) ? item[attrKey] : null;
+ if (val === Gloda.IGNORE_FACET)
+ continue;
+
+ // skip items the filter tells us to ignore
+ if (filter && !filter(val))
+ continue;
+
+ let valId = (val == null) ? null : val[idAttr];
+ // We need to use hasOwnProperty because tag nouns are complex objects
+ // with id's that are non-numeric and so can collide with the contents
+ // of Object.prototype. (Note: the "tags" attribute is actually handled
+ // by the DiscreteSetFaceter.)
+ if (groupMap.hasOwnProperty(valId)) {
+ groups[valId].push(item);
+ }
+ else {
+ groupMap[valId] = val;
+ groups[valId] = [item];
+ this.groupCount++;
+ }
+ }
+
+ let orderedGroups = Object.keys(groups).
+ map(key => [groupMap[key], groups[key]]);
+ let comparator = this.facetDef.groupComparator;
+ function comparatorHelper(a, b) {
+ return comparator(a[0], b[0]);
+ }
+ orderedGroups.sort(comparatorHelper);
+ this.orderedGroups = orderedGroups;
+ },
+};
+
+/**
+ * Facet sets of discrete items. For example, tags applied to messages.
+ *
+ * The main differences between us and |DiscreteFaceter| are:
+ * - The empty set is notable.
+ * - Specific set configurations could be interesting, but are not low-hanging
+ * fruit.
+ */
+function DiscreteSetFaceter(aAttrDef, aFacetDef) {
+ this.attrDef = aAttrDef;
+ this.facetDef = aFacetDef;
+}
+DiscreteSetFaceter.prototype = {
+ type: "discrete",
+ /**
+ * Facet the given set of items, deferring to the appropriate helper method
+ */
+ facetItems: function(aItems) {
+ if (this.attrDef.objectNounDef.isPrimitive)
+ return this.facetPrimitiveItems(aItems);
+ else
+ return this.facetComplexItems(aItems);
+ },
+ /**
+ * Facet an attribute whose value is primitive, meaning that it is a raw
+ * numeric value or string, rather than a complex object.
+ */
+ facetPrimitiveItems: function(aItems) {
+ let attrKey = this.attrDef.boundName;
+ let nounDef = this.attrDef.objectNounDef;
+ let filter = this.facetDef.filter;
+
+ let groups = this.groups = {};
+ let valStrToVal = {};
+ this.groupCount = 0;
+
+ for (let item of aItems) {
+ let vals = (attrKey in item) ? item[attrKey] : null;
+ if (vals === Gloda.IGNORE_FACET)
+ continue;
+
+ if (vals == null || vals.length == 0) {
+ vals = [null];
+ }
+ for (let val of vals) {
+ // skip items the filter tells us to ignore
+ if (filter && !filter(val))
+ continue;
+
+ // We need to use hasOwnProperty because we cannot guarantee that the
+ // contents of val won't collide with the attributes in
+ // Object.prototype.
+ if (groups.hasOwnProperty(val))
+ groups[val].push(item);
+ else {
+ groups[val] = [item];
+ valStrToVal[val] = val;
+ this.groupCount++;
+ }
+ }
+ }
+
+ let orderedGroups = Object.keys(groups).
+ map(key => [valStrToVal[key], groups[key]]);
+ let comparator = this.facetDef.groupComparator;
+ function comparatorHelper(a, b) {
+ return comparator(a[0], b[0]);
+ }
+ orderedGroups.sort(comparatorHelper);
+ this.orderedGroups = orderedGroups;
+ },
+ /**
+ * Facet an attribute whose value is a complex object that can be identified
+ * by its 'id' attribute. This is the case where the value is itself a noun
+ * instance.
+ */
+ facetComplexItems: function(aItems) {
+ let attrKey = this.attrDef.boundName;
+ let nounDef = this.attrDef.objectNounDef;
+ let filter = this.facetDef.filter;
+ let idAttr = this.facetDef.groupIdAttr;
+
+ let groups = this.groups = {};
+ let groupMap = this.groupMap = {};
+ this.groupCount = 0;
+
+ for (let item of aItems) {
+ let vals = (attrKey in item) ? item[attrKey] : null;
+ if (vals === Gloda.IGNORE_FACET)
+ continue;
+
+ if (vals == null || vals.length == 0) {
+ vals = [null];
+ }
+ for (let val of vals) {
+ // skip items the filter tells us to ignore
+ if (filter && !filter(val))
+ continue;
+
+ let valId = (val == null) ? null : val[idAttr];
+ // We need to use hasOwnProperty because tag nouns are complex objects
+ // with id's that are non-numeric and so can collide with the contents
+ // of Object.prototype.
+ if (groupMap.hasOwnProperty(valId)) {
+ groups[valId].push(item);
+ }
+ else {
+ groupMap[valId] = val;
+ groups[valId] = [item];
+ this.groupCount++;
+ }
+ }
+ }
+
+ let orderedGroups = Object.keys(groups).
+ map(key => [groupMap[key], groups[key]]);
+ let comparator = this.facetDef.groupComparator;
+ function comparatorHelper(a, b) {
+ return comparator(a[0], b[0]);
+ }
+ orderedGroups.sort(comparatorHelper);
+ this.orderedGroups = orderedGroups;
+ },
+};
+
+/**
+ * Given a non-singular attribute, facet it as if it were a boolean based on
+ * whether there is anything in the list (set).
+ */
+function NonEmptySetFaceter(aAttrDef, aFacetDef) {
+ this.attrDef = aAttrDef;
+ this.facetDef = aFacetDef;
+}
+NonEmptySetFaceter.prototype = {
+ type: "boolean",
+ /**
+ * Facet the given set of items, deferring to the appropriate helper method
+ */
+ facetItems: function(aItems) {
+ let attrKey = this.attrDef.boundName;
+ let nounDef = this.attrDef.objectNounDef;
+
+ let trueValues = [];
+ let falseValues = [];
+
+ let groups = this.groups = {};
+ this.groupCount = 0;
+
+ for (let item of aItems) {
+ let vals = (attrKey in item) ? item[attrKey] : null;
+ if (vals == null || vals.length == 0)
+ falseValues.push(item);
+ else
+ trueValues.push(item);
+ }
+
+ this.orderedGroups = [];
+ if (trueValues.length)
+ this.orderedGroups.push([true, trueValues]);
+ if (falseValues.length)
+ this.orderedGroups.push([false, falseValues]);
+ this.groupCount = this.orderedGroups.length;
+ },
+ makeQuery: function(aGroupValues, aInclusive) {
+ let query = this.query = Gloda.newQuery(Gloda.NOUN_MESSAGE);
+
+ let constraintFunc = query[this.attrDef.boundName];
+ constraintFunc.call(query);
+
+ // Our query is always for non-empty lists (at this time), so we want to
+ // invert if they're excluding 'true' or including 'false', which means !=.
+ let invert = aGroupValues[0] != aInclusive;
+
+ return [query, invert];
+ }
+};
+
+
+/**
+ * Facet dates. We build a hierarchical nested structure of year, month, and
+ * day nesting levels. This decision was made speculatively in the hopes that
+ * it would allow us to do clustered analysis and that there might be a benefit
+ * for that. For example, if you search for "Christmas", we might notice
+ * clusters of messages around December of each year. We could then present
+ * these in a list as likely candidates, rather than a graphical timeline.
+ * Alternately, it could be used to inform a non-linear visualization. As it
+ * stands (as of this writing), it's just a complicating factor.
+ */
+function DateFaceter(aAttrDef, aFacetDef) {
+ this.attrDef = aAttrDef;
+ this.facetDef = aFacetDef;
+}
+DateFaceter.prototype = {
+ type: "date",
+ /**
+ *
+ */
+ facetItems: function(aItems) {
+ let attrKey = this.attrDef.boundName;
+ let nounDef = this.attrDef.objectNounDef;
+
+ let years = this.years = {_subCount: 0};
+ // generally track the time range
+ let oldest = null, newest = null;
+
+ let validItems = this.validItems = [];
+
+ // just cheat and put us at the front...
+ this.groupCount = aItems.length ? 1000 : 0;
+ this.orderedGroups = null;
+
+ /** The number of items with a null/missing attribute. */
+ this.missing = 0;
+
+ /**
+ * The number of items with a date that is unreasonably far in the past or
+ * in the future. Old-wise, we are concerned about incorrectly formatted
+ * messages (spam) that end up placed around the UNIX epoch. New-wise,
+ * we are concerned about messages that can't be explained by users who
+ * don't know how to set their clocks (both the current user and people
+ * sending them mail), mainly meaning spam.
+ * We want to avoid having our clever time-scale logic being made useless by
+ * these unreasonable messages.
+ */
+ this.unreasonable = 0;
+ // feb 1, 1970
+ let tooOld = new Date(1970, 1, 1);
+ // 3 days from now
+ let tooNew = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000);
+
+ for (let item of aItems) {
+ let val = (attrKey in item) ? item[attrKey] : null;
+ // -- missing
+ if (val == null) {
+ this.missing++;
+ continue;
+ }
+
+ // -- unreasonable
+ if (val < tooOld || val > tooNew) {
+ this.unreasonable++;
+ continue;
+ }
+
+ this.validItems.push(item);
+
+ // -- time range
+ if (oldest == null)
+ oldest = newest = val;
+ else if (val < oldest)
+ oldest = val;
+ else if (val > newest)
+ newest = val;
+
+ // -- bucket
+ // - year
+ let year, valYear = val.getYear();
+ if (valYear in years) {
+ year = years[valYear];
+ year._dateCount++;
+ }
+ else {
+ year = years[valYear] = {
+ _dateCount: 1,
+ _subCount: 0
+ };
+ years._subCount++;
+ }
+
+ // - month
+ let month, valMonth = val.getMonth();
+ if (valMonth in year) {
+ month = year[valMonth];
+ month._dateCount++;
+ }
+ else {
+ month = year[valMonth] = {
+ _dateCount: 1,
+ _subCount: 0
+ };
+ year._subCount++;
+ }
+
+ // - day
+ let valDate = val.getDate();
+ if (valDate in month) {
+ month[valDate].push(item);
+ }
+ else {
+ month[valDate] = [item];
+ }
+ }
+
+ this.oldest = oldest;
+ this.newest = newest;
+ },
+
+ _unionMonth: function(aMonthObj) {
+ let dayItemLists = [];
+ for (let key in aMonthObj) {
+ let dayItemList = aMonthObj[key];
+ if (typeof(key) == "string" && key.startsWith('_'))
+ continue;
+ dayItemLists.push(dayItemList);
+ }
+ return Array.concat.apply([], dayItemLists);
+ },
+
+ _unionYear: function(aYearObj) {
+ let monthItemLists = [];
+ for (let key in aYearObj) {
+ let monthObj = aYearObj[key];
+ if (typeof(key) == "string" && key.startsWith('_'))
+ continue;
+ monthItemLists.push(this._unionMonth(monthObj));
+ }
+ return Array.concat.apply([], monthItemLists);
+ }
+};
diff --git a/mailnews/db/gloda/modules/fundattr.js b/mailnews/db/gloda/modules/fundattr.js
new file mode 100644
index 000000000..75a424adb
--- /dev/null
+++ b/mailnews/db/gloda/modules/fundattr.js
@@ -0,0 +1,907 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['GlodaFundAttr'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+Cu.import("resource:///modules/StringBundle.js");
+
+Cu.import("resource:///modules/gloda/utils.js");
+Cu.import("resource:///modules/gloda/gloda.js");
+Cu.import("resource:///modules/gloda/datastore.js");
+Cu.import("resource:///modules/gloda/datamodel.js"); // for GlodaAttachment
+
+Cu.import("resource:///modules/gloda/noun_mimetype.js");
+Cu.import("resource:///modules/gloda/connotent.js");
+
+/**
+ * @namespace The Gloda Fundamental Attribute provider is a special attribute
+ * provider; it provides attributes that the rest of the providers should be
+ * able to assume exist. Also, it may end up accessing things at a lower level
+ * than most extension providers should do. In summary, don't mimic this code
+ * unless you won't complain when your code breaks.
+ */
+var GlodaFundAttr = {
+ providerName: "gloda.fundattr",
+ strings: new StringBundle("chrome://messenger/locale/gloda.properties"),
+ _log: null,
+
+ init: function gloda_explattr_init() {
+ this._log = Log4Moz.repository.getLogger("gloda.fundattr");
+
+ try {
+ this.defineAttributes();
+ }
+ catch (ex) {
+ this._log.error("Error in init: " + ex);
+ throw ex;
+ }
+ },
+
+ POPULARITY_FROM_ME_TO: 10,
+ POPULARITY_FROM_ME_CC: 4,
+ POPULARITY_FROM_ME_BCC: 3,
+ POPULARITY_TO_ME: 5,
+ POPULARITY_CC_ME: 1,
+ POPULARITY_BCC_ME: 1,
+
+ /** Boost for messages 'I' sent */
+ NOTABILITY_FROM_ME: 10,
+ /** Boost for messages involving 'me'. */
+ NOTABILITY_INVOLVING_ME: 1,
+ /** Boost for message from someone in 'my' address book. */
+ NOTABILITY_FROM_IN_ADDR_BOOK: 10,
+ /** Boost for the first person involved in my address book. */
+ NOTABILITY_INVOLVING_ADDR_BOOK_FIRST: 8,
+ /** Boost for each additional person involved in my address book. */
+ NOTABILITY_INVOLVING_ADDR_BOOK_ADDL: 2,
+
+ defineAttributes: function gloda_fundattr_defineAttributes() {
+ /* ***** Conversations ***** */
+ // conversation: subjectMatches
+ this._attrConvSubject = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "subjectMatches",
+ singular: true,
+ special: Gloda.kSpecialFulltext,
+ specialColumnName: "subject",
+ subjectNouns: [Gloda.NOUN_CONVERSATION],
+ objectNoun: Gloda.NOUN_FULLTEXT,
+ });
+
+ /* ***** Messages ***** */
+ // folder
+ this._attrFolder = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "folder",
+ singular: true,
+ facet: true,
+ special: Gloda.kSpecialColumn,
+ specialColumnName: "folderID",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_FOLDER,
+ }); // tested-by: test_attributes_fundamental
+ this._attrAccount = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "account",
+ canQuery: "memory",
+ singular: true,
+ facet: true,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_ACCOUNT
+ });
+ this._attrMessageKey = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "messageKey",
+ singular: true,
+ special: Gloda.kSpecialColumn,
+ specialColumnName: "messageKey",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_NUMBER,
+ canQuery: true,
+ }); // tested-by: test_attributes_fundamental
+
+ // We need to surface the deleted attribute for querying, but there is no
+ // reason for user code, so let's call it "_deleted" rather than deleted.
+ // (In fact, our validity constraints require a special query formulation
+ // that user code should have no clue exists. That's right user code,
+ // that's a dare.)
+ Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "_deleted",
+ singular: true,
+ special: Gloda.kSpecialColumn,
+ specialColumnName: "deleted",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_NUMBER,
+ });
+
+
+ // -- fulltext search helpers
+ // fulltextMatches. Match over message subject, body, and attachments
+ // @testpoint gloda.noun.message.attr.fulltextMatches
+ this._attrFulltext = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "fulltextMatches",
+ singular: true,
+ special: Gloda.kSpecialFulltext,
+ specialColumnName: "messagesText",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_FULLTEXT,
+ });
+
+ // subjectMatches. Fulltext match on subject
+ // @testpoint gloda.noun.message.attr.subjectMatches
+ this._attrSubjectText = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "subjectMatches",
+ singular: true,
+ special: Gloda.kSpecialFulltext,
+ specialColumnName: "subject",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_FULLTEXT,
+ });
+
+ // bodyMatches. super-synthetic full-text matching...
+ // @testpoint gloda.noun.message.attr.bodyMatches
+ this._attrBody = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "bodyMatches",
+ singular: true,
+ special: Gloda.kSpecialFulltext,
+ specialColumnName: "body",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_FULLTEXT,
+ });
+
+ // attachmentNamesMatch
+ // @testpoint gloda.noun.message.attr.attachmentNamesMatch
+ this._attrAttachmentNames = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "attachmentNamesMatch",
+ singular: true,
+ special: Gloda.kSpecialFulltext,
+ specialColumnName: "attachmentNames",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_FULLTEXT,
+ });
+
+ // @testpoint gloda.noun.message.attr.authorMatches
+ this._attrAuthorFulltext = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "authorMatches",
+ singular: true,
+ special: Gloda.kSpecialFulltext,
+ specialColumnName: "author",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_FULLTEXT,
+ });
+
+ // @testpoint gloda.noun.message.attr.recipientsMatch
+ this._attrRecipientsFulltext = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "recipientsMatch",
+ singular: true,
+ special: Gloda.kSpecialFulltext,
+ specialColumnName: "recipients",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_FULLTEXT,
+ });
+
+ // --- synthetic stuff for some reason
+ // conversation
+ // @testpoint gloda.noun.message.attr.conversation
+ this._attrConversation = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "conversation",
+ singular: true,
+ special: Gloda.kSpecialColumnParent,
+ specialColumnName: "conversationID",
+ idStorageAttributeName: "_conversationID",
+ valueStorageAttributeName: "_conversation",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_CONVERSATION,
+ canQuery: true,
+ });
+
+ // --- Fundamental
+ // From
+ this._attrFrom = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "from",
+ singular: true,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_IDENTITY,
+ }); // tested-by: test_attributes_fundamental
+ // To
+ this._attrTo = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "to",
+ singular: false,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_IDENTITY,
+ }); // tested-by: test_attributes_fundamental
+ // Cc
+ this._attrCc = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "cc",
+ singular: false,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_IDENTITY,
+ }); // not-tested
+ /**
+ * Bcc'ed recipients; only makes sense for sent messages.
+ */
+ this._attrBcc = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "bcc",
+ singular: false,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_IDENTITY,
+ }); // not-tested
+
+ // Date. now lives on the row.
+ this._attrDate = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "date",
+ singular: true,
+ facet: {
+ type: "date",
+ },
+ special: Gloda.kSpecialColumn,
+ specialColumnName: "date",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_DATE,
+ }); // tested-by: test_attributes_fundamental
+
+ // Header message ID.
+ this._attrHeaderMessageID = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "headerMessageID",
+ singular: true,
+ special: Gloda.kSpecialString,
+ specialColumnName: "headerMessageID",
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_STRING,
+ canQuery: true,
+ }); // tested-by: test_attributes_fundamental
+
+ // Attachment MIME Types
+ this._attrAttachmentTypes = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "attachmentTypes",
+ singular: false,
+ emptySetIsSignificant: true,
+ facet: {
+ type: "default",
+ // This will group the MIME types by their category.
+ groupIdAttr: "category",
+ queryHelper: "Category",
+ },
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_MIME_TYPE,
+ });
+
+ // Attachment infos
+ this._attrIsEncrypted = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "isEncrypted",
+ singular: true,
+ emptySetIsSignificant: false,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_NUMBER,
+ });
+
+ // Attachment infos
+ this._attrAttachmentInfos = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "attachmentInfos",
+ singular: false,
+ emptySetIsSignificant: false,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_ATTACHMENT,
+ });
+
+ // --- Optimization
+ /**
+ * Involves means any of from/to/cc/bcc. The queries get ugly enough
+ * without this that it seems to justify the cost, especially given the
+ * frequent use case. (In fact, post-filtering for the specific from/to/cc
+ * is probably justifiable rather than losing this attribute...)
+ */
+ this._attrInvolves = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrOptimization,
+ attributeName: "involves",
+ singular: false,
+ facet: {
+ type: "default",
+ /**
+ * Filter out 'me', as we have other facets that deal with that, and the
+ * 'me' identities are so likely that they distort things.
+ *
+ * @return true if the identity is not one of my identities, false if it
+ * is.
+ */
+ filter: function gloda_explattr_involves_filter(aItem) {
+ return (!(aItem.id in Gloda.myIdentities));
+ }
+ },
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_IDENTITY,
+ }); // not-tested
+
+ /**
+ * Any of to/cc/bcc.
+ */
+ this._attrRecipients = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrOptimization,
+ attributeName: "recipients",
+ singular: false,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_IDENTITY,
+ }); // not-tested
+
+ // From Me (To/Cc/Bcc)
+ this._attrFromMe = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrOptimization,
+ attributeName: "fromMe",
+ singular: false,
+ // The interesting thing to a facet is whether the message is from me.
+ facet: {
+ type: "nonempty?"
+ },
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_PARAM_IDENTITY,
+ }); // not-tested
+ // To/Cc/Bcc Me
+ this._attrToMe = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "toMe",
+ // The interesting thing to a facet is whether the message is to me.
+ facet: {
+ type: "nonempty?"
+ },
+ singular: false,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_PARAM_IDENTITY,
+ }); // not-tested
+
+
+ // -- Mailing List
+ // Non-singular, but a hard call. Namely, it is obvious that a message can
+ // be addressed to multiple mailing lists. However, I don't see how you
+ // could receive a message with more than one set of List-* headers,
+ // since each list-serve would each send you a copy. Based on our current
+ // decision to treat each physical message as separate, it almost seems
+ // right to limit the list attribute to the copy that originated at the
+ // list. That may sound entirely wrong, but keep in mind that until we
+ // have seen a message from the list with the List headers, we can't
+ // definitely know it's a mailing list (although heuristics could take us
+ // pretty far). As such, the quasi-singular thing is appealing.
+ // Of course, the reality is that we really want to know if a message was
+ // sent to multiple mailing lists and be able to query on that.
+ // Additionally, our implicit-to logic needs to work on messages that
+ // weren't relayed by the list-serve, especially messages sent to the list
+ // by the user.
+ this._attrList = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "mailing-list",
+ bindName: "mailingLists",
+ singular: false,
+ emptySetIsSignificant: true,
+ facet: true,
+ subjectNouns: [Gloda.NOUN_MESSAGE],
+ objectNoun: Gloda.NOUN_IDENTITY,
+ }); // not-tested, not-implemented
+ },
+
+ RE_LIST_POST: /<mailto:([^>]+)>/,
+
+ /**
+ *
+ * Specializations:
+ * - Mailing Lists. Replies to a message on a mailing list frequently only
+ * have the list-serve as the 'to', so we try to generate a synthetic 'to'
+ * based on the author of the parent message when possible. (The 'possible'
+ * part is that we may not have a copy of the parent message at the time of
+ * processing.)
+ * - Newsgroups. Same deal as mailing lists.
+ */
+ process: function* gloda_fundattr_process(aGlodaMessage, aRawReps,
+ aIsNew, aCallbackHandle) {
+ let aMsgHdr = aRawReps.header;
+ let aMimeMsg = aRawReps.mime;
+
+ // -- From
+ // Let's use replyTo if available.
+ // er, since we are just dealing with mailing lists for now, forget the
+ // reply-to...
+ // TODO: deal with default charset issues
+ let author = null;
+ /*
+ try {
+ author = aMsgHdr.getStringProperty("replyTo");
+ }
+ catch (ex) {
+ }
+ */
+ if (author == null || author == "")
+ author = aMsgHdr.author;
+
+ let normalizedListPost = "";
+ if (aMimeMsg && aMimeMsg.has("list-post")) {
+ let match = this.RE_LIST_POST.exec(aMimeMsg.get("list-post"));
+ if (match)
+ normalizedListPost = "<" + match[1] + ">";
+ }
+
+ // Do not use the MIME decoded variants of any of the email addresses
+ // because if name is encoded and has a comma in it, it will break the
+ // address parser (which already knows how to do the decoding anyways).
+ let [authorIdentities, toIdentities, ccIdentities, bccIdentities,
+ listIdentities] =
+ yield aCallbackHandle.pushAndGo(
+ Gloda.getOrCreateMailIdentities(aCallbackHandle,
+ author, aMsgHdr.recipients,
+ aMsgHdr.ccList, aMsgHdr.bccList,
+ normalizedListPost));
+
+ if (authorIdentities.length != 1) {
+ throw new Gloda.BadItemContentsError(
+ "Message with subject '" + aMsgHdr.mime2DecodedSubject +
+ "' somehow lacks a valid author. Bailing.");
+ }
+ let authorIdentity = authorIdentities[0];
+ aGlodaMessage.from = authorIdentity;
+
+ // -- To, Cc, Bcc
+ aGlodaMessage.to = toIdentities;
+ aGlodaMessage.cc = ccIdentities;
+ aGlodaMessage.bcc = bccIdentities;
+
+ // -- Mailing List
+ if (listIdentities.length)
+ aGlodaMessage.mailingLists = listIdentities;
+
+ let findIsEncrypted = x =>
+ x.isEncrypted || (x.parts ? x.parts.some(findIsEncrypted) : false);
+
+ // -- Encryption
+ aGlodaMessage.isEncrypted = false;
+ if (aMimeMsg) {
+ aGlodaMessage.isEncrypted = findIsEncrypted(aMimeMsg);
+ }
+
+ // -- Attachments
+ if (aMimeMsg) {
+ // nsParseMailbox.cpp puts the attachment flag on msgHdrs as soon as it
+ // finds a multipart/mixed part. This is a good heuristic, but if it turns
+ // out the part has no filename, then we don't treat it as an attachment.
+ // We just streamed the message, and we have all the information to figure
+ // that out, so now is a good place to clear the flag if needed.
+ let foundRealAttachment = false;
+ let attachmentTypes = [];
+ for (let attachment of aMimeMsg.allAttachments) {
+ // We don't care about would-be attachments that are not user-intended
+ // attachments but rather artifacts of the message content.
+ // We also want to avoid dealing with obviously bogus mime types.
+ // (If you don't have a "/", you are probably bogus.)
+ if (attachment.isRealAttachment &&
+ attachment.contentType.includes("/")) {
+ attachmentTypes.push(MimeTypeNoun.getMimeType(attachment.contentType));
+ }
+ if (attachment.isRealAttachment)
+ foundRealAttachment = true;
+ }
+ if (attachmentTypes.length) {
+ aGlodaMessage.attachmentTypes = attachmentTypes;
+ }
+
+ let aMsgHdr = aRawReps.header;
+ let wasStreamed = aMsgHdr &&
+ !aGlodaMessage.isEncrypted &&
+ ((aMsgHdr.flags & Ci.nsMsgMessageFlags.Offline) ||
+ (aMsgHdr.folder instanceof Ci.nsIMsgLocalMailFolder));
+
+ // Clear the flag if it turns out there's no attachment after all and we
+ // streamed completely the message (if we didn't, then we have no
+ // knowledge of attachments, unless bug 673370 is fixed).
+ if (!foundRealAttachment && wasStreamed)
+ aMsgHdr.markHasAttachments(false);
+
+ // This is not the same kind of attachments as above. Now, we want to
+ // provide convenience attributes to Gloda consumers, so that they can run
+ // through the list of attachments of a given message, to possibly build a
+ // visualization on top of it. We still reject bogus mime types, which
+ // means yencode won't be supported. Oh, I feel really bad.
+ let attachmentInfos = [];
+ for (let att of aMimeMsg.allUserAttachments) {
+ attachmentInfos.push(this.glodaAttFromMimeAtt(aRawReps.trueGlodaRep,
+ att));
+ }
+ aGlodaMessage.attachmentInfos = attachmentInfos;
+ }
+
+ // TODO: deal with mailing lists, including implicit-to. this will require
+ // convincing the indexer to pass us in the previous message if it is
+ // available. (which we'll simply pass to everyone... it can help body
+ // logic for quoting purposes, etc. too.)
+
+ yield Gloda.kWorkDone;
+ },
+
+ glodaAttFromMimeAtt:
+ function gloda_fundattr_glodaAttFromMimeAtt(aGlodaMessage, aAtt) {
+ // So we don't want to store the URL because it can change over time if
+ // the message is moved. What we do is store the full URL if it's a
+ // detached attachment, otherwise just keep the part information, and
+ // rebuild the URL according to where the message is sitting.
+ let part, externalUrl;
+ if (aAtt.isExternal) {
+ externalUrl = aAtt.url;
+ } else {
+ let matches = aAtt.url.match(GlodaUtils.PART_RE);
+ if (matches && matches.length)
+ part = matches[1];
+ else
+ this._log.error("Error processing attachment: " + aAtt.url);
+ }
+ return new GlodaAttachment(aGlodaMessage,
+ aAtt.name,
+ aAtt.contentType,
+ aAtt.size,
+ part,
+ externalUrl,
+ aAtt.isExternal);
+ },
+
+ optimize: function* gloda_fundattr_optimize(aGlodaMessage, aRawReps,
+ aIsNew, aCallbackHandle) {
+
+ let aMsgHdr = aRawReps.header;
+
+ // for simplicity this is used for both involves and recipients
+ let involvesIdentities = {};
+ let involves = aGlodaMessage.involves || [];
+ let recipients = aGlodaMessage.recipients || [];
+
+ // 'me' specialization optimizations
+ let toMe = aGlodaMessage.toMe || [];
+ let fromMe = aGlodaMessage.fromMe || [];
+
+ let myIdentities = Gloda.myIdentities; // needless optimization?
+ let authorIdentity = aGlodaMessage.from;
+ let isFromMe = authorIdentity.id in myIdentities;
+
+ // The fulltext search column for the author. We want to have in here:
+ // - The e-mail address and display name as enclosed on the message.
+ // - The name per the address book card for this e-mail address, if we have
+ // one.
+ aGlodaMessage._indexAuthor = aMsgHdr.mime2DecodedAuthor;
+ // The fulltext search column for the recipients. (same deal)
+ aGlodaMessage._indexRecipients = aMsgHdr.mime2DecodedRecipients;
+
+ if (isFromMe)
+ aGlodaMessage.notability += this.NOTABILITY_FROM_ME;
+ else {
+ let authorCard = authorIdentity.abCard;
+ if (authorCard) {
+ aGlodaMessage.notability += this.NOTABILITY_FROM_IN_ADDR_BOOK;
+ // @testpoint gloda.noun.message.attr.authorMatches
+ aGlodaMessage._indexAuthor += ' ' + authorCard.displayName;
+ }
+ }
+
+ involves.push(authorIdentity);
+ involvesIdentities[authorIdentity.id] = true;
+
+ let involvedAddrBookCount = 0;
+
+ for (let toIdentity of aGlodaMessage.to) {
+ if (!(toIdentity.id in involvesIdentities)) {
+ involves.push(toIdentity);
+ recipients.push(toIdentity);
+ involvesIdentities[toIdentity.id] = true;
+ let toCard = toIdentity.abCard;
+ if (toCard) {
+ involvedAddrBookCount++;
+ // @testpoint gloda.noun.message.attr.recipientsMatch
+ aGlodaMessage._indexRecipients += ' ' + toCard.displayName;
+ }
+ }
+
+ // optimization attribute to-me ('I' am the parameter)
+ if (toIdentity.id in myIdentities) {
+ toMe.push([toIdentity, authorIdentity]);
+ if (aIsNew)
+ authorIdentity.contact.popularity += this.POPULARITY_TO_ME;
+ }
+ // optimization attribute from-me-to ('I' am the parameter)
+ if (isFromMe) {
+ fromMe.push([authorIdentity, toIdentity]);
+ // also, popularity
+ if (aIsNew)
+ toIdentity.contact.popularity += this.POPULARITY_FROM_ME_TO;
+ }
+ }
+ for (let ccIdentity of aGlodaMessage.cc) {
+ if (!(ccIdentity.id in involvesIdentities)) {
+ involves.push(ccIdentity);
+ recipients.push(ccIdentity);
+ involvesIdentities[ccIdentity.id] = true;
+ let ccCard = ccIdentity.abCard;
+ if (ccCard) {
+ involvedAddrBookCount++;
+ // @testpoint gloda.noun.message.attr.recipientsMatch
+ aGlodaMessage._indexRecipients += ' ' + ccCard.displayName;
+ }
+ }
+ // optimization attribute cc-me ('I' am the parameter)
+ if (ccIdentity.id in myIdentities) {
+ toMe.push([ccIdentity, authorIdentity]);
+ if (aIsNew)
+ authorIdentity.contact.popularity += this.POPULARITY_CC_ME;
+ }
+ // optimization attribute from-me-to ('I' am the parameter)
+ if (isFromMe) {
+ fromMe.push([authorIdentity, ccIdentity]);
+ // also, popularity
+ if (aIsNew)
+ ccIdentity.contact.popularity += this.POPULARITY_FROM_ME_CC;
+ }
+ }
+ // just treat bcc like cc; the intent is the same although the exact
+ // semantics differ.
+ for (let bccIdentity of aGlodaMessage.bcc) {
+ if (!(bccIdentity.id in involvesIdentities)) {
+ involves.push(bccIdentity);
+ recipients.push(bccIdentity);
+ involvesIdentities[bccIdentity.id] = true;
+ let bccCard = bccIdentity.abCard;
+ if (bccCard) {
+ involvedAddrBookCount++;
+ // @testpoint gloda.noun.message.attr.recipientsMatch
+ aGlodaMessage._indexRecipients += ' ' + bccCard.displayName;
+ }
+ }
+ // optimization attribute cc-me ('I' am the parameter)
+ if (bccIdentity.id in myIdentities) {
+ toMe.push([bccIdentity, authorIdentity]);
+ if (aIsNew)
+ authorIdentity.contact.popularity += this.POPULARITY_BCC_ME;
+ }
+ // optimization attribute from-me-to ('I' am the parameter)
+ if (isFromMe) {
+ fromMe.push([authorIdentity, bccIdentity]);
+ // also, popularity
+ if (aIsNew)
+ bccIdentity.contact.popularity += this.POPULARITY_FROM_ME_BCC;
+ }
+ }
+
+ if (involvedAddrBookCount)
+ aGlodaMessage.notability += this.NOTABILITY_INVOLVING_ADDR_BOOK_FIRST +
+ (involvedAddrBookCount - 1) * this.NOTABILITY_INVOLVING_ADDR_BOOK_ADDL;
+
+ aGlodaMessage.involves = involves;
+ aGlodaMessage.recipients = recipients;
+ if (toMe.length) {
+ aGlodaMessage.toMe = toMe;
+ aGlodaMessage.notability += this.NOTABILITY_INVOLVING_ME;
+ }
+ if (fromMe.length)
+ aGlodaMessage.fromMe = fromMe;
+
+ // Content
+ if (aRawReps.bodyLines) {
+ aGlodaMessage._content = aRawReps.content = new GlodaContent();
+ if (this.contentWhittle({}, aRawReps.bodyLines, aGlodaMessage._content)) {
+ // we were going to do something here?
+ }
+ }
+ else {
+ aRawReps.content = null;
+ }
+
+ yield Gloda.kWorkDone;
+ },
+
+ /**
+ * Duplicates the notability logic from optimize(). Arguably optimize should
+ * be factored to call us, grokNounItem should be factored to call us, or we
+ * should get sufficiently fancy that our code wildly diverges.
+ */
+ score: function gloda_fundattr_score(aMessage, aContext) {
+ let score = 0;
+
+ let authorIdentity = aMessage.from;
+ if (authorIdentity.id in Gloda.myIdentities)
+ score += this.NOTABILITY_FROM_ME;
+ else if (authorIdentity.inAddressBook)
+ score += this.NOTABILITY_FROM_IN_ADDR_BOOK;
+ if (aMessage.toMe)
+ score += this.NOTABILITY_INVOLVING_ME;
+
+ let involvedAddrBookCount = 0;
+ for (let [, identity] in Iterator(aMessage.to))
+ if (identity.inAddressBook)
+ involvedAddrBookCount++;
+ for (let [, identity] in Iterator(aMessage.cc))
+ if (identity.inAddressBook)
+ involvedAddrBookCount++;
+ if (involvedAddrBookCount)
+ score += this.NOTABILITY_INVOLVING_ADDR_BOOK_FIRST +
+ (involvedAddrBookCount - 1) * this.NOTABILITY_INVOLVING_ADDR_BOOK_ADDL;
+ return score;
+ },
+
+ _countQuoteDepthAndNormalize:
+ function gloda_fundattr__countQuoteDepthAndNormalize(aLine) {
+ let count = 0;
+ let lastStartOffset = 0;
+
+ for (let i = 0; i < aLine.length; i++) {
+ let c = aLine[i];
+ if (c == ">") {
+ count++;
+ lastStartOffset = i+1;
+ }
+ else if (c == " ") {
+ }
+ else {
+ return [count,
+ lastStartOffset ? aLine.substring(lastStartOffset) : aLine];
+ }
+ }
+
+ return [count, lastStartOffset ? aLine.substring(lastStartOffset) : aLine];
+ },
+
+ /**
+ * Attempt to understand simple quoting constructs that use ">" with
+ * obvious phrases to enter the quoting block. No support for other types
+ * of quoting at this time. Also no support for piercing the wrapper of
+ * forwarded messages to actually be the content of the forwarded message.
+ */
+ contentWhittle: function gloda_fundattr_contentWhittle(aMeta,
+ aBodyLines, aContent) {
+ if (!aContent.volunteerContent(aContent.kPriorityBase))
+ return false;
+
+ // duplicate the list; we mutate somewhat...
+ let bodyLines = aBodyLines.concat();
+
+ // lastNonBlankLine originally was just for detecting quoting idioms where
+ // the "wrote" line was separated from the quoted block by a blank line.
+ // Now we also use it for whitespace suppression at the boundaries of
+ // quoted and un-quoted text. (We keep blank lines within the same
+ // 'block' of quoted or non-quoted text.)
+ // Because we now have two goals for it, and we still want to suppress blank
+ // lines when there is a 'wrote' line involved, we introduce...
+ // prevLastNonBlankLine! This arguably suggests refactoring should be the
+ // next step, but things work for now.
+ let rangeStart = 0, lastNonBlankLine = null, prevLastNonBlankLine = null;
+ let inQuoteDepth = 0;
+ for (let [iLine, line] of bodyLines.entries()) {
+ if (!line || (line == "\xa0")) /* unicode non breaking space */
+ continue;
+
+ if (line.startsWith(">")) {
+ if (!inQuoteDepth) {
+ let rangeEnd = iLine - 1;
+ let quoteRangeStart = iLine;
+ // see if the last non-blank-line was a lead-in...
+ if (lastNonBlankLine != null) {
+ // TODO: localize quote range start detection
+ if (aBodyLines[lastNonBlankLine].includes("wrote")) {
+ quoteRangeStart = lastNonBlankLine;
+ rangeEnd = lastNonBlankLine - 1;
+ // we 'used up' lastNonBlankLine, let's promote the prev guy to
+ // be the new lastNonBlankLine for the next logic block
+ lastNonBlankLine = prevLastNonBlankLine;
+ }
+ // eat the trailing whitespace...
+ if (lastNonBlankLine != null)
+ rangeEnd = Math.min(rangeEnd, lastNonBlankLine);
+ }
+ if (rangeEnd >= rangeStart)
+ aContent.content(aBodyLines.slice(rangeStart, rangeEnd+1));
+
+ [inQuoteDepth, line] = this._countQuoteDepthAndNormalize(line);
+ bodyLines[iLine] = line;
+ rangeStart = quoteRangeStart;
+ }
+ else {
+ let curQuoteDepth;
+ [curQuoteDepth, line] = this._countQuoteDepthAndNormalize(line);
+ bodyLines[iLine] = line;
+
+ if (curQuoteDepth != inQuoteDepth) {
+ // we could do some "wrote" compensation here, but it's not really
+ // as important. let's wait for a more clever algorithm.
+ aContent.quoted(aBodyLines.slice(rangeStart, iLine), inQuoteDepth);
+ inQuoteDepth = curQuoteDepth;
+ rangeStart = iLine;
+ }
+ }
+ }
+ else {
+ if (inQuoteDepth) {
+ aContent.quoted(aBodyLines.slice(rangeStart, iLine), inQuoteDepth);
+ inQuoteDepth = 0;
+ rangeStart = iLine;
+ }
+ }
+
+ prevLastNonBlankLine = lastNonBlankLine;
+ lastNonBlankLine = iLine;
+ }
+
+ if (inQuoteDepth) {
+ aContent.quoted(aBodyLines.slice(rangeStart), inQuoteDepth);
+ }
+ else {
+ aContent.content(aBodyLines.slice(rangeStart, lastNonBlankLine+1));
+ }
+
+ return true;
+ },
+};
diff --git a/mailnews/db/gloda/modules/gloda.js b/mailnews/db/gloda/modules/gloda.js
new file mode 100644
index 000000000..3a32e6041
--- /dev/null
+++ b/mailnews/db/gloda/modules/gloda.js
@@ -0,0 +1,2283 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['Gloda'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+Cu.import("resource:///modules/gloda/datastore.js");
+Cu.import("resource:///modules/gloda/datamodel.js");
+Cu.import("resource:///modules/gloda/databind.js");
+Cu.import("resource:///modules/gloda/collection.js");
+Cu.import("resource:///modules/gloda/connotent.js");
+Cu.import("resource:///modules/gloda/query.js");
+Cu.import("resource:///modules/gloda/utils.js");
+
+Cu.import("resource:///modules/iteratorUtils.jsm");
+Cu.import("resource:///modules/IOUtils.js");
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ * @see |Gloda.BadItemContentsError|
+ */
+function BadItemContentsError(aMessage) {
+ this.message = aMessage;
+}
+BadItemContentsError.prototype = {
+ toString: function BadItemContentsError_toString() {
+ return this.message;
+ }
+};
+
+
+/**
+ * Provides the user-visible (and extension visible) global database
+ * functionality. There is currently a dependency/ordering
+ * problem in that the concept of 'gloda' also includes some logic that is
+ * contributed by built-in extensions, if you will. Those built-in extensions
+ * (fundattr.js, explattr.js) also import this file. To avoid a circular
+ * dependency, those built-in extensions are loaded by everybody.js. The
+ * simplest/best solution is probably to move everybody.js to be gloda.js and
+ * have it re-export only 'Gloda'. gloda.js (this file) can then move to be
+ * gloda_int.js (or whatever our eventual naming scheme is), which built-in
+ * extensions can explicitly rely upon.
+ *
+ * === Concepts
+ *
+ * == Nouns
+ *
+ * Inspired by reasonable uses of triple-stores, I have tried to leverage
+ * existing model and terminology rather than rolling out own for everything.
+ * The idea with triple-stores is that you have a subject, a predicate, and an
+ * object. For example, if we are talking about a message, that is the
+ * subject, the predicate could roughly be sent-by, and the object a person.
+ * We can generalize this idea to say that the subject and objects are nouns.
+ * Since we want to be more flexible than only dealing with messages, we
+ * therefore introduce the concept of nouns as an organizing principle.
+ *
+ * == Attributes
+ *
+ * Our attributes definitions are basically our predicates. When we define
+ * an attribute, it's a label with a bunch of meta-data. Our attribute
+ * instances are basically a 'triple' in a triple-store. The attributes
+ * are stored in database rows that imply a specific noun-type (ex: the
+ * messageAttributes table), with an ID identifying the message which is our
+ * subject, an attribute ID which identifies the attribute definition in use
+ * (and therefore the predicate), plus an object ID (given context aka the
+ * noun type by the attribute's meta-data) which identifies the 'object'.
+ *
+ * == But...
+ *
+ * Things aren't entirely as clear as they could be right now, terminology/
+ * concept/implementation-wise. Some work is probably still in order.
+ *
+ * === Implementation
+ *
+ * == Nouns
+ *
+ * So, we go and define the nouns that are roughly the classes in our data
+ * model. Every 'class' we define in datamodel.js is a noun that gets defined
+ * here in the Gloda core. We provide sufficient meta-data about the noun to
+ * serialize/deserialize its representation from our database representation.
+ * Nouns do not have to be defined in this class, but can also be contributed
+ * by external code.
+ * We have a concept of 'first class' nouns versus non-first class nouns. The
+ * distinction is meant to be whether we can store meta-information about those
+ * nouns using attributes. Right now, only message are real first-class nouns,
+ * but we want to expand that to include contacts and eventually events and
+ * tasks as lightning-integration occurs. In practice, we are stretching the
+ * definition of first-class nouns slightly to include things we can't store
+ * meta-data about, but want to be able to query about. We do want to resolve
+ * this.
+ *
+ * == Attributes
+ *
+ * Attributes are defined by "attribute providers" who are responsible for
+ * taking an instance of a first-class noun (for which they are registered)
+ * plus perhaps some other meta-data, and returning a list of attributes
+ * extracted from that noun. For now, this means messages. Attribute
+ * providers may create new data records as a side-effect of the indexing
+ * process, although we have not yet fully dealt with the problem of deleting
+ * these records should they become orphaned in the database due to the
+ * purging of a message and its attributes.
+ * All of the 'core' gloda attributes are provided by the fundattr.js and
+ * explattr.js providers.
+ *
+ * === (Notable) Future Work
+ *
+ * == Attributes
+ *
+ * Attribute mechanisms currently lack any support for 'overriding' attributes
+ * provided by other attribute providers. For example, the fundattr provider
+ * tells us who a message is 'from' based on the e-mail address present.
+ * However, other plugins may actually know better. For example, the bugzilla
+ * daemon e-mails based on bug activity although the daemon gets the credit
+ * as the official sender. A bugzilla plugin can easily extract the actual
+ * person/e-mail addressed who did something on the bug to cause the
+ * notification to be sent. In practice, we would like that person to be
+ * the 'sender' of the bugmail. But we can't really do that right, yet.
+ *
+ * @namespace
+ */
+var Gloda = {
+ /**
+ * Initialize logging, the datastore (SQLite database), the core nouns and
+ * attributes, and the contact and identities that belong to the presumed
+ * current user (based on accounts).
+ *
+ * Additional nouns and the core attribute providers are initialized by the
+ * everybody.js module which ensures all of those dependencies are loaded
+ * (and initialized).
+ */
+ _init: function gloda_ns_init() {
+ this._initLogging();
+ GlodaDatastore._init(this._nounIDToDef);
+ this._initAttributes();
+ this._initMyIdentities();
+ },
+
+ _log: null,
+ /**
+ * Initialize logging; the error console window gets Warning/Error, and stdout
+ * (via dump) gets everything.
+ */
+ _initLogging: function gloda_ns_initLogging() {
+ let formatter = new Log4Moz.BasicFormatter();
+ Log4Moz.repository.rootLogger.level = Log4Moz.Level.Debug;
+
+ let enableConsoleLogging = false;
+ let enableDumpLogging = false;
+ // should we assume there is someone else consuming our log4moz stream?
+ let enableUpstreamLogging = false;
+ let considerNetLogging = false;
+
+ let glodaLog = Log4Moz.repository.getLogger("gloda");
+ glodaLog.level = Log4Moz.Level.Warn;
+
+ try {
+ // figure out if event-driven indexing should be enabled...
+ let branch = Services.prefs.getBranch("mailnews.database.global.logging.");
+ enableConsoleLogging = branch.getBoolPref("console");
+ enableDumpLogging = branch.getBoolPref("dump");
+ enableUpstreamLogging = branch.getBoolPref("upstream");
+ considerNetLogging = branch.getBoolPref("net");
+ } catch (ex) {}
+
+ if (enableConsoleLogging) {
+ let capp = new Log4Moz.ConsoleAppender(formatter);
+ capp.level = Log4Moz.Level.Warn;
+ glodaLog.addAppender(capp);
+ }
+
+ if (enableDumpLogging) {
+ let dapp = new Log4Moz.DumpAppender(formatter);
+ dapp.level = Log4Moz.Level.All;
+ glodaLog.level = Log4Moz.Level.All;
+ glodaLog.addAppender(dapp);
+ }
+
+ if (enableUpstreamLogging) {
+ glodaLog.level = Log4Moz.Level.All;
+ }
+
+ if (considerNetLogging) {
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append("chainsaw.ptr");
+ if (file.exists()) {
+ let data = IOUtils.loadFileToString(file);
+ data = data.trim();
+ let [host, port] = data.split(":");
+ let xf = new Log4Moz.XMLFormatter();
+ let sapp = new Log4Moz.SocketAppender(host, Number(port), xf);
+ sapp.level = Log4Moz.Level.All;
+ glodaLog.level = Log4Moz.Level.All;
+ glodaLog.addAppender(sapp);
+ }
+ }
+
+ this._log = Log4Moz.repository.getLogger("gloda.NS");
+ this._log.info("Logging Initialized");
+ },
+
+ /**
+ * The indexer is idle.
+ */
+ kIndexerIdle: 0,
+ /**
+ * The indexer is doing something. We used to have other specific states, but
+ * they have been rendered irrelevant and wiped from existence.
+ */
+ kIndexerIndexing: 1,
+
+ /**
+ * Synchronous activities performed that can be thought of as one processing
+ * token. Potentially yield the event-loop and re-schedule for later based
+ * on how long we've actually taken/etc. The goal here is that code that
+ * is doing stuff synchronously yields with kWorkSync periodically to make
+ * sure that it doesn't dominate the event-loop. Unless the processing
+ * in question is particularly intensive, it should be reasonable to apply
+ * some decimation factor (ex: 32 or 64) with the general goal of yielding
+ * every 3-10 milliseconds.
+ */
+ kWorkSync: 0,
+ /**
+ * Asynchronous activity performed, you need to relinquish flow control and
+ * trust us to call callbackDriver later.
+ */
+ kWorkAsync: 1,
+ /**
+ * We are all done with our task, close us and figure out something else to do.
+ */
+ kWorkDone: 2,
+ /**
+ * We are not done with our task, but we think it's a good idea to take a
+ * breather because we believe we have tied up the event loop for a
+ * non-trivial amount of time. So please re-schedule us in the future.
+ *
+ * This is currently only used internally by the indexer's batching logic;
+ * minor changes may be required if used by actual indexers.
+ */
+ kWorkPause: 3,
+ /**
+ * We are done with our task, and have a result that we are returning. This
+ * should only be used by your callback handler's doneWithResult method.
+ * Ex: you are passed aCallbackHandle, and you do
+ * "yield aCallbackHandle.doneWithResult(myResult);".
+ */
+ kWorkDoneWithResult: 4,
+
+ /**
+ * Callers should access the unique ID for the GlodaDatastore
+ * with this getter. If the GlodaDatastore has not been
+ * initialized, this value is null.
+ *
+ * @return a UUID as a string, ex: "c4dd0159-9287-480f-a648-a4613e147fdb"
+ */
+ get datastoreID() {
+ return GlodaDatastore._datastoreID;
+ },
+
+ /**
+ * Lookup a gloda message from an nsIMsgDBHdr, with the result returned as a
+ * collection. Keep in mind that the message may not be indexed, so you
+ * may end up with an empty collection. (Also keep in mind that this query
+ * is asynchronous, so you will want your action-taking logic to be found
+ * in your listener's onQueryCompleted method; the result will not be in
+ * the collection when this method returns.)
+ *
+ * @param aMsgHdr The header of the message you want the gloda message for.
+ * @param aListener The listener that should be registered with the collection
+ * @param aData The (optional) value to set as the data attribute on the
+ * collection.
+ *
+ * @return The collection that will receive the results.
+ *
+ * @testpoint gloda.ns.getMessageCollectionForHeader()
+ */
+ getMessageCollectionForHeader: function gloda_ns_getMessageForHeader(aMsgHdr,
+ aListener, aData) {
+ let query = Gloda.newQuery(Gloda.NOUN_MESSAGE);
+ query.folder(aMsgHdr.folder).messageKey(aMsgHdr.messageKey);
+ return query.getCollection(aListener, aData);
+ },
+
+ /**
+ * Given a list of message headers, return a collection containing the gloda
+ * messages that correspond to those headers. Keep in mind that gloda may
+ * not have indexed all the messages, so the returned collection may not have
+ * a message for each header you provide. (Also keep in mind that this query
+ * is asynchronous, so you will want your action-taking logic to be found
+ * in your listener's onQueryCompleted method; no results will be present in
+ * the collection when this method returns.)
+ *
+ * @param aHeaders A javascript Array or and XPCOM list that fixIterator can
+ * can handle.
+ * @param aListener The listener that should be registered with the collection
+ * @param aData The (optional) value to set as the data attribute on the
+ * collection.
+ *
+ * @return The collection that will receive the results.
+ *
+ * @testpoint gloda.ns.getMessageCollectionForHeaders()
+ */
+ getMessageCollectionForHeaders: function gloda_ns_getMessagesForHeaders(
+ aHeaders, aListener, aData) {
+ // group the headers by the folder they are found in
+ let headersByFolder = {};
+ let iter;
+ for (let header in fixIterator(aHeaders)) {
+ let folderURI = header.folder.URI;
+ let headersForFolder = headersByFolder[folderURI];
+ if (headersForFolder === undefined)
+ headersByFolder[folderURI] = [header];
+ else
+ headersForFolder.push(header);
+ }
+
+ let query = Gloda.newQuery(Gloda.NOUN_MESSAGE);
+ let clause;
+ // build a query, using a separate union clause for each folder.
+ for (let folderURI in headersByFolder) {
+ let headersForFolder = headersByFolder[folderURI];
+ let folder = this.getFolderForFolder(headersForFolder[0].folder);
+ // if this is the first or clause, just use the query itself
+ if (!clause)
+ clause = query;
+ else // create a new query clause via the 'or' command
+ clause = query.or();
+
+ clause.folder(folder);
+ let messageKeys = headersForFolder.map(hdr => hdr.messageKey);
+ clause.messageKey.apply(clause, messageKeys);
+ }
+
+ return query.getCollection(aListener, aData);
+ },
+
+ /**
+ * @testpoint gloda.ns.getMessageContent
+ */
+ getMessageContent: function gloda_ns_getMessageContent(aGlodaMessage, aMimeMsg) {
+ return mimeMsgToContentAndMeta(aMimeMsg, aGlodaMessage.folderMessage.folder)[0];
+ },
+
+ getFolderForFolder: function gloda_ns_getFolderForFolder(aMsgFolder) {
+ return GlodaDatastore._mapFolder(aMsgFolder);
+ },
+
+ /**
+ * Takes one or more strings containing lists of comma-delimited e-mail
+ * addresses with optional display names, and returns a list of sub-lists of
+ * identities, where each sub-list corresponds to each of the strings passed
+ * as arguments. These identities are loaded from the database if they
+ * already exist, or created if they do not yet exist.
+ * If the identities need to be created, they will also result in the
+ * creation of a gloda contact. If a display name was provided with the
+ * e-mail address, it will become the name of the gloda contact. If a
+ * display name was not provided, the e-mail address will also serve as the
+ * contact name.
+ * This method uses the indexer's callback handle mechanism, and does not
+ * obey traditional return semantics.
+ *
+ * We normalize all e-mail addresses to be lowercase as a normative measure.
+ *
+ * @param aCallbackHandle The GlodaIndexer callback handle (or equivalent)
+ * that you are operating under.
+ * @param ... One or more strings. Each string can contain zero or more
+ * e-mail addresses with display name. If more than one address is given,
+ * they should be comma-delimited. For example
+ * '"Bob Smith" <bob@example.com>' is an address with display name. Mime
+ * header decoding is performed, but is ignorant of any folder-level
+ * character set overrides.
+ * @returns via the callback handle mechanism, a list containing one sub-list
+ * for each string argument passed. Each sub-list containts zero or more
+ * GlodaIdentity instances corresponding to the addresses provided.
+ */
+ getOrCreateMailIdentities:
+ function* gloda_ns_getOrCreateMailIdentities(aCallbackHandle) {
+ let addresses = {};
+ let resultLists = [];
+
+ // parse the strings
+ for (let iArg = 1; iArg < arguments.length; iArg++) {
+ let aMailAddresses = arguments[iArg];
+ let parsed = GlodaUtils.parseMailAddresses(aMailAddresses);
+
+ let resultList = [];
+ resultLists.push(resultList);
+
+ let identities = [];
+ for (let iAddress = 0; iAddress < parsed.count; iAddress++) {
+ let address = parsed.addresses[iAddress].toLowerCase();
+ if (address in addresses)
+ addresses[address].push(resultList);
+ else
+ addresses[address] = [parsed.names[iAddress], resultList];
+ }
+ }
+
+ let addressList = Object.keys(addresses);
+ if (addressList.length == 0) {
+ yield aCallbackHandle.doneWithResult(resultLists);
+ // we should be stopped before we reach this point, but safety first.
+ return;
+ }
+
+ let query = this.newQuery(this.NOUN_IDENTITY);
+ query.kind("email");
+ query.value.apply(query, addressList);
+ let collection = query.getCollection(aCallbackHandle);
+ yield this.kWorkAsync;
+
+ // put the identities in the appropriate result lists
+ for (let identity of collection.items) {
+ let nameAndResultLists = addresses[identity.value];
+ this._log.debug(" found identity for '" + nameAndResultLists[0] + "' (" +
+ identity.value + ")");
+ // index 0 is the name, skip it
+ for (let iResList = 1; iResList < nameAndResultLists.length; iResList++) {
+ nameAndResultLists[iResList].push(identity);
+ }
+ delete addresses[identity.value];
+ }
+
+ // create the identities that did not exist yet
+ for (let address in addresses) {
+ let nameAndResultLists = addresses[address];
+ let name = nameAndResultLists[0];
+
+ this._log.debug(" creating contact for '" + name + "' (" + address + ")");
+
+ // try and find an existing address book contact.
+ let card = GlodaUtils.getCardForEmail(address);
+ // XXX when we have the address book GUID stuff, we need to use that to
+ // find existing contacts... (this will introduce a new query phase
+ // where we batch all the GUIDs for an async query)
+ // XXX when the address book supports multiple e-mail addresses, we
+ // should also just create identities for any that don't yet exist
+
+ // if there is no name, just use the e-mail (the ab indexer actually
+ // processes the card's displayName for synchronization, so we don't
+ // need to do that.)
+ if (!name)
+ name = address;
+
+ let contact = GlodaDatastore.createContact(null, null, name, 0, 0);
+
+ // we must create the identity. use a blank description because there's
+ // nothing to differentiate it from other identities, as this contact
+ // only has one initially (us).
+ // XXX when we have multiple e-mails and there is a meaning associated
+ // with each e-mail, try and use that to populate the description.
+ // XXX we are creating the identity here before we insert the contact.
+ // conceptually it is good for us to be creating the identity before
+ // exposing it to the address-book indexer, but we could get our id's
+ // in a bad way from not deferring the identity insertion until after
+ // the contact insertion.
+ let identity = GlodaDatastore.createIdentity(contact.id, contact,
+ "email", address, /* description */ "", /* relay? */ false);
+ contact._identities = [identity];
+
+ // give the address book indexer a chance if we have a card.
+ // (it will fix-up the name based on the card as appropriate)
+ if (card)
+ yield aCallbackHandle.pushAndGo(
+ Gloda.grokNounItem(contact, {card: card}, true, true,
+ aCallbackHandle));
+ else // grokNounItem will issue the insert for us...
+ GlodaDatastore.insertContact(contact);
+
+ for (let iResList = 1; iResList < nameAndResultLists.length; iResList++) {
+ nameAndResultLists[iResList].push(identity);
+ }
+ }
+
+ yield aCallbackHandle.doneWithResult(resultLists);
+ },
+
+ /**
+ * Dictionary of the user's known identities; key is the identity id, value
+ * is the actual identity. This is populated by _initMyIdentities based on
+ * the accounts defined.
+ */
+ myIdentities: {},
+ /**
+ * The contact corresponding to the current user. We are assuming that only
+ * a single user/human being uses the current profile. This is known to be
+ * a flawed assumption, but is the best first approximation available.
+ *
+ * @TODO attempt to deal with multile people using the same profile
+ */
+ myContact: null,
+ /**
+ * Populate myIdentities with all of our identities. Currently we do this
+ * by assuming that there is one human/user per profile, and that all of the
+ * accounts defined in the profile belong to them. The single contact is
+ * stored on myContact.
+ *
+ * @TODO deal with account addition/modification/removal
+ * @TODO attempt to deal with multiple people using the same profile
+ */
+ _initMyIdentities: function gloda_ns_initMyIdentities() {
+ let myContact = null;
+ let myIdentities = {};
+ let myEmailAddresses = {}; // process each email at most once; stored here
+
+ let fullName, fallbackName;
+ let existingIdentities = [];
+ let identitiesToCreate = [];
+
+ let numIdentities = MailServices.accounts.allIdentities.length;
+
+ // nothing to do if there are no accounts/identities.
+ if (!numIdentities)
+ return;
+
+ for (let iIdentity = 0; iIdentity < numIdentities; iIdentity++) {
+ let msgIdentity =
+ MailServices.accounts.allIdentities.queryElementAt(iIdentity,
+ Ci.nsIMsgIdentity);
+
+ if (!fullName)
+ fullName = msgIdentity.fullName;
+ if (!fallbackName)
+ fallbackName = msgIdentity.email;
+
+ let emailAddress = msgIdentity.email;
+ let replyTo = msgIdentity.replyTo;
+
+ // find the identities if they exist, flag to create them if they don't
+ if (emailAddress) {
+ let parsed = GlodaUtils.parseMailAddresses(emailAddress);
+ if (!(parsed.addresses[0] in myEmailAddresses)) {
+ let identity = GlodaDatastore.getIdentity("email",
+ parsed.addresses[0]);
+ if (identity)
+ existingIdentities.push(identity);
+ else
+ identitiesToCreate.push(parsed.addresses[0]);
+ myEmailAddresses[parsed.addresses[0]] = true;
+ }
+ }
+ if (replyTo) {
+ let parsed = GlodaUtils.parseMailAddresses(replyTo);
+ if (!(parsed.addresses[0] in myEmailAddresses)) {
+ let identity = GlodaDatastore.getIdentity("email",
+ parsed.addresses[0]);
+ if (identity)
+ existingIdentities.push(identity);
+ else
+ identitiesToCreate.push(parsed.addresses[0]);
+ myEmailAddresses[parsed.addresses[0]] = true;
+ }
+ }
+ }
+
+ // we need to establish the identity.contact portions of the relationship
+ for (let identity of existingIdentities) {
+ identity._contact = GlodaDatastore.getContactByID(identity.contactID);
+ }
+
+ if (existingIdentities.length) {
+ // just use the first guy's contact
+ myContact = existingIdentities[0].contact;
+ }
+ else {
+ // create a new contact
+ myContact = GlodaDatastore.createContact(null, null,
+ fullName || fallbackName,
+ 0, 0);
+ GlodaDatastore.insertContact(myContact);
+ }
+
+ if (identitiesToCreate.length) {
+ for (let iIdentity = 0; iIdentity < identitiesToCreate.length;
+ iIdentity++) {
+ let emailAddress = identitiesToCreate[iIdentity];
+ // XXX this won't always be of type "email" as we add new account types
+ // XXX the blank string could be trying to differentiate; we do have
+ // enough info to do it.
+ let identity = GlodaDatastore.createIdentity(myContact.id, myContact,
+ "email",
+ emailAddress,
+ "", false);
+ existingIdentities.push(identity);
+ }
+ }
+
+ for (let iIdentity = 0; iIdentity < existingIdentities.length;
+ iIdentity++) {
+ let identity = existingIdentities[iIdentity];
+ myIdentities[identity.id] = identity;
+ }
+
+ this.myContact = myContact;
+ this.myIdentities = myIdentities;
+ myContact._identities = Object.keys(myIdentities).
+ map(id => myIdentities[id]);
+
+ // we need contacts to make these objects reachable via the collection
+ // manager.
+ this._myContactCollection = this.explicitCollection(this.NOUN_CONTACT,
+ [this.myContact]);
+ this._myIdentitiesCollection =
+ this.explicitCollection(this.NOUN_IDENTITY, this.myContact._identities);
+ },
+
+ /**
+ * An attribute that is a defining characteristic of the subject.
+ */
+ kAttrFundamental: 0,
+ /**
+ * An attribute that is an optimization derived from two or more fundamental
+ * attributes and exists solely to improve database query performance.
+ */
+ kAttrOptimization: 1,
+ /**
+ * An attribute that is derived from the content of the subject. For example,
+ * a message that references a bugzilla bug could have a "derived" attribute
+ * that captures the bugzilla reference. This is not
+ */
+ kAttrDerived: 2,
+ /**
+ * An attribute that is the result of an explicit and intentional user action
+ * upon the subject. For example, a tag placed on a message by a user (or
+ * at the user's request by a filter) is explicit.
+ */
+ kAttrExplicit: 3,
+ /**
+ * An attribute that is indirectly the result of a user's behaviour. For
+ * example, if a user consults a message multiple times, we may conclude that
+ * the user finds the message interesting. It is "implied", if you will,
+ * that the message is interesting.
+ */
+ kAttrImplicit: 4,
+
+ /**
+ * This attribute is not 'special'; it is stored as a (thing id, attribute id,
+ * attribute id) tuple in the database rather than on thing's row or on
+ * thing's fulltext row. (Where "thing" could be a message or any other
+ * first class noun.)
+ */
+ kSpecialNotAtAll: GlodaDatastore.kSpecialNotAtAll,
+ /**
+ * This attribute is stored as a numeric column on the row for the noun. The
+ * attribute definition should include this value as 'special' and the
+ * column name that stores the attribute as 'specialColumnName'.
+ */
+ kSpecialColumn: GlodaDatastore.kSpecialColumn,
+ kSpecialColumnChildren: GlodaDatastore.kSpecialColumnChildren,
+ kSpecialColumnParent: GlodaDatastore.kSpecialColumnParent,
+ /**
+ * This attribute is stored as a string column on the row for the noun. It
+ * differs from kSpecialColumn in that it is a string, which once had
+ * query ramifications and one day may have them again.
+ */
+ kSpecialString: GlodaDatastore.kSpecialString,
+ /**
+ * This attribute is stored as a fulltext column on the fulltext table for
+ * the noun. The attribute defintion should include this value as 'special'
+ * and the column name that stores the table as 'specialColumnName'.
+ */
+ kSpecialFulltext: GlodaDatastore.kSpecialFulltext,
+
+ /**
+ * The extensionName used for the attributes defined by core gloda plugins
+ * such as fundattr.js and explattr.js.
+ */
+ BUILT_IN: "built-in",
+
+ /**
+ * Special sentinel value that will cause facets to skip a noun instance
+ * when an attribute has this value.
+ */
+ IGNORE_FACET: GlodaDatastore.IGNORE_FACET,
+
+ /*
+ * The following are explicit noun IDs. While most extension-provided nouns
+ * will have dynamically allocated id's that are looked up by name, these
+ * id's can be relied upon to exist and be accessible via these
+ * pseudo-constants. It's not really clear that we need these, although it
+ * does potentially simplify code to not have to look up all of their nouns
+ * at initialization time.
+ */
+ /**
+ * Boolean values, expressed as 0/1 in the database and non-continuous for
+ * constraint purposes. Like numbers, such nouns require their attributes
+ * to provide them with context, lacking any of their own.
+ * Having this as a noun type may be a bad idea; a change of nomenclature
+ * (so that we are not claiming a boolean value is a noun, but still using
+ * it in the same way) or implementation to require each boolean noun
+ * actually be its own noun may be in order.
+ */
+ NOUN_BOOLEAN: 1,
+ /**
+ * A number, which could mean an integer or floating point values. We treat
+ * these as continuous, meaning that queries on them can have ranged
+ * constraints expressed on them. Lacking any inherent context, numbers
+ * depend on their attributes to parameterize them as required.
+ * Same deal as with NOUN_BOOLEAN, we may need to change this up conceptually.
+ */
+ NOUN_NUMBER: 2,
+ /**
+ * A (non-fulltext) string.
+ * Same deal as with NOUN_BOOLEAN, we may need to change this up conceptually.
+ */
+ NOUN_STRING: 3,
+ /** A date, encoded as a PRTime, represented as a js Date object. */
+ NOUN_DATE: 10,
+ /**
+ * Fulltext search support, somewhat magical. This is only intended to be
+ * used for kSpecialFulltext attributes, and exclusively as a constraint
+ * mechanism. The values are always represented as strings. It is presumed
+ * that the user of this functionality knows how to generate SQLite FTS3
+ * style MATCH queries, or is okay with us just gluing them together with
+ * " OR " when used in an or-constraint case. Gloda's query mechanism
+ * currently lacks the ability to to compile Gloda-style and-constraints
+ * into a single MATCH query, but it will turn out okay, just less
+ * efficiently than it could.
+ */
+ NOUN_FULLTEXT: 20,
+ /**
+ * Represents a MIME Type. We currently lack any human-intelligible
+ * descriptions of mime types.
+ */
+ NOUN_MIME_TYPE: 40,
+ /**
+ * Captures a message tag as well as when the tag's presence was observed,
+ * hoping to approximate when the tag was applied. It's a somewhat dubious
+ * attempt to not waste our opporunity to store a value along with the tag.
+ * (The tag is actually stored as an attribute parameter on the attribute
+ * definition, rather than a value in the attribute 'instance' for the
+ * message.)
+ */
+ NOUN_TAG: 50,
+ /**
+ * Doesn't actually work owing to a lack of an object to represent a folder.
+ * We do expose the folderURI and folderID of a message, but need to map that
+ * to a good abstraction. Probably something thin around a SteelFolder or
+ * the like; we would contribute the functionality to easily move from a
+ * folder to the list of gloda messages in that folder, as well as the
+ * indexing preferences for that folder.
+ * @TODO folder noun and related abstraction
+ */
+ NOUN_FOLDER: GlodaFolder.prototype.NOUN_ID, // 100
+ /**
+ * All messages belong to a conversation. See datamodel.js for the
+ * definition of the GlodaConversation class.
+ */
+ NOUN_CONVERSATION: GlodaConversation.prototype.NOUN_ID, // 101
+ /**
+ * A one-to-one correspondence with underlying (indexed) nsIMsgDBHdr
+ * instances. See datamodel.js for the definition of the GlodaMessage class.
+ */
+ NOUN_MESSAGE: GlodaMessage.prototype.NOUN_ID, // 102
+ /**
+ * Corresponds to a human being, who may have multiple electronic identities
+ * (a la NOUN_IDENTITY). There is no requirement for association with an
+ * address book contact, although when the address book contact exists,
+ * we want to be associated with it. See datamodel.js for the definition
+ * of the GlodaContact class.
+ */
+ NOUN_CONTACT: GlodaContact.prototype.NOUN_ID, // 103
+ /**
+ * A single identity of a contact, who may have one or more. E-mail accounts,
+ * instant messaging accounts, social network site accounts, etc. are each
+ * identities. See datamodel.js for the definition of the GlodaIdentity
+ * class.
+ */
+ NOUN_IDENTITY: GlodaIdentity.prototype.NOUN_ID, // 104
+ /**
+ * An attachment to a message. A message may have many different attachments.
+ */
+ NOUN_ATTACHMENT: GlodaAttachment.prototype.NOUN_ID, // 105
+ /**
+ * An account related to a message. A message can have only one account.
+ */
+ NOUN_ACCOUNT: GlodaAccount.prototype.NOUN_ID, // 106
+
+ /**
+ * Parameterized identities, for use in the from-me, to-me, cc-me optimization
+ * cases. Not for reuse without some thought. These nouns use the parameter
+ * to store the 'me' identity that we are talking about, and the value to
+ * store the identity of the other party. So in both the from-me and to-me
+ * cases involving 'me' and 'foo@bar', the 'me' identity is always stored via
+ * the attribute parameter, and the 'foo@bar' identity is always stored as
+ * the attribute value. See fundattr.js for more information on this, but
+ * you probably shouldn't be touching this unless you are fundattr.
+ */
+ NOUN_PARAM_IDENTITY: 200,
+
+ /** Next Noun ID to hand out, these don't need to be persisted (for now). */
+ _nextNounID: 1000,
+
+ /**
+ * Maps noun names to noun IDs.
+ */
+ _nounNameToNounID: {},
+ /**
+ * Maps noun IDs to noun definition dictionaries. (Noun definition
+ * dictionaries provided to us at the time a noun was defined, plus some
+ * additional stuff we put in there.)
+ */
+ _nounIDToDef: {},
+
+ _managedToJSON: function gloda_ns_managedToJSON(aItem) {
+ return aItem.id;
+ },
+
+ /**
+ * Define a noun. Takes a dictionary with the following keys/values:
+ *
+ * @param aNounDef.name The name of the noun. This is not a display name
+ * (anything being displayed needs to be localized, after all), but simply
+ * the canonical name for debugging purposes and for people to pass to
+ * lookupNoun. The suggested convention is lower-case-dash-delimited,
+ * with names being singular (since it's a single noun we are referring
+ * to.)
+ * @param aNounDef.class The 'class' to which an instance of the noun will
+ * belong (aka will pass an instanceof test). You may also provide this
+ * as 'clazz' if the keyword makes your IDE angry.
+ * @param aNounDef.allowsArbitraryAttrs Is this a 'first class noun'/can it be
+ * a subject, AKA can this noun have attributes stored on it that relate
+ * it to other things? For example, a message is first-class; we store
+ * attributes of messages. A date is not first-class now, nor is it
+ * likely to be; we will not store attributes about a date, although dates
+ * will be the objects of other subjects. (For example: we might
+ * associate a date with a calendar event, but the date is an attribute of
+ * the calendar event and not vice versa.)
+ * @param aNounDef.usesParameter A boolean indicating whether this noun
+ * requires use of the 'parameter' BLOB storage field on the attribute
+ * bindings in the database to persist itself. Use of parameters should
+ * be limited to a reasonable number of values (16-32 is okay, more than
+ * that is pushing it and 256 should be considered an absolute upper
+ * bound) because of the database organization. When false, your
+ * toParamAndValue function is expected to return null for the parameter
+ * and likewise your fromParamAndValue should expect ignore and generally
+ * ignore the argument.
+ * @param aNounDef.toParamAndValue A function that takes an instantiated noun
+ * instance and returns a 2-element list of [parameter, value] where
+ * parameter may only be non-null if you passed a usesParameter of true.
+ * Parameter may be of any type (BLOB), and value must be numeric (pass
+ * 0 if you don't need the value).
+ *
+ * @param aNounDef.isPrimitive True when the noun instance is a raw numeric
+ * value/string/boolean. False when the instance is an object. When
+ * false, it is assumed the attribute that serves as a unique identifier
+ * for the value is "id" unless 'idAttr' is provided.
+ * @param [aNounDef.idAttr="id"] For non-primitive nouns, this is the
+ * attribute on the object that uniquely identifies it.
+ *
+ * @param aNounDef.schema Unsupported mechanism by which you can define a
+ * table that corresponds to this noun. The table will be created if it
+ * does not exist.
+ * - name The table name; don't conflict with other things!
+ * - columns A list of [column name, sqlite type] tuples. You should
+ * always include a definition like ["id", "INTEGER PRIMARY KEY"] for
+ * now (and it should be the first column name too.) If you care about
+ * how the attributes are poked into your object (for example, you want
+ * underscores used for some of them because the attributes should be
+ * immutable), then you can include a third string that is the name of
+ * the attribute to use.
+ * - indices A dictionary of lists of column names, where the key name
+ * becomes the index name. Ex: {foo: ["bar"]} results in an index on
+ * the column "bar" where the index is named "foo".
+ */
+ defineNoun: function gloda_ns_defineNoun(aNounDef, aNounID) {
+ this._log.info("Defining noun: " + aNounDef.name);
+ if (aNounID === undefined)
+ aNounID = this._nextNounID++;
+ aNounDef.id = aNounID;
+
+ // Let people whose editors get angry about illegal attribute names use
+ // clazz instead of class.
+ if (aNounDef.clazz)
+ aNounDef.class = aNounDef.clazz;
+
+ if (!("idAttr" in aNounDef))
+ aNounDef.idAttr = "id";
+ if (!("comparator" in aNounDef)) {
+ aNounDef.comparator = function() {
+ throw new Error("Noun type '" + aNounDef.name +
+ "' lacks a real comparator.");
+ };
+ }
+
+ // We allow nouns to have data tables associated with them where we do all
+ // the legwork. The schema attribute is the gateway to this magical world
+ // of functionality. Said door is officially unsupported.
+ if (aNounDef.schema) {
+ if (!aNounDef.tableName) {
+ if (aNounDef.schema.name)
+ aNounDef.tableName = "ext_" + aNounDef.schema.name;
+ else
+ aNounDef.tableName = "ext_" + aNounDef.name;
+ }
+ // this creates the data table and binder and hooks everything up
+ GlodaDatastore.createNounTable(aNounDef);
+
+ if (!aNounDef.toParamAndValue)
+ aNounDef.toParamAndValue = function (aThing) {
+ if (aThing instanceof aNounDef.class)
+ return [null, aThing.id];
+ else // assume they're just passing the id directly
+ return [null, aThing];
+ };
+ }
+
+ // if it has a table, you can query on it. seems straight-forward.
+ if (aNounDef.tableName) {
+ [aNounDef.queryClass, aNounDef.nullQueryClass,
+ aNounDef.explicitQueryClass, aNounDef.wildcardQueryClass] =
+ GlodaQueryClassFactory(aNounDef);
+ aNounDef._dbMeta = {};
+ aNounDef.class.prototype.NOUN_ID = aNounDef.id;
+ aNounDef.class.prototype.NOUN_DEF = aNounDef;
+ aNounDef.toJSON = this._managedToJSON;
+
+ aNounDef.specialLoadAttribs = [];
+
+ // - define the 'id' constrainer
+ let idConstrainer = function() {
+ let constraint = [GlodaDatastore.kConstraintIdIn, null];
+ for (let iArg = 0; iArg < arguments.length; iArg++) {
+ constraint.push(arguments[iArg]);
+ }
+ this._constraints.push(constraint);
+ return this;
+ };
+ aNounDef.queryClass.prototype.id = idConstrainer;
+ }
+ if (aNounDef.cache) {
+ let cacheCost = aNounDef.cacheCost || 1024;
+ let cacheBudget = aNounDef.cacheBudget || 128 * 1024;
+ let cacheSize = Math.floor(cacheBudget / cacheCost);
+ if (cacheSize)
+ GlodaCollectionManager.defineCache(aNounDef, cacheSize);
+ }
+ aNounDef.attribsByBoundName = {};
+ aNounDef.domExposeAttribsByBoundName = {};
+
+ aNounDef.objectNounOfAttributes = [];
+
+ this._nounNameToNounID[aNounDef.name] = aNounID;
+ this._nounIDToDef[aNounID] = aNounDef;
+ aNounDef.actions = [];
+
+ this._attrProviderOrderByNoun[aNounDef.id] = [];
+ this._attrOptimizerOrderByNoun[aNounDef.id] = [];
+ this._attrProvidersByNoun[aNounDef.id] = {};
+
+ return aNounDef;
+ },
+
+ /**
+ * Lookup a noun (ID) suitable for passing to defineAttribute's various
+ * noun arguments. Throws an exception if the noun with the given name
+ * cannot be found; the assumption is that you can't live without the noun.
+ */
+ lookupNoun: function gloda_ns_lookupNoun(aNounName) {
+ if (aNounName in this._nounNameToNounID)
+ return this._nounNameToNounID[aNounName];
+
+ throw Error("Unable to locate noun with name '" + aNounName + "', but I " +
+ "do know about: " +
+ Object.keys(this._nounNameToNounID).join(", "));
+ },
+
+ /**
+ * Lookup a noun def given a name.
+ */
+ lookupNounDef: function gloda_ns_lookupNoun(aNounName) {
+ return this._nounIDToDef[this.lookupNoun(aNounName)];
+ },
+
+
+ /**
+ * Define an action on a noun. During the prototype stage, this was conceived
+ * of as a way to expose all the constraints possible given a noun. For
+ * example, if you have an identity or a contact, you could use this to
+ * see all the messages sent from/to a given contact. It was likewise
+ * thought potentially usable for future expansion. For example, you could
+ * also decide to send an e-mail to a contact when you have the contact
+ * instance available.
+ * Outside of the 'expmess' checkbox-happy prototype, this functionality is
+ * not used. As such, this functionality should be considered in flux and
+ * subject to changes. Also, very open to specific suggestsions motivated
+ * by use cases.
+ * One conceptual issue raised by this mechanism is the interaction of actions
+ * with facts like "this message is read". We currently implement the 'fact'
+ * by defining an attribute with a 'boolean' noun type. To deal with this,
+ * in various places we pass-in the attribute as well as the noun value.
+ * Since the relationships for booleans and integers in these cases is
+ * standard and well-defined, this works out pretty well, but suggests we
+ * need to think things through.
+ *
+ * @param aNounID The ID of the noun you want to define an action on.
+ * @param aActionMeta The dictionary describing the noun. The dictionary
+ * should have the following fields:
+ * - actionType: a string indicating the type of action. Currently, only
+ * "filter" is a legal value.
+ * - actionTarget: the noun ID of the noun type on which this action is
+ * applicable. For example,
+ *
+ * The following should be present for actionType=="filter";
+ * - shortName: The name that should be used to display this constraint. For
+ * example, a checkbox-heavy UI might display a checkbox for each constraint
+ * using shortName as the label.
+ * - makeConstraint: A function that takes the attribute that is the source
+ * of the noun and the noun instance as arguments, and returns APV-style
+ * constraints. Since the APV-style query mechanism is now deprecated,
+ * this signature is deprecated. Probably the way to update this would be
+ * to pass in the query instance that constraints should be contributed to.
+ */
+ defineNounAction: function gloda_ns_defineNounAction(aNounID, aActionMeta) {
+ let nounDef = this._nounIDToDef[aNounID];
+ nounDef.actions.push(aActionMeta);
+ },
+
+ /**
+ * Retrieve all of the actions (as defined using defineNounAction) for the
+ * given noun type (via noun ID) with the given action type (ex: filter).
+ */
+ getNounActions: function gloda_ns_getNounActions(aNounID, aActionType) {
+ let nounDef = this._nounIDToDef[aNounID];
+ if (!nounDef)
+ return [];
+ return nounDef.actions.
+ filter(action => !aActionType || (action.actionType == aActionType));
+ },
+
+ /** Attribute providers in the sequence to process them. */
+ _attrProviderOrderByNoun: {},
+ /** Attribute providers that provide optimizers, in the sequence to proc. */
+ _attrOptimizerOrderByNoun: {},
+ /** Maps attribute providers to the list of attributes they provide */
+ _attrProviders: {},
+ /**
+ * Maps nouns to their attribute providers to a list of the attributes they
+ * provide for the noun.
+ */
+ _attrProvidersByNoun: {},
+
+ /**
+ * Define the core nouns (that are not defined elsewhere) and a few noun
+ * actions. Core nouns could be defined in other files, assuming dependency
+ * issues are resolved via the everybody.js mechanism or something else.
+ * Right now, noun_tag defines the tag noun. If we broke more of these out,
+ * we would probably want to move the 'class' code from datamodel.js, the
+ * SQL table def and helper code from datastore.js (and this code) to their
+ * own noun_*.js files. There are some trade-offs to be made, and I think
+ * we can deal with those once we start to integrate lightning/calendar and
+ * our noun space gets large and more heterogeneous.
+ */
+ _initAttributes: function gloda_ns_initAttributes() {
+ this.defineNoun({
+ name: "bool",
+ clazz: Boolean, allowsArbitraryAttrs: false,
+ isPrimitive: true,
+ // favor true before false
+ comparator: function gloda_bool_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return b - a;
+ },
+ toParamAndValue: function(aBool) {
+ return [null, aBool ? 1 : 0];
+ }}, this.NOUN_BOOLEAN);
+ this.defineNoun({
+ name: "number",
+ clazz: Number, allowsArbitraryAttrs: false, continuous: true,
+ isPrimitive: true,
+ comparator: function gloda_number_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a - b;
+ },
+ toParamAndValue: function(aNum) {
+ return [null, aNum];
+ }}, this.NOUN_NUMBER);
+ this.defineNoun({
+ name: "string",
+ clazz: String, allowsArbitraryAttrs: false,
+ isPrimitive: true,
+ comparator: function gloda_string_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a.localeCompare(b);
+ },
+ toParamAndValue: function(aString) {
+ return [null, aString];
+ }}, this.NOUN_STRING);
+ this.defineNoun({
+ name: "date",
+ clazz: Date, allowsArbitraryAttrs: false, continuous: true,
+ isPrimitive: true,
+ comparator: function gloda_data_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a - b;
+ },
+ toParamAndValue: function(aDate) {
+ return [null, aDate.valueOf() * 1000];
+ }}, this.NOUN_DATE);
+ this.defineNoun({
+ name: "fulltext",
+ clazz: String, allowsArbitraryAttrs: false, continuous: false,
+ isPrimitive: true,
+ comparator: function gloda_fulltext_comparator(a, b) {
+ throw new Error("Fulltext nouns are not comparable!");
+ },
+ // as noted on NOUN_FULLTEXT, we just pass the string around. it never
+ // hits the database, so it's okay.
+ toParamAndValue: function(aString) {
+ return [null, aString];
+ }}, this.NOUN_FULLTEXT);
+
+ this.defineNoun({
+ name: "folder",
+ clazz: GlodaFolder,
+ allowsArbitraryAttrs: false,
+ isPrimitive: false,
+ queryHelpers: {
+ /**
+ * Query for accounts based on the account associated with folders. We
+ * walk all of the folders associated with an account and put them in
+ * the list of folders that match if gloda would index them. This is
+ * unsuitable for producing a persistable constraint since it does not
+ * adapt for added/deleted folders. However, it is sufficient for
+ * faceting. Also, we don't persist constraints yet.
+ *
+ * @TODO The long-term solution is to move towards using arithmetic
+ * encoding on folder-id's like we use for MIME types and friends.
+ */
+ Account: function(aAttrDef, aArguments) {
+ let folderValues = [];
+ let seenRootFolders = {};
+ for (let iArg = 0; iArg < aArguments.length; iArg++) {
+ let givenFolder = aArguments[iArg];
+ let givenMsgFolder = givenFolder.getXPCOMFolder(
+ givenFolder.kActivityFolderOnlyNoData);
+ let rootFolder = givenMsgFolder.rootFolder;
+
+ // skip processing this folder if we have already processed its
+ // root folder.
+ if (rootFolder.URI in seenRootFolders)
+ continue;
+ seenRootFolders[rootFolder.URI] = true;
+
+ let allFolders = rootFolder.descendants;
+ for (let folder in fixIterator(allFolders, Ci.nsIMsgFolder)) {
+ let folderFlags = folder.flags;
+
+ // Ignore virtual folders, non-mail folders.
+ // XXX this is derived from GlodaIndexer's shouldIndexFolder.
+ // This should probably just use centralized code or the like.
+ if (!(folderFlags & Ci.nsMsgFolderFlags.Mail) ||
+ (folderFlags & Ci.nsMsgFolderFlags.Virtual))
+ continue;
+ // we only index local or IMAP folders
+ if (!(folder instanceof Ci.nsIMsgLocalMailFolder) &&
+ !(folder instanceof Ci.nsIMsgImapMailFolder))
+ continue;
+
+ let glodaFolder = Gloda.getFolderForFolder(folder);
+ folderValues.push(glodaFolder);
+ }
+ }
+ return this._inConstraintHelper(aAttrDef, folderValues);
+ }
+ },
+ comparator: function gloda_folder_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a.name.localeCompare(b.name);
+ },
+ toParamAndValue: function(aFolderOrGlodaFolder) {
+ if (aFolderOrGlodaFolder instanceof GlodaFolder)
+ return [null, aFolderOrGlodaFolder.id];
+ else
+ return [null, GlodaDatastore._mapFolder(aFolderOrGlodaFolder).id];
+ }}, this.NOUN_FOLDER);
+ this.defineNoun({
+ name: "account",
+ clazz: GlodaAccount,
+ allowsArbitraryAttrs: false,
+ isPrimitive: false,
+ equals: function(a, b) {
+ if (a && !b || !a && b)
+ return false;
+ if (!a && !b)
+ return true;
+ return a.id == b.id;
+ },
+ comparator: function gloda_account_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a.name.localeCompare(b.name);
+ }}, this.NOUN_ACCOUNT);
+ this.defineNoun({
+ name: "conversation",
+ clazz: GlodaConversation,
+ allowsArbitraryAttrs: false,
+ isPrimitive: false,
+ cache: true, cacheCost: 512,
+ tableName: "conversations",
+ attrTableName: "messageAttributes", attrIDColumnName: "conversationID",
+ datastore: GlodaDatastore,
+ objFromRow: GlodaDatastore._conversationFromRow,
+ comparator: function gloda_conversation_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a.subject.localeCompare(b.subject);
+ },
+ toParamAndValue: function(aConversation) {
+ if (aConversation instanceof GlodaConversation)
+ return [null, aConversation.id];
+ else // assume they're just passing the id directly
+ return [null, aConversation];
+ }}, this.NOUN_CONVERSATION);
+ this.defineNoun({
+ name: "message",
+ clazz: GlodaMessage,
+ allowsArbitraryAttrs: true,
+ isPrimitive: false,
+ cache: true, cacheCost: 2048,
+ tableName: "messages",
+ // we will always have a fulltext row, even for messages where we don't
+ // have the body available. this is because we want the subject indexed.
+ dbQueryJoinMagic:
+ " INNER JOIN messagesText ON messages.id = messagesText.rowid",
+ attrTableName: "messageAttributes", attrIDColumnName: "messageID",
+ datastore: GlodaDatastore, objFromRow: GlodaDatastore._messageFromRow,
+ dbAttribAdjuster: GlodaDatastore.adjustMessageAttributes,
+ dbQueryValidityConstraintSuffix:
+ " AND +deleted = 0 AND +folderID IS NOT NULL AND +messageKey IS NOT NULL",
+ // This is what's used when we have no validity constraints, i.e. we allow
+ // for ghost messages, which do not have a row in the messagesText table.
+ dbQueryJoinMagicWithNoValidityConstraints:
+ " LEFT JOIN messagesText ON messages.id = messagesText.rowid",
+ objInsert: GlodaDatastore.insertMessage,
+ objUpdate: GlodaDatastore.updateMessage,
+ toParamAndValue: function(aMessage) {
+ if (aMessage instanceof GlodaMessage)
+ return [null, aMessage.id];
+ else // assume they're just passing the id directly
+ return [null, aMessage];
+ }}, this.NOUN_MESSAGE);
+ this.defineNoun({
+ name: "contact",
+ clazz: GlodaContact,
+ allowsArbitraryAttrs: true,
+ isPrimitive: false,
+ cache: true, cacheCost: 128,
+ tableName: "contacts",
+ attrTableName: "contactAttributes", attrIDColumnName: "contactID",
+ datastore: GlodaDatastore, objFromRow: GlodaDatastore._contactFromRow,
+ dbAttribAdjuster: GlodaDatastore.adjustAttributes,
+ objInsert: GlodaDatastore.insertContact,
+ objUpdate: GlodaDatastore.updateContact,
+ comparator: function gloda_contact_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a.name.localeCompare(b.name);
+ },
+ toParamAndValue: function(aContact) {
+ if (aContact instanceof GlodaContact)
+ return [null, aContact.id];
+ else // assume they're just passing the id directly
+ return [null, aContact];
+ }}, this.NOUN_CONTACT);
+ this.defineNoun({
+ name: "identity",
+ clazz: GlodaIdentity,
+ allowsArbitraryAttrs: false,
+ isPrimitive: false,
+ cache: true, cacheCost: 128,
+ usesUniqueValue: true,
+ tableName: "identities",
+ datastore: GlodaDatastore, objFromRow: GlodaDatastore._identityFromRow,
+ /**
+ * Short string is the contact name, long string includes the identity
+ * value too, delimited by a colon. Not tremendously localizable.
+ */
+ userVisibleString: function(aIdentity, aLong) {
+ if (!aLong)
+ return aIdentity.contact.name;
+ if (aIdentity.contact.name == aIdentity.value)
+ return aIdentity.value;
+ return aIdentity.contact.name + " (" + aIdentity.value + ")";
+ },
+ comparator: function gloda_identity_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a.contact.name.localeCompare(b.contact.name);
+ },
+ toParamAndValue: function(aIdentity) {
+ if (aIdentity instanceof GlodaIdentity)
+ return [null, aIdentity.id];
+ else // assume they're just passing the id directly
+ return [null, aIdentity];
+ }}, this.NOUN_IDENTITY);
+ this.defineNoun({
+ name: "attachment-infos",
+ clazz: GlodaAttachment,
+ allowsArbitraryAttrs: false,
+ isPrimitive: false,
+ toJSON: function (x) {
+ return [
+ x._name,
+ x._contentType,
+ x._size,
+ x._part,
+ x._externalUrl,
+ x._isExternal
+ ]
+ },
+ fromJSON: function (x, aGlodaMessage) {
+ let [name, contentType, size, _part, _externalUrl, isExternal] = x;
+ return new GlodaAttachment(aGlodaMessage, name, contentType, size, _part, _externalUrl, isExternal);
+ },
+ }, this.NOUN_ATTACHMENT);
+
+ // parameterized identity is just two identities; we store the first one
+ // (whose value set must be very constrainted, like the 'me' identities)
+ // as the parameter, the second (which does not need to be constrained)
+ // as the value.
+ this.defineNoun({
+ name: "parameterized-identity",
+ clazz: null,
+ allowsArbitraryAttrs: false,
+ comparator: function gloda_fulltext_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ // First sort by the first identity in the tuple
+ // Since our general use-case is for the first guy to be "me", we only
+ // compare the identity value, not the name.
+ let fic = a[0].value.localeCompare(b[0].value);
+ if (fic)
+ return fic;
+ // Next compare the second identity in the tuple, but use the contact
+ // this time to be consistent with our identity comparator.
+ return a[1].contact.name.localeCompare(b[1].contact.name);
+ },
+ computeDelta: function(aCurValues, aOldValues) {
+ let oldMap = {};
+ for (let tupe of aOldValues) {
+ let [originIdentity, targetIdentity] = tupe;
+ let targets = oldMap[originIdentity];
+ if (targets === undefined)
+ targets = oldMap[originIdentity] = {};
+ targets[targetIdentity] = true;
+ }
+
+ let added = [], removed = [];
+ for (let tupe of aCurValues) {
+ let [originIdentity, targetIdentity] = tupe;
+ let targets = oldMap[originIdentity];
+ if ((targets === undefined) || !(targetIdentity in targets))
+ added.push(tupe);
+ else
+ delete targets[targetIdentity];
+ }
+
+ for (let originIdentity in oldMap) {
+ let targets = oldMap[originIdentity];
+ for (let targetIdentity in targets) {
+ removed.push([originIdentity, targetIdentity]);
+ }
+ }
+
+ return [added, removed];
+ },
+ contributeObjDependencies: function(aJsonValues, aReferencesByNounID,
+ aInverseReferencesByNounID) {
+ // nothing to do with a zero-length list
+ if (aJsonValues.length == 0)
+ return false;
+
+ let nounIdentityDef = Gloda._nounIDToDef[Gloda.NOUN_IDENTITY];
+ let references = aReferencesByNounID[nounIdentityDef.id];
+ if (references === undefined)
+ references = aReferencesByNounID[nounIdentityDef.id] = {};
+
+ for (let tupe of aJsonValues) {
+ let [originIdentityID, targetIdentityID] = tupe;
+ if (!(originIdentityID in references))
+ references[originIdentityID] = null;
+ if (!(targetIdentityID in references))
+ references[targetIdentityID] = null;
+ }
+
+ return true;
+ },
+ resolveObjDependencies: function(aJsonValues, aReferencesByNounID,
+ aInverseReferencesByNounID) {
+ let references =
+ aReferencesByNounID[Gloda.NOUN_IDENTITY];
+
+ let results = [];
+ for (let tupe of aJsonValues) {
+ let [originIdentityID, targetIdentityID] = tupe;
+ results.push([references[originIdentityID],
+ references[targetIdentityID]]);
+ }
+
+ return results;
+ },
+ toJSON: function (aIdentityTuple) {
+ return [aIdentityTuple[0].id, aIdentityTuple[1].id];
+ },
+ toParamAndValue: function(aIdentityTuple) {
+ return [aIdentityTuple[0].id, aIdentityTuple[1].id];
+ }}, this.NOUN_PARAM_IDENTITY);
+
+ GlodaDatastore.getAllAttributes();
+ },
+
+ /**
+ * Create accessor functions to 'bind' an attribute to underlying normalized
+ * attribute storage, as well as creating the appropriate query object
+ * constraint helper functions. This name is somewhat of a misnomer because
+ * special attributes are not 'bound' (because specific/non-generic per-class
+ * code provides the properties) but still depend on this method to
+ * establish their constraint helper methods.
+ *
+ * @XXX potentially rename to not suggest binding is required.
+ */
+ _bindAttribute: function gloda_ns_bindAttr(aAttrDef, aSubjectNounDef) {
+ let objectNounDef = aAttrDef.objectNounDef;
+
+ // -- the query constraint helpers
+ if (aSubjectNounDef.queryClass !== undefined) {
+ let constrainer;
+ let canQuery = true;
+ if (aAttrDef.special == this.kSpecialFulltext) {
+ constrainer = function() {
+ let constraint = [GlodaDatastore.kConstraintFulltext, aAttrDef];
+ for (let iArg = 0; iArg < arguments.length; iArg++) {
+ constraint.push(arguments[iArg]);
+ }
+ this._constraints.push(constraint);
+ return this;
+ };
+ }
+ else if (aAttrDef.canQuery || aAttrDef.attributeName.startsWith("_")) {
+ constrainer = function() {
+ let constraint = [GlodaDatastore.kConstraintIn, aAttrDef];
+ for (let iArg = 0; iArg < arguments.length; iArg++) {
+ constraint.push(arguments[iArg]);
+ }
+ this._constraints.push(constraint);
+ return this;
+ };
+ } else {
+ constrainer = function() {
+ throw new Error(
+ "Cannot query on attribute "+aAttrDef.attributeName
+ + " because its canQuery parameter hasn't been set to true."
+ + " Reading the comments about Gloda.defineAttribute may be a"
+ + " sensible thing to do now.");
+ }
+ canQuery = false;
+ }
+
+ aSubjectNounDef.queryClass.prototype[aAttrDef.boundName] = constrainer;
+
+ // Don't bind extra query-able attributes if we're unable to perform a
+ // search on the attribute.
+ if (!canQuery)
+ return;
+
+ // - ranged value helper: fooRange
+ if (objectNounDef.continuous) {
+ // takes one or more tuples of [lower bound, upper bound]
+ let rangedConstrainer = function() {
+ let constraint = [GlodaDatastore.kConstraintRanges, aAttrDef];
+ for (let iArg = 0; iArg < arguments.length; iArg++ ) {
+ constraint.push(arguments[iArg]);
+ }
+ this._constraints.push(constraint);
+ return this;
+ };
+
+ aSubjectNounDef.queryClass.prototype[aAttrDef.boundName + "Range"] =
+ rangedConstrainer;
+ }
+
+ // - string LIKE helper for special on-row attributes: fooLike
+ // (it is impossible to store a string as an indexed attribute, which is
+ // why we do this for on-row only.)
+ if (aAttrDef.special == this.kSpecialString) {
+ let likeConstrainer = function() {
+ let constraint = [GlodaDatastore.kConstraintStringLike, aAttrDef];
+ for (let iArg = 0; iArg < arguments.length; iArg++) {
+ constraint.push(arguments[iArg]);
+ }
+ this._constraints.push(constraint);
+ return this;
+ };
+
+ aSubjectNounDef.queryClass.prototype[aAttrDef.boundName + "Like"] =
+ likeConstrainer;
+ }
+
+ // - Custom helpers provided by the noun type...
+ if ("queryHelpers" in objectNounDef) {
+ for (let name in objectNounDef.queryHelpers) {
+ let helper = objectNounDef.queryHelpers[name];
+ // we need a new closure...
+ let helperFunc = helper;
+ aSubjectNounDef.queryClass.prototype[aAttrDef.boundName + name] =
+ function() {
+ return helperFunc.call(this, aAttrDef, arguments);
+ };
+ }
+ }
+ }
+ },
+
+ /**
+ * Names of attribute-specific localized strings and the JS attribute they are
+ * exposed as in the attribute's "strings" attribute (if the provider has a
+ * string bundle exposed on its "strings" attribute). They are rooted at
+ * "gloda.SUBJECT-NOUN-NAME.attr.ATTR-NAME.*".
+ *
+ * Please consult the localization notes in gloda.properties to understand
+ * what these are used for.
+ */
+ _ATTR_LOCALIZED_STRINGS: {
+ /* - Faceting */
+ facetNameLabel: "facetNameLabel",
+ includeLabel: "includeLabel",
+ excludeLabel: "excludeLabel",
+ remainderLabel: "remainderLabel",
+ mustMatchLabel: "mustMatchLabel",
+ cantMatchLabel: "cantMatchLabel",
+ mayMatchLabel: "mayMatchLabel",
+ mustMatchNoneLabel: "mustMatchNoneLabel",
+ mustMatchSomeLabel: "mustMatchSomeLabel",
+ mayMatchAnyLabel: "mayMatchAnyLabel",
+ },
+ /**
+ * Define an attribute and all its meta-data. Takes a single dictionary as
+ * its argument, with the following required properties:
+ *
+ * @param aAttrDef.provider The object instance providing a 'process' method.
+ * @param aAttrDef.extensionName The name of the extension providing these
+ * attributes.
+ * @param aAttrDef.attributeType The type of attribute, one of the values from
+ * the kAttr* enumeration.
+ * @param aAttrDef.attributeName The name of the attribute, which also doubles
+ * as the bound property name if you pass 'bind' a value of true. You are
+ * responsible for avoiding collisions, which presumably will mean
+ * checking/updating a wiki page in the future, or just prefixing your
+ * attribute name with your extension name or something like that.
+ * @param aAttrDef.bind Should this attribute be 'bound' as a convenience
+ * attribute on the subject's object (true/false)? For example, with an
+ * attributeName of "foo" and passing true for 'bind' with a subject noun
+ * of NOUN_MESSAGE, GlodaMessage instances will expose a "foo" getter that
+ * returns the value of the attribute. If 'singular' is true, this means
+ * an instance of the object class corresponding to the noun type or null
+ * if the attribute does not exist. If 'singular' is false, this means a
+ * list of instances of the object class corresponding to the noun type,
+ * where the list may be empty if no instances of the attribute are
+ * present.
+ * @param aAttrDef.bindName Optional override of attributeName for purposes of
+ * the binding property's name.
+ * @param aAttrDef.singular Is the attribute going to happen at most once
+ * (true), or potentially multiple times (false). This affects whether
+ * the binding returns a list or just a single item (which is null when
+ * the attribute is not present).
+ * @param [aAttrDef.emptySetIsSignificant=false] Should we
+ * @param aAttrDef.subjectNouns A list of object types (NOUNs) that this
+ * attribute can be set on. Each element in the list should be one of the
+ * NOUN_* constants or a dynamically registered noun type.
+ * @param aAttrDef.objectNoun The object type (one of the NOUN_* constants or
+ * a dynamically registered noun types) that is the 'object' in the
+ * traditional RDF triple. More pragmatically, in the database row used
+ * to represent an attribute, we store the subject (ex: message ID),
+ * attribute ID, and an integer which is the integer representation of the
+ * 'object' whose type you are defining right here.
+ */
+ defineAttribute: function gloda_ns_defineAttribute(aAttrDef) {
+ // ensure required properties exist on aAttrDef
+ if (!("provider" in aAttrDef) ||
+ !("extensionName" in aAttrDef) ||
+ !("attributeType" in aAttrDef) ||
+ !("attributeName" in aAttrDef) ||
+ !("singular" in aAttrDef) ||
+ !("subjectNouns" in aAttrDef) ||
+ !("objectNoun" in aAttrDef))
+ // perhaps we should have a list of required attributes, perchance with
+ // and explanation of what it holds, and use that to be friendlier?
+ throw Error("You omitted a required attribute defining property, please" +
+ " consult the documentation as penance.");
+
+ // -- Fill in defaults
+ if (!("emptySetIsSignificant" in aAttrDef))
+ aAttrDef.emptySetIsSignificant = false;
+
+ if (!("canQuery" in aAttrDef))
+ aAttrDef.canQuery = aAttrDef.facet ? true : false;
+
+ // return if the attribute has already been defined
+ if (aAttrDef.dbDef)
+ return aAttrDef;
+
+ // - first time we've seen a provider init logic
+ if (!(aAttrDef.provider.providerName in this._attrProviders)) {
+ this._attrProviders[aAttrDef.provider.providerName] = [];
+ if (aAttrDef.provider.contentWhittle)
+ whittlerRegistry.registerWhittler(aAttrDef.provider);
+ }
+
+ let compoundName = aAttrDef.extensionName + ":" + aAttrDef.attributeName;
+ // -- Database Definition
+ let attrDBDef;
+ if (compoundName in GlodaDatastore._attributeDBDefs) {
+ // the existence of the GlodaAttributeDBDef means that either it has
+ // already been fully defined, or has been loaded from the database but
+ // not yet 'bound' to a provider (and had important meta-info that
+ // doesn't go in the db copied over)
+ attrDBDef = GlodaDatastore._attributeDBDefs[compoundName];
+ }
+ // we need to create the attribute definition in the database
+ else {
+ let attrID = null;
+ attrID = GlodaDatastore._createAttributeDef(aAttrDef.attributeType,
+ aAttrDef.extensionName,
+ aAttrDef.attributeName,
+ null);
+
+ attrDBDef = new GlodaAttributeDBDef(GlodaDatastore, attrID, compoundName,
+ aAttrDef.attributeType, aAttrDef.extensionName, aAttrDef.attributeName);
+ GlodaDatastore._attributeDBDefs[compoundName] = attrDBDef;
+ GlodaDatastore._attributeIDToDBDefAndParam[attrID] = [attrDBDef, null];
+ }
+
+ aAttrDef.dbDef = attrDBDef;
+ attrDBDef.attrDef = aAttrDef;
+
+ aAttrDef.id = aAttrDef.dbDef.id;
+
+ if ("bindName" in aAttrDef)
+ aAttrDef.boundName = aAttrDef.bindName;
+ else
+ aAttrDef.boundName = aAttrDef.attributeName;
+
+ aAttrDef.objectNounDef = this._nounIDToDef[aAttrDef.objectNoun];
+ aAttrDef.objectNounDef.objectNounOfAttributes.push(aAttrDef);
+
+ // -- Facets
+ function normalizeFacetDef(aFacetDef) {
+ if (!("groupIdAttr" in aFacetDef))
+ aFacetDef.groupIdAttr = aAttrDef.objectNounDef.idAttr;
+ if (!("groupComparator" in aFacetDef))
+ aFacetDef.groupComparator = aAttrDef.objectNounDef.comparator;
+ if (!("filter" in aFacetDef))
+ aFacetDef.filter = null;
+ }
+ // No facet attribute means no facet desired; set an explicit null so that
+ // code can check without doing an "in" check.
+ if (!("facet" in aAttrDef))
+ aAttrDef.facet = null;
+ // Promote "true" facet values to the defaults. Where attributes have
+ // specified values, make sure we fill in any missing defaults.
+ else {
+ if (aAttrDef.facet == true) {
+ aAttrDef.facet = {
+ type: "default",
+ groupIdAttr: aAttrDef.objectNounDef.idAttr,
+ groupComparator: aAttrDef.objectNounDef.comparator,
+ filter: null,
+ };
+ }
+ else {
+ normalizeFacetDef(aAttrDef.facet);
+ }
+ }
+ if ("extraFacets" in aAttrDef) {
+ for (let facetDef of aAttrDef.extraFacets) {
+ normalizeFacetDef(facetDef);
+ }
+ }
+
+ function gatherLocalizedStrings(aBundle, aPropRoot, aStickIn) {
+ for (let propName in Gloda._ATTR_LOCALIZED_STRINGS) {
+ let attrName = Gloda._ATTR_LOCALIZED_STRINGS[propName];
+ try {
+ aStickIn[attrName] = aBundle.get(aPropRoot + propName);
+ }
+ catch (ex) {
+ // do nothing. nsIStringBundle throws exceptions because it is a
+ // standard nsresult type of API and our helper buddy does nothing
+ // to help us. (StringBundle.js, that is.)
+ }
+ }
+ }
+
+ // -- L10n.
+ // If the provider has a string bundle, populate a "strings" attribute with
+ // our standard attribute strings that can be UI exposed.
+ if (("strings" in aAttrDef.provider) && (aAttrDef.facet)) {
+ let bundle = aAttrDef.provider.strings;
+
+ // -- attribute strings
+ let attrStrings = aAttrDef.facet.strings = {};
+ // we use the first subject the attribute applies to as the basis of
+ // where to get the string from. Mainly because we currently don't have
+ // any attributes with multiple subjects nor a use-case where we expose
+ // multiple noun types via the UI. (Just messages right now.)
+ let canonicalSubject = this._nounIDToDef[aAttrDef.subjectNouns[0]];
+ let propRoot = "gloda." + canonicalSubject.name + ".attr." +
+ aAttrDef.attributeName + ".";
+ gatherLocalizedStrings(bundle, propRoot, attrStrings);
+
+ // -- alias strings for synthetic facets
+ if ("extraFacets" in aAttrDef) {
+ for (let facetDef of aAttrDef.extraFacets) {
+ facetDef.strings = {};
+ let aliasPropRoot = "gloda." + canonicalSubject.name + ".attr." +
+ facetDef.alias + ".";
+ gatherLocalizedStrings(bundle, aliasPropRoot, facetDef.strings);
+ }
+ }
+ }
+
+ // -- Subject Noun Binding
+ for (let iSubject = 0; iSubject < aAttrDef.subjectNouns.length;
+ iSubject++) {
+ let subjectType = aAttrDef.subjectNouns[iSubject];
+ let subjectNounDef = this._nounIDToDef[subjectType];
+ this._bindAttribute(aAttrDef, subjectNounDef);
+
+ // update the provider maps...
+ if (this._attrProviderOrderByNoun[subjectType]
+ .indexOf(aAttrDef.provider) == -1) {
+ this._attrProviderOrderByNoun[subjectType].push(aAttrDef.provider);
+ if (aAttrDef.provider.optimize)
+ this._attrOptimizerOrderByNoun[subjectType].push(aAttrDef.provider);
+ this._attrProvidersByNoun[subjectType][aAttrDef.provider] = [];
+ }
+ this._attrProvidersByNoun[subjectType][aAttrDef.provider].push(aAttrDef);
+
+ subjectNounDef.attribsByBoundName[aAttrDef.boundName] = aAttrDef;
+ if (aAttrDef.domExpose)
+ subjectNounDef.domExposeAttribsByBoundName[aAttrDef.boundName] =
+ aAttrDef;
+
+ if (aAttrDef.special & this.kSpecialColumn)
+ subjectNounDef.specialLoadAttribs.push(aAttrDef);
+
+ // if this is a parent column attribute, make note of it so that if we
+ // need to do an inverse references lookup, we know what column we are
+ // issuing against.
+ if (aAttrDef.special === this.kSpecialColumnParent) {
+ subjectNounDef.parentColumnAttr = aAttrDef;
+ }
+
+ if (aAttrDef.objectNounDef.tableName ||
+ aAttrDef.objectNounDef.contributeObjDependencies) {
+ subjectNounDef.hasObjDependencies = true;
+ }
+ }
+
+ this._attrProviders[aAttrDef.provider.providerName].push(aAttrDef);
+ return aAttrDef;
+ },
+
+ /**
+ * Retrieve the attribute provided by the given extension with the given
+ * attribute name. The original idea was that plugins would effectively
+ * name-space attributes, helping avoid collisions. Since we are leaning
+ * towards using binding heavily, this doesn't really help, as the collisions
+ * will just occur on the attribute name instead. Also, this can turn
+ * extensions into liars as name changes/moves to core/etc. happen.
+ * @TODO consider removing the extension name argument parameter requirement
+ */
+ getAttrDef: function gloda_ns_getAttrDef(aPluginName, aAttrName) {
+ let compoundName = aPluginName + ":" + aAttrName;
+ return GlodaDatastore._attributeDBDefs[compoundName];
+ },
+
+ /**
+ * Create a new query instance for the given noun-type. This provides
+ * a generic way to provide constraint-based queries of any first-class
+ * nouns supported by the system.
+ *
+ * The idea is that every attribute on an object can be used to express
+ * a constraint on the query object. Constraints implicitly 'AND' together,
+ * but providing multiple arguments to a constraint function results in an
+ * 'OR'ing of those values. Additionally, you can call or() on the returned
+ * query to create an alternate query that is effectively a giant OR against
+ * all the constraints you create on the main query object (or any other
+ * alternate queries returned by or()). (Note: there is no nesting of these
+ * alternate queries. query.or().or() is equivalent to query.or())
+ * For each attribute, there is a constraint with the same name that takes
+ * one or more arguments. The arguments represent a set of OR values that
+ * objects matching the query can have. (If you want the constraint
+ * effectively ANDed together, just invoke the constraint function
+ * multiple times.) For example, newQuery(NOUN_PERSON).age(25) would
+ * constraint to all the people aged 25, while age(25, 26) would constrain
+ * to all the people age 25 or 26.
+ * For each attribute with a 'continuous' noun, there is a constraint with the
+ * attribute name with "Range" appended. It takes two arguments which are an
+ * inclusive lower bound and an inclusive lower bound for values in the
+ * range. If you would like an open-ended range on either side, pass null
+ * for that argument. If you would like to specify multiple ranges that
+ * should be ORed together, simply pass additional (pairs of) arguments.
+ * For example, newQuery(NOUN_PERSON).age(25,100) would constraint to all
+ * the people who are >= 25 and <= 100. Likewise age(25, null) would just
+ * return all the people who are 25 or older. And age(25,30,35,40) would
+ * return people who are either 25-30 or 35-30.
+ * There are also full-text constraint columns. In a nutshell, their
+ * arguments are the strings that should be passed to the SQLite FTS3
+ * MATCH clause.
+ *
+ * @param aNounID The (integer) noun-id of the noun you want to query on.
+ * @param aOptions an optional dictionary of query options, see the GlodaQuery
+ * class documentation.
+ */
+ newQuery: function gloda_ns_newQuery(aNounID, aOptions) {
+ let nounDef = this._nounIDToDef[aNounID];
+ return new nounDef.queryClass(aOptions);
+ },
+
+ /**
+ * Create a collection/query for the given noun-type that only matches the
+ * provided items. This is to be used when you have an explicit set of items
+ * that you would still like to receive updates for.
+ */
+ explicitCollection: function gloda_ns_explicitCollection(aNounID, aItems) {
+ let nounDef = this._nounIDToDef[aNounID];
+ let collection = new GlodaCollection(nounDef, aItems, null, null);
+ let query = new nounDef.explicitQueryClass(collection);
+ collection.query = query;
+ GlodaCollectionManager.registerCollection(collection);
+ return collection;
+ },
+
+ /**
+ * Debugging 'wildcard' collection creation support. A wildcard collection
+ * will 'accept' any new item instances presented to the collection manager
+ * as new. The result is that it allows you to be notified as new items
+ * as they are indexed, existing items as they are loaded from the database,
+ * etc.
+ * Because the items are added to the collection without limit, this will
+ * result in a leak if you don't do something to clean up after the
+ * collection. (Forgetting about the collection will suffice, as it is still
+ * weakly held.)
+ */
+ _wildcardCollection: function gloda_ns_wildcardCollection(aNounID, aItems) {
+ let nounDef = this._nounIDToDef[aNounID];
+ let collection = new GlodaCollection(nounDef, aItems, null, null);
+ let query = new nounDef.wildcardQueryClass(collection);
+ collection.query = query;
+ GlodaCollectionManager.registerCollection(collection);
+ return collection;
+ },
+
+ /**
+ * Attribute providers attempting to index something that experience a fatal
+ * problem should throw one of these. For example:
+ * "throw new Gloda.BadItemContentsError('Message lacks an author.');".
+ *
+ * We're not really taking advantage of this yet, but it's a good idea.
+ */
+ BadItemContentsError: BadItemContentsError,
+
+ /**
+ * Populate a gloda representation of an item given the thus-far built
+ * representation, the previous representation, and one or more raw
+ * representations. The attribute providers/optimizers for the given noun
+ * type are invoked, allowing them to contribute/alter things. Following
+ * that, we build and persist our attribute representations.
+ *
+ * The result of the processing ends up with attributes in 3 different forms:
+ * - Database attribute rows (to be added and removed).
+ * - In-memory representation.
+ * - JSON-able representation.
+ *
+ * @param aItem The noun instance you want processed.
+ * @param aRawReps A dictionary that we pass to the attribute providers.
+ * There is a(n implied) contract between the caller of grokNounItem for a
+ * given noun type and the attribute providers for that noun type, and we
+ * have nothing to do with it OTHER THAN inserting a 'trueGlodaRep'
+ * value into it. In the event of reindexing an existing object, the
+ * gloda representation we pass to the indexers is actually a clone that
+ * allows the asynchronous indexers to mutate the object without
+ * causing visible changes in the existing representation of the gloda
+ * object. We patch the changes back onto the original item atomically
+ * once indexing completes. The 'trueGlodaRep' is then useful for
+ * objects that hang off of the gloda instance that need a reference
+ * back to their containing object for API convenience purposes.
+ * @param aIsConceptuallyNew Is the item "new" in the sense that it would
+ * never have been visible from within user code? This translates into
+ * whether this should trigger an itemAdded notification or an
+ * itemModified notification.
+ * @param aIsRecordNew Is the item "new" in the sense that we should INSERT
+ * a record rather than UPDATE-ing a record. For example, when dealing
+ * with messages where we may have a ghost, the ghost message is not a
+ * new record, but is conceptually new.
+ * @param aCallbackHandle The GlodaIndexer-style callback handle that is being
+ * used to drive this processing in an async fashion. (See
+ * GlodaIndexer._callbackHandle).
+ * @param aDoCache Should we allow this item to be contributed to its noun
+ * cache?
+ */
+ grokNounItem: function* gloda_ns_grokNounItem(aItem, aRawReps,
+ aIsConceptuallyNew, aIsRecordNew, aCallbackHandle, aDoCache) {
+ let itemNounDef = aItem.NOUN_DEF;
+ let attribsByBoundName = itemNounDef.attribsByBoundName;
+
+ this._log.info(" ** grokNounItem: " + itemNounDef.name);
+
+ let addDBAttribs = [];
+ let removeDBAttribs = [];
+
+ let jsonDict = {};
+
+ let aOldItem;
+ aRawReps.trueGlodaRep = aItem;
+ if (aIsConceptuallyNew) // there is no old item if we are new.
+ aOldItem = {};
+ else {
+ aOldItem = aItem;
+ // we want to create a clone of the existing item so that we can know the
+ // deltas that happened for indexing purposes
+ aItem = aItem._clone();
+ }
+
+ // Have the attribute providers directly set properties on the aItem
+ let attrProviders = this._attrProviderOrderByNoun[itemNounDef.id];
+ for (let iProvider = 0; iProvider < attrProviders.length; iProvider++) {
+ this._log.info(" * provider: " + attrProviders[iProvider].providerName);
+ yield aCallbackHandle.pushAndGo(
+ attrProviders[iProvider].process(aItem, aRawReps, aIsConceptuallyNew,
+ aCallbackHandle));
+ }
+
+ let attrOptimizers = this._attrOptimizerOrderByNoun[itemNounDef.id];
+ for (let iProvider = 0; iProvider < attrOptimizers.length; iProvider++) {
+ this._log.info(" * optimizer: " + attrOptimizers[iProvider].providerName);
+ yield aCallbackHandle.pushAndGo(
+ attrOptimizers[iProvider].optimize(aItem, aRawReps, aIsConceptuallyNew,
+ aCallbackHandle));
+ }
+ this._log.info(" ** done with providers.");
+
+ // Iterate over the attributes on the item
+ for (let key of Object.keys(aItem)) {
+ let value = aItem[key];
+ // ignore keys that start with underscores, they are private and not
+ // persisted by our attribute mechanism. (they are directly handled by
+ // the object implementation.)
+ if (key.startsWith("_"))
+ continue;
+ // find the attribute definition that corresponds to this key
+ let attrib = attribsByBoundName[key];
+ // if there's no attribute, that's not good, but not horrible.
+ if (attrib === undefined) {
+ this._log.warn("new proc ignoring attrib: " + key);
+ continue;
+ }
+
+ let attribDB = attrib.dbDef;
+ let objectNounDef = attrib.objectNounDef;
+
+ // - translate for our JSON rep
+ if (attrib.singular) {
+ if (objectNounDef.toJSON)
+ jsonDict[attrib.id] = objectNounDef.toJSON(value);
+ else
+ jsonDict[attrib.id] = value;
+ }
+ else {
+ if (objectNounDef.toJSON) {
+ let toJSON = objectNounDef.toJSON;
+ jsonDict[attrib.id] = [];
+ for (let [, subValue] in Iterator(value)) {
+ jsonDict[attrib.id].push(toJSON(subValue));
+ }
+ }
+ else
+ jsonDict[attrib.id] = value;
+ }
+
+ let oldValue = aOldItem[key];
+
+ // the 'old' item is still the canonical one; update it
+ // do the update now, because we may skip operations on addDBAttribs and
+ // removeDBattribs, if the attribute is not to generate entries in
+ // messageAttributes
+ if (oldValue !== undefined || !aIsConceptuallyNew)
+ aOldItem[key] = value;
+
+ // the new canQuery property has to be set to true to generate entries
+ // in the messageAttributes table. Any other truthy value (like a non
+ // empty string), will still make the message query-able but without
+ // using the database.
+ if (attrib.canQuery !== true) {
+ continue;
+ }
+
+ // - database index attributes
+
+ // perform a delta analysis against the old value, if we have one
+ if (oldValue !== undefined) {
+ // in the singular case if they don't match, it's one add and one remove
+ if (attrib.singular) {
+ // test for identicality, failing that, see if they have explicit
+ // equals support.
+ if ((value !== oldValue) &&
+ (!value.equals || !value.equals(oldValue))) {
+ addDBAttribs.push(attribDB.convertValuesToDBAttributes([value])[0]);
+ removeDBAttribs.push(
+ attribDB.convertValuesToDBAttributes([oldValue])[0]);
+ }
+ }
+ // in the plural case, we have to figure the deltas accounting for
+ // possible changes in ordering (which is insignificant from an
+ // indexing perspective)
+ // some nouns may not meet === equivalence needs, so must provide a
+ // custom computeDelta method to help us out
+ else if (objectNounDef.computeDelta) {
+ let [valuesAdded, valuesRemoved] =
+ objectNounDef.computeDelta(value, oldValue);
+ // convert the values to database-style attribute rows
+ addDBAttribs.push.apply(addDBAttribs,
+ attribDB.convertValuesToDBAttributes(valuesAdded));
+ removeDBAttribs.push.apply(removeDBAttribs,
+ attribDB.convertValuesToDBAttributes(valuesRemoved));
+ }
+ else {
+ // build a map of the previous values; we will delete the values as
+ // we see them so that we will know what old values are no longer
+ // present in the current set of values.
+ let oldValueMap = {};
+ for (let anOldValue of oldValue) {
+ // remember, the key is just the toString'ed value, so we need to
+ // store and use the actual value as the value!
+ oldValueMap[anOldValue] = anOldValue;
+ }
+ // traverse the current values...
+ let valuesAdded = [];
+ for (let curValue of value) {
+ if (curValue in oldValueMap)
+ delete oldValueMap[curValue];
+ else
+ valuesAdded.push(curValue);
+ }
+ // anything still on oldValueMap was removed.
+ let valuesRemoved = Object.keys(oldValueMap).
+ map(key => oldValueMap[key]);
+ // convert the values to database-style attribute rows
+ addDBAttribs.push.apply(addDBAttribs,
+ attribDB.convertValuesToDBAttributes(valuesAdded));
+ removeDBAttribs.push.apply(removeDBAttribs,
+ attribDB.convertValuesToDBAttributes(valuesRemoved));
+ }
+
+ // Add/remove the empty set indicator as appropriate.
+ if (attrib.emptySetIsSignificant) {
+ // if we are now non-zero but previously were zero, remove.
+ if (value.length && !oldValue.length)
+ removeDBAttribs.push([GlodaDatastore.kEmptySetAttrId, attribDB.id]);
+ // if we are now zero length but previously were not, add
+ else if (!value.length && oldValue.length)
+ addDBAttribs.push([GlodaDatastore.kEmptySetAttrId, attribDB.id]);
+ }
+ }
+ // no old value, all values are new
+ else {
+ // add the db reps on the new values
+ if (attrib.singular)
+ value = [value];
+ addDBAttribs.push.apply(addDBAttribs,
+ attribDB.convertValuesToDBAttributes(value));
+ // Add the empty set indicator for the attribute id if appropriate.
+ if (!value.length && attrib.emptySetIsSignificant)
+ addDBAttribs.push([GlodaDatastore.kEmptySetAttrId, attribDB.id]);
+ }
+ }
+
+ // Iterate over any remaining values in old items for purge purposes.
+ for (let key of Object.keys(aOldItem)) {
+ let value = aOldItem[key];
+ // ignore keys that start with underscores, they are private and not
+ // persisted by our attribute mechanism. (they are directly handled by
+ // the object implementation.)
+ if (key.startsWith("_"))
+ continue;
+ // ignore things we saw in the new guy
+ if (key in aItem)
+ continue;
+
+ // find the attribute definition that corresponds to this key
+ let attrib = attribsByBoundName[key];
+ // if there's no attribute, that's not good, but not horrible.
+ if (attrib === undefined) {
+ continue;
+ }
+
+ // delete these from the old item, as the old item is canonical, and
+ // should no longer have these values
+ delete aOldItem[key];
+
+ if (attrib.canQuery !== true) {
+ this._log.debug("Not inserting attribute "+attrib.attributeName
+ +" into the db, since we don't plan on querying on it");
+ continue;
+ }
+
+ if (attrib.singular)
+ value = [value];
+ let attribDB = attrib.dbDef;
+ removeDBAttribs.push.apply(removeDBAttribs,
+ attribDB.convertValuesToDBAttributes(value));
+ // remove the empty set marker if there should have been one
+ if (!value.length && attrib.emptySetIsSignificant)
+ removeDBAttribs.push([GlodaDatastore.kEmptySetAttrId, attribDB.id]);
+ }
+
+ aItem._jsonText = JSON.stringify(jsonDict);
+ this._log.debug(" json text: " + aItem._jsonText);
+
+ if (aIsRecordNew) {
+ this._log.debug(" inserting item");
+ itemNounDef.objInsert.call(itemNounDef.datastore, aItem);
+ }
+ else {
+ this._log.debug(" updating item");
+ itemNounDef.objUpdate.call(itemNounDef.datastore, aItem);
+ }
+
+ this._log.debug(" adjusting attributes, add: " + addDBAttribs + " rem: " +
+ removeDBAttribs);
+ itemNounDef.dbAttribAdjuster.call(itemNounDef.datastore, aItem,
+ addDBAttribs, removeDBAttribs);
+
+ if (!aIsConceptuallyNew && ("_declone" in aOldItem))
+ aOldItem._declone(aItem);
+
+ // Cache ramifications...
+ if (aDoCache === undefined || aDoCache) {
+ if (aIsConceptuallyNew)
+ GlodaCollectionManager.itemsAdded(aItem.NOUN_ID, [aItem]);
+ else
+ GlodaCollectionManager.itemsModified(aOldItem.NOUN_ID, [aOldItem]);
+ }
+
+ this._log.debug(" done grokking.");
+
+ yield this.kWorkDone;
+ },
+
+ /**
+ * Processes a list of noun instances for their score within a given context.
+ * This is primarily intended for use by search ranking mechanisms, but could
+ * be used elsewhere too. (It does, however, depend on the complicity of the
+ * score method implementations to not get confused.)
+ *
+ * @param aItems The non-empty list of items to score.
+ * @param aContext A noun-specific dictionary that we just pass to the funcs.
+ * @param aExtraScoreFuncs A list of extra scoring functions to apply.
+ * @returns A list of integer scores equal in length to aItems.
+ */
+ scoreNounItems: function gloda_ns_grokNounItem(aItems, aContext,
+ aExtraScoreFuncs) {
+ let scores = [];
+ // bail if there is nothing to score
+ if (!aItems.length)
+ return scores;
+
+ let itemNounDef = aItems[0].NOUN_DEF;
+ if (aExtraScoreFuncs == null)
+ aExtraScoreFuncs = [];
+
+ for (let item of aItems) {
+ let score = 0;
+ let attrProviders = this._attrProviderOrderByNoun[itemNounDef.id];
+ for (let iProvider = 0; iProvider < attrProviders.length; iProvider++) {
+ let provider = attrProviders[iProvider];
+ if (provider.score)
+ score += provider.score(item);
+ }
+ for (let [, extraScoreFunc] in Iterator(aExtraScoreFuncs))
+ score += extraScoreFunc(item, aContext);
+ scores.push(score);
+ }
+
+ return scores;
+ }
+};
+
+/* and initialize the Gloda object/NS before we return... */
+try {
+ Gloda._init();
+}
+catch (ex) {
+ Gloda._log.debug("Exception during Gloda init (" + ex.fileName + ":" +
+ ex.lineNumber + "): " + ex);
+};
+/* but don't forget that we effectively depend on everybody.js too, and
+ currently on our importer to be importing that if they need us fully armed
+ and operational. */
diff --git a/mailnews/db/gloda/modules/index_ab.js b/mailnews/db/gloda/modules/index_ab.js
new file mode 100644
index 000000000..299733275
--- /dev/null
+++ b/mailnews/db/gloda/modules/index_ab.js
@@ -0,0 +1,287 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['GlodaABIndexer', 'GlodaABAttrs'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/collection.js");
+Cu.import("resource:///modules/gloda/datastore.js");
+Cu.import("resource:///modules/gloda/gloda.js");
+Cu.import("resource:///modules/gloda/indexer.js");
+Cu.import("resource:///modules/gloda/log4moz.js");
+Cu.import("resource:///modules/gloda/noun_freetag.js");
+Cu.import("resource:///modules/gloda/utils.js");
+Cu.import("resource:///modules/mailServices.js");
+
+
+var GlodaABIndexer = {
+ _log: null,
+
+ name: "index_ab",
+ enable: function() {
+ if (this._log == null)
+ this._log = Log4Moz.repository.getLogger("gloda.index_ab");
+
+ MailServices.ab.addAddressBookListener(this,
+ Ci.nsIAbListener.itemAdded |
+ Ci.nsIAbListener.itemChanged |
+ Ci.nsIAbListener.directoryItemRemoved);
+ },
+
+ disable: function() {
+ MailServices.ab.removeAddressBookListener(this);
+ },
+
+ // it's a getter so we can reference 'this'
+ get workers() {
+ return [
+ ["ab-card", {
+ worker: this._worker_index_card,
+ }],
+ ];
+ },
+
+ _worker_index_card: function*(aJob, aCallbackHandle) {
+ let card = aJob.id;
+
+ if (card.primaryEmail) {
+ // load the identity
+ let query = Gloda.newQuery(Gloda.NOUN_IDENTITY);
+ query.kind("email");
+ // we currently normalize all e-mail addresses to be lowercase
+ query.value(card.primaryEmail.toLowerCase());
+ let identityCollection = query.getCollection(aCallbackHandle);
+ yield Gloda.kWorkAsync;
+
+ if (identityCollection.items.length) {
+ let identity = identityCollection.items[0];
+ // force the identity to know it has an associated ab card.
+ identity._hasAddressBookCard = true;
+
+ this._log.debug("Found identity, processing card.");
+ yield aCallbackHandle.pushAndGo(
+ Gloda.grokNounItem(identity.contact, {card: card}, false, false,
+ aCallbackHandle));
+ this._log.debug("Done processing card.");
+ }
+ }
+
+ yield GlodaIndexer.kWorkDone;
+ },
+
+ initialSweep: function() {
+ },
+
+ /* ------ nsIAbListener ------ */
+ /**
+ * When an address book card is added, update the cached GlodaIdentity
+ * object's cached idea of whether the identity has an ab card.
+ */
+ onItemAdded: function ab_indexer_onItemAdded(aParentDir, aItem) {
+ if (!(aItem instanceof Ci.nsIAbCard))
+ return;
+
+ this._log.debug("Received Card Add Notification");
+ let identity = GlodaCollectionManager.cacheLookupOneByUniqueValue(
+ Gloda.NOUN_IDENTITY, "email@" + aItem.primaryEmail.toLowerCase());
+ if (identity)
+ identity._hasAddressBookCard = true;
+ },
+ /**
+ * When an address book card is added, update the cached GlodaIdentity
+ * object's cached idea of whether the identity has an ab card.
+ */
+ onItemRemoved: function ab_indexer_onItemRemoved(aParentDir, aItem) {
+ if (!(aItem instanceof Ci.nsIAbCard))
+ return;
+
+ this._log.debug("Received Card Removal Notification");
+ let identity = GlodaCollectionManager.cacheLookupOneByUniqueValue(
+ Gloda.NOUN_IDENTITY, "email@" + aItem.primaryEmail.toLowerCase());
+ if (identity)
+ identity._hasAddressBookCard = false;
+
+ },
+ onItemPropertyChanged: function ab_indexer_onItemPropertyChanged(aItem,
+ aProperty, aOldValue, aNewValue) {
+ if (aProperty == null && aItem instanceof Ci.nsIAbCard) {
+ this._log.debug("Received Card Change Notification");
+
+ let card = aItem; // instanceof already QueryInterface'd for us.
+ let job = new IndexingJob("ab-card", card);
+ GlodaIndexer.indexJob(job);
+ }
+ }
+};
+GlodaIndexer.registerIndexer(GlodaABIndexer);
+
+var GlodaABAttrs = {
+ providerName: "gloda.ab_attr",
+ _log: null,
+
+ init: function() {
+ this._log = Log4Moz.repository.getLogger("gloda.abattrs");
+
+ try {
+ this.defineAttributes();
+ }
+ catch (ex) {
+ this._log.error("Error in init: " + ex);
+ throw ex;
+ }
+ },
+
+ defineAttributes: function() {
+ /* ***** Contacts ***** */
+ this._attrIdentityContact = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "identities",
+ singular: false,
+ special: Gloda.kSpecialColumnChildren,
+ //specialColumnName: "contactID",
+ storageAttributeName: "_identities",
+ subjectNouns: [Gloda.NOUN_CONTACT],
+ objectNoun: Gloda.NOUN_IDENTITY,
+ }); // tested-by: test_attributes_fundamental
+ this._attrContactName = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "name",
+ singular: true,
+ special: Gloda.kSpecialString,
+ specialColumnName: "name",
+ subjectNouns: [Gloda.NOUN_CONTACT],
+ objectNoun: Gloda.NOUN_STRING,
+ canQuery: true,
+ }); // tested-by: test_attributes_fundamental
+ this._attrContactPopularity = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "popularity",
+ singular: true,
+ special: Gloda.kSpecialColumn,
+ specialColumnName: "popularity",
+ subjectNouns: [Gloda.NOUN_CONTACT],
+ objectNoun: Gloda.NOUN_NUMBER,
+ canQuery: true,
+ }); // not-tested
+ this._attrContactFrecency = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "frecency",
+ singular: true,
+ special: Gloda.kSpecialColumn,
+ specialColumnName: "frecency",
+ subjectNouns: [Gloda.NOUN_CONTACT],
+ objectNoun: Gloda.NOUN_NUMBER,
+ canQuery: true,
+ }); // not-tested
+
+ /* ***** Identities ***** */
+ this._attrIdentityContact = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrDerived,
+ attributeName: "contact",
+ singular: true,
+ special: Gloda.kSpecialColumnParent,
+ specialColumnName: "contactID", // the column in the db
+ idStorageAttributeName: "_contactID",
+ valueStorageAttributeName: "_contact",
+ subjectNouns: [Gloda.NOUN_IDENTITY],
+ objectNoun: Gloda.NOUN_CONTACT,
+ canQuery: true,
+ }); // tested-by: test_attributes_fundamental
+ this._attrIdentityKind = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "kind",
+ singular: true,
+ special: Gloda.kSpecialString,
+ specialColumnName: "kind",
+ subjectNouns: [Gloda.NOUN_IDENTITY],
+ objectNoun: Gloda.NOUN_STRING,
+ canQuery: true,
+ }); // tested-by: test_attributes_fundamental
+ this._attrIdentityValue = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrFundamental,
+ attributeName: "value",
+ singular: true,
+ special: Gloda.kSpecialString,
+ specialColumnName: "value",
+ subjectNouns: [Gloda.NOUN_IDENTITY],
+ objectNoun: Gloda.NOUN_STRING,
+ canQuery: true,
+ }); // tested-by: test_attributes_fundamental
+
+ /* ***** Contact Meta ***** */
+ // Freeform tags; not explicit like thunderbird's fundamental tags.
+ // we differentiate for now because of fundamental implementation
+ // differences.
+ this._attrFreeTag = Gloda.defineAttribute({
+ provider: this,
+ extensionName: Gloda.BUILT_IN,
+ attributeType: Gloda.kAttrExplicit,
+ attributeName: "freetag",
+ bind: true,
+ bindName: "freeTags",
+ singular: false,
+ subjectNouns: [Gloda.NOUN_CONTACT],
+ objectNoun: Gloda.lookupNoun("freetag"),
+ parameterNoun: null,
+ canQuery: true,
+ }); // not-tested
+ // we need to find any existing bound freetag attributes, and use them to
+ // populate to FreeTagNoun's understanding
+ if ("parameterBindings" in this._attrFreeTag) {
+ for (let freeTagName in this._attrFreeTag.parameterBindings) {
+ this._log.debug("Telling FreeTagNoun about: " + freeTagName);
+ FreeTagNoun.getFreeTag(freeTagName);
+ }
+ }
+ },
+
+ process: function*(aContact, aRawReps, aIsNew, aCallbackHandle) {
+ let card = aRawReps.card;
+ if (aContact.NOUN_ID != Gloda.NOUN_CONTACT) {
+ this._log.warn("Somehow got a non-contact: " + aContact);
+ return; // this will produce an exception; we like.
+ }
+
+ // update the name
+ if (card.displayName && card.displayName != aContact.name)
+ aContact.name = card.displayName;
+
+ aContact.freeTags = [];
+
+ let tags = null;
+ try {
+ tags = card.getProperty("Categories", null);
+ } catch (ex) {
+ this._log.error("Problem accessing property: " + ex);
+ }
+ if (tags) {
+ for (let tagName of tags.split(",")) {
+ tagName = tagName.trim();
+ if (tagName) {
+ aContact.freeTags.push(FreeTagNoun.getFreeTag(tagName));
+ }
+ }
+ }
+
+ yield Gloda.kWorkDone;
+ }
+};
diff --git a/mailnews/db/gloda/modules/index_msg.js b/mailnews/db/gloda/modules/index_msg.js
new file mode 100644
index 000000000..d4d7a8ceb
--- /dev/null
+++ b/mailnews/db/gloda/modules/index_msg.js
@@ -0,0 +1,3334 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/*
+ * This file currently contains a fairly general implementation of asynchronous
+ * indexing with a very explicit message indexing implementation. As gloda
+ * will eventually want to index more than just messages, the message-specific
+ * things should ideally lose their special hold on this file. This will
+ * benefit readability/size as well.
+ */
+
+this.EXPORTED_SYMBOLS = ['GlodaMsgIndexer'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/iteratorUtils.jsm");
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource:///modules/MailUtils.js");
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+Cu.import("resource:///modules/gloda/utils.js");
+Cu.import("resource:///modules/gloda/datastore.js");
+Cu.import("resource:///modules/gloda/datamodel.js");
+Cu.import("resource:///modules/gloda/gloda.js");
+Cu.import("resource:///modules/gloda/collection.js");
+Cu.import("resource:///modules/gloda/connotent.js");
+
+Cu.import("resource:///modules/gloda/indexer.js");
+
+Cu.import("resource:///modules/gloda/mimemsg.js");
+
+XPCOMUtils.defineLazyServiceGetter(this, "atomService",
+ "@mozilla.org/atom-service;1",
+ "nsIAtomService");
+
+// Components.results does not have mailnews error codes!
+var NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE = 0x80550005;
+
+var GLODA_MESSAGE_ID_PROPERTY = "gloda-id";
+/**
+ * Message header property to track dirty status; one of
+ * |GlodaIndexer.kMessageClean|, |GlodaIndexer.kMessageDirty|,
+ * |GlodaIndexer.kMessageFilthy|.
+ */
+var GLODA_DIRTY_PROPERTY = "gloda-dirty";
+
+/**
+ * The sentinel GLODA_MESSAGE_ID_PROPERTY value indicating that a message fails
+ * to index and we should not bother trying again, at least not until a new
+ * release is made.
+ *
+ * This should ideally just flip between 1 and 2, with GLODA_OLD_BAD_MESSAGE_ID
+ * flipping in the other direction. If we start having more trailing badness,
+ * _indexerGetEnumerator and GLODA_OLD_BAD_MESSAGE_ID will need to be altered.
+ *
+ * When flipping this, be sure to update glodaTestHelper.js's copy.
+ */
+var GLODA_BAD_MESSAGE_ID = 2;
+/**
+ * The gloda id we used to use to mark messages as bad, but now should be
+ * treated as eligible for indexing. This is only ever used for consideration
+ * when creating msg header enumerators with `_indexerGetEnumerator` which
+ * means we only will re-index such messages in an indexing sweep. Accordingly
+ * event-driven indexing will still treat such messages as unindexed (and
+ * unindexable) until an indexing sweep picks them up.
+ */
+var GLODA_OLD_BAD_MESSAGE_ID = 1;
+var GLODA_FIRST_VALID_MESSAGE_ID = 32;
+
+var JUNK_SCORE_PROPERTY = "junkscore";
+var JUNK_SPAM_SCORE_STR = Ci.nsIJunkMailPlugin.IS_SPAM_SCORE.toString();
+var JUNK_HAM_SCORE_STR = Ci.nsIJunkMailPlugin.IS_HAM_SCORE.toString();
+
+var nsIArray = Ci.nsIArray;
+var nsIMsgFolder = Ci.nsIMsgFolder;
+var nsIMsgLocalMailFolder = Ci.nsIMsgLocalMailFolder;
+var nsIMsgImapMailFolder = Ci.nsIMsgImapMailFolder;
+var nsIMsgDBHdr = Ci.nsIMsgDBHdr;
+var nsMsgFolderFlags = Ci.nsMsgFolderFlags;
+var nsMsgMessageFlags = Ci.nsMsgMessageFlags;
+var nsMsgProcessingFlags = Ci.nsMsgProcessingFlags;
+
+/**
+ * The processing flags that tell us that a message header has not yet been
+ * reported to us via msgsClassified. If it has one of these flags, it is
+ * still being processed.
+ */
+var NOT_YET_REPORTED_PROCESSING_FLAGS =
+ nsMsgProcessingFlags.NotReportedClassified |
+ nsMsgProcessingFlags.ClassifyJunk;
+
+// for list comprehension fun
+function* range(begin, end) {
+ for (let i = begin; i < end; ++i) {
+ yield i;
+ }
+}
+
+/**
+ * We do not set properties on the messages until we perform a DB commit; this
+ * helper class tracks messages that we have indexed but are not yet marked
+ * as such on their header.
+ */
+var PendingCommitTracker = {
+ /**
+ * Maps message URIs to their gloda ids.
+ *
+ * I am not entirely sure why I chose the URI for the key rather than
+ * gloda folder ID + message key. Most likely it was to simplify debugging
+ * since the gloda folder ID is opaque while the URI is very informative. It
+ * is also possible I was afraid of IMAP folder renaming triggering a UID
+ * renumbering?
+ */
+ _indexedMessagesPendingCommitByKey: {},
+ /**
+ * Map from the pending commit gloda id to a tuple of [the corresponding
+ * message header, dirtyState].
+ */
+ _indexedMessagesPendingCommitByGlodaId: {},
+ /**
+ * Do we have a post-commit handler registered with this transaction yet?
+ */
+ _pendingCommit: false,
+
+ /**
+ * The function gets called when the commit actually happens to flush our
+ * message id's.
+ *
+ * It is very possible that by the time this call happens we have left the
+ * folder and nulled out msgDatabase on the folder. Since nulling it out
+ * is what causes the commit, if we set the headers here without somehow
+ * forcing a commit, we will lose. Badly.
+ * Accordingly, we make a list of all the folders that the headers belong to
+ * as we iterate, make sure to re-attach their msgDatabase before forgetting
+ * the headers, then make sure to zero the msgDatabase again, triggering a
+ * commit. If there were a way to directly get the nsIMsgDatabase from the
+ * header we could do that and call commit directly. We don't track
+ * databases along with the headers since the headers can change because of
+ * moves and that would increase the number of moving parts.
+ */
+ _commitCallback: function PendingCommitTracker_commitCallback() {
+ let foldersByURI = {};
+ let lastFolder = null;
+
+ for (let glodaId in
+ PendingCommitTracker._indexedMessagesPendingCommitByGlodaId) {
+ let [msgHdr, dirtyState] =
+ PendingCommitTracker._indexedMessagesPendingCommitByGlodaId[glodaId];
+ // Mark this message as indexed.
+ // It's conceivable the database could have gotten blown away, in which
+ // case the message headers are going to throw exceptions when we try
+ // and touch them. So we wrap this in a try block that complains about
+ // this unforeseen circumstance. (noteFolderDatabaseGettingBlownAway
+ // should have been called and avoided this situation in all known
+ // situations.)
+ try {
+ let curGlodaId = msgHdr.getUint32Property(GLODA_MESSAGE_ID_PROPERTY);
+ if (curGlodaId != glodaId)
+ msgHdr.setUint32Property(GLODA_MESSAGE_ID_PROPERTY, glodaId);
+ let headerDirty = msgHdr.getUint32Property(GLODA_DIRTY_PROPERTY);
+ if (headerDirty != dirtyState)
+ msgHdr.setUint32Property(GLODA_DIRTY_PROPERTY, dirtyState);
+
+ // Make sure this folder is in our foldersByURI map.
+ if (lastFolder == msgHdr.folder)
+ continue;
+ lastFolder = msgHdr.folder;
+ let folderURI = lastFolder.URI;
+ if (!(folderURI in foldersByURI))
+ foldersByURI[folderURI] = lastFolder;
+ }
+ catch (ex) {
+ GlodaMsgIndexer._log.error(
+ "Exception while attempting to mark message with gloda state after" +
+ "db commit", ex);
+ }
+ }
+
+ // it is vitally important to do this before we forget about the headers!
+ for (let uri in foldersByURI) {
+ let folder = foldersByURI[uri];
+ // This will not cause a parse. The database is in-memory since we have
+ // a header that belongs to it. This just causes the folder to
+ // re-acquire a reference from the database manager.
+ let ignoredDb = folder.msgDatabase;
+ // And this will cause a commit. (And must be done since we don't want
+ // to cause a leak.)
+ folder.msgDatabase = null;
+ }
+
+ PendingCommitTracker._indexedMessagesPendingCommitByGlodaId = {};
+ PendingCommitTracker._indexedMessagesPendingCommitByKey = {};
+
+ PendingCommitTracker._pendingCommit = false;
+ },
+
+ /**
+ * Track a message header that should be marked with the given gloda id when
+ * the database commits.
+ */
+ track: function PendingCommitTracker_track(aMsgHdr, aGlodaId) {
+ let pendingKey = aMsgHdr.folder.URI + "#" + aMsgHdr.messageKey;
+ this._indexedMessagesPendingCommitByKey[pendingKey] = aGlodaId;
+ this._indexedMessagesPendingCommitByGlodaId[aGlodaId] =
+ [aMsgHdr, GlodaMsgIndexer.kMessageClean];
+
+ if (!this._pendingCommit) {
+ GlodaDatastore.runPostCommit(this._commitCallback);
+ this._pendingCommit = true;
+ }
+ },
+
+ /**
+ * Get the current state of a message header given that we cannot rely on just
+ * looking at the header's properties because we defer setting those
+ * until the SQLite commit happens.
+ *
+ * @return Tuple of [gloda id, dirty status].
+ */
+ getGlodaState:
+ function PendingCommitTracker_getGlodaState(aMsgHdr) {
+ // If it's in the pending commit table, then the message is basically
+ // clean. Return that info.
+ let pendingKey = aMsgHdr.folder.URI + "#" + aMsgHdr.messageKey;
+ if (pendingKey in this._indexedMessagesPendingCommitByKey) {
+ let glodaId =
+ PendingCommitTracker._indexedMessagesPendingCommitByKey[pendingKey];
+ return [glodaId, this._indexedMessagesPendingCommitByGlodaId[glodaId][1]];
+ }
+ else {
+ // Otherwise the header's concept of state is correct.
+ let glodaId = aMsgHdr.getUint32Property(GLODA_MESSAGE_ID_PROPERTY);
+ let glodaDirty = aMsgHdr.getUint32Property(GLODA_DIRTY_PROPERTY);
+ return [glodaId, glodaDirty];
+ }
+ },
+
+ /**
+ * Update our structure to reflect moved headers. Moves are currently
+ * treated as weakly interesting and do not require a reindexing
+ * although collections will get notified. So our job is to to fix-up
+ * the pending commit information if the message has a pending commit.
+ */
+ noteMove: function PendingCommitTracker_noteMove(aOldHdr, aNewHdr) {
+ let oldKey = aOldHdr.folder.URI + "#" + aOldHdr.messageKey;
+ if (!(oldKey in this._indexedMessagesPendingCommitByKey))
+ return;
+
+ let glodaId = this._indexedMessagesPendingCommitByKey[oldKey];
+ delete this._indexedMessagesPendingCommitByKey[oldKey];
+
+ let newKey = aNewHdr.folder.URI + "#" + aNewHdr.messageKey;
+ this._indexedMessagesPendingCommitByKey[newKey] = glodaId;
+
+ // only clobber the header, not the dirty state
+ this._indexedMessagesPendingCommitByGlodaId[glodaId][0] = aNewHdr;
+ },
+
+ /**
+ * A blind move is one where we have the source header but not the destination
+ * header. This happens for IMAP messages that do not involve offline fake
+ * headers.
+ * XXX Since IMAP moves will propagate the gloda-id/gloda-dirty bits for us,
+ * we could detect the other side of the move when it shows up as a
+ * msgsClassified event and restore the mapping information. Since the
+ * offline fake header case should now cover the bulk of IMAP move
+ * operations, we probably do not need to pursue this.
+ *
+ * We just re-dispatch to noteDirtyHeader because we can't do anything more
+ * clever.
+ */
+ noteBlindMove: function PendingCommitTracker_noteBlindMove(aOldHdr) {
+ this.noteDirtyHeader(aOldHdr);
+ },
+
+ /**
+ * If a message is dirty we should stop tracking it for post-commit
+ * purposes. This is not because we don't want to write to its header
+ * when we commit as much as that we want to avoid |getHeaderGlodaState|
+ * reporting that the message is clean. We could complicate our state
+ * by storing that information, but this is easier and ends up the same
+ * in the end.
+ */
+ noteDirtyHeader: function PendingCommitTracker_noteDirtyHeader(aMsgHdr) {
+ let pendingKey = aMsgHdr.folder.URI + "#" + aMsgHdr.messageKey;
+ if (!(pendingKey in this._indexedMessagesPendingCommitByKey))
+ return;
+
+ // (It is important that we get the gloda id from our own structure!)
+ let glodaId = this._indexedMessagesPendingCommitByKey[pendingKey];
+ this._indexedMessagesPendingCommitByGlodaId[glodaId][1] =
+ GlodaMsgIndexer.kMessageDirty;
+ },
+
+ /**
+ * Sometimes a folder database gets blown away. This happens for one of two
+ * expected reasons right now:
+ * - Folder compaction.
+ * - Explicit reindexing of a folder via the folder properties "rebuild index"
+ * button.
+ *
+ * When this happens, we are basically out of luck and need to discard
+ * everything about the folder. The good news is that the folder compaction
+ * pass is clever enough to re-establish the linkages that are being lost
+ * when we drop these things on the floor. Reindexing of a folder is not
+ * clever enough to deal with this but is an exceptional case of last resort
+ * (the user should not normally be performing a reindex as part of daily
+ * operation), so we accept that messages may be redundantly indexed.
+ */
+ noteFolderDatabaseGettingBlownAway:
+ function PendingCommitTracker_noteFolderDatabaseGettingBlownAway(
+ aMsgFolder) {
+ let uri = aMsgFolder.URI + "#";
+ for (let key in Iterator(this._indexedMessagesPendingCommitByKey, true)) {
+ // this is not as efficient as it could be, but compaction is relatively
+ // rare and the number of pending headers is generally going to be
+ // small.
+ if (key.indexOf(uri) == 0) {
+ delete this._indexedMessagesPendingCommitByKey[key];
+ }
+ }
+ },
+};
+
+/**
+ * This callback handles processing the asynchronous query results of
+ * |GlodaMsgIndexer.getMessagesByMessageID|.
+ */
+function MessagesByMessageIdCallback(aMsgIDToIndex, aResults,
+ aCallback, aCallbackThis) {
+ this.msgIDToIndex = aMsgIDToIndex;
+ this.results = aResults;
+ this.callback = aCallback;
+ this.callbackThis = aCallbackThis;
+}
+
+MessagesByMessageIdCallback.prototype = {
+ _log: Log4Moz.repository.getLogger("gloda.index_msg.mbm"),
+
+ onItemsAdded: function gloda_ds_mbmi_onItemsAdded(aItems, aCollection) {
+ // just outright bail if we are shutdown
+ if (GlodaDatastore.datastoreIsShutdown)
+ return;
+
+ this._log.debug("getting results...");
+ for (let message of aItems) {
+ this.results[this.msgIDToIndex[message.headerMessageID]].push(message);
+ }
+ },
+ onItemsModified: function () {},
+ onItemsRemoved: function () {},
+ onQueryCompleted: function gloda_ds_mbmi_onQueryCompleted(aCollection) {
+ // just outright bail if we are shutdown
+ if (GlodaDatastore.datastoreIsShutdown)
+ return;
+
+ if (this._log.level <= Log4Moz.Level.Debug)
+ this._log.debug("query completed, notifying... " + this.results);
+
+ this.callback.call(this.callbackThis, this.results);
+ }
+};
+
+
+/**
+ * The message indexer!
+ *
+ * === Message Indexing Strategy
+ * To these ends, we implement things like so:
+ *
+ * Mesage State Tracking
+ * - We store a property on all indexed headers indicating their gloda message
+ * id. This allows us to tell whether a message is indexed from the header,
+ * without having to consult the SQL database.
+ * - When we receive an event that indicates that a message's meta-data has
+ * changed and gloda needs to re-index the message, we set a property on the
+ * header that indicates the message is dirty. This property can indicate
+ * that the message needs to be re-indexed but the gloda-id is valid (dirty)
+ * or that the message's gloda-id is invalid (filthy) because the gloda
+ * database has been blown away.
+ * - We track whether a folder is up-to-date on our GlodaFolder representation
+ * using a concept of dirtiness, just like messages. Like messages, a folder
+ * can be dirty or filthy. A dirty folder has at least one dirty message in
+ * it which means we should scan the folder. A filthy folder means that
+ * every message in the folder should be considered filthy. Folders start
+ * out filthy when Gloda is first told about them indicating we cannot
+ * trust any of the gloda-id's in the folders. Filthy folders are downgraded
+ * to dirty folders after we mark all of the headers with gloda-id's filthy.
+ *
+ * Indexing Message Control
+ * - We index the headers of all IMAP messages. We index the bodies of all IMAP
+ * messages that are offline. We index all local messages. We plan to avoid
+ * indexing news messages.
+ * - We would like a way to express desires about indexing that either don't
+ * confound offline storage with indexing, or actually allow some choice.
+ *
+ * Indexing Messages
+ * - We have two major modes of indexing: sweep and event-driven. When we
+ * start up we kick off an indexing sweep. We use event-driven indexing
+ * as we receive events for eligible messages, but if we get too many
+ * events we start dropping them on the floor and just flag that an indexing
+ * sweep is required.
+ * - The sweep initiates folder indexing jobs based on the priorities assigned
+ * to folders. Folder indexing uses a filtered message enumerator to find
+ * messages that need to be indexed, minimizing wasteful exposure of message
+ * headers to XPConnect that we would not end up indexing.
+ * - For local folders, we use GetDatabaseWithReparse to ensure that the .msf
+ * file exists. For IMAP folders, we simply use GetDatabase because we know
+ * the auto-sync logic will make sure that the folder is up-to-date and we
+ * want to avoid creating problems through use of updateFolder.
+ *
+ * Junk Mail
+ * - We do not index junk. We do not index messages until the junk/non-junk
+ * determination has been made. If a message gets marked as junk, we act like
+ * it was deleted.
+ * - We know when a message is actively queued for junk processing thanks to
+ * folder processing flags. nsMsgDBFolder::CallFilterPlugins does this
+ * prior to initiating spam processing. Unfortunately, this method does not
+ * get called until after we receive the notification about the existence of
+ * the header. How long after can vary on different factors. The longest
+ * delay is in the IMAP case where there is a filter that requires the
+ * message body to be present; the method does not get called until all the
+ * bodies are downloaded.
+ *
+ */
+var GlodaMsgIndexer = {
+ /**
+ * A partial attempt to generalize to support multiple databases. Each
+ * database would have its own datastore would have its own indexer. But
+ * we rather inter-mingle our use of this field with the singleton global
+ * GlodaDatastore.
+ */
+ _datastore: GlodaDatastore,
+ _log: Log4Moz.repository.getLogger("gloda.index_msg"),
+
+ _junkService: MailServices.junk,
+
+ name: "index_msg",
+ /**
+ * Are we enabled, read: are we processing change events?
+ */
+ _enabled: false,
+ get enabled() { return this._enabled; },
+
+ enable: function msg_indexer_enable() {
+ // initialize our listeners' this pointers
+ this._databaseAnnouncerListener.indexer = this;
+ this._msgFolderListener.indexer = this;
+
+ // register for:
+ // - folder loaded events, so we know when getDatabaseWithReparse has
+ // finished updating the index/what not (if it was't immediately
+ // available)
+ // - property changes (so we know when a message's read/starred state have
+ // changed.)
+ this._folderListener._init(this);
+ MailServices.mailSession.AddFolderListener(this._folderListener,
+ Ci.nsIFolderListener.intPropertyChanged |
+ Ci.nsIFolderListener.propertyFlagChanged |
+ Ci.nsIFolderListener.event);
+
+ MailServices.mfn.addListener(this._msgFolderListener,
+ // note: intentionally no msgAdded notification is requested.
+ Ci.nsIMsgFolderNotificationService.msgsClassified |
+ Ci.nsIMsgFolderNotificationService.msgsDeleted |
+ Ci.nsIMsgFolderNotificationService.msgsMoveCopyCompleted |
+ Ci.nsIMsgFolderNotificationService.msgKeyChanged |
+ Ci.nsIMsgFolderNotificationService.folderAdded |
+ Ci.nsIMsgFolderNotificationService.folderDeleted |
+ Ci.nsIMsgFolderNotificationService.folderMoveCopyCompleted |
+ Ci.nsIMsgFolderNotificationService.folderRenamed |
+ Ci.nsIMsgFolderNotificationService.itemEvent);
+
+ this._enabled = true;
+
+ this._considerSchemaMigration();
+
+ this._log.info("Event-Driven Indexing is now " + this._enabled);
+ },
+ disable: function msg_indexer_disable() {
+ // remove FolderLoaded notification listener
+ MailServices.mailSession.RemoveFolderListener(this._folderListener);
+
+ MailServices.mfn.removeListener(this._msgFolderListener);
+
+ this._indexerLeaveFolder(); // nop if we aren't "in" a folder
+
+ this._enabled = false;
+
+ this._log.info("Event-Driven Indexing is now " + this._enabled);
+ },
+
+ /**
+ * Indicates that we have pending deletions to process, meaning that there
+ * are gloda message rows flagged for deletion. If this value is a boolean,
+ * it means the value is known reliably. If this value is null, it means
+ * that we don't know, likely because we have started up and have not checked
+ * the database.
+ */
+ pendingDeletions: null,
+
+ /**
+ * The message (or folder state) is believed up-to-date.
+ */
+ kMessageClean: 0,
+ /**
+ * The message (or folder) is known to not be up-to-date. In the case of
+ * folders, this means that some of the messages in the folder may be dirty.
+ * However, because of the way our indexing works, it is possible there may
+ * actually be no dirty messages in a folder. (We attempt to process
+ * messages in an event-driven fashion for a finite number of messages, but
+ * because we can quit without completing processing of the queue, we need to
+ * mark the folder dirty, just-in-case.) (We could do some extra leg-work
+ * and do a better job of marking the folder clean again.)
+ */
+ kMessageDirty: 1,
+ /**
+ * We have not indexed the folder at all, but messages in the folder think
+ * they are indexed. We downgrade the folder to just kMessageDirty after
+ * marking all the messages in the folder as dirty. We do this so that if we
+ * have to stop indexing the folder we can still build on our progress next
+ * time we enter the folder.
+ * We mark all folders filthy when (re-)creating the database because there
+ * may be previous state left over from an earlier database.
+ */
+ kMessageFilthy: 2,
+
+ /**
+ * A message addition job yet to be (completely) processed. Since message
+ * addition events come to us one-by-one, in order to aggregate them into a
+ * job, we need something like this. It's up to the indexing loop to
+ * decide when to null this out; it can either do it when it first starts
+ * processing it, or when it has processed the last thing. It's really a
+ * question of whether we want retrograde motion in the folder progress bar
+ * or the message progress bar.
+ */
+ _pendingAddJob: null,
+
+ /**
+ * The number of messages that we should queue for processing before letting
+ * them fall on the floor and relying on our folder-walking logic to ensure
+ * that the messages are indexed.
+ * The reason we allow for queueing messages in an event-driven fashion is
+ * that once we have reached a steady-state, it is preferable to be able to
+ * deal with new messages and modified meta-data in a prompt fasion rather
+ * than having to (potentially) walk every folder in the system just to find
+ * the message that the user changed the tag on.
+ */
+ _indexMaxEventQueueMessages: 20,
+
+ /**
+ * Unit testing hook to get us to emit additional logging that verges on
+ * inane for general usage but is helpful in unit test output to get a lay
+ * of the land and for paranoia reasons.
+ */
+ _unitTestSuperVerbose: false,
+
+ /** The GlodaFolder corresponding to the folder we are indexing. */
+ _indexingGlodaFolder: null,
+ /** The nsIMsgFolder we are currently indexing. */
+ _indexingFolder: null,
+ /** The nsIMsgDatabase we are currently indexing. */
+ _indexingDatabase: null,
+ /**
+ * The iterator we are using to iterate over the headers in
+ * this._indexingDatabase.
+ */
+ _indexingIterator: null,
+
+ /** folder whose entry we are pending on */
+ _pendingFolderEntry: null,
+
+ // copy-down the work constants from Gloda
+ kWorkSync: Gloda.kWorkSync,
+ kWorkAsync: Gloda.kWorkAsync,
+ kWorkDone: Gloda.kWorkDone,
+ kWorkPause: Gloda.kWorkPause,
+ kWorkDoneWithResult: Gloda.kWorkDoneWithResult,
+
+ /**
+ * Async common logic that we want to deal with the given folder ID. Besides
+ * cutting down on duplicate code, this ensures that we are listening on
+ * the folder in case it tries to go away when we are using it.
+ *
+ * @return true when the folder was successfully entered, false when we need
+ * to pend on notification of updating of the folder (due to re-parsing
+ * or what have you). In the event of an actual problem, an exception
+ * will escape.
+ */
+ _indexerEnterFolder: function gloda_index_indexerEnterFolder(aFolderID) {
+ // leave the folder if we haven't explicitly left it.
+ if (this._indexingFolder !== null) {
+ this._indexerLeaveFolder();
+ }
+
+ this._indexingGlodaFolder = GlodaDatastore._mapFolderID(aFolderID);
+ this._indexingFolder = this._indexingGlodaFolder.getXPCOMFolder(
+ this._indexingGlodaFolder.kActivityIndexing);
+
+ if (this._indexingFolder)
+ this._log.debug("Entering folder: " + this._indexingFolder.URI);
+
+ try {
+ // The msf may need to be created or otherwise updated for local folders.
+ // This may require yielding until such time as the msf has been created.
+ try {
+ if (this._indexingFolder instanceof nsIMsgLocalMailFolder) {
+ this._indexingDatabase =
+ this._indexingFolder.getDatabaseWithReparse(null,
+ null);
+ }
+ // we need do nothing special for IMAP, news, or other
+ }
+ // getDatabaseWithReparse can return either NS_ERROR_NOT_INITIALIZED or
+ // NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE if the net result is that it
+ // is going to send us a notification when the reparse has completed.
+ // (note that although internally NS_MSG_ERROR_FOLDER_SUMMARY_MISSING
+ // might get flung around, it won't make it out to us, and will instead
+ // be permuted into an NS_ERROR_NOT_INITIALIZED.)
+ catch (e) {
+ if ((e.result == Cr.NS_ERROR_NOT_INITIALIZED) ||
+ (e.result == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)) {
+ // this means that we need to pend on the update; the listener for
+ // FolderLoaded events will call _indexerCompletePendingFolderEntry.
+ this._log.debug("Pending on folder load...");
+ this._pendingFolderEntry = this._indexingFolder;
+ return this.kWorkAsync;
+ } else {
+ throw e;
+ }
+ }
+ // we get an nsIMsgDatabase out of this (unsurprisingly) which
+ // explicitly inherits from nsIDBChangeAnnouncer, which has the
+ // AddListener call we want.
+ if (this._indexingDatabase == null)
+ this._indexingDatabase = this._indexingFolder.msgDatabase;
+ this._indexingDatabase.AddListener(this._databaseAnnouncerListener);
+ }
+ catch (ex) {
+ this._log.error("Problem entering folder: " +
+ (this._indexingFolder ?
+ this._indexingFolder.prettiestName : "unknown") +
+ ", skipping. Error was: " + ex.fileName + ":" +
+ ex.lineNumber + ": " + ex);
+ this._indexingGlodaFolder.indexing = false;
+ this._indexingFolder = null;
+ this._indexingGlodaFolder = null;
+ this._indexingDatabase = null;
+ this._indexingEnumerator = null;
+
+ // re-throw, we just wanted to make sure this junk is cleaned up and
+ // get localized error logging...
+ throw ex;
+ }
+
+ return this.kWorkSync;
+ },
+
+ /**
+ * If the folder was still parsing/updating when we tried to enter, then this
+ * handler will get called by the listener who got the FolderLoaded message.
+ * All we need to do is get the database reference, register a listener on
+ * the db, and retrieve an iterator if desired.
+ */
+ _indexerCompletePendingFolderEntry:
+ function gloda_indexer_indexerCompletePendingFolderEntry() {
+ this._indexingDatabase = this._indexingFolder.msgDatabase;
+ this._indexingDatabase.AddListener(this._databaseAnnouncerListener);
+ this._log.debug("...Folder Loaded!");
+
+ // the load is no longer pending; we certainly don't want more notifications
+ this._pendingFolderEntry = null;
+ // indexerEnterFolder returned kWorkAsync, which means we need to notify
+ // the callback driver to get things going again.
+ GlodaIndexer.callbackDriver();
+ },
+
+ /**
+ * Enumerate all messages in the folder.
+ */
+ kEnumAllMsgs: 0,
+ /**
+ * Enumerate messages that look like they need to be indexed.
+ */
+ kEnumMsgsToIndex: 1,
+ /**
+ * Enumerate messages that are already indexed.
+ */
+ kEnumIndexedMsgs: 2,
+
+ /**
+ * Synchronous helper to get an enumerator for the current folder (as found
+ * in |_indexingFolder|.
+ *
+ * @param aEnumKind One of |kEnumAllMsgs|, |kEnumMsgsToIndex|, or
+ * |kEnumIndexedMsgs|.
+ * @param [aAllowPreBadIds=false] Only valid for |kEnumIndexedMsgs|, tells us
+ * that we should treat message with any gloda-id as dirty, not just
+ * messages that have non-bad message id's.
+ */
+ _indexerGetEnumerator: function gloda_indexer_indexerGetEnumerator(
+ aEnumKind, aAllowPreBadIds) {
+ if (aEnumKind == this.kEnumMsgsToIndex) {
+ // We need to create search terms for messages to index. Messages should
+ // be indexed if they're indexable (local or offline and not expunged)
+ // and either: haven't been indexed, are dirty, or are marked with with
+ // a former GLODA_BAD_MESSAGE_ID that is no longer our bad marker. (Our
+ // bad marker can change on minor schema revs so that we can try and
+ // reindex those messages exactly once and without needing to go through
+ // a pass to mark them as needing one more try.)
+ // The basic search expression is:
+ // ((GLODA_MESSAGE_ID_PROPERTY Is 0) ||
+ // (GLODA_MESSAGE_ID_PROPERTY Is GLODA_OLD_BAD_MESSAGE_ID) ||
+ // (GLODA_DIRTY_PROPERTY Isnt 0)) &&
+ // (JUNK_SCORE_PROPERTY Isnt 100)
+ // If the folder !isLocal we add the terms:
+ // - if the folder is offline -- && (Status Is nsMsgMessageFlags.Offline)
+ // - && (Status Isnt nsMsgMessageFlags.Expunged)
+
+ let searchSession = Cc["@mozilla.org/messenger/searchSession;1"]
+ .createInstance(Ci.nsIMsgSearchSession);
+ let searchTerms = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ let isLocal = this._indexingFolder instanceof nsIMsgLocalMailFolder;
+
+ searchSession.addScopeTerm(Ci.nsMsgSearchScope.offlineMail,
+ this._indexingFolder);
+ let nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+ let nsMsgSearchOp = Ci.nsMsgSearchOp;
+
+ // first term: (GLODA_MESSAGE_ID_PROPERTY Is 0
+ let searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = false; // actually don't care here
+ searchTerm.beginsGrouping = true;
+ searchTerm.attrib = nsMsgSearchAttrib.Uint32HdrProperty;
+ searchTerm.op = nsMsgSearchOp.Is;
+ let value = searchTerm.value;
+ value.attrib = searchTerm.attrib;
+ value.status = 0;
+ searchTerm.value = value;
+ searchTerm.hdrProperty = GLODA_MESSAGE_ID_PROPERTY;
+ searchTerms.appendElement(searchTerm, false);
+
+ // second term: || GLODA_MESSAGE_ID_PROPERTY Is GLODA_OLD_BAD_MESSAGE_ID
+ searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = false; // OR
+ searchTerm.attrib = nsMsgSearchAttrib.Uint32HdrProperty;
+ searchTerm.op = nsMsgSearchOp.Is;
+ value = searchTerm.value;
+ value.attrib = searchTerm.attrib;
+ value.status = GLODA_OLD_BAD_MESSAGE_ID;
+ searchTerm.value = value;
+ searchTerm.hdrProperty = GLODA_MESSAGE_ID_PROPERTY;
+ searchTerms.appendElement(searchTerm, false);
+
+ // third term: || GLODA_DIRTY_PROPERTY Isnt 0 )
+ searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = false;
+ searchTerm.endsGrouping = true;
+ searchTerm.attrib = nsMsgSearchAttrib.Uint32HdrProperty;
+ searchTerm.op = nsMsgSearchOp.Isnt;
+ value = searchTerm.value;
+ value.attrib = searchTerm.attrib;
+ value.status = 0;
+ searchTerm.value = value;
+ searchTerm.hdrProperty = GLODA_DIRTY_PROPERTY;
+ searchTerms.appendElement(searchTerm, false);
+
+ // JUNK_SCORE_PROPERTY Isnt 100
+ // For symmetry with our event-driven stuff, we just directly deal with
+ // the header property.
+ searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = true;
+ searchTerm.attrib = nsMsgSearchAttrib.HdrProperty;
+ searchTerm.op = nsMsgSearchOp.Isnt;
+ value = searchTerm.value;
+ value.attrib = searchTerm.attrib;
+ value.str = JUNK_SPAM_SCORE_STR;
+ searchTerm.value = value;
+ searchTerm.hdrProperty = JUNK_SCORE_PROPERTY;
+ searchTerms.appendElement(searchTerm, false);
+
+ if (!isLocal)
+ {
+ // If the folder is offline, then the message should be too
+ if (this._indexingFolder.flags & Ci.nsMsgFolderFlags.Offline) {
+ // third term: && Status Is nsMsgMessageFlags.Offline
+ searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = true;
+ searchTerm.attrib = nsMsgSearchAttrib.MsgStatus;
+ searchTerm.op = nsMsgSearchOp.Is;
+ value = searchTerm.value;
+ value.attrib = searchTerm.attrib;
+ value.status = nsMsgMessageFlags.Offline;
+ searchTerm.value = value;
+ searchTerms.appendElement(searchTerm, false);
+ }
+
+ // fourth term: && Status Isnt nsMsgMessageFlags.Expunged
+ searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = true;
+ searchTerm.attrib = nsMsgSearchAttrib.MsgStatus;
+ searchTerm.op = nsMsgSearchOp.Isnt;
+ value = searchTerm.value;
+ value.attrib = searchTerm.attrib;
+ value.status = nsMsgMessageFlags.Expunged;
+ searchTerm.value = value;
+ searchTerms.appendElement(searchTerm, false);
+ }
+
+ this._indexingEnumerator =
+ this._indexingDatabase.getFilterEnumerator(searchTerms, true);
+ }
+ else if (aEnumKind == this.kEnumIndexedMsgs) {
+ // Enumerate only messages that are already indexed. This comes out to:
+ // ((GLODA_MESSAGE_ID_PROPERTY > GLODA_FIRST_VALID_MESSAGE_ID-1) &&
+ // (GLODA_DIRTY_PROPERTY Isnt kMessageFilthy))
+ // In English, a message is indexed if (by clause):
+ // 1) The message has a gloda-id and that gloda-id is in the valid range
+ // (and not in the bad message marker range).
+ // 2) The message has not been marked filthy (which invalidates the
+ // gloda-id.) We also assume that the folder would not have been
+ // entered at all if it was marked filthy.
+ let searchSession = Cc["@mozilla.org/messenger/searchSession;1"]
+ .createInstance(Ci.nsIMsgSearchSession);
+ let searchTerms = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+
+ searchSession.addScopeTerm(Ci.nsMsgSearchScope.offlineMail,
+ this._indexingFolder);
+ let nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+ let nsMsgSearchOp = Ci.nsMsgSearchOp;
+
+ // first term: (GLODA_MESSAGE_ID_PROPERTY > GLODA_FIRST_VALID_MESSAGE_ID-1
+ let searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = false; // actually don't care here
+ searchTerm.beginsGrouping = true;
+ searchTerm.attrib = nsMsgSearchAttrib.Uint32HdrProperty;
+ // use != 0 if we're allow pre-bad ids.
+ searchTerm.op = aAllowPreBadIds ? nsMsgSearchOp.Isnt
+ : nsMsgSearchOp.IsGreaterThan;
+ let value = searchTerm.value;
+ value.attrib = searchTerm.attrib;
+ value.status = aAllowPreBadIds ? 0 : (GLODA_FIRST_VALID_MESSAGE_ID - 1);
+ searchTerm.value = value;
+ searchTerm.hdrProperty = GLODA_MESSAGE_ID_PROPERTY;
+ searchTerms.appendElement(searchTerm, false);
+
+ // second term: && GLODA_DIRTY_PROPERTY Isnt kMessageFilthy)
+ searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = true;
+ searchTerm.endsGrouping = true;
+ searchTerm.attrib = nsMsgSearchAttrib.Uint32HdrProperty;
+ searchTerm.op = nsMsgSearchOp.Isnt;
+ value = searchTerm.value;
+ value.attrib = searchTerm.attrib;
+ value.status = this.kMessageFilthy;
+ searchTerm.value = value;
+ searchTerm.hdrProperty = GLODA_DIRTY_PROPERTY;
+ searchTerms.appendElement(searchTerm, false);
+
+ // The use-case of already indexed messages does not want them reversed;
+ // we care about seeing the message keys in order.
+ this._indexingEnumerator =
+ this._indexingDatabase.getFilterEnumerator(searchTerms, false);
+ }
+ else if (aEnumKind == this.kEnumAllMsgs) {
+ this._indexingEnumerator =
+ this._indexingDatabase.ReverseEnumerateMessages();
+ }
+ else {
+ throw new Error("Unknown enumerator type requested:" + aEnumKind);
+ }
+ },
+
+ _indexerLeaveFolder: function gloda_index_indexerLeaveFolder() {
+ if (this._indexingFolder !== null) {
+ if (this._indexingDatabase) {
+ this._indexingDatabase.Commit(Ci.nsMsgDBCommitType.kLargeCommit);
+ // remove our listener!
+ this._indexingDatabase.RemoveListener(this._databaseAnnouncerListener);
+ }
+ // let the gloda folder know we are done indexing
+ this._indexingGlodaFolder.indexing = false;
+ // null everyone out
+ this._indexingFolder = null;
+ this._indexingGlodaFolder = null;
+ this._indexingDatabase = null;
+ this._indexingEnumerator = null;
+ }
+ },
+
+ /**
+ * Event fed to us by our nsIFolderListener when a folder is loaded. We use
+ * this event to know when a folder we were trying to open to index is
+ * actually ready to be indexed. (The summary may have not existed, may have
+ * been out of date, or otherwise.)
+ *
+ * @param aFolder An nsIMsgFolder, already QI'd.
+ */
+ _onFolderLoaded: function gloda_index_onFolderLoaded(aFolder) {
+ if ((this._pendingFolderEntry !== null) &&
+ (aFolder.URI == this._pendingFolderEntry.URI))
+ this._indexerCompletePendingFolderEntry();
+ },
+
+ // it's a getter so we can reference 'this'. we could memoize.
+ get workers() {
+ return [
+ ["folderSweep", {
+ worker: this._worker_indexingSweep,
+ jobCanceled: this._cleanup_indexingSweep,
+ cleanup: this._cleanup_indexingSweep,
+ }],
+ ["folder", {
+ worker: this._worker_folderIndex,
+ recover: this._recover_indexMessage,
+ cleanup: this._cleanup_indexing,
+ }],
+ ["folderCompact", {
+ worker: this._worker_folderCompactionPass,
+ // compaction enters the folder so needs to know how to leave
+ cleanup: this._cleanup_indexing,
+ }],
+ ["message", {
+ worker: this._worker_messageIndex,
+ onSchedule: this._schedule_messageIndex,
+ jobCanceled: this._canceled_messageIndex,
+ recover: this._recover_indexMessage,
+ cleanup: this._cleanup_indexing,
+ }],
+ ["delete", {
+ worker: this._worker_processDeletes,
+ }],
+
+ ["fixMissingContacts", {
+ worker: this._worker_fixMissingContacts,
+ }],
+ ];
+ },
+
+ _schemaMigrationInitiated: false,
+ _considerSchemaMigration: function() {
+ if (!this._schemaMigrationInitiated &&
+ GlodaDatastore._actualSchemaVersion === 26) {
+ let job = new IndexingJob("fixMissingContacts", null);
+ GlodaIndexer.indexJob(job);
+ this._schemaMigrationInitiated = true;
+ }
+ },
+
+ initialSweep: function() {
+ this.indexingSweepNeeded = true;
+ },
+
+ _indexingSweepActive: false,
+ /**
+ * Indicate that an indexing sweep is desired. We kick-off an indexing
+ * sweep at start-up and whenever we receive an event-based notification
+ * that we either can't process as an event or that we normally handle
+ * during the sweep pass anyways.
+ */
+ set indexingSweepNeeded(aNeeded) {
+ if (!this._indexingSweepActive && aNeeded) {
+ let job = new IndexingJob("folderSweep", null);
+ job.mappedFolders = false;
+ GlodaIndexer.indexJob(job);
+ this._indexingSweepActive = true;
+ }
+ },
+
+ /**
+ * Performs the folder sweep, locating folders that should be indexed, and
+ * creating a folder indexing job for them, and rescheduling itself for
+ * execution after that job is completed. Once it indexes all the folders,
+ * if we believe we have deletions to process (or just don't know), it kicks
+ * off a deletion processing job.
+ *
+ * Folder traversal logic is based off the spotlight/vista indexer code; we
+ * retrieve the list of servers and folders each time want to find a new
+ * folder to index. This avoids needing to maintain a perfect model of the
+ * folder hierarchy at all times. (We may eventually want to do that, but
+ * this is sufficient and safe for now.) Although our use of dirty flags on
+ * the folders allows us to avoid tracking the 'last folder' we processed,
+ * we do so to avoid getting 'trapped' in a folder with a high rate of
+ * changes.
+ */
+ _worker_indexingSweep: function* gloda_worker_indexingSweep(aJob) {
+ if (!aJob.mappedFolders) {
+ // Walk the folders and make sure all the folders we would want to index
+ // are mapped. Build up a list of GlodaFolders as we go, so that we can
+ // sort them by their indexing priority.
+ let foldersToProcess = aJob.foldersToProcess = [];
+
+ let allFolders = MailServices.accounts.allFolders;
+ for (let folder in fixIterator(allFolders, Ci.nsIMsgFolder)) {
+ if (this.shouldIndexFolder(folder))
+ foldersToProcess.push(Gloda.getFolderForFolder(folder));
+ }
+
+ // sort the folders by priority (descending)
+ foldersToProcess.sort(function (a, b) {
+ return b.indexingPriority - a.indexingPriority;
+ });
+
+ aJob.mappedFolders = true;
+ }
+
+ // -- process the folders (in sorted order)
+ while (aJob.foldersToProcess.length) {
+ let glodaFolder = aJob.foldersToProcess.shift();
+ // ignore folders that:
+ // - have been deleted out of existence!
+ // - are not dirty/have not been compacted
+ // - are actively being compacted
+ if (glodaFolder._deleted ||
+ (!glodaFolder.dirtyStatus && !glodaFolder.compacted) ||
+ glodaFolder.compacting)
+ continue;
+
+ // If the folder is marked as compacted, give it a compaction job.
+ if (glodaFolder.compacted)
+ GlodaIndexer.indexJob(new IndexingJob("folderCompact", glodaFolder.id));
+
+ // add a job for the folder indexing if it was dirty
+ if (glodaFolder.dirtyStatus)
+ GlodaIndexer.indexJob(new IndexingJob("folder", glodaFolder.id));
+
+ // re-schedule this job (although this worker will die)
+ GlodaIndexer.indexJob(aJob);
+ yield this.kWorkDone;
+ }
+
+ // consider deletion
+ if (this.pendingDeletions || this.pendingDeletions === null)
+ GlodaIndexer.indexJob(new IndexingJob("delete", null));
+
+ // we don't have any more work to do...
+ this._indexingSweepActive = false;
+ yield this.kWorkDone;
+ },
+
+ /**
+ * The only state we need to cleanup is that there is no longer an active
+ * indexing sweep.
+ */
+ _cleanup_indexingSweep: function gloda_canceled_indexingSweep(aJob) {
+ this._indexingSweepActive = false;
+ },
+
+ /**
+ * The number of headers to look at before yielding with kWorkSync. This
+ * is for time-slicing purposes so we still yield to the UI periodically.
+ */
+ HEADER_CHECK_SYNC_BLOCK_SIZE: 25,
+
+ /**
+ * The number of headers to look at before calling
+ */
+ HEADER_CHECK_GC_BLOCK_SIZE: 256,
+
+ FOLDER_COMPACTION_PASS_BATCH_SIZE: 512,
+ /**
+ * Special indexing pass for (local) folders than have been compacted. The
+ * compaction can cause message keys to change because message keys in local
+ * folders are simply offsets into the mbox file. Accordingly, we need to
+ * update the gloda records/objects to point them at the new message key.
+ *
+ * Our general algorithm is to perform two traversals in parallel. The first
+ * is a straightforward enumeration of the message headers in the folder that
+ * apparently have been already indexed. These provide us with the message
+ * key and the "gloda-id" property.
+ * The second is a list of tuples containing a gloda message id, its current
+ * message key per the gloda database, and the message-id header. We re-fill
+ * the list with batches on-demand. This allows us to both avoid dispatching
+ * needless UPDATEs as well as deal with messages that were tracked by the
+ * PendingCommitTracker but were discarded by the compaction notification.
+ *
+ * We end up processing two streams of gloda-id's and some extra info. In
+ * the normal case we expect these two streams to line up exactly and all
+ * we need to do is update the message key if it has changed.
+ *
+ * There are a few exceptional cases where things do not line up:
+ * 1) The gloda database knows about a message that the enumerator does not
+ * know about...
+ * a) This message exists in the folder (identified using its message-id
+ * header). This means the message got indexed but PendingCommitTracker
+ * had to forget about the info when the compaction happened. We
+ * re-establish the link and track the message in PendingCommitTracker
+ * again.
+ * b) The message does not exist in the folder. This means the message got
+ * indexed, PendingCommitTracker had to forget about the info, and
+ * then the message either got moved or deleted before now. We mark
+ * the message as deleted; this allows the gloda message to be reused
+ * if the move target has not yet been indexed or purged if it already
+ * has been and the gloda message is a duplicate. And obviously, if the
+ * event that happened was actually a delete, then the delete is the
+ * right thing to do.
+ * 2) The enumerator knows about a message that the gloda database does not
+ * know about. This is unexpected and should not happen. We log a
+ * warning. We are able to differentiate this case from case #1a by
+ * retrieving the message header associated with the next gloda message
+ * (using the message-id header per 1a again). If the gloda message's
+ * message key is after the enumerator's message key then we know this is
+ * case #2. (It implies an insertion in the enumerator stream which is how
+ * we define the unexpected case.)
+ *
+ * Besides updating the database rows, we also need to make sure that
+ * in-memory representations are updated. Immediately after dispatching
+ * UPDATE changes to the database we use the same set of data to walk the
+ * live collections and update any affected messages. We are then able to
+ * discard the information. Although this means that we will have to
+ * potentially walk the live collections multiple times, unless something
+ * has gone horribly wrong, the number of collections should be reasonable
+ * and the lookups are cheap. We bias batch sizes accordingly.
+ *
+ * Because we operate based on chunks we need to make sure that when we
+ * actually deal with multiple chunks that we don't step on our own feet with
+ * our database updates. Since compaction of message key K results in a new
+ * message key K' such that K' <= K, we can reliably issue database
+ * updates for all values <= K. Which means our feet are safe no matter
+ * when we issue the update command. For maximum cache benefit, we issue
+ * our updates prior to our new query since they should still be maximally
+ * hot at that point.
+ */
+ _worker_folderCompactionPass:
+ function* gloda_worker_folderCompactionPass(aJob, aCallbackHandle) {
+ yield this._indexerEnterFolder(aJob.id);
+
+ // It's conceivable that with a folder sweep we might end up trying to
+ // compact a folder twice. Bail early in this case.
+ if (!this._indexingGlodaFolder.compacted)
+ yield this.kWorkDone;
+
+ // this is a forward enumeration (sometimes we reverse enumerate; not here)
+ this._indexerGetEnumerator(this.kEnumIndexedMsgs);
+
+ const HEADER_CHECK_SYNC_BLOCK_SIZE = this.HEADER_CHECK_SYNC_BLOCK_SIZE;
+ const HEADER_CHECK_GC_BLOCK_SIZE = this.HEADER_CHECK_GC_BLOCK_SIZE;
+ const FOLDER_COMPACTION_PASS_BATCH_SIZE =
+ this.FOLDER_COMPACTION_PASS_BATCH_SIZE;
+
+ // Tuples of [gloda id, message key, message-id header] from
+ // folderCompactionPassBlockFetch
+ let glodaIdsMsgKeysHeaderIds = [];
+ // Unpack each tuple from glodaIdsMsgKeysHeaderIds into these guys.
+ // (Initialize oldMessageKey because we use it to kickstart our query.)
+ let oldGlodaId, oldMessageKey = -1, oldHeaderMessageId;
+ // parallel lists of gloda ids and message keys to pass to
+ // GlodaDatastore.updateMessageLocations
+ let updateGlodaIds = [];
+ let updateMessageKeys = [];
+ // list of gloda id's to mark deleted
+ let deleteGlodaIds = [];
+ let exceptionalMessages = {};
+
+ // for GC reasons we need to track the number of headers seen
+ let numHeadersSeen = 0;
+
+ // We are consuming two lists; our loop structure has to reflect that.
+ let headerIter = Iterator(fixIterator(this._indexingEnumerator,
+ nsIMsgDBHdr));
+ let mayHaveMoreGlodaMessages = true;
+ let keepIterHeader = false;
+ let keepGlodaTuple = false;
+ let msgHdr = null;
+ while (headerIter || mayHaveMoreGlodaMessages) {
+ let glodaId;
+ if (headerIter) {
+ try {
+ if (!keepIterHeader)
+ msgHdr = headerIter.next();
+ else
+ keepIterHeader = false;
+ }
+ catch (ex) {
+ if (ex instanceof StopIteration) {
+ headerIter = null;
+ msgHdr = null;
+ // do the loop check again
+ continue;
+ } else {
+ throw ex;
+ }
+ }
+ }
+
+ if (msgHdr) {
+ numHeadersSeen++;
+ if (numHeadersSeen % HEADER_CHECK_SYNC_BLOCK_SIZE == 0)
+ yield this.kWorkSync;
+
+ if (numHeadersSeen % HEADER_CHECK_GC_BLOCK_SIZE == 0)
+ GlodaUtils.considerHeaderBasedGC(HEADER_CHECK_GC_BLOCK_SIZE);
+
+ // There is no need to check with PendingCommitTracker. If a message
+ // somehow got indexed between the time the compaction killed
+ // everything and the time we run, that is a bug.
+ glodaId = msgHdr.getUint32Property(GLODA_MESSAGE_ID_PROPERTY);
+ // (there is also no need to check for gloda dirty since the enumerator
+ // filtered that for us.)
+ }
+
+ // get more [gloda id, message key, message-id header] tuples if out
+ if (!glodaIdsMsgKeysHeaderIds.length && mayHaveMoreGlodaMessages) {
+ // Since we operate on blocks, getting a new block implies we should
+ // flush the last block if applicable.
+ if (updateGlodaIds.length) {
+ GlodaDatastore.updateMessageLocations(updateGlodaIds,
+ updateMessageKeys,
+ aJob.id, true);
+ updateGlodaIds = [];
+ updateMessageKeys = [];
+ }
+
+ if (deleteGlodaIds.length) {
+ GlodaDatastore.markMessagesDeletedByIDs(deleteGlodaIds);
+ deleteGlodaIds = [];
+ }
+
+ GlodaDatastore.folderCompactionPassBlockFetch(
+ aJob.id, oldMessageKey + 1, FOLDER_COMPACTION_PASS_BATCH_SIZE,
+ aCallbackHandle.wrappedCallback);
+ glodaIdsMsgKeysHeaderIds = yield this.kWorkAsync;
+ // Reverse so we can use pop instead of shift and I don't need to be
+ // paranoid about performance.
+ glodaIdsMsgKeysHeaderIds.reverse();
+
+ if (!glodaIdsMsgKeysHeaderIds.length) {
+ mayHaveMoreGlodaMessages = false;
+
+ // We shouldn't be in the loop anymore if headerIter is dead now.
+ if (!headerIter)
+ break;
+ }
+ }
+
+ if (!keepGlodaTuple) {
+ if (mayHaveMoreGlodaMessages)
+ [oldGlodaId, oldMessageKey, oldHeaderMessageId] =
+ glodaIdsMsgKeysHeaderIds.pop();
+ else
+ oldGlodaId = oldMessageKey = oldHeaderMessageId = null;
+ }
+ else {
+ keepGlodaTuple = false;
+ }
+
+ // -- normal expected case
+ if (glodaId == oldGlodaId) {
+ // only need to do something if the key is not right
+ if (msgHdr.messageKey != oldMessageKey) {
+ updateGlodaIds.push(glodaId);
+ updateMessageKeys.push(msgHdr.messageKey);
+ }
+ }
+ // -- exceptional cases
+ else {
+ // This should always return a value unless something is very wrong.
+ // We do not want to catch the exception if one happens.
+ let idBasedHeader = oldHeaderMessageId ?
+ this._indexingDatabase.getMsgHdrForMessageID(oldHeaderMessageId) :
+ false;
+ // - Case 1b.
+ // We want to mark the message as deleted.
+ if (idBasedHeader == null) {
+ deleteGlodaIds.push(oldGlodaId);
+ }
+ // - Case 1a
+ // The expected case is that the message referenced by the gloda
+ // database precedes the header the enumerator told us about. This
+ // is expected because if PendingCommitTracker did not mark the
+ // message as indexed/clean then the enumerator would not tell us
+ // about it.
+ // Also, if we ran out of headers from the enumerator, this is a dead
+ // giveaway that this is the expected case.
+ else if (idBasedHeader &&
+ ((msgHdr &&
+ idBasedHeader.messageKey < msgHdr.messageKey) ||
+ !msgHdr)) {
+ // tell the pending commit tracker about the gloda database one
+ PendingCommitTracker.track(idBasedHeader, oldGlodaId);
+ // and we might need to update the message key too
+ if (idBasedHeader.messageKey != oldMessageKey) {
+ updateGlodaIds.push(oldGlodaId);
+ updateMessageKeys.push(idBasedHeader.messageKey);
+ }
+ // Take another pass through the loop so that we check the
+ // enumerator header against the next message in the gloda
+ // database.
+ keepIterHeader = true;
+ }
+ // - Case 2
+ // Whereas if the message referenced by gloda has a message key
+ // greater than the one returned by the enumerator, then we have a
+ // header claiming to be indexed by gloda that gloda does not
+ // actually know about. This is exceptional and gets a warning.
+ else if (msgHdr) {
+ this._log.warn("Observed header that claims to be gloda indexed " +
+ "but that gloda has never heard of during " +
+ "compaction." +
+ " In folder: " + msgHdr.folder.URI +
+ " sketchy key: " + msgHdr.messageKey +
+ " subject: " + msgHdr.mime2DecodedSubject);
+ // Keep this tuple around for the next enumerator provided header
+ keepGlodaTuple = true;
+ }
+ }
+ }
+ // If we don't flush the update, no one will!
+ if (updateGlodaIds.length)
+ GlodaDatastore.updateMessageLocations(updateGlodaIds,
+ updateMessageKeys,
+ aJob.id, true);
+ if (deleteGlodaIds.length)
+ GlodaDatastore.markMessagesDeletedByIDs(deleteGlodaIds);
+
+ this._indexingGlodaFolder._setCompactedState(false);
+
+ this._indexerLeaveFolder();
+ yield this.kWorkDone;
+ },
+
+ /**
+ * Index the contents of a folder.
+ */
+ _worker_folderIndex:
+ function* gloda_worker_folderIndex(aJob, aCallbackHandle) {
+ let logDebug = this._log.level <= Log4Moz.Level.Debug;
+ yield this._indexerEnterFolder(aJob.id);
+
+ if (!this.shouldIndexFolder(this._indexingFolder)) {
+ aJob.safelyInvokeCallback(true);
+ yield this.kWorkDone;
+ }
+
+ // Make sure listeners get notified about this job.
+ GlodaIndexer._notifyListeners();
+
+ // there is of course a cost to all this header investigation even if we
+ // don't do something. so we will yield with kWorkSync for every block.
+ const HEADER_CHECK_SYNC_BLOCK_SIZE = this.HEADER_CHECK_SYNC_BLOCK_SIZE;
+ const HEADER_CHECK_GC_BLOCK_SIZE = this.HEADER_CHECK_GC_BLOCK_SIZE;
+
+ // we can safely presume if we are here that this folder has been selected
+ // for offline processing...
+
+ // -- Filthy Folder
+ // A filthy folder may have misleading properties on the message that claim
+ // the message is indexed. They are misleading because the database, for
+ // whatever reason, does not have the messages (accurately) indexed.
+ // We need to walk all the messages and mark them filthy if they have a
+ // dirty property. Once we have done this, we can downgrade the folder's
+ // dirty status to plain dirty. We do this rather than trying to process
+ // everyone in one go in a filthy context because if we have to terminate
+ // indexing before we quit, we don't want to have to re-index messages next
+ // time. (This could even lead to never completing indexing in a
+ // pathological situation.)
+ let glodaFolder = GlodaDatastore._mapFolder(this._indexingFolder);
+ if (glodaFolder.dirtyStatus == glodaFolder.kFolderFilthy) {
+ this._indexerGetEnumerator(this.kEnumIndexedMsgs, true);
+ let count = 0;
+ for (let msgHdr in fixIterator(this._indexingEnumerator, nsIMsgDBHdr)) {
+ // we still need to avoid locking up the UI, pause periodically...
+ if (++count % HEADER_CHECK_SYNC_BLOCK_SIZE == 0)
+ yield this.kWorkSync;
+
+ if (count % HEADER_CHECK_GC_BLOCK_SIZE == 0)
+ GlodaUtils.considerHeaderBasedGC(HEADER_CHECK_GC_BLOCK_SIZE);
+
+ let glodaMessageId = msgHdr.getUint32Property(
+ GLODA_MESSAGE_ID_PROPERTY);
+ // if it has a gloda message id, we need to mark it filthy
+ if (glodaMessageId != 0)
+ msgHdr.setUint32Property(GLODA_DIRTY_PROPERTY, this.kMessageFilthy);
+ // if it doesn't have a gloda message id, we will definitely index it,
+ // so no action is required.
+ }
+ // Commit the filthy status changes to the message database.
+ this._indexingDatabase.Commit(Ci.nsMsgDBCommitType.kLargeCommit);
+
+ // this will automatically persist to the database
+ glodaFolder._downgradeDirtyStatus(glodaFolder.kFolderDirty);
+ }
+
+ // Figure out whether we're supposed to index _everything_ or just what
+ // has not yet been indexed.
+ let force = ("force" in aJob) && aJob.force;
+ let enumeratorType = force ? this.kEnumAllMsgs : this.kEnumMsgsToIndex;
+
+ // Pass 1: count the number of messages to index.
+ // We do this in order to be able to report to the user what we're doing.
+ // TODO: give up after reaching a certain number of messages in folders
+ // with ridiculous numbers of messages and make the interface just say
+ // something like "over N messages to go."
+
+ this._indexerGetEnumerator(enumeratorType);
+
+ let numMessagesToIndex = 0;
+ let numMessagesOut = {};
+ // Keep going until we run out of headers.
+ while (this._indexingFolder.msgDatabase.nextMatchingHdrs(
+ this._indexingEnumerator,
+ HEADER_CHECK_SYNC_BLOCK_SIZE * 8, // this way is faster, do more
+ 0, // moot, we don't return headers
+ null, // don't return headers, we just want the count
+ numMessagesOut)) {
+ numMessagesToIndex += numMessagesOut.value;
+ yield this.kWorkSync;
+ }
+ numMessagesToIndex += numMessagesOut.value;
+
+ aJob.goal = numMessagesToIndex;
+
+ if (numMessagesToIndex > 0) {
+ // We used up the iterator, get a new one.
+ this._indexerGetEnumerator(enumeratorType);
+
+ // Pass 2: index the messages.
+ let count = 0;
+ for (let msgHdr in fixIterator(this._indexingEnumerator, nsIMsgDBHdr)) {
+ // per above, we want to periodically release control while doing all
+ // this header traversal/investigation.
+ if (++count % HEADER_CHECK_SYNC_BLOCK_SIZE == 0)
+ yield this.kWorkSync;
+
+ if (count % HEADER_CHECK_GC_BLOCK_SIZE == 0)
+ GlodaUtils.considerHeaderBasedGC(HEADER_CHECK_GC_BLOCK_SIZE);
+
+ // To keep our counts more accurate, increment the offset before
+ // potentially skipping any messages.
+ ++aJob.offset;
+
+ // Skip messages that have not yet been reported to us as existing via
+ // msgsClassified.
+ if (this._indexingFolder.getProcessingFlags(msgHdr.messageKey) &
+ NOT_YET_REPORTED_PROCESSING_FLAGS)
+ continue;
+
+ // Because the gloda id could be in-flight, we need to double-check the
+ // enumerator here since it can't know about our in-memory stuff.
+ let [glodaId, glodaDirty] = PendingCommitTracker.getGlodaState(msgHdr);
+ // if the message seems valid and we are not forcing indexing, skip it.
+ // (that means good gloda id and not dirty)
+ if (!force &&
+ glodaId >= GLODA_FIRST_VALID_MESSAGE_ID &&
+ glodaDirty == this.kMessageClean)
+ continue;
+
+ if (logDebug)
+ this._log.debug(">>> calling _indexMessage");
+ yield aCallbackHandle.pushAndGo(
+ this._indexMessage(msgHdr, aCallbackHandle),
+ {what: "indexMessage", msgHdr: msgHdr});
+ GlodaIndexer._indexedMessageCount++;
+ if (logDebug)
+ this._log.debug("<<< back from _indexMessage");
+ }
+ }
+
+ // This will trigger an (async) db update which cannot hit the disk prior to
+ // the actual database records that constitute the clean state.
+ // XXX There is the slight possibility that, in the event of a crash, this
+ // will hit the disk but the gloda-id properties on the headers will not
+ // get set. This should ideally be resolved by detecting a non-clean
+ // shutdown and marking all folders as dirty.
+ glodaFolder._downgradeDirtyStatus(glodaFolder.kFolderClean);
+
+ // by definition, it's not likely we'll visit this folder again anytime soon
+ this._indexerLeaveFolder();
+
+ aJob.safelyInvokeCallback(true);
+
+ yield this.kWorkDone;
+ },
+
+ /**
+ * Invoked when a "message" job is scheduled so that we can clear
+ * _pendingAddJob if that is the job. We do this so that work items are not
+ * added to _pendingAddJob while it is being processed.
+ */
+ _schedule_messageIndex: function(aJob, aCallbackHandle) {
+ // we do not want new work items to be added as we are processing, so
+ // clear _pendingAddJob. A new job will be created as needed.
+ if (aJob === this._pendingAddJob)
+ this._pendingAddJob = null;
+ // update our goal from the items length
+ aJob.goal = aJob.items.length;
+ },
+ /**
+ * If the job gets canceled, we need to make sure that we clear out pending
+ * add job or our state will get wonky.
+ */
+ _canceled_messageIndex: function gloda_index_msg_canceled_messageIndex(aJob) {
+ if (aJob === this._pendingAddJob)
+ this._pendingAddJob = null;
+ },
+
+
+ /**
+ * Index a specific list of messages that we know to index from
+ * event-notification hints.
+ */
+ _worker_messageIndex:
+ function* gloda_worker_messageIndex(aJob, aCallbackHandle) {
+ // if we are already in the correct folder, our "get in the folder" clause
+ // will not execute, so we need to make sure this value is accurate in
+ // that case. (and we want to avoid multiple checks...)
+ for (; aJob.offset < aJob.items.length; aJob.offset++) {
+ let item = aJob.items[aJob.offset];
+ // item is either [folder ID, message key] or
+ // [folder ID, message ID]
+
+ let glodaFolderId = item[0];
+ // If the folder has been deleted since we queued, skip this message
+ if (!GlodaDatastore._folderIdKnown(glodaFolderId))
+ continue;
+ let glodaFolder = GlodaDatastore._mapFolderID(glodaFolderId);
+
+ // Stay out of folders that:
+ // - are compacting / compacted and not yet processed
+ // - got deleted (this would be redundant if we had a stance on id nukage)
+ // (these things could have changed since we queued the event)
+ if (glodaFolder.compacting || glodaFolder.compacted ||
+ glodaFolder._deleted)
+ continue;
+
+ // get in the folder
+ if (this._indexingGlodaFolder != glodaFolder) {
+ yield this._indexerEnterFolder(glodaFolderId);
+
+ // Now that we have the real nsIMsgFolder, sanity-check that we should
+ // be indexing it. (There are some checks that require the
+ // nsIMsgFolder.)
+ if (!this.shouldIndexFolder(this._indexingFolder))
+ continue;
+ }
+
+ let msgHdr;
+ // GetMessageHeader can be affected by the use cache, so we need to check
+ // ContainsKey first to see if the header is really actually there.
+ if (typeof item[1] == "number")
+ msgHdr = this._indexingDatabase.ContainsKey(item[1]) &&
+ this._indexingFolder.GetMessageHeader(item[1]);
+ else
+ // same deal as in move processing.
+ // TODO fixme to not assume singular message-id's.
+ msgHdr = this._indexingDatabase.getMsgHdrForMessageID(item[1]);
+
+ if (msgHdr)
+ yield aCallbackHandle.pushAndGo(
+ this._indexMessage(msgHdr, aCallbackHandle),
+ {what: "indexMessage", msgHdr: msgHdr});
+ else
+ yield this.kWorkSync;
+ }
+
+ // There is no real reason to stay 'in' the folder. If we are going to get
+ // more events from the folder, its database would have to be open for us
+ // to get the events, so it's not like we're creating an efficiency
+ // problem where we unload a folder just to load it again in 2 seconds.
+ // (Well, at least assuming the views are good about holding onto the
+ // database references even though they go out of their way to avoid
+ // holding onto message header references.)
+ this._indexerLeaveFolder();
+
+ yield this.kWorkDone;
+ },
+
+ /**
+ * Recover from a "folder" or "message" job failing inside a call to
+ * |_indexMessage|, marking the message bad. If we were not in an
+ * |_indexMessage| call, then fail to recover.
+ *
+ * @param aJob The job that was being worked. We ignore this for now.
+ * @param aContextStack The callbackHandle mechanism's context stack. When we
+ * invoke pushAndGo for _indexMessage we put something in so we can
+ * detect when it is on the async stack.
+ * @param aException The exception that is necessitating we attempt to
+ * recover.
+ *
+ * @return 1 if we were able to recover (because we want the call stack
+ * popped down to our worker), false if we can't.
+ */
+ _recover_indexMessage:
+ function gloda_index_recover_indexMessage(aJob, aContextStack,
+ aException) {
+ // See if indexMessage is on the stack...
+ if (aContextStack.length >= 2 &&
+ aContextStack[1] &&
+ ("what" in aContextStack[1]) &&
+ aContextStack[1].what == "indexMessage") {
+ // it is, so this is probably recoverable.
+
+ this._log.debug(
+ "Exception while indexing message, marking it bad (gloda id of 1).");
+
+ // -- Mark the message as bad
+ let msgHdr = aContextStack[1].msgHdr;
+ // (In the worst case, the header is no longer valid, which will result in
+ // exceptions. We need to be prepared for that.)
+ try {
+ msgHdr.setUint32Property(GLODA_MESSAGE_ID_PROPERTY,
+ GLODA_BAD_MESSAGE_ID);
+ // clear the dirty bit if it has one
+ if (msgHdr.getUint32Property(GLODA_DIRTY_PROPERTY))
+ msgHdr.setUint32Property(GLODA_DIRTY_PROPERTY, 0);
+ }
+ catch (ex) {
+ // If we are indexing a folder and the message header is no longer
+ // valid, then it's quite likely the whole folder is no longer valid.
+ // But since in the event-driven message indexing case we could have
+ // other valid things to look at, let's try and recover. The folder
+ // indexing case will come back to us shortly and we will indicate
+ // recovery is not possible at that point.
+ // So do nothing here since by popping the indexing of the specific
+ // message out of existence we are recovering.
+ }
+ return 1;
+ }
+ return false;
+ },
+
+ /**
+ * Cleanup after an aborted "folder" or "message" job.
+ */
+ _cleanup_indexing: function gloda_index_cleanup_indexing(aJob) {
+ this._indexerLeaveFolder();
+ aJob.safelyInvokeCallback(false);
+ },
+
+ /**
+ * Maximum number of deleted messages to process at a time. Arbitrary; there
+ * are no real known performance constraints at this point.
+ */
+ DELETED_MESSAGE_BLOCK_SIZE: 32,
+
+ /**
+ * Process pending deletes...
+ */
+ _worker_processDeletes: function* gloda_worker_processDeletes(aJob,
+ aCallbackHandle) {
+
+ // Count the number of messages we will eventually process. People freak
+ // out when the number is constantly increasing because they think gloda
+ // has gone rogue. (Note: new deletions can still accumulate during
+ // our execution, so we may 'expand' our count a little still.)
+ this._datastore.countDeletedMessages(aCallbackHandle.wrappedCallback);
+ aJob.goal = yield this.kWorkAsync;
+ this._log.debug("There are currently " + aJob.goal + " messages awaiting" +
+ " deletion processing.");
+
+ // get a block of messages to delete.
+ let query = Gloda.newQuery(Gloda.NOUN_MESSAGE, {
+ noDbQueryValidityConstraints: true,
+ });
+ query._deleted(1);
+ query.limit(this.DELETED_MESSAGE_BLOCK_SIZE);
+ let deletedCollection = query.getCollection(aCallbackHandle);
+ yield this.kWorkAsync;
+
+ while (deletedCollection.items.length) {
+ for (let message of deletedCollection.items) {
+ // If it turns out our count is wrong (because some new deletions
+ // happened since we entered this worker), let's issue a new count
+ // and use that to accurately update our goal.
+ if (aJob.offset >= aJob.goal) {
+ this._datastore.countDeletedMessages(aCallbackHandle.wrappedCallback);
+ aJob.goal += yield this.kWorkAsync;
+ }
+
+ yield aCallbackHandle.pushAndGo(this._deleteMessage(message,
+ aCallbackHandle));
+ aJob.offset++;
+ yield this.kWorkSync;
+ }
+
+ deletedCollection = query.getCollection(aCallbackHandle);
+ yield this.kWorkAsync;
+ }
+ this.pendingDeletions = false;
+
+ yield this.kWorkDone;
+ },
+
+ _worker_fixMissingContacts: function*(aJob, aCallbackHandle) {
+ let identityContactInfos = [], fixedContacts = {};
+
+ // -- asynchronously get a list of all identities without contacts
+ // The upper bound on the number of messed up contacts is the number of
+ // contacts in the user's address book. This should be small enough
+ // (and the data size small enough) that this won't explode thunderbird.
+ let queryStmt = GlodaDatastore._createAsyncStatement(
+ "SELECT identities.id, identities.contactID, identities.value " +
+ "FROM identities " +
+ "LEFT JOIN contacts ON identities.contactID = contacts.id " +
+ "WHERE identities.kind = 'email' AND contacts.id IS NULL",
+ true);
+ queryStmt.executeAsync({
+ handleResult: function(aResultSet) {
+ let row;
+ while ((row = aResultSet.getNextRow())) {
+ identityContactInfos.push({
+ identityId: row.getInt64(0),
+ contactId: row.getInt64(1),
+ email: row.getString(2)
+ });
+ }
+ },
+ handleError: function(aError) {
+ },
+ handleCompletion: function(aReason) {
+ GlodaDatastore._asyncCompleted();
+ aCallbackHandle.wrappedCallback();
+ },
+ });
+ queryStmt.finalize();
+ GlodaDatastore._pendingAsyncStatements++;
+ yield this.kWorkAsync;
+
+ // -- perform fixes only if there were missing contacts
+ if (identityContactInfos.length) {
+ const yieldEvery = 64;
+ // - create the missing contacts
+ for (let i = 0; i < identityContactInfos.length; i++) {
+ if ((i % yieldEvery) === 0)
+ yield this.kWorkSync;
+
+ let info = identityContactInfos[i],
+ card = GlodaUtils.getCardForEmail(info.email),
+ contact = new GlodaContact(
+ GlodaDatastore, info.contactId,
+ null, null,
+ card ? (card.displayName || info.email) : info.email,
+ 0, 0);
+ GlodaDatastore.insertContact(contact);
+
+ // update the in-memory rep of the identity to know about the contact
+ // if there is one.
+ let identity = GlodaCollectionManager.cacheLookupOne(
+ Gloda.NOUN_IDENTITY, info.identityId, false);
+ if (identity) {
+ // Unfortunately, although this fixes the (reachable) Identity and
+ // exposes the Contact, it does not make the Contact reachable from
+ // the collection manager. This will make explicit queries that look
+ // up the contact potentially see the case where
+ // contact.identities[0].contact !== contact. Alternately, that
+ // may not happen and instead the "contact" object we created above
+ // may become unlinked. (I'd have to trace some logic I don't feel
+ // like tracing.) Either way, The potential fallout is minimal
+ // since the object identity invariant will just lapse and popularity
+ // on the contact may become stale, and neither of those meaningfully
+ // affect the operation of anything in Thunderbird.
+ // If we really cared, we could find all the dominant collections
+ // that reference the identity and update their corresponding
+ // contact collection to make it reachable. That use-case does not
+ // exist outside of here, which is why we're punting.
+ identity._contact = contact;
+ contact._identities = [identity];
+ }
+
+ // NOTE: If the addressbook indexer did anything useful other than
+ // adapting to name changes, we could schedule indexing of the cards at
+ // this time. However, as of this writing, it doesn't, and this task
+ // is a one-off relevant only to the time of this writing.
+ }
+
+ // - mark all folders as dirty, initiate indexing sweep
+ this.dirtyAllKnownFolders();
+ this.indexingSweepNeeded = true;
+ }
+
+ // -- mark the schema upgrade, be done
+ GlodaDatastore._updateSchemaVersion(GlodaDatastore._schemaVersion);
+ yield this.kWorkDone;
+ },
+
+ /**
+ * Determine whether a folder is suitable for indexing.
+ *
+ * @param aMsgFolder An nsIMsgFolder you want to see if we should index.
+ *
+ * @returns true if we want to index messages in this type of folder, false if
+ * we do not.
+ */
+ shouldIndexFolder: function(aMsgFolder) {
+ let folderFlags = aMsgFolder.flags;
+ // Completely ignore non-mail and virtual folders. They should never even
+ // get to be GlodaFolder instances.
+ if (!(folderFlags & Ci.nsMsgFolderFlags.Mail) ||
+ (folderFlags & Ci.nsMsgFolderFlags.Virtual))
+ return false;
+
+ // Some folders do not really exist; we can detect this by getStringProperty
+ // exploding when we call it. This is primarily a concern because
+ // _mapFolder calls said exploding method, but we also don't want to
+ // even think about indexing folders that don't exist. (Such folders are
+ // likely the result of a messed up profile.)
+ try {
+ // flags is used because it should always be in the cache avoiding a miss
+ // which would compel an msf open.
+ aMsgFolder.getStringProperty("flags");
+ } catch (ex) {
+ return false;
+ }
+
+ // Now see what our gloda folder information has to say about the folder.
+ let glodaFolder = GlodaDatastore._mapFolder(aMsgFolder);
+ return glodaFolder.indexingPriority != glodaFolder.kIndexingNeverPriority;
+ },
+
+ /**
+ * Sets the indexing priority for this folder and persists it both to Gloda,
+ * and, for backup purposes, to the nsIMsgFolder via string property as well.
+ *
+ * Setting this priority may cause the indexer to either reindex this folder,
+ * or remove this folder from the existing index.
+ *
+ * @param {nsIMsgFolder} aFolder
+ * @param {Number} aPriority (one of the priority constants from GlodaFolder)
+ */
+ setFolderIndexingPriority: function glodaSetFolderIndexingPriority(aFolder, aPriority) {
+
+ let glodaFolder = GlodaDatastore._mapFolder(aFolder);
+
+ // if there's been no change, we're done
+ if (aPriority == glodaFolder.indexingPriority) {
+ return;
+ }
+
+ // save off the old priority, and set the new one
+ let previousPrio = glodaFolder.indexingPriority;
+ glodaFolder._indexingPriority = aPriority;
+
+ // persist the new priority
+ GlodaDatastore.updateFolderIndexingPriority(glodaFolder);
+ aFolder.setStringProperty("indexingPriority", Number(aPriority).toString());
+
+ // if we've been told never to index this folder...
+ if (aPriority == glodaFolder.kIndexingNeverPriority) {
+
+ // stop doing so
+ if (this._indexingFolder == aFolder)
+ GlodaIndexer.killActiveJob();
+
+ // mark all existing messages as deleted
+ GlodaDatastore.markMessagesDeletedByFolderID(glodaFolder.id);
+
+ // re-index
+ GlodaMsgIndexer.indexingSweepNeeded = true;
+
+ } else if (previousPrio == glodaFolder.kIndexingNeverPriority) {
+
+ // there's no existing index, but the user now wants one
+ glodaFolder._dirtyStatus = glodaFolder.kFolderFilthy;
+ GlodaDatastore.updateFolderDirtyStatus(glodaFolder)
+ GlodaMsgIndexer.indexingSweepNeeded = true;
+ }
+ },
+
+ /**
+ * Resets the indexing priority on the given folder to whatever the default
+ * is for folders of that type.
+ *
+ * @note Calls setFolderIndexingPriority under the hood, so has identical
+ * potential reindexing side-effects
+ *
+ * @param {nsIMsgFolder} aFolder
+ * @param {boolean} aAllowSpecialFolderIndexing
+ */
+ resetFolderIndexingPriority: function glodaResetFolderIndexingPriority(aFolder, aAllowSpecialFolderIndexing) {
+ this.setFolderIndexingPriority(aFolder,
+ GlodaDatastore.getDefaultIndexingPriority(aFolder,
+ aAllowSpecialFolderIndexing));
+ },
+
+ /**
+ * Queue all of the folders of all of the accounts of the current profile
+ * for indexing. We traverse all folders and queue them immediately to try
+ * and have an accurate estimate of the number of folders that need to be
+ * indexed. (We previously queued accounts rather than immediately
+ * walking their list of folders.)
+ */
+ indexEverything: function glodaIndexEverything() {
+ this._log.info("Queueing all accounts for indexing.");
+
+ GlodaDatastore._beginTransaction();
+ for (let account in fixIterator(MailServices.accounts.accounts,
+ Ci.nsIMsgAccount)) {
+ this.indexAccount(account);
+ }
+ GlodaDatastore._commitTransaction();
+ },
+
+ /**
+ * Queue all of the folders belonging to an account for indexing.
+ */
+ indexAccount: function glodaIndexAccount(aAccount) {
+ let rootFolder = aAccount.incomingServer.rootFolder;
+ if (rootFolder instanceof Ci.nsIMsgFolder) {
+ this._log.info("Queueing account folders for indexing: " + aAccount.key);
+
+ let allFolders = rootFolder.descendants;
+ let folderJobs = [];
+ for (let folder in fixIterator(allFolders, Ci.nsIMsgFolder)) {
+ if (this.shouldIndexFolder(folder))
+ GlodaIndexer.indexJob(
+ new IndexingJob("folder", GlodaDatastore._mapFolder(folder).id));
+ }
+ }
+ else {
+ this._log.info("Skipping Account, root folder not nsIMsgFolder");
+ }
+ },
+
+ /**
+ * Queue a single folder for indexing given an nsIMsgFolder.
+ *
+ * @param [aOptions.callback] A callback to invoke when the folder finishes
+ * indexing. First argument is true if the task ran to completion
+ * successfully, false if we had to abort for some reason.
+ * @param [aOptions.force=false] Should we force the indexing of all messages
+ * in the folder (true) or just index what hasn't been indexed (false).
+ * @return true if we are going to index the folder, false if not.
+ */
+ indexFolder: function glodaIndexFolder(aMsgFolder, aOptions) {
+ if (!this.shouldIndexFolder(aMsgFolder))
+ return false;
+ let glodaFolder = GlodaDatastore._mapFolder(aMsgFolder);
+ // stay out of compacting/compacted folders
+ if (glodaFolder.compacting || glodaFolder.compacted)
+ return false;
+
+ this._log.info("Queue-ing folder for indexing: " +
+ aMsgFolder.prettiestName);
+ let job = new IndexingJob("folder", glodaFolder.id);
+ if (aOptions) {
+ if ("callback" in aOptions)
+ job.callback = aOptions.callback;
+ if ("force" in aOptions)
+ job.force = true;
+ }
+ GlodaIndexer.indexJob(job);
+ return true;
+ },
+
+ /**
+ * Queue a list of messages for indexing.
+ *
+ * @param aFoldersAndMessages List of [nsIMsgFolder, message key] tuples.
+ */
+ indexMessages: function gloda_index_indexMessages(aFoldersAndMessages) {
+ let job = new IndexingJob("message", null);
+ job.items = aFoldersAndMessages.
+ map(fm => [GlodaDatastore._mapFolder(fm[0]).id, fm[1]]);
+ GlodaIndexer.indexJob(job);
+ },
+
+ /**
+ * Mark all known folders as dirty so that the next indexing sweep goes
+ * into all folders and checks their contents to see if they need to be
+ * indexed.
+ *
+ * This is being added for the migration case where we want to try and reindex
+ * all of the messages that had been marked with GLODA_BAD_MESSAGE_ID but
+ * which is now GLODA_OLD_BAD_MESSAGE_ID and so we should attempt to reindex
+ * them.
+ */
+ dirtyAllKnownFolders: function gloda_index_msg_dirtyAllKnownFolders() {
+ // Just iterate over the datastore's folder map and tell each folder to
+ // be dirty if its priority is not disabled.
+ for (let folderID in GlodaDatastore._folderByID) {
+ let glodaFolder = GlodaDatastore._folderByID[folderID];
+ if (glodaFolder.indexingPriority !== glodaFolder.kIndexingNeverPriority)
+ glodaFolder._ensureFolderDirty();
+ }
+ },
+
+ /**
+ * Given a message header, return whether this message is likely to have
+ * been indexed or not.
+ *
+ * This means the message must:
+ * - Be in a folder eligible for gloda indexing. (Not News, etc.)
+ * - Be in a non-filthy folder.
+ * - Be gloda-indexed and non-filthy.
+ *
+ * @param aMsgHdr A message header.
+ * @returns true if the message is likely to have been indexed.
+ */
+ isMessageIndexed: function gloda_index_isMessageIndexed(aMsgHdr) {
+ // If it's in a folder that we flat out do not index, say no.
+ if (!this.shouldIndexFolder(aMsgHdr.folder))
+ return false;
+ let glodaFolder = GlodaDatastore._mapFolder(aMsgHdr.folder);
+ let [glodaId, glodaDirty] = PendingCommitTracker.getGlodaState(aMsgHdr);
+ return glodaId >= GLODA_FIRST_VALID_MESSAGE_ID &&
+ glodaDirty != GlodaMsgIndexer.kMessageFilthy &&
+ glodaFolder &&
+ glodaFolder.dirtyStatus != glodaFolder.kFolderFilthy;
+ },
+
+ /* *********** Event Processing *********** */
+
+ /**
+ * Tracks messages we have received msgKeyChanged notifications for in order
+ * to provide batching and to suppress needless reindexing when we receive
+ * the expected follow-up msgsClassified notification.
+ *
+ * The entries in this dictionary should be extremely short-lived as we
+ * receive the msgKeyChanged notification as the offline fake header is
+ * converted into a real header (which is accompanied by a msgAdded
+ * notification we don't pay attention to). Once the headers finish
+ * updating, the message classifier will get its at-bat and should likely
+ * find that the messages have already been classified and so fast-path
+ * them.
+ *
+ * The keys in this dictionary are chosen to be consistent with those of
+ * PendingCommitTracker: the folder.URI + "#" + the (new) message key.
+ * The values in the dictionary are either an object with "id" (the gloda
+ * id), "key" (the new message key), and "dirty" (is it dirty and so
+ * should still be queued for indexing) attributes, or null indicating that
+ * no change in message key occurred and so no database changes are required.
+ */
+ _keyChangedBatchInfo: {},
+
+ /**
+ * Common logic for things that want to feed event-driven indexing. This gets
+ * called by both |_msgFolderListener.msgsClassified| when we are first
+ * seeing a message as well as by |_folderListener| when things happen to
+ * existing messages. Although we could slightly specialize for the
+ * new-to-us case, it works out to be cleaner to just treat them the same
+ * and take a very small performance hit.
+ *
+ * @param aMsgHdrs Something fixIterator will work on to return an iterator
+ * on the set of messages that we should treat as potentially changed.
+ * @param aDirtyingEvent Is this event inherently dirtying? Receiving a
+ * msgsClassified notification is not inherently dirtying because it is
+ * just telling us that a message exists. We use this knowledge to
+ * ignore the msgsClassified notifications for messages we have received
+ * msgKeyChanged notifications for and fast-pathed. Since it is possible
+ * for user action to do something that dirties the message between the
+ * time we get the msgKeyChanged notification and when we receive the
+ * msgsClassified notification, we want to make sure we don't get
+ * confused. (Although since we remove the message from our ignore-set
+ * after the first notification, we would likely just mistakenly treat
+ * the msgsClassified notification as something dirtying, so it would
+ * still work out...)
+ */
+ _reindexChangedMessages: function gloda_indexer_reindexChangedMessage(
+ aMsgHdrs, aDirtyingEvent) {
+ let glodaIdsNeedingDeletion = null;
+ let messageKeyChangedIds = null, messageKeyChangedNewKeys = null;
+ for (let msgHdr in fixIterator(aMsgHdrs, nsIMsgDBHdr)) {
+ // -- Index this folder?
+ let msgFolder = msgHdr.folder;
+ if (!this.shouldIndexFolder(msgFolder)) {
+ continue;
+ }
+ // -- Ignore messages in filthy folders!
+ // A filthy folder can only be processed by an indexing sweep, and at
+ // that point the message will get indexed.
+ let glodaFolder = GlodaDatastore._mapFolder(msgHdr.folder);
+ if (glodaFolder.dirtyStatus == glodaFolder.kFolderFilthy)
+ continue;
+
+ // -- msgKeyChanged event follow-up
+ if (!aDirtyingEvent) {
+ let keyChangedKey = msgHdr.folder.URI + "#" + msgHdr.messageKey;
+ if (keyChangedKey in this._keyChangedBatchInfo) {
+ var keyChangedInfo = this._keyChangedBatchInfo[keyChangedKey];
+ delete this._keyChangedBatchInfo[keyChangedKey];
+
+ // Null means to ignore this message because the key did not change
+ // (and the message was not dirty so it is safe to ignore.)
+ if (keyChangedInfo == null)
+ continue;
+ // (the key may be null if we only generated the entry because the
+ // message was dirty)
+ if (keyChangedInfo.key !== null) {
+ if (messageKeyChangedIds == null) {
+ messageKeyChangedIds = [];
+ messageKeyChangedNewKeys = [];
+ }
+ messageKeyChangedIds.push(keyChangedInfo.id);
+ messageKeyChangedNewKeys.push(keyChangedInfo.key);
+ }
+ // ignore the message because it was not dirty
+ if (!keyChangedInfo.isDirty)
+ continue;
+ }
+ }
+
+ // -- Index this message?
+ // We index local messages, IMAP messages that are offline, and IMAP
+ // messages that aren't offline but whose folders aren't offline either
+ let isFolderLocal = msgFolder instanceof nsIMsgLocalMailFolder;
+ if (!isFolderLocal) {
+ if (!(msgHdr.flags & nsMsgMessageFlags.Offline) &&
+ (msgFolder.flags & nsMsgFolderFlags.Offline)) {
+ continue;
+ }
+ }
+ // Ignore messages whose processing flags indicate it has not yet been
+ // classified. In the IMAP case if the Offline flag is going to get set
+ // we are going to see it before the msgsClassified event so this is
+ // very important.
+ if (msgFolder.getProcessingFlags(msgHdr.messageKey) &
+ NOT_YET_REPORTED_PROCESSING_FLAGS)
+ continue;
+
+ let [glodaId, glodaDirty] = PendingCommitTracker.getGlodaState(msgHdr);
+
+ let isSpam = msgHdr.getStringProperty(JUNK_SCORE_PROPERTY) ==
+ JUNK_SPAM_SCORE_STR;
+
+ // -- Is the message currently gloda indexed?
+ if (glodaId >= GLODA_FIRST_VALID_MESSAGE_ID &&
+ glodaDirty != this.kMessageFilthy) {
+ // - Is the message spam?
+ if (isSpam) {
+ // Treat this as a deletion...
+ if (!glodaIdsNeedingDeletion)
+ glodaIdsNeedingDeletion = [];
+ glodaIdsNeedingDeletion.push(glodaId);
+ // and skip to the next message
+ continue;
+ }
+
+ // - Mark the message dirty if it is clean.
+ // (This is the only case in which we need to mark dirty so that the
+ // indexing sweep takes care of things if we don't process this in
+ // an event-driven fashion. If the message has no gloda-id or does
+ // and it's already dirty or filthy, it is already marked for
+ // indexing.)
+ if (glodaDirty == this.kMessageClean)
+ msgHdr.setUint32Property(GLODA_DIRTY_PROPERTY, this.kMessageDirty);
+ // if the message is pending clean, this change invalidates that.
+ PendingCommitTracker.noteDirtyHeader(msgHdr);
+ }
+ // If it's not indexed but is spam, ignore it.
+ else if (isSpam) {
+ continue;
+ }
+ // (we want to index the message if we are here)
+
+ // mark the folder dirty too, so we know to look inside
+ glodaFolder._ensureFolderDirty();
+
+ if (this._pendingAddJob == null) {
+ this._pendingAddJob = new IndexingJob("message", null);
+ GlodaIndexer.indexJob(this._pendingAddJob);
+ }
+ // only queue the message if we haven't overflowed our event-driven budget
+ if (this._pendingAddJob.items.length <
+ this._indexMaxEventQueueMessages) {
+ this._pendingAddJob.items.push(
+ [GlodaDatastore._mapFolder(msgFolder).id, msgHdr.messageKey]);
+ }
+ else {
+ this.indexingSweepNeeded = true;
+ }
+ }
+
+ // Process any message key changes (from earlier msgKeyChanged events)
+ if (messageKeyChangedIds != null)
+ GlodaDatastore.updateMessageKeys(messageKeyChangedIds,
+ messageKeyChangedNewKeys);
+
+ // If we accumulated any deletions in there, batch them off now.
+ if (glodaIdsNeedingDeletion) {
+ GlodaDatastore.markMessagesDeletedByIDs(glodaIdsNeedingDeletion);
+ this.pendingDeletions = true;
+ }
+ },
+
+
+ /* ***** Folder Changes ***** */
+ /**
+ * All additions and removals are queued for processing. Indexing messages
+ * is potentially phenomenally expensive, and deletion can still be
+ * relatively expensive due to our need to delete the message, its
+ * attributes, and all attributes that reference it. Additionally,
+ * attribute deletion costs are higher than attribute look-up because
+ * there is the actual row plus its 3 indices, and our covering indices are
+ * no help there.
+ *
+ */
+ _msgFolderListener: {
+ indexer: null,
+
+ /**
+ * We no longer use the msgAdded notification, instead opting to wait until
+ * junk/trait classification has run (or decided not to run) and all
+ * filters have run. The msgsClassified notification provides that for us.
+ */
+ msgAdded: function gloda_indexer_msgAdded(aMsgHdr) {
+ // we are never called! we do not enable this bit!
+ },
+
+ /**
+ * Process (apparently newly added) messages that have been looked at by
+ * the message classifier. This ensures that if the message was going
+ * to get marked as spam, this will have already happened.
+ *
+ * Besides truly new (to us) messages, We will also receive this event for
+ * messages that are the result of IMAP message move/copy operations,
+ * including both moves that generated offline fake headers and those that
+ * did not. In the offline fake header case, however, we are able to
+ * ignore their msgsClassified events because we will have received a
+ * msgKeyChanged notification sometime in the recent past.
+ */
+ msgsClassified: function gloda_indexer_msgsClassified(
+ aMsgHdrs, aJunkClassified, aTraitClassified) {
+ this.indexer._log.debug("msgsClassified notification");
+ try {
+ GlodaMsgIndexer._reindexChangedMessages(aMsgHdrs.enumerate(), false);
+ }
+ catch (ex) {
+ this.indexer._log.error("Explosion in msgsClassified handling:", ex);
+ }
+ },
+
+ /**
+ * Handle real, actual deletion (move to trash and IMAP deletion model
+ * don't count); we only see the deletion here when it becomes forever,
+ * or rather _just before_ it becomes forever. Because the header is
+ * going away, we need to either process things immediately or extract the
+ * information required to purge it later without the header.
+ * To this end, we mark all messages that were indexed in the gloda message
+ * database as deleted. We set our pending deletions flag to let our
+ * indexing logic know that after its next wave of folder traversal, it
+ * should perform a deletion pass. If it turns out the messages are coming
+ * back, the fact that deletion is thus deferred can be handy, as we can
+ * reuse the existing gloda message.
+ */
+ msgsDeleted: function gloda_indexer_msgsDeleted(aMsgHdrs) {
+ this.indexer._log.debug("msgsDeleted notification");
+ let glodaMessageIds = [];
+
+ for (let iMsgHdr = 0; iMsgHdr < aMsgHdrs.length; iMsgHdr++) {
+ let msgHdr = aMsgHdrs.queryElementAt(iMsgHdr, nsIMsgDBHdr);
+ let [glodaId, glodaDirty] = PendingCommitTracker.getGlodaState(msgHdr);
+ if (glodaId >= GLODA_FIRST_VALID_MESSAGE_ID &&
+ glodaDirty != GlodaMsgIndexer.kMessageFilthy)
+ glodaMessageIds.push(glodaId);
+ }
+
+ if (glodaMessageIds.length) {
+ GlodaMsgIndexer._datastore.markMessagesDeletedByIDs(glodaMessageIds);
+ GlodaMsgIndexer.pendingDeletions = true;
+ }
+ },
+
+ /**
+ * Process a move or copy.
+ *
+ * Moves to a local folder or an IMAP folder where we are generating offline
+ * fake headers are dealt with efficiently because we get both the source
+ * and destination headers. The main ingredient to having offline fake
+ * headers is that allowUndo was true when the operation was performance.
+ * The only non-obvious thing is that we need to make sure that we deal
+ * with the impact of filthy folders and messages on gloda-id's (they
+ * invalidate the gloda-id).
+ *
+ * Moves to an IMAP folder that do not generate offline fake headers do not
+ * provide us with the target header, but the IMAP SetPendingAttributes
+ * logic will still attempt to propagate the properties on the message
+ * header so when we eventually see it in the msgsClassified notification,
+ * it should have the properties of the source message copied over.
+ * We make sure that gloda-id's do not get propagated when messages are
+ * moved from IMAP folders that are marked filthy or are marked as not
+ * supposed to be indexed by clearing the pending attributes for the header
+ * being tracked by the destination IMAP folder.
+ * We could fast-path the IMAP move case in msgsClassified by noticing that
+ * a message is showing up with a gloda-id header already and just
+ * performing an async location update.
+ *
+ * Moves that occur involving 'compacted' folders are fine and do not
+ * require special handling here. The one tricky super-edge-case that
+ * can happen (and gets handled by the compaction pass) is the move of a
+ * message that got gloda indexed that did not already have a gloda-id and
+ * PendingCommitTracker did not get to flush the gloda-id before the
+ * compaction happened. In that case our move logic cannot know to do
+ * anything and the gloda database still thinks the message lives in our
+ * folder. The compaction pass will deal with this by marking the message
+ * as deleted. The rationale being that marking it deleted allows the
+ * message to be re-used if it gets indexed in the target location, or if
+ * the target location has already been indexed, we no longer need the
+ * duplicate and it should be deleted. (Also, it is unable to distinguish
+ * between a case where the message got deleted versus moved.)
+ *
+ * Because copied messages are, by their nature, duplicate messages, we
+ * do not particularly care about them. As such, we defer their processing
+ * to the automatic sync logic that will happen much later on. This is
+ * potentially desirable in case the user deletes some of the original
+ * messages, allowing us to reuse the gloda message representations when
+ * we finally get around to indexing the messages. We do need to mark the
+ * folder as dirty, though, to clue in the sync logic.
+ */
+ msgsMoveCopyCompleted: function gloda_indexer_msgsMoveCopyCompleted(aMove,
+ aSrcMsgHdrs, aDestFolder, aDestMsgHdrs) {
+ this.indexer._log.debug("MoveCopy notification. Move: " + aMove);
+ try {
+ // ---- Move
+ if (aMove) {
+ // -- Effectively a deletion?
+ // If the destination folder is not indexed, it's like these messages
+ // are being deleted.
+ if (!GlodaMsgIndexer.shouldIndexFolder(aDestFolder)) {
+ this.msgsDeleted(aSrcMsgHdrs);
+ return;
+ }
+
+ // -- Avoid propagation of filthy gloda-id's.
+ // If the source folder is filthy or should not be indexed (and so
+ // any gloda-id's found in there are gibberish), our only job is to
+ // strip the gloda-id's off of all the destination headers because
+ // none of the gloda-id's are valid (and so we certainly don't want
+ // to try and use them as a basis for updating message keys.)
+ let srcMsgFolder = aSrcMsgHdrs.queryElementAt(0, nsIMsgDBHdr).folder;
+ if (!this.indexer.shouldIndexFolder(srcMsgFolder) ||
+ (GlodaDatastore._mapFolder(srcMsgFolder).dirtyStatus ==
+ GlodaFolder.prototype.kFolderFilthy)) {
+ // Local case, just modify the destination headers directly.
+ if (aDestMsgHdrs) {
+ for (let destMsgHdr in fixIterator(aDestMsgHdrs, nsIMsgDBHdr)) {
+ // zero it out if it exists
+ // (no need to deal with pending commit issues here; a filthy
+ // folder by definition has nothing indexed in it.)
+ let glodaId = destMsgHdr.getUint32Property(
+ GLODA_MESSAGE_ID_PROPERTY);
+ if (glodaId)
+ destMsgHdr.setUint32Property(GLODA_MESSAGE_ID_PROPERTY,
+ 0);
+ }
+
+ // Since we are moving messages from a folder where they were
+ // effectively not indexed, it is up to us to make sure the
+ // messages now get indexed.
+ this.indexer._reindexChangedMessages(aDestMsgHdrs.enumerate());
+ return;
+ }
+ // IMAP move case, we need to operate on the pending headers using
+ // the source header to get the pending header and as the
+ // indication of what has been already set on the pending header.
+ else {
+ let destDb;
+ // so, this can fail, and there's not much we can do about it.
+ try {
+ destDb = aDestFolder.msgDatabase;
+ } catch (ex) {
+ this.indexer._log.warn("Destination database for " +
+ aDestFolder.prettiestName +
+ " not ready on IMAP move." +
+ " Gloda corruption possible.");
+ return;
+ }
+ for (let srcMsgHdr in fixIterator(aSrcMsgHdrs, nsIMsgDBHdr)) {
+ // zero it out if it exists
+ // (no need to deal with pending commit issues here; a filthy
+ // folder by definition has nothing indexed in it.)
+ let glodaId = srcMsgHdr.getUint32Property(
+ GLODA_MESSAGE_ID_PROPERTY);
+ if (glodaId)
+ destDb.setUint32AttributeOnPendingHdr(
+ srcMsgHdr, GLODA_MESSAGE_ID_PROPERTY, 0);
+ }
+
+ // Nothing remains to be done. The msgClassified event will take
+ // care of making sure the message gets indexed.
+ return;
+ }
+ }
+
+
+ // --- Have destination headers (local case):
+ if (aDestMsgHdrs) {
+ // -- Update message keys for valid gloda-id's.
+ // (Which means ignore filthy gloda-id's.)
+ let glodaIds = [];
+ let newMessageKeys = [];
+ aSrcMsgHdrs.QueryInterface(nsIArray);
+ aDestMsgHdrs.QueryInterface(nsIArray);
+ // Track whether we see any messages that are not gloda indexed so
+ // we know if we have to mark the destination folder dirty.
+ let sawNonGlodaMessage = false;
+ for (let iMsg = 0; iMsg < aSrcMsgHdrs.length; iMsg++) {
+ let srcMsgHdr = aSrcMsgHdrs.queryElementAt(iMsg, nsIMsgDBHdr);
+ let destMsgHdr = aDestMsgHdrs.queryElementAt(iMsg, nsIMsgDBHdr);
+
+ let [glodaId, dirtyStatus] =
+ PendingCommitTracker.getGlodaState(srcMsgHdr);
+ if (glodaId >= GLODA_FIRST_VALID_MESSAGE_ID &&
+ dirtyStatus != GlodaMsgIndexer.kMessageFilthy) {
+ // we may need to update the pending commit map (it checks)
+ PendingCommitTracker.noteMove(srcMsgHdr, destMsgHdr);
+ // but we always need to update our database
+ glodaIds.push(glodaId);
+ newMessageKeys.push(destMsgHdr.messageKey);
+ }
+ else {
+ sawNonGlodaMessage = true;
+ }
+ }
+
+ // this method takes care to update the in-memory representations
+ // too; we don't need to do anything
+ if (glodaIds.length)
+ GlodaDatastore.updateMessageLocations(glodaIds, newMessageKeys,
+ aDestFolder);
+
+ // Mark the destination folder dirty if we saw any messages that
+ // were not already gloda indexed.
+ if (sawNonGlodaMessage) {
+ let destGlodaFolder = GlodaDatastore._mapFolder(aDestFolder);
+ destGlodaFolder._ensureFolderDirty();
+ this.indexer.indexingSweepNeeded = true;
+ }
+ }
+ // --- No dest headers (IMAP case):
+ // Update any valid gloda indexed messages into their new folder to
+ // make the indexer's life easier when it sees the messages in their
+ // new folder.
+ else {
+ let glodaIds = [];
+
+ let srcFolderIsLocal =
+ (srcMsgFolder instanceof nsIMsgLocalMailFolder);
+ for (let iMsgHdr = 0; iMsgHdr < aSrcMsgHdrs.length; iMsgHdr++) {
+ let msgHdr = aSrcMsgHdrs.queryElementAt(iMsgHdr, nsIMsgDBHdr);
+
+ let [glodaId, dirtyStatus] =
+ PendingCommitTracker.getGlodaState(msgHdr);
+ if (glodaId >= GLODA_FIRST_VALID_MESSAGE_ID &&
+ dirtyStatus != GlodaMsgIndexer.kMessageFilthy) {
+ // we may need to update the pending commit map (it checks)
+ PendingCommitTracker.noteBlindMove(msgHdr);
+ // but we always need to update our database
+ glodaIds.push(glodaId);
+
+ // XXX UNDO WORKAROUND
+ // This constitutes a move from a local folder to an IMAP
+ // folder. Undo does not currently do the right thing for us,
+ // but we have a chance of not orphaning the message if we
+ // mark the source header as dirty so that when the message
+ // gets re-added we see it. (This does require that we enter
+ // the folder; we set the folder dirty after the loop to
+ // increase the probability of this but it's not foolproof
+ // depending on when the next indexing sweep happens and when
+ // the user performs an undo.)
+ msgHdr.setUint32Property(GLODA_DIRTY_PROPERTY,
+ GlodaMsgIndexer.kMessageDirty);
+ }
+ }
+ // XXX ALSO UNDO WORKAROUND
+ if (srcFolderIsLocal) {
+ let srcGlodaFolder = GlodaDatastore._mapFolder(srcMsgFolder);
+ srcGlodaFolder._ensureFolderDirty();
+ }
+
+ // quickly move them to the right folder, zeroing their message keys
+ GlodaDatastore.updateMessageFoldersByKeyPurging(glodaIds,
+ aDestFolder);
+ // we _do not_ need to mark the folder as dirty, because the
+ // message added events will cause that to happen.
+ }
+ }
+ // ---- Copy case
+ else {
+ // -- Do not propagate gloda-id's for copies
+ // (Only applies if we have the destination header, which means local)
+ if (aDestMsgHdrs) {
+ for (let destMsgHdr in fixIterator(aDestMsgHdrs, nsIMsgDBHdr)) {
+ let glodaId = destMsgHdr.getUint32Property(
+ GLODA_MESSAGE_ID_PROPERTY);
+ if (glodaId)
+ destMsgHdr.setUint32Property(GLODA_MESSAGE_ID_PROPERTY, 0);
+ }
+ }
+
+ // mark the folder as dirty; we'll get to it later.
+ let destGlodaFolder = GlodaDatastore._mapFolder(aDestFolder);
+ destGlodaFolder._ensureFolderDirty();
+ this.indexer.indexingSweepNeeded = true;
+ }
+ } catch (ex) {
+ this.indexer._log.error("Problem encountered during message move/copy:",
+ ex.stack);
+ }
+ },
+
+ /**
+ * Queue up message key changes that are a result of offline fake headers
+ * being made real for the actual update during the msgsClassified
+ * notification that is expected after this. We defer the
+ * actual work (if there is any to be done; the fake header might have
+ * guessed the right UID correctly) so that we can batch our work.
+ *
+ * The expectation is that there will be no meaningful time window between
+ * this notification and the msgsClassified notification since the message
+ * classifier should not actually need to classify the messages (they
+ * should already have been classified) and so can fast-path them.
+ */
+ msgKeyChanged: function gloda_indexer_msgKeyChangeded(aOldMsgKey,
+ aNewMsgHdr) {
+ try {
+ let val = null, newKey = aNewMsgHdr.messageKey;
+ let [glodaId, glodaDirty] =
+ PendingCommitTracker.getGlodaState(aNewMsgHdr);
+ // If we haven't indexed this message yet, take no action, and leave it
+ // up to msgsClassified to take proper action.
+ if (glodaId < GLODA_FIRST_VALID_MESSAGE_ID)
+ return;
+ // take no action on filthy messages,
+ // generate an entry if dirty or the keys don't match.
+ if ((glodaDirty !== GlodaMsgIndexer.kMessageFilthy) &&
+ ((glodaDirty === GlodaMsgIndexer.kMessageDirty) ||
+ (aOldMsgKey !== newKey))) {
+ val = {
+ id: glodaId,
+ key: (aOldMsgKey !== newKey) ? newKey : null,
+ isDirty: glodaDirty === GlodaMsgIndexer.kMessageDirty,
+ };
+ }
+
+ let key = aNewMsgHdr.folder.URI + "#" + aNewMsgHdr.messageKey;
+ this.indexer._keyChangedBatchInfo[key] = val;
+ }
+ // this is more for the unit test to fail rather than user error reporting
+ catch (ex) {
+ this.indexer._log.error("Problem encountered during msgKeyChanged" +
+ " notification handling: " + ex + "\n\n" +
+ ex.stack + " \n\n");
+ }
+ },
+
+ /**
+ * Detect newly added folders before they get messages so we map them before
+ * they get any messages added to them. If we only hear about them after
+ * they get their 1st message, then we will mark them filthy, but if we mark
+ * them before that, they get marked clean.
+ */
+ folderAdded: function gloda_indexer_folderAdded(aMsgFolder) {
+ // This is invoked for its side-effect of invoking _mapFolder and doing so
+ // only after filtering out folders we don't care about.
+ GlodaMsgIndexer.shouldIndexFolder(aMsgFolder);
+ },
+
+ /**
+ * Handles folder no-longer-exists-ence. We mark all messages as deleted
+ * and remove the folder from our URI table. Currently, if a folder that
+ * contains other folders is deleted, we may either receive one
+ * notification for the folder that is deleted, or a notification for the
+ * folder and one for each of its descendents. This depends upon the
+ * underlying account implementation, so we explicitly handle each case.
+ * Namely, we treat it as if we're only planning on getting one, but we
+ * handle if the children are already gone for some reason.
+ */
+ folderDeleted: function gloda_indexer_folderDeleted(aFolder) {
+ this.indexer._log.debug("folderDeleted notification");
+ try {
+ let delFunc = function(aFolder, indexer) {
+ if (indexer._datastore._folderKnown(aFolder)) {
+ indexer._log.info("Processing deletion of folder " +
+ aFolder.prettiestName + ".");
+ let glodaFolder = GlodaDatastore._mapFolder(aFolder);
+ indexer._datastore.markMessagesDeletedByFolderID(glodaFolder.id);
+ indexer._datastore.deleteFolderByID(glodaFolder.id);
+ GlodaDatastore._killGlodaFolderIntoTombstone(glodaFolder);
+ }
+ else {
+ indexer._log.info("Ignoring deletion of folder " +
+ aFolder.prettiestName +
+ " because it is unknown to gloda.");
+ }
+ };
+
+ let descendentFolders = aFolder.descendants;
+ // (the order of operations does not matter; child, non-child, whatever.)
+ // delete the parent
+ delFunc(aFolder, this.indexer);
+ // delete all its descendents
+ for (let folder in fixIterator(descendentFolders, Ci.nsIMsgFolder)) {
+ delFunc(folder, this.indexer);
+ }
+
+ this.indexer.pendingDeletions = true;
+ } catch (ex) {
+ this.indexer._log.error("Problem encountered during folder deletion" +
+ ": " + ex + "\n\n" + ex.stack + "\n\n");
+ }
+ },
+
+ /**
+ * Handle a folder being copied or moved.
+ * Moves are handled by a helper function shared with _folderRenameHelper
+ * (which takes care of any nesting involved).
+ * Copies are actually ignored, because our periodic indexing traversal
+ * should discover these automatically. We could hint ourselves into
+ * action, but arguably a set of completely duplicate messages is not
+ * a high priority for indexing.
+ */
+ folderMoveCopyCompleted: function gloda_indexer_folderMoveCopyCompleted(
+ aMove, aSrcFolder, aDestFolder) {
+ this.indexer._log.debug("folderMoveCopy notification (Move: " + aMove
+ + ")");
+ if (aMove) {
+ let srcURI = aSrcFolder.URI;
+ let targetURI = aDestFolder.URI +
+ srcURI.substring(srcURI.lastIndexOf("/"));
+ this._folderRenameHelper(aSrcFolder, targetURI);
+ }
+ else {
+ this.indexer.indexingSweepNeeded = true;
+ }
+ },
+
+ /**
+ * We just need to update the URI <-> ID maps and the row in the database,
+ * all of which is actually done by the datastore for us.
+ * This method needs to deal with the complexity where local folders will
+ * generate a rename notification for each sub-folder, but IMAP folders
+ * will generate only a single notification. Our logic primarily handles
+ * this by not exploding if the original folder no longer exists.
+ */
+ _folderRenameHelper: function gloda_indexer_folderRenameHelper(aOrigFolder,
+ aNewURI) {
+ let newFolder = MailUtils.getFolderForURI(aNewURI);
+ let specialFolderFlags = Ci.nsMsgFolderFlags.Trash | Ci.nsMsgFolderFlags.Junk;
+ if (newFolder.isSpecialFolder(specialFolderFlags, true)) {
+ let descendentFolders = newFolder.descendants;
+
+ // First thing to do: make sure we don't index the resulting folder and
+ // its descendents.
+ GlodaMsgIndexer.resetFolderIndexingPriority(newFolder);
+ for (let folder in fixIterator(descendentFolders, Ci.nsIMsgFolder)) {
+ GlodaMsgIndexer.resetFolderIndexingPriority(folder);
+ }
+
+ // Remove from the index messages from the original folder
+ this.folderDeleted(aOrigFolder);
+ } else {
+ let descendentFolders = aOrigFolder.descendants;
+
+ let origURI = aOrigFolder.URI;
+ // this rename is straightforward.
+ GlodaDatastore.renameFolder(aOrigFolder, aNewURI);
+
+ for (let folder in fixIterator(descendentFolders, Ci.nsIMsgFolder)) {
+ let oldSubURI = folder.URI;
+ // mangle a new URI from the old URI. we could also try and do a
+ // parallel traversal of the new folder hierarchy, but that seems like
+ // more work.
+ let newSubURI = aNewURI + oldSubURI.substring(origURI.length);
+ this.indexer._datastore.renameFolder(oldSubURI, newSubURI);
+ }
+
+ this.indexer._log.debug("folder renamed: " + origURI + " to " + aNewURI);
+ }
+ },
+
+ /**
+ * Handle folder renames, dispatching to our rename helper (which also
+ * takes care of any nested folder issues.)
+ */
+ folderRenamed: function gloda_indexer_folderRenamed(aOrigFolder,
+ aNewFolder) {
+ this._folderRenameHelper(aOrigFolder, aNewFolder.URI);
+ },
+
+ /**
+ * This tells us about many exciting things. What they are and what we do:
+ *
+ * - FolderCompactStart: Mark the folder as compacting in our in-memory
+ * representation. This should keep any new indexing out of the folder
+ * until it is done compacting. Also, kill any active or existing jobs
+ * to index the folder.
+ * - FolderCompactFinish: Mark the folder as done compacting in our
+ * in-memory representation. Assuming the folder was known to us and
+ * not marked filthy, queue a compaction job.
+ *
+ * - FolderReindexTriggered: We do the same thing as FolderCompactStart
+ * but don't mark the folder as compacting.
+ *
+ * - JunkStatusChanged: We mark the messages that have had their junk
+ * state change to be reindexed.
+ */
+ itemEvent: function gloda_indexer_itemEvent(aItem, aEvent, aData) {
+ // Compact and Reindex are close enough that we can reuse the same code
+ // with one minor difference.
+ if (aEvent == "FolderCompactStart" ||
+ aEvent == "FolderReindexTriggered") {
+ let aMsgFolder = aItem.QueryInterface(nsIMsgFolder);
+ // ignore folders we ignore...
+ if (!GlodaMsgIndexer.shouldIndexFolder(aMsgFolder))
+ return;
+
+ let glodaFolder = GlodaDatastore._mapFolder(aMsgFolder);
+ if (aEvent == "FolderCompactStart")
+ glodaFolder.compacting = true;
+
+ // Purge any explicit indexing of said folder.
+ GlodaIndexer.purgeJobsUsingFilter(function (aJob) {
+ return (aJob.jobType == "folder" &&
+ aJob.id == aMsgFolder.id);
+ });
+
+ // Abort the active job if it's in the folder (this covers both
+ // event-driven indexing that happens to be in the folder as well
+ // explicit folder indexing of the folder).
+ if (GlodaMsgIndexer._indexingFolder == aMsgFolder)
+ GlodaIndexer.killActiveJob();
+
+ // Tell the PendingCommitTracker to throw away anything it is tracking
+ // about the folder. We will pick up the pieces in the compaction
+ // pass.
+ PendingCommitTracker.noteFolderDatabaseGettingBlownAway(aMsgFolder);
+
+ // (We do not need to mark the folder dirty because if we were indexing
+ // it, it already must have been marked dirty.)
+ }
+ else if (aEvent == "FolderCompactFinish") {
+ let aMsgFolder = aItem.QueryInterface(nsIMsgFolder);
+ // ignore folders we ignore...
+ if (!GlodaMsgIndexer.shouldIndexFolder(aMsgFolder))
+ return;
+
+ let glodaFolder = GlodaDatastore._mapFolder(aMsgFolder);
+ glodaFolder.compacting = false;
+ glodaFolder._setCompactedState(true);
+
+ // Queue compaction unless the folder was filthy (in which case there
+ // are no valid gloda-id's to update.)
+ if (glodaFolder.dirtyStatus != glodaFolder.kFolderFilthy)
+ GlodaIndexer.indexJob(
+ new IndexingJob("folderCompact", glodaFolder.id));
+
+ // Queue indexing of the folder if it is dirty. We are doing this
+ // mainly in case we were indexing it before the compaction started.
+ // It should be reasonably harmless if we weren't.
+ // (It would probably be better to just make sure that there is an
+ // indexing sweep queued or active, and if it's already active that
+ // this folder is in the queue to be processed.)
+ if (glodaFolder.dirtyStatus == glodaFolder.kFolderDirty)
+ GlodaIndexer.indexJob(new IndexingJob("folder", glodaFolder.id));
+ }
+ else if (aEvent == "JunkStatusChanged") {
+ this.indexer._log.debug("JunkStatusChanged notification");
+ aItem.QueryInterface(Ci.nsIArray);
+ GlodaMsgIndexer._reindexChangedMessages(aItem.enumerate(), true);
+ }
+ },
+ },
+
+ /**
+ * A nsIFolderListener (listening on nsIMsgMailSession so we get all of
+ * these events) PRIMARILY to get folder loaded notifications. Because of
+ * deficiencies in the nsIMsgFolderListener's events at this time, we also
+ * get our folder-added and newsgroup notifications from here for now. (This
+ * will be rectified.)
+ */
+ _folderListener: {
+ indexer: null,
+
+ _init: function gloda_indexer_fl_init(aIndexer) {
+ this.indexer = aIndexer;
+ },
+
+ // We explicitly know about these things rather than bothering with some
+ // form of registration scheme because these aren't going to change much.
+ get _kFolderLoadedAtom() {
+ delete this._kFolderLoadedAtom;
+ return this._kFolderLoadedAtom = atomService.getAtom("FolderLoaded");
+ },
+ get _kKeywordsAtom() {
+ delete this._kKeywordsAtom;
+ return this._kKeywordsAtom = atomService.getAtom("Keywords");
+ },
+ get _kStatusAtom() {
+ delete this._kStatusAtom;
+ return this._kStatusAtom = atomService.getAtom("Status");
+ },
+ get _kFlaggedAtom() {
+ delete this._kFlaggedAtom;
+ return this._kFlaggedAtom = atomService.getAtom("Flagged");
+ },
+ get _kFolderFlagAtom() {
+ delete this._kFolderFlagAtom;
+ return this._kFolderFlagAtom = atomService.getAtom("FolderFlag");
+ },
+
+ OnItemAdded: function gloda_indexer_OnItemAdded(aParentItem, aItem) {
+ },
+ OnItemRemoved: function gloda_indexer_OnItemRemoved(aParentItem, aItem) {
+ },
+ OnItemPropertyChanged: function gloda_indexer_OnItemPropertyChanged(
+ aItem, aProperty, aOldValue, aNewValue) {
+ },
+ /**
+ * Detect changes to folder flags and reset our indexing priority. This
+ * is important because (all?) folders start out without any flags and
+ * then get their flags added to them.
+ */
+ OnItemIntPropertyChanged: function gloda_indexer_OnItemIntPropertyChanged(
+ aFolderItem, aProperty, aOldValue, aNewValue) {
+ if (aProperty !== this._kFolderFlagAtom)
+ return;
+ if (!GlodaMsgIndexer.shouldIndexFolder(aFolderItem))
+ return;
+ // Only reset priority if folder Special Use changes.
+ if ((aOldValue & Ci.nsMsgFolderFlags.SpecialUse) ==
+ (aNewValue & Ci.nsMsgFolderFlags.SpecialUse))
+ return;
+ GlodaMsgIndexer.resetFolderIndexingPriority(aFolderItem);
+ },
+ OnItemBoolPropertyChanged: function gloda_indexer_OnItemBoolPropertyChanged(
+ aItem, aProperty, aOldValue, aNewValue) {
+ },
+ OnItemUnicharPropertyChanged:
+ function gloda_indexer_OnItemUnicharPropertyChanged(
+ aItem, aProperty, aOldValue, aNewValue) {
+
+ },
+ /**
+ * Notice when user activity adds/removes tags or changes a message's
+ * status.
+ */
+ OnItemPropertyFlagChanged: function gloda_indexer_OnItemPropertyFlagChanged(
+ aMsgHdr, aProperty, aOldValue, aNewValue) {
+ if (aProperty == this._kKeywordsAtom ||
+ // We could care less about the new flag changing.
+ (aProperty == this._kStatusAtom &&
+ (aOldValue ^ aNewValue) != nsMsgMessageFlags.New &&
+ // We do care about IMAP deletion, but msgsDeleted tells us that, so
+ // ignore IMAPDeleted too...
+ (aOldValue ^ aNewValue) != nsMsgMessageFlags.IMAPDeleted) ||
+ aProperty == this._kFlaggedAtom) {
+ GlodaMsgIndexer._reindexChangedMessages([aMsgHdr], true);
+ }
+ },
+
+ /**
+ * Get folder loaded notifications for folders that had to do some
+ * (asynchronous) processing before they could be opened.
+ */
+ OnItemEvent: function gloda_indexer_OnItemEvent(aFolder, aEvent) {
+ if (aEvent == this._kFolderLoadedAtom)
+ this.indexer._onFolderLoaded(aFolder);
+ },
+ },
+
+ /* ***** Rebuilding / Reindexing ***** */
+ /**
+ * Allow us to invalidate an outstanding folder traversal because the
+ * underlying database is going away. We use other means for detecting
+ * modifications of the message (labeling, marked (un)read, starred, etc.)
+ *
+ * This is an nsIDBChangeListener listening to an nsIDBChangeAnnouncer. To
+ * add ourselves, we get us a nice nsMsgDatabase, query it to the announcer,
+ * then call AddListener.
+ */
+ _databaseAnnouncerListener: {
+ indexer: null,
+ /**
+ * XXX We really should define the operations under which we expect this to
+ * occur. While we know this must be happening as the result of a
+ * ForceClosed call, we don't have a comprehensive list of when this is
+ * expected to occur. Some reasons:
+ * - Compaction (although we should already have killed the job thanks to
+ * our compaction notification)
+ * - UID validity rolls.
+ * - Folder Rename
+ * - Folder Delete
+ * The fact that we already have the database open when getting this means
+ * that it had to be valid before we opened it, which hopefully rules out
+ * modification of the mbox file by an external process (since that is
+ * forbidden when we are running) and many other exotic things.
+ *
+ * So this really ends up just being a correctness / safety protection
+ * mechanism. At least now that we have better compaction support.
+ */
+ onAnnouncerGoingAway: function gloda_indexer_dbGoingAway(
+ aDBChangeAnnouncer) {
+ // The fact that we are getting called means we have an active folder and
+ // that we therefore are the active job. As such, we must kill the
+ // active job.
+ // XXX In the future, when we support interleaved event-driven indexing
+ // that bumps long-running indexing tasks, the semantics of this will
+ // have to change a bit since we will want to maintain being active in a
+ // folder even when bumped. However, we will probably have a more
+ // complex notion of indexing contexts on a per-job basis.
+ GlodaIndexer.killActiveJob();
+ },
+
+ onHdrFlagsChanged: function(aHdrChanged, aOldFlags, aNewFlags, aInstigator) {},
+ onHdrDeleted: function(aHdrChanged, aParentKey, aFlags, aInstigator) {},
+ onHdrAdded: function(aHdrChanged, aParentKey, aFlags, aInstigator) {},
+ onParentChanged: function(aKeyChanged, aOldParent, aNewParent,
+ aInstigator) {},
+ onReadChanged: function(aInstigator) {},
+ onJunkScoreChanged: function(aInstigator) {},
+ onHdrPropertyChanged: function (aHdrToChange, aPreChange, aStatus,
+ aInstigator) {},
+ onEvent: function (aDB, aEvent) {},
+ },
+
+ /**
+ * Given a list of Message-ID's, return a matching list of lists of messages
+ * matching those Message-ID's. So if you pass an array with three
+ * Message-ID's ["a", "b", "c"], you would get back an array containing
+ * 3 lists, where the first list contains all the messages with a message-id
+ * of "a", and so forth. The reason a list is returned rather than null/a
+ * message is that we accept the reality that we have multiple copies of
+ * messages with the same ID.
+ * This call is asynchronous because it depends on previously created messages
+ * to be reflected in our results, which requires us to execute on the async
+ * thread where all our writes happen. This also turns out to be a
+ * reasonable thing because we could imagine pathological cases where there
+ * could be a lot of message-id's and/or a lot of messages with those
+ * message-id's.
+ *
+ * The returned collection will include both 'ghost' messages (messages
+ * that exist for conversation-threading purposes only) as well as deleted
+ * messages in addition to the normal 'live' messages that non-privileged
+ * queries might return.
+ */
+ getMessagesByMessageID: function gloda_ns_getMessagesByMessageID(aMessageIDs,
+ aCallback, aCallbackThis) {
+ let msgIDToIndex = {};
+ let results = [];
+ for (let iID = 0; iID < aMessageIDs.length; ++iID) {
+ let msgID = aMessageIDs[iID];
+ results.push([]);
+ msgIDToIndex[msgID] = iID;
+ }
+
+ // (Note: although we are performing a lookup with no validity constraints
+ // and using the same object-relational-mapper-ish layer used by things
+ // that do have constraints, we are not at risk of exposing deleted
+ // messages to other code and getting it confused. The only way code
+ // can find a message is if it shows up in their queries or gets announced
+ // via GlodaCollectionManager.itemsAdded, neither of which will happen.)
+ let query = Gloda.newQuery(Gloda.NOUN_MESSAGE, {
+ noDbQueryValidityConstraints: true,
+ });
+ query.headerMessageID.apply(query, aMessageIDs);
+ query.frozen = true;
+
+ let listener = new MessagesByMessageIdCallback(msgIDToIndex, results,
+ aCallback, aCallbackThis);
+ return query.getCollection(listener, null, {becomeNull: true});
+ },
+
+ /**
+ * A reference to MsgHdrToMimeMessage that unit testing can clobber when it
+ * wants to cause us to hang or inject a fault. If you are not
+ * glodaTestHelper.js then _do not touch this_.
+ */
+ _MsgHdrToMimeMessageFunc: MsgHdrToMimeMessage,
+ /**
+ * Primary message indexing logic. This method is mainly concerned with
+ * getting all the information about the message required for threading /
+ * conversation building and subsequent processing. It is responsible for
+ * determining whether to reuse existing gloda messages or whether a new one
+ * should be created. Most attribute stuff happens in fund_attr.js or
+ * expl_attr.js.
+ *
+ * Prior to calling this method, the caller must have invoked
+ * |_indexerEnterFolder|, leaving us with the following true invariants
+ * below.
+ *
+ * @pre aMsgHdr.folder == this._indexingFolder
+ * @pre aMsgHdr.folder.msgDatabase == this._indexingDatabase
+ */
+ _indexMessage: function* gloda_indexMessage(aMsgHdr, aCallbackHandle) {
+ let logDebug = this._log.level <= Log4Moz.Level.Debug;
+ if (logDebug)
+ this._log.debug("*** Indexing message: " + aMsgHdr.messageKey + " : " +
+ aMsgHdr.subject);
+
+ // If the message is offline, then get the message body as well
+ let isMsgOffline = false;
+ let aMimeMsg;
+ if ((aMsgHdr.flags & nsMsgMessageFlags.Offline) ||
+ (aMsgHdr.folder instanceof nsIMsgLocalMailFolder)) {
+ isMsgOffline = true;
+ this._MsgHdrToMimeMessageFunc(aMsgHdr, aCallbackHandle.callbackThis,
+ aCallbackHandle.callback, false, {saneBodySize: true});
+ aMimeMsg = (yield this.kWorkAsync)[1];
+ }
+ else {
+ if (logDebug)
+ this._log.debug(" * Message is not offline -- only headers indexed");
+ }
+
+ if (logDebug)
+ this._log.debug(" * Got message, subject " + aMsgHdr.subject);
+
+ if (this._unitTestSuperVerbose) {
+ if (aMimeMsg)
+ this._log.debug(" * Got Mime " + aMimeMsg.prettyString());
+ else
+ this._log.debug(" * NO MIME MESSAGE!!!\n");
+ }
+
+ // -- Find/create the conversation the message belongs to.
+ // Our invariant is that all messages that exist in the database belong to
+ // a conversation.
+
+ // - See if any of the ancestors exist and have a conversationID...
+ // (references are ordered from old [0] to new [n-1])
+ let references = Array.from(range(0, aMsgHdr.numReferences)).
+ map(i => aMsgHdr.getStringReference(i));
+ // also see if we already know about the message...
+ references.push(aMsgHdr.messageId);
+
+ this.getMessagesByMessageID(references, aCallbackHandle.callback,
+ aCallbackHandle.callbackThis);
+ // (ancestorLists has a direct correspondence to the message ids)
+ let ancestorLists = yield this.kWorkAsync;
+
+ if (logDebug) {
+ this._log.debug("ancestors raw: " + ancestorLists);
+ this._log.debug("ref len: " + references.length +
+ " anc len: " + ancestorLists.length);
+ this._log.debug("references: " +
+ Log4Moz.enumerateProperties(references).join(","));
+ this._log.debug("ancestors: " +
+ Log4Moz.enumerateProperties(ancestorLists).join(","));
+ }
+
+ // pull our current message lookup results off
+ references.pop();
+ let candidateCurMsgs = ancestorLists.pop();
+
+ let conversationID = null;
+ let conversation = null;
+ // -- figure out the conversation ID
+ // if we have a clone/already exist, just use his conversation ID
+ if (candidateCurMsgs.length > 0) {
+ conversationID = candidateCurMsgs[0].conversationID;
+ conversation = candidateCurMsgs[0].conversation;
+ }
+ // otherwise check out our ancestors
+ else {
+ // (walk from closest to furthest ancestor)
+ for (let iAncestor = ancestorLists.length-1; iAncestor >= 0;
+ --iAncestor) {
+ let ancestorList = ancestorLists[iAncestor];
+
+ if (ancestorList.length > 0) {
+ // we only care about the first instance of the message because we are
+ // able to guarantee the invariant that all messages with the same
+ // message id belong to the same conversation.
+ let ancestor = ancestorList[0];
+ if (conversationID === null) {
+ conversationID = ancestor.conversationID;
+ conversation = ancestor.conversation;
+ }
+ else if (conversationID != ancestor.conversationID) {
+ // XXX this inconsistency is known and understood and tracked by
+ // bug 478162 https://bugzilla.mozilla.org/show_bug.cgi?id=478162
+ //this._log.error("Inconsistency in conversations invariant on " +
+ // ancestor.headerMessageID + ". It has conv id " +
+ // ancestor.conversationID + " but expected " +
+ // conversationID + ". ID: " + ancestor.id);
+ }
+ }
+ }
+ }
+
+ // nobody had one? create a new conversation
+ if (conversationID === null) {
+ // (the create method could issue the id, making the call return
+ // without waiting for the database...)
+ conversation = this._datastore.createConversation(
+ aMsgHdr.mime2DecodedSubject, null, null);
+ conversationID = conversation.id;
+ }
+
+ // Walk from furthest to closest ancestor, creating the ancestors that don't
+ // exist. (This is possible if previous messages that were consumed in this
+ // thread only had an in-reply-to or for some reason did not otherwise
+ // provide the full references chain.)
+ for (let iAncestor = 0; iAncestor < ancestorLists.length; ++iAncestor) {
+ let ancestorList = ancestorLists[iAncestor];
+
+ if (ancestorList.length == 0) {
+ if (logDebug)
+ this._log.debug("creating message with: null, " + conversationID +
+ ", " + references[iAncestor] +
+ ", null.");
+ let ancestor = this._datastore.createMessage(null, null, // ghost
+ conversationID, null,
+ references[iAncestor],
+ null, // no subject
+ null, // no body
+ null); // no attachments
+ this._datastore.insertMessage(ancestor);
+ ancestorLists[iAncestor].push(ancestor);
+ }
+ }
+ // now all our ancestors exist, though they may be ghost-like...
+
+ // find if there's a ghost version of our message or we already have indexed
+ // this message.
+ let curMsg = null;
+ if (logDebug)
+ this._log.debug(candidateCurMsgs.length + " candidate messages");
+ for (let iCurCand = 0; iCurCand < candidateCurMsgs.length; iCurCand++) {
+ let candMsg = candidateCurMsgs[iCurCand];
+
+ if (logDebug)
+ this._log.debug("candidate folderID: " + candMsg.folderID +
+ " messageKey: " + candMsg.messageKey);
+
+ if (candMsg.folderURI == this._indexingFolder.URI) {
+ // if we are in the same folder and we have the same message key, we
+ // are definitely the same, stop looking.
+ if (candMsg.messageKey == aMsgHdr.messageKey) {
+ curMsg = candMsg;
+ break;
+ }
+ // if (we are in the same folder and) the candidate message has a null
+ // message key, we treat it as our best option unless we find an exact
+ // key match. (this would happen because the 'move' notification case
+ // has to deal with not knowing the target message key. this case
+ // will hopefully be somewhat improved in the future to not go through
+ // this path which mandates re-indexing of the message in its entirety)
+ if (candMsg.messageKey === null)
+ curMsg = candMsg;
+ // if (we are in the same folder and) the candidate message's underlying
+ // message no longer exists/matches, we'll assume we are the same but
+ // were betrayed by a re-indexing or something, but we have to make
+ // sure a perfect match doesn't turn up.
+ else if ((curMsg === null) &&
+ !this._indexingDatabase.ContainsKey(candMsg.messageKey))
+ curMsg = candMsg;
+ }
+ // a ghost/deleted message is fine
+ else if ((curMsg === null) && (candMsg.folderID === null)) {
+ curMsg = candMsg;
+ }
+ }
+
+ let attachmentNames = null;
+ if (aMimeMsg) {
+ attachmentNames = aMimeMsg.allAttachments.
+ filter(att => att.isRealAttachment).map(att => att.name);
+ }
+
+ let isConceptuallyNew, isRecordNew, insertFulltext;
+ if (curMsg === null) {
+ curMsg = this._datastore.createMessage(aMsgHdr.folder,
+ aMsgHdr.messageKey,
+ conversationID,
+ aMsgHdr.date,
+ aMsgHdr.messageId);
+ curMsg._conversation = conversation;
+ isConceptuallyNew = isRecordNew = insertFulltext = true;
+ }
+ else {
+ isRecordNew = false;
+ // the message is conceptually new if it was a ghost or dead.
+ isConceptuallyNew = curMsg._isGhost || curMsg._isDeleted;
+ // insert fulltext if it was a ghost
+ insertFulltext = curMsg._isGhost;
+ curMsg._folderID = this._datastore._mapFolder(aMsgHdr.folder).id;
+ curMsg._messageKey = aMsgHdr.messageKey;
+ curMsg.date = new Date(aMsgHdr.date / 1000);
+ // the message may have been deleted; tell it to make sure it's not.
+ curMsg._ensureNotDeleted();
+ // note: we are assuming that our matching logic is flawless in that
+ // if this message was not a ghost, we are assuming the 'body'
+ // associated with the id is still exactly the same. It is conceivable
+ // that there are cases where this is not true.
+ }
+
+ if (aMimeMsg) {
+ let bodyPlain = aMimeMsg.coerceBodyToPlaintext(aMsgHdr.folder);
+ if (bodyPlain) {
+ curMsg._bodyLines = bodyPlain.split(/\r?\n/);
+ // curMsg._content gets set by fundattr.js
+ }
+ }
+
+ // Mark the message as new (for the purposes of fulltext insertion)
+ if (insertFulltext)
+ curMsg._isNew = true;
+
+ curMsg._subject = aMsgHdr.mime2DecodedSubject;
+ curMsg._attachmentNames = attachmentNames;
+
+ // curMsg._indexAuthor gets set by fundattr.js
+ // curMsg._indexRecipients gets set by fundattr.js
+
+ // zero the notability so everything in grokNounItem can just increment
+ curMsg.notability = 0;
+
+ yield aCallbackHandle.pushAndGo(
+ Gloda.grokNounItem(curMsg,
+ {header: aMsgHdr, mime: aMimeMsg, bodyLines: curMsg._bodyLines},
+ isConceptuallyNew, isRecordNew,
+ aCallbackHandle));
+
+ delete curMsg._bodyLines;
+ delete curMsg._content;
+ delete curMsg._isNew;
+ delete curMsg._indexAuthor;
+ delete curMsg._indexRecipients;
+
+ // we want to update the header for messages only after the transaction
+ // irrevocably hits the disk. otherwise we could get confused if the
+ // transaction rolls back or what not.
+ PendingCommitTracker.track(aMsgHdr, curMsg.id);
+
+ yield this.kWorkDone;
+ },
+
+ /**
+ * Wipe a message out of existence from our index. This is slightly more
+ * tricky than one would first expect because there are potentially
+ * attributes not immediately associated with this message that reference
+ * the message. Not only that, but deletion of messages may leave a
+ * conversation posessing only ghost messages, which we don't want, so we
+ * need to nuke the moot conversation and its moot ghost messages.
+ * For now, we are actually punting on that trickiness, and the exact
+ * nuances aren't defined yet because we have not decided whether to store
+ * such attributes redundantly. For example, if we have subject-pred-object,
+ * we could actually store this as attributes (subject, id, object) and
+ * (object, id, subject). In such a case, we could query on (subject, *)
+ * and use the results to delete the (object, id, subject) case. If we
+ * don't redundantly store attributes, we can deal with the problem by
+ * collecting up all the attributes that accept a message as their object
+ * type and issuing a delete against that. For example, delete (*, [1,2,3],
+ * message id).
+ * (We are punting because we haven't implemented support for generating
+ * attributes like that yet.)
+ *
+ * @TODO: implement deletion of attributes that reference (deleted) messages
+ */
+ _deleteMessage: function* gloda_index_deleteMessage(aMessage,
+ aCallbackHandle) {
+ let logDebug = this._log.level <= Log4Moz.Level.Debug;
+ if (logDebug)
+ this._log.debug("*** Deleting message: " + aMessage);
+
+ // -- delete our attributes
+ // delete the message's attributes (if we implement the cascade delete, that
+ // could do the honors for us... right now we define the trigger in our
+ // schema but the back-end ignores it)
+ GlodaDatastore.clearMessageAttributes(aMessage);
+
+ // -- delete our message or ghost us, and maybe nuke the whole conversation
+ // Look at the other messages in the conversation.
+ // (Note: although we are performing a lookup with no validity constraints
+ // and using the same object-relational-mapper-ish layer used by things
+ // that do have constraints, we are not at risk of exposing deleted
+ // messages to other code and getting it confused. The only way code
+ // can find a message is if it shows up in their queries or gets announced
+ // via GlodaCollectionManager.itemsAdded, neither of which will happen.)
+ let convPrivQuery = Gloda.newQuery(Gloda.NOUN_MESSAGE, {
+ noDbQueryValidityConstraints: true,
+ });
+ convPrivQuery.conversation(aMessage.conversation);
+ let conversationCollection = convPrivQuery.getCollection(aCallbackHandle);
+ yield this.kWorkAsync;
+
+ let conversationMsgs = conversationCollection.items;
+
+ // Count the number of ghosts messages we see to determine if we are
+ // the last message alive.
+ let ghostCount = 0;
+ let twinMessageExists = false;
+ for (let convMsg of conversationMsgs) {
+ // ignore our own message
+ if (convMsg.id == aMessage.id)
+ continue;
+
+ if (convMsg._isGhost)
+ ghostCount++;
+ // This message is our (living) twin if it is not a ghost, not deleted,
+ // and has the same message-id header.
+ else if (!convMsg._isDeleted &&
+ convMsg.headerMessageID == aMessage.headerMessageID)
+ twinMessageExists = true;
+ }
+
+ // -- If everyone else is a ghost, blow away the conversation.
+ // If there are messages still alive or deleted but we have not yet gotten
+ // to them yet _deleteMessage, then do not do this. (We will eventually
+ // hit this case if they are all deleted.)
+ if ((conversationMsgs.length - 1) == ghostCount) {
+ // - Obliterate each message
+ for (let msg of conversationMsgs) {
+ GlodaDatastore.deleteMessageByID(msg.id);
+ }
+ // - Obliterate the conversation
+ GlodaDatastore.deleteConversationByID(aMessage.conversationID);
+ // *no one* should hold a reference or use aMessage after this point,
+ // trash it so such ne'er do'wells are made plain.
+ aMessage._objectPurgedMakeYourselfUnpleasant();
+ }
+ // -- Ghost or purge us as appropriate
+ else {
+ // Purge us if we have a (living) twin; no ghost required.
+ if (twinMessageExists) {
+ GlodaDatastore.deleteMessageByID(aMessage.id);
+ // *no one* should hold a reference or use aMessage after this point,
+ // trash it so such ne'er do'wells are made plain.
+ aMessage._objectPurgedMakeYourselfUnpleasant();
+ }
+ // No twin, a ghost is required, we become the ghost.
+ else {
+ aMessage._ghost();
+ GlodaDatastore.updateMessage(aMessage);
+ // ghosts don't have fulltext. purge it.
+ GlodaDatastore.deleteMessageTextByID(aMessage.id);
+ }
+ }
+
+ yield this.kWorkDone;
+ },
+};
+GlodaIndexer.registerIndexer(GlodaMsgIndexer);
diff --git a/mailnews/db/gloda/modules/indexer.js b/mailnews/db/gloda/modules/indexer.js
new file mode 100644
index 000000000..f6c939530
--- /dev/null
+++ b/mailnews/db/gloda/modules/indexer.js
@@ -0,0 +1,1409 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file currently contains a fairly general implementation of asynchronous
+ * indexing with a very explicit message indexing implementation. As gloda
+ * will eventually want to index more than just messages, the message-specific
+ * things should ideally lose their special hold on this file. This will
+ * benefit readability/size as well.
+ */
+
+this.EXPORTED_SYMBOLS = ['GlodaIndexer', 'IndexingJob'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/iteratorUtils.jsm");
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+Cu.import("resource:///modules/gloda/utils.js");
+Cu.import("resource:///modules/gloda/datastore.js");
+Cu.import("resource:///modules/gloda/gloda.js");
+Cu.import("resource:///modules/gloda/collection.js");
+Cu.import("resource:///modules/gloda/connotent.js");
+
+/**
+ * @class Capture the indexing batch concept explicitly.
+ *
+ * @param aJobType The type of thing we are indexing. Current choices are:
+ * "folder" and "message". Previous choices included "account". The indexer
+ * currently knows too much about these; they should be de-coupled.
+ * @param aID Specific to the job type, but for now only used to hold folder
+ * IDs.
+ *
+ * @ivar items The list of items to process during this job/batch. (For
+ * example, if this is a "messages" job, this would be the list of messages
+ * to process, although the specific representation is determined by the
+ * job.) The list will only be mutated through the addition of extra items.
+ * @ivar offset The current offset into the 'items' list (if used), updated as
+ * processing occurs. If 'items' is not used, the processing code can also
+ * update this in a similar fashion. This is used by the status
+ * notification code in conjunction with goal.
+ * @ivar goal The total number of items to index/actions to perform in this job.
+ * This number may increase during the life of the job, but should not
+ * decrease. This is used by the status notification code in conjunction
+ * with the goal.
+ */
+function IndexingJob(aJobType, aID, aItems) {
+ this.jobType = aJobType;
+ this.id = aID;
+ this.items = (aItems != null) ? aItems : [];
+ this.offset = 0;
+ this.goal = null;
+ this.callback = null;
+ this.callbackThis = null;
+}
+IndexingJob.prototype = {
+ /**
+ * Invoke the callback associated with this job, passing through all arguments
+ * received by this function to the callback function.
+ */
+ safelyInvokeCallback: function() {
+ if (!this.callback)
+ return;
+ try {
+ this.callback.apply(this.callbackThis, arguments);
+ }
+ catch(ex) {
+ GlodaIndexer._log.warn("job callback invocation problem:", ex);
+ }
+ },
+ toString: function IndexingJob_toString() {
+ return "[job:" + this.jobType +
+ " id:" + this.id + " items:" + (this.items ? this.items.length : "no") +
+ " offset:" + this.offset + " goal:" + this.goal + "]";
+ }
+};
+
+/**
+ * @namespace Core indexing logic, plus message-specific indexing logic.
+ *
+ * === Indexing Goals
+ * We have the following goals:
+ *
+ * Responsiveness
+ * - When the user wants to quit, we should be able to stop and quit in a timely
+ * fasion.
+ * - We should not interfere with the user's thunderbird usage.
+ *
+ * Correctness
+ * - Quitting should not result in any information loss; we should (eventually)
+ * end up at the same indexed state regardless of whether a user lets
+ * indexing run to completion or restarts thunderbird in the middle of the
+ * process. (It is okay to take slightly longer in the latter case.)
+ *
+ * Worst Case Scenario Avoidance
+ * - We should try to be O(1) memory-wise regardless of what notifications
+ * are thrown at us.
+ *
+ * === Indexing Throttling
+ *
+ * Adaptive Indexing
+ * - The indexer tries to stay out of the way of other running code in
+ * Thunderbird (autosync) and other code on the system. We try and target
+ * some number of milliseconds of activity between intentional inactive
+ * periods. The number of milliseconds of activity varies based on whether we
+ * believe the user to be actively using the computer or idle. We use our
+ * inactive periods as a way to measure system load; if we receive our
+ * notification promptly at the end of our inactive period, we believe the
+ * system is not heavily loaded. If we do not get notified promptly, we
+ * assume there is other stuff going on and back off.
+ *
+ */
+var GlodaIndexer = {
+ /**
+ * A partial attempt to generalize to support multiple databases. Each
+ * database would have its own datastore would have its own indexer. But
+ * we rather inter-mingle our use of this field with the singleton global
+ * GlodaDatastore.
+ */
+ _datastore: GlodaDatastore,
+ _log: Log4Moz.repository.getLogger("gloda.indexer"),
+ /**
+ * Our nsITimer that we use to schedule ourselves on the main thread
+ * intermittently. The timer always exists but may not always be active.
+ */
+ _timer: null,
+ /**
+ * Our nsITimer that we use to schedule events in the "far" future. For now,
+ * this means not compelling an initial indexing sweep until some number of
+ * seconds after startup.
+ */
+ _longTimer: null,
+
+ /**
+ * Periodic performance adjustment parameters: The overall goal is to adjust
+ * our rate of work so that we don't interfere with the user's activities
+ * when they are around (non-idle), and the system in general (when idle).
+ * Being nice when idle isn't quite as important, but is a good idea so that
+ * when the user un-idles we are able to back off nicely. Also, we give
+ * other processes on the system a chance to do something.
+ *
+ * We do this by organizing our work into discrete "tokens" of activity,
+ * then processing the number of tokens that we have determined will
+ * not impact the UI. Then we pause to give other activities a chance to get
+ * some work done, and we measure whether anything happened during our pause.
+ * If something else is going on in our application during that pause, we
+ * give it priority (up to a point) by delaying further indexing.
+ *
+ * Keep in mind that many of our operations are actually asynchronous, so we
+ * aren't entirely starving the event queue. However, a lot of the async
+ * stuff can end up not having any actual delay between events. For
+ * example, we only index offline message bodies, so there's no network
+ * latency involved, just disk IO; the only meaningful latency will be the
+ * initial disk seek (if there is one... pre-fetching may seriously be our
+ * friend).
+ *
+ * In order to maintain responsiveness, I assert that we want to minimize the
+ * length of the time we are dominating the event queue. This suggests
+ * that we want break up our blocks of work frequently. But not so
+ * frequently that there is a lot of waste. Accordingly our algorithm is
+ * basically:
+ *
+ * - Estimate the time that it takes to process a token, and schedule the
+ * number of tokens that should fit into that time.
+ * - Detect user activity, and back off immediately if found.
+ * - Try to delay commits and garbage collection until the user is inactive,
+ * as these tend to cause a brief pause in the UI.
+ */
+
+ /**
+ * The number of milliseconds before we declare the user idle and step up our
+ * indexing.
+ */
+ _INDEX_IDLE_ADJUSTMENT_TIME: 5000,
+
+ /**
+ * The time delay in milliseconds before we should schedule our initial sweep.
+ */
+ _INITIAL_SWEEP_DELAY: 10000,
+
+ /**
+ * How many milliseconds in the future should we schedule indexing to start
+ * when turning on indexing (and it was not previously active).
+ */
+ _INDEX_KICKOFF_DELAY: 200,
+
+ /**
+ * The time interval, in milliseconds, of pause between indexing batches. The
+ * maximum processor consumption is determined by this constant and the
+ * active |_cpuTargetIndexTime|.
+ *
+ * For current constants, that puts us at 50% while the user is active and 83%
+ * when idle.
+ */
+ _INDEX_INTERVAL: 32,
+
+ /**
+ * Number of indexing 'tokens' we are allowed to consume before yielding for
+ * each incremental pass. Consider a single token equal to indexing a single
+ * medium-sized message. This may be altered by user session (in)activity.
+ * Because we fetch message bodies, which is potentially asynchronous, this
+ * is not a precise knob to twiddle.
+ */
+ _indexTokens: 2,
+
+ /**
+ * Stopwatches used to measure performance during indexing, and during
+ * pauses between indexing. These help us adapt our indexing constants so
+ * as to not explode your computer. Kind of us, no?
+ */
+ _perfIndexStopwatch: null,
+ _perfPauseStopwatch: null,
+ /**
+ * Do we have an uncommitted indexer transaction that idle callback should commit?
+ */
+ _idleToCommit: false,
+ /**
+ * Target CPU time per batch of tokens, current value (milliseconds).
+ */
+ _cpuTargetIndexTime: 32,
+ /**
+ * Target CPU time per batch of tokens, during non-idle (milliseconds).
+ */
+ _CPU_TARGET_INDEX_TIME_ACTIVE: 32,
+ /**
+ * Target CPU time per batch of tokens, during idle (milliseconds).
+ */
+ _CPU_TARGET_INDEX_TIME_IDLE: 160,
+ /**
+ * Average CPU time per processed token (milliseconds).
+ */
+ _cpuAverageTimePerToken: 16,
+ /**
+ * Damping factor for _cpuAverageTimePerToken, as an approximate
+ * number of tokens to include in the average time.
+ */
+ _CPU_AVERAGE_TIME_DAMPING: 200,
+ /**
+ * Maximum tokens per batch. This is normally just a sanity check.
+ */
+ _CPU_MAX_TOKENS_PER_BATCH: 100,
+ /**
+ * CPU usage during a pause to declare that system was busy (milliseconds).
+ * This is typically set as 1.5 times the minimum resolution of the cpu
+ * usage clock, which is 16 milliseconds on Windows systems, and (I think)
+ * smaller on other systems, so we take the worst case.
+ */
+ _CPU_IS_BUSY_TIME: 24,
+ /**
+ * Time that return from pause may be late before the system is declared
+ * busy, in milliseconds. (Same issues as _CPU_IS_BUSY_TIME).
+ */
+ _PAUSE_LATE_IS_BUSY_TIME: 24,
+ /**
+ * Number of times that we will repeat a pause while waiting for a
+ * free CPU.
+ */
+ _PAUSE_REPEAT_LIMIT: 10,
+ /**
+ * Minimum time delay between commits, in milliseconds.
+ */
+ _MINIMUM_COMMIT_TIME: 5000,
+ /**
+ * Maximum time delay between commits, in milliseconds.
+ */
+ _MAXIMUM_COMMIT_TIME: 20000,
+
+ /**
+ * Unit testing hook to get us to emit additional logging that verges on
+ * inane for general usage but is helpful in unit test output to get a lay
+ * of the land and for paranoia reasons.
+ */
+ _unitTestSuperVerbose: false,
+ /**
+ * Unit test vector to get notified when a worker has a problem and it has
+ * a recover helper associated. This gets called with an argument
+ * indicating whether the recovery helper indicates recovery was possible.
+ */
+ _unitTestHookRecover: null,
+ /**
+ * Unit test vector to get notified when a worker runs into an exceptional
+ * situation (an exception propagates or gets explicitly killed) and needs
+ * to be cleaned up. This gets called with an argument indicating if there
+ * was a helper that was used or if we just did the default cleanup thing.
+ */
+ _unitTestHookCleanup: null,
+
+ /**
+ * Last commit time. Tracked to try and only commit at reasonable intervals.
+ */
+ _lastCommitTime: Date.now(),
+
+ _inited: false,
+ /**
+ * Initialize the indexer.
+ */
+ _init: function gloda_index_init() {
+ if (this._inited)
+ return;
+
+ this._inited = true;
+
+ this._callbackHandle.init();
+
+ if (Services.io.offline)
+ this._suppressIndexing = true;
+
+ // create the timer that drives our intermittent indexing
+ this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ // create the timer for larger offsets independent of indexing
+ this._longTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ this._idleService = Cc["@mozilla.org/widget/idleservice;1"]
+ .getService(Ci.nsIIdleService);
+
+ // create our performance stopwatches
+ try {
+ this._perfIndexStopwatch = Cc["@mozilla.org/stopwatch;1"]
+ .createInstance(Ci.nsIStopwatch);
+ this._perfPauseStopwatch = Cc["@mozilla.org/stopwatch;1"]
+ .createInstance(Ci.nsIStopwatch);
+
+ } catch (ex) {
+ this._log.error("problem creating stopwatch!: " + ex);
+ }
+
+ // register for shutdown notifications
+ Services.obs.addObserver(this, "quit-application", false);
+
+ // figure out if event-driven indexing should be enabled...
+ let branch = Services.prefs.getBranch("mailnews.database.global.indexer.");
+ let eventDrivenEnabled = false; // default
+ let performInitialSweep = true; // default
+ try {
+ eventDrivenEnabled = branch.getBoolPref("enabled");
+ } catch (ex) {
+ dump("%%% annoying exception on pref access: " + ex);
+ }
+ // this is a secret preference mainly intended for testing purposes.
+ try {
+ performInitialSweep = branch.getBoolPref("perform_initial_sweep");
+ } catch (ex) {}
+ // pretend we have already performed an initial sweep...
+ if (!performInitialSweep)
+ this._initialSweepPerformed = true;
+
+ this.enabled = eventDrivenEnabled;
+ },
+
+ /**
+ * When shutdown, indexing immediately ceases and no further progress should
+ * be made. This flag goes true once, and never returns to false. Being
+ * in this state is a destructive thing from whence we cannot recover.
+ */
+ _indexerIsShutdown: false,
+
+ /**
+ * Shutdown the indexing process and datastore as quickly as possible in
+ * a synchronous fashion.
+ */
+ _shutdown: function gloda_index_shutdown() {
+ // no more timer events, please
+ try {
+ this._timer.cancel();
+ } catch (ex) {}
+ this._timer = null;
+ try {
+ this._longTimer.cancel();
+ } catch (ex) {}
+ this._longTimer = null;
+
+ this._perfIndexStopwatch = null;
+ this._perfPauseStopwatch = null;
+
+ // Remove listeners to avoid reference cycles on the off chance one of them
+ // holds a reference to the indexer object.
+ this._indexListeners = [];
+
+ this._indexerIsShutdown = true;
+
+ if (this.enabled)
+ this._log.info("Shutting Down");
+
+ // don't let anything try and convince us to start indexing again
+ this.suppressIndexing = true;
+
+ // If there is an active job and it has a cleanup handler, run it.
+ if (this._curIndexingJob) {
+ let workerDef = this._curIndexingJob._workerDef;
+ try {
+ if (workerDef.cleanup)
+ workerDef.cleanup.call(workerDef.indexer, this._curIndexingJob);
+ }
+ catch (ex) {
+ this._log.error("problem during worker cleanup during shutdown.");
+ }
+ }
+ // Definitely clean out the async call stack and any associated data
+ this._callbackHandle.cleanup();
+ this._workBatchData = undefined;
+
+ // disable ourselves and all of the specific indexers
+ this.enabled = false;
+
+ GlodaDatastore.shutdown();
+ },
+
+ /**
+ * The list of indexers registered with us. If you are a core gloda indexer
+ * (you ship with gloda), then you can import this file directly and should
+ * make sure your indexer is imported in 'everybody.js' in the right order.
+ * If you are not core gloda, then you should import 'public.js' and only
+ * then should you import 'indexer.js' to get at GlodaIndexer.
+ */
+ _indexers: [],
+ /**
+ * Register an indexer with the Gloda indexing mechanism.
+ *
+ * @param aIndexer.name The name of your indexer.
+ * @param aIndexer.enable Your enable function. This will be called during
+ * the call to registerIndexer if Gloda indexing is already enabled. If
+ * indexing is not yet enabled, you will be called
+ * @param aIndexer.disable Your disable function. This will be called when
+ * indexing is disabled or we are shutting down. This will only be called
+ * if enable has already been called.
+ * @param aIndexer.workers A list of tuples of the form [worker type code,
+ * worker generator function, optional scheduling trigger function]. The
+ * type code is the string used to uniquely identify the job type. If you
+ * are not core gloda, your job type must start with your extension's name
+ * and a colon; you can collow that with anything you want. The worker
+ * generator is not easily explained in here. The trigger function is
+ * invoked immediately prior to calling the generator to create it. The
+ * trigger function takes the job as an argument and should perform any
+ * finalization required on the job. Most workers should not need to use
+ * the trigger function.
+ * @param aIndexer.initialSweep We call this to tell each indexer when it is
+ * its turn to run its indexing sweep. The idea of the indexing sweep is
+ * that this is when you traverse things eligible for indexing to make
+ * sure they are indexed. Right now we just call everyone at the same
+ * time and hope that their jobs don't fight too much.
+ */
+ registerIndexer: function gloda_index_registerIndexer(aIndexer) {
+ this._log.info("Registering indexer: " + aIndexer.name);
+ this._indexers.push(aIndexer);
+
+ try {
+ for (let workerInfo of aIndexer.workers) {
+ let workerCode = workerInfo[0];
+ let workerDef = workerInfo[1];
+ workerDef.name = workerCode;
+ workerDef.indexer = aIndexer;
+ this._indexerWorkerDefs[workerCode] = workerDef;
+ if (!("recover" in workerDef))
+ workerDef.recover = null;
+ if (!("cleanup" in workerDef))
+ workerDef.cleanup = null;
+ if (!("onSchedule" in workerDef))
+ workerDef.onSchedule = null;
+ if (!("jobCanceled" in workerDef))
+ workerDef.jobCanceled = null;
+ }
+ }
+ catch (ex) {
+ this._log.warn("Helper indexer threw exception on worker enum.");
+ }
+
+ if (this._enabled) {
+ try {
+ aIndexer.enable();
+ } catch (ex) {
+ this._log.warn("Helper indexer threw exception on enable: " + ex);
+ }
+ }
+ },
+
+ /**
+ * Are we enabled, read: are we processing change events?
+ */
+ _enabled: false,
+ get enabled() { return this._enabled; },
+ set enabled(aEnable) {
+ if (!this._enabled && aEnable) {
+ // register for offline notifications
+ Services.obs.addObserver(this, "network:offline-status-changed", false);
+
+ // register for idle notification
+ this._idleService.addIdleObserver(this, this._indexIdleThresholdSecs);
+
+ this._enabled = true;
+
+ for (let indexer of this._indexers) {
+ try {
+ indexer.enable();
+ } catch (ex) {
+ this._log.warn("Helper indexer threw exception on enable: " + ex);
+ }
+ }
+
+ // if we have an accumulated desire to index things, kick it off again.
+ if (this._indexingDesired) {
+ this._indexingDesired = false; // it's edge-triggered for now
+ this.indexing = true;
+ }
+
+ // if we have not done an initial sweep, schedule scheduling one.
+ if (!this._initialSweepPerformed) {
+ this._longTimer.initWithCallback(this._scheduleInitialSweep,
+ this._INITIAL_SWEEP_DELAY, Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ }
+ else if (this._enabled && !aEnable) {
+ for (let indexer of this._indexers) {
+ try {
+ indexer.disable();
+ } catch (ex) {
+ this._log.warn("Helper indexer threw exception on disable: " + ex);
+ }
+ }
+
+ // remove offline observer
+ Services.obs.removeObserver(this, "network:offline-status-changed");
+
+ // remove idle
+ this._idleService.removeIdleObserver(this, this._indexIdleThresholdSecs);
+
+ this._enabled = false;
+ }
+
+ },
+
+ /** Track whether indexing is desired (we have jobs to prosecute). */
+ _indexingDesired: false,
+ /**
+ * Track whether we have an actively pending callback or timer event. We do
+ * this so we don't experience a transient suppression and accidentally
+ * get multiple event-chains driving indexing at the same time (which the
+ * code will not handle correctly).
+ */
+ _indexingActive: false,
+ /**
+ * Indicates whether indexing is currently ongoing. This may return false
+ * while indexing activities are still active, but they will quiesce shortly.
+ */
+ get indexing() {
+ return this._indexingDesired && !this._suppressIndexing;
+ },
+ /** Indicates whether indexing is desired. */
+ get indexingDesired() {
+ return this._indexingDesired;
+ },
+ /**
+ * Set this to true to indicate there is indexing work to perform. This does
+ * not mean indexing will begin immediately (if it wasn't active), however.
+ * If suppressIndexing has been set, we won't do anything until indexing is
+ * no longer suppressed.
+ */
+ set indexing(aShouldIndex) {
+ if (!this._indexingDesired && aShouldIndex) {
+ this._indexingDesired = true;
+ if (this.enabled && !this._indexingActive && !this._suppressIndexing) {
+ this._log.info("+++ Indexing Queue Processing Commencing");
+ this._indexingActive = true;
+ this._timer.initWithCallback(this._timerCallbackDriver,
+ this._INDEX_KICKOFF_DELAY,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ }
+ },
+
+ _suppressIndexing: false,
+ /**
+ * Set whether or not indexing should be suppressed. This is to allow us to
+ * avoid running down a laptop's battery when it is not on AC. Only code
+ * in charge of regulating that tracking should be setting this variable; if
+ * other factors want to contribute to such a decision, this logic needs to
+ * be changed to track that, since last-write currently wins.
+ */
+ set suppressIndexing(aShouldSuppress) {
+ this._suppressIndexing = aShouldSuppress;
+
+ // re-start processing if we are no longer suppressing, there is work yet
+ // to do, and the indexing process had actually stopped.
+ if (!this._suppressIndexing && this._indexingDesired &&
+ !this._indexingActive) {
+ this._log.info("+++ Indexing Queue Processing Resuming");
+ this._indexingActive = true;
+ this._timer.initWithCallback(this._timerCallbackDriver,
+ this._INDEX_KICKOFF_DELAY,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ },
+
+ /**
+ * Track whether an initial sweep has been performed. This mainly exists so
+ * that unit testing can stop us from performing an initial sweep.
+ */
+ _initialSweepPerformed: false,
+ /**
+ * Our timer-driven callback to schedule our first initial indexing sweep.
+ * Because it is invoked by an nsITimer it operates without the benefit of
+ * a 'this' context and must use GlodaIndexer instead of this.
+ * Since an initial sweep could have been performed before we get invoked,
+ * we need to check whether an initial sweep is still desired before trying
+ * to schedule one. We don't need to worry about whether one is active
+ * because the indexingSweepNeeded takes care of that.
+ */
+ _scheduleInitialSweep: function gloda_index_scheduleInitialSweep() {
+ if (GlodaIndexer._initialSweepPerformed)
+ return;
+ GlodaIndexer._initialSweepPerformed = true;
+ for (let indexer of GlodaIndexer._indexers) {
+ indexer.initialSweep();
+ }
+ },
+
+ kWorkSync: Gloda.kWorkSync,
+ kWorkAsync: Gloda.kWorkAsync,
+ kWorkDone: Gloda.kWorkDone,
+ kWorkPause: Gloda.kWorkPause,
+ kWorkDoneWithResult: Gloda.kWorkDoneWithResult,
+
+ /**
+ * Our current job number. Meaningless value that increments with every job
+ * we process that resets to 0 when we run out of jobs. Currently used by
+ * the activity manager's gloda listener to tell when we have changed jobs.
+ * We really need a better listener mechanism.
+ */
+ _indexingJobCount: 0,
+
+ /**
+ * A list of IndexingJob instances to process.
+ */
+ _indexQueue: [],
+
+ /**
+ * The current indexing job.
+ */
+ _curIndexingJob: null,
+
+ /**
+ * The number of seconds before we declare the user idle and commit if
+ * needed.
+ */
+ _indexIdleThresholdSecs: 3,
+
+ _indexListeners: [],
+ /**
+ * Add an indexing progress listener. The listener will be notified of at
+ * least all major status changes (idle -> indexing, indexing -> idle), plus
+ * arbitrary progress updates during the indexing process.
+ * If indexing is not active when the listener is added, a synthetic idle
+ * notification will be generated.
+ *
+ * @param aListener A listener function, taking arguments: status (Gloda.
+ * kIndexer*), the folder name if a folder is involved (string or null),
+ * current zero-based job number (int),
+ * current item number being indexed in this job (int), total number
+ * of items in this job to be indexed (int).
+ *
+ * @TODO should probably allow for a 'this' value to be provided
+ * @TODO generalize to not be folder/message specific. use nouns!
+ */
+ addListener: function gloda_index_addListener(aListener) {
+ // should we weakify?
+ if (this._indexListeners.indexOf(aListener) == -1)
+ this._indexListeners.push(aListener);
+ // if we aren't indexing, give them an idle indicator, otherwise they can
+ // just be happy when we hit the next actual status point.
+ if (!this.indexing)
+ aListener(Gloda.kIndexerIdle, null, 0, 0, 1);
+ return aListener;
+ },
+ /**
+ * Remove the given listener so that it no longer receives indexing progress
+ * updates.
+ */
+ removeListener: function gloda_index_removeListener(aListener) {
+ let index = this._indexListeners.indexOf(aListener);
+ if (index != -1)
+ this._indexListeners.splice(index, 1);
+ },
+ /**
+ * Helper method to tell listeners what we're up to. For code simplicity,
+ * the caller is just deciding when to send this update (preferably at
+ * reasonable intervals), and doesn't need to provide any indication of
+ * state... we figure that out ourselves.
+ *
+ * This was not pretty but got ugly once we moved the message indexing out
+ * to its own indexer. Some generalization is required but will likely
+ * require string hooks.
+ */
+ _notifyListeners: function gloda_index_notifyListeners() {
+ let status, prettyName, jobIndex, jobItemIndex, jobItemGoal, jobType;
+
+ if (this.indexing && this._curIndexingJob) {
+ let job = this._curIndexingJob;
+ status = Gloda.kIndexerIndexing;
+
+ let indexer = this._indexerWorkerDefs[job.jobType].indexer;
+ if ("_indexingFolder" in indexer)
+ prettyName = (indexer._indexingFolder != null) ?
+ indexer._indexingFolder.prettiestName : null;
+ else
+ prettyName = null;
+
+ jobIndex = this._indexingJobCount-1;
+ jobItemIndex = job.offset;
+ jobItemGoal = job.goal;
+ jobType = job.jobType;
+ }
+ else {
+ status = Gloda.kIndexerIdle;
+ prettyName = null;
+ jobIndex = 0;
+ jobItemIndex = 0;
+ jobItemGoal = 1;
+ jobType = null;
+ }
+
+ // Some people ascribe to the belief that the most you can give is 100%.
+ // We know better, but let's humor them.
+ if (jobItemIndex > jobItemGoal)
+ jobItemGoal = jobItemIndex;
+
+ for (let iListener = this._indexListeners.length-1; iListener >= 0;
+ iListener--) {
+ let listener = this._indexListeners[iListener];
+ try {
+ listener(status, prettyName, jobIndex, jobItemIndex, jobItemGoal,
+ jobType);
+ }
+ catch(ex) {
+ this._log.error(ex);
+ }
+ }
+ },
+
+ /**
+ * A wrapped callback driver intended to be used by timers that provide
+ * arguments we really do not care about.
+ */
+ _timerCallbackDriver: function gloda_index_timerCallbackDriver() {
+ GlodaIndexer.callbackDriver();
+ },
+
+ /**
+ * A simple callback driver wrapper to provide 'this'.
+ */
+ _wrapCallbackDriver: function gloda_index_wrapCallbackDriver() {
+ GlodaIndexer.callbackDriver.apply(GlodaIndexer, arguments);
+ },
+
+ /**
+ * The current processing 'batch' generator, produced by a call to workBatch()
+ * and used by callbackDriver to drive execution.
+ */
+ _batch: null,
+ _inCallback: false,
+ _savedCallbackArgs: null,
+ /**
+ * The root work-driver. callbackDriver creates workBatch generator instances
+ * (stored in _batch) which run until they are done (kWorkDone) or they
+ * (really the embedded activeIterator) encounter something asynchronous.
+ * The convention is that all the callback handlers end up calling us,
+ * ensuring that control-flow properly resumes. If the batch completes,
+ * we re-schedule ourselves after a time delay (controlled by _INDEX_INTERVAL)
+ * and return. (We use one-shot timers because repeating-slack does not
+ * know enough to deal with our (current) asynchronous nature.)
+ */
+ callbackDriver: function gloda_index_callbackDriver() {
+ // just bail if we are shutdown
+ if (this._indexerIsShutdown)
+ return;
+
+ // it is conceivable that someone we call will call something that in some
+ // cases might be asynchronous, and in other cases immediately generate
+ // events without returning. In the interest of (stack-depth) sanity,
+ // let's handle this by performing a minimal time-delay callback.
+ // this is also now a good thing sequencing-wise. if we get our callback
+ // with data before the underlying function has yielded, we obviously can't
+ // cram the data in yet. Our options in this case are to either mark the
+ // fact that the callback has already happened and immediately return to
+ // the iterator when it does bubble up the kWorkAsync, or we can do as we
+ // have been doing, but save the
+ if (this._inCallback) {
+ this._savedCallbackArgs = arguments;
+ this._timer.initWithCallback(this._timerCallbackDriver,
+ 0,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ return;
+ }
+ this._inCallback = true;
+
+ try {
+ if (this._batch === null)
+ this._batch = this.workBatch();
+
+ // kWorkAsync, kWorkDone, kWorkPause are allowed out; kWorkSync is not
+ // On kWorkDone, we want to schedule another timer to fire on us if we are
+ // not done indexing. (On kWorkAsync, we don't care what happens, because
+ // someone else will be receiving the callback, and they will call us when
+ // they are done doing their thing.
+ let args;
+ if (this._savedCallbackArgs != null) {
+ args = this._savedCallbackArgs;
+ this._savedCallbackArgs = null;
+ }
+ else
+ args = arguments; //Array.slice.call(arguments);
+
+ let result;
+ if (args.length == 0)
+ result = this._batch.next().value;
+ else if (args.length == 1)
+ result = this._batch.next(args[0]).value;
+ else // arguments works with destructuring assignment
+ result = this._batch.next(args).value;
+ switch (result) {
+ // job's done, close the batch and re-schedule ourselves if there's more
+ // to do.
+ case this.kWorkDone:
+ this._batch.return();
+ this._batch = null;
+ // (intentional fall-through to re-scheduling logic)
+ // the batch wants to get re-scheduled, do so.
+ case this.kWorkPause:
+ if (this.indexing)
+ this._timer.initWithCallback(this._timerCallbackDriver,
+ this._INDEX_INTERVAL,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ else { // it's important to indicate no more callbacks are in flight
+ this._indexingActive = false;
+ }
+ break;
+ case this.kWorkAsync:
+ // there is nothing to do. some other code is now responsible for
+ // calling us.
+ break;
+ }
+ }
+ finally {
+ this._inCallback = false;
+ }
+ },
+
+ _callbackHandle: {
+ init: function gloda_index_callbackhandle_init() {
+ this.wrappedCallback = GlodaIndexer._wrapCallbackDriver;
+ this.callbackThis = GlodaIndexer;
+ this.callback = GlodaIndexer.callbackDriver;
+ },
+ /**
+ * The stack of generators we are processing. The (numerically) last one is
+ * also the |activeIterator|.
+ */
+ activeStack: [],
+ /**
+ * The generator at the top of the |activeStack| and that we will call next
+ * or send on next if nothing changes.
+ */
+ activeIterator: null,
+ /**
+ * Meta-information about the generators at each level of the stack.
+ */
+ contextStack: [],
+ /**
+ * Push a new generator onto the stack. It becomes the active generator.
+ */
+ push: function gloda_index_callbackhandle_push(aIterator, aContext) {
+ this.activeStack.push(aIterator);
+ this.contextStack.push(aContext);
+ this.activeIterator = aIterator;
+ },
+ /**
+ * For use by generators that want to call another asynchronous process
+ * implemented as a generator. They should do
+ * "yield aCallbackHandle.pushAndGo(someGenerator(arg1, arg2));".
+ *
+ * @public
+ */
+ pushAndGo: function gloda_index_callbackhandle_pushAndGo(aIterator,
+ aContext) {
+ this.push(aIterator, aContext);
+ return GlodaIndexer.kWorkSync;
+ },
+ /**
+ * Pop the active generator off the stack.
+ */
+ pop: function gloda_index_callbackhandle_pop() {
+ this.activeIterator.return();
+ this.activeStack.pop();
+ this.contextStack.pop();
+ if (this.activeStack.length)
+ this.activeIterator = this.activeStack[this.activeStack.length - 1];
+ else
+ this.activeIterator = null;
+ },
+ /**
+ * Someone propagated an exception and we need to clean-up all the active
+ * logic as best we can. Which is not really all that well.
+ *
+ * @param [aOptionalStopAtDepth=0] The length the stack should be when this
+ * method completes. Pass 0 or omit for us to clear everything out.
+ * Pass 1 to leave just the top-level generator intact.
+ */
+ cleanup: function gloda_index_callbackhandle_cleanup(aOptionalStopAtDepth) {
+ if (aOptionalStopAtDepth === undefined)
+ aOptionalStopAtDepth = 0;
+ while (this.activeStack.length > aOptionalStopAtDepth) {
+ this.pop();
+ }
+ },
+ /**
+ * For use when a generator finishes up by calling |doneWithResult| on us;
+ * the async driver calls this to pop that generator off the stack
+ * and get the result it passed in to its call to |doneWithResult|.
+ *
+ * @protected
+ */
+ popWithResult: function gloda_index_callbackhandle_popWithResult() {
+ this.pop();
+ let result = this._result;
+ this._result = null;
+ return result;
+ },
+ _result: null,
+ /**
+ * For use by generators that want to return a result to the calling
+ * asynchronous generator. Specifically, they should do
+ * "yield aCallbackHandle.doneWithResult(RESULT);".
+ *
+ * @public
+ */
+ doneWithResult: function gloda_index_callbackhandle_doneWithResult(aResult){
+ this._result = aResult;
+ return Gloda.kWorkDoneWithResult;
+ },
+
+ /* be able to serve as a collection listener, resuming the active iterator's
+ last yield kWorkAsync */
+ onItemsAdded: function() {},
+ onItemsModified: function() {},
+ onItemsRemoved: function() {},
+ onQueryCompleted: function(aCollection) {
+ GlodaIndexer.callbackDriver();
+ }
+ },
+ _workBatchData: undefined,
+ /**
+ * The workBatch generator handles a single 'batch' of processing, managing
+ * the database transaction and keeping track of "tokens". It drives the
+ * activeIterator generator which is doing the work.
+ * workBatch will only produce kWorkAsync, kWorkPause, and kWorkDone
+ * notifications. If activeIterator returns kWorkSync and there are still
+ * tokens available, workBatch will keep driving the activeIterator until it
+ * encounters a kWorkAsync (which workBatch will yield to callbackDriver), or
+ * it runs out of tokens and yields a kWorkPause or kWorkDone.
+ */
+ workBatch: function* gloda_index_workBatch() {
+
+ // Do we still have an open transaction? If not, start a new one.
+ if (!this._idleToCommit)
+ GlodaDatastore._beginTransaction();
+ else
+ // We'll manage commit ourself while this routine is active.
+ this._idleToCommit = false;
+
+ this._perfIndexStopwatch.start();
+ let batchCount;
+ let haveMoreWork = true;
+ let transactionToCommit = true;
+ let inIdle;
+
+ let notifyDecimator = 0;
+
+ while (haveMoreWork) {
+ // Both explicit work activity points (sync + async) and transfer of
+ // control return (via kWorkDone*) results in a token being eaten. The
+ // idea now is to make tokens less precious so that the adaptive logic
+ // can adjust them with less impact. (Before this change, doing 1
+ // token's work per cycle ended up being an entire non-idle time-slice's
+ // work.)
+ // During this loop we track the clock real-time used even though we
+ // frequently yield to asynchronous operations. These asynchronous
+ // operations are either database queries or message streaming requests.
+ // Both may involve disk I/O but no network I/O (since we only stream
+ // messages that are already available offline), but in an ideal
+ // situation will come from cache and so the work this function kicks off
+ // will dominate.
+ // We do not use the CPU time to this end because...
+ // 1) Our timer granularity on linux is worse for CPU than for wall time.
+ // 2) That can fail to account for our I/O cost.
+ // 3) If something with a high priority / low latency need (like playing
+ // a video) is fighting us, although using CPU time will accurately
+ // express how much time we are actually spending to index, our goal
+ // is to control the duration of our time slices, not be "right" about
+ // the actual CPU cost. In that case, if we attempted to take on more
+ // work, we would likely interfere with the higher priority process or
+ // make ourselves less responsive by drawing out the period of time we
+ // are dominating the main thread.
+ this._perfIndexStopwatch.start();
+ // For telemetry purposes, we want to know how many messages we've been
+ // processing during that batch, and how long it took, pauses included.
+ let t0 = Date.now();
+ this._indexedMessageCount = 0;
+ batchCount = 0;
+ while (batchCount < this._indexTokens) {
+ if ((this._callbackHandle.activeIterator === null) &&
+ !this._hireJobWorker()) {
+ haveMoreWork = false;
+ break;
+ }
+ batchCount++;
+
+ // XXX for performance, we may want to move the try outside the for loop
+ // with a quasi-redundant outer loop that shunts control back inside
+ // if we left the loop due to an exception (without consuming all the
+ // tokens.)
+ try {
+ switch (this._callbackHandle
+ .activeIterator.next(this._workBatchData).value) {
+ case this.kWorkSync:
+ this._workBatchData = undefined;
+ break;
+ case this.kWorkAsync:
+ this._workBatchData = yield this.kWorkAsync;
+ break;
+ case this.kWorkDone:
+ this._callbackHandle.pop();
+ this._workBatchData = undefined;
+ break;
+ case this.kWorkDoneWithResult:
+ this._workBatchData = this._callbackHandle.popWithResult();
+ break;
+ default:
+ break;
+ }
+ }
+ catch (ex) {
+ this._log.debug("Exception in batch processing:", ex);
+ let workerDef = this._curIndexingJob._workerDef;
+ if (workerDef.recover) {
+ let recoverToDepth;
+ try {
+ recoverToDepth =
+ workerDef.recover.call(workerDef.indexer,
+ this._curIndexingJob,
+ this._callbackHandle.contextStack,
+ ex);
+ }
+ catch (ex2) {
+ this._log.error("Worker '" + workerDef.name +
+ "' recovery function itself failed:", ex2);
+ }
+ if (this._unitTestHookRecover)
+ this._unitTestHookRecover(recoverToDepth, ex,
+ this._curIndexingJob,
+ this._callbackHandle);
+
+ if (recoverToDepth) {
+ this._callbackHandle.cleanup(recoverToDepth);
+ continue;
+ }
+ }
+ // (we either did not have a recover handler or it couldn't recover)
+ // call the cleanup helper if there is one
+ if (workerDef.cleanup) {
+ try {
+ workerDef.cleanup.call(workerDef.indexer, this._curIndexingJob);
+ }
+ catch (ex2) {
+ this._log.error("Worker '" + workerDef.name +
+ "' cleanup function itself failed:", ex2);
+ }
+ if (this._unitTestHookCleanup)
+ this._unitTestHookCleanup(true, ex, this._curIndexingJob,
+ this._callbackHandle);
+ }
+ else {
+ if (this._unitTestHookCleanup)
+ this._unitTestHookCleanup(false, ex, this._curIndexingJob,
+ this._callbackHandle);
+ }
+
+ // Clean out everything on the async stack, warn about the job, kill.
+ // We do not log this warning lightly; it will break unit tests and
+ // be visible to users. Anything expected should likely have a
+ // recovery function or the cleanup logic should be extended to
+ // indicate that the failure is acceptable.
+ this._callbackHandle.cleanup();
+ this._log.warn("Problem during " + this._curIndexingJob +
+ ", bailing:", ex);
+ this._curIndexingJob = null;
+ // the data must now be invalid
+ this._workBatchData = undefined;
+ }
+ }
+ this._perfIndexStopwatch.stop();
+
+ // idleTime can throw if there is no idle-provider available, such as an
+ // X session without the relevant extensions available. In this case
+ // we assume that the user is never idle.
+ try {
+ // We want to stop ASAP when leaving idle, so we can't rely on the
+ // standard polled callback. We do the polling ourselves.
+ if (this._idleService.idleTime < this._INDEX_IDLE_ADJUSTMENT_TIME) {
+ inIdle = false;
+ this._cpuTargetIndexTime = this._CPU_TARGET_INDEX_TIME_ACTIVE;
+ }
+ else {
+ inIdle = true;
+ this._cpuTargetIndexTime = this._CPU_TARGET_INDEX_TIME_IDLE;
+ }
+ }
+ catch (ex) {
+ inIdle = false;
+ }
+
+ // take a breather by having the caller re-schedule us sometime in the
+ // future, but only if we're going to perform another loop iteration.
+ if (haveMoreWork) {
+ notifyDecimator = (notifyDecimator + 1) % 32;
+ if (!notifyDecimator)
+ this._notifyListeners();
+
+ for (let pauseCount = 0;
+ pauseCount < this._PAUSE_REPEAT_LIMIT;
+ pauseCount++) {
+ this._perfPauseStopwatch.start();
+
+ yield this.kWorkPause;
+
+ this._perfPauseStopwatch.stop();
+ // We repeat the pause if the pause was longer than
+ // we expected, or if it used a significant amount
+ // of cpu, either of which indicate significant other
+ // activity.
+ if ((this._perfPauseStopwatch.cpuTimeSeconds * 1000 <
+ this._CPU_IS_BUSY_TIME) &&
+ (this._perfPauseStopwatch.realTimeSeconds * 1000 -
+ this._INDEX_INTERVAL < this._PAUSE_LATE_IS_BUSY_TIME))
+ break;
+ }
+ }
+
+ // All pauses have been taken, how effective were we? Report!
+ // XXX: there's possibly a lot of fluctuation since we go through here
+ // every 5 messages or even less
+ if (this._indexedMessageCount > 0) {
+ let delta = (Date.now() - t0)/1000; // in seconds
+ let v = Math.round(this._indexedMessageCount/delta);
+ try {
+ let h = Services.telemetry
+ .getHistogramById("THUNDERBIRD_INDEXING_RATE_MSG_PER_S");
+ h.add(v);
+ } catch (e) {
+ this._log.warn("Couldn't report telemetry", e, v);
+ }
+ }
+
+ if (batchCount > 0) {
+ let totalTime = this._perfIndexStopwatch.realTimeSeconds * 1000;
+ let timePerToken = totalTime / batchCount;
+ // Damp the average time since it is a rough estimate only.
+ this._cpuAverageTimePerToken =
+ (totalTime +
+ this._CPU_AVERAGE_TIME_DAMPING * this._cpuAverageTimePerToken) /
+ (batchCount + this._CPU_AVERAGE_TIME_DAMPING);
+ // We use the larger of the recent or the average time per token, so
+ // that we can respond quickly to slow down indexing if there
+ // is a sudden increase in time per token.
+ let bestTimePerToken =
+ Math.max(timePerToken, this._cpuAverageTimePerToken);
+ // Always index at least one token!
+ this._indexTokens =
+ Math.max(1, this._cpuTargetIndexTime / bestTimePerToken);
+ // But no more than the a maximum limit, just for sanity's sake.
+ this._indexTokens = Math.min(this._CPU_MAX_TOKENS_PER_BATCH,
+ this._indexTokens);
+ this._indexTokens = Math.ceil(this._indexTokens);
+ }
+
+ // Should we try to commit now?
+ let elapsed = Date.now() - this._lastCommitTime;
+ // Commit tends to cause a brief UI pause, so we try to delay it (but not
+ // forever) if the user is active. If we're done and idling, we'll also
+ // commit, otherwise we'll let the idle callback do it.
+ let doCommit = transactionToCommit &&
+ ((elapsed > this._MAXIMUM_COMMIT_TIME) ||
+ (inIdle &&
+ (elapsed > this._MINIMUM_COMMIT_TIME || !haveMoreWork)));
+ if (doCommit) {
+ GlodaCollectionManager.cacheCommitDirty();
+ // Set up an async notification to happen after the commit completes so
+ // that we can avoid the indexer doing something with the database that
+ // causes the main thread to block against the completion of the commit
+ // (which can be a while) on 1.9.1.
+ GlodaDatastore.runPostCommit(this._callbackHandle.wrappedCallback);
+ // kick off the commit
+ GlodaDatastore._commitTransaction();
+ yield this.kWorkAsync;
+ // Let's do the GC after the commit completes just so we can avoid
+ // having any ugly interactions.
+ GlodaUtils.forceGarbageCollection(false);
+ this._lastCommitTime = Date.now();
+ // Restart the transaction if we still have work.
+ if (haveMoreWork)
+ GlodaDatastore._beginTransaction();
+ else
+ transactionToCommit = false;
+ }
+ }
+
+ this._notifyListeners();
+
+ // If we still have a transaction to commit, tell idle to do the commit
+ // when it gets around to it.
+ if (transactionToCommit)
+ this._idleToCommit = true;
+
+ yield this.kWorkDone;
+ },
+
+ /**
+ * Maps indexing job type names to a worker definition.
+ * The worker definition is an object with the following attributes where
+ * only worker is required:
+ * - worker:
+ * - onSchedule: A function to be invoked when the worker is scheduled. The
+ * job is passed as an argument.
+ * - recover:
+ * - cleanup:
+ */
+ _indexerWorkerDefs: {},
+ /**
+ * Perform the initialization step and return a generator if there is any
+ * steady-state processing to be had.
+ */
+ _hireJobWorker: function gloda_index_hireJobWorker() {
+ // In no circumstances should there be data bouncing around from previous
+ // calls if we are here. |killActiveJob| depends on this.
+ this._workBatchData = undefined;
+
+ if (this._indexQueue.length == 0) {
+ this._log.info("--- Done indexing, disabling timer renewal.");
+
+ this._curIndexingJob = null;
+ this._indexingDesired = false;
+ this._indexingJobCount = 0;
+ return false;
+ }
+
+ let job = this._curIndexingJob = this._indexQueue.shift();
+ this._indexingJobCount++;
+
+ let generator = null;
+
+ if (job.jobType in this._indexerWorkerDefs) {
+ let workerDef = this._indexerWorkerDefs[job.jobType];
+ job._workerDef = workerDef;
+
+ // Prior to creating the worker, call the scheduling trigger function
+ // if there is one. This is so that jobs can be finalized. The
+ // initial use case is event-driven message indexing that accumulates
+ // a list of messages to index but wants it locked down once we start
+ // processing the list.
+ if (workerDef.onSchedule)
+ workerDef.onSchedule.call(workerDef.indexer, job);
+
+ generator = workerDef.worker.call(workerDef.indexer, job,
+ this._callbackHandle);
+ }
+ else {
+ // Nothing we can do about this. Be loud about it and try to schedule
+ // something else.
+ this._log.error("Unknown job type: " + job.jobType);
+ return this._hireJobWorker();
+ }
+
+ if (this._unitTestSuperVerbose)
+ this._log.debug("Hired job of type: " + job.jobType);
+
+ this._notifyListeners();
+
+ if (generator) {
+ this._callbackHandle.push(generator);
+ return true;
+ }
+ else
+ return false;
+ },
+
+ /**
+ * Schedule a job for indexing.
+ */
+ indexJob: function glodaIndexJob(aJob) {
+ this._log.info("Queue-ing job for indexing: " + aJob.jobType);
+
+ this._indexQueue.push(aJob);
+ this.indexing = true;
+ },
+
+ /**
+ * Kill the active job. This means a few things:
+ * - Kill all the generators in the callbackHandle stack.
+ * - If we are currently waiting on an async return, we need to make sure it
+ * does not screw us up.
+ * - Make sure the job's cleanup function gets called if appropriate.
+ *
+ * The async return case is actually not too troublesome. Since there is an
+ * active indexing job and we are not (by fiat) in that call stack, we know
+ * that the callback driver is guaranteed to get triggered again somehow.
+ * The only issue is to make sure that _workBatchData does not end up with
+ * the data. We compel |_hireJobWorker| to erase it to this end.
+ *
+ * @note You MUST NOT call this function from inside a job or an async funtion
+ * on the callbackHandle's stack of generators. If you are in that
+ * situation, you should just throw an exception. At the very least,
+ * use a timeout to trigger us.
+ */
+ killActiveJob: function() {
+ // There is nothing to do if we have no job
+ if (!this._curIndexingJob)
+ return;
+
+ // -- Blow away the stack with cleanup.
+ let workerDef = this._curIndexingJob._workerDef;
+ if (this._unitTestSuperVerbose)
+ this._log.debug("Killing job of type: " + this._curIndexingJob.jobType);
+ if (this._unitTestHookCleanup)
+ this._unitTestHookCleanup(workerDef.cleanup ? true : false,
+ "no exception, this was killActiveJob",
+ this._curIndexingJob,
+ this._callbackHandle);
+ this._callbackHandle.cleanup();
+ if (workerDef.cleanup)
+ workerDef.cleanup.call(workerDef.indexer, this._curIndexingJob);
+
+ // Eliminate the job.
+ this._curIndexingJob = null;
+ },
+
+ /**
+ * Purge all jobs that the filter function returns true for. This does not
+ * kill the active job, use |killActiveJob| to do that.
+ *
+ * Make sure to call this function before killActiveJob
+ *
+ * @param aFilterElimFunc A filter function that takes an |IndexingJob| and
+ * returns true if the job should be purged, false if it should not be.
+ * The filter sees the jobs in the order they are scheduled.
+ */
+ purgeJobsUsingFilter: function(aFilterElimFunc) {
+ for (let iJob = 0; iJob < this._indexQueue.length; iJob++) {
+ let job = this._indexQueue[iJob];
+
+ // If the filter says to, splice the job out of existence (and make sure
+ // to fixup iJob to compensate.)
+ if (aFilterElimFunc(job)) {
+ if (this._unitTestSuperVerbose)
+ this._log.debug("Purging job of type: " + job.jobType);
+ this._indexQueue.splice(iJob--, 1);
+ let workerDef = this._indexerWorkerDefs[job.jobType];
+ if (workerDef.jobCanceled)
+ workerDef.jobCanceled.call(workerDef.indexer, job);
+ }
+ }
+ },
+
+ /* *********** Event Processing *********** */
+ observe: function gloda_indexer_observe(aSubject, aTopic, aData) {
+ // idle
+ if (aTopic == "idle") {
+ // Do we need to commit an indexer transaction?
+ if (this._idleToCommit) {
+ this._idleToCommit = false;
+ GlodaCollectionManager.cacheCommitDirty();
+ GlodaDatastore._commitTransaction();
+ this._lastCommitTime = Date.now();
+ this._notifyListeners();
+ }
+ }
+ // offline status
+ else if (aTopic == "network:offline-status-changed") {
+ if (aData == "offline") {
+ this.suppressIndexing = true;
+ }
+ else { // online
+ this.suppressIndexing = false;
+ }
+ }
+ // shutdown fallback
+ else if (aTopic == "quit-application") {
+ this._shutdown();
+ }
+ },
+
+
+};
+// we used to initialize here; now we have public.js do it for us after the
+// indexers register themselves so we know about all our built-in indexers
+// at init-time.
diff --git a/mailnews/db/gloda/modules/log4moz.js b/mailnews/db/gloda/modules/log4moz.js
new file mode 100644
index 000000000..379c669e1
--- /dev/null
+++ b/mailnews/db/gloda/modules/log4moz.js
@@ -0,0 +1,932 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['Log4Moz'];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+var MODE_RDONLY = 0x01;
+var MODE_WRONLY = 0x02;
+var MODE_CREATE = 0x08;
+var MODE_APPEND = 0x10;
+var MODE_TRUNCATE = 0x20;
+
+var PERMS_FILE = parseInt("0644", 8);
+var PERMS_DIRECTORY = parseInt("0755", 8);
+
+var ONE_BYTE = 1;
+var ONE_KILOBYTE = 1024 * ONE_BYTE;
+var ONE_MEGABYTE = 1024 * ONE_KILOBYTE;
+
+var DEFAULT_NETWORK_TIMEOUT_DELAY = 5;
+
+var CDATA_START = "<![CDATA[";
+var CDATA_END = "]]>";
+var CDATA_ESCAPED_END = CDATA_END + "]]&gt;" + CDATA_START;
+
+var Log4Moz = {
+ Level: {
+ Fatal: 70,
+ Error: 60,
+ Warn: 50,
+ Info: 40,
+ Config: 30,
+ Debug: 20,
+ Trace: 10,
+ All: 0,
+ Desc: {
+ 70: "FATAL",
+ 60: "ERROR",
+ 50: "WARN",
+ 40: "INFO",
+ 30: "CONFIG",
+ 20: "DEBUG",
+ 10: "TRACE",
+ 0: "ALL"
+ }
+ },
+
+ /**
+ * Create a logger and configure it with dump and console appenders as
+ * specified by prefs based on the logger name.
+ *
+ * E.g., if the loggername is foo, then look for prefs
+ * foo.logging.console
+ * foo.logging.dump
+ *
+ * whose values can be empty: no logging of that type; or any of
+ * 'Fatal', 'Error', 'Warn', 'Info', 'Config', 'Debug', 'Trace', 'All',
+ * in which case the logging level for each appender will be set accordingly
+ *
+ * Parameters:
+ *
+ * @param loggername The name of the logger
+ * @param level (optional) the level of the logger itself
+ * @param consoleLevel (optional) the level of the console appender
+ * @param dumpLevel (optional) the level of the dump appender
+ *
+ * As described above, well-named prefs override the last two parameters
+ **/
+
+ getConfiguredLogger: function(loggername, level, consoleLevel, dumpLevel) {
+ let log = Log4Moz.repository.getLogger(loggername);
+ if (log._configured)
+ return log
+
+ let formatter = new Log4Moz.BasicFormatter();
+
+ level = level || Log4Moz.Level.Error;
+
+ consoleLevel = consoleLevel || -1;
+ dumpLevel = dumpLevel || -1;
+ let branch = Services.prefs.getBranch(loggername + ".logging.");
+ if (branch)
+ {
+ try {
+ // figure out if event-driven indexing should be enabled...
+ let consoleLevelString = branch.getCharPref("console");
+ if (consoleLevelString) {
+ // capitalize to fit with Log4Moz.Level expectations
+ consoleLevelString = consoleLevelString.charAt(0).toUpperCase() +
+ consoleLevelString.substr(1).toLowerCase();
+ consoleLevel = (consoleLevelString == 'None') ?
+ 100 : Log4Moz.Level[consoleLevelString];
+ }
+ } catch (ex) {
+ // Ignore if preference is not found
+ }
+ try {
+ let dumpLevelString = branch.getCharPref("dump");
+ if (dumpLevelString) {
+ // capitalize to fit with Log4Moz.Level expectations
+ dumpLevelString = dumpLevelString.charAt(0).toUpperCase() +
+ dumpLevelString.substr(1).toLowerCase();
+ dumpLevel = (dumpLevelString == 'None') ?
+ 100 : Log4Moz.Level[dumpLevelString];
+ }
+ } catch (ex) {
+ // Ignore if preference is not found
+ }
+ }
+
+ if (consoleLevel != 100) {
+ if (consoleLevel == -1)
+ consoleLevel = Log4Moz.Level.Error;
+ let capp = new Log4Moz.ConsoleAppender(formatter);
+ capp.level = consoleLevel;
+ log.addAppender(capp);
+ }
+
+ if (dumpLevel != 100) {
+ if (dumpLevel == -1)
+ dumpLevel = Log4Moz.Level.Error;
+ let dapp = new Log4Moz.DumpAppender(formatter);
+ dapp.level = dumpLevel;
+ log.addAppender(dapp);
+ }
+
+ log.level = Math.min(level, Math.min(consoleLevel, dumpLevel));
+
+ log._configured = true;
+
+ return log;
+ },
+
+ get repository() {
+ delete Log4Moz.repository;
+ Log4Moz.repository = new LoggerRepository();
+ return Log4Moz.repository;
+ },
+ set repository(value) {
+ delete Log4Moz.repository;
+ Log4Moz.repository = value;
+ },
+
+ get LogMessage() { return LogMessage; },
+ get Logger() { return Logger; },
+ get LoggerRepository() { return LoggerRepository; },
+
+ get Formatter() { return Formatter; },
+ get BasicFormatter() { return BasicFormatter; },
+ get XMLFormatter() { return XMLFormatter; },
+ get JSONFormatter() { return JSONFormatter; },
+ get Appender() { return Appender; },
+ get DumpAppender() { return DumpAppender; },
+ get ConsoleAppender() { return ConsoleAppender; },
+ get TimeAwareMemoryBucketAppender() { return TimeAwareMemoryBucketAppender; },
+ get FileAppender() { return FileAppender; },
+ get SocketAppender() { return SocketAppender; },
+ get RotatingFileAppender() { return RotatingFileAppender; },
+ get ThrowingAppender() { return ThrowingAppender; },
+
+ // Logging helper:
+ // let logger = Log4Moz.repository.getLogger("foo");
+ // logger.info(Log4Moz.enumerateInterfaces(someObject).join(","));
+ enumerateInterfaces: function Log4Moz_enumerateInterfaces(aObject) {
+ let interfaces = [];
+
+ for (i in Ci) {
+ try {
+ aObject.QueryInterface(Ci[i]);
+ interfaces.push(i);
+ }
+ catch(ex) {}
+ }
+
+ return interfaces;
+ },
+
+ // Logging helper:
+ // let logger = Log4Moz.repository.getLogger("foo");
+ // logger.info(Log4Moz.enumerateProperties(someObject).join(","));
+ enumerateProperties: function Log4Moz_enumerateProps(aObject,
+ aExcludeComplexTypes) {
+ let properties = [];
+
+ for (var p in aObject) {
+ try {
+ if (aExcludeComplexTypes &&
+ (typeof aObject[p] == "object" || typeof aObject[p] == "function"))
+ continue;
+ properties.push(p + " = " + aObject[p]);
+ }
+ catch(ex) {
+ properties.push(p + " = " + ex);
+ }
+ }
+
+ return properties;
+ }
+};
+
+function LoggerContext() {
+ this._started = this._lastStateChange = Date.now();
+ this._state = "started";
+}
+LoggerContext.prototype = {
+ _jsonMe: true,
+ _id: "unknown",
+ setState: function LoggerContext_state(aState) {
+ this._state = aState;
+ this._lastStateChange = Date.now();
+ return this;
+ },
+ finish: function LoggerContext_finish() {
+ this._finished = Date.now();
+ this._state = "finished";
+ return this;
+ },
+ toString: function LoggerContext_toString() {
+ return "[Context: " + this._id + " state: " + this._state + "]";
+ }
+};
+
+
+/*
+ * LogMessage
+ * Encapsulates a single log event's data
+ */
+function LogMessage(loggerName, level, messageObjects){
+ this.loggerName = loggerName;
+ this.messageObjects = messageObjects;
+ this.level = level;
+ this.time = Date.now();
+}
+LogMessage.prototype = {
+ get levelDesc() {
+ if (this.level in Log4Moz.Level.Desc)
+ return Log4Moz.Level.Desc[this.level];
+ return "UNKNOWN";
+ },
+
+ toString: function LogMsg_toString(){
+ return "LogMessage [" + this.time + " " + this.level + " " +
+ this.messageObjects + "]";
+ }
+};
+
+/*
+ * Logger
+ * Hierarchical version. Logs to all appenders, assigned or inherited
+ */
+
+function Logger(name, repository) {
+ this._init(name, repository);
+}
+Logger.prototype = {
+ _init: function Logger__init(name, repository) {
+ if (!repository)
+ repository = Log4Moz.repository;
+ this._name = name;
+ this.children = [];
+ this.ownAppenders = [];
+ this.appenders = [];
+ this._repository = repository;
+ },
+
+ get name() {
+ return this._name;
+ },
+
+ _level: null,
+ get level() {
+ if (this._level != null)
+ return this._level;
+ if (this.parent)
+ return this.parent.level;
+ dump("log4moz warning: root logger configuration error: no level defined\n");
+ return Log4Moz.Level.All;
+ },
+ set level(level) {
+ this._level = level;
+ },
+
+ _parent: null,
+ get parent() { return this._parent; },
+ set parent(parent) {
+ if (this._parent == parent) {
+ return;
+ }
+ // Remove ourselves from parent's children
+ if (this._parent) {
+ let index = this._parent.children.indexOf(this);
+ if (index != -1) {
+ this._parent.children.splice(index, 1);
+ }
+ }
+ this._parent = parent;
+ parent.children.push(this);
+ this.updateAppenders();
+ },
+
+ updateAppenders: function updateAppenders() {
+ if (this._parent) {
+ let notOwnAppenders = this._parent.appenders.filter(function(appender) {
+ return this.ownAppenders.indexOf(appender) == -1;
+ }, this);
+ this.appenders = notOwnAppenders.concat(this.ownAppenders);
+ } else {
+ this.appenders = this.ownAppenders.slice();
+ }
+
+ // Update children's appenders.
+ for (let i = 0; i < this.children.length; i++) {
+ this.children[i].updateAppenders();
+ }
+ },
+
+ addAppender: function Logger_addAppender(appender) {
+ if (this.ownAppenders.indexOf(appender) != -1) {
+ return;
+ }
+ this.ownAppenders.push(appender);
+ this.updateAppenders();
+ },
+
+ _nextContextId: 0,
+ newContext: function Logger_newContext(objWithProps) {
+ if (!("_id" in objWithProps))
+ objWithProps._id = this._name + ":" + (++this._nextContextId);
+
+ let c = new LoggerContext();
+ c._isContext = true;
+ for (let key in objWithProps) {
+ c[key] = objWithProps[key];
+ }
+ return c;
+ },
+
+ log: function Logger_log(message) {
+ if (this.level > message.level)
+ return;
+ let appenders = this.appenders;
+ for (let i = 0; i < appenders.length; i++){
+ appenders[i].append(message);
+ }
+ },
+
+ removeAppender: function Logger_removeAppender(appender) {
+ let index = this.ownAppenders.indexOf(appender);
+ if (index == -1) {
+ return;
+ }
+ this.ownAppenders.splice(index, 1);
+ this.updateAppenders();
+ },
+
+ log: function Logger_log(level, args) {
+ if (this.level > level)
+ return;
+
+ // Hold off on creating the message object until we actually have
+ // an appender that's responsible.
+ let message;
+ let appenders = this.appenders;
+ for (let i = 0; i < appenders.length; i++){
+ let appender = appenders[i];
+ if (appender.level > level)
+ continue;
+
+ if (!message)
+ message = new LogMessage(this._name, level,
+ Array.prototype.slice.call(args));
+
+ appender.append(message);
+ }
+ },
+
+ fatal: function Logger_fatal() {
+ this.log(Log4Moz.Level.Fatal, arguments);
+ },
+ error: function Logger_error() {
+ this.log(Log4Moz.Level.Error, arguments);
+ },
+ warn: function Logger_warn() {
+ this.log(Log4Moz.Level.Warn, arguments);
+ },
+ info: function Logger_info(string) {
+ this.log(Log4Moz.Level.Info, arguments);
+ },
+ config: function Logger_config(string) {
+ this.log(Log4Moz.Level.Config, arguments);
+ },
+ debug: function Logger_debug(string) {
+ this.log(Log4Moz.Level.Debug, arguments);
+ },
+ trace: function Logger_trace(string) {
+ this.log(Log4Moz.Level.Trace, arguments);
+ }
+};
+
+/*
+ * LoggerRepository
+ * Implements a hierarchy of Loggers
+ */
+
+function LoggerRepository() {}
+LoggerRepository.prototype = {
+ _loggers: {},
+
+ _rootLogger: null,
+ get rootLogger() {
+ if (!this._rootLogger) {
+ this._rootLogger = new Logger("root", this);
+ this._rootLogger.level = Log4Moz.Level.All;
+ }
+ return this._rootLogger;
+ },
+ set rootLogger(logger) {
+ throw "Cannot change the root logger";
+ },
+
+ _updateParents: function LogRep__updateParents(name) {
+ let pieces = name.split('.');
+ let cur, parent;
+
+ // find the closest parent
+ // don't test for the logger name itself, as there's a chance it's already
+ // there in this._loggers
+ for (let i = 0; i < pieces.length - 1; i++) {
+ if (cur)
+ cur += '.' + pieces[i];
+ else
+ cur = pieces[i];
+ if (cur in this._loggers)
+ parent = cur;
+ }
+
+ // if we didn't assign a parent above, there is no parent
+ if (!parent)
+ this._loggers[name].parent = this.rootLogger;
+ else
+ this._loggers[name].parent = this._loggers[parent];
+
+ // trigger updates for any possible descendants of this logger
+ for (let logger in this._loggers) {
+ if (logger != name && logger.indexOf(name) == 0)
+ this._updateParents(logger);
+ }
+ },
+
+ getLogger: function LogRep_getLogger(name) {
+ if (name in this._loggers)
+ return this._loggers[name];
+ this._loggers[name] = new Logger(name, this);
+ this._updateParents(name);
+ return this._loggers[name];
+ }
+};
+
+/*
+ * Formatters
+ * These massage a LogMessage into whatever output is desired
+ * Only the BasicFormatter is currently implemented
+ */
+
+// Abstract formatter
+function Formatter() {}
+Formatter.prototype = {
+ format: function Formatter_format(message) {}
+};
+
+// services' log4moz lost the date formatting default...
+function BasicFormatter(dateFormat) {
+ if (dateFormat)
+ this.dateFormat = dateFormat;
+}
+BasicFormatter.prototype = {
+ __proto__: Formatter.prototype,
+
+ _dateFormat: null,
+
+ get dateFormat() {
+ if (!this._dateFormat)
+ this._dateFormat = "%Y-%m-%d %H:%M:%S";
+ return this._dateFormat;
+ },
+
+ set dateFormat(format) {
+ this._dateFormat = format;
+ },
+
+ format: function BF_format(message) {
+ let date = new Date(message.time);
+ // The trick below prevents errors further down because mo is null or
+ // undefined.
+ let messageString = message.messageObjects.map(mo => "" + mo).join(" ");
+ return date.toLocaleFormat(this.dateFormat) + "\t" +
+ message.loggerName + "\t" + message.levelDesc + "\t" +
+ messageString + "\n";
+ }
+};
+
+/*
+ * XMLFormatter
+ * Format like log4j's XMLLayout. The intent is that you can hook this up to
+ * a SocketAppender and point them at a Chainsaw GUI running with an
+ * XMLSocketReceiver running. Then your output comes out in Chainsaw.
+ * (Chainsaw is log4j's GUI that displays log output with niceties such as
+ * filtering and conditional coloring.)
+ */
+
+function XMLFormatter() {}
+XMLFormatter.prototype = {
+ __proto__: Formatter.prototype,
+
+ format: function XF_format(message) {
+ let cdataEscapedMessage =
+ message.messageObjects
+ .map(mo => (typeof(mo) == "object") ? mo.toString() : mo)
+ .join(" ")
+ .split(CDATA_END).join(CDATA_ESCAPED_END);
+ return "<log4j:event logger='" + message.loggerName + "' " +
+ "level='" + message.levelDesc + "' thread='unknown' " +
+ "timestamp='" + message.time + "'>" +
+ "<log4j:message><![CDATA[" + cdataEscapedMessage + "]]></log4j:message>" +
+ "</log4j:event>";
+ }
+};
+
+function JSONFormatter() {
+}
+JSONFormatter.prototype = {
+ __proto__: Formatter.prototype,
+
+ format: function JF_format(message) {
+ // XXX I did all kinds of questionable things in here; they should be
+ // resolved...
+ // 1) JSON does not walk the __proto__ chain; there is no need to clobber
+ // it.
+ // 2) Our net mutation is sorta redundant messageObjects alongside
+ // msgObjects, although we only serialize one.
+ let origMessageObjects = message.messageObjects;
+ message.messageObjects = [];
+ let reProto = [];
+ for (let messageObject of origMessageObjects) {
+ if (messageObject)
+ if (messageObject._jsonMe) {
+ message.messageObjects.push(messageObject);
+// FIXME: the commented out code should be fixed in a better way.
+// See bug 984539: find a good way to avoid JSONing the impl in log4moz
+// // temporarily strip the prototype to avoid JSONing the impl.
+// reProto.push([messageObject, messageObject.__proto__]);
+// messageObject.__proto__ = undefined;
+ }
+ else
+ message.messageObjects.push(messageObject.toString());
+ else
+ message.messageObjects.push(messageObject);
+ }
+ let encoded = JSON.stringify(message) + "\r\n";
+ message.msgObjects = origMessageObjects;
+// for (let objectAndProtoPair of reProto) {
+// objectAndProtoPair[0].__proto__ = objectAndProtoPair[1];
+// }
+ return encoded;
+ }
+};
+
+
+/*
+ * Appenders
+ * These can be attached to Loggers to log to different places
+ * Simply subclass and override doAppend to implement a new one
+ */
+
+function Appender(formatter) {
+ this._name = "Appender";
+ this._formatter = formatter? formatter : new BasicFormatter();
+}
+Appender.prototype = {
+ _level: Log4Moz.Level.All,
+
+ append: function App_append(message) {
+ this.doAppend(this._formatter.format(message));
+ },
+ toString: function App_toString() {
+ return this._name + " [level=" + this._level +
+ ", formatter=" + this._formatter + "]";
+ },
+ doAppend: function App_doAppend(message) {}
+};
+
+/*
+ * DumpAppender
+ * Logs to standard out
+ */
+
+function DumpAppender(formatter) {
+ this._name = "DumpAppender";
+ this._formatter = formatter? formatter : new BasicFormatter();
+}
+DumpAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ doAppend: function DApp_doAppend(message) {
+ dump(message);
+ }
+};
+
+/**
+ * An in-memory appender that always logs to its in-memory bucket and associates
+ * each message with a timestamp. Whoever creates us is responsible for causing
+ * us to switch to a new bucket using whatever criteria is appropriate.
+ *
+ * This is intended to be used roughly like an in-memory circular buffer. The
+ * expectation is that we are being used for unit tests and that each unit test
+ * function will get its own bucket. In the event that a test fails we would
+ * be asked for the contents of the current bucket and some portion of the
+ * previous bucket using up to some duration.
+ */
+function TimeAwareMemoryBucketAppender() {
+ this._name = "TimeAwareMemoryBucketAppender";
+ this._level = Log4Moz.Level.All;
+
+ this._lastBucket = null;
+ // to minimize object construction, even indices are timestamps, odd indices
+ // are the message objects.
+ this._curBucket = [];
+ this._curBucketStartedAt = Date.now();
+}
+TimeAwareMemoryBucketAppender.prototype = {
+ get level() { return this._level; },
+ set level(level) { this._level = level; },
+
+ append: function TAMBA_append(message) {
+ if (this._level <= message.level)
+ this._curBucket.push(message);
+ },
+
+ newBucket: function() {
+ this._lastBucket = this._curBucket;
+ this._curBucketStartedAt = Date.now();
+ this._curBucket = [];
+ },
+
+ getPreviousBucketEvents: function(aNumMS) {
+ let lastBucket = this._lastBucket;
+ if (lastBucket == null || !lastBucket.length)
+ return [];
+ let timeBound = this._curBucketStartedAt - aNumMS;
+ // seek backwards through the list...
+ let i;
+ for (i = lastBucket.length - 1; i >= 0; i --) {
+ if (lastBucket[i].time < timeBound)
+ break;
+ }
+ return lastBucket.slice(i+1);
+ },
+
+ getBucketEvents: function() {
+ return this._curBucket.concat();
+ },
+
+ toString: function() {
+ return "[TimeAwareMemoryBucketAppender]";
+ },
+};
+
+/*
+ * ConsoleAppender
+ * Logs to the javascript console
+ */
+
+function ConsoleAppender(formatter) {
+ this._name = "ConsoleAppender";
+ this._formatter = formatter;
+}
+ConsoleAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ // override to send Error and higher level messages to Components.utils.reportError()
+ append: function CApp_append(message) {
+ let stringMessage = this._formatter.format(message);
+ if (message.level > Log4Moz.Level.Warn) {
+ Cu.reportError(stringMessage);
+ }
+ this.doAppend(stringMessage);
+ },
+
+ doAppend: function CApp_doAppend(message) {
+ Services.console.logStringMessage(message);
+ }
+};
+
+/*
+ * FileAppender
+ * Logs to a file
+ */
+
+function FileAppender(file, formatter) {
+ this._name = "FileAppender";
+ this._file = file; // nsIFile
+ this._formatter = formatter? formatter : new BasicFormatter();
+}
+FileAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ __fos: null,
+ get _fos() {
+ if (!this.__fos)
+ this.openStream();
+ return this.__fos;
+ },
+
+ openStream: function FApp_openStream() {
+ this.__fos = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ let flags = MODE_WRONLY | MODE_CREATE | MODE_APPEND;
+ this.__fos.init(this._file, flags, PERMS_FILE, 0);
+ },
+
+ closeStream: function FApp_closeStream() {
+ if (!this.__fos)
+ return;
+ try {
+ this.__fos.close();
+ this.__fos = null;
+ } catch(e) {
+ dump("Failed to close file output stream\n" + e);
+ }
+ },
+
+ doAppend: function FApp_doAppend(message) {
+ if (message === null || message.length <= 0)
+ return;
+ try {
+ this._fos.write(message, message.length);
+ } catch(e) {
+ dump("Error writing file:\n" + e);
+ }
+ },
+
+ clear: function FApp_clear() {
+ this.closeStream();
+ this._file.remove(false);
+ }
+};
+
+/*
+ * RotatingFileAppender
+ * Similar to FileAppender, but rotates logs when they become too large
+ */
+
+function RotatingFileAppender(file, formatter, maxSize, maxBackups) {
+ if (maxSize === undefined)
+ maxSize = ONE_MEGABYTE * 2;
+
+ if (maxBackups === undefined)
+ maxBackups = 0;
+
+ this._name = "RotatingFileAppender";
+ this._file = file; // nsIFile
+ this._formatter = formatter? formatter : new BasicFormatter();
+ this._maxSize = maxSize;
+ this._maxBackups = maxBackups;
+}
+RotatingFileAppender.prototype = {
+ __proto__: FileAppender.prototype,
+
+ doAppend: function RFApp_doAppend(message) {
+ if (message === null || message.length <= 0)
+ return;
+ try {
+ this.rotateLogs();
+ this._fos.write(message, message.length);
+ } catch(e) {
+ dump("Error writing file:\n" + e);
+ }
+ },
+ rotateLogs: function RFApp_rotateLogs() {
+ if(this._file.exists() &&
+ this._file.fileSize < this._maxSize)
+ return;
+
+ this.closeStream();
+
+ for (let i = this.maxBackups - 1; i > 0; i--){
+ let backup = this._file.parent.clone();
+ backup.append(this._file.leafName + "." + i);
+ if (backup.exists())
+ backup.moveTo(this._file.parent, this._file.leafName + "." + (i + 1));
+ }
+
+ let cur = this._file.clone();
+ if (cur.exists())
+ cur.moveTo(cur.parent, cur.leafName + ".1");
+
+ // Note: this._file still points to the same file
+ }
+};
+
+/*
+ * SocketAppender
+ * Logs via TCP to a given host and port. Attempts to automatically reconnect
+ * when the connection drops or cannot be initially re-established. Connection
+ * attempts will happen at most every timeoutDelay seconds (has a sane default
+ * if left blank). Messages are dropped when there is no connection.
+ */
+
+function SocketAppender(host, port, formatter, timeoutDelay) {
+ this._name = "SocketAppender";
+ this._host = host;
+ this._port = port;
+ this._formatter = formatter? formatter : new BasicFormatter();
+ this._timeout_delay = timeoutDelay || DEFAULT_NETWORK_TIMEOUT_DELAY;
+
+ this._socketService = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+ this._mainThread = Services.tm.mainThread;
+}
+SocketAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ __nos: null,
+ get _nos() {
+ if (!this.__nos)
+ this.openStream();
+ return this.__nos;
+ },
+ _nextCheck: 0,
+ openStream: function SApp_openStream() {
+ let now = Date.now();
+ if (now <= this._nextCheck) {
+ return;
+ }
+ this._nextCheck = now + this._timeout_delay * 1000;
+ try {
+ this._transport = this._socketService.createTransport(
+ null, 0, // default socket type
+ this._host, this._port,
+ null); // no proxy
+ this._transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT,
+ this._timeout_delay);
+ // do not set a timeout for TIMEOUT_READ_WRITE. The timeout is not
+ // entirely intuitive; your socket will time out if no one reads or
+ // writes to the socket within the timeout. That, as you can imagine,
+ // is not what we want.
+ this._transport.setEventSink(this, this._mainThread);
+
+ let outputStream = this._transport.openOutputStream(
+ 0, // neither blocking nor unbuffered operation is desired
+ 0, // default buffer size is fine
+ 0 // default buffer count is fine
+ );
+
+ let uniOutputStream = Cc["@mozilla.org/intl/converter-output-stream;1"]
+ .createInstance(Ci.nsIConverterOutputStream);
+ uniOutputStream.init(outputStream, "utf-8", 0, 0x0000);
+
+ this.__nos = uniOutputStream;
+ } catch (ex) {
+ dump("Unexpected SocketAppender connection problem: " +
+ ex.fileName + ":" + ex.lineNumber + ": " + ex + "\n");
+ }
+ },
+
+ closeStream: function SApp_closeStream() {
+ if (!this._transport)
+ return;
+ try {
+ this._connected = false;
+ this._transport = null;
+ let nos = this.__nos;
+ this.__nos = null;
+ nos.close();
+ } catch(e) {
+ // this shouldn't happen, but no one cares
+ }
+ },
+
+ doAppend: function SApp_doAppend(message) {
+ if (message === null || message.length <= 0)
+ return;
+ try {
+ let nos = this._nos;
+ if (nos)
+ nos.writeString(message);
+ } catch(e) {
+ if (this._transport && !this._transport.isAlive()) {
+ this.closeStream();
+ }
+ }
+ },
+
+ clear: function SApp_clear() {
+ this.closeStream();
+ },
+
+ /* nsITransportEventSink */
+ onTransportStatus: function SApp_onTransportStatus(aTransport, aStatus,
+ aProgress, aProgressMax) {
+ if (aStatus == 0x804b0004) // STATUS_CONNECTED_TO is not a constant.
+ this._connected = true;
+ },
+};
+
+/**
+ * Throws an exception whenever it gets a message. Intended to be used in
+ * automated testing situations where the code would normally log an error but
+ * not die in a fatal manner.
+ */
+function ThrowingAppender(thrower, formatter) {
+ this._name = "ThrowingAppender";
+ this._formatter = formatter? formatter : new BasicFormatter();
+ this._thrower = thrower;
+}
+ThrowingAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ doAppend: function TApp_doAppend(message) {
+ if (this._thrower)
+ this._thrower(message);
+ else
+ throw message;
+ }
+};
diff --git a/mailnews/db/gloda/modules/mimeTypeCategories.js b/mailnews/db/gloda/modules/mimeTypeCategories.js
new file mode 100644
index 000000000..463443044
--- /dev/null
+++ b/mailnews/db/gloda/modules/mimeTypeCategories.js
@@ -0,0 +1,204 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file wants to be a data file of some sort. It might do better as a real
+ * raw JSON file. It is trying to be one right now, but it obviously is not.
+ */
+
+var EXPORTED_SYMBOLS = ['MimeCategoryMapping'];
+
+/**
+ * Input data structure to allow us to build a fast mapping from mime type to
+ * category name. The keys in MimeCategoryMapping are the top-level
+ * categories. Each value can either be a list of MIME types or a nested
+ * object which recursively defines sub-categories. We currently do not use
+ * the sub-categories. They are just there to try and organize the MIME types
+ * a little and open the door to future enhancements.
+ *
+ * Do _not_ add additional top-level categories unless you have added
+ * corresponding entries to gloda.properties under the
+ * "gloda.mimetype.category" branch and are making sure localizers are aware
+ * of the change and have time to localize it.
+ *
+ * Entries with wildcards in them are part of a fallback strategy by the
+ * |mimeTypeNoun| and do not actually use regular expressions or anything like
+ * that. Everything is a straight string lookup. Given "foo/bar" we look for
+ * "foo/bar", then "foo/*", and finally "*".
+ */
+var MimeCategoryMapping = {
+ archives: [
+ "application/java-archive",
+ "application/x-java-archive",
+ "application/x-jar",
+ "application/x-java-jnlp-file",
+
+ "application/mac-binhex40",
+ "application/vnd.ms-cab-compressed",
+
+ "application/x-arc",
+ "application/x-arj",
+ "application/x-compress",
+ "application/x-compressed-tar",
+ "application/x-cpio",
+ "application/x-cpio-compressed",
+ "application/x-deb",
+
+ "application/x-bittorrent",
+
+ "application/x-rar",
+ "application/x-rar-compressed",
+ "application/x-7z-compressed",
+ "application/zip",
+ "application/x-zip-compressed",
+ "application/x-zip",
+
+ "application/x-bzip",
+ "application/x-bzip-compressed-tar",
+ "application/x-bzip2",
+ "application/x-gzip",
+ "application/x-tar",
+ "application/x-tar-gz",
+ "application/x-tarz",
+ ],
+ documents: {
+ database: [
+ "application/vnd.ms-access",
+ "application/x-msaccess",
+ "application/msaccess",
+ "application/vnd.msaccess",
+ "application/x-msaccess",
+ "application/mdb",
+ "application/x-mdb",
+
+ "application/vnd.oasis.opendocument.database",
+
+ ],
+ graphics: [
+ "application/postscript",
+ "application/x-bzpostscript",
+ "application/x-dvi",
+ "application/x-gzdvi",
+
+ "application/illustrator",
+
+ "application/vnd.corel-draw",
+ "application/cdr",
+ "application/coreldraw",
+ "application/x-cdr",
+ "application/x-coreldraw",
+ "image/cdr",
+ "image/x-cdr",
+ "zz-application/zz-winassoc-cdr",
+
+ "application/vnd.oasis.opendocument.graphics",
+ "application/vnd.oasis.opendocument.graphics-template",
+ "application/vnd.oasis.opendocument.image",
+
+ "application/x-dia-diagram",
+ ],
+ presentation: [
+ "application/vnd.ms-powerpoint.presentation.macroenabled.12",
+ "application/vnd.ms-powerpoint.template.macroenabled.12",
+ "application/vnd.ms-powerpoint",
+ "application/powerpoint",
+ "application/mspowerpoint",
+ "application/x-mspowerpoint",
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ "application/vnd.openxmlformats-officedocument.presentationml.template",
+
+ "application/vnd.oasis.opendocument.presentation",
+ "application/vnd.oasis.opendocument.presentation-template"
+ ],
+ spreadsheet: [
+ "application/vnd.lotus-1-2-3",
+ "application/x-lotus123",
+ "application/x-123",
+ "application/lotus123",
+ "application/wk1",
+
+ "application/x-quattropro",
+
+ "application/vnd.ms-excel.sheet.binary.macroenabled.12",
+ "application/vnd.ms-excel.sheet.macroenabled.12",
+ "application/vnd.ms-excel.template.macroenabled.12",
+ "application/vnd.ms-excel",
+ "application/msexcel",
+ "application/x-msexcel",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
+
+ "application/vnd.oasis.opendocument.formula",
+ "application/vnd.oasis.opendocument.formula-template",
+ "application/vnd.oasis.opendocument.chart",
+ "application/vnd.oasis.opendocument.chart-template",
+ "application/vnd.oasis.opendocument.spreadsheet",
+ "application/vnd.oasis.opendocument.spreadsheet-template",
+
+ "application/x-gnumeric",
+ ],
+ wordProcessor: [
+ "application/msword",
+ "application/vnd.ms-word",
+ "application/x-msword",
+ "application/msword-template",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
+ "application/vnd.ms-word.document.macroenabled.12",
+ "application/vnd.ms-word.template.macroenabled.12",
+ "application/x-mswrite",
+ "application/x-pocket-word",
+
+ "application/rtf",
+ "text/rtf",
+
+
+ "application/vnd.oasis.opendocument.text",
+ "application/vnd.oasis.opendocument.text-master",
+ "application/vnd.oasis.opendocument.text-template",
+ "application/vnd.oasis.opendocument.text-web",
+
+ "application/vnd.wordperfect",
+
+ "application/x-abiword",
+ "application/x-amipro",
+ ],
+ suite: [
+ "application/vnd.ms-works"
+ ],
+ },
+ images: [
+ "image/*"
+ ],
+ media: {
+ audio: [
+ "audio/*",
+ ],
+ video: [
+ "video/*",
+ ],
+ container: [
+ "application/ogg",
+
+ "application/smil",
+ "application/vnd.ms-asf",
+ "application/vnd.rn-realmedia",
+ "application/x-matroska",
+ "application/x-quicktime-media-link",
+ "application/x-quicktimeplayer",
+ ]
+ },
+ other: [
+ "*"
+ ],
+ pdf: [
+ "application/pdf",
+ "application/x-pdf",
+ "image/pdf",
+ "file/pdf",
+
+ "application/x-bzpdf",
+ "application/x-gzpdf",
+ ],
+}
diff --git a/mailnews/db/gloda/modules/mimemsg.js b/mailnews/db/gloda/modules/mimemsg.js
new file mode 100644
index 000000000..ff984c178
--- /dev/null
+++ b/mailnews/db/gloda/modules/mimemsg.js
@@ -0,0 +1,719 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['MsgHdrToMimeMessage',
+ 'MimeMessage', 'MimeContainer',
+ 'MimeBody', 'MimeUnknown',
+ 'MimeMessageAttachment'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var EMITTER_MIME_CODE = "application/x-js-mime-message";
+
+/**
+ * The URL listener is surplus because the CallbackStreamListener ends up
+ * getting the same set of events, effectively.
+ */
+var dumbUrlListener = {
+ OnStartRunningUrl: function (aUrl) {
+ },
+ OnStopRunningUrl: function (aUrl, aExitCode) {
+ },
+};
+
+/**
+ * Maintain a list of all active stream listeners so that we can cancel them all
+ * during shutdown. If we don't cancel them, we risk calls into javascript
+ * from C++ after the various XPConnect contexts have already begun their
+ * teardown process.
+ */
+var activeStreamListeners = {};
+
+var shutdownCleanupObserver = {
+ _initialized: false,
+ ensureInitialized: function mimemsg_shutdownCleanupObserver_init() {
+ if (this._initialized)
+ return;
+
+ Services.obs.addObserver(this, "quit-application", false);
+
+ this._initialized = true;
+ },
+
+ observe: function mimemsg_shutdownCleanupObserver_observe(
+ aSubject, aTopic, aData) {
+ if (aTopic == "quit-application") {
+ Services.obs.removeObserver(this, "quit-application");
+
+ for (let uri in activeStreamListeners) {
+ let streamListener = activeStreamListeners[uri];
+ if (streamListener._request)
+ streamListener._request.cancel(Cr.NS_BINDING_ABORTED);
+ }
+ }
+ }
+};
+
+function CallbackStreamListener(aMsgHdr, aCallbackThis, aCallback) {
+ this._msgHdr = aMsgHdr;
+ let hdrURI = aMsgHdr.folder.getUriForMsg(aMsgHdr);
+ this._request = null;
+ this._stream = null;
+ if (aCallback === undefined) {
+ this._callbacksThis = [null];
+ this._callbacks = [aCallbackThis];
+ }
+ else {
+ this._callbacksThis = [aCallbackThis];
+ this._callbacks =[aCallback];
+ }
+ activeStreamListeners[hdrURI] = this;
+}
+
+CallbackStreamListener.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener]),
+
+ // nsIRequestObserver part
+ onStartRequest: function (aRequest, aContext) {
+ this._request = aRequest;
+ },
+ onStopRequest: function (aRequest, aContext, aStatusCode) {
+ let msgURI = this._msgHdr.folder.getUriForMsg(this._msgHdr);
+ delete activeStreamListeners[msgURI];
+
+ aContext.QueryInterface(Ci.nsIURI);
+ let message = MsgHdrToMimeMessage.RESULT_RENDEVOUZ[aContext.spec];
+ if (message === undefined)
+ message = null;
+
+ delete MsgHdrToMimeMessage.RESULT_RENDEVOUZ[aContext.spec];
+
+ for (let i = 0; i < this._callbacksThis.length; i++) {
+ try {
+ this._callbacks[i].call(this._callbacksThis[i], this._msgHdr, message);
+ } catch (e) {
+ // Most of the time, exceptions will silently disappear into the endless
+ // deeps of XPConnect, and never reach the surface ever again. At least
+ // warn the user if he has dump enabled.
+ dump("The MsgHdrToMimeMessage callback threw an exception: "+e+"\n");
+ // That one will probably never make it to the original caller.
+ throw(e);
+ }
+ }
+
+ this._msgHdr = null;
+ this._request = null;
+ this._stream = null;
+ this._callbacksThis = null;
+ this._callbacks = null;
+ },
+
+ /* okay, our onDataAvailable should actually never be called. the stream
+ converter is actually eating everything except the start and stop
+ notification. */
+ // nsIStreamListener part
+ onDataAvailable: function (aRequest,aContext,aInputStream,aOffset,aCount) {
+ dump("this should not be happening! arrgggggh!\n");
+ if (this._stream === null) {
+ this._stream = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ this._stream.init(aInputStream);
+ }
+ this._stream.read(aCount);
+
+ },
+};
+
+var gMessenger = Cc["@mozilla.org/messenger;1"].
+ createInstance(Ci.nsIMessenger);
+
+function stripEncryptedParts(aPart) {
+ if (aPart.parts && aPart.isEncrypted) {
+ aPart.parts = []; // Show an empty container.
+ } else if (aPart.parts) {
+ aPart.parts = aPart.parts.map(stripEncryptedParts);
+ }
+ return aPart;
+}
+
+/**
+ * Starts retrieval of a MimeMessage instance for the given message header.
+ * Your callback will be called with the message header you provide and the
+ *
+ * @param aMsgHdr The message header to retrieve the body for and build a MIME
+ * representation of the message.
+ * @param aCallbackThis The (optional) 'this' to use for your callback function.
+ * @param aCallback The callback function to invoke on completion of message
+ * parsing or failure. The first argument passed will be the nsIMsgDBHdr
+ * you passed to this function. The second argument will be the MimeMessage
+ * instance resulting from the processing on success, and null on failure.
+ * @param [aAllowDownload=false] Should we allow the message to be downloaded
+ * for this streaming request? The default is false, which means that we
+ * require that the message be available offline. If false is passed and
+ * the message is not available offline, we will propagate an exception
+ * thrown by the underlying code.
+ * @param [aOptions] Optional options.
+ * @param [aOptions.saneBodySize] Limit body sizes to a 'reasonable' size in
+ * order to combat corrupt offline/message stores creating pathological
+ * situtations where we have erroneously multi-megabyte messages. This
+ * also likely reduces the impact of legitimately ridiculously large
+ * messages.
+ * @param [aOptions.partsOnDemand] If this is a message stored on an IMAP
+ * server, and for whatever reason, it isn't available locally, then setting
+ * this option to true will make sure that attachments aren't downloaded.
+ * This makes sure the message is available quickly.
+ * @param [aOptions.examineEncryptedParts] By default, we won't reveal the
+ * contents of multipart/encrypted parts to the consumers, unless explicitly
+ * requested. In the case of MIME/PGP messages, for instance, the message
+ * will appear as an empty multipart/encrypted container, unless this option
+ * is used.
+ */
+function MsgHdrToMimeMessage(aMsgHdr, aCallbackThis, aCallback,
+ aAllowDownload, aOptions) {
+ shutdownCleanupObserver.ensureInitialized();
+
+ let requireOffline = !aAllowDownload;
+
+ let msgURI = aMsgHdr.folder.getUriForMsg(aMsgHdr);
+ let msgService = gMessenger.messageServiceFromURI(msgURI);
+
+ MsgHdrToMimeMessage.OPTION_TUNNEL = aOptions;
+ let partsOnDemandStr = (aOptions && aOptions.partsOnDemand)
+ ? "&fetchCompleteMessage=false"
+ : "";
+ // By default, Enigmail only decrypts a message streamed via libmime if it's
+ // the one currently on display in the message reader. With this option, we're
+ // letting Enigmail know that it should decrypt the message since the client
+ // explicitly asked for it.
+ let encryptedStr = (aOptions && aOptions.examineEncryptedParts)
+ ? "&examineEncryptedParts=true"
+ : "";
+
+ // S/MIME, our other encryption backend, is not that smart, and always
+ // decrypts data. In order to protect sensitive data (e.g. not index it in
+ // Gloda), unless the client asked for encrypted data, we pass to the client
+ // callback a stripped-down version of the MIME structure where encrypted
+ // parts have been removed.
+ let wrapCallback = function (aCallback, aCallbackThis) {
+ if (aOptions && aOptions.examineEncryptedParts)
+ return aCallback;
+ else
+ return ((aMsgHdr, aMimeMsg) =>
+ aCallback.call(aCallbackThis, aMsgHdr, stripEncryptedParts(aMimeMsg))
+ );
+ };
+
+ // Apparently there used to be an old syntax where the callback was the second
+ // argument...
+ let callback = aCallback ? aCallback : aCallbackThis;
+ let callbackThis = aCallback ? aCallbackThis : null;
+
+ // if we're already streaming this msg, just add the callback
+ // to the listener.
+ let listenerForURI = activeStreamListeners[msgURI];
+ if (listenerForURI != undefined) {
+ listenerForURI._callbacks.push(wrapCallback(callback, callbackThis));
+ listenerForURI._callbacksThis.push(callbackThis);
+ return;
+ }
+ let streamListener = new CallbackStreamListener(
+ aMsgHdr,
+ callbackThis,
+ wrapCallback(callback, callbackThis)
+ );
+
+ try {
+ let streamURI = msgService.streamMessage(
+ msgURI,
+ streamListener, // consumer
+ null, // nsIMsgWindow
+ dumbUrlListener, // nsIUrlListener
+ true, // have them create the converter
+ // additional uri payload, note that "header=" is prepended automatically
+ "filter&emitter=js"+partsOnDemandStr+encryptedStr,
+ requireOffline);
+ } catch (ex) {
+ // If streamMessage throws an exception, we should make sure to clear the
+ // activeStreamListener, or any subsequent attempt at sreaming this URI
+ // will silently fail
+ if (activeStreamListeners[msgURI]) {
+ delete activeStreamListeners[msgURI];
+ }
+ MsgHdrToMimeMessage.OPTION_TUNNEL = null;
+ throw(ex);
+ }
+
+ MsgHdrToMimeMessage.OPTION_TUNNEL = null;
+}
+
+/**
+ * Let the jsmimeemitter provide us with results. The poor emitter (if I am
+ * understanding things correctly) is evaluated outside of the C.u.import
+ * world, so if we were to import him, we would not see him, but rather a new
+ * copy of him. This goes for his globals, etc. (and is why we live in this
+ * file right here). Also, it appears that the XPCOM JS wrappers aren't
+ * magically unified so that we can try and pass data as expando properties
+ * on things like the nsIUri instances either. So we have the jsmimeemitter
+ * import us and poke things into RESULT_RENDEVOUZ. We put it here on this
+ * function to try and be stealthy and avoid polluting the namespaces (or
+ * encouraging bad behaviour) of our importers.
+ *
+ * If you can come up with a prettier way to shuttle this data, please do.
+ */
+MsgHdrToMimeMessage.RESULT_RENDEVOUZ = {};
+/**
+ * Cram rich options here for the MimeMessageEmitter to grab from. We
+ * leverage the known control-flow to avoid needing a whole dictionary here.
+ * We set this immediately before constructing the emitter and clear it
+ * afterwards. Control flow is never yielded during the process and reentrancy
+ * cannot happen via any other means.
+ */
+MsgHdrToMimeMessage.OPTION_TUNNEL = null;
+
+var HeaderHandlerBase = {
+ /**
+ * Look-up a header that should be present at most once.
+ *
+ * @param aHeaderName The header name to retrieve, case does not matter.
+ * @param aDefaultValue The value to return if the header was not found, null
+ * if left unspecified.
+ * @return the value of the header if present, and the default value if not
+ * (defaults to null). If the header was present multiple times, the first
+ * instance of the header is returned. Use getAll if you want all of the
+ * values for the multiply-defined header.
+ */
+ get: function MimeMessage_get(aHeaderName, aDefaultValue) {
+ if (aDefaultValue === undefined) {
+ aDefaultValue = null;
+ }
+ let lowerHeader = aHeaderName.toLowerCase();
+ if (lowerHeader in this.headers)
+ // we require that the list cannot be empty if present
+ return this.headers[lowerHeader][0];
+ else
+ return aDefaultValue;
+ },
+ /**
+ * Look-up a header that can be present multiple times. Use get for headers
+ * that you only expect to be present at most once.
+ *
+ * @param aHeaderName The header name to retrieve, case does not matter.
+ * @return An array containing the values observed, which may mean a zero
+ * length array.
+ */
+ getAll: function MimeMessage_getAll(aHeaderName) {
+ let lowerHeader = aHeaderName.toLowerCase();
+ if (lowerHeader in this.headers)
+ return this.headers[lowerHeader];
+ else
+ return [];
+ },
+ /**
+ * @param aHeaderName Header name to test for its presence.
+ * @return true if the message has (at least one value for) the given header
+ * name.
+ */
+ has: function MimeMessage_has(aHeaderName) {
+ let lowerHeader = aHeaderName.toLowerCase();
+ return lowerHeader in this.headers;
+ },
+ _prettyHeaderString: function MimeMessage__prettyHeaderString(aIndent) {
+ if (aIndent === undefined)
+ aIndent = "";
+ let s = "";
+ for (let header in this.headers) {
+ let values = this.headers[header];
+ s += "\n " + aIndent + header + ": " + values;
+ }
+ return s;
+ }
+};
+
+/**
+ * @ivar partName The MIME part, ex "1.2.2.1". The partName of a (top-level)
+ * message is "1", its first child is "1.1", its second child is "1.2",
+ * its first child's first child is "1.1.1", etc.
+ * @ivar headers Maps lower-cased header field names to a list of the values
+ * seen for the given header. Use get or getAll as convenience helpers.
+ * @ivar parts The list of the MIME part children of this message. Children
+ * will be either MimeMessage instances, MimeMessageAttachment instances,
+ * MimeContainer instances, or MimeUnknown instances. The latter two are
+ * the result of limitations in the Javascript representation generation
+ * at this time, combined with the need to most accurately represent the
+ * MIME structure.
+ */
+function MimeMessage() {
+ this.partName = null;
+ this.headers = {};
+ this.parts = [];
+ this.isEncrypted = false;
+}
+
+MimeMessage.prototype = {
+ __proto__: HeaderHandlerBase,
+ contentType: "message/rfc822",
+
+ /**
+ * @return a list of all attachments contained in this message and all its
+ * sub-messages. Only MimeMessageAttachment instances will be present in
+ * the list (no sub-messages).
+ */
+ get allAttachments() {
+ let results = []; // messages are not attachments, don't include self
+ for (let iChild = 0; iChild < this.parts.length; iChild++) {
+ let child = this.parts[iChild];
+ results = results.concat(child.allAttachments);
+ }
+ return results;
+ },
+
+ /**
+ * @return a list of all attachments contained in this message, with
+ * included/forwarded messages treated as real attachments. Attachments
+ * contained in inner messages won't be shown.
+ */
+ get allUserAttachments() {
+ if (this.url)
+ // The jsmimeemitter camouflaged us as a MimeAttachment
+ return [this];
+ else
+ // Why is there no flatten method for arrays?
+ return this.parts.map(child => child.allUserAttachments)
+ .reduce((a, b) => a.concat(b), []);
+ },
+
+ /**
+ * @return the total size of this message, that is, the size of all subparts
+ */
+ get size () {
+ return this.parts.map(child => child.size)
+ .reduce((a, b) => a + Math.max(b, 0), 0);
+ },
+
+ /**
+ * In the case of attached messages, libmime considers them as attachments,
+ * and if the body is, say, quoted-printable encoded, then libmime will start
+ * counting bytes and notify the js mime emitter about it. The JS mime emitter
+ * being a nice guy, it will try to set a size on us. While this is the
+ * expected behavior for MimeMsgAttachments, we must make sure we can handle
+ * that (failing to write a setter results in exceptions being thrown).
+ */
+ set size (whatever) {
+ // nop
+ },
+
+ /**
+ * @param aMsgFolder A message folder, any message folder. Because this is
+ * a hack.
+ * @return The concatenation of all of the body parts where parts
+ * available as text/plain are pulled as-is, and parts only available
+ * as text/html are converted to plaintext form first. In other words,
+ * if we see a multipart/alternative with a text/plain, we take the
+ * text/plain. If we see a text/html without an alternative, we convert
+ * that to text.
+ */
+ coerceBodyToPlaintext:
+ function MimeMessage_coerceBodyToPlaintext(aMsgFolder) {
+ let bodies = [];
+ for (let part of this.parts) {
+ // an undefined value for something not having the method is fine
+ let body = part.coerceBodyToPlaintext &&
+ part.coerceBodyToPlaintext(aMsgFolder);
+ if (body)
+ bodies.push(body);
+ }
+ if (bodies)
+ return bodies.join("");
+ else
+ return "";
+ },
+
+ /**
+ * Convert the message and its hierarchy into a "pretty string". The message
+ * and each MIME part get their own line. The string never ends with a
+ * newline. For a non-multi-part message, only a single line will be
+ * returned.
+ * Messages have their subject displayed, attachments have their filename and
+ * content-type (ex: image/jpeg) displayed. "Filler" classes simply have
+ * their class displayed.
+ */
+ prettyString: function MimeMessage_prettyString(aVerbose, aIndent,
+ aDumpBody) {
+ if (aIndent === undefined)
+ aIndent = "";
+ let nextIndent = aIndent + " ";
+
+ let s = "Message "+(this.isEncrypted ? "[encrypted] " : "") +
+ "(" + this.size + " bytes): " +
+ "subject" in this.headers ? this.headers.subject : "";
+ if (aVerbose)
+ s += this._prettyHeaderString(nextIndent);
+
+ for (let iPart = 0; iPart < this.parts.length; iPart++) {
+ let part = this.parts[iPart];
+ s += "\n" + nextIndent + (iPart+1) + " " +
+ part.prettyString(aVerbose, nextIndent, aDumpBody);
+ }
+
+ return s;
+ },
+};
+
+
+/**
+ * @ivar contentType The content-type of this container.
+ * @ivar parts The parts held by this container. These can be instances of any
+ * of the classes found in this file.
+ */
+function MimeContainer(aContentType) {
+ this.partName = null;
+ this.contentType = aContentType;
+ this.headers = {};
+ this.parts = [];
+ this.isEncrypted = false;
+}
+
+MimeContainer.prototype = {
+ __proto__: HeaderHandlerBase,
+ get allAttachments() {
+ let results = [];
+ for (let iChild = 0; iChild < this.parts.length; iChild++) {
+ let child = this.parts[iChild];
+ results = results.concat(child.allAttachments);
+ }
+ return results;
+ },
+ get allUserAttachments () {
+ return this.parts.map(child => child.allUserAttachments)
+ .reduce((a, b) => a.concat(b), []);
+ },
+ get size () {
+ return this.parts.map(child => child.size)
+ .reduce((a, b) => a + Math.max(b, 0), 0);
+ },
+ set size (whatever) {
+ // nop
+ },
+ coerceBodyToPlaintext:
+ function MimeContainer_coerceBodyToPlaintext(aMsgFolder) {
+ if (this.contentType == "multipart/alternative") {
+ let htmlPart;
+ // pick the text/plain if we can find one, otherwise remember the HTML one
+ for (let part of this.parts) {
+ if (part.contentType == "text/plain")
+ return part.body;
+ if (part.contentType == "text/html")
+ htmlPart = part;
+ // text/enriched gets transformed into HTML, use it if we don't already
+ // have an HTML part.
+ else if (!htmlPart && part.contentType == "text/enriched")
+ htmlPart = part;
+ }
+ // convert the HTML part if we have one
+ if (htmlPart)
+ return aMsgFolder.convertMsgSnippetToPlainText(htmlPart.body);
+ }
+ // if it's not alternative, recurse/aggregate using MimeMessage logic
+ return MimeMessage.prototype.coerceBodyToPlaintext.call(this, aMsgFolder);
+ },
+ prettyString: function MimeContainer_prettyString(aVerbose, aIndent,
+ aDumpBody) {
+ let nextIndent = aIndent + " ";
+
+ let s = "Container "+(this.isEncrypted ? "[encrypted] " : "")+
+ "(" + this.size + " bytes): " + this.contentType;
+ if (aVerbose)
+ s += this._prettyHeaderString(nextIndent);
+
+ for (let iPart = 0; iPart < this.parts.length; iPart++) {
+ let part = this.parts[iPart];
+ s += "\n" + nextIndent + (iPart+1) + " " +
+ part.prettyString(aVerbose, nextIndent, aDumpBody);
+ }
+
+ return s;
+ },
+ toString: function MimeContainer_toString() {
+ return "Container: " + this.contentType;
+ }
+};
+
+/**
+ * @class Represents a body portion that we understand and do not believe to be
+ * a proper attachment. This means text/plain or text/html and it has no
+ * filename. (A filename suggests an attachment.)
+ *
+ * @ivar contentType The content type of this body materal; text/plain or
+ * text/html.
+ * @ivar body The actual body content.
+ */
+function MimeBody(aContentType) {
+ this.partName = null;
+ this.contentType = aContentType;
+ this.headers = {};
+ this.body = "";
+ this.isEncrypted = false;
+}
+
+MimeBody.prototype = {
+ __proto__: HeaderHandlerBase,
+ get allAttachments() {
+ return []; // we are a leaf
+ },
+ get allUserAttachments() {
+ return []; // we are a leaf
+ },
+ get size() {
+ return this.body.length;
+ },
+ set size (whatever) {
+ // nop
+ },
+ appendBody: function MimeBody_append(aBuf) {
+ this.body += aBuf;
+ },
+ coerceBodyToPlaintext:
+ function MimeBody_coerceBodyToPlaintext(aMsgFolder) {
+ if (this.contentType == "text/plain")
+ return this.body;
+ // text/enriched gets transformed into HTML by libmime
+ if (this.contentType == "text/html" ||
+ this.contentType == "text/enriched")
+ return aMsgFolder.convertMsgSnippetToPlainText(this.body);
+ return "";
+ },
+ prettyString: function MimeBody_prettyString(aVerbose, aIndent, aDumpBody) {
+ let s = "Body: "+(this.isEncrypted ? "[encrypted] " : "")+
+ "" + this.contentType + " (" + this.body.length + " bytes" +
+ (aDumpBody ? (": '" + this.body + "'") : "") + ")";
+ if (aVerbose)
+ s += this._prettyHeaderString(aIndent + " ");
+ return s;
+ },
+ toString: function MimeBody_toString() {
+ return "Body: " + this.contentType + " (" + this.body.length + " bytes)";
+ }
+};
+
+/**
+ * @class A MIME Leaf node that doesn't have a filename so we assume it's not
+ * intended to be an attachment proper. This is probably meant for inline
+ * display or is the result of someone amusing themselves by composing messages
+ * by hand or a bad client. This class should probably be renamed or we should
+ * introduce a better named class that we try and use in preference to this
+ * class.
+ *
+ * @ivar contentType The content type of this part.
+ */
+function MimeUnknown(aContentType) {
+ this.partName = null;
+ this.contentType = aContentType;
+ this.headers = {};
+ // Looks like libmime does not always intepret us as an attachment, which
+ // means we'll have to have a default size. Returning undefined would cause
+ // the recursive size computations to fail.
+ this._size = 0;
+ this.isEncrypted = false;
+ // We want to make sure MimeUnknown has a part property: S/MIME encrypted
+ // messages have a topmost MimeUnknown part, with the encrypted bit set to 1,
+ // and we need to ensure all other encrypted parts are children of this
+ // topmost part.
+ this.parts = [];
+}
+
+MimeUnknown.prototype = {
+ __proto__: HeaderHandlerBase,
+ get allAttachments() {
+ return this.parts.map(child => child.allAttachments)
+ .reduce((a, b) => a.concat(b), []);
+ },
+ get allUserAttachments() {
+ return this.parts.map(child => child.allUserAttachments)
+ .reduce((a, b) => a.concat(b), []);
+ },
+ get size() {
+ return this._size + this.parts.map(child => child.size)
+ .reduce((a, b) => a + Math.max(b, 0), 0);
+ },
+ set size(aSize) {
+ this._size = aSize;
+ },
+ prettyString: function MimeUnknown_prettyString(aVerbose, aIndent,
+ aDumpBody) {
+ let nextIndent = aIndent + " ";
+
+ let s = "Unknown: "+(this.isEncrypted ? "[encrypted] " : "")+
+ "" + this.contentType + " (" + this.size + " bytes)";
+ if (aVerbose)
+ s += this._prettyHeaderString(aIndent + " ");
+
+ for (let iPart = 0; iPart < this.parts.length; iPart++) {
+ let part = this.parts[iPart];
+ s += "\n" + nextIndent + (iPart+1) + " " +
+ (part ? part.prettyString(aVerbose, nextIndent, aDumpBody) : "NULL");
+ }
+ return s;
+ },
+ toString: function MimeUnknown_toString() {
+ return "Unknown: " + this.contentType;
+ }
+};
+
+/**
+ * @class An attachment proper. We think it's an attachment because it has a
+ * filename that libmime was able to figure out.
+ *
+ * @ivar partName @see{MimeMessage.partName}
+ * @ivar name The filename of this attachment.
+ * @ivar contentType The MIME content type of this part.
+ * @ivar url The URL to stream if you want the contents of this part.
+ * @ivar isExternal Is the attachment stored someplace else than in the message?
+ * @ivar size The size of the attachment if available, -1 otherwise (size is set
+ * after initialization by jsmimeemitter.js)
+ */
+function MimeMessageAttachment(aPartName, aName, aContentType, aUrl,
+ aIsExternal) {
+ this.partName = aPartName;
+ this.name = aName;
+ this.contentType = aContentType;
+ this.url = aUrl;
+ this.isExternal = aIsExternal;
+ this.headers = {};
+ this.isEncrypted = false;
+ // parts is copied over from the part instance that preceded us
+ // headers is copied over from the part instance that preceded us
+ // isEncrypted is copied over from the part instance that preceded us
+}
+
+MimeMessageAttachment.prototype = {
+ __proto__: HeaderHandlerBase,
+ // This is a legacy property.
+ get isRealAttachment() {
+ return true;
+ },
+ get allAttachments() {
+ return [this]; // we are a leaf, so just us.
+ },
+ get allUserAttachments() {
+ return [this];
+ },
+ prettyString: function MimeMessageAttachment_prettyString(aVerbose, aIndent,
+ aDumpBody) {
+ let s = "Attachment "+(this.isEncrypted ? "[encrypted] " : "")+
+ "(" + this.size+" bytes): "
+ + this.name + ", " + this.contentType;
+ if (aVerbose)
+ s += this._prettyHeaderString(aIndent + " ");
+ return s;
+ },
+ toString: function MimeMessageAttachment_toString() {
+ return this.prettyString(false, "");
+ },
+};
diff --git a/mailnews/db/gloda/modules/moz.build b/mailnews/db/gloda/modules/moz.build
new file mode 100644
index 000000000..7ad34e2a6
--- /dev/null
+++ b/mailnews/db/gloda/modules/moz.build
@@ -0,0 +1,32 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXTRA_JS_MODULES.gloda += [
+ 'collection.js',
+ 'connotent.js',
+ 'databind.js',
+ 'datamodel.js',
+ 'datastore.js',
+ 'dbview.js',
+ 'everybody.js',
+ 'explattr.js',
+ 'facet.js',
+ 'fundattr.js',
+ 'gloda.js',
+ 'index_ab.js',
+ 'index_msg.js',
+ 'indexer.js',
+ 'log4moz.js',
+ 'mimemsg.js',
+ 'mimeTypeCategories.js',
+ 'msg_search.js',
+ 'noun_freetag.js',
+ 'noun_mimetype.js',
+ 'noun_tag.js',
+ 'public.js',
+ 'query.js',
+ 'suffixtree.js',
+ 'utils.js',
+]
diff --git a/mailnews/db/gloda/modules/msg_search.js b/mailnews/db/gloda/modules/msg_search.js
new file mode 100644
index 000000000..8ba854406
--- /dev/null
+++ b/mailnews/db/gloda/modules/msg_search.js
@@ -0,0 +1,346 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["GlodaMsgSearcher"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/gloda/public.js");
+
+/**
+ * How much time boost should a 'score point' amount to? The authoritative,
+ * incontrivertible answer, across all time and space, is a week.
+ * Note that gloda stores timestamps as PRTimes for no exceedingly good
+ * reason.
+ */
+var FUZZSCORE_TIMESTAMP_FACTOR = 1000 * 1000 * 60 * 60 * 24 * 7;
+
+var RANK_USAGE =
+ "glodaRank(matchinfo(messagesText), 1.0, 2.0, 2.0, 1.5, 1.5)";
+
+var DASCORE =
+ "(((" + RANK_USAGE + " + messages.notability) * " +
+ FUZZSCORE_TIMESTAMP_FACTOR +
+ ") + messages.date)";
+
+/**
+ * A new optimization decision we are making is that we do not want to carry
+ * around any data in our ephemeral tables that is not used for whittling the
+ * result set. The idea is that the btree page cache or OS cache is going to
+ * save us from the disk seeks and carrying around the extra data is just going
+ * to be CPU/memory churn that slows us down.
+ *
+ * Additionally, we try and avoid row lookups that would have their results
+ * discarded by the LIMIT. Because of limitations in FTS3 (which might
+ * be addressed in FTS4 by a feature request), we can't avoid the 'messages'
+ * lookup since that has the message's date and static notability but we can
+ * defer the 'messagesText' lookup.
+ *
+ * This is the access pattern we are after here:
+ * 1) Order the matches with minimized lookup and result storage costs.
+ * - The innermost MATCH does the doclist magic and provides us with
+ * matchinfo() support which does not require content row retrieval
+ * from messagesText. Unfortunately, this is not enough to whittle anything
+ * because we still need static interestingness, so...
+ * - Based on the match we retrieve the date and notability for that row from
+ * 'messages' using this in conjunction with matchinfo() to provide a score
+ * that we can then use to LIMIT our results.
+ * 2) We reissue the MATCH query so that we will be able to use offsets(), but
+ * we intersect the results of this MATCH against our LIMITed results from
+ * step 1.
+ * - We use 'docid IN (phase 1 query)' to accomplish this because it results in
+ * efficient lookup. If we just use a join, we get O(mn) performance because
+ * a cartesian join ends up being performed where either we end up performing
+ * the fulltext query M times and table scan intersect with the results from
+ * phase 1 or we do the fulltext once but traverse the entire result set from
+ * phase 1 N times.
+ * - We believe that the re-execution of the MATCH query should have no disk
+ * costs because it should still be cached by SQLite or the OS. In the case
+ * where memory is so constrained this is not true our behavior is still
+ * probably preferable than the old way because that would have caused lots
+ * of swapping.
+ * - This part of the query otherwise resembles the basic gloda query but with
+ * the inclusion of the offsets() invocation. The messages table lookup
+ * should not involve any disk traffic because the pages should still be
+ * cached (SQLite or OS) from phase 1. The messagesText lookup is new, and
+ * this is the major disk-seek reduction optimization we are making. (Since
+ * we avoid this lookup for all of the documents that were excluded by the
+ * LIMIT.) Since offsets() also needs to retrieve the row from messagesText
+ * there is a nice synergy there.
+ */
+var NUEVO_FULLTEXT_SQL =
+ "SELECT messages.*, messagesText.*, offsets(messagesText) AS osets " +
+ "FROM messagesText, messages " +
+ "WHERE" +
+ " messagesText MATCH ?1 " +
+ " AND messagesText.docid IN (" +
+ "SELECT docid " +
+ "FROM messagesText JOIN messages ON messagesText.docid = messages.id " +
+ "WHERE messagesText MATCH ?1 " +
+ "ORDER BY " + DASCORE + " DESC " +
+ "LIMIT ?2" +
+ " )" +
+ " AND messages.id = messagesText.docid " +
+ " AND +messages.deleted = 0" +
+ " AND +messages.folderID IS NOT NULL" +
+ " AND +messages.messageKey IS NOT NULL";
+
+function identityFunc(x) {
+ return x;
+}
+
+function oneLessMaxZero(x) {
+ if (x <= 1)
+ return 0;
+ else
+ return x - 1;
+}
+
+function reduceSum(accum, curValue) {
+ return accum + curValue;
+}
+
+/*
+ * Columns are: body, subject, attachment names, author, recipients
+ */
+
+/**
+ * Scores if all search terms match in a column. We bias against author
+ * slightly and recipient a bit more in this case because a search that
+ * entirely matches just on a person should give a mention of that person
+ * in the subject or attachment a fighting chance.
+ * Keep in mind that because of our indexing in the face of address book
+ * contacts (namely, we index the name used in the e-mail as well as the
+ * display name on the address book card associated with the e-mail adress)
+ * a contact is going to bias towards matching multiple times.
+ */
+var COLUMN_ALL_MATCH_SCORES = [4, 20, 20, 16, 12];
+/**
+ * Score for each distinct term that matches in the column. This is capped
+ * by COLUMN_ALL_SCORES.
+ */
+var COLUMN_PARTIAL_PER_MATCH_SCORES = [1, 4, 4, 4, 3];
+/**
+ * If a term matches multiple times, what is the marginal score for each
+ * additional match. We count the total number of matches beyond the
+ * first match for each term. In other words, if we have 3 terms which
+ * matched 5, 3, and 0 times, then the total from our perspective is
+ * (5 - 1) + (3 - 1) + 0 = 4 + 2 + 0 = 6. We take the minimum of that value
+ * and the value in COLUMN_MULTIPLE_MATCH_LIMIT and multiply by the value in
+ * COLUMN_MULTIPLE_MATCH_SCORES.
+ */
+var COLUMN_MULTIPLE_MATCH_SCORES = [1, 0, 0, 0, 0];
+var COLUMN_MULTIPLE_MATCH_LIMIT = [10, 0, 0, 0, 0];
+
+/**
+ * Score the message on its offsets (from stashedColumns).
+ */
+function scoreOffsets(aMessage, aContext) {
+ let score = 0;
+
+ let termTemplate = aContext.terms.map(_ => 0);
+ // for each column, a list of the incidence of each term
+ let columnTermIncidence = [termTemplate.concat(),
+ termTemplate.concat(),
+ termTemplate.concat(),
+ termTemplate.concat(),
+ termTemplate.concat()];
+
+ // we need a friendlyParseInt because otherwise the radix stuff happens
+ // because of the extra arguments map parses. curse you, map!
+ let offsetNums =
+ aContext.stashedColumns[aMessage.id][0].split(" ").map(x => parseInt(x));
+ for (let i=0; i < offsetNums.length; i += 4) {
+ let columnIndex = offsetNums[i];
+ let termIndex = offsetNums[i+1];
+ columnTermIncidence[columnIndex][termIndex]++;
+ }
+
+ for (let iColumn = 0; iColumn < COLUMN_ALL_MATCH_SCORES.length; iColumn++) {
+ let termIncidence = columnTermIncidence[iColumn];
+ // bestow all match credit
+ if (termIncidence.every(identityFunc))
+ score += COLUMN_ALL_MATCH_SCORES[iColumn];
+ // bestow partial match credit
+ else if (termIncidence.some(identityFunc))
+ score += Math.min(COLUMN_ALL_MATCH_SCORES[iColumn],
+ COLUMN_PARTIAL_PER_MATCH_SCORES[iColumn] *
+ termIncidence.filter(identityFunc).length);
+ // bestow multiple match credit
+ score += Math.min(termIncidence.map(oneLessMaxZero).reduce(reduceSum, 0),
+ COLUMN_MULTIPLE_MATCH_LIMIT[iColumn]) *
+ COLUMN_MULTIPLE_MATCH_SCORES[iColumn];
+ }
+
+ return score;
+}
+
+/**
+ * The searcher basically looks like a query, but is specialized for fulltext
+ * search against messages. Most of the explicit specialization involves
+ * crafting a SQL query that attempts to order the matches by likelihood that
+ * the user was looking for it. This is based on full-text matches combined
+ * with an explicit (generic) interest score value placed on the message at
+ * indexing time. This is followed by using the more generic gloda scoring
+ * mechanism to explicitly score the messages given the search context in
+ * addition to the more generic score adjusting rules.
+ */
+function GlodaMsgSearcher(aListener, aSearchString, aAndTerms) {
+ this.listener = aListener;
+
+ this.searchString = aSearchString;
+ this.fulltextTerms = this.parseSearchString(aSearchString);
+ this.andTerms = (aAndTerms != null) ? aAndTerms : true;
+
+ this.query = null;
+ this.collection = null;
+
+ this.scores = null;
+}
+GlodaMsgSearcher.prototype = {
+ /**
+ * Number of messages to retrieve initially.
+ */
+ get retrievalLimit() {
+ return Services.prefs.getIntPref(
+ "mailnews.database.global.search.msg.limit"
+ );
+ },
+
+ /**
+ * Parse the string into terms/phrases by finding matching double-quotes.
+ */
+ parseSearchString: function GlodaMsgSearcher_parseSearchString(aSearchString) {
+ aSearchString = aSearchString.trim();
+ let terms = [];
+
+ /*
+ * Add the term as long as the trim on the way in didn't obliterate it.
+ *
+ * In the future this might have other helper logic; it did once before.
+ */
+ function addTerm(aTerm) {
+ if (aTerm)
+ terms.push(aTerm);
+ }
+
+ while (aSearchString) {
+ if (aSearchString.startsWith('"')) {
+ let endIndex = aSearchString.indexOf(aSearchString[0], 1);
+ // eat the quote if it has no friend
+ if (endIndex == -1) {
+ aSearchString = aSearchString.substring(1);
+ continue;
+ }
+
+ addTerm(aSearchString.substring(1, endIndex).trim());
+ aSearchString = aSearchString.substring(endIndex + 1);
+ continue;
+ }
+
+ let spaceIndex = aSearchString.indexOf(" ");
+ if (spaceIndex == -1) {
+ addTerm(aSearchString);
+ break;
+ }
+
+ addTerm(aSearchString.substring(0, spaceIndex));
+ aSearchString = aSearchString.substring(spaceIndex+1);
+ }
+
+ return terms;
+ },
+
+ buildFulltextQuery: function GlodaMsgSearcher_buildFulltextQuery() {
+ let query = Gloda.newQuery(Gloda.NOUN_MESSAGE, {
+ noMagic: true,
+ explicitSQL: NUEVO_FULLTEXT_SQL,
+ limitClauseAlreadyIncluded: true,
+ // osets is 0-based column number 14 (volatile to column changes)
+ // save the offset column for extra analysis
+ stashColumns: [14]
+ });
+
+ let fulltextQueryString = "";
+
+ for (let [iTerm, term] of this.fulltextTerms.entries()) {
+ if (iTerm)
+ fulltextQueryString += this.andTerms ? " " : " OR ";
+
+ // Put our term in quotes. This is needed for the tokenizer to be able
+ // to do useful things. The exception is people clever enough to use
+ // NEAR.
+ if (/^NEAR(\/\d+)?$/.test(term))
+ fulltextQueryString += term;
+ // Check if this is a single-character CJK search query. If so, we want
+ // to add a wildcard.
+ // Our tokenizer treats anything at/above 0x2000 as CJK for now.
+ else if (term.length == 1 && term.charCodeAt(0) >= 0x2000)
+ fulltextQueryString += term + "*";
+ else if (
+ term.length == 2 &&
+ term.charCodeAt(0) >= 0x2000 &&
+ term.charCodeAt(1) >= 0x2000
+ || term.length >= 3
+ )
+ fulltextQueryString += '"' + term + '"';
+
+ }
+
+ query.fulltextMatches(fulltextQueryString);
+ query.limit(this.retrievalLimit);
+
+ return query;
+ },
+
+ getCollection: function GlodaMsgSearcher_getCollection(
+ aListenerOverride, aData) {
+ if (aListenerOverride)
+ this.listener = aListenerOverride;
+
+ this.query = this.buildFulltextQuery();
+ this.collection = this.query.getCollection(this, aData);
+ this.completed = false;
+
+ return this.collection;
+ },
+
+ sortBy: '-dascore',
+
+ onItemsAdded: function GlodaMsgSearcher_onItemsAdded(aItems, aCollection) {
+ let newScores = Gloda.scoreNounItems(
+ aItems,
+ {
+ terms: this.fulltextTerms,
+ stashedColumns: aCollection.stashedColumns
+ },
+ [scoreOffsets]);
+ if (this.scores)
+ this.scores = this.scores.concat(newScores);
+ else
+ this.scores = newScores;
+
+ if (this.listener)
+ this.listener.onItemsAdded(aItems, aCollection);
+ },
+ onItemsModified: function GlodaMsgSearcher_onItemsModified(aItems,
+ aCollection) {
+ if (this.listener)
+ this.listener.onItemsModified(aItems, aCollection);
+ },
+ onItemsRemoved: function GlodaMsgSearcher_onItemsRemoved(aItems,
+ aCollection) {
+ if (this.listener)
+ this.listener.onItemsRemoved(aItems, aCollection);
+ },
+ onQueryCompleted: function GlodaMsgSearcher_onQueryCompleted(aCollection) {
+ this.completed = true;
+ if (this.listener)
+ this.listener.onQueryCompleted(aCollection);
+ },
+};
diff --git a/mailnews/db/gloda/modules/noun_freetag.js b/mailnews/db/gloda/modules/noun_freetag.js
new file mode 100644
index 000000000..8c92d53ae
--- /dev/null
+++ b/mailnews/db/gloda/modules/noun_freetag.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['FreeTag', 'FreeTagNoun'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+Cu.import("resource:///modules/gloda/gloda.js");
+
+function FreeTag(aTagName) {
+ this.name = aTagName;
+}
+
+FreeTag.prototype = {
+ toString: function () {
+ return this.name;
+ }
+};
+
+/**
+ * @namespace Tag noun provider. Since the tag unique value is stored as a
+ * parameter, we are an odd case and semantically confused.
+ */
+var FreeTagNoun = {
+ _log: Log4Moz.repository.getLogger("gloda.noun.freetag"),
+
+ name: "freetag",
+ clazz: FreeTag,
+ allowsArbitraryAttrs: false,
+ usesParameter: true,
+
+ _listeners: [],
+ addListener: function(aListener) {
+ this._listeners.push(aListener);
+ },
+ removeListener: function(aListener) {
+ let index = this._listeners.indexOf(aListener);
+ if (index >=0)
+ this._listeners.splice(index, 1);
+ },
+
+ populateKnownFreeTags: function() {
+ for (let attr of this.objectNounOfAttributes) {
+ let attrDB = attr.dbDef;
+ for (let param in attrDB.parameterBindings) {
+ this.getFreeTag(param);
+ }
+ }
+ },
+
+ knownFreeTags: {},
+ getFreeTag: function(aTagName) {
+ let tag = this.knownFreeTags[aTagName];
+ if (!tag) {
+ tag = this.knownFreeTags[aTagName] = new FreeTag(aTagName);
+ for (let listener of this._listeners)
+ listener.onFreeTagAdded(tag);
+ }
+ return tag;
+ },
+
+ comparator: function gloda_noun_freetag_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a.name.localeCompare(b.name);
+ },
+
+ toParamAndValue: function gloda_noun_freetag_toParamAndValue(aTag) {
+ return [aTag.name, null];
+ },
+
+ toJSON: function gloda_noun_freetag_toJSON(aTag) {
+ return aTag.name;
+ },
+ fromJSON: function gloda_noun_freetag_fromJSON(aTagName) {
+ return this.getFreeTag(aTagName);
+ },
+};
+
+Gloda.defineNoun(FreeTagNoun);
diff --git a/mailnews/db/gloda/modules/noun_mimetype.js b/mailnews/db/gloda/modules/noun_mimetype.js
new file mode 100644
index 000000000..066031c4a
--- /dev/null
+++ b/mailnews/db/gloda/modules/noun_mimetype.js
@@ -0,0 +1,365 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['MimeType', 'MimeTypeNoun'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+Cu.import("resource:///modules/StringBundle.js");
+
+var LOG = Log4Moz.repository.getLogger("gloda.noun.mimetype");
+
+Cu.import("resource:///modules/gloda/gloda.js");
+
+var CategoryStringMap = {};
+
+/**
+ * Mime type abstraction that exists primarily so we can map mime types to
+ * integer id's.
+ *
+ * Instances of this class should only be retrieved via |MimeTypeNoun|; no one
+ * should ever create an instance directly.
+ */
+function MimeType(aID, aType, aSubType, aFullType, aCategory) {
+ this._id = aID;
+ this._type = aType;
+ this._subType = aSubType;
+ this._fullType = aFullType;
+ this._category = aCategory;
+}
+
+MimeType.prototype = {
+ /**
+ * The integer id we have associated with the mime type. This is stable for
+ * the lifetime of the database, which means that anything in the Gloda
+ * database can use this without fear. Things not persisted in the database
+ * should use the actual string mime type, retrieval via |fullType|.
+ */
+ get id() { return this._id; },
+ /**
+ * The first part of the MIME type; "text/plain" gets you "text".
+ */
+ get type() { return this._type; },
+ set fullType(aFullType) {
+ if (!this._fullType) {
+ this._fullType = aFullType;
+ [this._type, this._subType] = this._fullType.split("/");
+ this._category =
+ MimeTypeNoun._getCategoryForMimeType(aFullType, this._type);
+ }
+ },
+ /**
+ * If the |fullType| is "text/plain", subType is "plain".
+ */
+ get subType() { return this._subType; },
+ /**
+ * The full MIME type; "text/plain" returns "text/plain".
+ */
+ get fullType() { return this._fullType; },
+ toString: function () {
+ return this.fullType;
+ },
+
+ /**
+ * @return the category we believe this mime type belongs to. This category
+ * name should never be shown directly to the user. Instead, use
+ * |categoryLabel| to get the localized name for the category. The
+ * category mapping comes from mimeTypesCategories.js.
+ */
+ get category() {
+ return this._category;
+ },
+ /**
+ * @return The localized label for the category from gloda.properties in the
+ * "gloda.mimetype.category.CATEGORY.label" definition using the value
+ * from |category|.
+ */
+ get categoryLabel() {
+ return CategoryStringMap[this._category];
+ }
+};
+
+/**
+ * Mime type noun provider.
+ *
+ * The set of MIME Types is sufficiently limited that we can keep them all in
+ * memory. In theory it is also sufficiently limited that we could use the
+ * parameter mechanism in the database. However, it is more efficient, for
+ * both space and performance reasons, to store the specific mime type as a
+ * value. For future-proofing reasons, we opt to use a database table to
+ * persist the mapping rather than a hard-coded list. A preferences file or
+ * other text file would arguably suffice, but for consistency reasons, the
+ * database is not a bad thing.
+ */
+var MimeTypeNoun = {
+ name: "mime-type",
+ clazz: MimeType, // gloda supports clazz as well as class
+ allowsArbitraryAttrs: false,
+
+ _strings: new StringBundle("chrome://messenger/locale/gloda.properties"),
+
+ // note! update test_noun_mimetype if you change our internals!
+ _mimeTypes: {},
+ _mimeTypesByID: {},
+ TYPE_BLOCK_SIZE: 16384,
+ _mimeTypeHighID: {},
+ _mimeTypeRangeDummyObjects: {},
+ _highID: 0,
+
+ // we now use the exciting 'schema' mechanism of defineNoun to get our table
+ // created for us, plus some helper methods that we simply don't use.
+ schema: {
+ name: 'mimeTypes',
+ columns: [['id', 'INTEGER PRIMARY KEY', '_id'],
+ ['mimeType', 'TEXT', 'fullType']],
+ },
+
+ _init: function() {
+ LOG.debug("loading MIME types");
+ this._loadCategoryMapping();
+ this._loadMimeTypes();
+ },
+
+ /**
+ * A map from MIME type to category name.
+ */
+ _mimeTypeToCategory: {},
+ /**
+ * Load the contents of mimeTypeCategories.js and populate
+ */
+ _loadCategoryMapping: function MimeTypeNoun__loadCategoryMapping() {
+ let mimecatNS = {};
+ Cu.import("resource:///modules/gloda/mimeTypeCategories.js",
+ mimecatNS);
+ let mcm = mimecatNS.MimeCategoryMapping;
+
+ let mimeTypeToCategory = this._mimeTypeToCategory;
+
+ function procMapObj(aSubTree, aCategories) {
+ for (let key in aSubTree) {
+ let value = aSubTree[key];
+ // Add this category to our nested categories list. Use concat since
+ // the list will be long-lived and each list needs to be distinct.
+ let categories = aCategories.concat();
+ categories.push(key);
+
+ if (categories.length == 1) {
+ CategoryStringMap[key] =
+ MimeTypeNoun._strings.get(
+ "gloda.mimetype.category." + key + ".label");
+ }
+
+ // Is it an array? If so, just process this depth
+ if (Array.isArray(value)) {
+ for (let mimeTypeStr of value) {
+ mimeTypeToCategory[mimeTypeStr] = categories;
+ }
+ }
+ // it's yet another sub-tree branch
+ else {
+ procMapObj(value, categories);
+ }
+ }
+ }
+
+ procMapObj(mimecatNS.MimeCategoryMapping, []);
+ },
+
+ /**
+ * Lookup the category associated with a MIME type given its full type and
+ * type. (So, "foo/bar" and "foo" for "foo/bar".)
+ */
+ _getCategoryForMimeType:
+ function MimeTypeNoun__getCategoryForMimeType(aFullType, aType) {
+ if (aFullType in this._mimeTypeToCategory)
+ return this._mimeTypeToCategory[aFullType][0];
+ let wildType = aType + "/*";
+ if (wildType in this._mimeTypeToCategory)
+ return this._mimeTypeToCategory[wildType][0];
+ return this._mimeTypeToCategory["*"][0];
+ },
+
+ /**
+ * In order to allow the gloda query mechanism to avoid hitting the database,
+ * we need to either define the noun type as cachable and have a super-large
+ * cache or simply have a collection with every MIME type in it that stays
+ * alive forever.
+ * This is that collection. It is initialized by |_loadMimeTypes|. As new
+ * MIME types are created, we add them to the collection.
+ */
+ _universalCollection: null,
+
+ /**
+ * Kick off a query of all the mime types in our database, leaving
+ * |_processMimeTypes| to actually do the legwork.
+ */
+ _loadMimeTypes: function MimeTypeNoun__loadMimeTypes() {
+ // get all the existing mime types!
+ let query = Gloda.newQuery(this.id);
+ let nullFunc = function() {};
+ this._universalCollection = query.getCollection({
+ onItemsAdded: nullFunc, onItemsModified: nullFunc,
+ onItemsRemoved: nullFunc,
+ onQueryCompleted: function (aCollection) {
+ MimeTypeNoun._processMimeTypes(aCollection.items);
+ }
+ }, null);
+ },
+
+ /**
+ * For the benefit of our Category queryHelper, we need dummy ranged objects
+ * that cover the numerical address space allocated to the category. We
+ * can't use a real object for the upper-bound because the upper-bound is
+ * constantly growing and there is the chance the query might get persisted,
+ * which means these values need to be long-lived. Unfortunately, our
+ * solution to this problem (dummy objects) complicates the second case,
+ * should it ever occur. (Because the dummy objects cannot be persisted
+ * on their own... but there are other issues that will come up that we will
+ * just have to deal with then.)
+ */
+ _createCategoryDummies: function (aId, aCategory) {
+ let blockBottom = aId - (aId % this.TYPE_BLOCK_SIZE);
+ let blockTop = blockBottom + this.TYPE_BLOCK_SIZE - 1;
+ this._mimeTypeRangeDummyObjects[aCategory] = [
+ new MimeType(blockBottom, "!category-dummy!", aCategory,
+ "!category-dummy!/" + aCategory, aCategory),
+ new MimeType(blockTop, "!category-dummy!", aCategory,
+ "!category-dummy!/" + aCategory, aCategory)
+ ];
+ },
+
+ _processMimeTypes: function MimeTypeNoun__processMimeTypes(aMimeTypes) {
+ for (let mimeType of aMimeTypes) {
+ if (mimeType.id > this._highID)
+ this._highID = mimeType.id;
+ this._mimeTypes[mimeType] = mimeType;
+ this._mimeTypesByID[mimeType.id] = mimeType;
+
+ let typeBlock = mimeType.id - (mimeType.id % this.TYPE_BLOCK_SIZE);
+ let blockHighID = (mimeType.category in this._mimeTypeHighID) ?
+ this._mimeTypeHighID[mimeType.category] : undefined;
+ // create the dummy range objects
+ if (blockHighID === undefined)
+ this._createCategoryDummies(mimeType.id, mimeType.category);
+ if ((blockHighID === undefined) || mimeType.id > blockHighID)
+ this._mimeTypeHighID[mimeType.category] = mimeType.id;
+ }
+ },
+
+ _addNewMimeType: function MimeTypeNoun__addNewMimeType(aMimeTypeName) {
+ let [typeName, subTypeName] = aMimeTypeName.split("/");
+ let category = this._getCategoryForMimeType(aMimeTypeName, typeName);
+
+ if (!(category in this._mimeTypeHighID)) {
+ let nextID = this._highID - (this._highID % this.TYPE_BLOCK_SIZE) +
+ this.TYPE_BLOCK_SIZE;
+ this._mimeTypeHighID[category] = nextID;
+ this._createCategoryDummies(nextID, category);
+ }
+
+ let nextID = ++this._mimeTypeHighID[category];
+
+ let mimeType = new MimeType(nextID, typeName, subTypeName, aMimeTypeName,
+ category);
+ if (mimeType.id > this._highID)
+ this._highID = mimeType.id;
+
+ this._mimeTypes[aMimeTypeName] = mimeType;
+ this._mimeTypesByID[nextID] = mimeType;
+
+ // As great as the gloda extension mechanisms are, we don't think it makes
+ // a lot of sense to use them in this case. So we directly trigger object
+ // insertion without any of the grokNounItem stuff.
+ this.objInsert.call(this.datastore, mimeType);
+ // Since we bypass grokNounItem and its fun, we need to explicitly add the
+ // new MIME-type to _universalCollection ourselves. Don't try this at
+ // home, kids.
+ this._universalCollection._onItemsAdded([mimeType]);
+
+ return mimeType;
+ },
+
+ /**
+ * Map a mime type to a |MimeType| instance, creating it if necessary.
+ *
+ * @param aMimeTypeName The mime type. It may optionally include parameters
+ * (which will be ignored). A mime type is of the form "type/subtype".
+ * A type with parameters would look like 'type/subtype; param="value"'.
+ */
+ getMimeType: function MimeTypeNoun_getMimeType(aMimeTypeName) {
+ // first, lose any parameters
+ let semiIndex = aMimeTypeName.indexOf(";");
+ if (semiIndex >= 0)
+ aMimeTypeName = aMimeTypeName.substring(0, semiIndex);
+ aMimeTypeName = aMimeTypeName.trim().toLowerCase();
+
+ if (aMimeTypeName in this._mimeTypes)
+ return this._mimeTypes[aMimeTypeName];
+ else
+ return this._addNewMimeType(aMimeTypeName);
+ },
+
+ /**
+ * Query helpers contribute additional functions to the query object for the
+ * attributes that use the noun type. For example, we define Category, so
+ * for the "attachmentTypes" attribute, "attachmentTypesCategory" would be
+ * exposed.
+ */
+ queryHelpers: {
+ /**
+ * Query for MIME type categories based on one or more MIME type objects
+ * passed in. We want the range to span the entire block allocated to the
+ * category.
+ *
+ * @param aAttrDef The attribute that is using us.
+ * @param aArguments The actual arguments object that
+ */
+ Category: function(aAttrDef, aArguments) {
+ let rangePairs = [];
+ // If there are no arguments then we want to fall back to the 'in'
+ // constraint which matches on any attachment.
+ if (aArguments.length == 0)
+ return this._inConstraintHelper(aAttrDef, []);
+
+ for (let iArg = 0; iArg < aArguments.length; iArg++) {
+ let arg = aArguments[iArg];
+ rangePairs.push(MimeTypeNoun._mimeTypeRangeDummyObjects[arg.category]);
+ }
+ return this._rangedConstraintHelper(aAttrDef, rangePairs);
+ }
+ },
+
+ comparator: function gloda_noun_mimeType_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a.fullType.localeCompare(b.fullType);
+ },
+
+ toParamAndValue: function gloda_noun_mimeType_toParamAndValue(aMimeType) {
+ return [null, aMimeType.id];
+ },
+ toJSON: function gloda_noun_mimeType_toJSON(aMimeType) {
+ return aMimeType.id;
+ },
+ fromJSON: function gloda_noun_mimeType_fromJSON(aMimeTypeID) {
+ return this._mimeTypesByID[aMimeTypeID];
+ },
+};
+Gloda.defineNoun(MimeTypeNoun, Gloda.NOUN_MIME_TYPE);
+try {
+MimeTypeNoun._init();
+} catch (ex) {
+ LOG.error("problem init-ing: " + ex.fileName + ":" + ex.lineNumber + ": " + ex);
+}
diff --git a/mailnews/db/gloda/modules/noun_tag.js b/mailnews/db/gloda/modules/noun_tag.js
new file mode 100644
index 000000000..292bdad9b
--- /dev/null
+++ b/mailnews/db/gloda/modules/noun_tag.js
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['TagNoun'];
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/gloda.js");
+
+/**
+ * @namespace Tag noun provider.
+ */
+var TagNoun = {
+ name: "tag",
+ clazz: Ci.nsIMsgTag,
+ usesParameter: true,
+ allowsArbitraryAttrs: false,
+ idAttr: "key",
+ _msgTagService: null,
+ _tagMap: null,
+ _tagList: null,
+
+ _init: function () {
+ this._msgTagService = MailServices.tags;
+ this._updateTagMap();
+ },
+
+ getAllTags: function gloda_noun_tag_getAllTags() {
+ if (this._tagList == null)
+ this._updateTagMap();
+ return this._tagList;
+ },
+
+ _updateTagMap: function gloda_noun_tag_updateTagMap() {
+ this._tagMap = {};
+ let tagArray = this._tagList = this._msgTagService.getAllTags({});
+ for (let iTag = 0; iTag < tagArray.length; iTag++) {
+ let tag = tagArray[iTag];
+ this._tagMap[tag.key] = tag;
+ }
+ },
+
+ comparator: function gloda_noun_tag_comparator(a, b) {
+ if (a == null) {
+ if (b == null)
+ return 0;
+ else
+ return 1;
+ }
+ else if (b == null) {
+ return -1;
+ }
+ return a.tag.localeCompare(b.tag);
+ },
+ userVisibleString: function gloda_noun_tag_userVisibleString(aTag) {
+ return aTag.tag;
+ },
+
+ // we cannot be an attribute value
+
+ toParamAndValue: function gloda_noun_tag_toParamAndValue(aTag) {
+ return [aTag.key, null];
+ },
+ toJSON: function gloda_noun_tag_toJSON(aTag) {
+ return aTag.key;
+ },
+ fromJSON: function gloda_noun_tag_fromJSON(aTagKey, aIgnored) {
+ let tag = this._tagMap.hasOwnProperty(aTagKey) ? this._tagMap[aTagKey]
+ : undefined;
+ // you will note that if a tag is removed, we are unable to aggressively
+ // deal with this. we are okay with this, but it would be nice to be able
+ // to listen to the message tag service to know when we should rebuild.
+ if ((tag === undefined) && this._msgTagService.isValidKey(aTagKey)) {
+ this._updateTagMap();
+ tag = this._tagMap[aTagKey];
+ }
+ // we intentionally are returning undefined if the tag doesn't exist
+ return tag;
+ },
+ /**
+ * Convenience helper to turn a tag key into a tag name.
+ */
+ getTag: function gloda_noun_tag_getTag(aTagKey) {
+ return this.fromJSON(aTagKey);
+ }
+};
+
+TagNoun._init();
+Gloda.defineNoun(TagNoun, Gloda.NOUN_TAG);
diff --git a/mailnews/db/gloda/modules/public.js b/mailnews/db/gloda/modules/public.js
new file mode 100644
index 000000000..63c7d87a9
--- /dev/null
+++ b/mailnews/db/gloda/modules/public.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["Gloda"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/gloda.js");
+Cu.import("resource:///modules/gloda/everybody.js");
+Cu.import("resource:///modules/gloda/indexer.js");
+// initialize the indexer! (who was actually imported as a nested dep by the
+// things everybody.js imported.) We waited until now so it could know about
+// its indexers.
+GlodaIndexer._init();
+Cu.import("resource:///modules/gloda/index_msg.js");
+
+/**
+ * Expose some junk
+ */
+function proxy(aSourceObj, aSourceAttr, aDestObj, aDestAttr) {
+ aDestObj[aDestAttr] = function() {
+ return aSourceObj[aSourceAttr].apply(aSourceObj, arguments);
+ };
+}
+
+proxy(GlodaIndexer, "addListener", Gloda, "addIndexerListener");
+proxy(GlodaIndexer, "removeListener", Gloda, "removeIndexerListener");
+proxy(GlodaMsgIndexer, "isMessageIndexed", Gloda, "isMessageIndexed");
+proxy(GlodaMsgIndexer, "setFolderIndexingPriority", Gloda,
+ "setFolderIndexingPriority");
+proxy(GlodaMsgIndexer, "resetFolderIndexingPriority", Gloda,
+ "resetFolderIndexingPriority");
diff --git a/mailnews/db/gloda/modules/query.js b/mailnews/db/gloda/modules/query.js
new file mode 100644
index 000000000..2c2139498
--- /dev/null
+++ b/mailnews/db/gloda/modules/query.js
@@ -0,0 +1,618 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["GlodaQueryClassFactory"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+
+// GlodaDatastore has some constants we need, and oddly enough, there was no
+// load dependency preventing us from doing this.
+Cu.import("resource:///modules/gloda/datastore.js");
+
+/**
+ * @class Query class core; each noun gets its own sub-class where attributes
+ * have helper methods bound.
+ *
+ * @param aOptions A dictionary of options. Current legal options are:
+ * - noMagic: Indicates that the noun's dbQueryJoinMagic should be ignored.
+ * Currently, this means that messages will not have their
+ * full-text indexed values re-attached. This is planned to be
+ * offset by having queries/cache lookups that do not request
+ * noMagic to ensure that their data does get loaded.
+ * - explicitSQL: A hand-rolled alternate representation for the core
+ * SELECT portion of the SQL query. The queryFromQuery logic still
+ * generates its normal query, we just ignore its result in favor of
+ * your provided value. This means that the positional parameter
+ * list is still built and you should/must rely on those bound
+ * parameters (using '?'). The replacement occurs prior to the
+ * outerWrapColumns, ORDER BY, and LIMIT contributions to the query.
+ * - outerWrapColumns: If provided, wraps the query in a "SELECT *,blah
+ * FROM (actual query)" where blah is your list of outerWrapColumns
+ * made comma-delimited. The idea is that this allows you to
+ * reference the result of expressions inside the query using their
+ * names rather than having to duplicate the logic. In practice,
+ * this makes things more readable but is unlikely to improve
+ * performance. (Namely, my use of 'offsets' for full-text stuff
+ * ends up in the EXPLAIN plan twice despite this.)
+ * - noDbQueryValidityConstraints: Indicates that any validity constraints
+ * should be ignored. This should be used when you need to get every
+ * match regardless of whether it's valid.
+ *
+ * @property _owner The query instance that holds the list of unions...
+ * @property _constraints A list of (lists of OR constraints) that are ANDed
+ * together. For example [[FROM bob, FROM jim], [DATE last week]] would
+ * be requesting us to find all the messages from either bob or jim, and
+ * sent in the last week.
+ * @property _unions A list of other queries whose results are unioned with our
+ * own. There is no concept of nesting or sub-queries apart from this
+ * mechanism.
+ */
+function GlodaQueryClass(aOptions) {
+ this.options = (aOptions != null) ? aOptions : {};
+
+ // if we are an 'or' clause, who is our parent whom other 'or' clauses should
+ // spawn from...
+ this._owner = null;
+ // our personal chain of and-ing.
+ this._constraints = [];
+ // the other instances we union with
+ this._unions = [];
+
+ this._order = [];
+ this._limit = 0;
+}
+
+GlodaQueryClass.prototype = {
+ WILDCARD: {},
+
+ get constraintCount() {
+ return this._constraints.length;
+ },
+
+ or: function gloda_query_or() {
+ let owner = this._owner || this;
+ let orQuery = new this._queryClass();
+ orQuery._owner = owner;
+ owner._unions.push(orQuery);
+ return orQuery;
+ },
+
+ orderBy: function gloda_query_orderBy() {
+ for (let iArg = 0; iArg < arguments.length; iArg++) {
+ let arg = arguments[iArg];
+ this._order.push(arg);
+ }
+ return this;
+ },
+
+ limit: function gloda_query_limit(aLimit) {
+ this._limit = aLimit;
+ return this;
+ },
+
+ /**
+ * Return a collection asynchronously populated by this collection. You must
+ * provide a listener to receive notifications from the collection as it
+ * receives updates. The listener object should implement onItemsAdded,
+ * onItemsModified, and onItemsRemoved methods, all of which take a single
+ * argument which is the list of items which have been added, modified, or
+ * removed respectively.
+ *
+ * @param aListener The collection listener.
+ * @param [aData] The data attribute to set on the collection.
+ * @param [aArgs.becomeExplicit] Make the collection explicit so that the
+ * collection will only ever contain results found from the database
+ * query and the query will not be updated as new items are indexed that
+ * also match the query.
+ * @param [aArgs.becomeNull] Change the collection's query to a null query so
+ * that it will never receive any additional added/modified/removed events
+ * apart from the underlying database query. This is really only intended
+ * for gloda internal use but may be acceptable for non-gloda use. Please
+ * ask on mozilla.dev.apps.thunderbird first to make sure there isn't a
+ * better solution for your use-case. (Note: removals will still happen
+ * when things get fully deleted.)
+ */
+ getCollection: function gloda_query_getCollection(aListener, aData, aArgs) {
+ this.completed = false;
+ return this._nounDef.datastore.queryFromQuery(this, aListener, aData,
+ /* aExistingCollection */ null, /* aMasterCollection */ null,
+ aArgs);
+ },
+
+ /**
+ * Test whether the given first-class noun instance satisfies this query.
+ *
+ * @testpoint gloda.query.test
+ */
+ test: function gloda_query_test(aObj) {
+ // when changing this method, be sure that GlodaDatastore's queryFromQuery
+ // method likewise has any required changes made.
+ let unionQueries = [this].concat(this._unions);
+
+ for (let iUnion = 0; iUnion < unionQueries.length; iUnion++) {
+ let curQuery = unionQueries[iUnion];
+
+ // assume success until a specific (or) constraint proves us wrong
+ let querySatisfied = true;
+ for (let iConstraint = 0; iConstraint < curQuery._constraints.length;
+ iConstraint++) {
+ let constraint = curQuery._constraints[iConstraint];
+ let [constraintType, attrDef] = constraint;
+ let boundName = attrDef ? attrDef.boundName : "id";
+ if ((boundName in aObj) &&
+ aObj[boundName] === GlodaDatastore.IGNORE_FACET) {
+ querySatisfied = false;
+ break;
+ }
+
+ let constraintValues = constraint.slice(2);
+
+ if (constraintType === GlodaDatastore.kConstraintIdIn) {
+ if (constraintValues.indexOf(aObj.id) == -1) {
+ querySatisfied = false;
+ break;
+ }
+ }
+ // @testpoint gloda.query.test.kConstraintIn
+ else if ((constraintType === GlodaDatastore.kConstraintIn) ||
+ (constraintType === GlodaDatastore.kConstraintEquals)) {
+ let objectNounDef = attrDef.objectNounDef;
+
+ // if they provide an equals comparator, use that.
+ // (note: the next case has better optimization possibilities than
+ // this mechanism, but of course has higher initialization costs or
+ // code complexity costs...)
+ if (objectNounDef.equals) {
+ let testValues;
+ if (!(boundName in aObj))
+ testValues = [];
+ else if (attrDef.singular)
+ testValues = [aObj[boundName]];
+ else
+ testValues = aObj[boundName];
+
+ // If there are no constraints, then we are just testing for there
+ // being a value. Succeed (continue) in that case.
+ if (constraintValues.length == 0 && testValues.length &&
+ testValues[0] != null)
+ continue;
+
+ // If there are no test values and the empty set is significant,
+ // then check if any of the constraint values are null (our
+ // empty indicator.)
+ if (testValues.length == 0 && attrDef.emptySetIsSignificant) {
+ let foundEmptySetSignifier = false;
+ for (let constraintValue of constraintValues) {
+ if (constraintValue == null) {
+ foundEmptySetSignifier = true;
+ break;
+ }
+ }
+ if (foundEmptySetSignifier)
+ continue;
+ }
+
+ let foundMatch = false;
+ for (let testValue of testValues) {
+ for (let value of constraintValues) {
+ if (objectNounDef.equals(testValue, value)) {
+ foundMatch = true;
+ break;
+ }
+ }
+ if (foundMatch)
+ break;
+ }
+ if (!foundMatch) {
+ querySatisfied = false;
+ break;
+ }
+ }
+ // otherwise, we need to convert everyone to their param/value form
+ // in order to test for equality
+ else {
+ // let's just do the simple, obvious thing for now. which is
+ // what we did in the prior case but exploding values using
+ // toParamAndValue, and then comparing.
+ let testValues;
+ if (!(boundName in aObj))
+ testValues = [];
+ else if (attrDef.singular)
+ testValues = [aObj[boundName]];
+ else
+ testValues = aObj[boundName];
+
+ // If there are no constraints, then we are just testing for there
+ // being a value. Succeed (continue) in that case.
+ if (constraintValues.length == 0 && testValues.length &&
+ testValues[0] != null)
+ continue;
+ // If there are no test values and the empty set is significant,
+ // then check if any of the constraint values are null (our
+ // empty indicator.)
+ if (testValues.length == 0 && attrDef.emptySetIsSignificant) {
+ let foundEmptySetSignifier = false;
+ for (let constraintValue of constraintValues) {
+ if (constraintValue == null) {
+ foundEmptySetSignifier = true;
+ break;
+ }
+ }
+ if (foundEmptySetSignifier)
+ continue;
+ }
+
+ let foundMatch = false;
+ for (let testValue of testValues) {
+ let [aParam, aValue] = objectNounDef.toParamAndValue(testValue);
+ for (let value of constraintValues) {
+ // skip empty set check sentinel values
+ if (value == null && attrDef.emptySetIsSignificant)
+ continue;
+ let [bParam, bValue] = objectNounDef.toParamAndValue(value);
+ if (aParam == bParam && aValue == bValue) {
+ foundMatch = true;
+ break;
+ }
+ }
+ if (foundMatch)
+ break;
+ }
+ if (!foundMatch) {
+ querySatisfied = false;
+ break;
+ }
+ }
+ }
+ // @testpoint gloda.query.test.kConstraintRanges
+ else if (constraintType === GlodaDatastore.kConstraintRanges) {
+ let objectNounDef = attrDef.objectNounDef;
+
+ let testValues;
+ if (!(boundName in aObj))
+ testValues = [];
+ else if (attrDef.singular)
+ testValues = [aObj[boundName]];
+ else
+ testValues = aObj[boundName];
+
+ let foundMatch = false;
+ for (let testValue of testValues) {
+ let [tParam, tValue] = objectNounDef.toParamAndValue(testValue);
+ for (let rangeTuple of constraintValues) {
+ let [lowerRValue, upperRValue] = rangeTuple;
+ if (lowerRValue == null) {
+ let [upperParam, upperValue] =
+ objectNounDef.toParamAndValue(upperRValue);
+ if (tParam == upperParam && tValue <= upperValue) {
+ foundMatch = true;
+ break;
+ }
+ }
+ else if (upperRValue == null) {
+ let [lowerParam, lowerValue] =
+ objectNounDef.toParamAndValue(lowerRValue);
+ if (tParam == lowerParam && tValue >= lowerValue) {
+ foundMatch = true;
+ break;
+ }
+ }
+ else { // no one is null
+ let [upperParam, upperValue] =
+ objectNounDef.toParamAndValue(upperRValue);
+ let [lowerParam, lowerValue] =
+ objectNounDef.toParamAndValue(lowerRValue);
+ if ((tParam == lowerParam) && (tValue >= lowerValue) &&
+ (tParam == upperParam) && (tValue <= upperValue)) {
+ foundMatch = true;
+ break;
+ }
+ }
+ }
+ if (foundMatch)
+ break;
+ }
+ if (!foundMatch) {
+ querySatisfied = false;
+ break;
+ }
+ }
+ // @testpoint gloda.query.test.kConstraintStringLike
+ else if (constraintType === GlodaDatastore.kConstraintStringLike) {
+ let curIndex = 0;
+ let value = (boundName in aObj) ? aObj[boundName] : "";
+ // the attribute must be singular, we don't support arrays of strings.
+ for (let valuePart of constraintValues) {
+ if (typeof valuePart == "string") {
+ let index = value.indexOf(valuePart);
+ // if curIndex is null, we just need any match
+ // if it's not null, it must match the offset of our found match
+ if (curIndex === null) {
+ if (index == -1)
+ querySatisfied = false;
+ else
+ curIndex = index + valuePart.length;
+ }
+ else {
+ if (index != curIndex)
+ querySatisfied = false;
+ else
+ curIndex = index + valuePart.length;
+ }
+ if (!querySatisfied)
+ break;
+ }
+ else // wild!
+ curIndex = null;
+ }
+ // curIndex must be null or equal to the length of the string
+ if (querySatisfied && curIndex !== null && curIndex != value.length)
+ querySatisfied = false;
+ }
+ // @testpoint gloda.query.test.kConstraintFulltext
+ else if (constraintType === GlodaDatastore.kConstraintFulltext) {
+ // this is beyond our powers. Even if we have the fulltext content in
+ // memory, which we may not, the tokenization and such to perform
+ // the testing gets very complicated in the face of i18n, etc.
+ // so, let's fail if the item is not already in the collection, and
+ // let the testing continue if it is. (some other constraint may no
+ // longer apply...)
+ if (!(aObj.id in this.collection._idMap))
+ querySatisfied = false;
+ }
+
+ if (!querySatisfied)
+ break;
+ }
+
+ if (querySatisfied)
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Helper code for noun definitions of queryHelpers that want to build a
+ * traditional in/equals constraint. The goal is to let them build a range
+ * without having to know how we structure |_constraints|.
+ *
+ * @protected
+ */
+ _inConstraintHelper:
+ function gloda_query__discreteConstraintHelper(aAttrDef, aValues) {
+ let constraint =
+ [GlodaDatastore.kConstraintIn, aAttrDef].concat(aValues);
+ this._constraints.push(constraint);
+ return this;
+ },
+
+ /**
+ * Helper code for noun definitions of queryHelpers that want to build a
+ * range. The goal is to let them build a range without having to know how
+ * we structure |_constraints| or requiring them to mark themselves as
+ * continuous to get a "Range".
+ *
+ * @protected
+ */
+ _rangedConstraintHelper:
+ function gloda_query__rangedConstraintHelper(aAttrDef, aRanges) {
+ let constraint =
+ [GlodaDatastore.kConstraintRanges, aAttrDef].concat(aRanges);
+ this._constraints.push(constraint);
+ return this;
+ }
+};
+
+/**
+ * @class A query that never matches anything.
+ *
+ * Collections corresponding to this query are intentionally frozen in time and
+ * do not want to be notified of any updates. We need the collection to be
+ * registered with the collection manager so that the noun instances in the
+ * collection are always 'reachable' via the collection for as long as we might
+ * be handing out references to the instances. (The other way to avoid updates
+ * would be to not register the collection, but then items might not be
+ * reachable.)
+ * This is intended to be used in implementation details behind the gloda
+ * abstraction barrier. For example, the message indexer likes to be able
+ * to represent 'ghost' and deleted messages, but these should never be exposed
+ * to the user. For code simplicity, it wants to be able to use the query
+ * mechanism. But it doesn't want updates that are effectively
+ * nonsensical. For example, a ghost message that is reused by message
+ * indexing may already be present in a collection; when the collection manager
+ * receives an itemsAdded event, a GlodaExplicitQueryClass would result in
+ * an item added notification in that case, which would wildly not be desired.
+ */
+function GlodaNullQueryClass() {
+}
+
+GlodaNullQueryClass.prototype = {
+ /**
+ * No options; they are currently only needed for SQL query generation, which
+ * does not happen for null queries.
+ */
+ options: {},
+
+ /**
+ * Provide a duck-typing way of indicating to GlodaCollectionManager that our
+ * associated collection just doesn't want anything to change. Our test
+ * function is able to convey most of it, but special-casing has to happen
+ * somewhere, so it happens here.
+ */
+ frozen: true,
+
+ /**
+ * Since our query never matches anything, it doesn't make sense to let
+ * someone attempt to construct a boolean OR involving us.
+ *
+ * @returns null
+ */
+ or: function() {
+ return null;
+ },
+
+ /**
+ * Return nothing (null) because it does not make sense to create a collection
+ * based on a null query. This method is normally used (on a normal query)
+ * to return a collection populated by the constraints of the query. We
+ * match nothing, so we should return nothing. More importantly, you are
+ * currently doing something wrong if you try and do this, so null is
+ * appropriate. It may turn out that it makes sense for us to return an
+ * empty collection in the future for sentinel value purposes, but we'll
+ * cross that bridge when we come to it.
+ *
+ * @returns null
+ */
+ getCollection: function() {
+ return null;
+ },
+
+ /**
+ * Never matches anything.
+ *
+ * @param aObj The object someone wants us to test for relevance to our
+ * associated collection. But we don't care! Not a fig!
+ * @returns false
+ */
+ test: function gloda_query_null_test(aObj) {
+ return false;
+ }
+};
+
+/**
+ * @class A query that only 'tests' for already belonging to the collection.
+ *
+ * This type of collection is useful for when you (or rather your listener)
+ * are interested in hearing about modifications to your collection or removals
+ * from your collection because of deletion, but do not want to be notified
+ * about newly indexed items matching your normal query constraints.
+ *
+ * @param aCollection The collection this query belongs to. This needs to be
+ * passed-in here or the collection should set the attribute directly when
+ * the query is passed in to a collection's constructor.
+ */
+function GlodaExplicitQueryClass(aCollection) {
+ this.collection = aCollection;
+}
+
+GlodaExplicitQueryClass.prototype = {
+ /**
+ * No options; they are currently only needed for SQL query generation, which
+ * does not happen for explicit queries.
+ */
+ options: {},
+
+ /**
+ * Since our query is intended to only match the contents of our collection,
+ * it doesn't make sense to let someone attempt to construct a boolean OR
+ * involving us.
+ *
+ * @returns null
+ */
+ or: function() {
+ return null;
+ },
+
+ /**
+ * Return nothing (null) because it does not make sense to create a collection
+ * based on an explicit query. This method is normally used (on a normal
+ * query) to return a collection populated by the constraints of the query.
+ * In the case of an explicit query, we expect it will be associated with
+ * either a hand-created collection or the results of a normal query that is
+ * immediately converted into an explicit query. In all likelihood, calling
+ * this method on an instance of this type is an error, so it is helpful to
+ * return null because people will error hard.
+ *
+ * @returns null
+ */
+ getCollection: function() {
+ return null;
+ },
+
+ /**
+ * Matches only items that are already in the collection associated with this
+ * query (by id).
+ *
+ * @param aObj The object/item to test for already being in the associated
+ * collection.
+ * @returns true when the object is in the associated collection, otherwise
+ * false.
+ */
+ test: function gloda_query_explicit_test(aObj) {
+ return (aObj.id in this.collection._idMap);
+ }
+};
+
+/**
+ * @class A query that 'tests' true for everything. Intended for debugging purposes
+ * only.
+ */
+function GlodaWildcardQueryClass() {
+}
+
+GlodaWildcardQueryClass.prototype = {
+ /**
+ * No options; they are currently only needed for SQL query generation.
+ */
+ options: {},
+
+ // don't let people try and mess with us
+ or: function() { return null; },
+ // don't let people try and query on us (until we have a real use case for
+ // that...)
+ getCollection: function() { return null; },
+ /**
+ * Everybody wins!
+ */
+ test: function gloda_query_explicit_test(aObj) {
+ return true;
+ }
+};
+
+/**
+ * Factory method to effectively create per-noun subclasses of GlodaQueryClass,
+ * GlodaNullQueryClass, GlodaExplicitQueryClass, and GlodaWildcardQueryClass.
+ * For GlodaQueryClass this allows us to add per-noun helpers. For the others,
+ * this is merely a means of allowing us to attach the (per-noun) nounDef to
+ * the 'class'.
+ */
+function GlodaQueryClassFactory(aNounDef) {
+ let newQueryClass = function(aOptions) {
+ GlodaQueryClass.call(this, aOptions);
+ };
+ newQueryClass.prototype = new GlodaQueryClass();
+ newQueryClass.prototype._queryClass = newQueryClass;
+ newQueryClass.prototype._nounDef = aNounDef;
+
+ let newNullClass = function(aCollection) {
+ GlodaNullQueryClass.call(this);
+ this.collection = aCollection;
+ };
+ newNullClass.prototype = new GlodaNullQueryClass();
+ newNullClass.prototype._queryClass = newNullClass;
+ newNullClass.prototype._nounDef = aNounDef;
+
+ let newExplicitClass = function(aCollection) {
+ GlodaExplicitQueryClass.call(this);
+ this.collection = aCollection;
+ };
+ newExplicitClass.prototype = new GlodaExplicitQueryClass();
+ newExplicitClass.prototype._queryClass = newExplicitClass;
+ newExplicitClass.prototype._nounDef = aNounDef;
+
+ let newWildcardClass = function(aCollection) {
+ GlodaWildcardQueryClass.call(this);
+ this.collection = aCollection;
+ };
+ newWildcardClass.prototype = new GlodaWildcardQueryClass();
+ newWildcardClass.prototype._queryClass = newWildcardClass;
+ newWildcardClass.prototype._nounDef = aNounDef;
+
+ return [newQueryClass, newNullClass, newExplicitClass, newWildcardClass];
+}
diff --git a/mailnews/db/gloda/modules/suffixtree.js b/mailnews/db/gloda/modules/suffixtree.js
new file mode 100644
index 000000000..009ad5f9d
--- /dev/null
+++ b/mailnews/db/gloda/modules/suffixtree.js
@@ -0,0 +1,340 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["SuffixTree", "MultiSuffixTree"];
+
+/**
+ * Given a list of strings and a corresponding map of items that those strings
+ * correspond to, build a suffix tree.
+ */
+function MultiSuffixTree(aStrings, aItems) {
+ if (aStrings.length != aItems.length)
+ throw new Error("Array lengths need to be the same.");
+
+ let s = '';
+ let offsetsToItems = [];
+ let lastLength = 0;
+ for (let i = 0; i < aStrings.length; i++) {
+ s += aStrings[i];
+ offsetsToItems.push(lastLength, s.length, aItems[i]);
+ lastLength = s.length;
+ }
+
+ this._construct(s);
+ this._offsetsToItems = offsetsToItems;
+ this._numItems = aItems.length;
+}
+
+/**
+ * @constructor
+ */
+function State(aStartIndex, aEndIndex, aSuffix) {
+ this.start = aStartIndex;
+ this.end = aEndIndex;
+ this.suffix = aSuffix;
+}
+
+var dump;
+if (dump === undefined) {
+ dump = function(a) {
+ print(a.slice(0, -1));
+ };
+}
+
+/**
+ * Since objects are basically hash-tables anyways, we simply create an
+ * attribute whose name is the first letter of the edge string. (So, the
+ * edge string can conceptually be a multi-letter string, but since we would
+ * split it were there any ambiguity, it's okay to just use the single letter.)
+ * This avoids having to update the attribute name or worry about tripping our
+ * implementation up.
+ */
+State.prototype = {
+ get isExplicit() {
+ // our end is not inclusive...
+ return (this.end <= this.start);
+ },
+ get isImplicit() {
+ // our end is not inclusive...
+ return (this.end > this.start);
+ },
+
+ get length() {
+ return this.end - this.start;
+ },
+
+ toString: function State_toString() {
+ return "[Start: " + this.start + " End: " + this.end +
+ (this.suffix ? " non-null suffix]" : " null suffix]");
+ }
+};
+
+/**
+ * Suffix tree implemented using Ukkonen's algorithm.
+ * @constructor
+ */
+function SuffixTree(aStr) {
+ this._construct(aStr);
+}
+
+/**
+ * States are
+ */
+SuffixTree.prototype = {
+ /**
+ * Find all items matching the provided substring.
+ */
+ findMatches: function findMatches(aSubstring) {
+ let results = [];
+ let state = this._root;
+ let index=0;
+ let end = aSubstring.length;
+ while(index < end) {
+ state = state[aSubstring[index]];
+ // bail if there was no edge
+ if (state === undefined)
+ return results;
+ // bail if the portion of the edge we traversed is not equal to that
+ // portion of our pattern
+ let actualTraverseLength = Math.min(state.length,
+ end - index);
+ if (this._str.substring(state.start,
+ state.start + actualTraverseLength) !=
+ aSubstring.substring(index, index + actualTraverseLength))
+ return results;
+ index += state.length;
+ }
+
+ // state should now be the node which itself and all its children match...
+ // The delta is to adjust us to the offset of the last letter of our match;
+ // the edge we traversed to get here may have found us traversing more
+ // than we wanted.
+ // index - end captures the over-shoot of the edge traversal,
+ // index - end + 1 captures the fact that we want to find the last letter
+ // that matched, not just the first letter beyond it
+ // However, if this state is a leaf node (end == 'infinity'), then 'end'
+ // isn't describing an edge at all and we want to avoid accounting for it.
+ let delta;
+ /*
+ if (state.end != this._infinity)
+ //delta = index - end + 1;
+ delta = end - (index - state.length);
+ else */
+ delta = index - state.length - end + 1;
+
+ this._resultGather(state, results, {}, end, delta, true);
+ return results;
+ },
+
+ _resultGather: function resultGather(aState, aResults, aPresence,
+ aPatLength, aDelta, alreadyAdjusted) {
+ // find the item that this state originated from based on the state's
+ // start character. offsetToItem holds [string start index, string end
+ // index (exclusive), item reference]. So we want to binary search to
+ // find the string whose start/end index contains the state's start index.
+ let low = 0;
+ let high = this._numItems-1;
+ let mid, stringStart, stringEnd;
+
+ let patternLast = aState.start - aDelta;
+ while (low <= high) {
+ mid = low + Math.floor((high - low) / 2); // excessive, especially with js nums
+ stringStart = this._offsetsToItems[mid*3];
+ let startDelta = stringStart - patternLast;
+ stringEnd = this._offsetsToItems[mid*3+1];
+ let endDelta = stringEnd - patternLast;
+ if (startDelta > 0)
+ high = mid - 1;
+ else if (endDelta <= 0)
+ low = mid + 1;
+ else {
+ break;
+ }
+ }
+
+ // - The match occurred completely inside a source string. Success.
+ // - The match spans more than one source strings, and is therefore not
+ // a match.
+
+ // at this point, we have located the origin string that corresponds to the
+ // start index of this state.
+ // - The match terminated with the end of the preceding string, and does
+ // not match us at all. We, and potentially our children, are merely
+ // serving as a unique terminal.
+ // - The
+
+ let patternFirst = patternLast - (aPatLength - 1);
+
+ if (patternFirst >= stringStart) {
+ if (!(stringStart in aPresence)) {
+ aPresence[stringStart] = true;
+ aResults.push(this._offsetsToItems[mid*3+2]);
+ }
+ }
+
+ // bail if we had it coming OR
+ // if the result terminates at/part-way through this state, meaning any
+ // of its children are not going to be actual results, just hangers
+ // on.
+/*
+ if (bail || (end <= aState.end)) {
+dump(" bailing! (bail was: " + bail + ")\n");
+ return;
+ }
+*/
+ // process our children...
+ for (let key in aState) {
+ // edges have attributes of length 1...
+ if (key.length == 1) {
+ let statePrime = aState[key];
+ this._resultGather(statePrime, aResults, aPresence, aPatLength,
+ aDelta + aState.length, //(alreadyAdjusted ? 0 : aState.length),
+ false);
+ }
+ }
+ },
+
+ /**
+ * Given a reference 'pair' of a state and a string (may be 'empty'=explicit,
+ * which means no work to do and we return immediately) follow that state
+ * (and then the successive states)'s transitions until we run out of
+ * transitions. This happens either when we find an explicit state, or
+ * find ourselves partially along an edge (conceptually speaking). In
+ * the partial case, we return the state prior to the edge traversal.
+ * (The information about the 'edge' is contained on its target State;
+ * we can do this because a state is only referenced by one other state.)
+ */
+ _canonize: function canonize(aState, aStart, aEnd) {
+ if (aEnd <= aStart) {
+ return [aState, aStart];
+ }
+
+ let statePrime;
+ // we treat an aState of null as 'bottom', which has transitions for every
+ // letter in the alphabet to 'root'. rather than create all those
+ // transitions, we special-case here.
+ if (aState === null)
+ statePrime = this._root;
+ else
+ statePrime = aState[this._str[aStart]];
+ while (statePrime.length <= aEnd - aStart) { // (no 1 adjustment required)
+ aStart += statePrime.length;
+ aState = statePrime;
+ if (aStart < aEnd) {
+ statePrime = aState[this._str[aStart]];
+ }
+ }
+ return [aState, aStart];
+ },
+
+ /**
+ * Given a reference 'pair' whose state may or may not be explicit (and for
+ * which we will perform the required splitting to make it explicit), test
+ * whether it already possesses a transition corresponding to the provided
+ * character.
+ * @return A list of: whether we had to make it explicit, the (potentially)
+ * new explicit state.
+ */
+ _testAndSplit: function testAndSplit(aState, aStart, aEnd, aChar) {
+ if (aStart < aEnd) { // it's not explicit
+ let statePrime = aState[this._str[aStart]];
+ let length = aEnd - aStart;
+ if (aChar == this._str[statePrime.start + length]) {
+ return [true, aState];
+ }
+ else {
+ // do splitting... aState -> rState -> statePrime
+ let rState = new State(statePrime.start, statePrime.start + length);
+ aState[this._str[statePrime.start]] = rState;
+ statePrime.start += length;
+ rState[this._str[statePrime.start]] = statePrime;
+ return [false, rState];
+ }
+ }
+ else { // it's already explicit
+ if (aState === null) { // bottom case... shouldn't happen, but hey.
+ return [true, aState];
+ }
+ return [(aChar in aState), aState];
+ }
+
+ },
+
+ _update: function update(aState, aStart, aIndex) {
+ let oldR = this._root;
+ let textAtIndex = this._str[aIndex]; // T sub i (0-based corrected...)
+ // because of the way we store the 'end' value as a one-past form, we do
+ // not need to subtract 1 off of aIndex.
+ let [endPoint, rState] = this._testAndSplit(aState, aStart, aIndex, //no -1
+ textAtIndex);
+ while (!endPoint) {
+ let rPrime = new State(aIndex, this._infinity);
+ rState[textAtIndex] = rPrime;
+ if (oldR !== this._root)
+ oldR.suffix = rState;
+ oldR = rState;
+ [aState, aStart] = this._canonize(aState.suffix, aStart, aIndex); // no -1
+ [endPoint, rState] = this._testAndSplit(aState, aStart, aIndex, // no -1
+ textAtIndex);
+ }
+ if (oldR !== this._root)
+ oldR.suffix = aState;
+
+ return [aState, aStart];
+ },
+
+ _construct: function construct(aStr) {
+ this._str = aStr;
+ // just needs to be longer than the string.
+ this._infinity = aStr.length + 1;
+
+ //this._bottom = new State(0, -1, null);
+ this._root = new State(-1, 0, null); // null === bottom
+ let state = this._root;
+ let start = 0;
+
+ for (let i = 0; i < aStr.length; i++) {
+ [state, start] = this._update(state, start, i); // treat as flowing -1...
+ [state, start] = this._canonize(state, start, i+1); // 1-length string
+ }
+ },
+
+ dump: function SuffixTree_show(aState, aIndent, aKey) {
+ if (aState === undefined)
+ aState = this._root;
+ if (aIndent === undefined) {
+ aIndent = "";
+ aKey = ".";
+ }
+
+ if (aState.isImplicit) {
+ let snip;
+ if (aState.length > 10)
+ snip = this._str.slice(aState.start,
+ Math.min(aState.start+10, this._str.length)) + "...";
+ else
+ snip = this._str.slice(aState.start,
+ Math.min(aState.end, this._str.length));
+ dump(aIndent + aKey + ":" + snip + "(" +
+ aState.start + ":" + aState.end + ")\n");
+ }
+ else
+ dump(aIndent + aKey + ": (explicit:" + aState.start + ":" + aState.end +")\n");
+ let nextIndent = aIndent + " ";
+ let keys = Object.keys(aState).filter(c => c.length == 1);
+ for (let key of keys) {
+ this.dump(aState[key], nextIndent, key);
+ }
+ }
+};
+MultiSuffixTree.prototype = SuffixTree.prototype;
+
+function examplar() {
+ let names = ["AndrewSmith", "AndrewJones", "MarkSmith", "BryanClark",
+ "MarthaJones", "DavidAscher", "DanMosedale", "DavidBienvenu",
+ "JanetDavis", "JosephBryant"];
+ let b = new MultiSuffixTree(names, names);
+ b.dump();
+ dump(b.findMatches("rya") + "\n");
+}
diff --git a/mailnews/db/gloda/modules/utils.js b/mailnews/db/gloda/modules/utils.js
new file mode 100644
index 000000000..2a095c427
--- /dev/null
+++ b/mailnews/db/gloda/modules/utils.js
@@ -0,0 +1,155 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ['GlodaUtils'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/mailServices.js");
+
+/**
+ * @namespace A holding place for logic that is not gloda-specific and should
+ * reside elsewhere.
+ */
+var GlodaUtils = {
+
+ /**
+ * This Regexp is super-complicated and used at least in two different parts of
+ * the code, so let's expose it from one single location.
+ */
+ PART_RE: new RegExp("^[^?]+\\?(?:/;section=\\d+\\?)?(?:[^&]+&)*part=([^&]+)(?:&[^&]+)*$"),
+
+ deMime: function gloda_utils_deMime(aString) {
+ return MailServices.mimeConverter.decodeMimeHeader(aString, null, false, true);
+ },
+
+ _headerParser: MailServices.headerParser,
+
+ /**
+ * Parses an RFC 2822 list of e-mail addresses and returns an object with
+ * 4 attributes, as described below. We will use the example of the user
+ * passing an argument of '"Bob Smith" <bob@example.com>'.
+ *
+ * This method (by way of nsIMsgHeaderParser) takes care of decoding mime
+ * headers, but is not aware of folder-level character set overrides.
+ *
+ * count: the number of addresses parsed. (ex: 1)
+ * addresses: a list of e-mail addresses (ex: ["bob@example.com"])
+ * names: a list of names (ex: ["Bob Smith"])
+ * fullAddresses: aka the list of name and e-mail together (ex: ['"Bob Smith"
+ * <bob@example.com>']).
+ *
+ * This method is a convenience wrapper around nsIMsgHeaderParser.
+ */
+ parseMailAddresses: function gloda_utils_parseMailAddresses(aMailAddresses) {
+ let addresses = {}, names = {}, fullAddresses = {};
+ this._headerParser.parseHeadersWithArray(aMailAddresses, addresses,
+ names, fullAddresses);
+ return {names: names.value, addresses: addresses.value,
+ fullAddresses: fullAddresses.value,
+ count: names.value.length};
+ },
+
+ /**
+ * MD5 hash a string and return the hex-string result. Impl from nsICryptoHash
+ * docs.
+ */
+ md5HashString: function gloda_utils_md5hash(aString) {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ let trash = {};
+ converter.charset = "UTF-8";
+ let data = converter.convertToByteArray(aString, trash);
+
+ let hasher = Cc['@mozilla.org/security/hash;1'].
+ createInstance(Ci.nsICryptoHash);
+ hasher.init(Ci.nsICryptoHash.MD5);
+ hasher.update(data, data.length);
+ let hash = hasher.finish(false);
+
+ // return the two-digit hexadecimal code for a byte
+ function toHexString(charCode) {
+ return ("0" + charCode.toString(16)).slice(-2);
+ }
+
+ // convert the binary hash data to a hex string.
+ let hex = Object.keys(hash).map(i => toHexString(hash.charCodeAt(i)));
+ return hex.join("");
+ },
+
+ getCardForEmail: function gloda_utils_getCardForEmail(aAddress) {
+ // search through all of our local address books looking for a match.
+ let enumerator = MailServices.ab.directories;
+ let cardForEmailAddress;
+ let addrbook;
+ while (!cardForEmailAddress && enumerator.hasMoreElements())
+ {
+ addrbook = enumerator.getNext().QueryInterface(Ci.nsIAbDirectory);
+ try
+ {
+ cardForEmailAddress = addrbook.cardForEmailAddress(aAddress);
+ if (cardForEmailAddress)
+ return cardForEmailAddress;
+ } catch (ex) {}
+ }
+
+ return null;
+ },
+
+ _FORCE_GC_AFTER_NUM_HEADERS: 4096,
+ _headersSeen: 0,
+ /**
+ * As |forceGarbageCollection| says, once XPConnect sees a header, it likes
+ * to hold onto that reference. This method is used to track the number of
+ * headers we have seen and force a GC when we have to.
+ *
+ * Ideally the indexer's idle-biased GC mechanism would take care of all the
+ * GC; we are just a failsafe to make sure that our memory usage is bounded
+ * based on the number of headers we have seen rather than just time.
+ * Since holding onto headers can keep databases around too, this also
+ * helps avoid keeping file handles open, etc.
+ *
+ * |forceGarbageCollection| will zero our tracking variable when a GC happens
+ * so we are informed by the indexer's GC triggering.
+ *
+ * And of course, we don't want to trigger collections willy nilly because
+ * they have a cost even if there is no garbage.
+ *
+ * @param aNumHeadersSeen The number of headers code has seen. A granularity
+ * of hundreds of messages should be fine.
+ */
+ considerHeaderBasedGC: function(aNumHeadersSeen) {
+ this._headersSeen += aNumHeadersSeen;
+ if (this._headersSeen >= this._FORCE_GC_AFTER_NUM_HEADERS)
+ this.forceGarbageCollection();
+ },
+
+ /**
+ * Force a garbage-collection sweep. Gloda has to force garbage collection
+ * periodically because XPConnect's XPCJSRuntime::DeferredRelease mechanism
+ * can end up holding onto a ridiculously high number of XPConnect objects in
+ * between normal garbage collections. This has mainly posed a problem
+ * because nsAutolock is a jerk in DEBUG builds in 1.9.1, but in theory this
+ * also helps us even out our memory usage.
+ * We also are starting to do this more to try and keep the garbage collection
+ * durations acceptable. We intentionally avoid triggering the cycle
+ * collector in those cases, as we do presume a non-trivial fixed cost for
+ * cycle collection. (And really all we want is XPConnect to not be a jerk.)
+ * This method exists mainly to centralize our GC activities and because if
+ * we do start involving the cycle collector, that is a non-trivial block of
+ * code to copy-and-paste all over the place (at least in a module).
+ *
+ * @param aCycleCollecting Do we need the cycle collector to run? Currently
+ * unused / unimplemented, but we would use
+ * nsIDOMWindowUtils.garbageCollect() to do so.
+ */
+ forceGarbageCollection:
+ function gloda_utils_garbageCollection(aCycleCollecting) {
+ Cu.forceGC();
+ this._headersSeen = 0;
+ }
+};
diff --git a/mailnews/db/gloda/moz.build b/mailnews/db/gloda/moz.build
new file mode 100644
index 000000000..7cbae7782
--- /dev/null
+++ b/mailnews/db/gloda/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'modules',
+ 'components',
+]
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/mailnews/db/moz.build b/mailnews/db/moz.build
new file mode 100644
index 000000000..27ab5338d
--- /dev/null
+++ b/mailnews/db/moz.build
@@ -0,0 +1,10 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'msgdb',
+ 'gloda',
+]
+
diff --git a/mailnews/db/msgdb/moz.build b/mailnews/db/msgdb/moz.build
new file mode 100644
index 000000000..27dcb8746
--- /dev/null
+++ b/mailnews/db/msgdb/moz.build
@@ -0,0 +1,9 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'public',
+ 'src',
+]
diff --git a/mailnews/db/msgdb/public/moz.build b/mailnews/db/msgdb/public/moz.build
new file mode 100644
index 000000000..9bf74e1d6
--- /dev/null
+++ b/mailnews/db/msgdb/public/moz.build
@@ -0,0 +1,27 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIDBChangeAnnouncer.idl',
+ 'nsIDBChangeListener.idl',
+ 'nsIDBFolderInfo.idl',
+ 'nsIMsgDatabase.idl',
+ 'nsIMsgOfflineImapOperation.idl',
+ 'nsINewsDatabase.idl',
+]
+
+XPIDL_MODULE = 'msgdb'
+
+EXPORTS += [
+ 'nsDBFolderInfo.h',
+ 'nsImapMailDatabase.h',
+ 'nsMailDatabase.h',
+ 'nsMsgDatabase.h',
+ 'nsMsgDBCID.h',
+ 'nsMsgHdr.h',
+ 'nsMsgThread.h',
+ 'nsNewsDatabase.h',
+]
+
diff --git a/mailnews/db/msgdb/public/nsDBFolderInfo.h b/mailnews/db/msgdb/public/nsDBFolderInfo.h
new file mode 100644
index 000000000..b9747feb9
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsDBFolderInfo.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This class encapsulates the global information about a folder stored in the
+ summary file.
+*/
+#ifndef _nsDBFolderInfo_H
+#define _nsDBFolderInfo_H
+
+#include "mozilla/MemoryReporting.h"
+#include "nsStringGlue.h"
+#include "MailNewsTypes.h"
+#include "mdb.h"
+#include "nsTArray.h"
+#include "nsIDBFolderInfo.h"
+#include <time.h>
+
+class nsMsgDatabase;
+
+// again, this could inherit from nsISupports, but I don't see the need as of yet.
+// I'm not sure it needs to be ref-counted (but I think it does).
+
+// I think these getters and setters really need to go through mdb and not rely on the object
+// caching the values. If this somehow turns out to be prohibitively expensive, we can invent
+// some sort of dirty mechanism, but I think it turns out that these values will be cached by
+// the MSG_FolderInfo's anyway.
+class nsDBFolderInfo : public nsIDBFolderInfo
+{
+public:
+ friend class nsMsgDatabase;
+
+ explicit nsDBFolderInfo(nsMsgDatabase *mdb);
+
+ NS_DECL_ISUPPORTS
+ // interface methods.
+ NS_DECL_NSIDBFOLDERINFO
+ // create the appropriate table and row in a new db.
+ nsresult AddToNewMDB();
+ // accessor methods.
+
+ bool TestFlag(int32_t flags);
+ int16_t GetIMAPHierarchySeparator() ;
+ void SetIMAPHierarchySeparator(int16_t hierarchyDelimiter) ;
+ void ChangeImapTotalPendingMessages(int32_t delta);
+ void ChangeImapUnreadPendingMessages(int32_t delta) ;
+
+ nsresult InitFromExistingDB();
+ // get and set arbitrary property, aka row cell value.
+ nsresult SetPropertyWithToken(mdb_token aProperty, const nsAString &propertyStr);
+ nsresult SetUint32PropertyWithToken(mdb_token aProperty, uint32_t propertyValue);
+ nsresult SetInt64PropertyWithToken(mdb_token aProperty, int64_t propertyValue);
+ nsresult SetInt32PropertyWithToken(mdb_token aProperty, int32_t propertyValue);
+ nsresult GetPropertyWithToken(mdb_token aProperty, nsAString &propertyValue);
+ nsresult GetUint32PropertyWithToken(mdb_token aProperty, uint32_t &propertyValue, uint32_t defaultValue = 0);
+ nsresult GetInt32PropertyWithToken(mdb_token aProperty, int32_t &propertyValue, int32_t defaultValue = 0);
+ nsresult GetInt64PropertyWithToken(mdb_token aProperty,
+ int64_t &propertyValue, int64_t defaultValue = 0);
+
+ nsTArray<nsMsgKey> m_lateredKeys; // list of latered messages
+
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return m_lateredKeys.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+protected:
+ virtual ~nsDBFolderInfo();
+
+ // initialize from appropriate table and row in existing db.
+ nsresult InitMDBInfo();
+ nsresult LoadMemberVariables();
+
+ nsresult AdjustHighWater(nsMsgKey highWater, bool force);
+
+ void ReleaseExternalReferences(); // let go of any references to other objects.
+
+ int64_t m_folderSize;
+ int64_t m_expungedBytes; // sum of size of deleted messages in folder
+ uint32_t m_folderDate;
+ nsMsgKey m_highWaterMessageKey; // largest news article number or imap uid whose header we've seen
+
+ // m_numUnreadMessages and m_numMessages can never be negative. 0 means 'no msgs'.
+ int32_t m_numUnreadMessages;
+ int32_t m_numMessages; // includes expunged and ignored messages
+
+ int32_t m_flags; // folder specific flags. This holds things like re-use thread pane,
+ // configured for off-line use, use default retrieval, purge article/header options
+
+ uint16_t m_version; // for upgrading...
+ int16_t m_IMAPHierarchySeparator; // imap path separator
+
+ // mail only (for now)
+
+ // IMAP only
+ int32_t m_ImapUidValidity;
+ int32_t m_totalPendingMessages;
+ int32_t m_unreadPendingMessages;
+
+ // news only (for now)
+ nsMsgKey m_expiredMark; // Highest invalid article number in group - for expiring
+ // the db folder info will have to know what db and row it belongs to, since it is really
+ // just a wrapper around the singleton folder info row in the mdb.
+ nsMsgDatabase *m_mdb;
+ nsIMdbTable *m_mdbTable; // singleton table in db
+ nsIMdbRow *m_mdbRow; // singleton row in table;
+
+ nsCString m_charSet;
+ bool m_charSetOverride;
+ bool m_mdbTokensInitialized;
+
+ mdb_token m_rowScopeToken;
+ mdb_token m_tableKindToken;
+ // tokens for the pre-set columns - we cache these for speed, which may be silly
+ mdb_token m_mailboxNameColumnToken;
+ mdb_token m_numMessagesColumnToken;
+ mdb_token m_numUnreadMessagesColumnToken;
+ mdb_token m_flagsColumnToken;
+ mdb_token m_folderSizeColumnToken;
+ mdb_token m_expungedBytesColumnToken;
+ mdb_token m_folderDateColumnToken;
+ mdb_token m_highWaterMessageKeyColumnToken;
+
+ mdb_token m_imapUidValidityColumnToken;
+ mdb_token m_totalPendingMessagesColumnToken;
+ mdb_token m_unreadPendingMessagesColumnToken;
+ mdb_token m_expiredMarkColumnToken;
+ mdb_token m_versionColumnToken;
+};
+
+#endif
diff --git a/mailnews/db/msgdb/public/nsIDBChangeAnnouncer.idl b/mailnews/db/msgdb/public/nsIDBChangeAnnouncer.idl
new file mode 100644
index 000000000..28551ea60
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsIDBChangeAnnouncer.idl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIDBChangeListener;
+interface nsIMsgDBHdr;
+
+[scriptable, uuid(22baf00b-939d-42c3-ac51-21d99dfa1f05)]
+
+interface nsIDBChangeAnnouncer : nsISupports {
+ /* these 2 calls return NS_OK on success, NS_COMFALSE on failure */
+ void AddListener(in nsIDBChangeListener listener);
+ void RemoveListener(in nsIDBChangeListener listener);
+
+ void NotifyHdrChangeAll(in nsIMsgDBHdr aHdrChanged, in unsigned long aOldFlags, in unsigned long aNewFlags,
+ in nsIDBChangeListener instigator);
+ void NotifyHdrAddedAll(in nsIMsgDBHdr aHdrAdded, in nsMsgKey parentKey, in long flags,
+ in nsIDBChangeListener instigator);
+ void NotifyHdrDeletedAll(in nsIMsgDBHdr aHdrDeleted, in nsMsgKey parentKey, in long flags,
+ in nsIDBChangeListener instigator);
+ void NotifyParentChangedAll(in nsMsgKey keyReparented, in nsMsgKey oldParent, in nsMsgKey newParent, in nsIDBChangeListener instigator);
+
+ void NotifyReadChanged(in nsIDBChangeListener instigator);
+
+ void NotifyJunkScoreChanged(in nsIDBChangeListener aInstigator);
+
+ void NotifyAnnouncerGoingAway();
+};
+
diff --git a/mailnews/db/msgdb/public/nsIDBChangeListener.idl b/mailnews/db/msgdb/public/nsIDBChangeListener.idl
new file mode 100644
index 000000000..3acda8ad8
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsIDBChangeListener.idl
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIDBChangeAnnouncer;
+interface nsIMsgDBHdr;
+interface nsIMsgDatabase;
+
+/**
+ * These callbacks are provided to allow listeners to the message database
+ * to update their status when changes occur.
+ */
+[scriptable, uuid(21c56d34-71b9-42bb-9606-331a6a5f8210)]
+
+interface nsIDBChangeListener : nsISupports {
+ /**
+ * Callback when message flags are changed.
+ *
+ * @param aHdrChanged The changed header.
+ * @param aOldFlags Message flags prior to change.
+ * @param aNewFlags Message flags after change.
+ * @param aInstigator Object that initiated the change.
+ */
+ void onHdrFlagsChanged(in nsIMsgDBHdr aHdrChanged, in unsigned long aOldFlags,
+ in unsigned long aNewFlags, in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback when message is marked as deleted.
+ *
+ * @param aHdrChanged The message header that is going to be deleted.
+ * @param aParentKey Key of parent.
+ * @param aFlags Flags that message has before delete.
+ * @param aInstigator Object that initiated the change. Can be null.
+ */
+ void onHdrDeleted(in nsIMsgDBHdr aHdrChanged, in nsMsgKey aParentKey, in long aFlags,
+ in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback when message is added.
+ *
+ * @param aHdrChanged The message header that is added.
+ * @param aParentKey Parent key of message.
+ * @param aFlags Flags that new message will have.
+ * @param aInstigator Object that initiated the change. Can be null.
+ */
+ void onHdrAdded(in nsIMsgDBHdr aHdrChanged, in nsMsgKey aParentKey, in long aFlags,
+ in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback when message parrent is changed. Parent is changed when message is deleted or moved.
+ *
+ * @param aKeyChanged The message key that parent key was changed.
+ * @param oldParent Old parent key.
+ * @param newParent New parent key.
+ * @param aInstigator Object that initiated the change. Can be null.
+ */
+ void onParentChanged(in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in nsMsgKey newParent,
+ in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback when announcer is going away. This is good place to release strong pointers to announcer.
+ *
+ * @param instigator Object that initiated the change. Can be null.
+ */
+ void onAnnouncerGoingAway(in nsIDBChangeAnnouncer instigator);
+
+ /**
+ * Callback when read flag is changed.
+ *
+ * @param aInstigator Object that initiated the change. Can be null.
+ */
+ void onReadChanged(in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback used in case when "junkscore" property is changed.
+ *
+ * @param aInstigator Object that initiated the change. Can be null.
+ */
+ void onJunkScoreChanged(in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback used in the general case where any field may have changed.
+ * OnHdrPropertyChanged is called twice per change. On the first call, aPreChange
+ * is true, and aStatus is undefined. OnHdrPropertyChanged saves any required status in aStatus
+ * (such as a filter match). The calling function stores the value of aStatus, changes the
+ * header aHdrToChange, then calls OnHdrPropertyChanged again with aPreChange false. On this
+ * second call, the stored value of aStatus is provided, so that any changes may be noted.
+ *
+ * @param aHdrToChange the message header that is changing.
+ * @param aPreChange true on first call before change, false on second call after change
+ * @param aStatus storage location provided by calling routine for status
+ * @param aInstigator object that initiated the change
+ */
+ void onHdrPropertyChanged(in nsIMsgDBHdr aHdrToChange, in boolean aPreChange, inout uint32_t aStatus,
+ in nsIDBChangeListener aInstigator);
+
+ /**
+ * Generic notification for extensibility. Common events should be documented
+ * here so we have a hope of keeping the documentation up to date.
+ * Current events are:
+ * "DBOpened" - When a pending listener becomes real. This can happen when
+ * the existing db is force closed and a new one opened. Only
+ * registered pending listeners are notified.
+ *
+ * @param aDB the db for this event.
+ * @param aEvent type of event.
+ *
+ */
+ void onEvent(in nsIMsgDatabase aDB, in string aEvent);
+};
+
diff --git a/mailnews/db/msgdb/public/nsIDBFolderInfo.idl b/mailnews/db/msgdb/public/nsIDBFolderInfo.idl
new file mode 100644
index 000000000..59b294d3b
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsIDBFolderInfo.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+[scriptable, uuid(a72dab4b-b3bd-471e-9a38-1b242b385459)]
+interface nsIDBFolderInfo : nsISupports {
+ attribute long flags;
+
+ /**
+ * Or's aFlags into flags.
+ *
+ * @param - the flags(s) to set
+ *
+ * @return - the resulting flags.
+ */
+ long orFlags(in long aFlags);
+ /**
+ * And's aFlags with flags, set flags to the result
+ *
+ * @param the flags(s) to AND
+ *
+ * @return the resulting flags.
+ */
+ long andFlags(in long aFlags);
+
+ /**
+ * Allows us to keep track of the highwater mark
+ *
+ * @param aNewKey If larger than the current highwater
+ * mark, sets the highwater mark to aNewKey.
+ */
+ void onKeyAdded(in nsMsgKey aNewKey);
+
+ attribute nsMsgKey highWater;
+ attribute nsMsgKey expiredMark;
+ attribute long long folderSize;
+ attribute unsigned long folderDate;
+ void changeNumUnreadMessages(in long aDelta);
+ void changeNumMessages(in long aDelta);
+
+ // numUnreadMessages and numMessages will never return negative numbers. 0 means 'no msgs'.
+ attribute long numUnreadMessages;
+ attribute long numMessages;
+
+ attribute long long expungedBytes;
+ attribute long imapUidValidity;
+ attribute unsigned long version;
+ attribute long imapTotalPendingMessages;
+ attribute long imapUnreadPendingMessages;
+
+ attribute nsMsgViewTypeValue viewType;
+ attribute nsMsgViewFlagsTypeValue viewFlags;
+ attribute nsMsgViewSortTypeValue sortType;
+ attribute nsMsgViewSortOrderValue sortOrder;
+
+ void changeExpungedBytes(in long aDelta);
+
+ /**
+ * Gets a string property from the folder.
+ *
+ * @param propertyName The name of the property for the value to retrieve.
+ */
+ ACString getCharProperty(in string propertyName);
+
+ /**
+ * Sets a string property from the folder.
+ *
+ * @param propertyName The name of the property for which to set a value
+ * @param propertyValue The new value of the property.
+ */
+ void setCharProperty(in string aPropertyName, in ACString aPropertyValue);
+ void setUint32Property(in string propertyName, in unsigned long propertyValue);
+ void setInt64Property(in string propertyName, in long long propertyValue);
+ unsigned long getUint32Property(in string propertyName, in unsigned long defaultValue);
+ long long getInt64Property(in string propertyName, in long long defaultValue);
+ boolean getBooleanProperty(in string propertyName, in boolean defaultValue);
+ void setBooleanProperty(in string propertyName, in boolean aPropertyValue);
+ nsIDBFolderInfo GetTransferInfo();
+ void initFromTransferInfo(in nsIDBFolderInfo transferInfo);
+
+ /**
+ * Gets/Sets the current character set for the folder. If there is no
+ * specific character set for the folder, it will return an empty string.
+ */
+ attribute ACString characterSet;
+
+ /**
+ * Returns the effective character set on the folder. If there is no specific
+ * set defined for the folder, it will return the default character set.
+ */
+ readonly attribute ACString effectiveCharacterSet;
+
+ attribute boolean characterSetOverride;
+
+ attribute AString locale;
+ attribute AString mailboxName;
+
+
+ AString getProperty(in string propertyName);
+ void setProperty(in string propertyName, in AString propertyStr);
+
+ attribute string knownArtsSet;
+ attribute ACString folderName;
+};
diff --git a/mailnews/db/msgdb/public/nsIMsgDatabase.idl b/mailnews/db/msgdb/public/nsIMsgDatabase.idl
new file mode 100644
index 000000000..6b79fc940
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsIMsgDatabase.idl
@@ -0,0 +1,570 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * @defgroup msgdb Mailnews message database
+ * This module is the access point to locally-stored databases.
+ *
+ * These databases are stored in .msf files. Each file contains useful cached
+ * information, like the message id or references, as well as the cc header or
+ * tag information. This cached information is encapsulated in nsIMsgDBHdr.
+ *
+ * Also included is threading information, mostly encapsulated in nsIMsgThread.
+ * The final component is the database folder info, which contains information
+ * on the view and basic information also stored in the folder cache such as the
+ * name or most recent update.
+ *
+ * What this module does not do is access individual messages. Access is
+ * strictly controlled by the nsIMsgFolder objects and their backends.
+ * @{
+ */
+#include "nsISupports.idl"
+#include "nsIDBChangeAnnouncer.idl"
+
+%{C++
+#include "nsTArray.h"
+%}
+
+interface nsIMutableArray;
+interface nsIMsgDatabase;
+interface nsIMsgDBView;
+interface nsIDBChangeListener;
+interface nsIMsgDBHdr;
+interface nsISimpleEnumerator;
+interface nsIMsgThread;
+interface nsIDBFolderInfo;
+interface nsIMsgOfflineImapOperation;
+interface nsIMsgFolder;
+interface nsIMsgKeyArray;
+interface nsIFile;
+interface nsIArray;
+
+typedef unsigned long nsMsgRetainByPreference;
+
+
+[scriptable, uuid(fe8b7cec-eec8-4bcd-82ff-d8bb23cef3da)]
+
+interface nsIMsgRetentionSettings : nsISupports
+{
+ const unsigned long nsMsgRetainAll = 1;
+ const unsigned long nsMsgRetainByAge = 2;
+ const unsigned long nsMsgRetainByNumHeaders = 3;
+
+ attribute boolean useServerDefaults;
+ attribute nsMsgRetainByPreference retainByPreference;
+ attribute unsigned long daysToKeepHdrs;
+ attribute unsigned long numHeadersToKeep;
+
+ // this is for keeping offline bodies.
+ attribute boolean cleanupBodiesByDays;
+ attribute unsigned long daysToKeepBodies;
+
+ /**
+ * Should retention settings be applied to flagged/starred messages?
+ * If false, flagged messages are never automatically deleted.
+ */
+ attribute boolean applyToFlaggedMessages;
+};
+
+[scriptable, uuid(86a9da90-14f1-11d5-a5c0-0060b0fc04b7)]
+interface nsIMsgDownloadSettings : nsISupports
+{
+ attribute boolean useServerDefaults;
+ attribute boolean downloadByDate;
+ attribute boolean downloadUnreadOnly;
+ attribute unsigned long ageLimitOfMsgsToDownload;
+};
+
+typedef long nsMsgDBCommit;
+
+[scriptable, uuid(15431853-e448-45dc-8978-9958bf74d9b7)]
+
+interface nsMsgDBCommitType
+{
+ const long kLargeCommit = 1;
+ const long kSessionCommit = 2;
+ const long kCompressCommit = 3;
+};
+
+[ref] native nsMsgKeyArrayRef(nsTArray<nsMsgKey>);
+[ptr] native nsMsgKeyArrayPtr(nsTArray<nsMsgKey>);
+
+/**
+ * A service to open mail databases and manipulate listeners automatically.
+ *
+ * The contract ID for this component is
+ * <tt>\@mozilla.org/msgDatabase/msgDBService;1</tt>.
+ */
+[scriptable, uuid(4cbbf024-3760-402d-89f3-6ababafeb07d)]
+interface nsIMsgDBService : nsISupports
+{
+ /**
+ * Opens a database for a given folder.
+ *
+ * This method is preferred over nsIMsgDBService::openMailDBFromFile if the
+ * caller has an actual nsIMsgFolder around. If the database detects that it
+ * is unreadable or out of date (using nsIMsgDatabase::outOfDate) it will
+ * destroy itself and prepare to be rebuilt, unless aLeaveInvalidDB is true.
+ *
+ * If one gets a NS_MSG_ERROR_FOLDER_SUMMARY_MISSING message, then one
+ * should call nsIMsgDBService::createNewDB to create the new database.
+ *
+ * @param aFolder The folder whose database should be returned.
+ * @param aLeaveInvalidDB Whether or not the database should be deleted if it
+ * is invalid.
+ * @return A new nsIMsgDatabase object representing the folder
+ * database that was opened.
+ * @exception NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
+ * The file could not be created.
+ * @exception NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE
+ * The database is present (and was opened), but the
+ * summary file is out of date.
+ * @exception NS_MSG_ERROR_FOLDER_SUMMARY_MISSING
+ * The database is present, but the summary file is
+ * missing.
+ * @see nsIMsgDatabase::Open
+ * @see nsIMsgDBService::createNewDB
+ */
+ nsIMsgDatabase openFolderDB(in nsIMsgFolder aFolder,
+ in boolean aLeaveInvalidDB);
+
+ /**
+ * This is the same as a synchronous open in terms of params and errors.
+ * But to finish opening the db, the caller must call
+ * nsIMsgDBService::OpenMore repeatedly until the open is finished.
+ * @see nsIMsgDBService::openFolderDB
+ * @see nsIMsgDBService::openMore
+ */
+ nsIMsgDatabase asyncOpenFolderDB(in nsIMsgFolder aFolder,
+ in boolean aLeaveInvalidDB);
+
+ /**
+ * Continues the open process for a db opened with
+ * nsIMsgDBService::asyncOpenFolderDB. Returns true if the db is ready
+ * to use, false if openMore needs to be called again.
+ * This will throw the same kinds of exceptions as openFolderDB.
+ * @param aTimeHint approximate number of milliseconds to spend
+ * before returning. This is more of a floor than
+ a ceiling, since we can't guarantee that there
+ won't be one big chunk that we can't interrupt.
+ * @return true if db is ready to use, false if openMore needs to
+ * be called again.
+ * @see nsIMsgDBService::openFolderDB
+ */
+ boolean openMore(in nsIMsgDatabase aDB, in unsigned long aTimeHint);
+
+ /**
+ * Creates a new database for the given folder.
+ *
+ * If the database already exists, it will return the database, emit a
+ * warning, but not fully initialize it. For this reason, it should only be
+ * used when it is known that the database does not exist, such as when
+ * nsIMsgDBService::openFolderDB throws an error.
+ *
+ * @see nsIMsgDBService::openFolderDB
+ */
+ nsIMsgDatabase createNewDB(in nsIMsgFolder aFolder);
+
+ /**
+ * Opens or creates a database for a given file.
+ *
+ * This method should only be used if the caller does not have a folder
+ * instance, because the resulting db and message headers retrieved from the
+ * database would not know their owning folder, which limits their usefulness.
+ * For this reason, one should use nsIMsgDBService::openFolderDB instead
+ * except under special circumstances.
+ *
+ * Unlike nsIMsgDBService::openFolderDB, there is no corresponding method to
+ * create a new database if opening the database failed. However, this method
+ * will never throw NS_MSG_ERROR_FOLDER_SUMMARY_MISSING, so no corresponding
+ * method is needed.
+ *
+ * @param aFile The file for which the database should be returned.
+ * @param aFolder Folder the db corresponds to (may be null)
+ * @param aCreate Whether or not the file should be created.
+ * @param aLeaveInvalidDB Whether or not the database should be deleted if it
+ * is invalid.
+ * @return A new nsIMsgDatabase object encapsulating the file
+ * passed in.
+ * @exception NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
+ * The file could not be created.
+ * @see nsIMsgDBService::openFolderDB
+ * @see nsIMsgDatabase::Open
+ */
+ nsIMsgDatabase openMailDBFromFile(in nsIFile aFile,
+ in nsIMsgFolder aFolder,
+ in boolean aCreate,
+ in boolean aLeaveInvalidDB);
+ /**
+ * Adds the given listener to the listener set for the folder.
+ *
+ * Since the message database will likely be opened and closed many times, by
+ * registering using this method, one will be guaranteed to see all subsequent
+ * modifications. This will also add the listener to the database if it is
+ * already opened.
+ *
+ * @param aFolder The folder to add a listener to.
+ * @param aListener The listener to add the folder to.
+ */
+ void registerPendingListener(in nsIMsgFolder aFolder,
+ in nsIDBChangeListener aListener);
+ /**
+ * Removes the listener from all folder listener sets.
+ *
+ * @param aListener The listener to remove.
+ * @exception NS_ERROR_FAILURE
+ * The listener is not registered.
+ */
+ void unregisterPendingListener(in nsIDBChangeListener aListener);
+
+ /**
+ * Get the db for a folder, if already open.
+ *
+ * @param aFolder The folder to get the cached (open) db for.
+ *
+ * @returns null if the db isn't open, otherwise the db.
+ */
+ nsIMsgDatabase cachedDBForFolder(in nsIMsgFolder aFolder);
+
+ /**
+ * Close the db for a folder, if already open.
+ *
+ * @param aFolder The folder to close the cached (open) db for.
+ */
+ void forceFolderDBClosed(in nsIMsgFolder aFolder);
+
+ /// an enumerator to iterate over the open dbs.
+ readonly attribute nsIArray openDBs;
+};
+
+[scriptable, uuid(b64e66f8-4717-423a-be42-482658fb2199)]
+interface nsIMsgDatabase : nsIDBChangeAnnouncer {
+ void Close(in boolean aForceCommit);
+
+ void Commit(in nsMsgDBCommit commitType);
+ // Force closed is evil, and we should see if we can do without it.
+ // In 4.x, it was mainly used to remove corrupted databases.
+ void ForceClosed();
+ void clearCachedHdrs();
+ void resetHdrCacheSize(in unsigned long size);
+
+ readonly attribute nsIDBFolderInfo dBFolderInfo;
+
+ /// Size of the database file in bytes.
+ readonly attribute long long databaseSize;
+
+ /// Folder this db was opened on.
+ readonly attribute nsIMsgFolder folder;
+
+ /**
+ * This is used when deciding which db's to close to free up memory
+ * and other resources in an LRU manner. It doesn't track every operation
+ * on every object from the db, but high level things like open, commit,
+ * and perhaps some of the list methods. Commit should be a proxy for all
+ * the mutation methods.
+ *
+ * I'm allowing clients to set the last use time as well, so that
+ * nsIMsgFolder.msgDatabase can set the last use time.
+ */
+ attribute PRTime lastUseTime;
+
+ // get a message header for the given key. Caller must release()!
+
+ nsIMsgDBHdr GetMsgHdrForKey(in nsMsgKey key);
+ nsIMsgDBHdr getMsgHdrForMessageID(in string messageID);
+
+ /**
+ * get a message header for a Gmail message with the given X-GM-MSGID.
+ */
+ nsIMsgDBHdr GetMsgHdrForGMMsgID(in string aGmailMessageID);
+ //Returns whether or not this database contains the given key
+ boolean ContainsKey(in nsMsgKey key);
+
+/**
+ * Must call AddNewHdrToDB after creating. The idea is that you create
+ * a new header, fill in its properties, and then call AddNewHdrToDB.
+ * AddNewHdrToDB will send notifications to any listeners.
+ *
+ * @param aKey msgKey for the new header. If aKey is nsMsgKey_None,
+ * we will auto-assign a new key.
+ */
+ nsIMsgDBHdr CreateNewHdr(in nsMsgKey aKey);
+
+ void AddNewHdrToDB(in nsIMsgDBHdr newHdr, in boolean notify);
+
+ nsIMsgDBHdr CopyHdrFromExistingHdr(in nsMsgKey key, in nsIMsgDBHdr existingHdr, in boolean addHdrToDB);
+
+ /**
+ * Returns all message keys stored in the database.
+ * Keys are returned in the order as stored in the database.
+ * The caller should sort them if it needs to.
+ */
+ void ListAllKeys(in nsIMsgKeyArray array);
+
+ nsISimpleEnumerator EnumerateMessages();
+ nsISimpleEnumerator ReverseEnumerateMessages();
+ nsISimpleEnumerator EnumerateThreads();
+
+ /**
+ * Get an enumerator for use with nextMatchingHdrs. The enumerator
+ * will only return messages that match the passed-in search terms.
+ *
+ * @param searchTerms array of search terms to evaluate.
+ * @param reverse start at the end, defaults to false.
+ *
+ * @returns an enumerator for passing into nextMatchingHdrs
+ */
+ nsISimpleEnumerator getFilterEnumerator(in nsIArray searchTerms,
+ [optional] in boolean reverse);
+
+ /**
+ * Get the next N matching headers using a filter enumerator
+ * obtained by calling getFilterEnumerator.
+ *
+ * @param enumerator - This *must* be a filter enumerator
+ * @param numHdrsToLookAt if non 0, the number of hdrs to advance the
+ * enumerator before returning.
+ * @param maxResults if non 0, the max results to return.
+ * @param matchingHdrs if non null, array of matching hdrs.
+ * @param numMatches if non null, the number of matching hdrs.
+ *
+ * @returns false, if done, true if more hdrs to look at.
+ */
+ boolean nextMatchingHdrs(in nsISimpleEnumerator enumerator,
+ in long numHdrsToLookAt,
+ in long maxResults,
+ in nsIMutableArray matchingHdrs,
+ out long numMatches);
+
+
+ // count the total and unread msgs, and adjust global count if needed
+ void syncCounts();
+
+ nsIMsgThread GetThreadContainingMsgHdr(in nsIMsgDBHdr msgHdr) ;
+
+ // helpers for user command functions like delete, mark read, etc.
+
+ void MarkHdrRead(in nsIMsgDBHdr msgHdr, in boolean bRead,
+ in nsIDBChangeListener instigator);
+
+ void MarkHdrReplied(in nsIMsgDBHdr msgHdr, in boolean bReplied,
+ in nsIDBChangeListener instigator);
+
+ void MarkHdrMarked(in nsIMsgDBHdr msgHdr, in boolean mark,
+ in nsIDBChangeListener instigator);
+ /**
+ * Remove the new status from a message.
+ *
+ * @param aMsgHdr The database reference header for the message
+ * @param aInstigator Reference to original calling object
+ */
+ void MarkHdrNotNew(in nsIMsgDBHdr aMsgHdr,
+ in nsIDBChangeListener aInstigator);
+
+ // MDN support
+ void MarkMDNNeeded(in nsMsgKey key, in boolean bNeeded,
+ in nsIDBChangeListener instigator);
+
+ // MarkMDNneeded only used when mail server is a POP3 server
+ // or when the IMAP server does not support user defined
+ // PERMANENTFLAGS
+ boolean IsMDNNeeded(in nsMsgKey key);
+
+ void MarkMDNSent(in nsMsgKey key, in boolean bNeeded,
+ in nsIDBChangeListener instigator);
+ boolean IsMDNSent(in nsMsgKey key);
+
+// methods to get and set docsets for ids.
+ void MarkRead(in nsMsgKey key, in boolean bRead,
+ in nsIDBChangeListener instigator);
+
+ void MarkReplied(in nsMsgKey key, in boolean bReplied,
+ in nsIDBChangeListener instigator);
+
+ void MarkForwarded(in nsMsgKey key, in boolean bForwarded,
+ in nsIDBChangeListener instigator);
+
+ void MarkHasAttachments(in nsMsgKey key, in boolean bHasAttachments,
+ in nsIDBChangeListener instigator);
+
+ void MarkThreadRead(in nsIMsgThread thread, in nsIDBChangeListener instigator,
+ out unsigned long aCount,
+ [array, size_is(aCount)] out nsMsgKey aKeys);
+
+ /// Mark the specified thread ignored.
+ void MarkThreadIgnored(in nsIMsgThread thread, in nsMsgKey threadKey,
+ in boolean bIgnored,
+ in nsIDBChangeListener instigator);
+
+ /// Mark the specified thread watched.
+ void MarkThreadWatched(in nsIMsgThread thread, in nsMsgKey threadKey,
+ in boolean bWatched,
+ in nsIDBChangeListener instigator);
+
+ /// Mark the specified subthread ignored.
+ void MarkHeaderKilled(in nsIMsgDBHdr msg, in boolean bIgnored,
+ in nsIDBChangeListener instigator);
+
+ /// Is the message read.
+ boolean IsRead(in nsMsgKey key);
+ /// Is the message part of an ignored thread.
+ boolean IsIgnored(in nsMsgKey key);
+ /// Is the message part of a watched thread.
+ boolean IsWatched(in nsMsgKey key);
+ /// Is the message flagged/starred.
+ boolean IsMarked(in nsMsgKey key);
+ /// Does the message have attachments.
+ boolean HasAttachments(in nsMsgKey key);
+
+ void MarkAllRead(out unsigned long aCount,
+ [array, size_is(aCount)] out nsMsgKey aKeys);
+
+ void deleteMessages(in unsigned long aNumKeys,
+ [array, size_is(aNumKeys)] in nsMsgKey nsMsgKeys,
+ in nsIDBChangeListener instigator);
+ void DeleteMessage(in nsMsgKey key,
+ in nsIDBChangeListener instigator,
+ in boolean commit);
+ void DeleteHeader(in nsIMsgDBHdr msgHdr, in nsIDBChangeListener instigator,
+ in boolean commit, in boolean notify);
+
+ // lower level routine that doesn't remove hdr from thread or adjust counts
+ void RemoveHeaderMdbRow(in nsIMsgDBHdr msgHdr);
+
+ void UndoDelete(in nsIMsgDBHdr msgHdr);
+
+ void MarkMarked(in nsMsgKey key, in boolean mark,
+ in nsIDBChangeListener instigator);
+ void MarkOffline(in nsMsgKey key, in boolean offline,
+ in nsIDBChangeListener instigator);
+ void SetLabel(in nsMsgKey key, in nsMsgLabelValue label);
+ void setStringProperty(in nsMsgKey aKey, in string aProperty, in string aValue);
+ /**
+ * Set the value of a string property in a message header
+ *
+ * @param msgHdr Header of the message whose property will be changed
+ * @param aProperty the property to change
+ * @param aValue new value for the property
+ */
+ void setStringPropertyByHdr(in nsIMsgDBHdr msgHdr, in string aProperty, in string aValue);
+
+ /**
+ * Set the value of a uint32 property in a message header.
+ *
+ * @param aMsgHdr header of the message whose property will be changed
+ * @param aProperty the property to change
+ * @param aValue new value for the property
+ */
+ void setUint32PropertyByHdr(in nsIMsgDBHdr aMsgHdr,
+ in string aProperty, in unsigned long aValue);
+
+ void MarkImapDeleted(in nsMsgKey key, in boolean deleted,
+ in nsIDBChangeListener instigator);
+
+ readonly attribute nsMsgKey FirstNew;
+
+ attribute nsIMsgRetentionSettings msgRetentionSettings;
+ // purge unwanted message headers and/or bodies. If deleteViaFolder is
+ // true, we'll call nsIMsgFolder::DeleteMessages to delete the messages.
+ // Otherwise, we'll just delete them from the db.
+ void applyRetentionSettings(in nsIMsgRetentionSettings aMsgRetentionSettings,
+ in boolean aDeleteViaFolder);
+
+ attribute nsIMsgDownloadSettings msgDownloadSettings;
+
+ boolean HasNew();
+ void ClearNewList(in boolean notify);
+ void AddToNewList(in nsMsgKey key);
+
+ // used mainly to force the timestamp of a local mail folder db to
+ // match the time stamp of the corresponding berkeley mail folder,
+ // but also useful to tell the summary to mark itself invalid
+ // Also, if a local folder is being reparsed, summary will be invalid
+ // until the reparsing is done.
+ attribute boolean summaryValid;
+
+ // batching - can be used to cache file stream for local mail,
+ // and perhaps to use the mdb batching mechanism as well.
+ void StartBatch();
+ void EndBatch();
+ // offline operations - we could move these into an offline operation interface
+ // but it would have to be in nsMailDatabase, since local folders can be move destinations
+ nsIMsgOfflineImapOperation GetOfflineOpForKey(in nsMsgKey messageKey, in boolean create);
+ void RemoveOfflineOp(in nsIMsgOfflineImapOperation op);
+ nsISimpleEnumerator EnumerateOfflineOps();
+ [noscript] void ListAllOfflineOpIds(in nsMsgKeyArrayPtr offlineOpIds);
+ [noscript] void ListAllOfflineDeletes(in nsMsgKeyArrayPtr offlineDeletes);
+ void ListAllOfflineMsgs(in nsIMsgKeyArray aKeys);
+
+ void setAttributeOnPendingHdr(in nsIMsgDBHdr pendingHdr, in string property,
+ in string propertyVal);
+
+ void setUint32AttributeOnPendingHdr(in nsIMsgDBHdr pendingHdr, in string property,
+ in unsigned long propertyVal);
+
+ /**
+ * Sets a pending 64 bit attribute, which tells the DB that when a message
+ * which looks like the pendingHdr (e.g., same message-id) is added to the
+ * db, set the passed in property and value on the new header. This is
+ * usually because we've copied an imap message to a different folder, and
+ * want to carry forward attributes from the original message to the copy,
+ * but don't have the message hdr for the copy yet so we can't set
+ * attributes directly.
+ *
+ * @param aPendingHdr usually the source of the copy.
+ * @param aProperty name of property to set.
+ * @param aPropertyVal 64 bit value of property to set.
+ */
+ void setUint64AttributeOnPendingHdr(in nsIMsgDBHdr aPendingHdr,
+ in string aProperty,
+ in unsigned long long aPropertyVal);
+
+ /**
+ * Given a message header with its message-id set, update any pending
+ * attributes on the header.
+ *
+ * @param aNewHdr a new header that may have pending attributes.
+ */
+ void updatePendingAttributes(in nsIMsgDBHdr aNewHdr);
+
+ readonly attribute nsMsgKey lowWaterArticleNum;
+ readonly attribute nsMsgKey highWaterArticleNum;
+ attribute nsMsgKey nextPseudoMsgKey; //for undo-redo of move pop->imap
+ readonly attribute nsMsgKey nextFakeOfflineMsgKey; // for saving "fake" offline msg hdrs
+ // for sorting
+ void createCollationKey(in AString sourceString, out unsigned long aCount,
+ [array, size_is(aCount)] out octet aKey);
+ long compareCollationKeys(in unsigned long aLen1,
+ [array, size_is(aLen1)] in octet key1,
+ in unsigned long aLen2,
+ [array, size_is(aLen2)] in octet key2);
+
+ // when creating a view, the default sort order and view flags
+ // use these for the default. (this allows news to override, so that
+ // news can be threaded by default)
+ readonly attribute nsMsgViewFlagsTypeValue defaultViewFlags;
+ readonly attribute nsMsgViewSortTypeValue defaultSortType;
+ readonly attribute nsMsgViewSortOrderValue defaultSortOrder;
+
+ // for msg hdr hash table allocation. controllable by caller to improve folder loading preformance.
+ attribute unsigned long msgHdrCacheSize;
+
+ /**
+ * The list of messages currently in the NEW state.
+ *
+ * If there are no such messages, a null pointer may be returned.
+ * the caller should free when done using free.
+ */
+ void getNewList(out unsigned long count, [array, size_is(count)] out nsMsgKey newKeys);
+
+ // These are used for caching search hits in a db, to speed up saved search folders.
+ nsISimpleEnumerator getCachedHits(in string aSearchFolderUri);
+ void refreshCache(in string aSearchFolderUri, in unsigned long aNumKeys, [array, size_is (aNumKeys)] in nsMsgKey aNewHits,
+ out unsigned long aNumBadHits, [array, size_is(aNumBadHits)] out nsMsgKey aStaleHits);
+ void updateHdrInCache(in string aSearchFolderUri, in nsIMsgDBHdr aHdr, in boolean aAdd);
+ boolean hdrIsInCache(in string aSearchFolderUri, in nsIMsgDBHdr aHdr);
+
+};
+/** @} */
diff --git a/mailnews/db/msgdb/public/nsIMsgOfflineImapOperation.idl b/mailnews/db/msgdb/public/nsIMsgOfflineImapOperation.idl
new file mode 100644
index 000000000..b07beef63
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsIMsgOfflineImapOperation.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+// #include "nsIImapUrl.idl" // for imapMessageFlagsType
+
+typedef unsigned short imapMessageFlagsType;
+
+typedef long nsOfflineImapOperationType;
+
+[scriptable, uuid(b5229a55-22bb-444b-be92-13d719353828)]
+
+interface nsIMsgOfflineImapOperation : nsISupports
+{
+// type of stored imap operations
+ const long kFlagsChanged = 0x1;
+ const long kMsgMoved = 0x2;
+ const long kMsgCopy = 0x4;
+ const long kMoveResult = 0x8;
+ const long kAppendDraft = 0x10;
+ const long kAddedHeader = 0x20;
+ const long kDeletedMsg = 0x40;
+ const long kMsgMarkedDeleted = 0x80;
+ const long kAppendTemplate = 0x100;
+ const long kDeleteAllMsgs = 0x200;
+ const long kAddKeywords = 0x400;
+ const long kRemoveKeywords = 0x800;
+
+ attribute nsOfflineImapOperationType operation;
+ void clearOperation(in nsOfflineImapOperationType operation);
+ attribute nsMsgKey messageKey;
+
+ // for move/copy operations, the msg key of the source msg.
+ attribute nsMsgKey srcMessageKey;
+
+ attribute imapMessageFlagsType flagOperation;
+ attribute imapMessageFlagsType newFlags; // for kFlagsChanged
+ attribute string destinationFolderURI; // for move or copy
+ attribute string sourceFolderURI;
+ void addKeywordToAdd(in string aKeyword);
+ void addKeywordToRemove(in string aKeyword);
+ readonly attribute string keywordsToAdd;
+ readonly attribute string keywordsToRemove;
+ readonly attribute long numberOfCopies;
+ void addMessageCopyOperation(in string destinationBox);
+ string getCopyDestination(in long copyIndex);
+ attribute unsigned long msgSize;
+ attribute boolean playingBack;
+};
+
diff --git a/mailnews/db/msgdb/public/nsINewsDatabase.idl b/mailnews/db/msgdb/public/nsINewsDatabase.idl
new file mode 100644
index 000000000..151a42f01
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsINewsDatabase.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include "nsMsgKeySet.h"
+%}
+
+[ptr] native nsMsgKeySetPtr(nsMsgKeySet);
+
+[scriptable, uuid(f700208a-1dd1-11b2-b947-e4e1e4fdf278)]
+
+interface nsINewsDatabase : nsISupports {
+ [noscript] attribute nsMsgKeySetPtr readSet;
+};
diff --git a/mailnews/db/msgdb/public/nsImapMailDatabase.h b/mailnews/db/msgdb/public/nsImapMailDatabase.h
new file mode 100644
index 000000000..b6092c284
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsImapMailDatabase.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsImapMailDatabase_H_
+#define _nsImapMailDatabase_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMailDatabase.h"
+
+class nsImapMailDatabase : public nsMailDatabase
+{
+public:
+ // OK, it's dumb that this should require a fileSpec, since there is no file
+ // for the folder. This is mainly because we're deriving from nsMailDatabase;
+ // Perhaps we shouldn't...
+ nsImapMailDatabase();
+ virtual ~nsImapMailDatabase();
+
+ NS_IMETHOD StartBatch() override;
+ NS_IMETHOD EndBatch() override;
+ NS_IMETHOD GetSummaryValid(bool *aResult) override;
+ NS_IMETHOD SetSummaryValid(bool valid = true) override;
+ virtual nsresult AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr) override;
+
+ NS_IMETHOD ForceClosed() override;
+ NS_IMETHOD AddNewHdrToDB(nsIMsgDBHdr *newHdr, bool notify) override;
+ NS_IMETHOD SetAttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
+ const char *propertyVal) override;
+ NS_IMETHOD SetUint32AttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
+ uint32_t propertyVal) override;
+ NS_IMETHOD SetUint64AttributeOnPendingHdr(nsIMsgDBHdr *aPendingHdr,
+ const char *aProperty,
+ uint64_t aPropertyVal) override;
+ NS_IMETHOD DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys,
+ nsIDBChangeListener *instigator) override;
+ NS_IMETHOD UpdatePendingAttributes(nsIMsgDBHdr* aNewHdr) override;
+
+protected:
+ // IMAP does not set local file flags, override does nothing
+ virtual void UpdateFolderFlag(nsIMsgDBHdr *msgHdr, bool bSet,
+ nsMsgMessageFlagType flag, nsIOutputStream **ppFileStream);
+
+ nsresult GetRowForPendingHdr(nsIMsgDBHdr *pendingHdr, nsIMdbRow **row);
+ nsresult GetAllPendingHdrsTable();
+ mdb_token m_pendingHdrsRowScopeToken;
+ mdb_token m_pendingHdrsTableKindToken;
+ nsCOMPtr<nsIMdbTable> m_mdbAllPendingHdrsTable;
+};
+
+
+#endif
diff --git a/mailnews/db/msgdb/public/nsMailDatabase.h b/mailnews/db/msgdb/public/nsMailDatabase.h
new file mode 100644
index 000000000..6a5c6b5c4
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsMailDatabase.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMailDatabase_H_
+#define _nsMailDatabase_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgDatabase.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+
+// This is the subclass of nsMsgDatabase that handles local mail messages.
+class nsIOFileStream;
+class nsIFile;
+class nsOfflineImapOperation;
+
+class nsMailDatabase : public nsMsgDatabase
+{
+public:
+ nsMailDatabase();
+ virtual ~nsMailDatabase();
+ NS_IMETHOD ForceClosed() override;
+ NS_IMETHOD DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys,
+ nsIDBChangeListener *instigator) override;
+
+ NS_IMETHOD StartBatch() override;
+ NS_IMETHOD EndBatch() override;
+
+ nsresult Open(nsMsgDBService* aDBService, nsIFile *aSummaryFile, bool create, bool upgrading) override;
+ virtual nsMailDatabase *GetMailDB() {return this;}
+
+ virtual uint32_t GetCurVersion() override {return kMsgDBVersion;}
+
+ NS_IMETHOD GetOfflineOpForKey(nsMsgKey opKey, bool create,
+ nsIMsgOfflineImapOperation **op) override;
+ NS_IMETHOD RemoveOfflineOp(nsIMsgOfflineImapOperation *op) override;
+
+ NS_IMETHOD SetSummaryValid(bool valid) override;
+ NS_IMETHOD GetSummaryValid(bool *valid) override;
+
+ NS_IMETHOD EnumerateOfflineOps(nsISimpleEnumerator **enumerator) override;
+ NS_IMETHOD ListAllOfflineOpIds(nsTArray<nsMsgKey> *offlineOpIds) override;
+ NS_IMETHOD ListAllOfflineDeletes(nsTArray<nsMsgKey> *offlineDeletes) override;
+
+ friend class nsMsgOfflineOpEnumerator;
+protected:
+
+ nsresult GetAllOfflineOpsTable(); // get this on demand
+
+ // get the time and date of the mailbox file
+ void GetMailboxModProperties(int64_t *aSize, uint32_t *aDate);
+
+ nsCOMPtr <nsIMdbTable> m_mdbAllOfflineOpsTable;
+ mdb_token m_offlineOpsRowScopeToken;
+ mdb_token m_offlineOpsTableKindToken;
+
+ virtual void SetReparse(bool reparse);
+
+protected:
+
+ bool m_reparse;
+};
+
+#endif
diff --git a/mailnews/db/msgdb/public/nsMsgDBCID.h b/mailnews/db/msgdb/public/nsMsgDBCID.h
new file mode 100644
index 000000000..557cc24c0
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsMsgDBCID.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgDBCID_h__
+#define nsMsgDBCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+// 03223c50-1e88-45e8-ba1a-7ce792dc3fc3
+#define NS_MSGDB_SERVICE_CID \
+{ 0x03223c50, 0x1e88, 0x45e8, \
+ { 0xba, 0x1a, 0x7c, 0xe7, 0x92, 0xdc, 0x3f, 0xc3 } }
+
+#define NS_MSGDB_SERVICE_CONTRACTID \
+ "@mozilla.org/msgDatabase/msgDBService;1"
+
+#define NS_MSGDB_CONTRACTID \
+ "@mozilla.org/nsMsgDatabase/msgDB-"
+
+#define NS_MAILBOXDB_CONTRACTID \
+ NS_MSGDB_CONTRACTID"mailbox"
+
+// a86c86ae-e97f-11d2-a506-0060b0fc04b7
+#define NS_MAILDB_CID \
+{ 0xa86c86ae, 0xe97f, 0x11d2, \
+ { 0xa5, 0x06, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 } }
+
+#define NS_NEWSDB_CONTRACTID \
+ NS_MSGDB_CONTRACTID"news"
+
+// 36414aa0-e980-11d2-a506-0060b0fc04b7
+#define NS_NEWSDB_CID \
+{ 0x36414aa0, 0xe980, 0x11d2, \
+ { 0xa5, 0x06, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 } }
+
+#define NS_IMAPDB_CONTRACTID \
+ NS_MSGDB_CONTRACTID"imap"
+
+// 9e4b07ee-e980-11d2-a506-0060b0fc04b7
+#define NS_IMAPDB_CID \
+{ 0x9e4b07ee, 0xe980, 0x11d2, \
+ { 0xa5, 0x06, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 } }
+
+#define NS_MSG_RETENTIONSETTINGS_CID \
+{ 0x1bd976d6, 0xdf44, 0x11d4, \
+ {0xa5, 0xb6, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7} }
+
+#define NS_MSG_RETENTIONSETTINGS_CONTRACTID \
+ "@mozilla.org/msgDatabase/retentionSettings;1"
+
+// 4e3dae5a-157a-11d5-a5c0-0060b0fc04b7
+#define NS_MSG_DOWNLOADSETTINGS_CID \
+{ 0x4e3dae5a, 0x157a, 0x11d5, \
+ {0xa5, 0xc0, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7} }
+
+#define NS_MSG_DOWNLOADSETTINGS_CONTRACTID \
+ "@mozilla.org/msgDatabase/downloadSettings;1"
+
+#endif
diff --git a/mailnews/db/msgdb/public/nsMsgDatabase.h b/mailnews/db/msgdb/public/nsMsgDatabase.h
new file mode 100644
index 000000000..bb011e85c
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsMsgDatabase.h
@@ -0,0 +1,462 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgDatabase_H_
+#define _nsMsgDatabase_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgHdr.h"
+#include "nsStringGlue.h"
+#include "nsAutoPtr.h"
+#include "nsIDBChangeListener.h"
+#include "nsIDBChangeAnnouncer.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFolder.h"
+#include "nsIMutableArray.h"
+#include "nsDBFolderInfo.h"
+#include "nsICollation.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIMimeConverter.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "PLDHashTable.h"
+#include "nsTArray.h"
+#include "nsTObserverArray.h"
+class ListContext;
+class nsMsgKeySet;
+class nsMsgThread;
+class nsMsgDatabase;
+class nsIMsgThread;
+class nsIDBFolderInfo;
+
+const int32_t kMsgDBVersion = 1;
+
+// Hopefully we're not opening up lots of databases at the same time, however
+// this will give us a buffer before we need to start reallocating the cache
+// array.
+const uint32_t kInitialMsgDBCacheSize = 20;
+
+class nsMsgDBService final : public nsIMsgDBService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGDBSERVICE
+
+ nsMsgDBService();
+
+ void AddToCache(nsMsgDatabase* pMessageDB);
+ void DumpCache();
+ void EnsureCached(nsMsgDatabase* pMessageDB)
+ {
+ if (!m_dbCache.Contains(pMessageDB))
+ m_dbCache.AppendElement(pMessageDB);
+ }
+ void RemoveFromCache(nsMsgDatabase* pMessageDB)
+ {
+ m_dbCache.RemoveElement(pMessageDB);
+ }
+
+protected:
+ ~nsMsgDBService();
+ void HookupPendingListeners(nsIMsgDatabase *db, nsIMsgFolder *folder);
+ void FinishDBOpen(nsIMsgFolder *aFolder, nsMsgDatabase *aMsgDB);
+ nsMsgDatabase* FindInCache(nsIFile *dbName);
+
+ nsCOMArray <nsIMsgFolder> m_foldersPendingListeners;
+ nsCOMArray <nsIDBChangeListener> m_pendingListeners;
+ AutoTArray<nsMsgDatabase*, kInitialMsgDBCacheSize> m_dbCache;
+};
+
+class nsMsgDBEnumerator : public nsISimpleEnumerator {
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsMsgDBEnumerator methods:
+ typedef nsresult (*nsMsgDBEnumeratorFilter)(nsIMsgDBHdr* hdr, void* closure);
+
+ nsMsgDBEnumerator(nsMsgDatabase* db, nsIMdbTable *table,
+ nsMsgDBEnumeratorFilter filter, void* closure,
+ bool iterateForwards = true);
+ void Clear();
+
+ nsresult GetRowCursor();
+ virtual nsresult PrefetchNext();
+ RefPtr<nsMsgDatabase> mDB;
+ nsCOMPtr<nsIMdbTableRowCursor> mRowCursor;
+ mdb_pos mRowPos;
+ nsCOMPtr<nsIMsgDBHdr> mResultHdr;
+ bool mDone;
+ bool mNextPrefetched;
+ bool mIterateForwards;
+ nsMsgDBEnumeratorFilter mFilter;
+ nsCOMPtr <nsIMdbTable> mTable;
+ void* mClosure;
+ // This is used when the caller wants to limit how many headers the
+ // enumerator looks at in any given time slice.
+ mdb_pos mStopPos;
+
+protected:
+ virtual ~nsMsgDBEnumerator();
+};
+
+class nsMsgFilteredDBEnumerator : public nsMsgDBEnumerator
+{
+public:
+ nsMsgFilteredDBEnumerator(nsMsgDatabase* db, nsIMdbTable *table,
+ bool reverse, nsIArray *searchTerms);
+ virtual ~nsMsgFilteredDBEnumerator();
+ nsresult InitSearchSession(nsIArray *searchTerms, nsIMsgFolder *folder);
+
+protected:
+ virtual nsresult PrefetchNext() override;
+
+ nsCOMPtr <nsIMsgSearchSession> m_searchSession;
+
+};
+
+namespace mozilla {
+namespace mailnews {
+class MsgDBReporter;
+}
+}
+
+class nsMsgDatabase : public nsIMsgDatabase
+{
+public:
+ friend class nsMsgDBService;
+ friend class nsMsgPropertyEnumerator; // accesses m_mdbEnv and m_mdbStore
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDBCHANGEANNOUNCER
+ NS_DECL_NSIMSGDATABASE
+
+ /**
+ * Opens a database folder.
+ *
+ * @param aFolderName The name of the folder to create.
+ * @param aCreate Whether or not the file should be created.
+ * @param aLeaveInvalidDB Set to true if you do not want the database to be
+ * deleted if it is invalid.
+ * @exception NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
+ * The file could not be created.
+ * @exception NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE
+ * The database is present (and was opened), but the
+ * summary file is out of date.
+ * @exception NS_MSG_ERROR_FOLDER_SUMMARY_MISSING
+ * The database is present (and was opened), but the
+ * summary file is missing.
+ */
+ virtual nsresult Open(nsMsgDBService *aDBService, nsIFile *aFolderName,
+ bool aCreate, bool aLeaveInvalidDB);
+ virtual nsresult IsHeaderRead(nsIMsgDBHdr *hdr, bool *pRead);
+ virtual nsresult MarkHdrReadInDB(nsIMsgDBHdr *msgHdr, bool bRead,
+ nsIDBChangeListener *instigator);
+ nsresult OpenInternal(nsMsgDBService *aDBService, nsIFile *aFolderName,
+ bool aCreate, bool aLeaveInvalidDB, bool sync);
+ nsresult CheckForErrors(nsresult err, bool sync, nsMsgDBService *aDBService, nsIFile *summaryFile);
+ virtual nsresult OpenMDB(const char *dbName, bool create, bool sync);
+ virtual nsresult CloseMDB(bool commit);
+ virtual nsresult CreateMsgHdr(nsIMdbRow* hdrRow, nsMsgKey key, nsIMsgDBHdr **result);
+ virtual nsresult GetThreadForMsgKey(nsMsgKey msgKey, nsIMsgThread **result);
+ virtual nsresult EnumerateMessagesWithFlag(nsISimpleEnumerator* *result, uint32_t *pFlag);
+ nsresult GetSearchResultsTable(const char *searchFolderUri, bool createIfMissing, nsIMdbTable **table);
+
+ // this might just be for debugging - we'll see.
+ nsresult ListAllThreads(nsTArray<nsMsgKey> *threadIds);
+ //////////////////////////////////////////////////////////////////////////////
+ // nsMsgDatabase methods:
+ nsMsgDatabase();
+
+ void GetMDBFactory(nsIMdbFactory ** aMdbFactory);
+ nsIMdbEnv *GetEnv() {return m_mdbEnv;}
+ nsIMdbStore *GetStore() {return m_mdbStore;}
+ virtual uint32_t GetCurVersion();
+ nsresult GetCollationKeyGenerator();
+ nsIMimeConverter * GetMimeConverter();
+
+ nsresult GetTableCreateIfMissing(const char *scope, const char *kind, nsIMdbTable **table,
+ mdb_token &scopeToken, mdb_token &kindToken);
+
+ //helper function to fill in nsStrings from hdr row cell contents.
+ nsresult RowCellColumnTonsString(nsIMdbRow *row, mdb_token columnToken, nsAString &resultStr);
+ nsresult RowCellColumnToUInt32(nsIMdbRow *row, mdb_token columnToken, uint32_t *uint32Result, uint32_t defaultValue = 0);
+ nsresult RowCellColumnToUInt32(nsIMdbRow *row, mdb_token columnToken, uint32_t &uint32Result, uint32_t defaultValue = 0);
+ nsresult RowCellColumnToUInt64(nsIMdbRow *row, mdb_token columnToken, uint64_t *uint64Result, uint64_t defaultValue = 0);
+ nsresult RowCellColumnToMime2DecodedString(nsIMdbRow *row, mdb_token columnToken, nsAString &resultStr);
+ nsresult RowCellColumnToCollationKey(nsIMdbRow *row, mdb_token columnToken, uint8_t **result, uint32_t *len);
+ nsresult RowCellColumnToConstCharPtr(nsIMdbRow *row, mdb_token columnToken, const char **ptr);
+ nsresult RowCellColumnToAddressCollationKey(nsIMdbRow *row, mdb_token colToken, uint8_t **result, uint32_t *len);
+
+ nsresult GetEffectiveCharset(nsIMdbRow *row, nsACString &resultCharset);
+
+ // these methods take the property name as a string, not a token.
+ // they should be used when the properties aren't accessed a lot
+ nsresult GetProperty(nsIMdbRow *row, const char *propertyName, char **result);
+ nsresult SetProperty(nsIMdbRow *row, const char *propertyName, const char *propertyVal);
+ nsresult GetPropertyAsNSString(nsIMdbRow *row, const char *propertyName, nsAString &result);
+ nsresult SetPropertyFromNSString(nsIMdbRow *row, const char *propertyName, const nsAString &propertyVal);
+ nsresult GetUint32Property(nsIMdbRow *row, const char *propertyName, uint32_t *result, uint32_t defaultValue = 0);
+ nsresult GetUint64Property(nsIMdbRow *row, const char *propertyName, uint64_t *result, uint64_t defaultValue = 0);
+ nsresult SetUint32Property(nsIMdbRow *row, const char *propertyName, uint32_t propertyVal);
+ nsresult SetUint64Property(nsIMdbRow *row, const char *propertyName, uint64_t propertyVal);
+ nsresult GetBooleanProperty(nsIMdbRow *row, const char *propertyName,
+ bool *result, bool defaultValue = false);
+ nsresult SetBooleanProperty(nsIMdbRow *row, const char *propertyName,
+ bool propertyVal);
+ // helper function for once we have the token.
+ nsresult SetNSStringPropertyWithToken(nsIMdbRow *row, mdb_token aProperty, const nsAString &propertyStr);
+
+ // helper functions to put values in cells for the passed-in row
+ nsresult UInt32ToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, uint32_t value);
+ nsresult CharPtrToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, const char *charPtr);
+ nsresult RowCellColumnToCharPtr(nsIMdbRow *row, mdb_token columnToken, char **result);
+ nsresult UInt64ToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, uint64_t value);
+
+ // helper functions to copy an nsString to a yarn, int32 to yarn, and vice versa.
+ static struct mdbYarn *nsStringToYarn(struct mdbYarn *yarn, const nsAString &str);
+ static struct mdbYarn *UInt32ToYarn(struct mdbYarn *yarn, uint32_t i);
+ static struct mdbYarn *UInt64ToYarn(struct mdbYarn *yarn, uint64_t i);
+ static void YarnTonsString(struct mdbYarn *yarn, nsAString &str);
+ static void YarnTonsCString(struct mdbYarn *yarn, nsACString &str);
+ static void YarnToUInt32(struct mdbYarn *yarn, uint32_t *i);
+ static void YarnToUInt64(struct mdbYarn *yarn, uint64_t *i);
+
+#ifdef DEBUG
+ virtual nsresult DumpContents();
+ nsresult DumpThread(nsMsgKey threadId);
+ nsresult DumpMsgChildren(nsIMsgDBHdr *msgHdr);
+#endif
+
+ friend class nsMsgHdr; // use this to get access to cached tokens for hdr fields
+ friend class nsMsgThread; // use this to get access to cached tokens for hdr fields
+ friend class nsMsgDBEnumerator;
+ friend class nsMsgDBThreadEnumerator;
+protected:
+ virtual ~nsMsgDatabase();
+
+ // prefs stuff - in future, we might want to cache the prefs interface
+ nsresult GetBoolPref(const char *prefName, bool *result);
+ nsresult GetIntPref(const char *prefName, int32_t *result);
+ virtual void GetGlobalPrefs();
+ // retrieval methods
+ nsIMsgThread * GetThreadForReference(nsCString &msgID, nsIMsgDBHdr **pMsgHdr);
+ nsIMsgThread * GetThreadForSubject(nsCString &subject);
+ nsIMsgThread * GetThreadForMessageId(nsCString &msgId);
+ nsIMsgThread * GetThreadForThreadId(nsMsgKey threadId);
+ nsMsgHdr * GetMsgHdrForReference(nsCString &reference);
+ nsIMsgDBHdr * GetMsgHdrForSubject(nsCString &subject);
+ // threading interfaces
+ virtual nsresult CreateNewThread(nsMsgKey key, const char *subject, nsMsgThread **newThread);
+ virtual bool ThreadBySubjectWithoutRe();
+ virtual bool UseStrictThreading();
+ virtual bool UseCorrectThreading();
+ virtual nsresult ThreadNewHdr(nsMsgHdr* hdr, bool &newThread);
+ virtual nsresult AddNewThread(nsMsgHdr *msgHdr);
+ virtual nsresult AddToThread(nsMsgHdr *newHdr, nsIMsgThread *thread, nsIMsgDBHdr *pMsgHdr, bool threadInThread);
+
+ static PRTime gLastUseTime; // global last use time
+ PRTime m_lastUseTime; // last use time for this db
+ // inline to make instrumentation as cheap as possible
+ inline void RememberLastUseTime() {gLastUseTime = m_lastUseTime = PR_Now();}
+
+ bool MatchDbName(nsIFile *dbName); // returns TRUE if they match
+
+ // Flag handling routines
+ virtual nsresult SetKeyFlag(nsMsgKey key, bool set, uint32_t flag,
+ nsIDBChangeListener *instigator = NULL);
+ virtual nsresult SetMsgHdrFlag(nsIMsgDBHdr *msgHdr, bool set, uint32_t flag,
+ nsIDBChangeListener *instigator);
+
+ virtual bool SetHdrFlag(nsIMsgDBHdr *, bool bSet, nsMsgMessageFlagType flag);
+ virtual bool SetHdrReadFlag(nsIMsgDBHdr *, bool pRead);
+ virtual uint32_t GetStatusFlags(nsIMsgDBHdr *msgHdr, uint32_t origFlags);
+ // helper function which doesn't involve thread object
+
+ virtual nsresult RemoveHeaderFromDB(nsMsgHdr *msgHdr);
+ virtual nsresult RemoveHeaderFromThread(nsMsgHdr *msgHdr);
+ virtual nsresult AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr);
+
+ nsCOMPtr <nsICollation> m_collationKeyGenerator;
+ nsCOMPtr <nsIMimeConverter> m_mimeConverter;
+ nsCOMPtr <nsIMsgRetentionSettings> m_retentionSettings;
+ nsCOMPtr <nsIMsgDownloadSettings> m_downloadSettings;
+
+ nsresult PurgeMessagesOlderThan(uint32_t daysToKeepHdrs,
+ bool applyToFlaggedMessages,
+ nsIMutableArray *hdrsToDelete);
+ nsresult PurgeExcessMessages(uint32_t numHeadersToKeep,
+ bool applyToFlaggedMessages,
+ nsIMutableArray *hdrsToDelete);
+
+ // mdb bookkeeping stuff
+ virtual nsresult InitExistingDB();
+ virtual nsresult InitNewDB();
+ virtual nsresult InitMDBInfo();
+
+ nsCOMPtr <nsIMsgFolder> m_folder;
+ nsDBFolderInfo *m_dbFolderInfo;
+ nsMsgKey m_nextPseudoMsgKey;
+ nsIMdbEnv *m_mdbEnv; // to be used in all the db calls.
+ nsIMdbStore *m_mdbStore;
+ nsIMdbTable *m_mdbAllMsgHeadersTable;
+ nsIMdbTable *m_mdbAllThreadsTable;
+
+ // Used for asynchronous db opens. If non-null, we're still opening
+ // the underlying mork database. If null, the db has been completely opened.
+ nsCOMPtr<nsIMdbThumb> m_thumb;
+ // used to remember the args to Open for async open.
+ bool m_create;
+ bool m_leaveInvalidDB;
+
+ nsCString m_dbName;
+ nsTArray<nsMsgKey> m_newSet; // new messages since last open.
+ bool m_mdbTokensInitialized;
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> > m_ChangeListeners;
+ mdb_token m_hdrRowScopeToken;
+ mdb_token m_threadRowScopeToken;
+ mdb_token m_hdrTableKindToken;
+ mdb_token m_threadTableKindToken;
+ mdb_token m_allThreadsTableKindToken;
+ mdb_token m_subjectColumnToken;
+ mdb_token m_senderColumnToken;
+ mdb_token m_messageIdColumnToken;
+ mdb_token m_referencesColumnToken;
+ mdb_token m_recipientsColumnToken;
+ mdb_token m_dateColumnToken;
+ mdb_token m_messageSizeColumnToken;
+ mdb_token m_flagsColumnToken;
+ mdb_token m_priorityColumnToken;
+ mdb_token m_labelColumnToken;
+ mdb_token m_statusOffsetColumnToken;
+ mdb_token m_numLinesColumnToken;
+ mdb_token m_ccListColumnToken;
+ mdb_token m_bccListColumnToken;
+ mdb_token m_threadFlagsColumnToken;
+ mdb_token m_threadIdColumnToken;
+ mdb_token m_threadChildrenColumnToken;
+ mdb_token m_threadUnreadChildrenColumnToken;
+ mdb_token m_messageThreadIdColumnToken;
+ mdb_token m_threadSubjectColumnToken;
+ mdb_token m_messageCharSetColumnToken;
+ mdb_token m_threadParentColumnToken;
+ mdb_token m_threadRootKeyColumnToken;
+ mdb_token m_threadNewestMsgDateColumnToken;
+ mdb_token m_offlineMsgOffsetColumnToken;
+ mdb_token m_offlineMessageSizeColumnToken;
+
+ // header caching stuff - MRU headers, keeps them around in memory
+ nsresult AddHdrToCache(nsIMsgDBHdr *hdr, nsMsgKey key);
+ nsresult ClearHdrCache(bool reInit);
+ nsresult RemoveHdrFromCache(nsIMsgDBHdr *hdr, nsMsgKey key);
+ // all headers currently instantiated, doesn't hold refs
+ // these get added when msg hdrs get constructed, and removed when they get destroyed.
+ nsresult GetHdrFromUseCache(nsMsgKey key, nsIMsgDBHdr* *result);
+ nsresult AddHdrToUseCache(nsIMsgDBHdr *hdr, nsMsgKey key);
+ nsresult ClearUseHdrCache();
+ nsresult RemoveHdrFromUseCache(nsIMsgDBHdr *hdr, nsMsgKey key);
+
+ // not-reference holding array of threads we've handed out.
+ // If a db goes away, it will clean up the outstanding threads.
+ // We use an nsTArray because we don't expect to ever have very many
+ // of these, rarely more than 5.
+ nsTArray<nsMsgThread *> m_threads;
+ // Clear outstanding thread objects
+ void ClearThreads();
+ nsMsgThread *FindExistingThread(nsMsgKey threadId);
+
+ mdb_pos FindInsertIndexInSortedTable(nsIMdbTable *table, mdb_id idToInsert);
+
+ void ClearCachedObjects(bool dbGoingAway);
+ void ClearEnumerators();
+ // all instantiated headers, but doesn't hold refs.
+ PLDHashTable *m_headersInUse;
+ static PLDHashNumber HashKey(const void* aKey);
+ static bool MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey);
+ static void MoveEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, PLDHashEntryHdr* aTo);
+ static void ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry);
+ static PLDHashTableOps gMsgDBHashTableOps;
+ struct MsgHdrHashElement : public PLDHashEntryHdr {
+ nsMsgKey mKey;
+ nsIMsgDBHdr *mHdr;
+ };
+ PLDHashTable *m_cachedHeaders;
+ bool m_bCacheHeaders;
+ nsMsgKey m_cachedThreadId;
+ nsCOMPtr <nsIMsgThread> m_cachedThread;
+ nsCOMPtr<nsIMdbFactory> mMdbFactory;
+
+ // Message reference hash table
+ static PLDHashTableOps gRefHashTableOps;
+ struct RefHashElement : public PLDHashEntryHdr {
+ const char *mRef; // Hash entry key, must come first
+ nsMsgKey mThreadId;
+ uint32_t mCount;
+ };
+ PLDHashTable *m_msgReferences;
+ nsresult GetRefFromHash(nsCString &reference, nsMsgKey *threadId);
+ nsresult AddRefToHash(nsCString &reference, nsMsgKey threadId);
+ nsresult AddMsgRefsToHash(nsIMsgDBHdr *msgHdr);
+ nsresult RemoveRefFromHash(nsCString &reference);
+ nsresult RemoveMsgRefsFromHash(nsIMsgDBHdr *msgHdr);
+ nsresult InitRefHash();
+
+ // not-reference holding array of enumerators we've handed out.
+ // If a db goes away, it will clean up the outstanding enumerators.
+ nsTArray<nsMsgDBEnumerator *> m_enumerators;
+
+ // Memory reporter details
+public:
+ static size_t HeaderHashSizeOf(PLDHashEntryHdr *hdr,
+ mozilla::MallocSizeOf aMallocSizeOf,
+ void *arg);
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+private:
+ uint32_t m_cacheSize;
+ RefPtr<mozilla::mailnews::MsgDBReporter> mMemReporter;
+};
+
+class nsMsgRetentionSettings : public nsIMsgRetentionSettings
+{
+public:
+ nsMsgRetentionSettings();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGRETENTIONSETTINGS
+protected:
+ virtual ~nsMsgRetentionSettings();
+ nsMsgRetainByPreference m_retainByPreference;
+ uint32_t m_daysToKeepHdrs;
+ uint32_t m_numHeadersToKeep;
+ bool m_useServerDefaults;
+ bool m_cleanupBodiesByDays;
+ uint32_t m_daysToKeepBodies;
+ bool m_applyToFlaggedMessages;
+};
+
+class nsMsgDownloadSettings : public nsIMsgDownloadSettings
+{
+public:
+ nsMsgDownloadSettings();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGDOWNLOADSETTINGS
+protected:
+ virtual ~nsMsgDownloadSettings();
+ bool m_useServerDefaults;
+ bool m_downloadUnreadOnly;
+ bool m_downloadByDate;
+ int32_t m_ageLimitOfMsgsToDownload;
+};
+
+#endif
diff --git a/mailnews/db/msgdb/public/nsMsgHdr.h b/mailnews/db/msgdb/public/nsMsgHdr.h
new file mode 100644
index 000000000..23e2d083f
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsMsgHdr.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgHdr_H
+#define _nsMsgHdr_H
+
+#include "mozilla/MemoryReporting.h"
+#include "nsIMsgHdr.h"
+#include "nsStringGlue.h"
+#include "MailNewsTypes.h"
+#include "mdb.h"
+#include "nsTArray.h"
+
+class nsMsgDatabase;
+class nsCString;
+class nsIMsgThread;
+
+class nsMsgHdr : public nsIMsgDBHdr {
+public:
+ NS_DECL_NSIMSGDBHDR
+ friend class nsMsgDatabase;
+ friend class nsImapMailDatabase;
+ friend class nsMsgPropertyEnumerator;
+ friend class nsMsgThread;
+
+ ////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
+ // nsMsgHdr methods:
+ nsMsgHdr(nsMsgDatabase *db, nsIMdbRow *dbRow);
+
+ NS_DECL_ISUPPORTS
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOfFun) const
+ {
+ return m_references.ShallowSizeOfExcludingThis(aMallocSizeOfFun);
+ }
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOfFun) const
+ {
+ return aMallocSizeOfFun(this) + SizeOfExcludingThis(aMallocSizeOfFun);
+ }
+
+protected:
+ nsIMdbRow* GetMDBRow() { return m_mdbRow; }
+ void ReleaseMDBRow() { NS_IF_RELEASE(m_mdbRow); }
+ nsMsgDatabase* GetMdb() { return m_mdb; }
+ void ClearCachedValues() { m_initedValues = 0; }
+
+ virtual nsresult GetRawFlags(uint32_t *result);
+
+ bool IsParentOf(nsIMsgDBHdr *possibleChild);
+ bool IsAncestorOf(nsIMsgDBHdr *possibleChild);
+
+private:
+ virtual ~nsMsgHdr();
+
+ void Init();
+ virtual nsresult InitFlags();
+ virtual nsresult InitCachedValues();
+
+ bool IsAncestorKilled(uint32_t ancestorsToCheck);
+ void ReparentInThread(nsIMsgThread *thread);
+
+ nsresult SetStringColumn(const char *str, mdb_token token);
+ nsresult SetUInt32Column(uint32_t value, mdb_token token);
+ nsresult GetUInt32Column(mdb_token token, uint32_t *pvalue, uint32_t defaultValue = 0);
+ nsresult SetUInt64Column(uint64_t value, mdb_token token);
+ nsresult GetUInt64Column(mdb_token token, uint64_t *pvalue, uint64_t defaultValue = 0);
+
+ // reference and threading stuff.
+ nsresult ParseReferences(const char *references);
+ const char* GetNextReference(const char *startNextRef, nsCString &reference,
+ bool acceptNonDelimitedReferences);
+
+ nsMsgKey m_threadId;
+ nsMsgKey m_messageKey; // news: article number, local mail: key, imap: uid...
+ nsMsgKey m_threadParent; // message this is a reply to, in thread.
+ PRTime m_date;
+ uint32_t m_messageSize; // lines for news articles, bytes for mail messages
+ uint32_t m_statusOffset; // offset in a local mail message of the x-mozilla-status header
+ uint32_t m_flags;
+ // avoid parsing references every time we want one
+ nsTArray<nsCString> m_references;
+ nsMsgPriorityValue m_priority;
+
+ // nsMsgHdrs will have to know what db and row they belong to, since they are really
+ // just a wrapper around the msg row in the mdb. This could cause problems,
+ // though I hope not.
+ nsMsgDatabase *m_mdb;
+ nsIMdbRow *m_mdbRow;
+ uint32_t m_initedValues;
+};
+
+#endif
+
diff --git a/mailnews/db/msgdb/public/nsMsgThread.h b/mailnews/db/msgdb/public/nsMsgThread.h
new file mode 100644
index 000000000..62d303bc8
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsMsgThread.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgThread_H
+#define _nsMsgThread_H
+
+#include "nsAutoPtr.h"
+#include "nsIMsgThread.h"
+#include "nsStringGlue.h"
+#include "MailNewsTypes.h"
+#include "mdb.h"
+
+class nsIMdbTable;
+class nsIMsgDBHdr;
+class nsMsgDatabase;
+
+class nsMsgThread : public nsIMsgThread {
+public:
+ nsMsgThread();
+ nsMsgThread(nsMsgDatabase *db, nsIMdbTable *table);
+
+ friend class nsMsgThreadEnumerator;
+ friend class nsMsgDatabase;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGTHREAD
+
+ RefPtr<nsMsgDatabase> m_mdbDB;
+
+protected:
+ virtual ~nsMsgThread();
+
+ void Init();
+ void Clear();
+ virtual nsresult InitCachedValues();
+ nsresult ChangeChildCount(int32_t delta);
+ nsresult ChangeUnreadChildCount(int32_t delta);
+ nsresult RemoveChild(nsMsgKey msgKey);
+ nsresult SetThreadRootKey(nsMsgKey threadRootKey);
+ nsresult GetChildHdrForKey(nsMsgKey desiredKey,
+ nsIMsgDBHdr **result, int32_t *resultIndex);
+ nsresult RerootThread(nsIMsgDBHdr *newParentOfOldRoot, nsIMsgDBHdr *oldRoot, nsIDBChangeAnnouncer *announcer);
+ nsresult ReparentChildrenOf(nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeAnnouncer *announcer);
+
+ nsresult ReparentNonReferenceChildrenOf(nsIMsgDBHdr *topLevelHdr, nsMsgKey newParentKey,
+ nsIDBChangeAnnouncer *announcer);
+ nsresult ReparentMsgsWithInvalidParent(uint32_t numChildren, nsMsgKey threadParentKey);
+
+ nsMsgKey m_threadKey;
+ uint32_t m_numChildren;
+ uint32_t m_numUnreadChildren;
+ uint32_t m_flags;
+ nsCOMPtr<nsIMdbTable> m_mdbTable;
+ nsCOMPtr<nsIMdbRow> m_metaRow;
+ bool m_cachedValuesInitialized;
+ nsMsgKey m_threadRootKey;
+ uint32_t m_newestMsgDate;
+};
+
+#endif
+
diff --git a/mailnews/db/msgdb/public/nsNewsDatabase.h b/mailnews/db/msgdb/public/nsNewsDatabase.h
new file mode 100644
index 000000000..33e225913
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsNewsDatabase.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsNewsDatabase_H_
+#define _nsNewsDatabase_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgDatabase.h"
+#include "nsINewsDatabase.h"
+#include "nsTArray.h"
+
+class nsIDBChangeListener;
+class MSG_RetrieveArtInfo;
+class MSG_PurgeInfo;
+// news group database
+
+class nsNewsDatabase : public nsMsgDatabase , public nsINewsDatabase
+{
+public:
+ nsNewsDatabase();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINEWSDATABASE
+
+ NS_IMETHOD Close(bool forceCommit) override;
+ NS_IMETHOD ForceClosed() override;
+ NS_IMETHOD Commit(nsMsgDBCommit commitType) override;
+ virtual uint32_t GetCurVersion() override;
+
+ // methods to get and set docsets for ids.
+ NS_IMETHOD IsRead(nsMsgKey key, bool *pRead) override;
+ virtual nsresult IsHeaderRead(nsIMsgDBHdr *msgHdr, bool *pRead) override;
+
+ NS_IMETHOD GetHighWaterArticleNum(nsMsgKey *key) override;
+ NS_IMETHOD GetLowWaterArticleNum(nsMsgKey *key) override;
+ NS_IMETHOD MarkAllRead(uint32_t *aNumMarked, nsMsgKey **thoseMarked) override;
+
+ virtual nsresult ExpireUpTo(nsMsgKey expireKey);
+ virtual nsresult ExpireRange(nsMsgKey startRange, nsMsgKey endRange);
+
+ virtual bool SetHdrReadFlag(nsIMsgDBHdr *msgHdr, bool bRead) override;
+
+ virtual nsresult AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr) override;
+ nsresult SyncWithReadSet();
+
+ NS_IMETHOD GetDefaultViewFlags(nsMsgViewFlagsTypeValue *aDefaultViewFlags) override;
+ NS_IMETHOD GetDefaultSortType(nsMsgViewSortTypeValue *aDefaultSortType) override;
+ NS_IMETHOD GetDefaultSortOrder(nsMsgViewSortOrderValue *aDefaultSortOrder) override;
+
+protected:
+ virtual ~nsNewsDatabase();
+ // this is owned by the nsNewsFolder, which lives longer than the db.
+ nsMsgKeySet *m_readSet;
+};
+
+#endif
diff --git a/mailnews/db/msgdb/src/moz.build b/mailnews/db/msgdb/src/moz.build
new file mode 100644
index 000000000..07f537462
--- /dev/null
+++ b/mailnews/db/msgdb/src/moz.build
@@ -0,0 +1,18 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsDBFolderInfo.cpp',
+ 'nsImapMailDatabase.cpp',
+ 'nsMailDatabase.cpp',
+ 'nsMsgDatabase.cpp',
+ 'nsMsgHdr.cpp',
+ 'nsMsgOfflineImapOperation.cpp',
+ 'nsMsgThread.cpp',
+ 'nsNewsDatabase.cpp',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/db/msgdb/src/nsDBFolderInfo.cpp b/mailnews/db/msgdb/src/nsDBFolderInfo.cpp
new file mode 100644
index 000000000..0260738d0
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsDBFolderInfo.cpp
@@ -0,0 +1,977 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsDBFolderInfo.h"
+#include "nsMsgDatabase.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIMsgDBView.h"
+#include "nsServiceManagerUtils.h"
+#include "nsImapCore.h"
+#include "mozilla/Services.h"
+
+static const char *kDBFolderInfoScope = "ns:msg:db:row:scope:dbfolderinfo:all";
+static const char *kDBFolderInfoTableKind = "ns:msg:db:table:kind:dbfolderinfo";
+
+struct mdbOid gDBFolderInfoOID;
+
+static const char * kNumMessagesColumnName ="numMsgs";
+// have to leave this as numNewMsgs even though it's numUnread Msgs
+static const char * kNumUnreadMessagesColumnName = "numNewMsgs";
+static const char * kFlagsColumnName = "flags";
+static const char * kFolderSizeColumnName = "folderSize";
+static const char * kExpungedBytesColumnName = "expungedBytes";
+static const char * kFolderDateColumnName = "folderDate";
+static const char * kHighWaterMessageKeyColumnName = "highWaterKey";
+
+static const char * kImapUidValidityColumnName = "UIDValidity";
+static const char * kTotalPendingMessagesColumnName = "totPendingMsgs";
+static const char * kUnreadPendingMessagesColumnName = "unreadPendingMsgs";
+static const char * kMailboxNameColumnName = "mailboxName";
+static const char * kKnownArtsSetColumnName = "knownArts";
+static const char * kExpiredMarkColumnName = "expiredMark";
+static const char * kVersionColumnName = "version";
+static const char * kCharacterSetColumnName = "charSet";
+static const char * kCharacterSetOverrideColumnName = "charSetOverride";
+static const char * kLocaleColumnName = "locale";
+
+
+#define kMAILNEWS_VIEW_DEFAULT_CHARSET "mailnews.view_default_charset"
+#define kMAILNEWS_DEFAULT_CHARSET_OVERRIDE "mailnews.force_charset_override"
+static nsCString* gDefaultCharacterSet = nullptr;
+static bool gDefaultCharacterOverride;
+static nsIObserver *gFolderCharsetObserver = nullptr;
+
+// observer for charset related preference notification
+class nsFolderCharsetObserver : public nsIObserver {
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsFolderCharsetObserver() { }
+private:
+ virtual ~nsFolderCharsetObserver() {}
+};
+
+NS_IMPL_ISUPPORTS(nsFolderCharsetObserver, nsIObserver)
+
+NS_IMETHODIMP nsFolderCharsetObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID))
+ {
+ nsDependentString prefName(someData);
+
+ if (prefName.EqualsLiteral(kMAILNEWS_VIEW_DEFAULT_CHARSET))
+ {
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ rv = prefBranch->GetComplexValue(kMAILNEWS_VIEW_DEFAULT_CHARSET,
+ NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString ucsval;
+ pls->ToString(getter_Copies(ucsval));
+ if (!ucsval.IsEmpty())
+ {
+ if (gDefaultCharacterSet)
+ CopyUTF16toUTF8(ucsval, *gDefaultCharacterSet);
+ }
+ }
+ }
+ else if (prefName.EqualsLiteral(kMAILNEWS_DEFAULT_CHARSET_OVERRIDE))
+ {
+ rv = prefBranch->GetBoolPref(kMAILNEWS_DEFAULT_CHARSET_OVERRIDE, &gDefaultCharacterOverride);
+ }
+ }
+ else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID))
+ {
+ rv = prefBranch->RemoveObserver(kMAILNEWS_VIEW_DEFAULT_CHARSET, this);
+ rv = prefBranch->RemoveObserver(kMAILNEWS_DEFAULT_CHARSET_OVERRIDE, this);
+ NS_IF_RELEASE(gFolderCharsetObserver);
+ delete gDefaultCharacterSet;
+ gDefaultCharacterSet = nullptr;
+ }
+ return rv;
+}
+
+
+NS_IMPL_ADDREF(nsDBFolderInfo)
+NS_IMPL_RELEASE(nsDBFolderInfo)
+
+NS_IMETHODIMP
+nsDBFolderInfo::QueryInterface(REFNSIID iid, void** result)
+{
+ if (! result)
+ return NS_ERROR_NULL_POINTER;
+
+ *result = nullptr;
+ if(iid.Equals(NS_GET_IID(nsIDBFolderInfo)) ||
+ iid.Equals(NS_GET_IID(nsISupports)))
+ {
+ *result = static_cast<nsIDBFolderInfo*>(this);
+ AddRef();
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+}
+
+
+nsDBFolderInfo::nsDBFolderInfo(nsMsgDatabase *mdb)
+ : m_flags(0),
+ m_expiredMark(0),
+ m_expiredMarkColumnToken(0)
+{
+ m_mdbTable = NULL;
+ m_mdbRow = NULL;
+ m_version = 1; // for upgrading...
+ m_IMAPHierarchySeparator = 0; // imap path separator
+ // mail only (for now)
+ m_folderSize = 0;
+ m_folderDate = 0;
+ m_expungedBytes = 0; // sum of size of deleted messages in folder
+ m_highWaterMessageKey = 0;
+
+ m_numUnreadMessages = 0;
+ m_numMessages = 0;
+ // IMAP only
+ m_ImapUidValidity = kUidUnknown;
+ m_totalPendingMessages =0;
+ m_unreadPendingMessages = 0;
+
+ m_mdbTokensInitialized = false;
+ m_charSetOverride = false;
+
+ if (!gFolderCharsetObserver)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ rv = prefBranch->GetComplexValue(kMAILNEWS_VIEW_DEFAULT_CHARSET,
+ NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString ucsval;
+ pls->ToString(getter_Copies(ucsval));
+ if (!ucsval.IsEmpty())
+ {
+ if (!gDefaultCharacterSet)
+ gDefaultCharacterSet = new nsCString;
+
+ if (gDefaultCharacterSet)
+ CopyUTF16toUTF8(ucsval, *gDefaultCharacterSet);
+ }
+ }
+ rv = prefBranch->GetBoolPref(kMAILNEWS_DEFAULT_CHARSET_OVERRIDE, &gDefaultCharacterOverride);
+
+ gFolderCharsetObserver = new nsFolderCharsetObserver();
+ NS_ASSERTION(gFolderCharsetObserver, "failed to create observer");
+
+ // register prefs callbacks
+ if (gFolderCharsetObserver)
+ {
+ NS_ADDREF(gFolderCharsetObserver);
+ rv = prefBranch->AddObserver(kMAILNEWS_VIEW_DEFAULT_CHARSET, gFolderCharsetObserver, false);
+ rv = prefBranch->AddObserver(kMAILNEWS_DEFAULT_CHARSET_OVERRIDE, gFolderCharsetObserver, false);
+
+ // also register for shutdown
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ {
+ rv = observerService->AddObserver(gFolderCharsetObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ }
+ }
+ }
+ }
+
+ m_mdb = mdb;
+ if (mdb)
+ {
+ nsresult err;
+
+ // mdb->AddRef();
+ err = m_mdb->GetStore()->StringToToken(mdb->GetEnv(), kDBFolderInfoScope, &m_rowScopeToken);
+ if (NS_SUCCEEDED(err))
+ {
+ err = m_mdb->GetStore()->StringToToken(mdb->GetEnv(), kDBFolderInfoTableKind, &m_tableKindToken);
+ if (NS_SUCCEEDED(err))
+ {
+ gDBFolderInfoOID.mOid_Scope = m_rowScopeToken;
+ gDBFolderInfoOID.mOid_Id = 1;
+ }
+ }
+ InitMDBInfo();
+ }
+}
+
+nsDBFolderInfo::~nsDBFolderInfo()
+{
+ // nsMsgDatabase strictly owns nsDBFolderInfo, so don't ref-count db.
+ ReleaseExternalReferences();
+}
+
+// Release any objects we're holding onto. This needs to be safe
+// to call multiple times.
+void nsDBFolderInfo::ReleaseExternalReferences()
+{
+ if (m_mdb)
+ {
+ if (m_mdbTable)
+ {
+ NS_RELEASE(m_mdbTable);
+ m_mdbTable = nullptr;
+ }
+ if (m_mdbRow)
+ {
+ NS_RELEASE(m_mdbRow);
+ m_mdbRow = nullptr;
+ }
+ m_mdb = nullptr;
+ }
+}
+
+// this routine sets up a new db to know about the dbFolderInfo stuff...
+nsresult nsDBFolderInfo::AddToNewMDB()
+{
+ nsresult ret = NS_OK;
+ if (m_mdb && m_mdb->GetStore())
+ {
+ nsIMdbStore *store = m_mdb->GetStore();
+ // create the unique table for the dbFolderInfo.
+ nsresult err = store->NewTable(m_mdb->GetEnv(), m_rowScopeToken,
+ m_tableKindToken, true, nullptr, &m_mdbTable);
+
+ // create the singleton row for the dbFolderInfo.
+ err = store->NewRowWithOid(m_mdb->GetEnv(),
+ &gDBFolderInfoOID, &m_mdbRow);
+
+ // add the row to the singleton table.
+ if (m_mdbRow && NS_SUCCEEDED(err))
+ err = m_mdbTable->AddRow(m_mdb->GetEnv(), m_mdbRow);
+
+ ret = err; // what are we going to do about nsresult's?
+ }
+ return ret;
+}
+
+nsresult nsDBFolderInfo::InitFromExistingDB()
+{
+ nsresult ret = NS_OK;
+ if (m_mdb && m_mdb->GetStore())
+ {
+ nsIMdbStore *store = m_mdb->GetStore();
+ if (store)
+ {
+ mdb_pos rowPos;
+ mdb_count outTableCount; // current number of such tables
+ mdb_bool mustBeUnique; // whether port can hold only one of these
+ mdb_bool hasOid;
+ ret = store->GetTableKind(m_mdb->GetEnv(), m_rowScopeToken, m_tableKindToken, &outTableCount,
+ &mustBeUnique, &m_mdbTable);
+ // NS_ASSERTION(mustBeUnique && outTableCount == 1, "only one global db info allowed");
+
+ if (m_mdbTable)
+ {
+ // find singleton row for global info.
+ ret = m_mdbTable->HasOid(m_mdb->GetEnv(), &gDBFolderInfoOID, &hasOid);
+ if (NS_SUCCEEDED(ret))
+ {
+ nsIMdbTableRowCursor *rowCursor;
+ rowPos = -1;
+ ret= m_mdbTable->GetTableRowCursor(m_mdb->GetEnv(), rowPos, &rowCursor);
+ if (NS_SUCCEEDED(ret))
+ {
+ ret = rowCursor->NextRow(m_mdb->GetEnv(), &m_mdbRow, &rowPos);
+ NS_RELEASE(rowCursor);
+ if (!m_mdbRow)
+ ret = NS_ERROR_FAILURE;
+ if (NS_SUCCEEDED(ret))
+ LoadMemberVariables();
+ }
+ }
+ }
+ else
+ ret = NS_ERROR_FAILURE;
+ }
+ }
+ return ret;
+}
+
+nsresult nsDBFolderInfo::InitMDBInfo()
+{
+ nsresult ret = NS_OK;
+ if (!m_mdbTokensInitialized && m_mdb && m_mdb->GetStore())
+ {
+ nsIMdbStore *store = m_mdb->GetStore();
+ nsIMdbEnv *env = m_mdb->GetEnv();
+
+ store->StringToToken(env, kNumMessagesColumnName, &m_numMessagesColumnToken);
+ store->StringToToken(env, kNumUnreadMessagesColumnName, &m_numUnreadMessagesColumnToken);
+ store->StringToToken(env, kFlagsColumnName, &m_flagsColumnToken);
+ store->StringToToken(env, kFolderSizeColumnName, &m_folderSizeColumnToken);
+ store->StringToToken(env, kExpungedBytesColumnName, &m_expungedBytesColumnToken);
+ store->StringToToken(env, kFolderDateColumnName, &m_folderDateColumnToken);
+
+ store->StringToToken(env, kHighWaterMessageKeyColumnName, &m_highWaterMessageKeyColumnToken);
+ store->StringToToken(env, kMailboxNameColumnName, &m_mailboxNameColumnToken);
+
+ store->StringToToken(env, kImapUidValidityColumnName, &m_imapUidValidityColumnToken);
+ store->StringToToken(env, kTotalPendingMessagesColumnName, &m_totalPendingMessagesColumnToken);
+ store->StringToToken(env, kUnreadPendingMessagesColumnName, &m_unreadPendingMessagesColumnToken);
+ store->StringToToken(env, kExpiredMarkColumnName, &m_expiredMarkColumnToken);
+ store->StringToToken(env, kVersionColumnName, &m_versionColumnToken);
+ m_mdbTokensInitialized = true;
+ }
+
+ return ret;
+}
+
+nsresult nsDBFolderInfo::LoadMemberVariables()
+{
+ // it's really not an error for these properties to not exist...
+ GetInt32PropertyWithToken(m_numMessagesColumnToken, m_numMessages);
+ GetInt32PropertyWithToken(m_numUnreadMessagesColumnToken, m_numUnreadMessages);
+ GetInt32PropertyWithToken(m_flagsColumnToken, m_flags);
+ GetInt64PropertyWithToken(m_folderSizeColumnToken, m_folderSize);
+ GetUint32PropertyWithToken(m_folderDateColumnToken, m_folderDate);
+ GetInt32PropertyWithToken(m_imapUidValidityColumnToken, m_ImapUidValidity, kUidUnknown);
+ GetUint32PropertyWithToken(m_expiredMarkColumnToken, m_expiredMark);
+ GetInt64PropertyWithToken(m_expungedBytesColumnToken, m_expungedBytes);
+ GetUint32PropertyWithToken(m_highWaterMessageKeyColumnToken, m_highWaterMessageKey);
+ int32_t version;
+
+ GetInt32PropertyWithToken(m_versionColumnToken, version);
+ m_version = (uint16_t) version;
+ m_charSetOverride = gDefaultCharacterOverride;
+ uint32_t propertyValue;
+ nsresult rv = GetUint32Property(kCharacterSetOverrideColumnName, gDefaultCharacterOverride, &propertyValue);
+ if (NS_SUCCEEDED(rv))
+ m_charSetOverride = propertyValue;
+
+ m_mdb->GetProperty(m_mdbRow, kCharacterSetColumnName, getter_Copies(m_charSet));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetVersion(uint32_t version)
+{
+ m_version = version;
+ return SetUint32PropertyWithToken(m_versionColumnToken, (uint32_t) m_version);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetVersion(uint32_t *version)
+{
+ *version = m_version;
+ return NS_OK;
+}
+
+
+nsresult nsDBFolderInfo::AdjustHighWater(nsMsgKey highWater, bool force)
+{
+ if (force || m_highWaterMessageKey < highWater)
+ {
+ m_highWaterMessageKey = highWater;
+ SetUint32PropertyWithToken(m_highWaterMessageKeyColumnToken, highWater);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetHighWater(nsMsgKey highWater)
+{
+ return AdjustHighWater(highWater, true);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::OnKeyAdded(nsMsgKey aNewKey)
+{
+ return AdjustHighWater(aNewKey, false);
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetFolderSize(int64_t *size)
+{
+ NS_ENSURE_ARG_POINTER(size);
+ *size = m_folderSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetFolderSize(int64_t size)
+{
+ m_folderSize = size;
+ return SetInt64Property(kFolderSizeColumnName, m_folderSize);
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetFolderDate(uint32_t *folderDate)
+{
+ NS_ENSURE_ARG_POINTER(folderDate);
+ *folderDate = m_folderDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetFolderDate(uint32_t folderDate)
+{
+ m_folderDate = folderDate;
+ return SetUint32PropertyWithToken(m_folderDateColumnToken, folderDate);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetHighWater(nsMsgKey *result)
+{
+ // Sanity check highwater - if it gets too big, other code
+ // can fail. Look through last 100 messages to recalculate
+ // the highwater mark.
+ *result = m_highWaterMessageKey;
+ if (m_highWaterMessageKey > 0xFFFFFF00 && m_mdb)
+ {
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ nsresult rv = m_mdb->ReverseEnumerateMessages(getter_AddRefs(hdrs));
+ if (NS_FAILED(rv))
+ return rv;
+ bool hasMore = false;
+ nsCOMPtr<nsIMsgDBHdr> pHeader;
+ nsMsgKey recalculatedHighWater = 1;
+ int32_t i = 0;
+ while(i++ < 100 && NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore))
+ && hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ (void) hdrs->GetNext(getter_AddRefs(supports));
+ pHeader = do_QueryInterface(supports);
+ if (pHeader)
+ {
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ if (msgKey > recalculatedHighWater)
+ recalculatedHighWater = msgKey;
+ }
+ }
+ NS_ASSERTION(m_highWaterMessageKey >= recalculatedHighWater,
+ "highwater incorrect");
+ m_highWaterMessageKey = recalculatedHighWater;
+ }
+ *result = m_highWaterMessageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetExpiredMark(nsMsgKey expiredKey)
+{
+ m_expiredMark = expiredKey;
+ return SetUint32PropertyWithToken(m_expiredMarkColumnToken, expiredKey);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetExpiredMark(nsMsgKey *result)
+{
+ *result = m_expiredMark;
+ return NS_OK;
+}
+
+// The size of the argument depends on the maximum size of a single message
+NS_IMETHODIMP nsDBFolderInfo::ChangeExpungedBytes(int32_t delta)
+{
+ return SetExpungedBytes(m_expungedBytes + delta);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetMailboxName(const nsAString &newBoxName)
+{
+ return SetPropertyWithToken(m_mailboxNameColumnToken, newBoxName);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetMailboxName(nsAString &boxName)
+{
+ return GetPropertyWithToken(m_mailboxNameColumnToken, boxName);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::ChangeNumUnreadMessages(int32_t delta)
+{
+ m_numUnreadMessages += delta;
+ // m_numUnreadMessages can never be set to negative.
+ if (m_numUnreadMessages < 0)
+ {
+#ifdef DEBUG_bienvenu1
+ NS_ASSERTION(false, "Hardcoded assertion");
+#endif
+ m_numUnreadMessages = 0;
+ }
+ return SetUint32PropertyWithToken(m_numUnreadMessagesColumnToken, m_numUnreadMessages);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::ChangeNumMessages(int32_t delta)
+{
+ m_numMessages += delta;
+ // m_numMessages can never be set to negative.
+ if (m_numMessages < 0)
+ {
+#ifdef DEBUG_bienvenu
+ NS_ASSERTION(false, "num messages can't be < 0");
+#endif
+ m_numMessages = 0;
+ }
+ return SetUint32PropertyWithToken(m_numMessagesColumnToken, m_numMessages);
+}
+
+
+NS_IMETHODIMP nsDBFolderInfo::GetNumUnreadMessages(int32_t *result)
+{
+ *result = m_numUnreadMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetNumUnreadMessages(int32_t numUnreadMessages)
+{
+ m_numUnreadMessages = numUnreadMessages;
+ return SetUint32PropertyWithToken(m_numUnreadMessagesColumnToken, m_numUnreadMessages);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetNumMessages(int32_t *result)
+{
+ *result = m_numMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetNumMessages(int32_t numMessages)
+{
+ m_numMessages = numMessages;
+ return SetUint32PropertyWithToken(m_numMessagesColumnToken, m_numMessages);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetExpungedBytes(int64_t *result)
+{
+ *result = m_expungedBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetExpungedBytes(int64_t expungedBytes)
+{
+ m_expungedBytes = expungedBytes;
+ return SetInt64PropertyWithToken(m_expungedBytesColumnToken, m_expungedBytes);
+}
+
+
+NS_IMETHODIMP nsDBFolderInfo::GetFlags(int32_t *result)
+{
+ *result = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetFlags(int32_t flags)
+{
+ nsresult ret = NS_OK;
+
+ if (m_flags != flags)
+ {
+ NS_ASSERTION((m_flags & nsMsgFolderFlags::Inbox) == 0 || (flags & nsMsgFolderFlags::Inbox) != 0, "lost inbox flag");
+ m_flags = flags;
+ ret = SetInt32PropertyWithToken(m_flagsColumnToken, m_flags);
+ }
+ return ret;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::OrFlags(int32_t flags, int32_t *result)
+{
+ m_flags |= flags;
+ *result = m_flags;
+ return SetInt32PropertyWithToken(m_flagsColumnToken, m_flags);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::AndFlags(int32_t flags, int32_t *result)
+{
+ m_flags &= flags;
+ *result = m_flags;
+ return SetInt32PropertyWithToken(m_flagsColumnToken, m_flags);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetImapUidValidity(int32_t *result)
+{
+ *result = m_ImapUidValidity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetImapUidValidity(int32_t uidValidity)
+{
+ m_ImapUidValidity = uidValidity;
+ return SetUint32PropertyWithToken(m_imapUidValidityColumnToken, m_ImapUidValidity);
+}
+
+bool nsDBFolderInfo::TestFlag(int32_t flags)
+{
+ return (m_flags & flags) != 0;
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetCharacterSet(nsACString &result)
+{
+ if (!m_charSet.IsEmpty())
+ result.Assign(m_charSet);
+ else if (gDefaultCharacterSet)
+ result.Assign(*gDefaultCharacterSet);
+ else
+ result.Truncate();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetEffectiveCharacterSet(nsACString &result)
+{
+ result.Truncate();
+ if (NS_FAILED(GetCharProperty(kCharacterSetColumnName, result)) ||
+ (result.IsEmpty() && gDefaultCharacterSet))
+ result = *gDefaultCharacterSet;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetCharacterSet(const nsACString &charSet)
+{
+ m_charSet.Assign(charSet);
+ return SetCharProperty(kCharacterSetColumnName, charSet);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetCharacterSetOverride(bool *characterSetOverride)
+{
+ NS_ENSURE_ARG_POINTER(characterSetOverride);
+ *characterSetOverride = m_charSetOverride;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetCharacterSetOverride(bool characterSetOverride)
+{
+ m_charSetOverride = characterSetOverride;
+ return SetUint32Property(kCharacterSetOverrideColumnName, characterSetOverride);
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetLocale(nsAString &result)
+{
+ GetProperty(kLocaleColumnName, result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetLocale(const nsAString &locale)
+{
+ return SetProperty(kLocaleColumnName, locale);
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetImapTotalPendingMessages(int32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_totalPendingMessages;
+ return NS_OK;
+}
+
+void nsDBFolderInfo::ChangeImapTotalPendingMessages(int32_t delta)
+{
+ m_totalPendingMessages+=delta;
+ SetInt32PropertyWithToken(m_totalPendingMessagesColumnToken, m_totalPendingMessages);
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetImapUnreadPendingMessages(int32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_unreadPendingMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetImapUnreadPendingMessages(int32_t numUnreadPendingMessages)
+{
+ m_unreadPendingMessages = numUnreadPendingMessages;
+ return SetUint32PropertyWithToken(m_unreadPendingMessagesColumnToken, m_unreadPendingMessages);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetImapTotalPendingMessages(int32_t numTotalPendingMessages)
+{
+ m_totalPendingMessages = numTotalPendingMessages;
+ return SetUint32PropertyWithToken(m_totalPendingMessagesColumnToken, m_totalPendingMessages);
+}
+
+void nsDBFolderInfo::ChangeImapUnreadPendingMessages(int32_t delta)
+{
+ m_unreadPendingMessages+=delta;
+ SetInt32PropertyWithToken(m_unreadPendingMessagesColumnToken, m_unreadPendingMessages);
+}
+
+/* attribute nsMsgViewTypeValue viewType; */
+NS_IMETHODIMP nsDBFolderInfo::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+ uint32_t viewTypeValue;
+ nsresult rv = GetUint32Property("viewType", nsMsgViewType::eShowAllThreads, &viewTypeValue);
+ *aViewType = viewTypeValue;
+ return rv;
+}
+NS_IMETHODIMP nsDBFolderInfo::SetViewType(nsMsgViewTypeValue aViewType)
+{
+ return SetUint32Property("viewType", aViewType);
+}
+
+/* attribute nsMsgViewFlagsTypeValue viewFlags; */
+NS_IMETHODIMP nsDBFolderInfo::GetViewFlags(nsMsgViewFlagsTypeValue *aViewFlags)
+{
+ nsMsgViewFlagsTypeValue defaultViewFlags;
+ nsresult rv = m_mdb->GetDefaultViewFlags(&defaultViewFlags);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t viewFlagsValue;
+ rv = GetUint32Property("viewFlags", defaultViewFlags, &viewFlagsValue);
+ *aViewFlags = viewFlagsValue;
+ return rv;
+}
+NS_IMETHODIMP nsDBFolderInfo::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags)
+{
+ return SetUint32Property("viewFlags", aViewFlags);
+}
+
+/* attribute nsMsgViewSortTypeValue sortType; */
+NS_IMETHODIMP nsDBFolderInfo::GetSortType(nsMsgViewSortTypeValue *aSortType)
+{
+ nsMsgViewSortTypeValue defaultSortType;
+ nsresult rv = m_mdb->GetDefaultSortType(&defaultSortType);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t sortTypeValue;
+ rv = GetUint32Property("sortType", defaultSortType, &sortTypeValue);
+ *aSortType = sortTypeValue;
+ return rv;
+}
+NS_IMETHODIMP nsDBFolderInfo::SetSortType(nsMsgViewSortTypeValue aSortType)
+{
+ return SetUint32Property("sortType", aSortType);
+}
+
+/* attribute nsMsgViewSortOrderValue sortOrder; */
+NS_IMETHODIMP nsDBFolderInfo::GetSortOrder(nsMsgViewSortOrderValue *aSortOrder)
+{
+ nsMsgViewSortOrderValue defaultSortOrder;
+ nsresult rv = m_mdb->GetDefaultSortOrder(&defaultSortOrder);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t sortOrderValue;
+ rv = GetUint32Property("sortOrder", defaultSortOrder, &sortOrderValue);
+ *aSortOrder = sortOrderValue;
+ return rv;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetSortOrder(nsMsgViewSortOrderValue aSortOrder)
+{
+ return SetUint32Property("sortOrder", aSortOrder);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetKnownArtsSet(const char *newsArtSet)
+{
+ return m_mdb->SetProperty(m_mdbRow, kKnownArtsSetColumnName, newsArtSet);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetKnownArtsSet(char **newsArtSet)
+{
+ return m_mdb->GetProperty(m_mdbRow, kKnownArtsSetColumnName, newsArtSet);
+}
+
+// get arbitrary property, aka row cell value.
+NS_IMETHODIMP nsDBFolderInfo::GetProperty(const char *propertyName, nsAString &resultProperty)
+{
+ return m_mdb->GetPropertyAsNSString(m_mdbRow, propertyName, resultProperty);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetCharProperty(const char *aPropertyName,
+ const nsACString &aPropertyValue)
+{
+ return m_mdb->SetProperty(m_mdbRow, aPropertyName,
+ nsCString(aPropertyValue).get());
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetCharProperty(const char *propertyName,
+ nsACString &resultProperty)
+{
+ nsCString result;
+ nsresult rv = m_mdb->GetProperty(m_mdbRow, propertyName, getter_Copies(result));
+ if (NS_SUCCEEDED(rv))
+ resultProperty.Assign(result);
+ return rv;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetUint32Property(const char *propertyName, uint32_t propertyValue)
+{
+ return m_mdb->SetUint32Property(m_mdbRow, propertyName, propertyValue);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetInt64Property(const char *propertyName, int64_t propertyValue)
+{
+ return m_mdb->SetUint64Property(m_mdbRow, propertyName, (uint64_t) propertyValue);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetProperty(const char *propertyName, const nsAString &propertyStr)
+{
+ return m_mdb->SetPropertyFromNSString(m_mdbRow, propertyName, propertyStr);
+}
+
+nsresult nsDBFolderInfo::SetPropertyWithToken(mdb_token aProperty, const nsAString &propertyStr)
+{
+ return m_mdb->SetNSStringPropertyWithToken(m_mdbRow, aProperty, propertyStr);
+}
+
+nsresult nsDBFolderInfo::SetUint32PropertyWithToken(mdb_token aProperty, uint32_t propertyValue)
+{
+ return m_mdb->UInt32ToRowCellColumn(m_mdbRow, aProperty, propertyValue);
+}
+
+nsresult nsDBFolderInfo::SetInt64PropertyWithToken(mdb_token aProperty, int64_t propertyValue)
+{
+ return m_mdb->UInt64ToRowCellColumn(m_mdbRow, aProperty, (uint64_t) propertyValue);
+}
+
+nsresult nsDBFolderInfo::SetInt32PropertyWithToken(mdb_token aProperty, int32_t propertyValue)
+{
+ nsAutoString propertyStr;
+ propertyStr.AppendInt(propertyValue, 16);
+ return SetPropertyWithToken(aProperty, propertyStr);
+}
+
+nsresult nsDBFolderInfo::GetPropertyWithToken(mdb_token aProperty, nsAString &resultProperty)
+{
+ return m_mdb->RowCellColumnTonsString(m_mdbRow, aProperty, resultProperty);
+}
+
+nsresult nsDBFolderInfo::GetUint32PropertyWithToken(mdb_token aProperty, uint32_t &propertyValue, uint32_t defaultValue)
+{
+ return m_mdb->RowCellColumnToUInt32(m_mdbRow, aProperty, propertyValue, defaultValue);
+}
+
+nsresult nsDBFolderInfo::GetInt32PropertyWithToken(mdb_token aProperty, int32_t &propertyValue, int32_t defaultValue)
+{
+ return m_mdb->RowCellColumnToUInt32(m_mdbRow, aProperty, (uint32_t &) propertyValue, defaultValue);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetUint32Property(const char *propertyName, uint32_t defaultValue, uint32_t *propertyValue)
+{
+ return m_mdb->GetUint32Property(m_mdbRow, propertyName, propertyValue, defaultValue);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetInt64Property(const char *propertyName, int64_t defaultValue, int64_t *propertyValue)
+{
+ return m_mdb->GetUint64Property(m_mdbRow, propertyName, (uint64_t *) &propertyValue, defaultValue);
+}
+
+nsresult nsDBFolderInfo::GetInt64PropertyWithToken(mdb_token aProperty,
+ int64_t &propertyValue,
+ int64_t defaultValue)
+{
+ return m_mdb->RowCellColumnToUInt64(m_mdbRow, aProperty, (uint64_t *) &propertyValue, defaultValue);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetBooleanProperty(const char *propertyName, bool defaultValue, bool *propertyValue)
+{
+ uint32_t defaultUint32Value = (defaultValue) ? 1 : 0;
+ uint32_t returnValue;
+ nsresult rv = m_mdb->GetUint32Property(m_mdbRow, propertyName, &returnValue, defaultUint32Value);
+ *propertyValue = (returnValue != 0);
+ return rv;
+}
+NS_IMETHODIMP nsDBFolderInfo::SetBooleanProperty(const char *propertyName, bool propertyValue)
+{
+ return m_mdb->SetUint32Property(m_mdbRow, propertyName, propertyValue ? 1 : 0);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetFolderName(nsACString &folderName)
+{
+ return GetCharProperty("folderName", folderName);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetFolderName(const nsACString &folderName)
+{
+ return SetCharProperty("folderName", folderName);
+}
+
+class nsTransferDBFolderInfo : public nsDBFolderInfo
+{
+public:
+ nsTransferDBFolderInfo();
+ virtual ~nsTransferDBFolderInfo();
+ // parallel arrays of properties and values
+ nsTArray<nsCString> m_properties;
+ nsTArray<nsCString> m_values;
+};
+
+nsTransferDBFolderInfo::nsTransferDBFolderInfo() : nsDBFolderInfo(nullptr)
+{
+}
+
+nsTransferDBFolderInfo::~nsTransferDBFolderInfo()
+{
+}
+
+/* void GetTransferInfo (out nsIDBFolderInfo transferInfo); */
+NS_IMETHODIMP nsDBFolderInfo::GetTransferInfo(nsIDBFolderInfo **transferInfo)
+{
+ NS_ENSURE_ARG_POINTER(transferInfo);
+
+ nsTransferDBFolderInfo *newInfo = new nsTransferDBFolderInfo;
+ *transferInfo = newInfo;
+ NS_ADDREF(newInfo);
+
+ mdb_count numCells;
+ mdbYarn cellYarn;
+ mdb_column cellColumn;
+ char columnName[100];
+ mdbYarn cellName = { columnName, 0, sizeof(columnName), 0, 0, nullptr };
+
+ NS_ASSERTION(m_mdbRow, "null row in getTransferInfo");
+ m_mdbRow->GetCount(m_mdb->GetEnv(), &numCells);
+ // iterate over the cells in the dbfolderinfo remembering attribute names and values.
+ for (mdb_count cellIndex = 0; cellIndex < numCells; cellIndex++)
+ {
+ nsresult err = m_mdbRow->SeekCellYarn(m_mdb->GetEnv(), cellIndex, &cellColumn, nullptr);
+ if (NS_SUCCEEDED(err))
+ {
+ err = m_mdbRow->AliasCellYarn(m_mdb->GetEnv(), cellColumn, &cellYarn);
+ if (NS_SUCCEEDED(err))
+ {
+ m_mdb->GetStore()->TokenToString(m_mdb->GetEnv(), cellColumn, &cellName);
+ newInfo->m_values.AppendElement(Substring((const char *)cellYarn.mYarn_Buf,
+ (const char *) cellYarn.mYarn_Buf + cellYarn.mYarn_Fill));
+ newInfo->m_properties.AppendElement(Substring((const char *) cellName.mYarn_Buf,
+ (const char *) cellName.mYarn_Buf + cellName.mYarn_Fill));
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+/* void InitFromTransferInfo (in nsIDBFolderInfo transferInfo); */
+NS_IMETHODIMP nsDBFolderInfo::InitFromTransferInfo(nsIDBFolderInfo *aTransferInfo)
+{
+ NS_ENSURE_ARG(aTransferInfo);
+
+ nsTransferDBFolderInfo *transferInfo = static_cast<nsTransferDBFolderInfo *>(aTransferInfo);
+
+ for (uint32_t i = 0; i < transferInfo->m_values.Length(); i++)
+ SetCharProperty(transferInfo->m_properties[i].get(), transferInfo->m_values[i]);
+
+ LoadMemberVariables();
+ return NS_OK;
+}
+
diff --git a/mailnews/db/msgdb/src/nsImapMailDatabase.cpp b/mailnews/db/msgdb/src/nsImapMailDatabase.cpp
new file mode 100644
index 000000000..84865eb80
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsImapMailDatabase.cpp
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/stat.h>
+
+#include "msgCore.h"
+#include "nsImapMailDatabase.h"
+#include "nsDBFolderInfo.h"
+
+const char *kPendingHdrsScope = "ns:msg:db:row:scope:pending:all"; // scope for all offine ops table
+const char *kPendingHdrsTableKind = "ns:msg:db:table:kind:pending";
+struct mdbOid gAllPendingHdrsTableOID;
+
+nsImapMailDatabase::nsImapMailDatabase()
+{
+ m_mdbAllPendingHdrsTable = nullptr;
+}
+
+nsImapMailDatabase::~nsImapMailDatabase()
+{
+}
+
+NS_IMETHODIMP nsImapMailDatabase::GetSummaryValid(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (m_dbFolderInfo)
+ {
+ uint32_t version;
+ m_dbFolderInfo->GetVersion(&version);
+ *aResult = (GetCurVersion() == version);
+ }
+ else
+ *aResult = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::SetSummaryValid(bool valid)
+{
+ if (m_dbFolderInfo)
+ {
+ m_dbFolderInfo->SetVersion(valid ? GetCurVersion() : 0);
+ Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return NS_OK;
+}
+
+// IMAP does not set local file flags, override does nothing
+void nsImapMailDatabase::UpdateFolderFlag(nsIMsgDBHdr * /* msgHdr */, bool /* bSet */,
+ nsMsgMessageFlagType /* flag */, nsIOutputStream ** /* ppFileStream */)
+{
+}
+
+// We override this to avoid our parent class (nsMailDatabase)'s
+// grabbing of the folder semaphore, and bailing on failure.
+NS_IMETHODIMP nsImapMailDatabase::DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys, nsIDBChangeListener *instigator)
+{
+ return nsMsgDatabase::DeleteMessages(aNumKeys, nsMsgKeys, instigator);
+}
+
+// override so nsMailDatabase methods that deal with m_folderStream are *not* called
+NS_IMETHODIMP nsImapMailDatabase::StartBatch()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::EndBatch()
+{
+ return NS_OK;
+}
+
+nsresult nsImapMailDatabase::AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr)
+{
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::Offline && m_dbFolderInfo)
+ {
+ uint32_t size = 0;
+ (void)msgHdr->GetOfflineMessageSize(&size);
+ return m_dbFolderInfo->ChangeExpungedBytes (size);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::ForceClosed()
+{
+ m_mdbAllPendingHdrsTable = nullptr;
+ return nsMailDatabase::ForceClosed();
+}
+
+nsresult nsImapMailDatabase::GetAllPendingHdrsTable()
+{
+ nsresult rv = NS_OK;
+ if (!m_mdbAllPendingHdrsTable)
+ rv = GetTableCreateIfMissing(kPendingHdrsScope, kPendingHdrsTableKind, getter_AddRefs(m_mdbAllPendingHdrsTable),
+ m_pendingHdrsRowScopeToken, m_pendingHdrsTableKindToken) ;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::AddNewHdrToDB(nsIMsgDBHdr *newHdr, bool notify)
+{
+ nsresult rv = nsMsgDatabase::AddNewHdrToDB(newHdr, notify);
+ if (NS_SUCCEEDED(rv))
+ rv = UpdatePendingAttributes(newHdr);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::UpdatePendingAttributes(nsIMsgDBHdr* aNewHdr)
+{
+ nsresult rv = GetAllPendingHdrsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ mdb_count numPendingHdrs = 0;
+ m_mdbAllPendingHdrsTable->GetCount(GetEnv(), &numPendingHdrs);
+ if (numPendingHdrs > 0)
+ {
+ mdbYarn messageIdYarn;
+ nsCOMPtr <nsIMdbRow> pendingRow;
+ mdbOid outRowId;
+
+ nsCString messageId;
+ aNewHdr->GetMessageId(getter_Copies(messageId));
+ messageIdYarn.mYarn_Buf = (void*)messageId.get();
+ messageIdYarn.mYarn_Fill = messageId.Length();
+ messageIdYarn.mYarn_Form = 0;
+ messageIdYarn.mYarn_Size = messageIdYarn.mYarn_Fill;
+
+ m_mdbStore->FindRow(GetEnv(), m_pendingHdrsRowScopeToken,
+ m_messageIdColumnToken, &messageIdYarn, &outRowId, getter_AddRefs(pendingRow));
+ if (pendingRow)
+ {
+ mdb_count numCells;
+ mdbYarn cellYarn;
+ mdb_column cellColumn;
+ uint32_t existingFlags;
+
+ pendingRow->GetCount(GetEnv(), &numCells);
+ aNewHdr->GetFlags(&existingFlags);
+ // iterate over the cells in the pending hdr setting properties on the aNewHdr.
+ // we skip cell 0, which is the messageId;
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(aNewHdr); // closed system, cast ok
+ nsIMdbRow *row = msgHdr->GetMDBRow();
+ for (mdb_count cellIndex = 1; cellIndex < numCells; cellIndex++)
+ {
+ nsresult err = pendingRow->SeekCellYarn(GetEnv(), cellIndex, &cellColumn, nullptr);
+ if (NS_SUCCEEDED(err))
+ {
+ err = pendingRow->AliasCellYarn(GetEnv(), cellColumn, &cellYarn);
+ if (NS_SUCCEEDED(err))
+ {
+ if (row)
+ row->AddColumn(GetEnv(), cellColumn, &cellYarn);
+ }
+ }
+ }
+ // We might have changed some cached values, so force a refresh.
+ msgHdr->ClearCachedValues();
+ uint32_t resultFlags;
+ msgHdr->OrFlags(existingFlags, &resultFlags);
+ m_mdbAllPendingHdrsTable->CutRow(GetEnv(), pendingRow);
+ pendingRow->CutAllColumns(GetEnv());
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapMailDatabase::GetRowForPendingHdr(nsIMsgDBHdr *pendingHdr,
+ nsIMdbRow **row)
+{
+ nsresult rv = GetAllPendingHdrsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mdbYarn messageIdYarn;
+ nsCOMPtr<nsIMdbRow> pendingRow;
+ mdbOid outRowId;
+ nsCString messageId;
+ pendingHdr->GetMessageId(getter_Copies(messageId));
+ messageIdYarn.mYarn_Buf = (void*)messageId.get();
+ messageIdYarn.mYarn_Fill = messageId.Length();
+ messageIdYarn.mYarn_Form = 0;
+ messageIdYarn.mYarn_Size = messageIdYarn.mYarn_Fill;
+
+ rv = m_mdbStore->FindRow(GetEnv(), m_pendingHdrsRowScopeToken,
+ m_messageIdColumnToken, &messageIdYarn, &outRowId, getter_AddRefs(pendingRow));
+
+ if (!pendingRow)
+ rv = m_mdbStore->NewRow(GetEnv(), m_pendingHdrsRowScopeToken, getter_AddRefs(pendingRow));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (pendingRow)
+ {
+ // now we need to add cells to the row to remember the messageid, property and property value, and flags.
+ // Then, when hdrs are added to the db, we'll check if they have a matching message-id, and if so,
+ // set the property and flags
+ // XXX we already fetched messageId from the pending hdr, could it have changed by the time we get here?
+ nsCString messageId;
+ pendingHdr->GetMessageId(getter_Copies(messageId));
+ // we're just going to ignore messages without a message-id. They should be rare. If SPAM messages often
+ // didn't have message-id's, they'd be filtered on the server, most likely, and spammers would then
+ // start putting in message-id's.
+ if (!messageId.IsEmpty())
+ {
+ extern const char *kMessageIdColumnName;
+ m_mdbAllPendingHdrsTable->AddRow(GetEnv(), pendingRow);
+ // make sure this is the first cell so that when we ignore the first
+ // cell in nsImapMailDatabase::AddNewHdrToDB, we're ignoring the right one
+ (void) SetProperty(pendingRow, kMessageIdColumnName, messageId.get());
+ pendingRow.forget(row);
+ }
+ else
+ return NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::SetAttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
+ const char *propertyVal)
+{
+ NS_ENSURE_ARG_POINTER(pendingHdr);
+ nsCOMPtr<nsIMdbRow> pendingRow;
+ nsresult rv = GetRowForPendingHdr(pendingHdr, getter_AddRefs(pendingRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetProperty(pendingRow, property, propertyVal);
+}
+
+NS_IMETHODIMP
+nsImapMailDatabase::SetUint32AttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr,
+ const char *property,
+ uint32_t propertyVal)
+{
+ NS_ENSURE_ARG_POINTER(pendingHdr);
+ nsCOMPtr<nsIMdbRow> pendingRow;
+ nsresult rv = GetRowForPendingHdr(pendingHdr, getter_AddRefs(pendingRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetUint32Property(pendingRow, property, propertyVal);
+}
+
+NS_IMETHODIMP
+nsImapMailDatabase::SetUint64AttributeOnPendingHdr(nsIMsgDBHdr *aPendingHdr,
+ const char *aProperty,
+ uint64_t aPropertyVal)
+{
+ NS_ENSURE_ARG_POINTER(aPendingHdr);
+ nsCOMPtr<nsIMdbRow> pendingRow;
+ nsresult rv = GetRowForPendingHdr(aPendingHdr, getter_AddRefs(pendingRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetUint64Property(pendingRow, aProperty, aPropertyVal);
+}
diff --git a/mailnews/db/msgdb/src/nsMailDatabase.cpp b/mailnews/db/msgdb/src/nsMailDatabase.cpp
new file mode 100644
index 000000000..9e7c15260
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMailDatabase.cpp
@@ -0,0 +1,443 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMailDatabase.h"
+#include "nsDBFolderInfo.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsNetUtil.h"
+#include "nsMsgOfflineImapOperation.h"
+#include "nsMsgFolderFlags.h"
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgPluggableStore.h"
+
+extern PRLogModuleInfo *IMAPOffline;
+
+using namespace mozilla;
+
+// scope for all offine ops table
+const char *kOfflineOpsScope = "ns:msg:db:row:scope:ops:all";
+const char *kOfflineOpsTableKind = "ns:msg:db:table:kind:ops";
+struct mdbOid gAllOfflineOpsTableOID;
+
+nsMailDatabase::nsMailDatabase() : m_reparse(false)
+{
+ m_mdbAllOfflineOpsTable = nullptr;
+ m_offlineOpsRowScopeToken = 0;
+ m_offlineOpsTableKindToken = 0;
+}
+
+nsMailDatabase::~nsMailDatabase()
+{
+}
+
+// caller passes in upgrading==true if they want back a db even if the db is out of date.
+// If so, they'll extract out the interesting info from the db, close it, delete it, and
+// then try to open the db again, prior to reparsing.
+nsresult nsMailDatabase::Open(nsMsgDBService* aDBService, nsIFile *aSummaryFile,
+ bool aCreate, bool aUpgrading)
+{
+#ifdef DEBUG
+ nsString leafName;
+ aSummaryFile->GetLeafName(leafName);
+ if (!StringEndsWith(leafName, NS_LITERAL_STRING(".msf"),
+ nsCaseInsensitiveStringComparator()))
+ NS_ERROR("non summary file passed into open\n");
+#endif
+ return nsMsgDatabase::Open(aDBService, aSummaryFile, aCreate, aUpgrading);
+}
+
+NS_IMETHODIMP nsMailDatabase::ForceClosed()
+{
+ m_mdbAllOfflineOpsTable = nullptr;
+ return nsMsgDatabase::ForceClosed();
+}
+
+// get this on demand so that only db's that have offline ops will
+// create the table.
+nsresult nsMailDatabase::GetAllOfflineOpsTable()
+{
+ nsresult rv = NS_OK;
+ if (!m_mdbAllOfflineOpsTable)
+ rv = GetTableCreateIfMissing(kOfflineOpsScope, kOfflineOpsTableKind, getter_AddRefs(m_mdbAllOfflineOpsTable),
+ m_offlineOpsRowScopeToken, m_offlineOpsTableKindToken) ;
+ return rv;
+}
+
+NS_IMETHODIMP nsMailDatabase::StartBatch()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailDatabase::EndBatch()
+{
+ SetSummaryValid(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailDatabase::DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys, nsIDBChangeListener *instigator)
+{
+ nsresult rv;
+ if (m_folder)
+ {
+ bool isLocked;
+ m_folder->GetLocked(&isLocked);
+ if (isLocked)
+ {
+ NS_ASSERTION(false, "Some other operation is in progress");
+ return NS_MSG_FOLDER_BUSY;
+ }
+ }
+
+ rv = nsMsgDatabase::DeleteMessages(aNumKeys, nsMsgKeys, instigator);
+ SetSummaryValid(true);
+ return rv;
+}
+
+NS_IMETHODIMP nsMailDatabase::GetSummaryValid(bool *aResult)
+{
+ uint32_t version;
+ m_dbFolderInfo->GetVersion(&version);
+ if (GetCurVersion() != version)
+ {
+ *aResult = false;
+ return NS_OK;
+ }
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ if (!m_folder) {
+ // If the folder is not set, we just return without checking the validity
+ // of the summary file. For now, this is an expected condition when the
+ // message database is being opened from a URL in
+ // nsMailboxUrl::GetMsgHdrForKey() which calls
+ // nsMsgDBService::OpenMailDBFromFile() without a folder.
+ // Returning an error here would lead to the deletion of the MSF in the
+ // caller nsMsgDatabase::CheckForErrors().
+ *aResult = true;
+ return NS_OK;
+ }
+ nsresult rv = m_folder->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->IsSummaryFileValid(m_folder, this, aResult);
+}
+
+NS_IMETHODIMP nsMailDatabase::SetSummaryValid(bool aValid)
+{
+ nsMsgDatabase::SetSummaryValid(aValid);
+
+ if (!m_folder)
+ return NS_ERROR_NULL_POINTER;
+
+ // If this is a virtual folder, there is no storage.
+ bool flag;
+ m_folder->GetFlag(nsMsgFolderFlags::Virtual, &flag);
+ if (flag)
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = m_folder->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->SetSummaryFileValid(m_folder, this, aValid);
+}
+
+NS_IMETHODIMP nsMailDatabase::RemoveOfflineOp(nsIMsgOfflineImapOperation *op)
+{
+
+ nsresult rv = GetAllOfflineOpsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!op || !m_mdbAllOfflineOpsTable)
+ return NS_ERROR_NULL_POINTER;
+ nsMsgOfflineImapOperation* offlineOp = static_cast<nsMsgOfflineImapOperation*>(op); // closed system, so this is ok
+ nsIMdbRow* row = offlineOp->GetMDBRow();
+ rv = m_mdbAllOfflineOpsTable->CutRow(GetEnv(), row);
+ row->CutAllColumns(GetEnv());
+ return rv;
+}
+
+NS_IMETHODIMP nsMailDatabase::GetOfflineOpForKey(nsMsgKey msgKey, bool create, nsIMsgOfflineImapOperation **offlineOp)
+{
+ mdb_bool hasOid;
+ mdbOid rowObjectId;
+ nsresult err;
+
+ if (!IMAPOffline)
+ IMAPOffline = PR_NewLogModule("IMAPOFFLINE");
+ nsresult rv = GetAllOfflineOpsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!offlineOp || !m_mdbAllOfflineOpsTable)
+ return NS_ERROR_NULL_POINTER;
+
+ *offlineOp = NULL;
+
+ rowObjectId.mOid_Id = msgKey;
+ rowObjectId.mOid_Scope = m_offlineOpsRowScopeToken;
+ err = m_mdbAllOfflineOpsTable->HasOid(GetEnv(), &rowObjectId, &hasOid);
+ if (NS_SUCCEEDED(err) && m_mdbStore && (hasOid || create))
+ {
+ nsCOMPtr <nsIMdbRow> offlineOpRow;
+ err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, getter_AddRefs(offlineOpRow));
+
+ if (create)
+ {
+ if (!offlineOpRow)
+ {
+ err = m_mdbStore->NewRowWithOid(GetEnv(), &rowObjectId, getter_AddRefs(offlineOpRow));
+ NS_ENSURE_SUCCESS(err, err);
+ }
+ if (offlineOpRow && !hasOid)
+ m_mdbAllOfflineOpsTable->AddRow(GetEnv(), offlineOpRow);
+ }
+
+ if (NS_SUCCEEDED(err) && offlineOpRow)
+ {
+ *offlineOp = new nsMsgOfflineImapOperation(this, offlineOpRow);
+ if (*offlineOp)
+ (*offlineOp)->SetMessageKey(msgKey);
+ NS_IF_ADDREF(*offlineOp);
+ }
+ if (!hasOid && m_dbFolderInfo)
+ {
+ // set initial value for flags so we don't lose them.
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ (*offlineOp)->SetNewFlags(flags);
+ }
+ int32_t newFlags;
+ m_dbFolderInfo->OrFlags(nsMsgFolderFlags::OfflineEvents, &newFlags);
+ }
+ }
+
+ return err;
+}
+
+NS_IMETHODIMP nsMailDatabase::EnumerateOfflineOps(nsISimpleEnumerator **enumerator)
+{
+ NS_ASSERTION(false, "not impl yet");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP nsMailDatabase::ListAllOfflineOpIds(nsTArray<nsMsgKey> *offlineOpIds)
+{
+ NS_ENSURE_ARG(offlineOpIds);
+ nsresult rv = GetAllOfflineOpsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsIMdbTableRowCursor *rowCursor;
+ if (!IMAPOffline)
+ IMAPOffline = PR_NewLogModule("IMAPOFFLINE");
+
+ if (m_mdbAllOfflineOpsTable)
+ {
+ nsresult err = m_mdbAllOfflineOpsTable->GetTableRowCursor(GetEnv(), -1, &rowCursor);
+ while (NS_SUCCEEDED(err) && rowCursor)
+ {
+ mdbOid outOid;
+ mdb_pos outPos;
+
+ err = rowCursor->NextRowOid(GetEnv(), &outOid, &outPos);
+ // is this right? Mork is returning a 0 id, but that should valid.
+ if (outPos < 0 || outOid.mOid_Id == (mdb_id) -1)
+ break;
+ if (NS_SUCCEEDED(err))
+ {
+ offlineOpIds->AppendElement(outOid.mOid_Id);
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ {
+ nsCOMPtr <nsIMsgOfflineImapOperation> offlineOp;
+ GetOfflineOpForKey(outOid.mOid_Id, false, getter_AddRefs(offlineOp));
+ if (offlineOp)
+ {
+ nsMsgOfflineImapOperation *logOp = static_cast<nsMsgOfflineImapOperation *>(static_cast<nsIMsgOfflineImapOperation *>(offlineOp.get()));
+ if (logOp)
+ logOp->Log(IMAPOffline);
+
+ }
+ }
+ }
+ }
+ // TODO: would it cause a problem to replace this with "rv = err;" ?
+ rv = (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+ rowCursor->Release();
+ }
+
+ offlineOpIds->Sort();
+ return rv;
+}
+
+NS_IMETHODIMP nsMailDatabase::ListAllOfflineDeletes(nsTArray<nsMsgKey> *offlineDeletes)
+{
+ NS_ENSURE_ARG_POINTER(offlineDeletes);
+
+ nsresult rv = GetAllOfflineOpsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsIMdbTableRowCursor *rowCursor;
+ if (m_mdbAllOfflineOpsTable)
+ {
+ nsresult err = m_mdbAllOfflineOpsTable->GetTableRowCursor(GetEnv(), -1, &rowCursor);
+ while (NS_SUCCEEDED(err) && rowCursor)
+ {
+ mdbOid outOid;
+ mdb_pos outPos;
+ nsIMdbRow* offlineOpRow;
+
+ err = rowCursor->NextRow(GetEnv(), &offlineOpRow, &outPos);
+ // is this right? Mork is returning a 0 id, but that should valid.
+ if (outPos < 0 || offlineOpRow == nullptr)
+ break;
+ if (NS_SUCCEEDED(err))
+ {
+ offlineOpRow->GetOid(GetEnv(), &outOid);
+ nsIMsgOfflineImapOperation *offlineOp = new nsMsgOfflineImapOperation(this, offlineOpRow);
+ if (offlineOp)
+ {
+ NS_ADDREF(offlineOp);
+ imapMessageFlagsType newFlags;
+ nsOfflineImapOperationType opType;
+
+ offlineOp->GetOperation(&opType);
+ offlineOp->GetNewFlags(&newFlags);
+ if (opType & nsIMsgOfflineImapOperation::kMsgMoved ||
+ ((opType & nsIMsgOfflineImapOperation::kFlagsChanged)
+ && (newFlags & nsIMsgOfflineImapOperation::kMsgMarkedDeleted)))
+ offlineDeletes->AppendElement(outOid.mOid_Id);
+ NS_RELEASE(offlineOp);
+ }
+ offlineOpRow->Release();
+ }
+ }
+ // TODO: would it cause a problem to replace this with "rv = err;" ?
+ rv = (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+ rowCursor->Release();
+ }
+ return rv;
+}
+
+// This is used to remember that the db is out of sync with the mail folder
+// and needs to be regenerated.
+void nsMailDatabase::SetReparse(bool reparse)
+{
+ m_reparse = reparse;
+}
+
+class nsMsgOfflineOpEnumerator : public nsISimpleEnumerator {
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ explicit nsMsgOfflineOpEnumerator(nsMailDatabase* db);
+
+protected:
+ virtual ~nsMsgOfflineOpEnumerator();
+ nsresult GetRowCursor();
+ nsresult PrefetchNext();
+ nsMailDatabase* mDB;
+ nsIMdbTableRowCursor* mRowCursor;
+ nsCOMPtr <nsIMsgOfflineImapOperation> mResultOp;
+ bool mDone;
+ bool mNextPrefetched;
+};
+
+nsMsgOfflineOpEnumerator::nsMsgOfflineOpEnumerator(nsMailDatabase* db)
+ : mDB(db), mRowCursor(nullptr), mDone(false)
+{
+ NS_ADDREF(mDB);
+ mNextPrefetched = false;
+}
+
+nsMsgOfflineOpEnumerator::~nsMsgOfflineOpEnumerator()
+{
+ NS_IF_RELEASE(mRowCursor);
+ NS_RELEASE(mDB);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgOfflineOpEnumerator, nsISimpleEnumerator)
+
+nsresult nsMsgOfflineOpEnumerator::GetRowCursor()
+{
+ nsresult rv = NS_OK;
+ mDone = false;
+
+ if (!mDB || !mDB->m_mdbAllOfflineOpsTable)
+ return NS_ERROR_NULL_POINTER;
+
+ rv = mDB->m_mdbAllOfflineOpsTable->GetTableRowCursor(mDB->GetEnv(), -1, &mRowCursor);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgOfflineOpEnumerator::GetNext(nsISupports **aItem)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ nsresult rv = NS_OK;
+ if (!mNextPrefetched)
+ rv = PrefetchNext();
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mResultOp)
+ {
+ *aItem = mResultOp;
+ NS_ADDREF(*aItem);
+ mNextPrefetched = false;
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgOfflineOpEnumerator::PrefetchNext()
+{
+ nsresult rv = NS_OK;
+ nsIMdbRow* offlineOpRow;
+ mdb_pos rowPos;
+
+ if (!mRowCursor)
+ {
+ rv = GetRowCursor();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ rv = mRowCursor->NextRow(mDB->GetEnv(), &offlineOpRow, &rowPos);
+ if (!offlineOpRow)
+ {
+ mDone = true;
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv))
+ {
+ mDone = true;
+ return rv;
+ }
+
+ nsIMsgOfflineImapOperation *op = new nsMsgOfflineImapOperation(mDB, offlineOpRow);
+ mResultOp = op;
+ if (!op)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (mResultOp)
+ {
+ mNextPrefetched = true;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgOfflineOpEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!mNextPrefetched)
+ PrefetchNext();
+ *aResult = !mDone;
+ return NS_OK;
+}
diff --git a/mailnews/db/msgdb/src/nsMsgDatabase.cpp b/mailnews/db/msgdb/src/nsMsgDatabase.cpp
new file mode 100644
index 000000000..31da7bc5e
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMsgDatabase.cpp
@@ -0,0 +1,5914 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// this file implements the nsMsgDatabase interface using the MDB Interface.
+
+#include "nscore.h"
+#include "msgCore.h"
+#include "nsMailDatabase.h"
+#include "nsDBFolderInfo.h"
+#include "nsMsgKeySet.h"
+#include "nsMsgThread.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsMsgBaseCID.h"
+#include "nsMorkCID.h"
+#include "nsIMdbFactoryFactory.h"
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include "nsMsgDBCID.h"
+#include "nsILocale.h"
+#include "nsMsgMimeCID.h"
+#include "nsILocaleService.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgDBView.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "MailNewsTypes2.h"
+#include "nsMsgUtils.h"
+#include "nsMsgKeyArray.h"
+#include "nsIMutableArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsICollation.h"
+#include "nsCollationCID.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsArrayEnumerator.h"
+#include "nsIMemoryReporter.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "mozilla/mailnews/Services.h"
+
+using namespace mozilla::mailnews;
+using namespace mozilla;
+
+#if defined(DEBUG_sspitzer_) || defined(DEBUG_seth_)
+#define DEBUG_MSGKEYSET 1
+#endif
+
+#define MSG_HASH_SIZE 512
+
+// This will be used on discovery, since we don't know total.
+const int32_t kMaxHdrsInCache = 512;
+
+// special keys
+static const nsMsgKey kAllMsgHdrsTableKey = 1;
+static const nsMsgKey kTableKeyForThreadOne = 0xfffffffe;
+static const nsMsgKey kAllThreadsTableKey = 0xfffffffd;
+static const nsMsgKey kFirstPseudoKey = 0xfffffff0;
+static const nsMsgKey kIdStartOfFake = 0xffffff80;
+static const nsMsgKey kForceReparseKey = 0xfffffff0;
+
+static PRLogModuleInfo* DBLog;
+
+PRTime nsMsgDatabase::gLastUseTime;
+
+NS_IMPL_ISUPPORTS(nsMsgDBService, nsIMsgDBService)
+
+nsMsgDBService::nsMsgDBService()
+{
+ DBLog = PR_NewLogModule("MSGDB");
+}
+
+
+nsMsgDBService::~nsMsgDBService()
+{
+#ifdef DEBUG
+ // If you hit this warning, it means that some code is holding onto
+ // a db at shutdown.
+ NS_WARNING_ASSERTION(!m_dbCache.Length(), "some msg dbs left open");
+ for (uint32_t i = 0; i < m_dbCache.Length(); i++)
+ {
+ nsMsgDatabase* pMessageDB = m_dbCache.ElementAt(i);
+ if (pMessageDB)
+ printf("db left open %s\n", (const char *) pMessageDB->m_dbName.get());
+ }
+#endif
+}
+
+NS_IMETHODIMP nsMsgDBService::OpenFolderDB(nsIMsgFolder *aFolder,
+ bool aLeaveInvalidDB,
+ nsIMsgDatabase **_retval)
+{
+ NS_ENSURE_ARG(aFolder);
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ nsresult rv = aFolder->GetServer(getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> summaryFilePath;
+ rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgDatabase *cacheDB = FindInCache(summaryFilePath);
+ if (cacheDB)
+ {
+ // this db could have ended up in the folder cache w/o an m_folder pointer via
+ // OpenMailDBFromFile. If so, take this chance to fix the folder.
+ if (!cacheDB->m_folder)
+ cacheDB->m_folder = aFolder;
+ cacheDB->RememberLastUseTime();
+ *_retval = cacheDB; // FindInCache already addRefed.
+ // if m_thumb is set, someone is asynchronously opening the db. But our
+ // caller wants to synchronously open it, so just do it.
+ if (cacheDB->m_thumb)
+ return cacheDB->Open(this, summaryFilePath, false, aLeaveInvalidDB);
+ return NS_OK;
+ }
+
+ nsCString localDatabaseType;
+ incomingServer->GetLocalDatabaseType(localDatabaseType);
+ nsAutoCString dbContractID(NS_MSGDB_CONTRACTID);
+ dbContractID.Append(localDatabaseType.get());
+ nsCOMPtr <nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't try to create the database yet--let the createNewDB call do that.
+ nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(msgDB.get());
+ msgDatabase->m_folder = aFolder;
+ rv = msgDatabase->Open(this, summaryFilePath, false, aLeaveInvalidDB);
+ if (NS_FAILED(rv) && rv != NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ return rv;
+
+ NS_ADDREF(*_retval = msgDB);
+
+ if (NS_FAILED(rv))
+ {
+#ifdef DEBUG
+ // Doing these checks for debug only as we don't want to report certain
+ // errors in debug mode, but in release mode we wouldn't report them either
+
+ // These errors are expected.
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
+ rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ return rv;
+
+ // If it isn't one of the expected errors, throw a warning.
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ return rv;
+ }
+
+ FinishDBOpen(aFolder, msgDatabase);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBService::AsyncOpenFolderDB(nsIMsgFolder *aFolder,
+ bool aLeaveInvalidDB,
+ nsIMsgDatabase **_retval)
+{
+ NS_ENSURE_ARG(aFolder);
+
+ nsCOMPtr <nsIFile> summaryFilePath;
+ nsresult rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgDatabase *cacheDB = FindInCache(summaryFilePath);
+ if (cacheDB)
+ {
+ // this db could have ended up in the folder cache w/o an m_folder pointer via
+ // OpenMailDBFromFile. If so, take this chance to fix the folder.
+ if (!cacheDB->m_folder)
+ cacheDB->m_folder = aFolder;
+ *_retval = cacheDB; // FindInCache already addRefed.
+ // We don't care if an other consumer is thumbing the store. In that
+ // case, they'll both thumb the store.
+ return NS_OK;
+ }
+
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = aFolder->GetServer(getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString localDatabaseType;
+ incomingServer->GetLocalDatabaseType(localDatabaseType);
+ nsAutoCString dbContractID(NS_MSGDB_CONTRACTID);
+ dbContractID.Append(localDatabaseType.get());
+ nsCOMPtr <nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(msgDB.get());
+ rv = msgDatabase->OpenInternal(this, summaryFilePath, false, aLeaveInvalidDB,
+ false /* open asynchronously */);
+
+ NS_ADDREF(*_retval = msgDB);
+ msgDatabase->m_folder = aFolder;
+
+ if (NS_FAILED(rv))
+ {
+#ifdef DEBUG
+ // Doing these checks for debug only as we don't want to report certain
+ // errors in debug mode, but in release mode we wouldn't report them either
+
+ // These errors are expected.
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
+ rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ return rv;
+
+ // If it isn't one of the expected errors, throw a warning.
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ return rv;
+ }
+
+ FinishDBOpen(aFolder, msgDatabase);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBService::OpenMore(nsIMsgDatabase *aDB,
+ uint32_t aTimeHint,
+ bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(aDB);
+ NS_ENSURE_TRUE(msgDatabase, NS_ERROR_INVALID_ARG);
+ // Check if this db has been opened.
+ if (!msgDatabase->m_thumb)
+ {
+ *_retval = true;
+ return NS_OK;
+ }
+ nsresult rv;
+ *_retval = false;
+ PRIntervalTime startTime = PR_IntervalNow();
+ do
+ {
+ mdb_count outTotal; // total somethings to do in operation
+ mdb_count outCurrent; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken; // is operation irreparably dead and broken?
+ rv = msgDatabase->m_thumb->DoMore(msgDatabase->m_mdbEnv,
+ &outTotal, &outCurrent, &outDone,
+ &outBroken);
+ if (NS_FAILED(rv))
+ break;
+ if (outDone)
+ {
+ nsCOMPtr<nsIMdbFactory> mdbFactory;
+ msgDatabase->GetMDBFactory(getter_AddRefs(mdbFactory));
+ NS_ENSURE_TRUE(mdbFactory, NS_ERROR_FAILURE);
+ rv = mdbFactory->ThumbToOpenStore(msgDatabase->m_mdbEnv, msgDatabase->m_thumb, &msgDatabase->m_mdbStore);
+ msgDatabase->m_thumb = nullptr;
+ nsCOMPtr<nsIFile> folderPath;
+ (void) msgDatabase->m_folder->GetFilePath(getter_AddRefs(folderPath));
+ nsCOMPtr <nsIFile> summaryFile;
+ (void) GetSummaryFileLocation(folderPath, getter_AddRefs(summaryFile));
+
+ if (NS_SUCCEEDED(rv))
+ rv = (msgDatabase->m_mdbStore) ? msgDatabase->InitExistingDB() : NS_ERROR_FAILURE;
+ if (NS_SUCCEEDED(rv))
+ rv = msgDatabase->CheckForErrors(rv, false, this, summaryFile);
+
+ FinishDBOpen(msgDatabase->m_folder, msgDatabase);
+ break;
+ }
+ }
+ while (PR_IntervalToMilliseconds(PR_IntervalNow() - startTime) <= aTimeHint);
+ *_retval = !msgDatabase->m_thumb;
+ return rv;
+}
+
+/**
+ * When a db is opened, we need to hook up any pending listeners for
+ * that db, and notify them.
+ */
+void nsMsgDBService::HookupPendingListeners(nsIMsgDatabase *db,
+ nsIMsgFolder *folder)
+{
+ for (int32_t listenerIndex = 0;
+ listenerIndex < m_foldersPendingListeners.Count(); listenerIndex++)
+ {
+ // check if we have a pending listener on this db, and if so, add it.
+ if (m_foldersPendingListeners[listenerIndex] == folder)
+ {
+ db->AddListener(m_pendingListeners.ObjectAt(listenerIndex));
+ m_pendingListeners.ObjectAt(listenerIndex)->OnEvent(db, "DBOpened");
+ }
+ }
+}
+
+void nsMsgDBService::FinishDBOpen(nsIMsgFolder *aFolder, nsMsgDatabase *aMsgDB)
+{
+ uint32_t folderFlags;
+ aFolder->GetFlags(&folderFlags);
+
+ if (! (folderFlags & nsMsgFolderFlags::Virtual) &&
+ aMsgDB->m_mdbAllMsgHeadersTable)
+ {
+ mdb_count numHdrsInTable = 0;
+ int32_t numMessages;
+ aMsgDB->m_mdbAllMsgHeadersTable->GetCount(aMsgDB->GetEnv(),
+ &numHdrsInTable);
+ aMsgDB->m_dbFolderInfo->GetNumMessages(&numMessages);
+ if (numMessages != (int32_t) numHdrsInTable)
+ aMsgDB->SyncCounts();
+ }
+ HookupPendingListeners(aMsgDB, aFolder);
+ aMsgDB->RememberLastUseTime();
+}
+
+//----------------------------------------------------------------------
+// FindInCache - this addrefs the db it finds.
+//----------------------------------------------------------------------
+nsMsgDatabase* nsMsgDBService::FindInCache(nsIFile *dbName)
+{
+ for (uint32_t i = 0; i < m_dbCache.Length(); i++)
+ {
+ nsMsgDatabase* pMessageDB = m_dbCache[i];
+ if (pMessageDB->MatchDbName(dbName))
+ {
+ if (pMessageDB->m_mdbStore) // don't return db without store
+ {
+ NS_ADDREF(pMessageDB);
+ return pMessageDB;
+ }
+ }
+ }
+ return nullptr;
+}
+
+// This method is called when the caller is trying to create a db without
+// having a corresponding nsIMsgFolder object. This happens in a few
+// situations, including imap folder discovery, compacting local folders,
+// and copying local folders.
+NS_IMETHODIMP nsMsgDBService::OpenMailDBFromFile(nsIFile *aFolderName,
+ nsIMsgFolder *aFolder,
+ bool aCreate,
+ bool aLeaveInvalidDB,
+ nsIMsgDatabase** pMessageDB)
+{
+ if (!aFolderName)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr <nsIFile> dbPath;
+ nsresult rv = GetSummaryFileLocation(aFolderName, getter_AddRefs(dbPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *pMessageDB = FindInCache(dbPath);
+ if (*pMessageDB)
+ return NS_OK;
+
+ RefPtr<nsMailDatabase> msgDB = new nsMailDatabase;
+ NS_ENSURE_TRUE(msgDB, NS_ERROR_OUT_OF_MEMORY);
+ rv = msgDB->Open(this, dbPath, aCreate, aLeaveInvalidDB);
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ return rv;
+ NS_IF_ADDREF(*pMessageDB = msgDB);
+ if (aCreate && msgDB && rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ rv = NS_OK;
+ if (NS_SUCCEEDED(rv))
+ msgDB->m_folder = aFolder;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBService::CreateNewDB(nsIMsgFolder *aFolder,
+ nsIMsgDatabase **_retval)
+{
+ NS_ENSURE_ARG(aFolder);
+
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ nsresult rv = aFolder->GetServer(getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> summaryFilePath;
+ rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString localDatabaseType;
+ incomingServer->GetLocalDatabaseType(localDatabaseType);
+ nsAutoCString dbContractID(NS_MSGDB_CONTRACTID);
+ dbContractID.Append(localDatabaseType.get());
+
+ nsCOMPtr <nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(msgDB.get());
+
+ msgDatabase->m_folder = aFolder;
+ rv = msgDatabase->Open(this, summaryFilePath, true, true);
+
+ // We are trying to create a new database, but that implies that it did not
+ // already exist. Open returns NS_MSG_ERROR_FOLDER_SUMMARY_MISSING for the
+ // successful creation of a new database. But if it existed for some
+ // reason, then we would get rv = NS_OK instead. That is a "failure"
+ // from our perspective, so we want to return a failure since we are not
+ // returning a valid database object.
+ NS_ENSURE_TRUE(rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING,
+ NS_SUCCEEDED(rv) ? NS_ERROR_FILE_ALREADY_EXISTS : rv);
+
+ NS_ADDREF(*_retval = msgDB);
+
+ HookupPendingListeners(msgDB, aFolder);
+
+ msgDatabase->RememberLastUseTime();
+
+ return NS_OK;
+}
+
+/* void registerPendingListener (in nsIMsgFolder aFolder, in nsIDBChangeListener aListener); */
+NS_IMETHODIMP nsMsgDBService::RegisterPendingListener(nsIMsgFolder *aFolder, nsIDBChangeListener *aListener)
+{
+ // need to make sure we don't hold onto these forever. Maybe a shutdown listener?
+ // if there is a db open on this folder already, we should register the listener.
+ m_foldersPendingListeners.AppendObject(aFolder);
+ m_pendingListeners.AppendObject(aListener);
+ nsCOMPtr <nsIMsgDatabase> openDB;
+ CachedDBForFolder(aFolder, getter_AddRefs(openDB));
+ if (openDB)
+ openDB->AddListener(aListener);
+ return NS_OK;
+}
+
+/* void unregisterPendingListener (in nsIDBChangeListener aListener); */
+NS_IMETHODIMP nsMsgDBService::UnregisterPendingListener(nsIDBChangeListener *aListener)
+{
+ int32_t listenerIndex = m_pendingListeners.IndexOfObject(aListener);
+ if (listenerIndex != -1)
+ {
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ CachedDBForFolder(m_foldersPendingListeners[listenerIndex], getter_AddRefs(msgDB));
+ if (msgDB)
+ msgDB->RemoveListener(aListener);
+ m_foldersPendingListeners.RemoveObjectAt(listenerIndex);
+ m_pendingListeners.RemoveObjectAt(listenerIndex);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBService::CachedDBForFolder(nsIMsgFolder *aFolder, nsIMsgDatabase **aRetDB)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aRetDB);
+
+ nsCOMPtr<nsIFile> summaryFilePath;
+ nsresult rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aRetDB = FindInCache(summaryFilePath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBService::ForceFolderDBClosed(nsIMsgFolder *aFolder)
+{
+ nsCOMPtr<nsIMsgDatabase> mailDB;
+ nsresult rv = CachedDBForFolder(aFolder, getter_AddRefs(mailDB));
+ if (mailDB)
+ {
+ mailDB->ForceClosed();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBService::GetOpenDBs(nsIArray **aOpenDBs)
+{
+ NS_ENSURE_ARG_POINTER(aOpenDBs);
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> openDBs(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < m_dbCache.Length(); i++)
+ openDBs->AppendElement(m_dbCache[i], false);
+
+ openDBs.forget(aOpenDBs);
+ return NS_OK;
+}
+
+static bool gGotGlobalPrefs = false;
+static bool gThreadWithoutRe = true;
+static bool gStrictThreading = false;
+static bool gCorrectThreading = false;
+
+void nsMsgDatabase::GetGlobalPrefs()
+{
+ if (!gGotGlobalPrefs)
+ {
+ GetBoolPref("mail.thread_without_re", &gThreadWithoutRe);
+ GetBoolPref("mail.strict_threading", &gStrictThreading);
+ GetBoolPref("mail.correct_threading", &gCorrectThreading);
+ gGotGlobalPrefs = true;
+ }
+}
+
+nsresult nsMsgDatabase::AddHdrToCache(nsIMsgDBHdr *hdr, nsMsgKey key) // do we want key? We could get it from hdr
+{
+ if (m_bCacheHeaders)
+ {
+ if (!m_cachedHeaders)
+ m_cachedHeaders = new PLDHashTable(&gMsgDBHashTableOps, sizeof(struct MsgHdrHashElement), m_cacheSize);
+ if (m_cachedHeaders)
+ {
+ if (key == nsMsgKey_None)
+ hdr->GetMessageKey(&key);
+ if (m_cachedHeaders->EntryCount() > m_cacheSize)
+ ClearHdrCache(true);
+ PLDHashEntryHdr *entry = m_cachedHeaders->Add((void *)(uintptr_t) key, mozilla::fallible);
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY; // XXX out of memory
+
+ MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry);
+ element->mHdr = hdr;
+ element->mKey = key;
+ NS_ADDREF(hdr); // make the cache hold onto the header
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::SetMsgHdrCacheSize(uint32_t aSize)
+{
+ m_cacheSize = aSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetMsgHdrCacheSize(uint32_t *aSize)
+{
+ NS_ENSURE_ARG_POINTER(aSize);
+ *aSize = m_cacheSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetLastUseTime(PRTime *aTime)
+{
+ NS_ENSURE_ARG_POINTER(aTime);
+ *aTime = m_lastUseTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetLastUseTime(PRTime aTime)
+{
+ gLastUseTime = m_lastUseTime = aTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetDatabaseSize(int64_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> summaryFilePath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = summaryFilePath->InitWithNativePath(m_dbName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = summaryFilePath->Exists(&exists);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (exists)
+ rv = summaryFilePath->GetFileSize(_retval);
+ else
+ *_retval = 0;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ClearCachedHdrs()
+{
+ ClearCachedObjects(false);
+#ifdef DEBUG_bienvenu1
+ if (mRefCnt > 1)
+ {
+ NS_ASSERTION(false, "");
+ printf("someone's holding onto db - refs = %ld\n", mRefCnt);
+ }
+#endif
+ return NS_OK;
+}
+
+void nsMsgDatabase::ClearEnumerators()
+{
+ // clear out existing enumerators
+ nsTArray<nsMsgDBEnumerator *> copyEnumerators;
+ copyEnumerators.SwapElements(m_enumerators);
+
+ uint32_t numEnums = copyEnumerators.Length();
+ for (uint32_t i = 0; i < numEnums; i++)
+ copyEnumerators[i]->Clear();
+}
+
+nsMsgThread *nsMsgDatabase::FindExistingThread(nsMsgKey threadId)
+{
+ uint32_t numThreads = m_threads.Length();
+ for (uint32_t i = 0; i < numThreads; i++)
+ if (m_threads[i]->m_threadKey == threadId)
+ return m_threads[i];
+
+ return nullptr;
+}
+
+void nsMsgDatabase::ClearThreads()
+{
+ // clear out existing threads
+ nsTArray<nsMsgThread *> copyThreads;
+ copyThreads.SwapElements(m_threads);
+
+ uint32_t numThreads = copyThreads.Length();
+ for (uint32_t i = 0; i < numThreads; i++)
+ copyThreads[i]->Clear();
+}
+
+void nsMsgDatabase::ClearCachedObjects(bool dbGoingAway)
+{
+ ClearHdrCache(false);
+#ifdef DEBUG_DavidBienvenu
+ if (m_headersInUse && m_headersInUse->EntryCount() > 0)
+ {
+ NS_ASSERTION(false, "leaking headers");
+ printf("leaking %d headers in %s\n", m_headersInUse->EntryCount(), (const char *) m_dbName);
+ }
+#endif
+ m_cachedThread = nullptr;
+ m_cachedThreadId = nsMsgKey_None;
+ // We should only clear the use hdr cache when the db is going away, or we could
+ // end up with multiple copies of the same logical msg hdr, which will lead to
+ // ref-counting problems.
+ if (dbGoingAway)
+ {
+ ClearUseHdrCache();
+ ClearThreads();
+ }
+ m_thumb = nullptr;
+}
+
+nsresult nsMsgDatabase::ClearHdrCache(bool reInit)
+{
+ if (m_cachedHeaders)
+ {
+ // save this away in case we renter this code.
+ PLDHashTable *saveCachedHeaders = m_cachedHeaders;
+ m_cachedHeaders = nullptr;
+ for (auto iter = saveCachedHeaders->Iter(); !iter.Done(); iter.Next()) {
+ auto element = static_cast<MsgHdrHashElement*>(iter.Get());
+ if (element)
+ NS_IF_RELEASE(element->mHdr);
+ }
+
+ if (reInit)
+ {
+ saveCachedHeaders->ClearAndPrepareForLength(m_cacheSize);
+ m_cachedHeaders = saveCachedHeaders;
+ }
+ else
+ {
+ delete saveCachedHeaders;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::RemoveHdrFromCache(nsIMsgDBHdr *hdr, nsMsgKey key)
+{
+ if (m_cachedHeaders)
+ {
+ if (key == nsMsgKey_None)
+ hdr->GetMessageKey(&key);
+
+ PLDHashEntryHdr *entry =
+ m_cachedHeaders->Search((const void *)(uintptr_t) key);
+ if (entry)
+ {
+ m_cachedHeaders->Remove((void *)(uintptr_t) key);
+ NS_RELEASE(hdr); // get rid of extra ref the cache was holding.
+ }
+
+ }
+ return NS_OK;
+}
+
+
+nsresult nsMsgDatabase::GetHdrFromUseCache(nsMsgKey key, nsIMsgDBHdr* *result)
+{
+ if (!result)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ *result = nullptr;
+
+ if (m_headersInUse)
+ {
+ PLDHashEntryHdr *entry =
+ m_headersInUse->Search((const void *)(uintptr_t) key);
+ if (entry)
+ {
+ MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry);
+ *result = element->mHdr;
+ }
+ if (*result)
+ {
+ NS_ADDREF(*result);
+ rv = NS_OK;
+ }
+ }
+ return rv;
+}
+
+PLDHashTableOps nsMsgDatabase::gMsgDBHashTableOps =
+{
+ HashKey,
+ MatchEntry,
+ MoveEntry,
+ ClearEntry,
+ nullptr
+};
+
+// HashKey is supposed to maximize entropy in the low order bits, and the key
+// as is, should do that.
+PLDHashNumber
+nsMsgDatabase::HashKey(const void* aKey)
+{
+ return PLDHashNumber(NS_PTR_TO_INT32(aKey));
+}
+
+bool
+nsMsgDatabase::MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey)
+{
+ const MsgHdrHashElement* hdr = static_cast<const MsgHdrHashElement*>(aEntry);
+ return aKey == (const void *)(uintptr_t) hdr->mKey; // ### or get the key from the hdr...
+}
+
+void
+nsMsgDatabase::MoveEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, PLDHashEntryHdr* aTo)
+{
+ const MsgHdrHashElement* from = static_cast<const MsgHdrHashElement*>(aFrom);
+ MsgHdrHashElement* to = static_cast<MsgHdrHashElement*>(aTo);
+ // ### eh? Why is this needed? I don't think we have a copy operator?
+ *to = *from;
+}
+
+void
+nsMsgDatabase::ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
+{
+ MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(aEntry);
+ element->mHdr = nullptr; // eh? Need to release this or not?
+ element->mKey = nsMsgKey_None; // eh?
+}
+
+
+nsresult nsMsgDatabase::AddHdrToUseCache(nsIMsgDBHdr *hdr, nsMsgKey key)
+{
+ if (!m_headersInUse)
+ {
+ mdb_count numHdrs = MSG_HASH_SIZE;
+ if (m_mdbAllMsgHeadersTable)
+ m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrs);
+ m_headersInUse = new PLDHashTable(&gMsgDBHashTableOps, sizeof(struct MsgHdrHashElement), std::max((mdb_count)MSG_HASH_SIZE, numHdrs));
+ }
+ if (m_headersInUse)
+ {
+ if (key == nsMsgKey_None)
+ hdr->GetMessageKey(&key);
+ PLDHashEntryHdr *entry = m_headersInUse->Add((void *)(uintptr_t) key, mozilla::fallible);
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY; // XXX out of memory
+
+ MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry);
+ element->mHdr = hdr;
+ element->mKey = key;
+ // the hash table won't add ref, we'll do it ourselves
+ // stand for the addref that CreateMsgHdr normally does.
+ NS_ADDREF(hdr);
+ return NS_OK;
+ }
+
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult nsMsgDatabase::ClearUseHdrCache()
+{
+ if (m_headersInUse)
+ {
+ // clear mdb row pointers of any headers still in use, because the
+ // underlying db is going away.
+ for (auto iter = m_headersInUse->Iter(); !iter.Done(); iter.Next()) {
+ auto element = static_cast<const MsgHdrHashElement*>(iter.Get());
+ if (element && element->mHdr) {
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(element->mHdr); // closed system, so this is ok
+ // clear out m_mdbRow member variable - the db is going away, which means that this member
+ // variable might very well point to a mork db that is gone.
+ NS_IF_RELEASE(msgHdr->m_mdbRow);
+ // NS_IF_RELEASE(msgHdr->m_mdb);
+ }
+ }
+ delete m_headersInUse;
+ m_headersInUse = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::RemoveHdrFromUseCache(nsIMsgDBHdr *hdr, nsMsgKey key)
+{
+ if (m_headersInUse)
+ {
+ if (key == nsMsgKey_None)
+ hdr->GetMessageKey(&key);
+
+ m_headersInUse->Remove((void *)(uintptr_t) key);
+ }
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgDatabase::CreateMsgHdr(nsIMdbRow* hdrRow, nsMsgKey key, nsIMsgDBHdr* *result)
+{
+ NS_ENSURE_ARG_POINTER(hdrRow);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsresult rv = GetHdrFromUseCache(key, result);
+ if (NS_SUCCEEDED(rv) && *result)
+ {
+ hdrRow->Release();
+ return rv;
+ }
+
+ nsMsgHdr *msgHdr = new nsMsgHdr(this, hdrRow);
+ if(!msgHdr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ msgHdr->SetMessageKey(key);
+ // don't need to addref here; GetHdrFromUseCache addrefs.
+ *result = msgHdr;
+
+ AddHdrToCache(msgHdr, key);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::AddListener(nsIDBChangeListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ m_ChangeListeners.AppendElementUnlessExists(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::RemoveListener(nsIDBChangeListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ m_ChangeListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+// XXX should we return rv for listener->propertyfunc_?
+#define NOTIFY_LISTENERS(propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator iter(m_ChangeListeners); \
+ nsCOMPtr<nsIDBChangeListener> listener; \
+ while (iter.HasMore()) { \
+ listener = iter.GetNext(); \
+ listener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+// change announcer methods - just broadcast to all listeners.
+NS_IMETHODIMP nsMsgDatabase::NotifyHdrChangeAll(nsIMsgDBHdr *aHdrChanged,
+ uint32_t aOldFlags,
+ uint32_t aNewFlags,
+ nsIDBChangeListener *aInstigator)
+{
+ // We will only notify the change if the header exists in the database.
+ // This allows database functions to be usable in both the case where the
+ // header is in the db, or the header is not so no notifications should be
+ // given.
+ nsMsgKey key;
+ bool inDb = false;
+ if (aHdrChanged)
+ {
+ aHdrChanged->GetMessageKey(&key);
+ ContainsKey(key, &inDb);
+ }
+ if (inDb)
+ NOTIFY_LISTENERS(OnHdrFlagsChanged,
+ (aHdrChanged, aOldFlags, aNewFlags, aInstigator));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::NotifyReadChanged(nsIDBChangeListener *aInstigator)
+{
+ NOTIFY_LISTENERS(OnReadChanged, (aInstigator));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::NotifyJunkScoreChanged(nsIDBChangeListener *aInstigator)
+{
+ NOTIFY_LISTENERS(OnJunkScoreChanged, (aInstigator));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::NotifyHdrDeletedAll(nsIMsgDBHdr *aHdrDeleted,
+ nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener *aInstigator)
+{
+ NOTIFY_LISTENERS(OnHdrDeleted, (aHdrDeleted, aParentKey, aFlags, aInstigator));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::NotifyHdrAddedAll(nsIMsgDBHdr *aHdrAdded,
+ nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener *aInstigator)
+{
+#ifdef DEBUG_bienvenu1
+ printf("notifying add of %ld parent %ld\n", keyAdded, parentKey);
+#endif
+ NOTIFY_LISTENERS(OnHdrAdded, (aHdrAdded, aParentKey, aFlags, aInstigator));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::NotifyParentChangedAll(nsMsgKey aKeyReparented,
+ nsMsgKey aOldParent,
+ nsMsgKey aNewParent,
+ nsIDBChangeListener *aInstigator)
+{
+ NOTIFY_LISTENERS(OnParentChanged,
+ (aKeyReparented, aOldParent, aNewParent, aInstigator));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::NotifyAnnouncerGoingAway(void)
+{
+ NOTIFY_LISTENERS(OnAnnouncerGoingAway, (this));
+ return NS_OK;
+}
+
+bool nsMsgDatabase::MatchDbName(nsIFile *dbName) // returns true if they match
+{
+ nsCString dbPath;
+ dbName->GetNativePath(dbPath);
+ return dbPath.Equals(m_dbName);
+}
+
+void nsMsgDBService::AddToCache(nsMsgDatabase* pMessageDB)
+{
+#ifdef DEBUG_David_Bienvenu
+ NS_ASSERTION(m_dbCache.Length() < 50, "50 or more open db's");
+#endif
+#ifdef DEBUG
+ if (pMessageDB->m_folder)
+ {
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ CachedDBForFolder(pMessageDB->m_folder, getter_AddRefs(msgDB));
+ NS_ASSERTION(!msgDB, "shouldn't have db in cache");
+ }
+#endif
+ m_dbCache.AppendElement(pMessageDB);
+}
+
+/**
+ * Log the open db's, and how many headers are in memory.
+ */
+void nsMsgDBService::DumpCache()
+{
+ nsMsgDatabase* db = nullptr;
+ MOZ_LOG(DBLog, LogLevel::Info, ("%d open DB's\n", m_dbCache.Length()));
+ for (uint32_t i = 0; i < m_dbCache.Length(); i++)
+ {
+ db = m_dbCache.ElementAt(i);
+ MOZ_LOG(DBLog, LogLevel::Info, ("%s - %ld hdrs in use\n",
+ (const char*)db->m_dbName.get(),
+ db->m_headersInUse ? db->m_headersInUse->EntryCount() : 0));
+ }
+}
+
+// Memory Reporting implementations
+
+size_t nsMsgDatabase::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t totalSize = 0;
+ if (m_dbFolderInfo)
+ totalSize += m_dbFolderInfo->SizeOfExcludingThis(aMallocSizeOf);
+ if (m_mdbEnv)
+ {
+ nsIMdbHeap *morkHeap = nullptr;
+ m_mdbEnv->GetHeap(&morkHeap);
+ if (morkHeap)
+ totalSize += morkHeap->GetUsedSize();
+ }
+ totalSize += m_newSet.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ totalSize += m_ChangeListeners.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ totalSize += m_threads.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ // We have two tables of header objects, but every header in m_cachedHeaders
+ // should be in m_headersInUse.
+ // double-counting...
+ size_t headerSize = 0;
+ if (m_headersInUse)
+ {
+ headerSize = m_headersInUse->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ for (auto iter = m_headersInUse->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<MsgHdrHashElement*>(iter.Get());
+ // Sigh, this is dangerous, but so long as this is a closed system, this
+ // is safe.
+ headerSize += static_cast<nsMsgHdr*>(entry->mHdr)->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+ totalSize += headerSize;
+ if (m_msgReferences)
+ totalSize += m_msgReferences->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ return totalSize;
+}
+
+namespace mozilla {
+namespace mailnews {
+
+MOZ_DEFINE_MALLOC_SIZE_OF(GetMallocSize)
+
+class MsgDBReporter final : public nsIMemoryReporter
+{
+ nsMsgDatabase *mDatabase;
+public:
+ explicit MsgDBReporter(nsMsgDatabase *db) : mDatabase(db) {}
+
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD GetName(nsACString &aName)
+ {
+ aName.AssignLiteral("msg-database-objects");
+ return NS_OK;
+ }
+
+ NS_IMETHOD CollectReports(nsIMemoryReporterCallback*aCb,
+ nsISupports* aClosure,
+ bool aAnonymize) override
+ {
+ nsCString path;
+ GetPath(path, aAnonymize);
+ return aCb->Callback(EmptyCString(), path,
+ nsIMemoryReporter::KIND_HEAP,
+ nsIMemoryReporter::UNITS_BYTES,
+ mDatabase->SizeOfIncludingThis(GetMallocSize),
+ NS_LITERAL_CSTRING("Memory used for the folder database."),
+ aClosure);
+ }
+
+ void GetPath(nsACString &memoryPath, bool aAnonymize)
+ {
+ memoryPath.AssignLiteral("explicit/maildb/database(");
+ nsCOMPtr<nsIMsgFolder> folder;
+ mDatabase->GetFolder(getter_AddRefs(folder));
+ if (folder)
+ {
+ if (aAnonymize)
+ memoryPath.AppendLiteral("<anonymized>");
+ else
+ {
+ nsAutoCString folderURL;
+ folder->GetFolderURL(folderURL);
+ MsgReplaceChar(folderURL, '/', '\\');
+ memoryPath += folderURL;
+ }
+ } else {
+ memoryPath.AppendLiteral("UNKNOWN-FOLDER");
+ }
+ memoryPath.Append(')');
+ }
+
+private:
+ ~MsgDBReporter() {}
+};
+
+NS_IMPL_ISUPPORTS(MsgDBReporter, nsIMemoryReporter)
+}
+}
+
+nsMsgDatabase::nsMsgDatabase()
+ : m_dbFolderInfo(nullptr),
+ m_nextPseudoMsgKey(kFirstPseudoKey),
+ m_mdbEnv(nullptr), m_mdbStore(nullptr),
+ m_mdbAllMsgHeadersTable(nullptr), m_mdbAllThreadsTable(nullptr),
+ m_create(false),
+ m_leaveInvalidDB(false),
+ m_dbName(""),
+ m_mdbTokensInitialized(false),
+ m_hdrRowScopeToken(0),
+ m_hdrTableKindToken(0),
+ m_threadTableKindToken(0),
+ m_subjectColumnToken(0),
+ m_senderColumnToken(0),
+ m_messageIdColumnToken(0),
+ m_referencesColumnToken(0),
+ m_recipientsColumnToken(0),
+ m_dateColumnToken(0),
+ m_messageSizeColumnToken(0),
+ m_flagsColumnToken(0),
+ m_priorityColumnToken(0),
+ m_labelColumnToken(0),
+ m_statusOffsetColumnToken(0),
+ m_numLinesColumnToken(0),
+ m_ccListColumnToken(0),
+ m_bccListColumnToken(0),
+ m_threadFlagsColumnToken(0),
+ m_threadIdColumnToken(0),
+ m_threadChildrenColumnToken(0),
+ m_threadUnreadChildrenColumnToken(0),
+ m_messageThreadIdColumnToken(0),
+ m_threadSubjectColumnToken(0),
+ m_messageCharSetColumnToken(0),
+ m_threadParentColumnToken(0),
+ m_threadRootKeyColumnToken(0),
+ m_threadNewestMsgDateColumnToken(0),
+ m_offlineMsgOffsetColumnToken(0),
+ m_offlineMessageSizeColumnToken(0),
+ m_headersInUse(nullptr),
+ m_cachedHeaders(nullptr),
+ m_bCacheHeaders(true),
+ m_cachedThreadId(nsMsgKey_None),
+ m_msgReferences(nullptr),
+ m_cacheSize(kMaxHdrsInCache)
+{
+ mMemReporter = new mozilla::mailnews::MsgDBReporter(this);
+ mozilla::RegisterWeakMemoryReporter(mMemReporter);
+}
+
+nsMsgDatabase::~nsMsgDatabase()
+{
+ mozilla::UnregisterWeakMemoryReporter(mMemReporter);
+ // Close(FALSE); // better have already been closed.
+ ClearCachedObjects(true);
+ ClearEnumerators();
+ delete m_cachedHeaders;
+ delete m_headersInUse;
+
+ if (m_msgReferences)
+ {
+ delete m_msgReferences;
+ m_msgReferences = nullptr;
+ }
+
+ MOZ_LOG(DBLog, LogLevel::Info, ("closing database %s\n",
+ (const char*)m_dbName.get()));
+
+ nsCOMPtr<nsIMsgDBService> serv(do_GetService(NS_MSGDB_SERVICE_CONTRACTID));
+ if (serv)
+ static_cast<nsMsgDBService*>(serv.get())->RemoveFromCache(this);
+
+ // if the db folder info refers to the mdb db, we must clear it because
+ // the reference will be a dangling one soon.
+ if (m_dbFolderInfo)
+ m_dbFolderInfo->ReleaseExternalReferences();
+
+ NS_IF_RELEASE(m_dbFolderInfo);
+ if (m_mdbAllMsgHeadersTable)
+ m_mdbAllMsgHeadersTable->Release();
+
+ if (m_mdbAllThreadsTable)
+ m_mdbAllThreadsTable->Release();
+
+ if (m_mdbStore)
+ m_mdbStore->Release();
+
+ if (m_mdbEnv)
+ {
+ m_mdbEnv->Release(); //??? is this right?
+ m_mdbEnv = nullptr;
+ }
+ m_ChangeListeners.Clear();
+}
+
+NS_IMPL_ISUPPORTS(nsMsgDatabase, nsIMsgDatabase, nsIDBChangeAnnouncer)
+
+void nsMsgDatabase::GetMDBFactory(nsIMdbFactory ** aMdbFactory)
+{
+ if (!mMdbFactory)
+ {
+ nsresult rv;
+ nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && mdbFactoryService)
+ mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory));
+ }
+ NS_IF_ADDREF(*aMdbFactory = mMdbFactory);
+}
+
+// aLeaveInvalidDB: true if caller wants back a db even out of date.
+// If so, they'll extract out the interesting info from the db, close it,
+// delete it, and then try to open the db again, prior to reparsing.
+nsresult nsMsgDatabase::Open(nsMsgDBService *aDBService, nsIFile *aFolderName,
+ bool aCreate, bool aLeaveInvalidDB)
+{
+ return nsMsgDatabase::OpenInternal(aDBService, aFolderName, aCreate, aLeaveInvalidDB,
+ true /* open synchronously */);
+}
+
+nsresult nsMsgDatabase::OpenInternal(nsMsgDBService *aDBService,
+ nsIFile *summaryFile, bool aCreate,
+ bool aLeaveInvalidDB, bool sync)
+{
+ nsAutoCString summaryFilePath;
+ summaryFile->GetNativePath(summaryFilePath);
+
+ MOZ_LOG(DBLog, LogLevel::Info, ("nsMsgDatabase::Open(%s, %s, %p, %s)\n",
+ (const char*)summaryFilePath.get(), aCreate ? "TRUE":"FALSE",
+ this, aLeaveInvalidDB ? "TRUE":"FALSE"));
+
+
+ nsresult rv = OpenMDB(summaryFilePath.get(), aCreate, sync);
+ if (NS_FAILED(rv))
+ MOZ_LOG(DBLog, LogLevel::Info, ("error opening db %lx", rv));
+
+ if (MOZ_LOG_TEST(DBLog, LogLevel::Debug))
+ aDBService->DumpCache();
+
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ return rv;
+
+ m_create = aCreate;
+ m_leaveInvalidDB = aLeaveInvalidDB;
+ if (!sync && NS_SUCCEEDED(rv))
+ {
+ aDBService->AddToCache(this);
+ // remember open options for when the parsing is complete.
+ return rv;
+ }
+ return CheckForErrors(rv, true, aDBService, summaryFile);
+}
+
+nsresult nsMsgDatabase::CheckForErrors(nsresult err, bool sync,
+ nsMsgDBService *aDBService,
+ nsIFile *summaryFile)
+{
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ bool summaryFileExists;
+ bool newFile = false;
+ bool deleteInvalidDB = false;
+
+ bool exists;
+ int64_t fileSize;
+ summaryFile->Exists(&exists);
+ summaryFile->GetFileSize(&fileSize);
+ // if the old summary doesn't exist, we're creating a new one.
+ if ((!exists || !fileSize) && m_create)
+ newFile = true;
+
+ summaryFileExists = exists && fileSize > 0;
+
+ if (NS_SUCCEEDED(err))
+ {
+ if (!m_dbFolderInfo)
+ {
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ else
+ {
+ if (!newFile && summaryFileExists)
+ {
+ bool valid = false;
+ nsresult rv = GetSummaryValid(&valid);
+ if (NS_FAILED(rv) || !valid)
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ // compare current version of db versus filed out version info.
+ uint32_t version;
+ m_dbFolderInfo->GetVersion(&version);
+ if (GetCurVersion() != version)
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+
+ // Check if we should force a reparse because, for example, we have
+ // reached the key limit.
+ bool forceReparse;
+ m_dbFolderInfo->GetBooleanProperty("forceReparse", false, &forceReparse);
+ if (forceReparse)
+ {
+ NS_WARNING("Forcing a reparse presumably because key limit reached");
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+
+ }
+ if (NS_FAILED(err) && !m_leaveInvalidDB)
+ deleteInvalidDB = true;
+ }
+ else
+ {
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ deleteInvalidDB = true;
+ }
+
+ if (deleteInvalidDB)
+ {
+ // this will make the db folder info release its ref to the mail db...
+ NS_IF_RELEASE(m_dbFolderInfo);
+ ForceClosed();
+ if (err == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ summaryFile->Remove(false);
+ }
+ if (NS_FAILED(err) || newFile)
+ {
+ // if we couldn't open file, or we have a blank one, and we're supposed
+ // to upgrade, updgrade it.
+ if (newFile && !m_leaveInvalidDB) // caller is upgrading, and we have empty summary file,
+ { // leave db around and open so caller can upgrade it.
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_MISSING;
+ }
+ else if (NS_FAILED(err) && err != NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ {
+ Close(false);
+ summaryFile->Remove(false); // blow away the db if it's corrupt.
+ }
+ }
+ if (sync && (NS_SUCCEEDED(err) || err == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING))
+ aDBService->AddToCache(this);
+ return (summaryFileExists) ? err : NS_MSG_ERROR_FOLDER_SUMMARY_MISSING;
+}
+
+/**
+ * Open the MDB database synchronously or async based on sync argument.
+ * If successful, this routine will set up the m_mdbStore and m_mdbEnv of
+ * the database object so other database calls can work.
+ */
+nsresult nsMsgDatabase::OpenMDB(const char *dbName, bool create, bool sync)
+{
+ nsresult ret = NS_OK;
+ nsCOMPtr<nsIMdbFactory> mdbFactory;
+ GetMDBFactory(getter_AddRefs(mdbFactory));
+ if (mdbFactory)
+ {
+ ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv);
+ if (NS_SUCCEEDED(ret))
+ {
+ nsIMdbHeap* dbHeap = nullptr;
+
+ if (m_mdbEnv)
+ m_mdbEnv->SetAutoClear(true);
+ m_dbName = dbName;
+ bool exists = false;
+ nsCOMPtr<nsIFile> dbFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &ret);
+ if (NS_SUCCEEDED(ret) && dbFile) {
+ ret = dbFile->InitWithNativePath(m_dbName);
+ if (NS_SUCCEEDED(ret))
+ ret = dbFile->Exists(&exists);
+ }
+ if (!exists)
+ {
+ ret = NS_MSG_ERROR_FOLDER_SUMMARY_MISSING;
+ }
+ // If m_thumb is set, we're asynchronously opening the db already.
+ else if (!m_thumb)
+ {
+ mdbOpenPolicy inOpenPolicy;
+ mdb_bool canOpen;
+ mdbYarn outFormatVersion;
+
+ nsIMdbFile* oldFile = nullptr;
+ ret = mdbFactory->OpenOldFile(m_mdbEnv, dbHeap, dbName,
+ mdbBool_kFalse, // not readonly, we want modifiable
+ &oldFile);
+ if (oldFile)
+ {
+ if (NS_SUCCEEDED(ret))
+ {
+ ret = mdbFactory->CanOpenFilePort(m_mdbEnv, oldFile, // the file to investigate
+ &canOpen, &outFormatVersion);
+ if (NS_SUCCEEDED(ret) && canOpen)
+ {
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->OpenFileStore(m_mdbEnv, dbHeap,
+ oldFile, &inOpenPolicy, getter_AddRefs(m_thumb));
+ }
+ else
+ ret = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ NS_RELEASE(oldFile); // always release our file ref, store has own
+ }
+ }
+ if (NS_SUCCEEDED(ret) && m_thumb && sync)
+ {
+ mdb_count outTotal; // total somethings to do in operation
+ mdb_count outCurrent; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken; // is operation irreparably dead and broken?
+ do
+ {
+ ret = m_thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken);
+ if (NS_FAILED(ret))
+ {// mork isn't really doing NS errors yet.
+ outDone = true;
+ break;
+ }
+ }
+ while (NS_SUCCEEDED(ret) && !outBroken && !outDone);
+ // m_mdbEnv->ClearErrors(); // ### temporary...
+ // only 0 is a non-error return.
+ if (NS_SUCCEEDED(ret) && outDone)
+ {
+ ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, m_thumb, &m_mdbStore);
+ if (NS_SUCCEEDED(ret))
+ ret = (m_mdbStore) ? InitExistingDB() : NS_ERROR_FAILURE;
+ }
+#ifdef DEBUG_bienvenu1
+ DumpContents();
+#endif
+ m_thumb = nullptr;
+ }
+ else if (create) // ### need error code saying why open file store failed
+ {
+ nsIMdbFile* newFile = 0;
+ ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, dbName, &newFile);
+ if (NS_FAILED(ret))
+ ret = NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+ if ( newFile )
+ {
+ if (NS_SUCCEEDED(ret))
+ {
+ mdbOpenPolicy inOpenPolicy;
+
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->CreateNewFileStore(m_mdbEnv, dbHeap,
+ newFile, &inOpenPolicy, &m_mdbStore);
+ if (NS_SUCCEEDED(ret))
+ ret = (m_mdbStore) ? InitNewDB() : NS_ERROR_FAILURE;
+ }
+ NS_RELEASE(newFile); // always release our file ref, store has own
+ }
+ }
+ }
+ }
+#ifdef DEBUG_David_Bienvenu
+// NS_ASSERTION(NS_SUCCEEDED(ret), "failed opening mdb");
+#endif
+ return ret;
+}
+
+nsresult nsMsgDatabase::CloseMDB(bool commit)
+{
+ if (commit)
+ Commit(nsMsgDBCommitType::kSessionCommit);
+ return(NS_OK);
+}
+
+
+// force the database to close - this'll flush out anybody holding onto
+// a database without having a listener!
+// This is evil in the com world, but there are times we need to delete the file.
+NS_IMETHODIMP nsMsgDatabase::ForceClosed()
+{
+ nsresult err = NS_OK;
+
+ // make sure someone has a reference so object won't get deleted out from under us.
+ AddRef();
+ NotifyAnnouncerGoingAway();
+ // make sure dbFolderInfo isn't holding onto mork stuff because mork db is going away
+ if (m_dbFolderInfo)
+ m_dbFolderInfo->ReleaseExternalReferences();
+ NS_IF_RELEASE(m_dbFolderInfo);
+
+ err = CloseMDB(true); // Backup DB will try to recover info, so commit
+ ClearCachedObjects(true);
+ ClearEnumerators();
+if (m_mdbAllMsgHeadersTable)
+ {
+ m_mdbAllMsgHeadersTable->Release();
+ m_mdbAllMsgHeadersTable = nullptr;
+ }
+ if (m_mdbAllThreadsTable)
+ {
+ m_mdbAllThreadsTable->Release();
+ m_mdbAllThreadsTable = nullptr;
+ }
+ if (m_mdbStore)
+ {
+ m_mdbStore->Release();
+ m_mdbStore = nullptr;
+ }
+
+ // better not be any listeners, because we're going away.
+ NS_ASSERTION(m_ChangeListeners.IsEmpty(), "shouldn't have any listeners left");
+
+ Release();
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetDBFolderInfo(nsIDBFolderInfo **result)
+{
+ if (!m_dbFolderInfo)
+ {
+ NS_ERROR("db must be corrupt");
+ return NS_ERROR_NULL_POINTER;
+ }
+ NS_ADDREF(*result = m_dbFolderInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetFolder(nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_IF_ADDREF(*aFolder = m_folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::Commit(nsMsgDBCommit commitType)
+{
+ nsresult err = NS_OK;
+ nsCOMPtr<nsIMdbThumb> commitThumb;
+
+ RememberLastUseTime();
+ if (commitType == nsMsgDBCommitType::kLargeCommit || commitType == nsMsgDBCommitType::kSessionCommit)
+ {
+ mdb_percent outActualWaste = 0;
+ mdb_bool outShould;
+ if (m_mdbStore) {
+ err = m_mdbStore->ShouldCompress(GetEnv(), 30, &outActualWaste, &outShould);
+ if (NS_SUCCEEDED(err) && outShould)
+ commitType = nsMsgDBCommitType::kCompressCommit;
+ }
+ }
+ // commitType = nsMsgDBCommitType::kCompressCommit; // ### until incremental writing works.
+
+ if (m_mdbStore)
+ {
+ switch (commitType)
+ {
+ case nsMsgDBCommitType::kLargeCommit:
+ err = m_mdbStore->LargeCommit(GetEnv(), getter_AddRefs(commitThumb));
+ break;
+ case nsMsgDBCommitType::kSessionCommit:
+ err = m_mdbStore->SessionCommit(GetEnv(), getter_AddRefs(commitThumb));
+ break;
+ case nsMsgDBCommitType::kCompressCommit:
+ err = m_mdbStore->CompressCommit(GetEnv(), getter_AddRefs(commitThumb));
+ break;
+ }
+ }
+ if (commitThumb)
+ {
+ mdb_count outTotal = 0; // total somethings to do in operation
+ mdb_count outCurrent = 0; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken = false; // is operation irreparably dead and broken?
+ while (!outDone && !outBroken && NS_SUCCEEDED(err))
+ {
+ err = commitThumb->DoMore(GetEnv(), &outTotal, &outCurrent, &outDone, &outBroken);
+ }
+
+ }
+ // ### do something with error, but clear it now because mork errors out on commits.
+ if (GetEnv())
+ GetEnv()->ClearErrors();
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && accountManager)
+ {
+ nsCOMPtr<nsIMsgFolderCache> folderCache;
+
+ rv = accountManager->GetFolderCache(getter_AddRefs(folderCache));
+ if (NS_SUCCEEDED(rv) && folderCache)
+ {
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ rv = folderCache->GetCacheElement(m_dbName, false, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(rv) && cacheElement && m_dbFolderInfo)
+ {
+ int32_t totalMessages, unreadMessages, pendingMessages, pendingUnreadMessages;
+
+ m_dbFolderInfo->GetNumMessages(&totalMessages);
+ m_dbFolderInfo->GetNumUnreadMessages(&unreadMessages);
+ m_dbFolderInfo->GetImapUnreadPendingMessages(&pendingUnreadMessages);
+ m_dbFolderInfo->GetImapTotalPendingMessages(&pendingMessages);
+ cacheElement->SetInt32Property("totalMsgs", totalMessages);
+ cacheElement->SetInt32Property("totalUnreadMsgs", unreadMessages);
+ cacheElement->SetInt32Property("pendingMsgs", pendingMessages);
+ cacheElement->SetInt32Property("pendingUnreadMsgs", pendingUnreadMessages);
+ folderCache->Commit(false);
+ }
+ }
+ }
+
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::Close(bool forceCommit /* = TRUE */)
+{
+ return CloseMDB(forceCommit);
+}
+
+const char *kMsgHdrsScope = "ns:msg:db:row:scope:msgs:all"; // scope for all headers table
+const char *kMsgHdrsTableKind = "ns:msg:db:table:kind:msgs";
+const char *kThreadTableKind = "ns:msg:db:table:kind:thread";
+const char *kThreadHdrsScope = "ns:msg:db:row:scope:threads:all"; // scope for all threads table
+const char *kAllThreadsTableKind = "ns:msg:db:table:kind:allthreads"; // kind for table of all threads
+const char *kSubjectColumnName = "subject";
+const char *kSenderColumnName = "sender";
+const char *kMessageIdColumnName = "message-id";
+const char *kReferencesColumnName = "references";
+const char *kRecipientsColumnName = "recipients";
+const char *kDateColumnName = "date";
+const char *kMessageSizeColumnName = "size";
+const char *kFlagsColumnName = "flags";
+const char *kPriorityColumnName = "priority";
+const char *kLabelColumnName = "label";
+const char *kStatusOffsetColumnName = "statusOfset";
+const char *kNumLinesColumnName = "numLines";
+const char *kCCListColumnName = "ccList";
+const char *kBCCListColumnName = "bccList";
+const char *kMessageThreadIdColumnName = "msgThreadId";
+const char *kThreadFlagsColumnName = "threadFlags";
+const char *kThreadIdColumnName = "threadId";
+const char *kThreadChildrenColumnName = "children";
+const char *kThreadUnreadChildrenColumnName = "unreadChildren";
+const char *kThreadSubjectColumnName = "threadSubject";
+const char *kMessageCharSetColumnName = "msgCharSet";
+const char *kThreadParentColumnName = "threadParent";
+const char *kThreadRootColumnName = "threadRoot";
+const char *kThreadNewestMsgDateColumnName = "threadNewestMsgDate";
+const char *kOfflineMsgOffsetColumnName = "msgOffset";
+const char *kOfflineMsgSizeColumnName = "offlineMsgSize";
+struct mdbOid gAllMsgHdrsTableOID;
+struct mdbOid gAllThreadsTableOID;
+const char *kFixedBadRefThreadingProp = "fixedBadRefThreading";
+
+// set up empty tables, dbFolderInfo, etc.
+nsresult nsMsgDatabase::InitNewDB()
+{
+ nsresult err = NS_OK;
+
+ err = InitMDBInfo();
+ if (NS_SUCCEEDED(err))
+ {
+ nsDBFolderInfo *dbFolderInfo = new nsDBFolderInfo(this);
+ if (dbFolderInfo)
+ {
+ NS_ADDREF(dbFolderInfo);
+ err = dbFolderInfo->AddToNewMDB();
+ dbFolderInfo->SetVersion(GetCurVersion());
+ dbFolderInfo->SetBooleanProperty("forceReparse", false);
+ dbFolderInfo->SetBooleanProperty(kFixedBadRefThreadingProp, true);
+ nsIMdbStore *store = GetStore();
+ // create the unique table for the dbFolderInfo.
+ struct mdbOid allMsgHdrsTableOID;
+ struct mdbOid allThreadsTableOID;
+ if (!store)
+ return NS_ERROR_NULL_POINTER;
+
+ allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
+ allMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey;
+ allThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
+ allThreadsTableOID.mOid_Id = kAllThreadsTableKey;
+
+ // TODO: check this error value?
+ (void) store->NewTableWithOid(GetEnv(), &allMsgHdrsTableOID, m_hdrTableKindToken,
+ false, nullptr, &m_mdbAllMsgHeadersTable);
+
+ // error here is not fatal.
+ (void) store->NewTableWithOid(GetEnv(), &allThreadsTableOID, m_allThreadsTableKindToken,
+ false, nullptr, &m_mdbAllThreadsTable);
+
+ m_dbFolderInfo = dbFolderInfo;
+
+ }
+ else
+ err = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::GetTableCreateIfMissing(const char *scope, const char *kind, nsIMdbTable **table,
+ mdb_token &scopeToken, mdb_token &kindToken)
+{
+ struct mdbOid tableOID;
+
+ if (!m_mdbStore)
+ return NS_ERROR_FAILURE;
+ (void) m_mdbStore->StringToToken(GetEnv(), scope, &scopeToken);
+ (void) m_mdbStore->StringToToken(GetEnv(), kind, &kindToken);
+ tableOID.mOid_Scope = scopeToken;
+ tableOID.mOid_Id = 1;
+
+ nsresult rv = m_mdbStore->GetTable(GetEnv(), &tableOID, table);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // create new all all offline ops table, if it doesn't exist.
+ if (NS_SUCCEEDED(rv) && !*table)
+ {
+ rv = m_mdbStore->NewTable(GetEnv(), scopeToken,kindToken,
+ false, nullptr, table);
+ if (NS_FAILED(rv) || !*table)
+ rv = NS_ERROR_FAILURE;
+ }
+ NS_ASSERTION(NS_SUCCEEDED(rv), "couldn't create offline ops table");
+ return rv;
+}
+
+nsresult nsMsgDatabase::InitExistingDB()
+{
+ nsresult err = NS_OK;
+
+ err = InitMDBInfo();
+ if (NS_SUCCEEDED(err))
+ {
+ err = GetStore()->GetTable(GetEnv(), &gAllMsgHdrsTableOID, &m_mdbAllMsgHeadersTable);
+ if (NS_SUCCEEDED(err))
+ {
+ m_dbFolderInfo = new nsDBFolderInfo(this);
+ if (m_dbFolderInfo)
+ {
+ NS_ADDREF(m_dbFolderInfo);
+ err = m_dbFolderInfo->InitFromExistingDB();
+ }
+ }
+ else
+ err = NS_ERROR_FAILURE;
+
+ NS_ASSERTION(NS_SUCCEEDED(err), "failed initing existing db");
+ NS_ENSURE_SUCCESS(err, err);
+ // create new all msg hdrs table, if it doesn't exist.
+ if (NS_SUCCEEDED(err) && !m_mdbAllMsgHeadersTable)
+ {
+ struct mdbOid allMsgHdrsTableOID;
+ allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
+ allMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey;
+
+ nsresult mdberr = GetStore()->NewTableWithOid(GetEnv(), &allMsgHdrsTableOID, m_hdrTableKindToken,
+ false, nullptr, &m_mdbAllMsgHeadersTable);
+ if (NS_FAILED(mdberr) || !m_mdbAllMsgHeadersTable)
+ err = NS_ERROR_FAILURE;
+ }
+ struct mdbOid allThreadsTableOID;
+ allThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
+ allThreadsTableOID.mOid_Id = kAllThreadsTableKey;
+ err = GetStore()->GetTable(GetEnv(), &gAllThreadsTableOID, &m_mdbAllThreadsTable);
+ if (!m_mdbAllThreadsTable)
+ {
+
+ nsresult mdberr = GetStore()->NewTableWithOid(GetEnv(), &allThreadsTableOID, m_allThreadsTableKindToken,
+ false, nullptr, &m_mdbAllThreadsTable);
+ if (NS_FAILED(mdberr) || !m_mdbAllThreadsTable)
+ err = NS_ERROR_FAILURE;
+ }
+ }
+ if (NS_SUCCEEDED(err) && m_dbFolderInfo)
+ {
+ bool fixedBadRefThreading;
+ m_dbFolderInfo->GetBooleanProperty(kFixedBadRefThreadingProp, false, &fixedBadRefThreading);
+ if (!fixedBadRefThreading)
+ {
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ err = EnumerateMessages(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(err) && enumerator)
+ {
+ bool hasMore;
+
+ while (NS_SUCCEEDED(err = enumerator->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ err = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(err), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryInterface(supports);
+ if (msgHdr && NS_SUCCEEDED(err))
+ {
+ nsCString messageId;
+ nsAutoCString firstReference;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ msgHdr->GetStringReference(0, firstReference);
+ if (messageId.Equals(firstReference))
+ {
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ break;
+ }
+ }
+ }
+ }
+
+ m_dbFolderInfo->SetBooleanProperty(kFixedBadRefThreadingProp, true);
+ }
+
+ }
+ return err;
+}
+
+// initialize the various tokens and tables in our db's env
+nsresult nsMsgDatabase::InitMDBInfo()
+{
+ nsresult err = NS_OK;
+
+ if (!m_mdbTokensInitialized && GetStore())
+ {
+ m_mdbTokensInitialized = true;
+ err = GetStore()->StringToToken(GetEnv(), kMsgHdrsScope, &m_hdrRowScopeToken);
+ if (NS_SUCCEEDED(err))
+ {
+ GetStore()->StringToToken(GetEnv(), kSubjectColumnName, &m_subjectColumnToken);
+ GetStore()->StringToToken(GetEnv(), kSenderColumnName, &m_senderColumnToken);
+ GetStore()->StringToToken(GetEnv(), kMessageIdColumnName, &m_messageIdColumnToken);
+ // if we just store references as a string, we won't get any savings from the
+ // fact there's a lot of duplication. So we may want to break them up into
+ // multiple columns, r1, r2, etc.
+ GetStore()->StringToToken(GetEnv(), kReferencesColumnName, &m_referencesColumnToken);
+ // similarly, recipients could be tokenized properties
+ GetStore()->StringToToken(GetEnv(), kRecipientsColumnName, &m_recipientsColumnToken);
+ GetStore()->StringToToken(GetEnv(), kDateColumnName, &m_dateColumnToken);
+ GetStore()->StringToToken(GetEnv(), kMessageSizeColumnName, &m_messageSizeColumnToken);
+ GetStore()->StringToToken(GetEnv(), kFlagsColumnName, &m_flagsColumnToken);
+ GetStore()->StringToToken(GetEnv(), kPriorityColumnName, &m_priorityColumnToken);
+ GetStore()->StringToToken(GetEnv(), kLabelColumnName, &m_labelColumnToken);
+ GetStore()->StringToToken(GetEnv(), kStatusOffsetColumnName, &m_statusOffsetColumnToken);
+ GetStore()->StringToToken(GetEnv(), kNumLinesColumnName, &m_numLinesColumnToken);
+ GetStore()->StringToToken(GetEnv(), kCCListColumnName, &m_ccListColumnToken);
+ GetStore()->StringToToken(GetEnv(), kBCCListColumnName, &m_bccListColumnToken);
+ GetStore()->StringToToken(GetEnv(), kMessageThreadIdColumnName, &m_messageThreadIdColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadIdColumnName, &m_threadIdColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadFlagsColumnName, &m_threadFlagsColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadNewestMsgDateColumnName, &m_threadNewestMsgDateColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadChildrenColumnName, &m_threadChildrenColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadUnreadChildrenColumnName, &m_threadUnreadChildrenColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadSubjectColumnName, &m_threadSubjectColumnToken);
+ GetStore()->StringToToken(GetEnv(), kMessageCharSetColumnName, &m_messageCharSetColumnToken);
+ err = GetStore()->StringToToken(GetEnv(), kMsgHdrsTableKind, &m_hdrTableKindToken);
+ if (NS_SUCCEEDED(err))
+ err = GetStore()->StringToToken(GetEnv(), kThreadTableKind, &m_threadTableKindToken);
+ err = GetStore()->StringToToken(GetEnv(), kAllThreadsTableKind, &m_allThreadsTableKindToken);
+ err = GetStore()->StringToToken(GetEnv(), kThreadHdrsScope, &m_threadRowScopeToken);
+ err = GetStore()->StringToToken(GetEnv(), kThreadParentColumnName, &m_threadParentColumnToken);
+ err = GetStore()->StringToToken(GetEnv(), kThreadRootColumnName, &m_threadRootKeyColumnToken);
+ err = GetStore()->StringToToken(GetEnv(), kOfflineMsgOffsetColumnName, &m_offlineMsgOffsetColumnToken);
+ err = GetStore()->StringToToken(GetEnv(), kOfflineMsgSizeColumnName, &m_offlineMessageSizeColumnToken);
+
+ if (NS_SUCCEEDED(err))
+ {
+ // The table of all message hdrs will have table id 1.
+ gAllMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
+ gAllMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey;
+ gAllThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
+ gAllThreadsTableOID.mOid_Id = kAllThreadsTableKey;
+
+ }
+ }
+ }
+ return err;
+}
+
+// Returns if the db contains this key
+NS_IMETHODIMP nsMsgDatabase::ContainsKey(nsMsgKey key, bool *containsKey)
+{
+
+ nsresult err = NS_OK;
+ mdb_bool hasOid;
+ mdbOid rowObjectId;
+
+ if (!containsKey || !m_mdbAllMsgHeadersTable)
+ return NS_ERROR_NULL_POINTER;
+ *containsKey = false;
+
+ rowObjectId.mOid_Id = key;
+ rowObjectId.mOid_Scope = m_hdrRowScopeToken;
+ err = m_mdbAllMsgHeadersTable->HasOid(GetEnv(), &rowObjectId, &hasOid);
+ if(NS_SUCCEEDED(err))
+ *containsKey = hasOid;
+
+ return err;
+}
+
+// get a message header for the given key. Caller must release()!
+NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForKey(nsMsgKey key, nsIMsgDBHdr **pmsgHdr)
+{
+ nsresult err = NS_OK;
+ mdb_bool hasOid;
+ mdbOid rowObjectId;
+
+ // Because this may be called a lot, and we don't want gettimeofday() to show
+ // up in trace logs, we just remember the most recent time any db was used,
+ // which should be close enough for our purposes.
+ m_lastUseTime = gLastUseTime;
+
+#ifdef DEBUG_bienvenu1
+ NS_ASSERTION(m_folder, "folder should be set");
+#endif
+
+ if (!pmsgHdr || !m_mdbAllMsgHeadersTable || !m_mdbStore)
+ return NS_ERROR_NULL_POINTER;
+
+ *pmsgHdr = NULL;
+ err = GetHdrFromUseCache(key, pmsgHdr);
+ if (NS_SUCCEEDED(err) && *pmsgHdr)
+ return err;
+
+ rowObjectId.mOid_Id = key;
+ rowObjectId.mOid_Scope = m_hdrRowScopeToken;
+ err = m_mdbAllMsgHeadersTable->HasOid(GetEnv(), &rowObjectId, &hasOid);
+ if (NS_SUCCEEDED(err) /* && hasOid */)
+ {
+ nsIMdbRow *hdrRow;
+ err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, &hdrRow);
+
+ if (NS_SUCCEEDED(err))
+ {
+ if (!hdrRow)
+ {
+ err = NS_ERROR_NULL_POINTER;
+ }
+ else
+ {
+ // NS_ASSERTION(hasOid, "we had oid, right?");
+ err = CreateMsgHdr(hdrRow, key, pmsgHdr);
+ }
+ }
+ }
+
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::StartBatch()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::EndBatch()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::DeleteMessage(nsMsgKey key, nsIDBChangeListener *instigator, bool commit)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (!msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ rv = DeleteHeader(msgHdr, instigator, commit, true);
+ return rv;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys, nsIDBChangeListener *instigator)
+{
+ nsresult err = NS_OK;
+
+ uint32_t kindex;
+ for (kindex = 0; kindex < aNumKeys; kindex++)
+ {
+ nsMsgKey key = nsMsgKeys[kindex];
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ bool hasKey;
+
+ if (NS_SUCCEEDED(ContainsKey(key, &hasKey)) && hasKey)
+ {
+ err = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(err))
+ {
+ err = NS_MSG_MESSAGE_NOT_FOUND;
+ break;
+ }
+ if (msgHdr)
+ err = DeleteHeader(msgHdr, instigator, kindex % 300 == 0, true);
+ if (NS_FAILED(err))
+ break;
+ }
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr)
+{
+ uint32_t size = 0;
+ (void)msgHdr->GetMessageSize(&size);
+ return m_dbFolderInfo->ChangeExpungedBytes (size);
+}
+
+NS_IMETHODIMP nsMsgDatabase::DeleteHeader(nsIMsgDBHdr *msg, nsIDBChangeListener *instigator, bool commit, bool notify)
+{
+ if (!msg)
+ return NS_ERROR_NULL_POINTER;
+
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(msg); // closed system, so this is ok
+ nsMsgKey key;
+ (void)msg->GetMessageKey(&key);
+ // only need to do this for mail - will this speed up news expiration?
+ SetHdrFlag(msg, true, nsMsgMessageFlags::Expunged); // tell mailbox (mail)
+
+ bool hdrWasNew = m_newSet.BinaryIndexOf(key) != m_newSet.NoIndex;
+ m_newSet.RemoveElement(key);
+
+ if (m_dbFolderInfo != NULL)
+ {
+ bool isRead;
+ m_dbFolderInfo->ChangeNumMessages(-1);
+ IsRead(key, &isRead);
+ if (!isRead)
+ m_dbFolderInfo->ChangeNumUnreadMessages(-1);
+ AdjustExpungedBytesOnDelete(msg);
+ }
+
+ uint32_t flags;
+ nsMsgKey threadParent;
+
+ //Save off flags and threadparent since they will no longer exist after we remove the header from the db.
+ if (notify)
+ {
+ (void)msg->GetFlags(&flags);
+ msg->GetThreadParent(&threadParent);
+ }
+
+ RemoveHeaderFromThread(msgHdr);
+ if (notify)
+ {
+ // If deleted hdr was new, restore the new flag on flags
+ // so saved searches will know to reduce their new msg count.
+ if (hdrWasNew)
+ flags |= nsMsgMessageFlags::New;
+ NotifyHdrDeletedAll(msg, threadParent, flags, instigator); // tell listeners
+ }
+ // if (!onlyRemoveFromThread) // to speed up expiration, try this. But really need to do this in RemoveHeaderFromDB
+ nsresult ret = RemoveHeaderFromDB(msgHdr);
+
+
+ if (commit)
+ Commit(nsMsgDBCommitType::kLargeCommit); // ### dmb is this a good time to commit?
+ return ret;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::UndoDelete(nsIMsgDBHdr *aMsgHdr)
+{
+ if (aMsgHdr)
+ {
+ // Force deleted flag, so SetHdrFlag won't bail out because deleted flag isn't set.
+ uint32_t result;
+ aMsgHdr->OrFlags(nsMsgMessageFlags::Expunged, &result);
+ SetHdrFlag(aMsgHdr, false, nsMsgMessageFlags::Expunged); // Clear deleted flag in db.
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::RemoveHeaderFromThread(nsMsgHdr *msgHdr)
+{
+ if (!msgHdr)
+ return NS_ERROR_NULL_POINTER;
+ nsresult ret = NS_OK;
+ nsCOMPtr <nsIMsgThread> thread ;
+ ret = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(ret) && thread)
+ {
+ nsCOMPtr <nsIDBChangeAnnouncer> announcer = do_QueryInterface(this);
+ ret = thread->RemoveChildHdr(msgHdr, announcer);
+ }
+ return ret;
+}
+
+NS_IMETHODIMP nsMsgDatabase::RemoveHeaderMdbRow(nsIMsgDBHdr *msg)
+{
+ NS_ENSURE_ARG_POINTER(msg);
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(msg); // closed system, so this is ok
+ return RemoveHeaderFromDB(msgHdr);
+}
+
+// This is a lower level routine which doesn't send notifcations or
+// update folder info. One use is when a rule fires moving a header
+// from one db to another, to remove it from the first db.
+
+nsresult nsMsgDatabase::RemoveHeaderFromDB(nsMsgHdr *msgHdr)
+{
+ if (!msgHdr)
+ return NS_ERROR_NULL_POINTER;
+ nsresult ret = NS_OK;
+
+ RemoveHdrFromCache(msgHdr, nsMsgKey_None);
+ if (UseCorrectThreading())
+ RemoveMsgRefsFromHash(msgHdr);
+ nsIMdbRow* row = msgHdr->GetMDBRow();
+ if (row)
+ {
+ ret = m_mdbAllMsgHeadersTable->CutRow(GetEnv(), row);
+ row->CutAllColumns(GetEnv());
+ }
+ msgHdr->ClearCachedValues();
+ return ret;
+}
+
+nsresult nsMsgDatabase::IsRead(nsMsgKey key, bool *pRead)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+ rv = IsHeaderRead(msgHdr, pRead);
+ return rv;
+}
+
+uint32_t nsMsgDatabase::GetStatusFlags(nsIMsgDBHdr *msgHdr, uint32_t origFlags)
+{
+ uint32_t statusFlags = origFlags;
+ bool isRead = true;
+
+ nsMsgKey key;
+ (void)msgHdr->GetMessageKey(&key);
+ if ((!m_newSet.IsEmpty() && m_newSet[m_newSet.Length() - 1] == key) ||
+ (m_newSet.BinaryIndexOf(key) != m_newSet.NoIndex))
+ statusFlags |= nsMsgMessageFlags::New;
+ if (NS_SUCCEEDED(IsHeaderRead(msgHdr, &isRead)) && isRead)
+ statusFlags |= nsMsgMessageFlags::Read;
+ return statusFlags;
+}
+
+nsresult nsMsgDatabase::IsHeaderRead(nsIMsgDBHdr *msgHdr, bool *pRead)
+{
+ if (!msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsMsgHdr* hdr = static_cast<nsMsgHdr*>(msgHdr); // closed system, cast ok
+ // can't call GetFlags, because it will be recursive.
+ uint32_t flags;
+ hdr->GetRawFlags(&flags);
+ *pRead = !!(flags & nsMsgMessageFlags::Read);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::IsMarked(nsMsgKey key, bool *pMarked)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv))
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+ *pMarked = !!(flags & nsMsgMessageFlags::Marked);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::IsIgnored(nsMsgKey key, bool *pIgnored)
+{
+ NS_ENSURE_ARG_POINTER(pIgnored);
+
+ nsCOMPtr <nsIMsgThread> threadHdr;
+
+ nsresult rv = GetThreadForMsgKey(key, getter_AddRefs(threadHdr));
+ // This should be very surprising, but we leave that up to the caller
+ // to determine for now.
+ if (!threadHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ uint32_t threadFlags;
+ threadHdr->GetFlags(&threadFlags);
+ *pIgnored = !!(threadFlags & nsMsgMessageFlags::Ignored);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::IsWatched(nsMsgKey key, bool *pWatched)
+{
+ NS_ENSURE_ARG_POINTER(pWatched);
+
+ nsCOMPtr <nsIMsgThread> threadHdr;
+
+ nsresult rv = GetThreadForMsgKey(key, getter_AddRefs(threadHdr));
+ // This should be very surprising, but we leave that up to the caller
+ // to determine for now.
+ if (!threadHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ uint32_t threadFlags;
+ threadHdr->GetFlags(&threadFlags);
+ *pWatched = !!(threadFlags & nsMsgMessageFlags::Watched);
+ return rv;
+}
+
+nsresult nsMsgDatabase::HasAttachments(nsMsgKey key, bool *pHasThem)
+{
+ NS_ENSURE_ARG_POINTER(pHasThem);
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+ *pHasThem = !!(flags & nsMsgMessageFlags::Attachment);
+ return rv;
+}
+
+bool nsMsgDatabase::SetHdrReadFlag(nsIMsgDBHdr *msgHdr, bool bRead)
+{
+ return SetHdrFlag(msgHdr, bRead, nsMsgMessageFlags::Read);
+}
+
+nsresult nsMsgDatabase::MarkHdrReadInDB(nsIMsgDBHdr *msgHdr, bool bRead,
+ nsIDBChangeListener *instigator)
+{
+ nsresult rv;
+ nsMsgKey key;
+ uint32_t oldFlags;
+ bool hdrInDB;
+ (void)msgHdr->GetMessageKey(&key);
+ msgHdr->GetFlags(&oldFlags);
+
+ m_newSet.RemoveElement(key);
+ (void) ContainsKey(key, &hdrInDB);
+ if (hdrInDB && m_dbFolderInfo)
+ {
+ if (bRead)
+ m_dbFolderInfo->ChangeNumUnreadMessages(-1);
+ else
+ m_dbFolderInfo->ChangeNumUnreadMessages(1);
+ }
+
+ SetHdrReadFlag(msgHdr, bRead); // this will cause a commit, at least for local mail, so do it after we change
+ // the folder counts above, so they will get committed too.
+ uint32_t flags;
+ rv = msgHdr->GetFlags(&flags);
+ flags &= ~nsMsgMessageFlags::New;
+ msgHdr->SetFlags(flags);
+ if (NS_FAILED(rv)) return rv;
+
+ if (oldFlags == flags)
+ return NS_OK;
+
+ return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkRead(nsMsgKey key, bool bRead,
+ nsIDBChangeListener *instigator)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+
+ rv = MarkHdrRead(msgHdr, bRead, instigator);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkReplied(nsMsgKey key, bool bReplied,
+ nsIDBChangeListener *instigator /* = NULL */)
+{
+ return SetKeyFlag(key, bReplied, nsMsgMessageFlags::Replied, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkForwarded(nsMsgKey key, bool bForwarded,
+ nsIDBChangeListener *instigator /* = NULL */)
+{
+ return SetKeyFlag(key, bForwarded, nsMsgMessageFlags::Forwarded, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkHasAttachments(nsMsgKey key, bool bHasAttachments,
+ nsIDBChangeListener *instigator)
+{
+ return SetKeyFlag(key, bHasAttachments, nsMsgMessageFlags::Attachment, instigator);
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::MarkThreadRead(nsIMsgThread *thread, nsIDBChangeListener *instigator,
+ uint32_t *aNumMarked, nsMsgKey **aThoseMarked)
+{
+ NS_ENSURE_ARG_POINTER(thread);
+ NS_ENSURE_ARG_POINTER(aNumMarked);
+ NS_ENSURE_ARG_POINTER(aThoseMarked);
+ nsresult rv = NS_OK;
+
+ uint32_t numChildren;
+ nsTArray<nsMsgKey> thoseMarked;
+ thread->GetNumChildren(&numChildren);
+ for (uint32_t curChildIndex = 0; curChildIndex < numChildren; curChildIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> child;
+
+ rv = thread->GetChildHdrAt(curChildIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ bool isRead = true;
+ IsHeaderRead(child, &isRead);
+ if (!isRead)
+ {
+ nsMsgKey key;
+ if (NS_SUCCEEDED(child->GetMessageKey(&key)))
+ thoseMarked.AppendElement(key);
+ MarkHdrRead(child, true, instigator);
+ }
+ }
+ }
+
+ *aNumMarked = thoseMarked.Length();
+
+ if (thoseMarked.Length())
+ {
+ *aThoseMarked =
+ (nsMsgKey *) nsMemory::Clone(&thoseMarked[0],
+ thoseMarked.Length() * sizeof(nsMsgKey));
+ if (!*aThoseMarked)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ *aThoseMarked = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::MarkThreadIgnored(nsIMsgThread *thread, nsMsgKey threadKey, bool bIgnored,
+ nsIDBChangeListener *instigator)
+{
+ NS_ENSURE_ARG(thread);
+ uint32_t threadFlags;
+ thread->GetFlags(&threadFlags);
+ uint32_t oldThreadFlags = threadFlags; // not quite right, since we probably want msg hdr flags.
+ if (bIgnored)
+ {
+ threadFlags |= nsMsgMessageFlags::Ignored;
+ threadFlags &= ~nsMsgMessageFlags::Watched; // ignore is implicit un-watch
+ }
+ else
+ threadFlags &= ~nsMsgMessageFlags::Ignored;
+ thread->SetFlags(threadFlags);
+
+ nsCOMPtr <nsIMsgDBHdr> msg;
+ nsresult rv = GetMsgHdrForKey(threadKey, getter_AddRefs(msg));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NotifyHdrChangeAll(msg, oldThreadFlags, threadFlags, instigator);
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::MarkHeaderKilled(nsIMsgDBHdr *msg, bool bIgnored,
+ nsIDBChangeListener *instigator)
+{
+ uint32_t msgFlags;
+ msg->GetFlags(&msgFlags);
+ uint32_t oldFlags = msgFlags;
+ if (bIgnored)
+ msgFlags |= nsMsgMessageFlags::Ignored;
+ else
+ msgFlags &= ~nsMsgMessageFlags::Ignored;
+ msg->SetFlags(msgFlags);
+
+ return NotifyHdrChangeAll(msg, oldFlags, msgFlags, instigator);
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::MarkThreadWatched(nsIMsgThread *thread, nsMsgKey threadKey, bool bWatched,
+ nsIDBChangeListener *instigator)
+{
+ NS_ENSURE_ARG(thread);
+ uint32_t threadFlags;
+ thread->GetFlags(&threadFlags);
+ uint32_t oldThreadFlags = threadFlags; // not quite right, since we probably want msg hdr flags.
+ if (bWatched)
+ {
+ threadFlags |= nsMsgMessageFlags::Watched;
+ threadFlags &= ~nsMsgMessageFlags::Ignored; // watch is implicit un-ignore
+ }
+ else
+ threadFlags &= ~nsMsgMessageFlags::Watched;
+
+ nsCOMPtr <nsIMsgDBHdr> msg;
+ GetMsgHdrForKey(threadKey, getter_AddRefs(msg));
+
+ nsresult rv = NotifyHdrChangeAll(msg, oldThreadFlags, threadFlags, instigator);
+ thread->SetFlags(threadFlags);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkMarked(nsMsgKey key, bool mark,
+ nsIDBChangeListener *instigator)
+{
+ return SetKeyFlag(key, mark, nsMsgMessageFlags::Marked, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkOffline(nsMsgKey key, bool offline,
+ nsIDBChangeListener *instigator)
+{
+ return SetKeyFlag(key, offline, nsMsgMessageFlags::Offline, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetStringProperty(nsMsgKey aKey, const char *aProperty, const char *aValue)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForKey(aKey, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+ return SetStringPropertyByHdr(msgHdr, aProperty, aValue);
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetStringPropertyByHdr(nsIMsgDBHdr *msgHdr, const char *aProperty, const char *aValue)
+{
+ // don't do notifications if message not yet added to database.
+ // Ignore errors (consequences of failure are minor).
+ bool notify = true;
+ nsMsgKey key = nsMsgKey_None;
+ msgHdr->GetMessageKey(&key);
+ ContainsKey(key, &notify);
+
+ nsCString oldValue;
+ nsresult rv = msgHdr->GetStringProperty(aProperty, getter_Copies(oldValue));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if no change to this string property, bail out
+ if (oldValue.Equals(aValue))
+ return NS_OK;
+
+ // Precall OnHdrPropertyChanged to store prechange status
+ nsTArray<uint32_t> statusArray(m_ChangeListeners.Length());
+ uint32_t status;
+ nsCOMPtr<nsIDBChangeListener> listener;
+ if (notify)
+ {
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners);
+ while (listeners.HasMore())
+ {
+ listener = listeners.GetNext();
+ listener->OnHdrPropertyChanged(msgHdr, true, &status, nullptr);
+ // ignore errors, but append element to keep arrays in sync
+ statusArray.AppendElement(status);
+ }
+ }
+
+ rv = msgHdr->SetStringProperty(aProperty, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ //Postcall OnHdrPropertyChanged to process the change
+ if (notify)
+ {
+ // if this is the junk score property notify, as long as we're not going
+ // from no value to non junk
+ if (!strcmp(aProperty, "junkscore") && !(oldValue.IsEmpty() && !strcmp(aValue, "0")))
+ NotifyJunkScoreChanged(nullptr);
+
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners);
+ for (uint32_t i = 0; listeners.HasMore(); i++)
+ {
+ listener = listeners.GetNext();
+ status = statusArray[i];
+ listener->OnHdrPropertyChanged(msgHdr, false, &status, nullptr);
+ // ignore errors
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::SetUint32PropertyByHdr(nsIMsgDBHdr *aMsgHdr,
+ const char *aProperty,
+ uint32_t aValue)
+{
+ // If no change to this property, bail out.
+ uint32_t oldValue;
+ nsresult rv = aMsgHdr->GetUint32Property(aProperty, &oldValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (oldValue == aValue)
+ return NS_OK;
+
+ // Don't do notifications if message not yet added to database.
+ bool notify = true;
+ nsMsgKey key = nsMsgKey_None;
+ aMsgHdr->GetMessageKey(&key);
+ ContainsKey(key, &notify);
+
+ // Precall OnHdrPropertyChanged to store prechange status.
+ nsTArray<uint32_t> statusArray(m_ChangeListeners.Length());
+ uint32_t status;
+ nsCOMPtr<nsIDBChangeListener> listener;
+ if (notify)
+ {
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners);
+ while (listeners.HasMore())
+ {
+ listener = listeners.GetNext();
+ listener->OnHdrPropertyChanged(aMsgHdr, true, &status, nullptr);
+ // Ignore errors, but append element to keep arrays in sync.
+ statusArray.AppendElement(status);
+ }
+ }
+
+ rv = aMsgHdr->SetUint32Property(aProperty, aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Postcall OnHdrPropertyChanged to process the change.
+ if (notify)
+ {
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners);
+ for (uint32_t i = 0; listeners.HasMore(); i++)
+ {
+ listener = listeners.GetNext();
+ status = statusArray[i];
+ listener->OnHdrPropertyChanged(aMsgHdr, false, &status, nullptr);
+ // Ignore errors.
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetLabel(nsMsgKey key, nsMsgLabelValue label)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ nsMsgLabelValue oldLabel;
+ msgHdr->GetLabel(&oldLabel);
+
+ msgHdr->SetLabel(label);
+ // clear old label
+ if (oldLabel != label)
+ {
+ if (oldLabel != 0)
+ rv = SetKeyFlag(key, false, oldLabel << 25, nullptr);
+ // set the flag in the x-mozilla-status2 line.
+ rv = SetKeyFlag(key, true, label << 25, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkImapDeleted(nsMsgKey key, bool deleted,
+ nsIDBChangeListener *instigator)
+{
+ return SetKeyFlag(key, deleted, nsMsgMessageFlags::IMAPDeleted, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkMDNNeeded(nsMsgKey key, bool bNeeded,
+ nsIDBChangeListener *instigator /* = NULL */)
+{
+ return SetKeyFlag(key, bNeeded, nsMsgMessageFlags::MDNReportNeeded, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::IsMDNNeeded(nsMsgKey key, bool *pNeeded)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+ *pNeeded = !!(flags & nsMsgMessageFlags::MDNReportNeeded);
+ return rv;
+}
+
+
+nsresult nsMsgDatabase::MarkMDNSent(nsMsgKey key, bool bSent,
+ nsIDBChangeListener *instigator /* = NULL */)
+{
+ return SetKeyFlag(key, bSent, nsMsgMessageFlags::MDNReportSent, instigator);
+}
+
+
+nsresult nsMsgDatabase::IsMDNSent(nsMsgKey key, bool *pSent)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+ *pSent = !!(flags & nsMsgMessageFlags::MDNReportSent);
+ return rv;
+}
+
+
+nsresult nsMsgDatabase::SetKeyFlag(nsMsgKey key, bool set, uint32_t flag,
+ nsIDBChangeListener *instigator)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+
+ uint32_t oldFlags;
+ msgHdr->GetFlags(&oldFlags);
+
+ SetHdrFlag(msgHdr, set, flag);
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+
+ if (oldFlags == flags)
+ return NS_OK;
+
+ return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator);
+}
+
+nsresult nsMsgDatabase::SetMsgHdrFlag(nsIMsgDBHdr *msgHdr, bool set, uint32_t flag, nsIDBChangeListener *instigator)
+{
+ uint32_t oldFlags;
+ msgHdr->GetFlags(&oldFlags);
+
+ SetHdrFlag(msgHdr, set, flag);
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+
+ if (oldFlags == flags)
+ return NS_OK;
+
+ return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator);
+}
+
+// Helper routine - lowest level of flag setting - returns true if flags change,
+// false otherwise.
+bool nsMsgDatabase::SetHdrFlag(nsIMsgDBHdr *msgHdr, bool bSet, nsMsgMessageFlagType flag)
+{
+ uint32_t statusFlags;
+ (void)msgHdr->GetFlags(&statusFlags);
+ uint32_t currentStatusFlags = GetStatusFlags(msgHdr, statusFlags);
+ bool flagAlreadySet = (currentStatusFlags & flag) != 0;
+
+ if ((flagAlreadySet && !bSet) || (!flagAlreadySet && bSet))
+ {
+ uint32_t resultFlags;
+ if (bSet)
+ msgHdr->OrFlags(flag, &resultFlags);
+ else
+ msgHdr->AndFlags(~flag, &resultFlags);
+ return true;
+ }
+ return false;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::MarkHdrRead(nsIMsgDBHdr *msgHdr, bool bRead,
+ nsIDBChangeListener *instigator)
+{
+ bool isReadInDB = true;
+ nsresult rv = nsMsgDatabase::IsHeaderRead(msgHdr, &isReadInDB);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isRead = true;
+ rv = IsHeaderRead(msgHdr, &isRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if the flag is already correct in the db, don't change it.
+ // Check msg flags as well as IsHeaderRead in case it's a newsgroup
+ // and the msghdr flags are out of sync with the newsrc settings.
+ // (we could override this method for news db's, but it's a trivial fix here.
+ if (bRead != isRead || isRead != isReadInDB)
+ {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+
+ bool inDB = false;
+ (void)ContainsKey(msgKey, &inDB);
+
+ if (inDB)
+ {
+ nsCOMPtr <nsIMsgThread> threadHdr;
+ rv = GetThreadForMsgKey(msgKey, getter_AddRefs(threadHdr));
+ if (threadHdr)
+ threadHdr->MarkChildRead(bRead);
+ }
+ return MarkHdrReadInDB(msgHdr, bRead, instigator);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkHdrReplied(nsIMsgDBHdr *msgHdr, bool bReplied,
+ nsIDBChangeListener *instigator)
+{
+ return SetMsgHdrFlag(msgHdr, bReplied, nsMsgMessageFlags::Replied, instigator);
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::MarkHdrMarked(nsIMsgDBHdr *msgHdr, bool mark,
+ nsIDBChangeListener *instigator)
+{
+ return SetMsgHdrFlag(msgHdr, mark, nsMsgMessageFlags::Marked, instigator);
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::MarkHdrNotNew(nsIMsgDBHdr *aMsgHdr,
+ nsIDBChangeListener *aInstigator)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ nsMsgKey msgKey;
+ aMsgHdr->GetMessageKey(&msgKey);
+ m_newSet.RemoveElement(msgKey);
+ return SetMsgHdrFlag(aMsgHdr, false, nsMsgMessageFlags::New, aInstigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkAllRead(uint32_t *aNumKeys, nsMsgKey **aThoseMarked)
+{
+ NS_ENSURE_ARG_POINTER(aNumKeys);
+ NS_ENSURE_ARG_POINTER(aThoseMarked);
+ nsMsgHdr *pHeader;
+
+ nsCOMPtr<nsISimpleEnumerator> hdrs;
+ nsTArray<nsMsgKey> thoseMarked;
+ nsresult rv = EnumerateMessages(getter_AddRefs(hdrs));
+ if (NS_FAILED(rv))
+ return rv;
+ bool hasMore = false;
+
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = hdrs->GetNext((nsISupports**)&pHeader);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ if (NS_FAILED(rv))
+ break;
+
+ bool isRead;
+ IsHeaderRead(pHeader, &isRead);
+
+ if (!isRead)
+ {
+ nsMsgKey key;
+ (void)pHeader->GetMessageKey(&key);
+ thoseMarked.AppendElement(key);
+ rv = MarkHdrRead(pHeader, true, nullptr); // ### dmb - blow off error?
+ }
+ NS_RELEASE(pHeader);
+ }
+
+ *aNumKeys = thoseMarked.Length();
+
+ if (thoseMarked.Length())
+ {
+ *aThoseMarked = (nsMsgKey *) nsMemory::Clone(&thoseMarked[0],
+ thoseMarked.Length() * sizeof(nsMsgKey));
+ if (!*aThoseMarked)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ *aThoseMarked = nullptr;
+
+ // force num new to 0.
+ int32_t numUnreadMessages;
+
+ rv = m_dbFolderInfo->GetNumUnreadMessages(&numUnreadMessages);
+ if (NS_SUCCEEDED(rv))
+ m_dbFolderInfo->ChangeNumUnreadMessages(-numUnreadMessages);
+ // caller will Commit the db, so no need to do it here.
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::AddToNewList(nsMsgKey key)
+{
+ // we add new keys in increasing order...
+ if (m_newSet.IsEmpty() || (m_newSet[m_newSet.Length() - 1] < key))
+ m_newSet.AppendElement(key);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::ClearNewList(bool notify /* = FALSE */)
+{
+ nsresult err = NS_OK;
+ if (notify && !m_newSet.IsEmpty()) // need to update view
+ {
+ nsTArray<nsMsgKey> saveNewSet;
+ // clear m_newSet so that the code that's listening to the key change
+ // doesn't think we have new messages and send notifications all over
+ // that we have new messages.
+ saveNewSet.SwapElements(m_newSet);
+ for (uint32_t elementIndex = saveNewSet.Length() - 1; ; elementIndex--)
+ {
+ nsMsgKey lastNewKey = saveNewSet.ElementAt(elementIndex);
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ err = GetMsgHdrForKey(lastNewKey, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(err))
+ {
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+
+ if ((flags | nsMsgMessageFlags::New) != flags)
+ {
+ msgHdr->AndFlags(~nsMsgMessageFlags::New, &flags);
+ NotifyHdrChangeAll(msgHdr, flags | nsMsgMessageFlags::New, flags, nullptr);
+ }
+ }
+ if (elementIndex == 0)
+ break;
+ }
+ }
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::HasNew(bool *_retval)
+{
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ *_retval = (m_newSet.Length() > 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetFirstNew(nsMsgKey *result)
+{
+ bool hasnew;
+ nsresult rv = HasNew(&hasnew);
+ if (NS_FAILED(rv)) return rv;
+ *result = (hasnew) ? m_newSet.ElementAt(0) : nsMsgKey_None;
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsMsgDBEnumerator::nsMsgDBEnumerator(nsMsgDatabase* db,
+ nsIMdbTable *table,
+ nsMsgDBEnumeratorFilter filter,
+ void* closure,
+ bool iterateForwards)
+ : mDB(db),
+ mDone(false),
+ mIterateForwards(iterateForwards),
+ mFilter(filter),
+ mClosure(closure),
+ mStopPos(-1)
+{
+ mNextPrefetched = false;
+ mTable = table;
+ mRowPos = 0;
+ mDB->m_enumerators.AppendElement(this);
+}
+
+nsMsgDBEnumerator::~nsMsgDBEnumerator()
+{
+ Clear();
+}
+
+void nsMsgDBEnumerator::Clear()
+{
+ mRowCursor = nullptr;
+ mTable = nullptr;
+ mResultHdr = nullptr;
+ if (mDB)
+ mDB->m_enumerators.RemoveElement(this);
+ mDB = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgDBEnumerator, nsISimpleEnumerator)
+
+nsresult nsMsgDBEnumerator::GetRowCursor()
+{
+ mDone = false;
+
+ if (!mDB || !mTable)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mIterateForwards)
+ {
+ mRowPos = -1;
+ }
+ else
+ {
+ mdb_count numRows;
+ mTable->GetCount(mDB->GetEnv(), &numRows);
+ mRowPos = numRows; // startPos is 0 relative.
+ }
+ return mTable->GetTableRowCursor(mDB->GetEnv(), mRowPos, getter_AddRefs(mRowCursor));
+}
+
+NS_IMETHODIMP nsMsgDBEnumerator::GetNext(nsISupports **aItem)
+{
+ if (!aItem)
+ return NS_ERROR_NULL_POINTER;
+ nsresult rv = NS_OK;
+ if (!mNextPrefetched)
+ rv = PrefetchNext();
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mResultHdr)
+ {
+ *aItem = mResultHdr;
+ NS_ADDREF(*aItem);
+ mNextPrefetched = false;
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgDBEnumerator::PrefetchNext()
+{
+ nsresult rv = NS_OK;
+ nsIMdbRow* hdrRow;
+ uint32_t flags;
+
+ if (!mRowCursor)
+ {
+ rv = GetRowCursor();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ do
+ {
+ mResultHdr = nullptr;
+ if (mIterateForwards)
+ rv = mRowCursor->NextRow(mDB->GetEnv(), &hdrRow, &mRowPos);
+ else
+ rv = mRowCursor->PrevRow(mDB->GetEnv(), &hdrRow, &mRowPos);
+ if (!hdrRow)
+ {
+ mDone = true;
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv))
+ {
+ mDone = true;
+ return rv;
+ }
+ //Get key from row
+ mdbOid outOid;
+ nsMsgKey key = nsMsgKey_None;
+ rv = hdrRow->GetOid(mDB->GetEnv(), &outOid);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ key = outOid.mOid_Id;
+
+ rv = mDB->GetHdrFromUseCache(key, getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ hdrRow->Release();
+ else {
+ rv = mDB->CreateMsgHdr(hdrRow, key, getter_AddRefs(mResultHdr));
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ }
+
+ if (mResultHdr)
+ mResultHdr->GetFlags(&flags);
+ else
+ flags = 0;
+ }
+ while (mFilter && NS_FAILED(mFilter(mResultHdr, mClosure)) && !(flags & nsMsgMessageFlags::Expunged));
+
+ if (mResultHdr)
+ {
+ mNextPrefetched = true;
+ return NS_OK;
+ }
+ else
+ mNextPrefetched = false;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBEnumerator::HasMoreElements(bool *aResult)
+{
+ if (!aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!mNextPrefetched && (NS_FAILED(PrefetchNext())))
+ mDone = true;
+ *aResult = !mDone;
+ return NS_OK;
+}
+
+nsMsgFilteredDBEnumerator::nsMsgFilteredDBEnumerator(nsMsgDatabase* db,
+ nsIMdbTable *table,
+ bool reverse,
+ nsIArray *searchTerms)
+ : nsMsgDBEnumerator(db, table, nullptr, nullptr, !reverse)
+{
+}
+
+nsMsgFilteredDBEnumerator::~nsMsgFilteredDBEnumerator()
+{
+}
+
+/**
+ * Create the search session for the enumerator,
+ * add the scope term for "folder" to the search session, and add the search
+ * terms in the array to the search session.
+ */
+nsresult nsMsgFilteredDBEnumerator::InitSearchSession(nsIArray *searchTerms, nsIMsgFolder *folder)
+{
+ nsresult rv;
+ m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, folder);
+ // add each item in termsArray to the search session
+ uint32_t numTerms;
+ rv = searchTerms->GetLength(&numTerms);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < numTerms; i++)
+ {
+ nsCOMPtr <nsIMsgSearchTerm> searchTerm;
+ searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm), getter_AddRefs(searchTerm));
+ m_searchSession->AppendTerm(searchTerm);
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgFilteredDBEnumerator::PrefetchNext()
+{
+ nsresult rv;
+ do
+ {
+ rv = nsMsgDBEnumerator::PrefetchNext();
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ {
+ bool matches;
+ rv = m_searchSession->MatchHdr(mResultHdr, mDB, &matches);
+ if (NS_SUCCEEDED(rv) && matches)
+ break;
+ mResultHdr = nullptr;
+ }
+ else
+ break;
+ } while (mStopPos == -1 || mRowPos != mStopPos);
+
+ if (!mResultHdr)
+ mNextPrefetched = false;
+
+ return rv;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsMsgDatabase::EnumerateMessages(nsISimpleEnumerator* *result)
+{
+ RememberLastUseTime();
+ NS_ENSURE_ARG_POINTER(result);
+ nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
+ nullptr, nullptr);
+ if (!e)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = e);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::ReverseEnumerateMessages(nsISimpleEnumerator* *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
+ nullptr, nullptr, false);
+ if (!e)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = e);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::GetFilterEnumerator(nsIArray *searchTerms, bool aReverse,
+ nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ RefPtr<nsMsgFilteredDBEnumerator> e =
+ new nsMsgFilteredDBEnumerator(this, m_mdbAllMsgHeadersTable, aReverse,
+ searchTerms);
+
+ NS_ENSURE_TRUE(e, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = e->InitSearchSession(searchTerms, m_folder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return CallQueryInterface(e.get(), aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::NextMatchingHdrs(nsISimpleEnumerator *aEnumerator,
+ int32_t aNumHdrsToLookAt, int32_t aMaxResults,
+ nsIMutableArray *aMatchingHdrs,
+ int32_t *aNumMatches, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aEnumerator);
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsMsgFilteredDBEnumerator *enumerator =
+ static_cast<nsMsgFilteredDBEnumerator *> (aEnumerator);
+
+ // Force mRowPos to be initialized.
+ if (!enumerator->mRowCursor)
+ enumerator->GetRowCursor();
+
+ if (aNumHdrsToLookAt)
+ {
+ enumerator->mStopPos = enumerator->mIterateForwards ?
+ enumerator->mRowPos + aNumHdrsToLookAt :
+ enumerator->mRowPos - aNumHdrsToLookAt;
+ if (enumerator->mStopPos < 0)
+ enumerator->mStopPos = 0;
+ }
+ int32_t numMatches = 0;
+ nsresult rv;
+ do
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ nsCOMPtr <nsIMsgDBHdr> nextMessage = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(rv) && nextMessage)
+ {
+ if (aMatchingHdrs)
+ aMatchingHdrs->AppendElement(nextMessage, false);
+ ++numMatches;
+ if (aMaxResults && numMatches == aMaxResults)
+ break;
+ }
+ else
+ break;
+ }
+ while (true);
+
+ if (aNumMatches)
+ *aNumMatches = numMatches;
+
+ *aResult = !enumerator->mDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::SyncCounts()
+{
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ nsresult rv = EnumerateMessages(getter_AddRefs(hdrs));
+ if (NS_FAILED(rv))
+ return rv;
+ bool hasMore = false;
+
+ mdb_count numHdrsInTable = 0;
+ int32_t numUnread = 0;
+ int32_t numHdrs = 0;
+
+ if (m_mdbAllMsgHeadersTable)
+ m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrsInTable);
+ else
+ return NS_ERROR_NULL_POINTER;
+
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = hdrs->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports);
+
+ bool isRead;
+ IsHeaderRead(pHeader, &isRead);
+ if (!isRead)
+ numUnread++;
+ numHdrs++;
+ }
+
+ int32_t oldTotal, oldUnread;
+ (void) m_dbFolderInfo->GetNumUnreadMessages(&oldUnread);
+ (void) m_dbFolderInfo->GetNumMessages(&oldTotal);
+ if (oldUnread != numUnread)
+ m_dbFolderInfo->ChangeNumUnreadMessages(numUnread - oldUnread);
+ if (oldTotal != numHdrs)
+ m_dbFolderInfo->ChangeNumMessages(numHdrs - oldTotal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ListAllKeys(nsIMsgKeyArray *aKeys)
+{
+ NS_ENSURE_ARG_POINTER(aKeys);
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+
+ RememberLastUseTime();
+
+ if (m_mdbAllMsgHeadersTable)
+ {
+ uint32_t numMsgs = 0;
+ m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numMsgs);
+ aKeys->SetCapacity(numMsgs);
+ rv = m_mdbAllMsgHeadersTable->GetTableRowCursor(GetEnv(), -1,
+ getter_AddRefs(rowCursor));
+ while (NS_SUCCEEDED(rv) && rowCursor)
+ {
+ mdbOid outOid;
+ mdb_pos outPos;
+
+ rv = rowCursor->NextRowOid(GetEnv(), &outOid, &outPos);
+ // is this right? Mork is returning a 0 id, but that should valid.
+ if (outPos < 0 || outOid.mOid_Id == (mdb_id) -1)
+ break;
+ if (NS_SUCCEEDED(rv))
+ aKeys->AppendElement(outOid.mOid_Id);
+ }
+ }
+ return rv;
+}
+
+class nsMsgDBThreadEnumerator : public nsISimpleEnumerator, public nsIDBChangeListener
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ NS_DECL_NSIDBCHANGELISTENER
+
+ // nsMsgDBEnumerator methods:
+ typedef nsresult (*nsMsgDBThreadEnumeratorFilter)(nsIMsgThread* thread);
+
+ nsMsgDBThreadEnumerator(nsMsgDatabase* db, nsMsgDBThreadEnumeratorFilter filter);
+
+protected:
+ virtual ~nsMsgDBThreadEnumerator();
+ nsresult GetTableCursor(void);
+ nsresult PrefetchNext();
+ nsMsgDatabase* mDB;
+ nsCOMPtr <nsIMdbPortTableCursor> mTableCursor;
+ nsIMsgThread* mResultThread;
+ bool mDone;
+ bool mNextPrefetched;
+ nsMsgDBThreadEnumeratorFilter mFilter;
+};
+
+nsMsgDBThreadEnumerator::nsMsgDBThreadEnumerator(nsMsgDatabase* db,
+ nsMsgDBThreadEnumeratorFilter filter)
+ : mDB(db), mTableCursor(nullptr), mResultThread(nullptr), mDone(false),
+ mFilter(filter)
+{
+ mDB->AddListener(this);
+ mNextPrefetched = false;
+}
+
+nsMsgDBThreadEnumerator::~nsMsgDBThreadEnumerator()
+{
+ mTableCursor = nullptr;
+ NS_IF_RELEASE(mResultThread);
+ if (mDB)
+ mDB->RemoveListener(this);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgDBThreadEnumerator, nsISimpleEnumerator, nsIDBChangeListener)
+
+
+/* void OnHdrFlagsChanged (in nsIMsgDBHdr aHdrChanged, in unsigned long aOldFlags, in unsigned long aNewFlags, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+//void OnHdrPropertyChanged(in nsIMsgDBHdr aHdrToChange, in bool aPreChange,
+// inout uint32_t aStatus, in nsIDBChangeListener aInstigator);
+NS_IMETHODIMP
+nsMsgDBThreadEnumerator::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus,
+ nsIDBChangeListener * aInstigator)
+{
+ return NS_OK;
+}
+
+/* void onHdrDeleted (in nsIMsgDBHdr aHdrChanged, in nsMsgKey aParentKey, in long aFlags, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void onHdrAdded (in nsIMsgDBHdr aHdrChanged, in nsMsgKey aParentKey, in long aFlags, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnHdrAdded(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void onParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in nsMsgKey newParent, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void onAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
+{
+ mTableCursor = nullptr;
+ NS_IF_RELEASE(mResultThread);
+ mDB->RemoveListener(this);
+ mDB = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
+/* void onReadChanged (in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnReadChanged(nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void onJunkScoreChanged (in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnJunkScoreChanged(nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+nsresult nsMsgDBThreadEnumerator::GetTableCursor(void)
+{
+ nsresult rv = NS_OK;
+
+ if (!mDB || !mDB->m_mdbStore)
+ return NS_ERROR_NULL_POINTER;
+
+ mDB->m_mdbStore->GetPortTableCursor(mDB->GetEnv(), mDB->m_hdrRowScopeToken, mDB->m_threadTableKindToken,
+ getter_AddRefs(mTableCursor));
+
+ if (NS_FAILED(rv))
+ return rv;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBThreadEnumerator::GetNext(nsISupports **aItem)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ *aItem = nullptr;
+ nsresult rv = NS_OK;
+ if (!mNextPrefetched)
+ rv = PrefetchNext();
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mResultThread)
+ {
+ *aItem = mResultThread;
+ NS_ADDREF(mResultThread);
+ mNextPrefetched = false;
+ }
+ }
+ return rv;
+}
+
+
+nsresult nsMsgDBThreadEnumerator::PrefetchNext()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMdbTable> table;
+
+ if (!mDB)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!mTableCursor)
+ {
+ rv = GetTableCursor();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ while (true)
+ {
+ NS_IF_RELEASE(mResultThread);
+ mResultThread = nullptr;
+ rv = mTableCursor->NextTable(mDB->GetEnv(), getter_AddRefs(table));
+ if (!table)
+ {
+ mDone = true;
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv))
+ {
+ mDone = true;
+ return rv;
+ }
+
+ mdbOid tableId;
+ table->GetOid(mDB->GetEnv(), &tableId);
+
+ mResultThread = mDB->FindExistingThread(tableId.mOid_Id);
+ if (!mResultThread)
+ mResultThread = new nsMsgThread(mDB, table);
+
+ if (mResultThread)
+ {
+ uint32_t numChildren = 0;
+ NS_ADDREF(mResultThread);
+ mResultThread->GetNumChildren(&numChildren);
+ // we've got empty thread; don't tell caller about it.
+ if (numChildren == 0)
+ continue;
+ }
+ if (mFilter && NS_FAILED(mFilter(mResultThread)))
+ continue;
+ else
+ break;
+ }
+ if (mResultThread)
+ {
+ mNextPrefetched = true;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBThreadEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!mNextPrefetched)
+ PrefetchNext();
+ *aResult = !mDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::EnumerateThreads(nsISimpleEnumerator* *result)
+{
+ RememberLastUseTime();
+ nsMsgDBThreadEnumerator* e = new nsMsgDBThreadEnumerator(this, nullptr);
+ if (e == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = e);
+ return NS_OK;
+}
+
+// only return headers with a particular flag set
+static nsresult
+nsMsgFlagSetFilter(nsIMsgDBHdr *msg, void *closure)
+{
+ uint32_t msgFlags, desiredFlags;
+ desiredFlags = * (uint32_t *) closure;
+ msg->GetFlags(&msgFlags);
+ return (msgFlags & desiredFlags) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsMsgDatabase::EnumerateMessagesWithFlag(nsISimpleEnumerator* *result, uint32_t *pFlag)
+{
+ RememberLastUseTime();
+
+ nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, nsMsgFlagSetFilter, pFlag);
+ if (!e)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = e);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::CreateNewHdr(nsMsgKey key, nsIMsgDBHdr **pnewHdr)
+{
+ nsresult err = NS_OK;
+ nsIMdbRow *hdrRow = nullptr;
+ struct mdbOid allMsgHdrsTableOID;
+
+ if (!pnewHdr || !m_mdbAllMsgHeadersTable || !m_mdbStore)
+ return NS_ERROR_NULL_POINTER;
+
+ if (key != nsMsgKey_None)
+ {
+ allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
+ allMsgHdrsTableOID.mOid_Id = key; // presumes 0 is valid key value
+
+ err = m_mdbStore->GetRow(GetEnv(), &allMsgHdrsTableOID, &hdrRow);
+ if (!hdrRow)
+ err = m_mdbStore->NewRowWithOid(GetEnv(), &allMsgHdrsTableOID, &hdrRow);
+ }
+ else
+ {
+ // Mork will assign an ID to the new row, generally the next available ID.
+ err = m_mdbStore->NewRow(GetEnv(), m_hdrRowScopeToken, &hdrRow);
+ if (hdrRow)
+ {
+ struct mdbOid oid;
+ hdrRow->GetOid(GetEnv(), &oid);
+ key = oid.mOid_Id;
+ }
+ else
+ {
+ // We failed to create a new row. That can happen if we run out of keys,
+ // which will force a reparse.
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ if (NS_SUCCEEDED(ListAllKeys(keys)))
+ {
+ uint32_t numKeys;
+ keys->GetLength(&numKeys);
+ for (uint32_t i = 0; i < numKeys; i++)
+ {
+ if (keys->m_keys[i] >= kForceReparseKey)
+ {
+ // Force a reparse.
+ if (m_dbFolderInfo)
+ m_dbFolderInfo->SetBooleanProperty("forceReparse", true);
+ break;
+ }
+ }
+ }
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ }
+ if (NS_FAILED(err))
+ return err;
+ err = CreateMsgHdr(hdrRow, key, pnewHdr);
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::AddNewHdrToDB(nsIMsgDBHdr *newHdr, bool notify)
+{
+ NS_ENSURE_ARG_POINTER(newHdr);
+ nsMsgHdr* hdr = static_cast<nsMsgHdr*>(newHdr); // closed system, cast ok
+ bool newThread;
+ bool hasKey = false;
+ nsMsgKey msgKey = nsMsgKey_None;
+ (void)hdr->GetMessageKey(&msgKey);
+ (void)ContainsKey(msgKey, &hasKey);
+ if (hasKey)
+ {
+ NS_ERROR("adding hdr that already exists");
+ return NS_ERROR_FAILURE;
+ }
+ nsresult err = ThreadNewHdr(hdr, newThread);
+ // we thread header before we add it to the all headers table
+ // so that subject and reference threading will work (otherwise,
+ // when we try to find the first header with the same subject or
+ // reference, we get the new header!)
+ if (NS_SUCCEEDED(err))
+ {
+ nsMsgKey key;
+ uint32_t flags;
+
+ newHdr->GetMessageKey(&key);
+ hdr->GetRawFlags(&flags);
+ // use raw flags instead of GetFlags, because GetFlags will
+ // pay attention to what's in m_newSet, and this new hdr isn't
+ // in m_newSet yet.
+ if (flags & nsMsgMessageFlags::New)
+ {
+ uint32_t newFlags;
+ newHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags); // make sure not filed out
+ AddToNewList(key);
+ }
+ if (m_dbFolderInfo != NULL)
+ {
+ m_dbFolderInfo->ChangeNumMessages(1);
+ bool isRead = true;
+ IsHeaderRead(newHdr, &isRead);
+ if (!isRead)
+ m_dbFolderInfo->ChangeNumUnreadMessages(1);
+ m_dbFolderInfo->OnKeyAdded(key);
+ }
+
+ err = m_mdbAllMsgHeadersTable->AddRow(GetEnv(), hdr->GetMDBRow());
+ if (notify)
+ {
+ nsMsgKey threadParent;
+
+ newHdr->GetThreadParent(&threadParent);
+ NotifyHdrAddedAll(newHdr, threadParent, flags, NULL);
+ }
+
+ if (UseCorrectThreading())
+ err = AddMsgRefsToHash(newHdr);
+ }
+ NS_ASSERTION(NS_SUCCEEDED(err), "error creating thread");
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::CopyHdrFromExistingHdr(nsMsgKey key, nsIMsgDBHdr *existingHdr, bool addHdrToDB, nsIMsgDBHdr **newHdr)
+{
+ nsresult err = NS_OK;
+
+ if (existingHdr)
+ {
+ nsMsgHdr* sourceMsgHdr = static_cast<nsMsgHdr*>(existingHdr); // closed system, cast ok
+ nsMsgHdr *destMsgHdr = nullptr;
+ CreateNewHdr(key, (nsIMsgDBHdr **) &destMsgHdr);
+ nsIMdbRow *sourceRow = sourceMsgHdr->GetMDBRow();
+ if (!destMsgHdr || !sourceRow)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsIMdbRow *destRow = destMsgHdr->GetMDBRow();
+ if (!destRow)
+ return NS_ERROR_UNEXPECTED;
+
+ err = destRow->SetRow(GetEnv(), sourceRow);
+ if (NS_SUCCEEDED(err))
+ {
+ // we may have gotten the header from a cache - calling SetRow
+ // basically invalidates any cached values, so invalidate them.
+ destMsgHdr->ClearCachedValues();
+ if(addHdrToDB)
+ err = AddNewHdrToDB(destMsgHdr, true);
+ if (NS_SUCCEEDED(err) && newHdr)
+ *newHdr = destMsgHdr;
+ }
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::RowCellColumnTonsString(nsIMdbRow *hdrRow, mdb_token columnToken, nsAString &resultStr)
+{
+ NS_ENSURE_ARG_POINTER(hdrRow);
+
+ struct mdbYarn yarn;
+ nsresult rv = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ YarnTonsString(&yarn, resultStr);
+ return NS_OK;
+}
+
+// as long as the row still exists, and isn't changed, the returned const char ** will be valid.
+// But be very careful using this data - the caller should never return it in turn to another caller.
+nsresult nsMsgDatabase::RowCellColumnToConstCharPtr(nsIMdbRow *hdrRow, mdb_token columnToken, const char **ptr)
+{
+ NS_ENSURE_ARG_POINTER(hdrRow);
+
+ struct mdbYarn yarn;
+ nsresult rv = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *ptr = (const char*)yarn.mYarn_Buf;
+ return NS_OK;
+}
+
+nsIMimeConverter *nsMsgDatabase::GetMimeConverter()
+{
+ if (!m_mimeConverter)
+ {
+ // apply mime decode
+ m_mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID);
+ }
+ return m_mimeConverter;
+}
+
+nsresult nsMsgDatabase::GetEffectiveCharset(nsIMdbRow *row, nsACString &resultCharset)
+{
+ resultCharset.Truncate();
+ bool characterSetOverride;
+ m_dbFolderInfo->GetCharacterSetOverride(&characterSetOverride);
+ nsresult rv = RowCellColumnToCharPtr(row, m_messageCharSetColumnToken, getter_Copies(resultCharset));
+ if (NS_FAILED(rv) || resultCharset.IsEmpty() ||
+ resultCharset.Equals("us-ascii") || characterSetOverride)
+ {
+ rv = m_dbFolderInfo->GetEffectiveCharacterSet(resultCharset);
+ }
+ return rv;
+}
+
+nsresult nsMsgDatabase::RowCellColumnToMime2DecodedString(nsIMdbRow *row, mdb_token columnToken, nsAString &resultStr)
+{
+ nsresult err = NS_OK;
+ const char *nakedString = nullptr;
+ err = RowCellColumnToConstCharPtr(row, columnToken, &nakedString);
+ if (NS_SUCCEEDED(err) && nakedString && strlen(nakedString))
+ {
+ GetMimeConverter();
+ if (m_mimeConverter)
+ {
+ nsAutoString decodedStr;
+ nsCString charSet;
+ GetEffectiveCharset(row, charSet);
+
+ err = m_mimeConverter->DecodeMimeHeader(nakedString, charSet.get(),
+ false, true, resultStr);
+ }
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::RowCellColumnToAddressCollationKey(nsIMdbRow *row, mdb_token colToken, uint8_t **result, uint32_t *len)
+{
+ nsString sender;
+ nsresult rv = RowCellColumnToMime2DecodedString(row, colToken, sender);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString name;
+ ExtractName(DecodedHeader(sender), name);
+ return CreateCollationKey(name, len, result);
+}
+
+nsresult nsMsgDatabase::GetCollationKeyGenerator()
+{
+ nsresult err = NS_OK;
+ if (!m_collationKeyGenerator)
+ {
+ nsCOMPtr <nsILocale> locale;
+ nsAutoString localeName;
+
+ // get a locale service
+ nsCOMPtr <nsILocaleService> localeService = do_GetService(NS_LOCALESERVICE_CONTRACTID, &err);
+ if (NS_SUCCEEDED(err))
+ {
+ // do this for a new db if no UI to be provided for locale selection
+ err = localeService->GetApplicationLocale(getter_AddRefs(locale));
+
+ if (locale)
+ {
+ // or generate a locale from a stored locale name ("en_US", "fr_FR")
+ //err = localeFactory->NewLocale(&localeName, &locale);
+
+ nsCOMPtr <nsICollationFactory> f = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &err);
+ if (NS_SUCCEEDED(err) && f)
+ {
+ // get a collation interface instance
+ err = f->CreateCollation(locale, getter_AddRefs(m_collationKeyGenerator));
+ }
+ }
+ }
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::RowCellColumnToCollationKey(nsIMdbRow *row, mdb_token columnToken, uint8_t **result, uint32_t *len)
+{
+ const char *nakedString = nullptr;
+ nsresult err;
+
+ err = RowCellColumnToConstCharPtr(row, columnToken, &nakedString);
+ if (!nakedString)
+ nakedString = "";
+ if (NS_SUCCEEDED(err))
+ {
+ GetMimeConverter();
+ if (m_mimeConverter)
+ {
+ nsCString decodedStr;
+ nsCString charSet;
+ GetEffectiveCharset(row, charSet);
+
+ err = m_mimeConverter->DecodeMimeHeaderToUTF8(
+ nsDependentCString(nakedString), charSet.get(), false,
+ true, decodedStr);
+ if (NS_SUCCEEDED(err))
+ err = CreateCollationKey(NS_ConvertUTF8toUTF16(decodedStr), len, result);
+ }
+ }
+ return err;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::CompareCollationKeys(uint32_t len1, uint8_t *key1, uint32_t len2,
+ uint8_t *key2, int32_t *result)
+{
+ nsresult rv = GetCollationKeyGenerator();
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!m_collationKeyGenerator) return NS_ERROR_FAILURE;
+
+ rv = m_collationKeyGenerator->CompareRawSortKey(key1,len1,key2,len2,result);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::CreateCollationKey(const nsAString& sourceString, uint32_t *len,
+ uint8_t **result)
+{
+ nsresult err = GetCollationKeyGenerator();
+ NS_ENSURE_SUCCESS(err,err);
+ if (!m_collationKeyGenerator) return NS_ERROR_FAILURE;
+
+ err = m_collationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, sourceString, result, len);
+ NS_ENSURE_SUCCESS(err,err);
+ return err;
+}
+
+nsresult nsMsgDatabase::RowCellColumnToUInt32(nsIMdbRow *hdrRow, mdb_token columnToken, uint32_t &uint32Result, uint32_t defaultValue)
+{
+ return RowCellColumnToUInt32(hdrRow, columnToken, &uint32Result, defaultValue);
+}
+
+nsresult nsMsgDatabase::RowCellColumnToUInt32(nsIMdbRow *hdrRow, mdb_token columnToken, uint32_t *uint32Result, uint32_t defaultValue)
+{
+ nsresult err = NS_OK;
+
+ if (uint32Result)
+ *uint32Result = defaultValue;
+ if (hdrRow) // ### probably should be an error if hdrRow is NULL...
+ {
+ struct mdbYarn yarn;
+ err = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
+ if (NS_SUCCEEDED(err))
+ YarnToUInt32(&yarn, uint32Result);
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::UInt32ToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, uint32_t value)
+{
+ struct mdbYarn yarn;
+ char yarnBuf[100];
+
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ yarn.mYarn_Buf = (void *) yarnBuf;
+ yarn.mYarn_Size = sizeof(yarnBuf);
+ yarn.mYarn_Fill = yarn.mYarn_Size;
+ yarn.mYarn_Form = 0;
+ yarn.mYarn_Grow = NULL;
+ return row->AddColumn(GetEnv(), columnToken, UInt32ToYarn(&yarn, value));
+}
+
+nsresult nsMsgDatabase::UInt64ToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, uint64_t value)
+{
+ NS_ENSURE_ARG_POINTER(row);
+ struct mdbYarn yarn;
+ char yarnBuf[17]; // max string is 16 bytes, + 1 for null.
+
+ yarn.mYarn_Buf = (void *) yarnBuf;
+ yarn.mYarn_Size = sizeof(yarnBuf);
+ yarn.mYarn_Form = 0;
+ yarn.mYarn_Grow = NULL;
+ PR_snprintf((char *) yarn.mYarn_Buf, yarn.mYarn_Size, "%llx", value);
+ yarn.mYarn_Fill = PL_strlen((const char *) yarn.mYarn_Buf);
+ return row->AddColumn(GetEnv(), columnToken, &yarn);
+}
+
+nsresult
+nsMsgDatabase::RowCellColumnToUInt64(nsIMdbRow *hdrRow, mdb_token columnToken,
+ uint64_t *uint64Result,
+ uint64_t defaultValue)
+{
+ nsresult err = NS_OK;
+
+ if (uint64Result)
+ *uint64Result = defaultValue;
+ if (hdrRow) // ### probably should be an error if hdrRow is NULL...
+ {
+ struct mdbYarn yarn;
+ err = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
+ if (NS_SUCCEEDED(err))
+ YarnToUInt64(&yarn, uint64Result);
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::CharPtrToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, const char *charPtr)
+{
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ struct mdbYarn yarn;
+ yarn.mYarn_Buf = (void *) charPtr;
+ yarn.mYarn_Size = PL_strlen((const char *) yarn.mYarn_Buf) + 1;
+ yarn.mYarn_Fill = yarn.mYarn_Size - 1;
+ yarn.mYarn_Form = 0; // what to do with this? we're storing csid in the msg hdr...
+
+ return row->AddColumn(GetEnv(), columnToken, &yarn);
+}
+
+// caller must NS_Free result
+nsresult nsMsgDatabase::RowCellColumnToCharPtr(nsIMdbRow *row, mdb_token columnToken, char **result)
+{
+ nsresult err = NS_ERROR_NULL_POINTER;
+
+ if (row && result)
+ {
+ struct mdbYarn yarn;
+ err = row->AliasCellYarn(GetEnv(), columnToken, &yarn);
+ if (NS_SUCCEEDED(err))
+ {
+ *result = (char *)NS_Alloc(yarn.mYarn_Fill + 1);
+ if (*result)
+ {
+ if (yarn.mYarn_Fill > 0)
+ memcpy(*result, yarn.mYarn_Buf, yarn.mYarn_Fill);
+ (*result)[yarn.mYarn_Fill] = '\0';
+ }
+ else
+ err = NS_ERROR_OUT_OF_MEMORY;
+
+ }
+ }
+ return err;
+}
+
+
+
+/* static */struct mdbYarn *nsMsgDatabase::nsStringToYarn(struct mdbYarn *yarn, const nsAString &str)
+{
+ yarn->mYarn_Buf = ToNewCString(NS_ConvertUTF16toUTF8(str));
+ yarn->mYarn_Size = str.Length() + 1;
+ yarn->mYarn_Fill = yarn->mYarn_Size - 1;
+ yarn->mYarn_Form = 0; // what to do with this? we're storing csid in the msg hdr...
+ return yarn;
+}
+
+/* static */struct mdbYarn *nsMsgDatabase::UInt32ToYarn(struct mdbYarn *yarn, uint32_t i)
+{
+ PR_snprintf((char *) yarn->mYarn_Buf, yarn->mYarn_Size, "%lx", i);
+ yarn->mYarn_Fill = PL_strlen((const char *) yarn->mYarn_Buf);
+ yarn->mYarn_Form = 0; // what to do with this? Should be parsed out of the mime2 header?
+ return yarn;
+}
+
+/* static */struct mdbYarn *nsMsgDatabase::UInt64ToYarn(struct mdbYarn *yarn, uint64_t i)
+{
+ PR_snprintf((char *) yarn->mYarn_Buf, yarn->mYarn_Size, "%llx", i);
+ yarn->mYarn_Fill = PL_strlen((const char *) yarn->mYarn_Buf);
+ yarn->mYarn_Form = 0;
+ return yarn;
+}
+
+/* static */void nsMsgDatabase::YarnTonsString(struct mdbYarn *yarn, nsAString &str)
+{
+ const char* buf = (const char*)yarn->mYarn_Buf;
+ if (buf)
+ CopyASCIItoUTF16(Substring(buf, buf + yarn->mYarn_Fill), str);
+ else
+ str.Truncate();
+}
+
+/* static */void nsMsgDatabase::YarnTonsCString(struct mdbYarn *yarn, nsACString &str)
+{
+ const char* buf = (const char*)yarn->mYarn_Buf;
+ if (buf)
+ str.Assign(buf, yarn->mYarn_Fill);
+ else
+ str.Truncate();
+}
+
+// WARNING - if yarn is empty, *pResult will not be changed!!!!
+// this is so we can leave default values as they were.
+/* static */void nsMsgDatabase::YarnToUInt32(struct mdbYarn *yarn, uint32_t *pResult)
+{
+ uint8_t numChars = std::min<mdb_fill>(8, yarn->mYarn_Fill);
+
+ if (numChars == 0)
+ return;
+
+ *pResult = MsgUnhex((char *) yarn->mYarn_Buf, numChars);
+}
+
+// WARNING - if yarn is empty, *pResult will not be changed!!!!
+// this is so we can leave default values as they were.
+/* static */void nsMsgDatabase::YarnToUInt64(struct mdbYarn *yarn, uint64_t *pResult)
+{
+ uint8_t numChars = std::min<mdb_fill>(16, yarn->mYarn_Fill);
+
+ if (numChars == 0)
+ return;
+
+ *pResult = MsgUnhex((char *) yarn->mYarn_Buf, numChars);
+}
+
+nsresult nsMsgDatabase::GetProperty(nsIMdbRow *row, const char *propertyName, char **result)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ if (m_mdbStore)
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ else
+ err = NS_ERROR_NULL_POINTER;
+ if (NS_SUCCEEDED(err))
+ err = RowCellColumnToCharPtr(row, property_token, result);
+
+ return err;
+}
+
+nsresult nsMsgDatabase::SetProperty(nsIMdbRow *row, const char *propertyName, const char *propertyVal)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ CharPtrToRowCellColumn(row, property_token, propertyVal);
+ return err;
+}
+
+nsresult nsMsgDatabase::GetPropertyAsNSString(nsIMdbRow *row, const char *propertyName, nsAString &result)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ err = RowCellColumnTonsString(row, property_token, result);
+
+ return err;
+}
+
+nsresult nsMsgDatabase::SetPropertyFromNSString(nsIMdbRow *row, const char *propertyName, const nsAString &propertyVal)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ return SetNSStringPropertyWithToken(row, property_token, propertyVal);
+
+ return err;
+}
+
+
+nsresult nsMsgDatabase::GetUint32Property(nsIMdbRow *row, const char *propertyName, uint32_t *result, uint32_t defaultValue)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ err = RowCellColumnToUInt32(row, property_token, result, defaultValue);
+
+ return err;
+}
+
+nsresult nsMsgDatabase::GetUint64Property(nsIMdbRow *row, const char *propertyName, uint64_t *result, uint64_t defaultValue)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ err = RowCellColumnToUInt64(row, property_token, result, defaultValue);
+
+ return err;
+}
+
+nsresult nsMsgDatabase::SetUint32Property(nsIMdbRow *row, const char *propertyName, uint32_t propertyVal)
+{
+ struct mdbYarn yarn;
+ char int32StrBuf[20];
+ yarn.mYarn_Buf = int32StrBuf;
+ yarn.mYarn_Size = sizeof(int32StrBuf);
+ yarn.mYarn_Fill = sizeof(int32StrBuf);
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ mdb_token property_token;
+
+ nsresult err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ {
+ UInt32ToYarn(&yarn, propertyVal);
+ err = row->AddColumn(GetEnv(), property_token, &yarn);
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::SetUint64Property(nsIMdbRow *row,
+ const char *propertyName,
+ uint64_t propertyVal)
+{
+ struct mdbYarn yarn;
+ char int64StrBuf[100];
+ yarn.mYarn_Buf = int64StrBuf;
+ yarn.mYarn_Size = sizeof(int64StrBuf);
+ yarn.mYarn_Fill = sizeof(int64StrBuf);
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ mdb_token property_token;
+
+ nsresult err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ {
+ UInt64ToYarn(&yarn, propertyVal);
+ err = row->AddColumn(GetEnv(), property_token, &yarn);
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::GetBooleanProperty(nsIMdbRow *row, const char *propertyName,
+ bool *result,
+ bool defaultValue /* = false */)
+{
+ uint32_t res;
+ nsresult rv = GetUint32Property(row, propertyName, &res, (uint32_t) defaultValue);
+ *result = !!res;
+ return rv;
+}
+
+nsresult nsMsgDatabase::SetBooleanProperty(nsIMdbRow *row, const char *propertyName,
+ bool propertyVal)
+{
+ return SetUint32Property(row, propertyName, (uint32_t) propertyVal);
+}
+
+nsresult nsMsgDatabase::SetNSStringPropertyWithToken(nsIMdbRow *row, mdb_token aProperty, const nsAString &propertyStr)
+{
+ NS_ENSURE_ARG(row);
+ struct mdbYarn yarn;
+
+ yarn.mYarn_Grow = NULL;
+ nsresult err = row->AddColumn(GetEnv(), aProperty, nsStringToYarn(&yarn, propertyStr));
+ free((char *)yarn.mYarn_Buf); // won't need this when we have nsCString
+ return err;
+}
+
+
+uint32_t nsMsgDatabase::GetCurVersion()
+{
+ return kMsgDBVersion;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetSummaryValid(bool valid /* = true */)
+{
+ // If the file was invalid when opened (for example in folder compact), then it may
+ // not have been added to the cache. Add it now if missing.
+ if (valid)
+ {
+ nsCOMPtr<nsIMsgDBService> serv(mozilla::services::GetDBService());
+ static_cast<nsMsgDBService*>(serv.get())->EnsureCached(this);
+ }
+ // setting the version to 0 ought to make it pretty invalid.
+ if (m_dbFolderInfo)
+ m_dbFolderInfo->SetVersion(valid ? GetCurVersion() : 0);
+
+ // for default db (and news), there's no nothing to set to make it it valid
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::GetSummaryValid(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+
+// protected routines
+
+// should we thread messages with common subjects that don't start with Re: together?
+// I imagine we might have separate preferences for mail and news, so this is a virtual method.
+bool nsMsgDatabase::ThreadBySubjectWithoutRe()
+{
+ GetGlobalPrefs();
+ return gThreadWithoutRe;
+}
+
+bool nsMsgDatabase::UseStrictThreading()
+{
+ GetGlobalPrefs();
+ return gStrictThreading;
+}
+
+// Should we make sure messages are always threaded correctly (see bug 181446)
+bool nsMsgDatabase::UseCorrectThreading()
+{
+ GetGlobalPrefs();
+ return gCorrectThreading;
+}
+
+// adapted from removed PL_DHashFreeStringKey
+static void
+msg_DHashFreeStringKey(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
+{
+ const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry;
+ free((void*)stub->key);
+ PLDHashTable::ClearEntryStub(aTable, aEntry);
+}
+
+PLDHashTableOps nsMsgDatabase::gRefHashTableOps =
+{
+ PLDHashTable::HashStringKey,
+ PLDHashTable::MatchStringKey,
+ PLDHashTable::MoveEntryStub,
+ msg_DHashFreeStringKey,
+ nullptr
+};
+
+nsresult nsMsgDatabase::GetRefFromHash(nsCString &reference, nsMsgKey *threadId)
+{
+ // Initialize the reference hash
+ if (!m_msgReferences)
+ {
+ nsresult rv = InitRefHash();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Find reference from the hash
+ PLDHashEntryHdr *entry =
+ m_msgReferences->Search((const void *) reference.get());
+ if (entry)
+ {
+ RefHashElement *element = static_cast<RefHashElement *>(entry);
+ *threadId = element->mThreadId;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsMsgDatabase::AddRefToHash(nsCString &reference, nsMsgKey threadId)
+{
+ if (m_msgReferences)
+ {
+ PLDHashEntryHdr *entry = m_msgReferences->Add((void *) reference.get(), mozilla::fallible);
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY; // XXX out of memory
+
+ RefHashElement *element = static_cast<RefHashElement *>(entry);
+ if (!element->mRef)
+ {
+ element->mRef = ToNewCString(reference); // Will be freed in msg_DHashFreeStringKey()
+ element->mThreadId = threadId;
+ element->mCount = 1;
+ }
+ else
+ element->mCount++;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::AddMsgRefsToHash(nsIMsgDBHdr *msgHdr)
+{
+ uint16_t numReferences = 0;
+ nsMsgKey threadId;
+ nsresult rv = NS_OK;
+
+ msgHdr->GetThreadId(&threadId);
+ msgHdr->GetNumReferences(&numReferences);
+
+ for (int32_t i = 0; i < numReferences; i++)
+ {
+ nsAutoCString reference;
+
+ msgHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty())
+ break;
+
+ rv = AddRefToHash(reference, threadId);
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ return rv;
+}
+
+nsresult nsMsgDatabase::RemoveRefFromHash(nsCString &reference)
+{
+ if (m_msgReferences)
+ {
+ PLDHashEntryHdr *entry =
+ m_msgReferences->Search((const void *) reference.get());
+ if (entry)
+ {
+ RefHashElement *element = static_cast<RefHashElement *>(entry);
+ if (--element->mCount == 0)
+ m_msgReferences->Remove((void *) reference.get());
+ }
+ }
+ return NS_OK;
+}
+
+// Filter only messages with one or more references
+nsresult nsMsgDatabase::RemoveMsgRefsFromHash(nsIMsgDBHdr *msgHdr)
+{
+ uint16_t numReferences = 0;
+ nsresult rv = NS_OK;
+
+ msgHdr->GetNumReferences(&numReferences);
+
+ for (int32_t i = 0; i < numReferences; i++)
+ {
+ nsAutoCString reference;
+
+ msgHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty())
+ break;
+
+ rv = RemoveRefFromHash(reference);
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ return rv;
+}
+
+static nsresult nsReferencesOnlyFilter(nsIMsgDBHdr *msg, void *closure)
+{
+ uint16_t numReferences = 0;
+ msg->GetNumReferences(&numReferences);
+ return (numReferences) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsMsgDatabase::InitRefHash()
+{
+ // Delete an existing table just in case
+ if (m_msgReferences)
+ delete m_msgReferences;
+
+ // Create new table
+ m_msgReferences = new PLDHashTable(&gRefHashTableOps, sizeof(struct RefHashElement), MSG_HASH_SIZE);
+ if (!m_msgReferences)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Create enumerator to go through all messages with references
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ enumerator = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, nsReferencesOnlyFilter, nullptr);
+ if (enumerator == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Populate table with references of existing messages
+ bool hasMore;
+ nsresult rv = NS_OK;
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryInterface(supports);
+ if (msgHdr && NS_SUCCEEDED(rv))
+ rv = AddMsgRefsToHash(msgHdr);
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ return rv;
+}
+
+nsresult nsMsgDatabase::CreateNewThread(nsMsgKey threadId, const char *subject, nsMsgThread **pnewThread)
+{
+ nsresult err = NS_OK;
+ nsCOMPtr<nsIMdbTable> threadTable;
+ struct mdbOid threadTableOID;
+ struct mdbOid allThreadsTableOID;
+
+ if (!pnewThread || !m_mdbStore)
+ return NS_ERROR_NULL_POINTER;
+
+ threadTableOID.mOid_Scope = m_hdrRowScopeToken;
+ threadTableOID.mOid_Id = threadId;
+
+ // Under some circumstances, mork seems to reuse an old table when we create one.
+ // Prevent problems from that by finding any old table first, and deleting its rows.
+ nsresult res = GetStore()->GetTable(GetEnv(), &threadTableOID, getter_AddRefs(threadTable));
+ if (NS_SUCCEEDED(res) && threadTable)
+ threadTable->CutAllRows(GetEnv());
+
+ err = GetStore()->NewTableWithOid(GetEnv(), &threadTableOID, m_threadTableKindToken,
+ false, nullptr, getter_AddRefs(threadTable));
+ if (NS_FAILED(err))
+ return err;
+
+ allThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
+ allThreadsTableOID.mOid_Id = threadId;
+
+ // add a row for this thread in the table of all threads that we'll use
+ // to do our mapping between subject strings and threads.
+ nsCOMPtr<nsIMdbRow> threadRow;
+
+ err = m_mdbStore->GetRow(GetEnv(), &allThreadsTableOID,
+ getter_AddRefs(threadRow));
+ if (!threadRow)
+ {
+ err = m_mdbStore->NewRowWithOid(GetEnv(), &allThreadsTableOID,
+ getter_AddRefs(threadRow));
+ if (NS_SUCCEEDED(err) && threadRow)
+ {
+ if (m_mdbAllThreadsTable)
+ m_mdbAllThreadsTable->AddRow(GetEnv(), threadRow);
+ err = CharPtrToRowCellColumn(threadRow, m_threadSubjectColumnToken, subject);
+ }
+ }
+ else
+ {
+#ifdef DEBUG_David_Bienvenu
+ NS_WARNING("odd that thread row already exists");
+#endif
+ threadRow->CutAllColumns(GetEnv());
+ nsCOMPtr<nsIMdbRow> metaRow;
+ threadTable->GetMetaRow(GetEnv(), nullptr, nullptr, getter_AddRefs(metaRow));
+ if (metaRow)
+ metaRow->CutAllColumns(GetEnv());
+
+ CharPtrToRowCellColumn(threadRow, m_threadSubjectColumnToken, subject);
+ }
+
+
+ *pnewThread = new nsMsgThread(this, threadTable);
+ if (*pnewThread)
+ {
+ (*pnewThread)->SetThreadKey(threadId);
+ m_cachedThread = *pnewThread;
+ m_cachedThreadId = threadId;
+ }
+ return err;
+}
+
+
+nsIMsgThread *nsMsgDatabase::GetThreadForReference(nsCString &msgID, nsIMsgDBHdr **pMsgHdr)
+{
+ nsMsgKey threadId;
+ nsIMsgDBHdr *msgHdr = nullptr;
+ GetMsgHdrForMessageID(msgID.get(), &msgHdr);
+ nsIMsgThread *thread = NULL;
+
+ if (msgHdr != NULL)
+ {
+ if (NS_SUCCEEDED(msgHdr->GetThreadId(&threadId)))
+ {
+ // find thread header for header whose message id we matched.
+ thread = GetThreadForThreadId(threadId);
+ }
+ if (pMsgHdr)
+ *pMsgHdr = msgHdr;
+ else
+ msgHdr->Release();
+ }
+ // Referenced message not found, check if there are messages that reference same message
+ else if (UseCorrectThreading())
+ {
+ if (NS_SUCCEEDED(GetRefFromHash(msgID, &threadId)))
+ thread = GetThreadForThreadId(threadId);
+ }
+
+ return thread;
+}
+
+nsIMsgThread * nsMsgDatabase::GetThreadForSubject(nsCString &subject)
+{
+ nsIMsgThread *thread = nullptr;
+
+ mdbYarn subjectYarn;
+
+ subjectYarn.mYarn_Buf = (void*)subject.get();
+ subjectYarn.mYarn_Fill = PL_strlen(subject.get());
+ subjectYarn.mYarn_Form = 0;
+ subjectYarn.mYarn_Size = subjectYarn.mYarn_Fill;
+
+ nsCOMPtr <nsIMdbRow> threadRow;
+ mdbOid outRowId;
+ if (m_mdbStore)
+ {
+ nsresult result = m_mdbStore->FindRow(GetEnv(), m_threadRowScopeToken,
+ m_threadSubjectColumnToken, &subjectYarn, &outRowId, getter_AddRefs(threadRow));
+ if (NS_SUCCEEDED(result) && threadRow)
+ {
+ //Get key from row
+ mdbOid outOid;
+ nsMsgKey key = nsMsgKey_None;
+ if (NS_SUCCEEDED(threadRow->GetOid(GetEnv(), &outOid)))
+ key = outOid.mOid_Id;
+ // find thread header for header whose message id we matched.
+ // It is fine if key was not found,
+ // GetThreadForThreadId(nsMsgKey_None) returns nullptr.
+ thread = GetThreadForThreadId(key);
+ }
+#ifdef DEBUG_bienvenu1
+ else
+ {
+ nsresult rv;
+ RefPtr<nsMsgThread> pThread;
+
+ nsCOMPtr <nsIMdbPortTableCursor> tableCursor;
+ m_mdbStore->GetPortTableCursor(GetEnv(), m_hdrRowScopeToken, m_threadTableKindToken,
+ getter_AddRefs(tableCursor));
+
+ nsCOMPtr<nsIMdbTable> table;
+
+ while (true)
+ {
+ rv = tableCursor->NextTable(GetEnv(), getter_AddRefs(table));
+ if (!table)
+ break;
+ if (NS_FAILED(rv))
+ break;
+
+ pThread = new nsMsgThread(this, table);
+ if (pThread)
+ {
+ nsCString curSubject;
+ pThread->GetSubject(curSubject);
+ if (subject.Equals(curSubject))
+ {
+ NS_ERROR("thread with subject exists, but FindRow didn't find it\n");
+ break;
+ }
+ }
+ else
+ break;
+ }
+ }
+#endif
+ }
+ return thread;
+}
+
+// Returns thread that contains a message that references the passed message ID
+nsIMsgThread *nsMsgDatabase::GetThreadForMessageId(nsCString &msgId)
+{
+ nsIMsgThread *thread = NULL;
+ nsMsgKey threadId;
+
+ if (NS_SUCCEEDED(GetRefFromHash(msgId, &threadId)))
+ thread = GetThreadForThreadId(threadId);
+
+ return thread;
+}
+
+nsresult nsMsgDatabase::ThreadNewHdr(nsMsgHdr* newHdr, bool &newThread)
+{
+ nsresult result=NS_ERROR_UNEXPECTED;
+ nsCOMPtr <nsIMsgThread> thread;
+ nsCOMPtr <nsIMsgDBHdr> replyToHdr;
+ nsMsgKey threadId = nsMsgKey_None, newHdrKey;
+
+ if (!newHdr)
+ return NS_ERROR_NULL_POINTER;
+
+ newHdr->SetThreadParent(nsMsgKey_None); // if we're undoing, could have a thread parent
+ uint16_t numReferences = 0;
+ uint32_t newHdrFlags = 0;
+
+ // use raw flags instead of GetFlags, because GetFlags will
+ // pay attention to what's in m_newSet, and this new hdr isn't
+ // in m_newSet yet.
+ newHdr->GetRawFlags(&newHdrFlags);
+ newHdr->GetNumReferences(&numReferences);
+ newHdr->GetMessageKey(&newHdrKey);
+
+ // try reference threading first
+ for (int32_t i = numReferences - 1; i >= 0; i--)
+ {
+ nsAutoCString reference;
+
+ newHdr->GetStringReference(i, reference);
+ // first reference we have hdr for is best top-level hdr.
+ // but we have to handle case of promoting new header to top-level
+ // in case the top-level header comes after a reply.
+
+ if (reference.IsEmpty())
+ break;
+
+ thread = dont_AddRef(GetThreadForReference(reference, getter_AddRefs(replyToHdr))) ;
+ if (thread)
+ {
+ if (replyToHdr)
+ {
+ nsMsgKey replyToKey;
+ replyToHdr->GetMessageKey(&replyToKey);
+ // message claims to be a reply to itself - ignore that since it leads to corrupt threading.
+ if (replyToKey == newHdrKey)
+ {
+ // bad references - throw them all away.
+ newHdr->SetMessageId("");
+ thread = nullptr;
+ break;
+ }
+ }
+ thread->GetThreadKey(&threadId);
+ newHdr->SetThreadId(threadId);
+ result = AddToThread(newHdr, thread, replyToHdr, true);
+ break;
+ }
+ }
+ // if user hasn't said "only thread by ref headers", thread by subject
+ if (!thread && !UseStrictThreading())
+ {
+ // try subject threading if we couldn't find a reference and the subject starts with Re:
+ nsCString subject;
+ newHdr->GetSubject(getter_Copies(subject));
+ if (ThreadBySubjectWithoutRe() || (newHdrFlags & nsMsgMessageFlags::HasRe))
+ {
+ nsAutoCString cSubject(subject);
+ thread = dont_AddRef(GetThreadForSubject(cSubject));
+ if(thread)
+ {
+ thread->GetThreadKey(&threadId);
+ newHdr->SetThreadId(threadId);
+ //TRACE("threading based on subject %s\n", (const char *) msgHdr->m_subject);
+ // if we move this and do subject threading after, ref threading,
+ // don't thread within children, since we know it won't work. But for now, pass TRUE.
+ result = AddToThread(newHdr, thread, nullptr, true);
+ }
+ }
+ }
+
+ // Check if this is a new parent to an existing message (that has a reference to this message)
+ if (!thread && UseCorrectThreading())
+ {
+ nsCString msgId;
+ newHdr->GetMessageId(getter_Copies(msgId));
+
+ thread = dont_AddRef(GetThreadForMessageId(msgId));
+ if (thread)
+ {
+ thread->GetThreadKey(&threadId);
+ newHdr->SetThreadId(threadId);
+ result = AddToThread(newHdr, thread, nullptr, true);
+ }
+ }
+
+ if (!thread)
+ {
+ // Not a parent or child, make it a new thread for now
+ result = AddNewThread(newHdr);
+ newThread = true;
+ }
+ else
+ {
+ newThread = false;
+ }
+ return result;
+}
+
+nsresult nsMsgDatabase::AddToThread(nsMsgHdr *newHdr, nsIMsgThread *thread, nsIMsgDBHdr *inReplyTo, bool threadInThread)
+{
+ // don't worry about real threading yet.
+ nsCOMPtr <nsIDBChangeAnnouncer> announcer = do_QueryInterface(this);
+
+ return thread->AddChild(newHdr, inReplyTo, threadInThread, announcer);
+}
+
+nsMsgHdr * nsMsgDatabase::GetMsgHdrForReference(nsCString &reference)
+{
+ NS_ASSERTION(false, "not implemented yet.");
+ return nullptr;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForMessageID(const char *aMsgID, nsIMsgDBHdr **aHdr)
+{
+ NS_ENSURE_ARG_POINTER(aHdr);
+ NS_ENSURE_ARG_POINTER(aMsgID);
+ nsIMsgDBHdr *msgHdr = nullptr;
+ nsresult rv = NS_OK;
+ mdbYarn messageIdYarn;
+
+ messageIdYarn.mYarn_Buf = (void *) aMsgID;
+ messageIdYarn.mYarn_Fill = PL_strlen(aMsgID);
+ messageIdYarn.mYarn_Form = 0;
+ messageIdYarn.mYarn_Size = messageIdYarn.mYarn_Fill;
+
+ nsIMdbRow *hdrRow;
+ mdbOid outRowId;
+ nsresult result;
+ if (m_mdbStore)
+ result = m_mdbStore->FindRow(GetEnv(), m_hdrRowScopeToken,
+ m_messageIdColumnToken, &messageIdYarn, &outRowId,
+ &hdrRow);
+ else
+ return NS_ERROR_FAILURE;
+ if (NS_SUCCEEDED(result) && hdrRow)
+ {
+ //Get key from row
+ mdbOid outOid;
+ nsMsgKey key = nsMsgKey_None;
+ rv = hdrRow->GetOid(GetEnv(), &outOid);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ key = outOid.mOid_Id;
+
+ rv = GetHdrFromUseCache(key, &msgHdr);
+ if (NS_SUCCEEDED(rv) && msgHdr)
+ hdrRow->Release();
+ else {
+ rv = CreateMsgHdr(hdrRow, key, &msgHdr);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ }
+ }
+ *aHdr = msgHdr; // already addreffed above.
+ return NS_OK; // it's not an error not to find a msg hdr.
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForGMMsgID(const char *aGMMsgId, nsIMsgDBHdr **aHdr)
+{
+ NS_ENSURE_ARG_POINTER(aGMMsgId);
+ NS_ENSURE_ARG_POINTER(aHdr);
+ nsIMsgDBHdr *msgHdr = nullptr;
+ nsresult rv = NS_OK;
+ mdbYarn gMailMessageIdYarn;
+ gMailMessageIdYarn.mYarn_Buf = (void *) aGMMsgId;
+ gMailMessageIdYarn.mYarn_Fill = strlen(aGMMsgId);
+ gMailMessageIdYarn.mYarn_Form = 0;
+ gMailMessageIdYarn.mYarn_Size = gMailMessageIdYarn.mYarn_Fill;
+
+ nsIMdbRow *hdrRow;
+ mdbOid outRowId;
+ nsresult result;
+ mdb_token property_token;
+ NS_ENSURE_TRUE(m_mdbStore, NS_ERROR_NULL_POINTER);
+ result = m_mdbStore->StringToToken(GetEnv(), "X-GM-MSGID",
+ &property_token);
+ NS_ENSURE_SUCCESS(result, result);
+ result = m_mdbStore->FindRow(GetEnv(), m_hdrRowScopeToken,
+ property_token, &gMailMessageIdYarn, &outRowId, &hdrRow);
+ if (NS_SUCCEEDED(result) && hdrRow)
+ {
+ // Get key from row
+ mdbOid outOid;
+ rv = hdrRow->GetOid(GetEnv(), &outOid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey key = outOid.mOid_Id;
+ rv = GetHdrFromUseCache(key, &msgHdr);
+ if ((NS_SUCCEEDED(rv) && msgHdr))
+ hdrRow->Release();
+ else {
+ rv = CreateMsgHdr(hdrRow, key, &msgHdr);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ }
+ }
+ *aHdr = msgHdr;
+ return NS_OK; // it's not an error not to find a msg hdr.
+}
+
+nsIMsgDBHdr *nsMsgDatabase::GetMsgHdrForSubject(nsCString &subject)
+{
+ nsIMsgDBHdr *msgHdr = nullptr;
+ nsresult rv = NS_OK;
+ mdbYarn subjectYarn;
+
+ subjectYarn.mYarn_Buf = (void*)subject.get();
+ subjectYarn.mYarn_Fill = PL_strlen(subject.get());
+ subjectYarn.mYarn_Form = 0;
+ subjectYarn.mYarn_Size = subjectYarn.mYarn_Fill;
+
+ nsIMdbRow *hdrRow;
+ mdbOid outRowId;
+ nsresult result = GetStore()->FindRow(GetEnv(), m_hdrRowScopeToken,
+ m_subjectColumnToken, &subjectYarn, &outRowId,
+ &hdrRow);
+ if (NS_SUCCEEDED(result) && hdrRow)
+ {
+ //Get key from row
+ mdbOid outOid;
+ nsMsgKey key = nsMsgKey_None;
+ rv = hdrRow->GetOid(GetEnv(), &outOid);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return nullptr;
+ key = outOid.mOid_Id;
+
+ rv = GetHdrFromUseCache(key, &msgHdr);
+ if (NS_SUCCEEDED(rv) && msgHdr)
+ hdrRow->Release();
+ else {
+ rv = CreateMsgHdr(hdrRow, key, &msgHdr);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return nullptr;
+ }
+ }
+ return msgHdr;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **result)
+{
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = nullptr;
+ nsMsgKey threadId = nsMsgKey_None;
+ (void)msgHdr->GetThreadId(&threadId);
+ if (threadId != nsMsgKey_None)
+ *result = GetThreadForThreadId(threadId);
+
+ // if we can't find the thread, try using the msg key as the thread id,
+ // because the msg hdr might not have the thread id set correctly
+ // Or maybe the message was deleted?
+ if (!*result)
+ {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ *result = GetThreadForThreadId(msgKey);
+ }
+ // failure is normal when message was deleted
+ return (*result) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+
+nsresult nsMsgDatabase::GetThreadForMsgKey(nsMsgKey msgKey, nsIMsgThread **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr <nsIMsgDBHdr> msg;
+ nsresult rv = GetMsgHdrForKey(msgKey, getter_AddRefs(msg));
+
+ if (NS_SUCCEEDED(rv) && msg)
+ rv = GetThreadContainingMsgHdr(msg, aResult);
+
+ return rv;
+}
+
+// caller needs to unrefer.
+nsIMsgThread * nsMsgDatabase::GetThreadForThreadId(nsMsgKey threadId)
+{
+
+ nsIMsgThread *retThread = (threadId == m_cachedThreadId && m_cachedThread) ?
+ m_cachedThread.get() : FindExistingThread(threadId);
+ if (retThread)
+ {
+ NS_ADDREF(retThread);
+ return retThread;
+ }
+ if (m_mdbStore)
+ {
+ mdbOid tableId;
+ tableId.mOid_Id = threadId;
+ tableId.mOid_Scope = m_hdrRowScopeToken;
+
+ nsCOMPtr<nsIMdbTable> threadTable;
+ nsresult res = m_mdbStore->GetTable(GetEnv(), &tableId,
+ getter_AddRefs(threadTable));
+
+ if (NS_SUCCEEDED(res) && threadTable)
+ {
+ retThread = new nsMsgThread(this, threadTable);
+ if (retThread)
+ {
+ NS_ADDREF(retThread);
+ m_cachedThread = retThread;
+ m_cachedThreadId = threadId;
+ }
+ }
+ }
+ return retThread;
+}
+
+// make the passed in header a thread header
+nsresult nsMsgDatabase::AddNewThread(nsMsgHdr *msgHdr)
+{
+
+ if (!msgHdr)
+ return NS_ERROR_NULL_POINTER;
+
+ nsMsgThread *threadHdr = nullptr;
+
+ nsCString subject;
+ nsMsgKey threadKey;
+ msgHdr->GetMessageKey(&threadKey);
+ // can't have a thread with key 1 since that's the table id of the all msg hdr table,
+ // so give it kTableKeyForThreadOne (0xfffffffe).
+ if (threadKey == kAllMsgHdrsTableKey)
+ threadKey = kTableKeyForThreadOne;
+
+ nsresult err = msgHdr->GetSubject(getter_Copies(subject));
+
+ err = CreateNewThread(threadKey, subject.get(), &threadHdr);
+ msgHdr->SetThreadId(threadKey);
+ if (threadHdr)
+ {
+ threadHdr->AddRef();
+ // err = msgHdr->GetSubject(subject);
+ // threadHdr->SetThreadKey(msgHdr->m_messageKey);
+ // threadHdr->SetSubject(subject.get());
+ // need to add the thread table to the db.
+ AddToThread(msgHdr, threadHdr, nullptr, false);
+ threadHdr->Release();
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::GetBoolPref(const char *prefName, bool *result)
+{
+ bool prefValue = false;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (pPrefBranch)
+ {
+ rv = pPrefBranch->GetBoolPref(prefName, &prefValue);
+ *result = prefValue;
+ }
+ return rv;
+}
+
+nsresult nsMsgDatabase::GetIntPref(const char *prefName, int32_t *result)
+{
+ int32_t prefValue = 0;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (pPrefBranch)
+ {
+ rv = pPrefBranch->GetIntPref(prefName, &prefValue);
+ *result = prefValue;
+ }
+ return rv;
+}
+
+
+nsresult nsMsgDatabase::ListAllThreads(nsTArray<nsMsgKey> *threadIds)
+{
+ nsresult rv;
+ nsMsgThread *pThread;
+
+ nsCOMPtr <nsISimpleEnumerator> threads;
+ rv = EnumerateThreads(getter_AddRefs(threads));
+ if (NS_FAILED(rv)) return rv;
+ bool hasMore = false;
+
+ while (NS_SUCCEEDED(rv = threads->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = threads->GetNext((nsISupports**)&pThread);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (threadIds) {
+ nsMsgKey key;
+ (void)pThread->GetThreadKey(&key);
+ threadIds->AppendElement(key);
+ }
+ // NS_RELEASE(pThread);
+ pThread = nullptr;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetAttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
+ const char *propertyVal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetUint32AttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
+ uint32_t propertyVal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::SetUint64AttributeOnPendingHdr(nsIMsgDBHdr *aPendingHdr,
+ const char *aProperty,
+ uint64_t aPropertyVal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::UpdatePendingAttributes(nsIMsgDBHdr *aNewHdr)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetOfflineOpForKey(nsMsgKey msgKey, bool create, nsIMsgOfflineImapOperation **offlineOp)
+{
+ NS_ASSERTION(false, "overridden by nsMailDatabase");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDatabase::RemoveOfflineOp(nsIMsgOfflineImapOperation *op)
+{
+ NS_ASSERTION(false, "overridden by nsMailDatabase");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::ListAllOfflineMsgs(nsIMsgKeyArray *aKeys)
+{
+ NS_ENSURE_ARG_POINTER(aKeys);
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ uint32_t flag = nsMsgMessageFlags::Offline;
+ // if we change this routine to return an enumerator that generates the keys
+ // one by one, we'll need to somehow make a copy of flag for the enumerator
+ // to own, since the enumerator will persist past the life of flag on the stack.
+ nsresult rv = EnumerateMessagesWithFlag(getter_AddRefs(enumerator), &flag);
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ bool hasMoreElements;
+ while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) && hasMoreElements)
+ {
+ nsCOMPtr<nsISupports> childSupports;
+ rv = enumerator->GetNext(getter_AddRefs(childSupports));
+ if(NS_FAILED(rv))
+ return rv;
+
+ // clear out db hdr, because it won't be valid when we get rid of the .msf file
+ nsCOMPtr<nsIMsgDBHdr> dbMessage(do_QueryInterface(childSupports, &rv));
+ if(NS_SUCCEEDED(rv) && dbMessage)
+ {
+ nsMsgKey msgKey;
+ dbMessage->GetMessageKey(&msgKey);
+ aKeys->AppendElement(msgKey);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::EnumerateOfflineOps(nsISimpleEnumerator **enumerator)
+{
+ NS_ASSERTION(false, "overridden by nsMailDatabase");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ListAllOfflineOpIds(nsTArray<nsMsgKey> *offlineOpIds)
+{
+ NS_ASSERTION(false, "overridden by nsMailDatabase");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ListAllOfflineDeletes(nsTArray<nsMsgKey> *offlineDeletes)
+{
+ nsresult ret = NS_OK;
+ if (!offlineDeletes)
+ return NS_ERROR_NULL_POINTER;
+
+ // technically, notimplemented, but no one's putting offline ops in anyway.
+ return ret;
+}
+NS_IMETHODIMP nsMsgDatabase::GetHighWaterArticleNum(nsMsgKey *key)
+{
+ if (!m_dbFolderInfo)
+ return NS_ERROR_NULL_POINTER;
+ return m_dbFolderInfo->GetHighWater(key);
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetLowWaterArticleNum(nsMsgKey *key)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* attribute nsMsgKey NextPseudoMsgKey */
+
+NS_IMETHODIMP nsMsgDatabase::GetNextPseudoMsgKey(nsMsgKey *nextPseudoMsgKey)
+{
+ NS_ENSURE_ARG_POINTER(nextPseudoMsgKey);
+ *nextPseudoMsgKey = m_nextPseudoMsgKey--;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetNextPseudoMsgKey(nsMsgKey nextPseudoMsgKey)
+{
+ m_nextPseudoMsgKey = nextPseudoMsgKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetNextFakeOfflineMsgKey(nsMsgKey *nextFakeOfflineMsgKey)
+{
+ NS_ENSURE_ARG_POINTER(nextFakeOfflineMsgKey);
+ // iterate over hdrs looking for first non-existant fake offline msg key
+ nsMsgKey fakeMsgKey = kIdStartOfFake;
+
+ bool containsKey;
+ do
+ {
+ ContainsKey(fakeMsgKey, &containsKey);
+ if (!containsKey)
+ break;
+ fakeMsgKey--;
+ }
+ while (containsKey);
+
+ *nextFakeOfflineMsgKey = fakeMsgKey;
+ return NS_OK;
+}
+
+#ifdef DEBUG
+nsresult nsMsgDatabase::DumpContents()
+{
+ nsMsgKey key;
+ uint32_t i;
+
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ if (!keys)
+ return NS_ERROR_OUT_OF_MEMORY;
+ nsresult rv = ListAllKeys(keys);
+ uint32_t numKeys;
+ keys->GetLength(&numKeys);
+ for (i = 0; i < numKeys; i++) {
+ key = keys->m_keys[i];
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString author;
+ nsCString subject;
+
+ msgHdr->GetMessageKey(&key);
+ msgHdr->GetAuthor(getter_Copies(author));
+ msgHdr->GetSubject(getter_Copies(subject));
+ printf("hdr key = %u, author = %s subject = %s\n", key, author.get(), subject.get());
+ }
+ }
+ nsTArray<nsMsgKey> threads;
+ rv = ListAllThreads(&threads);
+ for ( i = 0; i < threads.Length(); i++)
+ {
+ key = threads[i];
+ printf("thread key = %u\n", key);
+ // DumpThread(key);
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::DumpMsgChildren(nsIMsgDBHdr *msgHdr)
+{
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::DumpThread(nsMsgKey threadId)
+{
+ nsresult rv = NS_OK;
+ nsIMsgThread *thread = nullptr;
+
+ thread = GetThreadForThreadId(threadId);
+ if (thread)
+ {
+ nsISimpleEnumerator *enumerator = nullptr;
+
+ rv = thread->EnumerateMessages(nsMsgKey_None, &enumerator);
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ bool hasMore = false;
+
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> pMessage = do_QueryInterface(supports);
+ if (NS_FAILED(rv) || !pMessage)
+ break;
+ }
+ NS_RELEASE(enumerator);
+
+ }
+ }
+ return rv;
+}
+#endif /* DEBUG */
+
+NS_IMETHODIMP nsMsgDatabase::SetMsgRetentionSettings(nsIMsgRetentionSettings *retentionSettings)
+{
+ m_retentionSettings = retentionSettings;
+ if (retentionSettings && m_dbFolderInfo)
+ {
+ nsresult rv;
+
+ nsMsgRetainByPreference retainByPreference;
+ uint32_t daysToKeepHdrs;
+ uint32_t numHeadersToKeep;
+ uint32_t daysToKeepBodies;
+ bool cleanupBodiesByDays;
+ bool useServerDefaults;
+ bool applyToFlaggedMessages;
+
+ rv = retentionSettings->GetRetainByPreference(&retainByPreference);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = retentionSettings->GetDaysToKeepHdrs(&daysToKeepHdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = retentionSettings->GetNumHeadersToKeep(&numHeadersToKeep);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = retentionSettings->GetDaysToKeepBodies(&daysToKeepBodies);
+ NS_ENSURE_SUCCESS(rv, rv);
+ (void) retentionSettings->GetCleanupBodiesByDays(&cleanupBodiesByDays);
+ (void) retentionSettings->GetUseServerDefaults(&useServerDefaults);
+ rv = retentionSettings->GetApplyToFlaggedMessages(&applyToFlaggedMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // need to write this to the db. We'll just use the dbfolderinfo to write properties.
+ m_dbFolderInfo->SetUint32Property("retainBy", retainByPreference);
+ m_dbFolderInfo->SetUint32Property("daysToKeepHdrs", daysToKeepHdrs);
+ m_dbFolderInfo->SetUint32Property("numHdrsToKeep", numHeadersToKeep);
+ m_dbFolderInfo->SetUint32Property("daysToKeepBodies", daysToKeepBodies);
+ m_dbFolderInfo->SetBooleanProperty("cleanupBodies", cleanupBodiesByDays);
+ m_dbFolderInfo->SetBooleanProperty("useServerDefaults", useServerDefaults);
+ m_dbFolderInfo->SetBooleanProperty("applyToFlaggedMessages", applyToFlaggedMessages);
+ }
+ Commit(nsMsgDBCommitType::kLargeCommit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetMsgRetentionSettings(nsIMsgRetentionSettings **retentionSettings)
+{
+ NS_ENSURE_ARG_POINTER(retentionSettings);
+ if (!m_retentionSettings)
+ {
+ // create a new one, and initialize it from the db.
+ m_retentionSettings = new nsMsgRetentionSettings;
+ if (m_retentionSettings && m_dbFolderInfo)
+ {
+ nsMsgRetainByPreference retainByPreference;
+ uint32_t daysToKeepHdrs = 0;
+ uint32_t numHeadersToKeep = 0;
+ bool useServerDefaults;
+ uint32_t daysToKeepBodies = 0;
+ bool cleanupBodiesByDays = false;
+ bool applyToFlaggedMessages;
+
+ m_dbFolderInfo->GetUint32Property("retainBy", nsIMsgRetentionSettings::nsMsgRetainAll, &retainByPreference);
+ m_dbFolderInfo->GetUint32Property("daysToKeepHdrs", 0, &daysToKeepHdrs);
+ m_dbFolderInfo->GetUint32Property("numHdrsToKeep", 0, &numHeadersToKeep);
+ m_dbFolderInfo->GetUint32Property("daysToKeepBodies", 0, &daysToKeepBodies);
+ m_dbFolderInfo->GetBooleanProperty("useServerDefaults", true, &useServerDefaults);
+ m_dbFolderInfo->GetBooleanProperty("cleanupBodies", false, &cleanupBodiesByDays);
+ m_dbFolderInfo->GetBooleanProperty("applyToFlaggedMessages", false,
+ &applyToFlaggedMessages);
+ m_retentionSettings->SetRetainByPreference(retainByPreference);
+ m_retentionSettings->SetDaysToKeepHdrs(daysToKeepHdrs);
+ m_retentionSettings->SetNumHeadersToKeep(numHeadersToKeep);
+ m_retentionSettings->SetDaysToKeepBodies(daysToKeepBodies);
+ m_retentionSettings->SetUseServerDefaults(useServerDefaults);
+ m_retentionSettings->SetCleanupBodiesByDays(cleanupBodiesByDays);
+ m_retentionSettings->SetApplyToFlaggedMessages(applyToFlaggedMessages);
+ }
+ }
+ *retentionSettings = m_retentionSettings;
+ NS_IF_ADDREF(*retentionSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetMsgDownloadSettings(nsIMsgDownloadSettings *downloadSettings)
+{
+ m_downloadSettings = downloadSettings;
+ if (downloadSettings && m_dbFolderInfo)
+ {
+ nsresult rv;
+
+ bool useServerDefaults;
+ bool downloadByDate;
+ uint32_t ageLimitOfMsgsToDownload;
+ bool downloadUnreadOnly;
+
+ rv = downloadSettings->GetUseServerDefaults(&useServerDefaults);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = downloadSettings->GetDownloadByDate(&downloadByDate);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // need to write this to the db. We'll just use the dbfolderinfo to write properties.
+ m_dbFolderInfo->SetBooleanProperty("useServerDefaults", useServerDefaults);
+ m_dbFolderInfo->SetBooleanProperty("downloadByDate", downloadByDate);
+ m_dbFolderInfo->SetBooleanProperty("downloadUnreadOnly", downloadUnreadOnly);
+ m_dbFolderInfo->SetUint32Property("ageLimit", ageLimitOfMsgsToDownload);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetMsgDownloadSettings(nsIMsgDownloadSettings **downloadSettings)
+{
+ NS_ENSURE_ARG_POINTER(downloadSettings);
+ if (!m_downloadSettings)
+ {
+ // create a new one, and initialize it from the db.
+ m_downloadSettings = new nsMsgDownloadSettings;
+ if (m_downloadSettings && m_dbFolderInfo)
+ {
+ bool useServerDefaults;
+ bool downloadByDate;
+ uint32_t ageLimitOfMsgsToDownload;
+ bool downloadUnreadOnly;
+
+ m_dbFolderInfo->GetBooleanProperty("useServerDefaults", true, &useServerDefaults);
+ m_dbFolderInfo->GetBooleanProperty("downloadByDate", false, &downloadByDate);
+ m_dbFolderInfo->GetBooleanProperty("downloadUnreadOnly", false, &downloadUnreadOnly);
+ m_dbFolderInfo->GetUint32Property("ageLimit", 0, &ageLimitOfMsgsToDownload);
+
+ m_downloadSettings->SetUseServerDefaults(useServerDefaults);
+ m_downloadSettings->SetDownloadByDate(downloadByDate);
+ m_downloadSettings->SetDownloadUnreadOnly(downloadUnreadOnly);
+ m_downloadSettings->SetAgeLimitOfMsgsToDownload(ageLimitOfMsgsToDownload);
+ }
+ }
+ *downloadSettings = m_downloadSettings;
+ NS_IF_ADDREF(*downloadSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ApplyRetentionSettings(nsIMsgRetentionSettings *aMsgRetentionSettings,
+ bool aDeleteViaFolder)
+{
+ NS_ENSURE_ARG_POINTER(aMsgRetentionSettings);
+ nsresult rv = NS_OK;
+
+ if (!m_folder)
+ return NS_ERROR_NULL_POINTER;
+
+ bool isDraftsTemplatesOutbox;
+ uint32_t dtoFlags = nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates |
+ nsMsgFolderFlags::Queue;
+ (void) m_folder->IsSpecialFolder(dtoFlags, true, &isDraftsTemplatesOutbox);
+ // Never apply retention settings to Drafts/Templates/Outbox.
+ if (isDraftsTemplatesOutbox)
+ return NS_OK;
+
+ nsCOMPtr <nsIMutableArray> msgHdrsToDelete;
+ if (aDeleteViaFolder)
+ {
+ msgHdrsToDelete = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsMsgRetainByPreference retainByPreference;
+ aMsgRetentionSettings->GetRetainByPreference(&retainByPreference);
+
+ bool applyToFlaggedMessages = false;
+ aMsgRetentionSettings->GetApplyToFlaggedMessages(&applyToFlaggedMessages);
+
+ uint32_t daysToKeepHdrs = 0;
+ uint32_t numHeadersToKeep = 0;
+ switch (retainByPreference)
+ {
+ case nsIMsgRetentionSettings::nsMsgRetainAll:
+ break;
+ case nsIMsgRetentionSettings::nsMsgRetainByAge:
+ aMsgRetentionSettings->GetDaysToKeepHdrs(&daysToKeepHdrs);
+ rv = PurgeMessagesOlderThan(daysToKeepHdrs,
+ applyToFlaggedMessages, msgHdrsToDelete);
+ break;
+ case nsIMsgRetentionSettings::nsMsgRetainByNumHeaders:
+ aMsgRetentionSettings->GetNumHeadersToKeep(&numHeadersToKeep);
+ rv = PurgeExcessMessages(numHeadersToKeep,
+ applyToFlaggedMessages, msgHdrsToDelete);
+ break;
+ }
+ if (m_folder)
+ {
+ // update the time we attempted to purge this folder
+ char dateBuf[100];
+ dateBuf[0] = '\0';
+ PRExplodedTime exploded;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y", &exploded);
+ m_folder->SetStringProperty("LastPurgeTime", nsDependentCString(dateBuf));
+ }
+ if (msgHdrsToDelete)
+ {
+ uint32_t count;
+ msgHdrsToDelete->GetLength(&count);
+ if (count > 0)
+ rv = m_folder->DeleteMessages(msgHdrsToDelete, nullptr, true, false, nullptr, false);
+ }
+ return rv;
+}
+
+nsresult nsMsgDatabase::PurgeMessagesOlderThan(uint32_t daysToKeepHdrs,
+ bool applyToFlaggedMessages,
+ nsIMutableArray *hdrsToDelete)
+{
+ nsresult rv = NS_OK;
+ nsMsgHdr *pHeader;
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ rv = EnumerateMessages(getter_AddRefs(hdrs));
+ nsTArray<nsMsgKey> keysToDelete;
+
+ if (NS_FAILED(rv))
+ return rv;
+ bool hasMore = false;
+
+ PRTime cutOffDay = PR_Now() - daysToKeepHdrs * PR_USEC_PER_DAY;
+
+ // so now cutOffDay is the PRTime cut-off point. Any msg with a date less than that will get purged.
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ bool purgeHdr = false;
+
+ rv = hdrs->GetNext((nsISupports**)&pHeader);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ if (NS_FAILED(rv))
+ break;
+
+ if (!applyToFlaggedMessages)
+ {
+ uint32_t flags;
+ (void)pHeader->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Marked)
+ continue;
+ }
+
+ if (!purgeHdr)
+ {
+ PRTime date;
+ pHeader->GetDate(&date);
+ if (date < cutOffDay)
+ purgeHdr = true;
+ }
+ if (purgeHdr)
+ {
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ keysToDelete.AppendElement(msgKey);
+ if (hdrsToDelete)
+ hdrsToDelete->AppendElement(pHeader, false);
+ }
+ NS_RELEASE(pHeader);
+ }
+
+ if (!hdrsToDelete)
+ {
+ DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(), nullptr);
+
+ if (keysToDelete.Length() > 10) // compress commit if we deleted more than 10
+ Commit(nsMsgDBCommitType::kCompressCommit);
+ else if (!keysToDelete.IsEmpty())
+ Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+nsresult nsMsgDatabase::PurgeExcessMessages(uint32_t numHeadersToKeep,
+ bool applyToFlaggedMessages,
+ nsIMutableArray *hdrsToDelete)
+{
+ nsresult rv = NS_OK;
+ nsMsgHdr *pHeader;
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ rv = EnumerateMessages(getter_AddRefs(hdrs));
+ if (NS_FAILED(rv))
+ return rv;
+ bool hasMore = false;
+ nsTArray<nsMsgKey> keysToDelete;
+
+ mdb_count numHdrs = 0;
+ if (m_mdbAllMsgHeadersTable)
+ m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrs);
+ else
+ return NS_ERROR_NULL_POINTER;
+
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ bool purgeHdr = false;
+ rv = hdrs->GetNext((nsISupports**)&pHeader);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ if (NS_FAILED(rv))
+ break;
+
+ if (!applyToFlaggedMessages)
+ {
+ uint32_t flags;
+ (void)pHeader->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Marked)
+ continue;
+ }
+
+ // this isn't quite right - we want to prefer unread messages (keep all of those we can)
+ if (numHdrs > numHeadersToKeep)
+ purgeHdr = true;
+
+ if (purgeHdr)
+ {
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ keysToDelete.AppendElement(msgKey);
+ numHdrs--;
+ if (hdrsToDelete)
+ hdrsToDelete->AppendElement(pHeader, false);
+ }
+ NS_RELEASE(pHeader);
+ }
+
+ if (!hdrsToDelete)
+ {
+ int32_t numKeysToDelete = keysToDelete.Length();
+ if (numKeysToDelete > 0)
+ {
+ DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(), nullptr);
+ if (numKeysToDelete > 10) // compress commit if we deleted more than 10
+ Commit(nsMsgDBCommitType::kCompressCommit);
+ else
+ Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ }
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgRetentionSettings, nsIMsgRetentionSettings)
+
+// Initialise the member variables to resonable defaults.
+nsMsgRetentionSettings::nsMsgRetentionSettings()
+: m_retainByPreference(1),
+ m_daysToKeepHdrs(0),
+ m_numHeadersToKeep(0),
+ m_useServerDefaults(true),
+ m_cleanupBodiesByDays(false),
+ m_daysToKeepBodies(0),
+ m_applyToFlaggedMessages(false)
+{
+}
+
+nsMsgRetentionSettings::~nsMsgRetentionSettings()
+{
+}
+
+/* attribute unsigned long retainByPreference */
+
+NS_IMETHODIMP nsMsgRetentionSettings::GetRetainByPreference(nsMsgRetainByPreference *retainByPreference)
+{
+ NS_ENSURE_ARG_POINTER(retainByPreference);
+ *retainByPreference = m_retainByPreference;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgRetentionSettings::SetRetainByPreference(nsMsgRetainByPreference retainByPreference)
+{
+ m_retainByPreference = retainByPreference;
+ return NS_OK;
+}
+
+/* attribute long daysToKeepHdrs; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetDaysToKeepHdrs(uint32_t *aDaysToKeepHdrs)
+{
+ NS_ENSURE_ARG_POINTER(aDaysToKeepHdrs);
+ *aDaysToKeepHdrs = m_daysToKeepHdrs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgRetentionSettings::SetDaysToKeepHdrs(uint32_t aDaysToKeepHdrs)
+{
+ m_daysToKeepHdrs = aDaysToKeepHdrs;
+ return NS_OK;
+}
+
+/* attribute long numHeadersToKeep; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetNumHeadersToKeep(uint32_t *aNumHeadersToKeep)
+{
+ NS_ENSURE_ARG_POINTER(aNumHeadersToKeep);
+ *aNumHeadersToKeep = m_numHeadersToKeep;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgRetentionSettings::SetNumHeadersToKeep(uint32_t aNumHeadersToKeep)
+{
+ m_numHeadersToKeep = aNumHeadersToKeep;
+ return NS_OK;
+}
+/* attribute boolean useServerDefaults; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetUseServerDefaults(bool *aUseServerDefaults)
+{
+ NS_ENSURE_ARG_POINTER(aUseServerDefaults);
+ *aUseServerDefaults = m_useServerDefaults;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgRetentionSettings::SetUseServerDefaults(bool aUseServerDefaults)
+{
+ m_useServerDefaults = aUseServerDefaults;
+ return NS_OK;
+}
+
+/* attribute boolean cleanupBodiesByDays; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetCleanupBodiesByDays(bool *aCleanupBodiesByDays)
+{
+ NS_ENSURE_ARG_POINTER(aCleanupBodiesByDays);
+ *aCleanupBodiesByDays = m_cleanupBodiesByDays;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgRetentionSettings::SetCleanupBodiesByDays(bool aCleanupBodiesByDays)
+{
+ m_cleanupBodiesByDays = aCleanupBodiesByDays;
+ return NS_OK;
+}
+
+/* attribute long daysToKeepBodies; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetDaysToKeepBodies(uint32_t *aDaysToKeepBodies)
+{
+ NS_ENSURE_ARG_POINTER(aDaysToKeepBodies);
+ *aDaysToKeepBodies = m_daysToKeepBodies;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgRetentionSettings::SetDaysToKeepBodies(uint32_t aDaysToKeepBodies)
+{
+ m_daysToKeepBodies = aDaysToKeepBodies;
+ return NS_OK;
+}
+
+/* attribute boolean applyToFlaggedMessages; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetApplyToFlaggedMessages(bool *aApplyToFlaggedMessages)
+{
+ NS_ENSURE_ARG_POINTER(aApplyToFlaggedMessages);
+ *aApplyToFlaggedMessages = m_applyToFlaggedMessages;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgRetentionSettings::SetApplyToFlaggedMessages(bool aApplyToFlaggedMessages)
+{
+ m_applyToFlaggedMessages = aApplyToFlaggedMessages;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgDownloadSettings, nsIMsgDownloadSettings)
+
+nsMsgDownloadSettings::nsMsgDownloadSettings()
+{
+ m_useServerDefaults = false;
+ m_downloadUnreadOnly = false;
+ m_downloadByDate = false;
+ m_ageLimitOfMsgsToDownload = 0;
+}
+
+nsMsgDownloadSettings::~nsMsgDownloadSettings()
+{
+}
+
+/* attribute boolean useServerDefaults; */
+NS_IMETHODIMP nsMsgDownloadSettings::GetUseServerDefaults(bool *aUseServerDefaults)
+{
+ NS_ENSURE_ARG_POINTER(aUseServerDefaults);
+ *aUseServerDefaults = m_useServerDefaults;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgDownloadSettings::SetUseServerDefaults(bool aUseServerDefaults)
+{
+ m_useServerDefaults = aUseServerDefaults;
+ return NS_OK;
+}
+
+
+/* attribute boolean downloadUnreadOnly; */
+NS_IMETHODIMP nsMsgDownloadSettings::GetDownloadUnreadOnly(bool *aDownloadUnreadOnly)
+{
+ NS_ENSURE_ARG_POINTER(aDownloadUnreadOnly);
+ *aDownloadUnreadOnly = m_downloadUnreadOnly;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgDownloadSettings::SetDownloadUnreadOnly(bool aDownloadUnreadOnly)
+{
+ m_downloadUnreadOnly = aDownloadUnreadOnly;
+ return NS_OK;
+}
+
+/* attribute boolean downloadByDate; */
+NS_IMETHODIMP nsMsgDownloadSettings::GetDownloadByDate(bool *aDownloadByDate)
+{
+ NS_ENSURE_ARG_POINTER(aDownloadByDate);
+ *aDownloadByDate = m_downloadByDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDownloadSettings::SetDownloadByDate(bool aDownloadByDate)
+{
+ m_downloadByDate = aDownloadByDate;
+ return NS_OK;
+}
+
+
+/* attribute long ageLimitOfMsgsToDownload; */
+NS_IMETHODIMP nsMsgDownloadSettings::GetAgeLimitOfMsgsToDownload(uint32_t *ageLimitOfMsgsToDownload)
+{
+ NS_ENSURE_ARG_POINTER(ageLimitOfMsgsToDownload);
+ *ageLimitOfMsgsToDownload = m_ageLimitOfMsgsToDownload;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgDownloadSettings::SetAgeLimitOfMsgsToDownload(uint32_t ageLimitOfMsgsToDownload)
+{
+ m_ageLimitOfMsgsToDownload = ageLimitOfMsgsToDownload;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetDefaultViewFlags(nsMsgViewFlagsTypeValue *aDefaultViewFlags)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultViewFlags);
+ GetIntPref("mailnews.default_view_flags", aDefaultViewFlags);
+ if (*aDefaultViewFlags < nsMsgViewFlagsType::kNone ||
+ *aDefaultViewFlags > (nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kShowIgnored |
+ nsMsgViewFlagsType::kUnreadOnly |
+ nsMsgViewFlagsType::kExpandAll |
+ nsMsgViewFlagsType::kGroupBySort))
+ *aDefaultViewFlags = nsMsgViewFlagsType::kNone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetDefaultSortType(nsMsgViewSortTypeValue *aDefaultSortType)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultSortType);
+ GetIntPref("mailnews.default_sort_type", aDefaultSortType);
+ if (*aDefaultSortType < nsMsgViewSortType::byDate ||
+ *aDefaultSortType > nsMsgViewSortType::byAccount)
+ *aDefaultSortType = nsMsgViewSortType::byDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetDefaultSortOrder(nsMsgViewSortOrderValue *aDefaultSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultSortOrder);
+ GetIntPref("mailnews.default_sort_order", aDefaultSortOrder);
+ if (*aDefaultSortOrder != nsMsgViewSortOrder::descending)
+ *aDefaultSortOrder = nsMsgViewSortOrder::ascending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ResetHdrCacheSize(uint32_t aSize)
+{
+ if (m_cacheSize > aSize)
+ {
+ m_cacheSize = aSize;
+ ClearHdrCache(false);
+ }
+ return NS_OK;
+}
+
+/**
+ void getNewList(out unsigned long count, [array, size_is(count)] out long newKeys);
+ */
+NS_IMETHODIMP
+nsMsgDatabase::GetNewList(uint32_t *aCount, nsMsgKey **aNewKeys)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+ NS_ENSURE_ARG_POINTER(aNewKeys);
+
+ *aCount = m_newSet.Length();
+ if (*aCount > 0)
+ {
+ *aNewKeys = static_cast<nsMsgKey *>(moz_xmalloc(*aCount * sizeof(nsMsgKey)));
+ if (!*aNewKeys)
+ return NS_ERROR_OUT_OF_MEMORY;
+ memcpy(*aNewKeys, m_newSet.Elements(), *aCount * sizeof(nsMsgKey));
+ return NS_OK;
+ }
+ // if there were no new messages, signal this by returning a null pointer
+ //
+ *aNewKeys = nullptr;
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::GetSearchResultsTable(const char *searchFolderUri, bool createIfMissing, nsIMdbTable **table)
+{
+ mdb_kind kindToken;
+ mdb_count numTables;
+ mdb_bool mustBeUnique;
+ NS_ENSURE_TRUE(m_mdbStore, NS_ERROR_NULL_POINTER);
+
+ nsresult err = m_mdbStore->StringToToken(GetEnv(), searchFolderUri, &kindToken);
+ err = m_mdbStore->GetTableKind(GetEnv(), m_hdrRowScopeToken, kindToken,
+ &numTables, &mustBeUnique, table);
+ if ((!*table || NS_FAILED(err)) && createIfMissing)
+ err = m_mdbStore->NewTable(GetEnv(), m_hdrRowScopeToken, kindToken, true, nullptr, table);
+
+ return *table ? err : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::GetCachedHits(const char *aSearchFolderUri, nsISimpleEnumerator **aEnumerator)
+{
+ nsCOMPtr <nsIMdbTable> table;
+ (void) GetSearchResultsTable(aSearchFolderUri, false, getter_AddRefs(table));
+ if (!table)
+ return NS_ERROR_FAILURE; // expected result for no cached hits
+ nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, table, nullptr, nullptr);
+ if (e == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aEnumerator = e);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::RefreshCache(const char *aSearchFolderUri, uint32_t aNumKeys, nsMsgKey *aNewHits, uint32_t *aNumBadHits, nsMsgKey **aStaleHits)
+{
+ nsCOMPtr <nsIMdbTable> table;
+ nsresult err = GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table));
+ NS_ENSURE_SUCCESS(err, err);
+ // update the table so that it just contains aNewHits.
+ // And, keep track of the headers in the original table but not in aNewHits, so we
+ // can put those in aStaleHits.
+ // both aNewHits and the db table are sorted by uid/key.
+ // So, start at the beginning of the table and the aNewHits array.
+ uint32_t newHitIndex = 0;
+ uint32_t tableRowIndex = 0;
+
+ uint32_t rowCount;
+ table->GetCount(GetEnv(), &rowCount);
+ nsTArray<nsMsgKey> staleHits;
+ // should assert that each array is sorted
+ while (newHitIndex < aNumKeys || tableRowIndex < rowCount)
+ {
+ mdbOid oid;
+ nsMsgKey tableRowKey = nsMsgKey_None;
+ if (tableRowIndex < rowCount)
+ {
+ nsresult ret = table->PosToOid (GetEnv(), tableRowIndex, &oid);
+ if (NS_FAILED(ret))
+ {
+ tableRowIndex++;
+ continue;
+ }
+ tableRowKey = oid.mOid_Id; // ### TODO need the real key for the 0th key problem.
+ }
+
+ if (newHitIndex < aNumKeys && aNewHits[newHitIndex] == tableRowKey)
+ {
+ newHitIndex++;
+ tableRowIndex++;
+ continue;
+ }
+ else if (tableRowIndex >= rowCount || (newHitIndex < aNumKeys && aNewHits[newHitIndex] < tableRowKey))
+ {
+ nsCOMPtr <nsIMdbRow> hdrRow;
+ mdbOid rowObjectId;
+
+ rowObjectId.mOid_Id = aNewHits[newHitIndex];
+ rowObjectId.mOid_Scope = m_hdrRowScopeToken;
+ err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, getter_AddRefs(hdrRow));
+ if (hdrRow)
+ {
+ table->AddRow(GetEnv(), hdrRow);
+ mdb_pos newPos;
+ table->MoveRow(GetEnv(), hdrRow, rowCount, tableRowIndex, &newPos);
+ rowCount++;
+ tableRowIndex++;
+ }
+ newHitIndex++;
+ continue;
+ }
+ else if (newHitIndex >= aNumKeys || aNewHits[newHitIndex] > tableRowKey)
+ {
+ staleHits.AppendElement(tableRowKey);
+ table->CutOid(GetEnv(), &oid);
+ rowCount--;
+ continue; // don't increment tableRowIndex since we removed that row.
+ }
+ }
+ *aNumBadHits = staleHits.Length();
+ if (*aNumBadHits)
+ {
+ *aStaleHits = static_cast<nsMsgKey *>(moz_xmalloc(*aNumBadHits * sizeof(nsMsgKey)));
+ if (!*aStaleHits)
+ return NS_ERROR_OUT_OF_MEMORY;
+ memcpy(*aStaleHits, staleHits.Elements(), *aNumBadHits * sizeof(nsMsgKey));
+ }
+ else
+ *aStaleHits = nullptr;
+
+#ifdef DEBUG_David_Bienvenu
+ printf("after refreshing cache\n");
+ // iterate over table and assert that it's in id order
+ table->GetCount(GetEnv(), &rowCount);
+ mdbOid oid;
+ tableRowIndex = 0;
+ mdb_id prevId = 0;
+ while (tableRowIndex < rowCount)
+ {
+ nsresult ret = table->PosToOid (m_mdbEnv, tableRowIndex++, &oid);
+ if (tableRowIndex > 1 && oid.mOid_Id <= prevId)
+ {
+ NS_ASSERTION(false, "inserting row into cached hits table, not sorted correctly");
+ printf("key %lx is before or equal %lx\n", prevId, oid.mOid_Id);
+ }
+ prevId = oid.mOid_Id;
+ }
+
+#endif
+ Commit(nsMsgDBCommitType::kLargeCommit);
+ return NS_OK;
+}
+
+// search sorted table
+mdb_pos nsMsgDatabase::FindInsertIndexInSortedTable(nsIMdbTable *table, mdb_id idToInsert)
+{
+ mdb_pos searchPos = 0;
+ uint32_t rowCount;
+ table->GetCount(GetEnv(), &rowCount);
+ mdb_pos hi = rowCount;
+ mdb_pos lo = 0;
+
+ while (hi > lo)
+ {
+ mdbOid outOid;
+ searchPos = (lo + hi - 1) / 2;
+ table->PosToOid(GetEnv(), searchPos, &outOid);
+ if (outOid.mOid_Id == idToInsert)
+ {
+ NS_ASSERTION(false, "id shouldn't be in table");
+ return hi;
+ }
+ if (outOid.mOid_Id > idToInsert)
+ hi = searchPos;
+ else // if (outOid.mOid_Id < idToInsert)
+ lo = searchPos + 1;
+ }
+ return hi;
+}
+NS_IMETHODIMP
+nsMsgDatabase::UpdateHdrInCache(const char *aSearchFolderUri, nsIMsgDBHdr *aHdr, bool aAdd)
+{
+ nsCOMPtr <nsIMdbTable> table;
+ nsresult err = GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table));
+ NS_ENSURE_SUCCESS(err, err);
+ nsMsgKey key;
+ err = aHdr->GetMessageKey(&key);
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(aHdr); // closed system, so this is ok
+ nsIMdbRow* hdrRow = msgHdr->GetMDBRow();
+ if (NS_SUCCEEDED(err) && m_mdbStore && hdrRow)
+ {
+ if (!aAdd)
+ {
+ table->CutRow(m_mdbEnv, hdrRow);
+ }
+ else
+ {
+ mdbOid rowId;
+ hdrRow->GetOid(m_mdbEnv, &rowId);
+ mdb_pos insertPos = FindInsertIndexInSortedTable(table, rowId.mOid_Id);
+ uint32_t rowCount;
+ table->GetCount(m_mdbEnv, &rowCount);
+ table->AddRow(m_mdbEnv, hdrRow);
+ mdb_pos newPos;
+ table->MoveRow(m_mdbEnv, hdrRow, rowCount, insertPos, &newPos);
+ }
+ }
+
+// if (aAdd)
+ // if we need to add this hdr, we need to insert it in key order.
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsMsgDatabase::HdrIsInCache(const char* aSearchFolderUri, nsIMsgDBHdr *aHdr, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsCOMPtr <nsIMdbTable> table;
+ nsresult err = GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table));
+ NS_ENSURE_SUCCESS(err, err);
+ nsMsgKey key;
+ aHdr->GetMessageKey(&key);
+ mdbOid rowObjectId;
+ rowObjectId.mOid_Id = key;
+ rowObjectId.mOid_Scope = m_hdrRowScopeToken;
+ mdb_bool hasOid;
+ err = table->HasOid(GetEnv(), &rowObjectId, &hasOid);
+ *aResult = hasOid;
+ return err;
+}
+
diff --git a/mailnews/db/msgdb/src/nsMsgHdr.cpp b/mailnews/db/msgdb/src/nsMsgHdr.cpp
new file mode 100644
index 000000000..7c5cf5709
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMsgHdr.cpp
@@ -0,0 +1,1097 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsMsgHdr.h"
+#include "nsMsgDatabase.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgThread.h"
+#include "nsMsgMimeCID.h"
+#include "mozilla/Attributes.h"
+
+using namespace mozilla::mailnews;
+
+NS_IMPL_ISUPPORTS(nsMsgHdr, nsIMsgDBHdr)
+
+#define FLAGS_INITED 0x1
+#define CACHED_VALUES_INITED 0x2
+#define REFERENCES_INITED 0x4
+#define THREAD_PARENT_INITED 0x8
+
+nsMsgHdr::nsMsgHdr(nsMsgDatabase *db, nsIMdbRow *dbRow)
+{
+ m_mdb = db;
+ Init();
+ m_mdbRow = dbRow;
+ if(m_mdb)
+ {
+ m_mdb->AddRef();
+ mdbOid outOid;
+ if (dbRow && NS_SUCCEEDED(dbRow->GetOid(m_mdb->GetEnv(), &outOid)))
+ {
+ m_messageKey = outOid.mOid_Id;
+ m_mdb->AddHdrToUseCache((nsIMsgDBHdr *) this, m_messageKey);
+ }
+ }
+}
+
+
+void nsMsgHdr::Init()
+{
+ m_initedValues = 0;
+ m_statusOffset = 0xffffffff;
+ m_messageKey = nsMsgKey_None;
+ m_messageSize = 0;
+ m_date = 0;
+ m_flags = 0;
+ m_mdbRow = NULL;
+ m_threadId = nsMsgKey_None;
+ m_threadParent = nsMsgKey_None;
+}
+
+nsresult nsMsgHdr::InitCachedValues()
+{
+ nsresult err = NS_OK;
+
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!(m_initedValues & CACHED_VALUES_INITED))
+ {
+ uint32_t uint32Value;
+ mdbOid outOid;
+ if (NS_SUCCEEDED(m_mdbRow->GetOid(m_mdb->GetEnv(), &outOid)))
+ m_messageKey = outOid.mOid_Id;
+
+ err = GetUInt32Column(m_mdb->m_messageSizeColumnToken, &m_messageSize);
+
+ err = GetUInt32Column(m_mdb->m_dateColumnToken, &uint32Value);
+ Seconds2PRTime(uint32Value, &m_date);
+
+ err = GetUInt32Column(m_mdb->m_messageThreadIdColumnToken, &m_threadId);
+
+ if (NS_SUCCEEDED(err))
+ m_initedValues |= CACHED_VALUES_INITED;
+ }
+ return err;
+}
+
+nsresult nsMsgHdr::InitFlags()
+{
+
+ nsresult err = NS_OK;
+
+ if (!m_mdb)
+ return NS_ERROR_NULL_POINTER;
+
+ if(!(m_initedValues & FLAGS_INITED))
+ {
+ err = GetUInt32Column(m_mdb->m_flagsColumnToken, &m_flags);
+ m_flags &= ~nsMsgMessageFlags::New; // don't get new flag from MDB
+
+ if(NS_SUCCEEDED(err))
+ m_initedValues |= FLAGS_INITED;
+ }
+
+ return err;
+
+}
+
+nsMsgHdr::~nsMsgHdr()
+{
+ if (m_mdbRow)
+ {
+ if (m_mdb)
+ {
+ NS_RELEASE(m_mdbRow);
+ m_mdb->RemoveHdrFromUseCache((nsIMsgDBHdr *) this, m_messageKey);
+ }
+ }
+ NS_IF_RELEASE(m_mdb);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetMessageKey(nsMsgKey *result)
+{
+ if (m_messageKey == nsMsgKey_None && m_mdbRow != NULL)
+ {
+ mdbOid outOid;
+ if (NS_SUCCEEDED(m_mdbRow->GetOid(m_mdb->GetEnv(), &outOid)))
+ m_messageKey = outOid.mOid_Id;
+
+ }
+ *result = m_messageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetThreadId(nsMsgKey *result)
+{
+
+ if (!(m_initedValues & CACHED_VALUES_INITED))
+ InitCachedValues();
+
+ if (result)
+ {
+ *result = m_threadId;
+ return NS_OK;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetThreadId(nsMsgKey inKey)
+{
+ m_threadId = inKey;
+ return SetUInt32Column(m_threadId, m_mdb->m_messageThreadIdColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetMessageKey(nsMsgKey value)
+{
+ m_messageKey = value;
+ return NS_OK;
+}
+
+nsresult nsMsgHdr::GetRawFlags(uint32_t *result)
+{
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ *result = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetFlags(uint32_t *result)
+{
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ if (m_mdb)
+ *result = m_mdb->GetStatusFlags(this, m_flags);
+ else
+ *result = m_flags;
+#ifdef DEBUG_bienvenu
+ NS_ASSERTION(! (*result & (nsMsgMessageFlags::Elided)), "shouldn't be set in db");
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetFlags(uint32_t flags)
+{
+#ifdef DEBUG_bienvenu
+ NS_ASSERTION(! (flags & (nsMsgMessageFlags::Elided)), "shouldn't set this flag on db");
+#endif
+ m_initedValues |= FLAGS_INITED;
+ m_flags = flags;
+ // don't write out nsMsgMessageFlags::New to MDB.
+ return SetUInt32Column(m_flags & ~nsMsgMessageFlags::New, m_mdb->m_flagsColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::OrFlags(uint32_t flags, uint32_t *result)
+{
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ if ((m_flags & flags) != flags)
+ SetFlags (m_flags | flags);
+ *result = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::AndFlags(uint32_t flags, uint32_t *result)
+{
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ if ((m_flags & flags) != m_flags)
+ SetFlags (m_flags & flags);
+ *result = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::MarkHasAttachments(bool bHasAttachments)
+{
+ nsresult rv = NS_OK;
+
+ if(m_mdb)
+ {
+ nsMsgKey key;
+ rv = GetMessageKey(&key);
+ if(NS_SUCCEEDED(rv))
+ rv = m_mdb->MarkHasAttachments(key, bHasAttachments, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgHdr::MarkRead(bool bRead)
+{
+ nsresult rv = NS_OK;
+
+ if(m_mdb)
+ {
+ nsMsgKey key;
+ rv = GetMessageKey(&key);
+ if(NS_SUCCEEDED(rv))
+ rv = m_mdb->MarkRead(key, bRead, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgHdr::MarkFlagged(bool bFlagged)
+{
+ nsresult rv = NS_OK;
+
+ if(m_mdb)
+ {
+ nsMsgKey key;
+ rv = GetMessageKey(&key);
+ if(NS_SUCCEEDED(rv))
+ rv = m_mdb->MarkMarked(key, bFlagged, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetProperty(const char *propertyName, nsAString &resultProperty)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->GetPropertyAsNSString(m_mdbRow, propertyName, resultProperty);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetProperty(const char *propertyName, const nsAString &propertyStr)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->SetPropertyFromNSString(m_mdbRow, propertyName, propertyStr);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetStringProperty(const char *propertyName, const char *propertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->SetProperty(m_mdbRow, propertyName, propertyValue);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetStringProperty(const char *propertyName, char **aPropertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->GetProperty(m_mdbRow, propertyName, aPropertyValue);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetUint32Property(const char *propertyName, uint32_t *pResult)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->GetUint32Property(m_mdbRow, propertyName, pResult);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetUint32Property(const char *propertyName, uint32_t value)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->SetUint32Property(m_mdbRow, propertyName, value);
+}
+
+
+NS_IMETHODIMP nsMsgHdr::GetNumReferences(uint16_t *result)
+{
+ if (!(m_initedValues & REFERENCES_INITED))
+ {
+ const char *references;
+ if (NS_SUCCEEDED(m_mdb->RowCellColumnToConstCharPtr(GetMDBRow(),
+ m_mdb->m_referencesColumnToken, &references)))
+ ParseReferences(references);
+ m_initedValues |= REFERENCES_INITED;
+ }
+
+ if (result)
+ *result = m_references.Length();
+ // there is no real failure here; if there are no references, there are no
+ // references.
+ return NS_OK;
+}
+
+nsresult nsMsgHdr::ParseReferences(const char *references)
+{
+ const char *startNextRef = references;
+ nsAutoCString resultReference;
+ nsCString messageId;
+ GetMessageId(getter_Copies(messageId));
+
+ while (startNextRef && *startNextRef)
+ {
+ startNextRef = GetNextReference(startNextRef, resultReference,
+ startNextRef == references);
+ // Don't add self-references.
+ if (!resultReference.IsEmpty() && !resultReference.Equals(messageId))
+ m_references.AppendElement(resultReference);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetStringReference(int32_t refNum, nsACString& resultReference)
+{
+ nsresult err = NS_OK;
+
+ if(!(m_initedValues & REFERENCES_INITED))
+ GetNumReferences(nullptr); // it can handle the null
+
+ if ((uint32_t)refNum < m_references.Length())
+ resultReference = m_references.ElementAt(refNum);
+ else
+ err = NS_ERROR_ILLEGAL_VALUE;
+ return err;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetDate(PRTime *result)
+{
+ if (!(m_initedValues & CACHED_VALUES_INITED))
+ InitCachedValues();
+
+ *result = m_date;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetDateInSeconds(uint32_t *aResult)
+{
+ return GetUInt32Column(m_mdb->m_dateColumnToken, aResult);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetMessageId(const char *messageId)
+{
+ if (messageId && *messageId == '<')
+ {
+ nsAutoCString tempMessageID(messageId + 1);
+ if (tempMessageID.CharAt(tempMessageID.Length() - 1) == '>')
+ tempMessageID.SetLength(tempMessageID.Length() - 1);
+ return SetStringColumn(tempMessageID.get(), m_mdb->m_messageIdColumnToken);
+ }
+ return SetStringColumn(messageId, m_mdb->m_messageIdColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetSubject(const char *subject)
+{
+ return SetStringColumn(subject, m_mdb->m_subjectColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetAuthor(const char *author)
+{
+ return SetStringColumn(author, m_mdb->m_senderColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetReferences(const char *references)
+{
+ NS_ENSURE_ARG_POINTER(references);
+ m_references.Clear();
+ ParseReferences(references);
+
+ m_initedValues |= REFERENCES_INITED;
+
+ return SetStringColumn(references, m_mdb->m_referencesColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetRecipients(const char *recipients)
+{
+ // need to put in rfc822 address parsing code here (or make caller do it...)
+ return SetStringColumn(recipients, m_mdb->m_recipientsColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetCcList(const char *ccList)
+{
+ return SetStringColumn(ccList, m_mdb->m_ccListColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetBccList(const char *bccList)
+{
+ return SetStringColumn(bccList, m_mdb->m_bccListColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetMessageSize(uint32_t messageSize)
+{
+ SetUInt32Column(messageSize, m_mdb->m_messageSizeColumnToken);
+ m_messageSize = messageSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetOfflineMessageSize(uint32_t *result)
+{
+ uint32_t size;
+ nsresult res = GetUInt32Column(m_mdb->m_offlineMessageSizeColumnToken, &size);
+
+ *result = size;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetOfflineMessageSize(uint32_t messageSize)
+{
+ return SetUInt32Column(messageSize, m_mdb->m_offlineMessageSizeColumnToken);
+}
+
+
+NS_IMETHODIMP nsMsgHdr::SetLineCount(uint32_t lineCount)
+{
+ SetUInt32Column(lineCount, m_mdb->m_numLinesColumnToken);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetStatusOffset(uint32_t statusOffset)
+{
+ return SetUInt32Column(statusOffset, m_mdb->m_statusOffsetColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetDate(PRTime date)
+{
+ m_date = date;
+ uint32_t seconds;
+ PRTime2Seconds(date, &seconds);
+ return SetUInt32Column((uint32_t) seconds, m_mdb->m_dateColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetStatusOffset(uint32_t *result)
+{
+ uint32_t offset = 0;
+ nsresult res = GetUInt32Column(m_mdb->m_statusOffsetColumnToken, &offset);
+
+ *result = offset;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetPriority(nsMsgPriorityValue priority)
+{
+ SetUInt32Column((uint32_t) priority, m_mdb->m_priorityColumnToken);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetPriority(nsMsgPriorityValue *result)
+{
+ if (!result)
+ return NS_ERROR_NULL_POINTER;
+
+ uint32_t priority = 0;
+ nsresult rv = GetUInt32Column(m_mdb->m_priorityColumnToken, &priority);
+ if (NS_FAILED(rv)) return rv;
+
+ *result = (nsMsgPriorityValue) priority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetLabel(nsMsgLabelValue label)
+{
+ SetUInt32Column((uint32_t) label, m_mdb->m_labelColumnToken);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetLabel(nsMsgLabelValue *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ return GetUInt32Column(m_mdb->m_labelColumnToken, result);
+}
+
+// I'd like to not store the account key, if the msg is in
+// the same account as it was received in, to save disk space and memory.
+// This might be problematic when a message gets moved...
+// And I'm not sure if we should short circuit it here,
+// or at a higher level where it might be more efficient.
+NS_IMETHODIMP nsMsgHdr::SetAccountKey(const char *aAccountKey)
+{
+ return SetStringProperty("account", aAccountKey);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetAccountKey(char **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ return GetStringProperty("account", aResult);
+}
+
+
+NS_IMETHODIMP nsMsgHdr::GetMessageOffset(uint64_t *result)
+{
+ NS_ENSURE_ARG(result);
+
+ // if there is a message offset, use it, otherwise, we'll use the message key.
+ (void) GetUInt64Column(m_mdb->m_offlineMsgOffsetColumnToken, result, (unsigned)-1);
+ if (*result == (unsigned)-1)
+ *result = m_messageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetMessageOffset(uint64_t offset)
+{
+ SetUInt64Column(offset, m_mdb->m_offlineMsgOffsetColumnToken);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgHdr::GetMessageSize(uint32_t *result)
+{
+ uint32_t size;
+ nsresult res = GetUInt32Column(m_mdb->m_messageSizeColumnToken, &size);
+
+ *result = size;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetLineCount(uint32_t *result)
+{
+ uint32_t linecount;
+ nsresult res = GetUInt32Column(m_mdb->m_numLinesColumnToken, &linecount);
+ *result = linecount;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetPriorityString(const char *priority)
+{
+ nsMsgPriorityValue priorityVal = nsMsgPriority::Default;
+
+ // We can ignore |NS_MsgGetPriorityFromString()| return value,
+ // since we set a default value for |priorityVal|.
+ NS_MsgGetPriorityFromString(priority, priorityVal);
+
+ return SetPriority(priorityVal);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetAuthor(char* *resultAuthor)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetSubject(char* *resultSubject)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetRecipients(char* *resultRecipients)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetCcList(char * *resultCCList)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_ccListColumnToken, resultCCList);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetBccList(char * *resultBCCList)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_bccListColumnToken, resultBCCList);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetMessageId(char * *resultMessageId)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_messageIdColumnToken, resultMessageId);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetMime2DecodedAuthor(nsAString &resultAuthor)
+{
+ return m_mdb->RowCellColumnToMime2DecodedString(GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetMime2DecodedSubject(nsAString &resultSubject)
+{
+ return m_mdb->RowCellColumnToMime2DecodedString(GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetMime2DecodedRecipients(nsAString &resultRecipients)
+{
+ return m_mdb->RowCellColumnToMime2DecodedString(GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients);
+}
+
+
+NS_IMETHODIMP nsMsgHdr::GetAuthorCollationKey(uint32_t *len, uint8_t **resultAuthor)
+{
+ return m_mdb->RowCellColumnToAddressCollationKey(GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor, len);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetSubjectCollationKey(uint32_t *len, uint8_t **resultSubject)
+{
+ return m_mdb->RowCellColumnToCollationKey(GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject, len);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetRecipientsCollationKey(uint32_t *len, uint8_t **resultRecipients)
+{
+ return m_mdb->RowCellColumnToCollationKey(GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients, len);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetCharset(char **aCharset)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_messageCharSetColumnToken, aCharset);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetCharset(const char *aCharset)
+{
+ return SetStringColumn(aCharset, m_mdb->m_messageCharSetColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetEffectiveCharset(nsACString &resultCharset)
+{
+ return m_mdb->GetEffectiveCharset(m_mdbRow, resultCharset);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetThreadParent(nsMsgKey inKey)
+{
+ m_threadParent = inKey;
+ if (inKey == m_messageKey)
+ NS_ASSERTION(false, "can't be your own parent");
+ SetUInt32Column(m_threadParent, m_mdb->m_threadParentColumnToken);
+ m_initedValues |= THREAD_PARENT_INITED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetThreadParent(nsMsgKey *result)
+{
+ nsresult res;
+ if(!(m_initedValues & THREAD_PARENT_INITED))
+ {
+ res = GetUInt32Column(m_mdb->m_threadParentColumnToken, &m_threadParent, nsMsgKey_None);
+ if (NS_SUCCEEDED(res))
+ m_initedValues |= THREAD_PARENT_INITED;
+ }
+ *result = m_threadParent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetFolder(nsIMsgFolder **result)
+{
+ NS_ENSURE_ARG(result);
+
+ if (m_mdb && m_mdb->m_folder)
+ {
+ *result = m_mdb->m_folder;
+ NS_ADDREF(*result);
+ }
+ else
+ *result = nullptr;
+ return NS_OK;
+}
+
+nsresult nsMsgHdr::SetStringColumn(const char *str, mdb_token token)
+{
+ NS_ENSURE_ARG_POINTER(str);
+ return m_mdb->CharPtrToRowCellColumn(m_mdbRow, token, str);
+}
+
+nsresult nsMsgHdr::SetUInt32Column(uint32_t value, mdb_token token)
+{
+ return m_mdb->UInt32ToRowCellColumn(m_mdbRow, token, value);
+}
+
+nsresult nsMsgHdr::GetUInt32Column(mdb_token token, uint32_t *pvalue, uint32_t defaultValue)
+{
+ return m_mdb->RowCellColumnToUInt32(GetMDBRow(), token, pvalue, defaultValue);
+}
+
+nsresult nsMsgHdr::SetUInt64Column(uint64_t value, mdb_token token)
+{
+ return m_mdb->UInt64ToRowCellColumn(m_mdbRow, token, value);
+}
+
+nsresult nsMsgHdr::GetUInt64Column(mdb_token token, uint64_t *pvalue, uint64_t defaultValue)
+{
+ return m_mdb->RowCellColumnToUInt64(GetMDBRow(), token, pvalue, defaultValue);
+}
+
+/**
+ * Roughly speaking, get the next message-id (starts with a '<' ends with a
+ * '>'). Except, we also try to handle the case where your reference is of
+ * a prehistoric vintage that just stuck any old random junk in there. Our
+ * old logic would (unintentionally?) just trim the whitespace off the front
+ * and hand you everything after that. We change things at all because that
+ * same behaviour does not make sense if we have already seen a proper message
+ * id. We keep the old behaviour at all because it would seem to have
+ * benefits. (See jwz's non-zero stats: http://www.jwz.org/doc/threading.html)
+ * So, to re-state, if there is a valid message-id in there at all, we only
+ * return valid message-id's (sans bracketing '<' and '>'). If there isn't,
+ * our result (via "references") is a left-trimmed copy of the string. If
+ * there is nothing in there, our result is an empty string.) We do require
+ * that you pass allowNonDelimitedReferences what it demands, though.
+ * For example: "<valid@stuff> this stuff is invalid" would net you
+ * "valid@stuff" and "this stuff is invalid" as results. We now only would
+ * provide "valid-stuff" and an empty string (which you should ignore) as
+ * results. However "this stuff is invalid" would return itself, allowing
+ * anything relying on that behaviour to keep working.
+ *
+ * Note: We accept anything inside the '<' and '>'; technically, we should want
+ * at least a '@' in there (per rfc 2822). But since we're going out of our
+ * way to support weird things...
+ *
+ * @param startNextRef The position to start at; this should either be the start
+ * of your references string or our return value from a previous call.
+ * @param reference You pass a nsCString by reference, we put the reference we
+ * find in it, if we find one. It may be empty! Beware!
+ * @param allowNonDelimitedReferences Should we support the
+ * pre-reasonable-standards form of In-Reply-To where it could be any
+ * arbitrary string and our behaviour was just to take off leading
+ * whitespace. It only makes sense to pass true for your first call to this
+ * function, as if you are around to make a second call, it means we found
+ * a properly formatted message-id and so we should only look for more
+ * properly formatted message-ids.
+ * @returns The next starting position of this routine, which may be pointing at
+ * a nul '\0' character to indicate termination.
+ */
+const char *nsMsgHdr::GetNextReference(const char *startNextRef,
+ nsCString &reference,
+ bool acceptNonDelimitedReferences)
+{
+ const char *ptr = startNextRef;
+ const char *whitespaceEndedAt = nullptr;
+ const char *firstMessageIdChar = nullptr;
+
+ // make the reference result string empty by default; we will set it to
+ // something valid if the time comes.
+ reference.Truncate();
+
+ // walk until we find a '<', but keep track of the first point we found that
+ // was not whitespace (as defined by previous versions of this code.)
+ for (bool foundLessThan = false; !foundLessThan; ptr++)
+ {
+ switch (*ptr)
+ {
+ case '\0':
+ // if we are at the end of the string, we found some non-whitespace, and
+ // the caller requested that we accept non-delimited whitespace,
+ // give them that as their reference. (otherwise, leave it empty)
+ if (acceptNonDelimitedReferences && whitespaceEndedAt)
+ reference = whitespaceEndedAt;
+ return ptr;
+ case ' ':
+ case '\r':
+ case '\n':
+ case '\t':
+ // do nothing, make default case mean you didn't get whitespace
+ break;
+ case '<':
+ firstMessageIdChar = ++ptr; // skip over the '<'
+ foundLessThan = true; // (flag to stop)
+ // intentional fallthrough so whitespaceEndedAt will definitely have
+ // a non-NULL value, just in case the message-id is not valid (no '>')
+ // and the old-school support is desired.
+ MOZ_FALLTHROUGH;
+ default:
+ if (!whitespaceEndedAt)
+ whitespaceEndedAt = ptr;
+ break;
+ }
+ }
+
+ // keep going until we hit a '>' or hit the end of the string
+ for(; *ptr ; ptr++)
+ {
+ if (*ptr == '>')
+ {
+ // it's valid, update reference, making sure to stop before the '>'
+ reference.Assign(firstMessageIdChar, ptr - firstMessageIdChar);
+ // and return a start point just after the '>'
+ return ++ptr;
+ }
+ }
+
+ // we did not have a fully-formed, valid message-id, so consider falling back
+ if (acceptNonDelimitedReferences && whitespaceEndedAt)
+ reference = whitespaceEndedAt;
+ return ptr;
+}
+
+bool nsMsgHdr::IsParentOf(nsIMsgDBHdr *possibleChild)
+{
+ uint16_t referenceToCheck = 0;
+ possibleChild->GetNumReferences(&referenceToCheck);
+ nsAutoCString reference;
+
+ nsCString messageId;
+ GetMessageId(getter_Copies(messageId));
+
+ while (referenceToCheck > 0)
+ {
+ possibleChild->GetStringReference(referenceToCheck - 1, reference);
+
+ if (reference.Equals(messageId))
+ return true;
+ // if reference didn't match, check if this ref is for a non-existent
+ // header. If it is, continue looking at ancestors.
+ nsCOMPtr <nsIMsgDBHdr> refHdr;
+ if (!m_mdb)
+ break;
+ (void) m_mdb->GetMsgHdrForMessageID(reference.get(), getter_AddRefs(refHdr));
+ if (refHdr)
+ break;
+ referenceToCheck--;
+ }
+ return false;
+}
+
+bool nsMsgHdr::IsAncestorOf(nsIMsgDBHdr *possibleChild)
+{
+ const char *references;
+ nsMsgHdr* curHdr = static_cast<nsMsgHdr*>(possibleChild); // closed system, cast ok
+ m_mdb->RowCellColumnToConstCharPtr(curHdr->GetMDBRow(), m_mdb->m_referencesColumnToken, &references);
+ if (!references)
+ return false;
+
+ nsCString messageId;
+ // should put < > around message id to make strstr strictly match
+ GetMessageId(getter_Copies(messageId));
+ return (strstr(references, messageId.get()) != nullptr);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetIsRead(bool *isRead)
+{
+ NS_ENSURE_ARG_POINTER(isRead);
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ *isRead = !!(m_flags & nsMsgMessageFlags::Read);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetIsFlagged(bool *isFlagged)
+{
+ NS_ENSURE_ARG_POINTER(isFlagged);
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ *isFlagged = !!(m_flags & nsMsgMessageFlags::Marked);
+ return NS_OK;
+}
+
+void nsMsgHdr::ReparentInThread(nsIMsgThread *thread)
+{
+ NS_WARNING("Borked message header, attempting to fix!");
+ uint32_t numChildren;
+ thread->GetNumChildren(&numChildren);
+ // bail out early for the singleton thread case.
+ if (numChildren == 1)
+ {
+ SetThreadParent(nsMsgKey_None);
+ return;
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgDBHdr> curHdr;
+ // loop through thread, looking for our proper parent.
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ thread->GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
+ // closed system, cast ok
+ nsMsgHdr* curMsgHdr = static_cast<nsMsgHdr*>(curHdr.get());
+ if (curHdr && curMsgHdr->IsParentOf(this))
+ {
+ nsMsgKey curHdrKey;
+ curHdr->GetMessageKey(&curHdrKey);
+ SetThreadParent(curHdrKey);
+ return;
+ }
+ }
+ // we didn't find it. So either the root header is our parent,
+ // or we're the root.
+ int32_t rootIndex;
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ thread->GetRootHdr(&rootIndex, getter_AddRefs(rootHdr));
+ NS_ASSERTION(rootHdr, "thread has no root hdr - shouldn't happen");
+ if (rootHdr)
+ {
+ nsMsgKey rootKey;
+ rootHdr->GetMessageKey(&rootKey);
+ // if we're the root, our thread parent is -1.
+ SetThreadParent(rootKey == m_messageKey ? nsMsgKey_None : rootKey);
+ }
+ }
+}
+
+bool nsMsgHdr::IsAncestorKilled(uint32_t ancestorsToCheck)
+{
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ bool isKilled = m_flags & nsMsgMessageFlags::Ignored;
+
+ if (!isKilled)
+ {
+ nsMsgKey threadParent;
+ GetThreadParent(&threadParent);
+
+ if (threadParent == m_messageKey)
+ {
+ // isKilled is false by virtue of the enclosing if statement
+ NS_ERROR("Thread is parent of itself, please fix!");
+ nsCOMPtr<nsIMsgThread> thread;
+ (void) m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread));
+ if (!thread)
+ return false;
+ ReparentInThread(thread);
+ // Something's wrong, but the problem happened some time ago, so erroring
+ // out now is probably not a good idea. Ergo, we'll pretend to be OK, show
+ // the user the thread (err on the side of caution), and let the assertion
+ // alert debuggers to a problem.
+ return false;
+ }
+ if (threadParent != nsMsgKey_None)
+ {
+ nsCOMPtr<nsIMsgDBHdr> parentHdr;
+ (void) m_mdb->GetMsgHdrForKey(threadParent, getter_AddRefs(parentHdr));
+
+ if (parentHdr)
+ {
+ // More proofing against crashers. This crasher was derived from the
+ // fact that something got borked, leaving is in hand with a circular
+ // reference to borked headers inducing these loops. The defining
+ // characteristic of these headers is that they don't actually seat
+ // themselves in the thread properly.
+ nsCOMPtr<nsIMsgThread> thread;
+ (void) m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread));
+ if (thread)
+ {
+ nsCOMPtr<nsIMsgDBHdr> claimant;
+ (void) thread->GetChild(threadParent, getter_AddRefs(claimant));
+ if (!claimant)
+ {
+ // attempt to reparent, and say the thread isn't killed,
+ // erring on the side of safety.
+ ReparentInThread(thread);
+ return false;
+ }
+ }
+
+ if (!ancestorsToCheck)
+ {
+ // We think we have a parent, but we have no more ancestors to check
+ NS_ASSERTION(false, "cycle in parent relationship, please fix!");
+ return false;
+ }
+ // closed system, cast ok
+ nsMsgHdr* parent = static_cast<nsMsgHdr*>(parentHdr.get());
+ return parent->IsAncestorKilled(ancestorsToCheck - 1);
+ }
+ }
+ }
+ return isKilled;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetIsKilled(bool *isKilled)
+{
+ NS_ENSURE_ARG_POINTER(isKilled);
+ *isKilled = false;
+ nsCOMPtr<nsIMsgThread> thread;
+ (void) m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread));
+ // if we can't find the thread, let's at least check one level; maybe
+ // the header hasn't been added to a thread yet.
+ uint32_t numChildren = 1;
+ if (thread)
+ thread->GetNumChildren(&numChildren);
+ if (!numChildren)
+ return NS_ERROR_FAILURE;
+ // We can't have as many ancestors as there are messages in the thread,
+ // so tell IsAncestorKilled to only check numChildren - 1 ancestors.
+ *isKilled = IsAncestorKilled(numChildren - 1);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#include "nsIStringEnumerator.h"
+#include "nsAutoPtr.h"
+#define NULL_MORK_COLUMN 0
+class nsMsgPropertyEnumerator : public nsIUTF8StringEnumerator
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ explicit nsMsgPropertyEnumerator(nsMsgHdr* aHdr);
+ void PrefetchNext();
+
+protected:
+ virtual ~nsMsgPropertyEnumerator();
+ nsCOMPtr<nsIMdbRowCellCursor> mRowCellCursor;
+ nsCOMPtr<nsIMdbEnv> m_mdbEnv;
+ nsCOMPtr<nsIMdbStore> m_mdbStore;
+ // Hold a reference to the hdr so it will hold an xpcom reference to the
+ // underlying mdb row. The row cell cursor will crash if the underlying
+ // row goes away.
+ RefPtr<nsMsgHdr> m_hdr;
+ bool mNextPrefetched;
+ mdb_column mNextColumn;
+};
+
+nsMsgPropertyEnumerator::nsMsgPropertyEnumerator(nsMsgHdr* aHdr)
+ : mNextPrefetched(false),
+ mNextColumn(NULL_MORK_COLUMN)
+{
+ RefPtr<nsMsgDatabase> mdb;
+ nsCOMPtr<nsIMdbRow> mdbRow;
+
+ if (aHdr &&
+ (mdbRow = aHdr->GetMDBRow()) &&
+ (m_hdr = aHdr) &&
+ (mdb = aHdr->GetMdb()) &&
+ (m_mdbEnv = mdb->m_mdbEnv) &&
+ (m_mdbStore = mdb->m_mdbStore))
+ {
+ mdbRow->GetRowCellCursor(m_mdbEnv, -1, getter_AddRefs(mRowCellCursor));
+ }
+}
+
+nsMsgPropertyEnumerator::~nsMsgPropertyEnumerator()
+{
+ // Need to clear this before the nsMsgHdr and its corresponding
+ // nsIMdbRow potentially go away.
+ mRowCellCursor = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgPropertyEnumerator, nsIUTF8StringEnumerator)
+
+NS_IMETHODIMP nsMsgPropertyEnumerator::GetNext(nsACString& aItem)
+{
+ PrefetchNext();
+ if (mNextColumn == NULL_MORK_COLUMN)
+ return NS_ERROR_FAILURE; // call HasMore first
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NOT_INITIALIZED;
+ mNextPrefetched = false;
+ char columnName[100];
+ struct mdbYarn colYarn = {columnName, 0, sizeof(columnName), 0, 0, nullptr};
+ // Get the column of the cell
+ nsresult rv = m_mdbStore->TokenToString(m_mdbEnv, mNextColumn, &colYarn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aItem.Assign(static_cast<char *>(colYarn.mYarn_Buf), colYarn.mYarn_Fill);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgPropertyEnumerator::HasMore(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ PrefetchNext();
+ *aResult = (mNextColumn != NULL_MORK_COLUMN);
+ return NS_OK;
+}
+
+void nsMsgPropertyEnumerator::PrefetchNext(void)
+{
+ if (!mNextPrefetched && m_mdbEnv && mRowCellCursor)
+ {
+ mNextPrefetched = true;
+ nsCOMPtr<nsIMdbCell> cell;
+ mRowCellCursor->NextCell(m_mdbEnv, getter_AddRefs(cell), &mNextColumn, nullptr);
+ if (mNextColumn == NULL_MORK_COLUMN)
+ {
+ // free up references
+ m_mdbStore = nullptr;
+ m_mdbEnv = nullptr;
+ mRowCellCursor = nullptr;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMsgHdr::GetPropertyEnumerator(nsIUTF8StringEnumerator** _result)
+{
+ nsMsgPropertyEnumerator* enumerator = new nsMsgPropertyEnumerator(this);
+ if (!enumerator)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*_result = enumerator);
+ return NS_OK;
+}
diff --git a/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.cpp b/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.cpp
new file mode 100644
index 000000000..2ce35047a
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.cpp
@@ -0,0 +1,378 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgOfflineImapOperation.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+PRLogModuleInfo *IMAPOffline;
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsMsgOfflineImapOperation, nsIMsgOfflineImapOperation)
+
+// property names for offine imap operation fields.
+#define PROP_OPERATION "op"
+#define PROP_OPERATION_FLAGS "opFlags"
+#define PROP_NEW_FLAGS "newFlags"
+#define PROP_MESSAGE_KEY "msgKey"
+#define PROP_SRC_MESSAGE_KEY "srcMsgKey"
+#define PROP_SRC_FOLDER_URI "srcFolderURI"
+#define PROP_MOVE_DEST_FOLDER_URI "moveDest"
+#define PROP_NUM_COPY_DESTS "numCopyDests"
+#define PROP_COPY_DESTS "copyDests" // how to delimit these? Or should we do the "dest1","dest2" etc trick? But then we'd need to shuffle
+ // them around since we delete off the front first.
+#define PROP_KEYWORD_ADD "addedKeywords"
+#define PROP_KEYWORD_REMOVE "removedKeywords"
+#define PROP_MSG_SIZE "msgSize"
+#define PROP_PLAYINGBACK "inPlayback"
+
+nsMsgOfflineImapOperation::nsMsgOfflineImapOperation(nsMsgDatabase *db, nsIMdbRow *row)
+{
+ NS_ASSERTION(db, "can't have null db");
+ NS_ASSERTION(row, "can't have null row");
+ m_operation = 0;
+ m_operationFlags = 0;
+ m_messageKey = nsMsgKey_None;
+ m_sourceMessageKey = nsMsgKey_None;
+ m_mdb = db;
+ NS_ADDREF(m_mdb);
+ m_mdbRow = row;
+ m_newFlags = 0;
+ m_mdb->GetUint32Property(m_mdbRow, PROP_OPERATION, (uint32_t *) &m_operation, 0);
+ m_mdb->GetUint32Property(m_mdbRow, PROP_MESSAGE_KEY, &m_messageKey, 0);
+ m_mdb->GetUint32Property(m_mdbRow, PROP_OPERATION_FLAGS, &m_operationFlags, 0);
+ m_mdb->GetUint32Property(m_mdbRow, PROP_NEW_FLAGS, (uint32_t *) &m_newFlags, 0);
+}
+
+nsMsgOfflineImapOperation::~nsMsgOfflineImapOperation()
+{
+ // clear the row first, in case we're holding the last reference
+ // to the db.
+ m_mdbRow = nullptr;
+ NS_IF_RELEASE(m_mdb);
+}
+
+/* attribute nsOfflineImapOperationType operation; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetOperation(nsOfflineImapOperationType *aOperation)
+{
+ NS_ENSURE_ARG(aOperation);
+ *aOperation = m_operation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetOperation(nsOfflineImapOperationType aOperation)
+{
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x setOperation was %x add %x", m_messageKey, m_operation, aOperation));
+
+ m_operation |= aOperation;
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_OPERATION, m_operation);
+}
+
+/* void clearOperation (in nsOfflineImapOperationType operation); */
+NS_IMETHODIMP nsMsgOfflineImapOperation::ClearOperation(nsOfflineImapOperationType aOperation)
+{
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x clearOperation was %x clear %x", m_messageKey, m_operation, aOperation));
+ m_operation &= ~aOperation;
+ switch (aOperation)
+ {
+ case kMsgMoved:
+ case kAppendTemplate:
+ case kAppendDraft:
+ m_moveDestination.Truncate();
+ break;
+ case kMsgCopy:
+ m_copyDestinations.RemoveElementAt(0);
+ break;
+ }
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_OPERATION, m_operation);
+}
+
+/* attribute nsMsgKey messageKey; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetMessageKey(nsMsgKey *aMessageKey)
+{
+ NS_ENSURE_ARG(aMessageKey);
+ *aMessageKey = m_messageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetMessageKey(nsMsgKey aMessageKey)
+{
+ m_messageKey = aMessageKey;
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_MESSAGE_KEY, m_messageKey);
+}
+
+/* attribute nsMsgKey srcMessageKey; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetSrcMessageKey(nsMsgKey *aMessageKey)
+{
+ NS_ENSURE_ARG(aMessageKey);
+ return m_mdb->GetUint32Property(m_mdbRow, PROP_SRC_MESSAGE_KEY, aMessageKey, nsMsgKey_None);
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetSrcMessageKey(nsMsgKey aMessageKey)
+{
+ m_messageKey = aMessageKey;
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_SRC_MESSAGE_KEY, m_messageKey);
+}
+
+/* attribute imapMessageFlagsType flagOperation; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetFlagOperation(imapMessageFlagsType *aFlagOperation)
+{
+ NS_ENSURE_ARG(aFlagOperation);
+ *aFlagOperation = m_operationFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetFlagOperation(imapMessageFlagsType aFlagOperation)
+{
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x setFlagOperation was %x add %x", m_messageKey, m_operationFlags, aFlagOperation));
+ SetOperation(kFlagsChanged);
+ nsresult rv = SetNewFlags(aFlagOperation);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_operationFlags |= aFlagOperation;
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_OPERATION_FLAGS, m_operationFlags);
+}
+
+/* attribute imapMessageFlagsType flagOperation; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetNewFlags(imapMessageFlagsType *aNewFlags)
+{
+ NS_ENSURE_ARG(aNewFlags);
+ uint32_t flags;
+ nsresult rv = m_mdb->GetUint32Property(m_mdbRow, PROP_NEW_FLAGS, &flags, 0);
+ *aNewFlags = m_newFlags = (imapMessageFlagsType) flags;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetNewFlags(imapMessageFlagsType aNewFlags)
+{
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info) && m_newFlags != aNewFlags)
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x SetNewFlags was %x to %x", m_messageKey, m_newFlags, aNewFlags));
+ m_newFlags = aNewFlags;
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_NEW_FLAGS, m_newFlags);
+}
+
+
+/* attribute string destinationFolderURI; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetDestinationFolderURI(char * *aDestinationFolderURI)
+{
+ NS_ENSURE_ARG(aDestinationFolderURI);
+ (void) m_mdb->GetProperty(m_mdbRow, PROP_MOVE_DEST_FOLDER_URI, getter_Copies(m_moveDestination));
+ *aDestinationFolderURI = ToNewCString(m_moveDestination);
+ return (*aDestinationFolderURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetDestinationFolderURI(const char * aDestinationFolderURI)
+{
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x SetDestinationFolderURI to %s", m_messageKey, aDestinationFolderURI));
+ m_moveDestination = aDestinationFolderURI ? aDestinationFolderURI : 0;
+ return m_mdb->SetProperty(m_mdbRow, PROP_MOVE_DEST_FOLDER_URI, aDestinationFolderURI);
+}
+
+/* attribute string sourceFolderURI; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetSourceFolderURI(char * *aSourceFolderURI)
+{
+ NS_ENSURE_ARG(aSourceFolderURI);
+ nsresult rv = m_mdb->GetProperty(m_mdbRow, PROP_SRC_FOLDER_URI, getter_Copies(m_sourceFolder));
+ *aSourceFolderURI = ToNewCString(m_sourceFolder);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetSourceFolderURI(const char * aSourceFolderURI)
+{
+ m_sourceFolder = aSourceFolderURI ? aSourceFolderURI : 0;
+ SetOperation(kMoveResult);
+
+ return m_mdb->SetProperty(m_mdbRow, PROP_SRC_FOLDER_URI, aSourceFolderURI);
+}
+
+/* attribute string keyword; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetKeywordsToAdd(char * *aKeywords)
+{
+ NS_ENSURE_ARG(aKeywords);
+ nsresult rv = m_mdb->GetProperty(m_mdbRow, PROP_KEYWORD_ADD, getter_Copies(m_keywordsToAdd));
+ *aKeywords = ToNewCString(m_keywordsToAdd);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::AddKeywordToAdd(const char * aKeyword)
+{
+ SetOperation(kAddKeywords);
+ return AddKeyword(aKeyword, m_keywordsToAdd, PROP_KEYWORD_ADD, m_keywordsToRemove, PROP_KEYWORD_REMOVE);
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetKeywordsToRemove(char * *aKeywords)
+{
+ NS_ENSURE_ARG(aKeywords);
+ nsresult rv = m_mdb->GetProperty(m_mdbRow, PROP_KEYWORD_REMOVE, getter_Copies(m_keywordsToRemove));
+ *aKeywords = ToNewCString(m_keywordsToRemove);
+ return rv;
+}
+
+nsresult nsMsgOfflineImapOperation::AddKeyword(const char *aKeyword, nsCString &addList, const char *addProp,
+ nsCString &removeList, const char *removeProp)
+{
+ int32_t startOffset, keywordLength;
+ if (!MsgFindKeyword(nsDependentCString(aKeyword), addList, &startOffset, &keywordLength))
+ {
+ if (!addList.IsEmpty())
+ addList.Append(' ');
+ addList.Append(aKeyword);
+ }
+ // if the keyword we're removing was in the list of keywords to add,
+ // cut it from that list.
+ if (MsgFindKeyword(nsDependentCString(aKeyword), removeList, &startOffset, &keywordLength))
+ {
+ removeList.Cut(startOffset, keywordLength);
+ m_mdb->SetProperty(m_mdbRow, removeProp, removeList.get());
+ }
+ return m_mdb->SetProperty(m_mdbRow, addProp, addList.get());
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::AddKeywordToRemove(const char * aKeyword)
+{
+ SetOperation(kRemoveKeywords);
+ return AddKeyword(aKeyword, m_keywordsToRemove, PROP_KEYWORD_REMOVE, m_keywordsToAdd, PROP_KEYWORD_ADD);
+}
+
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::AddMessageCopyOperation(const char *destinationBox)
+{
+ SetOperation(kMsgCopy);
+ nsAutoCString newDest(destinationBox);
+ nsresult rv = GetCopiesFromDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_copyDestinations.AppendElement(newDest);
+ return SetCopiesToDB();
+}
+
+// we write out the folders as one string, separated by 0x1.
+#define FOLDER_SEP_CHAR '\001'
+
+nsresult nsMsgOfflineImapOperation::GetCopiesFromDB()
+{
+ nsCString copyDests;
+ m_copyDestinations.Clear();
+ nsresult rv = m_mdb->GetProperty(m_mdbRow, PROP_COPY_DESTS, getter_Copies(copyDests));
+ // use 0x1 as the delimiter between folder names since it's not a legal character
+ if (NS_SUCCEEDED(rv) && !copyDests.IsEmpty())
+ {
+ int32_t curCopyDestStart = 0;
+ int32_t nextCopyDestPos = 0;
+
+ while (nextCopyDestPos != -1)
+ {
+ nsCString curDest;
+ nextCopyDestPos = copyDests.FindChar(FOLDER_SEP_CHAR, curCopyDestStart);
+ if (nextCopyDestPos > 0)
+ curDest = Substring(copyDests, curCopyDestStart, nextCopyDestPos - curCopyDestStart);
+ else
+ curDest = Substring(copyDests, curCopyDestStart, copyDests.Length() - curCopyDestStart);
+ curCopyDestStart = nextCopyDestPos + 1;
+ m_copyDestinations.AppendElement(curDest);
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgOfflineImapOperation::SetCopiesToDB()
+{
+ nsAutoCString copyDests;
+
+ // use 0x1 as the delimiter between folders
+ for (uint32_t i = 0; i < m_copyDestinations.Length(); i++)
+ {
+ if (i > 0)
+ copyDests.Append(FOLDER_SEP_CHAR);
+ copyDests.Append(m_copyDestinations.ElementAt(i));
+ }
+ return m_mdb->SetProperty(m_mdbRow, PROP_COPY_DESTS, copyDests.get());
+}
+
+/* attribute long numberOfCopies; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetNumberOfCopies(int32_t *aNumberOfCopies)
+{
+ NS_ENSURE_ARG(aNumberOfCopies);
+ nsresult rv = GetCopiesFromDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aNumberOfCopies = m_copyDestinations.Length();
+ return NS_OK;
+}
+
+/* string getCopyDestination (in long copyIndex); */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetCopyDestination(int32_t copyIndex, char **retval)
+{
+ NS_ENSURE_ARG(retval);
+ nsresult rv = GetCopiesFromDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (copyIndex >= (int32_t)m_copyDestinations.Length())
+ return NS_ERROR_ILLEGAL_VALUE;
+ *retval = ToNewCString(m_copyDestinations.ElementAt(copyIndex));
+ return (*retval) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+/* attribute unsigned log msgSize; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetMsgSize(uint32_t *aMsgSize)
+{
+ NS_ENSURE_ARG(aMsgSize);
+ return m_mdb->GetUint32Property(m_mdbRow, PROP_MSG_SIZE, aMsgSize, 0);
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetMsgSize(uint32_t aMsgSize)
+{
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_MSG_SIZE, aMsgSize);
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetPlayingBack(bool aPlayingBack)
+{
+ return m_mdb->SetBooleanProperty(m_mdbRow, PROP_PLAYINGBACK, aPlayingBack);
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetPlayingBack(bool *aPlayingBack)
+{
+ NS_ENSURE_ARG(aPlayingBack);
+ return m_mdb->GetBooleanProperty(m_mdbRow, PROP_PLAYINGBACK, aPlayingBack);
+}
+
+
+void nsMsgOfflineImapOperation::Log(PRLogModuleInfo *logFile)
+{
+ if (!IMAPOffline)
+ IMAPOffline = PR_NewLogModule("IMAPOFFLINE");
+ if (!MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ return;
+ // const long kMoveResult = 0x8;
+ // const long kAppendDraft = 0x10;
+ // const long kAddedHeader = 0x20;
+ // const long kDeletedMsg = 0x40;
+ // const long kMsgMarkedDeleted = 0x80;
+ // const long kAppendTemplate = 0x100;
+ // const long kDeleteAllMsgs = 0x200;
+ if (m_operation & nsIMsgOfflineImapOperation::kFlagsChanged)
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x changeFlag:%x", m_messageKey, m_newFlags));
+ if (m_operation & nsIMsgOfflineImapOperation::kMsgMoved)
+ {
+ nsCString moveDestFolder;
+ GetDestinationFolderURI(getter_Copies(moveDestFolder));
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x moveTo:%s", m_messageKey, moveDestFolder.get()));
+ }
+ if (m_operation & nsIMsgOfflineImapOperation::kMsgCopy)
+ {
+ nsCString copyDests;
+ m_mdb->GetProperty(m_mdbRow, PROP_COPY_DESTS, getter_Copies(copyDests));
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x moveTo:%s", m_messageKey, copyDests.get()));
+ }
+ if (m_operation & nsIMsgOfflineImapOperation::kAppendDraft)
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x append draft", m_messageKey));
+ if (m_operation & nsIMsgOfflineImapOperation::kAddKeywords)
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x add keyword:%s", m_messageKey, m_keywordsToAdd.get()));
+ if (m_operation & nsIMsgOfflineImapOperation::kRemoveKeywords)
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x remove keyword:%s", m_messageKey, m_keywordsToRemove.get()));
+}
diff --git a/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.h b/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.h
new file mode 100644
index 000000000..e892a253a
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsMsgOfflineImapOperation_H_
+
+#include "nsIMsgOfflineImapOperation.h"
+#include "mdb.h"
+#include "nsMsgDatabase.h"
+#include "prlog.h"
+
+class nsMsgOfflineImapOperation : public nsIMsgOfflineImapOperation
+{
+public:
+ /** Instance Methods **/
+ nsMsgOfflineImapOperation(nsMsgDatabase *db, nsIMdbRow *row);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGOFFLINEIMAPOPERATION
+
+
+ nsIMdbRow *GetMDBRow() {return m_mdbRow;}
+ nsresult GetCopiesFromDB();
+ nsresult SetCopiesToDB();
+ void Log(PRLogModuleInfo *logFile);
+protected:
+ virtual ~nsMsgOfflineImapOperation();
+ nsresult AddKeyword(const char *aKeyword, nsCString &addList, const char *addProp,
+ nsCString &removeList, const char *removeProp);
+
+ nsOfflineImapOperationType m_operation;
+ nsMsgKey m_messageKey;
+ nsMsgKey m_sourceMessageKey;
+ uint32_t m_operationFlags; // what to do on sync
+ imapMessageFlagsType m_newFlags; // used for kFlagsChanged
+
+ // these are URI's, and are escaped. Thus, we can use a delimter like ' '
+ // because the real spaces should be escaped.
+ nsCString m_sourceFolder;
+ nsCString m_moveDestination;
+ nsTArray<nsCString> m_copyDestinations;
+
+ nsCString m_keywordsToAdd;
+ nsCString m_keywordsToRemove;
+
+ // nsMsgOfflineImapOperation will have to know what db and row they belong to, since they are really
+ // just a wrapper around the offline operation row in the mdb.
+ // though I hope not.
+ nsMsgDatabase *m_mdb;
+ nsCOMPtr <nsIMdbRow> m_mdbRow;
+};
+
+
+
+#endif /* _nsMsgOfflineImapOperation_H_ */
+
diff --git a/mailnews/db/msgdb/src/nsMsgThread.cpp b/mailnews/db/msgdb/src/nsMsgThread.cpp
new file mode 100644
index 000000000..0d8ad3d85
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMsgThread.cpp
@@ -0,0 +1,1180 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgDatabase.h"
+#include "nsCOMPtr.h"
+#include "nsMsgThread.h"
+#include "MailNewsTypes2.h"
+#include "mozilla/DebugOnly.h"
+
+NS_IMPL_ISUPPORTS(nsMsgThread, nsIMsgThread)
+
+nsMsgThread::nsMsgThread()
+{
+ MOZ_COUNT_CTOR(nsMsgThread);
+ Init();
+}
+nsMsgThread::nsMsgThread(nsMsgDatabase *db, nsIMdbTable *table)
+{
+ MOZ_COUNT_CTOR(nsMsgThread);
+ Init();
+ m_mdbTable = table;
+ m_mdbDB = db;
+ if (db)
+ db->m_threads.AppendElement(this);
+ else
+ NS_ERROR("no db for thread");
+#ifdef DEBUG_David_Bienvenu
+ if (m_mdbDB->m_threads.Length() > 5)
+ printf("more than five outstanding threads\n");
+#endif
+ if (table && db)
+ {
+ table->GetMetaRow(db->GetEnv(), nullptr, nullptr, getter_AddRefs(m_metaRow));
+ InitCachedValues();
+ }
+}
+
+void nsMsgThread::Init()
+{
+ m_threadKey = nsMsgKey_None;
+ m_threadRootKey = nsMsgKey_None;
+ m_numChildren = 0;
+ m_numUnreadChildren = 0;
+ m_flags = 0;
+ m_newestMsgDate = 0;
+ m_cachedValuesInitialized = false;
+}
+
+nsMsgThread::~nsMsgThread()
+{
+ MOZ_COUNT_DTOR(nsMsgThread);
+ if (m_mdbDB)
+ {
+ mozilla::DebugOnly<bool> found = m_mdbDB->m_threads.RemoveElement(this);
+ NS_ASSERTION(found, "removing thread not in threads array");
+ }
+ else // This can happen if db is forced closed
+ NS_WARNING("null db in thread");
+ Clear();
+}
+
+void nsMsgThread::Clear()
+{
+ m_mdbTable = nullptr;
+ m_metaRow = nullptr;
+ m_mdbDB = nullptr;
+}
+
+nsresult nsMsgThread::InitCachedValues()
+{
+ nsresult err = NS_OK;
+
+ NS_ENSURE_TRUE(m_mdbDB && m_metaRow, NS_ERROR_INVALID_POINTER);
+
+ if (!m_cachedValuesInitialized)
+ {
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadFlagsColumnToken, &m_flags);
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadChildrenColumnToken, &m_numChildren);
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadIdColumnToken, &m_threadKey, nsMsgKey_None);
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadUnreadChildrenColumnToken, &m_numUnreadChildren);
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadRootKeyColumnToken, &m_threadRootKey, nsMsgKey_None);
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadNewestMsgDateColumnToken, &m_newestMsgDate, 0);
+ // fix num children if it's wrong. this doesn't work - some DB's have a bogus thread table
+ // that is full of bogus headers - don't know why.
+ uint32_t rowCount = 0;
+ m_mdbTable->GetCount(m_mdbDB->GetEnv(), &rowCount);
+ // NS_ASSERTION(m_numChildren <= rowCount, "num children wrong - fixing");
+ if (m_numChildren > rowCount)
+ ChangeChildCount((int32_t) rowCount - (int32_t) m_numChildren);
+ if ((int32_t) m_numUnreadChildren < 0)
+ ChangeUnreadChildCount(- (int32_t) m_numUnreadChildren);
+ if (NS_SUCCEEDED(err))
+ m_cachedValuesInitialized = true;
+ }
+ return err;
+}
+
+NS_IMETHODIMP nsMsgThread::SetThreadKey(nsMsgKey threadKey)
+{
+ NS_ASSERTION(m_threadKey == nsMsgKey_None || m_threadKey == threadKey,
+ "shouldn't be changing thread key");
+ m_threadKey = threadKey;
+ // by definition, the initial thread key is also the thread root key.
+ SetThreadRootKey(threadKey);
+ // gotta set column in meta row here.
+ return m_mdbDB->UInt32ToRowCellColumn(
+ m_metaRow, m_mdbDB->m_threadIdColumnToken, threadKey);
+}
+
+NS_IMETHODIMP nsMsgThread::GetThreadKey(nsMsgKey *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsresult res = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadIdColumnToken, &m_threadKey);
+ *result = m_threadKey;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgThread::GetFlags(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsresult res = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadFlagsColumnToken, &m_flags);
+ *result = m_flags;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgThread::SetFlags(uint32_t flags)
+{
+ m_flags = flags;
+ return m_mdbDB->UInt32ToRowCellColumn(
+ m_metaRow, m_mdbDB->m_threadFlagsColumnToken, m_flags);
+}
+
+NS_IMETHODIMP nsMsgThread::SetSubject(const nsACString& aSubject)
+{
+ return m_mdbDB->CharPtrToRowCellColumn(m_metaRow, m_mdbDB->m_threadSubjectColumnToken, nsCString(aSubject).get());
+}
+
+NS_IMETHODIMP nsMsgThread::GetSubject(nsACString& aSubject)
+{
+ nsCString subjectStr;
+ nsresult rv = m_mdbDB->RowCellColumnToCharPtr(m_metaRow, m_mdbDB->m_threadSubjectColumnToken,
+ getter_Copies(subjectStr));
+
+ aSubject.Assign(subjectStr);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::GetNumChildren(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_numChildren;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgThread::GetNumUnreadChildren (uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_numUnreadChildren;
+ return NS_OK;
+}
+
+nsresult nsMsgThread::RerootThread(nsIMsgDBHdr *newParentOfOldRoot, nsIMsgDBHdr *oldRoot, nsIDBChangeAnnouncer *announcer)
+{
+ nsresult rv = NS_OK;
+ mdb_pos outPos;
+ nsMsgKey newHdrAncestor;
+ nsCOMPtr <nsIMsgDBHdr> ancestorHdr = newParentOfOldRoot;
+ nsMsgKey newRoot;
+
+ ancestorHdr->GetMessageKey(&newRoot);
+ // loop trying to find the oldest ancestor of this msg
+ // that is a parent of the root. The oldest ancestor will
+ // become the root of the thread.
+ do
+ {
+ ancestorHdr->GetThreadParent(&newHdrAncestor);
+ if (newHdrAncestor != nsMsgKey_None && newHdrAncestor != m_threadRootKey && newHdrAncestor != newRoot)
+ {
+ newRoot = newHdrAncestor;
+ rv = m_mdbDB->GetMsgHdrForKey(newRoot, getter_AddRefs(ancestorHdr));
+ }
+ }
+ while (NS_SUCCEEDED(rv) && ancestorHdr && newHdrAncestor != nsMsgKey_None && newHdrAncestor != m_threadRootKey
+ && newHdrAncestor != newRoot);
+ SetThreadRootKey(newRoot);
+ ReparentNonReferenceChildrenOf(oldRoot, newRoot, announcer);
+ if (ancestorHdr)
+ {
+ nsIMsgDBHdr *msgHdr = ancestorHdr;
+ nsMsgHdr* rootMsgHdr = static_cast<nsMsgHdr*>(msgHdr); // closed system, cast ok
+ nsIMdbRow *newRootHdrRow = rootMsgHdr->GetMDBRow();
+ // move the root hdr to pos 0.
+ m_mdbTable->MoveRow(m_mdbDB->GetEnv(), newRootHdrRow, -1, 0, &outPos);
+ ancestorHdr->SetThreadParent(nsMsgKey_None);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::AddChild(nsIMsgDBHdr *child, nsIMsgDBHdr *inReplyTo, bool threadInThread,
+ nsIDBChangeAnnouncer *announcer)
+{
+ nsresult rv = NS_OK;
+ nsMsgHdr* hdr = static_cast<nsMsgHdr*>(child); // closed system, cast ok
+ uint32_t newHdrFlags = 0;
+ uint32_t msgDate;
+ nsMsgKey newHdrKey = 0;
+ bool parentKeyNeedsSetting = true;
+
+ nsIMdbRow *hdrRow = hdr->GetMDBRow();
+ NS_ENSURE_STATE(hdrRow);
+ hdr->GetRawFlags(&newHdrFlags);
+ hdr->GetMessageKey(&newHdrKey);
+ hdr->GetDateInSeconds(&msgDate);
+ if (msgDate > m_newestMsgDate)
+ SetNewestMsgDate(msgDate);
+
+ if (newHdrFlags & nsMsgMessageFlags::Watched)
+ SetFlags(m_flags | nsMsgMessageFlags::Watched);
+
+ child->AndFlags(~(nsMsgMessageFlags::Watched), &newHdrFlags);
+
+ // These are threading flags that the child may have set before being added
+ // to the database.
+ uint32_t protoThreadFlags;
+ child->GetUint32Property("ProtoThreadFlags", &protoThreadFlags);
+ SetFlags(m_flags | protoThreadFlags);
+ // Clear the flag so that it doesn't fudge anywhere else
+ child->SetUint32Property("ProtoThreadFlags", 0);
+
+ uint32_t numChildren;
+ uint32_t childIndex = 0;
+
+ // get the num children before we add the new header.
+ GetNumChildren(&numChildren);
+
+ // if this is an empty thread, set the root key to this header's key
+ if (numChildren == 0)
+ SetThreadRootKey(newHdrKey);
+
+ if (m_mdbTable)
+ {
+ m_mdbTable->AddRow(m_mdbDB->GetEnv(), hdrRow);
+ ChangeChildCount(1);
+ if (! (newHdrFlags & nsMsgMessageFlags::Read))
+ ChangeUnreadChildCount(1);
+ }
+ if (inReplyTo)
+ {
+ nsMsgKey parentKey;
+ inReplyTo->GetMessageKey(&parentKey);
+ child->SetThreadParent(parentKey);
+ parentKeyNeedsSetting = false;
+ }
+
+ // check if this header is a parent of one of the messages in this thread
+ bool hdrMoved = false;
+ nsCOMPtr <nsIMsgDBHdr> curHdr;
+ uint32_t moveIndex = 0;
+
+ PRTime newHdrDate;
+ child->GetDate(&newHdrDate);
+
+ // This is an ugly but simple fix for a difficult problem. Basically, when we add
+ // a message to a thread, we have to run through the thread to see if the new
+ // message is a parent of an existing message in the thread, and adjust things
+ // accordingly. If you thread by subject, and you have a large folder with
+ // messages w/ all the same subject, this code can take a really long time. So the
+ // pragmatic thing is to say that for threads with more than 1000 messages, it's
+ // simply not worth dealing with the case where the parent comes in after the
+ // child. Threads with more than 1000 messages are pretty unwieldy anyway.
+ // See Bug 90452
+
+ if (numChildren < 1000)
+ {
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsMsgKey msgKey = nsMsgKey_None;
+
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr)
+ {
+ if (hdr->IsParentOf(curHdr))
+ {
+ nsMsgKey oldThreadParent;
+ mdb_pos outPos;
+ // move this hdr before the current header.
+ if (!hdrMoved)
+ {
+ m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, childIndex, &outPos);
+ hdrMoved = true;
+ curHdr->GetThreadParent(&oldThreadParent);
+ curHdr->GetMessageKey(&msgKey);
+ nsCOMPtr <nsIMsgDBHdr> curParent;
+ m_mdbDB->GetMsgHdrForKey(oldThreadParent, getter_AddRefs(curParent));
+ if (curParent && hdr->IsAncestorOf(curParent))
+ {
+ nsMsgKey curParentKey;
+ curParent->GetMessageKey(&curParentKey);
+ if (curParentKey == m_threadRootKey)
+ {
+ m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, 0, &outPos);
+ RerootThread(child, curParent, announcer);
+ parentKeyNeedsSetting = false;
+ }
+ }
+ else if (msgKey == m_threadRootKey)
+ {
+ RerootThread(child, curHdr, announcer);
+ parentKeyNeedsSetting = false;
+ }
+ }
+ curHdr->SetThreadParent(newHdrKey);
+ // TODO: what should be msgKey if hdrMoved was true above?
+ if (msgKey == newHdrKey)
+ parentKeyNeedsSetting = false;
+
+ // OK, this is a reparenting - need to send notification
+ if (announcer)
+ announcer->NotifyParentChangedAll(msgKey, oldThreadParent, newHdrKey, nullptr);
+#ifdef DEBUG_bienvenu1
+ if (newHdrKey != m_threadKey)
+ printf("adding second level child\n");
+#endif
+ }
+ // Calculate a position for this child in date order
+ else if (!hdrMoved && childIndex > 0 && moveIndex == 0)
+ {
+ PRTime curHdrDate;
+
+ curHdr->GetDate(&curHdrDate);
+ if (newHdrDate < curHdrDate)
+ moveIndex = childIndex;
+ }
+ }
+ }
+ }
+ // If this header is not a reply to a header in the thread, and isn't a parent
+ // check to see if it starts with Re: - if not, and the first header does start
+ // with re, should we make this header the top level header?
+ // If it's date is less (or it's ID?), then yes.
+ if (numChildren > 0 && !(newHdrFlags & nsMsgMessageFlags::HasRe) && !inReplyTo)
+ {
+ PRTime topLevelHdrDate;
+
+ nsCOMPtr <nsIMsgDBHdr> topLevelHdr;
+ rv = GetRootHdr(nullptr, getter_AddRefs(topLevelHdr));
+ if (NS_SUCCEEDED(rv) && topLevelHdr)
+ {
+ topLevelHdr->GetDate(&topLevelHdrDate);
+ if (newHdrDate < topLevelHdrDate)
+ {
+ RerootThread(child, topLevelHdr, announcer);
+ mdb_pos outPos;
+ m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, 0, &outPos);
+ hdrMoved = true;
+ topLevelHdr->SetThreadParent(newHdrKey);
+ parentKeyNeedsSetting = false;
+ // ### need to get ancestor of new hdr here too.
+ SetThreadRootKey(newHdrKey);
+ child->SetThreadParent(nsMsgKey_None);
+ // argh, here we'd need to adjust all the headers that listed
+ // the demoted header as their thread parent, but only because
+ // of subject threading. Adjust them to point to the new parent,
+ // that is.
+ ReparentNonReferenceChildrenOf(topLevelHdr, newHdrKey, announcer);
+ }
+ }
+ }
+ // OK, check to see if we added this header, and didn't parent it.
+
+ if (numChildren > 0 && parentKeyNeedsSetting)
+ child->SetThreadParent(m_threadRootKey);
+
+ // Move child to keep thread sorted in ascending date order
+ if (!hdrMoved && moveIndex > 0)
+ {
+ mdb_pos outPos;
+ m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, moveIndex, &outPos);
+ }
+
+ // do this after we've put the new hdr in the thread
+ bool isKilled;
+ child->GetIsKilled(&isKilled);
+ if ((m_flags & nsMsgMessageFlags::Ignored || isKilled) && m_mdbDB)
+ m_mdbDB->MarkHdrRead(child, true, nullptr);
+#ifdef DEBUG_David_Bienvenu
+ nsMsgKey msgHdrThreadKey;
+ child->GetThreadId(&msgHdrThreadKey);
+ NS_ASSERTION(msgHdrThreadKey == m_threadKey, "adding msg to thread it doesn't belong to");
+#endif
+#ifdef DEBUG_bienvenu1
+ nsMsgDatabase *msgDB = static_cast<nsMsgDatabase*>(m_mdbDB);
+ msgDB->DumpThread(m_threadRootKey);
+#endif
+ return rv;
+}
+
+nsresult nsMsgThread::ReparentNonReferenceChildrenOf(nsIMsgDBHdr *oldTopLevelHdr, nsMsgKey newParentKey,
+ nsIDBChangeAnnouncer *announcer)
+{
+ nsCOMPtr <nsIMsgDBHdr> curHdr;
+ uint32_t numChildren;
+ uint32_t childIndex = 0;
+
+ GetNumChildren(&numChildren);
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsMsgKey oldTopLevelHdrKey;
+
+ oldTopLevelHdr->GetMessageKey(&oldTopLevelHdrKey);
+ nsresult rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr)
+ {
+ nsMsgKey oldThreadParent, curHdrKey;
+ nsMsgHdr* oldTopLevelMsgHdr = static_cast<nsMsgHdr*>(oldTopLevelHdr); // closed system, cast ok
+ curHdr->GetThreadParent(&oldThreadParent);
+ curHdr->GetMessageKey(&curHdrKey);
+ if (oldThreadParent == oldTopLevelHdrKey && curHdrKey != newParentKey && !oldTopLevelMsgHdr->IsParentOf(curHdr))
+ {
+ curHdr->GetThreadParent(&oldThreadParent);
+ curHdr->SetThreadParent(newParentKey);
+ // OK, this is a reparenting - need to send notification
+ if (announcer)
+ announcer->NotifyParentChangedAll(curHdrKey, oldThreadParent, newParentKey, nullptr);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgThread::GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsresult rv;
+
+ if (aIndex >= m_numChildren)
+ {
+ *aResult = nsMsgKey_None;
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ mdbOid oid;
+ rv = m_mdbTable->PosToOid( m_mdbDB->GetEnv(), aIndex, &oid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = oid.mOid_Id;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgThread::GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **result)
+{
+ // mork doesn't seem to handle this correctly, so deal with going off
+ // the end here.
+ if (aIndex >= m_numChildren)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ mdbOid oid;
+ nsresult rv = m_mdbTable->PosToOid( m_mdbDB->GetEnv(), aIndex, &oid);
+ NS_ENSURE_SUCCESS(rv, NS_MSG_MESSAGE_NOT_FOUND);
+ nsIMdbRow *hdrRow = nullptr;
+ rv = m_mdbTable->PosToRow(m_mdbDB->GetEnv(), aIndex, &hdrRow);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hdrRow, NS_ERROR_FAILURE);
+ // CreateMsgHdr takes ownership of the hdrRow reference.
+ rv = m_mdbDB->CreateMsgHdr(hdrRow, oid.mOid_Id , result);
+ return (NS_SUCCEEDED(rv)) ? NS_OK : NS_MSG_MESSAGE_NOT_FOUND;
+}
+
+NS_IMETHODIMP nsMsgThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr **result)
+{
+ nsresult rv;
+
+ mdb_bool hasOid;
+ mdbOid rowObjectId;
+
+ NS_ENSURE_ARG_POINTER(result);
+ NS_ENSURE_TRUE(m_mdbTable, NS_ERROR_INVALID_POINTER);
+
+ *result = NULL;
+ rowObjectId.mOid_Id = msgKey;
+ rowObjectId.mOid_Scope = m_mdbDB->m_hdrRowScopeToken;
+ rv = m_mdbTable->HasOid(m_mdbDB->GetEnv(), &rowObjectId, &hasOid);
+
+ if (NS_SUCCEEDED(rv) && hasOid && m_mdbDB && m_mdbDB->m_mdbStore)
+ {
+ nsIMdbRow *hdrRow = nullptr;
+ rv = m_mdbDB->m_mdbStore->GetRow(m_mdbDB->GetEnv(), &rowObjectId, &hdrRow);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hdrRow, NS_ERROR_FAILURE);
+ rv = m_mdbDB->CreateMsgHdr(hdrRow, msgKey, result);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::RemoveChildAt(uint32_t aIndex)
+{
+ return NS_OK;
+}
+
+nsresult nsMsgThread::RemoveChild(nsMsgKey msgKey)
+{
+ nsresult rv;
+
+ mdbOid rowObjectId;
+ rowObjectId.mOid_Id = msgKey;
+ rowObjectId.mOid_Scope = m_mdbDB->m_hdrRowScopeToken;
+ rv = m_mdbTable->CutOid(m_mdbDB->GetEnv(), &rowObjectId);
+ // if this thread is empty, remove it from the all threads table.
+ if (m_numChildren == 0 && m_mdbDB->m_mdbAllThreadsTable)
+ {
+ mdbOid rowID;
+ rowID.mOid_Id = m_threadKey;
+ rowID.mOid_Scope = m_mdbDB->m_threadRowScopeToken;
+
+ m_mdbDB->m_mdbAllThreadsTable->CutOid(m_mdbDB->GetEnv(), &rowID);
+ }
+#if 0 // this seems to cause problems
+ if (m_numChildren == 0 && m_metaRow && m_mdbDB)
+ m_metaRow->CutAllColumns(m_mdbDB->GetEnv());
+#endif
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer)
+{
+ uint32_t flags;
+ nsMsgKey key;
+ nsMsgKey threadParent;
+
+ NS_ENSURE_ARG_POINTER(child);
+
+ child->GetFlags(&flags);
+ child->GetMessageKey(&key);
+
+ child->GetThreadParent(&threadParent);
+ ReparentChildrenOf(key, threadParent, announcer);
+
+ // if this was the newest msg, clear the newest msg date so we'll recalc.
+ uint32_t date;
+ child->GetDateInSeconds(&date);
+ if (date == m_newestMsgDate)
+ SetNewestMsgDate(0);
+
+ if (!(flags & nsMsgMessageFlags::Read))
+ ChangeUnreadChildCount(-1);
+ ChangeChildCount(-1);
+ return RemoveChild(key);
+}
+
+nsresult nsMsgThread::ReparentChildrenOf(nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeAnnouncer *announcer)
+{
+ nsresult rv = NS_OK;
+
+ uint32_t numChildren;
+ uint32_t childIndex = 0;
+
+ GetNumChildren(&numChildren);
+
+ nsCOMPtr <nsIMsgDBHdr> curHdr;
+ if (numChildren > 0)
+ {
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr)
+ {
+ nsMsgKey threadParent;
+
+ curHdr->GetThreadParent(&threadParent);
+ if (threadParent == oldParent)
+ {
+ nsMsgKey curKey;
+
+ curHdr->SetThreadParent(newParent);
+ curHdr->GetMessageKey(&curKey);
+ if (announcer)
+ announcer->NotifyParentChangedAll(curKey, oldParent, newParent, nullptr);
+ // if the old parent was the root of the thread, then only the first child gets
+ // promoted to root, and other children become children of the new root.
+ if (newParent == nsMsgKey_None)
+ {
+ SetThreadRootKey(curKey);
+ newParent = curKey;
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::MarkChildRead(bool bRead)
+{
+ ChangeUnreadChildCount(bRead ? -1 : 1);
+ return NS_OK;
+}
+
+class nsMsgThreadEnumerator : public nsISimpleEnumerator {
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsMsgThreadEnumerator methods:
+ typedef nsresult (*nsMsgThreadEnumeratorFilter)(nsIMsgDBHdr* hdr, void* closure);
+
+ nsMsgThreadEnumerator(nsMsgThread *thread, nsMsgKey startKey,
+ nsMsgThreadEnumeratorFilter filter, void* closure);
+ int32_t MsgKeyFirstChildIndex(nsMsgKey inMsgKey);
+
+protected:
+ virtual ~nsMsgThreadEnumerator();
+
+ nsresult Prefetch();
+
+ nsIMdbTableRowCursor* mRowCursor;
+ nsCOMPtr <nsIMsgDBHdr> mResultHdr;
+ nsMsgThread* mThread;
+ nsMsgKey mThreadParentKey;
+ nsMsgKey mFirstMsgKey;
+ int32_t mChildIndex;
+ bool mDone;
+ bool mNeedToPrefetch;
+ nsMsgThreadEnumeratorFilter mFilter;
+ void* mClosure;
+ bool mFoundChildren;
+};
+
+nsMsgThreadEnumerator::nsMsgThreadEnumerator(nsMsgThread *thread, nsMsgKey startKey,
+ nsMsgThreadEnumeratorFilter filter, void* closure)
+ : mRowCursor(nullptr), mDone(false),
+ mFilter(filter), mClosure(closure), mFoundChildren(false)
+{
+ mThreadParentKey = startKey;
+ mChildIndex = 0;
+ mThread = thread;
+ mNeedToPrefetch = true;
+ mFirstMsgKey = nsMsgKey_None;
+
+ nsresult rv = mThread->GetRootHdr(nullptr, getter_AddRefs(mResultHdr));
+
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ mResultHdr->GetMessageKey(&mFirstMsgKey);
+
+ uint32_t numChildren;
+ mThread->GetNumChildren(&numChildren);
+
+ if (mThreadParentKey != nsMsgKey_None)
+ {
+ nsMsgKey msgKey = nsMsgKey_None;
+ uint32_t childIndex = 0;
+
+
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ {
+ mResultHdr->GetMessageKey(&msgKey);
+
+ if (msgKey == startKey)
+ {
+ mChildIndex = MsgKeyFirstChildIndex(msgKey);
+ mDone = (mChildIndex < 0);
+ break;
+ }
+
+ if (mDone)
+ break;
+
+ }
+ else
+ NS_ASSERTION(false, "couldn't get child from thread");
+ }
+ }
+
+#ifdef DEBUG_bienvenu1
+ nsCOMPtr <nsIMsgDBHdr> child;
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ nsMsgKey threadParent;
+ nsMsgKey msgKey;
+ // we're only doing one level of threading, so check if caller is
+ // asking for children of the first message in the thread or not.
+ // if not, we will tell him there are no children.
+ child->GetMessageKey(&msgKey);
+ child->GetThreadParent(&threadParent);
+
+ printf("index = %ld key = %ld parent = %lx\n", childIndex, msgKey, threadParent);
+ }
+ }
+#endif
+ NS_ADDREF(thread);
+}
+
+nsMsgThreadEnumerator::~nsMsgThreadEnumerator()
+{
+ NS_RELEASE(mThread);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgThreadEnumerator, nsISimpleEnumerator)
+
+
+int32_t nsMsgThreadEnumerator::MsgKeyFirstChildIndex(nsMsgKey inMsgKey)
+{
+ // if (msgKey != mThreadParentKey)
+ // mDone = true;
+ // look through rest of thread looking for a child of this message.
+ // If the inMsgKey is the first message in the thread, then all children
+ // without parents are considered to be children of inMsgKey.
+ // Otherwise, only true children qualify.
+ uint32_t numChildren;
+ nsCOMPtr <nsIMsgDBHdr> curHdr;
+ int32_t firstChildIndex = -1;
+
+ mThread->GetNumChildren(&numChildren);
+
+ // if this is the first message in the thread, just check if there's more than
+ // one message in the thread.
+ // if (inMsgKey == mThread->m_threadRootKey)
+ // return (numChildren > 1) ? 1 : -1;
+
+ for (uint32_t curChildIndex = 0; curChildIndex < numChildren; curChildIndex++)
+ {
+ nsresult rv = mThread->GetChildHdrAt(curChildIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr)
+ {
+ nsMsgKey parentKey;
+
+ curHdr->GetThreadParent(&parentKey);
+ if (parentKey == inMsgKey)
+ {
+ firstChildIndex = curChildIndex;
+ break;
+ }
+ }
+ }
+#ifdef DEBUG_bienvenu1
+ printf("first child index of %ld = %ld\n", inMsgKey, firstChildIndex);
+#endif
+ return firstChildIndex;
+}
+
+NS_IMETHODIMP nsMsgThreadEnumerator::GetNext(nsISupports **aItem)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+ nsresult rv;
+
+ if (mNeedToPrefetch)
+ {
+ rv = Prefetch();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mResultHdr)
+ {
+ *aItem = mResultHdr;
+ NS_ADDREF(*aItem);
+ mNeedToPrefetch = true;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgThreadEnumerator::Prefetch()
+{
+ nsresult rv=NS_OK; // XXX or should this default to an error?
+ mResultHdr = nullptr;
+ if (mThreadParentKey == nsMsgKey_None)
+ {
+ rv = mThread->GetRootHdr(&mChildIndex, getter_AddRefs(mResultHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && mResultHdr, "better be able to get root hdr");
+ mChildIndex = 0; // since root can be anywhere, set mChildIndex to 0.
+ }
+ else if (!mDone)
+ {
+ uint32_t numChildren;
+ mThread->GetNumChildren(&numChildren);
+
+ while (mChildIndex < (int32_t) numChildren)
+ {
+ rv = mThread->GetChildHdrAt(mChildIndex++, getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ {
+ nsMsgKey parentKey;
+ nsMsgKey curKey;
+
+ if (mFilter && NS_FAILED(mFilter(mResultHdr, mClosure))) {
+ mResultHdr = nullptr;
+ continue;
+ }
+
+ mResultHdr->GetThreadParent(&parentKey);
+ mResultHdr->GetMessageKey(&curKey);
+ // if the parent is the same as the msg we're enumerating over,
+ // or the parentKey isn't set, and we're iterating over the top
+ // level message in the thread, then leave mResultHdr set to cur msg.
+ if (parentKey == mThreadParentKey ||
+ (parentKey == nsMsgKey_None
+ && mThreadParentKey == mFirstMsgKey && curKey != mThreadParentKey))
+ break;
+ mResultHdr = nullptr;
+ }
+ else
+ NS_ASSERTION(false, "better be able to get child");
+ }
+ if (!mResultHdr && mThreadParentKey == mFirstMsgKey && !mFoundChildren && numChildren > 1)
+ mThread->ReparentMsgsWithInvalidParent(numChildren, mThreadParentKey);
+ }
+ if (!mResultHdr)
+ {
+ mDone = true;
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv))
+ {
+ mDone = true;
+ return rv;
+ }
+ else
+ mNeedToPrefetch = false;
+ mFoundChildren = true;
+
+#ifdef DEBUG_bienvenu1
+ nsMsgKey debugMsgKey;
+ mResultHdr->GetMessageKey(&debugMsgKey);
+ printf("next for %ld = %ld\n", mThreadParentKey, debugMsgKey);
+#endif
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThreadEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (mNeedToPrefetch)
+ Prefetch();
+ *aResult = !mDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgThread::EnumerateMessages(nsMsgKey parentKey, nsISimpleEnumerator* *result)
+{
+ nsMsgThreadEnumerator* e = new nsMsgThreadEnumerator(this, parentKey, nullptr, nullptr);
+ NS_ENSURE_TRUE(e, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*result = e);
+ return NS_OK;
+}
+
+nsresult nsMsgThread::ReparentMsgsWithInvalidParent(uint32_t numChildren, nsMsgKey threadParentKey)
+{
+ nsresult rv = NS_OK;
+ // run through looking for messages that don't have a correct parent,
+ // i.e., a parent that's in the thread!
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> curChild;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(curChild));
+ if (NS_SUCCEEDED(rv) && curChild)
+ {
+ nsMsgKey parentKey;
+ nsCOMPtr <nsIMsgDBHdr> parent;
+
+ curChild->GetThreadParent(&parentKey);
+
+ if (parentKey != nsMsgKey_None)
+ {
+ GetChild(parentKey, getter_AddRefs(parent));
+ if (!parent)
+ curChild->SetThreadParent(threadParentKey);
+ else
+ {
+ nsMsgKey childKey;
+ curChild->GetMessageKey(&childKey);
+ // can't be your own parent; set parent to thread parent,
+ // or make ourselves the root if we are the root.
+ if (childKey == parentKey)
+ curChild->SetThreadParent(m_threadRootKey == childKey ?
+ nsMsgKey_None : m_threadRootKey);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::GetRootHdr(int32_t *resultIndex, nsIMsgDBHdr **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = nullptr;
+ nsresult rv = NS_OK;
+
+ if (m_threadRootKey != nsMsgKey_None)
+ {
+ rv = GetChildHdrForKey(m_threadRootKey, result, resultIndex);
+ if (NS_SUCCEEDED(rv) && *result)
+ {
+ // check that we're really the root key.
+ nsMsgKey parentKey;
+ (*result)->GetThreadParent(&parentKey);
+ if (parentKey == nsMsgKey_None)
+ return rv;
+ NS_RELEASE(*result);
+ }
+#ifdef DEBUG_David_Bienvenu
+ printf("need to reset thread root key\n");
+#endif
+ uint32_t numChildren;
+ nsMsgKey threadParentKey = nsMsgKey_None;
+ GetNumChildren(&numChildren);
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> curChild;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(curChild));
+ if (NS_SUCCEEDED(rv) && curChild)
+ {
+ nsMsgKey parentKey;
+
+ curChild->GetThreadParent(&parentKey);
+ if (parentKey == nsMsgKey_None)
+ {
+ curChild->GetMessageKey(&threadParentKey);
+ if (*result)
+ {
+ NS_WARNING("two top level msgs, not good");
+ continue;
+ }
+ SetThreadRootKey(threadParentKey);
+ if (resultIndex)
+ *resultIndex = childIndex;
+ NS_ADDREF(*result = curChild);
+ ReparentMsgsWithInvalidParent(numChildren, threadParentKey);
+ // return NS_OK;
+ }
+ }
+ }
+ }
+ if (!*result)
+ {
+ // if we can't get the thread root key, we'll just get the first hdr.
+ // there's a bug where sometimes we weren't resetting the thread root key
+ // when removing the thread root key.
+ if (resultIndex)
+ *resultIndex = 0;
+ rv = GetChildHdrAt(0, result);
+ }
+ if (!*result)
+ return rv;
+ // Check that the thread id of the message is this thread.
+ nsMsgKey threadId = nsMsgKey_None;
+ (void)(*result)->GetThreadId(&threadId);
+ if (threadId != m_threadKey)
+ (*result)->SetThreadId(m_threadKey);
+ return rv;
+}
+
+nsresult nsMsgThread::ChangeChildCount(int32_t delta)
+{
+ nsresult rv;
+
+ uint32_t childCount = 0;
+ m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadChildrenColumnToken, childCount);
+
+ NS_WARNING_ASSERTION(childCount != 0 || delta > 0, "child count gone negative");
+ childCount += delta;
+
+ NS_WARNING_ASSERTION((int32_t) childCount >= 0, "child count gone to 0 or below");
+ if ((int32_t) childCount < 0) // force child count to >= 0
+ childCount = 0;
+
+ rv = m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadChildrenColumnToken, childCount);
+ m_numChildren = childCount;
+ return rv;
+}
+
+nsresult nsMsgThread::ChangeUnreadChildCount(int32_t delta)
+{
+ nsresult rv;
+
+ uint32_t childCount = 0;
+ m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadUnreadChildrenColumnToken, childCount);
+ childCount += delta;
+ if ((int32_t) childCount < 0)
+ {
+#ifdef DEBUG_bienvenu1
+ NS_ASSERTION(false, "negative unread child count");
+#endif
+ childCount = 0;
+ }
+ rv = m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadUnreadChildrenColumnToken, childCount);
+ m_numUnreadChildren = childCount;
+ return rv;
+}
+
+nsresult nsMsgThread::SetThreadRootKey(nsMsgKey threadRootKey)
+{
+ m_threadRootKey = threadRootKey;
+ return m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadRootKeyColumnToken, threadRootKey);
+}
+
+nsresult nsMsgThread::GetChildHdrForKey(nsMsgKey desiredKey, nsIMsgDBHdr **result, int32_t *resultIndex)
+{
+ uint32_t numChildren;
+ uint32_t childIndex = 0;
+ nsresult rv = NS_OK; // XXX or should this default to an error?
+
+ NS_ENSURE_ARG_POINTER(result);
+
+ GetNumChildren(&numChildren);
+
+ if ((int32_t) numChildren < 0)
+ numChildren = 0;
+
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = GetChildHdrAt(childIndex, result);
+ if (NS_SUCCEEDED(rv) && *result)
+ {
+ nsMsgKey msgKey;
+ // we're only doing one level of threading, so check if caller is
+ // asking for children of the first message in the thread or not.
+ // if not, we will tell him there are no children.
+ (*result)->GetMessageKey(&msgKey);
+
+ if (msgKey == desiredKey)
+ {
+ nsMsgKey threadKey;
+ (*result)->GetThreadId(&threadKey);
+ if (threadKey != m_threadKey) // this msg isn't in this thread
+ {
+ NS_WARNING("msg in wrong thread - this shouldn't happen");
+ uint32_t msgSize;
+ (*result)->GetMessageSize(&msgSize);
+ if (msgSize == 0) // this is a phantom message - let's get rid of it.
+ {
+ RemoveChild(msgKey);
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ else
+ {
+ // otherwise, let's try to figure out which thread
+ // this message really belongs to.
+ nsCOMPtr<nsIMsgThread> threadKeyThread =
+ dont_AddRef(m_mdbDB->GetThreadForThreadId(threadKey));
+ if (threadKeyThread)
+ {
+ nsCOMPtr<nsIMsgDBHdr> otherThreadHdr;
+ threadKeyThread->GetChild(msgKey, getter_AddRefs(otherThreadHdr));
+ if (otherThreadHdr)
+ {
+ // Message is in one thread but has a different thread id.
+ // Remove it from the thread and then rethread it.
+ RemoveChild(msgKey);
+ threadKeyThread->RemoveChildHdr(otherThreadHdr, nullptr);
+ bool newThread;
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(otherThreadHdr.get());
+ m_mdbDB->ThreadNewHdr(msgHdr, newThread);
+ }
+ else
+ {
+ (*result)->SetThreadId(m_threadKey);
+ }
+ }
+ }
+ }
+ break;
+ }
+ NS_RELEASE(*result);
+ }
+ }
+ if (resultIndex)
+ *resultIndex = (int32_t) childIndex;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::GetFirstUnreadChild(nsIMsgDBHdr **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ uint32_t numChildren;
+ nsresult rv = NS_OK;
+ uint8_t minLevel = 0xff;
+
+ GetNumChildren(&numChildren);
+
+ if ((int32_t) numChildren < 0)
+ numChildren = 0;
+
+ nsCOMPtr <nsIMsgDBHdr> retHdr;
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ nsMsgKey msgKey;
+ child->GetMessageKey(&msgKey);
+
+ bool isRead;
+ rv = m_mdbDB->IsRead(msgKey, &isRead);
+ if (NS_SUCCEEDED(rv) && !isRead)
+ {
+ // this is the root, so it's the best we're going to do.
+ if (msgKey == m_threadRootKey)
+ {
+ retHdr = child;
+ break;
+ }
+ uint8_t level = 0;
+ nsMsgKey parentId;
+ child->GetThreadParent(&parentId);
+ nsCOMPtr <nsIMsgDBHdr> parent;
+ // count number of ancestors - that's our level
+ while (parentId != nsMsgKey_None)
+ {
+ rv = m_mdbDB->GetMsgHdrForKey(parentId, getter_AddRefs(parent));
+ if (parent)
+ {
+ parent->GetThreadParent(&parentId);
+ level++;
+ }
+ }
+ if (level < minLevel)
+ {
+ minLevel = level;
+ retHdr = child;
+ }
+ }
+ }
+ }
+
+ NS_IF_ADDREF(*result = retHdr);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::GetNewestMsgDate(uint32_t *aResult)
+{
+ // if this hasn't been set, figure it out by enumerating the msgs in the thread.
+ if (!m_newestMsgDate)
+ {
+ uint32_t numChildren;
+ nsresult rv;
+
+ GetNumChildren(&numChildren);
+
+ if ((int32_t) numChildren < 0)
+ numChildren = 0;
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t msgDate;
+ child->GetDateInSeconds(&msgDate);
+ if (msgDate > m_newestMsgDate)
+ m_newestMsgDate = msgDate;
+ }
+ }
+
+ }
+ *aResult = m_newestMsgDate;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgThread::SetNewestMsgDate(uint32_t aNewestMsgDate)
+{
+ m_newestMsgDate = aNewestMsgDate;
+ return m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadNewestMsgDateColumnToken, aNewestMsgDate);
+}
diff --git a/mailnews/db/msgdb/src/nsNewsDatabase.cpp b/mailnews/db/msgdb/src/nsNewsDatabase.cpp
new file mode 100644
index 000000000..3fb3dbb2b
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsNewsDatabase.cpp
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsIMsgDBView.h"
+#include "nsIMsgThread.h"
+#include "nsNewsDatabase.h"
+#include "nsMsgKeySet.h"
+#include "nsCOMPtr.h"
+#include "prlog.h"
+
+#if defined(DEBUG_sspitzer_) || defined(DEBUG_seth_)
+#define DEBUG_NEWS_DATABASE 1
+#endif
+
+nsNewsDatabase::nsNewsDatabase()
+{
+ m_readSet = nullptr;
+}
+
+nsNewsDatabase::~nsNewsDatabase()
+{
+}
+
+NS_IMPL_ADDREF_INHERITED(nsNewsDatabase, nsMsgDatabase)
+NS_IMPL_RELEASE_INHERITED(nsNewsDatabase, nsMsgDatabase)
+
+NS_IMETHODIMP nsNewsDatabase::QueryInterface(REFNSIID aIID, void** aInstancePtr)
+{
+ if (!aInstancePtr) return NS_ERROR_NULL_POINTER;
+ *aInstancePtr = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsINewsDatabase)))
+ {
+ *aInstancePtr = static_cast<nsINewsDatabase *>(this);
+ }
+
+ if(*aInstancePtr)
+ {
+ AddRef();
+ return NS_OK;
+ }
+
+ return nsMsgDatabase::QueryInterface(aIID, aInstancePtr);
+}
+
+nsresult nsNewsDatabase::Close(bool forceCommit)
+{
+ return nsMsgDatabase::Close(forceCommit);
+}
+
+nsresult nsNewsDatabase::ForceClosed()
+{
+ return nsMsgDatabase::ForceClosed();
+}
+
+nsresult nsNewsDatabase::Commit(nsMsgDBCommit commitType)
+{
+ if (m_dbFolderInfo && m_readSet)
+ {
+ // let's write out our idea of the read set so we can compare it with that of
+ // the .rc file next time we start up.
+ nsCString readSet;
+ m_readSet->Output(getter_Copies(readSet));
+ m_dbFolderInfo->SetCharProperty("readSet", readSet);
+ }
+ return nsMsgDatabase::Commit(commitType);
+}
+
+
+uint32_t nsNewsDatabase::GetCurVersion()
+{
+ return kMsgDBVersion;
+}
+
+NS_IMETHODIMP nsNewsDatabase::IsRead(nsMsgKey key, bool *pRead)
+{
+ NS_ASSERTION(pRead, "null out param in IsRead");
+ if (!pRead) return NS_ERROR_NULL_POINTER;
+
+ if (!m_readSet) return NS_ERROR_FAILURE;
+
+ *pRead = m_readSet->IsMember(key);
+ return NS_OK;
+}
+
+nsresult nsNewsDatabase::IsHeaderRead(nsIMsgDBHdr *msgHdr, bool *pRead)
+{
+ nsresult rv;
+ nsMsgKey messageKey;
+
+ if (!msgHdr || !pRead) return NS_ERROR_NULL_POINTER;
+
+ rv = msgHdr->GetMessageKey(&messageKey);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = IsRead(messageKey,pRead);
+ return rv;
+}
+
+// return highest article number we've seen.
+NS_IMETHODIMP nsNewsDatabase::GetHighWaterArticleNum(nsMsgKey *key)
+{
+ NS_ASSERTION(m_dbFolderInfo, "null db folder info");
+ if (!m_dbFolderInfo)
+ return NS_ERROR_FAILURE;
+ return m_dbFolderInfo->GetHighWater(key);
+}
+
+// return the key of the first article number we know about.
+// Since the iterator iterates in id order, we can just grab the
+// messagekey of the first header it returns.
+// ### dmb
+// This will not deal with the situation where we get holes in
+// the headers we know about. Need to figure out how and when
+// to solve that. This could happen if a transfer is interrupted.
+// Do we need to keep track of known arts permanently?
+NS_IMETHODIMP nsNewsDatabase::GetLowWaterArticleNum(nsMsgKey *key)
+{
+ nsresult rv;
+ nsMsgHdr *pHeader;
+
+ nsCOMPtr<nsISimpleEnumerator> hdrs;
+ rv = EnumerateMessages(getter_AddRefs(hdrs));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = hdrs->GetNext((nsISupports**)&pHeader);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ if (NS_FAILED(rv))
+ return rv;
+
+ return pHeader->GetMessageKey(key);
+}
+
+nsresult nsNewsDatabase::ExpireUpTo(nsMsgKey expireKey)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+nsresult nsNewsDatabase::ExpireRange(nsMsgKey startRange, nsMsgKey endRange)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP nsNewsDatabase::GetReadSet(nsMsgKeySet **pSet)
+{
+ if (!pSet) return NS_ERROR_NULL_POINTER;
+ *pSet = m_readSet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNewsDatabase::SetReadSet(nsMsgKeySet *pSet)
+{
+ m_readSet = pSet;
+
+ if (m_readSet)
+ {
+ // compare this read set with the one in the db folder info.
+ // If not equivalent, sync with this one.
+ nsCString dbReadSet;
+ if (m_dbFolderInfo)
+ m_dbFolderInfo->GetCharProperty("readSet", dbReadSet);
+ nsCString newsrcReadSet;
+ m_readSet->Output(getter_Copies(newsrcReadSet));
+ if (!dbReadSet.Equals(newsrcReadSet))
+ SyncWithReadSet();
+ }
+ return NS_OK;
+}
+
+
+bool nsNewsDatabase::SetHdrReadFlag(nsIMsgDBHdr *msgHdr, bool bRead)
+{
+ nsresult rv;
+ bool isRead;
+ rv = IsHeaderRead(msgHdr, &isRead);
+
+ if (isRead == bRead)
+ {
+ // give the base class a chance to update m_flags.
+ nsMsgDatabase::SetHdrReadFlag(msgHdr, bRead);
+ return false;
+ }
+ else {
+ nsMsgKey messageKey;
+
+ // give the base class a chance to update m_flags.
+ nsMsgDatabase::SetHdrReadFlag(msgHdr, bRead);
+ rv = msgHdr->GetMessageKey(&messageKey);
+ if (NS_FAILED(rv)) return false;
+
+ NS_ASSERTION(m_readSet, "m_readSet is null");
+ if (!m_readSet) return false;
+
+ if (!bRead) {
+#ifdef DEBUG_NEWS_DATABASE
+ printf("remove %d from the set\n",messageKey);
+#endif
+
+ m_readSet->Remove(messageKey);
+
+ rv = NotifyReadChanged(nullptr);
+ if (NS_FAILED(rv)) return false;
+ }
+ else {
+#ifdef DEBUG_NEWS_DATABASE
+ printf("add %d to the set\n",messageKey);
+#endif
+
+ if (m_readSet->Add(messageKey) < 0) return false;
+
+ rv = NotifyReadChanged(nullptr);
+ if (NS_FAILED(rv)) return false;
+ }
+ }
+ return true;
+}
+
+NS_IMETHODIMP nsNewsDatabase::MarkAllRead(uint32_t *aNumMarked,
+ nsMsgKey **aThoseMarked)
+{
+ nsMsgKey lowWater = nsMsgKey_None, highWater;
+ nsCString knownArts;
+ if (m_dbFolderInfo)
+ {
+ m_dbFolderInfo->GetKnownArtsSet(getter_Copies(knownArts));
+ nsMsgKeySet *knownKeys = nsMsgKeySet::Create(knownArts.get());
+ if (knownKeys)
+ lowWater = knownKeys->GetFirstMember();
+
+ delete knownKeys;
+ }
+ if (lowWater == nsMsgKey_None)
+ GetLowWaterArticleNum(&lowWater);
+ GetHighWaterArticleNum(&highWater);
+ if (lowWater > 2)
+ m_readSet->AddRange(1, lowWater - 1);
+ nsresult err = nsMsgDatabase::MarkAllRead(aNumMarked, aThoseMarked);
+ if (NS_SUCCEEDED(err) && 1 <= highWater)
+ m_readSet->AddRange(1, highWater); // mark everything read in newsrc.
+
+ return err;
+}
+
+nsresult nsNewsDatabase::SyncWithReadSet()
+{
+
+ // The code below attempts to update the underlying nsMsgDatabase's idea
+ // of read/unread flags to match the read set in the .newsrc file. It should
+ // only be called when they don't match, e.g., we crashed after committing the
+ // db but before writing out the .newsrc
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ nsresult rv = EnumerateMessages(getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false, readInNewsrc, isReadInDB, changed = false;
+ int32_t numMessages = 0, numUnreadMessages = 0;
+ nsMsgKey messageKey;
+ nsCOMPtr <nsIMsgThread> threadHdr;
+
+ // Scan all messages in DB
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ rv = hdrs->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgDBHdr> header = do_QueryInterface(supports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsMsgDatabase::IsHeaderRead(header, &isReadInDB);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ header->GetMessageKey(&messageKey);
+ IsRead(messageKey,&readInNewsrc);
+
+ numMessages++;
+ if (!readInNewsrc)
+ numUnreadMessages++;
+
+ // If DB and readSet disagree on Read/Unread, fix DB
+ if (readInNewsrc!=isReadInDB)
+ {
+ MarkHdrRead(header, readInNewsrc, nullptr);
+ changed = true;
+ }
+ }
+
+ // Update FolderInfo Counters
+ int32_t oldMessages, oldUnreadMessages;
+ rv = m_dbFolderInfo->GetNumMessages(&oldMessages);
+ if (NS_SUCCEEDED(rv) && oldMessages!=numMessages)
+ {
+ changed = true;
+ m_dbFolderInfo->ChangeNumMessages(numMessages-oldMessages);
+ }
+ rv = m_dbFolderInfo->GetNumUnreadMessages(&oldUnreadMessages);
+ if (NS_SUCCEEDED(rv) && oldUnreadMessages!=numUnreadMessages)
+ {
+ changed = true;
+ m_dbFolderInfo->ChangeNumUnreadMessages(numUnreadMessages-oldUnreadMessages);
+ }
+
+ if (changed)
+ Commit(nsMsgDBCommitType::kLargeCommit);
+
+ return rv;
+}
+
+nsresult nsNewsDatabase::AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr)
+{
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::Offline && m_dbFolderInfo)
+ {
+ uint32_t size = 0;
+ (void)msgHdr->GetOfflineMessageSize(&size);
+ return m_dbFolderInfo->ChangeExpungedBytes (size);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNewsDatabase::GetDefaultViewFlags(nsMsgViewFlagsTypeValue *aDefaultViewFlags)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultViewFlags);
+ GetIntPref("mailnews.default_news_view_flags", aDefaultViewFlags);
+ if (*aDefaultViewFlags < nsMsgViewFlagsType::kNone ||
+ *aDefaultViewFlags > (nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kShowIgnored |
+ nsMsgViewFlagsType::kUnreadOnly |
+ nsMsgViewFlagsType::kExpandAll |
+ nsMsgViewFlagsType::kGroupBySort))
+ *aDefaultViewFlags = nsMsgViewFlagsType::kThreadedDisplay;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNewsDatabase::GetDefaultSortType(nsMsgViewSortTypeValue *aDefaultSortType)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultSortType);
+ GetIntPref("mailnews.default_news_sort_type", aDefaultSortType);
+ if (*aDefaultSortType < nsMsgViewSortType::byDate ||
+ *aDefaultSortType > nsMsgViewSortType::byAccount)
+ *aDefaultSortType = nsMsgViewSortType::byThread;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNewsDatabase::GetDefaultSortOrder(nsMsgViewSortOrderValue *aDefaultSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultSortOrder);
+ GetIntPref("mailnews.default_news_sort_order", aDefaultSortOrder);
+ if (*aDefaultSortOrder != nsMsgViewSortOrder::descending)
+ *aDefaultSortOrder = nsMsgViewSortOrder::ascending;
+ return NS_OK;
+}
diff --git a/mailnews/extensions/bayesian-spam-filter/moz.build b/mailnews/extensions/bayesian-spam-filter/moz.build
new file mode 100644
index 000000000..8755860cb
--- /dev/null
+++ b/mailnews/extensions/bayesian-spam-filter/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['src']
diff --git a/mailnews/extensions/bayesian-spam-filter/src/moz.build b/mailnews/extensions/bayesian-spam-filter/src/moz.build
new file mode 100644
index 000000000..5819766a3
--- /dev/null
+++ b/mailnews/extensions/bayesian-spam-filter/src/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsBayesianFilter.cpp',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.cpp b/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.cpp
new file mode 100644
index 000000000..d0336db1d
--- /dev/null
+++ b/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.cpp
@@ -0,0 +1,2758 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBayesianFilter.h"
+#include "nsIInputStream.h"
+#include "nsIStreamListener.h"
+#include "nsNetUtil.h"
+#include "nsQuickSort.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h" // for GetMessageServiceFromURI
+#include "prnetdb.h"
+#include "nsIMsgWindow.h"
+#include "mozilla/Logging.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsUnicharUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsNetCID.h"
+#include "nsIMimeHeaders.h"
+#include "nsMsgMimeCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIStringEnumerator.h"
+#include "nsIObserverService.h"
+#include "nsIChannel.h"
+
+using namespace mozilla;
+
+// needed to mark attachment flag on the db hdr
+#include "nsIMsgHdr.h"
+
+// needed to strip html out of the body
+#include "nsIContentSerializer.h"
+#include "nsLayoutCID.h"
+#include "nsIParserUtils.h"
+#include "nsIDocumentEncoder.h"
+
+#include "nsIncompleteGamma.h"
+#include <math.h>
+#include <prmem.h>
+#include "nsIMsgTraitService.h"
+#include "mozilla/Services.h"
+#include "mozilla/Attributes.h"
+#include <cstdlib> // for std::abs(int/long)
+#include <cmath> // for std::abs(float/double)
+
+static PRLogModuleInfo *BayesianFilterLogModule = nullptr;
+
+#define kDefaultJunkThreshold .99 // we override this value via a pref
+static const char* kBayesianFilterTokenDelimiters = " \t\n\r\f.";
+static unsigned int kMinLengthForToken = 3; // lower bound on the number of characters in a word before we treat it as a token
+static unsigned int kMaxLengthForToken = 12; // upper bound on the number of characters in a word to be declared as a token
+
+#define FORGED_RECEIVED_HEADER_HINT NS_LITERAL_CSTRING("may be forged")
+
+#ifndef M_LN2
+#define M_LN2 0.69314718055994530942
+#endif
+
+#ifndef M_E
+#define M_E 2.7182818284590452354
+#endif
+
+// provide base implementation of hash lookup of a string
+struct BaseToken : public PLDHashEntryHdr
+{
+ const char* mWord;
+};
+
+// token for a particular message
+// mCount, mAnalysisLink are initialized to zero by the hash code
+struct Token : public BaseToken {
+ uint32_t mCount;
+ uint32_t mAnalysisLink; // index in mAnalysisStore of the AnalysisPerToken
+ // object for the first trait for this token
+};
+
+// token stored in a training file for a group of messages
+// mTraitLink is initialized to 0 by the hash code
+struct CorpusToken : public BaseToken
+{
+ uint32_t mTraitLink; // index in mTraitStore of the TraitPerToken
+ // object for the first trait for this token
+};
+
+// set the value of a TraitPerToken object
+TraitPerToken::TraitPerToken(uint32_t aTraitId, uint32_t aCount)
+ : mId(aTraitId), mCount(aCount), mNextLink(0)
+{
+}
+
+// shorthand representations of trait ids for junk and good
+static const uint32_t kJunkTrait = nsIJunkMailPlugin::JUNK_TRAIT;
+static const uint32_t kGoodTrait = nsIJunkMailPlugin::GOOD_TRAIT;
+
+// set the value of an AnalysisPerToken object
+AnalysisPerToken::AnalysisPerToken(
+ uint32_t aTraitIndex, double aDistance, double aProbability) :
+ mTraitIndex(aTraitIndex),
+ mDistance(aDistance),
+ mProbability(aProbability),
+ mNextLink(0)
+{
+}
+
+// the initial size of the AnalysisPerToken linked list storage
+const uint32_t kAnalysisStoreCapacity = 2048;
+
+// the initial size of the TraitPerToken linked list storage
+const uint32_t kTraitStoreCapacity = 16384;
+
+// Size of Auto arrays representing per trait information
+const uint32_t kTraitAutoCapacity = 10;
+
+TokenEnumeration::TokenEnumeration(PLDHashTable* table)
+ : mIterator(table->Iter())
+{
+}
+
+inline bool TokenEnumeration::hasMoreTokens()
+{
+ return !mIterator.Done();
+}
+
+inline BaseToken* TokenEnumeration::nextToken()
+{
+ auto token = static_cast<BaseToken*>(mIterator.Get());
+ mIterator.Next();
+ return token;
+}
+
+// member variables
+static const PLDHashTableOps gTokenTableOps = {
+ PLDHashTable::HashStringKey,
+ PLDHashTable::MatchStringKey,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr
+};
+
+TokenHash::TokenHash(uint32_t aEntrySize)
+ : mTokenTable(&gTokenTableOps, aEntrySize, 128)
+{
+ mEntrySize = aEntrySize;
+ PL_INIT_ARENA_POOL(&mWordPool, "Words Arena", 16384);
+}
+
+TokenHash::~TokenHash()
+{
+ PL_FinishArenaPool(&mWordPool);
+}
+
+nsresult TokenHash::clearTokens()
+{
+ // we re-use the tokenizer when classifying multiple messages,
+ // so this gets called after every message classification.
+ mTokenTable.ClearAndPrepareForLength(128);
+ PL_FreeArenaPool(&mWordPool);
+ return NS_OK;
+}
+
+char* TokenHash::copyWord(const char* word, uint32_t len)
+{
+ void* result;
+ uint32_t size = 1 + len;
+ PL_ARENA_ALLOCATE(result, &mWordPool, size);
+ if (result)
+ memcpy(result, word, size);
+ return reinterpret_cast<char*>(result);
+}
+
+inline BaseToken* TokenHash::get(const char* word)
+{
+ PLDHashEntryHdr* entry = mTokenTable.Search(word);
+ if (entry)
+ return static_cast<BaseToken*>(entry);
+ return NULL;
+}
+
+BaseToken* TokenHash::add(const char* word)
+{
+ if (!word || !*word)
+ {
+ NS_ERROR("Trying to add a null word");
+ return nullptr;
+ }
+
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug, ("add word: %s", word));
+
+ PLDHashEntryHdr* entry = mTokenTable.Add(word, mozilla::fallible);
+ BaseToken* token = static_cast<BaseToken*>(entry);
+ if (token) {
+ if (token->mWord == NULL) {
+ uint32_t len = strlen(word);
+ NS_ASSERTION(len != 0, "adding zero length word to tokenizer");
+ if (!len)
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug, ("adding zero length word to tokenizer"));
+ token->mWord = copyWord(word, len);
+ NS_ASSERTION(token->mWord, "copyWord failed");
+ if (!token->mWord) {
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Error, ("copyWord failed: %s (%d)", word, len));
+ mTokenTable.RawRemove(entry);
+ return NULL;
+ }
+ }
+ }
+ return token;
+}
+
+inline uint32_t TokenHash::countTokens()
+{
+ return mTokenTable.EntryCount();
+}
+
+inline TokenEnumeration TokenHash::getTokens()
+{
+ return TokenEnumeration(&mTokenTable);
+}
+
+Tokenizer::Tokenizer() :
+ TokenHash(sizeof(Token)),
+ mBodyDelimiters(kBayesianFilterTokenDelimiters),
+ mHeaderDelimiters(kBayesianFilterTokenDelimiters),
+ mCustomHeaderTokenization(false),
+ mMaxLengthForToken(kMaxLengthForToken),
+ mIframeToDiv(false)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefs->GetBranch("mailnews.bayesian_spam_filter.", getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS_VOID(rv); // no branch defined, just use defaults
+
+ /*
+ * RSS feeds store their summary as alternate content of an iframe. But due
+ * to bug 365953, this is not seen by the serializer. As a workaround, allow
+ * the tokenizer to replace the iframe with div for tokenization.
+ */
+ rv = prefBranch->GetBoolPref("iframe_to_div", &mIframeToDiv);
+ if (NS_FAILED(rv))
+ mIframeToDiv = false;
+
+ /*
+ * the list of delimiters used to tokenize the message and body
+ * defaults to the value in kBayesianFilterTokenDelimiters, but may be
+ * set with the following preferences for the body and header
+ * separately.
+ *
+ * \t, \n, \v, \f, \r, and \\ will be escaped to their normal
+ * C-library values, all other two-letter combinations beginning with \
+ * will be ignored.
+ */
+
+ prefBranch->GetCharPref("body_delimiters", getter_Copies(mBodyDelimiters));
+ if (!mBodyDelimiters.IsEmpty())
+ UnescapeCString(mBodyDelimiters);
+ else // prefBranch empties the result when it fails :(
+ mBodyDelimiters.Assign(kBayesianFilterTokenDelimiters);
+
+ prefBranch->GetCharPref("header_delimiters", getter_Copies(mHeaderDelimiters));
+ if (!mHeaderDelimiters.IsEmpty())
+ UnescapeCString(mHeaderDelimiters);
+ else
+ mHeaderDelimiters.Assign(kBayesianFilterTokenDelimiters);
+
+ /*
+ * Extensions may wish to enable or disable tokenization of certain headers.
+ * Define any headers to enable/disable in a string preference like this:
+ * "mailnews.bayesian_spam_filter.tokenizeheader.headername"
+ *
+ * where "headername" is the header to tokenize. For example, to tokenize the
+ * header "x-spam-status" use the preference:
+ *
+ * "mailnews.bayesian_spam_filter.tokenizeheader.x-spam-status"
+ *
+ * The value of the string preference will be interpreted in one of
+ * four ways, depending on the value:
+ *
+ * If "false" then do not tokenize that header
+ * If "full" then add the entire header value as a token,
+ * without breaking up into subtokens using delimiters
+ * If "standard" then tokenize the header using as delimiters the current
+ * value of the generic header delimiters
+ * Any other string is interpreted as a list of delimiters to use to parse
+ * the header. \t, \n, \v, \f, \r, and \\ will be escaped to their normal
+ * C-library values, all other two-letter combinations beginning with \
+ * will be ignored.
+ *
+ * Header names in the preference should be all lower case
+ *
+ * Extensions may also set the maximum length of a token (default is
+ * kMaxLengthForToken) by setting the int preference:
+ * "mailnews.bayesian_spam_filter.maxlengthfortoken"
+ */
+
+ char** headers;
+ uint32_t count;
+
+ // get customized maximum token length
+ int32_t maxLengthForToken;
+ rv = prefBranch->GetIntPref("maxlengthfortoken", &maxLengthForToken);
+ mMaxLengthForToken = NS_SUCCEEDED(rv) ? uint32_t(maxLengthForToken) : kMaxLengthForToken;
+
+ rv = prefs->GetBranch("mailnews.bayesian_spam_filter.tokenizeheader.", getter_AddRefs(prefBranch));
+ if (NS_SUCCEEDED(rv))
+ rv = prefBranch->GetChildList("", &count, &headers);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ mCustomHeaderTokenization = true;
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsCString value;
+ prefBranch->GetCharPref(headers[i], getter_Copies(value));
+ if (value.EqualsLiteral("false"))
+ {
+ mDisabledHeaders.AppendElement(headers[i]);
+ continue;
+ }
+ mEnabledHeaders.AppendElement(headers[i]);
+ if (value.EqualsLiteral("standard"))
+ value.SetIsVoid(true); // Void means use default delimiter
+ else if (value.EqualsLiteral("full"))
+ value.Truncate(); // Empty means add full header
+ else
+ UnescapeCString(value);
+ mEnabledHeadersDelimiters.AppendElement(value);
+ }
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, headers);
+ }
+}
+
+Tokenizer::~Tokenizer()
+{
+}
+
+inline Token* Tokenizer::get(const char* word)
+{
+ return static_cast<Token*>(TokenHash::get(word));
+}
+
+Token* Tokenizer::add(const char* word, uint32_t count)
+{
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug, ("add word: %s (count=%d)",
+ word, count));
+
+ Token* token = static_cast<Token*>(TokenHash::add(word));
+ if (token)
+ {
+ token->mCount += count; // hash code initializes this to zero
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug,
+ ("adding word to tokenizer: %s (count=%d) (mCount=%d)",
+ word, count, token->mCount));
+ }
+ return token;
+}
+
+static bool isDecimalNumber(const char* word)
+{
+ const char* p = word;
+ if (*p == '-') ++p;
+ char c;
+ while ((c = *p++)) {
+ if (!isdigit((unsigned char) c))
+ return false;
+ }
+ return true;
+}
+
+static bool isASCII(const char* word)
+{
+ const unsigned char* p = (const unsigned char*)word;
+ unsigned char c;
+ while ((c = *p++)) {
+ if (c > 127)
+ return false;
+ }
+ return true;
+}
+
+inline bool isUpperCase(char c) { return ('A' <= c) && (c <= 'Z'); }
+
+static char* toLowerCase(char* str)
+{
+ char c, *p = str;
+ while ((c = *p++)) {
+ if (isUpperCase(c))
+ p[-1] = c + ('a' - 'A');
+ }
+ return str;
+}
+
+void Tokenizer::addTokenForHeader(const char * aTokenPrefix, nsACString& aValue,
+ bool aTokenizeValue, const char* aDelimiters)
+{
+ if (aValue.Length())
+ {
+ ToLowerCase(aValue);
+ if (!aTokenizeValue)
+ {
+ nsCString tmpStr;
+ tmpStr.Assign(aTokenPrefix);
+ tmpStr.Append(':');
+ tmpStr.Append(aValue);
+
+ add(tmpStr.get());
+ }
+ else
+ {
+ char* word;
+ nsCString str(aValue);
+ char *next = str.BeginWriting();
+ const char* delimiters = !aDelimiters ?
+ mHeaderDelimiters.get() : aDelimiters;
+ while ((word = NS_strtok(delimiters, &next)) != NULL)
+ {
+ if (strlen(word) < kMinLengthForToken)
+ continue;
+ if (isDecimalNumber(word))
+ continue;
+ if (isASCII(word))
+ {
+ nsCString tmpStr;
+ tmpStr.Assign(aTokenPrefix);
+ tmpStr.Append(':');
+ tmpStr.Append(word);
+ add(tmpStr.get());
+ }
+ }
+ }
+ }
+}
+
+void Tokenizer::tokenizeAttachment(const char * aContentType, const char * aFileName)
+{
+ nsAutoCString contentType;
+ nsAutoCString fileName;
+ fileName.Assign(aFileName);
+ contentType.Assign(aContentType);
+
+ // normalize the content type and the file name
+ ToLowerCase(fileName);
+ ToLowerCase(contentType);
+ addTokenForHeader("attachment/filename", fileName);
+
+ addTokenForHeader("attachment/content-type", contentType);
+}
+
+void Tokenizer::tokenizeHeaders(nsIUTF8StringEnumerator * aHeaderNames, nsIUTF8StringEnumerator * aHeaderValues)
+{
+ nsCString headerValue;
+ nsAutoCString headerName; // we'll be normalizing all header names to lower case
+ bool hasMore;
+
+ while (aHeaderNames->HasMore(&hasMore), hasMore)
+ {
+ aHeaderNames->GetNext(headerName);
+ ToLowerCase(headerName);
+ aHeaderValues->GetNext(headerValue);
+
+ bool headerProcessed = false;
+ if (mCustomHeaderTokenization)
+ {
+ // Process any exceptions set from preferences
+ for (uint32_t i = 0; i < mEnabledHeaders.Length(); i++)
+ if (headerName.Equals(mEnabledHeaders[i]))
+ {
+ if (mEnabledHeadersDelimiters[i].IsVoid())
+ // tokenize with standard delimiters for all headers
+ addTokenForHeader(headerName.get(), headerValue, true);
+ else if (mEnabledHeadersDelimiters[i].IsEmpty())
+ // do not break the header into tokens
+ addTokenForHeader(headerName.get(), headerValue);
+ else
+ // use the delimiter in mEnabledHeadersDelimiters
+ addTokenForHeader(headerName.get(), headerValue, true,
+ mEnabledHeadersDelimiters[i].get());
+ headerProcessed = true;
+ break; // we found the header, no need to look for more custom values
+ }
+
+ for (uint32_t i = 0; i < mDisabledHeaders.Length(); i++)
+ {
+ if (headerName.Equals(mDisabledHeaders[i]))
+ {
+ headerProcessed = true;
+ break;
+ }
+ }
+
+ if (headerProcessed)
+ continue;
+ }
+
+ switch (headerName.First())
+ {
+ case 'c':
+ if (headerName.Equals("content-type"))
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ break;
+
+ // extract the charset parameter
+ nsCString parameterValue;
+ mimehdrpar->GetParameterInternal(headerValue.get(), "charset", nullptr, nullptr, getter_Copies(parameterValue));
+ addTokenForHeader("charset", parameterValue);
+
+ // create a token containing just the content type
+ mimehdrpar->GetParameterInternal(headerValue.get(), "type", nullptr, nullptr, getter_Copies(parameterValue));
+ if (!parameterValue.Length())
+ mimehdrpar->GetParameterInternal(headerValue.get(), nullptr /* use first unnamed param */, nullptr, nullptr, getter_Copies(parameterValue));
+ addTokenForHeader("content-type/type", parameterValue);
+
+ // XXX: should we add a token for the entire content-type header as well or just these parts we have extracted?
+ }
+ break;
+ case 'r':
+ if (headerName.Equals("received"))
+ {
+ // look for the string "may be forged" in the received headers. sendmail sometimes adds this hint
+ // This does not compile on linux yet. Need to figure out why. Commenting out for now
+ // if (FindInReadable(FORGED_RECEIVED_HEADER_HINT, headerValue))
+ // addTokenForHeader(headerName.get(), FORGED_RECEIVED_HEADER_HINT);
+ }
+
+ // leave out reply-to
+ break;
+ case 's':
+ if (headerName.Equals("subject"))
+ {
+ // we want to tokenize the subject
+ addTokenForHeader(headerName.get(), headerValue, true);
+ }
+
+ // important: leave out sender field. Too strong of an indicator
+ break;
+ case 'x': // (2) X-Mailer / user-agent works best if it is untokenized, just fold the case and any leading/trailing white space
+ // all headers beginning with x-mozilla are being changed by us, so ignore
+ if (Substring(headerName, 0, 9).Equals("x-mozilla"))
+ break;
+ // fall through
+ MOZ_FALLTHROUGH;
+ case 'u':
+ addTokenForHeader(headerName.get(), headerValue);
+ break;
+ default:
+ addTokenForHeader(headerName.get(), headerValue);
+ break;
+ } // end switch
+
+ }
+}
+
+void Tokenizer::tokenize_ascii_word(char * aWord)
+{
+ // always deal with normalized lower case strings
+ toLowerCase(aWord);
+ uint32_t wordLength = strlen(aWord);
+
+ // if the wordLength is within our accepted token limit, then add it
+ if (wordLength >= kMinLengthForToken && wordLength <= mMaxLengthForToken)
+ add(aWord);
+ else if (wordLength > mMaxLengthForToken)
+ {
+ // don't skip over the word if it looks like an email address,
+ // there is value in adding tokens for addresses
+ nsDependentCString word (aWord, wordLength); // CHEAP, no allocation occurs here...
+
+ // XXX: i think the 40 byte check is just for perf reasons...if the email address is longer than that then forget about it.
+ const char *atSign = strchr(aWord, '@');
+ if (wordLength < 40 && strchr(aWord, '.') && atSign && !strchr(atSign + 1, '@'))
+ {
+ uint32_t numBytesToSep = atSign - aWord;
+ if (numBytesToSep < wordLength - 1) // if the @ sign is the last character, it must not be an email address
+ {
+ // split the john@foo.com into john and foo.com, treat them as separate tokens
+ nsCString emailNameToken;
+ emailNameToken.AssignLiteral("email name:");
+ emailNameToken.Append(Substring(word, 0, numBytesToSep++));
+ add(emailNameToken.get());
+ nsCString emailAddrToken;
+ emailAddrToken.AssignLiteral("email addr:");
+ emailAddrToken.Append(Substring(word, numBytesToSep, wordLength - numBytesToSep));
+ add(emailAddrToken.get());
+ return;
+ }
+ }
+
+ // there is value in generating a token indicating the number
+ // of characters we are skipping. We'll round to the nearest 10
+ nsCString skipToken;
+ skipToken.AssignLiteral("skip:");
+ skipToken.Append(word[0]);
+ skipToken.Append(' ');
+ skipToken.AppendInt((wordLength/10) * 10);
+ add(skipToken.get());
+ }
+}
+
+// one substract and one conditional jump should be faster than two conditional jump on most recent system.
+#define IN_RANGE(x, low, high) ((uint16_t)((x)-(low)) <= (high)-(low))
+
+#define IS_JA_HIRAGANA(x) IN_RANGE(x, 0x3040, 0x309F)
+// swapping the range using xor operation to reduce conditional jump.
+#define IS_JA_KATAKANA(x) (IN_RANGE(x^0x0004, 0x30A0, 0x30FE)||(IN_RANGE(x, 0xFF66, 0xFF9F)))
+#define IS_JA_KANJI(x) (IN_RANGE(x, 0x2E80, 0x2FDF)||IN_RANGE(x, 0x4E00, 0x9FAF))
+#define IS_JA_KUTEN(x) (((x)==0x3001)||((x)==0xFF64)||((x)==0xFF0E))
+#define IS_JA_TOUTEN(x) (((x)==0x3002)||((x)==0xFF61)||((x)==0xFF0C))
+#define IS_JA_SPACE(x) ((x)==0x3000)
+#define IS_JA_FWLATAIN(x) IN_RANGE(x, 0xFF01, 0xFF5E)
+#define IS_JA_FWNUMERAL(x) IN_RANGE(x, 0xFF10, 0xFF19)
+
+#define IS_JAPANESE_SPECIFIC(x) (IN_RANGE(x, 0x3040, 0x30FF)||IN_RANGE(x, 0xFF01, 0xFF9F))
+
+enum char_class{
+ others = 0,
+ space,
+ hiragana,
+ katakana,
+ kanji,
+ kuten,
+ touten,
+ kigou,
+ fwlatain,
+ ascii
+};
+
+static char_class getCharClass(char16_t c)
+{
+ char_class charClass = others;
+
+ if(IS_JA_HIRAGANA(c))
+ charClass = hiragana;
+ else if(IS_JA_KATAKANA(c))
+ charClass = katakana;
+ else if(IS_JA_KANJI(c))
+ charClass = kanji;
+ else if(IS_JA_KUTEN(c))
+ charClass = kuten;
+ else if(IS_JA_TOUTEN(c))
+ charClass = touten;
+ else if(IS_JA_FWLATAIN(c))
+ charClass = fwlatain;
+
+ return charClass;
+}
+
+static bool isJapanese(const char* word)
+{
+ nsString text = NS_ConvertUTF8toUTF16(word);
+ char16_t* p = (char16_t*)text.get();
+ char16_t c;
+
+ // it is japanese chunk if it contains any hiragana or katakana.
+ while((c = *p++))
+ if( IS_JAPANESE_SPECIFIC(c))
+ return true;
+
+ return false;
+}
+
+static bool isFWNumeral(const char16_t* p1, const char16_t* p2)
+{
+ for(;p1<p2;p1++)
+ if(!IS_JA_FWNUMERAL(*p1))
+ return false;
+
+ return true;
+}
+
+// The japanese tokenizer was added as part of Bug #277354
+void Tokenizer::tokenize_japanese_word(char* chunk)
+{
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug, ("entering tokenize_japanese_word(%s)", chunk));
+
+ nsString srcStr = NS_ConvertUTF8toUTF16(chunk);
+ const char16_t* p1 = srcStr.get();
+ const char16_t* p2 = p1;
+ if(!*p2) return;
+
+ char_class cc = getCharClass(*p2);
+ while(*(++p2))
+ {
+ if(cc == getCharClass(*p2))
+ continue;
+
+ nsCString token = NS_ConvertUTF16toUTF8(p1, p2-p1);
+ if( (!isDecimalNumber(token.get())) && (!isFWNumeral(p1, p2)))
+ {
+ nsCString tmpStr;
+ tmpStr.AppendLiteral("JA:");
+ tmpStr.Append(token);
+ add(tmpStr.get());
+ }
+
+ cc = getCharClass(*p2);
+ p1 = p2;
+ }
+}
+
+nsresult Tokenizer::stripHTML(const nsAString& inString, nsAString& outString)
+{
+ uint32_t flags = nsIDocumentEncoder::OutputLFLineBreak
+ | nsIDocumentEncoder::OutputNoScriptContent
+ | nsIDocumentEncoder::OutputNoFramesContent
+ | nsIDocumentEncoder::OutputBodyOnly;
+ nsCOMPtr<nsIParserUtils> utils =
+ do_GetService(NS_PARSERUTILS_CONTRACTID);
+ return utils->ConvertToPlainText(inString, flags, 80, outString);
+}
+
+void Tokenizer::tokenize(const char* aText)
+{
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug, ("tokenize: %s", aText));
+
+ // strip out HTML tags before we begin processing
+ // uggh but first we have to blow up our string into UCS2
+ // since that's what the document encoder wants. UTF8/UCS2, I wish we all
+ // spoke the same language here..
+ nsString text = NS_ConvertUTF8toUTF16(aText);
+ nsString strippedUCS2;
+
+ // RSS feeds store their summary information as an iframe. But due to
+ // bug 365953, we can't see those in the plaintext serializer. As a
+ // workaround, allow an option to replace iframe with div in the message
+ // text. We disable by default, since most people won't be applying bayes
+ // to RSS
+
+ if (mIframeToDiv)
+ {
+ MsgReplaceSubstring(text, NS_LITERAL_STRING("<iframe"),
+ NS_LITERAL_STRING("<div"));
+ MsgReplaceSubstring(text, NS_LITERAL_STRING("/iframe>"),
+ NS_LITERAL_STRING("/div>"));
+ }
+
+ stripHTML(text, strippedUCS2);
+
+ // convert 0x3000(full width space) into 0x0020
+ char16_t * substr_start = strippedUCS2.BeginWriting();
+ char16_t * substr_end = strippedUCS2.EndWriting();
+ while (substr_start != substr_end) {
+ if (*substr_start == 0x3000)
+ *substr_start = 0x0020;
+ ++substr_start;
+ }
+
+ nsCString strippedStr = NS_ConvertUTF16toUTF8(strippedUCS2);
+ char * strippedText = strippedStr.BeginWriting();
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug, ("tokenize stripped html: %s", strippedText));
+
+ char* word;
+ char* next = strippedText;
+ while ((word = NS_strtok(mBodyDelimiters.get(), &next)) != NULL) {
+ if (!*word) continue;
+ if (isDecimalNumber(word)) continue;
+ if (isASCII(word))
+ tokenize_ascii_word(word);
+ else if (isJapanese(word))
+ tokenize_japanese_word(word);
+ else {
+ nsresult rv;
+ // use I18N scanner to break this word into meaningful semantic units.
+ if (!mScanner) {
+ mScanner = do_CreateInstance(NS_SEMANTICUNITSCANNER_CONTRACTID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "couldn't create semantic unit scanner!");
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+ if (mScanner) {
+ mScanner->Start("UTF-8");
+ // convert this word from UTF-8 into UCS2.
+ NS_ConvertUTF8toUTF16 uword(word);
+ ToLowerCase(uword);
+ const char16_t* utext = uword.get();
+ int32_t len = uword.Length(), pos = 0, begin, end;
+ bool gotUnit;
+ while (pos < len) {
+ rv = mScanner->Next(utext, len, pos, true, &begin, &end, &gotUnit);
+ if (NS_SUCCEEDED(rv) && gotUnit) {
+ NS_ConvertUTF16toUTF8 utfUnit(utext + begin, end - begin);
+ add(utfUnit.get());
+ // advance to end of current unit.
+ pos = end;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+// helper function to escape \n, \t, etc from a CString
+void Tokenizer::UnescapeCString(nsCString& aCString)
+{
+ nsAutoCString result;
+
+ const char* readEnd = aCString.EndReading();
+ char* writeStart = result.BeginWriting();
+ char* writeIter = writeStart;
+
+ bool inEscape = false;
+ for (const char* readIter = aCString.BeginReading(); readIter != readEnd; readIter++)
+ {
+ if (!inEscape)
+ {
+ if (*readIter == '\\')
+ inEscape = true;
+ else
+ *(writeIter++) = *readIter;
+ }
+ else
+ {
+ inEscape = false;
+ switch (*readIter)
+ {
+ case '\\':
+ *(writeIter++) = '\\';
+ break;
+ case 't':
+ *(writeIter++) = '\t';
+ break;
+ case 'n':
+ *(writeIter++) = '\n';
+ break;
+ case 'v':
+ *(writeIter++) = '\v';
+ break;
+ case 'f':
+ *(writeIter++) = '\f';
+ break;
+ case 'r':
+ *(writeIter++) = '\r';
+ break;
+ default:
+ // all other escapes are ignored
+ break;
+ }
+ }
+ }
+ result.SetLength(writeIter - writeStart);
+ aCString.Assign(result);
+}
+
+Token* Tokenizer::copyTokens()
+{
+ uint32_t count = countTokens();
+ if (count > 0) {
+ Token* tokens = new Token[count];
+ if (tokens) {
+ Token* tp = tokens;
+ TokenEnumeration e(&mTokenTable);
+ while (e.hasMoreTokens())
+ *tp++ = *(static_cast<Token*>(e.nextToken()));
+ }
+ return tokens;
+ }
+ return NULL;
+}
+
+class TokenAnalyzer {
+public:
+ virtual ~TokenAnalyzer() {}
+
+ virtual void analyzeTokens(Tokenizer& tokenizer) = 0;
+ void setTokenListener(nsIStreamListener *aTokenListener)
+ {
+ mTokenListener = aTokenListener;
+ }
+
+ void setSource(const char *sourceURI) {mTokenSource = sourceURI;}
+
+ nsCOMPtr<nsIStreamListener> mTokenListener;
+ nsCString mTokenSource;
+
+};
+
+/**
+ * This class downloads the raw content of an email message, buffering until
+ * complete segments are seen, that is until a linefeed is seen, although
+ * any of the valid token separators would do. This could be a further
+ * refinement.
+ */
+class TokenStreamListener : public nsIStreamListener, nsIMsgHeaderSink {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIMSGHEADERSINK
+
+ explicit TokenStreamListener(TokenAnalyzer* analyzer);
+protected:
+ virtual ~TokenStreamListener();
+ TokenAnalyzer* mAnalyzer;
+ char* mBuffer;
+ uint32_t mBufferSize;
+ uint32_t mLeftOverCount;
+ Tokenizer mTokenizer;
+ bool mSetAttachmentFlag;
+};
+
+const uint32_t kBufferSize = 16384;
+
+TokenStreamListener::TokenStreamListener(TokenAnalyzer* analyzer)
+ : mAnalyzer(analyzer),
+ mBuffer(NULL), mBufferSize(kBufferSize), mLeftOverCount(0),
+ mSetAttachmentFlag(false)
+{
+}
+
+TokenStreamListener::~TokenStreamListener()
+{
+ delete[] mBuffer;
+ delete mAnalyzer;
+}
+
+NS_IMPL_ISUPPORTS(TokenStreamListener, nsIRequestObserver, nsIStreamListener, nsIMsgHeaderSink)
+
+NS_IMETHODIMP TokenStreamListener::ProcessHeaders(nsIUTF8StringEnumerator *aHeaderNames, nsIUTF8StringEnumerator *aHeaderValues, bool dontCollectAddress)
+{
+ mTokenizer.tokenizeHeaders(aHeaderNames, aHeaderValues);
+ return NS_OK;
+}
+
+NS_IMETHODIMP TokenStreamListener::HandleAttachment(const char *contentType, const char *url, const char16_t *displayName, const char *uri, bool aIsExternalAttachment)
+{
+ mTokenizer.tokenizeAttachment(contentType, NS_ConvertUTF16toUTF8(displayName).get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP TokenStreamListener::AddAttachmentField(const char *field, const char *value)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP TokenStreamListener::OnEndAllAttachments()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP TokenStreamListener::OnEndMsgDownload(nsIMsgMailNewsUrl *url)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP TokenStreamListener::OnMsgHasRemoteContent(nsIMsgDBHdr *aMsgHdr,
+ nsIURI *aContentURI,
+ bool aCanOverride)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP TokenStreamListener::OnEndMsgHeaders(nsIMsgMailNewsUrl *url)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP TokenStreamListener::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ return NS_OK;
+}
+NS_IMETHODIMP TokenStreamListener::SetSecurityInfo(nsISupports * aSecurityInfo)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP TokenStreamListener::GetDummyMsgHeader(nsIMsgDBHdr **aMsgDBHdr)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP TokenStreamListener::ResetProperties()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP TokenStreamListener::GetProperties(nsIWritablePropertyBag2 * *aProperties)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void onStartRequest (in nsIRequest aRequest, in nsISupports aContext); */
+NS_IMETHODIMP TokenStreamListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ mLeftOverCount = 0;
+ if (!mBuffer)
+ {
+ mBuffer = new char[mBufferSize];
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // get the url for the channel and set our nsIMsgHeaderSink on it so we get notified
+ // about the headers and attachments
+
+ nsCOMPtr<nsIChannel> channel (do_QueryInterface(aRequest));
+ if (channel)
+ {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(uri);
+ if (mailUrl)
+ mailUrl->SetMsgHeaderSink(static_cast<nsIMsgHeaderSink*>(this));
+ }
+
+ return NS_OK;
+}
+
+/* void onDataAvailable (in nsIRequest aRequest, in nsISupports aContext, in nsIInputStream aInputStream, in unsigned long long aOffset, in unsigned long aCount); */
+NS_IMETHODIMP TokenStreamListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext, nsIInputStream *aInputStream, uint64_t aOffset, uint32_t aCount)
+{
+ nsresult rv = NS_OK;
+
+ while (aCount > 0) {
+ uint32_t readCount, totalCount = (aCount + mLeftOverCount);
+ if (totalCount >= mBufferSize) {
+ readCount = mBufferSize - mLeftOverCount - 1;
+ } else {
+ readCount = aCount;
+ }
+
+ // mBuffer is supposed to be allocated in onStartRequest. But something
+ // is causing that to not happen, so as a last-ditch attempt we'll
+ // do it here.
+ if (!mBuffer)
+ {
+ mBuffer = new char[mBufferSize];
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ char* buffer = mBuffer;
+ rv = aInputStream->Read(buffer + mLeftOverCount, readCount, &readCount);
+ if (NS_FAILED(rv))
+ break;
+
+ if (readCount == 0) {
+ rv = NS_ERROR_UNEXPECTED;
+ NS_WARNING("failed to tokenize");
+ break;
+ }
+
+ aCount -= readCount;
+
+ /* consume the tokens up to the last legal token delimiter in the buffer. */
+ totalCount = (readCount + mLeftOverCount);
+ buffer[totalCount] = '\0';
+ char* lastDelimiter = NULL;
+ char* scan = buffer + totalCount;
+ while (scan > buffer) {
+ if (strchr(mTokenizer.mBodyDelimiters.get(), *--scan)) {
+ lastDelimiter = scan;
+ break;
+ }
+ }
+
+ if (lastDelimiter) {
+ *lastDelimiter = '\0';
+ mTokenizer.tokenize(buffer);
+
+ uint32_t consumedCount = 1 + (lastDelimiter - buffer);
+ mLeftOverCount = totalCount - consumedCount;
+ if (mLeftOverCount)
+ memmove(buffer, buffer + consumedCount, mLeftOverCount);
+ } else {
+ /* didn't find a delimiter, keep the whole buffer around. */
+ mLeftOverCount = totalCount;
+ if (totalCount >= (mBufferSize / 2)) {
+ uint32_t newBufferSize = mBufferSize * 2;
+ char* newBuffer = new char[newBufferSize];
+ NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
+ memcpy(newBuffer, mBuffer, mLeftOverCount);
+ delete[] mBuffer;
+ mBuffer = newBuffer;
+ mBufferSize = newBufferSize;
+ }
+ }
+ }
+
+ return rv;
+}
+
+/* void onStopRequest (in nsIRequest aRequest, in nsISupports aContext, in nsresult aStatusCode); */
+NS_IMETHODIMP TokenStreamListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatusCode)
+{
+ if (mLeftOverCount) {
+ /* assume final buffer is complete. */
+ mBuffer[mLeftOverCount] = '\0';
+ mTokenizer.tokenize(mBuffer);
+ }
+
+ /* finally, analyze the tokenized message. */
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug, ("analyze the tokenized message"));
+ if (mAnalyzer)
+ mAnalyzer->analyzeTokens(mTokenizer);
+
+ return NS_OK;
+}
+
+/* Implementation file */
+
+NS_IMPL_ISUPPORTS(nsBayesianFilter, nsIMsgFilterPlugin,
+ nsIJunkMailPlugin, nsIMsgCorpus, nsISupportsWeakReference,
+ nsIObserver)
+
+nsBayesianFilter::nsBayesianFilter()
+ : mTrainingDataDirty(false)
+{
+ if (!BayesianFilterLogModule)
+ BayesianFilterLogModule = PR_NewLogModule("BayesianFilter");
+
+ int32_t junkThreshold = 0;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (pPrefBranch)
+ pPrefBranch->GetIntPref("mail.adaptivefilters.junk_threshold", &junkThreshold);
+
+ mJunkProbabilityThreshold = (static_cast<double>(junkThreshold)) / 100.0;
+ if (mJunkProbabilityThreshold == 0 || mJunkProbabilityThreshold >= 1)
+ mJunkProbabilityThreshold = kDefaultJunkThreshold;
+
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Warning, ("junk probability threshold: %f", mJunkProbabilityThreshold));
+
+ mCorpus.readTrainingData();
+
+ // get parameters for training data flushing, from the prefs
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed accessing preferences service");
+ rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed getting preferences branch");
+
+ rv = prefBranch->GetIntPref("mailnews.bayesian_spam_filter.flush.minimum_interval",&mMinFlushInterval);
+ // it is not a good idea to allow a minimum interval of under 1 second
+ if (NS_FAILED(rv) || (mMinFlushInterval <= 1000) )
+ mMinFlushInterval = DEFAULT_MIN_INTERVAL_BETWEEN_WRITES;
+
+ rv = prefBranch->GetIntPref("mailnews.bayesian_spam_filter.junk_maxtokens", &mMaximumTokenCount);
+ if (NS_FAILED(rv))
+ mMaximumTokenCount = 0; // which means do not limit token counts
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Warning, ("maximum junk tokens: %d", mMaximumTokenCount));
+
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create a timer; training data will only be written on exit");
+
+ // the timer is not used on object construction, since for
+ // the time being there are no dirying messages
+
+ // give a default capacity to the memory structure used to store
+ // per-message/per-trait token data
+ mAnalysisStore.SetCapacity(kAnalysisStoreCapacity);
+
+ // dummy 0th element. Index 0 means "end of list" so we need to
+ // start from 1
+ AnalysisPerToken analysisPT(0, 0.0, 0.0);
+ mAnalysisStore.AppendElement(analysisPT);
+ mNextAnalysisIndex = 1;
+}
+
+nsresult nsBayesianFilter::Init()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->AddObserver(this, "profile-before-change", true);
+ return NS_OK;
+}
+
+void
+nsBayesianFilter::TimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ // we will flush the training data to disk after enough time has passed
+ // since the first time a message has been classified after the last flush
+
+ nsBayesianFilter *filter = static_cast<nsBayesianFilter *>(aClosure);
+ filter->mCorpus.writeTrainingData(filter->mMaximumTokenCount);
+ filter->mTrainingDataDirty = false;
+}
+
+nsBayesianFilter::~nsBayesianFilter()
+{
+ if (mTimer)
+ {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ // call shutdown when we are going away in case we need
+ // to flush the training set to disk
+ Shutdown();
+}
+
+// this object is used for one call to classifyMessage or classifyMessages().
+// So if we're classifying multiple messages, this object will be used for each message.
+// It's going to hold a reference to itself, basically, to stay in memory.
+class MessageClassifier : public TokenAnalyzer {
+public:
+ // full classifier with arbitrary traits
+ MessageClassifier(nsBayesianFilter* aFilter,
+ nsIJunkMailClassificationListener* aJunkListener,
+ nsIMsgTraitClassificationListener* aTraitListener,
+ nsIMsgTraitDetailListener* aDetailListener,
+ nsTArray<uint32_t>& aProTraits,
+ nsTArray<uint32_t>& aAntiTraits,
+ nsIMsgWindow *aMsgWindow,
+ uint32_t aNumMessagesToClassify,
+ const char **aMessageURIs)
+ : mFilter(aFilter),
+ mJunkMailPlugin(aFilter),
+ mJunkListener(aJunkListener),
+ mTraitListener(aTraitListener),
+ mDetailListener(aDetailListener),
+ mProTraits(aProTraits),
+ mAntiTraits(aAntiTraits),
+ mMsgWindow(aMsgWindow)
+ {
+ mCurMessageToClassify = 0;
+ mNumMessagesToClassify = aNumMessagesToClassify;
+ mMessageURIs = (char **) moz_xmalloc(sizeof(char *) * aNumMessagesToClassify);
+ for (uint32_t i = 0; i < aNumMessagesToClassify; i++)
+ mMessageURIs[i] = PL_strdup(aMessageURIs[i]);
+
+ }
+
+ // junk-only classifier
+ MessageClassifier(nsBayesianFilter* aFilter,
+ nsIJunkMailClassificationListener* aJunkListener,
+ nsIMsgWindow *aMsgWindow,
+ uint32_t aNumMessagesToClassify,
+ const char **aMessageURIs)
+ : mFilter(aFilter),
+ mJunkMailPlugin(aFilter),
+ mJunkListener(aJunkListener),
+ mTraitListener(nullptr),
+ mDetailListener(nullptr),
+ mMsgWindow(aMsgWindow)
+ {
+ mCurMessageToClassify = 0;
+ mNumMessagesToClassify = aNumMessagesToClassify;
+ mMessageURIs = (char **) moz_xmalloc(sizeof(char *) * aNumMessagesToClassify);
+ for (uint32_t i = 0; i < aNumMessagesToClassify; i++)
+ mMessageURIs[i] = PL_strdup(aMessageURIs[i]);
+ mProTraits.AppendElement(kJunkTrait);
+ mAntiTraits.AppendElement(kGoodTrait);
+
+ }
+
+ virtual ~MessageClassifier()
+ {
+ if (mMessageURIs)
+ {
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mNumMessagesToClassify, mMessageURIs);
+ }
+ }
+ virtual void analyzeTokens(Tokenizer& tokenizer)
+ {
+ mFilter->classifyMessage(tokenizer,
+ mTokenSource.get(),
+ mProTraits,
+ mAntiTraits,
+ mJunkListener,
+ mTraitListener,
+ mDetailListener);
+ tokenizer.clearTokens();
+ classifyNextMessage();
+ }
+
+ virtual void classifyNextMessage()
+ {
+
+ if (++mCurMessageToClassify < mNumMessagesToClassify && mMessageURIs[mCurMessageToClassify]) {
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Warning, ("classifyNextMessage(%s)", mMessageURIs[mCurMessageToClassify]));
+ mFilter->tokenizeMessage(mMessageURIs[mCurMessageToClassify], mMsgWindow, this);
+ }
+ else
+ {
+ // call all listeners with null parameters to signify end of batch
+ if (mJunkListener)
+ mJunkListener->OnMessageClassified(nullptr, nsIJunkMailPlugin::UNCLASSIFIED, 0);
+ if (mTraitListener)
+ mTraitListener->OnMessageTraitsClassified(nullptr, 0, nullptr, nullptr);
+ mTokenListener = nullptr; // this breaks the circular ref that keeps this object alive
+ // so we will be destroyed as a result.
+ }
+ }
+
+private:
+ nsBayesianFilter* mFilter;
+ nsCOMPtr<nsIJunkMailPlugin> mJunkMailPlugin;
+ nsCOMPtr<nsIJunkMailClassificationListener> mJunkListener;
+ nsCOMPtr<nsIMsgTraitClassificationListener> mTraitListener;
+ nsCOMPtr<nsIMsgTraitDetailListener> mDetailListener;
+ nsTArray<uint32_t> mProTraits;
+ nsTArray<uint32_t> mAntiTraits;
+ nsCOMPtr<nsIMsgWindow> mMsgWindow;
+ int32_t mNumMessagesToClassify;
+ int32_t mCurMessageToClassify; // 0-based index
+ char **mMessageURIs;
+};
+
+nsresult nsBayesianFilter::tokenizeMessage(const char* aMessageURI, nsIMsgWindow *aMsgWindow, TokenAnalyzer* aAnalyzer)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+
+ nsCOMPtr <nsIMsgMessageService> msgService;
+ nsresult rv = GetMessageServiceFromURI(nsDependentCString(aMessageURI), getter_AddRefs(msgService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aAnalyzer->setSource(aMessageURI);
+ nsCOMPtr<nsIURI> dummyNull;
+ return msgService->StreamMessage(aMessageURI, aAnalyzer->mTokenListener,
+ aMsgWindow, nullptr, true /* convert data */,
+ NS_LITERAL_CSTRING("filter"), false, getter_AddRefs(dummyNull));
+}
+
+// a TraitAnalysis is the per-token representation of the statistical
+// calculations, basically created to group information that is then
+// sorted by mDistance
+struct TraitAnalysis
+{
+ uint32_t mTokenIndex;
+ double mDistance;
+ double mProbability;
+};
+
+// comparator required to sort an nsTArray
+class compareTraitAnalysis
+{
+public:
+ bool Equals(const TraitAnalysis& a, const TraitAnalysis& b) const
+ {
+ return a.mDistance == b.mDistance;
+ }
+ bool LessThan(const TraitAnalysis& a, const TraitAnalysis& b) const
+ {
+ return a.mDistance < b.mDistance;
+ }
+};
+
+inline double dmax(double x, double y) { return (x > y ? x : y); }
+inline double dmin(double x, double y) { return (x < y ? x : y); }
+
+// Chi square functions are implemented by an incomplete gamma function.
+// Note that chi2P's callers multiply the arguments by 2 but chi2P
+// divides them by 2 again. Inlining chi2P gives the compiler a
+// chance to notice this.
+
+// Both chi2P and nsIncompleteGammaP set *error negative on domain
+// errors and nsIncompleteGammaP sets it posivive on internal errors.
+// This may be useful but the chi2P callers treat any error as fatal.
+
+// Note that converting unsigned ints to floating point can be slow on
+// some platforms (like Intel) so use signed quantities for the numeric
+// routines.
+static inline double chi2P (double chi2, double nu, int32_t *error)
+{
+ // domain checks; set error and return a dummy value
+ if (chi2 < 0.0 || nu <= 0.0)
+ {
+ *error = -1;
+ return 0.0;
+ }
+ // reversing the arguments is intentional
+ return nsIncompleteGammaP (nu/2.0, chi2/2.0, error);
+}
+
+void nsBayesianFilter::classifyMessage(
+ Tokenizer& tokenizer,
+ const char* messageURI,
+ nsTArray<uint32_t>& aProTraits,
+ nsTArray<uint32_t>& aAntiTraits,
+ nsIJunkMailClassificationListener* listener,
+ nsIMsgTraitClassificationListener* aTraitListener,
+ nsIMsgTraitDetailListener* aDetailListener)
+{
+ Token* tokens = tokenizer.copyTokens();
+ uint32_t tokenCount;
+ if (!tokens)
+ {
+ // This can happen with problems with UTF conversion
+ NS_ERROR("Trying to classify a null or invalid message");
+ tokenCount = 0;
+ // don't return so that we still call the listeners
+ }
+ else
+ {
+ tokenCount = tokenizer.countTokens();
+ }
+
+ if (aProTraits.Length() != aAntiTraits.Length())
+ {
+ NS_ERROR("Each Pro trait needs a matching Anti trait");
+ return;
+ }
+
+ /* this part is similar to the Graham algorithm with some adjustments. */
+ uint32_t traitCount = aProTraits.Length();
+
+ // pro message counts per trait index
+ AutoTArray<uint32_t, kTraitAutoCapacity> numProMessages;
+ // anti message counts per trait index
+ AutoTArray<uint32_t, kTraitAutoCapacity> numAntiMessages;
+ // array of pro aliases per trait index
+ AutoTArray<uint32_t*, kTraitAutoCapacity > proAliasArrays;
+ // number of pro aliases per trait index
+ AutoTArray<uint32_t, kTraitAutoCapacity > proAliasesLengths;
+ // array of anti aliases per trait index
+ AutoTArray<uint32_t*, kTraitAutoCapacity> antiAliasArrays;
+ // number of anti aliases per trait index
+ AutoTArray<uint32_t, kTraitAutoCapacity > antiAliasesLengths;
+ // construct the outgoing listener arrays
+ AutoTArray<uint32_t, kTraitAutoCapacity> traits;
+ AutoTArray<uint32_t, kTraitAutoCapacity> percents;
+ if (traitCount > kTraitAutoCapacity)
+ {
+ traits.SetCapacity(traitCount);
+ percents.SetCapacity(traitCount);
+ numProMessages.SetCapacity(traitCount);
+ numAntiMessages.SetCapacity(traitCount);
+ proAliasesLengths.SetCapacity(traitCount);
+ antiAliasesLengths.SetCapacity(traitCount);
+ proAliasArrays.SetCapacity(traitCount);
+ antiAliasArrays.SetCapacity(traitCount);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgTraitService> traitService(do_GetService("@mozilla.org/msg-trait-service;1", &rv));
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("Failed to get trait service");
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Error, ("Failed to get trait service"));
+ }
+
+ // get aliases and message counts for the pro and anti traits
+ for (uint32_t traitIndex = 0; traitIndex < traitCount; traitIndex++)
+ {
+ nsresult rv;
+
+ // pro trait
+ uint32_t proAliasesLength = 0;
+ uint32_t* proAliases = nullptr;
+ uint32_t proTrait = aProTraits[traitIndex];
+ if (traitService)
+ {
+ rv = traitService->GetAliases(proTrait, &proAliasesLength, &proAliases);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("trait service failed to get aliases");
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Error, ("trait service failed to get aliases"));
+ }
+ }
+ proAliasesLengths.AppendElement(proAliasesLength);
+ proAliasArrays.AppendElement(proAliases);
+ uint32_t proMessageCount = mCorpus.getMessageCount(proTrait);
+ for (uint32_t aliasIndex = 0; aliasIndex < proAliasesLength; aliasIndex++)
+ proMessageCount += mCorpus.getMessageCount(proAliases[aliasIndex]);
+ numProMessages.AppendElement(proMessageCount);
+
+ // anti trait
+ uint32_t antiAliasesLength = 0;
+ uint32_t* antiAliases = nullptr;
+ uint32_t antiTrait = aAntiTraits[traitIndex];
+ if (traitService)
+ {
+ rv = traitService->GetAliases(antiTrait, &antiAliasesLength, &antiAliases);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("trait service failed to get aliases");
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Error, ("trait service failed to get aliases"));
+ }
+ }
+ antiAliasesLengths.AppendElement(antiAliasesLength);
+ antiAliasArrays.AppendElement(antiAliases);
+ uint32_t antiMessageCount = mCorpus.getMessageCount(antiTrait);
+ for (uint32_t aliasIndex = 0; aliasIndex < antiAliasesLength; aliasIndex++)
+ antiMessageCount += mCorpus.getMessageCount(antiAliases[aliasIndex]);
+ numAntiMessages.AppendElement(antiMessageCount);
+ }
+
+ for (uint32_t i = 0; i < tokenCount; ++i)
+ {
+ Token& token = tokens[i];
+ CorpusToken* t = mCorpus.get(token.mWord);
+ if (!t)
+ continue;
+ for (uint32_t traitIndex = 0; traitIndex < traitCount; traitIndex++)
+ {
+ uint32_t iProCount = mCorpus.getTraitCount(t, aProTraits[traitIndex]);
+ // add in any counts for aliases to proTrait
+ for (uint32_t aliasIndex = 0; aliasIndex < proAliasesLengths[traitIndex]; aliasIndex++)
+ iProCount += mCorpus.getTraitCount(t, proAliasArrays[traitIndex][aliasIndex]);
+ double proCount = static_cast<double>(iProCount);
+
+ uint32_t iAntiCount = mCorpus.getTraitCount(t, aAntiTraits[traitIndex]);
+ // add in any counts for aliases to antiTrait
+ for (uint32_t aliasIndex = 0; aliasIndex < antiAliasesLengths[traitIndex]; aliasIndex++)
+ iAntiCount += mCorpus.getTraitCount(t, antiAliasArrays[traitIndex][aliasIndex]);
+ double antiCount = static_cast<double>(iAntiCount);
+
+ double prob, denom;
+ // Prevent a divide by zero error by setting defaults for prob
+
+ // If there are no matching tokens at all, ignore.
+ if (antiCount == 0.0 && proCount == 0.0)
+ continue;
+ // if only anti match, set probability to 0%
+ if (proCount == 0.0)
+ prob = 0.0;
+ // if only pro match, set probability to 100%
+ else if (antiCount == 0.0)
+ prob = 1.0;
+ // not really needed, but just to be sure check the denom as well
+ else if ((denom = proCount * numAntiMessages[traitIndex] +
+ antiCount * numProMessages[traitIndex]) == 0.0)
+ continue;
+ else
+ prob = (proCount * numAntiMessages[traitIndex]) / denom;
+
+ double n = proCount + antiCount;
+ prob = (0.225 + n * prob) / (.45 + n);
+ double distance = std::abs(prob - 0.5);
+ if (distance >= .1)
+ {
+ mozilla::DebugOnly<nsresult> rv = setAnalysis(token, traitIndex, distance, prob);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Problem in setAnalysis");
+ }
+ }
+ }
+
+ for (uint32_t traitIndex = 0; traitIndex < traitCount; traitIndex++)
+ {
+ AutoTArray<TraitAnalysis, 1024> traitAnalyses;
+ // copy valid tokens into an array to sort
+ for (uint32_t tokenIndex = 0; tokenIndex < tokenCount; tokenIndex++)
+ {
+ uint32_t storeIndex = getAnalysisIndex(tokens[tokenIndex], traitIndex);
+ if (storeIndex)
+ {
+ TraitAnalysis ta =
+ {tokenIndex,
+ mAnalysisStore[storeIndex].mDistance,
+ mAnalysisStore[storeIndex].mProbability};
+ traitAnalyses.AppendElement(ta);
+ }
+ }
+
+ // sort the array by the distances
+ traitAnalyses.Sort(compareTraitAnalysis());
+ uint32_t count = traitAnalyses.Length();
+ uint32_t first, last = count;
+ const uint32_t kMaxTokens = 150;
+ first = ( count > kMaxTokens) ? count - kMaxTokens : 0;
+
+ // Setup the arrays to save details if needed
+ nsTArray<double> sArray;
+ nsTArray<double> hArray;
+ uint32_t usedTokenCount = ( count > kMaxTokens) ? kMaxTokens : count;
+ if (aDetailListener)
+ {
+ sArray.SetCapacity(usedTokenCount);
+ hArray.SetCapacity(usedTokenCount);
+ }
+
+ double H = 1.0, S = 1.0;
+ int32_t Hexp = 0, Sexp = 0;
+ uint32_t goodclues=0;
+ int e;
+
+ // index from end to analyze most significant first
+ for (uint32_t ip1 = last; ip1 != first; --ip1)
+ {
+ TraitAnalysis& ta = traitAnalyses[ip1 - 1];
+ if (ta.mDistance > 0.0)
+ {
+ goodclues++;
+ double value = ta.mProbability;
+ S *= (1.0 - value);
+ H *= value;
+ if ( S < 1e-200 )
+ {
+ S = frexp(S, &e);
+ Sexp += e;
+ }
+ if ( H < 1e-200 )
+ {
+ H = frexp(H, &e);
+ Hexp += e;
+ }
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Warning,
+ ("token probability (%s) is %f",
+ tokens[ta.mTokenIndex].mWord, ta.mProbability));
+ }
+ if (aDetailListener)
+ {
+ sArray.AppendElement(log(S) + Sexp * M_LN2);
+ hArray.AppendElement(log(H) + Hexp * M_LN2);
+ }
+ }
+
+ S = log(S) + Sexp * M_LN2;
+ H = log(H) + Hexp * M_LN2;
+
+ double prob;
+ if (goodclues > 0)
+ {
+ int32_t chi_error;
+ S = chi2P(-2.0 * S, 2.0 * goodclues, &chi_error);
+ if (!chi_error)
+ H = chi2P(-2.0 * H, 2.0 * goodclues, &chi_error);
+ // if any error toss the entire calculation
+ if (!chi_error)
+ prob = (S-H +1.0) / 2.0;
+ else
+ prob = 0.5;
+ }
+ else
+ prob = 0.5;
+
+ if (aDetailListener)
+ {
+ // Prepare output arrays
+ nsTArray<uint32_t> tokenPercents(usedTokenCount);
+ nsTArray<uint32_t> runningPercents(usedTokenCount);
+ nsTArray<char16_t*> tokenStrings(usedTokenCount);
+
+ double clueCount = 1.0;
+ for (uint32_t tokenIndex = 0; tokenIndex < usedTokenCount; tokenIndex++)
+ {
+ TraitAnalysis& ta = traitAnalyses[last - 1 - tokenIndex];
+ int32_t chi_error;
+ S = chi2P(-2.0 * sArray[tokenIndex], 2.0 * clueCount, &chi_error);
+ if (!chi_error)
+ H = chi2P(-2.0 * hArray[tokenIndex], 2.0 * clueCount, &chi_error);
+ clueCount += 1.0;
+ double runningProb;
+ if (!chi_error)
+ runningProb = (S - H + 1.0) / 2.0;
+ else
+ runningProb = 0.5;
+ runningPercents.AppendElement(static_cast<uint32_t>(runningProb *
+ 100. + .5));
+ tokenPercents.AppendElement(static_cast<uint32_t>(ta.mProbability *
+ 100. + .5));
+ tokenStrings.AppendElement(ToNewUnicode(NS_ConvertUTF8toUTF16(
+ tokens[ta.mTokenIndex].mWord)));
+ }
+
+ aDetailListener->OnMessageTraitDetails(messageURI, aProTraits[traitIndex],
+ usedTokenCount, (const char16_t**)tokenStrings.Elements(),
+ tokenPercents.Elements(), runningPercents.Elements());
+ for (uint32_t tokenIndex = 0; tokenIndex < usedTokenCount; tokenIndex++)
+ NS_Free(tokenStrings[tokenIndex]);
+ }
+
+ uint32_t proPercent = static_cast<uint32_t>(prob*100. + .5);
+
+ // directly classify junk to maintain backwards compatibility
+ if (aProTraits[traitIndex] == kJunkTrait)
+ {
+ bool isJunk = (prob >= mJunkProbabilityThreshold);
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Info,
+ ("%s is junk probability = (%f) HAM SCORE:%f SPAM SCORE:%f",
+ messageURI, prob,H,S));
+
+ // the algorithm in "A Plan For Spam" assumes that you have a large good
+ // corpus and a large junk corpus.
+ // that won't be the case with users who first use the junk mail trait
+ // so, we do certain things to encourage them to train.
+ //
+ // if there are no good tokens, assume the message is junk
+ // this will "encourage" the user to train
+ // and if there are no bad tokens, assume the message is not junk
+ // this will also "encourage" the user to train
+ // see bug #194238
+
+ if (listener && !mCorpus.getMessageCount(kGoodTrait))
+ isJunk = true;
+ else if (listener && !mCorpus.getMessageCount(kJunkTrait))
+ isJunk = false;
+
+ if (listener)
+ listener->OnMessageClassified(messageURI, isJunk ?
+ nsMsgJunkStatus(nsIJunkMailPlugin::JUNK) :
+ nsMsgJunkStatus(nsIJunkMailPlugin::GOOD), proPercent);
+ }
+
+ if (aTraitListener)
+ {
+ traits.AppendElement(aProTraits[traitIndex]);
+ percents.AppendElement(proPercent);
+ }
+
+ // free aliases arrays returned from XPCOM
+ if (proAliasesLengths[traitIndex])
+ NS_Free(proAliasArrays[traitIndex]);
+ if (antiAliasesLengths[traitIndex])
+ NS_Free(antiAliasArrays[traitIndex]);
+ }
+
+ if (aTraitListener)
+ aTraitListener->OnMessageTraitsClassified(messageURI,
+ traits.Length(), traits.Elements(), percents.Elements());
+
+ delete[] tokens;
+ // reuse mAnalysisStore without clearing memory
+ mNextAnalysisIndex = 1;
+ // but shrink it back to the default size
+ if (mAnalysisStore.Length() > kAnalysisStoreCapacity)
+ mAnalysisStore.RemoveElementsAt(kAnalysisStoreCapacity,
+ mAnalysisStore.Length() - kAnalysisStoreCapacity);
+ mAnalysisStore.Compact();
+}
+
+void nsBayesianFilter::classifyMessage(
+ Tokenizer& tokens,
+ const char* messageURI,
+ nsIJunkMailClassificationListener* aJunkListener)
+{
+ AutoTArray<uint32_t, 1> proTraits;
+ AutoTArray<uint32_t, 1> antiTraits;
+ proTraits.AppendElement(kJunkTrait);
+ antiTraits.AppendElement(kGoodTrait);
+ classifyMessage(tokens, messageURI, proTraits, antiTraits,
+ aJunkListener, nullptr, nullptr);
+}
+
+NS_IMETHODIMP
+nsBayesianFilter::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *someData)
+{
+ if (!strcmp(aTopic, "profile-before-change"))
+ Shutdown();
+ return NS_OK;
+}
+
+/* void shutdown (); */
+NS_IMETHODIMP nsBayesianFilter::Shutdown()
+{
+ if (mTrainingDataDirty)
+ mCorpus.writeTrainingData(mMaximumTokenCount);
+ mTrainingDataDirty = false;
+
+ return NS_OK;
+}
+
+/* readonly attribute boolean shouldDownloadAllHeaders; */
+NS_IMETHODIMP nsBayesianFilter::GetShouldDownloadAllHeaders(bool *aShouldDownloadAllHeaders)
+{
+ // bayesian filters work on the whole msg body currently.
+ *aShouldDownloadAllHeaders = false;
+ return NS_OK;
+}
+
+/* void classifyMessage (in string aMsgURL, in nsIJunkMailClassificationListener aListener); */
+NS_IMETHODIMP nsBayesianFilter::ClassifyMessage(const char *aMessageURL, nsIMsgWindow *aMsgWindow, nsIJunkMailClassificationListener *aListener)
+{
+ MessageClassifier* analyzer = new MessageClassifier(this, aListener, aMsgWindow, 1, &aMessageURL);
+ NS_ENSURE_TRUE(analyzer, NS_ERROR_OUT_OF_MEMORY);
+ TokenStreamListener *tokenListener = new TokenStreamListener(analyzer);
+ NS_ENSURE_TRUE(tokenListener, NS_ERROR_OUT_OF_MEMORY);
+ analyzer->setTokenListener(tokenListener);
+ return tokenizeMessage(aMessageURL, aMsgWindow, analyzer);
+}
+
+/* void classifyMessages (in unsigned long aCount, [array, size_is (aCount)] in string aMsgURLs, in nsIJunkMailClassificationListener aListener); */
+NS_IMETHODIMP nsBayesianFilter::ClassifyMessages(uint32_t aCount, const char **aMsgURLs, nsIMsgWindow *aMsgWindow, nsIJunkMailClassificationListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aMsgURLs);
+
+ TokenAnalyzer* analyzer = new MessageClassifier(this, aListener, aMsgWindow, aCount, aMsgURLs);
+ NS_ENSURE_TRUE(analyzer, NS_ERROR_OUT_OF_MEMORY);
+ TokenStreamListener *tokenListener = new TokenStreamListener(analyzer);
+ NS_ENSURE_TRUE(tokenListener, NS_ERROR_OUT_OF_MEMORY);
+ analyzer->setTokenListener(tokenListener);
+ return tokenizeMessage(aMsgURLs[0], aMsgWindow, analyzer);
+}
+
+nsresult nsBayesianFilter::setAnalysis(Token& token, uint32_t aTraitIndex,
+ double aDistance, double aProbability)
+{
+ uint32_t nextLink = token.mAnalysisLink;
+ uint32_t lastLink = 0;
+ uint32_t linkCount = 0, maxLinks = 100;
+
+ // try to find an existing element. Limit the search to maxLinks
+ // as a precaution
+ for (linkCount = 0; nextLink && linkCount < maxLinks; linkCount++)
+ {
+ AnalysisPerToken &rAnalysis = mAnalysisStore[nextLink];
+ if (rAnalysis.mTraitIndex == aTraitIndex)
+ {
+ rAnalysis.mDistance = aDistance;
+ rAnalysis.mProbability = aProbability;
+ return NS_OK;
+ }
+ lastLink = nextLink;
+ nextLink = rAnalysis.mNextLink;
+ }
+ if (linkCount >= maxLinks)
+ return NS_ERROR_FAILURE;
+
+ // trait does not exist, so add it
+
+ AnalysisPerToken analysis(aTraitIndex, aDistance, aProbability);
+ if (mAnalysisStore.Length() == mNextAnalysisIndex)
+ mAnalysisStore.InsertElementAt(mNextAnalysisIndex, analysis);
+ else if (mAnalysisStore.Length() > mNextAnalysisIndex)
+ mAnalysisStore.ReplaceElementsAt(mNextAnalysisIndex, 1, analysis);
+ else // we can only insert at the end of the array
+ return NS_ERROR_FAILURE;
+
+ if (lastLink)
+ // the token had at least one link, so update the last link to point to
+ // the new item
+ mAnalysisStore[lastLink].mNextLink = mNextAnalysisIndex;
+ else
+ // need to update the token's first link
+ token.mAnalysisLink = mNextAnalysisIndex;
+ mNextAnalysisIndex++;
+ return NS_OK;
+}
+
+uint32_t nsBayesianFilter::getAnalysisIndex(Token& token, uint32_t aTraitIndex)
+{
+ uint32_t nextLink;
+ uint32_t linkCount = 0, maxLinks = 100;
+ for (nextLink = token.mAnalysisLink; nextLink && linkCount < maxLinks; linkCount++)
+ {
+ AnalysisPerToken &rAnalysis = mAnalysisStore[nextLink];
+ if (rAnalysis.mTraitIndex == aTraitIndex)
+ return nextLink;
+ nextLink = rAnalysis.mNextLink;
+ }
+ NS_ASSERTION(linkCount < maxLinks, "corrupt analysis store");
+
+ // Trait not found, indicate by zero
+ return 0;
+}
+
+NS_IMETHODIMP nsBayesianFilter::ClassifyTraitsInMessage(
+ const char *aMsgURI,
+ uint32_t aTraitCount,
+ uint32_t *aProTraits,
+ uint32_t *aAntiTraits,
+ nsIMsgTraitClassificationListener *aTraitListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIJunkMailClassificationListener *aJunkListener)
+{
+ return ClassifyTraitsInMessages(1, &aMsgURI, aTraitCount, aProTraits,
+ aAntiTraits, aTraitListener, aMsgWindow, aJunkListener);
+}
+
+NS_IMETHODIMP nsBayesianFilter::ClassifyTraitsInMessages(
+ uint32_t aCount,
+ const char **aMsgURIs,
+ uint32_t aTraitCount,
+ uint32_t *aProTraits,
+ uint32_t *aAntiTraits,
+ nsIMsgTraitClassificationListener *aTraitListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIJunkMailClassificationListener *aJunkListener)
+{
+ AutoTArray<uint32_t, kTraitAutoCapacity> proTraits;
+ AutoTArray<uint32_t, kTraitAutoCapacity> antiTraits;
+ if (aTraitCount > kTraitAutoCapacity)
+ {
+ proTraits.SetCapacity(aTraitCount);
+ antiTraits.SetCapacity(aTraitCount);
+ }
+ proTraits.AppendElements(aProTraits, aTraitCount);
+ antiTraits.AppendElements(aAntiTraits, aTraitCount);
+
+ MessageClassifier* analyzer = new MessageClassifier(this, aJunkListener,
+ aTraitListener, nullptr, proTraits, antiTraits, aMsgWindow, aCount, aMsgURIs);
+ NS_ENSURE_TRUE(analyzer, NS_ERROR_OUT_OF_MEMORY);
+
+ TokenStreamListener *tokenListener = new TokenStreamListener(analyzer);
+ NS_ENSURE_TRUE(tokenListener, NS_ERROR_OUT_OF_MEMORY);
+
+ analyzer->setTokenListener(tokenListener);
+ return tokenizeMessage(aMsgURIs[0], aMsgWindow, analyzer);
+}
+
+class MessageObserver : public TokenAnalyzer {
+public:
+ MessageObserver(nsBayesianFilter* filter,
+ nsTArray<uint32_t>& aOldClassifications,
+ nsTArray<uint32_t>& aNewClassifications,
+ nsIJunkMailClassificationListener* aJunkListener,
+ nsIMsgTraitClassificationListener* aTraitListener)
+ : mFilter(filter), mJunkMailPlugin(filter), mJunkListener(aJunkListener),
+ mTraitListener(aTraitListener),
+ mOldClassifications(aOldClassifications),
+ mNewClassifications(aNewClassifications)
+ {
+ }
+
+ virtual void analyzeTokens(Tokenizer& tokenizer)
+ {
+ mFilter->observeMessage(tokenizer, mTokenSource.get(), mOldClassifications,
+ mNewClassifications, mJunkListener, mTraitListener);
+ // release reference to listener, which will allow us to go away as well.
+ mTokenListener = nullptr;
+ }
+
+private:
+ nsBayesianFilter* mFilter;
+ nsCOMPtr<nsIJunkMailPlugin> mJunkMailPlugin;
+ nsCOMPtr<nsIJunkMailClassificationListener> mJunkListener;
+ nsCOMPtr<nsIMsgTraitClassificationListener> mTraitListener;
+ nsTArray<uint32_t> mOldClassifications;
+ nsTArray<uint32_t> mNewClassifications;
+};
+
+NS_IMETHODIMP nsBayesianFilter::SetMsgTraitClassification(
+ const char *aMsgURI,
+ uint32_t aOldCount,
+ uint32_t *aOldTraits,
+ uint32_t aNewCount,
+ uint32_t *aNewTraits,
+ nsIMsgTraitClassificationListener *aTraitListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIJunkMailClassificationListener *aJunkListener)
+{
+ AutoTArray<uint32_t, kTraitAutoCapacity> oldTraits;
+ AutoTArray<uint32_t, kTraitAutoCapacity> newTraits;
+ if (aOldCount > kTraitAutoCapacity)
+ oldTraits.SetCapacity(aOldCount);
+ if (aNewCount > kTraitAutoCapacity)
+ newTraits.SetCapacity(aNewCount);
+ oldTraits.AppendElements(aOldTraits, aOldCount);
+ newTraits.AppendElements(aNewTraits, aNewCount);
+
+ MessageObserver* analyzer = new MessageObserver(this, oldTraits,
+ newTraits, aJunkListener, aTraitListener);
+ NS_ENSURE_TRUE(analyzer, NS_ERROR_OUT_OF_MEMORY);
+
+ TokenStreamListener *tokenListener = new TokenStreamListener(analyzer);
+ NS_ENSURE_TRUE(tokenListener, NS_ERROR_OUT_OF_MEMORY);
+
+ analyzer->setTokenListener(tokenListener);
+ return tokenizeMessage(aMsgURI, aMsgWindow, analyzer);
+}
+
+// set new message classifications for a message
+void nsBayesianFilter::observeMessage(
+ Tokenizer& tokenizer,
+ const char* messageURL,
+ nsTArray<uint32_t>& oldClassifications,
+ nsTArray<uint32_t>& newClassifications,
+ nsIJunkMailClassificationListener* aJunkListener,
+ nsIMsgTraitClassificationListener* aTraitListener)
+{
+
+ bool trainingDataWasDirty = mTrainingDataDirty;
+
+ // Uhoh...if the user is re-training then the message may already be classified and we are classifying it again with the same classification.
+ // the old code would have removed the tokens for this message then added them back. But this really hurts the message occurrence
+ // count for tokens if you just removed training.dat and are re-training. See Bug #237095 for more details.
+ // What can we do here? Well we can skip the token removal step if the classifications are the same and assume the user is
+ // just re-training. But this then allows users to re-classify the same message on the same training set over and over again
+ // leading to data skew. But that's all I can think to do right now to address this.....
+ uint32_t oldLength = oldClassifications.Length();
+ for (uint32_t index = 0; index < oldLength; index++)
+ {
+ uint32_t trait = oldClassifications.ElementAt(index);
+ // skip removing if trait is also in the new set
+ if (newClassifications.Contains(trait))
+ continue;
+ // remove the tokens from the token set it is currently in
+ uint32_t messageCount;
+ messageCount = mCorpus.getMessageCount(trait);
+ if (messageCount > 0)
+ {
+ mCorpus.setMessageCount(trait, messageCount - 1);
+ mCorpus.forgetTokens(tokenizer, trait, 1);
+ mTrainingDataDirty = true;
+ }
+ }
+
+ nsMsgJunkStatus newClassification = nsIJunkMailPlugin::UNCLASSIFIED;
+ uint32_t junkPercent = 0; // 0 here is no possibility of meeting the classification
+ uint32_t newLength = newClassifications.Length();
+ for (uint32_t index = 0; index < newLength; index++)
+ {
+ uint32_t trait = newClassifications.ElementAt(index);
+ mCorpus.setMessageCount(trait, mCorpus.getMessageCount(trait) + 1);
+ mCorpus.rememberTokens(tokenizer, trait, 1);
+ mTrainingDataDirty = true;
+
+ if (aJunkListener)
+ {
+ if (trait == kJunkTrait)
+ {
+ junkPercent = nsIJunkMailPlugin::IS_SPAM_SCORE;
+ newClassification = nsIJunkMailPlugin::JUNK;
+ }
+ else if (trait == kGoodTrait)
+ {
+ junkPercent = nsIJunkMailPlugin::IS_HAM_SCORE;
+ newClassification = nsIJunkMailPlugin::GOOD;
+ }
+ }
+ }
+
+ if (aJunkListener)
+ aJunkListener->OnMessageClassified(messageURL, newClassification, junkPercent);
+
+ if (aTraitListener)
+ {
+ // construct the outgoing listener arrays
+ AutoTArray<uint32_t, kTraitAutoCapacity> traits;
+ AutoTArray<uint32_t, kTraitAutoCapacity> percents;
+ uint32_t newLength = newClassifications.Length();
+ if (newLength > kTraitAutoCapacity)
+ {
+ traits.SetCapacity(newLength);
+ percents.SetCapacity(newLength);
+ }
+ traits.AppendElements(newClassifications);
+ for (uint32_t index = 0; index < newLength; index++)
+ percents.AppendElement(100); // This is 100 percent, or certainty
+ aTraitListener->OnMessageTraitsClassified(messageURL,
+ traits.Length(), traits.Elements(), percents.Elements());
+ }
+
+ if (mTrainingDataDirty && !trainingDataWasDirty && ( mTimer != nullptr ))
+ {
+ // if training data became dirty just now, schedule flush
+ // mMinFlushInterval msec from now
+ MOZ_LOG(
+ BayesianFilterLogModule, LogLevel::Debug,
+ ("starting training data flush timer %i msec", mMinFlushInterval));
+ mTimer->InitWithFuncCallback(nsBayesianFilter::TimerCallback, this, mMinFlushInterval, nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+NS_IMETHODIMP nsBayesianFilter::GetUserHasClassified(bool *aResult)
+{
+ *aResult = ( (mCorpus.getMessageCount(kGoodTrait) +
+ mCorpus.getMessageCount(kJunkTrait)) &&
+ mCorpus.countTokens());
+ return NS_OK;
+}
+
+// Set message classification (only allows junk and good)
+NS_IMETHODIMP nsBayesianFilter::SetMessageClassification(
+ const char *aMsgURL,
+ nsMsgJunkStatus aOldClassification,
+ nsMsgJunkStatus aNewClassification,
+ nsIMsgWindow *aMsgWindow,
+ nsIJunkMailClassificationListener *aListener)
+{
+ AutoTArray<uint32_t, 1> oldClassifications;
+ AutoTArray<uint32_t, 1> newClassifications;
+
+ // convert between classifications and trait
+ if (aOldClassification == nsIJunkMailPlugin::JUNK)
+ oldClassifications.AppendElement(kJunkTrait);
+ else if (aOldClassification == nsIJunkMailPlugin::GOOD)
+ oldClassifications.AppendElement(kGoodTrait);
+ if (aNewClassification == nsIJunkMailPlugin::JUNK)
+ newClassifications.AppendElement(kJunkTrait);
+ else if (aNewClassification == nsIJunkMailPlugin::GOOD)
+ newClassifications.AppendElement(kGoodTrait);
+
+ MessageObserver* analyzer = new MessageObserver(this, oldClassifications,
+ newClassifications, aListener, nullptr);
+ NS_ENSURE_TRUE(analyzer, NS_ERROR_OUT_OF_MEMORY);
+
+ TokenStreamListener *tokenListener = new TokenStreamListener(analyzer);
+ NS_ENSURE_TRUE(tokenListener, NS_ERROR_OUT_OF_MEMORY);
+
+ analyzer->setTokenListener(tokenListener);
+ return tokenizeMessage(aMsgURL, aMsgWindow, analyzer);
+}
+
+NS_IMETHODIMP nsBayesianFilter::ResetTrainingData()
+{
+ return mCorpus.resetTrainingData();
+}
+
+NS_IMETHODIMP nsBayesianFilter::DetailMessage(const char *aMsgURI,
+ uint32_t aProTrait, uint32_t aAntiTrait,
+ nsIMsgTraitDetailListener *aDetailListener, nsIMsgWindow *aMsgWindow)
+{
+ AutoTArray<uint32_t, 1> proTraits;
+ AutoTArray<uint32_t, 1> antiTraits;
+ proTraits.AppendElement(aProTrait);
+ antiTraits.AppendElement(aAntiTrait);
+
+ MessageClassifier* analyzer = new MessageClassifier(this, nullptr,
+ nullptr, aDetailListener, proTraits, antiTraits, aMsgWindow, 1, &aMsgURI);
+ NS_ENSURE_TRUE(analyzer, NS_ERROR_OUT_OF_MEMORY);
+
+ TokenStreamListener *tokenListener = new TokenStreamListener(analyzer);
+ NS_ENSURE_TRUE(tokenListener, NS_ERROR_OUT_OF_MEMORY);
+
+ analyzer->setTokenListener(tokenListener);
+ return tokenizeMessage(aMsgURI, aMsgWindow, analyzer);
+}
+
+// nsIMsgCorpus implementation
+
+NS_IMETHODIMP nsBayesianFilter::CorpusCounts(uint32_t aTrait,
+ uint32_t *aMessageCount,
+ uint32_t *aTokenCount)
+{
+ NS_ENSURE_ARG_POINTER(aTokenCount);
+ *aTokenCount = mCorpus.countTokens();
+ if (aTrait && aMessageCount)
+ *aMessageCount = mCorpus.getMessageCount(aTrait);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBayesianFilter::ClearTrait(uint32_t aTrait)
+{
+ return mCorpus.ClearTrait(aTrait);
+}
+
+NS_IMETHODIMP
+nsBayesianFilter::UpdateData(nsIFile *aFile,
+ bool aIsAdd,
+ uint32_t aRemapCount,
+ uint32_t *aFromTraits,
+ uint32_t *aToTraits)
+{
+ return mCorpus.UpdateData(aFile, aIsAdd, aRemapCount, aFromTraits, aToTraits);
+}
+
+NS_IMETHODIMP
+nsBayesianFilter::GetTokenCount(const nsACString &aWord,
+ uint32_t aTrait,
+ uint32_t *aCount)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+ CorpusToken* t = mCorpus.get(PromiseFlatCString(aWord).get());
+ uint32_t count = mCorpus.getTraitCount(t, aTrait);
+ *aCount = count;
+ return NS_OK;
+}
+
+/* Corpus Store */
+
+/*
+ Format of the training file for version 1:
+ [0xFEEDFACE]
+ [number good messages][number bad messages]
+ [number good tokens]
+ [count][length of word]word
+ ...
+ [number bad tokens]
+ [count][length of word]word
+ ...
+
+ Format of the trait file for version 1:
+ [0xFCA93601] (the 01 is the version)
+ for each trait to write
+ [id of trait to write] (0 means end of list)
+ [number of messages per trait]
+ for each token with non-zero count
+ [count]
+ [length of word]word
+*/
+
+CorpusStore::CorpusStore() :
+ TokenHash(sizeof(CorpusToken)),
+ mNextTraitIndex(1) // skip 0 since index=0 will mean end of linked list
+{
+ getTrainingFile(getter_AddRefs(mTrainingFile));
+ mTraitStore.SetCapacity(kTraitStoreCapacity);
+ TraitPerToken traitPT(0, 0);
+ mTraitStore.AppendElement(traitPT); // dummy 0th element
+}
+
+CorpusStore::~CorpusStore()
+{
+}
+
+inline int writeUInt32(FILE* stream, uint32_t value)
+{
+ value = PR_htonl(value);
+ return fwrite(&value, sizeof(uint32_t), 1, stream);
+}
+
+inline int readUInt32(FILE* stream, uint32_t* value)
+{
+ int n = fread(value, sizeof(uint32_t), 1, stream);
+ if (n == 1) {
+ *value = PR_ntohl(*value);
+ }
+ return n;
+}
+
+void CorpusStore::forgetTokens(Tokenizer& aTokenizer,
+ uint32_t aTraitId, uint32_t aCount)
+{
+ // if we are forgetting the tokens for a message, should only
+ // subtract 1 from the occurrence count for that token in the training set
+ // because we assume we only bumped the training set count once per messages
+ // containing the token.
+ TokenEnumeration tokens = aTokenizer.getTokens();
+ while (tokens.hasMoreTokens())
+ {
+ CorpusToken* token = static_cast<CorpusToken*>(tokens.nextToken());
+ remove(token->mWord, aTraitId, aCount);
+ }
+}
+
+void CorpusStore::rememberTokens(Tokenizer& aTokenizer,
+ uint32_t aTraitId, uint32_t aCount)
+{
+ TokenEnumeration tokens = aTokenizer.getTokens();
+ while (tokens.hasMoreTokens())
+ {
+ CorpusToken* token = static_cast<CorpusToken*>(tokens.nextToken());
+ if (!token)
+ {
+ NS_ERROR("null token");
+ continue;
+ }
+ add(token->mWord, aTraitId, aCount);
+ }
+}
+
+bool CorpusStore::writeTokens(FILE* stream, bool shrink, uint32_t aTraitId)
+{
+ uint32_t tokenCount = countTokens();
+ uint32_t newTokenCount = 0;
+
+ // calculate the tokens for this trait to write
+
+ TokenEnumeration tokens = getTokens();
+ for (uint32_t i = 0; i < tokenCount; ++i)
+ {
+ CorpusToken* token = static_cast<CorpusToken*>(tokens.nextToken());
+ uint32_t count = getTraitCount(token, aTraitId);
+ // Shrinking the token database is accomplished by dividing all token counts by 2.
+ // If shrinking, we'll ignore counts < 2, otherwise only ignore counts of < 1
+ if ((shrink && count > 1) || (!shrink && count))
+ newTokenCount++;
+ }
+
+ if (writeUInt32(stream, newTokenCount) != 1)
+ return false;
+
+ if (newTokenCount > 0)
+ {
+ TokenEnumeration tokens = getTokens();
+ for (uint32_t i = 0; i < tokenCount; ++i)
+ {
+ CorpusToken* token = static_cast<CorpusToken*>(tokens.nextToken());
+ uint32_t wordCount = getTraitCount(token, aTraitId);
+ if (shrink)
+ wordCount /= 2;
+ if (!wordCount)
+ continue; // Don't output zero count words
+ if (writeUInt32(stream, wordCount) != 1)
+ return false;
+ uint32_t tokenLength = strlen(token->mWord);
+ if (writeUInt32(stream, tokenLength) != 1)
+ return false;
+ if (fwrite(token->mWord, tokenLength, 1, stream) != 1)
+ return false;
+ }
+ }
+ return true;
+}
+
+bool CorpusStore::readTokens(FILE* stream, int64_t fileSize,
+ uint32_t aTraitId, bool aIsAdd)
+{
+ uint32_t tokenCount;
+ if (readUInt32(stream, &tokenCount) != 1)
+ return false;
+
+ int64_t fpos = ftell(stream);
+ if (fpos < 0)
+ return false;
+
+ uint32_t bufferSize = 4096;
+ char* buffer = new char[bufferSize];
+ if (!buffer) return false;
+
+ for (uint32_t i = 0; i < tokenCount; ++i) {
+ uint32_t count;
+ if (readUInt32(stream, &count) != 1)
+ break;
+ uint32_t size;
+ if (readUInt32(stream, &size) != 1)
+ break;
+ fpos += 8;
+ if (fpos + size > fileSize) {
+ delete[] buffer;
+ return false;
+ }
+ if (size >= bufferSize) {
+ delete[] buffer;
+ while (size >= bufferSize) {
+ bufferSize *= 2;
+ if (bufferSize == 0)
+ return false;
+ }
+ buffer = new char[bufferSize];
+ if (!buffer) return false;
+ }
+ if (fread(buffer, size, 1, stream) != 1)
+ break;
+ fpos += size;
+ buffer[size] = '\0';
+ if (aIsAdd)
+ add(buffer, aTraitId, count);
+ else
+ remove(buffer, aTraitId, count);
+ }
+
+ delete[] buffer;
+
+ return true;
+}
+
+nsresult CorpusStore::getTrainingFile(nsIFile ** aTrainingFile)
+{
+ // should we cache the profile manager's directory?
+ nsCOMPtr<nsIFile> profileDir;
+
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = profileDir->Append(NS_LITERAL_STRING("training.dat"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return profileDir->QueryInterface(NS_GET_IID(nsIFile), (void **) aTrainingFile);
+}
+
+nsresult CorpusStore::getTraitFile(nsIFile ** aTraitFile)
+{
+ // should we cache the profile manager's directory?
+ nsCOMPtr<nsIFile> profileDir;
+
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = profileDir->Append(NS_LITERAL_STRING("traits.dat"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return profileDir->QueryInterface(NS_GET_IID(nsIFile), (void **) aTraitFile);
+}
+
+static const char kMagicCookie[] = { '\xFE', '\xED', '\xFA', '\xCE' };
+
+// random string used to identify trait file and version (last byte is version)
+static const char kTraitCookie[] = { '\xFC', '\xA9', '\x36', '\x01' };
+
+void CorpusStore::writeTrainingData(uint32_t aMaximumTokenCount)
+{
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug, ("writeTrainingData() entered"));
+ if (!mTrainingFile)
+ return;
+
+ /*
+ * For backwards compatibility, write the good and junk tokens to
+ * training.dat; additional traits are added to a different file
+ */
+
+ // open the file, and write out training data
+ FILE* stream;
+ nsresult rv = mTrainingFile->OpenANSIFileDesc("wb", &stream);
+ if (NS_FAILED(rv))
+ return;
+
+ // If the number of tokens exceeds our limit, set the shrink flag
+ bool shrink = false;
+ if ((aMaximumTokenCount > 0) && // if 0, do not limit tokens
+ (countTokens() > aMaximumTokenCount))
+ {
+ shrink = true;
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Warning, ("shrinking token data file"));
+ }
+
+ // We implement shrink by dividing counts by two
+ uint32_t shrinkFactor = shrink ? 2 : 1;
+
+ if (!((fwrite(kMagicCookie, sizeof(kMagicCookie), 1, stream) == 1) &&
+ (writeUInt32(stream, getMessageCount(kGoodTrait) / shrinkFactor)) &&
+ (writeUInt32(stream, getMessageCount(kJunkTrait) / shrinkFactor)) &&
+ writeTokens(stream, shrink, kGoodTrait) &&
+ writeTokens(stream, shrink, kJunkTrait)))
+ {
+ NS_WARNING("failed to write training data.");
+ fclose(stream);
+ // delete the training data file, since it is potentially corrupt.
+ mTrainingFile->Remove(false);
+ }
+ else
+ {
+ fclose(stream);
+ }
+
+ /*
+ * Write the remaining data to a second file traits.dat
+ */
+
+ if (!mTraitFile)
+ {
+ getTraitFile(getter_AddRefs(mTraitFile));
+ if (!mTraitFile)
+ return;
+ }
+
+ // open the file, and write out training data
+ rv = mTraitFile->OpenANSIFileDesc("wb", &stream);
+ if (NS_FAILED(rv))
+ return;
+
+ uint32_t numberOfTraits = mMessageCounts.Length();
+ bool error;
+ while (1) // break on error or done
+ {
+ if ((error = (fwrite(kTraitCookie, sizeof(kTraitCookie), 1, stream) != 1)))
+ break;
+
+ for (uint32_t index = 0; index < numberOfTraits; index++)
+ {
+ uint32_t trait = mMessageCountsId[index];
+ if (trait == 1 || trait == 2)
+ continue; // junk traits are stored in training.dat
+ if ((error = (writeUInt32(stream, trait) != 1)))
+ break;
+ if ((error = (writeUInt32(stream, mMessageCounts[index] / shrinkFactor) != 1)))
+ break;
+ if ((error = !writeTokens(stream, shrink, trait)))
+ break;
+ }
+ break;
+ }
+ // we add a 0 at the end to represent end of trait list
+ error = writeUInt32(stream, 0) != 1;
+
+ fclose(stream);
+ if (error)
+ {
+ NS_WARNING("failed to write trait data.");
+ // delete the trait data file, since it is probably corrupt.
+ mTraitFile->Remove(false);
+ }
+
+ if (shrink)
+ {
+ // We'll clear the tokens, and read them back in from the file.
+ // Yes this is slower than in place, but this is a rare event.
+
+ if (countTokens())
+ {
+ clearTokens();
+ for (uint32_t index = 0; index < numberOfTraits; index++)
+ mMessageCounts[index] = 0;
+ }
+
+ readTrainingData();
+ }
+}
+
+void CorpusStore::readTrainingData()
+{
+
+ /*
+ * To maintain backwards compatibility, good and junk traits
+ * are stored in a file "training.dat"
+ */
+ if (!mTrainingFile)
+ return;
+
+ bool exists;
+ nsresult rv = mTrainingFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return;
+
+ FILE* stream;
+ rv = mTrainingFile->OpenANSIFileDesc("rb", &stream);
+ if (NS_FAILED(rv))
+ return;
+
+ int64_t fileSize;
+ rv = mTrainingFile->GetFileSize(&fileSize);
+ if (NS_FAILED(rv))
+ return;
+
+ // FIXME: should make sure that the tokenizers are empty.
+ char cookie[4];
+ uint32_t goodMessageCount = 0, junkMessageCount = 0;
+ if (!((fread(cookie, sizeof(cookie), 1, stream) == 1) &&
+ (memcmp(cookie, kMagicCookie, sizeof(cookie)) == 0) &&
+ (readUInt32(stream, &goodMessageCount) == 1) &&
+ (readUInt32(stream, &junkMessageCount) == 1) &&
+ readTokens(stream, fileSize, kGoodTrait, true) &&
+ readTokens(stream, fileSize, kJunkTrait, true))) {
+ NS_WARNING("failed to read training data.");
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Error, ("failed to read training data."));
+ }
+ setMessageCount(kGoodTrait, goodMessageCount);
+ setMessageCount(kJunkTrait, junkMessageCount);
+
+ fclose(stream);
+
+ /*
+ * Additional traits are stored in traits.dat
+ */
+
+ if (!mTraitFile)
+ {
+ getTraitFile(getter_AddRefs(mTraitFile));
+ if (!mTraitFile)
+ return;
+ }
+
+ rv = mTraitFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return;
+
+ rv = UpdateData(mTraitFile, true, 0, nullptr, nullptr);
+
+ if (NS_FAILED(rv))
+ {
+ NS_WARNING("failed to read training data.");
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Error, ("failed to read training data."));
+ }
+ return;
+}
+
+nsresult CorpusStore::resetTrainingData()
+{
+ // clear out our in memory training tokens...
+ if (countTokens())
+ clearTokens();
+
+ uint32_t length = mMessageCounts.Length();
+ for (uint32_t index = 0 ; index < length; index++)
+ mMessageCounts[index] = 0;
+
+ if (mTrainingFile)
+ mTrainingFile->Remove(false);
+ if (mTraitFile)
+ mTraitFile->Remove(false);
+ return NS_OK;
+}
+
+inline CorpusToken* CorpusStore::get(const char* word)
+{
+ return static_cast<CorpusToken*>(TokenHash::get(word));
+}
+
+nsresult CorpusStore::updateTrait(CorpusToken* token, uint32_t aTraitId,
+ int32_t aCountChange)
+{
+ NS_ENSURE_ARG_POINTER(token);
+ uint32_t nextLink = token->mTraitLink;
+ uint32_t lastLink = 0;
+
+ uint32_t linkCount, maxLinks = 100; //sanity check
+ for (linkCount = 0; nextLink && linkCount < maxLinks; linkCount++)
+ {
+ TraitPerToken& traitPT = mTraitStore[nextLink];
+ if (traitPT.mId == aTraitId)
+ {
+ // be careful with signed versus unsigned issues here
+ if (static_cast<int32_t>(traitPT.mCount) + aCountChange > 0)
+ traitPT.mCount += aCountChange;
+ else
+ traitPT.mCount = 0;
+ // we could delete zero count traits here, but let's not. It's rare anyway.
+ return NS_OK;
+ }
+ lastLink = nextLink;
+ nextLink = traitPT.mNextLink;
+ }
+ if (linkCount >= maxLinks)
+ return NS_ERROR_FAILURE;
+
+ // trait does not exist, so add it
+
+ if (aCountChange > 0) // don't set a negative count
+ {
+ TraitPerToken traitPT(aTraitId, aCountChange);
+ if (mTraitStore.Length() == mNextTraitIndex)
+ mTraitStore.InsertElementAt(mNextTraitIndex, traitPT);
+ else if (mTraitStore.Length() > mNextTraitIndex)
+ mTraitStore.ReplaceElementsAt(mNextTraitIndex, 1, traitPT);
+ else
+ return NS_ERROR_FAILURE;
+ if (lastLink)
+ // the token had a parent, so update it
+ mTraitStore[lastLink].mNextLink = mNextTraitIndex;
+ else
+ // need to update the token's root link
+ token->mTraitLink = mNextTraitIndex;
+ mNextTraitIndex++;
+ }
+ return NS_OK;
+}
+
+uint32_t CorpusStore::getTraitCount(CorpusToken* token, uint32_t aTraitId)
+{
+ uint32_t nextLink;
+ if (!token || !(nextLink = token->mTraitLink))
+ return 0;
+
+ uint32_t linkCount, maxLinks = 100; //sanity check
+ for (linkCount = 0; nextLink && linkCount < maxLinks; linkCount++)
+ {
+ TraitPerToken& traitPT = mTraitStore[nextLink];
+ if (traitPT.mId == aTraitId)
+ return traitPT.mCount;
+ nextLink = traitPT.mNextLink;
+ }
+ NS_ASSERTION(linkCount < maxLinks, "Corrupt trait count store");
+
+ // trait not found (or error), so count is zero
+ return 0;
+}
+
+CorpusToken* CorpusStore::add(const char* word, uint32_t aTraitId, uint32_t aCount)
+{
+ CorpusToken* token = static_cast<CorpusToken*>(TokenHash::add(word));
+ if (token) {
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug,
+ ("adding word to corpus store: %s (Trait=%d) (deltaCount=%d)",
+ word, aTraitId, aCount));
+ updateTrait(token, aTraitId, aCount);
+ }
+ return token;
+ }
+
+void CorpusStore::remove(const char* word, uint32_t aTraitId, uint32_t aCount)
+{
+ MOZ_LOG(BayesianFilterLogModule, LogLevel::Debug,
+ ("remove word: %s (TraitId=%d) (Count=%d)",
+ word, aTraitId, aCount));
+ CorpusToken* token = get(word);
+ if (token)
+ updateTrait(token, aTraitId, -static_cast<int32_t>(aCount));
+}
+
+uint32_t CorpusStore::getMessageCount(uint32_t aTraitId)
+{
+ size_t index = mMessageCountsId.IndexOf(aTraitId);
+ if (index == mMessageCountsId.NoIndex)
+ return 0;
+ return mMessageCounts.ElementAt(index);
+}
+
+void CorpusStore::setMessageCount(uint32_t aTraitId, uint32_t aCount)
+{
+ size_t index = mMessageCountsId.IndexOf(aTraitId);
+ if (index == mMessageCountsId.NoIndex)
+ {
+ mMessageCounts.AppendElement(aCount);
+ mMessageCountsId.AppendElement(aTraitId);
+ }
+ else
+ {
+ mMessageCounts[index] = aCount;
+ }
+}
+
+nsresult
+CorpusStore::UpdateData(nsIFile *aFile,
+ bool aIsAdd,
+ uint32_t aRemapCount,
+ uint32_t *aFromTraits,
+ uint32_t *aToTraits)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ if (aRemapCount)
+ {
+ NS_ENSURE_ARG_POINTER(aFromTraits);
+ NS_ENSURE_ARG_POINTER(aToTraits);
+ }
+
+ int64_t fileSize;
+ nsresult rv = aFile->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ FILE* stream;
+ rv = aFile->OpenANSIFileDesc("rb", &stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool error;
+ do // break on error or done
+ {
+ char cookie[4];
+ if ((error = (fread(cookie, sizeof(cookie), 1, stream) != 1)))
+ break;
+
+ if ((error = memcmp(cookie, kTraitCookie, sizeof(cookie))))
+ break;
+
+ uint32_t fileTrait;
+ while ( !(error = (readUInt32(stream, &fileTrait) != 1)) && fileTrait)
+ {
+ uint32_t count;
+ if ((error = (readUInt32(stream, &count) != 1)))
+ break;
+
+ uint32_t localTrait = fileTrait;
+ // remap the trait
+ for (uint32_t i = 0; i < aRemapCount; i++)
+ {
+ if (aFromTraits[i] == fileTrait)
+ localTrait = aToTraits[i];
+ }
+
+ uint32_t messageCount = getMessageCount(localTrait);
+ if (aIsAdd)
+ messageCount += count;
+ else if (count > messageCount)
+ messageCount = 0;
+ else
+ messageCount -= count;
+ setMessageCount(localTrait, messageCount);
+
+ if ((error = !readTokens(stream, fileSize, localTrait, aIsAdd)))
+ break;
+ }
+ break;
+ } while (0);
+
+ fclose(stream);
+
+ if (error)
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+nsresult CorpusStore::ClearTrait(uint32_t aTrait)
+{
+ // clear message counts
+ setMessageCount(aTrait, 0);
+
+ TokenEnumeration tokens = getTokens();
+ while (tokens.hasMoreTokens())
+ {
+ CorpusToken* token = static_cast<CorpusToken*>(tokens.nextToken());
+ int32_t wordCount = static_cast<int32_t>(getTraitCount(token, aTrait));
+ updateTrait(token, aTrait, -wordCount);
+ }
+ return NS_OK;
+}
diff --git a/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.h b/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.h
new file mode 100644
index 000000000..a1e2beaab
--- /dev/null
+++ b/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.h
@@ -0,0 +1,404 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBayesianFilter_h__
+#define nsBayesianFilter_h__
+
+#include <stdio.h>
+#include "nsCOMPtr.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsISemanticUnitScanner.h"
+#include "PLDHashTable.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsStringGlue.h"
+#include "nsWeakReference.h"
+#include "nsIObserver.h"
+
+// XXX can't simply byte align arenas, must at least 2-byte align.
+#define PL_ARENA_CONST_ALIGN_MASK 1
+#include "plarena.h"
+
+#define DEFAULT_MIN_INTERVAL_BETWEEN_WRITES 15*60*1000
+
+struct Token;
+class TokenEnumeration;
+class TokenAnalyzer;
+class nsIMsgWindow;
+class nsIMimeHeaders;
+class nsIUTF8StringEnumerator;
+struct BaseToken;
+struct CorpusToken;
+
+/**
+ * Helper class to enumerate Token objects in a PLDHashTable
+ * safely and without copying (see bugzilla #174859). The
+ * enumeration is safe to use until an Add()
+ * or Remove() is performed on the table.
+ */
+class TokenEnumeration {
+public:
+ explicit TokenEnumeration(PLDHashTable* table);
+ bool hasMoreTokens();
+ BaseToken* nextToken();
+
+private:
+ PLDHashTable::Iterator mIterator;
+};
+
+// A trait is some aspect of a message, like being junk or tagged as
+// Personal, that the statistical classifier should track. The Trait
+// structure is a per-token representation of information pertaining to
+// a message trait.
+//
+// Traits per token are maintained as a linked list.
+//
+struct TraitPerToken
+{
+ uint32_t mId; // identifying number for a trait
+ uint32_t mCount; // count of messages with this token and trait
+ uint32_t mNextLink; // index in mTraitStore for the next trait, or 0
+ // for none
+ TraitPerToken(uint32_t aId, uint32_t aCount); // inititializer
+};
+
+// An Analysis is the statistical results for a particular message, a
+// particular token, and for a particular pair of trait/antitrait, that
+// is then used in subsequent analysis to score the message.
+//
+// Analyses per token are maintained as a linked list.
+//
+struct AnalysisPerToken
+{
+ uint32_t mTraitIndex; // index representing a protrait/antitrait pair.
+ // So if we are analyzing 3 different traits, then
+ // the first trait is 0, the second 1, etc.
+ double mDistance; // absolute value of mProbability - 0.5
+ double mProbability; // relative indicator of match of trait to token
+ uint32_t mNextLink; // index in mAnalysisStore for the Analysis object
+ // for the next trait index, or 0 for none.
+ // initializer
+ AnalysisPerToken(uint32_t aTraitIndex, double aDistance, double aProbability);
+};
+
+class TokenHash {
+public:
+
+ virtual ~TokenHash();
+ /**
+ * Clears out the previous message tokens.
+ */
+ nsresult clearTokens();
+ uint32_t countTokens();
+ TokenEnumeration getTokens();
+ BaseToken* add(const char* word);
+
+protected:
+ explicit TokenHash(uint32_t entrySize);
+ PLArenaPool mWordPool;
+ uint32_t mEntrySize;
+ PLDHashTable mTokenTable;
+ char* copyWord(const char* word, uint32_t len);
+ BaseToken* get(const char* word);
+};
+
+class Tokenizer: public TokenHash {
+public:
+ Tokenizer();
+ ~Tokenizer();
+
+ Token* get(const char* word);
+
+ // The training set keeps an occurrence count on each word. This count
+ // is supposed to count the # of messsages it occurs in.
+ // When add/remove is called while tokenizing a message and NOT the training set,
+ //
+ Token* add(const char* word, uint32_t count = 1);
+
+ Token* copyTokens();
+
+ void tokenize(const char* text);
+
+ /**
+ * Creates specific tokens based on the mime headers for the message being tokenized
+ */
+ void tokenizeHeaders(nsIUTF8StringEnumerator * aHeaderNames, nsIUTF8StringEnumerator * aHeaderValues);
+
+ void tokenizeAttachment(const char * aContentType, const char * aFileName);
+
+ nsCString mBodyDelimiters; // delimiters for body tokenization
+ nsCString mHeaderDelimiters; // delimiters for header tokenization
+
+ // arrays of extra headers to tokenize / to not tokenize
+ nsTArray<nsCString> mEnabledHeaders;
+ nsTArray<nsCString> mDisabledHeaders;
+ // Delimiters used in tokenizing a particular header.
+ // Parallel array to mEnabledHeaders
+ nsTArray<nsCString> mEnabledHeadersDelimiters;
+ bool mCustomHeaderTokenization; // Are there any preference-set tokenization customizations?
+ uint32_t mMaxLengthForToken; // maximum length of a token
+ // should we convert iframe to div during tokenization?
+ bool mIframeToDiv;
+
+private:
+
+ void tokenize_ascii_word(char * word);
+ void tokenize_japanese_word(char* chunk);
+ inline void addTokenForHeader(const char * aTokenPrefix, nsACString& aValue,
+ bool aTokenizeValue = false, const char* aDelimiters = nullptr);
+ nsresult stripHTML(const nsAString& inString, nsAString& outString);
+ // helper function to escape \n, \t, etc from a CString
+ void UnescapeCString(nsCString& aCString);
+
+private:
+ nsCOMPtr<nsISemanticUnitScanner> mScanner;
+};
+
+/**
+ * Implements storage of a collection of message tokens and counts for
+ * a corpus of classified messages
+ */
+
+class CorpusStore: public TokenHash {
+public:
+ CorpusStore();
+ ~CorpusStore();
+
+ /**
+ * retrieve the token structure for a particular string
+ *
+ * @param word the character representation of the token
+ *
+ * @return token structure containing counts, null if not found
+ */
+ CorpusToken* get(const char* word);
+
+ /**
+ * add tokens to the storage, or increment counts if already exists.
+ *
+ * @param aTokenizer tokenizer for the list of tokens to remember
+ * @param aTraitId id for the trait whose counts will be remembered
+ * @param aCount number of new messages represented by the token list
+ */
+ void rememberTokens(Tokenizer& aTokenizer, uint32_t aTraitId, uint32_t aCount);
+
+ /**
+ * decrement counts for tokens in the storage, removing if all counts
+ * are zero
+ *
+ * @param aTokenizer tokenizer for the list of tokens to forget
+ * @param aTraitId id for the trait whose counts will be removed
+ * @param aCount number of messages represented by the token list
+ */
+ void forgetTokens(Tokenizer& aTokenizer, uint32_t aTraitId, uint32_t aCount);
+
+ /**
+ * write the corpus information to file storage
+ *
+ * @param aMaximumTokenCount prune tokens if number of tokens exceeds
+ * this value. == 0 for no pruning
+ */
+ void writeTrainingData(uint32_t aMaximumTokenCount);
+
+ /**
+ * read the corpus information from file storage
+ */
+ void readTrainingData();
+
+ /**
+ * delete the local corpus storage file and data
+ */
+ nsresult resetTrainingData();
+
+ /**
+ * get the count of messages whose tokens are stored that are associated
+ * with a trait
+ *
+ * @param aTraitId identifier for the trait
+ * @return number of messages for that trait
+ */
+ uint32_t getMessageCount(uint32_t aTraitId);
+
+ /**
+ * set the count of messages whose tokens are stored that are associated
+ * with a trait
+ *
+ * @param aTraitId identifier for the trait
+ * @param aCount number of messages for that trait
+ */
+ void setMessageCount(uint32_t aTraitId, uint32_t aCount);
+
+ /**
+ * get the count of messages associated with a particular token and trait
+ *
+ * @param token the token string and associated counts
+ * @param aTraitId identifier for the trait
+ */
+ uint32_t getTraitCount(CorpusToken *token, uint32_t aTraitId);
+
+ /**
+ * Add (or remove) data from a particular file to the corpus data.
+ *
+ * @param aFile the file with the data, in the format:
+ *
+ * Format of the trait file for version 1:
+ * [0xFCA93601] (the 01 is the version)
+ * for each trait to write:
+ * [id of trait to write] (0 means end of list)
+ * [number of messages per trait]
+ * for each token with non-zero count
+ * [count]
+ * [length of word]word
+ *
+ * @param aIsAdd should the data be added, or removed? true if adding,
+ * else removing.
+ *
+ * @param aRemapCount number of items in the parallel arrays aFromTraits,
+ * aToTraits. These arrays allow conversion of the
+ * trait id stored in the file (which may be originated
+ * externally) to the trait id used in the local corpus
+ * (which is defined locally using nsIMsgTraitService).
+ *
+ * @param aFromTraits array of trait ids used in aFile. If aFile contains
+ * trait ids that are not in this array, they are not
+ * remapped, but assummed to be local trait ids.
+ *
+ * @param aToTraits array of trait ids, corresponding to elements of
+ * aFromTraits, that represent the local trait ids to be
+ * used in storing data from aFile into the local corpus.
+ *
+ */
+ nsresult UpdateData(nsIFile *aFile, bool aIsAdd,
+ uint32_t aRemapCount, uint32_t *aFromTraits,
+ uint32_t *aToTraits);
+
+ /**
+ * remove all counts (message and tokens) for a trait id
+ *
+ * @param aTrait trait id for the trait to remove
+ */
+ nsresult ClearTrait(uint32_t aTrait);
+
+protected:
+
+ /**
+ * return the local corpus storage file for junk traits
+ */
+ nsresult getTrainingFile(nsIFile ** aFile);
+
+ /**
+ * return the local corpus storage file for non-junk traits
+ */
+ nsresult getTraitFile(nsIFile ** aFile);
+
+ /**
+ * read token strings from the data file
+ *
+ * @param stream file stream with token data
+ * @param fileSize file size
+ * @param aTraitId id for the trait whose counts will be read
+ * @param aIsAdd true to add the counts, false to remove them
+ *
+ * @return true if successful, false if error
+ */
+ bool readTokens(FILE* stream, int64_t fileSize, uint32_t aTraitId,
+ bool aIsAdd);
+
+ /**
+ * write token strings to the data file
+ */
+ bool writeTokens(FILE* stream, bool shrink, uint32_t aTraitId);
+
+ /**
+ * remove counts for a token string
+ */
+ void remove(const char* word, uint32_t aTraitId, uint32_t aCount);
+
+ /**
+ * add counts for a token string, adding the token string if new
+ */
+ CorpusToken* add(const char* word, uint32_t aTraitId, uint32_t aCount);
+
+ /**
+ * change counts in a trait in the traits array, adding the trait if needed
+ */
+ nsresult updateTrait(CorpusToken* token, uint32_t aTraitId,
+ int32_t aCountChange);
+ nsCOMPtr<nsIFile> mTrainingFile; // file used to store junk training data
+ nsCOMPtr<nsIFile> mTraitFile; // file used to store non-junk
+ // training data
+ nsTArray<TraitPerToken> mTraitStore; // memory for linked-list of counts
+ uint32_t mNextTraitIndex; // index in mTraitStore to first empty
+ // TraitPerToken
+ nsTArray<uint32_t> mMessageCounts; // count of messages per trait
+ // represented in the store
+ nsTArray<uint32_t> mMessageCountsId; // Parallel array to mMessageCounts, with
+ // the corresponding trait ID
+};
+
+class nsBayesianFilter : public nsIJunkMailPlugin, nsIMsgCorpus,
+ nsIObserver, nsSupportsWeakReference {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGFILTERPLUGIN
+ NS_DECL_NSIJUNKMAILPLUGIN
+ NS_DECL_NSIMSGCORPUS
+ NS_DECL_NSIOBSERVER
+
+ nsBayesianFilter();
+
+ nsresult Init();
+
+ nsresult tokenizeMessage(const char* messageURI, nsIMsgWindow *aMsgWindow, TokenAnalyzer* analyzer);
+ void classifyMessage(Tokenizer& tokens, const char* messageURI,
+ nsIJunkMailClassificationListener* listener);
+
+ void classifyMessage(
+ Tokenizer& tokenizer,
+ const char* messageURI,
+ nsTArray<uint32_t>& aProTraits,
+ nsTArray<uint32_t>& aAntiTraits,
+ nsIJunkMailClassificationListener* listener,
+ nsIMsgTraitClassificationListener* aTraitListener,
+ nsIMsgTraitDetailListener* aDetailListener);
+
+ void observeMessage(Tokenizer& tokens, const char* messageURI,
+ nsTArray<uint32_t>& oldClassifications,
+ nsTArray<uint32_t>& newClassifications,
+ nsIJunkMailClassificationListener* listener,
+ nsIMsgTraitClassificationListener* aTraitListener);
+
+
+protected:
+ virtual ~nsBayesianFilter();
+
+ static void TimerCallback(nsITimer* aTimer, void* aClosure);
+
+ CorpusStore mCorpus;
+ double mJunkProbabilityThreshold;
+ int32_t mMaximumTokenCount;
+ bool mTrainingDataDirty;
+ int32_t mMinFlushInterval; // in milliseconds, must be positive
+ //and not too close to 0
+ nsCOMPtr<nsITimer> mTimer;
+
+ // index in mAnalysisStore for first empty AnalysisPerToken
+ uint32_t mNextAnalysisIndex;
+ // memory for linked list of AnalysisPerToken objects
+ nsTArray<AnalysisPerToken> mAnalysisStore;
+ /**
+ * Determine the location in mAnalysisStore where the AnalysisPerToken
+ * object for a particular token and trait is stored
+ */
+ uint32_t getAnalysisIndex(Token& token, uint32_t aTraitIndex);
+ /**
+ * Set the value of the AnalysisPerToken object for a particular
+ * token and trait
+ */
+ nsresult setAnalysis(Token& token, uint32_t aTraitIndex,
+ double aDistance, double aProbability);
+};
+
+#endif // _nsBayesianFilter_h__
diff --git a/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilterCID.h b/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilterCID.h
new file mode 100644
index 000000000..b1a54a1fc
--- /dev/null
+++ b/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilterCID.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBayesianFilterCID_h__
+#define nsBayesianFilterCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+#include "nsIMsgMdnGenerator.h"
+
+#define NS_BAYESIANFILTER_CONTRACTID \
+ "@mozilla.org/messenger/filter-plugin;1?name=bayesianfilter"
+#define NS_BAYESIANFILTER_CID \
+{ /* F1070BFA-D539-11D6-90CA-00039310A47A */ \
+ 0xF1070BFA, 0xD539, 0x11D6, \
+ { 0x90, 0xCA, 0x00, 0x03, 0x93, 0x10, 0xA4, 0x7A }}
+
+#endif /* nsBayesianFilterCID_h__ */
diff --git a/mailnews/extensions/bayesian-spam-filter/src/nsIncompleteGamma.h b/mailnews/extensions/bayesian-spam-filter/src/nsIncompleteGamma.h
new file mode 100644
index 000000000..1f7388b16
--- /dev/null
+++ b/mailnews/extensions/bayesian-spam-filter/src/nsIncompleteGamma.h
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIncompleteGamma_h__
+#define nsIncompleteGamma_h__
+
+/* An implementation of the incomplete gamma functions for real
+ arguments. P is defined as
+
+ x
+ /
+ 1 [ a - 1 - t
+ P(a, x) = -------- I t e dt
+ Gamma(a) ]
+ /
+ 0
+
+ and
+
+ infinity
+ /
+ 1 [ a - 1 - t
+ Q(a, x) = -------- I t e dt
+ Gamma(a) ]
+ /
+ x
+
+ so that P(a,x) + Q(a,x) = 1.
+
+ Both a series expansion and a continued fraction exist. This
+ implementation uses the more efficient method based on the arguments.
+
+ Either case involves calculating a multiplicative term:
+ e^(-x)*x^a/Gamma(a).
+ Here we calculate the log of this term. Most math libraries have a
+ "lgamma" function but it is not re-entrant. Some libraries have a
+ "lgamma_r" which is re-entrant. Use it if possible. I have included a
+ simple replacement but it is certainly not as accurate.
+
+ Relative errors are almost always < 1e-10 and usually < 1e-14. Very
+ small and very large arguments cause trouble.
+
+ The region where a < 0.5 and x < 0.5 has poor error properties and is
+ not too stable. Get a better routine if you need results in this
+ region.
+
+ The error argument will be set negative if there is a domain error or
+ positive for an internal calculation error, currently lack of
+ convergence. A value is always returned, though.
+
+ */
+
+#include <math.h>
+#include <float.h>
+
+// the main routine
+static double nsIncompleteGammaP (double a, double x, int *error);
+
+// nsLnGamma(z): either a wrapper around lgamma_r or the internal function.
+// C_m = B[2*m]/(2*m*(2*m-1)) where B is a Bernoulli number
+static const double C_1 = 1.0 / 12.0;
+static const double C_2 = -1.0 / 360.0;
+static const double C_3 = 1.0 / 1260.0;
+static const double C_4 = -1.0 / 1680.0;
+static const double C_5 = 1.0 / 1188.0;
+static const double C_6 = -691.0 / 360360.0;
+static const double C_7 = 1.0 / 156.0;
+static const double C_8 = -3617.0 / 122400.0;
+static const double C_9 = 43867.0 / 244188.0;
+static const double C_10 = -174611.0 / 125400.0;
+static const double C_11 = 77683.0 / 5796.0;
+
+// truncated asymptotic series in 1/z
+static inline double lngamma_asymp (double z)
+{
+ double w, w2, sum;
+ w = 1.0 / z;
+ w2 = w * w;
+ sum = w * (w2 * (w2 * (w2 * (w2 * (w2 * (w2 * (w2 * (w2 * (w2
+ * (C_11 * w2 + C_10) + C_9) + C_8) + C_7) + C_6)
+ + C_5) + C_4) + C_3) + C_2) + C_1);
+
+ return sum;
+}
+
+struct fact_table_s
+{
+ double fact;
+ double lnfact;
+};
+
+// for speed and accuracy
+static const struct fact_table_s FactTable[] = {
+ {1.000000000000000, 0.0000000000000000000000e+00},
+ {1.000000000000000, 0.0000000000000000000000e+00},
+ {2.000000000000000, 6.9314718055994530942869e-01},
+ {6.000000000000000, 1.7917594692280550007892e+00},
+ {24.00000000000000, 3.1780538303479456197550e+00},
+ {120.0000000000000, 4.7874917427820459941458e+00},
+ {720.0000000000000, 6.5792512120101009952602e+00},
+ {5040.000000000000, 8.5251613610654142999881e+00},
+ {40320.00000000000, 1.0604602902745250228925e+01},
+ {362880.0000000000, 1.2801827480081469610995e+01},
+ {3628800.000000000, 1.5104412573075515295248e+01},
+ {39916800.00000000, 1.7502307845873885839769e+01},
+ {479001600.0000000, 1.9987214495661886149228e+01},
+ {6227020800.000000, 2.2552163853123422886104e+01},
+ {87178291200.00000, 2.5191221182738681499610e+01},
+ {1307674368000.000, 2.7899271383840891566988e+01},
+ {20922789888000.00, 3.0671860106080672803835e+01},
+ {355687428096000.0, 3.3505073450136888885825e+01},
+ {6402373705728000., 3.6395445208033053576674e+01}
+};
+#define FactTableLength (int)(sizeof(FactTable)/sizeof(FactTable[0]))
+
+// for speed
+static const double ln_2pi_2 = 0.918938533204672741803; // log(2*PI)/2
+
+/* A simple lgamma function, not very robust.
+
+ Valid for z_in > 0 ONLY.
+
+ For z_in > 8 precision is quite good, relative errors < 1e-14 and
+ usually better. For z_in < 8 relative errors increase but are usually
+ < 1e-10. In two small regions, 1 +/- .001 and 2 +/- .001 errors
+ increase quickly.
+*/
+static double nsLnGamma (double z_in, int *gsign)
+{
+ double scale, z, sum, result;
+ *gsign = 1;
+
+ int zi = (int) z_in;
+ if (z_in == (double) zi)
+ {
+ if (0 < zi && zi <= FactTableLength)
+ return FactTable[zi - 1].lnfact; // gamma(z) = (z-1)!
+ }
+
+ for (scale = 1.0, z = z_in; z < 8.0; ++z)
+ scale *= z;
+
+ sum = lngamma_asymp (z);
+ result = (z - 0.5) * log (z) - z + ln_2pi_2 - log (scale);
+ result += sum;
+ return result;
+}
+
+// log( e^(-x)*x^a/Gamma(a) )
+static inline double lnPQfactor (double a, double x)
+{
+ int gsign; // ignored because a > 0
+ return a * log (x) - x - nsLnGamma (a, &gsign);
+}
+
+static double Pseries (double a, double x, int *error)
+{
+ double sum, term;
+ const double eps = 2.0 * DBL_EPSILON;
+ const int imax = 5000;
+ int i;
+
+ sum = term = 1.0 / a;
+ for (i = 1; i < imax; ++i)
+ {
+ term *= x / (a + i);
+ sum += term;
+ if (fabs (term) < eps * fabs (sum))
+ break;
+ }
+
+ if (i >= imax)
+ *error = 1;
+
+ return sum;
+}
+
+static double Qcontfrac (double a, double x, int *error)
+{
+ double result, D, C, e, f, term;
+ const double eps = 2.0 * DBL_EPSILON;
+ const double small =
+ DBL_EPSILON * DBL_EPSILON * DBL_EPSILON * DBL_EPSILON;
+ const int imax = 5000;
+ int i;
+
+ // modified Lentz method
+ f = x - a + 1.0;
+ if (fabs (f) < small)
+ f = small;
+ C = f + 1.0 / small;
+ D = 1.0 / f;
+ result = D;
+ for (i = 1; i < imax; ++i)
+ {
+ e = i * (a - i);
+ f += 2.0;
+ D = f + e * D;
+ if (fabs (D) < small)
+ D = small;
+ D = 1.0 / D;
+ C = f + e / C;
+ if (fabs (C) < small)
+ C = small;
+ term = C * D;
+ result *= term;
+ if (fabs (term - 1.0) < eps)
+ break;
+ }
+
+ if (i >= imax)
+ *error = 1;
+ return result;
+}
+
+static double nsIncompleteGammaP (double a, double x, int *error)
+{
+ double result, dom, ldom;
+ // domain errors. the return values are meaningless but have
+ // to return something.
+ *error = -1;
+ if (a <= 0.0)
+ return 1.0;
+ if (x < 0.0)
+ return 0.0;
+ *error = 0;
+ if (x == 0.0)
+ return 0.0;
+
+ ldom = lnPQfactor (a, x);
+ dom = exp (ldom);
+ // might need to adjust the crossover point
+ if (a <= 0.5)
+ {
+ if (x < a + 1.0)
+ result = dom * Pseries (a, x, error);
+ else
+ result = 1.0 - dom * Qcontfrac (a, x, error);
+ }
+ else
+ {
+ if (x < a)
+ result = dom * Pseries (a, x, error);
+ else
+ result = 1.0 - dom * Qcontfrac (a, x, error);
+ }
+
+ // not clear if this can ever happen
+ if (result > 1.0)
+ result = 1.0;
+ if (result < 0.0)
+ result = 0.0;
+ return result;
+}
+
+#endif
+
diff --git a/mailnews/extensions/dsn/content/am-dsn.js b/mailnews/extensions/dsn/content/am-dsn.js
new file mode 100644
index 000000000..2c8a5f923
--- /dev/null
+++ b/mailnews/extensions/dsn/content/am-dsn.js
@@ -0,0 +1,36 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var useCustomPrefs;
+var requestAlways;
+var gIdentity;
+
+function onInit()
+{
+ useCustomPrefs = document.getElementById("identity.dsn_use_custom_prefs");
+ requestAlways = document.getElementById("identity.dsn_always_request_on");
+
+ EnableDisableCustomSettings();
+
+ return true;
+}
+
+function onSave()
+{
+}
+
+function EnableDisableCustomSettings() {
+ if (useCustomPrefs && (useCustomPrefs.getAttribute("value") == "false"))
+ requestAlways.setAttribute("disabled", "true");
+ else
+ requestAlways.removeAttribute("disabled");
+
+ return true;
+}
+
+function onPreInit(account, accountValues)
+{
+ gIdentity = account.defaultIdentity;
+}
diff --git a/mailnews/extensions/dsn/content/am-dsn.xul b/mailnews/extensions/dsn/content/am-dsn.xul
new file mode 100644
index 000000000..1327021ca
--- /dev/null
+++ b/mailnews/extensions/dsn/content/am-dsn.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+
+<!--
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-dsn.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="parent.onPanelLoaded('am-dsn.xul');">
+
+ <stringbundle id="bundle_smime" src="chrome://messenger/locale/am-dsn.properties"/>
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-dsn.js"/>
+
+ <dialogheader title="&pane.title;"/>
+
+ <groupbox>
+
+ <caption label="&pane.title;"/>
+
+ <hbox id="prefChoices" align="center">
+ <radiogroup id="identity.dsn_use_custom_prefs"
+ wsm_persist="true"
+ genericattr="true"
+ preftype="bool"
+ prefstring="mail.identity.%identitykey%.dsn_use_custom_prefs"
+ oncommand="EnableDisableCustomSettings();">
+
+ <radio id="identity.select_global_prefs"
+ value="false"
+ label="&useGlobalPrefs.label;"
+ accesskey="&useGlobalPrefs.accesskey;"/>
+
+ <radio id="identity.select_custom_prefs"
+ value="true"
+ label="&useCustomPrefs.label;"
+ accesskey="&useCustomPrefs.accesskey;"/>
+ </radiogroup>
+ </hbox>
+
+ <vbox id="dsnSettings" class="indent" align="start">
+ <checkbox id="identity.dsn_always_request_on"
+ label="&requestAlways.label;"
+ accesskey="&requestAlways.accesskey;"
+ wsm_persist="true"
+ genericattr="true"
+ iscontrolcontainer="true"
+ preftype="bool"
+ prefstring="mail.identity.%identitykey%.dsn_always_request_on"/>
+ </vbox>
+ </groupbox>
+</page>
diff --git a/mailnews/extensions/dsn/content/dsn.js b/mailnews/extensions/dsn/content/dsn.js
new file mode 100644
index 000000000..043aa1b94
--- /dev/null
+++ b/mailnews/extensions/dsn/content/dsn.js
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * default prefs for dsn
+ */
+pref("mail.identity.default.dsn_use_custom_prefs", false); // false: Use global true: Use custom
+pref("mail.identity.default.dsn_always_request_on", false);
diff --git a/mailnews/extensions/dsn/jar.mn b/mailnews/extensions/dsn/jar.mn
new file mode 100644
index 000000000..2ecdd7057
--- /dev/null
+++ b/mailnews/extensions/dsn/jar.mn
@@ -0,0 +1,9 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifdef MOZ_SUITE
+messenger.jar:
+ content/messenger/am-dsn.xul (content/am-dsn.xul)
+ content/messenger/am-dsn.js (content/am-dsn.js)
+#endif
diff --git a/mailnews/extensions/dsn/moz.build b/mailnews/extensions/dsn/moz.build
new file mode 100644
index 000000000..10cc8cb56
--- /dev/null
+++ b/mailnews/extensions/dsn/moz.build
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXTRA_COMPONENTS += [
+ 'src/dsn-service.js',
+ 'src/dsn-service.manifest',
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+JS_PREFERENCE_FILES += [
+ 'content/dsn.js',
+] \ No newline at end of file
diff --git a/mailnews/extensions/dsn/src/dsn-service.js b/mailnews/extensions/dsn/src/dsn-service.js
new file mode 100644
index 000000000..76ceeb04b
--- /dev/null
+++ b/mailnews/extensions/dsn/src/dsn-service.js
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function DSNService() {}
+
+DSNService.prototype = {
+ name: "dsn",
+ chromePackageName: "messenger",
+ showPanel: function(server) {
+ // don't show the panel for news, rss, or local accounts
+ return (server.type != "nntp" && server.type != "rss" &&
+ server.type != "none");
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgAccountManagerExtension]),
+ classID: Components.ID("{849dab91-9bc9-4508-a0ee-c2453e7c092d}"),
+};
+
+var components = [DSNService];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/extensions/dsn/src/dsn-service.manifest b/mailnews/extensions/dsn/src/dsn-service.manifest
new file mode 100644
index 000000000..2853fb3e8
--- /dev/null
+++ b/mailnews/extensions/dsn/src/dsn-service.manifest
@@ -0,0 +1,3 @@
+component {849dab91-9bc9-4508-a0ee-c2453e7c092d} dsn-service.js
+contract @mozilla.org/accountmanager/extension;1?name=dsn {849dab91-9bc9-4508-a0ee-c2453e7c092d}
+category mailnews-accountmanager-extensions dsn-account-manager-extension @mozilla.org/accountmanager/extension;1?name=dsn
diff --git a/mailnews/extensions/fts3/data/README b/mailnews/extensions/fts3/data/README
new file mode 100644
index 000000000..d7c13abbb
--- /dev/null
+++ b/mailnews/extensions/fts3/data/README
@@ -0,0 +1,5 @@
+The data files in this directory come from the ICU project:
+http://bugs.icu-project.org/trac/browser/icu/trunk/source/data/unidata/norm2
+
+They are intended to be consumed by the ICU project's gennorm2 script. We have
+our own script that processes them.
diff --git a/mailnews/extensions/fts3/data/generate_table.py b/mailnews/extensions/fts3/data/generate_table.py
new file mode 100644
index 000000000..f6b012685
--- /dev/null
+++ b/mailnews/extensions/fts3/data/generate_table.py
@@ -0,0 +1,264 @@
+#!/usr/bin/python
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Mozilla Thunderbird.
+#
+# The Initial Developer of the Original Code is Mozilla Japan.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Makoto Kato <m_kato@ga2.so-net.ne.jp>
+# Andrew Sutherland <asutherland@asutherland.org>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+import re
+
+def printTable(f, t):
+ i = f
+ while i <= t:
+ c = array[i]
+ print "0x%04x," % c,
+ i = i + 1
+ if not i % 8:
+ print "\n\t",
+
+print '''/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Japan.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Makoto Kato <m_kato@ga2.so-net.ne.jp>
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* THIS FILE IS GENERATED BY generate_table.py. DON'T EDIT THIS */
+'''
+
+p = re.compile('([0-9A-F]{4,5})(?:\.\.([0-9A-F]{4,5}))?[=\>]([0-9A-F]{4,5})?')
+G_FROM = 1
+G_TO = 2
+G_FIRSTVAL = 3
+
+# Array whose value at index i is the unicode value unicode character i should
+# map to.
+array = []
+# Contents of gNormalizeTable. We insert zero entries for sub-pages where we
+# have no mappings. We insert references to the tables where we do have
+# such tables.
+globalTable = []
+globalTable.append("0")
+# The (exclusive) upper bound of the conversion table, unicode character-wise.
+# This is 0x10000 because our generated table is only 16-bit. This also limits
+# the values we can map to; we perform an identity mapping for target values
+# that >= maxmapping.
+maxmapping = 0x10000
+sizePerTable = 64
+
+# Map characters that the mapping tells us to obliterate to the NUKE_CHAR
+# (such lines look like "FFF0..FFF8>")
+# We do this because if we didn't do this, we would emit these characters as
+# part of a token, which we definitely don't want.
+NUKE_CHAR = 0x20
+
+# --- load case folding table
+# entries in the file look like:
+# 0041>0061
+# 02D8>0020 0306
+# 2000..200A>0020
+#
+# The 0041 (uppercase A) tells us it lowercases to 0061 (lowercase a).
+# The 02D8 is a "spacing clone[s] of diacritic" breve which gets decomposed into
+# a space character and a breve. This entry/type of entry also shows up in
+# 'nfkc.txt'.
+# The 2000..200A covers a range of space characters and maps them down to the
+# 'normal' space character.
+
+file = open('nfkc_cf.txt')
+
+m = None
+line = "\n"
+i = 0x0
+while i < maxmapping and line:
+ if not m:
+ line = file.readline()
+ m = p.match(line)
+ if not m:
+ continue
+ low = int(m.group(G_FROM), 16)
+ # if G_TO is present, use it, otherwise fallback to low
+ high = m.group(G_TO) and int(m.group(G_TO), 16) or low
+ # if G_FIRSTVAL is present use it, otherwise use NUKE_CHAR
+ val = (m.group(G_FIRSTVAL) and int(m.group(G_FIRSTVAL), 16)
+ or NUKE_CHAR)
+ continue
+
+
+ if i >= low and i <= high:
+ if val >= maxmapping:
+ array.append(i)
+ else:
+ array.append(val)
+ if i == high:
+ m = None
+ else:
+ array.append(i)
+ i = i + 1
+file.close()
+
+# --- load normalization / decomposition table
+# It is important that this file gets processed second because the other table
+# will tell us about mappings from uppercase U with diaeresis to lowercase u
+# with diaeresis. We obviously don't want that clobbering our value. (Although
+# this would work out if we propagated backwards rather than forwards...)
+#
+# - entries in this file that we care about look like:
+# 00A0>0020
+# 0100=0041 0304
+#
+# They are found in the "Canonical and compatibility decomposition mappings"
+# section.
+#
+# The 00A0 is mapping NBSP to the normal space character.
+# The 0100 (a capital A with a bar over top of) is equivalent to 0041 (capital
+# A) plus a 0304 (combining overline). We do not care about the combining
+# marks which is why our regular expression does not capture it.
+#
+#
+# - entries that we do not care about look like:
+# 0300..0314:230
+#
+# These map marks to their canonical combining class which appears to be a way
+# of specifying the precedence / order in which marks should be combined. The
+# key thing is we don't care about them.
+file = open('nfkc.txt')
+line = file.readline()
+m = p.match(line)
+while line:
+ if not m:
+ line = file.readline()
+ m = p.match(line)
+ continue
+
+ low = int(m.group(G_FROM), 16)
+ # if G_TO is present, use it, otherwise fallback to low
+ high = m.group(G_TO) and int(m.group(G_TO), 16) or low
+ # if G_FIRSTVAL is present use it, otherwise fall back to NUKE_CHAR
+ val = m.group(G_FIRSTVAL) and int(m.group(G_FIRSTVAL), 16) or NUKE_CHAR
+ for i in range(low, high+1):
+ if i < maxmapping and val < maxmapping:
+ array[i] = val
+ m = None
+file.close()
+
+# --- generate a normalized table to support case and accent folding
+
+i = 0
+needTerm = False;
+while i < maxmapping:
+ if not i % sizePerTable:
+ # table is empty?
+ j = i
+ while j < i + sizePerTable:
+ if array[j] != j:
+ break
+ j += 1
+
+ if j == i + sizePerTable:
+ if i:
+ globalTable.append("0")
+ i += sizePerTable
+ continue
+
+ if needTerm:
+ print "};\n"
+ globalTable.append("gNormalizeTable%04x" % i)
+ print "static const unsigned short gNormalizeTable%04x[] = {\n\t" % i,
+ print "/* U+%04x */\n\t" % i,
+ needTerm = True
+ # Decomposition does not case-fold, so we want to compensate by
+ # performing a lookup here. Because decomposition chains can be
+ # example: 01d5, a capital U with a diaeresis and a bar. yes, really.
+ # 01d5 -> 00dc -> 0055 (U) -> 0075 (u)
+ c = array[i]
+ while c != array[c]:
+ c = array[c]
+ if c >= 0x41 and c <= 0x5a:
+ raise Exception('got an uppercase character somehow: %x => %x'
+ % (i, c))
+ print "0x%04x," % c,
+ i = i + 1
+ if not i % 8:
+ print "\n\t",
+
+print "};\n\nstatic const unsigned short* gNormalizeTable[] = {",
+i = 0
+while i < (maxmapping / sizePerTable):
+ if not i % 4:
+ print "\n\t",
+ print globalTable[i] + ",",
+ i += 1
+
+print '''
+};
+
+unsigned int normalize_character(const unsigned int c)
+{
+ if (c >= ''' + ('0x%x' % (maxmapping,)) + ''' || !gNormalizeTable[c >> 6])
+ return c;
+ return gNormalizeTable[c >> 6][c & 0x3f];
+}
+'''
diff --git a/mailnews/extensions/fts3/data/nfkc.txt b/mailnews/extensions/fts3/data/nfkc.txt
new file mode 100644
index 000000000..08aaf353f
--- /dev/null
+++ b/mailnews/extensions/fts3/data/nfkc.txt
@@ -0,0 +1,5786 @@
+# Copyright (C) 1999-2010, International Business Machines
+# Corporation and others. All Rights Reserved.
+#
+# file name: nfkc.txt
+#
+# machine-generated on: 2009-11-30
+#
+
+# Canonical_Combining_Class (ccc) values
+0300..0314:230
+0315:232
+0316..0319:220
+031A:232
+031B:216
+031C..0320:220
+0321..0322:202
+0323..0326:220
+0327..0328:202
+0329..0333:220
+0334..0338:1
+0339..033C:220
+033D..0344:230
+0345:240
+0346:230
+0347..0349:220
+034A..034C:230
+034D..034E:220
+0350..0352:230
+0353..0356:220
+0357:230
+0358:232
+0359..035A:220
+035B:230
+035C:233
+035D..035E:234
+035F:233
+0360..0361:234
+0362:233
+0363..036F:230
+0483..0487:230
+0591:220
+0592..0595:230
+0596:220
+0597..0599:230
+059A:222
+059B:220
+059C..05A1:230
+05A2..05A7:220
+05A8..05A9:230
+05AA:220
+05AB..05AC:230
+05AD:222
+05AE:228
+05AF:230
+05B0:10
+05B1:11
+05B2:12
+05B3:13
+05B4:14
+05B5:15
+05B6:16
+05B7:17
+05B8:18
+05B9..05BA:19
+05BB:20
+05BC:21
+05BD:22
+05BF:23
+05C1:24
+05C2:25
+05C4:230
+05C5:220
+05C7:18
+0610..0617:230
+0618:30
+0619:31
+061A:32
+064B:27
+064C:28
+064D:29
+064E:30
+064F:31
+0650:32
+0651:33
+0652:34
+0653..0654:230
+0655..0656:220
+0657..065B:230
+065C:220
+065D..065E:230
+0670:35
+06D6..06DC:230
+06DF..06E2:230
+06E3:220
+06E4:230
+06E7..06E8:230
+06EA:220
+06EB..06EC:230
+06ED:220
+0711:36
+0730:230
+0731:220
+0732..0733:230
+0734:220
+0735..0736:230
+0737..0739:220
+073A:230
+073B..073C:220
+073D:230
+073E:220
+073F..0741:230
+0742:220
+0743:230
+0744:220
+0745:230
+0746:220
+0747:230
+0748:220
+0749..074A:230
+07EB..07F1:230
+07F2:220
+07F3:230
+0816..0819:230
+081B..0823:230
+0825..0827:230
+0829..082D:230
+093C:7
+094D:9
+0951:230
+0952:220
+0953..0954:230
+09BC:7
+09CD:9
+0A3C:7
+0A4D:9
+0ABC:7
+0ACD:9
+0B3C:7
+0B4D:9
+0BCD:9
+0C4D:9
+0C55:84
+0C56:91
+0CBC:7
+0CCD:9
+0D4D:9
+0DCA:9
+0E38..0E39:103
+0E3A:9
+0E48..0E4B:107
+0EB8..0EB9:118
+0EC8..0ECB:122
+0F18..0F19:220
+0F35:220
+0F37:220
+0F39:216
+0F71:129
+0F72:130
+0F74:132
+0F7A..0F7D:130
+0F80:130
+0F82..0F83:230
+0F84:9
+0F86..0F87:230
+0FC6:220
+1037:7
+1039..103A:9
+108D:220
+135F:230
+1714:9
+1734:9
+17D2:9
+17DD:230
+18A9:228
+1939:222
+193A:230
+193B:220
+1A17:230
+1A18:220
+1A60:9
+1A75..1A7C:230
+1A7F:220
+1B34:7
+1B44:9
+1B6B:230
+1B6C:220
+1B6D..1B73:230
+1BAA:9
+1C37:7
+1CD0..1CD2:230
+1CD4:1
+1CD5..1CD9:220
+1CDA..1CDB:230
+1CDC..1CDF:220
+1CE0:230
+1CE2..1CE8:1
+1CED:220
+1DC0..1DC1:230
+1DC2:220
+1DC3..1DC9:230
+1DCA:220
+1DCB..1DCC:230
+1DCD:234
+1DCE:214
+1DCF:220
+1DD0:202
+1DD1..1DE6:230
+1DFD:220
+1DFE:230
+1DFF:220
+20D0..20D1:230
+20D2..20D3:1
+20D4..20D7:230
+20D8..20DA:1
+20DB..20DC:230
+20E1:230
+20E5..20E6:1
+20E7:230
+20E8:220
+20E9:230
+20EA..20EB:1
+20EC..20EF:220
+20F0:230
+2CEF..2CF1:230
+2DE0..2DFF:230
+302A:218
+302B:228
+302C:232
+302D:222
+302E..302F:224
+3099..309A:8
+A66F:230
+A67C..A67D:230
+A6F0..A6F1:230
+A806:9
+A8C4:9
+A8E0..A8F1:230
+A92B..A92D:220
+A953:9
+A9B3:7
+A9C0:9
+AAB0:230
+AAB2..AAB3:230
+AAB4:220
+AAB7..AAB8:230
+AABE..AABF:230
+AAC1:230
+ABED:9
+FB1E:26
+FE20..FE26:230
+101FD:220
+10A0D:220
+10A0F:230
+10A38:230
+10A39:1
+10A3A:220
+10A3F:9
+110B9:9
+110BA:7
+1D165..1D166:216
+1D167..1D169:1
+1D16D:226
+1D16E..1D172:216
+1D17B..1D182:220
+1D185..1D189:230
+1D18A..1D18B:220
+1D1AA..1D1AD:230
+1D242..1D244:230
+
+# Canonical and compatibility decomposition mappings
+00A0>0020
+00A8>0020 0308
+00AA>0061
+00AF>0020 0304
+00B2>0032
+00B3>0033
+00B4>0020 0301
+00B5>03BC
+00B8>0020 0327
+00B9>0031
+00BA>006F
+00BC>0031 2044 0034
+00BD>0031 2044 0032
+00BE>0033 2044 0034
+00C0=0041 0300
+00C1=0041 0301
+00C2=0041 0302
+00C3=0041 0303
+00C4=0041 0308
+00C5=0041 030A
+00C7=0043 0327
+00C8=0045 0300
+00C9=0045 0301
+00CA=0045 0302
+00CB=0045 0308
+00CC=0049 0300
+00CD=0049 0301
+00CE=0049 0302
+00CF=0049 0308
+00D1=004E 0303
+00D2=004F 0300
+00D3=004F 0301
+00D4=004F 0302
+00D5=004F 0303
+00D6=004F 0308
+00D9=0055 0300
+00DA=0055 0301
+00DB=0055 0302
+00DC=0055 0308
+00DD=0059 0301
+00E0=0061 0300
+00E1=0061 0301
+00E2=0061 0302
+00E3=0061 0303
+00E4=0061 0308
+00E5=0061 030A
+00E7=0063 0327
+00E8=0065 0300
+00E9=0065 0301
+00EA=0065 0302
+00EB=0065 0308
+00EC=0069 0300
+00ED=0069 0301
+00EE=0069 0302
+00EF=0069 0308
+00F1=006E 0303
+00F2=006F 0300
+00F3=006F 0301
+00F4=006F 0302
+00F5=006F 0303
+00F6=006F 0308
+00F9=0075 0300
+00FA=0075 0301
+00FB=0075 0302
+00FC=0075 0308
+00FD=0079 0301
+00FF=0079 0308
+0100=0041 0304
+0101=0061 0304
+0102=0041 0306
+0103=0061 0306
+0104=0041 0328
+0105=0061 0328
+0106=0043 0301
+0107=0063 0301
+0108=0043 0302
+0109=0063 0302
+010A=0043 0307
+010B=0063 0307
+010C=0043 030C
+010D=0063 030C
+010E=0044 030C
+010F=0064 030C
+0112=0045 0304
+0113=0065 0304
+0114=0045 0306
+0115=0065 0306
+0116=0045 0307
+0117=0065 0307
+0118=0045 0328
+0119=0065 0328
+011A=0045 030C
+011B=0065 030C
+011C=0047 0302
+011D=0067 0302
+011E=0047 0306
+011F=0067 0306
+0120=0047 0307
+0121=0067 0307
+0122=0047 0327
+0123=0067 0327
+0124=0048 0302
+0125=0068 0302
+0128=0049 0303
+0129=0069 0303
+012A=0049 0304
+012B=0069 0304
+012C=0049 0306
+012D=0069 0306
+012E=0049 0328
+012F=0069 0328
+0130=0049 0307
+0132>0049 004A
+0133>0069 006A
+0134=004A 0302
+0135=006A 0302
+0136=004B 0327
+0137=006B 0327
+0139=004C 0301
+013A=006C 0301
+013B=004C 0327
+013C=006C 0327
+013D=004C 030C
+013E=006C 030C
+013F>004C 00B7
+0140>006C 00B7
+0143=004E 0301
+0144=006E 0301
+0145=004E 0327
+0146=006E 0327
+0147=004E 030C
+0148=006E 030C
+0149>02BC 006E
+014C=004F 0304
+014D=006F 0304
+014E=004F 0306
+014F=006F 0306
+0150=004F 030B
+0151=006F 030B
+0154=0052 0301
+0155=0072 0301
+0156=0052 0327
+0157=0072 0327
+0158=0052 030C
+0159=0072 030C
+015A=0053 0301
+015B=0073 0301
+015C=0053 0302
+015D=0073 0302
+015E=0053 0327
+015F=0073 0327
+0160=0053 030C
+0161=0073 030C
+0162=0054 0327
+0163=0074 0327
+0164=0054 030C
+0165=0074 030C
+0168=0055 0303
+0169=0075 0303
+016A=0055 0304
+016B=0075 0304
+016C=0055 0306
+016D=0075 0306
+016E=0055 030A
+016F=0075 030A
+0170=0055 030B
+0171=0075 030B
+0172=0055 0328
+0173=0075 0328
+0174=0057 0302
+0175=0077 0302
+0176=0059 0302
+0177=0079 0302
+0178=0059 0308
+0179=005A 0301
+017A=007A 0301
+017B=005A 0307
+017C=007A 0307
+017D=005A 030C
+017E=007A 030C
+017F>0073
+01A0=004F 031B
+01A1=006F 031B
+01AF=0055 031B
+01B0=0075 031B
+01C4>0044 017D
+01C5>0044 017E
+01C6>0064 017E
+01C7>004C 004A
+01C8>004C 006A
+01C9>006C 006A
+01CA>004E 004A
+01CB>004E 006A
+01CC>006E 006A
+01CD=0041 030C
+01CE=0061 030C
+01CF=0049 030C
+01D0=0069 030C
+01D1=004F 030C
+01D2=006F 030C
+01D3=0055 030C
+01D4=0075 030C
+01D5=00DC 0304
+01D6=00FC 0304
+01D7=00DC 0301
+01D8=00FC 0301
+01D9=00DC 030C
+01DA=00FC 030C
+01DB=00DC 0300
+01DC=00FC 0300
+01DE=00C4 0304
+01DF=00E4 0304
+01E0=0226 0304
+01E1=0227 0304
+01E2=00C6 0304
+01E3=00E6 0304
+01E6=0047 030C
+01E7=0067 030C
+01E8=004B 030C
+01E9=006B 030C
+01EA=004F 0328
+01EB=006F 0328
+01EC=01EA 0304
+01ED=01EB 0304
+01EE=01B7 030C
+01EF=0292 030C
+01F0=006A 030C
+01F1>0044 005A
+01F2>0044 007A
+01F3>0064 007A
+01F4=0047 0301
+01F5=0067 0301
+01F8=004E 0300
+01F9=006E 0300
+01FA=00C5 0301
+01FB=00E5 0301
+01FC=00C6 0301
+01FD=00E6 0301
+01FE=00D8 0301
+01FF=00F8 0301
+0200=0041 030F
+0201=0061 030F
+0202=0041 0311
+0203=0061 0311
+0204=0045 030F
+0205=0065 030F
+0206=0045 0311
+0207=0065 0311
+0208=0049 030F
+0209=0069 030F
+020A=0049 0311
+020B=0069 0311
+020C=004F 030F
+020D=006F 030F
+020E=004F 0311
+020F=006F 0311
+0210=0052 030F
+0211=0072 030F
+0212=0052 0311
+0213=0072 0311
+0214=0055 030F
+0215=0075 030F
+0216=0055 0311
+0217=0075 0311
+0218=0053 0326
+0219=0073 0326
+021A=0054 0326
+021B=0074 0326
+021E=0048 030C
+021F=0068 030C
+0226=0041 0307
+0227=0061 0307
+0228=0045 0327
+0229=0065 0327
+022A=00D6 0304
+022B=00F6 0304
+022C=00D5 0304
+022D=00F5 0304
+022E=004F 0307
+022F=006F 0307
+0230=022E 0304
+0231=022F 0304
+0232=0059 0304
+0233=0079 0304
+02B0>0068
+02B1>0266
+02B2>006A
+02B3>0072
+02B4>0279
+02B5>027B
+02B6>0281
+02B7>0077
+02B8>0079
+02D8>0020 0306
+02D9>0020 0307
+02DA>0020 030A
+02DB>0020 0328
+02DC>0020 0303
+02DD>0020 030B
+02E0>0263
+02E1>006C
+02E2>0073
+02E3>0078
+02E4>0295
+0340>0300
+0341>0301
+0343>0313
+0344>0308 0301
+0374>02B9
+037A>0020 0345
+037E>003B
+0384>0020 0301
+0385>00A8 0301
+0386=0391 0301
+0387>00B7
+0388=0395 0301
+0389=0397 0301
+038A=0399 0301
+038C=039F 0301
+038E=03A5 0301
+038F=03A9 0301
+0390=03CA 0301
+03AA=0399 0308
+03AB=03A5 0308
+03AC=03B1 0301
+03AD=03B5 0301
+03AE=03B7 0301
+03AF=03B9 0301
+03B0=03CB 0301
+03CA=03B9 0308
+03CB=03C5 0308
+03CC=03BF 0301
+03CD=03C5 0301
+03CE=03C9 0301
+03D0>03B2
+03D1>03B8
+03D2>03A5
+03D3>03D2 0301
+03D4>03D2 0308
+03D5>03C6
+03D6>03C0
+03F0>03BA
+03F1>03C1
+03F2>03C2
+03F4>0398
+03F5>03B5
+03F9>03A3
+0400=0415 0300
+0401=0415 0308
+0403=0413 0301
+0407=0406 0308
+040C=041A 0301
+040D=0418 0300
+040E=0423 0306
+0419=0418 0306
+0439=0438 0306
+0450=0435 0300
+0451=0435 0308
+0453=0433 0301
+0457=0456 0308
+045C=043A 0301
+045D=0438 0300
+045E=0443 0306
+0476=0474 030F
+0477=0475 030F
+04C1=0416 0306
+04C2=0436 0306
+04D0=0410 0306
+04D1=0430 0306
+04D2=0410 0308
+04D3=0430 0308
+04D6=0415 0306
+04D7=0435 0306
+04DA=04D8 0308
+04DB=04D9 0308
+04DC=0416 0308
+04DD=0436 0308
+04DE=0417 0308
+04DF=0437 0308
+04E2=0418 0304
+04E3=0438 0304
+04E4=0418 0308
+04E5=0438 0308
+04E6=041E 0308
+04E7=043E 0308
+04EA=04E8 0308
+04EB=04E9 0308
+04EC=042D 0308
+04ED=044D 0308
+04EE=0423 0304
+04EF=0443 0304
+04F0=0423 0308
+04F1=0443 0308
+04F2=0423 030B
+04F3=0443 030B
+04F4=0427 0308
+04F5=0447 0308
+04F8=042B 0308
+04F9=044B 0308
+0587>0565 0582
+0622=0627 0653
+0623=0627 0654
+0624=0648 0654
+0625=0627 0655
+0626=064A 0654
+0675>0627 0674
+0676>0648 0674
+0677>06C7 0674
+0678>064A 0674
+06C0=06D5 0654
+06C2=06C1 0654
+06D3=06D2 0654
+0929=0928 093C
+0931=0930 093C
+0934=0933 093C
+0958>0915 093C
+0959>0916 093C
+095A>0917 093C
+095B>091C 093C
+095C>0921 093C
+095D>0922 093C
+095E>092B 093C
+095F>092F 093C
+09CB=09C7 09BE
+09CC=09C7 09D7
+09DC>09A1 09BC
+09DD>09A2 09BC
+09DF>09AF 09BC
+0A33>0A32 0A3C
+0A36>0A38 0A3C
+0A59>0A16 0A3C
+0A5A>0A17 0A3C
+0A5B>0A1C 0A3C
+0A5E>0A2B 0A3C
+0B48=0B47 0B56
+0B4B=0B47 0B3E
+0B4C=0B47 0B57
+0B5C>0B21 0B3C
+0B5D>0B22 0B3C
+0B94=0B92 0BD7
+0BCA=0BC6 0BBE
+0BCB=0BC7 0BBE
+0BCC=0BC6 0BD7
+0C48=0C46 0C56
+0CC0=0CBF 0CD5
+0CC7=0CC6 0CD5
+0CC8=0CC6 0CD6
+0CCA=0CC6 0CC2
+0CCB=0CCA 0CD5
+0D4A=0D46 0D3E
+0D4B=0D47 0D3E
+0D4C=0D46 0D57
+0DDA=0DD9 0DCA
+0DDC=0DD9 0DCF
+0DDD=0DDC 0DCA
+0DDE=0DD9 0DDF
+0E33>0E4D 0E32
+0EB3>0ECD 0EB2
+0EDC>0EAB 0E99
+0EDD>0EAB 0EA1
+0F0C>0F0B
+0F43>0F42 0FB7
+0F4D>0F4C 0FB7
+0F52>0F51 0FB7
+0F57>0F56 0FB7
+0F5C>0F5B 0FB7
+0F69>0F40 0FB5
+0F73>0F71 0F72
+0F75>0F71 0F74
+0F76>0FB2 0F80
+0F77>0FB2 0F81
+0F78>0FB3 0F80
+0F79>0FB3 0F81
+0F81>0F71 0F80
+0F93>0F92 0FB7
+0F9D>0F9C 0FB7
+0FA2>0FA1 0FB7
+0FA7>0FA6 0FB7
+0FAC>0FAB 0FB7
+0FB9>0F90 0FB5
+1026=1025 102E
+10FC>10DC
+1B06=1B05 1B35
+1B08=1B07 1B35
+1B0A=1B09 1B35
+1B0C=1B0B 1B35
+1B0E=1B0D 1B35
+1B12=1B11 1B35
+1B3B=1B3A 1B35
+1B3D=1B3C 1B35
+1B40=1B3E 1B35
+1B41=1B3F 1B35
+1B43=1B42 1B35
+1D2C>0041
+1D2D>00C6
+1D2E>0042
+1D30>0044
+1D31>0045
+1D32>018E
+1D33>0047
+1D34>0048
+1D35>0049
+1D36>004A
+1D37>004B
+1D38>004C
+1D39>004D
+1D3A>004E
+1D3C>004F
+1D3D>0222
+1D3E>0050
+1D3F>0052
+1D40>0054
+1D41>0055
+1D42>0057
+1D43>0061
+1D44>0250
+1D45>0251
+1D46>1D02
+1D47>0062
+1D48>0064
+1D49>0065
+1D4A>0259
+1D4B>025B
+1D4C>025C
+1D4D>0067
+1D4F>006B
+1D50>006D
+1D51>014B
+1D52>006F
+1D53>0254
+1D54>1D16
+1D55>1D17
+1D56>0070
+1D57>0074
+1D58>0075
+1D59>1D1D
+1D5A>026F
+1D5B>0076
+1D5C>1D25
+1D5D>03B2
+1D5E>03B3
+1D5F>03B4
+1D60>03C6
+1D61>03C7
+1D62>0069
+1D63>0072
+1D64>0075
+1D65>0076
+1D66>03B2
+1D67>03B3
+1D68>03C1
+1D69>03C6
+1D6A>03C7
+1D78>043D
+1D9B>0252
+1D9C>0063
+1D9D>0255
+1D9E>00F0
+1D9F>025C
+1DA0>0066
+1DA1>025F
+1DA2>0261
+1DA3>0265
+1DA4>0268
+1DA5>0269
+1DA6>026A
+1DA7>1D7B
+1DA8>029D
+1DA9>026D
+1DAA>1D85
+1DAB>029F
+1DAC>0271
+1DAD>0270
+1DAE>0272
+1DAF>0273
+1DB0>0274
+1DB1>0275
+1DB2>0278
+1DB3>0282
+1DB4>0283
+1DB5>01AB
+1DB6>0289
+1DB7>028A
+1DB8>1D1C
+1DB9>028B
+1DBA>028C
+1DBB>007A
+1DBC>0290
+1DBD>0291
+1DBE>0292
+1DBF>03B8
+1E00=0041 0325
+1E01=0061 0325
+1E02=0042 0307
+1E03=0062 0307
+1E04=0042 0323
+1E05=0062 0323
+1E06=0042 0331
+1E07=0062 0331
+1E08=00C7 0301
+1E09=00E7 0301
+1E0A=0044 0307
+1E0B=0064 0307
+1E0C=0044 0323
+1E0D=0064 0323
+1E0E=0044 0331
+1E0F=0064 0331
+1E10=0044 0327
+1E11=0064 0327
+1E12=0044 032D
+1E13=0064 032D
+1E14=0112 0300
+1E15=0113 0300
+1E16=0112 0301
+1E17=0113 0301
+1E18=0045 032D
+1E19=0065 032D
+1E1A=0045 0330
+1E1B=0065 0330
+1E1C=0228 0306
+1E1D=0229 0306
+1E1E=0046 0307
+1E1F=0066 0307
+1E20=0047 0304
+1E21=0067 0304
+1E22=0048 0307
+1E23=0068 0307
+1E24=0048 0323
+1E25=0068 0323
+1E26=0048 0308
+1E27=0068 0308
+1E28=0048 0327
+1E29=0068 0327
+1E2A=0048 032E
+1E2B=0068 032E
+1E2C=0049 0330
+1E2D=0069 0330
+1E2E=00CF 0301
+1E2F=00EF 0301
+1E30=004B 0301
+1E31=006B 0301
+1E32=004B 0323
+1E33=006B 0323
+1E34=004B 0331
+1E35=006B 0331
+1E36=004C 0323
+1E37=006C 0323
+1E38=1E36 0304
+1E39=1E37 0304
+1E3A=004C 0331
+1E3B=006C 0331
+1E3C=004C 032D
+1E3D=006C 032D
+1E3E=004D 0301
+1E3F=006D 0301
+1E40=004D 0307
+1E41=006D 0307
+1E42=004D 0323
+1E43=006D 0323
+1E44=004E 0307
+1E45=006E 0307
+1E46=004E 0323
+1E47=006E 0323
+1E48=004E 0331
+1E49=006E 0331
+1E4A=004E 032D
+1E4B=006E 032D
+1E4C=00D5 0301
+1E4D=00F5 0301
+1E4E=00D5 0308
+1E4F=00F5 0308
+1E50=014C 0300
+1E51=014D 0300
+1E52=014C 0301
+1E53=014D 0301
+1E54=0050 0301
+1E55=0070 0301
+1E56=0050 0307
+1E57=0070 0307
+1E58=0052 0307
+1E59=0072 0307
+1E5A=0052 0323
+1E5B=0072 0323
+1E5C=1E5A 0304
+1E5D=1E5B 0304
+1E5E=0052 0331
+1E5F=0072 0331
+1E60=0053 0307
+1E61=0073 0307
+1E62=0053 0323
+1E63=0073 0323
+1E64=015A 0307
+1E65=015B 0307
+1E66=0160 0307
+1E67=0161 0307
+1E68=1E62 0307
+1E69=1E63 0307
+1E6A=0054 0307
+1E6B=0074 0307
+1E6C=0054 0323
+1E6D=0074 0323
+1E6E=0054 0331
+1E6F=0074 0331
+1E70=0054 032D
+1E71=0074 032D
+1E72=0055 0324
+1E73=0075 0324
+1E74=0055 0330
+1E75=0075 0330
+1E76=0055 032D
+1E77=0075 032D
+1E78=0168 0301
+1E79=0169 0301
+1E7A=016A 0308
+1E7B=016B 0308
+1E7C=0056 0303
+1E7D=0076 0303
+1E7E=0056 0323
+1E7F=0076 0323
+1E80=0057 0300
+1E81=0077 0300
+1E82=0057 0301
+1E83=0077 0301
+1E84=0057 0308
+1E85=0077 0308
+1E86=0057 0307
+1E87=0077 0307
+1E88=0057 0323
+1E89=0077 0323
+1E8A=0058 0307
+1E8B=0078 0307
+1E8C=0058 0308
+1E8D=0078 0308
+1E8E=0059 0307
+1E8F=0079 0307
+1E90=005A 0302
+1E91=007A 0302
+1E92=005A 0323
+1E93=007A 0323
+1E94=005A 0331
+1E95=007A 0331
+1E96=0068 0331
+1E97=0074 0308
+1E98=0077 030A
+1E99=0079 030A
+1E9A>0061 02BE
+1E9B>017F 0307
+1EA0=0041 0323
+1EA1=0061 0323
+1EA2=0041 0309
+1EA3=0061 0309
+1EA4=00C2 0301
+1EA5=00E2 0301
+1EA6=00C2 0300
+1EA7=00E2 0300
+1EA8=00C2 0309
+1EA9=00E2 0309
+1EAA=00C2 0303
+1EAB=00E2 0303
+1EAC=1EA0 0302
+1EAD=1EA1 0302
+1EAE=0102 0301
+1EAF=0103 0301
+1EB0=0102 0300
+1EB1=0103 0300
+1EB2=0102 0309
+1EB3=0103 0309
+1EB4=0102 0303
+1EB5=0103 0303
+1EB6=1EA0 0306
+1EB7=1EA1 0306
+1EB8=0045 0323
+1EB9=0065 0323
+1EBA=0045 0309
+1EBB=0065 0309
+1EBC=0045 0303
+1EBD=0065 0303
+1EBE=00CA 0301
+1EBF=00EA 0301
+1EC0=00CA 0300
+1EC1=00EA 0300
+1EC2=00CA 0309
+1EC3=00EA 0309
+1EC4=00CA 0303
+1EC5=00EA 0303
+1EC6=1EB8 0302
+1EC7=1EB9 0302
+1EC8=0049 0309
+1EC9=0069 0309
+1ECA=0049 0323
+1ECB=0069 0323
+1ECC=004F 0323
+1ECD=006F 0323
+1ECE=004F 0309
+1ECF=006F 0309
+1ED0=00D4 0301
+1ED1=00F4 0301
+1ED2=00D4 0300
+1ED3=00F4 0300
+1ED4=00D4 0309
+1ED5=00F4 0309
+1ED6=00D4 0303
+1ED7=00F4 0303
+1ED8=1ECC 0302
+1ED9=1ECD 0302
+1EDA=01A0 0301
+1EDB=01A1 0301
+1EDC=01A0 0300
+1EDD=01A1 0300
+1EDE=01A0 0309
+1EDF=01A1 0309
+1EE0=01A0 0303
+1EE1=01A1 0303
+1EE2=01A0 0323
+1EE3=01A1 0323
+1EE4=0055 0323
+1EE5=0075 0323
+1EE6=0055 0309
+1EE7=0075 0309
+1EE8=01AF 0301
+1EE9=01B0 0301
+1EEA=01AF 0300
+1EEB=01B0 0300
+1EEC=01AF 0309
+1EED=01B0 0309
+1EEE=01AF 0303
+1EEF=01B0 0303
+1EF0=01AF 0323
+1EF1=01B0 0323
+1EF2=0059 0300
+1EF3=0079 0300
+1EF4=0059 0323
+1EF5=0079 0323
+1EF6=0059 0309
+1EF7=0079 0309
+1EF8=0059 0303
+1EF9=0079 0303
+1F00=03B1 0313
+1F01=03B1 0314
+1F02=1F00 0300
+1F03=1F01 0300
+1F04=1F00 0301
+1F05=1F01 0301
+1F06=1F00 0342
+1F07=1F01 0342
+1F08=0391 0313
+1F09=0391 0314
+1F0A=1F08 0300
+1F0B=1F09 0300
+1F0C=1F08 0301
+1F0D=1F09 0301
+1F0E=1F08 0342
+1F0F=1F09 0342
+1F10=03B5 0313
+1F11=03B5 0314
+1F12=1F10 0300
+1F13=1F11 0300
+1F14=1F10 0301
+1F15=1F11 0301
+1F18=0395 0313
+1F19=0395 0314
+1F1A=1F18 0300
+1F1B=1F19 0300
+1F1C=1F18 0301
+1F1D=1F19 0301
+1F20=03B7 0313
+1F21=03B7 0314
+1F22=1F20 0300
+1F23=1F21 0300
+1F24=1F20 0301
+1F25=1F21 0301
+1F26=1F20 0342
+1F27=1F21 0342
+1F28=0397 0313
+1F29=0397 0314
+1F2A=1F28 0300
+1F2B=1F29 0300
+1F2C=1F28 0301
+1F2D=1F29 0301
+1F2E=1F28 0342
+1F2F=1F29 0342
+1F30=03B9 0313
+1F31=03B9 0314
+1F32=1F30 0300
+1F33=1F31 0300
+1F34=1F30 0301
+1F35=1F31 0301
+1F36=1F30 0342
+1F37=1F31 0342
+1F38=0399 0313
+1F39=0399 0314
+1F3A=1F38 0300
+1F3B=1F39 0300
+1F3C=1F38 0301
+1F3D=1F39 0301
+1F3E=1F38 0342
+1F3F=1F39 0342
+1F40=03BF 0313
+1F41=03BF 0314
+1F42=1F40 0300
+1F43=1F41 0300
+1F44=1F40 0301
+1F45=1F41 0301
+1F48=039F 0313
+1F49=039F 0314
+1F4A=1F48 0300
+1F4B=1F49 0300
+1F4C=1F48 0301
+1F4D=1F49 0301
+1F50=03C5 0313
+1F51=03C5 0314
+1F52=1F50 0300
+1F53=1F51 0300
+1F54=1F50 0301
+1F55=1F51 0301
+1F56=1F50 0342
+1F57=1F51 0342
+1F59=03A5 0314
+1F5B=1F59 0300
+1F5D=1F59 0301
+1F5F=1F59 0342
+1F60=03C9 0313
+1F61=03C9 0314
+1F62=1F60 0300
+1F63=1F61 0300
+1F64=1F60 0301
+1F65=1F61 0301
+1F66=1F60 0342
+1F67=1F61 0342
+1F68=03A9 0313
+1F69=03A9 0314
+1F6A=1F68 0300
+1F6B=1F69 0300
+1F6C=1F68 0301
+1F6D=1F69 0301
+1F6E=1F68 0342
+1F6F=1F69 0342
+1F70=03B1 0300
+1F71>03AC
+1F72=03B5 0300
+1F73>03AD
+1F74=03B7 0300
+1F75>03AE
+1F76=03B9 0300
+1F77>03AF
+1F78=03BF 0300
+1F79>03CC
+1F7A=03C5 0300
+1F7B>03CD
+1F7C=03C9 0300
+1F7D>03CE
+1F80=1F00 0345
+1F81=1F01 0345
+1F82=1F02 0345
+1F83=1F03 0345
+1F84=1F04 0345
+1F85=1F05 0345
+1F86=1F06 0345
+1F87=1F07 0345
+1F88=1F08 0345
+1F89=1F09 0345
+1F8A=1F0A 0345
+1F8B=1F0B 0345
+1F8C=1F0C 0345
+1F8D=1F0D 0345
+1F8E=1F0E 0345
+1F8F=1F0F 0345
+1F90=1F20 0345
+1F91=1F21 0345
+1F92=1F22 0345
+1F93=1F23 0345
+1F94=1F24 0345
+1F95=1F25 0345
+1F96=1F26 0345
+1F97=1F27 0345
+1F98=1F28 0345
+1F99=1F29 0345
+1F9A=1F2A 0345
+1F9B=1F2B 0345
+1F9C=1F2C 0345
+1F9D=1F2D 0345
+1F9E=1F2E 0345
+1F9F=1F2F 0345
+1FA0=1F60 0345
+1FA1=1F61 0345
+1FA2=1F62 0345
+1FA3=1F63 0345
+1FA4=1F64 0345
+1FA5=1F65 0345
+1FA6=1F66 0345
+1FA7=1F67 0345
+1FA8=1F68 0345
+1FA9=1F69 0345
+1FAA=1F6A 0345
+1FAB=1F6B 0345
+1FAC=1F6C 0345
+1FAD=1F6D 0345
+1FAE=1F6E 0345
+1FAF=1F6F 0345
+1FB0=03B1 0306
+1FB1=03B1 0304
+1FB2=1F70 0345
+1FB3=03B1 0345
+1FB4=03AC 0345
+1FB6=03B1 0342
+1FB7=1FB6 0345
+1FB8=0391 0306
+1FB9=0391 0304
+1FBA=0391 0300
+1FBB>0386
+1FBC=0391 0345
+1FBD>0020 0313
+1FBE>03B9
+1FBF>0020 0313
+1FC0>0020 0342
+1FC1>00A8 0342
+1FC2=1F74 0345
+1FC3=03B7 0345
+1FC4=03AE 0345
+1FC6=03B7 0342
+1FC7=1FC6 0345
+1FC8=0395 0300
+1FC9>0388
+1FCA=0397 0300
+1FCB>0389
+1FCC=0397 0345
+1FCD>1FBF 0300
+1FCE>1FBF 0301
+1FCF>1FBF 0342
+1FD0=03B9 0306
+1FD1=03B9 0304
+1FD2=03CA 0300
+1FD3>0390
+1FD6=03B9 0342
+1FD7=03CA 0342
+1FD8=0399 0306
+1FD9=0399 0304
+1FDA=0399 0300
+1FDB>038A
+1FDD>1FFE 0300
+1FDE>1FFE 0301
+1FDF>1FFE 0342
+1FE0=03C5 0306
+1FE1=03C5 0304
+1FE2=03CB 0300
+1FE3>03B0
+1FE4=03C1 0313
+1FE5=03C1 0314
+1FE6=03C5 0342
+1FE7=03CB 0342
+1FE8=03A5 0306
+1FE9=03A5 0304
+1FEA=03A5 0300
+1FEB>038E
+1FEC=03A1 0314
+1FED>00A8 0300
+1FEE>0385
+1FEF>0060
+1FF2=1F7C 0345
+1FF3=03C9 0345
+1FF4=03CE 0345
+1FF6=03C9 0342
+1FF7=1FF6 0345
+1FF8=039F 0300
+1FF9>038C
+1FFA=03A9 0300
+1FFB>038F
+1FFC=03A9 0345
+1FFD>00B4
+1FFE>0020 0314
+2000>2002
+2001>2003
+2002>0020
+2003>0020
+2004>0020
+2005>0020
+2006>0020
+2007>0020
+2008>0020
+2009>0020
+200A>0020
+2011>2010
+2017>0020 0333
+2024>002E
+2025>002E 002E
+2026>002E 002E 002E
+202F>0020
+2033>2032 2032
+2034>2032 2032 2032
+2036>2035 2035
+2037>2035 2035 2035
+203C>0021 0021
+203E>0020 0305
+2047>003F 003F
+2048>003F 0021
+2049>0021 003F
+2057>2032 2032 2032 2032
+205F>0020
+2070>0030
+2071>0069
+2074>0034
+2075>0035
+2076>0036
+2077>0037
+2078>0038
+2079>0039
+207A>002B
+207B>2212
+207C>003D
+207D>0028
+207E>0029
+207F>006E
+2080>0030
+2081>0031
+2082>0032
+2083>0033
+2084>0034
+2085>0035
+2086>0036
+2087>0037
+2088>0038
+2089>0039
+208A>002B
+208B>2212
+208C>003D
+208D>0028
+208E>0029
+2090>0061
+2091>0065
+2092>006F
+2093>0078
+2094>0259
+20A8>0052 0073
+2100>0061 002F 0063
+2101>0061 002F 0073
+2102>0043
+2103>00B0 0043
+2105>0063 002F 006F
+2106>0063 002F 0075
+2107>0190
+2109>00B0 0046
+210A>0067
+210B>0048
+210C>0048
+210D>0048
+210E>0068
+210F>0127
+2110>0049
+2111>0049
+2112>004C
+2113>006C
+2115>004E
+2116>004E 006F
+2119>0050
+211A>0051
+211B>0052
+211C>0052
+211D>0052
+2120>0053 004D
+2121>0054 0045 004C
+2122>0054 004D
+2124>005A
+2126>03A9
+2128>005A
+212A>004B
+212B>00C5
+212C>0042
+212D>0043
+212F>0065
+2130>0045
+2131>0046
+2133>004D
+2134>006F
+2135>05D0
+2136>05D1
+2137>05D2
+2138>05D3
+2139>0069
+213B>0046 0041 0058
+213C>03C0
+213D>03B3
+213E>0393
+213F>03A0
+2140>2211
+2145>0044
+2146>0064
+2147>0065
+2148>0069
+2149>006A
+2150>0031 2044 0037
+2151>0031 2044 0039
+2152>0031 2044 0031 0030
+2153>0031 2044 0033
+2154>0032 2044 0033
+2155>0031 2044 0035
+2156>0032 2044 0035
+2157>0033 2044 0035
+2158>0034 2044 0035
+2159>0031 2044 0036
+215A>0035 2044 0036
+215B>0031 2044 0038
+215C>0033 2044 0038
+215D>0035 2044 0038
+215E>0037 2044 0038
+215F>0031 2044
+2160>0049
+2161>0049 0049
+2162>0049 0049 0049
+2163>0049 0056
+2164>0056
+2165>0056 0049
+2166>0056 0049 0049
+2167>0056 0049 0049 0049
+2168>0049 0058
+2169>0058
+216A>0058 0049
+216B>0058 0049 0049
+216C>004C
+216D>0043
+216E>0044
+216F>004D
+2170>0069
+2171>0069 0069
+2172>0069 0069 0069
+2173>0069 0076
+2174>0076
+2175>0076 0069
+2176>0076 0069 0069
+2177>0076 0069 0069 0069
+2178>0069 0078
+2179>0078
+217A>0078 0069
+217B>0078 0069 0069
+217C>006C
+217D>0063
+217E>0064
+217F>006D
+2189>0030 2044 0033
+219A=2190 0338
+219B=2192 0338
+21AE=2194 0338
+21CD=21D0 0338
+21CE=21D4 0338
+21CF=21D2 0338
+2204=2203 0338
+2209=2208 0338
+220C=220B 0338
+2224=2223 0338
+2226=2225 0338
+222C>222B 222B
+222D>222B 222B 222B
+222F>222E 222E
+2230>222E 222E 222E
+2241=223C 0338
+2244=2243 0338
+2247=2245 0338
+2249=2248 0338
+2260=003D 0338
+2262=2261 0338
+226D=224D 0338
+226E=003C 0338
+226F=003E 0338
+2270=2264 0338
+2271=2265 0338
+2274=2272 0338
+2275=2273 0338
+2278=2276 0338
+2279=2277 0338
+2280=227A 0338
+2281=227B 0338
+2284=2282 0338
+2285=2283 0338
+2288=2286 0338
+2289=2287 0338
+22AC=22A2 0338
+22AD=22A8 0338
+22AE=22A9 0338
+22AF=22AB 0338
+22E0=227C 0338
+22E1=227D 0338
+22E2=2291 0338
+22E3=2292 0338
+22EA=22B2 0338
+22EB=22B3 0338
+22EC=22B4 0338
+22ED=22B5 0338
+2329>3008
+232A>3009
+2460>0031
+2461>0032
+2462>0033
+2463>0034
+2464>0035
+2465>0036
+2466>0037
+2467>0038
+2468>0039
+2469>0031 0030
+246A>0031 0031
+246B>0031 0032
+246C>0031 0033
+246D>0031 0034
+246E>0031 0035
+246F>0031 0036
+2470>0031 0037
+2471>0031 0038
+2472>0031 0039
+2473>0032 0030
+2474>0028 0031 0029
+2475>0028 0032 0029
+2476>0028 0033 0029
+2477>0028 0034 0029
+2478>0028 0035 0029
+2479>0028 0036 0029
+247A>0028 0037 0029
+247B>0028 0038 0029
+247C>0028 0039 0029
+247D>0028 0031 0030 0029
+247E>0028 0031 0031 0029
+247F>0028 0031 0032 0029
+2480>0028 0031 0033 0029
+2481>0028 0031 0034 0029
+2482>0028 0031 0035 0029
+2483>0028 0031 0036 0029
+2484>0028 0031 0037 0029
+2485>0028 0031 0038 0029
+2486>0028 0031 0039 0029
+2487>0028 0032 0030 0029
+2488>0031 002E
+2489>0032 002E
+248A>0033 002E
+248B>0034 002E
+248C>0035 002E
+248D>0036 002E
+248E>0037 002E
+248F>0038 002E
+2490>0039 002E
+2491>0031 0030 002E
+2492>0031 0031 002E
+2493>0031 0032 002E
+2494>0031 0033 002E
+2495>0031 0034 002E
+2496>0031 0035 002E
+2497>0031 0036 002E
+2498>0031 0037 002E
+2499>0031 0038 002E
+249A>0031 0039 002E
+249B>0032 0030 002E
+249C>0028 0061 0029
+249D>0028 0062 0029
+249E>0028 0063 0029
+249F>0028 0064 0029
+24A0>0028 0065 0029
+24A1>0028 0066 0029
+24A2>0028 0067 0029
+24A3>0028 0068 0029
+24A4>0028 0069 0029
+24A5>0028 006A 0029
+24A6>0028 006B 0029
+24A7>0028 006C 0029
+24A8>0028 006D 0029
+24A9>0028 006E 0029
+24AA>0028 006F 0029
+24AB>0028 0070 0029
+24AC>0028 0071 0029
+24AD>0028 0072 0029
+24AE>0028 0073 0029
+24AF>0028 0074 0029
+24B0>0028 0075 0029
+24B1>0028 0076 0029
+24B2>0028 0077 0029
+24B3>0028 0078 0029
+24B4>0028 0079 0029
+24B5>0028 007A 0029
+24B6>0041
+24B7>0042
+24B8>0043
+24B9>0044
+24BA>0045
+24BB>0046
+24BC>0047
+24BD>0048
+24BE>0049
+24BF>004A
+24C0>004B
+24C1>004C
+24C2>004D
+24C3>004E
+24C4>004F
+24C5>0050
+24C6>0051
+24C7>0052
+24C8>0053
+24C9>0054
+24CA>0055
+24CB>0056
+24CC>0057
+24CD>0058
+24CE>0059
+24CF>005A
+24D0>0061
+24D1>0062
+24D2>0063
+24D3>0064
+24D4>0065
+24D5>0066
+24D6>0067
+24D7>0068
+24D8>0069
+24D9>006A
+24DA>006B
+24DB>006C
+24DC>006D
+24DD>006E
+24DE>006F
+24DF>0070
+24E0>0071
+24E1>0072
+24E2>0073
+24E3>0074
+24E4>0075
+24E5>0076
+24E6>0077
+24E7>0078
+24E8>0079
+24E9>007A
+24EA>0030
+2A0C>222B 222B 222B 222B
+2A74>003A 003A 003D
+2A75>003D 003D
+2A76>003D 003D 003D
+2ADC>2ADD 0338
+2C7C>006A
+2C7D>0056
+2D6F>2D61
+2E9F>6BCD
+2EF3>9F9F
+2F00>4E00
+2F01>4E28
+2F02>4E36
+2F03>4E3F
+2F04>4E59
+2F05>4E85
+2F06>4E8C
+2F07>4EA0
+2F08>4EBA
+2F09>513F
+2F0A>5165
+2F0B>516B
+2F0C>5182
+2F0D>5196
+2F0E>51AB
+2F0F>51E0
+2F10>51F5
+2F11>5200
+2F12>529B
+2F13>52F9
+2F14>5315
+2F15>531A
+2F16>5338
+2F17>5341
+2F18>535C
+2F19>5369
+2F1A>5382
+2F1B>53B6
+2F1C>53C8
+2F1D>53E3
+2F1E>56D7
+2F1F>571F
+2F20>58EB
+2F21>5902
+2F22>590A
+2F23>5915
+2F24>5927
+2F25>5973
+2F26>5B50
+2F27>5B80
+2F28>5BF8
+2F29>5C0F
+2F2A>5C22
+2F2B>5C38
+2F2C>5C6E
+2F2D>5C71
+2F2E>5DDB
+2F2F>5DE5
+2F30>5DF1
+2F31>5DFE
+2F32>5E72
+2F33>5E7A
+2F34>5E7F
+2F35>5EF4
+2F36>5EFE
+2F37>5F0B
+2F38>5F13
+2F39>5F50
+2F3A>5F61
+2F3B>5F73
+2F3C>5FC3
+2F3D>6208
+2F3E>6236
+2F3F>624B
+2F40>652F
+2F41>6534
+2F42>6587
+2F43>6597
+2F44>65A4
+2F45>65B9
+2F46>65E0
+2F47>65E5
+2F48>66F0
+2F49>6708
+2F4A>6728
+2F4B>6B20
+2F4C>6B62
+2F4D>6B79
+2F4E>6BB3
+2F4F>6BCB
+2F50>6BD4
+2F51>6BDB
+2F52>6C0F
+2F53>6C14
+2F54>6C34
+2F55>706B
+2F56>722A
+2F57>7236
+2F58>723B
+2F59>723F
+2F5A>7247
+2F5B>7259
+2F5C>725B
+2F5D>72AC
+2F5E>7384
+2F5F>7389
+2F60>74DC
+2F61>74E6
+2F62>7518
+2F63>751F
+2F64>7528
+2F65>7530
+2F66>758B
+2F67>7592
+2F68>7676
+2F69>767D
+2F6A>76AE
+2F6B>76BF
+2F6C>76EE
+2F6D>77DB
+2F6E>77E2
+2F6F>77F3
+2F70>793A
+2F71>79B8
+2F72>79BE
+2F73>7A74
+2F74>7ACB
+2F75>7AF9
+2F76>7C73
+2F77>7CF8
+2F78>7F36
+2F79>7F51
+2F7A>7F8A
+2F7B>7FBD
+2F7C>8001
+2F7D>800C
+2F7E>8012
+2F7F>8033
+2F80>807F
+2F81>8089
+2F82>81E3
+2F83>81EA
+2F84>81F3
+2F85>81FC
+2F86>820C
+2F87>821B
+2F88>821F
+2F89>826E
+2F8A>8272
+2F8B>8278
+2F8C>864D
+2F8D>866B
+2F8E>8840
+2F8F>884C
+2F90>8863
+2F91>897E
+2F92>898B
+2F93>89D2
+2F94>8A00
+2F95>8C37
+2F96>8C46
+2F97>8C55
+2F98>8C78
+2F99>8C9D
+2F9A>8D64
+2F9B>8D70
+2F9C>8DB3
+2F9D>8EAB
+2F9E>8ECA
+2F9F>8F9B
+2FA0>8FB0
+2FA1>8FB5
+2FA2>9091
+2FA3>9149
+2FA4>91C6
+2FA5>91CC
+2FA6>91D1
+2FA7>9577
+2FA8>9580
+2FA9>961C
+2FAA>96B6
+2FAB>96B9
+2FAC>96E8
+2FAD>9751
+2FAE>975E
+2FAF>9762
+2FB0>9769
+2FB1>97CB
+2FB2>97ED
+2FB3>97F3
+2FB4>9801
+2FB5>98A8
+2FB6>98DB
+2FB7>98DF
+2FB8>9996
+2FB9>9999
+2FBA>99AC
+2FBB>9AA8
+2FBC>9AD8
+2FBD>9ADF
+2FBE>9B25
+2FBF>9B2F
+2FC0>9B32
+2FC1>9B3C
+2FC2>9B5A
+2FC3>9CE5
+2FC4>9E75
+2FC5>9E7F
+2FC6>9EA5
+2FC7>9EBB
+2FC8>9EC3
+2FC9>9ECD
+2FCA>9ED1
+2FCB>9EF9
+2FCC>9EFD
+2FCD>9F0E
+2FCE>9F13
+2FCF>9F20
+2FD0>9F3B
+2FD1>9F4A
+2FD2>9F52
+2FD3>9F8D
+2FD4>9F9C
+2FD5>9FA0
+3000>0020
+3036>3012
+3038>5341
+3039>5344
+303A>5345
+304C=304B 3099
+304E=304D 3099
+3050=304F 3099
+3052=3051 3099
+3054=3053 3099
+3056=3055 3099
+3058=3057 3099
+305A=3059 3099
+305C=305B 3099
+305E=305D 3099
+3060=305F 3099
+3062=3061 3099
+3065=3064 3099
+3067=3066 3099
+3069=3068 3099
+3070=306F 3099
+3071=306F 309A
+3073=3072 3099
+3074=3072 309A
+3076=3075 3099
+3077=3075 309A
+3079=3078 3099
+307A=3078 309A
+307C=307B 3099
+307D=307B 309A
+3094=3046 3099
+309B>0020 3099
+309C>0020 309A
+309E=309D 3099
+309F>3088 308A
+30AC=30AB 3099
+30AE=30AD 3099
+30B0=30AF 3099
+30B2=30B1 3099
+30B4=30B3 3099
+30B6=30B5 3099
+30B8=30B7 3099
+30BA=30B9 3099
+30BC=30BB 3099
+30BE=30BD 3099
+30C0=30BF 3099
+30C2=30C1 3099
+30C5=30C4 3099
+30C7=30C6 3099
+30C9=30C8 3099
+30D0=30CF 3099
+30D1=30CF 309A
+30D3=30D2 3099
+30D4=30D2 309A
+30D6=30D5 3099
+30D7=30D5 309A
+30D9=30D8 3099
+30DA=30D8 309A
+30DC=30DB 3099
+30DD=30DB 309A
+30F4=30A6 3099
+30F7=30EF 3099
+30F8=30F0 3099
+30F9=30F1 3099
+30FA=30F2 3099
+30FE=30FD 3099
+30FF>30B3 30C8
+3131>1100
+3132>1101
+3133>11AA
+3134>1102
+3135>11AC
+3136>11AD
+3137>1103
+3138>1104
+3139>1105
+313A>11B0
+313B>11B1
+313C>11B2
+313D>11B3
+313E>11B4
+313F>11B5
+3140>111A
+3141>1106
+3142>1107
+3143>1108
+3144>1121
+3145>1109
+3146>110A
+3147>110B
+3148>110C
+3149>110D
+314A>110E
+314B>110F
+314C>1110
+314D>1111
+314E>1112
+314F>1161
+3150>1162
+3151>1163
+3152>1164
+3153>1165
+3154>1166
+3155>1167
+3156>1168
+3157>1169
+3158>116A
+3159>116B
+315A>116C
+315B>116D
+315C>116E
+315D>116F
+315E>1170
+315F>1171
+3160>1172
+3161>1173
+3162>1174
+3163>1175
+3164>1160
+3165>1114
+3166>1115
+3167>11C7
+3168>11C8
+3169>11CC
+316A>11CE
+316B>11D3
+316C>11D7
+316D>11D9
+316E>111C
+316F>11DD
+3170>11DF
+3171>111D
+3172>111E
+3173>1120
+3174>1122
+3175>1123
+3176>1127
+3177>1129
+3178>112B
+3179>112C
+317A>112D
+317B>112E
+317C>112F
+317D>1132
+317E>1136
+317F>1140
+3180>1147
+3181>114C
+3182>11F1
+3183>11F2
+3184>1157
+3185>1158
+3186>1159
+3187>1184
+3188>1185
+3189>1188
+318A>1191
+318B>1192
+318C>1194
+318D>119E
+318E>11A1
+3192>4E00
+3193>4E8C
+3194>4E09
+3195>56DB
+3196>4E0A
+3197>4E2D
+3198>4E0B
+3199>7532
+319A>4E59
+319B>4E19
+319C>4E01
+319D>5929
+319E>5730
+319F>4EBA
+3200>0028 1100 0029
+3201>0028 1102 0029
+3202>0028 1103 0029
+3203>0028 1105 0029
+3204>0028 1106 0029
+3205>0028 1107 0029
+3206>0028 1109 0029
+3207>0028 110B 0029
+3208>0028 110C 0029
+3209>0028 110E 0029
+320A>0028 110F 0029
+320B>0028 1110 0029
+320C>0028 1111 0029
+320D>0028 1112 0029
+320E>0028 1100 1161 0029
+320F>0028 1102 1161 0029
+3210>0028 1103 1161 0029
+3211>0028 1105 1161 0029
+3212>0028 1106 1161 0029
+3213>0028 1107 1161 0029
+3214>0028 1109 1161 0029
+3215>0028 110B 1161 0029
+3216>0028 110C 1161 0029
+3217>0028 110E 1161 0029
+3218>0028 110F 1161 0029
+3219>0028 1110 1161 0029
+321A>0028 1111 1161 0029
+321B>0028 1112 1161 0029
+321C>0028 110C 116E 0029
+321D>0028 110B 1169 110C 1165 11AB 0029
+321E>0028 110B 1169 1112 116E 0029
+3220>0028 4E00 0029
+3221>0028 4E8C 0029
+3222>0028 4E09 0029
+3223>0028 56DB 0029
+3224>0028 4E94 0029
+3225>0028 516D 0029
+3226>0028 4E03 0029
+3227>0028 516B 0029
+3228>0028 4E5D 0029
+3229>0028 5341 0029
+322A>0028 6708 0029
+322B>0028 706B 0029
+322C>0028 6C34 0029
+322D>0028 6728 0029
+322E>0028 91D1 0029
+322F>0028 571F 0029
+3230>0028 65E5 0029
+3231>0028 682A 0029
+3232>0028 6709 0029
+3233>0028 793E 0029
+3234>0028 540D 0029
+3235>0028 7279 0029
+3236>0028 8CA1 0029
+3237>0028 795D 0029
+3238>0028 52B4 0029
+3239>0028 4EE3 0029
+323A>0028 547C 0029
+323B>0028 5B66 0029
+323C>0028 76E3 0029
+323D>0028 4F01 0029
+323E>0028 8CC7 0029
+323F>0028 5354 0029
+3240>0028 796D 0029
+3241>0028 4F11 0029
+3242>0028 81EA 0029
+3243>0028 81F3 0029
+3244>554F
+3245>5E7C
+3246>6587
+3247>7B8F
+3250>0050 0054 0045
+3251>0032 0031
+3252>0032 0032
+3253>0032 0033
+3254>0032 0034
+3255>0032 0035
+3256>0032 0036
+3257>0032 0037
+3258>0032 0038
+3259>0032 0039
+325A>0033 0030
+325B>0033 0031
+325C>0033 0032
+325D>0033 0033
+325E>0033 0034
+325F>0033 0035
+3260>1100
+3261>1102
+3262>1103
+3263>1105
+3264>1106
+3265>1107
+3266>1109
+3267>110B
+3268>110C
+3269>110E
+326A>110F
+326B>1110
+326C>1111
+326D>1112
+326E>1100 1161
+326F>1102 1161
+3270>1103 1161
+3271>1105 1161
+3272>1106 1161
+3273>1107 1161
+3274>1109 1161
+3275>110B 1161
+3276>110C 1161
+3277>110E 1161
+3278>110F 1161
+3279>1110 1161
+327A>1111 1161
+327B>1112 1161
+327C>110E 1161 11B7 1100 1169
+327D>110C 116E 110B 1174
+327E>110B 116E
+3280>4E00
+3281>4E8C
+3282>4E09
+3283>56DB
+3284>4E94
+3285>516D
+3286>4E03
+3287>516B
+3288>4E5D
+3289>5341
+328A>6708
+328B>706B
+328C>6C34
+328D>6728
+328E>91D1
+328F>571F
+3290>65E5
+3291>682A
+3292>6709
+3293>793E
+3294>540D
+3295>7279
+3296>8CA1
+3297>795D
+3298>52B4
+3299>79D8
+329A>7537
+329B>5973
+329C>9069
+329D>512A
+329E>5370
+329F>6CE8
+32A0>9805
+32A1>4F11
+32A2>5199
+32A3>6B63
+32A4>4E0A
+32A5>4E2D
+32A6>4E0B
+32A7>5DE6
+32A8>53F3
+32A9>533B
+32AA>5B97
+32AB>5B66
+32AC>76E3
+32AD>4F01
+32AE>8CC7
+32AF>5354
+32B0>591C
+32B1>0033 0036
+32B2>0033 0037
+32B3>0033 0038
+32B4>0033 0039
+32B5>0034 0030
+32B6>0034 0031
+32B7>0034 0032
+32B8>0034 0033
+32B9>0034 0034
+32BA>0034 0035
+32BB>0034 0036
+32BC>0034 0037
+32BD>0034 0038
+32BE>0034 0039
+32BF>0035 0030
+32C0>0031 6708
+32C1>0032 6708
+32C2>0033 6708
+32C3>0034 6708
+32C4>0035 6708
+32C5>0036 6708
+32C6>0037 6708
+32C7>0038 6708
+32C8>0039 6708
+32C9>0031 0030 6708
+32CA>0031 0031 6708
+32CB>0031 0032 6708
+32CC>0048 0067
+32CD>0065 0072 0067
+32CE>0065 0056
+32CF>004C 0054 0044
+32D0>30A2
+32D1>30A4
+32D2>30A6
+32D3>30A8
+32D4>30AA
+32D5>30AB
+32D6>30AD
+32D7>30AF
+32D8>30B1
+32D9>30B3
+32DA>30B5
+32DB>30B7
+32DC>30B9
+32DD>30BB
+32DE>30BD
+32DF>30BF
+32E0>30C1
+32E1>30C4
+32E2>30C6
+32E3>30C8
+32E4>30CA
+32E5>30CB
+32E6>30CC
+32E7>30CD
+32E8>30CE
+32E9>30CF
+32EA>30D2
+32EB>30D5
+32EC>30D8
+32ED>30DB
+32EE>30DE
+32EF>30DF
+32F0>30E0
+32F1>30E1
+32F2>30E2
+32F3>30E4
+32F4>30E6
+32F5>30E8
+32F6>30E9
+32F7>30EA
+32F8>30EB
+32F9>30EC
+32FA>30ED
+32FB>30EF
+32FC>30F0
+32FD>30F1
+32FE>30F2
+3300>30A2 30D1 30FC 30C8
+3301>30A2 30EB 30D5 30A1
+3302>30A2 30F3 30DA 30A2
+3303>30A2 30FC 30EB
+3304>30A4 30CB 30F3 30B0
+3305>30A4 30F3 30C1
+3306>30A6 30A9 30F3
+3307>30A8 30B9 30AF 30FC 30C9
+3308>30A8 30FC 30AB 30FC
+3309>30AA 30F3 30B9
+330A>30AA 30FC 30E0
+330B>30AB 30A4 30EA
+330C>30AB 30E9 30C3 30C8
+330D>30AB 30ED 30EA 30FC
+330E>30AC 30ED 30F3
+330F>30AC 30F3 30DE
+3310>30AE 30AC
+3311>30AE 30CB 30FC
+3312>30AD 30E5 30EA 30FC
+3313>30AE 30EB 30C0 30FC
+3314>30AD 30ED
+3315>30AD 30ED 30B0 30E9 30E0
+3316>30AD 30ED 30E1 30FC 30C8 30EB
+3317>30AD 30ED 30EF 30C3 30C8
+3318>30B0 30E9 30E0
+3319>30B0 30E9 30E0 30C8 30F3
+331A>30AF 30EB 30BC 30A4 30ED
+331B>30AF 30ED 30FC 30CD
+331C>30B1 30FC 30B9
+331D>30B3 30EB 30CA
+331E>30B3 30FC 30DD
+331F>30B5 30A4 30AF 30EB
+3320>30B5 30F3 30C1 30FC 30E0
+3321>30B7 30EA 30F3 30B0
+3322>30BB 30F3 30C1
+3323>30BB 30F3 30C8
+3324>30C0 30FC 30B9
+3325>30C7 30B7
+3326>30C9 30EB
+3327>30C8 30F3
+3328>30CA 30CE
+3329>30CE 30C3 30C8
+332A>30CF 30A4 30C4
+332B>30D1 30FC 30BB 30F3 30C8
+332C>30D1 30FC 30C4
+332D>30D0 30FC 30EC 30EB
+332E>30D4 30A2 30B9 30C8 30EB
+332F>30D4 30AF 30EB
+3330>30D4 30B3
+3331>30D3 30EB
+3332>30D5 30A1 30E9 30C3 30C9
+3333>30D5 30A3 30FC 30C8
+3334>30D6 30C3 30B7 30A7 30EB
+3335>30D5 30E9 30F3
+3336>30D8 30AF 30BF 30FC 30EB
+3337>30DA 30BD
+3338>30DA 30CB 30D2
+3339>30D8 30EB 30C4
+333A>30DA 30F3 30B9
+333B>30DA 30FC 30B8
+333C>30D9 30FC 30BF
+333D>30DD 30A4 30F3 30C8
+333E>30DC 30EB 30C8
+333F>30DB 30F3
+3340>30DD 30F3 30C9
+3341>30DB 30FC 30EB
+3342>30DB 30FC 30F3
+3343>30DE 30A4 30AF 30ED
+3344>30DE 30A4 30EB
+3345>30DE 30C3 30CF
+3346>30DE 30EB 30AF
+3347>30DE 30F3 30B7 30E7 30F3
+3348>30DF 30AF 30ED 30F3
+3349>30DF 30EA
+334A>30DF 30EA 30D0 30FC 30EB
+334B>30E1 30AC
+334C>30E1 30AC 30C8 30F3
+334D>30E1 30FC 30C8 30EB
+334E>30E4 30FC 30C9
+334F>30E4 30FC 30EB
+3350>30E6 30A2 30F3
+3351>30EA 30C3 30C8 30EB
+3352>30EA 30E9
+3353>30EB 30D4 30FC
+3354>30EB 30FC 30D6 30EB
+3355>30EC 30E0
+3356>30EC 30F3 30C8 30B2 30F3
+3357>30EF 30C3 30C8
+3358>0030 70B9
+3359>0031 70B9
+335A>0032 70B9
+335B>0033 70B9
+335C>0034 70B9
+335D>0035 70B9
+335E>0036 70B9
+335F>0037 70B9
+3360>0038 70B9
+3361>0039 70B9
+3362>0031 0030 70B9
+3363>0031 0031 70B9
+3364>0031 0032 70B9
+3365>0031 0033 70B9
+3366>0031 0034 70B9
+3367>0031 0035 70B9
+3368>0031 0036 70B9
+3369>0031 0037 70B9
+336A>0031 0038 70B9
+336B>0031 0039 70B9
+336C>0032 0030 70B9
+336D>0032 0031 70B9
+336E>0032 0032 70B9
+336F>0032 0033 70B9
+3370>0032 0034 70B9
+3371>0068 0050 0061
+3372>0064 0061
+3373>0041 0055
+3374>0062 0061 0072
+3375>006F 0056
+3376>0070 0063
+3377>0064 006D
+3378>0064 006D 00B2
+3379>0064 006D 00B3
+337A>0049 0055
+337B>5E73 6210
+337C>662D 548C
+337D>5927 6B63
+337E>660E 6CBB
+337F>682A 5F0F 4F1A 793E
+3380>0070 0041
+3381>006E 0041
+3382>03BC 0041
+3383>006D 0041
+3384>006B 0041
+3385>004B 0042
+3386>004D 0042
+3387>0047 0042
+3388>0063 0061 006C
+3389>006B 0063 0061 006C
+338A>0070 0046
+338B>006E 0046
+338C>03BC 0046
+338D>03BC 0067
+338E>006D 0067
+338F>006B 0067
+3390>0048 007A
+3391>006B 0048 007A
+3392>004D 0048 007A
+3393>0047 0048 007A
+3394>0054 0048 007A
+3395>03BC 2113
+3396>006D 2113
+3397>0064 2113
+3398>006B 2113
+3399>0066 006D
+339A>006E 006D
+339B>03BC 006D
+339C>006D 006D
+339D>0063 006D
+339E>006B 006D
+339F>006D 006D 00B2
+33A0>0063 006D 00B2
+33A1>006D 00B2
+33A2>006B 006D 00B2
+33A3>006D 006D 00B3
+33A4>0063 006D 00B3
+33A5>006D 00B3
+33A6>006B 006D 00B3
+33A7>006D 2215 0073
+33A8>006D 2215 0073 00B2
+33A9>0050 0061
+33AA>006B 0050 0061
+33AB>004D 0050 0061
+33AC>0047 0050 0061
+33AD>0072 0061 0064
+33AE>0072 0061 0064 2215 0073
+33AF>0072 0061 0064 2215 0073 00B2
+33B0>0070 0073
+33B1>006E 0073
+33B2>03BC 0073
+33B3>006D 0073
+33B4>0070 0056
+33B5>006E 0056
+33B6>03BC 0056
+33B7>006D 0056
+33B8>006B 0056
+33B9>004D 0056
+33BA>0070 0057
+33BB>006E 0057
+33BC>03BC 0057
+33BD>006D 0057
+33BE>006B 0057
+33BF>004D 0057
+33C0>006B 03A9
+33C1>004D 03A9
+33C2>0061 002E 006D 002E
+33C3>0042 0071
+33C4>0063 0063
+33C5>0063 0064
+33C6>0043 2215 006B 0067
+33C7>0043 006F 002E
+33C8>0064 0042
+33C9>0047 0079
+33CA>0068 0061
+33CB>0048 0050
+33CC>0069 006E
+33CD>004B 004B
+33CE>004B 004D
+33CF>006B 0074
+33D0>006C 006D
+33D1>006C 006E
+33D2>006C 006F 0067
+33D3>006C 0078
+33D4>006D 0062
+33D5>006D 0069 006C
+33D6>006D 006F 006C
+33D7>0050 0048
+33D8>0070 002E 006D 002E
+33D9>0050 0050 004D
+33DA>0050 0052
+33DB>0073 0072
+33DC>0053 0076
+33DD>0057 0062
+33DE>0056 2215 006D
+33DF>0041 2215 006D
+33E0>0031 65E5
+33E1>0032 65E5
+33E2>0033 65E5
+33E3>0034 65E5
+33E4>0035 65E5
+33E5>0036 65E5
+33E6>0037 65E5
+33E7>0038 65E5
+33E8>0039 65E5
+33E9>0031 0030 65E5
+33EA>0031 0031 65E5
+33EB>0031 0032 65E5
+33EC>0031 0033 65E5
+33ED>0031 0034 65E5
+33EE>0031 0035 65E5
+33EF>0031 0036 65E5
+33F0>0031 0037 65E5
+33F1>0031 0038 65E5
+33F2>0031 0039 65E5
+33F3>0032 0030 65E5
+33F4>0032 0031 65E5
+33F5>0032 0032 65E5
+33F6>0032 0033 65E5
+33F7>0032 0034 65E5
+33F8>0032 0035 65E5
+33F9>0032 0036 65E5
+33FA>0032 0037 65E5
+33FB>0032 0038 65E5
+33FC>0032 0039 65E5
+33FD>0033 0030 65E5
+33FE>0033 0031 65E5
+33FF>0067 0061 006C
+A770>A76F
+F900>8C48
+F901>66F4
+F902>8ECA
+F903>8CC8
+F904>6ED1
+F905>4E32
+F906>53E5
+F907>9F9C
+F908>9F9C
+F909>5951
+F90A>91D1
+F90B>5587
+F90C>5948
+F90D>61F6
+F90E>7669
+F90F>7F85
+F910>863F
+F911>87BA
+F912>88F8
+F913>908F
+F914>6A02
+F915>6D1B
+F916>70D9
+F917>73DE
+F918>843D
+F919>916A
+F91A>99F1
+F91B>4E82
+F91C>5375
+F91D>6B04
+F91E>721B
+F91F>862D
+F920>9E1E
+F921>5D50
+F922>6FEB
+F923>85CD
+F924>8964
+F925>62C9
+F926>81D8
+F927>881F
+F928>5ECA
+F929>6717
+F92A>6D6A
+F92B>72FC
+F92C>90CE
+F92D>4F86
+F92E>51B7
+F92F>52DE
+F930>64C4
+F931>6AD3
+F932>7210
+F933>76E7
+F934>8001
+F935>8606
+F936>865C
+F937>8DEF
+F938>9732
+F939>9B6F
+F93A>9DFA
+F93B>788C
+F93C>797F
+F93D>7DA0
+F93E>83C9
+F93F>9304
+F940>9E7F
+F941>8AD6
+F942>58DF
+F943>5F04
+F944>7C60
+F945>807E
+F946>7262
+F947>78CA
+F948>8CC2
+F949>96F7
+F94A>58D8
+F94B>5C62
+F94C>6A13
+F94D>6DDA
+F94E>6F0F
+F94F>7D2F
+F950>7E37
+F951>964B
+F952>52D2
+F953>808B
+F954>51DC
+F955>51CC
+F956>7A1C
+F957>7DBE
+F958>83F1
+F959>9675
+F95A>8B80
+F95B>62CF
+F95C>6A02
+F95D>8AFE
+F95E>4E39
+F95F>5BE7
+F960>6012
+F961>7387
+F962>7570
+F963>5317
+F964>78FB
+F965>4FBF
+F966>5FA9
+F967>4E0D
+F968>6CCC
+F969>6578
+F96A>7D22
+F96B>53C3
+F96C>585E
+F96D>7701
+F96E>8449
+F96F>8AAA
+F970>6BBA
+F971>8FB0
+F972>6C88
+F973>62FE
+F974>82E5
+F975>63A0
+F976>7565
+F977>4EAE
+F978>5169
+F979>51C9
+F97A>6881
+F97B>7CE7
+F97C>826F
+F97D>8AD2
+F97E>91CF
+F97F>52F5
+F980>5442
+F981>5973
+F982>5EEC
+F983>65C5
+F984>6FFE
+F985>792A
+F986>95AD
+F987>9A6A
+F988>9E97
+F989>9ECE
+F98A>529B
+F98B>66C6
+F98C>6B77
+F98D>8F62
+F98E>5E74
+F98F>6190
+F990>6200
+F991>649A
+F992>6F23
+F993>7149
+F994>7489
+F995>79CA
+F996>7DF4
+F997>806F
+F998>8F26
+F999>84EE
+F99A>9023
+F99B>934A
+F99C>5217
+F99D>52A3
+F99E>54BD
+F99F>70C8
+F9A0>88C2
+F9A1>8AAA
+F9A2>5EC9
+F9A3>5FF5
+F9A4>637B
+F9A5>6BAE
+F9A6>7C3E
+F9A7>7375
+F9A8>4EE4
+F9A9>56F9
+F9AA>5BE7
+F9AB>5DBA
+F9AC>601C
+F9AD>73B2
+F9AE>7469
+F9AF>7F9A
+F9B0>8046
+F9B1>9234
+F9B2>96F6
+F9B3>9748
+F9B4>9818
+F9B5>4F8B
+F9B6>79AE
+F9B7>91B4
+F9B8>96B8
+F9B9>60E1
+F9BA>4E86
+F9BB>50DA
+F9BC>5BEE
+F9BD>5C3F
+F9BE>6599
+F9BF>6A02
+F9C0>71CE
+F9C1>7642
+F9C2>84FC
+F9C3>907C
+F9C4>9F8D
+F9C5>6688
+F9C6>962E
+F9C7>5289
+F9C8>677B
+F9C9>67F3
+F9CA>6D41
+F9CB>6E9C
+F9CC>7409
+F9CD>7559
+F9CE>786B
+F9CF>7D10
+F9D0>985E
+F9D1>516D
+F9D2>622E
+F9D3>9678
+F9D4>502B
+F9D5>5D19
+F9D6>6DEA
+F9D7>8F2A
+F9D8>5F8B
+F9D9>6144
+F9DA>6817
+F9DB>7387
+F9DC>9686
+F9DD>5229
+F9DE>540F
+F9DF>5C65
+F9E0>6613
+F9E1>674E
+F9E2>68A8
+F9E3>6CE5
+F9E4>7406
+F9E5>75E2
+F9E6>7F79
+F9E7>88CF
+F9E8>88E1
+F9E9>91CC
+F9EA>96E2
+F9EB>533F
+F9EC>6EBA
+F9ED>541D
+F9EE>71D0
+F9EF>7498
+F9F0>85FA
+F9F1>96A3
+F9F2>9C57
+F9F3>9E9F
+F9F4>6797
+F9F5>6DCB
+F9F6>81E8
+F9F7>7ACB
+F9F8>7B20
+F9F9>7C92
+F9FA>72C0
+F9FB>7099
+F9FC>8B58
+F9FD>4EC0
+F9FE>8336
+F9FF>523A
+FA00>5207
+FA01>5EA6
+FA02>62D3
+FA03>7CD6
+FA04>5B85
+FA05>6D1E
+FA06>66B4
+FA07>8F3B
+FA08>884C
+FA09>964D
+FA0A>898B
+FA0B>5ED3
+FA0C>5140
+FA0D>55C0
+FA10>585A
+FA12>6674
+FA15>51DE
+FA16>732A
+FA17>76CA
+FA18>793C
+FA19>795E
+FA1A>7965
+FA1B>798F
+FA1C>9756
+FA1D>7CBE
+FA1E>7FBD
+FA20>8612
+FA22>8AF8
+FA25>9038
+FA26>90FD
+FA2A>98EF
+FA2B>98FC
+FA2C>9928
+FA2D>9DB4
+FA30>4FAE
+FA31>50E7
+FA32>514D
+FA33>52C9
+FA34>52E4
+FA35>5351
+FA36>559D
+FA37>5606
+FA38>5668
+FA39>5840
+FA3A>58A8
+FA3B>5C64
+FA3C>5C6E
+FA3D>6094
+FA3E>6168
+FA3F>618E
+FA40>61F2
+FA41>654F
+FA42>65E2
+FA43>6691
+FA44>6885
+FA45>6D77
+FA46>6E1A
+FA47>6F22
+FA48>716E
+FA49>722B
+FA4A>7422
+FA4B>7891
+FA4C>793E
+FA4D>7949
+FA4E>7948
+FA4F>7950
+FA50>7956
+FA51>795D
+FA52>798D
+FA53>798E
+FA54>7A40
+FA55>7A81
+FA56>7BC0
+FA57>7DF4
+FA58>7E09
+FA59>7E41
+FA5A>7F72
+FA5B>8005
+FA5C>81ED
+FA5D>8279
+FA5E>8279
+FA5F>8457
+FA60>8910
+FA61>8996
+FA62>8B01
+FA63>8B39
+FA64>8CD3
+FA65>8D08
+FA66>8FB6
+FA67>9038
+FA68>96E3
+FA69>97FF
+FA6A>983B
+FA6B>6075
+FA6C>242EE
+FA6D>8218
+FA70>4E26
+FA71>51B5
+FA72>5168
+FA73>4F80
+FA74>5145
+FA75>5180
+FA76>52C7
+FA77>52FA
+FA78>559D
+FA79>5555
+FA7A>5599
+FA7B>55E2
+FA7C>585A
+FA7D>58B3
+FA7E>5944
+FA7F>5954
+FA80>5A62
+FA81>5B28
+FA82>5ED2
+FA83>5ED9
+FA84>5F69
+FA85>5FAD
+FA86>60D8
+FA87>614E
+FA88>6108
+FA89>618E
+FA8A>6160
+FA8B>61F2
+FA8C>6234
+FA8D>63C4
+FA8E>641C
+FA8F>6452
+FA90>6556
+FA91>6674
+FA92>6717
+FA93>671B
+FA94>6756
+FA95>6B79
+FA96>6BBA
+FA97>6D41
+FA98>6EDB
+FA99>6ECB
+FA9A>6F22
+FA9B>701E
+FA9C>716E
+FA9D>77A7
+FA9E>7235
+FA9F>72AF
+FAA0>732A
+FAA1>7471
+FAA2>7506
+FAA3>753B
+FAA4>761D
+FAA5>761F
+FAA6>76CA
+FAA7>76DB
+FAA8>76F4
+FAA9>774A
+FAAA>7740
+FAAB>78CC
+FAAC>7AB1
+FAAD>7BC0
+FAAE>7C7B
+FAAF>7D5B
+FAB0>7DF4
+FAB1>7F3E
+FAB2>8005
+FAB3>8352
+FAB4>83EF
+FAB5>8779
+FAB6>8941
+FAB7>8986
+FAB8>8996
+FAB9>8ABF
+FABA>8AF8
+FABB>8ACB
+FABC>8B01
+FABD>8AFE
+FABE>8AED
+FABF>8B39
+FAC0>8B8A
+FAC1>8D08
+FAC2>8F38
+FAC3>9072
+FAC4>9199
+FAC5>9276
+FAC6>967C
+FAC7>96E3
+FAC8>9756
+FAC9>97DB
+FACA>97FF
+FACB>980B
+FACC>983B
+FACD>9B12
+FACE>9F9C
+FACF>2284A
+FAD0>22844
+FAD1>233D5
+FAD2>3B9D
+FAD3>4018
+FAD4>4039
+FAD5>25249
+FAD6>25CD0
+FAD7>27ED3
+FAD8>9F43
+FAD9>9F8E
+FB00>0066 0066
+FB01>0066 0069
+FB02>0066 006C
+FB03>0066 0066 0069
+FB04>0066 0066 006C
+FB05>017F 0074
+FB06>0073 0074
+FB13>0574 0576
+FB14>0574 0565
+FB15>0574 056B
+FB16>057E 0576
+FB17>0574 056D
+FB1D>05D9 05B4
+FB1F>05F2 05B7
+FB20>05E2
+FB21>05D0
+FB22>05D3
+FB23>05D4
+FB24>05DB
+FB25>05DC
+FB26>05DD
+FB27>05E8
+FB28>05EA
+FB29>002B
+FB2A>05E9 05C1
+FB2B>05E9 05C2
+FB2C>FB49 05C1
+FB2D>FB49 05C2
+FB2E>05D0 05B7
+FB2F>05D0 05B8
+FB30>05D0 05BC
+FB31>05D1 05BC
+FB32>05D2 05BC
+FB33>05D3 05BC
+FB34>05D4 05BC
+FB35>05D5 05BC
+FB36>05D6 05BC
+FB38>05D8 05BC
+FB39>05D9 05BC
+FB3A>05DA 05BC
+FB3B>05DB 05BC
+FB3C>05DC 05BC
+FB3E>05DE 05BC
+FB40>05E0 05BC
+FB41>05E1 05BC
+FB43>05E3 05BC
+FB44>05E4 05BC
+FB46>05E6 05BC
+FB47>05E7 05BC
+FB48>05E8 05BC
+FB49>05E9 05BC
+FB4A>05EA 05BC
+FB4B>05D5 05B9
+FB4C>05D1 05BF
+FB4D>05DB 05BF
+FB4E>05E4 05BF
+FB4F>05D0 05DC
+FB50>0671
+FB51>0671
+FB52>067B
+FB53>067B
+FB54>067B
+FB55>067B
+FB56>067E
+FB57>067E
+FB58>067E
+FB59>067E
+FB5A>0680
+FB5B>0680
+FB5C>0680
+FB5D>0680
+FB5E>067A
+FB5F>067A
+FB60>067A
+FB61>067A
+FB62>067F
+FB63>067F
+FB64>067F
+FB65>067F
+FB66>0679
+FB67>0679
+FB68>0679
+FB69>0679
+FB6A>06A4
+FB6B>06A4
+FB6C>06A4
+FB6D>06A4
+FB6E>06A6
+FB6F>06A6
+FB70>06A6
+FB71>06A6
+FB72>0684
+FB73>0684
+FB74>0684
+FB75>0684
+FB76>0683
+FB77>0683
+FB78>0683
+FB79>0683
+FB7A>0686
+FB7B>0686
+FB7C>0686
+FB7D>0686
+FB7E>0687
+FB7F>0687
+FB80>0687
+FB81>0687
+FB82>068D
+FB83>068D
+FB84>068C
+FB85>068C
+FB86>068E
+FB87>068E
+FB88>0688
+FB89>0688
+FB8A>0698
+FB8B>0698
+FB8C>0691
+FB8D>0691
+FB8E>06A9
+FB8F>06A9
+FB90>06A9
+FB91>06A9
+FB92>06AF
+FB93>06AF
+FB94>06AF
+FB95>06AF
+FB96>06B3
+FB97>06B3
+FB98>06B3
+FB99>06B3
+FB9A>06B1
+FB9B>06B1
+FB9C>06B1
+FB9D>06B1
+FB9E>06BA
+FB9F>06BA
+FBA0>06BB
+FBA1>06BB
+FBA2>06BB
+FBA3>06BB
+FBA4>06C0
+FBA5>06C0
+FBA6>06C1
+FBA7>06C1
+FBA8>06C1
+FBA9>06C1
+FBAA>06BE
+FBAB>06BE
+FBAC>06BE
+FBAD>06BE
+FBAE>06D2
+FBAF>06D2
+FBB0>06D3
+FBB1>06D3
+FBD3>06AD
+FBD4>06AD
+FBD5>06AD
+FBD6>06AD
+FBD7>06C7
+FBD8>06C7
+FBD9>06C6
+FBDA>06C6
+FBDB>06C8
+FBDC>06C8
+FBDD>0677
+FBDE>06CB
+FBDF>06CB
+FBE0>06C5
+FBE1>06C5
+FBE2>06C9
+FBE3>06C9
+FBE4>06D0
+FBE5>06D0
+FBE6>06D0
+FBE7>06D0
+FBE8>0649
+FBE9>0649
+FBEA>0626 0627
+FBEB>0626 0627
+FBEC>0626 06D5
+FBED>0626 06D5
+FBEE>0626 0648
+FBEF>0626 0648
+FBF0>0626 06C7
+FBF1>0626 06C7
+FBF2>0626 06C6
+FBF3>0626 06C6
+FBF4>0626 06C8
+FBF5>0626 06C8
+FBF6>0626 06D0
+FBF7>0626 06D0
+FBF8>0626 06D0
+FBF9>0626 0649
+FBFA>0626 0649
+FBFB>0626 0649
+FBFC>06CC
+FBFD>06CC
+FBFE>06CC
+FBFF>06CC
+FC00>0626 062C
+FC01>0626 062D
+FC02>0626 0645
+FC03>0626 0649
+FC04>0626 064A
+FC05>0628 062C
+FC06>0628 062D
+FC07>0628 062E
+FC08>0628 0645
+FC09>0628 0649
+FC0A>0628 064A
+FC0B>062A 062C
+FC0C>062A 062D
+FC0D>062A 062E
+FC0E>062A 0645
+FC0F>062A 0649
+FC10>062A 064A
+FC11>062B 062C
+FC12>062B 0645
+FC13>062B 0649
+FC14>062B 064A
+FC15>062C 062D
+FC16>062C 0645
+FC17>062D 062C
+FC18>062D 0645
+FC19>062E 062C
+FC1A>062E 062D
+FC1B>062E 0645
+FC1C>0633 062C
+FC1D>0633 062D
+FC1E>0633 062E
+FC1F>0633 0645
+FC20>0635 062D
+FC21>0635 0645
+FC22>0636 062C
+FC23>0636 062D
+FC24>0636 062E
+FC25>0636 0645
+FC26>0637 062D
+FC27>0637 0645
+FC28>0638 0645
+FC29>0639 062C
+FC2A>0639 0645
+FC2B>063A 062C
+FC2C>063A 0645
+FC2D>0641 062C
+FC2E>0641 062D
+FC2F>0641 062E
+FC30>0641 0645
+FC31>0641 0649
+FC32>0641 064A
+FC33>0642 062D
+FC34>0642 0645
+FC35>0642 0649
+FC36>0642 064A
+FC37>0643 0627
+FC38>0643 062C
+FC39>0643 062D
+FC3A>0643 062E
+FC3B>0643 0644
+FC3C>0643 0645
+FC3D>0643 0649
+FC3E>0643 064A
+FC3F>0644 062C
+FC40>0644 062D
+FC41>0644 062E
+FC42>0644 0645
+FC43>0644 0649
+FC44>0644 064A
+FC45>0645 062C
+FC46>0645 062D
+FC47>0645 062E
+FC48>0645 0645
+FC49>0645 0649
+FC4A>0645 064A
+FC4B>0646 062C
+FC4C>0646 062D
+FC4D>0646 062E
+FC4E>0646 0645
+FC4F>0646 0649
+FC50>0646 064A
+FC51>0647 062C
+FC52>0647 0645
+FC53>0647 0649
+FC54>0647 064A
+FC55>064A 062C
+FC56>064A 062D
+FC57>064A 062E
+FC58>064A 0645
+FC59>064A 0649
+FC5A>064A 064A
+FC5B>0630 0670
+FC5C>0631 0670
+FC5D>0649 0670
+FC5E>0020 064C 0651
+FC5F>0020 064D 0651
+FC60>0020 064E 0651
+FC61>0020 064F 0651
+FC62>0020 0650 0651
+FC63>0020 0651 0670
+FC64>0626 0631
+FC65>0626 0632
+FC66>0626 0645
+FC67>0626 0646
+FC68>0626 0649
+FC69>0626 064A
+FC6A>0628 0631
+FC6B>0628 0632
+FC6C>0628 0645
+FC6D>0628 0646
+FC6E>0628 0649
+FC6F>0628 064A
+FC70>062A 0631
+FC71>062A 0632
+FC72>062A 0645
+FC73>062A 0646
+FC74>062A 0649
+FC75>062A 064A
+FC76>062B 0631
+FC77>062B 0632
+FC78>062B 0645
+FC79>062B 0646
+FC7A>062B 0649
+FC7B>062B 064A
+FC7C>0641 0649
+FC7D>0641 064A
+FC7E>0642 0649
+FC7F>0642 064A
+FC80>0643 0627
+FC81>0643 0644
+FC82>0643 0645
+FC83>0643 0649
+FC84>0643 064A
+FC85>0644 0645
+FC86>0644 0649
+FC87>0644 064A
+FC88>0645 0627
+FC89>0645 0645
+FC8A>0646 0631
+FC8B>0646 0632
+FC8C>0646 0645
+FC8D>0646 0646
+FC8E>0646 0649
+FC8F>0646 064A
+FC90>0649 0670
+FC91>064A 0631
+FC92>064A 0632
+FC93>064A 0645
+FC94>064A 0646
+FC95>064A 0649
+FC96>064A 064A
+FC97>0626 062C
+FC98>0626 062D
+FC99>0626 062E
+FC9A>0626 0645
+FC9B>0626 0647
+FC9C>0628 062C
+FC9D>0628 062D
+FC9E>0628 062E
+FC9F>0628 0645
+FCA0>0628 0647
+FCA1>062A 062C
+FCA2>062A 062D
+FCA3>062A 062E
+FCA4>062A 0645
+FCA5>062A 0647
+FCA6>062B 0645
+FCA7>062C 062D
+FCA8>062C 0645
+FCA9>062D 062C
+FCAA>062D 0645
+FCAB>062E 062C
+FCAC>062E 0645
+FCAD>0633 062C
+FCAE>0633 062D
+FCAF>0633 062E
+FCB0>0633 0645
+FCB1>0635 062D
+FCB2>0635 062E
+FCB3>0635 0645
+FCB4>0636 062C
+FCB5>0636 062D
+FCB6>0636 062E
+FCB7>0636 0645
+FCB8>0637 062D
+FCB9>0638 0645
+FCBA>0639 062C
+FCBB>0639 0645
+FCBC>063A 062C
+FCBD>063A 0645
+FCBE>0641 062C
+FCBF>0641 062D
+FCC0>0641 062E
+FCC1>0641 0645
+FCC2>0642 062D
+FCC3>0642 0645
+FCC4>0643 062C
+FCC5>0643 062D
+FCC6>0643 062E
+FCC7>0643 0644
+FCC8>0643 0645
+FCC9>0644 062C
+FCCA>0644 062D
+FCCB>0644 062E
+FCCC>0644 0645
+FCCD>0644 0647
+FCCE>0645 062C
+FCCF>0645 062D
+FCD0>0645 062E
+FCD1>0645 0645
+FCD2>0646 062C
+FCD3>0646 062D
+FCD4>0646 062E
+FCD5>0646 0645
+FCD6>0646 0647
+FCD7>0647 062C
+FCD8>0647 0645
+FCD9>0647 0670
+FCDA>064A 062C
+FCDB>064A 062D
+FCDC>064A 062E
+FCDD>064A 0645
+FCDE>064A 0647
+FCDF>0626 0645
+FCE0>0626 0647
+FCE1>0628 0645
+FCE2>0628 0647
+FCE3>062A 0645
+FCE4>062A 0647
+FCE5>062B 0645
+FCE6>062B 0647
+FCE7>0633 0645
+FCE8>0633 0647
+FCE9>0634 0645
+FCEA>0634 0647
+FCEB>0643 0644
+FCEC>0643 0645
+FCED>0644 0645
+FCEE>0646 0645
+FCEF>0646 0647
+FCF0>064A 0645
+FCF1>064A 0647
+FCF2>0640 064E 0651
+FCF3>0640 064F 0651
+FCF4>0640 0650 0651
+FCF5>0637 0649
+FCF6>0637 064A
+FCF7>0639 0649
+FCF8>0639 064A
+FCF9>063A 0649
+FCFA>063A 064A
+FCFB>0633 0649
+FCFC>0633 064A
+FCFD>0634 0649
+FCFE>0634 064A
+FCFF>062D 0649
+FD00>062D 064A
+FD01>062C 0649
+FD02>062C 064A
+FD03>062E 0649
+FD04>062E 064A
+FD05>0635 0649
+FD06>0635 064A
+FD07>0636 0649
+FD08>0636 064A
+FD09>0634 062C
+FD0A>0634 062D
+FD0B>0634 062E
+FD0C>0634 0645
+FD0D>0634 0631
+FD0E>0633 0631
+FD0F>0635 0631
+FD10>0636 0631
+FD11>0637 0649
+FD12>0637 064A
+FD13>0639 0649
+FD14>0639 064A
+FD15>063A 0649
+FD16>063A 064A
+FD17>0633 0649
+FD18>0633 064A
+FD19>0634 0649
+FD1A>0634 064A
+FD1B>062D 0649
+FD1C>062D 064A
+FD1D>062C 0649
+FD1E>062C 064A
+FD1F>062E 0649
+FD20>062E 064A
+FD21>0635 0649
+FD22>0635 064A
+FD23>0636 0649
+FD24>0636 064A
+FD25>0634 062C
+FD26>0634 062D
+FD27>0634 062E
+FD28>0634 0645
+FD29>0634 0631
+FD2A>0633 0631
+FD2B>0635 0631
+FD2C>0636 0631
+FD2D>0634 062C
+FD2E>0634 062D
+FD2F>0634 062E
+FD30>0634 0645
+FD31>0633 0647
+FD32>0634 0647
+FD33>0637 0645
+FD34>0633 062C
+FD35>0633 062D
+FD36>0633 062E
+FD37>0634 062C
+FD38>0634 062D
+FD39>0634 062E
+FD3A>0637 0645
+FD3B>0638 0645
+FD3C>0627 064B
+FD3D>0627 064B
+FD50>062A 062C 0645
+FD51>062A 062D 062C
+FD52>062A 062D 062C
+FD53>062A 062D 0645
+FD54>062A 062E 0645
+FD55>062A 0645 062C
+FD56>062A 0645 062D
+FD57>062A 0645 062E
+FD58>062C 0645 062D
+FD59>062C 0645 062D
+FD5A>062D 0645 064A
+FD5B>062D 0645 0649
+FD5C>0633 062D 062C
+FD5D>0633 062C 062D
+FD5E>0633 062C 0649
+FD5F>0633 0645 062D
+FD60>0633 0645 062D
+FD61>0633 0645 062C
+FD62>0633 0645 0645
+FD63>0633 0645 0645
+FD64>0635 062D 062D
+FD65>0635 062D 062D
+FD66>0635 0645 0645
+FD67>0634 062D 0645
+FD68>0634 062D 0645
+FD69>0634 062C 064A
+FD6A>0634 0645 062E
+FD6B>0634 0645 062E
+FD6C>0634 0645 0645
+FD6D>0634 0645 0645
+FD6E>0636 062D 0649
+FD6F>0636 062E 0645
+FD70>0636 062E 0645
+FD71>0637 0645 062D
+FD72>0637 0645 062D
+FD73>0637 0645 0645
+FD74>0637 0645 064A
+FD75>0639 062C 0645
+FD76>0639 0645 0645
+FD77>0639 0645 0645
+FD78>0639 0645 0649
+FD79>063A 0645 0645
+FD7A>063A 0645 064A
+FD7B>063A 0645 0649
+FD7C>0641 062E 0645
+FD7D>0641 062E 0645
+FD7E>0642 0645 062D
+FD7F>0642 0645 0645
+FD80>0644 062D 0645
+FD81>0644 062D 064A
+FD82>0644 062D 0649
+FD83>0644 062C 062C
+FD84>0644 062C 062C
+FD85>0644 062E 0645
+FD86>0644 062E 0645
+FD87>0644 0645 062D
+FD88>0644 0645 062D
+FD89>0645 062D 062C
+FD8A>0645 062D 0645
+FD8B>0645 062D 064A
+FD8C>0645 062C 062D
+FD8D>0645 062C 0645
+FD8E>0645 062E 062C
+FD8F>0645 062E 0645
+FD92>0645 062C 062E
+FD93>0647 0645 062C
+FD94>0647 0645 0645
+FD95>0646 062D 0645
+FD96>0646 062D 0649
+FD97>0646 062C 0645
+FD98>0646 062C 0645
+FD99>0646 062C 0649
+FD9A>0646 0645 064A
+FD9B>0646 0645 0649
+FD9C>064A 0645 0645
+FD9D>064A 0645 0645
+FD9E>0628 062E 064A
+FD9F>062A 062C 064A
+FDA0>062A 062C 0649
+FDA1>062A 062E 064A
+FDA2>062A 062E 0649
+FDA3>062A 0645 064A
+FDA4>062A 0645 0649
+FDA5>062C 0645 064A
+FDA6>062C 062D 0649
+FDA7>062C 0645 0649
+FDA8>0633 062E 0649
+FDA9>0635 062D 064A
+FDAA>0634 062D 064A
+FDAB>0636 062D 064A
+FDAC>0644 062C 064A
+FDAD>0644 0645 064A
+FDAE>064A 062D 064A
+FDAF>064A 062C 064A
+FDB0>064A 0645 064A
+FDB1>0645 0645 064A
+FDB2>0642 0645 064A
+FDB3>0646 062D 064A
+FDB4>0642 0645 062D
+FDB5>0644 062D 0645
+FDB6>0639 0645 064A
+FDB7>0643 0645 064A
+FDB8>0646 062C 062D
+FDB9>0645 062E 064A
+FDBA>0644 062C 0645
+FDBB>0643 0645 0645
+FDBC>0644 062C 0645
+FDBD>0646 062C 062D
+FDBE>062C 062D 064A
+FDBF>062D 062C 064A
+FDC0>0645 062C 064A
+FDC1>0641 0645 064A
+FDC2>0628 062D 064A
+FDC3>0643 0645 0645
+FDC4>0639 062C 0645
+FDC5>0635 0645 0645
+FDC6>0633 062E 064A
+FDC7>0646 062C 064A
+FDF0>0635 0644 06D2
+FDF1>0642 0644 06D2
+FDF2>0627 0644 0644 0647
+FDF3>0627 0643 0628 0631
+FDF4>0645 062D 0645 062F
+FDF5>0635 0644 0639 0645
+FDF6>0631 0633 0648 0644
+FDF7>0639 0644 064A 0647
+FDF8>0648 0633 0644 0645
+FDF9>0635 0644 0649
+FDFA>0635 0644 0649 0020 0627 0644 0644 0647 0020 0639 0644 064A 0647 0020 0648 0633 0644 0645
+FDFB>062C 0644 0020 062C 0644 0627 0644 0647
+FDFC>0631 06CC 0627 0644
+FE10>002C
+FE11>3001
+FE12>3002
+FE13>003A
+FE14>003B
+FE15>0021
+FE16>003F
+FE17>3016
+FE18>3017
+FE19>2026
+FE30>2025
+FE31>2014
+FE32>2013
+FE33>005F
+FE34>005F
+FE35>0028
+FE36>0029
+FE37>007B
+FE38>007D
+FE39>3014
+FE3A>3015
+FE3B>3010
+FE3C>3011
+FE3D>300A
+FE3E>300B
+FE3F>3008
+FE40>3009
+FE41>300C
+FE42>300D
+FE43>300E
+FE44>300F
+FE47>005B
+FE48>005D
+FE49>203E
+FE4A>203E
+FE4B>203E
+FE4C>203E
+FE4D>005F
+FE4E>005F
+FE4F>005F
+FE50>002C
+FE51>3001
+FE52>002E
+FE54>003B
+FE55>003A
+FE56>003F
+FE57>0021
+FE58>2014
+FE59>0028
+FE5A>0029
+FE5B>007B
+FE5C>007D
+FE5D>3014
+FE5E>3015
+FE5F>0023
+FE60>0026
+FE61>002A
+FE62>002B
+FE63>002D
+FE64>003C
+FE65>003E
+FE66>003D
+FE68>005C
+FE69>0024
+FE6A>0025
+FE6B>0040
+FE70>0020 064B
+FE71>0640 064B
+FE72>0020 064C
+FE74>0020 064D
+FE76>0020 064E
+FE77>0640 064E
+FE78>0020 064F
+FE79>0640 064F
+FE7A>0020 0650
+FE7B>0640 0650
+FE7C>0020 0651
+FE7D>0640 0651
+FE7E>0020 0652
+FE7F>0640 0652
+FE80>0621
+FE81>0622
+FE82>0622
+FE83>0623
+FE84>0623
+FE85>0624
+FE86>0624
+FE87>0625
+FE88>0625
+FE89>0626
+FE8A>0626
+FE8B>0626
+FE8C>0626
+FE8D>0627
+FE8E>0627
+FE8F>0628
+FE90>0628
+FE91>0628
+FE92>0628
+FE93>0629
+FE94>0629
+FE95>062A
+FE96>062A
+FE97>062A
+FE98>062A
+FE99>062B
+FE9A>062B
+FE9B>062B
+FE9C>062B
+FE9D>062C
+FE9E>062C
+FE9F>062C
+FEA0>062C
+FEA1>062D
+FEA2>062D
+FEA3>062D
+FEA4>062D
+FEA5>062E
+FEA6>062E
+FEA7>062E
+FEA8>062E
+FEA9>062F
+FEAA>062F
+FEAB>0630
+FEAC>0630
+FEAD>0631
+FEAE>0631
+FEAF>0632
+FEB0>0632
+FEB1>0633
+FEB2>0633
+FEB3>0633
+FEB4>0633
+FEB5>0634
+FEB6>0634
+FEB7>0634
+FEB8>0634
+FEB9>0635
+FEBA>0635
+FEBB>0635
+FEBC>0635
+FEBD>0636
+FEBE>0636
+FEBF>0636
+FEC0>0636
+FEC1>0637
+FEC2>0637
+FEC3>0637
+FEC4>0637
+FEC5>0638
+FEC6>0638
+FEC7>0638
+FEC8>0638
+FEC9>0639
+FECA>0639
+FECB>0639
+FECC>0639
+FECD>063A
+FECE>063A
+FECF>063A
+FED0>063A
+FED1>0641
+FED2>0641
+FED3>0641
+FED4>0641
+FED5>0642
+FED6>0642
+FED7>0642
+FED8>0642
+FED9>0643
+FEDA>0643
+FEDB>0643
+FEDC>0643
+FEDD>0644
+FEDE>0644
+FEDF>0644
+FEE0>0644
+FEE1>0645
+FEE2>0645
+FEE3>0645
+FEE4>0645
+FEE5>0646
+FEE6>0646
+FEE7>0646
+FEE8>0646
+FEE9>0647
+FEEA>0647
+FEEB>0647
+FEEC>0647
+FEED>0648
+FEEE>0648
+FEEF>0649
+FEF0>0649
+FEF1>064A
+FEF2>064A
+FEF3>064A
+FEF4>064A
+FEF5>0644 0622
+FEF6>0644 0622
+FEF7>0644 0623
+FEF8>0644 0623
+FEF9>0644 0625
+FEFA>0644 0625
+FEFB>0644 0627
+FEFC>0644 0627
+FF01>0021
+FF02>0022
+FF03>0023
+FF04>0024
+FF05>0025
+FF06>0026
+FF07>0027
+FF08>0028
+FF09>0029
+FF0A>002A
+FF0B>002B
+FF0C>002C
+FF0D>002D
+FF0E>002E
+FF0F>002F
+FF10>0030
+FF11>0031
+FF12>0032
+FF13>0033
+FF14>0034
+FF15>0035
+FF16>0036
+FF17>0037
+FF18>0038
+FF19>0039
+FF1A>003A
+FF1B>003B
+FF1C>003C
+FF1D>003D
+FF1E>003E
+FF1F>003F
+FF20>0040
+FF21>0041
+FF22>0042
+FF23>0043
+FF24>0044
+FF25>0045
+FF26>0046
+FF27>0047
+FF28>0048
+FF29>0049
+FF2A>004A
+FF2B>004B
+FF2C>004C
+FF2D>004D
+FF2E>004E
+FF2F>004F
+FF30>0050
+FF31>0051
+FF32>0052
+FF33>0053
+FF34>0054
+FF35>0055
+FF36>0056
+FF37>0057
+FF38>0058
+FF39>0059
+FF3A>005A
+FF3B>005B
+FF3C>005C
+FF3D>005D
+FF3E>005E
+FF3F>005F
+FF40>0060
+FF41>0061
+FF42>0062
+FF43>0063
+FF44>0064
+FF45>0065
+FF46>0066
+FF47>0067
+FF48>0068
+FF49>0069
+FF4A>006A
+FF4B>006B
+FF4C>006C
+FF4D>006D
+FF4E>006E
+FF4F>006F
+FF50>0070
+FF51>0071
+FF52>0072
+FF53>0073
+FF54>0074
+FF55>0075
+FF56>0076
+FF57>0077
+FF58>0078
+FF59>0079
+FF5A>007A
+FF5B>007B
+FF5C>007C
+FF5D>007D
+FF5E>007E
+FF5F>2985
+FF60>2986
+FF61>3002
+FF62>300C
+FF63>300D
+FF64>3001
+FF65>30FB
+FF66>30F2
+FF67>30A1
+FF68>30A3
+FF69>30A5
+FF6A>30A7
+FF6B>30A9
+FF6C>30E3
+FF6D>30E5
+FF6E>30E7
+FF6F>30C3
+FF70>30FC
+FF71>30A2
+FF72>30A4
+FF73>30A6
+FF74>30A8
+FF75>30AA
+FF76>30AB
+FF77>30AD
+FF78>30AF
+FF79>30B1
+FF7A>30B3
+FF7B>30B5
+FF7C>30B7
+FF7D>30B9
+FF7E>30BB
+FF7F>30BD
+FF80>30BF
+FF81>30C1
+FF82>30C4
+FF83>30C6
+FF84>30C8
+FF85>30CA
+FF86>30CB
+FF87>30CC
+FF88>30CD
+FF89>30CE
+FF8A>30CF
+FF8B>30D2
+FF8C>30D5
+FF8D>30D8
+FF8E>30DB
+FF8F>30DE
+FF90>30DF
+FF91>30E0
+FF92>30E1
+FF93>30E2
+FF94>30E4
+FF95>30E6
+FF96>30E8
+FF97>30E9
+FF98>30EA
+FF99>30EB
+FF9A>30EC
+FF9B>30ED
+FF9C>30EF
+FF9D>30F3
+FF9E>3099
+FF9F>309A
+FFA0>3164
+FFA1>3131
+FFA2>3132
+FFA3>3133
+FFA4>3134
+FFA5>3135
+FFA6>3136
+FFA7>3137
+FFA8>3138
+FFA9>3139
+FFAA>313A
+FFAB>313B
+FFAC>313C
+FFAD>313D
+FFAE>313E
+FFAF>313F
+FFB0>3140
+FFB1>3141
+FFB2>3142
+FFB3>3143
+FFB4>3144
+FFB5>3145
+FFB6>3146
+FFB7>3147
+FFB8>3148
+FFB9>3149
+FFBA>314A
+FFBB>314B
+FFBC>314C
+FFBD>314D
+FFBE>314E
+FFC2>314F
+FFC3>3150
+FFC4>3151
+FFC5>3152
+FFC6>3153
+FFC7>3154
+FFCA>3155
+FFCB>3156
+FFCC>3157
+FFCD>3158
+FFCE>3159
+FFCF>315A
+FFD2>315B
+FFD3>315C
+FFD4>315D
+FFD5>315E
+FFD6>315F
+FFD7>3160
+FFDA>3161
+FFDB>3162
+FFDC>3163
+FFE0>00A2
+FFE1>00A3
+FFE2>00AC
+FFE3>00AF
+FFE4>00A6
+FFE5>00A5
+FFE6>20A9
+FFE8>2502
+FFE9>2190
+FFEA>2191
+FFEB>2192
+FFEC>2193
+FFED>25A0
+FFEE>25CB
+1109A=11099 110BA
+1109C=1109B 110BA
+110AB=110A5 110BA
+1D15E>1D157 1D165
+1D15F>1D158 1D165
+1D160>1D15F 1D16E
+1D161>1D15F 1D16F
+1D162>1D15F 1D170
+1D163>1D15F 1D171
+1D164>1D15F 1D172
+1D1BB>1D1B9 1D165
+1D1BC>1D1BA 1D165
+1D1BD>1D1BB 1D16E
+1D1BE>1D1BC 1D16E
+1D1BF>1D1BB 1D16F
+1D1C0>1D1BC 1D16F
+1D400>0041
+1D401>0042
+1D402>0043
+1D403>0044
+1D404>0045
+1D405>0046
+1D406>0047
+1D407>0048
+1D408>0049
+1D409>004A
+1D40A>004B
+1D40B>004C
+1D40C>004D
+1D40D>004E
+1D40E>004F
+1D40F>0050
+1D410>0051
+1D411>0052
+1D412>0053
+1D413>0054
+1D414>0055
+1D415>0056
+1D416>0057
+1D417>0058
+1D418>0059
+1D419>005A
+1D41A>0061
+1D41B>0062
+1D41C>0063
+1D41D>0064
+1D41E>0065
+1D41F>0066
+1D420>0067
+1D421>0068
+1D422>0069
+1D423>006A
+1D424>006B
+1D425>006C
+1D426>006D
+1D427>006E
+1D428>006F
+1D429>0070
+1D42A>0071
+1D42B>0072
+1D42C>0073
+1D42D>0074
+1D42E>0075
+1D42F>0076
+1D430>0077
+1D431>0078
+1D432>0079
+1D433>007A
+1D434>0041
+1D435>0042
+1D436>0043
+1D437>0044
+1D438>0045
+1D439>0046
+1D43A>0047
+1D43B>0048
+1D43C>0049
+1D43D>004A
+1D43E>004B
+1D43F>004C
+1D440>004D
+1D441>004E
+1D442>004F
+1D443>0050
+1D444>0051
+1D445>0052
+1D446>0053
+1D447>0054
+1D448>0055
+1D449>0056
+1D44A>0057
+1D44B>0058
+1D44C>0059
+1D44D>005A
+1D44E>0061
+1D44F>0062
+1D450>0063
+1D451>0064
+1D452>0065
+1D453>0066
+1D454>0067
+1D456>0069
+1D457>006A
+1D458>006B
+1D459>006C
+1D45A>006D
+1D45B>006E
+1D45C>006F
+1D45D>0070
+1D45E>0071
+1D45F>0072
+1D460>0073
+1D461>0074
+1D462>0075
+1D463>0076
+1D464>0077
+1D465>0078
+1D466>0079
+1D467>007A
+1D468>0041
+1D469>0042
+1D46A>0043
+1D46B>0044
+1D46C>0045
+1D46D>0046
+1D46E>0047
+1D46F>0048
+1D470>0049
+1D471>004A
+1D472>004B
+1D473>004C
+1D474>004D
+1D475>004E
+1D476>004F
+1D477>0050
+1D478>0051
+1D479>0052
+1D47A>0053
+1D47B>0054
+1D47C>0055
+1D47D>0056
+1D47E>0057
+1D47F>0058
+1D480>0059
+1D481>005A
+1D482>0061
+1D483>0062
+1D484>0063
+1D485>0064
+1D486>0065
+1D487>0066
+1D488>0067
+1D489>0068
+1D48A>0069
+1D48B>006A
+1D48C>006B
+1D48D>006C
+1D48E>006D
+1D48F>006E
+1D490>006F
+1D491>0070
+1D492>0071
+1D493>0072
+1D494>0073
+1D495>0074
+1D496>0075
+1D497>0076
+1D498>0077
+1D499>0078
+1D49A>0079
+1D49B>007A
+1D49C>0041
+1D49E>0043
+1D49F>0044
+1D4A2>0047
+1D4A5>004A
+1D4A6>004B
+1D4A9>004E
+1D4AA>004F
+1D4AB>0050
+1D4AC>0051
+1D4AE>0053
+1D4AF>0054
+1D4B0>0055
+1D4B1>0056
+1D4B2>0057
+1D4B3>0058
+1D4B4>0059
+1D4B5>005A
+1D4B6>0061
+1D4B7>0062
+1D4B8>0063
+1D4B9>0064
+1D4BB>0066
+1D4BD>0068
+1D4BE>0069
+1D4BF>006A
+1D4C0>006B
+1D4C1>006C
+1D4C2>006D
+1D4C3>006E
+1D4C5>0070
+1D4C6>0071
+1D4C7>0072
+1D4C8>0073
+1D4C9>0074
+1D4CA>0075
+1D4CB>0076
+1D4CC>0077
+1D4CD>0078
+1D4CE>0079
+1D4CF>007A
+1D4D0>0041
+1D4D1>0042
+1D4D2>0043
+1D4D3>0044
+1D4D4>0045
+1D4D5>0046
+1D4D6>0047
+1D4D7>0048
+1D4D8>0049
+1D4D9>004A
+1D4DA>004B
+1D4DB>004C
+1D4DC>004D
+1D4DD>004E
+1D4DE>004F
+1D4DF>0050
+1D4E0>0051
+1D4E1>0052
+1D4E2>0053
+1D4E3>0054
+1D4E4>0055
+1D4E5>0056
+1D4E6>0057
+1D4E7>0058
+1D4E8>0059
+1D4E9>005A
+1D4EA>0061
+1D4EB>0062
+1D4EC>0063
+1D4ED>0064
+1D4EE>0065
+1D4EF>0066
+1D4F0>0067
+1D4F1>0068
+1D4F2>0069
+1D4F3>006A
+1D4F4>006B
+1D4F5>006C
+1D4F6>006D
+1D4F7>006E
+1D4F8>006F
+1D4F9>0070
+1D4FA>0071
+1D4FB>0072
+1D4FC>0073
+1D4FD>0074
+1D4FE>0075
+1D4FF>0076
+1D500>0077
+1D501>0078
+1D502>0079
+1D503>007A
+1D504>0041
+1D505>0042
+1D507>0044
+1D508>0045
+1D509>0046
+1D50A>0047
+1D50D>004A
+1D50E>004B
+1D50F>004C
+1D510>004D
+1D511>004E
+1D512>004F
+1D513>0050
+1D514>0051
+1D516>0053
+1D517>0054
+1D518>0055
+1D519>0056
+1D51A>0057
+1D51B>0058
+1D51C>0059
+1D51E>0061
+1D51F>0062
+1D520>0063
+1D521>0064
+1D522>0065
+1D523>0066
+1D524>0067
+1D525>0068
+1D526>0069
+1D527>006A
+1D528>006B
+1D529>006C
+1D52A>006D
+1D52B>006E
+1D52C>006F
+1D52D>0070
+1D52E>0071
+1D52F>0072
+1D530>0073
+1D531>0074
+1D532>0075
+1D533>0076
+1D534>0077
+1D535>0078
+1D536>0079
+1D537>007A
+1D538>0041
+1D539>0042
+1D53B>0044
+1D53C>0045
+1D53D>0046
+1D53E>0047
+1D540>0049
+1D541>004A
+1D542>004B
+1D543>004C
+1D544>004D
+1D546>004F
+1D54A>0053
+1D54B>0054
+1D54C>0055
+1D54D>0056
+1D54E>0057
+1D54F>0058
+1D550>0059
+1D552>0061
+1D553>0062
+1D554>0063
+1D555>0064
+1D556>0065
+1D557>0066
+1D558>0067
+1D559>0068
+1D55A>0069
+1D55B>006A
+1D55C>006B
+1D55D>006C
+1D55E>006D
+1D55F>006E
+1D560>006F
+1D561>0070
+1D562>0071
+1D563>0072
+1D564>0073
+1D565>0074
+1D566>0075
+1D567>0076
+1D568>0077
+1D569>0078
+1D56A>0079
+1D56B>007A
+1D56C>0041
+1D56D>0042
+1D56E>0043
+1D56F>0044
+1D570>0045
+1D571>0046
+1D572>0047
+1D573>0048
+1D574>0049
+1D575>004A
+1D576>004B
+1D577>004C
+1D578>004D
+1D579>004E
+1D57A>004F
+1D57B>0050
+1D57C>0051
+1D57D>0052
+1D57E>0053
+1D57F>0054
+1D580>0055
+1D581>0056
+1D582>0057
+1D583>0058
+1D584>0059
+1D585>005A
+1D586>0061
+1D587>0062
+1D588>0063
+1D589>0064
+1D58A>0065
+1D58B>0066
+1D58C>0067
+1D58D>0068
+1D58E>0069
+1D58F>006A
+1D590>006B
+1D591>006C
+1D592>006D
+1D593>006E
+1D594>006F
+1D595>0070
+1D596>0071
+1D597>0072
+1D598>0073
+1D599>0074
+1D59A>0075
+1D59B>0076
+1D59C>0077
+1D59D>0078
+1D59E>0079
+1D59F>007A
+1D5A0>0041
+1D5A1>0042
+1D5A2>0043
+1D5A3>0044
+1D5A4>0045
+1D5A5>0046
+1D5A6>0047
+1D5A7>0048
+1D5A8>0049
+1D5A9>004A
+1D5AA>004B
+1D5AB>004C
+1D5AC>004D
+1D5AD>004E
+1D5AE>004F
+1D5AF>0050
+1D5B0>0051
+1D5B1>0052
+1D5B2>0053
+1D5B3>0054
+1D5B4>0055
+1D5B5>0056
+1D5B6>0057
+1D5B7>0058
+1D5B8>0059
+1D5B9>005A
+1D5BA>0061
+1D5BB>0062
+1D5BC>0063
+1D5BD>0064
+1D5BE>0065
+1D5BF>0066
+1D5C0>0067
+1D5C1>0068
+1D5C2>0069
+1D5C3>006A
+1D5C4>006B
+1D5C5>006C
+1D5C6>006D
+1D5C7>006E
+1D5C8>006F
+1D5C9>0070
+1D5CA>0071
+1D5CB>0072
+1D5CC>0073
+1D5CD>0074
+1D5CE>0075
+1D5CF>0076
+1D5D0>0077
+1D5D1>0078
+1D5D2>0079
+1D5D3>007A
+1D5D4>0041
+1D5D5>0042
+1D5D6>0043
+1D5D7>0044
+1D5D8>0045
+1D5D9>0046
+1D5DA>0047
+1D5DB>0048
+1D5DC>0049
+1D5DD>004A
+1D5DE>004B
+1D5DF>004C
+1D5E0>004D
+1D5E1>004E
+1D5E2>004F
+1D5E3>0050
+1D5E4>0051
+1D5E5>0052
+1D5E6>0053
+1D5E7>0054
+1D5E8>0055
+1D5E9>0056
+1D5EA>0057
+1D5EB>0058
+1D5EC>0059
+1D5ED>005A
+1D5EE>0061
+1D5EF>0062
+1D5F0>0063
+1D5F1>0064
+1D5F2>0065
+1D5F3>0066
+1D5F4>0067
+1D5F5>0068
+1D5F6>0069
+1D5F7>006A
+1D5F8>006B
+1D5F9>006C
+1D5FA>006D
+1D5FB>006E
+1D5FC>006F
+1D5FD>0070
+1D5FE>0071
+1D5FF>0072
+1D600>0073
+1D601>0074
+1D602>0075
+1D603>0076
+1D604>0077
+1D605>0078
+1D606>0079
+1D607>007A
+1D608>0041
+1D609>0042
+1D60A>0043
+1D60B>0044
+1D60C>0045
+1D60D>0046
+1D60E>0047
+1D60F>0048
+1D610>0049
+1D611>004A
+1D612>004B
+1D613>004C
+1D614>004D
+1D615>004E
+1D616>004F
+1D617>0050
+1D618>0051
+1D619>0052
+1D61A>0053
+1D61B>0054
+1D61C>0055
+1D61D>0056
+1D61E>0057
+1D61F>0058
+1D620>0059
+1D621>005A
+1D622>0061
+1D623>0062
+1D624>0063
+1D625>0064
+1D626>0065
+1D627>0066
+1D628>0067
+1D629>0068
+1D62A>0069
+1D62B>006A
+1D62C>006B
+1D62D>006C
+1D62E>006D
+1D62F>006E
+1D630>006F
+1D631>0070
+1D632>0071
+1D633>0072
+1D634>0073
+1D635>0074
+1D636>0075
+1D637>0076
+1D638>0077
+1D639>0078
+1D63A>0079
+1D63B>007A
+1D63C>0041
+1D63D>0042
+1D63E>0043
+1D63F>0044
+1D640>0045
+1D641>0046
+1D642>0047
+1D643>0048
+1D644>0049
+1D645>004A
+1D646>004B
+1D647>004C
+1D648>004D
+1D649>004E
+1D64A>004F
+1D64B>0050
+1D64C>0051
+1D64D>0052
+1D64E>0053
+1D64F>0054
+1D650>0055
+1D651>0056
+1D652>0057
+1D653>0058
+1D654>0059
+1D655>005A
+1D656>0061
+1D657>0062
+1D658>0063
+1D659>0064
+1D65A>0065
+1D65B>0066
+1D65C>0067
+1D65D>0068
+1D65E>0069
+1D65F>006A
+1D660>006B
+1D661>006C
+1D662>006D
+1D663>006E
+1D664>006F
+1D665>0070
+1D666>0071
+1D667>0072
+1D668>0073
+1D669>0074
+1D66A>0075
+1D66B>0076
+1D66C>0077
+1D66D>0078
+1D66E>0079
+1D66F>007A
+1D670>0041
+1D671>0042
+1D672>0043
+1D673>0044
+1D674>0045
+1D675>0046
+1D676>0047
+1D677>0048
+1D678>0049
+1D679>004A
+1D67A>004B
+1D67B>004C
+1D67C>004D
+1D67D>004E
+1D67E>004F
+1D67F>0050
+1D680>0051
+1D681>0052
+1D682>0053
+1D683>0054
+1D684>0055
+1D685>0056
+1D686>0057
+1D687>0058
+1D688>0059
+1D689>005A
+1D68A>0061
+1D68B>0062
+1D68C>0063
+1D68D>0064
+1D68E>0065
+1D68F>0066
+1D690>0067
+1D691>0068
+1D692>0069
+1D693>006A
+1D694>006B
+1D695>006C
+1D696>006D
+1D697>006E
+1D698>006F
+1D699>0070
+1D69A>0071
+1D69B>0072
+1D69C>0073
+1D69D>0074
+1D69E>0075
+1D69F>0076
+1D6A0>0077
+1D6A1>0078
+1D6A2>0079
+1D6A3>007A
+1D6A4>0131
+1D6A5>0237
+1D6A8>0391
+1D6A9>0392
+1D6AA>0393
+1D6AB>0394
+1D6AC>0395
+1D6AD>0396
+1D6AE>0397
+1D6AF>0398
+1D6B0>0399
+1D6B1>039A
+1D6B2>039B
+1D6B3>039C
+1D6B4>039D
+1D6B5>039E
+1D6B6>039F
+1D6B7>03A0
+1D6B8>03A1
+1D6B9>03F4
+1D6BA>03A3
+1D6BB>03A4
+1D6BC>03A5
+1D6BD>03A6
+1D6BE>03A7
+1D6BF>03A8
+1D6C0>03A9
+1D6C1>2207
+1D6C2>03B1
+1D6C3>03B2
+1D6C4>03B3
+1D6C5>03B4
+1D6C6>03B5
+1D6C7>03B6
+1D6C8>03B7
+1D6C9>03B8
+1D6CA>03B9
+1D6CB>03BA
+1D6CC>03BB
+1D6CD>03BC
+1D6CE>03BD
+1D6CF>03BE
+1D6D0>03BF
+1D6D1>03C0
+1D6D2>03C1
+1D6D3>03C2
+1D6D4>03C3
+1D6D5>03C4
+1D6D6>03C5
+1D6D7>03C6
+1D6D8>03C7
+1D6D9>03C8
+1D6DA>03C9
+1D6DB>2202
+1D6DC>03F5
+1D6DD>03D1
+1D6DE>03F0
+1D6DF>03D5
+1D6E0>03F1
+1D6E1>03D6
+1D6E2>0391
+1D6E3>0392
+1D6E4>0393
+1D6E5>0394
+1D6E6>0395
+1D6E7>0396
+1D6E8>0397
+1D6E9>0398
+1D6EA>0399
+1D6EB>039A
+1D6EC>039B
+1D6ED>039C
+1D6EE>039D
+1D6EF>039E
+1D6F0>039F
+1D6F1>03A0
+1D6F2>03A1
+1D6F3>03F4
+1D6F4>03A3
+1D6F5>03A4
+1D6F6>03A5
+1D6F7>03A6
+1D6F8>03A7
+1D6F9>03A8
+1D6FA>03A9
+1D6FB>2207
+1D6FC>03B1
+1D6FD>03B2
+1D6FE>03B3
+1D6FF>03B4
+1D700>03B5
+1D701>03B6
+1D702>03B7
+1D703>03B8
+1D704>03B9
+1D705>03BA
+1D706>03BB
+1D707>03BC
+1D708>03BD
+1D709>03BE
+1D70A>03BF
+1D70B>03C0
+1D70C>03C1
+1D70D>03C2
+1D70E>03C3
+1D70F>03C4
+1D710>03C5
+1D711>03C6
+1D712>03C7
+1D713>03C8
+1D714>03C9
+1D715>2202
+1D716>03F5
+1D717>03D1
+1D718>03F0
+1D719>03D5
+1D71A>03F1
+1D71B>03D6
+1D71C>0391
+1D71D>0392
+1D71E>0393
+1D71F>0394
+1D720>0395
+1D721>0396
+1D722>0397
+1D723>0398
+1D724>0399
+1D725>039A
+1D726>039B
+1D727>039C
+1D728>039D
+1D729>039E
+1D72A>039F
+1D72B>03A0
+1D72C>03A1
+1D72D>03F4
+1D72E>03A3
+1D72F>03A4
+1D730>03A5
+1D731>03A6
+1D732>03A7
+1D733>03A8
+1D734>03A9
+1D735>2207
+1D736>03B1
+1D737>03B2
+1D738>03B3
+1D739>03B4
+1D73A>03B5
+1D73B>03B6
+1D73C>03B7
+1D73D>03B8
+1D73E>03B9
+1D73F>03BA
+1D740>03BB
+1D741>03BC
+1D742>03BD
+1D743>03BE
+1D744>03BF
+1D745>03C0
+1D746>03C1
+1D747>03C2
+1D748>03C3
+1D749>03C4
+1D74A>03C5
+1D74B>03C6
+1D74C>03C7
+1D74D>03C8
+1D74E>03C9
+1D74F>2202
+1D750>03F5
+1D751>03D1
+1D752>03F0
+1D753>03D5
+1D754>03F1
+1D755>03D6
+1D756>0391
+1D757>0392
+1D758>0393
+1D759>0394
+1D75A>0395
+1D75B>0396
+1D75C>0397
+1D75D>0398
+1D75E>0399
+1D75F>039A
+1D760>039B
+1D761>039C
+1D762>039D
+1D763>039E
+1D764>039F
+1D765>03A0
+1D766>03A1
+1D767>03F4
+1D768>03A3
+1D769>03A4
+1D76A>03A5
+1D76B>03A6
+1D76C>03A7
+1D76D>03A8
+1D76E>03A9
+1D76F>2207
+1D770>03B1
+1D771>03B2
+1D772>03B3
+1D773>03B4
+1D774>03B5
+1D775>03B6
+1D776>03B7
+1D777>03B8
+1D778>03B9
+1D779>03BA
+1D77A>03BB
+1D77B>03BC
+1D77C>03BD
+1D77D>03BE
+1D77E>03BF
+1D77F>03C0
+1D780>03C1
+1D781>03C2
+1D782>03C3
+1D783>03C4
+1D784>03C5
+1D785>03C6
+1D786>03C7
+1D787>03C8
+1D788>03C9
+1D789>2202
+1D78A>03F5
+1D78B>03D1
+1D78C>03F0
+1D78D>03D5
+1D78E>03F1
+1D78F>03D6
+1D790>0391
+1D791>0392
+1D792>0393
+1D793>0394
+1D794>0395
+1D795>0396
+1D796>0397
+1D797>0398
+1D798>0399
+1D799>039A
+1D79A>039B
+1D79B>039C
+1D79C>039D
+1D79D>039E
+1D79E>039F
+1D79F>03A0
+1D7A0>03A1
+1D7A1>03F4
+1D7A2>03A3
+1D7A3>03A4
+1D7A4>03A5
+1D7A5>03A6
+1D7A6>03A7
+1D7A7>03A8
+1D7A8>03A9
+1D7A9>2207
+1D7AA>03B1
+1D7AB>03B2
+1D7AC>03B3
+1D7AD>03B4
+1D7AE>03B5
+1D7AF>03B6
+1D7B0>03B7
+1D7B1>03B8
+1D7B2>03B9
+1D7B3>03BA
+1D7B4>03BB
+1D7B5>03BC
+1D7B6>03BD
+1D7B7>03BE
+1D7B8>03BF
+1D7B9>03C0
+1D7BA>03C1
+1D7BB>03C2
+1D7BC>03C3
+1D7BD>03C4
+1D7BE>03C5
+1D7BF>03C6
+1D7C0>03C7
+1D7C1>03C8
+1D7C2>03C9
+1D7C3>2202
+1D7C4>03F5
+1D7C5>03D1
+1D7C6>03F0
+1D7C7>03D5
+1D7C8>03F1
+1D7C9>03D6
+1D7CA>03DC
+1D7CB>03DD
+1D7CE>0030
+1D7CF>0031
+1D7D0>0032
+1D7D1>0033
+1D7D2>0034
+1D7D3>0035
+1D7D4>0036
+1D7D5>0037
+1D7D6>0038
+1D7D7>0039
+1D7D8>0030
+1D7D9>0031
+1D7DA>0032
+1D7DB>0033
+1D7DC>0034
+1D7DD>0035
+1D7DE>0036
+1D7DF>0037
+1D7E0>0038
+1D7E1>0039
+1D7E2>0030
+1D7E3>0031
+1D7E4>0032
+1D7E5>0033
+1D7E6>0034
+1D7E7>0035
+1D7E8>0036
+1D7E9>0037
+1D7EA>0038
+1D7EB>0039
+1D7EC>0030
+1D7ED>0031
+1D7EE>0032
+1D7EF>0033
+1D7F0>0034
+1D7F1>0035
+1D7F2>0036
+1D7F3>0037
+1D7F4>0038
+1D7F5>0039
+1D7F6>0030
+1D7F7>0031
+1D7F8>0032
+1D7F9>0033
+1D7FA>0034
+1D7FB>0035
+1D7FC>0036
+1D7FD>0037
+1D7FE>0038
+1D7FF>0039
+1F100>0030 002E
+1F101>0030 002C
+1F102>0031 002C
+1F103>0032 002C
+1F104>0033 002C
+1F105>0034 002C
+1F106>0035 002C
+1F107>0036 002C
+1F108>0037 002C
+1F109>0038 002C
+1F10A>0039 002C
+1F110>0028 0041 0029
+1F111>0028 0042 0029
+1F112>0028 0043 0029
+1F113>0028 0044 0029
+1F114>0028 0045 0029
+1F115>0028 0046 0029
+1F116>0028 0047 0029
+1F117>0028 0048 0029
+1F118>0028 0049 0029
+1F119>0028 004A 0029
+1F11A>0028 004B 0029
+1F11B>0028 004C 0029
+1F11C>0028 004D 0029
+1F11D>0028 004E 0029
+1F11E>0028 004F 0029
+1F11F>0028 0050 0029
+1F120>0028 0051 0029
+1F121>0028 0052 0029
+1F122>0028 0053 0029
+1F123>0028 0054 0029
+1F124>0028 0055 0029
+1F125>0028 0056 0029
+1F126>0028 0057 0029
+1F127>0028 0058 0029
+1F128>0028 0059 0029
+1F129>0028 005A 0029
+1F12A>3014 0053 3015
+1F12B>0043
+1F12C>0052
+1F12D>0043 0044
+1F12E>0057 005A
+1F131>0042
+1F13D>004E
+1F13F>0050
+1F142>0053
+1F146>0057
+1F14A>0048 0056
+1F14B>004D 0056
+1F14C>0053 0044
+1F14D>0053 0053
+1F14E>0050 0050 0056
+1F190>0044 004A
+1F200>307B 304B
+1F210>624B
+1F211>5B57
+1F212>53CC
+1F213>30C7
+1F214>4E8C
+1F215>591A
+1F216>89E3
+1F217>5929
+1F218>4EA4
+1F219>6620
+1F21A>7121
+1F21B>6599
+1F21C>524D
+1F21D>5F8C
+1F21E>518D
+1F21F>65B0
+1F220>521D
+1F221>7D42
+1F222>751F
+1F223>8CA9
+1F224>58F0
+1F225>5439
+1F226>6F14
+1F227>6295
+1F228>6355
+1F229>4E00
+1F22A>4E09
+1F22B>904A
+1F22C>5DE6
+1F22D>4E2D
+1F22E>53F3
+1F22F>6307
+1F230>8D70
+1F231>6253
+1F240>3014 672C 3015
+1F241>3014 4E09 3015
+1F242>3014 4E8C 3015
+1F243>3014 5B89 3015
+1F244>3014 70B9 3015
+1F245>3014 6253 3015
+1F246>3014 76D7 3015
+1F247>3014 52DD 3015
+1F248>3014 6557 3015
+2F800>4E3D
+2F801>4E38
+2F802>4E41
+2F803>20122
+2F804>4F60
+2F805>4FAE
+2F806>4FBB
+2F807>5002
+2F808>507A
+2F809>5099
+2F80A>50E7
+2F80B>50CF
+2F80C>349E
+2F80D>2063A
+2F80E>514D
+2F80F>5154
+2F810>5164
+2F811>5177
+2F812>2051C
+2F813>34B9
+2F814>5167
+2F815>518D
+2F816>2054B
+2F817>5197
+2F818>51A4
+2F819>4ECC
+2F81A>51AC
+2F81B>51B5
+2F81C>291DF
+2F81D>51F5
+2F81E>5203
+2F81F>34DF
+2F820>523B
+2F821>5246
+2F822>5272
+2F823>5277
+2F824>3515
+2F825>52C7
+2F826>52C9
+2F827>52E4
+2F828>52FA
+2F829>5305
+2F82A>5306
+2F82B>5317
+2F82C>5349
+2F82D>5351
+2F82E>535A
+2F82F>5373
+2F830>537D
+2F831>537F
+2F832>537F
+2F833>537F
+2F834>20A2C
+2F835>7070
+2F836>53CA
+2F837>53DF
+2F838>20B63
+2F839>53EB
+2F83A>53F1
+2F83B>5406
+2F83C>549E
+2F83D>5438
+2F83E>5448
+2F83F>5468
+2F840>54A2
+2F841>54F6
+2F842>5510
+2F843>5553
+2F844>5563
+2F845>5584
+2F846>5584
+2F847>5599
+2F848>55AB
+2F849>55B3
+2F84A>55C2
+2F84B>5716
+2F84C>5606
+2F84D>5717
+2F84E>5651
+2F84F>5674
+2F850>5207
+2F851>58EE
+2F852>57CE
+2F853>57F4
+2F854>580D
+2F855>578B
+2F856>5832
+2F857>5831
+2F858>58AC
+2F859>214E4
+2F85A>58F2
+2F85B>58F7
+2F85C>5906
+2F85D>591A
+2F85E>5922
+2F85F>5962
+2F860>216A8
+2F861>216EA
+2F862>59EC
+2F863>5A1B
+2F864>5A27
+2F865>59D8
+2F866>5A66
+2F867>36EE
+2F868>36FC
+2F869>5B08
+2F86A>5B3E
+2F86B>5B3E
+2F86C>219C8
+2F86D>5BC3
+2F86E>5BD8
+2F86F>5BE7
+2F870>5BF3
+2F871>21B18
+2F872>5BFF
+2F873>5C06
+2F874>5F53
+2F875>5C22
+2F876>3781
+2F877>5C60
+2F878>5C6E
+2F879>5CC0
+2F87A>5C8D
+2F87B>21DE4
+2F87C>5D43
+2F87D>21DE6
+2F87E>5D6E
+2F87F>5D6B
+2F880>5D7C
+2F881>5DE1
+2F882>5DE2
+2F883>382F
+2F884>5DFD
+2F885>5E28
+2F886>5E3D
+2F887>5E69
+2F888>3862
+2F889>22183
+2F88A>387C
+2F88B>5EB0
+2F88C>5EB3
+2F88D>5EB6
+2F88E>5ECA
+2F88F>2A392
+2F890>5EFE
+2F891>22331
+2F892>22331
+2F893>8201
+2F894>5F22
+2F895>5F22
+2F896>38C7
+2F897>232B8
+2F898>261DA
+2F899>5F62
+2F89A>5F6B
+2F89B>38E3
+2F89C>5F9A
+2F89D>5FCD
+2F89E>5FD7
+2F89F>5FF9
+2F8A0>6081
+2F8A1>393A
+2F8A2>391C
+2F8A3>6094
+2F8A4>226D4
+2F8A5>60C7
+2F8A6>6148
+2F8A7>614C
+2F8A8>614E
+2F8A9>614C
+2F8AA>617A
+2F8AB>618E
+2F8AC>61B2
+2F8AD>61A4
+2F8AE>61AF
+2F8AF>61DE
+2F8B0>61F2
+2F8B1>61F6
+2F8B2>6210
+2F8B3>621B
+2F8B4>625D
+2F8B5>62B1
+2F8B6>62D4
+2F8B7>6350
+2F8B8>22B0C
+2F8B9>633D
+2F8BA>62FC
+2F8BB>6368
+2F8BC>6383
+2F8BD>63E4
+2F8BE>22BF1
+2F8BF>6422
+2F8C0>63C5
+2F8C1>63A9
+2F8C2>3A2E
+2F8C3>6469
+2F8C4>647E
+2F8C5>649D
+2F8C6>6477
+2F8C7>3A6C
+2F8C8>654F
+2F8C9>656C
+2F8CA>2300A
+2F8CB>65E3
+2F8CC>66F8
+2F8CD>6649
+2F8CE>3B19
+2F8CF>6691
+2F8D0>3B08
+2F8D1>3AE4
+2F8D2>5192
+2F8D3>5195
+2F8D4>6700
+2F8D5>669C
+2F8D6>80AD
+2F8D7>43D9
+2F8D8>6717
+2F8D9>671B
+2F8DA>6721
+2F8DB>675E
+2F8DC>6753
+2F8DD>233C3
+2F8DE>3B49
+2F8DF>67FA
+2F8E0>6785
+2F8E1>6852
+2F8E2>6885
+2F8E3>2346D
+2F8E4>688E
+2F8E5>681F
+2F8E6>6914
+2F8E7>3B9D
+2F8E8>6942
+2F8E9>69A3
+2F8EA>69EA
+2F8EB>6AA8
+2F8EC>236A3
+2F8ED>6ADB
+2F8EE>3C18
+2F8EF>6B21
+2F8F0>238A7
+2F8F1>6B54
+2F8F2>3C4E
+2F8F3>6B72
+2F8F4>6B9F
+2F8F5>6BBA
+2F8F6>6BBB
+2F8F7>23A8D
+2F8F8>21D0B
+2F8F9>23AFA
+2F8FA>6C4E
+2F8FB>23CBC
+2F8FC>6CBF
+2F8FD>6CCD
+2F8FE>6C67
+2F8FF>6D16
+2F900>6D3E
+2F901>6D77
+2F902>6D41
+2F903>6D69
+2F904>6D78
+2F905>6D85
+2F906>23D1E
+2F907>6D34
+2F908>6E2F
+2F909>6E6E
+2F90A>3D33
+2F90B>6ECB
+2F90C>6EC7
+2F90D>23ED1
+2F90E>6DF9
+2F90F>6F6E
+2F910>23F5E
+2F911>23F8E
+2F912>6FC6
+2F913>7039
+2F914>701E
+2F915>701B
+2F916>3D96
+2F917>704A
+2F918>707D
+2F919>7077
+2F91A>70AD
+2F91B>20525
+2F91C>7145
+2F91D>24263
+2F91E>719C
+2F91F>243AB
+2F920>7228
+2F921>7235
+2F922>7250
+2F923>24608
+2F924>7280
+2F925>7295
+2F926>24735
+2F927>24814
+2F928>737A
+2F929>738B
+2F92A>3EAC
+2F92B>73A5
+2F92C>3EB8
+2F92D>3EB8
+2F92E>7447
+2F92F>745C
+2F930>7471
+2F931>7485
+2F932>74CA
+2F933>3F1B
+2F934>7524
+2F935>24C36
+2F936>753E
+2F937>24C92
+2F938>7570
+2F939>2219F
+2F93A>7610
+2F93B>24FA1
+2F93C>24FB8
+2F93D>25044
+2F93E>3FFC
+2F93F>4008
+2F940>76F4
+2F941>250F3
+2F942>250F2
+2F943>25119
+2F944>25133
+2F945>771E
+2F946>771F
+2F947>771F
+2F948>774A
+2F949>4039
+2F94A>778B
+2F94B>4046
+2F94C>4096
+2F94D>2541D
+2F94E>784E
+2F94F>788C
+2F950>78CC
+2F951>40E3
+2F952>25626
+2F953>7956
+2F954>2569A
+2F955>256C5
+2F956>798F
+2F957>79EB
+2F958>412F
+2F959>7A40
+2F95A>7A4A
+2F95B>7A4F
+2F95C>2597C
+2F95D>25AA7
+2F95E>25AA7
+2F95F>7AEE
+2F960>4202
+2F961>25BAB
+2F962>7BC6
+2F963>7BC9
+2F964>4227
+2F965>25C80
+2F966>7CD2
+2F967>42A0
+2F968>7CE8
+2F969>7CE3
+2F96A>7D00
+2F96B>25F86
+2F96C>7D63
+2F96D>4301
+2F96E>7DC7
+2F96F>7E02
+2F970>7E45
+2F971>4334
+2F972>26228
+2F973>26247
+2F974>4359
+2F975>262D9
+2F976>7F7A
+2F977>2633E
+2F978>7F95
+2F979>7FFA
+2F97A>8005
+2F97B>264DA
+2F97C>26523
+2F97D>8060
+2F97E>265A8
+2F97F>8070
+2F980>2335F
+2F981>43D5
+2F982>80B2
+2F983>8103
+2F984>440B
+2F985>813E
+2F986>5AB5
+2F987>267A7
+2F988>267B5
+2F989>23393
+2F98A>2339C
+2F98B>8201
+2F98C>8204
+2F98D>8F9E
+2F98E>446B
+2F98F>8291
+2F990>828B
+2F991>829D
+2F992>52B3
+2F993>82B1
+2F994>82B3
+2F995>82BD
+2F996>82E6
+2F997>26B3C
+2F998>82E5
+2F999>831D
+2F99A>8363
+2F99B>83AD
+2F99C>8323
+2F99D>83BD
+2F99E>83E7
+2F99F>8457
+2F9A0>8353
+2F9A1>83CA
+2F9A2>83CC
+2F9A3>83DC
+2F9A4>26C36
+2F9A5>26D6B
+2F9A6>26CD5
+2F9A7>452B
+2F9A8>84F1
+2F9A9>84F3
+2F9AA>8516
+2F9AB>273CA
+2F9AC>8564
+2F9AD>26F2C
+2F9AE>455D
+2F9AF>4561
+2F9B0>26FB1
+2F9B1>270D2
+2F9B2>456B
+2F9B3>8650
+2F9B4>865C
+2F9B5>8667
+2F9B6>8669
+2F9B7>86A9
+2F9B8>8688
+2F9B9>870E
+2F9BA>86E2
+2F9BB>8779
+2F9BC>8728
+2F9BD>876B
+2F9BE>8786
+2F9BF>45D7
+2F9C0>87E1
+2F9C1>8801
+2F9C2>45F9
+2F9C3>8860
+2F9C4>8863
+2F9C5>27667
+2F9C6>88D7
+2F9C7>88DE
+2F9C8>4635
+2F9C9>88FA
+2F9CA>34BB
+2F9CB>278AE
+2F9CC>27966
+2F9CD>46BE
+2F9CE>46C7
+2F9CF>8AA0
+2F9D0>8AED
+2F9D1>8B8A
+2F9D2>8C55
+2F9D3>27CA8
+2F9D4>8CAB
+2F9D5>8CC1
+2F9D6>8D1B
+2F9D7>8D77
+2F9D8>27F2F
+2F9D9>20804
+2F9DA>8DCB
+2F9DB>8DBC
+2F9DC>8DF0
+2F9DD>208DE
+2F9DE>8ED4
+2F9DF>8F38
+2F9E0>285D2
+2F9E1>285ED
+2F9E2>9094
+2F9E3>90F1
+2F9E4>9111
+2F9E5>2872E
+2F9E6>911B
+2F9E7>9238
+2F9E8>92D7
+2F9E9>92D8
+2F9EA>927C
+2F9EB>93F9
+2F9EC>9415
+2F9ED>28BFA
+2F9EE>958B
+2F9EF>4995
+2F9F0>95B7
+2F9F1>28D77
+2F9F2>49E6
+2F9F3>96C3
+2F9F4>5DB2
+2F9F5>9723
+2F9F6>29145
+2F9F7>2921A
+2F9F8>4A6E
+2F9F9>4A76
+2F9FA>97E0
+2F9FB>2940A
+2F9FC>4AB2
+2F9FD>29496
+2F9FE>980B
+2F9FF>980B
+2FA00>9829
+2FA01>295B6
+2FA02>98E2
+2FA03>4B33
+2FA04>9929
+2FA05>99A7
+2FA06>99C2
+2FA07>99FE
+2FA08>4BCE
+2FA09>29B30
+2FA0A>9B12
+2FA0B>9C40
+2FA0C>9CFD
+2FA0D>4CCE
+2FA0E>4CED
+2FA0F>9D67
+2FA10>2A0CE
+2FA11>4CF8
+2FA12>2A105
+2FA13>2A20E
+2FA14>2A291
+2FA15>9EBB
+2FA16>4D56
+2FA17>9EF9
+2FA18>9EFE
+2FA19>9F05
+2FA1A>9F0F
+2FA1B>9F16
+2FA1C>9F3B
+2FA1D>2A600
diff --git a/mailnews/extensions/fts3/data/nfkc_cf.txt b/mailnews/extensions/fts3/data/nfkc_cf.txt
new file mode 100644
index 000000000..becabbbf3
--- /dev/null
+++ b/mailnews/extensions/fts3/data/nfkc_cf.txt
@@ -0,0 +1,5376 @@
+# Extracted from:
+# DerivedNormalizationProps-5.2.0.txt
+# Date: 2009-08-26, 18:18:50 GMT [MD]
+#
+# Unicode Character Database
+# Copyright (c) 1991-2009 Unicode, Inc.
+# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For documentation, see http://www.unicode.org/reports/tr44/
+
+# ================================================
+# This file has been reformatted into syntax for the
+# gennorm2 Normalizer2 data generator tool.
+# Only the NFKC_CF mappings are retained and reformatted.
+# Reformatting via regular expression: s/ *; NFKC_CF; */>/
+# Use this file as the second gennorm2 input file after nfkc.txt.
+# ================================================
+
+# Derived Property: NFKC_Casefold (NFKC_CF)
+# This property removes certain variations from characters: case, compatibility, and default-ignorables.
+# It is used for loose matching and certain types of identifiers.
+# It is constructed by applying NFKC, CaseFolding, and removal of Default_Ignorable_Code_Points.
+# The process of applying these transformations is repeated until a stable result is produced.
+# WARNING: Application to STRINGS must apply NFC after mapping each character, because characters may interact.
+# For more information, see [http://www.unicode.org/reports/tr44/]
+# Omitted code points are unchanged by this mapping.
+# @missing: 0000..10FFFF><code point>
+
+# All code points not explicitly listed for NFKC_Casefold
+# have the value <codepoint>.
+
+0041>0061
+0042>0062
+0043>0063
+0044>0064
+0045>0065
+0046>0066
+0047>0067
+0048>0068
+0049>0069
+004A>006A
+004B>006B
+004C>006C
+004D>006D
+004E>006E
+004F>006F
+0050>0070
+0051>0071
+0052>0072
+0053>0073
+0054>0074
+0055>0075
+0056>0076
+0057>0077
+0058>0078
+0059>0079
+005A>007A
+00A0>0020
+00A8>0020 0308
+00AA>0061
+00AD>
+00AF>0020 0304
+00B2>0032
+00B3>0033
+00B4>0020 0301
+00B5>03BC
+00B8>0020 0327
+00B9>0031
+00BA>006F
+00BC>0031 2044 0034
+00BD>0031 2044 0032
+00BE>0033 2044 0034
+00C0>00E0
+00C1>00E1
+00C2>00E2
+00C3>00E3
+00C4>00E4
+00C5>00E5
+00C6>00E6
+00C7>00E7
+00C8>00E8
+00C9>00E9
+00CA>00EA
+00CB>00EB
+00CC>00EC
+00CD>00ED
+00CE>00EE
+00CF>00EF
+00D0>00F0
+00D1>00F1
+00D2>00F2
+00D3>00F3
+00D4>00F4
+00D5>00F5
+00D6>00F6
+00D8>00F8
+00D9>00F9
+00DA>00FA
+00DB>00FB
+00DC>00FC
+00DD>00FD
+00DE>00FE
+00DF>0073 0073
+0100>0101
+0102>0103
+0104>0105
+0106>0107
+0108>0109
+010A>010B
+010C>010D
+010E>010F
+0110>0111
+0112>0113
+0114>0115
+0116>0117
+0118>0119
+011A>011B
+011C>011D
+011E>011F
+0120>0121
+0122>0123
+0124>0125
+0126>0127
+0128>0129
+012A>012B
+012C>012D
+012E>012F
+0130>0069 0307
+0132..0133>0069 006A
+0134>0135
+0136>0137
+0139>013A
+013B>013C
+013D>013E
+013F..0140>006C 00B7
+0141>0142
+0143>0144
+0145>0146
+0147>0148
+0149>02BC 006E
+014A>014B
+014C>014D
+014E>014F
+0150>0151
+0152>0153
+0154>0155
+0156>0157
+0158>0159
+015A>015B
+015C>015D
+015E>015F
+0160>0161
+0162>0163
+0164>0165
+0166>0167
+0168>0169
+016A>016B
+016C>016D
+016E>016F
+0170>0171
+0172>0173
+0174>0175
+0176>0177
+0178>00FF
+0179>017A
+017B>017C
+017D>017E
+017F>0073
+0181>0253
+0182>0183
+0184>0185
+0186>0254
+0187>0188
+0189>0256
+018A>0257
+018B>018C
+018E>01DD
+018F>0259
+0190>025B
+0191>0192
+0193>0260
+0194>0263
+0196>0269
+0197>0268
+0198>0199
+019C>026F
+019D>0272
+019F>0275
+01A0>01A1
+01A2>01A3
+01A4>01A5
+01A6>0280
+01A7>01A8
+01A9>0283
+01AC>01AD
+01AE>0288
+01AF>01B0
+01B1>028A
+01B2>028B
+01B3>01B4
+01B5>01B6
+01B7>0292
+01B8>01B9
+01BC>01BD
+01C4..01C6>0064 017E
+01C7..01C9>006C 006A
+01CA..01CC>006E 006A
+01CD>01CE
+01CF>01D0
+01D1>01D2
+01D3>01D4
+01D5>01D6
+01D7>01D8
+01D9>01DA
+01DB>01DC
+01DE>01DF
+01E0>01E1
+01E2>01E3
+01E4>01E5
+01E6>01E7
+01E8>01E9
+01EA>01EB
+01EC>01ED
+01EE>01EF
+01F1..01F3>0064 007A
+01F4>01F5
+01F6>0195
+01F7>01BF
+01F8>01F9
+01FA>01FB
+01FC>01FD
+01FE>01FF
+0200>0201
+0202>0203
+0204>0205
+0206>0207
+0208>0209
+020A>020B
+020C>020D
+020E>020F
+0210>0211
+0212>0213
+0214>0215
+0216>0217
+0218>0219
+021A>021B
+021C>021D
+021E>021F
+0220>019E
+0222>0223
+0224>0225
+0226>0227
+0228>0229
+022A>022B
+022C>022D
+022E>022F
+0230>0231
+0232>0233
+023A>2C65
+023B>023C
+023D>019A
+023E>2C66
+0241>0242
+0243>0180
+0244>0289
+0245>028C
+0246>0247
+0248>0249
+024A>024B
+024C>024D
+024E>024F
+02B0>0068
+02B1>0266
+02B2>006A
+02B3>0072
+02B4>0279
+02B5>027B
+02B6>0281
+02B7>0077
+02B8>0079
+02D8>0020 0306
+02D9>0020 0307
+02DA>0020 030A
+02DB>0020 0328
+02DC>0020 0303
+02DD>0020 030B
+02E0>0263
+02E1>006C
+02E2>0073
+02E3>0078
+02E4>0295
+0340>0300
+0341>0301
+0343>0313
+0344>0308 0301
+0345>03B9
+034F>
+0370>0371
+0372>0373
+0374>02B9
+0376>0377
+037A>0020 03B9
+037E>003B
+0384>0020 0301
+0385>0020 0308 0301
+0386>03AC
+0387>00B7
+0388>03AD
+0389>03AE
+038A>03AF
+038C>03CC
+038E>03CD
+038F>03CE
+0391>03B1
+0392>03B2
+0393>03B3
+0394>03B4
+0395>03B5
+0396>03B6
+0397>03B7
+0398>03B8
+0399>03B9
+039A>03BA
+039B>03BB
+039C>03BC
+039D>03BD
+039E>03BE
+039F>03BF
+03A0>03C0
+03A1>03C1
+03A3>03C3
+03A4>03C4
+03A5>03C5
+03A6>03C6
+03A7>03C7
+03A8>03C8
+03A9>03C9
+03AA>03CA
+03AB>03CB
+03C2>03C3
+03CF>03D7
+03D0>03B2
+03D1>03B8
+03D2>03C5
+03D3>03CD
+03D4>03CB
+03D5>03C6
+03D6>03C0
+03D8>03D9
+03DA>03DB
+03DC>03DD
+03DE>03DF
+03E0>03E1
+03E2>03E3
+03E4>03E5
+03E6>03E7
+03E8>03E9
+03EA>03EB
+03EC>03ED
+03EE>03EF
+03F0>03BA
+03F1>03C1
+03F2>03C3
+03F4>03B8
+03F5>03B5
+03F7>03F8
+03F9>03C3
+03FA>03FB
+03FD>037B
+03FE>037C
+03FF>037D
+0400>0450
+0401>0451
+0402>0452
+0403>0453
+0404>0454
+0405>0455
+0406>0456
+0407>0457
+0408>0458
+0409>0459
+040A>045A
+040B>045B
+040C>045C
+040D>045D
+040E>045E
+040F>045F
+0410>0430
+0411>0431
+0412>0432
+0413>0433
+0414>0434
+0415>0435
+0416>0436
+0417>0437
+0418>0438
+0419>0439
+041A>043A
+041B>043B
+041C>043C
+041D>043D
+041E>043E
+041F>043F
+0420>0440
+0421>0441
+0422>0442
+0423>0443
+0424>0444
+0425>0445
+0426>0446
+0427>0447
+0428>0448
+0429>0449
+042A>044A
+042B>044B
+042C>044C
+042D>044D
+042E>044E
+042F>044F
+0460>0461
+0462>0463
+0464>0465
+0466>0467
+0468>0469
+046A>046B
+046C>046D
+046E>046F
+0470>0471
+0472>0473
+0474>0475
+0476>0477
+0478>0479
+047A>047B
+047C>047D
+047E>047F
+0480>0481
+048A>048B
+048C>048D
+048E>048F
+0490>0491
+0492>0493
+0494>0495
+0496>0497
+0498>0499
+049A>049B
+049C>049D
+049E>049F
+04A0>04A1
+04A2>04A3
+04A4>04A5
+04A6>04A7
+04A8>04A9
+04AA>04AB
+04AC>04AD
+04AE>04AF
+04B0>04B1
+04B2>04B3
+04B4>04B5
+04B6>04B7
+04B8>04B9
+04BA>04BB
+04BC>04BD
+04BE>04BF
+04C0>04CF
+04C1>04C2
+04C3>04C4
+04C5>04C6
+04C7>04C8
+04C9>04CA
+04CB>04CC
+04CD>04CE
+04D0>04D1
+04D2>04D3
+04D4>04D5
+04D6>04D7
+04D8>04D9
+04DA>04DB
+04DC>04DD
+04DE>04DF
+04E0>04E1
+04E2>04E3
+04E4>04E5
+04E6>04E7
+04E8>04E9
+04EA>04EB
+04EC>04ED
+04EE>04EF
+04F0>04F1
+04F2>04F3
+04F4>04F5
+04F6>04F7
+04F8>04F9
+04FA>04FB
+04FC>04FD
+04FE>04FF
+0500>0501
+0502>0503
+0504>0505
+0506>0507
+0508>0509
+050A>050B
+050C>050D
+050E>050F
+0510>0511
+0512>0513
+0514>0515
+0516>0517
+0518>0519
+051A>051B
+051C>051D
+051E>051F
+0520>0521
+0522>0523
+0524>0525
+0531>0561
+0532>0562
+0533>0563
+0534>0564
+0535>0565
+0536>0566
+0537>0567
+0538>0568
+0539>0569
+053A>056A
+053B>056B
+053C>056C
+053D>056D
+053E>056E
+053F>056F
+0540>0570
+0541>0571
+0542>0572
+0543>0573
+0544>0574
+0545>0575
+0546>0576
+0547>0577
+0548>0578
+0549>0579
+054A>057A
+054B>057B
+054C>057C
+054D>057D
+054E>057E
+054F>057F
+0550>0580
+0551>0581
+0552>0582
+0553>0583
+0554>0584
+0555>0585
+0556>0586
+0587>0565 0582
+0675>0627 0674
+0676>0648 0674
+0677>06C7 0674
+0678>064A 0674
+0958>0915 093C
+0959>0916 093C
+095A>0917 093C
+095B>091C 093C
+095C>0921 093C
+095D>0922 093C
+095E>092B 093C
+095F>092F 093C
+09DC>09A1 09BC
+09DD>09A2 09BC
+09DF>09AF 09BC
+0A33>0A32 0A3C
+0A36>0A38 0A3C
+0A59>0A16 0A3C
+0A5A>0A17 0A3C
+0A5B>0A1C 0A3C
+0A5E>0A2B 0A3C
+0B5C>0B21 0B3C
+0B5D>0B22 0B3C
+0E33>0E4D 0E32
+0EB3>0ECD 0EB2
+0EDC>0EAB 0E99
+0EDD>0EAB 0EA1
+0F0C>0F0B
+0F43>0F42 0FB7
+0F4D>0F4C 0FB7
+0F52>0F51 0FB7
+0F57>0F56 0FB7
+0F5C>0F5B 0FB7
+0F69>0F40 0FB5
+0F73>0F71 0F72
+0F75>0F71 0F74
+0F76>0FB2 0F80
+0F77>0FB2 0F71 0F80
+0F78>0FB3 0F80
+0F79>0FB3 0F71 0F80
+0F81>0F71 0F80
+0F93>0F92 0FB7
+0F9D>0F9C 0FB7
+0FA2>0FA1 0FB7
+0FA7>0FA6 0FB7
+0FAC>0FAB 0FB7
+0FB9>0F90 0FB5
+10A0>2D00
+10A1>2D01
+10A2>2D02
+10A3>2D03
+10A4>2D04
+10A5>2D05
+10A6>2D06
+10A7>2D07
+10A8>2D08
+10A9>2D09
+10AA>2D0A
+10AB>2D0B
+10AC>2D0C
+10AD>2D0D
+10AE>2D0E
+10AF>2D0F
+10B0>2D10
+10B1>2D11
+10B2>2D12
+10B3>2D13
+10B4>2D14
+10B5>2D15
+10B6>2D16
+10B7>2D17
+10B8>2D18
+10B9>2D19
+10BA>2D1A
+10BB>2D1B
+10BC>2D1C
+10BD>2D1D
+10BE>2D1E
+10BF>2D1F
+10C0>2D20
+10C1>2D21
+10C2>2D22
+10C3>2D23
+10C4>2D24
+10C5>2D25
+10FC>10DC
+115F..1160>
+17B4..17B5>
+180B..180D>
+1D2C>0061
+1D2D>00E6
+1D2E>0062
+1D30>0064
+1D31>0065
+1D32>01DD
+1D33>0067
+1D34>0068
+1D35>0069
+1D36>006A
+1D37>006B
+1D38>006C
+1D39>006D
+1D3A>006E
+1D3C>006F
+1D3D>0223
+1D3E>0070
+1D3F>0072
+1D40>0074
+1D41>0075
+1D42>0077
+1D43>0061
+1D44>0250
+1D45>0251
+1D46>1D02
+1D47>0062
+1D48>0064
+1D49>0065
+1D4A>0259
+1D4B>025B
+1D4C>025C
+1D4D>0067
+1D4F>006B
+1D50>006D
+1D51>014B
+1D52>006F
+1D53>0254
+1D54>1D16
+1D55>1D17
+1D56>0070
+1D57>0074
+1D58>0075
+1D59>1D1D
+1D5A>026F
+1D5B>0076
+1D5C>1D25
+1D5D>03B2
+1D5E>03B3
+1D5F>03B4
+1D60>03C6
+1D61>03C7
+1D62>0069
+1D63>0072
+1D64>0075
+1D65>0076
+1D66>03B2
+1D67>03B3
+1D68>03C1
+1D69>03C6
+1D6A>03C7
+1D78>043D
+1D9B>0252
+1D9C>0063
+1D9D>0255
+1D9E>00F0
+1D9F>025C
+1DA0>0066
+1DA1>025F
+1DA2>0261
+1DA3>0265
+1DA4>0268
+1DA5>0269
+1DA6>026A
+1DA7>1D7B
+1DA8>029D
+1DA9>026D
+1DAA>1D85
+1DAB>029F
+1DAC>0271
+1DAD>0270
+1DAE>0272
+1DAF>0273
+1DB0>0274
+1DB1>0275
+1DB2>0278
+1DB3>0282
+1DB4>0283
+1DB5>01AB
+1DB6>0289
+1DB7>028A
+1DB8>1D1C
+1DB9>028B
+1DBA>028C
+1DBB>007A
+1DBC>0290
+1DBD>0291
+1DBE>0292
+1DBF>03B8
+1E00>1E01
+1E02>1E03
+1E04>1E05
+1E06>1E07
+1E08>1E09
+1E0A>1E0B
+1E0C>1E0D
+1E0E>1E0F
+1E10>1E11
+1E12>1E13
+1E14>1E15
+1E16>1E17
+1E18>1E19
+1E1A>1E1B
+1E1C>1E1D
+1E1E>1E1F
+1E20>1E21
+1E22>1E23
+1E24>1E25
+1E26>1E27
+1E28>1E29
+1E2A>1E2B
+1E2C>1E2D
+1E2E>1E2F
+1E30>1E31
+1E32>1E33
+1E34>1E35
+1E36>1E37
+1E38>1E39
+1E3A>1E3B
+1E3C>1E3D
+1E3E>1E3F
+1E40>1E41
+1E42>1E43
+1E44>1E45
+1E46>1E47
+1E48>1E49
+1E4A>1E4B
+1E4C>1E4D
+1E4E>1E4F
+1E50>1E51
+1E52>1E53
+1E54>1E55
+1E56>1E57
+1E58>1E59
+1E5A>1E5B
+1E5C>1E5D
+1E5E>1E5F
+1E60>1E61
+1E62>1E63
+1E64>1E65
+1E66>1E67
+1E68>1E69
+1E6A>1E6B
+1E6C>1E6D
+1E6E>1E6F
+1E70>1E71
+1E72>1E73
+1E74>1E75
+1E76>1E77
+1E78>1E79
+1E7A>1E7B
+1E7C>1E7D
+1E7E>1E7F
+1E80>1E81
+1E82>1E83
+1E84>1E85
+1E86>1E87
+1E88>1E89
+1E8A>1E8B
+1E8C>1E8D
+1E8E>1E8F
+1E90>1E91
+1E92>1E93
+1E94>1E95
+1E9A>0061 02BE
+1E9B>1E61
+1E9E>0073 0073
+1EA0>1EA1
+1EA2>1EA3
+1EA4>1EA5
+1EA6>1EA7
+1EA8>1EA9
+1EAA>1EAB
+1EAC>1EAD
+1EAE>1EAF
+1EB0>1EB1
+1EB2>1EB3
+1EB4>1EB5
+1EB6>1EB7
+1EB8>1EB9
+1EBA>1EBB
+1EBC>1EBD
+1EBE>1EBF
+1EC0>1EC1
+1EC2>1EC3
+1EC4>1EC5
+1EC6>1EC7
+1EC8>1EC9
+1ECA>1ECB
+1ECC>1ECD
+1ECE>1ECF
+1ED0>1ED1
+1ED2>1ED3
+1ED4>1ED5
+1ED6>1ED7
+1ED8>1ED9
+1EDA>1EDB
+1EDC>1EDD
+1EDE>1EDF
+1EE0>1EE1
+1EE2>1EE3
+1EE4>1EE5
+1EE6>1EE7
+1EE8>1EE9
+1EEA>1EEB
+1EEC>1EED
+1EEE>1EEF
+1EF0>1EF1
+1EF2>1EF3
+1EF4>1EF5
+1EF6>1EF7
+1EF8>1EF9
+1EFA>1EFB
+1EFC>1EFD
+1EFE>1EFF
+1F08>1F00
+1F09>1F01
+1F0A>1F02
+1F0B>1F03
+1F0C>1F04
+1F0D>1F05
+1F0E>1F06
+1F0F>1F07
+1F18>1F10
+1F19>1F11
+1F1A>1F12
+1F1B>1F13
+1F1C>1F14
+1F1D>1F15
+1F28>1F20
+1F29>1F21
+1F2A>1F22
+1F2B>1F23
+1F2C>1F24
+1F2D>1F25
+1F2E>1F26
+1F2F>1F27
+1F38>1F30
+1F39>1F31
+1F3A>1F32
+1F3B>1F33
+1F3C>1F34
+1F3D>1F35
+1F3E>1F36
+1F3F>1F37
+1F48>1F40
+1F49>1F41
+1F4A>1F42
+1F4B>1F43
+1F4C>1F44
+1F4D>1F45
+1F59>1F51
+1F5B>1F53
+1F5D>1F55
+1F5F>1F57
+1F68>1F60
+1F69>1F61
+1F6A>1F62
+1F6B>1F63
+1F6C>1F64
+1F6D>1F65
+1F6E>1F66
+1F6F>1F67
+1F71>03AC
+1F73>03AD
+1F75>03AE
+1F77>03AF
+1F79>03CC
+1F7B>03CD
+1F7D>03CE
+1F80>1F00 03B9
+1F81>1F01 03B9
+1F82>1F02 03B9
+1F83>1F03 03B9
+1F84>1F04 03B9
+1F85>1F05 03B9
+1F86>1F06 03B9
+1F87>1F07 03B9
+1F88>1F00 03B9
+1F89>1F01 03B9
+1F8A>1F02 03B9
+1F8B>1F03 03B9
+1F8C>1F04 03B9
+1F8D>1F05 03B9
+1F8E>1F06 03B9
+1F8F>1F07 03B9
+1F90>1F20 03B9
+1F91>1F21 03B9
+1F92>1F22 03B9
+1F93>1F23 03B9
+1F94>1F24 03B9
+1F95>1F25 03B9
+1F96>1F26 03B9
+1F97>1F27 03B9
+1F98>1F20 03B9
+1F99>1F21 03B9
+1F9A>1F22 03B9
+1F9B>1F23 03B9
+1F9C>1F24 03B9
+1F9D>1F25 03B9
+1F9E>1F26 03B9
+1F9F>1F27 03B9
+1FA0>1F60 03B9
+1FA1>1F61 03B9
+1FA2>1F62 03B9
+1FA3>1F63 03B9
+1FA4>1F64 03B9
+1FA5>1F65 03B9
+1FA6>1F66 03B9
+1FA7>1F67 03B9
+1FA8>1F60 03B9
+1FA9>1F61 03B9
+1FAA>1F62 03B9
+1FAB>1F63 03B9
+1FAC>1F64 03B9
+1FAD>1F65 03B9
+1FAE>1F66 03B9
+1FAF>1F67 03B9
+1FB2>1F70 03B9
+1FB3>03B1 03B9
+1FB4>03AC 03B9
+1FB7>1FB6 03B9
+1FB8>1FB0
+1FB9>1FB1
+1FBA>1F70
+1FBB>03AC
+1FBC>03B1 03B9
+1FBD>0020 0313
+1FBE>03B9
+1FBF>0020 0313
+1FC0>0020 0342
+1FC1>0020 0308 0342
+1FC2>1F74 03B9
+1FC3>03B7 03B9
+1FC4>03AE 03B9
+1FC7>1FC6 03B9
+1FC8>1F72
+1FC9>03AD
+1FCA>1F74
+1FCB>03AE
+1FCC>03B7 03B9
+1FCD>0020 0313 0300
+1FCE>0020 0313 0301
+1FCF>0020 0313 0342
+1FD3>0390
+1FD8>1FD0
+1FD9>1FD1
+1FDA>1F76
+1FDB>03AF
+1FDD>0020 0314 0300
+1FDE>0020 0314 0301
+1FDF>0020 0314 0342
+1FE3>03B0
+1FE8>1FE0
+1FE9>1FE1
+1FEA>1F7A
+1FEB>03CD
+1FEC>1FE5
+1FED>0020 0308 0300
+1FEE>0020 0308 0301
+1FEF>0060
+1FF2>1F7C 03B9
+1FF3>03C9 03B9
+1FF4>03CE 03B9
+1FF7>1FF6 03B9
+1FF8>1F78
+1FF9>03CC
+1FFA>1F7C
+1FFB>03CE
+1FFC>03C9 03B9
+1FFD>0020 0301
+1FFE>0020 0314
+2000..200A>0020
+200B..200F>
+2011>2010
+2017>0020 0333
+2024>002E
+2025>002E 002E
+2026>002E 002E 002E
+202A..202E>
+202F>0020
+2033>2032 2032
+2034>2032 2032 2032
+2036>2035 2035
+2037>2035 2035 2035
+203C>0021 0021
+203E>0020 0305
+2047>003F 003F
+2048>003F 0021
+2049>0021 003F
+2057>2032 2032 2032 2032
+205F>0020
+2060..2064>
+2065..2069>
+206A..206F>
+2070>0030
+2071>0069
+2074>0034
+2075>0035
+2076>0036
+2077>0037
+2078>0038
+2079>0039
+207A>002B
+207B>2212
+207C>003D
+207D>0028
+207E>0029
+207F>006E
+2080>0030
+2081>0031
+2082>0032
+2083>0033
+2084>0034
+2085>0035
+2086>0036
+2087>0037
+2088>0038
+2089>0039
+208A>002B
+208B>2212
+208C>003D
+208D>0028
+208E>0029
+2090>0061
+2091>0065
+2092>006F
+2093>0078
+2094>0259
+20A8>0072 0073
+2100>0061 002F 0063
+2101>0061 002F 0073
+2102>0063
+2103>00B0 0063
+2105>0063 002F 006F
+2106>0063 002F 0075
+2107>025B
+2109>00B0 0066
+210A>0067
+210B..210E>0068
+210F>0127
+2110..2111>0069
+2112..2113>006C
+2115>006E
+2116>006E 006F
+2119>0070
+211A>0071
+211B..211D>0072
+2120>0073 006D
+2121>0074 0065 006C
+2122>0074 006D
+2124>007A
+2126>03C9
+2128>007A
+212A>006B
+212B>00E5
+212C>0062
+212D>0063
+212F..2130>0065
+2131>0066
+2132>214E
+2133>006D
+2134>006F
+2135>05D0
+2136>05D1
+2137>05D2
+2138>05D3
+2139>0069
+213B>0066 0061 0078
+213C>03C0
+213D..213E>03B3
+213F>03C0
+2140>2211
+2145..2146>0064
+2147>0065
+2148>0069
+2149>006A
+2150>0031 2044 0037
+2151>0031 2044 0039
+2152>0031 2044 0031 0030
+2153>0031 2044 0033
+2154>0032 2044 0033
+2155>0031 2044 0035
+2156>0032 2044 0035
+2157>0033 2044 0035
+2158>0034 2044 0035
+2159>0031 2044 0036
+215A>0035 2044 0036
+215B>0031 2044 0038
+215C>0033 2044 0038
+215D>0035 2044 0038
+215E>0037 2044 0038
+215F>0031 2044
+2160>0069
+2161>0069 0069
+2162>0069 0069 0069
+2163>0069 0076
+2164>0076
+2165>0076 0069
+2166>0076 0069 0069
+2167>0076 0069 0069 0069
+2168>0069 0078
+2169>0078
+216A>0078 0069
+216B>0078 0069 0069
+216C>006C
+216D>0063
+216E>0064
+216F>006D
+2170>0069
+2171>0069 0069
+2172>0069 0069 0069
+2173>0069 0076
+2174>0076
+2175>0076 0069
+2176>0076 0069 0069
+2177>0076 0069 0069 0069
+2178>0069 0078
+2179>0078
+217A>0078 0069
+217B>0078 0069 0069
+217C>006C
+217D>0063
+217E>0064
+217F>006D
+2183>2184
+2189>0030 2044 0033
+222C>222B 222B
+222D>222B 222B 222B
+222F>222E 222E
+2230>222E 222E 222E
+2329>3008
+232A>3009
+2460>0031
+2461>0032
+2462>0033
+2463>0034
+2464>0035
+2465>0036
+2466>0037
+2467>0038
+2468>0039
+2469>0031 0030
+246A>0031 0031
+246B>0031 0032
+246C>0031 0033
+246D>0031 0034
+246E>0031 0035
+246F>0031 0036
+2470>0031 0037
+2471>0031 0038
+2472>0031 0039
+2473>0032 0030
+2474>0028 0031 0029
+2475>0028 0032 0029
+2476>0028 0033 0029
+2477>0028 0034 0029
+2478>0028 0035 0029
+2479>0028 0036 0029
+247A>0028 0037 0029
+247B>0028 0038 0029
+247C>0028 0039 0029
+247D>0028 0031 0030 0029
+247E>0028 0031 0031 0029
+247F>0028 0031 0032 0029
+2480>0028 0031 0033 0029
+2481>0028 0031 0034 0029
+2482>0028 0031 0035 0029
+2483>0028 0031 0036 0029
+2484>0028 0031 0037 0029
+2485>0028 0031 0038 0029
+2486>0028 0031 0039 0029
+2487>0028 0032 0030 0029
+2488>0031 002E
+2489>0032 002E
+248A>0033 002E
+248B>0034 002E
+248C>0035 002E
+248D>0036 002E
+248E>0037 002E
+248F>0038 002E
+2490>0039 002E
+2491>0031 0030 002E
+2492>0031 0031 002E
+2493>0031 0032 002E
+2494>0031 0033 002E
+2495>0031 0034 002E
+2496>0031 0035 002E
+2497>0031 0036 002E
+2498>0031 0037 002E
+2499>0031 0038 002E
+249A>0031 0039 002E
+249B>0032 0030 002E
+249C>0028 0061 0029
+249D>0028 0062 0029
+249E>0028 0063 0029
+249F>0028 0064 0029
+24A0>0028 0065 0029
+24A1>0028 0066 0029
+24A2>0028 0067 0029
+24A3>0028 0068 0029
+24A4>0028 0069 0029
+24A5>0028 006A 0029
+24A6>0028 006B 0029
+24A7>0028 006C 0029
+24A8>0028 006D 0029
+24A9>0028 006E 0029
+24AA>0028 006F 0029
+24AB>0028 0070 0029
+24AC>0028 0071 0029
+24AD>0028 0072 0029
+24AE>0028 0073 0029
+24AF>0028 0074 0029
+24B0>0028 0075 0029
+24B1>0028 0076 0029
+24B2>0028 0077 0029
+24B3>0028 0078 0029
+24B4>0028 0079 0029
+24B5>0028 007A 0029
+24B6>0061
+24B7>0062
+24B8>0063
+24B9>0064
+24BA>0065
+24BB>0066
+24BC>0067
+24BD>0068
+24BE>0069
+24BF>006A
+24C0>006B
+24C1>006C
+24C2>006D
+24C3>006E
+24C4>006F
+24C5>0070
+24C6>0071
+24C7>0072
+24C8>0073
+24C9>0074
+24CA>0075
+24CB>0076
+24CC>0077
+24CD>0078
+24CE>0079
+24CF>007A
+24D0>0061
+24D1>0062
+24D2>0063
+24D3>0064
+24D4>0065
+24D5>0066
+24D6>0067
+24D7>0068
+24D8>0069
+24D9>006A
+24DA>006B
+24DB>006C
+24DC>006D
+24DD>006E
+24DE>006F
+24DF>0070
+24E0>0071
+24E1>0072
+24E2>0073
+24E3>0074
+24E4>0075
+24E5>0076
+24E6>0077
+24E7>0078
+24E8>0079
+24E9>007A
+24EA>0030
+2A0C>222B 222B 222B 222B
+2A74>003A 003A 003D
+2A75>003D 003D
+2A76>003D 003D 003D
+2ADC>2ADD 0338
+2C00>2C30
+2C01>2C31
+2C02>2C32
+2C03>2C33
+2C04>2C34
+2C05>2C35
+2C06>2C36
+2C07>2C37
+2C08>2C38
+2C09>2C39
+2C0A>2C3A
+2C0B>2C3B
+2C0C>2C3C
+2C0D>2C3D
+2C0E>2C3E
+2C0F>2C3F
+2C10>2C40
+2C11>2C41
+2C12>2C42
+2C13>2C43
+2C14>2C44
+2C15>2C45
+2C16>2C46
+2C17>2C47
+2C18>2C48
+2C19>2C49
+2C1A>2C4A
+2C1B>2C4B
+2C1C>2C4C
+2C1D>2C4D
+2C1E>2C4E
+2C1F>2C4F
+2C20>2C50
+2C21>2C51
+2C22>2C52
+2C23>2C53
+2C24>2C54
+2C25>2C55
+2C26>2C56
+2C27>2C57
+2C28>2C58
+2C29>2C59
+2C2A>2C5A
+2C2B>2C5B
+2C2C>2C5C
+2C2D>2C5D
+2C2E>2C5E
+2C60>2C61
+2C62>026B
+2C63>1D7D
+2C64>027D
+2C67>2C68
+2C69>2C6A
+2C6B>2C6C
+2C6D>0251
+2C6E>0271
+2C6F>0250
+2C70>0252
+2C72>2C73
+2C75>2C76
+2C7C>006A
+2C7D>0076
+2C7E>023F
+2C7F>0240
+2C80>2C81
+2C82>2C83
+2C84>2C85
+2C86>2C87
+2C88>2C89
+2C8A>2C8B
+2C8C>2C8D
+2C8E>2C8F
+2C90>2C91
+2C92>2C93
+2C94>2C95
+2C96>2C97
+2C98>2C99
+2C9A>2C9B
+2C9C>2C9D
+2C9E>2C9F
+2CA0>2CA1
+2CA2>2CA3
+2CA4>2CA5
+2CA6>2CA7
+2CA8>2CA9
+2CAA>2CAB
+2CAC>2CAD
+2CAE>2CAF
+2CB0>2CB1
+2CB2>2CB3
+2CB4>2CB5
+2CB6>2CB7
+2CB8>2CB9
+2CBA>2CBB
+2CBC>2CBD
+2CBE>2CBF
+2CC0>2CC1
+2CC2>2CC3
+2CC4>2CC5
+2CC6>2CC7
+2CC8>2CC9
+2CCA>2CCB
+2CCC>2CCD
+2CCE>2CCF
+2CD0>2CD1
+2CD2>2CD3
+2CD4>2CD5
+2CD6>2CD7
+2CD8>2CD9
+2CDA>2CDB
+2CDC>2CDD
+2CDE>2CDF
+2CE0>2CE1
+2CE2>2CE3
+2CEB>2CEC
+2CED>2CEE
+2D6F>2D61
+2E9F>6BCD
+2EF3>9F9F
+2F00>4E00
+2F01>4E28
+2F02>4E36
+2F03>4E3F
+2F04>4E59
+2F05>4E85
+2F06>4E8C
+2F07>4EA0
+2F08>4EBA
+2F09>513F
+2F0A>5165
+2F0B>516B
+2F0C>5182
+2F0D>5196
+2F0E>51AB
+2F0F>51E0
+2F10>51F5
+2F11>5200
+2F12>529B
+2F13>52F9
+2F14>5315
+2F15>531A
+2F16>5338
+2F17>5341
+2F18>535C
+2F19>5369
+2F1A>5382
+2F1B>53B6
+2F1C>53C8
+2F1D>53E3
+2F1E>56D7
+2F1F>571F
+2F20>58EB
+2F21>5902
+2F22>590A
+2F23>5915
+2F24>5927
+2F25>5973
+2F26>5B50
+2F27>5B80
+2F28>5BF8
+2F29>5C0F
+2F2A>5C22
+2F2B>5C38
+2F2C>5C6E
+2F2D>5C71
+2F2E>5DDB
+2F2F>5DE5
+2F30>5DF1
+2F31>5DFE
+2F32>5E72
+2F33>5E7A
+2F34>5E7F
+2F35>5EF4
+2F36>5EFE
+2F37>5F0B
+2F38>5F13
+2F39>5F50
+2F3A>5F61
+2F3B>5F73
+2F3C>5FC3
+2F3D>6208
+2F3E>6236
+2F3F>624B
+2F40>652F
+2F41>6534
+2F42>6587
+2F43>6597
+2F44>65A4
+2F45>65B9
+2F46>65E0
+2F47>65E5
+2F48>66F0
+2F49>6708
+2F4A>6728
+2F4B>6B20
+2F4C>6B62
+2F4D>6B79
+2F4E>6BB3
+2F4F>6BCB
+2F50>6BD4
+2F51>6BDB
+2F52>6C0F
+2F53>6C14
+2F54>6C34
+2F55>706B
+2F56>722A
+2F57>7236
+2F58>723B
+2F59>723F
+2F5A>7247
+2F5B>7259
+2F5C>725B
+2F5D>72AC
+2F5E>7384
+2F5F>7389
+2F60>74DC
+2F61>74E6
+2F62>7518
+2F63>751F
+2F64>7528
+2F65>7530
+2F66>758B
+2F67>7592
+2F68>7676
+2F69>767D
+2F6A>76AE
+2F6B>76BF
+2F6C>76EE
+2F6D>77DB
+2F6E>77E2
+2F6F>77F3
+2F70>793A
+2F71>79B8
+2F72>79BE
+2F73>7A74
+2F74>7ACB
+2F75>7AF9
+2F76>7C73
+2F77>7CF8
+2F78>7F36
+2F79>7F51
+2F7A>7F8A
+2F7B>7FBD
+2F7C>8001
+2F7D>800C
+2F7E>8012
+2F7F>8033
+2F80>807F
+2F81>8089
+2F82>81E3
+2F83>81EA
+2F84>81F3
+2F85>81FC
+2F86>820C
+2F87>821B
+2F88>821F
+2F89>826E
+2F8A>8272
+2F8B>8278
+2F8C>864D
+2F8D>866B
+2F8E>8840
+2F8F>884C
+2F90>8863
+2F91>897E
+2F92>898B
+2F93>89D2
+2F94>8A00
+2F95>8C37
+2F96>8C46
+2F97>8C55
+2F98>8C78
+2F99>8C9D
+2F9A>8D64
+2F9B>8D70
+2F9C>8DB3
+2F9D>8EAB
+2F9E>8ECA
+2F9F>8F9B
+2FA0>8FB0
+2FA1>8FB5
+2FA2>9091
+2FA3>9149
+2FA4>91C6
+2FA5>91CC
+2FA6>91D1
+2FA7>9577
+2FA8>9580
+2FA9>961C
+2FAA>96B6
+2FAB>96B9
+2FAC>96E8
+2FAD>9751
+2FAE>975E
+2FAF>9762
+2FB0>9769
+2FB1>97CB
+2FB2>97ED
+2FB3>97F3
+2FB4>9801
+2FB5>98A8
+2FB6>98DB
+2FB7>98DF
+2FB8>9996
+2FB9>9999
+2FBA>99AC
+2FBB>9AA8
+2FBC>9AD8
+2FBD>9ADF
+2FBE>9B25
+2FBF>9B2F
+2FC0>9B32
+2FC1>9B3C
+2FC2>9B5A
+2FC3>9CE5
+2FC4>9E75
+2FC5>9E7F
+2FC6>9EA5
+2FC7>9EBB
+2FC8>9EC3
+2FC9>9ECD
+2FCA>9ED1
+2FCB>9EF9
+2FCC>9EFD
+2FCD>9F0E
+2FCE>9F13
+2FCF>9F20
+2FD0>9F3B
+2FD1>9F4A
+2FD2>9F52
+2FD3>9F8D
+2FD4>9F9C
+2FD5>9FA0
+3000>0020
+3036>3012
+3038>5341
+3039>5344
+303A>5345
+309B>0020 3099
+309C>0020 309A
+309F>3088 308A
+30FF>30B3 30C8
+3131>1100
+3132>1101
+3133>11AA
+3134>1102
+3135>11AC
+3136>11AD
+3137>1103
+3138>1104
+3139>1105
+313A>11B0
+313B>11B1
+313C>11B2
+313D>11B3
+313E>11B4
+313F>11B5
+3140>111A
+3141>1106
+3142>1107
+3143>1108
+3144>1121
+3145>1109
+3146>110A
+3147>110B
+3148>110C
+3149>110D
+314A>110E
+314B>110F
+314C>1110
+314D>1111
+314E>1112
+314F>1161
+3150>1162
+3151>1163
+3152>1164
+3153>1165
+3154>1166
+3155>1167
+3156>1168
+3157>1169
+3158>116A
+3159>116B
+315A>116C
+315B>116D
+315C>116E
+315D>116F
+315E>1170
+315F>1171
+3160>1172
+3161>1173
+3162>1174
+3163>1175
+3164>
+3165>1114
+3166>1115
+3167>11C7
+3168>11C8
+3169>11CC
+316A>11CE
+316B>11D3
+316C>11D7
+316D>11D9
+316E>111C
+316F>11DD
+3170>11DF
+3171>111D
+3172>111E
+3173>1120
+3174>1122
+3175>1123
+3176>1127
+3177>1129
+3178>112B
+3179>112C
+317A>112D
+317B>112E
+317C>112F
+317D>1132
+317E>1136
+317F>1140
+3180>1147
+3181>114C
+3182>11F1
+3183>11F2
+3184>1157
+3185>1158
+3186>1159
+3187>1184
+3188>1185
+3189>1188
+318A>1191
+318B>1192
+318C>1194
+318D>119E
+318E>11A1
+3192>4E00
+3193>4E8C
+3194>4E09
+3195>56DB
+3196>4E0A
+3197>4E2D
+3198>4E0B
+3199>7532
+319A>4E59
+319B>4E19
+319C>4E01
+319D>5929
+319E>5730
+319F>4EBA
+3200>0028 1100 0029
+3201>0028 1102 0029
+3202>0028 1103 0029
+3203>0028 1105 0029
+3204>0028 1106 0029
+3205>0028 1107 0029
+3206>0028 1109 0029
+3207>0028 110B 0029
+3208>0028 110C 0029
+3209>0028 110E 0029
+320A>0028 110F 0029
+320B>0028 1110 0029
+320C>0028 1111 0029
+320D>0028 1112 0029
+320E>0028 AC00 0029
+320F>0028 B098 0029
+3210>0028 B2E4 0029
+3211>0028 B77C 0029
+3212>0028 B9C8 0029
+3213>0028 BC14 0029
+3214>0028 C0AC 0029
+3215>0028 C544 0029
+3216>0028 C790 0029
+3217>0028 CC28 0029
+3218>0028 CE74 0029
+3219>0028 D0C0 0029
+321A>0028 D30C 0029
+321B>0028 D558 0029
+321C>0028 C8FC 0029
+321D>0028 C624 C804 0029
+321E>0028 C624 D6C4 0029
+3220>0028 4E00 0029
+3221>0028 4E8C 0029
+3222>0028 4E09 0029
+3223>0028 56DB 0029
+3224>0028 4E94 0029
+3225>0028 516D 0029
+3226>0028 4E03 0029
+3227>0028 516B 0029
+3228>0028 4E5D 0029
+3229>0028 5341 0029
+322A>0028 6708 0029
+322B>0028 706B 0029
+322C>0028 6C34 0029
+322D>0028 6728 0029
+322E>0028 91D1 0029
+322F>0028 571F 0029
+3230>0028 65E5 0029
+3231>0028 682A 0029
+3232>0028 6709 0029
+3233>0028 793E 0029
+3234>0028 540D 0029
+3235>0028 7279 0029
+3236>0028 8CA1 0029
+3237>0028 795D 0029
+3238>0028 52B4 0029
+3239>0028 4EE3 0029
+323A>0028 547C 0029
+323B>0028 5B66 0029
+323C>0028 76E3 0029
+323D>0028 4F01 0029
+323E>0028 8CC7 0029
+323F>0028 5354 0029
+3240>0028 796D 0029
+3241>0028 4F11 0029
+3242>0028 81EA 0029
+3243>0028 81F3 0029
+3244>554F
+3245>5E7C
+3246>6587
+3247>7B8F
+3250>0070 0074 0065
+3251>0032 0031
+3252>0032 0032
+3253>0032 0033
+3254>0032 0034
+3255>0032 0035
+3256>0032 0036
+3257>0032 0037
+3258>0032 0038
+3259>0032 0039
+325A>0033 0030
+325B>0033 0031
+325C>0033 0032
+325D>0033 0033
+325E>0033 0034
+325F>0033 0035
+3260>1100
+3261>1102
+3262>1103
+3263>1105
+3264>1106
+3265>1107
+3266>1109
+3267>110B
+3268>110C
+3269>110E
+326A>110F
+326B>1110
+326C>1111
+326D>1112
+326E>AC00
+326F>B098
+3270>B2E4
+3271>B77C
+3272>B9C8
+3273>BC14
+3274>C0AC
+3275>C544
+3276>C790
+3277>CC28
+3278>CE74
+3279>D0C0
+327A>D30C
+327B>D558
+327C>CC38 ACE0
+327D>C8FC C758
+327E>C6B0
+3280>4E00
+3281>4E8C
+3282>4E09
+3283>56DB
+3284>4E94
+3285>516D
+3286>4E03
+3287>516B
+3288>4E5D
+3289>5341
+328A>6708
+328B>706B
+328C>6C34
+328D>6728
+328E>91D1
+328F>571F
+3290>65E5
+3291>682A
+3292>6709
+3293>793E
+3294>540D
+3295>7279
+3296>8CA1
+3297>795D
+3298>52B4
+3299>79D8
+329A>7537
+329B>5973
+329C>9069
+329D>512A
+329E>5370
+329F>6CE8
+32A0>9805
+32A1>4F11
+32A2>5199
+32A3>6B63
+32A4>4E0A
+32A5>4E2D
+32A6>4E0B
+32A7>5DE6
+32A8>53F3
+32A9>533B
+32AA>5B97
+32AB>5B66
+32AC>76E3
+32AD>4F01
+32AE>8CC7
+32AF>5354
+32B0>591C
+32B1>0033 0036
+32B2>0033 0037
+32B3>0033 0038
+32B4>0033 0039
+32B5>0034 0030
+32B6>0034 0031
+32B7>0034 0032
+32B8>0034 0033
+32B9>0034 0034
+32BA>0034 0035
+32BB>0034 0036
+32BC>0034 0037
+32BD>0034 0038
+32BE>0034 0039
+32BF>0035 0030
+32C0>0031 6708
+32C1>0032 6708
+32C2>0033 6708
+32C3>0034 6708
+32C4>0035 6708
+32C5>0036 6708
+32C6>0037 6708
+32C7>0038 6708
+32C8>0039 6708
+32C9>0031 0030 6708
+32CA>0031 0031 6708
+32CB>0031 0032 6708
+32CC>0068 0067
+32CD>0065 0072 0067
+32CE>0065 0076
+32CF>006C 0074 0064
+32D0>30A2
+32D1>30A4
+32D2>30A6
+32D3>30A8
+32D4>30AA
+32D5>30AB
+32D6>30AD
+32D7>30AF
+32D8>30B1
+32D9>30B3
+32DA>30B5
+32DB>30B7
+32DC>30B9
+32DD>30BB
+32DE>30BD
+32DF>30BF
+32E0>30C1
+32E1>30C4
+32E2>30C6
+32E3>30C8
+32E4>30CA
+32E5>30CB
+32E6>30CC
+32E7>30CD
+32E8>30CE
+32E9>30CF
+32EA>30D2
+32EB>30D5
+32EC>30D8
+32ED>30DB
+32EE>30DE
+32EF>30DF
+32F0>30E0
+32F1>30E1
+32F2>30E2
+32F3>30E4
+32F4>30E6
+32F5>30E8
+32F6>30E9
+32F7>30EA
+32F8>30EB
+32F9>30EC
+32FA>30ED
+32FB>30EF
+32FC>30F0
+32FD>30F1
+32FE>30F2
+3300>30A2 30D1 30FC 30C8
+3301>30A2 30EB 30D5 30A1
+3302>30A2 30F3 30DA 30A2
+3303>30A2 30FC 30EB
+3304>30A4 30CB 30F3 30B0
+3305>30A4 30F3 30C1
+3306>30A6 30A9 30F3
+3307>30A8 30B9 30AF 30FC 30C9
+3308>30A8 30FC 30AB 30FC
+3309>30AA 30F3 30B9
+330A>30AA 30FC 30E0
+330B>30AB 30A4 30EA
+330C>30AB 30E9 30C3 30C8
+330D>30AB 30ED 30EA 30FC
+330E>30AC 30ED 30F3
+330F>30AC 30F3 30DE
+3310>30AE 30AC
+3311>30AE 30CB 30FC
+3312>30AD 30E5 30EA 30FC
+3313>30AE 30EB 30C0 30FC
+3314>30AD 30ED
+3315>30AD 30ED 30B0 30E9 30E0
+3316>30AD 30ED 30E1 30FC 30C8 30EB
+3317>30AD 30ED 30EF 30C3 30C8
+3318>30B0 30E9 30E0
+3319>30B0 30E9 30E0 30C8 30F3
+331A>30AF 30EB 30BC 30A4 30ED
+331B>30AF 30ED 30FC 30CD
+331C>30B1 30FC 30B9
+331D>30B3 30EB 30CA
+331E>30B3 30FC 30DD
+331F>30B5 30A4 30AF 30EB
+3320>30B5 30F3 30C1 30FC 30E0
+3321>30B7 30EA 30F3 30B0
+3322>30BB 30F3 30C1
+3323>30BB 30F3 30C8
+3324>30C0 30FC 30B9
+3325>30C7 30B7
+3326>30C9 30EB
+3327>30C8 30F3
+3328>30CA 30CE
+3329>30CE 30C3 30C8
+332A>30CF 30A4 30C4
+332B>30D1 30FC 30BB 30F3 30C8
+332C>30D1 30FC 30C4
+332D>30D0 30FC 30EC 30EB
+332E>30D4 30A2 30B9 30C8 30EB
+332F>30D4 30AF 30EB
+3330>30D4 30B3
+3331>30D3 30EB
+3332>30D5 30A1 30E9 30C3 30C9
+3333>30D5 30A3 30FC 30C8
+3334>30D6 30C3 30B7 30A7 30EB
+3335>30D5 30E9 30F3
+3336>30D8 30AF 30BF 30FC 30EB
+3337>30DA 30BD
+3338>30DA 30CB 30D2
+3339>30D8 30EB 30C4
+333A>30DA 30F3 30B9
+333B>30DA 30FC 30B8
+333C>30D9 30FC 30BF
+333D>30DD 30A4 30F3 30C8
+333E>30DC 30EB 30C8
+333F>30DB 30F3
+3340>30DD 30F3 30C9
+3341>30DB 30FC 30EB
+3342>30DB 30FC 30F3
+3343>30DE 30A4 30AF 30ED
+3344>30DE 30A4 30EB
+3345>30DE 30C3 30CF
+3346>30DE 30EB 30AF
+3347>30DE 30F3 30B7 30E7 30F3
+3348>30DF 30AF 30ED 30F3
+3349>30DF 30EA
+334A>30DF 30EA 30D0 30FC 30EB
+334B>30E1 30AC
+334C>30E1 30AC 30C8 30F3
+334D>30E1 30FC 30C8 30EB
+334E>30E4 30FC 30C9
+334F>30E4 30FC 30EB
+3350>30E6 30A2 30F3
+3351>30EA 30C3 30C8 30EB
+3352>30EA 30E9
+3353>30EB 30D4 30FC
+3354>30EB 30FC 30D6 30EB
+3355>30EC 30E0
+3356>30EC 30F3 30C8 30B2 30F3
+3357>30EF 30C3 30C8
+3358>0030 70B9
+3359>0031 70B9
+335A>0032 70B9
+335B>0033 70B9
+335C>0034 70B9
+335D>0035 70B9
+335E>0036 70B9
+335F>0037 70B9
+3360>0038 70B9
+3361>0039 70B9
+3362>0031 0030 70B9
+3363>0031 0031 70B9
+3364>0031 0032 70B9
+3365>0031 0033 70B9
+3366>0031 0034 70B9
+3367>0031 0035 70B9
+3368>0031 0036 70B9
+3369>0031 0037 70B9
+336A>0031 0038 70B9
+336B>0031 0039 70B9
+336C>0032 0030 70B9
+336D>0032 0031 70B9
+336E>0032 0032 70B9
+336F>0032 0033 70B9
+3370>0032 0034 70B9
+3371>0068 0070 0061
+3372>0064 0061
+3373>0061 0075
+3374>0062 0061 0072
+3375>006F 0076
+3376>0070 0063
+3377>0064 006D
+3378>0064 006D 0032
+3379>0064 006D 0033
+337A>0069 0075
+337B>5E73 6210
+337C>662D 548C
+337D>5927 6B63
+337E>660E 6CBB
+337F>682A 5F0F 4F1A 793E
+3380>0070 0061
+3381>006E 0061
+3382>03BC 0061
+3383>006D 0061
+3384>006B 0061
+3385>006B 0062
+3386>006D 0062
+3387>0067 0062
+3388>0063 0061 006C
+3389>006B 0063 0061 006C
+338A>0070 0066
+338B>006E 0066
+338C>03BC 0066
+338D>03BC 0067
+338E>006D 0067
+338F>006B 0067
+3390>0068 007A
+3391>006B 0068 007A
+3392>006D 0068 007A
+3393>0067 0068 007A
+3394>0074 0068 007A
+3395>03BC 006C
+3396>006D 006C
+3397>0064 006C
+3398>006B 006C
+3399>0066 006D
+339A>006E 006D
+339B>03BC 006D
+339C>006D 006D
+339D>0063 006D
+339E>006B 006D
+339F>006D 006D 0032
+33A0>0063 006D 0032
+33A1>006D 0032
+33A2>006B 006D 0032
+33A3>006D 006D 0033
+33A4>0063 006D 0033
+33A5>006D 0033
+33A6>006B 006D 0033
+33A7>006D 2215 0073
+33A8>006D 2215 0073 0032
+33A9>0070 0061
+33AA>006B 0070 0061
+33AB>006D 0070 0061
+33AC>0067 0070 0061
+33AD>0072 0061 0064
+33AE>0072 0061 0064 2215 0073
+33AF>0072 0061 0064 2215 0073 0032
+33B0>0070 0073
+33B1>006E 0073
+33B2>03BC 0073
+33B3>006D 0073
+33B4>0070 0076
+33B5>006E 0076
+33B6>03BC 0076
+33B7>006D 0076
+33B8>006B 0076
+33B9>006D 0076
+33BA>0070 0077
+33BB>006E 0077
+33BC>03BC 0077
+33BD>006D 0077
+33BE>006B 0077
+33BF>006D 0077
+33C0>006B 03C9
+33C1>006D 03C9
+33C2>0061 002E 006D 002E
+33C3>0062 0071
+33C4>0063 0063
+33C5>0063 0064
+33C6>0063 2215 006B 0067
+33C7>0063 006F 002E
+33C8>0064 0062
+33C9>0067 0079
+33CA>0068 0061
+33CB>0068 0070
+33CC>0069 006E
+33CD>006B 006B
+33CE>006B 006D
+33CF>006B 0074
+33D0>006C 006D
+33D1>006C 006E
+33D2>006C 006F 0067
+33D3>006C 0078
+33D4>006D 0062
+33D5>006D 0069 006C
+33D6>006D 006F 006C
+33D7>0070 0068
+33D8>0070 002E 006D 002E
+33D9>0070 0070 006D
+33DA>0070 0072
+33DB>0073 0072
+33DC>0073 0076
+33DD>0077 0062
+33DE>0076 2215 006D
+33DF>0061 2215 006D
+33E0>0031 65E5
+33E1>0032 65E5
+33E2>0033 65E5
+33E3>0034 65E5
+33E4>0035 65E5
+33E5>0036 65E5
+33E6>0037 65E5
+33E7>0038 65E5
+33E8>0039 65E5
+33E9>0031 0030 65E5
+33EA>0031 0031 65E5
+33EB>0031 0032 65E5
+33EC>0031 0033 65E5
+33ED>0031 0034 65E5
+33EE>0031 0035 65E5
+33EF>0031 0036 65E5
+33F0>0031 0037 65E5
+33F1>0031 0038 65E5
+33F2>0031 0039 65E5
+33F3>0032 0030 65E5
+33F4>0032 0031 65E5
+33F5>0032 0032 65E5
+33F6>0032 0033 65E5
+33F7>0032 0034 65E5
+33F8>0032 0035 65E5
+33F9>0032 0036 65E5
+33FA>0032 0037 65E5
+33FB>0032 0038 65E5
+33FC>0032 0039 65E5
+33FD>0033 0030 65E5
+33FE>0033 0031 65E5
+33FF>0067 0061 006C
+A640>A641
+A642>A643
+A644>A645
+A646>A647
+A648>A649
+A64A>A64B
+A64C>A64D
+A64E>A64F
+A650>A651
+A652>A653
+A654>A655
+A656>A657
+A658>A659
+A65A>A65B
+A65C>A65D
+A65E>A65F
+A662>A663
+A664>A665
+A666>A667
+A668>A669
+A66A>A66B
+A66C>A66D
+A680>A681
+A682>A683
+A684>A685
+A686>A687
+A688>A689
+A68A>A68B
+A68C>A68D
+A68E>A68F
+A690>A691
+A692>A693
+A694>A695
+A696>A697
+A722>A723
+A724>A725
+A726>A727
+A728>A729
+A72A>A72B
+A72C>A72D
+A72E>A72F
+A732>A733
+A734>A735
+A736>A737
+A738>A739
+A73A>A73B
+A73C>A73D
+A73E>A73F
+A740>A741
+A742>A743
+A744>A745
+A746>A747
+A748>A749
+A74A>A74B
+A74C>A74D
+A74E>A74F
+A750>A751
+A752>A753
+A754>A755
+A756>A757
+A758>A759
+A75A>A75B
+A75C>A75D
+A75E>A75F
+A760>A761
+A762>A763
+A764>A765
+A766>A767
+A768>A769
+A76A>A76B
+A76C>A76D
+A76E>A76F
+A770>A76F
+A779>A77A
+A77B>A77C
+A77D>1D79
+A77E>A77F
+A780>A781
+A782>A783
+A784>A785
+A786>A787
+A78B>A78C
+F900>8C48
+F901>66F4
+F902>8ECA
+F903>8CC8
+F904>6ED1
+F905>4E32
+F906>53E5
+F907..F908>9F9C
+F909>5951
+F90A>91D1
+F90B>5587
+F90C>5948
+F90D>61F6
+F90E>7669
+F90F>7F85
+F910>863F
+F911>87BA
+F912>88F8
+F913>908F
+F914>6A02
+F915>6D1B
+F916>70D9
+F917>73DE
+F918>843D
+F919>916A
+F91A>99F1
+F91B>4E82
+F91C>5375
+F91D>6B04
+F91E>721B
+F91F>862D
+F920>9E1E
+F921>5D50
+F922>6FEB
+F923>85CD
+F924>8964
+F925>62C9
+F926>81D8
+F927>881F
+F928>5ECA
+F929>6717
+F92A>6D6A
+F92B>72FC
+F92C>90CE
+F92D>4F86
+F92E>51B7
+F92F>52DE
+F930>64C4
+F931>6AD3
+F932>7210
+F933>76E7
+F934>8001
+F935>8606
+F936>865C
+F937>8DEF
+F938>9732
+F939>9B6F
+F93A>9DFA
+F93B>788C
+F93C>797F
+F93D>7DA0
+F93E>83C9
+F93F>9304
+F940>9E7F
+F941>8AD6
+F942>58DF
+F943>5F04
+F944>7C60
+F945>807E
+F946>7262
+F947>78CA
+F948>8CC2
+F949>96F7
+F94A>58D8
+F94B>5C62
+F94C>6A13
+F94D>6DDA
+F94E>6F0F
+F94F>7D2F
+F950>7E37
+F951>964B
+F952>52D2
+F953>808B
+F954>51DC
+F955>51CC
+F956>7A1C
+F957>7DBE
+F958>83F1
+F959>9675
+F95A>8B80
+F95B>62CF
+F95C>6A02
+F95D>8AFE
+F95E>4E39
+F95F>5BE7
+F960>6012
+F961>7387
+F962>7570
+F963>5317
+F964>78FB
+F965>4FBF
+F966>5FA9
+F967>4E0D
+F968>6CCC
+F969>6578
+F96A>7D22
+F96B>53C3
+F96C>585E
+F96D>7701
+F96E>8449
+F96F>8AAA
+F970>6BBA
+F971>8FB0
+F972>6C88
+F973>62FE
+F974>82E5
+F975>63A0
+F976>7565
+F977>4EAE
+F978>5169
+F979>51C9
+F97A>6881
+F97B>7CE7
+F97C>826F
+F97D>8AD2
+F97E>91CF
+F97F>52F5
+F980>5442
+F981>5973
+F982>5EEC
+F983>65C5
+F984>6FFE
+F985>792A
+F986>95AD
+F987>9A6A
+F988>9E97
+F989>9ECE
+F98A>529B
+F98B>66C6
+F98C>6B77
+F98D>8F62
+F98E>5E74
+F98F>6190
+F990>6200
+F991>649A
+F992>6F23
+F993>7149
+F994>7489
+F995>79CA
+F996>7DF4
+F997>806F
+F998>8F26
+F999>84EE
+F99A>9023
+F99B>934A
+F99C>5217
+F99D>52A3
+F99E>54BD
+F99F>70C8
+F9A0>88C2
+F9A1>8AAA
+F9A2>5EC9
+F9A3>5FF5
+F9A4>637B
+F9A5>6BAE
+F9A6>7C3E
+F9A7>7375
+F9A8>4EE4
+F9A9>56F9
+F9AA>5BE7
+F9AB>5DBA
+F9AC>601C
+F9AD>73B2
+F9AE>7469
+F9AF>7F9A
+F9B0>8046
+F9B1>9234
+F9B2>96F6
+F9B3>9748
+F9B4>9818
+F9B5>4F8B
+F9B6>79AE
+F9B7>91B4
+F9B8>96B8
+F9B9>60E1
+F9BA>4E86
+F9BB>50DA
+F9BC>5BEE
+F9BD>5C3F
+F9BE>6599
+F9BF>6A02
+F9C0>71CE
+F9C1>7642
+F9C2>84FC
+F9C3>907C
+F9C4>9F8D
+F9C5>6688
+F9C6>962E
+F9C7>5289
+F9C8>677B
+F9C9>67F3
+F9CA>6D41
+F9CB>6E9C
+F9CC>7409
+F9CD>7559
+F9CE>786B
+F9CF>7D10
+F9D0>985E
+F9D1>516D
+F9D2>622E
+F9D3>9678
+F9D4>502B
+F9D5>5D19
+F9D6>6DEA
+F9D7>8F2A
+F9D8>5F8B
+F9D9>6144
+F9DA>6817
+F9DB>7387
+F9DC>9686
+F9DD>5229
+F9DE>540F
+F9DF>5C65
+F9E0>6613
+F9E1>674E
+F9E2>68A8
+F9E3>6CE5
+F9E4>7406
+F9E5>75E2
+F9E6>7F79
+F9E7>88CF
+F9E8>88E1
+F9E9>91CC
+F9EA>96E2
+F9EB>533F
+F9EC>6EBA
+F9ED>541D
+F9EE>71D0
+F9EF>7498
+F9F0>85FA
+F9F1>96A3
+F9F2>9C57
+F9F3>9E9F
+F9F4>6797
+F9F5>6DCB
+F9F6>81E8
+F9F7>7ACB
+F9F8>7B20
+F9F9>7C92
+F9FA>72C0
+F9FB>7099
+F9FC>8B58
+F9FD>4EC0
+F9FE>8336
+F9FF>523A
+FA00>5207
+FA01>5EA6
+FA02>62D3
+FA03>7CD6
+FA04>5B85
+FA05>6D1E
+FA06>66B4
+FA07>8F3B
+FA08>884C
+FA09>964D
+FA0A>898B
+FA0B>5ED3
+FA0C>5140
+FA0D>55C0
+FA10>585A
+FA12>6674
+FA15>51DE
+FA16>732A
+FA17>76CA
+FA18>793C
+FA19>795E
+FA1A>7965
+FA1B>798F
+FA1C>9756
+FA1D>7CBE
+FA1E>7FBD
+FA20>8612
+FA22>8AF8
+FA25>9038
+FA26>90FD
+FA2A>98EF
+FA2B>98FC
+FA2C>9928
+FA2D>9DB4
+FA30>4FAE
+FA31>50E7
+FA32>514D
+FA33>52C9
+FA34>52E4
+FA35>5351
+FA36>559D
+FA37>5606
+FA38>5668
+FA39>5840
+FA3A>58A8
+FA3B>5C64
+FA3C>5C6E
+FA3D>6094
+FA3E>6168
+FA3F>618E
+FA40>61F2
+FA41>654F
+FA42>65E2
+FA43>6691
+FA44>6885
+FA45>6D77
+FA46>6E1A
+FA47>6F22
+FA48>716E
+FA49>722B
+FA4A>7422
+FA4B>7891
+FA4C>793E
+FA4D>7949
+FA4E>7948
+FA4F>7950
+FA50>7956
+FA51>795D
+FA52>798D
+FA53>798E
+FA54>7A40
+FA55>7A81
+FA56>7BC0
+FA57>7DF4
+FA58>7E09
+FA59>7E41
+FA5A>7F72
+FA5B>8005
+FA5C>81ED
+FA5D..FA5E>8279
+FA5F>8457
+FA60>8910
+FA61>8996
+FA62>8B01
+FA63>8B39
+FA64>8CD3
+FA65>8D08
+FA66>8FB6
+FA67>9038
+FA68>96E3
+FA69>97FF
+FA6A>983B
+FA6B>6075
+FA6C>242EE
+FA6D>8218
+FA70>4E26
+FA71>51B5
+FA72>5168
+FA73>4F80
+FA74>5145
+FA75>5180
+FA76>52C7
+FA77>52FA
+FA78>559D
+FA79>5555
+FA7A>5599
+FA7B>55E2
+FA7C>585A
+FA7D>58B3
+FA7E>5944
+FA7F>5954
+FA80>5A62
+FA81>5B28
+FA82>5ED2
+FA83>5ED9
+FA84>5F69
+FA85>5FAD
+FA86>60D8
+FA87>614E
+FA88>6108
+FA89>618E
+FA8A>6160
+FA8B>61F2
+FA8C>6234
+FA8D>63C4
+FA8E>641C
+FA8F>6452
+FA90>6556
+FA91>6674
+FA92>6717
+FA93>671B
+FA94>6756
+FA95>6B79
+FA96>6BBA
+FA97>6D41
+FA98>6EDB
+FA99>6ECB
+FA9A>6F22
+FA9B>701E
+FA9C>716E
+FA9D>77A7
+FA9E>7235
+FA9F>72AF
+FAA0>732A
+FAA1>7471
+FAA2>7506
+FAA3>753B
+FAA4>761D
+FAA5>761F
+FAA6>76CA
+FAA7>76DB
+FAA8>76F4
+FAA9>774A
+FAAA>7740
+FAAB>78CC
+FAAC>7AB1
+FAAD>7BC0
+FAAE>7C7B
+FAAF>7D5B
+FAB0>7DF4
+FAB1>7F3E
+FAB2>8005
+FAB3>8352
+FAB4>83EF
+FAB5>8779
+FAB6>8941
+FAB7>8986
+FAB8>8996
+FAB9>8ABF
+FABA>8AF8
+FABB>8ACB
+FABC>8B01
+FABD>8AFE
+FABE>8AED
+FABF>8B39
+FAC0>8B8A
+FAC1>8D08
+FAC2>8F38
+FAC3>9072
+FAC4>9199
+FAC5>9276
+FAC6>967C
+FAC7>96E3
+FAC8>9756
+FAC9>97DB
+FACA>97FF
+FACB>980B
+FACC>983B
+FACD>9B12
+FACE>9F9C
+FACF>2284A
+FAD0>22844
+FAD1>233D5
+FAD2>3B9D
+FAD3>4018
+FAD4>4039
+FAD5>25249
+FAD6>25CD0
+FAD7>27ED3
+FAD8>9F43
+FAD9>9F8E
+FB00>0066 0066
+FB01>0066 0069
+FB02>0066 006C
+FB03>0066 0066 0069
+FB04>0066 0066 006C
+FB05..FB06>0073 0074
+FB13>0574 0576
+FB14>0574 0565
+FB15>0574 056B
+FB16>057E 0576
+FB17>0574 056D
+FB1D>05D9 05B4
+FB1F>05F2 05B7
+FB20>05E2
+FB21>05D0
+FB22>05D3
+FB23>05D4
+FB24>05DB
+FB25>05DC
+FB26>05DD
+FB27>05E8
+FB28>05EA
+FB29>002B
+FB2A>05E9 05C1
+FB2B>05E9 05C2
+FB2C>05E9 05BC 05C1
+FB2D>05E9 05BC 05C2
+FB2E>05D0 05B7
+FB2F>05D0 05B8
+FB30>05D0 05BC
+FB31>05D1 05BC
+FB32>05D2 05BC
+FB33>05D3 05BC
+FB34>05D4 05BC
+FB35>05D5 05BC
+FB36>05D6 05BC
+FB38>05D8 05BC
+FB39>05D9 05BC
+FB3A>05DA 05BC
+FB3B>05DB 05BC
+FB3C>05DC 05BC
+FB3E>05DE 05BC
+FB40>05E0 05BC
+FB41>05E1 05BC
+FB43>05E3 05BC
+FB44>05E4 05BC
+FB46>05E6 05BC
+FB47>05E7 05BC
+FB48>05E8 05BC
+FB49>05E9 05BC
+FB4A>05EA 05BC
+FB4B>05D5 05B9
+FB4C>05D1 05BF
+FB4D>05DB 05BF
+FB4E>05E4 05BF
+FB4F>05D0 05DC
+FB50..FB51>0671
+FB52..FB55>067B
+FB56..FB59>067E
+FB5A..FB5D>0680
+FB5E..FB61>067A
+FB62..FB65>067F
+FB66..FB69>0679
+FB6A..FB6D>06A4
+FB6E..FB71>06A6
+FB72..FB75>0684
+FB76..FB79>0683
+FB7A..FB7D>0686
+FB7E..FB81>0687
+FB82..FB83>068D
+FB84..FB85>068C
+FB86..FB87>068E
+FB88..FB89>0688
+FB8A..FB8B>0698
+FB8C..FB8D>0691
+FB8E..FB91>06A9
+FB92..FB95>06AF
+FB96..FB99>06B3
+FB9A..FB9D>06B1
+FB9E..FB9F>06BA
+FBA0..FBA3>06BB
+FBA4..FBA5>06C0
+FBA6..FBA9>06C1
+FBAA..FBAD>06BE
+FBAE..FBAF>06D2
+FBB0..FBB1>06D3
+FBD3..FBD6>06AD
+FBD7..FBD8>06C7
+FBD9..FBDA>06C6
+FBDB..FBDC>06C8
+FBDD>06C7 0674
+FBDE..FBDF>06CB
+FBE0..FBE1>06C5
+FBE2..FBE3>06C9
+FBE4..FBE7>06D0
+FBE8..FBE9>0649
+FBEA..FBEB>0626 0627
+FBEC..FBED>0626 06D5
+FBEE..FBEF>0626 0648
+FBF0..FBF1>0626 06C7
+FBF2..FBF3>0626 06C6
+FBF4..FBF5>0626 06C8
+FBF6..FBF8>0626 06D0
+FBF9..FBFB>0626 0649
+FBFC..FBFF>06CC
+FC00>0626 062C
+FC01>0626 062D
+FC02>0626 0645
+FC03>0626 0649
+FC04>0626 064A
+FC05>0628 062C
+FC06>0628 062D
+FC07>0628 062E
+FC08>0628 0645
+FC09>0628 0649
+FC0A>0628 064A
+FC0B>062A 062C
+FC0C>062A 062D
+FC0D>062A 062E
+FC0E>062A 0645
+FC0F>062A 0649
+FC10>062A 064A
+FC11>062B 062C
+FC12>062B 0645
+FC13>062B 0649
+FC14>062B 064A
+FC15>062C 062D
+FC16>062C 0645
+FC17>062D 062C
+FC18>062D 0645
+FC19>062E 062C
+FC1A>062E 062D
+FC1B>062E 0645
+FC1C>0633 062C
+FC1D>0633 062D
+FC1E>0633 062E
+FC1F>0633 0645
+FC20>0635 062D
+FC21>0635 0645
+FC22>0636 062C
+FC23>0636 062D
+FC24>0636 062E
+FC25>0636 0645
+FC26>0637 062D
+FC27>0637 0645
+FC28>0638 0645
+FC29>0639 062C
+FC2A>0639 0645
+FC2B>063A 062C
+FC2C>063A 0645
+FC2D>0641 062C
+FC2E>0641 062D
+FC2F>0641 062E
+FC30>0641 0645
+FC31>0641 0649
+FC32>0641 064A
+FC33>0642 062D
+FC34>0642 0645
+FC35>0642 0649
+FC36>0642 064A
+FC37>0643 0627
+FC38>0643 062C
+FC39>0643 062D
+FC3A>0643 062E
+FC3B>0643 0644
+FC3C>0643 0645
+FC3D>0643 0649
+FC3E>0643 064A
+FC3F>0644 062C
+FC40>0644 062D
+FC41>0644 062E
+FC42>0644 0645
+FC43>0644 0649
+FC44>0644 064A
+FC45>0645 062C
+FC46>0645 062D
+FC47>0645 062E
+FC48>0645 0645
+FC49>0645 0649
+FC4A>0645 064A
+FC4B>0646 062C
+FC4C>0646 062D
+FC4D>0646 062E
+FC4E>0646 0645
+FC4F>0646 0649
+FC50>0646 064A
+FC51>0647 062C
+FC52>0647 0645
+FC53>0647 0649
+FC54>0647 064A
+FC55>064A 062C
+FC56>064A 062D
+FC57>064A 062E
+FC58>064A 0645
+FC59>064A 0649
+FC5A>064A 064A
+FC5B>0630 0670
+FC5C>0631 0670
+FC5D>0649 0670
+FC5E>0020 064C 0651
+FC5F>0020 064D 0651
+FC60>0020 064E 0651
+FC61>0020 064F 0651
+FC62>0020 0650 0651
+FC63>0020 0651 0670
+FC64>0626 0631
+FC65>0626 0632
+FC66>0626 0645
+FC67>0626 0646
+FC68>0626 0649
+FC69>0626 064A
+FC6A>0628 0631
+FC6B>0628 0632
+FC6C>0628 0645
+FC6D>0628 0646
+FC6E>0628 0649
+FC6F>0628 064A
+FC70>062A 0631
+FC71>062A 0632
+FC72>062A 0645
+FC73>062A 0646
+FC74>062A 0649
+FC75>062A 064A
+FC76>062B 0631
+FC77>062B 0632
+FC78>062B 0645
+FC79>062B 0646
+FC7A>062B 0649
+FC7B>062B 064A
+FC7C>0641 0649
+FC7D>0641 064A
+FC7E>0642 0649
+FC7F>0642 064A
+FC80>0643 0627
+FC81>0643 0644
+FC82>0643 0645
+FC83>0643 0649
+FC84>0643 064A
+FC85>0644 0645
+FC86>0644 0649
+FC87>0644 064A
+FC88>0645 0627
+FC89>0645 0645
+FC8A>0646 0631
+FC8B>0646 0632
+FC8C>0646 0645
+FC8D>0646 0646
+FC8E>0646 0649
+FC8F>0646 064A
+FC90>0649 0670
+FC91>064A 0631
+FC92>064A 0632
+FC93>064A 0645
+FC94>064A 0646
+FC95>064A 0649
+FC96>064A 064A
+FC97>0626 062C
+FC98>0626 062D
+FC99>0626 062E
+FC9A>0626 0645
+FC9B>0626 0647
+FC9C>0628 062C
+FC9D>0628 062D
+FC9E>0628 062E
+FC9F>0628 0645
+FCA0>0628 0647
+FCA1>062A 062C
+FCA2>062A 062D
+FCA3>062A 062E
+FCA4>062A 0645
+FCA5>062A 0647
+FCA6>062B 0645
+FCA7>062C 062D
+FCA8>062C 0645
+FCA9>062D 062C
+FCAA>062D 0645
+FCAB>062E 062C
+FCAC>062E 0645
+FCAD>0633 062C
+FCAE>0633 062D
+FCAF>0633 062E
+FCB0>0633 0645
+FCB1>0635 062D
+FCB2>0635 062E
+FCB3>0635 0645
+FCB4>0636 062C
+FCB5>0636 062D
+FCB6>0636 062E
+FCB7>0636 0645
+FCB8>0637 062D
+FCB9>0638 0645
+FCBA>0639 062C
+FCBB>0639 0645
+FCBC>063A 062C
+FCBD>063A 0645
+FCBE>0641 062C
+FCBF>0641 062D
+FCC0>0641 062E
+FCC1>0641 0645
+FCC2>0642 062D
+FCC3>0642 0645
+FCC4>0643 062C
+FCC5>0643 062D
+FCC6>0643 062E
+FCC7>0643 0644
+FCC8>0643 0645
+FCC9>0644 062C
+FCCA>0644 062D
+FCCB>0644 062E
+FCCC>0644 0645
+FCCD>0644 0647
+FCCE>0645 062C
+FCCF>0645 062D
+FCD0>0645 062E
+FCD1>0645 0645
+FCD2>0646 062C
+FCD3>0646 062D
+FCD4>0646 062E
+FCD5>0646 0645
+FCD6>0646 0647
+FCD7>0647 062C
+FCD8>0647 0645
+FCD9>0647 0670
+FCDA>064A 062C
+FCDB>064A 062D
+FCDC>064A 062E
+FCDD>064A 0645
+FCDE>064A 0647
+FCDF>0626 0645
+FCE0>0626 0647
+FCE1>0628 0645
+FCE2>0628 0647
+FCE3>062A 0645
+FCE4>062A 0647
+FCE5>062B 0645
+FCE6>062B 0647
+FCE7>0633 0645
+FCE8>0633 0647
+FCE9>0634 0645
+FCEA>0634 0647
+FCEB>0643 0644
+FCEC>0643 0645
+FCED>0644 0645
+FCEE>0646 0645
+FCEF>0646 0647
+FCF0>064A 0645
+FCF1>064A 0647
+FCF2>0640 064E 0651
+FCF3>0640 064F 0651
+FCF4>0640 0650 0651
+FCF5>0637 0649
+FCF6>0637 064A
+FCF7>0639 0649
+FCF8>0639 064A
+FCF9>063A 0649
+FCFA>063A 064A
+FCFB>0633 0649
+FCFC>0633 064A
+FCFD>0634 0649
+FCFE>0634 064A
+FCFF>062D 0649
+FD00>062D 064A
+FD01>062C 0649
+FD02>062C 064A
+FD03>062E 0649
+FD04>062E 064A
+FD05>0635 0649
+FD06>0635 064A
+FD07>0636 0649
+FD08>0636 064A
+FD09>0634 062C
+FD0A>0634 062D
+FD0B>0634 062E
+FD0C>0634 0645
+FD0D>0634 0631
+FD0E>0633 0631
+FD0F>0635 0631
+FD10>0636 0631
+FD11>0637 0649
+FD12>0637 064A
+FD13>0639 0649
+FD14>0639 064A
+FD15>063A 0649
+FD16>063A 064A
+FD17>0633 0649
+FD18>0633 064A
+FD19>0634 0649
+FD1A>0634 064A
+FD1B>062D 0649
+FD1C>062D 064A
+FD1D>062C 0649
+FD1E>062C 064A
+FD1F>062E 0649
+FD20>062E 064A
+FD21>0635 0649
+FD22>0635 064A
+FD23>0636 0649
+FD24>0636 064A
+FD25>0634 062C
+FD26>0634 062D
+FD27>0634 062E
+FD28>0634 0645
+FD29>0634 0631
+FD2A>0633 0631
+FD2B>0635 0631
+FD2C>0636 0631
+FD2D>0634 062C
+FD2E>0634 062D
+FD2F>0634 062E
+FD30>0634 0645
+FD31>0633 0647
+FD32>0634 0647
+FD33>0637 0645
+FD34>0633 062C
+FD35>0633 062D
+FD36>0633 062E
+FD37>0634 062C
+FD38>0634 062D
+FD39>0634 062E
+FD3A>0637 0645
+FD3B>0638 0645
+FD3C..FD3D>0627 064B
+FD50>062A 062C 0645
+FD51..FD52>062A 062D 062C
+FD53>062A 062D 0645
+FD54>062A 062E 0645
+FD55>062A 0645 062C
+FD56>062A 0645 062D
+FD57>062A 0645 062E
+FD58..FD59>062C 0645 062D
+FD5A>062D 0645 064A
+FD5B>062D 0645 0649
+FD5C>0633 062D 062C
+FD5D>0633 062C 062D
+FD5E>0633 062C 0649
+FD5F..FD60>0633 0645 062D
+FD61>0633 0645 062C
+FD62..FD63>0633 0645 0645
+FD64..FD65>0635 062D 062D
+FD66>0635 0645 0645
+FD67..FD68>0634 062D 0645
+FD69>0634 062C 064A
+FD6A..FD6B>0634 0645 062E
+FD6C..FD6D>0634 0645 0645
+FD6E>0636 062D 0649
+FD6F..FD70>0636 062E 0645
+FD71..FD72>0637 0645 062D
+FD73>0637 0645 0645
+FD74>0637 0645 064A
+FD75>0639 062C 0645
+FD76..FD77>0639 0645 0645
+FD78>0639 0645 0649
+FD79>063A 0645 0645
+FD7A>063A 0645 064A
+FD7B>063A 0645 0649
+FD7C..FD7D>0641 062E 0645
+FD7E>0642 0645 062D
+FD7F>0642 0645 0645
+FD80>0644 062D 0645
+FD81>0644 062D 064A
+FD82>0644 062D 0649
+FD83..FD84>0644 062C 062C
+FD85..FD86>0644 062E 0645
+FD87..FD88>0644 0645 062D
+FD89>0645 062D 062C
+FD8A>0645 062D 0645
+FD8B>0645 062D 064A
+FD8C>0645 062C 062D
+FD8D>0645 062C 0645
+FD8E>0645 062E 062C
+FD8F>0645 062E 0645
+FD92>0645 062C 062E
+FD93>0647 0645 062C
+FD94>0647 0645 0645
+FD95>0646 062D 0645
+FD96>0646 062D 0649
+FD97..FD98>0646 062C 0645
+FD99>0646 062C 0649
+FD9A>0646 0645 064A
+FD9B>0646 0645 0649
+FD9C..FD9D>064A 0645 0645
+FD9E>0628 062E 064A
+FD9F>062A 062C 064A
+FDA0>062A 062C 0649
+FDA1>062A 062E 064A
+FDA2>062A 062E 0649
+FDA3>062A 0645 064A
+FDA4>062A 0645 0649
+FDA5>062C 0645 064A
+FDA6>062C 062D 0649
+FDA7>062C 0645 0649
+FDA8>0633 062E 0649
+FDA9>0635 062D 064A
+FDAA>0634 062D 064A
+FDAB>0636 062D 064A
+FDAC>0644 062C 064A
+FDAD>0644 0645 064A
+FDAE>064A 062D 064A
+FDAF>064A 062C 064A
+FDB0>064A 0645 064A
+FDB1>0645 0645 064A
+FDB2>0642 0645 064A
+FDB3>0646 062D 064A
+FDB4>0642 0645 062D
+FDB5>0644 062D 0645
+FDB6>0639 0645 064A
+FDB7>0643 0645 064A
+FDB8>0646 062C 062D
+FDB9>0645 062E 064A
+FDBA>0644 062C 0645
+FDBB>0643 0645 0645
+FDBC>0644 062C 0645
+FDBD>0646 062C 062D
+FDBE>062C 062D 064A
+FDBF>062D 062C 064A
+FDC0>0645 062C 064A
+FDC1>0641 0645 064A
+FDC2>0628 062D 064A
+FDC3>0643 0645 0645
+FDC4>0639 062C 0645
+FDC5>0635 0645 0645
+FDC6>0633 062E 064A
+FDC7>0646 062C 064A
+FDF0>0635 0644 06D2
+FDF1>0642 0644 06D2
+FDF2>0627 0644 0644 0647
+FDF3>0627 0643 0628 0631
+FDF4>0645 062D 0645 062F
+FDF5>0635 0644 0639 0645
+FDF6>0631 0633 0648 0644
+FDF7>0639 0644 064A 0647
+FDF8>0648 0633 0644 0645
+FDF9>0635 0644 0649
+FDFA>0635 0644 0649 0020 0627 0644 0644 0647 0020 0639 0644 064A 0647 0020 0648 0633 0644 0645
+FDFB>062C 0644 0020 062C 0644 0627 0644 0647
+FDFC>0631 06CC 0627 0644
+FE00..FE0F>
+FE10>002C
+FE11>3001
+FE12>3002
+FE13>003A
+FE14>003B
+FE15>0021
+FE16>003F
+FE17>3016
+FE18>3017
+FE19>002E 002E 002E
+FE30>002E 002E
+FE31>2014
+FE32>2013
+FE33..FE34>005F
+FE35>0028
+FE36>0029
+FE37>007B
+FE38>007D
+FE39>3014
+FE3A>3015
+FE3B>3010
+FE3C>3011
+FE3D>300A
+FE3E>300B
+FE3F>3008
+FE40>3009
+FE41>300C
+FE42>300D
+FE43>300E
+FE44>300F
+FE47>005B
+FE48>005D
+FE49..FE4C>0020 0305
+FE4D..FE4F>005F
+FE50>002C
+FE51>3001
+FE52>002E
+FE54>003B
+FE55>003A
+FE56>003F
+FE57>0021
+FE58>2014
+FE59>0028
+FE5A>0029
+FE5B>007B
+FE5C>007D
+FE5D>3014
+FE5E>3015
+FE5F>0023
+FE60>0026
+FE61>002A
+FE62>002B
+FE63>002D
+FE64>003C
+FE65>003E
+FE66>003D
+FE68>005C
+FE69>0024
+FE6A>0025
+FE6B>0040
+FE70>0020 064B
+FE71>0640 064B
+FE72>0020 064C
+FE74>0020 064D
+FE76>0020 064E
+FE77>0640 064E
+FE78>0020 064F
+FE79>0640 064F
+FE7A>0020 0650
+FE7B>0640 0650
+FE7C>0020 0651
+FE7D>0640 0651
+FE7E>0020 0652
+FE7F>0640 0652
+FE80>0621
+FE81..FE82>0622
+FE83..FE84>0623
+FE85..FE86>0624
+FE87..FE88>0625
+FE89..FE8C>0626
+FE8D..FE8E>0627
+FE8F..FE92>0628
+FE93..FE94>0629
+FE95..FE98>062A
+FE99..FE9C>062B
+FE9D..FEA0>062C
+FEA1..FEA4>062D
+FEA5..FEA8>062E
+FEA9..FEAA>062F
+FEAB..FEAC>0630
+FEAD..FEAE>0631
+FEAF..FEB0>0632
+FEB1..FEB4>0633
+FEB5..FEB8>0634
+FEB9..FEBC>0635
+FEBD..FEC0>0636
+FEC1..FEC4>0637
+FEC5..FEC8>0638
+FEC9..FECC>0639
+FECD..FED0>063A
+FED1..FED4>0641
+FED5..FED8>0642
+FED9..FEDC>0643
+FEDD..FEE0>0644
+FEE1..FEE4>0645
+FEE5..FEE8>0646
+FEE9..FEEC>0647
+FEED..FEEE>0648
+FEEF..FEF0>0649
+FEF1..FEF4>064A
+FEF5..FEF6>0644 0622
+FEF7..FEF8>0644 0623
+FEF9..FEFA>0644 0625
+FEFB..FEFC>0644 0627
+FEFF>
+FF01>0021
+FF02>0022
+FF03>0023
+FF04>0024
+FF05>0025
+FF06>0026
+FF07>0027
+FF08>0028
+FF09>0029
+FF0A>002A
+FF0B>002B
+FF0C>002C
+FF0D>002D
+FF0E>002E
+FF0F>002F
+FF10>0030
+FF11>0031
+FF12>0032
+FF13>0033
+FF14>0034
+FF15>0035
+FF16>0036
+FF17>0037
+FF18>0038
+FF19>0039
+FF1A>003A
+FF1B>003B
+FF1C>003C
+FF1D>003D
+FF1E>003E
+FF1F>003F
+FF20>0040
+FF21>0061
+FF22>0062
+FF23>0063
+FF24>0064
+FF25>0065
+FF26>0066
+FF27>0067
+FF28>0068
+FF29>0069
+FF2A>006A
+FF2B>006B
+FF2C>006C
+FF2D>006D
+FF2E>006E
+FF2F>006F
+FF30>0070
+FF31>0071
+FF32>0072
+FF33>0073
+FF34>0074
+FF35>0075
+FF36>0076
+FF37>0077
+FF38>0078
+FF39>0079
+FF3A>007A
+FF3B>005B
+FF3C>005C
+FF3D>005D
+FF3E>005E
+FF3F>005F
+FF40>0060
+FF41>0061
+FF42>0062
+FF43>0063
+FF44>0064
+FF45>0065
+FF46>0066
+FF47>0067
+FF48>0068
+FF49>0069
+FF4A>006A
+FF4B>006B
+FF4C>006C
+FF4D>006D
+FF4E>006E
+FF4F>006F
+FF50>0070
+FF51>0071
+FF52>0072
+FF53>0073
+FF54>0074
+FF55>0075
+FF56>0076
+FF57>0077
+FF58>0078
+FF59>0079
+FF5A>007A
+FF5B>007B
+FF5C>007C
+FF5D>007D
+FF5E>007E
+FF5F>2985
+FF60>2986
+FF61>3002
+FF62>300C
+FF63>300D
+FF64>3001
+FF65>30FB
+FF66>30F2
+FF67>30A1
+FF68>30A3
+FF69>30A5
+FF6A>30A7
+FF6B>30A9
+FF6C>30E3
+FF6D>30E5
+FF6E>30E7
+FF6F>30C3
+FF70>30FC
+FF71>30A2
+FF72>30A4
+FF73>30A6
+FF74>30A8
+FF75>30AA
+FF76>30AB
+FF77>30AD
+FF78>30AF
+FF79>30B1
+FF7A>30B3
+FF7B>30B5
+FF7C>30B7
+FF7D>30B9
+FF7E>30BB
+FF7F>30BD
+FF80>30BF
+FF81>30C1
+FF82>30C4
+FF83>30C6
+FF84>30C8
+FF85>30CA
+FF86>30CB
+FF87>30CC
+FF88>30CD
+FF89>30CE
+FF8A>30CF
+FF8B>30D2
+FF8C>30D5
+FF8D>30D8
+FF8E>30DB
+FF8F>30DE
+FF90>30DF
+FF91>30E0
+FF92>30E1
+FF93>30E2
+FF94>30E4
+FF95>30E6
+FF96>30E8
+FF97>30E9
+FF98>30EA
+FF99>30EB
+FF9A>30EC
+FF9B>30ED
+FF9C>30EF
+FF9D>30F3
+FF9E>3099
+FF9F>309A
+FFA0>
+FFA1>1100
+FFA2>1101
+FFA3>11AA
+FFA4>1102
+FFA5>11AC
+FFA6>11AD
+FFA7>1103
+FFA8>1104
+FFA9>1105
+FFAA>11B0
+FFAB>11B1
+FFAC>11B2
+FFAD>11B3
+FFAE>11B4
+FFAF>11B5
+FFB0>111A
+FFB1>1106
+FFB2>1107
+FFB3>1108
+FFB4>1121
+FFB5>1109
+FFB6>110A
+FFB7>110B
+FFB8>110C
+FFB9>110D
+FFBA>110E
+FFBB>110F
+FFBC>1110
+FFBD>1111
+FFBE>1112
+FFC2>1161
+FFC3>1162
+FFC4>1163
+FFC5>1164
+FFC6>1165
+FFC7>1166
+FFCA>1167
+FFCB>1168
+FFCC>1169
+FFCD>116A
+FFCE>116B
+FFCF>116C
+FFD2>116D
+FFD3>116E
+FFD4>116F
+FFD5>1170
+FFD6>1171
+FFD7>1172
+FFDA>1173
+FFDB>1174
+FFDC>1175
+FFE0>00A2
+FFE1>00A3
+FFE2>00AC
+FFE3>0020 0304
+FFE4>00A6
+FFE5>00A5
+FFE6>20A9
+FFE8>2502
+FFE9>2190
+FFEA>2191
+FFEB>2192
+FFEC>2193
+FFED>25A0
+FFEE>25CB
+FFF0..FFF8>
+10400>10428
+10401>10429
+10402>1042A
+10403>1042B
+10404>1042C
+10405>1042D
+10406>1042E
+10407>1042F
+10408>10430
+10409>10431
+1040A>10432
+1040B>10433
+1040C>10434
+1040D>10435
+1040E>10436
+1040F>10437
+10410>10438
+10411>10439
+10412>1043A
+10413>1043B
+10414>1043C
+10415>1043D
+10416>1043E
+10417>1043F
+10418>10440
+10419>10441
+1041A>10442
+1041B>10443
+1041C>10444
+1041D>10445
+1041E>10446
+1041F>10447
+10420>10448
+10421>10449
+10422>1044A
+10423>1044B
+10424>1044C
+10425>1044D
+10426>1044E
+10427>1044F
+1D15E>1D157 1D165
+1D15F>1D158 1D165
+1D160>1D158 1D165 1D16E
+1D161>1D158 1D165 1D16F
+1D162>1D158 1D165 1D170
+1D163>1D158 1D165 1D171
+1D164>1D158 1D165 1D172
+1D173..1D17A>
+1D1BB>1D1B9 1D165
+1D1BC>1D1BA 1D165
+1D1BD>1D1B9 1D165 1D16E
+1D1BE>1D1BA 1D165 1D16E
+1D1BF>1D1B9 1D165 1D16F
+1D1C0>1D1BA 1D165 1D16F
+1D400>0061
+1D401>0062
+1D402>0063
+1D403>0064
+1D404>0065
+1D405>0066
+1D406>0067
+1D407>0068
+1D408>0069
+1D409>006A
+1D40A>006B
+1D40B>006C
+1D40C>006D
+1D40D>006E
+1D40E>006F
+1D40F>0070
+1D410>0071
+1D411>0072
+1D412>0073
+1D413>0074
+1D414>0075
+1D415>0076
+1D416>0077
+1D417>0078
+1D418>0079
+1D419>007A
+1D41A>0061
+1D41B>0062
+1D41C>0063
+1D41D>0064
+1D41E>0065
+1D41F>0066
+1D420>0067
+1D421>0068
+1D422>0069
+1D423>006A
+1D424>006B
+1D425>006C
+1D426>006D
+1D427>006E
+1D428>006F
+1D429>0070
+1D42A>0071
+1D42B>0072
+1D42C>0073
+1D42D>0074
+1D42E>0075
+1D42F>0076
+1D430>0077
+1D431>0078
+1D432>0079
+1D433>007A
+1D434>0061
+1D435>0062
+1D436>0063
+1D437>0064
+1D438>0065
+1D439>0066
+1D43A>0067
+1D43B>0068
+1D43C>0069
+1D43D>006A
+1D43E>006B
+1D43F>006C
+1D440>006D
+1D441>006E
+1D442>006F
+1D443>0070
+1D444>0071
+1D445>0072
+1D446>0073
+1D447>0074
+1D448>0075
+1D449>0076
+1D44A>0077
+1D44B>0078
+1D44C>0079
+1D44D>007A
+1D44E>0061
+1D44F>0062
+1D450>0063
+1D451>0064
+1D452>0065
+1D453>0066
+1D454>0067
+1D456>0069
+1D457>006A
+1D458>006B
+1D459>006C
+1D45A>006D
+1D45B>006E
+1D45C>006F
+1D45D>0070
+1D45E>0071
+1D45F>0072
+1D460>0073
+1D461>0074
+1D462>0075
+1D463>0076
+1D464>0077
+1D465>0078
+1D466>0079
+1D467>007A
+1D468>0061
+1D469>0062
+1D46A>0063
+1D46B>0064
+1D46C>0065
+1D46D>0066
+1D46E>0067
+1D46F>0068
+1D470>0069
+1D471>006A
+1D472>006B
+1D473>006C
+1D474>006D
+1D475>006E
+1D476>006F
+1D477>0070
+1D478>0071
+1D479>0072
+1D47A>0073
+1D47B>0074
+1D47C>0075
+1D47D>0076
+1D47E>0077
+1D47F>0078
+1D480>0079
+1D481>007A
+1D482>0061
+1D483>0062
+1D484>0063
+1D485>0064
+1D486>0065
+1D487>0066
+1D488>0067
+1D489>0068
+1D48A>0069
+1D48B>006A
+1D48C>006B
+1D48D>006C
+1D48E>006D
+1D48F>006E
+1D490>006F
+1D491>0070
+1D492>0071
+1D493>0072
+1D494>0073
+1D495>0074
+1D496>0075
+1D497>0076
+1D498>0077
+1D499>0078
+1D49A>0079
+1D49B>007A
+1D49C>0061
+1D49E>0063
+1D49F>0064
+1D4A2>0067
+1D4A5>006A
+1D4A6>006B
+1D4A9>006E
+1D4AA>006F
+1D4AB>0070
+1D4AC>0071
+1D4AE>0073
+1D4AF>0074
+1D4B0>0075
+1D4B1>0076
+1D4B2>0077
+1D4B3>0078
+1D4B4>0079
+1D4B5>007A
+1D4B6>0061
+1D4B7>0062
+1D4B8>0063
+1D4B9>0064
+1D4BB>0066
+1D4BD>0068
+1D4BE>0069
+1D4BF>006A
+1D4C0>006B
+1D4C1>006C
+1D4C2>006D
+1D4C3>006E
+1D4C5>0070
+1D4C6>0071
+1D4C7>0072
+1D4C8>0073
+1D4C9>0074
+1D4CA>0075
+1D4CB>0076
+1D4CC>0077
+1D4CD>0078
+1D4CE>0079
+1D4CF>007A
+1D4D0>0061
+1D4D1>0062
+1D4D2>0063
+1D4D3>0064
+1D4D4>0065
+1D4D5>0066
+1D4D6>0067
+1D4D7>0068
+1D4D8>0069
+1D4D9>006A
+1D4DA>006B
+1D4DB>006C
+1D4DC>006D
+1D4DD>006E
+1D4DE>006F
+1D4DF>0070
+1D4E0>0071
+1D4E1>0072
+1D4E2>0073
+1D4E3>0074
+1D4E4>0075
+1D4E5>0076
+1D4E6>0077
+1D4E7>0078
+1D4E8>0079
+1D4E9>007A
+1D4EA>0061
+1D4EB>0062
+1D4EC>0063
+1D4ED>0064
+1D4EE>0065
+1D4EF>0066
+1D4F0>0067
+1D4F1>0068
+1D4F2>0069
+1D4F3>006A
+1D4F4>006B
+1D4F5>006C
+1D4F6>006D
+1D4F7>006E
+1D4F8>006F
+1D4F9>0070
+1D4FA>0071
+1D4FB>0072
+1D4FC>0073
+1D4FD>0074
+1D4FE>0075
+1D4FF>0076
+1D500>0077
+1D501>0078
+1D502>0079
+1D503>007A
+1D504>0061
+1D505>0062
+1D507>0064
+1D508>0065
+1D509>0066
+1D50A>0067
+1D50D>006A
+1D50E>006B
+1D50F>006C
+1D510>006D
+1D511>006E
+1D512>006F
+1D513>0070
+1D514>0071
+1D516>0073
+1D517>0074
+1D518>0075
+1D519>0076
+1D51A>0077
+1D51B>0078
+1D51C>0079
+1D51E>0061
+1D51F>0062
+1D520>0063
+1D521>0064
+1D522>0065
+1D523>0066
+1D524>0067
+1D525>0068
+1D526>0069
+1D527>006A
+1D528>006B
+1D529>006C
+1D52A>006D
+1D52B>006E
+1D52C>006F
+1D52D>0070
+1D52E>0071
+1D52F>0072
+1D530>0073
+1D531>0074
+1D532>0075
+1D533>0076
+1D534>0077
+1D535>0078
+1D536>0079
+1D537>007A
+1D538>0061
+1D539>0062
+1D53B>0064
+1D53C>0065
+1D53D>0066
+1D53E>0067
+1D540>0069
+1D541>006A
+1D542>006B
+1D543>006C
+1D544>006D
+1D546>006F
+1D54A>0073
+1D54B>0074
+1D54C>0075
+1D54D>0076
+1D54E>0077
+1D54F>0078
+1D550>0079
+1D552>0061
+1D553>0062
+1D554>0063
+1D555>0064
+1D556>0065
+1D557>0066
+1D558>0067
+1D559>0068
+1D55A>0069
+1D55B>006A
+1D55C>006B
+1D55D>006C
+1D55E>006D
+1D55F>006E
+1D560>006F
+1D561>0070
+1D562>0071
+1D563>0072
+1D564>0073
+1D565>0074
+1D566>0075
+1D567>0076
+1D568>0077
+1D569>0078
+1D56A>0079
+1D56B>007A
+1D56C>0061
+1D56D>0062
+1D56E>0063
+1D56F>0064
+1D570>0065
+1D571>0066
+1D572>0067
+1D573>0068
+1D574>0069
+1D575>006A
+1D576>006B
+1D577>006C
+1D578>006D
+1D579>006E
+1D57A>006F
+1D57B>0070
+1D57C>0071
+1D57D>0072
+1D57E>0073
+1D57F>0074
+1D580>0075
+1D581>0076
+1D582>0077
+1D583>0078
+1D584>0079
+1D585>007A
+1D586>0061
+1D587>0062
+1D588>0063
+1D589>0064
+1D58A>0065
+1D58B>0066
+1D58C>0067
+1D58D>0068
+1D58E>0069
+1D58F>006A
+1D590>006B
+1D591>006C
+1D592>006D
+1D593>006E
+1D594>006F
+1D595>0070
+1D596>0071
+1D597>0072
+1D598>0073
+1D599>0074
+1D59A>0075
+1D59B>0076
+1D59C>0077
+1D59D>0078
+1D59E>0079
+1D59F>007A
+1D5A0>0061
+1D5A1>0062
+1D5A2>0063
+1D5A3>0064
+1D5A4>0065
+1D5A5>0066
+1D5A6>0067
+1D5A7>0068
+1D5A8>0069
+1D5A9>006A
+1D5AA>006B
+1D5AB>006C
+1D5AC>006D
+1D5AD>006E
+1D5AE>006F
+1D5AF>0070
+1D5B0>0071
+1D5B1>0072
+1D5B2>0073
+1D5B3>0074
+1D5B4>0075
+1D5B5>0076
+1D5B6>0077
+1D5B7>0078
+1D5B8>0079
+1D5B9>007A
+1D5BA>0061
+1D5BB>0062
+1D5BC>0063
+1D5BD>0064
+1D5BE>0065
+1D5BF>0066
+1D5C0>0067
+1D5C1>0068
+1D5C2>0069
+1D5C3>006A
+1D5C4>006B
+1D5C5>006C
+1D5C6>006D
+1D5C7>006E
+1D5C8>006F
+1D5C9>0070
+1D5CA>0071
+1D5CB>0072
+1D5CC>0073
+1D5CD>0074
+1D5CE>0075
+1D5CF>0076
+1D5D0>0077
+1D5D1>0078
+1D5D2>0079
+1D5D3>007A
+1D5D4>0061
+1D5D5>0062
+1D5D6>0063
+1D5D7>0064
+1D5D8>0065
+1D5D9>0066
+1D5DA>0067
+1D5DB>0068
+1D5DC>0069
+1D5DD>006A
+1D5DE>006B
+1D5DF>006C
+1D5E0>006D
+1D5E1>006E
+1D5E2>006F
+1D5E3>0070
+1D5E4>0071
+1D5E5>0072
+1D5E6>0073
+1D5E7>0074
+1D5E8>0075
+1D5E9>0076
+1D5EA>0077
+1D5EB>0078
+1D5EC>0079
+1D5ED>007A
+1D5EE>0061
+1D5EF>0062
+1D5F0>0063
+1D5F1>0064
+1D5F2>0065
+1D5F3>0066
+1D5F4>0067
+1D5F5>0068
+1D5F6>0069
+1D5F7>006A
+1D5F8>006B
+1D5F9>006C
+1D5FA>006D
+1D5FB>006E
+1D5FC>006F
+1D5FD>0070
+1D5FE>0071
+1D5FF>0072
+1D600>0073
+1D601>0074
+1D602>0075
+1D603>0076
+1D604>0077
+1D605>0078
+1D606>0079
+1D607>007A
+1D608>0061
+1D609>0062
+1D60A>0063
+1D60B>0064
+1D60C>0065
+1D60D>0066
+1D60E>0067
+1D60F>0068
+1D610>0069
+1D611>006A
+1D612>006B
+1D613>006C
+1D614>006D
+1D615>006E
+1D616>006F
+1D617>0070
+1D618>0071
+1D619>0072
+1D61A>0073
+1D61B>0074
+1D61C>0075
+1D61D>0076
+1D61E>0077
+1D61F>0078
+1D620>0079
+1D621>007A
+1D622>0061
+1D623>0062
+1D624>0063
+1D625>0064
+1D626>0065
+1D627>0066
+1D628>0067
+1D629>0068
+1D62A>0069
+1D62B>006A
+1D62C>006B
+1D62D>006C
+1D62E>006D
+1D62F>006E
+1D630>006F
+1D631>0070
+1D632>0071
+1D633>0072
+1D634>0073
+1D635>0074
+1D636>0075
+1D637>0076
+1D638>0077
+1D639>0078
+1D63A>0079
+1D63B>007A
+1D63C>0061
+1D63D>0062
+1D63E>0063
+1D63F>0064
+1D640>0065
+1D641>0066
+1D642>0067
+1D643>0068
+1D644>0069
+1D645>006A
+1D646>006B
+1D647>006C
+1D648>006D
+1D649>006E
+1D64A>006F
+1D64B>0070
+1D64C>0071
+1D64D>0072
+1D64E>0073
+1D64F>0074
+1D650>0075
+1D651>0076
+1D652>0077
+1D653>0078
+1D654>0079
+1D655>007A
+1D656>0061
+1D657>0062
+1D658>0063
+1D659>0064
+1D65A>0065
+1D65B>0066
+1D65C>0067
+1D65D>0068
+1D65E>0069
+1D65F>006A
+1D660>006B
+1D661>006C
+1D662>006D
+1D663>006E
+1D664>006F
+1D665>0070
+1D666>0071
+1D667>0072
+1D668>0073
+1D669>0074
+1D66A>0075
+1D66B>0076
+1D66C>0077
+1D66D>0078
+1D66E>0079
+1D66F>007A
+1D670>0061
+1D671>0062
+1D672>0063
+1D673>0064
+1D674>0065
+1D675>0066
+1D676>0067
+1D677>0068
+1D678>0069
+1D679>006A
+1D67A>006B
+1D67B>006C
+1D67C>006D
+1D67D>006E
+1D67E>006F
+1D67F>0070
+1D680>0071
+1D681>0072
+1D682>0073
+1D683>0074
+1D684>0075
+1D685>0076
+1D686>0077
+1D687>0078
+1D688>0079
+1D689>007A
+1D68A>0061
+1D68B>0062
+1D68C>0063
+1D68D>0064
+1D68E>0065
+1D68F>0066
+1D690>0067
+1D691>0068
+1D692>0069
+1D693>006A
+1D694>006B
+1D695>006C
+1D696>006D
+1D697>006E
+1D698>006F
+1D699>0070
+1D69A>0071
+1D69B>0072
+1D69C>0073
+1D69D>0074
+1D69E>0075
+1D69F>0076
+1D6A0>0077
+1D6A1>0078
+1D6A2>0079
+1D6A3>007A
+1D6A4>0131
+1D6A5>0237
+1D6A8>03B1
+1D6A9>03B2
+1D6AA>03B3
+1D6AB>03B4
+1D6AC>03B5
+1D6AD>03B6
+1D6AE>03B7
+1D6AF>03B8
+1D6B0>03B9
+1D6B1>03BA
+1D6B2>03BB
+1D6B3>03BC
+1D6B4>03BD
+1D6B5>03BE
+1D6B6>03BF
+1D6B7>03C0
+1D6B8>03C1
+1D6B9>03B8
+1D6BA>03C3
+1D6BB>03C4
+1D6BC>03C5
+1D6BD>03C6
+1D6BE>03C7
+1D6BF>03C8
+1D6C0>03C9
+1D6C1>2207
+1D6C2>03B1
+1D6C3>03B2
+1D6C4>03B3
+1D6C5>03B4
+1D6C6>03B5
+1D6C7>03B6
+1D6C8>03B7
+1D6C9>03B8
+1D6CA>03B9
+1D6CB>03BA
+1D6CC>03BB
+1D6CD>03BC
+1D6CE>03BD
+1D6CF>03BE
+1D6D0>03BF
+1D6D1>03C0
+1D6D2>03C1
+1D6D3..1D6D4>03C3
+1D6D5>03C4
+1D6D6>03C5
+1D6D7>03C6
+1D6D8>03C7
+1D6D9>03C8
+1D6DA>03C9
+1D6DB>2202
+1D6DC>03B5
+1D6DD>03B8
+1D6DE>03BA
+1D6DF>03C6
+1D6E0>03C1
+1D6E1>03C0
+1D6E2>03B1
+1D6E3>03B2
+1D6E4>03B3
+1D6E5>03B4
+1D6E6>03B5
+1D6E7>03B6
+1D6E8>03B7
+1D6E9>03B8
+1D6EA>03B9
+1D6EB>03BA
+1D6EC>03BB
+1D6ED>03BC
+1D6EE>03BD
+1D6EF>03BE
+1D6F0>03BF
+1D6F1>03C0
+1D6F2>03C1
+1D6F3>03B8
+1D6F4>03C3
+1D6F5>03C4
+1D6F6>03C5
+1D6F7>03C6
+1D6F8>03C7
+1D6F9>03C8
+1D6FA>03C9
+1D6FB>2207
+1D6FC>03B1
+1D6FD>03B2
+1D6FE>03B3
+1D6FF>03B4
+1D700>03B5
+1D701>03B6
+1D702>03B7
+1D703>03B8
+1D704>03B9
+1D705>03BA
+1D706>03BB
+1D707>03BC
+1D708>03BD
+1D709>03BE
+1D70A>03BF
+1D70B>03C0
+1D70C>03C1
+1D70D..1D70E>03C3
+1D70F>03C4
+1D710>03C5
+1D711>03C6
+1D712>03C7
+1D713>03C8
+1D714>03C9
+1D715>2202
+1D716>03B5
+1D717>03B8
+1D718>03BA
+1D719>03C6
+1D71A>03C1
+1D71B>03C0
+1D71C>03B1
+1D71D>03B2
+1D71E>03B3
+1D71F>03B4
+1D720>03B5
+1D721>03B6
+1D722>03B7
+1D723>03B8
+1D724>03B9
+1D725>03BA
+1D726>03BB
+1D727>03BC
+1D728>03BD
+1D729>03BE
+1D72A>03BF
+1D72B>03C0
+1D72C>03C1
+1D72D>03B8
+1D72E>03C3
+1D72F>03C4
+1D730>03C5
+1D731>03C6
+1D732>03C7
+1D733>03C8
+1D734>03C9
+1D735>2207
+1D736>03B1
+1D737>03B2
+1D738>03B3
+1D739>03B4
+1D73A>03B5
+1D73B>03B6
+1D73C>03B7
+1D73D>03B8
+1D73E>03B9
+1D73F>03BA
+1D740>03BB
+1D741>03BC
+1D742>03BD
+1D743>03BE
+1D744>03BF
+1D745>03C0
+1D746>03C1
+1D747..1D748>03C3
+1D749>03C4
+1D74A>03C5
+1D74B>03C6
+1D74C>03C7
+1D74D>03C8
+1D74E>03C9
+1D74F>2202
+1D750>03B5
+1D751>03B8
+1D752>03BA
+1D753>03C6
+1D754>03C1
+1D755>03C0
+1D756>03B1
+1D757>03B2
+1D758>03B3
+1D759>03B4
+1D75A>03B5
+1D75B>03B6
+1D75C>03B7
+1D75D>03B8
+1D75E>03B9
+1D75F>03BA
+1D760>03BB
+1D761>03BC
+1D762>03BD
+1D763>03BE
+1D764>03BF
+1D765>03C0
+1D766>03C1
+1D767>03B8
+1D768>03C3
+1D769>03C4
+1D76A>03C5
+1D76B>03C6
+1D76C>03C7
+1D76D>03C8
+1D76E>03C9
+1D76F>2207
+1D770>03B1
+1D771>03B2
+1D772>03B3
+1D773>03B4
+1D774>03B5
+1D775>03B6
+1D776>03B7
+1D777>03B8
+1D778>03B9
+1D779>03BA
+1D77A>03BB
+1D77B>03BC
+1D77C>03BD
+1D77D>03BE
+1D77E>03BF
+1D77F>03C0
+1D780>03C1
+1D781..1D782>03C3
+1D783>03C4
+1D784>03C5
+1D785>03C6
+1D786>03C7
+1D787>03C8
+1D788>03C9
+1D789>2202
+1D78A>03B5
+1D78B>03B8
+1D78C>03BA
+1D78D>03C6
+1D78E>03C1
+1D78F>03C0
+1D790>03B1
+1D791>03B2
+1D792>03B3
+1D793>03B4
+1D794>03B5
+1D795>03B6
+1D796>03B7
+1D797>03B8
+1D798>03B9
+1D799>03BA
+1D79A>03BB
+1D79B>03BC
+1D79C>03BD
+1D79D>03BE
+1D79E>03BF
+1D79F>03C0
+1D7A0>03C1
+1D7A1>03B8
+1D7A2>03C3
+1D7A3>03C4
+1D7A4>03C5
+1D7A5>03C6
+1D7A6>03C7
+1D7A7>03C8
+1D7A8>03C9
+1D7A9>2207
+1D7AA>03B1
+1D7AB>03B2
+1D7AC>03B3
+1D7AD>03B4
+1D7AE>03B5
+1D7AF>03B6
+1D7B0>03B7
+1D7B1>03B8
+1D7B2>03B9
+1D7B3>03BA
+1D7B4>03BB
+1D7B5>03BC
+1D7B6>03BD
+1D7B7>03BE
+1D7B8>03BF
+1D7B9>03C0
+1D7BA>03C1
+1D7BB..1D7BC>03C3
+1D7BD>03C4
+1D7BE>03C5
+1D7BF>03C6
+1D7C0>03C7
+1D7C1>03C8
+1D7C2>03C9
+1D7C3>2202
+1D7C4>03B5
+1D7C5>03B8
+1D7C6>03BA
+1D7C7>03C6
+1D7C8>03C1
+1D7C9>03C0
+1D7CA..1D7CB>03DD
+1D7CE>0030
+1D7CF>0031
+1D7D0>0032
+1D7D1>0033
+1D7D2>0034
+1D7D3>0035
+1D7D4>0036
+1D7D5>0037
+1D7D6>0038
+1D7D7>0039
+1D7D8>0030
+1D7D9>0031
+1D7DA>0032
+1D7DB>0033
+1D7DC>0034
+1D7DD>0035
+1D7DE>0036
+1D7DF>0037
+1D7E0>0038
+1D7E1>0039
+1D7E2>0030
+1D7E3>0031
+1D7E4>0032
+1D7E5>0033
+1D7E6>0034
+1D7E7>0035
+1D7E8>0036
+1D7E9>0037
+1D7EA>0038
+1D7EB>0039
+1D7EC>0030
+1D7ED>0031
+1D7EE>0032
+1D7EF>0033
+1D7F0>0034
+1D7F1>0035
+1D7F2>0036
+1D7F3>0037
+1D7F4>0038
+1D7F5>0039
+1D7F6>0030
+1D7F7>0031
+1D7F8>0032
+1D7F9>0033
+1D7FA>0034
+1D7FB>0035
+1D7FC>0036
+1D7FD>0037
+1D7FE>0038
+1D7FF>0039
+1F100>0030 002E
+1F101>0030 002C
+1F102>0031 002C
+1F103>0032 002C
+1F104>0033 002C
+1F105>0034 002C
+1F106>0035 002C
+1F107>0036 002C
+1F108>0037 002C
+1F109>0038 002C
+1F10A>0039 002C
+1F110>0028 0061 0029
+1F111>0028 0062 0029
+1F112>0028 0063 0029
+1F113>0028 0064 0029
+1F114>0028 0065 0029
+1F115>0028 0066 0029
+1F116>0028 0067 0029
+1F117>0028 0068 0029
+1F118>0028 0069 0029
+1F119>0028 006A 0029
+1F11A>0028 006B 0029
+1F11B>0028 006C 0029
+1F11C>0028 006D 0029
+1F11D>0028 006E 0029
+1F11E>0028 006F 0029
+1F11F>0028 0070 0029
+1F120>0028 0071 0029
+1F121>0028 0072 0029
+1F122>0028 0073 0029
+1F123>0028 0074 0029
+1F124>0028 0075 0029
+1F125>0028 0076 0029
+1F126>0028 0077 0029
+1F127>0028 0078 0029
+1F128>0028 0079 0029
+1F129>0028 007A 0029
+1F12A>3014 0073 3015
+1F12B>0063
+1F12C>0072
+1F12D>0063 0064
+1F12E>0077 007A
+1F131>0062
+1F13D>006E
+1F13F>0070
+1F142>0073
+1F146>0077
+1F14A>0068 0076
+1F14B>006D 0076
+1F14C>0073 0064
+1F14D>0073 0073
+1F14E>0070 0070 0076
+1F190>0064 006A
+1F200>307B 304B
+1F210>624B
+1F211>5B57
+1F212>53CC
+1F213>30C7
+1F214>4E8C
+1F215>591A
+1F216>89E3
+1F217>5929
+1F218>4EA4
+1F219>6620
+1F21A>7121
+1F21B>6599
+1F21C>524D
+1F21D>5F8C
+1F21E>518D
+1F21F>65B0
+1F220>521D
+1F221>7D42
+1F222>751F
+1F223>8CA9
+1F224>58F0
+1F225>5439
+1F226>6F14
+1F227>6295
+1F228>6355
+1F229>4E00
+1F22A>4E09
+1F22B>904A
+1F22C>5DE6
+1F22D>4E2D
+1F22E>53F3
+1F22F>6307
+1F230>8D70
+1F231>6253
+1F240>3014 672C 3015
+1F241>3014 4E09 3015
+1F242>3014 4E8C 3015
+1F243>3014 5B89 3015
+1F244>3014 70B9 3015
+1F245>3014 6253 3015
+1F246>3014 76D7 3015
+1F247>3014 52DD 3015
+1F248>3014 6557 3015
+2F800>4E3D
+2F801>4E38
+2F802>4E41
+2F803>20122
+2F804>4F60
+2F805>4FAE
+2F806>4FBB
+2F807>5002
+2F808>507A
+2F809>5099
+2F80A>50E7
+2F80B>50CF
+2F80C>349E
+2F80D>2063A
+2F80E>514D
+2F80F>5154
+2F810>5164
+2F811>5177
+2F812>2051C
+2F813>34B9
+2F814>5167
+2F815>518D
+2F816>2054B
+2F817>5197
+2F818>51A4
+2F819>4ECC
+2F81A>51AC
+2F81B>51B5
+2F81C>291DF
+2F81D>51F5
+2F81E>5203
+2F81F>34DF
+2F820>523B
+2F821>5246
+2F822>5272
+2F823>5277
+2F824>3515
+2F825>52C7
+2F826>52C9
+2F827>52E4
+2F828>52FA
+2F829>5305
+2F82A>5306
+2F82B>5317
+2F82C>5349
+2F82D>5351
+2F82E>535A
+2F82F>5373
+2F830>537D
+2F831..2F833>537F
+2F834>20A2C
+2F835>7070
+2F836>53CA
+2F837>53DF
+2F838>20B63
+2F839>53EB
+2F83A>53F1
+2F83B>5406
+2F83C>549E
+2F83D>5438
+2F83E>5448
+2F83F>5468
+2F840>54A2
+2F841>54F6
+2F842>5510
+2F843>5553
+2F844>5563
+2F845..2F846>5584
+2F847>5599
+2F848>55AB
+2F849>55B3
+2F84A>55C2
+2F84B>5716
+2F84C>5606
+2F84D>5717
+2F84E>5651
+2F84F>5674
+2F850>5207
+2F851>58EE
+2F852>57CE
+2F853>57F4
+2F854>580D
+2F855>578B
+2F856>5832
+2F857>5831
+2F858>58AC
+2F859>214E4
+2F85A>58F2
+2F85B>58F7
+2F85C>5906
+2F85D>591A
+2F85E>5922
+2F85F>5962
+2F860>216A8
+2F861>216EA
+2F862>59EC
+2F863>5A1B
+2F864>5A27
+2F865>59D8
+2F866>5A66
+2F867>36EE
+2F868>36FC
+2F869>5B08
+2F86A..2F86B>5B3E
+2F86C>219C8
+2F86D>5BC3
+2F86E>5BD8
+2F86F>5BE7
+2F870>5BF3
+2F871>21B18
+2F872>5BFF
+2F873>5C06
+2F874>5F53
+2F875>5C22
+2F876>3781
+2F877>5C60
+2F878>5C6E
+2F879>5CC0
+2F87A>5C8D
+2F87B>21DE4
+2F87C>5D43
+2F87D>21DE6
+2F87E>5D6E
+2F87F>5D6B
+2F880>5D7C
+2F881>5DE1
+2F882>5DE2
+2F883>382F
+2F884>5DFD
+2F885>5E28
+2F886>5E3D
+2F887>5E69
+2F888>3862
+2F889>22183
+2F88A>387C
+2F88B>5EB0
+2F88C>5EB3
+2F88D>5EB6
+2F88E>5ECA
+2F88F>2A392
+2F890>5EFE
+2F891..2F892>22331
+2F893>8201
+2F894..2F895>5F22
+2F896>38C7
+2F897>232B8
+2F898>261DA
+2F899>5F62
+2F89A>5F6B
+2F89B>38E3
+2F89C>5F9A
+2F89D>5FCD
+2F89E>5FD7
+2F89F>5FF9
+2F8A0>6081
+2F8A1>393A
+2F8A2>391C
+2F8A3>6094
+2F8A4>226D4
+2F8A5>60C7
+2F8A6>6148
+2F8A7>614C
+2F8A8>614E
+2F8A9>614C
+2F8AA>617A
+2F8AB>618E
+2F8AC>61B2
+2F8AD>61A4
+2F8AE>61AF
+2F8AF>61DE
+2F8B0>61F2
+2F8B1>61F6
+2F8B2>6210
+2F8B3>621B
+2F8B4>625D
+2F8B5>62B1
+2F8B6>62D4
+2F8B7>6350
+2F8B8>22B0C
+2F8B9>633D
+2F8BA>62FC
+2F8BB>6368
+2F8BC>6383
+2F8BD>63E4
+2F8BE>22BF1
+2F8BF>6422
+2F8C0>63C5
+2F8C1>63A9
+2F8C2>3A2E
+2F8C3>6469
+2F8C4>647E
+2F8C5>649D
+2F8C6>6477
+2F8C7>3A6C
+2F8C8>654F
+2F8C9>656C
+2F8CA>2300A
+2F8CB>65E3
+2F8CC>66F8
+2F8CD>6649
+2F8CE>3B19
+2F8CF>6691
+2F8D0>3B08
+2F8D1>3AE4
+2F8D2>5192
+2F8D3>5195
+2F8D4>6700
+2F8D5>669C
+2F8D6>80AD
+2F8D7>43D9
+2F8D8>6717
+2F8D9>671B
+2F8DA>6721
+2F8DB>675E
+2F8DC>6753
+2F8DD>233C3
+2F8DE>3B49
+2F8DF>67FA
+2F8E0>6785
+2F8E1>6852
+2F8E2>6885
+2F8E3>2346D
+2F8E4>688E
+2F8E5>681F
+2F8E6>6914
+2F8E7>3B9D
+2F8E8>6942
+2F8E9>69A3
+2F8EA>69EA
+2F8EB>6AA8
+2F8EC>236A3
+2F8ED>6ADB
+2F8EE>3C18
+2F8EF>6B21
+2F8F0>238A7
+2F8F1>6B54
+2F8F2>3C4E
+2F8F3>6B72
+2F8F4>6B9F
+2F8F5>6BBA
+2F8F6>6BBB
+2F8F7>23A8D
+2F8F8>21D0B
+2F8F9>23AFA
+2F8FA>6C4E
+2F8FB>23CBC
+2F8FC>6CBF
+2F8FD>6CCD
+2F8FE>6C67
+2F8FF>6D16
+2F900>6D3E
+2F901>6D77
+2F902>6D41
+2F903>6D69
+2F904>6D78
+2F905>6D85
+2F906>23D1E
+2F907>6D34
+2F908>6E2F
+2F909>6E6E
+2F90A>3D33
+2F90B>6ECB
+2F90C>6EC7
+2F90D>23ED1
+2F90E>6DF9
+2F90F>6F6E
+2F910>23F5E
+2F911>23F8E
+2F912>6FC6
+2F913>7039
+2F914>701E
+2F915>701B
+2F916>3D96
+2F917>704A
+2F918>707D
+2F919>7077
+2F91A>70AD
+2F91B>20525
+2F91C>7145
+2F91D>24263
+2F91E>719C
+2F91F>243AB
+2F920>7228
+2F921>7235
+2F922>7250
+2F923>24608
+2F924>7280
+2F925>7295
+2F926>24735
+2F927>24814
+2F928>737A
+2F929>738B
+2F92A>3EAC
+2F92B>73A5
+2F92C..2F92D>3EB8
+2F92E>7447
+2F92F>745C
+2F930>7471
+2F931>7485
+2F932>74CA
+2F933>3F1B
+2F934>7524
+2F935>24C36
+2F936>753E
+2F937>24C92
+2F938>7570
+2F939>2219F
+2F93A>7610
+2F93B>24FA1
+2F93C>24FB8
+2F93D>25044
+2F93E>3FFC
+2F93F>4008
+2F940>76F4
+2F941>250F3
+2F942>250F2
+2F943>25119
+2F944>25133
+2F945>771E
+2F946..2F947>771F
+2F948>774A
+2F949>4039
+2F94A>778B
+2F94B>4046
+2F94C>4096
+2F94D>2541D
+2F94E>784E
+2F94F>788C
+2F950>78CC
+2F951>40E3
+2F952>25626
+2F953>7956
+2F954>2569A
+2F955>256C5
+2F956>798F
+2F957>79EB
+2F958>412F
+2F959>7A40
+2F95A>7A4A
+2F95B>7A4F
+2F95C>2597C
+2F95D..2F95E>25AA7
+2F95F>7AEE
+2F960>4202
+2F961>25BAB
+2F962>7BC6
+2F963>7BC9
+2F964>4227
+2F965>25C80
+2F966>7CD2
+2F967>42A0
+2F968>7CE8
+2F969>7CE3
+2F96A>7D00
+2F96B>25F86
+2F96C>7D63
+2F96D>4301
+2F96E>7DC7
+2F96F>7E02
+2F970>7E45
+2F971>4334
+2F972>26228
+2F973>26247
+2F974>4359
+2F975>262D9
+2F976>7F7A
+2F977>2633E
+2F978>7F95
+2F979>7FFA
+2F97A>8005
+2F97B>264DA
+2F97C>26523
+2F97D>8060
+2F97E>265A8
+2F97F>8070
+2F980>2335F
+2F981>43D5
+2F982>80B2
+2F983>8103
+2F984>440B
+2F985>813E
+2F986>5AB5
+2F987>267A7
+2F988>267B5
+2F989>23393
+2F98A>2339C
+2F98B>8201
+2F98C>8204
+2F98D>8F9E
+2F98E>446B
+2F98F>8291
+2F990>828B
+2F991>829D
+2F992>52B3
+2F993>82B1
+2F994>82B3
+2F995>82BD
+2F996>82E6
+2F997>26B3C
+2F998>82E5
+2F999>831D
+2F99A>8363
+2F99B>83AD
+2F99C>8323
+2F99D>83BD
+2F99E>83E7
+2F99F>8457
+2F9A0>8353
+2F9A1>83CA
+2F9A2>83CC
+2F9A3>83DC
+2F9A4>26C36
+2F9A5>26D6B
+2F9A6>26CD5
+2F9A7>452B
+2F9A8>84F1
+2F9A9>84F3
+2F9AA>8516
+2F9AB>273CA
+2F9AC>8564
+2F9AD>26F2C
+2F9AE>455D
+2F9AF>4561
+2F9B0>26FB1
+2F9B1>270D2
+2F9B2>456B
+2F9B3>8650
+2F9B4>865C
+2F9B5>8667
+2F9B6>8669
+2F9B7>86A9
+2F9B8>8688
+2F9B9>870E
+2F9BA>86E2
+2F9BB>8779
+2F9BC>8728
+2F9BD>876B
+2F9BE>8786
+2F9BF>45D7
+2F9C0>87E1
+2F9C1>8801
+2F9C2>45F9
+2F9C3>8860
+2F9C4>8863
+2F9C5>27667
+2F9C6>88D7
+2F9C7>88DE
+2F9C8>4635
+2F9C9>88FA
+2F9CA>34BB
+2F9CB>278AE
+2F9CC>27966
+2F9CD>46BE
+2F9CE>46C7
+2F9CF>8AA0
+2F9D0>8AED
+2F9D1>8B8A
+2F9D2>8C55
+2F9D3>27CA8
+2F9D4>8CAB
+2F9D5>8CC1
+2F9D6>8D1B
+2F9D7>8D77
+2F9D8>27F2F
+2F9D9>20804
+2F9DA>8DCB
+2F9DB>8DBC
+2F9DC>8DF0
+2F9DD>208DE
+2F9DE>8ED4
+2F9DF>8F38
+2F9E0>285D2
+2F9E1>285ED
+2F9E2>9094
+2F9E3>90F1
+2F9E4>9111
+2F9E5>2872E
+2F9E6>911B
+2F9E7>9238
+2F9E8>92D7
+2F9E9>92D8
+2F9EA>927C
+2F9EB>93F9
+2F9EC>9415
+2F9ED>28BFA
+2F9EE>958B
+2F9EF>4995
+2F9F0>95B7
+2F9F1>28D77
+2F9F2>49E6
+2F9F3>96C3
+2F9F4>5DB2
+2F9F5>9723
+2F9F6>29145
+2F9F7>2921A
+2F9F8>4A6E
+2F9F9>4A76
+2F9FA>97E0
+2F9FB>2940A
+2F9FC>4AB2
+2F9FD>29496
+2F9FE..2F9FF>980B
+2FA00>9829
+2FA01>295B6
+2FA02>98E2
+2FA03>4B33
+2FA04>9929
+2FA05>99A7
+2FA06>99C2
+2FA07>99FE
+2FA08>4BCE
+2FA09>29B30
+2FA0A>9B12
+2FA0B>9C40
+2FA0C>9CFD
+2FA0D>4CCE
+2FA0E>4CED
+2FA0F>9D67
+2FA10>2A0CE
+2FA11>4CF8
+2FA12>2A105
+2FA13>2A20E
+2FA14>2A291
+2FA15>9EBB
+2FA16>4D56
+2FA17>9EF9
+2FA18>9EFE
+2FA19>9F05
+2FA1A>9F0F
+2FA1B>9F16
+2FA1C>9F3B
+2FA1D>2A600
+E0000>
+E0001>
+E0002..E001F>
+E0020..E007F>
+E0080..E00FF>
+E0100..E01EF>
+E01F0..E0FFF>
+
+# Total code points: 9740
diff --git a/mailnews/extensions/fts3/public/moz.build b/mailnews/extensions/fts3/public/moz.build
new file mode 100644
index 000000000..9d38f0177
--- /dev/null
+++ b/mailnews/extensions/fts3/public/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIFts3Tokenizer.idl',
+]
+
+XPIDL_MODULE = 'fts3tok'
+
diff --git a/mailnews/extensions/fts3/public/nsIFts3Tokenizer.idl b/mailnews/extensions/fts3/public/nsIFts3Tokenizer.idl
new file mode 100644
index 000000000..c2bb7d435
--- /dev/null
+++ b/mailnews/extensions/fts3/public/nsIFts3Tokenizer.idl
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIStorageConnection;
+
+[scriptable, uuid(136c88ea-7003-4fe8-8835-333fd18e598c)]
+interface nsIFts3Tokenizer : nsISupports {
+ // register FTS3 tokenizer module for "mozporter" tokenizer
+ // mozporter is based by porter tokenizer with bi-gram tokenizer for CJK
+ void registerTokenizer(in mozIStorageConnection connection);
+};
diff --git a/mailnews/extensions/fts3/src/Normalize.c b/mailnews/extensions/fts3/src/Normalize.c
new file mode 100644
index 000000000..92ac400e2
--- /dev/null
+++ b/mailnews/extensions/fts3/src/Normalize.c
@@ -0,0 +1,1929 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* THIS FILE IS GENERATED BY generate_table.py. DON'T EDIT THIS */
+
+static const unsigned short gNormalizeTable0040[] = {
+ /* U+0040 */
+ 0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+ };
+
+static const unsigned short gNormalizeTable0080[] = {
+ /* U+0080 */
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+ 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+ 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+ 0x0020, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+ 0x0020, 0x00a9, 0x0061, 0x00ab, 0x00ac, 0x0020, 0x00ae, 0x0020,
+ 0x00b0, 0x00b1, 0x0032, 0x0033, 0x0020, 0x03bc, 0x00b6, 0x00b7,
+ 0x0020, 0x0031, 0x006f, 0x00bb, 0x0031, 0x0031, 0x0033, 0x00bf,
+ };
+
+static const unsigned short gNormalizeTable00c0[] = {
+ /* U+00c0 */
+ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063,
+ 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
+ 0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00d7,
+ 0x00f8, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0073,
+ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063,
+ 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
+ 0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00f7,
+ 0x00f8, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0079,
+ };
+
+static const unsigned short gNormalizeTable0100[] = {
+ /* U+0100 */
+ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0063, 0x0063,
+ 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0064, 0x0064,
+ 0x0111, 0x0111, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065,
+ 0x0065, 0x0065, 0x0065, 0x0065, 0x0067, 0x0067, 0x0067, 0x0067,
+ 0x0067, 0x0067, 0x0067, 0x0067, 0x0068, 0x0068, 0x0127, 0x0127,
+ 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0069,
+ 0x0069, 0x0131, 0x0069, 0x0069, 0x006a, 0x006a, 0x006b, 0x006b,
+ 0x0138, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c,
+ };
+
+static const unsigned short gNormalizeTable0140[] = {
+ /* U+0140 */
+ 0x006c, 0x0142, 0x0142, 0x006e, 0x006e, 0x006e, 0x006e, 0x006e,
+ 0x006e, 0x02bc, 0x014b, 0x014b, 0x006f, 0x006f, 0x006f, 0x006f,
+ 0x006f, 0x006f, 0x0153, 0x0153, 0x0072, 0x0072, 0x0072, 0x0072,
+ 0x0072, 0x0072, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073,
+ 0x0073, 0x0073, 0x0074, 0x0074, 0x0074, 0x0074, 0x0167, 0x0167,
+ 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075,
+ 0x0075, 0x0075, 0x0075, 0x0075, 0x0077, 0x0077, 0x0079, 0x0079,
+ 0x0079, 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x0073,
+ };
+
+static const unsigned short gNormalizeTable0180[] = {
+ /* U+0180 */
+ 0x0180, 0x0253, 0x0183, 0x0183, 0x0185, 0x0185, 0x0254, 0x0188,
+ 0x0188, 0x0256, 0x0257, 0x018c, 0x018c, 0x018d, 0x01dd, 0x0259,
+ 0x025b, 0x0192, 0x0192, 0x0260, 0x0263, 0x0195, 0x0269, 0x0268,
+ 0x0199, 0x0199, 0x019a, 0x019b, 0x026f, 0x0272, 0x019e, 0x0275,
+ 0x006f, 0x006f, 0x01a3, 0x01a3, 0x01a5, 0x01a5, 0x0280, 0x01a8,
+ 0x01a8, 0x0283, 0x01aa, 0x01ab, 0x01ad, 0x01ad, 0x0288, 0x0075,
+ 0x0075, 0x028a, 0x028b, 0x01b4, 0x01b4, 0x01b6, 0x01b6, 0x0292,
+ 0x01b9, 0x01b9, 0x01ba, 0x01bb, 0x01bd, 0x01bd, 0x01be, 0x01bf,
+ };
+
+static const unsigned short gNormalizeTable01c0[] = {
+ /* U+01c0 */
+ 0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x0064, 0x0064, 0x0064, 0x006c,
+ 0x006c, 0x006c, 0x006e, 0x006e, 0x006e, 0x0061, 0x0061, 0x0069,
+ 0x0069, 0x006f, 0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075,
+ 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x01dd, 0x0061, 0x0061,
+ 0x0061, 0x0061, 0x00e6, 0x00e6, 0x01e5, 0x01e5, 0x0067, 0x0067,
+ 0x006b, 0x006b, 0x006f, 0x006f, 0x006f, 0x006f, 0x0292, 0x0292,
+ 0x006a, 0x0064, 0x0064, 0x0064, 0x0067, 0x0067, 0x0195, 0x01bf,
+ 0x006e, 0x006e, 0x0061, 0x0061, 0x00e6, 0x00e6, 0x00f8, 0x00f8,
+ };
+
+static const unsigned short gNormalizeTable0200[] = {
+ /* U+0200 */
+ 0x0061, 0x0061, 0x0061, 0x0061, 0x0065, 0x0065, 0x0065, 0x0065,
+ 0x0069, 0x0069, 0x0069, 0x0069, 0x006f, 0x006f, 0x006f, 0x006f,
+ 0x0072, 0x0072, 0x0072, 0x0072, 0x0075, 0x0075, 0x0075, 0x0075,
+ 0x0073, 0x0073, 0x0074, 0x0074, 0x021d, 0x021d, 0x0068, 0x0068,
+ 0x019e, 0x0221, 0x0223, 0x0223, 0x0225, 0x0225, 0x0061, 0x0061,
+ 0x0065, 0x0065, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f,
+ 0x006f, 0x006f, 0x0079, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
+ 0x0238, 0x0239, 0x2c65, 0x023c, 0x023c, 0x019a, 0x2c66, 0x023f,
+ };
+
+static const unsigned short gNormalizeTable0240[] = {
+ /* U+0240 */
+ 0x0240, 0x0242, 0x0242, 0x0180, 0x0289, 0x028c, 0x0247, 0x0247,
+ 0x0249, 0x0249, 0x024b, 0x024b, 0x024d, 0x024d, 0x024f, 0x024f,
+ 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
+ 0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f,
+ 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
+ 0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f,
+ 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
+ 0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f,
+ };
+
+static const unsigned short gNormalizeTable0280[] = {
+ /* U+0280 */
+ 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
+ 0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f,
+ 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
+ 0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f,
+ 0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7,
+ 0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af,
+ 0x0068, 0x0266, 0x006a, 0x0072, 0x0279, 0x027b, 0x0281, 0x0077,
+ 0x0079, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf,
+ };
+
+static const unsigned short gNormalizeTable02c0[] = {
+ /* U+02c0 */
+ 0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7,
+ 0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf,
+ 0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02de, 0x02df,
+ 0x0263, 0x006c, 0x0073, 0x0078, 0x0295, 0x02e5, 0x02e6, 0x02e7,
+ 0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef,
+ 0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7,
+ 0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff,
+ };
+
+static const unsigned short gNormalizeTable0340[] = {
+ /* U+0340 */
+ 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x03b9, 0x0346, 0x0347,
+ 0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x0020,
+ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
+ 0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f,
+ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
+ 0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f,
+ 0x0371, 0x0371, 0x0373, 0x0373, 0x02b9, 0x0375, 0x0377, 0x0377,
+ 0x0378, 0x0379, 0x0020, 0x037b, 0x037c, 0x037d, 0x003b, 0x037f,
+ };
+
+static const unsigned short gNormalizeTable0380[] = {
+ /* U+0380 */
+ 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x0020, 0x03b1, 0x00b7,
+ 0x03b5, 0x03b7, 0x03b9, 0x038b, 0x03bf, 0x038d, 0x03c5, 0x03c9,
+ 0x03b9, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7,
+ 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf,
+ 0x03c0, 0x03c1, 0x03a2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7,
+ 0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03b1, 0x03b5, 0x03b7, 0x03b9,
+ 0x03c5, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7,
+ 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf,
+ };
+
+static const unsigned short gNormalizeTable03c0[] = {
+ /* U+03c0 */
+ 0x03c0, 0x03c1, 0x03c3, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7,
+ 0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03bf, 0x03c5, 0x03c9, 0x03d7,
+ 0x03b2, 0x03b8, 0x03c5, 0x03c5, 0x03c5, 0x03c6, 0x03c0, 0x03d7,
+ 0x03d9, 0x03d9, 0x03db, 0x03db, 0x03dd, 0x03dd, 0x03df, 0x03df,
+ 0x03e1, 0x03e1, 0x03e3, 0x03e3, 0x03e5, 0x03e5, 0x03e7, 0x03e7,
+ 0x03e9, 0x03e9, 0x03eb, 0x03eb, 0x03ed, 0x03ed, 0x03ef, 0x03ef,
+ 0x03ba, 0x03c1, 0x03c3, 0x03f3, 0x03b8, 0x03b5, 0x03f6, 0x03f8,
+ 0x03f8, 0x03c3, 0x03fb, 0x03fb, 0x03fc, 0x037b, 0x037c, 0x037d,
+ };
+
+static const unsigned short gNormalizeTable0400[] = {
+ /* U+0400 */
+ 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
+ 0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f,
+ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+ 0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
+ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+ 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f,
+ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+ 0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
+ };
+
+static const unsigned short gNormalizeTable0440[] = {
+ /* U+0440 */
+ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+ 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f,
+ 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
+ 0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f,
+ 0x0461, 0x0461, 0x0463, 0x0463, 0x0465, 0x0465, 0x0467, 0x0467,
+ 0x0469, 0x0469, 0x046b, 0x046b, 0x046d, 0x046d, 0x046f, 0x046f,
+ 0x0471, 0x0471, 0x0473, 0x0473, 0x0475, 0x0475, 0x0475, 0x0475,
+ 0x0479, 0x0479, 0x047b, 0x047b, 0x047d, 0x047d, 0x047f, 0x047f,
+ };
+
+static const unsigned short gNormalizeTable0480[] = {
+ /* U+0480 */
+ 0x0481, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
+ 0x0488, 0x0489, 0x048b, 0x048b, 0x048d, 0x048d, 0x048f, 0x048f,
+ 0x0491, 0x0491, 0x0493, 0x0493, 0x0495, 0x0495, 0x0497, 0x0497,
+ 0x0499, 0x0499, 0x049b, 0x049b, 0x049d, 0x049d, 0x049f, 0x049f,
+ 0x04a1, 0x04a1, 0x04a3, 0x04a3, 0x04a5, 0x04a5, 0x04a7, 0x04a7,
+ 0x04a9, 0x04a9, 0x04ab, 0x04ab, 0x04ad, 0x04ad, 0x04af, 0x04af,
+ 0x04b1, 0x04b1, 0x04b3, 0x04b3, 0x04b5, 0x04b5, 0x04b7, 0x04b7,
+ 0x04b9, 0x04b9, 0x04bb, 0x04bb, 0x04bd, 0x04bd, 0x04bf, 0x04bf,
+ };
+
+static const unsigned short gNormalizeTable04c0[] = {
+ /* U+04c0 */
+ 0x04cf, 0x0436, 0x0436, 0x04c4, 0x04c4, 0x04c6, 0x04c6, 0x04c8,
+ 0x04c8, 0x04ca, 0x04ca, 0x04cc, 0x04cc, 0x04ce, 0x04ce, 0x04cf,
+ 0x0430, 0x0430, 0x0430, 0x0430, 0x04d5, 0x04d5, 0x0435, 0x0435,
+ 0x04d9, 0x04d9, 0x04d9, 0x04d9, 0x0436, 0x0436, 0x0437, 0x0437,
+ 0x04e1, 0x04e1, 0x0438, 0x0438, 0x0438, 0x0438, 0x043e, 0x043e,
+ 0x04e9, 0x04e9, 0x04e9, 0x04e9, 0x044d, 0x044d, 0x0443, 0x0443,
+ 0x0443, 0x0443, 0x0443, 0x0443, 0x0447, 0x0447, 0x04f7, 0x04f7,
+ 0x044b, 0x044b, 0x04fb, 0x04fb, 0x04fd, 0x04fd, 0x04ff, 0x04ff,
+ };
+
+static const unsigned short gNormalizeTable0500[] = {
+ /* U+0500 */
+ 0x0501, 0x0501, 0x0503, 0x0503, 0x0505, 0x0505, 0x0507, 0x0507,
+ 0x0509, 0x0509, 0x050b, 0x050b, 0x050d, 0x050d, 0x050f, 0x050f,
+ 0x0511, 0x0511, 0x0513, 0x0513, 0x0515, 0x0515, 0x0517, 0x0517,
+ 0x0519, 0x0519, 0x051b, 0x051b, 0x051d, 0x051d, 0x051f, 0x051f,
+ 0x0521, 0x0521, 0x0523, 0x0523, 0x0525, 0x0525, 0x0526, 0x0527,
+ 0x0528, 0x0529, 0x052a, 0x052b, 0x052c, 0x052d, 0x052e, 0x052f,
+ 0x0530, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567,
+ 0x0568, 0x0569, 0x056a, 0x056b, 0x056c, 0x056d, 0x056e, 0x056f,
+ };
+
+static const unsigned short gNormalizeTable0540[] = {
+ /* U+0540 */
+ 0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577,
+ 0x0578, 0x0579, 0x057a, 0x057b, 0x057c, 0x057d, 0x057e, 0x057f,
+ 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0557,
+ 0x0558, 0x0559, 0x055a, 0x055b, 0x055c, 0x055d, 0x055e, 0x055f,
+ 0x0560, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567,
+ 0x0568, 0x0569, 0x056a, 0x056b, 0x056c, 0x056d, 0x056e, 0x056f,
+ 0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577,
+ 0x0578, 0x0579, 0x057a, 0x057b, 0x057c, 0x057d, 0x057e, 0x057f,
+ };
+
+static const unsigned short gNormalizeTable0580[] = {
+ /* U+0580 */
+ 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0565,
+ 0x0588, 0x0589, 0x058a, 0x058b, 0x058c, 0x058d, 0x058e, 0x058f,
+ 0x0590, 0x0591, 0x0592, 0x0593, 0x0594, 0x0595, 0x0596, 0x0597,
+ 0x0598, 0x0599, 0x059a, 0x059b, 0x059c, 0x059d, 0x059e, 0x059f,
+ 0x05a0, 0x05a1, 0x05a2, 0x05a3, 0x05a4, 0x05a5, 0x05a6, 0x05a7,
+ 0x05a8, 0x05a9, 0x05aa, 0x05ab, 0x05ac, 0x05ad, 0x05ae, 0x05af,
+ 0x05b0, 0x05b1, 0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7,
+ 0x05b8, 0x05b9, 0x05ba, 0x05bb, 0x05bc, 0x05bd, 0x05be, 0x05bf,
+ };
+
+static const unsigned short gNormalizeTable0600[] = {
+ /* U+0600 */
+ 0x0600, 0x0601, 0x0602, 0x0603, 0x0604, 0x0605, 0x0606, 0x0607,
+ 0x0608, 0x0609, 0x060a, 0x060b, 0x060c, 0x060d, 0x060e, 0x060f,
+ 0x0610, 0x0611, 0x0612, 0x0613, 0x0614, 0x0615, 0x0616, 0x0617,
+ 0x0618, 0x0619, 0x061a, 0x061b, 0x061c, 0x061d, 0x061e, 0x061f,
+ 0x0620, 0x0621, 0x0627, 0x0627, 0x0648, 0x0627, 0x064a, 0x0627,
+ 0x0628, 0x0629, 0x062a, 0x062b, 0x062c, 0x062d, 0x062e, 0x062f,
+ 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637,
+ 0x0638, 0x0639, 0x063a, 0x063b, 0x063c, 0x063d, 0x063e, 0x063f,
+ };
+
+static const unsigned short gNormalizeTable0640[] = {
+ /* U+0640 */
+ 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647,
+ 0x0648, 0x0649, 0x064a, 0x064b, 0x064c, 0x064d, 0x064e, 0x064f,
+ 0x0650, 0x0651, 0x0652, 0x0653, 0x0654, 0x0655, 0x0656, 0x0657,
+ 0x0658, 0x0659, 0x065a, 0x065b, 0x065c, 0x065d, 0x065e, 0x065f,
+ 0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667,
+ 0x0668, 0x0669, 0x066a, 0x066b, 0x066c, 0x066d, 0x066e, 0x066f,
+ 0x0670, 0x0671, 0x0672, 0x0673, 0x0674, 0x0627, 0x0648, 0x06c7,
+ 0x064a, 0x0679, 0x067a, 0x067b, 0x067c, 0x067d, 0x067e, 0x067f,
+ };
+
+static const unsigned short gNormalizeTable06c0[] = {
+ /* U+06c0 */
+ 0x06d5, 0x06c1, 0x06c1, 0x06c3, 0x06c4, 0x06c5, 0x06c6, 0x06c7,
+ 0x06c8, 0x06c9, 0x06ca, 0x06cb, 0x06cc, 0x06cd, 0x06ce, 0x06cf,
+ 0x06d0, 0x06d1, 0x06d2, 0x06d2, 0x06d4, 0x06d5, 0x06d6, 0x06d7,
+ 0x06d8, 0x06d9, 0x06da, 0x06db, 0x06dc, 0x06dd, 0x06de, 0x06df,
+ 0x06e0, 0x06e1, 0x06e2, 0x06e3, 0x06e4, 0x06e5, 0x06e6, 0x06e7,
+ 0x06e8, 0x06e9, 0x06ea, 0x06eb, 0x06ec, 0x06ed, 0x06ee, 0x06ef,
+ 0x06f0, 0x06f1, 0x06f2, 0x06f3, 0x06f4, 0x06f5, 0x06f6, 0x06f7,
+ 0x06f8, 0x06f9, 0x06fa, 0x06fb, 0x06fc, 0x06fd, 0x06fe, 0x06ff,
+ };
+
+static const unsigned short gNormalizeTable0900[] = {
+ /* U+0900 */
+ 0x0900, 0x0901, 0x0902, 0x0903, 0x0904, 0x0905, 0x0906, 0x0907,
+ 0x0908, 0x0909, 0x090a, 0x090b, 0x090c, 0x090d, 0x090e, 0x090f,
+ 0x0910, 0x0911, 0x0912, 0x0913, 0x0914, 0x0915, 0x0916, 0x0917,
+ 0x0918, 0x0919, 0x091a, 0x091b, 0x091c, 0x091d, 0x091e, 0x091f,
+ 0x0920, 0x0921, 0x0922, 0x0923, 0x0924, 0x0925, 0x0926, 0x0927,
+ 0x0928, 0x0928, 0x092a, 0x092b, 0x092c, 0x092d, 0x092e, 0x092f,
+ 0x0930, 0x0930, 0x0932, 0x0933, 0x0933, 0x0935, 0x0936, 0x0937,
+ 0x0938, 0x0939, 0x093a, 0x093b, 0x093c, 0x093d, 0x093e, 0x093f,
+ };
+
+static const unsigned short gNormalizeTable0940[] = {
+ /* U+0940 */
+ 0x0940, 0x0941, 0x0942, 0x0943, 0x0944, 0x0945, 0x0946, 0x0947,
+ 0x0948, 0x0949, 0x094a, 0x094b, 0x094c, 0x094d, 0x094e, 0x094f,
+ 0x0950, 0x0951, 0x0952, 0x0953, 0x0954, 0x0955, 0x0956, 0x0957,
+ 0x0915, 0x0916, 0x0917, 0x091c, 0x0921, 0x0922, 0x092b, 0x092f,
+ 0x0960, 0x0961, 0x0962, 0x0963, 0x0964, 0x0965, 0x0966, 0x0967,
+ 0x0968, 0x0969, 0x096a, 0x096b, 0x096c, 0x096d, 0x096e, 0x096f,
+ 0x0970, 0x0971, 0x0972, 0x0973, 0x0974, 0x0975, 0x0976, 0x0977,
+ 0x0978, 0x0979, 0x097a, 0x097b, 0x097c, 0x097d, 0x097e, 0x097f,
+ };
+
+static const unsigned short gNormalizeTable09c0[] = {
+ /* U+09c0 */
+ 0x09c0, 0x09c1, 0x09c2, 0x09c3, 0x09c4, 0x09c5, 0x09c6, 0x09c7,
+ 0x09c8, 0x09c9, 0x09ca, 0x09c7, 0x09c7, 0x09cd, 0x09ce, 0x09cf,
+ 0x09d0, 0x09d1, 0x09d2, 0x09d3, 0x09d4, 0x09d5, 0x09d6, 0x09d7,
+ 0x09d8, 0x09d9, 0x09da, 0x09db, 0x09a1, 0x09a2, 0x09de, 0x09af,
+ 0x09e0, 0x09e1, 0x09e2, 0x09e3, 0x09e4, 0x09e5, 0x09e6, 0x09e7,
+ 0x09e8, 0x09e9, 0x09ea, 0x09eb, 0x09ec, 0x09ed, 0x09ee, 0x09ef,
+ 0x09f0, 0x09f1, 0x09f2, 0x09f3, 0x09f4, 0x09f5, 0x09f6, 0x09f7,
+ 0x09f8, 0x09f9, 0x09fa, 0x09fb, 0x09fc, 0x09fd, 0x09fe, 0x09ff,
+ };
+
+static const unsigned short gNormalizeTable0a00[] = {
+ /* U+0a00 */
+ 0x0a00, 0x0a01, 0x0a02, 0x0a03, 0x0a04, 0x0a05, 0x0a06, 0x0a07,
+ 0x0a08, 0x0a09, 0x0a0a, 0x0a0b, 0x0a0c, 0x0a0d, 0x0a0e, 0x0a0f,
+ 0x0a10, 0x0a11, 0x0a12, 0x0a13, 0x0a14, 0x0a15, 0x0a16, 0x0a17,
+ 0x0a18, 0x0a19, 0x0a1a, 0x0a1b, 0x0a1c, 0x0a1d, 0x0a1e, 0x0a1f,
+ 0x0a20, 0x0a21, 0x0a22, 0x0a23, 0x0a24, 0x0a25, 0x0a26, 0x0a27,
+ 0x0a28, 0x0a29, 0x0a2a, 0x0a2b, 0x0a2c, 0x0a2d, 0x0a2e, 0x0a2f,
+ 0x0a30, 0x0a31, 0x0a32, 0x0a32, 0x0a34, 0x0a35, 0x0a38, 0x0a37,
+ 0x0a38, 0x0a39, 0x0a3a, 0x0a3b, 0x0a3c, 0x0a3d, 0x0a3e, 0x0a3f,
+ };
+
+static const unsigned short gNormalizeTable0a40[] = {
+ /* U+0a40 */
+ 0x0a40, 0x0a41, 0x0a42, 0x0a43, 0x0a44, 0x0a45, 0x0a46, 0x0a47,
+ 0x0a48, 0x0a49, 0x0a4a, 0x0a4b, 0x0a4c, 0x0a4d, 0x0a4e, 0x0a4f,
+ 0x0a50, 0x0a51, 0x0a52, 0x0a53, 0x0a54, 0x0a55, 0x0a56, 0x0a57,
+ 0x0a58, 0x0a16, 0x0a17, 0x0a1c, 0x0a5c, 0x0a5d, 0x0a2b, 0x0a5f,
+ 0x0a60, 0x0a61, 0x0a62, 0x0a63, 0x0a64, 0x0a65, 0x0a66, 0x0a67,
+ 0x0a68, 0x0a69, 0x0a6a, 0x0a6b, 0x0a6c, 0x0a6d, 0x0a6e, 0x0a6f,
+ 0x0a70, 0x0a71, 0x0a72, 0x0a73, 0x0a74, 0x0a75, 0x0a76, 0x0a77,
+ 0x0a78, 0x0a79, 0x0a7a, 0x0a7b, 0x0a7c, 0x0a7d, 0x0a7e, 0x0a7f,
+ };
+
+static const unsigned short gNormalizeTable0b40[] = {
+ /* U+0b40 */
+ 0x0b40, 0x0b41, 0x0b42, 0x0b43, 0x0b44, 0x0b45, 0x0b46, 0x0b47,
+ 0x0b47, 0x0b49, 0x0b4a, 0x0b47, 0x0b47, 0x0b4d, 0x0b4e, 0x0b4f,
+ 0x0b50, 0x0b51, 0x0b52, 0x0b53, 0x0b54, 0x0b55, 0x0b56, 0x0b57,
+ 0x0b58, 0x0b59, 0x0b5a, 0x0b5b, 0x0b21, 0x0b22, 0x0b5e, 0x0b5f,
+ 0x0b60, 0x0b61, 0x0b62, 0x0b63, 0x0b64, 0x0b65, 0x0b66, 0x0b67,
+ 0x0b68, 0x0b69, 0x0b6a, 0x0b6b, 0x0b6c, 0x0b6d, 0x0b6e, 0x0b6f,
+ 0x0b70, 0x0b71, 0x0b72, 0x0b73, 0x0b74, 0x0b75, 0x0b76, 0x0b77,
+ 0x0b78, 0x0b79, 0x0b7a, 0x0b7b, 0x0b7c, 0x0b7d, 0x0b7e, 0x0b7f,
+ };
+
+static const unsigned short gNormalizeTable0b80[] = {
+ /* U+0b80 */
+ 0x0b80, 0x0b81, 0x0b82, 0x0b83, 0x0b84, 0x0b85, 0x0b86, 0x0b87,
+ 0x0b88, 0x0b89, 0x0b8a, 0x0b8b, 0x0b8c, 0x0b8d, 0x0b8e, 0x0b8f,
+ 0x0b90, 0x0b91, 0x0b92, 0x0b93, 0x0b92, 0x0b95, 0x0b96, 0x0b97,
+ 0x0b98, 0x0b99, 0x0b9a, 0x0b9b, 0x0b9c, 0x0b9d, 0x0b9e, 0x0b9f,
+ 0x0ba0, 0x0ba1, 0x0ba2, 0x0ba3, 0x0ba4, 0x0ba5, 0x0ba6, 0x0ba7,
+ 0x0ba8, 0x0ba9, 0x0baa, 0x0bab, 0x0bac, 0x0bad, 0x0bae, 0x0baf,
+ 0x0bb0, 0x0bb1, 0x0bb2, 0x0bb3, 0x0bb4, 0x0bb5, 0x0bb6, 0x0bb7,
+ 0x0bb8, 0x0bb9, 0x0bba, 0x0bbb, 0x0bbc, 0x0bbd, 0x0bbe, 0x0bbf,
+ };
+
+static const unsigned short gNormalizeTable0bc0[] = {
+ /* U+0bc0 */
+ 0x0bc0, 0x0bc1, 0x0bc2, 0x0bc3, 0x0bc4, 0x0bc5, 0x0bc6, 0x0bc7,
+ 0x0bc8, 0x0bc9, 0x0bc6, 0x0bc7, 0x0bc6, 0x0bcd, 0x0bce, 0x0bcf,
+ 0x0bd0, 0x0bd1, 0x0bd2, 0x0bd3, 0x0bd4, 0x0bd5, 0x0bd6, 0x0bd7,
+ 0x0bd8, 0x0bd9, 0x0bda, 0x0bdb, 0x0bdc, 0x0bdd, 0x0bde, 0x0bdf,
+ 0x0be0, 0x0be1, 0x0be2, 0x0be3, 0x0be4, 0x0be5, 0x0be6, 0x0be7,
+ 0x0be8, 0x0be9, 0x0bea, 0x0beb, 0x0bec, 0x0bed, 0x0bee, 0x0bef,
+ 0x0bf0, 0x0bf1, 0x0bf2, 0x0bf3, 0x0bf4, 0x0bf5, 0x0bf6, 0x0bf7,
+ 0x0bf8, 0x0bf9, 0x0bfa, 0x0bfb, 0x0bfc, 0x0bfd, 0x0bfe, 0x0bff,
+ };
+
+static const unsigned short gNormalizeTable0c40[] = {
+ /* U+0c40 */
+ 0x0c40, 0x0c41, 0x0c42, 0x0c43, 0x0c44, 0x0c45, 0x0c46, 0x0c47,
+ 0x0c46, 0x0c49, 0x0c4a, 0x0c4b, 0x0c4c, 0x0c4d, 0x0c4e, 0x0c4f,
+ 0x0c50, 0x0c51, 0x0c52, 0x0c53, 0x0c54, 0x0c55, 0x0c56, 0x0c57,
+ 0x0c58, 0x0c59, 0x0c5a, 0x0c5b, 0x0c5c, 0x0c5d, 0x0c5e, 0x0c5f,
+ 0x0c60, 0x0c61, 0x0c62, 0x0c63, 0x0c64, 0x0c65, 0x0c66, 0x0c67,
+ 0x0c68, 0x0c69, 0x0c6a, 0x0c6b, 0x0c6c, 0x0c6d, 0x0c6e, 0x0c6f,
+ 0x0c70, 0x0c71, 0x0c72, 0x0c73, 0x0c74, 0x0c75, 0x0c76, 0x0c77,
+ 0x0c78, 0x0c79, 0x0c7a, 0x0c7b, 0x0c7c, 0x0c7d, 0x0c7e, 0x0c7f,
+ };
+
+static const unsigned short gNormalizeTable0cc0[] = {
+ /* U+0cc0 */
+ 0x0cbf, 0x0cc1, 0x0cc2, 0x0cc3, 0x0cc4, 0x0cc5, 0x0cc6, 0x0cc6,
+ 0x0cc6, 0x0cc9, 0x0cc6, 0x0cc6, 0x0ccc, 0x0ccd, 0x0cce, 0x0ccf,
+ 0x0cd0, 0x0cd1, 0x0cd2, 0x0cd3, 0x0cd4, 0x0cd5, 0x0cd6, 0x0cd7,
+ 0x0cd8, 0x0cd9, 0x0cda, 0x0cdb, 0x0cdc, 0x0cdd, 0x0cde, 0x0cdf,
+ 0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x0ce7,
+ 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x0cef,
+ 0x0cf0, 0x0cf1, 0x0cf2, 0x0cf3, 0x0cf4, 0x0cf5, 0x0cf6, 0x0cf7,
+ 0x0cf8, 0x0cf9, 0x0cfa, 0x0cfb, 0x0cfc, 0x0cfd, 0x0cfe, 0x0cff,
+ };
+
+static const unsigned short gNormalizeTable0d40[] = {
+ /* U+0d40 */
+ 0x0d40, 0x0d41, 0x0d42, 0x0d43, 0x0d44, 0x0d45, 0x0d46, 0x0d47,
+ 0x0d48, 0x0d49, 0x0d46, 0x0d47, 0x0d46, 0x0d4d, 0x0d4e, 0x0d4f,
+ 0x0d50, 0x0d51, 0x0d52, 0x0d53, 0x0d54, 0x0d55, 0x0d56, 0x0d57,
+ 0x0d58, 0x0d59, 0x0d5a, 0x0d5b, 0x0d5c, 0x0d5d, 0x0d5e, 0x0d5f,
+ 0x0d60, 0x0d61, 0x0d62, 0x0d63, 0x0d64, 0x0d65, 0x0d66, 0x0d67,
+ 0x0d68, 0x0d69, 0x0d6a, 0x0d6b, 0x0d6c, 0x0d6d, 0x0d6e, 0x0d6f,
+ 0x0d70, 0x0d71, 0x0d72, 0x0d73, 0x0d74, 0x0d75, 0x0d76, 0x0d77,
+ 0x0d78, 0x0d79, 0x0d7a, 0x0d7b, 0x0d7c, 0x0d7d, 0x0d7e, 0x0d7f,
+ };
+
+static const unsigned short gNormalizeTable0dc0[] = {
+ /* U+0dc0 */
+ 0x0dc0, 0x0dc1, 0x0dc2, 0x0dc3, 0x0dc4, 0x0dc5, 0x0dc6, 0x0dc7,
+ 0x0dc8, 0x0dc9, 0x0dca, 0x0dcb, 0x0dcc, 0x0dcd, 0x0dce, 0x0dcf,
+ 0x0dd0, 0x0dd1, 0x0dd2, 0x0dd3, 0x0dd4, 0x0dd5, 0x0dd6, 0x0dd7,
+ 0x0dd8, 0x0dd9, 0x0dd9, 0x0ddb, 0x0dd9, 0x0dd9, 0x0dd9, 0x0ddf,
+ 0x0de0, 0x0de1, 0x0de2, 0x0de3, 0x0de4, 0x0de5, 0x0de6, 0x0de7,
+ 0x0de8, 0x0de9, 0x0dea, 0x0deb, 0x0dec, 0x0ded, 0x0dee, 0x0def,
+ 0x0df0, 0x0df1, 0x0df2, 0x0df3, 0x0df4, 0x0df5, 0x0df6, 0x0df7,
+ 0x0df8, 0x0df9, 0x0dfa, 0x0dfb, 0x0dfc, 0x0dfd, 0x0dfe, 0x0dff,
+ };
+
+static const unsigned short gNormalizeTable0e00[] = {
+ /* U+0e00 */
+ 0x0e00, 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07,
+ 0x0e08, 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f,
+ 0x0e10, 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17,
+ 0x0e18, 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f,
+ 0x0e20, 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27,
+ 0x0e28, 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f,
+ 0x0e30, 0x0e31, 0x0e32, 0x0e4d, 0x0e34, 0x0e35, 0x0e36, 0x0e37,
+ 0x0e38, 0x0e39, 0x0e3a, 0x0e3b, 0x0e3c, 0x0e3d, 0x0e3e, 0x0e3f,
+ };
+
+static const unsigned short gNormalizeTable0e80[] = {
+ /* U+0e80 */
+ 0x0e80, 0x0e81, 0x0e82, 0x0e83, 0x0e84, 0x0e85, 0x0e86, 0x0e87,
+ 0x0e88, 0x0e89, 0x0e8a, 0x0e8b, 0x0e8c, 0x0e8d, 0x0e8e, 0x0e8f,
+ 0x0e90, 0x0e91, 0x0e92, 0x0e93, 0x0e94, 0x0e95, 0x0e96, 0x0e97,
+ 0x0e98, 0x0e99, 0x0e9a, 0x0e9b, 0x0e9c, 0x0e9d, 0x0e9e, 0x0e9f,
+ 0x0ea0, 0x0ea1, 0x0ea2, 0x0ea3, 0x0ea4, 0x0ea5, 0x0ea6, 0x0ea7,
+ 0x0ea8, 0x0ea9, 0x0eaa, 0x0eab, 0x0eac, 0x0ead, 0x0eae, 0x0eaf,
+ 0x0eb0, 0x0eb1, 0x0eb2, 0x0ecd, 0x0eb4, 0x0eb5, 0x0eb6, 0x0eb7,
+ 0x0eb8, 0x0eb9, 0x0eba, 0x0ebb, 0x0ebc, 0x0ebd, 0x0ebe, 0x0ebf,
+ };
+
+static const unsigned short gNormalizeTable0ec0[] = {
+ /* U+0ec0 */
+ 0x0ec0, 0x0ec1, 0x0ec2, 0x0ec3, 0x0ec4, 0x0ec5, 0x0ec6, 0x0ec7,
+ 0x0ec8, 0x0ec9, 0x0eca, 0x0ecb, 0x0ecc, 0x0ecd, 0x0ece, 0x0ecf,
+ 0x0ed0, 0x0ed1, 0x0ed2, 0x0ed3, 0x0ed4, 0x0ed5, 0x0ed6, 0x0ed7,
+ 0x0ed8, 0x0ed9, 0x0eda, 0x0edb, 0x0eab, 0x0eab, 0x0ede, 0x0edf,
+ 0x0ee0, 0x0ee1, 0x0ee2, 0x0ee3, 0x0ee4, 0x0ee5, 0x0ee6, 0x0ee7,
+ 0x0ee8, 0x0ee9, 0x0eea, 0x0eeb, 0x0eec, 0x0eed, 0x0eee, 0x0eef,
+ 0x0ef0, 0x0ef1, 0x0ef2, 0x0ef3, 0x0ef4, 0x0ef5, 0x0ef6, 0x0ef7,
+ 0x0ef8, 0x0ef9, 0x0efa, 0x0efb, 0x0efc, 0x0efd, 0x0efe, 0x0eff,
+ };
+
+static const unsigned short gNormalizeTable0f00[] = {
+ /* U+0f00 */
+ 0x0f00, 0x0f01, 0x0f02, 0x0f03, 0x0f04, 0x0f05, 0x0f06, 0x0f07,
+ 0x0f08, 0x0f09, 0x0f0a, 0x0f0b, 0x0f0b, 0x0f0d, 0x0f0e, 0x0f0f,
+ 0x0f10, 0x0f11, 0x0f12, 0x0f13, 0x0f14, 0x0f15, 0x0f16, 0x0f17,
+ 0x0f18, 0x0f19, 0x0f1a, 0x0f1b, 0x0f1c, 0x0f1d, 0x0f1e, 0x0f1f,
+ 0x0f20, 0x0f21, 0x0f22, 0x0f23, 0x0f24, 0x0f25, 0x0f26, 0x0f27,
+ 0x0f28, 0x0f29, 0x0f2a, 0x0f2b, 0x0f2c, 0x0f2d, 0x0f2e, 0x0f2f,
+ 0x0f30, 0x0f31, 0x0f32, 0x0f33, 0x0f34, 0x0f35, 0x0f36, 0x0f37,
+ 0x0f38, 0x0f39, 0x0f3a, 0x0f3b, 0x0f3c, 0x0f3d, 0x0f3e, 0x0f3f,
+ };
+
+static const unsigned short gNormalizeTable0f40[] = {
+ /* U+0f40 */
+ 0x0f40, 0x0f41, 0x0f42, 0x0f42, 0x0f44, 0x0f45, 0x0f46, 0x0f47,
+ 0x0f48, 0x0f49, 0x0f4a, 0x0f4b, 0x0f4c, 0x0f4c, 0x0f4e, 0x0f4f,
+ 0x0f50, 0x0f51, 0x0f51, 0x0f53, 0x0f54, 0x0f55, 0x0f56, 0x0f56,
+ 0x0f58, 0x0f59, 0x0f5a, 0x0f5b, 0x0f5b, 0x0f5d, 0x0f5e, 0x0f5f,
+ 0x0f60, 0x0f61, 0x0f62, 0x0f63, 0x0f64, 0x0f65, 0x0f66, 0x0f67,
+ 0x0f68, 0x0f40, 0x0f6a, 0x0f6b, 0x0f6c, 0x0f6d, 0x0f6e, 0x0f6f,
+ 0x0f70, 0x0f71, 0x0f72, 0x0f71, 0x0f74, 0x0f71, 0x0fb2, 0x0fb2,
+ 0x0fb3, 0x0fb3, 0x0f7a, 0x0f7b, 0x0f7c, 0x0f7d, 0x0f7e, 0x0f7f,
+ };
+
+static const unsigned short gNormalizeTable0f80[] = {
+ /* U+0f80 */
+ 0x0f80, 0x0f71, 0x0f82, 0x0f83, 0x0f84, 0x0f85, 0x0f86, 0x0f87,
+ 0x0f88, 0x0f89, 0x0f8a, 0x0f8b, 0x0f8c, 0x0f8d, 0x0f8e, 0x0f8f,
+ 0x0f90, 0x0f91, 0x0f92, 0x0f92, 0x0f94, 0x0f95, 0x0f96, 0x0f97,
+ 0x0f98, 0x0f99, 0x0f9a, 0x0f9b, 0x0f9c, 0x0f9c, 0x0f9e, 0x0f9f,
+ 0x0fa0, 0x0fa1, 0x0fa1, 0x0fa3, 0x0fa4, 0x0fa5, 0x0fa6, 0x0fa6,
+ 0x0fa8, 0x0fa9, 0x0faa, 0x0fab, 0x0fab, 0x0fad, 0x0fae, 0x0faf,
+ 0x0fb0, 0x0fb1, 0x0fb2, 0x0fb3, 0x0fb4, 0x0fb5, 0x0fb6, 0x0fb7,
+ 0x0fb8, 0x0f90, 0x0fba, 0x0fbb, 0x0fbc, 0x0fbd, 0x0fbe, 0x0fbf,
+ };
+
+static const unsigned short gNormalizeTable1000[] = {
+ /* U+1000 */
+ 0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, 0x1007,
+ 0x1008, 0x1009, 0x100a, 0x100b, 0x100c, 0x100d, 0x100e, 0x100f,
+ 0x1010, 0x1011, 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017,
+ 0x1018, 0x1019, 0x101a, 0x101b, 0x101c, 0x101d, 0x101e, 0x101f,
+ 0x1020, 0x1021, 0x1022, 0x1023, 0x1024, 0x1025, 0x1025, 0x1027,
+ 0x1028, 0x1029, 0x102a, 0x102b, 0x102c, 0x102d, 0x102e, 0x102f,
+ 0x1030, 0x1031, 0x1032, 0x1033, 0x1034, 0x1035, 0x1036, 0x1037,
+ 0x1038, 0x1039, 0x103a, 0x103b, 0x103c, 0x103d, 0x103e, 0x103f,
+ };
+
+static const unsigned short gNormalizeTable1080[] = {
+ /* U+1080 */
+ 0x1080, 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087,
+ 0x1088, 0x1089, 0x108a, 0x108b, 0x108c, 0x108d, 0x108e, 0x108f,
+ 0x1090, 0x1091, 0x1092, 0x1093, 0x1094, 0x1095, 0x1096, 0x1097,
+ 0x1098, 0x1099, 0x109a, 0x109b, 0x109c, 0x109d, 0x109e, 0x109f,
+ 0x2d00, 0x2d01, 0x2d02, 0x2d03, 0x2d04, 0x2d05, 0x2d06, 0x2d07,
+ 0x2d08, 0x2d09, 0x2d0a, 0x2d0b, 0x2d0c, 0x2d0d, 0x2d0e, 0x2d0f,
+ 0x2d10, 0x2d11, 0x2d12, 0x2d13, 0x2d14, 0x2d15, 0x2d16, 0x2d17,
+ 0x2d18, 0x2d19, 0x2d1a, 0x2d1b, 0x2d1c, 0x2d1d, 0x2d1e, 0x2d1f,
+ };
+
+static const unsigned short gNormalizeTable10c0[] = {
+ /* U+10c0 */
+ 0x2d20, 0x2d21, 0x2d22, 0x2d23, 0x2d24, 0x2d25, 0x10c6, 0x10c7,
+ 0x10c8, 0x10c9, 0x10ca, 0x10cb, 0x10cc, 0x10cd, 0x10ce, 0x10cf,
+ 0x10d0, 0x10d1, 0x10d2, 0x10d3, 0x10d4, 0x10d5, 0x10d6, 0x10d7,
+ 0x10d8, 0x10d9, 0x10da, 0x10db, 0x10dc, 0x10dd, 0x10de, 0x10df,
+ 0x10e0, 0x10e1, 0x10e2, 0x10e3, 0x10e4, 0x10e5, 0x10e6, 0x10e7,
+ 0x10e8, 0x10e9, 0x10ea, 0x10eb, 0x10ec, 0x10ed, 0x10ee, 0x10ef,
+ 0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x10f7,
+ 0x10f8, 0x10f9, 0x10fa, 0x10fb, 0x10dc, 0x10fd, 0x10fe, 0x10ff,
+ };
+
+static const unsigned short gNormalizeTable1140[] = {
+ /* U+1140 */
+ 0x1140, 0x1141, 0x1142, 0x1143, 0x1144, 0x1145, 0x1146, 0x1147,
+ 0x1148, 0x1149, 0x114a, 0x114b, 0x114c, 0x114d, 0x114e, 0x114f,
+ 0x1150, 0x1151, 0x1152, 0x1153, 0x1154, 0x1155, 0x1156, 0x1157,
+ 0x1158, 0x1159, 0x115a, 0x115b, 0x115c, 0x115d, 0x115e, 0x0020,
+ 0x0020, 0x1161, 0x1162, 0x1163, 0x1164, 0x1165, 0x1166, 0x1167,
+ 0x1168, 0x1169, 0x116a, 0x116b, 0x116c, 0x116d, 0x116e, 0x116f,
+ 0x1170, 0x1171, 0x1172, 0x1173, 0x1174, 0x1175, 0x1176, 0x1177,
+ 0x1178, 0x1179, 0x117a, 0x117b, 0x117c, 0x117d, 0x117e, 0x117f,
+ };
+
+static const unsigned short gNormalizeTable1780[] = {
+ /* U+1780 */
+ 0x1780, 0x1781, 0x1782, 0x1783, 0x1784, 0x1785, 0x1786, 0x1787,
+ 0x1788, 0x1789, 0x178a, 0x178b, 0x178c, 0x178d, 0x178e, 0x178f,
+ 0x1790, 0x1791, 0x1792, 0x1793, 0x1794, 0x1795, 0x1796, 0x1797,
+ 0x1798, 0x1799, 0x179a, 0x179b, 0x179c, 0x179d, 0x179e, 0x179f,
+ 0x17a0, 0x17a1, 0x17a2, 0x17a3, 0x17a4, 0x17a5, 0x17a6, 0x17a7,
+ 0x17a8, 0x17a9, 0x17aa, 0x17ab, 0x17ac, 0x17ad, 0x17ae, 0x17af,
+ 0x17b0, 0x17b1, 0x17b2, 0x17b3, 0x0020, 0x0020, 0x17b6, 0x17b7,
+ 0x17b8, 0x17b9, 0x17ba, 0x17bb, 0x17bc, 0x17bd, 0x17be, 0x17bf,
+ };
+
+static const unsigned short gNormalizeTable1800[] = {
+ /* U+1800 */
+ 0x1800, 0x1801, 0x1802, 0x1803, 0x1804, 0x1805, 0x1806, 0x1807,
+ 0x1808, 0x1809, 0x180a, 0x0020, 0x0020, 0x0020, 0x180e, 0x180f,
+ 0x1810, 0x1811, 0x1812, 0x1813, 0x1814, 0x1815, 0x1816, 0x1817,
+ 0x1818, 0x1819, 0x181a, 0x181b, 0x181c, 0x181d, 0x181e, 0x181f,
+ 0x1820, 0x1821, 0x1822, 0x1823, 0x1824, 0x1825, 0x1826, 0x1827,
+ 0x1828, 0x1829, 0x182a, 0x182b, 0x182c, 0x182d, 0x182e, 0x182f,
+ 0x1830, 0x1831, 0x1832, 0x1833, 0x1834, 0x1835, 0x1836, 0x1837,
+ 0x1838, 0x1839, 0x183a, 0x183b, 0x183c, 0x183d, 0x183e, 0x183f,
+ };
+
+static const unsigned short gNormalizeTable1b00[] = {
+ /* U+1b00 */
+ 0x1b00, 0x1b01, 0x1b02, 0x1b03, 0x1b04, 0x1b05, 0x1b05, 0x1b07,
+ 0x1b07, 0x1b09, 0x1b09, 0x1b0b, 0x1b0b, 0x1b0d, 0x1b0d, 0x1b0f,
+ 0x1b10, 0x1b11, 0x1b11, 0x1b13, 0x1b14, 0x1b15, 0x1b16, 0x1b17,
+ 0x1b18, 0x1b19, 0x1b1a, 0x1b1b, 0x1b1c, 0x1b1d, 0x1b1e, 0x1b1f,
+ 0x1b20, 0x1b21, 0x1b22, 0x1b23, 0x1b24, 0x1b25, 0x1b26, 0x1b27,
+ 0x1b28, 0x1b29, 0x1b2a, 0x1b2b, 0x1b2c, 0x1b2d, 0x1b2e, 0x1b2f,
+ 0x1b30, 0x1b31, 0x1b32, 0x1b33, 0x1b34, 0x1b35, 0x1b36, 0x1b37,
+ 0x1b38, 0x1b39, 0x1b3a, 0x1b3a, 0x1b3c, 0x1b3c, 0x1b3e, 0x1b3f,
+ };
+
+static const unsigned short gNormalizeTable1b40[] = {
+ /* U+1b40 */
+ 0x1b3e, 0x1b3f, 0x1b42, 0x1b42, 0x1b44, 0x1b45, 0x1b46, 0x1b47,
+ 0x1b48, 0x1b49, 0x1b4a, 0x1b4b, 0x1b4c, 0x1b4d, 0x1b4e, 0x1b4f,
+ 0x1b50, 0x1b51, 0x1b52, 0x1b53, 0x1b54, 0x1b55, 0x1b56, 0x1b57,
+ 0x1b58, 0x1b59, 0x1b5a, 0x1b5b, 0x1b5c, 0x1b5d, 0x1b5e, 0x1b5f,
+ 0x1b60, 0x1b61, 0x1b62, 0x1b63, 0x1b64, 0x1b65, 0x1b66, 0x1b67,
+ 0x1b68, 0x1b69, 0x1b6a, 0x1b6b, 0x1b6c, 0x1b6d, 0x1b6e, 0x1b6f,
+ 0x1b70, 0x1b71, 0x1b72, 0x1b73, 0x1b74, 0x1b75, 0x1b76, 0x1b77,
+ 0x1b78, 0x1b79, 0x1b7a, 0x1b7b, 0x1b7c, 0x1b7d, 0x1b7e, 0x1b7f,
+ };
+
+static const unsigned short gNormalizeTable1d00[] = {
+ /* U+1d00 */
+ 0x1d00, 0x1d01, 0x1d02, 0x1d03, 0x1d04, 0x1d05, 0x1d06, 0x1d07,
+ 0x1d08, 0x1d09, 0x1d0a, 0x1d0b, 0x1d0c, 0x1d0d, 0x1d0e, 0x1d0f,
+ 0x1d10, 0x1d11, 0x1d12, 0x1d13, 0x1d14, 0x1d15, 0x1d16, 0x1d17,
+ 0x1d18, 0x1d19, 0x1d1a, 0x1d1b, 0x1d1c, 0x1d1d, 0x1d1e, 0x1d1f,
+ 0x1d20, 0x1d21, 0x1d22, 0x1d23, 0x1d24, 0x1d25, 0x1d26, 0x1d27,
+ 0x1d28, 0x1d29, 0x1d2a, 0x1d2b, 0x0061, 0x00e6, 0x0062, 0x1d2f,
+ 0x0064, 0x0065, 0x01dd, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b,
+ 0x006c, 0x006d, 0x006e, 0x1d3b, 0x006f, 0x0223, 0x0070, 0x0072,
+ };
+
+static const unsigned short gNormalizeTable1d40[] = {
+ /* U+1d40 */
+ 0x0074, 0x0075, 0x0077, 0x0061, 0x0250, 0x0251, 0x1d02, 0x0062,
+ 0x0064, 0x0065, 0x0259, 0x025b, 0x025c, 0x0067, 0x1d4e, 0x006b,
+ 0x006d, 0x014b, 0x006f, 0x0254, 0x1d16, 0x1d17, 0x0070, 0x0074,
+ 0x0075, 0x1d1d, 0x026f, 0x0076, 0x1d25, 0x03b2, 0x03b3, 0x03b4,
+ 0x03c6, 0x03c7, 0x0069, 0x0072, 0x0075, 0x0076, 0x03b2, 0x03b3,
+ 0x03c1, 0x03c6, 0x03c7, 0x1d6b, 0x1d6c, 0x1d6d, 0x1d6e, 0x1d6f,
+ 0x1d70, 0x1d71, 0x1d72, 0x1d73, 0x1d74, 0x1d75, 0x1d76, 0x1d77,
+ 0x043d, 0x1d79, 0x1d7a, 0x1d7b, 0x1d7c, 0x1d7d, 0x1d7e, 0x1d7f,
+ };
+
+static const unsigned short gNormalizeTable1d80[] = {
+ /* U+1d80 */
+ 0x1d80, 0x1d81, 0x1d82, 0x1d83, 0x1d84, 0x1d85, 0x1d86, 0x1d87,
+ 0x1d88, 0x1d89, 0x1d8a, 0x1d8b, 0x1d8c, 0x1d8d, 0x1d8e, 0x1d8f,
+ 0x1d90, 0x1d91, 0x1d92, 0x1d93, 0x1d94, 0x1d95, 0x1d96, 0x1d97,
+ 0x1d98, 0x1d99, 0x1d9a, 0x0252, 0x0063, 0x0255, 0x00f0, 0x025c,
+ 0x0066, 0x025f, 0x0261, 0x0265, 0x0268, 0x0269, 0x026a, 0x1d7b,
+ 0x029d, 0x026d, 0x1d85, 0x029f, 0x0271, 0x0270, 0x0272, 0x0273,
+ 0x0274, 0x0275, 0x0278, 0x0282, 0x0283, 0x01ab, 0x0289, 0x028a,
+ 0x1d1c, 0x028b, 0x028c, 0x007a, 0x0290, 0x0291, 0x0292, 0x03b8,
+ };
+
+static const unsigned short gNormalizeTable1e00[] = {
+ /* U+1e00 */
+ 0x0061, 0x0061, 0x0062, 0x0062, 0x0062, 0x0062, 0x0062, 0x0062,
+ 0x0063, 0x0063, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064,
+ 0x0064, 0x0064, 0x0064, 0x0064, 0x0065, 0x0065, 0x0065, 0x0065,
+ 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0066, 0x0066,
+ 0x0067, 0x0067, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068,
+ 0x0068, 0x0068, 0x0068, 0x0068, 0x0069, 0x0069, 0x0069, 0x0069,
+ 0x006b, 0x006b, 0x006b, 0x006b, 0x006b, 0x006b, 0x006c, 0x006c,
+ 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006d, 0x006d,
+ };
+
+static const unsigned short gNormalizeTable1e40[] = {
+ /* U+1e40 */
+ 0x006d, 0x006d, 0x006d, 0x006d, 0x006e, 0x006e, 0x006e, 0x006e,
+ 0x006e, 0x006e, 0x006e, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f,
+ 0x006f, 0x006f, 0x006f, 0x006f, 0x0070, 0x0070, 0x0070, 0x0070,
+ 0x0072, 0x0072, 0x0072, 0x0072, 0x0072, 0x0072, 0x0072, 0x0072,
+ 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073,
+ 0x0073, 0x0073, 0x0074, 0x0074, 0x0074, 0x0074, 0x0074, 0x0074,
+ 0x0074, 0x0074, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075,
+ 0x0075, 0x0075, 0x0075, 0x0075, 0x0076, 0x0076, 0x0076, 0x0076,
+ };
+
+static const unsigned short gNormalizeTable1e80[] = {
+ /* U+1e80 */
+ 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077,
+ 0x0077, 0x0077, 0x0078, 0x0078, 0x0078, 0x0078, 0x0079, 0x0079,
+ 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x0068, 0x0074,
+ 0x0077, 0x0079, 0x0061, 0x0073, 0x1e9c, 0x1e9d, 0x0073, 0x1e9f,
+ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061,
+ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061,
+ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061,
+ 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065,
+ };
+
+static const unsigned short gNormalizeTable1ec0[] = {
+ /* U+1ec0 */
+ 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065,
+ 0x0069, 0x0069, 0x0069, 0x0069, 0x006f, 0x006f, 0x006f, 0x006f,
+ 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f,
+ 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f,
+ 0x006f, 0x006f, 0x006f, 0x006f, 0x0075, 0x0075, 0x0075, 0x0075,
+ 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075,
+ 0x0075, 0x0075, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079,
+ 0x0079, 0x0079, 0x1efb, 0x1efb, 0x1efd, 0x1efd, 0x1eff, 0x1eff,
+ };
+
+static const unsigned short gNormalizeTable1f00[] = {
+ /* U+1f00 */
+ 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1,
+ 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1,
+ 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x1f16, 0x1f17,
+ 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x1f1e, 0x1f1f,
+ 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7,
+ 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7,
+ 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9,
+ 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9,
+ };
+
+static const unsigned short gNormalizeTable1f40[] = {
+ /* U+1f40 */
+ 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x1f46, 0x1f47,
+ 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x1f4e, 0x1f4f,
+ 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c5,
+ 0x1f58, 0x03c5, 0x1f5a, 0x03c5, 0x1f5c, 0x03c5, 0x1f5e, 0x03c5,
+ 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9,
+ 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9,
+ 0x03b1, 0x03b1, 0x03b5, 0x03b5, 0x03b7, 0x03b7, 0x03b9, 0x03b9,
+ 0x03bf, 0x03bf, 0x03c5, 0x03c5, 0x03c9, 0x03c9, 0x1f7e, 0x1f7f,
+ };
+
+static const unsigned short gNormalizeTable1f80[] = {
+ /* U+1f80 */
+ 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1,
+ 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1,
+ 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7,
+ 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7,
+ 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9,
+ 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9,
+ 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x1fb5, 0x03b1, 0x03b1,
+ 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x0020, 0x03b9, 0x0020,
+ };
+
+static const unsigned short gNormalizeTable1fc0[] = {
+ /* U+1fc0 */
+ 0x0020, 0x0020, 0x03b7, 0x03b7, 0x03b7, 0x1fc5, 0x03b7, 0x03b7,
+ 0x03b5, 0x03b5, 0x03b7, 0x03b7, 0x03b7, 0x0020, 0x0020, 0x0020,
+ 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x1fd4, 0x1fd5, 0x03b9, 0x03b9,
+ 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x1fdc, 0x0020, 0x0020, 0x0020,
+ 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c1, 0x03c1, 0x03c5, 0x03c5,
+ 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c1, 0x0020, 0x0020, 0x0060,
+ 0x1ff0, 0x1ff1, 0x03c9, 0x03c9, 0x03c9, 0x1ff5, 0x03c9, 0x03c9,
+ 0x03bf, 0x03bf, 0x03c9, 0x03c9, 0x03c9, 0x0020, 0x0020, 0x1fff,
+ };
+
+static const unsigned short gNormalizeTable2000[] = {
+ /* U+2000 */
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x2010, 0x2010, 0x2012, 0x2013, 0x2014, 0x2015, 0x2016, 0x0020,
+ 0x2018, 0x2019, 0x201a, 0x201b, 0x201c, 0x201d, 0x201e, 0x201f,
+ 0x2020, 0x2021, 0x2022, 0x2023, 0x002e, 0x002e, 0x002e, 0x2027,
+ 0x2028, 0x2029, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x2030, 0x2031, 0x2032, 0x2032, 0x2032, 0x2035, 0x2035, 0x2035,
+ 0x2038, 0x2039, 0x203a, 0x203b, 0x0021, 0x203d, 0x0020, 0x203f,
+ };
+
+static const unsigned short gNormalizeTable2040[] = {
+ /* U+2040 */
+ 0x2040, 0x2041, 0x2042, 0x2043, 0x2044, 0x2045, 0x2046, 0x003f,
+ 0x003f, 0x0021, 0x204a, 0x204b, 0x204c, 0x204d, 0x204e, 0x204f,
+ 0x2050, 0x2051, 0x2052, 0x2053, 0x2054, 0x2055, 0x2056, 0x2032,
+ 0x2058, 0x2059, 0x205a, 0x205b, 0x205c, 0x205d, 0x205e, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0030, 0x0069, 0x2072, 0x2073, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x002b, 0x2212, 0x003d, 0x0028, 0x0029, 0x006e,
+ };
+
+static const unsigned short gNormalizeTable2080[] = {
+ /* U+2080 */
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x002b, 0x2212, 0x003d, 0x0028, 0x0029, 0x208f,
+ 0x0061, 0x0065, 0x006f, 0x0078, 0x0259, 0x2095, 0x2096, 0x2097,
+ 0x2098, 0x2099, 0x209a, 0x209b, 0x209c, 0x209d, 0x209e, 0x209f,
+ 0x20a0, 0x20a1, 0x20a2, 0x20a3, 0x20a4, 0x20a5, 0x20a6, 0x20a7,
+ 0x0072, 0x20a9, 0x20aa, 0x20ab, 0x20ac, 0x20ad, 0x20ae, 0x20af,
+ 0x20b0, 0x20b1, 0x20b2, 0x20b3, 0x20b4, 0x20b5, 0x20b6, 0x20b7,
+ 0x20b8, 0x20b9, 0x20ba, 0x20bb, 0x20bc, 0x20bd, 0x20be, 0x20bf,
+ };
+
+static const unsigned short gNormalizeTable2100[] = {
+ /* U+2100 */
+ 0x0061, 0x0061, 0x0063, 0x00b0, 0x2104, 0x0063, 0x0063, 0x025b,
+ 0x2108, 0x00b0, 0x0067, 0x0068, 0x0068, 0x0068, 0x0068, 0x0127,
+ 0x0069, 0x0069, 0x006c, 0x006c, 0x2114, 0x006e, 0x006e, 0x2117,
+ 0x2118, 0x0070, 0x0071, 0x0072, 0x0072, 0x0072, 0x211e, 0x211f,
+ 0x0073, 0x0074, 0x0074, 0x2123, 0x007a, 0x2125, 0x03c9, 0x2127,
+ 0x007a, 0x2129, 0x006b, 0x0061, 0x0062, 0x0063, 0x212e, 0x0065,
+ 0x0065, 0x0066, 0x214e, 0x006d, 0x006f, 0x05d0, 0x05d1, 0x05d2,
+ 0x05d3, 0x0069, 0x213a, 0x0066, 0x03c0, 0x03b3, 0x03b3, 0x03c0,
+ };
+
+static const unsigned short gNormalizeTable2140[] = {
+ /* U+2140 */
+ 0x2211, 0x2141, 0x2142, 0x2143, 0x2144, 0x0064, 0x0064, 0x0065,
+ 0x0069, 0x006a, 0x214a, 0x214b, 0x214c, 0x214d, 0x214e, 0x214f,
+ 0x0031, 0x0031, 0x0031, 0x0031, 0x0032, 0x0031, 0x0032, 0x0033,
+ 0x0034, 0x0031, 0x0035, 0x0031, 0x0033, 0x0035, 0x0037, 0x0031,
+ 0x0069, 0x0069, 0x0069, 0x0069, 0x0076, 0x0076, 0x0076, 0x0076,
+ 0x0069, 0x0078, 0x0078, 0x0078, 0x006c, 0x0063, 0x0064, 0x006d,
+ 0x0069, 0x0069, 0x0069, 0x0069, 0x0076, 0x0076, 0x0076, 0x0076,
+ 0x0069, 0x0078, 0x0078, 0x0078, 0x006c, 0x0063, 0x0064, 0x006d,
+ };
+
+static const unsigned short gNormalizeTable2180[] = {
+ /* U+2180 */
+ 0x2180, 0x2181, 0x2182, 0x2184, 0x2184, 0x2185, 0x2186, 0x2187,
+ 0x2188, 0x0030, 0x218a, 0x218b, 0x218c, 0x218d, 0x218e, 0x218f,
+ 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x2196, 0x2197,
+ 0x2198, 0x2199, 0x2190, 0x2192, 0x219c, 0x219d, 0x219e, 0x219f,
+ 0x21a0, 0x21a1, 0x21a2, 0x21a3, 0x21a4, 0x21a5, 0x21a6, 0x21a7,
+ 0x21a8, 0x21a9, 0x21aa, 0x21ab, 0x21ac, 0x21ad, 0x2194, 0x21af,
+ 0x21b0, 0x21b1, 0x21b2, 0x21b3, 0x21b4, 0x21b5, 0x21b6, 0x21b7,
+ 0x21b8, 0x21b9, 0x21ba, 0x21bb, 0x21bc, 0x21bd, 0x21be, 0x21bf,
+ };
+
+static const unsigned short gNormalizeTable21c0[] = {
+ /* U+21c0 */
+ 0x21c0, 0x21c1, 0x21c2, 0x21c3, 0x21c4, 0x21c5, 0x21c6, 0x21c7,
+ 0x21c8, 0x21c9, 0x21ca, 0x21cb, 0x21cc, 0x21d0, 0x21d4, 0x21d2,
+ 0x21d0, 0x21d1, 0x21d2, 0x21d3, 0x21d4, 0x21d5, 0x21d6, 0x21d7,
+ 0x21d8, 0x21d9, 0x21da, 0x21db, 0x21dc, 0x21dd, 0x21de, 0x21df,
+ 0x21e0, 0x21e1, 0x21e2, 0x21e3, 0x21e4, 0x21e5, 0x21e6, 0x21e7,
+ 0x21e8, 0x21e9, 0x21ea, 0x21eb, 0x21ec, 0x21ed, 0x21ee, 0x21ef,
+ 0x21f0, 0x21f1, 0x21f2, 0x21f3, 0x21f4, 0x21f5, 0x21f6, 0x21f7,
+ 0x21f8, 0x21f9, 0x21fa, 0x21fb, 0x21fc, 0x21fd, 0x21fe, 0x21ff,
+ };
+
+static const unsigned short gNormalizeTable2200[] = {
+ /* U+2200 */
+ 0x2200, 0x2201, 0x2202, 0x2203, 0x2203, 0x2205, 0x2206, 0x2207,
+ 0x2208, 0x2208, 0x220a, 0x220b, 0x220b, 0x220d, 0x220e, 0x220f,
+ 0x2210, 0x2211, 0x2212, 0x2213, 0x2214, 0x2215, 0x2216, 0x2217,
+ 0x2218, 0x2219, 0x221a, 0x221b, 0x221c, 0x221d, 0x221e, 0x221f,
+ 0x2220, 0x2221, 0x2222, 0x2223, 0x2223, 0x2225, 0x2225, 0x2227,
+ 0x2228, 0x2229, 0x222a, 0x222b, 0x222b, 0x222b, 0x222e, 0x222e,
+ 0x222e, 0x2231, 0x2232, 0x2233, 0x2234, 0x2235, 0x2236, 0x2237,
+ 0x2238, 0x2239, 0x223a, 0x223b, 0x223c, 0x223d, 0x223e, 0x223f,
+ };
+
+static const unsigned short gNormalizeTable2240[] = {
+ /* U+2240 */
+ 0x2240, 0x223c, 0x2242, 0x2243, 0x2243, 0x2245, 0x2246, 0x2245,
+ 0x2248, 0x2248, 0x224a, 0x224b, 0x224c, 0x224d, 0x224e, 0x224f,
+ 0x2250, 0x2251, 0x2252, 0x2253, 0x2254, 0x2255, 0x2256, 0x2257,
+ 0x2258, 0x2259, 0x225a, 0x225b, 0x225c, 0x225d, 0x225e, 0x225f,
+ 0x003d, 0x2261, 0x2261, 0x2263, 0x2264, 0x2265, 0x2266, 0x2267,
+ 0x2268, 0x2269, 0x226a, 0x226b, 0x226c, 0x224d, 0x003c, 0x003e,
+ 0x2264, 0x2265, 0x2272, 0x2273, 0x2272, 0x2273, 0x2276, 0x2277,
+ 0x2276, 0x2277, 0x227a, 0x227b, 0x227c, 0x227d, 0x227e, 0x227f,
+ };
+
+static const unsigned short gNormalizeTable2280[] = {
+ /* U+2280 */
+ 0x227a, 0x227b, 0x2282, 0x2283, 0x2282, 0x2283, 0x2286, 0x2287,
+ 0x2286, 0x2287, 0x228a, 0x228b, 0x228c, 0x228d, 0x228e, 0x228f,
+ 0x2290, 0x2291, 0x2292, 0x2293, 0x2294, 0x2295, 0x2296, 0x2297,
+ 0x2298, 0x2299, 0x229a, 0x229b, 0x229c, 0x229d, 0x229e, 0x229f,
+ 0x22a0, 0x22a1, 0x22a2, 0x22a3, 0x22a4, 0x22a5, 0x22a6, 0x22a7,
+ 0x22a8, 0x22a9, 0x22aa, 0x22ab, 0x22a2, 0x22a8, 0x22a9, 0x22ab,
+ 0x22b0, 0x22b1, 0x22b2, 0x22b3, 0x22b4, 0x22b5, 0x22b6, 0x22b7,
+ 0x22b8, 0x22b9, 0x22ba, 0x22bb, 0x22bc, 0x22bd, 0x22be, 0x22bf,
+ };
+
+static const unsigned short gNormalizeTable22c0[] = {
+ /* U+22c0 */
+ 0x22c0, 0x22c1, 0x22c2, 0x22c3, 0x22c4, 0x22c5, 0x22c6, 0x22c7,
+ 0x22c8, 0x22c9, 0x22ca, 0x22cb, 0x22cc, 0x22cd, 0x22ce, 0x22cf,
+ 0x22d0, 0x22d1, 0x22d2, 0x22d3, 0x22d4, 0x22d5, 0x22d6, 0x22d7,
+ 0x22d8, 0x22d9, 0x22da, 0x22db, 0x22dc, 0x22dd, 0x22de, 0x22df,
+ 0x227c, 0x227d, 0x2291, 0x2292, 0x22e4, 0x22e5, 0x22e6, 0x22e7,
+ 0x22e8, 0x22e9, 0x22b2, 0x22b3, 0x22b4, 0x22b5, 0x22ee, 0x22ef,
+ 0x22f0, 0x22f1, 0x22f2, 0x22f3, 0x22f4, 0x22f5, 0x22f6, 0x22f7,
+ 0x22f8, 0x22f9, 0x22fa, 0x22fb, 0x22fc, 0x22fd, 0x22fe, 0x22ff,
+ };
+
+static const unsigned short gNormalizeTable2300[] = {
+ /* U+2300 */
+ 0x2300, 0x2301, 0x2302, 0x2303, 0x2304, 0x2305, 0x2306, 0x2307,
+ 0x2308, 0x2309, 0x230a, 0x230b, 0x230c, 0x230d, 0x230e, 0x230f,
+ 0x2310, 0x2311, 0x2312, 0x2313, 0x2314, 0x2315, 0x2316, 0x2317,
+ 0x2318, 0x2319, 0x231a, 0x231b, 0x231c, 0x231d, 0x231e, 0x231f,
+ 0x2320, 0x2321, 0x2322, 0x2323, 0x2324, 0x2325, 0x2326, 0x2327,
+ 0x2328, 0x3008, 0x3009, 0x232b, 0x232c, 0x232d, 0x232e, 0x232f,
+ 0x2330, 0x2331, 0x2332, 0x2333, 0x2334, 0x2335, 0x2336, 0x2337,
+ 0x2338, 0x2339, 0x233a, 0x233b, 0x233c, 0x233d, 0x233e, 0x233f,
+ };
+
+static const unsigned short gNormalizeTable2440[] = {
+ /* U+2440 */
+ 0x2440, 0x2441, 0x2442, 0x2443, 0x2444, 0x2445, 0x2446, 0x2447,
+ 0x2448, 0x2449, 0x244a, 0x244b, 0x244c, 0x244d, 0x244e, 0x244f,
+ 0x2450, 0x2451, 0x2452, 0x2453, 0x2454, 0x2455, 0x2456, 0x2457,
+ 0x2458, 0x2459, 0x245a, 0x245b, 0x245c, 0x245d, 0x245e, 0x245f,
+ 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038,
+ 0x0039, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031,
+ 0x0031, 0x0031, 0x0031, 0x0032, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ };
+
+static const unsigned short gNormalizeTable2480[] = {
+ /* U+2480 */
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038,
+ 0x0039, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031,
+ 0x0031, 0x0031, 0x0031, 0x0032, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0061, 0x0062,
+ 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a,
+ };
+
+static const unsigned short gNormalizeTable24c0[] = {
+ /* U+24c0 */
+ 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072,
+ 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a,
+ 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068,
+ 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070,
+ 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
+ 0x0079, 0x007a, 0x0030, 0x24eb, 0x24ec, 0x24ed, 0x24ee, 0x24ef,
+ 0x24f0, 0x24f1, 0x24f2, 0x24f3, 0x24f4, 0x24f5, 0x24f6, 0x24f7,
+ 0x24f8, 0x24f9, 0x24fa, 0x24fb, 0x24fc, 0x24fd, 0x24fe, 0x24ff,
+ };
+
+static const unsigned short gNormalizeTable2a00[] = {
+ /* U+2a00 */
+ 0x2a00, 0x2a01, 0x2a02, 0x2a03, 0x2a04, 0x2a05, 0x2a06, 0x2a07,
+ 0x2a08, 0x2a09, 0x2a0a, 0x2a0b, 0x222b, 0x2a0d, 0x2a0e, 0x2a0f,
+ 0x2a10, 0x2a11, 0x2a12, 0x2a13, 0x2a14, 0x2a15, 0x2a16, 0x2a17,
+ 0x2a18, 0x2a19, 0x2a1a, 0x2a1b, 0x2a1c, 0x2a1d, 0x2a1e, 0x2a1f,
+ 0x2a20, 0x2a21, 0x2a22, 0x2a23, 0x2a24, 0x2a25, 0x2a26, 0x2a27,
+ 0x2a28, 0x2a29, 0x2a2a, 0x2a2b, 0x2a2c, 0x2a2d, 0x2a2e, 0x2a2f,
+ 0x2a30, 0x2a31, 0x2a32, 0x2a33, 0x2a34, 0x2a35, 0x2a36, 0x2a37,
+ 0x2a38, 0x2a39, 0x2a3a, 0x2a3b, 0x2a3c, 0x2a3d, 0x2a3e, 0x2a3f,
+ };
+
+static const unsigned short gNormalizeTable2a40[] = {
+ /* U+2a40 */
+ 0x2a40, 0x2a41, 0x2a42, 0x2a43, 0x2a44, 0x2a45, 0x2a46, 0x2a47,
+ 0x2a48, 0x2a49, 0x2a4a, 0x2a4b, 0x2a4c, 0x2a4d, 0x2a4e, 0x2a4f,
+ 0x2a50, 0x2a51, 0x2a52, 0x2a53, 0x2a54, 0x2a55, 0x2a56, 0x2a57,
+ 0x2a58, 0x2a59, 0x2a5a, 0x2a5b, 0x2a5c, 0x2a5d, 0x2a5e, 0x2a5f,
+ 0x2a60, 0x2a61, 0x2a62, 0x2a63, 0x2a64, 0x2a65, 0x2a66, 0x2a67,
+ 0x2a68, 0x2a69, 0x2a6a, 0x2a6b, 0x2a6c, 0x2a6d, 0x2a6e, 0x2a6f,
+ 0x2a70, 0x2a71, 0x2a72, 0x2a73, 0x003a, 0x003d, 0x003d, 0x2a77,
+ 0x2a78, 0x2a79, 0x2a7a, 0x2a7b, 0x2a7c, 0x2a7d, 0x2a7e, 0x2a7f,
+ };
+
+static const unsigned short gNormalizeTable2ac0[] = {
+ /* U+2ac0 */
+ 0x2ac0, 0x2ac1, 0x2ac2, 0x2ac3, 0x2ac4, 0x2ac5, 0x2ac6, 0x2ac7,
+ 0x2ac8, 0x2ac9, 0x2aca, 0x2acb, 0x2acc, 0x2acd, 0x2ace, 0x2acf,
+ 0x2ad0, 0x2ad1, 0x2ad2, 0x2ad3, 0x2ad4, 0x2ad5, 0x2ad6, 0x2ad7,
+ 0x2ad8, 0x2ad9, 0x2ada, 0x2adb, 0x2add, 0x2add, 0x2ade, 0x2adf,
+ 0x2ae0, 0x2ae1, 0x2ae2, 0x2ae3, 0x2ae4, 0x2ae5, 0x2ae6, 0x2ae7,
+ 0x2ae8, 0x2ae9, 0x2aea, 0x2aeb, 0x2aec, 0x2aed, 0x2aee, 0x2aef,
+ 0x2af0, 0x2af1, 0x2af2, 0x2af3, 0x2af4, 0x2af5, 0x2af6, 0x2af7,
+ 0x2af8, 0x2af9, 0x2afa, 0x2afb, 0x2afc, 0x2afd, 0x2afe, 0x2aff,
+ };
+
+static const unsigned short gNormalizeTable2c00[] = {
+ /* U+2c00 */
+ 0x2c30, 0x2c31, 0x2c32, 0x2c33, 0x2c34, 0x2c35, 0x2c36, 0x2c37,
+ 0x2c38, 0x2c39, 0x2c3a, 0x2c3b, 0x2c3c, 0x2c3d, 0x2c3e, 0x2c3f,
+ 0x2c40, 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47,
+ 0x2c48, 0x2c49, 0x2c4a, 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f,
+ 0x2c50, 0x2c51, 0x2c52, 0x2c53, 0x2c54, 0x2c55, 0x2c56, 0x2c57,
+ 0x2c58, 0x2c59, 0x2c5a, 0x2c5b, 0x2c5c, 0x2c5d, 0x2c5e, 0x2c2f,
+ 0x2c30, 0x2c31, 0x2c32, 0x2c33, 0x2c34, 0x2c35, 0x2c36, 0x2c37,
+ 0x2c38, 0x2c39, 0x2c3a, 0x2c3b, 0x2c3c, 0x2c3d, 0x2c3e, 0x2c3f,
+ };
+
+static const unsigned short gNormalizeTable2c40[] = {
+ /* U+2c40 */
+ 0x2c40, 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47,
+ 0x2c48, 0x2c49, 0x2c4a, 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f,
+ 0x2c50, 0x2c51, 0x2c52, 0x2c53, 0x2c54, 0x2c55, 0x2c56, 0x2c57,
+ 0x2c58, 0x2c59, 0x2c5a, 0x2c5b, 0x2c5c, 0x2c5d, 0x2c5e, 0x2c5f,
+ 0x2c61, 0x2c61, 0x026b, 0x1d7d, 0x027d, 0x2c65, 0x2c66, 0x2c68,
+ 0x2c68, 0x2c6a, 0x2c6a, 0x2c6c, 0x2c6c, 0x0251, 0x0271, 0x0250,
+ 0x0252, 0x2c71, 0x2c73, 0x2c73, 0x2c74, 0x2c76, 0x2c76, 0x2c77,
+ 0x2c78, 0x2c79, 0x2c7a, 0x2c7b, 0x006a, 0x0076, 0x023f, 0x0240,
+ };
+
+static const unsigned short gNormalizeTable2c80[] = {
+ /* U+2c80 */
+ 0x2c81, 0x2c81, 0x2c83, 0x2c83, 0x2c85, 0x2c85, 0x2c87, 0x2c87,
+ 0x2c89, 0x2c89, 0x2c8b, 0x2c8b, 0x2c8d, 0x2c8d, 0x2c8f, 0x2c8f,
+ 0x2c91, 0x2c91, 0x2c93, 0x2c93, 0x2c95, 0x2c95, 0x2c97, 0x2c97,
+ 0x2c99, 0x2c99, 0x2c9b, 0x2c9b, 0x2c9d, 0x2c9d, 0x2c9f, 0x2c9f,
+ 0x2ca1, 0x2ca1, 0x2ca3, 0x2ca3, 0x2ca5, 0x2ca5, 0x2ca7, 0x2ca7,
+ 0x2ca9, 0x2ca9, 0x2cab, 0x2cab, 0x2cad, 0x2cad, 0x2caf, 0x2caf,
+ 0x2cb1, 0x2cb1, 0x2cb3, 0x2cb3, 0x2cb5, 0x2cb5, 0x2cb7, 0x2cb7,
+ 0x2cb9, 0x2cb9, 0x2cbb, 0x2cbb, 0x2cbd, 0x2cbd, 0x2cbf, 0x2cbf,
+ };
+
+static const unsigned short gNormalizeTable2cc0[] = {
+ /* U+2cc0 */
+ 0x2cc1, 0x2cc1, 0x2cc3, 0x2cc3, 0x2cc5, 0x2cc5, 0x2cc7, 0x2cc7,
+ 0x2cc9, 0x2cc9, 0x2ccb, 0x2ccb, 0x2ccd, 0x2ccd, 0x2ccf, 0x2ccf,
+ 0x2cd1, 0x2cd1, 0x2cd3, 0x2cd3, 0x2cd5, 0x2cd5, 0x2cd7, 0x2cd7,
+ 0x2cd9, 0x2cd9, 0x2cdb, 0x2cdb, 0x2cdd, 0x2cdd, 0x2cdf, 0x2cdf,
+ 0x2ce1, 0x2ce1, 0x2ce3, 0x2ce3, 0x2ce4, 0x2ce5, 0x2ce6, 0x2ce7,
+ 0x2ce8, 0x2ce9, 0x2cea, 0x2cec, 0x2cec, 0x2cee, 0x2cee, 0x2cef,
+ 0x2cf0, 0x2cf1, 0x2cf2, 0x2cf3, 0x2cf4, 0x2cf5, 0x2cf6, 0x2cf7,
+ 0x2cf8, 0x2cf9, 0x2cfa, 0x2cfb, 0x2cfc, 0x2cfd, 0x2cfe, 0x2cff,
+ };
+
+static const unsigned short gNormalizeTable2d40[] = {
+ /* U+2d40 */
+ 0x2d40, 0x2d41, 0x2d42, 0x2d43, 0x2d44, 0x2d45, 0x2d46, 0x2d47,
+ 0x2d48, 0x2d49, 0x2d4a, 0x2d4b, 0x2d4c, 0x2d4d, 0x2d4e, 0x2d4f,
+ 0x2d50, 0x2d51, 0x2d52, 0x2d53, 0x2d54, 0x2d55, 0x2d56, 0x2d57,
+ 0x2d58, 0x2d59, 0x2d5a, 0x2d5b, 0x2d5c, 0x2d5d, 0x2d5e, 0x2d5f,
+ 0x2d60, 0x2d61, 0x2d62, 0x2d63, 0x2d64, 0x2d65, 0x2d66, 0x2d67,
+ 0x2d68, 0x2d69, 0x2d6a, 0x2d6b, 0x2d6c, 0x2d6d, 0x2d6e, 0x2d61,
+ 0x2d70, 0x2d71, 0x2d72, 0x2d73, 0x2d74, 0x2d75, 0x2d76, 0x2d77,
+ 0x2d78, 0x2d79, 0x2d7a, 0x2d7b, 0x2d7c, 0x2d7d, 0x2d7e, 0x2d7f,
+ };
+
+static const unsigned short gNormalizeTable2e80[] = {
+ /* U+2e80 */
+ 0x2e80, 0x2e81, 0x2e82, 0x2e83, 0x2e84, 0x2e85, 0x2e86, 0x2e87,
+ 0x2e88, 0x2e89, 0x2e8a, 0x2e8b, 0x2e8c, 0x2e8d, 0x2e8e, 0x2e8f,
+ 0x2e90, 0x2e91, 0x2e92, 0x2e93, 0x2e94, 0x2e95, 0x2e96, 0x2e97,
+ 0x2e98, 0x2e99, 0x2e9a, 0x2e9b, 0x2e9c, 0x2e9d, 0x2e9e, 0x6bcd,
+ 0x2ea0, 0x2ea1, 0x2ea2, 0x2ea3, 0x2ea4, 0x2ea5, 0x2ea6, 0x2ea7,
+ 0x2ea8, 0x2ea9, 0x2eaa, 0x2eab, 0x2eac, 0x2ead, 0x2eae, 0x2eaf,
+ 0x2eb0, 0x2eb1, 0x2eb2, 0x2eb3, 0x2eb4, 0x2eb5, 0x2eb6, 0x2eb7,
+ 0x2eb8, 0x2eb9, 0x2eba, 0x2ebb, 0x2ebc, 0x2ebd, 0x2ebe, 0x2ebf,
+ };
+
+static const unsigned short gNormalizeTable2ec0[] = {
+ /* U+2ec0 */
+ 0x2ec0, 0x2ec1, 0x2ec2, 0x2ec3, 0x2ec4, 0x2ec5, 0x2ec6, 0x2ec7,
+ 0x2ec8, 0x2ec9, 0x2eca, 0x2ecb, 0x2ecc, 0x2ecd, 0x2ece, 0x2ecf,
+ 0x2ed0, 0x2ed1, 0x2ed2, 0x2ed3, 0x2ed4, 0x2ed5, 0x2ed6, 0x2ed7,
+ 0x2ed8, 0x2ed9, 0x2eda, 0x2edb, 0x2edc, 0x2edd, 0x2ede, 0x2edf,
+ 0x2ee0, 0x2ee1, 0x2ee2, 0x2ee3, 0x2ee4, 0x2ee5, 0x2ee6, 0x2ee7,
+ 0x2ee8, 0x2ee9, 0x2eea, 0x2eeb, 0x2eec, 0x2eed, 0x2eee, 0x2eef,
+ 0x2ef0, 0x2ef1, 0x2ef2, 0x9f9f, 0x2ef4, 0x2ef5, 0x2ef6, 0x2ef7,
+ 0x2ef8, 0x2ef9, 0x2efa, 0x2efb, 0x2efc, 0x2efd, 0x2efe, 0x2eff,
+ };
+
+static const unsigned short gNormalizeTable2f00[] = {
+ /* U+2f00 */
+ 0x4e00, 0x4e28, 0x4e36, 0x4e3f, 0x4e59, 0x4e85, 0x4e8c, 0x4ea0,
+ 0x4eba, 0x513f, 0x5165, 0x516b, 0x5182, 0x5196, 0x51ab, 0x51e0,
+ 0x51f5, 0x5200, 0x529b, 0x52f9, 0x5315, 0x531a, 0x5338, 0x5341,
+ 0x535c, 0x5369, 0x5382, 0x53b6, 0x53c8, 0x53e3, 0x56d7, 0x571f,
+ 0x58eb, 0x5902, 0x590a, 0x5915, 0x5927, 0x5973, 0x5b50, 0x5b80,
+ 0x5bf8, 0x5c0f, 0x5c22, 0x5c38, 0x5c6e, 0x5c71, 0x5ddb, 0x5de5,
+ 0x5df1, 0x5dfe, 0x5e72, 0x5e7a, 0x5e7f, 0x5ef4, 0x5efe, 0x5f0b,
+ 0x5f13, 0x5f50, 0x5f61, 0x5f73, 0x5fc3, 0x6208, 0x6236, 0x624b,
+ };
+
+static const unsigned short gNormalizeTable2f40[] = {
+ /* U+2f40 */
+ 0x652f, 0x6534, 0x6587, 0x6597, 0x65a4, 0x65b9, 0x65e0, 0x65e5,
+ 0x66f0, 0x6708, 0x6728, 0x6b20, 0x6b62, 0x6b79, 0x6bb3, 0x6bcb,
+ 0x6bd4, 0x6bdb, 0x6c0f, 0x6c14, 0x6c34, 0x706b, 0x722a, 0x7236,
+ 0x723b, 0x723f, 0x7247, 0x7259, 0x725b, 0x72ac, 0x7384, 0x7389,
+ 0x74dc, 0x74e6, 0x7518, 0x751f, 0x7528, 0x7530, 0x758b, 0x7592,
+ 0x7676, 0x767d, 0x76ae, 0x76bf, 0x76ee, 0x77db, 0x77e2, 0x77f3,
+ 0x793a, 0x79b8, 0x79be, 0x7a74, 0x7acb, 0x7af9, 0x7c73, 0x7cf8,
+ 0x7f36, 0x7f51, 0x7f8a, 0x7fbd, 0x8001, 0x800c, 0x8012, 0x8033,
+ };
+
+static const unsigned short gNormalizeTable2f80[] = {
+ /* U+2f80 */
+ 0x807f, 0x8089, 0x81e3, 0x81ea, 0x81f3, 0x81fc, 0x820c, 0x821b,
+ 0x821f, 0x826e, 0x8272, 0x8278, 0x864d, 0x866b, 0x8840, 0x884c,
+ 0x8863, 0x897e, 0x898b, 0x89d2, 0x8a00, 0x8c37, 0x8c46, 0x8c55,
+ 0x8c78, 0x8c9d, 0x8d64, 0x8d70, 0x8db3, 0x8eab, 0x8eca, 0x8f9b,
+ 0x8fb0, 0x8fb5, 0x9091, 0x9149, 0x91c6, 0x91cc, 0x91d1, 0x9577,
+ 0x9580, 0x961c, 0x96b6, 0x96b9, 0x96e8, 0x9751, 0x975e, 0x9762,
+ 0x9769, 0x97cb, 0x97ed, 0x97f3, 0x9801, 0x98a8, 0x98db, 0x98df,
+ 0x9996, 0x9999, 0x99ac, 0x9aa8, 0x9ad8, 0x9adf, 0x9b25, 0x9b2f,
+ };
+
+static const unsigned short gNormalizeTable2fc0[] = {
+ /* U+2fc0 */
+ 0x9b32, 0x9b3c, 0x9b5a, 0x9ce5, 0x9e75, 0x9e7f, 0x9ea5, 0x9ebb,
+ 0x9ec3, 0x9ecd, 0x9ed1, 0x9ef9, 0x9efd, 0x9f0e, 0x9f13, 0x9f20,
+ 0x9f3b, 0x9f4a, 0x9f52, 0x9f8d, 0x9f9c, 0x9fa0, 0x2fd6, 0x2fd7,
+ 0x2fd8, 0x2fd9, 0x2fda, 0x2fdb, 0x2fdc, 0x2fdd, 0x2fde, 0x2fdf,
+ 0x2fe0, 0x2fe1, 0x2fe2, 0x2fe3, 0x2fe4, 0x2fe5, 0x2fe6, 0x2fe7,
+ 0x2fe8, 0x2fe9, 0x2fea, 0x2feb, 0x2fec, 0x2fed, 0x2fee, 0x2fef,
+ 0x2ff0, 0x2ff1, 0x2ff2, 0x2ff3, 0x2ff4, 0x2ff5, 0x2ff6, 0x2ff7,
+ 0x2ff8, 0x2ff9, 0x2ffa, 0x2ffb, 0x2ffc, 0x2ffd, 0x2ffe, 0x2fff,
+ };
+
+static const unsigned short gNormalizeTable3000[] = {
+ /* U+3000 */
+ 0x0020, 0x3001, 0x3002, 0x3003, 0x3004, 0x3005, 0x3006, 0x3007,
+ 0x3008, 0x3009, 0x300a, 0x300b, 0x300c, 0x300d, 0x300e, 0x300f,
+ 0x3010, 0x3011, 0x3012, 0x3013, 0x3014, 0x3015, 0x3016, 0x3017,
+ 0x3018, 0x3019, 0x301a, 0x301b, 0x301c, 0x301d, 0x301e, 0x301f,
+ 0x3020, 0x3021, 0x3022, 0x3023, 0x3024, 0x3025, 0x3026, 0x3027,
+ 0x3028, 0x3029, 0x302a, 0x302b, 0x302c, 0x302d, 0x302e, 0x302f,
+ 0x3030, 0x3031, 0x3032, 0x3033, 0x3034, 0x3035, 0x3012, 0x3037,
+ 0x5341, 0x5344, 0x5345, 0x303b, 0x303c, 0x303d, 0x303e, 0x303f,
+ };
+
+static const unsigned short gNormalizeTable3040[] = {
+ /* U+3040 */
+ 0x3040, 0x3041, 0x3042, 0x3043, 0x3044, 0x3045, 0x3046, 0x3047,
+ 0x3048, 0x3049, 0x304a, 0x304b, 0x304b, 0x304d, 0x304d, 0x304f,
+ 0x304f, 0x3051, 0x3051, 0x3053, 0x3053, 0x3055, 0x3055, 0x3057,
+ 0x3057, 0x3059, 0x3059, 0x305b, 0x305b, 0x305d, 0x305d, 0x305f,
+ 0x305f, 0x3061, 0x3061, 0x3063, 0x3064, 0x3064, 0x3066, 0x3066,
+ 0x3068, 0x3068, 0x306a, 0x306b, 0x306c, 0x306d, 0x306e, 0x306f,
+ 0x306f, 0x306f, 0x3072, 0x3072, 0x3072, 0x3075, 0x3075, 0x3075,
+ 0x3078, 0x3078, 0x3078, 0x307b, 0x307b, 0x307b, 0x307e, 0x307f,
+ };
+
+static const unsigned short gNormalizeTable3080[] = {
+ /* U+3080 */
+ 0x3080, 0x3081, 0x3082, 0x3083, 0x3084, 0x3085, 0x3086, 0x3087,
+ 0x3088, 0x3089, 0x308a, 0x308b, 0x308c, 0x308d, 0x308e, 0x308f,
+ 0x3090, 0x3091, 0x3092, 0x3093, 0x3046, 0x3095, 0x3096, 0x3097,
+ 0x3098, 0x3099, 0x309a, 0x0020, 0x0020, 0x309d, 0x309d, 0x3088,
+ 0x30a0, 0x30a1, 0x30a2, 0x30a3, 0x30a4, 0x30a5, 0x30a6, 0x30a7,
+ 0x30a8, 0x30a9, 0x30aa, 0x30ab, 0x30ab, 0x30ad, 0x30ad, 0x30af,
+ 0x30af, 0x30b1, 0x30b1, 0x30b3, 0x30b3, 0x30b5, 0x30b5, 0x30b7,
+ 0x30b7, 0x30b9, 0x30b9, 0x30bb, 0x30bb, 0x30bd, 0x30bd, 0x30bf,
+ };
+
+static const unsigned short gNormalizeTable30c0[] = {
+ /* U+30c0 */
+ 0x30bf, 0x30c1, 0x30c1, 0x30c3, 0x30c4, 0x30c4, 0x30c6, 0x30c6,
+ 0x30c8, 0x30c8, 0x30ca, 0x30cb, 0x30cc, 0x30cd, 0x30ce, 0x30cf,
+ 0x30cf, 0x30cf, 0x30d2, 0x30d2, 0x30d2, 0x30d5, 0x30d5, 0x30d5,
+ 0x30d8, 0x30d8, 0x30d8, 0x30db, 0x30db, 0x30db, 0x30de, 0x30df,
+ 0x30e0, 0x30e1, 0x30e2, 0x30e3, 0x30e4, 0x30e5, 0x30e6, 0x30e7,
+ 0x30e8, 0x30e9, 0x30ea, 0x30eb, 0x30ec, 0x30ed, 0x30ee, 0x30ef,
+ 0x30f0, 0x30f1, 0x30f2, 0x30f3, 0x30a6, 0x30f5, 0x30f6, 0x30ef,
+ 0x30f0, 0x30f1, 0x30f2, 0x30fb, 0x30fc, 0x30fd, 0x30fd, 0x30b3,
+ };
+
+static const unsigned short gNormalizeTable3100[] = {
+ /* U+3100 */
+ 0x3100, 0x3101, 0x3102, 0x3103, 0x3104, 0x3105, 0x3106, 0x3107,
+ 0x3108, 0x3109, 0x310a, 0x310b, 0x310c, 0x310d, 0x310e, 0x310f,
+ 0x3110, 0x3111, 0x3112, 0x3113, 0x3114, 0x3115, 0x3116, 0x3117,
+ 0x3118, 0x3119, 0x311a, 0x311b, 0x311c, 0x311d, 0x311e, 0x311f,
+ 0x3120, 0x3121, 0x3122, 0x3123, 0x3124, 0x3125, 0x3126, 0x3127,
+ 0x3128, 0x3129, 0x312a, 0x312b, 0x312c, 0x312d, 0x312e, 0x312f,
+ 0x3130, 0x1100, 0x1101, 0x11aa, 0x1102, 0x11ac, 0x11ad, 0x1103,
+ 0x1104, 0x1105, 0x11b0, 0x11b1, 0x11b2, 0x11b3, 0x11b4, 0x11b5,
+ };
+
+static const unsigned short gNormalizeTable3140[] = {
+ /* U+3140 */
+ 0x111a, 0x1106, 0x1107, 0x1108, 0x1121, 0x1109, 0x110a, 0x110b,
+ 0x110c, 0x110d, 0x110e, 0x110f, 0x1110, 0x1111, 0x1112, 0x1161,
+ 0x1162, 0x1163, 0x1164, 0x1165, 0x1166, 0x1167, 0x1168, 0x1169,
+ 0x116a, 0x116b, 0x116c, 0x116d, 0x116e, 0x116f, 0x1170, 0x1171,
+ 0x1172, 0x1173, 0x1174, 0x1175, 0x0020, 0x1114, 0x1115, 0x11c7,
+ 0x11c8, 0x11cc, 0x11ce, 0x11d3, 0x11d7, 0x11d9, 0x111c, 0x11dd,
+ 0x11df, 0x111d, 0x111e, 0x1120, 0x1122, 0x1123, 0x1127, 0x1129,
+ 0x112b, 0x112c, 0x112d, 0x112e, 0x112f, 0x1132, 0x1136, 0x1140,
+ };
+
+static const unsigned short gNormalizeTable3180[] = {
+ /* U+3180 */
+ 0x1147, 0x114c, 0x11f1, 0x11f2, 0x1157, 0x1158, 0x1159, 0x1184,
+ 0x1185, 0x1188, 0x1191, 0x1192, 0x1194, 0x119e, 0x11a1, 0x318f,
+ 0x3190, 0x3191, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e0a, 0x4e2d,
+ 0x4e0b, 0x7532, 0x4e59, 0x4e19, 0x4e01, 0x5929, 0x5730, 0x4eba,
+ 0x31a0, 0x31a1, 0x31a2, 0x31a3, 0x31a4, 0x31a5, 0x31a6, 0x31a7,
+ 0x31a8, 0x31a9, 0x31aa, 0x31ab, 0x31ac, 0x31ad, 0x31ae, 0x31af,
+ 0x31b0, 0x31b1, 0x31b2, 0x31b3, 0x31b4, 0x31b5, 0x31b6, 0x31b7,
+ 0x31b8, 0x31b9, 0x31ba, 0x31bb, 0x31bc, 0x31bd, 0x31be, 0x31bf,
+ };
+
+static const unsigned short gNormalizeTable3200[] = {
+ /* U+3200 */
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x321f,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028,
+ };
+
+static const unsigned short gNormalizeTable3240[] = {
+ /* U+3240 */
+ 0x0028, 0x0028, 0x0028, 0x0028, 0x554f, 0x5e7c, 0x6587, 0x7b8f,
+ 0x3248, 0x3249, 0x324a, 0x324b, 0x324c, 0x324d, 0x324e, 0x324f,
+ 0x0070, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032,
+ 0x0032, 0x0032, 0x0033, 0x0033, 0x0033, 0x0033, 0x0033, 0x0033,
+ 0x1100, 0x1102, 0x1103, 0x1105, 0x1106, 0x1107, 0x1109, 0x110b,
+ 0x110c, 0x110e, 0x110f, 0x1110, 0x1111, 0x1112, 0x1100, 0x1102,
+ 0x1103, 0x1105, 0x1106, 0x1107, 0x1109, 0x110b, 0x110c, 0x110e,
+ 0x110f, 0x1110, 0x1111, 0x1112, 0x110e, 0x110c, 0x110b, 0x327f,
+ };
+
+static const unsigned short gNormalizeTable3280[] = {
+ /* U+3280 */
+ 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
+ 0x4e5d, 0x5341, 0x6708, 0x706b, 0x6c34, 0x6728, 0x91d1, 0x571f,
+ 0x65e5, 0x682a, 0x6709, 0x793e, 0x540d, 0x7279, 0x8ca1, 0x795d,
+ 0x52b4, 0x79d8, 0x7537, 0x5973, 0x9069, 0x512a, 0x5370, 0x6ce8,
+ 0x9805, 0x4f11, 0x5199, 0x6b63, 0x4e0a, 0x4e2d, 0x4e0b, 0x5de6,
+ 0x53f3, 0x533b, 0x5b97, 0x5b66, 0x76e3, 0x4f01, 0x8cc7, 0x5354,
+ 0x591c, 0x0033, 0x0033, 0x0033, 0x0033, 0x0034, 0x0034, 0x0034,
+ 0x0034, 0x0034, 0x0034, 0x0034, 0x0034, 0x0034, 0x0034, 0x0035,
+ };
+
+static const unsigned short gNormalizeTable32c0[] = {
+ /* U+32c0 */
+ 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038,
+ 0x0039, 0x0031, 0x0031, 0x0031, 0x0068, 0x0065, 0x0065, 0x006c,
+ 0x30a2, 0x30a4, 0x30a6, 0x30a8, 0x30aa, 0x30ab, 0x30ad, 0x30af,
+ 0x30b1, 0x30b3, 0x30b5, 0x30b7, 0x30b9, 0x30bb, 0x30bd, 0x30bf,
+ 0x30c1, 0x30c4, 0x30c6, 0x30c8, 0x30ca, 0x30cb, 0x30cc, 0x30cd,
+ 0x30ce, 0x30cf, 0x30d2, 0x30d5, 0x30d8, 0x30db, 0x30de, 0x30df,
+ 0x30e0, 0x30e1, 0x30e2, 0x30e4, 0x30e6, 0x30e8, 0x30e9, 0x30ea,
+ 0x30eb, 0x30ec, 0x30ed, 0x30ef, 0x30f0, 0x30f1, 0x30f2, 0x32ff,
+ };
+
+static const unsigned short gNormalizeTable3300[] = {
+ /* U+3300 */
+ 0x30a2, 0x30a2, 0x30a2, 0x30a2, 0x30a4, 0x30a4, 0x30a6, 0x30a8,
+ 0x30a8, 0x30aa, 0x30aa, 0x30ab, 0x30ab, 0x30ab, 0x30ab, 0x30ab,
+ 0x30ad, 0x30ad, 0x30ad, 0x30ad, 0x30ad, 0x30ad, 0x30ad, 0x30ad,
+ 0x30af, 0x30af, 0x30af, 0x30af, 0x30b1, 0x30b3, 0x30b3, 0x30b5,
+ 0x30b5, 0x30b7, 0x30bb, 0x30bb, 0x30bf, 0x30c6, 0x30c8, 0x30c8,
+ 0x30ca, 0x30ce, 0x30cf, 0x30cf, 0x30cf, 0x30cf, 0x30d2, 0x30d2,
+ 0x30d2, 0x30d2, 0x30d5, 0x30d5, 0x30d5, 0x30d5, 0x30d8, 0x30d8,
+ 0x30d8, 0x30d8, 0x30d8, 0x30d8, 0x30d8, 0x30db, 0x30db, 0x30db,
+ };
+
+static const unsigned short gNormalizeTable3340[] = {
+ /* U+3340 */
+ 0x30db, 0x30db, 0x30db, 0x30de, 0x30de, 0x30de, 0x30de, 0x30de,
+ 0x30df, 0x30df, 0x30df, 0x30e1, 0x30e1, 0x30e1, 0x30e4, 0x30e4,
+ 0x30e6, 0x30ea, 0x30ea, 0x30eb, 0x30eb, 0x30ec, 0x30ec, 0x30ef,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031,
+ 0x0031, 0x0031, 0x0031, 0x0031, 0x0032, 0x0032, 0x0032, 0x0032,
+ 0x0032, 0x0068, 0x0064, 0x0061, 0x0062, 0x006f, 0x0070, 0x0064,
+ 0x0064, 0x0064, 0x0069, 0x5e73, 0x662d, 0x5927, 0x660e, 0x682a,
+ };
+
+static const unsigned short gNormalizeTable3380[] = {
+ /* U+3380 */
+ 0x0070, 0x006e, 0x03bc, 0x006d, 0x006b, 0x006b, 0x006d, 0x0067,
+ 0x0063, 0x006b, 0x0070, 0x006e, 0x03bc, 0x03bc, 0x006d, 0x006b,
+ 0x0068, 0x006b, 0x006d, 0x0067, 0x0074, 0x03bc, 0x006d, 0x0064,
+ 0x006b, 0x0066, 0x006e, 0x03bc, 0x006d, 0x0063, 0x006b, 0x006d,
+ 0x0063, 0x006d, 0x006b, 0x006d, 0x0063, 0x006d, 0x006b, 0x006d,
+ 0x006d, 0x0070, 0x006b, 0x006d, 0x0067, 0x0072, 0x0072, 0x0072,
+ 0x0070, 0x006e, 0x03bc, 0x006d, 0x0070, 0x006e, 0x03bc, 0x006d,
+ 0x006b, 0x006d, 0x0070, 0x006e, 0x03bc, 0x006d, 0x006b, 0x006d,
+ };
+
+static const unsigned short gNormalizeTable33c0[] = {
+ /* U+33c0 */
+ 0x006b, 0x006d, 0x0061, 0x0062, 0x0063, 0x0063, 0x0063, 0x0063,
+ 0x0064, 0x0067, 0x0068, 0x0068, 0x0069, 0x006b, 0x006b, 0x006b,
+ 0x006c, 0x006c, 0x006c, 0x006c, 0x006d, 0x006d, 0x006d, 0x0070,
+ 0x0070, 0x0070, 0x0070, 0x0073, 0x0073, 0x0077, 0x0076, 0x0061,
+ 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038,
+ 0x0039, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031,
+ 0x0031, 0x0031, 0x0031, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032,
+ 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0033, 0x0033, 0x0067,
+ };
+
+static const unsigned short gNormalizeTablea640[] = {
+ /* U+a640 */
+ 0xa641, 0xa641, 0xa643, 0xa643, 0xa645, 0xa645, 0xa647, 0xa647,
+ 0xa649, 0xa649, 0xa64b, 0xa64b, 0xa64d, 0xa64d, 0xa64f, 0xa64f,
+ 0xa651, 0xa651, 0xa653, 0xa653, 0xa655, 0xa655, 0xa657, 0xa657,
+ 0xa659, 0xa659, 0xa65b, 0xa65b, 0xa65d, 0xa65d, 0xa65f, 0xa65f,
+ 0xa660, 0xa661, 0xa663, 0xa663, 0xa665, 0xa665, 0xa667, 0xa667,
+ 0xa669, 0xa669, 0xa66b, 0xa66b, 0xa66d, 0xa66d, 0xa66e, 0xa66f,
+ 0xa670, 0xa671, 0xa672, 0xa673, 0xa674, 0xa675, 0xa676, 0xa677,
+ 0xa678, 0xa679, 0xa67a, 0xa67b, 0xa67c, 0xa67d, 0xa67e, 0xa67f,
+ };
+
+static const unsigned short gNormalizeTablea680[] = {
+ /* U+a680 */
+ 0xa681, 0xa681, 0xa683, 0xa683, 0xa685, 0xa685, 0xa687, 0xa687,
+ 0xa689, 0xa689, 0xa68b, 0xa68b, 0xa68d, 0xa68d, 0xa68f, 0xa68f,
+ 0xa691, 0xa691, 0xa693, 0xa693, 0xa695, 0xa695, 0xa697, 0xa697,
+ 0xa698, 0xa699, 0xa69a, 0xa69b, 0xa69c, 0xa69d, 0xa69e, 0xa69f,
+ 0xa6a0, 0xa6a1, 0xa6a2, 0xa6a3, 0xa6a4, 0xa6a5, 0xa6a6, 0xa6a7,
+ 0xa6a8, 0xa6a9, 0xa6aa, 0xa6ab, 0xa6ac, 0xa6ad, 0xa6ae, 0xa6af,
+ 0xa6b0, 0xa6b1, 0xa6b2, 0xa6b3, 0xa6b4, 0xa6b5, 0xa6b6, 0xa6b7,
+ 0xa6b8, 0xa6b9, 0xa6ba, 0xa6bb, 0xa6bc, 0xa6bd, 0xa6be, 0xa6bf,
+ };
+
+static const unsigned short gNormalizeTablea700[] = {
+ /* U+a700 */
+ 0xa700, 0xa701, 0xa702, 0xa703, 0xa704, 0xa705, 0xa706, 0xa707,
+ 0xa708, 0xa709, 0xa70a, 0xa70b, 0xa70c, 0xa70d, 0xa70e, 0xa70f,
+ 0xa710, 0xa711, 0xa712, 0xa713, 0xa714, 0xa715, 0xa716, 0xa717,
+ 0xa718, 0xa719, 0xa71a, 0xa71b, 0xa71c, 0xa71d, 0xa71e, 0xa71f,
+ 0xa720, 0xa721, 0xa723, 0xa723, 0xa725, 0xa725, 0xa727, 0xa727,
+ 0xa729, 0xa729, 0xa72b, 0xa72b, 0xa72d, 0xa72d, 0xa72f, 0xa72f,
+ 0xa730, 0xa731, 0xa733, 0xa733, 0xa735, 0xa735, 0xa737, 0xa737,
+ 0xa739, 0xa739, 0xa73b, 0xa73b, 0xa73d, 0xa73d, 0xa73f, 0xa73f,
+ };
+
+static const unsigned short gNormalizeTablea740[] = {
+ /* U+a740 */
+ 0xa741, 0xa741, 0xa743, 0xa743, 0xa745, 0xa745, 0xa747, 0xa747,
+ 0xa749, 0xa749, 0xa74b, 0xa74b, 0xa74d, 0xa74d, 0xa74f, 0xa74f,
+ 0xa751, 0xa751, 0xa753, 0xa753, 0xa755, 0xa755, 0xa757, 0xa757,
+ 0xa759, 0xa759, 0xa75b, 0xa75b, 0xa75d, 0xa75d, 0xa75f, 0xa75f,
+ 0xa761, 0xa761, 0xa763, 0xa763, 0xa765, 0xa765, 0xa767, 0xa767,
+ 0xa769, 0xa769, 0xa76b, 0xa76b, 0xa76d, 0xa76d, 0xa76f, 0xa76f,
+ 0xa76f, 0xa771, 0xa772, 0xa773, 0xa774, 0xa775, 0xa776, 0xa777,
+ 0xa778, 0xa77a, 0xa77a, 0xa77c, 0xa77c, 0x1d79, 0xa77f, 0xa77f,
+ };
+
+static const unsigned short gNormalizeTablea780[] = {
+ /* U+a780 */
+ 0xa781, 0xa781, 0xa783, 0xa783, 0xa785, 0xa785, 0xa787, 0xa787,
+ 0xa788, 0xa789, 0xa78a, 0xa78c, 0xa78c, 0xa78d, 0xa78e, 0xa78f,
+ 0xa790, 0xa791, 0xa792, 0xa793, 0xa794, 0xa795, 0xa796, 0xa797,
+ 0xa798, 0xa799, 0xa79a, 0xa79b, 0xa79c, 0xa79d, 0xa79e, 0xa79f,
+ 0xa7a0, 0xa7a1, 0xa7a2, 0xa7a3, 0xa7a4, 0xa7a5, 0xa7a6, 0xa7a7,
+ 0xa7a8, 0xa7a9, 0xa7aa, 0xa7ab, 0xa7ac, 0xa7ad, 0xa7ae, 0xa7af,
+ 0xa7b0, 0xa7b1, 0xa7b2, 0xa7b3, 0xa7b4, 0xa7b5, 0xa7b6, 0xa7b7,
+ 0xa7b8, 0xa7b9, 0xa7ba, 0xa7bb, 0xa7bc, 0xa7bd, 0xa7be, 0xa7bf,
+ };
+
+static const unsigned short gNormalizeTablef900[] = {
+ /* U+f900 */
+ 0x8c48, 0x66f4, 0x8eca, 0x8cc8, 0x6ed1, 0x4e32, 0x53e5, 0x9f9c,
+ 0x9f9c, 0x5951, 0x91d1, 0x5587, 0x5948, 0x61f6, 0x7669, 0x7f85,
+ 0x863f, 0x87ba, 0x88f8, 0x908f, 0x6a02, 0x6d1b, 0x70d9, 0x73de,
+ 0x843d, 0x916a, 0x99f1, 0x4e82, 0x5375, 0x6b04, 0x721b, 0x862d,
+ 0x9e1e, 0x5d50, 0x6feb, 0x85cd, 0x8964, 0x62c9, 0x81d8, 0x881f,
+ 0x5eca, 0x6717, 0x6d6a, 0x72fc, 0x90ce, 0x4f86, 0x51b7, 0x52de,
+ 0x64c4, 0x6ad3, 0x7210, 0x76e7, 0x8001, 0x8606, 0x865c, 0x8def,
+ 0x9732, 0x9b6f, 0x9dfa, 0x788c, 0x797f, 0x7da0, 0x83c9, 0x9304,
+ };
+
+static const unsigned short gNormalizeTablef940[] = {
+ /* U+f940 */
+ 0x9e7f, 0x8ad6, 0x58df, 0x5f04, 0x7c60, 0x807e, 0x7262, 0x78ca,
+ 0x8cc2, 0x96f7, 0x58d8, 0x5c62, 0x6a13, 0x6dda, 0x6f0f, 0x7d2f,
+ 0x7e37, 0x964b, 0x52d2, 0x808b, 0x51dc, 0x51cc, 0x7a1c, 0x7dbe,
+ 0x83f1, 0x9675, 0x8b80, 0x62cf, 0x6a02, 0x8afe, 0x4e39, 0x5be7,
+ 0x6012, 0x7387, 0x7570, 0x5317, 0x78fb, 0x4fbf, 0x5fa9, 0x4e0d,
+ 0x6ccc, 0x6578, 0x7d22, 0x53c3, 0x585e, 0x7701, 0x8449, 0x8aaa,
+ 0x6bba, 0x8fb0, 0x6c88, 0x62fe, 0x82e5, 0x63a0, 0x7565, 0x4eae,
+ 0x5169, 0x51c9, 0x6881, 0x7ce7, 0x826f, 0x8ad2, 0x91cf, 0x52f5,
+ };
+
+static const unsigned short gNormalizeTablef980[] = {
+ /* U+f980 */
+ 0x5442, 0x5973, 0x5eec, 0x65c5, 0x6ffe, 0x792a, 0x95ad, 0x9a6a,
+ 0x9e97, 0x9ece, 0x529b, 0x66c6, 0x6b77, 0x8f62, 0x5e74, 0x6190,
+ 0x6200, 0x649a, 0x6f23, 0x7149, 0x7489, 0x79ca, 0x7df4, 0x806f,
+ 0x8f26, 0x84ee, 0x9023, 0x934a, 0x5217, 0x52a3, 0x54bd, 0x70c8,
+ 0x88c2, 0x8aaa, 0x5ec9, 0x5ff5, 0x637b, 0x6bae, 0x7c3e, 0x7375,
+ 0x4ee4, 0x56f9, 0x5be7, 0x5dba, 0x601c, 0x73b2, 0x7469, 0x7f9a,
+ 0x8046, 0x9234, 0x96f6, 0x9748, 0x9818, 0x4f8b, 0x79ae, 0x91b4,
+ 0x96b8, 0x60e1, 0x4e86, 0x50da, 0x5bee, 0x5c3f, 0x6599, 0x6a02,
+ };
+
+static const unsigned short gNormalizeTablef9c0[] = {
+ /* U+f9c0 */
+ 0x71ce, 0x7642, 0x84fc, 0x907c, 0x9f8d, 0x6688, 0x962e, 0x5289,
+ 0x677b, 0x67f3, 0x6d41, 0x6e9c, 0x7409, 0x7559, 0x786b, 0x7d10,
+ 0x985e, 0x516d, 0x622e, 0x9678, 0x502b, 0x5d19, 0x6dea, 0x8f2a,
+ 0x5f8b, 0x6144, 0x6817, 0x7387, 0x9686, 0x5229, 0x540f, 0x5c65,
+ 0x6613, 0x674e, 0x68a8, 0x6ce5, 0x7406, 0x75e2, 0x7f79, 0x88cf,
+ 0x88e1, 0x91cc, 0x96e2, 0x533f, 0x6eba, 0x541d, 0x71d0, 0x7498,
+ 0x85fa, 0x96a3, 0x9c57, 0x9e9f, 0x6797, 0x6dcb, 0x81e8, 0x7acb,
+ 0x7b20, 0x7c92, 0x72c0, 0x7099, 0x8b58, 0x4ec0, 0x8336, 0x523a,
+ };
+
+static const unsigned short gNormalizeTablefa00[] = {
+ /* U+fa00 */
+ 0x5207, 0x5ea6, 0x62d3, 0x7cd6, 0x5b85, 0x6d1e, 0x66b4, 0x8f3b,
+ 0x884c, 0x964d, 0x898b, 0x5ed3, 0x5140, 0x55c0, 0xfa0e, 0xfa0f,
+ 0x585a, 0xfa11, 0x6674, 0xfa13, 0xfa14, 0x51de, 0x732a, 0x76ca,
+ 0x793c, 0x795e, 0x7965, 0x798f, 0x9756, 0x7cbe, 0x7fbd, 0xfa1f,
+ 0x8612, 0xfa21, 0x8af8, 0xfa23, 0xfa24, 0x9038, 0x90fd, 0xfa27,
+ 0xfa28, 0xfa29, 0x98ef, 0x98fc, 0x9928, 0x9db4, 0xfa2e, 0xfa2f,
+ 0x4fae, 0x50e7, 0x514d, 0x52c9, 0x52e4, 0x5351, 0x559d, 0x5606,
+ 0x5668, 0x5840, 0x58a8, 0x5c64, 0x5c6e, 0x6094, 0x6168, 0x618e,
+ };
+
+static const unsigned short gNormalizeTablefa40[] = {
+ /* U+fa40 */
+ 0x61f2, 0x654f, 0x65e2, 0x6691, 0x6885, 0x6d77, 0x6e1a, 0x6f22,
+ 0x716e, 0x722b, 0x7422, 0x7891, 0x793e, 0x7949, 0x7948, 0x7950,
+ 0x7956, 0x795d, 0x798d, 0x798e, 0x7a40, 0x7a81, 0x7bc0, 0x7df4,
+ 0x7e09, 0x7e41, 0x7f72, 0x8005, 0x81ed, 0x8279, 0x8279, 0x8457,
+ 0x8910, 0x8996, 0x8b01, 0x8b39, 0x8cd3, 0x8d08, 0x8fb6, 0x9038,
+ 0x96e3, 0x97ff, 0x983b, 0x6075, 0xfa6c, 0x8218, 0xfa6e, 0xfa6f,
+ 0x4e26, 0x51b5, 0x5168, 0x4f80, 0x5145, 0x5180, 0x52c7, 0x52fa,
+ 0x559d, 0x5555, 0x5599, 0x55e2, 0x585a, 0x58b3, 0x5944, 0x5954,
+ };
+
+static const unsigned short gNormalizeTablefa80[] = {
+ /* U+fa80 */
+ 0x5a62, 0x5b28, 0x5ed2, 0x5ed9, 0x5f69, 0x5fad, 0x60d8, 0x614e,
+ 0x6108, 0x618e, 0x6160, 0x61f2, 0x6234, 0x63c4, 0x641c, 0x6452,
+ 0x6556, 0x6674, 0x6717, 0x671b, 0x6756, 0x6b79, 0x6bba, 0x6d41,
+ 0x6edb, 0x6ecb, 0x6f22, 0x701e, 0x716e, 0x77a7, 0x7235, 0x72af,
+ 0x732a, 0x7471, 0x7506, 0x753b, 0x761d, 0x761f, 0x76ca, 0x76db,
+ 0x76f4, 0x774a, 0x7740, 0x78cc, 0x7ab1, 0x7bc0, 0x7c7b, 0x7d5b,
+ 0x7df4, 0x7f3e, 0x8005, 0x8352, 0x83ef, 0x8779, 0x8941, 0x8986,
+ 0x8996, 0x8abf, 0x8af8, 0x8acb, 0x8b01, 0x8afe, 0x8aed, 0x8b39,
+ };
+
+static const unsigned short gNormalizeTablefac0[] = {
+ /* U+fac0 */
+ 0x8b8a, 0x8d08, 0x8f38, 0x9072, 0x9199, 0x9276, 0x967c, 0x96e3,
+ 0x9756, 0x97db, 0x97ff, 0x980b, 0x983b, 0x9b12, 0x9f9c, 0xfacf,
+ 0xfad0, 0xfad1, 0x3b9d, 0x4018, 0x4039, 0xfad5, 0xfad6, 0xfad7,
+ 0x9f43, 0x9f8e, 0xfada, 0xfadb, 0xfadc, 0xfadd, 0xfade, 0xfadf,
+ 0xfae0, 0xfae1, 0xfae2, 0xfae3, 0xfae4, 0xfae5, 0xfae6, 0xfae7,
+ 0xfae8, 0xfae9, 0xfaea, 0xfaeb, 0xfaec, 0xfaed, 0xfaee, 0xfaef,
+ 0xfaf0, 0xfaf1, 0xfaf2, 0xfaf3, 0xfaf4, 0xfaf5, 0xfaf6, 0xfaf7,
+ 0xfaf8, 0xfaf9, 0xfafa, 0xfafb, 0xfafc, 0xfafd, 0xfafe, 0xfaff,
+ };
+
+static const unsigned short gNormalizeTablefb00[] = {
+ /* U+fb00 */
+ 0x0066, 0x0066, 0x0066, 0x0066, 0x0066, 0x0073, 0x0073, 0xfb07,
+ 0xfb08, 0xfb09, 0xfb0a, 0xfb0b, 0xfb0c, 0xfb0d, 0xfb0e, 0xfb0f,
+ 0xfb10, 0xfb11, 0xfb12, 0x0574, 0x0574, 0x0574, 0x057e, 0x0574,
+ 0xfb18, 0xfb19, 0xfb1a, 0xfb1b, 0xfb1c, 0x05d9, 0xfb1e, 0x05f2,
+ 0x05e2, 0x05d0, 0x05d3, 0x05d4, 0x05db, 0x05dc, 0x05dd, 0x05e8,
+ 0x05ea, 0x002b, 0x05e9, 0x05e9, 0x05e9, 0x05e9, 0x05d0, 0x05d0,
+ 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0xfb37,
+ 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0xfb3d, 0x05de, 0xfb3f,
+ };
+
+static const unsigned short gNormalizeTablefb40[] = {
+ /* U+fb40 */
+ 0x05e0, 0x05e1, 0xfb42, 0x05e3, 0x05e4, 0xfb45, 0x05e6, 0x05e7,
+ 0x05e8, 0x05e9, 0x05ea, 0x05d5, 0x05d1, 0x05db, 0x05e4, 0x05d0,
+ 0x0671, 0x0671, 0x067b, 0x067b, 0x067b, 0x067b, 0x067e, 0x067e,
+ 0x067e, 0x067e, 0x0680, 0x0680, 0x0680, 0x0680, 0x067a, 0x067a,
+ 0x067a, 0x067a, 0x067f, 0x067f, 0x067f, 0x067f, 0x0679, 0x0679,
+ 0x0679, 0x0679, 0x06a4, 0x06a4, 0x06a4, 0x06a4, 0x06a6, 0x06a6,
+ 0x06a6, 0x06a6, 0x0684, 0x0684, 0x0684, 0x0684, 0x0683, 0x0683,
+ 0x0683, 0x0683, 0x0686, 0x0686, 0x0686, 0x0686, 0x0687, 0x0687,
+ };
+
+static const unsigned short gNormalizeTablefb80[] = {
+ /* U+fb80 */
+ 0x0687, 0x0687, 0x068d, 0x068d, 0x068c, 0x068c, 0x068e, 0x068e,
+ 0x0688, 0x0688, 0x0698, 0x0698, 0x0691, 0x0691, 0x06a9, 0x06a9,
+ 0x06a9, 0x06a9, 0x06af, 0x06af, 0x06af, 0x06af, 0x06b3, 0x06b3,
+ 0x06b3, 0x06b3, 0x06b1, 0x06b1, 0x06b1, 0x06b1, 0x06ba, 0x06ba,
+ 0x06bb, 0x06bb, 0x06bb, 0x06bb, 0x06d5, 0x06d5, 0x06c1, 0x06c1,
+ 0x06c1, 0x06c1, 0x06be, 0x06be, 0x06be, 0x06be, 0x06d2, 0x06d2,
+ 0x06d2, 0x06d2, 0xfbb2, 0xfbb3, 0xfbb4, 0xfbb5, 0xfbb6, 0xfbb7,
+ 0xfbb8, 0xfbb9, 0xfbba, 0xfbbb, 0xfbbc, 0xfbbd, 0xfbbe, 0xfbbf,
+ };
+
+static const unsigned short gNormalizeTablefbc0[] = {
+ /* U+fbc0 */
+ 0xfbc0, 0xfbc1, 0xfbc2, 0xfbc3, 0xfbc4, 0xfbc5, 0xfbc6, 0xfbc7,
+ 0xfbc8, 0xfbc9, 0xfbca, 0xfbcb, 0xfbcc, 0xfbcd, 0xfbce, 0xfbcf,
+ 0xfbd0, 0xfbd1, 0xfbd2, 0x06ad, 0x06ad, 0x06ad, 0x06ad, 0x06c7,
+ 0x06c7, 0x06c6, 0x06c6, 0x06c8, 0x06c8, 0x06c7, 0x06cb, 0x06cb,
+ 0x06c5, 0x06c5, 0x06c9, 0x06c9, 0x06d0, 0x06d0, 0x06d0, 0x06d0,
+ 0x0649, 0x0649, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a,
+ 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a,
+ 0x064a, 0x064a, 0x064a, 0x064a, 0x06cc, 0x06cc, 0x06cc, 0x06cc,
+ };
+
+static const unsigned short gNormalizeTablefc00[] = {
+ /* U+fc00 */
+ 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x0628, 0x0628, 0x0628,
+ 0x0628, 0x0628, 0x0628, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a,
+ 0x062a, 0x062b, 0x062b, 0x062b, 0x062b, 0x062c, 0x062c, 0x062d,
+ 0x062d, 0x062e, 0x062e, 0x062e, 0x0633, 0x0633, 0x0633, 0x0633,
+ 0x0635, 0x0635, 0x0636, 0x0636, 0x0636, 0x0636, 0x0637, 0x0637,
+ 0x0638, 0x0639, 0x0639, 0x063a, 0x063a, 0x0641, 0x0641, 0x0641,
+ 0x0641, 0x0641, 0x0641, 0x0642, 0x0642, 0x0642, 0x0642, 0x0643,
+ 0x0643, 0x0643, 0x0643, 0x0643, 0x0643, 0x0643, 0x0643, 0x0644,
+ };
+
+static const unsigned short gNormalizeTablefc40[] = {
+ /* U+fc40 */
+ 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0645, 0x0645, 0x0645,
+ 0x0645, 0x0645, 0x0645, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646,
+ 0x0646, 0x0647, 0x0647, 0x0647, 0x0647, 0x064a, 0x064a, 0x064a,
+ 0x064a, 0x064a, 0x064a, 0x0630, 0x0631, 0x0649, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x064a, 0x064a, 0x064a, 0x064a,
+ 0x064a, 0x064a, 0x0628, 0x0628, 0x0628, 0x0628, 0x0628, 0x0628,
+ 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062b, 0x062b,
+ 0x062b, 0x062b, 0x062b, 0x062b, 0x0641, 0x0641, 0x0642, 0x0642,
+ };
+
+static const unsigned short gNormalizeTablefc80[] = {
+ /* U+fc80 */
+ 0x0643, 0x0643, 0x0643, 0x0643, 0x0643, 0x0644, 0x0644, 0x0644,
+ 0x0645, 0x0645, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646,
+ 0x0649, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a,
+ 0x064a, 0x064a, 0x064a, 0x064a, 0x0628, 0x0628, 0x0628, 0x0628,
+ 0x0628, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062b, 0x062c,
+ 0x062c, 0x062d, 0x062d, 0x062e, 0x062e, 0x0633, 0x0633, 0x0633,
+ 0x0633, 0x0635, 0x0635, 0x0635, 0x0636, 0x0636, 0x0636, 0x0636,
+ 0x0637, 0x0638, 0x0639, 0x0639, 0x063a, 0x063a, 0x0641, 0x0641,
+ };
+
+static const unsigned short gNormalizeTablefcc0[] = {
+ /* U+fcc0 */
+ 0x0641, 0x0641, 0x0642, 0x0642, 0x0643, 0x0643, 0x0643, 0x0643,
+ 0x0643, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0645, 0x0645,
+ 0x0645, 0x0645, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646, 0x0647,
+ 0x0647, 0x0647, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a,
+ 0x064a, 0x0628, 0x0628, 0x062a, 0x062a, 0x062b, 0x062b, 0x0633,
+ 0x0633, 0x0634, 0x0634, 0x0643, 0x0643, 0x0644, 0x0646, 0x0646,
+ 0x064a, 0x064a, 0x0640, 0x0640, 0x0640, 0x0637, 0x0637, 0x0639,
+ 0x0639, 0x063a, 0x063a, 0x0633, 0x0633, 0x0634, 0x0634, 0x062d,
+ };
+
+static const unsigned short gNormalizeTablefd00[] = {
+ /* U+fd00 */
+ 0x062d, 0x062c, 0x062c, 0x062e, 0x062e, 0x0635, 0x0635, 0x0636,
+ 0x0636, 0x0634, 0x0634, 0x0634, 0x0634, 0x0634, 0x0633, 0x0635,
+ 0x0636, 0x0637, 0x0637, 0x0639, 0x0639, 0x063a, 0x063a, 0x0633,
+ 0x0633, 0x0634, 0x0634, 0x062d, 0x062d, 0x062c, 0x062c, 0x062e,
+ 0x062e, 0x0635, 0x0635, 0x0636, 0x0636, 0x0634, 0x0634, 0x0634,
+ 0x0634, 0x0634, 0x0633, 0x0635, 0x0636, 0x0634, 0x0634, 0x0634,
+ 0x0634, 0x0633, 0x0634, 0x0637, 0x0633, 0x0633, 0x0633, 0x0634,
+ 0x0634, 0x0634, 0x0637, 0x0638, 0x0627, 0x0627, 0xfd3e, 0xfd3f,
+ };
+
+static const unsigned short gNormalizeTablefd40[] = {
+ /* U+fd40 */
+ 0xfd40, 0xfd41, 0xfd42, 0xfd43, 0xfd44, 0xfd45, 0xfd46, 0xfd47,
+ 0xfd48, 0xfd49, 0xfd4a, 0xfd4b, 0xfd4c, 0xfd4d, 0xfd4e, 0xfd4f,
+ 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a,
+ 0x062c, 0x062c, 0x062d, 0x062d, 0x0633, 0x0633, 0x0633, 0x0633,
+ 0x0633, 0x0633, 0x0633, 0x0633, 0x0635, 0x0635, 0x0635, 0x0634,
+ 0x0634, 0x0634, 0x0634, 0x0634, 0x0634, 0x0634, 0x0636, 0x0636,
+ 0x0636, 0x0637, 0x0637, 0x0637, 0x0637, 0x0639, 0x0639, 0x0639,
+ 0x0639, 0x063a, 0x063a, 0x063a, 0x0641, 0x0641, 0x0642, 0x0642,
+ };
+
+static const unsigned short gNormalizeTablefd80[] = {
+ /* U+fd80 */
+ 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644,
+ 0x0644, 0x0645, 0x0645, 0x0645, 0x0645, 0x0645, 0x0645, 0x0645,
+ 0xfd90, 0xfd91, 0x0645, 0x0647, 0x0647, 0x0646, 0x0646, 0x0646,
+ 0x0646, 0x0646, 0x0646, 0x0646, 0x064a, 0x064a, 0x0628, 0x062a,
+ 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062c, 0x062c, 0x062c,
+ 0x0633, 0x0635, 0x0634, 0x0636, 0x0644, 0x0644, 0x064a, 0x064a,
+ 0x064a, 0x0645, 0x0642, 0x0646, 0x0642, 0x0644, 0x0639, 0x0643,
+ 0x0646, 0x0645, 0x0644, 0x0643, 0x0644, 0x0646, 0x062c, 0x062d,
+ };
+
+static const unsigned short gNormalizeTablefdc0[] = {
+ /* U+fdc0 */
+ 0x0645, 0x0641, 0x0628, 0x0643, 0x0639, 0x0635, 0x0633, 0x0646,
+ 0xfdc8, 0xfdc9, 0xfdca, 0xfdcb, 0xfdcc, 0xfdcd, 0xfdce, 0xfdcf,
+ 0xfdd0, 0xfdd1, 0xfdd2, 0xfdd3, 0xfdd4, 0xfdd5, 0xfdd6, 0xfdd7,
+ 0xfdd8, 0xfdd9, 0xfdda, 0xfddb, 0xfddc, 0xfddd, 0xfdde, 0xfddf,
+ 0xfde0, 0xfde1, 0xfde2, 0xfde3, 0xfde4, 0xfde5, 0xfde6, 0xfde7,
+ 0xfde8, 0xfde9, 0xfdea, 0xfdeb, 0xfdec, 0xfded, 0xfdee, 0xfdef,
+ 0x0635, 0x0642, 0x0627, 0x0627, 0x0645, 0x0635, 0x0631, 0x0639,
+ 0x0648, 0x0635, 0x0635, 0x062c, 0x0631, 0xfdfd, 0xfdfe, 0xfdff,
+ };
+
+static const unsigned short gNormalizeTablefe00[] = {
+ /* U+fe00 */
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x002c, 0x3001, 0x3002, 0x003a, 0x003b, 0x0021, 0x003f, 0x3016,
+ 0x3017, 0x002e, 0xfe1a, 0xfe1b, 0xfe1c, 0xfe1d, 0xfe1e, 0xfe1f,
+ 0xfe20, 0xfe21, 0xfe22, 0xfe23, 0xfe24, 0xfe25, 0xfe26, 0xfe27,
+ 0xfe28, 0xfe29, 0xfe2a, 0xfe2b, 0xfe2c, 0xfe2d, 0xfe2e, 0xfe2f,
+ 0x002e, 0x2014, 0x2013, 0x005f, 0x005f, 0x0028, 0x0029, 0x007b,
+ 0x007d, 0x3014, 0x3015, 0x3010, 0x3011, 0x300a, 0x300b, 0x3008,
+ };
+
+static const unsigned short gNormalizeTablefe40[] = {
+ /* U+fe40 */
+ 0x3009, 0x300c, 0x300d, 0x300e, 0x300f, 0xfe45, 0xfe46, 0x005b,
+ 0x005d, 0x0020, 0x0020, 0x0020, 0x0020, 0x005f, 0x005f, 0x005f,
+ 0x002c, 0x3001, 0x002e, 0xfe53, 0x003b, 0x003a, 0x003f, 0x0021,
+ 0x2014, 0x0028, 0x0029, 0x007b, 0x007d, 0x3014, 0x3015, 0x0023,
+ 0x0026, 0x002a, 0x002b, 0x002d, 0x003c, 0x003e, 0x003d, 0xfe67,
+ 0x005c, 0x0024, 0x0025, 0x0040, 0xfe6c, 0xfe6d, 0xfe6e, 0xfe6f,
+ 0x0020, 0x0640, 0x0020, 0xfe73, 0x0020, 0xfe75, 0x0020, 0x0640,
+ 0x0020, 0x0640, 0x0020, 0x0640, 0x0020, 0x0640, 0x0020, 0x0640,
+ };
+
+static const unsigned short gNormalizeTablefe80[] = {
+ /* U+fe80 */
+ 0x0621, 0x0627, 0x0627, 0x0627, 0x0627, 0x0648, 0x0648, 0x0627,
+ 0x0627, 0x064a, 0x064a, 0x064a, 0x064a, 0x0627, 0x0627, 0x0628,
+ 0x0628, 0x0628, 0x0628, 0x0629, 0x0629, 0x062a, 0x062a, 0x062a,
+ 0x062a, 0x062b, 0x062b, 0x062b, 0x062b, 0x062c, 0x062c, 0x062c,
+ 0x062c, 0x062d, 0x062d, 0x062d, 0x062d, 0x062e, 0x062e, 0x062e,
+ 0x062e, 0x062f, 0x062f, 0x0630, 0x0630, 0x0631, 0x0631, 0x0632,
+ 0x0632, 0x0633, 0x0633, 0x0633, 0x0633, 0x0634, 0x0634, 0x0634,
+ 0x0634, 0x0635, 0x0635, 0x0635, 0x0635, 0x0636, 0x0636, 0x0636,
+ };
+
+static const unsigned short gNormalizeTablefec0[] = {
+ /* U+fec0 */
+ 0x0636, 0x0637, 0x0637, 0x0637, 0x0637, 0x0638, 0x0638, 0x0638,
+ 0x0638, 0x0639, 0x0639, 0x0639, 0x0639, 0x063a, 0x063a, 0x063a,
+ 0x063a, 0x0641, 0x0641, 0x0641, 0x0641, 0x0642, 0x0642, 0x0642,
+ 0x0642, 0x0643, 0x0643, 0x0643, 0x0643, 0x0644, 0x0644, 0x0644,
+ 0x0644, 0x0645, 0x0645, 0x0645, 0x0645, 0x0646, 0x0646, 0x0646,
+ 0x0646, 0x0647, 0x0647, 0x0647, 0x0647, 0x0648, 0x0648, 0x0649,
+ 0x0649, 0x064a, 0x064a, 0x064a, 0x064a, 0x0644, 0x0644, 0x0644,
+ 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0xfefd, 0xfefe, 0x0020,
+ };
+
+static const unsigned short gNormalizeTableff00[] = {
+ /* U+ff00 */
+ 0xff00, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ };
+
+static const unsigned short gNormalizeTableff40[] = {
+ /* U+ff40 */
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2985,
+ 0x2986, 0x3002, 0x300c, 0x300d, 0x3001, 0x30fb, 0x30f2, 0x30a1,
+ 0x30a3, 0x30a5, 0x30a7, 0x30a9, 0x30e3, 0x30e5, 0x30e7, 0x30c3,
+ 0x30fc, 0x30a2, 0x30a4, 0x30a6, 0x30a8, 0x30aa, 0x30ab, 0x30ad,
+ 0x30af, 0x30b1, 0x30b3, 0x30b5, 0x30b7, 0x30b9, 0x30bb, 0x30bd,
+ };
+
+static const unsigned short gNormalizeTableff80[] = {
+ /* U+ff80 */
+ 0x30bf, 0x30c1, 0x30c4, 0x30c6, 0x30c8, 0x30ca, 0x30cb, 0x30cc,
+ 0x30cd, 0x30ce, 0x30cf, 0x30d2, 0x30d5, 0x30d8, 0x30db, 0x30de,
+ 0x30df, 0x30e0, 0x30e1, 0x30e2, 0x30e4, 0x30e6, 0x30e8, 0x30e9,
+ 0x30ea, 0x30eb, 0x30ec, 0x30ed, 0x30ef, 0x30f3, 0x3099, 0x309a,
+ 0x0020, 0x1100, 0x1101, 0x11aa, 0x1102, 0x11ac, 0x11ad, 0x1103,
+ 0x1104, 0x1105, 0x11b0, 0x11b1, 0x11b2, 0x11b3, 0x11b4, 0x11b5,
+ 0x111a, 0x1106, 0x1107, 0x1108, 0x1121, 0x1109, 0x110a, 0x110b,
+ 0x110c, 0x110d, 0x110e, 0x110f, 0x1110, 0x1111, 0x1112, 0xffbf,
+ };
+
+static const unsigned short gNormalizeTableffc0[] = {
+ /* U+ffc0 */
+ 0xffc0, 0xffc1, 0x1161, 0x1162, 0x1163, 0x1164, 0x1165, 0x1166,
+ 0xffc8, 0xffc9, 0x1167, 0x1168, 0x1169, 0x116a, 0x116b, 0x116c,
+ 0xffd0, 0xffd1, 0x116d, 0x116e, 0x116f, 0x1170, 0x1171, 0x1172,
+ 0xffd8, 0xffd9, 0x1173, 0x1174, 0x1175, 0xffdd, 0xffde, 0xffdf,
+ 0x00a2, 0x00a3, 0x00ac, 0x0020, 0x00a6, 0x00a5, 0x20a9, 0xffe7,
+ 0x2502, 0x2190, 0x2191, 0x2192, 0x2193, 0x25a0, 0x25cb, 0xffef,
+ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+ 0x0020, 0xfff9, 0xfffa, 0xfffb, 0xfffc, 0xfffd, 0xfffe, 0xffff,
+ };
+
+static const unsigned short* gNormalizeTable[] = {
+ 0, gNormalizeTable0040, gNormalizeTable0080, gNormalizeTable00c0,
+ gNormalizeTable0100, gNormalizeTable0140, gNormalizeTable0180, gNormalizeTable01c0,
+ gNormalizeTable0200, gNormalizeTable0240, gNormalizeTable0280, gNormalizeTable02c0,
+ 0, gNormalizeTable0340, gNormalizeTable0380, gNormalizeTable03c0,
+ gNormalizeTable0400, gNormalizeTable0440, gNormalizeTable0480, gNormalizeTable04c0,
+ gNormalizeTable0500, gNormalizeTable0540, gNormalizeTable0580, 0,
+ gNormalizeTable0600, gNormalizeTable0640, 0, gNormalizeTable06c0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ gNormalizeTable0900, gNormalizeTable0940, 0, gNormalizeTable09c0,
+ gNormalizeTable0a00, gNormalizeTable0a40, 0, 0,
+ 0, gNormalizeTable0b40, gNormalizeTable0b80, gNormalizeTable0bc0,
+ 0, gNormalizeTable0c40, 0, gNormalizeTable0cc0,
+ 0, gNormalizeTable0d40, 0, gNormalizeTable0dc0,
+ gNormalizeTable0e00, 0, gNormalizeTable0e80, gNormalizeTable0ec0,
+ gNormalizeTable0f00, gNormalizeTable0f40, gNormalizeTable0f80, 0,
+ gNormalizeTable1000, 0, gNormalizeTable1080, gNormalizeTable10c0,
+ 0, gNormalizeTable1140, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, gNormalizeTable1780, 0,
+ gNormalizeTable1800, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ gNormalizeTable1b00, gNormalizeTable1b40, 0, 0,
+ 0, 0, 0, 0,
+ gNormalizeTable1d00, gNormalizeTable1d40, gNormalizeTable1d80, 0,
+ gNormalizeTable1e00, gNormalizeTable1e40, gNormalizeTable1e80, gNormalizeTable1ec0,
+ gNormalizeTable1f00, gNormalizeTable1f40, gNormalizeTable1f80, gNormalizeTable1fc0,
+ gNormalizeTable2000, gNormalizeTable2040, gNormalizeTable2080, 0,
+ gNormalizeTable2100, gNormalizeTable2140, gNormalizeTable2180, gNormalizeTable21c0,
+ gNormalizeTable2200, gNormalizeTable2240, gNormalizeTable2280, gNormalizeTable22c0,
+ gNormalizeTable2300, 0, 0, 0,
+ 0, gNormalizeTable2440, gNormalizeTable2480, gNormalizeTable24c0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ gNormalizeTable2a00, gNormalizeTable2a40, 0, gNormalizeTable2ac0,
+ 0, 0, 0, 0,
+ gNormalizeTable2c00, gNormalizeTable2c40, gNormalizeTable2c80, gNormalizeTable2cc0,
+ 0, gNormalizeTable2d40, 0, 0,
+ 0, 0, gNormalizeTable2e80, gNormalizeTable2ec0,
+ gNormalizeTable2f00, gNormalizeTable2f40, gNormalizeTable2f80, gNormalizeTable2fc0,
+ gNormalizeTable3000, gNormalizeTable3040, gNormalizeTable3080, gNormalizeTable30c0,
+ gNormalizeTable3100, gNormalizeTable3140, gNormalizeTable3180, 0,
+ gNormalizeTable3200, gNormalizeTable3240, gNormalizeTable3280, gNormalizeTable32c0,
+ gNormalizeTable3300, gNormalizeTable3340, gNormalizeTable3380, gNormalizeTable33c0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, gNormalizeTablea640, gNormalizeTablea680, 0,
+ gNormalizeTablea700, gNormalizeTablea740, gNormalizeTablea780, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ gNormalizeTablef900, gNormalizeTablef940, gNormalizeTablef980, gNormalizeTablef9c0,
+ gNormalizeTablefa00, gNormalizeTablefa40, gNormalizeTablefa80, gNormalizeTablefac0,
+ gNormalizeTablefb00, gNormalizeTablefb40, gNormalizeTablefb80, gNormalizeTablefbc0,
+ gNormalizeTablefc00, gNormalizeTablefc40, gNormalizeTablefc80, gNormalizeTablefcc0,
+ gNormalizeTablefd00, gNormalizeTablefd40, gNormalizeTablefd80, gNormalizeTablefdc0,
+ gNormalizeTablefe00, gNormalizeTablefe40, gNormalizeTablefe80, gNormalizeTablefec0,
+ gNormalizeTableff00, gNormalizeTableff40, gNormalizeTableff80, gNormalizeTableffc0,
+};
+
+unsigned int normalize_character(const unsigned int c)
+{
+ if (c >= 0x10000 || !gNormalizeTable[c >> 6])
+ return c;
+ return gNormalizeTable[c >> 6][c & 0x3f];
+}
+
diff --git a/mailnews/extensions/fts3/src/README.mozilla b/mailnews/extensions/fts3/src/README.mozilla
new file mode 100644
index 000000000..0bfe7deb3
--- /dev/null
+++ b/mailnews/extensions/fts3/src/README.mozilla
@@ -0,0 +1,3 @@
+fts3_porter.c code is from SQLite3.
+
+This customized tokenizer "mozporter" by Mozilla supports CJK indexing using bi-gram. So you have to use bi-gram search string if you wanto to search CJK character.
diff --git a/mailnews/extensions/fts3/src/fts3_porter.c b/mailnews/extensions/fts3/src/fts3_porter.c
new file mode 100644
index 000000000..2276244c1
--- /dev/null
+++ b/mailnews/extensions/fts3/src/fts3_porter.c
@@ -0,0 +1,1150 @@
+/*
+** 2006 September 30
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Implementation of the full-text-search tokenizer that implements
+** a Porter stemmer.
+**
+*/
+
+/*
+ * This file is based on the SQLite FTS3 Porter Stemmer implementation.
+ *
+ * This is an attempt to provide some level of full-text search to users of
+ * Thunderbird who use languages that are not space/punctuation delimited.
+ * This is accomplished by performing bi-gram indexing of characters fall
+ * into the unicode space occupied by character sets used in such languages.
+ *
+ * Bi-gram indexing means that given the string "12345" we would index the
+ * pairs "12", "23", "34", and "45" (with position information). We do this
+ * because we are not sure where the word/semantic boundaries are in that
+ * string. Then, when a user searches for "234" the FTS3 engine tokenizes the
+ * search query into "23" and "34". Using special phrase-logic FTS3 requires
+ * the matches to have the tokens "23" and "34" adjacent to each other and in
+ * that order. In theory if the user searched for "2345" we we could just
+ * search for "23 NEAR/2 34". Unfortunately, NEAR does not imply ordering,
+ * so even though that would be more efficient, we would lose correctness
+ * and cannot do it.
+ *
+ * The efficiency and usability of bi-gram search assumes that the character
+ * space is large enough and actually observed bi-grams sufficiently
+ * distributed throughout the potential space so that the search bi-grams
+ * generated when the user issues a query find a 'reasonable' number of
+ * documents for each bi-gram match.
+ *
+ * Mozilla contributors:
+ * Makoto Kato <m_kato@ga2.so-net.ne.jp>
+ * Andrew Sutherland <asutherland@asutherland.org>
+ */
+
+/*
+** The code in this file is only compiled if:
+**
+** * The FTS3 module is being built as an extension
+** (in which case SQLITE_CORE is not defined), or
+**
+** * The FTS3 module is being built into the core of
+** SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
+*/
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
+
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "fts3_tokenizer.h"
+
+/* need some defined to compile without sqlite3 code */
+
+#define sqlite3_malloc malloc
+#define sqlite3_free free
+#define sqlite3_realloc realloc
+
+static const unsigned char sqlite3Utf8Trans1[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
+};
+
+typedef unsigned char u8;
+
+/**
+ * SQLite helper macro from sqlite3.c (really utf.c) to encode a unicode
+ * character into utf8.
+ *
+ * @param zOut A pointer to the current write position that is updated by
+ * the routine. At entry it should point to one-past the last valid
+ * encoded byte. The same holds true at exit.
+ * @param c The character to encode; this should be an unsigned int.
+ */
+#define WRITE_UTF8(zOut, c) { \
+ if( c<0x0080 ){ \
+ *zOut++ = (u8)(c&0xff); \
+ } \
+ else if( c<0x0800 ){ \
+ *zOut++ = 0xC0 + (u8)((c>>6) & 0x1F); \
+ *zOut++ = 0x80 + (u8)(c & 0x3F); \
+ } \
+ else if( c<0x10000 ){ \
+ *zOut++ = 0xE0 + (u8)((c>>12) & 0x0F); \
+ *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); \
+ *zOut++ = 0x80 + (u8)(c & 0x3F); \
+ }else{ \
+ *zOut++ = 0xf0 + (u8)((c>>18) & 0x07); \
+ *zOut++ = 0x80 + (u8)((c>>12) & 0x3F); \
+ *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); \
+ *zOut++ = 0x80 + (u8)(c & 0x3F); \
+ } \
+}
+
+/**
+ * Fudge factor to avoid buffer overwrites when WRITE_UTF8 is involved.
+ *
+ * Our normalization table includes entries that may result in a larger
+ * utf-8 encoding. Namely, 023a maps to 2c65. This is a growth from 2 bytes
+ * as utf-8 encoded to 3 bytes. This is currently the only transition possible
+ * because 1-byte encodings are known to stay 1-byte and our normalization
+ * table is 16-bit and so can't generate a 4-byte encoded output.
+ *
+ * For simplicity, we just multiple by 2 which covers the current case and
+ * potential growth for 2-byte to 4-byte growth. We can afford to do this
+ * because we're not talking about a lot of memory here as a rule.
+ */
+#define MAX_UTF8_GROWTH_FACTOR 2
+
+/**
+ * Helper from sqlite3.c to read a single UTF8 character.
+ *
+ * The clever bit with multi-byte reading is that you keep going until you find
+ * a byte whose top bits are not '10'. A single-byte UTF8 character will have
+ * '00' or '01', and a multi-byte UTF8 character must start with '11'.
+ *
+ * In the event of illegal UTF-8 this macro may read an arbitrary number of
+ * characters but will never read past zTerm. The resulting character value
+ * of illegal UTF-8 can be anything, although efforts are made to return the
+ * illegal character (0xfffd) for UTF-16 surrogates.
+ *
+ * @param zIn A pointer to the current position that is updated by the routine,
+ * pointing at the start of the next character when the routine returns.
+ * @param zTerm A pointer one past the end of the buffer.
+ * @param c The 'unsigned int' to hold the resulting character value. Do not
+ * use a short or a char.
+ */
+#define READ_UTF8(zIn, zTerm, c) { \
+ c = *(zIn++); \
+ if( c>=0xc0 ){ \
+ c = sqlite3Utf8Trans1[c-0xc0]; \
+ while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \
+ c = (c<<6) + (0x3f & *(zIn++)); \
+ } \
+ if( c<0x80 \
+ || (c&0xFFFFF800)==0xD800 \
+ || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \
+ } \
+}
+
+/* end of compatible block to complie codes */
+
+/*
+** Class derived from sqlite3_tokenizer
+*/
+typedef struct porter_tokenizer {
+ sqlite3_tokenizer base; /* Base class */
+} porter_tokenizer;
+
+/*
+** Class derived from sqlit3_tokenizer_cursor
+*/
+typedef struct porter_tokenizer_cursor {
+ sqlite3_tokenizer_cursor base;
+ const char *zInput; /* input we are tokenizing */
+ int nInput; /* size of the input */
+ int iOffset; /* current position in zInput */
+ int iToken; /* index of next token to be returned */
+ unsigned char *zToken; /* storage for current token */
+ int nAllocated; /* space allocated to zToken buffer */
+ /**
+ * Store the offset of the second character in the bi-gram pair that we just
+ * emitted so that we can consider it being the first character in a bi-gram
+ * pair.
+ * The value 0 indicates that there is no previous such character. This is
+ * an acceptable sentinel value because the 0th offset can never be the
+ * offset of the second in a bi-gram pair.
+ *
+ * For example, let us say we are tokenizing a string of 4 CJK characters
+ * represented by the byte-string "11223344" where each repeated digit
+ * indicates 2-bytes of storage used to encode the character in UTF-8.
+ * (It actually takes 3, btw.) Then on the passes to emit each token,
+ * the iOffset and iPrevGigramOffset values at entry will be:
+ *
+ * 1122: iOffset = 0, iPrevBigramOffset = 0
+ * 2233: iOffset = 4, iPrevBigramOffset = 2
+ * 3344: iOffset = 6, iPrevBigramOffset = 4
+ * (nothing will be emitted): iOffset = 8, iPrevBigramOffset = 6
+ */
+ int iPrevBigramOffset; /* previous result was bi-gram */
+} porter_tokenizer_cursor;
+
+
+/* Forward declaration */
+static const sqlite3_tokenizer_module porterTokenizerModule;
+
+/* from normalize.c */
+extern unsigned int normalize_character(const unsigned int c);
+
+/*
+** Create a new tokenizer instance.
+*/
+static int porterCreate(
+ int argc, const char * const *argv,
+ sqlite3_tokenizer **ppTokenizer
+){
+ porter_tokenizer *t;
+ t = (porter_tokenizer *) sqlite3_malloc(sizeof(*t));
+ if( t==NULL ) return SQLITE_NOMEM;
+ memset(t, 0, sizeof(*t));
+ *ppTokenizer = &t->base;
+ return SQLITE_OK;
+}
+
+/*
+** Destroy a tokenizer
+*/
+static int porterDestroy(sqlite3_tokenizer *pTokenizer){
+ sqlite3_free(pTokenizer);
+ return SQLITE_OK;
+}
+
+/*
+** Prepare to begin tokenizing a particular string. The input
+** string to be tokenized is zInput[0..nInput-1]. A cursor
+** used to incrementally tokenize this string is returned in
+** *ppCursor.
+*/
+static int porterOpen(
+ sqlite3_tokenizer *pTokenizer, /* The tokenizer */
+ const char *zInput, int nInput, /* String to be tokenized */
+ sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */
+){
+ porter_tokenizer_cursor *c;
+
+ c = (porter_tokenizer_cursor *) sqlite3_malloc(sizeof(*c));
+ if( c==NULL ) return SQLITE_NOMEM;
+
+ c->zInput = zInput;
+ if( zInput==0 ){
+ c->nInput = 0;
+ }else if( nInput<0 ){
+ c->nInput = (int)strlen(zInput);
+ }else{
+ c->nInput = nInput;
+ }
+ c->iOffset = 0; /* start tokenizing at the beginning */
+ c->iToken = 0;
+ c->zToken = NULL; /* no space allocated, yet. */
+ c->nAllocated = 0;
+ c->iPrevBigramOffset = 0;
+
+ *ppCursor = &c->base;
+ return SQLITE_OK;
+}
+
+/*
+** Close a tokenization cursor previously opened by a call to
+** porterOpen() above.
+*/
+static int porterClose(sqlite3_tokenizer_cursor *pCursor){
+ porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor;
+ sqlite3_free(c->zToken);
+ sqlite3_free(c);
+ return SQLITE_OK;
+}
+/*
+** Vowel or consonant
+*/
+static const char cType[] = {
+ 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
+ 1, 1, 1, 2, 1
+};
+
+/*
+** isConsonant() and isVowel() determine if their first character in
+** the string they point to is a consonant or a vowel, according
+** to Porter ruls.
+**
+** A consonate is any letter other than 'a', 'e', 'i', 'o', or 'u'.
+** 'Y' is a consonant unless it follows another consonant,
+** in which case it is a vowel.
+**
+** In these routine, the letters are in reverse order. So the 'y' rule
+** is that 'y' is a consonant unless it is followed by another
+** consonent.
+*/
+static int isVowel(const char*);
+static int isConsonant(const char *z){
+ int j;
+ char x = *z;
+ if( x==0 ) return 0;
+ assert( x>='a' && x<='z' );
+ j = cType[x-'a'];
+ if( j<2 ) return j;
+ return z[1]==0 || isVowel(z + 1);
+}
+static int isVowel(const char *z){
+ int j;
+ char x = *z;
+ if( x==0 ) return 0;
+ assert( x>='a' && x<='z' );
+ j = cType[x-'a'];
+ if( j<2 ) return 1-j;
+ return isConsonant(z + 1);
+}
+
+/*
+** Let any sequence of one or more vowels be represented by V and let
+** C be sequence of one or more consonants. Then every word can be
+** represented as:
+**
+** [C] (VC){m} [V]
+**
+** In prose: A word is an optional consonant followed by zero or
+** vowel-consonant pairs followed by an optional vowel. "m" is the
+** number of vowel consonant pairs. This routine computes the value
+** of m for the first i bytes of a word.
+**
+** Return true if the m-value for z is 1 or more. In other words,
+** return true if z contains at least one vowel that is followed
+** by a consonant.
+**
+** In this routine z[] is in reverse order. So we are really looking
+** for an instance of of a consonant followed by a vowel.
+*/
+static int m_gt_0(const char *z){
+ while( isVowel(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isConsonant(z) ){ z++; }
+ return *z!=0;
+}
+
+/* Like mgt0 above except we are looking for a value of m which is
+** exactly 1
+*/
+static int m_eq_1(const char *z){
+ while( isVowel(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isConsonant(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isVowel(z) ){ z++; }
+ if( *z==0 ) return 1;
+ while( isConsonant(z) ){ z++; }
+ return *z==0;
+}
+
+/* Like mgt0 above except we are looking for a value of m>1 instead
+** or m>0
+*/
+static int m_gt_1(const char *z){
+ while( isVowel(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isConsonant(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isVowel(z) ){ z++; }
+ if( *z==0 ) return 0;
+ while( isConsonant(z) ){ z++; }
+ return *z!=0;
+}
+
+/*
+** Return TRUE if there is a vowel anywhere within z[0..n-1]
+*/
+static int hasVowel(const char *z){
+ while( isConsonant(z) ){ z++; }
+ return *z!=0;
+}
+
+/*
+** Return TRUE if the word ends in a double consonant.
+**
+** The text is reversed here. So we are really looking at
+** the first two characters of z[].
+*/
+static int doubleConsonant(const char *z){
+ return isConsonant(z) && z[0]==z[1] && isConsonant(z+1);
+}
+
+/*
+** Return TRUE if the word ends with three letters which
+** are consonant-vowel-consonent and where the final consonant
+** is not 'w', 'x', or 'y'.
+**
+** The word is reversed here. So we are really checking the
+** first three letters and the first one cannot be in [wxy].
+*/
+static int star_oh(const char *z){
+ return
+ z[0]!=0 && isConsonant(z) &&
+ z[0]!='w' && z[0]!='x' && z[0]!='y' &&
+ z[1]!=0 && isVowel(z+1) &&
+ z[2]!=0 && isConsonant(z+2);
+}
+
+/*
+** If the word ends with zFrom and xCond() is true for the stem
+** of the word that preceeds the zFrom ending, then change the
+** ending to zTo.
+**
+** The input word *pz and zFrom are both in reverse order. zTo
+** is in normal order.
+**
+** Return TRUE if zFrom matches. Return FALSE if zFrom does not
+** match. Not that TRUE is returned even if xCond() fails and
+** no substitution occurs.
+*/
+static int stem(
+ char **pz, /* The word being stemmed (Reversed) */
+ const char *zFrom, /* If the ending matches this... (Reversed) */
+ const char *zTo, /* ... change the ending to this (not reversed) */
+ int (*xCond)(const char*) /* Condition that must be true */
+){
+ char *z = *pz;
+ while( *zFrom && *zFrom==*z ){ z++; zFrom++; }
+ if( *zFrom!=0 ) return 0;
+ if( xCond && !xCond(z) ) return 1;
+ while( *zTo ){
+ *(--z) = *(zTo++);
+ }
+ *pz = z;
+ return 1;
+}
+
+/**
+ * Voiced sound mark is only on Japanese. It is like accent. It combines with
+ * previous character. Example, "サ" (Katakana) with "゛" (voiced sound mark) is
+ * "ザ". Although full-width character mapping has combined character like "ザ",
+ * there is no combined character on half-width Katanaka character mapping.
+ */
+static int isVoicedSoundMark(const unsigned int c)
+{
+ if (c == 0xff9e || c == 0xff9f || c == 0x3099 || c == 0x309a)
+ return 1;
+ return 0;
+}
+
+/**
+ * How many unicode characters to take from the front and back of a term in
+ * |copy_stemmer|.
+ */
+#define COPY_STEMMER_COPY_HALF_LEN 10
+
+/**
+ * Normalizing but non-stemming term copying.
+ *
+ * The original function would take 10 bytes from the front and 10 bytes from
+ * the back if there were no digits in the string and it was more than 20
+ * bytes long. If there were digits involved that would decrease to 3 bytes
+ * from the front and 3 from the back. This would potentially corrupt utf-8
+ * encoded characters, which is fine from the perspective of the FTS3 logic.
+ *
+ * In our revised form we now operate on a unicode character basis rather than
+ * a byte basis. Additionally we use the same length limit even if there are
+ * digits involved because it's not clear digit token-space reduction is saving
+ * us from anything and could be hurting. Specifically, if no one is ever
+ * going to search on things with digits, then we should just remove them.
+ * Right now, the space reduction is going to increase false positives when
+ * people do search on them and increase the number of collisions sufficiently
+ * to make it really expensive. The caveat is there will be some increase in
+ * index size which could be meaningful if people are receiving lots of emails
+ * full of distinct numbers.
+ *
+ * In order to do the copy-from-the-front and copy-from-the-back trick, once
+ * we reach N characters in, we set zFrontEnd to the current value of zOut
+ * (which represents the termination of the first part of the result string)
+ * and set zBackStart to the value of zOutStart. We then advanced zBackStart
+ * along a character at a time as we write more characters. Once we have
+ * traversed the entire string, if zBackStart > zFrontEnd, then we know
+ * the string should be shrunk using the characters in the two ranges.
+ *
+ * (It would be faster to scan from the back with specialized logic but that
+ * particular logic seems easy to screw up and we don't have unit tests in here
+ * to the extent required.)
+ *
+ * @param zIn Input string to normalize and potentially shrink.
+ * @param nBytesIn The number of bytes in zIn, distinct from the number of
+ * unicode characters encoded in zIn.
+ * @param zOut The string to write our output into. This must have at least
+ * nBytesIn * MAX_UTF8_GROWTH_FACTOR in order to compensate for
+ * normalization that results in a larger utf-8 encoding.
+ * @param pnBytesOut Integer to write the number of bytes in zOut into.
+ */
+static void copy_stemmer(const unsigned char *zIn, const int nBytesIn,
+ unsigned char *zOut, int *pnBytesOut){
+ const unsigned char *zInTerm = zIn + nBytesIn;
+ unsigned char *zOutStart = zOut;
+ unsigned int c;
+ unsigned int charCount = 0;
+ unsigned char *zFrontEnd = NULL, *zBackStart = NULL;
+ unsigned int trashC;
+
+ /* copy normalized character */
+ while (zIn < zInTerm) {
+ READ_UTF8(zIn, zInTerm, c);
+ c = normalize_character(c);
+
+ /* ignore voiced/semi-voiced sound mark */
+ if (!isVoicedSoundMark(c)) {
+ /* advance one non-voiced sound mark character. */
+ if (zBackStart)
+ READ_UTF8(zBackStart, zOut, trashC);
+
+ WRITE_UTF8(zOut, c);
+ charCount++;
+ if (charCount == COPY_STEMMER_COPY_HALF_LEN) {
+ zFrontEnd = zOut;
+ zBackStart = zOutStart;
+ }
+ }
+ }
+
+ /* if we need to shrink the string, transplant the back bytes */
+ if (zBackStart > zFrontEnd) { /* this handles when both are null too */
+ size_t backBytes = zOut - zBackStart;
+ memmove(zFrontEnd, zBackStart, backBytes);
+ zOut = zFrontEnd + backBytes;
+ }
+ *zOut = 0;
+ *pnBytesOut = zOut - zOutStart;
+}
+
+
+/*
+** Stem the input word zIn[0..nIn-1]. Store the output in zOut.
+** zOut is at least big enough to hold nIn bytes. Write the actual
+** size of the output word (exclusive of the '\0' terminator) into *pnOut.
+**
+** Any upper-case characters in the US-ASCII character set ([A-Z])
+** are converted to lower case. Upper-case UTF characters are
+** unchanged.
+**
+** Words that are longer than about 20 bytes are stemmed by retaining
+** a few bytes from the beginning and the end of the word. If the
+** word contains digits, 3 bytes are taken from the beginning and
+** 3 bytes from the end. For long words without digits, 10 bytes
+** are taken from each end. US-ASCII case folding still applies.
+**
+** If the input word contains not digits but does characters not
+** in [a-zA-Z] then no stemming is attempted and this routine just
+** copies the input into the input into the output with US-ASCII
+** case folding.
+**
+** Stemming never increases the length of the word. So there is
+** no chance of overflowing the zOut buffer.
+*/
+static void porter_stemmer(
+ const unsigned char *zIn,
+ unsigned int nIn,
+ unsigned char *zOut,
+ int *pnOut
+){
+ unsigned int i, j, c;
+ char zReverse[28];
+ char *z, *z2;
+ const unsigned char *zTerm = zIn + nIn;
+ const unsigned char *zTmp = zIn;
+
+ if( nIn<3 || nIn>=sizeof(zReverse)-7 ){
+ /* The word is too big or too small for the porter stemmer.
+ ** Fallback to the copy stemmer */
+ copy_stemmer(zIn, nIn, zOut, pnOut);
+ return;
+ }
+ for (j = sizeof(zReverse) - 6; zTmp < zTerm; j--) {
+ READ_UTF8(zTmp, zTerm, c);
+ c = normalize_character(c);
+ if( c>='a' && c<='z' ){
+ zReverse[j] = c;
+ }else{
+ /* The use of a character not in [a-zA-Z] means that we fallback
+ ** to the copy stemmer */
+ copy_stemmer(zIn, nIn, zOut, pnOut);
+ return;
+ }
+ }
+ memset(&zReverse[sizeof(zReverse)-5], 0, 5);
+ z = &zReverse[j+1];
+
+
+ /* Step 1a */
+ if( z[0]=='s' ){
+ if(
+ !stem(&z, "sess", "ss", 0) &&
+ !stem(&z, "sei", "i", 0) &&
+ !stem(&z, "ss", "ss", 0)
+ ){
+ z++;
+ }
+ }
+
+ /* Step 1b */
+ z2 = z;
+ if( stem(&z, "dee", "ee", m_gt_0) ){
+ /* Do nothing. The work was all in the test */
+ }else if(
+ (stem(&z, "gni", "", hasVowel) || stem(&z, "de", "", hasVowel))
+ && z!=z2
+ ){
+ if( stem(&z, "ta", "ate", 0) ||
+ stem(&z, "lb", "ble", 0) ||
+ stem(&z, "zi", "ize", 0) ){
+ /* Do nothing. The work was all in the test */
+ }else if( doubleConsonant(z) && (*z!='l' && *z!='s' && *z!='z') ){
+ z++;
+ }else if( m_eq_1(z) && star_oh(z) ){
+ *(--z) = 'e';
+ }
+ }
+
+ /* Step 1c */
+ if( z[0]=='y' && hasVowel(z+1) ){
+ z[0] = 'i';
+ }
+
+ /* Step 2 */
+ switch( z[1] ){
+ case 'a':
+ (void) (stem(&z, "lanoita", "ate", m_gt_0) ||
+ stem(&z, "lanoit", "tion", m_gt_0));
+ break;
+ case 'c':
+ (void) (stem(&z, "icne", "ence", m_gt_0) ||
+ stem(&z, "icna", "ance", m_gt_0));
+ break;
+ case 'e':
+ (void) (stem(&z, "rezi", "ize", m_gt_0));
+ break;
+ case 'g':
+ (void) (stem(&z, "igol", "log", m_gt_0));
+ break;
+ case 'l':
+ (void) (stem(&z, "ilb", "ble", m_gt_0) ||
+ stem(&z, "illa", "al", m_gt_0) ||
+ stem(&z, "iltne", "ent", m_gt_0) ||
+ stem(&z, "ile", "e", m_gt_0) ||
+ stem(&z, "ilsuo", "ous", m_gt_0));
+ break;
+ case 'o':
+ (void) (stem(&z, "noitazi", "ize", m_gt_0) ||
+ stem(&z, "noita", "ate", m_gt_0) ||
+ stem(&z, "rota", "ate", m_gt_0));
+ break;
+ case 's':
+ (void) (stem(&z, "msila", "al", m_gt_0) ||
+ stem(&z, "ssenevi", "ive", m_gt_0) ||
+ stem(&z, "ssenluf", "ful", m_gt_0) ||
+ stem(&z, "ssensuo", "ous", m_gt_0));
+ break;
+ case 't':
+ (void) (stem(&z, "itila", "al", m_gt_0) ||
+ stem(&z, "itivi", "ive", m_gt_0) ||
+ stem(&z, "itilib", "ble", m_gt_0));
+ break;
+ }
+
+ /* Step 3 */
+ switch( z[0] ){
+ case 'e':
+ (void) (stem(&z, "etaci", "ic", m_gt_0) ||
+ stem(&z, "evita", "", m_gt_0) ||
+ stem(&z, "ezila", "al", m_gt_0));
+ break;
+ case 'i':
+ (void) (stem(&z, "itici", "ic", m_gt_0));
+ break;
+ case 'l':
+ (void) (stem(&z, "laci", "ic", m_gt_0) ||
+ stem(&z, "luf", "", m_gt_0));
+ break;
+ case 's':
+ (void) (stem(&z, "ssen", "", m_gt_0));
+ break;
+ }
+
+ /* Step 4 */
+ switch( z[1] ){
+ case 'a':
+ if( z[0]=='l' && m_gt_1(z+2) ){
+ z += 2;
+ }
+ break;
+ case 'c':
+ if( z[0]=='e' && z[2]=='n' && (z[3]=='a' || z[3]=='e') && m_gt_1(z+4) ){
+ z += 4;
+ }
+ break;
+ case 'e':
+ if( z[0]=='r' && m_gt_1(z+2) ){
+ z += 2;
+ }
+ break;
+ case 'i':
+ if( z[0]=='c' && m_gt_1(z+2) ){
+ z += 2;
+ }
+ break;
+ case 'l':
+ if( z[0]=='e' && z[2]=='b' && (z[3]=='a' || z[3]=='i') && m_gt_1(z+4) ){
+ z += 4;
+ }
+ break;
+ case 'n':
+ if( z[0]=='t' ){
+ if( z[2]=='a' ){
+ if( m_gt_1(z+3) ){
+ z += 3;
+ }
+ }else if( z[2]=='e' ){
+ (void) (stem(&z, "tneme", "", m_gt_1) ||
+ stem(&z, "tnem", "", m_gt_1) ||
+ stem(&z, "tne", "", m_gt_1));
+ }
+ }
+ break;
+ case 'o':
+ if( z[0]=='u' ){
+ if( m_gt_1(z+2) ){
+ z += 2;
+ }
+ }else if( z[3]=='s' || z[3]=='t' ){
+ (void) (stem(&z, "noi", "", m_gt_1));
+ }
+ break;
+ case 's':
+ if( z[0]=='m' && z[2]=='i' && m_gt_1(z+3) ){
+ z += 3;
+ }
+ break;
+ case 't':
+ (void) (stem(&z, "eta", "", m_gt_1) ||
+ stem(&z, "iti", "", m_gt_1));
+ break;
+ case 'u':
+ if( z[0]=='s' && z[2]=='o' && m_gt_1(z+3) ){
+ z += 3;
+ }
+ break;
+ case 'v':
+ case 'z':
+ if( z[0]=='e' && z[2]=='i' && m_gt_1(z+3) ){
+ z += 3;
+ }
+ break;
+ }
+
+ /* Step 5a */
+ if( z[0]=='e' ){
+ if( m_gt_1(z+1) ){
+ z++;
+ }else if( m_eq_1(z+1) && !star_oh(z+1) ){
+ z++;
+ }
+ }
+
+ /* Step 5b */
+ if( m_gt_1(z) && z[0]=='l' && z[1]=='l' ){
+ z++;
+ }
+
+ /* z[] is now the stemmed word in reverse order. Flip it back
+ ** around into forward order and return.
+ */
+ *pnOut = i = strlen(z);
+ zOut[i] = 0;
+ while( *z ){
+ zOut[--i] = *(z++);
+ }
+}
+
+/**
+ * Indicate whether characters in the 0x30 - 0x7f region can be part of a token.
+ * Letters and numbers can; punctuation (and 'del') can't.
+ */
+static const char porterIdChar[] = {
+/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */
+};
+
+/**
+ * Test whether a character is a (non-ascii) space character or not. isDelim
+ * uses the existing porter stemmer logic for anything in the ASCII (< 0x80)
+ * space which covers 0x20.
+ *
+ * 0x2000-0x206F is the general punctuation table. 0x2000 - 0x200b are spaces.
+ * The spaces 0x2000 - 0x200a are all defined as roughly equivalent to a
+ * standard 0x20 space. 0x200b is a "zero width space" (ZWSP) and not like an
+ * 0x20 space. 0x202f is a narrow no-break space and roughly equivalent to an
+ * 0x20 space. 0x205f is a "medium mathematical space" and defined as roughly
+ * equivalent to an 0x20 space.
+ */
+#define IS_UNI_SPACE(x) (((x)>=0x2000&&(x)<=0x200a) || (x)==0x202f || (x)==0x205f)
+/**
+ * What we are checking for:
+ * - 0x3001: Ideographic comma (-> 0x2c ',')
+ * - 0x3002: Ideographic full stop (-> 0x2e '.')
+ * - 0xff0c: fullwidth comma (~ wide 0x2c ',')
+ * - 0xff0e: fullwidth full stop (~ wide 0x2e '.')
+ * - 0xff61: halfwidth ideographic full stop (~ narrow 0x3002)
+ * - 0xff64: halfwidth ideographic comma (~ narrow 0x3001)
+ *
+ * It is possible we should be treating other things as delimiters!
+ */
+#define IS_JA_DELIM(x) (((x)==0x3001)||((x)==0xFF64)||((x)==0xFF0E)||((x)==0x3002)||((x)==0xFF61)||((x)==0xFF0C))
+
+/**
+ * The previous character was a delimeter (which includes the start of the
+ * string).
+ */
+#define BIGRAM_RESET 0
+/**
+ * The previous character was a CJK character and we have only seen one of them.
+ * If we had seen more than one in a row it would be the BIGRAM_USE state.
+ */
+#define BIGRAM_UNKNOWN 1
+/**
+ * We have seen two or more CJK characters in a row.
+ */
+#define BIGRAM_USE 2
+/**
+ * The previous character was ASCII or something in the unicode general scripts
+ * area that we do not believe is a delimeter. We call it 'alpha' as in
+ * alphabetic/alphanumeric and something that should be tokenized based on
+ * delimiters rather than on a bi-gram basis.
+ */
+#define BIGRAM_ALPHA 3
+
+static int isDelim(
+ const unsigned char *zCur, /* IN: current pointer of token */
+ const unsigned char *zTerm, /* IN: one character beyond end of token */
+ int *len, /* OUT: analyzed bytes in this token */
+ int *state /* IN/OUT: analyze state */
+){
+ const unsigned char *zIn = zCur;
+ unsigned int c;
+ int delim;
+
+ /* get the unicode character to analyze */
+ READ_UTF8(zIn, zTerm, c);
+ c = normalize_character(c);
+ *len = zIn - zCur;
+
+ /* ASCII character range has rule */
+ if( c < 0x80 ){
+ // This is original porter stemmer isDelim logic.
+ // 0x0 - 0x1f are all control characters, 0x20 is space, 0x21-0x2f are
+ // punctuation.
+ delim = (c < 0x30 || !porterIdChar[c - 0x30]);
+ // cases: "&a", "&."
+ if (*state == BIGRAM_USE || *state == BIGRAM_UNKNOWN ){
+ /* previous maybe CJK and current is ascii */
+ *state = BIGRAM_ALPHA; /*ascii*/
+ delim = 1; /* must break */
+ } else if (delim == 1) {
+ // cases: "a.", ".."
+ /* this is delimiter character */
+ *state = BIGRAM_RESET; /*reset*/
+ } else {
+ // cases: "aa", ".a"
+ *state = BIGRAM_ALPHA; /*ascii*/
+ }
+ return delim;
+ }
+
+ // (at this point we must be a non-ASCII character)
+
+ /* voiced/semi-voiced sound mark is ignore */
+ if (isVoicedSoundMark(c) && *state != BIGRAM_ALPHA) {
+ /* ignore this because it is combined with previous char */
+ return 0;
+ }
+
+ /* this isn't CJK range, so return as no delim */
+ // Anything less than 0x2000 (except to U+0E00-U+0EFF and U+1780-U+17FF)
+ // is the general scripts area and should not be bi-gram indexed.
+ // 0xa000 - 0a4cf is the Yi area. It is apparently a phonetic language whose
+ // usage does not appear to have simple delimeter rules, so we're leaving it
+ // as bigram processed. This is a guess, if you know better, let us know.
+ // (We previously bailed on this range too.)
+ // Addition, U+0E00-U+0E7F is Thai, U+0E80-U+0EFF is Laos,
+ // and U+1780-U+17FF is Khmer. It is no easy way to break each word.
+ // So these should use bi-gram too.
+ // cases: "aa", ".a", "&a"
+ if (c < 0xe00 ||
+ (c >= 0xf00 && c < 0x1780) ||
+ (c >= 0x1800 && c < 0x2000)) {
+ *state = BIGRAM_ALPHA; /* not really ASCII but same idea; tokenize it */
+ return 0;
+ }
+
+ // (at this point we must be a bi-grammable char or delimiter)
+
+ /* this is space character or delim character */
+ // cases: "a.", "..", "&."
+ if( IS_UNI_SPACE(c) || IS_JA_DELIM(c) ){
+ *state = BIGRAM_RESET; /* reset */
+ return 1; /* it actually is a delimiter; report as such */
+ }
+
+ // (at this point we must be a bi-grammable char)
+
+ // cases: "a&"
+ if( *state==BIGRAM_ALPHA ){
+ /* Previous is ascii and current maybe CJK */
+ *state = BIGRAM_UNKNOWN; /* mark as unknown */
+ return 1; /* break to emit the ASCII token*/
+ }
+
+ /* We have no rule for CJK!. use bi-gram */
+ // cases: "&&"
+ if( *state==BIGRAM_UNKNOWN || *state==BIGRAM_USE ){
+ /* previous state is unknown. mark as bi-gram */
+ *state = BIGRAM_USE;
+ return 1; /* break to emit the digram */
+ }
+
+ // cases: ".&" (*state == BIGRAM_RESET)
+ *state = BIGRAM_UNKNOWN; /* mark as unknown */
+ return 0; /* no need to break; nothing to emit */
+}
+
+/**
+ * Generate a new token. There are basically three types of token we can
+ * generate:
+ * - A porter stemmed token. This is a word entirely comprised of ASCII
+ * characters. We run the porter stemmer algorithm against the word.
+ * Because we have no way to know what is and is not an English word
+ * (the only language for which the porter stemmer was designed), this
+ * could theoretically map multiple words that are not variations of the
+ * same word down to the same root, resulting in potentially unexpected
+ * result inclusions in the search results. We accept this result because
+ * there's not a lot we can do about it and false positives are much
+ * better than false negatives.
+ * - A copied token; case/accent-folded but not stemmed. We call the porter
+ * stemmer for all non-CJK cases and it diverts to the copy stemmer if it
+ * sees any non-ASCII characters (after folding) or if the string is too
+ * long. The copy stemmer will shrink the string if it is deemed too long.
+ * - A bi-gram token; two CJK-ish characters. For query reasons we generate a
+ * series of overlapping bi-grams. (We can't require the user to start their
+ * search based on the arbitrary context of the indexed documents.)
+ *
+ * It may be useful to think of this function as operating at the points between
+ * characters. While we are considering the 'current' character (the one after
+ * the 'point'), we are also interested in the 'previous' character (the one
+ * preceding the point).
+ * At any 'point', there are a number of possible situations which I will
+ * illustrate with pairs of characters. 'a' means alphanumeric ASCII or a
+ * non-ASCII character that is not bi-grammable or a delimeter, '.'
+ * means a delimiter (space or punctuation), '&' means a bi-grammable
+ * character.
+ * - aa: We are in the midst of a token. State remains BIGRAM_ALPHA.
+ * - a.: We will generate a porter stemmed or copied token. State was
+ * BIGRAM_ALPHA, gets set to BIGRAM_RESET.
+ * - a&: We will generate a porter stemmed or copied token; we will set our
+ * state to BIGRAM_UNKNOWN to indicate we have seen one bigram character
+ * but that it is not yet time to emit a bigram.
+ * - .a: We are starting a token. State was BIGRAM_RESET, gets set to
+ * BIGRAM_ALPHA.
+ * - ..: We skip/eat the delimeters. State stays BIGRAM_RESET.
+ * - .&: State set to BIGRAM_UNKNOWN to indicate we have seen one bigram char.
+ * - &a: If the state was BIGRAM_USE, we generate a bi-gram token. If the state
+ * was BIGRAM_UNKNOWN we had only seen one CJK character and so don't do
+ * anything. State is set to BIGRAM_ALPHA.
+ * - &.: Same as the "&a" case, but state is set to BIGRAM_RESET.
+ * - &&: We will generate a bi-gram token. State was either BIGRAM_UNKNOWN or
+ * BIGRAM_USE, gets set to BIGRAM_USE.
+ */
+static int porterNext(
+ sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by porterOpen */
+ const char **pzToken, /* OUT: *pzToken is the token text */
+ int *pnBytes, /* OUT: Number of bytes in token */
+ int *piStartOffset, /* OUT: Starting offset of token */
+ int *piEndOffset, /* OUT: Ending offset of token */
+ int *piPosition /* OUT: Position integer of token */
+){
+ porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor;
+ const unsigned char *z = (unsigned char *) c->zInput;
+ int len = 0;
+ int state;
+
+ while( c->iOffset < c->nInput ){
+ int iStartOffset, numChars;
+
+ /*
+ * This loop basically has two modes of operation:
+ * - general processing (iPrevBigramOffset == 0 here)
+ * - CJK processing (iPrevBigramOffset != 0 here)
+ *
+ * In an general processing pass we skip over all the delimiters, leaving us
+ * at a character that promises to produce a token. This could be a CJK
+ * token (state == BIGRAM_USE) or an ALPHA token (state == BIGRAM_ALPHA).
+ * If it was a CJK token, we transition into CJK state for the next loop.
+ * If it was an alpha token, our current offset is pointing at a delimiter
+ * (which could be a CJK character), so it is good that our next pass
+ * through the function and loop will skip over any delimiters. If the
+ * delimiter we hit was a CJK character, the next time through we will
+ * not treat it as a delimiter though; the entry state for that scan is
+ * BIGRAM_RESET so the transition is not treated as a delimiter!
+ *
+ * The CJK pass always starts with the second character in a bi-gram emitted
+ * as a token in the previous step. No delimiter skipping is required
+ * because we know that first character might produce a token for us. It
+ * only 'might' produce a token because the previous pass performed no
+ * lookahead and cannot be sure it is followed by another CJK character.
+ * This is why
+ */
+
+ // If we have a previous bigram offset
+ if (c->iPrevBigramOffset == 0) {
+ /* Scan past delimiter characters */
+ state = BIGRAM_RESET; /* reset */
+ while (c->iOffset < c->nInput &&
+ isDelim(z + c->iOffset, z + c->nInput, &len, &state)) {
+ c->iOffset += len;
+ }
+
+ } else {
+ /* for bigram indexing, use previous offset */
+ c->iOffset = c->iPrevBigramOffset;
+ }
+
+ /* Count non-delimiter characters. */
+ iStartOffset = c->iOffset;
+ numChars = 0;
+
+ // Start from a reset state. This means the first character we see
+ // (which will not be a delimiter) determines which of ALPHA or CJK modes
+ // we are operating in. (It won't be a delimiter because in a 'general'
+ // pass as defined above, we will have eaten all the delimiters, and in
+ // a CJK pass we are guaranteed that the first character is CJK.)
+ state = BIGRAM_RESET; /* state is reset */
+ // Advance until it is time to emit a token.
+ // For ALPHA characters, this means advancing until we encounter a delimiter
+ // or a CJK character. iOffset will be pointing at the delimiter or CJK
+ // character, aka one beyond the last ALPHA character.
+ // For CJK characters this means advancing until we encounter an ALPHA
+ // character, a delimiter, or we have seen two consecutive CJK
+ // characters. iOffset points at the ALPHA/delimiter in the first 2 cases
+ // and the second of two CJK characters in the last case.
+ // Because of the way this loop is structured, iOffset is only updated
+ // when we don't terminate. However, if we terminate, len still contains
+ // the number of bytes in the character found at iOffset. (This is useful
+ // in the CJK case.)
+ while (c->iOffset < c->nInput &&
+ !isDelim(z + c->iOffset, z + c->nInput, &len, &state)) {
+ c->iOffset += len;
+ numChars++;
+ }
+
+ if (state == BIGRAM_USE) {
+ /* Split word by bigram */
+ // Right now iOffset is pointing at the second character in a pair.
+ // Save this offset so next-time through we start with that as the
+ // first character.
+ c->iPrevBigramOffset = c->iOffset;
+ // And now advance so that iOffset is pointing at the character after
+ // the second character in the bi-gram pair. Also count the char.
+ c->iOffset += len;
+ numChars++;
+ } else {
+ /* Reset bigram offset */
+ c->iPrevBigramOffset = 0;
+ }
+
+ /* We emit a token if:
+ * - there are two ideograms together,
+ * - there are three chars or more,
+ * - we think this is a query and wildcard magic is desired.
+ * We think is a wildcard query when we have a single character, it starts
+ * at the start of the buffer, it's CJK, our current offset is one shy of
+ * nInput and the character at iOffset is '*'. Because the state gets
+ * clobbered by the incidence of '*' our requirement for CJK is that the
+ * implied character length is at least 3 given that it takes at least 3
+ * bytes to encode to 0x2000.
+ */
+ // It is possible we have no token to emit here if iPrevBigramOffset was not
+ // 0 on entry and there was no second CJK character. iPrevBigramOffset
+ // will now be 0 if that is the case (and c->iOffset == iStartOffset).
+ if (// allow two-character words only if in bigram
+ (numChars == 2 && state == BIGRAM_USE) ||
+ // otherwise, drop two-letter words (considered stop-words)
+ (numChars >=3) ||
+ // wildcard case:
+ (numChars == 1 && iStartOffset == 0 &&
+ (c->iOffset >= 3) &&
+ (c->iOffset == c->nInput - 1) &&
+ (z[c->iOffset] == '*'))) {
+ /* figure out the number of bytes to copy/stem */
+ int n = c->iOffset - iStartOffset;
+ /* make sure there is enough buffer space */
+ if (n * MAX_UTF8_GROWTH_FACTOR > c->nAllocated) {
+ c->nAllocated = n * MAX_UTF8_GROWTH_FACTOR + 20;
+ c->zToken = sqlite3_realloc(c->zToken, c->nAllocated);
+ if (c->zToken == NULL)
+ return SQLITE_NOMEM;
+ }
+
+ if (state == BIGRAM_USE) {
+ /* This is by bigram. So it is unnecessary to convert word */
+ copy_stemmer(&z[iStartOffset], n, c->zToken, pnBytes);
+ } else {
+ porter_stemmer(&z[iStartOffset], n, c->zToken, pnBytes);
+ }
+ *pzToken = (const char*)c->zToken;
+ *piStartOffset = iStartOffset;
+ *piEndOffset = c->iOffset;
+ *piPosition = c->iToken++;
+ return SQLITE_OK;
+ }
+ }
+ return SQLITE_DONE;
+}
+
+/*
+** The set of routines that implement the porter-stemmer tokenizer
+*/
+static const sqlite3_tokenizer_module porterTokenizerModule = {
+ 0,
+ porterCreate,
+ porterDestroy,
+ porterOpen,
+ porterClose,
+ porterNext,
+};
+
+/*
+** Allocate a new porter tokenizer. Return a pointer to the new
+** tokenizer in *ppModule
+*/
+void sqlite3Fts3PorterTokenizerModule(
+ sqlite3_tokenizer_module const**ppModule
+){
+ *ppModule = &porterTokenizerModule;
+}
+
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
diff --git a/mailnews/extensions/fts3/src/fts3_tokenizer.h b/mailnews/extensions/fts3/src/fts3_tokenizer.h
new file mode 100644
index 000000000..906303db4
--- /dev/null
+++ b/mailnews/extensions/fts3/src/fts3_tokenizer.h
@@ -0,0 +1,148 @@
+/*
+** 2006 July 10
+**
+** The author disclaims copyright to this source code.
+**
+*************************************************************************
+** Defines the interface to tokenizers used by fulltext-search. There
+** are three basic components:
+**
+** sqlite3_tokenizer_module is a singleton defining the tokenizer
+** interface functions. This is essentially the class structure for
+** tokenizers.
+**
+** sqlite3_tokenizer is used to define a particular tokenizer, perhaps
+** including customization information defined at creation time.
+**
+** sqlite3_tokenizer_cursor is generated by a tokenizer to generate
+** tokens from a particular input.
+*/
+#ifndef _FTS3_TOKENIZER_H_
+#define _FTS3_TOKENIZER_H_
+
+/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time.
+** If tokenizers are to be allowed to call sqlite3_*() functions, then
+** we will need a way to register the API consistently.
+*/
+#include "sqlite3.h"
+
+/*
+** Structures used by the tokenizer interface. When a new tokenizer
+** implementation is registered, the caller provides a pointer to
+** an sqlite3_tokenizer_module containing pointers to the callback
+** functions that make up an implementation.
+**
+** When an fts3 table is created, it passes any arguments passed to
+** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the
+** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer
+** implementation. The xCreate() function in turn returns an
+** sqlite3_tokenizer structure representing the specific tokenizer to
+** be used for the fts3 table (customized by the tokenizer clause arguments).
+**
+** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen()
+** method is called. It returns an sqlite3_tokenizer_cursor object
+** that may be used to tokenize a specific input buffer based on
+** the tokenization rules supplied by a specific sqlite3_tokenizer
+** object.
+*/
+typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module;
+typedef struct sqlite3_tokenizer sqlite3_tokenizer;
+typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor;
+
+struct sqlite3_tokenizer_module {
+
+ /*
+ ** Structure version. Should always be set to 0.
+ */
+ int iVersion;
+
+ /*
+ ** Create a new tokenizer. The values in the argv[] array are the
+ ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL
+ ** TABLE statement that created the fts3 table. For example, if
+ ** the following SQL is executed:
+ **
+ ** CREATE .. USING fts3( ... , tokenizer <tokenizer-name> arg1 arg2)
+ **
+ ** then argc is set to 2, and the argv[] array contains pointers
+ ** to the strings "arg1" and "arg2".
+ **
+ ** This method should return either SQLITE_OK (0), or an SQLite error
+ ** code. If SQLITE_OK is returned, then *ppTokenizer should be set
+ ** to point at the newly created tokenizer structure. The generic
+ ** sqlite3_tokenizer.pModule variable should not be initialised by
+ ** this callback. The caller will do so.
+ */
+ int (*xCreate)(
+ int argc, /* Size of argv array */
+ const char *const*argv, /* Tokenizer argument strings */
+ sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
+ );
+
+ /*
+ ** Destroy an existing tokenizer. The fts3 module calls this method
+ ** exactly once for each successful call to xCreate().
+ */
+ int (*xDestroy)(sqlite3_tokenizer *pTokenizer);
+
+ /*
+ ** Create a tokenizer cursor to tokenize an input buffer. The caller
+ ** is responsible for ensuring that the input buffer remains valid
+ ** until the cursor is closed (using the xClose() method).
+ */
+ int (*xOpen)(
+ sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
+ const char *pInput, int nBytes, /* Input buffer */
+ sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
+ );
+
+ /*
+ ** Destroy an existing tokenizer cursor. The fts3 module calls this
+ ** method exactly once for each successful call to xOpen().
+ */
+ int (*xClose)(sqlite3_tokenizer_cursor *pCursor);
+
+ /*
+ ** Retrieve the next token from the tokenizer cursor pCursor. This
+ ** method should either return SQLITE_OK and set the values of the
+ ** "OUT" variables identified below, or SQLITE_DONE to indicate that
+ ** the end of the buffer has been reached, or an SQLite error code.
+ **
+ ** *ppToken should be set to point at a buffer containing the
+ ** normalized version of the token (i.e. after any case-folding and/or
+ ** stemming has been performed). *pnBytes should be set to the length
+ ** of this buffer in bytes. The input text that generated the token is
+ ** identified by the byte offsets returned in *piStartOffset and
+ ** *piEndOffset. *piStartOffset should be set to the index of the first
+ ** byte of the token in the input buffer. *piEndOffset should be set
+ ** to the index of the first byte just past the end of the token in
+ ** the input buffer.
+ **
+ ** The buffer *ppToken is set to point at is managed by the tokenizer
+ ** implementation. It is only required to be valid until the next call
+ ** to xNext() or xClose().
+ */
+ /* TODO(shess) current implementation requires pInput to be
+ ** nul-terminated. This should either be fixed, or pInput/nBytes
+ ** should be converted to zInput.
+ */
+ int (*xNext)(
+ sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
+ const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */
+ int *piStartOffset, /* OUT: Byte offset of token in input buffer */
+ int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
+ int *piPosition /* OUT: Number of tokens returned before this one */
+ );
+};
+
+struct sqlite3_tokenizer {
+ const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */
+ /* Tokenizer implementations will typically add additional fields */
+};
+
+struct sqlite3_tokenizer_cursor {
+ sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */
+ /* Tokenizer implementations will typically add additional fields */
+};
+
+#endif /* _FTS3_TOKENIZER_H_ */
diff --git a/mailnews/extensions/fts3/src/moz.build b/mailnews/extensions/fts3/src/moz.build
new file mode 100644
index 000000000..a2b3c60d5
--- /dev/null
+++ b/mailnews/extensions/fts3/src/moz.build
@@ -0,0 +1,18 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'fts3_porter.c',
+ 'Normalize.c',
+]
+
+SOURCES += [
+ 'nsFts3Tokenizer.cpp',
+ 'nsGlodaRankerFunction.cpp',
+]
+
+FINAL_LIBRARY = 'mail'
+
+CXXFLAGS += CONFIG['SQLITE_CFLAGS']
diff --git a/mailnews/extensions/fts3/src/nsFts3Tokenizer.cpp b/mailnews/extensions/fts3/src/nsFts3Tokenizer.cpp
new file mode 100644
index 000000000..12bd70ead
--- /dev/null
+++ b/mailnews/extensions/fts3/src/nsFts3Tokenizer.cpp
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFts3Tokenizer.h"
+
+#include "nsGlodaRankerFunction.h"
+
+#include "nsIFts3Tokenizer.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageStatement.h"
+#include "nsStringGlue.h"
+
+extern "C" void sqlite3Fts3PorterTokenizerModule(
+ sqlite3_tokenizer_module const**ppModule);
+
+extern "C" void glodaRankFunc(sqlite3_context *pCtx,
+ int nVal,
+ sqlite3_value **apVal);
+
+NS_IMPL_ISUPPORTS(nsFts3Tokenizer,nsIFts3Tokenizer)
+
+nsFts3Tokenizer::nsFts3Tokenizer()
+{
+}
+
+nsFts3Tokenizer::~nsFts3Tokenizer()
+{
+}
+
+NS_IMETHODIMP
+nsFts3Tokenizer::RegisterTokenizer(mozIStorageConnection *connection)
+{
+ nsresult rv;
+ nsCOMPtr<mozIStorageStatement> selectStatement;
+
+ // -- register the tokenizer
+ rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT fts3_tokenizer(?1, ?2)"),
+ getter_AddRefs(selectStatement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const sqlite3_tokenizer_module* module = nullptr;
+ sqlite3Fts3PorterTokenizerModule(&module);
+ if (!module)
+ return NS_ERROR_FAILURE;
+
+ rv = selectStatement->BindUTF8StringParameter(
+ 0, NS_LITERAL_CSTRING("mozporter"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = selectStatement->BindBlobParameter(1,
+ (uint8_t*)&module,
+ sizeof(module));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ rv = selectStatement->ExecuteStep(&hasMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // -- register the ranking function
+ nsCOMPtr<mozIStorageFunction> func = new nsGlodaRankerFunction();
+ NS_ENSURE_TRUE(func, NS_ERROR_OUT_OF_MEMORY);
+ rv = connection->CreateFunction(
+ NS_LITERAL_CSTRING("glodaRank"),
+ -1, // variable argument support
+ func
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
diff --git a/mailnews/extensions/fts3/src/nsFts3Tokenizer.h b/mailnews/extensions/fts3/src/nsFts3Tokenizer.h
new file mode 100644
index 000000000..eb1e83bd5
--- /dev/null
+++ b/mailnews/extensions/fts3/src/nsFts3Tokenizer.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFts3Tokenizer_h__
+#define nsFts3Tokenizer_h__
+
+#include "nsCOMPtr.h"
+#include "nsIFts3Tokenizer.h"
+#include "fts3_tokenizer.h"
+
+extern const sqlite3_tokenizer_module* getWindowsTokenizer();
+
+class nsFts3Tokenizer final : public nsIFts3Tokenizer {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFTS3TOKENIZER
+
+ nsFts3Tokenizer();
+
+private:
+ ~nsFts3Tokenizer();
+};
+
+#endif
diff --git a/mailnews/extensions/fts3/src/nsFts3TokenizerCID.h b/mailnews/extensions/fts3/src/nsFts3TokenizerCID.h
new file mode 100644
index 000000000..1f9c101bc
--- /dev/null
+++ b/mailnews/extensions/fts3/src/nsFts3TokenizerCID.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFts3TokenizerCID_h__
+#define nsFts3TokenizerCID_h__
+
+#define NS_FTS3TOKENIZER_CONTRACTID \
+ "@mozilla.org/messenger/fts3tokenizer;1"
+#define NS_FTS3TOKENIZER_CID \
+{ /* a67d724d-0015-4e2e-8cad-b84775330924 */ \
+ 0xa67d724d, 0x0015, 0x4e2e, \
+ { 0x8c, 0xad, 0xb8, 0x47, 0x75, 0x33, 0x09, 0x24 }}
+
+#endif /* nsFts3TokenizerCID_h__ */
diff --git a/mailnews/extensions/fts3/src/nsGlodaRankerFunction.cpp b/mailnews/extensions/fts3/src/nsGlodaRankerFunction.cpp
new file mode 100644
index 000000000..fb576685d
--- /dev/null
+++ b/mailnews/extensions/fts3/src/nsGlodaRankerFunction.cpp
@@ -0,0 +1,145 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Global Database.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsGlodaRankerFunction.h"
+#include "mozIStorageValueArray.h"
+
+#include "sqlite3.h"
+
+#include "nsCOMPtr.h"
+#include "nsVariant.h"
+#include "nsComponentManagerUtils.h"
+
+#ifndef SQLITE_VERSION_NUMBER
+#error "We need SQLITE_VERSION_NUMBER defined!"
+#endif
+
+NS_IMPL_ISUPPORTS(nsGlodaRankerFunction, mozIStorageFunction)
+
+nsGlodaRankerFunction::nsGlodaRankerFunction()
+{
+}
+
+nsGlodaRankerFunction::~nsGlodaRankerFunction()
+{
+}
+
+static uint32_t COLUMN_SATURATION[] = {10, 1, 1, 1, 1};
+
+/**
+ * Our ranking function basically just multiplies the weight of the column
+ * against the number of (saturating) matches.
+ *
+ * The original code is a SQLite example ranking function, although somewhat
+ * rather modified at this point. All SQLite code is public domain, so we are
+ * subsuming it to MPL1.1/LGPL2/GPL2.
+ */
+NS_IMETHODIMP
+nsGlodaRankerFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+{
+ // all argument names are maintained from the original SQLite code.
+ uint32_t nVal;
+ nsresult rv = aArguments->GetNumEntries(&nVal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* Check that the number of arguments passed to this function is correct.
+ * If not, return an error. Set aArgsData to point to the array
+ * of unsigned integer values returned by FTS3 function. Set nPhrase
+ * to contain the number of reportable phrases in the users full-text
+ * query, and nCol to the number of columns in the table.
+ */
+ if (nVal < 1)
+ return NS_ERROR_INVALID_ARG;
+
+ uint32_t lenArgsData;
+ uint32_t *aArgsData = (uint32_t *)aArguments->AsSharedBlob(0, &lenArgsData);
+
+ uint32_t nPhrase = aArgsData[0];
+ uint32_t nCol = aArgsData[1];
+ if (nVal != (1 + nCol))
+ return NS_ERROR_INVALID_ARG;
+
+ double score = 0.0;
+
+ // SQLite 3.6.22 has a different matchinfo layout than SQLite 3.6.23+
+#if SQLITE_VERSION_NUMBER <= 3006022
+ /* Iterate through each phrase in the users query. */
+ for (uint32_t iPhrase = 0; iPhrase < nPhrase; iPhrase++) {
+ // in SQ
+ for (uint32_t iCol = 0; iCol < nCol; iCol++) {
+ uint32_t nHitCount = aArgsData[2 + (iPhrase+1)*nCol + iCol];
+ double weight = aArguments->AsDouble(iCol+1);
+ if (nHitCount > 0) {
+ score += (nHitCount > COLUMN_SATURATION[iCol]) ?
+ (COLUMN_SATURATION[iCol] * weight) :
+ (nHitCount * weight);
+ }
+ }
+ }
+#else
+ /* Iterate through each phrase in the users query. */
+ for (uint32_t iPhrase = 0; iPhrase < nPhrase; iPhrase++) {
+ /* Now iterate through each column in the users query. For each column,
+ ** increment the relevancy score by:
+ **
+ ** (<hit count> / <global hit count>) * <column weight>
+ **
+ ** aPhraseinfo[] points to the start of the data for phrase iPhrase. So
+ ** the hit count and global hit counts for each column are found in
+ ** aPhraseinfo[iCol*3] and aPhraseinfo[iCol*3+1], respectively.
+ */
+ uint32_t *aPhraseinfo = &aArgsData[2 + iPhrase*nCol*3];
+ for (uint32_t iCol = 0; iCol < nCol; iCol++) {
+ uint32_t nHitCount = aPhraseinfo[3 * iCol];
+ double weight = aArguments->AsDouble(iCol+1);
+ if (nHitCount > 0) {
+ score += (nHitCount > COLUMN_SATURATION[iCol]) ?
+ (COLUMN_SATURATION[iCol] * weight) :
+ (nHitCount * weight);
+ }
+ }
+ }
+#endif
+
+ nsCOMPtr<nsIWritableVariant> result = new nsVariant();
+
+ rv = result->SetAsDouble(score);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*_result = result);
+ return NS_OK;
+}
diff --git a/mailnews/extensions/fts3/src/nsGlodaRankerFunction.h b/mailnews/extensions/fts3/src/nsGlodaRankerFunction.h
new file mode 100644
index 000000000..5c5c920d0
--- /dev/null
+++ b/mailnews/extensions/fts3/src/nsGlodaRankerFunction.h
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsGlodaRankerFunction_h_
+#define _nsGlodaRankerFunction_h_
+
+#include "mozIStorageFunction.h"
+
+/**
+ * Basically a port of the example FTS3 ranking function to mozStorage's
+ * view of the universe. This might get fancier at some point.
+ */
+class nsGlodaRankerFunction final : public mozIStorageFunction
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ nsGlodaRankerFunction();
+private:
+ ~nsGlodaRankerFunction();
+};
+
+#endif // _nsGlodaRankerFunction_h_
diff --git a/mailnews/extensions/mailviews/content/mailViews.dat b/mailnews/extensions/mailviews/content/mailViews.dat
new file mode 100644
index 000000000..8565d0061
--- /dev/null
+++ b/mailnews/extensions/mailviews/content/mailViews.dat
@@ -0,0 +1,22 @@
+version="8"
+logging="no"
+name="People I Know"
+enabled="yes"
+type="1"
+condition="AND (from,is in ab,moz-abmdbdirectory://abook.mab)"
+name="Recent Mail"
+enabled="yes"
+type="1"
+condition="AND (age in days,is less than,1)"
+name="Last 5 Days"
+enabled="yes"
+type="1"
+condition="AND (age in days,is less than,5)"
+name="Not Junk"
+enabled="yes"
+type="1"
+condition="AND (junk status,isn't,2)"
+name="Has Attachments"
+enabled="yes"
+type="1"
+condition="AND (has attachment status,is,true)"
diff --git a/mailnews/extensions/mailviews/content/moz.build b/mailnews/extensions/mailviews/content/moz.build
new file mode 100644
index 000000000..f5564e55b
--- /dev/null
+++ b/mailnews/extensions/mailviews/content/moz.build
@@ -0,0 +1,8 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+FINAL_TARGET_FILES.defaults.messenger += [
+ 'mailViews.dat',
+]
diff --git a/mailnews/extensions/mailviews/public/moz.build b/mailnews/extensions/mailviews/public/moz.build
new file mode 100644
index 000000000..4d8dd8287
--- /dev/null
+++ b/mailnews/extensions/mailviews/public/moz.build
@@ -0,0 +1,12 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIMsgMailView.idl',
+ 'nsIMsgMailViewList.idl',
+]
+
+XPIDL_MODULE = 'mailview'
+
diff --git a/mailnews/extensions/mailviews/public/nsIMsgMailView.idl b/mailnews/extensions/mailviews/public/nsIMsgMailView.idl
new file mode 100644
index 000000000..54f22e76f
--- /dev/null
+++ b/mailnews/extensions/mailviews/public/nsIMsgMailView.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+%{C++
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+%}
+interface nsISupportsArray;
+
+interface nsIMsgSearchTerm;
+
+[scriptable, uuid(28AC84DF-CBE5-430d-A5C0-4FA63B5424DF)]
+interface nsIMsgMailView : nsISupports {
+ attribute wstring mailViewName;
+ readonly attribute wstring prettyName; // localized pretty name
+
+ // the array of search terms
+ attribute nsISupportsArray searchTerms;
+
+ // these two helper methods are required to allow searchTermsOverlay.js to
+ // manipulate a mail view without knowing it is dealing with a mail view. nsIMsgFilter
+ // and nsIMsgSearchSession have the same two methods....we should probably make an interface around them.
+ void appendTerm(in nsIMsgSearchTerm term);
+ nsIMsgSearchTerm createTerm();
+
+};
diff --git a/mailnews/extensions/mailviews/public/nsIMsgMailViewList.idl b/mailnews/extensions/mailviews/public/nsIMsgMailViewList.idl
new file mode 100644
index 000000000..e0c846dae
--- /dev/null
+++ b/mailnews/extensions/mailviews/public/nsIMsgMailViewList.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgMailView.idl"
+
+///////////////////////////////////////////////////////////////////////////////
+// A mail view list is a list of mail views a particular implementor provides
+///////////////////////////////////////////////////////////////////////////////
+
+typedef long nsMsgMailViewListFileAttribValue;
+
+[scriptable, uuid(6DD798D7-9528-49e6-9447-3AAF14D2D36F)]
+interface nsIMsgMailViewList : nsISupports {
+
+ readonly attribute unsigned long mailViewCount;
+
+ nsIMsgMailView getMailViewAt(in unsigned long mailViewIndex);
+
+ void addMailView(in nsIMsgMailView mailView);
+ void removeMailView(in nsIMsgMailView mailView);
+
+ nsIMsgMailView createMailView();
+
+ void save();
+};
diff --git a/mailnews/extensions/mailviews/src/moz.build b/mailnews/extensions/mailviews/src/moz.build
new file mode 100644
index 000000000..ee128fb8a
--- /dev/null
+++ b/mailnews/extensions/mailviews/src/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsMsgMailViewList.cpp',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/extensions/mailviews/src/nsMsgMailViewList.cpp b/mailnews/extensions/mailviews/src/nsMsgMailViewList.cpp
new file mode 100644
index 000000000..8e5e04a3f
--- /dev/null
+++ b/mailnews/extensions/mailviews/src/nsMsgMailViewList.cpp
@@ -0,0 +1,312 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgMailViewList.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsIFileChannel.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsMsgBaseCID.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsIMsgFilter.h"
+
+#define kDefaultViewPeopleIKnow "People I Know"
+#define kDefaultViewRecent "Recent Mail"
+#define kDefaultViewFiveDays "Last 5 Days"
+#define kDefaultViewNotJunk "Not Junk"
+#define kDefaultViewHasAttachments "Has Attachments"
+
+nsMsgMailView::nsMsgMailView()
+{
+ mViewSearchTerms = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID);
+}
+
+NS_IMPL_ADDREF(nsMsgMailView)
+NS_IMPL_RELEASE(nsMsgMailView)
+NS_IMPL_QUERY_INTERFACE(nsMsgMailView, nsIMsgMailView)
+
+nsMsgMailView::~nsMsgMailView()
+{
+ if (mViewSearchTerms)
+ mViewSearchTerms->Clear();
+}
+
+NS_IMETHODIMP nsMsgMailView::GetMailViewName(char16_t ** aMailViewName)
+{
+ NS_ENSURE_ARG_POINTER(aMailViewName);
+
+ *aMailViewName = ToNewUnicode(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailView::SetMailViewName(const char16_t * aMailViewName)
+{
+ mName = aMailViewName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailView::GetPrettyName(char16_t ** aMailViewName)
+{
+ NS_ENSURE_ARG_POINTER(aMailViewName);
+
+ nsresult rv = NS_OK;
+ if (!mBundle)
+ {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ bundleService->CreateBundle("chrome://messenger/locale/mailviews.properties",
+ getter_AddRefs(mBundle));
+ }
+
+ NS_ENSURE_TRUE(mBundle, NS_ERROR_FAILURE);
+
+ // see if mName has an associated pretty name inside our string bundle and if so, use that as the pretty name
+ // otherwise just return mName
+ if (mName.EqualsLiteral(kDefaultViewPeopleIKnow))
+ rv = mBundle->GetStringFromName(u"mailViewPeopleIKnow", aMailViewName);
+ else if (mName.EqualsLiteral(kDefaultViewRecent))
+ rv = mBundle->GetStringFromName(u"mailViewRecentMail", aMailViewName);
+ else if (mName.EqualsLiteral(kDefaultViewFiveDays))
+ rv = mBundle->GetStringFromName(u"mailViewLastFiveDays", aMailViewName);
+ else if (mName.EqualsLiteral(kDefaultViewNotJunk))
+ rv = mBundle->GetStringFromName(u"mailViewNotJunk", aMailViewName);
+ else if (mName.EqualsLiteral(kDefaultViewHasAttachments))
+ rv = mBundle->GetStringFromName(u"mailViewHasAttachments", aMailViewName);
+ else
+ *aMailViewName = ToNewUnicode(mName);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailView::GetSearchTerms(nsISupportsArray ** aSearchTerms)
+{
+ NS_ENSURE_ARG_POINTER(aSearchTerms);
+ NS_IF_ADDREF(*aSearchTerms = mViewSearchTerms);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailView::SetSearchTerms(nsISupportsArray * aSearchTerms)
+{
+ mViewSearchTerms = aSearchTerms;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailView::AppendTerm(nsIMsgSearchTerm * aTerm)
+{
+ NS_ENSURE_TRUE(aTerm, NS_ERROR_NULL_POINTER);
+
+ return mViewSearchTerms->AppendElement(static_cast<nsISupports*>(aTerm));
+}
+
+NS_IMETHODIMP nsMsgMailView::CreateTerm(nsIMsgSearchTerm **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsCOMPtr<nsIMsgSearchTerm> searchTerm = do_CreateInstance("@mozilla.org/messenger/searchTerm;1");
+ NS_IF_ADDREF(*aResult = searchTerm);
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// nsMsgMailViewList implementation
+/////////////////////////////////////////////////////////////////////////////
+nsMsgMailViewList::nsMsgMailViewList()
+{
+ LoadMailViews();
+}
+
+NS_IMPL_ADDREF(nsMsgMailViewList)
+NS_IMPL_RELEASE(nsMsgMailViewList)
+NS_IMPL_QUERY_INTERFACE(nsMsgMailViewList, nsIMsgMailViewList)
+
+nsMsgMailViewList::~nsMsgMailViewList()
+{
+
+}
+
+NS_IMETHODIMP nsMsgMailViewList::GetMailViewCount(uint32_t * aCount)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+
+ *aCount = m_mailViews.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailViewList::GetMailViewAt(uint32_t aMailViewIndex, nsIMsgMailView ** aMailView)
+{
+ NS_ENSURE_ARG_POINTER(aMailView);
+
+ uint32_t mailViewCount = m_mailViews.Length();
+
+ NS_ENSURE_ARG(mailViewCount > aMailViewIndex);
+
+ NS_IF_ADDREF(*aMailView = m_mailViews[aMailViewIndex]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailViewList::AddMailView(nsIMsgMailView * aMailView)
+{
+ NS_ENSURE_ARG_POINTER(aMailView);
+
+ m_mailViews.AppendElement(aMailView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailViewList::RemoveMailView(nsIMsgMailView * aMailView)
+{
+ NS_ENSURE_ARG_POINTER(aMailView);
+
+ m_mailViews.RemoveElement(aMailView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailViewList::CreateMailView(nsIMsgMailView ** aMailView)
+{
+ NS_ENSURE_ARG_POINTER(aMailView);
+
+ nsMsgMailView * mailView = new nsMsgMailView;
+ NS_ENSURE_TRUE(mailView, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_IF_ADDREF(*aMailView = mailView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailViewList::Save()
+{
+ // brute force...remove all the old filters in our filter list, then we'll re-add our current
+ // list
+ nsCOMPtr<nsIMsgFilter> msgFilter;
+ uint32_t numFilters = 0;
+ if (mFilterList)
+ mFilterList->GetFilterCount(&numFilters);
+ while (numFilters)
+ {
+ mFilterList->RemoveFilterAt(numFilters - 1);
+ numFilters--;
+ }
+
+ // now convert our mail view list into a filter list and save it
+ ConvertMailViewListToFilterList();
+
+ // now save the filters to our file
+ return mFilterList ? mFilterList->SaveToDefaultFile() : NS_ERROR_FAILURE;
+}
+
+nsresult nsMsgMailViewList::ConvertMailViewListToFilterList()
+{
+ uint32_t mailViewCount = m_mailViews.Length();
+ nsCOMPtr<nsIMsgMailView> mailView;
+ nsCOMPtr<nsIMsgFilter> newMailFilter;
+ nsString mailViewName;
+ for (uint32_t index = 0; index < mailViewCount; index++)
+ {
+ GetMailViewAt(index, getter_AddRefs(mailView));
+ if (!mailView)
+ continue;
+ mailView->GetMailViewName(getter_Copies(mailViewName));
+ mFilterList->CreateFilter(mailViewName, getter_AddRefs(newMailFilter));
+ if (!newMailFilter)
+ continue;
+
+ nsCOMPtr<nsISupportsArray> searchTerms;
+ mailView->GetSearchTerms(getter_AddRefs(searchTerms));
+ newMailFilter->SetSearchTerms(searchTerms);
+ mFilterList->InsertFilterAt(index, newMailFilter);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgMailViewList::LoadMailViews()
+{
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(nsDependentCString("mailViews.dat"));
+
+ // if the file doesn't exist, we should try to get it from the defaults directory and copy it over
+ bool exists = false;
+ file->Exists(&exists);
+ if (!exists)
+ {
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> defaultMessagesFile;
+ nsCOMPtr<nsIFile> profileDir;
+ rv = mailSession->GetDataFilesDir("messenger", getter_AddRefs(defaultMessagesFile));
+ rv = defaultMessagesFile->AppendNative(nsDependentCString("mailViews.dat"));
+
+ // get the profile directory
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir));
+
+ // now copy the file over to the profile directory
+ defaultMessagesFile->CopyToNative(profileDir, EmptyCString());
+ }
+ // this is kind of a hack but I think it will be an effective hack. The filter service already knows how to
+ // take a nsIFile and parse the contents into filters which are very similar to mail views. Intead of
+ // re-writing all of that dirty parsing code, let's just re-use it then convert the results into a data strcuture
+ // we wish to give to our consumers.
+
+ nsCOMPtr<nsIMsgFilterService> filterService = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ nsCOMPtr<nsIMsgFilterList> mfilterList;
+
+ rv = filterService->OpenFilterList(file, nullptr, nullptr, getter_AddRefs(mFilterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return ConvertFilterListToMailViews();
+}
+/**
+ * Converts the filter list into our mail view objects,
+ * stripping out just the info we need.
+ */
+nsresult nsMsgMailViewList::ConvertFilterListToMailViews()
+{
+ nsresult rv = NS_OK;
+ m_mailViews.Clear();
+
+ // iterate over each filter in the list
+ uint32_t numFilters = 0;
+ mFilterList->GetFilterCount(&numFilters);
+ for (uint32_t index = 0; index < numFilters; index++)
+ {
+ nsCOMPtr<nsIMsgFilter> msgFilter;
+ rv = mFilterList->GetFilterAt(index, getter_AddRefs(msgFilter));
+ if (NS_FAILED(rv) || !msgFilter)
+ continue;
+
+ // create a new nsIMsgMailView for this item
+ nsCOMPtr<nsIMsgMailView> newMailView;
+ rv = CreateMailView(getter_AddRefs(newMailView));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString filterName;
+ msgFilter->GetFilterName(filterName);
+ newMailView->SetMailViewName(filterName.get());
+
+ nsCOMPtr<nsISupportsArray> filterSearchTerms;
+ rv = msgFilter->GetSearchTerms(getter_AddRefs(filterSearchTerms));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = newMailView->SetSearchTerms(filterSearchTerms);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now append this new mail view to our global list view
+ m_mailViews.AppendElement(newMailView);
+ }
+
+ return rv;
+}
diff --git a/mailnews/extensions/mailviews/src/nsMsgMailViewList.h b/mailnews/extensions/mailviews/src/nsMsgMailViewList.h
new file mode 100644
index 000000000..321803392
--- /dev/null
+++ b/mailnews/extensions/mailviews/src/nsMsgMailViewList.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef _nsMsgMailViewList_H_
+#define _nsMsgMailViewList_H_
+
+#include "nscore.h"
+#include "nsIMsgMailViewList.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsIStringBundle.h"
+#include "nsStringGlue.h"
+#include "nsIMsgFilterList.h"
+
+// a mail View is just a name and an array of search terms
+class nsMsgMailView : public nsIMsgMailView
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGMAILVIEW
+
+ nsMsgMailView();
+
+protected:
+ virtual ~nsMsgMailView();
+ nsString mName;
+ nsCOMPtr<nsIStringBundle> mBundle;
+ nsCOMPtr<nsISupportsArray> mViewSearchTerms;
+};
+
+
+class nsMsgMailViewList : public nsIMsgMailViewList
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGMAILVIEWLIST
+
+ nsMsgMailViewList();
+
+protected:
+ virtual ~nsMsgMailViewList();
+ nsresult LoadMailViews(); // reads in user defined mail views from our default file
+ nsresult ConvertFilterListToMailViews();
+ nsresult ConvertMailViewListToFilterList();
+
+ nsCOMArray<nsIMsgMailView> m_mailViews;
+ nsCOMPtr<nsIMsgFilterList> mFilterList; // our internal filter list representation
+};
+
+#endif
diff --git a/mailnews/extensions/mailviews/src/nsMsgMailViewsCID.h b/mailnews/extensions/mailviews/src/nsMsgMailViewsCID.h
new file mode 100644
index 000000000..9487e5f4c
--- /dev/null
+++ b/mailnews/extensions/mailviews/src/nsMsgMailViewsCID.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgMailViewsCID_h__
+#define nsMsgMailViewsCID_h__
+
+#define NS_MSGMAILVIEWLIST_CONTRACTID \
+ "@mozilla.org/messenger/mailviewlist;1"
+
+#define NS_MSGMAILVIEWLIST_CID \
+{ /* A0258267-44FD-4886-A858-8192615178EC */ \
+ 0xa0258267, 0x44fd, 0x4886, \
+ { 0xa8, 0x58, 0x81, 0x92, 0x61, 0x51, 0x78, 0xec }}
+
+#endif /* nsMsgMailViewsCID_h__*/
diff --git a/mailnews/extensions/mdn/content/am-mdn.js b/mailnews/extensions/mdn/content/am-mdn.js
new file mode 100644
index 000000000..4dd9e8d8d
--- /dev/null
+++ b/mailnews/extensions/mdn/content/am-mdn.js
@@ -0,0 +1,155 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var useCustomPrefs;
+var requestReceipt;
+var leaveInInbox;
+var moveToSent;
+var receiptSend;
+var neverReturn;
+var returnSome;
+var notInToCcPref;
+var notInToCcLabel;
+var outsideDomainPref;
+var outsideDomainLabel;
+var otherCasesPref;
+var otherCasesLabel;
+var receiptArriveLabel;
+var receiptRequestLabel;
+var gIdentity;
+var gIncomingServer;
+var gMdnPrefBranch;
+
+function onInit()
+{
+ useCustomPrefs = document.getElementById("identity.use_custom_prefs");
+ requestReceipt = document.getElementById("identity.request_return_receipt_on");
+ leaveInInbox = document.getElementById("leave_in_inbox");
+ moveToSent = document.getElementById("move_to_sent");
+ receiptSend = document.getElementById("server.mdn_report_enabled");
+ neverReturn = document.getElementById("never_return");
+ returnSome = document.getElementById("return_some");
+ notInToCcPref = document.getElementById("server.mdn_not_in_to_cc");
+ notInToCcLabel = document.getElementById("notInToCcLabel");
+ outsideDomainPref = document.getElementById("server.mdn_outside_domain");
+ outsideDomainLabel = document.getElementById("outsideDomainLabel");
+ otherCasesPref = document.getElementById("server.mdn_other");
+ otherCasesLabel = document.getElementById("otherCasesLabel");
+ receiptArriveLabel = document.getElementById("receiptArriveLabel");
+ receiptRequestLabel = document.getElementById("receiptRequestLabel");
+
+ EnableDisableCustomSettings();
+
+ return true;
+}
+
+function onSave()
+{
+
+}
+
+function EnableDisableCustomSettings() {
+ if (useCustomPrefs && (useCustomPrefs.getAttribute("value") == "false")) {
+ requestReceipt.setAttribute("disabled", "true");
+ leaveInInbox.setAttribute("disabled", "true");
+ moveToSent.setAttribute("disabled", "true");
+ neverReturn.setAttribute("disabled", "true");
+ returnSome.setAttribute("disabled", "true");
+ receiptArriveLabel.setAttribute("disabled", "true");
+ receiptRequestLabel.setAttribute("disabled", "true");
+ }
+ else {
+ requestReceipt.removeAttribute("disabled");
+ leaveInInbox.removeAttribute("disabled");
+ moveToSent.removeAttribute("disabled");
+ neverReturn.removeAttribute("disabled");
+ returnSome.removeAttribute("disabled");
+ receiptArriveLabel.removeAttribute("disabled");
+ receiptRequestLabel.removeAttribute("disabled");
+ }
+ EnableDisableAllowedReceipts();
+ // Lock id based prefs
+ onLockPreference("mail.identity", gIdentity.key);
+ // Lock server based prefs
+ onLockPreference("mail.server", gIncomingServer.key);
+ return true;
+}
+
+function EnableDisableAllowedReceipts() {
+ if (receiptSend) {
+ if (!neverReturn.getAttribute("disabled") && (receiptSend.getAttribute("value") != "false")) {
+ notInToCcPref.removeAttribute("disabled");
+ notInToCcLabel.removeAttribute("disabled");
+ outsideDomainPref.removeAttribute("disabled");
+ outsideDomainLabel.removeAttribute("disabled");
+ otherCasesPref.removeAttribute("disabled");
+ otherCasesLabel.removeAttribute("disabled");
+ }
+ else {
+ notInToCcPref.setAttribute("disabled", "true");
+ notInToCcLabel.setAttribute("disabled", "true");
+ outsideDomainPref.setAttribute("disabled", "true");
+ outsideDomainLabel.setAttribute("disabled", "true");
+ otherCasesPref.setAttribute("disabled", "true");
+ otherCasesLabel.setAttribute("disabled", "true");
+ }
+ }
+ return true;
+}
+
+function onPreInit(account, accountValues)
+{
+ gIdentity = account.defaultIdentity;
+ gIncomingServer = account.incomingServer;
+}
+
+// Disables xul elements that have associated preferences locked.
+function onLockPreference(initPrefString, keyString)
+{
+ var finalPrefString;
+
+ var allPrefElements = [
+ { prefstring:"request_return_receipt_on", id:"identity.request_return_receipt_on"},
+ { prefstring:"select_custom_prefs", id:"identity.select_custom_prefs"},
+ { prefstring:"select_global_prefs", id:"identity.select_global_prefs"},
+ { prefstring:"incorporate_return_receipt", id:"server.incorporate_return_receipt"},
+ { prefstring:"never_return", id:"never_return"},
+ { prefstring:"return_some", id:"return_some"},
+ { prefstring:"mdn_not_in_to_cc", id:"server.mdn_not_in_to_cc"},
+ { prefstring:"mdn_outside_domain", id:"server.mdn_outside_domain"},
+ { prefstring:"mdn_other", id:"server.mdn_other"},
+ ];
+
+ finalPrefString = initPrefString + "." + keyString + ".";
+ gMdnPrefBranch = Services.prefs.getBranch(finalPrefString);
+
+ disableIfLocked( allPrefElements );
+}
+
+function disableIfLocked( prefstrArray )
+{
+ for (var i=0; i<prefstrArray.length; i++) {
+ var id = prefstrArray[i].id;
+ var element = document.getElementById(id);
+ if (gMdnPrefBranch.prefIsLocked(prefstrArray[i].prefstring)) {
+ if (id == "server.incorporate_return_receipt")
+ {
+ document.getElementById("leave_in_inbox").setAttribute("disabled", "true");
+ document.getElementById("move_to_sent").setAttribute("disabled", "true");
+ }
+ else
+ element.setAttribute("disabled", "true");
+ }
+ }
+}
+
+/**
+ * Opens Preferences (Options) dialog on the pane and tab where
+ * the global receipts settings can be found.
+ */
+function showGlobalReceipts() {
+ openPrefsFromAccountManager("paneAdvanced", "generalTab",
+ {subdialog: "showReturnReceipts"}, "receipts_pane");
+}
diff --git a/mailnews/extensions/mdn/content/am-mdn.xul b/mailnews/extensions/mdn/content/am-mdn.xul
new file mode 100644
index 000000000..c290752ab
--- /dev/null
+++ b/mailnews/extensions/mdn/content/am-mdn.xul
@@ -0,0 +1,136 @@
+<?xml version="1.0"?>
+
+<!--
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-mdn.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&pane.title;"
+ onload="parent.onPanelLoaded('am-mdn.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <stringbundle id="bundle_smime" src="chrome://messenger/locale/am-mdn.properties"/>
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-mdn.js"/>
+
+ <dialogheader title="&pane.title;"/>
+
+ <groupbox>
+
+ <caption label="&pane.title;"/>
+
+ <hbox id="prefChoices" align="center" flex="1">
+ <radiogroup id="identity.use_custom_prefs" wsm_persist="true" genericattr="true"
+ preftype="bool" prefstring="mail.identity.%identitykey%.use_custom_prefs"
+ oncommand="EnableDisableCustomSettings();" flex="1">
+ <radio id="identity.select_global_prefs"
+ value="false"
+ label="&useGlobalPrefs.label;"
+ accesskey="&useGlobalPrefs.accesskey;"/>
+ <hbox flex="1">
+ <spacer flex="1"/>
+ <button id="globalReceiptsLink"
+ label="&globalReceipts.label;"
+ accesskey="&globalReceipts.accesskey;"
+ oncommand="showGlobalReceipts();"/>
+ </hbox>
+ <radio id="identity.select_custom_prefs"
+ value="true"
+ label="&useCustomPrefs.label;"
+ accesskey="&useCustomPrefs.accesskey;"/>
+ </radiogroup>
+ </hbox>
+
+ <vbox id="returnReceiptSettings" class="indent" align="start">
+ <checkbox id="identity.request_return_receipt_on" label="&requestReceipt.label;"
+ accesskey="&requestReceipt.accesskey;"
+ wsm_persist="true" genericattr="true" iscontrolcontainer="true"
+ preftype="bool" prefstring="mail.identity.%identitykey%.request_return_receipt_on"/>
+
+ <separator/>
+
+ <vbox id="receiptArrive">
+ <label id="receiptArriveLabel" control="server.incorporate_return_receipt">&receiptArrive.label;</label>
+ <radiogroup id="server.incorporate_return_receipt" wsm_persist="true" genericattr="true"
+ preftype="int" prefstring="mail.server.%serverkey%.incorporate_return_receipt"
+ class="indent">
+ <radio id="leave_in_inbox" value="0" label="&leaveIt.label;"
+ accesskey="&leaveIt.accesskey;"/>
+ <radio id="move_to_sent" value="1" label="&moveToSent.label;"
+ accesskey="&moveToSent.accesskey;"/>
+ </radiogroup>
+ </vbox>
+
+ <separator/>
+
+ <vbox id="receiptRequest">
+ <label id="receiptRequestLabel" control="server.mdn_report_enabled">&requestMDN.label;</label>
+ <radiogroup id="server.mdn_report_enabled" wsm_persist="true" genericattr="true"
+ preftype="bool" prefstring="mail.server.%serverkey%.mdn_report_enabled"
+ oncommand="EnableDisableAllowedReceipts();"
+ class="indent">
+ <radio id="never_return" value="false" label="&never.label;"
+ accesskey="&never.accesskey;"/>
+ <radio id="return_some" value="true" label="&returnSome.label;"
+ accesskey="&returnSome.accesskey;"/>
+
+ <hbox id="receiptSendIf" class="indent">
+ <grid>
+ <columns><column/><column/></columns>
+ <rows>
+ <row align="center">
+ <label id="notInToCcLabel" value="&notInToCc.label;"
+ accesskey="&notInToCc.accesskey;" control="server.mdn_not_in_to_cc"/>
+ <menulist id="server.mdn_not_in_to_cc" wsm_persist="true" genericattr="true"
+ preftype="int" prefstring="mail.server.%serverkey%.mdn_not_in_to_cc">
+ <menupopup>
+ <menuitem value="0" label="&neverSend.label;"/>
+ <menuitem value="1" label="&alwaysSend.label;"/>
+ <menuitem value="2" label="&askMe.label;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label id="outsideDomainLabel" value="&outsideDomain.label;"
+ accesskey="&outsideDomain.accesskey;" control="server.mdn_outside_domain"/>
+ <menulist id="server.mdn_outside_domain" wsm_persist="true" genericattr="true"
+ preftype="int" prefstring="mail.server.%serverkey%.mdn_outside_domain">
+ <menupopup>
+ <menuitem value="0" label="&neverSend.label;"/>
+ <menuitem value="1" label="&alwaysSend.label;"/>
+ <menuitem value="2" label="&askMe.label;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label id="otherCasesLabel" value="&otherCases.label;"
+ accesskey="&otherCases.accesskey;" control="server.mdn_other"/>
+ <menulist id="server.mdn_other" wsm_persist="true" genericattr="true"
+ preftype="int" prefstring="mail.server.%serverkey%.mdn_other">
+ <menupopup>
+ <menuitem value="0" label="&neverSend.label;"/>
+ <menuitem value="1" label="&alwaysSend.label;"/>
+ <menuitem value="2" label="&askMe.label;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+ </radiogroup>
+
+ </vbox>
+
+ </vbox>
+
+ </groupbox>
+ </vbox>
+
+</page>
diff --git a/mailnews/extensions/mdn/content/mdn.js b/mailnews/extensions/mdn/content/mdn.js
new file mode 100644
index 000000000..9ac481573
--- /dev/null
+++ b/mailnews/extensions/mdn/content/mdn.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * default prefs for mdn
+ */
+
+pref("mail.identity.default.use_custom_prefs", false); // false: Use global true: Use custom
+
+pref("mail.identity.default.request_return_receipt_on", false);
+
+pref("mail.server.default.incorporate_return_receipt", 0); // 0: Inbox/filter 1: Sent folder
+
+pref("mail.server.default.mdn_report_enabled", true); // false: Never return receipts true: Return some receipts
+
+pref("mail.server.default.mdn_not_in_to_cc", 2); // 0: Never 1: Always 2: Ask me 3: Denial
+pref("mail.server.default.mdn_outside_domain", 2);
+pref("mail.server.default.mdn_other", 2);
+
+pref("mail.identity.default.request_receipt_header_type", 0); // return receipt header type - 0: MDN-DNT 1: RRT 2: Both
+
+pref("mail.server.default.mdn_report_enabled", true);
diff --git a/mailnews/extensions/mdn/jar.mn b/mailnews/extensions/mdn/jar.mn
new file mode 100644
index 000000000..74fde6b60
--- /dev/null
+++ b/mailnews/extensions/mdn/jar.mn
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+messenger.jar:
+ content/messenger/am-mdn.xul (content/am-mdn.xul)
+ content/messenger/am-mdn.js (content/am-mdn.js)
diff --git a/mailnews/extensions/mdn/moz.build b/mailnews/extensions/mdn/moz.build
new file mode 100644
index 000000000..5aaa32895
--- /dev/null
+++ b/mailnews/extensions/mdn/moz.build
@@ -0,0 +1,12 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['src']
+
+JAR_MANIFESTS += ['jar.mn']
+
+JS_PREFERENCE_FILES += [
+ 'content/mdn.js',
+]
diff --git a/mailnews/extensions/mdn/src/mdn-service.js b/mailnews/extensions/mdn/src/mdn-service.js
new file mode 100644
index 000000000..69f557274
--- /dev/null
+++ b/mailnews/extensions/mdn/src/mdn-service.js
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function MDNService() {}
+
+MDNService.prototype = {
+ name: "mdn",
+ chromePackageName: "messenger",
+ showPanel: function(server) {
+ // don't show the panel for news, rss, or local accounts
+ return (server.type != "nntp" && server.type != "rss" &&
+ server.type != "none");
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgAccountManagerExtension]),
+ classID: Components.ID("{e007d92e-1dd1-11b2-a61e-dc962c9b8571}"),
+};
+
+var components = [MDNService];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/extensions/mdn/src/mdn-service.manifest b/mailnews/extensions/mdn/src/mdn-service.manifest
new file mode 100644
index 000000000..493a3c69b
--- /dev/null
+++ b/mailnews/extensions/mdn/src/mdn-service.manifest
@@ -0,0 +1,3 @@
+component {e007d92e-1dd1-11b2-a61e-dc962c9b8571} mdn-service.js
+contract @mozilla.org/accountmanager/extension;1?name=mdn {e007d92e-1dd1-11b2-a61e-dc962c9b8571}
+category mailnews-accountmanager-extensions mdn-account-manager-extension @mozilla.org/accountmanager/extension;1?name=mdn
diff --git a/mailnews/extensions/mdn/src/moz.build b/mailnews/extensions/mdn/src/moz.build
new file mode 100644
index 000000000..1aef9b208
--- /dev/null
+++ b/mailnews/extensions/mdn/src/moz.build
@@ -0,0 +1,16 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsMsgMdnGenerator.cpp',
+]
+
+EXTRA_COMPONENTS += [
+ 'mdn-service.js',
+ 'mdn-service.manifest',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/extensions/mdn/src/nsMsgMdnCID.h b/mailnews/extensions/mdn/src/nsMsgMdnCID.h
new file mode 100644
index 000000000..4dd832b7f
--- /dev/null
+++ b/mailnews/extensions/mdn/src/nsMsgMdnCID.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgMdnCID_h__
+#define nsMsgMdnCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+#include "nsIMsgMdnGenerator.h"
+
+#define NS_MSGMDNGENERATOR_CONTRACTID \
+ "@mozilla.org/messenger-mdn/generator;1"
+#define NS_MSGMDNGENERATOR_CID \
+{ /* ec917b13-8f73-4d4d-9146-d7f7aafe9076 */ \
+ 0xec917b13, 0x8f73, 0x4d4d, \
+ { 0x91, 0x46, 0xd7, 0xf7, 0xaa, 0xfe, 0x90, 0x76 }}
+
+#endif /* nsMsgMdnCID_h__ */
diff --git a/mailnews/extensions/mdn/src/nsMsgMdnGenerator.cpp b/mailnews/extensions/mdn/src/nsMsgMdnGenerator.cpp
new file mode 100644
index 000000000..31b55fe2f
--- /dev/null
+++ b/mailnews/extensions/mdn/src/nsMsgMdnGenerator.cpp
@@ -0,0 +1,1139 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgMdnGenerator.h"
+#include "nsImapCore.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsMimeTypes.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "prsystem.h"
+#include "nsMsgI18N.h"
+#include "nsMailHeaders.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsIHttpProtocolHandler.h"
+#include "nsISmtpService.h" // for actually sending the message...
+#include "nsMsgCompCID.h"
+#include "nsComposeStrings.h"
+#include "nsISmtpServer.h"
+#include "nsIPrompt.h"
+#include "nsIMsgCompUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIStringBundle.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsMsgUtils.h"
+#include "nsNetUtil.h"
+#include "nsIMsgDatabase.h"
+#include "mozilla/Services.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+using namespace mozilla::mailnews;
+
+#define MDN_NOT_IN_TO_CC ((int) 0x0001)
+#define MDN_OUTSIDE_DOMAIN ((int) 0x0002)
+
+#define HEADER_RETURN_PATH "Return-Path"
+#define HEADER_DISPOSITION_NOTIFICATION_TO "Disposition-Notification-To"
+#define HEADER_APPARENTLY_TO "Apparently-To"
+#define HEADER_ORIGINAL_RECIPIENT "Original-Recipient"
+#define HEADER_REPORTING_UA "Reporting-UA"
+#define HEADER_MDN_GATEWAY "MDN-Gateway"
+#define HEADER_FINAL_RECIPIENT "Final-Recipient"
+#define HEADER_DISPOSITION "Disposition"
+#define HEADER_ORIGINAL_MESSAGE_ID "Original-Message-ID"
+#define HEADER_FAILURE "Failure"
+#define HEADER_ERROR "Error"
+#define HEADER_WARNING "Warning"
+#define HEADER_RETURN_RECEIPT_TO "Return-Receipt-To"
+#define HEADER_X_ACCEPT_LANGUAGE "X-Accept-Language"
+
+#define PUSH_N_FREE_STRING(p) \
+ do { if (p) { rv = WriteString(p); PR_smprintf_free(p); p=0; \
+ if (NS_FAILED(rv)) return rv; } \
+ else { return NS_ERROR_OUT_OF_MEMORY; } } while (0)
+
+// String bundle for mdn. Class static.
+#define MDN_STRINGBUNDLE_URL "chrome://messenger/locale/msgmdn.properties"
+
+#if defined(DEBUG_jefft)
+#define DEBUG_MDN(s) printf("%s\n", s)
+#else
+#define DEBUG_MDN(s)
+#endif
+
+// machine parsible string; should not be localized
+char DispositionTypes[7][16] = {
+ "displayed",
+ "dispatched",
+ "processed",
+ "deleted",
+ "denied",
+ "failed",
+ ""
+};
+
+NS_IMPL_ISUPPORTS(nsMsgMdnGenerator, nsIMsgMdnGenerator, nsIUrlListener)
+
+nsMsgMdnGenerator::nsMsgMdnGenerator()
+{
+ m_disposeType = eDisplayed;
+ m_outputStream = nullptr;
+ m_reallySendMdn = false;
+ m_autoSend = false;
+ m_autoAction = false;
+ m_mdnEnabled = false;
+ m_notInToCcOp = eNeverSendOp;
+ m_outsideDomainOp = eNeverSendOp;
+ m_otherOp = eNeverSendOp;
+}
+
+nsMsgMdnGenerator::~nsMsgMdnGenerator()
+{
+}
+
+nsresult nsMsgMdnGenerator::FormatStringFromName(const char16_t *aName,
+ const char16_t *aString,
+ char16_t **aResultString)
+{
+ DEBUG_MDN("nsMsgMdnGenerator::FormatStringFromName");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr <nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle(MDN_STRINGBUNDLE_URL,
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ const char16_t *formatStrings[1] = { aString };
+ rv = bundle->FormatStringFromName(aName,
+ formatStrings, 1, aResultString);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsMsgMdnGenerator::GetStringFromName(const char16_t *aName,
+ char16_t **aResultString)
+{
+ DEBUG_MDN("nsMsgMdnGenerator::GetStringFromName");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr <nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle(MDN_STRINGBUNDLE_URL,
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = bundle->GetStringFromName(aName, aResultString);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsMsgMdnGenerator::StoreMDNSentFlag(nsIMsgFolder *folder,
+ nsMsgKey key)
+{
+ DEBUG_MDN("nsMsgMdnGenerator::StoreMDNSentFlag");
+
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ nsresult rv = folder->GetMsgDatabase(getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDB->MarkMDNSent(key, true, nullptr);
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
+ // Store the $MDNSent flag if the folder is an Imap Mail Folder
+ if (imapFolder)
+ return imapFolder->StoreImapFlags(kImapMsgMDNSentFlag, true, &key, 1, nullptr);
+ return rv;
+}
+
+nsresult nsMsgMdnGenerator::ClearMDNNeededFlag(nsIMsgFolder *folder,
+ nsMsgKey key)
+{
+ DEBUG_MDN("nsMsgMdnGenerator::ClearMDNNeededFlag");
+
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ nsresult rv = folder->GetMsgDatabase(getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgDB->MarkMDNNeeded(key, false, nullptr);
+}
+
+bool nsMsgMdnGenerator::ProcessSendMode()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::ProcessSendMode");
+ int32_t miscState = 0;
+
+ if (m_identity)
+ {
+ m_identity->GetEmail(m_email);
+ if (m_email.IsEmpty())
+ return m_reallySendMdn;
+
+ const char *accountDomain = strchr(m_email.get(), '@');
+ if (!accountDomain)
+ return m_reallySendMdn;
+
+ if (MailAddrMatch(m_email.get(), m_dntRrt.get())) // return address is self, don't send
+ return false;
+
+ // *** fix me see Bug 132504 for more information
+ // *** what if the message has been filtered to different account
+ if (!PL_strcasestr(m_dntRrt.get(), accountDomain))
+ miscState |= MDN_OUTSIDE_DOMAIN;
+ if (NotInToOrCc())
+ miscState |= MDN_NOT_IN_TO_CC;
+ m_reallySendMdn = true;
+ // *********
+ // How are we gona deal with the auto forwarding issues? Some server
+ // didn't bother to add addition header or modify existing header to
+ // thev message when forwarding. They simply copy the exact same
+ // message to another user's mailbox. Some change To: to
+ // Apparently-To:
+ // Unfortunately, there is nothing we can do. It's out of our control.
+ // *********
+ // starting from lowest denominator to highest
+ if (!miscState)
+ { // under normal situation: recipent is in to and cc list,
+ // and the sender is from the same domain
+ switch (m_otherOp)
+ {
+ default:
+ case eNeverSendOp:
+ m_reallySendMdn = false;
+ break;
+ case eAutoSendOp:
+ m_autoSend = true;
+ break;
+ case eAskMeOp:
+ m_autoSend = false;
+ break;
+ case eDeniedOp:
+ m_autoSend = true;
+ m_disposeType = eDenied;
+ break;
+ }
+ }
+ else if (miscState == (MDN_OUTSIDE_DOMAIN | MDN_NOT_IN_TO_CC))
+ {
+ if (m_outsideDomainOp != m_notInToCcOp)
+ {
+ m_autoSend = false; // ambiguous; always ask user
+ }
+ else
+ {
+ switch (m_outsideDomainOp)
+ {
+ default:
+ case eNeverSendOp:
+ m_reallySendMdn = false;
+ break;
+ case eAutoSendOp:
+ m_autoSend = true;
+ break;
+ case eAskMeOp:
+ m_autoSend = false;
+ break;
+ }
+ }
+ }
+ else if (miscState & MDN_OUTSIDE_DOMAIN)
+ {
+ switch (m_outsideDomainOp)
+ {
+ default:
+ case eNeverSendOp:
+ m_reallySendMdn = false;
+ break;
+ case eAutoSendOp:
+ m_autoSend = true;
+ break;
+ case eAskMeOp:
+ m_autoSend = false;
+ break;
+ }
+ }
+ else if (miscState & MDN_NOT_IN_TO_CC)
+ {
+ switch (m_notInToCcOp)
+ {
+ default:
+ case eNeverSendOp:
+ m_reallySendMdn = false;
+ break;
+ case eAutoSendOp:
+ m_autoSend = true;
+ break;
+ case eAskMeOp:
+ m_autoSend = false;
+ break;
+ }
+ }
+ }
+ return m_reallySendMdn;
+}
+
+bool nsMsgMdnGenerator::MailAddrMatch(const char *addr1, const char *addr2)
+{
+ // Comparing two email addresses returns true if matched; local/account
+ // part comparison is case sensitive; domain part comparison is case
+ // insensitive
+ DEBUG_MDN("nsMsgMdnGenerator::MailAddrMatch");
+ bool isMatched = true;
+ const char *atSign1 = nullptr, *atSign2 = nullptr;
+ const char *lt = nullptr, *local1 = nullptr, *local2 = nullptr;
+ const char *end1 = nullptr, *end2 = nullptr;
+
+ if (!addr1 || !addr2)
+ return false;
+
+ lt = strchr(addr1, '<');
+ local1 = !lt ? addr1 : lt+1;
+ lt = strchr(addr2, '<');
+ local2 = !lt ? addr2 : lt+1;
+ end1 = strchr(local1, '>');
+ if (!end1)
+ end1 = addr1 + strlen(addr1);
+ end2 = strchr(local2, '>');
+ if (!end2)
+ end2 = addr2 + strlen(addr2);
+ atSign1 = strchr(local1, '@');
+ atSign2 = strchr(local2, '@');
+ if (!atSign1 || !atSign2 // ill formed addr spec
+ || (atSign1 - local1) != (atSign2 - local2))
+ isMatched = false;
+ else if (strncmp(local1, local2, (atSign1-local1))) // case sensitive
+ // compare for local part
+ isMatched = false;
+ else if ((end1 - atSign1) != (end2 - atSign2) ||
+ PL_strncasecmp(atSign1, atSign2, (end1-atSign1))) // case
+ // insensitive compare for domain part
+ isMatched = false;
+ return isMatched;
+}
+
+bool nsMsgMdnGenerator::NotInToOrCc()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::NotInToOrCc");
+ nsCString reply_to;
+ nsCString to;
+ nsCString cc;
+
+ m_identity->GetReplyTo(reply_to);
+ m_headers->ExtractHeader(HEADER_TO, true, to);
+ m_headers->ExtractHeader(HEADER_CC, true, cc);
+
+ // start with a simple check
+ if ((!to.IsEmpty() && PL_strcasestr(to.get(), m_email.get())) ||
+ (!cc.IsEmpty() && PL_strcasestr(cc.get(), m_email.get()))) {
+ return false;
+ }
+
+ if ((!reply_to.IsEmpty() && !to.IsEmpty() && PL_strcasestr(to.get(), reply_to.get())) ||
+ (!reply_to.IsEmpty() && !cc.IsEmpty() && PL_strcasestr(cc.get(), reply_to.get()))) {
+ return false;
+ }
+ return true;
+}
+
+bool nsMsgMdnGenerator::ValidateReturnPath()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::ValidateReturnPath");
+ // ValidateReturnPath applies to Automatic Send Mode only. If we were not
+ // in auto send mode we simply by passing the check
+ if (!m_autoSend)
+ return m_reallySendMdn;
+
+ nsCString returnPath;
+ m_headers->ExtractHeader(HEADER_RETURN_PATH, false, returnPath);
+ if (returnPath.IsEmpty())
+ {
+ m_autoSend = false;
+ return m_reallySendMdn;
+ }
+ m_autoSend = MailAddrMatch(returnPath.get(), m_dntRrt.get());
+ return m_reallySendMdn;
+}
+
+nsresult nsMsgMdnGenerator::CreateMdnMsg()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::CreateMdnMsg");
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> tmpFile;
+ rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "mdnmsg",
+ getter_AddRefs(m_file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream),
+ m_file,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"creating mdn: failed to output stream");
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ rv = CreateFirstPart();
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = CreateSecondPart();
+ if (NS_SUCCEEDED(rv))
+ rv = CreateThirdPart();
+ }
+
+ if (m_outputStream)
+ {
+ m_outputStream->Flush();
+ m_outputStream->Close();
+ }
+ if (NS_FAILED(rv))
+ m_file->Remove(false);
+ else
+ rv = SendMdnMsg();
+
+ return NS_OK;
+}
+
+nsresult nsMsgMdnGenerator::CreateFirstPart()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::CreateFirstPart");
+ char *convbuf = nullptr, *tmpBuffer = nullptr;
+ char *parm = nullptr;
+ nsString firstPart1;
+ nsString firstPart2;
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgCompUtils> compUtils;
+
+ if (m_mimeSeparator.IsEmpty())
+ {
+ compUtils = do_GetService(NS_MSGCOMPUTILS_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = compUtils->MimeMakeSeparator("mdn", getter_Copies(m_mimeSeparator));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (m_mimeSeparator.IsEmpty())
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ tmpBuffer = (char *) PR_CALLOC(256);
+
+ if (!tmpBuffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
+
+ int gmtoffset = (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset)
+ / 60;
+ /* Use PR_FormatTimeUSEnglish() to format the date in US English format,
+ then figure out what our local GMT offset is, and append it (since
+ PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as
+ per RFC 1123 (superceding RFC 822.)
+ */
+ PR_FormatTimeUSEnglish(tmpBuffer, 100,
+ "Date: %a, %d %b %Y %H:%M:%S ",
+ &now);
+
+ PR_snprintf(tmpBuffer + strlen(tmpBuffer), 100,
+ "%c%02d%02d" CRLF,
+ (gmtoffset >= 0 ? '+' : '-'),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60));
+
+ rv = WriteString(tmpBuffer);
+ PR_Free(tmpBuffer);
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool conformToStandard = false;
+ if (compUtils)
+ compUtils->GetMsgMimeConformToStandard(&conformToStandard);
+
+ nsString fullName;
+ m_identity->GetFullName(fullName);
+
+ nsCString fullAddress;
+ // convert fullName to UTF8 before passing it to MakeMimeAddress
+ MakeMimeAddress(NS_ConvertUTF16toUTF8(fullName), m_email, fullAddress);
+
+ convbuf = nsMsgI18NEncodeMimePartIIStr(fullAddress.get(),
+ true, m_charset.get(), 0, conformToStandard);
+
+ parm = PR_smprintf("From: %s" CRLF, convbuf ? convbuf : m_email.get());
+
+ rv = FormatStringFromName(u"MsgMdnMsgSentTo", NS_ConvertASCIItoUTF16(m_email).get(),
+ getter_Copies(firstPart1));
+ if (NS_FAILED(rv))
+ return rv;
+
+ PUSH_N_FREE_STRING (parm);
+
+ PR_Free(convbuf);
+
+ if (compUtils)
+ {
+ nsCString msgId;
+ rv = compUtils->MsgGenerateMessageId(m_identity, getter_Copies(msgId));
+ tmpBuffer = PR_smprintf("Message-ID: %s" CRLF, msgId.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+ }
+
+ nsString receipt_string;
+ switch (m_disposeType)
+ {
+ case nsIMsgMdnGenerator::eDisplayed:
+ rv = GetStringFromName(
+ u"MdnDisplayedReceipt",
+ getter_Copies(receipt_string));
+ break;
+ case nsIMsgMdnGenerator::eDispatched:
+ rv = GetStringFromName(
+ u"MdnDispatchedReceipt",
+ getter_Copies(receipt_string));
+ break;
+ case nsIMsgMdnGenerator::eProcessed:
+ rv = GetStringFromName(
+ u"MdnProcessedReceipt",
+ getter_Copies(receipt_string));
+ break;
+ case nsIMsgMdnGenerator::eDeleted:
+ rv = GetStringFromName(
+ u"MdnDeletedReceipt",
+ getter_Copies(receipt_string));
+ break;
+ case nsIMsgMdnGenerator::eDenied:
+ rv = GetStringFromName(
+ u"MdnDeniedReceipt",
+ getter_Copies(receipt_string));
+ break;
+ case nsIMsgMdnGenerator::eFailed:
+ rv = GetStringFromName(
+ u"MdnFailedReceipt",
+ getter_Copies(receipt_string));
+ break;
+ default:
+ rv = NS_ERROR_INVALID_ARG;
+ break;
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ receipt_string.AppendLiteral(" - ");
+
+ char * encodedReceiptString = nsMsgI18NEncodeMimePartIIStr(NS_ConvertUTF16toUTF8(receipt_string).get(), false,
+ "UTF-8", 0, conformToStandard);
+
+ nsCString subject;
+ m_headers->ExtractHeader(HEADER_SUBJECT, false, subject);
+ convbuf = nsMsgI18NEncodeMimePartIIStr(subject.Length() ? subject.get() : "[no subject]",
+ false, m_charset.get(), 0, conformToStandard);
+ tmpBuffer = PR_smprintf("Subject: %s%s" CRLF,
+ encodedReceiptString,
+ (convbuf ? convbuf : (subject.Length() ? subject.get() :
+ "[no subject]")));
+
+ PUSH_N_FREE_STRING(tmpBuffer);
+ PR_Free(convbuf);
+ PR_Free(encodedReceiptString);
+
+ convbuf = nsMsgI18NEncodeMimePartIIStr(m_dntRrt.get(), true, m_charset.get(), 0, conformToStandard);
+ tmpBuffer = PR_smprintf("To: %s" CRLF, convbuf ? convbuf :
+ m_dntRrt.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ PR_Free(convbuf);
+
+ // *** This is not in the spec. I am adding this so we could do
+ // threading
+ m_headers->ExtractHeader(HEADER_MESSAGE_ID, false, m_messageId);
+
+ if (!m_messageId.IsEmpty())
+ {
+ if (*m_messageId.get() == '<')
+ tmpBuffer = PR_smprintf("References: %s" CRLF, m_messageId.get());
+ else
+ tmpBuffer = PR_smprintf("References: <%s>" CRLF, m_messageId.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+ }
+ tmpBuffer = PR_smprintf("%s" CRLF, "MIME-Version: 1.0");
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("Content-Type: multipart/report; \
+report-type=disposition-notification;\r\n\tboundary=\"%s\"" CRLF CRLF,
+ m_mimeSeparator.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("--%s" CRLF, m_mimeSeparator.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("Content-Type: text/plain; charset=UTF-8" CRLF);
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("Content-Transfer-Encoding: %s" CRLF CRLF,
+ ENCODING_8BIT);
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ if (!firstPart1.IsEmpty())
+ {
+ tmpBuffer = PR_smprintf("%s" CRLF CRLF, NS_ConvertUTF16toUTF8(firstPart1).get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+ }
+
+ switch (m_disposeType)
+ {
+ case nsIMsgMdnGenerator::eDisplayed:
+ rv = GetStringFromName(
+ u"MsgMdnDisplayed",
+ getter_Copies(firstPart2));
+ break;
+ case nsIMsgMdnGenerator::eDispatched:
+ rv = GetStringFromName(
+ u"MsgMdnDispatched",
+ getter_Copies(firstPart2));
+ break;
+ case nsIMsgMdnGenerator::eProcessed:
+ rv = GetStringFromName(
+ u"MsgMdnProcessed",
+ getter_Copies(firstPart2));
+ break;
+ case nsIMsgMdnGenerator::eDeleted:
+ rv = GetStringFromName(
+ u"MsgMdnDeleted",
+ getter_Copies(firstPart2));
+ break;
+ case nsIMsgMdnGenerator::eDenied:
+ rv = GetStringFromName(
+ u"MsgMdnDenied",
+ getter_Copies(firstPart2));
+ break;
+ case nsIMsgMdnGenerator::eFailed:
+ rv = GetStringFromName(
+ u"MsgMdnFailed",
+ getter_Copies(firstPart2));
+ break;
+ default:
+ rv = NS_ERROR_INVALID_ARG;
+ break;
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!firstPart2.IsEmpty())
+ {
+ tmpBuffer =
+ PR_smprintf("%s" CRLF CRLF,
+ NS_ConvertUTF16toUTF8(firstPart2).get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+ }
+
+ return rv;
+}
+
+nsresult nsMsgMdnGenerator::CreateSecondPart()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::CreateSecondPart");
+ char *tmpBuffer = nullptr;
+ char *convbuf = nullptr;
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgCompUtils> compUtils;
+ bool conformToStandard = false;
+
+ tmpBuffer = PR_smprintf("--%s" CRLF, m_mimeSeparator.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("%s" CRLF, "Content-Type: message/disposition-notification; name=\042MDNPart2.txt\042");
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("%s" CRLF, "Content-Disposition: inline");
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("Content-Transfer-Encoding: %s" CRLF CRLF,
+ ENCODING_7BIT);
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ nsCOMPtr<nsIHttpProtocolHandler> pHTTPHandler =
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv);
+ if (NS_SUCCEEDED(rv) && pHTTPHandler)
+ {
+ nsAutoCString userAgentString;
+ pHTTPHandler->GetUserAgent(userAgentString);
+
+ if (!userAgentString.IsEmpty())
+ {
+ // Prepend the product name with the dns name according to RFC 3798.
+ char hostName[256];
+ PR_GetSystemInfo(PR_SI_HOSTNAME_UNTRUNCATED, hostName, sizeof hostName);
+ if ((hostName[0] != '\0') && (strchr(hostName, '.') != NULL))
+ {
+ userAgentString.Insert("; ", 0);
+ userAgentString.Insert(nsDependentCString(hostName), 0);
+ }
+
+ tmpBuffer = PR_smprintf("Reporting-UA: %s" CRLF,
+ userAgentString.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+ }
+ }
+
+ nsCString originalRecipient;
+ m_headers->ExtractHeader(HEADER_ORIGINAL_RECIPIENT, false,
+ originalRecipient);
+
+ if (!originalRecipient.IsEmpty())
+ {
+ tmpBuffer = PR_smprintf("Original-Recipient: %s" CRLF,
+ originalRecipient.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+ }
+
+ compUtils = do_GetService(NS_MSGCOMPUTILS_CONTRACTID, &rv);
+ if (compUtils)
+ compUtils->GetMsgMimeConformToStandard(&conformToStandard);
+
+ convbuf = nsMsgI18NEncodeMimePartIIStr(
+ m_email.get(), true, m_charset.get(), 0,
+ conformToStandard);
+ tmpBuffer = PR_smprintf("Final-Recipient: rfc822;%s" CRLF, convbuf ?
+ convbuf : m_email.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ PR_Free (convbuf);
+
+ if (*m_messageId.get() == '<')
+ tmpBuffer = PR_smprintf("Original-Message-ID: %s" CRLF, m_messageId.get());
+ else
+ tmpBuffer = PR_smprintf("Original-Message-ID: <%s>" CRLF, m_messageId.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("Disposition: %s/%s; %s" CRLF CRLF,
+ (m_autoAction ? "automatic-action" :
+ "manual-action"),
+ (m_autoSend ? "MDN-sent-automatically" :
+ "MDN-sent-manually"),
+ DispositionTypes[(int) m_disposeType]);
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ return rv;
+}
+
+nsresult nsMsgMdnGenerator::CreateThirdPart()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::CreateThirdPart");
+ char *tmpBuffer = nullptr;
+ nsresult rv = NS_OK;
+
+ tmpBuffer = PR_smprintf("--%s" CRLF, m_mimeSeparator.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("%s" CRLF, "Content-Type: text/rfc822-headers; name=\042MDNPart3.txt\042");
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("%s" CRLF, "Content-Transfer-Encoding: 7bit");
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ tmpBuffer = PR_smprintf("%s" CRLF CRLF, "Content-Disposition: inline");
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ rv = OutputAllHeaders();
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = WriteString(CRLF);
+ if (NS_FAILED(rv))
+ return rv;
+
+ tmpBuffer = PR_smprintf("--%s--" CRLF, m_mimeSeparator.get());
+ PUSH_N_FREE_STRING(tmpBuffer);
+
+ return rv;
+}
+
+
+nsresult nsMsgMdnGenerator::OutputAllHeaders()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::OutputAllHeaders");
+ nsCString all_headers;
+ int32_t all_headers_size = 0;
+ nsresult rv = NS_OK;
+
+ rv = m_headers->GetAllHeaders(all_headers);
+ if (NS_FAILED(rv))
+ return rv;
+ all_headers_size = all_headers.Length();
+ char *buf = (char *) all_headers.get(),
+ *buf_end = (char *) all_headers.get()+all_headers_size;
+ char *start = buf, *end = buf;
+
+ while (buf < buf_end)
+ {
+ switch (*buf)
+ {
+ case 0:
+ if (*(buf+1) == '\n')
+ {
+ // *buf = '\r';
+ end = buf;
+ }
+ else if (*(buf+1) == 0)
+ {
+ // the case of message id
+ *buf = '>';
+ }
+ break;
+ case '\r':
+ end = buf;
+ *buf = 0;
+ break;
+ case '\n':
+ if (buf > start && *(buf-1) == 0)
+ {
+ start = buf + 1;
+ end = start;
+ }
+ else
+ {
+ end = buf;
+ }
+ *buf = 0;
+ break;
+ default:
+ break;
+ }
+ buf++;
+
+ if (end > start && *end == 0)
+ {
+ // strip out private X-Mozilla-Status header & X-Mozilla-Draft-Info && envelope header
+ if (!PL_strncasecmp(start, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN)
+ || !PL_strncasecmp(start, X_MOZILLA_DRAFT_INFO, X_MOZILLA_DRAFT_INFO_LEN)
+ || !PL_strncasecmp(start, "From ", 5))
+ {
+ while ( end < buf_end &&
+ (*end == '\n' || *end == '\r' || *end == 0))
+ end++;
+ start = end;
+ }
+ else
+ {
+ NS_ASSERTION (*end == 0, "content of end should be null");
+ rv = WriteString(start);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = WriteString(CRLF);
+ while ( end < buf_end &&
+ (*end == '\n' || *end == '\r' || *end == 0))
+ end++;
+ start = end;
+ }
+ buf = start;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgMdnGenerator::SendMdnMsg()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::SendMdnMsg");
+ nsresult rv;
+ nsCOMPtr<nsISmtpService> smtpService = do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIRequest> aRequest;
+ smtpService->SendMailMessage(m_file, m_dntRrt.get(), m_identity,
+ nullptr, this, nullptr, nullptr, false, nullptr,
+ getter_AddRefs(aRequest));
+
+ return NS_OK;
+}
+
+nsresult nsMsgMdnGenerator::WriteString( const char *str )
+{
+ NS_ENSURE_ARG (str);
+ uint32_t len = strlen(str);
+ uint32_t wLen = 0;
+
+ return m_outputStream->Write(str, len, &wLen);
+}
+
+nsresult nsMsgMdnGenerator::InitAndProcess(bool *needToAskUser)
+{
+ DEBUG_MDN("nsMsgMdnGenerator::InitAndProcess");
+ nsresult rv = m_folder->GetServer(getter_AddRefs(m_server));
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (accountManager && m_server)
+ {
+ if (!m_identity)
+ {
+ // check if this is a message delivered to the global inbox,
+ // in which case we find the originating account's identity.
+ nsCString accountKey;
+ m_headers->ExtractHeader(HEADER_X_MOZILLA_ACCOUNT_KEY, false,
+ accountKey);
+ nsCOMPtr <nsIMsgAccount> account;
+ if (!accountKey.IsEmpty())
+ accountManager->GetAccount(accountKey, getter_AddRefs(account));
+ if (account)
+ account->GetIncomingServer(getter_AddRefs(m_server));
+
+ if (m_server)
+ {
+ // Find the correct identity based on the "To:" and "Cc:" header
+ nsCString mailTo;
+ nsCString mailCC;
+ m_headers->ExtractHeader(HEADER_TO, true, mailTo);
+ m_headers->ExtractHeader(HEADER_CC, true, mailCC);
+ nsCOMPtr<nsIArray> servIdentities;
+ accountManager->GetIdentitiesForServer(m_server, getter_AddRefs(servIdentities));
+ if (servIdentities)
+ {
+ nsCOMPtr<nsIMsgIdentity> ident;
+ nsCString identEmail;
+ uint32_t count = 0;
+ servIdentities->GetLength(&count);
+ // First check in the "To:" header
+ for (uint32_t i = 0; i < count; i++)
+ {
+ ident = do_QueryElementAt(servIdentities, i, &rv);
+ if (NS_FAILED(rv))
+ continue;
+ ident->GetEmail(identEmail);
+ if (!mailTo.IsEmpty() && !identEmail.IsEmpty() &&
+ mailTo.Find(identEmail, CaseInsensitiveCompare) != kNotFound)
+ {
+ m_identity = ident;
+ break;
+ }
+ }
+ // If no match, check the "Cc:" header
+ if (!m_identity)
+ {
+ for (uint32_t i = 0; i < count; i++)
+ {
+ rv = servIdentities->QueryElementAt(i, NS_GET_IID(nsIMsgIdentity),getter_AddRefs(ident));
+ if (NS_FAILED(rv))
+ continue;
+ ident->GetEmail(identEmail);
+ if (!mailCC.IsEmpty() && !identEmail.IsEmpty() &&
+ mailCC.Find(identEmail, CaseInsensitiveCompare) != kNotFound)
+ {
+ m_identity = ident;
+ break;
+ }
+ }
+ }
+ }
+
+ // If no match again, use the first identity
+ if (!m_identity)
+ rv = accountManager->GetFirstIdentityForServer(m_server, getter_AddRefs(m_identity));
+ }
+ }
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (m_identity)
+ {
+ bool useCustomPrefs = false;
+ m_identity->GetBoolAttribute("use_custom_prefs", &useCustomPrefs);
+ if (useCustomPrefs)
+ {
+ bool bVal = false;
+ m_server->GetBoolValue("mdn_report_enabled", &bVal);
+ m_mdnEnabled = bVal;
+ m_server->GetIntValue("mdn_not_in_to_cc", &m_notInToCcOp);
+ m_server->GetIntValue("mdn_outside_domain",
+ &m_outsideDomainOp);
+ m_server->GetIntValue("mdn_other", &m_otherOp);
+ }
+ else
+ {
+ bool bVal = false;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if(prefBranch)
+ {
+ prefBranch->GetBoolPref("mail.mdn.report.enabled",
+ &bVal);
+ m_mdnEnabled = bVal;
+ prefBranch->GetIntPref("mail.mdn.report.not_in_to_cc",
+ &m_notInToCcOp);
+ prefBranch->GetIntPref("mail.mdn.report.outside_domain",
+ &m_outsideDomainOp);
+ prefBranch->GetIntPref("mail.mdn.report.other",
+ &m_otherOp);
+ }
+ }
+ }
+ }
+
+ rv = m_folder->GetCharset(m_charset);
+ if (m_mdnEnabled)
+ {
+ m_headers->ExtractHeader(HEADER_DISPOSITION_NOTIFICATION_TO, false,
+ m_dntRrt);
+ if (m_dntRrt.IsEmpty())
+ m_headers->ExtractHeader(HEADER_RETURN_RECEIPT_TO, false,
+ m_dntRrt);
+ if (!m_dntRrt.IsEmpty() && ProcessSendMode() && ValidateReturnPath())
+ {
+ if (!m_autoSend)
+ {
+ *needToAskUser = true;
+ rv = NS_OK;
+ }
+ else
+ {
+ *needToAskUser = false;
+ rv = UserAgreed();
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMdnGenerator::Process(EDisposeType type,
+ nsIMsgWindow *aWindow,
+ nsIMsgFolder *folder,
+ nsMsgKey key,
+ nsIMimeHeaders *headers,
+ bool autoAction,
+ bool *_retval)
+{
+ DEBUG_MDN("nsMsgMdnGenerator::Process");
+ NS_ENSURE_ARG_POINTER(folder);
+ NS_ENSURE_ARG_POINTER(headers);
+ NS_ENSURE_ARG_POINTER(aWindow);
+ NS_ENSURE_TRUE(key != nsMsgKey_None, NS_ERROR_INVALID_ARG);
+ m_disposeType = type;
+ m_autoAction = autoAction;
+ m_window = aWindow;
+ m_folder = folder;
+ m_headers = headers;
+ m_key = key;
+
+ mozilla::DebugOnly<nsresult> rv = InitAndProcess(_retval);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "InitAndProcess failed");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMdnGenerator::UserAgreed()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::UserAgreed");
+ (void) NoteMDNRequestHandled();
+ return CreateMdnMsg();
+}
+
+NS_IMETHODIMP nsMsgMdnGenerator::UserDeclined()
+{
+ DEBUG_MDN("nsMsgMdnGenerator::UserDeclined");
+ return NoteMDNRequestHandled();
+}
+
+/**
+ * Set/clear flags appropriately so we won't ask user again about MDN
+ * request for this message.
+ */
+nsresult nsMsgMdnGenerator::NoteMDNRequestHandled()
+{
+ nsresult rv = StoreMDNSentFlag(m_folder, m_key);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "StoreMDNSentFlag failed");
+ rv = ClearMDNNeededFlag(m_folder, m_key);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "ClearMDNNeededFlag failed");
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMdnGenerator::OnStartRunningUrl(nsIURI *url)
+{
+ DEBUG_MDN("nsMsgMdnGenerator::OnStartRunningUrl");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMdnGenerator::OnStopRunningUrl(nsIURI *url,
+ nsresult aExitCode)
+{
+ nsresult rv;
+
+ DEBUG_MDN("nsMsgMdnGenerator::OnStopRunningUrl");
+ if (m_file)
+ m_file->Remove(false);
+
+ if (NS_SUCCEEDED(aExitCode))
+ return NS_OK;
+
+ const char16_t* exitString;
+
+ switch (aExitCode)
+ {
+ case NS_ERROR_UNKNOWN_HOST:
+ case NS_ERROR_UNKNOWN_PROXY_HOST:
+ exitString = u"smtpSendFailedUnknownServer";
+ break;
+ case NS_ERROR_CONNECTION_REFUSED:
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ exitString = u"smtpSendRequestRefused";
+ break;
+ case NS_ERROR_NET_INTERRUPT:
+ case NS_ERROR_ABORT: // we have no proper string for error code NS_ERROR_ABORT in compose bundle
+ exitString = u"smtpSendInterrupted";
+ break;
+ case NS_ERROR_NET_TIMEOUT:
+ case NS_ERROR_NET_RESET:
+ exitString = u"smtpSendTimeout";
+ break;
+ default:
+ exitString = errorStringNameForErrorCode(aExitCode);
+ break;
+ }
+
+ nsCOMPtr<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Get the smtp hostname and format the string.
+ nsCString smtpHostName;
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = smtpService->GetServerByIdentity(m_identity, getter_AddRefs(smtpServer));
+ if (NS_SUCCEEDED(rv))
+ smtpServer->GetHostname(smtpHostName);
+
+ nsAutoString hostStr;
+ CopyASCIItoUTF16(smtpHostName, hostStr);
+ const char16_t *params[] = { hostStr.get() };
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString failed_msg, dialogTitle;
+
+ bundle->FormatStringFromName(exitString, params, 1, getter_Copies(failed_msg));
+ bundle->GetStringFromName(u"sendMessageErrorTitle", getter_Copies(dialogTitle));
+
+ nsCOMPtr<nsIPrompt> dialog;
+ rv = m_window->GetPromptDialog(getter_AddRefs(dialog));
+ if (NS_SUCCEEDED(rv))
+ dialog->Alert(dialogTitle.get(),failed_msg.get());
+
+ return NS_OK;
+}
diff --git a/mailnews/extensions/mdn/src/nsMsgMdnGenerator.h b/mailnews/extensions/mdn/src/nsMsgMdnGenerator.h
new file mode 100644
index 000000000..54b6efab4
--- /dev/null
+++ b/mailnews/extensions/mdn/src/nsMsgMdnGenerator.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMsgMdnGenerator_H_
+#define _nsMsgMdnGenerator_H_
+
+#include "nsIMsgMdnGenerator.h"
+#include "nsCOMPtr.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIOutputStream.h"
+#include "nsIFile.h"
+#include "nsIMsgIdentity.h"
+#include "nsIMsgWindow.h"
+#include "nsIMimeHeaders.h"
+#include "nsStringGlue.h"
+#include "MailNewsTypes2.h"
+
+#define eNeverSendOp ((int32_t) 0)
+#define eAutoSendOp ((int32_t) 1)
+#define eAskMeOp ((int32_t) 2)
+#define eDeniedOp ((int32_t) 3)
+
+class nsMsgMdnGenerator : public nsIMsgMdnGenerator, public nsIUrlListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGMDNGENERATOR
+ NS_DECL_NSIURLLISTENER
+
+ nsMsgMdnGenerator();
+
+private:
+ virtual ~nsMsgMdnGenerator();
+
+ // Sanity Check methods
+ bool ProcessSendMode(); // must called prior ValidateReturnPath
+ bool ValidateReturnPath();
+ bool NotInToOrCc();
+ bool MailAddrMatch(const char *addr1, const char *addr2);
+
+ nsresult StoreMDNSentFlag(nsIMsgFolder *folder, nsMsgKey key);
+ nsresult ClearMDNNeededFlag(nsIMsgFolder *folder, nsMsgKey key);
+ nsresult NoteMDNRequestHandled();
+
+ nsresult CreateMdnMsg();
+ nsresult CreateFirstPart();
+ nsresult CreateSecondPart();
+ nsresult CreateThirdPart();
+ nsresult SendMdnMsg();
+
+ // string bundle helper methods
+ nsresult GetStringFromName(const char16_t *aName, char16_t **aResultString);
+ nsresult FormatStringFromName(const char16_t *aName,
+ const char16_t *aString,
+ char16_t **aResultString);
+
+ // other helper methods
+ nsresult InitAndProcess(bool *needToAskUser);
+ nsresult OutputAllHeaders();
+ nsresult WriteString(const char *str);
+
+private:
+ EDisposeType m_disposeType;
+ nsCOMPtr<nsIMsgWindow> m_window;
+ nsCOMPtr<nsIOutputStream> m_outputStream;
+ nsCOMPtr<nsIFile> m_file;
+ nsCOMPtr<nsIMsgIdentity> m_identity;
+ nsMsgKey m_key;
+ nsCString m_charset;
+ nsCString m_email;
+ nsCString m_mimeSeparator;
+ nsCString m_messageId;
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsCOMPtr<nsIMsgIncomingServer> m_server;
+ nsCOMPtr<nsIMimeHeaders> m_headers;
+ nsCString m_dntRrt;
+ int32_t m_notInToCcOp;
+ int32_t m_outsideDomainOp;
+ int32_t m_otherOp;
+ bool m_reallySendMdn;
+ bool m_autoSend;
+ bool m_autoAction;
+ bool m_mdnEnabled;
+};
+
+#endif // _nsMsgMdnGenerator_H_
+
diff --git a/mailnews/extensions/moz.build b/mailnews/extensions/moz.build
new file mode 100644
index 000000000..846e47871
--- /dev/null
+++ b/mailnews/extensions/moz.build
@@ -0,0 +1,19 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# These extensions are not optional.
+DIRS += [
+ 'mdn',
+ 'mailviews/public',
+ 'mailviews/src',
+ 'mailviews/content',
+ 'bayesian-spam-filter',
+ 'offline-startup',
+ 'newsblog',
+ 'fts3/public',
+ 'fts3/src',
+ 'smime',
+]
+
diff --git a/mailnews/extensions/newsblog/content/Feed.js b/mailnews/extensions/newsblog/content/Feed.js
new file mode 100644
index 000000000..7e47260a8
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/Feed.js
@@ -0,0 +1,620 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Cache for all of the feeds currently being downloaded, indexed by URL,
+// so the load event listener can access the Feed objects after it finishes
+// downloading the feed.
+var FeedCache =
+{
+ mFeeds: {},
+
+ putFeed: function (aFeed)
+ {
+ this.mFeeds[this.normalizeHost(aFeed.url)] = aFeed;
+ },
+
+ getFeed: function (aUrl)
+ {
+ let index = this.normalizeHost(aUrl);
+ if (index in this.mFeeds)
+ return this.mFeeds[index];
+
+ return null;
+ },
+
+ removeFeed: function (aUrl)
+ {
+ let index = this.normalizeHost(aUrl);
+ if (index in this.mFeeds)
+ delete this.mFeeds[index];
+ },
+
+ normalizeHost: function (aUrl)
+ {
+ try
+ {
+ let normalizedUrl = Services.io.newURI(aUrl, null, null);
+ normalizedUrl.host = normalizedUrl.host.toLowerCase();
+ return normalizedUrl.spec
+ }
+ catch (ex)
+ {
+ return aUrl;
+ }
+ }
+};
+
+function Feed(aResource, aRSSServer)
+{
+ this.resource = aResource.QueryInterface(Ci.nsIRDFResource);
+ this.server = aRSSServer;
+}
+
+Feed.prototype =
+{
+ description: null,
+ author: null,
+ request: null,
+ server: null,
+ downloadCallback: null,
+ resource: null,
+ items: new Array(),
+ itemsStored: 0,
+ mFolder: null,
+ mInvalidFeed: false,
+ mFeedType: null,
+ mLastModified: null,
+
+ get folder()
+ {
+ return this.mFolder;
+ },
+
+ set folder (aFolder)
+ {
+ this.mFolder = aFolder;
+ },
+
+ get name()
+ {
+ // Used for the feed's title in Subcribe dialog and opml export.
+ let name = this.title || this.description || this.url;
+ return name.replace(/[\n\r\t]+/g, " ").replace(/[\x00-\x1F]+/g, "");
+ },
+
+ get folderName()
+ {
+ if (this.mFolderName)
+ return this.mFolderName;
+
+ // Get a unique sanitized name. Use title or description as a base;
+ // these are mandatory by spec. Length of 80 is plenty.
+ let folderName = (this.title || this.description || "").substr(0,80);
+ let defaultName = FeedUtils.strings.GetStringFromName("ImportFeedsNew");
+ return this.mFolderName = FeedUtils.getSanitizedFolderName(this.server.rootMsgFolder,
+ folderName,
+ defaultName,
+ true);
+ },
+
+ download: function(aParseItems, aCallback)
+ {
+ // May be null.
+ this.downloadCallback = aCallback;
+
+ // Whether or not to parse items when downloading and parsing the feed.
+ // Defaults to true, but setting to false is useful for obtaining
+ // just the title of the feed when the user subscribes to it.
+ this.parseItems = aParseItems == null ? true : aParseItems ? true : false;
+
+ // Before we do anything, make sure the url is an http url. This is just
+ // a sanity check so we don't try opening mailto urls, imap urls, etc. that
+ // the user may have tried to subscribe to as an rss feed.
+ if (!FeedUtils.isValidScheme(this.url))
+ {
+ // Simulate an invalid feed error.
+ FeedUtils.log.info("Feed.download: invalid protocol for - " + this.url);
+ this.onParseError(this);
+ return;
+ }
+
+ // Before we try to download the feed, make sure we aren't already
+ // processing the feed by looking up the url in our feed cache.
+ if (FeedCache.getFeed(this.url))
+ {
+ if (this.downloadCallback)
+ this.downloadCallback.downloaded(this, FeedUtils.kNewsBlogFeedIsBusy);
+ // Return, the feed is already in use.
+ return;
+ }
+
+ if (Services.io.offline) {
+ // If offline and don't want to go online, just add the feed subscription;
+ // it can be verified later (the folder name will be the url if not adding
+ // to an existing folder). Only for subscribe actions; passive biff and
+ // active get new messages are handled prior to getting here.
+ let win = Services.wm.getMostRecentWindow("mail:3pane");
+ if (!win.MailOfflineMgr.getNewMail()) {
+ this.storeNextItem();
+ return;
+ }
+ }
+
+ this.request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsIXMLHttpRequest);
+ // Must set onProgress before calling open.
+ this.request.onprogress = this.onProgress;
+ this.request.open("GET", this.url, true);
+ this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE |
+ Ci.nsIRequest.INHIBIT_CACHING;
+
+ // Some servers, if sent If-Modified-Since, will send 304 if subsequently
+ // not sent If-Modified-Since, as in the case of an unsubscribe and new
+ // subscribe. Send start of century date to force a download; some servers
+ // will 304 on older dates (such as epoch 1970).
+ let lastModified = this.lastModified || "Sat, 01 Jan 2000 00:00:00 GMT";
+ this.request.setRequestHeader("If-Modified-Since", lastModified);
+
+ // Only order what you're going to eat...
+ this.request.responseType = "document";
+ this.request.overrideMimeType("text/xml");
+ this.request.setRequestHeader("Accept", FeedUtils.REQUEST_ACCEPT);
+ this.request.timeout = FeedUtils.REQUEST_TIMEOUT;
+ this.request.onload = this.onDownloaded;
+ this.request.onerror = this.onDownloadError;
+ this.request.ontimeout = this.onDownloadError;
+ FeedCache.putFeed(this);
+ this.request.send(null);
+ },
+
+ onDownloaded: function(aEvent)
+ {
+ let request = aEvent.target;
+ let isHttp = request.channel.originalURI.scheme.startsWith("http");
+ let url = request.channel.originalURI.spec;
+ if (isHttp && (request.status < 200 || request.status >= 300))
+ {
+ Feed.prototype.onDownloadError(aEvent);
+ return;
+ }
+
+ FeedUtils.log.debug("Feed.onDownloaded: got a download - " + url);
+ let feed = FeedCache.getFeed(url);
+ if (!feed)
+ throw new Error("Feed.onDownloaded: error - couldn't retrieve feed " +
+ "from cache");
+
+ // If the server sends a Last-Modified header, store the property on the
+ // feed so we can use it when making future requests, to avoid downloading
+ // and parsing feeds that have not changed. Don't update if merely checking
+ // the url, as for subscribe move/copy, as a subsequent refresh may get a 304.
+ // Save the response and persist it only upon successful completion of the
+ // refresh cycle (i.e. not if the request is cancelled).
+ let lastModifiedHeader = request.getResponseHeader("Last-Modified");
+ feed.mLastModified = (lastModifiedHeader && feed.parseItems) ?
+ lastModifiedHeader : null;
+
+ // The download callback is called asynchronously when parse() is done.
+ feed.parse();
+ },
+
+ onProgress: function(aEvent)
+ {
+ let request = aEvent.target;
+ let url = request.channel.originalURI.spec;
+ let feed = FeedCache.getFeed(url);
+
+ if (feed.downloadCallback)
+ feed.downloadCallback.onProgress(feed, aEvent.loaded, aEvent.total,
+ aEvent.lengthComputable);
+ },
+
+ onDownloadError: function(aEvent)
+ {
+ let request = aEvent.target;
+ let url = request.channel.originalURI.spec;
+ let feed = FeedCache.getFeed(url);
+ if (feed.downloadCallback)
+ {
+ // Generic network or 'not found' error initially.
+ let error = FeedUtils.kNewsBlogRequestFailure;
+
+ if (request.status == 304) {
+ // If the http status code is 304, the feed has not been modified
+ // since we last downloaded it and does not need to be parsed.
+ error = FeedUtils.kNewsBlogNoNewItems;
+ }
+ else {
+ let [errType, errName] = FeedUtils.createTCPErrorFromFailedXHR(request);
+ FeedUtils.log.info("Feed.onDownloaded: request errType:errName:statusCode - " +
+ errType + ":" + errName + ":" + request.status);
+ if (errType == "SecurityCertificate")
+ // This is the code for nsINSSErrorsService.ERROR_CLASS_BAD_CERT
+ // overrideable security certificate errors.
+ error = FeedUtils.kNewsBlogBadCertError;
+
+ if (request.status == 401 || request.status == 403)
+ // Unauthorized or Forbidden.
+ error = FeedUtils.kNewsBlogNoAuthError;
+ }
+
+ feed.downloadCallback.downloaded(feed, error);
+ }
+
+ FeedCache.removeFeed(url);
+ },
+
+ onParseError: function(aFeed)
+ {
+ if (!aFeed)
+ return;
+
+ aFeed.mInvalidFeed = true;
+ if (aFeed.downloadCallback)
+ aFeed.downloadCallback.downloaded(aFeed, FeedUtils.kNewsBlogInvalidFeed);
+
+ FeedCache.removeFeed(aFeed.url);
+ },
+
+ onUrlChange: function(aFeed, aOldUrl)
+ {
+ if (!aFeed)
+ return;
+
+ // Simulate a cancel after a url update; next cycle will check the new url.
+ aFeed.mInvalidFeed = true;
+ if (aFeed.downloadCallback)
+ aFeed.downloadCallback.downloaded(aFeed, FeedUtils.kNewsBlogCancel);
+
+ FeedCache.removeFeed(aOldUrl);
+ },
+
+ get url()
+ {
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ let url = ds.GetTarget(this.resource, FeedUtils.DC_IDENTIFIER, true);
+ if (url)
+ url = url.QueryInterface(Ci.nsIRDFLiteral).Value;
+ else
+ url = this.resource.ValueUTF8;
+
+ return url;
+ },
+
+ get title()
+ {
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ let title = ds.GetTarget(this.resource, FeedUtils.DC_TITLE, true);
+ if (title)
+ title = title.QueryInterface(Ci.nsIRDFLiteral).Value;
+
+ return title;
+ },
+
+ set title (aNewTitle)
+ {
+ if (!aNewTitle)
+ return;
+
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ aNewTitle = FeedUtils.rdf.GetLiteral(aNewTitle);
+ let old_title = ds.GetTarget(this.resource, FeedUtils.DC_TITLE, true);
+ if (old_title)
+ ds.Change(this.resource, FeedUtils.DC_TITLE, old_title, aNewTitle);
+ else
+ ds.Assert(this.resource, FeedUtils.DC_TITLE, aNewTitle, true);
+ },
+
+ get lastModified()
+ {
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ let lastModified = ds.GetTarget(this.resource,
+ FeedUtils.DC_LASTMODIFIED,
+ true);
+ if (lastModified)
+ lastModified = lastModified.QueryInterface(Ci.nsIRDFLiteral).Value;
+
+ return lastModified;
+ },
+
+ set lastModified(aLastModified)
+ {
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ aLastModified = FeedUtils.rdf.GetLiteral(aLastModified);
+ let old_lastmodified = ds.GetTarget(this.resource,
+ FeedUtils.DC_LASTMODIFIED,
+ true);
+ if (old_lastmodified)
+ ds.Change(this.resource, FeedUtils.DC_LASTMODIFIED,
+ old_lastmodified, aLastModified);
+ else
+ ds.Assert(this.resource, FeedUtils.DC_LASTMODIFIED, aLastModified, true);
+ },
+
+ get quickMode ()
+ {
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ let quickMode = ds.GetTarget(this.resource, FeedUtils.FZ_QUICKMODE, true);
+ if (quickMode)
+ {
+ quickMode = quickMode.QueryInterface(Ci.nsIRDFLiteral);
+ quickMode = quickMode.Value == "true";
+ }
+
+ return quickMode;
+ },
+
+ set quickMode (aNewQuickMode)
+ {
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ aNewQuickMode = FeedUtils.rdf.GetLiteral(aNewQuickMode);
+ let old_quickMode = ds.GetTarget(this.resource,
+ FeedUtils.FZ_QUICKMODE,
+ true);
+ if (old_quickMode)
+ ds.Change(this.resource, FeedUtils.FZ_QUICKMODE,
+ old_quickMode, aNewQuickMode);
+ else
+ ds.Assert(this.resource, FeedUtils.FZ_QUICKMODE,
+ aNewQuickMode, true);
+ },
+
+ get options ()
+ {
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ let options = ds.GetTarget(this.resource, FeedUtils.FZ_OPTIONS, true);
+ if (options)
+ return JSON.parse(options.QueryInterface(Ci.nsIRDFLiteral).Value);
+
+ return null;
+ },
+
+ set options (aOptions)
+ {
+ let newOptions = aOptions ? FeedUtils.newOptions(aOptions) :
+ FeedUtils._optionsDefault;
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ newOptions = FeedUtils.rdf.GetLiteral(JSON.stringify(newOptions));
+ let oldOptions = ds.GetTarget(this.resource, FeedUtils.FZ_OPTIONS, true);
+ if (oldOptions)
+ ds.Change(this.resource, FeedUtils.FZ_OPTIONS, oldOptions, newOptions);
+ else
+ ds.Assert(this.resource, FeedUtils.FZ_OPTIONS, newOptions, true);
+ },
+
+ categoryPrefs: function ()
+ {
+ let categoryPrefsAcct = FeedUtils.getOptionsAcct(this.server).category;
+ if (!this.options)
+ return categoryPrefsAcct;
+
+ return this.options.category;
+ },
+
+ get link ()
+ {
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ let link = ds.GetTarget(this.resource, FeedUtils.RSS_LINK, true);
+ if (link)
+ link = link.QueryInterface(Ci.nsIRDFLiteral).Value;
+
+ return link;
+ },
+
+ set link (aNewLink)
+ {
+ if (!aNewLink)
+ return;
+
+ let ds = FeedUtils.getSubscriptionsDS(this.server);
+ aNewLink = FeedUtils.rdf.GetLiteral(aNewLink);
+ let old_link = ds.GetTarget(this.resource, FeedUtils.RSS_LINK, true);
+ if (old_link)
+ ds.Change(this.resource, FeedUtils.RSS_LINK, old_link, aNewLink);
+ else
+ ds.Assert(this.resource, FeedUtils.RSS_LINK, aNewLink, true);
+ },
+
+ parse: function()
+ {
+ // Create a feed parser which will parse the feed.
+ let parser = new FeedParser();
+ this.itemsToStore = parser.parseFeed(this, this.request.responseXML);
+ parser = null;
+
+ if (this.mInvalidFeed)
+ {
+ this.request = null;
+ this.mInvalidFeed = false;
+ return;
+ }
+
+ // storeNextItem() will iterate through the parsed items, storing each one.
+ this.itemsToStoreIndex = 0;
+ this.itemsStored = 0;
+ this.storeNextItem();
+ },
+
+ invalidateItems: function ()
+ {
+ let ds = FeedUtils.getItemsDS(this.server);
+ FeedUtils.log.debug("Feed.invalidateItems: for url - " + this.url);
+ let items = ds.GetSources(FeedUtils.FZ_FEED, this.resource, true);
+ let item;
+
+ while (items.hasMoreElements())
+ {
+ item = items.getNext();
+ item = item.QueryInterface(Ci.nsIRDFResource);
+ FeedUtils.log.trace("Feed.invalidateItems: item - " + item.Value);
+ let valid = ds.GetTarget(item, FeedUtils.FZ_VALID, true);
+ if (valid)
+ ds.Unassert(item, FeedUtils.FZ_VALID, valid, true);
+ }
+ },
+
+ removeInvalidItems: function(aDeleteFeed)
+ {
+ let ds = FeedUtils.getItemsDS(this.server);
+ FeedUtils.log.debug("Feed.removeInvalidItems: for url - " + this.url);
+ let items = ds.GetSources(FeedUtils.FZ_FEED, this.resource, true);
+ let item;
+ let currentTime = new Date().getTime();
+ while (items.hasMoreElements())
+ {
+ item = items.getNext();
+ item = item.QueryInterface(Ci.nsIRDFResource);
+
+ if (ds.HasAssertion(item, FeedUtils.FZ_VALID,
+ FeedUtils.RDF_LITERAL_TRUE, true))
+ continue;
+
+ let lastSeenTime = ds.GetTarget(item, FeedUtils.FZ_LAST_SEEN_TIMESTAMP, true);
+ if (lastSeenTime)
+ lastSeenTime = parseInt(lastSeenTime.QueryInterface(Ci.nsIRDFLiteral).Value)
+ else
+ lastSeenTime = 0;
+
+ if ((currentTime - lastSeenTime) < FeedUtils.INVALID_ITEM_PURGE_DELAY &&
+ !aDeleteFeed)
+ // Don't immediately purge items in active feeds; do so for deleted feeds.
+ continue;
+
+ FeedUtils.log.trace("Feed.removeInvalidItems: item - " + item.Value);
+ ds.Unassert(item, FeedUtils.FZ_FEED, this.resource, true);
+ if (ds.hasArcOut(item, FeedUtils.FZ_FEED))
+ FeedUtils.log.debug("Feed.removeInvalidItems: " + item.Value +
+ " is from more than one feed; only the reference to" +
+ " this feed removed");
+ else
+ FeedUtils.removeAssertions(ds, item);
+ }
+ },
+
+ createFolder: function()
+ {
+ if (this.folder)
+ return;
+
+ try {
+ this.folder = this.server.rootMsgFolder
+ .QueryInterface(Ci.nsIMsgLocalMailFolder)
+ .createLocalSubfolder(this.folderName);
+ }
+ catch (ex) {
+ // An error creating.
+ FeedUtils.log.info("Feed.createFolder: error creating folder - '"+
+ this.folderName+"' in parent folder "+
+ this.server.rootMsgFolder.filePath.path + " -- "+ex);
+ // But its remnants are still there, clean up.
+ let xfolder = this.server.rootMsgFolder.getChildNamed(this.folderName);
+ this.server.rootMsgFolder.propagateDelete(xfolder, true, null);
+ }
+ },
+
+ // Gets the next item from itemsToStore and forces that item to be stored
+ // to the folder. If more items are left to be stored, fires a timer for
+ // the next one, otherwise triggers a download done notification to the UI.
+ storeNextItem: function()
+ {
+ if (FeedUtils.CANCEL_REQUESTED)
+ {
+ FeedUtils.CANCEL_REQUESTED = false;
+ this.cleanupParsingState(this, FeedUtils.kNewsBlogCancel);
+ return;
+ }
+
+ if (!this.itemsToStore || !this.itemsToStore.length)
+ {
+ let code = FeedUtils.kNewsBlogSuccess;
+ this.createFolder();
+ if (!this.folder)
+ code = FeedUtils.kNewsBlogFileError;
+ this.cleanupParsingState(this, code);
+ return;
+ }
+
+ let item = this.itemsToStore[this.itemsToStoreIndex];
+
+ if (item.store())
+ this.itemsStored++;
+
+ if (!this.folder)
+ {
+ this.cleanupParsingState(this, FeedUtils.kNewsBlogFileError);
+ return;
+ }
+
+ this.itemsToStoreIndex++;
+
+ // If the listener is tracking progress for each item, report it here.
+ if (item.feed.downloadCallback && item.feed.downloadCallback.onFeedItemStored)
+ item.feed.downloadCallback.onFeedItemStored(item.feed,
+ this.itemsToStoreIndex,
+ this.itemsToStore.length);
+
+ // Eventually we'll report individual progress here.
+
+ if (this.itemsToStoreIndex < this.itemsToStore.length)
+ {
+ if (!this.storeItemsTimer)
+ this.storeItemsTimer = Cc["@mozilla.org/timer;1"].
+ createInstance(Ci.nsITimer);
+ this.storeItemsTimer.initWithCallback(this, 50, Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ else
+ {
+ // We have just finished downloading one or more feed items into the
+ // destination folder; if the folder is still listed as having new
+ // messages in it, then we should set the biff state on the folder so the
+ // right RDF UI changes happen in the folder pane to indicate new mail.
+ if (item.feed.folder.hasNewMessages)
+ {
+ item.feed.folder.biffState = Ci.nsIMsgFolder.nsMsgBiffState_NewMail;
+ // Run the bayesian spam filter, if enabled.
+ item.feed.folder.callFilterPlugins(null);
+ }
+
+ this.cleanupParsingState(this, FeedUtils.kNewsBlogSuccess);
+ }
+ },
+
+ cleanupParsingState: function(aFeed, aCode)
+ {
+ // Now that we are done parsing the feed, remove the feed from the cache.
+ FeedCache.removeFeed(aFeed.url);
+
+ if (aFeed.parseItems)
+ {
+ // Do this only if we're in parse/store mode.
+ aFeed.removeInvalidItems(false);
+
+ if (aCode == FeedUtils.kNewsBlogSuccess && aFeed.mLastModified)
+ aFeed.lastModified = aFeed.mLastModified;
+
+ // Flush any feed item changes to disk.
+ let ds = FeedUtils.getItemsDS(aFeed.server);
+ ds.Flush();
+ FeedUtils.log.debug("Feed.cleanupParsingState: items stored - " + this.itemsStored);
+ }
+
+ // Force the xml http request to go away. This helps reduce some nasty
+ // assertions on shut down.
+ this.request = null;
+ this.itemsToStore = "";
+ this.itemsToStoreIndex = 0;
+ this.itemsStored = 0;
+ this.storeItemsTimer = null;
+
+ if (aFeed.downloadCallback)
+ aFeed.downloadCallback.downloaded(aFeed, aCode);
+ },
+
+ // nsITimerCallback
+ notify: function(aTimer)
+ {
+ this.storeNextItem();
+ }
+};
diff --git a/mailnews/extensions/newsblog/content/FeedItem.js b/mailnews/extensions/newsblog/content/FeedItem.js
new file mode 100644
index 000000000..25fc8ac30
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/FeedItem.js
@@ -0,0 +1,494 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function FeedItem()
+{
+ this.mDate = FeedUtils.getValidRFC5322Date();
+ this.mUnicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ this.mParserUtils = Cc["@mozilla.org/parserutils;1"].
+ getService(Ci.nsIParserUtils);
+}
+
+FeedItem.prototype =
+{
+ // Only for IETF Atom.
+ xmlContentBase: null,
+ id: null,
+ feed: null,
+ description: null,
+ content: null,
+ enclosures: [],
+ title: null,
+ // Author must be angle bracket enclosed to function as an addr-spec, in the
+ // absence of an addr-spec portion of an RFC5322 email address, as other
+ // functionality (gloda search) depends on this.
+ author: "<anonymous>",
+ inReplyTo: "",
+ keywords: [],
+ mURL: null,
+ characterSet: "UTF-8",
+
+ ENCLOSURE_BOUNDARY_PREFIX: "--------------", // 14 dashes
+ ENCLOSURE_HEADER_BOUNDARY_PREFIX: "------------", // 12 dashes
+ MESSAGE_TEMPLATE: '\n' +
+ '<html>\n' +
+ ' <head>\n' +
+ ' <title>%TITLE%</title>\n' +
+ ' <base href="%BASE%">\n' +
+ ' </head>\n' +
+ ' <body id="msgFeedSummaryBody" selected="false">\n' +
+ ' %CONTENT%\n' +
+ ' </body>\n' +
+ '</html>\n',
+
+ get url()
+ {
+ return this.mURL;
+ },
+
+ set url(aVal)
+ {
+ try
+ {
+ this.mURL = Services.io.newURI(aVal, null, null).spec;
+ }
+ catch(ex)
+ {
+ // The url as published or constructed can be a non url. It's used as a
+ // feeditem identifier in feeditems.rdf, as a messageId, and as an href
+ // and for the content-base header. Save as is; ensure not null.
+ this.mURL = aVal ? aVal : "";
+ }
+ },
+
+ get date()
+ {
+ return this.mDate;
+ },
+
+ set date (aVal)
+ {
+ this.mDate = aVal;
+ },
+
+ get identity ()
+ {
+ return this.feed.name + ": " + this.title + " (" + this.id + ")"
+ },
+
+ normalizeMessageID: function(messageID)
+ {
+ // Escape occurrences of message ID meta characters <, >, and @.
+ messageID.replace(/</g, "%3C");
+ messageID.replace(/>/g, "%3E");
+ messageID.replace(/@/g, "%40");
+ messageID = "<" + messageID.trim() + "@" + "localhost.localdomain" + ">";
+
+ FeedUtils.log.trace("FeedItem.normalizeMessageID: messageID - " + messageID);
+ return messageID;
+ },
+
+ get itemUniqueURI()
+ {
+ return this.createURN(this.id);
+ },
+
+ get contentBase()
+ {
+ if(this.xmlContentBase)
+ return this.xmlContentBase
+ else
+ return this.mURL;
+ },
+
+ store: function()
+ {
+ // this.title and this.content contain HTML.
+ // this.mUrl and this.contentBase contain plain text.
+
+ let stored = false;
+ let resource = this.findStoredResource();
+ if (!this.feed.folder)
+ return stored;
+
+ if (resource == null)
+ {
+ resource = FeedUtils.rdf.GetResource(this.itemUniqueURI);
+ if (!this.content)
+ {
+ FeedUtils.log.trace("FeedItem.store: " + this.identity +
+ " no content; storing description or title");
+ this.content = this.description || this.title;
+ }
+
+ let content = this.MESSAGE_TEMPLATE;
+ content = content.replace(/%TITLE%/, this.title);
+ content = content.replace(/%BASE%/, this.htmlEscape(this.contentBase));
+ content = content.replace(/%CONTENT%/, this.content);
+ this.content = content;
+ this.writeToFolder();
+ this.markStored(resource);
+ stored = true;
+ }
+ this.markValid(resource);
+ return stored;
+ },
+
+ findStoredResource: function()
+ {
+ // Checks to see if the item has already been stored in its feed's
+ // message folder.
+ FeedUtils.log.trace("FeedItem.findStoredResource: checking if stored - " +
+ this.identity);
+
+ let server = this.feed.server;
+ let folder = this.feed.folder;
+
+ if (!folder)
+ {
+ FeedUtils.log.debug("FeedItem.findStoredResource: folder '" +
+ this.feed.folderName +
+ "' doesn't exist; creating as child of " +
+ server.rootMsgFolder.prettyName + "\n");
+ this.feed.createFolder();
+ return null;
+ }
+
+ let ds = FeedUtils.getItemsDS(server);
+ let itemURI = this.itemUniqueURI;
+ let itemResource = FeedUtils.rdf.GetResource(itemURI);
+
+ let downloaded = ds.GetTarget(itemResource, FeedUtils.FZ_STORED, true);
+
+ if (!downloaded ||
+ downloaded.QueryInterface(Ci.nsIRDFLiteral).Value == "false")
+ {
+ FeedUtils.log.trace("FeedItem.findStoredResource: not stored");
+ return null;
+ }
+
+ FeedUtils.log.trace("FeedItem.findStoredResource: already stored");
+ return itemResource;
+ },
+
+ markValid: function(resource)
+ {
+ let ds = FeedUtils.getItemsDS(this.feed.server);
+
+ let newTimeStamp = FeedUtils.rdf.GetLiteral(new Date().getTime());
+ let currentTimeStamp = ds.GetTarget(resource,
+ FeedUtils.FZ_LAST_SEEN_TIMESTAMP,
+ true);
+ if (currentTimeStamp)
+ ds.Change(resource, FeedUtils.FZ_LAST_SEEN_TIMESTAMP,
+ currentTimeStamp, newTimeStamp);
+ else
+ ds.Assert(resource, FeedUtils.FZ_LAST_SEEN_TIMESTAMP,
+ newTimeStamp, true);
+
+ if (!ds.HasAssertion(resource, FeedUtils.FZ_FEED,
+ FeedUtils.rdf.GetResource(this.feed.url), true))
+ ds.Assert(resource, FeedUtils.FZ_FEED,
+ FeedUtils.rdf.GetResource(this.feed.url), true);
+
+ if (ds.hasArcOut(resource, FeedUtils.FZ_VALID))
+ {
+ let currentValue = ds.GetTarget(resource, FeedUtils.FZ_VALID, true);
+ ds.Change(resource, FeedUtils.FZ_VALID,
+ currentValue, FeedUtils.RDF_LITERAL_TRUE);
+ }
+ else
+ ds.Assert(resource, FeedUtils.FZ_VALID, FeedUtils.RDF_LITERAL_TRUE, true);
+ },
+
+ markStored: function(resource)
+ {
+ let ds = FeedUtils.getItemsDS(this.feed.server);
+
+ if (!ds.HasAssertion(resource, FeedUtils.FZ_FEED,
+ FeedUtils.rdf.GetResource(this.feed.url), true))
+ ds.Assert(resource, FeedUtils.FZ_FEED,
+ FeedUtils.rdf.GetResource(this.feed.url), true);
+
+ let currentValue;
+ if (ds.hasArcOut(resource, FeedUtils.FZ_STORED))
+ {
+ currentValue = ds.GetTarget(resource, FeedUtils.FZ_STORED, true);
+ ds.Change(resource, FeedUtils.FZ_STORED,
+ currentValue, FeedUtils.RDF_LITERAL_TRUE);
+ }
+ else
+ ds.Assert(resource, FeedUtils.FZ_STORED,
+ FeedUtils.RDF_LITERAL_TRUE, true);
+ },
+
+ writeToFolder: function()
+ {
+ FeedUtils.log.trace("FeedItem.writeToFolder: " + this.identity +
+ " writing to message folder " + this.feed.name);
+
+ // The subject may contain HTML entities. Convert these to their unencoded
+ // state. i.e. &amp; becomes '&'.
+ let title = this.title;
+ title = this.mParserUtils.convertToPlainText(
+ title,
+ Ci.nsIDocumentEncoder.OutputSelectionOnly |
+ Ci.nsIDocumentEncoder.OutputAbsoluteLinks,
+ 0);
+
+ // Compress white space in the subject to make it look better. Trim
+ // leading/trailing spaces to prevent mbox header folding issue at just
+ // the right subject length.
+ this.title = title.replace(/[\t\r\n]+/g, " ").trim();
+
+ // If the date looks like it's in W3C-DTF format, convert it into
+ // an IETF standard date. Otherwise assume it's in IETF format.
+ if (this.mDate.search(/^\d\d\d\d/) != -1)
+ this.mDate = new Date(this.mDate).toUTCString();
+
+ // If there is an inreplyto value, create the headers.
+ let inreplytoHdrsStr = this.inReplyTo ?
+ ("References: " + this.inReplyTo + "\n" +
+ "In-Reply-To: " + this.inReplyTo + "\n") : "";
+
+ // Support multiple authors in From.
+ let fromStr = this.createHeaderStrFromArray("From: ", this.author);
+
+ // If there are keywords (categories), create the headers.
+ let keywordsStr = this.createHeaderStrFromArray("Keywords: ", this.keywords);
+
+ // Escape occurrences of "From " at the beginning of lines of
+ // content per the mbox standard, since "From " denotes a new
+ // message, and add a line break so we know the last line has one.
+ this.content = this.content.replace(/([\r\n]+)(>*From )/g, "$1>$2");
+ this.content += "\n";
+
+ // The opening line of the message, mandated by standards to start
+ // with "From ". It's useful to construct this separately because
+ // we not only need to write it into the message, we also need to
+ // use it to calculate the offset of the X-Mozilla-Status lines from
+ // the front of the message for the statusOffset property of the
+ // DB header object.
+ let openingLine = 'From - ' + this.mDate + '\n';
+
+ let source =
+ openingLine +
+ 'X-Mozilla-Status: 0000\n' +
+ 'X-Mozilla-Status2: 00000000\n' +
+ 'X-Mozilla-Keys: ' + " ".repeat(80) + '\n' +
+ 'Received: by localhost; ' + FeedUtils.getValidRFC5322Date() + '\n' +
+ 'Date: ' + this.mDate + '\n' +
+ 'Message-Id: ' + this.normalizeMessageID(this.id) + '\n' +
+ fromStr +
+ 'MIME-Version: 1.0\n' +
+ 'Subject: ' + this.title + '\n' +
+ inreplytoHdrsStr +
+ keywordsStr +
+ 'Content-Transfer-Encoding: 8bit\n' +
+ 'Content-Base: ' + this.mURL + '\n';
+
+ if (this.enclosures.length)
+ {
+ let boundaryID = source.length;
+ source += 'Content-Type: multipart/mixed; boundary="' +
+ this.ENCLOSURE_HEADER_BOUNDARY_PREFIX + boundaryID + '"' + '\n\n' +
+ 'This is a multi-part message in MIME format.\n' +
+ this.ENCLOSURE_BOUNDARY_PREFIX + boundaryID + '\n' +
+ 'Content-Type: text/html; charset=' + this.characterSet + '\n' +
+ 'Content-Transfer-Encoding: 8bit\n' +
+ this.content;
+
+ this.enclosures.forEach(function(enclosure) {
+ source += enclosure.convertToAttachment(boundaryID);
+ });
+
+ source += this.ENCLOSURE_BOUNDARY_PREFIX + boundaryID + '--' + '\n\n\n';
+ }
+ else
+ source += 'Content-Type: text/html; charset=' + this.characterSet + '\n' +
+ this.content;
+
+ FeedUtils.log.trace("FeedItem.writeToFolder: " + this.identity +
+ " is " + source.length + " characters long");
+
+ // Get the folder and database storing the feed's messages and headers.
+ let folder = this.feed.folder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ let msgFolder = folder.QueryInterface(Ci.nsIMsgFolder);
+ msgFolder.gettingNewMessages = true;
+ // Source is a unicode string, we want to save a char * string in
+ // the original charset. So convert back.
+ this.mUnicodeConverter.charset = this.characterSet;
+ let msgDBHdr = folder.addMessage(this.mUnicodeConverter.ConvertFromUnicode(source));
+ msgDBHdr.OrFlags(Ci.nsMsgMessageFlags.FeedMsg);
+ msgFolder.gettingNewMessages = false;
+ this.tagItem(msgDBHdr, this.keywords);
+ },
+
+/**
+ * Create a header string from an array. Intended for comma separated headers
+ * like From or Keywords. In the case of a longer than RFC5322 recommended
+ * line length, create multiple folded lines (easier to parse than multiple
+ * headers).
+ *
+ * @param {String} headerName - Name of the header.
+ * @param {String[]} headerItemsArray - An Array of strings to concatenate.
+ *
+ * @returns {String} - The header string.
+ */
+ createHeaderStrFromArray(headerName, headerItemsArray) {
+ let headerStr = "";
+ if (!headerItemsArray || headerItemsArray.length == 0) {
+ return headerStr;
+ }
+
+ const HEADER = headerName;
+ const LINELENGTH = 78;
+ const MAXLINELENGTH = 990;
+ let items = [].concat(headerItemsArray);
+ let lines = [];
+ headerStr = HEADER;
+ while (items.length) {
+ let item = items.shift();
+ if (headerStr.length + item.length > LINELENGTH &&
+ headerStr.length > HEADER.length) {
+ lines.push(headerStr);
+ headerStr = " ".repeat(HEADER.length);
+ }
+
+ headerStr += headerStr.length + item.length > MAXLINELENGTH ?
+ item.substr(0, MAXLINELENGTH - headerStr.length) + "…, " :
+ item + ", ";
+ }
+
+ headerStr = headerStr.replace(/,\s$/, "\n");
+ lines.push(headerStr);
+ headerStr = lines.join("\n");
+
+ return headerStr;
+ },
+
+/**
+ * Autotag messages.
+ *
+ * @param nsIMsgDBHdr aMsgDBHdr - message to tag
+ * @param array aKeywords - keywords (tags)
+ */
+ tagItem: function(aMsgDBHdr, aKeywords)
+ {
+ let categoryPrefs = this.feed.categoryPrefs();
+ if (!aKeywords.length || !categoryPrefs.enabled)
+ return;
+
+ let msgArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ msgArray.appendElement(aMsgDBHdr, false);
+
+ let prefix = categoryPrefs.prefixEnabled ? categoryPrefs.prefix : "";
+ let rtl = Services.prefs.getIntPref("bidi.direction") == 2;
+
+ let keys = [];
+ for (let keyword of aKeywords)
+ {
+ keyword = rtl ? keyword + prefix : prefix + keyword;
+ let keyForTag = MailServices.tags.getKeyForTag(keyword);
+ if (!keyForTag)
+ {
+ // Add the tag if it doesn't exist.
+ MailServices.tags.addTag(keyword, "", FeedUtils.AUTOTAG);
+ keyForTag = MailServices.tags.getKeyForTag(keyword);
+ }
+
+ // Add the tag key to the keys array.
+ keys.push(keyForTag);
+ }
+
+ if (keys.length)
+ // Add the keys to the message.
+ aMsgDBHdr.folder.addKeywordsToMessages(msgArray, keys.join(" "));
+ },
+
+ htmlEscape: function(s)
+ {
+ s = s.replace(/&/g, "&amp;");
+ s = s.replace(/>/g, "&gt;");
+ s = s.replace(/</g, "&lt;");
+ s = s.replace(/'/g, "&#39;");
+ s = s.replace(/"/g, "&quot;");
+ return s;
+ },
+
+ createURN: function(aName)
+ {
+ // Returns name as a URN in the 'feeditem' namespace. The returned URN is
+ // (or is intended to be) RFC2141 compliant.
+ // The builtin encodeURI provides nearly the exact encoding functionality
+ // required by the RFC. The exceptions are that NULL characters should not
+ // appear, and that #, /, ?, &, and ~ should be escaped.
+ // NULL characters are removed before encoding.
+
+ let name = aName.replace(/\0/g, "");
+ let encoded = encodeURI(name);
+ encoded = encoded.replace(/\#/g, "%23");
+ encoded = encoded.replace(/\//g, "%2f");
+ encoded = encoded.replace(/\?/g, "%3f");
+ encoded = encoded.replace(/\&/g, "%26");
+ encoded = encoded.replace(/\~/g, "%7e");
+
+ return FeedUtils.FZ_ITEM_NS + encoded;
+ }
+};
+
+
+// A feed enclosure is to RSS what an attachment is for e-mail. We make
+// enclosures look like attachments in the UI.
+function FeedEnclosure(aURL, aContentType, aLength, aTitle)
+{
+ this.mURL = aURL;
+ // Store a reasonable mimetype if content-type is not present.
+ this.mContentType = aContentType || "application/unknown";
+ this.mLength = aLength;
+ this.mTitle = aTitle;
+
+ // Generate a fileName from the URL.
+ if (this.mURL) {
+ try {
+ let uri = Services.io.newURI(this.mURL).QueryInterface(Ci.nsIURL);
+ this.mFileName = uri.fileName;
+ // Determine mimetype from extension if content-type is not present.
+ if (!aContentType) {
+ let contentType = Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromExtension(uri.fileExtension);
+ this.mContentType = contentType;
+ }
+ } catch(ex) {
+ this.mFileName = this.mURL;
+ }
+ }
+}
+
+FeedEnclosure.prototype =
+{
+ mURL: "",
+ mContentType: "",
+ mLength: 0,
+ mFileName: "",
+ mTitle: "",
+ ENCLOSURE_BOUNDARY_PREFIX: "--------------", // 14 dashes
+
+ // Returns a string that looks like an e-mail attachment which represents
+ // the enclosure.
+ convertToAttachment: function(aBoundaryID)
+ {
+ return '\n' +
+ this.ENCLOSURE_BOUNDARY_PREFIX + aBoundaryID + '\n' +
+ 'Content-Type: ' + this.mContentType +
+ '; name="' + (this.mTitle || this.mFileName) +
+ (this.mLength ? '"; size=' + this.mLength : '"') + '\n' +
+ 'X-Mozilla-External-Attachment-URL: ' + this.mURL + '\n' +
+ 'Content-Disposition: attachment; filename="' + this.mFileName + '"\n\n' +
+ FeedUtils.strings.GetStringFromName("externalAttachmentMsg") + '\n';
+ }
+};
diff --git a/mailnews/extensions/newsblog/content/FeedUtils.jsm b/mailnews/extensions/newsblog/content/FeedUtils.jsm
new file mode 100644
index 000000000..f8368ac25
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/FeedUtils.jsm
@@ -0,0 +1,1609 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["Feed", "FeedItem", "FeedParser", "FeedUtils"];
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource:///modules/MailUtils.js");
+Cu.import("resource:///modules/jsmime.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+Services.scriptloader.loadSubScript("chrome://messenger-newsblog/content/Feed.js");
+Services.scriptloader.loadSubScript("chrome://messenger-newsblog/content/FeedItem.js");
+Services.scriptloader.loadSubScript("chrome://messenger-newsblog/content/feed-parser.js");
+
+var FeedUtils = {
+ MOZ_PARSERERROR_NS: "http://www.mozilla.org/newlayout/xml/parsererror.xml",
+
+ RDF_SYNTAX_NS: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+ RDF_SYNTAX_TYPE: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
+ get RDF_TYPE() { return this.rdf.GetResource(this.RDF_SYNTAX_TYPE) },
+
+ RSS_090_NS: "http://my.netscape.com/rdf/simple/0.9/",
+
+ RSS_NS: "http://purl.org/rss/1.0/",
+ get RSS_CHANNEL() { return this.rdf.GetResource(this.RSS_NS + "channel") },
+ get RSS_TITLE() { return this.rdf.GetResource(this.RSS_NS + "title") },
+ get RSS_DESCRIPTION() { return this.rdf.GetResource(this.RSS_NS + "description") },
+ get RSS_ITEMS() { return this.rdf.GetResource(this.RSS_NS + "items") },
+ get RSS_ITEM() { return this.rdf.GetResource(this.RSS_NS + "item") },
+ get RSS_LINK() { return this.rdf.GetResource(this.RSS_NS + "link") },
+
+ RSS_CONTENT_NS: "http://purl.org/rss/1.0/modules/content/",
+ get RSS_CONTENT_ENCODED() {
+ return this.rdf.GetResource(this.RSS_CONTENT_NS + "encoded");
+ },
+
+ DC_NS: "http://purl.org/dc/elements/1.1/",
+ get DC_PUBLISHER() { return this.rdf.GetResource(this.DC_NS + "publisher"); },
+ get DC_CREATOR() { return this.rdf.GetResource(this.DC_NS + "creator") },
+ get DC_SUBJECT() { return this.rdf.GetResource(this.DC_NS + "subject") },
+ get DC_DATE() { return this.rdf.GetResource(this.DC_NS + "date") },
+ get DC_TITLE() { return this.rdf.GetResource(this.DC_NS + "title") },
+ get DC_LASTMODIFIED() { return this.rdf.GetResource(this.DC_NS + "lastModified") },
+ get DC_IDENTIFIER() { return this.rdf.GetResource(this.DC_NS + "identifier") },
+
+ MRSS_NS: "http://search.yahoo.com/mrss/",
+ FEEDBURNER_NS: "http://rssnamespace.org/feedburner/ext/1.0",
+ ITUNES_NS: "http://www.itunes.com/dtds/podcast-1.0.dtd",
+
+ FZ_NS: "urn:forumzilla:",
+ FZ_ITEM_NS: "urn:feeditem:",
+ get FZ_ROOT() { return this.rdf.GetResource(this.FZ_NS + "root") },
+ get FZ_FEEDS() { return this.rdf.GetResource(this.FZ_NS + "feeds") },
+ get FZ_FEED() { return this.rdf.GetResource(this.FZ_NS + "feed") },
+ get FZ_QUICKMODE() { return this.rdf.GetResource(this.FZ_NS + "quickMode") },
+ get FZ_DESTFOLDER() { return this.rdf.GetResource(this.FZ_NS + "destFolder") },
+ get FZ_STORED() { return this.rdf.GetResource(this.FZ_NS + "stored") },
+ get FZ_VALID() { return this.rdf.GetResource(this.FZ_NS + "valid") },
+ get FZ_OPTIONS() { return this.rdf.GetResource(this.FZ_NS + "options"); },
+ get FZ_LAST_SEEN_TIMESTAMP() {
+ return this.rdf.GetResource(this.FZ_NS + "last-seen-timestamp");
+ },
+
+ get RDF_LITERAL_TRUE() { return this.rdf.GetLiteral("true") },
+ get RDF_LITERAL_FALSE() { return this.rdf.GetLiteral("false") },
+
+ // Atom constants
+ ATOM_03_NS: "http://purl.org/atom/ns#",
+ ATOM_IETF_NS: "http://www.w3.org/2005/Atom",
+ ATOM_THREAD_NS: "http://purl.org/syndication/thread/1.0",
+
+ // Accept content mimetype preferences for feeds.
+ REQUEST_ACCEPT: "application/atom+xml," +
+ "application/rss+xml;q=0.9," +
+ "application/rdf+xml;q=0.8," +
+ "application/xml;q=0.7,text/xml;q=0.7," +
+ "*/*;q=0.1",
+ // Timeout for nonresponse to request, 30 seconds.
+ REQUEST_TIMEOUT: 30 * 1000,
+
+ // The approximate amount of time, specified in milliseconds, to leave an
+ // item in the RDF cache after the item has dissappeared from feeds.
+ // The delay is currently one day.
+ INVALID_ITEM_PURGE_DELAY: 24 * 60 * 60 * 1000,
+
+ kBiffMinutesDefault: 100,
+ kNewsBlogSuccess: 0,
+ // Usually means there was an error trying to parse the feed.
+ kNewsBlogInvalidFeed: 1,
+ // Generic networking failure when trying to download the feed.
+ kNewsBlogRequestFailure: 2,
+ kNewsBlogFeedIsBusy: 3,
+ // For 304 Not Modified; There are no new articles for this feed.
+ kNewsBlogNoNewItems: 4,
+ kNewsBlogCancel: 5,
+ kNewsBlogFileError: 6,
+ // Invalid certificate, for overridable user exception errors.
+ kNewsBlogBadCertError: 7,
+ // For 401 Unauthorized or 403 Forbidden.
+ kNewsBlogNoAuthError: 8,
+
+ CANCEL_REQUESTED: false,
+ AUTOTAG: "~AUTOTAG",
+
+/**
+ * Get all rss account servers rootFolders.
+ *
+ * @return array of nsIMsgIncomingServer (empty array if none).
+ */
+ getAllRssServerRootFolders: function() {
+ let rssRootFolders = [];
+ let allServers = MailServices.accounts.allServers;
+ for (let i = 0; i < allServers.length; i++)
+ {
+ let server = allServers.queryElementAt(i, Ci.nsIMsgIncomingServer);
+ if (server && server.type == "rss")
+ rssRootFolders.push(server.rootFolder);
+ }
+
+ // By default, Tb sorts by hostname, ie Feeds, Feeds-1, and not by alpha
+ // prettyName. Do the same as a stock install to match folderpane order.
+ rssRootFolders.sort(function(a, b) { return a.hostname > b.hostname });
+
+ return rssRootFolders;
+ },
+
+/**
+ * Create rss account.
+ *
+ * @param string [aName] - optional account name to override default.
+ * @return nsIMsgAccount.
+ */
+ createRssAccount: function(aName) {
+ let userName = "nobody";
+ let hostName = "Feeds";
+ let hostNamePref = hostName;
+ let server;
+ let serverType = "rss";
+ let defaultName = FeedUtils.strings.GetStringFromName("feeds-accountname");
+ let i = 2;
+ while (MailServices.accounts.findRealServer(userName, hostName, serverType, 0))
+ // If "Feeds" exists, try "Feeds-2", then "Feeds-3", etc.
+ hostName = hostNamePref + "-" + i++;
+
+ server = MailServices.accounts.createIncomingServer(userName, hostName, serverType);
+ server.biffMinutes = FeedUtils.kBiffMinutesDefault;
+ server.prettyName = aName ? aName : defaultName;
+ server.valid = true;
+ let account = MailServices.accounts.createAccount();
+ account.incomingServer = server;
+
+ // Ensure the Trash folder db (.msf) is created otherwise folder/message
+ // deletes will throw until restart creates it.
+ server.msgStore.discoverSubFolders(server.rootMsgFolder, false);
+
+ // Create "Local Folders" if none exist yet as it's guaranteed that
+ // those exist when any account exists.
+ let localFolders;
+ try {
+ localFolders = MailServices.accounts.localFoldersServer;
+ }
+ catch (ex) {}
+
+ if (!localFolders)
+ MailServices.accounts.createLocalMailAccount();
+
+ // Save new accounts in case of a crash.
+ try {
+ MailServices.accounts.saveAccountInfo();
+ }
+ catch (ex) {
+ this.log.error("FeedUtils.createRssAccount: error on saveAccountInfo - " + ex);
+ }
+
+ this.log.debug("FeedUtils.createRssAccount: " +
+ account.incomingServer.rootFolder.prettyName);
+
+ return account;
+ },
+
+/**
+ * Helper routine that checks our subscriptions list array and returns
+ * true if the url is already in our list. This is used to prevent the
+ * user from subscribing to the same feed multiple times for the same server.
+ *
+ * @param string aUrl - the url.
+ * @param nsIMsgIncomingServer aServer - account server.
+ * @return boolean - true if exists else false.
+ */
+ feedAlreadyExists: function(aUrl, aServer) {
+ let ds = this.getSubscriptionsDS(aServer);
+ let feeds = this.getSubscriptionsList(ds);
+ let resource = this.rdf.GetResource(aUrl);
+ if (feeds.IndexOf(resource) == -1)
+ return false;
+
+ let folder = ds.GetTarget(resource, FeedUtils.FZ_DESTFOLDER, true)
+ .QueryInterface(Ci.nsIRDFResource).ValueUTF8;
+ this.log.info("FeedUtils.feedAlreadyExists: feed url " + aUrl +
+ " subscribed in folder url " + decodeURI(folder));
+
+ return true;
+ },
+
+/**
+ * Download a feed url on biff or get new messages.
+ *
+ * @param nsIMsgFolder aFolder - folder
+ * @param nsIUrlListener aUrlListener - feed url
+ * @param bool aIsBiff - true if biff, false if manual get
+ * @param nsIDOMWindow aMsgWindow - window
+ */
+ downloadFeed: function(aFolder, aUrlListener, aIsBiff, aMsgWindow) {
+ if (Services.io.offline)
+ return;
+
+ // We don't yet support the ability to check for new articles while we are
+ // in the middle of subscribing to a feed. For now, abort the check for
+ // new feeds.
+ if (FeedUtils.progressNotifier.mSubscribeMode)
+ {
+ FeedUtils.log.warn("downloadFeed: Aborting RSS New Mail Check. " +
+ "Feed subscription in progress\n");
+ return;
+ }
+
+ let allFolders = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ if (!aFolder.isServer) {
+ // Add the base folder; it does not get returned by ListDescendants. Do not
+ // add the account folder as it doesn't have the feedUrl property or even
+ // a msgDatabase necessarily.
+ allFolders.appendElement(aFolder, false);
+ }
+
+ aFolder.ListDescendants(allFolders);
+
+ let folder;
+ function* feeder() {
+ let numFolders = allFolders.length;
+ for (let i = 0; i < numFolders; i++) {
+ folder = allFolders.queryElementAt(i, Ci.nsIMsgFolder);
+ FeedUtils.log.debug("downloadFeed: START x/# foldername:uri - " +
+ (i+1) + "/" + numFolders + " " +
+ folder.name + ":" + folder.URI);
+
+ // Ensure folder's msgDatabase is openable for new message processing.
+ // If not, reparse. After the async reparse the folder will be ready
+ // for the next cycle; don't bother with a listener. Continue with
+ // the next folder, as attempting to add a message to a folder with
+ // an unavailable msgDatabase will throw later.
+ if (!FeedUtils.isMsgDatabaseOpenable(folder, true))
+ continue;
+
+ let feedUrlArray = FeedUtils.getFeedUrlsInFolder(folder);
+ // Continue if there are no feedUrls for the folder in the feeds
+ // database. All folders in Trash are skipped.
+ if (!feedUrlArray)
+ continue;
+
+ FeedUtils.log.debug("downloadFeed: CONTINUE foldername:urlArray - " +
+ folder.name + ":" + feedUrlArray);
+
+ FeedUtils.progressNotifier.init(aMsgWindow, false);
+
+ // We need to kick off a download for each feed.
+ let id, feed;
+ for (let url of feedUrlArray)
+ {
+ id = FeedUtils.rdf.GetResource(url);
+ feed = new Feed(id, folder.server);
+ feed.folder = folder;
+ // Bump our pending feed download count.
+ FeedUtils.progressNotifier.mNumPendingFeedDownloads++;
+ feed.download(true, FeedUtils.progressNotifier);
+ FeedUtils.log.debug("downloadFeed: DOWNLOAD feed url - " + url);
+
+ Services.tm.mainThread.dispatch(function() {
+ try {
+ let done = getFeed.next().done;
+ if (done) {
+ // Finished with all feeds in base folder and its subfolders.
+ FeedUtils.log.debug("downloadFeed: Finished with folder - " +
+ aFolder.name);
+ folder = null;
+ allFolders = null;
+ }
+ }
+ catch (ex) {
+ FeedUtils.log.error("downloadFeed: error - " + ex);
+ FeedUtils.progressNotifier.downloaded({name: folder.name}, 0);
+ }
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+
+ yield undefined;
+ }
+ }
+ }
+
+ let getFeed = feeder();
+ try {
+ let done = getFeed.next().done;
+ if (done) {
+ // Nothing to do.
+ FeedUtils.log.debug("downloadFeed: Nothing to do in folder - " +
+ aFolder.name);
+ folder = null;
+ allFolders = null;
+ }
+ }
+ catch (ex) {
+ FeedUtils.log.error("downloadFeed: error - " + ex);
+ FeedUtils.progressNotifier.downloaded({name: aFolder.name}, 0);
+ }
+ },
+
+/**
+ * Subscribe a new feed url.
+ *
+ * @param string aUrl - feed url
+ * @param nsIMsgFolder aFolder - folder
+ * @param nsIDOMWindow aMsgWindow - window
+ */
+ subscribeToFeed: function(aUrl, aFolder, aMsgWindow) {
+ // We don't support the ability to subscribe to several feeds at once yet.
+ // For now, abort the subscription if we are already in the middle of
+ // subscribing to a feed via drag and drop.
+ if (FeedUtils.progressNotifier.mNumPendingFeedDownloads)
+ {
+ FeedUtils.log.warn("subscribeToFeed: Aborting RSS subscription. " +
+ "Feed downloads already in progress\n");
+ return;
+ }
+
+ // If aFolder is null, then use the root folder for the first RSS account.
+ if (!aFolder)
+ aFolder = FeedUtils.getAllRssServerRootFolders()[0];
+
+ // If the user has no Feeds account yet, create one.
+ if (!aFolder)
+ aFolder = FeedUtils.createRssAccount().incomingServer.rootFolder;
+
+ if (!aMsgWindow)
+ {
+ let wlist = Services.wm.getEnumerator("mail:3pane");
+ if (wlist.hasMoreElements())
+ {
+ let win = wlist.getNext().QueryInterface(Ci.nsIDOMWindow);
+ win.focus();
+ aMsgWindow = win.msgWindow;
+ }
+ else
+ {
+ // If there are no open windows, open one, pass it the URL, and
+ // during opening it will subscribe to the feed.
+ let arg = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ arg.data = aUrl;
+ Services.ww.openWindow(null, "chrome://messenger/content/",
+ "_blank", "chrome,dialog=no,all", arg);
+ return;
+ }
+ }
+
+ // If aUrl is a feed url, then it is either of the form
+ // feed://example.org/feed.xml or feed:https://example.org/feed.xml.
+ // Replace feed:// with http:// per the spec, then strip off feed:
+ // for the second case.
+ aUrl = aUrl.replace(/^feed:\x2f\x2f/i, "http://");
+ aUrl = aUrl.replace(/^feed:/i, "");
+
+ // Make sure we aren't already subscribed to this feed before we attempt
+ // to subscribe to it.
+ if (FeedUtils.feedAlreadyExists(aUrl, aFolder.server))
+ {
+ aMsgWindow.statusFeedback.showStatusString(
+ FeedUtils.strings.GetStringFromName("subscribe-feedAlreadySubscribed"));
+ return;
+ }
+
+ let itemResource = FeedUtils.rdf.GetResource(aUrl);
+ let feed = new Feed(itemResource, aFolder.server);
+ feed.quickMode = feed.server.getBoolValue("quickMode");
+ feed.options = FeedUtils.getOptionsAcct(feed.server);
+
+ // If the root server, create a new folder for the feed. The user must
+ // want us to add this subscription url to an existing RSS folder.
+ if (!aFolder.isServer)
+ feed.folder = aFolder;
+
+ FeedUtils.progressNotifier.init(aMsgWindow, true);
+ FeedUtils.progressNotifier.mNumPendingFeedDownloads++;
+ feed.download(true, FeedUtils.progressNotifier);
+ },
+
+/**
+ * Add a feed record to the feeds.rdf database and update the folder's feedUrl
+ * property.
+ *
+ * @param object aFeed - our feed object
+ */
+ addFeed: function(aFeed) {
+ let ds = this.getSubscriptionsDS(aFeed.folder.server);
+ let feeds = this.getSubscriptionsList(ds);
+
+ // Generate a unique ID for the feed.
+ let id = aFeed.url;
+ let i = 1;
+ while (feeds.IndexOf(this.rdf.GetResource(id)) != -1 && ++i < 1000)
+ id = aFeed.url + i;
+ if (i == 1000)
+ throw new Error("FeedUtils.addFeed: couldn't generate a unique ID " +
+ "for feed " + aFeed.url);
+
+ // Add the feed to the list.
+ id = this.rdf.GetResource(id);
+ feeds.AppendElement(id);
+ ds.Assert(id, this.RDF_TYPE, this.FZ_FEED, true);
+ ds.Assert(id, this.DC_IDENTIFIER, this.rdf.GetLiteral(aFeed.url), true);
+ if (aFeed.title)
+ ds.Assert(id, this.DC_TITLE, this.rdf.GetLiteral(aFeed.title), true);
+ ds.Assert(id, this.FZ_DESTFOLDER, aFeed.folder, true);
+ ds.Flush();
+
+ // Update folderpane.
+ this.setFolderPaneProperty(aFeed.folder, "favicon", null, "row");
+ },
+
+/**
+ * Delete a feed record from the feeds.rdf database and update the folder's
+ * feedUrl property.
+ *
+ * @param nsIRDFResource aId - feed url as rdf resource.
+ * @param nsIMsgIncomingServer aServer - folder's account server.
+ * @param nsIMsgFolder aParentFolder - owning folder.
+ */
+ deleteFeed: function(aId, aServer, aParentFolder) {
+ let feed = new Feed(aId, aServer);
+ let ds = this.getSubscriptionsDS(aServer);
+
+ if (!feed || !ds)
+ return;
+
+ // Remove the feed from the subscriptions ds.
+ let feeds = this.getSubscriptionsList(ds);
+ let index = feeds.IndexOf(aId);
+ if (index != -1)
+ feeds.RemoveElementAt(index, false);
+
+ // Remove all assertions about the feed from the subscriptions database.
+ this.removeAssertions(ds, aId);
+ ds.Flush();
+
+ // Remove all assertions about items in the feed from the items database.
+ let itemds = this.getItemsDS(aServer);
+ feed.invalidateItems();
+ feed.removeInvalidItems(true);
+ itemds.Flush();
+
+ // Update folderpane.
+ this.setFolderPaneProperty(aParentFolder, "favicon", null, "row");
+ },
+
+/**
+ * Change an existing feed's url, as identified by FZ_FEED resource in the
+ * feeds.rdf subscriptions database.
+ *
+ * @param obj aFeed - the feed object
+ * @param string aNewUrl - new url
+ * @return bool - true if successful, else false
+ */
+ changeUrlForFeed: function(aFeed, aNewUrl) {
+ if (!aFeed || !aFeed.folder || !aNewUrl)
+ return false;
+
+ if (this.feedAlreadyExists(aNewUrl, aFeed.folder.server))
+ {
+ this.log.info("FeedUtils.changeUrlForFeed: new feed url " + aNewUrl +
+ " already subscribed in account " + aFeed.folder.server.prettyName);
+ return false;
+ }
+
+ let title = aFeed.title;
+ let link = aFeed.link;
+ let quickMode = aFeed.quickMode;
+ let options = aFeed.options;
+
+ this.deleteFeed(this.rdf.GetResource(aFeed.url),
+ aFeed.folder.server, aFeed.folder);
+ aFeed.resource = this.rdf.GetResource(aNewUrl)
+ .QueryInterface(Ci.nsIRDFResource);
+ aFeed.title = title;
+ aFeed.link = link;
+ aFeed.quickMode = quickMode;
+ aFeed.options = options;
+ this.addFeed(aFeed);
+
+ let win = Services.wm.getMostRecentWindow("Mail:News-BlogSubscriptions");
+ if (win)
+ win.FeedSubscriptions.refreshSubscriptionView(aFeed.folder, aNewUrl);
+
+ return true;
+ },
+
+/**
+ * Get the list of feed urls for a folder, as identified by the FZ_DESTFOLDER
+ * tag, directly from the primary feeds.rdf subscriptions database.
+ *
+ * @param nsIMsgFolder - the folder.
+ * @return array of urls, or null if none.
+ */
+ getFeedUrlsInFolder: function(aFolder) {
+ if (aFolder.isServer || aFolder.server.type != "rss" ||
+ aFolder.getFlag(Ci.nsMsgFolderFlags.Trash) ||
+ aFolder.getFlag(Ci.nsMsgFolderFlags.Virtual) ||
+ !aFolder.filePath.exists())
+ // There are never any feedUrls in the account/non-feed/trash/virtual
+ // folders or in a ghost folder (nonexistant on disk yet found in
+ // aFolder.subFolders).
+ return null;
+
+ let feedUrlArray = [];
+
+ // Get the list from the feeds database.
+ try {
+ let ds = this.getSubscriptionsDS(aFolder.server);
+ let enumerator = ds.GetSources(this.FZ_DESTFOLDER, aFolder, true);
+ while (enumerator.hasMoreElements())
+ {
+ let containerArc = enumerator.getNext();
+ let uri = containerArc.QueryInterface(Ci.nsIRDFResource).ValueUTF8;
+ feedUrlArray.push(uri);
+ }
+ }
+ catch(ex)
+ {
+ this.log.error("getFeedUrlsInFolder: feeds.rdf db error - " + ex);
+ this.log.error("getFeedUrlsInFolder: feeds.rdf db error for account - " +
+ aFolder.server.serverURI + " : " + aFolder.server.prettyName);
+ }
+
+ return feedUrlArray.length ? feedUrlArray : null;
+ },
+
+/**
+ * Check if the folder's msgDatabase is openable, reparse if desired.
+ *
+ * @param nsIMsgFolder aFolder - the folder
+ * @param boolean aReparse - reparse if true
+ * @return boolean - true if msgDb is available, else false
+ */
+ isMsgDatabaseOpenable: function(aFolder, aReparse) {
+ let msgDb;
+ try {
+ msgDb = Cc["@mozilla.org/msgDatabase/msgDBService;1"]
+ .getService(Ci.nsIMsgDBService).openFolderDB(aFolder, true);
+ }
+ catch (ex) {}
+
+ if (msgDb)
+ return true;
+
+ if (!aReparse)
+ return false;
+
+ // Force a reparse.
+ FeedUtils.log.debug("checkMsgDb: rebuild msgDatabase for " +
+ aFolder.name + " - " + aFolder.filePath.path);
+ try {
+ // Ignore error returns.
+ aFolder.QueryInterface(Ci.nsIMsgLocalMailFolder)
+ .getDatabaseWithReparse(null, null);
+ }
+ catch (ex) {}
+
+ return false;
+ },
+
+/**
+ * Update a folderpane cached property.
+ *
+ * @param nsIMsgFolder aFolder - folder
+ * @param string aProperty - property
+ * @param string aValue - value
+ * @param string aInvalidate - "row" = folder's row.
+ * "all" = all rows.
+ */
+ setFolderPaneProperty: function(aFolder, aProperty, aValue, aInvalidate) {
+ let win = Services.wm.getMostRecentWindow("mail:3pane");
+ if (!aFolder || !aProperty || !win || !("gFolderTreeView" in win))
+ return;
+
+ win.gFolderTreeView.setFolderCacheProperty(aFolder, aProperty, aValue);
+
+ if (aInvalidate == "all") {
+ win.gFolderTreeView._tree.invalidate();
+ }
+ if (aInvalidate == "row") {
+ let row = win.gFolderTreeView.getIndexOfFolder(aFolder);
+ win.gFolderTreeView._tree.invalidateRow(row);
+ }
+ },
+
+/**
+ * Get the favicon for a feed folder subscription url (first one) or a feed
+ * message url. The favicon service caches it in memory if places history is
+ * not enabled.
+ *
+ * @param nsIMsgFolder aFolder - the feed folder or null if aUrl
+ * @param string aUrl - a url (feed, message, other) or null if aFolder
+ * @param string aIconUrl - the icon url if already determined, else null
+ * @param nsIDOMWindow aWindow - null if requesting url without setting it
+ * @param function aCallback - null or callback
+ * @return string - the favicon url or empty string
+ */
+ getFavicon: function(aFolder, aUrl, aIconUrl, aWindow, aCallback) {
+ // On any error, cache an empty string to show the default favicon, and
+ // don't try anymore in this session.
+ let useDefaultFavicon = (() => {
+ if (aCallback)
+ aCallback("");
+ return "";
+ });
+
+ if (!Services.prefs.getBoolPref("browser.chrome.site_icons") ||
+ !Services.prefs.getBoolPref("browser.chrome.favicons"))
+ return useDefaultFavicon();
+
+ if (aIconUrl != null)
+ return aIconUrl;
+
+ let onLoadSuccess = (aEvent => {
+ let iconUri = Services.io.newURI(aEvent.target.src, null, null);
+ aWindow.specialTabs.mFaviconService.setAndFetchFaviconForPage(
+ uri, iconUri, false,
+ aWindow.specialTabs.mFaviconService.FAVICON_LOAD_NON_PRIVATE,
+ null, Services.scriptSecurityManager.getSystemPrincipal());
+
+ if (aCallback)
+ aCallback(iconUri.spec);
+ });
+
+ let onLoadError = (aEvent => {
+ useDefaultFavicon();
+ let url = aEvent.target.src;
+ aWindow.specialTabs.getFaviconFromPage(url, aCallback);
+ });
+
+ let url = aUrl;
+ if (!url)
+ {
+ // Get the proposed iconUrl from the folder's first subscribed feed's
+ // <link>.
+ if (!aFolder)
+ return useDefaultFavicon();
+
+ let feedUrls = this.getFeedUrlsInFolder(aFolder);
+ url = feedUrls ? feedUrls[0] : null;
+ if (!url)
+ return useDefaultFavicon();
+ }
+
+ if (aFolder)
+ {
+ let ds = this.getSubscriptionsDS(aFolder.server);
+ let resource = this.rdf.GetResource(url).QueryInterface(Ci.nsIRDFResource);
+ let feedLinkUrl = ds.GetTarget(resource, this.RSS_LINK, true);
+ feedLinkUrl = feedLinkUrl ?
+ feedLinkUrl.QueryInterface(Ci.nsIRDFLiteral).Value : null;
+ url = feedLinkUrl && feedLinkUrl.startsWith("http") ? feedLinkUrl : url;
+ }
+
+ let uri, iconUri;
+ try {
+ uri = Services.io.newURI(url, null, null);
+ iconUri = Services.io.newURI(uri.prePath + "/favicon.ico", null, null);
+ }
+ catch (ex) {
+ return useDefaultFavicon();
+ }
+
+ if (!aWindow)
+ return iconUri.spec;
+
+ aWindow.specialTabs.loadFaviconImageNode(onLoadSuccess, onLoadError,
+ iconUri.spec);
+ // Cache the favicon url initially.
+ if (aCallback)
+ aCallback(iconUri.spec);
+
+ return iconUri.spec;
+ },
+
+/**
+ * Update the feeds.rdf database for rename and move/copy folder name changes.
+ *
+ * @param nsIMsgFolder aFolder - the folder, new if rename or target of
+ * move/copy folder (new parent)
+ * @param nsIMsgFolder aOrigFolder - original folder
+ * @param string aAction - "move" or "copy" or "rename"
+ */
+ updateSubscriptionsDS: function(aFolder, aOrigFolder, aAction) {
+ this.log.debug("FeedUtils.updateSubscriptionsDS: " +
+ "\nfolder changed - " + aAction +
+ "\nnew folder - " + aFolder.filePath.path +
+ "\norig folder - " + aOrigFolder.filePath.path);
+
+ if (aFolder.server.type != "rss" || FeedUtils.isInTrash(aOrigFolder))
+ // Target not a feed account folder; nothing to do, or move/rename in
+ // trash; no subscriptions already.
+ return;
+
+ let newFolder = aFolder;
+ let newParentURI = aFolder.URI;
+ let origParentURI = aOrigFolder.URI;
+ if (aAction == "move" || aAction == "copy")
+ {
+ // Get the new folder. Don't process the entire parent (new dest folder)!
+ newFolder = aFolder.getChildNamed(aOrigFolder.name);
+ origParentURI = aOrigFolder.parent ? aOrigFolder.parent.URI :
+ aOrigFolder.rootFolder.URI;
+ }
+
+ this.updateFolderChangeInFeedsDS(newFolder, aOrigFolder, null, null);
+
+ // There may be subfolders, but we only get a single notification; iterate
+ // over all descendent folders of the folder whose location has changed.
+ let newSubFolders = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ newFolder.ListDescendants(newSubFolders);
+ for (let i = 0; i < newSubFolders.length; i++)
+ {
+ let newSubFolder = newSubFolders.queryElementAt(i, Ci.nsIMsgFolder);
+ FeedUtils.updateFolderChangeInFeedsDS(newSubFolder, aOrigFolder,
+ newParentURI, origParentURI)
+ }
+ },
+
+/**
+ * Update the feeds.rdf database with the new folder's or subfolder's location
+ * for rename and move/copy name changes. The feeds.rdf subscriptions db is
+ * also synced on cross account folder copies. Note that if a copied folder's
+ * url exists in the new account, its active subscription will be switched to
+ * the folder being copied, to enforce the one unique url per account design.
+ *
+ * @param nsIMsgFolder aFolder - new folder
+ * @param nsIMsgFolder aOrigFolder - original folder
+ * @param string aNewAncestorURI - for subfolders, ancestor new folder
+ * @param string aOrigAncestorURI - for subfolders, ancestor original folder
+ */
+ updateFolderChangeInFeedsDS: function(aFolder, aOrigFolder,
+ aNewAncestorURI, aOrigAncestorURI) {
+ this.log.debug("updateFolderChangeInFeedsDS: " +
+ "\naFolder - " + aFolder.URI +
+ "\naOrigFolder - " + aOrigFolder.URI +
+ "\naOrigAncestor - " + aOrigAncestorURI +
+ "\naNewAncestor - " + aNewAncestorURI);
+
+ // Get the original folder's URI.
+ let folderURI = aFolder.URI;
+ let origURI = aNewAncestorURI && aOrigAncestorURI ?
+ folderURI.replace(aNewAncestorURI, aOrigAncestorURI) :
+ aOrigFolder.URI;
+ let origFolderRes = this.rdf.GetResource(origURI);
+ this.log.debug("updateFolderChangeInFeedsDS: urls origURI - " + origURI);
+ // Get the original folder's url list from the feeds database.
+ let feedUrlArray = [];
+ let dsSrc = this.getSubscriptionsDS(aOrigFolder.server);
+ try {
+ let enumerator = dsSrc.GetSources(this.FZ_DESTFOLDER, origFolderRes, true);
+ while (enumerator.hasMoreElements())
+ {
+ let containerArc = enumerator.getNext();
+ let uri = containerArc.QueryInterface(Ci.nsIRDFResource).ValueUTF8;
+ feedUrlArray.push(uri);
+ }
+ }
+ catch(ex)
+ {
+ this.log.error("updateFolderChangeInFeedsDS: feeds.rdf db error for account - " +
+ aOrigFolder.server.prettyName + " : " + ex);
+ }
+
+ if (!feedUrlArray.length)
+ {
+ this.log.debug("updateFolderChangeInFeedsDS: no feedUrls in this folder");
+ return;
+ }
+
+ let id, resource, node;
+ let ds = this.getSubscriptionsDS(aFolder.server);
+ for (let feedUrl of feedUrlArray)
+ {
+ this.log.debug("updateFolderChangeInFeedsDS: feedUrl - " + feedUrl);
+
+ id = this.rdf.GetResource(feedUrl);
+ // If move to trash, unsubscribe.
+ if (this.isInTrash(aFolder))
+ {
+ this.deleteFeed(id, aFolder.server, aFolder);
+ }
+ else
+ {
+ resource = this.rdf.GetResource(aFolder.URI);
+ // Get the node for the current folder URI.
+ node = ds.GetTarget(id, this.FZ_DESTFOLDER, true);
+ if (node)
+ {
+ ds.Change(id, this.FZ_DESTFOLDER, node, resource);
+ }
+ else
+ {
+ // If adding a new feed it's a cross account action; make sure to
+ // preserve all properties from the original datasource where
+ // available. Otherwise use the new folder's name and default server
+ // quickMode; preserve link and options.
+ let feedTitle = dsSrc.GetTarget(id, this.DC_TITLE, true);
+ feedTitle = feedTitle ? feedTitle.QueryInterface(Ci.nsIRDFLiteral).Value :
+ resource.name;
+ let link = dsSrc.GetTarget(id, FeedUtils.RSS_LINK, true);
+ link = link ? link.QueryInterface(Ci.nsIRDFLiteral).Value : "";
+ let quickMode = dsSrc.GetTarget(id, this.FZ_QUICKMODE, true);
+ quickMode = quickMode ? quickMode.QueryInterface(Ci.nsIRDFLiteral).Value :
+ null;
+ quickMode = quickMode == "true" ? true :
+ quickMode == "false" ? false :
+ aFeed.folder.server.getBoolValue("quickMode");
+ let options = dsSrc.GetTarget(id, this.FZ_OPTIONS, true);
+ options = options ? JSON.parse(options.QueryInterface(Ci.nsIRDFLiteral).Value) :
+ this.optionsTemplate;
+
+ let feed = new Feed(id, aFolder.server);
+ feed.folder = aFolder;
+ feed.title = feedTitle;
+ feed.link = link;
+ feed.quickMode = quickMode;
+ feed.options = options;
+ this.addFeed(feed);
+ }
+ }
+ }
+
+ ds.Flush();
+ },
+
+/**
+ * When subscribing to feeds by dnd on, or adding a url to, the account
+ * folder (only), or creating folder structure via opml import, a subfolder is
+ * autocreated and thus the derived/given name must be sanitized to prevent
+ * filesystem errors. Hashing invalid chars based on OS rather than filesystem
+ * is not strictly correct.
+ *
+ * @param nsIMsgFolder aParentFolder - parent folder
+ * @param string aProposedName - proposed name
+ * @param string aDefaultName - default name if proposed sanitizes to
+ * blank, caller ensures sane value
+ * @param bool aUnique - if true, return a unique indexed name.
+ * @return string - sanitized unique name
+ */
+ getSanitizedFolderName: function(aParentFolder, aProposedName, aDefaultName, aUnique) {
+ // Clean up the name for the strictest fs (fat) and to ensure portability.
+ // 1) Replace line breaks and tabs '\n\r\t' with a space.
+ // 2) Remove nonprintable ascii.
+ // 3) Remove invalid win chars '* | \ / : < > ? "'.
+ // 4) Remove all '.' as starting/ending with one is trouble on osx/win.
+ // 5) No leading/trailing spaces.
+ let folderName = aProposedName.replace(/[\n\r\t]+/g, " ")
+ .replace(/[\x00-\x1F]+/g, "")
+ .replace(/[*|\\\/:<>?"]+/g, "")
+ .replace(/[\.]+/g, "")
+ .trim();
+
+ // Prefix with __ if name is:
+ // 1) a reserved win filename.
+ // 2) an undeletable/unrenameable special folder name (bug 259184).
+ if (folderName.toUpperCase()
+ .match(/^COM\d$|^LPT\d$|^CON$|PRN$|^AUX$|^NUL$|^CLOCK\$/) ||
+ folderName.toUpperCase()
+ .match(/^INBOX$|^OUTBOX$|^UNSENT MESSAGES$|^TRASH$/))
+ folderName = "__" + folderName;
+
+ // Use a default if no name is found.
+ if (!folderName)
+ folderName = aDefaultName;
+
+ if (!aUnique)
+ return folderName;
+
+ // Now ensure the folder name is not a dupe; if so append index.
+ let folderNameBase = folderName;
+ let i = 2;
+ while (aParentFolder.containsChildNamed(folderName))
+ {
+ folderName = folderNameBase + "-" + i++;
+ }
+
+ return folderName;
+ },
+
+/**
+ * This object will contain all feed specific properties.
+ */
+ _optionsDefault: {
+ version: 1,
+ // Autotag and <category> handling options.
+ category: {
+ enabled: false,
+ prefixEnabled: false,
+ prefix: null,
+ }
+ },
+
+ get optionsTemplate()
+ {
+ // Copy the object.
+ return JSON.parse(JSON.stringify(this._optionsDefault));
+ },
+
+ getOptionsAcct: function(aServer)
+ {
+ let optionsAcctPref = "mail.server." + aServer.key + ".feed_options";
+ try {
+ return JSON.parse(Services.prefs.getCharPref(optionsAcctPref));
+ }
+ catch (ex) {
+ this.setOptionsAcct(aServer, this._optionsDefault);
+ return JSON.parse(Services.prefs.getCharPref(optionsAcctPref));
+ }
+ },
+
+ setOptionsAcct: function(aServer, aOptions)
+ {
+ let optionsAcctPref = "mail.server." + aServer.key + ".feed_options";
+ let newOptions = this.newOptions(aOptions);
+ Services.prefs.setCharPref(optionsAcctPref, JSON.stringify(newOptions));
+ },
+
+ newOptions: function(aOptions)
+ {
+ // TODO: Clean options, so that only keys in the active template are stored.
+ return aOptions;
+ },
+
+ getSubscriptionsDS: function(aServer) {
+ if (this[aServer.serverURI] && this[aServer.serverURI]["FeedsDS"])
+ return this[aServer.serverURI]["FeedsDS"];
+
+ let file = this.getSubscriptionsFile(aServer);
+ let url = Services.io.getProtocolHandler("file").
+ QueryInterface(Ci.nsIFileProtocolHandler).
+ getURLSpecFromFile(file);
+
+ // GetDataSourceBlocking has a cache, so it's cheap to do this again
+ // once we've already done it once.
+ let ds = this.rdf.GetDataSourceBlocking(url);
+
+ if (!ds)
+ throw new Error("FeedUtils.getSubscriptionsDS: can't get feed " +
+ "subscriptions data source - " + url);
+
+ if (!this[aServer.serverURI])
+ this[aServer.serverURI] = {};
+ return this[aServer.serverURI]["FeedsDS"] =
+ ds.QueryInterface(Ci.nsIRDFRemoteDataSource);
+ },
+
+ getSubscriptionsList: function(aDataSource) {
+ let list = aDataSource.GetTarget(this.FZ_ROOT, this.FZ_FEEDS, true);
+ list = list.QueryInterface(Ci.nsIRDFResource);
+ list = this.rdfContainerUtils.MakeSeq(aDataSource, list);
+ return list;
+ },
+
+ getSubscriptionsFile: function(aServer) {
+ aServer.QueryInterface(Ci.nsIRssIncomingServer);
+ let file = aServer.subscriptionsDataSourcePath;
+
+ // If the file doesn't exist, create it.
+ if (!file.exists())
+ this.createFile(file, this.FEEDS_TEMPLATE);
+
+ return file;
+ },
+
+ FEEDS_TEMPLATE: '<?xml version="1.0"?>\n' +
+ '<RDF:RDF xmlns:dc="http://purl.org/dc/elements/1.1/"\n' +
+ ' xmlns:fz="urn:forumzilla:"\n' +
+ ' xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n' +
+ ' <RDF:Description about="urn:forumzilla:root">\n' +
+ ' <fz:feeds>\n' +
+ ' <RDF:Seq>\n' +
+ ' </RDF:Seq>\n' +
+ ' </fz:feeds>\n' +
+ ' </RDF:Description>\n' +
+ '</RDF:RDF>\n',
+
+ getItemsDS: function(aServer) {
+ if (this[aServer.serverURI] && this[aServer.serverURI]["FeedItemsDS"])
+ return this[aServer.serverURI]["FeedItemsDS"];
+
+ let file = this.getItemsFile(aServer);
+ let url = Services.io.getProtocolHandler("file").
+ QueryInterface(Ci.nsIFileProtocolHandler).
+ getURLSpecFromFile(file);
+
+ // GetDataSourceBlocking has a cache, so it's cheap to do this again
+ // once we've already done it once.
+ let ds = this.rdf.GetDataSourceBlocking(url);
+ if (!ds)
+ throw new Error("FeedUtils.getItemsDS: can't get feed items " +
+ "data source - " + url);
+
+ // Note that it this point the datasource may not be loaded yet.
+ // You have to QueryInterface it to nsIRDFRemoteDataSource and check
+ // its "loaded" property to be sure. You can also attach an observer
+ // which will get notified when the load is complete.
+ if (!this[aServer.serverURI])
+ this[aServer.serverURI] = {};
+ return this[aServer.serverURI]["FeedItemsDS"] =
+ ds.QueryInterface(Ci.nsIRDFRemoteDataSource);
+ },
+
+ getItemsFile: function(aServer) {
+ aServer.QueryInterface(Ci.nsIRssIncomingServer);
+ let file = aServer.feedItemsDataSourcePath;
+
+ // If the file doesn't exist, create it.
+ if (!file.exists()) {
+ this.createFile(file, this.FEEDITEMS_TEMPLATE);
+ return file;
+ }
+
+ // If feeditems.rdf is not sane, duplicate messages will occur repeatedly
+ // until the file is corrected; check that the file is valid XML. This is
+ // done lazily only once in a session.
+ let fileUrl = Services.io.getProtocolHandler("file")
+ .QueryInterface(Ci.nsIFileProtocolHandler)
+ .getURLSpecFromFile(file);
+ let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ request.open("GET", fileUrl, false);
+ request.responseType = "document";
+ request.send();
+ let dom = request.responseXML;
+ if (dom instanceof Ci.nsIDOMXMLDocument &&
+ dom.documentElement.namespaceURI != this.MOZ_PARSERERROR_NS)
+ return file;
+
+ // Error on the file. Rename it and create a new one.
+ this.log.debug("FeedUtils.getItemsFile: error in feeditems.rdf");
+ let errName = "feeditems_error_" +
+ (new Date().toISOString()).replace(/\D/g, "") + ".rdf";
+ file.moveTo(file.parent, errName);
+ file = aServer.feedItemsDataSourcePath;
+ this.createFile(file, this.FEEDITEMS_TEMPLATE);
+ this.log.error("FeedUtils.getItemsFile: error in feeditems.rdf in account '" +
+ aServer.prettyName + "'; the file has been moved to " +
+ errName + " and a new file has been created. Recent messages " +
+ "may be duplicated.");
+ return file;
+ },
+
+ FEEDITEMS_TEMPLATE: '<?xml version="1.0"?>\n' +
+ '<RDF:RDF xmlns:dc="http://purl.org/dc/elements/1.1/"\n' +
+ ' xmlns:fz="urn:forumzilla:"\n' +
+ ' xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n' +
+ '</RDF:RDF>\n',
+
+ createFile: function(aFile, aTemplate) {
+ let fos = FileUtils.openSafeFileOutputStream(aFile);
+ fos.write(aTemplate, aTemplate.length);
+ FileUtils.closeSafeFileOutputStream(fos);
+ },
+
+ getParentTargetForChildResource: function(aChildResource, aParentTarget,
+ aServer) {
+ // Generic get feed property, based on child value. Assumes 1 unique
+ // child value with 1 unique parent, valid for feeds.rdf structure.
+ let ds = this.getSubscriptionsDS(aServer);
+ let childRes = this.rdf.GetResource(aChildResource);
+ let parent = null;
+
+ let arcsIn = ds.ArcLabelsIn(childRes);
+ while (arcsIn.hasMoreElements())
+ {
+ let arc = arcsIn.getNext();
+ if (arc instanceof Ci.nsIRDFResource)
+ {
+ parent = ds.GetSource(arc, childRes, true);
+ parent = parent.QueryInterface(Ci.nsIRDFResource);
+ break;
+ }
+ }
+
+ if (parent)
+ {
+ let resource = this.rdf.GetResource(parent.Value);
+ return ds.GetTarget(resource, aParentTarget, true);
+ }
+
+ return null;
+ },
+
+ removeAssertions: function(aDataSource, aResource) {
+ let properties = aDataSource.ArcLabelsOut(aResource);
+ let property;
+ while (properties.hasMoreElements())
+ {
+ property = properties.getNext();
+ let values = aDataSource.GetTargets(aResource, property, true);
+ let value;
+ while (values.hasMoreElements())
+ {
+ value = values.getNext();
+ aDataSource.Unassert(aResource, property, value, true);
+ }
+ }
+ },
+
+/**
+ * Dragging something from somewhere. It may be a nice x-moz-url or from a
+ * browser or app that provides a less nice dataTransfer object in the event.
+ * Extract the url and if it passes the scheme test, try to subscribe.
+ *
+ * @param nsIDOMDataTransfer aDataTransfer - the dnd event's dataTransfer.
+ * @return nsIURI uri - a uri if valid, null if none.
+ */
+ getFeedUriFromDataTransfer: function(aDataTransfer) {
+ let dt = aDataTransfer;
+ let types = ["text/x-moz-url-data", "text/x-moz-url"];
+ let validUri = false;
+ let uri = Cc["@mozilla.org/network/standard-url;1"].
+ createInstance(Ci.nsIURI);
+
+ if (dt.getData(types[0]))
+ {
+ // The url is the data.
+ uri.spec = dt.mozGetDataAt(types[0], 0);
+ validUri = this.isValidScheme(uri);
+ this.log.trace("getFeedUriFromDataTransfer: dropEffect:type:value - " +
+ dt.dropEffect + " : " + types[0] + " : " + uri.spec);
+ }
+ else if (dt.getData(types[1]))
+ {
+ // The url is the first part of the data, the second part is random.
+ uri.spec = dt.mozGetDataAt(types[1], 0).split("\n")[0];
+ validUri = this.isValidScheme(uri);
+ this.log.trace("getFeedUriFromDataTransfer: dropEffect:type:value - " +
+ dt.dropEffect + " : " + types[0] + " : " + uri.spec);
+ }
+ else
+ {
+ // Go through the types and see if there's a url; get the first one.
+ for (let i = 0; i < dt.types.length; i++) {
+ let spec = dt.mozGetDataAt(dt.types[i], 0);
+ this.log.trace("getFeedUriFromDataTransfer: dropEffect:index:type:value - " +
+ dt.dropEffect + " : " + i + " : " + dt.types[i] + " : "+spec);
+ try {
+ uri.spec = spec;
+ validUri = this.isValidScheme(uri);
+ }
+ catch(ex) {}
+
+ if (validUri)
+ break;
+ };
+ }
+
+ return validUri ? uri : null;
+ },
+
+ /**
+ * Returns security/certificate/network error details for an XMLHTTPRequest.
+ *
+ * @param XMLHTTPRequest xhr - The xhr request.
+ * @return array [string errType, string errName] (null if not determined).
+ */
+ createTCPErrorFromFailedXHR: function(xhr) {
+ let status = xhr.channel.QueryInterface(Ci.nsIRequest).status;
+
+ let errType = null;
+ let errName = null;
+ if ((status & 0xff0000) === 0x5a0000) {
+ // Security module.
+ const nsINSSErrorsService = Ci.nsINSSErrorsService;
+ let nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(nsINSSErrorsService);
+ let errorClass;
+
+ // getErrorClass()) will throw a generic NS_ERROR_FAILURE if the error
+ // code is somehow not in the set of covered errors.
+ try {
+ errorClass = nssErrorsService.getErrorClass(status);
+ }
+ catch (ex) {
+ // Catch security protocol exception.
+ errorClass = "SecurityProtocol";
+ }
+
+ if (errorClass == nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
+ errType = "SecurityCertificate";
+ }
+ else {
+ errType = "SecurityProtocol";
+ }
+
+ // NSS_SEC errors (happen below the base value because of negative vals).
+ if ((status & 0xffff) < Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) {
+ // The bases are actually negative, so in our positive numeric space,
+ // we need to subtract the base off our value.
+ let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) - (status & 0xffff);
+
+ switch (nssErr) {
+ case 11: // SEC_ERROR_EXPIRED_CERTIFICATE, sec(11)
+ errName = "SecurityExpiredCertificateError";
+ break;
+ case 12: // SEC_ERROR_REVOKED_CERTIFICATE, sec(12)
+ errName = "SecurityRevokedCertificateError";
+ break;
+
+ // Per bsmith, we will be unable to tell these errors apart very soon,
+ // so it makes sense to just folder them all together already.
+ case 13: // SEC_ERROR_UNKNOWN_ISSUER, sec(13)
+ case 20: // SEC_ERROR_UNTRUSTED_ISSUER, sec(20)
+ case 21: // SEC_ERROR_UNTRUSTED_CERT, sec(21)
+ case 36: // SEC_ERROR_CA_CERT_INVALID, sec(36)
+ errName = "SecurityUntrustedCertificateIssuerError";
+ break;
+ case 90: // SEC_ERROR_INADEQUATE_KEY_USAGE, sec(90)
+ errName = "SecurityInadequateKeyUsageError";
+ break;
+ case 176: // SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, sec(176)
+ errName = "SecurityCertificateSignatureAlgorithmDisabledError";
+ break;
+ default:
+ errName = "SecurityError";
+ break;
+ }
+ }
+ else {
+ // Calculating the difference.
+ let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) - (status & 0xffff);
+
+ switch (sslErr) {
+ case 3: // SSL_ERROR_NO_CERTIFICATE, ssl(3)
+ errName = "SecurityNoCertificateError";
+ break;
+ case 4: // SSL_ERROR_BAD_CERTIFICATE, ssl(4)
+ errName = "SecurityBadCertificateError";
+ break;
+ case 8: // SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, ssl(8)
+ errName = "SecurityUnsupportedCertificateTypeError";
+ break;
+ case 9: // SSL_ERROR_UNSUPPORTED_VERSION, ssl(9)
+ errName = "SecurityUnsupportedTLSVersionError";
+ break;
+ case 12: // SSL_ERROR_BAD_CERT_DOMAIN, ssl(12)
+ errName = "SecurityCertificateDomainMismatchError";
+ break;
+ default:
+ errName = "SecurityError";
+ break;
+ }
+ }
+ }
+ else {
+ errType = "Network";
+ switch (status) {
+ // Connect to host:port failed.
+ case 0x804B000C: // NS_ERROR_CONNECTION_REFUSED, network(13)
+ errName = "ConnectionRefusedError";
+ break;
+ // network timeout error.
+ case 0x804B000E: // NS_ERROR_NET_TIMEOUT, network(14)
+ errName = "NetworkTimeoutError";
+ break;
+ // Hostname lookup failed.
+ case 0x804B001E: // NS_ERROR_UNKNOWN_HOST, network(30)
+ errName = "DomainNotFoundError";
+ break;
+ case 0x804B0047: // NS_ERROR_NET_INTERRUPT, network(71)
+ errName = "NetworkInterruptError";
+ break;
+ default:
+ errName = "NetworkError";
+ break;
+ }
+ }
+
+ return [errType, errName];
+ },
+
+/**
+ * Returns if a uri/url is valid to subscribe.
+ *
+ * @param nsIURI aUri or string aUrl - the Uri/Url.
+ * @return boolean - true if a valid scheme, false if not.
+ */
+ _validSchemes: ["http", "https"],
+ isValidScheme: function(aUri) {
+ if (!(aUri instanceof Ci.nsIURI)) {
+ try {
+ aUri = Services.io.newURI(aUri, null, null);
+ }
+ catch (ex) {
+ return false;
+ }
+ }
+
+ return (this._validSchemes.indexOf(aUri.scheme) != -1);
+ },
+
+/**
+ * Is a folder Trash or in Trash.
+ *
+ * @param nsIMsgFolder aFolder - the folder.
+ * @return boolean - true if folder is Trash else false.
+ */
+ isInTrash: function(aFolder) {
+ let trashFolder =
+ aFolder.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+ if (trashFolder &&
+ (trashFolder == aFolder || trashFolder.isAncestorOf(aFolder)))
+ return true;
+ return false;
+ },
+
+/**
+ * Return a folder path string constructed from individual folder UTF8 names
+ * stored as properties (not possible hashes used to construct disk foldername).
+ *
+ * @param nsIMsgFolder aFolder - the folder.
+ * @return string prettyName | null - name or null if not a disk folder.
+ */
+ getFolderPrettyPath: function(aFolder) {
+ let msgFolder = MailUtils.getFolderForURI(aFolder.URI, true);
+ if (!msgFolder)
+ // Not a real folder uri.
+ return null;
+
+ if (msgFolder.URI == msgFolder.server.serverURI)
+ return msgFolder.server.prettyName;
+
+ // Server part first.
+ let pathParts = [msgFolder.server.prettyName];
+ let rawPathParts = msgFolder.URI.split(msgFolder.server.serverURI + "/");
+ let folderURI = msgFolder.server.serverURI;
+ rawPathParts = rawPathParts[1].split("/");
+ for (let i = 0; i < rawPathParts.length - 1; i++)
+ {
+ // Two or more folders deep parts here.
+ folderURI += "/" + rawPathParts[i];
+ msgFolder = MailUtils.getFolderForURI(folderURI, true);
+ pathParts.push(msgFolder.name);
+ }
+
+ // Leaf folder last.
+ pathParts.push(aFolder.name);
+ return pathParts.join("/");
+ },
+
+/**
+ * Date validator for feeds.
+ *
+ * @param string aDate - date string
+ * @return boolean - true if passes regex test, false if not
+ */
+ isValidRFC822Date: function(aDate)
+ {
+ const FZ_RFC822_RE = "^(((Mon)|(Tue)|(Wed)|(Thu)|(Fri)|(Sat)|(Sun)), *)?\\d\\d?" +
+ " +((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec))" +
+ " +\\d\\d(\\d\\d)? +\\d\\d:\\d\\d(:\\d\\d)? +(([+-]?\\d\\d\\d\\d)|(UT)|(GMT)" +
+ "|(EST)|(EDT)|(CST)|(CDT)|(MST)|(MDT)|(PST)|(PDT)|\\w)$";
+ let regex = new RegExp(FZ_RFC822_RE);
+ return regex.test(aDate);
+ },
+
+/**
+ * Create rfc5322 date.
+ *
+ * @param [string] aDateString - optional date string; if null or invalid
+ * date, get the current datetime.
+ * @return string - an rfc5322 date string
+ */
+ getValidRFC5322Date: function(aDateString)
+ {
+ let d = new Date(aDateString || new Date().getTime());
+ d = isNaN(d.getTime()) ? new Date() : d;
+ return jsmime.headeremitter.emitStructuredHeader("Date", d, {}).substring(6).trim();
+ },
+
+ // Progress glue code. Acts as a go between the RSS back end and the mail
+ // window front end determined by the aMsgWindow parameter passed into
+ // nsINewsBlogFeedDownloader.
+ progressNotifier: {
+ mSubscribeMode: false,
+ mMsgWindow: null,
+ mStatusFeedback: null,
+ mFeeds: {},
+ // Keeps track of the total number of feeds we have been asked to download.
+ // This number may not reflect the # of entries in our mFeeds array because
+ // not all feeds may have reported in for the first time.
+ mNumPendingFeedDownloads: 0,
+
+ init: function(aMsgWindow, aSubscribeMode)
+ {
+ if (!this.mNumPendingFeedDownloads)
+ {
+ // If we aren't already in the middle of downloading feed items.
+ this.mStatusFeedback = aMsgWindow ? aMsgWindow.statusFeedback : null;
+ this.mSubscribeMode = aSubscribeMode;
+ this.mMsgWindow = aMsgWindow;
+
+ if (this.mStatusFeedback)
+ {
+ this.mStatusFeedback.startMeteors();
+ this.mStatusFeedback.showStatusString(
+ FeedUtils.strings.GetStringFromName(
+ aSubscribeMode ? "subscribe-validating-feed" :
+ "newsblog-getNewMsgsCheck"));
+ }
+ }
+ },
+
+ downloaded: function(feed, aErrorCode)
+ {
+ let location = feed.folder ? feed.folder.filePath.path : "";
+ FeedUtils.log.debug("downloaded: "+
+ (this.mSubscribeMode ? "Subscribe " : "Update ") +
+ "errorCode:feedName:folder - " +
+ aErrorCode + " : " + feed.name + " : " + location);
+ if (this.mSubscribeMode)
+ {
+ if (aErrorCode == FeedUtils.kNewsBlogSuccess)
+ {
+ // Add the feed to the databases.
+ FeedUtils.addFeed(feed);
+
+ // Nice touch: select the folder that now contains the newly subscribed
+ // feed. This is particularly nice if we just finished subscribing
+ // to a feed URL that the operating system gave us.
+ this.mMsgWindow.windowCommands.selectFolder(feed.folder.URI);
+
+ // Check for an existing feed subscriptions window and update it.
+ let subscriptionsWindow =
+ Services.wm.getMostRecentWindow("Mail:News-BlogSubscriptions");
+ if (subscriptionsWindow)
+ subscriptionsWindow.FeedSubscriptions.
+ FolderListener.folderAdded(feed.folder);
+ }
+ else
+ {
+ // Non success. Remove intermediate traces from the feeds database.
+ if (feed && feed.url && feed.server)
+ FeedUtils.deleteFeed(FeedUtils.rdf.GetResource(feed.url),
+ feed.server,
+ feed.server.rootFolder);
+ }
+ }
+
+ if (feed.folder && aErrorCode != FeedUtils.kNewsBlogFeedIsBusy)
+ // Free msgDatabase after new mail biff is set; if busy let the next
+ // result do the freeing. Otherwise new messages won't be indicated.
+ feed.folder.msgDatabase = null;
+
+ let message = "";
+ if (feed.folder)
+ location = FeedUtils.getFolderPrettyPath(feed.folder) + " -> ";
+ switch (aErrorCode) {
+ case FeedUtils.kNewsBlogSuccess:
+ case FeedUtils.kNewsBlogFeedIsBusy:
+ message = "";
+ break;
+ case FeedUtils.kNewsBlogNoNewItems:
+ message = feed.url+". " +
+ FeedUtils.strings.GetStringFromName(
+ "newsblog-noNewArticlesForFeed");
+ break;
+ case FeedUtils.kNewsBlogInvalidFeed:
+ message = FeedUtils.strings.formatStringFromName(
+ "newsblog-feedNotValid", [feed.url], 1);
+ break;
+ case FeedUtils.kNewsBlogRequestFailure:
+ message = FeedUtils.strings.formatStringFromName(
+ "newsblog-networkError", [feed.url], 1);
+ break;
+ case FeedUtils.kNewsBlogFileError:
+ message = FeedUtils.strings.GetStringFromName(
+ "subscribe-errorOpeningFile");
+ break;
+ case FeedUtils.kNewsBlogBadCertError:
+ let host = Services.io.newURI(feed.url, null, null).host;
+ message = FeedUtils.strings.formatStringFromName(
+ "newsblog-badCertError", [host], 1);
+ break;
+ case FeedUtils.kNewsBlogNoAuthError:
+ message = FeedUtils.strings.formatStringFromName(
+ "newsblog-noAuthError", [feed.url], 1);
+ break;
+ }
+ if (message)
+ FeedUtils.log.info("downloaded: " +
+ (this.mSubscribeMode ? "Subscribe: " : "Update: ") +
+ location + message);
+
+ if (this.mStatusFeedback)
+ {
+ this.mStatusFeedback.showStatusString(message);
+ this.mStatusFeedback.stopMeteors();
+ }
+
+ if (!--this.mNumPendingFeedDownloads)
+ {
+ FeedUtils.getSubscriptionsDS(feed.server).Flush();
+ this.mFeeds = {};
+ this.mSubscribeMode = false;
+ FeedUtils.log.debug("downloaded: all pending downloads finished");
+
+ // Should we do this on a timer so the text sticks around for a little
+ // while? It doesnt look like we do it on a timer for newsgroups so
+ // we'll follow that model. Don't clear the status text if we just
+ // dumped an error to the status bar!
+ if (aErrorCode == FeedUtils.kNewsBlogSuccess && this.mStatusFeedback)
+ this.mStatusFeedback.showStatusString("");
+ }
+
+ feed = null;
+ },
+
+ // This gets called after the RSS parser finishes storing a feed item to
+ // disk. aCurrentFeedItems is an integer corresponding to how many feed
+ // items have been downloaded so far. aMaxFeedItems is an integer
+ // corresponding to the total number of feed items to download
+ onFeedItemStored: function (feed, aCurrentFeedItems, aMaxFeedItems)
+ {
+ // We currently don't do anything here. Eventually we may add status
+ // text about the number of new feed articles received.
+
+ if (this.mSubscribeMode && this.mStatusFeedback)
+ {
+ // If we are subscribing to a feed, show feed download progress.
+ this.mStatusFeedback.showStatusString(
+ FeedUtils.strings.formatStringFromName("subscribe-gettingFeedItems",
+ [aCurrentFeedItems, aMaxFeedItems], 2));
+ this.onProgress(feed, aCurrentFeedItems, aMaxFeedItems);
+ }
+ },
+
+ onProgress: function(feed, aProgress, aProgressMax, aLengthComputable)
+ {
+ if (feed.url in this.mFeeds)
+ // Have we already seen this feed?
+ this.mFeeds[feed.url].currentProgress = aProgress;
+ else
+ this.mFeeds[feed.url] = {currentProgress: aProgress,
+ maxProgress: aProgressMax};
+
+ this.updateProgressBar();
+ },
+
+ updateProgressBar: function()
+ {
+ let currentProgress = 0;
+ let maxProgress = 0;
+ for (let index in this.mFeeds)
+ {
+ currentProgress += this.mFeeds[index].currentProgress;
+ maxProgress += this.mFeeds[index].maxProgress;
+ }
+
+ // If we start seeing weird "jumping" behavior where the progress bar
+ // goes below a threshold then above it again, then we can factor a
+ // fudge factor here based on the number of feeds that have not reported
+ // yet and the avg progress we've already received for existing feeds.
+ // Fortunately the progressmeter is on a timer and only updates every so
+ // often. For the most part all of our request have initial progress
+ // before the UI actually picks up a progress value.
+ if (this.mStatusFeedback)
+ {
+ let progress = (currentProgress * 100) / maxProgress;
+ this.mStatusFeedback.showProgress(progress);
+ }
+ }
+ }
+};
+
+XPCOMUtils.defineLazyGetter(FeedUtils, "log", function() {
+ return Log4Moz.getConfiguredLogger("Feeds");
+});
+
+XPCOMUtils.defineLazyGetter(FeedUtils, "strings", function() {
+ return Services.strings.createBundle(
+ "chrome://messenger-newsblog/locale/newsblog.properties");
+});
+
+XPCOMUtils.defineLazyGetter(FeedUtils, "rdf", function() {
+ return Cc["@mozilla.org/rdf/rdf-service;1"].
+ getService(Ci.nsIRDFService);
+});
+
+XPCOMUtils.defineLazyGetter(FeedUtils, "rdfContainerUtils", function() {
+ return Cc["@mozilla.org/rdf/container-utils;1"].
+ getService(Ci.nsIRDFContainerUtils);
+});
diff --git a/mailnews/extensions/newsblog/content/am-newsblog.js b/mailnews/extensions/newsblog/content/am-newsblog.js
new file mode 100644
index 000000000..674280f81
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/am-newsblog.js
@@ -0,0 +1,63 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/FeedUtils.jsm");
+
+var gServer, autotagEnable, autotagUsePrefix, autotagPrefix;
+
+function onInit(aPageId, aServerId)
+{
+ var accountName = document.getElementById("server.prettyName");
+ var title = document.getElementById("am-newsblog-title");
+ var defaultTitle = title.getAttribute("defaultTitle");
+
+ var titleValue;
+ if (accountName.value)
+ titleValue = defaultTitle + " - <" + accountName.value + ">";
+ else
+ titleValue = defaultTitle;
+
+ title.setAttribute("title", titleValue);
+ document.title = titleValue;
+
+ onCheckItem("server.biffMinutes", ["server.doBiff"]);
+
+ autotagEnable = document.getElementById("autotagEnable");
+ autotagUsePrefix = document.getElementById("autotagUsePrefix");
+ autotagPrefix = document.getElementById("autotagPrefix");
+
+ let categoryPrefsAcct = FeedUtils.getOptionsAcct(gServer).category;
+ autotagEnable.checked = categoryPrefsAcct.enabled;
+ autotagUsePrefix.disabled = !autotagEnable.checked;
+ autotagUsePrefix.checked = categoryPrefsAcct.prefixEnabled;
+ autotagPrefix.disabled = autotagUsePrefix.disabled || !autotagUsePrefix.checked;
+ autotagPrefix.value = categoryPrefsAcct.prefix;
+}
+
+function onPreInit(account, accountValues)
+{
+ gServer = account.incomingServer;
+}
+
+function setCategoryPrefs(aNode)
+{
+ let options = FeedUtils.getOptionsAcct(gServer);
+ switch (aNode.id) {
+ case "autotagEnable":
+ options.category.enabled = aNode.checked;
+ autotagUsePrefix.disabled = !aNode.checked;
+ autotagPrefix.disabled = !aNode.checked || !autotagUsePrefix.checked;
+ break;
+ case "autotagUsePrefix":
+ options.category.prefixEnabled = aNode.checked;
+ autotagPrefix.disabled = aNode.disabled || !aNode.checked;
+ break;
+ case "autotagPrefix":
+ options.category.prefix = aNode.value;
+ break;
+ }
+
+ FeedUtils.setOptionsAcct(gServer, options)
+}
diff --git a/mailnews/extensions/newsblog/content/am-newsblog.xul b/mailnews/extensions/newsblog/content/am-newsblog.xul
new file mode 100644
index 000000000..19173d803
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/am-newsblog.xul
@@ -0,0 +1,155 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger-newsblog/skin/feed-subscriptions.css" type="text/css"?>
+
+<!DOCTYPE page [
+<!ENTITY % newsblogDTD SYSTEM "chrome://messenger-newsblog/locale/am-newsblog.dtd" >
+%newsblogDTD;
+<!ENTITY % feedDTD SYSTEM "chrome://messenger-newsblog/locale/feed-subscriptions.dtd" >
+%feedDTD;
+<!ENTITY % accountNoIdentDTD SYSTEM "chrome://messenger/locale/am-serverwithnoidentities.dtd" >
+%accountNoIdentDTD;
+<!ENTITY % accountServerTopDTD SYSTEM "chrome://messenger/locale/am-server-top.dtd">
+%accountServerTopDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="color-dialog"
+ title="&accountTitle.label;"
+ onload="parent.onPanelLoaded('am-newsblog.xul');">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger-newsblog/content/am-newsblog.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger-newsblog/content/newsblogOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/amUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/am-prefs.js"/>
+
+ <vbox flex="1" style="overflow: auto;">
+
+ <dialogheader id="am-newsblog-title" defaultTitle="&accountTitle.label;"/>
+
+ <description class="secDesc">&accountSettingsDesc.label;</description>
+
+ <hbox align="center">
+ <label value="&accountName.label;"
+ accesskey="&accountName.accesskey;"
+ control="server.prettyName"/>
+ <textbox id="server.prettyName"
+ wsm_persist="true"
+ size="30"
+ prefstring="mail.server.%serverkey%.name"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <caption label="&serverSettings.label;"/>
+
+ <checkbox id="server.loginAtStartUp"
+ wsm_persist="true"
+ label="&loginAtStartup.label;"
+ accesskey="&loginAtStartup.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.login_at_startup"/>
+
+ <hbox align="center">
+ <checkbox id="server.doBiff"
+ wsm_persist="true"
+ label="&biffStart.label;"
+ accesskey="&biffStart.accesskey;"
+ oncommand="onCheckItem('server.biffMinutes', [this.id]);"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.check_new_mail"/>
+ <textbox id="server.biffMinutes"
+ wsm_persist="true"
+ type="number"
+ size="3"
+ min="1"
+ increment="1"
+ preftype="int"
+ prefstring="mail.server.%serverkey%.check_time"
+ aria-labelledby="server.doBiff server.biffMinutes biffEnd"/>
+ <label id="biffEnd"
+ value="&biffEnd.label;"
+ control="server.biffMinutes"/>
+ </hbox>
+
+ <checkbox id="server.quickMode"
+ wsm_persist="true"
+ genericattr="true"
+ label="&useQuickMode.label;"
+ accesskey="&useQuickMode.accesskey;"
+ preftype="bool"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.quickMode"/>
+
+ <checkbox id="autotagEnable"
+ accesskey="&autotagEnable.accesskey;"
+ label="&autotagEnable.label;"
+ oncommand="setCategoryPrefs(this)"/>
+ <hbox>
+ <checkbox id="autotagUsePrefix"
+ class="indent"
+ accesskey="&autotagUsePrefix.accesskey;"
+ label="&autotagUsePrefix.label;"
+ oncommand="setCategoryPrefs(this)"/>
+ <textbox id="autotagPrefix"
+ placeholder="&autoTagPrefix.placeholder;"
+ clickSelectsAll="true"
+ onchange="setCategoryPrefs(this)"/>
+ </hbox>
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <caption label="&messageStorage.label;"/>
+
+ <checkbox id="server.emptyTrashOnExit"
+ wsm_persist="true"
+ label="&emptyTrashOnExit.label;"
+ accesskey="&emptyTrashOnExit.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.empty_trash_on_exit"/>
+
+ <separator class="thin"/>
+
+ <vbox>
+ <label value="&localPath.label;" control="server.localPath"/>
+ <hbox align="center">
+ <textbox readonly="true"
+ wsm_persist="true"
+ flex="1"
+ id="server.localPath"
+ datatype="nsIFile"
+ prefstring="mail.server.%serverkey%.directory"
+ class="uri-element"/>
+ <button id="browseForLocalFolder"
+ label="&browseFolder.label;"
+ filepickertitle="&localFolderPicker.label;"
+ accesskey="&browseFolder.accesskey;"
+ oncommand="BrowseForLocalFolders();"/>
+ </hbox>
+ </vbox>
+
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <hbox align="center">
+ <spacer flex="1"/>
+ <button label="&manageSubscriptions.label;"
+ accesskey="&manageSubscriptions.accesskey;"
+ oncommand="openSubscriptionsDialog(gServer.rootFolder);"/>
+ </hbox>
+ </vbox>
+</page>
diff --git a/mailnews/extensions/newsblog/content/feed-parser.js b/mailnews/extensions/newsblog/content/feed-parser.js
new file mode 100644
index 000000000..03fc72b22
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/feed-parser.js
@@ -0,0 +1,1080 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// The feed parser depends on FeedItem.js, Feed.js.
+function FeedParser() {
+ this.mSerializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
+ createInstance(Ci.nsIDOMSerializer);
+}
+
+FeedParser.prototype =
+{
+ // parseFeed() returns an array of parsed items ready for processing. It is
+ // currently a synchronous operation. If there is an error parsing the feed,
+ // parseFeed returns an empty feed in addition to calling aFeed.onParseError.
+ parseFeed: function (aFeed, aDOM)
+ {
+ if (!(aDOM instanceof Ci.nsIDOMXMLDocument))
+ {
+ // No xml doc.
+ return aFeed.onParseError(aFeed);
+ }
+
+ let doc = aDOM.documentElement;
+ if (doc.namespaceURI == FeedUtils.MOZ_PARSERERROR_NS)
+ {
+ // Gecko caught a basic parsing error.
+ let errStr = doc.firstChild.textContent + "\n" +
+ doc.firstElementChild.textContent;
+ FeedUtils.log.info("FeedParser.parseFeed: - " + errStr);
+ return aFeed.onParseError(aFeed);
+ }
+ else if (aDOM.querySelector("redirect"))
+ {
+ // Check for RSS2.0 redirect document.
+ let channel = aDOM.querySelector("redirect");
+ if (this.isPermanentRedirect(aFeed, channel, null, null))
+ return;
+
+ return aFeed.onParseError(aFeed);
+ }
+ else if (doc.namespaceURI == FeedUtils.RDF_SYNTAX_NS &&
+ doc.getElementsByTagNameNS(FeedUtils.RSS_NS, "channel")[0])
+ {
+ aFeed.mFeedType = "RSS_1.xRDF"
+ FeedUtils.log.debug("FeedParser.parseFeed: type:url - " +
+ aFeed.mFeedType +" : " +aFeed.url);
+ // aSource can be misencoded (XMLHttpRequest converts to UTF-8 by default),
+ // but the DOM is almost always right because it uses the hints in the
+ // XML file. This is slower, but not noticably so. Mozilla doesn't have
+ // the XMLHttpRequest.responseBody property that IE has, which provides
+ // access to the unencoded response.
+ let xmlString = this.mSerializer.serializeToString(doc);
+ return this.parseAsRSS1(aFeed, xmlString, aFeed.request.channel.URI);
+ }
+ else if (doc.namespaceURI == FeedUtils.ATOM_03_NS)
+ {
+ aFeed.mFeedType = "ATOM_0.3"
+ FeedUtils.log.debug("FeedParser.parseFeed: type:url - " +
+ aFeed.mFeedType +" : " +aFeed.url);
+ return this.parseAsAtom(aFeed, aDOM);
+ }
+ else if (doc.namespaceURI == FeedUtils.ATOM_IETF_NS)
+ {
+ aFeed.mFeedType = "ATOM_IETF"
+ FeedUtils.log.debug("FeedParser.parseFeed: type:url - " +
+ aFeed.mFeedType +" : " +aFeed.url);
+ return this.parseAsAtomIETF(aFeed, aDOM);
+ }
+ else if (doc.getElementsByTagNameNS(FeedUtils.RSS_090_NS, "channel")[0])
+ {
+ aFeed.mFeedType = "RSS_0.90"
+ FeedUtils.log.debug("FeedParser.parseFeed: type:url - " +
+ aFeed.mFeedType +" : " +aFeed.url);
+ return this.parseAsRSS2(aFeed, aDOM);
+ }
+ else
+ {
+ // Parse as RSS 0.9x. In theory even RSS 1.0 feeds could be parsed by
+ // the 0.9x parser if the RSS namespace were the default.
+ let rssVer = doc.localName == "rss" ? doc.getAttribute("version") : null;
+ if (rssVer)
+ aFeed.mFeedType = "RSS_" + rssVer;
+ else
+ aFeed.mFeedType = "RSS_0.9x?";
+ FeedUtils.log.debug("FeedParser.parseFeed: type:url - " +
+ aFeed.mFeedType +" : " +aFeed.url);
+ return this.parseAsRSS2(aFeed, aDOM);
+ }
+ },
+
+ parseAsRSS2: function (aFeed, aDOM)
+ {
+ // Get the first channel (assuming there is only one per RSS File).
+ let parsedItems = new Array();
+
+ let channel = aDOM.querySelector("channel");
+ if (!channel)
+ return aFeed.onParseError(aFeed);
+
+ // Usually the empty string, unless this is RSS .90.
+ let nsURI = channel.namespaceURI || "";
+ FeedUtils.log.debug("FeedParser.parseAsRSS2: channel nsURI - " + nsURI);
+
+ if (this.isPermanentRedirect(aFeed, null, channel, null))
+ return;
+
+ let tags = this.childrenByTagNameNS(channel, nsURI, "title");
+ aFeed.title = aFeed.title || this.getNodeValue(tags ? tags[0] : null);
+ tags = this.childrenByTagNameNS(channel, nsURI, "description");
+ aFeed.description = this.getNodeValueFormatted(tags ? tags[0] : null);
+ tags = this.childrenByTagNameNS(channel, nsURI, "link");
+ aFeed.link = this.validLink(this.getNodeValue(tags ? tags[0] : null));
+
+ if (!(aFeed.title || aFeed.description) || !aFeed.link)
+ {
+ FeedUtils.log.error("FeedParser.parseAsRSS2: missing mandatory element " +
+ "<title> and <description>, or <link>");
+ return aFeed.onParseError(aFeed);
+ }
+
+ if (!aFeed.parseItems)
+ return parsedItems;
+
+ aFeed.invalidateItems();
+ // XXX use getElementsByTagNameNS for now; childrenByTagNameNS would be
+ // better, but RSS .90 is still with us.
+ let itemNodes = aDOM.getElementsByTagNameNS(nsURI, "item");
+ itemNodes = itemNodes ? itemNodes : [];
+ FeedUtils.log.debug("FeedParser.parseAsRSS2: items to parse - " +
+ itemNodes.length);
+
+ for (let itemNode of itemNodes)
+ {
+ if (!itemNode.childElementCount)
+ continue;
+ let item = new FeedItem();
+ item.feed = aFeed;
+ item.enclosures = [];
+ item.keywords = [];
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.FEEDBURNER_NS, "origLink");
+ let link = this.validLink(this.getNodeValue(tags ? tags[0] : null));
+ if (!link)
+ {
+ tags = this.childrenByTagNameNS(itemNode, nsURI, "link");
+ link = this.validLink(this.getNodeValue(tags ? tags[0] : null));
+ }
+ tags = this.childrenByTagNameNS(itemNode, nsURI, "guid");
+ let guidNode = tags ? tags[0] : null;
+
+ let guid;
+ let isPermaLink = false;
+ if (guidNode)
+ {
+ guid = this.getNodeValue(guidNode);
+ // isPermaLink is true if the value is "true" or if the attribute is
+ // not present; all other values, including "false" and "False" and
+ // for that matter "TRuE" and "meatcake" are false.
+ if (!guidNode.hasAttribute("isPermaLink") ||
+ guidNode.getAttribute("isPermaLink") == "true")
+ isPermaLink = true;
+ // If attribute isPermaLink is missing, it is good to check the validity
+ // of <guid> value as an URL to avoid linking to non-URL strings.
+ if (!guidNode.hasAttribute("isPermaLink"))
+ {
+ try
+ {
+ Services.io.newURI(guid, null, null);
+ if (Services.io.extractScheme(guid) == "tag")
+ isPermaLink = false;
+ }
+ catch (ex)
+ {
+ isPermaLink = false;
+ }
+ }
+
+ item.id = guid;
+ }
+
+ let guidLink = this.validLink(guid);
+ item.url = isPermaLink && guidLink ? guidLink : link ? link : null;
+ tags = this.childrenByTagNameNS(itemNode, nsURI, "description");
+ item.description = this.getNodeValueFormatted(tags ? tags[0] : null);
+ tags = this.childrenByTagNameNS(itemNode, nsURI, "title");
+ item.title = this.getNodeValue(tags ? tags[0] : null);
+ if (!(item.title || item.description))
+ {
+ FeedUtils.log.info("FeedParser.parseAsRSS2: <item> missing mandatory " +
+ "element, either <title> or <description>; skipping");
+ continue;
+ }
+
+ if (!item.id)
+ {
+ // At this point, if there is no guid, uniqueness cannot be guaranteed
+ // by any of link or date (optional) or title (optional unless there
+ // is no description). Use a big chunk of description; minimize dupes
+ // with url and title if present.
+ item.id = (item.url || item.feed.url) + "#" + item.title + "#" +
+ (this.stripTags(item.description ?
+ item.description.substr(0, 150) : null) ||
+ item.title);
+ item.id = item.id.replace(/[\n\r\t\s]+/g, " ");
+ }
+
+ // Escape html entities in <title>, which are unescaped as textContent
+ // values. If the title is used as content, it will remain escaped; if
+ // it is used as the title, it will be unescaped upon store. Bug 1240603.
+ // The <description> tag must follow escaping examples found in
+ // http://www.rssboard.org/rss-encoding-examples, i.e. single escape angle
+ // brackets for tags, which are removed if used as title, and double
+ // escape entities for presentation in title.
+ // Better: always use <title>. Best: use Atom.
+ if (!item.title)
+ item.title = this.stripTags(item.description).substr(0, 150);
+ else
+ item.title = item.htmlEscape(item.title);
+
+ tags = this.childrenByTagNameNS(itemNode, nsURI, "author");
+ if (!tags)
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.DC_NS, "creator");
+ let author = this.getNodeValue(tags ? tags[0] : null) ||
+ aFeed.title;
+ author = this.cleanAuthorName(author);
+ item.author = author ? ["<" + author + ">"] : item.author;
+
+ tags = this.childrenByTagNameNS(itemNode, nsURI, "pubDate");
+ if (!tags || !this.getNodeValue(tags[0]))
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.DC_NS, "date");
+ item.date = this.getNodeValue(tags ? tags[0] : null) || item.date;
+
+ // If the date is invalid, users will see the beginning of the epoch
+ // unless we reset it here, so they'll see the current time instead.
+ // This is typical aggregator behavior.
+ if (item.date)
+ {
+ item.date = item.date.trim();
+ if (!FeedUtils.isValidRFC822Date(item.date))
+ {
+ // XXX Use this on the other formats as well.
+ item.date = this.dateRescue(item.date);
+ }
+ }
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.RSS_CONTENT_NS, "encoded");
+ item.content = this.getNodeValueFormatted(tags ? tags[0] : null);
+
+ // Handle <enclosures> and <media:content>, which may be in a
+ // <media:group> (if present).
+ tags = this.childrenByTagNameNS(itemNode, nsURI, "enclosure");
+ let encUrls = [];
+ if (tags)
+ for (let tag of tags)
+ {
+ let url = this.validLink(tag.getAttribute("url"));
+ if (url && encUrls.indexOf(url) == -1)
+ {
+ let type = this.removeUnprintableASCII(tag.getAttribute("type"));
+ let length = this.removeUnprintableASCII(tag.getAttribute("length"));
+ item.enclosures.push(new FeedEnclosure(url, type, length));
+ encUrls.push(url);
+ }
+ }
+
+ tags = itemNode.getElementsByTagNameNS(FeedUtils.MRSS_NS, "content");
+ if (tags)
+ for (let tag of tags)
+ {
+ let url = this.validLink(tag.getAttribute("url"));
+ if (url && encUrls.indexOf(url) == -1)
+ {
+ let type = this.removeUnprintableASCII(tag.getAttribute("type"));
+ let fileSize = this.removeUnprintableASCII(tag.getAttribute("fileSize"));
+ item.enclosures.push(new FeedEnclosure(url, type, fileSize));
+ }
+ }
+
+ // The <origEnclosureLink> tag has no specification, especially regarding
+ // whether more than one tag is allowed and, if so, how tags would
+ // relate to previously declared (and well specified) enclosure urls.
+ // The common usage is to include 1 origEnclosureLink, in addition to
+ // the specified enclosure tags for 1 enclosure. Thus, we will replace the
+ // first enclosure's, if found, url with the first <origEnclosureLink>
+ // url only or else add the <origEnclosureLink> url.
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.FEEDBURNER_NS, "origEnclosureLink");
+ let origEncUrl = this.validLink(this.getNodeValue(tags ? tags[0] : null));
+ if (origEncUrl)
+ {
+ if (item.enclosures.length)
+ item.enclosures[0].mURL = origEncUrl;
+ else
+ item.enclosures.push(new FeedEnclosure(origEncUrl));
+ }
+
+ // Support <category> and autotagging.
+ tags = this.childrenByTagNameNS(itemNode, nsURI, "category");
+ if (tags)
+ {
+ for (let tag of tags)
+ {
+ let term = this.getNodeValue(tag);
+ term = term ? this.xmlUnescape(term.replace(/,/g, ";")) : null;
+ if (term && item.keywords.indexOf(term) == -1)
+ item.keywords.push(term);
+ }
+ }
+
+ parsedItems.push(item);
+ }
+
+ return parsedItems;
+ },
+
+ parseAsRSS1 : function(aFeed, aSource, aBaseURI)
+ {
+ let parsedItems = new Array();
+
+ // RSS 1.0 is valid RDF, so use the RDF parser/service to extract data.
+ // Create a new RDF data source and parse the feed into it.
+ let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
+ createInstance(Ci.nsIRDFDataSource);
+
+ let rdfparser = Cc["@mozilla.org/rdf/xml-parser;1"].
+ createInstance(Ci.nsIRDFXMLParser);
+ rdfparser.parseString(ds, aBaseURI, aSource);
+
+ // Get information about the feed as a whole.
+ let channel = ds.GetSource(FeedUtils.RDF_TYPE, FeedUtils.RSS_CHANNEL, true);
+ if (!channel)
+ return aFeed.onParseError(aFeed);
+
+ if (this.isPermanentRedirect(aFeed, null, channel, ds))
+ return;
+
+ aFeed.title = aFeed.title ||
+ this.getRDFTargetValue(ds, channel, FeedUtils.RSS_TITLE) ||
+ aFeed.url;
+ aFeed.description = this.getRDFTargetValueFormatted(ds, channel, FeedUtils.RSS_DESCRIPTION) ||
+ "";
+ aFeed.link = this.validLink(this.getRDFTargetValue(ds, channel, FeedUtils.RSS_LINK)) ||
+ aFeed.url;
+
+ if (!(aFeed.title || aFeed.description) || !aFeed.link)
+ {
+ FeedUtils.log.error("FeedParser.parseAsRSS1: missing mandatory element " +
+ "<title> and <description>, or <link>");
+ return aFeed.onParseError(aFeed);
+ }
+
+ if (!aFeed.parseItems)
+ return parsedItems;
+
+ aFeed.invalidateItems();
+
+ // Ignore the <items> list and just get the <item>s.
+ let items = ds.GetSources(FeedUtils.RDF_TYPE, FeedUtils.RSS_ITEM, true);
+
+ let index = 0;
+ while (items.hasMoreElements())
+ {
+ let itemResource = items.getNext().QueryInterface(Ci.nsIRDFResource);
+ let item = new FeedItem();
+ item.feed = aFeed;
+
+ // Prefer the value of the link tag to the item URI since the URI could be
+ // a relative URN.
+ let uri = itemResource.ValueUTF8;
+ let link = this.validLink(this.getRDFTargetValue(ds, itemResource, FeedUtils.RSS_LINK));
+ item.url = link || uri;
+ item.description = this.getRDFTargetValueFormatted(ds, itemResource,
+ FeedUtils.RSS_DESCRIPTION);
+ item.title = this.getRDFTargetValue(ds, itemResource, FeedUtils.RSS_TITLE) ||
+ this.getRDFTargetValue(ds, itemResource, FeedUtils.DC_SUBJECT) ||
+ (item.description ?
+ (this.stripTags(item.description).substr(0, 150)) : null);
+ if (!item.url || !item.title)
+ {
+ FeedUtils.log.info("FeedParser.parseAsRSS1: <item> missing mandatory " +
+ "element <item rdf:about> and <link>, or <title> and " +
+ "no <description>; skipping");
+ continue;
+ }
+
+ item.id = item.url;
+ item.url = this.validLink(item.url);
+
+ let author = this.getRDFTargetValue(ds, itemResource, FeedUtils.DC_CREATOR) ||
+ this.getRDFTargetValue(ds, channel, FeedUtils.DC_CREATOR) ||
+ aFeed.title;
+ author = this.cleanAuthorName(author);
+ item.author = author ? ["<" + author + ">"] : item.author;
+
+ item.date = this.getRDFTargetValue(ds, itemResource, FeedUtils.DC_DATE) ||
+ item.date;
+ item.content = this.getRDFTargetValueFormatted(ds, itemResource,
+ FeedUtils.RSS_CONTENT_ENCODED);
+
+ parsedItems[index++] = item;
+ }
+ FeedUtils.log.debug("FeedParser.parseAsRSS1: items parsed - " + index);
+
+ return parsedItems;
+ },
+
+ parseAsAtom: function(aFeed, aDOM)
+ {
+ let parsedItems = new Array();
+
+ // Get the first channel (assuming there is only one per Atom File).
+ let channel = aDOM.querySelector("feed");
+ if (!channel)
+ return aFeed.onParseError(aFeed);
+
+ if (this.isPermanentRedirect(aFeed, null, channel, null))
+ return;
+
+ let tags = this.childrenByTagNameNS(channel, FeedUtils.ATOM_03_NS, "title");
+ aFeed.title = aFeed.title ||
+ this.stripTags(this.getNodeValue(tags ? tags[0] : null));
+ tags = this.childrenByTagNameNS(channel, FeedUtils.ATOM_03_NS, "tagline");
+ aFeed.description = this.getNodeValueFormatted(tags ? tags[0] : null);
+ tags = this.childrenByTagNameNS(channel, FeedUtils.ATOM_03_NS, "link");
+ aFeed.link = this.validLink(this.findAtomLink("alternate", tags));
+
+ if (!aFeed.title)
+ {
+ FeedUtils.log.error("FeedParser.parseAsAtom: missing mandatory element " +
+ "<title>");
+ return aFeed.onParseError(aFeed);
+ }
+
+ if (!aFeed.parseItems)
+ return parsedItems;
+
+ aFeed.invalidateItems();
+ let items = this.childrenByTagNameNS(channel, FeedUtils.ATOM_03_NS, "entry");
+ items = items ? items : [];
+ FeedUtils.log.debug("FeedParser.parseAsAtom: items to parse - " +
+ items.length);
+
+ for (let itemNode of items)
+ {
+ if (!itemNode.childElementCount)
+ continue;
+ let item = new FeedItem();
+ item.feed = aFeed;
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_03_NS, "link");
+ item.url = this.validLink(this.findAtomLink("alternate", tags));
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_03_NS, "id");
+ item.id = this.getNodeValue(tags ? tags[0] : null);
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_03_NS, "summary");
+ item.description = this.getNodeValueFormatted(tags ? tags[0] : null);
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_03_NS, "title");
+ item.title = this.getNodeValue(tags ? tags[0] : null) ||
+ (item.description ? item.description.substr(0, 150) : null);
+ if (!item.title || !item.id)
+ {
+ // We're lenient about other mandatory tags, but insist on these.
+ FeedUtils.log.info("FeedParser.parseAsAtom: <entry> missing mandatory " +
+ "element <id>, or <title> and no <summary>; skipping");
+ continue;
+ }
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_03_NS, "author");
+ if (!tags)
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_03_NS, "contributor");
+ if (!tags)
+ tags = this.childrenByTagNameNS(channel, FeedUtils.ATOM_03_NS, "author");
+
+ let authorEl = tags ? tags[0] : null;
+
+ let author = "";
+ if (authorEl)
+ {
+ tags = this.childrenByTagNameNS(authorEl, FeedUtils.ATOM_03_NS, "name");
+ let name = this.getNodeValue(tags ? tags[0] : null);
+ tags = this.childrenByTagNameNS(authorEl, FeedUtils.ATOM_03_NS, "email");
+ let email = this.getNodeValue(tags ? tags[0] : null);
+ if (name)
+ author = name + (email ? " <" + email + ">" : "");
+ else if (email)
+ author = email;
+ }
+
+ item.author = author || item.author || aFeed.title;
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_03_NS, "modified");
+ if (!tags || !this.getNodeValue(tags[0]))
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_03_NS, "issued");
+ if (!tags || !this.getNodeValue(tags[0]))
+ tags = this.childrenByTagNameNS(channel, FeedUtils.ATOM_03_NS, "created");
+
+ item.date = this.getNodeValue(tags ? tags[0] : null) || item.date;
+
+ // XXX We should get the xml:base attribute from the content tag as well
+ // and use it as the base HREF of the message.
+ // XXX Atom feeds can have multiple content elements; we should differentiate
+ // between them and pick the best one.
+ // Some Atom feeds wrap the content in a CTYPE declaration; others use
+ // a namespace to identify the tags as HTML; and a few are buggy and put
+ // HTML tags in without declaring their namespace so they look like Atom.
+ // We deal with the first two but not the third.
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_03_NS, "content");
+ let contentNode = tags ? tags[0] : null;
+
+ let content;
+ if (contentNode)
+ {
+ content = "";
+ for (let j = 0; j < contentNode.childNodes.length; j++)
+ {
+ let node = contentNode.childNodes.item(j);
+ if (node.nodeType == node.CDATA_SECTION_NODE)
+ content += node.data;
+ else
+ content += this.mSerializer.serializeToString(node);
+ }
+
+ if (contentNode.getAttribute("mode") == "escaped")
+ {
+ content = content.replace(/&lt;/g, "<");
+ content = content.replace(/&gt;/g, ">");
+ content = content.replace(/&amp;/g, "&");
+ }
+
+ if (content == "")
+ content = null;
+ }
+
+ item.content = content;
+ parsedItems.push(item);
+ }
+
+ return parsedItems;
+ },
+
+ parseAsAtomIETF: function(aFeed, aDOM)
+ {
+ let parsedItems = new Array();
+
+ // Get the first channel (assuming there is only one per Atom File).
+ let channel = this.childrenByTagNameNS(aDOM, FeedUtils.ATOM_IETF_NS, "feed")[0];
+ if (!channel)
+ return aFeed.onParseError(aFeed);
+
+ if (this.isPermanentRedirect(aFeed, null, channel, null))
+ return;
+
+ let tags = this.childrenByTagNameNS(channel, FeedUtils.ATOM_IETF_NS, "title");
+ aFeed.title = aFeed.title ||
+ this.stripTags(this.serializeTextConstruct(tags ? tags[0] : null));
+
+ tags = this.childrenByTagNameNS(channel, FeedUtils.ATOM_IETF_NS, "subtitle");
+ aFeed.description = this.serializeTextConstruct(tags ? tags[0] : null);
+
+ tags = this.childrenByTagNameNS(channel, FeedUtils.ATOM_IETF_NS, "link");
+ aFeed.link = this.findAtomLink("alternate", tags);
+ aFeed.link = this.validLink(aFeed.link);
+
+ if (!aFeed.title)
+ {
+ FeedUtils.log.error("FeedParser.parseAsAtomIETF: missing mandatory element " +
+ "<title>");
+ return aFeed.onParseError(aFeed);
+ }
+
+ if (!aFeed.parseItems)
+ return parsedItems;
+
+ aFeed.invalidateItems();
+ let items = this.childrenByTagNameNS(channel, FeedUtils.ATOM_IETF_NS, "entry");
+ items = items ? items : [];
+ FeedUtils.log.debug("FeedParser.parseAsAtomIETF: items to parse - " +
+ items.length);
+
+ for (let itemNode of items)
+ {
+ if (!itemNode.childElementCount)
+ continue;
+ let item = new FeedItem();
+ item.feed = aFeed;
+ item.enclosures = [];
+ item.keywords = [];
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.FEEDBURNER_NS, "origLink");
+ item.url = this.validLink(this.getNodeValue(tags ? tags[0] : null));
+ if (!item.url)
+ {
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "link");
+ item.url = this.validLink(this.findAtomLink("alternate", tags)) ||
+ aFeed.link;
+ }
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "id");
+ item.id = this.getNodeValue(tags ? tags[0] : null);
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "summary");
+ item.description = this.serializeTextConstruct(tags ? tags[0] : null);
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "title");
+ item.title = this.stripTags(this.serializeTextConstruct(tags ? tags[0] : null) ||
+ (item.description ?
+ item.description.substr(0, 150) : null));
+ if (!item.title || !item.id)
+ {
+ // We're lenient about other mandatory tags, but insist on these.
+ FeedUtils.log.info("FeedParser.parseAsAtomIETF: <entry> missing mandatory " +
+ "element <id>, or <title> and no <summary>; skipping");
+ continue;
+ }
+
+ // Support multiple authors.
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "source");
+ let source = tags ? tags[0] : null;
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "author");
+ if (!tags)
+ tags = this.childrenByTagNameNS(source, FeedUtils.ATOM_IETF_NS, "author");
+ if (!tags)
+ tags = this.childrenByTagNameNS(channel, FeedUtils.ATOM_IETF_NS, "author");
+
+ let authorTags = tags || [];
+ let authors = [];
+ for (let authorTag of authorTags) {
+ let author = "";
+ tags = this.childrenByTagNameNS(authorTag, FeedUtils.ATOM_IETF_NS, "name");
+ let name = this.getNodeValue(tags ? tags[0] : null);
+ tags = this.childrenByTagNameNS(authorTag, FeedUtils.ATOM_IETF_NS, "email");
+ let email = this.getNodeValue(tags ? tags[0] : null);
+ if (name) {
+ name = this.cleanAuthorName(name);
+ if (email) {
+ if (!email.match(/^<.*>$/)) {
+ email = " <" + email + ">";
+ }
+ author = name + email;
+ } else {
+ author = "<" + name + ">";
+ }
+ } else if (email) {
+ author = email;
+ }
+ if (author) {
+ authors.push(author);
+ }
+ }
+
+ if (authors.length == 0) {
+ tags = this.childrenByTagNameNS(channel, FeedUtils.DC_NS, "publisher");
+ let author = this.getNodeValue(tags ? tags[0] : null) ||
+ aFeed.title;
+ author = this.cleanAuthorName(author);
+ item.author = author ? ["<" + author + ">"] : item.author;
+ } else {
+ item.author = authors;
+ }
+ FeedUtils.log.trace("FeedParser.parseAsAtomIETF: author(s) - " + item.author);
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "updated");
+ if (!tags || !this.getNodeValue(tags[0]))
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "published");
+ if (!tags || !this.getNodeValue(tags[0]))
+ tags = this.childrenByTagNameNS(source, FeedUtils.ATOM_IETF_NS, "published");
+ item.date = this.getNodeValue(tags ? tags[0] : null) || item.date;
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "content");
+ item.content = this.serializeTextConstruct(tags ? tags[0] : null);
+
+ if (item.content)
+ item.xmlContentBase = tags ? tags[0].baseURI : null;
+ else if (item.description)
+ {
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "summary");
+ item.xmlContentBase = tags ? tags[0].baseURI : null;
+ }
+ else
+ item.xmlContentBase = itemNode.baseURI;
+
+ item.xmlContentBase = this.validLink(item.xmlContentBase);
+
+ // Handle <link rel="enclosure"> (if present).
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "link");
+ let encUrls = [];
+ if (tags)
+ for (let tag of tags)
+ {
+ let url = tag.getAttribute("rel") == "enclosure" ?
+ (tag.getAttribute("href") || "").trim() : null;
+ url = this.validLink(url);
+ if (url && encUrls.indexOf(url) == -1)
+ {
+ let type = this.removeUnprintableASCII(tag.getAttribute("type"));
+ let length = this.removeUnprintableASCII(tag.getAttribute("length"));
+ let title = this.removeUnprintableASCII(tag.getAttribute("title"));
+ item.enclosures.push(new FeedEnclosure(url, type, length, title));
+ encUrls.push(url);
+ }
+ }
+
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.FEEDBURNER_NS, "origEnclosureLink");
+ let origEncUrl = this.validLink(this.getNodeValue(tags ? tags[0] : null));
+ if (origEncUrl)
+ {
+ if (item.enclosures.length)
+ item.enclosures[0].mURL = origEncUrl;
+ else
+ item.enclosures.push(new FeedEnclosure(origEncUrl));
+ }
+
+ // Handle atom threading extension, RFC4685. There may be 1 or more tags,
+ // and each must contain a ref attribute with 1 Message-Id equivalent
+ // value. This is the only attr of interest in the spec for presentation.
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_THREAD_NS, "in-reply-to");
+ if (tags)
+ {
+ for (let tag of tags)
+ {
+ let ref = this.removeUnprintableASCII(tag.getAttribute("ref"));
+ if (ref)
+ item.inReplyTo += item.normalizeMessageID(ref) + " ";
+ }
+ item.inReplyTo = item.inReplyTo.trimRight();
+ }
+
+ // Support <category> and autotagging.
+ tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "category");
+ if (tags)
+ {
+ for (let tag of tags)
+ {
+ let term = this.removeUnprintableASCII(tag.getAttribute("term"));
+ term = term ? this.xmlUnescape(term.replace(/,/g, ";")).trim() : null;
+ if (term && item.keywords.indexOf(term) == -1)
+ item.keywords.push(term);
+ }
+ }
+
+ parsedItems.push(item);
+ }
+
+ return parsedItems;
+ },
+
+ isPermanentRedirect: function(aFeed, aRedirDocChannel, aFeedChannel, aDS)
+ {
+ // If subscribing to a new feed, do not check redirect tags.
+ if (!aFeed.downloadCallback || aFeed.downloadCallback.mSubscribeMode)
+ return false;
+
+ let tags, tagName, newUrl;
+ let oldUrl = aFeed.url;
+
+ // Check for RSS2.0 redirect document <newLocation> tag.
+ if (aRedirDocChannel)
+ {
+ tagName = "newLocation";
+ tags = this.childrenByTagNameNS(aRedirDocChannel, "", tagName);
+ newUrl = this.getNodeValue(tags ? tags[0] : null);
+ }
+
+ // Check for <itunes:new-feed-url> tag.
+ if (aFeedChannel)
+ {
+ tagName = "new-feed-url";
+ if (aDS)
+ {
+ tags = FeedUtils.rdf.GetResource(FeedUtils.ITUNES_NS + tagName);
+ newUrl = this.getRDFTargetValue(aDS, aFeedChannel, tags);
+ }
+ else
+ {
+ tags = this.childrenByTagNameNS(aFeedChannel, FeedUtils.ITUNES_NS, tagName);
+ newUrl = this.getNodeValue(tags ? tags[0] : null);
+ }
+ tagName = "itunes:" + tagName;
+ }
+
+ if (newUrl && newUrl != oldUrl && FeedUtils.isValidScheme(newUrl) &&
+ FeedUtils.changeUrlForFeed(aFeed, newUrl))
+ {
+ FeedUtils.log.info("FeedParser.isPermanentRedirect: found <" + tagName +
+ "> tag; updated feed url from: " + oldUrl + " to: " + newUrl +
+ " in folder: " + FeedUtils.getFolderPrettyPath(aFeed.folder));
+ aFeed.onUrlChange(aFeed, oldUrl);
+ return true;
+ }
+
+ return false;
+ },
+
+ serializeTextConstruct: function(textElement)
+ {
+ let content = "";
+ if (textElement)
+ {
+ let textType = textElement.getAttribute("type");
+
+ // Atom spec says consider it "text" if not present.
+ if (!textType)
+ textType = "text";
+
+ // There could be some strange content type we don't handle.
+ if (textType != "text" && textType != "html" && textType != "xhtml")
+ return null;
+
+ for (let j = 0; j < textElement.childNodes.length; j++)
+ {
+ let node = textElement.childNodes.item(j);
+ if (node.nodeType == node.CDATA_SECTION_NODE)
+ content += this.xmlEscape(node.data);
+ else
+ content += this.mSerializer.serializeToString(node);
+ }
+
+ if (textType == "html")
+ content = this.xmlUnescape(content);
+
+ content = content.trim();
+ }
+
+ // Other parts of the code depend on this being null if there's no content.
+ return content ? content : null;
+ },
+
+ /**
+ * Return a cleaned up author name value.
+ *
+ * @param {String} authorString - A string.
+ * @returns {String} - A clean string value.
+ */
+ cleanAuthorName(authorString) {
+ if (!authorString) {
+ return "";
+ }
+ FeedUtils.log.trace("FeedParser.cleanAuthor: author1 - " + authorString);
+ let author = authorString.replace(/[\n\r\t]+/g, " ")
+ .replace(/"/g, '\\"')
+ .trim();
+ // If the name contains special chars, quote it.
+ if (author.match(/[<>@,"]/)) {
+ author = '"' + author + '"';
+ }
+ FeedUtils.log.trace("FeedParser.cleanAuthor: author2 - " + author);
+
+ return author;
+ },
+
+ getRDFTargetValue: function(ds, source, property)
+ {
+ let nodeValue = this.getRDFTargetValueRaw(ds, source, property);
+ if (!nodeValue)
+ return null;
+
+ nodeValue = nodeValue.replace(/[\n\r\t]+/g, " ");
+ return this.removeUnprintableASCII(nodeValue);
+
+ },
+
+ getRDFTargetValueFormatted: function(ds, source, property)
+ {
+ let nodeValue = this.getRDFTargetValueRaw(ds, source, property);
+ if (!nodeValue)
+ return null;
+
+ return this.removeUnprintableASCIIexCRLFTAB(nodeValue);
+
+ },
+
+ getRDFTargetValueRaw: function(ds, source, property)
+ {
+ let node = ds.GetTarget(source, property, true);
+ if (node)
+ {
+ try
+ {
+ node = node.QueryInterface(Ci.nsIRDFLiteral);
+ if (node)
+ return node.Value.trim();
+ }
+ catch (e)
+ {
+ // If the RDF was bogus, do nothing. Rethrow if it's some other problem.
+ if (!((e instanceof Ci.nsIXPCException) &&
+ e.result == Cr.NS_ERROR_NO_INTERFACE))
+ throw new Error("FeedParser.getRDFTargetValue: " + e);
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Return a cleaned up node value. This is intended for values that are not
+ * multiline and not formatted. A sequence of tab or newline is converted to
+ * a space and unprintable ascii is removed.
+ *
+ * @param {Node} node - A DOM node.
+ * @return {String} - A clean string value or null.
+ */
+ getNodeValue: function(node)
+ {
+ let nodeValue = this.getNodeValueRaw(node);
+ if (!nodeValue)
+ return null;
+
+ nodeValue = nodeValue.replace(/[\n\r\t]+/g, " ");
+ return this.removeUnprintableASCII(nodeValue);
+ },
+
+ /**
+ * Return a cleaned up formatted node value, meaning CR/LF/TAB are retained
+ * while all other unprintable ascii is removed. This is intended for values
+ * that are multiline and formatted, such as content or description tags.
+ *
+ * @param {Node} node - A DOM node.
+ * @return {String} - A clean string value or null.
+ */
+ getNodeValueFormatted: function(node)
+ {
+ let nodeValue = this.getNodeValueRaw(node);
+ if (!nodeValue)
+ return null;
+
+ return this.removeUnprintableASCIIexCRLFTAB(nodeValue);
+ },
+
+ /**
+ * Return a raw node value, as received. This should be sanitized as
+ * appropriate.
+ *
+ * @param {Node} node - A DOM node.
+ * @return {String} - A string value or null.
+ */
+ getNodeValueRaw: function(node)
+ {
+ if (node && node.textContent)
+ return node.textContent.trim();
+
+ if (node && node.firstChild)
+ {
+ let ret = "";
+ for (let child = node.firstChild; child; child = child.nextSibling)
+ {
+ let value = this.getNodeValueRaw(child);
+ if (value)
+ ret += value;
+ }
+
+ if (ret)
+ return ret.trim();
+ }
+
+ return null;
+ },
+
+ // Finds elements that are direct children of the first arg.
+ childrenByTagNameNS: function(aElement, aNamespace, aTagName)
+ {
+ if (!aElement)
+ return null;
+
+ let matches = aElement.getElementsByTagNameNS(aNamespace, aTagName);
+ let matchingChildren = new Array();
+ for (let match of matches)
+ {
+ if (match.parentNode == aElement)
+ matchingChildren.push(match)
+ }
+
+ return matchingChildren.length ? matchingChildren : null;
+ },
+
+ /**
+ * Ensure <link> type tags start with http[s]://, ftp:// or magnet:
+ * for values stored in mail headers (content-base and remote enclosures),
+ * particularly to prevent data: uris, javascript, and other spoofing.
+ *
+ * @param {String} link - An intended http url string.
+ * @return {String} - A clean string starting with http, ftp or magnet,
+ * else null.
+ */
+ validLink: function(link)
+ {
+ if (/^((https?|ftp):\/\/|magnet:)/.test(link))
+ return this.removeUnprintableASCII(link.trim());
+
+ return null;
+ },
+
+ findAtomLink: function(linkRel, linkElements)
+ {
+ if (!linkElements)
+ return null;
+
+ // XXX Need to check for MIME type and hreflang.
+ for (let alink of linkElements) {
+ if (alink &&
+ // If there's a link rel.
+ ((alink.getAttribute("rel") && alink.getAttribute("rel") == linkRel) ||
+ // If there isn't, assume 'alternate'.
+ (!alink.getAttribute("rel") && (linkRel == "alternate"))) &&
+ alink.getAttribute("href"))
+ {
+ // Atom links are interpreted relative to xml:base.
+ try {
+ return Services.io.newURI(alink.baseURI, null, null).
+ resolve(alink.getAttribute("href"));
+ }
+ catch (ex) {}
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Remove unprintable ascii, particularly CR/LF, for non formatted tag values.
+ *
+ * @param {String} s - String to clean.
+ * @return {String}
+ */
+ removeUnprintableASCII: function(s)
+ {
+ return s ? s.replace(/[\x00-\x1F\x7F]+/g, "") : "";
+ },
+
+ /**
+ * Remove unprintable ascii, except CR/LF/TAB, for formatted tag values.
+ *
+ * @param {String} s - String to clean.
+ * @return {String}
+ */
+ removeUnprintableASCIIexCRLFTAB: function(s)
+ {
+ return s ? s.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]+/g, "") : "";
+ },
+
+ stripTags: function(someHTML)
+ {
+ return someHTML ? someHTML.replace(/<[^>]+>/g, "") : someHTML;
+ },
+
+ xmlUnescape: function(s)
+ {
+ s = s.replace(/&lt;/g, "<");
+ s = s.replace(/&gt;/g, ">");
+ s = s.replace(/&amp;/g, "&");
+ return s;
+ },
+
+ xmlEscape: function(s)
+ {
+ s = s.replace(/&/g, "&amp;");
+ s = s.replace(/>/g, "&gt;");
+ s = s.replace(/</g, "&lt;");
+ return s;
+ },
+
+ dateRescue: function(dateString)
+ {
+ // Deal with various kinds of invalid dates.
+ if (!isNaN(parseInt(dateString)))
+ {
+ // It's an integer, so maybe it's a timestamp.
+ let d = new Date(parseInt(dateString) * 1000);
+ let now = new Date();
+ let yeardiff = now.getFullYear() - d.getFullYear();
+ FeedUtils.log.trace("FeedParser.dateRescue: Rescue Timestamp date - " +
+ d.toString() + " ,year diff - " + yeardiff);
+ if (yeardiff >= 0 && yeardiff < 3)
+ // It's quite likely the correct date.
+ return d.toString();
+ }
+
+ // Could be an ISO8601/W3C date. If not, get the current time.
+ return FeedUtils.getValidRFC5322Date(dateString);
+ }
+};
diff --git a/mailnews/extensions/newsblog/content/feed-subscriptions.js b/mailnews/extensions/newsblog/content/feed-subscriptions.js
new file mode 100644
index 000000000..2b77e60c4
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/feed-subscriptions.js
@@ -0,0 +1,2703 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/FeedUtils.jsm");
+Components.utils.import("resource:///modules/gloda/log4moz.js");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var {classes: Cc, interfaces: Ci} = Components;
+
+var FeedSubscriptions = {
+ get mMainWin() { return Services.wm.getMostRecentWindow("mail:3pane"); },
+
+ get mTree() { return document.getElementById("rssSubscriptionsList"); },
+
+ mFeedContainers: [],
+ mRSSServer : null,
+ mActionMode : null,
+ kSubscribeMode : 1,
+ kUpdateMode : 2,
+ kMoveMode : 3,
+ kCopyMode : 4,
+ kImportingOPML : 5,
+ kVerifyUrlMode : 6,
+
+ get FOLDER_ACTIONS()
+ {
+ return Ci.nsIMsgFolderNotificationService.folderAdded |
+ Ci.nsIMsgFolderNotificationService.folderDeleted |
+ Ci.nsIMsgFolderNotificationService.folderRenamed |
+ Ci.nsIMsgFolderNotificationService.folderMoveCopyCompleted;
+ },
+
+ onLoad: function ()
+ {
+ // Extract the folder argument.
+ let folder;
+ if (window.arguments && window.arguments[0].folder)
+ folder = window.arguments[0].folder;
+
+ // Ensure dialog is fully loaded before selecting, to get visible row.
+ setTimeout(function() {
+ FeedSubscriptions.refreshSubscriptionView(folder)
+ }, 100);
+ let message = FeedUtils.strings.GetStringFromName("subscribe-loading");
+ this.updateStatusItem("statusText", message);
+
+ FeedUtils.CANCEL_REQUESTED = false;
+
+ if (this.mMainWin)
+ {
+ this.mMainWin.FeedFolderNotificationService = MailServices.mfn;
+ this.mMainWin.FeedFolderNotificationService
+ .addListener(this.FolderListener, this.FOLDER_ACTIONS);
+ }
+ },
+
+ onClose: function ()
+ {
+ let dismissDialog = true;
+
+ // If we are in the middle of subscribing to a feed, inform the user that
+ // dismissing the dialog right now will abort the feed subscription.
+ if (this.mActionMode == this.kSubscribeMode)
+ {
+ let pTitle = FeedUtils.strings.GetStringFromName(
+ "subscribe-cancelSubscriptionTitle");
+ let pMessage = FeedUtils.strings.GetStringFromName(
+ "subscribe-cancelSubscription");
+ dismissDialog =
+ !(Services.prompt.confirmEx(window, pTitle, pMessage,
+ Ci.nsIPromptService.STD_YES_NO_BUTTONS,
+ null, null, null, null, { }));
+ }
+
+ if (dismissDialog)
+ {
+ FeedUtils.CANCEL_REQUESTED = this.mActionMode == this.kSubscribeMode;
+ if (this.mMainWin)
+ {
+ this.mMainWin.FeedFolderNotificationService
+ .removeListener(this.FolderListener, this.FOLDER_ACTIONS);
+ delete this.mMainWin.FeedFolderNotificationService;
+ }
+ }
+
+ return dismissDialog;
+ },
+
+ refreshSubscriptionView: function(aSelectFolder, aSelectFeedUrl)
+ {
+ let item = this.mView.currentItem;
+ this.loadSubscriptions();
+ this.mTree.view = this.mView;
+
+ if (aSelectFolder && !aSelectFeedUrl)
+ this.selectFolder(aSelectFolder);
+ else
+ {
+ // If no folder to select, try to select the pre rebuild selection, in
+ // an existing window. For folderpane changes in a feed account.
+ if (item)
+ {
+ let rootFolder = item.container ? item.folder.rootFolder :
+ item.parentFolder.rootFolder;
+ if (item.container)
+ {
+ if (!this.selectFolder(item.folder, { open: item.open }))
+ // The item no longer exists, an ancestor folder was deleted or
+ // renamed/moved.
+ this.selectFolder(rootFolder);
+ }
+ else {
+ let url = item.parentFolder == aSelectFolder ? aSelectFeedUrl :
+ item.url;
+ this.selectFeed({ folder: rootFolder, url: url }, null);
+ }
+ }
+ }
+
+ this.mView.treeBox.ensureRowIsVisible(this.mView.selection.currentIndex);
+ this.clearStatusInfo();
+ },
+
+ mView:
+ {
+ kRowIndexUndefined: -1,
+
+ get currentItem() {
+ // Get the current selection, if any.
+ let seln = this.selection;
+ let currentSelectionIndex = seln ? seln.currentIndex : null;
+ let item;
+ if (currentSelectionIndex != null)
+ item = this.getItemAtIndex(currentSelectionIndex);
+
+ return item;
+ },
+
+ /* nsITreeView */
+ treeBox: null,
+
+ mRowCount: 0,
+ get rowCount() { return this.mRowCount; },
+
+ _selection: null,
+ get selection () { return this._selection; },
+ set selection (val) { return this._selection = val; },
+
+ setTree: function(aTreebox) { this.treeBox = aTreebox; },
+ isSeparator: function(aRow) { return false; },
+ isSorted: function() { return false; },
+ isSelectable: function(aRow, aColumn) { return false; },
+ isEditable: function (aRow, aColumn) { return false; },
+
+ getProgressMode : function(aRow, aCol) {},
+ cycleHeader: function(aCol) {},
+ cycleCell: function(aRow, aCol) {},
+ selectionChanged: function() {},
+ performAction: function(aAction) {},
+ performActionOnRow: function (aAction, aRow) {},
+ performActionOnCell: function(aAction, aRow, aCol) {},
+ getRowProperties: function(aRow) { return ""; },
+ getColumnProperties: function(aCol) { return ""; },
+ getCellValue: function (aRow, aColumn) {},
+ setCellValue: function (aRow, aColumn, aValue) {},
+ setCellText: function (aRow, aColumn, aValue) {},
+
+ getCellProperties: function (aRow, aColumn) {
+ let item = this.getItemAtIndex(aRow);
+ let folder = item && item.folder ? item.folder : null;
+#ifdef MOZ_THUNDERBIRD
+ let properties = ["folderNameCol"];
+ let hasFeeds = folder ? FeedUtils.getFeedUrlsInFolder(folder) : false;
+ let prop = !folder ? "isFeed-true" :
+ hasFeeds ? "isFeedFolder-true" :
+ folder.isServer ? "serverType-rss isServer-true" : null;
+ if (prop)
+ properties.push(prop);
+ return properties.join(" ");
+#else
+ return !folder ? "serverType-rss" :
+ folder.isServer ? "serverType-rss isServer-true" : "livemark";
+#endif
+ },
+
+ isContainer: function (aRow)
+ {
+ let item = this.getItemAtIndex(aRow);
+ return item ? item.container : false;
+ },
+
+ isContainerOpen: function (aRow)
+ {
+ let item = this.getItemAtIndex(aRow);
+ return item ? item.open : false;
+ },
+
+ isContainerEmpty: function (aRow)
+ {
+ let item = this.getItemAtIndex(aRow);
+ if (!item)
+ return false;
+
+ return item.children.length == 0;
+ },
+
+ getItemAtIndex: function (aRow)
+ {
+ if (aRow < 0 || aRow >= FeedSubscriptions.mFeedContainers.length)
+ return null;
+
+ return FeedSubscriptions.mFeedContainers[aRow];
+ },
+
+ getItemInViewIndex: function(aFolder)
+ {
+ if (!aFolder || !(aFolder instanceof Ci.nsIMsgFolder))
+ return null;
+
+ for (let index = 0; index < this.rowCount; index++)
+ {
+ // Find the visible folder in the view.
+ let item = this.getItemAtIndex(index);
+ if (item && item.container && item.url == aFolder.URI)
+ return index;
+ }
+
+ return null;
+ },
+
+ removeItemAtIndex: function (aRow, aNoSelect)
+ {
+ let itemToRemove = this.getItemAtIndex(aRow);
+ if (!itemToRemove)
+ return;
+
+ if (itemToRemove.container && itemToRemove.open)
+ // Close it, if open container.
+ this.toggleOpenState(aRow);
+
+ let parentIndex = this.getParentIndex(aRow);
+ let hasNextSibling = this.hasNextSibling(aRow, aRow);
+ if (parentIndex != this.kRowIndexUndefined)
+ {
+ let parent = this.getItemAtIndex(parentIndex);
+ if (parent)
+ {
+ for (let index = 0; index < parent.children.length; index++)
+ if (parent.children[index] == itemToRemove)
+ {
+ parent.children.splice(index, 1);
+ break;
+ }
+ }
+ }
+
+ // Now remove it from our view.
+ FeedSubscriptions.mFeedContainers.splice(aRow, 1);
+
+ // Now invalidate the correct tree rows.
+ this.mRowCount--;
+ this.treeBox.rowCountChanged(aRow, -1);
+
+ // Now update the selection position, unless noSelect (selection is
+ // done later or not at all). If the item is the last child, select the
+ // parent. Otherwise select the next sibling.
+ if (!aNoSelect) {
+ if (aRow <= FeedSubscriptions.mFeedContainers.length)
+ this.selection.select(hasNextSibling ? aRow : aRow - 1);
+ else
+ this.selection.clearSelection();
+ }
+
+ // Now refocus the tree.
+ FeedSubscriptions.mTree.focus();
+ },
+
+ getCellText: function (aRow, aColumn)
+ {
+ let item = this.getItemAtIndex(aRow);
+ return (item && aColumn.id == "folderNameCol") ? item.name : "";
+ },
+
+ getImageSrc: function(aRow, aCol)
+ {
+ let item = this.getItemAtIndex(aRow);
+ if ((item.folder && item.folder.isServer) || item.open)
+ return "";
+
+ if (item.favicon != null)
+ return item.favicon;
+
+ if (item.folder && FeedSubscriptions.mMainWin &&
+ "gFolderTreeView" in FeedSubscriptions.mMainWin) {
+ let favicon = FeedSubscriptions.mMainWin.gFolderTreeView
+ .getFolderCacheProperty(item.folder, "favicon");
+ if (favicon != null)
+ return item.favicon = favicon;
+ }
+
+ let callback = (iconUrl => {
+ item.favicon = iconUrl;
+ if (item.folder)
+ {
+ for (let child of item.children)
+ if (!child.container)
+ {
+ child.favicon = iconUrl;
+ break;
+ }
+ }
+
+ this.selection.tree.invalidateRow(aRow);
+ });
+
+ // A closed non server folder.
+ if (item.folder)
+ {
+ for (let child of item.children)
+ {
+ if (!child.container) {
+ if (child.favicon != null)
+ return child.favicon;
+
+ setTimeout(() => {
+ FeedUtils.getFavicon(child.parentFolder, child.url, null,
+ window, callback);
+ }, 0);
+ break;
+ }
+ }
+ }
+ else
+ {
+ // A feed.
+ setTimeout(() => {
+ FeedUtils.getFavicon(item.parentFolder, item.url, null,
+ window, callback);
+ }, 0);
+ }
+
+ // Store empty string to return default while favicons are retrieved.
+ return item.favicon = "";
+ },
+
+ canDrop: function (aRow, aOrientation)
+ {
+ let dropResult = this.extractDragData(aRow);
+ return aOrientation == Ci.nsITreeView.DROP_ON && dropResult.canDrop &&
+ (dropResult.dropUrl || dropResult.dropOnIndex != this.kRowIndexUndefined);
+ },
+
+ drop: function (aRow, aOrientation)
+ {
+ let win = FeedSubscriptions;
+ let results = this.extractDragData(aRow);
+ if (!results.canDrop)
+ return;
+
+ // Preselect the drop folder.
+ this.selection.select(aRow);
+
+ if (results.dropUrl)
+ {
+ // Don't freeze the app that initiated the drop just because we are
+ // in a loop waiting for the user to dimisss the add feed dialog.
+ setTimeout(function() {
+ win.addFeed(results.dropUrl, null, true, null, win.kSubscribeMode);
+ }, 0);
+ let folderItem = this.getItemAtIndex(aRow);
+ FeedUtils.log.debug("drop: folder, url - " +
+ folderItem.folder.name + ", " + results.dropUrl);
+ }
+ else if (results.dropOnIndex != this.kRowIndexUndefined)
+ {
+ win.moveCopyFeed(results.dropOnIndex, aRow, results.dropEffect);
+ }
+ },
+
+ // Helper function for drag and drop.
+ extractDragData: function(aRow)
+ {
+ let dt = this._currentDataTransfer;
+ let dragDataResults = { canDrop: false,
+ dropUrl: null,
+ dropOnIndex: this.kRowIndexUndefined,
+ dropEffect: dt.dropEffect };
+
+ if (dt.getData("text/x-moz-feed-index"))
+ {
+ // Dragging a feed in the tree.
+ if (this.selection)
+ {
+ dragDataResults.dropOnIndex = this.selection.currentIndex;
+
+ let curItem = this.getItemAtIndex(this.selection.currentIndex);
+ let newItem = this.getItemAtIndex(aRow);
+ let curServer = curItem && curItem.parentFolder ?
+ curItem.parentFolder.server : null;
+ let newServer = newItem && newItem.folder ?
+ newItem.folder.server : null;
+
+ // No copying within the same account and no moving to the account
+ // folder in the same account.
+ if (!(curServer == newServer &&
+ (dragDataResults.dropEffect == "copy" ||
+ newItem.folder == curItem.parentFolder ||
+ newItem.folder.isServer)))
+ dragDataResults.canDrop = true;
+ }
+ }
+ else
+ {
+ // Try to get a feed url.
+ let validUri = FeedUtils.getFeedUriFromDataTransfer(dt);
+
+ if (validUri)
+ {
+ dragDataResults.canDrop = true;
+ dragDataResults.dropUrl = validUri.spec;
+ }
+ }
+
+ return dragDataResults;
+ },
+
+ getParentIndex: function (aRow)
+ {
+ let item = this.getItemAtIndex(aRow);
+
+ if (item)
+ {
+ for (let index = aRow; index >= 0; index--)
+ if (FeedSubscriptions.mFeedContainers[index].level < item.level)
+ return index;
+ }
+
+ return this.kRowIndexUndefined;
+ },
+
+ isIndexChildOfParentIndex: function (aRow, aChildRow)
+ {
+ // For visible tree rows, not if items are children of closed folders.
+ let item = this.getItemAtIndex(aRow);
+ if (!item || aChildRow <= aRow)
+ return false;
+
+ let targetLevel = this.getItemAtIndex(aRow).level;
+ let rows = FeedSubscriptions.mFeedContainers;
+
+ for (let i = aRow + 1; i < rows.length; i++) {
+ if (this.getItemAtIndex(i).level <= targetLevel)
+ break;
+ if (aChildRow == i)
+ return true;
+ }
+
+ return false;
+ },
+
+ hasNextSibling: function(aRow, aAfterIndex) {
+ let targetLevel = this.getItemAtIndex(aRow).level;
+ let rows = FeedSubscriptions.mFeedContainers;
+ for (let i = aAfterIndex + 1; i < rows.length; i++) {
+ if (this.getItemAtIndex(i).level == targetLevel)
+ return true;
+ if (this.getItemAtIndex(i).level < targetLevel)
+ return false;
+ }
+
+ return false;
+ },
+
+ hasPreviousSibling: function (aRow)
+ {
+ let item = this.getItemAtIndex(aRow);
+ if (item && aRow)
+ return this.getItemAtIndex(aRow - 1).level == item.level;
+ else
+ return false;
+ },
+
+ getLevel: function (aRow)
+ {
+ let item = this.getItemAtIndex(aRow);
+ if (!item)
+ return 0;
+
+ return item.level;
+ },
+
+ toggleOpenState: function (aRow)
+ {
+ let item = this.getItemAtIndex(aRow);
+ if (!item)
+ return;
+
+ // Save off the current selection item.
+ let seln = this.selection;
+ let currentSelectionIndex = seln.currentIndex;
+
+ let rowsChanged = this.toggle(aRow)
+
+ // Now restore selection, ensuring selection is maintained on toggles.
+ if (currentSelectionIndex > aRow)
+ seln.currentIndex = currentSelectionIndex + rowsChanged;
+ else
+ seln.select(currentSelectionIndex);
+
+ seln.selectEventsSuppressed = false;
+ },
+
+ toggle: function (aRow)
+ {
+ // Collapse the row, or build sub rows based on open states in the map.
+ let item = this.getItemAtIndex(aRow);
+ if (!item)
+ return null;
+
+ let rows = FeedSubscriptions.mFeedContainers;
+ let rowCount = 0;
+ let multiplier;
+
+ function addDescendants(aItem)
+ {
+ for (let i = 0; i < aItem.children.length; i++)
+ {
+ rowCount++;
+ let child = aItem.children[i];
+ rows.splice(aRow + rowCount, 0, child);
+ if (child.open)
+ addDescendants(child);
+ }
+ }
+
+ if (item.open)
+ {
+ // Close the container. Add up all subfolders and their descendants
+ // who may be open.
+ multiplier = -1;
+ let nextRow = aRow + 1;
+ let nextItem = rows[nextRow];
+ while (nextItem && nextItem.level > item.level)
+ {
+ rowCount++;
+ nextItem = rows[++nextRow];
+ }
+
+ rows.splice(aRow + 1, rowCount);
+ }
+ else
+ {
+ // Open the container. Restore the open state of all subfolder and
+ // their descendants.
+ multiplier = 1;
+ addDescendants(item);
+ }
+
+ let delta = multiplier * rowCount;
+ this.mRowCount += delta;
+
+ item.open = !item.open;
+ // Suppress the select event caused by rowCountChanged.
+ this.selection.selectEventsSuppressed = true;
+ // Add or remove the children from our view.
+ this.treeBox.rowCountChanged(aRow, delta);
+ return delta;
+ }
+ },
+
+ makeFolderObject: function (aFolder, aCurrentLevel)
+ {
+ let defaultQuickMode = aFolder.server.getBoolValue("quickMode");
+ let optionsAcct = aFolder.isServer ? FeedUtils.getOptionsAcct(aFolder.server) :
+ null;
+ let open = !aFolder.isServer &&
+ aFolder.server == this.mRSSServer &&
+ this.mActionMode == this.kImportingOPML ? true : false
+ let folderObject = { children : [],
+ folder : aFolder,
+ name : aFolder.prettyName,
+ level : aCurrentLevel,
+ url : aFolder.URI,
+ quickMode: defaultQuickMode,
+ options : optionsAcct,
+ open : open,
+ container: true,
+ favicon : null };
+
+ // If a feed has any sub folders, add them to the list of children.
+ let folderEnumerator = aFolder.subFolders;
+
+ while (folderEnumerator.hasMoreElements())
+ {
+ let folder = folderEnumerator.getNext();
+ if ((folder instanceof Ci.nsIMsgFolder) &&
+ !folder.getFlag(Ci.nsMsgFolderFlags.Trash) &&
+ !folder.getFlag(Ci.nsMsgFolderFlags.Virtual))
+ {
+ folderObject.children
+ .push(this.makeFolderObject(folder, aCurrentLevel + 1));
+ }
+ }
+
+ let feeds = this.getFeedsInFolder(aFolder);
+ for (let feed of feeds)
+ {
+ // Now add any feed urls for the folder.
+ folderObject.children.push(this.makeFeedObject(feed,
+ aFolder,
+ aCurrentLevel + 1));
+ }
+
+ // Finally, set the folder's quickMode based on the its first feed's
+ // quickMode, since that is how the view determines summary mode, and now
+ // quickMode is updated to be the same for all feeds in a folder.
+ if (feeds && feeds[0])
+ folderObject.quickMode = feeds[0].quickMode;
+
+ folderObject.children = this.folderItemSorter(folderObject.children);
+
+ return folderObject;
+ },
+
+ folderItemSorter: function (aArray)
+ {
+ return aArray.sort(function(a, b) { return a.name.toLowerCase() >
+ b.name.toLowerCase() }).
+ sort(function(a, b) { return a.container < b.container });
+ },
+
+ getFeedsInFolder: function (aFolder)
+ {
+ let feeds = new Array();
+ let feedUrlArray = FeedUtils.getFeedUrlsInFolder(aFolder);
+ if (!feedUrlArray)
+ // No feedUrls in this folder.
+ return feeds;
+
+ for (let url of feedUrlArray)
+ {
+ let feedResource = FeedUtils.rdf.GetResource(url);
+ let feed = new Feed(feedResource, aFolder.server);
+ feeds.push(feed);
+ }
+
+ return feeds;
+ },
+
+ makeFeedObject: function (aFeed, aFolder, aLevel)
+ {
+ // Look inside the data source for the feed properties.
+ let feed = { children : [],
+ parentFolder: aFolder,
+ name : aFeed.title || aFeed.description || aFeed.url,
+ url : aFeed.url,
+ quickMode : aFeed.quickMode,
+ options : aFeed.options || FeedUtils.optionsTemplate,
+ level : aLevel,
+ open : false,
+ container : false,
+ favicon : null };
+ return feed;
+ },
+
+ loadSubscriptions: function ()
+ {
+ // Put together an array of folders. Each feed account level folder is
+ // included as the root.
+ let numFolders = 0;
+ let feedContainers = [];
+ // Get all the feed account folders.
+ let feedRootFolders = FeedUtils.getAllRssServerRootFolders();
+
+ feedRootFolders.forEach(function(rootFolder) {
+ feedContainers.push(this.makeFolderObject(rootFolder, 0));
+ numFolders++;
+ }, this);
+
+ this.mFeedContainers = feedContainers;
+ this.mView.mRowCount = numFolders;
+
+ FeedSubscriptions.mTree.focus();
+ },
+
+ /**
+ * Find the folder in the tree. The search may be limited to subfolders of
+ * a known folder, or expanded to include the entire tree. This function is
+ * also used to insert/remove folders without rebuilding the tree view cache
+ * (to avoid position/toggle state loss).
+ *
+ * @param aFolder nsIMsgFolder - the folder to find.
+ * @param [aParams] object - params object, containing:
+ *
+ * [parentIndex] int - index of folder to start the search; if
+ * null (default), the index of the folder's
+ * rootFolder will be used.
+ * [select] boolean - if true (default) the folder's ancestors
+ * will be opened and the folder selected.
+ * [open] boolean - if true (default) the folder is opened.
+ * [remove] boolean - delete the item from tree row cache if true,
+ * false (default) otherwise.
+ * [newFolder] nsIMsgFolder - if not null (default) the new folder,
+ * for add or rename.
+ *
+ * @return bool found - true if found, false if not.
+ */
+ selectFolder: function(aFolder, aParms)
+ {
+ let folderURI = aFolder.URI;
+ let parentIndex = aParms && ("parentIndex" in aParms) ? aParms.parentIndex : null;
+ let selectIt = aParms && ("select" in aParms) ? aParms.select : true;
+ let openIt = aParms && ("open" in aParms) ? aParms.open : true;
+ let removeIt = aParms && ("remove" in aParms) ? aParms.remove : false;
+ let newFolder = aParms && ("newFolder" in aParms) ? aParms.newFolder : null;
+ let startIndex, startItem;
+ let found = false;
+
+ let firstVisRow, curFirstVisRow, curLastVisRow;
+ if (this.mView.treeBox)
+ firstVisRow = this.mView.treeBox.getFirstVisibleRow();
+
+ if (parentIndex != null)
+ {
+ // Use the parentIndex if given.
+ startIndex = parentIndex;
+ if (aFolder.isServer)
+ // Fake item for account root folder.
+ startItem = { name: "AccountRoot",
+ children: [this.mView.getItemAtIndex(startIndex)],
+ container: true, open: false, url: null, level: -1};
+ else
+ startItem = this.mView.getItemAtIndex(startIndex);
+ }
+ else
+ {
+ // Get the folder's root parent index.
+ let index = 0;
+ for (index; index < this.mView.rowCount; index++)
+ {
+ let item = this.mView.getItemAtIndex(index);
+ if (item.url == aFolder.server.rootFolder.URI)
+ break;
+ }
+ startIndex = index;
+ if (aFolder.isServer)
+ // Fake item for account root folder.
+ startItem = { name: "AccountRoot",
+ children: [this.mView.getItemAtIndex(startIndex)],
+ container: true, open: false, url: null, level: -1};
+ else
+ startItem = this.mView.getItemAtIndex(startIndex);
+ }
+
+ function containsFolder(aItem)
+ {
+ // Search for the folder. If it's found, set the open state on all
+ // ancestor folders. A toggle() rebuilds the view rows to match the map.
+ if (aItem.url == folderURI)
+ return found = true;
+
+ for (let i = 0; i < aItem.children.length; i++) {
+ if (aItem.children[i].container && containsFolder(aItem.children[i]))
+ {
+ if (removeIt && aItem.children[i].url == folderURI)
+ {
+ // Get all occurences in the tree cache arrays.
+ FeedUtils.log.debug("selectFolder: delete in cache, " +
+ "parent:children:item:index - "+
+ aItem.name + ":" + aItem.children.length + ":" +
+ aItem.children[i].name + ":" + i);
+ aItem.children.splice(i, 1);
+ FeedUtils.log.debug("selectFolder: deleted in cache, " +
+ "parent:children - " +
+ aItem.name + ":" + aItem.children.length);
+ removeIt = false;
+ return true;
+ }
+ if (newFolder)
+ {
+ let newItem = FeedSubscriptions.makeFolderObject(newFolder,
+ aItem.level + 1);
+ newItem.open = aItem.children[i].open;
+ if (newFolder.isServer)
+ FeedSubscriptions.mFeedContainers[startIndex] = newItem;
+ else
+ {
+ aItem.children[i] = newItem;
+ aItem.children = FeedSubscriptions.folderItemSorter(aItem.children);
+ }
+ FeedUtils.log.trace("selectFolder: parentName:newFolderName:newFolderItem - " +
+ aItem.name + ":" + newItem.name + ":" + newItem.toSource());
+ newFolder = null;
+ return true;
+ }
+ if (!found)
+ {
+ // For the folder to find.
+ found = true;
+ aItem.children[i].open = openIt;
+ }
+ else
+ {
+ // For ancestor folders.
+ if (selectIt || openIt)
+ aItem.children[i].open = true;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (startItem)
+ {
+ // Find a folder with a specific parent.
+ containsFolder(startItem);
+ if (!found)
+ return false;
+
+ if (!selectIt)
+ return true;
+
+ if (startItem.open)
+ this.mView.toggle(startIndex);
+ this.mView.toggleOpenState(startIndex);
+ }
+
+ for (let index = 0; index < this.mView.rowCount && selectIt; index++)
+ {
+ // The desired folder is now in the view.
+ let item = this.mView.getItemAtIndex(index);
+ if (!item.container)
+ continue;
+ if (item.url == folderURI)
+ {
+ if (item.children.length &&
+ ((!item.open && openIt) || (item.open && !openIt)))
+ this.mView.toggleOpenState(index);
+ this.mView.selection.select(index);
+ found = true;
+ break;
+ }
+ }
+
+ // Ensure tree position does not jump unnecessarily.
+ curFirstVisRow = this.mView.treeBox.getFirstVisibleRow();
+ curLastVisRow = this.mView.treeBox.getLastVisibleRow();
+ if (firstVisRow >= 0 &&
+ this.mView.rowCount - curLastVisRow > firstVisRow - curFirstVisRow)
+ this.mView.treeBox.scrollToRow(firstVisRow);
+ else
+ this.mView.treeBox.ensureRowIsVisible(this.mView.rowCount - 1);
+
+ FeedUtils.log.debug("selectFolder: curIndex:firstVisRow:" +
+ "curFirstVisRow:curLastVisRow:rowCount - " +
+ this.mView.selection.currentIndex + ":" +
+ firstVisRow + ":" +
+ curFirstVisRow + ":" + curLastVisRow + ":" + this.mView.rowCount);
+
+ return found;
+ },
+
+ /**
+ * Find the feed in the tree. The search first gets the feed's folder,
+ * then selects the child feed.
+ *
+ * @param aFeed {Feed object} - the feed to find.
+ * @param [aParentIndex] integer - index to start the folder search.
+ *
+ * @return found bool - true if found, false if not.
+ */
+ selectFeed: function(aFeed, aParentIndex)
+ {
+ let folder = aFeed.folder;
+ let found = false;
+
+ if (aFeed.folder.isServer) {
+ // If passed the root folder, the caller wants to get the feed's folder
+ // from the db (for cases of an ancestor folder rename/move).
+ let itemResource = FeedUtils.rdf.GetResource(aFeed.url);
+ let ds = FeedUtils.getSubscriptionsDS(aFeed.folder.server);
+ folder = ds.GetTarget(itemResource, FeedUtils.FZ_DESTFOLDER, true);
+ }
+
+ if (this.selectFolder(folder, { parentIndex: aParentIndex }))
+ {
+ let seln = this.mView.selection;
+ let item = this.mView.currentItem;
+ if (item) {
+ for (let i = seln.currentIndex + 1; i < this.mView.rowCount; i++) {
+ if (this.mView.getItemAtIndex(i).url == aFeed.url) {
+ this.mView.selection.select(i);
+ this.mView.treeBox.ensureRowIsVisible(i);
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+
+ return found;
+ },
+
+ updateFeedData: function (aItem)
+ {
+ if (!aItem)
+ return;
+
+ let nameValue = document.getElementById("nameValue");
+ let locationValue = document.getElementById("locationValue");
+ let locationValidate = document.getElementById("locationValidate");
+ let selectFolder = document.getElementById("selectFolder");
+ let selectFolderValue = document.getElementById("selectFolderValue");
+ let isServer = aItem.folder && aItem.folder.isServer;
+ let isFolder = aItem.folder && !aItem.folder.isServer;
+ let isFeed = !aItem.container;
+ let server, displayFolder;
+
+ if (isFeed)
+ {
+ // A feed item. Set the feed location and title info.
+ nameValue.value = aItem.name;
+ locationValue.value = aItem.url;
+ locationValidate.removeAttribute("collapsed");
+
+ // Root the location picker to the news & blogs server.
+ server = aItem.parentFolder.server;
+ displayFolder = aItem.parentFolder;
+ }
+ else
+ {
+ // A folder/container item.
+ nameValue.value = "";
+ nameValue.disabled = true;
+ locationValue.value = "";
+ locationValidate.setAttribute("collapsed", true);
+
+ server = aItem.folder.server;
+ displayFolder = aItem.folder;
+ }
+
+ // Common to both folder and feed items.
+ nameValue.disabled = aItem.container;
+ this.setFolderPicker(displayFolder, isFeed);
+
+ // Set quick mode value.
+ document.getElementById("quickMode").checked = aItem.quickMode;
+
+ // Autotag items.
+ let autotagEnable = document.getElementById("autotagEnable");
+ let autotagUsePrefix = document.getElementById("autotagUsePrefix");
+ let autotagPrefix = document.getElementById("autotagPrefix");
+ let categoryPrefsAcct = FeedUtils.getOptionsAcct(server).category;
+ if (isServer)
+ aItem.options = FeedUtils.getOptionsAcct(server);
+ let categoryPrefs = aItem.options ? aItem.options.category : null;
+
+ autotagEnable.checked = categoryPrefs && categoryPrefs.enabled;
+ autotagUsePrefix.checked = categoryPrefs && categoryPrefs.prefixEnabled;
+ autotagUsePrefix.disabled = !autotagEnable.checked;
+ autotagPrefix.disabled = autotagUsePrefix.disabled || !autotagUsePrefix.checked;
+ autotagPrefix.value = categoryPrefs && categoryPrefs.prefix ?
+ categoryPrefs.prefix : "";
+ },
+
+ setFolderPicker: function(aFolder, aIsFeed)
+ {
+ let editFeed = document.getElementById("editFeed");
+ let folderPrettyPath = FeedUtils.getFolderPrettyPath(aFolder);
+ if (!folderPrettyPath)
+ return editFeed.disabled = true;
+
+ let selectFolder = document.getElementById("selectFolder");
+ let selectFolderPopup = document.getElementById("selectFolderPopup");
+ let selectFolderValue = document.getElementById("selectFolderValue");
+
+ selectFolder.setAttribute("hidden", !aIsFeed);
+ selectFolder._folder = aFolder;
+ selectFolderValue.setAttribute("hidden", aIsFeed);
+ selectFolderValue.setAttribute("showfilepath", false);
+
+ if (aIsFeed)
+ {
+ selectFolderPopup._ensureInitialized();
+ selectFolderPopup.selectFolder(aFolder);
+ selectFolder.setAttribute("label", folderPrettyPath);
+ selectFolder.setAttribute("uri", aFolder.URI);
+ }
+ else
+ {
+ selectFolderValue.value = folderPrettyPath;
+ selectFolderValue.setAttribute("prettypath", folderPrettyPath);
+ selectFolderValue.setAttribute("filepath", aFolder.filePath.path);
+ }
+
+ return editFeed.disabled = false;
+ },
+
+ onClickSelectFolderValue: function(aEvent)
+ {
+ let target = aEvent.target;
+ if ((("button" in aEvent) &&
+ (aEvent.button != 0 ||
+ aEvent.originalTarget.localName != "div" ||
+ target.selectionStart != target.selectionEnd)) ||
+ (aEvent.keyCode && aEvent.keyCode != aEvent.DOM_VK_RETURN))
+ return;
+
+ // Toggle between showing prettyPath and absolute filePath.
+ if (target.getAttribute("showfilepath") == "true")
+ {
+ target.setAttribute("showfilepath", false);
+ target.value = target.getAttribute("prettypath");
+ }
+ else
+ {
+ target.setAttribute("showfilepath", true);
+ target.value = target.getAttribute("filepath");
+ }
+ },
+
+ setNewFolder: function(aEvent)
+ {
+ aEvent.stopPropagation();
+ this.setFolderPicker(aEvent.target._folder, true);
+ this.editFeed();
+ },
+
+ setSummary: function(aChecked)
+ {
+ let item = this.mView.currentItem;
+ if (!item || !item.folder)
+ // Not a folder.
+ return;
+
+ if (item.folder.isServer)
+ {
+ if (document.getElementById("locationValue").value)
+ // Intent is to add a feed/folder to the account, so return.
+ return;
+
+ // An account folder. If it changes, all non feed containing subfolders
+ // need to be updated with the new default.
+ item.folder.server.setBoolValue("quickMode", aChecked);
+ this.FolderListener.folderAdded(item.folder);
+ }
+ else if (!FeedUtils.getFeedUrlsInFolder(item.folder))
+ // Not a folder with feeds.
+ return;
+ else
+ {
+ let feedsInFolder = this.getFeedsInFolder(item.folder);
+ // Update the feeds database, for each feed in the folder.
+ feedsInFolder.forEach(function(feed) { feed.quickMode = aChecked; });
+ // Update the folder's feeds properties in the tree map.
+ item.children.forEach(function(feed) { feed.quickMode = aChecked; });
+ let ds = FeedUtils.getSubscriptionsDS(item.folder.server);
+ ds.Flush();
+ }
+
+ // Update the folder in the tree map.
+ item.quickMode = aChecked;
+ let message = FeedUtils.strings.GetStringFromName("subscribe-feedUpdated");
+ this.updateStatusItem("statusText", message);
+ },
+
+ setCategoryPrefs: function(aNode)
+ {
+ let item = this.mView.currentItem;
+ if (!item)
+ return;
+
+ let isServer = item.folder && item.folder.isServer;
+ let isFolder = item.folder && !item.folder.isServer;
+ let autotagEnable = document.getElementById("autotagEnable");
+ let autotagUsePrefix = document.getElementById("autotagUsePrefix");
+ let autotagPrefix = document.getElementById("autotagPrefix");
+ if (isFolder || (isServer && document.getElementById("locationValue").value))
+ {
+ // Intend to subscribe a feed to a folder, a value must be in the url
+ // field. Update states for addFeed() and return.
+ autotagUsePrefix.disabled = !autotagEnable.checked;
+ autotagPrefix.disabled = autotagUsePrefix.disabled || !autotagUsePrefix.checked;
+ return;
+ }
+
+ switch (aNode.id) {
+ case "autotagEnable":
+ item.options.category.enabled = aNode.checked;
+ break;
+ case "autotagUsePrefix":
+ item.options.category.prefixEnabled = aNode.checked;
+ item.options.category.prefix = autotagPrefix.value;
+ break;
+ }
+
+ if (isServer)
+ {
+ FeedUtils.setOptionsAcct(item.folder.server, item.options)
+ }
+ else
+ {
+ let feedResource = FeedUtils.rdf.GetResource(item.url);
+ let feed = new Feed(feedResource, item.parentFolder.server);
+ feed.options = item.options;
+ let ds = FeedUtils.getSubscriptionsDS(item.parentFolder.server);
+ ds.Flush();
+ }
+
+ this.updateFeedData(item);
+ let message = FeedUtils.strings.GetStringFromName("subscribe-feedUpdated");
+ this.updateStatusItem("statusText", message);
+ },
+
+ onKeyPress: function(aEvent)
+ {
+ if (aEvent.keyCode == aEvent.DOM_VK_DELETE &&
+ aEvent.target.id == "rssSubscriptionsList")
+ this.removeFeed(true);
+
+ this.clearStatusInfo();
+ },
+
+ onSelect: function ()
+ {
+ let item = this.mView.currentItem;
+ this.updateFeedData(item);
+ this.setSummaryFocus();
+ this.updateButtons(item);
+ },
+
+ updateButtons: function (aSelectedItem)
+ {
+ let item = aSelectedItem;
+ let isServer = item && item.folder && item.folder.isServer;
+ let disable = !item || !item.container || isServer ||
+ this.mActionMode == this.kImportingOPML;
+ document.getElementById("addFeed").disabled = disable;
+ disable = !item || (item.container && !isServer) ||
+ this.mActionMode == this.kImportingOPML;
+ document.getElementById("editFeed").disabled = disable;
+ disable = !item || item.container ||
+ this.mActionMode == this.kImportingOPML;
+ document.getElementById("removeFeed").disabled = disable;
+ disable = !item || !isServer ||
+ this.mActionMode == this.kImportingOPML;
+ document.getElementById("importOPML").disabled = disable;
+ document.getElementById("exportOPML").disabled = disable;
+ },
+
+ onMouseDown: function (aEvent)
+ {
+ if (aEvent.button != 0 ||
+ aEvent.target.id == "validationText" ||
+ aEvent.target.id == "addCertException")
+ return;
+
+ this.clearStatusInfo();
+ },
+
+ setSummaryFocus: function ()
+ {
+ let item = this.mView.currentItem;
+ if (!item)
+ return;
+
+ let locationValue = document.getElementById("locationValue");
+ let quickMode = document.getElementById("quickMode");
+ let autotagEnable = document.getElementById("autotagEnable");
+ let autotagUsePrefix = document.getElementById("autotagUsePrefix");
+ let autotagPrefix = document.getElementById("autotagPrefix");
+ let isServer = item.folder && item.folder.isServer;
+ let isFolder = item.folder && !item.folder.isServer;
+ let isFeed = !item.container;
+
+ // Enable summary/autotag by default.
+ quickMode.disabled = autotagEnable.disabled = false;
+ autotagUsePrefix.disabled = !autotagEnable.checked;
+ autotagPrefix.disabled = autotagUsePrefix.disabled || !autotagUsePrefix.checked;
+
+ if (isServer)
+ {
+ let disable = locationValue.hasAttribute("focused") || locationValue.value;
+ document.getElementById("addFeed").disabled = !disable;
+ document.getElementById("editFeed").disabled = disable;
+
+ }
+ else if (isFolder)
+ {
+ if (!locationValue.hasAttribute("focused") && !locationValue.value)
+ {
+ // Enabled for a folder with feeds. Autotag disabled unless intent is
+ // to add a feed.
+ quickMode.disabled = !FeedUtils.getFeedUrlsInFolder(item.folder);
+ autotagEnable.disabled = autotagUsePrefix.disabled =
+ autotagPrefix.disabled = true;
+ }
+ }
+ else
+ {
+ // Summary is per folder.
+ quickMode.disabled = true;
+ }
+ },
+
+ removeFeed: function (aPrompt)
+ {
+ let seln = this.mView.selection;
+ if (seln.count != 1)
+ return;
+
+ let itemToRemove = this.mView.getItemAtIndex(seln.currentIndex);
+
+ if (!itemToRemove || itemToRemove.container)
+ return;
+
+ if (aPrompt)
+ {
+ // Confirm unsubscribe prompt.
+ let pTitle = FeedUtils.strings.GetStringFromName(
+ "subscribe-confirmFeedDeletionTitle");
+ let pMessage = FeedUtils.strings.formatStringFromName(
+ "subscribe-confirmFeedDeletion", [itemToRemove.name], 1);
+ if (Services.prompt.confirmEx(window, pTitle, pMessage,
+ Ci.nsIPromptService.STD_YES_NO_BUTTONS,
+ null, null, null, null, { }))
+ return;
+ }
+
+ FeedUtils.deleteFeed(FeedUtils.rdf.GetResource(itemToRemove.url),
+ itemToRemove.parentFolder.server,
+ itemToRemove.parentFolder);
+
+ // Now that we have removed the feed from the datasource, it is time to
+ // update our view layer. Update parent folder's quickMode if necessary
+ // and remove the child from its parent folder object.
+ let parentIndex = this.mView.getParentIndex(seln.currentIndex);
+ let parentItem = this.mView.getItemAtIndex(parentIndex);
+ this.updateFolderQuickModeInView(itemToRemove, parentItem, true);
+ this.mView.removeItemAtIndex(seln.currentIndex, false);
+ let message = FeedUtils.strings.GetStringFromName("subscribe-feedRemoved");
+ this.updateStatusItem("statusText", message);
+ },
+
+
+ /**
+ * This addFeed is used by 1) Add button, 1) Update button, 3) Drop of a
+ * feed url on a folder (which can be an add or move). If Update, the new
+ * url is added and the old removed; thus aParse is false and no new messages
+ * are downloaded, the feed is only validated and stored in the db. If dnd,
+ * the drop folder is selected and the url is prefilled, so proceed just as
+ * though the url were entered manually. This allows a user to see the dnd
+ * url better in case of errors.
+ *
+ * @param [aFeedLocation] string - the feed url; get the url from the
+ * input field if null.
+ * @param [aFolder] nsIMsgFolder - folder to subscribe, current selected
+ * folder if null.
+ * @param [aParse] boolean - if true (default) parse and download
+ * the feed's articles.
+ * @param [aParams] object - additional params.
+ * @param [aMode] integer - action mode (default is kSubscribeMode)
+ * of the add.
+ *
+ * @return success boolean - true if edit checks passed and an
+ * async download has been initiated.
+ */
+ addFeed: function(aFeedLocation, aFolder, aParse, aParams, aMode)
+ {
+ let message;
+ let parse = aParse == null ? true : aParse;
+ let mode = aMode == null ? this.kSubscribeMode : aMode;
+ let locationValue = document.getElementById("locationValue");
+ let quickMode = aParams && ("quickMode" in aParams) ?
+ aParams.quickMode : document.getElementById("quickMode").checked;
+ let name = aParams && ("name" in aParams) ?
+ aParams.name : document.getElementById("nameValue").value;
+ let options = aParams && ("options" in aParams) ?
+ aParams.options : null;
+
+ if (aFeedLocation)
+ locationValue.value = aFeedLocation;
+ let feedLocation = locationValue.value.trim();
+
+ if (!feedLocation)
+ {
+ message = locationValue.getAttribute("placeholder");
+ this.updateStatusItem("statusText", message);
+ return false;
+ }
+
+ if (!FeedUtils.isValidScheme(feedLocation))
+ {
+ message = FeedUtils.strings.GetStringFromName("subscribe-feedNotValid");
+ this.updateStatusItem("statusText", message);
+ return false;
+ }
+
+ let addFolder;
+ if (aFolder)
+ {
+ // For Update or if passed a folder.
+ if (aFolder instanceof Ci.nsIMsgFolder)
+ addFolder = aFolder;
+ }
+ else
+ {
+ // A folder must be selected for Add and Drop.
+ let index = this.mView.selection.currentIndex;
+ let item = this.mView.getItemAtIndex(index);
+ if (item && item.container)
+ addFolder = item.folder;
+ }
+
+ // Shouldn't happen. Or else not passed an nsIMsgFolder.
+ if (!addFolder)
+ return false;
+
+ // Before we go any further, make sure the user is not already subscribed
+ // to this feed.
+ if (FeedUtils.feedAlreadyExists(feedLocation, addFolder.server))
+ {
+ message = FeedUtils.strings.GetStringFromName(
+ "subscribe-feedAlreadySubscribed");
+ this.updateStatusItem("statusText", message);
+ return false;
+ }
+
+ if (!options)
+ {
+ // Not passed a param, get values from the ui.
+ options = FeedUtils.optionsTemplate;
+ options.category.enabled = document.getElementById("autotagEnable").checked;
+ options.category.prefixEnabled = document.getElementById("autotagUsePrefix").checked;
+ options.category.prefix = document.getElementById("autotagPrefix").value;
+ }
+
+ let folderURI = addFolder.isServer ? null : addFolder.URI;
+ let feedProperties = { feedName : name,
+ feedLocation : feedLocation,
+ folderURI : folderURI,
+ server : addFolder.server,
+ quickMode : quickMode,
+ options : options };
+
+ let feed = this.storeFeed(feedProperties);
+ if (!feed)
+ return false;
+
+ // Now validate and start downloading the feed.
+ message = FeedUtils.strings.GetStringFromName("subscribe-validating-feed");
+ this.updateStatusItem("statusText", message);
+ this.updateStatusItem("progressMeter", 0);
+ document.getElementById("addFeed").setAttribute("disabled", true);
+ this.mActionMode = mode;
+ feed.download(parse, this.mFeedDownloadCallback);
+ return true;
+ },
+
+ // Helper routine used by addFeed and importOPMLFile.
+ storeFeed: function(feedProperties)
+ {
+ let itemResource = FeedUtils.rdf.GetResource(feedProperties.feedLocation);
+ let feed = new Feed(itemResource, feedProperties.server);
+
+ // If the user specified a folder to add the feed to, then set it here.
+ if (feedProperties.folderURI)
+ {
+ let folderResource = FeedUtils.rdf.GetResource(feedProperties.folderURI);
+ if (folderResource)
+ {
+ let folder = folderResource.QueryInterface(Ci.nsIMsgFolder);
+ if (folder && !folder.isServer)
+ feed.folder = folder;
+ }
+ }
+
+ feed.title = feedProperties.feedName;
+ feed.quickMode = feedProperties.quickMode;
+ feed.options = feedProperties.options;
+ return feed;
+ },
+
+ updateAccount: function(aItem)
+ {
+ // Check to see if the categoryPrefs custom prefix string value changed.
+ let editAutotagPrefix = document.getElementById("autotagPrefix").value;
+ if (aItem.options.category.prefix != editAutotagPrefix)
+ {
+ aItem.options.category.prefix = editAutotagPrefix;
+ FeedUtils.setOptionsAcct(aItem.folder.server, aItem.options)
+ let message = FeedUtils.strings.GetStringFromName("subscribe-feedUpdated");
+ this.updateStatusItem("statusText", message);
+ }
+ },
+
+ editFeed: function()
+ {
+ let seln = this.mView.selection;
+ if (seln.count != 1)
+ return;
+
+ let itemToEdit = this.mView.getItemAtIndex(seln.currentIndex);
+ if (itemToEdit.folder && itemToEdit.folder.isServer)
+ {
+ this.updateAccount(itemToEdit)
+ return;
+ }
+
+ if (!itemToEdit || itemToEdit.container || !itemToEdit.parentFolder)
+ return;
+
+ let resource = FeedUtils.rdf.GetResource(itemToEdit.url);
+ let currentFolderServer = itemToEdit.parentFolder.server;
+ let ds = FeedUtils.getSubscriptionsDS(currentFolderServer);
+ let currentFolderURI = itemToEdit.parentFolder.URI;
+ let feed = new Feed(resource, currentFolderServer);
+ feed.folder = itemToEdit.parentFolder;
+
+ let editNameValue = document.getElementById("nameValue").value;
+ let editFeedLocation = document.getElementById("locationValue").value.trim();
+ let selectFolder = document.getElementById("selectFolder");
+ let editQuickMode = document.getElementById("quickMode").checked;
+ let editAutotagPrefix = document.getElementById("autotagPrefix").value;
+
+ if (feed.url != editFeedLocation)
+ {
+ // Updating a url. We need to add the new url and delete the old, to
+ // ensure everything is cleaned up correctly.
+ this.addFeed(null, itemToEdit.parentFolder, false, null, this.kUpdateMode)
+ return;
+ }
+
+ // Did the user change the folder URI for storing the feed?
+ let editFolderURI = selectFolder.getAttribute("uri");
+ if (currentFolderURI != editFolderURI)
+ {
+ // Make sure the new folderpicked folder is visible.
+ this.selectFolder(selectFolder._folder);
+ // Now go back to the feed item.
+ this.selectFeed(feed, null);
+ // We need to find the index of the new parent folder.
+ let newParentIndex = this.mView.kRowIndexUndefined;
+ for (let index = 0; index < this.mView.rowCount; index++)
+ {
+ let item = this.mView.getItemAtIndex(index);
+ if (item && item.container && item.url == editFolderURI)
+ {
+ newParentIndex = index;
+ break;
+ }
+ }
+
+ if (newParentIndex != this.mView.kRowIndexUndefined)
+ this.moveCopyFeed(seln.currentIndex, newParentIndex, "move");
+
+ return;
+ }
+
+ let updated = false;
+ let message = "";
+ // Disable the button until the update completes and we process the async
+ // verify response.
+ document.getElementById("editFeed").setAttribute("disabled", true);
+
+ // Check to see if the title value changed, no blank title allowed.
+ if (feed.title != editNameValue)
+ {
+ if (!editNameValue)
+ {
+ document.getElementById("nameValue").value = feed.title;
+ }
+ else
+ {
+ feed.title = editNameValue;
+ itemToEdit.name = editNameValue;
+ seln.tree.invalidateRow(seln.currentIndex);
+ updated = true;
+ }
+ }
+
+ // Check to see if the quickMode value changed.
+ if (feed.quickMode != editQuickMode)
+ {
+ feed.quickMode = editQuickMode;
+ itemToEdit.quickMode = editQuickMode;
+ updated = true;
+ }
+
+ // Check to see if the categoryPrefs custom prefix string value changed.
+ if (itemToEdit.options.category.prefix != editAutotagPrefix &&
+ itemToEdit.options.category.prefix != null &&
+ editAutotagPrefix != "")
+ {
+ itemToEdit.options.category.prefix = editAutotagPrefix;
+ feed.options = itemToEdit.options;
+ updated = true;
+ }
+
+ let verifyDelay = 0;
+ if (updated) {
+ ds.Flush();
+ message = FeedUtils.strings.GetStringFromName("subscribe-feedUpdated");
+ this.updateStatusItem("statusText", message);
+ verifyDelay = 1500;
+ }
+
+ // Now we want to verify if the stored feed url still works. If it
+ // doesn't, show the error. Delay a bit to leave Updated message visible.
+ message = FeedUtils.strings.GetStringFromName("subscribe-validating-feed");
+ this.mActionMode = this.kVerifyUrlMode;
+ setTimeout(() => {
+ this.updateStatusItem("statusText", message);
+ this.updateStatusItem("progressMeter", "?");
+ feed.download(false, this.mFeedDownloadCallback);
+ }, verifyDelay);
+ },
+
+/**
+ * Moves or copies a feed to another folder or account.
+ *
+ * @param int aOldFeedIndex - index in tree of target feed item.
+ * @param int aNewParentIndex - index in tree of target parent folder item.
+ * @param string aMoveCopy - either "move" or "copy".
+ */
+ moveCopyFeed: function(aOldFeedIndex, aNewParentIndex, aMoveCopy)
+ {
+ let moveFeed = aMoveCopy == "move";
+ let currentItem = this.mView.getItemAtIndex(aOldFeedIndex);
+ if (!currentItem ||
+ this.mView.getParentIndex(aOldFeedIndex) == aNewParentIndex)
+ // If the new parent is the same as the current parent, then do nothing.
+ return;
+
+ let currentParentIndex = this.mView.getParentIndex(aOldFeedIndex);
+ let currentParentItem = this.mView.getItemAtIndex(currentParentIndex);
+ let currentParentResource = FeedUtils.rdf.GetResource(currentParentItem.url);
+ let currentFolder = currentParentResource.QueryInterface(Ci.nsIMsgFolder);
+
+ let newParentItem = this.mView.getItemAtIndex(aNewParentIndex);
+ let newParentResource = FeedUtils.rdf.GetResource(newParentItem.url);
+ let newFolder = newParentResource.QueryInterface(Ci.nsIMsgFolder);
+
+ let ds = FeedUtils.getSubscriptionsDS(currentItem.parentFolder.server);
+ let resource = FeedUtils.rdf.GetResource(currentItem.url);
+
+ let accountMoveCopy = false;
+ if (currentFolder.rootFolder.URI == newFolder.rootFolder.URI)
+ {
+ // Moving within the same account/feeds db.
+ if (newFolder.isServer || !moveFeed)
+ // No moving to account folder if already in the account; can only move,
+ // not copy, to folder in the same account.
+ return;
+
+ // Unassert the older URI, add an assertion for the new parent URI.
+ ds.Change(resource, FeedUtils.FZ_DESTFOLDER,
+ currentParentResource, newParentResource);
+ ds.Flush();
+
+ // Update folderpane favicons.
+ FeedUtils.setFolderPaneProperty(currentFolder, "favicon", null, "row");
+ FeedUtils.setFolderPaneProperty(newFolder, "favicon", null, "row");
+ }
+ else
+ {
+ // Moving/copying to a new account. If dropping on the account folder,
+ // a new subfolder is created if necessary.
+ accountMoveCopy = true;
+ let mode = moveFeed ? this.kMoveMode : this.kCopyMode;
+ let params = {quickMode: currentItem.quickMode,
+ name: currentItem.name,
+ options: currentItem.options};
+ // Subscribe to the new folder first. If it already exists in the
+ // account or on error, return.
+ if (!this.addFeed(currentItem.url, newFolder, false, params, mode))
+ return;
+ // Unsubscribe the feed from the old folder, if add to the new folder
+ // is successfull, and doing a move.
+ if (moveFeed)
+ FeedUtils.deleteFeed(FeedUtils.rdf.GetResource(currentItem.url),
+ currentItem.parentFolder.server,
+ currentItem.parentFolder);
+ }
+
+ // Update local favicons.
+ currentParentItem.favicon = newParentItem.favicon = null;
+
+ // Finally, update our view layer. Update old parent folder's quickMode
+ // and remove the old row, if move. Otherwise no change to the view.
+ if (moveFeed)
+ {
+ this.updateFolderQuickModeInView(currentItem, currentParentItem, true);
+ this.mView.removeItemAtIndex(aOldFeedIndex, true);
+ if (aNewParentIndex > aOldFeedIndex)
+ aNewParentIndex--;
+ }
+
+ if (accountMoveCopy)
+ {
+ // If a cross account move/copy, download callback will update the view
+ // with the new location. Preselect folder/mode for callback.
+ this.selectFolder(newFolder, { parentIndex: aNewParentIndex });
+ return;
+ }
+
+ // Add the new row location to the view.
+ currentItem.level = newParentItem.level + 1;
+ currentItem.parentFolder = newFolder;
+ this.updateFolderQuickModeInView(currentItem, newParentItem, false);
+ newParentItem.children.push(currentItem);
+
+ if (newParentItem.open)
+ // Close the container, selecting the feed will rebuild the view rows.
+ this.mView.toggle(aNewParentIndex);
+
+ this.selectFeed({folder: newParentItem.folder, url: currentItem.url},
+ aNewParentIndex);
+
+ let message = FeedUtils.strings.GetStringFromName("subscribe-feedMoved");
+ this.updateStatusItem("statusText", message);
+ },
+
+ updateFolderQuickModeInView: function (aFeedItem, aParentItem, aRemove)
+ {
+ let feedItem = aFeedItem;
+ let parentItem = aParentItem;
+ let feedUrlArray = FeedUtils.getFeedUrlsInFolder(feedItem.parentFolder);
+ let feedsInFolder = feedUrlArray ? feedUrlArray.length : 0;
+
+ if (aRemove && feedsInFolder < 1)
+ // Removed only feed in folder; set quickMode to server default.
+ parentItem.quickMode = parentItem.folder.server.getBoolValue("quickMode");
+
+ if (!aRemove)
+ {
+ // Just added a feed to a folder. If there are already feeds in the
+ // folder, the feed must reflect the parent's quickMode. If it is the
+ // only feed, update the parent folder to the feed's quickMode.
+ if (feedsInFolder > 1)
+ {
+ let feedResource = FeedUtils.rdf.GetResource(feedItem.url);
+ let feed = new Feed(feedResource, feedItem.parentFolder.server);
+ feed.quickMode = parentItem.quickMode;
+ feedItem.quickMode = parentItem.quickMode;
+ }
+ else
+ parentItem.quickMode = feedItem.quickMode;
+ }
+ },
+
+ onDragStart: function (aEvent)
+ {
+ // Get the selected feed article (if there is one).
+ let seln = this.mView.selection;
+ if (seln.count != 1)
+ return;
+
+ // Only initiate a drag if the item is a feed (ignore folders/containers).
+ let item = this.mView.getItemAtIndex(seln.currentIndex);
+ if (!item || item.container)
+ return;
+
+ aEvent.dataTransfer.setData("text/x-moz-feed-index", seln.currentIndex);
+ aEvent.dataTransfer.effectAllowed = "copyMove";
+ },
+
+ onDragOver: function (aEvent)
+ {
+ this.mView._currentDataTransfer = aEvent.dataTransfer;
+ },
+
+ mFeedDownloadCallback:
+ {
+ mSubscribeMode: true,
+ downloaded: function(feed, aErrorCode)
+ {
+ // Offline check is done in the context of 3pane, return to the subscribe
+ // window once the modal prompt is dispatched.
+ window.focus();
+ // Feed is null if our attempt to parse the feed failed.
+ let message = "";
+ let win = FeedSubscriptions;
+ if (aErrorCode == FeedUtils.kNewsBlogSuccess ||
+ aErrorCode == FeedUtils.kNewsBlogNoNewItems)
+ {
+ win.updateStatusItem("progressMeter", 100);
+
+ if (win.mActionMode == win.kVerifyUrlMode) {
+ // Just checking for errors, if none bye. The (non error) code
+ // kNewsBlogNoNewItems can only happen in verify mode.
+ win.mActionMode = null;
+ win.clearStatusInfo();
+ message = FeedUtils.strings.GetStringFromName("subscribe-feedVerified");
+ win.updateStatusItem("statusText", message);
+ document.getElementById("editFeed").removeAttribute("disabled");
+ return;
+ }
+
+ // Add the feed to the databases.
+ FeedUtils.addFeed(feed);
+
+ // Now add the feed to our view. If adding, the current selection will
+ // be a folder; if updating it will be a feed. No need to rebuild the
+ // entire view, that is too jarring.
+ let curIndex = win.mView.selection.currentIndex;
+ let curItem = win.mView.getItemAtIndex(curIndex);
+ if (curItem)
+ {
+ let parentIndex, parentItem, newItem, level;
+ let rows = win.mFeedContainers;
+ if (curItem.container)
+ {
+ // Open the container, if it exists.
+ let folderExists = win.selectFolder(feed.folder,
+ { parentIndex: curIndex });
+ if (!folderExists)
+ {
+ // This means a new folder was created.
+ parentIndex = curIndex;
+ parentItem = curItem;
+ level = curItem.level + 1;
+ newItem = win.makeFolderObject(feed.folder, level);
+ }
+ else
+ {
+ // If a folder happens to exist which matches one that would
+ // have been created, the feed system reuses it. Get the
+ // current item again if reusing a previously unselected folder.
+ curIndex = win.mView.selection.currentIndex;
+ curItem = win.mView.getItemAtIndex(curIndex);
+ parentIndex = curIndex;
+ parentItem = curItem;
+ level = curItem.level + 1;
+ newItem = win.makeFeedObject(feed, feed.folder, level);
+ }
+ }
+ else
+ {
+ // Adding a feed.
+ parentIndex = win.mView.getParentIndex(curIndex);
+ parentItem = win.mView.getItemAtIndex(parentIndex);
+ level = curItem.level;
+ newItem = win.makeFeedObject(feed, feed.folder, level);
+ }
+
+ if (!newItem.container)
+ win.updateFolderQuickModeInView(newItem, parentItem, false);
+ parentItem.children.push(newItem);
+ parentItem.children = win.folderItemSorter(parentItem.children);
+ parentItem.favicon = null;
+
+ if (win.mActionMode == win.kSubscribeMode)
+ message = FeedUtils.strings.GetStringFromName(
+ "subscribe-feedAdded");
+ if (win.mActionMode == win.kUpdateMode)
+ {
+ win.removeFeed(false);
+ message = FeedUtils.strings.GetStringFromName(
+ "subscribe-feedUpdated");
+ }
+ if (win.mActionMode == win.kMoveMode)
+ message = FeedUtils.strings.GetStringFromName(
+ "subscribe-feedMoved");
+ if (win.mActionMode == win.kCopyMode)
+ message = FeedUtils.strings.GetStringFromName(
+ "subscribe-feedCopied");
+
+ win.selectFeed(feed, parentIndex);
+ }
+ }
+ else
+ {
+ // Non success. Remove intermediate traces from the feeds database.
+ // But only if we're not in verify mode.
+ if (win.mActionMode != win.kVerifyUrlMode &&
+ feed && feed.url && feed.server)
+ FeedUtils.deleteFeed(FeedUtils.rdf.GetResource(feed.url),
+ feed.server,
+ feed.server.rootFolder);
+
+ if (aErrorCode == FeedUtils.kNewsBlogInvalidFeed)
+ message = FeedUtils.strings.GetStringFromName(
+ "subscribe-feedNotValid");
+ if (aErrorCode == FeedUtils.kNewsBlogRequestFailure)
+ message = FeedUtils.strings.GetStringFromName(
+ "subscribe-networkError");
+ if (aErrorCode == FeedUtils.kNewsBlogFileError)
+ message = FeedUtils.strings.GetStringFromName(
+ "subscribe-errorOpeningFile");
+ if (aErrorCode == FeedUtils.kNewsBlogBadCertError) {
+ let host = Services.io.newURI(feed.url, null, null).host;
+ message = FeedUtils.strings.formatStringFromName(
+ "newsblog-badCertError", [host], 1);
+ }
+ if (aErrorCode == FeedUtils.kNewsBlogNoAuthError)
+ message = FeedUtils.strings.GetStringFromName(
+ "subscribe-noAuthError");
+
+ if (win.mActionMode != win.kUpdateMode &&
+ win.mActionMode != win.kVerifyUrlMode)
+ // Re-enable the add button if subscribe failed.
+ document.getElementById("addFeed").removeAttribute("disabled");
+ if (win.mActionMode == win.kVerifyUrlMode)
+ // Re-enable the update button if verify failed.
+ document.getElementById("editFeed").removeAttribute("disabled");
+ }
+
+ win.mActionMode = null;
+ win.clearStatusInfo();
+ let code = feed.url.startsWith("http") ? aErrorCode : null;
+ win.updateStatusItem("statusText", message, code);
+ },
+
+ // This gets called after the RSS parser finishes storing a feed item to
+ // disk. aCurrentFeedItems is an integer corresponding to how many feed
+ // items have been downloaded so far. aMaxFeedItems is an integer
+ // corresponding to the total number of feed items to download.
+ onFeedItemStored: function (feed, aCurrentFeedItems, aMaxFeedItems)
+ {
+ window.focus();
+ let message = FeedUtils.strings.formatStringFromName(
+ "subscribe-gettingFeedItems",
+ [aCurrentFeedItems, aMaxFeedItems], 2);
+ FeedSubscriptions.updateStatusItem("statusText", message);
+ this.onProgress(feed, aCurrentFeedItems, aMaxFeedItems);
+ },
+
+ onProgress: function(feed, aProgress, aProgressMax, aLengthComputable)
+ {
+ FeedSubscriptions.updateStatusItem("progressMeter",
+ (aProgress * 100) / aProgressMax);
+ }
+ },
+
+ // Status routines.
+ updateStatusItem: function(aID, aValue, aErrorCode)
+ {
+ let el = document.getElementById(aID);
+ if (el.getAttribute("collapsed"))
+ el.removeAttribute("collapsed");
+
+ if (aID == "progressMeter")
+ el.setAttribute("mode", aValue == "?" ? "undetermined" : "determined");
+
+ if (aID == "statusText")
+ el.textContent = aValue;
+ else
+ el.value = aValue;
+
+ el = document.getElementById("validationText");
+ if (aErrorCode == FeedUtils.kNewsBlogInvalidFeed)
+ el.removeAttribute("collapsed");
+ else
+ el.setAttribute("collapsed", true);
+
+ el = document.getElementById("addCertException");
+ if (aErrorCode == FeedUtils.kNewsBlogBadCertError)
+ el.removeAttribute("collapsed");
+ else
+ el.setAttribute("collapsed", true);
+ },
+
+ clearStatusInfo: function()
+ {
+ document.getElementById("statusText").textContent = "";
+ document.getElementById("progressMeter").collapsed = true;
+ document.getElementById("validationText").collapsed = true;
+ document.getElementById("addCertException").collapsed = true;
+ },
+
+ checkValidation: function(aEvent)
+ {
+ if (aEvent.button != 0)
+ return;
+
+ let validationSite = "http://validator.w3.org";
+ let validationQuery = "http://validator.w3.org/feed/check.cgi?url=";
+
+ if (this.mMainWin)
+ {
+ let tabmail = this.mMainWin.document.getElementById("tabmail");
+ if (tabmail)
+ {
+ let feedLocation = document.getElementById("locationValue").value;
+ let url = validationQuery + encodeURIComponent(feedLocation);
+
+ this.mMainWin.focus();
+ this.mMainWin.openContentTab(url, "tab", "^" + validationSite);
+ FeedUtils.log.debug("checkValidation: query url - " + url);
+ }
+ }
+ aEvent.stopPropagation();
+ },
+
+ addCertExceptionDialog: function()
+ {
+ let feedURL = document.getElementById("locationValue").value.trim();
+ let params = { exceptionAdded : false,
+ location: feedURL,
+ prefetchCert: true };
+ window.openDialog("chrome://pippki/content/exceptionDialog.xul",
+ "", "chrome,centerscreen,modal", params);
+ if (params.exceptionAdded)
+ this.clearStatusInfo();
+ },
+
+ // Listener for folder pane changes.
+ FolderListener: {
+ get feedWindow() {
+ let subscriptionsWindow =
+ Services.wm.getMostRecentWindow("Mail:News-BlogSubscriptions");
+ return subscriptionsWindow ? subscriptionsWindow.FeedSubscriptions : null;
+ },
+
+ get currentSelectedIndex() {
+ return this.feedWindow ? this.feedWindow.mView.selection.currentIndex : -1;
+ },
+
+ get currentSelectedItem() {
+ return this.feedWindow ? this.feedWindow.mView.currentItem : null;
+ },
+
+ folderAdded: function(aFolder)
+ {
+ if (aFolder.server.type != "rss" ||
+ FeedUtils.isInTrash(aFolder))
+ return;
+
+ let parentFolder = aFolder.isServer ? aFolder : aFolder.parent;
+ FeedUtils.log.debug("folderAdded: folder:parent - " + aFolder.name + ":" +
+ (parentFolder ? parentFolder.filePath.path : "(null)"));
+
+ if (!parentFolder || !this.feedWindow)
+ return;
+
+ let feedWindow = this.feedWindow;
+ let curSelIndex = this.currentSelectedIndex;
+ let curSelItem = this.currentSelectedItem;
+ let firstVisRow = feedWindow.mView.treeBox.getFirstVisibleRow();
+ let indexInView = feedWindow.mView.getItemInViewIndex(parentFolder);
+ let open = indexInView != null;
+
+ if (aFolder.isServer)
+ {
+ if (indexInView != null)
+ // Existing account root folder in the view.
+ open = feedWindow.mView.getItemAtIndex(indexInView).open;
+ else
+ {
+ // Add the account root folder to the view.
+ feedWindow.mFeedContainers.push(feedWindow.makeFolderObject(parentFolder, 0));
+ feedWindow.mView.mRowCount++;
+ feedWindow.mTree.view = feedWindow.mView;
+ feedWindow.mView.treeBox.scrollToRow(firstVisRow);
+ return;
+ }
+ }
+
+ // Rebuild the added folder's parent item in the tree row cache.
+ feedWindow.selectFolder(parentFolder, { select: false,
+ open: open,
+ newFolder: parentFolder });
+
+ if (indexInView == null || !curSelItem)
+ // Folder isn't in the tree view, no need to update the view.
+ return;
+
+ let parentIndex = feedWindow.mView.getParentIndex(indexInView);
+ if (parentIndex == feedWindow.mView.kRowIndexUndefined)
+ // Root folder is its own parent.
+ parentIndex = indexInView;
+ if (open)
+ {
+ // Close an open parent (or root) folder.
+ feedWindow.mView.toggle(parentIndex);
+ feedWindow.mView.toggleOpenState(parentIndex);
+ }
+ feedWindow.mView.treeBox.scrollToRow(firstVisRow);
+
+ if (curSelItem.container)
+ feedWindow.selectFolder(curSelItem.folder, { open: curSelItem.open });
+ else
+ feedWindow.selectFeed({ folder: curSelItem.parentFolder,
+ url: curSelItem.url }, parentIndex);
+ },
+
+ folderDeleted: function(aFolder)
+ {
+ if (aFolder.server.type != "rss" || FeedUtils.isInTrash(aFolder))
+ return;
+
+ FeedUtils.log.debug("folderDeleted: folder - " + aFolder.name);
+ if (!this.feedWindow)
+ return;
+
+ let feedWindow = this.feedWindow;
+ let curSelIndex = this.currentSelectedIndex;
+ let indexInView = feedWindow.mView.getItemInViewIndex(aFolder);
+ let open = indexInView != null;
+
+ // Delete the folder from the tree row cache.
+ feedWindow.selectFolder(aFolder, { select: false, open: false, remove: true });
+
+ if (!open || curSelIndex < 0)
+ // Folder isn't in the tree view, no need to update the view.
+ return;
+
+ let select =
+ indexInView == curSelIndex ||
+ feedWindow.mView.isIndexChildOfParentIndex(indexInView, curSelIndex);
+ feedWindow.mView.removeItemAtIndex(indexInView, !select);
+ },
+
+ folderRenamed: function(aOrigFolder, aNewFolder)
+ {
+ if (aNewFolder.server.type != "rss" || FeedUtils.isInTrash(aNewFolder))
+ return;
+
+ FeedUtils.log.debug("folderRenamed: old:new - " +
+ aOrigFolder.name + ":" + aNewFolder.name);
+ if (!this.feedWindow)
+ return;
+
+ let feedWindow = this.feedWindow;
+ let curSelIndex = this.currentSelectedIndex;
+ let curSelItem = this.currentSelectedItem;
+ let firstVisRow = feedWindow.mView.treeBox.getFirstVisibleRow();
+ let indexInView = feedWindow.mView.getItemInViewIndex(aOrigFolder);
+ let open = indexInView != null;
+
+ // Rebuild the renamed folder's item in the tree row cache.
+ feedWindow.selectFolder(aOrigFolder, { select: false,
+ open: open,
+ newFolder: aNewFolder });
+
+ if (!open || !curSelItem)
+ // Folder isn't in the tree view, no need to update the view.
+ return;
+
+ let select =
+ indexInView == curSelIndex ||
+ feedWindow.mView.isIndexChildOfParentIndex(indexInView, curSelIndex);
+ let parentIndex = feedWindow.mView.getParentIndex(indexInView);
+ if (parentIndex == feedWindow.mView.kRowIndexUndefined)
+ // Root folder is its own parent.
+ parentIndex = indexInView;
+ feedWindow.mView.toggle(parentIndex);
+ feedWindow.mView.toggleOpenState(parentIndex);
+ feedWindow.mView.treeBox.scrollToRow(firstVisRow);
+
+ if (curSelItem.container) {
+ if (curSelItem.folder == aOrigFolder)
+ feedWindow.selectFolder(aNewFolder, { open: curSelItem.open });
+ else if (select)
+ feedWindow.mView.selection.select(indexInView);
+ else
+ feedWindow.selectFolder(curSelItem.folder, { open: curSelItem.open });
+ }
+ else
+ feedWindow.selectFeed({ folder: curSelItem.parentFolder.rootFolder,
+ url: curSelItem.url }, parentIndex);
+ },
+
+ folderMoveCopyCompleted: function(aMove, aSrcFolder, aDestFolder)
+ {
+ if (aDestFolder.server.type != "rss")
+ return;
+
+ FeedUtils.log.debug("folderMoveCopyCompleted: move:src:dest - " +
+ aMove + ":" + aSrcFolder.name + ":" + aDestFolder.name);
+ if (!this.feedWindow)
+ return;
+
+ let feedWindow = this.feedWindow;
+ let curSelIndex = this.currentSelectedIndex;
+ let curSelItem = this.currentSelectedItem;
+ let firstVisRow = feedWindow.mView.treeBox.getFirstVisibleRow();
+ let indexInView = feedWindow.mView.getItemInViewIndex(aSrcFolder);
+ let destIndexInView = feedWindow.mView.getItemInViewIndex(aDestFolder);
+ let open = indexInView != null || destIndexInView != null;
+ let parentIndex = feedWindow.mView.getItemInViewIndex(aDestFolder.parent ||
+ aDestFolder);
+ let select =
+ indexInView == curSelIndex ||
+ feedWindow.mView.isIndexChildOfParentIndex(indexInView, curSelIndex);
+
+ if (aMove)
+ {
+ this.folderDeleted(aSrcFolder);
+ if (aDestFolder.getFlag(Ci.nsMsgFolderFlags.Trash))
+ return;
+ }
+
+ setTimeout(function() {
+ // State on disk needs to settle before a folder object can be rebuilt.
+ feedWindow.selectFolder(aDestFolder, { select: false,
+ open: open || select,
+ newFolder: aDestFolder });
+
+ if (!open || !curSelItem)
+ // Folder isn't in the tree view, no need to update the view.
+ return;
+
+ feedWindow.mView.toggle(parentIndex);
+ feedWindow.mView.toggleOpenState(parentIndex);
+ feedWindow.mView.treeBox.scrollToRow(firstVisRow);
+
+ if (curSelItem.container) {
+ if (curSelItem.folder == aSrcFolder || select)
+ feedWindow.selectFolder(aDestFolder, { open: true });
+ else
+ feedWindow.selectFolder(curSelItem.folder, { open: curSelItem.open });
+ }
+ else
+ feedWindow.selectFeed({ folder: curSelItem.parentFolder.rootFolder,
+ url: curSelItem.url }, null);
+ }, 50);
+ }
+ },
+
+ /* *************************************************************** */
+ /* OPML Functions */
+ /* *************************************************************** */
+
+ get brandShortName() {
+ let brandBundle = document.getElementById("bundle_brand");
+ return brandBundle ? brandBundle.getString("brandShortName") : "";
+ },
+
+/**
+ * Export feeds as opml file Save As filepicker function.
+ *
+ * @param bool aList - if true, exporting as list; if false (default)
+ * exporting feeds in folder structure - used for title.
+ * @return nsILocalFile or null.
+ */
+ opmlPickSaveAsFile: function(aList)
+ {
+ let accountName = this.mRSSServer.rootFolder.prettyName;
+ let fileName = FeedUtils.strings.formatStringFromName(
+ "subscribe-OPMLExportDefaultFileName",
+ [this.brandShortName, accountName], 2);
+ let title = aList ? FeedUtils.strings.formatStringFromName(
+ "subscribe-OPMLExportTitleList", [accountName], 1) :
+ FeedUtils.strings.formatStringFromName(
+ "subscribe-OPMLExportTitleStruct", [accountName], 1);
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+
+ fp.defaultString = fileName;
+ fp.defaultExtension = "opml";
+ if (this.opmlLastSaveAsDir && (this.opmlLastSaveAsDir instanceof Ci.nsILocalFile))
+ fp.displayDirectory = this.opmlLastSaveAsDir;
+
+ let opmlFilterText = FeedUtils.strings.GetStringFromName(
+ "subscribe-OPMLExportOPMLFilesFilterText");
+ fp.appendFilter(opmlFilterText, "*.opml");
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.filterIndex = 0;
+ fp.init(window, title, Ci.nsIFilePicker.modeSave);
+
+ if (fp.show() != Ci.nsIFilePicker.returnCancel && fp.file)
+ {
+ this.opmlLastSaveAsDir = fp.file.parent;
+ return fp.file;
+ }
+
+ return null;
+ },
+
+/**
+ * Import feeds opml file Open filepicker function.
+ *
+ * @return nsILocalFile or null.
+ */
+ opmlPickOpenFile: function()
+ {
+ let title = FeedUtils.strings.GetStringFromName("subscribe-OPMLImportTitle");
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+
+ fp.defaultString = "";
+ if (this.opmlLastOpenDir && (this.opmlLastOpenDir instanceof Ci.nsILocalFile))
+ fp.displayDirectory = this.opmlLastOpenDir;
+
+ let opmlFilterText = FeedUtils.strings.GetStringFromName(
+ "subscribe-OPMLExportOPMLFilesFilterText");
+ fp.appendFilter(opmlFilterText, "*.opml");
+ fp.appendFilters(Ci.nsIFilePicker.filterXML);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.init(window, title, Ci.nsIFilePicker.modeOpen);
+
+ if (fp.show() != Ci.nsIFilePicker.returnCancel && fp.file)
+ {
+ this.opmlLastOpenDir = fp.file.parent;
+ return fp.file;
+ }
+
+ return null;
+ },
+
+ exportOPML: function(aEvent)
+ {
+ // Account folder must be selected.
+ let item = this.mView.currentItem;
+ if (!item || !item.folder || !item.folder.isServer)
+ return;
+
+ this.mRSSServer = item.folder.server;
+ let rootFolder = this.mRSSServer.rootFolder;
+ let exportAsList = aEvent.ctrlKey;
+ let SPACES2 = " ";
+ let SPACES4 = " ";
+
+ if (this.mRSSServer.rootFolder.hasSubFolders)
+ {
+ let opmlDoc = document.implementation.createDocument("", "opml", null);
+ let opmlRoot = opmlDoc.documentElement;
+ opmlRoot.setAttribute("version", "1.0");
+ opmlRoot.setAttribute("xmlns:fz", "urn:forumzilla:");
+
+ this.generatePPSpace(opmlRoot, SPACES2);
+
+ // Make the <head> element.
+ let head = opmlDoc.createElement("head");
+ this.generatePPSpace(head, SPACES4);
+ let titleText = FeedUtils.strings.formatStringFromName(
+ "subscribe-OPMLExportFileDialogTitle",
+ [this.brandShortName, rootFolder.prettyName], 2);
+ let title = opmlDoc.createElement("title");
+ title.appendChild(opmlDoc.createTextNode(titleText));
+ head.appendChild(title);
+ this.generatePPSpace(head, SPACES4);
+ let dt = opmlDoc.createElement("dateCreated");
+ dt.appendChild(opmlDoc.createTextNode((new Date()).toUTCString()));
+ head.appendChild(dt);
+ this.generatePPSpace(head, SPACES2);
+ opmlRoot.appendChild(head);
+
+ this.generatePPSpace(opmlRoot, SPACES2);
+
+ // Add <outline>s to the <body>.
+ let body = opmlDoc.createElement("body");
+ if (exportAsList)
+ this.generateOutlineList(rootFolder, body, SPACES4.length + 2);
+ else
+ this.generateOutlineStruct(rootFolder, body, SPACES4.length);
+
+ this.generatePPSpace(body, SPACES2);
+
+ if (!body.childElementCount)
+ // No folders/feeds.
+ return;
+
+ opmlRoot.appendChild(body);
+ this.generatePPSpace(opmlRoot, "");
+
+ let serializer = new XMLSerializer();
+
+ if (FeedUtils.log.level <= Log4Moz.Level.Debug)
+ FeedUtils.log.debug("exportOPML: opmlDoc -\n" +
+ serializer.serializeToString(opmlDoc) + "\n");
+
+ // Get file to save from filepicker.
+ let saveAsFile = this.opmlPickSaveAsFile(exportAsList);
+ if (!saveAsFile)
+ return;
+
+ let fos = FileUtils.openSafeFileOutputStream(saveAsFile);
+ serializer.serializeToStream(opmlDoc, fos, "utf-8");
+ FileUtils.closeSafeFileOutputStream(fos);
+
+ let statusReport = FeedUtils.strings.formatStringFromName(
+ "subscribe-OPMLExportDone", [saveAsFile.path], 1);
+ this.updateStatusItem("statusText", statusReport);
+ }
+ },
+
+ generatePPSpace: function(aNode, indentString)
+ {
+ aNode.appendChild(aNode.ownerDocument.createTextNode("\n"));
+ aNode.appendChild(aNode.ownerDocument.createTextNode(indentString));
+ },
+
+ generateOutlineList: function(baseFolder, parent, indentLevel)
+ {
+ // Pretty printing.
+ let indentString = " ".repeat(indentLevel - 2);
+
+ let feedOutline;
+ let folderEnumerator = baseFolder.subFolders;
+ while (folderEnumerator.hasMoreElements())
+ {
+ let folder = folderEnumerator.getNext().QueryInterface(Ci.nsIMsgFolder);
+ FeedUtils.log.debug("generateOutlineList: folder - " +
+ folder.filePath.path);
+ if (!(folder instanceof Ci.nsIMsgFolder) ||
+ folder.getFlag(Ci.nsMsgFolderFlags.Trash) ||
+ folder.getFlag(Ci.nsMsgFolderFlags.Virtual))
+ continue;
+
+ FeedUtils.log.debug("generateOutlineList: CONTINUE folderName - " +
+ folder.name);
+
+ if (folder.hasSubFolders)
+ {
+ FeedUtils.log.debug("generateOutlineList: has subfolders - " +
+ folder.name);
+ // Recurse.
+ this.generateOutlineList(folder, parent, indentLevel);
+ }
+
+ // Add outline elements with xmlUrls.
+ let feeds = this.getFeedsInFolder(folder);
+ for (let feed of feeds)
+ {
+ FeedUtils.log.debug("generateOutlineList: folder has FEED url - " +
+ folder.name + " : " + feed.url);
+ feedOutline = this.exportOPMLOutline(feed, parent.ownerDocument);
+ this.generatePPSpace(parent, indentString);
+ parent.appendChild(feedOutline);
+ }
+ }
+ },
+
+ generateOutlineStruct: function(baseFolder, parent, indentLevel)
+ {
+ // Pretty printing.
+ function indentString(len) { return " ".repeat(len - 2); };
+
+ let folderOutline, feedOutline;
+ let folderEnumerator = baseFolder.subFolders;
+ while (folderEnumerator.hasMoreElements())
+ {
+ let folder = folderEnumerator.getNext().QueryInterface(Ci.nsIMsgFolder);
+ FeedUtils.log.debug("generateOutlineStruct: folder - " +
+ folder.filePath.path);
+ if (!(folder instanceof Ci.nsIMsgFolder) ||
+ folder.getFlag(Ci.nsMsgFolderFlags.Trash) ||
+ folder.getFlag(Ci.nsMsgFolderFlags.Virtual))
+ continue;
+
+ FeedUtils.log.debug("generateOutlineStruct: CONTINUE folderName - " +
+ folder.name);
+
+ // Make a folder outline element.
+ folderOutline = parent.ownerDocument.createElement("outline");
+ folderOutline.setAttribute("title", folder.prettyName);
+ this.generatePPSpace(parent, indentString(indentLevel + 2));
+
+ if (folder.hasSubFolders)
+ {
+ FeedUtils.log.debug("generateOutlineStruct: has subfolders - " +
+ folder.name);
+ // Recurse.
+ this.generateOutlineStruct(folder, folderOutline, indentLevel + 2);
+ }
+
+ let feeds = this.getFeedsInFolder(folder);
+ for (let feed of feeds)
+ {
+ // Add feed outline elements with xmlUrls.
+ FeedUtils.log.debug("generateOutlineStruct: folder has FEED url - "+
+ folder.name + " : " + feed.url);
+ feedOutline = this.exportOPMLOutline(feed, parent.ownerDocument);
+ this.generatePPSpace(folderOutline, indentString(indentLevel + 4));
+ folderOutline.appendChild(feedOutline);
+ }
+
+ parent.appendChild(folderOutline);
+ }
+ },
+
+ exportOPMLOutline: function(aFeed, aDoc)
+ {
+ let outRv = aDoc.createElement("outline");
+ outRv.setAttribute("type", "rss");
+ outRv.setAttribute("title", aFeed.title);
+ outRv.setAttribute("text", aFeed.title);
+ outRv.setAttribute("version", "RSS");
+ outRv.setAttribute("fz:quickMode", aFeed.quickMode);
+ outRv.setAttribute("fz:options", JSON.stringify(aFeed.options));
+ outRv.setAttribute("xmlUrl", aFeed.url);
+ outRv.setAttribute("htmlUrl", aFeed.link);
+ return outRv;
+ },
+
+ importOPML: function()
+ {
+ // Account folder must be selected in subscribe dialog.
+ let item = this.mView ? this.mView.currentItem : null;
+ if (!item || !item.folder || !item.folder.isServer)
+ return;
+
+ let server = item.folder.server;
+ // Get file to open from filepicker.
+ let openFile = this.opmlPickOpenFile();
+ if (!openFile)
+ return;
+
+ this.mActionMode = this.kImportingOPML;
+ this.updateButtons(null);
+ this.selectFolder(item.folder, { select: false, open: true });
+ let statusReport = FeedUtils.strings.GetStringFromName("subscribe-loading");
+ this.updateStatusItem("statusText", statusReport);
+ // If there were a getElementsByAttribute in html, we could go determined...
+ this.updateStatusItem("progressMeter", "?");
+
+ if (!this.importOPMLFile(openFile, server, this.importOPMLFinished)) {
+ this.mActionMode = null;
+ this.updateButtons(item);
+ this.clearStatusInfo();
+ }
+ },
+
+/**
+ * Import opml file into a feed account. Used by the Subscribe dialog and
+ * the Import wizard.
+ *
+ * @param nsILocalFile aFile - the opml file.
+ * @param nsIMsgIncomingServer aServer - the account server.
+ * @param func aCallback - callback function.
+ *
+ * @return bool - false if error.
+ */
+ importOPMLFile: function(aFile, aServer, aCallback)
+ {
+ if (aServer && (aServer instanceof Ci.nsIMsgIncomingServer))
+ this.mRSSServer = aServer;
+
+ if (!aFile || !this.mRSSServer || !aCallback)
+ return false;
+
+ let opmlDom, statusReport;
+ let stream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+
+ // Read in file as raw bytes, so Expat can do the decoding for us.
+ try {
+ stream.init(aFile, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+ let parser = new DOMParser();
+ opmlDom = parser.parseFromStream(stream, null, stream.available(),
+ "application/xml");
+ }
+ catch(e) {
+ statusReport = FeedUtils.strings.GetStringFromName(
+ "subscribe-errorOpeningFile");
+ Services.prompt.alert(window, null, statusReport);
+ return false;
+ }
+ finally {
+ stream.close();
+ }
+
+ let body = opmlDom ? opmlDom.querySelector("body") : null;
+
+ // Return if the OPML file is invalid or empty.
+ if (!body || !body.childElementCount ||
+ opmlDom.documentElement.tagName != "opml")
+ {
+ statusReport = FeedUtils.strings.formatStringFromName(
+ "subscribe-OPMLImportInvalidFile", [aFile.leafName], 1);
+ Services.prompt.alert(window, null, statusReport);
+ return false;
+ }
+
+ this.importOPMLOutlines(body, this.mRSSServer, aCallback);
+ return true;
+ },
+
+ importOPMLOutlines: function(aBody, aRSSServer, aCallback)
+ {
+ let win = this;
+ let rssServer = aRSSServer;
+ let callback = aCallback;
+ let outline, feedFolder;
+ let badTag = false;
+ let firstFeedInFolderQuickMode = null;
+ let lastFolder;
+ let feedsAdded = 0;
+ let rssOutlines = 0;
+ let folderOutlines = 0;
+
+ function processor(aParentNode, aParentFolder)
+ {
+ FeedUtils.log.trace("importOPMLOutlines: PROCESSOR tag:name:childs - " +
+ aParentNode.tagName + ":" +
+ aParentNode.getAttribute("text") + ":" +
+ aParentNode.childElementCount);
+ while (true)
+ {
+ if (aParentNode.tagName == "body" && !aParentNode.childElementCount)
+ {
+ // Finished.
+ let statusReport = win.importOPMLStatus(feedsAdded, rssOutlines);
+ callback(statusReport, lastFolder, win);
+ return;
+ }
+
+ outline = aParentNode.firstElementChild;
+ if (outline.tagName != "outline")
+ {
+ FeedUtils.log.info("importOPMLOutlines: skipping, node is not an " +
+ "<outline> - <" + outline.tagName + ">");
+ badTag = true;
+ break;
+ }
+
+ let outlineName = outline.getAttribute("text") ||
+ outline.getAttribute("title") ||
+ outline.getAttribute("xmlUrl");
+ let feedUrl, folderURI;
+
+ if (outline.getAttribute("type") == "rss")
+ {
+ // A feed outline.
+ feedUrl = outline.getAttribute("xmlUrl") || outline.getAttribute("url");
+ if (!feedUrl)
+ {
+ FeedUtils.log.info("importOPMLOutlines: skipping, type=rss <outline> " +
+ "has no url - " + outlineName);
+ break;
+ }
+
+ rssOutlines++;
+ feedFolder = aParentFolder;
+
+ if (FeedUtils.feedAlreadyExists(feedUrl, rssServer))
+ {
+ FeedUtils.log.info("importOPMLOutlines: feed already subscribed in account " +
+ rssServer.prettyName + ", url - " + feedUrl);
+ break;
+ }
+
+ if (aParentNode.tagName == "outline" &&
+ aParentNode.getAttribute("type") != "rss")
+ // Parent is a folder, already created.
+ folderURI = feedFolder.URI;
+ else
+ {
+ // Parent is not a folder outline, likely the <body> in a flat list.
+ // Create feed's folder with feed's name and account rootFolder as
+ // parent of feed's folder.
+ // NOTE: Assume a type=rss outline must be a leaf and is not a
+ // direct parent of another type=rss outline; such a structure
+ // may lead to unintended nesting and inaccurate counts.
+ }
+
+ // Create the feed.
+ let quickMode = outline.hasAttribute("fz:quickMode") ?
+ outline.getAttribute("fz:quickMode") == "true" :
+ rssServer.getBoolValue("quickMode");
+ let options = outline.getAttribute("fz:options");
+ options = options ? JSON.parse(options) : null;
+
+ if (firstFeedInFolderQuickMode === null)
+ // The summary/web page pref applies to all feeds in a folder,
+ // though it is a property of an individual feed. This can be
+ // set (and is obvious) in the subscribe dialog; ensure import
+ // doesn't leave mismatches if mismatched in the opml file.
+ firstFeedInFolderQuickMode = quickMode;
+ else
+ quickMode = firstFeedInFolderQuickMode;
+
+ let feedProperties = { feedName : outlineName,
+ feedLocation : feedUrl,
+ server : rssServer,
+ folderURI : folderURI,
+ quickMode : quickMode,
+ options : options };
+
+ FeedUtils.log.info("importOPMLOutlines: importing feed: name, url - "+
+ outlineName + ", " + feedUrl);
+
+ let feed = win.storeFeed(feedProperties);
+ if (outline.hasAttribute("htmlUrl"))
+ feed.link = outline.getAttribute("htmlUrl");
+
+ feed.createFolder();
+ if (!feed.folder)
+ {
+ // Non success. Remove intermediate traces from the feeds database.
+ if (feed && feed.url && feed.server)
+ FeedUtils.deleteFeed(FeedUtils.rdf.GetResource(feed.url),
+ feed.server,
+ feed.server.rootFolder);
+ FeedUtils.log.info("importOPMLOutlines: skipping, error creating folder - '" +
+ feed.folderName + "' from outlineName - '" +
+ outlineName + "' in parent folder " +
+ aParentFolder.filePath.path);
+ badTag = true;
+ break;
+ }
+
+ // Add the feed to the databases.
+ FeedUtils.addFeed(feed);
+ // Feed correctly added.
+ feedsAdded++;
+ lastFolder = feed.folder;
+ }
+ else
+ {
+ // A folder outline. If a folder exists in the account structure at
+ // the same level as in the opml structure, feeds are placed into the
+ // existing folder.
+ let defaultName = FeedUtils.strings.GetStringFromName("ImportFeedsNew");
+ let folderName = FeedUtils.getSanitizedFolderName(aParentFolder,
+ outlineName,
+ defaultName,
+ false);
+ try {
+ feedFolder = aParentFolder.getChildNamed(folderName);
+ }
+ catch (ex) {
+ // Folder not found, create it.
+ FeedUtils.log.info("importOPMLOutlines: creating folder - '" +
+ folderName + "' from outlineName - '" +
+ outlineName + "' in parent folder " +
+ aParentFolder.filePath.path);
+ firstFeedInFolderQuickMode = null;
+ try {
+ feedFolder = aParentFolder.QueryInterface(Ci.nsIMsgLocalMailFolder).
+ createLocalSubfolder(folderName);
+ folderOutlines++;
+ }
+ catch (ex) {
+ // An error creating. Skip it.
+ FeedUtils.log.info("importOPMLOutlines: skipping, error creating folder - '" +
+ folderName + "' from outlineName - '" +
+ outlineName + "' in parent folder " +
+ aParentFolder.filePath.path);
+ let xfolder = aParentFolder.getChildNamed(folderName);
+ aParentFolder.propagateDelete(xfolder, true, null);
+ badTag = true;
+ break;
+ }
+ }
+ }
+ break;
+ }
+
+ if (!outline.childElementCount || badTag)
+ {
+ // Remove leaf nodes that are processed or bad tags from the opml dom,
+ // and go back to reparse. This method lets us use setTimeout to
+ // prevent UI hang, in situations of both deep and shallow trees.
+ // A yield/generator.next() method is fine for shallow trees, but not
+ // the true recursion required for deeper trees; both the shallow loop
+ // and the recurse should give it up.
+ outline.remove();
+ badTag = false;
+ outline = aBody;
+ feedFolder = rssServer.rootFolder;
+ }
+
+ setTimeout(function() {
+ processor(outline, feedFolder);
+ }, 0);
+ }
+
+ processor(aBody, rssServer.rootFolder);
+ },
+
+ importOPMLStatus: function(aFeedsAdded, aRssOutlines, aFolderOutlines)
+ {
+ let statusReport;
+ if (aRssOutlines > aFeedsAdded)
+ statusReport = FeedUtils.strings.formatStringFromName("subscribe-OPMLImportStatus",
+ [PluralForm.get(aFeedsAdded,
+ FeedUtils.strings.GetStringFromName("subscribe-OPMLImportUniqueFeeds"))
+ .replace("#1", aFeedsAdded),
+ PluralForm.get(aRssOutlines,
+ FeedUtils.strings.GetStringFromName("subscribe-OPMLImportFoundFeeds"))
+ .replace("#1", aRssOutlines)], 2);
+ else
+ statusReport = PluralForm.get(aFeedsAdded,
+ FeedUtils.strings.GetStringFromName("subscribe-OPMLImportFeedCount"))
+ .replace("#1", aFeedsAdded);
+
+ return statusReport;
+ },
+
+ importOPMLFinished: function(aStatusReport, aLastFolder, aWin)
+ {
+ if (aLastFolder)
+ {
+ aWin.selectFolder(aLastFolder, { select: false, newFolder: aLastFolder });
+ aWin.selectFolder(aLastFolder.parent);
+ }
+ aWin.mActionMode = null;
+ aWin.updateButtons(aWin.mView.currentItem);
+ aWin.clearStatusInfo();
+ aWin.updateStatusItem("statusText", aStatusReport);
+ }
+
+};
diff --git a/mailnews/extensions/newsblog/content/feed-subscriptions.xul b/mailnews/extensions/newsblog/content/feed-subscriptions.xul
new file mode 100644
index 000000000..d8dff29ee
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/feed-subscriptions.xul
@@ -0,0 +1,234 @@
+<?xml version="1.0"?>
+<!-- -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger-newsblog/skin/feed-subscriptions.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % feedDTD SYSTEM "chrome://messenger-newsblog/locale/feed-subscriptions.dtd">
+ %feedDTD;
+ <!ENTITY % certDTD SYSTEM "chrome://pippki/locale/certManager.dtd">
+ %certDTD;
+]>
+
+<window id="subscriptionsDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ flex="1"
+ title="&feedSubscriptions.label;"
+ windowtype="Mail:News-BlogSubscriptions"
+ persist="width height screenX screenY sizemode"
+ onload="FeedSubscriptions.onLoad();"
+ onclose="return FeedSubscriptions.onClose();"
+ onkeypress="FeedSubscriptions.onKeyPress(event);"
+ onmousedown="FeedSubscriptions.onMouseDown(event);">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/specialTabs.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger-newsblog/content/feed-subscriptions.js"/>
+
+ <keyset id="extensionsKeys">
+ <key id="key_close"
+ key="&cmd.close.commandKey;"
+ modifiers="accel"
+ oncommand="window.close();"/>
+ <key id="key_close2"
+ keycode="VK_ESCAPE"
+ oncommand="window.close();"/>
+ </keyset>
+
+ <stringbundle id="bundle_newsblog"
+ src="chrome://messenger-newsblog/locale/newsblog.properties"/>
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+
+ <vbox flex="1" id="contentPane">
+ <hbox align="right">
+ <label id="learnMore"
+ class="text-link"
+ crop="end"
+ value="&learnMore.label;"
+ href="https://support.mozilla.org/kb/how-subscribe-news-feeds-and-blogs"/>
+ </hbox>
+
+ <tree id="rssSubscriptionsList"
+ treelines="true"
+ flex="1"
+ hidecolumnpicker="true"
+ onselect="FeedSubscriptions.onSelect();"
+ seltype="single">
+ <treecols>
+ <treecol id="folderNameCol"
+ flex="2"
+ primary="true"
+ hideheader="true"/>
+ </treecols>
+ <treechildren id="subscriptionChildren"
+ ondragstart="FeedSubscriptions.onDragStart(event);"
+ ondragover="FeedSubscriptions.onDragOver(event);"/>
+ </tree>
+
+ <hbox id="rssFeedInfoBox">
+ <vbox flex="1">
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <hbox align="right" valign="middle">
+ <label id="nameLabel"
+ accesskey="&feedTitle.accesskey;"
+ control="nameValue"
+ value="&feedTitle.label;"/>
+ </hbox>
+ <textbox id="nameValue"
+ clickSelectsAll="true"/>
+ </row>
+ <row>
+ <hbox align="right" valign="middle">
+ <label id="locationLabel"
+ accesskey="&feedLocation.accesskey;"
+ control="locationValue"
+ value="&feedLocation.label;"/>
+ </hbox>
+ <hbox>
+ <textbox id="locationValue"
+ flex="1"
+ class="uri-element"
+ placeholder="&feedLocation.placeholder;"
+ clickSelectsAll="true"
+ onfocus="FeedSubscriptions.setSummaryFocus();"
+ onblur="FeedSubscriptions.setSummaryFocus();"/>
+ <hbox align="center">
+ <label id="locationValidate"
+ collapsed="true"
+ class="text-link"
+ crop="end"
+ value="&locationValidate.label;"
+ onclick="FeedSubscriptions.checkValidation(event);"/>
+ </hbox>
+ </hbox>
+ </row>
+ <row>
+ <hbox align="right" valign="middle">
+ <label id="feedFolderLabel"
+ value="&feedFolder.label;"
+ accesskey="&feedFolder.accesskey;"
+ control="selectFolder"/>
+ </hbox>
+ <hbox>
+ <menulist id="selectFolder"
+ flex="1"
+ class="folderMenuItem"
+ hidden="true">
+ <menupopup id="selectFolderPopup"
+ class="menulist-menupopup"
+ type="folder"
+ mode="feeds"
+ showFileHereLabel="true"
+ showAccountsFileHere="true"
+ oncommand="FeedSubscriptions.setNewFolder(event)"/>
+ </menulist>
+ <textbox id="selectFolderValue"
+ flex="1"
+ readonly="true"
+ onkeypress="FeedSubscriptions.onClickSelectFolderValue(event)"
+ onclick="FeedSubscriptions.onClickSelectFolderValue(event)"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <checkbox id="quickMode"
+ accesskey="&quickMode.accesskey;"
+ label="&quickMode.label;"
+ oncommand="FeedSubscriptions.setSummary(this.checked)"/>
+ <checkbox id="autotagEnable"
+ accesskey="&autotagEnable.accesskey;"
+ label="&autotagEnable.label;"
+ oncommand="FeedSubscriptions.setCategoryPrefs(this)"/>
+ <hbox>
+ <checkbox id="autotagUsePrefix"
+ class="indent"
+ accesskey="&autotagUsePrefix.accesskey;"
+ label="&autotagUsePrefix.label;"
+ oncommand="FeedSubscriptions.setCategoryPrefs(this)"/>
+ <textbox id="autotagPrefix"
+ placeholder="&autoTagPrefix.placeholder;"
+ clickSelectsAll="true"/>
+ </hbox>
+ <separator class="thin"/>
+ </vbox>
+ </hbox>
+
+ <hbox id="statusContainerBox"
+ align="center"
+ valign="middle">
+ <vbox flex="1">
+ <description id="statusText"/>
+ </vbox>
+ <spacer flex="1"/>
+ <label id="validationText"
+ collapsed="true"
+ class="text-link"
+ crop="end"
+ value="&validateText.label;"
+ onclick="FeedSubscriptions.checkValidation(event);"/>
+ <button id="addCertException"
+ collapsed="true"
+ label="&certmgr.addException.label;"
+ accesskey="&certmgr.addException.accesskey;"
+ oncommand="FeedSubscriptions.addCertExceptionDialog();"/>
+ <progressmeter id="progressMeter"
+ collapsed="true"
+ mode="determined"
+ value="0"/>
+ </hbox>
+
+ <hbox align="end">
+ <hbox class="actionButtons" flex="1">
+ <button id="addFeed"
+ label="&button.addFeed.label;"
+ accesskey="&button.addFeed.accesskey;"
+ oncommand="FeedSubscriptions.addFeed();"/>
+
+ <button id="editFeed"
+ disabled="true"
+ label="&button.updateFeed.label;"
+ accesskey="&button.updateFeed.accesskey;"
+ oncommand="FeedSubscriptions.editFeed();"/>
+
+ <button id="removeFeed"
+ disabled="true"
+ label="&button.removeFeed.label;"
+ accesskey="&button.removeFeed.accesskey;"
+ oncommand="FeedSubscriptions.removeFeed(true);"/>
+
+ <button id="importOPML"
+ label="&button.importOPML.label;"
+ accesskey="&button.importOPML.accesskey;"
+ oncommand="FeedSubscriptions.importOPML();"/>
+
+ <button id="exportOPML"
+ label="&button.exportOPML.label;"
+ accesskey="&button.exportOPML.accesskey;"
+ tooltiptext="&button.exportOPML.tooltip;"
+ oncommand="FeedSubscriptions.exportOPML(event);"/>
+
+ <spacer flex="1"/>
+
+ <button id="close"
+ label="&button.close.label;"
+ icon="close"
+ oncommand="if (FeedSubscriptions.onClose()) window.close();"/>
+ </hbox>
+ </hbox>
+ </vbox>
+</window>
diff --git a/mailnews/extensions/newsblog/content/feedAccountWizard.js b/mailnews/extensions/newsblog/content/feedAccountWizard.js
new file mode 100644
index 000000000..a79da073a
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/feedAccountWizard.js
@@ -0,0 +1,45 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/FeedUtils.jsm");
+
+/* Feed account standalone wizard functions */
+var FeedAccountWizard = {
+ accountName: "",
+
+ accountSetupPageInit: function() {
+ this.accountSetupPageValidate();
+ },
+
+ accountSetupPageValidate: function() {
+ this.accountName = document.getElementById("prettyName").value.trim();
+ document.documentElement.canAdvance = this.accountName;
+ },
+
+ accountSetupPageUnload: function() {
+ return;
+ },
+
+ donePageInit: function() {
+ document.getElementById("account.name.text").value = this.accountName;
+ },
+
+ onCancel: function() {
+ return true;
+ },
+
+ onFinish: function() {
+ let account = FeedUtils.createRssAccount(this.accountName);
+ if ("gFolderTreeView" in window.opener.top)
+ // Opened from 3pane File->New or Appmenu New Message, or
+ // Account Central link.
+ window.opener.top.gFolderTreeView.selectFolder(account.incomingServer.rootMsgFolder);
+ else if ("selectServer" in window.opener)
+ // Opened from Account Settings.
+ window.opener.selectServer(account.incomingServer);
+
+ window.close();
+ }
+}
diff --git a/mailnews/extensions/newsblog/content/feedAccountWizard.xul b/mailnews/extensions/newsblog/content/feedAccountWizard.xul
new file mode 100644
index 000000000..0535fb237
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/feedAccountWizard.xul
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountWizard.css" type="text/css"?>
+
+<!DOCTYPE wizard [
+ <!ENTITY % accountDTD SYSTEM "chrome://messenger/locale/AccountWizard.dtd">
+ %accountDTD;
+ <!ENTITY % newsblogDTD SYSTEM "chrome://messenger-newsblog/locale/am-newsblog.dtd" >
+ %newsblogDTD;
+ <!ENTITY % imDTD SYSTEM "chrome://messenger/locale/imAccountWizard.dtd" >
+ %imDTD;
+]>
+
+<wizard id="FeedAccountWizard"
+ title="&feedWindowTitle.label;"
+ onwizardcancel="return FeedAccountWizard.onCancel();"
+ onwizardfinish="return FeedAccountWizard.onFinish();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://messenger-newsblog/content/feedAccountWizard.js"/>
+
+ <!-- Account setup page : User gets a choice to enter a name for the account -->
+ <!-- Defaults : Feed account name -> default string -->
+ <wizardpage id="accountsetuppage"
+ pageid="accountsetuppage"
+ label="&accnameTitle.label;"
+ onpageshow="return FeedAccountWizard.accountSetupPageInit();"
+ onpageadvanced="return FeedAccountWizard.accountSetupPageUnload();">
+ <vbox flex="1">
+ <description>&accnameDesc.label;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label class="label"
+ value="&accnameLabel.label;"
+ accesskey="&accnameLabel.accesskey;"
+ control="prettyName"/>
+ <textbox id="prettyName"
+ flex="1"
+ value="&feeds.accountName;"
+ oninput="FeedAccountWizard.accountSetupPageValidate();"/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <!-- Done page : Summarizes information collected to create a feed account -->
+ <wizardpage id="done"
+ pageid="done"
+ label="&accountSummaryTitle.label;"
+ onpageshow="return FeedAccountWizard.donePageInit();">
+ <vbox flex="1">
+ <description>&accountSummaryInfo.label;</description>
+ <separator class="thin"/>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row id="account.name"
+ align="center">
+ <label id="account.name.label"
+ class="label"
+ flex="1"
+ value="&accnameLabel.label;"/>
+ <label id="account.name.text"
+ class="label"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ <spacer flex="1"/>
+ </vbox>
+ </wizardpage>
+
+</wizard>
diff --git a/mailnews/extensions/newsblog/content/newsblogOverlay.js b/mailnews/extensions/newsblog/content/newsblogOverlay.js
new file mode 100644
index 000000000..f7e08ec95
--- /dev/null
+++ b/mailnews/extensions/newsblog/content/newsblogOverlay.js
@@ -0,0 +1,363 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/gloda/mimemsg.js");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// This global is for SeaMonkey compatibility.
+var gShowFeedSummary;
+
+var FeedMessageHandler = {
+ gShowSummary: true,
+ gToggle: false,
+ kSelectOverrideWebPage: 0,
+ kSelectOverrideSummary: 1,
+ kSelectFeedDefault: 2,
+ kOpenWebPage: 0,
+ kOpenSummary: 1,
+ kOpenToggleInMessagePane: 2,
+ kOpenLoadInBrowser: 3,
+
+ /**
+ * How to load message on threadpane select.
+ */
+ get onSelectPref() {
+ return Services.prefs.getIntPref("rss.show.summary");
+ },
+
+ set onSelectPref(val) {
+ Services.prefs.setIntPref("rss.show.summary", val);
+ ReloadMessage();
+ },
+
+ /**
+ * Load web page on threadpane select.
+ */
+ get loadWebPageOnSelectPref() {
+ return Services.prefs.getIntPref("rss.message.loadWebPageOnSelect") ? true : false;
+ },
+
+ /**
+ * How to load message on open (enter/dbl click in threadpane, contextmenu).
+ */
+ get onOpenPref() {
+ return Services.prefs.getIntPref("rss.show.content-base");
+ },
+
+ set onOpenPref(val) {
+ Services.prefs.setIntPref("rss.show.content-base", val);
+ },
+
+ /**
+ * Determine if a message is a feed message. Prior to Tb15, a message had to
+ * be in an rss acount type folder. In Tb15 and later, a flag is set on the
+ * message itself upon initial store; the message can be moved to any folder.
+ *
+ * @param nsIMsgDBHdr aMsgHdr - the message.
+ *
+ * @return true if message is a feed, false if not.
+ */
+ isFeedMessage: function(aMsgHdr) {
+ return (aMsgHdr instanceof Components.interfaces.nsIMsgDBHdr) &&
+ ((aMsgHdr.flags & Components.interfaces.nsMsgMessageFlags.FeedMsg) ||
+ (aMsgHdr.folder && aMsgHdr.folder.server.type == "rss"));
+ },
+
+ /**
+ * Determine whether to show a feed message summary or load a web page in the
+ * message pane.
+ *
+ * @param nsIMsgDBHdr aMsgHdr - the message.
+ * @param bool aToggle - true if in toggle mode, false otherwise.
+ *
+ * @return true if summary is to be displayed, false if web page.
+ */
+ shouldShowSummary: function(aMsgHdr, aToggle) {
+ // Not a feed message, always show summary (the message).
+ if (!this.isFeedMessage(aMsgHdr))
+ return true;
+
+ // Notified of a summary reload when toggling, reset toggle and return.
+ if (!aToggle && this.gToggle)
+ return !(this.gToggle = false);
+
+ let showSummary = true;
+ this.gToggle = aToggle;
+
+ // Thunderbird 2 rss messages with 'Show article summary' not selected,
+ // ie message body constructed to show web page in an iframe, can't show
+ // a summary - notify user.
+ let browser = getBrowser();
+ let contentDoc = browser ? browser.contentDocument : null;
+ let rssIframe = contentDoc ? contentDoc.getElementById("_mailrssiframe") : null;
+ if (rssIframe) {
+ if (this.gToggle || this.onSelectPref == this.kSelectOverrideSummary)
+ this.gToggle = false;
+ return false;
+ }
+
+ if (aToggle)
+ // Toggle mode, flip value.
+ return gShowFeedSummary = this.gShowSummary = !this.gShowSummary;
+
+ let wintype = document.documentElement.getAttribute("windowtype");
+ let tabMail = document.getElementById("tabmail");
+ let messageTab = tabMail && tabMail.currentTabInfo.mode.type == "message";
+ let messageWindow = wintype == "mail:messageWindow";
+
+ switch (this.onSelectPref) {
+ case this.kSelectOverrideWebPage:
+ showSummary = false;
+ break;
+ case this.kSelectOverrideSummary:
+ showSummary = true
+ break;
+ case this.kSelectFeedDefault:
+ // Get quickmode per feed folder pref from feeds.rdf. If the feed
+ // message is not in a feed account folder (hence the folder is not in
+ // the feeds database), or FZ_QUICKMODE property is not found (possible
+ // in pre renovation urls), err on the side of showing the summary.
+ // For the former, toggle or global override is necessary; for the
+ // latter, a show summary checkbox toggle in Subscribe dialog will set
+ // one on the path to bliss.
+ let folder = aMsgHdr.folder, targetRes;
+ try {
+ targetRes = FeedUtils.getParentTargetForChildResource(
+ folder.URI, FeedUtils.FZ_QUICKMODE, folder.server);
+ }
+ catch (ex) {
+ // Not in a feed account folder or other error.
+ FeedUtils.log.info("FeedMessageHandler.shouldShowSummary: could not " +
+ "get summary pref for this folder");
+ }
+
+ showSummary = targetRes && targetRes.QueryInterface(Ci.nsIRDFLiteral).
+ Value == "false" ? false : true;
+ break;
+ }
+
+ gShowFeedSummary = this.gShowSummary = showSummary;
+
+ if (messageWindow || messageTab) {
+ // Message opened in either standalone window or tab, due to either
+ // message open pref (we are here only if the pref is 0 or 1) or
+ // contextmenu open.
+ switch (this.onOpenPref) {
+ case this.kOpenToggleInMessagePane:
+ // Opened by contextmenu, use the value derived above.
+ // XXX: allow a toggle via crtl?
+ break;
+ case this.kOpenWebPage:
+ showSummary = false;
+ break;
+ case this.kOpenSummary:
+ showSummary = true;
+ break;
+ }
+ }
+
+ // Auto load web page in browser on select, per pref; shouldShowSummary() is
+ // always called first to 1)test if feed, 2)get summary pref, so do it here.
+ if (this.loadWebPageOnSelectPref)
+ setTimeout(FeedMessageHandler.loadWebPage, 20, aMsgHdr, {browser:true});
+
+ return showSummary;
+ },
+
+ /**
+ * Load a web page for feed messages. Use MsgHdrToMimeMessage() to get
+ * the content-base url from the message headers. We cannot rely on
+ * currentHeaderData; it has not yet been streamed at our entry point in
+ * displayMessageChanged(), and in the case of a collapsed message pane it
+ * is not streamed.
+ *
+ * @param nsIMsgDBHdr aMessageHdr - the message.
+ * @param {obj} aWhere - name value=true pair, where name is in:
+ * 'messagepane', 'browser', 'tab', 'window'.
+ */
+ loadWebPage: function(aMessageHdr, aWhere) {
+ MsgHdrToMimeMessage(aMessageHdr, null, function(aMsgHdr, aMimeMsg) {
+ if (aMimeMsg && aMimeMsg.headers["content-base"] &&
+ aMimeMsg.headers["content-base"][0]) {
+ let url = aMimeMsg.headers["content-base"], uri;
+ try {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ url = converter.ConvertToUnicode(url);
+ uri = Services.io.newURI(url, null, null);
+ url = uri.spec;
+ }
+ catch (ex) {
+ FeedUtils.log.info("FeedMessageHandler.loadWebPage: " +
+ "invalid Content-Base header url - " + url);
+ return;
+ }
+ if (aWhere.browser)
+ Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Components.interfaces.nsIExternalProtocolService)
+ .loadURI(uri);
+ else if (aWhere.messagepane) {
+ let loadFlag = getBrowser().webNavigation.LOAD_FLAGS_NONE;
+ getBrowser().webNavigation.loadURI(url, loadFlag, null, null, null);
+ }
+ else if (aWhere.tab)
+ openContentTab(url, "tab", "^");
+ else if (aWhere.window)
+ openContentTab(url, "window", "^");
+ }
+ else
+ FeedUtils.log.info("FeedMessageHandler.loadWebPage: could not get " +
+ "Content-Base header url for this message");
+ });
+ },
+
+ /**
+ * Display summary or load web page for feed messages. Caller should already
+ * know if the message is a feed message.
+ *
+ * @param nsIMsgDBHdr aMsgHdr - the message.
+ * @param bool aShowSummary - true if summary is to be displayed, false if
+ * web page.
+ */
+ setContent: function(aMsgHdr, aShowSummary) {
+ if (aShowSummary) {
+ // Only here if toggling to summary in 3pane.
+ if (this.gToggle && gDBView && GetNumSelectedMessages() == 1)
+ ReloadMessage();
+ }
+ else {
+ let browser = getBrowser();
+ if (browser && browser.contentDocument && browser.contentDocument.body)
+ browser.contentDocument.body.hidden = true;
+ // If in a non rss folder, hide possible remote content bar on a web
+ // page load, as it doesn't apply.
+ if ("msgNotificationBar" in window)
+ gMessageNotificationBar.clearMsgNotifications();
+
+ this.loadWebPage(aMsgHdr, {messagepane:true});
+ this.gToggle = false;
+ }
+ }
+}
+
+function openSubscriptionsDialog(aFolder)
+{
+ // Check for an existing feed subscriptions window and focus it.
+ let subscriptionsWindow =
+ Services.wm.getMostRecentWindow("Mail:News-BlogSubscriptions");
+
+ if (subscriptionsWindow)
+ {
+ if (aFolder)
+ {
+ subscriptionsWindow.FeedSubscriptions.selectFolder(aFolder);
+ subscriptionsWindow.FeedSubscriptions.mView.treeBox.ensureRowIsVisible(
+ subscriptionsWindow.FeedSubscriptions.mView.selection.currentIndex);
+ }
+
+ subscriptionsWindow.focus();
+ }
+ else
+ {
+ window.openDialog("chrome://messenger-newsblog/content/feed-subscriptions.xul",
+ "", "centerscreen,chrome,dialog=no,resizable",
+ { folder: aFolder});
+ }
+}
+
+// Special case attempts to reply/forward/edit as new RSS articles. For
+// messages stored prior to Tb15, we are here only if the message's folder's
+// account server is rss and feed messages moved to other types will have their
+// summaries loaded, as viewing web pages only happened in an rss account.
+// The user may choose whether to load a summary or web page link by ensuring
+// the current feed message is being viewed as either a summary or web page.
+function openComposeWindowForRSSArticle(aMsgComposeWindow, aMsgHdr, aMessageUri,
+ aType, aFormat, aIdentity, aMsgWindow)
+{
+ // Ensure right content is handled for web pages in window/tab.
+ let tabmail = document.getElementById("tabmail");
+ let is3pane = tabmail && tabmail.selectedTab && tabmail.selectedTab.mode ?
+ tabmail.selectedTab.mode.type == "folder" : false;
+ let showingwebpage = ("FeedMessageHandler" in window) && !is3pane &&
+ FeedMessageHandler.onOpenPref == FeedMessageHandler.kOpenWebPage;
+
+ if (gShowFeedSummary && !showingwebpage)
+ {
+ // The user is viewing the summary.
+ MailServices.compose.OpenComposeWindow(aMsgComposeWindow, aMsgHdr, aMessageUri,
+ aType, aFormat, aIdentity, aMsgWindow);
+
+ }
+ else
+ {
+ // Set up the compose message and get the feed message's web page link.
+ let Cc = Components.classes;
+ let Ci = Components.interfaces;
+ let msgHdr = aMsgHdr;
+ let type = aType;
+ let msgComposeType = Ci.nsIMsgCompType;
+ let subject = msgHdr.mime2DecodedSubject;
+ let fwdPrefix = Services.prefs.getCharPref("mail.forward_subject_prefix");
+ fwdPrefix = fwdPrefix ? fwdPrefix + ": " : "";
+
+ let params = Cc["@mozilla.org/messengercompose/composeparams;1"]
+ .createInstance(Ci.nsIMsgComposeParams);
+
+ let composeFields = Cc["@mozilla.org/messengercompose/composefields;1"]
+ .createInstance(Ci.nsIMsgCompFields);
+
+ if (type == msgComposeType.Reply ||
+ type == msgComposeType.ReplyAll ||
+ type == msgComposeType.ReplyToSender ||
+ type == msgComposeType.ReplyToGroup ||
+ type == msgComposeType.ReplyToSenderAndGroup)
+ {
+ subject = "Re: " + subject;
+ }
+ else if (type == msgComposeType.ForwardInline ||
+ type == msgComposeType.ForwardAsAttachment)
+ {
+ subject = fwdPrefix + subject;
+ }
+
+ params.composeFields = composeFields;
+ params.composeFields.subject = subject;
+ params.composeFields.characterSet = msgHdr.Charset;
+ params.composeFields.body = "";
+ params.bodyIsLink = false;
+ params.identity = aIdentity;
+
+ try
+ {
+ // The feed's web page url is stored in the Content-Base header.
+ MsgHdrToMimeMessage(msgHdr, null, function(aMsgHdr, aMimeMsg) {
+ if (aMimeMsg && aMimeMsg.headers["content-base"] &&
+ aMimeMsg.headers["content-base"][0])
+ {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ let url = converter.ConvertToUnicode(aMimeMsg.headers["content-base"]);
+ params.composeFields.body = url;
+ params.bodyIsLink = true;
+ MailServices.compose.OpenComposeWindowWithParams(null, params);
+ }
+ else
+ // No content-base url, use the summary.
+ MailServices.compose.OpenComposeWindow(aMsgComposeWindow, aMsgHdr, aMessageUri,
+ aType, aFormat, aIdentity, aMsgWindow);
+
+ }, false, {saneBodySize: true});
+ }
+ catch (ex)
+ {
+ // Error getting header, use the summary.
+ MailServices.compose.OpenComposeWindow(aMsgComposeWindow, aMsgHdr, aMessageUri,
+ aType, aFormat, aIdentity, aMsgWindow);
+ }
+ }
+}
diff --git a/mailnews/extensions/newsblog/jar.mn b/mailnews/extensions/newsblog/jar.mn
new file mode 100644
index 000000000..aa16a0100
--- /dev/null
+++ b/mailnews/extensions/newsblog/jar.mn
@@ -0,0 +1,16 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+newsblog.jar:
+% content messenger-newsblog %content/messenger-newsblog/
+ content/messenger-newsblog/newsblogOverlay.js (content/newsblogOverlay.js)
+ content/messenger-newsblog/Feed.js (content/Feed.js)
+ content/messenger-newsblog/FeedItem.js (content/FeedItem.js)
+ content/messenger-newsblog/feed-parser.js (content/feed-parser.js)
+* content/messenger-newsblog/feed-subscriptions.js (content/feed-subscriptions.js)
+ content/messenger-newsblog/feed-subscriptions.xul (content/feed-subscriptions.xul)
+ content/messenger-newsblog/am-newsblog.js (content/am-newsblog.js)
+ content/messenger-newsblog/am-newsblog.xul (content/am-newsblog.xul)
+ content/messenger-newsblog/feedAccountWizard.js (content/feedAccountWizard.js)
+ content/messenger-newsblog/feedAccountWizard.xul (content/feedAccountWizard.xul)
diff --git a/mailnews/extensions/newsblog/js/newsblog.js b/mailnews/extensions/newsblog/js/newsblog.js
new file mode 100644
index 000000000..364038ee5
--- /dev/null
+++ b/mailnews/extensions/newsblog/js/newsblog.js
@@ -0,0 +1,99 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource:///modules/FeedUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var nsNewsBlogFeedDownloader =
+{
+ downloadFeed: function(aFolder, aUrlListener, aIsBiff, aMsgWindow)
+ {
+ FeedUtils.downloadFeed(aFolder, aUrlListener, aIsBiff, aMsgWindow);
+ },
+
+ subscribeToFeed: function(aUrl, aFolder, aMsgWindow)
+ {
+ FeedUtils.subscribeToFeed(aUrl, aFolder, aMsgWindow);
+ },
+
+ updateSubscriptionsDS: function(aFolder, aOrigFolder, aAction)
+ {
+ FeedUtils.updateSubscriptionsDS(aFolder, aOrigFolder, aAction);
+ },
+
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsINewsBlogFeedDownloader) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+}
+
+var nsNewsBlogAcctMgrExtension =
+{
+ name: "newsblog",
+ chromePackageName: "messenger-newsblog",
+ showPanel: function (server)
+ {
+ return false;
+ },
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIMsgAccountManagerExtension) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+}
+
+function FeedDownloader() {}
+
+FeedDownloader.prototype =
+{
+ classID: Components.ID("{5c124537-adca-4456-b2b5-641ab687d1f6}"),
+ _xpcom_factory:
+ {
+ createInstance: function (aOuter, aIID)
+ {
+ if (aOuter != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ if (!aIID.equals(Ci.nsINewsBlogFeedDownloader) &&
+ !aIID.equals(Ci.nsISupports))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // return the singleton
+ return nsNewsBlogFeedDownloader.QueryInterface(aIID);
+ }
+ } // factory
+}; // feed downloader
+
+function AcctMgrExtension() {}
+
+AcctMgrExtension.prototype =
+{
+ classID: Components.ID("{E109C05F-D304-4ca5-8C44-6DE1BFAF1F74}"),
+ _xpcom_factory:
+ {
+ createInstance: function (aOuter, aIID)
+ {
+ if (aOuter != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ if (!aIID.equals(Ci.nsIMsgAccountManagerExtension) &&
+ !aIID.equals(Ci.nsISupports))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // return the singleton
+ return nsNewsBlogAcctMgrExtension.QueryInterface(aIID);
+ }
+ } // factory
+}; // account manager extension
+
+var components = [FeedDownloader, AcctMgrExtension];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/extensions/newsblog/js/newsblog.manifest b/mailnews/extensions/newsblog/js/newsblog.manifest
new file mode 100644
index 000000000..5a24df5e7
--- /dev/null
+++ b/mailnews/extensions/newsblog/js/newsblog.manifest
@@ -0,0 +1,5 @@
+component {5c124537-adca-4456-b2b5-641ab687d1f6} newsblog.js
+contract @mozilla.org/newsblog-feed-downloader;1 {5c124537-adca-4456-b2b5-641ab687d1f6}
+component {E109C05F-D304-4ca5-8C44-6DE1BFAF1F74} newsblog.js
+contract @mozilla.org/accountmanager/extension;1?name=newsblog {E109C05F-D304-4ca5-8C44-6DE1BFAF1F74}
+category mailnews-accountmanager-extensions newsblog @mozilla.org/accountmanager/extension;1?name=newsblog
diff --git a/mailnews/extensions/newsblog/moz.build b/mailnews/extensions/newsblog/moz.build
new file mode 100644
index 000000000..367f60574
--- /dev/null
+++ b/mailnews/extensions/newsblog/moz.build
@@ -0,0 +1,18 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXTRA_COMPONENTS += [
+ 'js/newsblog.js',
+ 'js/newsblog.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'content/FeedUtils.jsm',
+]
+JAR_MANIFESTS += ['jar.mn']
+
+FINAL_TARGET_FILES.isp += [
+ 'rss.rdf',
+]
diff --git a/mailnews/extensions/newsblog/rss.rdf b/mailnews/extensions/newsblog/rss.rdf
new file mode 100644
index 000000000..c7223c01b
--- /dev/null
+++ b/mailnews/extensions/newsblog/rss.rdf
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE RDF SYSTEM "chrome://messenger-newsblog/locale/am-newsblog.dtd">
+<RDF:RDF
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <RDF:Description about="NC:ispinfo">
+ <NC:providers>
+ <NC:nsIMsgAccount about="newsblog">
+
+ <!-- server info -->
+ <NC:incomingServer>
+ <NC:nsIMsgIncomingServer>
+ <NC:hostName>Feeds</NC:hostName>
+ <NC:type>rss</NC:type>
+ <NC:biffMinutes>100</NC:biffMinutes>
+ <NC:username>nobody</NC:username>
+ </NC:nsIMsgIncomingServer>
+ </NC:incomingServer>
+
+ <!-- identity defaults -->
+ <NC:identity>
+ <NC:nsIMsgIdentity>
+ </NC:nsIMsgIdentity>
+ </NC:identity>
+
+ <NC:wizardAutoGenerateUniqueHostname>true</NC:wizardAutoGenerateUniqueHostname>
+ <NC:wizardHideIncoming>true</NC:wizardHideIncoming>
+ <NC:wizardAccountName>&feeds.accountName;</NC:wizardAccountName>
+ <NC:wizardSkipPanels>identitypage,incomingpage,outgoingpage</NC:wizardSkipPanels>
+ <NC:wizardShortName>&feeds.wizardShortName;</NC:wizardShortName>
+ <NC:wizardLongName>&feeds.wizardLongName;</NC:wizardLongName>
+ <NC:wizardLongNameAccesskey>&feeds.wizardLongName.accesskey;</NC:wizardLongNameAccesskey>
+ <NC:wizardShow>true</NC:wizardShow>
+ <NC:emailProviderName>RSS</NC:emailProviderName>
+ <NC:showServerDetailsOnWizardSummary>false</NC:showServerDetailsOnWizardSummary>
+ </NC:nsIMsgAccount>
+ </NC:providers>
+ </RDF:Description>
+</RDF:RDF>
diff --git a/mailnews/extensions/offline-startup/js/offlineStartup.js b/mailnews/extensions/offline-startup/js/offlineStartup.js
new file mode 100644
index 000000000..56584465d
--- /dev/null
+++ b/mailnews/extensions/offline-startup/js/offlineStartup.js
@@ -0,0 +1,170 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var kDebug = false;
+var kOfflineStartupPref = "offline.startup_state";
+var kRememberLastState = 0;
+var kAskForOnlineState = 1;
+var kAlwaysOnline = 2;
+var kAlwaysOffline = 3;
+var kAutomatic = 4;
+var gStartingUp = true;
+var gOfflineStartupMode; //0 = remember last state, 1 = ask me, 2 == online, 3 == offline, 4 = automatic
+
+
+////////////////////////////////////////////////////////////////////////
+//
+// nsOfflineStartup : nsIObserver
+//
+// Check if the user has set the pref to be prompted for
+// online/offline startup mode. If so, prompt the user. Also,
+// check if the user wants to remember their offline state
+// the next time they start up.
+// If the user shutdown offline, and is now starting up in online
+// mode, we will set the boolean pref "mailnews.playback_offline" to true.
+//
+////////////////////////////////////////////////////////////////////////
+
+var nsOfflineStartup =
+{
+ onProfileStartup: function()
+ {
+ debug("onProfileStartup");
+
+ if (gStartingUp)
+ {
+ gStartingUp = false;
+ // if checked, the "work offline" checkbox overrides
+ if (Services.io.offline && !Services.io.manageOfflineStatus)
+ {
+ debug("already offline!");
+ return;
+ }
+ }
+
+ var manageOfflineStatus = Services.prefs.getBoolPref("offline.autoDetect");
+ gOfflineStartupMode = Services.prefs.getIntPref(kOfflineStartupPref);
+ let wasOffline = !Services.prefs.getBoolPref("network.online");
+
+ if (gOfflineStartupMode == kAutomatic)
+ {
+ // Offline state should be managed automatically
+ // so do nothing specific at startup.
+ }
+ else if (gOfflineStartupMode == kAlwaysOffline)
+ {
+ Services.io.manageOfflineStatus = false;
+ Services.io.offline = true;
+ }
+ else if (gOfflineStartupMode == kAlwaysOnline)
+ {
+ Services.io.manageOfflineStatus = manageOfflineStatus;
+ if (wasOffline)
+ Services.prefs.setBoolPref("mailnews.playback_offline", true);
+ // If we're managing the offline status, don't force online here... it may
+ // be the network really is offline.
+ if (!manageOfflineStatus)
+ Services.io.offline = false;
+ }
+ else if (gOfflineStartupMode == kRememberLastState)
+ {
+ Services.io.manageOfflineStatus = manageOfflineStatus && !wasOffline;
+ // If we are meant to be online, and managing the offline status
+ // then don't force it - it may be the network really is offline.
+ if (!manageOfflineStatus || wasOffline)
+ Services.io.offline = wasOffline;
+ }
+ else if (gOfflineStartupMode == kAskForOnlineState)
+ {
+ var bundle = Services.strings.createBundle("chrome://messenger/locale/offlineStartup.properties");
+ var title = bundle.GetStringFromName("title");
+ var desc = bundle.GetStringFromName("desc");
+ var button0Text = bundle.GetStringFromName("workOnline");
+ var button1Text = bundle.GetStringFromName("workOffline");
+ var checkVal = {value:0};
+
+ var result = Services.prompt.confirmEx(null, title, desc,
+ (Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING) +
+ (Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_IS_STRING),
+ button0Text, button1Text, null, null, checkVal);
+ debug ("result = " + result + "\n");
+ Services.io.manageOfflineStatus = manageOfflineStatus && result != 1;
+ Services.io.offline = result == 1;
+ if (result != 1 && wasOffline)
+ Services.prefs.setBoolPref("mailnews.playback_offline", true);
+ }
+ },
+
+ observe: function(aSubject, aTopic, aData)
+ {
+ debug("observe: " + aTopic);
+
+ if (aTopic == "profile-change-net-teardown")
+ {
+ debug("remembering offline state");
+ Services.prefs.setBoolPref("network.online", !Services.io.offline);
+ }
+ else if (aTopic == "app-startup")
+ {
+ Services.obs.addObserver(this, "profile-after-change", false);
+ Services.obs.addObserver(this, "profile-change-net-teardown", false);
+ }
+ else if (aTopic == "profile-after-change")
+ {
+ this.onProfileStartup();
+ }
+ },
+
+
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Components.interfaces.nsIObserver) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+}
+
+function nsOfflineStartupModule()
+{
+}
+
+nsOfflineStartupModule.prototype =
+{
+ classID: Components.ID("3028a3c8-2165-42a4-b878-398da5d32736"),
+ _xpcom_factory:
+ {
+ createInstance: function(aOuter, aIID)
+ {
+ if (aOuter != null)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+
+ // return the singleton
+ return nsOfflineStartup.QueryInterface(aIID);
+ },
+
+ lockFactory: function(aLock)
+ {
+ // quieten warnings
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////
+//
+// Debug helper
+//
+////////////////////////////////////////////////////////////////////////
+if (!kDebug)
+ debug = function(m) {};
+else
+ debug = function(m) {dump("\t *** nsOfflineStartup: " + m + "\n");};
+
+var components = [nsOfflineStartupModule];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/extensions/offline-startup/js/offlineStartup.manifest b/mailnews/extensions/offline-startup/js/offlineStartup.manifest
new file mode 100644
index 000000000..61df84f76
--- /dev/null
+++ b/mailnews/extensions/offline-startup/js/offlineStartup.manifest
@@ -0,0 +1,3 @@
+component {3028a3c8-2165-42a4-b878-398da5d32736} offlineStartup.js
+contract @mozilla.org/offline-startup;1 {3028a3c8-2165-42a4-b878-398da5d32736}
+category app-startup Offline-startup @mozilla.org/offline-startup;1
diff --git a/mailnews/extensions/offline-startup/moz.build b/mailnews/extensions/offline-startup/moz.build
new file mode 100644
index 000000000..a8a406295
--- /dev/null
+++ b/mailnews/extensions/offline-startup/moz.build
@@ -0,0 +1,10 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXTRA_COMPONENTS += [
+ 'js/offlineStartup.js',
+ 'js/offlineStartup.manifest',
+]
+
diff --git a/mailnews/extensions/smime/content/am-smime.js b/mailnews/extensions/smime/content/am-smime.js
new file mode 100644
index 000000000..4a90d0cd7
--- /dev/null
+++ b/mailnews/extensions/smime/content/am-smime.js
@@ -0,0 +1,478 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var nsIX509CertDB = Components.interfaces.nsIX509CertDB;
+var nsX509CertDBContractID = "@mozilla.org/security/x509certdb;1";
+var nsIX509Cert = Components.interfaces.nsIX509Cert;
+
+var email_recipient_cert_usage = 5;
+var email_signing_cert_usage = 4;
+
+var gIdentity;
+var gPref = null;
+var gEncryptionCertName = null;
+var gHiddenEncryptionPolicy = null;
+var gEncryptionChoices = null;
+var gSignCertName = null;
+var gSignMessages = null;
+var gEncryptAlways = null;
+var gNeverEncrypt = null;
+var gBundle = null;
+var gBrandBundle;
+var gSmimePrefbranch;
+var gEncryptionChoicesLocked;
+var gSigningChoicesLocked;
+var kEncryptionCertPref = "identity.encryption_cert_name";
+var kSigningCertPref = "identity.signing_cert_name";
+
+function onInit()
+{
+ smimeInitializeFields();
+}
+
+function smimeInitializeFields()
+{
+ // initialize all of our elements based on the current identity values....
+ gEncryptionCertName = document.getElementById(kEncryptionCertPref);
+ gHiddenEncryptionPolicy = document.getElementById("identity.encryptionpolicy");
+ gEncryptionChoices = document.getElementById("encryptionChoices");
+ gSignCertName = document.getElementById(kSigningCertPref);
+ gSignMessages = document.getElementById("identity.sign_mail");
+ gEncryptAlways = document.getElementById("encrypt_mail_always");
+ gNeverEncrypt = document.getElementById("encrypt_mail_never");
+ gBundle = document.getElementById("bundle_smime");
+ gBrandBundle = document.getElementById("bundle_brand");
+
+ gEncryptionChoicesLocked = false;
+ gSigningChoicesLocked = false;
+
+ if (!gIdentity) {
+ // The user is going to create a new identity.
+ // Set everything to default values.
+ // Do not take over the values from gAccount.defaultIdentity
+ // as the new identity is going to have a different mail address.
+
+ gEncryptionCertName.value = "";
+ gEncryptionCertName.nickname = "";
+ gEncryptionCertName.dbKey = "";
+ gSignCertName.value = "";
+ gSignCertName.nickname = "";
+ gSignCertName.dbKey = "";
+
+ gEncryptAlways.setAttribute("disabled", true);
+ gNeverEncrypt.setAttribute("disabled", true);
+ gSignMessages.setAttribute("disabled", true);
+
+ gSignMessages.checked = false;
+ gEncryptionChoices.value = 0;
+ }
+ else {
+ var certdb = Components.classes[nsX509CertDBContractID].getService(nsIX509CertDB);
+ var x509cert = null;
+
+ gEncryptionCertName.value = gIdentity.getUnicharAttribute("encryption_cert_name");
+ gEncryptionCertName.dbKey = gIdentity.getCharAttribute("encryption_cert_dbkey");
+ // If we succeed in looking up the certificate by the dbkey pref, then
+ // append the serial number " [...]" to the display value, and remember the
+ // nickname in a separate property.
+ try {
+ if (certdb && gEncryptionCertName.dbKey &&
+ (x509cert = certdb.findCertByDBKey(gEncryptionCertName.dbKey))) {
+ gEncryptionCertName.value = x509cert.nickname + " [" + x509cert.serialNumber + "]";
+ gEncryptionCertName.nickname = x509cert.nickname;
+ }
+ } catch(e) {}
+
+ gEncryptionChoices.value = gIdentity.getIntAttribute("encryptionpolicy");
+
+ if (!gEncryptionCertName.value) {
+ gEncryptAlways.setAttribute("disabled", true);
+ gNeverEncrypt.setAttribute("disabled", true);
+ }
+ else {
+ enableEncryptionControls(true);
+ }
+
+ gSignCertName.value = gIdentity.getUnicharAttribute("signing_cert_name");
+ gSignCertName.dbKey = gIdentity.getCharAttribute("signing_cert_dbkey");
+ x509cert = null;
+ // same procedure as with gEncryptionCertName (see above)
+ try {
+ if (certdb && gSignCertName.dbKey &&
+ (x509cert = certdb.findCertByDBKey(gSignCertName.dbKey))) {
+ gSignCertName.value = x509cert.nickname + " [" + x509cert.serialNumber + "]";
+ gSignCertName.nickname = x509cert.nickname;
+ }
+ } catch(e) {}
+
+ gSignMessages.checked = gIdentity.getBoolAttribute("sign_mail");
+ if (!gSignCertName.value)
+ {
+ gSignMessages.setAttribute("disabled", true);
+ }
+ else {
+ enableSigningControls(true);
+ }
+ }
+
+ // Always start with enabling signing and encryption cert select buttons.
+ // This will keep the visibility of buttons in a sane state as user
+ // jumps from security panel of one account to another.
+ enableCertSelectButtons();
+
+ // Disable all locked elements on the panel
+ if (gIdentity)
+ onLockPreference();
+}
+
+function onPreInit(account, accountValues)
+{
+ gIdentity = account.defaultIdentity;
+}
+
+function onSave()
+{
+ smimeSave();
+}
+
+function smimeSave()
+{
+ // find out which radio for the encryption radio group is selected and set that on our hidden encryptionChoice pref....
+ var newValue = gEncryptionChoices.value;
+ gHiddenEncryptionPolicy.setAttribute('value', newValue);
+ gIdentity.setIntAttribute("encryptionpolicy", newValue);
+ gIdentity.setUnicharAttribute("encryption_cert_name",
+ gEncryptionCertName.nickname || gEncryptionCertName.value);
+ gIdentity.setCharAttribute("encryption_cert_dbkey", gEncryptionCertName.dbKey);
+
+ gIdentity.setBoolAttribute("sign_mail", gSignMessages.checked);
+ gIdentity.setUnicharAttribute("signing_cert_name",
+ gSignCertName.nickname || gSignCertName.value);
+ gIdentity.setCharAttribute("signing_cert_dbkey", gSignCertName.dbKey);
+}
+
+function smimeOnAcceptEditor()
+{
+ try {
+ if (!onOk())
+ return false;
+ }
+ catch (ex) {}
+
+ smimeSave();
+
+ return true;
+}
+
+function onLockPreference()
+{
+ var initPrefString = "mail.identity";
+ var finalPrefString;
+
+ var allPrefElements = [
+ { prefstring:"signingCertSelectButton", id:"signingCertSelectButton"},
+ { prefstring:"encryptionCertSelectButton", id:"encryptionCertSelectButton"},
+ { prefstring:"sign_mail", id:"identity.sign_mail"},
+ { prefstring:"encryptionpolicy", id:"encryptionChoices"}
+ ];
+
+ finalPrefString = initPrefString + "." + gIdentity.key + ".";
+ gSmimePrefbranch = Services.prefs.getBranch(finalPrefString);
+
+ disableIfLocked( allPrefElements );
+}
+
+
+// Does the work of disabling an element given the array which contains xul id/prefstring pairs.
+// Also saves the id/locked state in an array so that other areas of the code can avoid
+// stomping on the disabled state indiscriminately.
+function disableIfLocked( prefstrArray )
+{
+ var i;
+ for (i=0; i<prefstrArray.length; i++) {
+ var id = prefstrArray[i].id;
+ var element = document.getElementById(id);
+ if (gSmimePrefbranch.prefIsLocked(prefstrArray[i].prefstring)) {
+ // If encryption choices radio group is locked, make sure the individual
+ // choices in the group are locked. Set a global (gEncryptionChoicesLocked)
+ // indicating the status so that locking can be maintained further.
+ if (id == "encryptionChoices") {
+ document.getElementById("encrypt_mail_never").setAttribute("disabled", "true");
+ document.getElementById("encrypt_mail_always").setAttribute("disabled", "true");
+ gEncryptionChoicesLocked = true;
+ }
+ // If option to sign mail is locked (with true/false set in config file), disable
+ // the corresponding checkbox and set a global (gSigningChoicesLocked) in order to
+ // honor the locking as user changes other elements on the panel.
+ if (id == "identity.sign_mail") {
+ document.getElementById("identity.sign_mail").setAttribute("disabled", "true");
+ gSigningChoicesLocked = true;
+ }
+ else {
+ element.setAttribute("disabled", "true");
+ if (id == "signingCertSelectButton") {
+ document.getElementById("signingCertClearButton").setAttribute("disabled", "true");
+ }
+ else if (id == "encryptionCertSelectButton") {
+ document.getElementById("encryptionCertClearButton").setAttribute("disabled", "true");
+ }
+ }
+ }
+ }
+}
+
+function alertUser(message)
+{
+ Services.prompt.alert(window,
+ gBrandBundle.getString("brandShortName"),
+ message);
+}
+
+function askUser(message)
+{
+ let button = Services.prompt.confirmEx(
+ window,
+ gBrandBundle.getString("brandShortName"),
+ message,
+ Services.prompt.STD_YES_NO_BUTTONS,
+ null,
+ null,
+ null,
+ null,
+ {});
+ // confirmEx returns button index:
+ return (button == 0);
+}
+
+function checkOtherCert(cert, pref, usage, msgNeedCertWantSame, msgWantSame, msgNeedCertWantToSelect, enabler)
+{
+ var otherCertInfo = document.getElementById(pref);
+ if (!otherCertInfo)
+ return;
+
+ if (otherCertInfo.dbKey == cert.dbKey)
+ // all is fine, same cert is now selected for both purposes
+ return;
+
+ var certdb = Components.classes[nsX509CertDBContractID].getService(nsIX509CertDB);
+ if (!certdb)
+ return;
+
+ if (email_recipient_cert_usage == usage) {
+ matchingOtherCert = certdb.findEmailEncryptionCert(cert.nickname);
+ }
+ else if (email_signing_cert_usage == usage) {
+ matchingOtherCert = certdb.findEmailSigningCert(cert.nickname);
+ }
+ else
+ return;
+
+ var userWantsSameCert = false;
+
+ if (!otherCertInfo.value.length) {
+ if (matchingOtherCert && (matchingOtherCert.dbKey == cert.dbKey)) {
+ userWantsSameCert = askUser(gBundle.getString(msgNeedCertWantSame));
+ }
+ else {
+ if (askUser(gBundle.getString(msgNeedCertWantToSelect))) {
+ smimeSelectCert(pref);
+ }
+ }
+ }
+ else {
+ if (matchingOtherCert && (matchingOtherCert.dbKey == cert.dbKey)) {
+ userWantsSameCert = askUser(gBundle.getString(msgWantSame));
+ }
+ }
+
+ if (userWantsSameCert) {
+ otherCertInfo.value = cert.nickname + " [" + cert.serialNumber + "]";
+ otherCertInfo.nickname = cert.nickname;
+ otherCertInfo.dbKey = cert.dbKey;
+ enabler(true);
+ }
+}
+
+function smimeSelectCert(smime_cert)
+{
+ var certInfo = document.getElementById(smime_cert);
+ if (!certInfo)
+ return;
+
+ var picker = Components.classes["@mozilla.org/user_cert_picker;1"]
+ .createInstance(Components.interfaces.nsIUserCertPicker);
+ var canceled = new Object;
+ var x509cert = 0;
+ var certUsage;
+ var selectEncryptionCert;
+
+ if (smime_cert == kEncryptionCertPref) {
+ selectEncryptionCert = true;
+ certUsage = email_recipient_cert_usage;
+ } else if (smime_cert == kSigningCertPref) {
+ selectEncryptionCert = false;
+ certUsage = email_signing_cert_usage;
+ }
+
+ try {
+ x509cert = picker.pickByUsage(window,
+ certInfo.value,
+ certUsage, // this is from enum SECCertUsage
+ false, true,
+ gIdentity.email,
+ canceled);
+ } catch(e) {
+ canceled.value = false;
+ x509cert = null;
+ }
+
+ if (!canceled.value) {
+ if (!x509cert) {
+ if (gIdentity.email) {
+ alertUser(gBundle.getFormattedString(selectEncryptionCert ?
+ "NoEncryptionCertForThisAddress" :
+ "NoSigningCertForThisAddress",
+ [ gIdentity.email ]));
+ } else {
+ alertUser(gBundle.getString(selectEncryptionCert ?
+ "NoEncryptionCert" : "NoSigningCert"));
+ }
+ }
+ else {
+ certInfo.removeAttribute("disabled");
+ certInfo.value = x509cert.nickname + " [" + x509cert.serialNumber + "]";
+ certInfo.nickname = x509cert.nickname;
+ certInfo.dbKey = x509cert.dbKey;
+
+ if (selectEncryptionCert) {
+ enableEncryptionControls(true);
+
+ checkOtherCert(x509cert,
+ kSigningCertPref, email_signing_cert_usage,
+ "signing_needCertWantSame",
+ "signing_wantSame",
+ "signing_needCertWantToSelect",
+ enableSigningControls);
+ } else {
+ enableSigningControls(true);
+
+ checkOtherCert(x509cert,
+ kEncryptionCertPref, email_recipient_cert_usage,
+ "encryption_needCertWantSame",
+ "encryption_wantSame",
+ "encryption_needCertWantToSelect",
+ enableEncryptionControls);
+ }
+ }
+ }
+
+ enableCertSelectButtons();
+}
+
+function enableEncryptionControls(do_enable)
+{
+ if (gEncryptionChoicesLocked)
+ return;
+
+ if (do_enable) {
+ gEncryptAlways.removeAttribute("disabled");
+ gNeverEncrypt.removeAttribute("disabled");
+ gEncryptionCertName.removeAttribute("disabled");
+ }
+ else {
+ gEncryptAlways.setAttribute("disabled", "true");
+ gNeverEncrypt.setAttribute("disabled", "true");
+ gEncryptionCertName.setAttribute("disabled", "true");
+ gEncryptionChoices.value = 0;
+ }
+}
+
+function enableSigningControls(do_enable)
+{
+ if (gSigningChoicesLocked)
+ return;
+
+ if (do_enable) {
+ gSignMessages.removeAttribute("disabled");
+ gSignCertName.removeAttribute("disabled");
+ }
+ else {
+ gSignMessages.setAttribute("disabled", "true");
+ gSignCertName.setAttribute("disabled", "true");
+ gSignMessages.checked = false;
+ }
+}
+
+function enableCertSelectButtons()
+{
+ document.getElementById("signingCertSelectButton").removeAttribute("disabled");
+
+ if (document.getElementById('identity.signing_cert_name').value.length)
+ document.getElementById("signingCertClearButton").removeAttribute("disabled");
+ else
+ document.getElementById("signingCertClearButton").setAttribute("disabled", "true");
+
+ document.getElementById("encryptionCertSelectButton").removeAttribute("disabled");
+
+ if (document.getElementById('identity.encryption_cert_name').value.length)
+ document.getElementById("encryptionCertClearButton").removeAttribute("disabled");
+ else
+ document.getElementById("encryptionCertClearButton").setAttribute("disabled", "true");
+}
+
+function smimeClearCert(smime_cert)
+{
+ var certInfo = document.getElementById(smime_cert);
+ if (!certInfo)
+ return;
+
+ certInfo.setAttribute("disabled", "true");
+ certInfo.value = "";
+ certInfo.nickname = "";
+ certInfo.dbKey = "";
+
+ if (smime_cert == kEncryptionCertPref) {
+ enableEncryptionControls(false);
+ } else if (smime_cert == kSigningCertPref) {
+ enableSigningControls(false);
+ }
+
+ enableCertSelectButtons();
+}
+
+function openCertManager()
+{
+ // Check for an existing certManager window and focus it; it's not
+ // application modal.
+ let lastCertManager = Services.wm.getMostRecentWindow("mozilla:certmanager");
+ if (lastCertManager)
+ lastCertManager.focus();
+ else
+ window.openDialog("chrome://pippki/content/certManager.xul", "",
+ "centerscreen,resizable=yes,dialog=no");
+}
+
+function openDeviceManager()
+{
+ // Check for an existing deviceManager window and focus it; it's not
+ // application modal.
+ let lastCertManager = Services.wm.getMostRecentWindow("mozilla:devicemanager");
+ if (lastCertManager)
+ lastCertManager.focus();
+ else
+ window.openDialog("chrome://pippki/content/device_manager.xul", "",
+ "centerscreen,resizable=yes,dialog=no");
+}
+
+function smimeOnLoadEditor()
+{
+ smimeInitializeFields();
+
+ document.documentElement.setAttribute("ondialogaccept",
+ "return smimeOnAcceptEditor();");
+}
+
diff --git a/mailnews/extensions/smime/content/am-smime.xul b/mailnews/extensions/smime/content/am-smime.xul
new file mode 100644
index 000000000..bb46bb49d
--- /dev/null
+++ b/mailnews/extensions/smime/content/am-smime.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/am-smimeOverlay.xul"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-smime.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="color-dialog"
+ onload="parent.onPanelLoaded('am-smime.xul');"
+ ondialogaccept="smimeOnAcceptEditor();">
+
+ <vbox flex="1" style="overflow: auto;">
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-smime.js"/>
+
+ <dialogheader title="&securityTitle.label;"/>
+
+ <vbox flex="1" id="smimeEditing"/>
+ </vbox>
+
+</page>
diff --git a/mailnews/extensions/smime/content/am-smimeIdentityEditOverlay.xul b/mailnews/extensions/smime/content/am-smimeIdentityEditOverlay.xul
new file mode 100644
index 000000000..2ff5c2b7d
--- /dev/null
+++ b/mailnews/extensions/smime/content/am-smimeIdentityEditOverlay.xul
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css"
+ type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/am-smimeOverlay.xul"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/am-smime.dtd">
+
+<!--
+ This is the overlay that adds the SMIME configurator
+ to the identity editor of the account manager
+-->
+<overlay id="smimeAmIdEditOverlay"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/am-smime.js"/>
+
+ <tabs id="identitySettings">
+ <tab label="&securityTab.label;"/>
+ </tabs>
+
+ <tabpanels id="identityTabsPanels">
+ <vbox flex="1" name="smimeEditingContent" id="smimeEditing"/>
+ </tabpanels>
+
+ <script type="application/javascript">
+ <![CDATA[
+ window.addEventListener("load", smimeOnLoadEditor, false);
+ ]]>
+ </script>
+</overlay>
diff --git a/mailnews/extensions/smime/content/am-smimeOverlay.xul b/mailnews/extensions/smime/content/am-smimeOverlay.xul
new file mode 100644
index 000000000..eb76b4b2c
--- /dev/null
+++ b/mailnews/extensions/smime/content/am-smimeOverlay.xul
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css"
+ type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/am-smime.dtd">
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <vbox id="smimeEditing">
+
+ <stringbundleset>
+ <stringbundle id="bundle_smime" src="chrome://messenger/locale/am-smime.properties"/>
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ </stringbundleset>
+
+ <label hidden="true" wsm_persist="true" id="identity.encryptionpolicy"/>
+
+ <description>&securityHeading.label;</description>
+
+ <groupbox id="signing.titlebox">
+ <caption label="&signingGroupTitle.label;"/>
+
+ <label value="&signingCert.message;" control="identity.signing_cert_name"
+ prefstring="mail.identity.%identitykey%.encryptionpolicy"/>
+
+ <hbox align="center">
+ <textbox id="identity.signing_cert_name" wsm_persist="true" flex="1"
+ prefstring="mail.identity.%identitykey%.signing_cert_name"
+ readonly="true" disabled="true"/>
+
+ <button id="signingCertSelectButton"
+ label="&digitalSign.certificate.button;"
+ accesskey="&digitalSign.certificate.accesskey;"
+ oncommand="smimeSelectCert('identity.signing_cert_name')"/>
+
+ <button id="signingCertClearButton"
+ label="&digitalSign.certificate_clear.button;"
+ accesskey="&digitalSign.certificate_clear.accesskey;"
+ oncommand="smimeClearCert('identity.signing_cert_name')"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <checkbox id="identity.sign_mail" wsm_persist="true"
+ prefstring="mail.identity.%identitykey%.sign_mail"
+ label="&signMessage.label;" accesskey="&signMessage.accesskey;"/>
+ </groupbox>
+
+ <groupbox id="encryption.titlebox">
+ <caption label="&encryptionGroupTitle.label;"/>
+
+ <label value="&encryptionCert.message;"
+ control="identity.encryption_cert_name"/>
+
+ <hbox align="center">
+ <textbox id="identity.encryption_cert_name" wsm_persist="true" flex="1"
+ prefstring="mail.identity.%identitykey%.encryption_cert_name"
+ readonly="true" disabled="true"/>
+
+ <button id="encryptionCertSelectButton"
+ label="&encryption.certificate.button;"
+ accesskey="&encryption.certificate.accesskey;"
+ oncommand="smimeSelectCert('identity.encryption_cert_name')"/>
+
+ <button id="encryptionCertClearButton"
+ label="&encryption.certificate_clear.button;"
+ accesskey="&encryption.certificate_clear.accesskey;"
+ oncommand="smimeClearCert('identity.encryption_cert_name')"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <label value="&encryptionChoiceLabel.label;" control="encryptionChoices"/>
+
+ <radiogroup id="encryptionChoices">
+ <radio id="encrypt_mail_never" wsm_persist="true" value="0"
+ label="&neverEncrypt.label;"
+ accesskey="&neverEncrypt.accesskey;"/>
+
+ <radio id="encrypt_mail_always" wsm_persist="true" value="2"
+ label="&alwaysEncryptMessage.label;"
+ accesskey="&alwaysEncryptMessage.accesskey;"/>
+ </radiogroup>
+ </groupbox>
+
+ <!-- Certificate manager -->
+ <groupbox id="smimeCertificateManager" orient="horizontal">
+ <caption label="&certificates.label;"/>
+ <button id="openCertManagerButton" oncommand="openCertManager();"
+ label="&manageCerts2.label;" accesskey="&manageCerts2.accesskey;"
+ prefstring="security.disable_button.openCertManager"/>
+ <button id="openDeviceManagerButton" oncommand="openDeviceManager();"
+ label="&manageDevices.label;" accesskey="&manageDevices.accesskey;"
+ prefstring="security.disable_button.openDeviceManager"/>
+ </groupbox>
+ </vbox>
+</overlay>
diff --git a/mailnews/extensions/smime/content/certFetchingStatus.js b/mailnews/extensions/smime/content/certFetchingStatus.js
new file mode 100644
index 000000000..8848ff9b6
--- /dev/null
+++ b/mailnews/extensions/smime/content/certFetchingStatus.js
@@ -0,0 +1,265 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* We expect the following arguments:
+ - pref name of LDAP directory to fetch from
+ - array with email addresses
+
+ Display modal dialog with message and stop button.
+ In onload, kick off binding to LDAP.
+ When bound, kick off the searches.
+ On finding certificates, import into permanent cert database.
+ When all searches are finished, close the dialog.
+*/
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var nsIX509CertDB = Components.interfaces.nsIX509CertDB;
+var nsX509CertDB = "@mozilla.org/security/x509certdb;1";
+var CertAttribute = "usercertificate;binary";
+
+var gEmailAddresses;
+var gDirectoryPref;
+var gLdapServerURL;
+var gLdapConnection;
+var gCertDB;
+var gLdapOperation;
+var gLogin;
+
+function onLoad()
+{
+ gDirectoryPref = window.arguments[0];
+ gEmailAddresses = window.arguments[1];
+
+ if (!gEmailAddresses.length)
+ {
+ window.close();
+ return;
+ }
+
+ setTimeout(search, 1);
+}
+
+function search()
+{
+ // get the login to authenticate as, if there is one
+ try {
+ gLogin = Services.prefs.getComplexValue(gDirectoryPref + ".auth.dn", Components.interfaces.nsISupportsString).data;
+ } catch (ex) {
+ // if we don't have this pref, no big deal
+ }
+
+ try {
+ let url = Services.prefs.getCharPref(gDirectoryPref + ".uri");
+
+ gLdapServerURL = Services.io
+ .newURI(url, null, null).QueryInterface(Components.interfaces.nsILDAPURL);
+
+ gLdapConnection = Components.classes["@mozilla.org/network/ldap-connection;1"]
+ .createInstance().QueryInterface(Components.interfaces.nsILDAPConnection);
+
+ gLdapConnection.init(gLdapServerURL, gLogin, new boundListener(),
+ null, Components.interfaces.nsILDAPConnection.VERSION3);
+
+ } catch (ex) {
+ dump(ex);
+ dump(" exception creating ldap connection\n");
+ window.close();
+ }
+}
+
+function stopFetching()
+{
+ if (gLdapOperation) {
+ try {
+ gLdapOperation.abandon();
+ }
+ catch (e) {
+ }
+ }
+ return true;
+}
+
+function importCert(ber_value)
+{
+ if (!gCertDB) {
+ gCertDB = Components.classes[nsX509CertDB].getService(nsIX509CertDB);
+ }
+
+ var cert_length = new Object();
+ var cert_bytes = ber_value.get(cert_length);
+
+ if (cert_bytes) {
+ gCertDB.importEmailCertificate(cert_bytes, cert_length.value, null);
+ }
+}
+
+function getLDAPOperation()
+{
+ gLdapOperation = Components.classes["@mozilla.org/network/ldap-operation;1"]
+ .createInstance().QueryInterface(Components.interfaces.nsILDAPOperation);
+
+ gLdapOperation.init(gLdapConnection,
+ new ldapMessageListener(),
+ null);
+}
+
+function getPassword()
+{
+ // we only need a password if we are using credentials
+ if (gLogin)
+ {
+ let authPrompter = Services.ww.getNewAuthPrompter(window.QueryInterface(Components.interfaces.nsIDOMWindow));
+ let strBundle = document.getElementById('bundle_ldap');
+ let password = { value: "" };
+
+ // nsLDAPAutocompleteSession uses asciiHost instead of host for the prompt text, I think we should be
+ // consistent.
+ if (authPrompter.promptPassword(strBundle.getString("authPromptTitle"),
+ strBundle.getFormattedString("authPromptText", [gLdapServerURL.asciiHost]),
+ gLdapServerURL.spec,
+ authPrompter.SAVE_PASSWORD_PERMANENTLY,
+ password))
+ return password.value;
+ }
+
+ return null;
+}
+
+function kickOffBind()
+{
+ try {
+ getLDAPOperation();
+ gLdapOperation.simpleBind(getPassword());
+ }
+ catch (e) {
+ window.close();
+ }
+}
+
+function kickOffSearch()
+{
+ try {
+ var prefix1 = "";
+ var suffix1 = "";
+
+ var urlFilter = gLdapServerURL.filter;
+
+ if (urlFilter != null && urlFilter.length > 0 && urlFilter != "(objectclass=*)") {
+ if (urlFilter.startsWith('(')) {
+ prefix1 = "(&" + urlFilter;
+ }
+ else {
+ prefix1 = "(&(" + urlFilter + ")";
+ }
+ suffix1 = ")";
+ }
+
+ var prefix2 = "";
+ var suffix2 = "";
+
+ if (gEmailAddresses.length > 1) {
+ prefix2 = "(|";
+ suffix2 = ")";
+ }
+
+ var mailFilter = "";
+
+ for (var i = 0; i < gEmailAddresses.length; ++i) {
+ mailFilter += "(mail=" + gEmailAddresses[i] + ")";
+ }
+
+ var filter = prefix1 + prefix2 + mailFilter + suffix2 + suffix1;
+
+ var wanted_attributes = CertAttribute;
+
+ // Max search results =>
+ // Double number of email addresses, because each person might have
+ // multiple certificates listed. We expect at most two certificates,
+ // one for signing, one for encrypting.
+ // Maybe that number should be larger, to allow for deployments,
+ // where even more certs can be stored per user???
+
+ var maxEntriesWanted = gEmailAddresses.length * 2;
+
+ getLDAPOperation();
+ gLdapOperation.searchExt(gLdapServerURL.dn, gLdapServerURL.scope,
+ filter, wanted_attributes, 0, maxEntriesWanted);
+ }
+ catch (e) {
+ window.close();
+ }
+}
+
+
+function boundListener() {
+}
+
+boundListener.prototype.QueryInterface =
+ function(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsILDAPMessageListener))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+
+boundListener.prototype.onLDAPMessage =
+ function(aMessage) {
+ }
+
+boundListener.prototype.onLDAPInit =
+ function(aConn, aStatus) {
+ kickOffBind();
+ }
+
+
+function ldapMessageListener() {
+}
+
+ldapMessageListener.prototype.QueryInterface =
+ function(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsILDAPMessageListener))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+
+ldapMessageListener.prototype.onLDAPMessage =
+ function(aMessage) {
+ if (Components.interfaces.nsILDAPMessage.RES_SEARCH_RESULT == aMessage.type) {
+ window.close();
+ return;
+ }
+
+ if (Components.interfaces.nsILDAPMessage.RES_BIND == aMessage.type) {
+ if (Components.interfaces.nsILDAPErrors.SUCCESS != aMessage.errorCode) {
+ window.close();
+ }
+ else {
+ kickOffSearch();
+ }
+ return;
+ }
+
+ if (Components.interfaces.nsILDAPMessage.RES_SEARCH_ENTRY == aMessage.type) {
+ var outSize = new Object();
+ try {
+ var outBinValues = aMessage.getBinaryValues(CertAttribute, outSize);
+
+ var i;
+ for (i=0; i < outSize.value; ++i) {
+ importCert(outBinValues[i]);
+ }
+ }
+ catch (e) {
+ }
+ return;
+ }
+ }
+
+ldapMessageListener.prototype.onLDAPInit =
+ function(aConn, aStatus) {
+ }
diff --git a/mailnews/extensions/smime/content/certFetchingStatus.xul b/mailnews/extensions/smime/content/certFetchingStatus.xul
new file mode 100644
index 000000000..29b824fc9
--- /dev/null
+++ b/mailnews/extensions/smime/content/certFetchingStatus.xul
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/smime/certFetchingStatus.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger-smime/locale/certFetchingStatus.dtd">
+
+<dialog id="certFetchingStatus" title="&title.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 50em;"
+ buttons="cancel"
+ buttonlabelcancel="&stop.label;"
+ ondialogcancel="return stopFetching();"
+ onload="onLoad();">
+
+ <stringbundle id="bundle_ldap" src="chrome://mozldap/locale/ldap.properties"/>
+<script type="application/javascript" src="chrome://messenger-smime/content/certFetchingStatus.js"/>
+
+ <description>&info.message;</description>
+
+</dialog>
diff --git a/mailnews/extensions/smime/content/certpicker.js b/mailnews/extensions/smime/content/certpicker.js
new file mode 100644
index 000000000..19554066f
--- /dev/null
+++ b/mailnews/extensions/smime/content/certpicker.js
@@ -0,0 +1,73 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
+
+var dialogParams;
+var itemCount = 0;
+
+function onLoad()
+{
+ dialogParams = window.arguments[0].QueryInterface(nsIDialogParamBlock);
+
+ var selectElement = document.getElementById("nicknames");
+ itemCount = dialogParams.GetInt(0);
+
+ var selIndex = dialogParams.GetInt(1);
+ if (selIndex < 0) {
+ selIndex = 0;
+ }
+
+ for (let i = 0; i < itemCount; i++) {
+ let menuItemNode = document.createElement("menuitem");
+ let nick = dialogParams.GetString(i);
+ menuItemNode.setAttribute("value", i);
+ menuItemNode.setAttribute("label", nick); // This is displayed.
+ selectElement.firstChild.appendChild(menuItemNode);
+
+ if (selIndex == i) {
+ selectElement.selectedItem = menuItemNode;
+ }
+ }
+
+ dialogParams.SetInt(0, 0); // Set cancel return value.
+ setDetails();
+}
+
+function setDetails()
+{
+ let selItem = document.getElementById("nicknames").value;
+ if (selItem.length == 0) {
+ return;
+ }
+
+ let index = parseInt(selItem);
+ let details = dialogParams.GetString(index + itemCount);
+ document.getElementById("details").value = details;
+}
+
+function onCertSelected()
+{
+ setDetails();
+}
+
+function doOK()
+{
+ // Signal that the user accepted.
+ dialogParams.SetInt(0, 1);
+
+ // Signal the index of the selected cert in the list of cert nicknames
+ // provided.
+ let index = parseInt(document.getElementById("nicknames").value);
+ dialogParams.SetInt(1, index);
+ return true;
+}
+
+function doCancel()
+{
+ dialogParams.SetInt(0, 0); // Signal that the user cancelled.
+ return true;
+}
diff --git a/mailnews/extensions/smime/content/certpicker.xul b/mailnews/extensions/smime/content/certpicker.xul
new file mode 100644
index 000000000..2c4cd3b22
--- /dev/null
+++ b/mailnews/extensions/smime/content/certpicker.xul
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % amSMIMEDTD SYSTEM "chrome://messenger/locale/am-smime.dtd" >
+%amSMIMEDTD;
+]>
+
+<dialog id="certPicker" title="&certPicker.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 50em;"
+ buttons="accept,cancel"
+ ondialogaccept="return doOK();"
+ ondialogcancel="return doCancel();"
+ onload="onLoad();">
+
+<script type="application/javascript"
+ src="chrome://messenger/content/certpicker.js"/>
+
+ <hbox align="center">
+ <broadcaster id="certSelected" oncommand="onCertSelected();"/>
+ <label id="pickerInfo" value="&certPicker.info;"/>
+ <!-- The items in this menulist must never be sorted,
+ but remain in the order filled by the application
+ -->
+ <menulist id="nicknames" observes="certSelected">
+ <menupopup/>
+ </menulist>
+ </hbox>
+ <separator class="thin"/>
+ <label value="&certPicker.detailsLabel;"/>
+ <textbox readonly="true" id="details" multiline="true"
+ style="height: 12em;" flex="1"/>
+</dialog>
diff --git a/mailnews/extensions/smime/content/msgCompSMIMEOverlay.js b/mailnews/extensions/smime/content/msgCompSMIMEOverlay.js
new file mode 100644
index 000000000..582a073ea
--- /dev/null
+++ b/mailnews/extensions/smime/content/msgCompSMIMEOverlay.js
@@ -0,0 +1,357 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// Account encryption policy values:
+// const kEncryptionPolicy_Never = 0;
+// 'IfPossible' was used by ns4.
+// const kEncryptionPolicy_IfPossible = 1;
+var kEncryptionPolicy_Always = 2;
+
+var gEncryptedURIService =
+ Components.classes["@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"]
+ .getService(Components.interfaces.nsIEncryptedSMIMEURIsService);
+
+var gNextSecurityButtonCommand = "";
+var gSMFields = null;
+var gEncryptOptionChanged;
+var gSignOptionChanged;
+
+function onComposerLoad()
+{
+ // Are we already set up ? Or are the required fields missing ?
+ if (gSMFields || !gMsgCompose || !gMsgCompose.compFields)
+ return;
+
+ gMsgCompose.compFields.securityInfo = null;
+
+ gSMFields = Components.classes["@mozilla.org/messenger-smime/composefields;1"]
+ .createInstance(Components.interfaces.nsIMsgSMIMECompFields);
+ if (!gSMFields)
+ return;
+
+ gMsgCompose.compFields.securityInfo = gSMFields;
+
+ // Set up the intial security state.
+ gSMFields.requireEncryptMessage =
+ gCurrentIdentity.getIntAttribute("encryptionpolicy") == kEncryptionPolicy_Always;
+ if (!gSMFields.requireEncryptMessage &&
+ gEncryptedURIService &&
+ gEncryptedURIService.isEncrypted(gMsgCompose.originalMsgURI))
+ {
+ // Override encryption setting if original is known as encrypted.
+ gSMFields.requireEncryptMessage = true;
+ }
+ if (gSMFields.requireEncryptMessage)
+ setEncryptionUI();
+ else
+ setNoEncryptionUI();
+
+ gSMFields.signMessage = gCurrentIdentity.getBoolAttribute("sign_mail");
+ if (gSMFields.signMessage)
+ setSignatureUI();
+ else
+ setNoSignatureUI();
+}
+
+addEventListener("load", smimeComposeOnLoad, {capture: false, once: true});
+
+// this function gets called multiple times
+function smimeComposeOnLoad()
+{
+ onComposerLoad();
+
+ top.controllers.appendController(SecurityController);
+
+ addEventListener("compose-from-changed", onComposerFromChanged, true);
+ addEventListener("compose-send-message", onComposerSendMessage, true);
+
+ addEventListener("unload", smimeComposeOnUnload, {capture: false, once: true});
+}
+
+function smimeComposeOnUnload()
+{
+ removeEventListener("compose-from-changed", onComposerFromChanged, true);
+ removeEventListener("compose-send-message", onComposerSendMessage, true);
+
+ top.controllers.removeController(SecurityController);
+}
+
+function showNeedSetupInfo()
+{
+ let compSmimeBundle = document.getElementById("bundle_comp_smime");
+ let brandBundle = document.getElementById("bundle_brand");
+ if (!compSmimeBundle || !brandBundle)
+ return;
+
+ let buttonPressed = Services.prompt.confirmEx(window,
+ brandBundle.getString("brandShortName"),
+ compSmimeBundle.getString("NeedSetup"),
+ Services.prompt.STD_YES_NO_BUTTONS, 0, 0, 0, null, {});
+ if (buttonPressed == 0)
+ openHelp("sign-encrypt", "chrome://communicator/locale/help/suitehelp.rdf");
+}
+
+function toggleEncryptMessage()
+{
+ if (!gSMFields)
+ return;
+
+ gSMFields.requireEncryptMessage = !gSMFields.requireEncryptMessage;
+
+ if (gSMFields.requireEncryptMessage)
+ {
+ // Make sure we have a cert.
+ if (!gCurrentIdentity.getUnicharAttribute("encryption_cert_name"))
+ {
+ gSMFields.requireEncryptMessage = false;
+ showNeedSetupInfo();
+ return;
+ }
+
+ setEncryptionUI();
+ }
+ else
+ {
+ setNoEncryptionUI();
+ }
+
+ gEncryptOptionChanged = true;
+}
+
+function toggleSignMessage()
+{
+ if (!gSMFields)
+ return;
+
+ gSMFields.signMessage = !gSMFields.signMessage;
+
+ if (gSMFields.signMessage) // make sure we have a cert name...
+ {
+ if (!gCurrentIdentity.getUnicharAttribute("signing_cert_name"))
+ {
+ gSMFields.signMessage = false;
+ showNeedSetupInfo();
+ return;
+ }
+
+ setSignatureUI();
+ }
+ else
+ {
+ setNoSignatureUI();
+ }
+
+ gSignOptionChanged = true;
+}
+
+function setSecuritySettings(menu_id)
+{
+ if (!gSMFields)
+ return;
+
+ document.getElementById("menu_securityEncryptRequire" + menu_id)
+ .setAttribute("checked", gSMFields.requireEncryptMessage);
+ document.getElementById("menu_securitySign" + menu_id)
+ .setAttribute("checked", gSMFields.signMessage);
+}
+
+function setNextCommand(what)
+{
+ gNextSecurityButtonCommand = what;
+}
+
+function doSecurityButton()
+{
+ var what = gNextSecurityButtonCommand;
+ gNextSecurityButtonCommand = "";
+
+ switch (what)
+ {
+ case "encryptMessage":
+ toggleEncryptMessage();
+ break;
+
+ case "signMessage":
+ toggleSignMessage();
+ break;
+
+ case "show":
+ default:
+ showMessageComposeSecurityStatus();
+ }
+}
+
+function setNoSignatureUI()
+{
+ top.document.getElementById("securityStatus").removeAttribute("signing");
+ top.document.getElementById("signing-status").collapsed = true;
+}
+
+function setSignatureUI()
+{
+ top.document.getElementById("securityStatus").setAttribute("signing", "ok");
+ top.document.getElementById("signing-status").collapsed = false;
+}
+
+function setNoEncryptionUI()
+{
+ top.document.getElementById("securityStatus").removeAttribute("crypto");
+ top.document.getElementById("encryption-status").collapsed = true;
+}
+
+function setEncryptionUI()
+{
+ top.document.getElementById("securityStatus").setAttribute("crypto", "ok");
+ top.document.getElementById("encryption-status").collapsed = false;
+}
+
+function showMessageComposeSecurityStatus()
+{
+ Recipients2CompFields(gMsgCompose.compFields);
+
+ window.openDialog(
+ "chrome://messenger-smime/content/msgCompSecurityInfo.xul",
+ "",
+ "chrome,modal,resizable,centerscreen",
+ {
+ compFields : gMsgCompose.compFields,
+ subject : GetMsgSubjectElement().value,
+ smFields : gSMFields,
+ isSigningCertAvailable :
+ gCurrentIdentity.getUnicharAttribute("signing_cert_name") != "",
+ isEncryptionCertAvailable :
+ gCurrentIdentity.getUnicharAttribute("encryption_cert_name") != "",
+ currentIdentity : gCurrentIdentity
+ }
+ );
+}
+
+var SecurityController =
+{
+ supportsCommand: function(command)
+ {
+ switch (command)
+ {
+ case "cmd_viewSecurityStatus":
+ return true;
+
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(command)
+ {
+ switch (command)
+ {
+ case "cmd_viewSecurityStatus":
+ return true;
+
+ default:
+ return false;
+ }
+ }
+};
+
+function onComposerSendMessage()
+{
+ let missingCount = new Object();
+ let emailAddresses = new Object();
+
+ try
+ {
+ if (!gMsgCompose.compFields.securityInfo.requireEncryptMessage)
+ return;
+
+ Components.classes["@mozilla.org/messenger-smime/smimejshelper;1"]
+ .createInstance(Components.interfaces.nsISMimeJSHelper)
+ .getNoCertAddresses(gMsgCompose.compFields,
+ missingCount,
+ emailAddresses);
+ }
+ catch (e)
+ {
+ return;
+ }
+
+ if (missingCount.value > 0)
+ {
+ // The rules here: If the current identity has a directoryServer set, then
+ // use that, otherwise, try the global preference instead.
+
+ let autocompleteDirectory;
+
+ // Does the current identity override the global preference?
+ if (gCurrentIdentity.overrideGlobalPref)
+ {
+ autocompleteDirectory = gCurrentIdentity.directoryServer;
+ }
+ else
+ {
+ // Try the global one
+ if (Services.prefs.getBoolPref("ldap_2.autoComplete.useDirectory"))
+ autocompleteDirectory =
+ Services.prefs.getCharPref("ldap_2.autoComplete.directoryServer");
+ }
+
+ if (autocompleteDirectory)
+ window.openDialog("chrome://messenger-smime/content/certFetchingStatus.xul",
+ "",
+ "chrome,modal,resizable,centerscreen",
+ autocompleteDirectory,
+ emailAddresses.value);
+ }
+}
+
+function onComposerFromChanged()
+{
+ if (!gSMFields)
+ return;
+
+ var encryptionPolicy = gCurrentIdentity.getIntAttribute("encryptionpolicy");
+ var useEncryption = false;
+
+ if (!gEncryptOptionChanged)
+ {
+ // Encryption wasn't manually checked.
+ // Set up the encryption policy from the setting of the new identity.
+
+ // 0 == never, 1 == if possible (ns4), 2 == always encrypt.
+ useEncryption = (encryptionPolicy == kEncryptionPolicy_Always);
+ }
+ else
+ {
+ useEncryption = !!gCurrentIdentity.getUnicharAttribute("encryption_cert_name");
+ }
+
+ gSMFields.requireEncryptMessage = useEncryption;
+ if (useEncryption)
+ setEncryptionUI();
+ else
+ setNoEncryptionUI();
+
+ // - If signing is disabled, we will not turn it on automatically.
+ // - If signing is enabled, but the new account defaults to not sign, we will turn signing off.
+ var signMessage = gCurrentIdentity.getBoolAttribute("sign_mail");
+ var useSigning = false;
+
+ if (!gSignOptionChanged)
+ {
+ // Signing wasn't manually checked.
+ // Set up the signing policy from the setting of the new identity.
+ useSigning = signMessage;
+ }
+ else
+ {
+ useSigning = !!gCurrentIdentity.getUnicharAttribute("signing_cert_name");
+ }
+ gSMFields.signMessage = useSigning;
+ if (useSigning)
+ setSignatureUI();
+ else
+ setNoSignatureUI();
+}
diff --git a/mailnews/extensions/smime/content/msgCompSMIMEOverlay.xul b/mailnews/extensions/smime/content/msgCompSMIMEOverlay.xul
new file mode 100644
index 000000000..ec6495e20
--- /dev/null
+++ b/mailnews/extensions/smime/content/msgCompSMIMEOverlay.xul
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSMIMEOverlay.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger-smime/locale/msgCompSMIMEOverlay.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://messenger-smime/content/msgCompSMIMEOverlay.js"/>
+
+ <window id="msgcomposeWindow">
+ <broadcaster id="securityStatus" crypto="" signing=""/>
+ <observes element="securityStatus" attribute="crypto" />
+ <observes element="securityStatus" attribute="signing" />
+ <stringbundle id="bundle_comp_smime" src="chrome://messenger-smime/locale/msgCompSMIMEOverlay.properties"/>
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ </window>
+
+ <menupopup id="optionsMenuPopup"
+ onpopupshowing="setSecuritySettings(1);">
+ <menuseparator id="smimeOptionsSeparator"/>
+
+ <menuitem id="menu_securityEncryptRequire1"
+ type="checkbox"
+ label="&menu_securityEncryptRequire.label;"
+ accesskey="&menu_securityEncryptRequire.accesskey;"
+ oncommand="toggleEncryptMessage();"/>
+ <menuitem id="menu_securitySign1"
+ type="checkbox"
+ label="&menu_securitySign.label;"
+ accesskey="&menu_securitySign.accesskey;"
+ oncommand="toggleSignMessage();"/>
+ </menupopup>
+
+ <toolbarpalette id="MsgComposeToolbarPalette">
+ <toolbarbutton id="button-security"
+ type="menu-button"
+ class="toolbarbutton-1"
+ label="&securityButton.label;"
+ tooltiptext="&securityButton.tooltip;"
+ oncommand="doSecurityButton();">
+ <menupopup onpopupshowing="setSecuritySettings(2);">
+ <menuitem id="menu_securityEncryptRequire2"
+ type="checkbox"
+ label="&menu_securityEncryptRequire.label;"
+ accesskey="&menu_securityEncryptRequire.accesskey;"
+ oncommand="setNextCommand('encryptMessage');"/>
+ <menuitem id="menu_securitySign2"
+ type="checkbox"
+ label="&menu_securitySign.label;"
+ accesskey="&menu_securitySign.accesskey;"
+ oncommand="setNextCommand('signMessage');"/>
+ <menuseparator id="smimeToolbarButtonSeparator"/>
+ <menuitem id="menu_securityStatus2"
+ label="&menu_securityStatus.label;"
+ accesskey="&menu_securityStatus.accesskey;"
+ oncommand="setNextCommand('show');"/>
+ </menupopup>
+ </toolbarbutton>
+ </toolbarpalette>
+
+ <statusbar id="status-bar">
+ <statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic" collapsed="true"
+ id="signing-status" oncommand="showMessageComposeSecurityStatus();"/>
+ <statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic" collapsed="true"
+ id="encryption-status" oncommand="showMessageComposeSecurityStatus();"/>
+ </statusbar>
+
+ <commandset id="composeCommands">
+ <command id="cmd_viewSecurityStatus" oncommand="showMessageComposeSecurityStatus();"/>
+ </commandset>
+
+ <menupopup id="menu_View_Popup">
+ <menuseparator id="viewMenuBeforeSecurityStatusSeparator"/>
+ <menuitem id="menu_viewSecurityStatus"
+ label="&menu_viewSecurityStatus.label;"
+ accesskey="&menu_viewSecurityStatus.accesskey;"
+ command="cmd_viewSecurityStatus"/>
+ </menupopup>
+
+</overlay>
diff --git a/mailnews/extensions/smime/content/msgCompSecurityInfo.js b/mailnews/extensions/smime/content/msgCompSecurityInfo.js
new file mode 100644
index 000000000..5a2a7432f
--- /dev/null
+++ b/mailnews/extensions/smime/content/msgCompSecurityInfo.js
@@ -0,0 +1,244 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gListBox;
+var gViewButton;
+var gBundle;
+
+var gEmailAddresses;
+var gCertStatusSummaries;
+var gCertIssuedInfos;
+var gCertExpiresInfos;
+var gCerts;
+var gCount;
+
+var gSMimeContractID = "@mozilla.org/messenger-smime/smimejshelper;1";
+var gISMimeJSHelper = Components.interfaces.nsISMimeJSHelper;
+var gIX509Cert = Components.interfaces.nsIX509Cert;
+var nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
+var nsCertificateDialogs = "@mozilla.org/nsCertificateDialogs;1"
+
+function getStatusExplanation(value)
+{
+ switch (value)
+ {
+ case gIX509Cert.VERIFIED_OK:
+ return gBundle.getString("StatusValid");
+
+ case gIX509Cert.NOT_VERIFIED_UNKNOWN:
+ case gIX509Cert.INVALID_CA:
+ case gIX509Cert.USAGE_NOT_ALLOWED:
+ return gBundle.getString("StatusInvalid");
+
+ case gIX509Cert.CERT_REVOKED:
+ return gBundle.getString("StatusRevoked");
+
+ case gIX509Cert.CERT_EXPIRED:
+ return gBundle.getString("StatusExpired");
+
+ case gIX509Cert.CERT_NOT_TRUSTED:
+ case gIX509Cert.ISSUER_NOT_TRUSTED:
+ case gIX509Cert.ISSUER_UNKNOWN:
+ return gBundle.getString("StatusUntrusted");
+ }
+
+ return "";
+}
+
+function onLoad()
+{
+ var params = window.arguments[0];
+ if (!params)
+ return;
+
+ var helper = Components.classes[gSMimeContractID].createInstance(gISMimeJSHelper);
+
+ if (!helper)
+ return;
+
+ gListBox = document.getElementById("infolist");
+ gViewButton = document.getElementById("viewCertButton");
+ gBundle = document.getElementById("bundle_smime_comp_info");
+
+ gEmailAddresses = new Object();
+ gCertStatusSummaries = new Object();
+ gCertIssuedInfos = new Object();
+ gCertExpiresInfos = new Object();
+ gCerts = new Object();
+ gCount = new Object();
+ var canEncrypt = new Object();
+
+ var allow_ldap_cert_fetching = false;
+
+ try {
+ if (params.compFields.securityInfo.requireEncryptMessage) {
+ allow_ldap_cert_fetching = true;
+ }
+ }
+ catch (e)
+ {
+ }
+
+ while (true)
+ {
+ try
+ {
+ helper.getRecipientCertsInfo(
+ params.compFields,
+ gCount,
+ gEmailAddresses,
+ gCertStatusSummaries,
+ gCertIssuedInfos,
+ gCertExpiresInfos,
+ gCerts,
+ canEncrypt);
+ }
+ catch (e)
+ {
+ dump(e);
+ return;
+ }
+
+ if (!allow_ldap_cert_fetching)
+ break;
+
+ allow_ldap_cert_fetching = false;
+
+ var missing = new Array();
+
+ for (var j = gCount.value - 1; j >= 0; --j)
+ {
+ if (!gCerts.value[j])
+ {
+ missing[missing.length] = gEmailAddresses.value[j];
+ }
+ }
+
+ if (missing.length > 0)
+ {
+ var autocompleteLdap = Services.prefs
+ .getBoolPref("ldap_2.autoComplete.useDirectory");
+
+ if (autocompleteLdap)
+ {
+ var autocompleteDirectory = null;
+ if (params.currentIdentity.overrideGlobalPref) {
+ autocompleteDirectory = params.currentIdentity.directoryServer;
+ } else {
+ autocompleteDirectory = Services.prefs
+ .getCharPref("ldap_2.autoComplete.directoryServer");
+ }
+
+ if (autocompleteDirectory)
+ {
+ window.openDialog('chrome://messenger-smime/content/certFetchingStatus.xul',
+ '',
+ 'chrome,resizable=1,modal=1,dialog=1',
+ autocompleteDirectory,
+ missing
+ );
+ }
+ }
+ }
+ }
+
+ if (gBundle)
+ {
+ var yes_string = gBundle.getString("StatusYes");
+ var no_string = gBundle.getString("StatusNo");
+ var not_possible_string = gBundle.getString("StatusNotPossible");
+
+ var signed_element = document.getElementById("signed");
+ var encrypted_element = document.getElementById("encrypted");
+
+ if (params.smFields.requireEncryptMessage)
+ {
+ if (params.isEncryptionCertAvailable && canEncrypt.value)
+ {
+ encrypted_element.value = yes_string;
+ }
+ else
+ {
+ encrypted_element.value = not_possible_string;
+ }
+ }
+ else
+ {
+ encrypted_element.value = no_string;
+ }
+
+ if (params.smFields.signMessage)
+ {
+ if (params.isSigningCertAvailable)
+ {
+ signed_element.value = yes_string;
+ }
+ else
+ {
+ signed_element.value = not_possible_string;
+ }
+ }
+ else
+ {
+ signed_element.value = no_string;
+ }
+ }
+
+ var imax = gCount.value;
+
+ for (var i = 0; i < imax; ++i)
+ {
+ var listitem = document.createElement("listitem");
+
+ listitem.appendChild(createCell(gEmailAddresses.value[i]));
+
+ if (!gCerts.value[i])
+ {
+ listitem.appendChild(createCell(gBundle.getString("StatusNotFound")));
+ }
+ else
+ {
+ listitem.appendChild(createCell(getStatusExplanation(gCertStatusSummaries.value[i])));
+ listitem.appendChild(createCell(gCertIssuedInfos.value[i]));
+ listitem.appendChild(createCell(gCertExpiresInfos.value[i]));
+ }
+
+ gListBox.appendChild(listitem);
+ }
+}
+
+function onSelectionChange(event)
+{
+ gViewButton.disabled = !(gListBox.selectedItems.length == 1 &&
+ certForRow(gListBox.selectedIndex));
+}
+
+function viewCertHelper(parent, cert) {
+ var cd = Components.classes[nsCertificateDialogs].getService(nsICertificateDialogs);
+ cd.viewCert(parent, cert);
+}
+
+function certForRow(aRowIndex) {
+ return gCerts.value[aRowIndex];
+}
+
+function viewSelectedCert()
+{
+ if (!gViewButton.disabled)
+ viewCertHelper(window, certForRow(gListBox.selectedIndex));
+}
+
+function doHelpButton()
+{
+ openHelp('compose_security', 'chrome://communicator/locale/help/suitehelp.rdf');
+}
+
+function createCell(label)
+{
+ var cell = document.createElement("listcell");
+ cell.setAttribute("label", label)
+ return cell;
+}
diff --git a/mailnews/extensions/smime/content/msgCompSecurityInfo.xul b/mailnews/extensions/smime/content/msgCompSecurityInfo.xul
new file mode 100644
index 000000000..c8769d621
--- /dev/null
+++ b/mailnews/extensions/smime/content/msgCompSecurityInfo.xul
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSecurityInfo.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger-smime/locale/msgCompSecurityInfo.dtd">
+
+<dialog id="msgCompSecurityInfo" title="&title.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 50em;"
+ persist="width height"
+ buttons="accept"
+ onload="onLoad();">
+
+ <script type="application/javascript" src="chrome://messenger-smime/content/msgCompSecurityInfo.js"/>
+
+ <stringbundle id="bundle_smime_comp_info" src="chrome://messenger-smime/locale/msgCompSecurityInfo.properties"/>
+
+ <description>&subject.plaintextWarning;</description>
+ <separator class="thin"/>
+ <description>&status.heading;</description>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row>
+ <label value="&status.signed;"/>
+ <label id="signed"/>
+ </row>
+ <row>
+ <label value="&status.encrypted;"/>
+ <label id="encrypted"/>
+ </row>
+ </rows>
+ </grid>
+
+ <separator class="thin"/>
+ <label value="&status.certificates;" control="infolist"/>
+
+ <listbox id="infolist" flex="1"
+ onselect="onSelectionChange(event);">
+ <listcols>
+ <listcol flex="3" width="0"/>
+ <splitter class="tree-splitter"/>
+ <listcol flex="1" width="0"/>
+ <splitter class="tree-splitter"/>
+ <listcol flex="2" width="0"/>
+ <splitter class="tree-splitter"/>
+ <listcol flex="2" width="0"/>
+ </listcols>
+ <listhead>
+ <listheader label="&tree.recipient;"/>
+ <listheader label="&tree.status;"/>
+ <listheader label="&tree.issuedDate;"/>
+ <listheader label="&tree.expiresDate;"/>
+ </listhead>
+ </listbox>
+ <hbox pack="start">
+ <button id="viewCertButton" disabled="true"
+ label="&view.label;" accesskey="&view.accesskey;"
+ oncommand="viewSelectedCert();"/>
+ </hbox>
+</dialog>
diff --git a/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.js b/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.js
new file mode 100644
index 000000000..2d9469d6c
--- /dev/null
+++ b/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.js
@@ -0,0 +1,264 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gSignedUINode = null;
+var gEncryptedUINode = null;
+var gSMIMEContainer = null;
+var gStatusBar = null;
+var gSignedStatusPanel = null;
+var gEncryptedStatusPanel = null;
+
+var gEncryptedURIService = null;
+var gMyLastEncryptedURI = null;
+
+var gSMIMEBundle = null;
+// var gBrandBundle; -- defined in mailWindow.js
+
+// manipulates some globals from msgReadSMIMEOverlay.js
+
+var nsICMSMessageErrors = Components.interfaces.nsICMSMessageErrors;
+
+/// Get the necko URL for the message URI.
+function neckoURLForMessageURI(aMessageURI)
+{
+ let msgSvc = Components.classes["@mozilla.org/messenger;1"]
+ .createInstance(Components.interfaces.nsIMessenger)
+ .messageServiceFromURI(aMessageURI);
+ let neckoURI = {};
+ msgSvc.GetUrlForUri(aMessageURI, neckoURI, null);
+ return neckoURI.value.spec;
+}
+
+var smimeHeaderSink =
+{
+ maxWantedNesting: function()
+ {
+ return 1;
+ },
+
+ signedStatus: function(aNestingLevel, aSignatureStatus, aSignerCert)
+ {
+ if (aNestingLevel > 1) {
+ // we are not interested
+ return;
+ }
+
+ gSignatureStatus = aSignatureStatus;
+ gSignerCert = aSignerCert;
+
+ gSMIMEContainer.collapsed = false;
+ gSignedUINode.collapsed = false;
+ gSignedStatusPanel.collapsed = false;
+
+ switch (aSignatureStatus) {
+ case nsICMSMessageErrors.SUCCESS:
+ gSignedUINode.setAttribute("signed", "ok");
+ gStatusBar.setAttribute("signed", "ok");
+ break;
+
+ case nsICMSMessageErrors.VERIFY_NOT_YET_ATTEMPTED:
+ gSignedUINode.setAttribute("signed", "unknown");
+ gStatusBar.setAttribute("signed", "unknown");
+ break;
+
+ case nsICMSMessageErrors.VERIFY_CERT_WITHOUT_ADDRESS:
+ case nsICMSMessageErrors.VERIFY_HEADER_MISMATCH:
+ gSignedUINode.setAttribute("signed", "mismatch");
+ gStatusBar.setAttribute("signed", "mismatch");
+ break;
+
+ default:
+ gSignedUINode.setAttribute("signed", "notok");
+ gStatusBar.setAttribute("signed", "notok");
+ break;
+ }
+ },
+
+ encryptionStatus: function(aNestingLevel, aEncryptionStatus, aRecipientCert)
+ {
+ if (aNestingLevel > 1) {
+ // we are not interested
+ return;
+ }
+
+ gEncryptionStatus = aEncryptionStatus;
+ gEncryptionCert = aRecipientCert;
+
+ gSMIMEContainer.collapsed = false;
+ gEncryptedUINode.collapsed = false;
+ gEncryptedStatusPanel.collapsed = false;
+
+ if (nsICMSMessageErrors.SUCCESS == aEncryptionStatus)
+ {
+ gEncryptedUINode.setAttribute("encrypted", "ok");
+ gStatusBar.setAttribute("encrypted", "ok");
+ }
+ else
+ {
+ gEncryptedUINode.setAttribute("encrypted", "notok");
+ gStatusBar.setAttribute("encrypted", "notok");
+ }
+
+ if (gEncryptedURIService)
+ {
+ // Remember the message URI and the corresponding necko URI.
+ gMyLastEncryptedURI = GetLoadedMessage();
+ gEncryptedURIService.rememberEncrypted(gMyLastEncryptedURI);
+ gEncryptedURIService.rememberEncrypted(
+ neckoURLForMessageURI(gMyLastEncryptedURI));
+ }
+
+ switch (aEncryptionStatus)
+ {
+ case nsICMSMessageErrors.SUCCESS:
+ case nsICMSMessageErrors.ENCRYPT_INCOMPLETE:
+ break;
+ default:
+ var brand = gBrandBundle.getString("brandShortName");
+ var title = gSMIMEBundle.getString("CantDecryptTitle").replace(/%brand%/g, brand);
+ var body = gSMIMEBundle.getString("CantDecryptBody").replace(/%brand%/g, brand);
+
+ // insert our message
+ msgWindow.displayHTMLInMessagePane(title,
+ "<html>\n" +
+ "<body bgcolor=\"#fafaee\">\n" +
+ "<center><br><br><br>\n" +
+ "<table>\n" +
+ "<tr><td>\n" +
+ "<center><strong><font size=\"+3\">\n" +
+ title+"</font></center><br>\n" +
+ body+"\n" +
+ "</td></tr></table></center></body></html>", false);
+ }
+ },
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Components.interfaces.nsIMsgSMIMEHeaderSink) || iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+};
+
+function forgetEncryptedURI()
+{
+ if (gMyLastEncryptedURI && gEncryptedURIService)
+ {
+ gEncryptedURIService.forgetEncrypted(gMyLastEncryptedURI);
+ gEncryptedURIService.forgetEncrypted(
+ neckoURLForMessageURI(gMyLastEncryptedURI));
+ gMyLastEncryptedURI = null;
+ }
+}
+
+function onSMIMEStartHeaders()
+{
+ gEncryptionStatus = -1;
+ gSignatureStatus = -1;
+
+ gSignerCert = null;
+ gEncryptionCert = null;
+
+ gSMIMEContainer.collapsed = true;
+
+ gSignedUINode.collapsed = true;
+ gSignedUINode.removeAttribute("signed");
+ gSignedStatusPanel.collapsed = true;
+ gStatusBar.removeAttribute("signed");
+
+ gEncryptedUINode.collapsed = true;
+ gEncryptedUINode.removeAttribute("encrypted");
+ gEncryptedStatusPanel.collapsed = true;
+ gStatusBar.removeAttribute("encrypted");
+
+ forgetEncryptedURI();
+}
+
+function onSMIMEEndHeaders()
+{}
+
+function onSmartCardChange()
+{
+ // only reload encrypted windows
+ if (gMyLastEncryptedURI && gEncryptionStatus != -1)
+ ReloadMessage();
+}
+
+function msgHdrViewSMIMEOnLoad(event)
+{
+ window.crypto.enableSmartCardEvents = true;
+ document.addEventListener("smartcard-insert", onSmartCardChange, false);
+ document.addEventListener("smartcard-remove", onSmartCardChange, false);
+ if (!gSMIMEBundle)
+ gSMIMEBundle = document.getElementById("bundle_read_smime");
+
+ // we want to register our security header sink as an opaque nsISupports
+ // on the msgHdrSink used by mail.....
+ msgWindow.msgHeaderSink.securityInfo = smimeHeaderSink;
+
+ gSignedUINode = document.getElementById('signedHdrIcon');
+ gEncryptedUINode = document.getElementById('encryptedHdrIcon');
+ gSMIMEContainer = document.getElementById('smimeBox');
+ gStatusBar = document.getElementById('status-bar');
+ gSignedStatusPanel = document.getElementById('signed-status');
+ gEncryptedStatusPanel = document.getElementById('encrypted-status');
+
+ // add ourself to the list of message display listeners so we get notified when we are about to display a
+ // message.
+ var listener = {};
+ listener.onStartHeaders = onSMIMEStartHeaders;
+ listener.onEndHeaders = onSMIMEEndHeaders;
+ gMessageListeners.push(listener);
+
+ gEncryptedURIService =
+ Components.classes["@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"]
+ .getService(Components.interfaces.nsIEncryptedSMIMEURIsService);
+}
+
+function msgHdrViewSMIMEOnUnload(event)
+{
+ window.crypto.enableSmartCardEvents = false;
+ document.removeEventListener("smartcard-insert", onSmartCardChange, false);
+ document.removeEventListener("smartcard-remove", onSmartCardChange, false);
+ forgetEncryptedURI();
+ removeEventListener("messagepane-loaded", msgHdrViewSMIMEOnLoad, true);
+ removeEventListener("messagepane-unloaded", msgHdrViewSMIMEOnUnload, true);
+ removeEventListener("messagepane-hide", msgHdrViewSMIMEOnMessagePaneHide, true);
+ removeEventListener("messagepane-unhide", msgHdrViewSMIMEOnMessagePaneUnhide, true);
+}
+
+function msgHdrViewSMIMEOnMessagePaneHide()
+{
+ gSMIMEContainer.collapsed = true;
+ gSignedUINode.collapsed = true;
+ gSignedStatusPanel.collapsed = true;
+ gEncryptedUINode.collapsed = true;
+ gEncryptedStatusPanel.collapsed = true;
+}
+
+function msgHdrViewSMIMEOnMessagePaneUnhide()
+{
+ if (gEncryptionStatus != -1 || gSignatureStatus != -1)
+ {
+ gSMIMEContainer.collapsed = false;
+
+ if (gSignatureStatus != -1)
+ {
+ gSignedUINode.collapsed = false;
+ gSignedStatusPanel.collapsed = false;
+ }
+
+ if (gEncryptionStatus != -1)
+ {
+ gEncryptedUINode.collapsed = false;
+ gEncryptedStatusPanel.collapsed = false;
+ }
+ }
+}
+
+addEventListener('messagepane-loaded', msgHdrViewSMIMEOnLoad, true);
+addEventListener('messagepane-unloaded', msgHdrViewSMIMEOnUnload, true);
+addEventListener('messagepane-hide', msgHdrViewSMIMEOnMessagePaneHide, true);
+addEventListener('messagepane-unhide', msgHdrViewSMIMEOnMessagePaneUnhide, true);
diff --git a/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.xul b/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.xul
new file mode 100644
index 000000000..957d2a15b
--- /dev/null
+++ b/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.xul
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/smime/msgHdrViewSMIMEOverlay.css" type="text/css"?>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://messenger-smime/content/msgHdrViewSMIMEOverlay.js"/>
+<!-- These stringbundles are already defined in msgReadSMIMEOverlay.xul!
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_read_smime" src="chrome://messenger-smime/locale/msgReadSMIMEOverlay.properties"/>
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ </stringbundleset>
+-->
+
+ <hbox id="expandedHeaderView">
+ <vbox id="smimeBox" insertafter="expandedHeaders" collapsed="true">
+ <spacer flex="1"/>
+ <image id="signedHdrIcon"
+ onclick="showMessageReadSecurityInfo();" collapsed="true"/>
+ <image id="encryptedHdrIcon"
+ onclick="showMessageReadSecurityInfo();" collapsed="true"/>
+ <spacer flex="1"/>
+ </vbox>
+ </hbox>
+</overlay>
+
diff --git a/mailnews/extensions/smime/content/msgReadSMIMEOverlay.js b/mailnews/extensions/smime/content/msgReadSMIMEOverlay.js
new file mode 100644
index 000000000..ab362d418
--- /dev/null
+++ b/mailnews/extensions/smime/content/msgReadSMIMEOverlay.js
@@ -0,0 +1,102 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gEncryptionStatus = -1;
+var gSignatureStatus = -1;
+var gSignerCert = null;
+var gEncryptionCert = null;
+
+addEventListener("load", smimeReadOnLoad, {capture: false, once: true});
+
+function smimeReadOnLoad()
+{
+ top.controllers.appendController(SecurityController);
+
+ addEventListener("unload", smimeReadOnUnload, {capture: false, once: true});
+}
+
+function smimeReadOnUnload()
+{
+ top.controllers.removeController(SecurityController);
+}
+
+function showImapSignatureUnknown()
+{
+ let readSmimeBundle = document.getElementById("bundle_read_smime");
+ let brandBundle = document.getElementById("bundle_brand");
+ if (!readSmimeBundle || !brandBundle)
+ return;
+
+ if (Services.prompt.confirm(window, brandBundle.getString("brandShortName"),
+ readSmimeBundle.getString("ImapOnDemand")))
+ {
+ gDBView.reloadMessageWithAllParts();
+ }
+}
+
+function showMessageReadSecurityInfo()
+{
+ let gSignedUINode = document.getElementById("signedHdrIcon");
+ if (gSignedUINode && gSignedUINode.getAttribute("signed") == "unknown")
+ {
+ showImapSignatureUnknown();
+ return;
+ }
+
+ let params = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
+ .createInstance(Components.interfaces.nsIDialogParamBlock);
+ params.objects = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ // Append even if null... the receiver must handle that.
+ params.objects.appendElement(gSignerCert, false);
+ params.objects.appendElement(gEncryptionCert, false);
+
+ // int array starts with index 0, but that is used for window exit status
+ params.SetInt(1, gSignatureStatus);
+ params.SetInt(2, gEncryptionStatus);
+
+ window.openDialog("chrome://messenger-smime/content/msgReadSecurityInfo.xul",
+ "", "chrome,resizable,modal,dialog,centerscreen", params);
+}
+
+var SecurityController =
+{
+ supportsCommand: function(command)
+ {
+ switch (command)
+ {
+ case "cmd_viewSecurityStatus":
+ return true;
+
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(command)
+ {
+ switch (command)
+ {
+ case "cmd_viewSecurityStatus":
+ if (document.documentElement.getAttribute('windowtype') == "mail:messageWindow")
+ return GetNumSelectedMessages() > 0;
+
+ if (GetNumSelectedMessages() > 0 && gDBView)
+ {
+ let enabled = {value: false};
+ let checkStatus = {};
+ gDBView.getCommandStatus(nsMsgViewCommandType.cmdRequiringMsgBody,
+ enabled, checkStatus);
+ return enabled.value;
+ }
+ // else: fall through.
+
+ default:
+ return false;
+ }
+ }
+};
diff --git a/mailnews/extensions/smime/content/msgReadSMIMEOverlay.xul b/mailnews/extensions/smime/content/msgReadSMIMEOverlay.xul
new file mode 100644
index 000000000..a55828c0f
--- /dev/null
+++ b/mailnews/extensions/smime/content/msgReadSMIMEOverlay.xul
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/smime/msgReadSMIMEOverlay.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger-smime/locale/msgReadSMIMEOverlay.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://messenger-smime/content/msgReadSMIMEOverlay.js"/>
+
+ <commandset id="mailViewMenuItems">
+ <command id="cmd_viewSecurityStatus" oncommand="showMessageReadSecurityInfo();" disabled="true"/>
+ </commandset>
+
+ <menupopup id="menu_View_Popup">
+ <menuitem insertafter="pageSourceMenuItem" label="&menu_securityStatus.label;"
+ accesskey="&menu_securityStatus.accesskey;" command="cmd_viewSecurityStatus"/>
+ </menupopup>
+
+ <statusbar id="status-bar">
+ <statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic"
+ id="signed-status" collapsed="true" oncommand="showMessageReadSecurityInfo();"/>
+ <statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic"
+ id="encrypted-status" collapsed="true" oncommand="showMessageReadSecurityInfo();"/>
+ <stringbundle id="bundle_read_smime" src="chrome://messenger-smime/locale/msgReadSMIMEOverlay.properties"/>
+<!-- This stringbundle is already defined on top window level!
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+-->
+ </statusbar>
+
+</overlay>
diff --git a/mailnews/extensions/smime/content/msgReadSecurityInfo.js b/mailnews/extensions/smime/content/msgReadSecurityInfo.js
new file mode 100644
index 000000000..310cfc18a
--- /dev/null
+++ b/mailnews/extensions/smime/content/msgReadSecurityInfo.js
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
+var nsIX509Cert = Components.interfaces.nsIX509Cert;
+var nsICMSMessageErrors = Components.interfaces.nsICMSMessageErrors;
+var nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
+var nsCertificateDialogs = "@mozilla.org/nsCertificateDialogs;1"
+
+var gSignerCert = null;
+var gEncryptionCert = null;
+
+var gSignatureStatus = -1;
+var gEncryptionStatus = -1;
+
+function setText(id, value) {
+ var element = document.getElementById(id);
+ if (!element)
+ return;
+ if (element.hasChildNodes())
+ element.firstChild.remove();
+ var textNode = document.createTextNode(value);
+ element.appendChild(textNode);
+}
+
+function onLoad()
+{
+ var paramBlock = window.arguments[0].QueryInterface(nsIDialogParamBlock);
+ paramBlock.objects.QueryInterface(Components.interfaces.nsIMutableArray);
+ try {
+ gSignerCert = paramBlock.objects.queryElementAt(0, nsIX509Cert);
+ } catch(e) { } // maybe null
+ try {
+ gEncryptionCert = paramBlock.objects.queryElementAt(1, nsIX509Cert);
+ } catch(e) { } // maybe null
+
+ gSignatureStatus = paramBlock.GetInt(1);
+ gEncryptionStatus = paramBlock.GetInt(2);
+
+ var bundle = document.getElementById("bundle_smime_read_info");
+
+ if (bundle) {
+ var sigInfoLabel = null;
+ var sigInfoHeader = null;
+ var sigInfo = null;
+ var sigInfo_clueless = false;
+
+ switch (gSignatureStatus) {
+ case -1:
+ case nsICMSMessageErrors.VERIFY_NOT_SIGNED:
+ sigInfoLabel = "SINoneLabel";
+ sigInfo = "SINone";
+ break;
+
+ case nsICMSMessageErrors.SUCCESS:
+ sigInfoLabel = "SIValidLabel";
+ sigInfo = "SIValid";
+ break;
+
+
+ case nsICMSMessageErrors.VERIFY_BAD_SIGNATURE:
+ case nsICMSMessageErrors.VERIFY_DIGEST_MISMATCH:
+ sigInfoLabel = "SIInvalidLabel";
+ sigInfoHeader = "SIInvalidHeader";
+ sigInfo = "SIContentAltered";
+ break;
+
+ case nsICMSMessageErrors.VERIFY_UNKNOWN_ALGO:
+ case nsICMSMessageErrors.VERIFY_UNSUPPORTED_ALGO:
+ sigInfoLabel = "SIInvalidLabel";
+ sigInfoHeader = "SIInvalidHeader";
+ sigInfo = "SIInvalidCipher";
+ break;
+
+ case nsICMSMessageErrors.VERIFY_HEADER_MISMATCH:
+ sigInfoLabel = "SIPartiallyValidLabel";
+ sigInfoHeader = "SIPartiallyValidHeader";
+ sigInfo = "SIHeaderMismatch";
+ break;
+
+ case nsICMSMessageErrors.VERIFY_CERT_WITHOUT_ADDRESS:
+ sigInfoLabel = "SIPartiallyValidLabel";
+ sigInfoHeader = "SIPartiallyValidHeader";
+ sigInfo = "SICertWithoutAddress";
+ break;
+
+ case nsICMSMessageErrors.VERIFY_UNTRUSTED:
+ sigInfoLabel = "SIInvalidLabel";
+ sigInfoHeader = "SIInvalidHeader";
+ sigInfo = "SIUntrustedCA";
+ // XXX Need to extend to communicate better errors
+ // might also be:
+ // SIExpired SIRevoked SINotYetValid SIUnknownCA SIExpiredCA SIRevokedCA SINotYetValidCA
+ break;
+
+ case nsICMSMessageErrors.VERIFY_NOT_YET_ATTEMPTED:
+ case nsICMSMessageErrors.GENERAL_ERROR:
+ case nsICMSMessageErrors.VERIFY_NO_CONTENT_INFO:
+ case nsICMSMessageErrors.VERIFY_BAD_DIGEST:
+ case nsICMSMessageErrors.VERIFY_NOCERT:
+ case nsICMSMessageErrors.VERIFY_ERROR_UNVERIFIED:
+ case nsICMSMessageErrors.VERIFY_ERROR_PROCESSING:
+ case nsICMSMessageErrors.VERIFY_MALFORMED_SIGNATURE:
+ sigInfoLabel = "SIInvalidLabel";
+ sigInfoHeader = "SIInvalidHeader";
+ sigInfo_clueless = true;
+ break;
+ default:
+ Components.utils.reportError("Unexpected gSignatureStatus: " +
+ gSignatureStatus);
+ }
+
+ document.getElementById("signatureLabel").value =
+ bundle.getString(sigInfoLabel);
+
+ var label;
+ if (sigInfoHeader) {
+ label = document.getElementById("signatureHeader");
+ label.collapsed = false;
+ label.value = bundle.getString(sigInfoHeader);
+ }
+
+ var str;
+ if (sigInfo) {
+ str = bundle.getString(sigInfo);
+ }
+ else if (sigInfo_clueless) {
+ str = bundle.getString("SIClueless") + " (" + gSignatureStatus + ")";
+ }
+ setText("signatureExplanation", str);
+
+ var encInfoLabel = null;
+ var encInfoHeader = null;
+ var encInfo = null;
+ var encInfo_clueless = false;
+
+ switch (gEncryptionStatus) {
+ case -1:
+ encInfoLabel = "EINoneLabel2";
+ encInfo = "EINone";
+ break;
+
+ case nsICMSMessageErrors.SUCCESS:
+ encInfoLabel = "EIValidLabel";
+ encInfo = "EIValid";
+ break;
+
+ case nsICMSMessageErrors.ENCRYPT_INCOMPLETE:
+ encInfoLabel = "EIInvalidLabel";
+ encInfo = "EIContentAltered";
+ break;
+
+ case nsICMSMessageErrors.GENERAL_ERROR:
+ encInfoLabel = "EIInvalidLabel";
+ encInfoHeader = "EIInvalidHeader";
+ encInfo_clueless = 1;
+ break;
+ default:
+ Components.utils.reportError("Unexpected gEncryptionStatus: " +
+ gEncryptionStatus);
+ }
+
+ document.getElementById("encryptionLabel").value =
+ bundle.getString(encInfoLabel);
+
+ if (encInfoHeader) {
+ label = document.getElementById("encryptionHeader");
+ label.collapsed = false;
+ label.value = bundle.getString(encInfoHeader);
+ }
+
+ if (encInfo) {
+ str = bundle.getString(encInfo);
+ }
+ else if (encInfo_clueless) {
+ str = bundle.getString("EIClueless");
+ }
+ setText("encryptionExplanation", str);
+ }
+
+ if (gSignerCert) {
+ document.getElementById("signatureCert").collapsed = false;
+ if (gSignerCert.subjectName) {
+ document.getElementById("signedBy").value = gSignerCert.commonName;
+ }
+ if (gSignerCert.emailAddress) {
+ document.getElementById("signerEmail").value = gSignerCert.emailAddress;
+ }
+ if (gSignerCert.issuerName) {
+ document.getElementById("sigCertIssuedBy").value = gSignerCert.issuerCommonName;
+ }
+ }
+
+ if (gEncryptionCert) {
+ document.getElementById("encryptionCert").collapsed = false;
+ if (gEncryptionCert.subjectName) {
+ document.getElementById("encryptedFor").value = gEncryptionCert.commonName;
+ }
+ if (gEncryptionCert.emailAddress) {
+ document.getElementById("recipientEmail").value = gEncryptionCert.emailAddress;
+ }
+ if (gEncryptionCert.issuerName) {
+ document.getElementById("encCertIssuedBy").value = gEncryptionCert.issuerCommonName;
+ }
+ }
+}
+
+function viewCertHelper(parent, cert) {
+ var cd = Components.classes[nsCertificateDialogs].getService(nsICertificateDialogs);
+ cd.viewCert(parent, cert);
+}
+
+function viewSignatureCert()
+{
+ if (gSignerCert) {
+ viewCertHelper(window, gSignerCert);
+ }
+}
+
+function viewEncryptionCert()
+{
+ if (gEncryptionCert) {
+ viewCertHelper(window, gEncryptionCert);
+ }
+}
+
+function doHelpButton()
+{
+ openHelp('received_security');
+}
diff --git a/mailnews/extensions/smime/content/msgReadSecurityInfo.xul b/mailnews/extensions/smime/content/msgReadSecurityInfo.xul
new file mode 100644
index 000000000..8e0a1f5f5
--- /dev/null
+++ b/mailnews/extensions/smime/content/msgReadSecurityInfo.xul
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/smime/msgReadSecurityInfo.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger-smime/locale/msgReadSecurityInfo.dtd">
+
+<dialog id="msgReadSecurityInfo" title="&status.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 40em;"
+ buttons="accept"
+ onload="onLoad();">
+
+ <script type="application/javascript" src="chrome://messenger-smime/content/msgReadSecurityInfo.js"/>
+
+ <stringbundle id="bundle_smime_read_info" src="chrome://messenger-smime/locale/msgSecurityInfo.properties"/>
+
+ <vbox flex="1">
+ <label id="signatureLabel"/>
+ <label id="signatureHeader" collapsed="true"/>
+ <description id="signatureExplanation"/>
+ <vbox id="signatureCert" collapsed="true">
+ <hbox>
+ <label id="signedByLabel">&signer.name;</label>
+ <description id="signedBy"/>
+ </hbox>
+ <hbox>
+ <label id="signerEmailLabel">&email.address;</label>
+ <description id="signerEmail"/>
+ </hbox>
+ <hbox>
+ <label id="sigCertIssuedByLabel">&issuer.name;</label>
+ <description id="sigCertIssuedBy"/>
+ </hbox>
+ <hbox>
+ <button id="signatureCertView" label="&signatureCert.label;"
+ oncommand="viewSignatureCert()"/>
+ </hbox>
+ </vbox>
+
+ <separator/>
+
+ <label id="encryptionLabel"/>
+ <label id="encryptionHeader" collapsed="true"/>
+ <description id="encryptionExplanation"/>
+ <vbox id="encryptionCert" collapsed="true">
+ <hbox>
+ <label id="encryptedForLabel">&recipient.name;</label>
+ <description id="encryptedFor"/>
+ </hbox>
+ <hbox>
+ <label id="recipientEmailLabel">&email.address;</label>
+ <description id="recipientEmail"/>
+ </hbox>
+ <hbox>
+ <label id="encCertIssuedByLabel">&issuer.name;</label>
+ <description id="encCertIssuedBy"/>
+ </hbox>
+ <hbox>
+ <button id="encryptionCertView" label="&encryptionCert.label;"
+ oncommand="viewEncryptionCert()"/>
+ </hbox>
+ </vbox>
+ </vbox>
+</dialog>
diff --git a/mailnews/extensions/smime/content/smime.js b/mailnews/extensions/smime/content/smime.js
new file mode 100644
index 000000000..8259ead1a
--- /dev/null
+++ b/mailnews/extensions/smime/content/smime.js
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Add any default pref values we want for smime
+*/
+
+pref("mail.identity.default.encryption_cert_name","");
+pref("mail.identity.default.encryptionpolicy", 0);
+pref("mail.identity.default.signing_cert_name", "");
+pref("mail.identity.default.sign_mail", false);
+
+
diff --git a/mailnews/extensions/smime/jar.mn b/mailnews/extensions/smime/jar.mn
new file mode 100644
index 000000000..548fef63c
--- /dev/null
+++ b/mailnews/extensions/smime/jar.mn
@@ -0,0 +1,30 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifdef MOZ_SUITE
+messenger.jar:
+% content messenger-smime %content/messenger-smime/
+% overlay chrome://messenger/content/messengercompose/messengercompose.xul chrome://messenger-smime/content/msgCompSMIMEOverlay.xul
+% overlay chrome://messenger/content/msgHdrViewOverlay.xul chrome://messenger-smime/content/msgHdrViewSMIMEOverlay.xul
+% overlay chrome://messenger/content/mailWindowOverlay.xul chrome://messenger-smime/content/msgReadSMIMEOverlay.xul
+% overlay chrome://messenger/content/am-identity-edit.xul chrome://messenger/content/am-smimeIdentityEditOverlay.xul
+ content/messenger/am-smime.xul (content/am-smime.xul)
+ content/messenger/am-smime.js (content/am-smime.js)
+ content/messenger/am-smimeIdentityEditOverlay.xul (content/am-smimeIdentityEditOverlay.xul)
+ content/messenger/am-smimeOverlay.xul (content/am-smimeOverlay.xul)
+ content/messenger/certpicker.js (content/certpicker.js)
+ content/messenger/certpicker.xul (content/certpicker.xul)
+ content/messenger-smime/msgCompSMIMEOverlay.js (content/msgCompSMIMEOverlay.js)
+ content/messenger-smime/msgCompSMIMEOverlay.xul (content/msgCompSMIMEOverlay.xul)
+ content/messenger-smime/msgReadSMIMEOverlay.js (content/msgReadSMIMEOverlay.js)
+ content/messenger-smime/msgReadSMIMEOverlay.xul (content/msgReadSMIMEOverlay.xul)
+ content/messenger-smime/msgHdrViewSMIMEOverlay.xul (content/msgHdrViewSMIMEOverlay.xul)
+ content/messenger-smime/msgHdrViewSMIMEOverlay.js (content/msgHdrViewSMIMEOverlay.js)
+ content/messenger-smime/msgCompSecurityInfo.xul (content/msgCompSecurityInfo.xul)
+ content/messenger-smime/msgCompSecurityInfo.js (content/msgCompSecurityInfo.js)
+ content/messenger-smime/msgReadSecurityInfo.xul (content/msgReadSecurityInfo.xul)
+ content/messenger-smime/msgReadSecurityInfo.js (content/msgReadSecurityInfo.js)
+ content/messenger-smime/certFetchingStatus.xul (content/certFetchingStatus.xul)
+ content/messenger-smime/certFetchingStatus.js (content/certFetchingStatus.js)
+#endif
diff --git a/mailnews/extensions/smime/moz.build b/mailnews/extensions/smime/moz.build
new file mode 100644
index 000000000..3adf6a50e
--- /dev/null
+++ b/mailnews/extensions/smime/moz.build
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'public',
+ 'src',
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+JS_PREFERENCE_FILES += [
+ 'content/smime.js',
+]
diff --git a/mailnews/extensions/smime/public/moz.build b/mailnews/extensions/smime/public/moz.build
new file mode 100644
index 000000000..b7acbb0b2
--- /dev/null
+++ b/mailnews/extensions/smime/public/moz.build
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsICertPickDialogs.idl',
+ 'nsIEncryptedSMIMEURIsSrvc.idl',
+ 'nsIMsgSMIMECompFields.idl',
+ 'nsIMsgSMIMEHeaderSink.idl',
+ 'nsISMimeJSHelper.idl',
+ 'nsIUserCertPicker.idl',
+]
+
+XPIDL_MODULE = 'msgsmime'
diff --git a/mailnews/extensions/smime/public/nsICertPickDialogs.idl b/mailnews/extensions/smime/public/nsICertPickDialogs.idl
new file mode 100644
index 000000000..01a7f3712
--- /dev/null
+++ b/mailnews/extensions/smime/public/nsICertPickDialogs.idl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInterfaceRequestor;
+
+/**
+ * nsICertPickDialogs
+ * Provides generic UI for choosing a certificate
+ */
+[scriptable, uuid(51d59b08-1dd2-11b2-ad4a-a51b92f8a184)]
+interface nsICertPickDialogs : nsISupports
+{
+ /**
+ * PickCertificate
+ * General purpose certificate prompter
+ */
+ void PickCertificate(in nsIInterfaceRequestor ctx,
+ [array, size_is(count)] in wstring certNickList,
+ [array, size_is(count)] in wstring certDetailsList,
+ in unsigned long count,
+ inout long selectedIndex,
+ out boolean canceled);
+};
+
+%{C++
+#define NS_CERTPICKDIALOGS_CONTRACTID "@mozilla.org/nsCertPickDialogs;1"
+%}
diff --git a/mailnews/extensions/smime/public/nsIEncryptedSMIMEURIsSrvc.idl b/mailnews/extensions/smime/public/nsIEncryptedSMIMEURIsSrvc.idl
new file mode 100644
index 000000000..4b2b7c25c
--- /dev/null
+++ b/mailnews/extensions/smime/public/nsIEncryptedSMIMEURIsSrvc.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This is a private interface used exclusively by SMIME.
+ It provides functionality to the JS UI code,
+ that is only accessible from C++.
+*/
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(f86e55c9-530b-483f-91a7-10fb5b852488)]
+interface nsIEncryptedSMIMEURIsService : nsISupports
+{
+ /// Remember that this URI is encrypted.
+ void rememberEncrypted(in AUTF8String uri);
+
+ /// Forget that this URI is encrypted.
+ void forgetEncrypted(in AUTF8String uri);
+
+ /// Check if this URI is encrypted.
+ boolean isEncrypted(in AUTF8String uri);
+};
diff --git a/mailnews/extensions/smime/public/nsIMsgSMIMECompFields.idl b/mailnews/extensions/smime/public/nsIMsgSMIMECompFields.idl
new file mode 100644
index 000000000..0688afd76
--- /dev/null
+++ b/mailnews/extensions/smime/public/nsIMsgSMIMECompFields.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/* This is a private interface used exclusively by SMIME. NO ONE outside of extensions/smime
+ should have any knowledge nor should be referring to this interface.
+*/
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(338E91F9-5970-4f81-B771-0822A32B1161)]
+interface nsIMsgSMIMECompFields : nsISupports
+{
+ attribute boolean signMessage;
+ attribute boolean requireEncryptMessage;
+};
diff --git a/mailnews/extensions/smime/public/nsIMsgSMIMEHeaderSink.idl b/mailnews/extensions/smime/public/nsIMsgSMIMEHeaderSink.idl
new file mode 100644
index 000000000..ec449769a
--- /dev/null
+++ b/mailnews/extensions/smime/public/nsIMsgSMIMEHeaderSink.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/* This is a private interface used exclusively by SMIME. NO ONE outside of extensions/smime
+ or the hard coded smime decryption files in mime/src should have any knowledge nor should
+ be referring to this interface.
+*/
+
+#include "nsISupports.idl"
+
+interface nsIX509Cert;
+
+[scriptable, uuid(25380FA1-E70C-4e82-B0BC-F31C2F41C470)]
+interface nsIMsgSMIMEHeaderSink : nsISupports
+{
+ void signedStatus(in long aNestingLevel,
+ in long aSignatureStatus,
+ in nsIX509Cert aSignerCert,
+ in AUTF8String aMsgNeckoURL);
+ void encryptionStatus(in long aNestingLevel,
+ in long aEncryptionStatus,
+ in nsIX509Cert aReceipientCert,
+ in AUTF8String aMsgNeckoURL);
+
+ long maxWantedNesting(); // 1 == only info on outermost nesting level wanted
+};
diff --git a/mailnews/extensions/smime/public/nsISMimeJSHelper.idl b/mailnews/extensions/smime/public/nsISMimeJSHelper.idl
new file mode 100644
index 000000000..c29a77939
--- /dev/null
+++ b/mailnews/extensions/smime/public/nsISMimeJSHelper.idl
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This is a private interface used exclusively by SMIME.
+ It provides functionality to the JS UI code,
+ that is only accessible from C++.
+*/
+
+#include "nsISupports.idl"
+
+interface nsIMsgCompFields;
+interface nsIX509Cert;
+
+[scriptable, uuid(a54e3c8f-a000-4901-898f-fafb297b1546)]
+interface nsISMimeJSHelper : nsISupports
+{
+ /**
+ * Obtains detailed information about the certificate availability
+ * status of email recipients.
+ *
+ * @param compFields - Attributes of the composed message
+ *
+ * @param count - The number of entries in returned arrays
+ *
+ * @param emailAddresses - The list of all recipient email addresses
+ *
+ * @param certVerification - The verification/validity status of recipient certs
+ *
+ * @param certIssuedInfos - If a recipient cert was found, when has it been issued?
+ *
+ * @param certExpiredInfos - If a recipient cert was found, when will it expire?
+ *
+ * @param certs - The recipient certificates, which can contain null for not found
+ *
+ * @param canEncrypt - whether valid certificates have been found for all recipients
+ *
+ * @exception NS_ERROR_FAILURE - unexptected failure
+ *
+ * @exception NS_ERROR_OUT_OF_MEMORY - could not create the out list
+ *
+ * @exception NS_ERROR_INVALID_ARG
+ */
+ void getRecipientCertsInfo(in nsIMsgCompFields compFields,
+ out unsigned long count,
+ [array, size_is(count)] out wstring emailAddresses,
+ [array, size_is(count)] out long certVerification,
+ [array, size_is(count)] out wstring certIssuedInfos,
+ [array, size_is(count)] out wstring certExpiresInfos,
+ [array, size_is(count)] out nsIX509Cert certs,
+ out boolean canEncrypt);
+
+ /**
+ * Obtains a list of email addresses where valid email recipient certificates
+ * are not yet available.
+ *
+ * @param compFields - Attributes of the composed message
+ *
+ * @param count - The number of returned email addresses
+ *
+ * @param emailAddresses - The list of email addresses without valid certs
+ *
+ * @exception NS_ERROR_FAILURE - unexptected failure
+ *
+ * @exception NS_ERROR_OUT_OF_MEMORY - could not create the out list
+ *
+ * @exception NS_ERROR_INVALID_ARG
+ */
+ void getNoCertAddresses(in nsIMsgCompFields compFields,
+ out unsigned long count,
+ [array, size_is(count)] out wstring emailAddresses);
+};
diff --git a/mailnews/extensions/smime/public/nsIUserCertPicker.idl b/mailnews/extensions/smime/public/nsIUserCertPicker.idl
new file mode 100644
index 000000000..666941c29
--- /dev/null
+++ b/mailnews/extensions/smime/public/nsIUserCertPicker.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIX509Cert;
+interface nsIInterfaceRequestor;
+
+[scriptable, uuid(92396323-23f2-49e0-bf98-a25a725231ab)]
+interface nsIUserCertPicker : nsISupports {
+ nsIX509Cert pickByUsage(in nsIInterfaceRequestor ctx,
+ in wstring selectedNickname,
+ in long certUsage, // as defined by NSS enum SECCertUsage
+ in boolean allowInvalid,
+ in boolean allowDuplicateNicknames,
+ in AString emailAddress, // optional - if non-empty,
+ // skip certificates which
+ // have at least one e-mail
+ // address but do not
+ // include this specific one
+ out boolean canceled);
+};
+
+%{C++
+#define NS_CERT_PICKER_CONTRACTID "@mozilla.org/user_cert_picker;1"
+%}
diff --git a/mailnews/extensions/smime/src/moz.build b/mailnews/extensions/smime/src/moz.build
new file mode 100644
index 000000000..19a18f54f
--- /dev/null
+++ b/mailnews/extensions/smime/src/moz.build
@@ -0,0 +1,23 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsCertPicker.cpp',
+ 'nsEncryptedSMIMEURIsService.cpp',
+ 'nsMsgComposeSecure.cpp',
+ 'nsSMimeJSHelper.cpp',
+]
+
+EXTRA_COMPONENTS += [
+ 'smime-service.js',
+ 'smime-service.manifest',
+]
+
+FINAL_LIBRARY = 'mail'
+
+LOCAL_INCLUDES += [
+ '/security/manager/pki',
+ '/security/pkix/include'
+]
diff --git a/mailnews/extensions/smime/src/nsCertPicker.cpp b/mailnews/extensions/smime/src/nsCertPicker.cpp
new file mode 100644
index 000000000..183f9605c
--- /dev/null
+++ b/mailnews/extensions/smime/src/nsCertPicker.cpp
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCertPicker.h"
+
+#include "MainThreadUtils.h"
+#include "ScopedNSSTypes.h"
+#include "cert.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsICertPickDialogs.h"
+#include "nsIDOMWindow.h"
+#include "nsIDialogParamBlock.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIServiceManager.h"
+#include "nsIX509CertValidity.h"
+#include "nsMemory.h"
+#include "nsMsgComposeSecure.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSComponent.h"
+#include "nsNSSDialogHelper.h"
+#include "nsNSSHelper.h"
+#include "nsNSSShutDown.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "pkix/pkixtypes.h"
+
+using namespace mozilla;
+
+MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueCERTCertNicknames,
+ CERTCertNicknames,
+ CERT_FreeNicknames)
+
+CERTCertNicknames*
+getNSSCertNicknamesFromCertList(const UniqueCERTCertList& certList)
+{
+ static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
+
+ nsresult rv;
+
+ nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv));
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ nsAutoString expiredString, notYetValidString;
+ nsAutoString expiredStringLeadingSpace, notYetValidStringLeadingSpace;
+
+ nssComponent->GetPIPNSSBundleString("NicknameExpired", expiredString);
+ nssComponent->GetPIPNSSBundleString("NicknameNotYetValid", notYetValidString);
+
+ expiredStringLeadingSpace.Append(' ');
+ expiredStringLeadingSpace.Append(expiredString);
+
+ notYetValidStringLeadingSpace.Append(' ');
+ notYetValidStringLeadingSpace.Append(notYetValidString);
+
+ NS_ConvertUTF16toUTF8 aUtf8ExpiredString(expiredStringLeadingSpace);
+ NS_ConvertUTF16toUTF8 aUtf8NotYetValidString(notYetValidStringLeadingSpace);
+
+ return CERT_NicknameStringsFromCertList(certList.get(),
+ const_cast<char*>(aUtf8ExpiredString.get()),
+ const_cast<char*>(aUtf8NotYetValidString.get()));
+}
+
+nsresult
+FormatUIStrings(nsIX509Cert* cert, const nsAutoString& nickname,
+ nsAutoString& nickWithSerial, nsAutoString& details)
+{
+ if (!NS_IsMainThread()) {
+ NS_ERROR("nsNSSCertificate::FormatUIStrings called off the main thread");
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ RefPtr<nsMsgComposeSecure> mcs = new nsMsgComposeSecure;
+ if (!mcs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString info;
+ nsAutoString temp1;
+
+ nickWithSerial.Append(nickname);
+
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedFor", info))) {
+ details.Append(info);
+ details.Append(char16_t(' '));
+ if (NS_SUCCEEDED(cert->GetSubjectName(temp1)) && !temp1.IsEmpty()) {
+ details.Append(temp1);
+ }
+ details.Append(char16_t('\n'));
+ }
+
+ if (NS_SUCCEEDED(cert->GetSerialNumber(temp1)) && !temp1.IsEmpty()) {
+ details.AppendLiteral(" ");
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertDumpSerialNo", info))) {
+ details.Append(info);
+ details.AppendLiteral(": ");
+ }
+ details.Append(temp1);
+
+ nickWithSerial.AppendLiteral(" [");
+ nickWithSerial.Append(temp1);
+ nickWithSerial.Append(char16_t(']'));
+
+ details.Append(char16_t('\n'));
+ }
+
+ nsCOMPtr<nsIX509CertValidity> validity;
+ nsresult rv = cert->GetValidity(getter_AddRefs(validity));
+ if (NS_SUCCEEDED(rv) && validity) {
+ details.AppendLiteral(" ");
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoValid", info))) {
+ details.Append(info);
+ }
+
+ if (NS_SUCCEEDED(validity->GetNotBeforeLocalTime(temp1)) && !temp1.IsEmpty()) {
+ details.Append(char16_t(' '));
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoFrom", info))) {
+ details.Append(info);
+ details.Append(char16_t(' '));
+ }
+ details.Append(temp1);
+ }
+
+ if (NS_SUCCEEDED(validity->GetNotAfterLocalTime(temp1)) && !temp1.IsEmpty()) {
+ details.Append(char16_t(' '));
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoTo", info))) {
+ details.Append(info);
+ details.Append(char16_t(' '));
+ }
+ details.Append(temp1);
+ }
+
+ details.Append(char16_t('\n'));
+ }
+
+ if (NS_SUCCEEDED(cert->GetKeyUsages(temp1)) && !temp1.IsEmpty()) {
+ details.AppendLiteral(" ");
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertDumpKeyUsage", info))) {
+ details.Append(info);
+ details.AppendLiteral(": ");
+ }
+ details.Append(temp1);
+ details.Append(char16_t('\n'));
+ }
+
+ UniqueCERTCertificate nssCert(cert->GetCert());
+ if (!nssCert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString firstEmail;
+ const char* aWalkAddr;
+ for (aWalkAddr = CERT_GetFirstEmailAddress(nssCert.get())
+ ;
+ aWalkAddr
+ ;
+ aWalkAddr = CERT_GetNextEmailAddress(nssCert.get(), aWalkAddr))
+ {
+ NS_ConvertUTF8toUTF16 email(aWalkAddr);
+ if (email.IsEmpty())
+ continue;
+
+ if (firstEmail.IsEmpty()) {
+ // If the first email address from the subject DN is also present
+ // in the subjectAltName extension, GetEmailAddresses() will return
+ // it twice (as received from NSS). Remember the first address so that
+ // we can filter out duplicates later on.
+ firstEmail = email;
+
+ details.AppendLiteral(" ");
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoEmail", info))) {
+ details.Append(info);
+ details.AppendLiteral(": ");
+ }
+ details.Append(email);
+ }
+ else {
+ // Append current address if it's different from the first one.
+ if (!firstEmail.Equals(email)) {
+ details.AppendLiteral(", ");
+ details.Append(email);
+ }
+ }
+ }
+
+ if (!firstEmail.IsEmpty()) {
+ // We got at least one email address, so we want a newline
+ details.Append(char16_t('\n'));
+ }
+
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedBy", info))) {
+ details.Append(info);
+ details.Append(char16_t(' '));
+
+ if (NS_SUCCEEDED(cert->GetIssuerName(temp1)) && !temp1.IsEmpty()) {
+ details.Append(temp1);
+ }
+
+ details.Append(char16_t('\n'));
+ }
+
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoStoredIn", info))) {
+ details.Append(info);
+ details.Append(char16_t(' '));
+
+ if (NS_SUCCEEDED(cert->GetTokenName(temp1)) && !temp1.IsEmpty()) {
+ details.Append(temp1);
+ }
+ }
+
+ // the above produces the following output:
+ //
+ // Issued to: $subjectName
+ // Serial number: $serialNumber
+ // Valid from: $starting_date to $expiration_date
+ // Certificate Key usage: $usages
+ // Email: $address(es)
+ // Issued by: $issuerName
+ // Stored in: $token
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsCertPicker, nsICertPickDialogs, nsIUserCertPicker)
+
+nsCertPicker::nsCertPicker()
+{
+}
+
+nsCertPicker::~nsCertPicker()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+nsresult
+nsCertPicker::Init()
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> psm = do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCertPicker::PickCertificate(nsIInterfaceRequestor *ctx,
+ const char16_t **certNickList,
+ const char16_t **certDetailsList,
+ uint32_t count,
+ int32_t *selectedIndex,
+ bool *canceled)
+{
+ nsresult rv;
+ uint32_t i;
+
+ *canceled = false;
+
+ nsCOMPtr<nsIDialogParamBlock> block =
+ do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID);
+ if (!block) return NS_ERROR_FAILURE;
+
+ block->SetNumberStrings(1+count*2);
+
+ for (i = 0; i < count; i++) {
+ rv = block->SetString(i, certNickList[i]);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ for (i = 0; i < count; i++) {
+ rv = block->SetString(i+count, certDetailsList[i]);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = block->SetInt(0, count);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = block->SetInt(1, *selectedIndex);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = nsNSSDialogHelper::openDialog(nullptr,
+ "chrome://messenger/content/certpicker.xul",
+ block);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t status;
+
+ rv = block->GetInt(0, &status);
+ if (NS_FAILED(rv)) return rv;
+
+ *canceled = (status == 0)?true:false;
+ if (!*canceled) {
+ rv = block->GetInt(1, selectedIndex);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsCertPicker::PickByUsage(nsIInterfaceRequestor *ctx,
+ const char16_t *selectedNickname,
+ int32_t certUsage,
+ bool allowInvalid,
+ bool allowDuplicateNicknames,
+ const nsAString &emailAddress,
+ bool *canceled,
+ nsIX509Cert **_retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ int32_t selectedIndex = -1;
+ bool selectionFound = false;
+ char16_t **certNicknameList = nullptr;
+ char16_t **certDetailsList = nullptr;
+ CERTCertListNode* node = nullptr;
+ nsresult rv = NS_OK;
+
+ {
+ // Iterate over all certs. This assures that user is logged in to all hardware tokens.
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+ UniqueCERTCertList allcerts(PK11_ListCerts(PK11CertListUnique, ctx));
+ }
+
+ /* find all user certs that are valid for the specified usage */
+ /* note that we are allowing expired certs in this list */
+ UniqueCERTCertList certList(
+ CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(),
+ (SECCertUsage)certUsage,
+ !allowDuplicateNicknames,
+ !allowInvalid,
+ ctx));
+ if (!certList) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ /* if a (non-empty) emailAddress argument is supplied to PickByUsage, */
+ /* remove non-matching certificates from the candidate list */
+
+ if (!emailAddress.IsEmpty()) {
+ node = CERT_LIST_HEAD(certList);
+ while (!CERT_LIST_END(node, certList)) {
+ /* if the cert has at least one e-mail address, check if suitable */
+ if (CERT_GetFirstEmailAddress(node->cert)) {
+ RefPtr<nsNSSCertificate> tempCert(nsNSSCertificate::Create(node->cert));
+ bool match = false;
+ rv = tempCert->ContainsEmailAddress(emailAddress, &match);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!match) {
+ /* doesn't contain the specified address, so remove from the list */
+ CERTCertListNode* freenode = node;
+ node = CERT_LIST_NEXT(node);
+ CERT_RemoveCertListNode(freenode);
+ continue;
+ }
+ }
+ node = CERT_LIST_NEXT(node);
+ }
+ }
+
+ UniqueCERTCertNicknames nicknames(getNSSCertNicknamesFromCertList(certList));
+ if (!nicknames) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ certNicknameList = (char16_t **)moz_xmalloc(sizeof(char16_t *) * nicknames->numnicknames);
+ certDetailsList = (char16_t **)moz_xmalloc(sizeof(char16_t *) * nicknames->numnicknames);
+
+ if (!certNicknameList || !certDetailsList) {
+ free(certNicknameList);
+ free(certDetailsList);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ int32_t CertsToUse;
+
+ for (CertsToUse = 0, node = CERT_LIST_HEAD(certList.get());
+ !CERT_LIST_END(node, certList.get()) &&
+ CertsToUse < nicknames->numnicknames;
+ node = CERT_LIST_NEXT(node)
+ )
+ {
+ RefPtr<nsNSSCertificate> tempCert(nsNSSCertificate::Create(node->cert));
+
+ if (tempCert) {
+
+ nsAutoString i_nickname(NS_ConvertUTF8toUTF16(nicknames->nicknames[CertsToUse]));
+ nsAutoString nickWithSerial;
+ nsAutoString details;
+
+ if (!selectionFound) {
+ /* for the case when selectedNickname refers to a bare nickname */
+ if (i_nickname == nsDependentString(selectedNickname)) {
+ selectedIndex = CertsToUse;
+ selectionFound = true;
+ }
+ }
+
+ if (NS_SUCCEEDED(FormatUIStrings(tempCert, i_nickname, nickWithSerial,
+ details))) {
+ certNicknameList[CertsToUse] = ToNewUnicode(nickWithSerial);
+ certDetailsList[CertsToUse] = ToNewUnicode(details);
+ if (!selectionFound) {
+ /* for the case when selectedNickname refers to nickname + serial */
+ if (nickWithSerial == nsDependentString(selectedNickname)) {
+ selectedIndex = CertsToUse;
+ selectionFound = true;
+ }
+ }
+ }
+ else {
+ certNicknameList[CertsToUse] = nullptr;
+ certDetailsList[CertsToUse] = nullptr;
+ }
+
+ ++CertsToUse;
+ }
+ }
+
+ if (CertsToUse) {
+ nsCOMPtr<nsICertPickDialogs> dialogs;
+ rv = getNSSDialogs(getter_AddRefs(dialogs), NS_GET_IID(nsICertPickDialogs),
+ NS_CERTPICKDIALOGS_CONTRACTID);
+
+ if (NS_SUCCEEDED(rv)) {
+ // Show the cert picker dialog and get the index of the selected cert.
+ rv = dialogs->PickCertificate(ctx, (const char16_t**)certNicknameList,
+ (const char16_t**)certDetailsList,
+ CertsToUse, &selectedIndex, canceled);
+ }
+ }
+
+ int32_t i;
+ for (i = 0; i < CertsToUse; ++i) {
+ free(certNicknameList[i]);
+ free(certDetailsList[i]);
+ }
+ free(certNicknameList);
+ free(certDetailsList);
+
+ if (!CertsToUse) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_SUCCEEDED(rv) && !*canceled) {
+ for (i = 0, node = CERT_LIST_HEAD(certList);
+ !CERT_LIST_END(node, certList);
+ ++i, node = CERT_LIST_NEXT(node)) {
+
+ if (i == selectedIndex) {
+ RefPtr<nsNSSCertificate> cert = nsNSSCertificate::Create(node->cert);
+ if (!cert) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+
+ cert.forget(_retval);
+ break;
+ }
+ }
+ }
+
+ return rv;
+}
diff --git a/mailnews/extensions/smime/src/nsCertPicker.h b/mailnews/extensions/smime/src/nsCertPicker.h
new file mode 100644
index 000000000..e5881f14f
--- /dev/null
+++ b/mailnews/extensions/smime/src/nsCertPicker.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCertPicker_h
+#define nsCertPicker_h
+
+#include "nsICertPickDialogs.h"
+#include "nsIUserCertPicker.h"
+#include "nsNSSShutDown.h"
+
+#define NS_CERT_PICKER_CID \
+ { 0x735959a1, 0xaf01, 0x447e, { 0xb0, 0x2d, 0x56, 0xe9, 0x68, 0xfa, 0x52, 0xb4 } }
+
+class nsCertPicker : public nsICertPickDialogs
+ , public nsIUserCertPicker
+ , public nsNSSShutDownObject
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICERTPICKDIALOGS
+ NS_DECL_NSIUSERCERTPICKER
+
+ nsCertPicker();
+
+ // Nothing to actually release.
+ virtual void virtualDestroyNSSReference() override {}
+
+ nsresult Init();
+
+protected:
+ virtual ~nsCertPicker();
+};
+
+#endif // nsCertPicker_h
diff --git a/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp
new file mode 100644
index 000000000..9276a07a6
--- /dev/null
+++ b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsEncryptedSMIMEURIsService.h"
+
+NS_IMPL_ISUPPORTS(nsEncryptedSMIMEURIsService, nsIEncryptedSMIMEURIsService)
+
+nsEncryptedSMIMEURIsService::nsEncryptedSMIMEURIsService()
+{
+}
+
+nsEncryptedSMIMEURIsService::~nsEncryptedSMIMEURIsService()
+{
+}
+
+NS_IMETHODIMP nsEncryptedSMIMEURIsService::RememberEncrypted(const nsACString & uri)
+{
+ // Assuming duplicates are allowed.
+ mEncryptedURIs.AppendElement(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsEncryptedSMIMEURIsService::ForgetEncrypted(const nsACString & uri)
+{
+ // Assuming, this will only remove one copy of the string, if the array
+ // contains multiple copies of the same string.
+ mEncryptedURIs.RemoveElement(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsEncryptedSMIMEURIsService::IsEncrypted(const nsACString & uri, bool *_retval)
+{
+ *_retval = mEncryptedURIs.Contains(uri);
+ return NS_OK;
+}
diff --git a/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h
new file mode 100644
index 000000000..429acbf0a
--- /dev/null
+++ b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsEncryptedSMIMEURIsService_H_
+#define _nsEncryptedSMIMEURIsService_H_
+
+#include "nsIEncryptedSMIMEURIsSrvc.h"
+#include "nsTArray.h"
+#include "nsStringGlue.h"
+
+class nsEncryptedSMIMEURIsService : public nsIEncryptedSMIMEURIsService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIENCRYPTEDSMIMEURISSERVICE
+
+ nsEncryptedSMIMEURIsService();
+
+protected:
+ virtual ~nsEncryptedSMIMEURIsService();
+ nsTArray<nsCString> mEncryptedURIs;
+};
+
+#endif
diff --git a/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp b/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp
new file mode 100644
index 000000000..b98efdc5e
--- /dev/null
+++ b/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp
@@ -0,0 +1,1200 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgComposeSecure.h"
+
+#include "ScopedNSSTypes.h"
+#include "cert.h"
+#include "keyhi.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Services.h"
+#include "mozilla/mailnews/MimeEncoder.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "msgCore.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICryptoHash.h"
+#include "nsIMimeConverter.h"
+#include "nsIMsgCompFields.h"
+#include "nsIMsgIdentity.h"
+#include "nsIX509CertDB.h"
+#include "nsMemory.h"
+#include "nsMimeTypes.h"
+#include "nsMsgMimeCID.h"
+#include "nsNSSComponent.h"
+#include "nsServiceManagerUtils.h"
+#include "nspr.h"
+#include "pkix/Result.h"
+
+using namespace mozilla::mailnews;
+using namespace mozilla;
+using namespace mozilla::psm;
+
+#define MK_MIME_ERROR_WRITING_FILE -1
+
+#define SMIME_STRBUNDLE_URL "chrome://messenger/locale/am-smime.properties"
+
+// It doesn't make sense to encode the message because the message will be
+// displayed only if the MUA doesn't support MIME.
+// We need to consider what to do in case the server doesn't support 8BITMIME.
+// In short, we can't use non-ASCII characters here.
+static const char crypto_multipart_blurb[] = "This is a cryptographically signed message in MIME format.";
+
+static void mime_crypto_write_base64 (void *closure, const char *buf,
+ unsigned long size);
+static nsresult mime_encoder_output_fn(const char *buf, int32_t size,
+ void *closure);
+static nsresult mime_nested_encoder_output_fn(const char *buf, int32_t size,
+ void *closure);
+static nsresult make_multipart_signed_header_string(bool outer_p,
+ char **header_return,
+ char **boundary_return,
+ int16_t hash_type);
+static char *mime_make_separator(const char *prefix);
+
+
+static void
+GenerateGlobalRandomBytes(unsigned char *buf, int32_t len)
+{
+ static bool firstTime = true;
+
+ if (firstTime)
+ {
+ // Seed the random-number generator with current time so that
+ // the numbers will be different every time we run.
+ srand( (unsigned)PR_Now() );
+ firstTime = false;
+ }
+
+ for( int32_t i = 0; i < len; i++ )
+ buf[i] = rand() % 10;
+}
+
+char
+*mime_make_separator(const char *prefix)
+{
+ unsigned char rand_buf[13];
+ GenerateGlobalRandomBytes(rand_buf, 12);
+
+ return PR_smprintf("------------%s"
+ "%02X%02X%02X%02X"
+ "%02X%02X%02X%02X"
+ "%02X%02X%02X%02X",
+ prefix,
+ rand_buf[0], rand_buf[1], rand_buf[2], rand_buf[3],
+ rand_buf[4], rand_buf[5], rand_buf[6], rand_buf[7],
+ rand_buf[8], rand_buf[9], rand_buf[10], rand_buf[11]);
+}
+
+// end of copied code which needs fixed....
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Implementation of nsMsgSMIMEComposeFields
+/////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsMsgSMIMEComposeFields, nsIMsgSMIMECompFields)
+
+nsMsgSMIMEComposeFields::nsMsgSMIMEComposeFields()
+:mSignMessage(false), mAlwaysEncryptMessage(false)
+{
+}
+
+nsMsgSMIMEComposeFields::~nsMsgSMIMEComposeFields()
+{
+}
+
+NS_IMETHODIMP nsMsgSMIMEComposeFields::SetSignMessage(bool value)
+{
+ mSignMessage = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSMIMEComposeFields::GetSignMessage(bool *_retval)
+{
+ *_retval = mSignMessage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSMIMEComposeFields::SetRequireEncryptMessage(bool value)
+{
+ mAlwaysEncryptMessage = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSMIMEComposeFields::GetRequireEncryptMessage(bool *_retval)
+{
+ *_retval = mAlwaysEncryptMessage;
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Implementation of nsMsgComposeSecure
+/////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsMsgComposeSecure, nsIMsgComposeSecure)
+
+nsMsgComposeSecure::nsMsgComposeSecure()
+{
+ /* member initializers and constructor code */
+ mMultipartSignedBoundary = 0;
+ mBuffer = 0;
+ mBufferedBytes = 0;
+ mHashType = 0;
+}
+
+nsMsgComposeSecure::~nsMsgComposeSecure()
+{
+ /* destructor code */
+ if (mEncryptionContext) {
+ if (mBufferedBytes) {
+ mEncryptionContext->Update(mBuffer, mBufferedBytes);
+ mBufferedBytes = 0;
+ }
+ mEncryptionContext->Finish();
+ }
+
+ delete [] mBuffer;
+
+ PR_FREEIF(mMultipartSignedBoundary);
+}
+
+NS_IMETHODIMP nsMsgComposeSecure::RequiresCryptoEncapsulation(nsIMsgIdentity * aIdentity, nsIMsgCompFields * aCompFields, bool * aRequiresEncryptionWork)
+{
+ NS_ENSURE_ARG_POINTER(aRequiresEncryptionWork);
+
+ *aRequiresEncryptionWork = false;
+
+ bool alwaysEncryptMessages = false;
+ bool signMessage = false;
+ nsresult rv = ExtractEncryptionState(aIdentity, aCompFields, &signMessage, &alwaysEncryptMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (alwaysEncryptMessages || signMessage)
+ *aRequiresEncryptionWork = true;
+
+ return NS_OK;
+}
+
+
+nsresult nsMsgComposeSecure::GetSMIMEBundleString(const char16_t *name,
+ nsString &outString)
+{
+ outString.Truncate();
+
+ NS_ENSURE_ARG_POINTER(name);
+
+ NS_ENSURE_TRUE(InitializeSMIMEBundle(), NS_ERROR_FAILURE);
+
+ return mSMIMEBundle->GetStringFromName(name, getter_Copies(outString));
+}
+
+nsresult
+nsMsgComposeSecure::
+SMIMEBundleFormatStringFromName(const char16_t *name,
+ const char16_t **params,
+ uint32_t numParams,
+ char16_t **outString)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ if (!InitializeSMIMEBundle())
+ return NS_ERROR_FAILURE;
+
+ return mSMIMEBundle->FormatStringFromName(name, params,
+ numParams, outString);
+}
+
+bool nsMsgComposeSecure::InitializeSMIMEBundle()
+{
+ if (mSMIMEBundle)
+ return true;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ nsresult rv = bundleService->CreateBundle(SMIME_STRBUNDLE_URL,
+ getter_AddRefs(mSMIMEBundle));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+void nsMsgComposeSecure::SetError(nsIMsgSendReport *sendReport, const char16_t *bundle_string)
+{
+ if (!sendReport || !bundle_string)
+ return;
+
+ if (mErrorAlreadyReported)
+ return;
+
+ mErrorAlreadyReported = true;
+
+ nsString errorString;
+ nsresult res = GetSMIMEBundleString(bundle_string, errorString);
+ if (NS_SUCCEEDED(res) && !errorString.IsEmpty())
+ {
+ sendReport->SetMessage(nsIMsgSendReport::process_Current,
+ errorString.get(),
+ true);
+ }
+}
+
+void nsMsgComposeSecure::SetErrorWithParam(nsIMsgSendReport *sendReport, const char16_t *bundle_string, const char *param)
+{
+ if (!sendReport || !bundle_string || !param)
+ return;
+
+ if (mErrorAlreadyReported)
+ return;
+
+ mErrorAlreadyReported = true;
+
+ nsString errorString;
+ nsresult res;
+ const char16_t *params[1];
+
+ NS_ConvertASCIItoUTF16 ucs2(param);
+ params[0]= ucs2.get();
+
+ res = SMIMEBundleFormatStringFromName(bundle_string,
+ params,
+ 1,
+ getter_Copies(errorString));
+
+ if (NS_SUCCEEDED(res) && !errorString.IsEmpty())
+ {
+ sendReport->SetMessage(nsIMsgSendReport::process_Current,
+ errorString.get(),
+ true);
+ }
+}
+
+nsresult nsMsgComposeSecure::ExtractEncryptionState(nsIMsgIdentity * aIdentity, nsIMsgCompFields * aComposeFields, bool * aSignMessage, bool * aEncrypt)
+{
+ if (!aComposeFields && !aIdentity)
+ return NS_ERROR_FAILURE; // kick out...invalid args....
+
+ NS_ENSURE_ARG_POINTER(aSignMessage);
+ NS_ENSURE_ARG_POINTER(aEncrypt);
+
+ nsCOMPtr<nsISupports> securityInfo;
+ if (aComposeFields)
+ aComposeFields->GetSecurityInfo(getter_AddRefs(securityInfo));
+
+ if (securityInfo) // if we were given security comp fields, use them.....
+ {
+ nsCOMPtr<nsIMsgSMIMECompFields> smimeCompFields = do_QueryInterface(securityInfo);
+ if (smimeCompFields)
+ {
+ smimeCompFields->GetSignMessage(aSignMessage);
+ smimeCompFields->GetRequireEncryptMessage(aEncrypt);
+ return NS_OK;
+ }
+ }
+
+ // get the default info from the identity....
+ int32_t ep = 0;
+ nsresult testrv = aIdentity->GetIntAttribute("encryptionpolicy", &ep);
+ if (NS_FAILED(testrv)) {
+ *aEncrypt = false;
+ }
+ else {
+ *aEncrypt = (ep > 0);
+ }
+
+ testrv = aIdentity->GetBoolAttribute("sign_mail", aSignMessage);
+ if (NS_FAILED(testrv))
+ {
+ *aSignMessage = false;
+ }
+ return NS_OK;
+}
+
+// Select a hash algorithm to sign message
+// based on subject public key type and size.
+static nsresult
+GetSigningHashFunction(nsIX509Cert *aSigningCert, int16_t *hashType)
+{
+ // Get the signing certificate
+ CERTCertificate *scert = nullptr;
+ if (aSigningCert) {
+ scert = aSigningCert->GetCert();
+ }
+ if (!scert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniqueSECKEYPublicKey scertPublicKey(CERT_ExtractPublicKey(scert));
+ if (!scertPublicKey) {
+ return mozilla::MapSECStatus(SECFailure);
+ }
+ KeyType subjectPublicKeyType = SECKEY_GetPublicKeyType(scertPublicKey.get());
+
+ // Get the length of the signature in bits.
+ unsigned siglen = SECKEY_SignatureLen(scertPublicKey.get()) * 8;
+ if (!siglen) {
+ return mozilla::MapSECStatus(SECFailure);
+ }
+
+ // Select a hash function for signature generation whose security strength
+ // meets or exceeds the security strength of the public key, using NIST
+ // Special Publication 800-57, Recommendation for Key Management - Part 1:
+ // General (Revision 3), where Table 2 specifies the security strength of
+ // the public key and Table 3 lists acceptable hash functions. (The security
+ // strength of the hash (for digital signatures) is half the length of the
+ // output.)
+ // [SP 800-57 is available at http://csrc.nist.gov/publications/PubsSPs.html.]
+ if (subjectPublicKeyType == rsaKey) {
+ // For RSA, siglen is the same as the length of the modulus.
+
+ // SHA-1 provides equivalent security strength for up to 1024 bits
+ // SHA-256 provides equivalent security strength for up to 3072 bits
+
+ if (siglen > 3072) {
+ *hashType = nsICryptoHash::SHA512;
+ } else if (siglen > 1024) {
+ *hashType = nsICryptoHash::SHA256;
+ } else {
+ *hashType = nsICryptoHash::SHA1;
+ }
+ } else if (subjectPublicKeyType == dsaKey) {
+ // For DSA, siglen is twice the length of the q parameter of the key.
+ // The security strength of the key is half the length (in bits) of
+ // the q parameter of the key.
+
+ // NSS only supports SHA-1, SHA-224, and SHA-256 for DSA signatures.
+ // The S/MIME code does not support SHA-224.
+
+ if (siglen >= 512) { // 512-bit signature = 256-bit q parameter
+ *hashType = nsICryptoHash::SHA256;
+ } else {
+ *hashType = nsICryptoHash::SHA1;
+ }
+ } else if (subjectPublicKeyType == ecKey) {
+ // For ECDSA, siglen is twice the length of the field size. The security
+ // strength of the key is half the length (in bits) of the field size.
+
+ if (siglen >= 1024) { // 1024-bit signature = 512-bit field size
+ *hashType = nsICryptoHash::SHA512;
+ } else if (siglen >= 768) { // 768-bit signature = 384-bit field size
+ *hashType = nsICryptoHash::SHA384;
+ } else if (siglen >= 512) { // 512-bit signature = 256-bit field size
+ *hashType = nsICryptoHash::SHA256;
+ } else {
+ *hashType = nsICryptoHash::SHA1;
+ }
+ } else {
+ // Unknown key type
+ *hashType = nsICryptoHash::SHA256;
+ NS_WARNING("GetSigningHashFunction: Subject public key type unknown.");
+ }
+ return NS_OK;
+}
+
+/* void beginCryptoEncapsulation (in nsOutputFileStream aStream, in boolean aEncrypt, in boolean aSign, in string aRecipeints, in boolean aIsDraft); */
+NS_IMETHODIMP nsMsgComposeSecure::BeginCryptoEncapsulation(nsIOutputStream * aStream,
+ const char * aRecipients,
+ nsIMsgCompFields * aCompFields,
+ nsIMsgIdentity * aIdentity,
+ nsIMsgSendReport *sendReport,
+ bool aIsDraft)
+{
+ mErrorAlreadyReported = false;
+ nsresult rv = NS_OK;
+
+ bool encryptMessages = false;
+ bool signMessage = false;
+ ExtractEncryptionState(aIdentity, aCompFields, &signMessage, &encryptMessages);
+
+ if (!signMessage && !encryptMessages) return NS_ERROR_FAILURE;
+
+ mStream = aStream;
+ mIsDraft = aIsDraft;
+
+ if (encryptMessages && signMessage)
+ mCryptoState = mime_crypto_signed_encrypted;
+ else if (encryptMessages)
+ mCryptoState = mime_crypto_encrypted;
+ else if (signMessage)
+ mCryptoState = mime_crypto_clear_signed;
+ else
+ PR_ASSERT(0);
+
+ aIdentity->GetUnicharAttribute("signing_cert_name", mSigningCertName);
+ aIdentity->GetCharAttribute("signing_cert_dbkey", mSigningCertDBKey);
+ aIdentity->GetUnicharAttribute("encryption_cert_name", mEncryptionCertName);
+ aIdentity->GetCharAttribute("encryption_cert_dbkey", mEncryptionCertDBKey);
+
+ rv = MimeCryptoHackCerts(aRecipients, sendReport, encryptMessages, signMessage, aIdentity);
+ if (NS_FAILED(rv)) {
+ goto FAIL;
+ }
+
+ if (signMessage && mSelfSigningCert) {
+ rv = GetSigningHashFunction(mSelfSigningCert, &mHashType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ switch (mCryptoState)
+ {
+ case mime_crypto_clear_signed:
+ rv = MimeInitMultipartSigned(true, sendReport);
+ break;
+ case mime_crypto_opaque_signed:
+ PR_ASSERT(0); /* #### no api for this yet */
+ rv = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case mime_crypto_signed_encrypted:
+ rv = MimeInitEncryption(true, sendReport);
+ break;
+ case mime_crypto_encrypted:
+ rv = MimeInitEncryption(false, sendReport);
+ break;
+ case mime_crypto_none:
+ /* This can happen if mime_crypto_hack_certs() decided to turn off
+ encryption (by asking the user.) */
+ // XXX 1 is not a valid nsresult
+ rv = static_cast<nsresult>(1);
+ break;
+ default:
+ PR_ASSERT(0);
+ break;
+ }
+
+FAIL:
+ return rv;
+}
+
+/* void finishCryptoEncapsulation (in boolean aAbort); */
+NS_IMETHODIMP nsMsgComposeSecure::FinishCryptoEncapsulation(bool aAbort, nsIMsgSendReport *sendReport)
+{
+ nsresult rv = NS_OK;
+
+ if (!aAbort) {
+ switch (mCryptoState) {
+ case mime_crypto_clear_signed:
+ rv = MimeFinishMultipartSigned (true, sendReport);
+ break;
+ case mime_crypto_opaque_signed:
+ PR_ASSERT(0); /* #### no api for this yet */
+ rv = NS_ERROR_FAILURE;
+ break;
+ case mime_crypto_signed_encrypted:
+ rv = MimeFinishEncryption (true, sendReport);
+ break;
+ case mime_crypto_encrypted:
+ rv = MimeFinishEncryption (false, sendReport);
+ break;
+ default:
+ PR_ASSERT(0);
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgComposeSecure::MimeInitMultipartSigned(bool aOuter, nsIMsgSendReport *sendReport)
+{
+ /* First, construct and write out the multipart/signed MIME header data.
+ */
+ nsresult rv = NS_OK;
+ char *header = 0;
+ uint32_t L;
+
+ rv = make_multipart_signed_header_string(aOuter, &header,
+ &mMultipartSignedBoundary, mHashType);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ L = strlen(header);
+
+ if (aOuter){
+ /* If this is the outer block, write it to the file. */
+ uint32_t n;
+ rv = mStream->Write(header, L, &n);
+ if (NS_FAILED(rv) || n < L) {
+ // XXX This is -1, not an nsresult
+ rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE);
+ }
+ } else {
+ /* If this is an inner block, feed it through the crypto stream. */
+ rv = MimeCryptoWriteBlock (header, L);
+ }
+
+ PR_Free(header);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* Now initialize the crypto library, so that we can compute a hash
+ on the object which we are signing.
+ */
+
+ PR_SetError(0,0);
+ mDataHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDataHash->Init(mHashType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PR_SetError(0,0);
+ return rv;
+}
+
+nsresult nsMsgComposeSecure::MimeInitEncryption(bool aSign, nsIMsgSendReport *sendReport)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> sMIMEBundle;
+ nsString mime_smime_enc_content_desc;
+
+ bundleSvc->CreateBundle(SMIME_STRBUNDLE_URL, getter_AddRefs(sMIMEBundle));
+
+ if (!sMIMEBundle)
+ return NS_ERROR_FAILURE;
+
+ sMIMEBundle->GetStringFromName(u"mime_smimeEncryptedContentDesc",
+ getter_Copies(mime_smime_enc_content_desc));
+ NS_ConvertUTF16toUTF8 enc_content_desc_utf8(mime_smime_enc_content_desc);
+
+ nsCOMPtr<nsIMimeConverter> mimeConverter =
+ do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString encodedContentDescription;
+ mimeConverter->EncodeMimePartIIStr_UTF8(enc_content_desc_utf8, false, "UTF-8",
+ sizeof("Content-Description: "),
+ nsIMimeConverter::MIME_ENCODED_WORD_SIZE,
+ encodedContentDescription);
+
+ /* First, construct and write out the opaque-crypto-blob MIME header data.
+ */
+
+ char *s =
+ PR_smprintf("Content-Type: " APPLICATION_PKCS7_MIME
+ "; name=\"smime.p7m\"; smime-type=enveloped-data" CRLF
+ "Content-Transfer-Encoding: " ENCODING_BASE64 CRLF
+ "Content-Disposition: attachment"
+ "; filename=\"smime.p7m\"" CRLF
+ "Content-Description: %s" CRLF
+ CRLF,
+ encodedContentDescription.get());
+
+ uint32_t L;
+ if (!s) return NS_ERROR_OUT_OF_MEMORY;
+ L = strlen(s);
+ uint32_t n;
+ rv = mStream->Write(s, L, &n);
+ if (NS_FAILED(rv) || n < L) {
+ return NS_ERROR_FAILURE;
+ }
+ PR_Free(s);
+ s = 0;
+
+ /* Now initialize the crypto library, so that we can filter the object
+ to be encrypted through it.
+ */
+
+ if (!mIsDraft) {
+ uint32_t numCerts;
+ mCerts->GetLength(&numCerts);
+ PR_ASSERT(numCerts > 0);
+ if (numCerts == 0) return NS_ERROR_FAILURE;
+ }
+
+ // Initialize the base64 encoder
+ MOZ_ASSERT(!mCryptoEncoder, "Shouldn't have an encoder already");
+ mCryptoEncoder = MimeEncoder::GetBase64Encoder(mime_encoder_output_fn,
+ this);
+
+ /* Initialize the encrypter (and add the sender's cert.) */
+ PR_ASSERT(mSelfEncryptionCert);
+ PR_SetError(0,0);
+ mEncryptionCinfo = do_CreateInstance(NS_CMSMESSAGE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ rv = mEncryptionCinfo->CreateEncrypted(mCerts);
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorEncryptMail");
+ goto FAIL;
+ }
+
+ mEncryptionContext = do_CreateInstance(NS_CMSENCODER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mBuffer) {
+ mBuffer = new char[eBufferSize];
+ if (!mBuffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mBufferedBytes = 0;
+
+ rv = mEncryptionContext->Start(mEncryptionCinfo, mime_crypto_write_base64, mCryptoEncoder);
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorEncryptMail");
+ goto FAIL;
+ }
+
+ /* If we're signing, tack a multipart/signed header onto the front of
+ the data to be encrypted, and initialize the sign-hashing code too.
+ */
+ if (aSign) {
+ rv = MimeInitMultipartSigned(false, sendReport);
+ if (NS_FAILED(rv)) goto FAIL;
+ }
+
+ FAIL:
+ return rv;
+}
+
+nsresult nsMsgComposeSecure::MimeFinishMultipartSigned (bool aOuter, nsIMsgSendReport *sendReport)
+{
+ int status;
+ nsresult rv;
+ nsCOMPtr<nsICMSMessage> cinfo = do_CreateInstance(NS_CMSMESSAGE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICMSEncoder> encoder = do_CreateInstance(NS_CMSENCODER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char * header = nullptr;
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> sMIMEBundle;
+ nsString mime_smime_sig_content_desc;
+
+ bundleSvc->CreateBundle(SMIME_STRBUNDLE_URL, getter_AddRefs(sMIMEBundle));
+
+ if (!sMIMEBundle)
+ return NS_ERROR_FAILURE;
+
+ sMIMEBundle->GetStringFromName(u"mime_smimeSignatureContentDesc",
+ getter_Copies(mime_smime_sig_content_desc));
+
+ NS_ConvertUTF16toUTF8 sig_content_desc_utf8(mime_smime_sig_content_desc);
+
+ /* Compute the hash...
+ */
+
+ nsAutoCString hashString;
+ mDataHash->Finish(false, hashString);
+
+ mDataHash = nullptr;
+
+ status = PR_GetError();
+ if (status < 0) goto FAIL;
+
+ /* Write out the headers for the signature.
+ */
+ uint32_t L;
+ header =
+ PR_smprintf(CRLF
+ "--%s" CRLF
+ "Content-Type: " APPLICATION_PKCS7_SIGNATURE
+ "; name=\"smime.p7s\"" CRLF
+ "Content-Transfer-Encoding: " ENCODING_BASE64 CRLF
+ "Content-Disposition: attachment; "
+ "filename=\"smime.p7s\"" CRLF
+ "Content-Description: %s" CRLF
+ CRLF,
+ mMultipartSignedBoundary,
+ sig_content_desc_utf8.get());
+
+ if (!header) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+
+ L = strlen(header);
+ if (aOuter) {
+ /* If this is the outer block, write it to the file. */
+ uint32_t n;
+ rv = mStream->Write(header, L, &n);
+ if (NS_FAILED(rv) || n < L) {
+ // XXX This is -1, not an nsresult
+ rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE);
+ }
+ } else {
+ /* If this is an inner block, feed it through the crypto stream. */
+ rv = MimeCryptoWriteBlock (header, L);
+ }
+
+ PR_Free(header);
+
+ /* Create the signature...
+ */
+
+ NS_ASSERTION(mHashType, "Hash function for signature has not been set.");
+
+ PR_ASSERT (mSelfSigningCert);
+ PR_SetError(0,0);
+
+ rv = cinfo->CreateSigned(mSelfSigningCert, mSelfEncryptionCert,
+ (unsigned char*)hashString.get(), hashString.Length(), mHashType);
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorCanNotSignMail");
+ goto FAIL;
+ }
+
+ // Initialize the base64 encoder for the signature data.
+ MOZ_ASSERT(!mSigEncoder, "Shouldn't already have a mSigEncoder");
+ mSigEncoder = MimeEncoder::GetBase64Encoder(
+ (aOuter ? mime_encoder_output_fn : mime_nested_encoder_output_fn), this);
+
+ /* Write out the signature.
+ */
+ PR_SetError(0,0);
+ rv = encoder->Start(cinfo, mime_crypto_write_base64, mSigEncoder);
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorCanNotSignMail");
+ goto FAIL;
+ }
+
+ // We're not passing in any data, so no update needed.
+ rv = encoder->Finish();
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorCanNotSignMail");
+ goto FAIL;
+ }
+
+ // Shut down the sig's base64 encoder.
+ rv = mSigEncoder->Flush();
+ mSigEncoder = nullptr;
+ if (NS_FAILED(rv)) {
+ goto FAIL;
+ }
+
+ /* Now write out the terminating boundary.
+ */
+ {
+ uint32_t L;
+ char *header = PR_smprintf(CRLF "--%s--" CRLF,
+ mMultipartSignedBoundary);
+ PR_Free(mMultipartSignedBoundary);
+ mMultipartSignedBoundary = 0;
+
+ if (!header) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+ L = strlen(header);
+ if (aOuter) {
+ /* If this is the outer block, write it to the file. */
+ uint32_t n;
+ rv = mStream->Write(header, L, &n);
+ if (NS_FAILED(rv) || n < L)
+ // XXX This is -1, not an nsresult
+ rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE);
+ } else {
+ /* If this is an inner block, feed it through the crypto stream. */
+ rv = MimeCryptoWriteBlock (header, L);
+ }
+ }
+
+FAIL:
+ return rv;
+}
+
+
+/* Helper function for mime_finish_crypto_encapsulation() to close off
+ an opaque crypto object (for encrypted or signed-and-encrypted messages.)
+ */
+nsresult nsMsgComposeSecure::MimeFinishEncryption (bool aSign, nsIMsgSendReport *sendReport)
+{
+ nsresult rv;
+
+ /* If this object is both encrypted and signed, close off the
+ signature first (since it's inside.) */
+ if (aSign) {
+ rv = MimeFinishMultipartSigned (false, sendReport);
+ if (NS_FAILED(rv)) {
+ goto FAIL;
+ }
+ }
+
+ /* Close off the opaque encrypted blob.
+ */
+ PR_ASSERT(mEncryptionContext);
+
+ if (mBufferedBytes) {
+ rv = mEncryptionContext->Update(mBuffer, mBufferedBytes);
+ mBufferedBytes = 0;
+ if (NS_FAILED(rv)) {
+ PR_ASSERT(PR_GetError() < 0);
+ goto FAIL;
+ }
+ }
+
+ rv = mEncryptionContext->Finish();
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorEncryptMail");
+ goto FAIL;
+ }
+
+ mEncryptionContext = nullptr;
+
+ PR_ASSERT(mEncryptionCinfo);
+ if (!mEncryptionCinfo) {
+ rv = NS_ERROR_FAILURE;
+ }
+ if (mEncryptionCinfo) {
+ mEncryptionCinfo = nullptr;
+ }
+
+ // Shut down the base64 encoder.
+ mCryptoEncoder->Flush();
+ mCryptoEncoder = nullptr;
+
+ uint32_t n;
+ rv = mStream->Write(CRLF, 2, &n);
+ if (NS_FAILED(rv) || n < 2)
+ rv = NS_ERROR_FAILURE;
+
+ FAIL:
+ return rv;
+}
+
+/* Used to figure out what certs should be used when encrypting this message.
+ */
+nsresult nsMsgComposeSecure::MimeCryptoHackCerts(const char *aRecipients,
+ nsIMsgSendReport *sendReport,
+ bool aEncrypt,
+ bool aSign,
+ nsIMsgIdentity *aIdentity)
+{
+ nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID);
+ nsresult res;
+
+ mCerts = do_CreateInstance(NS_ARRAY_CONTRACTID, &res);
+ if (NS_FAILED(res)) {
+ return res;
+ }
+
+ PR_ASSERT(aEncrypt || aSign);
+
+ /*
+ Signing and encryption certs use the following (per-identity) preferences:
+ - "signing_cert_name"/"encryption_cert_name": a string specifying the
+ nickname of the certificate
+ - "signing_cert_dbkey"/"encryption_cert_dbkey": a Base64 encoded blob
+ specifying an nsIX509Cert dbKey (represents serial number
+ and issuer DN, which is considered to be unique for X.509 certificates)
+
+ When retrieving the prefs, we try (in this order):
+ 1) *_cert_dbkey, if available
+ 2) *_cert_name (for maintaining backwards compatibility with preference
+ attributes written by earlier versions)
+ */
+
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
+
+ UniqueCERTCertList builtChain;
+ if (!mEncryptionCertDBKey.IsEmpty()) {
+ certdb->FindCertByDBKey(mEncryptionCertDBKey.get(),
+ getter_AddRefs(mSelfEncryptionCert));
+ if (mSelfEncryptionCert &&
+ (certVerifier->VerifyCert(mSelfEncryptionCert->GetCert(),
+ certificateUsageEmailRecipient,
+ mozilla::pkix::Now(),
+ nullptr, nullptr,
+ builtChain) != mozilla::pkix::Success)) {
+ // not suitable for encryption, so unset cert and clear pref
+ mSelfEncryptionCert = nullptr;
+ mEncryptionCertDBKey.Truncate();
+ aIdentity->SetCharAttribute("encryption_cert_dbkey",
+ mEncryptionCertDBKey);
+ }
+ }
+ if (!mSelfEncryptionCert) {
+ certdb->FindEmailEncryptionCert(mEncryptionCertName,
+ getter_AddRefs(mSelfEncryptionCert));
+ }
+
+ // same procedure for the signing cert
+ if (!mSigningCertDBKey.IsEmpty()) {
+ certdb->FindCertByDBKey(mSigningCertDBKey.get(),
+ getter_AddRefs(mSelfSigningCert));
+ if (mSelfSigningCert &&
+ (certVerifier->VerifyCert(mSelfSigningCert->GetCert(),
+ certificateUsageEmailSigner,
+ mozilla::pkix::Now(),
+ nullptr, nullptr,
+ builtChain) != mozilla::pkix::Success)) {
+ // not suitable for signing, so unset cert and clear pref
+ mSelfSigningCert = nullptr;
+ mSigningCertDBKey.Truncate();
+ aIdentity->SetCharAttribute("signing_cert_dbkey", mSigningCertDBKey);
+ }
+ }
+ if (!mSelfSigningCert) {
+ certdb->FindEmailSigningCert(mSigningCertName,
+ getter_AddRefs(mSelfSigningCert));
+ }
+
+ // must have both the signing and encryption certs to sign
+ if (!mSelfSigningCert && aSign) {
+ SetError(sendReport, u"NoSenderSigningCert");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mSelfEncryptionCert && aEncrypt) {
+ SetError(sendReport, u"NoSenderEncryptionCert");
+ return NS_ERROR_FAILURE;
+ }
+
+
+ if (aEncrypt && mSelfEncryptionCert) {
+ // Make sure self's configured cert is prepared for being used
+ // as an email recipient cert.
+ UniqueCERTCertificate nsscert(mSelfEncryptionCert->GetCert());
+ if (!nsscert) {
+ return NS_ERROR_FAILURE;
+ }
+ // XXX: This does not respect the nsNSSShutDownObject protocol.
+ if (CERT_SaveSMimeProfile(nsscert.get(), nullptr, nullptr) != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ /* If the message is to be encrypted, then get the recipient certs */
+ if (aEncrypt) {
+ nsTArray<nsCString> mailboxes;
+ ExtractEmails(EncodedHeader(nsDependentCString(aRecipients)),
+ UTF16ArrayAdapter<>(mailboxes));
+ uint32_t count = mailboxes.Length();
+
+ bool already_added_self_cert = false;
+
+ for (uint32_t i = 0; i < count; i++) {
+ nsCString mailbox_lowercase;
+ ToLowerCase(mailboxes[i], mailbox_lowercase);
+ nsCOMPtr<nsIX509Cert> cert;
+ res = certdb->FindCertByEmailAddress(mailbox_lowercase.get(),
+ getter_AddRefs(cert));
+ if (NS_FAILED(res)) {
+ // Failure to find a valid encryption cert is fatal.
+ // Here I assume that mailbox is ascii rather than utf8.
+ SetErrorWithParam(sendReport,
+ u"MissingRecipientEncryptionCert",
+ mailboxes[i].get());
+
+ return res;
+ }
+
+ /* #### see if recipient requests `signedData'.
+ if (...) no_clearsigning_p = true;
+ (This is the only reason we even bother looking up the certs
+ of the recipients if we're sending a signed-but-not-encrypted
+ message.)
+ */
+
+ bool isSame;
+ if (NS_SUCCEEDED(cert->Equals(mSelfEncryptionCert, &isSame))
+ && isSame) {
+ already_added_self_cert = true;
+ }
+
+ mCerts->AppendElement(cert, false);
+ }
+
+ if (!already_added_self_cert) {
+ mCerts->AppendElement(mSelfEncryptionCert, false);
+ }
+ }
+ return res;
+}
+
+NS_IMETHODIMP nsMsgComposeSecure::MimeCryptoWriteBlock (const char *buf, int32_t size)
+{
+ int status = 0;
+ nsresult rv;
+
+ /* If this is a From line, mangle it before signing it. You just know
+ that something somewhere is going to mangle it later, and that's
+ going to cause the signature check to fail.
+
+ (This assumes that, in the cases where From-mangling must happen,
+ this function is called a line at a time. That happens to be the
+ case.)
+ */
+ if (size >= 5 && buf[0] == 'F' && !strncmp(buf, "From ", 5)) {
+ char mangle[] = ">";
+ nsresult res = MimeCryptoWriteBlock (mangle, 1);
+ if (NS_FAILED(res))
+ return res;
+ // This value will actually be cast back to an nsresult before use, so this
+ // cast is reasonable under the circumstances.
+ status = static_cast<int>(res);
+ }
+
+ /* If we're signing, or signing-and-encrypting, feed this data into
+ the computation of the hash. */
+ if (mDataHash) {
+ PR_SetError(0,0);
+ mDataHash->Update((const uint8_t*) buf, size);
+ status = PR_GetError();
+ if (status < 0) goto FAIL;
+ }
+
+ PR_SetError(0,0);
+ if (mEncryptionContext) {
+ /* If we're encrypting, or signing-and-encrypting, write this data
+ by filtering it through the crypto library. */
+
+ /* We want to create equally sized encryption strings */
+ const char *inputBytesIterator = buf;
+ uint32_t inputBytesLeft = size;
+
+ while (inputBytesLeft) {
+ const uint32_t spaceLeftInBuffer = eBufferSize - mBufferedBytes;
+ const uint32_t bytesToAppend = std::min(inputBytesLeft, spaceLeftInBuffer);
+
+ memcpy(mBuffer+mBufferedBytes, inputBytesIterator, bytesToAppend);
+ mBufferedBytes += bytesToAppend;
+
+ inputBytesIterator += bytesToAppend;
+ inputBytesLeft -= bytesToAppend;
+
+ if (eBufferSize == mBufferedBytes) {
+ rv = mEncryptionContext->Update(mBuffer, mBufferedBytes);
+ mBufferedBytes = 0;
+ if (NS_FAILED(rv)) {
+ status = PR_GetError();
+ PR_ASSERT(status < 0);
+ if (status >= 0) status = -1;
+ goto FAIL;
+ }
+ }
+ }
+ } else {
+ /* If we're not encrypting (presumably just signing) then write this
+ data directly to the file. */
+
+ uint32_t n;
+ rv = mStream->Write(buf, size, &n);
+ if (NS_FAILED(rv) || n < (uint32_t)size) {
+ // XXX MK_MIME_ERROR_WRITING_FILE is -1, which is not a valid nsresult
+ return static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE);
+ }
+ }
+ FAIL:
+ // XXX status sometimes has invalid nsresults like -1 or PR_GetError()
+ // assigned to it
+ return static_cast<nsresult>(status);
+}
+
+/* Returns a string consisting of a Content-Type header, and a boundary
+ string, suitable for moving from the header block, down into the body
+ of a multipart object. The boundary itself is also returned (so that
+ the caller knows what to write to close it off.)
+ */
+static nsresult
+make_multipart_signed_header_string(bool outer_p,
+ char **header_return,
+ char **boundary_return,
+ int16_t hash_type)
+{
+ const char *hashStr;
+ *header_return = 0;
+ *boundary_return = mime_make_separator("ms");
+
+ if (!*boundary_return)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ switch (hash_type) {
+ case nsICryptoHash::SHA1:
+ hashStr = PARAM_MICALG_SHA1;
+ break;
+ case nsICryptoHash::SHA256:
+ hashStr = PARAM_MICALG_SHA256;
+ break;
+ case nsICryptoHash::SHA384:
+ hashStr = PARAM_MICALG_SHA384;
+ break;
+ case nsICryptoHash::SHA512:
+ hashStr = PARAM_MICALG_SHA512;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *header_return = PR_smprintf(
+ "Content-Type: " MULTIPART_SIGNED "; "
+ "protocol=\"" APPLICATION_PKCS7_SIGNATURE "\"; "
+ "micalg=%s; "
+ "boundary=\"%s\"" CRLF
+ CRLF
+ "%s%s"
+ "--%s" CRLF,
+ hashStr,
+ *boundary_return,
+ (outer_p ? crypto_multipart_blurb : ""),
+ (outer_p ? CRLF CRLF : ""),
+ *boundary_return);
+
+ if (!*header_return) {
+ PR_Free(*boundary_return);
+ *boundary_return = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+/* Used as the output function of a SEC_PKCS7EncoderContext -- we feed
+ plaintext into the crypto engine, and it calls this function with encrypted
+ data; then this function writes a base64-encoded representation of that
+ data to the file (by filtering it through the given MimeEncoder object.)
+
+ Also used as the output function of SEC_PKCS7Encode() -- but in that case,
+ it's used to write the encoded representation of the signature. The only
+ difference is which MimeEncoder object is used.
+ */
+static void
+mime_crypto_write_base64 (void *closure, const char *buf, unsigned long size)
+{
+ MimeEncoder *encoder = (MimeEncoder *) closure;
+ nsresult rv = encoder->Write(buf, size);
+ PR_SetError(NS_FAILED(rv) ? static_cast<uint32_t>(rv) : 0, 0);
+}
+
+
+/* Used as the output function of MimeEncoder -- when we have generated
+ the signature for a multipart/signed object, this is used to write the
+ base64-encoded representation of the signature to the file.
+ */
+// TODO: size should probably be converted to uint32_t
+nsresult mime_encoder_output_fn(const char *buf, int32_t size, void *closure)
+{
+ nsMsgComposeSecure *state = (nsMsgComposeSecure *) closure;
+ nsCOMPtr<nsIOutputStream> stream;
+ state->GetOutputStream(getter_AddRefs(stream));
+ uint32_t n;
+ nsresult rv = stream->Write((char *) buf, size, &n);
+ if (NS_FAILED(rv) || n < (uint32_t)size)
+ return NS_ERROR_FAILURE;
+ else
+ return NS_OK;
+}
+
+/* Like mime_encoder_output_fn, except this is used for the case where we
+ are both signing and encrypting -- the base64-encoded output of the
+ signature should be fed into the crypto engine, rather than being written
+ directly to the file.
+ */
+static nsresult
+mime_nested_encoder_output_fn (const char *buf, int32_t size, void *closure)
+{
+ nsMsgComposeSecure *state = (nsMsgComposeSecure *) closure;
+
+ // Copy to new null-terminated string so JS glue doesn't crash when
+ // MimeCryptoWriteBlock() is implemented in JS.
+ nsCString bufWithNull;
+ bufWithNull.Assign(buf, size);
+ return state->MimeCryptoWriteBlock(bufWithNull.get(), size);
+}
diff --git a/mailnews/extensions/smime/src/nsMsgComposeSecure.h b/mailnews/extensions/smime/src/nsMsgComposeSecure.h
new file mode 100644
index 000000000..0f3b9ac60
--- /dev/null
+++ b/mailnews/extensions/smime/src/nsMsgComposeSecure.h
@@ -0,0 +1,106 @@
+/* -*- Mode: idl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsMsgComposeSecure_H_
+#define _nsMsgComposeSecure_H_
+
+#include "nsIMsgComposeSecure.h"
+#include "nsIMsgSMIMECompFields.h"
+#include "nsCOMPtr.h"
+#include "nsICMSEncoder.h"
+#include "nsIX509Cert.h"
+#include "nsIStringBundle.h"
+#include "nsICryptoHash.h"
+#include "nsICMSMessage.h"
+#include "nsIMutableArray.h"
+#include "nsStringGlue.h"
+#include "nsIOutputStream.h"
+#include "nsAutoPtr.h"
+
+class nsIMsgCompFields;
+namespace mozilla {
+namespace mailnews {
+class MimeEncoder;
+}
+}
+
+class nsMsgSMIMEComposeFields : public nsIMsgSMIMECompFields
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSMIMECOMPFIELDS
+
+ nsMsgSMIMEComposeFields();
+
+private:
+ virtual ~nsMsgSMIMEComposeFields();
+ bool mSignMessage;
+ bool mAlwaysEncryptMessage;
+};
+
+typedef enum {
+ mime_crypto_none, /* normal unencapsulated MIME message */
+ mime_crypto_clear_signed, /* multipart/signed encapsulation */
+ mime_crypto_opaque_signed, /* application/x-pkcs7-mime (signedData) */
+ mime_crypto_encrypted, /* application/x-pkcs7-mime */
+ mime_crypto_signed_encrypted /* application/x-pkcs7-mime */
+} mimeDeliveryCryptoState;
+
+class nsMsgComposeSecure : public nsIMsgComposeSecure
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOMPOSESECURE
+
+ nsMsgComposeSecure();
+
+ void GetOutputStream(nsIOutputStream **stream) { NS_IF_ADDREF(*stream = mStream);}
+ nsresult GetSMIMEBundleString(const char16_t *name, nsString &outString);
+
+private:
+ virtual ~nsMsgComposeSecure();
+ typedef mozilla::mailnews::MimeEncoder MimeEncoder;
+ nsresult MimeInitMultipartSigned(bool aOuter, nsIMsgSendReport *sendReport);
+ nsresult MimeInitEncryption(bool aSign, nsIMsgSendReport *sendReport);
+ nsresult MimeFinishMultipartSigned (bool aOuter, nsIMsgSendReport *sendReport);
+ nsresult MimeFinishEncryption (bool aSign, nsIMsgSendReport *sendReport);
+ nsresult MimeCryptoHackCerts(const char *aRecipients, nsIMsgSendReport *sendReport, bool aEncrypt, bool aSign, nsIMsgIdentity *aIdentity);
+ bool InitializeSMIMEBundle();
+ nsresult SMIMEBundleFormatStringFromName(const char16_t *name,
+ const char16_t **params,
+ uint32_t numParams,
+ char16_t **outString);
+ nsresult ExtractEncryptionState(nsIMsgIdentity * aIdentity, nsIMsgCompFields * aComposeFields, bool * aSignMessage, bool * aEncrypt);
+
+ mimeDeliveryCryptoState mCryptoState;
+ nsCOMPtr<nsIOutputStream> mStream;
+ int16_t mHashType;
+ nsCOMPtr<nsICryptoHash> mDataHash;
+ nsAutoPtr<MimeEncoder> mSigEncoder;
+ char *mMultipartSignedBoundary;
+ nsString mSigningCertName;
+ nsAutoCString mSigningCertDBKey;
+ nsCOMPtr<nsIX509Cert> mSelfSigningCert;
+ nsString mEncryptionCertName;
+ nsAutoCString mEncryptionCertDBKey;
+ nsCOMPtr<nsIX509Cert> mSelfEncryptionCert;
+ nsCOMPtr<nsIMutableArray> mCerts;
+ nsCOMPtr<nsICMSMessage> mEncryptionCinfo;
+ nsCOMPtr<nsICMSEncoder> mEncryptionContext;
+ nsCOMPtr<nsIStringBundle> mSMIMEBundle;
+
+ nsAutoPtr<MimeEncoder> mCryptoEncoder;
+ bool mIsDraft;
+
+ enum {eBufferSize = 8192};
+ char *mBuffer;
+ uint32_t mBufferedBytes;
+
+ bool mErrorAlreadyReported;
+ void SetError(nsIMsgSendReport *sendReport, const char16_t *bundle_string);
+ void SetErrorWithParam(nsIMsgSendReport *sendReport, const char16_t *bundle_string, const char *param);
+};
+
+#endif
diff --git a/mailnews/extensions/smime/src/nsMsgSMIMECID.h b/mailnews/extensions/smime/src/nsMsgSMIMECID.h
new file mode 100644
index 000000000..0fbf2d1bf
--- /dev/null
+++ b/mailnews/extensions/smime/src/nsMsgSMIMECID.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgSMIMECID_h__
+#define nsMsgSMIMECID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+#define NS_MSGSMIMECOMPFIELDS_CONTRACTID \
+ "@mozilla.org/messenger-smime/composefields;1"
+
+#define NS_MSGSMIMECOMPFIELDS_CID \
+{ /* 122C919C-96B7-49a0-BBC8-0ABC67EEFFE0 */ \
+ 0x122c919c, 0x96b7, 0x49a0, \
+ { 0xbb, 0xc8, 0xa, 0xbc, 0x67, 0xee, 0xff, 0xe0 }}
+
+#define NS_MSGCOMPOSESECURE_CID \
+{ /* dd753201-9a23-4e08-957f-b3616bf7e012 */ \
+ 0xdd753201, 0x9a23, 0x4e08, \
+ {0x95, 0x7f, 0xb3, 0x61, 0x6b, 0xf7, 0xe0, 0x12 }}
+
+#define NS_SMIMEJSHELPER_CONTRACTID \
+ "@mozilla.org/messenger-smime/smimejshelper;1"
+
+#define NS_SMIMEJSJELPER_CID \
+{ /* d57d928c-60e4-4f81-999d-5c762e611205 */ \
+ 0xd57d928c, 0x60e4, 0x4f81, \
+ {0x99, 0x9d, 0x5c, 0x76, 0x2e, 0x61, 0x12, 0x05 }}
+
+#define NS_SMIMEENCRYPTURISERVICE_CONTRACTID \
+ "@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"
+
+#define NS_SMIMEENCRYPTURISERVICE_CID \
+{ /* a0134d58-018f-4d40-a099-fa079e5024a6 */ \
+ 0xa0134d58, 0x018f, 0x4d40, \
+ {0xa0, 0x99, 0xfa, 0x07, 0x9e, 0x50, 0x24, 0xa6 }}
+
+#endif // nsMsgSMIMECID_h__
diff --git a/mailnews/extensions/smime/src/nsSMimeJSHelper.cpp b/mailnews/extensions/smime/src/nsSMimeJSHelper.cpp
new file mode 100644
index 000000000..c392980b6
--- /dev/null
+++ b/mailnews/extensions/smime/src/nsSMimeJSHelper.cpp
@@ -0,0 +1,335 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nspr.h"
+#include "nsSMimeJSHelper.h"
+#include "nsCOMPtr.h"
+#include "nsMemory.h"
+#include "nsStringGlue.h"
+#include "nsIX509CertDB.h"
+#include "nsIX509CertValidity.h"
+#include "nsIServiceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCRTGlue.h"
+
+using namespace mozilla::mailnews;
+
+NS_IMPL_ISUPPORTS(nsSMimeJSHelper, nsISMimeJSHelper)
+
+nsSMimeJSHelper::nsSMimeJSHelper()
+{
+}
+
+nsSMimeJSHelper::~nsSMimeJSHelper()
+{
+}
+
+NS_IMETHODIMP nsSMimeJSHelper::GetRecipientCertsInfo(
+ nsIMsgCompFields *compFields,
+ uint32_t *count,
+ char16_t ***emailAddresses,
+ int32_t **certVerification,
+ char16_t ***certIssuedInfos,
+ char16_t ***certExpiresInfos,
+ nsIX509Cert ***certs,
+ bool *canEncrypt)
+{
+ NS_ENSURE_ARG_POINTER(count);
+ *count = 0;
+
+ NS_ENSURE_ARG_POINTER(emailAddresses);
+ NS_ENSURE_ARG_POINTER(certVerification);
+ NS_ENSURE_ARG_POINTER(certIssuedInfos);
+ NS_ENSURE_ARG_POINTER(certExpiresInfos);
+ NS_ENSURE_ARG_POINTER(certs);
+ NS_ENSURE_ARG_POINTER(canEncrypt);
+
+ NS_ENSURE_ARG_POINTER(compFields);
+
+ nsTArray<nsCString> mailboxes;
+ nsresult rv = getMailboxList(compFields, mailboxes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t mailbox_count = mailboxes.Length();
+
+ nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID);
+
+ *count = mailbox_count;
+ *canEncrypt = false;
+ rv = NS_OK;
+
+ if (mailbox_count)
+ {
+ char16_t **outEA = static_cast<char16_t **>(moz_xmalloc(mailbox_count * sizeof(char16_t *)));
+ int32_t *outCV = static_cast<int32_t *>(moz_xmalloc(mailbox_count * sizeof(int32_t)));
+ char16_t **outCII = static_cast<char16_t **>(moz_xmalloc(mailbox_count * sizeof(char16_t *)));
+ char16_t **outCEI = static_cast<char16_t **>(moz_xmalloc(mailbox_count * sizeof(char16_t *)));
+ nsIX509Cert **outCerts = static_cast<nsIX509Cert **>(moz_xmalloc(mailbox_count * sizeof(nsIX509Cert *)));
+
+ if (!outEA || !outCV || !outCII || !outCEI || !outCerts)
+ {
+ free(outEA);
+ free(outCV);
+ free(outCII);
+ free(outCEI);
+ free(outCerts);
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ {
+ char16_t **iEA = outEA;
+ int32_t *iCV = outCV;
+ char16_t **iCII = outCII;
+ char16_t **iCEI = outCEI;
+ nsIX509Cert **iCert = outCerts;
+
+ bool found_blocker = false;
+ bool memory_failure = false;
+
+ for (uint32_t i = 0;
+ i < mailbox_count;
+ ++i, ++iEA, ++iCV, ++iCII, ++iCEI, ++iCert)
+ {
+ *iCert = nullptr;
+ *iCV = 0;
+ *iCII = nullptr;
+ *iCEI = nullptr;
+
+ if (memory_failure) {
+ *iEA = nullptr;
+ continue;
+ }
+
+ nsCString &email = mailboxes[i];
+ *iEA = ToNewUnicode(NS_ConvertUTF8toUTF16(email));
+ if (!*iEA) {
+ memory_failure = true;
+ continue;
+ }
+
+ nsCString email_lowercase;
+ ToLowerCase(email, email_lowercase);
+
+ nsCOMPtr<nsIX509Cert> cert;
+ if (NS_SUCCEEDED(certdb->FindCertByEmailAddress(
+ email_lowercase.get(), getter_AddRefs(cert))))
+ {
+ *iCert = cert;
+ NS_ADDREF(*iCert);
+
+ nsCOMPtr<nsIX509CertValidity> validity;
+ rv = cert->GetValidity(getter_AddRefs(validity));
+
+ if (NS_SUCCEEDED(rv)) {
+ nsString id, ed;
+
+ if (NS_SUCCEEDED(validity->GetNotBeforeLocalDay(id)))
+ {
+ *iCII = ToNewUnicode(id);
+ if (!*iCII) {
+ memory_failure = true;
+ continue;
+ }
+ }
+
+ if (NS_SUCCEEDED(validity->GetNotAfterLocalDay(ed)))
+ {
+ *iCEI = ToNewUnicode(ed);
+ if (!*iCEI) {
+ memory_failure = true;
+ continue;
+ }
+ }
+ }
+ }
+ else
+ {
+ found_blocker = true;
+ }
+ }
+
+ if (memory_failure) {
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mailbox_count, outEA);
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mailbox_count, outCII);
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mailbox_count, outCEI);
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(mailbox_count, outCerts);
+ free(outCV);
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ else {
+ if (mailbox_count > 0 && !found_blocker)
+ {
+ *canEncrypt = true;
+ }
+
+ *emailAddresses = outEA;
+ *certVerification = outCV;
+ *certIssuedInfos = outCII;
+ *certExpiresInfos = outCEI;
+ *certs = outCerts;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsSMimeJSHelper::GetNoCertAddresses(
+ nsIMsgCompFields *compFields,
+ uint32_t *count,
+ char16_t ***emailAddresses)
+{
+ NS_ENSURE_ARG_POINTER(count);
+ *count = 0;
+
+ NS_ENSURE_ARG_POINTER(emailAddresses);
+
+ NS_ENSURE_ARG_POINTER(compFields);
+
+ nsTArray<nsCString> mailboxes;
+ nsresult rv = getMailboxList(compFields, mailboxes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t mailbox_count = mailboxes.Length();
+
+ if (!mailbox_count)
+ {
+ *count = 0;
+ *emailAddresses = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID);
+
+ uint32_t missing_count = 0;
+ bool *haveCert = new bool[mailbox_count];
+ if (!haveCert)
+ {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = NS_OK;
+
+ if (mailbox_count)
+ {
+ for (uint32_t i = 0; i < mailbox_count; ++i)
+ {
+ haveCert[i] = false;
+
+ nsCString email_lowercase;
+ ToLowerCase(mailboxes[i], email_lowercase);
+
+ nsCOMPtr<nsIX509Cert> cert;
+ if (NS_SUCCEEDED(certdb->FindCertByEmailAddress(
+ email_lowercase.get(), getter_AddRefs(cert))))
+ haveCert[i] = true;
+
+ if (!haveCert[i])
+ ++missing_count;
+ }
+ }
+
+ *count = missing_count;
+
+ if (missing_count)
+ {
+ char16_t **outEA = static_cast<char16_t **>(moz_xmalloc(missing_count * sizeof(char16_t *)));
+ if (!outEA )
+ {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ {
+ char16_t **iEA = outEA;
+
+ bool memory_failure = false;
+
+ for (uint32_t i = 0; i < mailbox_count; ++i)
+ {
+ if (!haveCert[i])
+ {
+ if (memory_failure) {
+ *iEA = nullptr;
+ }
+ else {
+ *iEA = ToNewUnicode(NS_ConvertUTF8toUTF16(mailboxes[i]));
+ if (!*iEA) {
+ memory_failure = true;
+ }
+ }
+ ++iEA;
+ }
+ }
+
+ if (memory_failure) {
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(missing_count, outEA);
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ else {
+ *emailAddresses = outEA;
+ }
+ }
+ }
+ else
+ {
+ *emailAddresses = nullptr;
+ }
+
+ delete [] haveCert;
+ return rv;
+}
+
+nsresult nsSMimeJSHelper::getMailboxList(nsIMsgCompFields *compFields,
+ nsTArray<nsCString> &mailboxes)
+{
+ if (!compFields)
+ return NS_ERROR_INVALID_ARG;
+
+ nsresult res;
+ nsString to, cc, bcc, ng;
+
+ res = compFields->GetTo(to);
+ if (NS_FAILED(res))
+ return res;
+
+ res = compFields->GetCc(cc);
+ if (NS_FAILED(res))
+ return res;
+
+ res = compFields->GetBcc(bcc);
+ if (NS_FAILED(res))
+ return res;
+
+ res = compFields->GetNewsgroups(ng);
+ if (NS_FAILED(res))
+ return res;
+
+ {
+ nsCString all_recipients;
+
+ if (!to.IsEmpty()) {
+ all_recipients.Append(NS_ConvertUTF16toUTF8(to));
+ all_recipients.Append(',');
+ }
+
+ if (!cc.IsEmpty()) {
+ all_recipients.Append(NS_ConvertUTF16toUTF8(cc));
+ all_recipients.Append(',');
+ }
+
+ if (!bcc.IsEmpty()) {
+ all_recipients.Append(NS_ConvertUTF16toUTF8(bcc));
+ all_recipients.Append(',');
+ }
+
+ if (!ng.IsEmpty())
+ all_recipients.Append(NS_ConvertUTF16toUTF8(ng));
+
+ ExtractEmails(EncodedHeader(all_recipients),
+ UTF16ArrayAdapter<>(mailboxes));
+ }
+
+ return NS_OK;
+}
diff --git a/mailnews/extensions/smime/src/nsSMimeJSHelper.h b/mailnews/extensions/smime/src/nsSMimeJSHelper.h
new file mode 100644
index 000000000..403ab2098
--- /dev/null
+++ b/mailnews/extensions/smime/src/nsSMimeJSHelper.h
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsSMimeJSHelper_H_
+#define _nsSMimeJSHelper_H_
+
+#include "nsISMimeJSHelper.h"
+#include "nsIX509Cert.h"
+#include "nsIMsgCompFields.h"
+
+class nsSMimeJSHelper : public nsISMimeJSHelper
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISMIMEJSHELPER
+
+ nsSMimeJSHelper();
+
+private:
+ virtual ~nsSMimeJSHelper();
+ nsresult getMailboxList(nsIMsgCompFields *compFields,
+ nsTArray<nsCString> &mailboxes);
+};
+
+#endif
diff --git a/mailnews/extensions/smime/src/smime-service.js b/mailnews/extensions/smime/src/smime-service.js
new file mode 100644
index 000000000..2c84192dd
--- /dev/null
+++ b/mailnews/extensions/smime/src/smime-service.js
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function SMIMEService() {}
+
+SMIMEService.prototype = {
+ name: "smime",
+ chromePackageName: "messenger",
+ showPanel: function(server) {
+ // don't show the panel for news, rss, or local accounts
+ return (server.type != "nntp" && server.type != "rss" &&
+ server.type != "none");
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgAccountManagerExtension]),
+ classID: Components.ID("{f2809796-1dd1-11b2-8c1b-8f15f007c699}"),
+};
+
+var components = [SMIMEService];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/extensions/smime/src/smime-service.manifest b/mailnews/extensions/smime/src/smime-service.manifest
new file mode 100644
index 000000000..1b799ed7b
--- /dev/null
+++ b/mailnews/extensions/smime/src/smime-service.manifest
@@ -0,0 +1,3 @@
+component {f2809796-1dd1-11b2-8c1b-8f15f007c699} smime-service.js
+contract @mozilla.org/accountmanager/extension;1?name=smime {f2809796-1dd1-11b2-8c1b-8f15f007c699}
+category mailnews-accountmanager-extensions smime-account-manager-extension @mozilla.org/accountmanager/extension;1?name=smime
diff --git a/mailnews/imap/public/moz.build b/mailnews/imap/public/moz.build
new file mode 100644
index 000000000..c531cc82b
--- /dev/null
+++ b/mailnews/imap/public/moz.build
@@ -0,0 +1,32 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIAutoSyncFolderStrategy.idl',
+ 'nsIAutoSyncManager.idl',
+ 'nsIAutoSyncMsgStrategy.idl',
+ 'nsIAutoSyncState.idl',
+ 'nsIImapFlagAndUidState.idl',
+ 'nsIImapHeaderXferInfo.idl',
+ 'nsIImapIncomingServer.idl',
+ 'nsIImapMailFolderSink.idl',
+ 'nsIImapMessageSink.idl',
+ 'nsIImapMockChannel.idl',
+ 'nsIImapProtocol.idl',
+ 'nsIImapProtocolSink.idl',
+ 'nsIImapServerSink.idl',
+ 'nsIImapService.idl',
+ 'nsIImapUrl.idl',
+ 'nsIMailboxSpec.idl',
+ 'nsIMsgImapMailFolder.idl',
+]
+
+XPIDL_MODULE = 'msgimap'
+
+EXPORTS += [
+ 'nsIIMAPHostSessionList.h',
+ 'nsMsgImapCID.h',
+]
+
diff --git a/mailnews/imap/public/nsIAutoSyncFolderStrategy.idl b/mailnews/imap/public/nsIAutoSyncFolderStrategy.idl
new file mode 100644
index 000000000..d58b6085f
--- /dev/null
+++ b/mailnews/imap/public/nsIAutoSyncFolderStrategy.idl
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIAutoSyncMsgStrategy.idl"
+
+interface nsIMsgAccount;
+
+[scriptable, uuid(d3bf91cc-37bb-4752-9994-1a8473e46a90)]
+interface nsIAutoSyncFolderStrategy : nsISupports {
+
+ /**
+ * Returns a relative-priority for the second folder by comparing with the first one.
+ */
+ nsAutoSyncStrategyDecisionType sort(in nsIMsgFolder aFolder1, in nsIMsgFolder aFolder2);
+
+ /**
+ * Tests whether the given folder should be excluded or not.
+ */
+ boolean isExcluded(in nsIMsgFolder aFolder);
+
+};
diff --git a/mailnews/imap/public/nsIAutoSyncManager.idl b/mailnews/imap/public/nsIAutoSyncManager.idl
new file mode 100644
index 000000000..0dee48963
--- /dev/null
+++ b/mailnews/imap/public/nsIAutoSyncManager.idl
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAutoSyncMsgStrategy;
+interface nsIAutoSyncFolderStrategy;
+interface nsIMsgDBHdr;
+interface nsIAutoSyncState;
+interface nsIAutoSyncMgrListener;
+interface nsIMsgFolder;
+interface nsIMsgAccount;
+
+[scriptable, uuid(41ec36a7-1a53-4ca3-b698-dca6452a8761)]
+interface nsIAutoSyncMgrListener : nsISupports {
+
+ /**
+ * Queue types
+ */
+ const long PriorityQueue = 1;
+ const long UpdateQueue = 2;
+ const long DiscoveryQueue = 3;
+
+ /**
+ * It is called on the listener when a new folder is added into
+ * the queue
+ *
+ * @param aQType type of the queue
+ * @param aFolder folder that is added into the queue
+ */
+ void onFolderAddedIntoQ(in long aQType, in nsIMsgFolder aFolder);
+
+ /**
+ * It is called on the listener when a folder is removed from
+ * the queue
+ *
+ * @param aQType type of the queue
+ * @param aFolder folder that is removed from the queue
+ */
+ void onFolderRemovedFromQ(in long aQType, in nsIMsgFolder aFolder);
+
+ /**
+ * It is called on the listener when a message download is successfully started
+ *
+ * @param aFolder folder in which the download is started
+ * @param aNumberOfMessages number of the messages that will be downloaded
+ * @param aTotalPending total number of messages waiting to be downloaded
+ */
+ void onDownloadStarted(in nsIMsgFolder aFolder, in unsigned long aNumberOfMessages,
+ in unsigned long aTotalPending);
+ /**
+ * It is called on the listener when a message download on the given folder
+ * is completed
+ */
+ void onDownloadCompleted(in nsIMsgFolder aFolder);
+
+ /**
+ * It is called on the listener when an error occurs during the message download
+ */
+ void onDownloadError(in nsIMsgFolder aFolder);
+
+ /*
+ * Auto-Sync manager is running or waiting for idle
+ */
+ void onStateChanged(in boolean aRunning);
+
+ /**
+ * It is called on the listener after the auto-sync manager starts to process
+ * existing headers of the given folder to find missing message bodies
+ * (mostly for debugging purposes)
+ */
+ void onDiscoveryQProcessed(in nsIMsgFolder aFolder, in unsigned long aNumberOfHdrsProcessed,
+ in unsigned long aLeftToProcess);
+
+ /**
+ * It is called on the listener after the auto-sync manager updates the given folder
+ * (mostly for debugging purposes)
+ */
+ void onAutoSyncInitiated(in nsIMsgFolder aFolder);
+};
+
+
+[scriptable, uuid(7fe0b48e-f5d8-4747-beb7-888c9cced3a5)]
+interface nsIAutoSyncManager : nsISupports {
+
+ /**
+ * Download models
+ */
+ const long dmParallel = 0;
+ const long dmChained = 1;
+
+ /**
+ * Suggested minimum grouping size in bytes for message downloads.
+ * Setting this attribute to 0 resets its value to the
+ * hardcoded default.
+ */
+ attribute unsigned long groupSize;
+
+ /**
+ * Active strategy function to prioritize
+ * messages in the download queue
+ */
+ attribute nsIAutoSyncMsgStrategy msgStrategy;
+
+ /**
+ * Active strategy function to prioritize
+ * folders in the download queue
+ */
+ attribute nsIAutoSyncFolderStrategy folderStrategy;
+
+ /**
+ * Adds a listener to notify about auto-sync events
+ */
+ void addListener(in nsIAutoSyncMgrListener aListener);
+
+ /**
+ * Removes the listener from notification list
+ */
+ void removeListener(in nsIAutoSyncMgrListener aListener);
+
+ /**
+ * Tests the given message to make sure that whether
+ * it fits the download criteria or not
+ */
+ boolean doesMsgFitDownloadCriteria(in nsIMsgDBHdr aMsgHdr);
+
+ /**
+ * Called by the nsAutoSyncState object when the download
+ * queue is changed. Given interface is already addref'd.
+ */
+ void onDownloadQChanged(in nsIAutoSyncState aAutoSyncStateObj);
+
+ /**
+ * Called by the nsAutoSyncState object when the download
+ * is started. Given interface is already addref'd.
+ */
+ void onDownloadStarted(in nsIAutoSyncState aAutoSyncStateObj, in nsresult aStartCode);
+
+ /**
+ * Called by the nsAutoSyncState object when the download
+ * completed. Given interface is already addref'd.
+ */
+ void onDownloadCompleted(in nsIAutoSyncState aAutoSyncStateObj, in nsresult aExitCode);
+
+ /**
+ * Number of elements in the discovery queue.
+ * @see nsAutoSyncManager.h for details
+ */
+ readonly attribute unsigned long discoveryQLength;
+
+ /**
+ * Number of elements in the update queue.
+ * @see nsAutoSyncManager.h for details
+ */
+ readonly attribute unsigned long updateQLength;
+
+ /**
+ * Number of elements in the download queue (a.k.a priority queue).
+ * @see nsAutoSyncManager.h for details
+ */
+ readonly attribute unsigned long downloadQLength;
+
+ /**
+ * Active download model; Chained (serial), or Parallel
+ */
+ attribute long downloadModel;
+
+ /**
+ * The imap folder corresponding to aAutoSyncState has had a message
+ * added to it. Autosync may want to add this folder to the update q.
+ *
+ * @param aAutoSyncState state obj for folder needing updating
+ */
+ void onFolderHasPendingMsgs(in nsIAutoSyncState aAutoSyncState);
+
+ /// Pause autosync (e.g., we're downloading for offline).
+ void pause();
+
+ /// Resume normal autosync activities (e.g., we've come back online).
+ void resume();
+};
+
+%{C++
+#define NS_AUTOSYNCMANAGER_CID \
+{ /* C358C568-47B2-42b2-8146-3C0F8D1FAD6E */ \
+ 0xc358c568, 0x47b2, 0x42b2, \
+ { 0x81, 0x46, 0x3c, 0xf, 0x8d, 0x1f, 0xad, 0x6e }}
+#define NS_AUTOSYNCMANAGER_CLASSNAME \
+ "Auto-Sync Manager"
+#define NS_AUTOSYNCMANAGER_CONTRACTID \
+ "@mozilla.org/imap/autosyncmgr;1"
+%}
+
diff --git a/mailnews/imap/public/nsIAutoSyncMsgStrategy.idl b/mailnews/imap/public/nsIAutoSyncMsgStrategy.idl
new file mode 100644
index 000000000..93633a2cb
--- /dev/null
+++ b/mailnews/imap/public/nsIAutoSyncMsgStrategy.idl
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgDBHdr;
+
+typedef long nsAutoSyncStrategyDecisionType;
+
+[scriptable,uuid(0365bec5-3753-43c2-b13e-441747815f37)]
+interface nsAutoSyncStrategyDecisions
+{
+ /// same priority
+ const nsAutoSyncStrategyDecisionType Same = 0x00000001;
+ /// higher priority
+ const nsAutoSyncStrategyDecisionType Higher = 0x00000002;
+ /// lower priority
+ const nsAutoSyncStrategyDecisionType Lower = 0x00000004;
+};
+
+[scriptable, uuid(9cb4baff-3112-4cf8-8463-f81b0aa78f93)]
+interface nsIAutoSyncMsgStrategy : nsISupports {
+
+ /**
+ * Returns a relative-priority for the second message by comparing with the first message.
+ */
+ nsAutoSyncStrategyDecisionType sort(in nsIMsgFolder aFolder, in nsIMsgDBHdr aMsgHdr1, in nsIMsgDBHdr aMsgHdr2);
+
+ /**
+ * Tests whether the given message should be excluded or not.
+ */
+ boolean isExcluded(in nsIMsgFolder aFolder, in nsIMsgDBHdr aMsgHdr);
+};
diff --git a/mailnews/imap/public/nsIAutoSyncState.idl b/mailnews/imap/public/nsIAutoSyncState.idl
new file mode 100644
index 000000000..d03697e11
--- /dev/null
+++ b/mailnews/imap/public/nsIAutoSyncState.idl
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgFolder;
+interface nsIArray;
+interface nsIMutableArray;
+
+[scriptable, uuid(7512f927-b8f0-48c4-b101-03e859e61281)]
+interface nsIAutoSyncState : nsISupports {
+
+ /**
+ * Auto-Sync states.
+ *
+ * ***WARNING***: If you change these, be sure to update stateStrings in
+ * nsAutoSyncState.cpp. If you do not, out-of-bounds memory accesses may
+ * happen.
+ */
+
+ /** sync'd and no pending messages */
+ const long stCompletedIdle = 0;
+
+ /** STATUS issued. Will check to see if any counts changed since last STATUS */
+ const long stStatusIssued = 1;
+
+ /**
+ * Status found new messages. Update should be issued next. Status most
+ * likely was issued by non-autosync code (e.g., check other folders for
+ * new messages).
+ */
+ const long stUpdateNeeded = 2;
+
+ /** Update issued. Will figure out if there are any bodies to download */
+ const long stUpdateIssued = 3;
+
+ /** Message body download in progress */
+ const long stDownloadInProgress = 4;
+
+ /** ready to download the next group of messages */
+ const long stReadyToDownload = 5;
+
+ /**
+ * Puts the download queue offset to its previous position.
+ */
+ void rollback();
+
+ /**
+ * Clears the download queue. Resets the offsets.
+ */
+ void resetDownloadQ();
+
+ /**
+ * Rollbacks the offset to the previous position and
+ * changes the state to ready-to-download.
+ */
+ void tryCurrentGroupAgain(in unsigned long aRetryCount);
+
+ /**
+ * Resets the retry counter.
+ */
+ void resetRetryCounter();
+
+ /**
+ * Tests whether the given folder has the same imap server.
+ */
+ boolean isSibling(in nsIAutoSyncState aAnotherStateObj);
+
+ /**
+ * Update the folder to find new message headers to download
+ */
+ void updateFolder();
+
+ /**
+ * Downloads the bodies of the given messages from the server.
+ */
+ void downloadMessagesForOffline(in nsIArray aMessageList);
+
+ /**
+ * Populates the given array with the keys of the messages that will
+ * be downloaded next.
+ *
+ * @param aSuggestedGroupSizeLimit suggested size per group in bytes
+ * @param aActualGroupSize total size of the messages in bytes in the group
+ */
+ nsIMutableArray getNextGroupOfMessages(in unsigned long aSuggestedGroupSizeLimit,
+ out unsigned long aActualGroupSize);
+
+ /**
+ * Iterates through the existing headers of the folder to find
+ * the messages not downloaded yet.
+ *
+ * @param aNumberOfHeadersToProcess number of headers to be processed
+ * at this pass
+ *
+ * @return the number of headers left to process
+ */
+ unsigned long processExistingHeaders(in unsigned long aNumberOfHeadersToProcess);
+
+ /**
+ * Last time the existing headers are completely processed.
+ */
+ [noscript]readonly attribute PRTime lastSyncTime;
+
+ /**
+ * Last time the owner folder is updated.
+ */
+ [noscript]attribute PRTime lastUpdateTime;
+
+ /**
+ * Download operation state.
+ */
+ attribute long state;
+
+ /**
+ * Number of messages waiting to be downloaded.
+ */
+ readonly attribute long pendingMessageCount;
+
+ /**
+ * Total number of messages in the download queue.
+ */
+ readonly attribute long totalMessageCount;
+
+ /**
+ * The folder this auto-sync object is related to.
+ */
+ readonly attribute nsIMsgFolder ownerFolder;
+};
diff --git a/mailnews/imap/public/nsIIMAPHostSessionList.h b/mailnews/imap/public/nsIIMAPHostSessionList.h
new file mode 100644
index 000000000..e3ce35d4f
--- /dev/null
+++ b/mailnews/imap/public/nsIIMAPHostSessionList.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsIImapHostSessionList_H_
+#define _nsIImapHostSessionList_H_
+
+#include "nsISupports.h"
+#include "nsImapCore.h"
+
+class nsIMAPBodyShellCache;
+class nsIMAPBodyShell;
+class nsIImapIncomingServer;
+
+// f4d89e3e-77da-492c-962b-7835f0742c22
+#define NS_IIMAPHOSTSESSIONLIST_IID \
+{ 0xf4d89e3e, 0x77da, 0x492c, {0x96, 0x2b, 0x78, 0x35, 0xf0, 0x74, 0x2c, 0x22 } }
+
+// this is an interface to a linked list of host info's
+class nsIImapHostSessionList : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIMAPHOSTSESSIONLIST_IID)
+
+ // Host List
+ NS_IMETHOD AddHostToList(const char *serverKey, nsIImapIncomingServer *server) = 0;
+ NS_IMETHOD ResetAll() = 0;
+
+ // Capabilities
+ NS_IMETHOD GetHostHasAdminURL(const char *serverKey, bool &result) = 0;
+ NS_IMETHOD SetHostHasAdminURL(const char *serverKey, bool hasAdminUrl) = 0;
+ // Subscription
+ NS_IMETHOD GetHostIsUsingSubscription(const char *serverKey, bool &result) = 0;
+ NS_IMETHOD SetHostIsUsingSubscription(const char *serverKey, bool usingSubscription) = 0;
+
+ // Passwords
+ NS_IMETHOD GetPasswordForHost(const char *serverKey, nsString &result) = 0;
+ NS_IMETHOD SetPasswordForHost(const char *serverKey, const char *password) = 0;
+ NS_IMETHOD GetPasswordVerifiedOnline(const char *serverKey, bool &result) = 0;
+ NS_IMETHOD SetPasswordVerifiedOnline(const char *serverKey) = 0;
+
+ // OnlineDir
+ NS_IMETHOD GetOnlineDirForHost(const char *serverKey,
+ nsString &result) = 0;
+ NS_IMETHOD SetOnlineDirForHost(const char *serverKey,
+ const char *onlineDir) = 0;
+
+ // Delete is move to trash folder
+ NS_IMETHOD GetDeleteIsMoveToTrashForHost(const char *serverKey, bool &result) = 0;
+ NS_IMETHOD SetDeleteIsMoveToTrashForHost(const char *serverKey, bool isMoveToTrash) = 0;
+ NS_IMETHOD GetShowDeletedMessagesForHost(const char *serverKey, bool &result) = 0;
+
+ NS_IMETHOD SetShowDeletedMessagesForHost(const char *serverKey, bool showDeletedMessages) = 0;
+
+ // Get namespaces
+ NS_IMETHOD GetGotNamespacesForHost(const char *serverKey, bool &result) = 0;
+ NS_IMETHOD SetGotNamespacesForHost(const char *serverKey, bool gotNamespaces) = 0;
+
+ // Folders
+ NS_IMETHOD SetHaveWeEverDiscoveredFoldersForHost(const char *serverKey, bool discovered) = 0;
+ NS_IMETHOD GetHaveWeEverDiscoveredFoldersForHost(const char *serverKey, bool &result) = 0;
+
+ // Trash Folder
+ NS_IMETHOD SetOnlineTrashFolderExistsForHost(const char *serverKey, bool exists) = 0;
+ NS_IMETHOD GetOnlineTrashFolderExistsForHost(const char *serverKey, bool &result) = 0;
+
+ // INBOX
+ NS_IMETHOD GetOnlineInboxPathForHost(const char *serverKey, nsString &result) = 0;
+ NS_IMETHOD GetShouldAlwaysListInboxForHost(const char *serverKey, bool &result) = 0;
+ NS_IMETHOD SetShouldAlwaysListInboxForHost(const char *serverKey, bool shouldList) = 0;
+
+ // Namespaces
+ NS_IMETHOD GetNamespaceForMailboxForHost(const char *serverKey, const char *mailbox_name, nsIMAPNamespace * & result) = 0;
+ NS_IMETHOD SetNamespaceFromPrefForHost(const char *serverKey, const char *namespacePref, EIMAPNamespaceType type) = 0;
+ NS_IMETHOD AddNewNamespaceForHost(const char *serverKey, nsIMAPNamespace *ns) = 0;
+ NS_IMETHOD ClearServerAdvertisedNamespacesForHost(const char *serverKey) = 0;
+ NS_IMETHOD ClearPrefsNamespacesForHost(const char *serverKey) = 0;
+ NS_IMETHOD GetDefaultNamespaceOfTypeForHost(const char *serverKey, EIMAPNamespaceType type, nsIMAPNamespace * & result) = 0;
+ NS_IMETHOD SetNamespacesOverridableForHost(const char *serverKey, bool overridable) = 0;
+ NS_IMETHOD GetNamespacesOverridableForHost(const char *serverKey,bool &result) = 0;
+ NS_IMETHOD GetNumberOfNamespacesForHost(const char *serverKey, uint32_t &result) = 0;
+ NS_IMETHOD GetNamespaceNumberForHost(const char *serverKey, int32_t n, nsIMAPNamespace * &result) = 0;
+ // ### dmb hoo boy, how are we going to do this?
+ NS_IMETHOD CommitNamespacesForHost(nsIImapIncomingServer *server) = 0;
+ NS_IMETHOD FlushUncommittedNamespacesForHost(const char *serverKey, bool &result) = 0;
+
+ // Hierarchy Delimiters
+ NS_IMETHOD SetNamespaceHierarchyDelimiterFromMailboxForHost(const char *serverKey, const char *boxName, char delimiter) = 0;
+
+ // Message Body Shells
+ NS_IMETHOD AddShellToCacheForHost(const char *serverKey, nsIMAPBodyShell *shell) = 0;
+ NS_IMETHOD FindShellInCacheForHost(const char *serverKey, const char *mailboxName, const char *UID, IMAP_ContentModifiedType modType, nsIMAPBodyShell **result) = 0;
+ NS_IMETHOD ClearShellCacheForHost(const char *serverKey) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIImapHostSessionList,
+ NS_IIMAPHOSTSESSIONLIST_IID)
+
+#endif
diff --git a/mailnews/imap/public/nsIImapFlagAndUidState.idl b/mailnews/imap/public/nsIImapFlagAndUidState.idl
new file mode 100644
index 000000000..4a7498b9b
--- /dev/null
+++ b/mailnews/imap/public/nsIImapFlagAndUidState.idl
@@ -0,0 +1,74 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(290412eb-5824-4087-8984-05450c9397be)]
+interface nsIImapFlagAndUidState : nsISupports
+{
+ readonly attribute long numberOfMessages;
+ readonly attribute long numberOfRecentMessages;
+
+ /**
+ * If a full update, the total number of deleted messages
+ * in the folder; if a partial update, the number of deleted
+ * messages in the partial update
+ **/
+ readonly attribute long numberOfDeletedMessages;
+
+ /**
+ * If this is true, instead of fetching 1:* (FLAGS), and putting all
+ * UIDs and flags in the array, we only fetched the uids and flags
+ * that changed since the last time we were selected on this folder.
+ * This means we have a sparse array, and should not assume missing
+ * UIDs have been deleted.
+ **/
+ readonly attribute boolean partialUIDFetch;
+
+ /**
+ * Set of flags the server supports storing per message. See nsImapCore.h
+ * for the set of flags.
+ */
+ readonly attribute unsigned short supportedUserFlags;
+
+ /**
+ * OR's the passed in flags with the previous flags because we want to
+ * accumulate the FLAGS and PERMANENTFLAGS response.
+ *
+ * @param aFlags - flags to OR with current flags.
+ */
+ void orSupportedUserFlags(in unsigned short aFlags);
+
+ void getUidOfMessage(in long zeroBasedIndex, out unsigned long result);
+ void getMessageFlags(in long zeroBasedIndex, out unsigned short result);
+ void setMessageFlags(in long zeroBasedIndex, in unsigned short flags);
+ void expungeByIndex(in unsigned long zeroBasedIndex);
+ void addUidFlagPair(in unsigned long uid, in unsigned short flags, in unsigned long zeroBasedIndex);
+ void addUidCustomFlagPair(in unsigned long uid, in string customFlag);
+ string getCustomFlags(in unsigned long uid); // returns space-separated keywords
+ void reset();
+ void clearCustomFlags(in unsigned long uid);
+ /**
+ * Adds custom attributes to a hash table for the purpose of storing them
+ * them.
+ * @param aUid UID of the associated msg
+ * @param aCustomAttributeName Name of the custom attribute value
+ * @param aCustomAttributeValue Value of the attribute,
+ */
+ void setCustomAttribute(in unsigned long aUid,
+ in ACString aCustomAttributeName,
+ in ACString aCustomAttributeValue);
+
+ /**
+ * Gets the custom attributes from the hash table where they were stored earlier
+ * them.
+ * @param aUid UID of the associated msg
+ * @param aCustomAttributeName Name of the custom attribute value
+ * @param aCustomAttributeValue Value of the attribute,
+ */
+ ACString getCustomAttribute(in unsigned long aUid,
+ in ACString aCustomAttributeName);
+};
+
diff --git a/mailnews/imap/public/nsIImapHeaderXferInfo.idl b/mailnews/imap/public/nsIImapHeaderXferInfo.idl
new file mode 100644
index 000000000..b2b2e7840
--- /dev/null
+++ b/mailnews/imap/public/nsIImapHeaderXferInfo.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+[scriptable, uuid(38f8f784-b092-11d6-ba4b-00108335942a)]
+interface nsIImapHeaderInfo : nsISupports {
+ attribute nsMsgKey msgUid;
+ attribute long msgSize;
+ [noscript] void getMsgHdrs ([shared] out string aMsgHdrs); // this doesn't copy the msgHdrs
+ void cacheLine(in string line, in unsigned long uid);
+ void resetCache();
+};
+
+[scriptable, uuid(f0842eda-af29-4ecd-82e1-fba91bd65d66)]
+interface nsIImapHeaderXferInfo : nsISupports {
+ readonly attribute long numHeaders;
+ nsIImapHeaderInfo getHeader(in long hdrIndex);
+};
+
diff --git a/mailnews/imap/public/nsIImapIncomingServer.idl b/mailnews/imap/public/nsIImapIncomingServer.idl
new file mode 100644
index 000000000..2c687e0f3
--- /dev/null
+++ b/mailnews/imap/public/nsIImapIncomingServer.idl
@@ -0,0 +1,105 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIImapUrl;
+interface nsIImapProtocol;
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+
+typedef long nsMsgImapDeleteModel;
+
+[scriptable, uuid(bbfc33de-fe89-11d3-a564-0060b0fc04b7)]
+interface nsMsgImapDeleteModels
+{
+ const long IMAPDelete = 0; /* delete with a big red x */
+ const long MoveToTrash = 1; /* delete moves message to the trash */
+ const long DeleteNoTrash = 2; /* delete is shift delete - don't create or use trash */
+};
+
+[scriptable, uuid(ea6a0765-07b8-40df-924c-9004ed707251)]
+interface nsIImapIncomingServer : nsISupports {
+
+ attribute long maximumConnectionsNumber;
+ attribute ACString forceSelect;
+ attribute long timeOutLimits;
+ attribute ACString adminUrl;
+ attribute ACString serverDirectory;
+ /// RFC 2971 ID response stored as a pref
+ attribute ACString serverIDPref;
+ attribute boolean cleanupInboxOnExit;
+ attribute nsMsgImapDeleteModel deleteModel;
+ attribute boolean dualUseFolders;
+ attribute long emptyTrashThreshhold;
+ attribute ACString personalNamespace;
+ attribute ACString publicNamespace;
+ attribute ACString otherUsersNamespace;
+ attribute boolean offlineDownload;
+ attribute boolean overrideNamespaces;
+ attribute boolean usingSubscription;
+ attribute ACString manageMailAccountUrl;
+ attribute boolean fetchByChunks;
+ attribute boolean mimePartsOnDemand;
+ attribute boolean sendID;
+ attribute boolean isAOLServer;
+ attribute boolean capabilityACL;
+ attribute boolean capabilityQuota;
+ attribute boolean useIdle;
+ attribute boolean checkAllFoldersForNew;
+
+ /// Is this a GMail Server?
+ attribute boolean isGMailServer;
+
+ /**
+ * See IMAP RFC 4551
+ **/
+ attribute boolean useCondStore;
+
+ /**
+ * See IMAP RFC 4978
+ */
+ attribute boolean useCompressDeflate;
+
+ attribute AString trashFolderName;
+
+ attribute boolean downloadBodiesOnGetNewMail;
+ attribute boolean autoSyncOfflineStores;
+
+ /// Max age of messages we will autosync to, or keep in offline store.
+ attribute long autoSyncMaxAgeDays;
+
+ void GetImapConnectionAndLoadUrl(in nsIImapUrl aImapUrl,
+ in nsISupports aConsumer);
+
+ void RemoveConnection(in nsIImapProtocol aImapConnection);
+ void ResetNamespaceReferences();
+ void pseudoInterruptMsgLoad(in nsIMsgFolder aImapFolder, in nsIMsgWindow aMsgWindow, out boolean interrupted);
+ void ResetConnection(in ACString folderName);
+ void CloseConnectionForFolder(in nsIMsgFolder aMsgFolder);
+ void reDiscoverAllFolders();
+ nsIURI subscribeToFolder(in AString name, in boolean subscribe);
+ void GetNewMessagesForNonInboxFolders(in nsIMsgFolder aRootFolder,
+ in nsIMsgWindow aWindow,
+ in boolean forceAllFolders,
+ in boolean performingBiff);
+ /**
+ * Get the password from the nsIMsgIncomingServer. May prompt the user
+ * if there's no password in the password manager or cached in the
+ * server object.
+ * @param aWindow msgWindow to associate the password prompt with
+ * @return Password string.
+ * @exception NS_ERROR_FAILURE The password could not be obtained.
+ * @note NS_MSG_PASSWORD_PROMPT_CANCELLED is a success code that is returned
+ * if the prompt was presented to the user but the user cancelled the
+ * prompt.
+ */
+ ACString PromptPassword(in nsIMsgWindow aWindow);
+ attribute boolean doingLsub;
+
+ ACString getUriWithNamespacePrefixIfNecessary(in long namespaceType, in ACString originalUri);
+ attribute boolean shuttingDown;
+};
diff --git a/mailnews/imap/public/nsIImapMailFolderSink.idl b/mailnews/imap/public/nsIImapMailFolderSink.idl
new file mode 100644
index 000000000..465e72b1d
--- /dev/null
+++ b/mailnews/imap/public/nsIImapMailFolderSink.idl
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+#include "nsIImapProtocol.idl"
+#include "nsIMailboxSpec.idl"
+
+interface nsIMsgMailNewsUrl;
+interface nsIImapMockChannel;
+interface nsIImapHeaderXferInfo;
+
+typedef long ImapOnlineCopyState;
+
+[scriptable, uuid(5f7484b0-68b4-11d3-a53e-0060b0fc04b7)]
+interface ImapOnlineCopyStateType
+{
+ const long kInProgress = 0;
+ const long kSuccessfulCopy = 1;
+ const long kSuccessfulMove = 2;
+ const long kSuccessfulDelete = 3;
+ const long kFailedDelete = 4;
+ const long kReadyForAppendData = 5;
+ const long kFailedAppend = 6;
+ const long kInterruptedState = 7;
+ const long kFailedCopy = 8;
+ const long kFailedMove = 9;
+};
+
+[scriptable, uuid(525e1278-a39d-46d6-9dbc-b48c7e1d4faa)]
+interface nsIImapMailFolderSink : nsISupports {
+ attribute boolean folderNeedsACLListed;
+ attribute boolean folderNeedsSubscribing;
+ attribute boolean folderNeedsAdded;
+ attribute unsigned long aclFlags;
+ attribute long uidValidity;
+ /**
+ * Whether we have asked the server for this folder's quota information.
+ * If the server supports quotas, this occurs when the folder is opened.
+ */
+ attribute boolean folderQuotaCommandIssued;
+
+ /**
+ * Set FolderQuotaData information
+ * @param aFolderQuotaRoot The IMAP quota root for this folder,
+ * as returned by the GETQUOTAROOT IMAP command.
+ * @param aFolderQuotaUsedKB Used space, in KB, on this folder's quota root.
+ * @param aFolderQuotaMaxKB Size, in KB, of this folder's quota root.
+ **/
+ void setFolderQuotaData(in ACString aFolderQuotaRoot, in unsigned long aFolderQuotaUsedKB,
+ in unsigned long aFolderQuotaMaxKB);
+
+ /// Should we download all the rfc822 headers of messages, instead of subset.
+ readonly attribute boolean shouldDownloadAllHeaders;
+ readonly attribute char onlineDelimiter;
+ void OnNewIdleMessages();
+ // Tell mail master about the newly selected mailbox
+ void UpdateImapMailboxInfo(in nsIImapProtocol aProtocol,
+ in nsIMailboxSpec aSpec);
+ void UpdateImapMailboxStatus(in nsIImapProtocol aProtocol,
+ in nsIMailboxSpec aSpec);
+ /**
+ * Used when downloading headers in chunks.
+ * @param aSpec Mailbox spec of folder we're downloading headers for.
+ * @returns true if more to download, false otherwise.
+ * @returns total count of headers to download (across all chunks)
+ * @returns an array of msg keys to download, array size is this chunk's size
+ */
+ void getMsgHdrsToDownload(out boolean aMore, out long aTotalCount,
+ out unsigned long aCount,
+ [retval, array, size_is(aCount)] out nsMsgKey aKeys);
+ void parseMsgHdrs(in nsIImapProtocol aProtocol, in nsIImapHeaderXferInfo aHdrXferInfo);
+ void AbortHeaderParseStream(in nsIImapProtocol aProtocol) ;
+
+ void OnlineCopyCompleted(in nsIImapProtocol aProtocol, in ImapOnlineCopyState aCopyState);
+ void StartMessage(in nsIMsgMailNewsUrl aUrl);
+ void EndMessage(in nsIMsgMailNewsUrl aUrl, in nsMsgKey uidOfMessage);
+
+ void NotifySearchHit(in nsIMsgMailNewsUrl aUrl, in string hitLine);
+
+ void copyNextStreamMessage(in boolean copySucceeded, in nsISupports copyState);
+ void closeMockChannel(in nsIImapMockChannel aChannel);
+ void setUrlState(in nsIImapProtocol aProtocol, in nsIMsgMailNewsUrl aUrl,
+ in boolean isRunning, in boolean aSuspend,
+ in nsresult status);
+ void releaseUrlCacheEntry(in nsIMsgMailNewsUrl aUrl);
+
+ void headerFetchCompleted(in nsIImapProtocol aProtocol);
+ void setBiffStateAndUpdate(in long biffState);
+ void progressStatusString(in nsIImapProtocol aProtocol, in string aMsgId, in wstring extraInfo);
+ void percentProgress(in nsIImapProtocol aProtocol, in wstring aMessage,
+ in long long aCurrentProgress, in long long aMaxProgressProgressInfo);
+
+ void clearFolderRights();
+ void setCopyResponseUid(in string msgIdString,
+ in nsIImapUrl aUrl);
+ void setAppendMsgUid(in nsMsgKey newKey,
+ in nsIImapUrl aUrl);
+ ACString getMessageId(in nsIImapUrl aUrl);
+};
diff --git a/mailnews/imap/public/nsIImapMessageSink.idl b/mailnews/imap/public/nsIImapMessageSink.idl
new file mode 100644
index 000000000..420ea7642
--- /dev/null
+++ b/mailnews/imap/public/nsIImapMessageSink.idl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+#include "nsIImapUrl.idl"
+
+interface nsIMsgMailNewsUrl;
+
+[scriptable, uuid(6ffb6a92-e43a-405f-92ea-92cf81a5e17b)]
+
+interface nsIImapMessageSink : nsISupports {
+ // set up messge download output stream
+ void setupMsgWriteStream(in nsIFile aFile, in boolean aAppendDummyEnvelope);
+
+ /**
+ * Used by the imap protocol code to notify the core backend code about
+ * downloaded imap messages.
+ *
+ * @param aAdoptedMsgLine a string with a lot of message lines,
+ * separated by native line terminators.
+ * @param aUidOfMsg IMAP UID of the fetched message.
+ * @param aImapUrl IMAP Url used to fetch the message.
+ */
+ void parseAdoptedMsgLine(in string aAdoptedMsgLine, in nsMsgKey aUidOfMsg,
+ in nsIImapUrl aImapUrl);
+
+ /**
+ * Notify the backend that the imap protocol is done downloading a message
+ *
+ * @param aUidOfMsg IMAP UID of the fetched message.
+ * @param aMarkMsgRead Set the SEEN flag on the message.
+ * @param aImapUrl IMAP Url used to fetch the message.
+ * @param aUpdatedMessageSize if this parameter is not -1, the stored size of the message
+ * should be set to this value to reflect the actual size of
+ * the downloaded message.
+ */
+ void normalEndMsgWriteStream(in nsMsgKey aUidOfMessage,
+ in boolean aMarkMsgRead, in nsIImapUrl aImapUrl,
+ in long aUpdatedMessageSize);
+
+ void abortMsgWriteStream();
+
+ void beginMessageUpload();
+
+ /**
+ * Notify the message sink that one or more flags have changed
+ * For Condstore servers, also update the highestMod Sequence
+ * @param aFlags - The new flags for the message
+ * @param aKeywords keywords for the message
+ * @param aMessageKey - The UID of the message that changed
+ * @param aHighestModSeq - The highest mod seq the parser has seen
+ * for this folder
+ **/
+ void notifyMessageFlags(in unsigned long aFlags, in ACString aKeywords,
+ in nsMsgKey aMessageKey,
+ in unsigned long long aHighestModSeq);
+
+ void notifyMessageDeleted(in string aOnlineFolderName,in boolean aDeleteAllMsgs,in string aMsgIdString);
+
+ void getMessageSizeFromDB(in string aId, out unsigned long aSize);
+
+ void setContentModified(in nsIImapUrl aImapUrl, in nsImapContentModifiedType aModified);
+
+ /**
+ * For a message stored in a file, get the message metadata needed to copy
+ * that message to an imap folder
+ *
+ * @param aRunningUrl message URL
+ * @param aDate message date
+ * @param aKeywords message custom keywords (if supported by the server),
+ * including messages tags and junk status
+ *
+ * @return message flags
+ */
+ unsigned long getCurMoveCopyMessageInfo(in nsIImapUrl aRunningUrl,
+ out PRTime aDate, out ACString aKeywords);
+};
diff --git a/mailnews/imap/public/nsIImapMockChannel.idl b/mailnews/imap/public/nsIImapMockChannel.idl
new file mode 100644
index 000000000..ab4e6b996
--- /dev/null
+++ b/mailnews/imap/public/nsIImapMockChannel.idl
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/*
+ Because imap protocol connections (which are channels) run through a cache,
+ it isn't always the case that when you want to run a url, you actually get
+ a connection back right away. Often times, the url goes into a queue until
+ a connection becomes available.
+
+ Unfortunately, if we want to be a truly pluggable protocol with necko, necko
+ requires the ability to get a channel back right away when it wants to run
+ a url. It doesn't let you wait until an imap connection becomes available.
+
+ So I've created the notion of a "mock channel". This mock channel is what
+ gets returned to necko (or other callers) when they ask the imap service
+ for a new channel for a url. The mock channel has "mock" implementations
+ of nsIChannel. Eventually, when we actually assign the url to a real
+ channel, we set the real channel on the mock channel. From that point forward,
+ the mock channel forwards channel calls directly to the real channel.
+
+ In short, this class is how I'm solving the problem where necko wants
+ a channel back as soon as they ask for when with the fact that it
+ may be a while until the url is loaded into a connection.
+ */
+
+#include "nsISupports.idl"
+#include "nsIChannel.idl"
+
+interface nsIStreamListener;
+interface nsIProgressEventSink;
+interface nsIURI;
+interface nsIImapProtocol;
+
+[scriptable, uuid(e0178cd5-d37b-4bde-9ab8-752083536225)]
+
+interface nsIImapMockChannel : nsIChannel
+{
+ attribute nsIProgressEventSink progressEventSink;
+ void GetChannelListener(out nsIStreamListener aChannelListener);
+ void GetChannelContext(out nsISupports aChannelContext);
+ void Close();
+ void setImapProtocol(in nsIImapProtocol aProtocol);
+ [noscript] void setSecurityInfo(in nsISupports securityInfo);
+
+ void setURI(in nsIURI uri);
+};
diff --git a/mailnews/imap/public/nsIImapProtocol.idl b/mailnews/imap/public/nsIImapProtocol.idl
new file mode 100644
index 000000000..83b19a499
--- /dev/null
+++ b/mailnews/imap/public/nsIImapProtocol.idl
@@ -0,0 +1,71 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIImapUrl;
+interface nsIImapProtocol;
+interface nsIImapIncomingServer;
+interface nsIMsgFolder;
+interface nsIImapHostSessionList;
+interface nsIMsgWindow;
+interface nsIImapFlagAndUidState;
+
+[scriptable, uuid(290412eb-5824-4087-8984-05450c9397be)]
+interface nsIImapProtocol : nsISupports {
+ void LoadImapUrl(in nsIURI aUrl, in nsISupports aConsumer);
+
+ /**
+ * IsBusy returns true if the connection is currently processing a url
+ * and false otherwise.
+ */
+ void IsBusy(out boolean aIsConnectionBusy,
+ out boolean isInboxConnection);
+
+ /** Protocol instance examines the url, looking at the host name,
+ * user name and folder the action would be on in order to figure out
+ * if it can process this url. I decided to push the semantics about
+ * whether a connection can handle a url down into the connection level
+ * instead of in the connection cache.
+ */
+ void CanHandleUrl(in nsIImapUrl aImapUrl, out boolean aCanRunUrl,
+ out boolean hasToWait);
+
+ /**
+ * Initialize a protocol object.
+ * @param aHostSessionList host session list service
+ * @param aServer imap server the protocol object will be talking to
+ */
+ void Initialize(in nsIImapHostSessionList aHostSessionList, in nsIImapIncomingServer aServer);
+
+ void NotifyBodysToDownload(out unsigned long keys, in unsigned long count);
+ // methods to get data from the imap parser flag state.
+ void GetFlagsForUID(in unsigned long uid, out boolean foundIt, out unsigned short flags, out string customFlags);
+ void GetSupportedUserFlags(out unsigned short flags);
+
+ void GetRunningImapURL(out nsIImapUrl aImapUrl);
+
+ void GetRunningUrl(out nsIURI aUrl);
+
+ readonly attribute nsIImapFlagAndUidState flagAndUidState;
+ /**
+ * Tell thread to die - only call from the UI thread
+ *
+ * @param aIsSafeToClose false if we're dropping a timed out connection.
+ */
+ void tellThreadToDie(in boolean aIsSafeToClose);
+
+ // Get last active time stamp
+ void GetLastActiveTimeStamp(out PRTime aTimeStamp);
+
+ void pseudoInterruptMsgLoad(in nsIMsgFolder imapFolder, in nsIMsgWindow aMsgWindow, out boolean interrupted);
+ void GetSelectedMailboxName(out string folderName);
+ // Reset folder connection to authenticated state
+ void ResetToAuthenticatedState();
+
+ void OverrideConnectionInfo(in wstring pHost, in unsigned short pPort, in string pCookieData);
+};
+
diff --git a/mailnews/imap/public/nsIImapProtocolSink.idl b/mailnews/imap/public/nsIImapProtocolSink.idl
new file mode 100644
index 000000000..07b533065
--- /dev/null
+++ b/mailnews/imap/public/nsIImapProtocolSink.idl
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIMsgWindow;
+interface nsIMsgMailNewsUrl;
+
+/**
+ * Helper interface that contains operations MUST be proxied
+ * over UI thread.
+ */
+[scriptable, uuid(1217cd9d-7678-4026-b323-0d4b45816af0)]
+interface nsIImapProtocolSink : nsISupports {
+
+ /**
+ * Does general cleanup for the imap protocol object.
+ */
+ void closeStreams();
+ /**
+ * Get the msg window associated with a url
+ *
+ * @param aUrl url whose msgWindow we want.
+ * @returns msgWindow associated with url.
+ */
+ nsIMsgWindow getUrlWindow(in nsIMsgMailNewsUrl aUrl);
+
+ /**
+ * Setup main thread proxies.
+ */
+ void setupMainThreadProxies();
+};
diff --git a/mailnews/imap/public/nsIImapServerSink.idl b/mailnews/imap/public/nsIImapServerSink.idl
new file mode 100644
index 000000000..77fdcb2d9
--- /dev/null
+++ b/mailnews/imap/public/nsIImapServerSink.idl
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgWindow;
+interface nsIMsgMailNewsUrl;
+interface nsIImapProtocol;
+interface nsIImapUrl;
+interface nsIImapMockChannel;
+
+/**
+ * nsIImapServerSink is designed to be used as a proxy to the application's UI
+ * thread from the running IMAP threads.
+ */
+[scriptable, uuid(2160c641-e4fa-4bbc-ab8b-d9ba45069027)]
+interface nsIImapServerSink : nsISupports {
+ /**
+ * Check if the given folder path is a possible IMAP mailbox.
+ * @param folderPath folder path to check
+ * @param hierarchyDelimiter IMAP hierarchy delimiter in canonical format,
+ * i.e., hierarchy delimiter has been replaced
+ * with '/'
+ * @param boxFlags IMAP folder flags (for subscription, namespaces etc.)
+ * @return true if it's a new mailbox
+ */
+ boolean possibleImapMailbox(in ACString folderPath,
+ in char hierarchyDelimiter, in long boxFlags);
+ boolean folderNeedsACLInitialized(in ACString folderPath);
+ void addFolderRights(in ACString folderPath, in ACString userName, in ACString rights);
+ void refreshFolderRights(in ACString folderPath);
+ void discoveryDone();
+ void onlineFolderDelete(in ACString folderName);
+ void onlineFolderCreateFailed(in ACString aFolderName);
+ void onlineFolderRename(in nsIMsgWindow msgWindow, in ACString oldName, in ACString newName);
+ boolean folderIsNoSelect(in ACString folderName);
+ void setFolderAdminURL(in ACString folderName, in ACString adminUrl);
+ boolean folderVerifiedOnline(in ACString folderName);
+
+ void setCapability(in unsigned long long capability);
+ /// RFC 2971 ID server response
+ void setServerID(in ACString aServerID);
+ boolean loadNextQueuedUrl(in nsIImapProtocol protocol);
+
+ /**
+ * Prepare to retry the given URL.
+ * @param imapUrl the url we're going to retry
+ * @return channel to associate with the url. We return this because access
+ * to the channel should only happen on the ui thread.
+ */
+ nsIImapMockChannel prepareToRetryUrl(in nsIImapUrl imapUrl);
+
+ /**
+ * Suspend the url. This puts it at the end of the queue. If the queue is
+ * empty, the url will get resumed immediately. Currently, the plan is
+ * do this when we have to download a lot of headers in chunks, though we
+ * could find other uses for it.
+ * @param imapUrl url to suspend
+ */
+ void suspendUrl(in nsIImapUrl aImapUrl);
+
+ /**
+ * Retry the given URL.
+ * @param imapUrl url to retry
+ * @param channel the channel to associate with the url
+ */
+ void retryUrl(in nsIImapUrl imapUrl, in nsIImapMockChannel channel);
+
+ /**
+ * If previous URL failed, this gives server chance to abort URLs with same
+ * mock channel.
+ */
+ void abortQueuedUrls();
+ AString getImapStringByName(in string msgName);
+ /**
+ * Alerts the user that the login to the IMAP server failed. Asks whether the
+ * connection should: retry, cancel, or request a new password.
+ *
+ * @param aMsgWindow The message window associated with this action (cannot
+ * be null).
+ * @return The button pressed. 0 for retry, 1 for cancel,
+ * 2 for enter a new password.
+ */
+ int32_t promptLoginFailed(in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Alerts the user with the given string (FE = 'Front End').
+ *
+ * @param aAlertString The string to alert the user with.
+ * @param aUrl The running url.
+ */
+ void fEAlert(in AString aAlertString, in nsIMsgMailNewsUrl aUrl);
+
+ /**
+ * Alerts the user with a localized string. It will attempt to fill in
+ * the hostname into the string if necessary.
+ *
+ * @param aMsgName The id of the string to present to the user..
+ * @param aUrl The running url.
+ */
+ void fEAlertWithName(in string aMsgName, in nsIMsgMailNewsUrl aUrl);
+ /**
+ * Takes a response from the server and prepends it with IMAP_SERVER_SAID
+ *
+ * @param aServerString The string to alert the user with.
+ * @param url The running url.
+ */
+ void fEAlertFromServer(in ACString aServerString, in nsIMsgMailNewsUrl aUrl);
+
+ void commitNamespaces();
+
+ /**
+ * Returns a password via the out param, if we were able to prompt for one,
+ * or had one stored.
+ * If there is already a password prompt up, we return false, but we
+ * ask the async prompt service to notify us when we can put up a prompt.
+ * When that notification is received, we prompt the user and set the
+ * password on the protocol object, and signal a monitor that the imap
+ * thread should be waiting on.
+ *
+ * rv is NS_MSG_PASSWORD_PROMPT_CANCELLED if the user cancels the
+ * password prompt. That's not an exception, however.
+ *
+ * @param aProtocol imap protocol object requesting the password.
+ * @param aNewPasswordRequested Forces password prompt immediately
+ * @param aPassword returns the password, unless we had to prompt or use the,
+ * login manager and there was already a prompt up.
+ */
+ void asyncGetPassword(in nsIImapProtocol aProtocol,
+ in boolean aNewPasswordRequested,
+ out ACString aPassword);
+
+ attribute boolean userAuthenticated;
+ void setMailServerUrls(in ACString manageMailAccount, in ACString manageLists, in ACString manageFilters);
+
+ /** Used by the imap thread when upgrading from the socketType
+ * trySTARTTLS.
+ * @param aSucceeded whether STARTTLS succeeded. If it did, the server
+ * will set the socket type to alwaysSTARTTLS, otherwise plain.
+ */
+ void UpdateTrySTARTTLSPref(in boolean aSucceeded);
+
+ readonly attribute ACString arbitraryHeaders;
+ void forgetPassword();
+
+ readonly attribute boolean showAttachmentsInline;
+ string cramMD5Hash(in string decodedChallenge, in string key);
+ /// String to send to the imap server as the login user name.
+ readonly attribute ACString loginUsername;
+ /// String to send to the imap server as the user name.
+ readonly attribute ACString originalUsername;
+ /// Internal pref key, unique over all servers
+ readonly attribute ACString serverKey;
+ /// password for server login
+ readonly attribute ACString serverPassword;
+ /// remove a connection to the server
+ void removeServerConnection(in nsIImapProtocol aProtocol);
+ /// is the imap server shutting down?
+ readonly attribute boolean serverShuttingDown;
+ /// reset the connection for a particular folder
+ void resetServerConnection(in ACString aFolderName);
+ /// tell the server if listing using lsub command
+ void setServerDoingLsub(in boolean aDoingLsub);
+ /// set force select string
+ void SetServerForceSelect(in ACString forceSelect);
+};
diff --git a/mailnews/imap/public/nsIImapService.idl b/mailnews/imap/public/nsIImapService.idl
new file mode 100644
index 000000000..0a8521ee6
--- /dev/null
+++ b/mailnews/imap/public/nsIImapService.idl
@@ -0,0 +1,258 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+////////////////////////////////////////////////////////////////////////////////////////
+// The IMAP Service is an interfaced designed to make building and running imap urls
+// easier. Clients typically go to the imap service and ask it do things such as:
+// get new mail, etc....
+//
+// Oh and in case you couldn't tell by the name, the imap service is a service! and you
+// should go through the service manager to obtain an instance of it.
+////////////////////////////////////////////////////////////////////////////////////////
+
+#include "nsISupports.idl"
+#include "nsIImapUrl.idl"
+
+interface nsIImapMessageSink;
+interface nsIUrlListener;
+interface nsIURI;
+interface nsIFile;
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+interface nsIImapIncomingServer;
+interface nsICacheStorage;
+
+[scriptable, uuid(aba44b3d-7a0f-4987-8794-96d2de66d966)]
+interface nsIImapService : nsISupports
+{
+ // You can pass in null for the url listener and the url if you don't require either.....
+ void selectFolder(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow,
+ out nsIURI aURL);
+
+ /**
+ * Select the folder on the imap server without doing a sync of flags or
+ * headers. This is used for offline playback, where we don't want to
+ * download hdrs we don't have, because they may have been offline deleted.
+ *
+ * @param aImapMailFolder the folder to select
+ * @param aUrlListener url listener, can be null
+ * @param aMsgWindow msg window url is running in, can be null
+ *
+ * @returns the url created to run the lite select in.
+ */
+ nsIURI liteSelectFolder(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ void addImapFetchToUrl(in nsIURI aURL,
+ in nsIMsgFolder aImapMailFolder,
+ in ACString aMessageIdentifierList,
+ in ACString aAdditionalHeader);
+
+ void fetchMessage(in nsIImapUrl aUrl,
+ in nsImapState aImapAction,
+ in nsIMsgFolder aImapMailFolder,
+ in nsIImapMessageSink aImapMessageSink,
+ in nsIMsgWindow aMsgWindow,
+ in nsISupports aConsumer,
+ in ACString aMessageIdentifierList,
+ in boolean convertDataToText,
+ in ACString additionalHeader,
+ out nsIURI aOutURL);
+
+ void noop(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL);
+
+ void getHeaders(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in ACString aMessageIdentifierList,
+ in boolean aMessageIdsAreUID);
+
+ nsIURI getBodyStart(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in ACString aMessageIdentifierList,
+ in long numBytes);
+
+ /**
+ * Issue an EXPUNGE on the target folder.
+ *
+ * @param aImapMailFolder the folder to expunge
+ * @param aUrlListener url listener, can be null
+ * @param aMsgWindow msg window url is running in, can be null
+ *
+ * @returns the url created to run the expunge.
+ */
+ void expunge(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow,
+ out nsIURI aURL);
+
+ /**
+ * Issue a STATUS on the target folder.
+ *
+ * @param aImapMailFolder the folder to expunge
+ * @param aUrlListener url listener, can be null
+ *
+ * @returns the url created to run the status.
+ */
+ nsIURI updateFolderStatus(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener);
+
+ /**
+ * Verify that we can login.
+ *
+ * @param aImapMailFolder - any old imap folder - we just need it to
+ * set url sinks.
+ * @param aMsgWindow - nsIMsgWindow to use for notification callbacks.
+ * @return - the url that we run.
+ */
+ nsIURI verifyLogon(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ void biff(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in unsigned long aUidHighWater);
+
+ void deleteMessages(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in ACString aMessageIdentifierList,
+ in boolean aMessageIdsAreUID);
+
+ void deleteAllMessages(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL);
+
+ void addMessageFlags(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in ACString aMessageIdentifierList,
+ in imapMessageFlagsType aFlags,
+ in boolean aMessageIdsAreUID);
+
+ void subtractMessageFlags(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in ACString aMessageIdentifierList,
+ in imapMessageFlagsType aFlags,
+ in boolean aMessageIdsAreUID);
+
+ void setMessageFlags(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in ACString aMessageIdentifierList,
+ in imapMessageFlagsType aFlags,
+ in boolean aMessageIdsAreUID);
+
+ void discoverAllFolders(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow,
+ out nsIURI aURL);
+
+ void discoverAllAndSubscribedFolders(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL);
+
+ void discoverChildren(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in ACString folderPath,
+ out nsIURI aURL);
+
+ void onlineMessageCopy(in nsIMsgFolder aSrcFolder,
+ in ACString aMessageIds,
+ in nsIMsgFolder aDstFolder,
+ in boolean aIdsAreUids,
+ in boolean aIsMove,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in nsISupports aCopyState,
+ in nsIMsgWindow aWindow);
+
+
+ void appendMessageFromFile(in nsIFile aFile,
+ in nsIMsgFolder aDstFolder,
+ in ACString aMessageId,
+ in boolean idsAreUids,
+ in boolean aInSelectedState,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in nsISupports aCopyState,
+ in nsIMsgWindow aMsgWindow);
+
+ void downloadMessagesForOffline(in ACString aMessageIds, in nsIMsgFolder aSrcFolder,
+ in nsIUrlListener aListener, in nsIMsgWindow aMsgWindow);
+
+ nsIURI moveFolder(in nsIMsgFolder aSrcFolder,
+ in nsIMsgFolder aDstFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow msgWindow);
+
+ nsIURI renameLeaf(in nsIMsgFolder aSrcFolder,
+ in AString aLeafName,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow msgWindow);
+
+ nsIURI deleteFolder(in nsIMsgFolder aFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ nsIURI createFolder(in nsIMsgFolder aParentFolder,
+ in AString aLeafName,
+ in nsIUrlListener aUrlListener);
+
+ nsIURI listFolder(in nsIMsgFolder aMailFolder,
+ in nsIUrlListener aUrlListener);
+
+ nsIURI subscribeFolder(in nsIMsgFolder aMailFolder,
+ in AString mailboxName,
+ in nsIUrlListener aUrlListener);
+
+ nsIURI unsubscribeFolder(in nsIMsgFolder aMailFolder,
+ in AString mailboxName,
+ in nsIUrlListener aUrlListener);
+
+ // this method will first check if the folder exists but is
+ // not subscribed to, in which case it will subscribe to the folder.
+ // otherwise, it will try to create the folder. It will try to do this
+ // with one url.
+ nsIURI ensureFolderExists(in nsIMsgFolder aParentFolder,
+ in AString aLeafName,
+ in nsIUrlListener aUrlListener);
+
+
+ nsIURI getFolderAdminUrl(in nsIMsgFolder aMailFolder,
+ in nsIMsgWindow aMsgWindow,
+ in nsIUrlListener aUrlListener);
+
+ nsIURI issueCommandOnMsgs(in nsIMsgFolder aMailFolder,
+ in nsIMsgWindow aMsgWindow,
+ in ACString aCommand,
+ in ACString aMessageIdentifierList);
+
+ nsIURI fetchCustomMsgAttribute(in nsIMsgFolder aMailFolder,
+ in nsIMsgWindow aMsgWindow,
+ in ACString aAttribute,
+ in ACString aMessageIdentifierList);
+
+ nsIURI storeCustomKeywords(in nsIMsgFolder aMailFolder,
+ in nsIMsgWindow aMsgWindow,
+ in ACString flagsToAdd,
+ in ACString flagsToSubtract,
+ in ACString aMessageIdentifierList);
+
+ void getListOfFoldersOnServer(in nsIImapIncomingServer aServer, in nsIMsgWindow aMsgWindow);
+ void getListOfFoldersWithPath(in nsIImapIncomingServer aServer, in nsIMsgWindow aMsgWindow, in ACString folderPath);
+
+ nsISupports playbackAllOfflineOperations(in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
+ void downloadAllOffineImapFolders(in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
+
+ readonly attribute nsICacheStorage cacheStorage;
+};
diff --git a/mailnews/imap/public/nsIImapUrl.idl b/mailnews/imap/public/nsIImapUrl.idl
new file mode 100644
index 000000000..03d0e03e0
--- /dev/null
+++ b/mailnews/imap/public/nsIImapUrl.idl
@@ -0,0 +1,207 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIImapMailFolderSink;
+interface nsIImapMessageSink;
+interface nsIImapServerSink;
+interface nsIImapMockChannel;
+interface nsIFile;
+
+typedef long nsImapAction;
+typedef long nsImapState;
+
+typedef unsigned short imapMessageFlagsType;
+
+typedef long nsImapContentModifiedType;
+
+[scriptable, uuid(2e91901e-ff6c-11d3-b9fa-00108335942a)]
+interface nsImapContentModifiedTypes
+{
+ const long IMAP_CONTENT_NOT_MODIFIED = 0;
+ const long IMAP_CONTENT_MODIFIED_VIEW_INLINE = 1;
+ const long IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS = 2;
+ const long IMAP_CONTENT_FORCE_CONTENT_NOT_MODIFIED = 3;
+} ;
+
+[scriptable, uuid(fe2a8f9e-2886-4146-9896-27fff660c69f)]
+interface nsIImapUrl : nsISupports
+{
+ ///////////////////////////////////////////////////////////////////////////////
+ // Getters and Setters for the imap specific event sinks to bind to to the url
+ ///////////////////////////////////////////////////////////////////////////////
+ attribute nsIImapMailFolderSink imapMailFolderSink;
+ attribute nsIImapMessageSink imapMessageSink;
+ attribute nsIImapServerSink imapServerSink;
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // Getters and Setters for the imap url state
+ ///////////////////////////////////////////////////////////////////////////////
+ attribute nsImapAction imapAction;
+ readonly attribute nsImapState requiredImapState;
+ readonly attribute string imapPartToFetch;
+ readonly attribute ACString customAttributeToFetch;
+ attribute ACString customAttributeResult;
+ readonly attribute ACString command;
+ attribute ACString customCommandResult;
+ readonly attribute ACString customAddFlags;
+ readonly attribute ACString customSubtractFlags;
+ void allocateCanonicalPath(in string aServerPath, in char aOnlineDelimiter, out string aAllocatedPath);
+ void allocateServerPath(in string aCanonicalPath, in char aOnlineDelimiter, out string aAllocatedPath);
+ string createServerSourceFolderPathString();
+ string createCanonicalSourceFolderPathString();
+ string createServerDestinationFolderPathString();
+
+ string addOnlineDirectoryIfNecessary(in string onlineMailboxName);
+ void createSearchCriteriaString (out string aResult);
+ readonly attribute ACString listOfMessageIds;
+
+ boolean messageIdsAreUids();
+ readonly attribute imapMessageFlagsType msgFlags; // kAddMsgFlags or kSubtractMsgFlags only
+
+ readonly attribute long numBytesToFetch;
+ attribute char onlineSubDirSeparator;
+ attribute boolean allowContentChange;
+ attribute boolean mimePartSelectorDetected;
+ attribute nsImapContentModifiedType contentModified;
+ attribute boolean fetchPartsOnDemand; // set to true if we're fetching a msg for display and want to not download parts
+ attribute boolean msgLoadingFromCache; // true if this msg load is coming from a cache, so we can know to mark it read
+ attribute boolean externalLinkUrl; // true if we ran this url because the user clicked on a link.
+ attribute boolean validUrl; // false if we couldn't parse url for whatever reason.
+ attribute nsISupports copyState;
+ attribute nsIFile msgFile;
+ attribute nsIImapMockChannel mockChannel;
+ /**
+ * Set to true if we should store the msg(s) for offline use if we can,
+ * e.g., we're fetching a message and the folder is configured for offline
+ * use and we're not doing mime parts on demand.
+ */
+ attribute boolean storeResultsOffline;
+ /**
+ * If we fallback from fetching by parts to fetching the whole message,
+ * because all the parts were inline, this tells us we should store
+ * the message offline.
+ */
+ attribute boolean storeOfflineOnFallback;
+
+ /**
+ * This attribute defaults to false, but if we only want to use the offline
+ * cache (disk, memory, or offline store) to fetch the message, then we set
+ * this to true. Currently, nsIMsgMessageService.streamMessage does this.
+ */
+ attribute boolean localFetchOnly;
+
+ /// Server disconnected first time so we're retrying.
+ attribute boolean rerunningUrl;
+
+ /**
+ * Do we have more headers to download? This is set when we decide to
+ * download newest headers first, followed by older headers in a subsequent
+ * run of the url, which allows other urls to run against the folder in the
+ * meantime.
+ */
+ attribute boolean moreHeadersToDownload;
+
+ /**
+ * @{
+ * This is used to tell the runner of the url more about the status of
+ * the command, beyond whether it was successful or not. For example,
+ * subtracting flags from a UID that doesn't exist isn't an error
+ * (the server returns OK), but the backend code may want to know about it.
+ */
+ attribute long extraStatus;
+
+ /**
+ * Current possible extra status values
+ */
+ const long ImapStatusNone = 0;
+ const long ImapStatusFlagChangeFailed = 1;
+ const long ImapStatusFlagsNotSettable = 2;
+ /** @} */
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // Enumerated types specific to imap urls...
+ ///////////////////////////////////////////////////////////////////////////////
+
+ // the following are nsImapState enums.
+ // we have a basic set of imap url actions. These actions are nsImapActions.
+ // Certain actions require us to be in the authenticated state and others require us to
+ // be in the selected state. nsImapState is used to store the state the url needs to
+ // be in. You'll later see us refer to the imap url state in the imap protocol when we
+ // are processing the current url. Don't confuse nsImapState with the generic url state
+ // used to keep track of whether the url is running or not...
+ const long nsImapAuthenticatedState = 0;
+ const long nsImapSelectedState = 1;
+
+ const long nsImapActionSendText = 0; // a state used for testing purposes to send raw url text straight to the server....
+ // nsImapAuthenticatedStateUrl urls
+ // since the following url actions require us to be in the authenticated
+ // state, the high bit is left blank....
+ const long nsImapTest = 0x00000001;
+ const long nsImapCreateFolder = 0x00000005;
+ const long nsImapDeleteFolder = 0x00000006;
+ const long nsImapRenameFolder = 0x00000007;
+ const long nsImapMoveFolderHierarchy = 0x00000008;
+ const long nsImapLsubFolders = 0x00000009;
+ const long nsImapGetMailAccountUrl = 0x0000000A;
+ const long nsImapDiscoverChildrenUrl = 0x0000000B;
+ const long nsImapDiscoverAllBoxesUrl = 0x0000000D;
+ const long nsImapDiscoverAllAndSubscribedBoxesUrl = 0x0000000E;
+ const long nsImapAppendMsgFromFile = 0x0000000F;
+ const long nsImapSubscribe = 0x00000010;
+ const long nsImapUnsubscribe = 0x00000011;
+ const long nsImapRefreshACL = 0x00000012;
+ const long nsImapRefreshAllACLs = 0x00000013;
+ const long nsImapListFolder = 0x00000014;
+ const long nsImapUpgradeToSubscription = 0x00000015;
+ const long nsImapFolderStatus = 0x00000016;
+ const long nsImapRefreshFolderUrls = 0x00000017;
+ const long nsImapEnsureExistsFolder = 0x00000018;
+ const long nsImapOfflineToOnlineCopy = 0x00000019;
+ const long nsImapOfflineToOnlineMove = 0x0000001A;
+ const long nsImapVerifylogon = 0x0000001B;
+ // it's okay to add more imap actions that require us to
+ // be in the authenticated state here without renumbering
+ // the imap selected state url actions. just make sure you don't
+ // set the high bit...
+
+ // nsImapSelectedState urls. Note, the high bit is always set for
+ // imap actions which require us to be in the selected state
+ const long nsImapSelectFolder = 0x10000002;
+ const long nsImapLiteSelectFolder = 0x10000003;
+ const long nsImapExpungeFolder = 0x10000004;
+ const long nsImapMsgFetch = 0x10000018;
+ const long nsImapMsgHeader = 0x10000019;
+ const long nsImapSearch = 0x1000001A;
+ const long nsImapDeleteMsg = 0x1000001B;
+ const long nsImapDeleteAllMsgs = 0x1000001C;
+ const long nsImapAddMsgFlags = 0x1000001D;
+ const long nsImapSubtractMsgFlags = 0x1000001E;
+ const long nsImapSetMsgFlags = 0x1000001F;
+ const long nsImapOnlineCopy = 0x10000020;
+ const long nsImapOnlineMove = 0x10000021;
+ const long nsImapOnlineToOfflineCopy = 0x10000022;
+ const long nsImapOnlineToOfflineMove = 0x10000023;
+ const long nsImapMsgPreview = 0x10000024;
+ const long nsImapBiff = 0x10000026;
+ const long nsImapSelectNoopFolder = 0x10000027;
+ const long nsImapAppendDraftFromFile = 0x10000028;
+ const long nsImapUidExpunge = 0x10000029;
+ const long nsImapSaveMessageToDisk = 0x10000030;
+ const long nsImapOpenMimePart = 0x10000031;
+ const long nsImapMsgDownloadForOffline = 0x10000032;
+ const long nsImapDeleteFolderAndMsgs = 0x10000033;
+ const long nsImapUserDefinedMsgCommand = 0x10000034;
+ const long nsImapUserDefinedFetchAttribute = 0x10000035;
+ const long nsImapMsgFetchPeek = 0x10000036;
+ const long nsImapMsgStoreCustomKeywords = 0x10000037;
+
+ /// Constant for the default IMAP port number
+ const int32_t DEFAULT_IMAP_PORT = 143;
+
+ /// Constant for the default IMAP over ssl port number
+ const int32_t DEFAULT_IMAPS_PORT = 993;
+};
diff --git a/mailnews/imap/public/nsIMailboxSpec.idl b/mailnews/imap/public/nsIMailboxSpec.idl
new file mode 100644
index 000000000..5888ecbf3
--- /dev/null
+++ b/mailnews/imap/public/nsIMailboxSpec.idl
@@ -0,0 +1,43 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIImapFlagAndUidState.idl"
+
+interface nsIMAPNamespace;
+
+[scriptable, uuid(a9fbbc80-5291-4ed8-a7f7-c2fcad231269)]
+interface nsIMailboxSpec : nsISupports
+{
+ attribute long folder_UIDVALIDITY;
+ /**
+ * The highest modification sequence number the parser has seen
+ * for this mailbox. See IMAP RFC 4551
+ **/
+ attribute unsigned long long highestModSeq;
+ attribute long numMessages;
+ attribute long numUnseenMessages;
+ attribute long numRecentMessages;
+
+ /// If server supports UIDNEXT, we store the result here.
+ attribute long nextUID;
+
+ attribute unsigned long box_flags;
+ attribute unsigned long supportedUserFlags;
+
+ attribute ACString allocatedPathName;
+ attribute AString unicharPathName;
+ attribute char hierarchyDelimiter;
+ attribute ACString hostName;
+
+ attribute nsIImapFlagAndUidState flagState;
+
+ attribute boolean folderSelected;
+ attribute boolean discoveredFromLsub;
+
+ attribute boolean onlineVerified;
+
+ [noscript] attribute nsIMAPNamespace namespaceForFolder;
+};
diff --git a/mailnews/imap/public/nsIMsgImapMailFolder.idl b/mailnews/imap/public/nsIMsgImapMailFolder.idl
new file mode 100644
index 000000000..f86f622b4
--- /dev/null
+++ b/mailnews/imap/public/nsIMsgImapMailFolder.idl
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgFolder.idl"
+#include "nsIStringEnumerator.idl"
+
+interface nsIMsgWindow;
+interface nsIImapIncomingServer;
+interface nsIMsgParseMailMsgState;
+interface nsIAutoSyncState;
+
+/**
+ * This is a simple interface which allows the IMAP folder to update some
+ * values that the folder props js code will use to update the sharing and
+ * quota tabs in the folder properties.
+ */
+[scriptable, uuid(09D99F2C-3E23-4f8c-A536-5C277BAA9585)]
+interface nsIMsgImapFolderProps : nsISupports {
+
+ void setFolderType(in AString folderType);
+ void setFolderTypeDescription(in AString folderTypeDescription);
+ void setFolderPermissions(in AString permissions);
+ void serverDoesntSupportACL();
+
+ /**
+ * Toggles the display of quota information in the Quota tab of the folder properties.
+ * If on, the quota root, usage, and percentage used are displayed.
+ * If off, a status message is displayed. The status message can be set with setQuotaStatus().
+ * @param showData If true, display the quota root, usage information and usage percentage bar.
+ * If false, display the status message.
+ */
+ void showQuotaData(in boolean showData);
+
+ /**
+ * Sets the status string displayed in the Quota tab of the folder properties if quota
+ * information is not visible.
+ */
+ void setQuotaStatus(in AString folderQuotaStatus);
+
+ /**
+ * Updates the quota data displayed in the Quota tab.
+ */
+ void setQuotaData(in ACString quotaroot, in unsigned long usedKB, in unsigned long maxKB);
+};
+
+[scriptable, uuid(fea0f455-7adf-4683-bf2f-c95c3fff03df)]
+interface nsIMsgImapMailFolder : nsISupports {
+ void removeSubFolder(in nsIMsgFolder folder);
+ void createClientSubfolderInfo(in ACString folderName, in char hierarchyDelimiter,
+ in long flags, in boolean suppressNotification);
+ void list();
+ void renameLocal(in ACString newname, in nsIMsgFolder parent);
+ void prepareToRename();
+ void performExpand(in nsIMsgWindow aMsgWindow);
+ void recursiveCloseActiveConnections(in nsIImapIncomingServer aImapServer);
+ void renameClient(in nsIMsgWindow msgWindow, in nsIMsgFolder msgFolder, in ACString oldName, in ACString newName);
+
+ // these are used for offline synchronization
+ void storeImapFlags(in long aFlags, in boolean aAddFlags, [array, size_is (aNumKeys)]
+ in nsMsgKey aKeysToFlag, in unsigned long aNumKeys, in nsIUrlListener aUrlListener);
+ nsIURI setImapFlags(in string uids, in long flags);
+ void replayOfflineMoveCopy([array, size_is (numKeys)] in nsMsgKey keys, in unsigned long numKeys, in boolean isMove, in nsIMsgFolder aDstFolder,
+ in nsIUrlListener aUrlListener, in nsIMsgWindow aWindow);
+ nsIURI playbackOfflineFolderCreate(in AString folderName, in nsIMsgWindow aWindow);
+ /**
+ * This is called by the offline sync code to tell the imap folder to
+ * remember info about the header with this key (messageId and key) because
+ * it's an offline move result header, and we need to generate an
+ * nsIMsgFolderListener.msgKeyChanged notification when we download the
+ * real header from the imap server.
+ *
+ * @param aMsgKey msg key of move result pseudo hdr.
+ */
+ void addMoveResultPseudoKey(in nsMsgKey aMsgKey);
+ /**
+ * Select this folder on the imap server without doing a sync of flags or
+ * headers. This is used for offline playback, where we don't want to
+ * download hdrs we don't have, because they may have been offline deleted.
+ *
+ * @param aUrlListener url listener, can be null
+ * @param aWindow msg window url is running in, can be null
+ */
+ void liteSelect(in nsIUrlListener aUrlListener, in nsIMsgWindow aWindow);
+
+ void fillInFolderProps(in nsIMsgImapFolderProps aFolderProps);
+ void resetNamespaceReferences();
+ void folderPrivileges(in nsIMsgWindow aWindow);
+ nsIMsgImapMailFolder findOnlineSubFolder(in ACString onlineName);
+ void addFolderRights(in ACString userName, in ACString rights);
+ void refreshFolderRights();
+
+ /**
+ * Mark/unmark the header as pending removal from the offline store. If mark,
+ * this also increases the expungedBytes count on the folder so we know
+ * there's more local disk space to be reclaimed.
+ *
+ * @param aHdr msg hdr to mark pending removal from offline store.
+ * @param aMark whether to set or clear the pending removal status.
+ *
+ */
+ void markPendingRemoval(in nsIMsgDBHdr aHdr, in boolean aMark);
+
+ /**
+ * Issue an expunge of this folder to the imap server.
+ *
+ * @param aUrlListener url listener, can be null
+ * @param aWindow msg window url is running in, can be null
+ *
+ * @returns status of attempt to run url.
+ */
+ void expunge(in nsIUrlListener aListener, in nsIMsgWindow aMsgWindow);
+
+ void updateStatus(in nsIUrlListener aListener, in nsIMsgWindow aMsgWindow);
+ void updateFolderWithListener(in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
+ // this is used to issue an arbitrary imap command on the passed in msgs.
+ // It assumes the command needs to be run in the selected state.
+ nsIURI issueCommandOnMsgs(in ACString command, in string uids, in nsIMsgWindow aWindow);
+ nsIURI fetchCustomMsgAttribute(in ACString msgAttribute, in string uids, in nsIMsgWindow aWindow);
+ nsIURI storeCustomKeywords(in nsIMsgWindow aMsgWindow,
+ in ACString aFlagsToAdd,
+ in ACString aFlagsToSubtract,
+ [array, size_is (aNumKeys)] in nsMsgKey aKeysToStore,
+ in unsigned long aNumKeys);
+
+ void notifyIfNewMail();
+
+ void initiateAutoSync(in nsIUrlListener aUrlListener);
+
+ attribute boolean verifiedAsOnlineFolder;
+ attribute boolean explicitlyVerify;
+ attribute char hierarchyDelimiter;
+ attribute long boxFlags;
+ attribute ACString onlineName;
+ attribute boolean isNamespace;
+ readonly attribute boolean canOpenFolder;
+ attribute ACString adminUrl;
+ readonly attribute boolean hasAdminUrl;
+ attribute boolean performingBiff;
+ readonly attribute nsIMsgParseMailMsgState hdrParser;
+ readonly attribute nsIImapIncomingServer imapIncomingServer;
+ readonly attribute nsIAutoSyncState autoSyncStateObj;
+ /**
+ * @{
+ * These are used to access the response to the STATUS or SELECT command.
+ * The counts include deleted messages, or headers we haven't downloaded yet.
+ */
+ readonly attribute long serverTotal;
+ readonly attribute long serverUnseen;
+ readonly attribute long serverRecent;
+ readonly attribute long serverNextUID;
+ /** @} */
+
+ /**
+ * Quota
+ * |valid| indicates whether the server has provided quota information on
+ * this folder. This can be false
+ * - if the server does not supports quotas,
+ * - if there are no storage quotas on this folder, or
+ * - if the folder has never been opened.
+ * If it is true and maxKB > 0, the folder has a storage quota and
+ * the usedKB and maxKB attributes are set to the values provided by
+ * the server (in kilobytes), for this quota root.
+ * Lotus Notes sends us maxKB = 0, usedKB > 0 for unlimited quota.
+ */
+ void getQuota(out boolean valid, out unsigned long usedKB, out unsigned long maxKB);
+
+ /**
+ * List all (human) users apart from the current user who have access to
+ * this folder.
+ *
+ * You can find out which rights they have with getRightsForUser().
+ */
+ nsIUTF8StringEnumerator getOtherUsersWithAccess();
+
+ /**
+ * Which access rights a certain user has for this folder.
+ *
+ * @return list of flags
+ * e.g. "lrswipcd" for write access and "lrs" for read only access.
+ *
+ * See RFC 2086 (e.g. Cyrus) and RFC 4314 (e.g. dovecot)
+ *
+ * l = locate = visible in folder list
+ * r = read = list mails, get/read mail contents
+ * s = set seen flag = mark read. Does not affect other users.
+ * d (or t) = delete mails
+ * w = write = change (other) flags of existing mails
+ * i = insert = add mails to this folder
+ * p = post = send mail directly to the submission address for folder
+ * c (or k) = create subfolders
+ * (e = expunge = compress)
+ * (x = delete folder)
+ * a = admin = change permissions
+ */
+ ACString getPermissionsForUser(in ACString username);
+
+ /**
+ * Change the number of "pending" messages in a folder,
+ * messages we know about, but don't have the headers for yet
+ *
+ * @param aDelta amount to change total by.
+ */
+ void changePendingTotal(in long aDelta);
+
+ /**
+ * Change the number of "pending" unread messages in a folder,
+ * unread messages we know about, but don't have the headers for yet
+ *
+ * @param aDelta amount to change the unread count by.
+ */
+ void changePendingUnread(in long aDelta);
+};
diff --git a/mailnews/imap/public/nsMsgImapCID.h b/mailnews/imap/public/nsMsgImapCID.h
new file mode 100644
index 000000000..2ba83a28f
--- /dev/null
+++ b/mailnews/imap/public/nsMsgImapCID.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgImapCID_h__
+#define nsMsgImapCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+#include "nsMsgBaseCID.h"
+
+#define NS_IMAPURL_CID \
+{ /* 21A89611-DC0D-11d2-806C-006008128C4E */ \
+ 0x21a89611, 0xdc0d, 0x11d2, \
+ { 0x80, 0x6c, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e }}
+
+#define NS_IMAPPROTOCOLINFO_CONTRACTID \
+ NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX "imap"
+
+#define NS_IMAPINCOMINGSERVER_CONTRACTID \
+ NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX "imap"
+
+#define NS_IMAPSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/imapservice;1"
+
+#define NS_IMAPSERVICE_CID \
+{ /* C5852B22-EBE2-11d2-95AD-000064657374 */ \
+ 0xc5852b22, 0xebe2, 0x11d2, \
+ {0x95, 0xad, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74}}
+
+#define NS_IMAPPROTOCOL_CID \
+{ /* 8C0C40D1-E173-11d2-806E-006008128C4E */ \
+ 0x8c0c40d1, 0xe173, 0x11d2, \
+ { 0x80, 0x6e, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e }}
+
+
+#define NS_IIMAPHOSTSESSIONLIST_CID \
+{ /* 479ce8fc-e725-11d2-a505-0060b0fc04b7 */ \
+ 0x479ce8fc, 0xe725, 0x11d2, \
+ {0xa5, 0x05, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 }}
+
+#define NS_IMAPINCOMINGSERVER_CID \
+{ /* 8D3675E0-ED46-11d2-8077-006008128C4E */ \
+ 0x8d3675e0, 0xed46, 0x11d2, \
+ {0x80, 0x77, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e}}
+
+#define NS_IMAPRESOURCE_CID \
+{ /* fa32d000-f6a0-11d2-af8d-001083002da8 */ \
+ 0xfa32d000, 0xf6a0, 0x11d2, \
+ { 0xaf, 0x8d, 0x00, 0x10, 0x83, 0x00, 0x2d, 0xa8 }}
+
+// 4ECA51DF-6734-11d3-989A-001083010E9B
+#define NS_IMAPMOCKCHANNEL_CID \
+ {0x4eca51df, 0x6734, 0x11d3, \
+ {0x98, 0x9a, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b}}
+
+#endif // nsMsgImapCID_h__
diff --git a/mailnews/imap/src/moz.build b/mailnews/imap/src/moz.build
new file mode 100644
index 000000000..e62ec5fa8
--- /dev/null
+++ b/mailnews/imap/src/moz.build
@@ -0,0 +1,33 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'nsImapCore.h',
+]
+
+SOURCES += [
+ 'nsAutoSyncManager.cpp',
+ 'nsAutoSyncState.cpp',
+ 'nsIMAPBodyShell.cpp',
+ 'nsImapFlagAndUidState.cpp',
+ 'nsIMAPGenericParser.cpp',
+ 'nsIMAPHostSessionList.cpp',
+ 'nsImapIncomingServer.cpp',
+ 'nsImapMailFolder.cpp',
+ 'nsIMAPNamespace.cpp',
+ 'nsImapOfflineSync.cpp',
+ 'nsImapProtocol.cpp',
+ 'nsImapSearchResults.cpp',
+ 'nsImapServerResponseParser.cpp',
+ 'nsImapService.cpp',
+ 'nsImapStringBundle.cpp',
+ 'nsImapUndoTxn.cpp',
+ 'nsImapUrl.cpp',
+ 'nsImapUtils.cpp',
+ 'nsSyncRunnableHelpers.cpp',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/imap/src/nsAutoSyncManager.cpp b/mailnews/imap/src/nsAutoSyncManager.cpp
new file mode 100644
index 000000000..9778919c7
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncManager.cpp
@@ -0,0 +1,1412 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAutoSyncManager.h"
+#include "nsAutoSyncState.h"
+#include "nsIIdleService.h"
+#include "nsImapMailFolder.h"
+#include "nsMsgImapCID.h"
+#include "nsIObserverService.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgFolderFlags.h"
+#include "nsImapIncomingServer.h"
+#include "nsMsgUtils.h"
+#include "nsIIOService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsArrayUtils.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsDefaultAutoSyncMsgStrategy, nsIAutoSyncMsgStrategy)
+
+const char* kAppIdleNotification = "mail:appIdle";
+const char* kStartupDoneNotification = "mail-startup-done";
+PRLogModuleInfo *gAutoSyncLog;
+
+// recommended size of each group of messages per download
+static const uint32_t kDefaultGroupSize = 50U*1024U /* 50K */;
+
+nsDefaultAutoSyncMsgStrategy::nsDefaultAutoSyncMsgStrategy()
+{
+}
+
+nsDefaultAutoSyncMsgStrategy::~nsDefaultAutoSyncMsgStrategy()
+{
+}
+
+NS_IMETHODIMP nsDefaultAutoSyncMsgStrategy::Sort(nsIMsgFolder *aFolder,
+ nsIMsgDBHdr *aMsgHdr1, nsIMsgDBHdr *aMsgHdr2, nsAutoSyncStrategyDecisionType *aDecision)
+{
+ NS_ENSURE_ARG_POINTER(aDecision);
+
+ uint32_t msgSize1 = 0, msgSize2 = 0;
+ PRTime msgDate1 = 0, msgDate2 = 0;
+
+ if (!aMsgHdr1 || !aMsgHdr2)
+ {
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ return NS_OK;
+ }
+
+ aMsgHdr1->GetMessageSize(&msgSize1);
+ aMsgHdr1->GetDate(&msgDate1);
+
+ aMsgHdr2->GetMessageSize(&msgSize2);
+ aMsgHdr2->GetDate(&msgDate2);
+
+ //Special case: if message size is larger than a
+ // certain size, then place it to the bottom of the q
+ if (msgSize2 > kFirstPassMessageSize && msgSize1 > kFirstPassMessageSize)
+ *aDecision = msgSize2 > msgSize1 ?
+ nsAutoSyncStrategyDecisions::Lower : nsAutoSyncStrategyDecisions::Higher;
+ else if (msgSize2 > kFirstPassMessageSize)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else if (msgSize1 > kFirstPassMessageSize)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else
+ {
+ // Most recent and smallest first
+ if (msgDate1 < msgDate2)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else if (msgDate1 > msgDate2)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else
+ {
+ if (msgSize1 > msgSize2)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else if (msgSize1 < msgSize2)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDefaultAutoSyncMsgStrategy::IsExcluded(nsIMsgFolder *aFolder,
+ nsIMsgDBHdr *aMsgHdr, bool *aDecision)
+{
+ NS_ENSURE_ARG_POINTER(aDecision);
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aFolder);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ nsresult rv = aFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapIncomingServer> imapServer(do_QueryInterface(server, &rv));
+ int32_t offlineMsgAgeLimit = -1;
+ imapServer->GetAutoSyncMaxAgeDays(&offlineMsgAgeLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ PRTime msgDate;
+ aMsgHdr->GetDate(&msgDate);
+ *aDecision = offlineMsgAgeLimit > 0 &&
+ msgDate < MsgConvertAgeInDaysToCutoffDate(offlineMsgAgeLimit);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsDefaultAutoSyncFolderStrategy, nsIAutoSyncFolderStrategy)
+
+nsDefaultAutoSyncFolderStrategy::nsDefaultAutoSyncFolderStrategy()
+{
+}
+
+nsDefaultAutoSyncFolderStrategy::~nsDefaultAutoSyncFolderStrategy()
+{
+}
+
+NS_IMETHODIMP nsDefaultAutoSyncFolderStrategy::Sort(nsIMsgFolder *aFolderA,
+ nsIMsgFolder *aFolderB, nsAutoSyncStrategyDecisionType *aDecision)
+{
+ NS_ENSURE_ARG_POINTER(aDecision);
+
+ if (!aFolderA || !aFolderB)
+ {
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ return NS_OK;
+ }
+
+ bool isInbox1, isInbox2, isDrafts1, isDrafts2, isTrash1, isTrash2;
+ aFolderA->GetFlag(nsMsgFolderFlags::Inbox, &isInbox1);
+ aFolderB->GetFlag(nsMsgFolderFlags::Inbox, &isInbox2);
+ //
+ aFolderA->GetFlag(nsMsgFolderFlags::Drafts, &isDrafts1);
+ aFolderB->GetFlag(nsMsgFolderFlags::Drafts, &isDrafts2);
+ //
+ aFolderA->GetFlag(nsMsgFolderFlags::Trash, &isTrash1);
+ aFolderB->GetFlag(nsMsgFolderFlags::Trash, &isTrash2);
+
+ //Follow this order;
+ // INBOX > DRAFTS > SUBFOLDERS > TRASH
+
+ // test whether the folder is opened by the user.
+ // we give high priority to the folders explicitly opened by
+ // the user.
+ nsresult rv;
+ bool folderAOpen = false;
+ bool folderBOpen = false;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && session)
+ {
+ session->IsFolderOpenInWindow(aFolderA, &folderAOpen);
+ session->IsFolderOpenInWindow(aFolderB, &folderBOpen);
+ }
+
+ if (folderAOpen == folderBOpen)
+ {
+ // if both of them or none of them are opened by the user
+ // make your decision based on the folder type
+ if (isInbox2 || (isDrafts2 && !isInbox1) || isTrash1)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else if (isInbox1 || (isDrafts1 && !isDrafts2) || isTrash2)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ }
+ else
+ {
+ // otherwise give higher priority to opened one
+ *aDecision = folderBOpen ? nsAutoSyncStrategyDecisions::Higher :
+ nsAutoSyncStrategyDecisions::Lower;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDefaultAutoSyncFolderStrategy::IsExcluded(nsIMsgFolder *aFolder, bool *aDecision)
+{
+ NS_ENSURE_ARG_POINTER(aDecision);
+ NS_ENSURE_ARG_POINTER(aFolder);
+ uint32_t folderFlags;
+ aFolder->GetFlags(&folderFlags);
+ // exclude saved search
+ *aDecision = (folderFlags & nsMsgFolderFlags::Virtual);
+ if (!*aDecision)
+ {
+ // Exclude orphans
+ nsCOMPtr<nsIMsgFolder> parent;
+ aFolder->GetParent(getter_AddRefs(parent));
+ if (!parent)
+ *aDecision = true;
+ }
+ return NS_OK;
+}
+
+#define NOTIFY_LISTENERS_STATIC(obj_, propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<nsCOMPtr<nsIAutoSyncMgrListener> >::ForwardIterator iter(obj_->mListeners); \
+ nsCOMPtr<nsIAutoSyncMgrListener> listener; \
+ while (iter.HasMore()) { \
+ listener = iter.GetNext(); \
+ listener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+#define NOTIFY_LISTENERS(propertyfunc_, params_) \
+ NOTIFY_LISTENERS_STATIC(this, propertyfunc_, params_)
+
+nsAutoSyncManager::nsAutoSyncManager()
+{
+ mGroupSize = kDefaultGroupSize;
+
+ mIdleState = notIdle;
+ mStartupDone = false;
+ mDownloadModel = dmChained;
+ mUpdateState = completed;
+ mPaused = false;
+
+ nsresult rv;
+ mIdleService = do_GetService("@mozilla.org/widget/idleservice;1", &rv);
+ if (mIdleService)
+ mIdleService->AddIdleObserver(this, kIdleTimeInSec);
+
+ // Observe xpcom-shutdown event and app-idle changes
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ rv = observerService->AddObserver(this,
+ NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ false);
+ observerService->AddObserver(this, kAppIdleNotification, false);
+ observerService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false);
+ observerService->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, false);
+ observerService->AddObserver(this, kStartupDoneNotification, false);
+ gAutoSyncLog = PR_NewLogModule("ImapAutoSync");
+}
+
+nsAutoSyncManager::~nsAutoSyncManager()
+{
+}
+
+void nsAutoSyncManager::InitTimer()
+{
+ if (!mTimer)
+ {
+ nsresult rv;
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create timer in nsAutoSyncManager");
+
+ mTimer->InitWithFuncCallback(TimerCallback, (void *) this,
+ kTimerIntervalInMs, nsITimer::TYPE_REPEATING_SLACK);
+ }
+}
+
+void nsAutoSyncManager::StopTimer()
+{
+ if (mTimer)
+ {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void nsAutoSyncManager::StartTimerIfNeeded()
+{
+ if ((mUpdateQ.Count() > 0 || mDiscoveryQ.Count() > 0) && !mTimer)
+ InitTimer();
+}
+
+void nsAutoSyncManager::TimerCallback(nsITimer *aTimer, void *aClosure)
+{
+ if (!aClosure)
+ return;
+
+ nsAutoSyncManager *autoSyncMgr = static_cast<nsAutoSyncManager*>(aClosure);
+ if (autoSyncMgr->GetIdleState() == notIdle ||
+ (autoSyncMgr->mDiscoveryQ.Count() <= 0 && autoSyncMgr->mUpdateQ.Count() <= 0))
+ {
+ // Idle will create a new timer automatically if discovery Q or update Q is not empty
+ autoSyncMgr->StopTimer();
+ }
+
+ // process folders within the discovery queue
+ if (autoSyncMgr->mDiscoveryQ.Count() > 0)
+ {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mDiscoveryQ[0]);
+ if (autoSyncStateObj)
+ {
+ uint32_t leftToProcess;
+ nsresult rv = autoSyncStateObj->ProcessExistingHeaders(kNumberOfHeadersToProcess, &leftToProcess);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder)
+ NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnDiscoveryQProcessed, (folder, kNumberOfHeadersToProcess, leftToProcess));
+
+ if (NS_SUCCEEDED(rv) && 0 == leftToProcess)
+ {
+ autoSyncMgr->mDiscoveryQ.RemoveObjectAt(0);
+ if (folder)
+ NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnFolderRemovedFromQ, (nsIAutoSyncMgrListener::DiscoveryQueue, folder));
+ }
+ }
+ }
+
+ if (autoSyncMgr->mUpdateQ.Count() > 0)
+ {
+ if (autoSyncMgr->mUpdateState == completed)
+ {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mUpdateQ[0]);
+ if (autoSyncStateObj)
+ {
+ int32_t state;
+ nsresult rv = autoSyncStateObj->GetState(&state);
+ if (NS_SUCCEEDED(rv) && (state == nsAutoSyncState::stCompletedIdle ||
+ state == nsAutoSyncState::stUpdateNeeded))
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = imapFolder->InitiateAutoSync(autoSyncMgr);
+ if (NS_SUCCEEDED(rv))
+ {
+ autoSyncMgr->mUpdateState = initiated;
+ NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnAutoSyncInitiated, (folder));
+ }
+ }
+ }
+ }
+ }
+ // if initiation is not successful for some reason, or
+ // if there is an on going download for this folder,
+ // remove it from q and continue with the next one
+ if (autoSyncMgr->mUpdateState != initiated)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncMgr->mUpdateQ[0]->GetOwnerFolder(getter_AddRefs(folder));
+
+ autoSyncMgr->mUpdateQ.RemoveObjectAt(0);
+
+ if (folder)
+ NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnFolderRemovedFromQ, (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ }
+
+ }//endif
+
+}
+
+/**
+ * Populates aChainedQ with the auto-sync state objects that are not owned by
+ * the same imap server.
+ * Assumes that aChainedQ initially empty.
+ */
+void nsAutoSyncManager::ChainFoldersInQ(const nsCOMArray<nsIAutoSyncState> &aQueue,
+ nsCOMArray<nsIAutoSyncState> &aChainedQ)
+{
+ if (aQueue.Count() > 0)
+ aChainedQ.AppendObject(aQueue[0]);
+
+ int32_t pqElemCount = aQueue.Count();
+ for (int32_t pqidx = 1; pqidx < pqElemCount; pqidx++)
+ {
+ bool chained = false;
+ int32_t needToBeReplacedWith = -1;
+ int32_t elemCount = aChainedQ.Count();
+ for (int32_t idx = 0; idx < elemCount; idx++)
+ {
+ bool isSibling;
+ nsresult rv = aChainedQ[idx]->IsSibling(aQueue[pqidx], &isSibling);
+
+ if (NS_SUCCEEDED(rv) && isSibling)
+ {
+ // this prevent us to overwrite a lower priority sibling in
+ // download-in-progress state with a higher priority one.
+ // we have to wait until its download is completed before
+ // switching to new one.
+ int32_t state;
+ aQueue[pqidx]->GetState(&state);
+ if (aQueue[pqidx] != aChainedQ[idx] &&
+ state == nsAutoSyncState::stDownloadInProgress)
+ needToBeReplacedWith = idx;
+ else
+ chained = true;
+
+ break;
+ }
+ }//endfor
+
+ if (needToBeReplacedWith > -1)
+ aChainedQ.ReplaceObjectAt(aQueue[pqidx], needToBeReplacedWith);
+ else if (!chained)
+ aChainedQ.AppendObject(aQueue[pqidx]);
+
+ }//endfor
+}
+
+/**
+ * Searches the given queue for another folder owned by the same imap server.
+ */
+nsIAutoSyncState*
+nsAutoSyncManager::SearchQForSibling(const nsCOMArray<nsIAutoSyncState> &aQueue,
+ nsIAutoSyncState *aAutoSyncStateObj, int32_t aStartIdx, int32_t *aIndex)
+{
+ if (aIndex)
+ *aIndex = -1;
+
+ if (aAutoSyncStateObj)
+ {
+ bool isSibling;
+ int32_t elemCount = aQueue.Count();
+ for (int32_t idx = aStartIdx; idx < elemCount; idx++)
+ {
+ nsresult rv = aAutoSyncStateObj->IsSibling(aQueue[idx], &isSibling);
+
+ if (NS_SUCCEEDED(rv) && isSibling && aAutoSyncStateObj != aQueue[idx])
+ {
+ if (aIndex)
+ *aIndex = idx;
+
+ return aQueue[idx];
+ }
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * Searches for the next folder owned by the same imap server in the given queue,
+ * starting from the index of the given folder.
+ */
+nsIAutoSyncState*
+nsAutoSyncManager::GetNextSibling(const nsCOMArray<nsIAutoSyncState> &aQueue,
+ nsIAutoSyncState *aAutoSyncStateObj, int32_t *aIndex)
+{
+
+ if (aIndex)
+ *aIndex = -1;
+
+ if (aAutoSyncStateObj)
+ {
+ bool located = false;
+ bool isSibling;
+ int32_t elemCount = aQueue.Count();
+ for (int32_t idx = 0; idx < elemCount; idx++)
+ {
+ if (!located)
+ {
+ located = (aAutoSyncStateObj == aQueue[idx]);
+ continue;
+ }
+
+ nsresult rv = aAutoSyncStateObj->IsSibling(aQueue[idx], &isSibling);
+ if (NS_SUCCEEDED(rv) && isSibling)
+ {
+ if (aIndex)
+ *aIndex = idx;
+
+ return aQueue[idx];
+ }
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * Checks whether there is another folder in the given q that is owned
+ * by the same imap server or not.
+ *
+ * @param aQueue the queue that will be searched for a sibling
+ * @param aAutoSyncStateObj the auto-sync state object that we are looking
+ * a sibling for
+ * @param aState the state of the sibling. -1 means "any state"
+ * @param aIndex [out] the index of the found sibling, if it is provided by the
+ * caller (not null)
+ * @return true if found, false otherwise
+ */
+bool nsAutoSyncManager::DoesQContainAnySiblingOf(const nsCOMArray<nsIAutoSyncState> &aQueue,
+ nsIAutoSyncState *aAutoSyncStateObj,
+ const int32_t aState, int32_t *aIndex)
+{
+ if (aState == -1)
+ return (nullptr != SearchQForSibling(aQueue, aAutoSyncStateObj, 0, aIndex));
+
+ int32_t offset = 0;
+ nsIAutoSyncState *autoSyncState;
+ while ((autoSyncState = SearchQForSibling(aQueue, aAutoSyncStateObj, offset, &offset)))
+ {
+ int32_t state;
+ nsresult rv = autoSyncState->GetState(&state);
+ if (NS_SUCCEEDED(rv) && aState == state)
+ break;
+ else
+ offset++;
+ }
+ if (aIndex)
+ *aIndex = offset;
+
+ return (nullptr != autoSyncState);
+}
+
+/**
+ * Searches the given queue for the highest priority folder owned by the
+ * same imap server.
+ */
+nsIAutoSyncState*
+nsAutoSyncManager::GetHighestPrioSibling(const nsCOMArray<nsIAutoSyncState> &aQueue,
+ nsIAutoSyncState *aAutoSyncStateObj, int32_t *aIndex)
+{
+ return SearchQForSibling(aQueue, aAutoSyncStateObj, 0, aIndex);
+}
+
+// to chain update folder actions
+NS_IMETHODIMP nsAutoSyncManager::OnStartRunningUrl(nsIURI* aUrl)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsAutoSyncManager::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode)
+{
+ mUpdateState = completed;
+ if (mUpdateQ.Count() > 0)
+ mUpdateQ.RemoveObjectAt(0);
+
+ return aExitCode;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::Pause()
+{
+ StopTimer();
+ mPaused = true;
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("autosync paused\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::Resume()
+{
+ mPaused = false;
+ StartTimerIfNeeded();
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("autosync resumed\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::Observe(nsISupports*, const char *aTopic, const char16_t *aSomeData)
+{
+ if (!PL_strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID))
+ {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ observerService->RemoveObserver(this, kAppIdleNotification);
+ observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+ observerService->RemoveObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC);
+ observerService->RemoveObserver(this, kStartupDoneNotification);
+ }
+
+ // cancel and release the timer
+ if (mTimer)
+ {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ // unsubscribe from idle service
+ if (mIdleService)
+ mIdleService->RemoveIdleObserver(this, kIdleTimeInSec);
+
+ return NS_OK;
+ }
+ else if (!PL_strcmp(aTopic, kStartupDoneNotification))
+ {
+ mStartupDone = true;
+ }
+ else if (!PL_strcmp(aTopic, kAppIdleNotification))
+ {
+ if (nsDependentString(aSomeData).EqualsLiteral("idle"))
+ {
+ IdleState prevIdleState = GetIdleState();
+
+ // we were already idle (either system or app), so
+ // just remember that we're app idle and return.
+ SetIdleState(appIdle);
+ if (prevIdleState != notIdle)
+ return NS_OK;
+
+ return StartIdleProcessing();
+ }
+ // we're back from appIdle - if already notIdle, just return;
+ else if (GetIdleState() == notIdle)
+ return NS_OK;
+
+ SetIdleState(notIdle);
+ NOTIFY_LISTENERS(OnStateChanged, (false));
+ return NS_OK;
+ }
+ else if (!PL_strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC))
+ {
+ if (nsDependentString(aSomeData).EqualsLiteral(NS_IOSERVICE_ONLINE))
+ Resume();
+ }
+ else if (!PL_strcmp(aTopic, NS_IOSERVICE_GOING_OFFLINE_TOPIC))
+ {
+ Pause();
+ }
+ // we're back from system idle
+ else if (!PL_strcmp(aTopic, "back"))
+ {
+ // if we're app idle when we get back from system idle, we ignore
+ // it, since we'll keep doing our idle stuff.
+ if (GetIdleState() != appIdle)
+ {
+ SetIdleState(notIdle);
+ NOTIFY_LISTENERS(OnStateChanged, (false));
+ }
+ return NS_OK;
+ }
+ else // we've gone system idle
+ {
+ // Check if we were already idle. We may have gotten
+ // multiple system idle notificatons. In that case,
+ // just remember that we're systemIdle and return;
+ if (GetIdleState() != notIdle)
+ return NS_OK;
+
+ // we might want to remember if we were app idle, because
+ // coming back from system idle while app idle shouldn't stop
+ // app indexing. But I think it's OK for now just leave ourselves
+ // in appIdle state.
+ if (GetIdleState() != appIdle)
+ SetIdleState(systemIdle);
+ if (WeAreOffline())
+ return NS_OK;
+ return StartIdleProcessing();
+ }
+ return NS_OK;
+}
+
+nsresult nsAutoSyncManager::StartIdleProcessing()
+{
+ if (mPaused)
+ return NS_OK;
+
+ StartTimerIfNeeded();
+
+ // Ignore idle events sent during the startup
+ if (!mStartupDone)
+ return NS_OK;
+
+ // notify listeners that auto-sync is running
+ NOTIFY_LISTENERS(OnStateChanged, (true));
+
+ nsCOMArray<nsIAutoSyncState> chainedQ;
+ nsCOMArray<nsIAutoSyncState> *queue = &mPriorityQ;
+ if (mDownloadModel == dmChained)
+ {
+ ChainFoldersInQ(mPriorityQ, chainedQ);
+ queue = &chainedQ;
+ }
+
+ // to store the folders that should be removed from the priority
+ // queue at the end of the iteration.
+ nsCOMArray<nsIAutoSyncState> foldersToBeRemoved;
+
+ // process folders in the priority queue
+ int32_t elemCount = queue->Count();
+ for (int32_t idx = 0; idx < elemCount; idx++)
+ {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj((*queue)[idx]);
+ if (!autoSyncStateObj)
+ continue;
+
+ int32_t state;
+ autoSyncStateObj->GetState(&state);
+
+ //TODO: Test cached-connection availability in parallel mode
+ // and do not exceed (cached-connection count - 1)
+
+ if (state != nsAutoSyncState::stReadyToDownload)
+ continue;
+
+ nsresult rv = DownloadMessagesForOffline(autoSyncStateObj);
+ if (NS_FAILED(rv))
+ {
+ // special case: this folder does not have any message to download
+ // (see bug 457342), remove it explicitly from the queue when iteration
+ // is over.
+ // Note that in normal execution flow, folders are removed from priority
+ // queue only in OnDownloadCompleted when all messages are downloaded
+ // successfully. This is the only place we change this flow.
+ if (NS_ERROR_NOT_AVAILABLE == rv)
+ foldersToBeRemoved.AppendObject(autoSyncStateObj);
+
+ HandleDownloadErrorFor(autoSyncStateObj, rv);
+ }// endif
+ }//endfor
+
+ // remove folders with no pending messages from the priority queue
+ elemCount = foldersToBeRemoved.Count();
+ for (int32_t idx = 0; idx < elemCount; idx++)
+ {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(foldersToBeRemoved[idx]);
+ if (!autoSyncStateObj)
+ continue;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder)
+ NOTIFY_LISTENERS(OnDownloadCompleted, (folder));
+
+ autoSyncStateObj->SetState(nsAutoSyncState::stCompletedIdle);
+
+ if (mPriorityQ.RemoveObject(autoSyncStateObj))
+ NOTIFY_LISTENERS(OnFolderRemovedFromQ,
+ (nsIAutoSyncMgrListener::PriorityQueue, folder));
+ }
+
+ return AutoUpdateFolders();
+}
+
+/**
+ * Updates offline imap folders that are not synchronized recently. This is
+ * called whenever we're idle.
+ */
+nsresult nsAutoSyncManager::AutoUpdateFolders()
+{
+ nsresult rv;
+
+ // iterate through each imap account and update offline folders automatically
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIArray> accounts;
+ rv = accountManager->GetAccounts(getter_AddRefs(accounts));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t accountCount;
+ accounts->GetLength(&accountCount);
+
+ for (uint32_t i = 0; i < accountCount; ++i)
+ {
+ nsCOMPtr<nsIMsgAccount> account(do_QueryElementAt(accounts, i, &rv));
+ if (!account)
+ continue;
+
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = account->GetIncomingServer(getter_AddRefs(incomingServer));
+ if (!incomingServer)
+ continue;
+
+ nsCString type;
+ rv = incomingServer->GetType(type);
+
+ if (!type.EqualsLiteral("imap"))
+ continue;
+
+ // if we haven't logged onto this server yet, then skip this server.
+ bool passwordRequired;
+ incomingServer->GetServerRequiresPasswordForBiff(&passwordRequired);
+ if (passwordRequired)
+ continue;
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIArray> allDescendants;
+
+ rv = incomingServer->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ {
+ if (NS_FAILED(rv))
+ continue;
+
+ rv = rootFolder->GetDescendants(getter_AddRefs(allDescendants));
+ if (!allDescendants)
+ continue;
+
+ uint32_t cnt = 0;
+ rv = allDescendants->GetLength(&cnt);
+ if (NS_FAILED(rv))
+ continue;
+
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryElementAt(allDescendants, i, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ uint32_t folderFlags;
+ rv = folder->GetFlags(&folderFlags);
+ // Skip this folder if not offline or is a saved search or is no select.
+ if (NS_FAILED(rv) || !(folderFlags & nsMsgFolderFlags::Offline) ||
+ folderFlags & (nsMsgFolderFlags::Virtual |
+ nsMsgFolderFlags::ImapNoselect))
+ continue;
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = imapFolder->GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer)
+ {
+ bool autoSyncOfflineStores = false;
+ rv = imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
+
+ // skip if AutoSyncOfflineStores pref is not set for this folder
+ if (NS_FAILED(rv) || !autoSyncOfflineStores)
+ continue;
+ }
+
+ nsCOMPtr<nsIAutoSyncState> autoSyncState;
+ rv = imapFolder->GetAutoSyncStateObj(getter_AddRefs(autoSyncState));
+ NS_ASSERTION(autoSyncState, "*** nsAutoSyncState shouldn't be NULL, check owner folder");
+
+ // shouldn't happen but let's be defensive here
+ if (!autoSyncState)
+ continue;
+
+ int32_t state;
+ rv = autoSyncState->GetState(&state);
+
+ if (NS_SUCCEEDED(rv) && nsAutoSyncState::stCompletedIdle == state)
+ {
+ // ensure that we wait for at least nsMsgIncomingServer::BiffMinutes between
+ // each update of the same folder
+ PRTime lastUpdateTime;
+ rv = autoSyncState->GetLastUpdateTime(&lastUpdateTime);
+ PRTime span = GetUpdateIntervalFor(autoSyncState) * (PR_USEC_PER_SEC * 60UL);
+ if ( NS_SUCCEEDED(rv) && ((lastUpdateTime + span) < PR_Now()) )
+ {
+ if (mUpdateQ.IndexOf(autoSyncState) == -1)
+ {
+ mUpdateQ.AppendObject(autoSyncState);
+ if (folder)
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ, (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ }
+ }
+ }
+
+ // check last sync time
+ PRTime lastSyncTime;
+ rv = autoSyncState->GetLastSyncTime(&lastSyncTime);
+ if ( NS_SUCCEEDED(rv) && ((lastSyncTime + kAutoSyncFreq) < PR_Now()) )
+ {
+ // add this folder into discovery queue to process existing headers
+ // and discover messages not downloaded yet
+ if (mDiscoveryQ.IndexOf(autoSyncState) == -1)
+ {
+ mDiscoveryQ.AppendObject(autoSyncState);
+ if (folder)
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ, (nsIAutoSyncMgrListener::DiscoveryQueue, folder));
+ }
+ }
+ }//endfor
+ }//endif
+ }//endfor
+
+ // lazily create the timer if there is something to process in the queue
+ // when timer is done, it will self destruct
+ StartTimerIfNeeded();
+
+ return rv;
+}
+
+/**
+ * Places the given folder into the priority queue based on active
+ * strategy function.
+ */
+void nsAutoSyncManager::ScheduleFolderForOfflineDownload(nsIAutoSyncState *aAutoSyncStateObj)
+{
+ if (aAutoSyncStateObj && (mPriorityQ.IndexOf(aAutoSyncStateObj) == -1))
+ {
+ nsCOMPtr<nsIAutoSyncFolderStrategy> folStrategy;
+ GetFolderStrategy(getter_AddRefs(folStrategy));
+
+ if (mPriorityQ.Count() <= 0)
+ {
+ // make sure that we don't insert a folder excluded by the given strategy
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder)
+ {
+ bool excluded = false;
+ if (folStrategy)
+ folStrategy->IsExcluded(folder, &excluded);
+
+ if (!excluded)
+ {
+ mPriorityQ.AppendObject(aAutoSyncStateObj); // insert into the first spot
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ, (nsIAutoSyncMgrListener::PriorityQueue, folder));
+ }
+ }
+ }
+ else
+ {
+ // find the right spot for the given folder
+ uint32_t qidx = mPriorityQ.Count();
+ while (qidx > 0)
+ {
+ --qidx;
+
+ nsCOMPtr<nsIMsgFolder> folderA, folderB;
+ mPriorityQ[qidx]->GetOwnerFolder(getter_AddRefs(folderA));
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folderB));
+
+ bool excluded = false;
+ if (folderB && folStrategy)
+ folStrategy->IsExcluded(folderB, &excluded);
+
+ if (excluded)
+ break;
+
+ nsAutoSyncStrategyDecisionType decision = nsAutoSyncStrategyDecisions::Same;
+ if (folderA && folderB && folStrategy)
+ folStrategy->Sort(folderA, folderB, &decision);
+
+ if (decision == nsAutoSyncStrategyDecisions::Higher && 0 == qidx)
+ mPriorityQ.InsertObjectAt(aAutoSyncStateObj, 0);
+ else if (decision == nsAutoSyncStrategyDecisions::Higher)
+ continue;
+ else if (decision == nsAutoSyncStrategyDecisions::Lower)
+ mPriorityQ.InsertObjectAt(aAutoSyncStateObj, qidx+1);
+ else // decision == nsAutoSyncStrategyDecisions::Same
+ mPriorityQ.InsertObjectAt(aAutoSyncStateObj, qidx);
+
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ, (nsIAutoSyncMgrListener::PriorityQueue, folderB));
+ break;
+ }//endwhile
+ }
+ }//endif
+}
+
+/**
+ * Zero aSizeLimit means no limit
+ */
+nsresult nsAutoSyncManager::DownloadMessagesForOffline(nsIAutoSyncState *aAutoSyncStateObj, uint32_t aSizeLimit)
+{
+ if (!aAutoSyncStateObj)
+ return NS_ERROR_INVALID_ARG;
+
+ int32_t count;
+ nsresult rv = aAutoSyncStateObj->GetPendingMessageCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // special case: no more message to download for this folder:
+ // see HandleDownloadErrorFor for recovery policy
+ if (!count)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIMutableArray> messagesToDownload;
+ uint32_t totalSize = 0;
+ rv = aAutoSyncStateObj->GetNextGroupOfMessages(mGroupSize, &totalSize, getter_AddRefs(messagesToDownload));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // there are pending messages but the cumulative size is zero:
+ // treat as special case.
+ // Note that although it shouldn't happen, we know that sometimes
+ // imap servers manifest messages as zero length. By returning
+ // NS_ERROR_NOT_AVAILABLE we cause this folder to be removed from
+ // the priority queue temporarily (until the next idle or next update)
+ // in an effort to prevent it blocking other folders of the same account
+ // being synced.
+ if (!totalSize)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // ensure that we don't exceed the given size limit for this particular group
+ if (aSizeLimit && aSizeLimit < totalSize)
+ return NS_ERROR_FAILURE;
+
+ uint32_t length;
+ rv = messagesToDownload->GetLength(&length);
+ if (NS_SUCCEEDED(rv) && length > 0)
+ {
+ rv = aAutoSyncStateObj->DownloadMessagesForOffline(messagesToDownload);
+
+ int32_t totalCount;
+ (void) aAutoSyncStateObj->GetTotalMessageCount(&totalCount);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder)
+ NOTIFY_LISTENERS(OnDownloadStarted, (folder, length, totalCount));
+ }
+
+ return rv;
+}
+
+/**
+ * Assuming that the download operation on the given folder has been failed at least once,
+ * execute these steps:
+ * - put the auto-sync state into ready-to-download mode
+ * - rollback the message offset so we can try the same group again (unless the retry
+ * count is reached to the given limit)
+ * - if parallel model is active, wait to be resumed by the next idle
+ * - if chained model is active, search the priority queue to find a sibling to continue
+ * with.
+ */
+nsresult nsAutoSyncManager::HandleDownloadErrorFor(nsIAutoSyncState *aAutoSyncStateObj,
+ const nsresult error)
+{
+ if (!aAutoSyncStateObj)
+ return NS_ERROR_INVALID_ARG;
+
+ // ensure that an error occured
+ if (NS_SUCCEEDED(error))
+ return NS_OK;
+
+ // NS_ERROR_NOT_AVAILABLE is a special case/error happens when the queued folder
+ // doesn't have any message to download (see bug 457342). In such case we shouldn't
+ // retry the current message group, nor notify listeners. Simply continuing with the
+ // next sibling in the priority queue would suffice.
+
+ if (NS_ERROR_NOT_AVAILABLE != error)
+ {
+ // force the auto-sync state to try downloading the same group at least
+ // kGroupRetryCount times before it moves to the next one
+ aAutoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder)
+ NOTIFY_LISTENERS(OnDownloadError, (folder));
+ }
+
+ // if parallel model, don't do anything else
+
+ if (mDownloadModel == dmChained)
+ {
+ // switch to the next folder in the chain and continue downloading
+ nsIAutoSyncState *autoSyncStateObj = aAutoSyncStateObj;
+ nsIAutoSyncState *nextAutoSyncStateObj = nullptr;
+ while ( (nextAutoSyncStateObj = GetNextSibling(mPriorityQ, autoSyncStateObj)) )
+ {
+ autoSyncStateObj = nextAutoSyncStateObj;
+ nsresult rv = DownloadMessagesForOffline(autoSyncStateObj);
+ if (NS_SUCCEEDED(rv))
+ break;
+ else if (rv == NS_ERROR_NOT_AVAILABLE)
+ // next folder in the chain also doesn't have any message to download
+ // switch to next one if any
+ continue;
+ else
+ autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+ }
+ }
+
+ return NS_OK;
+}
+
+uint32_t nsAutoSyncManager::GetUpdateIntervalFor(nsIAutoSyncState *aAutoSyncStateObj)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (NS_FAILED(rv))
+ return kDefaultUpdateInterval;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv))
+ return kDefaultUpdateInterval;
+
+ if (server)
+ {
+ int32_t interval;
+ rv = server->GetBiffMinutes(&interval);
+
+ if (NS_SUCCEEDED(rv))
+ return (uint32_t)interval;
+ }
+
+ return kDefaultUpdateInterval;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetGroupSize(uint32_t *aGroupSize)
+{
+ NS_ENSURE_ARG_POINTER(aGroupSize);
+ *aGroupSize = mGroupSize;
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetGroupSize(uint32_t aGroupSize)
+{
+ mGroupSize = aGroupSize ? aGroupSize : kDefaultGroupSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetMsgStrategy(nsIAutoSyncMsgStrategy * *aMsgStrategy)
+{
+ NS_ENSURE_ARG_POINTER(aMsgStrategy);
+
+ // lazily create if it is not done already
+ if (!mMsgStrategyImpl)
+ {
+ mMsgStrategyImpl = new nsDefaultAutoSyncMsgStrategy;
+ if (!mMsgStrategyImpl)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_IF_ADDREF(*aMsgStrategy = mMsgStrategyImpl);
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetMsgStrategy(nsIAutoSyncMsgStrategy * aMsgStrategy)
+{
+ mMsgStrategyImpl = aMsgStrategy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetFolderStrategy(nsIAutoSyncFolderStrategy * *aFolderStrategy)
+{
+ NS_ENSURE_ARG_POINTER(aFolderStrategy);
+
+ // lazily create if it is not done already
+ if (!mFolderStrategyImpl)
+ {
+ mFolderStrategyImpl = new nsDefaultAutoSyncFolderStrategy;
+ if (!mFolderStrategyImpl)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_IF_ADDREF(*aFolderStrategy = mFolderStrategyImpl);
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetFolderStrategy(nsIAutoSyncFolderStrategy * aFolderStrategy)
+{
+ mFolderStrategyImpl = aFolderStrategy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::DoesMsgFitDownloadCriteria(nsIMsgDBHdr *aMsgHdr, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ uint32_t msgFlags = 0;
+ aMsgHdr->GetFlags(&msgFlags);
+
+ // check whether this message is marked imap deleted or not
+ *aResult = !(msgFlags & nsMsgMessageFlags::IMAPDeleted);
+ if (!(*aResult))
+ return NS_OK;
+
+ bool shouldStoreMsgOffline = true;
+ nsCOMPtr<nsIMsgFolder> folder;
+ aMsgHdr->GetFolder(getter_AddRefs(folder));
+ if (folder)
+ {
+ nsMsgKey msgKey;
+ nsresult rv = aMsgHdr->GetMessageKey(&msgKey);
+ // a cheap way to get the size limit for this folder and make
+ // sure that we don't have this message offline already
+ if (NS_SUCCEEDED(rv))
+ folder->ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
+ }
+
+ *aResult &= shouldStoreMsgOffline;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::OnDownloadQChanged(nsIAutoSyncState *aAutoSyncStateObj)
+{
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+ if (!autoSyncStateObj)
+ return NS_ERROR_INVALID_ARG;
+
+ if (mPaused)
+ return NS_OK;
+ // We want to start downloading immediately unless the folder is excluded.
+ bool excluded = false;
+ nsCOMPtr<nsIAutoSyncFolderStrategy> folStrategy;
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ GetFolderStrategy(getter_AddRefs(folStrategy));
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+
+ if (folder && folStrategy)
+ folStrategy->IsExcluded(folder, &excluded);
+
+ nsresult rv = NS_OK;
+
+ if (!excluded)
+ {
+ // Add this folder into the priority queue.
+ autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+ ScheduleFolderForOfflineDownload(autoSyncStateObj);
+
+ // If we operate in parallel mode or if there is no sibling downloading messages at the moment,
+ // we can download the first group of the messages for this folder
+ if (mDownloadModel == dmParallel ||
+ !DoesQContainAnySiblingOf(mPriorityQ, autoSyncStateObj, nsAutoSyncState::stDownloadInProgress))
+ {
+ // this will download the first group of messages immediately;
+ // to ensure that we don't end up downloading a large single message in not-idle time,
+ // we enforce a limit. If there is no message fits into this limit we postpone the
+ // download until the next idle.
+ if (GetIdleState() == notIdle)
+ rv = DownloadMessagesForOffline(autoSyncStateObj, kFirstGroupSizeLimit);
+ else
+ rv = DownloadMessagesForOffline(autoSyncStateObj);
+
+ if (NS_FAILED(rv))
+ autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::OnDownloadStarted(nsIAutoSyncState *aAutoSyncStateObj, nsresult aStartCode)
+{
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+ if (!autoSyncStateObj)
+ return NS_ERROR_INVALID_ARG;
+
+ // resume downloads during next idle time
+ if (NS_FAILED(aStartCode))
+ autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+
+ return aStartCode;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::OnDownloadCompleted(nsIAutoSyncState *aAutoSyncStateObj, nsresult aExitCode)
+{
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+ if (!autoSyncStateObj)
+ return NS_ERROR_INVALID_ARG;
+
+ nsresult rv = aExitCode;
+
+ if (NS_FAILED(aExitCode))
+ {
+ // retry the same group kGroupRetryCount times
+ // try again if TB still idle, otherwise wait for the next idle time
+ autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+ if (GetIdleState() != notIdle)
+ {
+ rv = DownloadMessagesForOffline(autoSyncStateObj);
+ if (NS_FAILED(rv))
+ rv = HandleDownloadErrorFor(autoSyncStateObj, rv);
+ }
+ return rv;
+ }
+
+ // download is successful, reset the retry counter of the folder
+ autoSyncStateObj->ResetRetryCounter();
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder)
+ NOTIFY_LISTENERS(OnDownloadCompleted, (folder));
+
+ int32_t count;
+ rv = autoSyncStateObj->GetPendingMessageCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIAutoSyncState *nextFolderToDownload = nullptr;
+ if (count > 0)
+ {
+ autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+
+ // in parallel model, we continue downloading the same folder as long as it has
+ // more pending messages
+ nextFolderToDownload = autoSyncStateObj;
+
+ // in chained model, ensure that we are always downloading the highest priority
+ // folder first
+ if (mDownloadModel == dmChained)
+ {
+ // switch to higher priority folder and continue to download,
+ // if any added recently
+ int32_t myIndex = mPriorityQ.IndexOf(autoSyncStateObj);
+
+ int32_t siblingIndex;
+ nsIAutoSyncState *sibling = GetHighestPrioSibling(mPriorityQ, autoSyncStateObj, &siblingIndex);
+
+ // lesser index = higher priority
+ if (sibling && myIndex > -1 && siblingIndex < myIndex)
+ nextFolderToDownload = sibling;
+ }
+ }
+ else
+ {
+ autoSyncStateObj->SetState(nsAutoSyncState::stCompletedIdle);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+
+ if (NS_SUCCEEDED(rv) && mPriorityQ.RemoveObject(autoSyncStateObj))
+ NOTIFY_LISTENERS(OnFolderRemovedFromQ, (nsIAutoSyncMgrListener::PriorityQueue, folder));
+
+ //find the next folder owned by the same server in the queue and continue downloading
+ if (mDownloadModel == dmChained)
+ nextFolderToDownload = GetHighestPrioSibling(mPriorityQ, autoSyncStateObj);
+
+ }//endif
+
+ // continue downloading if TB is still in idle state
+ if (nextFolderToDownload && GetIdleState() != notIdle)
+ {
+ rv = DownloadMessagesForOffline(nextFolderToDownload);
+ if (NS_FAILED(rv))
+ rv = HandleDownloadErrorFor(nextFolderToDownload, rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetDownloadModel(int32_t *aDownloadModel)
+{
+ NS_ENSURE_ARG_POINTER(aDownloadModel);
+ *aDownloadModel = mDownloadModel;
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetDownloadModel(int32_t aDownloadModel)
+{
+ mDownloadModel = aDownloadModel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::AddListener(nsIAutoSyncMgrListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ mListeners.AppendElementUnlessExists(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::RemoveListener(nsIAutoSyncMgrListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+/* readonly attribute unsigned long discoveryQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetDiscoveryQLength(uint32_t *aDiscoveryQLength)
+{
+ NS_ENSURE_ARG_POINTER(aDiscoveryQLength);
+ *aDiscoveryQLength = mDiscoveryQ.Count();
+ return NS_OK;
+}
+
+/* readonly attribute unsigned long uploadQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetUpdateQLength(uint32_t *aUpdateQLength)
+{
+ NS_ENSURE_ARG_POINTER(aUpdateQLength);
+ *aUpdateQLength = mUpdateQ.Count();
+ return NS_OK;
+}
+
+/* readonly attribute unsigned long downloadQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetDownloadQLength(uint32_t *aDownloadQLength)
+{
+ NS_ENSURE_ARG_POINTER(aDownloadQLength);
+ *aDownloadQLength = mPriorityQ.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::OnFolderHasPendingMsgs(nsIAutoSyncState *aAutoSyncStateObj)
+{
+ NS_ENSURE_ARG_POINTER(aAutoSyncStateObj);
+ if (mUpdateQ.IndexOf(aAutoSyncStateObj) == -1)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ // If this folder isn't the trash, add it to the update q.
+ if (folder)
+ {
+ bool isTrash;
+ folder->GetFlag(nsMsgFolderFlags::Trash, &isTrash);
+ if (!isTrash)
+ {
+ bool isSentOrArchive;
+ folder->IsSpecialFolder(nsMsgFolderFlags::SentMail|
+ nsMsgFolderFlags::Archive,
+ true, &isSentOrArchive);
+ // Sent or archive folders go to the q front, the rest to the end.
+ if (isSentOrArchive)
+ mUpdateQ.InsertObjectAt(aAutoSyncStateObj, 0);
+ else
+ mUpdateQ.AppendObject(aAutoSyncStateObj);
+ aAutoSyncStateObj->SetState(nsAutoSyncState::stUpdateNeeded);
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ }
+ }
+ }
+ return NS_OK;
+}
+
+void nsAutoSyncManager::SetIdleState(IdleState st)
+{
+ mIdleState = st;
+}
+
+nsAutoSyncManager::IdleState nsAutoSyncManager::GetIdleState() const
+{
+ return mIdleState;
+}
+
+NS_IMPL_ISUPPORTS(nsAutoSyncManager, nsIObserver, nsIUrlListener, nsIAutoSyncManager)
diff --git a/mailnews/imap/src/nsAutoSyncManager.h b/mailnews/imap/src/nsAutoSyncManager.h
new file mode 100644
index 000000000..2b98b7b82
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncManager.h
@@ -0,0 +1,265 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAutoSyncManager_h__
+#define nsAutoSyncManager_h__
+
+#include "nsAutoPtr.h"
+#include "nsStringGlue.h"
+#include "nsCOMArray.h"
+#include "nsIObserver.h"
+#include "nsIUrlListener.h"
+#include "nsITimer.h"
+#include "nsTObserverArray.h"
+#include "nsIAutoSyncManager.h"
+#include "nsIAutoSyncMsgStrategy.h"
+#include "nsIAutoSyncFolderStrategy.h"
+#include "prtime.h"
+
+class nsImapMailFolder;
+class nsIMsgDBHdr;
+class nsIIdleService;
+class nsIMsgFolder;
+
+/* Auto-Sync
+ *
+ * Background:
+ * it works only with offline imap folders. "autosync_offline_stores" pref
+ * enables/disables auto-sync mechanism. Note that setting "autosync_offline_stores"
+ * to false, or setting folder to not-offline doesn't stop synchronization
+ * process for already queued folders.
+ *
+ * Auto-Sync policy:
+ * o It kicks in during system idle time, and tries to download as much messages
+ * as possible based on given folder and message prioritization strategies/rules.
+ * Default folder prioritization strategy dictates to sort the folders based on the
+ * following order: INBOX > DRAFTS > SUBFOLDERS > TRASH.
+ * Similarly, default message prioritization strategy dictates to download the most
+ * recent and smallest message first. Also, by sorting the messages by size in the
+ * queue, it tries to maximize the number of messages downloaded.
+ * o It downloads the messages in groups. Default groups size is defined by |kDefaultGroupSize|.
+ * o It downloads the messages larger than the group size one-by-one.
+ * o If new messages arrive when not idle, it downloads the messages that do fit into
+ * |kFirstGroupSizeLimit| size limit immediately, without waiting for idle time, unless there is
+ * a sibling (a folder owned by the same imap server) in stDownloadInProgress state in the q
+ * o If new messages arrive when idle, it downloads all the messages without any restriction.
+ * o If new messages arrive into a folder while auto-sync is downloading other messages of the
+ * same folder, it simply puts the new messages into the folder's download queue, and
+ * re-prioritize the messages. That behavior makes sure that the high priority
+ * (defined by the message strategy) get downloaded first always.
+ * o If new messages arrive into a folder while auto-sync is downloading messages of a lower
+ * priority folder, auto-sync switches the folders in the queue and starts downloading the
+ * messages of the higher priority folder next time it downloads a message group.
+ * o Currently there is no way to stop/pause/cancel a message download. The smallest
+ * granularity is the message group size.
+ * o Auto-Sync manager periodically (kAutoSyncFreq) checks folder for existing messages
+ * w/o bodies. It persists the last time the folder is checked in the local database of the
+ * folder. We call this process 'Discovery'. This process is asynchronous and processes
+ * |kNumberOfHeadersToProcess| number of headers at each cycle. Since it works on local data,
+ * it doesn't consume lots of system resources, it does its job fast.
+ * o Discovery is necessary especially when the user makes a transition from not-offline
+ * to offline mode.
+ * o Update frequency is defined by nsMsgIncomingServer::BiffMinutes.
+ *
+ * Error Handling:
+ * o if the user moves/deletes/filters all messages of a folder already queued, auto-sync
+ * deals with that situation by skipping the folder in question, and continuing with the
+ * next in chain.
+ * o If the message size is zero, auto-sync ignores the message.
+ * o If the download of the message group fails for some reason, auto-sync tries to
+ * download the same group |kGroupRetryCount| times. If it still fails, continues with the
+ * next group of messages.
+ *
+ * Download Model:
+ * Parallel model should be used with the imap servers that do not have any "max number of sessions
+ * per IP" limit, and when the bandwidth is significantly large.
+ *
+ * How it really works:
+ * The AutoSyncManager gets an idle notification. First it processes any
+ * folders in the discovery queue (which means it schedules message download
+ * for any messages it previously determined it should download). Then it sets
+ * a timer, and in the timer callback, it processes the update q, by calling
+ * InitiateAutoSync on the first folder in the update q.
+ */
+
+/**
+ * Default strategy implementation to prioritize messages in the download queue.
+ */
+class nsDefaultAutoSyncMsgStrategy final : public nsIAutoSyncMsgStrategy
+{
+ static const uint32_t kFirstPassMessageSize = 60U*1024U; // 60K
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTOSYNCMSGSTRATEGY
+
+ nsDefaultAutoSyncMsgStrategy();
+
+ private:
+ ~nsDefaultAutoSyncMsgStrategy();
+};
+
+/**
+ * Default strategy implementation to prioritize folders in the download queue.
+ */
+class nsDefaultAutoSyncFolderStrategy final : public nsIAutoSyncFolderStrategy
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTOSYNCFOLDERSTRATEGY
+
+ nsDefaultAutoSyncFolderStrategy();
+
+ private:
+ ~nsDefaultAutoSyncFolderStrategy();
+};
+
+// see the end of the page for auto-sync internals
+
+/**
+ * Manages background message download operations for offline imap folders.
+ */
+class nsAutoSyncManager final : public nsIObserver,
+ public nsIUrlListener,
+ public nsIAutoSyncManager
+{
+ static const PRTime kAutoSyncFreq = 60UL * (PR_USEC_PER_SEC * 60UL); // 1hr
+ static const uint32_t kDefaultUpdateInterval = 10UL; // 10min
+ static const int32_t kTimerIntervalInMs = 400;
+ static const uint32_t kNumberOfHeadersToProcess = 250U;
+ // enforced size of the first group that will be downloaded before idle time
+ static const uint32_t kFirstGroupSizeLimit = 60U*1024U /* 60K */;
+ static const int32_t kIdleTimeInSec = 10;
+ static const uint32_t kGroupRetryCount = 3;
+
+ enum IdleState { systemIdle, appIdle, notIdle };
+ enum UpdateState { initiated, completed };
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIAUTOSYNCMANAGER
+
+ nsAutoSyncManager();
+
+ private:
+ ~nsAutoSyncManager();
+
+ void SetIdleState(IdleState st);
+ IdleState GetIdleState() const;
+ nsresult StartIdleProcessing();
+ nsresult AutoUpdateFolders();
+ void ScheduleFolderForOfflineDownload(nsIAutoSyncState *aAutoSyncStateObj);
+ nsresult DownloadMessagesForOffline(nsIAutoSyncState *aAutoSyncStateObj, uint32_t aSizeLimit = 0);
+ nsresult HandleDownloadErrorFor(nsIAutoSyncState *aAutoSyncStateObj, const nsresult error);
+
+ // Helper methods for priority Q operations
+ static
+ void ChainFoldersInQ(const nsCOMArray<nsIAutoSyncState> &aQueue,
+ nsCOMArray<nsIAutoSyncState> &aChainedQ);
+ static
+ nsIAutoSyncState* SearchQForSibling(const nsCOMArray<nsIAutoSyncState> &aQueue,
+ nsIAutoSyncState *aAutoSyncStateObj, int32_t aStartIdx, int32_t *aIndex = nullptr);
+ static
+ bool DoesQContainAnySiblingOf(const nsCOMArray<nsIAutoSyncState> &aQueue,
+ nsIAutoSyncState *aAutoSyncStateObj, const int32_t aState,
+ int32_t *aIndex = nullptr);
+ static
+ nsIAutoSyncState* GetNextSibling(const nsCOMArray<nsIAutoSyncState> &aQueue,
+ nsIAutoSyncState *aAutoSyncStateObj, int32_t *aIndex = nullptr);
+ static
+ nsIAutoSyncState* GetHighestPrioSibling(const nsCOMArray<nsIAutoSyncState> &aQueue,
+ nsIAutoSyncState *aAutoSyncStateObj, int32_t *aIndex = nullptr);
+
+ /// timer to process existing keys and updates
+ void InitTimer();
+ static void TimerCallback(nsITimer *aTimer, void *aClosure);
+ void StopTimer();
+ void StartTimerIfNeeded();
+
+ /// pref helpers
+ uint32_t GetUpdateIntervalFor(nsIAutoSyncState *aAutoSyncStateObj);
+
+ protected:
+ nsCOMPtr<nsIAutoSyncMsgStrategy> mMsgStrategyImpl;
+ nsCOMPtr<nsIAutoSyncFolderStrategy> mFolderStrategyImpl;
+ // contains the folders that will be downloaded on background
+ nsCOMArray<nsIAutoSyncState> mPriorityQ;
+ // contains the folders that will be examined for existing headers and
+ // adds the headers we don't have offline into the autosyncState
+ // object's download queue.
+ nsCOMArray<nsIAutoSyncState> mDiscoveryQ;
+ // contains the folders that will be checked for new messages with STATUS,
+ // and if there are any, we'll call UpdateFolder on them.
+ nsCOMArray<nsIAutoSyncState> mUpdateQ;
+ // this is the update state for the current folder.
+ UpdateState mUpdateState;
+
+ // This is set if auto sync has been completely paused.
+ bool mPaused;
+ // This is set if we've finished startup and should start
+ // paying attention to idle notifications.
+ bool mStartupDone;
+
+ private:
+ uint32_t mGroupSize;
+ IdleState mIdleState;
+ int32_t mDownloadModel;
+ nsCOMPtr<nsIIdleService> mIdleService;
+ nsCOMPtr<nsITimer> mTimer;
+ nsTObserverArray<nsCOMPtr<nsIAutoSyncMgrListener> > mListeners;
+};
+
+#endif
+
+/*
+How queues inter-relate:
+
+nsAutoSyncState has an internal priority queue to store messages waiting to be
+downloaded. nsAutoSyncMsgStrategy object determines the order in this queue,
+nsAutoSyncManager uses this queue to manage downloads. Two events cause a
+change in this queue:
+
+1) nsImapMailFolder::HeaderFetchCompleted: is triggered when TB notices that
+there are pending messages on the server -- via IDLE command from the server,
+via explicit select from the user, or via automatic Update during idle time. If
+it turns out that there are pending messages on the server, it adds them into
+nsAutoSyncState's download queue.
+
+2) nsAutoSyncState::ProcessExistingHeaders: is triggered for every imap folder
+every hour or so (see kAutoSyncFreq). nsAutoSyncManager uses an internal queue called Discovery
+queue to keep track of this task. The purpose of ProcessExistingHeaders()
+method is to check existing headers of a given folder in batches and discover
+the messages without bodies, in asynchronous fashion. This process is
+sequential, one and only one folder at any given time, very similar to
+indexing. Again, if it turns out that the folder in hand has messages w/o
+bodies, ProcessExistingHeaders adds them into nsAutoSyncState's download queue.
+
+Any change in nsAutoSyncState's download queue, notifies nsAutoSyncManager and
+nsAutoSyncManager puts the requesting nsAutoSyncState into its internal
+priority queue (called mPriorityQ) -- if the folder is not already there.
+nsAutoSyncFolderStrategy object determines the order in this queue. This queue
+is processed in two modes: chained and parallel.
+
+i) Chained: One folder per imap server any given time. Folders owned by
+different imap servers are simultaneous.
+
+ii) Parallel: All folders at the same time, using all cached-connections -
+a.k.a 'Folders gone wild' mode.
+
+The order the folders are added into the mPriorityQ doesn't matter since every
+time a batch completed for an imap server, nsAutoSyncManager adjusts the order.
+So, lets say that updating a sub-folder starts downloading message immediately,
+when an higher priority folder is added into the queue, nsAutoSyncManager
+switches to this higher priority folder instead of processing the next group of
+messages of the lower priority one. Setting group size too high might delay
+this switch at worst.
+
+And finally, Update queue helps nsAutoSyncManager to keep track of folders
+waiting to be updated. With the latest change, we update one and only one
+folder at any given time. Default frequency of updating is 10 min (kDefaultUpdateInterval).
+We add folders into the update queue during idle time, if they are not in mPriorityQ already.
+
+*/
diff --git a/mailnews/imap/src/nsAutoSyncState.cpp b/mailnews/imap/src/nsAutoSyncState.cpp
new file mode 100644
index 000000000..7b1aeabbe
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncState.cpp
@@ -0,0 +1,765 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAutoSyncState.h"
+#include "nsImapMailFolder.h"
+#include "nsMsgImapCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMsgKeyArray.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIAutoSyncManager.h"
+#include "nsIAutoSyncMsgStrategy.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+extern PRLogModuleInfo *gAutoSyncLog;
+
+MsgStrategyComparatorAdaptor::MsgStrategyComparatorAdaptor(nsIAutoSyncMsgStrategy* aStrategy,
+ nsIMsgFolder *aFolder, nsIMsgDatabase *aDatabase) : mStrategy(aStrategy), mFolder(aFolder),
+ mDatabase(aDatabase)
+{
+}
+
+/** @return True if the elements are equals; false otherwise. */
+bool MsgStrategyComparatorAdaptor::Equals(const nsMsgKey& a, const nsMsgKey& b) const
+{
+ nsCOMPtr<nsIMsgDBHdr> hdrA;
+ nsCOMPtr<nsIMsgDBHdr> hdrB;
+
+ mDatabase->GetMsgHdrForKey(a, getter_AddRefs(hdrA));
+ mDatabase->GetMsgHdrForKey(b, getter_AddRefs(hdrB));
+
+ if (hdrA && hdrB)
+ {
+ nsresult rv = NS_OK;
+ nsAutoSyncStrategyDecisionType decision = nsAutoSyncStrategyDecisions::Same;
+
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(mFolder);
+ if (mStrategy)
+ rv = mStrategy->Sort(folder, hdrA, hdrB, &decision);
+
+ if (NS_SUCCEEDED(rv))
+ return (decision == nsAutoSyncStrategyDecisions::Same);
+ }
+
+ return false;
+}
+
+/** @return True if (a < b); false otherwise. */
+bool MsgStrategyComparatorAdaptor::LessThan(const nsMsgKey& a, const nsMsgKey& b) const
+{
+ nsCOMPtr<nsIMsgDBHdr> hdrA;
+ nsCOMPtr<nsIMsgDBHdr> hdrB;
+
+ mDatabase->GetMsgHdrForKey(a, getter_AddRefs(hdrA));
+ mDatabase->GetMsgHdrForKey(b, getter_AddRefs(hdrB));
+
+ if (hdrA && hdrB)
+ {
+ nsresult rv = NS_OK;
+ nsAutoSyncStrategyDecisionType decision = nsAutoSyncStrategyDecisions::Same;
+
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(mFolder);
+ if (mStrategy)
+ rv = mStrategy->Sort(folder, hdrA, hdrB, &decision);
+
+ if (NS_SUCCEEDED(rv))
+ return (decision == nsAutoSyncStrategyDecisions::Lower);
+ }
+
+ return false;
+}
+
+nsAutoSyncState::nsAutoSyncState(nsImapMailFolder *aOwnerFolder, PRTime aLastSyncTime)
+ : mSyncState(stCompletedIdle), mOffset(0U), mLastOffset(0U), mLastServerTotal(0),
+ mLastServerRecent(0), mLastServerUnseen(0), mLastNextUID(0),
+ mLastSyncTime(aLastSyncTime), mLastUpdateTime(0UL), mProcessPointer(0U),
+ mIsDownloadQChanged(false), mRetryCounter(0U)
+{
+ mOwnerFolder = do_GetWeakReference(static_cast<nsIMsgImapMailFolder*>(aOwnerFolder));
+}
+
+nsAutoSyncState::~nsAutoSyncState()
+{
+}
+
+// TODO:XXXemre should be implemented when we start
+// doing space management
+nsresult nsAutoSyncState::ManageStorageSpace()
+{
+ return NS_OK;
+}
+
+nsresult nsAutoSyncState::PlaceIntoDownloadQ(const nsTArray<nsMsgKey> &aMsgKeyList)
+{
+ nsresult rv = NS_OK;
+ if (!aMsgKeyList.IsEmpty())
+ {
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ rv = folder->GetMsgDatabase(getter_AddRefs(database));
+ if (!database)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+ autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+
+ // increase the array size
+ mDownloadQ.SetCapacity(mDownloadQ.Length() + aMsgKeyList.Length());
+
+ // remove excluded messages
+ int32_t elemCount = aMsgKeyList.Length();
+ for (int32_t idx = 0; idx < elemCount; idx++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ bool containsKey;
+ database->ContainsKey(aMsgKeyList[idx], &containsKey);
+ if (!containsKey)
+ continue;
+ rv = database->GetMsgHdrForKey(aMsgKeyList[idx], getter_AddRefs(hdr));
+ if(!hdr)
+ continue; // can't get message header, continue with the next one
+
+ bool doesFit = true;
+ rv = autoSyncMgr->DoesMsgFitDownloadCriteria(hdr, &doesFit);
+ if (NS_SUCCEEDED(rv) && !mDownloadSet.Contains(aMsgKeyList[idx]) && doesFit)
+ {
+ bool excluded = false;
+ if (msgStrategy)
+ {
+ rv = msgStrategy->IsExcluded(folder, hdr, &excluded);
+
+ if (NS_SUCCEEDED(rv) && !excluded)
+ {
+ mIsDownloadQChanged = true;
+ mDownloadSet.PutEntry(aMsgKeyList[idx]);
+ mDownloadQ.AppendElement(aMsgKeyList[idx]);
+ }
+ }
+ }
+ }//endfor
+
+ if (mIsDownloadQChanged)
+ {
+ LogOwnerFolderName("Download Q is created for ");
+ LogQWithSize(mDownloadQ, 0);
+ rv = autoSyncMgr->OnDownloadQChanged(this);
+ }
+
+ }
+ return rv;
+}
+
+nsresult nsAutoSyncState::SortQueueBasedOnStrategy(nsTArray<nsMsgKey> &aQueue)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ rv = folder->GetMsgDatabase(getter_AddRefs(database));
+ if (!database)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+ rv = autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MsgStrategyComparatorAdaptor strategyComp(msgStrategy, folder, database);
+ aQueue.Sort(strategyComp);
+
+ return rv;
+}
+
+// This method is a hack to prioritize newly inserted messages,
+// without changing the size of the queue. It is required since
+// we cannot sort ranges in nsTArray.
+nsresult nsAutoSyncState::SortSubQueueBasedOnStrategy(nsTArray<nsMsgKey> &aQueue,
+ uint32_t aStartingOffset)
+{
+ NS_ASSERTION(aStartingOffset < aQueue.Length(), "*** Starting offset is out of range");
+
+ // Copy already downloaded messages into a temporary queue,
+ // we want to exclude them from the sort.
+ nsTArray<nsMsgKey> tmpQ;
+ tmpQ.AppendElements(aQueue.Elements(), aStartingOffset);
+
+ // Remove already downloaded messages and sort the resulting queue
+ aQueue.RemoveElementsAt(0, aStartingOffset);
+
+ nsresult rv = SortQueueBasedOnStrategy(aQueue);
+
+ // copy excluded messages back
+ aQueue.InsertElementsAt(0, tmpQ);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetNextGroupOfMessages(uint32_t aSuggestedGroupSizeLimit,
+ uint32_t *aActualGroupSize,
+ nsIMutableArray **aMessagesList)
+{
+ NS_ENSURE_ARG_POINTER(aMessagesList);
+ NS_ENSURE_ARG_POINTER(aActualGroupSize);
+
+ *aActualGroupSize = 0;
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ folder->GetMsgDatabase(getter_AddRefs(database));
+
+ nsCOMPtr<nsIMutableArray> group = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ if (database)
+ {
+ if (!mDownloadQ.IsEmpty())
+ {
+ // sort the download queue if new items are added since the last time
+ if (mIsDownloadQChanged)
+ {
+ // we want to sort only pending messages. mOffset is
+ // the position of the first pending message in the download queue
+ rv = (mOffset > 0)
+ ? SortSubQueueBasedOnStrategy(mDownloadQ, mOffset)
+ : SortQueueBasedOnStrategy(mDownloadQ);
+
+ if (NS_SUCCEEDED(rv))
+ mIsDownloadQChanged = false;
+ }
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t msgCount = mDownloadQ.Length();
+ uint32_t idx = mOffset;
+
+ nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+ autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+
+ for (; idx < msgCount; idx++)
+ {
+ bool containsKey = false;
+ database->ContainsKey(mDownloadQ[idx], &containsKey);
+ if (!containsKey)
+ {
+ mDownloadSet.RemoveEntry(mDownloadQ[idx]);
+ mDownloadQ.RemoveElementAt(idx--);
+ msgCount--;
+ continue;
+ }
+ nsCOMPtr<nsIMsgDBHdr> qhdr;
+ database->GetMsgHdrForKey(mDownloadQ[idx], getter_AddRefs(qhdr));
+ if(!qhdr)
+ continue; //maybe deleted, skip it!
+
+ // ensure that we don't have this message body offline already,
+ // possible if the user explicitly selects this message prior
+ // to auto-sync kicks in
+ bool hasMessageOffline;
+ folder->HasMsgOffline(mDownloadQ[idx], &hasMessageOffline);
+ if (hasMessageOffline)
+ continue;
+
+ // this check point allows msg strategy function
+ // to do last minute decisions based on the current
+ // state of TB such as the size of the message store etc.
+ if (msgStrategy)
+ {
+ bool excluded = false;
+ if (NS_SUCCEEDED(msgStrategy->IsExcluded(folder, qhdr, &excluded)) && excluded)
+ continue;
+ }
+
+ uint32_t msgSize;
+ qhdr->GetMessageSize(&msgSize);
+ // ignore 0 byte messages; the imap parser asserts when we try
+ // to download them, and there's no point anyway.
+ if (!msgSize)
+ continue;
+
+ if (!*aActualGroupSize && msgSize >= aSuggestedGroupSizeLimit)
+ {
+ *aActualGroupSize = msgSize;
+ group->AppendElement(qhdr, false);
+ idx++;
+ break;
+ }
+ else if ((*aActualGroupSize) + msgSize > aSuggestedGroupSizeLimit)
+ break;
+ else
+ {
+ group->AppendElement(qhdr, false);
+ *aActualGroupSize += msgSize;
+ }
+ }// endfor
+
+ mLastOffset = mOffset;
+ mOffset = idx;
+ }
+
+ LogOwnerFolderName("Next group of messages to be downloaded.");
+ LogQWithSize(group.get(), 0);
+ } //endif
+
+ // return it to the caller
+ NS_IF_ADDREF(*aMessagesList = group);
+
+ return NS_OK;
+}
+
+/**
+ * Usually called by nsAutoSyncManager when the last sync time is expired.
+ */
+NS_IMETHODIMP nsAutoSyncState::ProcessExistingHeaders(uint32_t aNumOfHdrsToProcess, uint32_t *aLeftToProcess)
+{
+ NS_ENSURE_ARG_POINTER(aLeftToProcess);
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ rv = folder->GetMsgDatabase(getter_AddRefs(database));
+ if (!database)
+ return NS_ERROR_FAILURE;
+
+ // create a queue to process existing headers for the first time
+ if (mExistingHeadersQ.IsEmpty())
+ {
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ rv = database->ListAllKeys(keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ keys->Sort();
+ mExistingHeadersQ.AppendElements(keys->m_keys);
+ mProcessPointer = 0;
+ }
+
+ // process the existing headers and find the messages not downloaded yet
+ uint32_t lastIdx = mProcessPointer;
+ nsTArray<nsMsgKey> msgKeys;
+ uint32_t keyCount = mExistingHeadersQ.Length();
+ for (; mProcessPointer < (lastIdx + aNumOfHdrsToProcess) && mProcessPointer < keyCount; mProcessPointer++)
+ {
+ bool hasMessageOffline;
+ folder->HasMsgOffline(mExistingHeadersQ[mProcessPointer], &hasMessageOffline);
+ if (!hasMessageOffline)
+ msgKeys.AppendElement(mExistingHeadersQ[mProcessPointer]);
+ }
+ if (!msgKeys.IsEmpty())
+ {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%d messages will be added into the download q of folder %s\n",
+ msgKeys.Length(), folderName.get()));
+
+ rv = PlaceIntoDownloadQ(msgKeys);
+ if (NS_FAILED(rv))
+ mProcessPointer = lastIdx;
+ }
+
+ *aLeftToProcess = keyCount - mProcessPointer;
+
+ // cleanup if we are done processing
+ if (0 == *aLeftToProcess)
+ {
+ mLastSyncTime = PR_Now();
+ mExistingHeadersQ.Clear();
+ mProcessPointer = 0;
+ folder->SetMsgDatabase(nullptr);
+ }
+
+ return rv;
+}
+
+void nsAutoSyncState::OnNewHeaderFetchCompleted(const nsTArray<nsMsgKey> &aMsgKeyList)
+{
+ SetLastUpdateTime(PR_Now());
+ if (!aMsgKeyList.IsEmpty())
+ PlaceIntoDownloadQ(aMsgKeyList);
+}
+
+NS_IMETHODIMP nsAutoSyncState::UpdateFolder()
+{
+ nsresult rv;
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIUrlListener> autoSyncMgrListener = do_QueryInterface(autoSyncMgr, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryReferent(mOwnerFolder, &rv);
+ SetState(nsAutoSyncState::stUpdateIssued);
+ return imapFolder->UpdateFolderWithListener(nullptr, autoSyncMgrListener);
+}
+
+NS_IMETHODIMP nsAutoSyncState::OnStartRunningUrl(nsIURI* aUrl)
+{
+ nsresult rv = NS_OK;
+
+ // if there is a problem to start the download, set rv with the
+ // corresponding error code. In that case, AutoSyncManager is going to
+ // set the autosync state to nsAutoSyncState::stReadyToDownload
+ // to resume downloading another time
+
+ // TODO: is there a way to make sure that download started without
+ // problem through nsIURI interface?
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return autoSyncMgr->OnDownloadStarted(this, rv);
+}
+
+NS_IMETHODIMP nsAutoSyncState::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIUrlListener> autoSyncMgrListener = do_QueryInterface(autoSyncMgr, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mSyncState == stStatusIssued)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t serverTotal, serverUnseen, serverRecent, serverNextUID;
+ imapFolder->GetServerTotal(&serverTotal);
+ imapFolder->GetServerUnseen(&serverUnseen);
+ imapFolder->GetServerRecent(&serverRecent);
+ imapFolder->GetServerNextUID(&serverNextUID);
+ if (serverNextUID != mLastNextUID || serverTotal != mLastServerTotal ||
+ serverUnseen != mLastServerUnseen || serverRecent != mLastServerRecent)
+ {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("folder %s status changed serverNextUID = %lx lastNextUID = %lx\n", folderName.get(),
+ serverNextUID, mLastNextUID));
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("serverTotal = %lx lastServerTotal = %lx serverRecent = %lx lastServerRecent = %lx\n",
+ serverTotal, mLastServerTotal, serverRecent, mLastServerRecent));
+ SetServerCounts(serverTotal, serverRecent, serverUnseen, serverNextUID);
+ SetState(nsAutoSyncState::stUpdateIssued);
+ return imapFolder->UpdateFolderWithListener(nullptr, autoSyncMgrListener);
+ }
+ else
+ {
+ ownerFolder->SetMsgDatabase(nullptr);
+ // nothing more to do.
+ SetState(nsAutoSyncState::stCompletedIdle);
+ // autoSyncMgr needs this notification, so manufacture it.
+ return autoSyncMgrListener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ }
+ //XXXemre how we recover from this error?
+ rv = ownerFolder->ReleaseSemaphore(ownerFolder);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "*** Cannot release folder semaphore");
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ if (mailUrl)
+ rv = mailUrl->UnRegisterListener(this);
+
+ return autoSyncMgr->OnDownloadCompleted(this, aExitCode);
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetState(int32_t *aState)
+{
+ NS_ENSURE_ARG_POINTER(aState);
+ *aState = mSyncState;
+ return NS_OK;
+}
+
+const char *stateStrings[] = {"idle", "status issued", "update needed",
+ "update issued", "downloading",
+ "ready to download"};
+
+NS_IMETHODIMP nsAutoSyncState::SetState(int32_t aState)
+{
+ mSyncState = aState;
+ if (aState == stCompletedIdle)
+ {
+ ResetDownloadQ();
+ //tell folder to let go of its cached msg db pointer
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && session)
+ {
+ nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool folderOpen;
+ uint32_t folderFlags;
+ ownerFolder->GetFlags(&folderFlags);
+ session->IsFolderOpenInWindow(ownerFolder, &folderOpen);
+ if (!folderOpen && ! (folderFlags & nsMsgFolderFlags::Inbox))
+ ownerFolder->SetMsgDatabase(nullptr);
+ }
+ }
+ nsCString logStr("Sync State set to ");
+ logStr.Append(stateStrings[aState]);
+ logStr.Append(" for ");
+ LogOwnerFolderName(logStr.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::TryCurrentGroupAgain(uint32_t aRetryCount)
+{
+ SetState(stReadyToDownload);
+
+ nsresult rv;
+ if (++mRetryCounter > aRetryCount)
+ {
+ ResetRetryCounter();
+ rv = NS_ERROR_FAILURE;
+ }
+ else
+ rv = Rollback();
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::ResetRetryCounter()
+{
+ mRetryCounter = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetPendingMessageCount(int32_t *aMsgCount)
+{
+ NS_ENSURE_ARG_POINTER(aMsgCount);
+ *aMsgCount = mDownloadQ.Length() - mOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetTotalMessageCount(int32_t *aMsgCount)
+{
+ NS_ENSURE_ARG_POINTER(aMsgCount);
+ *aMsgCount = mDownloadQ.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetOwnerFolder(nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aFolder = ownerFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::Rollback()
+{
+ mOffset = mLastOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::ResetDownloadQ()
+{
+ mOffset = mLastOffset = 0;
+ mDownloadSet.Clear();
+ mDownloadQ.Clear();
+ mDownloadQ.Compact();
+
+ return NS_OK;
+}
+
+/**
+ * Tests whether the given folder is owned by the same imap server
+ * or not.
+ */
+NS_IMETHODIMP nsAutoSyncState::IsSibling(nsIAutoSyncState *aAnotherStateObj, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folderA, folderB;
+
+ rv = GetOwnerFolder(getter_AddRefs(folderA));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = aAnotherStateObj->GetOwnerFolder(getter_AddRefs(folderB));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgIncomingServer> serverA, serverB;
+ rv = folderA->GetServer(getter_AddRefs(serverA));
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = folderB->GetServer(getter_AddRefs(serverB));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isSibling;
+ rv = serverA->Equals(serverB, &isSibling);
+
+ if (NS_SUCCEEDED(rv))
+ *aResult = isSibling;
+
+ return rv;
+}
+
+
+NS_IMETHODIMP nsAutoSyncState::DownloadMessagesForOffline(nsIArray *aMessagesList)
+{
+ NS_ENSURE_ARG_POINTER(aMessagesList);
+
+ uint32_t count;
+ nsresult rv = aMessagesList->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> msgKeys;
+
+ rv = nsImapMailFolder::BuildIdsAndKeyArray(aMessagesList, messageIds, msgKeys);
+ if (NS_FAILED(rv) || messageIds.IsEmpty())
+ return rv;
+
+ // acquire semaphore for offline store. If it fails, we won't download
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = folder->AcquireSemaphore(folder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("downloading %s for %s", messageIds.get(),
+ folderName.get()));
+ // start downloading
+ rv = imapService->DownloadMessagesForOffline(messageIds,
+ folder,
+ this,
+ nullptr);
+ if (NS_SUCCEEDED(rv))
+ SetState(stDownloadInProgress);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetLastSyncTime(PRTime *aLastSyncTime)
+{
+ NS_ENSURE_ARG_POINTER(aLastSyncTime);
+ *aLastSyncTime = mLastSyncTime;
+ return NS_OK;
+}
+
+void nsAutoSyncState::SetLastSyncTimeInSec(int32_t aLastSyncTime)
+{
+ mLastSyncTime = ((PRTime)aLastSyncTime * PR_USEC_PER_SEC);
+}
+
+
+NS_IMETHODIMP nsAutoSyncState::GetLastUpdateTime(PRTime *aLastUpdateTime)
+{
+ NS_ENSURE_ARG_POINTER(aLastUpdateTime);
+ *aLastUpdateTime = mLastUpdateTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::SetLastUpdateTime(PRTime aLastUpdateTime)
+{
+ mLastUpdateTime = aLastUpdateTime;
+ return NS_OK;
+}
+
+void nsAutoSyncState::SetServerCounts(int32_t total, int32_t recent,
+ int32_t unseen, int32_t nextUID)
+{
+ mLastServerTotal = total;
+ mLastServerRecent = recent;
+ mLastServerUnseen = unseen;
+ mLastNextUID = nextUID;
+}
+
+NS_IMPL_ISUPPORTS(nsAutoSyncState, nsIAutoSyncState, nsIUrlListener)
+
+
+void nsAutoSyncState::LogQWithSize(nsTArray<nsMsgKey>& q, uint32_t toOffset)
+{
+ nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+ if (ownerFolder)
+ {
+ nsCOMPtr<nsIMsgDatabase> database;
+ ownerFolder->GetMsgDatabase(getter_AddRefs(database));
+
+ uint32_t x = q.Length();
+ while (x > toOffset && database)
+ {
+ x--;
+ nsCOMPtr<nsIMsgDBHdr> h;
+ database->GetMsgHdrForKey(q[x], getter_AddRefs(h));
+ uint32_t s;
+ if (h)
+ {
+ h->GetMessageSize(&s);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("Elem #%d, size: %u bytes\n", x+1, s));
+ }
+ else
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("unable to get header for key %ul", q[x]));
+ }
+ }
+}
+
+void nsAutoSyncState::LogQWithSize(nsIMutableArray *q, uint32_t toOffset)
+{
+ nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+ if (ownerFolder)
+ {
+ nsCOMPtr<nsIMsgDatabase> database;
+ ownerFolder->GetMsgDatabase(getter_AddRefs(database));
+
+ uint32_t x;
+ q->GetLength(&x);
+ while (x > toOffset && database)
+ {
+ x--;
+ nsCOMPtr<nsIMsgDBHdr> h;
+ q->QueryElementAt(x, NS_GET_IID(nsIMsgDBHdr),
+ getter_AddRefs(h));
+ uint32_t s;
+ if (h)
+ {
+ h->GetMessageSize(&s);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("Elem #%d, size: %u bytes\n", x+1, s));
+ }
+ else
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("null header in q at index %ul", x));
+ }
+ }
+}
+
+void nsAutoSyncState::LogOwnerFolderName(const char *s)
+{
+ nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+ if (ownerFolder)
+ {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("*** %s Folder: %s ***\n", s, folderName.get()));
+ }
+}
diff --git a/mailnews/imap/src/nsAutoSyncState.h b/mailnews/imap/src/nsAutoSyncState.h
new file mode 100644
index 000000000..0b10c8c94
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncState.h
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAutoSyncState_h__
+#define nsAutoSyncState_h__
+
+#include "MailNewsTypes.h"
+#include "nsIAutoSyncState.h"
+#include "nsIAutoSyncManager.h"
+#include "nsIUrlListener.h"
+#include "nsWeakPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "prlog.h"
+#include "nsIWeakReferenceUtils.h"
+
+class nsImapMailFolder;
+class nsIAutoSyncMsgStrategy;
+class nsIMsgDatabase;
+
+/**
+ * An adaptor class to make msg strategy nsTArray.Sort()
+ * compatible.
+ */
+class MsgStrategyComparatorAdaptor
+{
+ public:
+ MsgStrategyComparatorAdaptor(nsIAutoSyncMsgStrategy* aStrategy,
+ nsIMsgFolder *aFolder, nsIMsgDatabase *aDatabase);
+
+ /** @return True if the elements are equals; false otherwise. */
+ bool Equals(const nsMsgKey& a, const nsMsgKey& b) const;
+
+ /** @return True if (a < b); false otherwise. */
+ bool LessThan(const nsMsgKey& a, const nsMsgKey& b) const;
+
+ private:
+ MsgStrategyComparatorAdaptor();
+
+ private:
+ nsIAutoSyncMsgStrategy *mStrategy;
+ nsIMsgFolder *mFolder;
+ nsIMsgDatabase *mDatabase;
+};
+
+
+/**
+ * Facilitates auto-sync capabilities for imap folders.
+ */
+class nsAutoSyncState final : public nsIAutoSyncState, public nsIUrlListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTOSYNCSTATE
+ NS_DECL_NSIURLLISTENER
+
+ explicit nsAutoSyncState(nsImapMailFolder *aOwnerFolder, PRTime aLastSyncTime = 0UL);
+
+ /// Called by owner folder when new headers are fetched from the server
+ void OnNewHeaderFetchCompleted(const nsTArray<nsMsgKey> &aMsgKeyList);
+
+ /// Sets the last sync time in lower precision (seconds)
+ void SetLastSyncTimeInSec(int32_t aLastSyncTime);
+
+ /// Manages storage space for auto-sync operations
+ nsresult ManageStorageSpace();
+
+ void SetServerCounts(int32_t total, int32_t recent, int32_t unseen,
+ int32_t nextUID);
+
+ private:
+ ~nsAutoSyncState();
+
+ nsresult PlaceIntoDownloadQ(const nsTArray<nsMsgKey> &aMsgKeyList);
+ nsresult SortQueueBasedOnStrategy(nsTArray<nsMsgKey> &aQueue);
+ nsresult SortSubQueueBasedOnStrategy(nsTArray<nsMsgKey> &aQueue,
+ uint32_t aStartingOffset);
+
+ void LogOwnerFolderName(const char *s);
+ void LogQWithSize(nsTArray<nsMsgKey>& q, uint32_t toOffset = 0);
+ void LogQWithSize(nsIMutableArray *q, uint32_t toOffset = 0);
+
+ private:
+ int32_t mSyncState;
+ nsWeakPtr mOwnerFolder;
+ uint32_t mOffset;
+ uint32_t mLastOffset;
+
+ // used to tell if the Server counts have changed.
+ int32_t mLastServerTotal;
+ int32_t mLastServerRecent;
+ int32_t mLastServerUnseen;
+ int32_t mLastNextUID;
+
+ PRTime mLastSyncTime;
+ PRTime mLastUpdateTime;
+ uint32_t mProcessPointer;
+ bool mIsDownloadQChanged;
+ uint32_t mRetryCounter;
+ nsTHashtable<nsUint32HashKey> mDownloadSet;
+ nsTArray<nsMsgKey> mDownloadQ;
+ nsTArray<nsMsgKey> mExistingHeadersQ;
+};
+
+#endif
diff --git a/mailnews/imap/src/nsIMAPBodyShell.cpp b/mailnews/imap/src/nsIMAPBodyShell.cpp
new file mode 100644
index 000000000..087fe2033
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPBodyShell.cpp
@@ -0,0 +1,1333 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsIMAPHostSessionList.h"
+#include "nsIMAPBodyShell.h"
+#include "nsImapProtocol.h"
+#include "nsImapStringBundle.h"
+
+#include "nsMimeTypes.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsITransport.h"
+#include "nsServiceManagerUtils.h"
+
+// need to talk to Rich about this...
+#define IMAP_EXTERNAL_CONTENT_HEADER "X-Mozilla-IMAP-Part"
+
+// imapbody.cpp
+// Implementation of the nsIMAPBodyShell and associated classes
+// These are used to parse IMAP BODYSTRUCTURE responses, and intelligently (?)
+// figure out what parts we need to display inline.
+
+/*
+ Create a nsIMAPBodyShell from a full BODYSTRUCUTRE response from the parser.
+
+ The body shell represents a single, top-level object, the message. The message body
+ might be treated as either a container or a leaf (just like any arbitrary part).
+
+ Steps for creating a part:
+ 1. Pull out the paren grouping for the part
+ 2. Create a generic part object with that buffer
+ 3. The factory will return either a leaf or container, depending on what it really is.
+ 4. It is responsible for parsing its children, if there are any
+*/
+
+
+///////////// nsIMAPBodyShell ////////////////////////////////////
+
+NS_IMPL_ISUPPORTS0(nsIMAPBodyShell)
+
+nsIMAPBodyShell::nsIMAPBodyShell(nsImapProtocol *protocolConnection,
+ nsIMAPBodypartMessage *message, uint32_t UID,
+ const char *folderName)
+{
+ m_isValid = false;
+ m_isBeingGenerated = false;
+ m_cached = false;
+ m_gotAttachmentPref = false;
+ m_generatingWholeMessage = false;
+ m_generatingPart = NULL;
+ m_protocolConnection = protocolConnection;
+ m_message = message;
+ NS_ASSERTION(m_protocolConnection, "non null connection");
+ if (!m_protocolConnection)
+ return;
+ m_prefetchQueue = new nsIMAPMessagePartIDArray();
+ if (!m_prefetchQueue)
+ return;
+ m_UID = "";
+ m_UID.AppendInt(UID);
+#ifdef DEBUG_chrisf
+ NS_ASSERTION(folderName);
+#endif
+ if (!folderName)
+ return;
+ m_folderName = NS_strdup(folderName);
+ if (!m_folderName)
+ return;
+
+ SetContentModified(GetShowAttachmentsInline() ? IMAP_CONTENT_MODIFIED_VIEW_INLINE : IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS);
+
+ SetIsValid(m_message != nullptr);
+}
+
+nsIMAPBodyShell::~nsIMAPBodyShell()
+{
+ delete m_message;
+ delete m_prefetchQueue;
+ PR_Free(m_folderName);
+}
+
+void nsIMAPBodyShell::SetIsValid(bool valid)
+{
+ m_isValid = valid;
+}
+
+bool nsIMAPBodyShell::GetShowAttachmentsInline()
+{
+ if (!m_gotAttachmentPref)
+ {
+ m_showAttachmentsInline = !m_protocolConnection || m_protocolConnection->GetShowAttachmentsInline();
+ m_gotAttachmentPref = true;
+ }
+
+ return m_showAttachmentsInline;
+}
+
+// Fills in buffer (and adopts storage) for header object
+void nsIMAPBodyShell::AdoptMessageHeaders(char *headers, const char *partNum)
+{
+ if (!GetIsValid())
+ return;
+
+ if (!partNum)
+ partNum = "0";
+
+ // we are going to say that a message header object only has
+ // part data, and no header data.
+ nsIMAPBodypart *foundPart = m_message->FindPartWithNumber(partNum);
+ if (foundPart)
+ {
+ nsIMAPBodypartMessage *messageObj = foundPart->GetnsIMAPBodypartMessage();
+ if (messageObj)
+ {
+ messageObj->AdoptMessageHeaders(headers);
+ if (!messageObj->GetIsValid())
+ SetIsValid(false);
+ }
+ else
+ {
+ // We were filling in message headers for a given part number.
+ // We looked up that part number, found an object, but it
+ // wasn't of type message/rfc822.
+ // Something's wrong.
+ NS_ASSERTION(false, "object not of type message rfc822");
+ }
+ }
+ else
+ SetIsValid(false);
+}
+
+// Fills in buffer (and adopts storage) for MIME headers in appropriate object.
+// If object can't be found, sets isValid to false.
+void nsIMAPBodyShell::AdoptMimeHeader(const char *partNum, char *mimeHeader)
+{
+ if (!GetIsValid())
+ return;
+
+ NS_ASSERTION(partNum, "null partnum in body shell");
+
+ nsIMAPBodypart *foundPart = m_message->FindPartWithNumber(partNum);
+
+ if (foundPart)
+ {
+ foundPart->AdoptHeaderDataBuffer(mimeHeader);
+ if (!foundPart->GetIsValid())
+ SetIsValid(false);
+ }
+ else
+ {
+ SetIsValid(false);
+ }
+}
+
+
+void nsIMAPBodyShell::AddPrefetchToQueue(nsIMAPeFetchFields fields, const char *partNumber)
+{
+ nsIMAPMessagePartID *newPart = new nsIMAPMessagePartID(fields, partNumber);
+ if (newPart)
+ {
+ m_prefetchQueue->AppendElement(newPart);
+ }
+ else
+ {
+ // HandleMemoryFailure();
+ }
+}
+
+// Flushes all of the prefetches that have been queued up in the prefetch queue,
+// freeing them as we go
+void nsIMAPBodyShell::FlushPrefetchQueue()
+{
+ m_protocolConnection->PipelinedFetchMessageParts(GetUID(), m_prefetchQueue);
+ m_prefetchQueue->RemoveAndFreeAll();
+}
+
+// Requires that the shell is valid when called
+// Performs a preflight check on all message parts to see if they are all
+// inline. Returns true if all parts are inline, false otherwise.
+bool nsIMAPBodyShell::PreflightCheckAllInline()
+{
+ bool rv = m_message->PreflightCheckAllInline(this);
+ // if (rv)
+ // MOZ_LOG(IMAP, out, ("BODYSHELL: All parts inline. Reverting to whole message download."));
+ return rv;
+}
+
+// When partNum is NULL, Generates a whole message and intelligently
+// leaves out parts that are not inline.
+
+// When partNum is not NULL, Generates a MIME part that hasn't been downloaded yet
+// Ok, here's how we're going to do this. Essentially, this
+// will be the mirror image of the "normal" generation.
+// All parts will be left out except a single part which is
+// explicitly specified. All relevant headers will be included.
+// Libmime will extract only the part of interest, so we don't
+// have to worry about the other parts. This also has the
+// advantage that it looks like it will be more workable for
+// nested parts. For instance, if a user clicks on a link to
+// a forwarded message, then that forwarded message may be
+// generated along with any images that the forwarded message
+// contains, for instance.
+
+
+int32_t nsIMAPBodyShell::Generate(char *partNum)
+{
+ m_isBeingGenerated = true;
+ m_generatingPart = partNum;
+ int32_t contentLength = 0;
+
+ if (!GetIsValid() || PreflightCheckAllInline())
+ {
+ // We don't have a valid shell, or all parts are going to be inline anyway. Fall back to fetching the whole message.
+#ifdef DEBUG_chrisf
+ NS_ASSERTION(GetIsValid());
+#endif
+ m_generatingWholeMessage = true;
+ uint32_t messageSize = m_protocolConnection->GetMessageSize(GetUID().get(), true);
+ m_protocolConnection->SetContentModified(IMAP_CONTENT_NOT_MODIFIED); // So that when we cache it, we know we have the whole message
+ if (!DeathSignalReceived())
+ m_protocolConnection->FallbackToFetchWholeMsg(GetUID(), messageSize);
+ contentLength = (int32_t) messageSize; // ugh
+ }
+ else
+ {
+ // We have a valid shell.
+ bool streamCreated = false;
+ m_generatingWholeMessage = false;
+
+ ////// PASS 1 : PREFETCH ///////
+ // First, prefetch any additional headers/data that we need
+ if (!GetPseudoInterrupted())
+ m_message->Generate(this, false, true); // This queues up everything we need to prefetch
+ // Now, run a single pipelined prefetch (neato!)
+ FlushPrefetchQueue();
+
+ ////// PASS 2 : COMPUTE STREAM SIZE ///////
+ // Next, figure out the size from the parts that we're going to fill in,
+ // plus all of the MIME headers, plus the message header itself
+ if (!GetPseudoInterrupted())
+ contentLength = m_message->Generate(this, false, false);
+
+ // Setup the stream
+ if (!GetPseudoInterrupted() && !DeathSignalReceived())
+ {
+ nsresult rv =
+ m_protocolConnection->BeginMessageDownLoad(contentLength, MESSAGE_RFC822);
+ if (NS_FAILED(rv))
+ {
+ m_generatingPart = nullptr;
+ m_protocolConnection->AbortMessageDownLoad();
+ return 0;
+ }
+ else
+ {
+ streamCreated = true;
+ }
+ }
+
+ ////// PASS 3 : GENERATE ///////
+ // Generate the message
+ if (!GetPseudoInterrupted() && !DeathSignalReceived())
+ m_message->Generate(this, true, false);
+
+ // Close the stream here - normal. If pseudointerrupted, the connection will abort the download stream
+ if (!GetPseudoInterrupted() && !DeathSignalReceived())
+ m_protocolConnection->NormalMessageEndDownload();
+ else if (streamCreated)
+ m_protocolConnection->AbortMessageDownLoad();
+
+ m_generatingPart = NULL;
+
+ }
+
+ m_isBeingGenerated = false;
+ return contentLength;
+}
+
+bool nsIMAPBodyShell::GetPseudoInterrupted()
+{
+ bool rv = m_protocolConnection->GetPseudoInterrupted();
+ return rv;
+}
+
+bool nsIMAPBodyShell::DeathSignalReceived()
+{
+ bool rv = m_protocolConnection->DeathSignalReceived();
+ return rv;
+}
+
+
+///////////// nsIMAPBodypart ////////////////////////////////////
+
+nsIMAPBodypart::nsIMAPBodypart(char *partNumber, nsIMAPBodypart *parentPart)
+{
+ SetIsValid(true);
+ m_parentPart = parentPart;
+ m_partNumberString = partNumber; // storage adopted
+ m_partData = NULL;
+ m_headerData = NULL;
+ m_boundaryData = NULL; // initialize from parsed BODYSTRUCTURE
+ m_contentLength = 0;
+ m_partLength = 0;
+
+ m_contentType = NULL;
+ m_bodyType = NULL;
+ m_bodySubType = NULL;
+ m_bodyID = NULL;
+ m_bodyDescription = NULL;
+ m_bodyEncoding = NULL;
+}
+
+nsIMAPBodypart::~nsIMAPBodypart()
+{
+ PR_FREEIF(m_partNumberString);
+ PR_FREEIF(m_contentType);
+ PR_FREEIF(m_bodyType);
+ PR_FREEIF(m_bodySubType);
+ PR_FREEIF(m_bodyID);
+ PR_FREEIF(m_bodyDescription);
+ PR_FREEIF(m_bodyEncoding);
+ PR_FREEIF(m_partData);
+ PR_FREEIF(m_headerData);
+ PR_FREEIF(m_boundaryData);
+}
+
+void nsIMAPBodypart::SetIsValid(bool valid)
+{
+ m_isValid = valid;
+ if (!m_isValid)
+ {
+ //MOZ_LOG(IMAP, out, ("BODYSHELL: Part is invalid. Part Number: %s Content-Type: %s", m_partNumberString, m_contentType));
+ }
+}
+
+// Adopts storage for part data buffer. If NULL, sets isValid to false.
+void nsIMAPBodypart::AdoptPartDataBuffer(char *buf)
+{
+ m_partData = buf;
+ if (!m_partData)
+ {
+ SetIsValid(false);
+ }
+}
+
+// Adopts storage for header data buffer. If NULL, sets isValid to false.
+void nsIMAPBodypart::AdoptHeaderDataBuffer(char *buf)
+{
+ m_headerData = buf;
+ if (!m_headerData)
+ {
+ SetIsValid(false);
+ }
+}
+
+// Finds the part with given part number
+// Returns a nsIMAPBodystructure of the matched part if it is this
+// or one of its children. Returns NULL otherwise.
+nsIMAPBodypart *nsIMAPBodypart::FindPartWithNumber(const char *partNum)
+{
+ // either brute force, or do it the smart way - look at the number.
+ // (the parts should be ordered, and hopefully indexed by their number)
+
+ if (m_partNumberString && !PL_strcasecmp(partNum, m_partNumberString))
+ return this;
+
+ //if (!m_partNumberString && !PL_strcasecmp(partNum, "1"))
+ // return this;
+
+ return NULL;
+}
+
+/*
+void nsIMAPBodypart::PrefetchMIMEHeader()
+{
+if (!m_headerData && !m_shell->DeathSignalReceived())
+{
+m_shell->GetConnection()->FetchMessage(m_shell->GetUID(), kMIMEHeader, true, 0, 0, m_partNumberString);
+// m_headerLength will be filled in when it is adopted from the parser
+}
+if (!m_headerData)
+{
+SetIsValid(false);
+}
+}
+*/
+
+void nsIMAPBodypart::QueuePrefetchMIMEHeader(nsIMAPBodyShell *aShell)
+{
+ aShell->AddPrefetchToQueue(kMIMEHeader, m_partNumberString);
+}
+
+int32_t nsIMAPBodypart::GenerateMIMEHeader(nsIMAPBodyShell *aShell, bool stream, bool prefetch)
+{
+ if (prefetch && !m_headerData)
+ {
+ QueuePrefetchMIMEHeader(aShell);
+ return 0;
+ }
+ else if (m_headerData)
+ {
+ int32_t mimeHeaderLength = 0;
+
+ if (!ShouldFetchInline(aShell))
+ {
+ // if this part isn't inline, add the X-Mozilla-IMAP-Part header
+ char *xPartHeader = PR_smprintf("%s: %s", IMAP_EXTERNAL_CONTENT_HEADER, m_partNumberString);
+ if (xPartHeader)
+ {
+ if (stream)
+ {
+ aShell->GetConnection()->Log("SHELL","GENERATE-XHeader",m_partNumberString);
+ aShell->GetConnection()->HandleMessageDownLoadLine(xPartHeader, false);
+ }
+ mimeHeaderLength += PL_strlen(xPartHeader);
+ PR_Free(xPartHeader);
+ }
+ }
+
+ mimeHeaderLength += PL_strlen(m_headerData);
+ if (stream)
+ {
+ aShell->GetConnection()->Log("SHELL","GENERATE-MIMEHeader",m_partNumberString);
+ aShell->GetConnection()->HandleMessageDownLoadLine(m_headerData, false); // all one line? Can we do that?
+ }
+
+ return mimeHeaderLength;
+ }
+ else
+ {
+ SetIsValid(false); // prefetch didn't adopt a MIME header
+ return 0;
+ }
+}
+
+int32_t nsIMAPBodypart::GeneratePart(nsIMAPBodyShell *aShell, bool stream, bool prefetch)
+{
+ if (prefetch)
+ return 0; // don't need to prefetch anything
+
+ if (m_partData) // we have prefetched the part data
+ {
+ if (stream)
+ {
+ aShell->GetConnection()->Log("SHELL","GENERATE-Part-Prefetched",m_partNumberString);
+ aShell->GetConnection()->HandleMessageDownLoadLine(m_partData, false);
+ }
+ return PL_strlen(m_partData);
+ }
+ else // we are fetching and streaming this part's body as we go
+ {
+ if (stream && !aShell->DeathSignalReceived())
+ {
+ char *generatingPart = aShell->GetGeneratingPart();
+ bool fetchingSpecificPart = (generatingPart && !PL_strcmp(generatingPart, m_partNumberString));
+
+ aShell->GetConnection()->Log("SHELL","GENERATE-Part-Inline",m_partNumberString);
+ aShell->GetConnection()->FetchTryChunking(aShell->GetUID(), kMIMEPart, true, m_partNumberString, m_partLength, !fetchingSpecificPart);
+ }
+ return m_partLength; // the part length has been filled in from the BODYSTRUCTURE response
+ }
+}
+
+int32_t nsIMAPBodypart::GenerateBoundary(nsIMAPBodyShell *aShell, bool stream, bool prefetch, bool lastBoundary)
+{
+ if (prefetch)
+ return 0; // don't need to prefetch anything
+
+ if (m_boundaryData)
+ {
+ if (!lastBoundary)
+ {
+ if (stream)
+ {
+ aShell->GetConnection()->Log("SHELL","GENERATE-Boundary",m_partNumberString);
+ aShell->GetConnection()->HandleMessageDownLoadLine(m_boundaryData, false);
+ }
+ return PL_strlen(m_boundaryData);
+ }
+ else // the last boundary
+ {
+ char *lastBoundaryData = PR_smprintf("%s--", m_boundaryData);
+ if (lastBoundaryData)
+ {
+ if (stream)
+ {
+ aShell->GetConnection()->Log("SHELL","GENERATE-Boundary-Last",m_partNumberString);
+ aShell->GetConnection()->HandleMessageDownLoadLine(lastBoundaryData, false);
+ }
+ int32_t rv = PL_strlen(lastBoundaryData);
+ PR_Free(lastBoundaryData);
+ return rv;
+ }
+ else
+ {
+ //HandleMemoryFailure();
+ return 0;
+ }
+ }
+ }
+ else
+ return 0;
+}
+
+int32_t nsIMAPBodypart::GenerateEmptyFilling(nsIMAPBodyShell *aShell, bool stream, bool prefetch)
+{
+ if (prefetch)
+ return 0; // don't need to prefetch anything
+
+ const nsString &emptyString = aShell->GetConnection()->
+ GetEmptyMimePartString();
+ if (!emptyString.IsEmpty())
+ {
+ if (stream)
+ {
+ nsImapProtocol *conn = aShell->GetConnection();
+ conn->Log("SHELL", "GENERATE-Filling", m_partNumberString);
+ conn->HandleMessageDownLoadLine(NS_ConvertUTF16toUTF8(emptyString).get(),
+ false);
+ }
+ return emptyString.Length();
+ }
+ else
+ return 0;
+}
+
+
+// Returns true if the prefs say that this content type should
+// explicitly be kept in when filling in the shell
+bool nsIMAPBodypart::ShouldExplicitlyFetchInline()
+{
+ return false;
+}
+
+
+// Returns true if the prefs say that this content type should
+// explicitly be left out when filling in the shell
+bool nsIMAPBodypart::ShouldExplicitlyNotFetchInline()
+{
+ return false;
+}
+
+
+///////////// nsIMAPBodypartLeaf /////////////////////////////
+
+
+nsIMAPBodypartLeaf::nsIMAPBodypartLeaf(char *partNum,
+ nsIMAPBodypart *parentPart,
+ char *bodyType, char *bodySubType,
+ char *bodyID, char *bodyDescription,
+ char *bodyEncoding, int32_t partLength,
+ bool preferPlainText)
+ : nsIMAPBodypart(partNum, parentPart), mPreferPlainText(preferPlainText)
+{
+ m_bodyType = bodyType;
+ m_bodySubType = bodySubType;
+ m_bodyID = bodyID;
+ m_bodyDescription = bodyDescription;
+ m_bodyEncoding = bodyEncoding;
+ m_partLength = partLength;
+ if (m_bodyType && m_bodySubType)
+ {
+ m_contentType = PR_smprintf("%s/%s", m_bodyType, m_bodySubType);
+ }
+ SetIsValid(true);
+}
+
+nsIMAPBodypartType nsIMAPBodypartLeaf::GetType()
+{
+ return IMAP_BODY_LEAF;
+}
+
+int32_t nsIMAPBodypartLeaf::Generate(nsIMAPBodyShell *aShell, bool stream, bool prefetch)
+{
+ int32_t len = 0;
+
+ if (GetIsValid())
+ {
+
+ if (stream && !prefetch)
+ aShell->GetConnection()->Log("SHELL","GENERATE-Leaf",m_partNumberString);
+
+ // Stream out the MIME part boundary
+ //GenerateBoundary();
+ NS_ASSERTION(m_parentPart, "part has no parent");
+ //nsIMAPBodypartMessage *parentMessage = m_parentPart ? m_parentPart->GetnsIMAPBodypartMessage() : NULL;
+
+ // Stream out the MIME header of this part, if this isn't the only body part of a message
+ //if (parentMessage ? !parentMessage->GetIsTopLevelMessage() : true)
+ if ((m_parentPart->GetType() != IMAP_BODY_MESSAGE_RFC822)
+ && !aShell->GetPseudoInterrupted())
+ len += GenerateMIMEHeader(aShell, stream, prefetch);
+
+ if (!aShell->GetPseudoInterrupted())
+ {
+ if (ShouldFetchInline(aShell))
+ {
+ // Fetch and stream the content of this part
+ len += GeneratePart(aShell, stream, prefetch);
+ }
+ else
+ {
+ // fill in the filling within the empty part
+ len += GenerateEmptyFilling(aShell, stream, prefetch);
+ }
+ }
+ }
+ m_contentLength = len;
+ return m_contentLength;
+}
+
+
+
+// returns true if this part should be fetched inline for generation.
+bool nsIMAPBodypartLeaf::ShouldFetchInline(nsIMAPBodyShell *aShell)
+{
+ char *generatingPart = aShell->GetGeneratingPart();
+ if (generatingPart)
+ {
+ // If we are generating a specific part
+ if (!PL_strcmp(generatingPart, m_partNumberString))
+ {
+ // This is the part we're generating
+ return true;
+ }
+ else
+ {
+ // If this is the only body part of a message, and that
+ // message is the part being generated, then this leaf should
+ // be inline as well.
+ if ((m_parentPart->GetType() == IMAP_BODY_MESSAGE_RFC822) &&
+ (!PL_strcmp(m_parentPart->GetPartNumberString(), generatingPart)))
+ return true;
+
+ // The parent of this part is a multipart
+ if (m_parentPart->GetType() == IMAP_BODY_MULTIPART)
+ {
+ // This is the first text part of a forwarded message
+ // with a multipart body, and that message is being generated,
+ // then generate this part.
+ nsIMAPBodypart *grandParent = m_parentPart->GetParentPart();
+ // grandParent must exist, since multiparts need parents
+ NS_ASSERTION(grandParent, "grandparent doesn't exist for multi-part alt");
+ if (grandParent &&
+ (grandParent->GetType() == IMAP_BODY_MESSAGE_RFC822) &&
+ (!PL_strcmp(grandParent->GetPartNumberString(), generatingPart)) &&
+ (m_partNumberString[PL_strlen(m_partNumberString)-1] == '1') &&
+ !PL_strcasecmp(m_bodyType, "text"))
+ return true; // we're downloading it inline
+
+
+ // This is a child of a multipart/appledouble attachment,
+ // and that multipart/appledouble attachment is being generated
+ if (m_parentPart &&
+ !PL_strcasecmp(m_parentPart->GetBodySubType(), "appledouble") &&
+ !PL_strcmp(m_parentPart->GetPartNumberString(), generatingPart))
+ return true; // we're downloading it inline
+ }
+
+ // Leave out all other leaves if this isn't the one
+ // we're generating.
+ // Maybe change later to check parents, etc.
+ return false;
+ }
+ }
+ else
+ {
+ // We are generating the whole message, possibly (hopefully)
+ // leaving out non-inline parts
+
+ if (ShouldExplicitlyFetchInline())
+ return true;
+ if (ShouldExplicitlyNotFetchInline())
+ return false;
+
+ // If the parent is a message (this is the only body part of that
+ // message), and that message should be inline, then its body
+ // should inherit the inline characteristics of that message
+ if (m_parentPart->GetType() == IMAP_BODY_MESSAGE_RFC822)
+ return m_parentPart->ShouldFetchInline(aShell);
+
+ // View Attachments As Links is on.
+ if (!(aShell->GetContentModified() == IMAP_CONTENT_MODIFIED_VIEW_INLINE))
+ {
+ // The last text part is still displayed inline,
+ // even if View Attachments As Links is on.
+ nsIMAPBodypart *grandParentPart = m_parentPart->GetParentPart();
+ if ((mPreferPlainText ||
+ !PL_strcasecmp(m_parentPart->GetBodySubType(), "mixed")) &&
+ !PL_strcmp(m_partNumberString, "1") &&
+ !PL_strcasecmp(m_bodyType, "text"))
+ return true; // we're downloading it inline
+
+ if ((!PL_strcasecmp(m_parentPart->GetBodySubType(), "alternative") ||
+ (grandParentPart &&
+ !PL_strcasecmp(grandParentPart->GetBodySubType(), "alternative"))) &&
+ !PL_strcasecmp(m_bodyType, "text") &&
+ ((!PL_strcasecmp(m_bodySubType, "plain") && mPreferPlainText) ||
+ (!PL_strcasecmp(m_bodySubType, "html") && !mPreferPlainText)))
+ return true;
+
+ // This is the first text part of a top-level multipart.
+ // For instance, a message with multipart body, where the first
+ // part is multipart, and this is the first leaf of that first part.
+ if (m_parentPart->GetType() == IMAP_BODY_MULTIPART &&
+ (PL_strlen(m_partNumberString) >= 2) &&
+ !PL_strcmp(m_partNumberString + PL_strlen(m_partNumberString) - 2, ".1") && // this is the first text type on this level
+ (!PL_strcmp(m_parentPart->GetPartNumberString(), "1") || !PL_strcmp(m_parentPart->GetPartNumberString(), "2")) &&
+ !PL_strcasecmp(m_bodyType, "text"))
+ return true;
+ // This is the first text part of a top-level multipart of the toplevelmessage
+ // This 'assumes' the text body is first leaf. This is not required for valid email.
+ // The only other way is to get content-disposition = attachment and exclude those text parts.
+ if (m_parentPart->GetType() == IMAP_BODY_MULTIPART &&
+ !PL_strcasecmp(m_bodyType, "text") &&
+ !PL_strcmp(m_parentPart->GetPartNumberString(), "0") &&
+ !PL_strcmp(m_partNumberString, "1"))
+ return true;
+
+ // we may have future problems needing tests here
+
+ return false; // we can leave it on the server
+ }
+#ifdef XP_MACOSX
+ // If it is either applesingle, or a resource fork for appledouble
+ if (!PL_strcasecmp(m_contentType, "application/applefile"))
+ {
+ // if it is appledouble
+ if (m_parentPart->GetType() == IMAP_BODY_MULTIPART &&
+ !PL_strcasecmp(m_parentPart->GetBodySubType(), "appledouble"))
+ {
+ // This is the resource fork of a multipart/appledouble.
+ // We inherit the inline attributes of the parent,
+ // which was derived from its OTHER child. (The data fork.)
+ return m_parentPart->ShouldFetchInline(aShell);
+ }
+ else // it is applesingle
+ {
+ return false; // we can leave it on the server
+ }
+ }
+#endif // XP_MACOSX
+
+ // Leave out parts with type application/*
+ if (!PL_strcasecmp(m_bodyType, "APPLICATION") && // If it is of type "application"
+ PL_strncasecmp(m_bodySubType, "x-pkcs7", 7) // and it's not a signature (signatures are inline)
+ )
+ return false; // we can leave it on the server
+ if (!PL_strcasecmp(m_bodyType, "AUDIO"))
+ return false;
+ // Here's where we can add some more intelligence -- let's leave out
+ // any other parts that we know we can't display inline.
+ return true; // we're downloading it inline
+ }
+}
+
+
+
+bool nsIMAPBodypartMultipart::IsLastTextPart(const char *partNumberString)
+{
+ // iterate backwards over the parent's part list and if the part is
+ // text, compare it to the part number string
+ for (int i = m_partList->Length() - 1; i >= 0; i--)
+ {
+ nsIMAPBodypart *part = m_partList->ElementAt(i);
+ if (!PL_strcasecmp(part->GetBodyType(), "text"))
+ return !PL_strcasecmp(part->GetPartNumberString(), partNumberString);
+ }
+ return false;
+}
+
+bool nsIMAPBodypartLeaf::PreflightCheckAllInline(nsIMAPBodyShell *aShell)
+{
+ // only need to check this part, since it has no children.
+ return ShouldFetchInline(aShell);
+}
+
+
+///////////// nsIMAPBodypartMessage ////////////////////////
+
+nsIMAPBodypartMessage::nsIMAPBodypartMessage(char *partNum,
+ nsIMAPBodypart *parentPart,
+ bool topLevelMessage,
+ char *bodyType, char *bodySubType,
+ char *bodyID,
+ char *bodyDescription,
+ char *bodyEncoding,
+ int32_t partLength,
+ bool preferPlainText)
+ : nsIMAPBodypartLeaf(partNum, parentPart, bodyType, bodySubType, bodyID,
+ bodyDescription, bodyEncoding, partLength,
+ preferPlainText)
+{
+ m_topLevelMessage = topLevelMessage;
+ if (m_topLevelMessage)
+ {
+ m_partNumberString = PR_smprintf("0");
+ if (!m_partNumberString)
+ {
+ SetIsValid(false);
+ return;
+ }
+ }
+ m_body = NULL;
+ m_headers = new nsIMAPMessageHeaders(m_partNumberString, this); // We always have a Headers object
+ if (!m_headers || !m_headers->GetIsValid())
+ {
+ SetIsValid(false);
+ return;
+ }
+ SetIsValid(true);
+}
+
+void nsIMAPBodypartMessage::SetBody(nsIMAPBodypart *body)
+{
+ if (m_body)
+ delete m_body;
+ m_body = body;
+}
+
+
+nsIMAPBodypartType nsIMAPBodypartMessage::GetType()
+{
+ return IMAP_BODY_MESSAGE_RFC822;
+}
+
+nsIMAPBodypartMessage::~nsIMAPBodypartMessage()
+{
+ delete m_headers;
+ delete m_body;
+}
+
+int32_t nsIMAPBodypartMessage::Generate(nsIMAPBodyShell *aShell, bool stream, bool prefetch)
+{
+ if (!GetIsValid())
+ return 0;
+
+ m_contentLength = 0;
+
+ if (stream && !prefetch)
+ aShell->GetConnection()->Log("SHELL","GENERATE-MessageRFC822",m_partNumberString);
+
+ if (!m_topLevelMessage && !aShell->GetPseudoInterrupted()) // not the top-level message - we need the MIME header as well as the message header
+ {
+ // but we don't need the MIME headers of a message/rfc822 part if this content
+ // type is in (part of) the main msg header. In other words, we still need
+ // these MIME headers if this message/rfc822 body part is enclosed in the msg
+ // body (most likely as a body part of a multipart/mixed msg).
+ // Don't fetch (bug 128888) Do fetch (bug 168097)
+ // ---------------------------------- -----------------------------------
+ // message/rfc822 (parent part) message/rfc822
+ // message/rfc822 <<<--- multipart/mixed (parent part)
+ // multipart/mixed message/rfc822 <<<---
+ // text/html (body text) multipart/mixed
+ // text/plain (attachment) text/html (body text)
+ // application/msword (attachment) text/plain (attachment)
+ // application/msword (attachment)
+ // "<<<---" points to the part we're examining here.
+ if ( PL_strcasecmp(m_bodyType, "message") || PL_strcasecmp(m_bodySubType, "rfc822") ||
+ PL_strcasecmp(m_parentPart->GetBodyType(), "message") || PL_strcasecmp(m_parentPart->GetBodySubType(), "rfc822") )
+ m_contentLength += GenerateMIMEHeader(aShell, stream, prefetch);
+ }
+
+ if (!aShell->GetPseudoInterrupted())
+ m_contentLength += m_headers->Generate(aShell, stream, prefetch);
+ if (!aShell->GetPseudoInterrupted())
+ m_contentLength += m_body->Generate(aShell, stream, prefetch);
+
+ return m_contentLength;
+}
+
+
+
+
+bool nsIMAPBodypartMessage::ShouldFetchInline(nsIMAPBodyShell *aShell)
+{
+ if (m_topLevelMessage) // the main message should always be defined as "inline"
+ return true;
+
+ char *generatingPart = aShell->GetGeneratingPart();
+ if (generatingPart)
+ {
+ // If we are generating a specific part
+ // Always generate containers (just don't fill them in)
+ // because it is low cost (everything is cached)
+ // and it gives the message its full MIME structure,
+ // to avoid any potential mishap.
+ return true;
+ }
+ else
+ {
+ // Generating whole message
+
+ if (ShouldExplicitlyFetchInline())
+ return true;
+ if (ShouldExplicitlyNotFetchInline())
+ return false;
+
+
+ // Message types are inline, by default.
+ return true;
+ }
+}
+
+bool nsIMAPBodypartMessage::PreflightCheckAllInline(nsIMAPBodyShell *aShell)
+{
+ if (!ShouldFetchInline(aShell))
+ return false;
+
+ return m_body->PreflightCheckAllInline(aShell);
+}
+
+// Fills in buffer (and adopts storage) for header object
+void nsIMAPBodypartMessage::AdoptMessageHeaders(char *headers)
+{
+ if (!GetIsValid())
+ return;
+
+ // we are going to say that the message headers only have
+ // part data, and no header data.
+ m_headers->AdoptPartDataBuffer(headers);
+ if (!m_headers->GetIsValid())
+ SetIsValid(false);
+}
+
+// Finds the part with given part number
+// Returns a nsIMAPBodystructure of the matched part if it is this
+// or one of its children. Returns NULL otherwise.
+nsIMAPBodypart *nsIMAPBodypartMessage::FindPartWithNumber(const char *partNum)
+{
+ // either brute force, or do it the smart way - look at the number.
+ // (the parts should be ordered, and hopefully indexed by their number)
+
+ if (!PL_strcasecmp(partNum, m_partNumberString))
+ return this;
+
+ return m_body->FindPartWithNumber(partNum);
+}
+
+///////////// nsIMAPBodypartMultipart ////////////////////////
+
+
+nsIMAPBodypartMultipart::nsIMAPBodypartMultipart(char *partNum, nsIMAPBodypart *parentPart) :
+nsIMAPBodypart(partNum, parentPart)
+{
+ if (!m_parentPart || (m_parentPart->GetType() == IMAP_BODY_MESSAGE_RFC822))
+ {
+ // the multipart (this) will inherit the part number of its parent
+ PR_FREEIF(m_partNumberString);
+ if (!m_parentPart)
+ {
+ m_partNumberString = PR_smprintf("0");
+ }
+ else
+ m_partNumberString = NS_strdup(m_parentPart->GetPartNumberString());
+ }
+ m_partList = new nsTArray<nsIMAPBodypart*>();
+ m_bodyType = NS_strdup("multipart");
+ if (m_partList && m_parentPart && m_bodyType)
+ SetIsValid(true);
+ else
+ SetIsValid(false);
+}
+
+nsIMAPBodypartType nsIMAPBodypartMultipart::GetType()
+{
+ return IMAP_BODY_MULTIPART;
+}
+
+nsIMAPBodypartMultipart::~nsIMAPBodypartMultipart()
+{
+ for (int i = m_partList->Length() - 1; i >= 0; i--)
+ {
+ delete m_partList->ElementAt(i);
+ }
+ delete m_partList;
+}
+
+void
+nsIMAPBodypartMultipart::SetBodySubType(char *bodySubType)
+{
+ PR_FREEIF(m_bodySubType);
+ PR_FREEIF(m_contentType);
+ m_bodySubType = bodySubType;
+ if (m_bodyType && m_bodySubType)
+ m_contentType = PR_smprintf("%s/%s", m_bodyType, m_bodySubType);
+}
+
+
+int32_t nsIMAPBodypartMultipart::Generate(nsIMAPBodyShell *aShell, bool stream, bool prefetch)
+{
+ int32_t len = 0;
+
+ if (GetIsValid())
+ {
+ if (stream && !prefetch)
+ aShell->GetConnection()->Log("SHELL","GENERATE-Multipart",m_partNumberString);
+
+ // Stream out the MIME header of this part
+
+ bool parentIsMessageType = GetParentPart() ? (GetParentPart()->GetType() == IMAP_BODY_MESSAGE_RFC822) : true;
+
+ // If this is multipart/signed, then we always want to generate the MIME headers of this multipart.
+ // Otherwise, we only want to do it if the parent is not of type "message"
+ bool needMIMEHeader = !parentIsMessageType; // !PL_strcasecmp(m_bodySubType, "signed") ? true : !parentIsMessageType;
+ if (needMIMEHeader && !aShell->GetPseudoInterrupted()) // not a message body's type
+ {
+ len += GenerateMIMEHeader(aShell, stream, prefetch);
+ }
+
+ if (ShouldFetchInline(aShell))
+ {
+ for (size_t i = 0; i < m_partList->Length(); i++)
+ {
+ if (!aShell->GetPseudoInterrupted())
+ len += GenerateBoundary(aShell, stream, prefetch, false);
+ if (!aShell->GetPseudoInterrupted())
+ len += m_partList->ElementAt(i)->Generate(aShell, stream, prefetch);
+ }
+ if (!aShell->GetPseudoInterrupted())
+ len += GenerateBoundary(aShell, stream, prefetch, true);
+ }
+ else
+ {
+ // fill in the filling within the empty part
+ if (!aShell->GetPseudoInterrupted())
+ len += GenerateEmptyFilling(aShell, stream, prefetch);
+ }
+ }
+ m_contentLength = len;
+ return m_contentLength;
+}
+
+
+bool nsIMAPBodypartMultipart::ShouldFetchInline(nsIMAPBodyShell *aShell)
+{
+ char *generatingPart = aShell->GetGeneratingPart();
+ if (generatingPart)
+ {
+ // If we are generating a specific part
+ // Always generate containers (just don't fill them in)
+ // because it is low cost (everything is cached)
+ // and it gives the message its full MIME structure,
+ // to avoid any potential mishap.
+ return true;
+ }
+ else
+ {
+ // Generating whole message
+
+ if (ShouldExplicitlyFetchInline())
+ return true;
+ if (ShouldExplicitlyNotFetchInline())
+ return false;
+
+ if (!PL_strcasecmp(m_bodySubType, "alternative"))
+ return true;
+
+ nsIMAPBodypart *grandparentPart = m_parentPart->GetParentPart();
+
+ // if we're a multipart sub-part of multipart alternative, we need to
+ // be fetched because mime will always display us.
+ if (!PL_strcasecmp(m_parentPart->GetBodySubType(), "alternative") &&
+ GetType() == IMAP_BODY_MULTIPART)
+ return true;
+ // If "Show Attachments as Links" is on, and
+ // the parent of this multipart is not a message,
+ // then it's not inline.
+ if (!(aShell->GetContentModified() == IMAP_CONTENT_MODIFIED_VIEW_INLINE) &&
+ (m_parentPart->GetType() != IMAP_BODY_MESSAGE_RFC822) &&
+ (m_parentPart->GetType() == IMAP_BODY_MULTIPART ?
+ (grandparentPart ? grandparentPart->GetType() != IMAP_BODY_MESSAGE_RFC822 : true)
+ : true))
+ return false;
+
+ // multiparts are always inline (even multipart/appledouble)
+ // (their children might not be, though)
+ return true;
+ }
+}
+
+bool nsIMAPBodypartMultipart::PreflightCheckAllInline(nsIMAPBodyShell *aShell)
+{
+ bool rv = ShouldFetchInline(aShell);
+
+ size_t i = 0;
+ while (rv && (i < m_partList->Length()))
+ {
+ rv = m_partList->ElementAt(i)->PreflightCheckAllInline(aShell);
+ i++;
+ }
+
+ return rv;
+}
+
+nsIMAPBodypart *nsIMAPBodypartMultipart::FindPartWithNumber(const char *partNum)
+{
+ NS_ASSERTION(partNum, "null part passed into FindPartWithNumber");
+
+ // check this
+ if (!PL_strcmp(partNum, m_partNumberString))
+ return this;
+
+ // check children
+ for (int i = m_partList->Length() - 1; i >= 0; i--)
+ {
+ nsIMAPBodypart *foundPart = m_partList->ElementAt(i)->FindPartWithNumber(partNum);
+ if (foundPart)
+ return foundPart;
+ }
+
+ // not this, or any of this's children
+ return NULL;
+}
+
+
+
+///////////// nsIMAPMessageHeaders ////////////////////////////////////
+
+
+
+nsIMAPMessageHeaders::nsIMAPMessageHeaders(char *partNum, nsIMAPBodypart *parentPart) :
+nsIMAPBodypart(partNum, parentPart)
+{
+ if (!partNum)
+ {
+ SetIsValid(false);
+ return;
+ }
+ m_partNumberString = NS_strdup(partNum);
+ if (!m_partNumberString)
+ {
+ SetIsValid(false);
+ return;
+ }
+ if (!m_parentPart || !m_parentPart->GetnsIMAPBodypartMessage())
+ {
+ // Message headers created without a valid Message parent
+ NS_ASSERTION(false, "creating message headers with invalid message parent");
+ SetIsValid(false);
+ }
+}
+
+nsIMAPBodypartType nsIMAPMessageHeaders::GetType()
+{
+ return IMAP_BODY_MESSAGE_HEADER;
+}
+
+void nsIMAPMessageHeaders::QueuePrefetchMessageHeaders(nsIMAPBodyShell *aShell)
+{
+
+ if (!m_parentPart->GetnsIMAPBodypartMessage()->GetIsTopLevelMessage()) // not top-level headers
+ aShell->AddPrefetchToQueue(kRFC822HeadersOnly, m_partNumberString);
+ else
+ aShell->AddPrefetchToQueue(kRFC822HeadersOnly, NULL);
+}
+
+int32_t nsIMAPMessageHeaders::Generate(nsIMAPBodyShell *aShell, bool stream, bool prefetch)
+{
+ // prefetch the header
+ if (prefetch && !m_partData && !aShell->DeathSignalReceived())
+ {
+ QueuePrefetchMessageHeaders(aShell);
+ }
+
+ if (stream && !prefetch)
+ aShell->GetConnection()->Log("SHELL","GENERATE-MessageHeaders",m_partNumberString);
+
+ // stream out the part data
+ if (ShouldFetchInline(aShell))
+ {
+ if (!aShell->GetPseudoInterrupted())
+ m_contentLength = GeneratePart(aShell, stream, prefetch);
+ }
+ else
+ {
+ m_contentLength = 0; // don't fill in any filling for the headers
+ }
+ return m_contentLength;
+}
+
+bool nsIMAPMessageHeaders::ShouldFetchInline(nsIMAPBodyShell *aShell)
+{
+ return m_parentPart->ShouldFetchInline(aShell);
+}
+
+
+///////////// nsIMAPBodyShellCache ////////////////////////////////////
+
+#if 0 // mscott - commenting out because it does not appear to be used
+static int
+imap_shell_cache_strcmp (const void *a, const void *b)
+{
+ return PL_strcmp ((const char *) a, (const char *) b);
+}
+#endif
+
+nsIMAPBodyShellCache::nsIMAPBodyShellCache()
+: m_shellHash(20)
+{
+ m_shellList = new nsTArray<nsIMAPBodyShell*>();
+}
+
+/* static */ nsIMAPBodyShellCache *nsIMAPBodyShellCache::Create()
+{
+ nsIMAPBodyShellCache *cache = new nsIMAPBodyShellCache();
+ if (!cache || !cache->m_shellList)
+ return NULL;
+
+ return cache;
+}
+
+nsIMAPBodyShellCache::~nsIMAPBodyShellCache()
+{
+ while (EjectEntry()) ;
+ delete m_shellList;
+}
+
+// We'll use an LRU scheme here.
+// We will add shells in numerical order, so the
+// least recently used one will be in slot 0.
+bool nsIMAPBodyShellCache::EjectEntry()
+{
+ if (m_shellList->Length() < 1)
+ return false;
+
+ nsIMAPBodyShell *removedShell = m_shellList->ElementAt(0);
+
+ m_shellList->RemoveElementAt(0);
+ m_shellHash.Remove(removedShell->GetUID());
+
+ return true;
+}
+
+void nsIMAPBodyShellCache::Clear()
+{
+ while (EjectEntry()) ;
+}
+
+bool nsIMAPBodyShellCache::AddShellToCache(nsIMAPBodyShell *shell)
+{
+ // If it's already in the cache, then just return.
+ // This has the side-effect of re-ordering the LRU list
+ // to put this at the top, which is good, because it's what we want.
+ if (FindShellForUID(shell->GetUID(), shell->GetFolderName(), shell->GetContentModified()))
+ return true;
+
+ // OK, so it's not in the cache currently.
+
+ // First, for safety sake, remove any entry with the given UID,
+ // just in case we have a collision between two messages in different
+ // folders with the same UID.
+ RefPtr<nsIMAPBodyShell> foundShell;
+ m_shellHash.Get(shell->GetUID(), getter_AddRefs(foundShell));
+ if (foundShell)
+ {
+ m_shellHash.Remove(foundShell->GetUID());
+ m_shellList->RemoveElement(foundShell);
+ }
+
+ // Add the new one to the cache
+ m_shellList->AppendElement(shell);
+
+ m_shellHash.Put(shell->GetUID(), shell);
+ shell->SetIsCached(true);
+
+ // while we're not over our size limit, eject entries
+ bool rv = true;
+ while (GetSize() > GetMaxSize())
+ rv = EjectEntry();
+
+ return rv;
+
+}
+
+nsIMAPBodyShell *nsIMAPBodyShellCache::FindShellForUID(nsCString &UID, const char *mailboxName,
+ IMAP_ContentModifiedType modType)
+{
+ RefPtr<nsIMAPBodyShell> foundShell;
+ m_shellHash.Get(UID, getter_AddRefs(foundShell));
+ if (!foundShell)
+ return nullptr;
+ // Make sure the content-modified types are compatible.
+ // This allows us to work seamlessly while people switch between
+ // View Attachments Inline and View Attachments As Links.
+ // Enforce the invariant that any cached shell we use
+ // match the current content-modified settings.
+ if (modType != foundShell->GetContentModified())
+ return nullptr;
+
+ // mailbox names must match also.
+ if (PL_strcmp(mailboxName, foundShell->GetFolderName()))
+ return nullptr;
+
+ // adjust the LRU stuff. This defeats the performance gain of the hash if
+ // it actually is found since this is linear.
+ m_shellList->RemoveElement(foundShell);
+ m_shellList->AppendElement(foundShell);// Adds to end
+
+ return foundShell;
+}
+
+///////////// nsIMAPMessagePartID ////////////////////////////////////
+
+
+nsIMAPMessagePartID::nsIMAPMessagePartID(nsIMAPeFetchFields fields, const char *partNumberString)
+: m_partNumberString(partNumberString),
+ m_fields(fields)
+{
+}
+
+nsIMAPMessagePartIDArray::nsIMAPMessagePartIDArray()
+{
+}
+
+nsIMAPMessagePartIDArray::~nsIMAPMessagePartIDArray()
+{
+ RemoveAndFreeAll();
+}
+
+void nsIMAPMessagePartIDArray::RemoveAndFreeAll()
+{
+ uint32_t n = Length();
+ for (uint32_t i = 0; i < n; i++)
+ {
+ nsIMAPMessagePartID *part = GetPart(i);
+ delete part;
+ }
+ Clear();
+}
diff --git a/mailnews/imap/src/nsIMAPBodyShell.h b/mailnews/imap/src/nsIMAPBodyShell.h
new file mode 100644
index 000000000..1c8d58ae5
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPBodyShell.h
@@ -0,0 +1,361 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+nsIMAPBodyShell and associated classes
+*/
+
+#ifndef IMAPBODY_H
+#define IMAPBODY_H
+
+#include "mozilla/Attributes.h"
+#include "nsImapCore.h"
+#include "nsStringGlue.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTArray.h"
+
+class nsImapProtocol;
+
+typedef enum _nsIMAPBodypartType {
+ IMAP_BODY_MESSAGE_RFC822,
+ IMAP_BODY_MESSAGE_HEADER,
+ IMAP_BODY_LEAF,
+ IMAP_BODY_MULTIPART
+} nsIMAPBodypartType;
+
+class nsIMAPBodyShell;
+class nsIMAPBodypartMessage;
+
+class nsIMAPBodypart
+{
+public:
+ // Construction
+ virtual bool GetIsValid() { return m_isValid; }
+ virtual void SetIsValid(bool valid);
+ virtual nsIMAPBodypartType GetType() = 0;
+
+ // Generation
+ // Generates an HTML representation of this part. Returns content length generated, -1 if failed.
+ virtual int32_t Generate(nsIMAPBodyShell *aShell, bool /*stream*/, bool /* prefetch */) { return -1; }
+ virtual void AdoptPartDataBuffer(char *buf); // Adopts storage for part data buffer. If NULL, sets isValid to false.
+ virtual void AdoptHeaderDataBuffer(char *buf); // Adopts storage for header data buffer. If NULL, sets isValid to false.
+ virtual bool ShouldFetchInline(nsIMAPBodyShell *aShell) { return true; } // returns true if this part should be fetched inline for generation.
+ virtual bool PreflightCheckAllInline(nsIMAPBodyShell *aShell) { return true; }
+
+ virtual bool ShouldExplicitlyFetchInline();
+ virtual bool ShouldExplicitlyNotFetchInline();
+ virtual bool IsLastTextPart(const char *partNumberString) {return true;}
+
+protected:
+ // If stream is false, simply returns the content length that will be generated
+ // the body of the part itself
+ virtual int32_t GeneratePart(nsIMAPBodyShell *aShell, bool stream, bool prefetch);
+ // the MIME headers of the part
+ virtual int32_t GenerateMIMEHeader(nsIMAPBodyShell *aShell, bool stream, bool prefetch);
+ // Generates the MIME boundary wrapper for this part.
+ virtual int32_t GenerateBoundary(nsIMAPBodyShell *aShell, bool stream, bool prefetch, bool lastBoundary);
+ // lastBoundary indicates whether or not this should be the boundary for the
+ // final MIME part of the multipart message.
+ // Generates (possibly empty) filling for a part that won't be filled in inline.
+ virtual int32_t GenerateEmptyFilling(nsIMAPBodyShell *aShell, bool stream, bool prefetch);
+
+ // Part Numbers / Hierarchy
+public:
+ virtual char *GetPartNumberString() { return m_partNumberString; }
+ virtual nsIMAPBodypart *FindPartWithNumber(const char *partNum); // Returns the part object with the given number
+ virtual nsIMAPBodypart *GetParentPart() { return m_parentPart; } // Returns the parent of this part.
+ // We will define a part of type message/rfc822 to be the
+ // parent of its body and header.
+ // A multipart is a parent of its child parts.
+ // All other leafs do not have children.
+
+ // Other / Helpers
+public:
+ virtual ~nsIMAPBodypart();
+ virtual nsIMAPBodypartMessage *GetnsIMAPBodypartMessage() { return NULL; }
+
+ const char *GetBodyType() { return m_bodyType; }
+ const char *GetBodySubType() { return m_bodySubType; }
+ void SetBoundaryData(char *boundaryData) { m_boundaryData = boundaryData; }
+
+protected:
+ virtual void QueuePrefetchMIMEHeader(nsIMAPBodyShell *aShell);
+ //virtual void PrefetchMIMEHeader(); // Initiates a prefetch for the MIME header of this part.
+ nsIMAPBodypart(char *partNumber, nsIMAPBodypart *parentPart);
+
+protected:
+ bool m_isValid; // If this part is valid.
+ char *m_partNumberString; // string representation of this part's full-hierarchy number. Define 0 to be the top-level message
+ char *m_partData; // data for this part. NULL if not filled in yet.
+ char *m_headerData; // data for this part's MIME header. NULL if not filled in yet.
+ char *m_boundaryData; // MIME boundary for this part
+ int32_t m_partLength;
+ int32_t m_contentLength; // Total content length which will be Generate()'d. -1 if not filled in yet.
+ nsIMAPBodypart *m_parentPart; // Parent of this part
+
+ // Fields - Filled in from parsed BODYSTRUCTURE response (as well as others)
+ char *m_contentType; // constructed from m_bodyType and m_bodySubType
+ char *m_bodyType;
+ char *m_bodySubType;
+ char *m_bodyID;
+ char *m_bodyDescription;
+ char *m_bodyEncoding;
+ // we ignore extension data for now
+};
+
+
+
+// Message headers
+// A special type of nsIMAPBodypart
+// These may be headers for the top-level message,
+// or any body part of type message/rfc822.
+class nsIMAPMessageHeaders : public nsIMAPBodypart
+{
+public:
+ nsIMAPMessageHeaders(char *partNum, nsIMAPBodypart *parentPart);
+ virtual nsIMAPBodypartType GetType() override;
+ // Generates an HTML representation of this part. Returns content length generated, -1 if failed.
+ virtual int32_t Generate(nsIMAPBodyShell *aShell, bool stream,
+ bool prefetch) override;
+ virtual bool ShouldFetchInline(nsIMAPBodyShell *aShell) override;
+ virtual void QueuePrefetchMessageHeaders(nsIMAPBodyShell *aShell);
+};
+
+
+class nsIMAPBodypartMultipart : public nsIMAPBodypart
+{
+public:
+ nsIMAPBodypartMultipart(char *partNum, nsIMAPBodypart *parentPart);
+ virtual nsIMAPBodypartType GetType() override;
+ virtual ~nsIMAPBodypartMultipart();
+ virtual bool ShouldFetchInline(nsIMAPBodyShell *aShell) override;
+ virtual bool PreflightCheckAllInline(nsIMAPBodyShell *aShell) override;
+ // Generates an HTML representation of this part. Returns content length generated, -1 if failed.
+ virtual int32_t Generate(nsIMAPBodyShell *aShell, bool stream,
+ bool prefetch) override;
+ // Returns the part object with the given number
+ virtual nsIMAPBodypart *FindPartWithNumber(const char *partNum
+ ) override;
+ virtual bool IsLastTextPart(const char *partNumberString) override;
+ void AppendPart(nsIMAPBodypart *part) { m_partList->AppendElement(part); }
+ void SetBodySubType(char *bodySubType);
+
+protected:
+ nsTArray<nsIMAPBodypart*> *m_partList; // An ordered list of top-level body parts for this shell
+};
+
+
+// The name "leaf" is somewhat misleading, since a part of type message/rfc822 is technically
+// a leaf, even though it can contain other parts within it.
+class nsIMAPBodypartLeaf : public nsIMAPBodypart
+{
+public:
+ nsIMAPBodypartLeaf(char *partNum, nsIMAPBodypart *parentPart, char *bodyType,
+ char *bodySubType, char *bodyID, char *bodyDescription,
+ char *bodyEncoding, int32_t partLength,
+ bool preferPlainText);
+ virtual nsIMAPBodypartType GetType() override;
+ // Generates an HTML representation of this part. Returns content length generated, -1 if failed.
+ virtual int32_t Generate(nsIMAPBodyShell *aShell, bool stream, bool prefetch) override;
+ // returns true if this part should be fetched inline for generation.
+ virtual bool ShouldFetchInline(nsIMAPBodyShell *aShell) override;
+ virtual bool PreflightCheckAllInline(nsIMAPBodyShell *aShell) override;
+private:
+ bool mPreferPlainText;
+};
+
+
+class nsIMAPBodypartMessage : public nsIMAPBodypartLeaf
+{
+public:
+ nsIMAPBodypartMessage(char *partNum, nsIMAPBodypart *parentPart,
+ bool topLevelMessage, char *bodyType,
+ char *bodySubType, char *bodyID,
+ char *bodyDescription, char *bodyEncoding,
+ int32_t partLength, bool preferPlainText);
+ void SetBody(nsIMAPBodypart *body);
+ virtual nsIMAPBodypartType GetType() override;
+ virtual ~nsIMAPBodypartMessage();
+ virtual int32_t Generate(nsIMAPBodyShell *aShell, bool stream,
+ bool prefetch) override;
+ virtual bool ShouldFetchInline(nsIMAPBodyShell *aShell) override;
+ virtual bool PreflightCheckAllInline(nsIMAPBodyShell *aShell) override;
+ // Returns the part object with the given number
+ virtual nsIMAPBodypart *FindPartWithNumber(const char *partNum
+ ) override;
+ void AdoptMessageHeaders(char *headers); // Fills in buffer (and adopts storage) for header object
+ // partNum specifies the message part number to which the
+ // headers correspond. NULL indicates the top-level message
+ virtual nsIMAPBodypartMessage *GetnsIMAPBodypartMessage() override { return this; }
+ virtual bool GetIsTopLevelMessage() { return m_topLevelMessage; }
+
+protected:
+ nsIMAPMessageHeaders *m_headers; // Every body shell should have headers
+ nsIMAPBodypart *m_body;
+ bool m_topLevelMessage; // Whether or not this is the top-level message
+
+};
+
+
+class nsIMAPMessagePartIDArray;
+
+// We will refer to a Body "Shell" as a hierarchical object representation of a parsed BODYSTRUCTURE
+// response. A shell contains representations of Shell "Parts." A Body Shell can undergo essentially
+// two operations: Construction and Generation.
+// Shell Construction occurs from a parsed a BODYSTRUCTURE response, split into empty parts.
+// Shell Generation generates a "MIME Shell" of the message and streams it to libmime for
+// display. The MIME Shell has selected (inline) parts filled in, and leaves all others
+// for on-demand retrieval through explicit part fetches.
+
+class nsIMAPBodyShell : public nsISupports
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ nsIMAPBodyShell(nsImapProtocol *protocolConnection,
+ nsIMAPBodypartMessage *message, uint32_t UID,
+ const char *folderName);
+ // To be used after a shell is uncached
+ void SetConnection(nsImapProtocol *con) { m_protocolConnection = con; }
+ virtual bool GetIsValid() { return m_isValid; }
+ virtual void SetIsValid(bool valid);
+
+ // Prefetch
+ // Adds a message body part to the queue to be prefetched
+ // in a single, pipelined command
+ void AddPrefetchToQueue(nsIMAPeFetchFields, const char *partNum);
+ // Runs a single pipelined command which fetches all of the
+ // elements in the prefetch queue
+ void FlushPrefetchQueue();
+ // Fills in buffer (and adopts storage) for header object
+ // partNum specifies the message part number to which the
+ // headers correspond. NULL indicates the top-level message
+ void AdoptMessageHeaders(char *headers, const char *partNum);
+ // Fills in buffer (and adopts storage) for MIME headers in appropriate object.
+ // If object can't be found, sets isValid to false.
+ void AdoptMimeHeader(const char *partNum, char *mimeHeader);
+
+ // Generation
+ // Streams out an HTML representation of this IMAP message, going along and
+ // fetching parts it thinks it needs, and leaving empty shells for the parts
+ // it doesn't.
+ // Returns number of bytes generated, or -1 if invalid.
+ // If partNum is not NULL, then this works to generates a MIME part that hasn't been downloaded yet
+ // and leaves out all other parts. By default, to generate a normal message, partNum should be NULL.
+ virtual int32_t Generate(char *partNum);
+
+ // Returns TRUE if the user has the pref "Show Attachments Inline" set.
+ // Returns FALSE if the setting is "Show Attachments as Links"
+ virtual bool GetShowAttachmentsInline();
+ // Returns true if all parts are inline, false otherwise. Does not generate anything.
+ bool PreflightCheckAllInline();
+
+ // Helpers
+ nsImapProtocol *GetConnection() { return m_protocolConnection; }
+ bool GetPseudoInterrupted();
+ bool DeathSignalReceived();
+ nsCString &GetUID() { return m_UID; }
+ const char *GetFolderName() { return m_folderName; }
+ char *GetGeneratingPart() { return m_generatingPart; }
+ // Returns true if this is in the process of being generated,
+ // so we don't re-enter
+ bool IsBeingGenerated() { return m_isBeingGenerated; }
+ bool IsShellCached() { return m_cached; }
+ void SetIsCached(bool isCached) { m_cached = isCached; }
+ bool GetGeneratingWholeMessage() { return m_generatingWholeMessage; }
+ IMAP_ContentModifiedType GetContentModified() { return m_contentModified; }
+ void SetContentModified(IMAP_ContentModifiedType modType) { m_contentModified = modType; }
+protected:
+ virtual ~nsIMAPBodyShell();
+
+ nsIMAPBodypartMessage *m_message;
+
+ nsIMAPMessagePartIDArray *m_prefetchQueue; // array of pipelined part prefetches. Ok, so it's not really a queue.
+
+ bool m_isValid;
+ nsImapProtocol *m_protocolConnection; // Connection, for filling in parts
+ nsCString m_UID; // UID of this message
+ char *m_folderName; // folder that contains this message
+ char *m_generatingPart; // If a specific part is being generated, this is it. Otherwise, NULL.
+ bool m_isBeingGenerated; // true if this body shell is in the process of being generated
+ bool m_gotAttachmentPref; // Whether or not m_showAttachmentsInline has been initialized
+ bool m_showAttachmentsInline; // Whether or not we should display attachment inline
+ bool m_cached; // Whether or not this shell is cached
+ bool m_generatingWholeMessage; // whether or not we are generating the whole (non-MPOD) message
+ // Set to false if we are generating by parts
+ // under what conditions the content has been modified.
+ // Either IMAP_CONTENT_MODIFIED_VIEW_INLINE or IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS
+ IMAP_ContentModifiedType m_contentModified;
+};
+
+
+
+// This class caches shells, so we don't have to always go and re-fetch them.
+// This does not cache any of the filled-in inline parts; those are cached individually
+// in the libnet memory cache. (ugh, how will we do that?)
+// Since we'll only be retrieving shells for messages over a given size, and since the
+// shells themselves won't be very large, this cache will not grow very big (relatively)
+// and should handle most common usage scenarios.
+
+// A body cache is associated with a given host, spanning folders.
+// It should pay attention to UIDVALIDITY.
+
+class nsIMAPBodyShellCache
+{
+public:
+ static nsIMAPBodyShellCache *Create();
+ virtual ~nsIMAPBodyShellCache();
+
+ // Adds shell to cache, possibly ejecting
+ // another entry based on scheme in EjectEntry().
+ bool AddShellToCache(nsIMAPBodyShell *shell);
+ // Looks up a shell in the cache given the message's UID.
+ nsIMAPBodyShell *FindShellForUID(nsCString &UID, const char *mailboxName,
+ IMAP_ContentModifiedType modType);
+ void Clear();
+
+protected:
+ nsIMAPBodyShellCache();
+ // Chooses an entry to eject; deletes that entry; and ejects it from the
+ // cache, clearing up a new space. Returns true if it found an entry
+ // to eject, false otherwise.
+ bool EjectEntry();
+ uint32_t GetSize() { return m_shellList->Length(); }
+ uint32_t GetMaxSize() { return 20; }
+ nsTArray<nsIMAPBodyShell*> *m_shellList; // For maintenance
+ // For quick lookup based on UID
+ nsRefPtrHashtable <nsCStringHashKey, nsIMAPBodyShell> m_shellHash;
+};
+
+// MessagePartID and MessagePartIDArray are used for pipelining prefetches.
+
+class nsIMAPMessagePartID
+{
+public:
+ nsIMAPMessagePartID(nsIMAPeFetchFields fields, const char *partNumberString);
+ nsIMAPeFetchFields GetFields() { return m_fields; }
+ const char *GetPartNumberString() { return m_partNumberString; }
+
+protected:
+ const char *m_partNumberString;
+ nsIMAPeFetchFields m_fields;
+};
+
+
+class nsIMAPMessagePartIDArray : public nsTArray<nsIMAPMessagePartID*> {
+public:
+ nsIMAPMessagePartIDArray();
+ ~nsIMAPMessagePartIDArray();
+
+ void RemoveAndFreeAll();
+ uint32_t GetNumParts() { return Length(); }
+ nsIMAPMessagePartID *GetPart(uint32_t i)
+ {
+ NS_ASSERTION(i < Length(), "invalid message part #");
+ return ElementAt(i);
+ }
+};
+
+
+#endif // IMAPBODY_H
diff --git a/mailnews/imap/src/nsIMAPGenericParser.cpp b/mailnews/imap/src/nsIMAPGenericParser.cpp
new file mode 100644
index 000000000..3053ab540
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPGenericParser.cpp
@@ -0,0 +1,484 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // for pre-compiled headers
+
+#include "nsImapCore.h"
+#include "nsImapProtocol.h"
+#include "nsIMAPGenericParser.h"
+#include "nsStringGlue.h"
+
+////////////////// nsIMAPGenericParser /////////////////////////
+
+
+nsIMAPGenericParser::nsIMAPGenericParser() :
+fNextToken(nullptr),
+fCurrentLine(nullptr),
+fLineOfTokens(nullptr),
+fStartOfLineOfTokens(nullptr),
+fCurrentTokenPlaceHolder(nullptr),
+fAtEndOfLine(false),
+fParserState(stateOK)
+{
+}
+
+nsIMAPGenericParser::~nsIMAPGenericParser()
+{
+ PR_FREEIF( fCurrentLine );
+ PR_FREEIF( fStartOfLineOfTokens);
+}
+
+void nsIMAPGenericParser::HandleMemoryFailure()
+{
+ SetConnected(false);
+}
+
+void nsIMAPGenericParser::ResetLexAnalyzer()
+{
+ PR_FREEIF( fCurrentLine );
+ PR_FREEIF( fStartOfLineOfTokens );
+
+ fNextToken = fCurrentLine = fLineOfTokens = fStartOfLineOfTokens = fCurrentTokenPlaceHolder = nullptr;
+ fAtEndOfLine = false;
+}
+
+bool nsIMAPGenericParser::LastCommandSuccessful()
+{
+ return fParserState == stateOK;
+}
+
+void nsIMAPGenericParser::SetSyntaxError(bool error, const char *msg)
+{
+ if (error)
+ fParserState |= stateSyntaxErrorFlag;
+ else
+ fParserState &= ~stateSyntaxErrorFlag;
+ NS_ASSERTION(!error, "syntax error in generic parser");
+}
+
+void nsIMAPGenericParser::SetConnected(bool connected)
+{
+ if (connected)
+ fParserState &= ~stateDisconnectedFlag;
+ else
+ fParserState |= stateDisconnectedFlag;
+}
+
+void nsIMAPGenericParser::skip_to_CRLF()
+{
+ while (Connected() && !fAtEndOfLine)
+ AdvanceToNextToken();
+}
+
+// fNextToken initially should point to
+// a string after the initial open paren ("(")
+// After this call, fNextToken points to the
+// first character after the matching close
+// paren. Only call AdvanceToNextToken() to get the NEXT
+// token after the one returned in fNextToken.
+void nsIMAPGenericParser::skip_to_close_paren()
+{
+ int numberOfCloseParensNeeded = 1;
+ while (ContinueParse())
+ {
+ // go through fNextToken, account for nested parens
+ const char *loc;
+ for (loc = fNextToken; loc && *loc; loc++)
+ {
+ if (*loc == '(')
+ numberOfCloseParensNeeded++;
+ else if (*loc == ')')
+ {
+ numberOfCloseParensNeeded--;
+ if (numberOfCloseParensNeeded == 0)
+ {
+ fNextToken = loc + 1;
+ if (!fNextToken || !*fNextToken)
+ AdvanceToNextToken();
+ return;
+ }
+ }
+ else if (*loc == '{' || *loc == '"') {
+ // quoted or literal
+ fNextToken = loc;
+ char *a = CreateString();
+ PR_FREEIF(a);
+ break; // move to next token
+ }
+ }
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+}
+
+void nsIMAPGenericParser::AdvanceToNextToken()
+{
+ if (!fCurrentLine || fAtEndOfLine)
+ AdvanceToNextLine();
+ if (Connected())
+ {
+ if (!fStartOfLineOfTokens)
+ {
+ // this is the first token of the line; setup tokenizer now
+ fStartOfLineOfTokens = PL_strdup(fCurrentLine);
+ if (!fStartOfLineOfTokens)
+ {
+ HandleMemoryFailure();
+ return;
+ }
+ fLineOfTokens = fStartOfLineOfTokens;
+ fCurrentTokenPlaceHolder = fStartOfLineOfTokens;
+ }
+ fNextToken = NS_strtok(WHITESPACE, &fCurrentTokenPlaceHolder);
+ if (!fNextToken)
+ {
+ fAtEndOfLine = true;
+ fNextToken = CRLF;
+ }
+ }
+}
+
+void nsIMAPGenericParser::AdvanceToNextLine()
+{
+ PR_FREEIF( fCurrentLine );
+ PR_FREEIF( fStartOfLineOfTokens);
+
+ bool ok = GetNextLineForParser(&fCurrentLine);
+ if (!ok)
+ {
+ SetConnected(false);
+ fStartOfLineOfTokens = nullptr;
+ fLineOfTokens = nullptr;
+ fCurrentTokenPlaceHolder = nullptr;
+ fAtEndOfLine = true;
+ fNextToken = CRLF;
+ }
+ else if (!fCurrentLine)
+ {
+ HandleMemoryFailure();
+ }
+ else
+ {
+ fNextToken = nullptr;
+ // determine if there are any tokens (without calling AdvanceToNextToken);
+ // otherwise we are already at end of line
+ NS_ASSERTION(strlen(WHITESPACE) == 3, "assume 3 chars of whitespace");
+ char *firstToken = fCurrentLine;
+ while (*firstToken && (*firstToken == WHITESPACE[0] ||
+ *firstToken == WHITESPACE[1] || *firstToken == WHITESPACE[2]))
+ firstToken++;
+ fAtEndOfLine = (*firstToken == '\0');
+ }
+}
+
+// advances |fLineOfTokens| by |bytesToAdvance| bytes
+void nsIMAPGenericParser::AdvanceTokenizerStartingPoint(int32_t bytesToAdvance)
+{
+ NS_PRECONDITION(bytesToAdvance>=0, "bytesToAdvance must not be negative");
+ if (!fStartOfLineOfTokens)
+ {
+ AdvanceToNextToken(); // the tokenizer was not yet initialized, do it now
+ if (!fStartOfLineOfTokens)
+ return;
+ }
+
+ if(!fStartOfLineOfTokens)
+ return;
+ // The last call to AdvanceToNextToken() cleared the token separator to '\0'
+ // iff |fCurrentTokenPlaceHolder|. We must recover this token separator now.
+ if (fCurrentTokenPlaceHolder)
+ {
+ int endTokenOffset = fCurrentTokenPlaceHolder - fStartOfLineOfTokens - 1;
+ if (endTokenOffset >= 0)
+ fStartOfLineOfTokens[endTokenOffset] = fCurrentLine[endTokenOffset];
+ }
+
+ NS_ASSERTION(bytesToAdvance + (fLineOfTokens-fStartOfLineOfTokens) <=
+ (int32_t)strlen(fCurrentLine), "cannot advance beyond end of fLineOfTokens");
+ fLineOfTokens += bytesToAdvance;
+ fCurrentTokenPlaceHolder = fLineOfTokens;
+}
+
+// RFC3501: astring = 1*ASTRING-CHAR / string
+// string = quoted / literal
+// This function leaves us off with fCurrentTokenPlaceHolder immediately after
+// the end of the Astring. Call AdvanceToNextToken() to get the token after it.
+char *nsIMAPGenericParser::CreateAstring()
+{
+ if (*fNextToken == '{')
+ return CreateLiteral(); // literal
+ else if (*fNextToken == '"')
+ return CreateQuoted(); // quoted
+ else
+ return CreateAtom(true); // atom
+}
+
+// Create an atom
+// This function does not advance the parser.
+// Call AdvanceToNextToken() to get the next token after the atom.
+// RFC3501: atom = 1*ATOM-CHAR
+// ASTRING-CHAR = ATOM-CHAR / resp-specials
+// ATOM-CHAR = <any CHAR except atom-specials>
+// atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards /
+// quoted-specials / resp-specials
+// list-wildcards = "%" / "*"
+// quoted-specials = DQUOTE / "\"
+// resp-specials = "]"
+// "Characters are 7-bit US-ASCII unless otherwise specified." [RFC3501, 1.2.]
+char *nsIMAPGenericParser::CreateAtom(bool isAstring)
+{
+ char *rv = PL_strdup(fNextToken);
+ if (!rv)
+ {
+ HandleMemoryFailure();
+ return nullptr;
+ }
+ // We wish to stop at the following characters (in decimal ascii)
+ // 1-31 (CTL), 32 (SP), 34 '"', 37 '%', 40-42 "()*", 92 '\\', 123 '{'
+ // also, ']' is only allowed in astrings
+ char *last = rv;
+ char c = *last;
+ while ((c > 42 || c == 33 || c == 35 || c == 36 || c == 38 || c == 39)
+ && c != '\\' && c != '{' && (isAstring || c != ']'))
+ c = *++last;
+ if (rv == last) {
+ SetSyntaxError(true, "no atom characters found");
+ PL_strfree(rv);
+ return nullptr;
+ }
+ if (*last)
+ {
+ // not the whole token was consumed
+ *last = '\0';
+ AdvanceTokenizerStartingPoint((fNextToken - fLineOfTokens) + (last-rv));
+ }
+ return rv;
+}
+
+// CreateNilString return either NULL (for "NIL") or a string
+// Call with fNextToken pointing to the thing which we think is the nilstring.
+// This function leaves us off with fCurrentTokenPlaceHolder immediately after
+// the end of the string.
+// Regardless of type, call AdvanceToNextToken() to get the token after it.
+// RFC3501: nstring = string / nil
+// nil = "NIL"
+char *nsIMAPGenericParser::CreateNilString()
+{
+ if (!PL_strncasecmp(fNextToken, "NIL", 3))
+ {
+ // check if there is text after "NIL" in fNextToken,
+ // equivalent handling as in CreateQuoted
+ if (fNextToken[3])
+ AdvanceTokenizerStartingPoint((fNextToken - fLineOfTokens) + 3);
+ return NULL;
+ }
+ else
+ return CreateString();
+}
+
+
+// Create a string, which can either be quoted or literal,
+// but not an atom.
+// This function leaves us off with fCurrentTokenPlaceHolder immediately after
+// the end of the String. Call AdvanceToNextToken() to get the token after it.
+char *nsIMAPGenericParser::CreateString()
+{
+ if (*fNextToken == '{')
+ {
+ char *rv = CreateLiteral(); // literal
+ return (rv);
+ }
+ else if (*fNextToken == '"')
+ {
+ char *rv = CreateQuoted(); // quoted
+ return (rv);
+ }
+ else
+ {
+ SetSyntaxError(true, "string does not start with '{' or '\"'");
+ return NULL;
+ }
+}
+
+// This function sets fCurrentTokenPlaceHolder immediately after the end of the
+// closing quote. Call AdvanceToNextToken() to get the token after it.
+// QUOTED_CHAR ::= <any TEXT_CHAR except quoted_specials> /
+// "\" quoted_specials
+// TEXT_CHAR ::= <any CHAR except CR and LF>
+// quoted_specials ::= <"> / "\"
+// Note that according to RFC 1064 and RFC 2060, CRs and LFs are not allowed
+// inside a quoted string. It is sufficient to read from the current line only.
+char *nsIMAPGenericParser::CreateQuoted(bool /*skipToEnd*/)
+{
+ // one char past opening '"'
+ char *currentChar = fCurrentLine + (fNextToken - fStartOfLineOfTokens) + 1;
+
+ int escapeCharsCut = 0;
+ nsCString returnString(currentChar);
+ int charIndex;
+ for (charIndex = 0; returnString.CharAt(charIndex) != '"'; charIndex++)
+ {
+ if (!returnString.CharAt(charIndex))
+ {
+ SetSyntaxError(true, "no closing '\"' found in quoted");
+ return nullptr;
+ }
+ else if (returnString.CharAt(charIndex) == '\\')
+ {
+ // eat the escape character, but keep the escaped character
+ returnString.Cut(charIndex, 1);
+ escapeCharsCut++;
+ }
+ }
+ // +2 because of the start and end quotes
+ AdvanceTokenizerStartingPoint((fNextToken - fLineOfTokens) +
+ charIndex + escapeCharsCut + 2);
+
+ returnString.SetLength(charIndex);
+ return ToNewCString(returnString);
+}
+
+
+// This function leaves us off with fCurrentTokenPlaceHolder immediately after
+// the end of the literal string. Call AdvanceToNextToken() to get the token
+// after the literal string.
+// RFC3501: literal = "{" number "}" CRLF *CHAR8
+// ; Number represents the number of CHAR8s
+// CHAR8 = %x01-ff
+// ; any OCTET except NUL, %x00
+char *nsIMAPGenericParser::CreateLiteral()
+{
+ int32_t numberOfCharsInMessage = atoi(fNextToken + 1);
+ uint32_t numBytes = numberOfCharsInMessage + 1;
+ NS_ASSERTION(numBytes, "overflow!");
+ if (!numBytes)
+ return nullptr;
+ char *returnString = (char *)PR_Malloc(numBytes);
+ if (!returnString)
+ {
+ HandleMemoryFailure();
+ return nullptr;
+ }
+
+ int32_t currentLineLength = 0;
+ int32_t charsReadSoFar = 0;
+ int32_t bytesToCopy = 0;
+ while (charsReadSoFar < numberOfCharsInMessage)
+ {
+ AdvanceToNextLine();
+ if (!ContinueParse())
+ break;
+
+ currentLineLength = strlen(fCurrentLine);
+ bytesToCopy = (currentLineLength > numberOfCharsInMessage - charsReadSoFar ?
+ numberOfCharsInMessage - charsReadSoFar : currentLineLength);
+ NS_ASSERTION(bytesToCopy, "zero-length line?");
+ memcpy(returnString + charsReadSoFar, fCurrentLine, bytesToCopy);
+ charsReadSoFar += bytesToCopy;
+ }
+
+ if (ContinueParse())
+ {
+ if (currentLineLength == bytesToCopy)
+ {
+ // We have consumed the entire line.
+ // Consider the input "{4}\r\n" "L1\r\n" " A2\r\n" which is read
+ // line-by-line. Reading an Astring, this should result in "L1\r\n".
+ // Note that the second line is "L1\r\n", where the "\r\n" is part of
+ // the literal. Hence, we now read the next line to ensure that the
+ // next call to AdvanceToNextToken() leads to fNextToken=="A2" in our
+ // example.
+ AdvanceToNextLine();
+ }
+ else
+ AdvanceTokenizerStartingPoint(bytesToCopy);
+ }
+
+ returnString[charsReadSoFar] = 0;
+ return returnString;
+}
+
+
+// Call this to create a buffer containing all characters within
+// a given set of parentheses.
+// Call this with fNextToken[0]=='(', that is, the open paren
+// of the group.
+// It will allocate and return all characters up to and including the corresponding
+// closing paren, and leave the parser in the right place afterwards.
+char *nsIMAPGenericParser::CreateParenGroup()
+{
+ NS_ASSERTION(fNextToken[0] == '(', "we don't have a paren group!");
+
+ int numOpenParens = 0;
+ AdvanceTokenizerStartingPoint(fNextToken - fLineOfTokens);
+
+ // Build up a buffer containing the paren group.
+ nsCString returnString;
+ char *parenGroupStart = fCurrentTokenPlaceHolder;
+ NS_ASSERTION(parenGroupStart[0] == '(', "we don't have a paren group (2)!");
+ while (*fCurrentTokenPlaceHolder)
+ {
+ if (*fCurrentTokenPlaceHolder == '{') // literal
+ {
+ // Ensure it is a properly formatted literal.
+ NS_ASSERTION(!strcmp("}\r\n", fCurrentTokenPlaceHolder + strlen(fCurrentTokenPlaceHolder) - 3), "not a literal");
+
+ // Append previous characters and the "{xx}\r\n" to buffer.
+ returnString.Append(parenGroupStart);
+
+ // Append literal itself.
+ AdvanceToNextToken();
+ if (!ContinueParse())
+ break;
+ char *lit = CreateLiteral();
+ NS_ASSERTION(lit, "syntax error or out of memory");
+ if (!lit)
+ break;
+ returnString.Append(lit);
+ PR_Free(lit);
+ if (!ContinueParse())
+ break;
+ parenGroupStart = fCurrentTokenPlaceHolder;
+ }
+ else if (*fCurrentTokenPlaceHolder == '"') // quoted
+ {
+ // Append the _escaped_ version of the quoted string:
+ // just skip it (because the quoted string must be on the same line).
+ AdvanceToNextToken();
+ if (!ContinueParse())
+ break;
+ char *q = CreateQuoted();
+ if (!q)
+ break;
+ PR_Free(q);
+ if (!ContinueParse())
+ break;
+ }
+ else
+ {
+ // Append this character to the buffer.
+ char c = *fCurrentTokenPlaceHolder++;
+ if (c == '(')
+ numOpenParens++;
+ else if (c == ')')
+ {
+ numOpenParens--;
+ if (numOpenParens == 0)
+ break;
+ }
+ }
+ }
+
+ if (numOpenParens != 0 || !ContinueParse())
+ {
+ SetSyntaxError(true, "closing ')' not found in paren group");
+ return nullptr;
+ }
+
+ returnString.Append(parenGroupStart, fCurrentTokenPlaceHolder - parenGroupStart);
+ AdvanceToNextToken();
+ return ToNewCString(returnString);
+}
+
diff --git a/mailnews/imap/src/nsIMAPGenericParser.h b/mailnews/imap/src/nsIMAPGenericParser.h
new file mode 100644
index 000000000..34f66e4ff
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPGenericParser.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+nsIMAPGenericParser is the base parser class used by the server parser and body shell parser
+*/
+
+#ifndef nsIMAPGenericParser_H
+#define nsIMAPGenericParser_H
+
+#include "nsImapCore.h"
+
+#define WHITESPACE " \015\012" // token delimiter
+
+
+class nsIMAPGenericParser
+{
+
+public:
+ nsIMAPGenericParser();
+ virtual ~nsIMAPGenericParser();
+
+ // Add any specific stuff in the derived class
+ virtual bool LastCommandSuccessful();
+
+ bool SyntaxError() { return (fParserState & stateSyntaxErrorFlag) != 0; }
+ bool ContinueParse() { return fParserState == stateOK; }
+ bool Connected() { return !(fParserState & stateDisconnectedFlag); }
+ void SetConnected(bool error);
+
+protected:
+
+ // This is a pure virtual member which must be overridden in the derived class
+ // for each different implementation of a nsIMAPGenericParser.
+ // For instance, one implementation (the nsIMAPServerState) might get the next line
+ // from an open socket, whereas another implementation might just get it from a buffer somewhere.
+ // This fills in nextLine with the buffer, and returns true if everything is OK.
+ // Returns false if there was some error encountered. In that case, we reset the parser.
+ virtual bool GetNextLineForParser(char **nextLine) = 0;
+
+ virtual void HandleMemoryFailure();
+ void skip_to_CRLF();
+ void skip_to_close_paren();
+ char *CreateString();
+ char *CreateAstring();
+ char *CreateNilString();
+ char *CreateLiteral();
+ char *CreateAtom(bool isAstring = false);
+ char *CreateQuoted(bool skipToEnd = true);
+ char *CreateParenGroup();
+ virtual void SetSyntaxError(bool error, const char *msg);
+
+ void AdvanceToNextToken();
+ void AdvanceToNextLine();
+ void AdvanceTokenizerStartingPoint(int32_t bytesToAdvance);
+ void ResetLexAnalyzer();
+
+protected:
+ // use with care
+ const char *fNextToken;
+ char *fCurrentLine;
+ char *fLineOfTokens;
+ char *fStartOfLineOfTokens;
+ char *fCurrentTokenPlaceHolder;
+ bool fAtEndOfLine;
+
+private:
+ enum nsIMAPGenericParserState { stateOK = 0,
+ stateSyntaxErrorFlag = 0x1,
+ stateDisconnectedFlag = 0x2 };
+ uint32_t fParserState;
+};
+
+#endif
diff --git a/mailnews/imap/src/nsIMAPHostSessionList.cpp b/mailnews/imap/src/nsIMAPHostSessionList.cpp
new file mode 100644
index 000000000..650faf9d4
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPHostSessionList.cpp
@@ -0,0 +1,701 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsIMAPHostSessionList.h"
+#include "nsIMAPBodyShell.h"
+#include "nsIMAPNamespace.h"
+#include "nsISupportsUtils.h"
+#include "nsIImapIncomingServer.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+nsIMAPHostInfo::nsIMAPHostInfo(const char *serverKey,
+ nsIImapIncomingServer *server)
+{
+ fServerKey = serverKey;
+ NS_ASSERTION(server, "*** Fatal null imap incoming server...\n");
+ server->GetServerDirectory(fOnlineDir);
+ fNextHost = NULL;
+ fCachedPassword = NULL;
+ fCapabilityFlags = kCapabilityUndefined;
+ fHierarchyDelimiters = NULL;
+#ifdef DEBUG_bienvenu1
+ fHaveWeEverDiscoveredFolders = true; // try this, see what bad happens - we'll need to
+ // figure out a way to make new accounts have it be false
+#else
+ fHaveWeEverDiscoveredFolders = false; // try this, see what bad happens
+#endif
+ fCanonicalOnlineSubDir = NULL;
+ fNamespaceList = nsIMAPNamespaceList::CreatensIMAPNamespaceList();
+ fUsingSubscription = true;
+ server->GetUsingSubscription(&fUsingSubscription);
+ fOnlineTrashFolderExists = false;
+ fShouldAlwaysListInbox = true;
+ fShellCache = nsIMAPBodyShellCache::Create();
+ fPasswordVerifiedOnline = false;
+ fDeleteIsMoveToTrash = true;
+ fShowDeletedMessages = false;
+ fGotNamespaces = false;
+ fHaveAdminURL = false;
+ fNamespacesOverridable = true;
+ server->GetOverrideNamespaces(&fNamespacesOverridable);
+ fTempNamespaceList = nsIMAPNamespaceList::CreatensIMAPNamespaceList();
+}
+
+nsIMAPHostInfo::~nsIMAPHostInfo()
+{
+ PR_Free(fCachedPassword);
+ PR_Free(fHierarchyDelimiters);
+ delete fNamespaceList;
+ delete fTempNamespaceList;
+ delete fShellCache;
+}
+
+NS_IMPL_ISUPPORTS(nsIMAPHostSessionList,
+ nsIImapHostSessionList,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+
+nsIMAPHostSessionList::nsIMAPHostSessionList()
+{
+ gCachedHostInfoMonitor = PR_NewMonitor(/* "accessing-hostlist-monitor"*/);
+ fHostInfoList = nullptr;
+}
+
+nsIMAPHostSessionList::~nsIMAPHostSessionList()
+{
+ ResetAll();
+ PR_DestroyMonitor(gCachedHostInfoMonitor);
+}
+
+nsresult nsIMAPHostSessionList::Init()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+ observerService->AddObserver(this, "profile-before-change", true);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsIMAPHostSessionList::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ if (!strcmp(aTopic, "profile-before-change"))
+ ResetAll();
+ else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID))
+ {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ observerService->RemoveObserver(this, "profile-before-change");
+ }
+ return NS_OK;
+}
+
+nsIMAPHostInfo *nsIMAPHostSessionList::FindHost(const char *serverKey)
+{
+ nsIMAPHostInfo *host;
+
+ // ### should also check userName here, if NON NULL
+ for (host = fHostInfoList; host; host = host->fNextHost)
+ {
+ if (host->fServerKey.Equals(serverKey, nsCaseInsensitiveCStringComparator()))
+ return host;
+ }
+ return host;
+}
+
+// reset any cached connection info - delete the lot of 'em
+NS_IMETHODIMP nsIMAPHostSessionList::ResetAll()
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *nextHost = NULL;
+ for (nsIMAPHostInfo *host = fHostInfoList; host; host = nextHost)
+ {
+ nextHost = host->fNextHost;
+ delete host;
+ }
+ fHostInfoList = NULL;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIMAPHostSessionList::AddHostToList(const char *serverKey,
+ nsIImapIncomingServer *server)
+{
+ nsIMAPHostInfo *newHost=NULL;
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ if (!FindHost(serverKey))
+ {
+ // stick it on the front
+ newHost = new nsIMAPHostInfo(serverKey, server);
+ if (newHost)
+ {
+ newHost->fNextHost = fHostInfoList;
+ fHostInfoList = newHost;
+ }
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (newHost == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetPasswordForHost(const char *serverKey, nsString &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ CopyASCIItoUTF16(nsDependentCString(host->fCachedPassword), result);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetPasswordForHost(const char *serverKey, const char *password)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ {
+ PR_FREEIF(host->fCachedPassword);
+ if (password)
+ host->fCachedPassword = NS_strdup(password);
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetPasswordVerifiedOnline(const char *serverKey)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fPasswordVerifiedOnline = true;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetPasswordVerifiedOnline(const char *serverKey, bool &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fPasswordVerifiedOnline;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetOnlineDirForHost(const char *serverKey,
+ nsString &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ CopyASCIItoUTF16(host->fOnlineDir, result);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetOnlineDirForHost(const char *serverKey,
+ const char *onlineDir)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ {
+ if (onlineDir)
+ host->fOnlineDir = onlineDir;
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetDeleteIsMoveToTrashForHost(const char *serverKey, bool &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fDeleteIsMoveToTrash;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetShowDeletedMessagesForHost(const char *serverKey, bool &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fShowDeletedMessages;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetDeleteIsMoveToTrashForHost(const char *serverKey, bool isMoveToTrash)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fDeleteIsMoveToTrash = isMoveToTrash;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetShowDeletedMessagesForHost(const char *serverKey, bool showDeletedMessages)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fShowDeletedMessages = showDeletedMessages;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetGotNamespacesForHost(const char *serverKey, bool &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fGotNamespaces;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetGotNamespacesForHost(const char *serverKey, bool gotNamespaces)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fGotNamespaces = gotNamespaces;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetHostIsUsingSubscription(const char *serverKey, bool &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fUsingSubscription;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetHostIsUsingSubscription(const char *serverKey, bool usingSubscription)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fUsingSubscription = usingSubscription;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetHostHasAdminURL(const char *serverKey, bool &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fHaveAdminURL;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetHostHasAdminURL(const char *serverKey, bool haveAdminURL)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fHaveAdminURL = haveAdminURL;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetHaveWeEverDiscoveredFoldersForHost(const char *serverKey, bool &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fHaveWeEverDiscoveredFolders;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetHaveWeEverDiscoveredFoldersForHost(const char *serverKey, bool discovered)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fHaveWeEverDiscoveredFolders = discovered;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetOnlineTrashFolderExistsForHost(const char *serverKey, bool exists)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fOnlineTrashFolderExists = exists;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetOnlineTrashFolderExistsForHost(const char *serverKey, bool &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fOnlineTrashFolderExists;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::AddNewNamespaceForHost(const char *serverKey, nsIMAPNamespace *ns)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fNamespaceList->AddNewNamespace(ns);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetNamespaceFromPrefForHost(const char *serverKey,
+ const char *namespacePref, EIMAPNamespaceType nstype)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ {
+ if (namespacePref)
+ {
+ int numNamespaces = host->fNamespaceList->UnserializeNamespaces(namespacePref, nullptr, 0);
+ char **prefixes = (char**) PR_CALLOC(numNamespaces * sizeof(char*));
+ if (prefixes)
+ {
+ int len = host->fNamespaceList->UnserializeNamespaces(namespacePref, prefixes, numNamespaces);
+ for (int i = 0; i < len; i++)
+ {
+ char *thisns = prefixes[i];
+ char delimiter = '/'; // a guess
+ if (PL_strlen(thisns) >= 1)
+ delimiter = thisns[PL_strlen(thisns)-1];
+ nsIMAPNamespace *ns = new nsIMAPNamespace(nstype, thisns, delimiter, true);
+ if (ns)
+ host->fNamespaceList->AddNewNamespace(ns);
+ PR_FREEIF(thisns);
+ }
+ PR_Free(prefixes);
+ }
+ }
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetNamespaceForMailboxForHost(const char *serverKey, const char *mailbox_name, nsIMAPNamespace * &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fNamespaceList->GetNamespaceForMailbox(mailbox_name);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+
+NS_IMETHODIMP nsIMAPHostSessionList::ClearPrefsNamespacesForHost(const char *serverKey)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fNamespaceList->ClearNamespaces(true, false, true);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+
+NS_IMETHODIMP nsIMAPHostSessionList::ClearServerAdvertisedNamespacesForHost(const char *serverKey)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fNamespaceList->ClearNamespaces(false, true, true);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetDefaultNamespaceOfTypeForHost(const char *serverKey, EIMAPNamespaceType type, nsIMAPNamespace * &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fNamespaceList->GetDefaultNamespaceOfType(type);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetNamespacesOverridableForHost(const char *serverKey, bool &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fNamespacesOverridable;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetNamespacesOverridableForHost(const char *serverKey, bool overridable)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fNamespacesOverridable = overridable;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetNumberOfNamespacesForHost(const char *serverKey, uint32_t &result)
+{
+ int32_t intResult = 0;
+
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ intResult = host->fNamespaceList->GetNumberOfNamespaces();
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ NS_ASSERTION(intResult >= 0, "negative number of namespaces");
+ result = (uint32_t) intResult;
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetNamespaceNumberForHost(const char *serverKey, int32_t n, nsIMAPNamespace * &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ result = host->fNamespaceList->GetNamespaceNumber(n);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+nsresult nsIMAPHostSessionList::SetNamespacesPrefForHost(nsIImapIncomingServer *aHost,
+ EIMAPNamespaceType type,
+ const char *pref)
+{
+ if (type == kPersonalNamespace)
+ aHost->SetPersonalNamespace(nsDependentCString(pref));
+ else if (type == kPublicNamespace)
+ aHost->SetPublicNamespace(nsDependentCString(pref));
+ else if (type == kOtherUsersNamespace)
+ aHost->SetOtherUsersNamespace(nsDependentCString(pref));
+ else
+ NS_ASSERTION(false, "bogus namespace type");
+ return NS_OK;
+
+}
+// do we need this? What should we do about the master thing?
+// Make sure this is running in the Mozilla thread when called
+NS_IMETHODIMP nsIMAPHostSessionList::CommitNamespacesForHost(nsIImapIncomingServer *aHost)
+{
+ NS_ENSURE_ARG_POINTER(aHost);
+ nsCString serverKey;
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer = do_QueryInterface(aHost);
+ if (!incomingServer)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = incomingServer->GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey.get());
+ if (host)
+ {
+ host->fGotNamespaces = true; // so we only issue NAMESPACE once per host per session.
+ EIMAPNamespaceType type = kPersonalNamespace;
+ for (int i = 1; i <= 3; i++)
+ {
+ switch(i)
+ {
+ case 1:
+ type = kPersonalNamespace;
+ break;
+ case 2:
+ type = kPublicNamespace;
+ break;
+ case 3:
+ type = kOtherUsersNamespace;
+ break;
+ default:
+ type = kPersonalNamespace;
+ break;
+ }
+
+ int32_t numInNS = host->fNamespaceList->GetNumberOfNamespaces(type);
+ if (numInNS == 0)
+ SetNamespacesPrefForHost(aHost, type, "");
+ else if (numInNS >= 1)
+ {
+ char *pref = PR_smprintf("");
+ for (int count = 1; count <= numInNS; count++)
+ {
+ nsIMAPNamespace *ns = host->fNamespaceList->GetNamespaceNumber(count, type);
+ if (ns)
+ {
+ if (count > 1)
+ {
+ // append the comma
+ char *tempPref = PR_smprintf("%s,",pref);
+ PR_FREEIF(pref);
+ pref = tempPref;
+ }
+ char *tempPref = PR_smprintf("%s\"%s\"",pref,ns->GetPrefix());
+ PR_FREEIF(pref);
+ pref = tempPref;
+ }
+ }
+ if (pref)
+ {
+ SetNamespacesPrefForHost(aHost, type, pref);
+ PR_Free(pref);
+ }
+ }
+ }
+ // clear, but don't delete the entries in, the temp namespace list
+ host->fTempNamespaceList->ClearNamespaces(true, true, false);
+
+ // Now reset all of libmsg's namespace references.
+ // Did I mention this needs to be running in the mozilla thread?
+ aHost->ResetNamespaceReferences();
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::FlushUncommittedNamespacesForHost(const char *serverKey, bool &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fTempNamespaceList->ClearNamespaces(true, true, true);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+
+// Returns NULL if there is no personal namespace on the given host
+NS_IMETHODIMP nsIMAPHostSessionList::GetOnlineInboxPathForHost(const char *serverKey, nsString &result)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ {
+ nsIMAPNamespace *ns = NULL;
+ ns = host->fNamespaceList->GetDefaultNamespaceOfType(kPersonalNamespace);
+ if (ns)
+ {
+ CopyASCIItoUTF16(nsDependentCString(ns->GetPrefix()), result);
+ result.AppendLiteral("INBOX");
+ }
+ }
+ else
+ result.Truncate();
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::GetShouldAlwaysListInboxForHost(const char* /*serverKey*/, bool &result)
+{
+ result = true;
+
+ /*
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ ret = host->fShouldAlwaysListInbox;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ */
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetShouldAlwaysListInboxForHost(const char *serverKey, bool shouldList)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ host->fShouldAlwaysListInbox = shouldList;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetNamespaceHierarchyDelimiterFromMailboxForHost(const char *serverKey, const char *boxName, char delimiter)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ {
+ nsIMAPNamespace *ns = host->fNamespaceList->GetNamespaceForMailbox(boxName);
+ if (ns && !ns->GetIsDelimiterFilledIn())
+ ns->SetDelimiter(delimiter, true);
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host) ? NS_OK : NS_ERROR_ILLEGAL_VALUE ;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::AddShellToCacheForHost(const char *serverKey, nsIMAPBodyShell *shell)
+{
+ nsresult rv = NS_OK;
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ {
+ if (host->fShellCache)
+ {
+ if (!host->fShellCache->AddShellToCache(shell))
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ else
+ rv = NS_ERROR_ILLEGAL_VALUE;
+
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return rv;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::FindShellInCacheForHost(const char *serverKey, const char *mailboxName, const char *UID,
+ IMAP_ContentModifiedType modType, nsIMAPBodyShell **shell)
+{
+ nsCString uidString(UID);
+
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host && host->fShellCache)
+ NS_IF_ADDREF(*shell = host->fShellCache->FindShellForUID(uidString,
+ mailboxName,
+ modType));
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP
+nsIMAPHostSessionList::ClearShellCacheForHost(const char *serverKey)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host && host->fShellCache)
+ host->fShellCache->Clear();
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
diff --git a/mailnews/imap/src/nsIMAPHostSessionList.h b/mailnews/imap/src/nsIMAPHostSessionList.h
new file mode 100644
index 000000000..5f601fe43
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPHostSessionList.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsIMAPHostSessionList_H_
+#define _nsIMAPHostSessionList_H_
+
+#include "mozilla/Attributes.h"
+#include "nsImapCore.h"
+#include "nsIIMAPHostSessionList.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nspr.h"
+
+class nsIMAPNamespaceList;
+class nsIImapIncomingServer;
+
+class nsIMAPHostInfo
+{
+public:
+ friend class nsIMAPHostSessionList;
+
+ nsIMAPHostInfo(const char *serverKey, nsIImapIncomingServer *server);
+ ~nsIMAPHostInfo();
+protected:
+ nsCString fServerKey;
+ char *fCachedPassword;
+ nsCString fOnlineDir;
+ nsIMAPHostInfo *fNextHost;
+ eIMAPCapabilityFlags fCapabilityFlags;
+ char *fHierarchyDelimiters;// string of top-level hierarchy delimiters
+ bool fHaveWeEverDiscoveredFolders;
+ char *fCanonicalOnlineSubDir;
+ nsIMAPNamespaceList *fNamespaceList, *fTempNamespaceList;
+ bool fNamespacesOverridable;
+ bool fUsingSubscription;
+ bool fOnlineTrashFolderExists;
+ bool fShouldAlwaysListInbox;
+ bool fHaveAdminURL;
+ bool fPasswordVerifiedOnline;
+ bool fDeleteIsMoveToTrash;
+ bool fShowDeletedMessages;
+ bool fGotNamespaces;
+ nsIMAPBodyShellCache *fShellCache;
+};
+
+// this is an interface to a linked list of host info's
+class nsIMAPHostSessionList : public nsIImapHostSessionList, public nsIObserver, public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsIMAPHostSessionList();
+ nsresult Init();
+ // Host List
+ NS_IMETHOD AddHostToList(const char *serverKey,
+ nsIImapIncomingServer *server) override;
+ NS_IMETHOD ResetAll() override;
+
+ // Capabilities
+ NS_IMETHOD GetHostHasAdminURL(const char *serverKey, bool &result) override;
+ NS_IMETHOD SetHostHasAdminURL(const char *serverKey, bool hasAdminUrl) override;
+ // Subscription
+ NS_IMETHOD GetHostIsUsingSubscription(const char *serverKey, bool &result) override;
+ NS_IMETHOD SetHostIsUsingSubscription(const char *serverKey, bool usingSubscription) override;
+
+ // Passwords
+ NS_IMETHOD GetPasswordForHost(const char *serverKey, nsString &result) override;
+ NS_IMETHOD SetPasswordForHost(const char *serverKey, const char *password) override;
+ NS_IMETHOD GetPasswordVerifiedOnline(const char *serverKey, bool &result) override;
+ NS_IMETHOD SetPasswordVerifiedOnline(const char *serverKey) override;
+
+ // OnlineDir
+ NS_IMETHOD GetOnlineDirForHost(const char *serverKey,
+ nsString &result) override;
+ NS_IMETHOD SetOnlineDirForHost(const char *serverKey,
+ const char *onlineDir) override;
+
+ // Delete is move to trash folder
+ NS_IMETHOD GetDeleteIsMoveToTrashForHost(const char *serverKey, bool &result) override;
+ NS_IMETHOD SetDeleteIsMoveToTrashForHost(const char *serverKey, bool isMoveToTrash) override;
+ // imap delete model (or not)
+ NS_IMETHOD GetShowDeletedMessagesForHost(const char *serverKey, bool &result) override;
+ NS_IMETHOD SetShowDeletedMessagesForHost(const char *serverKey, bool showDeletedMessages) override;
+
+ // Get namespaces
+ NS_IMETHOD GetGotNamespacesForHost(const char *serverKey, bool &result) override;
+ NS_IMETHOD SetGotNamespacesForHost(const char *serverKey, bool gotNamespaces) override;
+ // Folders
+ NS_IMETHOD SetHaveWeEverDiscoveredFoldersForHost(const char *serverKey, bool discovered) override;
+ NS_IMETHOD GetHaveWeEverDiscoveredFoldersForHost(const char *serverKey, bool &result) override;
+
+ // Trash Folder
+ NS_IMETHOD SetOnlineTrashFolderExistsForHost(const char *serverKey, bool exists) override;
+ NS_IMETHOD GetOnlineTrashFolderExistsForHost(const char *serverKey, bool &result) override;
+
+ // INBOX
+ NS_IMETHOD GetOnlineInboxPathForHost(const char *serverKey, nsString &result) override;
+ NS_IMETHOD GetShouldAlwaysListInboxForHost(const char *serverKey, bool &result) override;
+ NS_IMETHOD SetShouldAlwaysListInboxForHost(const char *serverKey, bool shouldList) override;
+
+ // Namespaces
+ NS_IMETHOD GetNamespaceForMailboxForHost(const char *serverKey, const char *mailbox_name, nsIMAPNamespace *&result) override;
+ NS_IMETHOD SetNamespaceFromPrefForHost(const char *serverKey, const char *namespacePref, EIMAPNamespaceType type) override;
+ NS_IMETHOD AddNewNamespaceForHost(const char *serverKey, nsIMAPNamespace *ns) override;
+ NS_IMETHOD ClearServerAdvertisedNamespacesForHost(const char *serverKey) override;
+ NS_IMETHOD ClearPrefsNamespacesForHost(const char *serverKey) override;
+ NS_IMETHOD GetDefaultNamespaceOfTypeForHost(const char *serverKey, EIMAPNamespaceType type, nsIMAPNamespace *&result) override;
+ NS_IMETHOD SetNamespacesOverridableForHost(const char *serverKey, bool overridable) override;
+ NS_IMETHOD GetNamespacesOverridableForHost(const char *serverKey,bool &result) override;
+ NS_IMETHOD GetNumberOfNamespacesForHost(const char *serverKey, uint32_t &result) override;
+ NS_IMETHOD GetNamespaceNumberForHost(const char *serverKey, int32_t n, nsIMAPNamespace * &result) override;
+ // ### dmb hoo boy, how are we going to do this?
+ NS_IMETHOD CommitNamespacesForHost(nsIImapIncomingServer *host) override;
+ NS_IMETHOD FlushUncommittedNamespacesForHost(const char *serverKey, bool &result) override;
+
+ // Hierarchy Delimiters
+ NS_IMETHOD SetNamespaceHierarchyDelimiterFromMailboxForHost(const char *serverKey, const char *boxName, char delimiter) override;
+
+ // Message Body Shells
+ NS_IMETHOD AddShellToCacheForHost(const char *serverKey, nsIMAPBodyShell *shell) override;
+ NS_IMETHOD FindShellInCacheForHost(const char *serverKey, const char *mailboxName, const char *UID, IMAP_ContentModifiedType modType, nsIMAPBodyShell **result) override;
+ NS_IMETHOD ClearShellCacheForHost(const char *serverKey) override;
+ PRMonitor *gCachedHostInfoMonitor;
+ nsIMAPHostInfo *fHostInfoList;
+protected:
+ virtual ~nsIMAPHostSessionList();
+ nsresult SetNamespacesPrefForHost(nsIImapIncomingServer *aHost,
+ EIMAPNamespaceType type,
+ const char *pref);
+ nsIMAPHostInfo *FindHost(const char *serverKey);
+};
+#endif
diff --git a/mailnews/imap/src/nsIMAPNamespace.cpp b/mailnews/imap/src/nsIMAPNamespace.cpp
new file mode 100644
index 000000000..2cba54016
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPNamespace.cpp
@@ -0,0 +1,651 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // for pre-compiled headers
+
+#include "nsImapCore.h"
+#include "nsIMAPNamespace.h"
+#include "nsImapProtocol.h"
+#include "nsMsgImapCID.h"
+#include "nsImapUrl.h"
+#include "nsStringGlue.h"
+#include "nsServiceManagerUtils.h"
+#include <algorithm>
+
+//////////////////// nsIMAPNamespace /////////////////////////////////////////////////////////////
+
+static NS_DEFINE_CID(kCImapHostSessionListCID, NS_IIMAPHOSTSESSIONLIST_CID);
+
+nsIMAPNamespace::nsIMAPNamespace(EIMAPNamespaceType type, const char *prefix, char delimiter, bool from_prefs)
+{
+ m_namespaceType = type;
+ m_prefix = PL_strdup(prefix);
+ m_fromPrefs = from_prefs;
+
+ m_delimiter = delimiter;
+ m_delimiterFilledIn = !m_fromPrefs; // if it's from the prefs, we can't be sure about the delimiter until we list it.
+}
+
+nsIMAPNamespace::~nsIMAPNamespace()
+{
+ PR_FREEIF(m_prefix);
+}
+
+void nsIMAPNamespace::SetDelimiter(char delimiter, bool delimiterFilledIn)
+{
+ m_delimiter = delimiter;
+ m_delimiterFilledIn = delimiterFilledIn;
+}
+
+// returns -1 if this box is not part of this namespace,
+// or the length of the prefix if it is part of this namespace
+int nsIMAPNamespace::MailboxMatchesNamespace(const char *boxname)
+{
+ if (!boxname) return -1;
+
+ // If the namespace is part of the boxname
+ if (!m_prefix || !*m_prefix)
+ return 0;
+
+ if (PL_strstr(boxname, m_prefix) == boxname)
+ return PL_strlen(m_prefix);
+
+ // If the boxname is part of the prefix
+ // (Used for matching Personal mailbox with Personal/ namespace, etc.)
+ if (PL_strstr(m_prefix, boxname) == m_prefix)
+ return PL_strlen(boxname);
+ return -1;
+}
+
+
+nsIMAPNamespaceList *nsIMAPNamespaceList::CreatensIMAPNamespaceList()
+{
+ nsIMAPNamespaceList *rv = new nsIMAPNamespaceList();
+ return rv;
+}
+
+nsIMAPNamespaceList::nsIMAPNamespaceList()
+{
+}
+
+int nsIMAPNamespaceList::GetNumberOfNamespaces()
+{
+ return m_NamespaceList.Length();
+}
+
+
+nsresult nsIMAPNamespaceList::InitFromString(const char *nameSpaceString, EIMAPNamespaceType nstype)
+{
+ nsresult rv = NS_OK;
+ if (nameSpaceString)
+ {
+ int numNamespaces = UnserializeNamespaces(nameSpaceString, nullptr, 0);
+ char **prefixes = (char**) PR_CALLOC(numNamespaces * sizeof(char*));
+ if (prefixes)
+ {
+ int len = UnserializeNamespaces(nameSpaceString, prefixes, numNamespaces);
+ for (int i = 0; i < len; i++)
+ {
+ char *thisns = prefixes[i];
+ char delimiter = '/'; // a guess
+ if (PL_strlen(thisns) >= 1)
+ delimiter = thisns[PL_strlen(thisns)-1];
+ nsIMAPNamespace *ns = new nsIMAPNamespace(nstype, thisns, delimiter, true);
+ if (ns)
+ AddNewNamespace(ns);
+ PR_FREEIF(thisns);
+ }
+ PR_Free(prefixes);
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsIMAPNamespaceList::OutputToString(nsCString &string)
+{
+ nsresult rv = NS_OK;
+
+ return rv;
+}
+
+
+int nsIMAPNamespaceList::GetNumberOfNamespaces(EIMAPNamespaceType type)
+{
+ int nodeIndex = 0, count = 0;
+ for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--)
+ {
+ nsIMAPNamespace *nspace = m_NamespaceList.ElementAt(nodeIndex);
+ if (nspace->GetType() == type)
+ {
+ count++;
+ }
+ }
+ return count;
+}
+
+int nsIMAPNamespaceList::AddNewNamespace(nsIMAPNamespace *ns)
+{
+ // If the namespace is from the NAMESPACE response, then we should see if there
+ // are any namespaces previously set by the preferences, or the default namespace. If so, remove these.
+
+ if (!ns->GetIsNamespaceFromPrefs())
+ {
+ int nodeIndex;
+ // iterate backwards because we delete elements
+ for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--)
+ {
+ nsIMAPNamespace *nspace = m_NamespaceList.ElementAt(nodeIndex);
+ // if we find existing namespace(s) that matches the
+ // new one, we'll just remove the old ones and let the
+ // new one get added when we've finished checking for
+ // matching namespaces or namespaces that came from prefs.
+ if (nspace &&
+ (nspace->GetIsNamespaceFromPrefs() ||
+ (!PL_strcmp(ns->GetPrefix(), nspace->GetPrefix()) &&
+ ns->GetType() == nspace->GetType() &&
+ ns->GetDelimiter() == nspace->GetDelimiter())))
+ {
+ m_NamespaceList.RemoveElementAt(nodeIndex);
+ delete nspace;
+ }
+ }
+ }
+
+ // Add the new namespace to the list. This must come after the removing code,
+ // or else we could never add the initial kDefaultNamespace type to the list.
+ m_NamespaceList.AppendElement(ns);
+
+ return 0;
+}
+
+
+// chrisf - later, fix this to know the real concept of "default" namespace of a given type
+nsIMAPNamespace *nsIMAPNamespaceList::GetDefaultNamespaceOfType(EIMAPNamespaceType type)
+{
+ nsIMAPNamespace *rv = 0, *firstOfType = 0;
+
+ int nodeIndex, count = m_NamespaceList.Length();
+ for (nodeIndex= 0; nodeIndex < count && !rv; nodeIndex++)
+ {
+ nsIMAPNamespace *ns = m_NamespaceList.ElementAt(nodeIndex);
+ if (ns->GetType() == type)
+ {
+ if (!firstOfType)
+ firstOfType = ns;
+ if (!(*(ns->GetPrefix())))
+ {
+ // This namespace's prefix is ""
+ // Therefore it is the default
+ rv = ns;
+ }
+ }
+ }
+ if (!rv)
+ rv = firstOfType;
+ return rv;
+}
+
+nsIMAPNamespaceList::~nsIMAPNamespaceList()
+{
+ ClearNamespaces(true, true, true);
+}
+
+// ClearNamespaces removes and deletes the namespaces specified, and if there are no namespaces left,
+void nsIMAPNamespaceList::ClearNamespaces(bool deleteFromPrefsNamespaces, bool deleteServerAdvertisedNamespaces, bool reallyDelete)
+{
+ int nodeIndex;
+
+ // iterate backwards because we delete elements
+ for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--)
+ {
+ nsIMAPNamespace *ns = m_NamespaceList.ElementAt(nodeIndex);
+ if (ns->GetIsNamespaceFromPrefs())
+ {
+ if (deleteFromPrefsNamespaces)
+ {
+ m_NamespaceList.RemoveElementAt(nodeIndex);
+ if (reallyDelete)
+ delete ns;
+ }
+ }
+ else if (deleteServerAdvertisedNamespaces)
+ {
+ m_NamespaceList.RemoveElementAt(nodeIndex);
+ if (reallyDelete)
+ delete ns;
+ }
+ }
+}
+
+nsIMAPNamespace *nsIMAPNamespaceList::GetNamespaceNumber(int nodeIndex)
+{
+ NS_ASSERTION(nodeIndex >= 0 && nodeIndex < GetNumberOfNamespaces(), "invalid IMAP namespace node index");
+ if (nodeIndex < 0) nodeIndex = 0;
+
+ // XXX really could be just ElementAt; that's why we have the assertion
+ return m_NamespaceList.SafeElementAt(nodeIndex);
+}
+
+nsIMAPNamespace *nsIMAPNamespaceList::GetNamespaceNumber(int nodeIndex, EIMAPNamespaceType type)
+{
+ int nodeCount, count = 0;
+ for (nodeCount = m_NamespaceList.Length() - 1; nodeCount >= 0; nodeCount--)
+ {
+ nsIMAPNamespace *nspace = m_NamespaceList.ElementAt(nodeCount);
+ if (nspace->GetType() == type)
+ {
+ count++;
+ if (count == nodeIndex)
+ return nspace;
+ }
+ }
+ return nullptr;
+}
+
+nsIMAPNamespace *nsIMAPNamespaceList::GetNamespaceForMailbox(const char *boxname)
+{
+ // We want to find the LONGEST substring that matches the beginning of this mailbox's path.
+ // This accounts for nested namespaces (i.e. "Public/" and "Public/Users/")
+
+ // Also, we want to match the namespace's mailbox to that namespace also:
+ // The Personal box will match the Personal/ namespace, etc.
+
+ // these lists shouldn't be too long (99% chance there won't be more than 3 or 4)
+ // so just do a linear search
+
+ int lengthMatched = -1;
+ int currentMatchedLength = -1;
+ nsIMAPNamespace *rv = nullptr;
+ int nodeIndex = 0;
+
+ if (!PL_strcasecmp(boxname, "INBOX"))
+ return GetDefaultNamespaceOfType(kPersonalNamespace);
+
+ for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--)
+ {
+ nsIMAPNamespace *nspace = m_NamespaceList.ElementAt(nodeIndex);
+ currentMatchedLength = nspace->MailboxMatchesNamespace(boxname);
+ if (currentMatchedLength > lengthMatched)
+ {
+ rv = nspace;
+ lengthMatched = currentMatchedLength;
+ }
+ }
+
+ return rv;
+}
+
+#define SERIALIZER_SEPARATORS ","
+
+/**
+ * If len is one, copies the first element of prefixes into serializedNamespaces.
+ * If len > 1, copies len strings from prefixes into serializedNamespaces
+ * as a comma-separated list of quoted strings.
+ */
+nsresult nsIMAPNamespaceList::SerializeNamespaces(char **prefixes, int len,
+ nsCString &serializedNamespaces)
+{
+ if (len <= 0)
+ return NS_OK;
+
+ if (len == 1)
+ {
+ serializedNamespaces.Assign(prefixes[0]);
+ return NS_OK;
+ }
+
+ for (int i = 0; i < len; i++)
+ {
+ if (i > 0)
+ serializedNamespaces.AppendLiteral(",");
+
+ serializedNamespaces.AppendLiteral("\"");
+ serializedNamespaces.Append(prefixes[i]);
+ serializedNamespaces.AppendLiteral("\"");
+ }
+ return NS_OK;
+}
+
+/* str is the string which needs to be unserialized.
+ If prefixes is NULL, simply returns the number of namespaces in str. (len is ignored)
+ If prefixes is not NULL, it should be an array of length len which is to be filled in
+ with newly-allocated string. Returns the number of strings filled in.
+*/
+int nsIMAPNamespaceList::UnserializeNamespaces(const char *str, char **prefixes, int len)
+{
+ if (!str)
+ return 0;
+ if (!prefixes)
+ {
+ if (str[0] != '"')
+ return 1;
+ else
+ {
+ int count = 0;
+ char *ourstr = PL_strdup(str);
+ char *origOurStr = ourstr;
+ if (ourstr)
+ {
+ char *token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr );
+ while (token != nullptr)
+ {
+ token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr );
+ count++;
+ }
+ PR_Free(origOurStr);
+ }
+ return count;
+ }
+ }
+ else
+ {
+ if ((str[0] != '"') && (len >= 1))
+ {
+ prefixes[0] = PL_strdup(str);
+ return 1;
+ }
+ else
+ {
+ int count = 0;
+ char *ourstr = PL_strdup(str);
+ char *origOurStr = ourstr;
+ if (ourstr)
+ {
+ char *token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr );
+ while ((count < len) && (token != nullptr))
+ {
+
+ char *current = PL_strdup(token), *where = current;
+ if (where[0] == '"')
+ where++;
+ if (where[PL_strlen(where)-1] == '"')
+ where[PL_strlen(where)-1] = 0;
+ prefixes[count] = PL_strdup(where);
+ PR_FREEIF(current);
+ token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr );
+ count++;
+ }
+ PR_Free(origOurStr);
+ }
+ return count;
+ }
+ }
+}
+
+
+
+
+char *nsIMAPNamespaceList::AllocateCanonicalFolderName(const char *onlineFolderName, char delimiter)
+{
+ char *canonicalPath = nullptr;
+ if (delimiter)
+ canonicalPath = nsImapUrl::ReplaceCharsInCopiedString(onlineFolderName, delimiter , '/');
+ else
+ canonicalPath = PL_strdup(onlineFolderName);
+
+ // eat any escape characters for escaped dir separators
+ if (canonicalPath)
+ {
+ char *currentEscapeSequence = strstr(canonicalPath, "\\/");
+ while (currentEscapeSequence)
+ {
+ strcpy(currentEscapeSequence, currentEscapeSequence+1);
+ currentEscapeSequence = strstr(currentEscapeSequence+1, "\\/");
+ }
+ }
+
+ return canonicalPath;
+}
+
+
+
+/*
+ GetFolderNameWithoutNamespace takes as input a folder name
+ in canonical form, and the namespace for the given folder. It returns an allocated
+ string of the folder's path with the namespace string stripped out. For instance,
+ when passed the folder Folders/a/b where the namespace is "Folders/", it will return
+ "a/b". Similarly, if the folder name is "#news/comp/mail/imap" in canonical form,
+ with a real delimiter of "." and a namespace of "#news.", it will return "comp/mail/imap".
+ The return value is always in canonical form.
+*/
+char* nsIMAPNamespaceList::GetFolderNameWithoutNamespace(nsIMAPNamespace *namespaceForFolder, const char *canonicalFolderName)
+{
+ NS_ASSERTION(canonicalFolderName, "null folder name");
+#ifdef DEBUG
+ NS_ASSERTION(namespaceForFolder || !PL_strcasecmp(canonicalFolderName, "INBOX"), "need namespace or INBOX");
+#endif
+
+ char *retFolderName = nullptr;
+
+ if (!PL_strcasecmp(canonicalFolderName, "INBOX"))
+ return PL_strdup(canonicalFolderName);
+
+ // convert the canonical path to the online path
+ char *convertedFolderName = nsIMAPNamespaceList::AllocateServerFolderName(canonicalFolderName, namespaceForFolder->GetDelimiter());
+ if (convertedFolderName)
+ {
+ char *beginFolderPath = nullptr;
+ if (strlen(convertedFolderName) <= strlen(namespaceForFolder->GetPrefix()))
+ beginFolderPath = convertedFolderName;
+ else
+ beginFolderPath = convertedFolderName + strlen(namespaceForFolder->GetPrefix());
+ NS_ASSERTION(beginFolderPath, "empty folder path");
+ retFolderName = nsIMAPNamespaceList::AllocateCanonicalFolderName(beginFolderPath, namespaceForFolder->GetDelimiter());
+ PR_Free(convertedFolderName);
+ }
+
+ NS_ASSERTION(retFolderName, "returning null folder name");
+ return retFolderName;
+}
+
+
+nsIMAPNamespace* nsIMAPNamespaceList::GetNamespaceForFolder(const char *hostName,
+ const char *canonicalFolderName,
+ char delimiter)
+{
+ if (!hostName || !canonicalFolderName)
+ return nullptr;
+
+ nsIMAPNamespace *resultNamespace = nullptr;
+ nsresult rv;
+ char *convertedFolderName = nsIMAPNamespaceList::AllocateServerFolderName(canonicalFolderName, delimiter);
+
+ if (convertedFolderName)
+ {
+
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_FAILED(rv))
+ return nullptr;
+ hostSessionList->GetNamespaceForMailboxForHost(hostName, convertedFolderName, resultNamespace);
+ PR_Free(convertedFolderName);
+ }
+ else
+ {
+ NS_ASSERTION(false, "couldn't get converted folder name");
+ }
+
+ return resultNamespace;
+}
+
+/* static */
+char *nsIMAPNamespaceList::AllocateServerFolderName(const char *canonicalFolderName, char delimiter)
+{
+ if (delimiter)
+ return nsImapUrl::ReplaceCharsInCopiedString(canonicalFolderName, '/', delimiter);
+ else
+ return NS_strdup(canonicalFolderName);
+}
+
+/*
+ GetFolderOwnerNameFromPath takes as inputs a folder name
+ in canonical form, and a namespace for that folder.
+ The namespace MUST be of type kOtherUsersNamespace, hence the folder MUST be
+ owned by another user. This function extracts the folder owner's name from the
+ canonical name of the folder, and returns an allocated copy of that owner's name
+*/
+/* static */
+char *nsIMAPNamespaceList::GetFolderOwnerNameFromPath(nsIMAPNamespace *namespaceForFolder, const char *canonicalFolderName)
+{
+ if (!namespaceForFolder || !canonicalFolderName)
+ {
+ NS_ASSERTION(false,"null namespace or canonical folder name");
+ return nullptr;
+ }
+
+ char *rv = nullptr;
+
+ // convert the canonical path to the online path
+ char *convertedFolderName = AllocateServerFolderName(canonicalFolderName, namespaceForFolder->GetDelimiter());
+ if (convertedFolderName)
+ {
+#ifdef DEBUG
+ NS_ASSERTION(strlen(convertedFolderName) > strlen(namespaceForFolder->GetPrefix()), "server folder name invalid");
+#endif
+ if (strlen(convertedFolderName) > strlen(namespaceForFolder->GetPrefix()))
+ {
+ char *owner = convertedFolderName + strlen(namespaceForFolder->GetPrefix());
+ NS_ASSERTION(owner, "couldn't find folder owner");
+ char *nextDelimiter = strchr(owner, namespaceForFolder->GetDelimiter());
+ // if !nextDelimiter, then the path is of the form Shared/Users/chrisf (no subfolder)
+ if (nextDelimiter)
+ {
+ *nextDelimiter = 0;
+ }
+ rv = PL_strdup(owner);
+ }
+ PR_Free(convertedFolderName);
+ }
+ else
+ {
+ NS_ASSERTION(false, "couldn't allocate server folder name");
+ }
+
+ return rv;
+}
+
+/*
+GetFolderIsNamespace returns TRUE if the given folder is the folder representing
+a namespace.
+*/
+
+bool nsIMAPNamespaceList::GetFolderIsNamespace(const char *hostName,
+ const char *canonicalFolderName,
+ char delimiter,nsIMAPNamespace *namespaceForFolder)
+{
+ NS_ASSERTION(namespaceForFolder, "null namespace");
+
+ bool rv = false;
+
+ const char *prefix = namespaceForFolder->GetPrefix();
+ NS_ASSERTION(prefix, "namespace has no prefix");
+ if (!prefix || !*prefix) // empty namespace prefix
+ return false;
+
+ char *convertedFolderName = AllocateServerFolderName(canonicalFolderName, delimiter);
+ if (convertedFolderName)
+ {
+ bool lastCharIsDelimiter = (prefix[strlen(prefix) - 1] == delimiter);
+
+ if (lastCharIsDelimiter)
+ {
+ rv = ((strncmp(convertedFolderName, prefix, strlen(convertedFolderName)) == 0) &&
+ (strlen(convertedFolderName) == strlen(prefix) - 1));
+ }
+ else
+ {
+ rv = (strcmp(convertedFolderName, prefix) == 0);
+ }
+
+ PR_Free(convertedFolderName);
+ }
+ else
+ {
+ NS_ASSERTION(false, "couldn't allocate server folder name");
+ }
+
+ return rv;
+}
+
+/*
+ SuggestHierarchySeparatorForNamespace takes a namespace from libmsg
+ and a hierarchy delimiter. If the namespace has not been filled in from
+ online NAMESPACE command yet, it fills in the suggested delimiter to be
+ used from then on (until it is overridden by an online response).
+*/
+
+void nsIMAPNamespaceList::SuggestHierarchySeparatorForNamespace(nsIMAPNamespace *namespaceForFolder, char delimiterFromFolder)
+{
+ NS_ASSERTION(namespaceForFolder, "need namespace");
+ if (namespaceForFolder && !namespaceForFolder->GetIsDelimiterFilledIn())
+ namespaceForFolder->SetDelimiter(delimiterFromFolder, false);
+}
+
+
+/*
+ GenerateFullFolderNameWithDefaultNamespace takes a folder name in canonical form,
+ converts it to online form, allocates a string to contain the full online server name
+ including the namespace prefix of the default namespace of the given type, in the form:
+ PR_smprintf("%s%s", prefix, onlineServerName) if there is a NULL owner
+ PR_smprintf("%s%s%c%s", prefix, owner, delimiter, onlineServerName) if there is an owner
+ It then converts this back to canonical form and returns it (allocated) to libmsg.
+ It returns NULL if there is no namespace of the given type.
+ If nsUsed is not passed in as NULL, then *nsUsed is filled in and returned; it is the
+ namespace used for generating the folder name.
+*/
+char *nsIMAPNamespaceList::GenerateFullFolderNameWithDefaultNamespace(const char *hostName,
+ const char *canonicalFolderName,
+ const char *owner,
+ EIMAPNamespaceType nsType,
+ nsIMAPNamespace **nsUsed)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ nsIMAPNamespace *ns;
+ char *fullFolderName = nullptr;
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(hostName, nsType, ns);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ if (ns)
+ {
+ if (nsUsed)
+ *nsUsed = ns;
+ const char *prefix = ns->GetPrefix();
+ char *convertedFolderName = AllocateServerFolderName(canonicalFolderName, ns->GetDelimiter());
+ if (convertedFolderName)
+ {
+ char *convertedReturnName = nullptr;
+ if (owner)
+ {
+ convertedReturnName = PR_smprintf("%s%s%c%s", prefix, owner, ns->GetDelimiter(), convertedFolderName);
+ }
+ else
+ {
+ convertedReturnName = PR_smprintf("%s%s", prefix, convertedFolderName);
+ }
+
+ if (convertedReturnName)
+ {
+ fullFolderName = AllocateCanonicalFolderName(convertedReturnName, ns->GetDelimiter());
+ PR_Free(convertedReturnName);
+ }
+ PR_Free(convertedFolderName);
+ }
+ else
+ {
+ NS_ASSERTION(false, "couldn't allocate server folder name");
+ }
+ }
+ else
+ {
+ // Could not find other users namespace on the given host
+ NS_WARNING("couldn't find namespace for given host");
+ }
+ return (fullFolderName);
+}
+
diff --git a/mailnews/imap/src/nsIMAPNamespace.h b/mailnews/imap/src/nsIMAPNamespace.h
new file mode 100644
index 000000000..327f78416
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPNamespace.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsIMAPNamespace_H_
+#define _nsIMAPNamespace_H_
+
+#include "nsTArray.h"
+
+class nsIMAPNamespace
+{
+
+public:
+ nsIMAPNamespace(EIMAPNamespaceType type, const char *prefix, char delimiter, bool from_prefs);
+
+ ~nsIMAPNamespace();
+
+ EIMAPNamespaceType GetType() { return m_namespaceType; }
+ const char * GetPrefix() { return m_prefix; }
+ char GetDelimiter() { return m_delimiter; }
+ void SetDelimiter(char delimiter, bool delimiterFilledIn);
+ bool GetIsDelimiterFilledIn() { return m_delimiterFilledIn; }
+ bool GetIsNamespaceFromPrefs() { return m_fromPrefs; }
+
+ // returns -1 if this box is not part of this namespace,
+ // or the length of the prefix if it is part of this namespace
+ int MailboxMatchesNamespace(const char *boxname);
+
+protected:
+ EIMAPNamespaceType m_namespaceType;
+ char *m_prefix;
+ char m_delimiter;
+ bool m_fromPrefs;
+ bool m_delimiterFilledIn;
+
+};
+
+
+// represents an array of namespaces for a given host
+class nsIMAPNamespaceList
+{
+public:
+ ~nsIMAPNamespaceList();
+
+ static nsIMAPNamespaceList *CreatensIMAPNamespaceList();
+
+ nsresult InitFromString(const char *nameSpaceString, EIMAPNamespaceType nstype);
+ nsresult OutputToString(nsCString &OutputString);
+ int UnserializeNamespaces(const char *str, char **prefixes, int len);
+ nsresult SerializeNamespaces(char **prefixes, int len, nsCString &serializedNamespace);
+
+ void ClearNamespaces(bool deleteFromPrefsNamespaces, bool deleteServerAdvertisedNamespaces, bool reallyDelete);
+ int GetNumberOfNamespaces();
+ int GetNumberOfNamespaces(EIMAPNamespaceType);
+ nsIMAPNamespace *GetNamespaceNumber(int nodeIndex);
+ nsIMAPNamespace *GetNamespaceNumber(int nodeIndex, EIMAPNamespaceType);
+
+ nsIMAPNamespace *GetDefaultNamespaceOfType(EIMAPNamespaceType type);
+ int AddNewNamespace(nsIMAPNamespace *ns);
+ nsIMAPNamespace *GetNamespaceForMailbox(const char *boxname);
+ static nsIMAPNamespace* GetNamespaceForFolder(const char *hostName,
+ const char *canonicalFolderName,
+ char delimiter);
+ static bool GetFolderIsNamespace(const char *hostName,
+ const char *canonicalFolderName,
+ char delimiter,nsIMAPNamespace *namespaceForFolder);
+ static char* GetFolderNameWithoutNamespace(nsIMAPNamespace *namespaceForFolder, const char *canonicalFolderName);
+ static char *AllocateServerFolderName(const char *canonicalFolderName, char delimiter);
+ static char *GetFolderOwnerNameFromPath(nsIMAPNamespace *namespaceForFolder, const char *canonicalFolderName);
+ static char *AllocateCanonicalFolderName(const char *onlineFolderName, char delimiter);
+ static void SuggestHierarchySeparatorForNamespace(nsIMAPNamespace *namespaceForFolder, char delimiterFromFolder);
+ static char *GenerateFullFolderNameWithDefaultNamespace(const char *hostName,
+ const char *canonicalFolderName,
+ const char *owner,
+ EIMAPNamespaceType nsType,
+ nsIMAPNamespace **nsUsed);
+
+protected:
+ nsIMAPNamespaceList(); // use CreatensIMAPNamespaceList to create one
+
+ nsTArray<nsIMAPNamespace*> m_NamespaceList;
+
+};
+
+
+#endif
diff --git a/mailnews/imap/src/nsImapCore.h b/mailnews/imap/src/nsImapCore.h
new file mode 100644
index 000000000..0eac33663
--- /dev/null
+++ b/mailnews/imap/src/nsImapCore.h
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsImapCore_H_
+#define _nsImapCore_H_
+
+#include "MailNewsTypes.h"
+#include "nsStringGlue.h"
+#include "nsIMailboxSpec.h"
+#include "nsIImapFlagAndUidState.h"
+
+class nsIMAPNamespace;
+class nsImapProtocol;
+class nsImapFlagAndUidState;
+
+/* imap message flags */
+typedef uint16_t imapMessageFlagsType;
+
+/* used for communication between imap thread and event sinks */
+#define kNoFlags 0x00 /* RFC flags */
+#define kMarked 0x01
+#define kUnmarked 0x02
+#define kNoinferiors 0x04
+#define kNoselect 0x08
+#define kImapTrash 0x10 /* Navigator flag */
+#define kJustExpunged 0x20 /* This update is a post expunge url update. */
+#define kPersonalMailbox 0x40 /* this mailbox is in the personal namespace */
+#define kPublicMailbox 0x80 /* this mailbox is in the public namespace */
+#define kOtherUsersMailbox 0x100 /* this mailbox is in the other users' namespace */
+#define kNameSpace 0x200 /* this mailbox IS a namespace */
+#define kNewlyCreatedFolder 0x400 /* this folder was just created */
+#define kImapDrafts 0x800 /* XLIST says this is the drafts folder */
+#define kImapSpam 0x1000 /* XLIST says this is the spam folder */
+#define kImapSent 0x2000 /* XLIST says this is the sent folder */
+#define kImapInbox 0x4000 /* XLIST says this is the INBOX folder */
+#define kImapAllMail 0x8000 /* XLIST says this is AllMail (GMail) */
+#define kImapXListTrash 0x10000 /* XLIST says this is the trash */
+#define kNonExistent 0x20000 /* RFC 5258, LIST-EXTENDED */
+#define kSubscribed 0x40000 /* RFC 5258, LIST-EXTENDED */
+#define kRemote 0x80000 /* RFC 5258, LIST-EXTENDED */
+#define kHasChildren 0x100000 /* RFC 5258, LIST-EXTENDED */
+#define kHasNoChildren 0x200000 /* RFC 5258, LIST-EXTENDED */
+#define kImapArchive 0x400000 /* RFC 5258, LIST-EXTENDED */
+
+/* flags for individual messages */
+/* currently the ui only offers \Seen and \Flagged */
+#define kNoImapMsgFlag 0x0000
+#define kImapMsgSeenFlag 0x0001
+#define kImapMsgAnsweredFlag 0x0002
+#define kImapMsgFlaggedFlag 0x0004
+#define kImapMsgDeletedFlag 0x0008
+#define kImapMsgDraftFlag 0x0010
+#define kImapMsgRecentFlag 0x0020
+#define kImapMsgForwardedFlag 0x0040 /* Not always supported, check mailbox folder */
+#define kImapMsgMDNSentFlag 0x0080 /* Not always supported. check mailbox folder */
+#define kImapMsgCustomKeywordFlag 0x0100 /* this msg has a custom keyword */
+#define kImapMsgLabelFlags 0x0E00 /* supports 5 labels only supported if the folder supports keywords */
+#define kImapMsgSupportMDNSentFlag 0x2000
+#define kImapMsgSupportForwardedFlag 0x4000
+/**
+ * We use a separate xlist trash flag so we can prefer the GMail trash
+ * over an existing Trash folder we may have created.
+ */
+#define kImapMsgSupportUserFlag 0x8000
+/* This seems to be the most cost effective way of
+* piggying back the server support user flag info.
+*/
+
+/* if a url creator does not know the hierarchyDelimiter, use this */
+#define kOnlineHierarchySeparatorUnknown '^'
+#define kOnlineHierarchySeparatorNil '|'
+
+#define IMAP_URL_TOKEN_SEPARATOR ">"
+#define kUidUnknown -1
+// Special initial value meaning ACLs need to be loaded from DB.
+#define kAclInvalid ((uint32_t) -1)
+
+// this has to do with Mime Parts on Demand. It used to live in net.h
+// I'm not sure where this will live, but here is OK temporarily
+typedef enum {
+ IMAP_CONTENT_NOT_MODIFIED = 0,
+ IMAP_CONTENT_MODIFIED_VIEW_INLINE,
+ IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS,
+ IMAP_CONTENT_FORCE_CONTENT_NOT_MODIFIED
+} IMAP_ContentModifiedType;
+
+// I think this should really go in an imap.h equivalent file
+typedef enum {
+ kPersonalNamespace = 0,
+ kOtherUsersNamespace,
+ kPublicNamespace,
+ kDefaultNamespace,
+ kUnknownNamespace
+} EIMAPNamespaceType;
+
+
+/**
+ * IMAP server feature, mostly CAPABILITY responses
+ *
+ * one of the cap flags below
+ */
+typedef uint64_t eIMAPCapabilityFlag;
+/**
+ * IMAP server features, mostly CAPABILITY responses
+ *
+ * any set of the cap flags below, i.e.
+ * i.e. 0, 1 or more |eIMAPCapabilityFlag|.
+ */
+typedef uint64_t eIMAPCapabilityFlags;
+
+const eIMAPCapabilityFlag kCapabilityUndefined = 0x00000000;
+const eIMAPCapabilityFlag kCapabilityDefined = 0x00000001;
+const eIMAPCapabilityFlag kHasAuthLoginCapability = 0x00000002; /* AUTH LOGIN (not the same as kHasAuthOldLoginCapability) */
+const eIMAPCapabilityFlag kHasAuthOldLoginCapability = 0x00000004; /* original IMAP login method */
+const eIMAPCapabilityFlag kHasXSenderCapability = 0x00000008;
+const eIMAPCapabilityFlag kIMAP4Capability = 0x00000010; /* RFC1734 */
+const eIMAPCapabilityFlag kIMAP4rev1Capability = 0x00000020; /* RFC2060 */
+const eIMAPCapabilityFlag kIMAP4other = 0x00000040; /* future rev?? */
+const eIMAPCapabilityFlag kNoHierarchyRename = 0x00000080; /* no hierarchy rename */
+const eIMAPCapabilityFlag kACLCapability = 0x00000100; /* ACL extension */
+const eIMAPCapabilityFlag kNamespaceCapability = 0x00000200; /* IMAP4 Namespace Extension */
+const eIMAPCapabilityFlag kHasIDCapability = 0x00000400; /* client user agent id extension */
+const eIMAPCapabilityFlag kXServerInfoCapability = 0x00000800; /* XSERVERINFO extension for admin urls */
+const eIMAPCapabilityFlag kHasAuthPlainCapability = 0x00001000; /* new form of auth plain base64 login */
+const eIMAPCapabilityFlag kUidplusCapability = 0x00002000; /* RFC 2359 UIDPLUS extension */
+const eIMAPCapabilityFlag kLiteralPlusCapability = 0x00004000; /* RFC 2088 LITERAL+ extension */
+const eIMAPCapabilityFlag kAOLImapCapability = 0x00008000; /* aol imap extensions */
+const eIMAPCapabilityFlag kHasLanguageCapability = 0x00010000; /* language extensions */
+const eIMAPCapabilityFlag kHasCRAMCapability = 0x00020000; /* CRAM auth extension */
+const eIMAPCapabilityFlag kQuotaCapability = 0x00040000; /* RFC 2087 quota extension */
+const eIMAPCapabilityFlag kHasIdleCapability = 0x00080000; /* RFC 2177 idle extension */
+const eIMAPCapabilityFlag kHasAuthNTLMCapability = 0x00100000; /* AUTH NTLM extension */
+const eIMAPCapabilityFlag kHasAuthMSNCapability = 0x00200000; /* AUTH MSN extension */
+const eIMAPCapabilityFlag kHasStartTLSCapability =0x00400000; /* STARTTLS support */
+const eIMAPCapabilityFlag kHasAuthNoneCapability = 0x00800000; /* needs no login */
+const eIMAPCapabilityFlag kHasAuthGssApiCapability = 0x01000000; /* GSSAPI AUTH */
+const eIMAPCapabilityFlag kHasCondStoreCapability = 0x02000000; /* RFC 3551 CondStore extension */
+const eIMAPCapabilityFlag kHasEnableCapability = 0x04000000; /* RFC 5161 ENABLE extension */
+const eIMAPCapabilityFlag kHasXListCapability = 0x08000000; /* XLIST extension */
+const eIMAPCapabilityFlag kHasCompressDeflateCapability = 0x10000000; /* RFC 4978 COMPRESS extension */
+const eIMAPCapabilityFlag kHasAuthExternalCapability = 0x20000000; /* RFC 2222 SASL AUTH EXTERNAL */
+const eIMAPCapabilityFlag kHasMoveCapability = 0x40000000; /* Proposed MOVE RFC */
+const eIMAPCapabilityFlag kHasHighestModSeqCapability = 0x80000000; /* Subset of RFC 3551 */
+// above are 32bit; below start the uint64_t bits 33-64
+const eIMAPCapabilityFlag kHasListExtendedCapability = 0x100000000LL; /* RFC 5258 */
+const eIMAPCapabilityFlag kHasSpecialUseCapability = 0x200000000LL; /* RFC 6154: Sent, Draft etc. folders */
+const eIMAPCapabilityFlag kGmailImapCapability = 0x400000000LL; /* X-GM-EXT-1 capability extension for gmail */
+#ifdef MOZ_MAILNEWS_OAUTH2
+const eIMAPCapabilityFlag kHasXOAuth2Capability = 0x800000000LL; /* AUTH XOAUTH2 extension */
+#endif
+
+
+// this used to be part of the connection object class - maybe we should move it into
+// something similar
+typedef enum {
+ kEveryThingRFC822,
+ kEveryThingRFC822Peek,
+ kHeadersRFC822andUid,
+ kUid,
+ kFlags,
+ kRFC822Size,
+ kRFC822HeadersOnly,
+ kMIMEPart,
+ kMIMEHeader,
+ kBodyStart
+} nsIMAPeFetchFields;
+
+typedef struct _utf_name_struct {
+ bool toUtf7Imap;
+ unsigned char *sourceString;
+ unsigned char *convertedString;
+} utf_name_struct;
+
+typedef struct _ProgressInfo {
+ char16_t *message;
+ int32_t currentProgress;
+ int32_t maxProgress;
+} ProgressInfo;
+
+typedef enum {
+ eContinue,
+ eContinueNew,
+ eListMyChildren,
+ eNewServerDirectory,
+ eCancelled
+} EMailboxDiscoverStatus;
+
+#endif
diff --git a/mailnews/imap/src/nsImapFlagAndUidState.cpp b/mailnews/imap/src/nsImapFlagAndUidState.cpp
new file mode 100644
index 000000000..deae831db
--- /dev/null
+++ b/mailnews/imap/src/nsImapFlagAndUidState.cpp
@@ -0,0 +1,321 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // for pre-compiled headers
+
+#include "nsImapCore.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsMsgUtils.h"
+#include "prcmon.h"
+#include "nspr.h"
+
+NS_IMPL_ISUPPORTS(nsImapFlagAndUidState, nsIImapFlagAndUidState)
+
+using namespace mozilla;
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetNumberOfMessages(int32_t *result)
+{
+ if (!result)
+ return NS_ERROR_NULL_POINTER;
+ *result = fUids.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetUidOfMessage(int32_t zeroBasedIndex, uint32_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ PR_CEnterMonitor(this);
+ *aResult = fUids.SafeElementAt(zeroBasedIndex, nsMsgKey_None);
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetMessageFlags(int32_t zeroBasedIndex, uint16_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = fFlags.SafeElementAt(zeroBasedIndex, kNoImapMsgFlag);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::SetMessageFlags(int32_t zeroBasedIndex, unsigned short flags)
+{
+ if (zeroBasedIndex < (int32_t)fUids.Length())
+ fFlags[zeroBasedIndex] = flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetNumberOfRecentMessages(int32_t *result)
+{
+ if (!result)
+ return NS_ERROR_NULL_POINTER;
+
+ PR_CEnterMonitor(this);
+ uint32_t counter = 0;
+ int32_t numUnseenMessages = 0;
+
+ for (counter = 0; counter < fUids.Length(); counter++)
+ {
+ if (fFlags[counter] & kImapMsgRecentFlag)
+ numUnseenMessages++;
+ }
+ PR_CExitMonitor(this);
+
+ *result = numUnseenMessages;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetPartialUIDFetch(bool *aPartialUIDFetch)
+{
+ NS_ENSURE_ARG_POINTER(aPartialUIDFetch);
+ *aPartialUIDFetch = fPartialUIDFetch;
+ return NS_OK;
+}
+
+/* amount to expand for imap entry flags when we need more */
+
+nsImapFlagAndUidState::nsImapFlagAndUidState(int32_t numberOfMessages)
+ : fUids(numberOfMessages),
+ fFlags(numberOfMessages),
+ m_customFlagsHash(10),
+ m_customAttributesHash(10),
+ mLock("nsImapFlagAndUidState.mLock")
+{
+ fSupportedUserFlags = 0;
+ fNumberDeleted = 0;
+ fPartialUIDFetch = true;
+}
+
+nsImapFlagAndUidState::~nsImapFlagAndUidState()
+{
+}
+
+NS_IMETHODIMP
+nsImapFlagAndUidState::OrSupportedUserFlags(uint16_t flags)
+{
+ fSupportedUserFlags |= flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapFlagAndUidState::GetSupportedUserFlags(uint16_t *aFlags)
+{
+ NS_ENSURE_ARG_POINTER(aFlags);
+ *aFlags = fSupportedUserFlags;
+ return NS_OK;
+}
+
+// we need to reset our flags, (re-read all) but chances are the memory allocation needed will be
+// very close to what we were already using
+
+NS_IMETHODIMP nsImapFlagAndUidState::Reset()
+{
+ PR_CEnterMonitor(this);
+ fNumberDeleted = 0;
+ m_customFlagsHash.Clear();
+ fUids.Clear();
+ fFlags.Clear();
+ fPartialUIDFetch = true;
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+
+// Remove (expunge) a message from our array, since now it is gone for good
+
+NS_IMETHODIMP nsImapFlagAndUidState::ExpungeByIndex(uint32_t msgIndex)
+{
+ // protect ourselves in case the server gave us an index key of -1 or 0
+ if ((int32_t) msgIndex <= 0)
+ return NS_ERROR_INVALID_ARG;
+
+ if ((uint32_t) fUids.Length() < msgIndex)
+ return NS_ERROR_INVALID_ARG;
+
+ PR_CEnterMonitor(this);
+ msgIndex--; // msgIndex is 1-relative
+ if (fFlags[msgIndex] & kImapMsgDeletedFlag) // see if we already had counted this one as deleted
+ fNumberDeleted--;
+ fUids.RemoveElementAt(msgIndex);
+ fFlags.RemoveElementAt(msgIndex);
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+
+// adds to sorted list, protects against duplicates and going past array bounds.
+NS_IMETHODIMP nsImapFlagAndUidState::AddUidFlagPair(uint32_t uid, imapMessageFlagsType flags, uint32_t zeroBasedIndex)
+{
+ if (uid == nsMsgKey_None) // ignore uid of -1
+ return NS_OK;
+ // check for potential overflow in buffer size for uid array
+ if (zeroBasedIndex > 0x3FFFFFFF)
+ return NS_ERROR_INVALID_ARG;
+ PR_CEnterMonitor(this);
+ // make sure there is room for this pair
+ if (zeroBasedIndex >= fUids.Length())
+ {
+ int32_t sizeToGrowBy = zeroBasedIndex - fUids.Length() + 1;
+ fUids.InsertElementsAt(fUids.Length(), sizeToGrowBy, 0);
+ fFlags.InsertElementsAt(fFlags.Length(), sizeToGrowBy, 0);
+ }
+
+ fUids[zeroBasedIndex] = uid;
+ fFlags[zeroBasedIndex] = flags;
+ if (flags & kImapMsgDeletedFlag)
+ fNumberDeleted++;
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetNumberOfDeletedMessages(int32_t *numDeletedMessages)
+{
+ NS_ENSURE_ARG_POINTER(numDeletedMessages);
+ *numDeletedMessages = NumberOfDeletedMessages();
+ return NS_OK;
+}
+
+int32_t nsImapFlagAndUidState::NumberOfDeletedMessages()
+{
+ return fNumberDeleted;
+}
+
+// since the uids are sorted, start from the back (rb)
+
+uint32_t nsImapFlagAndUidState::GetHighestNonDeletedUID()
+{
+ uint32_t msgIndex = fUids.Length();
+ do
+ {
+ if (msgIndex <= 0)
+ return(0);
+ msgIndex--;
+ if (fUids[msgIndex] && !(fFlags[msgIndex] & kImapMsgDeletedFlag))
+ return fUids[msgIndex];
+ }
+ while (msgIndex > 0);
+ return 0;
+}
+
+
+// Has the user read the last message here ? Used when we first open the inbox to see if there
+// really is new mail there.
+
+bool nsImapFlagAndUidState::IsLastMessageUnseen()
+{
+ uint32_t msgIndex = fUids.Length();
+
+ if (msgIndex <= 0)
+ return false;
+ msgIndex--;
+ // if last message is deleted, it was probably filtered the last time around
+ if (fUids[msgIndex] && (fFlags[msgIndex] & (kImapMsgSeenFlag | kImapMsgDeletedFlag)))
+ return false;
+ return true;
+}
+
+// find a message flag given a key with non-recursive binary search, since some folders
+// may have thousand of messages, once we find the key set its index, or the index of
+// where the key should be inserted
+
+imapMessageFlagsType nsImapFlagAndUidState::GetMessageFlagsFromUID(uint32_t uid, bool *foundIt, int32_t *ndx)
+{
+ PR_CEnterMonitor(this);
+ *ndx = (int32_t) fUids.IndexOfFirstElementGt(uid) - 1;
+ *foundIt = *ndx >= 0 && fUids[*ndx] == uid;
+ imapMessageFlagsType retFlags = (*foundIt) ? fFlags[*ndx] : kNoImapMsgFlag;
+ PR_CExitMonitor(this);
+ return retFlags;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::AddUidCustomFlagPair(uint32_t uid, const char *customFlag)
+{
+ if (!customFlag)
+ return NS_OK;
+
+ MutexAutoLock mon(mLock);
+ nsCString ourCustomFlags;
+ nsCString oldValue;
+ if (m_customFlagsHash.Get(uid, &oldValue))
+ {
+ // We'll store multiple keys as space-delimited since space is not
+ // a valid character in a keyword. First, we need to look for the
+ // customFlag in the existing flags;
+ nsDependentCString customFlagString(customFlag);
+ int32_t existingCustomFlagPos = oldValue.Find(customFlagString);
+ uint32_t customFlagLen = customFlagString.Length();
+ while (existingCustomFlagPos != kNotFound)
+ {
+ // if existing flags ends with this exact flag, or flag + ' '
+ // and the flag is at the beginning of the string or there is ' ' + flag
+ // then we have this flag already;
+ if (((oldValue.Length() == existingCustomFlagPos + customFlagLen) ||
+ (oldValue.CharAt(existingCustomFlagPos + customFlagLen) == ' ')) &&
+ ((existingCustomFlagPos == 0) ||
+ (oldValue.CharAt(existingCustomFlagPos - 1) == ' ')))
+ return NS_OK;
+ // else, advance to next flag
+ existingCustomFlagPos = MsgFind(oldValue, customFlagString, false, existingCustomFlagPos + customFlagLen);
+ }
+ ourCustomFlags.Assign(oldValue);
+ ourCustomFlags.AppendLiteral(" ");
+ ourCustomFlags.Append(customFlag);
+ m_customFlagsHash.Remove(uid);
+ }
+ else
+ {
+ ourCustomFlags.Assign(customFlag);
+ }
+ m_customFlagsHash.Put(uid, ourCustomFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetCustomFlags(uint32_t uid, char **customFlags)
+{
+ MutexAutoLock mon(mLock);
+ nsCString value;
+ if (m_customFlagsHash.Get(uid, &value))
+ {
+ *customFlags = NS_strdup(value.get());
+ return (*customFlags) ? NS_OK : NS_ERROR_FAILURE;
+ }
+ *customFlags = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::ClearCustomFlags(uint32_t uid)
+{
+ MutexAutoLock mon(mLock);
+ m_customFlagsHash.Remove(uid);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::SetCustomAttribute(uint32_t aUid,
+ const nsACString &aCustomAttributeName,
+ const nsACString &aCustomAttributeValue)
+{
+ nsCString key;
+ key.AppendInt((int64_t)aUid);
+ key.Append(aCustomAttributeName);
+ nsCString value;
+ value.Assign(aCustomAttributeValue);
+ m_customAttributesHash.Put(key, value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetCustomAttribute(uint32_t aUid,
+ const nsACString &aCustomAttributeName,
+ nsACString &aCustomAttributeValue)
+{
+ nsCString key;
+ key.AppendInt((int64_t)aUid);
+ key.Append(aCustomAttributeName);
+ nsCString val;
+ m_customAttributesHash.Get(key, &val);
+ aCustomAttributeValue.Assign(val);
+ return NS_OK;
+}
diff --git a/mailnews/imap/src/nsImapFlagAndUidState.h b/mailnews/imap/src/nsImapFlagAndUidState.h
new file mode 100644
index 000000000..259bc016f
--- /dev/null
+++ b/mailnews/imap/src/nsImapFlagAndUidState.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImapFlagAndUidState_h___
+#define nsImapFlagAndUidState_h___
+
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIImapFlagAndUidState.h"
+#include "mozilla/Mutex.h"
+
+const int32_t kImapFlagAndUidStateSize = 100;
+
+#include "nsBaseHashtable.h"
+#include "nsDataHashtable.h"
+
+class nsImapFlagAndUidState : public nsIImapFlagAndUidState
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ explicit nsImapFlagAndUidState(int numberOfMessages);
+
+ NS_DECL_NSIIMAPFLAGANDUIDSTATE
+
+ int32_t NumberOfDeletedMessages();
+
+ imapMessageFlagsType GetMessageFlagsFromUID(uint32_t uid, bool *foundIt, int32_t *ndx);
+
+ bool IsLastMessageUnseen(void);
+ bool GetPartialUIDFetch() {return fPartialUIDFetch;}
+ void SetPartialUIDFetch(bool isPartial) {fPartialUIDFetch = isPartial;}
+ uint32_t GetHighestNonDeletedUID();
+ uint16_t GetSupportedUserFlags() { return fSupportedUserFlags; }
+
+private:
+ virtual ~nsImapFlagAndUidState();
+
+ nsTArray<nsMsgKey> fUids;
+ nsTArray<imapMessageFlagsType> fFlags;
+ // Hash table, mapping uids to extra flags
+ nsDataHashtable<nsUint32HashKey, nsCString> m_customFlagsHash;
+ // Hash table, mapping UID+customAttributeName to customAttributeValue.
+ nsDataHashtable<nsCStringHashKey, nsCString> m_customAttributesHash;
+ uint16_t fSupportedUserFlags;
+ int32_t fNumberDeleted;
+ bool fPartialUIDFetch;
+ mozilla::Mutex mLock;
+};
+
+
+
+
+#endif
diff --git a/mailnews/imap/src/nsImapIncomingServer.cpp b/mailnews/imap/src/nsImapIncomingServer.cpp
new file mode 100644
index 000000000..77a34985b
--- /dev/null
+++ b/mailnews/imap/src/nsImapIncomingServer.cpp
@@ -0,0 +1,3382 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsMsgImapCID.h"
+
+#include "netCore.h"
+#include "nsIMAPHostSessionList.h"
+#include "nsImapIncomingServer.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIdentity.h"
+#include "nsIImapUrl.h"
+#include "nsIUrlListener.h"
+#include "nsThreadUtils.h"
+#include "nsImapProtocol.h"
+#include "nsCOMPtr.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsMsgFolderFlags.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgWindow.h"
+#include "nsImapMailFolder.h"
+#include "nsImapUtils.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIImapService.h"
+#include "nsMsgI18N.h"
+#include "nsIImapMockChannel.h"
+// for the memory cache...
+#include "nsICacheEntry.h"
+#include "nsImapUrl.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMAPNamespace.h"
+#include "nsArrayUtils.h"
+#include "nsITimer.h"
+#include "nsMsgUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCRTGlue.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+#define PREF_TRASH_FOLDER_NAME "trash_folder_name"
+#define DEFAULT_TRASH_FOLDER_NAME "Trash"
+
+static NS_DEFINE_CID(kImapProtocolCID, NS_IMAPPROTOCOL_CID);
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+static NS_DEFINE_CID(kSubscribableServerCID, NS_SUBSCRIBABLESERVER_CID);
+static NS_DEFINE_CID(kCImapHostSessionListCID, NS_IIMAPHOSTSESSIONLIST_CID);
+
+NS_IMPL_ADDREF_INHERITED(nsImapIncomingServer, nsMsgIncomingServer)
+NS_IMPL_RELEASE_INHERITED(nsImapIncomingServer, nsMsgIncomingServer)
+
+NS_INTERFACE_MAP_BEGIN(nsImapIncomingServer)
+ NS_INTERFACE_MAP_ENTRY(nsIImapServerSink)
+ NS_INTERFACE_MAP_ENTRY(nsIImapIncomingServer)
+ NS_INTERFACE_MAP_ENTRY(nsISubscribableServer)
+ NS_INTERFACE_MAP_ENTRY(nsIUrlListener)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgIncomingServer)
+
+nsImapIncomingServer::nsImapIncomingServer()
+ : mLock("nsImapIncomingServer.mLock")
+{
+ m_capability = kCapabilityUndefined;
+ mDoingSubscribeDialog = false;
+ mDoingLsub = false;
+ m_canHaveFilters = true;
+ m_userAuthenticated = false;
+ m_shuttingDown = false;
+}
+
+nsImapIncomingServer::~nsImapIncomingServer()
+{
+ mozilla::DebugOnly<nsresult> rv = ClearInner();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "ClearInner failed");
+ CloseCachedConnections();
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetKey(const nsACString& aKey) // override nsMsgIncomingServer's implementation...
+{
+ nsMsgIncomingServer::SetKey(aKey);
+
+ // okay now that the key has been set, we need to add ourselves to the
+ // host session list...
+
+ // every time we create an imap incoming server, we need to add it to the
+ // host session list!!
+
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString key(aKey);
+ hostSession->AddHostToList(key.get(), this);
+ nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash; // default to trash
+ GetDeleteModel(&deleteModel);
+ hostSession->SetDeleteIsMoveToTrashForHost(key.get(), deleteModel == nsMsgImapDeleteModels::MoveToTrash);
+ hostSession->SetShowDeletedMessagesForHost(key.get(), deleteModel == nsMsgImapDeleteModels::IMAPDelete);
+
+ nsAutoCString onlineDir;
+ rv = GetServerDirectory(onlineDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!onlineDir.IsEmpty())
+ hostSession->SetOnlineDirForHost(key.get(), onlineDir.get());
+
+ nsCString personalNamespace;
+ nsCString publicNamespace;
+ nsCString otherUsersNamespace;
+
+ rv = GetPersonalNamespace(personalNamespace);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetPublicNamespace(publicNamespace);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetOtherUsersNamespace(otherUsersNamespace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (personalNamespace.IsEmpty() && publicNamespace.IsEmpty() && otherUsersNamespace.IsEmpty())
+ personalNamespace.AssignLiteral("\"\"");
+
+ hostSession->SetNamespaceFromPrefForHost(key.get(), personalNamespace.get(),
+ kPersonalNamespace);
+
+ if (!publicNamespace.IsEmpty())
+ hostSession->SetNamespaceFromPrefForHost(key.get(), publicNamespace.get(),
+ kPublicNamespace);
+
+ if (!otherUsersNamespace.IsEmpty())
+ hostSession->SetNamespaceFromPrefForHost(key.get(), otherUsersNamespace.get(),
+ kOtherUsersNamespace);
+ return rv;
+}
+
+// construct the pretty name to show to the user if they haven't
+// specified one. This should be overridden for news and mail.
+NS_IMETHODIMP
+nsImapIncomingServer::GetConstructedPrettyName(nsAString& retval)
+{
+ nsAutoCString username;
+ nsAutoCString hostName;
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = accountManager->GetFirstIdentityForServer(this, getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoString emailAddress;
+
+ if (NS_SUCCEEDED(rv) && identity)
+ {
+ nsCString identityEmailAddress;
+ identity->GetEmail(identityEmailAddress);
+ CopyASCIItoUTF16(identityEmailAddress, emailAddress);
+ }
+ else
+ {
+ rv = GetRealUsername(username);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = GetRealHostName(hostName);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!username.IsEmpty() && !hostName.IsEmpty()) {
+ CopyASCIItoUTF16(username, emailAddress);
+ emailAddress.Append('@');
+ emailAddress.Append(NS_ConvertASCIItoUTF16(hostName));
+ }
+ }
+
+ return GetFormattedStringFromName(emailAddress, "imapDefaultAccountName", retval);
+}
+
+
+NS_IMETHODIMP nsImapIncomingServer::GetLocalStoreType(nsACString& type)
+{
+ type.AssignLiteral("imap");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetLocalDatabaseType(nsACString& type)
+{
+ type.AssignLiteral("imap");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetServerDirectory(nsACString& serverDirectory)
+{
+ return GetCharValue("server_sub_directory", serverDirectory);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetServerDirectory(const nsACString& serverDirectory)
+{
+ nsCString serverKey;
+ nsresult rv = GetKey(serverKey);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_SUCCEEDED(rv))
+ hostSession->SetOnlineDirForHost(serverKey.get(), PromiseFlatCString(serverDirectory).get());
+ }
+ return SetCharValue("server_sub_directory", serverDirectory);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetOverrideNamespaces(bool *bVal)
+{
+ return GetBoolValue("override_namespaces", bVal);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetOverrideNamespaces(bool bVal)
+{
+ nsCString serverKey;
+ GetKey(serverKey);
+ if (!serverKey.IsEmpty())
+ {
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_SUCCEEDED(rv))
+ hostSession->SetNamespacesOverridableForHost(serverKey.get(), bVal);
+ }
+ return SetBoolValue("override_namespaces", bVal);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetUsingSubscription(bool *bVal)
+{
+ return GetBoolValue("using_subscription", bVal);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetUsingSubscription(bool bVal)
+{
+ nsCString serverKey;
+ GetKey(serverKey);
+ if (!serverKey.IsEmpty())
+ {
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_SUCCEEDED(rv))
+ hostSession->SetHostIsUsingSubscription(serverKey.get(), bVal);
+ }
+ return SetBoolValue("using_subscription", bVal);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetMaximumConnectionsNumber(int32_t *aMaxConnections)
+{
+ NS_ENSURE_ARG_POINTER(aMaxConnections);
+
+ nsresult rv = GetIntValue("max_cached_connections", aMaxConnections);
+ // Get our maximum connection count. We need at least 1. If the value is 0,
+ // we use the default of 5. If it's negative, we treat that as 1.
+ if (NS_SUCCEEDED(rv) && *aMaxConnections > 0)
+ return NS_OK;
+
+ *aMaxConnections = (NS_FAILED(rv) || (*aMaxConnections == 0)) ? 5 : 1;
+ (void)SetMaximumConnectionsNumber(*aMaxConnections);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetMaximumConnectionsNumber(int32_t aMaxConnections)
+{
+ return SetIntValue("max_cached_connections", aMaxConnections);
+}
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, ForceSelect,
+ "force_select")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, DualUseFolders,
+ "dual_use_folders")
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, AdminUrl,
+ "admin_url")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, CleanupInboxOnExit,
+ "cleanup_inbox_on_exit")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, OfflineDownload,
+ "offline_download")
+
+NS_IMPL_SERVERPREF_INT(nsImapIncomingServer, EmptyTrashThreshhold,
+ "empty_trash_threshhold")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, DownloadBodiesOnGetNewMail,
+ "download_bodies_on_get_new_mail")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, AutoSyncOfflineStores,
+ "autosync_offline_stores")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, UseIdle,
+ "use_idle")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, CheckAllFoldersForNew,
+ "check_all_folders_for_new")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, UseCondStore,
+ "use_condstore")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, IsGMailServer,
+ "is_gmail")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, UseCompressDeflate,
+ "use_compress_deflate")
+
+NS_IMPL_SERVERPREF_INT(nsImapIncomingServer, AutoSyncMaxAgeDays,
+ "autosync_max_age_days")
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetShuttingDown(bool *retval)
+{
+ NS_ENSURE_ARG_POINTER(retval);
+ *retval = m_shuttingDown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetShuttingDown(bool val)
+{
+ m_shuttingDown = val;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetDeleteModel(int32_t *retval)
+{
+ NS_ENSURE_ARG(retval);
+ return GetIntValue("delete_model", retval);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetDeleteModel(int32_t ivalue)
+{
+ nsresult rv = SetIntValue("delete_model", ivalue);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ hostSession->SetDeleteIsMoveToTrashForHost(m_serverKey.get(), ivalue == nsMsgImapDeleteModels::MoveToTrash);
+ hostSession->SetShowDeletedMessagesForHost(m_serverKey.get(), ivalue == nsMsgImapDeleteModels::IMAPDelete);
+
+ nsAutoString trashFolderName;
+ nsresult rv = GetTrashFolderName(trashFolderName);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString trashFolderNameUtf7;
+ rv = CopyUTF16toMUTF7(trashFolderName, trashFolderNameUtf7);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ // XXX GetFolder only returns folders one level below root.
+ // trashFolderName is a leaf name. So this will not find INBOX.Trash
+ rv = GetFolder(trashFolderNameUtf7, getter_AddRefs(trashFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString trashURI;
+ trashFolder->GetURI(trashURI);
+ GetMsgFolderFromURI(trashFolder, trashURI, getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv) && trashFolder)
+ {
+ // If the trash folder is used, set the flag, otherwise clear it.
+ if (ivalue == nsMsgImapDeleteModels::MoveToTrash)
+ trashFolder->SetFlag(nsMsgFolderFlags::Trash);
+ else
+ trashFolder->ClearFlag(nsMsgFolderFlags::Trash);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMPL_SERVERPREF_INT(nsImapIncomingServer, TimeOutLimits,
+ "timeout")
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, ServerIDPref,
+ "serverIDResponse")
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, PersonalNamespace,
+ "namespace.personal")
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, PublicNamespace,
+ "namespace.public")
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, OtherUsersNamespace,
+ "namespace.other_users")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, FetchByChunks,
+ "fetch_by_chunks")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, MimePartsOnDemand,
+ "mime_parts_on_demand")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, SendID,
+ "send_client_info")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, CapabilityACL,
+ "cacheCapa.acl")
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, CapabilityQuota,
+ "cacheCapa.quota")
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetIsAOLServer(bool *aBool)
+{
+ NS_ENSURE_ARG_POINTER(aBool);
+ *aBool = ((m_capability & kAOLImapCapability) != 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetIsAOLServer(bool aBool)
+{
+ if (aBool)
+ m_capability |= kAOLImapCapability;
+ else
+ m_capability &= ~kAOLImapCapability;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::UpdateTrySTARTTLSPref(bool aStartTLSSucceeded)
+{
+ SetSocketType(aStartTLSSucceeded ? nsMsgSocketType::alwaysSTARTTLS :
+ nsMsgSocketType::plain);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetImapConnectionAndLoadUrl(nsIImapUrl* aImapUrl,
+ nsISupports* aConsumer)
+{
+ nsCOMPtr<nsIImapProtocol> aProtocol;
+
+ nsresult rv = GetImapConnection(aImapUrl, getter_AddRefs(aProtocol));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(aImapUrl, &rv);
+ if (aProtocol)
+ {
+ rv = aProtocol->LoadImapUrl(mailnewsurl, aConsumer);
+ // *** jt - in case of the time out situation or the connection gets
+ // terminated by some unforseen problems let's give it a second chance
+ // to run the url
+ if (NS_FAILED(rv) && rv != NS_ERROR_ILLEGAL_VALUE)
+ {
+ NS_ASSERTION(false, "shouldn't get an error loading url");
+ rv = aProtocol->LoadImapUrl(mailnewsurl, aConsumer);
+ }
+ }
+ else
+ { // unable to get an imap connection to run the url; add to the url
+ // queue
+ nsImapProtocol::LogImapUrl("queuing url", aImapUrl);
+ PR_CEnterMonitor(this);
+ m_urlQueue.AppendObject(aImapUrl);
+ m_urlConsumers.AppendElement(aConsumer);
+ NS_IF_ADDREF(aConsumer);
+ PR_CExitMonitor(this);
+ // let's try running it now - maybe the connection is free now.
+ bool urlRun;
+ rv = LoadNextQueuedUrl(nullptr, &urlRun);
+ }
+
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsImapIncomingServer::PrepareToRetryUrl(nsIImapUrl *aImapUrl, nsIImapMockChannel **aChannel)
+{
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ // maybe there's more we could do here, but this is all we need now.
+ return aImapUrl->GetMockChannel(aChannel);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SuspendUrl(nsIImapUrl *aImapUrl)
+{
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ nsImapProtocol::LogImapUrl("suspending url", aImapUrl);
+ PR_CEnterMonitor(this);
+ m_urlQueue.AppendObject(aImapUrl);
+ m_urlConsumers.AppendElement(nullptr);
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::RetryUrl(nsIImapUrl *aImapUrl, nsIImapMockChannel *aChannel)
+{
+ nsresult rv;
+ // Get current thread envent queue
+ aImapUrl->SetMockChannel(aChannel);
+ nsCOMPtr <nsIImapProtocol> protocolInstance;
+ nsImapProtocol::LogImapUrl("creating protocol instance to retry queued url", aImapUrl);
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+ rv = GetImapConnection(aImapUrl, getter_AddRefs(protocolInstance));
+ if (NS_SUCCEEDED(rv) && protocolInstance)
+ {
+ nsCOMPtr<nsIURI> url = do_QueryInterface(aImapUrl, &rv);
+ if (NS_SUCCEEDED(rv) && url)
+ {
+ nsImapProtocol::LogImapUrl("retrying url", aImapUrl);
+ rv = protocolInstance->LoadImapUrl(url, nullptr); // ### need to save the display consumer.
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed running queued url");
+ }
+ }
+ return rv;
+}
+
+// checks to see if there are any queued urls on this incoming server,
+// and if so, tries to run the oldest one. Returns true if the url is run
+// on the passed in protocol connection.
+NS_IMETHODIMP
+nsImapIncomingServer::LoadNextQueuedUrl(nsIImapProtocol *aProtocol, bool *aResult)
+{
+ if (WeAreOffline())
+ return NS_MSG_ERROR_OFFLINE;
+
+ nsresult rv = NS_OK;
+ bool urlRun = false;
+ bool keepGoing = true;
+ nsCOMPtr <nsIImapProtocol> protocolInstance ;
+
+ MutexAutoLock mon(mLock);
+ int32_t cnt = m_urlQueue.Count();
+
+ while (cnt > 0 && !urlRun && keepGoing)
+ {
+ nsCOMPtr<nsIImapUrl> aImapUrl(m_urlQueue[0]);
+ nsCOMPtr<nsIMsgMailNewsUrl> aMailNewsUrl(do_QueryInterface(aImapUrl, &rv));
+
+ bool removeUrlFromQueue = false;
+ if (aImapUrl)
+ {
+ nsImapProtocol::LogImapUrl("considering playing queued url", aImapUrl);
+ rv = DoomUrlIfChannelHasError(aImapUrl, &removeUrlFromQueue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // if we didn't doom the url, lets run it.
+ if (!removeUrlFromQueue)
+ {
+ nsISupports *aConsumer = m_urlConsumers.ElementAt(0);
+ NS_IF_ADDREF(aConsumer);
+
+ nsImapProtocol::LogImapUrl("creating protocol instance to play queued url", aImapUrl);
+ rv = GetImapConnection(aImapUrl, getter_AddRefs(protocolInstance));
+ if (NS_SUCCEEDED(rv) && protocolInstance)
+ {
+ nsCOMPtr<nsIURI> url = do_QueryInterface(aImapUrl, &rv);
+ if (NS_SUCCEEDED(rv) && url)
+ {
+ nsImapProtocol::LogImapUrl("playing queued url", aImapUrl);
+ rv = protocolInstance->LoadImapUrl(url, aConsumer);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed running queued url");
+ bool isInbox;
+ protocolInstance->IsBusy(&urlRun, &isInbox);
+ if (!urlRun)
+ nsImapProtocol::LogImapUrl("didn't need to run", aImapUrl);
+ removeUrlFromQueue = true;
+ }
+ }
+ else
+ {
+ nsImapProtocol::LogImapUrl("failed creating protocol instance to play queued url", aImapUrl);
+ keepGoing = false;
+ }
+ NS_IF_RELEASE(aConsumer);
+ }
+ if (removeUrlFromQueue)
+ {
+ m_urlQueue.RemoveObjectAt(0);
+ m_urlConsumers.RemoveElementAt(0);
+ }
+ }
+ cnt = m_urlQueue.Count();
+ }
+ if (aResult)
+ *aResult = urlRun && aProtocol && aProtocol == protocolInstance;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::AbortQueuedUrls()
+{
+ nsresult rv = NS_OK;
+
+ MutexAutoLock mon(mLock);
+ int32_t cnt = m_urlQueue.Count();
+
+ while (cnt > 0)
+ {
+ nsCOMPtr<nsIImapUrl> aImapUrl(m_urlQueue[cnt - 1]);
+ bool removeUrlFromQueue = false;
+
+ if (aImapUrl)
+ {
+ rv = DoomUrlIfChannelHasError(aImapUrl, &removeUrlFromQueue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (removeUrlFromQueue)
+ {
+ m_urlQueue.RemoveObjectAt(cnt - 1);
+ m_urlConsumers.RemoveElementAt(cnt - 1);
+ }
+ }
+ cnt--;
+ }
+
+ return rv;
+}
+
+// if this url has a channel with an error, doom it and its mem cache entries,
+// and notify url listeners.
+nsresult nsImapIncomingServer::DoomUrlIfChannelHasError(nsIImapUrl *aImapUrl, bool *urlDoomed)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> aMailNewsUrl(do_QueryInterface(aImapUrl, &rv));
+
+ if (aMailNewsUrl && aImapUrl)
+ {
+ nsCOMPtr <nsIImapMockChannel> mockChannel;
+
+ if (NS_SUCCEEDED(aImapUrl->GetMockChannel(getter_AddRefs(mockChannel))) && mockChannel)
+ {
+ nsresult requestStatus;
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mockChannel);
+ if (!request)
+ return NS_ERROR_FAILURE;
+ request->GetStatus(&requestStatus);
+ if (NS_FAILED(requestStatus))
+ {
+ nsresult res;
+ *urlDoomed = true;
+ nsImapProtocol::LogImapUrl("dooming url", aImapUrl);
+
+ mockChannel->Close(); // try closing it to get channel listener nulled out.
+
+ if (aMailNewsUrl)
+ {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ res = aMailNewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (NS_SUCCEEDED(res) && cacheEntry)
+ cacheEntry->AsyncDoom(nullptr);
+ // we're aborting this url - tell listeners
+ aMailNewsUrl->SetUrlState(false, NS_MSG_ERROR_URL_ABORTED);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::RemoveConnection(nsIImapProtocol* aImapConnection)
+{
+ PR_CEnterMonitor(this);
+ if (aImapConnection)
+ m_connectionCache.RemoveObject(aImapConnection);
+
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+bool
+nsImapIncomingServer::ConnectionTimeOut(nsIImapProtocol* aConnection)
+{
+ bool retVal = false;
+ if (!aConnection) return retVal;
+ nsresult rv;
+
+ int32_t timeoutInMinutes = 0;
+ rv = GetTimeOutLimits(&timeoutInMinutes);
+ if (NS_FAILED(rv) || timeoutInMinutes <= 0 || timeoutInMinutes > 29)
+ {
+ timeoutInMinutes = 29;
+ SetTimeOutLimits(timeoutInMinutes);
+ }
+
+ PRTime cacheTimeoutLimits = timeoutInMinutes * 60 * PR_USEC_PER_SEC;
+ PRTime lastActiveTimeStamp;
+ rv = aConnection->GetLastActiveTimeStamp(&lastActiveTimeStamp);
+
+ if (PR_Now() - lastActiveTimeStamp >= cacheTimeoutLimits)
+ {
+ nsCOMPtr<nsIImapProtocol> aProtocol(do_QueryInterface(aConnection,
+ &rv));
+ if (NS_SUCCEEDED(rv) && aProtocol)
+ {
+ RemoveConnection(aConnection);
+ aProtocol->TellThreadToDie(false);
+ retVal = true;
+ }
+ }
+ return retVal;
+}
+
+nsresult
+nsImapIncomingServer::GetImapConnection(nsIImapUrl * aImapUrl,
+ nsIImapProtocol ** aImapConnection)
+{
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+
+ nsresult rv = NS_OK;
+ bool canRunUrlImmediately = false;
+ bool canRunButBusy = false;
+ nsCOMPtr<nsIImapProtocol> connection;
+ nsCOMPtr<nsIImapProtocol> freeConnection;
+ bool isBusy = false;
+ bool isInboxConnection = false;
+
+ PR_CEnterMonitor(this);
+
+ int32_t maxConnections;
+ (void)GetMaximumConnectionsNumber(&maxConnections);
+
+ int32_t cnt = m_connectionCache.Count();
+
+ *aImapConnection = nullptr;
+ // iterate through the connection cache for a connection that can handle this url.
+ bool userCancelled = false;
+
+ // loop until we find a connection that can run the url, or doesn't have to wait?
+ for (int32_t i = cnt - 1; i >= 0 && !canRunUrlImmediately && !canRunButBusy; i--)
+ {
+ connection = m_connectionCache[i];
+ if (connection)
+ {
+ bool badConnection = ConnectionTimeOut(connection);
+ if (!badConnection)
+ {
+ badConnection = NS_FAILED(connection->CanHandleUrl(aImapUrl,
+ &canRunUrlImmediately,
+ &canRunButBusy));
+#ifdef DEBUG_bienvenu
+ nsAutoCString curSelectedFolderName;
+ if (connection)
+ connection->GetSelectedMailboxName(getter_Copies(curSelectedFolderName));
+ // check that no other connection is in the same selected state.
+ if (!curSelectedFolderName.IsEmpty())
+ {
+ for (uint32_t j = 0; j < cnt; j++)
+ {
+ if (j != i)
+ {
+ nsCOMPtr<nsIImapProtocol> otherConnection = do_QueryElementAt(m_connectionCache, j);
+ if (otherConnection)
+ {
+ nsAutoCString otherSelectedFolderName;
+ otherConnection->GetSelectedMailboxName(getter_Copies(otherSelectedFolderName));
+ NS_ASSERTION(!curSelectedFolderName.Equals(otherSelectedFolderName), "two connections selected on same folder");
+ }
+
+ }
+ }
+ }
+#endif // DEBUG_bienvenu
+ }
+ if (badConnection)
+ {
+ connection = nullptr;
+ continue;
+ }
+ }
+
+ // if this connection is wrong, but it's not busy, check if we should designate
+ // it as the free connection.
+ if (!canRunUrlImmediately && !canRunButBusy && connection)
+ {
+ rv = connection->IsBusy(&isBusy, &isInboxConnection);
+ if (NS_FAILED(rv))
+ continue;
+ // if max connections is <= 1, we have to re-use the inbox connection.
+ if (!isBusy && (!isInboxConnection || maxConnections <= 1))
+ {
+ if (!freeConnection)
+ freeConnection = connection;
+ else // check which is the better free connection to use.
+ { // We prefer one not in the selected state.
+ nsAutoCString selectedFolderName;
+ connection->GetSelectedMailboxName(getter_Copies(selectedFolderName));
+ if (selectedFolderName.IsEmpty())
+ freeConnection = connection;
+ }
+ }
+ }
+ // don't leave this loop with connection set if we can't use it!
+ if (!canRunButBusy && !canRunUrlImmediately)
+ connection = nullptr;
+ }
+
+ nsImapState requiredState;
+ aImapUrl->GetRequiredImapState(&requiredState);
+ // refresh cnt in case we killed one or more dead connections. This
+ // will prevent us from not spinning up a new connection when all
+ // connections were dead.
+ cnt = m_connectionCache.Count();
+ // if we got here and we have a connection, then we should return it!
+ if (canRunUrlImmediately && connection)
+ {
+ *aImapConnection = connection;
+ NS_IF_ADDREF(*aImapConnection);
+ }
+ else if (canRunButBusy)
+ {
+ // do nothing; return NS_OK; for queuing
+ }
+ else if (userCancelled)
+ {
+ rv = NS_BINDING_ABORTED; // user cancelled
+ }
+ // CanHandleUrl will pretend that some types of urls require a selected state url
+ // (e.g., a folder delete or msg append) but we shouldn't create new connections
+ // for these types of urls if we have a free connection. So we check the actual
+ // required state here.
+ else if (cnt < maxConnections
+ && (!freeConnection || requiredState == nsIImapUrl::nsImapSelectedState))
+ rv = CreateProtocolInstance(aImapConnection);
+ else if (freeConnection)
+ {
+ *aImapConnection = freeConnection;
+ NS_IF_ADDREF(*aImapConnection);
+ }
+ else // cannot get anyone to handle the url queue it
+ {
+ if (cnt >= maxConnections)
+ nsImapProtocol::LogImapUrl("exceeded connection cache limit", aImapUrl);
+ // caller will queue the url
+ }
+
+ PR_CExitMonitor(this);
+ return rv;
+}
+
+nsresult
+nsImapIncomingServer::CreateProtocolInstance(nsIImapProtocol ** aImapConnection)
+{
+ // create a new connection and add it to the connection cache
+ // we may need to flag the protocol connection as busy so we don't get
+ // a race condition where someone else goes through this code
+
+ int32_t authMethod;
+ GetAuthMethod(&authMethod);
+ nsresult rv;
+ // pre-flight that we have nss - on the ui thread - for MD5 etc.
+ switch (authMethod)
+ {
+ case nsMsgAuthMethod::passwordEncrypted:
+ case nsMsgAuthMethod::secure:
+ case nsMsgAuthMethod::anything:
+ {
+ nsCOMPtr<nsISupports> dummyUsedToEnsureNSSIsInitialized =
+ do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ default:
+ break;
+ }
+ nsIImapProtocol * protocolInstance;
+ rv = CallCreateInstance(kImapProtocolCID, &protocolInstance);
+ if (NS_SUCCEEDED(rv) && protocolInstance)
+ {
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = protocolInstance->Initialize(hostSession, this);
+ }
+
+ // take the protocol instance and add it to the connectionCache
+ if (protocolInstance)
+ m_connectionCache.AppendObject(protocolInstance);
+ *aImapConnection = protocolInstance; // this is already ref counted.
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::CloseConnectionForFolder(nsIMsgFolder *aMsgFolder)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapProtocol> connection;
+ bool isBusy = false, isInbox = false;
+ nsCString inFolderName;
+ nsCString connectionFolderName;
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aMsgFolder);
+
+ if (!imapFolder)
+ return NS_ERROR_NULL_POINTER;
+
+ int32_t cnt = m_connectionCache.Count();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ imapFolder->GetOnlineName(inFolderName);
+ PR_CEnterMonitor(this);
+
+ for (int32_t i = 0; i < cnt; ++i)
+ {
+ connection = m_connectionCache[i];
+ if (connection)
+ {
+ rv = connection->GetSelectedMailboxName(getter_Copies(connectionFolderName));
+ if (connectionFolderName.Equals(inFolderName))
+ {
+ rv = connection->IsBusy(&isBusy, &isInbox);
+ if (!isBusy)
+ rv = connection->TellThreadToDie(true);
+ break; // found it, end of the loop
+ }
+ }
+ }
+
+ PR_CExitMonitor(this);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::ResetConnection(const nsACString& folderName)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapProtocol> connection;
+ bool isBusy = false, isInbox = false;
+ nsCString curFolderName;
+
+ int32_t cnt = m_connectionCache.Count();
+
+ PR_CEnterMonitor(this);
+
+ for (int32_t i = 0; i < cnt; ++i)
+ {
+ connection = m_connectionCache[i];
+ if (connection)
+ {
+ rv = connection->GetSelectedMailboxName(getter_Copies(curFolderName));
+ if (curFolderName.Equals(folderName))
+ {
+ rv = connection->IsBusy(&isBusy, &isInbox);
+ if (!isBusy)
+ rv = connection->ResetToAuthenticatedState();
+ break; // found it, end of the loop
+ }
+ }
+ }
+
+ PR_CExitMonitor(this);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::PerformExpand(nsIMsgWindow *aMsgWindow)
+{
+ nsCString password;
+ nsresult rv;
+ rv = GetPassword(password);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (password.IsEmpty())
+ return NS_OK;
+
+ rv = ResetFoldersToUnverified(nullptr);
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = GetRootFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!rootMsgFolder) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+ rv = imapService->DiscoverAllFolders(rootMsgFolder,
+ this, aMsgWindow, nullptr);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::VerifyLogon(nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow, nsIURI **aURL)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ // this will create the resource if it doesn't exist, but it shouldn't
+ // do anything on disk.
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->VerifyLogon(rootFolder, aUrlListener, aMsgWindow, aURL);
+}
+
+NS_IMETHODIMP nsImapIncomingServer::PerformBiff(nsIMsgWindow* aMsgWindow)
+{
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ if(NS_SUCCEEDED(rv))
+ {
+ SetPerformingBiff(true);
+ rv = rootMsgFolder->GetNewMessages(aMsgWindow, nullptr);
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsImapIncomingServer::CloseCachedConnections()
+{
+ nsCOMPtr<nsIImapProtocol> connection;
+ PR_CEnterMonitor(this);
+
+ // iterate through the connection cache closing open connections.
+ int32_t cnt = m_connectionCache.Count();
+
+ for (int32_t i = cnt; i > 0; --i)
+ {
+ connection = m_connectionCache[i - 1];
+ if (connection)
+ connection->TellThreadToDie(true);
+ }
+
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+nsresult
+nsImapIncomingServer::CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder)
+{
+ nsImapMailFolder *newRootFolder = new nsImapMailFolder;
+ if (!newRootFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+ newRootFolder->Init(serverUri.get());
+ NS_ADDREF(*rootFolder = newRootFolder);
+ return NS_OK;
+}
+
+// nsIImapServerSink impl
+// aNewFolder will not be set if we're listing for the subscribe UI, since that's the way 4.x worked.
+NS_IMETHODIMP nsImapIncomingServer::PossibleImapMailbox(const nsACString& folderPath,
+ char hierarchyDelimiter,
+ int32_t boxFlags, bool *aNewFolder)
+{
+ NS_ENSURE_ARG_POINTER(aNewFolder);
+ NS_ENSURE_TRUE(!folderPath.IsEmpty(), NS_ERROR_FAILURE);
+
+ // folderPath is in canonical format, i.e., hierarchy separator has been replaced with '/'
+ nsresult rv;
+ bool found = false;
+ bool haveParent = false;
+ nsCOMPtr<nsIMsgImapMailFolder> hostFolder;
+ nsCOMPtr<nsIMsgFolder> aFolder;
+ bool explicitlyVerify = false;
+
+ *aNewFolder = false;
+ nsCOMPtr<nsIMsgFolder> a_nsIFolder;
+ rv = GetRootFolder(getter_AddRefs(a_nsIFolder));
+
+ if(NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString dupFolderPath(folderPath);
+ if (dupFolderPath.Last() == '/')
+ {
+ dupFolderPath.SetLength(dupFolderPath.Length()-1);
+ if (dupFolderPath.IsEmpty())
+ return NS_ERROR_FAILURE;
+ // *** this is what we did in 4.x in order to list uw folder only
+ // mailbox in order to get the \NoSelect flag
+ explicitlyVerify = !(boxFlags & kNameSpace);
+ }
+ if (mDoingSubscribeDialog)
+ {
+ // Make sure the imapmailfolder object has the right delimiter because the unsubscribed
+ // folders (those not in the 'lsub' list) have the delimiter set to the default ('^').
+ if (a_nsIFolder && !dupFolderPath.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ bool isNamespace = false;
+ bool noSelect = false;
+
+ rv = a_nsIFolder->FindSubFolder(dupFolderPath, getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_subscribeFolders.AppendObject(msgFolder);
+ noSelect = (boxFlags & kNoselect) != 0;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ isNamespace = (boxFlags & kNameSpace) != 0;
+ if (!isNamespace)
+ rv = AddTo(dupFolderPath, mDoingLsub && !noSelect/* add as subscribed */,
+ !noSelect, mDoingLsub /* change if exists */);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+ }
+ }
+
+ hostFolder = do_QueryInterface(a_nsIFolder, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString tempFolderName(dupFolderPath);
+ nsAutoCString tokenStr, remStr, changedStr;
+ int32_t slashPos = tempFolderName.FindChar('/');
+ if (slashPos > 0)
+ {
+ tokenStr = StringHead(tempFolderName, slashPos);
+ remStr = Substring(tempFolderName, slashPos);
+ }
+ else
+ tokenStr.Assign(tempFolderName);
+
+ if ((int32_t(PL_strcasecmp(tokenStr.get(), "INBOX"))==0) && (strcmp(tokenStr.get(), "INBOX") != 0))
+ changedStr.Append("INBOX");
+ else
+ changedStr.Append(tokenStr);
+
+ if (slashPos > 0 )
+ changedStr.Append(remStr);
+
+ dupFolderPath.Assign(changedStr);
+ nsAutoCString folderName(dupFolderPath);
+
+ nsAutoCString uri;
+ nsCString serverUri;
+ GetServerURI(serverUri);
+ uri.Assign(serverUri);
+ int32_t leafPos = folderName.RFindChar('/');
+ nsAutoCString parentName(folderName);
+ nsAutoCString parentUri(uri);
+
+ if (leafPos > 0)
+ {
+ // If there is a hierarchy, there is a parent.
+ // Don't strip off slash if it's the first character
+ parentName.SetLength(leafPos);
+ folderName.Cut(0, leafPos + 1); // get rid of the parent name
+ haveParent = true;
+ parentUri.Append('/');
+ parentUri.Append(parentName);
+ }
+ if (MsgLowerCaseEqualsLiteral(folderPath, "inbox") &&
+ hierarchyDelimiter == kOnlineHierarchySeparatorNil)
+ {
+ hierarchyDelimiter = '/'; // set to default in this case (as in 4.x)
+ hostFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ }
+
+ nsCOMPtr <nsIMsgFolder> child;
+
+ // nsCString possibleName(aSpec->allocatedPathName);
+ uri.Append('/');
+ uri.Append(dupFolderPath);
+ bool caseInsensitive = MsgLowerCaseEqualsLiteral(dupFolderPath, "inbox");
+ a_nsIFolder->GetChildWithURI(uri, true, caseInsensitive, getter_AddRefs(child));
+ // if we couldn't find this folder by URI, tell the imap code it's a new folder to us
+ *aNewFolder = !child;
+ if (child)
+ found = true;
+ if (!found)
+ {
+ // trying to find/discover the parent
+ if (haveParent)
+ {
+ nsCOMPtr <nsIMsgFolder> parent;
+ bool parentIsNew;
+ caseInsensitive = MsgLowerCaseEqualsLiteral(parentName, "inbox");
+ a_nsIFolder->GetChildWithURI(parentUri, true, caseInsensitive, getter_AddRefs(parent));
+ if (!parent /* || parentFolder->GetFolderNeedsAdded()*/)
+ {
+ PossibleImapMailbox(parentName, hierarchyDelimiter, kNoselect | // be defensive
+ ((boxFlags & //only inherit certain flags from the child
+ (kPublicMailbox | kOtherUsersMailbox | kPersonalMailbox))), &parentIsNew);
+ }
+ }
+ rv = hostFolder->CreateClientSubfolderInfo(dupFolderPath, hierarchyDelimiter,boxFlags, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ caseInsensitive = MsgLowerCaseEqualsLiteral(dupFolderPath, "inbox");
+ a_nsIFolder->GetChildWithURI(uri, true, caseInsensitive, getter_AddRefs(child));
+ }
+ if (child)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(child);
+ if (imapFolder)
+ {
+ nsAutoCString onlineName;
+ nsAutoString unicodeName;
+ imapFolder->SetVerifiedAsOnlineFolder(true);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ if (boxFlags & kImapTrash)
+ {
+ int32_t deleteModel;
+ GetDeleteModel(&deleteModel);
+ if (deleteModel == nsMsgImapDeleteModels::MoveToTrash)
+ child->SetFlag(nsMsgFolderFlags::Trash);
+ }
+
+ imapFolder->SetBoxFlags(boxFlags);
+ imapFolder->SetExplicitlyVerify(explicitlyVerify);
+ imapFolder->GetOnlineName(onlineName);
+
+ // online name needs to use the correct hierarchy delimiter (I think...)
+ // or the canonical path - one or the other, but be consistent.
+ MsgReplaceChar(dupFolderPath, '/', hierarchyDelimiter);
+ if (hierarchyDelimiter != '/')
+ nsImapUrl::UnescapeSlashes(dupFolderPath.BeginWriting());
+
+ // GMail gives us a localized name for the inbox but doesn't let
+ // us select that localized name.
+ if (boxFlags & kImapInbox)
+ imapFolder->SetOnlineName(NS_LITERAL_CSTRING("INBOX"));
+ else if (onlineName.IsEmpty() || !onlineName.Equals(dupFolderPath))
+ imapFolder->SetOnlineName(dupFolderPath);
+
+ if (hierarchyDelimiter != '/')
+ nsImapUrl::UnescapeSlashes(folderName.BeginWriting());
+ if (NS_SUCCEEDED(CopyMUTF7toUTF16(folderName, unicodeName)))
+ child->SetPrettyName(unicodeName);
+ }
+ }
+ if (!found && child)
+ child->SetMsgDatabase(nullptr); // close the db, so we don't hold open all the .msf files for new folders
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::AddFolderRights(const nsACString& mailboxName, const nsACString& userName,
+ const nsACString& rights)
+{
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if(NS_SUCCEEDED(rv) && rootFolder)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ if (imapRoot)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> foundFolder;
+ rv = imapRoot->FindOnlineSubFolder(mailboxName, getter_AddRefs(foundFolder));
+ if (NS_SUCCEEDED(rv) && foundFolder)
+ return foundFolder->AddFolderRights(userName, rights);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::FolderNeedsACLInitialized(const nsACString& folderPath,
+ bool *aNeedsACLInitialized)
+{
+ NS_ENSURE_ARG_POINTER(aNeedsACLInitialized);
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if(NS_SUCCEEDED(rv) && rootFolder)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ if (imapRoot)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> foundFolder;
+ rv = imapRoot->FindOnlineSubFolder(folderPath, getter_AddRefs(foundFolder));
+ if (NS_SUCCEEDED(rv) && foundFolder)
+ {
+ nsCOMPtr <nsIImapMailFolderSink> folderSink = do_QueryInterface(foundFolder);
+ if (folderSink)
+ return folderSink->GetFolderNeedsACLListed(aNeedsACLInitialized);
+ }
+ }
+ }
+ *aNeedsACLInitialized = false; // maybe we want to say TRUE here...
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::RefreshFolderRights(const nsACString& folderPath)
+{
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if(NS_SUCCEEDED(rv) && rootFolder)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ if (imapRoot)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> foundFolder;
+ rv = imapRoot->FindOnlineSubFolder(folderPath, getter_AddRefs(foundFolder));
+ if (NS_SUCCEEDED(rv) && foundFolder)
+ return foundFolder->RefreshFolderRights();
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapIncomingServer::GetFolder(const nsACString& name, nsIMsgFolder** pFolder)
+{
+ NS_ENSURE_ARG_POINTER(pFolder);
+ NS_ENSURE_TRUE(!name.IsEmpty(), NS_ERROR_FAILURE);
+ nsresult rv;
+ *pFolder = nullptr;
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ {
+ nsCString uri;
+ rv = rootFolder->GetURI(uri);
+ if (NS_SUCCEEDED(rv) && !uri.IsEmpty())
+ {
+ nsAutoCString uriString(uri);
+ uriString.Append('/');
+ uriString.Append(name);
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uriString, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ if (NS_SUCCEEDED(rv) && folder)
+ folder.swap(*pFolder);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::OnlineFolderDelete(const nsACString& aFolderName)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::OnlineFolderCreateFailed(const nsACString& aFolderName)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::OnlineFolderRename(nsIMsgWindow *msgWindow, const nsACString& oldName,
+ const nsACString& newName)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ if (!newName.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgFolder> me;
+ rv = GetFolder(oldName, getter_AddRefs(me));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> parent;
+ nsCString tmpNewName (newName);
+ int32_t folderStart = tmpNewName.RFindChar('/');
+ if (folderStart > 0)
+ {
+ rv = GetFolder(StringHead(tmpNewName, folderStart), getter_AddRefs(parent));
+ }
+ else // root is the parent
+ rv = GetRootFolder(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> folder;
+ folder = do_QueryInterface(me, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ folder->RenameLocal(tmpNewName, parent);
+ nsCOMPtr<nsIMsgImapMailFolder> parentImapFolder = do_QueryInterface(parent);
+
+ if (parentImapFolder)
+ parentImapFolder->RenameClient(msgWindow, me, oldName, tmpNewName);
+
+ nsCOMPtr <nsIMsgFolder> newFolder;
+ nsString unicodeNewName;
+ // tmpNewName is imap mod utf7. It needs to be convert to utf8.
+ CopyMUTF7toUTF16(tmpNewName, unicodeNewName);
+ CopyUTF16toUTF8(unicodeNewName, tmpNewName);
+ rv = GetFolder(tmpNewName, getter_AddRefs(newFolder));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIAtom> folderRenameAtom;
+ folderRenameAtom = MsgGetAtom("RenameCompleted");
+ newFolder->NotifyFolderEvent(folderRenameAtom);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::FolderIsNoSelect(const nsACString& aFolderName, bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nsresult rv = GetFolder(aFolderName, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder)
+ {
+ uint32_t flags;
+ msgFolder->GetFlags(&flags);
+ *result = ((flags & nsMsgFolderFlags::ImapNoselect) != 0);
+ }
+ else
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetFolderAdminURL(const nsACString& aFolderName, const nsACString& aFolderAdminUrl)
+{
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if(NS_SUCCEEDED(rv) && rootFolder)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ if (imapRoot)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> foundFolder;
+ rv = imapRoot->FindOnlineSubFolder(aFolderName, getter_AddRefs(foundFolder));
+ if (NS_SUCCEEDED(rv) && foundFolder)
+ return foundFolder->SetAdminUrl(aFolderAdminUrl);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::FolderVerifiedOnline(const nsACString& folderName, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = rootFolder->FindSubFolder(folderName, getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
+ if (imapFolder)
+ imapFolder->GetVerifiedAsOnlineFolder(aResult);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::DiscoveryDone()
+{
+ if (mDoingSubscribeDialog)
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootMsgFolder));
+ if (NS_SUCCEEDED(rv) && rootMsgFolder)
+ {
+ // GetResource() may return a node which is not in the folder
+ // tree hierarchy but in the rdf cache in case of the non-existing default
+ // Sent, Drafts, and Templates folders. The resouce will be eventually
+ // released when the rdf service shuts down. When we create the default
+ // folders later on in the imap server, the subsequent GetResource() of the
+ // same uri will get us the cached rdf resource which should have the folder
+ // flag set appropriately.
+ nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1",
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = accountMgr->GetFirstIdentityForServer(this, getter_AddRefs(identity));
+ if (NS_SUCCEEDED(rv) && identity)
+ {
+ nsCString folderUri;
+ identity->GetFccFolder(folderUri);
+ nsCString existingUri;
+
+ if (CheckSpecialFolder(rdf, folderUri, nsMsgFolderFlags::SentMail,
+ existingUri))
+ {
+ identity->SetFccFolder(existingUri);
+ identity->SetFccFolderPickerMode(NS_LITERAL_CSTRING("1"));
+ }
+ identity->GetDraftFolder(folderUri);
+ if (CheckSpecialFolder(rdf, folderUri, nsMsgFolderFlags::Drafts,
+ existingUri))
+ {
+ identity->SetDraftFolder(existingUri);
+ identity->SetDraftsFolderPickerMode(NS_LITERAL_CSTRING("1"));
+ }
+ bool archiveEnabled;
+ identity->GetArchiveEnabled(&archiveEnabled);
+ if (archiveEnabled)
+ {
+ identity->GetArchiveFolder(folderUri);
+ if (CheckSpecialFolder(rdf, folderUri, nsMsgFolderFlags::Archive,
+ existingUri))
+ {
+ identity->SetArchiveFolder(existingUri);
+ identity->SetArchivesFolderPickerMode(NS_LITERAL_CSTRING("1"));
+ }
+ }
+ identity->GetStationeryFolder(folderUri);
+ nsCOMPtr<nsIRDFResource> res;
+ if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res))))
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ if (NS_SUCCEEDED(rv))
+ rv = folder->SetFlag(nsMsgFolderFlags::Templates);
+ }
+ }
+
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = GetSpamSettings(getter_AddRefs(spamSettings));
+ if (NS_SUCCEEDED(rv) && spamSettings)
+ {
+ nsCString spamFolderUri, existingUri;
+ spamSettings->GetSpamFolderURI(getter_Copies(spamFolderUri));
+ if (CheckSpecialFolder(rdf, spamFolderUri, nsMsgFolderFlags::Junk,
+ existingUri))
+ {
+ // This only sets the cached values in the spam settings object.
+ spamSettings->SetActionTargetFolder(existingUri.get());
+ spamSettings->SetMoveTargetMode(nsISpamSettings::MOVE_TARGET_MODE_FOLDER);
+ // Set the preferences too so that the values persist.
+ SetCharValue("spamActionTargetFolder", existingUri);
+ SetIntValue("moveTargetMode", nsISpamSettings::MOVE_TARGET_MODE_FOLDER);
+ }
+ }
+
+ bool isGMailServer;
+ GetIsGMailServer(&isGMailServer);
+
+ // Verify there is only one trash folder. Another might be present if
+ // the trash name has been changed. Or we might be a gmail server and
+ // want to switch to gmail's trash folder.
+ nsCOMPtr<nsIArray> trashFolders;
+ rv = rootMsgFolder->GetFoldersWithFlags(nsMsgFolderFlags::Trash,
+ getter_AddRefs(trashFolders));
+
+ if (NS_SUCCEEDED(rv) && trashFolders)
+ {
+ uint32_t numFolders;
+ trashFolders->GetLength(&numFolders);
+ nsAutoString trashName;
+ if (NS_SUCCEEDED(GetTrashFolderName(trashName)))
+ {
+ for (uint32_t i = 0; i < numFolders; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> trashFolder(do_QueryElementAt(trashFolders, i));
+ if (trashFolder)
+ {
+ // If we're a gmail server, we clear the trash flags from folder(s)
+ // without the kImapXListTrash flag. For normal servers, we clear
+ // the trash folder flag if the folder name doesn't match the
+ // pref trash folder name.
+ if (isGMailServer)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(trashFolder));
+ int32_t boxFlags;
+ imapFolder->GetBoxFlags(&boxFlags);
+ if (boxFlags & kImapXListTrash)
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // trashName is the leaf name on the folder URI, which will be
+ // different from the folder GetName if the trash name is
+ // localized.
+ nsAutoCString trashURL;
+ trashFolder->GetFolderURL(trashURL);
+ int32_t leafPos = trashURL.RFindChar('/');
+ nsAutoCString unescapedName;
+ MsgUnescapeString(Substring(trashURL, leafPos + 1),
+ nsINetUtil::ESCAPE_URL_PATH, unescapedName);
+ nsAutoString nameUnicode;
+ if (NS_FAILED(CopyMUTF7toUTF16(unescapedName, nameUnicode)) ||
+ trashName.Equals(nameUnicode))
+ {
+ continue;
+ }
+ if (numFolders == 1)
+ {
+ // We got here because the preferred trash folder does not
+ // exist, but a folder got discovered to be the trash folder.
+ SetUnicharValue(PREF_TRASH_FOLDER_NAME, nameUnicode);
+ continue;
+ }
+ }
+ trashFolder->ClearFlag(nsMsgFolderFlags::Trash);
+ }
+ }
+ }
+ }
+ }
+
+ bool usingSubscription = true;
+ GetUsingSubscription(&usingSubscription);
+
+ nsCOMArray<nsIMsgImapMailFolder> unverifiedFolders;
+ GetUnverifiedFolders(unverifiedFolders);
+
+ int32_t count = unverifiedFolders.Count();
+ for (int32_t k = 0; k < count; ++k)
+ {
+ bool explicitlyVerify = false;
+ bool hasSubFolders = false;
+ uint32_t folderFlags;
+ nsCOMPtr<nsIMsgImapMailFolder> currentImapFolder(unverifiedFolders[k]);
+ nsCOMPtr<nsIMsgFolder> currentFolder(do_QueryInterface(currentImapFolder, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ currentFolder->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Virtual) // don't remove virtual folders
+ continue;
+
+ if ((!usingSubscription ||
+ (NS_SUCCEEDED(currentImapFolder->GetExplicitlyVerify(&explicitlyVerify)) &&
+ explicitlyVerify)) ||
+ ((NS_SUCCEEDED(currentFolder->GetHasSubFolders(&hasSubFolders)) &&
+ hasSubFolders) &&
+ !NoDescendentsAreVerified(currentFolder)))
+ {
+ bool isNamespace;
+ currentImapFolder->GetIsNamespace(&isNamespace);
+ if (!isNamespace) // don't list namespaces explicitly
+ {
+ // If there are no subfolders and this is unverified, we don't want to
+ // run this url. That is, we want to undiscover the folder.
+ // If there are subfolders and no descendants are verified, we want to
+ // undiscover all of the folders.
+ // Only if there are subfolders and at least one of them is verified
+ // do we want to refresh that folder's flags, because it won't be going
+ // away.
+ currentImapFolder->SetExplicitlyVerify(false);
+ currentImapFolder->List();
+ }
+ }
+ else
+ DeleteNonVerifiedFolders(currentFolder);
+ }
+
+ return rv;
+}
+
+// Check if the special folder corresponding to the uri exists. If not, check
+// if there already exists a folder with the special folder flag (the server may
+// have told us about a folder to use through XLIST). If so, return the uri of
+// the existing special folder. If not, set the special flag on the folder so
+// it will be there if and when the folder is created.
+// Return true if we found an existing special folder different than
+// the one specified in prefs, and the one specified by prefs doesn't exist.
+bool nsImapIncomingServer::CheckSpecialFolder(nsIRDFService *rdf,
+ nsCString &folderUri,
+ uint32_t folderFlag,
+ nsCString &existingUri)
+{
+ nsCOMPtr<nsIRDFResource> res;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIMsgFolder> existingFolder;
+ rootMsgFolder->GetFolderWithFlags(folderFlag, getter_AddRefs(existingFolder));
+
+ if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res))))
+ {
+ folder = do_QueryInterface(res, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> parent;
+ folder->GetParent(getter_AddRefs(parent));
+ if (parent)
+ {
+ existingFolder = nullptr;
+ }
+ if (!existingFolder)
+ {
+ folder->SetFlag(folderFlag);
+ }
+
+ nsString folderName;
+ folder->GetPrettyName(folderName);
+ // this will set the localized name based on the folder flag.
+ folder->SetPrettyName(folderName);
+ }
+ }
+
+ if (existingFolder)
+ {
+ existingFolder->GetURI(existingUri);
+ return true;
+ }
+
+ return false;
+}
+
+nsresult nsImapIncomingServer::DeleteNonVerifiedFolders(nsIMsgFolder *curFolder)
+{
+ bool autoUnsubscribeFromNoSelectFolders = true;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ prefBranch->GetBoolPref("mail.imap.auto_unsubscribe_from_noselect_folders", &autoUnsubscribeFromNoSelectFolders);
+
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+
+ rv = curFolder->GetSubFolders(getter_AddRefs(subFolders));
+ if(NS_SUCCEEDED(rv))
+ {
+ bool moreFolders;
+
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && moreFolders)
+ {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ bool childVerified = false;
+ nsCOMPtr <nsIMsgImapMailFolder> childImapFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && childImapFolder)
+ {
+ uint32_t flags;
+
+ nsCOMPtr <nsIMsgFolder> childFolder = do_QueryInterface(child, &rv);
+ rv = childImapFolder->GetVerifiedAsOnlineFolder(&childVerified);
+
+ rv = childFolder->GetFlags(&flags);
+ bool folderIsNoSelectFolder = NS_SUCCEEDED(rv) && ((flags & nsMsgFolderFlags::ImapNoselect) != 0);
+
+ bool usingSubscription = true;
+ GetUsingSubscription(&usingSubscription);
+ if (usingSubscription)
+ {
+ bool folderIsNameSpace = false;
+ bool noDescendentsAreVerified = NoDescendentsAreVerified(childFolder);
+ bool shouldDieBecauseNoSelect = (folderIsNoSelectFolder ?
+ ((noDescendentsAreVerified || AllDescendentsAreNoSelect(childFolder)) && !folderIsNameSpace)
+ : false);
+ if (!childVerified && (noDescendentsAreVerified || shouldDieBecauseNoSelect))
+ {
+ }
+ }
+ else
+ {
+ }
+ }
+ }
+ }
+ }
+
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = curFolder->GetParent(getter_AddRefs(parent));
+
+ if (NS_SUCCEEDED(rv) && parent)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapParent = do_QueryInterface(parent);
+ if (imapParent)
+ imapParent->RemoveSubFolder(curFolder);
+ }
+
+ return rv;
+}
+
+bool nsImapIncomingServer::NoDescendentsAreVerified(nsIMsgFolder *parentFolder)
+{
+ bool nobodyIsVerified = true;
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsresult rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders));
+ if(NS_SUCCEEDED(rv))
+ {
+ bool moreFolders;
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) &&
+ moreFolders && nobodyIsVerified)
+ {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ bool childVerified = false;
+ nsCOMPtr <nsIMsgImapMailFolder> childImapFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && childImapFolder)
+ {
+ nsCOMPtr <nsIMsgFolder> childFolder = do_QueryInterface(child, &rv);
+ rv = childImapFolder->GetVerifiedAsOnlineFolder(&childVerified);
+ nobodyIsVerified = !childVerified && NoDescendentsAreVerified(childFolder);
+ }
+ }
+ }
+ }
+ return nobodyIsVerified;
+}
+
+
+bool nsImapIncomingServer::AllDescendentsAreNoSelect(nsIMsgFolder *parentFolder)
+{
+ bool allDescendentsAreNoSelect = true;
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsresult rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders));
+ if(NS_SUCCEEDED(rv))
+ {
+ bool moreFolders;
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) &&
+ moreFolders && allDescendentsAreNoSelect)
+ {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ bool childIsNoSelect = false;
+ nsCOMPtr <nsIMsgImapMailFolder> childImapFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && childImapFolder)
+ {
+ uint32_t flags;
+ nsCOMPtr <nsIMsgFolder> childFolder = do_QueryInterface(child, &rv);
+ rv = childFolder->GetFlags(&flags);
+ childIsNoSelect = NS_SUCCEEDED(rv) && (flags & nsMsgFolderFlags::ImapNoselect);
+ allDescendentsAreNoSelect = !childIsNoSelect && AllDescendentsAreNoSelect(childFolder);
+ }
+ }
+ }
+ }
+#if 0
+ int numberOfSubfolders = parentFolder->GetNumSubFolders();
+
+ for (int childIndex=0; allDescendantsAreNoSelect && (childIndex < numberOfSubfolders); childIndex++)
+ {
+ MSG_IMAPFolderInfoMail *currentChild = (MSG_IMAPFolderInfoMail *) parentFolder->GetSubFolder(childIndex);
+ allDescendentsAreNoSelect = (currentChild->GetFolderPrefFlags() & MSG_FOLDER_PREF_IMAPNOSELECT) &&
+ AllDescendentsAreNoSelect(currentChild);
+ }
+#endif // 0
+ return allDescendentsAreNoSelect;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::PromptLoginFailed(nsIMsgWindow *aMsgWindow,
+ int32_t *aResult)
+{
+ nsAutoCString hostName;
+ GetRealHostName(hostName);
+
+ return MsgPromptLoginFailed(aMsgWindow, hostName, aResult);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::FEAlert(const nsAString& aAlertString,
+ nsIMsgMailNewsUrl *aUrl)
+{
+ GetStringBundle();
+
+ if (m_stringBundle)
+ {
+ nsAutoString hostName;
+ nsresult rv = GetPrettyName(hostName);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString message;
+ nsString tempString(aAlertString);
+ const char16_t *params[] = { hostName.get(), tempString.get() };
+
+ rv = m_stringBundle->FormatStringFromName(
+ u"imapServerAlert",
+ params, 2, getter_Copies(message));
+ if (NS_SUCCEEDED(rv))
+ return AlertUser(message, aUrl);
+ }
+ }
+ return AlertUser(aAlertString, aUrl);
+}
+
+nsresult nsImapIncomingServer::AlertUser(const nsAString& aString,
+ nsIMsgMailNewsUrl *aUrl)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mailSession->AlertUser(aString, aUrl);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::FEAlertWithName(const char* aMsgName, nsIMsgMailNewsUrl *aUrl)
+{
+ // don't bother the user if we're shutting down.
+ if (m_shuttingDown)
+ return NS_OK;
+
+ GetStringBundle();
+
+ nsString message;
+
+ if (m_stringBundle)
+ {
+ nsAutoCString hostName;
+ nsresult rv = GetHostName(hostName);
+ if (NS_SUCCEEDED(rv))
+ {
+ const NS_ConvertUTF8toUTF16 hostName16(hostName);
+ const char16_t *params[] = { hostName16.get() };
+ rv = m_stringBundle->FormatStringFromName(
+ NS_ConvertASCIItoUTF16(aMsgName).get(),
+ params, 1,getter_Copies(message));
+ if (NS_SUCCEEDED(rv))
+ return AlertUser(message, aUrl);
+ }
+ }
+
+ // Error condition
+ message.AssignLiteral("String Name ");
+ message.AppendASCII(aMsgName);
+ FEAlert(message, aUrl);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::FEAlertFromServer(const nsACString& aServerString,
+ nsIMsgMailNewsUrl *aUrl)
+{
+ NS_ENSURE_TRUE(!aServerString.IsEmpty(), NS_OK);
+
+ nsCString message(aServerString);
+ message.Trim(" \t\b\r\n");
+ if (message.Last() != '.')
+ message.Append('.');
+
+ // Skip over the first two words (the command tag and "NO").
+ // Find the first word break.
+ int32_t pos = message.FindChar(' ');
+
+ // Find the second word break.
+ if (pos != -1)
+ pos = message.FindChar(' ', pos + 1);
+
+ // Adjust the message.
+ if (pos != -1)
+ message = Substring(message, pos + 1);
+
+ nsString hostName;
+ GetPrettyName(hostName);
+
+ const char16_t *formatStrings[] =
+ {
+ hostName.get(),
+ nullptr,
+ nullptr
+ };
+
+ nsString msgName;
+ int32_t numStrings;
+ nsString fullMessage;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl);
+ NS_ENSURE_TRUE(imapUrl, NS_ERROR_INVALID_ARG);
+
+ nsImapState imapState;
+ nsImapAction imapAction;
+
+ imapUrl->GetRequiredImapState(&imapState);
+ imapUrl->GetImapAction(&imapAction);
+ nsString folderName;
+
+ NS_ConvertUTF8toUTF16 unicodeMsg(message);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ if (imapState == nsIImapUrl::nsImapSelectedState ||
+ imapAction == nsIImapUrl::nsImapFolderStatus)
+ {
+ aUrl->GetFolder(getter_AddRefs(folder));
+ if (folder)
+ folder->GetPrettyName(folderName);
+ numStrings = 3;
+ msgName.AssignLiteral("imapFolderCommandFailed");
+ formatStrings[1] = folderName.get();
+ }
+ else
+ {
+ msgName.AssignLiteral("imapServerCommandFailed");
+ numStrings = 2;
+ }
+
+ formatStrings[numStrings -1] = unicodeMsg.get();
+
+ nsresult rv = GetStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (m_stringBundle)
+ {
+ rv = m_stringBundle->FormatStringFromName(msgName.get(),
+ formatStrings, numStrings, getter_Copies(fullMessage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return AlertUser(fullMessage, aUrl);
+}
+
+#define IMAP_MSGS_URL "chrome://messenger/locale/imapMsgs.properties"
+
+nsresult nsImapIncomingServer::GetStringBundle()
+{
+ if (m_stringBundle)
+ return NS_OK;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+ return sBundleService->CreateBundle(IMAP_MSGS_URL, getter_AddRefs(m_stringBundle));
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetImapStringByName(const char* msgName, nsAString& aString)
+{
+ nsresult rv = NS_OK;
+ GetStringBundle();
+ if (m_stringBundle)
+ {
+ nsString res_str;
+ rv = m_stringBundle->GetStringFromName(
+ NS_ConvertASCIItoUTF16(msgName).get(),
+ getter_Copies(res_str));
+ aString.Assign(res_str);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+ aString.AssignLiteral("String Name ");
+ // mscott: FIX ME
+ aString.AppendASCII(msgName);
+ return NS_OK;
+}
+
+nsresult nsImapIncomingServer::ResetFoldersToUnverified(nsIMsgFolder *parentFolder)
+{
+ nsresult rv = NS_OK;
+ if (!parentFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ResetFoldersToUnverified(rootFolder);
+ }
+ else
+ {
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(parentFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapFolder->SetVerifiedAsOnlineFolder(false);
+ rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool moreFolders = false;
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && moreFolders)
+ {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ nsCOMPtr<nsIMsgFolder> childFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && childFolder)
+ {
+ rv = ResetFoldersToUnverified(childFolder);
+ if (NS_FAILED(rv))
+ break;
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+void
+nsImapIncomingServer::GetUnverifiedFolders(nsCOMArray<nsIMsgImapMailFolder> &aFoldersArray)
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ if (NS_FAILED(GetRootFolder(getter_AddRefs(rootFolder))) || !rootFolder)
+ return;
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapRoot(do_QueryInterface(rootFolder));
+ // don't need to verify the root.
+ if (imapRoot)
+ imapRoot->SetVerifiedAsOnlineFolder(true);
+
+ GetUnverifiedSubFolders(rootFolder, aFoldersArray);
+}
+
+void
+nsImapIncomingServer::GetUnverifiedSubFolders(nsIMsgFolder *parentFolder,
+ nsCOMArray<nsIMsgImapMailFolder> &aFoldersArray)
+{
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(parentFolder));
+
+ bool verified = false, explicitlyVerify = false;
+ if (imapFolder)
+ {
+ nsresult rv = imapFolder->GetVerifiedAsOnlineFolder(&verified);
+ if (NS_SUCCEEDED(rv))
+ rv = imapFolder->GetExplicitlyVerify(&explicitlyVerify);
+
+ if (NS_SUCCEEDED(rv) && (!verified || explicitlyVerify))
+ aFoldersArray.AppendObject(imapFolder);
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ if (NS_SUCCEEDED(parentFolder->GetSubFolders(getter_AddRefs(subFolders))))
+ {
+ bool moreFolders;
+
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && moreFolders)
+ {
+ nsCOMPtr<nsISupports> child;
+ subFolders->GetNext(getter_AddRefs(child));
+ if (child)
+ {
+ nsCOMPtr<nsIMsgFolder> childFolder(do_QueryInterface(child));
+ if (childFolder)
+ GetUnverifiedSubFolders(childFolder, aFoldersArray);
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP nsImapIncomingServer::ForgetSessionPassword()
+{
+ nsresult rv = nsMsgIncomingServer::ForgetSessionPassword();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // fix for bugscape bug #15485
+ // if we use turbo, and we logout, we need to make sure
+ // the server doesn't think it's authenticated.
+ // the biff timer continues to fire when you use turbo
+ // (see #143848). if we exited, we've set the password to null
+ // but if we're authenticated, and the biff timer goes off
+ // we'll still perform biff, because we use m_userAuthenticated
+ // to determine if we require a password for biff.
+ // (if authenticated, we don't require a password
+ // see nsMsgBiffManager::PerformBiff())
+ // performing biff without a password will pop up the prompt dialog
+ // which is pretty wacky, when it happens after you quit the application
+ m_userAuthenticated = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff)
+{
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ // if the user has already been authenticated, we've got the password
+ *aServerRequiresPasswordForBiff = !m_userAuthenticated;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::ForgetPassword()
+{
+ return nsMsgIncomingServer::ForgetPassword();
+}
+
+
+NS_IMETHODIMP
+nsImapIncomingServer::AsyncGetPassword(nsIImapProtocol *aProtocol,
+ bool aNewPasswordRequested,
+ nsACString &aPassword)
+{
+ if (m_password.IsEmpty())
+ {
+ // We're now going to need to do something that will end up with us either
+ // poking login manager or prompting the user. We need to ensure we only
+ // do one prompt at a time (and login manager could cause a master password
+ // prompt), so we need to use the async prompter.
+ nsresult rv;
+ nsCOMPtr<nsIMsgAsyncPrompter> asyncPrompter =
+ do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAsyncPromptListener> promptListener(do_QueryInterface(aProtocol));
+ rv = asyncPrompter->QueueAsyncAuthPrompt(m_serverKey, aNewPasswordRequested,
+ promptListener);
+ // Explict NS_ENSURE_SUCCESS for debug purposes as errors tend to get
+ // hidden.
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (!m_password.IsEmpty())
+ aPassword = m_password;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::PromptPassword(nsIMsgWindow *aMsgWindow,
+ nsACString &aPassword)
+{
+ nsString passwordTitle;
+ GetImapStringByName("imapEnterPasswordPromptTitle", passwordTitle);
+ NS_ENSURE_STATE(m_stringBundle);
+
+ nsAutoCString userName;
+ GetRealUsername(userName);
+
+ nsAutoCString hostName;
+ GetRealHostName(hostName);
+
+ nsresult rv = GetStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertASCIItoUTF16 finalUserName(userName);
+ NS_ConvertASCIItoUTF16 finalHostName(hostName);
+
+ const char16_t *formatStrings[] = { finalUserName.get(), finalHostName.get() };
+
+ nsString passwordText;
+ rv = m_stringBundle->FormatStringFromName(
+ u"imapEnterServerPasswordPrompt",
+ formatStrings, 2, getter_Copies(passwordText));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetPasswordWithUI(passwordText, passwordTitle, aMsgWindow, aPassword);
+ if (NS_SUCCEEDED(rv))
+ m_password = aPassword;
+ return rv;
+}
+
+// for the nsIImapServerSink interface
+NS_IMETHODIMP nsImapIncomingServer::SetCapability(eIMAPCapabilityFlags capability)
+{
+ m_capability = capability;
+ SetIsGMailServer((capability & kGmailImapCapability) != 0);
+ SetCapabilityACL(capability & kACLCapability);
+ SetCapabilityQuota(capability & kQuotaCapability);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetServerID(const nsACString &aServerID)
+{
+ return SetServerIDPref(aServerID);
+}
+
+NS_IMETHODIMP nsImapIncomingServer::CommitNamespaces()
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return hostSession->CommitNamespacesForHost(this);
+}
+
+NS_IMETHODIMP nsImapIncomingServer::PseudoInterruptMsgLoad(nsIMsgFolder *aImapFolder, nsIMsgWindow *aMsgWindow,
+ bool *interrupted)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapProtocol> connection;
+ PR_CEnterMonitor(this);
+ // iterate through the connection cache for a connection that is loading
+ // a message in this folder and should be pseudo-interrupted.
+ int32_t cnt = m_connectionCache.Count();
+
+ for (int32_t i = 0; i < cnt; ++i)
+ {
+ connection = m_connectionCache[i];
+ if (connection)
+ rv = connection->PseudoInterruptMsgLoad(aImapFolder, aMsgWindow, interrupted);
+ }
+
+ PR_CExitMonitor(this);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::ResetNamespaceReferences()
+{
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(rootFolder);
+ if (imapFolder)
+ rv = imapFolder->ResetNamespaceReferences();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetUserAuthenticated(bool aUserAuthenticated)
+{
+ m_userAuthenticated = aUserAuthenticated;
+ if (aUserAuthenticated)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ accountManager->SetUserNeedsToAuthenticate(false);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetUserAuthenticated(bool *aUserAuthenticated)
+{
+ NS_ENSURE_ARG_POINTER(aUserAuthenticated);
+ *aUserAuthenticated = m_userAuthenticated;
+ return NS_OK;
+}
+
+/* void SetMailServerUrls (in string manageMailAccount, in string manageLists, in string manageFilters); */
+NS_IMETHODIMP nsImapIncomingServer::SetMailServerUrls(const nsACString& manageMailAccount, const nsACString& manageLists,
+ const nsACString& manageFilters)
+{
+ return SetManageMailAccountUrl(manageMailAccount);
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetManageMailAccountUrl(const nsACString& manageMailAccountUrl)
+{
+ m_manageMailAccountUrl = manageMailAccountUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetManageMailAccountUrl(nsACString& manageMailAccountUrl)
+{
+ manageMailAccountUrl = m_manageMailAccountUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::StartPopulatingWithUri(nsIMsgWindow *aMsgWindow, bool aForceToServer /*ignored*/, const char *uri)
+{
+ NS_ENSURE_ARG_POINTER (uri);
+
+ nsresult rv;
+ mDoingSubscribeDialog = true;
+
+ rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mInner->StartPopulatingWithUri(aMsgWindow, aForceToServer, uri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // imap always uses the canonical delimiter form of paths for subscribe ui.
+ rv = SetDelimiter('/');
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = SetShowFullName(false);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString serverUri;
+ rv = GetServerURI(serverUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+/*
+ if uri = imap://user@host/foo/bar, the serverUri is imap://user@host
+ to get path from uri, skip over imap://user@host + 1 (for the /)
+*/
+ const char *path = uri + serverUri.Length() + 1;
+ return imapService->GetListOfFoldersWithPath(this, aMsgWindow, nsDependentCString(path));
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::StartPopulating(nsIMsgWindow *aMsgWindow, bool aForceToServer /*ignored*/, bool aGetOnlyNew)
+{
+ nsresult rv;
+ mDoingSubscribeDialog = true;
+
+ rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mInner->StartPopulating(aMsgWindow, aForceToServer, aGetOnlyNew);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // imap always uses the canonical delimiter form of paths for subscribe ui.
+ rv = SetDelimiter('/');
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = SetShowFullName(false);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return imapService->GetListOfFoldersOnServer(this, aMsgWindow);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::OnStartRunningUrl(nsIURI *url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::OnStopRunningUrl(nsIURI *url, nsresult exitCode)
+{
+ nsresult rv = exitCode;
+
+ // xxx todo get msgWindow from url
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);
+ if (imapUrl) {
+ nsImapAction imapAction = nsIImapUrl::nsImapTest;
+ imapUrl->GetImapAction(&imapAction);
+ switch (imapAction) {
+ case nsIImapUrl::nsImapDiscoverAllAndSubscribedBoxesUrl:
+ case nsIImapUrl::nsImapDiscoverChildrenUrl:
+ rv = UpdateSubscribed();
+ NS_ENSURE_SUCCESS(rv, rv);
+ mDoingSubscribeDialog = false;
+ rv = StopPopulating(msgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case nsIImapUrl::nsImapDiscoverAllBoxesUrl:
+ if (NS_SUCCEEDED(exitCode))
+ DiscoveryDone();
+ break;
+ case nsIImapUrl::nsImapFolderStatus:
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(imapUrl);
+ mailUrl->GetFolder(getter_AddRefs(msgFolder));
+ if (msgFolder)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool folderOpen;
+ rv = session->IsFolderOpenInWindow(msgFolder, &folderOpen);
+ if (NS_SUCCEEDED(rv) && !folderOpen && msgFolder)
+ msgFolder->SetMsgDatabase(nullptr);
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
+ m_foldersToStat.RemoveObject(imapFolder);
+ }
+ // if we get an error running the url, it's better
+ // not to chain the next url.
+ if (NS_FAILED(exitCode) && exitCode != NS_MSG_ERROR_IMAP_COMMAND_FAILED)
+ m_foldersToStat.Clear();
+ if (m_foldersToStat.Count() > 0)
+ m_foldersToStat[0]->UpdateStatus(this, nullptr);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetIncomingServer(nsIMsgIncomingServer *aServer)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetIncomingServer(aServer);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetShowFullName(bool showFullName)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetShowFullName(showFullName);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetDelimiter(char *aDelimiter)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetDelimiter(aDelimiter);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetDelimiter(char aDelimiter)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetDelimiter(aDelimiter);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetAsSubscribed(const nsACString &path)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetAsSubscribed(path);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::UpdateSubscribed()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::AddTo(const nsACString &aName, bool addAsSubscribed,
+ bool aSubscribable, bool changeIfExists)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // RFC 3501 allows UTF-8 in addition to modified UTF-7
+ // If it's not UTF-8, it cannot be MUTF7, either. We just ignore it.
+ // (otherwise we'll crash. see #63186)
+ if (!MsgIsUTF8(aName))
+ return NS_OK;
+
+ if (!NS_IsAscii(aName.BeginReading(), aName.Length())) {
+ nsAutoCString name;
+ CopyUTF16toMUTF7(NS_ConvertUTF8toUTF16(aName), name);
+ return mInner->AddTo(name, addAsSubscribed, aSubscribable, changeIfExists);
+ }
+ return mInner->AddTo(aName, addAsSubscribed, aSubscribable, changeIfExists);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::StopPopulating(nsIMsgWindow *aMsgWindow)
+{
+ nsCOMPtr<nsISubscribeListener> listener;
+ (void) GetSubscribeListener(getter_AddRefs(listener));
+
+ if (listener)
+ listener->OnDonePopulating();
+
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->StopPopulating(aMsgWindow);
+}
+
+
+NS_IMETHODIMP
+nsImapIncomingServer::SubscribeCleanup()
+{
+ m_subscribeFolders.Clear();
+ return ClearInner();
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetSubscribeListener(nsISubscribeListener *aListener)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetSubscribeListener(aListener);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetSubscribeListener(nsISubscribeListener **aListener)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetSubscribeListener(aListener);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::Subscribe(const char16_t *aName)
+{
+ NS_ENSURE_ARG_POINTER(aName);
+
+ return SubscribeToFolder(nsDependentString(aName), true, nullptr);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::Unsubscribe(const char16_t *aName)
+{
+ NS_ENSURE_ARG_POINTER(aName);
+
+ return SubscribeToFolder(nsDependentString(aName), false, nullptr);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SubscribeToFolder(const nsAString& aName, bool subscribe, nsIURI **aUri)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = GetRootFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Locate the folder so that the correct hierarchical delimiter is used in the
+ // folder pathnames, otherwise root's (ie, '^') is used and this is wrong.
+
+ // aName is not a genuine UTF-16 but just a zero-padded modified UTF-7
+ NS_LossyConvertUTF16toASCII folderCName(aName);
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ if (rootMsgFolder && !aName.IsEmpty())
+ rv = rootMsgFolder->FindSubFolder(folderCName, getter_AddRefs(msgFolder));
+
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+
+ nsAutoString unicodeName;
+ rv = CopyMUTF7toUTF16(folderCName, unicodeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (subscribe)
+ rv = imapService->SubscribeFolder(msgFolder, unicodeName, nullptr, aUri);
+ else
+ rv = imapService->UnsubscribeFolder(msgFolder, unicodeName, nullptr, nullptr);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetDoingLsub(bool doingLsub)
+{
+ mDoingLsub = doingLsub;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetDoingLsub(bool *doingLsub)
+{
+ NS_ENSURE_ARG_POINTER(doingLsub);
+ *doingLsub = mDoingLsub;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::ReDiscoverAllFolders()
+{
+ return PerformExpand(nullptr);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetState(const nsACString &path, bool state,
+ bool *stateChanged)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetState(path, state, stateChanged);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::HasChildren(const nsACString &path, bool *aHasChildren)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->HasChildren(path, aHasChildren);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::IsSubscribed(const nsACString &path,
+ bool *aIsSubscribed)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->IsSubscribed(path, aIsSubscribed);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::IsSubscribable(const nsACString &path, bool *aIsSubscribable)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->IsSubscribable(path, aIsSubscribable);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetLeafName(const nsACString &path, nsAString &aLeafName)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetLeafName(path, aLeafName);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetFirstChildURI(const nsACString &path, nsACString &aResult)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetFirstChildURI(path, aResult);
+}
+
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetChildren(const nsACString &aPath,
+ nsISimpleEnumerator **aResult)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetChildren(aPath, aResult);
+}
+
+nsresult
+nsImapIncomingServer::EnsureInner()
+{
+ nsresult rv = NS_OK;
+
+ if (mInner)
+ return NS_OK;
+
+ mInner = do_CreateInstance(kSubscribableServerCID,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return SetIncomingServer(this);
+}
+
+nsresult
+nsImapIncomingServer::ClearInner()
+{
+ nsresult rv = NS_OK;
+ if (mInner)
+ {
+ rv = mInner->SetSubscribeListener(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mInner->SetIncomingServer(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mInner = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::CommitSubscribeChanges()
+{
+ return ReDiscoverAllFolders();
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanBeDefaultServer(bool *canBeDefaultServer)
+{
+ NS_ENSURE_ARG_POINTER(canBeDefaultServer);
+ *canBeDefaultServer = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanCompactFoldersOnServer(bool *canCompactFoldersOnServer)
+{
+ NS_ENSURE_ARG_POINTER(canCompactFoldersOnServer);
+ // Initialize canCompactFoldersOnServer true, a default value for IMAP
+ *canCompactFoldersOnServer = true;
+ GetPrefForServerAttribute("canCompactFoldersOnServer", canCompactFoldersOnServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanUndoDeleteOnServer(bool *canUndoDeleteOnServer)
+{
+ NS_ENSURE_ARG_POINTER(canUndoDeleteOnServer);
+ // Initialize canUndoDeleteOnServer true, a default value for IMAP
+ *canUndoDeleteOnServer = true;
+ GetPrefForServerAttribute("canUndoDeleteOnServer", canUndoDeleteOnServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanSearchMessages(bool *canSearchMessages)
+{
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ // Initialize canSearchMessages true, a default value for IMAP
+ *canSearchMessages = true;
+ GetPrefForServerAttribute("canSearchMessages", canSearchMessages);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanEmptyTrashOnExit(bool *canEmptyTrashOnExit)
+{
+ NS_ENSURE_ARG_POINTER(canEmptyTrashOnExit);
+ // Initialize canEmptyTrashOnExit true, a default value for IMAP
+ *canEmptyTrashOnExit = true;
+ GetPrefForServerAttribute("canEmptyTrashOnExit", canEmptyTrashOnExit);
+ return NS_OK;
+}
+
+nsresult
+nsImapIncomingServer::CreateHostSpecificPrefName(const char *prefPrefix, nsAutoCString &prefName)
+{
+ NS_ENSURE_ARG_POINTER(prefPrefix);
+
+ nsCString hostName;
+ nsresult rv = GetHostName(hostName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ prefName = prefPrefix;
+ prefName.Append('.');
+ prefName.Append(hostName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetSupportsDiskSpace(bool *aSupportsDiskSpace)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsDiskSpace);
+ nsAutoCString prefName;
+ nsresult rv = CreateHostSpecificPrefName("default_supports_diskspace", prefName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = prefBranch->GetBoolPref(prefName.get(), aSupportsDiskSpace);
+
+ // Couldn't get the default value with the hostname.
+ // Fall back on IMAP default value
+ if (NS_FAILED(rv)) // set default value
+ *aSupportsDiskSpace = true;
+ return NS_OK;
+}
+
+// count number of non-busy connections in cache
+NS_IMETHODIMP
+nsImapIncomingServer::GetNumIdleConnections(int32_t *aNumIdleConnections)
+{
+ NS_ENSURE_ARG_POINTER(aNumIdleConnections);
+ *aNumIdleConnections = 0;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapProtocol> connection;
+ bool isBusy = false;
+ bool isInboxConnection;
+ PR_CEnterMonitor(this);
+
+ int32_t cnt = m_connectionCache.Count();
+
+ // loop counting idle connections
+ for (int32_t i = 0; i < cnt; ++i)
+ {
+ connection = m_connectionCache[i];
+ if (connection)
+ {
+ rv = connection->IsBusy(&isBusy, &isInboxConnection);
+ if (NS_FAILED(rv))
+ continue;
+ if (!isBusy)
+ (*aNumIdleConnections)++;
+ }
+ }
+ PR_CExitMonitor(this);
+ return rv;
+}
+
+
+/**
+ * Get the preference that tells us whether the imap server in question allows
+ * us to create subfolders. Some ISPs might not want users to create any folders
+ * besides the existing ones.
+ * We do want to identify all those servers that don't allow creation of subfolders
+ * and take them out of the account picker in the Copies and Folder panel.
+ */
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer)
+{
+ NS_ENSURE_ARG_POINTER(aCanCreateFoldersOnServer);
+ // Initialize aCanCreateFoldersOnServer true, a default value for IMAP
+ *aCanCreateFoldersOnServer = true;
+ GetPrefForServerAttribute("canCreateFolders", aCanCreateFoldersOnServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetOfflineSupportLevel(int32_t *aSupportLevel)
+{
+ NS_ENSURE_ARG_POINTER(aSupportLevel);
+ nsresult rv = NS_OK;
+
+ rv = GetIntValue("offline_support_level", aSupportLevel);
+ if (*aSupportLevel != OFFLINE_SUPPORT_LEVEL_UNDEFINED)
+ return rv;
+
+ nsAutoCString prefName;
+ rv = CreateHostSpecificPrefName("default_offline_support_level", prefName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = prefBranch->GetIntPref(prefName.get(), aSupportLevel);
+
+ // Couldn't get the pref value with the hostname.
+ // Fall back on IMAP default value
+ if (NS_FAILED(rv)) // set default value
+ *aSupportLevel = OFFLINE_SUPPORT_LEVEL_REGULAR;
+ return NS_OK;
+}
+
+// Called only during the migration process. This routine enables the generation of
+// unique account name based on the username, hostname and the port. If the port
+// is valid and not a default one, it will be appended to the account name.
+NS_IMETHODIMP
+nsImapIncomingServer::GeneratePrettyNameForMigration(nsAString& aPrettyName)
+{
+ nsCString userName;
+ nsCString hostName;
+
+/**
+ * Pretty name for migrated account is of format username@hostname:<port>,
+ * provided the port is valid and not the default
+*/
+ // Get user name to construct pretty name
+ nsresult rv = GetUsername(userName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get host name to construct pretty name
+ rv = GetHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t defaultServerPort;
+ int32_t defaultSecureServerPort;
+
+ // Here, the final contract ID is already known, so use it directly for efficiency.
+ nsCOMPtr <nsIMsgProtocolInfo> protocolInfo = do_GetService(NS_IMAPPROTOCOLINFO_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Get the default port
+ rv = protocolInfo->GetDefaultServerPort(false, &defaultServerPort);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Get the default secure port
+ rv = protocolInfo->GetDefaultServerPort(true, &defaultSecureServerPort);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Get the current server port
+ int32_t serverPort = PORT_NOT_SET;
+ rv = GetPort(&serverPort);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Is the server secure ?
+ int32_t socketType;
+ rv = GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv,rv);
+ bool isSecure = (socketType == nsMsgSocketType::SSL);
+
+ // Is server port a default port ?
+ bool isItDefaultPort = false;
+ if (((serverPort == defaultServerPort) && !isSecure)||
+ ((serverPort == defaultSecureServerPort) && isSecure))
+ isItDefaultPort = true;
+
+ // Construct pretty name from username and hostname
+ nsAutoString constructedPrettyName;
+ CopyASCIItoUTF16(userName,constructedPrettyName);
+ constructedPrettyName.Append('@');
+ constructedPrettyName.Append(NS_ConvertASCIItoUTF16(hostName));
+
+ // If the port is valid and not default, add port value to the pretty name
+ if ((serverPort > 0) && (!isItDefaultPort)) {
+ constructedPrettyName.Append(':');
+ constructedPrettyName.AppendInt(serverPort);
+ }
+
+ // Format the pretty name
+ return GetFormattedStringFromName(constructedPrettyName,
+ "imapDefaultAccountName",
+ aPrettyName);
+}
+
+nsresult
+nsImapIncomingServer::GetFormattedStringFromName(const nsAString& aValue,
+ const char* aName,
+ nsAString& aResult)
+{
+ nsresult rv = GetStringBundle();
+ if (m_stringBundle)
+ {
+ nsString tmpVal (aValue);
+ const char16_t *formatStrings[] = { tmpVal.get() };
+
+ nsString result;
+ rv = m_stringBundle->FormatStringFromName(
+ NS_ConvertASCIItoUTF16(aName).get(),
+ formatStrings, 1, getter_Copies(result));
+ aResult.Assign(result);
+ }
+ return rv;
+}
+
+nsresult
+nsImapIncomingServer::GetPrefForServerAttribute(const char *prefSuffix, bool *prefValue)
+{
+ // Any caller of this function must initialize prefValue with a default value
+ // as this code will not set prefValue when the pref does not exist and return
+ // NS_OK anyway
+
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NS_ENSURE_ARG_POINTER(prefValue);
+
+ if (NS_FAILED(mPrefBranch->GetBoolPref(prefSuffix, prefValue)))
+ mDefPrefBranch->GetBoolPref(prefSuffix, prefValue);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer)
+{
+ NS_ENSURE_ARG_POINTER(aCanFileMessagesOnServer);
+ // Initialize aCanFileMessagesOnServer true, a default value for IMAP
+ *aCanFileMessagesOnServer = true;
+ GetPrefForServerAttribute("canFileMessages", aCanFileMessagesOnServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetSearchValue(const nsAString &searchValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetSupportsSubscribeSearch(bool *retVal)
+{
+ NS_ENSURE_ARG_POINTER(retVal);
+ *retVal = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetFilterScope(nsMsgSearchScopeValue *filterScope)
+{
+ NS_ENSURE_ARG_POINTER(filterScope);
+ // If the inbox is enabled for offline use, then use the offline filter
+ // scope, else use the online filter scope.
+ //
+ // XXX We use the same scope for all folders with the same incoming server,
+ // yet it is possible to set the offline flag separately for each folder.
+ // Manual filters could perhaps check the offline status of each folder,
+ // though it's hard to see how to make that work since we only store filters
+ // per server.
+ //
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> offlineInboxMsgFolder;
+ rv = rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox |
+ nsMsgFolderFlags::Offline,
+ getter_AddRefs(offlineInboxMsgFolder));
+
+ *filterScope = offlineInboxMsgFolder ? nsMsgSearchScope::offlineMailFilter
+ : nsMsgSearchScope::onlineMailFilter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetSearchScope(nsMsgSearchScopeValue *searchScope)
+{
+ NS_ENSURE_ARG_POINTER(searchScope);
+ *searchScope = WeAreOffline() ? nsMsgSearchScope::offlineMail : nsMsgSearchScope::onlineMail;
+ return NS_OK;
+}
+
+// This is a recursive function. It gets new messages for current folder
+// first if it is marked, then calls itself recursively for each subfolder.
+NS_IMETHODIMP
+nsImapIncomingServer::GetNewMessagesForNonInboxFolders(nsIMsgFolder *aFolder,
+ nsIMsgWindow *aWindow,
+ bool forceAllFolders,
+ bool performingBiff)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ static bool gGotStatusPref = false;
+ static bool gUseStatus = false;
+
+ bool isServer;
+ (void) aFolder->GetIsServer(&isServer);
+ // Check this folder for new messages if it is marked to be checked
+ // or if we are forced to check all folders
+ uint32_t flags = 0;
+ aFolder->GetFlags(&flags);
+ nsresult rv;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool canOpen;
+ imapFolder->GetCanOpenFolder(&canOpen);
+ if (canOpen && ((forceAllFolders &&
+ !(flags & (nsMsgFolderFlags::Inbox | nsMsgFolderFlags::Trash |
+ nsMsgFolderFlags::Junk | nsMsgFolderFlags::Virtual))) ||
+ flags & nsMsgFolderFlags::CheckNew))
+ {
+ // Get new messages for this folder.
+ aFolder->SetGettingNewMessages(true);
+ if (performingBiff)
+ imapFolder->SetPerformingBiff(true);
+ bool isOpen = false;
+ nsCOMPtr <nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID);
+ if (mailSession && aFolder)
+ mailSession->IsFolderOpenInWindow(aFolder, &isOpen);
+ // eventually, the gGotStatusPref should go away, once we work out the kinks
+ // from using STATUS.
+ if (!gGotStatusPref)
+ {
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if(prefBranch)
+ prefBranch->GetBoolPref("mail.imap.use_status_for_biff", &gUseStatus);
+ gGotStatusPref = true;
+ }
+ if (gUseStatus && !isOpen)
+ {
+ if (!isServer && m_foldersToStat.IndexOf(imapFolder) == -1)
+ m_foldersToStat.AppendObject(imapFolder);
+ }
+ else
+ aFolder->UpdateFolder(aWindow);
+ }
+
+ // Loop through all subfolders to get new messages for them.
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = aFolder->GetSubFolders(getter_AddRefs(enumerator));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item));
+ if (!msgFolder)
+ {
+ NS_WARNING("Not an nsIMsgFolder");
+ continue;
+ }
+ GetNewMessagesForNonInboxFolders(msgFolder, aWindow, forceAllFolders,
+ performingBiff);
+ }
+ if (isServer && m_foldersToStat.Count() > 0)
+ m_foldersToStat[0]->UpdateStatus(this, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetArbitraryHeaders(nsACString &aResult)
+{
+ nsCOMPtr <nsIMsgFilterList> filterList;
+ nsresult rv = GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return filterList->GetArbitraryHeaders(aResult);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetShowAttachmentsInline(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true; // true per default
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ prefBranch->GetBoolPref("mail.inline_attachments", aResult);
+ return NS_OK; // In case this pref is not set we need to return NS_OK.
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetSocketType(int32_t aSocketType)
+{
+ int32_t oldSocketType;
+ nsresult rv = GetSocketType(&oldSocketType);
+ if (NS_SUCCEEDED(rv) && oldSocketType != aSocketType)
+ CloseCachedConnections();
+ return nsMsgIncomingServer::SetSocketType(aSocketType);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged)
+{
+ nsresult rv;
+ // 1. Do common things in the base class.
+ rv = nsMsgIncomingServer::OnUserOrHostNameChanged(oldName, newName, hostnameChanged);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // 2. Reset 'HaveWeEverDiscoveredFolders' flag so the new folder list can be
+ // reloaded (ie, DiscoverMailboxList() will be invoked in nsImapProtocol).
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList = do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString serverKey;
+ rv = GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ hostSessionList->SetHaveWeEverDiscoveredFoldersForHost(serverKey.get(), false);
+ // 3. Make all the existing folders 'unverified' so that they can be
+ // removed from the folder pane after users log into the new server.
+ ResetFoldersToUnverified(nullptr);
+ return NS_OK;
+}
+
+// use canonical format in originalUri & convertedUri
+NS_IMETHODIMP
+nsImapIncomingServer::GetUriWithNamespacePrefixIfNecessary(int32_t namespaceType,
+ const nsACString& originalUri,
+ nsACString& convertedUri)
+{
+ nsresult rv = NS_OK;
+ nsAutoCString serverKey;
+ rv = GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList = do_GetService(kCImapHostSessionListCID, &rv);
+ nsIMAPNamespace *ns = nullptr;
+ rv = hostSessionList->GetDefaultNamespaceOfTypeForHost(serverKey.get(), (EIMAPNamespaceType)namespaceType, ns);
+ if (ns)
+ {
+ nsAutoCString namespacePrefix(ns->GetPrefix());
+ if (!namespacePrefix.IsEmpty())
+ {
+ // check if namespacePrefix is the same as the online directory; if so, ignore it.
+ nsAutoCString onlineDir;
+ rv = GetServerDirectory(onlineDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!onlineDir.IsEmpty())
+ {
+ char delimiter = ns->GetDelimiter();
+ if ( onlineDir.Last() != delimiter )
+ onlineDir += delimiter;
+ if (onlineDir.Equals(namespacePrefix))
+ return NS_OK;
+ }
+
+ MsgReplaceChar(namespacePrefix, ns->GetDelimiter(), '/'); // use canonical format
+ nsCString uri(originalUri);
+ int32_t index = uri.Find("//"); // find scheme
+ index = uri.FindChar('/', index + 2); // find '/' after scheme
+ // it may be the case that this is the INBOX uri, in which case
+ // we don't want to prepend the namespace. In that case, the uri ends with "INBOX",
+ // but the namespace is "INBOX/", so they don't match.
+ if (MsgFind(uri, namespacePrefix, false, index + 1) != index + 1 &&
+ !MsgLowerCaseEqualsLiteral(Substring(uri, index + 1), "inbox"))
+ uri.Insert(namespacePrefix, index + 1); // insert namespace prefix
+ convertedUri = uri;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetTrashFolderName(nsAString& retval)
+{
+ nsresult rv = GetUnicharValue(PREF_TRASH_FOLDER_NAME, retval);
+ if (NS_FAILED(rv))
+ return rv;
+ if (retval.IsEmpty())
+ retval = NS_LITERAL_STRING(DEFAULT_TRASH_FOLDER_NAME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetTrashFolderName(const nsAString& chvalue)
+{
+ // clear trash flag from the old pref
+ nsAutoString oldTrashName;
+ nsresult rv = GetTrashFolderName(oldTrashName);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString oldTrashNameUtf7;
+ rv = CopyUTF16toMUTF7(oldTrashName, oldTrashNameUtf7);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> oldFolder;
+ rv = GetFolder(oldTrashNameUtf7, getter_AddRefs(oldFolder));
+ if (NS_SUCCEEDED(rv) && oldFolder)
+ oldFolder->ClearFlag(nsMsgFolderFlags::Trash);
+ }
+ }
+ return SetUnicharValue(PREF_TRASH_FOLDER_NAME, chvalue);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetMsgFolderFromURI(nsIMsgFolder *aFolderResource,
+ const nsACString& aURI,
+ nsIMsgFolder **aFolder)
+{
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ bool namespacePrefixAdded = false;
+ nsCString folderUriWithNamespace;
+
+ // Check if the folder exists as is...
+ nsresult rv = GetExistingMsgFolder(aURI, folderUriWithNamespace,
+ namespacePrefixAdded, false,
+ getter_AddRefs(msgFolder));
+
+ // Or try again with a case-insensitive lookup
+ if (NS_FAILED(rv) || !msgFolder)
+ rv = GetExistingMsgFolder(aURI, folderUriWithNamespace,
+ namespacePrefixAdded, true,
+ getter_AddRefs(msgFolder));
+
+ if (NS_FAILED(rv) || !msgFolder) {
+ // we didn't find the folder so we will have to create a new one.
+ if (namespacePrefixAdded)
+ {
+ nsCOMPtr <nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIRDFResource> resource;
+ rv = rdf->GetResource(folderUriWithNamespace, getter_AddRefs(resource));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgFolder> folderResource;
+ folderResource = do_QueryInterface(resource, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ msgFolder = folderResource;
+ }
+ else
+ msgFolder = aFolderResource;
+ }
+
+ msgFolder.swap(*aFolder);
+ return NS_OK;
+}
+
+nsresult
+nsImapIncomingServer::GetExistingMsgFolder(const nsACString& aURI,
+ nsACString& aFolderUriWithNamespace,
+ bool& aNamespacePrefixAdded,
+ bool aCaseInsensitive,
+ nsIMsgFolder **aFolder)
+{
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aNamespacePrefixAdded = false;
+ // Check if the folder exists as is...Even if we have a personal namespace,
+ // it might be in another namespace (e.g., shared) and this will catch that.
+ rv = rootMsgFolder->GetChildWithURI(aURI, true, aCaseInsensitive, aFolder);
+
+ // If we couldn't find the folder as is, check if we need to prepend the
+ // personal namespace
+ if (!*aFolder)
+ {
+ GetUriWithNamespacePrefixIfNecessary(kPersonalNamespace, aURI,
+ aFolderUriWithNamespace);
+ if (!aFolderUriWithNamespace.IsEmpty())
+ {
+ aNamespacePrefixAdded = true;
+ rv = rootMsgFolder->GetChildWithURI(aFolderUriWithNamespace, true,
+ aCaseInsensitive, aFolder);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::CramMD5Hash(const char *decodedChallenge, const char *key, char **result)
+{
+ NS_ENSURE_ARG_POINTER(decodedChallenge);
+ NS_ENSURE_ARG_POINTER(key);
+
+ unsigned char resultDigest[DIGEST_LENGTH];
+ nsresult rv = MSGCramMD5(decodedChallenge, strlen(decodedChallenge), key, strlen(key), resultDigest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *result = (char *) malloc(DIGEST_LENGTH);
+ if (*result)
+ memcpy(*result, resultDigest, DIGEST_LENGTH);
+ return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetLoginUsername(nsACString &aLoginUsername)
+{
+ return GetRealUsername(aLoginUsername);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetOriginalUsername(nsACString &aUsername)
+{
+ return GetUsername(aUsername);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetServerKey(nsACString &aServerKey)
+{
+ return GetKey(aServerKey);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetServerPassword(nsACString &aPassword)
+{
+ return GetPassword(aPassword);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::RemoveServerConnection(nsIImapProtocol* aProtocol)
+{
+ return RemoveConnection(aProtocol);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetServerShuttingDown(bool* aShuttingDown)
+{
+ return GetShuttingDown(aShuttingDown);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::ResetServerConnection(const nsACString& aFolderName)
+{
+ return ResetConnection(aFolderName);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetServerDoingLsub(bool aDoingLsub)
+{
+ return SetDoingLsub(aDoingLsub);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetServerForceSelect(const nsACString &aForceSelect)
+{
+ return SetForceSelect(aForceSelect);
+}
diff --git a/mailnews/imap/src/nsImapIncomingServer.h b/mailnews/imap/src/nsImapIncomingServer.h
new file mode 100644
index 000000000..0948a3178
--- /dev/null
+++ b/mailnews/imap/src/nsImapIncomingServer.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsImapIncomingServer_h
+#define __nsImapIncomingServer_h
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsIImapIncomingServer.h"
+#include "nsMsgIncomingServer.h"
+#include "nsIImapServerSink.h"
+#include "nsIStringBundle.h"
+#include "nsISubscribableServer.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "mozilla/Mutex.h"
+
+class nsIRDFService;
+
+/* get some implementation from nsMsgIncomingServer */
+class nsImapIncomingServer : public nsMsgIncomingServer,
+ public nsIImapIncomingServer,
+ public nsIImapServerSink,
+ public nsISubscribableServer,
+ public nsIUrlListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsImapIncomingServer();
+
+ // overriding nsMsgIncomingServer methods
+ NS_IMETHOD SetKey(const nsACString& aKey) override; // override nsMsgIncomingServer's implementation...
+ NS_IMETHOD GetLocalStoreType(nsACString& type) override;
+ NS_IMETHOD GetLocalDatabaseType(nsACString& type) override;
+
+ NS_DECL_NSIIMAPINCOMINGSERVER
+ NS_DECL_NSIIMAPSERVERSINK
+ NS_DECL_NSISUBSCRIBABLESERVER
+ NS_DECL_NSIURLLISTENER
+
+ NS_IMETHOD PerformBiff(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD PerformExpand(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD CloseCachedConnections() override;
+ NS_IMETHOD GetConstructedPrettyName(nsAString& retval) override;
+ NS_IMETHOD GetCanBeDefaultServer(bool *canBeDefaultServer) override;
+ NS_IMETHOD GetCanCompactFoldersOnServer(bool *canCompactFoldersOnServer
+ ) override;
+ NS_IMETHOD GetCanUndoDeleteOnServer(bool *canUndoDeleteOnServer) override;
+ NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override;
+ NS_IMETHOD GetCanEmptyTrashOnExit(bool *canEmptyTrashOnExit) override;
+ NS_IMETHOD GetOfflineSupportLevel(int32_t *aSupportLevel) override;
+ NS_IMETHOD GeneratePrettyNameForMigration(nsAString& aPrettyName) override;
+ NS_IMETHOD GetSupportsDiskSpace(bool *aSupportsDiskSpace) override;
+ NS_IMETHOD GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer
+ ) override;
+ NS_IMETHOD GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer
+ ) override;
+ NS_IMETHOD GetFilterScope(nsMsgSearchScopeValue *filterScope) override;
+ NS_IMETHOD GetSearchScope(nsMsgSearchScopeValue *searchScope) override;
+ NS_IMETHOD GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff
+ ) override;
+ NS_IMETHOD OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged) override;
+ NS_IMETHOD GetNumIdleConnections(int32_t *aNumIdleConnections);
+ NS_IMETHOD ForgetSessionPassword() override;
+ NS_IMETHOD GetMsgFolderFromURI(nsIMsgFolder *aFolderResource,
+ const nsACString& aURI,
+ nsIMsgFolder **aFolder) override;
+ NS_IMETHOD SetSocketType(int32_t aSocketType) override;
+ NS_IMETHOD VerifyLogon(nsIUrlListener *aUrlListener, nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL) override;
+
+protected:
+ virtual ~nsImapIncomingServer();
+ nsresult GetFolder(const nsACString& name, nsIMsgFolder** pFolder);
+ virtual nsresult CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder) override;
+ nsresult ResetFoldersToUnverified(nsIMsgFolder *parentFolder);
+ void GetUnverifiedSubFolders(nsIMsgFolder *parentFolder,
+ nsCOMArray<nsIMsgImapMailFolder> &aFoldersArray);
+ void GetUnverifiedFolders(nsCOMArray<nsIMsgImapMailFolder> &aFolderArray);
+ nsresult DeleteNonVerifiedFolders(nsIMsgFolder *parentFolder);
+ bool NoDescendentsAreVerified(nsIMsgFolder *parentFolder);
+ bool AllDescendentsAreNoSelect(nsIMsgFolder *parentFolder);
+
+ nsresult GetStringBundle();
+ static nsresult AlertUser(const nsAString& aString, nsIMsgMailNewsUrl *aUrl);
+
+private:
+ nsresult SubscribeToFolder(const char16_t *aName, bool subscribe);
+ nsresult GetImapConnection(nsIImapUrl* aImapUrl,
+ nsIImapProtocol** aImapConnection);
+ nsresult CreateProtocolInstance(nsIImapProtocol ** aImapConnection);
+ nsresult CreateHostSpecificPrefName(const char *prefPrefix, nsAutoCString &prefName);
+
+ nsresult DoomUrlIfChannelHasError(nsIImapUrl *aImapUrl, bool *urlDoomed);
+ bool ConnectionTimeOut(nsIImapProtocol* aImapConnection);
+ nsresult GetFormattedStringFromName(const nsAString& aValue, const char* aName, nsAString& aResult);
+ nsresult GetPrefForServerAttribute(const char *prefSuffix, bool *prefValue);
+ bool CheckSpecialFolder(nsIRDFService *rdf, nsCString &folderUri,
+ uint32_t folderFlag, nsCString &existingUri);
+
+ nsCOMArray<nsIImapProtocol> m_connectionCache;
+ nsCOMArray<nsIImapUrl> m_urlQueue;
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+ nsCOMArray<nsIMsgFolder> m_subscribeFolders; // used to keep folder resources around while subscribe UI is up.
+ nsCOMArray<nsIMsgImapMailFolder> m_foldersToStat; // folders to check for new mail with Status
+ nsTArray<nsISupports*> m_urlConsumers;
+ eIMAPCapabilityFlags m_capability;
+ nsCString m_manageMailAccountUrl;
+ bool m_userAuthenticated;
+ bool mDoingSubscribeDialog;
+ bool mDoingLsub;
+ bool m_shuttingDown;
+
+ mozilla::Mutex mLock;
+ // subscribe dialog stuff
+ nsresult AddFolderToSubscribeDialog(const char *parentUri, const char *uri,const char *folderName);
+ nsCOMPtr <nsISubscribableServer> mInner;
+ nsresult EnsureInner();
+ nsresult ClearInner();
+
+ // Utility function for checking folder existence
+ nsresult GetExistingMsgFolder(const nsACString& aURI,
+ nsACString& folderUriWithNamespace,
+ bool& namespacePrefixAdded,
+ bool caseInsensitive,
+ nsIMsgFolder **aFolder);
+};
+
+#endif
diff --git a/mailnews/imap/src/nsImapMailFolder.cpp b/mailnews/imap/src/nsImapMailFolder.cpp
new file mode 100644
index 000000000..da1411cd0
--- /dev/null
+++ b/mailnews/imap/src/nsImapMailFolder.cpp
@@ -0,0 +1,9837 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "prmem.h"
+#include "nsMsgImapCID.h"
+#include "nsImapMailFolder.h"
+#include "nsIFile.h"
+#include "nsIFolderListener.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsISeekableStream.h"
+#include "nsThreadUtils.h"
+#include "nsIImapUrl.h"
+#include "nsImapUtils.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgKeyArray.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgLocalCID.h"
+#include "nsImapUndoTxn.h"
+#include "nsIIMAPHostSessionList.h"
+#include "nsIMsgCopyService.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsImapStringBundle.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsTextFormatter.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsMsgI18N.h"
+#include "nsICacheSession.h"
+#include "nsIDOMWindow.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgSearchCustomTerm.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsImapMoveCoalescer.h"
+#include "nsIPrompt.h"
+#include "nsIPromptService.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIImapFlagAndUidState.h"
+#include "nsIImapHeaderXferInfo.h"
+#include "nsIMessenger.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIImapMockChannel.h"
+#include "nsIProgressEventSink.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsIMsgOfflineImapOperation.h"
+#include "nsImapOfflineSync.h"
+#include "nsIMsgAccountManager.h"
+#include "nsQuickSort.h"
+#include "nsIImapMockChannel.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+#include "nsIMAPNamespace.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMimeHeaders.h"
+#include "nsIMsgMdnGenerator.h"
+#include "nsISpamSettings.h"
+#include <time.h>
+#include "nsIMsgMailNewsUrl.h"
+#include "nsEmbedCID.h"
+#include "nsIMsgComposeService.h"
+#include "nsMsgCompCID.h"
+#include "nsICacheEntry.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIMsgIdentity.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIExternalProtocolService.h"
+#include "nsCExternalHandlerService.h"
+#include "prprf.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsArrayEnumerator.h"
+#include "nsAutoSyncManager.h"
+#include "nsIMsgFilterCustomAction.h"
+#include "nsMsgReadStateTxn.h"
+#include "nsIStringEnumerator.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsMsgLineBuffer.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Attributes.h"
+#include "nsStringStream.h"
+#include "nsIStreamListener.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+static NS_DEFINE_CID(kParseMailMsgStateCID, NS_PARSEMAILMSGSTATE_CID);
+static NS_DEFINE_CID(kCImapHostSessionList, NS_IIMAPHOSTSESSIONLIST_CID);
+
+extern PRLogModuleInfo *gAutoSyncLog;
+extern PRLogModuleInfo* IMAP;
+
+#define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders"
+
+/*
+ Copies the contents of srcDir into destDir.
+ destDir will be created if it doesn't exist.
+*/
+
+static
+nsresult RecursiveCopy(nsIFile* srcDir, nsIFile* destDir)
+{
+ nsresult rv;
+ bool isDir;
+
+ rv = srcDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ if (!isDir) return NS_ERROR_INVALID_ARG;
+
+ bool exists;
+ rv = destDir->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = destDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ if (NS_FAILED(rv)) return rv;
+
+ bool hasMore = false;
+ nsCOMPtr<nsISimpleEnumerator> dirIterator;
+ rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = dirIterator->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> dirEntry;
+
+ while (hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ rv = dirIterator->GetNext(getter_AddRefs(supports));
+ dirEntry = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(rv) && dirEntry)
+ {
+ rv = dirEntry->IsDirectory(&isDir);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (isDir)
+ {
+ nsCOMPtr<nsIFile> newChild;
+ rv = destDir->Clone(getter_AddRefs(newChild));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoString leafName;
+ dirEntry->GetLeafName(leafName);
+ newChild->AppendRelativePath(leafName);
+ rv = newChild->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = newChild->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ rv = RecursiveCopy(dirEntry, newChild);
+ }
+ }
+ else
+ rv = dirEntry->CopyTo(destDir, EmptyString());
+ }
+
+ }
+ rv = dirIterator->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return rv;
+}
+
+nsImapMailFolder::nsImapMailFolder() :
+ m_initialized(false),m_haveDiscoveredAllFolders(false),
+ m_curMsgUid(0), m_nextMessageByteLength(0),
+ m_urlRunning(false),
+ m_verifiedAsOnlineFolder(false),
+ m_explicitlyVerify(false),
+ m_folderIsNamespace(false),
+ m_folderNeedsSubscribing(false),
+ m_folderNeedsAdded(false),
+ m_folderNeedsACLListed(true),
+ m_performingBiff(false),
+ m_folderQuotaCommandIssued(false),
+ m_folderQuotaDataIsValid(false),
+ m_updatingFolder(false),
+ m_compactingOfflineStore(false),
+ m_expunging(false),
+ m_applyIncomingFilters(false),
+ m_downloadingFolderForOfflineUse(false),
+ m_filterListRequiresBody(false),
+ m_folderQuotaUsedKB(0),
+ m_folderQuotaMaxKB(0)
+{
+ MOZ_COUNT_CTOR(nsImapMailFolder); // double count these for now.
+
+ m_moveCoalescer = nullptr;
+ m_boxFlags = 0;
+ m_uidValidity = kUidUnknown;
+ m_numServerRecentMessages = 0;
+ m_numServerUnseenMessages = 0;
+ m_numServerTotalMessages = 0;
+ m_nextUID = nsMsgKey_None;
+ m_hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
+ m_folderACL = nullptr;
+ m_aclFlags = 0;
+ m_supportedUserFlags = 0;
+ m_namespace = nullptr;
+ m_pendingPlaybackReq = nullptr;
+}
+
+nsImapMailFolder::~nsImapMailFolder()
+{
+ MOZ_COUNT_DTOR(nsImapMailFolder);
+
+ NS_IF_RELEASE(m_moveCoalescer);
+ delete m_folderACL;
+
+ // cleanup any pending request
+ delete m_pendingPlaybackReq;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsImapMailFolder, nsMsgDBFolder)
+NS_IMPL_RELEASE_INHERITED(nsImapMailFolder, nsMsgDBFolder)
+NS_IMPL_QUERY_HEAD(nsImapMailFolder)
+ NS_IMPL_QUERY_BODY(nsIMsgImapMailFolder)
+ NS_IMPL_QUERY_BODY(nsICopyMessageListener)
+ NS_IMPL_QUERY_BODY(nsIImapMailFolderSink)
+ NS_IMPL_QUERY_BODY(nsIImapMessageSink)
+ NS_IMPL_QUERY_BODY(nsIUrlListener)
+ NS_IMPL_QUERY_BODY(nsIMsgFilterHitNotify)
+NS_IMPL_QUERY_TAIL_INHERITING(nsMsgDBFolder)
+
+nsresult nsImapMailFolder::AddDirectorySeparator(nsIFile *path)
+{
+ if (mURI.Equals(kImapRootURI))
+ {
+ // don't concat the full separator with .sbd
+ }
+ else
+ {
+ // see if there's a dir with the same name ending with .sbd
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.Append(NS_LITERAL_STRING(FOLDER_SUFFIX));
+ path->SetLeafName(leafName);
+ }
+
+ return NS_OK;
+}
+
+static bool
+nsShouldIgnoreFile(nsString& name)
+{
+ int32_t len = name.Length();
+ if (len > 4 && name.RFind(SUMMARY_SUFFIX, true) == len - 4)
+ {
+ name.SetLength(len-4); // truncate the string
+ return false;
+ }
+ return true;
+}
+
+nsresult nsImapMailFolder::CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder)
+{
+ nsImapMailFolder *newFolder = new nsImapMailFolder;
+ if (!newFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+ newFolder->Init(uri.get());
+ NS_ADDREF(*folder = newFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::AddSubfolder(const nsAString& aName, nsIMsgFolder** aChild)
+{
+ NS_ENSURE_ARG_POINTER(aChild);
+
+ int32_t flags = 0;
+ nsresult rv;
+ nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+
+ // If AddSubFolder starts getting called for folders other than virtual folders,
+ // we'll have to do convert those names to modified utf-7. For now, the account manager code
+ // that loads the virtual folders for each account, expects utf8 not modified utf-7.
+ nsAutoCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(aName, escapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uri += escapedName.get();
+
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+ rv = GetChildWithURI(uri, false/*deep*/, true /*case Insensitive*/, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder)
+ return NS_MSG_FOLDER_EXISTS;
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIFile> path;
+ rv = CreateDirectoryForFolder(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ folder->GetFlags((uint32_t *)&flags);
+
+ flags |= nsMsgFolderFlags::Mail;
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer)
+ {
+ bool setNewFoldersForOffline = false;
+ rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
+ if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
+ flags |= nsMsgFolderFlags::Offline;
+ }
+
+ folder->SetParent(this);
+
+ folder->SetFlags(flags);
+
+ mSubFolders.AppendObject(folder);
+ folder.swap(*aChild);
+
+ nsCOMPtr <nsIMsgImapMailFolder> imapChild = do_QueryInterface(*aChild);
+ if (imapChild)
+ {
+ imapChild->SetOnlineName(NS_LossyConvertUTF16toASCII(aName));
+ imapChild->SetHierarchyDelimiter(m_hierarchyDelimiter);
+ }
+ NotifyItemAdded(*aChild);
+ return rv;
+}
+
+nsresult nsImapMailFolder::AddSubfolderWithPath(nsAString& name, nsIFile *dbPath,
+ nsIMsgFolder **child, bool brandNew)
+{
+ NS_ENSURE_ARG_POINTER(child);
+ nsresult rv;
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+ AppendUTF16toUTF8(name, uri);
+
+ bool isServer;
+ rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isInbox = isServer && name.LowerCaseEqualsLiteral("inbox");
+
+ //will make sure mSubFolders does not have duplicates because of bogus msf files.
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+ rv = GetChildWithURI(uri, false/*deep*/, isInbox /*case Insensitive*/, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder)
+ return NS_MSG_FOLDER_EXISTS;
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ folder->SetFilePath(dbPath);
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags = 0;
+ folder->GetFlags(&flags);
+
+ folder->SetParent(this);
+ flags |= nsMsgFolderFlags::Mail;
+
+ uint32_t pFlags;
+ GetFlags(&pFlags);
+ bool isParentInbox = pFlags & nsMsgFolderFlags::Inbox;
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ //Only set these if these are top level children or parent is inbox
+ if (isInbox)
+ flags |= nsMsgFolderFlags::Inbox;
+ else if (isServer || isParentInbox)
+ {
+ nsMsgImapDeleteModel deleteModel;
+ imapServer->GetDeleteModel(&deleteModel);
+ if (deleteModel == nsMsgImapDeleteModels::MoveToTrash)
+ {
+ nsAutoString trashName;
+ GetTrashFolderName(trashName);
+ if (name.Equals(trashName))
+ flags |= nsMsgFolderFlags::Trash;
+ }
+ }
+
+ // Make the folder offline if it is newly created and the offline_download
+ // pref is true, unless it's the Trash or Junk folder.
+ if (brandNew && !(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
+ {
+ bool setNewFoldersForOffline = false;
+ rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
+ if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
+ flags |= nsMsgFolderFlags::Offline;
+ }
+
+ folder->SetFlags(flags);
+
+ if (folder)
+ mSubFolders.AppendObject(folder);
+ folder.swap(*child);
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::CreateSubFolders(nsIFile *path)
+{
+ nsresult rv = NS_OK;
+ nsAutoString currentFolderNameStr; // online name
+ nsAutoString currentFolderDBNameStr; // possibly munged name
+ nsCOMPtr<nsIMsgFolder> child;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsISimpleEnumerator> children;
+ rv = path->GetDirectoryEntries(getter_AddRefs(children));
+ bool more = false;
+ if (children)
+ children->HasMoreElements(&more);
+ nsCOMPtr<nsIFile> dirEntry;
+
+ while (more)
+ {
+ nsCOMPtr<nsISupports> supports;
+ rv = children->GetNext(getter_AddRefs(supports));
+ dirEntry = do_QueryInterface(supports);
+ if (NS_FAILED(rv) || !dirEntry)
+ break;
+ rv = children->HasMoreElements(&more);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIFile> currentFolderPath = do_QueryInterface(dirEntry);
+ currentFolderPath->GetLeafName(currentFolderNameStr);
+ if (nsShouldIgnoreFile(currentFolderNameStr))
+ continue;
+
+ // OK, here we need to get the online name from the folder cache if we can.
+ // If we can, use that to create the sub-folder
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ nsCOMPtr <nsIFile> curFolder = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFile> dbFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbFile->InitWithFile(currentFolderPath);
+ curFolder->InitWithFile(currentFolderPath);
+ // don't strip off the .msf in currentFolderPath.
+ currentFolderPath->SetLeafName(currentFolderNameStr);
+ currentFolderDBNameStr = currentFolderNameStr;
+ nsAutoString utf7LeafName = currentFolderNameStr;
+
+ if (curFolder)
+ {
+ rv = GetFolderCacheElemFromFile(dbFile, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(rv) && cacheElement)
+ {
+ nsCString onlineFullUtf7Name;
+
+ uint32_t folderFlags;
+ rv = cacheElement->GetInt32Property("flags", (int32_t *) &folderFlags);
+ if (NS_SUCCEEDED(rv) && folderFlags & nsMsgFolderFlags::Virtual) //ignore virtual folders
+ continue;
+ int32_t hierarchyDelimiter;
+ rv = cacheElement->GetInt32Property("hierDelim", &hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && hierarchyDelimiter == kOnlineHierarchySeparatorUnknown)
+ {
+ currentFolderPath->Remove(false);
+ continue; // blow away .msf files for folders with unknown delimiter.
+ }
+ rv = cacheElement->GetStringProperty("onlineName", onlineFullUtf7Name);
+ if (NS_SUCCEEDED(rv) && !onlineFullUtf7Name.IsEmpty())
+ {
+ CopyMUTF7toUTF16(onlineFullUtf7Name, currentFolderNameStr);
+ char delimiter = 0;
+ GetHierarchyDelimiter(&delimiter);
+ int32_t leafPos = currentFolderNameStr.RFindChar(delimiter);
+ if (leafPos > 0)
+ currentFolderNameStr.Cut(0, leafPos + 1);
+
+ // take the utf7 full online name, and determine the utf7 leaf name
+ CopyASCIItoUTF16(onlineFullUtf7Name, utf7LeafName);
+ leafPos = utf7LeafName.RFindChar(delimiter);
+ if (leafPos > 0)
+ utf7LeafName.Cut(0, leafPos + 1);
+ }
+ }
+ }
+ // make the imap folder remember the file spec it was created with.
+ nsCOMPtr <nsIFile> msfFilePath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msfFilePath->InitWithFile(currentFolderPath);
+ if (NS_SUCCEEDED(rv) && msfFilePath)
+ {
+ // leaf name is the db name w/o .msf (nsShouldIgnoreFile strips it off)
+ // so this trims the .msf off the file spec.
+ msfFilePath->SetLeafName(currentFolderDBNameStr);
+ }
+ // use the utf7 name as the uri for the folder.
+ AddSubfolderWithPath(utf7LeafName, msfFilePath, getter_AddRefs(child));
+ if (child)
+ {
+ // use the unicode name as the "pretty" name. Set it so it won't be
+ // automatically computed from the URI, which is in utf7 form.
+ if (!currentFolderNameStr.IsEmpty())
+ child->SetPrettyName(currentFolderNameStr);
+ child->SetMsgDatabase(nullptr);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetSubFolders(nsISimpleEnumerator **aResult)
+{
+ bool isServer;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_initialized)
+ {
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ // host directory does not need .sbd tacked on
+ if (!isServer)
+ {
+ rv = AddDirectorySeparator(pathFile);
+ if(NS_FAILED(rv)) return rv;
+ }
+
+ m_initialized = true; // need to set this here to avoid infinite recursion from CreateSubfolders.
+ // we have to treat the root folder specially, because it's name
+ // doesn't end with .sbd
+
+ int32_t newFlags = nsMsgFolderFlags::Mail;
+ bool isDirectory = false;
+ pathFile->IsDirectory(&isDirectory);
+ if (isDirectory)
+ {
+ newFlags |= (nsMsgFolderFlags::Directory | nsMsgFolderFlags::Elided);
+ if (!mIsServer)
+ SetFlag(newFlags);
+ rv = CreateSubFolders(pathFile);
+ }
+ if (isServer)
+ {
+ nsCOMPtr <nsIMsgFolder> inboxFolder;
+
+ GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inboxFolder));
+ if (!inboxFolder)
+ {
+ // create an inbox if we don't have one.
+ CreateClientSubfolderInfo(NS_LITERAL_CSTRING("INBOX"), kOnlineHierarchySeparatorUnknown, 0, true);
+ }
+ }
+
+ int32_t count = mSubFolders.Count();
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ for (int32_t i = 0; i < count; i++)
+ mSubFolders[i]->GetSubFolders(getter_AddRefs(dummy));
+
+ UpdateSummaryTotals(false);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER;
+}
+
+//Makes sure the database is open and exists. If the database is valid then
+//returns NS_OK. Otherwise returns a failure error value.
+nsresult nsImapMailFolder::GetDatabase()
+{
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ {
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the database, blowing it away if it needs to be rebuilt
+ rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase));
+ if (NS_FAILED(rv))
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // UpdateNewMessages/UpdateSummaryTotals can null mDatabase, so we save a local copy
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ UpdateNewMessages();
+ if(mAddListener)
+ database->AddListener(this);
+ UpdateSummaryTotals(true);
+ mDatabase = database;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateFolder(nsIMsgWindow * inMsgWindow)
+{
+ return UpdateFolderWithListener(inMsgWindow, nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateFolderWithListener(nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener)
+{
+ nsresult rv;
+ bool selectFolder = false;
+
+ // If this is the inbox, filters will be applied. Otherwise, we test the
+ // inherited folder property "applyIncomingFilters" (which defaults to empty).
+ // If this inherited property has the string value "true", we will apply
+ // filters even if this is not the inbox folder.
+ nsCString applyIncomingFilters;
+ GetInheritedStringProperty("applyIncomingFilters", applyIncomingFilters);
+ m_applyIncomingFilters = applyIncomingFilters.EqualsLiteral("true");
+
+ if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters)
+ {
+ if (!m_filterList)
+ rv = GetFilterList(aMsgWindow, getter_AddRefs(m_filterList));
+ // if there's no msg window, but someone is updating the inbox, we're
+ // doing something biff-like, and may download headers, so make biff notify.
+ if (!aMsgWindow && mFlags & nsMsgFolderFlags::Inbox)
+ SetPerformingBiff(true);
+ }
+
+ if (m_filterList)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool canFileMessagesOnServer = true;
+ rv = server->GetCanFileMessagesOnServer(&canFileMessagesOnServer);
+ // the mdn filter is for filing return receipts into the sent folder
+ // some servers (like AOL mail servers)
+ // can't file to the sent folder, so we don't add the filter for those servers
+ if (canFileMessagesOnServer)
+ {
+ rv = server->ConfigureTemporaryFilters(m_filterList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If a body filter is enabled for an offline folder, delay the filter
+ // application until after message has been downloaded.
+ m_filterListRequiresBody = false;
+
+ if (mFlags & nsMsgFolderFlags::Offline)
+ {
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ uint32_t filterCount = 0;
+ m_filterList->GetFilterCount(&filterCount);
+ for (uint32_t index = 0;
+ index < filterCount && !m_filterListRequiresBody;
+ ++index)
+ {
+ nsCOMPtr<nsIMsgFilter> filter;
+ m_filterList->GetFilterAt(index, getter_AddRefs(filter));
+ if (!filter)
+ continue;
+ nsMsgFilterTypeType filterType;
+ filter->GetFilterType(&filterType);
+ if (!(filterType & nsMsgFilterType::Incoming))
+ continue;
+ bool enabled = false;
+ filter->GetEnabled(&enabled);
+ if (!enabled)
+ continue;
+ nsCOMPtr<nsISupportsArray> searchTerms;
+ uint32_t numSearchTerms = 0;
+ filter->GetSearchTerms(getter_AddRefs(searchTerms));
+ if (searchTerms)
+ searchTerms->Count(&numSearchTerms);
+ for (uint32_t termIndex = 0;
+ termIndex < numSearchTerms && !m_filterListRequiresBody;
+ termIndex++)
+ {
+ nsCOMPtr<nsIMsgSearchTerm> term;
+ rv = searchTerms->QueryElementAt(termIndex,
+ NS_GET_IID(nsIMsgSearchTerm),
+ getter_AddRefs(term));
+ nsMsgSearchAttribValue attrib;
+ rv = term->GetAttrib(&attrib);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (attrib == nsMsgSearchAttrib::Body)
+ m_filterListRequiresBody = true;
+ else if (attrib == nsMsgSearchAttrib::Custom)
+ {
+ nsAutoCString customId;
+ rv = term->GetCustomId(customId);
+ nsCOMPtr<nsIMsgSearchCustomTerm> customTerm;
+ if (NS_SUCCEEDED(rv) && filterService)
+ rv = filterService->GetCustomTerm(customId,
+ getter_AddRefs(customTerm));
+ bool needsBody = false;
+ if (NS_SUCCEEDED(rv) && customTerm)
+ rv = customTerm->GetNeedsBody(&needsBody);
+ if (NS_SUCCEEDED(rv) && needsBody)
+ m_filterListRequiresBody = true;
+ }
+ }
+
+ // Also check if filter actions need the body, as this
+ // is supported in custom actions.
+ uint32_t numActions = 0;
+ filter->GetActionCount(&numActions);
+ for (uint32_t actionIndex = 0;
+ actionIndex < numActions && !m_filterListRequiresBody;
+ actionIndex++)
+ {
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = filter->GetActionAt(actionIndex, getter_AddRefs(action));
+ if (NS_FAILED(rv) || !action)
+ continue;
+
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = action->GetCustomAction(getter_AddRefs(customAction));
+ if (NS_FAILED(rv) || !customAction)
+ continue;
+
+ bool needsBody = false;
+ customAction->GetNeedsBody(&needsBody);
+ if (needsBody)
+ m_filterListRequiresBody = true;
+ }
+ }
+ }
+ }
+
+ selectFolder = true;
+
+ bool isServer;
+ rv = GetIsServer(&isServer);
+ if (NS_SUCCEEDED(rv) && isServer)
+ {
+ if (!m_haveDiscoveredAllFolders)
+ {
+ bool hasSubFolders = false;
+ GetHasSubFolders(&hasSubFolders);
+ if (!hasSubFolders)
+ {
+ rv = CreateClientSubfolderInfo(NS_LITERAL_CSTRING("Inbox"), kOnlineHierarchySeparatorUnknown,0, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ m_haveDiscoveredAllFolders = true;
+ }
+ selectFolder = false;
+ }
+ rv = GetDatabase();
+ if (NS_FAILED(rv))
+ {
+ ThrowAlertMsg("errorGettingDB", aMsgWindow);
+ return rv;
+ }
+ bool canOpenThisFolder = true;
+ GetCanOpenFolder(&canOpenThisFolder);
+
+ bool hasOfflineEvents = false;
+ GetFlag(nsMsgFolderFlags::OfflineEvents, &hasOfflineEvents);
+
+ if (!WeAreOffline())
+ {
+ if (hasOfflineEvents)
+ {
+ // hold a reference to the offline sync object. If ProcessNextOperation
+ // runs a url, a reference will be added to it. Otherwise, it will get
+ // destroyed when the refptr goes out of scope.
+ RefPtr<nsImapOfflineSync> goOnline = new nsImapOfflineSync(aMsgWindow, this, this);
+ if (goOnline)
+ {
+ m_urlListener = aUrlListener;
+ return goOnline->ProcessNextOperation();
+ }
+ }
+ }
+
+ // Check it we're password protecting the local store.
+ if (!PromptForMasterPasswordIfNecessary())
+ return NS_ERROR_FAILURE;
+
+ if (!canOpenThisFolder)
+ selectFolder = false;
+ // don't run select if we can't select the folder...
+ if (NS_SUCCEEDED(rv) && !m_urlRunning && selectFolder)
+ {
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIURI> url;
+ rv = imapService->SelectFolder(this, m_urlListener, aMsgWindow, getter_AddRefs(url));
+ if (NS_SUCCEEDED(rv))
+ {
+ m_urlRunning = true;
+ m_updatingFolder = true;
+ }
+ if (url)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(url, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mailnewsUrl->RegisterListener(this);
+ m_urlListener = aUrlListener;
+ }
+
+ // Allow IMAP folder auto-compact to occur when online or offline.
+ if (aMsgWindow)
+ AutoCompact(aMsgWindow);
+
+ if (rv == NS_MSG_ERROR_OFFLINE || rv == NS_BINDING_ABORTED)
+ {
+ rv = NS_OK;
+ NotifyFolderEvent(mFolderLoadedAtom);
+ }
+ }
+ else if (NS_SUCCEEDED(rv)) // tell the front end that the folder is loaded if we're not going to
+ { // actually run a url.
+ if (!m_updatingFolder) // if we're already running an update url, we'll let that one send the folder loaded
+ NotifyFolderEvent(mFolderLoadedAtom);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetMessages(nsISimpleEnumerator* *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ if (!mDatabase)
+ GetDatabase();
+ if (mDatabase)
+ return mDatabase->EnumerateMessages(result);
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsImapMailFolder::CreateSubfolder(const nsAString& folderName, nsIMsgWindow *msgWindow)
+{
+ if (folderName.IsEmpty())
+ return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ nsresult rv;
+ nsAutoString trashName;
+ GetTrashFolderName(trashName);
+ if ( folderName.Equals(trashName)) // Trash , a special folder
+ {
+ ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ }
+ else if (mIsServer && folderName.LowerCaseEqualsLiteral("inbox")) // Inbox, a special folder
+ {
+ ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ }
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return imapService->CreateFolder(this, folderName, this, nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::CreateClientSubfolderInfo(const nsACString& folderName,
+ char hierarchyDelimiter,
+ int32_t flags,
+ bool suppressNotification)
+{
+ nsresult rv = NS_OK;
+
+ //Get a directory based on our current path.
+ nsCOMPtr <nsIFile> path;
+ rv = CreateDirectoryForFolder(getter_AddRefs(path));
+ if(NS_FAILED(rv))
+ return rv;
+
+ NS_ConvertASCIItoUTF16 leafName(folderName);
+ nsAutoString folderNameStr;
+ nsAutoString parentName = leafName;
+ // use RFind, because folder can start with a delimiter and
+ // not be a leaf folder.
+ int32_t folderStart = leafName.RFindChar('/');
+ if (folderStart > 0)
+ {
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIRDFResource> res;
+ nsCOMPtr<nsIMsgImapMailFolder> parentFolder;
+ nsAutoCString uri (mURI);
+ leafName.Assign(Substring(parentName, folderStart + 1));
+ parentName.SetLength(folderStart);
+
+ rv = CreateDirectoryForFolder(getter_AddRefs(path));
+ if (NS_FAILED(rv))
+ return rv;
+ uri.Append('/');
+ uri.Append(NS_LossyConvertUTF16toASCII(parentName));
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+ parentFolder = do_QueryInterface(res, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString leafnameC;
+ LossyCopyUTF16toASCII(leafName, leafnameC);
+ return parentFolder->CreateClientSubfolderInfo(leafnameC, hierarchyDelimiter,flags, suppressNotification);
+ }
+
+ // if we get here, it's really a leaf, and "this" is the parent.
+ folderNameStr = leafName;
+
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDatabase> mailDBFactory;
+ nsCOMPtr<nsIMsgFolder> child;
+
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDatabase> unusedDB;
+ nsCOMPtr <nsIFile> dbFile;
+
+ // warning, path will be changed
+ rv = CreateFileForDB(folderNameStr, path, getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ //Now let's create the actual new folder
+ rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenMailDBFromFile(dbFile, child, true, true,
+ getter_AddRefs(unusedDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ rv = NS_OK;
+
+ if (NS_SUCCEEDED(rv) && unusedDB)
+ {
+ //need to set the folder name
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString onlineName(m_onlineFolderName);
+ if (!onlineName.IsEmpty())
+ onlineName.Append(hierarchyDelimiter);
+ onlineName.Append(NS_LossyConvertUTF16toASCII(folderNameStr));
+ imapFolder->SetVerifiedAsOnlineFolder(true);
+ imapFolder->SetOnlineName(onlineName);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ imapFolder->SetBoxFlags(flags);
+
+ // Now that the child is created and the boxflags are set we can be sure
+ // all special folder flags are known. The child may get its flags already
+ // in AddSubfolderWithPath if they were in FolderCache, but that's
+ // not always the case.
+ uint32_t flags = 0;
+ child->GetFlags(&flags);
+
+ // Set the offline use flag for the newly created folder if the
+ // offline_download preference is true, unless it's the Trash or Junk
+ // folder.
+ if (!(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
+ {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool setNewFoldersForOffline = false;
+ rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
+ if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
+ flags |= nsMsgFolderFlags::Offline;
+ }
+ else
+ {
+ flags &= ~nsMsgFolderFlags::Offline; // clear offline flag if set
+ }
+
+ flags |= nsMsgFolderFlags::Elided;
+ child->SetFlags(flags);
+
+ nsString unicodeName;
+ rv = CopyMUTF7toUTF16(nsCString(folderName), unicodeName);
+ if (NS_SUCCEEDED(rv))
+ child->SetPrettyName(unicodeName);
+
+ // store the online name as the mailbox name in the db folder info
+ // I don't think anyone uses the mailbox name, so we'll use it
+ // to restore the online name when blowing away an imap db.
+ if (folderInfo)
+ folderInfo->SetMailboxName(NS_ConvertASCIItoUTF16(onlineName));
+ }
+
+ unusedDB->SetSummaryValid(true);
+ unusedDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ unusedDB->Close(true);
+ // don't want to hold onto this newly created db.
+ child->SetMsgDatabase(nullptr);
+ }
+
+ if (!suppressNotification)
+ {
+ nsCOMPtr <nsIAtom> folderCreateAtom;
+ if(NS_SUCCEEDED(rv) && child)
+ {
+ NotifyItemAdded(child);
+ folderCreateAtom = MsgGetAtom("FolderCreateCompleted");
+ child->NotifyFolderEvent(folderCreateAtom);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderAdded(child);
+ }
+ else
+ {
+ folderCreateAtom = MsgGetAtom("FolderCreateFailed");
+ NotifyFolderEvent(folderCreateAtom);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::List()
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return imapService->ListFolder(this, this, nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::RemoveSubFolder (nsIMsgFolder *which)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> folders(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(folders, rv);
+ nsCOMPtr<nsISupports> folderSupport = do_QueryInterface(which, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ folders->AppendElement(folderSupport, false);
+ rv = nsMsgDBFolder::DeleteSubFolders(folders, nullptr);
+ which->Delete();
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::CreateStorageIfMissing(nsIUrlListener* urlListener)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgFolder> msgParent;
+ GetParent(getter_AddRefs(msgParent));
+
+ // parent is probably not set because *this* was probably created by rdf
+ // and not by folder discovery. So, we have to compute the parent.
+ if (!msgParent)
+ {
+ nsAutoCString folderName(mURI);
+
+ int32_t leafPos = folderName.RFindChar('/');
+ nsAutoCString parentName(folderName);
+
+ if (leafPos > 0)
+ {
+ // If there is a hierarchy, there is a parent.
+ // Don't strip off slash if it's the first character
+ parentName.SetLength(leafPos);
+ // get the corresponding RDF resource
+ // RDF will create the folder resource if it doesn't already exist
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIRDFResource> resource;
+ rv = rdf->GetResource(parentName, getter_AddRefs(resource));
+ if (NS_FAILED(rv)) return rv;
+ msgParent = do_QueryInterface(resource, &rv);
+ }
+ }
+ if (msgParent)
+ {
+ nsString folderName;
+ GetName(folderName);
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIURI> uri;
+ imapService->EnsureFolderExists(msgParent, folderName, urlListener, getter_AddRefs(uri));
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP nsImapMailFolder::GetVerifiedAsOnlineFolder(bool *aVerifiedAsOnlineFolder)
+{
+ NS_ENSURE_ARG_POINTER(aVerifiedAsOnlineFolder);
+ *aVerifiedAsOnlineFolder = m_verifiedAsOnlineFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetVerifiedAsOnlineFolder(bool aVerifiedAsOnlineFolder)
+{
+ m_verifiedAsOnlineFolder = aVerifiedAsOnlineFolder;
+ // mark ancestors as verified as well
+ if (aVerifiedAsOnlineFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> parent;
+ do
+ {
+ GetParent(getter_AddRefs(parent));
+ if (parent)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapParent = do_QueryInterface(parent);
+ if (imapParent)
+ {
+ bool verifiedOnline;
+ imapParent->GetVerifiedAsOnlineFolder(&verifiedOnline);
+ if (verifiedOnline)
+ break;
+ imapParent->SetVerifiedAsOnlineFolder(true);
+ }
+ }
+ }
+ while (parent);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOnlineDelimiter(char* onlineDelimiter)
+{
+ return GetHierarchyDelimiter(onlineDelimiter);
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetHierarchyDelimiter(char aHierarchyDelimiter)
+{
+ m_hierarchyDelimiter = aHierarchyDelimiter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetHierarchyDelimiter(char *aHierarchyDelimiter)
+{
+ NS_ENSURE_ARG_POINTER(aHierarchyDelimiter);
+ if (mIsServer)
+ {
+ // if it's the root folder, we don't know the delimiter. So look at the
+ // first child.
+ int32_t count = mSubFolders.Count();
+ if (count > 0)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> childFolder(do_QueryInterface(mSubFolders[0]));
+ if (childFolder)
+ {
+ nsresult rv = childFolder->GetHierarchyDelimiter(aHierarchyDelimiter);
+ // some code uses m_hierarchyDelimiter directly, so we should set it.
+ m_hierarchyDelimiter = *aHierarchyDelimiter;
+ return rv;
+ }
+ }
+ }
+ ReadDBFolderInfo(false); // update cache first.
+ *aHierarchyDelimiter = m_hierarchyDelimiter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetBoxFlags(int32_t aBoxFlags)
+{
+ ReadDBFolderInfo(false);
+
+ m_boxFlags = aBoxFlags;
+ uint32_t newFlags = mFlags;
+
+ newFlags |= nsMsgFolderFlags::ImapBox;
+
+ if (m_boxFlags & kNoinferiors)
+ newFlags |= nsMsgFolderFlags::ImapNoinferiors;
+ else
+ newFlags &= ~nsMsgFolderFlags::ImapNoinferiors;
+ if (m_boxFlags & kNoselect)
+ newFlags |= nsMsgFolderFlags::ImapNoselect;
+ else
+ newFlags &= ~nsMsgFolderFlags::ImapNoselect;
+ if (m_boxFlags & kPublicMailbox)
+ newFlags |= nsMsgFolderFlags::ImapPublic;
+ else
+ newFlags &= ~nsMsgFolderFlags::ImapPublic;
+ if (m_boxFlags & kOtherUsersMailbox)
+ newFlags |= nsMsgFolderFlags::ImapOtherUser;
+ else
+ newFlags &= ~nsMsgFolderFlags::ImapOtherUser;
+ if (m_boxFlags & kPersonalMailbox)
+ newFlags |= nsMsgFolderFlags::ImapPersonal;
+ else
+ newFlags &= ~nsMsgFolderFlags::ImapPersonal;
+
+ // The following are all flags returned by XLIST.
+ // nsImapIncomingServer::DiscoveryDone checks for these folders.
+ if (m_boxFlags & kImapDrafts)
+ newFlags |= nsMsgFolderFlags::Drafts;
+
+ if (m_boxFlags & kImapSpam)
+ newFlags |= nsMsgFolderFlags::Junk;
+
+ if (m_boxFlags & kImapSent)
+ newFlags |= nsMsgFolderFlags::SentMail;
+
+ if (m_boxFlags & kImapInbox)
+ newFlags |= nsMsgFolderFlags::Inbox;
+
+ if (m_boxFlags & kImapXListTrash)
+ {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash;
+ (void) GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer)
+ imapServer->GetDeleteModel(&deleteModel);
+ if (deleteModel == nsMsgImapDeleteModels::MoveToTrash)
+ newFlags |= nsMsgFolderFlags::Trash;
+ }
+ // Treat the GMail all mail folder as the archive folder.
+ if (m_boxFlags & (kImapAllMail | kImapArchive))
+ newFlags |= nsMsgFolderFlags::Archive;
+
+ SetFlags(newFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetBoxFlags(int32_t *aBoxFlags)
+{
+ NS_ENSURE_ARG_POINTER(aBoxFlags);
+ *aBoxFlags = m_boxFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetExplicitlyVerify(bool *aExplicitlyVerify)
+{
+ NS_ENSURE_ARG_POINTER(aExplicitlyVerify);
+ *aExplicitlyVerify = m_explicitlyVerify;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetExplicitlyVerify(bool aExplicitlyVerify)
+{
+ m_explicitlyVerify = aExplicitlyVerify;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetNoSelect(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ return GetFlag(nsMsgFolderFlags::ImapNoselect, aResult);
+}
+
+NS_IMETHODIMP nsImapMailFolder::ApplyRetentionSettings()
+{
+ int32_t numDaysToKeepOfflineMsgs = -1;
+
+ // Check if we've limited the offline storage by age.
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ imapServer->GetAutoSyncMaxAgeDays(&numDaysToKeepOfflineMsgs);
+
+ nsCOMPtr<nsIMsgDatabase> holdDBOpen;
+ if (numDaysToKeepOfflineMsgs > 0)
+ {
+ bool dbWasCached = mDatabase != nullptr;
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ rv = mDatabase->EnumerateMessages(getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool hasMore = false;
+
+ PRTime cutOffDay =
+ MsgConvertAgeInDaysToCutoffDate(numDaysToKeepOfflineMsgs);
+
+ nsCOMPtr <nsIMsgDBHdr> pHeader;
+ // so now cutOffDay is the PRTime cut-off point. Any offline msg with
+ // a date less than that will get marked for pending removal.
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ rv = hdrs->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pHeader = do_QueryInterface(supports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t msgFlags;
+ PRTime msgDate;
+ pHeader->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::Offline)
+ {
+ pHeader->GetDate(&msgDate);
+ MarkPendingRemoval(pHeader, msgDate < cutOffDay);
+ // I'm horribly tempted to break out of the loop if we've found
+ // a message after the cut-off date, because messages will most likely
+ // be in date order in the db, but there are always edge cases.
+ }
+ }
+ if (!dbWasCached)
+ {
+ holdDBOpen = mDatabase;
+ mDatabase = nullptr;
+ }
+ }
+ return nsMsgDBFolder::ApplyRetentionSettings();
+}
+
+/**
+ * The listener will get called when both the online expunge and the offline
+ * store compaction are finished (if the latter is needed).
+ */
+NS_IMETHODIMP nsImapMailFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
+{
+ GetDatabase();
+ // now's a good time to apply the retention settings. If we do delete any
+ // messages, the expunge is going to have to wait until the delete to
+ // finish before it can run, but the multiple-connection protection code
+ // should handle that.
+ if (mDatabase)
+ ApplyRetentionSettings();
+
+ m_urlListener = aListener;
+ // We should be able to compact the offline store now that this should
+ // just be called by the UI.
+ if (aMsgWindow && (mFlags & nsMsgFolderFlags::Offline))
+ {
+ m_compactingOfflineStore = true;
+ CompactOfflineStore(aMsgWindow, this);
+ }
+ if (WeAreOffline())
+ return NS_OK;
+ m_expunging = true;
+ return Expunge(this, aMsgWindow);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::NotifyCompactCompleted()
+{
+ if (!m_expunging && m_urlListener)
+ {
+ m_urlListener->OnStopRunningUrl(nullptr, NS_OK);
+ m_urlListener = nullptr;
+ }
+ m_compactingOfflineStore = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::MarkPendingRemoval(nsIMsgDBHdr *aHdr, bool aMark)
+{
+ NS_ENSURE_ARG_POINTER(aHdr);
+ uint32_t offlineMessageSize;
+ aHdr->GetOfflineMessageSize(&offlineMessageSize);
+ aHdr->SetStringProperty("pendingRemoval", aMark ? "1" : "");
+ if (!aMark)
+ return NS_OK;
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return dbFolderInfo->ChangeExpungedBytes(offlineMessageSize);
+}
+
+NS_IMETHODIMP nsImapMailFolder::Expunge(nsIUrlListener *aListener,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return imapService->Expunge(this, aListener, aMsgWindow, nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::CompactAll(nsIUrlListener *aListener,
+ nsIMsgWindow *aMsgWindow,
+ bool aCompactOfflineAlso)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> folderArray, offlineFolderArray;
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIArray> allDescendents;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ {
+ rootFolder->GetDescendants(getter_AddRefs(allDescendents));
+ uint32_t cnt = 0;
+ rv = allDescendents->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ folderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(folderArray, rv);
+ if (aCompactOfflineAlso)
+ {
+ offlineFolderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(offlineFolderArray, rv);
+ }
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (! (folderFlags & (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect)))
+ {
+ rv = folderArray->AppendElement(folder, false);
+ if (aCompactOfflineAlso)
+ offlineFolderArray->AppendElement(folder, false);
+ }
+ }
+ rv = folderArray->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (cnt == 0)
+ return NotifyCompactCompleted();
+ }
+ nsCOMPtr <nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderCompactor->CompactFolders(folderArray, offlineFolderArray,
+ aListener, aMsgWindow);
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateStatus(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIURI> uri;
+ rv = imapService->UpdateFolderStatus(this, aListener, getter_AddRefs(uri));
+ if (uri && !aMsgWindow)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(uri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // if no msg window, we won't put up error messages (this is almost certainly a biff-inspired status)
+ mailNewsUrl->SetSuppressErrorMsgs(true);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::EmptyTrash(nsIMsgWindow *aMsgWindow, nsIUrlListener *aListener)
+{
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ nsresult rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // if we are emptying trash on exit and we are an aol server then don't perform
+ // this operation because it's causing a hang that we haven't been able to figure out yet
+ // this is an rtm fix and we'll look for the right solution post rtm.
+ bool empytingOnExit = false;
+ accountManager->GetEmptyTrashInProgress(&empytingOnExit);
+ if (empytingOnExit)
+ {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer)
+ {
+ bool isAOLServer = false;
+ imapServer->GetIsAOLServer(&isAOLServer);
+ if (isAOLServer)
+ return NS_ERROR_FAILURE; // we will not be performing an empty trash....
+ } // if we fetched an imap server
+ } // if emptying trash on exit which is done through the account manager.
+
+ if (WeAreOffline())
+ {
+ nsCOMPtr <nsIMsgDatabase> trashDB;
+ rv = trashFolder->GetMsgDatabase(getter_AddRefs(trashDB));
+ if (trashDB)
+ {
+ nsMsgKey fakeKey;
+ trashDB->GetNextFakeOfflineMsgKey(&fakeKey);
+
+ nsCOMPtr <nsIMsgOfflineImapOperation> op;
+ rv = trashDB->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op));
+ trashFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
+ op->SetOperation(nsIMsgOfflineImapOperation::kDeleteAllMsgs);
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aListener)
+ rv = imapService->DeleteAllMessages(trashFolder, aListener, nullptr);
+ else
+ {
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(trashFolder);
+ rv = imapService->DeleteAllMessages(trashFolder, urlListener, nullptr);
+ }
+ // Return an error if this failed. We want the empty trash on exit code
+ // to know if this fails so that it doesn't block waiting for empty trash to finish.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasSubfolders = false;
+ rv = trashFolder->GetHasSubFolders(&hasSubfolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasSubfolders)
+ {
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsCOMPtr<nsISupports> item;
+ nsCOMArray<nsIMsgFolder> array;
+
+ rv = trashFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item, &rv));
+ if (NS_SUCCEEDED(rv))
+ array.AppendObject(folder);
+ }
+ }
+ for (int32_t i = array.Count() - 1; i >= 0; i--)
+ {
+ trashFolder->PropagateDelete(array[i], true, aMsgWindow);
+ // Remove the object, presumably to free it up before we delete the next.
+ array.RemoveObjectAt(i);
+ }
+ }
+
+ nsCOMPtr <nsIDBFolderInfo> transferInfo;
+ rv = trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Bulk-delete all the messages by deleting the msf file and storage.
+ // This is a little kludgy.
+ rv = trashFolder->Delete();
+ NS_ENSURE_SUCCESS(rv, rv);
+ trashFolder->SetDBTransferInfo(transferInfo);
+ trashFolder->SetSizeOnDisk(0);
+
+ // The trash folder has effectively been deleted.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderDeleted(trashFolder);
+
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::Delete()
+{
+ nsresult rv = nsMsgDBFolder::Delete();
+
+ // Should notify nsIMsgFolderListeners about the folder getting deleted?
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::Rename (const nsAString& newName, nsIMsgWindow *msgWindow)
+{
+ if (mFlags & nsMsgFolderFlags::Virtual)
+ return nsMsgDBFolder::Rename(newName, msgWindow);
+ nsresult rv;
+ nsAutoString newNameStr(newName);
+ if (newNameStr.FindChar(m_hierarchyDelimiter, 0) != kNotFound)
+ {
+ nsCOMPtr<nsIDocShell> docShell;
+ if (msgWindow)
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (docShell)
+ {
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle)
+ {
+ const char16_t *formatStrings[] =
+ {
+ (const char16_t*)(intptr_t)m_hierarchyDelimiter
+ };
+ nsString alertString;
+ rv = bundle->FormatStringFromName(
+ u"imapSpecialChar",
+ formatStrings, 1, getter_Copies(alertString));
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ // setting up the dialog title
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString dialogTitle;
+ nsString accountName;
+ rv = server->GetPrettyName(accountName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ const char16_t *titleParams[] = { accountName.get() };
+ rv = bundle->FormatStringFromName(
+ u"imapAlertDialogTitle",
+ titleParams, 1, getter_Copies(dialogTitle));
+
+ if (dialog && !alertString.IsEmpty())
+ dialog->Alert(dialogTitle.get(), alertString.get());
+ }
+ }
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr <nsIImapIncomingServer> incomingImapServer;
+ GetImapIncomingServer(getter_AddRefs(incomingImapServer));
+ if (incomingImapServer)
+ RecursiveCloseActiveConnections(incomingImapServer);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return imapService->RenameLeaf(this, newName, this, msgWindow, nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::RecursiveCloseActiveConnections(nsIImapIncomingServer *incomingImapServer)
+{
+ NS_ENSURE_ARG(incomingImapServer);
+
+ nsCOMPtr<nsIMsgImapMailFolder> folder;
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ folder = do_QueryInterface(mSubFolders[i]);
+ if (folder)
+ folder->RecursiveCloseActiveConnections(incomingImapServer);
+
+ incomingImapServer->CloseConnectionForFolder(mSubFolders[i]);
+ }
+ return NS_OK;
+}
+
+// this is called *after* we've done the rename on the server.
+NS_IMETHODIMP nsImapMailFolder::PrepareToRename()
+{
+ nsCOMPtr<nsIMsgImapMailFolder> folder;
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ folder = do_QueryInterface(mSubFolders[i]);
+ if (folder)
+ folder->PrepareToRename();
+ }
+
+ SetOnlineName(EmptyCString());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RenameLocal(const nsACString& newName, nsIMsgFolder *parent)
+{
+ // XXX Here it's assumed that IMAP folder names are stored locally
+ // in modified UTF-7 (ASCII-only) as is stored remotely. If we ever change
+ // this, we have to work with nsString instead of nsCString
+ // (ref. bug 264071)
+ nsAutoCString leafname(newName);
+ nsAutoCString parentName;
+ // newName always in the canonical form "greatparent/parentname/leafname"
+ int32_t leafpos = leafname.RFindChar('/');
+ if (leafpos >0)
+ leafname.Cut(0, leafpos+1);
+ m_msgParser = nullptr;
+ PrepareToRename();
+ CloseAndBackupFolderDB(leafname);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> oldPathFile;
+ rv = GetFilePath(getter_AddRefs(oldPathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> parentPathFile;
+ rv = parent->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isDirectory = false;
+ parentPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory)
+ AddDirectorySeparator(parentPathFile);
+
+ nsCOMPtr <nsIFile> dirFile;
+
+ int32_t count = mSubFolders.Count();
+ if (count > 0)
+ rv = CreateDirectoryForFolder(getter_AddRefs(dirFile));
+
+ nsCOMPtr <nsIFile> oldSummaryFile;
+ rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newNameStr;
+ oldSummaryFile->Remove(false);
+ if (count > 0)
+ {
+ newNameStr = leafname;
+ NS_MsgHashIfNecessary(newNameStr);
+ newNameStr += ".sbd";
+ nsAutoCString leafName;
+ dirFile->GetNativeLeafName(leafName);
+ if (!leafName.Equals(newNameStr))
+ return dirFile->MoveToNative(nullptr, newNameStr); // in case of rename operation leaf names will differ
+
+ parentPathFile->AppendNative(newNameStr); //only for move we need to progress further in case the parent differs
+ bool isDirectory = false;
+ parentPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory) {
+ rv = parentPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_ERROR("Directory already exists.");
+ }
+ rv = RecursiveCopy(dirFile, parentPathFile);
+ NS_ENSURE_SUCCESS(rv,rv);
+ dirFile->Remove(true); // moving folders
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetPrettyName(nsAString& prettyName)
+{
+ return GetName(prettyName);
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateSummaryTotals(bool force)
+{
+ // bug 72871 inserted the mIsServer check for IMAP
+ return mIsServer? NS_OK : nsMsgDBFolder::UpdateSummaryTotals(force);
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetDeletable (bool *deletable)
+{
+ NS_ENSURE_ARG_POINTER(deletable);
+
+ bool isServer;
+ GetIsServer(&isServer);
+
+ *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetSizeOnDisk(int64_t *size)
+{
+ NS_ENSURE_ARG_POINTER(size);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ // If this is the rootFolder, return 0 as a safe value.
+ if (NS_FAILED(rv) || isServer)
+ mFolderSize = 0;
+
+ *size = mFolderSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCanCreateSubfolders(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = !(mFlags & (nsMsgFolderFlags::ImapNoinferiors | nsMsgFolderFlags::Virtual));
+
+ bool isServer = false;
+ GetIsServer(&isServer);
+ if (!isServer)
+ {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ bool dualUseFolders = true;
+ if (NS_SUCCEEDED(rv) && imapServer)
+ imapServer->GetDualUseFolders(&dualUseFolders);
+ if (!dualUseFolders && *aResult)
+ *aResult = (mFlags & nsMsgFolderFlags::ImapNoselect);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCanSubscribe(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ bool isImapServer = false;
+ nsresult rv = GetIsServer(&isImapServer);
+ if (NS_FAILED(rv)) return rv;
+ // you can only subscribe to imap servers, not imap folders
+ *aResult = isImapServer;
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::GetServerKey(nsACString& serverKey)
+{
+ // look for matching imap folders, then pop folders
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv))
+ rv = server->GetKey(serverKey);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetImapIncomingServer(nsIImapIncomingServer **aImapIncomingServer)
+{
+ NS_ENSURE_ARG(aImapIncomingServer);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
+ {
+ nsCOMPtr <nsIImapIncomingServer> incomingServer = do_QueryInterface(server);
+ incomingServer.swap(*aImapIncomingServer);
+ return NS_OK;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag)
+{
+ nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag);
+
+ // set the mark message answered flag on the server for this message...
+ if (aMessage)
+ {
+ nsMsgKey msgKey;
+ aMessage->GetMessageKey(&msgKey);
+
+ if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied)
+ StoreImapFlags(kImapMsgAnsweredFlag, true, &msgKey, 1, nullptr);
+ else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
+ StoreImapFlags(kImapMsgForwardedFlag, true, &msgKey, 1, nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::MarkMessagesRead(nsIArray *messages, bool markRead)
+{
+ // tell the folder to do it, which will mark them read in the db.
+ nsresult rv = nsMsgDBFolder::MarkMessagesRead(messages, markRead);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keysToMarkRead;
+ rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ StoreImapFlags(kImapMsgSeenFlag, markRead, keysToMarkRead.Elements(), keysToMarkRead.Length(), nullptr);
+ rv = GetDatabase();
+ if (NS_SUCCEEDED(rv))
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetLabelForMessages(nsIArray *aMessages, nsMsgLabelValue aLabel)
+{
+ NS_ENSURE_ARG(aMessages);
+
+ nsresult rv = nsMsgDBFolder::SetLabelForMessages(aMessages, aLabel);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keysToLabel;
+ nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keysToLabel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ StoreImapFlags((aLabel << 9), true, keysToLabel.Elements(), keysToLabel.Length(), nullptr);
+ rv = GetDatabase();
+ if (NS_SUCCEEDED(rv))
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::MarkAllMessagesRead(nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = GetDatabase();
+ if(NS_SUCCEEDED(rv))
+ {
+ nsMsgKey *thoseMarked;
+ uint32_t numMarked;
+ EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/);
+ rv = mDatabase->MarkAllRead(&numMarked, &thoseMarked);
+ EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/);
+ if (NS_SUCCEEDED(rv) && numMarked)
+ {
+ rv = StoreImapFlags(kImapMsgSeenFlag, true, thoseMarked,
+ numMarked, nullptr);
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+
+ // Setup a undo-state
+ if (aMsgWindow)
+ rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked, numMarked);
+ free(thoseMarked);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::MarkThreadRead(nsIMsgThread *thread)
+{
+ nsresult rv = GetDatabase();
+ if(NS_SUCCEEDED(rv))
+ {
+ nsMsgKey *keys;
+ uint32_t numKeys;
+ rv = mDatabase->MarkThreadRead(thread, nullptr, &numKeys, &keys);
+ if (NS_SUCCEEDED(rv) && numKeys)
+ {
+ rv = StoreImapFlags(kImapMsgSeenFlag, true, keys, numKeys, nullptr);
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ free(keys);
+ }
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP nsImapMailFolder::ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element)
+{
+ nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element);
+ int32_t hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
+ nsCString onlineName;
+
+ element->GetInt32Property("boxFlags", &m_boxFlags);
+ if (NS_SUCCEEDED(element->GetInt32Property("hierDelim", &hierarchyDelimiter))
+ && hierarchyDelimiter != kOnlineHierarchySeparatorUnknown)
+ m_hierarchyDelimiter = (char) hierarchyDelimiter;
+ rv = element->GetStringProperty("onlineName", onlineName);
+ if (NS_SUCCEEDED(rv) && !onlineName.IsEmpty())
+ m_onlineFolderName.Assign(onlineName);
+
+ m_aclFlags = kAclInvalid; // init to invalid value.
+ element->GetInt32Property("aclFlags", (int32_t *) &m_aclFlags);
+ element->GetInt32Property("serverTotal", &m_numServerTotalMessages);
+ element->GetInt32Property("serverUnseen", &m_numServerUnseenMessages);
+ element->GetInt32Property("serverRecent", &m_numServerRecentMessages);
+ element->GetInt32Property("nextUID", &m_nextUID);
+ int32_t lastSyncTimeInSec;
+ if ( NS_FAILED(element->GetInt32Property("lastSyncTimeInSec", (int32_t *) &lastSyncTimeInSec)) )
+ lastSyncTimeInSec = 0U;
+
+ // make sure that auto-sync state object is created
+ InitAutoSyncState();
+ m_autoSyncStateObj->SetLastSyncTimeInSec(lastSyncTimeInSec);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::WriteToFolderCacheElem(nsIMsgFolderCacheElement *element)
+{
+ nsresult rv = nsMsgDBFolder::WriteToFolderCacheElem(element);
+ element->SetInt32Property("boxFlags", m_boxFlags);
+ element->SetInt32Property("hierDelim", (int32_t) m_hierarchyDelimiter);
+ element->SetStringProperty("onlineName", m_onlineFolderName);
+ element->SetInt32Property("aclFlags", (int32_t) m_aclFlags);
+ element->SetInt32Property("serverTotal", m_numServerTotalMessages);
+ element->SetInt32Property("serverUnseen", m_numServerUnseenMessages);
+ element->SetInt32Property("serverRecent", m_numServerRecentMessages);
+ if (m_nextUID != (int32_t) nsMsgKey_None)
+ element->SetInt32Property("nextUID", m_nextUID);
+
+ // store folder's last sync time
+ if (m_autoSyncStateObj)
+ {
+ PRTime lastSyncTime;
+ m_autoSyncStateObj->GetLastSyncTime(&lastSyncTime);
+ // store in sec
+ element->SetInt32Property("lastSyncTimeInSec", (int32_t) (lastSyncTime / PR_USEC_PER_SEC));
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::MarkMessagesFlagged(nsIArray *messages, bool markFlagged)
+{
+ nsresult rv;
+ // tell the folder to do it, which will mark them read in the db.
+ rv = nsMsgDBFolder::MarkMessagesFlagged(messages, markFlagged);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keysToMarkFlagged;
+ rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkFlagged);
+ if (NS_FAILED(rv)) return rv;
+ rv = StoreImapFlags(kImapMsgFlaggedFlag, markFlagged, keysToMarkFlagged.Elements(),
+ keysToMarkFlagged.Length(), nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetOnlineName(const nsACString& aOnlineFolderName)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ // do this after GetDBFolderInfoAndDB, because it crunches m_onlineFolderName (not sure why)
+ m_onlineFolderName = aOnlineFolderName;
+ if(NS_SUCCEEDED(rv) && folderInfo)
+ {
+ nsAutoString onlineName;
+ CopyASCIItoUTF16(aOnlineFolderName, onlineName);
+ rv = folderInfo->SetProperty("onlineName", onlineName);
+ rv = folderInfo->SetMailboxName(onlineName);
+ // so, when are we going to commit this? Definitely not every time!
+ // We could check if the online name has changed.
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ folderInfo = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOnlineName(nsACString& aOnlineFolderName)
+{
+ ReadDBFolderInfo(false); // update cache first.
+ aOnlineFolderName = m_onlineFolderName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db)
+{
+ NS_ENSURE_ARG_POINTER (folderInfo);
+ NS_ENSURE_ARG_POINTER (db);
+
+ nsresult rv = GetDatabase();
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ADDREF(*db = mDatabase);
+
+ rv = (*db)->GetDBFolderInfo(folderInfo);
+ if (NS_FAILED(rv))
+ return rv; //GetDBFolderInfo can't return NS_OK if !folderInfo
+
+ nsCString onlineName;
+ rv = (*folderInfo)->GetCharProperty("onlineName", onlineName);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!onlineName.IsEmpty())
+ m_onlineFolderName.Assign(onlineName);
+ else
+ {
+ nsAutoString autoOnlineName;
+ (*folderInfo)->GetMailboxName(autoOnlineName);
+ if (autoOnlineName.IsEmpty())
+ {
+ nsCString uri;
+ rv = GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString hostname;
+ rv = GetHostname(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString onlineCName;
+ rv = nsImapURI2FullName(kImapRootURI, hostname.get(), uri.get(), getter_Copies(onlineCName));
+ if (m_hierarchyDelimiter != '/')
+ MsgReplaceChar(onlineCName, '/', m_hierarchyDelimiter);
+ m_onlineFolderName.Assign(onlineCName);
+ CopyASCIItoUTF16(onlineCName, autoOnlineName);
+ }
+ (*folderInfo)->SetProperty("onlineName", autoOnlineName);
+ }
+ return rv;
+}
+
+/* static */ nsresult
+nsImapMailFolder::BuildIdsAndKeyArray(nsIArray* messages,
+ nsCString& msgIds,
+ nsTArray<nsMsgKey>& keyArray)
+{
+ NS_ENSURE_ARG_POINTER(messages);
+ nsresult rv;
+ uint32_t count = 0;
+ uint32_t i;
+ rv = messages->GetLength(&count);
+ if (NS_FAILED(rv)) return rv;
+
+ // build up message keys.
+ for (i = 0; i < count; i++)
+ {
+ nsMsgKey key;
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr = do_QueryElementAt(messages, i, &rv);
+ if (msgDBHdr)
+ rv = msgDBHdr->GetMessageKey(&key);
+ if (NS_SUCCEEDED(rv))
+ keyArray.AppendElement(key);
+ }
+ return AllocateUidStringFromKeys(keyArray.Elements(), keyArray.Length(), msgIds);
+}
+
+static int CompareKey (const void *v1, const void *v2, void *)
+{
+ // QuickSort callback to compare array values
+ nsMsgKey i1 = *(nsMsgKey *)v1;
+ nsMsgKey i2 = *(nsMsgKey *)v2;
+ return i1 - i2;
+}
+
+/* static */nsresult
+nsImapMailFolder::AllocateUidStringFromKeys(nsMsgKey *keys, uint32_t numKeys, nsCString &msgIds)
+{
+ if (!numKeys)
+ return NS_ERROR_INVALID_ARG;
+ nsresult rv = NS_OK;
+ uint32_t startSequence;
+ startSequence = keys[0];
+ uint32_t curSequenceEnd = startSequence;
+ uint32_t total = numKeys;
+ // sort keys and then generate ranges instead of singletons!
+ NS_QuickSort(keys, numKeys, sizeof(nsMsgKey), CompareKey, nullptr);
+ for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++)
+ {
+ uint32_t curKey = keys[keyIndex];
+ uint32_t nextKey = (keyIndex + 1 < total) ? keys[keyIndex + 1] : 0xFFFFFFFF;
+ bool lastKey = (nextKey == 0xFFFFFFFF);
+
+ if (lastKey)
+ curSequenceEnd = curKey;
+ if (nextKey == (uint32_t) curSequenceEnd + 1 && !lastKey)
+ {
+ curSequenceEnd = nextKey;
+ continue;
+ }
+ else if (curSequenceEnd > startSequence)
+ {
+ AppendUid(msgIds, startSequence);
+ msgIds += ':';
+ AppendUid(msgIds,curSequenceEnd);
+ if (!lastKey)
+ msgIds += ',';
+ startSequence = nextKey;
+ curSequenceEnd = startSequence;
+ }
+ else
+ {
+ startSequence = nextKey;
+ curSequenceEnd = startSequence;
+ AppendUid(msgIds, keys[keyIndex]);
+ if (!lastKey)
+ msgIds += ',';
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapMailFolder::MarkMessagesImapDeleted(nsTArray<nsMsgKey> *keyArray, bool deleted, nsIMsgDatabase *db)
+{
+ for (uint32_t kindex = 0; kindex < keyArray->Length(); kindex++)
+ {
+ nsMsgKey key = keyArray->ElementAt(kindex);
+ db->MarkImapDeleted(key, deleted, nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::DeleteMessages(nsIArray *messages,
+ nsIMsgWindow *msgWindow,
+ bool deleteStorage, bool isMove,
+ nsIMsgCopyServiceListener* listener,
+ bool allowUndo)
+{
+ // *** jt - assuming delete is move to the trash folder for now
+ nsCOMPtr<nsIRDFResource> res;
+ nsAutoCString uri;
+ bool deleteImmediatelyNoTrash = false;
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> srcKeyArray;
+ bool deleteMsgs = true; //used for toggling delete status - default is true
+ nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash;
+ imapMessageFlagsType messageFlags = kImapMsgDeletedFlag;
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv = GetFlag(nsMsgFolderFlags::Trash, &deleteImmediatelyNoTrash);
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+
+ if (NS_SUCCEEDED(rv) && imapServer)
+ {
+ imapServer->GetDeleteModel(&deleteModel);
+ if (deleteModel != nsMsgImapDeleteModels::MoveToTrash || deleteStorage)
+ deleteImmediatelyNoTrash = true;
+ // if we're deleting a message, we should pseudo-interrupt the msg
+ //load of the current message.
+ bool interrupted = false;
+ imapServer->PseudoInterruptMsgLoad(this, msgWindow, &interrupted);
+ }
+
+ rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+
+ if (!deleteImmediatelyNoTrash)
+ {
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
+ getter_AddRefs(trashFolder));
+ NS_ASSERTION(trashFolder, "couldn't find trash");
+ // if we can't find the trash, we'll just have to do an imap delete and pretend this is the trash
+ if (!trashFolder)
+ deleteImmediatelyNoTrash = true;
+ }
+ }
+
+ if ((NS_SUCCEEDED(rv) && deleteImmediatelyNoTrash) || deleteModel == nsMsgImapDeleteModels::IMAPDelete )
+ {
+ if (allowUndo)
+ {
+ //need to take care of these two delete models
+ RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
+ if (!undoMsgTxn || NS_FAILED(undoMsgTxn->Init(this, &srcKeyArray, messageIds.get(), nullptr,
+ true, isMove)))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ // we're adding this undo action before the delete is successful. This is evil,
+ // but 4.5 did it as well.
+ nsCOMPtr <nsITransactionManager> txnMgr;
+ if (msgWindow)
+ msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr)
+ txnMgr->DoTransaction(undoMsgTxn);
+ }
+
+ if (deleteModel == nsMsgImapDeleteModels::IMAPDelete && !deleteStorage)
+ {
+ uint32_t cnt, flags;
+ rv = messages->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ deleteMsgs = false;
+ for (uint32_t i=0; i <cnt; i++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i);
+ if (msgHdr)
+ {
+ msgHdr->GetFlags(&flags);
+ if (!(flags & nsMsgMessageFlags::IMAPDeleted))
+ {
+ deleteMsgs = true;
+ break;
+ }
+ }
+ }
+ }
+ // if copy service listener is also a url listener, pass that
+ // url listener into StoreImapFlags.
+ nsCOMPtr <nsIUrlListener> urlListener = do_QueryInterface(listener);
+ if (deleteMsgs)
+ messageFlags |= kImapMsgSeenFlag;
+ rv = StoreImapFlags(messageFlags, deleteMsgs, srcKeyArray.Elements(),
+ srcKeyArray.Length(), urlListener);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mDatabase)
+ {
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ if (deleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ MarkMessagesImapDeleted(&srcKeyArray, deleteMsgs, database);
+ else
+ {
+ EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/); //"remove it immediately" model
+ // Notify if this is an actual delete.
+ if (!isMove)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsDeleted(messages);
+ }
+ DeleteStoreMessages(messages);
+ database->DeleteMessages(srcKeyArray.Length(), srcKeyArray.Elements(), nullptr);
+ EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/);
+ }
+ NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ }
+ }
+ return rv;
+ }
+ else // have to move the messages to the trash
+ {
+ if(trashFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ nsCOMPtr<nsISupports>srcSupport;
+ uint32_t count = 0;
+ rv = messages->GetLength(&count);
+
+ rv = QueryInterface(NS_GET_IID(nsIMsgFolder), getter_AddRefs(srcFolder));
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyMessages(srcFolder, messages, trashFolder, true, listener, msgWindow, allowUndo);
+ }
+ }
+ return rv;
+}
+
+// check if folder is the trash, or a descendent of the trash
+// so we can tell if the folders we're deleting from it should
+// be *really* deleted.
+bool
+nsImapMailFolder::TrashOrDescendentOfTrash(nsIMsgFolder* folder)
+{
+ NS_ENSURE_TRUE(folder, false);
+ nsCOMPtr<nsIMsgFolder> parent;
+ nsCOMPtr<nsIMsgFolder> curFolder = folder;
+ nsresult rv;
+ uint32_t flags = 0;
+ do
+ {
+ rv = curFolder->GetFlags(&flags);
+ if (NS_FAILED(rv)) return false;
+ if (flags & nsMsgFolderFlags::Trash)
+ return true;
+ curFolder->GetParent(getter_AddRefs(parent));
+ if (!parent) return false;
+ curFolder = parent;
+ } while (NS_SUCCEEDED(rv) && curFolder);
+ return false;
+}
+NS_IMETHODIMP
+nsImapMailFolder::DeleteSubFolders(nsIArray* folders, nsIMsgWindow *msgWindow)
+{
+ nsCOMPtr<nsIMsgFolder> curFolder;
+ nsCOMPtr<nsIUrlListener> urlListener;
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ int32_t i;
+ uint32_t folderCount = 0;
+ nsresult rv;
+ // "this" is the folder we're deleting from
+ bool deleteNoTrash = TrashOrDescendentOfTrash(this) || !DeleteIsMoveToTrash();
+ bool confirmed = false;
+ bool confirmDeletion = true;
+
+ nsCOMPtr<nsIMutableArray> foldersRemaining(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ folders->GetLength(&folderCount);
+
+ for (i = folderCount - 1; i >= 0; i--)
+ {
+ curFolder = do_QueryElementAt(folders, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t folderFlags;
+ curFolder->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Virtual)
+ {
+ RemoveSubFolder(curFolder);
+ // since the folder pane only allows single selection, we can do this
+ deleteNoTrash = confirmed = true;
+ confirmDeletion = false;
+ }
+ else
+ foldersRemaining->InsertElementAt(curFolder, 0, false);
+ }
+ }
+
+ foldersRemaining->GetLength(&folderCount);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!deleteNoTrash)
+ {
+ rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ //If we can't find the trash folder and we are supposed to move it to the trash
+ //return failure.
+ if(NS_FAILED(rv) || !trashFolder)
+ return NS_ERROR_FAILURE;
+ bool canHaveSubFoldersOfTrash = true;
+ trashFolder->GetCanCreateSubfolders(&canHaveSubFoldersOfTrash);
+ if (canHaveSubFoldersOfTrash) // UW server doesn't set NOINFERIORS - check dual use pref
+ {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool serverSupportsDualUseFolders;
+ imapServer->GetDualUseFolders(&serverSupportsDualUseFolders);
+ if (!serverSupportsDualUseFolders)
+ canHaveSubFoldersOfTrash = false;
+ }
+ if (!canHaveSubFoldersOfTrash)
+ deleteNoTrash = true;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash", &confirmDeletion);
+ }
+ if (!confirmed && (confirmDeletion || deleteNoTrash)) //let us alert the user if we are deleting folder immediately
+ {
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = curFolder->GetName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ const char16_t *formatStrings[1] = { folderName.get() };
+
+ nsAutoString deleteFolderDialogTitle;
+ rv = bundle->GetStringFromName(
+ u"imapDeleteFolderDialogTitle",
+ getter_Copies(deleteFolderDialogTitle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString deleteFolderButtonLabel;
+ rv = bundle->GetStringFromName(
+ u"imapDeleteFolderButtonLabel",
+ getter_Copies(deleteFolderButtonLabel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString confirmationStr;
+ rv = bundle->FormatStringFromName((deleteNoTrash) ?
+ u"imapDeleteNoTrash" :
+ u"imapMoveFolderToTrash",
+ formatStrings, 1, getter_Copies(confirmationStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!msgWindow)
+ return NS_ERROR_NULL_POINTER;
+ nsCOMPtr<nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ nsCOMPtr<nsIPrompt> dialog;
+ if (docShell)
+ dialog = do_GetInterface(docShell);
+ if (dialog)
+ {
+ int32_t buttonPressed = 0;
+ // Default the dialog to "cancel".
+ const uint32_t buttonFlags =
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);
+
+ bool dummyValue = false;
+ rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(), confirmationStr.get(),
+ buttonFlags, deleteFolderButtonLabel.get(),
+ nullptr, nullptr, nullptr, &dummyValue,
+ &buttonPressed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ confirmed = !buttonPressed; // "ok" is in position 0
+ }
+ }
+ else
+ confirmed = true;
+
+ if (confirmed)
+ {
+ for (i = 0; i < (int32_t) folderCount; i++)
+ {
+ curFolder = do_QueryElementAt(foldersRemaining, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ urlListener = do_QueryInterface(curFolder);
+ if (deleteNoTrash)
+ rv = imapService->DeleteFolder(curFolder,
+ urlListener,
+ msgWindow,
+ nullptr);
+ else
+ {
+ bool confirm = false;
+ bool match = false;
+ rv = curFolder->MatchOrChangeFilterDestination(nullptr, false, &match);
+ if (match)
+ {
+ curFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirm);
+ if (!confirm)
+ return NS_OK;
+ }
+ rv = imapService->MoveFolder(curFolder,
+ trashFolder,
+ urlListener,
+ msgWindow,
+ nullptr);
+ }
+ }
+ }
+ }
+ //delete subfolders only if you are deleting things from trash
+ return confirmed && deleteNoTrash ? nsMsgDBFolder::DeleteSubFolders(foldersRemaining, msgWindow) : rv;
+}
+
+// FIXME: helper function to know whether we should check all IMAP folders
+// for new mail; this is necessary because of a legacy hidden preference
+// mail.check_all_imap_folders_for_new (now replaced by per-server preference
+// mail.server.%serverkey%.check_all_folders_for_new), still present in some
+// profiles.
+/*static*/
+bool nsImapMailFolder::ShouldCheckAllFolders(nsIImapIncomingServer *imapServer)
+{
+ // Check legacy global preference to see if we should check all folders for
+ // new messages, or just the inbox and marked ones.
+ bool checkAllFolders = false;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ // This pref might not exist, which is OK.
+ (void) prefBranch->GetBoolPref("mail.check_all_imap_folders_for_new", &checkAllFolders);
+
+ if (checkAllFolders)
+ return true;
+
+ // If the legacy preference doesn't exist or has its default value (False),
+ // the true preference is read.
+ imapServer->GetCheckAllFoldersForNew(&checkAllFolders);
+ return checkAllFolders;
+}
+
+// Called by Biff, or when user presses GetMsg button.
+NS_IMETHODIMP nsImapMailFolder::GetNewMessages(nsIMsgWindow *aWindow, nsIUrlListener *aListener)
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if(NS_SUCCEEDED(rv) && rootFolder)
+ {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool performingBiff = false;
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer = do_QueryInterface(imapServer, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ incomingServer->GetPerformingBiff(&performingBiff);
+ m_urlListener = aListener;
+
+ // See if we should check all folders for new messages, or just the inbox
+ // and marked ones
+ bool checkAllFolders = ShouldCheckAllFolders(imapServer);
+
+ // Get new messages for inbox
+ nsCOMPtr<nsIMsgFolder> inbox;
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(inbox));
+ if (inbox)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(inbox, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ imapFolder->SetPerformingBiff(performingBiff);
+ inbox->SetGettingNewMessages(true);
+ rv = inbox->UpdateFolder(aWindow);
+ }
+ // Get new messages for other folders if marked, or all of them if the pref is set
+ rv = imapServer->GetNewMessagesForNonInboxFolders(rootFolder, aWindow, checkAllFolders, performingBiff);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::Shutdown(bool shutdownChildren)
+{
+ m_filterList = nullptr;
+ m_initialized = false;
+ // mPath is used to decide if folder pathname needs to be reconstructed in GetPath().
+ mPath = nullptr;
+ NS_IF_RELEASE(m_moveCoalescer);
+ m_msgParser = nullptr;
+ if (m_playbackTimer)
+ {
+ m_playbackTimer->Cancel();
+ m_playbackTimer = nullptr;
+ }
+ m_pendingOfflineMoves.Clear();
+ return nsMsgDBFolder::Shutdown(shutdownChildren);
+}
+
+nsresult nsImapMailFolder::GetBodysToDownload(nsTArray<nsMsgKey> *keysOfMessagesToDownload)
+{
+ NS_ENSURE_ARG(keysOfMessagesToDownload);
+ NS_ENSURE_TRUE(mDatabase, NS_ERROR_FAILURE);
+
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ nsresult rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ bool hasMore;
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool shouldStoreMsgOffline = false;
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ // MsgFitsDownloadCriteria ignores nsMsgFolderFlags::Offline, which we want
+ if (m_downloadingFolderForOfflineUse)
+ MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline);
+ else
+ ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
+ if (shouldStoreMsgOffline)
+ keysOfMessagesToDownload->AppendElement(msgKey);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::OnNewIdleMessages()
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool checkAllFolders = ShouldCheckAllFolders(imapServer);
+
+ // only trigger biff if we're checking all new folders for new messages, or this particular folder,
+ // but excluding trash,junk, sent, and no select folders, by default.
+ if ((checkAllFolders &&
+ !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk | nsMsgFolderFlags::SentMail | nsMsgFolderFlags::ImapNoselect)))
+ || (mFlags & (nsMsgFolderFlags::CheckNew|nsMsgFolderFlags::Inbox)))
+ SetPerformingBiff(true);
+ return UpdateFolder(nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxInfo(nsIImapProtocol* aProtocol, nsIMailboxSpec* aSpec)
+{
+ nsresult rv;
+ ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
+ ChangeNumPendingUnread(-mNumPendingUnreadMessages);
+ m_numServerRecentMessages = 0; // clear this since we selected the folder.
+ m_numServerUnseenMessages = 0; // clear this since we selected the folder.
+
+ if (!mDatabase)
+ GetDatabase();
+
+ bool folderSelected;
+ rv = aSpec->GetFolderSelected(&folderSelected);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsTArray<nsMsgKey> existingKeys;
+ nsTArray<nsMsgKey> keysToDelete;
+ uint32_t numNewUnread;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ int32_t imapUIDValidity = 0;
+ if (mDatabase)
+ {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo)
+ {
+ dbFolderInfo->GetImapUidValidity(&imapUIDValidity);
+ uint64_t mailboxHighestModSeq;
+ aSpec->GetHighestModSeq(&mailboxHighestModSeq);
+ char intStrBuf[40];
+ PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", mailboxHighestModSeq);
+ dbFolderInfo->SetCharProperty(kModSeqPropertyName, nsDependentCString(intStrBuf));
+ }
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ if (!keys)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = mDatabase->ListAllKeys(keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ existingKeys.AppendElements(keys->m_keys);
+ mDatabase->ListAllOfflineDeletes(&existingKeys);
+ }
+ int32_t folderValidity;
+ aSpec->GetFolder_UIDVALIDITY(&folderValidity);
+ nsCOMPtr <nsIImapFlagAndUidState> flagState;
+ aSpec->GetFlagState(getter_AddRefs(flagState));
+
+ // remember what the supported user flags are.
+ uint32_t supportedUserFlags;
+ aSpec->GetSupportedUserFlags(&supportedUserFlags);
+ SetSupportedUserFlags(supportedUserFlags);
+
+ m_uidValidity = folderValidity;
+
+ if (imapUIDValidity != folderValidity)
+ {
+ NS_ASSERTION(imapUIDValidity == kUidUnknown,
+ "uid validity seems to have changed, blowing away db");
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIDBFolderInfo> transferInfo;
+ if (dbFolderInfo)
+ dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo));
+
+ // A backup message database might have been created earlier, for example
+ // if the user requested a reindex. We'll use the earlier one if we can,
+ // otherwise we'll try to backup at this point.
+ nsresult rvbackup = OpenBackupMsgDatabase();
+ if (mDatabase)
+ {
+ dbFolderInfo = nullptr;
+ if (NS_FAILED(rvbackup))
+ {
+ CloseAndBackupFolderDB(EmptyCString());
+ if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase)
+ {
+ mBackupDatabase->RemoveListener(this);
+ mBackupDatabase = nullptr;
+ }
+ }
+ else
+ mDatabase->ForceClosed();
+ }
+ mDatabase = nullptr;
+
+ nsCOMPtr <nsIFile> summaryFile;
+ rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile));
+ // Remove summary file.
+ if (NS_SUCCEEDED(rv) && summaryFile)
+ summaryFile->Remove(false);
+
+ // Create a new summary file, update the folder message counts, and
+ // Close the summary file db.
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+
+ if (NS_FAILED(rv) && mDatabase)
+ {
+ mDatabase->ForceClosed();
+ mDatabase = nullptr;
+ }
+ else if (NS_SUCCEEDED(rv) && mDatabase)
+ {
+ if (transferInfo)
+ SetDBTransferInfo(transferInfo);
+
+ SummaryChanged();
+ if (mDatabase)
+ {
+ if(mAddListener)
+ mDatabase->AddListener(this);
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ }
+ }
+ // store the new UIDVALIDITY value
+
+ if (NS_SUCCEEDED(rv) && dbFolderInfo)
+ {
+ dbFolderInfo->SetImapUidValidity(folderValidity);
+ // need to forget highest mod seq when uid validity rolls.
+ dbFolderInfo->SetCharProperty(kModSeqPropertyName, EmptyCString());
+ dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName, 0);
+ }
+ // delete all my msgs, the keys are bogus now
+ // add every message in this folder
+ existingKeys.Clear();
+ // keysToDelete.CopyArray(&existingKeys);
+
+ if (flagState)
+ {
+ nsTArray<nsMsgKey> no_existingKeys;
+ FindKeysToAdd(no_existingKeys, m_keysToFetch, numNewUnread, flagState);
+ }
+ if (NS_FAILED(rv))
+ pathFile->Remove(false);
+
+ }
+ else if (!flagState /*&& !NET_IsOffline() */) // if there are no messages on the server
+ keysToDelete = existingKeys;
+ else /* if ( !NET_IsOffline()) */
+ {
+ uint32_t boxFlags;
+ aSpec->GetBox_flags(&boxFlags);
+ // FindKeysToDelete and FindKeysToAdd require sorted lists
+ existingKeys.Sort();
+ FindKeysToDelete(existingKeys, keysToDelete, flagState, boxFlags);
+ // if this is the result of an expunge then don't grab headers
+ if (!(boxFlags & kJustExpunged))
+ FindKeysToAdd(existingKeys, m_keysToFetch, numNewUnread, flagState);
+ }
+ m_totalKeysToFetch = m_keysToFetch.Length();
+ if (!keysToDelete.IsEmpty() && mDatabase)
+ {
+ nsCOMPtr<nsIMutableArray> hdrsToDelete(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ MsgGetHeadersFromKeys(mDatabase, keysToDelete, hdrsToDelete);
+ // Notify nsIMsgFolderListeners of a mass delete, but only if we actually have headers
+ uint32_t numHdrs;
+ hdrsToDelete->GetLength(&numHdrs);
+ if (numHdrs)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsDeleted(hdrsToDelete);
+ }
+ DeleteStoreMessages(hdrsToDelete);
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false, false);
+ mDatabase->DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(), nullptr);
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true, false);
+ }
+ int32_t numUnreadFromServer;
+ aSpec->GetNumUnseenMessages(&numUnreadFromServer);
+
+ bool partialUIDFetch;
+ flagState->GetPartialUIDFetch(&partialUIDFetch);
+
+ // For partial UID fetches, we can only trust the numUnread from the server.
+ if (partialUIDFetch)
+ numNewUnread = numUnreadFromServer;
+
+ // If we are performing biff for this folder, tell the
+ // stand-alone biff about the new high water mark
+ if (m_performingBiff && numNewUnread)
+ {
+ // We must ensure that the server knows that we are performing biff.
+ // Otherwise the stand-alone biff won't fire.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
+ server->SetPerformingBiff(true);
+ SetNumNewMessages(numNewUnread);
+ }
+ SyncFlags(flagState);
+ if (mDatabase && (int32_t) (mNumUnreadMessages + m_keysToFetch.Length()) > numUnreadFromServer)
+ mDatabase->SyncCounts();
+
+ if (!m_keysToFetch.IsEmpty() && aProtocol)
+ PrepareToAddHeadersToMailDB(aProtocol);
+ else
+ {
+ bool gettingNewMessages;
+ GetGettingNewMessages(&gettingNewMessages);
+ if (gettingNewMessages)
+ ProgressStatusString(aProtocol, "imapNoNewMessages", nullptr);
+ SetPerformingBiff(false);
+ }
+ aSpec->GetNumMessages(&m_numServerTotalMessages);
+ aSpec->GetNumUnseenMessages(&m_numServerUnseenMessages);
+ aSpec->GetNumRecentMessages(&m_numServerRecentMessages);
+
+ // some servers don't return UIDNEXT on SELECT - don't crunch
+ // existing values in that case.
+ int32_t nextUID;
+ aSpec->GetNextUID(&nextUID);
+ if (nextUID != (int32_t) nsMsgKey_None)
+ m_nextUID = nextUID;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxStatus(
+ nsIImapProtocol* aProtocol, nsIMailboxSpec* aSpec)
+{
+ NS_ENSURE_ARG_POINTER(aSpec);
+ int32_t numUnread, numTotal;
+ aSpec->GetNumUnseenMessages(&numUnread);
+ aSpec->GetNumMessages(&numTotal);
+ aSpec->GetNumRecentMessages(&m_numServerRecentMessages);
+ int32_t prevNextUID = m_nextUID;
+ aSpec->GetNextUID(&m_nextUID);
+ bool summaryChanged = false;
+
+ // If m_numServerUnseenMessages is 0, it means
+ // this is the first time we've done a Status.
+ // In that case, we count all the previous pending unread messages we know about
+ // as unread messages.
+ // We may want to do similar things with total messages, but the total messages
+ // include deleted messages if the folder hasn't been expunged.
+ int32_t previousUnreadMessages = (m_numServerUnseenMessages)
+ ? m_numServerUnseenMessages : mNumPendingUnreadMessages + mNumUnreadMessages;
+ if (numUnread != previousUnreadMessages || m_nextUID != prevNextUID)
+ {
+ int32_t unreadDelta = numUnread - (mNumPendingUnreadMessages + mNumUnreadMessages);
+ if (numUnread - previousUnreadMessages != unreadDelta)
+ NS_WARNING("unread count should match server count");
+ ChangeNumPendingUnread(unreadDelta);
+ if (unreadDelta > 0 &&
+ !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
+ {
+ SetHasNewMessages(true);
+ SetNumNewMessages(unreadDelta);
+ SetBiffState(nsMsgBiffState_NewMail);
+ }
+ summaryChanged = true;
+ }
+ SetPerformingBiff(false);
+ if (m_numServerUnseenMessages != numUnread || m_numServerTotalMessages != numTotal)
+ {
+ if (numUnread > m_numServerUnseenMessages ||
+ m_numServerTotalMessages > numTotal)
+ NotifyHasPendingMsgs();
+ summaryChanged = true;
+ m_numServerUnseenMessages = numUnread;
+ m_numServerTotalMessages = numTotal;
+ }
+ if (summaryChanged)
+ SummaryChanged();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::ParseMsgHdrs(nsIImapProtocol *aProtocol, nsIImapHeaderXferInfo *aHdrXferInfo)
+{
+ NS_ENSURE_ARG_POINTER(aHdrXferInfo);
+ int32_t numHdrs;
+ nsCOMPtr <nsIImapHeaderInfo> headerInfo;
+ nsCOMPtr <nsIImapUrl> aImapUrl;
+ nsImapAction imapAction = nsIImapUrl::nsImapTest; // unused value.
+ if (!mDatabase)
+ GetDatabase();
+
+ nsresult rv = aHdrXferInfo->GetNumHeaders(&numHdrs);
+ if (aProtocol)
+ {
+ (void) aProtocol->GetRunningImapURL(getter_AddRefs(aImapUrl));
+ if (aImapUrl)
+ aImapUrl->GetImapAction(&imapAction);
+ }
+ for (uint32_t i = 0; NS_SUCCEEDED(rv) && (int32_t)i < numHdrs; i++)
+ {
+ rv = aHdrXferInfo->GetHeader(i, getter_AddRefs(headerInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!headerInfo)
+ break;
+ int32_t msgSize;
+ nsMsgKey msgKey;
+ bool containsKey;
+ const char *msgHdrs;
+ headerInfo->GetMsgSize(&msgSize);
+ headerInfo->GetMsgUid(&msgKey);
+ if (msgKey == nsMsgKey_None) // not a valid uid.
+ continue;
+ if (imapAction == nsIImapUrl::nsImapMsgPreview)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ headerInfo->GetMsgHdrs(&msgHdrs);
+ // create an input stream based on the hdr string.
+ nsCOMPtr<nsIStringInputStream> inputStream =
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ inputStream->ShareData(msgHdrs, strlen(msgHdrs));
+ GetMessageHeader(msgKey, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ GetMsgPreviewTextFromStream(msgHdr, inputStream);
+ continue;
+ }
+ if (mDatabase && NS_SUCCEEDED(mDatabase->ContainsKey(msgKey, &containsKey)) && containsKey)
+ {
+ NS_ERROR("downloading hdrs for hdr we already have");
+ continue;
+ }
+ nsresult rv = SetupHeaderParseStream(msgSize, EmptyCString(), nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ headerInfo->GetMsgHdrs(&msgHdrs);
+ rv = ParseAdoptedHeaderLine(msgHdrs, msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NormalEndHeaderParseStream(aProtocol, aImapUrl);
+ }
+ return rv;
+}
+
+nsresult nsImapMailFolder::SetupHeaderParseStream(uint32_t aSize,
+ const nsACString& content_type, nsIMailboxSpec *boxSpec)
+{
+ if (!mDatabase)
+ GetDatabase();
+ m_nextMessageByteLength = aSize;
+ if (!m_msgParser)
+ {
+ nsresult rv;
+ m_msgParser = do_CreateInstance(kParseMailMsgStateCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ m_msgParser->Clear();
+
+ m_msgParser->SetMailDB(mDatabase);
+ if (mBackupDatabase)
+ m_msgParser->SetBackupMailDB(mBackupDatabase);
+ return m_msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
+}
+
+nsresult nsImapMailFolder::ParseAdoptedHeaderLine(const char *aMessageLine, nsMsgKey aMsgKey)
+{
+ // we can get blocks that contain more than one line,
+ // but they never contain partial lines
+ const char *str = aMessageLine;
+ m_curMsgUid = aMsgKey;
+ m_msgParser->SetNewKey(m_curMsgUid);
+ // m_envelope_pos, for local folders,
+ // is the msg key. Setting this will set the msg key for the new header.
+
+ int32_t len = strlen(str);
+ char *currentEOL = PL_strstr(str, MSG_LINEBREAK);
+ const char *currentLine = str;
+ while (currentLine < (str + len))
+ {
+ if (currentEOL)
+ {
+ m_msgParser->ParseAFolderLine(currentLine,
+ (currentEOL + MSG_LINEBREAK_LEN) -
+ currentLine);
+ currentLine = currentEOL + MSG_LINEBREAK_LEN;
+ currentEOL = PL_strstr(currentLine, MSG_LINEBREAK);
+ }
+ else
+ {
+ m_msgParser->ParseAFolderLine(currentLine, PL_strlen(currentLine));
+ currentLine = str + len + 1;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::NormalEndHeaderParseStream(nsIImapProtocol *aProtocol, nsIImapUrl* imapUrl)
+{
+ nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
+ nsresult rv;
+ NS_ENSURE_TRUE(m_msgParser, NS_ERROR_NULL_POINTER);
+
+ nsMailboxParseState parseState;
+ m_msgParser->GetState(&parseState);
+ if (parseState == nsIMsgParseMailMsgState::ParseHeadersState)
+ m_msgParser->ParseAFolderLine(CRLF, 2);
+ rv = m_msgParser->GetNewMsgHdr(getter_AddRefs(newMsgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char *headers;
+ int32_t headersSize;
+
+ nsCOMPtr <nsIMsgWindow> msgWindow;
+ nsCOMPtr <nsIMsgMailNewsUrl> msgUrl;
+ if (imapUrl)
+ {
+ msgUrl = do_QueryInterface(imapUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ rv = imapServer->GetIsGMailServer(&m_isGmailServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newMsgHdr->SetMessageKey(m_curMsgUid);
+ TweakHeaderFlags(aProtocol, newMsgHdr);
+ uint32_t messageSize;
+ if (NS_SUCCEEDED(newMsgHdr->GetMessageSize(&messageSize)))
+ mFolderSize += messageSize;
+ m_msgMovedByFilter = false;
+
+ nsMsgKey highestUID = 0;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ if (mDatabase)
+ mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo)
+ dbFolderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0, &highestUID);
+
+ // If this is the inbox, try to apply filters. Otherwise, test the inherited
+ // folder property "applyIncomingFilters" (which defaults to empty). If this
+ // inherited property has the string value "true", then apply filters even
+ // if this is not the Inbox folder.
+ if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters)
+ {
+ // Use highwater to determine whether to filter?
+ bool filterOnHighwater = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ prefBranch->GetBoolPref("mail.imap.filter_on_new", &filterOnHighwater);
+
+ uint32_t msgFlags;
+ newMsgHdr->GetFlags(&msgFlags);
+
+ bool doFilter = filterOnHighwater ?
+ // Filter on largest UUID and not deleted.
+ m_curMsgUid > highestUID && !(msgFlags & nsMsgMessageFlags::IMAPDeleted) :
+ // Filter on unread and not deleted.
+ !(msgFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted));
+
+ if (doFilter)
+ {
+ int32_t duplicateAction = nsIMsgIncomingServer::keepDups;
+ if (server)
+ server->GetIncomingDuplicateAction(&duplicateAction);
+ if ((duplicateAction != nsIMsgIncomingServer::keepDups) &&
+ mFlags & nsMsgFolderFlags::Inbox)
+ {
+ bool isDup;
+ server->IsNewHdrDuplicate(newMsgHdr, &isDup);
+ if (isDup)
+ {
+ // we want to do something similar to applying filter hits.
+ // if a dup is marked read, it shouldn't trigger biff.
+ // Same for deleting it or moving it to trash.
+ switch (duplicateAction)
+ {
+ case nsIMsgIncomingServer::deleteDups:
+ {
+ uint32_t newFlags;
+ newMsgHdr->OrFlags(nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted, &newFlags);
+ StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
+ &m_curMsgUid, 1, nullptr);
+ m_msgMovedByFilter = true;
+ }
+ break;
+ case nsIMsgIncomingServer::moveDupsToTrash:
+ {
+ nsCOMPtr <nsIMsgFolder> trash;
+ GetTrashFolder(getter_AddRefs(trash));
+ if (trash)
+ {
+ nsCString trashUri;
+ trash->GetURI(trashUri);
+ nsresult err = MoveIncorporatedMessage(newMsgHdr, mDatabase, trashUri, nullptr, msgWindow);
+ if (NS_SUCCEEDED(err))
+ m_msgMovedByFilter = true;
+ }
+ }
+ break;
+ case nsIMsgIncomingServer::markDupsRead:
+ {
+ uint32_t newFlags;
+ newMsgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags);
+ StoreImapFlags(kImapMsgSeenFlag, true, &m_curMsgUid, 1, nullptr);
+ }
+ break;
+ }
+ int32_t numNewMessages;
+ GetNumNewMessages(false, &numNewMessages);
+ SetNumNewMessages(numNewMessages - 1);
+ }
+ }
+ rv = m_msgParser->GetAllHeaders(&headers, &headersSize);
+
+ if (NS_SUCCEEDED(rv) && headers && !m_msgMovedByFilter &&
+ !m_filterListRequiresBody)
+ {
+ if (m_filterList)
+ {
+ GetMoveCoalescer(); // not sure why we're doing this here.
+ m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr,
+ this, mDatabase, headers, headersSize,
+ this, msgWindow);
+ NotifyFolderEvent(mFiltersAppliedAtom);
+ }
+ }
+ }
+ }
+ // here we need to tweak flags from uid state..
+ if (mDatabase && (!m_msgMovedByFilter || ShowDeletedMessages()))
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ // Check if this header corresponds to a pseudo header
+ // we have from doing a pseudo-offline move and then downloading
+ // the real header from the server. In that case, we notify
+ // db/folder listeners that the pseudo-header has become the new
+ // header, i.e., the key has changed.
+ nsCString newMessageId;
+ nsMsgKey pseudoKey = nsMsgKey_None;
+ newMsgHdr->GetMessageId(getter_Copies(newMessageId));
+ m_pseudoHdrs.Get(newMessageId, &pseudoKey);
+ if (notifier && pseudoKey != nsMsgKey_None)
+ {
+ notifier->NotifyMsgKeyChanged(pseudoKey, newMsgHdr);
+ m_pseudoHdrs.Remove(newMessageId);
+ }
+ mDatabase->AddNewHdrToDB(newMsgHdr, true);
+ if (notifier)
+ notifier->NotifyMsgAdded(newMsgHdr);
+ // mark the header as not yet reported classified
+ OrProcessingFlags(m_curMsgUid, nsMsgProcessingFlags::NotReportedClassified);
+ }
+ // adjust highestRecordedUID
+ if (dbFolderInfo)
+ {
+ if (m_curMsgUid > highestUID)
+ dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName, m_curMsgUid);
+ }
+
+ if (m_isGmailServer)
+ {
+ nsCOMPtr<nsIImapFlagAndUidState> flagState;
+ aProtocol->GetFlagAndUidState(getter_AddRefs(flagState));
+ nsCString msgIDValue;
+ nsCString threadIDValue;
+ nsCString labelsValue;
+ flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-MSGID"), msgIDValue);
+ flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-THRID"), threadIDValue);
+ flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-LABELS"), labelsValue);
+ newMsgHdr->SetStringProperty("X-GM-MSGID", msgIDValue.get());
+ newMsgHdr->SetStringProperty("X-GM-THRID", threadIDValue.get());
+ newMsgHdr->SetStringProperty("X-GM-LABELS", labelsValue.get());
+ }
+
+ m_msgParser->Clear(); // clear out parser, because it holds onto a msg hdr.
+ m_msgParser->SetMailDB(nullptr); // tell it to let go of the db too.
+ // I don't think we want to do this - it does bad things like set the size incorrectly.
+ // m_msgParser->FinishHeader();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::AbortHeaderParseStream(nsIImapProtocol* aProtocol)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::BeginCopy(nsIMsgDBHdr *message)
+{
+ NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER);
+ nsresult rv;
+ if (m_copyState->m_tmpFile) // leftover file spec nuke it
+ {
+ rv = m_copyState->m_tmpFile->Remove(false);
+ if (NS_FAILED(rv))
+ {
+ nsCString nativePath;
+ m_copyState->m_tmpFile->GetNativePath(nativePath);
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't remove prev temp file %s: %lx\n", nativePath.get(), rv));
+ }
+ m_copyState->m_tmpFile = nullptr;
+ }
+ if (message)
+ m_copyState->m_message = do_QueryInterface(message, &rv);
+
+ rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "nscpmsg.txt",
+ getter_AddRefs(m_copyState->m_tmpFile));
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't find nscpmsg.txt:%lx\n", rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create a unique file, since multiple copies may be open on multiple folders
+ rv = m_copyState->m_tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't create temp nscpmsg.txt:%lx\n", rv));
+ // Last ditch attempt to create a temp file, because virus checker might
+ // be locking the previous temp file, and CreateUnique fails if the file
+ // is locked. Use the message key to make a unique name.
+ if (message)
+ {
+ nsCString tmpFileName("nscpmsg-");
+ nsMsgKey msgKey;
+ message->GetMessageKey(&msgKey);
+ tmpFileName.AppendInt(msgKey);
+ tmpFileName.Append(".txt");
+ m_copyState->m_tmpFile->SetNativeLeafName(tmpFileName);
+ rv = m_copyState->m_tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't create temp nscpmsg.txt:%lx\n", rv));
+ OnCopyCompleted(m_copyState->m_srcSupport, rv);
+ return rv;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIOutputStream> fileOutputStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_copyState->m_msgFileStream),
+ m_copyState->m_tmpFile, -1, 00600);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't create output file stream:%lx\n", rv));
+
+ if (!m_copyState->m_dataBuffer)
+ m_copyState->m_dataBuffer = (char*) PR_CALLOC(COPY_BUFFER_SIZE+1);
+ NS_ENSURE_TRUE(m_copyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY);
+ m_copyState->m_dataBufferSize = COPY_BUFFER_SIZE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::CopyDataToOutputStreamForAppend(nsIInputStream *aIStream,
+ int32_t aLength, nsIOutputStream *outputStream)
+{
+ uint32_t readCount;
+ uint32_t writeCount;
+ if (!m_copyState)
+ m_copyState = new nsImapMailCopyState();
+
+ if ( aLength + m_copyState->m_leftOver > m_copyState->m_dataBufferSize )
+ {
+ char *newBuffer = (char*) PR_REALLOC(m_copyState->m_dataBuffer, aLength + m_copyState->m_leftOver+ 1);
+ NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
+ m_copyState->m_dataBuffer = newBuffer;
+ m_copyState->m_dataBufferSize = aLength + m_copyState->m_leftOver;
+ }
+
+ char *start, *end;
+ uint32_t linebreak_len = 1;
+
+ nsresult rv = aIStream->Read(m_copyState->m_dataBuffer+m_copyState->m_leftOver, aLength, &readCount);
+ if (NS_FAILED(rv))
+ return rv;
+
+ m_copyState->m_leftOver += readCount;
+ m_copyState->m_dataBuffer[m_copyState->m_leftOver] = '\0';
+
+ start = m_copyState->m_dataBuffer;
+ if (m_copyState->m_eatLF)
+ {
+ if (*start == '\n')
+ start++;
+ m_copyState->m_eatLF = false;
+ }
+ end = PL_strpbrk(start, "\r\n");
+ if (end && *end == '\r' && *(end+1) == '\n')
+ linebreak_len = 2;
+
+ while (start && end)
+ {
+ if (PL_strncasecmp(start, "X-Mozilla-Status:", 17) &&
+ PL_strncasecmp(start, "X-Mozilla-Status2:", 18) &&
+ PL_strncmp(start, "From - ", 7))
+ {
+ rv = outputStream->Write(start,
+ end-start,
+ &writeCount);
+ rv = outputStream->Write(CRLF, 2, &writeCount);
+ }
+ start = end+linebreak_len;
+ if (start >=
+ m_copyState->m_dataBuffer+m_copyState->m_leftOver)
+ {
+ m_copyState->m_leftOver = 0;
+ break;
+ }
+ linebreak_len = 1;
+
+ end = PL_strpbrk(start, "\r\n");
+ if (end && *end == '\r')
+ {
+ if (*(end+1) == '\n')
+ linebreak_len = 2;
+ else if (! *(end+1)) // block might have split CRLF so remember if
+ m_copyState->m_eatLF = true; // we should eat LF
+ }
+
+ if (start && !end)
+ {
+ m_copyState->m_leftOver -= (start - m_copyState->m_dataBuffer);
+ memcpy(m_copyState->m_dataBuffer, start, m_copyState->m_leftOver+1); // including null
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::CopyDataDone()
+{
+ m_copyState = nullptr;
+ return NS_OK;
+}
+
+// sICopyMessageListener methods, BeginCopy, CopyData, EndCopy, EndMove, StartMessage, EndMessage
+NS_IMETHODIMP nsImapMailFolder::CopyData(nsIInputStream *aIStream, int32_t aLength)
+{
+ NS_ENSURE_TRUE(m_copyState && m_copyState->m_msgFileStream && m_copyState->m_dataBuffer, NS_ERROR_NULL_POINTER);
+ nsresult rv = CopyDataToOutputStreamForAppend(aIStream, aLength,
+ m_copyState->m_msgFileStream);
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyData failed:%lx\n", rv));
+ OnCopyCompleted(m_copyState->m_srcSupport, rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::EndCopy(bool copySucceeded)
+{
+ nsresult rv = copySucceeded ? NS_OK : NS_ERROR_FAILURE;
+ if (copySucceeded && m_copyState && m_copyState->m_msgFileStream)
+ {
+ nsCOMPtr<nsIUrlListener> urlListener;
+ m_copyState->m_msgFileStream->Close();
+ // m_tmpFile can be stale because we wrote to it
+ nsCOMPtr<nsIFile> tmpFile;
+ m_copyState->m_tmpFile->Clone(getter_AddRefs(tmpFile));
+ m_copyState->m_tmpFile = tmpFile;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
+ nsCOMPtr<nsISupports> copySupport;
+ if (m_copyState)
+ copySupport = do_QueryInterface(m_copyState);
+ rv = imapService->AppendMessageFromFile(m_copyState->m_tmpFile,
+ this, EmptyCString(), true,
+ m_copyState->m_selectedState,
+ urlListener, nullptr,
+ copySupport,
+ m_copyState->m_msgWindow);
+ }
+ if (NS_FAILED(rv) || !copySucceeded)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("EndCopy failed:%lx\n", rv));
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::EndMove(bool moveSucceeded)
+{
+ return NS_OK;
+}
+// this is the beginning of the next message copied
+NS_IMETHODIMP nsImapMailFolder::StartMessage()
+{
+ return NS_OK;
+}
+
+// just finished the current message.
+NS_IMETHODIMP nsImapMailFolder::EndMessage(nsMsgKey key)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindow *msgWindow, bool *applyMore)
+{
+ //
+ // This routine is called indirectly from ApplyFiltersToHdr in two
+ // circumstances, controlled by m_filterListRequiresBody:
+ //
+ // If false, after headers are parsed in NormalEndHeaderParseStream.
+ // If true, after the message body is downloaded in NormalEndMsgWriteStream.
+ //
+ // In NormalEndHeaderParseStream, the message has not been added to the
+ // database, and it is important that database notifications and count
+ // updates do not occur. In NormalEndMsgWriteStream, the message has been
+ // added to the database, and database notifications and count updates
+ // should be performed.
+ //
+
+ NS_ENSURE_ARG_POINTER(filter);
+ NS_ENSURE_ARG_POINTER(applyMore);
+
+ nsresult rv = NS_OK;
+
+ // look at action - currently handle move
+#ifdef DEBUG_bienvenu
+ printf("got a rule hit!\n");
+#endif
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ if (m_filterListRequiresBody)
+ GetMessageHeader(m_curMsgUid, getter_AddRefs(msgHdr));
+ else if (m_msgParser)
+ m_msgParser->GetNewMsgHdr(getter_AddRefs(msgHdr));
+ NS_ENSURE_TRUE(msgHdr, NS_ERROR_NULL_POINTER); //fatal error, cannot apply filters
+
+ bool deleteToTrash = DeleteIsMoveToTrash();
+
+ nsCOMPtr<nsIArray> filterActionList;
+
+ rv = filter->GetSortedActionList(getter_AddRefs(filterActionList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numActions;
+ rv = filterActionList->GetLength(&numActions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool loggingEnabled = false;
+ if (m_filterList && numActions)
+ (void)m_filterList->GetLoggingEnabled(&loggingEnabled);
+
+ bool msgIsNew = true;
+
+ for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++)
+ {
+ nsCOMPtr<nsIMsgRuleAction> filterAction;
+ rv = filterActionList->QueryElementAt(actionIndex, NS_GET_IID(nsIMsgRuleAction),
+ getter_AddRefs(filterAction));
+ if (NS_FAILED(rv) || !filterAction)
+ continue;
+
+ nsMsgRuleActionType actionType;
+ if (NS_SUCCEEDED(filterAction->GetType(&actionType)))
+ {
+ if (loggingEnabled)
+ (void) filter->LogRuleHit(filterAction, msgHdr);
+
+ nsCString actionTargetFolderUri;
+ if (actionType == nsMsgFilterAction::MoveToFolder ||
+ actionType == nsMsgFilterAction::CopyToFolder)
+ {
+ rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
+ if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty())
+ {
+ NS_ASSERTION(false, "actionTargetFolderUri is empty");
+ continue;
+ }
+ }
+
+ uint32_t msgFlags;
+ nsMsgKey msgKey;
+ nsAutoCString trashNameVal;
+
+ msgHdr->GetFlags(&msgFlags);
+ msgHdr->GetMessageKey(&msgKey);
+ bool isRead = (msgFlags & nsMsgMessageFlags::Read);
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ switch (actionType)
+ {
+ case nsMsgFilterAction::Delete:
+ {
+ if (deleteToTrash)
+ {
+ // set value to trash folder
+ nsCOMPtr <nsIMsgFolder> mailTrash;
+ rv = GetTrashFolder(getter_AddRefs(mailTrash));
+ if (NS_SUCCEEDED(rv) && mailTrash)
+ rv = mailTrash->GetURI(actionTargetFolderUri);
+ // msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags); // mark read in trash.
+ }
+ else // (!deleteToTrash)
+ {
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ mDatabase->MarkImapDeleted(msgKey, true, nullptr);
+ StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
+ &msgKey, 1, nullptr);
+ m_msgMovedByFilter = true; // this will prevent us from adding the header to the db.
+ }
+ msgIsNew = false;
+ }
+ // note that delete falls through to move.
+ MOZ_FALLTHROUGH;
+ case nsMsgFilterAction::MoveToFolder:
+ {
+ // if moving to a different file, do it.
+ nsCString uri;
+ rv = GetURI(uri);
+
+ if (!actionTargetFolderUri.Equals(uri))
+ {
+ msgHdr->GetFlags(&msgFlags);
+
+ if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead)
+ {
+ mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
+ mDatabase->MarkMDNSent(msgKey, true, nullptr);
+ }
+ nsresult err = MoveIncorporatedMessage(msgHdr, mDatabase, actionTargetFolderUri, filter, msgWindow);
+ if (NS_SUCCEEDED(err))
+ m_msgMovedByFilter = true;
+ }
+ // don't apply any more filters, even if it was a move to the same folder
+ *applyMore = false;
+ }
+ break;
+ case nsMsgFilterAction::CopyToFolder:
+ {
+ nsCString uri;
+ rv = GetURI(uri);
+
+ if (!actionTargetFolderUri.Equals(uri))
+ {
+ // XXXshaver I'm not actually 100% what the right semantics are for
+ // MDNs and copied messages, but I suspect deep down inside that
+ // we probably want to suppress them only on the copies.
+ msgHdr->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead)
+ {
+ mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
+ mDatabase->MarkMDNSent(msgKey, true, nullptr);
+ }
+
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(messageArray, rv);
+ messageArray->AppendElement(msgHdr, false);
+
+ nsCOMPtr<nsIMsgFolder> dstFolder;
+ rv = GetExistingFolder(actionTargetFolderUri, getter_AddRefs(dstFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyMessages(this, messageArray, dstFolder,
+ false, nullptr, msgWindow, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ break;
+ case nsMsgFilterAction::MarkRead:
+ {
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ StoreImapFlags(kImapMsgSeenFlag, true, &msgKey, 1, nullptr);
+ msgIsNew = false;
+ }
+ break;
+ case nsMsgFilterAction::MarkUnread:
+ {
+ mDatabase->MarkHdrRead(msgHdr, false, nullptr);
+ StoreImapFlags(kImapMsgSeenFlag, false, &msgKey, 1, nullptr);
+ msgIsNew = true;
+ }
+ break;
+ case nsMsgFilterAction::MarkFlagged:
+ {
+ mDatabase->MarkHdrMarked(msgHdr, true, nullptr);
+ StoreImapFlags(kImapMsgFlaggedFlag, true, &msgKey, 1, nullptr);
+ }
+ break;
+ case nsMsgFilterAction::KillThread:
+ case nsMsgFilterAction::WatchThread:
+ {
+ nsCOMPtr <nsIMsgThread> msgThread;
+ nsMsgKey threadKey;
+ mDatabase->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(msgThread));
+ if (msgThread)
+ {
+ msgThread->GetThreadKey(&threadKey);
+ if (actionType == nsMsgFilterAction::KillThread)
+ mDatabase->MarkThreadIgnored(msgThread, threadKey, true, nullptr);
+ else
+ mDatabase->MarkThreadWatched(msgThread, threadKey, true, nullptr);
+ }
+ else
+ {
+ if (actionType == nsMsgFilterAction::KillThread)
+ msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored);
+ else
+ msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Watched);
+ }
+ if (actionType == nsMsgFilterAction::KillThread)
+ {
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ StoreImapFlags(kImapMsgSeenFlag, true, &msgKey, 1, nullptr);
+ msgIsNew = false;
+ }
+ }
+ break;
+ case nsMsgFilterAction::KillSubthread:
+ {
+ mDatabase->MarkHeaderKilled(msgHdr, true, nullptr);
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ StoreImapFlags(kImapMsgSeenFlag, true, &msgKey, 1, nullptr);
+ msgIsNew = false;
+ }
+ break;
+ case nsMsgFilterAction::ChangePriority:
+ {
+ nsMsgPriorityValue filterPriority; // a int32_t
+ filterAction->GetPriority(&filterPriority);
+ mDatabase->SetUint32PropertyByHdr(msgHdr, "priority",
+ static_cast<uint32_t>(filterPriority));
+ }
+ break;
+ case nsMsgFilterAction::Label:
+ {
+ nsMsgLabelValue filterLabel;
+ filterAction->GetLabel(&filterLabel);
+ mDatabase->SetUint32PropertyByHdr(msgHdr, "label",
+ static_cast<uint32_t>(filterLabel));
+ StoreImapFlags((filterLabel << 9), true, &msgKey, 1, nullptr);
+ }
+ break;
+ case nsMsgFilterAction::AddTag:
+ {
+ nsCString keyword;
+ filterAction->GetStrValue(keyword);
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(messageArray, rv);
+ messageArray->AppendElement(msgHdr, false);
+ AddKeywordsToMessages(messageArray, keyword);
+ break;
+ }
+ case nsMsgFilterAction::JunkScore:
+ {
+ nsAutoCString junkScoreStr;
+ int32_t junkScore;
+ filterAction->GetJunkScore(&junkScore);
+ junkScoreStr.AppendInt(junkScore);
+ mDatabase->SetStringProperty(msgKey, "junkscore", junkScoreStr.get());
+ mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter");
+
+ // If score is available, set up to store junk status on server.
+ if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE ||
+ junkScore == nsIJunkMailPlugin::IS_HAM_SCORE)
+ {
+ nsTArray<nsMsgKey> *keysToClassify = m_moveCoalescer->GetKeyBucket(
+ (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) ? 0 : 1);
+ NS_ASSERTION(keysToClassify, "error getting key bucket");
+ if (keysToClassify)
+ keysToClassify->AppendElement(msgKey);
+ if (msgIsNew && junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE)
+ {
+ msgIsNew = false;
+ mDatabase->MarkHdrNotNew(msgHdr, nullptr);
+ // nsMsgDBFolder::SendFlagNotifications by the call to
+ // SetBiffState(nsMsgBiffState_NoMail) will reset numNewMessages
+ // only if the message is also read and database notifications
+ // are active, but we are not going to mark it read in this
+ // action, preferring to leave the choice to the user.
+ // So correct numNewMessages.
+ if (m_filterListRequiresBody)
+ {
+ msgHdr->GetFlags(&msgFlags);
+ if (!(msgFlags & nsMsgMessageFlags::Read))
+ {
+ int32_t numNewMessages;
+ GetNumNewMessages(false, &numNewMessages);
+ SetNumNewMessages(--numNewMessages);
+ SetHasNewMessages(numNewMessages != 0);
+ }
+ }
+ }
+ }
+ }
+ break;
+ case nsMsgFilterAction::Forward:
+ {
+ nsCString forwardTo;
+ filterAction->GetStrValue(forwardTo);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!forwardTo.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgComposeService> compService =
+ do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = compService->ForwardMessage(NS_ConvertASCIItoUTF16(forwardTo),
+ msgHdr, msgWindow, server,
+ nsIMsgComposeService::kForwardAsDefault);
+ }
+ }
+ break;
+
+ case nsMsgFilterAction::Reply:
+ {
+ nsCString replyTemplateUri;
+ filterAction->GetStrValue(replyTemplateUri);
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!replyTemplateUri.IsEmpty())
+ {
+ nsCOMPtr <nsIMsgComposeService> compService = do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID) ;
+ if (compService) {
+ rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri.get(), msgWindow, server);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("ReplyWithTemplate failed");
+ if (rv == NS_ERROR_ABORT) {
+ filter->LogRuleHitFail(filterAction, msgHdr, rv, "Sending reply aborted");
+ } else {
+ filter->LogRuleHitFail(filterAction, msgHdr, rv, "Error sending reply");
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case nsMsgFilterAction::StopExecution:
+ {
+ // don't apply any more filters
+ *applyMore = false;
+ }
+ break;
+
+ case nsMsgFilterAction::Custom:
+ {
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString value;
+ filterAction->GetStrValue(value);
+
+ nsCOMPtr<nsIMutableArray> messageArray(
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(messageArray, rv);
+ messageArray->AppendElement(msgHdr, false);
+
+ customAction->Apply(messageArray, value, nullptr,
+ nsMsgFilterType::InboxRule, msgWindow);
+ // allow custom action to affect new
+ msgHdr->GetFlags(&msgFlags);
+ if (!(msgFlags & nsMsgMessageFlags::New))
+ msgIsNew = false;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ if (!msgIsNew)
+ {
+ int32_t numNewMessages;
+ GetNumNewMessages(false, &numNewMessages);
+ // When database notifications are active, new counts will be reset
+ // to zero in nsMsgDBFolder::SendFlagNotifications by the call to
+ // SetBiffState(nsMsgBiffState_NoMail), so don't repeat them here.
+ if (!m_filterListRequiresBody)
+ SetNumNewMessages(--numNewMessages);
+ if (mDatabase)
+ mDatabase->MarkHdrNotNew(msgHdr, nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetImapFlags(const char *uids, int32_t flags, nsIURI **url)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return imapService->SetMessageFlags(this, this, url, nsAutoCString(uids), flags, true);
+}
+
+// "this" is the parent folder
+NS_IMETHODIMP nsImapMailFolder::PlaybackOfflineFolderCreate(const nsAString& aFolderName, nsIMsgWindow *aWindow, nsIURI **url)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return imapService->CreateFolder(this, aFolderName, this, url);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ReplayOfflineMoveCopy(nsMsgKey *aMsgKeys, uint32_t aNumKeys,
+ bool isMove, nsIMsgFolder *aDstFolder,
+ nsIUrlListener *aUrlListener, nsIMsgWindow *aWindow)
+{
+ nsresult rv;
+
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aDstFolder);
+ if (imapFolder)
+ {
+ nsImapMailFolder *destImapFolder = static_cast<nsImapMailFolder*>(aDstFolder);
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ nsCOMPtr<nsIMsgDatabase> dstFolderDB;
+ aDstFolder->GetMsgDatabase(getter_AddRefs(dstFolderDB));
+ if (dstFolderDB)
+ {
+ // find the fake header in the destination db, and use that to
+ // set the pending attributes on the real headers. To do this,
+ // we need to iterate over the offline ops in the destination db,
+ // looking for ones with matching keys and source folder uri.
+ // If we find that offline op, its "key" will be the key of the fake
+ // header, so we just need to get the header for that key
+ // from the dest db.
+ nsTArray<nsMsgKey> offlineOps;
+ if (NS_SUCCEEDED(dstFolderDB->ListAllOfflineOpIds(&offlineOps)))
+ {
+ nsCString srcFolderUri;
+ GetURI(srcFolderUri);
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
+ for (uint32_t opIndex = 0; opIndex < offlineOps.Length(); opIndex++)
+ {
+ dstFolderDB->GetOfflineOpForKey(offlineOps[opIndex], false,
+ getter_AddRefs(currentOp));
+ if (currentOp)
+ {
+ nsCString opSrcUri;
+ currentOp->GetSourceFolderURI(getter_Copies(opSrcUri));
+ if (opSrcUri.Equals(srcFolderUri))
+ {
+ nsMsgKey srcMessageKey;
+ currentOp->GetSrcMessageKey(&srcMessageKey);
+ for (uint32_t msgIndex = 0; msgIndex < aNumKeys; msgIndex++)
+ {
+ if (srcMessageKey == aMsgKeys[msgIndex])
+ {
+ nsCOMPtr<nsIMsgDBHdr> fakeDestHdr;
+ dstFolderDB->GetMsgHdrForKey(offlineOps[opIndex],
+ getter_AddRefs(fakeDestHdr));
+ if (fakeDestHdr)
+ messages->AppendElement(fakeDestHdr, false);
+ break;
+ }
+ }
+ }
+ }
+ }
+ destImapFolder->SetPendingAttributes(messages, isMove);
+ }
+ }
+ // if we can't get the dst folder db, we should still try to playback
+ // the offline move/copy.
+ }
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIURI> resultUrl;
+ nsAutoCString uids;
+ AllocateUidStringFromKeys(aMsgKeys, aNumKeys, uids);
+ rv = imapService->OnlineMessageCopy(this, uids, aDstFolder,
+ true, isMove, aUrlListener,
+ getter_AddRefs(resultUrl), nullptr, aWindow);
+ if (resultUrl)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(resultUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIUrlListener> folderListener = do_QueryInterface(aDstFolder);
+ if (folderListener)
+ mailnewsUrl->RegisterListener(folderListener);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::AddMoveResultPseudoKey(nsMsgKey aMsgKey)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> pseudoHdr;
+ rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(pseudoHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString messageId;
+ pseudoHdr->GetMessageId(getter_Copies(messageId));
+ // err on the side of caution and ignore messages w/o messageid.
+ if (messageId.IsEmpty())
+ return NS_OK;
+ m_pseudoHdrs.Put(messageId, aMsgKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::StoreImapFlags(int32_t flags, bool addFlags,
+ nsMsgKey *keys, uint32_t numKeys,
+ nsIUrlListener *aUrlListener)
+{
+ nsresult rv;
+ if (!WeAreOffline())
+ {
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString msgIds;
+ AllocateUidStringFromKeys(keys, numKeys, msgIds);
+ if (addFlags)
+ imapService->AddMessageFlags(this, aUrlListener ? aUrlListener : this,
+ nullptr, msgIds, flags, true);
+ else
+ imapService->SubtractMessageFlags(this, aUrlListener ? aUrlListener : this,
+ nullptr, msgIds, flags, true);
+ }
+ else
+ {
+ rv = GetDatabase();
+ if (NS_SUCCEEDED(rv) && mDatabase)
+ {
+ uint32_t total = numKeys;
+ for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++)
+ {
+ nsCOMPtr <nsIMsgOfflineImapOperation> op;
+ rv = mDatabase->GetOfflineOpForKey(keys[keyIndex], true, getter_AddRefs(op));
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ imapMessageFlagsType newFlags;
+ op->GetNewFlags(&newFlags);
+ op->SetFlagOperation(addFlags ? newFlags | flags : newFlags & ~flags);
+ }
+ }
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline flags
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::LiteSelect(nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->LiteSelectFolder(this, aUrlListener,
+ aMsgWindow, nullptr);
+}
+
+nsresult nsImapMailFolder::GetFolderOwnerUserName(nsACString& userName)
+{
+ if ((mFlags & nsMsgFolderFlags::ImapPersonal) ||
+ !(mFlags & (nsMsgFolderFlags::ImapPublic | nsMsgFolderFlags::ImapOtherUser)))
+ {
+ // this is one of our personal mail folders
+ // return our username on this host
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ return NS_FAILED(rv) ? rv : server->GetUsername(userName);
+ }
+
+ // the only other type of owner is if it's in the other users' namespace
+ if (!(mFlags & nsMsgFolderFlags::ImapOtherUser))
+ return NS_OK;
+
+ if (m_ownerUserName.IsEmpty())
+ {
+ nsCString onlineName;
+ GetOnlineName(onlineName);
+ m_ownerUserName = nsIMAPNamespaceList::GetFolderOwnerNameFromPath(GetNamespaceForFolder(), onlineName.get());
+ }
+ userName = m_ownerUserName;
+ return NS_OK;
+}
+
+nsIMAPNamespace *nsImapMailFolder::GetNamespaceForFolder()
+{
+ if (!m_namespace)
+ {
+#ifdef DEBUG_bienvenu
+ // Make sure this isn't causing us to open the database
+ NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown, "haven't set hierarchy delimiter");
+#endif
+ nsCString serverKey;
+ nsCString onlineName;
+ GetServerKey(serverKey);
+ GetOnlineName(onlineName);
+ char hierarchyDelimiter;
+ GetHierarchyDelimiter(&hierarchyDelimiter);
+
+ m_namespace = nsIMAPNamespaceList::GetNamespaceForFolder(
+ serverKey.get(), onlineName.get(), hierarchyDelimiter);
+ NS_ASSERTION(m_namespace, "didn't get namespace for folder");
+ if (m_namespace)
+ {
+ nsIMAPNamespaceList::SuggestHierarchySeparatorForNamespace(m_namespace, hierarchyDelimiter);
+ m_folderIsNamespace = nsIMAPNamespaceList::GetFolderIsNamespace(
+ serverKey.get(), onlineName.get(),
+ hierarchyDelimiter, m_namespace);
+ }
+ }
+ return m_namespace;
+}
+
+void nsImapMailFolder::SetNamespaceForFolder(nsIMAPNamespace *ns)
+{
+#ifdef DEBUG_bienvenu
+ NS_ASSERTION(ns, "null namespace");
+#endif
+ m_namespace = ns;
+}
+
+NS_IMETHODIMP nsImapMailFolder::FolderPrivileges(nsIMsgWindow *window)
+{
+ NS_ENSURE_ARG_POINTER(window);
+ nsresult rv = NS_OK; // if no window...
+ if (!m_adminUrl.IsEmpty())
+ {
+ nsCOMPtr<nsIExternalProtocolService> extProtService = do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
+ if (extProtService)
+ {
+ nsAutoCString scheme;
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), m_adminUrl.get())))
+ return rv;
+ uri->GetScheme(scheme);
+ if (!scheme.IsEmpty())
+ {
+ // if the URL scheme does not correspond to an exposed protocol, then we
+ // need to hand this link click over to the external protocol handler.
+ bool isExposed;
+ rv = extProtService->IsExposedProtocol(scheme.get(), &isExposed);
+ if (NS_SUCCEEDED(rv) && !isExposed)
+ return extProtService->LoadUrl(uri);
+ }
+ }
+ }
+ else
+ {
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = imapService->GetFolderAdminUrl(this, window, this, nullptr);
+ if (NS_SUCCEEDED(rv))
+ m_urlRunning = true;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetHasAdminUrl(bool *aBool)
+{
+ NS_ENSURE_ARG_POINTER(aBool);
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ nsCString manageMailAccountUrl;
+ if (NS_SUCCEEDED(rv) && imapServer)
+ rv = imapServer->GetManageMailAccountUrl(manageMailAccountUrl);
+ *aBool = (NS_SUCCEEDED(rv) && !manageMailAccountUrl.IsEmpty());
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetAdminUrl(nsACString& aResult)
+{
+ aResult = m_adminUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetAdminUrl(const nsACString& adminUrl)
+{
+ m_adminUrl = adminUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetHdrParser(nsIMsgParseMailMsgState **aHdrParser)
+{
+ NS_ENSURE_ARG_POINTER(aHdrParser);
+ NS_IF_ADDREF(*aHdrParser = m_msgParser);
+ return NS_OK;
+}
+
+ // this is used to issue an arbitrary imap command on the passed in msgs.
+ // It assumes the command needs to be run in the selected state.
+NS_IMETHODIMP nsImapMailFolder::IssueCommandOnMsgs(const nsACString& command, const char *uids, nsIMsgWindow *aWindow, nsIURI **url)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return imapService->IssueCommandOnMsgs(this, aWindow, command, nsDependentCString(uids), url);
+}
+
+NS_IMETHODIMP nsImapMailFolder::FetchCustomMsgAttribute(const nsACString& attribute, const char *uids, nsIMsgWindow *aWindow, nsIURI **url)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return imapService->FetchCustomMsgAttribute(this, aWindow, attribute, nsDependentCString(uids), url);
+}
+
+nsresult nsImapMailFolder::MoveIncorporatedMessage(nsIMsgDBHdr *mailHdr,
+ nsIMsgDatabase *sourceDB,
+ const nsACString& destFolderUri,
+ nsIMsgFilter *filter,
+ nsIMsgWindow *msgWindow)
+{
+ nsresult rv;
+ if (m_moveCoalescer)
+ {
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(destFolderUri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> destIFolder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (destIFolder)
+ {
+ // check if the destination is a real folder (by checking for null parent)
+ // and if it can file messages (e.g., servers or news folders can't file messages).
+ // Or read only imap folders...
+ bool canFileMessages = true;
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ destIFolder->GetParent(getter_AddRefs(parentFolder));
+ if (parentFolder)
+ destIFolder->GetCanFileMessages(&canFileMessages);
+ if (filter && (!parentFolder || !canFileMessages))
+ {
+ filter->SetEnabled(false);
+ m_filterList->SaveToDefaultFile();
+ destIFolder->ThrowAlertMsg("filterDisabled",msgWindow);
+ return NS_MSG_NOT_A_MAIL_FOLDER;
+ }
+ // put the header into the source db, since it needs to be there when we copy it
+ // and we need a valid header to pass to StartAsyncCopyMessagesInto
+ nsMsgKey keyToFilter;
+ mailHdr->GetMessageKey(&keyToFilter);
+
+ if (sourceDB && destIFolder)
+ {
+ bool imapDeleteIsMoveToTrash = DeleteIsMoveToTrash();
+ m_moveCoalescer->AddMove (destIFolder, keyToFilter);
+ // For each folder, we need to keep track of the ids we want to move to that
+ // folder - we used to store them in the MSG_FolderInfo and then when we'd finished
+ // downloading headers, we'd iterate through all the folders looking for the ones
+ // that needed messages moved into them - perhaps instead we could
+ // keep track of nsIMsgFolder, nsTArray<nsMsgKey> pairs here in the imap code.
+ // nsTArray<nsMsgKey> *idsToMoveFromInbox = msgFolder->GetImapIdsToMoveFromInbox();
+ // idsToMoveFromInbox->AppendElement(keyToFilter);
+ if (imapDeleteIsMoveToTrash)
+ {
+ }
+ bool isRead = false;
+ mailHdr->GetIsRead(&isRead);
+ if (imapDeleteIsMoveToTrash)
+ rv = NS_OK;
+ }
+ }
+ } else
+ rv = NS_ERROR_UNEXPECTED;
+
+ // we have to return an error because we do not actually move the message
+ // it is done async and that can fail
+ return rv;
+}
+
+/**
+ * This method assumes that key arrays and flag states are sorted by increasing key.
+ */
+void nsImapMailFolder::FindKeysToDelete(const nsTArray<nsMsgKey> &existingKeys,
+ nsTArray<nsMsgKey> &keysToDelete,
+ nsIImapFlagAndUidState *flagState,
+ uint32_t boxFlags)
+{
+ bool showDeletedMessages = ShowDeletedMessages();
+ int32_t numMessageInFlagState;
+ bool partialUIDFetch;
+ uint32_t uidOfMessage;
+ imapMessageFlagsType flags;
+
+ flagState->GetNumberOfMessages(&numMessageInFlagState);
+ flagState->GetPartialUIDFetch(&partialUIDFetch);
+
+ // if we're doing a partialUIDFetch, just delete the keys from the db
+ // that have the deleted flag set (if not using imap delete model)
+ // and return.
+ if (partialUIDFetch)
+ {
+ if (!showDeletedMessages)
+ {
+ for (uint32_t i = 0; (int32_t) i < numMessageInFlagState; i++)
+ {
+ flagState->GetUidOfMessage(i, &uidOfMessage);
+ // flag state will be zero filled up to first real uid, so ignore those.
+ if (uidOfMessage)
+ {
+ flagState->GetMessageFlags(i, &flags);
+ if (flags & kImapMsgDeletedFlag)
+ keysToDelete.AppendElement(uidOfMessage);
+ }
+ }
+ }
+ else if (boxFlags & kJustExpunged)
+ {
+ // we've just issued an expunge with a partial flag state. We should
+ // delete headers with the imap deleted flag set, because we can't
+ // tell from the expunge response which messages were deleted.
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ nsresult rv = GetMessages(getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ bool hasMore = false;
+ nsCOMPtr <nsIMsgDBHdr> pHeader;
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = hdrs->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ pHeader = do_QueryInterface(supports, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ uint32_t msgFlags;
+ pHeader->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::IMAPDeleted)
+ {
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ keysToDelete.AppendElement(msgKey);
+ }
+ }
+ }
+ return;
+ }
+ // otherwise, we have a complete set of uid's and flags, so we delete
+ // anything thats in existingKeys but not in the flag state, as well
+ // as messages with the deleted flag set.
+ uint32_t total = existingKeys.Length();
+ int onlineIndex = 0; // current index into flagState
+ for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++)
+ {
+
+ while ((onlineIndex < numMessageInFlagState) &&
+ (flagState->GetUidOfMessage(onlineIndex, &uidOfMessage), (existingKeys[keyIndex] > uidOfMessage) ))
+ onlineIndex++;
+
+ flagState->GetUidOfMessage(onlineIndex, &uidOfMessage);
+ flagState->GetMessageFlags(onlineIndex, &flags);
+ // delete this key if it is not there or marked deleted
+ if ( (onlineIndex >= numMessageInFlagState ) ||
+ (existingKeys[keyIndex] != uidOfMessage) ||
+ ((flags & kImapMsgDeletedFlag) && !showDeletedMessages) )
+ {
+ nsMsgKey doomedKey = existingKeys[keyIndex];
+ if ((int32_t) doomedKey <= 0 && doomedKey != nsMsgKey_None)
+ continue;
+ else
+ keysToDelete.AppendElement(existingKeys[keyIndex]);
+ }
+
+ flagState->GetUidOfMessage(onlineIndex, &uidOfMessage);
+ if (existingKeys[keyIndex] == uidOfMessage)
+ onlineIndex++;
+ }
+}
+
+void nsImapMailFolder::FindKeysToAdd(const nsTArray<nsMsgKey> &existingKeys, nsTArray<nsMsgKey> &keysToFetch, uint32_t &numNewUnread, nsIImapFlagAndUidState *flagState)
+{
+ bool showDeletedMessages = ShowDeletedMessages();
+ int dbIndex=0; // current index into existingKeys
+ int32_t existTotal, numberOfKnownKeys;
+ int32_t messageIndex;
+
+ numNewUnread = 0;
+ existTotal = numberOfKnownKeys = existingKeys.Length();
+ flagState->GetNumberOfMessages(&messageIndex);
+ bool partialUIDFetch;
+ flagState->GetPartialUIDFetch(&partialUIDFetch);
+
+ for (int32_t flagIndex=0; flagIndex < messageIndex; flagIndex++)
+ {
+ uint32_t uidOfMessage;
+ flagState->GetUidOfMessage(flagIndex, &uidOfMessage);
+ while ( (flagIndex < numberOfKnownKeys) && (dbIndex < existTotal) &&
+ existingKeys[dbIndex] < uidOfMessage)
+ dbIndex++;
+
+ if ( (flagIndex >= numberOfKnownKeys) ||
+ (dbIndex >= existTotal) ||
+ (existingKeys[dbIndex] != uidOfMessage ) )
+ {
+ numberOfKnownKeys++;
+
+ imapMessageFlagsType flags;
+ flagState->GetMessageFlags(flagIndex, &flags);
+ NS_ASSERTION(uidOfMessage != nsMsgKey_None, "got invalid msg key");
+ if (uidOfMessage && uidOfMessage != nsMsgKey_None && (showDeletedMessages || ! (flags & kImapMsgDeletedFlag)))
+ {
+ if (mDatabase)
+ {
+ bool dbContainsKey;
+ if (NS_SUCCEEDED(mDatabase->ContainsKey(uidOfMessage, &dbContainsKey)) &&
+ dbContainsKey)
+ {
+ // this is expected in the partial uid fetch case because the
+ // flag state does not contain all messages, so the db has
+ // messages the flag state doesn't know about.
+ if (!partialUIDFetch)
+ NS_ERROR("db has key - flagState messed up?");
+ continue;
+ }
+ }
+ keysToFetch.AppendElement(uidOfMessage);
+ if (! (flags & kImapMsgSeenFlag))
+ numNewUnread++;
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetMsgHdrsToDownload(bool *aMoreToDownload,
+ int32_t *aTotalCount,
+ uint32_t *aLength,
+ nsMsgKey **aKeys)
+{
+ NS_ENSURE_ARG_POINTER(aMoreToDownload);
+ NS_ENSURE_ARG_POINTER(aTotalCount);
+ NS_ENSURE_ARG_POINTER(aLength);
+ NS_ENSURE_ARG_POINTER(aKeys);
+
+ *aMoreToDownload = false;
+ *aTotalCount = m_totalKeysToFetch;
+ if (m_keysToFetch.IsEmpty())
+ {
+ *aLength = 0;
+ return NS_OK;
+ }
+
+ // if folder isn't open in a window, no reason to limit the number of headers
+ // we download.
+ nsCOMPtr<nsIMsgMailSession> session = do_GetService(NS_MSGMAILSESSION_CONTRACTID);
+ bool folderOpen = false;
+ if (session)
+ session->IsFolderOpenInWindow(this, &folderOpen);
+
+ int32_t hdrChunkSize = 200;
+ if (folderOpen)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (prefBranch)
+ prefBranch->GetIntPref("mail.imap.hdr_chunk_size", &hdrChunkSize);
+ }
+ int32_t numKeysToFetch = m_keysToFetch.Length();
+ int32_t startIndex = 0;
+ if (folderOpen && hdrChunkSize > 0 && (int32_t) m_keysToFetch.Length() > hdrChunkSize)
+ {
+ numKeysToFetch = hdrChunkSize;
+ *aMoreToDownload = true;
+ startIndex = m_keysToFetch.Length() - hdrChunkSize;
+ }
+ *aKeys = (nsMsgKey *) nsMemory::Clone(&m_keysToFetch[startIndex],
+ numKeysToFetch * sizeof(nsMsgKey));
+ NS_ENSURE_TRUE(*aKeys, NS_ERROR_OUT_OF_MEMORY);
+ // Remove these for the incremental header download case, so that
+ // we know we don't have to download them again.
+ m_keysToFetch.RemoveElementsAt(startIndex, numKeysToFetch);
+ *aLength = numKeysToFetch;
+
+ return NS_OK;
+}
+
+void nsImapMailFolder::PrepareToAddHeadersToMailDB(nsIImapProtocol* aProtocol)
+{
+ // now, tell it we don't need any bodies.
+ aProtocol->NotifyBodysToDownload(nullptr, 0);
+}
+
+void nsImapMailFolder::TweakHeaderFlags(nsIImapProtocol* aProtocol, nsIMsgDBHdr *tweakMe)
+{
+ if (mDatabase && aProtocol && tweakMe)
+ {
+ tweakMe->SetMessageKey(m_curMsgUid);
+ tweakMe->SetMessageSize(m_nextMessageByteLength);
+
+ bool foundIt = false;
+ imapMessageFlagsType imap_flags;
+
+ nsCString customFlags;
+ nsresult rv = aProtocol->GetFlagsForUID(m_curMsgUid, &foundIt, &imap_flags, getter_Copies(customFlags));
+ if (NS_SUCCEEDED(rv) && foundIt)
+ {
+ // make a mask and clear these message flags
+ uint32_t mask = nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |
+ nsMsgMessageFlags::Marked | nsMsgMessageFlags::IMAPDeleted |
+ nsMsgMessageFlags::Labels;
+ uint32_t dbHdrFlags;
+
+ tweakMe->GetFlags(&dbHdrFlags);
+ tweakMe->AndFlags(~mask, &dbHdrFlags);
+
+ // set the new value for these flags
+ uint32_t newFlags = 0;
+ if (imap_flags & kImapMsgSeenFlag)
+ newFlags |= nsMsgMessageFlags::Read;
+ else // if (imap_flags & kImapMsgRecentFlag)
+ newFlags |= nsMsgMessageFlags::New;
+
+ // Okay here is the MDN needed logic (if DNT header seen):
+ /* if server support user defined flag:
+ MDNSent flag set => clear kMDNNeeded flag
+ MDNSent flag not set => do nothing, leave kMDNNeeded on
+ else if
+ not nsMsgMessageFlags::New => clear kMDNNeeded flag
+ nsMsgMessageFlags::New => do nothing, leave kMDNNeeded on
+ */
+ uint16_t userFlags;
+ rv = aProtocol->GetSupportedUserFlags(&userFlags);
+ if (NS_SUCCEEDED(rv) && (userFlags & (kImapMsgSupportUserFlag |
+ kImapMsgSupportMDNSentFlag)))
+ {
+ if (imap_flags & kImapMsgMDNSentFlag)
+ {
+ newFlags |= nsMsgMessageFlags::MDNReportSent;
+ if (dbHdrFlags & nsMsgMessageFlags::MDNReportNeeded)
+ tweakMe->AndFlags(~nsMsgMessageFlags::MDNReportNeeded, &dbHdrFlags);
+ }
+ }
+
+ if (imap_flags & kImapMsgAnsweredFlag)
+ newFlags |= nsMsgMessageFlags::Replied;
+ if (imap_flags & kImapMsgFlaggedFlag)
+ newFlags |= nsMsgMessageFlags::Marked;
+ if (imap_flags & kImapMsgDeletedFlag)
+ newFlags |= nsMsgMessageFlags::IMAPDeleted;
+ if (imap_flags & kImapMsgForwardedFlag)
+ newFlags |= nsMsgMessageFlags::Forwarded;
+
+ // db label flags are 0x0E000000 and imap label flags are 0x0E00
+ // so we need to shift 16 bits to the left to convert them.
+ if (imap_flags & kImapMsgLabelFlags)
+ {
+ // we need to set label attribute on header because the dbview code
+ // does msgHdr->GetLabel when asked to paint a row
+ tweakMe->SetLabel((imap_flags & kImapMsgLabelFlags) >> 9);
+ newFlags |= (imap_flags & kImapMsgLabelFlags) << 16;
+ }
+ if (newFlags)
+ tweakMe->OrFlags(newFlags, &dbHdrFlags);
+ if (!customFlags.IsEmpty())
+ (void) HandleCustomFlags(m_curMsgUid, tweakMe, userFlags, customFlags);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetupMsgWriteStream(nsIFile * aFile, bool addDummyEnvelope)
+{
+ nsresult rv;
+ aFile->Remove(false);
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_tempMessageStream), aFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 00700);
+ if (m_tempMessageStream && addDummyEnvelope)
+ {
+ nsAutoCString result;
+ char *ct;
+ uint32_t writeCount;
+ time_t now = time ((time_t*) 0);
+ ct = ctime(&now);
+ ct[24] = 0;
+ result = "From - ";
+ result += ct;
+ result += MSG_LINEBREAK;
+
+ m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ result = "X-Mozilla-Status: 0001";
+ result += MSG_LINEBREAK;
+ m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ result = "X-Mozilla-Status2: 00000000";
+ result += MSG_LINEBREAK;
+ m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::DownloadMessagesForOffline(nsIArray *messages, nsIMsgWindow *window)
+{
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> srcKeyArray;
+ nsresult rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
+ if (NS_FAILED(rv) || messageIds.IsEmpty()) return rv;
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (NS_FAILED(rv))
+ {
+ ThrowAlertMsg("operationFailedFolderBusy", window);
+ return rv;
+ }
+ return imapService->DownloadMessagesForOffline(messageIds, this, this, window);
+}
+
+NS_IMETHODIMP nsImapMailFolder::DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow)
+{
+ nsresult rv;
+ nsCOMPtr <nsIURI> runningURI;
+ bool noSelect;
+ GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
+
+ if (!noSelect)
+ {
+ nsAutoCString messageIdsToDownload;
+ nsTArray<nsMsgKey> msgsToDownload;
+
+ GetDatabase();
+ m_downloadingFolderForOfflineUse = true;
+
+ rv = AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (NS_FAILED(rv))
+ {
+ m_downloadingFolderForOfflineUse = false;
+ ThrowAlertMsg("operationFailedFolderBusy", msgWindow);
+ return rv;
+ }
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Selecting the folder with nsIImapUrl::shouldStoreMsgOffline true will
+ // cause us to fetch any message bodies we don't have.
+ m_urlListener = listener;
+ rv = imapService->SelectFolder(this, this, msgWindow,
+ getter_AddRefs(runningURI));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(runningURI));
+ if (imapUrl)
+ imapUrl->SetStoreResultsOffline(true);
+ m_urlRunning = true;
+ }
+ }
+ else
+ rv = NS_MSG_FOLDER_UNREADABLE;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ParseAdoptedMsgLine(const char *adoptedMessageLine,
+ nsMsgKey uidOfMessage,
+ nsIImapUrl *aImapUrl)
+{
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ uint32_t count = 0;
+ nsresult rv;
+ // remember the uid of the message we're downloading.
+ m_curMsgUid = uidOfMessage;
+ if (!m_offlineHeader)
+ {
+ rv = GetMessageHeader(uidOfMessage, getter_AddRefs(m_offlineHeader));
+ if (NS_SUCCEEDED(rv) && !m_offlineHeader)
+ rv = NS_ERROR_UNEXPECTED;
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StartNewOfflineMessage();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // adoptedMessageLine is actually a string with a lot of message lines, separated by native line terminators
+ // we need to count the number of MSG_LINEBREAK's to determine how much to increment m_numOfflineMsgLines by.
+ const char *nextLine = adoptedMessageLine;
+ do
+ {
+ m_numOfflineMsgLines++;
+ nextLine = PL_strstr(nextLine, MSG_LINEBREAK);
+ if (nextLine)
+ nextLine += MSG_LINEBREAK_LEN;
+ }
+ while (nextLine && *nextLine);
+
+ if (m_tempMessageStream)
+ {
+ nsCOMPtr <nsISeekableStream> seekable (do_QueryInterface(m_tempMessageStream));
+ if (seekable)
+ seekable->Seek(PR_SEEK_END, 0);
+ rv = m_tempMessageStream->Write(adoptedMessageLine,
+ PL_strlen(adoptedMessageLine), &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+void nsImapMailFolder::EndOfflineDownload()
+{
+ if (m_tempMessageStream)
+ {
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+ ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (mDatabase)
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ m_offlineHeader = nullptr;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::NormalEndMsgWriteStream(nsMsgKey uidOfMessage,
+ bool markRead,
+ nsIImapUrl *imapUrl,
+ int32_t updatedMessageSize)
+{
+ if (updatedMessageSize != -1) {
+ // retrieve the message header to update size, if we don't already have it
+ nsCOMPtr<nsIMsgDBHdr> msgHeader = m_offlineHeader;
+ if (!msgHeader)
+ GetMessageHeader(uidOfMessage, getter_AddRefs(msgHeader));
+ if (msgHeader) {
+ uint32_t msgSize;
+ msgHeader->GetMessageSize(&msgSize);
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug, ("Updating stored message size from %u, new size %d",
+ msgSize, updatedMessageSize));
+ msgHeader->SetMessageSize(updatedMessageSize);
+ // only commit here if this isn't an offline message
+ // offline header gets committed in EndNewOfflineMessage() called below
+ if (mDatabase && !m_offlineHeader)
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ else
+ NS_WARNING("Failed to get message header when trying to update message size");
+ }
+
+ if (m_offlineHeader)
+ EndNewOfflineMessage();
+
+ m_curMsgUid = uidOfMessage;
+
+ // Apply filter now if it needed a body
+ if (m_filterListRequiresBody)
+ {
+ if (m_filterList)
+ {
+ nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
+ GetMessageHeader(uidOfMessage, getter_AddRefs(newMsgHdr));
+ GetMoveCoalescer();
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ if (imapUrl)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
+ msgUrl = do_QueryInterface(imapUrl, &rv);
+ if (msgUrl && NS_SUCCEEDED(rv))
+ msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ }
+ m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr,
+ this, mDatabase, nullptr, 0, this,
+ msgWindow);
+ NotifyFolderEvent(mFiltersAppliedAtom);
+ }
+ // Process filter plugins and other items normally done at the end of
+ // HeaderFetchCompleted.
+ bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
+ PlaybackCoalescedOperations();
+
+ bool filtersRun;
+ CallFilterPlugins(nullptr, &filtersRun);
+ int32_t numNewBiffMsgs = 0;
+ if (m_performingBiff)
+ GetNumNewMessages(false, &numNewBiffMsgs);
+
+ if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
+ (!pendingMoves || !ShowPreviewText()))
+ {
+ // If we are performing biff for this folder, tell the
+ // stand-alone biff about the new high water mark
+ // We must ensure that the server knows that we are performing biff.
+ // Otherwise the stand-alone biff won't fire.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
+ server->SetPerformingBiff(true);
+
+ SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
+ if (server)
+ server->SetPerformingBiff(false);
+ m_performingBiff = false;
+ }
+
+ if (m_filterList)
+ (void)m_filterList->FlushLogIfNecessary();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::AbortMsgWriteStream()
+{
+ m_offlineHeader = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+ // message move/copy related methods
+NS_IMETHODIMP
+nsImapMailFolder::OnlineCopyCompleted(nsIImapProtocol *aProtocol, ImapOnlineCopyState aCopyState)
+{
+ NS_ENSURE_ARG_POINTER(aProtocol);
+
+ nsresult rv;
+ if (aCopyState == ImapOnlineCopyStateType::kSuccessfulCopy)
+ {
+ nsCOMPtr <nsIImapUrl> imapUrl;
+ rv = aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
+ if (NS_FAILED(rv) || !imapUrl) return NS_ERROR_FAILURE;
+ nsImapAction action;
+ rv = imapUrl->GetImapAction(&action);
+ if (NS_FAILED(rv)) return rv;
+ if (action != nsIImapUrl::nsImapOnlineToOfflineMove)
+ return NS_ERROR_FAILURE; // don't assert here...
+ nsCString messageIds;
+ rv = imapUrl->GetListOfMessageIds(messageIds);
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return imapService->AddMessageFlags(this, nullptr, nullptr,
+ messageIds,
+ kImapMsgDeletedFlag,
+ true);
+ }
+ /* unhandled copystate */
+ else if (m_copyState) // whoops, this is the wrong folder - should use the source folder
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ srcFolder = do_QueryInterface(m_copyState->m_srcSupport, &rv);
+ if (srcFolder)
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CloseMockChannel(nsIImapMockChannel * aChannel)
+{
+ aChannel->Close();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ReleaseUrlCacheEntry(nsIMsgMailNewsUrl *aUrl)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ return aUrl->SetMemCacheEntry(nullptr);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::BeginMessageUpload()
+{
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsImapMailFolder::HandleCustomFlags(nsMsgKey uidOfMessage,
+ nsIMsgDBHdr *dbHdr,
+ uint16_t userFlags,
+ nsCString &keywords)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ToLowerCase(keywords);
+ bool messageClassified = true;
+ // Mac Mail uses "NotJunk"
+ if (keywords.Find("NonJunk", CaseInsensitiveCompare) != kNotFound ||
+ keywords.Find("NotJunk", CaseInsensitiveCompare) != kNotFound)
+ {
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
+ mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore.get());
+ }
+ // ### TODO: we really should parse the keywords into space delimited keywords before checking
+ else if (keywords.Find("Junk", CaseInsensitiveCompare) != kNotFound)
+ {
+ uint32_t newFlags;
+ dbHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_SPAM_SCORE);
+ mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore.get());
+ }
+ else
+ messageClassified = false;
+ if (messageClassified)
+ {
+ // only set the junkscore origin if it wasn't set before.
+ nsCString existingProperty;
+ dbHdr->GetStringProperty("junkscoreorigin", getter_Copies(existingProperty));
+ if (existingProperty.IsEmpty())
+ dbHdr->SetStringProperty("junkscoreorigin", "imapflag");
+ }
+ return (userFlags & kImapMsgSupportUserFlag) ?
+ dbHdr->SetStringProperty("keywords", keywords.get()) : NS_OK;
+}
+
+// synchronize the message flags in the database with the server flags
+nsresult nsImapMailFolder::SyncFlags(nsIImapFlagAndUidState *flagState)
+{
+ nsresult rv = GetDatabase(); // we need a database for this
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool partialUIDFetch;
+ flagState->GetPartialUIDFetch(&partialUIDFetch);
+
+ // update all of the database flags
+ int32_t messageIndex;
+ uint32_t messageSize;
+
+ // Take this opportunity to recalculate the folder size, if we're not a
+ // partial (condstore) fetch.
+ int64_t newFolderSize = 0;
+
+ flagState->GetNumberOfMessages(&messageIndex);
+
+ uint16_t supportedUserFlags;
+ flagState->GetSupportedUserFlags(&supportedUserFlags);
+
+ for (int32_t flagIndex = 0; flagIndex < messageIndex; flagIndex++)
+ {
+ uint32_t uidOfMessage;
+ flagState->GetUidOfMessage(flagIndex, &uidOfMessage);
+ imapMessageFlagsType flags;
+ flagState->GetMessageFlags(flagIndex, &flags);
+ nsCOMPtr<nsIMsgDBHdr> dbHdr;
+ bool containsKey;
+ rv = mDatabase->ContainsKey(uidOfMessage , &containsKey);
+ // if we don't have the header, don't diddle the flags.
+ // GetMsgHdrForKey will create the header if it doesn't exist.
+ if (NS_FAILED(rv) || !containsKey)
+ continue;
+
+ rv = mDatabase->GetMsgHdrForKey(uidOfMessage, getter_AddRefs(dbHdr));
+ if (NS_SUCCEEDED(dbHdr->GetMessageSize(&messageSize)))
+ newFolderSize += messageSize;
+
+ nsCString keywords;
+ if (NS_SUCCEEDED(flagState->GetCustomFlags(uidOfMessage, getter_Copies(keywords))))
+ HandleCustomFlags(uidOfMessage, dbHdr, supportedUserFlags, keywords);
+
+ NotifyMessageFlagsFromHdr(dbHdr, uidOfMessage, flags);
+ }
+ if (!partialUIDFetch && newFolderSize != mFolderSize)
+ {
+ int64_t oldFolderSize = mFolderSize;
+ mFolderSize = newFolderSize;
+ NotifyIntPropertyChanged(kFolderSizeAtom, oldFolderSize, mFolderSize);
+ }
+
+ return NS_OK;
+}
+
+// helper routine to sync the flags on a given header
+nsresult
+nsImapMailFolder::NotifyMessageFlagsFromHdr(nsIMsgDBHdr *dbHdr,
+ nsMsgKey msgKey, uint32_t flags)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Although it may seem strange to keep a local reference of mDatabase here,
+ // the current lifetime management of databases requires that methods sometimes
+ // null the database when they think they opened it. Unfortunately experience
+ // shows this happens when we don't expect, so for crash protection best
+ // practice with the current flawed database management is to keep a local
+ // reference when there will be complex calls in a method. See bug 1312254.
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ NS_ENSURE_STATE(database);
+
+ database->MarkHdrRead(dbHdr, (flags & kImapMsgSeenFlag) != 0, nullptr);
+ database->MarkHdrReplied(dbHdr, (flags & kImapMsgAnsweredFlag) != 0, nullptr);
+ database->MarkHdrMarked(dbHdr, (flags & kImapMsgFlaggedFlag) != 0, nullptr);
+ database->MarkImapDeleted(msgKey, (flags & kImapMsgDeletedFlag) != 0, nullptr);
+
+ uint32_t supportedFlags;
+ GetSupportedUserFlags(&supportedFlags);
+ if (supportedFlags & kImapMsgSupportForwardedFlag)
+ database->MarkForwarded(msgKey, (flags & kImapMsgForwardedFlag) != 0, nullptr);
+ // this turns on labels, but it doesn't handle the case where the user
+ // unlabels a message on one machine, and expects it to be unlabeled
+ // on their other machines. If I turn that on, I'll be removing all the labels
+ // that were assigned before we started storing them on the server, which will
+ // make some people very unhappy.
+ if (flags & kImapMsgLabelFlags)
+ database->SetLabel(msgKey, (flags & kImapMsgLabelFlags) >> 9);
+ else
+ {
+ if (supportedFlags & kImapMsgLabelFlags)
+ database->SetLabel(msgKey, 0);
+ }
+ if (supportedFlags & kImapMsgSupportMDNSentFlag)
+ database->MarkMDNSent(msgKey, (flags & kImapMsgMDNSentFlag) != 0, nullptr);
+
+ return NS_OK;
+}
+
+// message flags operation - this is called from the imap protocol,
+// proxied over from the imap thread to the ui thread, when a flag changes
+NS_IMETHODIMP
+nsImapMailFolder::NotifyMessageFlags(uint32_t aFlags,
+ const nsACString &aKeywords,
+ nsMsgKey aMsgKey, uint64_t aHighestModSeq)
+{
+ if (NS_SUCCEEDED(GetDatabase()) && mDatabase)
+ {
+ bool msgDeleted = aFlags & kImapMsgDeletedFlag;
+ if (aHighestModSeq || msgDeleted)
+ {
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo)
+ {
+ if (aHighestModSeq)
+ {
+ char intStrBuf[40];
+ PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", aHighestModSeq);
+ dbFolderInfo->SetCharProperty(kModSeqPropertyName, nsDependentCString(intStrBuf));
+ }
+ if (msgDeleted)
+ {
+ uint32_t oldDeletedCount;
+ dbFolderInfo->GetUint32Property(kDeletedHdrCountPropertyName, 0, &oldDeletedCount);
+ dbFolderInfo->SetUint32Property(kDeletedHdrCountPropertyName, oldDeletedCount + 1);
+ }
+ }
+ }
+ nsCOMPtr<nsIMsgDBHdr> dbHdr;
+ bool containsKey;
+ nsresult rv = mDatabase->ContainsKey(aMsgKey , &containsKey);
+ // if we don't have the header, don't diddle the flags.
+ // GetMsgHdrForKey will create the header if it doesn't exist.
+ if (NS_FAILED(rv) || !containsKey)
+ return rv;
+ rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(dbHdr));
+ if (NS_SUCCEEDED(rv) && dbHdr)
+ {
+ uint32_t supportedUserFlags;
+ GetSupportedUserFlags(&supportedUserFlags);
+ NotifyMessageFlagsFromHdr(dbHdr, aMsgKey, aFlags);
+ nsCString keywords(aKeywords);
+ HandleCustomFlags(aMsgKey, dbHdr, supportedUserFlags, keywords);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::NotifyMessageDeleted(const char * onlineFolderName, bool deleteAllMsgs, const char * msgIdString)
+{
+ if (deleteAllMsgs)
+ return NS_OK;
+
+ if (!msgIdString)
+ return NS_OK;
+
+ nsTArray<nsMsgKey> affectedMessages;
+ ParseUidString(msgIdString, affectedMessages);
+
+ if (!ShowDeletedMessages())
+ {
+ GetDatabase();
+ NS_ENSURE_TRUE(mDatabase, NS_OK);
+ if (!ShowDeletedMessages())
+ {
+ if (!affectedMessages.IsEmpty()) // perhaps Search deleted these messages
+ {
+ DeleteStoreMessages(affectedMessages);
+ mDatabase->DeleteMessages(affectedMessages.Length(), affectedMessages.Elements(), nullptr);
+ }
+ }
+ else // && !imapDeleteIsMoveToTrash // TODO: can this ever be executed?
+ SetIMAPDeletedFlag(mDatabase, affectedMessages, false);
+ }
+ return NS_OK;
+}
+
+bool nsImapMailFolder::ShowDeletedMessages()
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionList, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool showDeleted = false;
+ nsCString serverKey;
+ GetServerKey(serverKey);
+ hostSession->GetShowDeletedMessagesForHost(serverKey.get(), showDeleted);
+
+ return showDeleted;
+}
+
+bool nsImapMailFolder::DeleteIsMoveToTrash()
+{
+ nsresult err;
+ nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionList, &err);
+ NS_ENSURE_SUCCESS(err, true);
+ bool rv = true;
+
+ nsCString serverKey;
+ GetServerKey(serverKey);
+ hostSession->GetDeleteIsMoveToTrashForHost(serverKey.get(), rv);
+ return rv;
+}
+
+nsresult nsImapMailFolder::GetTrashFolder(nsIMsgFolder **pTrashFolder)
+{
+ NS_ENSURE_ARG_POINTER(pTrashFolder);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if(NS_SUCCEEDED(rv) && rootFolder)
+ {
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, pTrashFolder);
+ if (!*pTrashFolder)
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+
+// store nsMsgMessageFlags::IMAPDeleted in the specified mailhdr records
+void nsImapMailFolder::SetIMAPDeletedFlag(nsIMsgDatabase *mailDB, const nsTArray<nsMsgKey> &msgids, bool markDeleted)
+{
+ nsresult markStatus = NS_OK;
+ uint32_t total = msgids.Length();
+
+ for (uint32_t msgIndex=0; NS_SUCCEEDED(markStatus) && (msgIndex < total); msgIndex++)
+ markStatus = mailDB->MarkImapDeleted(msgids[msgIndex], markDeleted, nullptr);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetMessageSizeFromDB(const char * id, uint32_t *size)
+{
+ NS_ENSURE_ARG_POINTER(size);
+
+ *size = 0;
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (id)
+ {
+ nsMsgKey key = msgKeyFromInt(ParseUint64Str(id));
+ nsCOMPtr<nsIMsgDBHdr> mailHdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr)
+ rv = mailHdr->GetMessageSize(size);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetContentModified(nsIImapUrl *aImapUrl, nsImapContentModifiedType modified)
+{
+ return aImapUrl->SetContentModified(modified);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCurMoveCopyMessageInfo(nsIImapUrl *runningUrl,
+ PRTime *aDate,
+ nsACString& aKeywords,
+ uint32_t* aResult)
+{
+ nsCOMPtr <nsISupports> copyState;
+ runningUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState)
+ {
+ nsCOMPtr<nsImapMailCopyState> mailCopyState = do_QueryInterface(copyState);
+ uint32_t supportedFlags = 0;
+ GetSupportedUserFlags(&supportedFlags);
+ if (mailCopyState && mailCopyState->m_message)
+ {
+ nsMsgLabelValue label;
+ mailCopyState->m_message->GetFlags(aResult);
+ if (supportedFlags & (kImapMsgSupportUserFlag | kImapMsgLabelFlags))
+ {
+ mailCopyState->m_message->GetLabel(&label);
+ if (label != 0)
+ *aResult |= label << 25;
+ }
+ if (aDate)
+ mailCopyState->m_message->GetDate(aDate);
+ if (supportedFlags & kImapMsgSupportUserFlag)
+ {
+ // setup the custom imap keywords, which includes the message keywords
+ // plus any junk status
+ nsCString junkscore;
+ mailCopyState->m_message->GetStringProperty("junkscore",
+ getter_Copies(junkscore));
+ bool isJunk = false, isNotJunk = false;
+ if (!junkscore.IsEmpty())
+ {
+ if (junkscore.EqualsLiteral("0"))
+ isNotJunk = true;
+ else
+ isJunk = true;
+ }
+
+ nsCString keywords; // MsgFindKeyword can't use nsACString
+ mailCopyState->m_message->GetStringProperty("keywords",
+ getter_Copies(keywords));
+ int32_t start;
+ int32_t length;
+ bool hasJunk = MsgFindKeyword(NS_LITERAL_CSTRING("junk"),
+ keywords, &start, &length);
+ if (hasJunk && !isJunk)
+ keywords.Cut(start, length);
+ else if (!hasJunk && isJunk)
+ keywords.AppendLiteral(" Junk");
+ bool hasNonJunk = MsgFindKeyword(NS_LITERAL_CSTRING("nonjunk"),
+ keywords, &start, &length);
+ if (!hasNonJunk)
+ hasNonJunk = MsgFindKeyword(NS_LITERAL_CSTRING("notjunk"),
+ keywords, &start, &length);
+ if (hasNonJunk && !isNotJunk)
+ keywords.Cut(start, length);
+ else if (!hasNonJunk && isNotJunk)
+ keywords.AppendLiteral(" NonJunk");
+
+ // Cleanup extra spaces
+ while (!keywords.IsEmpty() && keywords.First() == ' ')
+ keywords.Cut(0, 1);
+ while (!keywords.IsEmpty() && keywords.Last() == ' ')
+ keywords.Cut(keywords.Length() - 1, 1);
+ while (!keywords.IsEmpty() &&
+ (start = keywords.Find(NS_LITERAL_CSTRING(" "))) >= 0)
+ keywords.Cut(start, 1);
+ aKeywords.Assign(keywords);
+ }
+ }
+ // if we don't have a source header, and it's not the drafts folder,
+ // then mark the message read, since it must be an append to the
+ // fcc or templates folder.
+ else if (mailCopyState)
+ {
+ *aResult = mailCopyState->m_newMsgFlags;
+ if (supportedFlags & kImapMsgSupportUserFlag)
+ aKeywords.Assign(mailCopyState->m_newMsgKeywords);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::OnStartRunningUrl(nsIURI *aUrl)
+{
+ NS_PRECONDITION(aUrl, "sanity check - need to be be running non-null url");
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ if (mailUrl)
+ {
+ bool updatingFolder;
+ mailUrl->GetUpdatingFolder(&updatingFolder);
+ m_updatingFolder = updatingFolder;
+ }
+ m_urlRunning = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ nsresult rv;
+ bool endedOfflineDownload = false;
+ nsImapAction imapAction = nsIImapUrl::nsImapTest;
+ m_urlRunning = false;
+ m_updatingFolder = false;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aUrl)
+ {
+ nsCOMPtr <nsIImapUrl> imapUrl = do_QueryInterface(aUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool downloadingForOfflineUse;
+ imapUrl->GetStoreResultsOffline(&downloadingForOfflineUse);
+ bool hasSemaphore = false;
+ // if we have the folder locked, clear it.
+ TestSemaphore(static_cast<nsIMsgFolder*>(this), &hasSemaphore);
+ if (hasSemaphore)
+ ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (downloadingForOfflineUse)
+ {
+ endedOfflineDownload = true;
+ EndOfflineDownload();
+ }
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ bool folderOpen = false;
+ if (mailUrl)
+ mailUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (session)
+ session->IsFolderOpenInWindow(this, &folderOpen);
+#ifdef DEBUG_bienvenu
+ printf("stop running url %s\n", aUrl->GetSpecOrDefault().get());
+#endif
+
+ if (imapUrl)
+ {
+ DisplayStatusMsg(imapUrl, EmptyString());
+ imapUrl->GetImapAction(&imapAction);
+ if (imapAction == nsIImapUrl::nsImapMsgFetch || imapAction == nsIImapUrl::nsImapMsgDownloadForOffline)
+ {
+ ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (!endedOfflineDownload)
+ EndOfflineDownload();
+ }
+
+ // Notify move, copy or delete (online operations)
+ // Not sure whether nsImapDeleteMsg is even used, deletes in all three models use nsImapAddMsgFlags.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier && m_copyState)
+ {
+ if (imapAction == nsIImapUrl::nsImapOnlineMove)
+ notifier->NotifyMsgsMoveCopyCompleted(true, m_copyState->m_messages, this, nullptr);
+ else if (imapAction == nsIImapUrl::nsImapOnlineCopy)
+ notifier->NotifyMsgsMoveCopyCompleted(false, m_copyState->m_messages, this, nullptr);
+ else if (imapAction == nsIImapUrl::nsImapDeleteMsg)
+ notifier->NotifyMsgsDeleted(m_copyState->m_messages);
+ }
+
+ switch(imapAction)
+ {
+ case nsIImapUrl::nsImapDeleteMsg:
+ case nsIImapUrl::nsImapOnlineMove:
+ case nsIImapUrl::nsImapOnlineCopy:
+ if (NS_SUCCEEDED(aExitCode))
+ {
+ if (folderOpen)
+ UpdateFolder(msgWindow);
+ else
+ UpdatePendingCounts();
+ }
+
+ if (m_copyState)
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(m_copyState->m_srcSupport, &rv);
+ if (m_copyState->m_isMove && !m_copyState->m_isCrossServerOp)
+ {
+ if (NS_SUCCEEDED(aExitCode))
+ {
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ if (srcFolder)
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (NS_SUCCEEDED(rv) && srcDB)
+ {
+ RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
+ nsTArray<nsMsgKey> srcKeyArray;
+ if (m_copyState->m_allowUndo)
+ {
+ msgTxn = m_copyState->m_undoMsgTxn;
+ if (msgTxn)
+ msgTxn->GetSrcKeyArray(srcKeyArray);
+ }
+ else
+ {
+ nsAutoCString messageIds;
+ rv = BuildIdsAndKeyArray(m_copyState->m_messages, messageIds, srcKeyArray);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (!ShowDeletedMessages())
+ {
+ // We only reach here for same-server operations
+ // (!m_copyState->m_isCrossServerOp in if above), so we can
+ // assume that the src is also imap that uses offline storage.
+ DeleteStoreMessages(srcKeyArray, srcFolder);
+ srcDB->DeleteMessages(srcKeyArray.Length(), srcKeyArray.Elements(), nullptr);
+ }
+ else
+ MarkMessagesImapDeleted(&srcKeyArray, true, srcDB);
+ }
+ srcFolder->EnableNotifications(allMessageCountNotifications, true, true/* dbBatching*/);
+ // even if we're showing deleted messages,
+ // we still need to notify FE so it will show the imap deleted flag
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ // is there a way to see that we think we have new msgs?
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool showPreviewText;
+ prefBranch->GetBoolPref("mail.biff.alert.show_preview", &showPreviewText);
+ // if we're showing preview text, update ourselves if we got a new unread
+ // message copied so that we can download the new headers and have a chance
+ // to preview the msg bodies.
+ if (!folderOpen && showPreviewText && m_copyState->m_unreadCount > 0
+ && ! (mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
+ UpdateFolder(msgWindow);
+ }
+ }
+ else
+ {
+ srcFolder->EnableNotifications(allMessageCountNotifications, true, true/* dbBatching*/);
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ }
+
+ }
+ if (m_copyState->m_msgWindow &&
+ m_copyState->m_undoMsgTxn && // may be null from filters
+ NS_SUCCEEDED(aExitCode)) //we should do this only if move/copy succeeds
+ {
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ m_copyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr)
+ {
+ mozilla::DebugOnly<nsresult> rv2 = txnMgr->DoTransaction(m_copyState->m_undoMsgTxn);
+ NS_ASSERTION(NS_SUCCEEDED(rv2), "doing transaction failed");
+ }
+ }
+ (void) OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
+ }
+
+ // we're the dest folder of a move/copy - if we're not open in the ui,
+ // then we should clear our nsMsgDatabase pointer. Otherwise, the db would
+ // be open until the user selected it and then selected another folder.
+ // but don't do this for the trash or inbox - we'll leave them open
+ if (!folderOpen && ! (mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
+ SetMsgDatabase(nullptr);
+ break;
+ case nsIImapUrl::nsImapSubtractMsgFlags:
+ {
+ // this isn't really right - we'd like to know we were
+ // deleting a message to start with, but it probably
+ // won't do any harm.
+ imapMessageFlagsType flags = 0;
+ imapUrl->GetMsgFlags(&flags);
+ //we need to subtract the delete flag in db only in case when we show deleted msgs
+ if (flags & kImapMsgDeletedFlag && ShowDeletedMessages())
+ {
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db)
+ {
+ nsTArray<nsMsgKey> keyArray;
+ nsCString keyString;
+ imapUrl->GetListOfMessageIds(keyString);
+ ParseUidString(keyString.get(), keyArray);
+ MarkMessagesImapDeleted(&keyArray, false, db);
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapAddMsgFlags:
+ {
+ imapMessageFlagsType flags = 0;
+ imapUrl->GetMsgFlags(&flags);
+ if (flags & kImapMsgDeletedFlag)
+ {
+ // we need to delete headers from db only when we don't show deleted msgs
+ if (!ShowDeletedMessages())
+ {
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db)
+ {
+ nsTArray<nsMsgKey> keyArray;
+ nsCString keyString;
+ imapUrl->GetListOfMessageIds(keyString);
+ ParseUidString(keyString.get(), keyArray);
+
+ // For pluggable stores that do not support compaction, we need
+ // to delete the messages now.
+ bool supportsCompaction;
+ uint32_t numHdrs = 0;
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void) GetMsgStore(getter_AddRefs(offlineStore));
+ if (offlineStore)
+ offlineStore->GetSupportsCompaction(&supportsCompaction);
+
+ nsCOMPtr<nsIMutableArray> msgHdrs;
+ if (notifier || !supportsCompaction)
+ {
+ msgHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ NS_ENSURE_STATE(msgHdrs);
+ MsgGetHeadersFromKeys(db, keyArray, msgHdrs);
+ msgHdrs->GetLength(&numHdrs);
+ }
+
+ // Notify listeners of delete.
+ if (notifier && numHdrs)
+ {
+ // XXX Currently, the DeleteMessages below gets executed twice on deletes.
+ // Once in DeleteMessages, once here. The second time, it silently fails
+ // to delete. This is why we're also checking whether the array is empty.
+ notifier->NotifyMsgsDeleted(msgHdrs);
+ }
+
+ if (!supportsCompaction && numHdrs)
+ DeleteStoreMessages(msgHdrs);
+
+ db->DeleteMessages(keyArray.Length(), keyArray.Elements(), nullptr);
+ db->SetSummaryValid(true);
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ }
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapAppendMsgFromFile:
+ case nsIImapUrl::nsImapAppendDraftFromFile:
+ if (m_copyState)
+ {
+ if (NS_SUCCEEDED(aExitCode))
+ {
+ UpdatePendingCounts();
+
+ m_copyState->m_curIndex++;
+ if (m_copyState->m_curIndex >= m_copyState->m_totalCount)
+ {
+ nsCOMPtr<nsIUrlListener> saveUrlListener = m_urlListener;
+ if (folderOpen)
+ {
+ // This gives a way for the caller to get notified
+ // when the UpdateFolder url is done.
+ if (m_copyState->m_listener)
+ m_urlListener = do_QueryInterface(m_copyState->m_listener);
+ }
+ if (m_copyState->m_msgWindow && m_copyState->m_undoMsgTxn)
+ {
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ m_copyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr)
+ txnMgr->DoTransaction(m_copyState->m_undoMsgTxn);
+ }
+ (void) OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
+ if (folderOpen ||
+ imapAction == nsIImapUrl::nsImapAppendDraftFromFile)
+ {
+ UpdateFolderWithListener(msgWindow, m_urlListener);
+ m_urlListener = saveUrlListener;
+ }
+ }
+ }
+ else
+ //clear the copyState if copy has failed
+ (void) OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
+ }
+ break;
+ case nsIImapUrl::nsImapMoveFolderHierarchy:
+ if (m_copyState) // delete folder gets here, but w/o an m_copyState
+ {
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(m_copyState->m_srcSupport);
+ if (srcFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ nsString srcName;
+ srcFolder->GetName(srcName);
+ GetChildNamed(srcName, getter_AddRefs(destFolder));
+ if (destFolder)
+ copyService->NotifyCompletion(m_copyState->m_srcSupport, destFolder, aExitCode);
+ }
+ m_copyState = nullptr;
+ }
+ break;
+ case nsIImapUrl::nsImapRenameFolder:
+ if (NS_FAILED(aExitCode))
+ {
+ nsCOMPtr <nsIAtom> folderRenameAtom;
+ folderRenameAtom = MsgGetAtom("RenameCompleted");
+ NotifyFolderEvent(folderRenameAtom);
+ }
+ break;
+ case nsIImapUrl::nsImapDeleteAllMsgs:
+ if (NS_SUCCEEDED(aExitCode))
+ {
+ if (folderOpen)
+ UpdateFolder(msgWindow);
+ else
+ {
+ ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
+ ChangeNumPendingUnread(-mNumPendingUnreadMessages);
+ m_numServerUnseenMessages = 0;
+ }
+
+ }
+ break;
+ case nsIImapUrl::nsImapListFolder:
+ if (NS_SUCCEEDED(aExitCode))
+ {
+ // listing folder will open db; don't leave the db open.
+ SetMsgDatabase(nullptr);
+ if (!m_verifiedAsOnlineFolder)
+ {
+ // If folder is not verified, we remove it.
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapParent = do_QueryInterface(parent);
+ if (imapParent)
+ imapParent->RemoveSubFolder(this);
+ }
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapRefreshFolderUrls:
+ // we finished getting an admin url for the folder.
+ if (!m_adminUrl.IsEmpty())
+ FolderPrivileges(msgWindow);
+ break;
+ case nsIImapUrl::nsImapCreateFolder:
+ if (NS_FAILED(aExitCode)) //if success notification already done
+ {
+ nsCOMPtr <nsIAtom> folderCreateAtom;
+ folderCreateAtom = MsgGetAtom("FolderCreateFailed");
+ NotifyFolderEvent(folderCreateAtom);
+ }
+ break;
+ case nsIImapUrl::nsImapSubscribe:
+ if (NS_SUCCEEDED(aExitCode) && msgWindow)
+ {
+ nsCString canonicalFolderName;
+ imapUrl->CreateCanonicalSourceFolderPathString(getter_Copies(canonicalFolderName));
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if(NS_SUCCEEDED(rv) && rootFolder)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ if (imapRoot)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> foundFolder;
+ rv = imapRoot->FindOnlineSubFolder(canonicalFolderName, getter_AddRefs(foundFolder));
+ if (NS_SUCCEEDED(rv) && foundFolder)
+ {
+ nsCString uri;
+ nsCOMPtr <nsIMsgFolder> msgFolder = do_QueryInterface(foundFolder);
+ if (msgFolder)
+ {
+ msgFolder->GetURI(uri);
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ msgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectFolder(uri);
+ }
+ }
+ }
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapExpungeFolder:
+ m_expunging = false;
+ break;
+ default:
+ break;
+ }
+ }
+ // give base class a chance to send folder loaded notification...
+ rv = nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
+ }
+ // if we're not running a url, we must not be getting new mail.
+ SetGettingNewMessages(false);
+ // don't send OnStopRunning notification if still compacting offline store.
+ if (m_urlListener && (imapAction != nsIImapUrl::nsImapExpungeFolder ||
+ !m_compactingOfflineStore))
+ {
+ nsCOMPtr<nsIUrlListener> saveListener = m_urlListener;
+ m_urlListener = nullptr;
+ saveListener->OnStopRunningUrl(aUrl, aExitCode);
+ }
+ return rv;
+}
+
+void nsImapMailFolder::UpdatePendingCounts()
+{
+ if (m_copyState)
+ {
+ ChangePendingTotal(m_copyState->m_isCrossServerOp ? 1 : m_copyState->m_totalCount);
+
+ // count the moves that were unread
+ int numUnread = m_copyState->m_unreadCount;
+ if (numUnread)
+ {
+ m_numServerUnseenMessages += numUnread; // adjust last status count by this delta.
+ ChangeNumPendingUnread(numUnread);
+ }
+ SummaryChanged();
+ }
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ClearFolderRights()
+{
+ SetFolderNeedsACLListed(false);
+ delete m_folderACL;
+ m_folderACL = new nsMsgIMAPFolderACL(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::AddFolderRights(const nsACString& userName, const nsACString& rights)
+{
+ SetFolderNeedsACLListed(false);
+ GetFolderACL()->SetFolderRightsForUser(userName, rights);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::RefreshFolderRights()
+{
+ if (GetFolderACL()->GetIsFolderShared())
+ SetFlag(nsMsgFolderFlags::PersonalShared);
+ else
+ ClearFlag(nsMsgFolderFlags::PersonalShared);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetCopyResponseUid(const char* msgIdString,
+ nsIImapUrl * aUrl)
+{ // CopyMessages() only
+ nsresult rv = NS_OK;
+ RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
+ nsCOMPtr<nsISupports> copyState;
+
+ if (aUrl)
+ aUrl->GetCopyState(getter_AddRefs(copyState));
+
+ if (copyState)
+ {
+ nsCOMPtr<nsImapMailCopyState> mailCopyState =
+ do_QueryInterface(copyState, &rv);
+ if (NS_FAILED(rv)) return rv;
+ if (mailCopyState->m_undoMsgTxn)
+ msgTxn = mailCopyState->m_undoMsgTxn;
+ }
+ else if (aUrl && m_pendingOfflineMoves.Length())
+ {
+ nsCString urlSourceMsgIds, undoTxnSourceMsgIds;
+ aUrl->GetListOfMessageIds(urlSourceMsgIds);
+ RefPtr<nsImapMoveCopyMsgTxn> imapUndo = m_pendingOfflineMoves[0];
+ if (imapUndo)
+ {
+ imapUndo->GetSrcMsgIds(undoTxnSourceMsgIds);
+ if (undoTxnSourceMsgIds.Equals(urlSourceMsgIds))
+ msgTxn = imapUndo;
+ // ### we should handle batched moves, but lets keep it simple for a2.
+ m_pendingOfflineMoves.Clear();
+ }
+ }
+ if (msgTxn)
+ msgTxn->SetCopyResponseUid(msgIdString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::StartMessage(nsIMsgMailNewsUrl * aUrl)
+{
+ nsCOMPtr<nsIImapUrl> imapUrl (do_QueryInterface(aUrl));
+ nsCOMPtr<nsISupports> copyState;
+ NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE);
+
+ imapUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState)
+ {
+ nsCOMPtr <nsICopyMessageStreamListener> listener = do_QueryInterface(copyState);
+ if (listener)
+ listener->StartMessage();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::EndMessage(nsIMsgMailNewsUrl * aUrl, nsMsgKey uidOfMessage)
+{
+ nsCOMPtr<nsIImapUrl> imapUrl (do_QueryInterface(aUrl));
+ nsCOMPtr<nsISupports> copyState;
+ NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE);
+ imapUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState)
+ {
+ nsCOMPtr <nsICopyMessageStreamListener> listener = do_QueryInterface(copyState);
+ if (listener)
+ listener->EndMessage(uidOfMessage);
+ }
+ return NS_OK;
+}
+
+#define WHITESPACE " \015\012" // token delimiter
+
+NS_IMETHODIMP
+nsImapMailFolder::NotifySearchHit(nsIMsgMailNewsUrl * aUrl,
+ const char* searchHitLine)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // expect search results in the form of "* SEARCH <hit> <hit> ..."
+ // expect search results in the form of "* SEARCH <hit> <hit> ..."
+ nsCString tokenString(searchHitLine);
+ char *currentPosition = PL_strcasestr(tokenString.get(), "SEARCH");
+ if (currentPosition)
+ {
+ currentPosition += strlen("SEARCH");
+ bool shownUpdateAlert = false;
+ char *hitUidToken = NS_strtok(WHITESPACE, &currentPosition);
+ while (hitUidToken)
+ {
+ long naturalLong; // %l is 64 bits on OSF1
+ sscanf(hitUidToken, "%ld", &naturalLong);
+ nsMsgKey hitUid = (nsMsgKey) naturalLong;
+
+ nsCOMPtr <nsIMsgDBHdr> hitHeader;
+ rv = mDatabase->GetMsgHdrForKey(hitUid, getter_AddRefs(hitHeader));
+ if (NS_SUCCEEDED(rv) && hitHeader)
+ {
+ nsCOMPtr <nsIMsgSearchSession> searchSession;
+ nsCOMPtr <nsIMsgSearchAdapter> searchAdapter;
+ aUrl->GetSearchSession(getter_AddRefs(searchSession));
+ if (searchSession)
+ {
+ searchSession->GetRunningAdapter(getter_AddRefs(searchAdapter));
+ if (searchAdapter)
+ searchAdapter->AddResultElement(hitHeader);
+ }
+ }
+ else if (!shownUpdateAlert)
+ {
+ }
+
+ hitUidToken = NS_strtok(WHITESPACE, &currentPosition);
+ }
+}
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetAppendMsgUid(nsMsgKey aKey,
+ nsIImapUrl * aUrl)
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> copyState;
+ if (aUrl)
+ aUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState)
+ {
+ nsCOMPtr<nsImapMailCopyState> mailCopyState = do_QueryInterface(copyState, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mailCopyState->m_undoMsgTxn) // CopyMessages()
+ {
+ RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
+ msgTxn = mailCopyState->m_undoMsgTxn;
+ msgTxn->AddDstKey(aKey);
+ }
+ else if (mailCopyState->m_listener) // CopyFileMessage();
+ // Draft/Template goes here
+ {
+ mailCopyState->m_appendUID = aKey;
+ mailCopyState->m_listener->SetMessageKey(aKey);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetMessageId(nsIImapUrl * aUrl,
+ nsACString &messageId)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISupports> copyState;
+
+ if (aUrl)
+ aUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState)
+ {
+ nsCOMPtr<nsImapMailCopyState> mailCopyState = do_QueryInterface(copyState, &rv);
+ if (NS_FAILED(rv)) return rv;
+ if (mailCopyState->m_listener)
+ rv = mailCopyState->m_listener->GetMessageId(messageId);
+ }
+ if (NS_SUCCEEDED(rv) && messageId.Length() > 0)
+ {
+ if (messageId.First() == '<')
+ messageId.Cut(0, 1);
+ if (messageId.Last() == '>')
+ messageId.SetLength(messageId.Length() -1);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::HeaderFetchCompleted(nsIImapProtocol* aProtocol)
+{
+ nsCOMPtr <nsIMsgWindow> msgWindow; // we might need this for the filter plugins.
+ if (mBackupDatabase)
+ RemoveBackupMsgDatabase();
+
+ SetSizeOnDisk(mFolderSize);
+ int32_t numNewBiffMsgs = 0;
+ if (m_performingBiff)
+ GetNumNewMessages(false, &numNewBiffMsgs);
+
+ bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
+ PlaybackCoalescedOperations();
+ if (aProtocol)
+ {
+ // check if we should download message bodies because it's the inbox and
+ // the server is specified as one where where we download msg bodies automatically.
+ // Or if we autosyncing all offline folders.
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ GetImapIncomingServer(getter_AddRefs(imapServer));
+
+ bool autoDownloadNewHeaders = false;
+ bool autoSyncOfflineStores = false;
+
+ if (imapServer)
+ {
+ imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
+ imapServer->GetDownloadBodiesOnGetNewMail(&autoDownloadNewHeaders);
+ if (m_filterListRequiresBody)
+ autoDownloadNewHeaders = true;
+ }
+ bool notifiedBodies = false;
+ if (m_downloadingFolderForOfflineUse || autoSyncOfflineStores ||
+ autoDownloadNewHeaders)
+ {
+ nsTArray<nsMsgKey> keysToDownload;
+ GetBodysToDownload(&keysToDownload);
+ // this is the case when DownloadAllForOffline is called.
+ if (!keysToDownload.IsEmpty() && (m_downloadingFolderForOfflineUse ||
+ autoDownloadNewHeaders))
+ {
+ notifiedBodies = true;
+ aProtocol->NotifyBodysToDownload(keysToDownload.Elements(), keysToDownload.Length());
+ }
+ else
+ {
+ // create auto-sync state object lazily
+ InitAutoSyncState();
+
+ // make enough room for new downloads
+ m_autoSyncStateObj->ManageStorageSpace();
+ m_autoSyncStateObj->SetServerCounts(m_numServerTotalMessages,
+ m_numServerRecentMessages,
+ m_numServerUnseenMessages,
+ m_nextUID);
+ m_autoSyncStateObj->OnNewHeaderFetchCompleted(keysToDownload);
+ }
+ }
+ if (!notifiedBodies)
+ aProtocol->NotifyBodysToDownload(nullptr, 0/*keysToFetch.Length() */);
+
+ nsCOMPtr <nsIURI> runningUri;
+ aProtocol->GetRunningUrl(getter_AddRefs(runningUri));
+ if (runningUri)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(runningUri);
+ if (mailnewsUrl)
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ }
+ }
+
+ // delay calling plugins if filter application is also delayed
+ if (!m_filterListRequiresBody)
+ {
+ bool filtersRun;
+ CallFilterPlugins(msgWindow, &filtersRun);
+ if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
+ (!pendingMoves || !ShowPreviewText()))
+ {
+ // If we are performing biff for this folder, tell the
+ // stand-alone biff about the new high water mark
+ // We must ensure that the server knows that we are performing biff.
+ // Otherwise the stand-alone biff won't fire.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
+ server->SetPerformingBiff(true);
+
+ SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
+ if (server)
+ server->SetPerformingBiff(false);
+ m_performingBiff = false;
+ }
+
+ if (m_filterList)
+ (void)m_filterList->FlushLogIfNecessary();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetBiffStateAndUpdate(nsMsgBiffState biffState)
+{
+ SetBiffState(biffState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetUidValidity(int32_t *uidValidity)
+{
+ NS_ENSURE_ARG(uidValidity);
+ if ((int32_t)m_uidValidity == kUidUnknown)
+ {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ (void) GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db));
+ if (db)
+ db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+
+ if (dbFolderInfo)
+ dbFolderInfo->GetImapUidValidity((int32_t *) &m_uidValidity);
+ }
+ *uidValidity = m_uidValidity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetUidValidity(int32_t uidValidity)
+{
+ m_uidValidity = uidValidity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::FillInFolderProps(nsIMsgImapFolderProps *aFolderProps)
+{
+ NS_ENSURE_ARG(aFolderProps);
+ const char* folderTypeStringID;
+ const char* folderTypeDescStringID = nullptr;
+ const char* folderQuotaStatusStringID;
+ nsString folderType;
+ nsString folderTypeDesc;
+ nsString folderQuotaStatusDesc;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the host session list and get server capabilities.
+ eIMAPCapabilityFlags capability = kCapabilityUndefined;
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ // if for some bizarre reason this fails, we'll still fall through to the normal sharing code
+ if (NS_SUCCEEDED(rv))
+ {
+ bool haveACL = false;
+ bool haveQuota = false;
+ imapServer->GetCapabilityACL(&haveACL);
+ imapServer->GetCapabilityQuota(&haveQuota);
+
+ // Figure out what to display in the Quota tab of the folder properties.
+ // Does the server support quotas?
+ if (haveQuota)
+ {
+ // Have we asked the server for quota information?
+ if(m_folderQuotaCommandIssued)
+ {
+ // Has the server replied with storage quota info?
+ if(m_folderQuotaDataIsValid)
+ {
+ // If so, set quota data
+ folderQuotaStatusStringID = nullptr;
+ aFolderProps->SetQuotaData(m_folderQuotaRoot, m_folderQuotaUsedKB, m_folderQuotaMaxKB);
+ }
+ else
+ {
+ // If not, there is no storage quota set on this folder
+ folderQuotaStatusStringID = "imapQuotaStatusNoQuota";
+ }
+ }
+ else
+ {
+ // The folder is not open, so no quota information is available
+ folderQuotaStatusStringID = "imapQuotaStatusFolderNotOpen";
+ }
+ }
+ else
+ {
+ // Either the server doesn't support quotas, or we don't know if it does
+ // (e.g., because we don't have a connection yet). If the latter, we fall back
+ // to saying that no information is available because the folder is not open.
+ folderQuotaStatusStringID = (capability == kCapabilityUndefined) ?
+ "imapQuotaStatusFolderNotOpen" :
+ "imapQuotaStatusNotSupported";
+ }
+
+ if(!folderQuotaStatusStringID)
+ {
+ // Display quota data
+ aFolderProps->ShowQuotaData(true);
+ }
+ else
+ {
+ // Hide quota data and show reason why it is not available
+ aFolderProps->ShowQuotaData(false);
+
+ rv = IMAPGetStringByName(folderQuotaStatusStringID,
+ getter_Copies(folderQuotaStatusDesc));
+ if (NS_SUCCEEDED(rv))
+ aFolderProps->SetQuotaStatus(folderQuotaStatusDesc);
+ }
+
+ // See if the server supports ACL.
+ // If not, just set the folder description to a string that says
+ // the server doesn't support sharing, and return.
+ if (!haveACL)
+ {
+ rv = IMAPGetStringByName("imapServerDoesntSupportAcl",
+ getter_Copies(folderTypeDesc));
+ if (NS_SUCCEEDED(rv))
+ aFolderProps->SetFolderTypeDescription(folderTypeDesc);
+ aFolderProps->ServerDoesntSupportACL();
+ return NS_OK;
+ }
+ }
+ if (mFlags & nsMsgFolderFlags::ImapPublic)
+ {
+ folderTypeStringID = "imapPublicFolderTypeName";
+ folderTypeDescStringID = "imapPublicFolderTypeDescription";
+ }
+ else if (mFlags & nsMsgFolderFlags::ImapOtherUser)
+ {
+ folderTypeStringID = "imapOtherUsersFolderTypeName";
+ nsCString owner;
+ nsString uniOwner;
+ GetFolderOwnerUserName(owner);
+ if (owner.IsEmpty())
+ {
+ rv = IMAPGetStringByName(folderTypeStringID,
+ getter_Copies(uniOwner));
+ // Another user's folder, for which we couldn't find an owner name
+ NS_ASSERTION(false, "couldn't get owner name for other user's folder");
+ }
+ else
+ {
+ // is this right? It doesn't leak, does it?
+ CopyASCIItoUTF16(owner, uniOwner);
+ }
+ const char16_t *params[] = { uniOwner.get() };
+ rv = bundle->FormatStringFromName(
+ u"imapOtherUsersFolderTypeDescription",
+ params, 1, getter_Copies(folderTypeDesc));
+ }
+ else if (GetFolderACL()->GetIsFolderShared())
+ {
+ folderTypeStringID = "imapPersonalSharedFolderTypeName";
+ folderTypeDescStringID = "imapPersonalSharedFolderTypeDescription";
+ }
+ else
+ {
+ folderTypeStringID = "imapPersonalSharedFolderTypeName";
+ folderTypeDescStringID = "imapPersonalFolderTypeDescription";
+ }
+
+ rv = IMAPGetStringByName(folderTypeStringID,
+ getter_Copies(folderType));
+ if (NS_SUCCEEDED(rv))
+ aFolderProps->SetFolderType(folderType);
+
+ if (folderTypeDesc.IsEmpty() && folderTypeDescStringID)
+ rv = IMAPGetStringByName(folderTypeDescStringID,
+ getter_Copies(folderTypeDesc));
+ if (!folderTypeDesc.IsEmpty())
+ aFolderProps->SetFolderTypeDescription(folderTypeDesc);
+
+ nsString rightsString;
+ rv = CreateACLRightsStringForFolder(rightsString);
+ if (NS_SUCCEEDED(rv))
+ aFolderProps->SetFolderPermissions(rightsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetAclFlags(uint32_t aclFlags)
+{
+ nsresult rv = NS_OK;
+ if (m_aclFlags != aclFlags)
+ {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ bool dbWasOpen = (mDatabase != nullptr);
+ rv = GetDatabase();
+
+ m_aclFlags = aclFlags;
+ if (mDatabase)
+ {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo)
+ dbFolderInfo->SetUint32Property("aclFlags", aclFlags);
+ // if setting the acl flags caused us to open the db, release the ref
+ // because on startup, we might get acl on all folders,which will
+ // leave a lot of db's open.
+ if (!dbWasOpen)
+ {
+ mDatabase->Close(true /* commit changes */);
+ mDatabase = nullptr;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetAclFlags(uint32_t *aclFlags)
+{
+ NS_ENSURE_ARG_POINTER(aclFlags);
+ nsresult rv;
+ ReadDBFolderInfo(false); // update cache first.
+ if (m_aclFlags == kAclInvalid) // -1 means invalid value, so get it from db.
+ {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ bool dbWasOpen = (mDatabase != nullptr);
+ rv = GetDatabase();
+
+ if (mDatabase)
+ {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo)
+ {
+ rv = dbFolderInfo->GetUint32Property("aclFlags", 0, aclFlags);
+ m_aclFlags = *aclFlags;
+ }
+ // if getting the acl flags caused us to open the db, release the ref
+ // because on startup, we might get acl on all folders,which will
+ // leave a lot of db's open.
+ if (!dbWasOpen)
+ {
+ mDatabase->Close(true /* commit changes */);
+ mDatabase = nullptr;
+ }
+ }
+ }
+ else
+ *aclFlags = m_aclFlags;
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::SetSupportedUserFlags(uint32_t userFlags)
+{
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsresult rv = GetDatabase();
+
+ m_supportedUserFlags = userFlags;
+ if (mDatabase)
+ {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo)
+ dbFolderInfo->SetUint32Property("imapFlags", userFlags);
+ }
+ return rv;
+}
+
+nsresult nsImapMailFolder::GetSupportedUserFlags(uint32_t *userFlags)
+{
+ NS_ENSURE_ARG_POINTER(userFlags);
+
+ nsresult rv = NS_OK;
+
+ ReadDBFolderInfo(false); // update cache first.
+ if (m_supportedUserFlags == 0) // 0 means invalid value, so get it from db.
+ {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ rv = GetDatabase();
+
+ if (mDatabase)
+ {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo)
+ {
+ rv = dbFolderInfo->GetUint32Property("imapFlags", 0, userFlags);
+ m_supportedUserFlags = *userFlags;
+ }
+ }
+ }
+ else
+ *userFlags = m_supportedUserFlags;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetCanOpenFolder(bool *aBool)
+{
+ NS_ENSURE_ARG_POINTER(aBool);
+ bool noSelect;
+ GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
+ *aBool = (noSelect) ? false : GetFolderACL()->GetCanIReadFolder();
+ return NS_OK;
+}
+
+///////// nsMsgIMAPFolderACL class ///////////////////////////////
+
+// This string is defined in the ACL RFC to be "anyone"
+#define IMAP_ACL_ANYONE_STRING "anyone"
+
+nsMsgIMAPFolderACL::nsMsgIMAPFolderACL(nsImapMailFolder *folder)
+: m_rightsHash(24)
+{
+ NS_ASSERTION(folder, "need folder");
+ m_folder = folder;
+ m_aclCount = 0;
+ BuildInitialACLFromCache();
+}
+
+nsMsgIMAPFolderACL::~nsMsgIMAPFolderACL()
+{
+}
+
+// We cache most of our own rights in the MSG_FOLDER_PREF_* flags
+void nsMsgIMAPFolderACL::BuildInitialACLFromCache()
+{
+ nsAutoCString myrights;
+
+ uint32_t startingFlags;
+ m_folder->GetAclFlags(&startingFlags);
+
+ if (startingFlags & IMAP_ACL_READ_FLAG)
+ myrights += "r";
+ if (startingFlags & IMAP_ACL_STORE_SEEN_FLAG)
+ myrights += "s";
+ if (startingFlags & IMAP_ACL_WRITE_FLAG)
+ myrights += "w";
+ if (startingFlags & IMAP_ACL_INSERT_FLAG)
+ myrights += "i";
+ if (startingFlags & IMAP_ACL_POST_FLAG)
+ myrights += "p";
+ if (startingFlags & IMAP_ACL_CREATE_SUBFOLDER_FLAG)
+ myrights +="c";
+ if (startingFlags & IMAP_ACL_DELETE_FLAG)
+ myrights += "dt";
+ if (startingFlags & IMAP_ACL_ADMINISTER_FLAG)
+ myrights += "a";
+ if (startingFlags & IMAP_ACL_EXPUNGE_FLAG)
+ myrights += "e";
+
+ if (!myrights.IsEmpty())
+ SetFolderRightsForUser(EmptyCString(), myrights);
+}
+
+void nsMsgIMAPFolderACL::UpdateACLCache()
+{
+ uint32_t startingFlags = 0;
+ m_folder->GetAclFlags(&startingFlags);
+
+ if (GetCanIReadFolder())
+ startingFlags |= IMAP_ACL_READ_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_READ_FLAG;
+
+ if (GetCanIStoreSeenInFolder())
+ startingFlags |= IMAP_ACL_STORE_SEEN_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_STORE_SEEN_FLAG;
+
+ if (GetCanIWriteFolder())
+ startingFlags |= IMAP_ACL_WRITE_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_WRITE_FLAG;
+
+ if (GetCanIInsertInFolder())
+ startingFlags |= IMAP_ACL_INSERT_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_INSERT_FLAG;
+
+ if (GetCanIPostToFolder())
+ startingFlags |= IMAP_ACL_POST_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_POST_FLAG;
+
+ if (GetCanICreateSubfolder())
+ startingFlags |= IMAP_ACL_CREATE_SUBFOLDER_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_CREATE_SUBFOLDER_FLAG;
+
+ if (GetCanIDeleteInFolder())
+ startingFlags |= IMAP_ACL_DELETE_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_DELETE_FLAG;
+
+ if (GetCanIAdministerFolder())
+ startingFlags |= IMAP_ACL_ADMINISTER_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_ADMINISTER_FLAG;
+
+ if (GetCanIExpungeFolder())
+ startingFlags |= IMAP_ACL_EXPUNGE_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_EXPUNGE_FLAG;
+
+ m_folder->SetAclFlags(startingFlags);
+}
+
+bool nsMsgIMAPFolderACL::SetFolderRightsForUser(const nsACString& userName, const nsACString& rights)
+{
+ nsCString myUserName;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // we need the real user name to match with what the imap server returns
+ // in the acl response.
+ server->GetRealUsername(myUserName);
+
+ nsAutoCString ourUserName;
+ if (userName.IsEmpty())
+ ourUserName.Assign(myUserName);
+ else
+ ourUserName.Assign(userName);
+
+ if (ourUserName.IsEmpty())
+ return false;
+
+ ToLowerCase(ourUserName);
+ nsCString oldValue;
+ m_rightsHash.Get(ourUserName, &oldValue);
+ if (!oldValue.IsEmpty())
+ {
+ m_rightsHash.Remove(ourUserName);
+ m_aclCount--;
+ NS_ASSERTION(m_aclCount >= 0, "acl count can't go negative");
+ }
+ m_aclCount++;
+ m_rightsHash.Put(ourUserName, PromiseFlatCString(rights));
+
+ if (myUserName.Equals(ourUserName) || ourUserName.EqualsLiteral(IMAP_ACL_ANYONE_STRING))
+ // if this is setting an ACL for me, cache it in the folder pref flags
+ UpdateACLCache();
+
+ return true;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOtherUsersWithAccess(
+ nsIUTF8StringEnumerator** aResult)
+{
+ return GetFolderACL()->GetOtherUsers(aResult);
+}
+
+class AdoptUTF8StringEnumerator final : public nsIUTF8StringEnumerator
+{
+public:
+ explicit AdoptUTF8StringEnumerator(nsTArray<nsCString>* array) :
+ mStrings(array), mIndex(0)
+ {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+private:
+ ~AdoptUTF8StringEnumerator()
+ {
+ delete mStrings;
+ }
+
+ nsTArray<nsCString>* mStrings;
+ uint32_t mIndex;
+};
+
+NS_IMPL_ISUPPORTS(AdoptUTF8StringEnumerator, nsIUTF8StringEnumerator)
+
+NS_IMETHODIMP
+AdoptUTF8StringEnumerator::HasMore(bool *aResult)
+{
+ *aResult = mIndex < mStrings->Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AdoptUTF8StringEnumerator::GetNext(nsACString& aResult)
+{
+ if (mIndex >= mStrings->Length())
+ return NS_ERROR_UNEXPECTED;
+
+ aResult.Assign((*mStrings)[mIndex]);
+ ++mIndex;
+ return NS_OK;
+}
+
+nsresult nsMsgIMAPFolderACL::GetOtherUsers(nsIUTF8StringEnumerator** aResult)
+{
+ nsTArray<nsCString>* resultArray = new nsTArray<nsCString>;
+ for (auto iter = m_rightsHash.Iter(); !iter.Done(); iter.Next()) {
+ resultArray->AppendElement(iter.Key());
+ }
+
+ // enumerator will free resultArray
+ *aResult = new AdoptUTF8StringEnumerator(resultArray);
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::GetPermissionsForUser(const nsACString& otherUser,
+ nsACString& aResult)
+{
+ nsCString str;
+ nsresult rv = GetFolderACL()->GetRightsStringForUser(otherUser, str);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aResult = str;
+ return NS_OK;
+}
+
+nsresult nsMsgIMAPFolderACL::GetRightsStringForUser(const nsACString& inUserName, nsCString &rights)
+{
+ nsCString userName;
+ userName.Assign(inUserName);
+ if (userName.IsEmpty())
+ {
+ nsCOMPtr <nsIMsgIncomingServer> server;
+
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // we need the real user name to match with what the imap server returns
+ // in the acl response.
+ server->GetRealUsername(userName);
+ }
+ ToLowerCase(userName);
+ m_rightsHash.Get(userName, &rights);
+ return NS_OK;
+}
+
+// First looks for individual user; then looks for 'anyone' if the user isn't found.
+// Returns defaultIfNotFound, if neither are found.
+bool nsMsgIMAPFolderACL::GetFlagSetInRightsForUser(const nsACString& userName, char flag, bool defaultIfNotFound)
+{
+ nsCString flags;
+ nsresult rv = GetRightsStringForUser(userName, flags);
+ NS_ENSURE_SUCCESS(rv, defaultIfNotFound);
+ if (flags.IsEmpty())
+ {
+ nsCString anyoneFlags;
+ GetRightsStringForUser(NS_LITERAL_CSTRING(IMAP_ACL_ANYONE_STRING), anyoneFlags);
+ if (anyoneFlags.IsEmpty())
+ return defaultIfNotFound;
+ else
+ return (anyoneFlags.FindChar(flag) != kNotFound);
+ }
+ else
+ return (flags.FindChar(flag) != kNotFound);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserLookupFolder(const nsACString& userName)
+{
+ return GetFlagSetInRightsForUser(userName, 'l', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserReadFolder(const nsACString& userName)
+{
+ return GetFlagSetInRightsForUser(userName, 'r', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserStoreSeenInFolder(const nsACString& userName)
+{
+ return GetFlagSetInRightsForUser(userName, 's', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserWriteFolder(const nsACString& userName)
+{
+ return GetFlagSetInRightsForUser(userName, 'w', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserInsertInFolder(const nsACString& userName)
+{
+ return GetFlagSetInRightsForUser(userName, 'i', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserPostToFolder(const nsACString& userName)
+{
+ return GetFlagSetInRightsForUser(userName, 'p', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserCreateSubfolder(const nsACString& userName)
+{
+ return GetFlagSetInRightsForUser(userName, 'c', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserDeleteInFolder(const nsACString& userName)
+{
+ return GetFlagSetInRightsForUser(userName, 'd', false)
+ || GetFlagSetInRightsForUser(userName, 't', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserAdministerFolder(const nsACString& userName)
+{
+ return GetFlagSetInRightsForUser(userName, 'a', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanILookupFolder()
+{
+ return GetFlagSetInRightsForUser(EmptyCString(), 'l', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIReadFolder()
+{
+ return GetFlagSetInRightsForUser(EmptyCString(), 'r', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIStoreSeenInFolder()
+{
+ return GetFlagSetInRightsForUser(EmptyCString(), 's', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIWriteFolder()
+{
+ return GetFlagSetInRightsForUser(EmptyCString(), 'w', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIInsertInFolder()
+{
+ return GetFlagSetInRightsForUser(EmptyCString(), 'i', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIPostToFolder()
+{
+ return GetFlagSetInRightsForUser(EmptyCString(), 'p', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanICreateSubfolder()
+{
+ return GetFlagSetInRightsForUser(EmptyCString(), 'c', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIDeleteInFolder()
+{
+ return GetFlagSetInRightsForUser(EmptyCString(), 'd', true) ||
+ GetFlagSetInRightsForUser(EmptyCString(), 't', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIAdministerFolder()
+{
+ return GetFlagSetInRightsForUser(EmptyCString(), 'a', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIExpungeFolder()
+{
+ return GetFlagSetInRightsForUser(EmptyCString(), 'e', true) ||
+ GetFlagSetInRightsForUser(EmptyCString(), 'd', true);
+}
+
+// We use this to see if the ACLs think a folder is shared or not.
+// We will define "Shared" in 5.0 to mean:
+// At least one user other than the currently authenticated user has at least one
+// explicitly-listed ACL right on that folder.
+bool nsMsgIMAPFolderACL::GetIsFolderShared()
+{
+ // If we have more than one ACL count for this folder, which means that someone
+ // other than ourself has rights on it, then it is "shared."
+ if (m_aclCount > 1)
+ return true;
+
+ // Or, if "anyone" has rights to it, it is shared.
+ nsCString anyonesRights;
+ m_rightsHash.Get(NS_LITERAL_CSTRING(IMAP_ACL_ANYONE_STRING), &anyonesRights);
+ return (!anyonesRights.IsEmpty());
+}
+
+bool nsMsgIMAPFolderACL::GetDoIHaveFullRightsForFolder()
+{
+ return (GetCanIReadFolder() &&
+ GetCanIWriteFolder() &&
+ GetCanIInsertInFolder() &&
+ GetCanIAdministerFolder() &&
+ GetCanICreateSubfolder() &&
+ GetCanIDeleteInFolder() &&
+ GetCanILookupFolder() &&
+ GetCanIStoreSeenInFolder() &&
+ GetCanIExpungeFolder() &&
+ GetCanIPostToFolder());
+}
+
+// Returns a newly allocated string describing these rights
+nsresult nsMsgIMAPFolderACL::CreateACLRightsString(nsAString& aRightsString)
+{
+ nsString curRight;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (GetDoIHaveFullRightsForFolder()) {
+ nsAutoString result;
+ rv = bundle->GetStringFromName(u"imapAclFullRights",
+ getter_Copies(result));
+ aRightsString.Assign(result);
+ return rv;
+ }
+ else
+ {
+ if (GetCanIReadFolder())
+ {
+ bundle->GetStringFromName(u"imapAclReadRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIWriteFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclWriteRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIInsertInFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclInsertRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanILookupFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclLookupRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIStoreSeenInFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclSeenRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIDeleteInFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclDeleteRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIExpungeFolder())
+ {
+ if (!aRightsString.IsEmpty())
+ aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclExpungeRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanICreateSubfolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclCreateRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIPostToFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclPostRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIAdministerFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclAdministerRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFilePath(nsIFile ** aPathName)
+{
+ // this will return a copy of mPath, which is what we want.
+ // this will also initialize mPath using parseURI if it isn't already done
+ return nsMsgDBFolder::GetFilePath(aPathName);
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFilePath(nsIFile * aPathName)
+{
+ return nsMsgDBFolder::SetFilePath(aPathName); // call base class so mPath will get set
+}
+
+nsresult nsImapMailFolder::DisplayStatusMsg(nsIImapUrl *aImapUrl, const nsAString& msg)
+{
+ nsCOMPtr<nsIImapMockChannel> mockChannel;
+ aImapUrl->GetMockChannel(getter_AddRefs(mockChannel));
+ if (mockChannel)
+ {
+ nsCOMPtr<nsIProgressEventSink> progressSink;
+ mockChannel->GetProgressEventSink(getter_AddRefs(progressSink));
+ if (progressSink)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mockChannel);
+ if (!request) return NS_ERROR_FAILURE;
+ progressSink->OnStatus(request, nullptr, NS_OK, PromiseFlatString(msg).get()); // XXX i18n message
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ProgressStatusString(nsIImapProtocol* aProtocol,
+ const char* aMsgName,
+ const char16_t * extraInfo)
+{
+ nsString progressMsg;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ {
+ nsCOMPtr<nsIImapServerSink> serverSink = do_QueryInterface(server);
+ if (serverSink)
+ serverSink->GetImapStringByName(aMsgName, progressMsg);
+ }
+ if (progressMsg.IsEmpty())
+ IMAPGetStringByName(aMsgName, getter_Copies(progressMsg));
+
+ if (aProtocol && !progressMsg.IsEmpty())
+ {
+ nsCOMPtr <nsIImapUrl> imapUrl;
+ aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
+ if (imapUrl)
+ {
+ if (extraInfo)
+ {
+ char16_t *printfString = nsTextFormatter::smprintf(progressMsg.get(), extraInfo);
+ if (printfString)
+ progressMsg.Adopt(printfString);
+ }
+
+ DisplayStatusMsg(imapUrl, progressMsg);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::PercentProgress(nsIImapProtocol* aProtocol,
+ const char16_t * aMessage,
+ int64_t aCurrentProgress, int64_t aMaxProgress)
+{
+ if (aProtocol)
+ {
+ nsCOMPtr <nsIImapUrl> imapUrl;
+ aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
+ if (imapUrl)
+ {
+ nsCOMPtr<nsIImapMockChannel> mockChannel;
+ imapUrl->GetMockChannel(getter_AddRefs(mockChannel));
+ if (mockChannel)
+ {
+ nsCOMPtr<nsIProgressEventSink> progressSink;
+ mockChannel->GetProgressEventSink(getter_AddRefs(progressSink));
+ if (progressSink)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mockChannel);
+ if (!request) return NS_ERROR_FAILURE;
+ progressSink->OnProgress(request, nullptr,
+ aCurrentProgress,
+ aMaxProgress);
+ if (aMessage)
+ progressSink->OnStatus(request, nullptr, NS_OK, aMessage); // XXX i18n message
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CopyNextStreamMessage(bool copySucceeded, nsISupports *copyState)
+{
+ //if copy has failed it could be either user interrupted it or for some other reason
+ //don't do any subsequent copies or delete src messages if it is move
+ if (!copySucceeded)
+ return NS_OK;
+ nsresult rv;
+ nsCOMPtr<nsImapMailCopyState> mailCopyState = do_QueryInterface(copyState, &rv);
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("QI copyState failed:%lx\n", rv));
+ return rv; // this can fail...
+ }
+
+ if (!mailCopyState->m_streamCopy)
+ return NS_OK;
+
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyNextStreamMessage: Copying %ld of %ld\n", mailCopyState->m_curIndex, mailCopyState->m_totalCount));
+ if (mailCopyState->m_curIndex < mailCopyState->m_totalCount)
+ {
+ mailCopyState->m_message = do_QueryElementAt(mailCopyState->m_messages,
+ mailCopyState->m_curIndex,
+ &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ bool isRead;
+ mailCopyState->m_message->GetIsRead(&isRead);
+ mailCopyState->m_unreadCount = (isRead) ? 0 : 1;
+ rv = CopyStreamMessage(mailCopyState->m_message,
+ this, mailCopyState->m_msgWindow, mailCopyState->m_isMove);
+ }
+ else
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("QueryElementAt %ld failed:%lx\n", mailCopyState->m_curIndex, rv));
+ }
+ }
+ else
+ {
+ // Notify of move/copy completion in case we have some source headers
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ {
+ uint32_t numHdrs;
+ mailCopyState->m_messages->GetLength(&numHdrs);
+ if (numHdrs)
+ notifier->NotifyMsgsMoveCopyCompleted(mailCopyState->m_isMove, mailCopyState->m_messages, this, nullptr);
+ }
+ if (mailCopyState->m_isMove)
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(mailCopyState->m_srcSupport, &rv));
+ if (NS_SUCCEEDED(rv) && srcFolder)
+ {
+ srcFolder->DeleteMessages(mailCopyState->m_messages, nullptr,
+ true, true, nullptr, false);
+ // we want to send this notification after the source messages have
+ // been deleted.
+ nsCOMPtr<nsIMsgLocalMailFolder> popFolder(do_QueryInterface(srcFolder));
+ if (popFolder) //needed if move pop->imap to notify FE
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ }
+ }
+ }
+ if (NS_FAILED(rv))
+ (void) OnCopyCompleted(mailCopyState->m_srcSupport, rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetUrlState(nsIImapProtocol* aProtocol,
+ nsIMsgMailNewsUrl* aUrl,
+ bool isRunning,
+ bool aSuspend,
+ nsresult statusCode)
+{
+ // If we have no path, then the folder has been shutdown, and there's
+ // no point in doing anything...
+ if (!mPath)
+ return NS_OK;
+ if (!isRunning)
+ {
+ ProgressStatusString(aProtocol, "imapDone", nullptr);
+ m_urlRunning = false;
+ // if no protocol, then we're reading from the mem or disk cache
+ // and we don't want to end the offline download just yet.
+ if (aProtocol)
+ {
+ EndOfflineDownload();
+ m_downloadingFolderForOfflineUse = false;
+ }
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aUrl));
+ if (imapUrl)
+ {
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ // if the server doesn't support copyUID, then SetCopyResponseUid won't
+ // get called, so we need to clear m_pendingOfflineMoves when the online
+ // move operation has finished.
+ if (imapAction == nsIImapUrl::nsImapOnlineMove)
+ m_pendingOfflineMoves.Clear();
+ }
+ }
+ if (aUrl && !aSuspend)
+ return aUrl->SetUrlState(isRunning, statusCode);
+ return statusCode;
+}
+
+// used when copying from local mail folder, or other imap server)
+nsresult
+nsImapMailFolder::CopyMessagesWithStream(nsIMsgFolder* srcFolder,
+ nsIArray* messages,
+ bool isMove,
+ bool isCrossServerOp,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener,
+ bool allowUndo)
+{
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ NS_ENSURE_ARG_POINTER(messages);
+ nsresult rv;
+ nsCOMPtr<nsISupports> aSupport(do_QueryInterface(srcFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = InitCopyState(aSupport, messages, isMove, false, isCrossServerOp,
+ 0, EmptyCString(), listener, msgWindow, allowUndo);
+ if(NS_FAILED(rv))
+ return rv;
+
+ m_copyState->m_streamCopy = true;
+
+ // ** jt - needs to create server to server move/copy undo msg txn
+ if (m_copyState->m_allowUndo)
+ {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> srcKeyArray;
+ rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
+
+ RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
+
+ if (!undoMsgTxn || NS_FAILED(undoMsgTxn->Init(srcFolder, &srcKeyArray, messageIds.get(), this,
+ true, isMove)))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (isMove)
+ {
+ if (mFlags & nsMsgFolderFlags::Trash)
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ }
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ m_copyState->m_undoMsgTxn = undoMsgTxn;
+ }
+ nsCOMPtr<nsIMsgDBHdr> msg;
+ msg = do_QueryElementAt(messages, 0, &rv);
+ if (NS_SUCCEEDED(rv))
+ CopyStreamMessage(msg, this, msgWindow, isMove);
+ return rv; //we are clearing copy state in CopyMessages on failure
+}
+
+nsresult nsImapMailFolder::GetClearedOriginalOp(nsIMsgOfflineImapOperation *op, nsIMsgOfflineImapOperation **originalOp, nsIMsgDatabase **originalDB)
+{
+ nsCOMPtr<nsIMsgOfflineImapOperation> returnOp;
+ nsOfflineImapOperationType opType;
+ op->GetOperation(&opType);
+ NS_ASSERTION(opType & nsIMsgOfflineImapOperation::kMoveResult, "not an offline move op");
+
+ nsCString sourceFolderURI;
+ op->GetSourceFolderURI(getter_Copies(sourceFolderURI));
+
+ nsCOMPtr<nsIRDFResource> res;
+ nsresult rv;
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = rdf->GetResource(sourceFolderURI, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> sourceFolder(do_QueryInterface(res, &rv));
+ if (NS_SUCCEEDED(rv) && sourceFolder)
+ {
+ if (sourceFolder)
+ {
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB);
+ if (*originalDB)
+ {
+ nsMsgKey originalKey;
+ op->GetMessageKey(&originalKey);
+ rv = (*originalDB)->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp));
+ if (NS_SUCCEEDED(rv) && returnOp)
+ {
+ nsCString moveDestination;
+ nsCString thisFolderURI;
+ GetURI(thisFolderURI);
+ returnOp->GetDestinationFolderURI(getter_Copies(moveDestination));
+ if (moveDestination.Equals(thisFolderURI))
+ returnOp->ClearOperation(nsIMsgOfflineImapOperation::kMoveResult);
+ }
+ }
+ }
+ }
+ }
+ returnOp.swap(*originalOp);
+ return rv;
+}
+
+nsresult nsImapMailFolder::GetOriginalOp(nsIMsgOfflineImapOperation *op, nsIMsgOfflineImapOperation **originalOp, nsIMsgDatabase **originalDB)
+{
+ nsCOMPtr<nsIMsgOfflineImapOperation> returnOp;
+ nsCString sourceFolderURI;
+ op->GetSourceFolderURI(getter_Copies(sourceFolderURI));
+
+ nsCOMPtr<nsIRDFResource> res;
+ nsresult rv;
+
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = rdf->GetResource(sourceFolderURI, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> sourceFolder(do_QueryInterface(res, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB);
+ if (*originalDB)
+ {
+ nsMsgKey originalKey;
+ op->GetMessageKey(&originalKey);
+ rv = (*originalDB)->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp));
+ }
+ }
+ returnOp.swap(*originalOp);
+ return rv;
+}
+
+nsresult nsImapMailFolder::CopyOfflineMsgBody(nsIMsgFolder *srcFolder,
+ nsIMsgDBHdr *destHdr,
+ nsIMsgDBHdr *origHdr,
+ nsIInputStream *inputStream,
+ nsIOutputStream *outputStream)
+{
+ nsresult rv;
+ nsCOMPtr <nsISeekableStream> seekable (do_QueryInterface(outputStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint64_t messageOffset;
+ uint32_t messageSize;
+ origHdr->GetMessageOffset(&messageOffset);
+ if (!messageOffset)
+ {
+ // Some offline stores may contain a bug where the storeToken is set but
+ // the messageOffset is zero. Detect cases like this, and use storeToken
+ // to set the missing messageOffset. Note this assumes mbox.
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void) GetMsgStore(getter_AddRefs(offlineStore));
+ if (offlineStore)
+ {
+ nsAutoCString type;
+ offlineStore->GetStoreType(type);
+ if (type.EqualsLiteral("mbox"))
+ {
+ nsCString storeToken;
+ origHdr->GetStringProperty("storeToken", getter_Copies(storeToken));
+ if (!storeToken.IsEmpty())
+ messageOffset = ParseUint64Str(storeToken.get());
+ }
+ }
+ }
+ origHdr->GetOfflineMessageSize(&messageSize);
+ if (!messageSize)
+ {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(srcFolder);
+ if (localFolder) //can just use regular message size
+ origHdr->GetMessageSize(&messageSize);
+ }
+ int64_t tellPos;
+ seekable->Tell(&tellPos);
+ destHdr->SetMessageOffset(tellPos);
+ nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(inputStream);
+ NS_ASSERTION(seekStream, "non seekable stream - can't read from offline msg");
+ if (seekStream)
+ {
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, messageOffset);
+ if (NS_SUCCEEDED(rv))
+ {
+ // now, copy the dest folder offline store msg to the temp file
+ char *inputBuffer = (char *) PR_Malloc(FILE_IO_BUFFER_SIZE);
+ int32_t bytesLeft;
+ uint32_t bytesRead, bytesWritten;
+ bytesLeft = messageSize;
+ rv = (inputBuffer) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ while (bytesLeft > 0 && NS_SUCCEEDED(rv))
+ {
+ rv = inputStream->Read(inputBuffer, FILE_IO_BUFFER_SIZE, &bytesRead);
+ if (NS_SUCCEEDED(rv) && bytesRead > 0)
+ {
+ rv = outputStream->Write(inputBuffer, std::min((int32_t) bytesRead, bytesLeft), &bytesWritten);
+ NS_ASSERTION((int32_t) bytesWritten == std::min((int32_t) bytesRead, bytesLeft), "wrote out incorrect number of bytes");
+ }
+ else
+ break;
+ bytesLeft -= bytesRead;
+ }
+ PR_FREEIF(inputBuffer);
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ outputStream->Flush();
+ uint32_t resultFlags;
+ destHdr->OrFlags(nsMsgMessageFlags::Offline, &resultFlags);
+ destHdr->SetOfflineMessageSize(messageSize);
+ }
+ return rv;
+}
+
+nsresult nsImapMailFolder::FindOpenRange(nsMsgKey &fakeBase, uint32_t srcCount)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey newBase = fakeBase - 1;
+ uint32_t freeCount = 0;
+ while (freeCount != srcCount && newBase > 0)
+ {
+ bool containsKey;
+ if (NS_SUCCEEDED(mDatabase->ContainsKey(newBase, &containsKey))
+ && !containsKey)
+ freeCount++;
+ else
+ freeCount = 0;
+ newBase--;
+ }
+ if (!newBase)
+ return NS_ERROR_FAILURE;
+ fakeBase = newBase;
+ return NS_OK;
+}
+
+// this imap folder is the destination of an offline move/copy.
+// We are either offline, or doing a pseudo-offline delete (where we do an offline
+// delete, load the next message, then playback the offline delete).
+nsresult nsImapMailFolder::CopyMessagesOffline(nsIMsgFolder* srcFolder,
+ nsIArray* messages,
+ bool isMove,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener)
+{
+ NS_ENSURE_ARG(messages);
+ nsresult rv;
+ nsresult stopit = NS_OK;
+ nsCOMPtr <nsIMsgDatabase> sourceMailDB;
+ nsCOMPtr <nsIDBFolderInfo> srcDbFolderInfo;
+ srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(srcDbFolderInfo), getter_AddRefs(sourceMailDB));
+ bool deleteToTrash = false;
+ bool deleteImmediately = false;
+ uint32_t srcCount;
+ messages->GetLength(&srcCount);
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ nsCOMPtr<nsIMutableArray> msgHdrsCopied(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ nsCOMPtr<nsIMutableArray> destMsgHdrs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+
+ if (!msgHdrsCopied || !destMsgHdrs)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (NS_SUCCEEDED(rv) && imapServer)
+ {
+ nsMsgImapDeleteModel deleteModel;
+ imapServer->GetDeleteModel(&deleteModel);
+ deleteToTrash = (deleteModel == nsMsgImapDeleteModels::MoveToTrash);
+ deleteImmediately = (deleteModel == nsMsgImapDeleteModels::DeleteNoTrash);
+ }
+
+ // This array is used only when we are actually removing the messages from the
+ // source database.
+ nsTArray<nsMsgKey> keysToDelete((isMove && (deleteToTrash || deleteImmediately)) ? srcCount : 0);
+
+ if (sourceMailDB)
+ {
+ // save the future ops in the source DB, if this is not a imap->local copy/move
+ nsCOMPtr <nsITransactionManager> txnMgr;
+ if (msgWindow)
+ msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr)
+ txnMgr->BeginBatch(nullptr);
+ nsCOMPtr<nsIMsgDatabase> database;
+ GetMsgDatabase(getter_AddRefs(database));
+ if (database)
+ {
+ // get the highest key in the dest db, so we can make up our fake keys
+ nsMsgKey fakeBase = 1;
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ rv = database->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey highWaterMark = nsMsgKey_None;
+ folderInfo->GetHighWater(&highWaterMark);
+ fakeBase += highWaterMark;
+ nsMsgKey fakeTop = fakeBase + srcCount;
+ // Check that we have enough room for the fake headers. If fakeTop
+ // is <= highWaterMark, we've overflowed.
+ if (fakeTop <= highWaterMark || fakeTop == nsMsgKey_None)
+ {
+ rv = FindOpenRange(fakeBase, srcCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // N.B. We must not return out of the for loop - we need the matching
+ // end notifications to be sent.
+ // We don't need to acquire the semaphor since this is synchronous
+ // on the UI thread but we should check if the offline store is locked.
+ bool isLocked;
+ GetLocked(&isLocked);
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool reusable = false;
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsTArray<nsMsgKey> addedKeys;
+ nsTArray<nsMsgKey> srcKeyArray;
+ nsCOMArray<nsIMsgDBHdr> addedHdrs;
+ nsCOMArray<nsIMsgDBHdr> srcMsgs;
+ nsOfflineImapOperationType moveCopyOpType;
+ nsOfflineImapOperationType deleteOpType = nsIMsgOfflineImapOperation::kDeletedMsg;
+ if (!deleteToTrash)
+ deleteOpType = nsIMsgOfflineImapOperation::kMsgMarkedDeleted;
+ nsCString messageIds;
+ rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
+ // put fake message in destination db, delete source if move
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false, false);
+ for (uint32_t sourceKeyIndex = 0; NS_SUCCEEDED(stopit) && (sourceKeyIndex < srcCount); sourceKeyIndex++)
+ {
+ bool messageReturningHome = false;
+ nsCString originalSrcFolderURI;
+ srcFolder->GetURI(originalSrcFolderURI);
+ nsCOMPtr<nsIMsgDBHdr> message;
+ message = do_QueryElementAt(messages, sourceKeyIndex);
+ nsMsgKey originalKey;
+ if (message)
+ rv = message->GetMessageKey(&originalKey);
+ else
+ {
+ NS_ERROR("bad msg in src array");
+ continue;
+ }
+ nsMsgKey msgKey;
+ message->GetMessageKey(&msgKey);
+ nsCOMPtr <nsIMsgOfflineImapOperation> sourceOp;
+ rv = sourceMailDB->GetOfflineOpForKey(originalKey, true, getter_AddRefs(sourceOp));
+ if (NS_SUCCEEDED(rv) && sourceOp)
+ {
+ srcFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
+ nsCOMPtr <nsIMsgDatabase> originalDB;
+ nsOfflineImapOperationType opType;
+ sourceOp->GetOperation(&opType);
+ // if we already have an offline op for this key, then we need to see if it was
+ // moved into the source folder while offline
+ if (opType == nsIMsgOfflineImapOperation::kMoveResult) // offline move
+ {
+ // gracious me, we are moving something we already moved while offline!
+ // find the original operation and clear it!
+ nsCOMPtr <nsIMsgOfflineImapOperation> originalOp;
+ rv = GetClearedOriginalOp(sourceOp, getter_AddRefs(originalOp), getter_AddRefs(originalDB));
+ if (originalOp)
+ {
+ nsCString srcFolderURI;
+ srcFolder->GetURI(srcFolderURI);
+ sourceOp->GetSourceFolderURI(getter_Copies(originalSrcFolderURI));
+ sourceOp->GetMessageKey(&originalKey);
+ if (isMove)
+ sourceMailDB->RemoveOfflineOp(sourceOp);
+ sourceOp = originalOp;
+ if (originalSrcFolderURI.Equals(srcFolderURI))
+ {
+ messageReturningHome = true;
+ originalDB->RemoveOfflineOp(originalOp);
+ }
+ }
+ }
+ if (!messageReturningHome)
+ {
+ nsCString folderURI;
+ GetURI(folderURI);
+ if (isMove)
+ {
+ uint32_t msgSize;
+ uint32_t msgFlags;
+ imapMessageFlagsType newImapFlags = 0;
+ message->GetMessageSize(&msgSize);
+ message->GetFlags(&msgFlags);
+ sourceOp->SetDestinationFolderURI(folderURI.get()); // offline move
+ sourceOp->SetOperation(nsIMsgOfflineImapOperation::kMsgMoved);
+ sourceOp->SetMsgSize(msgSize);
+ newImapFlags = msgFlags & 0x7;
+ if (msgFlags & nsMsgMessageFlags::Forwarded)
+ newImapFlags |= kImapMsgForwardedFlag;
+ sourceOp->SetNewFlags(newImapFlags);
+ }
+ else
+ sourceOp->AddMessageCopyOperation(folderURI.get()); // offline copy
+
+ sourceOp->GetOperation(&moveCopyOpType);
+ srcMsgs.AppendObject(message);
+ }
+ bool hasMsgOffline = false;
+ srcFolder->HasMsgOffline(originalKey, &hasMsgOffline);
+ }
+ else
+ stopit = NS_ERROR_FAILURE;
+
+ nsCOMPtr <nsIMsgDBHdr> mailHdr;
+ rv = sourceMailDB->GetMsgHdrForKey(originalKey, getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr)
+ {
+ bool successfulCopy = false;
+ nsMsgKey srcDBhighWaterMark;
+ srcDbFolderInfo->GetHighWater(&srcDBhighWaterMark);
+
+ nsCOMPtr <nsIMsgDBHdr> newMailHdr;
+ rv = database->CopyHdrFromExistingHdr(fakeBase + sourceKeyIndex, mailHdr,
+ true, getter_AddRefs(newMailHdr));
+ if (!newMailHdr || NS_FAILED(rv))
+ {
+ NS_ASSERTION(false, "failed to copy hdr");
+ stopit = rv;
+ }
+
+ if (NS_SUCCEEDED(stopit))
+ {
+ bool hasMsgOffline = false;
+
+ destMsgHdrs->AppendElement(newMailHdr, false);
+ srcFolder->HasMsgOffline(originalKey, &hasMsgOffline);
+ newMailHdr->SetUint32Property("pseudoHdr", 1);
+ if (!reusable)
+ (void)srcFolder->GetMsgInputStream(newMailHdr, &reusable,
+ getter_AddRefs(inputStream));
+
+ if (inputStream && hasMsgOffline && !isLocked)
+ {
+ rv = GetOfflineStoreOutputStream(newMailHdr,
+ getter_AddRefs(outputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyOfflineMsgBody(srcFolder, newMailHdr, mailHdr, inputStream,
+ outputStream);
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void) GetMsgStore(getter_AddRefs(offlineStore));
+ if (offlineStore)
+ offlineStore->FinishNewMessage(outputStream, newMailHdr);
+ }
+ else
+ database->MarkOffline(fakeBase + sourceKeyIndex, false, nullptr);
+
+ nsCOMPtr <nsIMsgOfflineImapOperation> destOp;
+ database->GetOfflineOpForKey(fakeBase + sourceKeyIndex, true, getter_AddRefs(destOp));
+ if (destOp)
+ {
+ // check if this is a move back to the original mailbox, in which case
+ // we just delete the offline operation.
+ if (messageReturningHome)
+ database->RemoveOfflineOp(destOp);
+ else
+ {
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ destOp->SetSourceFolderURI(originalSrcFolderURI.get());
+ destOp->SetSrcMessageKey(originalKey);
+ addedKeys.AppendElement(fakeBase + sourceKeyIndex);
+ addedHdrs.AppendObject(newMailHdr);
+ }
+ }
+ else
+ stopit = NS_ERROR_FAILURE;
+ }
+ successfulCopy = NS_SUCCEEDED(stopit);
+ nsMsgKey msgKey;
+ mailHdr->GetMessageKey(&msgKey);
+ if (isMove && successfulCopy)
+ {
+ if (deleteToTrash || deleteImmediately)
+ keysToDelete.AppendElement(msgKey);
+ else
+ sourceMailDB->MarkImapDeleted(msgKey, true, nullptr); // offline delete
+ }
+ if (successfulCopy)
+ // This is for both moves and copies
+ msgHdrsCopied->AppendElement(mailHdr, false);
+ }
+ }
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true, false);
+ RefPtr<nsImapOfflineTxn> addHdrMsgTxn = new
+ nsImapOfflineTxn(this, &addedKeys, nullptr, this, isMove, nsIMsgOfflineImapOperation::kAddedHeader,
+ addedHdrs);
+ if (addHdrMsgTxn && txnMgr)
+ txnMgr->DoTransaction(addHdrMsgTxn);
+ RefPtr<nsImapOfflineTxn> undoMsgTxn = new
+ nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this,
+ isMove, moveCopyOpType, srcMsgs);
+ if (undoMsgTxn)
+ {
+ if (isMove)
+ {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ nsCOMPtr<nsIMsgImapMailFolder> srcIsImap(do_QueryInterface(srcFolder));
+ // remember this undo transaction so we can hook up the result
+ // msg ids in the undo transaction.
+ if (srcIsImap)
+ {
+ nsImapMailFolder *srcImapFolder = static_cast<nsImapMailFolder*>(srcFolder);
+ srcImapFolder->m_pendingOfflineMoves.AppendElement(undoMsgTxn);
+ }
+ }
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ // we're adding this undo action before the delete is successful. This is evil,
+ // but 4.5 did it as well.
+ if (txnMgr)
+ txnMgr->DoTransaction(undoMsgTxn);
+ }
+ undoMsgTxn = new
+ nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this, isMove,
+ deleteOpType, srcMsgs);
+ if (undoMsgTxn)
+ {
+ if (isMove)
+ {
+ if (mFlags & nsMsgFolderFlags::Trash)
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ }
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ if (txnMgr)
+ txnMgr->DoTransaction(undoMsgTxn);
+ }
+ if (outputStream)
+ outputStream->Close();
+
+ if (isMove)
+ sourceMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ database->Commit(nsMsgDBCommitType::kLargeCommit);
+ SummaryChanged();
+ srcFolder->SummaryChanged();
+ }
+ if (txnMgr)
+ txnMgr->EndBatch(false);
+ }
+
+ // Do this before delete, as it destroys the messages
+ uint32_t numHdrs;
+ msgHdrsCopied->GetLength(&numHdrs);
+ if (numHdrs)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsMoveCopyCompleted(isMove, msgHdrsCopied, this, destMsgHdrs);
+ }
+
+ if (isMove && NS_SUCCEEDED(rv) && (deleteToTrash || deleteImmediately))
+ {
+ DeleteStoreMessages(keysToDelete, srcFolder);
+ srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false, false);
+ sourceMailDB->DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(),
+ nullptr);
+ srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true, false);
+ }
+
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ OnCopyCompleted(srcSupport, rv);
+
+ if (isMove)
+ srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ?
+ mDeleteOrMoveMsgCompletedAtom :
+ mDeleteOrMoveMsgFailedAtom);
+ return rv;
+}
+
+void nsImapMailFolder::SetPendingAttributes(nsIArray* messages, bool aIsMove)
+{
+
+ GetDatabase();
+ if (!mDatabase)
+ return;
+
+ uint32_t supportedUserFlags;
+ GetSupportedUserFlags(&supportedUserFlags);
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCString dontPreserve;
+
+ // These preferences exist so that extensions can control which properties
+ // are preserved in the database when a message is moved or copied. All
+ // properties are preserved except those listed in these preferences
+ if (aIsMove)
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnMove",
+ getter_Copies(dontPreserve));
+ else
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy",
+ getter_Copies(dontPreserve));
+
+ // We'll add spaces at beginning and end so we can search for space-name-space
+ nsCString dontPreserveEx(NS_LITERAL_CSTRING(" "));
+ dontPreserveEx.Append(dontPreserve);
+ dontPreserveEx.AppendLiteral(" ");
+
+ // these properties are set as integers below, so don't set them again
+ // in the iteration through the properties
+ dontPreserveEx.AppendLiteral("offlineMsgSize msgOffset flags priority pseudoHdr ");
+
+ // these fields are either copied separately when the server does not support
+ // custom IMAP flags, or managed directly through the flags
+ dontPreserveEx.AppendLiteral("keywords label ");
+
+ uint32_t i, count;
+
+ rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // check if any msg hdr has special flags or properties set
+ // that we need to set on the dest hdr
+ for (i = 0; i < count; i++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr = do_QueryElementAt(messages, i, &rv);
+ if (mDatabase && msgDBHdr)
+ {
+ if (!(supportedUserFlags & kImapMsgSupportUserFlag))
+ {
+ nsMsgLabelValue label;
+ msgDBHdr->GetLabel(&label);
+ if (label != 0)
+ {
+ nsAutoCString labelStr;
+ labelStr.AppendInt(label);
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "label", labelStr.get());
+ }
+ nsCString keywords;
+ msgDBHdr->GetStringProperty("keywords", getter_Copies(keywords));
+ if (!keywords.IsEmpty())
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "keywords", keywords.get());
+ }
+
+ // do this even if the server supports user-defined flags.
+ nsCOMPtr<nsIUTF8StringEnumerator> propertyEnumerator;
+ nsresult rv = msgDBHdr->GetPropertyEnumerator(getter_AddRefs(propertyEnumerator));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsAutoCString property;
+ nsCString sourceString;
+ bool hasMore;
+ while (NS_SUCCEEDED(propertyEnumerator->HasMore(&hasMore)) && hasMore)
+ {
+ propertyEnumerator->GetNext(property);
+ nsAutoCString propertyEx(NS_LITERAL_CSTRING(" "));
+ propertyEx.Append(property);
+ propertyEx.AppendLiteral(" ");
+ if (dontPreserveEx.Find(propertyEx) != kNotFound)
+ continue;
+
+ nsCString sourceString;
+ msgDBHdr->GetStringProperty(property.get(), getter_Copies(sourceString));
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, property.get(), sourceString.get());
+ }
+
+ uint32_t messageSize;
+ uint64_t messageOffset;
+ nsCString storeToken;
+ msgDBHdr->GetMessageOffset(&messageOffset);
+ msgDBHdr->GetOfflineMessageSize(&messageSize);
+ msgDBHdr->GetStringProperty("storeToken", getter_Copies(storeToken));
+ if (messageSize)
+ {
+ mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "offlineMsgSize",
+ messageSize);
+ mDatabase->SetUint64AttributeOnPendingHdr(msgDBHdr, "msgOffset",
+ messageOffset);
+ mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "flags",
+ nsMsgMessageFlags::Offline);
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "storeToken",
+ storeToken.get());
+ }
+ nsMsgPriorityValue priority;
+ msgDBHdr->GetPriority(&priority);
+ if(priority != 0)
+ {
+ nsAutoCString priorityStr;
+ priorityStr.AppendInt(priority);
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "priority", priorityStr.get());
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CopyMessages(nsIMsgFolder* srcFolder,
+ nsIArray* messages,
+ bool isMove,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener,
+ bool isFolder, //isFolder for future use when we do cross-server folder move/copy
+ bool allowUndo)
+{
+ UpdateTimestamps(allowUndo);
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgIncomingServer> srcServer;
+ nsCOMPtr <nsIMsgIncomingServer> dstServer;
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ bool sameServer = false;
+
+ rv = srcFolder->GetServer(getter_AddRefs(srcServer));
+ if(NS_FAILED(rv)) goto done;
+
+ rv = GetServer(getter_AddRefs(dstServer));
+ if(NS_FAILED(rv)) goto done;
+
+ NS_ENSURE_TRUE(dstServer, NS_ERROR_NULL_POINTER);
+
+ rv = dstServer->Equals(srcServer, &sameServer);
+ if (NS_FAILED(rv)) goto done;
+
+ // in theory, if allowUndo is true, then this is a user initiated
+ // action, and we should do it pseudo-offline. If it's not
+ // user initiated (e.g., mail filters firing), then allowUndo is
+ // false, and we should just do the action.
+ if (!WeAreOffline() && sameServer && allowUndo)
+ {
+ // complete the copy operation as in offline mode
+ rv = CopyMessagesOffline(srcFolder, messages, isMove, msgWindow, listener);
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "error offline copy");
+ // We'll warn if this fails, but we should still try to play back
+ // offline ops, because it's possible the copy got far enough to
+ // create the offline ops.
+
+ // We make sure that the source folder is an imap folder by limiting pseudo-offline
+ // operations to the same imap server. If we extend the code to cover non imap folders
+ // in the future (i.e. imap folder->local folder), then the following downcast
+ // will cause either a crash or compiler error. Do not forget to change it accordingly.
+ nsImapMailFolder *srcImapFolder = static_cast<nsImapMailFolder*>(srcFolder);
+
+ // lazily create playback timer if it is not already
+ // created
+ if (!srcImapFolder->m_playbackTimer)
+ {
+ rv = srcImapFolder->CreatePlaybackTimer();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (srcImapFolder->m_playbackTimer)
+ {
+ // if there is no pending request, create a new one, and set the timer. Otherwise
+ // use the existing one to reset the timer.
+ // it is callback function's responsibility to delete the new request object
+ if (!srcImapFolder->m_pendingPlaybackReq)
+ {
+ srcImapFolder->m_pendingPlaybackReq = new nsPlaybackRequest(srcImapFolder, msgWindow);
+ if (!srcImapFolder->m_pendingPlaybackReq)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ srcImapFolder->m_playbackTimer->InitWithFuncCallback(PlaybackTimerCallback, (void *) srcImapFolder->m_pendingPlaybackReq,
+ PLAYBACK_TIMER_INTERVAL_IN_MS, nsITimer::TYPE_ONE_SHOT);
+ }
+ return rv;
+ }
+ else
+ {
+ // sort the message array by key
+
+ uint32_t numMsgs = 0;
+ messages->GetLength(&numMsgs);
+ nsTArray<nsMsgKey> keyArray(numMsgs);
+ for (uint32_t i = 0; i < numMsgs; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> aMessage = do_QueryElementAt(messages, i, &rv);
+ if (NS_SUCCEEDED(rv) && aMessage)
+ {
+ nsMsgKey key;
+ aMessage->GetMessageKey(&key);
+ keyArray.AppendElement(key);
+ }
+ }
+ keyArray.Sort();
+
+ nsCOMPtr<nsIMutableArray> sortedMsgs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (WeAreOffline())
+ return CopyMessagesOffline(srcFolder, sortedMsgs, isMove, msgWindow, listener);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ SetPendingAttributes(sortedMsgs, isMove);
+
+ // if the folders aren't on the same server, do a stream base copy
+ if (!sameServer)
+ {
+ rv = CopyMessagesWithStream(srcFolder, sortedMsgs, isMove, true, msgWindow, listener, allowUndo);
+ goto done;
+ }
+
+ nsAutoCString messageIds;
+ rv = AllocateUidStringFromKeys(keyArray.Elements(), numMsgs, messageIds);
+ if(NS_FAILED(rv)) goto done;
+
+ nsCOMPtr<nsIUrlListener> urlListener;
+ rv = QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
+ rv = InitCopyState(srcSupport, sortedMsgs, isMove, true, false,
+ 0, EmptyCString(), listener, msgWindow, allowUndo);
+ if (NS_FAILED(rv)) goto done;
+
+ m_copyState->m_curIndex = m_copyState->m_totalCount;
+
+ if (isMove)
+ srcFolder->EnableNotifications(allMessageCountNotifications, false, true/* dbBatching*/); //disable message count notification
+
+ nsCOMPtr<nsISupports> copySupport = do_QueryInterface(m_copyState);
+ rv = imapService->OnlineMessageCopy(srcFolder, messageIds,
+ this, true, isMove,
+ urlListener, nullptr,
+ copySupport, msgWindow);
+ if (NS_SUCCEEDED(rv) && m_copyState->m_allowUndo)
+ {
+ RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
+ if (!undoMsgTxn || NS_FAILED(undoMsgTxn->Init(srcFolder, &keyArray,
+ messageIds.get(), this,
+ true, isMove)))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (isMove)
+ {
+ if (mFlags & nsMsgFolderFlags::Trash)
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ }
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ m_copyState->m_undoMsgTxn = undoMsgTxn;
+ }
+
+ }//endif
+
+done:
+ if (NS_FAILED(rv))
+ {
+ (void) OnCopyCompleted(srcSupport, rv);
+ if (isMove)
+ {
+ srcFolder->EnableNotifications(allMessageCountNotifications, true, true/* dbBatching*/); //enable message count notification
+ NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ }
+ }
+ return rv;
+}
+
+class nsImapFolderCopyState final : public nsIUrlListener, public nsIMsgCopyServiceListener
+{
+public:
+ nsImapFolderCopyState(nsIMsgFolder *destParent, nsIMsgFolder *srcFolder,
+ bool isMoveFolder, nsIMsgWindow *msgWindow, nsIMsgCopyServiceListener *listener);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ nsresult StartNextCopy();
+ nsresult AdvanceToNextFolder(nsresult aStatus);
+protected:
+ ~nsImapFolderCopyState();
+ RefPtr<nsImapMailFolder> m_newDestFolder;
+ nsCOMPtr<nsISupports> m_origSrcFolder;
+ nsCOMPtr<nsIMsgFolder> m_curDestParent;
+ nsCOMPtr<nsIMsgFolder> m_curSrcFolder;
+ bool m_isMoveFolder;
+ nsCOMPtr<nsIMsgCopyServiceListener> m_copySrvcListener;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ int32_t m_childIndex;
+ nsCOMArray<nsIMsgFolder> m_srcChildFolders;
+ nsCOMArray<nsIMsgFolder> m_destParents;
+
+};
+
+NS_IMPL_ISUPPORTS(nsImapFolderCopyState, nsIUrlListener, nsIMsgCopyServiceListener)
+
+nsImapFolderCopyState::nsImapFolderCopyState(nsIMsgFolder *destParent, nsIMsgFolder *srcFolder,
+ bool isMoveFolder, nsIMsgWindow *msgWindow, nsIMsgCopyServiceListener *listener)
+{
+ m_origSrcFolder = do_QueryInterface(srcFolder);
+ m_curDestParent = destParent;
+ m_curSrcFolder = srcFolder;
+ m_isMoveFolder = isMoveFolder;
+ m_msgWindow = msgWindow;
+ m_copySrvcListener = listener;
+ m_childIndex = -1;
+}
+
+nsImapFolderCopyState::~nsImapFolderCopyState()
+{
+}
+
+nsresult
+nsImapFolderCopyState::StartNextCopy()
+{
+ nsresult rv;
+ // first make sure dest folder exists.
+ nsCOMPtr <nsIImapService> imapService = do_GetService (NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString folderName;
+ m_curSrcFolder->GetName(folderName);
+
+ return imapService->EnsureFolderExists(m_curDestParent,
+ folderName,
+ this, nullptr);
+}
+
+nsresult nsImapFolderCopyState::AdvanceToNextFolder(nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+ m_childIndex++;
+ if (m_childIndex >= m_srcChildFolders.Count())
+ {
+ if (m_newDestFolder)
+ m_newDestFolder->OnCopyCompleted(m_origSrcFolder, aStatus);
+ Release();
+ }
+ else
+ {
+ m_curDestParent = m_destParents[m_childIndex];
+ m_curSrcFolder = m_srcChildFolders[m_childIndex];
+ rv = StartNextCopy();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapFolderCopyState::OnStartRunningUrl(nsIURI *aUrl)
+{
+ NS_PRECONDITION(aUrl, "sanity check - need to be be running non-null url");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapFolderCopyState::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ if (NS_FAILED(aExitCode))
+ {
+ if (m_copySrvcListener)
+ m_copySrvcListener->OnStopCopy(aExitCode);
+ Release();
+ return aExitCode; // or NS_OK???
+ }
+ nsresult rv = NS_OK;
+ if (aUrl)
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl);
+ if (imapUrl)
+ {
+ nsImapAction imapAction = nsIImapUrl::nsImapTest;
+ imapUrl->GetImapAction(&imapAction);
+
+ switch(imapAction)
+ {
+ case nsIImapUrl::nsImapEnsureExistsFolder:
+ {
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ nsString folderName;
+ nsCString utf7LeafName;
+ m_curSrcFolder->GetName(folderName);
+ rv = CopyUTF16toMUTF7(folderName, utf7LeafName);
+ rv = m_curDestParent->FindSubFolder(utf7LeafName, getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ // save the first new folder so we can send a notification to the
+ // copy service when this whole process is done.
+ if (!m_newDestFolder)
+ m_newDestFolder = static_cast<nsImapMailFolder*>(newMsgFolder.get());
+
+ // check if the source folder has children. If it does, list them
+ // into m_srcChildFolders, and set m_destParents for the
+ // corresponding indexes to the newly created folder.
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = m_curSrcFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> item;
+ bool hasMore = false;
+ uint32_t childIndex = 0;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ m_srcChildFolders.InsertElementAt(m_childIndex + childIndex + 1, folder);
+ m_destParents.InsertElementAt(m_childIndex + childIndex + 1, newMsgFolder);
+ }
+ ++childIndex;
+ }
+
+ rv = m_curSrcFolder->GetMessages(getter_AddRefs(enumerator));
+ nsCOMPtr<nsIMutableArray> msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(msgArray, rv);
+ hasMore = false;
+
+ if (enumerator)
+ rv = enumerator->HasMoreElements(&hasMore);
+
+ if (!hasMore)
+ return AdvanceToNextFolder(NS_OK);
+
+ while (NS_SUCCEEDED(rv) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgArray->AppendElement(item, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = enumerator->HasMoreElements(&hasMore);
+ }
+
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyMessages(m_curSrcFolder,
+ msgArray, newMsgFolder,
+ m_isMoveFolder,
+ this,
+ m_msgWindow,
+ false /* allowUndo */);
+ }
+ break;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapFolderCopyState::OnStartCopy()
+{
+ return NS_OK;
+}
+
+/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
+NS_IMETHODIMP nsImapFolderCopyState::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void SetMessageKey (in nsMsgKey aKey); */
+NS_IMETHODIMP nsImapFolderCopyState::SetMessageKey(nsMsgKey aKey)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void GetMessageId (in nsCString aMessageId); */
+NS_IMETHODIMP nsImapFolderCopyState::GetMessageId(nsACString& messageId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnStopCopy (in nsresult aStatus); */
+NS_IMETHODIMP nsImapFolderCopyState::OnStopCopy(nsresult aStatus)
+{
+ if (NS_SUCCEEDED(aStatus))
+ return AdvanceToNextFolder(aStatus);
+ if (m_copySrvcListener)
+ {
+ (void) m_copySrvcListener->OnStopCopy(aStatus);
+ m_copySrvcListener = nullptr;
+ }
+ Release();
+
+ return NS_OK;
+}
+
+// "this" is the parent of the copied folder.
+NS_IMETHODIMP
+nsImapMailFolder::CopyFolder(nsIMsgFolder* srcFolder,
+ bool isMoveFolder,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener)
+{
+ NS_ENSURE_ARG_POINTER(srcFolder);
+
+ nsresult rv = NS_OK;
+
+ if (isMoveFolder) //move folder permitted when dstFolder and the srcFolder are on same server
+ {
+ uint32_t folderFlags = 0;
+ if (srcFolder)
+ srcFolder->GetFlags(&folderFlags);
+
+ // if our source folder is a virtual folder
+ if (folderFlags & nsMsgFolderFlags::Virtual)
+ {
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ nsString folderName;
+ srcFolder->GetName(folderName);
+
+ nsAutoString safeFolderName(folderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+
+ srcFolder->ForceDBClosed();
+
+ nsCOMPtr<nsIFile> oldPathFile;
+ rv = srcFolder->GetFilePath(getter_AddRefs(oldPathFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIFile> summaryFile;
+ GetSummaryFileLocation(oldPathFile, getter_AddRefs(summaryFile));
+
+ nsCOMPtr<nsIFile> newPathFile;
+ rv = GetFilePath(getter_AddRefs(newPathFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isDirectory = false;
+ newPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory)
+ {
+ AddDirectorySeparator(newPathFile);
+ rv = newPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = CheckIfFolderExists(folderName, this, msgWindow);
+ if(NS_FAILED(rv))
+ return rv;
+
+ rv = summaryFile->CopyTo(newPathFile, EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newMsgFolder->SetPrettyName(folderName);
+
+ uint32_t flags;
+ srcFolder->GetFlags(&flags);
+ newMsgFolder->SetFlags(flags);
+
+ NotifyItemAdded(newMsgFolder);
+
+ // now remove the old folder
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ srcFolder->GetParent(getter_AddRefs(msgParent));
+ srcFolder->SetParent(nullptr);
+ if (msgParent)
+ {
+ msgParent->PropagateDelete(srcFolder, false, msgWindow); // The files have already been moved, so delete storage false
+ oldPathFile->Remove(false); //berkeley mailbox
+ nsCOMPtr <nsIMsgDatabase> srcDB; // we need to force closed the source db
+ srcFolder->Delete();
+
+ nsCOMPtr<nsIFile> parentPathFile;
+ rv = msgParent->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ AddDirectorySeparator(parentPathFile);
+ nsCOMPtr <nsISimpleEnumerator> children;
+ parentPathFile->GetDirectoryEntries(getter_AddRefs(children));
+ bool more;
+ // checks if the directory is empty or not
+ if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more)
+ parentPathFile->Remove(true);
+ }
+ }
+ else // non-virtual folder
+ {
+ nsCOMPtr <nsIImapService> imapService = do_GetService (NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ bool match = false;
+ bool confirmed = false;
+ if (mFlags & nsMsgFolderFlags::Trash)
+ {
+ rv = srcFolder->MatchOrChangeFilterDestination(nullptr, false, &match);
+ if (match)
+ {
+ srcFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirmed);
+ // should we return an error to copy service?
+ // or send a notification?
+ if (!confirmed)
+ return NS_OK;
+ }
+ }
+ rv = InitCopyState(srcSupport, nullptr, false, false, false,
+ 0, EmptyCString(), listener, msgWindow, false);
+ if (NS_FAILED(rv))
+ return OnCopyCompleted(srcSupport, rv);
+
+ rv = imapService->MoveFolder(srcFolder,
+ this,
+ this,
+ msgWindow,
+ nullptr);
+ }
+ }
+ else // copying folder (should only be across server?)
+ {
+ nsImapFolderCopyState *folderCopier = new nsImapFolderCopyState(this, srcFolder, isMoveFolder, msgWindow, listener);
+ NS_ADDREF(folderCopier); // it owns itself.
+ return folderCopier->StartNextCopy();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CopyFileMessage(nsIFile* file,
+ nsIMsgDBHdr* msgToReplace,
+ bool isDraftOrTemplate,
+ uint32_t aNewMsgFlags,
+ const nsACString &aNewMsgKeywords,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ nsMsgKey key = nsMsgKey_None;
+ nsAutoCString messageId;
+ nsCOMPtr<nsIUrlListener> urlListener;
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(file, &rv);
+
+ if (!messages)
+ return OnCopyCompleted(srcSupport, rv);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return OnCopyCompleted(srcSupport, rv);
+
+ rv = QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
+
+ if (msgToReplace)
+ {
+ rv = msgToReplace->GetMessageKey(&key);
+ if (NS_SUCCEEDED(rv))
+ {
+ messageId.AppendInt((int32_t) key);
+ // Perhaps we have the message offline, but even if we do it is
+ // not valid, since the only time we do a file copy for an
+ // existing message is when we are changing the message.
+ // So set the offline size to 0 to force SetPendingAttributes to
+ // clear the offline message flag.
+ msgToReplace->SetOfflineMessageSize(0);
+ messages->AppendElement(msgToReplace, false);
+ SetPendingAttributes(messages, false);
+ }
+ }
+
+ bool isMove = (msgToReplace ? true : false);
+ rv = InitCopyState(srcSupport, messages, isMove, isDraftOrTemplate,
+ false, aNewMsgFlags, aNewMsgKeywords, listener,
+ msgWindow, false);
+ if (NS_FAILED(rv))
+ return OnCopyCompleted(srcSupport, rv);
+
+ m_copyState->m_streamCopy = true;
+ nsCOMPtr<nsISupports> copySupport;
+ if( m_copyState )
+ copySupport = do_QueryInterface(m_copyState);
+ if (!isDraftOrTemplate)
+ {
+ m_copyState->m_totalCount = 1;
+ // This makes the IMAP APPEND set the INTERNALDATE for the msg copy
+ // we make when detaching/deleting attachments to the original msg date.
+ m_copyState->m_message = msgToReplace;
+ }
+ rv = imapService->AppendMessageFromFile(file, this, messageId,
+ true, isDraftOrTemplate,
+ urlListener, nullptr,
+ copySupport,
+ msgWindow);
+ if (NS_FAILED(rv))
+ return OnCopyCompleted(srcSupport, rv);
+
+ return rv;
+}
+
+nsresult
+nsImapMailFolder::CopyStreamMessage(nsIMsgDBHdr* message,
+ nsIMsgFolder* dstFolder, // should be this
+ nsIMsgWindow *aMsgWindow,
+ bool isMove)
+{
+ if (!m_copyState)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyStreamMessage failed with null m_copyState"));
+ NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER);
+ nsresult rv;
+ nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsICopyMessageListener> copyListener(do_QueryInterface(dstFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(m_copyState->m_srcSupport, &rv));
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyStreaMessage failed with null m_copyState->m_srcSupport"));
+ if (NS_FAILED(rv)) return rv;
+ rv = copyStreamListener->Init(srcFolder, copyListener, nullptr);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyStreaMessage failed in copyStreamListener->Init"));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryInterface(message, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString uri;
+ srcFolder->GetUriForMsg(msgHdr, uri);
+
+ if (!m_copyState->m_msgService)
+ rv = GetMessageServiceFromURI(uri, getter_AddRefs(m_copyState->m_msgService));
+
+ if (NS_SUCCEEDED(rv) && m_copyState->m_msgService)
+ {
+ nsCOMPtr<nsIStreamListener> streamListener(do_QueryInterface(copyStreamListener, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // put up status message here, if copying more than one message.
+ if (m_copyState->m_totalCount > 1)
+ {
+ nsString dstFolderName, progressText;
+ GetName(dstFolderName);
+ nsAutoString curMsgString;
+ nsAutoString totalMsgString;
+ totalMsgString.AppendInt(m_copyState->m_totalCount);
+ curMsgString.AppendInt(m_copyState->m_curIndex + 1);
+
+ const char16_t *formatStrings[3] = {curMsgString.get(),
+ totalMsgString.get(),
+ dstFolderName.get()
+ };
+
+ nsCOMPtr <nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->FormatStringFromName(
+ u"imapCopyingMessageOf2",
+ formatStrings, 3, getter_Copies(progressText));
+ nsCOMPtr <nsIMsgStatusFeedback> statusFeedback;
+ if (m_copyState->m_msgWindow)
+ m_copyState->m_msgWindow->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ if (statusFeedback)
+ {
+ statusFeedback->ShowStatusString(progressText);
+ int32_t percent;
+ percent = (100 * m_copyState->m_curIndex) / (int32_t) m_copyState->m_totalCount;
+ statusFeedback->ShowProgress(percent);
+ }
+ }
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = m_copyState->m_msgService->CopyMessage(uri.get(), streamListener,
+ isMove && !m_copyState->m_isCrossServerOp, nullptr, aMsgWindow,
+ getter_AddRefs(dummyNull));
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyMessage failed: uri %s\n", uri.get()));
+ }
+ return rv;
+}
+
+nsImapMailCopyState::nsImapMailCopyState() :
+ m_isMove(false), m_selectedState(false),
+ m_isCrossServerOp(false), m_curIndex(0),
+ m_totalCount(0), m_streamCopy(false), m_dataBuffer(nullptr),
+ m_dataBufferSize(0), m_leftOver(0), m_allowUndo(false),
+ m_eatLF(false), m_newMsgFlags(0), m_appendUID(nsMsgKey_None)
+{
+}
+
+nsImapMailCopyState::~nsImapMailCopyState()
+{
+ PR_Free(m_dataBuffer);
+ if (m_msgService && m_message)
+ {
+ nsCOMPtr <nsIMsgFolder> srcFolder = do_QueryInterface(m_srcSupport);
+ if (srcFolder)
+ {
+ nsCString uri;
+ srcFolder->GetUriForMsg(m_message, uri);
+ }
+ }
+ if (m_tmpFile)
+ m_tmpFile->Remove(false);
+}
+
+
+NS_IMPL_ISUPPORTS(nsImapMailCopyState, nsImapMailCopyState)
+
+nsresult
+nsImapMailFolder::InitCopyState(nsISupports* srcSupport,
+ nsIArray* messages,
+ bool isMove,
+ bool selectedState,
+ bool acrossServers,
+ uint32_t newMsgFlags,
+ const nsACString &newMsgKeywords,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow *msgWindow,
+ bool allowUndo)
+{
+ NS_ENSURE_ARG_POINTER(srcSupport);
+ NS_ENSURE_TRUE(!m_copyState, NS_ERROR_FAILURE);
+ nsresult rv;
+
+ m_copyState = new nsImapMailCopyState();
+ NS_ENSURE_TRUE(m_copyState,NS_ERROR_OUT_OF_MEMORY);
+
+ m_copyState->m_isCrossServerOp = acrossServers;
+ m_copyState->m_srcSupport = do_QueryInterface(srcSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_copyState->m_messages = messages;
+ if (messages)
+ rv = messages->GetLength(&m_copyState->m_totalCount);
+ if (!m_copyState->m_isCrossServerOp)
+ {
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t numUnread = 0;
+ for (uint32_t keyIndex=0; keyIndex < m_copyState->m_totalCount; keyIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(m_copyState->m_messages, keyIndex, &rv);
+ // if the key is not there, then assume what the caller tells us to.
+ bool isRead = false;
+ uint32_t flags;
+ if (message )
+ {
+ message->GetFlags(&flags);
+ isRead = flags & nsMsgMessageFlags::Read;
+ }
+ if (!isRead)
+ numUnread++;
+ }
+ m_copyState->m_unreadCount = numUnread;
+ }
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgDBHdr> message =
+ do_QueryElementAt(m_copyState->m_messages,
+ m_copyState->m_curIndex, &rv);
+ // if the key is not there, then assume what the caller tells us to.
+ bool isRead = false;
+ uint32_t flags;
+ if (message )
+ {
+ message->GetFlags(&flags);
+ isRead = flags & nsMsgMessageFlags::Read;
+ }
+ m_copyState->m_unreadCount = (isRead) ? 0 : 1;
+ }
+
+ m_copyState->m_isMove = isMove;
+ m_copyState->m_newMsgFlags = newMsgFlags;
+ m_copyState->m_newMsgKeywords = newMsgKeywords;
+ m_copyState->m_allowUndo = allowUndo;
+ m_copyState->m_selectedState = selectedState;
+ m_copyState->m_msgWindow = msgWindow;
+ if (listener)
+ m_copyState->m_listener = do_QueryInterface(listener, &rv);
+ return rv;
+}
+
+nsresult
+nsImapMailFolder::CopyFileToOfflineStore(nsIFile *srcFile, nsMsgKey msgKey)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool storeOffline = (mFlags & nsMsgFolderFlags::Offline) && !WeAreOffline();
+
+ if (msgKey == nsMsgKey_None)
+ {
+ // To support send filters, we need to store the message in the database when
+ // it is copied to the FCC folder. In that case, we know the UID of the
+ // message and therefore have the correct msgKey. In other cases, where
+ // we don't need the offline message copied, don't add to db.
+ if (!storeOffline)
+ return NS_OK;
+
+ mDatabase->GetNextFakeOfflineMsgKey(&msgKey);
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> fakeHdr;
+ rv = mDatabase->CreateNewHdr(msgKey, getter_AddRefs(fakeHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ fakeHdr->SetUint32Property("pseudoHdr", 1);
+
+ // Should we add this to the offline store?
+ nsCOMPtr<nsIOutputStream> offlineStore;
+ if (storeOffline)
+ {
+ rv = GetOfflineStoreOutputStream(fakeHdr, getter_AddRefs(offlineStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We set an offline kMoveResult because in any case we want to update this
+ // msgHdr with one downloaded from the server, with possible additional
+ // headers added.
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ rv = mDatabase->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ nsCString destFolderUri;
+ GetURI(destFolderUri);
+ op->SetOperation(nsIMsgOfflineImapOperation::kMoveResult);
+ op->SetDestinationFolderURI(destFolderUri.get());
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIMsgParseMailMsgState> msgParser =
+ do_CreateInstance(NS_PARSEMAILMSGSTATE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgParser->SetMailDB(mDatabase);
+
+ uint64_t offset = 0;
+ if (offlineStore)
+ {
+ // Tell the parser to use the offset that will be in the dest stream,
+ // not the temp file.
+ fakeHdr->GetMessageOffset(&offset);
+ }
+ msgParser->SetEnvelopePos(offset);
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), srcFile);
+ if (NS_SUCCEEDED(rv) && inputStream)
+ {
+ // Now, parse the temp file to (optionally) copy to
+ // the offline store for the cur folder.
+ RefPtr<nsMsgLineStreamBuffer> inputStreamBuffer =
+ new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE, true, false);
+ int64_t fileSize;
+ srcFile->GetFileSize(&fileSize);
+ uint32_t bytesWritten;
+ rv = NS_OK;
+ msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
+ msgParser->SetNewMsgHdr(fakeHdr);
+ bool needMoreData = false;
+ char * newLine = nullptr;
+ uint32_t numBytesInLine = 0;
+ if (offlineStore)
+ {
+ const char *envelope = "From " CRLF;
+ offlineStore->Write(envelope, strlen(envelope), &bytesWritten);
+ fileSize += bytesWritten;
+ }
+ do
+ {
+ newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine, needMoreData);
+ if (newLine)
+ {
+ msgParser->ParseAFolderLine(newLine, numBytesInLine);
+ if (offlineStore)
+ rv = offlineStore->Write(newLine, numBytesInLine, &bytesWritten);
+
+ NS_Free(newLine);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } while (newLine);
+
+ msgParser->FinishHeader();
+ uint32_t resultFlags;
+ if (offlineStore)
+ fakeHdr->OrFlags(nsMsgMessageFlags::Offline | nsMsgMessageFlags::Read, &resultFlags);
+ else
+ fakeHdr->OrFlags(nsMsgMessageFlags::Read, &resultFlags);
+ if (offlineStore)
+ fakeHdr->SetOfflineMessageSize(fileSize);
+ mDatabase->AddNewHdrToDB(fakeHdr, true /* notify */);
+
+ // Call FinishNewMessage before setting pending attributes, as in
+ // maildir it copies from tmp to cur and may change the storeToken
+ // to get a unique filename.
+ if (offlineStore)
+ {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ GetMsgStore(getter_AddRefs(msgStore));
+ if (msgStore)
+ msgStore->FinishNewMessage(offlineStore, fakeHdr);
+ }
+
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ messages->AppendElement(fakeHdr, false);
+
+ SetPendingAttributes(messages, false);
+ // Gloda needs this notification to index the fake message.
+ nsCOMPtr<nsIMsgFolderNotificationService>
+ notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsClassified(messages, false, false);
+ inputStream->Close();
+ inputStream = nullptr;
+ }
+ if (offlineStore)
+ offlineStore->Close();
+ return rv;
+}
+
+nsresult
+nsImapMailFolder::OnCopyCompleted(nsISupports *srcSupport, nsresult rv)
+{
+ // if it's a file, and the copy succeeded, then fcc the offline
+ // store, and add a kMoveResult offline op.
+ if (NS_SUCCEEDED(rv) && m_copyState)
+ {
+ nsCOMPtr<nsIFile> srcFile(do_QueryInterface(srcSupport));
+ if (srcFile)
+ (void) CopyFileToOfflineStore(srcFile, m_copyState->m_appendUID);
+ }
+ m_copyState = nullptr;
+ nsresult result;
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &result);
+ NS_ENSURE_SUCCESS(result, result);
+ return copyService->NotifyCompletion(srcSupport, this, rv);
+}
+
+nsresult nsImapMailFolder::CreateBaseMessageURI(const nsACString& aURI)
+{
+ return nsCreateImapBaseMessageURI(aURI, mBaseMessageURI);
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderURL(nsACString& aFolderURL)
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rootFolder->GetURI(aFolderURL);
+ if (rootFolder == this)
+ return NS_OK;
+
+ NS_ASSERTION(mURI.Length() > aFolderURL.Length(), "Should match with a folder name!");
+ nsCString escapedName;
+ MsgEscapeString(Substring(mURI, aFolderURL.Length()),
+ nsINetUtil::ESCAPE_URL_PATH,
+ escapedName);
+ if (escapedName.IsEmpty())
+ return NS_ERROR_OUT_OF_MEMORY;
+ aFolderURL.Append(escapedName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsSubscribing(bool *bVal)
+{
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_folderNeedsSubscribing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsSubscribing(bool bVal)
+{
+ m_folderNeedsSubscribing = bVal;
+ return NS_OK;
+}
+
+nsMsgIMAPFolderACL * nsImapMailFolder::GetFolderACL()
+{
+ if (!m_folderACL)
+ m_folderACL = new nsMsgIMAPFolderACL(this);
+ return m_folderACL;
+}
+
+nsresult nsImapMailFolder::CreateACLRightsStringForFolder(nsAString& rightsString)
+{
+ GetFolderACL(); // lazy create
+ NS_ENSURE_TRUE(m_folderACL, NS_ERROR_NULL_POINTER);
+ return m_folderACL->CreateACLRightsString(rightsString);
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsACLListed(bool *bVal)
+{
+ NS_ENSURE_ARG_POINTER(bVal);
+ bool dontNeedACLListed = !m_folderNeedsACLListed;
+ // if we haven't acl listed, and it's not a no select folder or the inbox,
+ // then we'll list the acl if it's not a namespace.
+ if (m_folderNeedsACLListed && !(mFlags & (nsMsgFolderFlags::ImapNoselect | nsMsgFolderFlags::Inbox)))
+ GetIsNamespace(&dontNeedACLListed);
+ *bVal = !dontNeedACLListed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsACLListed(bool bVal)
+{
+ m_folderNeedsACLListed = bVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetIsNamespace(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsresult rv = NS_OK;
+ if (!m_namespace)
+ {
+#ifdef DEBUG_bienvenu
+ // Make sure this isn't causing us to open the database
+ NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown, "hierarchy delimiter not set");
+#endif
+
+ nsCString onlineName, serverKey;
+ GetServerKey(serverKey);
+ GetOnlineName(onlineName);
+ char hierarchyDelimiter;
+ GetHierarchyDelimiter(&hierarchyDelimiter);
+
+ nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionList, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_namespace = nsIMAPNamespaceList::GetNamespaceForFolder(
+ serverKey.get(), onlineName.get(), hierarchyDelimiter);
+ if (m_namespace == nullptr)
+ {
+ if (mFlags & nsMsgFolderFlags::ImapOtherUser)
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(serverKey.get(), kOtherUsersNamespace, m_namespace);
+ else if (mFlags & nsMsgFolderFlags::ImapPublic)
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(serverKey.get(), kPublicNamespace, m_namespace);
+ else
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(serverKey.get(), kPersonalNamespace, m_namespace);
+ }
+ NS_ASSERTION(m_namespace, "failed to get namespace");
+ if (m_namespace)
+ {
+ nsIMAPNamespaceList::SuggestHierarchySeparatorForNamespace(m_namespace,
+ hierarchyDelimiter);
+ m_folderIsNamespace = nsIMAPNamespaceList::GetFolderIsNamespace(
+ serverKey.get(), onlineName.get(),
+ hierarchyDelimiter, m_namespace);
+ }
+ }
+ *aResult = m_folderIsNamespace;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetIsNamespace(bool isNamespace)
+{
+ m_folderIsNamespace = isNamespace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::ResetNamespaceReferences()
+{
+ nsCString serverKey;
+ nsCString onlineName;
+ GetServerKey(serverKey);
+ GetOnlineName(onlineName);
+ char hierarchyDelimiter;
+ GetHierarchyDelimiter(&hierarchyDelimiter);
+ m_namespace = nsIMAPNamespaceList::GetNamespaceForFolder(serverKey.get(),
+ onlineName.get(),
+ hierarchyDelimiter);
+ m_folderIsNamespace = m_namespace ? nsIMAPNamespaceList::GetFolderIsNamespace(
+ serverKey.get(), onlineName.get(),
+ hierarchyDelimiter, m_namespace) : false;
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ GetSubFolders(getter_AddRefs(enumerator));
+ if (!enumerator)
+ return NS_OK;
+
+ nsresult rv;
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIMsgImapMailFolder> folder(do_QueryInterface(item, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ folder->ResetNamespaceReferences();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::FindOnlineSubFolder(const nsACString& targetOnlineName, nsIMsgImapMailFolder **aResultFolder)
+{
+ nsresult rv = NS_OK;
+
+ nsCString onlineName;
+ GetOnlineName(onlineName);
+
+ if (onlineName.Equals(targetOnlineName))
+ return QueryInterface(NS_GET_IID(nsIMsgImapMailFolder), (void **) aResultFolder);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ GetSubFolders(getter_AddRefs(enumerator));
+ if (!enumerator)
+ return NS_OK;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIMsgImapMailFolder> folder(do_QueryInterface(item, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = folder->FindOnlineSubFolder(targetOnlineName, aResultFolder);
+ if (*aResultFolder)
+ return rv;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsAdded(bool *bVal)
+{
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_folderNeedsAdded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsAdded(bool bVal)
+{
+ m_folderNeedsAdded = bVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderQuotaCommandIssued(bool *aCmdIssued)
+{
+ NS_ENSURE_ARG_POINTER(aCmdIssued);
+ *aCmdIssued = m_folderQuotaCommandIssued;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderQuotaCommandIssued(bool aCmdIssued)
+{
+ m_folderQuotaCommandIssued = aCmdIssued;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderQuotaData(const nsACString &aFolderQuotaRoot,
+ uint32_t aFolderQuotaUsedKB,
+ uint32_t aFolderQuotaMaxKB)
+{
+ m_folderQuotaDataIsValid = true;
+ m_folderQuotaRoot = aFolderQuotaRoot;
+ m_folderQuotaUsedKB = aFolderQuotaUsedKB;
+ m_folderQuotaMaxKB = aFolderQuotaMaxKB;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetQuota(bool* aValid,
+ uint32_t* aUsed, uint32_t* aMax)
+{
+ NS_ENSURE_ARG_POINTER(aValid);
+ NS_ENSURE_ARG_POINTER(aUsed);
+ NS_ENSURE_ARG_POINTER(aMax);
+ *aValid = m_folderQuotaDataIsValid;
+ *aUsed = m_folderQuotaUsedKB;
+ *aMax = m_folderQuotaMaxKB;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::PerformExpand(nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ bool usingSubscription = false;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapServer->GetUsingSubscription(&usingSubscription);
+ if (NS_SUCCEEDED(rv) && !usingSubscription)
+ {
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->DiscoverChildren( this, this, m_onlineFolderName, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RenameClient(nsIMsgWindow *msgWindow, nsIMsgFolder *msgFolder, const nsACString& oldName, const nsACString& newName)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgImapMailFolder> oldImapFolder = do_QueryInterface(msgFolder, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ char hierarchyDelimiter = '/';
+ oldImapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
+ int32_t boxflags=0;
+ oldImapFolder->GetBoxFlags(&boxflags);
+
+ nsAutoString newLeafName;
+ NS_ConvertASCIItoUTF16 newNameString(newName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newLeafName = newNameString;
+ nsAutoString folderNameStr;
+ int32_t folderStart = newLeafName.RFindChar('/'); //internal use of hierarchyDelimiter is always '/'
+ if (folderStart > 0)
+ {
+ newLeafName = Substring(newNameString, folderStart + 1);
+ CreateDirectoryForFolder(getter_AddRefs(pathFile)); //needed when we move a folder to a folder with no subfolders.
+ }
+
+ // if we get here, it's really a leaf, and "this" is the parent.
+ folderNameStr = newLeafName;
+
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDatabase> mailDBFactory;
+ nsCOMPtr<nsIMsgFolder> child;
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder;
+
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> unusedDB;
+ nsCOMPtr <nsIFile> dbFile;
+
+ // warning, path will be changed
+ rv = CreateFileForDB(folderNameStr, pathFile, getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Use openMailDBFromFile() and not OpenFolderDB() here, since we don't use the DB.
+ rv = msgDBService->OpenMailDBFromFile(dbFile, nullptr, true, true,
+ getter_AddRefs(unusedDB));
+ if (NS_SUCCEEDED(rv) && unusedDB)
+ {
+ //need to set the folder name
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+
+ //Now let's create the actual new folder
+ rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child));
+ if (!child || NS_FAILED(rv))
+ return rv;
+ nsAutoString unicodeName;
+ rv = CopyMUTF7toUTF16(NS_LossyConvertUTF16toASCII(folderNameStr), unicodeName);
+ if (NS_SUCCEEDED(rv))
+ child->SetPrettyName(unicodeName);
+ imapFolder = do_QueryInterface(child);
+ if (imapFolder)
+ {
+ nsAutoCString onlineName(m_onlineFolderName);
+
+ if (!onlineName.IsEmpty())
+ onlineName.Append(hierarchyDelimiter);
+ onlineName.Append(NS_LossyConvertUTF16toASCII(folderNameStr));
+ imapFolder->SetVerifiedAsOnlineFolder(true);
+ imapFolder->SetOnlineName(onlineName);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ imapFolder->SetBoxFlags(boxflags);
+ // store the online name as the mailbox name in the db folder info
+ // I don't think anyone uses the mailbox name, so we'll use it
+ // to restore the online name when blowing away an imap db.
+ if (folderInfo)
+ {
+ nsAutoString unicodeOnlineName;
+ CopyASCIItoUTF16(onlineName, unicodeOnlineName);
+ folderInfo->SetMailboxName(unicodeOnlineName);
+ }
+ bool changed = false;
+ msgFolder->MatchOrChangeFilterDestination(child, false /*caseInsensitive*/, &changed);
+ if (changed)
+ msgFolder->AlertFilterChanged(msgWindow);
+ }
+ unusedDB->SetSummaryValid(true);
+ unusedDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ unusedDB->Close(true);
+ child->RenameSubFolders(msgWindow, msgFolder);
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ msgFolder->GetParent(getter_AddRefs(msgParent));
+ msgFolder->SetParent(nullptr);
+ // Reset online status now that the folder is renamed.
+ nsCOMPtr <nsIMsgImapMailFolder> oldImapFolder = do_QueryInterface(msgFolder);
+ if (oldImapFolder)
+ oldImapFolder->SetVerifiedAsOnlineFolder(false);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderRenamed(msgFolder, child);
+
+ // Do not propagate the deletion until after we have (synchronously) notified
+ // all listeners about the rename. This allows them to access properties on
+ // the source folder without experiencing failures.
+ if (msgParent)
+ msgParent->PropagateDelete(msgFolder, true, nullptr);
+ NotifyItemAdded(child);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RenameSubFolders(nsIMsgWindow *msgWindow, nsIMsgFolder *oldFolder)
+{
+ m_initialized = true;
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = oldFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ if (NS_FAILED(enumerator->GetNext(getter_AddRefs(item))))
+ continue;
+
+ nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgImapMailFolder> folder(do_QueryInterface(msgFolder, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ char hierarchyDelimiter = '/';
+ folder->GetHierarchyDelimiter(&hierarchyDelimiter);
+
+ int32_t boxflags;
+ folder->GetBoxFlags(&boxflags);
+
+ bool verified;
+ folder->GetVerifiedAsOnlineFolder(&verified);
+
+ nsCOMPtr<nsIFile> oldPathFile;
+ rv = msgFolder->GetFilePath(getter_AddRefs(oldPathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> newParentPathFile;
+ rv = GetFilePath(getter_AddRefs(newParentPathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = AddDirectorySeparator(newParentPathFile);
+ nsAutoCString oldLeafName;
+ oldPathFile->GetNativeLeafName(oldLeafName);
+ newParentPathFile->AppendNative(oldLeafName);
+
+ nsCString newPathStr;
+ newParentPathFile->GetNativePath(newPathStr);
+
+ nsCOMPtr<nsIFile> newPathFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newPathFile->InitWithFile(newParentPathFile);
+
+ nsCOMPtr<nsIFile> dbFilePath = newPathFile;
+
+ nsCOMPtr<nsIMsgFolder> child;
+
+ nsString folderName;
+ rv = msgFolder->GetName(folderName);
+ if (folderName.IsEmpty() || NS_FAILED(rv)) return rv;
+
+ nsCString utf7LeafName;
+ rv = CopyUTF16toMUTF7(folderName, utf7LeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX : Fix this non-sense by fixing AddSubfolderWithPath
+ nsAutoString unicodeLeafName;
+ CopyASCIItoUTF16(utf7LeafName, unicodeLeafName);
+
+ rv = AddSubfolderWithPath(unicodeLeafName, dbFilePath, getter_AddRefs(child));
+ if (!child || NS_FAILED(rv)) return rv;
+
+ child->SetName(folderName);
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(child);
+ nsCString onlineName;
+ GetOnlineName(onlineName);
+ nsAutoCString onlineCName(onlineName);
+ onlineCName.Append(hierarchyDelimiter);
+ onlineCName.Append(utf7LeafName);
+ if (imapFolder)
+ {
+ imapFolder->SetVerifiedAsOnlineFolder(verified);
+ imapFolder->SetOnlineName(onlineCName);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ imapFolder->SetBoxFlags(boxflags);
+
+ bool changed = false;
+ msgFolder->MatchOrChangeFilterDestination(child, false /*caseInsensitive*/, &changed);
+ if (changed)
+ msgFolder->AlertFilterChanged(msgWindow);
+ child->RenameSubFolders(msgWindow, msgFolder);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::IsCommandEnabled(const nsACString& command, bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = !(WeAreOffline() && (command.EqualsLiteral("cmd_renameFolder") ||
+ command.EqualsLiteral("cmd_compactFolder") ||
+ command.EqualsLiteral("button_compact") ||
+ command.EqualsLiteral("cmd_delete") ||
+ command.EqualsLiteral("button_delete")));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCanFileMessages(bool *aCanFileMessages)
+{
+ nsresult rv;
+ *aCanFileMessages = true;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ rv = server->GetCanFileMessagesOnServer(aCanFileMessages);
+
+ if (*aCanFileMessages)
+ rv = nsMsgDBFolder::GetCanFileMessages(aCanFileMessages);
+
+ if (*aCanFileMessages)
+ {
+ bool noSelect;
+ GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
+ *aCanFileMessages = (noSelect) ? false : GetFolderACL()->GetCanIInsertInFolder();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCanDeleteMessages(bool *aCanDeleteMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanDeleteMessages);
+ *aCanDeleteMessages = GetFolderACL()->GetCanIDeleteInFolder();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetPerformingBiff(bool *aPerformingBiff)
+{
+ NS_ENSURE_ARG_POINTER(aPerformingBiff);
+ *aPerformingBiff = m_performingBiff;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetPerformingBiff(bool aPerformingBiff)
+{
+ m_performingBiff = aPerformingBiff;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetFilterList(nsIMsgFilterList *aMsgFilterList)
+{
+ m_filterList = aMsgFilterList;
+ return nsMsgDBFolder::SetFilterList(aMsgFilterList);
+}
+
+nsresult nsImapMailFolder::GetMoveCoalescer()
+{
+ if (!m_moveCoalescer)
+ {
+ m_moveCoalescer = new nsImapMoveCoalescer(this, nullptr /* msgWindow */);
+ NS_ENSURE_TRUE (m_moveCoalescer, NS_ERROR_OUT_OF_MEMORY);
+ m_moveCoalescer->AddRef();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::StoreCustomKeywords(nsIMsgWindow *aMsgWindow, const nsACString& aFlagsToAdd,
+ const nsACString& aFlagsToSubtract, nsMsgKey *aKeysToStore, uint32_t aNumKeys, nsIURI **_retval)
+{
+ nsresult rv;
+ if (WeAreOffline())
+ {
+ GetDatabase();
+ if (mDatabase)
+ {
+ for (uint32_t keyIndex = 0; keyIndex < aNumKeys; keyIndex++)
+ {
+ nsCOMPtr <nsIMsgOfflineImapOperation> op;
+ rv = mDatabase->GetOfflineOpForKey(aKeysToStore[keyIndex], true, getter_AddRefs(op));
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ if (!aFlagsToAdd.IsEmpty())
+ op->AddKeywordToAdd(PromiseFlatCString(aFlagsToAdd).get());
+ if (!aFlagsToSubtract.IsEmpty())
+ op->AddKeywordToRemove(PromiseFlatCString(aFlagsToSubtract).get());
+ }
+ }
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline ops
+ return rv;
+ }
+ }
+ nsCOMPtr<nsIImapService> imapService(do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString msgIds;
+ AllocateUidStringFromKeys(aKeysToStore, aNumKeys, msgIds);
+ return imapService->StoreCustomKeywords(this, aMsgWindow, aFlagsToAdd,
+ aFlagsToSubtract, msgIds, _retval);
+}
+
+NS_IMETHODIMP nsImapMailFolder::NotifyIfNewMail()
+{
+ return PerformBiffNotifications();
+}
+
+bool nsImapMailFolder::ShowPreviewText()
+{
+ bool showPreviewText = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ prefBranch->GetBoolPref("mail.biff.alert.show_preview", &showPreviewText);
+ return showPreviewText;
+}
+
+nsresult
+nsImapMailFolder::PlaybackCoalescedOperations()
+{
+ if (m_moveCoalescer)
+ {
+ nsTArray<nsMsgKey> *junkKeysToClassify = m_moveCoalescer->GetKeyBucket(0);
+ if (junkKeysToClassify && !junkKeysToClassify->IsEmpty())
+ StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), NS_LITERAL_CSTRING("Junk"), EmptyCString(), junkKeysToClassify->Elements(), junkKeysToClassify->Length(), nullptr);
+ junkKeysToClassify->Clear();
+ nsTArray<nsMsgKey> *nonJunkKeysToClassify = m_moveCoalescer->GetKeyBucket(1);
+ if (nonJunkKeysToClassify && !nonJunkKeysToClassify->IsEmpty())
+ StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), NS_LITERAL_CSTRING("NonJunk"), EmptyCString(), nonJunkKeysToClassify->Elements(), nonJunkKeysToClassify->Length(), nullptr);
+ nonJunkKeysToClassify->Clear();
+ return m_moveCoalescer->PlaybackMoves(ShowPreviewText());
+ }
+ return NS_OK; // must not be any coalesced operations
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetJunkScoreForMessages(nsIArray *aMessages, const nsACString& aJunkScore)
+{
+ NS_ENSURE_ARG(aMessages);
+
+ nsresult rv = nsMsgDBFolder::SetJunkScoreForMessages(aMessages, aJunkScore);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keys;
+ nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ StoreCustomKeywords(nullptr, aJunkScore.Equals("0") ? NS_LITERAL_CSTRING("NonJunk") : NS_LITERAL_CSTRING("Junk"), EmptyCString(), keys.Elements(),
+ keys.Length(), nullptr);
+ if (mDatabase)
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::OnMessageClassified(const char * aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent)
+{
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aMsgURI) // not end of batch
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ rv = msgHdr->GetMessageKey(&msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if this message needs junk classification
+
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+
+ if (processingFlags & nsMsgProcessingFlags::ClassifyJunk)
+ {
+ nsMsgDBFolder::OnMessageClassified(aMsgURI, aClassification, aJunkPercent);
+
+ GetMoveCoalescer();
+ if (m_moveCoalescer)
+ {
+ nsTArray<nsMsgKey> *keysToClassify = m_moveCoalescer->GetKeyBucket((aClassification == nsIJunkMailPlugin::JUNK) ? 0 : 1);
+ NS_ASSERTION(keysToClassify, "error getting key bucket");
+ if (keysToClassify)
+ keysToClassify->AppendElement(msgKey);
+ }
+ if (aClassification == nsIJunkMailPlugin::JUNK)
+ {
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool markAsReadOnSpam;
+ (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam);
+ if (markAsReadOnSpam)
+ {
+ if (!m_junkMessagesToMarkAsRead)
+ {
+ m_junkMessagesToMarkAsRead = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ m_junkMessagesToMarkAsRead->AppendElement(msgHdr, false);
+ }
+
+ bool willMoveMessage = false;
+
+ // don't do the move when we are opening up
+ // the junk mail folder or the trash folder
+ // or when manually classifying messages in those folders
+ if (!(mFlags & nsMsgFolderFlags::Junk || mFlags & nsMsgFolderFlags::Trash))
+ {
+ bool moveOnSpam;
+ (void)spamSettings->GetMoveOnSpam(&moveOnSpam);
+ if (moveOnSpam)
+ {
+ nsCString spamFolderURI;
+ rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!spamFolderURI.IsEmpty())
+ {
+ rv = GetExistingFolder(spamFolderURI, getter_AddRefs(mSpamFolder));
+ if (NS_SUCCEEDED(rv) && mSpamFolder)
+ {
+ rv = mSpamFolder->SetFlag(nsMsgFolderFlags::Junk);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mSpamKeysToMove.AppendElement(msgKey);
+ willMoveMessage = true;
+ }
+ else
+ {
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // the listener should do
+ // rv = folder->SetFlag(nsMsgFolderFlags::Junk);
+ // NS_ENSURE_SUCCESS(rv,rv);
+ // if (NS_SUCCEEDED(GetMoveCoalescer())) {
+ // m_moveCoalescer->AddMove(folder, msgKey);
+ // willMoveMessage = true;
+ // }
+ rv = GetOrCreateFolder(spamFolderURI, nullptr /* aListener */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateFolder failed");
+ }
+ }
+ }
+ }
+ rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ }
+
+ else // end of batch
+ {
+ // Parent will apply post bayes filters.
+ nsMsgDBFolder::OnMessageClassified(nullptr, nsIJunkMailPlugin::UNCLASSIFIED, 0);
+
+ if (m_junkMessagesToMarkAsRead)
+ {
+ uint32_t count;
+ m_junkMessagesToMarkAsRead->GetLength(&count);
+ if (count > 0)
+ {
+ rv = MarkMessagesRead(m_junkMessagesToMarkAsRead, true);
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_junkMessagesToMarkAsRead->Clear();
+ }
+ }
+ if (!mSpamKeysToMove.IsEmpty())
+ {
+ GetMoveCoalescer();
+ for (uint32_t keyIndex = 0; keyIndex < mSpamKeysToMove.Length(); keyIndex++)
+ {
+ // If an upstream filter moved this message, don't move it here.
+ nsMsgKey msgKey = mSpamKeysToMove.ElementAt(keyIndex);
+ nsMsgProcessingFlagType processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+ if (!(processingFlags & nsMsgProcessingFlags::FilterToMove))
+ {
+ if (m_moveCoalescer && mSpamFolder)
+ m_moveCoalescer->AddMove(mSpamFolder, msgKey);
+ }
+ else
+ {
+ // We don't need the FilterToMove flag anymore.
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::FilterToMove);
+ }
+ }
+ mSpamKeysToMove.Clear();
+ }
+
+ // Let's not hold onto the spam folder reference longer than necessary.
+ mSpamFolder = nullptr;
+
+ bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
+ PlaybackCoalescedOperations();
+ // If we are performing biff for this folder, tell the server object
+ if ((!pendingMoves || !ShowPreviewText()) && m_performingBiff)
+ {
+ // we don't need to adjust the num new messages in this folder because
+ // the playback moves code already did that.
+ (void) PerformBiffNotifications();
+ server->SetPerformingBiff(false);
+ m_performingBiff = false;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetShouldDownloadAllHeaders(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ //for just the inbox, we check if the filter list has arbitary headers.
+ //for all folders, check if we have a spam plugin that requires all headers
+ if (mFlags & nsMsgFolderFlags::Inbox)
+ {
+ nsCOMPtr <nsIMsgFilterList> filterList;
+ nsresult rv = GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = filterList->GetShouldDownloadAllHeaders(aResult);
+ if (*aResult)
+ return rv;
+ }
+ nsCOMPtr <nsIMsgFilterPlugin> filterPlugin;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))))
+ server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
+
+ return (filterPlugin) ? filterPlugin->GetShouldDownloadAllHeaders(aResult) : NS_OK;
+}
+
+
+void nsImapMailFolder::GetTrashFolderName(nsAString &aFolderName)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv)) return;
+ imapServer = do_QueryInterface(server, &rv);
+ if (NS_FAILED(rv)) return;
+ imapServer->GetTrashFolderName(aFolderName);
+ return;
+}
+NS_IMETHODIMP nsImapMailFolder::FetchMsgPreviewText(nsMsgKey *aKeysToFetch, uint32_t aNumKeys,
+ bool aLocalOnly, nsIUrlListener *aUrlListener,
+ bool *aAsyncResults)
+{
+ NS_ENSURE_ARG_POINTER(aKeysToFetch);
+ NS_ENSURE_ARG_POINTER(aAsyncResults);
+
+ nsTArray<nsMsgKey> keysToFetchFromServer;
+
+ *aAsyncResults = false;
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgMessageService> msgService = do_QueryInterface(imapService, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < aNumKeys; i++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsCString prevBody;
+ rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ignore messages that already have a preview body.
+ msgHdr->GetStringProperty("preview", getter_Copies(prevBody));
+ if (!prevBody.IsEmpty())
+ continue;
+
+ /* check if message is in memory cache or offline store. */
+ nsCOMPtr <nsIURI> url;
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCString messageUri;
+ rv = GetUriForMsg(msgHdr, messageUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = msgService->GetUrlForUri(messageUri.get(), getter_AddRefs(url), nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Lets look in the offline store.
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (msgFlags & nsMsgMessageFlags::Offline)
+ {
+ int64_t messageOffset;
+ uint32_t messageSize;
+ GetOfflineFileStream(msgKey, &messageOffset, &messageSize, getter_AddRefs(inputStream));
+ if (inputStream)
+ rv = GetMsgPreviewTextFromStream(msgHdr, inputStream);
+ }
+ else if (!aLocalOnly) {
+ keysToFetchFromServer.AppendElement(msgKey);
+ }
+ }
+ if (!keysToFetchFromServer.IsEmpty())
+ {
+ uint32_t msgCount = keysToFetchFromServer.Length();
+ nsAutoCString messageIds;
+ AllocateImapUidString(keysToFetchFromServer.Elements(), msgCount,
+ nullptr, messageIds);
+ rv = imapService->GetBodyStart(this, aUrlListener,
+ messageIds, 2048, nullptr);
+ *aAsyncResults = true; // the preview text will be available async...
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords)
+{
+ nsresult rv = nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keys;
+ rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StoreCustomKeywords(nullptr, aKeywords, EmptyCString(), keys.Elements(), keys.Length(), nullptr);
+ if (mDatabase)
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords)
+{
+ nsresult rv = nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keys;
+ nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StoreCustomKeywords(nullptr, EmptyCString(), aKeywords, keys.Elements(), keys.Length(), nullptr);
+ if (mDatabase)
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetCustomIdentity(nsIMsgIdentity **aIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ if (mFlags & nsMsgFolderFlags::ImapOtherUser)
+ {
+ nsresult rv;
+ bool delegateOtherUsersFolders = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetBoolPref("mail.imap.delegateOtherUsersFolders", &delegateOtherUsersFolders);
+ // if we're automatically delegating other user's folders, we need to
+ // cons up an e-mail address for the other user. We do that by
+ // taking the other user's name and the current user's domain name,
+ // assuming they'll be the same. So, <otherUsersName>@<ourDomain>
+ if (delegateOtherUsersFolders)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgIdentity> ourIdentity;
+ nsCOMPtr <nsIMsgIdentity> retIdentity;
+ nsCOMPtr <nsIMsgAccount> account;
+ nsCString foldersUserName;
+ nsCString ourEmailAddress;
+
+ accountManager->FindAccountForServer(server, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+ account->GetDefaultIdentity(getter_AddRefs(ourIdentity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ ourIdentity->GetEmail(ourEmailAddress);
+ int32_t atPos = ourEmailAddress.FindChar('@');
+ if (atPos != kNotFound)
+ {
+ nsCString otherUsersEmailAddress;
+ GetFolderOwnerUserName(otherUsersEmailAddress);
+ otherUsersEmailAddress.Append(Substring(ourEmailAddress, atPos, ourEmailAddress.Length()));
+ nsCOMPtr<nsIArray> identities;
+ rv = accountManager->GetIdentitiesForServer(server, getter_AddRefs(identities));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numIdentities;
+ rv = identities->GetLength(&numIdentities);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t identityIndex = 0; identityIndex < numIdentities; identityIndex++)
+ {
+ nsCOMPtr<nsIMsgIdentity> identity = do_QueryElementAt(identities, identityIndex);
+ if (!identity)
+ continue;
+ nsCString identityEmail;
+ identity->GetEmail(identityEmail);
+ if (identityEmail.Equals(otherUsersEmailAddress))
+ {
+ retIdentity = identity;;
+ break;
+ }
+ }
+ if (!retIdentity)
+ {
+ // create the identity
+ rv = accountManager->CreateIdentity(getter_AddRefs(retIdentity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ retIdentity->SetEmail(otherUsersEmailAddress);
+ nsCOMPtr <nsIMsgAccount> account;
+ accountManager->FindAccountForServer(server, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+ account->AddIdentity(retIdentity);
+ }
+ }
+ if (retIdentity)
+ {
+ retIdentity.swap(*aIdentity);
+ return NS_OK;
+ }
+ }
+ }
+ return nsMsgDBFolder::GetCustomIdentity(aIdentity);
+}
+
+NS_IMETHODIMP nsImapMailFolder::ChangePendingTotal(int32_t aDelta)
+{
+ ChangeNumPendingTotalMessages(aDelta);
+ if (aDelta > 0)
+ NotifyHasPendingMsgs();
+ return NS_OK;
+}
+
+void nsImapMailFolder::NotifyHasPendingMsgs()
+{
+ InitAutoSyncState();
+ nsresult rv;
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ autoSyncMgr->OnFolderHasPendingMsgs(m_autoSyncStateObj);
+}
+
+/* void changePendingUnread (in long aDelta); */
+NS_IMETHODIMP nsImapMailFolder::ChangePendingUnread(int32_t aDelta)
+{
+ ChangeNumPendingUnread(aDelta);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerRecent(int32_t *aServerRecent)
+{
+ NS_ENSURE_ARG_POINTER(aServerRecent);
+ *aServerRecent = m_numServerRecentMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerTotal(int32_t *aServerTotal)
+{
+ NS_ENSURE_ARG_POINTER(aServerTotal);
+ *aServerTotal = m_numServerTotalMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerUnseen(int32_t *aServerUnseen)
+{
+ NS_ENSURE_ARG_POINTER(aServerUnseen);
+ *aServerUnseen = m_numServerUnseenMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerNextUID(int32_t *aNextUID)
+{
+ NS_ENSURE_ARG_POINTER(aNextUID);
+ *aNextUID = m_nextUID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetAutoSyncStateObj(nsIAutoSyncState **autoSyncStateObj)
+{
+ NS_ENSURE_ARG_POINTER(autoSyncStateObj);
+
+ // create auto-sync state object lazily
+ InitAutoSyncState();
+
+ NS_IF_ADDREF(*autoSyncStateObj = m_autoSyncStateObj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::InitiateAutoSync(nsIUrlListener *aUrlListener)
+{
+ nsCString folderName;
+ GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug, ("Updating folder: %s\n", folderName.get()));
+
+ // HACK: if UpdateFolder finds out that it can't open
+ // the folder, it doesn't set the url listener and returns
+ // no error. In this case, we return success from this call
+ // but the caller never gets a notification on its url listener.
+ bool canOpenThisFolder = true;
+ GetCanOpenFolder(&canOpenThisFolder);
+
+ if (!canOpenThisFolder)
+ {
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug, ("Cannot update folder: %s\n", folderName.get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // create auto-sync state object lazily
+ InitAutoSyncState();
+
+ // make sure we get the counts from the folder cache.
+ ReadDBFolderInfo(false);
+
+ nsresult rv = m_autoSyncStateObj->ManageStorageSpace();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t syncState;
+ m_autoSyncStateObj->GetState(&syncState);
+ if (syncState == nsAutoSyncState::stUpdateNeeded)
+ return m_autoSyncStateObj->UpdateFolder();
+
+ // We only want to init the autosyncStateObj server counts the first time
+ // we update, and update it when the STATUS call finishes. This deals with
+ // the case where biff is doing a STATUS on a non-inbox folder, which
+ // can make autosync think the counts aren't changing.
+ PRTime lastUpdateTime;
+ m_autoSyncStateObj->GetLastUpdateTime(&lastUpdateTime);
+ if (!lastUpdateTime)
+ m_autoSyncStateObj->SetServerCounts(m_numServerTotalMessages,
+ m_numServerRecentMessages,
+ m_numServerUnseenMessages,
+ m_nextUID);
+ // Issue a STATUS command and see if any counts changed.
+ m_autoSyncStateObj->SetState(nsAutoSyncState::stStatusIssued);
+ // The OnStopRunningUrl method of the autosync state obj
+ // will check if the counts or next uid have changed,
+ // and if so, will issue an UpdateFolder().
+ rv = UpdateStatus(m_autoSyncStateObj, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // record the last update time
+ m_autoSyncStateObj->SetLastUpdateTime(PR_Now());
+
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::CreatePlaybackTimer()
+{
+ nsresult rv = NS_OK;
+ if (!m_playbackTimer)
+ {
+ m_playbackTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create pseudo-offline operation timer in nsImapMailFolder");
+ }
+ return rv;
+}
+
+void nsImapMailFolder::PlaybackTimerCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsPlaybackRequest *request = static_cast<nsPlaybackRequest*>(aClosure);
+
+ NS_ASSERTION(request->SrcFolder->m_pendingPlaybackReq == request, "wrong playback request pointer");
+
+ RefPtr<nsImapOfflineSync> offlineSync = new nsImapOfflineSync(request->MsgWindow, nullptr, request->SrcFolder, true);
+ if (offlineSync)
+ {
+ mozilla::DebugOnly<nsresult> rv = offlineSync->ProcessNextOperation();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "pseudo-offline playback is not successful");
+ }
+
+ // release request struct
+ request->SrcFolder->m_pendingPlaybackReq = nullptr;
+ delete request;
+}
+
+void nsImapMailFolder::InitAutoSyncState()
+{
+ if (!m_autoSyncStateObj)
+ m_autoSyncStateObj = new nsAutoSyncState(this);
+}
+
+NS_IMETHODIMP nsImapMailFolder::HasMsgOffline(nsMsgKey msgKey, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = false;
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder)
+ *_retval = true;
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOfflineMsgFolder(nsMsgKey msgKey, nsIMsgFolder **aMsgFolder)
+{
+ // Check if we have the message in the current folder.
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ nsCOMPtr<nsIMsgFolder> subMsgFolder;
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (hdr)
+ {
+ uint32_t msgFlags = 0;
+ hdr->GetFlags(&msgFlags);
+ // Check if we already have this message body offline
+ if ((msgFlags & nsMsgMessageFlags::Offline))
+ {
+ NS_IF_ADDREF(*aMsgFolder = this);
+ return NS_OK;
+ }
+ }
+
+ if (!*aMsgFolder)
+ {
+ // Checking the existence of message in other folders in case of GMail Server
+ bool isGMail;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapServer->GetIsGMailServer(&isGMail);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isGMail)
+ {
+ nsCString labels;
+ nsTArray<nsCString> labelNames;
+ hdr->GetStringProperty("X-GM-LABELS", getter_Copies(labels));
+ ParseString(labels, ' ', labelNames);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIMsgImapMailFolder> subFolder;
+ for (uint32_t i = 0; i < labelNames.Length(); i++)
+ {
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && (rootFolder))
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapRootFolder = do_QueryInterface(rootFolder);
+ if (labelNames[i].Equals("\"\\\\Draft\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Equals("\"\\\\Inbox\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Equals("\"\\\\All Mail\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Archive,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Equals("\"\\\\Trash\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Equals("\"\\\\Spam\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Junk,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Equals("\"\\\\Sent\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Find("[Imap]/", CaseInsensitiveCompare) != kNotFound)
+ {
+ MsgReplaceSubstring(labelNames[i], "[Imap]/", "");
+ imapRootFolder->FindOnlineSubFolder(labelNames[i], getter_AddRefs(subFolder));
+ subMsgFolder = do_QueryInterface(subFolder);
+ }
+ if (!subMsgFolder)
+ {
+ imapRootFolder->FindOnlineSubFolder(labelNames[i], getter_AddRefs(subFolder));
+ subMsgFolder = do_QueryInterface(subFolder);
+ }
+ if (subMsgFolder)
+ {
+ nsCOMPtr<nsIMsgDatabase> db;
+ subMsgFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (db)
+ {
+ nsCOMPtr<nsIMsgDBHdr> retHdr;
+ nsCString gmMsgID;
+ hdr->GetStringProperty("X-GM-MSGID", getter_Copies(gmMsgID));
+ rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(), getter_AddRefs(retHdr));
+ if (NS_FAILED(rv))
+ return rv;
+ if (retHdr)
+ {
+ uint32_t gmFlags = 0;
+ retHdr->GetFlags(&gmFlags);
+ if ((gmFlags & nsMsgMessageFlags::Offline))
+ {
+ subMsgFolder.forget(aMsgFolder);
+ // Focus on first positive result.
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOfflineFileStream(nsMsgKey msgKey, int64_t *offset, uint32_t *size, nsIInputStream **aFileStream)
+{
+ NS_ENSURE_ARG(aFileStream);
+ nsCOMPtr<nsIMsgFolder> offlineFolder;
+ nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(offlineFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if(!offlineFolder)
+ return NS_ERROR_FAILURE;
+
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (offlineFolder == this)
+ return nsMsgDBFolder::GetOfflineFileStream(msgKey, offset, size, aFileStream);
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if (NS_FAILED(rv))
+ return rv;
+ if (hdr)
+ {
+ nsCString gmMsgID;
+ hdr->GetStringProperty("X-GM-MSGID", getter_Copies(gmMsgID));
+ nsCOMPtr<nsIMsgDatabase> db;
+ offlineFolder->GetMsgDatabase(getter_AddRefs(db));
+ rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(), getter_AddRefs(hdr));
+ if (NS_FAILED(rv))
+ return rv;
+ nsMsgKey newMsgKey;
+ hdr->GetMessageKey(&newMsgKey);
+ return offlineFolder->GetOfflineFileStream(newMsgKey, offset, size, aFileStream);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetIncomingServerType(nsACString& serverType)
+{
+ serverType.AssignLiteral("imap");
+ return NS_OK;
+}
+
+void nsImapMailFolder::DeleteStoreMessages(nsIArray* aMessages)
+{
+ // Delete messages for pluggable stores that do not support compaction.
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void) GetMsgStore(getter_AddRefs(offlineStore));
+
+ if (offlineStore)
+ {
+ bool supportsCompaction;
+ offlineStore->GetSupportsCompaction(&supportsCompaction);
+ if (!supportsCompaction)
+ offlineStore->DeleteMessages(aMessages);
+ }
+}
+
+void nsImapMailFolder::DeleteStoreMessages(nsTArray<nsMsgKey> &aMessages)
+{
+ DeleteStoreMessages(aMessages, this);
+}
+
+void nsImapMailFolder::DeleteStoreMessages(nsTArray<nsMsgKey> &aMessages,
+ nsIMsgFolder* aFolder)
+{
+ // Delete messages for pluggable stores that do not support compaction.
+ NS_ASSERTION(aFolder, "Missing Source Folder");
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void) aFolder->GetMsgStore(getter_AddRefs(offlineStore));
+ if (offlineStore)
+ {
+ bool supportsCompaction;
+ offlineStore->GetSupportsCompaction(&supportsCompaction);
+ if (!supportsCompaction)
+ {
+ nsCOMPtr<nsIMsgDatabase> db;
+ aFolder->GetMsgDatabase(getter_AddRefs(db));
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ if (db)
+ rv = MsgGetHeadersFromKeys(db, aMessages, messages);
+ if (NS_SUCCEEDED(rv))
+ offlineStore->DeleteMessages(messages);
+ else
+ NS_WARNING("Failed to get database");
+ }
+ }
+}
diff --git a/mailnews/imap/src/nsImapMailFolder.h b/mailnews/imap/src/nsImapMailFolder.h
new file mode 100644
index 000000000..71665ee79
--- /dev/null
+++ b/mailnews/imap/src/nsImapMailFolder.h
@@ -0,0 +1,550 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsImapMailFolder_h__
+#define nsImapMailFolder_h__
+
+#include "mozilla/Attributes.h"
+#include "nsImapCore.h"
+#include "nsMsgDBFolder.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapMessageSink.h"
+#include "nsICopyMessageListener.h"
+#include "nsIImapService.h"
+#include "nsIUrlListener.h"
+#include "nsAutoPtr.h"
+#include "nsIImapIncomingServer.h" // we need this for its IID
+#include "nsIMsgParseMailMsgState.h"
+#include "nsITransactionManager.h"
+#include "nsImapUndoTxn.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgFilterHitNotify.h"
+#include "nsIMsgFilterList.h"
+#include "prmon.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgThread.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIThread.h"
+#include "nsDataHashtable.h"
+#include "nsIMutableArray.h"
+#include "nsITimer.h"
+#include "nsCOMArray.h"
+#include "nsAutoSyncState.h"
+#include "nsIRequestObserver.h"
+
+class nsImapMoveCoalescer;
+class nsIMsgIdentity;
+class nsIMsgOfflineImapOperation;
+
+#define COPY_BUFFER_SIZE 16384
+
+#define NS_IMAPMAILCOPYSTATE_IID \
+{ 0xb64534f0, 0x3d53, 0x11d3, \
+ { 0xac, 0x2a, 0x00, 0x80, 0x5f, 0x8a, 0xc9, 0x68 } }
+
+class nsImapMailCopyState: public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMAPMAILCOPYSTATE_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsImapMailCopyState();
+
+ nsCOMPtr<nsISupports> m_srcSupport; // source file spec or folder
+ nsCOMPtr<nsIArray> m_messages; // array of source messages
+ RefPtr<nsImapMoveCopyMsgTxn> m_undoMsgTxn; // undo object with this copy operation
+ nsCOMPtr<nsIMsgDBHdr> m_message; // current message to be copied
+ nsCOMPtr<nsIMsgCopyServiceListener> m_listener; // listener of this copy
+ // operation
+ nsCOMPtr<nsIFile> m_tmpFile; // temp file spec for copy operation
+ nsCOMPtr<nsIMsgWindow> m_msgWindow; // msg window for copy operation
+
+ nsCOMPtr<nsIMsgMessageService> m_msgService; // source folder message service; can
+ // be Nntp, Mailbox, or Imap
+ bool m_isMove; // is a move
+ bool m_selectedState; // needs to be in selected state; append msg
+ bool m_isCrossServerOp; // are we copying between imap servers?
+ uint32_t m_curIndex; // message index to the message array which we are
+ // copying
+ uint32_t m_totalCount;// total count of messages we have to do
+ uint32_t m_unreadCount; // num unread messages we're moving
+ bool m_streamCopy;
+ char *m_dataBuffer; // temporary buffer for this copy operation
+ nsCOMPtr<nsIOutputStream> m_msgFileStream; // temporary file (processed mail)
+ uint32_t m_dataBufferSize;
+ uint32_t m_leftOver;
+ bool m_allowUndo;
+ bool m_eatLF;
+ uint32_t m_newMsgFlags; // only used if there's no m_message
+ nsCString m_newMsgKeywords; // ditto
+ // If the server supports UIDPLUS, this is the UID for the append,
+ // if we're doing an append.
+ nsMsgKey m_appendUID;
+
+private:
+ virtual ~nsImapMailCopyState();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsImapMailCopyState, NS_IMAPMAILCOPYSTATE_IID)
+
+// ACLs for this folder.
+// Generally, we will try to always query this class when performing
+// an operation on the folder.
+// If the server doesn't support ACLs, none of this data will be filled in.
+// Therefore, we can assume that if we look up ourselves and don't find
+// any info (and also look up "anyone") then we have full rights, that is, ACLs don't exist.
+class nsImapMailFolder;
+
+#define IMAP_ACL_READ_FLAG 0x0000001 /* SELECT, CHECK, FETCH, PARTIAL, SEARCH, COPY from folder */
+#define IMAP_ACL_STORE_SEEN_FLAG 0x0000002 /* STORE SEEN flag */
+#define IMAP_ACL_WRITE_FLAG 0x0000004 /* STORE flags other than SEEN and DELETED */
+#define IMAP_ACL_INSERT_FLAG 0x0000008 /* APPEND, COPY into folder */
+#define IMAP_ACL_POST_FLAG 0x0000010 /* Can I send mail to the submission address for folder? */
+#define IMAP_ACL_CREATE_SUBFOLDER_FLAG 0x0000020 /* Can I CREATE a subfolder of this folder? */
+#define IMAP_ACL_DELETE_FLAG 0x0000040 /* STORE DELETED flag */
+#define IMAP_ACL_ADMINISTER_FLAG 0x0000080 /* perform SETACL */
+#define IMAP_ACL_RETRIEVED_FLAG 0x0000100 /* ACL info for this folder has been initialized */
+#define IMAP_ACL_EXPUNGE_FLAG 0x0000200 // can EXPUNGE or do implicit EXPUNGE on CLOSE
+#define IMAP_ACL_DELETE_FOLDER 0x0000400 // can DELETE/RENAME folder
+
+class nsMsgIMAPFolderACL
+{
+public:
+ explicit nsMsgIMAPFolderACL(nsImapMailFolder *folder);
+ ~nsMsgIMAPFolderACL();
+
+ bool SetFolderRightsForUser(const nsACString& userName, const nsACString& rights);
+
+public:
+
+ // generic for any user, although we might not use them in
+ // DO NOT use these for looking up information about the currently authenticated user.
+ // (There are some different checks and defaults we do).
+ // Instead, use the functions below, GetICan....()
+ bool GetCanUserLookupFolder(const nsACString& userName); // Is folder visible to LIST/LSUB?
+ bool GetCanUserReadFolder(const nsACString& userName); // SELECT, CHECK, FETCH, PARTIAL, SEARCH, COPY from folder?
+ bool GetCanUserStoreSeenInFolder(const nsACString& userName); // STORE SEEN flag?
+ bool GetCanUserWriteFolder(const nsACString& userName); // STORE flags other than SEEN and DELETED?
+ bool GetCanUserInsertInFolder(const nsACString& userName); // APPEND, COPY into folder?
+ bool GetCanUserPostToFolder(const nsACString& userName); // Can I send mail to the submission address for folder?
+ bool GetCanUserCreateSubfolder(const nsACString& userName); // Can I CREATE a subfolder of this folder?
+ bool GetCanUserDeleteInFolder(const nsACString& userName); // STORE DELETED flag, perform EXPUNGE?
+ bool GetCanUserAdministerFolder(const nsACString& userName); // perform SETACL?
+
+ // Functions to find out rights for the currently authenticated user.
+
+ bool GetCanILookupFolder(); // Is folder visible to LIST/LSUB?
+ bool GetCanIReadFolder(); // SELECT, CHECK, FETCH, PARTIAL, SEARCH, COPY from folder?
+ bool GetCanIStoreSeenInFolder(); // STORE SEEN flag?
+ bool GetCanIWriteFolder(); // STORE flags other than SEEN and DELETED?
+ bool GetCanIInsertInFolder(); // APPEND, COPY into folder?
+ bool GetCanIPostToFolder(); // Can I send mail to the submission address for folder?
+ bool GetCanICreateSubfolder(); // Can I CREATE a subfolder of this folder?
+ bool GetCanIDeleteInFolder(); // STORE DELETED flag?
+ bool GetCanIAdministerFolder(); // perform SETACL?
+ bool GetCanIExpungeFolder(); // perform EXPUNGE?
+
+ bool GetDoIHaveFullRightsForFolder(); // Returns TRUE if I have full rights on this folder (all of the above return TRUE)
+
+ bool GetIsFolderShared(); // We use this to see if the ACLs think a folder is shared or not.
+ // We will define "Shared" in 5.0 to mean:
+ // At least one user other than the currently authenticated user has at least one
+ // explicitly-listed ACL right on that folder.
+
+ // Returns a newly allocated string describing these rights
+ nsresult CreateACLRightsString(nsAString& rightsString);
+
+ nsresult GetRightsStringForUser(const nsACString& userName, nsCString &rights);
+
+ nsresult GetOtherUsers(nsIUTF8StringEnumerator** aResult);
+
+protected:
+ bool GetFlagSetInRightsForUser(const nsACString& userName, char flag, bool defaultIfNotFound);
+ void BuildInitialACLFromCache();
+ void UpdateACLCache();
+
+protected:
+ nsDataHashtable <nsCStringHashKey, nsCString> m_rightsHash; // Hash table, mapping username strings to rights strings.
+ nsImapMailFolder *m_folder;
+ int32_t m_aclCount;
+
+};
+
+/**
+ * Encapsulates parameters required to playback offline ops
+ * on given folder.
+ */
+struct nsPlaybackRequest
+{
+ explicit nsPlaybackRequest(nsImapMailFolder *srcFolder, nsIMsgWindow *msgWindow)
+ : SrcFolder(srcFolder), MsgWindow(msgWindow)
+ {
+ }
+ nsImapMailFolder *SrcFolder;
+ nsCOMPtr<nsIMsgWindow> MsgWindow;
+};
+
+class nsImapMailFolder : public nsMsgDBFolder,
+ public nsIMsgImapMailFolder,
+ public nsIImapMailFolderSink,
+ public nsIImapMessageSink,
+ public nsICopyMessageListener,
+ public nsIMsgFilterHitNotify
+{
+ static const uint32_t PLAYBACK_TIMER_INTERVAL_IN_MS = 500;
+public:
+ nsImapMailFolder();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIMsgFolder methods:
+ NS_IMETHOD GetSubFolders(nsISimpleEnumerator **aResult) override;
+
+ NS_IMETHOD GetMessages(nsISimpleEnumerator* *result) override;
+ NS_IMETHOD UpdateFolder(nsIMsgWindow *aWindow) override;
+
+ NS_IMETHOD CreateSubfolder(const nsAString& folderName,nsIMsgWindow *msgWindow ) override;
+ NS_IMETHOD AddSubfolder(const nsAString& aName, nsIMsgFolder** aChild) override;
+ NS_IMETHODIMP CreateStorageIfMissing(nsIUrlListener* urlListener) override;
+
+ NS_IMETHOD Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD CompactAll(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow,
+ bool aCompactOfflineAlso) override;
+ NS_IMETHOD EmptyTrash(nsIMsgWindow *msgWindow, nsIUrlListener *aListener) override;
+ NS_IMETHOD CopyDataToOutputStreamForAppend(nsIInputStream *aIStream,
+ int32_t aLength, nsIOutputStream *outputStream) override;
+ NS_IMETHOD CopyDataDone() override;
+ NS_IMETHOD Delete () override;
+ NS_IMETHOD Rename (const nsAString& newName, nsIMsgWindow *msgWindow) override;
+ NS_IMETHOD RenameSubFolders(nsIMsgWindow *msgWindow, nsIMsgFolder *oldFolder) override;
+ NS_IMETHOD GetNoSelect(bool *aResult) override;
+
+ NS_IMETHOD GetPrettyName(nsAString& prettyName) override; // Override of the base, for top-level mail folder
+
+ NS_IMETHOD GetFolderURL(nsACString& url) override;
+
+ NS_IMETHOD UpdateSummaryTotals(bool force) override;
+
+ NS_IMETHOD GetDeletable (bool *deletable) override;
+
+ NS_IMETHOD GetSizeOnDisk(int64_t *size) override;
+
+ NS_IMETHOD GetCanCreateSubfolders(bool *aResult) override;
+ NS_IMETHOD GetCanSubscribe(bool *aResult) override;
+
+ NS_IMETHOD ApplyRetentionSettings() override;
+
+ NS_IMETHOD AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag) override;
+ NS_IMETHOD MarkMessagesRead(nsIArray *messages, bool markRead) override;
+ NS_IMETHOD MarkAllMessagesRead(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD MarkMessagesFlagged(nsIArray *messages, bool markFlagged) override;
+ NS_IMETHOD MarkThreadRead(nsIMsgThread *thread) override;
+ NS_IMETHOD SetLabelForMessages(nsIArray *aMessages, nsMsgLabelValue aLabel) override;
+ NS_IMETHOD SetJunkScoreForMessages(nsIArray *aMessages, const nsACString& aJunkScore) override;
+ NS_IMETHOD DeleteSubFolders(nsIArray *folders, nsIMsgWindow *msgWindow) override;
+ NS_IMETHOD ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element) override;
+ NS_IMETHOD WriteToFolderCacheElem(nsIMsgFolderCacheElement *element) override;
+
+ NS_IMETHOD GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo,
+ nsIMsgDatabase **db) override;
+ NS_IMETHOD DeleteMessages(nsIArray *messages,
+ nsIMsgWindow *msgWindow, bool
+ deleteStorage, bool isMove,
+ nsIMsgCopyServiceListener* listener, bool allowUndo) override;
+ NS_IMETHOD CopyMessages(nsIMsgFolder *srcFolder,
+ nsIArray* messages,
+ bool isMove, nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener, bool isFolder,
+ bool allowUndo) override;
+ NS_IMETHOD CopyFolder(nsIMsgFolder *srcFolder, bool isMove, nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener) override;
+ NS_IMETHOD CopyFileMessage(nsIFile* file,
+ nsIMsgDBHdr* msgToReplace,
+ bool isDraftOrTemplate,
+ uint32_t aNewMsgFlags,
+ const nsACString &aNewMsgKeywords,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener) override;
+ NS_IMETHOD GetNewMessages(nsIMsgWindow *aWindow, nsIUrlListener *aListener) override;
+
+ NS_IMETHOD GetFilePath(nsIFile** aPathName) override;
+ NS_IMETHOD SetFilePath(nsIFile * aPath) override;
+
+ NS_IMETHOD Shutdown(bool shutdownChildren) override;
+
+ NS_IMETHOD DownloadMessagesForOffline(nsIArray *messages, nsIMsgWindow *msgWindow) override;
+
+ NS_IMETHOD DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow) override;
+ NS_IMETHOD GetCanFileMessages(bool *aCanFileMessages) override;
+ NS_IMETHOD GetCanDeleteMessages(bool *aCanDeleteMessages) override;
+ NS_IMETHOD FetchMsgPreviewText(nsMsgKey *aKeysToFetch, uint32_t aNumKeys,
+ bool aLocalOnly, nsIUrlListener *aUrlListener,
+ bool *aAsyncResults) override;
+
+ NS_IMETHOD AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords) override;
+ NS_IMETHOD RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords) override;
+
+ NS_IMETHOD NotifyCompactCompleted() override;
+
+ // overrides nsMsgDBFolder::HasMsgOffline()
+ NS_IMETHOD HasMsgOffline(nsMsgKey msgKey, bool *_retval) override;
+ // overrides nsMsgDBFolder::GetOfflineFileStream()
+ NS_IMETHOD GetOfflineFileStream(nsMsgKey msgKey, int64_t *offset, uint32_t *size, nsIInputStream **aFileStream) override;
+
+ NS_DECL_NSIMSGIMAPMAILFOLDER
+ NS_DECL_NSIIMAPMAILFOLDERSINK
+ NS_DECL_NSIIMAPMESSAGESINK
+ NS_DECL_NSICOPYMESSAGELISTENER
+
+ // nsIUrlListener methods
+ NS_IMETHOD OnStartRunningUrl(nsIURI * aUrl) override;
+ NS_IMETHOD OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode) override;
+
+ NS_DECL_NSIMSGFILTERHITNOTIFY
+ NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER
+
+ NS_IMETHOD IsCommandEnabled(const nsACString& command, bool *result) override;
+ NS_IMETHOD SetFilterList(nsIMsgFilterList *aMsgFilterList) override;
+ NS_IMETHOD GetCustomIdentity(nsIMsgIdentity **aIdentity) override;
+
+ /**
+ * This method is used to locate a folder where a msg could be present, not just
+ * the folder where the message first arrives, this method searches for the existence
+ * of msg in all the folders/labels that we retrieve from X-GM-LABELS also.
+ * overrides nsMsgDBFolder::GetOfflineMsgFolder()
+ * @param msgKey key of the msg for which we are trying to get the folder;
+ * @param aMsgFolder required folder;
+ */
+ NS_IMETHOD GetOfflineMsgFolder(nsMsgKey msgKey, nsIMsgFolder **aMsgFolder) override;
+
+ NS_IMETHOD GetIncomingServerType(nsACString& serverType) override;
+
+ nsresult AddSubfolderWithPath(nsAString& name, nsIFile *dbPath, nsIMsgFolder **child, bool brandNew = false);
+ nsresult MoveIncorporatedMessage(nsIMsgDBHdr *mailHdr,
+ nsIMsgDatabase *sourceDB,
+ const nsACString& destFolder,
+ nsIMsgFilter *filter,
+ nsIMsgWindow *msgWindow);
+
+ // send notification to copy service listener.
+ nsresult OnCopyCompleted(nsISupports *srcSupport, nsresult exitCode);
+
+ static nsresult AllocateUidStringFromKeys(nsMsgKey *keys, uint32_t numKeys, nsCString &msgIds);
+ static nsresult BuildIdsAndKeyArray(nsIArray* messages, nsCString& msgIds, nsTArray<nsMsgKey>& keyArray);
+
+ // these might end up as an nsIImapMailFolder attribute.
+ nsresult SetSupportedUserFlags(uint32_t userFlags);
+ nsresult GetSupportedUserFlags(uint32_t *userFlags);
+
+ // Find the start of a range of msgKeys that can hold srcCount headers.
+ nsresult FindOpenRange(nsMsgKey &fakeBase, uint32_t srcCount);
+
+protected:
+ virtual ~nsImapMailFolder();
+ // Helper methods
+
+ virtual nsresult CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder) override;
+ void FindKeysToAdd(const nsTArray<nsMsgKey> &existingKeys, nsTArray<nsMsgKey>
+ &keysToFetch, uint32_t &numNewUnread, nsIImapFlagAndUidState *flagState);
+ void FindKeysToDelete(const nsTArray<nsMsgKey> &existingKeys, nsTArray<nsMsgKey>
+ &keysToFetch, nsIImapFlagAndUidState *flagState, uint32_t boxFlags);
+ void PrepareToAddHeadersToMailDB(nsIImapProtocol* aProtocol);
+ void TweakHeaderFlags(nsIImapProtocol* aProtocol, nsIMsgDBHdr *tweakMe);
+
+ nsresult SyncFlags(nsIImapFlagAndUidState *flagState);
+ nsresult HandleCustomFlags(nsMsgKey uidOfMessage, nsIMsgDBHdr *dbHdr,
+ uint16_t userFlags, nsCString& keywords);
+ nsresult NotifyMessageFlagsFromHdr(nsIMsgDBHdr *dbHdr, nsMsgKey msgKey,
+ uint32_t flags);
+
+ nsresult SetupHeaderParseStream(uint32_t size, const nsACString& content_type, nsIMailboxSpec *boxSpec);
+ nsresult ParseAdoptedHeaderLine(const char *messageLine, nsMsgKey msgKey);
+ nsresult NormalEndHeaderParseStream(nsIImapProtocol *aProtocol, nsIImapUrl *imapUrl);
+
+ void EndOfflineDownload();
+
+ /**
+ * At the end of a file-to-folder copy operation, copy the file to the
+ * offline store and/or add to the message database, (if needed).
+ *
+ * @param srcFile file containing the message key
+ * @param msgKey key to use for the new messages
+ */
+ nsresult CopyFileToOfflineStore(nsIFile *srcFile, nsMsgKey msgKey);
+
+ nsresult MarkMessagesImapDeleted(nsTArray<nsMsgKey> *keyArray, bool deleted, nsIMsgDatabase *db);
+
+ // Notifies imap autosync that it should update this folder when it
+ // gets a chance.
+ void NotifyHasPendingMsgs();
+ void UpdatePendingCounts();
+ void SetIMAPDeletedFlag(nsIMsgDatabase *mailDB, const nsTArray<nsMsgKey> &msgids, bool markDeleted);
+ virtual bool ShowDeletedMessages();
+ virtual bool DeleteIsMoveToTrash();
+ nsresult GetFolder(const nsACString& name, nsIMsgFolder **pFolder);
+ nsresult GetTrashFolder(nsIMsgFolder **pTrashFolder);
+ bool TrashOrDescendentOfTrash(nsIMsgFolder* folder);
+ static bool ShouldCheckAllFolders(nsIImapIncomingServer *imapServer);
+ nsresult GetServerKey(nsACString& serverKey);
+ nsresult DisplayStatusMsg(nsIImapUrl *aImapUrl, const nsAString& msg);
+
+ //nsresult RenameLocal(const char *newName);
+ nsresult AddDirectorySeparator(nsIFile *path);
+ nsresult CreateSubFolders(nsIFile *path);
+ nsresult GetDatabase() override;
+
+ nsresult GetFolderOwnerUserName(nsACString& userName);
+ nsIMAPNamespace *GetNamespaceForFolder();
+ void SetNamespaceForFolder(nsIMAPNamespace *ns);
+
+ nsMsgIMAPFolderACL * GetFolderACL();
+ nsresult CreateACLRightsStringForFolder(nsAString& rightsString);
+ nsresult GetBodysToDownload(nsTArray<nsMsgKey> *keysOfMessagesToDownload);
+ // Uber message copy service
+ nsresult CopyMessagesWithStream(nsIMsgFolder* srcFolder,
+ nsIArray* messages,
+ bool isMove,
+ bool isCrossServerOp,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener, bool allowUndo);
+ nsresult CopyStreamMessage(nsIMsgDBHdr* message, nsIMsgFolder* dstFolder,
+ nsIMsgWindow *msgWindow, bool isMove);
+ nsresult InitCopyState(nsISupports* srcSupport,
+ nsIArray* messages,
+ bool isMove,
+ bool selectedState,
+ bool acrossServers,
+ uint32_t newMsgFlags,
+ const nsACString &newMsgKeywords,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow *msgWindow,
+ bool allowUndo);
+ nsresult GetMoveCoalescer();
+ nsresult PlaybackCoalescedOperations();
+ virtual nsresult CreateBaseMessageURI(const nsACString& aURI) override;
+ // offline-ish methods
+ nsresult GetClearedOriginalOp(nsIMsgOfflineImapOperation *op, nsIMsgOfflineImapOperation **originalOp, nsIMsgDatabase **originalDB);
+ nsresult GetOriginalOp(nsIMsgOfflineImapOperation *op, nsIMsgOfflineImapOperation **originalOp, nsIMsgDatabase **originalDB);
+ nsresult CopyMessagesOffline(nsIMsgFolder* srcFolder,
+ nsIArray* messages,
+ bool isMove,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener);
+ void SetPendingAttributes(nsIArray* messages, bool aIsMove);
+
+ nsresult CopyOfflineMsgBody(nsIMsgFolder *srcFolder, nsIMsgDBHdr *destHdr,
+ nsIMsgDBHdr *origHdr, nsIInputStream *inputStream,
+ nsIOutputStream *outputStream);
+
+ void GetTrashFolderName(nsAString &aFolderName);
+ bool ShowPreviewText();
+
+ // Pseudo-Offline operation playback timer
+ static void PlaybackTimerCallback(nsITimer *aTimer, void *aClosure);
+
+ nsresult CreatePlaybackTimer();
+
+ // Allocate and initialize associated auto-sync state object.
+ void InitAutoSyncState();
+
+ bool m_initialized;
+ bool m_haveDiscoveredAllFolders;
+ nsCOMPtr<nsIMsgParseMailMsgState> m_msgParser;
+ nsCOMPtr<nsIMsgFilterList> m_filterList;
+ nsCOMPtr<nsIMsgFilterPlugin> m_filterPlugin; // XXX should be a list
+ // used with filter plugins to know when we've finished classifying and can playback moves
+ bool m_msgMovedByFilter;
+ nsImapMoveCoalescer *m_moveCoalescer; // strictly owned by the nsImapMailFolder
+ nsCOMPtr<nsIMutableArray> m_junkMessagesToMarkAsRead;
+ /// list of keys to be moved to the junk folder
+ nsTArray<nsMsgKey> mSpamKeysToMove;
+ /// the junk destination folder
+ nsCOMPtr<nsIMsgFolder> mSpamFolder;
+ nsMsgKey m_curMsgUid;
+ uint32_t m_uidValidity;
+
+ // These three vars are used to store counts from STATUS or SELECT command
+ // They include deleted messages, so they can differ from the generic
+ // folder total and unread counts.
+ int32_t m_numServerRecentMessages;
+ int32_t m_numServerUnseenMessages;
+ int32_t m_numServerTotalMessages;
+ // if server supports UIDNEXT, we store it here.
+ int32_t m_nextUID;
+
+ int32_t m_nextMessageByteLength;
+ nsCOMPtr<nsIUrlListener> m_urlListener;
+ bool m_urlRunning;
+
+ // undo move/copy transaction support
+ RefPtr<nsMsgTxn> m_pendingUndoTxn;
+ RefPtr<nsImapMailCopyState> m_copyState;
+ char m_hierarchyDelimiter;
+ int32_t m_boxFlags;
+ nsCString m_onlineFolderName;
+ nsCString m_ownerUserName; // username of the "other user," as in
+ // "Other Users' Mailboxes"
+
+ nsCString m_adminUrl; // url to run to set admin privileges for this folder
+ nsIMAPNamespace *m_namespace;
+ bool m_verifiedAsOnlineFolder;
+ bool m_explicitlyVerify; // whether or not we need to explicitly verify this through LIST
+ bool m_folderIsNamespace;
+ bool m_folderNeedsSubscribing;
+ bool m_folderNeedsAdded;
+ bool m_folderNeedsACLListed;
+ bool m_performingBiff;
+ bool m_folderQuotaCommandIssued;
+ bool m_folderQuotaDataIsValid;
+ bool m_updatingFolder;
+ // These two vars are used to keep track of compaction state so we can know
+ // when to send a done notification.
+ bool m_compactingOfflineStore;
+ bool m_expunging;
+ bool m_applyIncomingFilters; // apply filters to this folder, even if not the inbox
+ nsMsgIMAPFolderACL *m_folderACL;
+ uint32_t m_aclFlags;
+ uint32_t m_supportedUserFlags;
+
+ // determines if we are on GMail server
+ bool m_isGmailServer;
+ // offline imap support
+ bool m_downloadingFolderForOfflineUse;
+ bool m_filterListRequiresBody;
+
+ // auto-sync (automatic message download) support
+ RefPtr<nsAutoSyncState> m_autoSyncStateObj;
+
+ // Quota support
+ nsCString m_folderQuotaRoot;
+ uint32_t m_folderQuotaUsedKB;
+ uint32_t m_folderQuotaMaxKB;
+
+ // Pseudo-Offline Playback support
+ nsPlaybackRequest *m_pendingPlaybackReq;
+ nsCOMPtr<nsITimer> m_playbackTimer;
+ nsTArray<RefPtr<nsImapMoveCopyMsgTxn> > m_pendingOfflineMoves;
+ // hash table of mapping between messageids and message keys
+ // for pseudo hdrs.
+ nsDataHashtable<nsCStringHashKey, nsMsgKey> m_pseudoHdrs;
+
+ nsTArray<nsMsgKey> m_keysToFetch;
+ uint32_t m_totalKeysToFetch;
+
+ /**
+ * delete if appropriate local storage for messages in this folder
+ *
+ * @parm aMessages array (of nsIMsgDBHdr) of messages to delete
+ * (or an array of message keys)
+ * @parm aSrcFolder the folder containing the messages (optional)
+ */
+ void DeleteStoreMessages(nsIArray* aMessages);
+ void DeleteStoreMessages(nsTArray<nsMsgKey> &aMessages);
+ static void DeleteStoreMessages(nsTArray<nsMsgKey> &aMessages, nsIMsgFolder* aFolder);
+};
+#endif
diff --git a/mailnews/imap/src/nsImapOfflineSync.cpp b/mailnews/imap/src/nsImapOfflineSync.cpp
new file mode 100644
index 000000000..2d86ce1b5
--- /dev/null
+++ b/mailnews/imap/src/nsImapOfflineSync.cpp
@@ -0,0 +1,1290 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "netCore.h"
+#include "nsNetUtil.h"
+#include "nsImapOfflineSync.h"
+#include "nsImapMailFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIRDFService.h"
+#include "nsMsgBaseCID.h"
+#include "nsRDFCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgAccountManager.h"
+#include "nsINntpIncomingServer.h"
+#include "nsIRequestObserver.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsISeekableStream.h"
+#include "nsIMsgCopyService.h"
+#include "nsImapProtocol.h"
+#include "nsMsgUtils.h"
+#include "nsIMutableArray.h"
+#include "nsIAutoSyncManager.h"
+#include "nsArrayUtils.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+NS_IMPL_ISUPPORTS(nsImapOfflineSync, nsIUrlListener, nsIMsgCopyServiceListener, nsIDBChangeListener)
+
+nsImapOfflineSync::nsImapOfflineSync(nsIMsgWindow *window, nsIUrlListener *listener, nsIMsgFolder *singleFolderOnly, bool isPseudoOffline)
+{
+ m_singleFolderToUpdate = singleFolderOnly;
+ m_window = window;
+ // not the perfect place for this, but I think it will work.
+ if (m_window)
+ m_window->SetStopped(false);
+
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kFlagsChanged;
+ m_mailboxupdatesStarted = false;
+ m_mailboxupdatesFinished = false;
+ m_createdOfflineFolders = false;
+ m_pseudoOffline = isPseudoOffline;
+ m_KeyIndex = 0;
+ mCurrentUIDValidity = nsMsgKey_None;
+ m_listener = listener;
+}
+
+nsImapOfflineSync::~nsImapOfflineSync()
+{
+}
+
+void nsImapOfflineSync::SetWindow(nsIMsgWindow *window)
+{
+ m_window = window;
+}
+
+NS_IMETHODIMP nsImapOfflineSync::OnStartRunningUrl(nsIURI* url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapOfflineSync::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
+{
+ nsresult rv = exitCode;
+
+ // where do we make sure this gets cleared when we start running urls?
+ bool stopped = false;
+ if (m_window)
+ m_window->GetStopped(&stopped);
+
+ if (m_curTempFile)
+ {
+ m_curTempFile->Remove(false);
+ m_curTempFile = nullptr;
+ }
+ // NS_BINDING_ABORTED is used for the user pressing stop, which
+ // should cause us to abort the offline process. Other errors
+ // should allow us to continue.
+ if (stopped)
+ {
+ if (m_listener)
+ m_listener->OnStopRunningUrl(url, NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);
+
+ if (imapUrl)
+ nsImapProtocol::LogImapUrl(NS_SUCCEEDED(rv) ?
+ "offline imap url succeeded " :
+ "offline imap url failed ", imapUrl);
+
+ // If we succeeded, or it was an imap move/copy that timed out, clear the
+ // operation.
+ bool moveCopy = mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgCopy ||
+ mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgMoved;
+ if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_ERROR_IMAP_COMMAND_FAILED ||
+ (moveCopy && exitCode == NS_ERROR_NET_TIMEOUT))
+ {
+ ClearCurrentOps();
+ rv = ProcessNextOperation();
+ }
+ // else if it's a non-stop error, and we're doing multiple folders,
+ // go to the next folder.
+ else if (!m_singleFolderToUpdate)
+ {
+ if (AdvanceToNextFolder())
+ rv = ProcessNextOperation();
+ else if (m_listener)
+ m_listener->OnStopRunningUrl(url, rv);
+ }
+
+ return rv;
+}
+
+/**
+ * Leaves m_currentServer at the next imap or local mail "server" that
+ * might have offline events to playback. If no more servers,
+ * m_currentServer will be left at nullptr and the function returns false.
+ * Also, sets up m_serverEnumerator to enumerate over the server.
+ */
+bool nsImapOfflineSync::AdvanceToNextServer()
+{
+ nsresult rv = NS_OK;
+
+ if (!m_allServers)
+ {
+ NS_ASSERTION(!m_currentServer, "this shouldn't be set");
+ m_currentServer = nullptr;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ASSERTION(accountManager && NS_SUCCEEDED(rv), "couldn't get account mgr");
+ if (!accountManager || NS_FAILED(rv))
+ return false;
+
+ rv = accountManager->GetAllServers(getter_AddRefs(m_allServers));
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+ uint32_t serverIndex = 0;
+ if (m_currentServer)
+ {
+ rv = m_allServers->IndexOf(0, m_currentServer, &serverIndex);
+ if (NS_FAILED(rv))
+ serverIndex = -1;
+
+ // Move to the next server
+ ++serverIndex;
+ }
+ m_currentServer = nullptr;
+ uint32_t numServers;
+ m_allServers->GetLength(&numServers);
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+
+ while (serverIndex < numServers)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server(do_QueryElementAt(m_allServers, serverIndex));
+ serverIndex++;
+
+ nsCOMPtr<nsINntpIncomingServer> newsServer = do_QueryInterface(server);
+ if (newsServer) // news servers aren't involved in offline imap
+ continue;
+
+ if (server)
+ {
+ m_currentServer = server;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ {
+ rv = rootFolder->GetDescendants(getter_AddRefs(m_allFolders));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = m_allFolders->Enumerate(getter_AddRefs(m_serverEnumerator));
+ if (NS_SUCCEEDED(rv) && m_serverEnumerator)
+ {
+ bool hasMore = false;
+ rv = m_serverEnumerator->HasMoreElements(&hasMore);
+ if (NS_SUCCEEDED(rv) && hasMore)
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Sets m_currentFolder to the next folder to process.
+ *
+ * @return True if next folder to process was found, otherwise false.
+ */
+bool nsImapOfflineSync::AdvanceToNextFolder()
+{
+ // we always start by changing flags
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kFlagsChanged;
+
+ if (m_currentFolder)
+ {
+ m_currentFolder->SetMsgDatabase(nullptr);
+ m_currentFolder = nullptr;
+ }
+
+ bool hasMore = false;
+ if (m_currentServer)
+ m_serverEnumerator->HasMoreElements(&hasMore);
+ if (!hasMore)
+ hasMore = AdvanceToNextServer();
+
+ if (hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ nsresult rv = m_serverEnumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv))
+ m_currentFolder = do_QueryInterface(supports);
+ }
+ ClearDB();
+ return m_currentFolder;
+}
+
+void nsImapOfflineSync::AdvanceToFirstIMAPFolder()
+{
+ m_currentServer = nullptr;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
+ while (!imapFolder && AdvanceToNextFolder())
+ {
+ imapFolder = do_QueryInterface(m_currentFolder);
+ }
+}
+
+void nsImapOfflineSync::ProcessFlagOperation(nsIMsgOfflineImapOperation *op)
+{
+ nsCOMPtr <nsIMsgOfflineImapOperation> currentOp = op;
+ nsTArray<nsMsgKey> matchingFlagKeys;
+ uint32_t currentKeyIndex = m_KeyIndex;
+
+ imapMessageFlagsType matchingFlags;
+ currentOp->GetNewFlags(&matchingFlags);
+ imapMessageFlagsType flagOperation;
+ imapMessageFlagsType newFlags;
+ bool flagsMatch = true;
+ do
+ { // loop for all messsages with the same flags
+ if (flagsMatch)
+ {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ matchingFlagKeys.AppendElement(curKey);
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ }
+ currentOp = nullptr;
+ if (++currentKeyIndex < m_CurrentKeys.Length())
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex], false,
+ getter_AddRefs(currentOp));
+ if (currentOp)
+ {
+ currentOp->GetFlagOperation(&flagOperation);
+ currentOp->GetNewFlags(&newFlags);
+ }
+ flagsMatch = (flagOperation & nsIMsgOfflineImapOperation::kFlagsChanged)
+ && (newFlags == matchingFlags);
+ } while (currentOp);
+
+ if (!matchingFlagKeys.IsEmpty())
+ {
+ nsAutoCString uids;
+ nsImapMailFolder::AllocateUidStringFromKeys(matchingFlagKeys.Elements(), matchingFlagKeys.Length(), uids);
+ uint32_t curFolderFlags;
+ m_currentFolder->GetFlags(&curFolderFlags);
+
+ if (uids.get() && (curFolderFlags & nsMsgFolderFlags::ImapBox))
+ {
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_currentFolder);
+ nsCOMPtr <nsIURI> uriToSetFlags;
+ if (imapFolder)
+ {
+ rv = imapFolder->SetImapFlags(uids.get(), matchingFlags, getter_AddRefs(uriToSetFlags));
+ if (NS_SUCCEEDED(rv) && uriToSetFlags)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(uriToSetFlags);
+ if (mailnewsUrl)
+ mailnewsUrl->RegisterListener(this);
+ }
+ }
+ }
+ }
+ else
+ ProcessNextOperation();
+}
+
+void nsImapOfflineSync::ProcessKeywordOperation(nsIMsgOfflineImapOperation *op)
+{
+ nsCOMPtr <nsIMsgOfflineImapOperation> currentOp = op;
+ nsTArray<nsMsgKey> matchingKeywordKeys;
+ uint32_t currentKeyIndex = m_KeyIndex;
+
+ nsAutoCString keywords;
+ if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
+ currentOp->GetKeywordsToAdd(getter_Copies(keywords));
+ else
+ currentOp->GetKeywordsToRemove(getter_Copies(keywords));
+ bool keywordsMatch = true;
+ do
+ { // loop for all messsages with the same keywords
+ if (keywordsMatch)
+ {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ matchingKeywordKeys.AppendElement(curKey);
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ }
+ currentOp = nullptr;
+ if (++currentKeyIndex < m_CurrentKeys.Length())
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex], false,
+ getter_AddRefs(currentOp));
+ if (currentOp)
+ {
+ nsAutoCString curOpKeywords;
+ nsOfflineImapOperationType operation;
+ currentOp->GetOperation(&operation);
+ if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
+ currentOp->GetKeywordsToAdd(getter_Copies(curOpKeywords));
+ else
+ currentOp->GetKeywordsToRemove(getter_Copies(curOpKeywords));
+ keywordsMatch = (operation & mCurrentPlaybackOpType)
+ && (curOpKeywords.Equals(keywords));
+ }
+ } while (currentOp);
+
+ if (!matchingKeywordKeys.IsEmpty())
+ {
+ uint32_t curFolderFlags;
+ m_currentFolder->GetFlags(&curFolderFlags);
+
+ if (curFolderFlags & nsMsgFolderFlags::ImapBox)
+ {
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_currentFolder);
+ nsCOMPtr <nsIURI> uriToStoreCustomKeywords;
+ if (imapFolder)
+ {
+ rv = imapFolder->StoreCustomKeywords(m_window,
+ (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords) ? keywords : EmptyCString(),
+ (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kRemoveKeywords) ? keywords : EmptyCString(),
+ matchingKeywordKeys.Elements(),
+ matchingKeywordKeys.Length(), getter_AddRefs(uriToStoreCustomKeywords));
+ if (NS_SUCCEEDED(rv) && uriToStoreCustomKeywords)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(uriToStoreCustomKeywords);
+ if (mailnewsUrl)
+ mailnewsUrl->RegisterListener(this);
+ }
+ }
+ }
+ }
+ else
+ ProcessNextOperation();
+}
+
+void
+nsImapOfflineSync::ProcessAppendMsgOperation(nsIMsgOfflineImapOperation *currentOp, int32_t opType)
+{
+ nsCOMPtr <nsIMsgDBHdr> mailHdr;
+ nsMsgKey msgKey;
+ currentOp->GetMessageKey(&msgKey);
+ nsresult rv = m_currentDB->GetMsgHdrForKey(msgKey, getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr)
+ {
+ uint64_t messageOffset;
+ uint32_t messageSize;
+ mailHdr->GetMessageOffset(&messageOffset);
+ mailHdr->GetOfflineMessageSize(&messageSize);
+ nsCOMPtr<nsIFile> tmpFile;
+
+ if (NS_FAILED(GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "nscpmsg.txt",
+ getter_AddRefs(tmpFile))))
+ return;
+
+ if (NS_FAILED(tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600)))
+ return;
+
+ nsCOMPtr <nsIOutputStream> outputStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), tmpFile, PR_WRONLY | PR_CREATE_FILE, 00600);
+ if (NS_SUCCEEDED(rv) && outputStream)
+ {
+ nsCString moveDestination;
+ currentOp->GetDestinationFolderURI(getter_Copies(moveDestination));
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ nsCOMPtr<nsIRDFResource> res;
+ if (NS_FAILED(rv)) return ; // ### return error code.
+ rv = rdf->GetResource(moveDestination, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> destFolder(do_QueryInterface(res, &rv));
+ if (NS_SUCCEEDED(rv) && destFolder)
+ {
+ nsCOMPtr <nsIInputStream> offlineStoreInputStream;
+ bool reusable;
+ rv = destFolder->GetMsgInputStream(
+ mailHdr, &reusable, getter_AddRefs(offlineStoreInputStream));
+ if (NS_SUCCEEDED(rv) && offlineStoreInputStream)
+ {
+ nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(offlineStoreInputStream);
+ NS_ASSERTION(seekStream, "non seekable stream - can't read from offline msg");
+ if (seekStream)
+ {
+ rv = seekStream->Seek(PR_SEEK_SET, messageOffset);
+ if (NS_SUCCEEDED(rv))
+ {
+ // now, copy the dest folder offline store msg to the temp file
+ int32_t inputBufferSize = FILE_IO_BUFFER_SIZE;
+ char *inputBuffer = (char *) PR_Malloc(inputBufferSize);
+
+ int32_t bytesLeft;
+ uint32_t bytesRead, bytesWritten;
+ bytesLeft = messageSize;
+ rv = inputBuffer ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ while (bytesLeft > 0 && NS_SUCCEEDED(rv))
+ {
+ int32_t bytesToRead = std::min(inputBufferSize, bytesLeft);
+ rv = offlineStoreInputStream->Read(inputBuffer, bytesToRead, &bytesRead);
+ if (NS_SUCCEEDED(rv) && bytesRead > 0)
+ {
+ rv = outputStream->Write(inputBuffer, bytesRead, &bytesWritten);
+ NS_ASSERTION(bytesWritten == bytesRead, "wrote out incorrect number of bytes");
+ }
+ else
+ break;
+ bytesLeft -= bytesRead;
+ }
+ PR_FREEIF(inputBuffer);
+
+ outputStream->Flush();
+ outputStream->Close();
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIFile> cloneTmpFile;
+ // clone the tmp file to defeat nsIFile's stat/size caching.
+ tmpFile->Clone(getter_AddRefs(cloneTmpFile));
+ m_curTempFile = do_QueryInterface(cloneTmpFile);
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID);
+ if (copyService)
+ rv = copyService->CopyFileMessage(cloneTmpFile, destFolder,
+ /* nsIMsgDBHdr* msgToReplace */ nullptr,
+ true /* isDraftOrTemplate */,
+ 0, // new msg flags - are there interesting flags here?
+ EmptyCString(), /* are there keywords we should get? */
+ this,
+ m_window);
+ }
+ else
+ tmpFile->Remove(false);
+ }
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ m_currentDB->DeleteHeader(mailHdr, nullptr, true, true);
+ }
+ }
+ // want to close in failure case too
+ outputStream->Close();
+ }
+ }
+ }
+ }
+ else
+ {
+ m_currentDB->RemoveOfflineOp(currentOp);
+ ProcessNextOperation();
+ }
+}
+
+void nsImapOfflineSync::ClearCurrentOps()
+{
+ int32_t opCount = m_currentOpsToClear.Count();
+ for (int32_t i = opCount - 1; i >= 0; i--)
+ {
+ m_currentOpsToClear[i]->SetPlayingBack(false);
+ m_currentOpsToClear[i]->ClearOperation(mCurrentPlaybackOpType);
+ m_currentOpsToClear.RemoveObjectAt(i);
+ }
+}
+
+void nsImapOfflineSync::ProcessMoveOperation(nsIMsgOfflineImapOperation *op)
+{
+ nsTArray<nsMsgKey> matchingFlagKeys;
+ uint32_t currentKeyIndex = m_KeyIndex;
+ nsCString moveDestination;
+ op->GetDestinationFolderURI(getter_Copies(moveDestination));
+ bool moveMatches = true;
+ nsCOMPtr <nsIMsgOfflineImapOperation> currentOp = op;
+ do
+ { // loop for all messsages with the same destination
+ if (moveMatches)
+ {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ matchingFlagKeys.AppendElement(curKey);
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ }
+ currentOp = nullptr;
+
+ if (++currentKeyIndex < m_CurrentKeys.Length())
+ {
+ nsCString nextDestination;
+ nsresult rv = m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex], false, getter_AddRefs(currentOp));
+ moveMatches = false;
+ if (NS_SUCCEEDED(rv) && currentOp)
+ {
+ nsOfflineImapOperationType opType;
+ currentOp->GetOperation(&opType);
+ if (opType & nsIMsgOfflineImapOperation::kMsgMoved)
+ {
+ currentOp->GetDestinationFolderURI(getter_Copies(nextDestination));
+ moveMatches = moveDestination.Equals(nextDestination);
+ }
+ }
+ }
+ }
+ while (currentOp);
+
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ GetExistingFolder(moveDestination, getter_AddRefs(destFolder));
+ // if the dest folder doesn't really exist, these operations are
+ // going to fail, so clear them out and move on.
+ if (!destFolder)
+ {
+ NS_ERROR("trying to playing back move to non-existent folder");
+ ClearCurrentOps();
+ ProcessNextOperation();
+ return;
+ }
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_currentFolder);
+ if (imapFolder && DestFolderOnSameServer(destFolder))
+ {
+ imapFolder->ReplayOfflineMoveCopy(matchingFlagKeys.Elements(), matchingFlagKeys.Length(), true, destFolder,
+ this, m_window);
+ }
+ else
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ for (uint32_t keyIndex = 0; keyIndex < matchingFlagKeys.Length(); keyIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> mailHdr = nullptr;
+ rv = m_currentFolder->GetMessageHeader(matchingFlagKeys.ElementAt(keyIndex), getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr)
+ {
+ uint32_t msgSize;
+ // in case of a move, the header has already been deleted,
+ // so we've really got a fake header. We need to get its flags and
+ // size from the offline op to have any chance of doing the move.
+ mailHdr->GetMessageSize(&msgSize);
+ if (!msgSize)
+ {
+ imapMessageFlagsType newImapFlags;
+ uint32_t msgFlags = 0;
+ op->GetMsgSize(&msgSize);
+ op->GetNewFlags(&newImapFlags);
+ // first three bits are the same
+ msgFlags |= (newImapFlags & 0x07);
+ if (newImapFlags & kImapMsgForwardedFlag)
+ msgFlags |= nsMsgMessageFlags::Forwarded;
+ mailHdr->SetFlags(msgFlags);
+ mailHdr->SetMessageSize(msgSize);
+ }
+ messages->AppendElement(mailHdr, false);
+ }
+ }
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ if (copyService)
+ copyService->CopyMessages(m_currentFolder, messages, destFolder, true, this, m_window, false);
+ }
+ }
+}
+
+// I'm tempted to make this a method on nsIMsgFolder, but that interface
+// is already so huge, and there are only a few places in the code that do this.
+// If there end up to be more places that need this, then we can reconsider.
+bool nsImapOfflineSync::DestFolderOnSameServer(nsIMsgFolder *destFolder)
+{
+ nsCOMPtr<nsIMsgIncomingServer> srcServer;
+ nsCOMPtr<nsIMsgIncomingServer> dstServer;
+
+ bool sameServer = false;
+ if (NS_SUCCEEDED(m_currentFolder->GetServer(getter_AddRefs(srcServer)))
+ && NS_SUCCEEDED(destFolder->GetServer(getter_AddRefs(dstServer))))
+ dstServer->Equals(srcServer, &sameServer);
+ return sameServer;
+}
+
+void nsImapOfflineSync::ProcessCopyOperation(nsIMsgOfflineImapOperation *aCurrentOp)
+{
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp = aCurrentOp;
+
+ nsTArray<nsMsgKey> matchingFlagKeys;
+ uint32_t currentKeyIndex = m_KeyIndex;
+ nsCString copyDestination;
+ currentOp->GetCopyDestination(0, getter_Copies(copyDestination));
+ bool copyMatches = true;
+ nsresult rv;
+
+ do { // loop for all messsages with the same destination
+ if (copyMatches)
+ {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ matchingFlagKeys.AppendElement(curKey);
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ }
+ currentOp = nullptr;
+
+ if (++currentKeyIndex < m_CurrentKeys.Length())
+ {
+ nsCString nextDestination;
+ rv = m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex],
+ false, getter_AddRefs(currentOp));
+ copyMatches = false;
+ if (NS_SUCCEEDED(rv) && currentOp)
+ {
+ nsOfflineImapOperationType opType;
+ currentOp->GetOperation(&opType);
+ if (opType & nsIMsgOfflineImapOperation::kMsgCopy)
+ {
+ currentOp->GetCopyDestination(0, getter_Copies(nextDestination));
+ copyMatches = copyDestination.Equals(nextDestination);
+ }
+ }
+ }
+ }
+ while (currentOp);
+
+ nsAutoCString uids;
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ GetExistingFolder(copyDestination, getter_AddRefs(destFolder));
+ // if the dest folder doesn't really exist, these operations are
+ // going to fail, so clear them out and move on.
+ if (!destFolder)
+ {
+ NS_ERROR("trying to playing back copy to non-existent folder");
+ ClearCurrentOps();
+ ProcessNextOperation();
+ return;
+ }
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_currentFolder);
+ if (imapFolder && DestFolderOnSameServer(destFolder))
+ {
+ rv = imapFolder->ReplayOfflineMoveCopy(matchingFlagKeys.Elements(), matchingFlagKeys.Length(), false, destFolder,
+ this, m_window);
+ }
+ else
+ {
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (messages && NS_SUCCEEDED(rv))
+ {
+ for (uint32_t keyIndex = 0; keyIndex < matchingFlagKeys.Length(); keyIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> mailHdr = nullptr;
+ rv = m_currentFolder->GetMessageHeader(matchingFlagKeys.ElementAt(keyIndex), getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr)
+ messages->AppendElement(mailHdr, false);
+ }
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ if (copyService)
+ copyService->CopyMessages(m_currentFolder, messages, destFolder, false, this, m_window, false);
+ }
+ }
+}
+
+void nsImapOfflineSync::ProcessEmptyTrash()
+{
+ m_currentFolder->EmptyTrash(m_window, this);
+ ClearDB(); // EmptyTrash closes and deletes the trash db.
+}
+
+// returns true if we found a folder to create, false if we're done creating folders.
+bool nsImapOfflineSync::CreateOfflineFolders()
+{
+ while (m_currentFolder)
+ {
+ uint32_t flags;
+ m_currentFolder->GetFlags(&flags);
+ bool offlineCreate = (flags & nsMsgFolderFlags::CreatedOffline) != 0;
+ if (offlineCreate)
+ {
+ if (CreateOfflineFolder(m_currentFolder))
+ return true;
+ }
+ AdvanceToNextFolder();
+ }
+ return false;
+}
+
+bool nsImapOfflineSync::CreateOfflineFolder(nsIMsgFolder *folder)
+{
+ nsCOMPtr<nsIMsgFolder> parent;
+ folder->GetParent(getter_AddRefs(parent));
+
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(parent);
+ nsCOMPtr <nsIURI> createFolderURI;
+ nsCString onlineName;
+ imapFolder->GetOnlineName(onlineName);
+
+ NS_ConvertASCIItoUTF16 folderName(onlineName);
+ nsresult rv = imapFolder->PlaybackOfflineFolderCreate(folderName, nullptr, getter_AddRefs(createFolderURI));
+ if (createFolderURI && NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(createFolderURI);
+ if (mailnewsUrl)
+ mailnewsUrl->RegisterListener(this);
+ }
+ return NS_SUCCEEDED(rv) ? true : false; // this is asynch, we have to return and be called again by the OfflineOpExitFunction
+}
+
+int32_t nsImapOfflineSync::GetCurrentUIDValidity()
+{
+ if (m_currentFolder)
+ {
+ nsCOMPtr <nsIImapMailFolderSink> imapFolderSink = do_QueryInterface(m_currentFolder);
+ if (imapFolderSink)
+ imapFolderSink->GetUidValidity(&mCurrentUIDValidity);
+ }
+ return mCurrentUIDValidity;
+}
+
+/**
+ * Playing back offline operations is one giant state machine that runs through
+ * ProcessNextOperation.
+ * The first state is creating online any folders created offline (we do this
+ * first, so we can play back any operations in them in the next pass)
+ */
+nsresult nsImapOfflineSync::ProcessNextOperation()
+{
+ nsresult rv = NS_OK;
+
+ // if we haven't created offline folders, and we're updating all folders,
+ // first, find offline folders to create.
+ if (!m_createdOfflineFolders)
+ {
+ if (m_singleFolderToUpdate)
+ {
+ if (!m_pseudoOffline)
+ {
+ AdvanceToFirstIMAPFolder();
+ if (CreateOfflineFolders())
+ return NS_OK;
+ }
+ }
+ else
+ {
+ if (CreateOfflineFolders())
+ return NS_OK;
+ m_currentServer = nullptr;
+ AdvanceToNextFolder();
+ }
+ m_createdOfflineFolders = true;
+ }
+ // if updating one folder only, restore m_currentFolder to that folder
+ if (m_singleFolderToUpdate)
+ m_currentFolder = m_singleFolderToUpdate;
+
+ uint32_t folderFlags;
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ while (m_currentFolder && !m_currentDB)
+ {
+ m_currentFolder->GetFlags(&folderFlags);
+ // need to check if folder has offline events, /* or is configured for offline */
+ // shouldn't need to check if configured for offline use, since any folder with
+ // events should have nsMsgFolderFlags::OfflineEvents set.
+ if (folderFlags & (nsMsgFolderFlags::OfflineEvents /* | nsMsgFolderFlags::Offline */))
+ {
+ m_currentFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(m_currentDB));
+ if (m_currentDB)
+ m_currentDB->AddListener(this);
+ }
+
+ if (m_currentDB)
+ {
+ m_CurrentKeys.Clear();
+ m_KeyIndex = 0;
+ if (NS_FAILED(m_currentDB->ListAllOfflineOpIds(&m_CurrentKeys)) || m_CurrentKeys.IsEmpty())
+ {
+ ClearDB();
+ folderInfo = nullptr; // can't hold onto folderInfo longer than db
+ m_currentFolder->ClearFlag(nsMsgFolderFlags::OfflineEvents);
+ }
+ else
+ {
+ // trash any ghost msgs
+ bool deletedGhostMsgs = false;
+ for (uint32_t fakeIndex=0; fakeIndex < m_CurrentKeys.Length(); fakeIndex++)
+ {
+ nsCOMPtr <nsIMsgOfflineImapOperation> currentOp;
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[fakeIndex], false, getter_AddRefs(currentOp));
+ if (currentOp)
+ {
+ nsOfflineImapOperationType opType;
+ currentOp->GetOperation(&opType);
+
+ if (opType == nsIMsgOfflineImapOperation::kMoveResult)
+ {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ m_currentDB->RemoveOfflineOp(currentOp);
+ deletedGhostMsgs = true;
+
+ // Remember the pseudo headers before we delete them,
+ // and when we download new headers, tell listeners about the
+ // message key change between the pseudo headers and the real
+ // downloaded headers. Note that we're not currently sending
+ // a msgsDeleted notifcation for these headers, but the
+ // db listeners are notified about the deletion.
+ // for imap folders, we should adjust the pending counts, because we
+ // have a header that we know about, but don't have in the db.
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_currentFolder);
+ if (imapFolder)
+ {
+ bool hdrIsRead;
+ m_currentDB->IsRead(curKey, &hdrIsRead);
+ imapFolder->ChangePendingTotal(1);
+ if (!hdrIsRead)
+ imapFolder->ChangePendingUnread(1);
+ imapFolder->AddMoveResultPseudoKey(curKey);
+ }
+ m_currentDB->DeleteMessage(curKey, nullptr, false);
+ }
+ }
+ }
+
+ if (deletedGhostMsgs)
+ m_currentFolder->SummaryChanged();
+
+ m_CurrentKeys.Clear();
+ if (NS_FAILED(m_currentDB->ListAllOfflineOpIds(&m_CurrentKeys)) || m_CurrentKeys.IsEmpty())
+ {
+ ClearDB();
+ }
+ else if (folderFlags & nsMsgFolderFlags::ImapBox)
+ {
+ // if pseudo offline, falls through to playing ops back.
+ if (!m_pseudoOffline)
+ {
+ // there are operations to playback so check uid validity
+ SetCurrentUIDValidity(0); // force initial invalid state
+ // do a lite select here and hook ourselves up as a listener.
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_currentFolder, &rv);
+ if (imapFolder)
+ rv = imapFolder->LiteSelect(this, m_window);
+ // this is async, we will be called again by OnStopRunningUrl.
+ return rv;
+ }
+ }
+ }
+ }
+
+ if (!m_currentDB)
+ {
+ // only advance if we are doing all folders
+ if (!m_singleFolderToUpdate)
+ AdvanceToNextFolder();
+ else
+ m_currentFolder = nullptr; // force update of this folder now.
+ }
+
+ }
+
+ if (m_currentFolder)
+ m_currentFolder->GetFlags(&folderFlags);
+ // do the current operation
+ if (m_currentDB)
+ {
+ bool currentFolderFinished = false;
+ if (!folderInfo)
+ m_currentDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ // user canceled the lite select! if GetCurrentUIDValidity() == 0
+ if (folderInfo && (m_KeyIndex < m_CurrentKeys.Length()) &&
+ (m_pseudoOffline || (GetCurrentUIDValidity() != 0) ||
+ !(folderFlags & nsMsgFolderFlags::ImapBox)))
+ {
+ int32_t curFolderUidValidity;
+ folderInfo->GetImapUidValidity(&curFolderUidValidity);
+ bool uidvalidityChanged = (!m_pseudoOffline && folderFlags & nsMsgFolderFlags::ImapBox) && (GetCurrentUIDValidity() != curFolderUidValidity);
+ nsCOMPtr <nsIMsgOfflineImapOperation> currentOp;
+ if (uidvalidityChanged)
+ DeleteAllOfflineOpsForCurrentDB();
+ else
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false, getter_AddRefs(currentOp));
+
+ if (currentOp)
+ {
+ nsOfflineImapOperationType opType;
+ currentOp->GetOperation(&opType);
+ // loop until we find the next db record that matches the current playback operation
+ while (currentOp && !(opType & mCurrentPlaybackOpType))
+ {
+ // remove operations with no type.
+ if (!opType)
+ m_currentDB->RemoveOfflineOp(currentOp);
+ currentOp = nullptr;
+ ++m_KeyIndex;
+ if (m_KeyIndex < m_CurrentKeys.Length())
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex],
+ false, getter_AddRefs(currentOp));
+ if (currentOp)
+ currentOp->GetOperation(&opType);
+ }
+ // if we did not find a db record that matches the current playback operation,
+ // then move to the next playback operation and recurse.
+ if (!currentOp)
+ {
+ // we are done with the current type
+ if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kFlagsChanged)
+ {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kAddKeywords;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ }
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
+ {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kRemoveKeywords;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ }
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kRemoveKeywords)
+ {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kMsgCopy;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ }
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgCopy)
+ {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kMsgMoved;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ }
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgMoved)
+ {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kAppendDraft;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ }
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAppendDraft)
+ {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kAppendTemplate;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ }
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAppendTemplate)
+ {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kDeleteAllMsgs;
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ }
+ else
+ {
+ DeleteAllOfflineOpsForCurrentDB();
+ currentFolderFinished = true;
+ }
+
+ }
+ else
+ {
+ if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kFlagsChanged)
+ ProcessFlagOperation(currentOp);
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords
+ ||mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kRemoveKeywords)
+ ProcessKeywordOperation(currentOp);
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgCopy)
+ ProcessCopyOperation(currentOp);
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgMoved)
+ ProcessMoveOperation(currentOp);
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAppendDraft)
+ ProcessAppendMsgOperation(currentOp, nsIMsgOfflineImapOperation::kAppendDraft);
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAppendTemplate)
+ ProcessAppendMsgOperation(currentOp, nsIMsgOfflineImapOperation::kAppendTemplate);
+ else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kDeleteAllMsgs)
+ {
+ // empty trash is going to delete the db, so we'd better release the
+ // reference to the offline operation first.
+ currentOp = nullptr;
+ ProcessEmptyTrash();
+ }
+ else
+ NS_ERROR("invalid playback op type");
+ }
+ }
+ else
+ currentFolderFinished = true;
+ }
+ else
+ currentFolderFinished = true;
+
+ if (currentFolderFinished)
+ {
+ ClearDB();
+ if (!m_singleFolderToUpdate)
+ {
+ AdvanceToNextFolder();
+ ProcessNextOperation();
+ return NS_OK;
+ }
+ else
+ m_currentFolder = nullptr;
+ }
+ }
+
+ if (!m_currentFolder && !m_mailboxupdatesStarted)
+ {
+ m_mailboxupdatesStarted = true;
+
+ // if we are updating more than one folder then we need the iterator
+ if (!m_singleFolderToUpdate)
+ {
+ m_currentServer = nullptr;
+ AdvanceToNextFolder();
+ }
+ if (m_singleFolderToUpdate)
+ {
+ m_singleFolderToUpdate->ClearFlag(nsMsgFolderFlags::OfflineEvents);
+ m_singleFolderToUpdate->UpdateFolder(m_window);
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(m_singleFolderToUpdate));
+ if (imapFolder)
+ {
+ nsCOMPtr<nsIUrlListener> saveListener = m_listener;
+// m_listener = nullptr;
+// imapFolder->UpdateFolderWithListener(m_window, saveListener);
+ }
+ }
+ }
+ // if we get here, then I *think* we're done. Not sure, though.
+#ifdef DEBUG_bienvenu
+ printf("done with offline imap sync\n");
+#endif
+ nsCOMPtr <nsIUrlListener> saveListener = m_listener;
+ m_listener = nullptr;
+
+ if (saveListener)
+ saveListener->OnStopRunningUrl(nullptr /* don't know url */, rv);
+ return rv;
+}
+
+
+void nsImapOfflineSync::DeleteAllOfflineOpsForCurrentDB()
+{
+ m_KeyIndex = 0;
+ nsCOMPtr <nsIMsgOfflineImapOperation> currentOp;
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false, getter_AddRefs(currentOp));
+ while (currentOp)
+ {
+ // NS_ASSERTION(currentOp->GetOperationFlags() == 0);
+ // delete any ops that have already played back
+ m_currentDB->RemoveOfflineOp(currentOp);
+ currentOp = nullptr;
+
+ if (++m_KeyIndex < m_CurrentKeys.Length())
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false, getter_AddRefs(currentOp));
+ }
+ m_currentDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ // turn off nsMsgFolderFlags::OfflineEvents
+ if (m_currentFolder)
+ m_currentFolder->ClearFlag(nsMsgFolderFlags::OfflineEvents);
+}
+
+nsImapOfflineDownloader::nsImapOfflineDownloader(nsIMsgWindow *aMsgWindow, nsIUrlListener *aListener) : nsImapOfflineSync(aMsgWindow, aListener)
+{
+ // pause auto-sync service
+ nsresult rv;
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ autoSyncMgr->Pause();
+}
+
+nsImapOfflineDownloader::~nsImapOfflineDownloader()
+{
+}
+
+nsresult nsImapOfflineDownloader::ProcessNextOperation()
+{
+ nsresult rv = NS_OK;
+ if (!m_mailboxupdatesStarted)
+ {
+ m_mailboxupdatesStarted = true;
+ // Update the INBOX first so the updates on the remaining
+ // folders pickup the results of any filter moves.
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIArray> servers;
+ rv = accountManager->GetAllServers(getter_AddRefs(servers));
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (!m_mailboxupdatesFinished)
+ {
+ if (AdvanceToNextServer())
+ {
+ nsCOMPtr <nsIMsgFolder> rootMsgFolder;
+ m_currentServer->GetRootFolder(getter_AddRefs(rootMsgFolder));
+ nsCOMPtr<nsIMsgFolder> inbox;
+ if (rootMsgFolder)
+ {
+ rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(inbox));
+ if (inbox)
+ {
+ nsCOMPtr <nsIMsgFolder> offlineImapFolder;
+ nsCOMPtr <nsIMsgImapMailFolder> imapInbox = do_QueryInterface(inbox);
+ if (imapInbox)
+ {
+ rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Offline,
+ getter_AddRefs(offlineImapFolder));
+ if (!offlineImapFolder)
+ {
+ // no imap folders configured for offline use - check if the account is set up
+ // so that we always download inbox msg bodies for offline use
+ nsCOMPtr <nsIImapIncomingServer> imapServer = do_QueryInterface(m_currentServer);
+ if (imapServer)
+ {
+ bool downloadBodiesOnGetNewMail = false;
+ imapServer->GetDownloadBodiesOnGetNewMail(&downloadBodiesOnGetNewMail);
+ if (downloadBodiesOnGetNewMail)
+ offlineImapFolder = inbox;
+ }
+ }
+ }
+ // if this isn't an imap inbox, or we have an offline imap sub-folder, then update the inbox.
+ // otherwise, it's an imap inbox for an account with no folders configured for offline use,
+ // so just advance to the next server.
+ if (!imapInbox || offlineImapFolder)
+ {
+ // here we should check if this a pop3 server/inbox, and the user doesn't want
+ // to download pop3 mail for offline use.
+ if (!imapInbox)
+ {
+ }
+ rv = inbox->GetNewMessages(m_window, this);
+ if (NS_SUCCEEDED(rv))
+ return rv; // otherwise, fall through.
+ }
+ }
+ }
+ return ProcessNextOperation(); // recurse and do next server.
+ }
+ else
+ {
+ m_allServers = nullptr;
+ m_mailboxupdatesFinished = true;
+ }
+ }
+
+ while (AdvanceToNextFolder())
+ {
+ uint32_t folderFlags;
+
+ ClearDB();
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder;
+ if (m_currentFolder)
+ imapFolder = do_QueryInterface(m_currentFolder);
+ m_currentFolder->GetFlags(&folderFlags);
+ // need to check if folder has offline events, or is configured for offline
+ if (imapFolder && folderFlags & nsMsgFolderFlags::Offline &&
+ ! (folderFlags & nsMsgFolderFlags::Virtual))
+ {
+ rv = m_currentFolder->DownloadAllForOffline(this, m_window);
+ if (NS_SUCCEEDED(rv) || rv == NS_BINDING_ABORTED)
+ return rv;
+ // if this fails and the user didn't cancel/stop, fall through to code that advances to next folder
+ }
+ }
+ if (m_listener)
+ m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ return rv;
+}
+
+
+NS_IMETHODIMP nsImapOfflineSync::OnStartCopy()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
+NS_IMETHODIMP nsImapOfflineSync::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void SetMessageKey (in uint32_t aKey); */
+NS_IMETHODIMP nsImapOfflineSync::SetMessageKey(uint32_t aKey)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void GetMessageId (in nsCString aMessageId); */
+NS_IMETHODIMP nsImapOfflineSync::GetMessageId(nsACString& messageId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnStopCopy (in nsresult aStatus); */
+NS_IMETHODIMP nsImapOfflineSync::OnStopCopy(nsresult aStatus)
+{
+ return OnStopRunningUrl(nullptr, aStatus);
+}
+
+void nsImapOfflineSync::ClearDB()
+{
+ m_currentOpsToClear.Clear();
+ if (m_currentDB)
+ m_currentDB->RemoveListener(this);
+ m_currentDB = nullptr;
+}
+
+NS_IMETHODIMP
+nsImapOfflineSync::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange,
+ bool aPreChange, uint32_t *aStatus, nsIDBChangeListener * aInstigator)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsImapOfflineSync::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged,
+ uint32_t aOldFlags, uint32_t aNewFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapOfflineSync::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged,
+ nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapOfflineSync::OnHdrAdded(nsIMsgDBHdr *aHdrAdded,
+ nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void OnParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in nsMsgKey newParent, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP
+nsImapOfflineSync::OnParentChanged(nsMsgKey aKeyChanged,
+ nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void OnAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */
+NS_IMETHODIMP
+nsImapOfflineSync::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
+{
+ ClearDB();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapOfflineSync::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
+/* void OnReadChanged (in nsIDBChangeListener instigator); */
+NS_IMETHODIMP
+nsImapOfflineSync::OnReadChanged(nsIDBChangeListener *instigator)
+{
+ return NS_OK;
+}
+
+/* void OnJunkScoreChanged (in nsIDBChangeListener instigator); */
+NS_IMETHODIMP
+nsImapOfflineSync::OnJunkScoreChanged(nsIDBChangeListener *instigator)
+{
+ return NS_OK;
+}
+
diff --git a/mailnews/imap/src/nsImapOfflineSync.h b/mailnews/imap/src/nsImapOfflineSync.h
new file mode 100644
index 000000000..c339e8463
--- /dev/null
+++ b/mailnews/imap/src/nsImapOfflineSync.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsImapOfflineSync_H_
+#define _nsImapOfflineSync_H_
+
+
+#include "mozilla/Attributes.h"
+#include "nsIMsgDatabase.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgOfflineImapOperation.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgFolder.h"
+#include "nsCOMArray.h"
+#include "nsIDBChangeListener.h"
+
+class nsImapOfflineSync : public nsIUrlListener,
+ public nsIMsgCopyServiceListener,
+ public nsIDBChangeListener {
+public: // set to one folder to playback one folder only
+ nsImapOfflineSync(nsIMsgWindow *window, nsIUrlListener *listener,
+ nsIMsgFolder *singleFolderOnly = nullptr,
+ bool isPseudoOffline = false);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+ NS_DECL_NSIDBCHANGELISTENER
+ virtual nsresult ProcessNextOperation(); // this kicks off playback
+
+ int32_t GetCurrentUIDValidity();
+ void SetCurrentUIDValidity(int32_t uidvalidity) { mCurrentUIDValidity = uidvalidity; }
+
+ void SetPseudoOffline(bool pseudoOffline) {m_pseudoOffline = pseudoOffline;}
+ bool ProcessingStaleFolderUpdate() { return m_singleFolderToUpdate != nullptr; }
+
+ bool CreateOfflineFolder(nsIMsgFolder *folder);
+ void SetWindow(nsIMsgWindow *window);
+protected:
+ virtual ~nsImapOfflineSync();
+
+ bool CreateOfflineFolders();
+ bool DestFolderOnSameServer(nsIMsgFolder *destFolder);
+ bool AdvanceToNextServer();
+ bool AdvanceToNextFolder();
+ void AdvanceToFirstIMAPFolder();
+ void DeleteAllOfflineOpsForCurrentDB();
+ void ClearCurrentOps();
+ // Clears m_currentDB, and unregister listener.
+ void ClearDB();
+ void ProcessFlagOperation(nsIMsgOfflineImapOperation *currentOp);
+ void ProcessKeywordOperation(nsIMsgOfflineImapOperation *op);
+ void ProcessMoveOperation(nsIMsgOfflineImapOperation *currentOp);
+ void ProcessCopyOperation(nsIMsgOfflineImapOperation *currentOp);
+ void ProcessEmptyTrash();
+ void ProcessAppendMsgOperation(nsIMsgOfflineImapOperation *currentOp,
+ nsOfflineImapOperationType opType);
+
+ nsCOMPtr <nsIMsgFolder> m_currentFolder;
+ nsCOMPtr <nsIMsgFolder> m_singleFolderToUpdate;
+ nsCOMPtr <nsIMsgWindow> m_window;
+ nsCOMPtr <nsIArray> m_allServers;
+ nsCOMPtr <nsIArray> m_allFolders;
+ nsCOMPtr <nsIMsgIncomingServer> m_currentServer;
+ nsCOMPtr <nsISimpleEnumerator> m_serverEnumerator;
+ nsCOMPtr <nsIFile> m_curTempFile;
+
+ nsTArray<nsMsgKey> m_CurrentKeys;
+ nsCOMArray<nsIMsgOfflineImapOperation> m_currentOpsToClear;
+ uint32_t m_KeyIndex;
+ nsCOMPtr <nsIMsgDatabase> m_currentDB;
+ nsCOMPtr <nsIUrlListener> m_listener;
+ int32_t mCurrentUIDValidity;
+ int32_t mCurrentPlaybackOpType; // kFlagsChanged -> kMsgCopy -> kMsgMoved
+ bool m_mailboxupdatesStarted;
+ bool m_mailboxupdatesFinished;
+ bool m_pseudoOffline; // for queueing online events in offline db
+ bool m_createdOfflineFolders;
+
+};
+
+class nsImapOfflineDownloader : public nsImapOfflineSync
+{
+public:
+ nsImapOfflineDownloader(nsIMsgWindow *window, nsIUrlListener *listener);
+ virtual ~nsImapOfflineDownloader();
+ virtual nsresult ProcessNextOperation() override; // this kicks off download
+};
+
+#endif
diff --git a/mailnews/imap/src/nsImapProtocol.cpp b/mailnews/imap/src/nsImapProtocol.cpp
new file mode 100644
index 000000000..4cfa9dab2
--- /dev/null
+++ b/mailnews/imap/src/nsImapProtocol.cpp
@@ -0,0 +1,10093 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// as does this
+#include "msgCore.h" // for pre-compiled headers
+#include "nsMsgUtils.h"
+
+#include "nsIServiceManager.h"
+#include "nsICharsetConverterManager.h"
+#include "nsIStringBundle.h"
+#include "nsVersionComparator.h"
+
+#include "nsMsgImapCID.h"
+#include "nsThreadUtils.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsImapCore.h"
+#include "nsImapProtocol.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMAPHostSessionList.h"
+#include "nsIMAPBodyShell.h"
+#include "nsImapMailFolder.h"
+#include "nsIMsgAccountManager.h"
+#include "nsImapServerResponseParser.h"
+#include "nspr.h"
+#include "plbase64.h"
+#include "nsIImapService.h"
+#include "nsISocketTransportService.h"
+#include "nsIStreamListenerTee.h"
+#include "nsNetUtil.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIPipe.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgMessageFlags.h"
+#include "nsImapStringBundle.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsTextFormatter.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgI18N.h"
+// for the memory cache...
+#include "nsICacheEntry.h"
+#include "nsICacheStorage.h"
+#include "nsICacheEntryOpenCallback.h"
+
+#include "nsIPrompt.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsILoadInfo.h"
+#include "nsIMessengerWindowService.h"
+#include "nsIWindowMediator.h"
+#include "nsIWindowWatcher.h"
+#include "nsCOMPtr.h"
+#include "nsMimeTypes.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsIXULAppInfo.h"
+#include "nsSyncRunnableHelpers.h"
+
+PRLogModuleInfo *IMAP;
+
+// netlib required files
+#include "nsIStreamListener.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsImapUtils.h"
+#include "nsIStreamConverterService.h"
+#include "nsIProxyInfo.h"
+#include "nsISSLSocketControl.h"
+#include "nsProxyRelease.h"
+#include "nsDebug.h"
+#include "nsMsgCompressIStream.h"
+#include "nsMsgCompressOStream.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Attributes.h"
+#include "nsIPrincipal.h"
+#include "nsContentSecurityManager.h"
+
+using namespace mozilla;
+
+#define ONE_SECOND ((uint32_t)1000) // one second
+
+#define OUTPUT_BUFFER_SIZE (4096*2) // mscott - i should be able to remove this if I can use nsMsgLineBuffer???
+
+#define IMAP_ENV_HEADERS "From To Cc Bcc Subject Date Message-ID "
+#define IMAP_DB_HEADERS "Priority X-Priority References Newsgroups In-Reply-To Content-Type Reply-To"
+#define IMAP_ENV_AND_DB_HEADERS IMAP_ENV_HEADERS IMAP_DB_HEADERS
+static const PRIntervalTime kImapSleepTime = PR_MillisecondsToInterval(60000);
+static int32_t gPromoteNoopToCheckCount = 0;
+static const uint32_t kFlagChangesBeforeCheck = 10;
+static const int32_t kMaxSecondsBeforeCheck = 600;
+
+class AutoProxyReleaseMsgWindow
+{
+ public:
+ AutoProxyReleaseMsgWindow()
+ : mMsgWindow()
+ {}
+ ~AutoProxyReleaseMsgWindow()
+ {
+ NS_ReleaseOnMainThread(dont_AddRef(mMsgWindow));
+ }
+ nsIMsgWindow** StartAssignment()
+ {
+ MOZ_ASSERT(!mMsgWindow);
+ return &mMsgWindow;
+ }
+ operator nsIMsgWindow*()
+ {
+ return mMsgWindow;
+ }
+ private:
+ nsIMsgWindow* mMsgWindow;
+};
+
+nsIMsgWindow**
+getter_AddRefs(AutoProxyReleaseMsgWindow& aSmartPtr)
+{
+ return aSmartPtr.StartAssignment();
+}
+
+NS_IMPL_ISUPPORTS(nsMsgImapHdrXferInfo, nsIImapHeaderXferInfo)
+
+nsMsgImapHdrXferInfo::nsMsgImapHdrXferInfo()
+ : m_hdrInfos(kNumHdrsToXfer)
+{
+ m_nextFreeHdrInfo = 0;
+}
+
+nsMsgImapHdrXferInfo::~nsMsgImapHdrXferInfo()
+{
+}
+
+NS_IMETHODIMP nsMsgImapHdrXferInfo::GetNumHeaders(int32_t *aNumHeaders)
+{
+ *aNumHeaders = m_nextFreeHdrInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgImapHdrXferInfo::GetHeader(int32_t hdrIndex, nsIImapHeaderInfo **aResult)
+{
+ // If the header index is more than (or equal to) our next free pointer, then
+ // its a header we haven't really got and the caller has done something
+ // wrong.
+ NS_ENSURE_TRUE(hdrIndex < m_nextFreeHdrInfo, NS_ERROR_NULL_POINTER);
+
+ *aResult = m_hdrInfos.SafeObjectAt(hdrIndex);
+ if (!*aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+static const int32_t kInitLineHdrCacheSize = 512; // should be about right
+
+nsIImapHeaderInfo* nsMsgImapHdrXferInfo::StartNewHdr()
+{
+ if (m_nextFreeHdrInfo >= kNumHdrsToXfer)
+ return nullptr;
+
+ nsIImapHeaderInfo *result = m_hdrInfos.SafeObjectAt(m_nextFreeHdrInfo++);
+ if (result)
+ return result;
+
+ nsMsgImapLineDownloadCache *lineCache = new nsMsgImapLineDownloadCache();
+ if (!lineCache)
+ return nullptr;
+
+ lineCache->GrowBuffer(kInitLineHdrCacheSize);
+
+ m_hdrInfos.AppendObject(lineCache);
+
+ return lineCache;
+}
+
+// maybe not needed...
+void nsMsgImapHdrXferInfo::FinishCurrentHdr()
+{
+ // nothing to do?
+}
+
+void nsMsgImapHdrXferInfo::ResetAll()
+{
+ int32_t count = m_hdrInfos.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsIImapHeaderInfo *hdrInfo = m_hdrInfos[i];
+ if (hdrInfo)
+ hdrInfo->ResetCache();
+ }
+ m_nextFreeHdrInfo = 0;
+}
+
+void nsMsgImapHdrXferInfo::ReleaseAll()
+{
+ m_hdrInfos.Clear();
+ m_nextFreeHdrInfo = 0;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgImapLineDownloadCache, nsIImapHeaderInfo)
+
+// **** helper class for downloading line ****
+nsMsgImapLineDownloadCache::nsMsgImapLineDownloadCache()
+{
+ fLineInfo = (msg_line_info *) PR_CALLOC(sizeof( msg_line_info));
+ fLineInfo->uidOfMessage = nsMsgKey_None;
+ m_msgSize = 0;
+}
+
+nsMsgImapLineDownloadCache::~nsMsgImapLineDownloadCache()
+{
+ PR_Free( fLineInfo);
+}
+
+uint32_t nsMsgImapLineDownloadCache::CurrentUID()
+{
+ return fLineInfo->uidOfMessage;
+}
+
+uint32_t nsMsgImapLineDownloadCache::SpaceAvailable()
+{
+ return kDownLoadCacheSize - m_bufferPos;
+}
+
+msg_line_info *nsMsgImapLineDownloadCache::GetCurrentLineInfo()
+{
+ AppendBuffer("", 1); // null terminate the buffer
+ fLineInfo->adoptedMessageLine = GetBuffer();
+ return fLineInfo;
+}
+
+NS_IMETHODIMP nsMsgImapLineDownloadCache::ResetCache()
+{
+ ResetWritePos();
+ return NS_OK;
+}
+
+bool nsMsgImapLineDownloadCache::CacheEmpty()
+{
+ return m_bufferPos == 0;
+}
+
+NS_IMETHODIMP nsMsgImapLineDownloadCache::CacheLine(const char *line, uint32_t uid)
+{
+ NS_ASSERTION((PL_strlen(line) + 1) <= SpaceAvailable(),
+ "Oops... line length greater than space available");
+
+ fLineInfo->uidOfMessage = uid;
+
+ AppendString(line);
+ return NS_OK;
+}
+
+/* attribute nsMsgKey msgUid; */
+NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgUid(nsMsgKey *aMsgUid)
+{
+ *aMsgUid = fLineInfo->uidOfMessage;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgImapLineDownloadCache::SetMsgUid(nsMsgKey aMsgUid)
+{
+ fLineInfo->uidOfMessage = aMsgUid;
+ return NS_OK;
+}
+
+/* attribute long msgSize; */
+NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgSize(int32_t *aMsgSize)
+{
+ *aMsgSize = m_msgSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgImapLineDownloadCache::SetMsgSize(int32_t aMsgSize)
+{
+ m_msgSize = aMsgSize;
+ return NS_OK;
+}
+
+/* attribute string msgHdrs; */
+NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgHdrs(const char **aMsgHdrs)
+{
+ // this doesn't copy the string
+ AppendBuffer("", 1); // null terminate the buffer
+ *aMsgHdrs = GetBuffer();
+ return NS_OK;
+}
+
+/* the following macros actually implement addref, release and query interface for our component. */
+
+NS_IMPL_ADDREF_INHERITED(nsImapProtocol, nsMsgProtocol)
+NS_IMPL_RELEASE_INHERITED(nsImapProtocol, nsMsgProtocol )
+
+NS_INTERFACE_MAP_BEGIN(nsImapProtocol)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIImapProtocol)
+ NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsIImapProtocol)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIImapProtocolSink)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+static int32_t gTooFastTime = 2;
+static int32_t gIdealTime = 4;
+static int32_t gChunkAddSize = 16384;
+static int32_t gChunkSize = 250000;
+static int32_t gChunkThreshold = gChunkSize + gChunkSize/2;
+static bool gChunkSizeDirty = false;
+static bool gFetchByChunks = true;
+static bool gInitialized = false;
+static bool gHideUnusedNamespaces = true;
+static bool gHideOtherUsersFromList = false;
+static bool gUseEnvelopeCmd = false;
+static bool gUseLiteralPlus = true;
+static bool gExpungeAfterDelete = false;
+static bool gCheckDeletedBeforeExpunge = false; //bug 235004
+static int32_t gResponseTimeout = 60;
+static nsCString gForceSelectDetect;
+static nsTArray<nsCString> gForceSelectServersArray;
+
+// let delete model control expunging, i.e., don't ever expunge when the
+// user chooses the imap delete model, otherwise, expunge when over the
+// threshhold. This is the normal TB behavior.
+static const int32_t kAutoExpungeDeleteModel = 0;
+// Expunge whenever the folder is opened
+static const int32_t kAutoExpungeAlways = 1;
+// Expunge when over the threshhold, independent of the delete model.
+static const int32_t kAutoExpungeOnThreshold = 2;
+static int32_t gExpungeOption = kAutoExpungeDeleteModel;
+static int32_t gExpungeThreshold = 20;
+
+const int32_t kAppBufSize = 100;
+// can't use static nsCString because it shows up as a leak.
+static char gAppName[kAppBufSize];
+static char gAppVersion[kAppBufSize];
+
+nsresult nsImapProtocol::GlobalInitialization(nsIPrefBranch *aPrefBranch)
+{
+ gInitialized = true;
+
+ aPrefBranch->GetIntPref("mail.imap.chunk_fast", &gTooFastTime); // secs we read too little too fast
+ aPrefBranch->GetIntPref("mail.imap.chunk_ideal", &gIdealTime); // secs we read enough in good time
+ aPrefBranch->GetIntPref("mail.imap.chunk_add", &gChunkAddSize); // buffer size to add when wasting time
+ aPrefBranch->GetIntPref("mail.imap.chunk_size", &gChunkSize);
+ aPrefBranch->GetIntPref("mail.imap.min_chunk_size_threshold",
+ &gChunkThreshold);
+ aPrefBranch->GetBoolPref("mail.imap.hide_other_users",
+ &gHideOtherUsersFromList);
+ aPrefBranch->GetBoolPref("mail.imap.hide_unused_namespaces",
+ &gHideUnusedNamespaces);
+ aPrefBranch->GetIntPref("mail.imap.noop_check_count",
+ &gPromoteNoopToCheckCount);
+ aPrefBranch->GetBoolPref("mail.imap.use_envelope_cmd",
+ &gUseEnvelopeCmd);
+ aPrefBranch->GetBoolPref("mail.imap.use_literal_plus", &gUseLiteralPlus);
+ aPrefBranch->GetBoolPref("mail.imap.expunge_after_delete",
+ &gExpungeAfterDelete);
+ aPrefBranch->GetBoolPref("mail.imap.check_deleted_before_expunge",
+ &gCheckDeletedBeforeExpunge);
+ aPrefBranch->GetIntPref("mail.imap.expunge_option", &gExpungeOption);
+ aPrefBranch->GetIntPref("mail.imap.expunge_threshold_number",
+ &gExpungeThreshold);
+ aPrefBranch->GetIntPref("mailnews.tcptimeout", &gResponseTimeout);
+ aPrefBranch->GetCharPref("mail.imap.force_select_detect",
+ getter_Copies(gForceSelectDetect));
+ ParseString(gForceSelectDetect, ';', gForceSelectServersArray);
+
+ nsCOMPtr<nsIXULAppInfo> appInfo(do_GetService(XULAPPINFO_SERVICE_CONTRACTID));
+
+ if (appInfo)
+ {
+ nsCString appName, appVersion;
+ appInfo->GetName(appName);
+ appInfo->GetVersion(appVersion);
+ PL_strncpyz(gAppName, appName.get(), kAppBufSize);
+ PL_strncpyz(gAppVersion, appVersion.get(), kAppBufSize);
+ }
+ return NS_OK;
+}
+
+nsImapProtocol::nsImapProtocol() : nsMsgProtocol(nullptr),
+ m_dataAvailableMonitor("imapDataAvailable"),
+ m_urlReadyToRunMonitor("imapUrlReadyToRun"),
+ m_pseudoInterruptMonitor("imapPseudoInterrupt"),
+ m_dataMemberMonitor("imapDataMember"),
+ m_threadDeathMonitor("imapThreadDeath"),
+ m_waitForBodyIdsMonitor("imapWaitForBodyIds"),
+ m_fetchBodyListMonitor("imapFetchBodyList"),
+ m_passwordReadyMonitor("imapPasswordReady"),
+ mLock("nsImapProtocol.mLock"),
+ m_parser(*this)
+{
+ m_urlInProgress = false;
+ m_idle = false;
+ m_retryUrlOnError = false;
+ m_useIdle = true; // by default, use it
+ m_useCondStore = true;
+ m_useCompressDeflate = true;
+ m_ignoreExpunges = false;
+ m_prefAuthMethods = kCapabilityUndefined;
+ m_failedAuthMethods = 0;
+ m_currentAuthMethod = kCapabilityUndefined;
+ m_socketType = nsMsgSocketType::trySTARTTLS;
+ m_connectionStatus = NS_OK;
+ m_safeToCloseConnection = false;
+ m_hostSessionList = nullptr;
+ m_flagState = nullptr;
+ m_fetchBodyIdList = nullptr;
+ m_isGmailServer = false;
+ m_fetchingWholeMessage = false;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ NS_ASSERTION(prefBranch, "FAILED to create the preference service");
+
+ // read in the accept languages preference
+ if (prefBranch)
+ {
+ if (!gInitialized)
+ GlobalInitialization(prefBranch);
+
+ nsCOMPtr<nsIPrefLocalizedString> prefString;
+ prefBranch->GetComplexValue("intl.accept_languages",
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(prefString));
+ if (prefString)
+ prefString->ToString(getter_Copies(mAcceptLanguages));
+
+ nsCString customDBHeaders;
+ prefBranch->GetCharPref("mailnews.customDBHeaders",
+ getter_Copies(customDBHeaders));
+
+ ParseString(customDBHeaders, ' ', mCustomDBHeaders);
+ prefBranch->GetBoolPref("mailnews.display.prefer_plaintext", &m_preferPlainText);
+
+ nsAutoCString customHeaders;;
+ prefBranch->GetCharPref("mailnews.customHeaders",
+ getter_Copies(customHeaders));
+ customHeaders.StripWhitespace();
+ ParseString(customHeaders, ':', mCustomHeaders);
+ }
+
+ // ***** Thread support *****
+ m_thread = nullptr;
+ m_imapThreadIsRunning = false;
+ m_currentServerCommandTagNumber = 0;
+ m_active = false;
+ m_folderNeedsSubscribing = false;
+ m_folderNeedsACLRefreshed = false;
+ m_threadShouldDie = false;
+ m_inThreadShouldDie = false;
+ m_pseudoInterrupted = false;
+ m_nextUrlReadyToRun = false;
+ m_trackingTime = false;
+ m_curFetchSize = 0;
+ m_startTime = 0;
+ m_endTime = 0;
+ m_lastActiveTime = 0;
+ m_lastProgressTime = 0;
+ ResetProgressInfo();
+
+ m_tooFastTime = 0;
+ m_idealTime = 0;
+ m_chunkAddSize = 0;
+ m_chunkStartSize = 0;
+ m_fetchByChunks = true;
+ m_sendID = true;
+ m_chunkSize = 0;
+ m_chunkThreshold = 0;
+ m_fromHeaderSeen = false;
+ m_closeNeededBeforeSelect = false;
+ m_needNoop = false;
+ m_noopCount = 0;
+ m_fetchBodyListIsNew = false;
+ m_flagChangeCount = 0;
+ m_lastCheckTime = PR_Now();
+
+ m_checkForNewMailDownloadsHeaders = true; // this should be on by default
+ m_hierarchyNameState = kNoOperationInProgress;
+ m_discoveryStatus = eContinue;
+
+ m_overRideUrlConnectionInfo = false;
+ // m_dataOutputBuf is used by Send Data
+ m_dataOutputBuf = (char *) PR_CALLOC(sizeof(char) * OUTPUT_BUFFER_SIZE);
+ m_allocatedSize = OUTPUT_BUFFER_SIZE;
+
+ // used to buffer incoming data by ReadNextLine
+ m_inputStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true /* allocate new lines */, false /* leave CRLFs on the returned string */);
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
+ m_progressStringName.Truncate();
+
+ // since these are embedded in the nsImapProtocol object, but passed
+ // through proxied xpcom methods, just AddRef them here.
+ m_hdrDownloadCache = new nsMsgImapHdrXferInfo();
+ m_downloadLineCache = new nsMsgImapLineDownloadCache();
+
+ // subscription
+ m_autoSubscribe = true;
+ m_autoUnsubscribe = true;
+ m_autoSubscribeOnOpen = true;
+ m_deletableChildren = nullptr;
+
+ mFolderLastModSeq = 0;
+
+ Configure(gTooFastTime, gIdealTime, gChunkAddSize, gChunkSize,
+ gChunkThreshold, gFetchByChunks);
+ m_forceSelect = false;
+
+ // where should we do this? Perhaps in the factory object?
+ if (!IMAP)
+ IMAP = PR_NewLogModule("IMAP");
+}
+
+nsresult nsImapProtocol::Configure(int32_t TooFastTime, int32_t IdealTime,
+ int32_t ChunkAddSize, int32_t ChunkSize, int32_t ChunkThreshold,
+ bool FetchByChunks)
+{
+ m_tooFastTime = TooFastTime; // secs we read too little too fast
+ m_idealTime = IdealTime; // secs we read enough in good time
+ m_chunkAddSize = ChunkAddSize; // buffer size to add when wasting time
+ m_chunkStartSize = m_chunkSize = ChunkSize;
+ m_chunkThreshold = ChunkThreshold;
+ m_fetchByChunks = FetchByChunks;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsImapProtocol::Initialize(nsIImapHostSessionList *aHostSessionList,
+ nsIImapIncomingServer *aServer)
+{
+ NS_PRECONDITION(aHostSessionList && aServer,
+ "oops...trying to initialize with a null host session list or server!");
+ if (!aHostSessionList || !aServer)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = m_downloadLineCache->GrowBuffer(kDownLoadCacheSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_flagState = new nsImapFlagAndUidState(kImapFlagAndUidStateSize);
+ if (!m_flagState)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ aServer->GetUseIdle(&m_useIdle);
+ aServer->GetForceSelect(m_forceSelectValue);
+ aServer->GetUseCondStore(&m_useCondStore);
+ aServer->GetUseCompressDeflate(&m_useCompressDeflate);
+ NS_ADDREF(m_flagState);
+
+ m_hostSessionList = aHostSessionList;
+ m_parser.SetHostSessionList(aHostSessionList);
+ m_parser.SetFlagState(m_flagState);
+
+ // Initialize the empty mime part string on the main thread.
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = bundle->GetStringFromName(u"imapEmptyMimePart",
+ getter_Copies(m_emptyMimePartString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now initialize the thread for the connection
+ if (m_thread == nullptr)
+ {
+ nsresult rv = NS_NewThread(getter_AddRefs(m_iThread), this);
+ if (NS_FAILED(rv))
+ {
+ NS_ASSERTION(m_iThread, "Unable to create imap thread.\n");
+ return rv;
+ }
+ m_iThread->GetPRThread(&m_thread);
+
+ }
+ return NS_OK;
+}
+
+nsImapProtocol::~nsImapProtocol()
+{
+ PR_Free(m_fetchBodyIdList);
+
+ NS_IF_RELEASE(m_flagState);
+
+ PR_Free(m_dataOutputBuf);
+
+ // **** We must be out of the thread main loop function
+ NS_ASSERTION(!m_imapThreadIsRunning, "Oops, thread is still running.\n");
+}
+
+const nsCString&
+nsImapProtocol::GetImapHostName()
+{
+ if (m_runningUrl && m_hostName.IsEmpty())
+ {
+ nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningUrl);
+ url->GetAsciiHost(m_hostName);
+ }
+
+ return m_hostName;
+}
+
+const nsCString&
+nsImapProtocol::GetImapUserName()
+{
+ if (m_userName.IsEmpty() && m_imapServerSink)
+ {
+ m_imapServerSink->GetOriginalUsername(m_userName);
+ }
+ return m_userName;
+}
+
+const char*
+nsImapProtocol::GetImapServerKey()
+{
+ if (m_serverKey.IsEmpty() && m_imapServerSink)
+ {
+ m_imapServerSink->GetServerKey(m_serverKey);
+ }
+ return m_serverKey.get();
+}
+
+nsresult
+nsImapProtocol::SetupSinkProxy()
+{
+ nsresult res;
+ if (m_runningUrl)
+ {
+ if (!m_imapMailFolderSink)
+ {
+ nsCOMPtr<nsIImapMailFolderSink> aImapMailFolderSink;
+ (void) m_runningUrl->GetImapMailFolderSink(getter_AddRefs(aImapMailFolderSink));
+ if (aImapMailFolderSink)
+ {
+ m_imapMailFolderSink = new ImapMailFolderSinkProxy(aImapMailFolderSink);
+ }
+ }
+
+ if (!m_imapMessageSink)
+ {
+ nsCOMPtr<nsIImapMessageSink> aImapMessageSink;
+ (void) m_runningUrl->GetImapMessageSink(getter_AddRefs(aImapMessageSink));
+ if (aImapMessageSink) {
+ m_imapMessageSink = new ImapMessageSinkProxy(aImapMessageSink);
+ } else {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ if (!m_imapServerSink)
+ {
+ nsCOMPtr<nsIImapServerSink> aImapServerSink;
+ res = m_runningUrl->GetImapServerSink(getter_AddRefs(aImapServerSink));
+ if (aImapServerSink) {
+ m_imapServerSink = new ImapServerSinkProxy(aImapServerSink);
+ m_imapServerSinkLatest = m_imapServerSink;
+ } else {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ if (!m_imapProtocolSink)
+ {
+ nsCOMPtr<nsIImapProtocolSink> anImapProxyHelper(do_QueryInterface(NS_ISUPPORTS_CAST(nsIImapProtocolSink*, this), &res));
+ m_imapProtocolSink = new ImapProtocolSinkProxy(anImapProxyHelper);
+ }
+ }
+ return NS_OK;
+}
+
+static void SetSecurityCallbacksFromChannel(nsISocketTransport* aTrans, nsIChannel* aChannel)
+{
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ nsCOMPtr<nsIInterfaceRequestor> securityCallbacks;
+ MsgNewNotificationCallbacksAggregation(callbacks, loadGroup,
+ getter_AddRefs(securityCallbacks));
+ if (securityCallbacks)
+ aTrans->SetSecurityCallbacks(securityCallbacks);
+}
+
+// Setup With Url is intended to set up data which is held on a PER URL basis and not
+// a per connection basis. If you have data which is independent of the url we are currently
+// running, then you should put it in Initialize().
+// This is only ever called from the UI thread. It is called from LoadUrl, right
+// before the url gets run - i.e., the url is next in line to run.
+nsresult nsImapProtocol::SetupWithUrl(nsIURI * aURL, nsISupports* aConsumer)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ NS_PRECONDITION(aURL, "null URL passed into Imap Protocol");
+ if (aURL)
+ {
+ m_runningUrl = do_QueryInterface(aURL, &rv);
+ m_runningUrlLatest = m_runningUrl;
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(m_server);
+ if (!server)
+ {
+ rv = mailnewsUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_server = do_GetWeakReference(server);
+ }
+ nsCOMPtr <nsIMsgFolder> folder;
+ mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ mFolderLastModSeq = 0;
+ mFolderTotalMsgCount = 0;
+ mFolderHighestUID = 0;
+ m_uidValidity = kUidUnknown;
+ if (folder)
+ {
+ nsCOMPtr<nsIMsgDatabase> folderDB;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(folderDB));
+ if (folderInfo)
+ {
+ nsCString modSeqStr;
+ folderInfo->GetCharProperty(kModSeqPropertyName, modSeqStr);
+ mFolderLastModSeq = ParseUint64Str(modSeqStr.get());
+ folderInfo->GetNumMessages(&mFolderTotalMsgCount);
+ folderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0, &mFolderHighestUID);
+ folderInfo->GetImapUidValidity(&m_uidValidity);
+ }
+ }
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ nsCOMPtr<nsIStreamListener> aRealStreamListener = do_QueryInterface(aConsumer);
+ m_runningUrl->GetMockChannel(getter_AddRefs(m_mockChannel));
+ imapServer->GetIsGMailServer(&m_isGmailServer);
+ if (!m_mockChannel)
+ {
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // there are several imap operations that aren't initiated via a nsIChannel::AsyncOpen call on the mock channel.
+ // such as selecting a folder. nsImapProtocol now insists on a mock channel when processing a url.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ aURL,
+ nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ m_mockChannel = do_QueryInterface(channel);
+
+ // Certain imap operations (not initiated by the IO Service via AsyncOpen) can be interrupted by the stop button on the toolbar.
+ // We do this by using the loadgroup of the docshell for the message pane. We really shouldn't be doing this..
+ // See the comment in nsMsgMailNewsUrl::GetLoadGroup.
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup)); // get the message pane load group
+ nsCOMPtr<nsIRequest> ourRequest = do_QueryInterface(m_mockChannel);
+ if (loadGroup)
+ loadGroup->AddRequest(ourRequest, nullptr /* context isupports */);
+ }
+
+ if (m_mockChannel)
+ {
+ m_mockChannel->SetImapProtocol(this);
+ // if we have a listener from a mock channel, over-ride the consumer that was passed in
+ nsCOMPtr<nsIStreamListener> channelListener;
+ m_mockChannel->GetChannelListener(getter_AddRefs(channelListener));
+ if (channelListener) // only over-ride if we have a non null channel listener
+ aRealStreamListener = channelListener;
+ m_mockChannel->GetChannelContext(getter_AddRefs(m_channelContext));
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ if (!msgWindow)
+ GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIDocShell> docShell;
+ msgWindow->GetMessageWindowDocShell(getter_AddRefs(docShell));
+ nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(docShell));
+ nsCOMPtr<nsIInterfaceRequestor> interfaceRequestor;
+ msgWindow->GetNotificationCallbacks(getter_AddRefs(interfaceRequestor));
+ nsCOMPtr<nsIInterfaceRequestor> aggregateIR;
+ MsgNewInterfaceRequestorAggregation(interfaceRequestor, ir, getter_AddRefs(aggregateIR));
+ m_mockChannel->SetNotificationCallbacks(aggregateIR);
+ }
+ }
+
+ // since we'll be making calls directly from the imap thread to the channel listener,
+ // we need to turn it into a proxy object....we'll assume that the listener is on the same thread
+ // as the event sink queue
+ if (aRealStreamListener)
+ {
+ NS_ASSERTION(!m_channelListener, "shouldn't already have a channel listener");
+ m_channelListener = new StreamListenerProxy(aRealStreamListener);
+ }
+
+ server->GetRealHostName(m_realHostName);
+ int32_t authMethod;
+ (void) server->GetAuthMethod(&authMethod);
+#ifdef MOZ_MAILNEWS_OAUTH2
+ InitPrefAuthMethods(authMethod, server);
+#else
+ InitPrefAuthMethods(authMethod);
+#endif
+ (void) server->GetSocketType(&m_socketType);
+ bool shuttingDown;
+ (void) imapServer->GetShuttingDown(&shuttingDown);
+ if (!shuttingDown)
+ (void) imapServer->GetUseIdle(&m_useIdle);
+ else
+ m_useIdle = false;
+ imapServer->GetFetchByChunks(&m_fetchByChunks);
+ imapServer->GetSendID(&m_sendID);
+
+ nsAutoString trashFolderName;
+ if (NS_SUCCEEDED(imapServer->GetTrashFolderName(trashFolderName)))
+ CopyUTF16toMUTF7(trashFolderName, m_trashFolderName);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ {
+ bool preferPlainText;
+ prefBranch->GetBoolPref("mailnews.display.prefer_plaintext", &preferPlainText);
+ // If the pref has changed since the last time we ran a url,
+ // clear the shell cache for this host.
+ if (preferPlainText != m_preferPlainText)
+ {
+ m_hostSessionList->ClearShellCacheForHost(GetImapServerKey());
+ m_preferPlainText = preferPlainText;
+ }
+ }
+
+ if ( m_runningUrl && !m_transport /* and we don't have a transport yet */)
+ {
+ // extract the file name and create a file transport...
+ int32_t port=-1;
+ server->GetPort(&port);
+
+ if (port <= 0)
+ {
+ int32_t socketType;
+ // Be a bit smarter about setting the default port
+ port = (NS_SUCCEEDED(server->GetSocketType(&socketType)) &&
+ socketType == nsMsgSocketType::SSL) ?
+ nsIImapUrl::DEFAULT_IMAPS_PORT : nsIImapUrl::DEFAULT_IMAP_PORT;
+ }
+ nsCOMPtr<nsISocketTransportService> socketService =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && aURL)
+ {
+ aURL->GetPort(&port);
+
+ Log("SetupWithUrl", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ const char *connectionType = nullptr;
+
+ if (m_socketType == nsMsgSocketType::SSL)
+ connectionType = "ssl";
+ else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ connectionType = "starttls";
+ // This can go away once we think everyone is migrated
+ // away from the trySTARTTLS socket type.
+ else if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ connectionType = "starttls";
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ if (m_mockChannel)
+ rv = MsgExamineForProxy(m_mockChannel, getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv))
+ proxyInfo = nullptr;
+
+ const nsACString *socketHost;
+ uint16_t socketPort;
+
+ if (m_overRideUrlConnectionInfo)
+ {
+ socketHost = &m_logonHost;
+ socketPort = m_logonPort;
+ }
+ else
+ {
+ socketHost = &m_realHostName;
+ socketPort = port;
+ }
+ rv = socketService->CreateTransport(&connectionType, connectionType != nullptr,
+ *socketHost, socketPort, proxyInfo,
+ getter_AddRefs(m_transport));
+ if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS)
+ {
+ connectionType = nullptr;
+ m_socketType = nsMsgSocketType::plain;
+ rv = socketService->CreateTransport(&connectionType, connectionType != nullptr,
+ *socketHost, socketPort, proxyInfo,
+ getter_AddRefs(m_transport));
+ }
+ // remember so we can know whether we can issue a start tls or not...
+ m_connectionType = connectionType;
+ if (m_transport && m_mockChannel)
+ {
+ uint8_t qos;
+ rv = GetQoSBits(&qos);
+ if (NS_SUCCEEDED(rv))
+ m_transport->SetQoSBits(qos);
+
+ // Ensure that the socket can get the notification callbacks
+ SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);
+
+ // open buffered, blocking input stream
+ rv = m_transport->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(m_inputStream));
+ if (NS_FAILED(rv)) return rv;
+
+ // open buffered, blocking output stream
+ rv = m_transport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(m_outputStream));
+ if (NS_FAILED(rv)) return rv;
+ SetFlag(IMAP_CONNECTION_IS_OPEN);
+ }
+ }
+ } // if m_runningUrl
+
+ if (m_transport && m_mockChannel)
+ {
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT, gResponseTimeout + 60);
+ int32_t readWriteTimeout = gResponseTimeout;
+ if (m_runningUrl)
+ {
+ m_runningUrl->GetImapAction(&m_imapAction);
+ // this is a silly hack, but the default of 100 seconds is way too long
+ // for things like APPEND, which should come back immediately.
+ if (m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
+ m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile)
+ {
+ readWriteTimeout = 20;
+ }
+ else if (m_imapAction == nsIImapUrl::nsImapOnlineMove ||
+ m_imapAction == nsIImapUrl::nsImapOnlineCopy)
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ uint32_t copyCount = CountMessagesInIdString(messageIdString.get());
+ // If we're move/copying a large number of messages,
+ // which should be rare, increase the timeout based on number
+ // of messages. 40 messages per second should be sufficiently slow.
+ if (copyCount > 2400) // 40 * 60, 60 is default read write timeout
+ readWriteTimeout = std::max(readWriteTimeout, (int32_t)copyCount/40);
+ }
+ }
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, readWriteTimeout);
+ // set the security info for the mock channel to be the security status for our underlying transport.
+ nsCOMPtr<nsISupports> securityInfo;
+ m_transport->GetSecurityInfo(getter_AddRefs(securityInfo));
+ m_mockChannel->SetSecurityInfo(securityInfo);
+
+ SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);
+
+ nsCOMPtr<nsITransportEventSink> sink = do_QueryInterface(m_mockChannel);
+ if (sink) {
+ nsCOMPtr<nsIThread> thread = do_GetMainThread();
+ m_transport->SetEventSink(sink, thread);
+ }
+
+ // and if we have a cache entry that we are saving the message to, set the security info on it too.
+ // since imap only uses the memory cache, passing this on is the right thing to do.
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (cacheEntry)
+ cacheEntry->SetSecurityInfo(securityInfo);
+ }
+ }
+ } // if aUR
+
+ return rv;
+}
+
+
+// when the connection is done processing the current state, free any per url state data...
+void nsImapProtocol::ReleaseUrlState(bool rerunning)
+{
+ // clear out the socket's reference to the notification callbacks for this transaction
+ {
+ MutexAutoLock mon(mLock);
+ if (m_transport)
+ {
+ m_transport->SetSecurityCallbacks(nullptr);
+ m_transport->SetEventSink(nullptr, nullptr);
+ }
+ }
+
+ if (m_mockChannel && !rerunning)
+ {
+ // Proxy the close of the channel to the ui thread.
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->CloseMockChannel(m_mockChannel);
+ else
+ m_mockChannel->Close();
+
+ {
+ // grab a lock so m_mockChannel doesn't get cleared out
+ // from under us.
+ MutexAutoLock mon(mLock);
+ if (m_mockChannel)
+ {
+ // Proxy the release of the channel to the main thread. This is something
+ // that the xpcom proxy system should do for us!
+ NS_ReleaseOnMainThread(m_mockChannel.forget());
+ }
+ }
+ }
+
+ m_channelContext = nullptr; // this might be the url - null it out before the final release of the url
+ m_imapMessageSink = nullptr;
+
+ // Proxy the release of the listener to the main thread. This is something
+ // that the xpcom proxy system should do for us!
+ {
+ // grab a lock so the m_channelListener doesn't get cleared.
+ MutexAutoLock mon(mLock);
+ if (m_channelListener)
+ {
+ NS_ReleaseOnMainThread(m_channelListener.forget());
+ }
+ }
+ m_channelInputStream = nullptr;
+ m_channelOutputStream = nullptr;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl;
+ nsCOMPtr<nsIImapMailFolderSink> saveFolderSink;
+
+ {
+ MutexAutoLock mon(mLock);
+ if (m_runningUrl)
+ {
+ mailnewsurl = do_QueryInterface(m_runningUrl);
+ // It is unclear what 'saveFolderSink' is used for, most likely to hold
+ // a reference for a little longer. See bug 1324893 and bug 391259.
+ saveFolderSink = m_imapMailFolderSink;
+
+ m_runningUrl = nullptr; // force us to release our last reference on the url
+ m_urlInProgress = false;
+ }
+ }
+ // Need to null this out whether we have an m_runningUrl or not
+ m_imapMailFolderSink = nullptr;
+
+ // we want to make sure the imap protocol's last reference to the url gets released
+ // back on the UI thread. This ensures that the objects the imap url hangs on to
+ // properly get released back on the UI thread.
+ if (mailnewsurl)
+ {
+ NS_ReleaseOnMainThread(mailnewsurl.forget());
+ }
+ saveFolderSink = nullptr;
+}
+
+
+class nsImapThreadShutdownEvent : public mozilla::Runnable {
+public:
+ explicit nsImapThreadShutdownEvent(nsIThread *thread) : mThread(thread) {
+ }
+ NS_IMETHOD Run() {
+ mThread->Shutdown();
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+
+NS_IMETHODIMP nsImapProtocol::Run()
+{
+ PR_CEnterMonitor(this);
+ NS_ASSERTION(!m_imapThreadIsRunning,
+ "Oh. oh. thread is already running. What's wrong here?");
+ if (m_imapThreadIsRunning)
+ {
+ PR_CExitMonitor(this);
+ return NS_OK;
+ }
+
+ m_imapThreadIsRunning = true;
+ PR_CExitMonitor(this);
+
+ // call the platform specific main loop ....
+ ImapThreadMainLoop();
+
+ if (m_runningUrl)
+ {
+ NS_ReleaseOnMainThread(m_runningUrl.forget());
+ }
+
+ // close streams via UI thread if it's not already done
+ if (m_imapProtocolSink)
+ m_imapProtocolSink->CloseStreams();
+
+ m_imapMailFolderSink = nullptr;
+ m_imapMessageSink = nullptr;
+
+ // shutdown this thread, but do it from the main thread
+ nsCOMPtr<nsIRunnable> ev = new nsImapThreadShutdownEvent(m_iThread);
+ if (NS_FAILED(NS_DispatchToMainThread(ev)))
+ NS_WARNING("Failed to dispatch nsImapThreadShutdownEvent");
+ m_iThread = nullptr;
+
+ // Release protocol object on the main thread to avoid destruction of 'this'
+ // on the IMAP thread, which causes grief for weak references.
+ nsCOMPtr<nsIImapProtocol> releaseOnMain(this);
+ NS_ReleaseOnMainThread(releaseOnMain.forget());
+ return NS_OK;
+}
+
+//
+// Must be called from UI thread only
+//
+NS_IMETHODIMP nsImapProtocol::CloseStreams()
+{
+ // make sure that it is called by the UI thread
+ MOZ_ASSERT(NS_IsMainThread(), "CloseStreams() should not be called from an off UI thread");
+
+ {
+ MutexAutoLock mon(mLock);
+ if (m_transport)
+ {
+ // make sure the transport closes (even if someone is still indirectly
+ // referencing it).
+ m_transport->Close(NS_ERROR_ABORT);
+ m_transport = nullptr;
+ }
+ m_inputStream = nullptr;
+ m_outputStream = nullptr;
+ m_channelListener = nullptr;
+ m_channelContext = nullptr;
+ if (m_mockChannel)
+ {
+ m_mockChannel->Close();
+ m_mockChannel = nullptr;
+ }
+ m_channelInputStream = nullptr;
+ m_channelOutputStream = nullptr;
+
+ // Close scope because we must let go of the monitor before calling
+ // RemoveConnection to unblock anyone who tries to get a monitor to the
+ // protocol object while holding onto a monitor to the server.
+ }
+ nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server);
+ if (me_server)
+ {
+ nsresult result;
+ nsCOMPtr<nsIImapIncomingServer>
+ aImapServer(do_QueryInterface(me_server, &result));
+ if (NS_SUCCEEDED(result))
+ aImapServer->RemoveConnection(this);
+ me_server = nullptr;
+ }
+ m_server = nullptr;
+ // take this opportunity of being on the UI thread to
+ // persist chunk prefs if they've changed
+ if (gChunkSizeDirty)
+ {
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ {
+ prefBranch->SetIntPref("mail.imap.chunk_size", gChunkSize);
+ prefBranch->SetIntPref("mail.imap.min_chunk_size_threshold", gChunkThreshold);
+ gChunkSizeDirty = false;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetUrlWindow(nsIMsgMailNewsUrl *aUrl,
+ nsIMsgWindow **aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+ return aUrl->GetMsgWindow(aMsgWindow);
+}
+
+NS_IMETHODIMP nsImapProtocol::SetupMainThreadProxies()
+{
+ return SetupSinkProxy();
+}
+
+NS_IMETHODIMP nsImapProtocol::OnInputStreamReady(nsIAsyncInputStream *inStr)
+{
+ // should we check if it's a close vs. data available?
+ if (m_idle)
+ {
+ uint64_t bytesAvailable = 0;
+ (void) inStr->Available(&bytesAvailable);
+ // check if data available - might be a close
+ if (bytesAvailable != 0)
+ {
+ ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);
+ m_lastActiveTime = PR_Now();
+ m_nextUrlReadyToRun = true;
+ mon.Notify();
+ }
+ }
+ return NS_OK;
+}
+
+// this is to be called from the UI thread. It sets m_threadShouldDie,
+// and then signals the imap thread, which, when it wakes up, should exit.
+// The imap thread cleanup code will check m_safeToCloseConnection.
+NS_IMETHODIMP
+nsImapProtocol::TellThreadToDie(bool aIsSafeToClose)
+{
+ NS_WARNING_ASSERTION(NS_IsMainThread(),
+ "TellThreadToDie(aIsSafeToClose) should only be called from UI thread");
+ MutexAutoLock mon(mLock);
+
+ nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server);
+ if (me_server)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIImapIncomingServer>
+ aImapServer(do_QueryInterface(me_server, &rv));
+ if (NS_SUCCEEDED(rv))
+ aImapServer->RemoveConnection(this);
+ m_server = nullptr;
+ me_server = nullptr;
+ }
+ {
+ ReentrantMonitorAutoEnter deathMon(m_threadDeathMonitor);
+ m_safeToCloseConnection = aIsSafeToClose;
+ m_threadShouldDie = true;
+ }
+ ReentrantMonitorAutoEnter readyMon(m_urlReadyToRunMonitor);
+ m_nextUrlReadyToRun = true;
+ readyMon.Notify();
+ return NS_OK;
+}
+
+void
+nsImapProtocol::TellThreadToDie()
+{
+ nsresult rv = NS_OK;
+ NS_WARNING_ASSERTION(!NS_IsMainThread(),
+ "TellThreadToDie() should not be called from UI thread");
+
+ // prevent re-entering this method because it may lock the UI.
+ if (m_inThreadShouldDie)
+ return;
+ m_inThreadShouldDie = true;
+
+ // This routine is called only from the imap protocol thread.
+ // The UI thread causes this to be called by calling TellThreadToDie.
+ // In that case, m_safeToCloseConnection will be FALSE if it's dropping a
+ // timed out connection, true when closing a cached connection.
+ // We're using PR_CEnter/ExitMonitor because Monitors don't like having
+ // us to hold one monitor and call code that gets a different monitor. And
+ // some of the methods we call here use Monitors.
+ PR_CEnterMonitor(this);
+
+ m_urlInProgress = true; // let's say it's busy so no one tries to use
+ // this about to die connection.
+ bool urlWritingData = false;
+ bool connectionIdle = !m_runningUrl;
+
+ if (!connectionIdle)
+ urlWritingData = m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile
+ || m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile;
+
+ bool closeNeeded = GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected && m_safeToCloseConnection;
+ nsCString command;
+ // if a url is writing data, we can't even logout, so we're just
+ // going to close the connection as if the user pressed stop.
+ if (m_currentServerCommandTagNumber > 0 && !urlWritingData)
+ {
+ bool isAlive = false;
+ if (m_transport)
+ rv = m_transport->IsAlive(&isAlive);
+
+ if (TestFlag(IMAP_CONNECTION_IS_OPEN) && m_idle && isAlive)
+ EndIdle(false);
+
+ if (NS_SUCCEEDED(rv) && isAlive && closeNeeded && GetDeleteIsMoveToTrash() &&
+ TestFlag(IMAP_CONNECTION_IS_OPEN) && m_outputStream)
+ Close(true, connectionIdle);
+
+ if (NS_SUCCEEDED(rv) && isAlive && TestFlag(IMAP_CONNECTION_IS_OPEN) &&
+ NS_SUCCEEDED(GetConnectionStatus()) && m_outputStream)
+ Logout(true, connectionIdle);
+ }
+ PR_CExitMonitor(this);
+ // close streams via UI thread
+ if (m_imapProtocolSink)
+ {
+ m_imapProtocolSink->CloseStreams();
+ m_imapProtocolSink = nullptr;
+ }
+ Log("TellThreadToDie", nullptr, "close socket connection");
+
+ {
+ ReentrantMonitorAutoEnter mon(m_threadDeathMonitor);
+ m_threadShouldDie = true;
+ }
+ {
+ ReentrantMonitorAutoEnter dataMon(m_dataAvailableMonitor);
+ dataMon.Notify();
+ }
+ ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor);
+ urlReadyMon.NotifyAll();
+}
+
+NS_IMETHODIMP
+nsImapProtocol::GetLastActiveTimeStamp(PRTime* aTimeStamp)
+{
+ if (aTimeStamp)
+ *aTimeStamp = m_lastActiveTime;
+ return NS_OK;
+}
+
+static void DoomCacheEntry(nsIMsgMailNewsUrl *url);
+NS_IMETHODIMP
+nsImapProtocol::PseudoInterruptMsgLoad(nsIMsgFolder *aImapFolder, nsIMsgWindow *aMsgWindow, bool *interrupted)
+{
+ NS_ENSURE_ARG (interrupted);
+
+ *interrupted = false;
+
+ PR_CEnterMonitor(this);
+
+ if (m_runningUrl && !TestFlag(IMAP_CLEAN_UP_URL_STATE))
+ {
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+
+ if (imapAction == nsIImapUrl::nsImapMsgFetch)
+ {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapUrl> runningImapURL;
+
+ rv = GetRunningImapURL(getter_AddRefs(runningImapURL));
+ if (NS_SUCCEEDED(rv) && runningImapURL)
+ {
+ nsCOMPtr <nsIMsgFolder> runningImapFolder;
+ nsCOMPtr <nsIMsgWindow> msgWindow;
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(runningImapURL);
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ mailnewsUrl->GetFolder(getter_AddRefs(runningImapFolder));
+ if (aImapFolder == runningImapFolder && msgWindow == aMsgWindow)
+ {
+ PseudoInterrupt(true);
+ *interrupted = true;
+ }
+ // If we're interrupted, doom any incomplete cache entry.
+ DoomCacheEntry(mailnewsUrl);
+ }
+ }
+ }
+ PR_CExitMonitor(this);
+#ifdef DEBUG_bienvenu
+ printf("interrupt msg load : %s\n", (*interrupted) ? "TRUE" : "FALSE");
+#endif
+ return NS_OK;
+}
+
+void
+nsImapProtocol::ImapThreadMainLoop()
+{
+ MOZ_LOG(IMAP, LogLevel::Debug, ("ImapThreadMainLoop entering [this=%x]\n", this));
+
+ PRIntervalTime sleepTime = kImapSleepTime;
+ while (!DeathSignalReceived())
+ {
+ nsresult rv = NS_OK;
+ bool readyToRun;
+
+ // wait for an URL to process...
+ {
+ ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);
+
+ while (NS_SUCCEEDED(rv) && !DeathSignalReceived() &&
+ !m_nextUrlReadyToRun && !m_threadShouldDie)
+ rv = mon.Wait(sleepTime);
+
+ readyToRun = m_nextUrlReadyToRun;
+ m_nextUrlReadyToRun = false;
+ }
+ // This will happen if the UI thread signals us to die
+ if (m_threadShouldDie)
+ {
+ TellThreadToDie();
+ break;
+ }
+
+ if (NS_FAILED(rv) && PR_PENDING_INTERRUPT_ERROR == PR_GetError())
+ {
+ printf("error waiting for monitor\n");
+ break;
+ }
+
+ if (readyToRun && m_runningUrl)
+ {
+ if (m_currentServerCommandTagNumber && m_transport)
+ {
+ bool isAlive;
+ rv = m_transport->IsAlive(&isAlive);
+ // if the transport is not alive, and we've ever sent a command with this connection, kill it.
+ // otherwise, we've probably just not finished setting it so don't kill it!
+ if (NS_FAILED(rv) || !isAlive)
+ {
+ // This says we never started running the url, which is the case.
+ m_runningUrl->SetRerunningUrl(false);
+ RetryUrl();
+ return;
+ }
+ }
+ //
+ // NOTE: Though we cleared m_nextUrlReadyToRun above, it may have been
+ // set by LoadImapUrl, which runs on the main thread. Because of this,
+ // we must not try to clear m_nextUrlReadyToRun here.
+ //
+ if (ProcessCurrentURL())
+ {
+ m_nextUrlReadyToRun = true;
+ m_imapMailFolderSink = nullptr;
+ }
+ else
+ {
+ // see if we want to go into idle mode. Might want to check a pref here too.
+ if (m_useIdle && !m_urlInProgress && GetServerStateParser().GetCapabilityFlag() & kHasIdleCapability
+ && GetServerStateParser().GetIMAPstate()
+ == nsImapServerResponseParser::kFolderSelected)
+ {
+ Idle(); // for now, lets just do it. We'll probably want to use a timer
+ if (!m_idle)
+ {
+ // Server rejected IDLE. Treat like IDLE not enabled or available.
+ m_imapMailFolderSink = nullptr;
+ }
+ }
+ else // if not idle, don't need to remember folder sink
+ m_imapMailFolderSink = nullptr;
+ }
+ }
+ else if (m_idle && !m_threadShouldDie)
+ {
+ HandleIdleResponses();
+ }
+ if (!GetServerStateParser().Connected())
+ break;
+#ifdef DEBUG_bienvenu
+ else
+ printf("ready to run but no url and not idle\n");
+#endif
+ // This can happen if the UI thread closes cached connections in the
+ // OnStopRunningUrl notification.
+ if (m_threadShouldDie)
+ TellThreadToDie();
+ }
+ m_imapThreadIsRunning = false;
+
+ MOZ_LOG(IMAP, LogLevel::Debug, ("ImapThreadMainLoop leaving [this=%x]\n", this));
+}
+
+void nsImapProtocol::HandleIdleResponses()
+{
+ // int32_t oldRecent = GetServerStateParser().NumberOfRecentMessages();
+ nsAutoCString commandBuffer(GetServerCommandTag());
+ commandBuffer.Append(" IDLE" CRLF);
+
+ do
+ {
+ ParseIMAPandCheckForNewMail(commandBuffer.get());
+ }
+ while (m_inputStreamBuffer->NextLineAvailable() && GetServerStateParser().Connected());
+
+ // if (oldRecent != GetServerStateParser().NumberOfRecentMessages())
+ // We might check that something actually changed, but for now we can
+ // just assume it. OnNewIdleMessages must run a url, so that
+ // we'll go back into asyncwait mode.
+ if (GetServerStateParser().Connected() && m_imapMailFolderSink)
+ m_imapMailFolderSink->OnNewIdleMessages();
+}
+
+void nsImapProtocol::EstablishServerConnection()
+{
+#define ESC_LENGTH(x) (sizeof(x) - 1)
+#define ESC_OK "* OK"
+#define ESC_OK_LEN ESC_LENGTH(ESC_OK)
+#define ESC_PREAUTH "* PREAUTH"
+#define ESC_PREAUTH_LEN ESC_LENGTH(ESC_PREAUTH)
+#define ESC_CAPABILITY_STAR "* "
+#define ESC_CAPABILITY_STAR_LEN ESC_LENGTH(ESC_CAPABILITY_STAR)
+#define ESC_CAPABILITY_OK "* OK ["
+#define ESC_CAPABILITY_OK_LEN ESC_LENGTH(ESC_CAPABILITY_OK)
+#define ESC_CAPABILITY_GREETING (ESC_CAPABILITY_OK "CAPABILITY")
+#define ESC_CAPABILITY_GREETING_LEN ESC_LENGTH(ESC_CAPABILITY_GREETING)
+
+ char * serverResponse = CreateNewLineFromSocket(); // read in the greeting
+
+ // record the fact that we've received a greeting for this connection so we don't ever
+ // try to do it again..
+ if (serverResponse)
+ SetFlag(IMAP_RECEIVED_GREETING);
+
+ if (!PL_strncasecmp(serverResponse, ESC_OK, ESC_OK_LEN))
+ {
+ SetConnectionStatus(NS_OK);
+
+ if (!PL_strncasecmp(serverResponse, ESC_CAPABILITY_GREETING, ESC_CAPABILITY_GREETING_LEN))
+ {
+ nsAutoCString tmpstr(serverResponse);
+ int32_t endIndex = tmpstr.FindChar(']', ESC_CAPABILITY_GREETING_LEN);
+ if (endIndex >= 0)
+ {
+ // Allocate the new buffer here. This buffer will be passed to
+ // ParseIMAPServerResponse() where it will be used to fill the
+ // fCurrentLine field and will be freed by the next call to
+ // ResetLexAnalyzer().
+ char *fakeServerResponse = (char*)PR_Malloc(PL_strlen(serverResponse));
+ // Munge the greeting into something that would pass for an IMAP
+ // server's response to a "CAPABILITY" command.
+ strcpy(fakeServerResponse, ESC_CAPABILITY_STAR);
+ strcat(fakeServerResponse, serverResponse + ESC_CAPABILITY_OK_LEN);
+ fakeServerResponse[endIndex - ESC_CAPABILITY_OK_LEN + ESC_CAPABILITY_STAR_LEN] = '\0';
+ // Tell the response parser that we just issued a "CAPABILITY" and
+ // got the following back.
+ GetServerStateParser().ParseIMAPServerResponse("1 CAPABILITY", true, fakeServerResponse);
+ }
+ }
+ }
+ else if (!PL_strncasecmp(serverResponse, ESC_PREAUTH, ESC_PREAUTH_LEN))
+ {
+ // we've been pre-authenticated.
+ // we can skip the whole password step, right into the
+ // kAuthenticated state
+ GetServerStateParser().PreauthSetAuthenticatedState();
+
+ if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined)
+ Capability();
+
+ if ( !(GetServerStateParser().GetCapabilityFlag() &
+ (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other) ) )
+ {
+ // AlertUserEvent_UsingId(MK_MSG_IMAP_SERVER_NOT_IMAP4);
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ }
+ else
+ {
+ // let's record the user as authenticated.
+ m_imapServerSink->SetUserAuthenticated(true);
+
+ ProcessAfterAuthenticated();
+ // the connection was a success
+ SetConnectionStatus(NS_OK);
+ }
+ }
+
+ PR_Free(serverResponse); // we don't care about the greeting yet...
+
+#undef ESC_LENGTH
+#undef ESC_OK
+#undef ESC_OK_LEN
+#undef ESC_PREAUTH
+#undef ESC_PREAUTH_LEN
+#undef ESC_CAPABILITY_STAR
+#undef ESC_CAPABILITY_STAR_LEN
+#undef ESC_CAPABILITY_OK
+#undef ESC_CAPABILITY_OK_LEN
+#undef ESC_CAPABILITY_GREETING
+#undef ESC_CAPABILITY_GREETING_LEN
+}
+
+// This can get called from the UI thread or an imap thread.
+// It makes sure we don't get left with partial messages in
+// the memory cache.
+static void DoomCacheEntry(nsIMsgMailNewsUrl *url)
+{
+ bool readingFromMemCache = false;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);
+ imapUrl->GetMsgLoadingFromCache(&readingFromMemCache);
+ if (!readingFromMemCache)
+ {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ url->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (cacheEntry)
+ cacheEntry->AsyncDoom(nullptr);
+ }
+}
+
+// returns true if another url was run, false otherwise.
+bool nsImapProtocol::ProcessCurrentURL()
+{
+ nsresult rv = NS_OK;
+ if (m_idle)
+ EndIdle();
+
+ if (m_retryUrlOnError)
+ {
+ // we clear this flag if we're re-running immediately, because that
+ // means we never sent a start running url notification, and later we
+ // don't send start running notification if we think we're rerunning
+ // the url (see first call to SetUrlState below). This means we won't
+ // send a start running notification, which means our stop running
+ // notification will be ignored because we don't think we were running.
+ m_runningUrl->SetRerunningUrl(false);
+ return RetryUrl();
+ }
+ Log("ProcessCurrentURL", nullptr, "entering");
+ (void) GetImapHostName(); // force m_hostName to get set.
+
+
+ bool logonFailed = false;
+ bool anotherUrlRun = false;
+ bool rerunningUrl = false;
+ bool isExternalUrl;
+ bool validUrl = true;
+
+ PseudoInterrupt(false); // clear this if left over from previous url.
+
+ m_runningUrl->GetRerunningUrl(&rerunningUrl);
+ m_runningUrl->GetExternalLinkUrl(&isExternalUrl);
+ m_runningUrl->GetValidUrl(&validUrl);
+ m_runningUrl->GetImapAction(&m_imapAction);
+
+ if (isExternalUrl)
+ {
+ if (m_imapAction == nsIImapUrl::nsImapSelectFolder)
+ {
+ // we need to send a start request so that the doc loader
+ // will call HandleContent on the imap service so we
+ // can abort this url, and run a new url in a new msg window
+ // to run the folder load url and get off this crazy merry-go-round.
+ if (m_channelListener)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ m_channelListener->OnStartRequest(request, m_channelContext);
+ }
+ return false;
+ }
+ }
+
+ if (!m_imapMailFolderSink && m_imapProtocolSink)
+ {
+ // This occurs when running another URL in the main thread loop
+ rv = m_imapProtocolSink->SetupMainThreadProxies();
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ // Reinitialize the parser
+ GetServerStateParser().InitializeState();
+ GetServerStateParser().SetConnected(true);
+
+ // acknowledge that we are running the url now..
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningUrl, &rv);
+ nsAutoCString urlSpec;
+ rv = mailnewsurl->GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, false);
+ Log("ProcessCurrentURL", urlSpec.get(), (validUrl) ? " = currentUrl\n" : " is not valid\n");
+ if (!validUrl)
+ return false;
+
+ if (NS_SUCCEEDED(rv) && mailnewsurl && m_imapMailFolderSink && !rerunningUrl)
+ m_imapMailFolderSink->SetUrlState(this, mailnewsurl, true, false,
+ NS_OK);
+
+ // if we are set up as a channel, we should notify our channel listener that we are starting...
+ // so pass in ourself as the channel and not the underlying socket or file channel the protocol
+ // happens to be using
+ if (m_channelListener) // ### not sure we want to do this if rerunning url...
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ m_channelListener->OnStartRequest(request, m_channelContext);
+ }
+ // If we haven't received the greeting yet, we need to make sure we strip
+ // it out of the input before we start to do useful things...
+ if (!TestFlag(IMAP_RECEIVED_GREETING))
+ EstablishServerConnection();
+
+ // Step 1: If we have not moved into the authenticated state yet then do so
+ // by attempting to logon.
+ if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()) &&
+ (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kNonAuthenticated))
+ {
+ /* if we got here, the server's greeting should not have been PREAUTH */
+ if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined)
+ Capability();
+
+ if ( !(GetServerStateParser().GetCapabilityFlag() & (kIMAP4Capability | kIMAP4rev1Capability |
+ kIMAP4other) ) )
+ {
+ if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()))
+ AlertUserEventUsingName("imapServerNotImap4");
+
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ }
+ else
+ {
+ if ((m_connectionType.Equals("starttls")
+ && (m_socketType == nsMsgSocketType::trySTARTTLS
+ && (GetServerStateParser().GetCapabilityFlag() & kHasStartTLSCapability)))
+ || m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ StartTLS();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsCOMPtr<nsISupports> secInfo;
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport, &rv);
+ if (NS_FAILED(rv))
+ return false;
+
+ rv = strans->GetSecurityInfo(getter_AddRefs(secInfo));
+
+ if (NS_SUCCEEDED(rv) && secInfo)
+ {
+ nsCOMPtr<nsISSLSocketControl> sslControl = do_QueryInterface(secInfo, &rv);
+
+ if (NS_SUCCEEDED(rv) && sslControl)
+ {
+ rv = sslControl->StartTLS();
+ if (NS_SUCCEEDED(rv))
+ {
+ if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ m_imapServerSink->UpdateTrySTARTTLSPref(true);
+ // force re-issue of "capability", because servers may
+ // enable other auth features (e.g. remove LOGINDISABLED
+ // and add AUTH=PLAIN) after we upgraded to SSL.
+ Capability();
+ eIMAPCapabilityFlags capabilityFlag = GetServerStateParser().GetCapabilityFlag();
+ // Courier imap doesn't return STARTTLS capability if we've done
+ // a STARTTLS! But we need to remember this capability so we'll
+ // try to use STARTTLS next time.
+ if (!(capabilityFlag & kHasStartTLSCapability))
+ {
+ capabilityFlag |= kHasStartTLSCapability;
+ GetServerStateParser().SetCapabilityFlag(capabilityFlag);
+ CommitCapability();
+ }
+ }
+ }
+ }
+ if (NS_FAILED(rv))
+ {
+ nsAutoCString logLine("STARTTLS negotiation failed. Error 0x");
+ logLine.AppendInt(static_cast<uint32_t>(rv), 16);
+ Log("ProcessCurrentURL", nullptr, logLine.get());
+ if (m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ SetConnectionStatus(rv); // stop netlib
+ m_transport->Close(rv);
+ }
+ else if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ m_imapServerSink->UpdateTrySTARTTLSPref(false);
+ }
+ }
+ else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ if (m_transport)
+ m_transport->Close(rv);
+ }
+ else if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ {
+ // STARTTLS failed, so downgrade socket type
+ m_imapServerSink->UpdateTrySTARTTLSPref(false);
+ }
+ }
+ else if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ {
+ // we didn't know the server supported TLS when we created
+ // the socket, so we're going to retry with a STARTTLS socket
+ if (GetServerStateParser().GetCapabilityFlag() & kHasStartTLSCapability)
+ {
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(NS_ERROR_FAILURE);
+ return RetryUrl();
+ }
+ else
+ {
+ // trySTARTTLS set, but server doesn't have TLS capability,
+ // so downgrade socket type
+ m_imapServerSink->UpdateTrySTARTTLSPref(false);
+ m_socketType = nsMsgSocketType::plain;
+ }
+ }
+ logonFailed = !TryToLogon();
+ if (m_retryUrlOnError)
+ return RetryUrl();
+ }
+ } // if death signal not received
+
+ if (!DeathSignalReceived() && (NS_SUCCEEDED(GetConnectionStatus())))
+ {
+ // if the server supports a language extension then we should
+ // attempt to issue the language extension.
+ if ( GetServerStateParser().GetCapabilityFlag() & kHasLanguageCapability)
+ Language();
+
+ if (m_runningUrl)
+ FindMailboxesIfNecessary();
+
+ nsImapState imapState = nsIImapUrl::ImapStatusNone;
+ if (m_runningUrl)
+ m_runningUrl->GetRequiredImapState(&imapState);
+
+ if (imapState == nsIImapUrl::nsImapAuthenticatedState)
+ ProcessAuthenticatedStateURL();
+ else // must be a url that requires us to be in the selected state
+ ProcessSelectedStateURL();
+
+ if (m_retryUrlOnError)
+ return RetryUrl();
+
+ // The URL has now been processed
+ if ((!logonFailed && NS_FAILED(GetConnectionStatus())) ||
+ DeathSignalReceived())
+ HandleCurrentUrlError();
+
+ }
+ else if (!logonFailed)
+ HandleCurrentUrlError();
+
+// if we are set up as a channel, we should notify our channel listener that we are stopping...
+// so pass in ourself as the channel and not the underlying socket or file channel the protocol
+// happens to be using
+ if (m_channelListener)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ NS_ASSERTION(request, "no request");
+ if (request) {
+ nsresult status;
+ request->GetStatus(&status);
+ if (!GetServerStateParser().LastCommandSuccessful() && NS_SUCCEEDED(status))
+ status = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ rv = m_channelListener->OnStopRequest(request, m_channelContext, status);
+ }
+ }
+ bool suspendUrl = false;
+ m_runningUrl->GetMoreHeadersToDownload(&suspendUrl);
+ if (mailnewsurl && m_imapMailFolderSink)
+ {
+ if (logonFailed)
+ rv = NS_ERROR_FAILURE;
+ else if (GetServerStateParser().CommandFailed())
+ rv = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ else
+ rv = GetConnectionStatus();
+ // we are done with this url.
+ m_imapMailFolderSink->SetUrlState(this, mailnewsurl, false, suspendUrl,
+ rv);
+ // doom the cache entry
+ if (NS_FAILED(rv) && DeathSignalReceived() && m_mockChannel)
+ DoomCacheEntry(mailnewsurl);
+ }
+ else
+ {
+ // That's seen at times in debug sessions.
+ NS_WARNING("missing url or sink");
+ }
+
+ // disable timeouts before caching connection.
+ if (m_transport)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, PR_UINT32_MAX);
+
+ SetFlag(IMAP_CLEAN_UP_URL_STATE);
+
+ nsCOMPtr <nsISupports> copyState;
+ if (m_runningUrl)
+ m_runningUrl->GetCopyState(getter_AddRefs(copyState));
+ // this is so hokey...we MUST clear any local references to the url
+ // BEFORE calling ReleaseUrlState
+ mailnewsurl = nullptr;
+
+ if (suspendUrl)
+ m_imapServerSink->SuspendUrl(m_runningUrl);
+ // save the imap folder sink since we need it to do the CopyNextStreamMessage
+ RefPtr<ImapMailFolderSinkProxy> imapMailFolderSink = m_imapMailFolderSink;
+ // release the url as we are done with it...
+ ReleaseUrlState(false);
+ ResetProgressInfo();
+
+ ClearFlag(IMAP_CLEAN_UP_URL_STATE);
+
+ if (imapMailFolderSink)
+ {
+ if (copyState)
+ {
+ rv = imapMailFolderSink->CopyNextStreamMessage(GetServerStateParser().LastCommandSuccessful() &&
+ NS_SUCCEEDED(GetConnectionStatus()),
+ copyState);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, LogLevel::Info, ("CopyNextStreamMessage failed:%lx\n", rv));
+
+ NS_ReleaseOnMainThread(copyState.forget());
+ }
+ // we might need this to stick around for IDLE support
+ m_imapMailFolderSink = imapMailFolderSink;
+ imapMailFolderSink = nullptr;
+ }
+ else
+ MOZ_LOG(IMAP, LogLevel::Info, ("null imapMailFolderSink\n"));
+
+ // now try queued urls, now that we've released this connection.
+ if (m_imapServerSink)
+ {
+ if (NS_SUCCEEDED(GetConnectionStatus()))
+ rv = m_imapServerSink->LoadNextQueuedUrl(this, &anotherUrlRun);
+ else // if we don't do this, they'll just sit and spin until
+ // we run some other url on this server.
+ {
+ Log("ProcessCurrentURL", nullptr, "aborting queued urls");
+ rv = m_imapServerSink->AbortQueuedUrls();
+ }
+ }
+
+ // if we didn't run another url, release the server sink to
+ // cut circular refs.
+ if (!anotherUrlRun)
+ m_imapServerSink = nullptr;
+
+ if (NS_FAILED(GetConnectionStatus()) || !GetServerStateParser().Connected()
+ || GetServerStateParser().SyntaxError())
+ {
+ if (m_imapServerSink)
+ m_imapServerSink->RemoveServerConnection(this);
+
+ if (!DeathSignalReceived())
+ {
+ TellThreadToDie();
+ }
+ }
+ else
+ {
+ if (m_imapServerSink)
+ {
+ bool shuttingDown;
+ m_imapServerSink->GetServerShuttingDown(&shuttingDown);
+ if (shuttingDown)
+ m_useIdle = false;
+ }
+ }
+ return anotherUrlRun;
+}
+
+bool nsImapProtocol::RetryUrl()
+{
+ nsCOMPtr <nsIImapUrl> kungFuGripImapUrl = m_runningUrl;
+ nsCOMPtr <nsIImapMockChannel> saveMockChannel;
+
+ // the mock channel might be null - that's OK.
+ if (m_imapServerSink)
+ (void) m_imapServerSink->PrepareToRetryUrl(kungFuGripImapUrl, getter_AddRefs(saveMockChannel));
+
+ ReleaseUrlState(true);
+ if (m_imapServerSink)
+ {
+ m_imapServerSink->RemoveServerConnection(this);
+ m_imapServerSink->RetryUrl(kungFuGripImapUrl, saveMockChannel);
+ }
+ return (m_imapServerSink != nullptr); // we're running a url (the same url)
+}
+
+// ignoreBadAndNOResponses --> don't throw a error dialog if this command results in a NO or Bad response
+// from the server..in other words the command is "exploratory" and we don't really care if it succeeds or fails.
+void nsImapProtocol::ParseIMAPandCheckForNewMail(const char* commandString, bool aIgnoreBadAndNOResponses)
+{
+ if (commandString)
+ GetServerStateParser().ParseIMAPServerResponse(commandString, aIgnoreBadAndNOResponses);
+ else
+ GetServerStateParser().ParseIMAPServerResponse(m_currentCommand.get(), aIgnoreBadAndNOResponses);
+ // **** fix me for new mail biff state *****
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// End of nsIStreamListenerSupport
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsImapProtocol::GetRunningUrl(nsIURI **result)
+{
+ if (result && m_runningUrl)
+ return m_runningUrl->QueryInterface(NS_GET_IID(nsIURI), (void**)
+ result);
+ else
+ return NS_ERROR_NULL_POINTER;
+}
+
+
+NS_IMETHODIMP nsImapProtocol::GetRunningImapURL(nsIImapUrl **aImapUrl)
+{
+ if (aImapUrl && m_runningUrl)
+ return m_runningUrl->QueryInterface(NS_GET_IID(nsIImapUrl), (void**) aImapUrl);
+ else
+ return NS_ERROR_NULL_POINTER;
+
+}
+
+/*
+ * Writes the data contained in dataBuffer into the current output stream. It also informs
+ * the transport layer that this data is now available for transmission.
+ * Returns a positive number for success, 0 for failure (not all the bytes were written to the
+ * stream, etc). We need to make another pass through this file to install an error system (mscott)
+ */
+
+nsresult nsImapProtocol::SendData(const char * dataBuffer, bool aSuppressLogging)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+
+ if (!m_transport)
+ {
+ Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
+ // the connection died unexpectedly! so clear the open connection flag
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (dataBuffer && m_outputStream)
+ {
+ m_currentCommand = dataBuffer;
+ if (!aSuppressLogging)
+ Log("SendData", nullptr, dataBuffer);
+ else
+ Log("SendData", nullptr, "Logging suppressed for this command (it probably contained authentication information)");
+
+ {
+ // don't allow someone to close the stream/transport out from under us
+ // this can happen when the ui thread calls TellThreadToDie.
+ PR_CEnterMonitor(this);
+ uint32_t n;
+ if (m_outputStream)
+ rv = m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer), &n);
+ PR_CExitMonitor(this);
+ }
+ if (NS_FAILED(rv))
+ {
+ Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
+ // the connection died unexpectedly! so clear the open connection flag
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(rv);
+ if (m_runningUrl && !m_retryUrlOnError)
+ {
+ bool alreadyRerunningUrl;
+ m_runningUrl->GetRerunningUrl(&alreadyRerunningUrl);
+ if (!alreadyRerunningUrl)
+ {
+ m_runningUrl->SetRerunningUrl(true);
+ m_retryUrlOnError = true;
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Begin protocol state machine functions...
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+ // ProcessProtocolState - we override this only so we'll link - it should never get called.
+
+nsresult nsImapProtocol::ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length)
+{
+ return NS_OK;
+}
+
+class UrlListenerNotifierEvent : public mozilla::Runnable
+{
+public:
+ UrlListenerNotifierEvent(nsIMsgMailNewsUrl *aUrl, nsIImapProtocol *aProtocol)
+ : mUrl(aUrl), mProtocol(aProtocol)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mUrl)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ mUrl->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_TRUE(folder, NS_OK);
+ nsCOMPtr<nsIImapMailFolderSink> folderSink(do_QueryInterface(folder));
+ // This causes the url listener to get OnStart and Stop notifications.
+ folderSink->SetUrlState(mProtocol, mUrl, true, false, NS_OK);
+ folderSink->SetUrlState(mProtocol, mUrl, false, false, NS_OK);
+ }
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIMsgMailNewsUrl> mUrl;
+ nsCOMPtr<nsIImapProtocol> mProtocol;
+};
+
+
+bool nsImapProtocol::TryToRunUrlLocally(nsIURI *aURL, nsISupports *aConsumer)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aURL, &rv));
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL);
+ nsCString messageIdString;
+ imapUrl->GetListOfMessageIds(messageIdString);
+ bool useLocalCache = false;
+ if (!messageIdString.IsEmpty() && !HandlingMultipleMessages(messageIdString))
+ {
+ nsImapAction action;
+ imapUrl->GetImapAction(&action);
+ nsCOMPtr <nsIMsgFolder> folder;
+ mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_TRUE(folder, false);
+
+ folder->HasMsgOffline(strtoul(messageIdString.get(), nullptr, 10), &useLocalCache);
+ mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+ // We're downloading a single message for offline use, and it's
+ // already offline. So we shouldn't do anything, but we do
+ // need to notify the url listener.
+ if (useLocalCache && action == nsIImapUrl::nsImapMsgDownloadForOffline)
+ {
+ nsCOMPtr<nsIRunnable> event = new UrlListenerNotifierEvent(mailnewsUrl,
+ this);
+ // Post this as an event because it can lead to re-entrant calls to
+ // LoadNextQueuedUrl if the listener runs a new url.
+ if (event)
+ NS_DispatchToCurrentThread(event);
+ return true;
+ }
+ }
+ if (!useLocalCache)
+ return false;
+
+ nsCOMPtr<nsIImapMockChannel> mockChannel;
+ imapUrl->GetMockChannel(getter_AddRefs(mockChannel));
+ if (!mockChannel)
+ return false;
+
+ nsImapMockChannel *imapChannel = static_cast<nsImapMockChannel *>(mockChannel.get());
+ if (!imapChannel)
+ return false;
+
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ imapChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (!loadGroup) // if we don't have one, the url will snag one from the msg window...
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ if (loadGroup)
+ loadGroup->RemoveRequest((nsIRequest *) mockChannel, nullptr /* context isupports */, NS_OK);
+
+ if (imapChannel->ReadFromLocalCache())
+ {
+ (void) imapChannel->NotifyStartEndReadFromCache(true);
+ return true;
+ }
+ return false;
+}
+
+
+// LoadImapUrl takes a url, initializes all of our url specific data by calling SetupUrl.
+// If we don't have a connection yet, we open the connection. Finally, we signal the
+// url to run monitor to let the imap main thread loop process the current url (it is waiting
+// on this monitor). There is a contract that the imap thread has already been started b4 we
+// attempt to load a url....
+NS_IMETHODIMP nsImapProtocol::LoadImapUrl(nsIURI * aURL, nsISupports * aConsumer)
+{
+ nsresult rv;
+ if (aURL)
+ {
+#ifdef DEBUG_bienvenu
+ printf("loading url %s\n", aURL->GetSpecOrDefault().get());
+#endif
+ if (TryToRunUrlLocally(aURL, aConsumer))
+ return NS_OK;
+ m_urlInProgress = true;
+ m_imapMailFolderSink = nullptr;
+ rv = SetupWithUrl(aURL, aConsumer);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error setting up imap url");
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = SetupSinkProxy(); // generate proxies for all of the event sinks in the url
+ if (NS_FAILED(rv)) // URL can be invalid.
+ return rv;
+
+ m_lastActiveTime = PR_Now();
+ if (m_transport && m_runningUrl)
+ {
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+ // if we're shutting down, and not running the kinds of urls we run at
+ // shutdown, then this should fail because running urls during
+ // shutdown will very likely fail and potentially hang.
+ nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool shuttingDown = false;
+ (void) accountMgr->GetShutdownInProgress(&shuttingDown);
+ if (shuttingDown && imapAction != nsIImapUrl::nsImapExpungeFolder &&
+ imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
+ imapAction != nsIImapUrl::nsImapDeleteFolder)
+ return NS_ERROR_FAILURE;
+
+ // if we're running a select or delete all, do a noop first.
+ // this should really be in the connection cache code when we know
+ // we're pulling out a selected state connection, but maybe we
+ // can get away with this.
+ m_needNoop = (imapAction == nsIImapUrl::nsImapSelectFolder || imapAction == nsIImapUrl::nsImapDeleteAllMsgs);
+
+ // We now have a url to run so signal the monitor for url ready to be processed...
+ ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor);
+ m_nextUrlReadyToRun = true;
+ urlReadyMon.Notify();
+
+ } // if we have an imap url and a transport
+ else
+ NS_ASSERTION(false, "missing channel or running url");
+
+ } // if we received a url!
+ else
+ rv = NS_ERROR_UNEXPECTED;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapProtocol::IsBusy(bool *aIsConnectionBusy,
+ bool *isInboxConnection)
+{
+ if (!aIsConnectionBusy || !isInboxConnection)
+ return NS_ERROR_NULL_POINTER;
+ nsresult rv = NS_OK;
+ *aIsConnectionBusy = false;
+ *isInboxConnection = false;
+ if (!m_transport)
+ {
+ // this connection might not be fully set up yet.
+ rv = NS_ERROR_FAILURE;
+ }
+ else
+ {
+ if (m_urlInProgress) // do we have a url? That means we're working on it...
+ *aIsConnectionBusy = true;
+
+ if (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected && GetServerStateParser().GetSelectedMailboxName() &&
+ PL_strcasecmp(GetServerStateParser().GetSelectedMailboxName(),
+ "Inbox") == 0)
+ *isInboxConnection = true;
+
+ }
+ return rv;
+}
+
+#define IS_SUBSCRIPTION_RELATED_ACTION(action) (action == nsIImapUrl::nsImapSubscribe\
+|| action == nsIImapUrl::nsImapUnsubscribe || action == nsIImapUrl::nsImapDiscoverAllBoxesUrl || action == nsIImapUrl::nsImapListFolder)
+
+
+// canRunUrl means the connection is not busy, and is in the selected state
+// for the desired folder (or authenticated).
+// has to wait means it's in the right selected state, but busy.
+NS_IMETHODIMP nsImapProtocol::CanHandleUrl(nsIImapUrl * aImapUrl,
+ bool * aCanRunUrl,
+ bool * hasToWait)
+{
+ if (!aCanRunUrl || !hasToWait || !aImapUrl)
+ return NS_ERROR_NULL_POINTER;
+ nsresult rv = NS_OK;
+ MutexAutoLock mon(mLock);
+
+ *aCanRunUrl = false; // assume guilty until proven otherwise...
+ *hasToWait = false;
+
+ if (DeathSignalReceived())
+ return NS_ERROR_FAILURE;
+
+ bool isBusy = false;
+ bool isInboxConnection = false;
+
+ if (!m_transport)
+ {
+ // this connection might not be fully set up yet.
+ return NS_ERROR_FAILURE;
+ }
+ IsBusy(&isBusy, &isInboxConnection);
+ bool inSelectedState = GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected;
+
+ nsAutoCString curSelectedUrlFolderName;
+ nsAutoCString pendingUrlFolderName;
+ if (inSelectedState)
+ curSelectedUrlFolderName = GetServerStateParser().GetSelectedMailboxName();
+
+ if (isBusy)
+ {
+ nsImapState curUrlImapState;
+ NS_ASSERTION(m_runningUrl,"isBusy, but no running url.");
+ if (m_runningUrl)
+ {
+ m_runningUrl->GetRequiredImapState(&curUrlImapState);
+ if (curUrlImapState == nsIImapUrl::nsImapSelectedState)
+ {
+ char *folderName = GetFolderPathString();
+ if (!curSelectedUrlFolderName.Equals(folderName))
+ pendingUrlFolderName.Assign(folderName);
+ inSelectedState = true;
+ PR_Free(folderName);
+ }
+ }
+ }
+
+ nsImapState imapState;
+ nsImapAction actionForProposedUrl;
+ aImapUrl->GetImapAction(&actionForProposedUrl);
+ aImapUrl->GetRequiredImapState(&imapState);
+
+ // OK, this is a bit of a hack - we're going to pretend that
+ // these types of urls requires a selected state connection on
+ // the folder in question. This isn't technically true,
+ // but we would much rather use that connection for several reasons,
+ // one is that some UW servers require us to use that connection
+ // the other is that we don't want to leave a connection dangling in
+ // the selected state for the deleted folder.
+ // If we don't find a connection in that selected state,
+ // we'll fall back to the first free connection.
+ bool isSelectedStateUrl = imapState == nsIImapUrl::nsImapSelectedState
+ || actionForProposedUrl == nsIImapUrl::nsImapDeleteFolder || actionForProposedUrl == nsIImapUrl::nsImapRenameFolder
+ || actionForProposedUrl == nsIImapUrl::nsImapMoveFolderHierarchy
+ || actionForProposedUrl == nsIImapUrl::nsImapAppendDraftFromFile
+ || actionForProposedUrl == nsIImapUrl::nsImapAppendMsgFromFile
+ || actionForProposedUrl == nsIImapUrl::nsImapFolderStatus;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(aImapUrl);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = msgUrl->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv))
+ {
+ // compare host/user between url and connection.
+ nsCString urlHostName;
+ nsCString urlUserName;
+ rv = server->GetHostName(urlHostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = server->GetUsername(urlUserName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((GetImapHostName().IsEmpty() ||
+ urlHostName.Equals(GetImapHostName(), nsCaseInsensitiveCStringComparator())) &&
+ (GetImapUserName().IsEmpty() ||
+ urlUserName.Equals(GetImapUserName(), nsCaseInsensitiveCStringComparator())))
+ {
+ if (isSelectedStateUrl)
+ {
+ if (inSelectedState)
+ {
+ // *** jt - in selected state can only run url with
+ // matching foldername
+ char *folderNameForProposedUrl = nullptr;
+ rv = aImapUrl->CreateServerSourceFolderPathString(
+ &folderNameForProposedUrl);
+ if (NS_SUCCEEDED(rv) && folderNameForProposedUrl)
+ {
+ bool isInbox =
+ PL_strcasecmp("Inbox", folderNameForProposedUrl) == 0;
+ if (!curSelectedUrlFolderName.IsEmpty() || !pendingUrlFolderName.IsEmpty())
+ {
+ bool matched = isInbox ?
+ PL_strcasecmp(curSelectedUrlFolderName.get(),
+ folderNameForProposedUrl) == 0 :
+ PL_strcmp(curSelectedUrlFolderName.get(),
+ folderNameForProposedUrl) == 0;
+ if (!matched && !pendingUrlFolderName.IsEmpty())
+ {
+ matched = isInbox ?
+ PL_strcasecmp(pendingUrlFolderName.get(),
+ folderNameForProposedUrl) == 0 :
+ PL_strcmp(pendingUrlFolderName.get(),
+ folderNameForProposedUrl) == 0;
+ }
+ if (matched)
+ {
+ if (isBusy)
+ *hasToWait = true;
+ else
+ *aCanRunUrl = true;
+ }
+ }
+ }
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("proposed url = %s folder for connection %s has To Wait = %s can run = %s",
+ folderNameForProposedUrl, curSelectedUrlFolderName.get(),
+ (*hasToWait) ? "TRUE" : "FALSE", (*aCanRunUrl) ? "TRUE" : "FALSE"));
+ PR_FREEIF(folderNameForProposedUrl);
+ }
+ }
+ else // *** jt - an authenticated state url can be run in either
+ // authenticated or selected state
+ {
+ nsImapAction actionForRunningUrl;
+
+ // If proposed url is subscription related, and we are currently running
+ // a subscription url, then we want to queue the proposed url after the current url.
+ // Otherwise, we can run this url if we're not busy.
+ // If we never find a running subscription-related url, the caller will
+ // just use whatever free connection it can find, which is what we want.
+ if (IS_SUBSCRIPTION_RELATED_ACTION(actionForProposedUrl))
+ {
+ if (isBusy && m_runningUrl)
+ {
+ m_runningUrl->GetImapAction(&actionForRunningUrl);
+ if (IS_SUBSCRIPTION_RELATED_ACTION(actionForRunningUrl))
+ {
+ *aCanRunUrl = false;
+ *hasToWait = true;
+ }
+ }
+ }
+ else
+ {
+ if (!isBusy)
+ *aCanRunUrl = true;
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+
+// Command tag handling stuff
+void nsImapProtocol::IncrementCommandTagNumber()
+{
+ sprintf(m_currentServerCommandTag, "%u", ++m_currentServerCommandTagNumber);
+}
+
+const char *nsImapProtocol::GetServerCommandTag()
+{
+ return m_currentServerCommandTag;
+}
+
+void nsImapProtocol::ProcessSelectedStateURL()
+{
+ nsCString mailboxName;
+ bool bMessageIdsAreUids = true;
+ bool moreHeadersToDownload;
+ imapMessageFlagsType msgFlags = 0;
+ nsCString urlHost;
+
+ // this can't fail, can it?
+ nsresult res;
+ res = m_runningUrl->GetImapAction(&m_imapAction);
+ m_runningUrl->MessageIdsAreUids(&bMessageIdsAreUids);
+ m_runningUrl->GetMsgFlags(&msgFlags);
+ m_runningUrl->GetMoreHeadersToDownload(&moreHeadersToDownload);
+
+ res = CreateServerSourceFolderPathString(getter_Copies(mailboxName));
+ if (NS_FAILED(res))
+ Log("ProcessSelectedStateURL", nullptr, "error getting source folder path string");
+
+ if (NS_SUCCEEDED(res) && !DeathSignalReceived())
+ {
+ bool selectIssued = false;
+ if (GetServerStateParser().GetIMAPstate() == nsImapServerResponseParser::kFolderSelected)
+ {
+ if (GetServerStateParser().GetSelectedMailboxName() &&
+ PL_strcmp(GetServerStateParser().GetSelectedMailboxName(),
+ mailboxName.get()))
+ { // we are selected in another folder
+ if (m_closeNeededBeforeSelect)
+ Close();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ selectIssued = true;
+ SelectMailbox(mailboxName.get());
+ }
+ }
+ else if (!GetServerStateParser().GetSelectedMailboxName())
+ { // why are we in the selected state with no box name?
+ SelectMailbox(mailboxName.get());
+ selectIssued = true;
+ }
+ else if (moreHeadersToDownload && m_imapMailFolderSink) // we need to fetch older headers
+ {
+ nsMsgKey *msgIdList = nullptr;
+ uint32_t msgCount = 0;
+ bool more;
+ m_imapMailFolderSink->GetMsgHdrsToDownload(&more, &m_progressCount,
+ &msgCount, &msgIdList);
+ if (msgIdList)
+ {
+ FolderHeaderDump(msgIdList, msgCount);
+ NS_Free(msgIdList);
+ m_runningUrl->SetMoreHeadersToDownload(more);
+ // We're going to be re-running this url.
+ if (more)
+ m_runningUrl->SetRerunningUrl(true);
+ }
+ HeaderFetchCompleted();
+ }
+ else
+ {
+ // get new message counts, if any, from server
+ if (m_needNoop)
+ {
+ // For some IMAP servers, to detect new email we must send imap
+ // SELECT even if already SELECTed on the same mailbox. For other
+ // servers that simply don't support IDLE, doing select here will
+ // cause emails to be properly marked "read" after they have been
+ // read in another email client.
+ if (m_forceSelect)
+ {
+ SelectMailbox(mailboxName.get());
+ selectIssued = true;
+ }
+
+ m_noopCount++;
+ if ((gPromoteNoopToCheckCount > 0 && (m_noopCount % gPromoteNoopToCheckCount) == 0) ||
+ CheckNeeded())
+ Check();
+ else
+ Noop(); // I think this is needed when we're using a cached connection
+ m_needNoop = false;
+ }
+ }
+ }
+ else
+ {
+ // go to selected state
+ SelectMailbox(mailboxName.get());
+ selectIssued = GetServerStateParser().LastCommandSuccessful();
+ }
+
+ if (selectIssued)
+ RefreshACLForFolderIfNecessary(mailboxName.get());
+
+ bool uidValidityOk = true;
+ if (GetServerStateParser().LastCommandSuccessful() && selectIssued &&
+ (m_imapAction != nsIImapUrl::nsImapSelectFolder) && (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder))
+ {
+
+ // error on the side of caution, if the fe event fails to set uidStruct->returnValidity, then assume that UIDVALIDITY
+ // did not roll. This is a common case event for attachments that are fetched within a browser context.
+ if (!DeathSignalReceived())
+ uidValidityOk = m_uidValidity == kUidUnknown ||
+ m_uidValidity == GetServerStateParser().FolderUID();
+ }
+
+ if (!uidValidityOk)
+ Log("ProcessSelectedStateURL", nullptr, "uid validity not ok");
+ if (GetServerStateParser().LastCommandSuccessful() && !DeathSignalReceived() && (uidValidityOk || m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs))
+ {
+ if (GetServerStateParser().CurrentFolderReadOnly())
+ {
+ Log("ProcessSelectedStateURL", nullptr, "current folder read only");
+ if (m_imapAction == nsIImapUrl::nsImapAddMsgFlags ||
+ m_imapAction == nsIImapUrl::nsImapSubtractMsgFlags)
+ {
+ bool canChangeFlag = false;
+ if (GetServerStateParser().ServerHasACLCapability() && m_imapMailFolderSink)
+ {
+ uint32_t aclFlags = 0;
+
+ if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags))
+ && aclFlags != 0) // make sure we have some acl flags
+ canChangeFlag = ((msgFlags & kImapMsgSeenFlag) && (aclFlags & IMAP_ACL_STORE_SEEN_FLAG));
+ }
+ else
+ canChangeFlag = (GetServerStateParser().SettablePermanentFlags() & msgFlags) == msgFlags;
+ if (!canChangeFlag)
+ return;
+ }
+ if (m_imapAction == nsIImapUrl::nsImapExpungeFolder || m_imapAction == nsIImapUrl::nsImapDeleteMsg ||
+ m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs)
+ return;
+ }
+ switch (m_imapAction)
+ {
+ case nsIImapUrl::nsImapLiteSelectFolder:
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ m_imapMailFolderSink && !moreHeadersToDownload)
+ {
+ m_imapMailFolderSink->SetUidValidity(GetServerStateParser().FolderUID());
+ ProcessMailboxUpdate(false); // handle uidvalidity change
+ }
+ break;
+ case nsIImapUrl::nsImapSaveMessageToDisk:
+ case nsIImapUrl::nsImapMsgFetch:
+ case nsIImapUrl::nsImapMsgFetchPeek:
+ case nsIImapUrl::nsImapMsgDownloadForOffline:
+ case nsIImapUrl::nsImapMsgPreview:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ // we don't want to send the flags back in a group
+ if (HandlingMultipleMessages(messageIdString) || m_imapAction == nsIImapUrl::nsImapMsgDownloadForOffline
+ || m_imapAction == nsIImapUrl::nsImapMsgPreview)
+ {
+ // multiple messages, fetch them all
+ SetProgressString("imapFolderReceivingMessageOf2");
+
+ m_progressIndex = 0;
+ m_progressCount = CountMessagesInIdString(messageIdString.get());
+
+ // we need to set this so we'll get the msg from the memory cache.
+ if (m_imapAction == nsIImapUrl::nsImapMsgFetchPeek)
+ SetContentModified(IMAP_CONTENT_NOT_MODIFIED);
+
+ FetchMessage(messageIdString,
+ (m_imapAction == nsIImapUrl::nsImapMsgPreview)
+ ? kBodyStart : kEveryThingRFC822Peek);
+ if (m_imapAction == nsIImapUrl::nsImapMsgPreview)
+ HeaderFetchCompleted();
+ SetProgressString(nullptr);
+ }
+ else
+ {
+ // A single message ID
+ nsIMAPeFetchFields whatToFetch = kEveryThingRFC822;
+ if(m_imapAction == nsIImapUrl::nsImapMsgFetchPeek)
+ whatToFetch = kEveryThingRFC822Peek;
+
+ // First, let's see if we're requesting a specific MIME part
+ char *imappart = nullptr;
+ m_runningUrl->GetImapPartToFetch(&imappart);
+ if (imappart)
+ {
+ if (bMessageIdsAreUids)
+ {
+ // We actually want a specific MIME part of the message.
+ // The Body Shell will generate it, even though we haven't downloaded it yet.
+
+ IMAP_ContentModifiedType modType = GetShowAttachmentsInline() ?
+ IMAP_CONTENT_MODIFIED_VIEW_INLINE :
+ IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS ;
+
+ RefPtr<nsIMAPBodyShell> foundShell;
+ res = m_hostSessionList->FindShellInCacheForHost(GetImapServerKey(),
+ GetServerStateParser().GetSelectedMailboxName(),
+ messageIdString.get(), modType, getter_AddRefs(foundShell));
+ if (!foundShell)
+ {
+ // The shell wasn't in the cache. Deal with this case later.
+ Log("SHELL",NULL,"Loading part, shell not found in cache!");
+ //MOZ_LOG(IMAP, out, ("BODYSHELL: Loading part, shell not found in cache!"));
+ // The parser will extract the part number from the current URL.
+ SetContentModified(modType);
+ Bodystructure(messageIdString, bMessageIdsAreUids);
+ }
+ else
+ {
+ Log("SHELL", NULL, "Loading Part, using cached shell.");
+ //MOZ_LOG(IMAP, out, ("BODYSHELL: Loading part, using cached shell."));
+ SetContentModified(modType);
+ foundShell->SetConnection(this);
+ GetServerStateParser().UseCachedShell(foundShell);
+ //Set the current uid in server state parser (in case it was used for new mail msgs earlier).
+ GetServerStateParser().SetCurrentResponseUID(strtoul(messageIdString.get(), nullptr, 10));
+ foundShell->Generate(imappart);
+ GetServerStateParser().UseCachedShell(NULL);
+ }
+ }
+ else
+ {
+ // Message IDs are not UIDs.
+ NS_ASSERTION(false, "message ids aren't uids");
+ }
+ PR_Free(imappart);
+ }
+ else
+ {
+ // downloading a single message: try to do it by bodystructure, and/or do it by chunks
+ uint32_t messageSize = GetMessageSize(messageIdString.get(), bMessageIdsAreUids);
+ // We need to check the format_out bits to see if we are allowed to leave out parts,
+ // or if we are required to get the whole thing. Some instances where we are allowed
+ // to do it by parts: when viewing a message, replying to a message, or viewing its source
+ // Some times when we're NOT allowed: when forwarding a message, saving it, moving it, etc.
+ // need to set a flag in the url, I guess, equiv to allow_content_changed.
+ bool allowedToBreakApart = true; // (ce && !DeathSignalReceived()) ? ce->URL_s->allow_content_change : false;
+ bool mimePartSelectorDetected;
+ bool urlOKToFetchByParts = false;
+ m_runningUrl->GetMimePartSelectorDetected(&mimePartSelectorDetected);
+ m_runningUrl->GetFetchPartsOnDemand(&urlOKToFetchByParts);
+
+#ifdef PR_LOGGING
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningUrl);
+ nsAutoCString urlSpec;
+ if (mailnewsurl)
+ urlSpec = mailnewsurl->GetSpecOrDefault();
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("SHELL: URL %s, OKToFetchByParts %d, allowedToBreakApart %d, ShouldFetchAllParts %d",
+ urlSpec.get(), urlOKToFetchByParts, allowedToBreakApart,
+ GetShouldFetchAllParts()));
+ }
+#endif
+
+ if (urlOKToFetchByParts &&
+ allowedToBreakApart &&
+ !GetShouldFetchAllParts() &&
+ GetServerStateParser().ServerHasIMAP4Rev1Capability() /* &&
+ !mimePartSelectorDetected */) // if a ?part=, don't do BS.
+ {
+ // OK, we're doing bodystructure
+
+ // Before fetching the bodystructure, let's check our body shell cache to see if
+ // we already have it around.
+ RefPtr<nsIMAPBodyShell> foundShell;
+ IMAP_ContentModifiedType modType = GetShowAttachmentsInline() ?
+ IMAP_CONTENT_MODIFIED_VIEW_INLINE :
+ IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS ;
+
+ bool wasStoringMsgOffline;
+ m_runningUrl->GetStoreResultsOffline(&wasStoringMsgOffline);
+ m_runningUrl->SetStoreOfflineOnFallback(wasStoringMsgOffline);
+ m_runningUrl->SetStoreResultsOffline(false);
+ SetContentModified(modType); // This will be looked at by the cache
+ if (bMessageIdsAreUids)
+ {
+ res = m_hostSessionList->FindShellInCacheForHost(GetImapServerKey(),
+ GetServerStateParser().GetSelectedMailboxName(),
+ messageIdString.get(), modType, getter_AddRefs(foundShell));
+ if (foundShell)
+ {
+ Log("SHELL",NULL,"Loading message, using cached shell.");
+ //MOZ_LOG(IMAP, out, ("BODYSHELL: Loading message, using cached shell."));
+ foundShell->SetConnection(this);
+ GetServerStateParser().UseCachedShell(foundShell);
+ //Set the current uid in server state parser (in case it was used for new mail msgs earlier).
+ GetServerStateParser().SetCurrentResponseUID(strtoul(messageIdString.get(), nullptr, 10));
+ foundShell->Generate(NULL);
+ GetServerStateParser().UseCachedShell(NULL);
+ }
+ }
+
+ if (!foundShell)
+ Bodystructure(messageIdString, bMessageIdsAreUids);
+ }
+ else
+ {
+ // Not doing bodystructure. Fetch the whole thing, and try to do
+ // it in chunks.
+ SetContentModified(IMAP_CONTENT_NOT_MODIFIED);
+ FetchTryChunking(messageIdString, whatToFetch,
+ bMessageIdsAreUids, NULL, messageSize, true);
+ }
+ }
+ if (GetServerStateParser().LastCommandSuccessful()
+ && m_imapAction != nsIImapUrl::nsImapMsgPreview
+ && m_imapAction != nsIImapUrl::nsImapMsgFetchPeek)
+ {
+ uint32_t uid = strtoul(messageIdString.get(), nullptr, 10);
+ int32_t index;
+ bool foundIt;
+ imapMessageFlagsType flags = m_flagState->GetMessageFlagsFromUID(uid, &foundIt, &index);
+ if (foundIt)
+ {
+ flags |= kImapMsgSeenFlag;
+ m_flagState->SetMessageFlags(index, flags);
+ }
+ }
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapExpungeFolder:
+ Expunge();
+ // note fall through to next cases.
+ MOZ_FALLTHROUGH;
+ case nsIImapUrl::nsImapSelectFolder:
+ case nsIImapUrl::nsImapSelectNoopFolder:
+ if (!moreHeadersToDownload)
+ ProcessMailboxUpdate(true);
+ break;
+ case nsIImapUrl::nsImapMsgHeader:
+ {
+ nsCString messageIds;
+ m_runningUrl->GetListOfMessageIds(messageIds);
+
+ FetchMessage(messageIds,
+ kHeadersRFC822andUid);
+ // if we explicitly ask for headers, as opposed to getting them as a result
+ // of selecting the folder, or biff, send the headerFetchCompleted notification
+ // to flush out the header cache.
+ HeaderFetchCompleted();
+ }
+ break;
+ case nsIImapUrl::nsImapSearch:
+ {
+ nsAutoCString searchCriteriaString;
+ m_runningUrl->CreateSearchCriteriaString(getter_Copies(searchCriteriaString));
+ Search(searchCriteriaString.get(), bMessageIdsAreUids);
+ // drop the results on the floor for now
+ }
+ break;
+ case nsIImapUrl::nsImapUserDefinedMsgCommand:
+ {
+ nsCString messageIdString;
+ nsCString command;
+
+ m_runningUrl->GetCommand(command);
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ IssueUserDefinedMsgCommand(command.get(), messageIdString.get());
+ }
+ break;
+ case nsIImapUrl::nsImapUserDefinedFetchAttribute:
+ {
+ nsCString messageIdString;
+ nsCString attribute;
+
+ m_runningUrl->GetCustomAttributeToFetch(attribute);
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ FetchMsgAttribute(messageIdString, attribute);
+ }
+ break;
+ case nsIImapUrl::nsImapMsgStoreCustomKeywords:
+ {
+ // if the server doesn't support user defined flags, don't try to set them.
+ uint16_t userFlags;
+ GetSupportedUserFlags(&userFlags);
+ if (! (userFlags & kImapMsgSupportUserFlag))
+ break;
+ nsCString messageIdString;
+ nsCString addFlags;
+ nsCString subtractFlags;
+
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ m_runningUrl->GetCustomAddFlags(addFlags);
+ m_runningUrl->GetCustomSubtractFlags(subtractFlags);
+ if (!addFlags.IsEmpty())
+ {
+ nsAutoCString storeString("+FLAGS (");
+ storeString.Append(addFlags);
+ storeString.Append(")");
+ Store(messageIdString, storeString.get(), true);
+ }
+ if (!subtractFlags.IsEmpty())
+ {
+ nsAutoCString storeString("-FLAGS (");
+ storeString.Append(subtractFlags);
+ storeString.Append(")");
+ Store(messageIdString, storeString.get(), true);
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapDeleteMsg:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProgressEventFunctionUsingName(HandlingMultipleMessages(messageIdString) ?
+ "imapDeletingMessages" :
+ "imapDeletingMessage");
+
+ Store(messageIdString, "+FLAGS (\\Deleted)", bMessageIdsAreUids);
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ //delete_message_struct *deleteMsg = (delete_message_struct *) PR_Malloc (sizeof(delete_message_struct));
+ // convert name back from utf7
+ nsCString canonicalName;
+ const char *selectedMailboxName = GetServerStateParser().GetSelectedMailboxName();
+ if (selectedMailboxName)
+ {
+ m_runningUrl->AllocateCanonicalPath(selectedMailboxName,
+ kOnlineHierarchySeparatorUnknown, getter_Copies(canonicalName));
+ }
+
+ if (m_imapMessageSink)
+ m_imapMessageSink->NotifyMessageDeleted(canonicalName.get(), false, messageIdString.get());
+ // notice we don't wait for this to finish...
+ }
+ else
+ HandleMemoryFailure();
+ }
+ break;
+ case nsIImapUrl::nsImapDeleteFolderAndMsgs:
+ DeleteFolderAndMsgs(mailboxName.get());
+ break;
+ case nsIImapUrl::nsImapDeleteAllMsgs:
+ {
+ uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages();
+ if (numberOfMessages)
+ {
+ Store(NS_LITERAL_CSTRING("1:*"), "+FLAGS.SILENT (\\Deleted)",
+ false); // use sequence #'s
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ Expunge(); // expunge messages with deleted flag
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ // convert name back from utf7
+ nsCString canonicalName;
+ const char *selectedMailboxName = GetServerStateParser().GetSelectedMailboxName();
+ if (selectedMailboxName )
+ {
+ m_runningUrl->AllocateCanonicalPath(selectedMailboxName,
+ kOnlineHierarchySeparatorUnknown, getter_Copies(canonicalName));
+ }
+
+ if (m_imapMessageSink)
+ m_imapMessageSink->NotifyMessageDeleted(canonicalName.get(), true, nullptr);
+ }
+
+ }
+ bool deleteSelf = false;
+ DeleteSubFolders(mailboxName.get(), deleteSelf); // don't delete self
+ }
+ break;
+ case nsIImapUrl::nsImapAppendDraftFromFile:
+ {
+ OnAppendMsgFromFile();
+ }
+ break;
+ case nsIImapUrl::nsImapAddMsgFlags:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids,
+ msgFlags, true);
+ }
+ break;
+ case nsIImapUrl::nsImapSubtractMsgFlags:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids,
+ msgFlags, false);
+ }
+ break;
+ case nsIImapUrl::nsImapSetMsgFlags:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids,
+ msgFlags, true);
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids,
+ ~msgFlags, false);
+ }
+ break;
+ case nsIImapUrl::nsImapBiff:
+ PeriodicBiff();
+ break;
+ case nsIImapUrl::nsImapOnlineCopy:
+ case nsIImapUrl::nsImapOnlineMove:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ char *destinationMailbox = OnCreateServerDestinationFolderPathString();
+
+ if (destinationMailbox)
+ {
+ if (m_imapAction == nsIImapUrl::nsImapOnlineMove)
+ {
+ if (HandlingMultipleMessages(messageIdString))
+ ProgressEventFunctionUsingNameWithString("imapMovingMessages", destinationMailbox);
+ else
+ ProgressEventFunctionUsingNameWithString("imapMovingMessage", destinationMailbox);
+ }
+ else {
+ if (HandlingMultipleMessages(messageIdString))
+ ProgressEventFunctionUsingNameWithString("imapCopyingMessages", destinationMailbox);
+ else
+ ProgressEventFunctionUsingNameWithString("imapCopyingMessage", destinationMailbox);
+ }
+ Copy(messageIdString.get(), destinationMailbox, bMessageIdsAreUids);
+ PR_FREEIF( destinationMailbox);
+ ImapOnlineCopyState copyState;
+ if (DeathSignalReceived())
+ copyState = ImapOnlineCopyStateType::kInterruptedState;
+ else
+ copyState = GetServerStateParser().LastCommandSuccessful() ?
+ (ImapOnlineCopyState) ImapOnlineCopyStateType::kSuccessfulCopy :
+ (ImapOnlineCopyState) ImapOnlineCopyStateType::kFailedCopy;
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyState);
+ // Don't mark message 'Deleted' for AOL servers or standard imap servers
+ // that support MOVE since we already issued an 'xaol-move' or 'move' command.
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ (m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ !(GetServerStateParser().ServerIsAOLServer() ||
+ GetServerStateParser().GetCapabilityFlag() & kHasMoveCapability))
+ {
+ // Simulate MOVE for servers that don't support MOVE: do COPY-DELETE-EXPUNGE.
+ Store(messageIdString, "+FLAGS (\\Deleted \\Seen)",
+ bMessageIdsAreUids);
+ bool storeSuccessful = GetServerStateParser().LastCommandSuccessful();
+ if (storeSuccessful)
+ {
+ if(gExpungeAfterDelete)
+ {
+ // This will expunge all emails marked as deleted in mailbox,
+ // not just the ones marked as deleted above.
+ Expunge();
+ }
+ else
+ {
+ // Check if UIDPLUS capable so we can just expunge emails we just
+ // copied and marked as deleted. This prevents expunging emails
+ // that other clients may have marked as deleted in the mailbox
+ // and don't want them to disappear.
+ // Only do UidExpunge() when user selected delete method is "Move
+ // it to this folder" or "Remove it immediately", not when the
+ // delete method is "Just mark it as deleted".
+ if (!GetShowDeletedMessages() &&
+ (GetServerStateParser().GetCapabilityFlag() & kUidplusCapability))
+ {
+ UidExpunge(messageIdString);
+ }
+ }
+ }
+ if (m_imapMailFolderSink)
+ {
+ copyState = storeSuccessful ? (ImapOnlineCopyState) ImapOnlineCopyStateType::kSuccessfulDelete
+ : (ImapOnlineCopyState) ImapOnlineCopyStateType::kFailedDelete;
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyState);
+ }
+ }
+ }
+ else
+ HandleMemoryFailure();
+ }
+ break;
+ case nsIImapUrl::nsImapOnlineToOfflineCopy:
+ case nsIImapUrl::nsImapOnlineToOfflineMove:
+ {
+ nsCString messageIdString;
+ nsresult rv = m_runningUrl->GetListOfMessageIds(messageIdString);
+ if (NS_SUCCEEDED(rv))
+ {
+ SetProgressString("imapFolderReceivingMessageOf2");
+ m_progressIndex = 0;
+ m_progressCount = CountMessagesInIdString(messageIdString.get());
+
+ FetchMessage(messageIdString, kEveryThingRFC822Peek);
+
+ SetProgressString(nullptr);
+ if (m_imapMailFolderSink)
+ {
+ ImapOnlineCopyState copyStatus;
+ copyStatus = GetServerStateParser().LastCommandSuccessful() ?
+ ImapOnlineCopyStateType::kSuccessfulCopy : ImapOnlineCopyStateType::kFailedCopy;
+
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus);
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ (m_imapAction == nsIImapUrl::nsImapOnlineToOfflineMove))
+ {
+ Store(messageIdString, "+FLAGS (\\Deleted \\Seen)",bMessageIdsAreUids);
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ copyStatus = ImapOnlineCopyStateType::kSuccessfulDelete;
+ if (gExpungeAfterDelete)
+ Expunge();
+ }
+ else
+ copyStatus = ImapOnlineCopyStateType::kFailedDelete;
+
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus);
+ }
+ }
+ }
+ else
+ HandleMemoryFailure();
+ }
+ break;
+ default:
+ if (GetServerStateParser().LastCommandSuccessful() && !uidValidityOk)
+ ProcessMailboxUpdate(false); // handle uidvalidity change
+ break;
+ }
+ }
+ }
+ else if (!DeathSignalReceived())
+ HandleMemoryFailure();
+}
+
+nsresult nsImapProtocol::BeginMessageDownLoad(
+ uint32_t total_message_size, // for user, headers and body
+ const char *content_type)
+{
+ nsresult rv = NS_OK;
+ char *sizeString = PR_smprintf("OPEN Size: %ld", total_message_size);
+ Log("STREAM",sizeString,"Begin Message Download Stream");
+ PR_Free(sizeString);
+ // start counting how many bytes we see in this message after all transformations
+ m_bytesToChannel = 0;
+
+ if (content_type)
+ {
+ m_fromHeaderSeen = false;
+ if (GetServerStateParser().GetDownloadingHeaders())
+ {
+ // if we get multiple calls to BeginMessageDownload w/o intervening
+ // calls to NormalEndMessageDownload or Abort, then we're just
+ // going to fake a NormalMessageEndDownload. This will most likely
+ // cause an empty header to get written to the db, and the user
+ // will have to delete the empty header themselves, which
+ // should remove the message from the server as well.
+ if (m_curHdrInfo)
+ NormalMessageEndDownload();
+ if (!m_curHdrInfo)
+ m_curHdrInfo = m_hdrDownloadCache->StartNewHdr();
+ if (m_curHdrInfo)
+ m_curHdrInfo->SetMsgSize(total_message_size);
+ return NS_OK;
+ }
+ // if we have a mock channel, that means we have a channel listener who wants the
+ // message. So set up a pipe. We'll write the messsage into one end of the pipe
+ // and they will read it out of the other end.
+ else if (m_channelListener)
+ {
+ // create a pipe to pump the message into...the output will go to whoever
+ // is consuming the message display
+ // we create an "infinite" pipe in case we get extremely long lines from the imap server,
+ // and the consumer is waiting for a whole line
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+ rv = pipe->Init(false, false, 4096, PR_UINT32_MAX);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // These always succeed because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(m_channelInputStream)));
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(m_channelOutputStream)));
+ }
+ // else, if we are saving the message to disk!
+ else if (m_imapMessageSink /* && m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk */)
+ {
+ // we get here when download the inbox for offline use
+ nsCOMPtr<nsIFile> file;
+ bool addDummyEnvelope = true;
+ nsCOMPtr<nsIMsgMessageUrl> msgurl = do_QueryInterface(m_runningUrl);
+ msgurl->GetMessageFile(getter_AddRefs(file));
+ msgurl->GetAddDummyEnvelope(&addDummyEnvelope);
+ if (file)
+ rv = m_imapMessageSink->SetupMsgWriteStream(file, addDummyEnvelope);
+ }
+ if (m_imapMailFolderSink && m_runningUrl)
+ {
+ nsCOMPtr <nsISupports> copyState;
+ if (m_runningUrl)
+ {
+ m_runningUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) // only need this notification during copy
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailurl = do_QueryInterface(m_runningUrl);
+ m_imapMailFolderSink->StartMessage(mailurl);
+ }
+ }
+ }
+
+ }
+ else
+ HandleMemoryFailure();
+ return rv;
+}
+
+void
+nsImapProtocol::GetShouldDownloadAllHeaders(bool *aResult)
+{
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetShouldDownloadAllHeaders(aResult);
+}
+
+void
+nsImapProtocol::GetArbitraryHeadersToDownload(nsCString &aResult)
+{
+ if (m_imapServerSink)
+ m_imapServerSink->GetArbitraryHeaders(aResult);
+}
+
+void
+nsImapProtocol::AdjustChunkSize()
+{
+ int32_t deltaInSeconds;
+ PRTime2Seconds(m_endTime - m_startTime, &deltaInSeconds);
+ m_trackingTime = false;
+ if (deltaInSeconds < 0)
+ return; // bogus for some reason
+
+ if (deltaInSeconds <= m_tooFastTime && m_curFetchSize >= m_chunkSize)
+ {
+ m_chunkSize += m_chunkAddSize;
+ m_chunkThreshold = m_chunkSize + (m_chunkSize / 2);
+ // we used to have a max for the chunk size - I don't think that's needed.
+ }
+ else if (deltaInSeconds <= m_idealTime)
+ return;
+ else
+ {
+ if (m_chunkSize > m_chunkStartSize)
+ m_chunkSize = m_chunkStartSize;
+ else if (m_chunkSize > (m_chunkAddSize * 2))
+ m_chunkSize -= m_chunkAddSize;
+ m_chunkThreshold = m_chunkSize + (m_chunkSize / 2);
+ }
+ // remember these new values globally so new connections
+ // can take advantage of them.
+ if (gChunkSize != m_chunkSize)
+ {
+ // will cause chunk size pref to be written in CloseStream.
+ gChunkSizeDirty = true;
+ gChunkSize = m_chunkSize;
+ gChunkThreshold = m_chunkThreshold;
+ }
+}
+
+// authenticated state commands
+
+// escape any backslashes or quotes. Backslashes are used a lot with our NT server
+void nsImapProtocol::CreateEscapedMailboxName(const char *rawName, nsCString &escapedName)
+{
+ escapedName.Assign(rawName);
+
+ for (int32_t strIndex = 0; *rawName; strIndex++)
+ {
+ char currentChar = *rawName++;
+ if ((currentChar == '\\') || (currentChar == '\"'))
+ escapedName.Insert('\\', strIndex++);
+ }
+}
+void nsImapProtocol::SelectMailbox(const char *mailboxName)
+{
+ ProgressEventFunctionUsingNameWithString("imapStatusSelectingMailbox", mailboxName);
+ IncrementCommandTagNumber();
+
+ m_closeNeededBeforeSelect = false; // initial value
+ GetServerStateParser().ResetFlagInfo();
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsCString commandBuffer(GetServerCommandTag());
+ commandBuffer.Append(" select \"");
+ commandBuffer.Append(escapedName.get());
+ commandBuffer.Append("\"");
+ if (UseCondStore())
+ commandBuffer.Append(" (CONDSTORE)");
+ commandBuffer.Append(CRLF);
+
+ nsresult res;
+ res = SendData(commandBuffer.get());
+ if (NS_FAILED(res)) return;
+ ParseIMAPandCheckForNewMail();
+
+ int32_t numOfMessagesInFlagState = 0;
+ nsImapAction imapAction;
+ m_flagState->GetNumberOfMessages(&numOfMessagesInFlagState);
+ res = m_runningUrl->GetImapAction(&imapAction);
+ // if we've selected a mailbox, and we're not going to do an update because of the
+ // url type, but don't have the flags, go get them!
+ if (GetServerStateParser().LastCommandSuccessful() && NS_SUCCEEDED(res) &&
+ imapAction != nsIImapUrl::nsImapSelectFolder && imapAction != nsIImapUrl::nsImapExpungeFolder
+ && imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
+ imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
+ ((GetServerStateParser().NumberOfMessages() != numOfMessagesInFlagState) && (numOfMessagesInFlagState == 0)))
+ {
+ ProcessMailboxUpdate(false);
+ }
+}
+
+// Please call only with a single message ID
+void nsImapProtocol::Bodystructure(const nsCString &messageId, bool idIsUid)
+{
+ IncrementCommandTagNumber();
+
+ nsCString commandString(GetServerCommandTag());
+ if (idIsUid)
+ commandString.Append(" UID");
+ commandString.Append(" fetch ");
+
+ commandString.Append(messageId);
+ commandString.Append(" (BODYSTRUCTURE)" CRLF);
+
+ nsresult rv = SendData(commandString.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString.get());
+}
+
+void nsImapProtocol::PipelinedFetchMessageParts(const char *uid, nsIMAPMessagePartIDArray *parts)
+{
+ // assumes no chunking
+
+ // build up a string to fetch
+ nsCString stringToFetch, what;
+ uint32_t currentPartNum = 0;
+ while ((parts->GetNumParts() > currentPartNum) && !DeathSignalReceived())
+ {
+ nsIMAPMessagePartID *currentPart = parts->GetPart(currentPartNum);
+ if (currentPart)
+ {
+ // Do things here depending on the type of message part
+ // Append it to the fetch string
+ if (currentPartNum > 0)
+ stringToFetch.Append(" ");
+
+ switch (currentPart->GetFields())
+ {
+ case kMIMEHeader:
+ what = "BODY.PEEK[";
+ what.Append(currentPart->GetPartNumberString());
+ what.Append(".MIME]");
+ stringToFetch.Append(what);
+ break;
+ case kRFC822HeadersOnly:
+ if (currentPart->GetPartNumberString())
+ {
+ what = "BODY.PEEK[";
+ what.Append(currentPart->GetPartNumberString());
+ what.Append(".HEADER]");
+ stringToFetch.Append(what);
+ }
+ else
+ {
+ // headers for the top-level message
+ stringToFetch.Append("BODY.PEEK[HEADER]");
+ }
+ break;
+ default:
+ NS_ASSERTION(false, "we should only be pipelining MIME headers and Message headers");
+ break;
+ }
+
+ }
+ currentPartNum++;
+ }
+
+ // Run the single, pipelined fetch command
+ if ((parts->GetNumParts() > 0) && !DeathSignalReceived() && !GetPseudoInterrupted() && stringToFetch.get())
+ {
+ IncrementCommandTagNumber();
+
+ nsCString commandString(GetServerCommandTag());
+ commandString.Append(" UID fetch ");
+ commandString.Append(uid, 10);
+ commandString.Append(" (");
+ commandString.Append(stringToFetch);
+ commandString.Append(")" CRLF);
+ nsresult rv = SendData(commandString.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString.get());
+ }
+}
+
+
+void nsImapProtocol::FetchMsgAttribute(const nsCString &messageIds, const nsCString &attribute)
+{
+ IncrementCommandTagNumber();
+
+ nsAutoCString commandString (GetServerCommandTag());
+ commandString.Append(" UID fetch ");
+ commandString.Append(messageIds);
+ commandString.Append(" (");
+ commandString.Append(attribute);
+ commandString.Append(")" CRLF);
+ nsresult rv = SendData(commandString.get());
+
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString.get());
+ GetServerStateParser().SetFetchingFlags(false);
+ // Always clear this flag after every fetch.
+ m_fetchingWholeMessage = false;
+}
+
+// this routine is used to fetch a message or messages, or headers for a
+// message...
+
+void nsImapProtocol::FallbackToFetchWholeMsg(const nsCString &messageId, uint32_t messageSize)
+{
+ if (m_imapMessageSink && m_runningUrl)
+ {
+ bool shouldStoreMsgOffline;
+ m_runningUrl->GetStoreOfflineOnFallback(&shouldStoreMsgOffline);
+ m_runningUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
+ }
+ FetchTryChunking(messageId,
+ m_imapAction == nsIImapUrl::nsImapMsgFetchPeek ?
+ kEveryThingRFC822Peek : kEveryThingRFC822,
+ true, nullptr, messageSize, true);
+}
+
+void
+nsImapProtocol::FetchMessage(const nsCString &messageIds,
+ nsIMAPeFetchFields whatToFetch,
+ const char *fetchModifier,
+ uint32_t startByte, uint32_t numBytes,
+ char *part)
+{
+ IncrementCommandTagNumber();
+
+ nsCString commandString;
+ commandString = "%s UID fetch";
+
+ switch (whatToFetch) {
+ case kEveryThingRFC822:
+ m_flagChangeCount++;
+ m_fetchingWholeMessage = true;
+ if (m_trackingTime)
+ AdjustChunkSize(); // we started another segment
+ m_startTime = PR_Now(); // save start of download time
+ m_trackingTime = true;
+ MOZ_LOG(IMAP, LogLevel::Debug, ("FetchMessage everything: curFetchSize %u numBytes %u",
+ m_curFetchSize, numBytes));
+ if (numBytes > 0)
+ m_curFetchSize = numBytes;
+
+ if (GetServerStateParser().ServerHasIMAP4Rev1Capability())
+ {
+ if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
+ commandString.Append(" %s (XSENDER UID RFC822.SIZE BODY[]");
+ else
+ commandString.Append(" %s (UID RFC822.SIZE BODY[]");
+ }
+ else
+ {
+ if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
+ commandString.Append(" %s (XSENDER UID RFC822.SIZE RFC822");
+ else
+ commandString.Append(" %s (UID RFC822.SIZE RFC822");
+ }
+ if (numBytes > 0)
+ {
+ // if we are retrieving chunks
+ char *byterangeString = PR_smprintf("<%ld.%ld>",startByte, numBytes);
+ if (byterangeString)
+ {
+ commandString.Append(byterangeString);
+ PR_Free(byterangeString);
+ }
+ }
+ commandString.Append(")");
+
+ break;
+
+ case kEveryThingRFC822Peek:
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("FetchMessage peek: curFetchSize %u numBytes %u",
+ m_curFetchSize, numBytes));
+ if (numBytes > 0)
+ m_curFetchSize = numBytes;
+ const char *formatString = "";
+ eIMAPCapabilityFlags server_capabilityFlags = GetServerStateParser().GetCapabilityFlag();
+
+ m_fetchingWholeMessage = true;
+ if (server_capabilityFlags & kIMAP4rev1Capability)
+ {
+ // use body[].peek since rfc822.peek is not in IMAP4rev1
+ if (server_capabilityFlags & kHasXSenderCapability)
+ formatString = " %s (XSENDER UID RFC822.SIZE BODY.PEEK[]";
+ else
+ formatString = " %s (UID RFC822.SIZE BODY.PEEK[]";
+ }
+ else
+ {
+ if (server_capabilityFlags & kHasXSenderCapability)
+ formatString = " %s (XSENDER UID RFC822.SIZE RFC822.peek";
+ else
+ formatString = " %s (UID RFC822.SIZE RFC822.peek";
+ }
+
+ commandString.Append(formatString);
+ if (numBytes > 0)
+ {
+ // if we are retrieving chunks
+ char *byterangeString = PR_smprintf("<%ld.%ld>",startByte, numBytes);
+ if (byterangeString)
+ {
+ commandString.Append(byterangeString);
+ PR_Free(byterangeString);
+ }
+ }
+ commandString.Append(")");
+ }
+ break;
+ case kHeadersRFC822andUid:
+ if (GetServerStateParser().ServerHasIMAP4Rev1Capability())
+ {
+ eIMAPCapabilityFlags server_capabilityFlags = GetServerStateParser().GetCapabilityFlag();
+ bool aolImapServer = ((server_capabilityFlags & kAOLImapCapability) != 0);
+ bool downloadAllHeaders = false;
+ // checks if we're filtering on "any header" or running a spam filter requiring all headers
+ GetShouldDownloadAllHeaders(&downloadAllHeaders);
+
+ if (!downloadAllHeaders) // if it's ok -- no filters on any header, etc.
+ {
+ char *headersToDL = nullptr;
+ char *what = nullptr;
+ const char *dbHeaders = (gUseEnvelopeCmd) ? IMAP_DB_HEADERS : IMAP_ENV_AND_DB_HEADERS;
+ nsCString arbitraryHeaders;
+ GetArbitraryHeadersToDownload(arbitraryHeaders);
+ for (uint32_t i = 0; i < mCustomDBHeaders.Length(); i++)
+ {
+ if (arbitraryHeaders.Find(mCustomDBHeaders[i], CaseInsensitiveCompare) == kNotFound)
+ {
+ if (!arbitraryHeaders.IsEmpty())
+ arbitraryHeaders.Append(' ');
+ arbitraryHeaders.Append(mCustomDBHeaders[i]);
+ }
+ }
+ for (uint32_t i = 0; i < mCustomHeaders.Length(); i++)
+ {
+ if (arbitraryHeaders.Find(mCustomHeaders[i], CaseInsensitiveCompare) == kNotFound)
+ {
+ if (!arbitraryHeaders.IsEmpty())
+ arbitraryHeaders.Append(' ');
+ arbitraryHeaders.Append(mCustomHeaders[i]);
+ }
+ }
+ if (arbitraryHeaders.IsEmpty())
+ headersToDL = strdup(dbHeaders);
+ else
+ headersToDL = PR_smprintf("%s %s",dbHeaders, arbitraryHeaders.get());
+
+ if (gUseEnvelopeCmd)
+ what = PR_smprintf(" ENVELOPE BODY.PEEK[HEADER.FIELDS (%s)])", headersToDL);
+ else
+ what = PR_smprintf(" BODY.PEEK[HEADER.FIELDS (%s)])",headersToDL);
+ NS_Free(headersToDL);
+ if (what)
+ {
+ commandString.Append(" %s (UID ");
+ if (m_isGmailServer)
+ commandString.Append("X-GM-MSGID X-GM-THRID X-GM-LABELS ");
+ if (aolImapServer)
+ commandString.Append(" XAOL.SIZE") ;
+ else
+ commandString.Append("RFC822.SIZE");
+ commandString.Append(" FLAGS");
+ commandString.Append(what);
+ PR_Free(what);
+ }
+ else
+ {
+ commandString.Append(" %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
+ }
+ }
+ else
+ commandString.Append(" %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
+ }
+ else
+ commandString.Append(" %s (UID RFC822.SIZE RFC822.HEADER FLAGS)");
+ break;
+ case kUid:
+ commandString.Append(" %s (UID)");
+ break;
+ case kFlags:
+ GetServerStateParser().SetFetchingFlags(true);
+ commandString.Append(" %s (FLAGS)");
+ break;
+ case kRFC822Size:
+ commandString.Append(" %s (RFC822.SIZE)");
+ break;
+ case kBodyStart:
+ {
+ int32_t numBytesToFetch;
+ m_runningUrl->GetNumBytesToFetch(&numBytesToFetch);
+
+ commandString.Append(" %s (UID BODY.PEEK[HEADER.FIELDS (Content-Type Content-Transfer-Encoding)] BODY.PEEK[TEXT]<0.");
+ commandString.AppendInt(numBytesToFetch);
+ commandString.Append(">)");
+ }
+ break;
+ case kRFC822HeadersOnly:
+ if (GetServerStateParser().ServerHasIMAP4Rev1Capability())
+ {
+ if (part)
+ {
+ commandString.Append(" %s (BODY[");
+ char *what = PR_smprintf("%s.HEADER])", part);
+ if (what)
+ {
+ commandString.Append(what);
+ PR_Free(what);
+ }
+ else
+ HandleMemoryFailure();
+ }
+ else
+ {
+ // headers for the top-level message
+ commandString.Append(" %s (BODY[HEADER])");
+ }
+ }
+ else
+ commandString.Append(" %s (RFC822.HEADER)");
+ break;
+ case kMIMEPart:
+ commandString.Append(" %s (BODY.PEEK[%s]");
+ if (numBytes > 0)
+ {
+ // if we are retrieving chunks
+ char *byterangeString = PR_smprintf("<%ld.%ld>",startByte, numBytes);
+ if (byterangeString)
+ {
+ commandString.Append(byterangeString);
+ PR_Free(byterangeString);
+ }
+ }
+ commandString.Append(")");
+ break;
+ case kMIMEHeader:
+ commandString.Append(" %s (BODY[%s.MIME])");
+ break;
+ };
+
+ if (fetchModifier)
+ commandString.Append(fetchModifier);
+
+ commandString.Append(CRLF);
+
+ // since messageIds can be infinitely long, use a dynamic buffer rather than the fixed one
+ const char *commandTag = GetServerCommandTag();
+ int protocolStringSize = commandString.Length() + messageIds.Length() + PL_strlen(commandTag) + 1 +
+ (part ? PL_strlen(part) : 0);
+ char *protocolString = (char *) PR_CALLOC( protocolStringSize );
+
+ if (protocolString)
+ {
+ char *cCommandStr = ToNewCString(commandString);
+ if ((whatToFetch == kMIMEPart) ||
+ (whatToFetch == kMIMEHeader))
+ {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ cCommandStr, // format string
+ commandTag, // command tag
+ messageIds.get(),
+ part);
+ }
+ else
+ {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ cCommandStr, // format string
+ commandTag, // command tag
+ messageIds.get());
+ }
+
+ nsresult rv = SendData(protocolString);
+
+ free(cCommandStr);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(protocolString);
+ PR_Free(protocolString);
+ GetServerStateParser().SetFetchingFlags(false);
+ // Always clear this flag after every fetch.
+ m_fetchingWholeMessage = false;
+ if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded())
+ Check();
+ }
+ else
+ HandleMemoryFailure();
+}
+
+void nsImapProtocol::FetchTryChunking(const nsCString &messageIds,
+ nsIMAPeFetchFields whatToFetch,
+ bool idIsUid,
+ char *part,
+ uint32_t downloadSize,
+ bool tryChunking)
+{
+ GetServerStateParser().SetTotalDownloadSize(downloadSize);
+ MOZ_LOG(IMAP, LogLevel::Debug, ("FetchTryChunking: curFetchSize %u", downloadSize));
+ m_curFetchSize = downloadSize; // we'll change this if chunking.
+ if (m_fetchByChunks && tryChunking &&
+ GetServerStateParser().ServerHasIMAP4Rev1Capability() &&
+ (downloadSize > (uint32_t) m_chunkThreshold))
+ {
+ uint32_t startByte = 0;
+ m_curFetchSize = m_chunkSize;
+ GetServerStateParser().ClearLastFetchChunkReceived();
+ while (!DeathSignalReceived() && !GetPseudoInterrupted() &&
+ !GetServerStateParser().GetLastFetchChunkReceived() &&
+ GetServerStateParser().ContinueParse())
+ {
+ FetchMessage(messageIds,
+ whatToFetch,
+ nullptr,
+ startByte, m_chunkSize,
+ part);
+ startByte += m_chunkSize;
+ }
+
+ // Only abort the stream if this is a normal message download
+ // Otherwise, let the body shell abort the stream.
+ if ((whatToFetch == kEveryThingRFC822)
+ &&
+ ((startByte > 0 && (startByte < downloadSize) &&
+ (DeathSignalReceived() || GetPseudoInterrupted())) ||
+ !GetServerStateParser().ContinueParse()))
+ {
+ AbortMessageDownLoad();
+ PseudoInterrupt(false);
+ }
+ }
+ else
+ {
+ // small message, or (we're not chunking and not doing bodystructure),
+ // or the server is not rev1.
+ // Just fetch the whole thing.
+ FetchMessage(messageIds, whatToFetch, nullptr, 0, 0, part);
+ }
+}
+
+
+void nsImapProtocol::PipelinedFetchMessageParts(nsCString &uid, nsIMAPMessagePartIDArray *parts)
+{
+ // assumes no chunking
+
+ // build up a string to fetch
+ nsCString stringToFetch;
+ nsCString what;
+
+ uint32_t currentPartNum = 0;
+ while ((parts->GetNumParts() > currentPartNum) && !DeathSignalReceived())
+ {
+ nsIMAPMessagePartID *currentPart = parts->GetPart(currentPartNum);
+ if (currentPart)
+ {
+ // Do things here depending on the type of message part
+ // Append it to the fetch string
+ if (currentPartNum > 0)
+ stringToFetch += " ";
+
+ switch (currentPart->GetFields())
+ {
+ case kMIMEHeader:
+ what = "BODY.PEEK[";
+ what += currentPart->GetPartNumberString();
+ what += ".MIME]";
+ stringToFetch += what;
+ break;
+ case kRFC822HeadersOnly:
+ if (currentPart->GetPartNumberString())
+ {
+ what = "BODY.PEEK[";
+ what += currentPart->GetPartNumberString();
+ what += ".HEADER]";
+ stringToFetch += what;
+ }
+ else
+ {
+ // headers for the top-level message
+ stringToFetch += "BODY.PEEK[HEADER]";
+ }
+ break;
+ default:
+ NS_ASSERTION(false, "we should only be pipelining MIME headers and Message headers");
+ break;
+ }
+
+ }
+ currentPartNum++;
+ }
+
+ // Run the single, pipelined fetch command
+ if ((parts->GetNumParts() > 0) && !DeathSignalReceived() && !GetPseudoInterrupted() && stringToFetch.get())
+ {
+ IncrementCommandTagNumber();
+
+ char *commandString = PR_smprintf("%s UID fetch %s (%s)%s",
+ GetServerCommandTag(), uid.get(),
+ stringToFetch.get(), CRLF);
+
+ if (commandString)
+ {
+ nsresult rv = SendData(commandString);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString);
+ PR_Free(commandString);
+ }
+ else
+ HandleMemoryFailure();
+ }
+}
+
+
+void
+nsImapProtocol::PostLineDownLoadEvent(const char *line, uint32_t uidOfMessage)
+{
+ if (!GetServerStateParser().GetDownloadingHeaders())
+ {
+ uint32_t byteCount = PL_strlen(line);
+ bool echoLineToMessageSink = false;
+ // if we have a channel listener, then just spool the message
+ // directly to the listener
+ if (m_channelListener)
+ {
+ uint32_t count = 0;
+ if (m_channelOutputStream)
+ {
+ nsresult rv = m_channelOutputStream->Write(line, byteCount, &count);
+ NS_ASSERTION(count == byteCount, "IMAP channel pipe couldn't buffer entire write");
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ m_channelListener->OnDataAvailable(request, m_channelContext, m_channelInputStream, 0, count);
+ }
+ // else some sort of explosion?
+ }
+ }
+ if (m_runningUrl)
+ m_runningUrl->GetStoreResultsOffline(&echoLineToMessageSink);
+
+ m_bytesToChannel += byteCount;
+ if (m_imapMessageSink && line && echoLineToMessageSink && !GetPseudoInterrupted())
+ m_imapMessageSink->ParseAdoptedMsgLine(line, uidOfMessage, m_runningUrl);
+ }
+ // ***** We need to handle the pseudo interrupt here *****
+}
+
+// Handle a line seen by the parser.
+// * The argument |lineCopy| must be nullptr or should contain the same string as
+// |line|. |lineCopy| will be modified.
+// * A line may be passed by parts, e.g., "part1 part2\r\n" may be passed as
+// HandleMessageDownLoadLine("part 1 ", 1);
+// HandleMessageDownLoadLine("part 2\r\n", 0);
+// However, it is assumed that a CRLF or a CRCRLF is never split (i.e., this is
+// ensured *before* invoking this method).
+void nsImapProtocol::HandleMessageDownLoadLine(const char *line, bool isPartialLine,
+ char *lineCopy)
+{
+ NS_ENSURE_TRUE_VOID(line);
+ NS_PRECONDITION(lineCopy == nullptr || !PL_strcmp(line, lineCopy),
+ "line and lineCopy must contain the same string");
+ const char *messageLine = line;
+ uint32_t lineLength = strlen(messageLine);
+ const char *cEndOfLine = messageLine + lineLength;
+ char *localMessageLine = nullptr;
+
+ // If we obtain a partial line (due to fetching by chunks), we do not
+ // add/modify the end-of-line terminator.
+ if (!isPartialLine)
+ {
+ // Change this line to native line termination, duplicate if necessary.
+ // Do not assume that the line really ends in CRLF
+ // to start with, even though it is supposed to be RFC822
+
+ // normalize line endings to CRLF unless we are saving the message to disk
+ bool canonicalLineEnding = true;
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl);
+
+ if (m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk && msgUrl)
+ msgUrl->GetCanonicalLineEnding(&canonicalLineEnding);
+
+ NS_PRECONDITION(MSG_LINEBREAK_LEN == 1 ||
+ (MSG_LINEBREAK_LEN == 2 && !PL_strcmp(CRLF, MSG_LINEBREAK)),
+ "violated assumptions on MSG_LINEBREAK");
+ if (MSG_LINEBREAK_LEN == 1 && !canonicalLineEnding)
+ {
+ bool lineEndsWithCRorLF = lineLength >= 1 &&
+ (cEndOfLine[-1] == '\r' || cEndOfLine[-1] == '\n');
+ char *endOfLine;
+ if (lineCopy && lineEndsWithCRorLF) // true for most lines
+ {
+ endOfLine = lineCopy + lineLength;
+ messageLine = lineCopy;
+ }
+ else
+ {
+ // leave enough room for one more char, MSG_LINEBREAK[0]
+ localMessageLine = (char *) PR_MALLOC(lineLength + 2);
+ if (!localMessageLine) // memory failure
+ return;
+ PL_strcpy(localMessageLine, line);
+ endOfLine = localMessageLine + lineLength;
+ messageLine = localMessageLine;
+ }
+
+ if (lineLength >= 2 &&
+ endOfLine[-2] == '\r' &&
+ endOfLine[-1] == '\n')
+ {
+ if(lineLength>=3 && endOfLine[-3] == '\r') // CRCRLF
+ {
+ endOfLine--;
+ lineLength--;
+ }
+ /* CRLF -> CR or LF */
+ endOfLine[-2] = MSG_LINEBREAK[0];
+ endOfLine[-1] = '\0';
+ lineLength--;
+ }
+ else if (lineLength >= 1 &&
+ ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n')))
+ {
+ /* CR -> LF or LF -> CR */
+ endOfLine[-1] = MSG_LINEBREAK[0];
+ }
+ else // no eol characters at all
+ {
+ endOfLine[0] = MSG_LINEBREAK[0]; // CR or LF
+ endOfLine[1] = '\0';
+ lineLength++;
+ }
+ }
+ else // enforce canonical CRLF linebreaks
+ {
+ if (lineLength==0 || (lineLength == 1 && cEndOfLine[-1] == '\n'))
+ {
+ messageLine = CRLF;
+ lineLength = 2;
+ }
+ else if (cEndOfLine[-1] != '\n' || cEndOfLine[-2] != '\r' ||
+ (lineLength >=3 && cEndOfLine[-3] == '\r'))
+ {
+ // The line does not end in CRLF (or it ends in CRCRLF).
+ // Copy line and leave enough room for two more chars (CR and LF).
+ localMessageLine = (char *) PR_MALLOC(lineLength + 3);
+ if (!localMessageLine) // memory failure
+ return;
+ PL_strcpy(localMessageLine, line);
+ char *endOfLine = localMessageLine + lineLength;
+ messageLine = localMessageLine;
+
+ if (lineLength>=3 && endOfLine[-1] == '\n' &&
+ endOfLine[-2] == '\r')
+ {
+ // CRCRLF -> CRLF
+ endOfLine[-2] = '\n';
+ endOfLine[-1] = '\0';
+ lineLength--;
+ }
+ else if ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n'))
+ {
+ // LF -> CRLF or CR -> CRLF
+ endOfLine[-1] = '\r';
+ endOfLine[0] = '\n';
+ endOfLine[1] = '\0';
+ lineLength++;
+ }
+ else // no eol characters at all
+ {
+ endOfLine[0] = '\r';
+ endOfLine[1] = '\n';
+ endOfLine[2] = '\0';
+ lineLength += 2;
+ }
+ }
+ }
+ }
+ NS_ASSERTION(lineLength == PL_strlen(messageLine), "lineLength not accurate");
+
+ // check if sender obtained via XSENDER server extension matches "From:" field
+ const char *xSenderInfo = GetServerStateParser().GetXSenderInfo();
+ if (xSenderInfo && *xSenderInfo && !m_fromHeaderSeen)
+ {
+ if (!PL_strncmp("From: ", messageLine, 6))
+ {
+ m_fromHeaderSeen = true;
+ if (PL_strstr(messageLine, xSenderInfo) != NULL)
+ // Adding a X-Mozilla-Status line here is not very elegant but it
+ // works. Another X-Mozilla-Status line is added to the message when
+ // downloading to a local folder; this new line will also contain the
+ // 'authed' flag we are adding here. (If the message is again
+ // uploaded to the server, this flag is lost.)
+ // 0x0200 == nsMsgMessageFlags::SenderAuthed
+ HandleMessageDownLoadLine("X-Mozilla-Status: 0200\r\n", false);
+ GetServerStateParser().FreeXSenderInfo();
+ }
+ }
+
+ if (GetServerStateParser().GetDownloadingHeaders())
+ {
+ if (!m_curHdrInfo)
+ BeginMessageDownLoad(GetServerStateParser().SizeOfMostRecentMessage(), MESSAGE_RFC822);
+ if (m_curHdrInfo)
+ m_curHdrInfo->CacheLine(messageLine, GetServerStateParser().CurrentResponseUID());
+ PR_Free(localMessageLine);
+ return;
+ }
+ // if this line is for a different message, or the incoming line is too big
+ if (((m_downloadLineCache->CurrentUID() != GetServerStateParser().CurrentResponseUID()) && !m_downloadLineCache->CacheEmpty()) ||
+ (m_downloadLineCache->SpaceAvailable() < lineLength + 1) )
+ FlushDownloadCache();
+
+ // so now the cache is flushed, but this string might still be to big
+ if (m_downloadLineCache->SpaceAvailable() < lineLength + 1)
+ PostLineDownLoadEvent(messageLine, GetServerStateParser().CurrentResponseUID());
+ else
+ m_downloadLineCache->CacheLine(messageLine, GetServerStateParser().CurrentResponseUID());
+
+ PR_Free(localMessageLine);
+}
+
+void nsImapProtocol::FlushDownloadCache()
+{
+ if (!m_downloadLineCache->CacheEmpty())
+ {
+ msg_line_info *downloadLine = m_downloadLineCache->GetCurrentLineInfo();
+ PostLineDownLoadEvent(downloadLine->adoptedMessageLine,
+ downloadLine->uidOfMessage);
+ m_downloadLineCache->ResetCache();
+ }
+}
+
+void nsImapProtocol::NormalMessageEndDownload()
+{
+ Log("STREAM", "CLOSE", "Normal Message End Download Stream");
+
+ if (m_trackingTime)
+ AdjustChunkSize();
+ if (m_imapMailFolderSink && m_curHdrInfo && GetServerStateParser().GetDownloadingHeaders())
+ {
+ m_curHdrInfo->SetMsgSize(GetServerStateParser().SizeOfMostRecentMessage());
+ m_curHdrInfo->SetMsgUid(GetServerStateParser().CurrentResponseUID());
+ m_hdrDownloadCache->FinishCurrentHdr();
+ int32_t numHdrsCached;
+ m_hdrDownloadCache->GetNumHeaders(&numHdrsCached);
+ if (numHdrsCached == kNumHdrsToXfer)
+ {
+ m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache);
+ m_hdrDownloadCache->ResetAll();
+ }
+ }
+ FlushDownloadCache();
+
+ if (!GetServerStateParser().GetDownloadingHeaders())
+ {
+ int32_t updatedMessageSize = -1;
+ if (m_fetchingWholeMessage)
+ {
+ updatedMessageSize = m_bytesToChannel;
+#ifdef PR_LOGGING
+ if (m_bytesToChannel != GetServerStateParser().SizeOfMostRecentMessage()) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("STREAM:CLOSE Server's RFC822.SIZE %u, actual size %u",
+ GetServerStateParser().SizeOfMostRecentMessage(),
+ m_bytesToChannel));
+ }
+#endif
+ }
+ // need to know if we're downloading for display or not. We'll use action == nsImapMsgFetch for now
+ nsImapAction imapAction = nsIImapUrl::nsImapSelectFolder; // just set it to some legal value
+ if (m_runningUrl)
+ m_runningUrl->GetImapAction(&imapAction);
+
+ if (m_imapMessageSink)
+ m_imapMessageSink->NormalEndMsgWriteStream(m_downloadLineCache->CurrentUID(), imapAction == nsIImapUrl::nsImapMsgFetch, m_runningUrl, updatedMessageSize);
+
+ if (m_runningUrl && m_imapMailFolderSink)
+ {
+ nsCOMPtr <nsISupports> copyState;
+ m_runningUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) // only need this notification during copy
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl (do_QueryInterface(m_runningUrl));
+ m_imapMailFolderSink->EndMessage(mailUrl, m_downloadLineCache->CurrentUID());
+ }
+ }
+ }
+ m_curHdrInfo = nullptr;
+}
+
+void nsImapProtocol::AbortMessageDownLoad()
+{
+ Log("STREAM", "CLOSE", "Abort Message Download Stream");
+
+ if (m_trackingTime)
+ AdjustChunkSize();
+ FlushDownloadCache();
+ if (GetServerStateParser().GetDownloadingHeaders())
+ {
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->AbortHeaderParseStream(this);
+ }
+ else if (m_imapMessageSink)
+ m_imapMessageSink->AbortMsgWriteStream();
+
+ m_curHdrInfo = nullptr;
+}
+
+
+void nsImapProtocol::ProcessMailboxUpdate(bool handlePossibleUndo)
+{
+ if (DeathSignalReceived())
+ return;
+
+ // Update quota information
+ char *boxName;
+ GetSelectedMailboxName(&boxName);
+ GetQuotaDataIfSupported(boxName);
+ PR_Free(boxName);
+
+ // fetch the flags and uids of all existing messages or new ones
+ if (!DeathSignalReceived() && GetServerStateParser().NumberOfMessages())
+ {
+ if (handlePossibleUndo)
+ {
+ // undo any delete flags we may have asked to
+ nsCString undoIdsStr;
+ nsAutoCString undoIds;
+
+ GetCurrentUrl()->GetListOfMessageIds(undoIdsStr);
+ undoIds.Assign(undoIdsStr);
+ if (!undoIds.IsEmpty())
+ {
+ char firstChar = (char) undoIds.CharAt(0);
+ undoIds.Cut(0, 1); // remove first character
+ // if this string started with a '-', then this is an undo of a delete
+ // if its a '+' its a redo
+ if (firstChar == '-')
+ Store(undoIds, "-FLAGS (\\Deleted)", true); // most servers will fail silently on a failure, deal with it?
+ else if (firstChar == '+')
+ Store(undoIds, "+FLAGS (\\Deleted)", true); // most servers will fail silently on a failure, deal with it?
+ else
+ NS_ASSERTION(false, "bogus undo Id's");
+ }
+ }
+
+ // make the parser record these flags
+ nsCString fetchStr;
+ int32_t added = 0, deleted = 0;
+
+ m_flagState->GetNumberOfMessages(&added);
+ deleted = m_flagState->NumberOfDeletedMessages();
+ bool flagStateEmpty = !added;
+ // Figure out if we need to do any kind of sync.
+ bool needFolderSync = (flagStateEmpty || added == deleted) && (!UseCondStore() ||
+ (GetServerStateParser().fHighestModSeq != mFolderLastModSeq) ||
+ (GetShowDeletedMessages() &&
+ GetServerStateParser().NumberOfMessages() != mFolderTotalMsgCount));
+
+ // Figure out if we need to do a full sync (UID Fetch Flags 1:*),
+ // a partial sync using CHANGEDSINCE, or a sync from the previous
+ // highwater mark.
+
+ // if the folder doesn't know about the highest uid, or the flag state
+ // is empty, and we're not using CondStore, we need a full sync.
+ bool needFullFolderSync = !mFolderHighestUID || (flagStateEmpty && !UseCondStore());
+
+ if (needFullFolderSync || needFolderSync)
+ {
+ nsCString idsToFetch("1:*");
+ char fetchModifier[40] = "";
+ if (!needFullFolderSync && !GetShowDeletedMessages() && UseCondStore())
+ PR_snprintf(fetchModifier, sizeof(fetchModifier), " (CHANGEDSINCE %llu)",
+ mFolderLastModSeq);
+ else
+ m_flagState->SetPartialUIDFetch(false);
+
+ FetchMessage(idsToFetch, kFlags, fetchModifier);
+ // lets see if we should expunge during a full sync of flags.
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ // if we did a CHANGEDSINCE fetch, do a sanity check on the msg counts
+ // to see if some other client may have done an expunge.
+ if (m_flagState->GetPartialUIDFetch())
+ {
+ if (m_flagState->NumberOfDeletedMessages() +
+ mFolderTotalMsgCount != GetServerStateParser().NumberOfMessages())
+ {
+ // sanity check failed - fall back to full flag sync
+ m_flagState->Reset();
+ m_flagState->SetPartialUIDFetch(false);
+ FetchMessage(NS_LITERAL_CSTRING("1:*"), kFlags);
+ }
+ }
+ int32_t numDeleted = m_flagState->NumberOfDeletedMessages();
+ // Don't do expunge when we are lite selecting folder because we
+ // could be doing undo.
+ // Expunge if we're always expunging, or the number of deleted messages
+ // is over the threshhold, and we're either always respecting the
+ // threshhold, or we're expunging based on the delete model, and
+ // the delete model is not the imap delete model.
+ if (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
+ (gExpungeOption == kAutoExpungeAlways ||
+ (numDeleted >= gExpungeThreshold &&
+ (gExpungeOption == kAutoExpungeOnThreshold ||
+ (gExpungeOption == kAutoExpungeDeleteModel && !GetShowDeletedMessages())))))
+ Expunge();
+ }
+ }
+ else
+ {
+ uint32_t highestRecordedUID = GetServerStateParser().HighestRecordedUID();
+ // if we're using CONDSTORE, and the parser hasn't seen any UIDs, use
+ // the highest UID we've seen from the folder.
+ if (UseCondStore() && !highestRecordedUID)
+ highestRecordedUID = mFolderHighestUID;
+
+ AppendUid(fetchStr, highestRecordedUID + 1);
+ fetchStr.Append(":*");
+ FetchMessage(fetchStr, kFlags); // only new messages please
+ }
+ }
+ else if (GetServerStateParser().LastCommandSuccessful())
+ {
+ GetServerStateParser().ResetFlagInfo();
+ // the flag state is empty, but not partial.
+ m_flagState->SetPartialUIDFetch(false);
+ }
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsImapAction imapAction;
+ nsresult res = m_runningUrl->GetImapAction(&imapAction);
+ if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapLiteSelectFolder)
+ return;
+ }
+
+ bool entered_waitForBodyIdsMonitor = false;
+
+ uint32_t *msgIdList = nullptr;
+ uint32_t msgCount = 0;
+
+ nsImapMailboxSpec *new_spec = GetServerStateParser().CreateCurrentMailboxSpec();
+ if (new_spec && GetServerStateParser().LastCommandSuccessful())
+ {
+ nsImapAction imapAction;
+ nsresult res = m_runningUrl->GetImapAction(&imapAction);
+ if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapExpungeFolder)
+ new_spec->mBoxFlags |= kJustExpunged;
+ m_waitForBodyIdsMonitor.Enter();
+ entered_waitForBodyIdsMonitor = true;
+
+ if (m_imapMailFolderSink)
+ {
+ bool more;
+ m_imapMailFolderSink->UpdateImapMailboxInfo(this, new_spec);
+ m_imapMailFolderSink->GetMsgHdrsToDownload(&more, &m_progressCount,
+ &msgCount, &msgIdList);
+ m_progressIndex = 0;
+ m_runningUrl->SetMoreHeadersToDownload(more);
+ // We're going to be re-running this url if there are more headers.
+ if (more)
+ m_runningUrl->SetRerunningUrl(true);
+ }
+ }
+ else if (!new_spec)
+ HandleMemoryFailure();
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ if (entered_waitForBodyIdsMonitor)
+ m_waitForBodyIdsMonitor.Exit();
+
+ if (msgIdList && !DeathSignalReceived() && GetServerStateParser().LastCommandSuccessful())
+ {
+ FolderHeaderDump(msgIdList, msgCount);
+ NS_Free( msgIdList);
+ }
+ HeaderFetchCompleted();
+ // this might be bogus, how are we going to do pane notification and stuff when we fetch bodies without
+ // headers!
+ }
+ else if (entered_waitForBodyIdsMonitor) // need to exit this monitor if death signal received
+ m_waitForBodyIdsMonitor.Exit();
+
+ // wait for a list of bodies to fetch.
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ WaitForPotentialListOfBodysToFetch(&msgIdList, msgCount);
+ if ( msgCount && GetServerStateParser().LastCommandSuccessful())
+ {
+ // Tell the url that it should store the msg fetch results offline,
+ // while we're dumping the messages, and then restore the setting.
+ bool wasStoringOffline;
+ m_runningUrl->GetStoreResultsOffline(&wasStoringOffline);
+ m_runningUrl->SetStoreResultsOffline(true);
+ m_progressIndex = 0;
+ m_progressCount = msgCount;
+ FolderMsgDump(msgIdList, msgCount, kEveryThingRFC822Peek);
+ m_runningUrl->SetStoreResultsOffline(wasStoringOffline);
+ }
+ }
+ if (!GetServerStateParser().LastCommandSuccessful())
+ GetServerStateParser().ResetFlagInfo();
+
+ NS_IF_RELEASE(new_spec);
+}
+
+void nsImapProtocol::FolderHeaderDump(uint32_t *msgUids, uint32_t msgCount)
+{
+ FolderMsgDump(msgUids, msgCount, kHeadersRFC822andUid);
+}
+
+void nsImapProtocol::FolderMsgDump(uint32_t *msgUids, uint32_t msgCount, nsIMAPeFetchFields fields)
+{
+ // lets worry about this progress stuff later.
+ switch (fields) {
+ case kHeadersRFC822andUid:
+ SetProgressString("imapReceivingMessageHeaders2");
+ break;
+ case kFlags:
+ SetProgressString("imapReceivingMessageFlags2");
+ break;
+ default:
+ SetProgressString("imapFolderReceivingMessageOf2");
+ break;
+ }
+
+ FolderMsgDumpLoop(msgUids, msgCount, fields);
+
+ SetProgressString(nullptr);
+}
+
+void nsImapProtocol::WaitForPotentialListOfBodysToFetch(uint32_t **msgIdList, uint32_t &msgCount)
+{
+ PRIntervalTime sleepTime = kImapSleepTime;
+
+ ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
+ while(!m_fetchBodyListIsNew && !DeathSignalReceived())
+ fetchListMon.Wait(sleepTime);
+ m_fetchBodyListIsNew = false;
+
+ *msgIdList = m_fetchBodyIdList;
+ msgCount = m_fetchBodyCount;
+}
+
+// libmsg uses this to notify a running imap url about message bodies it should download.
+// why not just have libmsg explicitly download the message bodies?
+NS_IMETHODIMP nsImapProtocol::NotifyBodysToDownload(uint32_t *keys, uint32_t keyCount)
+{
+ ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
+ PR_FREEIF(m_fetchBodyIdList);
+ m_fetchBodyIdList = (uint32_t *) PR_MALLOC(keyCount * sizeof(uint32_t));
+ if (m_fetchBodyIdList)
+ memcpy(m_fetchBodyIdList, keys, keyCount * sizeof(uint32_t));
+ m_fetchBodyCount = keyCount;
+ m_fetchBodyListIsNew = true;
+ fetchListMon.Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetFlagsForUID(uint32_t uid, bool *foundIt, imapMessageFlagsType *resultFlags, char **customFlags)
+{
+ int32_t i;
+
+ imapMessageFlagsType flags = m_flagState->GetMessageFlagsFromUID(uid, foundIt, &i);
+ if (*foundIt)
+ {
+ *resultFlags = flags;
+ if ((flags & kImapMsgCustomKeywordFlag) && customFlags)
+ m_flagState->GetCustomFlags(uid, customFlags);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetFlagAndUidState(nsIImapFlagAndUidState **aFlagState)
+{
+ NS_ENSURE_ARG_POINTER(aFlagState);
+ NS_IF_ADDREF(*aFlagState = m_flagState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetSupportedUserFlags(uint16_t *supportedFlags)
+{
+ if (!supportedFlags)
+ return NS_ERROR_NULL_POINTER;
+
+ *supportedFlags = m_flagState->GetSupportedUserFlags();
+ return NS_OK;
+}
+void nsImapProtocol::FolderMsgDumpLoop(uint32_t *msgUids, uint32_t msgCount, nsIMAPeFetchFields fields)
+{
+ int32_t msgCountLeft = msgCount;
+ uint32_t msgsDownloaded = 0;
+ do
+ {
+ nsCString idString;
+ uint32_t msgsToDownload = msgCountLeft;
+ AllocateImapUidString(msgUids + msgsDownloaded, msgsToDownload, m_flagState, idString); // 20 * 200
+ FetchMessage(idString, fields);
+ msgsDownloaded += msgsToDownload;
+ msgCountLeft -= msgsToDownload;
+ }
+ while (msgCountLeft > 0 && !DeathSignalReceived());
+}
+
+void nsImapProtocol::HeaderFetchCompleted()
+{
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache);
+ m_hdrDownloadCache->ReleaseAll();
+
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->HeaderFetchCompleted(this);
+}
+
+
+// Use the noop to tell the server we are still here, and therefore we are willing to receive
+// status updates. The recent or exists response from the server could tell us that there is
+// more mail waiting for us, but we need to check the flags of the mail and the high water mark
+// to make sure that we do not tell the user that there is new mail when perhaps they have
+// already read it in another machine.
+
+void nsImapProtocol::PeriodicBiff()
+{
+
+ nsMsgBiffState startingState = m_currentBiffState;
+
+ if (GetServerStateParser().GetIMAPstate() == nsImapServerResponseParser::kFolderSelected)
+ {
+ Noop(); // check the latest number of messages
+ int32_t numMessages = 0;
+ m_flagState->GetNumberOfMessages(&numMessages);
+ if (GetServerStateParser().NumberOfMessages() != numMessages)
+ {
+ uint32_t id = GetServerStateParser().HighestRecordedUID() + 1;
+ nsCString fetchStr; // only update flags
+ uint32_t added = 0, deleted = 0;
+
+ deleted = m_flagState->NumberOfDeletedMessages();
+ added = numMessages;
+ if (!added || (added == deleted)) // empty keys, get them all
+ id = 1;
+
+ //sprintf(fetchStr, "%ld:%ld", id, id + GetServerStateParser().NumberOfMessages() - fFlagState->GetNumberOfMessages());
+ AppendUid(fetchStr, id);
+ fetchStr.Append(":*");
+ FetchMessage(fetchStr, kFlags);
+ if (((uint32_t) m_flagState->GetHighestNonDeletedUID() >= id) && m_flagState->IsLastMessageUnseen())
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NewMail;
+ else
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
+ }
+ else
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
+ }
+ else
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
+
+ if (startingState != m_currentBiffState)
+ SendSetBiffIndicatorEvent(m_currentBiffState);
+}
+
+void nsImapProtocol::SendSetBiffIndicatorEvent(nsMsgBiffState newState)
+{
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->SetBiffStateAndUpdate(newState);
+}
+
+// We get called to see if there is mail waiting for us at the server, even if it may have been
+// read elsewhere. We just want to know if we should download headers or not.
+
+bool nsImapProtocol::CheckNewMail()
+{
+ return m_checkForNewMailDownloadsHeaders;
+}
+
+/* static */ void nsImapProtocol::LogImapUrl(const char *logMsg, nsIImapUrl *imapUrl)
+{
+ // nsImapProtocol is not always constructed before this static method is called
+ if (!IMAP)
+ IMAP = PR_NewLogModule("IMAP");
+
+ if (MOZ_LOG_TEST(IMAP, LogLevel::Info))
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl);
+ if (mailnewsUrl)
+ {
+ nsAutoCString urlSpec, unescapedUrlSpec;
+ nsresult rv = mailnewsUrl->GetSpec(urlSpec);
+ if (NS_FAILED(rv))
+ return;
+ MsgUnescapeString(urlSpec, 0, unescapedUrlSpec);
+ MOZ_LOG(IMAP, LogLevel::Info, ("%s:%s", logMsg, unescapedUrlSpec.get()));
+ }
+ }
+}
+
+// log info including current state...
+void nsImapProtocol::Log(const char *logSubName, const char *extraInfo, const char *logData)
+{
+ if (MOZ_LOG_TEST(IMAP, LogLevel::Info))
+ {
+ static const char nonAuthStateName[] = "NA";
+ static const char authStateName[] = "A";
+ static const char selectedStateName[] = "S";
+ const nsCString& hostName = GetImapHostName(); // initilize to empty string
+
+ int32_t logDataLen = PL_strlen(logData); // PL_strlen checks for null
+ nsCString logDataLines;
+ const char *logDataToLog;
+ int32_t lastLineEnd;
+
+ const int kLogDataChunkSize = 400; // nspr line length is 512, and we
+ // allow some space for the log preamble.
+
+ // break up buffers > 400 bytes on line boundaries.
+ if (logDataLen > kLogDataChunkSize)
+ {
+ logDataLines.Assign(logData);
+ lastLineEnd = MsgRFindChar(logDataLines, '\n', kLogDataChunkSize);
+ // null terminate the last line
+ if (lastLineEnd == kNotFound)
+ lastLineEnd = kLogDataChunkSize - 1;
+
+ logDataLines.Insert( '\0', lastLineEnd + 1);
+ logDataToLog = logDataLines.get();
+ }
+ else
+ {
+ logDataToLog = logData;
+ lastLineEnd = logDataLen;
+ }
+ switch (GetServerStateParser().GetIMAPstate())
+ {
+ case nsImapServerResponseParser::kFolderSelected:
+ if (extraInfo)
+ MOZ_LOG(IMAP, LogLevel::Info, ("%x:%s:%s-%s:%s:%s: %.400s", this, hostName.get(),
+ selectedStateName, GetServerStateParser().GetSelectedMailboxName(),
+ logSubName, extraInfo, logDataToLog));
+ else
+ MOZ_LOG(IMAP, LogLevel::Info, ("%x:%s:%s-%s:%s: %.400s", this, hostName.get(),
+ selectedStateName, GetServerStateParser().GetSelectedMailboxName(),
+ logSubName, logDataToLog));
+ break;
+ case nsImapServerResponseParser::kNonAuthenticated:
+ case nsImapServerResponseParser::kAuthenticated:
+ {
+ const char *stateName = (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kNonAuthenticated)
+ ? nonAuthStateName : authStateName;
+ if (extraInfo)
+ MOZ_LOG(IMAP, LogLevel::Info, ("%x:%s:%s:%s:%s: %.400s", this,
+ hostName.get(),stateName,logSubName,extraInfo,logDataToLog));
+ else
+ MOZ_LOG(IMAP, LogLevel::Info, ("%x:%s:%s:%s: %.400s",this,
+ hostName.get(),stateName,logSubName,logDataToLog));
+ }
+ }
+
+ // dump the rest of the string in < 400 byte chunks
+ while (logDataLen > kLogDataChunkSize)
+ {
+ logDataLines.Cut(0, lastLineEnd + 2); // + 2 to account for the LF and the '\0' we added
+ logDataLen = logDataLines.Length();
+ lastLineEnd = (logDataLen > kLogDataChunkSize) ? MsgRFindChar(logDataLines, '\n', kLogDataChunkSize) : kNotFound;
+ // null terminate the last line
+ if (lastLineEnd == kNotFound)
+ lastLineEnd = kLogDataChunkSize - 1;
+ logDataLines.Insert( '\0', lastLineEnd + 1);
+ logDataToLog = logDataLines.get();
+ MOZ_LOG(IMAP, LogLevel::Info, ("%.400s", logDataToLog));
+ }
+ }
+}
+
+// In 4.5, this posted an event back to libmsg and blocked until it got a response.
+// We may still have to do this.It would be nice if we could preflight this value,
+// but we may not always know when we'll need it.
+uint32_t nsImapProtocol::GetMessageSize(const char * messageId, bool idsAreUids)
+{
+ const char *folderFromParser = GetServerStateParser().GetSelectedMailboxName();
+ if (!folderFromParser || !messageId || !m_runningUrl || !m_hostSessionList)
+ return 0;
+
+ char *folderName = nullptr;
+ uint32_t size;
+
+ nsIMAPNamespace *nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ folderFromParser,
+ nsForMailbox);
+
+ m_runningUrl->AllocateCanonicalPath(folderFromParser,
+ nsForMailbox
+ ? nsForMailbox->GetDelimiter()
+ : kOnlineHierarchySeparatorUnknown,
+ &folderName);
+
+ if (folderName && m_imapMessageSink)
+ m_imapMessageSink->GetMessageSizeFromDB(messageId, &size);
+
+ PR_FREEIF(folderName);
+
+ if (DeathSignalReceived())
+ size = 0;
+
+ return size;
+}
+
+// message id string utility functions
+/* static */bool nsImapProtocol::HandlingMultipleMessages(const nsCString & messageIdString)
+{
+ return (MsgFindCharInSet(messageIdString, ",:") != kNotFound);
+}
+
+uint32_t nsImapProtocol::CountMessagesInIdString(const char *idString)
+{
+ uint32_t numberOfMessages = 0;
+ char *uidString = PL_strdup(idString);
+
+ if (uidString)
+ {
+ // This is in the form <id>,<id>, or <id1>:<id2>
+ char curChar = *uidString;
+ bool isRange = false;
+ int32_t curToken;
+ int32_t saveStartToken=0;
+
+ for (char *curCharPtr = uidString; curChar && *curCharPtr;)
+ {
+ char *currentKeyToken = curCharPtr;
+ curChar = *curCharPtr;
+ while (curChar != ':' && curChar != ',' && curChar != '\0')
+ curChar = *curCharPtr++;
+ *(curCharPtr - 1) = '\0';
+ curToken = atol(currentKeyToken);
+ if (isRange)
+ {
+ while (saveStartToken < curToken)
+ {
+ numberOfMessages++;
+ saveStartToken++;
+ }
+ }
+
+ numberOfMessages++;
+ isRange = (curChar == ':');
+ if (isRange)
+ saveStartToken = curToken + 1;
+ }
+ PR_Free(uidString);
+ }
+ return numberOfMessages;
+}
+
+
+// It would be really nice not to have to use this method nearly as much as we did
+// in 4.5 - we need to think about this some. Some of it may just go away in the new world order
+bool nsImapProtocol::DeathSignalReceived()
+{
+ // ignore mock channel status if we've been pseudo interrupted
+ // ### need to make sure we clear pseudo interrupted status appropriately.
+ if (!GetPseudoInterrupted() && m_mockChannel)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ if (request)
+ {
+ nsresult returnValue;
+ request->GetStatus(&returnValue);
+ if (NS_FAILED(returnValue))
+ return false;
+ }
+ }
+
+ // Check the other way of cancelling.
+ ReentrantMonitorAutoEnter threadDeathMon(m_threadDeathMonitor);
+ return m_threadShouldDie;
+}
+
+NS_IMETHODIMP nsImapProtocol::ResetToAuthenticatedState()
+{
+ GetServerStateParser().PreauthSetAuthenticatedState();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImapProtocol::GetSelectedMailboxName(char ** folderName)
+{
+ if (!folderName) return NS_ERROR_NULL_POINTER;
+ if (GetServerStateParser().GetSelectedMailboxName())
+ *folderName =
+ PL_strdup((GetServerStateParser().GetSelectedMailboxName()));
+ return NS_OK;
+}
+
+bool nsImapProtocol::GetPseudoInterrupted()
+{
+ ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
+ return m_pseudoInterrupted;
+}
+
+void nsImapProtocol::PseudoInterrupt(bool the_interrupt)
+{
+ ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
+ m_pseudoInterrupted = the_interrupt;
+ if (the_interrupt)
+ Log("CONTROL", NULL, "PSEUDO-Interrupted");
+}
+
+void nsImapProtocol::SetActive(bool active)
+{
+ ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor);
+ m_active = active;
+}
+
+bool nsImapProtocol::GetActive()
+{
+ ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor);
+ return m_active;
+}
+
+bool nsImapProtocol::GetShowAttachmentsInline()
+{
+ bool showAttachmentsInline = true;
+ if (m_imapServerSink)
+ m_imapServerSink->GetShowAttachmentsInline(&showAttachmentsInline);
+ return showAttachmentsInline;
+
+}
+
+void nsImapProtocol::SetContentModified(IMAP_ContentModifiedType modified)
+{
+ if (m_runningUrl && m_imapMessageSink)
+ m_imapMessageSink->SetContentModified(m_runningUrl, modified);
+}
+
+
+bool nsImapProtocol::GetShouldFetchAllParts()
+{
+ if (m_runningUrl && !DeathSignalReceived())
+ {
+ nsImapContentModifiedType contentModified;
+ if (NS_SUCCEEDED(m_runningUrl->GetContentModified(&contentModified)))
+ return (contentModified == IMAP_CONTENT_FORCE_CONTENT_NOT_MODIFIED);
+ }
+ return true;
+}
+
+// Adds a set of rights for a given user on a given mailbox on the current host.
+// if userName is NULL, it means "me," or MYRIGHTS.
+void nsImapProtocol::AddFolderRightsForUser(const char *mailboxName, const char *userName, const char *rights)
+{
+ if (!userName)
+ userName = "";
+ if (m_imapServerSink)
+ m_imapServerSink->AddFolderRights(nsDependentCString(mailboxName), nsDependentCString(userName),
+ nsDependentCString(rights));
+}
+
+void nsImapProtocol::SetCopyResponseUid(const char *msgIdString)
+{
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->SetCopyResponseUid(msgIdString, m_runningUrl);
+}
+
+void nsImapProtocol::CommitNamespacesForHostEvent()
+{
+ if (m_imapServerSink)
+ m_imapServerSink->CommitNamespaces();
+}
+
+// notifies libmsg that we have new capability data for the current host
+void nsImapProtocol::CommitCapability()
+{
+ if (m_imapServerSink)
+ {
+ m_imapServerSink->SetCapability(GetServerStateParser().GetCapabilityFlag());
+ }
+}
+
+// rights is a single string of rights, as specified by RFC2086, the IMAP ACL extension.
+// Clears all rights for a given folder, for all users.
+void nsImapProtocol::ClearAllFolderRights()
+{
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->ClearFolderRights();
+}
+
+
+char* nsImapProtocol::CreateNewLineFromSocket()
+{
+ bool needMoreData = false;
+ char * newLine = nullptr;
+ uint32_t numBytesInLine = 0;
+ nsresult rv = NS_OK;
+ // we hold a ref to the input stream in case we get cancelled from the
+ // ui thread, which releases our ref to the input stream, and can
+ // cause the pipe to get deleted before the monitor the read is
+ // blocked on gets notified. When that happens, the imap thread
+ // will stay blocked.
+ nsCOMPtr <nsIInputStream> kungFuGrip = m_inputStream;
+ do
+ {
+ newLine = m_inputStreamBuffer->ReadNextLine(m_inputStream, numBytesInLine, needMoreData, &rv);
+ MOZ_LOG(IMAP, LogLevel::Debug, ("ReadNextLine [stream=%x nb=%u needmore=%u]\n",
+ m_inputStream.get(), numBytesInLine, needMoreData));
+
+ } while (!newLine && NS_SUCCEEDED(rv) && !DeathSignalReceived()); // until we get the next line and haven't been interrupted
+
+ kungFuGrip = nullptr;
+
+ if (NS_FAILED(rv))
+ {
+ switch (rv)
+ {
+ case NS_ERROR_UNKNOWN_HOST:
+ case NS_ERROR_UNKNOWN_PROXY_HOST:
+ AlertUserEventUsingName("imapUnknownHostError");
+ break;
+ case NS_ERROR_CONNECTION_REFUSED:
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ AlertUserEventUsingName("imapConnectionRefusedError");
+ break;
+ case NS_ERROR_NET_TIMEOUT:
+ case NS_ERROR_NET_RESET:
+ case NS_BASE_STREAM_CLOSED:
+ case NS_ERROR_NET_INTERRUPT:
+ // we should retry on RESET, especially for SSL...
+ if ((TestFlag(IMAP_RECEIVED_GREETING) || rv == NS_ERROR_NET_RESET) &&
+ m_runningUrl && !m_retryUrlOnError)
+ {
+ bool rerunningUrl;
+ nsImapAction imapAction;
+ m_runningUrl->GetRerunningUrl(&rerunningUrl);
+ m_runningUrl->GetImapAction(&imapAction);
+ // don't rerun if we already were rerunning. And don't rerun
+ // online move/copies that timeout.
+ if (!rerunningUrl && (rv != NS_ERROR_NET_TIMEOUT ||
+ (imapAction != nsIImapUrl::nsImapOnlineCopy &&
+ imapAction != nsIImapUrl::nsImapOnlineMove)))
+ {
+ m_runningUrl->SetRerunningUrl(true);
+ m_retryUrlOnError = true;
+ break;
+ }
+ }
+ if (rv == NS_ERROR_NET_TIMEOUT)
+ AlertUserEventUsingName("imapNetTimeoutError");
+ else
+ AlertUserEventUsingName(TestFlag(IMAP_RECEIVED_GREETING) ?
+ "imapServerDisconnected" :
+ "imapServerDroppedConnection");
+ break;
+ default:
+ break;
+ }
+
+ nsAutoCString logMsg("clearing IMAP_CONNECTION_IS_OPEN - rv = ");
+ logMsg.AppendInt(static_cast<uint32_t>(rv), 16);
+ Log("CreateNewLineFromSocket", nullptr, logMsg.get());
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ }
+ Log("CreateNewLineFromSocket", nullptr, newLine);
+ SetConnectionStatus(newLine && numBytesInLine ? NS_OK : rv); // set > 0 if string is not null or empty
+ return newLine;
+}
+
+nsresult
+nsImapProtocol::GetConnectionStatus()
+{
+ return m_connectionStatus;
+}
+
+void
+nsImapProtocol::SetConnectionStatus(nsresult status)
+{
+ m_connectionStatus = status;
+}
+
+void
+nsImapProtocol::NotifyMessageFlags(imapMessageFlagsType flags,
+ const nsACString &keywords,
+ nsMsgKey key, uint64_t highestModSeq)
+{
+ if (m_imapMessageSink)
+ {
+ // if we're selecting the folder, don't need to report the flags; we've already fetched them.
+ if (m_imapAction != nsIImapUrl::nsImapSelectFolder &&
+ (m_imapAction != nsIImapUrl::nsImapMsgFetch ||
+ (flags & ~kImapMsgRecentFlag) != kImapMsgSeenFlag))
+ m_imapMessageSink->NotifyMessageFlags(flags, keywords, key, highestModSeq);
+ }
+}
+
+void
+nsImapProtocol::NotifySearchHit(const char * hitLine)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl, &rv);
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->NotifySearchHit(mailnewsUrl, hitLine);
+}
+
+void nsImapProtocol::SetMailboxDiscoveryStatus(EMailboxDiscoverStatus status)
+{
+ ReentrantMonitorAutoEnter mon(m_dataMemberMonitor);
+ m_discoveryStatus = status;
+}
+
+EMailboxDiscoverStatus nsImapProtocol::GetMailboxDiscoveryStatus( )
+{
+ ReentrantMonitorAutoEnter mon(m_dataMemberMonitor);
+ return m_discoveryStatus;
+}
+
+bool
+nsImapProtocol::GetSubscribingNow()
+{
+ // ***** code me *****
+ return false;// ***** for now
+}
+
+void
+nsImapProtocol::DiscoverMailboxSpec(nsImapMailboxSpec * adoptedBoxSpec)
+{
+ nsIMAPNamespace *ns = nullptr;
+
+ NS_ASSERTION (m_hostSessionList, "fatal null host session list");
+ if (!m_hostSessionList)
+ return;
+
+ m_hostSessionList->GetDefaultNamespaceOfTypeForHost(
+ GetImapServerKey(), kPersonalNamespace, ns);
+ const char *nsPrefix = ns ? ns->GetPrefix() : 0;
+
+ if (m_specialXListMailboxes.Count() > 0)
+ {
+ int32_t hashValue = 0;
+ nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
+ m_specialXListMailboxes.Get(strHashKey, &hashValue);
+ adoptedBoxSpec->mBoxFlags |= hashValue;
+ }
+
+ switch (m_hierarchyNameState)
+ {
+ case kXListing:
+ if (adoptedBoxSpec->mBoxFlags &
+ (kImapXListTrash|kImapAllMail|kImapInbox|kImapSent|kImapSpam|kImapDrafts))
+ {
+ nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
+ m_specialXListMailboxes.Put(mailboxName, adoptedBoxSpec->mBoxFlags);
+ // Remember hierarchy delimiter in case this is the first time we've
+ // connected to the server and we need it to be correct for the two-level
+ // XLIST we send (INBOX is guaranteed to be in the first response).
+ if (adoptedBoxSpec->mBoxFlags & kImapInbox)
+ m_runningUrl->SetOnlineSubDirSeparator(adoptedBoxSpec->mHierarchySeparator);
+
+ }
+ NS_IF_RELEASE(adoptedBoxSpec);
+ break;
+ case kListingForFolderFlags:
+ {
+ // store mailbox flags from LIST for use by LSUB
+ nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
+ m_standardListMailboxes.Put(mailboxName, adoptedBoxSpec->mBoxFlags);
+ }
+ NS_IF_RELEASE(adoptedBoxSpec);
+ break;
+ case kListingForCreate:
+ case kNoOperationInProgress:
+ case kDiscoverTrashFolderInProgress:
+ case kListingForInfoAndDiscovery:
+ {
+ // standard mailbox specs are stored in m_standardListMailboxes
+ // because LSUB does necessarily return all mailbox flags.
+ // count should be > 0 only when we are looking at response of LSUB
+ if (m_standardListMailboxes.Count() > 0)
+ {
+ int32_t hashValue = 0;
+ nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
+ if (m_standardListMailboxes.Get(strHashKey, &hashValue))
+ adoptedBoxSpec->mBoxFlags |= hashValue;
+ else
+ // if mailbox is not in hash list, then it is subscribed but does not
+ // exist, so we make sure it can't be selected
+ adoptedBoxSpec->mBoxFlags |= kNoselect;
+ }
+ if (ns && nsPrefix) // if no personal namespace, there can be no Trash folder
+ {
+ bool onlineTrashFolderExists = false;
+ if (m_hostSessionList)
+ {
+ if (adoptedBoxSpec->mBoxFlags & (kImapTrash|kImapXListTrash))
+ {
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(), true);
+ onlineTrashFolderExists = true;
+ }
+ else
+ {
+ m_hostSessionList->GetOnlineTrashFolderExistsForHost(
+ GetImapServerKey(), onlineTrashFolderExists);
+ }
+ }
+
+ // Don't set the Trash flag if not using the Trash model
+ if (GetDeleteIsMoveToTrash() && !onlineTrashFolderExists &&
+ adoptedBoxSpec->mAllocatedPathName.Find(m_trashFolderName, CaseInsensitiveCompare) != -1)
+ {
+ bool trashExists = false;
+ nsCString trashMatch(CreatePossibleTrashName(nsPrefix));
+ nsCString serverTrashName;
+ m_runningUrl->AllocateCanonicalPath(trashMatch.get(),
+ ns->GetDelimiter(),
+ getter_Copies(serverTrashName));
+ if (StringBeginsWith(serverTrashName,
+ NS_LITERAL_CSTRING("INBOX/"),
+ nsCaseInsensitiveCStringComparator()))
+ {
+ nsAutoCString pathName(adoptedBoxSpec->mAllocatedPathName.get() + 6);
+ trashExists =
+ StringBeginsWith(adoptedBoxSpec->mAllocatedPathName,
+ serverTrashName,
+ nsCaseInsensitiveCStringComparator()) && /* "INBOX/" */
+ pathName.Equals(Substring(serverTrashName, 6), nsCaseInsensitiveCStringComparator());
+ }
+ else
+ trashExists = adoptedBoxSpec->mAllocatedPathName.Equals(serverTrashName, nsCaseInsensitiveCStringComparator());
+
+ if (m_hostSessionList)
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(), trashExists);
+
+ if (trashExists)
+ adoptedBoxSpec->mBoxFlags |= kImapTrash;
+ }
+ }
+
+ // Discover the folder (shuttle over to libmsg, yay)
+ // Do this only if the folder name is not empty (i.e. the root)
+ if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty())
+ {
+ if (m_hierarchyNameState == kListingForCreate)
+ adoptedBoxSpec->mBoxFlags |= kNewlyCreatedFolder;
+
+ if (m_imapServerSink)
+ {
+ bool newFolder;
+
+ m_imapServerSink->PossibleImapMailbox(adoptedBoxSpec->mAllocatedPathName,
+ adoptedBoxSpec->mHierarchySeparator,
+ adoptedBoxSpec->mBoxFlags, &newFolder);
+ // if it's a new folder to the server sink, setting discovery status to
+ // eContinueNew will cause us to get the ACL for the new folder.
+ if (newFolder)
+ SetMailboxDiscoveryStatus(eContinueNew);
+
+ bool useSubscription = false;
+
+ if (m_hostSessionList)
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
+ useSubscription);
+
+ if ((GetMailboxDiscoveryStatus() != eContinue) &&
+ (GetMailboxDiscoveryStatus() != eContinueNew) &&
+ (GetMailboxDiscoveryStatus() != eListMyChildren))
+ {
+ SetConnectionStatus(NS_ERROR_FAILURE);
+ }
+ else if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty() &&
+ (GetMailboxDiscoveryStatus() == eListMyChildren) &&
+ (!useSubscription || GetSubscribingNow()))
+ {
+ NS_ASSERTION (false,
+ "we should never get here anymore");
+ SetMailboxDiscoveryStatus(eContinue);
+ }
+ else if (GetMailboxDiscoveryStatus() == eContinueNew)
+ {
+ if (m_hierarchyNameState == kListingForInfoAndDiscovery &&
+ !adoptedBoxSpec->mAllocatedPathName.IsEmpty() &&
+ !(adoptedBoxSpec->mBoxFlags & kNameSpace))
+ {
+ // remember the info here also
+ nsIMAPMailboxInfo *mb = new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName, adoptedBoxSpec->mHierarchySeparator);
+ m_listedMailboxList.AppendElement(mb);
+ }
+ SetMailboxDiscoveryStatus(eContinue);
+ }
+ }
+ }
+ }
+ NS_IF_RELEASE( adoptedBoxSpec);
+ break;
+ case kDiscoverBaseFolderInProgress:
+ break;
+ case kDeleteSubFoldersInProgress:
+ {
+ NS_ASSERTION(m_deletableChildren, "Oops .. null m_deletableChildren\n");
+ m_deletableChildren->AppendElement(ToNewCString(adoptedBoxSpec->mAllocatedPathName));
+ NS_IF_RELEASE(adoptedBoxSpec);
+ }
+ break;
+ case kListingForInfoOnly:
+ {
+ //UpdateProgressWindowForUpgrade(adoptedBoxSpec->allocatedPathName);
+ ProgressEventFunctionUsingNameWithString("imapDiscoveringMailbox",
+ adoptedBoxSpec->mAllocatedPathName.get());
+ nsIMAPMailboxInfo *mb = new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName,
+ adoptedBoxSpec->mHierarchySeparator);
+ m_listedMailboxList.AppendElement(mb);
+ NS_IF_RELEASE(adoptedBoxSpec);
+ }
+ break;
+ case kDiscoveringNamespacesOnly:
+ {
+ NS_IF_RELEASE(adoptedBoxSpec);
+ }
+ break;
+ default:
+ NS_ASSERTION (false, "we aren't supposed to be here");
+ break;
+ }
+}
+
+void
+nsImapProtocol::AlertUserEventUsingName(const char* aMessageName)
+{
+ if (m_imapServerSink)
+ {
+ bool suppressErrorMsg = false;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl)
+ mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg);
+
+ if (!suppressErrorMsg)
+ m_imapServerSink->FEAlertWithName(aMessageName,
+ mailnewsUrl);
+ }
+}
+
+void
+nsImapProtocol::AlertUserEvent(const char * message)
+{
+ if (m_imapServerSink)
+ {
+ bool suppressErrorMsg = false;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl)
+ mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg);
+
+ if (!suppressErrorMsg)
+ m_imapServerSink->FEAlert(NS_ConvertASCIItoUTF16(message), mailnewsUrl);
+ }
+}
+
+void
+nsImapProtocol::AlertUserEventFromServer(const char * aServerEvent, bool aForIdle)
+{
+ if (aServerEvent)
+ {
+ // If called due to BAD/NO imap IDLE response, the server sink and running url
+ // are typically null when IDLE command is sent. So use the stored latest
+ // values for these so that the error alert notification occurs.
+ if (aForIdle && !m_imapServerSink && !m_runningUrl && m_imapServerSinkLatest)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrlLatest);
+ m_imapServerSinkLatest->FEAlertFromServer(nsDependentCString(aServerEvent),
+ mailnewsUrl);
+ }
+ else if (m_imapServerSink)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ m_imapServerSink->FEAlertFromServer(nsDependentCString(aServerEvent),
+ mailnewsUrl);
+ }
+ }
+}
+
+void nsImapProtocol::ResetProgressInfo()
+{
+ m_lastProgressTime = 0;
+ m_lastPercent = -1;
+ m_lastProgressStringName.Truncate();
+}
+
+void nsImapProtocol::SetProgressString(const char * stringName)
+{
+ m_progressStringName.Assign(stringName);
+ if (!m_progressStringName.IsEmpty() && m_imapServerSink)
+ m_imapServerSink->GetImapStringByName(stringName,
+ m_progressString);
+}
+
+void
+nsImapProtocol::ShowProgress()
+{
+ if (!m_progressString.IsEmpty() && !m_progressStringName.IsEmpty())
+ {
+ char16_t *progressString = NULL;
+ const char *mailboxName = GetServerStateParser().GetSelectedMailboxName();
+ nsString unicodeMailboxName;
+ nsresult rv = CopyMUTF7toUTF16(nsDependentCString(mailboxName),
+ unicodeMailboxName);
+ if (NS_SUCCEEDED(rv))
+ {
+ // ### should convert mailboxName to char16_t and change %s to %S in msg text
+ progressString = nsTextFormatter::smprintf(m_progressString.get(),
+ unicodeMailboxName.get(), ++m_progressIndex, m_progressCount);
+ if (progressString)
+ {
+ PercentProgressUpdateEvent(progressString, m_progressIndex, m_progressCount);
+ nsTextFormatter::smprintf_free(progressString);
+ }
+ }
+ }
+}
+
+void
+nsImapProtocol::ProgressEventFunctionUsingName(const char* aMsgName)
+{
+ if (m_imapMailFolderSink && !m_lastProgressStringName.Equals(aMsgName))
+ {
+ m_imapMailFolderSink->ProgressStatusString(this, aMsgName, nullptr);
+ m_lastProgressStringName.Assign(aMsgName);
+ // who's going to free this? Does ProgressStatusString complete synchronously?
+ }
+}
+
+void
+nsImapProtocol::ProgressEventFunctionUsingNameWithString(const char* aMsgName,
+ const char * aExtraInfo)
+{
+ if (m_imapMailFolderSink)
+ {
+ nsString unicodeStr;
+ nsresult rv = CopyMUTF7toUTF16(nsDependentCString(aExtraInfo), unicodeStr);
+ if (NS_SUCCEEDED(rv))
+ m_imapMailFolderSink->ProgressStatusString(this, aMsgName, unicodeStr.get());
+ }
+}
+
+void
+nsImapProtocol::PercentProgressUpdateEvent(char16_t *message, int64_t currentProgress, int64_t maxProgress)
+{
+ int64_t nowMS = 0;
+ int32_t percent = (100 * currentProgress) / maxProgress;
+ if (percent == m_lastPercent)
+ return; // hasn't changed, right? So just return. Do we need to clear this anywhere?
+
+ if (percent < 100) // always need to do 100%
+ {
+ nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
+ if (nowMS - m_lastProgressTime < 750)
+ return;
+ }
+
+ m_lastPercent = percent;
+ m_lastProgressTime = nowMS;
+
+ // set our max progress on the running URL
+ if (m_runningUrl)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
+ mailnewsUrl->SetMaxProgress(maxProgress);
+ }
+
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->PercentProgress(this, message, currentProgress, maxProgress);
+}
+
+ // imap commands issued by the parser
+void
+nsImapProtocol::Store(const nsCString &messageList, const char * messageData,
+ bool idsAreUid)
+{
+
+ // turn messageList back into key array and then back into a message id list,
+ // but use the flag state to handle ranges correctly.
+ nsCString messageIdList;
+ nsTArray<nsMsgKey> msgKeys;
+ if (idsAreUid)
+ ParseUidString(messageList.get(), msgKeys);
+
+ int32_t msgCountLeft = msgKeys.Length();
+ uint32_t msgsHandled = 0;
+ do
+ {
+ nsCString idString;
+
+ uint32_t msgsToHandle = msgCountLeft;
+ if (idsAreUid)
+ AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle, m_flagState, idString); // 20 * 200
+ else
+ idString.Assign(messageList);
+
+
+ msgsHandled += msgsToHandle;
+ msgCountLeft -= msgsToHandle;
+
+ IncrementCommandTagNumber();
+ const char *formatString;
+ if (idsAreUid)
+ formatString = "%s uid store %s %s\015\012";
+ else
+ formatString = "%s store %s %s\015\012";
+
+ // we might need to close this mailbox after this
+ m_closeNeededBeforeSelect = GetDeleteIsMoveToTrash() &&
+ (PL_strcasestr(messageData, "\\Deleted"));
+
+ const char *commandTag = GetServerCommandTag();
+ int protocolStringSize = PL_strlen(formatString) +
+ messageList.Length() + PL_strlen(messageData) +
+ PL_strlen(commandTag) + 1;
+ char *protocolString = (char *) PR_CALLOC( protocolStringSize );
+
+ if (protocolString)
+ {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ formatString, // format string
+ commandTag, // command tag
+ idString.get(),
+ messageData);
+
+ nsresult rv = SendData(protocolString);
+ if (NS_SUCCEEDED(rv))
+ {
+ m_flagChangeCount++;
+ ParseIMAPandCheckForNewMail(protocolString);
+ if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded())
+ Check();
+ }
+ PR_Free(protocolString);
+ }
+ else
+ HandleMemoryFailure();
+ }
+ while (msgCountLeft > 0 && !DeathSignalReceived());
+
+}
+
+void
+nsImapProtocol::IssueUserDefinedMsgCommand(const char *command, const char * messageList)
+{
+ IncrementCommandTagNumber();
+
+ const char *formatString;
+ formatString = "%s uid %s %s\015\012";
+
+ const char *commandTag = GetServerCommandTag();
+ int protocolStringSize = PL_strlen(formatString) +
+ PL_strlen(messageList) + PL_strlen(command) +
+ PL_strlen(commandTag) + 1;
+ char *protocolString = (char *) PR_CALLOC( protocolStringSize );
+
+ if (protocolString)
+ {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ formatString, // format string
+ commandTag, // command tag
+ command,
+ messageList);
+
+ nsresult rv = SendData(protocolString);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(protocolString);
+ PR_Free(protocolString);
+ }
+ else
+ HandleMemoryFailure();
+}
+
+void
+nsImapProtocol::UidExpunge(const nsCString &messageSet)
+{
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+ command.Append(" uid expunge ");
+ command.Append(messageSet);
+ command.Append(CRLF);
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void
+nsImapProtocol::Expunge()
+{
+ uint32_t aclFlags = 0;
+ if (GetServerStateParser().ServerHasACLCapability() && m_imapMailFolderSink)
+ m_imapMailFolderSink->GetAclFlags(&aclFlags);
+
+ if (aclFlags && !(aclFlags & IMAP_ACL_EXPUNGE_FLAG))
+ return;
+ ProgressEventFunctionUsingName("imapStatusExpungingMailbox");
+
+ if(gCheckDeletedBeforeExpunge)
+ {
+ GetServerStateParser().ResetSearchResultSequence();
+ Search("SEARCH DELETED", false, false);
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsImapSearchResultIterator *search = GetServerStateParser().CreateSearchResultIterator();
+ nsMsgKey key = search->GetNextMessageNumber();
+ delete search;
+ if (key == 0)
+ return; //no deleted messages to expunge (bug 235004)
+ }
+ }
+
+ IncrementCommandTagNumber();
+ nsAutoCString command(GetServerCommandTag());
+ command.Append(" expunge" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void
+nsImapProtocol::HandleMemoryFailure()
+{
+ PR_CEnterMonitor(this);
+ // **** jefft fix me!!!!!! ******
+ // m_imapThreadIsRunning = false;
+ // SetConnectionStatus(-1);
+ PR_CExitMonitor(this);
+}
+
+void nsImapProtocol::HandleCurrentUrlError()
+{
+ // This is to handle a move/copy failing, especially because the user
+ // cancelled the password prompt.
+ (void) m_runningUrl->GetImapAction(&m_imapAction);
+ if (m_imapAction == nsIImapUrl::nsImapOfflineToOnlineMove || m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile
+ || m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile)
+ {
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->OnlineCopyCompleted(this, ImapOnlineCopyStateType::kFailedCopy);
+ }
+}
+
+void nsImapProtocol::StartTLS()
+{
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" STARTTLS" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Capability()
+{
+
+ ProgressEventFunctionUsingName("imapStatusCheckCompat");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" capability" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::ID()
+{
+ if (!gAppName[0])
+ return;
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+ command.Append(" ID (\"name\" \"");
+ command.Append(gAppName);
+ command.Append("\" \"version\" \"");
+ command.Append(gAppVersion);
+ command.Append("\")" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::EnableCondStore()
+{
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" ENABLE CONDSTORE" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::StartCompressDeflate()
+{
+ // only issue a compression request if we haven't already
+ if (!TestFlag(IMAP_ISSUED_COMPRESS_REQUEST))
+ {
+ SetFlag(IMAP_ISSUED_COMPRESS_REQUEST);
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" COMPRESS DEFLATE" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ {
+ ParseIMAPandCheckForNewMail();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ rv = BeginCompressing();
+ if (NS_FAILED(rv))
+ {
+ Log("CompressDeflate", nullptr, "failed to enable compression");
+ // we can't use this connection without compression any more, so die
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(rv);
+ return;
+ }
+ }
+ }
+ }
+}
+
+nsresult nsImapProtocol::BeginCompressing()
+{
+ // wrap the streams in compression layers that compress or decompress
+ // all traffic.
+ RefPtr<nsMsgCompressIStream> new_in = new nsMsgCompressIStream();
+ if (!new_in)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = new_in->InitInputStream(m_inputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_inputStream = new_in;
+
+ RefPtr<nsMsgCompressOStream> new_out = new nsMsgCompressOStream();
+ if (!new_out)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = new_out->InitOutputStream(m_outputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_outputStream = new_out;
+ return rv;
+}
+
+void nsImapProtocol::Language()
+{
+ // only issue the language request if we haven't done so already...
+ if (!TestFlag(IMAP_ISSUED_LANGUAGE_REQUEST))
+ {
+ SetFlag(IMAP_ISSUED_LANGUAGE_REQUEST);
+ ProgressEventFunctionUsingName("imapStatusCheckCompat");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ // extract the desired language attribute from prefs
+ nsresult rv = NS_OK;
+
+ // we need to parse out the first language out of this comma separated list....
+ // i.e if we have en,ja we only want to send en to the server.
+ if (mAcceptLanguages.get())
+ {
+ nsAutoCString extractedLanguage;
+ LossyCopyUTF16toASCII(mAcceptLanguages, extractedLanguage);
+ int32_t pos = extractedLanguage.FindChar(',');
+ if (pos > 0) // we have a comma separated list of languages...
+ extractedLanguage.SetLength(pos); // truncate everything after the first comma (including the comma)
+
+ if (extractedLanguage.IsEmpty())
+ return;
+
+ command.Append(" LANGUAGE ");
+ command.Append(extractedLanguage);
+ command.Append(CRLF);
+
+ rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(nullptr, true /* ignore bad or no result from the server for this command */);
+ }
+ }
+}
+
+void nsImapProtocol::EscapeUserNamePasswordString(const char *strToEscape, nsCString *resultStr)
+{
+ if (strToEscape)
+ {
+ uint32_t i = 0;
+ uint32_t escapeStrlen = strlen(strToEscape);
+ for (i=0; i<escapeStrlen; i++)
+ {
+ if (strToEscape[i] == '\\' || strToEscape[i] == '\"')
+ {
+ resultStr->Append('\\');
+ }
+ resultStr->Append(strToEscape[i]);
+ }
+ }
+}
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue,
+ nsIMsgIncomingServer *aServer)
+#else
+void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue)
+#endif
+{
+ // for m_prefAuthMethods, using the same flags as server capablities.
+ switch (authMethodPrefValue)
+ {
+ case nsMsgAuthMethod::none:
+ m_prefAuthMethods = kHasAuthNoneCapability;
+ break;
+ case nsMsgAuthMethod::old:
+ m_prefAuthMethods = kHasAuthOldLoginCapability;
+ break;
+ case nsMsgAuthMethod::passwordCleartext:
+ m_prefAuthMethods = kHasAuthOldLoginCapability |
+ kHasAuthLoginCapability | kHasAuthPlainCapability;
+ break;
+ case nsMsgAuthMethod::passwordEncrypted:
+ m_prefAuthMethods = kHasCRAMCapability;
+ break;
+ case nsMsgAuthMethod::NTLM:
+ m_prefAuthMethods = kHasAuthNTLMCapability | kHasAuthMSNCapability;
+ break;
+ case nsMsgAuthMethod::GSSAPI:
+ m_prefAuthMethods = kHasAuthGssApiCapability;
+ break;
+ case nsMsgAuthMethod::External:
+ m_prefAuthMethods = kHasAuthExternalCapability;
+ break;
+ case nsMsgAuthMethod::secure:
+ m_prefAuthMethods = kHasCRAMCapability |
+ kHasAuthGssApiCapability |
+ kHasAuthNTLMCapability | kHasAuthMSNCapability;
+ break;
+ default:
+ NS_ASSERTION(false, "IMAP: authMethod pref invalid");
+ // TODO log to error console
+ MOZ_LOG(IMAP, LogLevel::Error,
+ ("IMAP: bad pref authMethod = %d\n", authMethodPrefValue));
+ // fall to any
+ MOZ_FALLTHROUGH;
+ case nsMsgAuthMethod::anything:
+ m_prefAuthMethods = kHasAuthOldLoginCapability |
+ kHasAuthLoginCapability | kHasAuthPlainCapability |
+ kHasCRAMCapability | kHasAuthGssApiCapability |
+ kHasAuthNTLMCapability | kHasAuthMSNCapability |
+#ifdef MOZ_MAILNEWS_OAUTH2
+ kHasAuthExternalCapability | kHasXOAuth2Capability;
+ break;
+ case nsMsgAuthMethod::OAuth2:
+ m_prefAuthMethods = kHasXOAuth2Capability;
+#else
+ kHasAuthExternalCapability;
+#endif
+ break;
+
+ }
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ if (m_prefAuthMethods & kHasXOAuth2Capability)
+ mOAuth2Support = new mozilla::mailnews::OAuth2ThreadHelper(aServer);
+
+ // Disable OAuth2 support if we don't have the prefs installed.
+ if (m_prefAuthMethods & kHasXOAuth2Capability &&
+ (!mOAuth2Support || !mOAuth2Support->SupportsOAuth2()))
+ m_prefAuthMethods &= ~kHasXOAuth2Capability;
+#endif
+
+ NS_ASSERTION(m_prefAuthMethods != kCapabilityUndefined,
+ "IMAP: InitPrefAuthMethods() didn't work");
+}
+
+/**
+ * Changes m_currentAuthMethod to pick the best remaining one
+ * which is allowed by server and prefs and not marked failed.
+ * The order of preference and trying of auth methods is encoded here.
+ */
+nsresult nsImapProtocol::ChooseAuthMethod()
+{
+ eIMAPCapabilityFlags serverCaps = GetServerStateParser().GetCapabilityFlag();
+ eIMAPCapabilityFlags availCaps = serverCaps & m_prefAuthMethods & ~m_failedAuthMethods;
+
+ MOZ_LOG(IMAP, LogLevel::Debug, ("IMAP auth: server caps 0x%llx, pref 0x%llx, failed 0x%llx, avail caps 0x%llx",
+ serverCaps, m_prefAuthMethods, m_failedAuthMethods, availCaps));
+#ifdef MOZ_MAILNEWS_OAUTH2
+ MOZ_LOG(IMAP, LogLevel::Debug, ("(GSSAPI = 0x%llx, CRAM = 0x%llx, NTLM = 0x%llx, "
+ "MSN = 0x%llx, PLAIN = 0x%llx,\n LOGIN = 0x%llx, old-style IMAP login = 0x%llx"
+ ", auth external IMAP login = 0x%llx, OAUTH2 = 0x%llx)",
+ kHasAuthGssApiCapability, kHasCRAMCapability, kHasAuthNTLMCapability,
+ kHasAuthMSNCapability, kHasAuthPlainCapability, kHasAuthLoginCapability,
+ kHasAuthOldLoginCapability, kHasAuthExternalCapability, kHasXOAuth2Capability));
+#else
+ MOZ_LOG(IMAP, LogLevel::Debug, ("(GSSAPI = 0x%llx, CRAM = 0x%llx, NTLM = 0x%llx, "
+ "MSN = 0x%llx, PLAIN = 0x%llx,\n LOGIN = 0x%llx, old-style IMAP login = 0x%llx"
+ ", auth external IMAP login = 0x%llx",
+ kHasAuthGssApiCapability, kHasCRAMCapability, kHasAuthNTLMCapability,
+ kHasAuthMSNCapability, kHasAuthPlainCapability, kHasAuthLoginCapability,
+ kHasAuthOldLoginCapability, kHasAuthExternalCapability));
+#endif
+
+ if (kHasAuthExternalCapability & availCaps)
+ m_currentAuthMethod = kHasAuthExternalCapability;
+ else if (kHasAuthGssApiCapability & availCaps)
+ m_currentAuthMethod = kHasAuthGssApiCapability;
+ else if (kHasCRAMCapability & availCaps)
+ m_currentAuthMethod = kHasCRAMCapability;
+ else if (kHasAuthNTLMCapability & availCaps)
+ m_currentAuthMethod = kHasAuthNTLMCapability;
+ else if (kHasAuthMSNCapability & availCaps)
+ m_currentAuthMethod = kHasAuthMSNCapability;
+#ifdef MOZ_MAILNEWS_OAUTH2
+ else if (kHasXOAuth2Capability & availCaps)
+ m_currentAuthMethod = kHasXOAuth2Capability;
+#endif
+ else if (kHasAuthPlainCapability & availCaps)
+ m_currentAuthMethod = kHasAuthPlainCapability;
+ else if (kHasAuthLoginCapability & availCaps)
+ m_currentAuthMethod = kHasAuthLoginCapability;
+ else if (kHasAuthOldLoginCapability & availCaps)
+ m_currentAuthMethod = kHasAuthOldLoginCapability;
+ else
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("no remaining auth method"));
+ m_currentAuthMethod = kCapabilityUndefined;
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_LOG(IMAP, LogLevel::Debug, ("trying auth method 0x%llx", m_currentAuthMethod));
+ return NS_OK;
+}
+
+void nsImapProtocol::MarkAuthMethodAsFailed(eIMAPCapabilityFlags failedAuthMethod)
+{
+ MOZ_LOG(IMAP, LogLevel::Debug, ("marking auth method 0x%llx failed", failedAuthMethod));
+ m_failedAuthMethods |= failedAuthMethod;
+}
+
+/**
+ * Start over, trying all auth methods again
+ */
+void nsImapProtocol::ResetAuthMethods()
+{
+ MOZ_LOG(IMAP, LogLevel::Debug, ("resetting (failed) auth methods"));
+ m_currentAuthMethod = kCapabilityUndefined;
+ m_failedAuthMethods = 0;
+}
+
+nsresult nsImapProtocol::AuthLogin(const char *userName, const nsCString &password, eIMAPCapabilityFlag flag)
+{
+ ProgressEventFunctionUsingName("imapStatusSendingAuthLogin");
+ IncrementCommandTagNumber();
+
+ char * currentCommand=nullptr;
+ nsresult rv;
+
+ MOZ_LOG(IMAP, LogLevel::Debug, ("IMAP: trying auth method 0x%llx", m_currentAuthMethod));
+
+ if (flag & kHasAuthExternalCapability)
+ {
+ char *base64UserName = PL_Base64Encode(userName, strlen(userName), nullptr);
+ nsAutoCString command (GetServerCommandTag());
+ command.Append(" authenticate EXTERNAL " );
+ command.Append(base64UserName);
+ command.Append(CRLF);
+ PR_Free(base64UserName);
+ rv = SendData(command.get());
+ ParseIMAPandCheckForNewMail();
+ nsImapServerResponseParser &parser = GetServerStateParser();
+ if (parser.LastCommandSuccessful())
+ return NS_OK;
+ parser.SetCapabilityFlag(parser.GetCapabilityFlag() & ~kHasAuthExternalCapability);
+ }
+ else if (flag & kHasCRAMCapability)
+ {
+ NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER);
+ MOZ_LOG(IMAP, LogLevel::Debug, ("MD5 auth"));
+ // inform the server that we want to begin a CRAM authentication procedure...
+ nsAutoCString command (GetServerCommandTag());
+ command.Append(" authenticate CRAM-MD5" CRLF);
+ rv = SendData(command.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ char *digest = nullptr;
+ char *cramDigest = GetServerStateParser().fAuthChallenge;
+ char *decodedChallenge = PL_Base64Decode(cramDigest,
+ strlen(cramDigest), nullptr);
+ rv = m_imapServerSink->CramMD5Hash(decodedChallenge, password.get(), &digest);
+ PR_Free(decodedChallenge);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(digest, NS_ERROR_NULL_POINTER);
+ nsAutoCString encodedDigest;
+ char hexVal[8];
+
+ for (uint32_t j=0; j<16; j++)
+ {
+ PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)(digest[j]));
+ encodedDigest.Append(hexVal);
+ }
+
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s %s", userName, encodedDigest.get());
+ char *base64Str = PL_Base64Encode(m_dataOutputBuf, strlen(m_dataOutputBuf), nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+ PR_Free(digest);
+ rv = SendData(m_dataOutputBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail(command.get());
+ }
+ } // if CRAM response was received
+ else if (flag & kHasAuthGssApiCapability)
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("MD5 auth"));
+
+ // Only try GSSAPI once - if it fails, its going to be because we don't
+ // have valid credentials
+ //MarkAuthMethodAsFailed(kHasAuthGssApiCapability);
+
+ // We do step1 first, so we don't try GSSAPI against a server which
+ // we can't get credentials for.
+ nsAutoCString response;
+
+ nsAutoCString service("imap@");
+ service.Append(m_realHostName);
+ rv = DoGSSAPIStep1(service.get(), userName, response);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString command (GetServerCommandTag());
+ command.Append(" authenticate GSSAPI" CRLF);
+ rv = SendData(command.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ParseIMAPandCheckForNewMail("AUTH GSSAPI");
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ response += CRLF;
+ rv = SendData(response.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail(command.get());
+ nsresult gssrv = NS_OK;
+
+ while (GetServerStateParser().LastCommandSuccessful() &&
+ NS_SUCCEEDED(gssrv) && gssrv != NS_SUCCESS_AUTH_FINISHED)
+ {
+ nsCString challengeStr(GetServerStateParser().fAuthChallenge);
+ gssrv = DoGSSAPIStep2(challengeStr, response);
+ if (NS_SUCCEEDED(gssrv))
+ {
+ response += CRLF;
+ rv = SendData(response.get());
+ }
+ else
+ rv = SendData("*" CRLF);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail(command.get());
+ }
+ // TODO: whether it worked or not is shown by LastCommandSuccessful(), not gssrv, right?
+ }
+ }
+ else if (flag & (kHasAuthNTLMCapability | kHasAuthMSNCapability))
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("NTLM auth"));
+ nsAutoCString command (GetServerCommandTag());
+ command.Append((flag & kHasAuthNTLMCapability) ? " authenticate NTLM" CRLF
+ : " authenticate MSN" CRLF);
+ rv = SendData(command.get());
+ ParseIMAPandCheckForNewMail("AUTH NTLM"); // this just waits for ntlm step 1
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsAutoCString cmd;
+ rv = DoNtlmStep1(userName, password.get(), cmd);
+ NS_ENSURE_SUCCESS(rv, rv);
+ cmd += CRLF;
+ rv = SendData(cmd.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail(command.get());
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsCString challengeStr(GetServerStateParser().fAuthChallenge);
+ nsCString response;
+ rv = DoNtlmStep2(challengeStr, response);
+ NS_ENSURE_SUCCESS(rv, rv);
+ response += CRLF;
+ rv = SendData(response.get());
+ ParseIMAPandCheckForNewMail(command.get());
+ }
+ }
+ }
+ else if (flag & kHasAuthPlainCapability)
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("PLAIN auth"));
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s authenticate PLAIN" CRLF, GetServerCommandTag());
+ rv = SendData(m_dataOutputBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+ currentCommand = PL_strdup(m_dataOutputBuf); /* StrAllocCopy(currentCommand, GetOutputBuffer()); */
+ ParseIMAPandCheckForNewMail();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ // RFC 4616
+ char plainstr[512]; // placeholder for "<NUL>userName<NUL>password" TODO nsAutoCString
+ int len = 1; // count for first <NUL> char
+ memset(plainstr, 0, 512);
+ PR_snprintf(&plainstr[1], 510, "%s", userName);
+ len += PL_strlen(userName);
+ len++; // count for second <NUL> char
+ PR_snprintf(&plainstr[len], 511-len, "%s", password.get());
+ len += password.Length();
+ char *base64Str = PL_Base64Encode(plainstr, len, nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+
+ bool isResend = false;
+ while (true)
+ {
+ // Send authentication string (true: suppress logging the string).
+ rv = SendData(m_dataOutputBuf, true);
+ if (NS_FAILED(rv))
+ break;
+ ParseIMAPandCheckForNewMail(currentCommand);
+ if (!GetServerStateParser().WaitingForMoreClientInput())
+ break;
+
+ // Server is asking for authentication string again. So we send the
+ // same string again although we already know that it will be
+ // rejected again. We do that to get a firm authentication failure
+ // instead of a resend request. That keeps things in order before
+ // failing "authenticate PLAIN" and trying another method if capable.
+ if (isResend)
+ {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ isResend = true;
+ }
+ } // if the last command succeeded
+ } // if auth plain capability
+ else if (flag & kHasAuthLoginCapability)
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("LOGIN auth"));
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s authenticate LOGIN" CRLF, GetServerCommandTag());
+ rv = SendData(m_dataOutputBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+ currentCommand = PL_strdup(m_dataOutputBuf);
+ ParseIMAPandCheckForNewMail();
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ char *base64Str = PL_Base64Encode(userName, PL_strlen(userName), nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+ rv = SendData(m_dataOutputBuf, true /* suppress logging */);
+ if (NS_SUCCEEDED(rv))
+ {
+ ParseIMAPandCheckForNewMail(currentCommand);
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ base64Str = PL_Base64Encode(password.get(), password.Length(), nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+ rv = SendData(m_dataOutputBuf, true /* suppress logging */);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(currentCommand);
+ } // if last command successful
+ } // if last command successful
+ } // if last command successful
+ } // if has auth login capability
+ else if (flag & kHasAuthOldLoginCapability)
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("old-style auth"));
+ ProgressEventFunctionUsingName("imapStatusSendingLogin");
+ IncrementCommandTagNumber();
+ nsCString command (GetServerCommandTag());
+ nsAutoCString escapedUserName;
+ command.Append(" login \"");
+ EscapeUserNamePasswordString(userName, &escapedUserName);
+ command.Append(escapedUserName);
+ command.Append("\" \"");
+
+ // if the password contains a \, login will fail
+ // turn foo\bar into foo\\bar
+ nsAutoCString correctedPassword;
+ EscapeUserNamePasswordString(password.get(), &correctedPassword);
+ command.Append(correctedPassword);
+ command.Append("\"" CRLF);
+ rv = SendData(command.get(), true /* suppress logging */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail();
+ }
+#ifdef MOZ_MAILNEWS_OAUTH2
+ else if (flag & kHasXOAuth2Capability)
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("XOAUTH2 auth"));
+
+ // Get the XOAuth2 base64 string.
+ NS_ASSERTION(mOAuth2Support,
+ "What are we doing here without OAuth2 helper?");
+ if (!mOAuth2Support)
+ return NS_ERROR_UNEXPECTED;
+ nsAutoCString base64Str;
+ mOAuth2Support->GetXOAuth2String(base64Str);
+ mOAuth2Support = nullptr; // Its purpose has been served.
+ if (base64Str.IsEmpty())
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("OAuth2 failed"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Send the data on the network.
+ nsAutoCString command (GetServerCommandTag());
+ command += " AUTHENTICATE XOAUTH2 ";
+ command += base64Str;
+ command += CRLF;
+ rv = SendData(command.get(), true /* suppress logging */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail();
+ }
+#endif
+ else if (flag & kHasAuthNoneCapability)
+ {
+ // TODO What to do? "login <username>" like POP?
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ else
+ {
+ MOZ_LOG(IMAP, LogLevel::Error, ("flags param has no auth scheme selected"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PR_Free(currentCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return GetServerStateParser().LastCommandSuccessful() ?
+ NS_OK : NS_ERROR_FAILURE;
+}
+
+void nsImapProtocol::OnLSubFolders()
+{
+ // **** use to find out whether Drafts, Sent, & Templates folder
+ // exists or not even the user didn't subscribe to it
+ char *mailboxName = OnCreateServerSourceFolderPathString();
+ if (mailboxName)
+ {
+ ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
+ IncrementCommandTagNumber();
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE,"%s list \"\" \"%s\"" CRLF, GetServerCommandTag(), mailboxName);
+ nsresult rv = SendData(m_dataOutputBuf);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+ PR_Free(mailboxName);
+ }
+ else
+ {
+ HandleMemoryFailure();
+ }
+
+}
+
+void nsImapProtocol::OnAppendMsgFromFile()
+{
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_OK;
+ rv = m_runningUrl->GetMsgFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv) && file)
+ {
+ char *mailboxName = OnCreateServerSourceFolderPathString();
+ if (mailboxName)
+ {
+ imapMessageFlagsType flagsToSet = 0;
+ uint32_t msgFlags = 0;
+ PRTime date = 0;
+ nsCString keywords;
+ if (m_imapMessageSink)
+ m_imapMessageSink->GetCurMoveCopyMessageInfo(m_runningUrl, &date,
+ keywords, &msgFlags);
+
+ if (msgFlags & nsMsgMessageFlags::Read)
+ flagsToSet |= kImapMsgSeenFlag;
+ if (msgFlags & nsMsgMessageFlags::MDNReportSent)
+ flagsToSet |= kImapMsgMDNSentFlag;
+ // convert msg flag label (0xE000000) to imap flag label (0x0E00)
+ if (msgFlags & nsMsgMessageFlags::Labels)
+ flagsToSet |= (msgFlags & nsMsgMessageFlags::Labels) >> 16;
+ if (msgFlags & nsMsgMessageFlags::Marked)
+ flagsToSet |= kImapMsgFlaggedFlag;
+ if (msgFlags & nsMsgMessageFlags::Replied)
+ flagsToSet |= kImapMsgAnsweredFlag;
+ if (msgFlags & nsMsgMessageFlags::Forwarded)
+ flagsToSet |= kImapMsgForwardedFlag;
+
+ // If the message copied was a draft, flag it as such
+ nsImapAction imapAction;
+ rv = m_runningUrl->GetImapAction(&imapAction);
+ if (NS_SUCCEEDED(rv) && (imapAction == nsIImapUrl::nsImapAppendDraftFromFile))
+ flagsToSet |= kImapMsgDraftFlag;
+ UploadMessageFromFile(file, mailboxName, date, flagsToSet, keywords);
+ PR_Free( mailboxName );
+ }
+ else
+ {
+ HandleMemoryFailure();
+ }
+ }
+}
+
+void nsImapProtocol::UploadMessageFromFile (nsIFile* file,
+ const char* mailboxName,
+ PRTime date,
+ imapMessageFlagsType flags,
+ nsCString &keywords)
+{
+ if (!file || !mailboxName) return;
+ IncrementCommandTagNumber();
+
+ int64_t fileSize = 0;
+ int64_t totalSize;
+ uint32_t readCount;
+ char *dataBuffer = nullptr;
+ nsCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsresult rv;
+ bool eof = false;
+ nsCString flagString;
+
+ nsCOMPtr <nsIInputStream> fileInputStream;
+
+ if (!escapedName.IsEmpty())
+ {
+ command.Append(" append \"");
+ command.Append(escapedName);
+ command.Append("\"");
+ if (flags || keywords.Length())
+ {
+ command.Append(" (");
+
+ if (flags)
+ {
+ SetupMessageFlagsString(flagString, flags,
+ GetServerStateParser().SupportsUserFlags());
+ command.Append(flagString);
+ }
+ if (keywords.Length())
+ {
+ if (flags)
+ command.Append(' ');
+ command.Append(keywords);
+ }
+ command.Append(")");
+ }
+
+ // date should never be 0, but just in case...
+ if (date)
+ {
+ /* Use PR_FormatTimeUSEnglish() to format the date in US English format,
+ then figure out what our local GMT offset is, and append it (since
+ PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as
+ per RFC 1123 (superceding RFC 822.)
+ */
+ char szDateTime[64];
+ char dateStr[100];
+ PRExplodedTime exploded;
+ PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(szDateTime, sizeof(szDateTime), "%d-%b-%Y %H:%M:%S", &exploded);
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
+ int gmtoffset = (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60;
+ PR_snprintf(dateStr, sizeof(dateStr),
+ " \"%s %c%02d%02d\"",
+ szDateTime,
+ (gmtoffset >= 0 ? '+' : '-'),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60));
+
+ command.Append(dateStr);
+ }
+ command.Append(" {");
+
+ dataBuffer = (char*) PR_CALLOC(COPY_BUFFER_SIZE+1);
+ if (!dataBuffer) goto done;
+ rv = file->GetFileSize(&fileSize);
+ NS_ASSERTION(fileSize, "got empty file in UploadMessageFromFile");
+ if (NS_FAILED(rv) || !fileSize) goto done;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), file);
+ if (NS_FAILED(rv) || !fileInputStream) goto done;
+ command.AppendInt((int32_t)fileSize);
+
+ // Set useLiteralPlus to true if server has capability LITERAL+ and
+ // LITERAL+ useage is enabled in the config editor,
+ // i.e., "mail.imap.use_literal_plus" = true.
+ bool useLiteralPlus = (GetServerStateParser().GetCapabilityFlag() &
+ kLiteralPlusCapability) && gUseLiteralPlus;
+ if (useLiteralPlus)
+ command.Append("+}" CRLF);
+ else
+ command.Append("}" CRLF);
+
+ rv = SendData(command.get());
+ if (NS_FAILED(rv)) goto done;
+
+ if (!useLiteralPlus)
+ {
+ ParseIMAPandCheckForNewMail();
+ if (!GetServerStateParser().LastCommandSuccessful())
+ goto done;
+ }
+
+ totalSize = fileSize;
+ readCount = 0;
+ while(NS_SUCCEEDED(rv) && !eof && totalSize > 0)
+ {
+ rv = fileInputStream->Read(dataBuffer, COPY_BUFFER_SIZE, &readCount);
+ if (NS_SUCCEEDED(rv) && !readCount)
+ rv = NS_ERROR_FAILURE;
+
+ if (NS_SUCCEEDED(rv))
+ {
+ NS_ASSERTION(readCount <= (uint32_t) totalSize, "got more bytes than there should be");
+ dataBuffer[readCount] = 0;
+ rv = SendData(dataBuffer);
+ totalSize -= readCount;
+ PercentProgressUpdateEvent(nullptr, fileSize - totalSize, fileSize);
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = SendData(CRLF); // complete the append
+ ParseIMAPandCheckForNewMail(command.get());
+
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+
+ if (GetServerStateParser().LastCommandSuccessful() && (
+ imapAction == nsIImapUrl::nsImapAppendDraftFromFile || imapAction==nsIImapUrl::nsImapAppendMsgFromFile))
+ {
+ if (GetServerStateParser().GetCapabilityFlag() &
+ kUidplusCapability)
+ {
+ nsMsgKey newKey = GetServerStateParser().CurrentResponseUID();
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->SetAppendMsgUid(newKey, m_runningUrl);
+
+ // Courier imap server seems to have problems with recently
+ // appended messages. Noop seems to clear its confusion.
+ if (FolderIsSelected(mailboxName))
+ Noop();
+
+ nsCString oldMsgId;
+ rv = m_runningUrl->GetListOfMessageIds(oldMsgId);
+ if (NS_SUCCEEDED(rv) && !oldMsgId.IsEmpty())
+ {
+ bool idsAreUids = true;
+ m_runningUrl->MessageIdsAreUids(&idsAreUids);
+ Store(oldMsgId, "+FLAGS (\\Deleted)", idsAreUids);
+ UidExpunge(oldMsgId);
+ }
+ }
+ // for non UIDPLUS servers,
+ // this code used to check for imapAction==nsIImapUrl::nsImapAppendMsgFromFile, which
+ // meant we'd get into this code whenever sending a message, as well
+ // as when copying messages to an imap folder from local folders or an other imap server.
+ // This made sending a message slow when there was a large sent folder. I don't believe
+ // this code worked anyway.
+ else if (m_imapMailFolderSink && imapAction == nsIImapUrl::nsImapAppendDraftFromFile )
+ { // *** code me to search for the newly appended message
+ // go to selected state
+ nsCString messageId;
+ rv = m_imapMailFolderSink->GetMessageId(m_runningUrl, messageId);
+ if (NS_SUCCEEDED(rv) && !messageId.IsEmpty() &&
+ GetServerStateParser().LastCommandSuccessful())
+ {
+ // if the appended to folder isn't selected in the connection,
+ // select it.
+ if (!FolderIsSelected(mailboxName))
+ SelectMailbox(mailboxName);
+ else
+ Noop(); // See if this makes SEARCH work on the newly appended msg.
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ command = "SEARCH UNDELETED HEADER Message-ID ";
+ command.Append(messageId);
+
+ // Clean up result sequence before issuing the cmd.
+ GetServerStateParser().ResetSearchResultSequence();
+
+ Search(command.get(), true, false);
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsMsgKey newkey = nsMsgKey_None;
+ nsImapSearchResultIterator *searchResult =
+ GetServerStateParser().CreateSearchResultIterator();
+ newkey = searchResult->GetNextMessageNumber();
+ delete searchResult;
+ if (newkey != nsMsgKey_None)
+ m_imapMailFolderSink->SetAppendMsgUid(newkey, m_runningUrl);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+done:
+ PR_Free(dataBuffer);
+ if (fileInputStream)
+ fileInputStream->Close();
+}
+
+//caller must free using PR_Free
+char * nsImapProtocol::OnCreateServerSourceFolderPathString()
+{
+ char *sourceMailbox = nullptr;
+ char hierarchyDelimiter = 0;
+ char onlineDelimiter = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter);
+
+ if (onlineDelimiter != kOnlineHierarchySeparatorUnknown &&
+ onlineDelimiter != hierarchyDelimiter)
+ m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter);
+
+ m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox);
+
+ return sourceMailbox;
+}
+
+//caller must free using PR_Free, safe to call from ui thread
+char * nsImapProtocol::GetFolderPathString()
+{
+ char *sourceMailbox = nullptr;
+ char onlineSubDirDelimiter = 0;
+ char hierarchyDelimiter = 0;
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+
+ m_runningUrl->GetOnlineSubDirSeparator(&onlineSubDirDelimiter);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ mailnewsUrl->GetFolder(getter_AddRefs(msgFolder));
+ if (msgFolder)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
+ if (imapFolder)
+ {
+ imapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
+ if (hierarchyDelimiter != kOnlineHierarchySeparatorUnknown &&
+ onlineSubDirDelimiter != hierarchyDelimiter)
+ m_runningUrl->SetOnlineSubDirSeparator(hierarchyDelimiter);
+ }
+ }
+ m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox);
+
+ return sourceMailbox;
+}
+
+nsresult nsImapProtocol::CreateServerSourceFolderPathString(char **result)
+{
+ NS_ENSURE_ARG(result);
+ *result = OnCreateServerSourceFolderPathString();
+ return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+//caller must free using PR_Free
+char * nsImapProtocol::OnCreateServerDestinationFolderPathString()
+{
+ char *destinationMailbox = nullptr;
+ char hierarchyDelimiter = 0;
+ char onlineDelimiter = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter);
+ if (onlineDelimiter != kOnlineHierarchySeparatorUnknown &&
+ onlineDelimiter != hierarchyDelimiter)
+ m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter);
+
+ m_runningUrl->CreateServerDestinationFolderPathString(&destinationMailbox);
+
+ return destinationMailbox;
+}
+
+void nsImapProtocol::OnCreateFolder(const char * aSourceMailbox)
+{
+ bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
+ if (created)
+ {
+ m_hierarchyNameState = kListingForCreate;
+ nsCString mailboxWODelim(aSourceMailbox);
+ RemoveHierarchyDelimiter(mailboxWODelim);
+ List(mailboxWODelim.get(), false);
+ m_hierarchyNameState = kNoOperationInProgress;
+ }
+ else
+ FolderNotCreated(aSourceMailbox);
+}
+
+void nsImapProtocol::OnEnsureExistsFolder(const char * aSourceMailbox)
+{
+
+ List(aSourceMailbox, false); // how to tell if that succeeded?
+ bool exists = false;
+
+ // try converting aSourceMailbox to canonical format
+
+ nsIMAPNamespace *nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ aSourceMailbox, nsForMailbox);
+ // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox\n");
+
+ nsCString name;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(aSourceMailbox,
+ nsForMailbox->GetDelimiter(),
+ getter_Copies(name));
+ else
+ m_runningUrl->AllocateCanonicalPath(aSourceMailbox,
+ kOnlineHierarchySeparatorUnknown,
+ getter_Copies(name));
+
+ if (m_imapServerSink)
+ m_imapServerSink->FolderVerifiedOnline(name, &exists);
+
+ if (exists)
+ {
+ Subscribe(aSourceMailbox);
+ }
+ else
+ {
+ bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
+ if (created)
+ {
+ List(aSourceMailbox, false);
+ }
+ }
+ if (!GetServerStateParser().LastCommandSuccessful())
+ FolderNotCreated(aSourceMailbox);
+}
+
+
+void nsImapProtocol::OnSubscribe(const char * sourceMailbox)
+{
+ Subscribe(sourceMailbox);
+}
+
+void nsImapProtocol::OnUnsubscribe(const char * sourceMailbox)
+{
+ // When we try to auto-unsubscribe from \Noselect folders,
+ // some servers report errors if we were already unsubscribed
+ // from them.
+ bool lastReportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Unsubscribe(sourceMailbox);
+ GetServerStateParser().SetReportingErrors(lastReportingErrors);
+}
+
+void nsImapProtocol::RefreshACLForFolderIfNecessary(const char *mailboxName)
+{
+ if (GetServerStateParser().ServerHasACLCapability())
+ {
+ if (!m_folderNeedsACLRefreshed && m_imapMailFolderSink)
+ m_imapMailFolderSink->GetFolderNeedsACLListed(&m_folderNeedsACLRefreshed);
+ if (m_folderNeedsACLRefreshed)
+ {
+ RefreshACLForFolder(mailboxName);
+ m_folderNeedsACLRefreshed = false;
+ }
+ }
+}
+
+void nsImapProtocol::RefreshACLForFolder(const char *mailboxName)
+{
+
+ nsIMAPNamespace *ns = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(), mailboxName, ns);
+ if (ns)
+ {
+ switch (ns->GetType())
+ {
+ case kPersonalNamespace:
+ // It's a personal folder, most likely.
+ // I find it hard to imagine a server that supports ACL that doesn't support NAMESPACE,
+ // so most likely we KNOW that this is a personal, rather than the default, namespace.
+
+ // First, clear what we have.
+ ClearAllFolderRights();
+ // Now, get the new one.
+ GetMyRightsForFolder(mailboxName);
+ if (m_imapMailFolderSink)
+ {
+ uint32_t aclFlags = 0;
+ if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags)) && aclFlags & IMAP_ACL_ADMINISTER_FLAG)
+ GetACLForFolder(mailboxName);
+ }
+
+ // We're all done, refresh the icon/flags for this folder
+ RefreshFolderACLView(mailboxName, ns);
+ break;
+ default:
+ // We know it's a public folder or other user's folder.
+ // We only want our own rights
+
+ // First, clear what we have
+ ClearAllFolderRights();
+ // Now, get the new one.
+ GetMyRightsForFolder(mailboxName);
+ // We're all done, refresh the icon/flags for this folder
+ RefreshFolderACLView(mailboxName, ns);
+ break;
+ }
+ }
+ else
+ {
+ // no namespace, not even default... can this happen?
+ NS_ASSERTION(false, "couldn't get namespace");
+ }
+}
+
+void nsImapProtocol::RefreshFolderACLView(const char *mailboxName, nsIMAPNamespace *nsForMailbox)
+{
+ nsCString canonicalMailboxName;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(mailboxName, nsForMailbox->GetDelimiter(), getter_Copies(canonicalMailboxName));
+ else
+ m_runningUrl->AllocateCanonicalPath(mailboxName, kOnlineHierarchySeparatorUnknown, getter_Copies(canonicalMailboxName));
+
+ if (m_imapServerSink)
+ m_imapServerSink->RefreshFolderRights(canonicalMailboxName);
+}
+
+void nsImapProtocol::GetACLForFolder(const char *mailboxName)
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ command.Append(" getacl \"");
+ command.Append(escapedName);
+ command.Append("\"" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::OnRefreshAllACLs()
+{
+ m_hierarchyNameState = kListingForInfoOnly;
+ nsIMAPMailboxInfo *mb = NULL;
+
+ // This will fill in the list
+ List("*", true);
+
+ int32_t total = m_listedMailboxList.Length(), count = 0;
+ GetServerStateParser().SetReportingErrors(false);
+ for (int32_t i = 0; i < total; i++)
+ {
+ mb = m_listedMailboxList.ElementAt(i);
+ if (mb) // paranoia
+ {
+ char *onlineName = nullptr;
+ m_runningUrl->AllocateServerPath(PromiseFlatCString(mb->GetMailboxName()).get(), mb->GetDelimiter(), &onlineName);
+ if (onlineName)
+ {
+ RefreshACLForFolder(onlineName);
+ NS_Free(onlineName);
+ }
+ PercentProgressUpdateEvent(NULL, count, total);
+ delete mb;
+ count++;
+ }
+ }
+ m_listedMailboxList.Clear();
+
+ PercentProgressUpdateEvent(NULL, 100, 100);
+ GetServerStateParser().SetReportingErrors(true);
+ m_hierarchyNameState = kNoOperationInProgress;
+}
+
+// any state commands
+void nsImapProtocol::Logout(bool shuttingDown /* = false */,
+ bool waitForResponse /* = true */)
+{
+ if (!shuttingDown)
+ ProgressEventFunctionUsingName("imapStatusLoggingOut");
+
+/******************************************************************
+ * due to the undo functionality we cannot issule a close when logout; there
+ * is no way to do an undo if the message has been permanently expunge
+ * jt - 07/12/1999
+
+ bool closeNeeded = GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected;
+
+ if (closeNeeded && GetDeleteIsMoveToTrash())
+ Close();
+********************/
+
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" logout" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (m_transport && shuttingDown)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
+ // the socket may be dead before we read the response, so drop it.
+ if (NS_SUCCEEDED(rv) && waitForResponse)
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Noop()
+{
+ //ProgressUpdateEvent("noop...");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" noop" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::XServerInfo()
+{
+
+ ProgressEventFunctionUsingName("imapGettingServerInfo");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" XSERVERINFO MANAGEACCOUNTURL MANAGELISTSURL MANAGEFILTERSURL" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Netscape()
+{
+ ProgressEventFunctionUsingName("imapGettingServerInfo");
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" netscape" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+
+
+void nsImapProtocol::XMailboxInfo(const char *mailboxName)
+{
+
+ ProgressEventFunctionUsingName("imapGettingMailboxInfo");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" XMAILBOXINFO \"");
+ command.Append(mailboxName);
+ command.Append("\" MANAGEURL POSTURL" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Namespace()
+{
+
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" namespace" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+
+void nsImapProtocol::MailboxData()
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" mailboxdata" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+
+void nsImapProtocol::GetMyRightsForFolder(const char *mailboxName)
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ if (MailboxIsNoSelectMailbox(escapedName.get()))
+ return; // Don't issue myrights on Noselect folder
+
+ command.Append(" myrights \"");
+ command.Append(escapedName);
+ command.Append("\"" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+bool nsImapProtocol::FolderIsSelected(const char *mailboxName)
+{
+ return (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected && GetServerStateParser().GetSelectedMailboxName() &&
+ PL_strcmp(GetServerStateParser().GetSelectedMailboxName(),
+ mailboxName) == 0);
+}
+
+void nsImapProtocol::OnStatusForFolder(const char *mailboxName)
+{
+
+ if (FolderIsSelected(mailboxName))
+ {
+ int32_t prevNumMessages = GetServerStateParser().NumberOfMessages();
+ Noop();
+ // OnNewIdleMessages will cause the ui thread to update the folder
+ if (m_imapMailFolderSink && (GetServerStateParser().NumberOfRecentMessages()
+ || prevNumMessages != GetServerStateParser().NumberOfMessages()))
+ m_imapMailFolderSink->OnNewIdleMessages();
+ return;
+ }
+
+ IncrementCommandTagNumber();
+
+ nsAutoCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ command.Append(" STATUS \"");
+ command.Append(escapedName);
+ command.Append("\" (UIDNEXT MESSAGES UNSEEN RECENT)" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsImapMailboxSpec *new_spec = GetServerStateParser().CreateCurrentMailboxSpec(mailboxName);
+ if (new_spec && m_imapMailFolderSink)
+ m_imapMailFolderSink->UpdateImapMailboxStatus(this, new_spec);
+ NS_IF_RELEASE(new_spec);
+ }
+}
+
+
+void nsImapProtocol::OnListFolder(const char * aSourceMailbox, bool aBool)
+{
+ List(aSourceMailbox, aBool);
+}
+
+
+// Returns true if the mailbox is a NoSelect mailbox.
+// If we don't know about it, returns false.
+bool nsImapProtocol::MailboxIsNoSelectMailbox(const char *mailboxName)
+{
+ bool rv = false;
+
+ nsIMAPNamespace *nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ mailboxName, nsForMailbox);
+ // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox\n");
+
+ nsCString name;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(mailboxName,
+ nsForMailbox->GetDelimiter(),
+ getter_Copies(name));
+ else
+ m_runningUrl->AllocateCanonicalPath(mailboxName,
+ kOnlineHierarchySeparatorUnknown,
+ getter_Copies(name));
+
+ if (name.IsEmpty())
+ return false;
+
+ NS_ASSERTION(m_imapServerSink, "unexpected, no imap server sink, see bug #194335");
+ if (m_imapServerSink)
+ m_imapServerSink->FolderIsNoSelect(name, &rv);
+ return rv;
+}
+
+nsresult nsImapProtocol::SetFolderAdminUrl(const char *mailboxName)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER; // if m_imapServerSink is null, rv will be this.
+
+ nsIMAPNamespace *nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ mailboxName, nsForMailbox);
+
+ nsCString name;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(mailboxName,
+ nsForMailbox->GetDelimiter(),
+ getter_Copies(name));
+ else
+ m_runningUrl->AllocateCanonicalPath(mailboxName,
+ kOnlineHierarchySeparatorUnknown,
+ getter_Copies(name));
+
+ if (m_imapServerSink)
+ rv = m_imapServerSink->SetFolderAdminURL(name, nsDependentCString(GetServerStateParser().GetManageFolderUrl()));
+ return rv;
+}
+// returns true is the delete succeeded (regardless of subscription changes)
+bool nsImapProtocol::DeleteMailboxRespectingSubscriptions(const char *mailboxName)
+{
+ bool rv = true;
+ if (!MailboxIsNoSelectMailbox(mailboxName))
+ {
+ // Only try to delete it if it really exists
+ DeleteMailbox(mailboxName);
+ rv = GetServerStateParser().LastCommandSuccessful();
+ }
+
+ // We can unsubscribe even if the mailbox doesn't exist.
+ if (rv && m_autoUnsubscribe) // auto-unsubscribe is on
+ {
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Unsubscribe(mailboxName);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+
+ }
+ return (rv);
+}
+
+// returns true is the rename succeeded (regardless of subscription changes)
+// reallyRename tells us if we should really do the rename (true) or if we should just move subscriptions (false)
+bool nsImapProtocol::RenameMailboxRespectingSubscriptions(const char *existingName, const char *newName, bool reallyRename)
+{
+ bool rv = true;
+ if (reallyRename && !MailboxIsNoSelectMailbox(existingName))
+ {
+ RenameMailbox(existingName, newName);
+ rv = GetServerStateParser().LastCommandSuccessful();
+ }
+
+ if (rv)
+ {
+ if (m_autoSubscribe) // if auto-subscribe is on
+ {
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Subscribe(newName);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+ if (m_autoUnsubscribe) // if auto-unsubscribe is on
+ {
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Unsubscribe(existingName);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+ }
+ return (rv);
+}
+
+bool nsImapProtocol::RenameHierarchyByHand(const char *oldParentMailboxName,
+ const char *newParentMailboxName)
+{
+ bool renameSucceeded = true;
+ char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
+ m_deletableChildren = new nsTArray<char*>();
+
+ bool nonHierarchicalRename =
+ ((GetServerStateParser().GetCapabilityFlag() & kNoHierarchyRename)
+ || MailboxIsNoSelectMailbox(oldParentMailboxName));
+
+ if (m_deletableChildren)
+ {
+ m_hierarchyNameState = kDeleteSubFoldersInProgress;
+ nsIMAPNamespace *ns = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ oldParentMailboxName,
+ ns); // for delimiter
+ if (!ns)
+ {
+ if (!PL_strcasecmp(oldParentMailboxName, "INBOX"))
+ m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(),
+ kPersonalNamespace,
+ ns);
+ }
+ if (ns)
+ {
+ nsCString pattern(oldParentMailboxName);
+ pattern += ns->GetDelimiter();
+ pattern += "*";
+ bool isUsingSubscription = false;
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
+ isUsingSubscription);
+
+ if (isUsingSubscription)
+ Lsub(pattern.get(), false);
+ else
+ List(pattern.get(), false);
+ }
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ renameSucceeded = // rename this, and move subscriptions
+ RenameMailboxRespectingSubscriptions(oldParentMailboxName,
+ newParentMailboxName, true);
+
+ size_t numberToDelete = m_deletableChildren->Length();
+ size_t childIndex;
+
+ for (childIndex = 0;
+ (childIndex < numberToDelete) && renameSucceeded; childIndex++)
+ {
+ // the imap parser has already converted to a non UTF7 string in the canonical
+ // format so convert it back
+ char *currentName = m_deletableChildren->ElementAt(childIndex);
+ if (currentName)
+ {
+ char *serverName = nullptr;
+ m_runningUrl->AllocateServerPath(currentName,
+ onlineDirSeparator,
+ &serverName);
+ PR_FREEIF(currentName);
+ currentName = serverName;
+ }
+
+ // calculate the new name and do the rename
+ nsCString newChildName(newParentMailboxName);
+ newChildName += (currentName + PL_strlen(oldParentMailboxName));
+ // Pass in 'nonHierarchicalRename' to determine if we should really
+ // reanme, or just move subscriptions.
+ renameSucceeded =
+ RenameMailboxRespectingSubscriptions(currentName,
+ newChildName.get(),
+ nonHierarchicalRename);
+ PR_FREEIF(currentName);
+ }
+
+ delete m_deletableChildren;
+ m_deletableChildren = nullptr;
+ }
+
+ return renameSucceeded;
+}
+
+bool nsImapProtocol::DeleteSubFolders(const char* selectedMailbox, bool &aDeleteSelf)
+{
+ bool deleteSucceeded = true;
+ m_deletableChildren = new nsTArray<char*>();
+
+ if (m_deletableChildren)
+ {
+ bool folderDeleted = false;
+
+ m_hierarchyNameState = kDeleteSubFoldersInProgress;
+ nsCString pattern(selectedMailbox);
+ char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
+ m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
+ pattern.Append(onlineDirSeparator);
+ pattern.Append('*');
+
+ if (!pattern.IsEmpty())
+ {
+ List(pattern.get(), false);
+ }
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ // this should be a short list so perform a sequential search for the
+ // longest name mailbox. Deleting the longest first will hopefully
+ // prevent the server from having problems about deleting parents
+ // ** jt - why? I don't understand this.
+ size_t numberToDelete = m_deletableChildren->Length();
+ size_t outerIndex, innerIndex;
+
+ // intelligently decide if myself(either plain format or following the dir-separator)
+ // is in the sub-folder list
+ bool folderInSubfolderList = false; // For Performance
+ char *selectedMailboxDir = nullptr;
+ {
+ int32_t length = strlen(selectedMailbox);
+ selectedMailboxDir = (char *)PR_MALLOC(length+2);
+ if( selectedMailboxDir ) // only do the intelligent test if there is enough memory
+ {
+ strcpy(selectedMailboxDir, selectedMailbox);
+ selectedMailboxDir[length] = onlineDirSeparator;
+ selectedMailboxDir[length+1] = '\0';
+ size_t i;
+ for( i=0; i<numberToDelete && !folderInSubfolderList; i++ )
+ {
+ char *currentName = m_deletableChildren->ElementAt(i);
+ if( !strcmp(currentName, selectedMailbox) || !strcmp(currentName, selectedMailboxDir) )
+ folderInSubfolderList = true;
+ }
+ }
+ }
+
+ deleteSucceeded = GetServerStateParser().LastCommandSuccessful();
+ for (outerIndex = 0;
+ (outerIndex < numberToDelete) && deleteSucceeded;
+ outerIndex++)
+ {
+ char* longestName = nullptr;
+ size_t longestIndex = 0; // fix bogus warning by initializing
+ for (innerIndex = 0;
+ innerIndex < m_deletableChildren->Length();
+ innerIndex++)
+ {
+ char *currentName = m_deletableChildren->ElementAt(innerIndex);
+ if (!longestName || strlen(longestName) < strlen(currentName))
+ {
+ longestName = currentName;
+ longestIndex = innerIndex;
+ }
+ }
+ // the imap parser has already converted to a non UTF7 string in
+ // the canonical format so convert it back
+ if (longestName)
+ {
+ char *serverName = nullptr;
+
+ m_deletableChildren->RemoveElementAt(longestIndex);
+ m_runningUrl->AllocateServerPath(longestName,
+ onlineDirSeparator,
+ &serverName);
+ PR_FREEIF(longestName);
+ longestName = serverName;
+ }
+
+ // some imap servers include the selectedMailbox in the list of
+ // subfolders of the selectedMailbox. Check for this so we don't
+ // delete the selectedMailbox (usually the trash and doing an
+ // empty trash)
+ // The Cyrus imap server ignores the "INBOX.Trash" constraining
+ // string passed to the list command. Be defensive and make sure
+ // we only delete children of the trash
+ if (longestName &&
+ strcmp(selectedMailbox, longestName) &&
+ !strncmp(selectedMailbox, longestName, strlen(selectedMailbox)))
+ {
+ if( selectedMailboxDir && !strcmp(selectedMailboxDir, longestName) ) // just myself
+ {
+ if( aDeleteSelf )
+ {
+ bool deleted = DeleteMailboxRespectingSubscriptions(longestName);
+ if (deleted)
+ FolderDeleted(longestName);
+ folderDeleted = deleted;
+ deleteSucceeded = deleted;
+ }
+ }
+ else
+ {
+ if (m_imapServerSink)
+ m_imapServerSink->ResetServerConnection(nsDependentCString(longestName));
+ bool deleted = false;
+ if( folderInSubfolderList ) // for performance
+ {
+ nsTArray<char*> *pDeletableChildren = m_deletableChildren;
+ m_deletableChildren = nullptr;
+ bool folderDeleted = true;
+ deleted = DeleteSubFolders(longestName, folderDeleted);
+ // longestName may have subfolder list including itself
+ if( !folderDeleted )
+ {
+ if (deleted)
+ deleted = DeleteMailboxRespectingSubscriptions(longestName);
+ if (deleted)
+ FolderDeleted(longestName);
+ }
+ m_deletableChildren = pDeletableChildren;
+ }
+ else
+ {
+ deleted = DeleteMailboxRespectingSubscriptions(longestName);
+ if (deleted)
+ FolderDeleted(longestName);
+ }
+ deleteSucceeded = deleted;
+ }
+ }
+ PR_FREEIF(longestName);
+ }
+
+ aDeleteSelf = folderDeleted; // feedback if myself is deleted
+ PR_Free(selectedMailboxDir);
+
+ delete m_deletableChildren;
+ m_deletableChildren = nullptr;
+ }
+ return deleteSucceeded;
+}
+
+void nsImapProtocol::FolderDeleted(const char *mailboxName)
+{
+ char onlineDelimiter = kOnlineHierarchySeparatorUnknown;
+ nsCString orphanedMailboxName;
+
+ if (mailboxName)
+ {
+ m_runningUrl->AllocateCanonicalPath(mailboxName, onlineDelimiter,
+ getter_Copies(orphanedMailboxName));
+ if (m_imapServerSink)
+ m_imapServerSink->OnlineFolderDelete(orphanedMailboxName);
+ }
+}
+
+void nsImapProtocol::FolderNotCreated(const char *folderName)
+{
+ if (folderName && m_imapServerSink)
+ m_imapServerSink->OnlineFolderCreateFailed(nsDependentCString(folderName));
+}
+
+void nsImapProtocol::FolderRenamed(const char *oldName,
+ const char *newName)
+{
+ char onlineDelimiter = kOnlineHierarchySeparatorUnknown;
+
+ if ((m_hierarchyNameState == kNoOperationInProgress) ||
+ (m_hierarchyNameState == kListingForInfoAndDiscovery))
+
+ {
+ nsCString canonicalOldName, canonicalNewName;
+ m_runningUrl->AllocateCanonicalPath(oldName,
+ onlineDelimiter,
+ getter_Copies(canonicalOldName));
+ m_runningUrl->AllocateCanonicalPath(newName,
+ onlineDelimiter,
+ getter_Copies(canonicalNewName));
+ AutoProxyReleaseMsgWindow msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ m_imapServerSink->OnlineFolderRename(msgWindow, canonicalOldName, canonicalNewName);
+ }
+}
+
+void nsImapProtocol::OnDeleteFolder(const char * sourceMailbox)
+{
+ // intelligently delete the folder
+ bool folderDeleted = true;
+ bool deleted = DeleteSubFolders(sourceMailbox, folderDeleted);
+ if( !folderDeleted )
+ {
+ if (deleted)
+ deleted = DeleteMailboxRespectingSubscriptions(sourceMailbox);
+ if (deleted)
+ FolderDeleted(sourceMailbox);
+ }
+}
+
+void nsImapProtocol::RemoveMsgsAndExpunge()
+{
+ uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages();
+ if (numberOfMessages)
+ {
+ // Remove all msgs and expunge the folder (ie, compact it).
+ Store(NS_LITERAL_CSTRING("1:*"), "+FLAGS.SILENT (\\Deleted)", false); // use sequence #'s
+ if (GetServerStateParser().LastCommandSuccessful())
+ Expunge();
+ }
+}
+
+void nsImapProtocol::DeleteFolderAndMsgs(const char * sourceMailbox)
+{
+ RemoveMsgsAndExpunge();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ // All msgs are deleted successfully - let's remove the folder itself.
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ OnDeleteFolder(sourceMailbox);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+}
+
+void nsImapProtocol::OnRenameFolder(const char * sourceMailbox)
+{
+ char *destinationMailbox = OnCreateServerDestinationFolderPathString();
+
+ if (destinationMailbox)
+ {
+ bool renamed = RenameHierarchyByHand(sourceMailbox, destinationMailbox);
+ if (renamed)
+ FolderRenamed(sourceMailbox, destinationMailbox);
+
+ PR_Free( destinationMailbox);
+ }
+ else
+ HandleMemoryFailure();
+}
+
+void nsImapProtocol::OnMoveFolderHierarchy(const char * sourceMailbox)
+{
+ char *destinationMailbox = OnCreateServerDestinationFolderPathString();
+
+ if (destinationMailbox)
+ {
+ nsCString newBoxName;
+ newBoxName.Adopt(destinationMailbox);
+
+ char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
+ m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
+
+ nsCString oldBoxName(sourceMailbox);
+ int32_t leafStart = oldBoxName.RFindChar(onlineDirSeparator);
+ nsCString leafName;
+
+ if (-1 == leafStart)
+ leafName = oldBoxName; // this is a root level box
+ else
+ leafName = Substring(oldBoxName, leafStart+1);
+
+ if ( !newBoxName.IsEmpty() )
+ newBoxName.Append(onlineDirSeparator);
+ newBoxName.Append(leafName);
+ bool renamed = RenameHierarchyByHand(sourceMailbox,
+ newBoxName.get());
+ if (renamed)
+ FolderRenamed(sourceMailbox, newBoxName.get());
+ }
+ else
+ HandleMemoryFailure();
+}
+
+void nsImapProtocol::FindMailboxesIfNecessary()
+{
+ // biff should not discover mailboxes
+ bool foundMailboxesAlready = false;
+ nsImapAction imapAction;
+
+ // need to do this for every connection in order to see folders.
+ (void) m_runningUrl->GetImapAction(&imapAction);
+ nsresult rv = m_hostSessionList->GetHaveWeEverDiscoveredFoldersForHost(GetImapServerKey(), foundMailboxesAlready);
+ if (NS_SUCCEEDED(rv) && !foundMailboxesAlready &&
+ (imapAction != nsIImapUrl::nsImapBiff) &&
+ (imapAction != nsIImapUrl::nsImapVerifylogon) &&
+ (imapAction != nsIImapUrl::nsImapDiscoverAllBoxesUrl) &&
+ (imapAction != nsIImapUrl::nsImapUpgradeToSubscription) &&
+ !GetSubscribingNow())
+ DiscoverMailboxList();
+}
+
+void nsImapProtocol::DiscoverAllAndSubscribedBoxes()
+{
+ // used for subscribe pane
+ // iterate through all namespaces
+ uint32_t count = 0;
+ m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count);
+
+ for (uint32_t i = 0; i < count; i++ )
+ {
+ nsIMAPNamespace *ns = nullptr;
+
+ m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(), i,
+ ns);
+ if (ns &&
+ gHideOtherUsersFromList ? (ns->GetType() != kOtherUsersNamespace)
+ : true)
+ {
+ const char *prefix = ns->GetPrefix();
+ if (prefix)
+ {
+ nsAutoCString inboxNameWithDelim("INBOX");
+ inboxNameWithDelim.Append(ns->GetDelimiter());
+
+ if (!gHideUnusedNamespaces && *prefix &&
+ PL_strcasecmp(prefix, inboxNameWithDelim.get())) /* only do it for
+ non-empty namespace prefixes */
+ {
+ // Explicitly discover each Namespace, just so they're
+ // there in the subscribe UI
+ nsImapMailboxSpec *boxSpec = new nsImapMailboxSpec;
+ if (boxSpec)
+ {
+ NS_ADDREF(boxSpec);
+ boxSpec->mFolderSelected = false;
+ boxSpec->mHostName.Assign(GetImapHostName());
+ boxSpec->mConnection = this;
+ boxSpec->mFlagState = nullptr;
+ boxSpec->mDiscoveredFromLsub = true;
+ boxSpec->mOnlineVerified = true;
+ boxSpec->mBoxFlags = kNoselect;
+ boxSpec->mHierarchySeparator = ns->GetDelimiter();
+
+ m_runningUrl->AllocateCanonicalPath(ns->GetPrefix(), ns->GetDelimiter(),
+ getter_Copies(boxSpec->mAllocatedPathName));
+ boxSpec->mNamespaceForFolder = ns;
+ boxSpec->mBoxFlags |= kNameSpace;
+
+ switch (ns->GetType())
+ {
+ case kPersonalNamespace:
+ boxSpec->mBoxFlags |= kPersonalMailbox;
+ break;
+ case kPublicNamespace:
+ boxSpec->mBoxFlags |= kPublicMailbox;
+ break;
+ case kOtherUsersNamespace:
+ boxSpec->mBoxFlags |= kOtherUsersMailbox;
+ break;
+ default: // (kUnknownNamespace)
+ break;
+ }
+
+ DiscoverMailboxSpec(boxSpec);
+ }
+ else
+ HandleMemoryFailure();
+ }
+
+ nsAutoCString allPattern(prefix);
+ allPattern += '*';
+
+ nsAutoCString topLevelPattern(prefix);
+ topLevelPattern += '%';
+
+ nsAutoCString secondLevelPattern;
+
+ char delimiter = ns->GetDelimiter();
+ if (delimiter)
+ {
+ // Hierarchy delimiter might be NIL, in which case there's no hierarchy anyway
+ secondLevelPattern = prefix;
+ secondLevelPattern += '%';
+ secondLevelPattern += delimiter;
+ secondLevelPattern += '%';
+ }
+
+ if (!m_imapServerSink) return;
+
+ if (!allPattern.IsEmpty())
+ {
+ m_imapServerSink->SetServerDoingLsub(true);
+ Lsub(allPattern.get(), true); // LSUB all the subscribed
+ }
+ if (!topLevelPattern.IsEmpty())
+ {
+ m_imapServerSink->SetServerDoingLsub(false);
+ List(topLevelPattern.get(), true); // LIST the top level
+ }
+ if (!secondLevelPattern.IsEmpty())
+ {
+ m_imapServerSink->SetServerDoingLsub(false);
+ List(secondLevelPattern.get(), true); // LIST the second level
+ }
+ }
+ }
+ }
+}
+
+// DiscoverMailboxList() is used to actually do the discovery of folders
+// for a host. This is used both when we initially start up (and re-sync)
+// and also when the user manually requests a re-sync, by collapsing and
+// expanding a host in the folder pane. This is not used for the subscribe
+// pane.
+// DiscoverMailboxList() also gets the ACLs for each newly discovered folder
+void nsImapProtocol::DiscoverMailboxList()
+{
+ bool usingSubscription = false;
+
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(), usingSubscription);
+ // Pretend that the Trash folder doesn't exist, so we will rediscover it if we need to.
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(), false);
+
+ // should we check a pref here, to be able to turn off XList?
+ bool hasXLIST = GetServerStateParser().GetCapabilityFlag() & kHasXListCapability;
+ if (hasXLIST && usingSubscription)
+ {
+ m_hierarchyNameState = kXListing;
+ nsAutoCString pattern("%");
+ List("%", true, true);
+ // We list the first and second levels since special folders are unlikely
+ // to be more than 2 levels deep.
+ char separator = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&separator);
+ pattern.Append(separator);
+ pattern += '%';
+ List(pattern.get(), true, true);
+ }
+
+ SetMailboxDiscoveryStatus(eContinue);
+ if (GetServerStateParser().ServerHasACLCapability())
+ m_hierarchyNameState = kListingForInfoAndDiscovery;
+ else
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ // iterate through all namespaces and LSUB them.
+ uint32_t count = 0;
+ m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count);
+ for (uint32_t i = 0; i < count; i++ )
+ {
+ nsIMAPNamespace * ns = nullptr;
+ m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(),i,ns);
+ if (ns)
+ {
+ const char *prefix = ns->GetPrefix();
+ if (prefix)
+ {
+ nsAutoCString inboxNameWithDelim("INBOX");
+ inboxNameWithDelim.Append(ns->GetDelimiter());
+
+ // static bool gHideUnusedNamespaces = true;
+ // mscott -> WARNING!!! i where are we going to get this
+ // global variable for unused name spaces from???
+ // dmb - we should get this from a per-host preference,
+ // I'd say. But for now, just make it true;
+ if (!gHideUnusedNamespaces && *prefix &&
+ PL_strcasecmp(prefix, inboxNameWithDelim.get())) // only do it for
+ // non-empty namespace prefixes, and for non-INBOX prefix
+ {
+ // Explicitly discover each Namespace, so that we can
+ // create subfolders of them,
+ nsImapMailboxSpec *boxSpec = new nsImapMailboxSpec;
+ if (boxSpec)
+ {
+ NS_ADDREF(boxSpec);
+ boxSpec->mFolderSelected = false;
+ boxSpec->mHostName = GetImapHostName();
+ boxSpec->mConnection = this;
+ boxSpec->mFlagState = nullptr;
+ boxSpec->mDiscoveredFromLsub = true;
+ boxSpec->mOnlineVerified = true;
+ boxSpec->mBoxFlags = kNoselect;
+ boxSpec->mHierarchySeparator = ns->GetDelimiter();
+ // Until |AllocateCanonicalPath()| gets updated:
+ m_runningUrl->AllocateCanonicalPath(
+ ns->GetPrefix(), ns->GetDelimiter(),
+ getter_Copies(boxSpec->mAllocatedPathName));
+ boxSpec->mNamespaceForFolder = ns;
+ boxSpec->mBoxFlags |= kNameSpace;
+
+ switch (ns->GetType())
+ {
+ case kPersonalNamespace:
+ boxSpec->mBoxFlags |= kPersonalMailbox;
+ break;
+ case kPublicNamespace:
+ boxSpec->mBoxFlags |= kPublicMailbox;
+ break;
+ case kOtherUsersNamespace:
+ boxSpec->mBoxFlags |= kOtherUsersMailbox;
+ break;
+ default: // (kUnknownNamespace)
+ break;
+ }
+
+ DiscoverMailboxSpec(boxSpec);
+ }
+ else
+ HandleMemoryFailure();
+ }
+
+ // now do the folders within this namespace
+ nsCString pattern;
+ nsCString pattern2;
+ if (usingSubscription)
+ {
+ pattern.Append(prefix);
+ pattern.Append("*");
+ }
+ else
+ {
+ pattern.Append(prefix);
+ pattern.Append("%"); // mscott just need one percent right?
+ // pattern = PR_smprintf("%s%%", prefix);
+ char delimiter = ns->GetDelimiter();
+ if (delimiter)
+ {
+ // delimiter might be NIL, in which case there's no hierarchy anyway
+ pattern2 = prefix;
+ pattern2 += "%";
+ pattern2 += delimiter;
+ pattern2 += "%";
+ // pattern2 = PR_smprintf("%s%%%c%%", prefix, delimiter);
+ }
+ }
+ // Note: It is important to make sure we are respecting the server_sub_directory
+ // preference when calling List and Lsub (2nd arg = true), otherwise
+ // we end up with performance issues or even crashes when connecting to
+ // servers that expose the users entire home directory (like UW-IMAP).
+ if (usingSubscription) { // && !GetSubscribingNow()) should never get here from subscribe pane
+ if (GetServerStateParser().GetCapabilityFlag() & kHasListExtendedCapability)
+ Lsub(pattern.get(), true); // do LIST (SUBSCRIBED)
+ else {
+ // store mailbox flags from LIST
+ EMailboxHierarchyNameState currentState = m_hierarchyNameState;
+ m_hierarchyNameState = kListingForFolderFlags;
+ List(pattern.get(), true);
+ m_hierarchyNameState = currentState;
+ // then do LSUB using stored flags
+ Lsub(pattern.get(), true);
+ m_standardListMailboxes.Clear();
+ }
+ }
+ else
+ {
+ List(pattern.get(), true, hasXLIST);
+ List(pattern2.get(), true, hasXLIST);
+ }
+ }
+ }
+ }
+
+ // explicitly LIST the INBOX if (a) we're not using subscription, or (b) we are using subscription and
+ // the user wants us to always show the INBOX.
+ bool listInboxForHost = false;
+ m_hostSessionList->GetShouldAlwaysListInboxForHost(GetImapServerKey(), listInboxForHost);
+ if (!usingSubscription || listInboxForHost)
+ List("INBOX", true);
+
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ MailboxDiscoveryFinished();
+
+ // Get the ACLs for newly discovered folders
+ if (GetServerStateParser().ServerHasACLCapability())
+ {
+ int32_t total = m_listedMailboxList.Length(), cnt = 0;
+ // Let's not turn this off here, since we don't turn it on after
+ // GetServerStateParser().SetReportingErrors(false);
+ if (total)
+ {
+ ProgressEventFunctionUsingName("imapGettingACLForFolder");
+ nsIMAPMailboxInfo * mb = nullptr;
+ do
+ {
+ if (m_listedMailboxList.Length() == 0)
+ break;
+
+ mb = m_listedMailboxList[0]; // get top element
+ m_listedMailboxList.RemoveElementAt(0); // XP_ListRemoveTopObject(fListedMailboxList);
+ if (mb)
+ {
+ if (FolderNeedsACLInitialized(PromiseFlatCString(mb->GetMailboxName()).get()))
+ {
+ char *onlineName = nullptr;
+ m_runningUrl->AllocateServerPath(PromiseFlatCString(mb->GetMailboxName()).get(),
+ mb->GetDelimiter(), &onlineName);
+ if (onlineName)
+ {
+ RefreshACLForFolder(onlineName);
+ PR_Free(onlineName);
+ }
+ }
+ PercentProgressUpdateEvent(NULL, cnt, total);
+ delete mb; // this is the last time we're using the list, so delete the entries here
+ cnt++;
+ }
+ } while (mb && !DeathSignalReceived());
+ }
+ }
+}
+
+bool nsImapProtocol::FolderNeedsACLInitialized(const char *folderName)
+{
+ bool rv = false;
+ m_imapServerSink->FolderNeedsACLInitialized(nsDependentCString(folderName), &rv);
+ return rv;
+}
+
+void nsImapProtocol::MailboxDiscoveryFinished()
+{
+ if (!DeathSignalReceived() && !GetSubscribingNow() &&
+ ((m_hierarchyNameState == kNoOperationInProgress) ||
+ (m_hierarchyNameState == kListingForInfoAndDiscovery)))
+ {
+ nsIMAPNamespace *ns = nullptr;
+ m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(), kPersonalNamespace, ns);
+ const char *personalDir = ns ? ns->GetPrefix() : 0;
+
+ bool trashFolderExists = false;
+ bool usingSubscription = false;
+ m_hostSessionList->GetOnlineTrashFolderExistsForHost(GetImapServerKey(), trashFolderExists);
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),usingSubscription);
+ if (!trashFolderExists && GetDeleteIsMoveToTrash() && usingSubscription)
+ {
+ // maybe we're not subscribed to the Trash folder
+ if (personalDir)
+ {
+ nsCString originalTrashName(CreatePossibleTrashName(personalDir));
+ m_hierarchyNameState = kDiscoverTrashFolderInProgress;
+ List(originalTrashName.get(), true);
+ m_hierarchyNameState = kNoOperationInProgress;
+ }
+ }
+
+ // There is no Trash folder (either LIST'd or LSUB'd), and we're using the
+ // Delete-is-move-to-Trash model, and there is a personal namespace
+ if (!trashFolderExists && GetDeleteIsMoveToTrash() && ns)
+ {
+ nsCString trashName(CreatePossibleTrashName(ns->GetPrefix()));
+ nsCString onlineTrashName;
+ m_runningUrl->AllocateServerPath(trashName.get(), ns->GetDelimiter(),
+ getter_Copies(onlineTrashName));
+
+ GetServerStateParser().SetReportingErrors(false);
+ bool created = CreateMailboxRespectingSubscriptions(onlineTrashName.get());
+ GetServerStateParser().SetReportingErrors(true);
+
+ // force discovery of new trash folder.
+ if (created)
+ {
+ m_hierarchyNameState = kDiscoverTrashFolderInProgress;
+ List(onlineTrashName.get(), false);
+ m_hierarchyNameState = kNoOperationInProgress;
+ }
+ else
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(), true);
+ } //if trash folder doesn't exist
+ m_hostSessionList->SetHaveWeEverDiscoveredFoldersForHost(GetImapServerKey(), true);
+
+ // notify front end that folder discovery is complete....
+ if (m_imapServerSink)
+ m_imapServerSink->DiscoveryDone();
+ }
+}
+
+// returns the mailboxName with the IMAP delimiter removed from the tail end
+void nsImapProtocol::RemoveHierarchyDelimiter(nsCString &mailboxName)
+{
+ char onlineDelimiter[2] = {0, 0};
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter[0]);
+ // take the hierarchy delimiter off the end, if any.
+ if (onlineDelimiter[0])
+ mailboxName.Trim(onlineDelimiter, false, true);
+}
+
+// returns true is the create succeeded (regardless of subscription changes)
+bool nsImapProtocol::CreateMailboxRespectingSubscriptions(const char *mailboxName)
+{
+ CreateMailbox(mailboxName);
+ bool rv = GetServerStateParser().LastCommandSuccessful();
+ if (rv && m_autoSubscribe) // auto-subscribe is on
+ {
+ // create succeeded - let's subscribe to it
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ nsCString mailboxWODelim(mailboxName);
+ RemoveHierarchyDelimiter(mailboxWODelim);
+ OnSubscribe(mailboxWODelim.get());
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+ return rv;
+}
+
+void nsImapProtocol::CreateMailbox(const char *mailboxName)
+{
+ ProgressEventFunctionUsingName("imapStatusCreatingMailbox");
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsCString command(GetServerCommandTag());
+ command += " create \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if(NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+ // If that failed, let's list the parent folder to see if
+ // it allows inferiors, so we won't try to create sub-folders
+ // of the parent folder again in the current session.
+ if (GetServerStateParser().CommandFailed())
+ {
+ // Figure out parent folder name.
+ nsCString parentName(mailboxName);
+ char hierarchyDelimiter;
+ m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
+ int32_t leafPos = parentName.RFindChar(hierarchyDelimiter);
+ if (leafPos > 0)
+ {
+ parentName.SetLength(leafPos);
+ List(parentName.get(), false);
+ // We still want the caller to know the create failed, so restore that.
+ GetServerStateParser().SetCommandFailed(true);
+ }
+ }
+}
+
+void nsImapProtocol::DeleteMailbox(const char *mailboxName)
+{
+
+ // check if this connection currently has the folder to be deleted selected.
+ // If so, we should close it because at least some UW servers don't like you deleting
+ // a folder you have open.
+ if (FolderIsSelected(mailboxName))
+ Close();
+
+
+ ProgressEventFunctionUsingNameWithString("imapStatusDeletingMailbox", mailboxName);
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsCString command(GetServerCommandTag());
+ command += " delete \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::RenameMailbox(const char *existingName,
+ const char *newName)
+{
+ // just like DeleteMailbox; Some UW servers don't like it.
+ if (FolderIsSelected(existingName))
+ Close();
+
+ ProgressEventFunctionUsingNameWithString("imapStatusRenamingMailbox", existingName);
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedExistingName;
+ nsCString escapedNewName;
+ CreateEscapedMailboxName(existingName, escapedExistingName);
+ CreateEscapedMailboxName(newName, escapedNewName);
+ nsCString command(GetServerCommandTag());
+ command += " rename \"";
+ command += escapedExistingName;
+ command += "\" \"";
+ command += escapedNewName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+nsCString nsImapProtocol::CreatePossibleTrashName(const char *prefix)
+{
+ nsCString returnTrash(prefix);
+ returnTrash += m_trashFolderName;
+ return returnTrash;
+}
+
+bool nsImapProtocol::GetListSubscribedIsBrokenOnServer()
+{
+ // This is a workaround for an issue with LIST(SUBSCRIBED) crashing older versions of Zimbra
+ if (GetServerStateParser().GetServerID().Find("\"NAME\" \"Zimbra\"", CaseInsensitiveCompare) != kNotFound) {
+ nsCString serverID(GetServerStateParser().GetServerID());
+ int start = serverID.Find("\"VERSION\" \"", CaseInsensitiveCompare) + 11;
+ int length = serverID.Find("\" ", start, CaseInsensitiveCompare);
+ const nsDependentCSubstring serverVersionSubstring = Substring(serverID, start, length);
+ nsCString serverVersionStr(serverVersionSubstring);
+ Version serverVersion(serverVersionStr.get());
+ Version sevenTwoThree("7.2.3_");
+ Version eightZeroZero("8.0.0_");
+ Version eightZeroThree("8.0.3_");
+ if ((serverVersion < sevenTwoThree) ||
+ ((serverVersion >= eightZeroZero) && (serverVersion < eightZeroThree)))
+ return true;
+ }
+ return false;
+}
+
+// This identifies servers that require an extra imap SELECT to detect new
+// email in a mailbox. Servers requiring this are found by comparing their
+// ID string, returned with imap ID command, to strings entered in
+// mail.imap.force_select_detect. Only openwave servers used by
+// Charter/Spectrum ISP returning an ID containing the strings ""name" "Email Mx""
+// and ""vendor" "Openwave Messaging"" are now known to have this issue. The
+// compared strings can be modified with the config editor if necessary
+// (e.g., a "version" substring could be added). Also, additional servers
+// having a different set of strings can be added if ever needed.
+// The mail.imap.force_select_detect uses semicolon delimiter between
+// servers and within a server substrings to compare are comma delimited.
+// This example force_select_detect value shows how two servers types
+// could be detected:
+// "name" "Email Mx","vendor" "Openwave Messaging";"vendor" "Yahoo! Inc.","name" "Y!IMAP";
+bool nsImapProtocol::IsExtraSelectNeeded()
+{
+ bool retVal;
+ for (uint32_t i = 0; i < gForceSelectServersArray.Length(); i++)
+ {
+ retVal = true;
+ nsTArray<nsCString> forceSelectStringsArray;
+ ParseString(gForceSelectServersArray[i], ',', forceSelectStringsArray);
+ for (uint32_t j = 0; j < forceSelectStringsArray.Length(); j++)
+ {
+ // Each substring within the server string must be contained in ID string.
+ // First un-escape any comma (%2c) or semicolon (%3b) within the substring.
+ nsAutoCString unescapedString;
+ MsgUnescapeString(forceSelectStringsArray[j], 0, unescapedString);
+ if (GetServerStateParser().GetServerID()
+ .Find(unescapedString, CaseInsensitiveCompare) == kNotFound)
+ {
+ retVal = false;
+ break;
+ }
+ }
+ // Matches found for all substrings for the server.
+ if (retVal)
+ return true;
+ }
+
+ // If reached, no substrings match for any server.
+ return false;
+}
+
+void nsImapProtocol::Lsub(const char *mailboxPattern, bool addDirectoryIfNecessary)
+{
+ ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
+
+ IncrementCommandTagNumber();
+
+ char *boxnameWithOnlineDirectory = nullptr;
+ if (addDirectoryIfNecessary)
+ m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern, &boxnameWithOnlineDirectory);
+
+ nsCString escapedPattern;
+ CreateEscapedMailboxName(boxnameWithOnlineDirectory ?
+ boxnameWithOnlineDirectory :
+ mailboxPattern, escapedPattern);
+
+ nsCString command (GetServerCommandTag());
+ eIMAPCapabilityFlags flag = GetServerStateParser().GetCapabilityFlag();
+ bool useListSubscribed = (flag & kHasListExtendedCapability) &&
+ !GetListSubscribedIsBrokenOnServer();
+ if (useListSubscribed)
+ command += " list (subscribed)";
+ else
+ command += " lsub";
+ command += " \"\" \"";
+ command += escapedPattern;
+ if (useListSubscribed && (flag & kHasSpecialUseCapability))
+ command += "\" return (special-use)" CRLF;
+ else
+ command += "\"" CRLF;
+
+ PR_Free(boxnameWithOnlineDirectory);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(command.get(), true);
+}
+
+void nsImapProtocol::List(const char *mailboxPattern, bool addDirectoryIfNecessary,
+ bool useXLIST)
+{
+ ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
+
+ IncrementCommandTagNumber();
+
+ char *boxnameWithOnlineDirectory = nullptr;
+ if (addDirectoryIfNecessary)
+ m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern, &boxnameWithOnlineDirectory);
+
+ nsCString escapedPattern;
+ CreateEscapedMailboxName(boxnameWithOnlineDirectory ?
+ boxnameWithOnlineDirectory :
+ mailboxPattern, escapedPattern);
+
+ nsCString command (GetServerCommandTag());
+ command += useXLIST ?
+ " xlist \"\" \"" : " list \"\" \"";
+ command += escapedPattern;
+ command += "\"" CRLF;
+
+ PR_Free(boxnameWithOnlineDirectory);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(command.get(), true);
+}
+
+void nsImapProtocol::Subscribe(const char *mailboxName)
+{
+ ProgressEventFunctionUsingNameWithString("imapStatusSubscribeToMailbox", mailboxName);
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ nsCString command (GetServerCommandTag());
+ command += " subscribe \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Unsubscribe(const char *mailboxName)
+{
+ ProgressEventFunctionUsingNameWithString("imapStatusUnsubscribeMailbox", mailboxName);
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ nsCString command (GetServerCommandTag());
+ command += " unsubscribe \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Idle()
+{
+ IncrementCommandTagNumber();
+
+ if (m_urlInProgress)
+ return;
+ nsAutoCString command (GetServerCommandTag());
+ command += " IDLE" CRLF;
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ {
+ // we'll just get back a continuation char at first.
+ // + idling...
+ ParseIMAPandCheckForNewMail();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ m_idle = true;
+ // this will cause us to get notified of data or the socket getting closed.
+ // That notification will occur on the socket transport thread - we just
+ // need to poke a monitor so the imap thread will do a blocking read
+ // and parse the data.
+ nsCOMPtr <nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_inputStream);
+ if (asyncInputStream)
+ asyncInputStream->AsyncWait(this, 0, 0, nullptr);
+ }
+ else
+ {
+ m_idle = false;
+ }
+ }
+}
+
+// until we can fix the hang on shutdown waiting for server
+// responses, we need to not wait for the server response
+// on shutdown.
+void nsImapProtocol::EndIdle(bool waitForResponse /* = true */)
+{
+ // clear the async wait - otherwise, we seem to have trouble doing a blocking read
+ nsCOMPtr <nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_inputStream);
+ if (asyncInputStream)
+ asyncInputStream->AsyncWait(nullptr, 0, 0, nullptr);
+ nsresult rv = SendData("DONE" CRLF);
+ // set a short timeout if we don't want to wait for a response
+ if (m_transport && !waitForResponse)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
+ if (NS_SUCCEEDED(rv))
+ {
+ m_idle = false;
+ ParseIMAPandCheckForNewMail();
+ }
+ m_imapMailFolderSink = nullptr;
+}
+
+
+void nsImapProtocol::Search(const char * searchCriteria,
+ bool useUID,
+ bool notifyHit /* true */)
+{
+ m_notifySearchHit = notifyHit;
+ ProgressEventFunctionUsingName("imapStatusSearchMailbox");
+ IncrementCommandTagNumber();
+
+ nsCString protocolString(GetServerCommandTag());
+ // the searchCriteria string contains the 'search ....' string
+ if (useUID)
+ protocolString.Append(" uid");
+ protocolString.Append(" ");
+ protocolString.Append(searchCriteria);
+ // the search criteria can contain string literals, which means we
+ // need to break up the protocol string by CRLF's, and after sending CRLF,
+ // wait for the server to respond OK before sending more data
+ nsresult rv;
+ int32_t crlfIndex;
+ while (crlfIndex = protocolString.Find(CRLF), crlfIndex != kNotFound && !DeathSignalReceived())
+ {
+ nsAutoCString tempProtocolString;
+ tempProtocolString = StringHead(protocolString, crlfIndex + 2);
+ rv = SendData(tempProtocolString.get());
+ if (NS_FAILED(rv))
+ return;
+ ParseIMAPandCheckForNewMail();
+ protocolString.Cut(0, crlfIndex + 2);
+ }
+ protocolString.Append(CRLF);
+
+ rv = SendData(protocolString.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Copy(const char * messageList,
+ const char *destinationMailbox,
+ bool idsAreUid)
+{
+ IncrementCommandTagNumber();
+
+ nsCString escapedDestination;
+ CreateEscapedMailboxName(destinationMailbox, escapedDestination);
+
+ // turn messageList back into key array and then back into a message id list,
+ // but use the flag state to handle ranges correctly.
+ nsCString messageIdList;
+ nsTArray<nsMsgKey> msgKeys;
+ if (idsAreUid)
+ ParseUidString(messageList, msgKeys);
+
+ int32_t msgCountLeft = msgKeys.Length();
+ uint32_t msgsHandled = 0;
+
+ do
+ {
+ nsCString idString;
+
+ uint32_t msgsToHandle = msgCountLeft;
+ if (idsAreUid)
+ AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle, m_flagState, idString);
+ else
+ idString.Assign(messageList);
+
+ msgsHandled += msgsToHandle;
+ msgCountLeft -= msgsToHandle;
+
+ IncrementCommandTagNumber();
+ nsAutoCString protocolString(GetServerCommandTag());
+ if (idsAreUid)
+ protocolString.Append(" uid");
+ // If it's a MOVE operation on aol servers then use 'xaol-move' cmd.
+ if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ GetServerStateParser().ServerIsAOLServer())
+ protocolString.Append(" xaol-move ");
+ else if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ GetServerStateParser().GetCapabilityFlag() & kHasMoveCapability)
+ protocolString.Append(" move ");
+ else
+ protocolString.Append(" copy ");
+
+
+ protocolString.Append(idString);
+ protocolString.Append(" \"");
+ protocolString.Append(escapedDestination);
+ protocolString.Append("\"" CRLF);
+
+ nsresult rv = SendData(protocolString.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(protocolString.get());
+ }
+ while (msgCountLeft > 0 && !DeathSignalReceived());
+}
+
+void nsImapProtocol::NthLevelChildList(const char* onlineMailboxPrefix,
+ int32_t depth)
+{
+ NS_ASSERTION (depth >= 0,
+ "Oops ... depth must be equal or greater than 0");
+ if (depth < 0) return;
+
+ nsCString truncatedPrefix (onlineMailboxPrefix);
+ char16_t slash = '/';
+ if (truncatedPrefix.Last() == slash)
+ truncatedPrefix.SetLength(truncatedPrefix.Length()-1);
+
+ nsAutoCString pattern(truncatedPrefix);
+ nsAutoCString suffix;
+ int count = 0;
+ char separator = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&separator);
+ suffix.Assign(separator);
+ suffix += '%';
+
+ while (count < depth)
+ {
+ pattern += suffix;
+ count++;
+ List(pattern.get(), false);
+ }
+}
+
+void nsImapProtocol::ProcessAuthenticatedStateURL()
+{
+ nsImapAction imapAction;
+ char * sourceMailbox = nullptr;
+ m_runningUrl->GetImapAction(&imapAction);
+
+ // switch off of the imap url action and take an appropriate action
+ switch (imapAction)
+ {
+ case nsIImapUrl::nsImapLsubFolders:
+ OnLSubFolders();
+ break;
+ case nsIImapUrl::nsImapAppendMsgFromFile:
+ OnAppendMsgFromFile();
+ break;
+ case nsIImapUrl::nsImapDiscoverAllBoxesUrl:
+ NS_ASSERTION (!GetSubscribingNow(),
+ "Oops ... should not get here from subscribe UI");
+ DiscoverMailboxList();
+ break;
+ case nsIImapUrl::nsImapDiscoverAllAndSubscribedBoxesUrl:
+ DiscoverAllAndSubscribedBoxes();
+ break;
+ case nsIImapUrl::nsImapCreateFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnCreateFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapEnsureExistsFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnEnsureExistsFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapDiscoverChildrenUrl:
+ {
+ char *canonicalParent = nullptr;
+ m_runningUrl->CreateServerSourceFolderPathString(&canonicalParent);
+ if (canonicalParent)
+ {
+ NthLevelChildList(canonicalParent, 2);
+ PR_Free(canonicalParent);
+ }
+ break;
+ }
+ case nsIImapUrl::nsImapSubscribe:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnSubscribe(sourceMailbox); // used to be called subscribe
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ bool shouldList;
+ // if url is an external click url, then we should list the folder
+ // after subscribing to it, so we can select it.
+ m_runningUrl->GetExternalLinkUrl(&shouldList);
+ if (shouldList)
+ OnListFolder(sourceMailbox, true);
+ }
+ break;
+ case nsIImapUrl::nsImapUnsubscribe:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnUnsubscribe(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRefreshACL:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ RefreshACLForFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRefreshAllACLs:
+ OnRefreshAllACLs();
+ break;
+ case nsIImapUrl::nsImapListFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnListFolder(sourceMailbox, false);
+ break;
+ case nsIImapUrl::nsImapFolderStatus:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnStatusForFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRefreshFolderUrls:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ XMailboxInfo(sourceMailbox);
+ if (GetServerStateParser().LastCommandSuccessful())
+ SetFolderAdminUrl(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapDeleteFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnDeleteFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRenameFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnRenameFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapMoveFolderHierarchy:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnMoveFolderHierarchy(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapVerifylogon:
+ break;
+ default:
+ break;
+ }
+ PR_Free(sourceMailbox);
+}
+
+void nsImapProtocol::ProcessAfterAuthenticated()
+{
+ // if we're a netscape server, and we haven't got the admin url, get it
+ bool hasAdminUrl = true;
+
+ if (NS_SUCCEEDED(m_hostSessionList->GetHostHasAdminURL(GetImapServerKey(), hasAdminUrl))
+ && !hasAdminUrl)
+ {
+ if (GetServerStateParser().ServerHasServerInfo())
+ {
+ XServerInfo();
+ if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink)
+ {
+ m_imapServerSink->SetMailServerUrls(GetServerStateParser().GetMailAccountUrl(),
+ GetServerStateParser().GetManageListsUrl(),
+ GetServerStateParser().GetManageFiltersUrl());
+ // we've tried to ask for it, so don't try again this session.
+ m_hostSessionList->SetHostHasAdminURL(GetImapServerKey(), true);
+ }
+ }
+ else if (GetServerStateParser().ServerIsNetscape3xServer())
+ {
+ Netscape();
+ if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink)
+ m_imapServerSink->SetMailServerUrls(GetServerStateParser().GetMailAccountUrl(),
+ EmptyCString(), EmptyCString());
+ }
+ }
+
+ if (GetServerStateParser().ServerHasNamespaceCapability())
+ {
+ bool nameSpacesOverridable = false;
+ bool haveNameSpacesForHost = false;
+ m_hostSessionList->GetNamespacesOverridableForHost(GetImapServerKey(), nameSpacesOverridable);
+ m_hostSessionList->GetGotNamespacesForHost(GetImapServerKey(), haveNameSpacesForHost);
+
+ // mscott: VERIFY THIS CLAUSE!!!!!!!
+ if (nameSpacesOverridable && !haveNameSpacesForHost)
+ Namespace();
+ }
+
+ // If the server supports compression, turn it on now.
+ // Choosing this spot (after login has finished) because
+ // many proxies (e.g. perdition, nginx) talk IMAP to the
+ // client until login is finished, then hand off to the
+ // backend. If we enable compression early the proxy
+ // will be confused.
+ if (UseCompressDeflate())
+ StartCompressDeflate();
+
+ if ((GetServerStateParser().GetCapabilityFlag() & kHasEnableCapability) &&
+ UseCondStore())
+ EnableCondStore();
+
+ bool haveIdResponse = false;
+ if ((GetServerStateParser().GetCapabilityFlag() & kHasIDCapability) &&
+ m_sendID)
+ {
+ ID();
+ if (m_imapServerSink && !GetServerStateParser().GetServerID().IsEmpty())
+ {
+ haveIdResponse = true;
+ // Determine value for m_forceSelect based on config editor
+ // entries and comparison to imap ID string returned by the server.
+ m_imapServerSink->SetServerID(GetServerStateParser().GetServerID());
+ switch (m_forceSelectValue.get()[0])
+ {
+ // Yes: Set to always force even if imap server doesn't need it.
+ case 'y':
+ case 'Y':
+ m_forceSelect = true;
+ break;
+
+ // No: Set to never force a select for this imap server.
+ case 'n':
+ case 'N':
+ m_forceSelect = false;
+ break;
+
+ // Auto: Set to force only if imap server requires it.
+ default:
+ nsAutoCString statusString;
+ m_forceSelect = IsExtraSelectNeeded();
+ // Setting to "yes-auto" or "no-auto" avoids doing redundant calls to
+ // IsExtraSelectNeeded() on subsequent ID() occurrences. It also
+ // provides feedback to the user regarding the detection status.
+ if (m_forceSelect)
+ {
+ // Set preference value to "yes-auto".
+ statusString.Assign("yes-auto");
+ }
+ else
+ {
+ // Set preference value to "no-auto".
+ statusString.Assign("no-auto");
+ }
+ m_imapServerSink->SetServerForceSelect(statusString);
+ break;
+ }
+ }
+ }
+
+ // If no ID capability or empty ID response, user may still want to
+ // change "force select".
+ if (!haveIdResponse)
+ {
+ switch (m_forceSelectValue.get()[0])
+ {
+ case 'a':
+ {
+ // If default "auto", set to "no-auto" so visible in config editor
+ // and set/keep m_forceSelect false.
+ nsAutoCString statusString;
+ statusString.Assign("no-auto");
+ m_imapServerSink->SetServerForceSelect(statusString);
+ m_forceSelect = false;
+ }
+ break;
+ case 'y':
+ case 'Y':
+ m_forceSelect = true;
+ break;
+ default:
+ m_forceSelect = false;
+ }
+ }
+}
+
+void nsImapProtocol::SetupMessageFlagsString(nsCString& flagString,
+ imapMessageFlagsType flags,
+ uint16_t userFlags)
+{
+ if (flags & kImapMsgSeenFlag)
+ flagString.Append("\\Seen ");
+ if (flags & kImapMsgAnsweredFlag)
+ flagString.Append("\\Answered ");
+ if (flags & kImapMsgFlaggedFlag)
+ flagString.Append("\\Flagged ");
+ if (flags & kImapMsgDeletedFlag)
+ flagString.Append("\\Deleted ");
+ if (flags & kImapMsgDraftFlag)
+ flagString.Append("\\Draft ");
+ if (flags & kImapMsgRecentFlag)
+ flagString.Append("\\Recent ");
+ if ((flags & kImapMsgForwardedFlag) &&
+ (userFlags & kImapMsgSupportForwardedFlag))
+ flagString.Append("$Forwarded "); // Not always available
+ if ((flags & kImapMsgMDNSentFlag) && (
+ userFlags & kImapMsgSupportMDNSentFlag))
+ flagString.Append("$MDNSent "); // Not always available
+
+ // eat the last space
+ if (!flagString.IsEmpty())
+ flagString.SetLength(flagString.Length()-1);
+}
+
+void nsImapProtocol::ProcessStoreFlags(const nsCString &messageIdsString,
+ bool idsAreUids,
+ imapMessageFlagsType flags,
+ bool addFlags)
+{
+ nsCString flagString;
+
+ uint16_t userFlags = GetServerStateParser().SupportsUserFlags();
+ uint16_t settableFlags = GetServerStateParser().SettablePermanentFlags();
+
+ if (!addFlags && (flags & userFlags) && !(flags & settableFlags))
+ {
+ if (m_runningUrl)
+ m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagsNotSettable);
+ return; // if cannot set any of the flags bail out
+ }
+
+ if (addFlags)
+ flagString = "+Flags (";
+ else
+ flagString = "-Flags (";
+
+ if (flags & kImapMsgSeenFlag && kImapMsgSeenFlag & settableFlags)
+ flagString .Append("\\Seen ");
+ if (flags & kImapMsgAnsweredFlag && kImapMsgAnsweredFlag & settableFlags)
+ flagString .Append("\\Answered ");
+ if (flags & kImapMsgFlaggedFlag && kImapMsgFlaggedFlag & settableFlags)
+ flagString .Append("\\Flagged ");
+ if (flags & kImapMsgDeletedFlag && kImapMsgDeletedFlag & settableFlags)
+ flagString .Append("\\Deleted ");
+ if (flags & kImapMsgDraftFlag && kImapMsgDraftFlag & settableFlags)
+ flagString .Append("\\Draft ");
+ if (flags & kImapMsgForwardedFlag && kImapMsgSupportForwardedFlag & userFlags)
+ flagString .Append("$Forwarded "); // if supported
+ if (flags & kImapMsgMDNSentFlag && kImapMsgSupportMDNSentFlag & userFlags)
+ flagString .Append("$MDNSent "); // if supported
+
+ if (flagString.Length() > 8) // if more than "+Flags ("
+ {
+ // replace the final space with ')'
+ flagString.SetCharAt(')',flagString.Length() - 1);
+
+ Store(messageIdsString, flagString.get(), idsAreUids);
+ if (m_runningUrl && idsAreUids)
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ nsTArray<nsMsgKey> msgKeys;
+ ParseUidString(messageIdString.get(), msgKeys);
+
+ int32_t msgCount = msgKeys.Length();
+ for (int32_t i = 0; i < msgCount; i++)
+ {
+ bool found;
+ imapMessageFlagsType resultFlags;
+ // check if the flags were added/removed, and if the uid really exists.
+ nsresult rv = GetFlagsForUID(msgKeys[i], &found, &resultFlags, nullptr);
+ if (NS_FAILED(rv) || !found ||
+ (addFlags && ((flags & resultFlags) != flags)) ||
+ (!addFlags && ((flags & resultFlags) != 0)))
+ {
+ m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagChangeFailed);
+ break;
+ }
+ }
+
+ }
+ }
+}
+
+
+void nsImapProtocol::Close(bool shuttingDown /* = false */,
+ bool waitForResponse /* = true */)
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" close" CRLF);
+
+ if (!shuttingDown)
+ ProgressEventFunctionUsingName("imapStatusCloseMailbox");
+
+ GetServerStateParser().ResetFlagInfo();
+
+ nsresult rv = SendData(command.get());
+ if (m_transport && shuttingDown)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
+
+ if (NS_SUCCEEDED(rv) && waitForResponse)
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::XAOL_Option(const char *option)
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" XAOL-OPTION ");
+ command.Append(option);
+ command.Append(CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Check()
+{
+ //ProgressUpdateEvent("Checking mailbox...");
+
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" check" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ {
+ m_flagChangeCount = 0;
+ m_lastCheckTime = PR_Now();
+ ParseIMAPandCheckForNewMail();
+ }
+}
+
+nsresult nsImapProtocol::GetMsgWindow(nsIMsgWindow **aMsgWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(m_runningUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!m_imapProtocolSink)
+ return NS_ERROR_FAILURE;
+ return m_imapProtocolSink->GetUrlWindow(mailnewsUrl, aMsgWindow);
+}
+
+/**
+ * Get password from RAM, disk (password manager) or user (dialog)
+ * @return NS_MSG_PASSWORD_PROMPT_CANCELLED
+ * (which is NS_SUCCEEDED!) when user cancelled
+ * NS_FAILED(rv) for other errors
+ */
+nsresult nsImapProtocol::GetPassword(nsCString &password,
+ bool newPasswordRequested)
+{
+ // we are in the imap thread so *NEVER* try to extract the password with UI
+ // if logon redirection has changed the password, use the cookie as the password
+ if (m_overRideUrlConnectionInfo)
+ {
+ password.Assign(m_logonCookie);
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(m_server, NS_ERROR_NULL_POINTER);
+ nsresult rv;
+
+ // Get the password already stored in mem
+ rv = m_imapServerSink->GetServerPassword(password);
+ if (NS_FAILED(rv) || password.IsEmpty())
+ {
+ AutoProxyReleaseMsgWindow msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ NS_ENSURE_TRUE(msgWindow, NS_ERROR_NOT_AVAILABLE); // biff case
+
+ // Get the password from pw manager (harddisk) or user (dialog)
+ m_passwordObtained = false;
+ rv = m_imapServerSink->AsyncGetPassword(this,
+ newPasswordRequested,
+ password);
+ if (password.IsEmpty())
+ {
+ PRIntervalTime sleepTime = kImapSleepTime;
+ m_passwordStatus = NS_OK;
+ ReentrantMonitorAutoEnter mon(m_passwordReadyMonitor);
+ while (!m_passwordObtained && !NS_FAILED(m_passwordStatus) &&
+ m_passwordStatus != NS_MSG_PASSWORD_PROMPT_CANCELLED &&
+ !DeathSignalReceived())
+ mon.Wait(sleepTime);
+ rv = m_passwordStatus;
+ password = m_password;
+ }
+ }
+ if (!password.IsEmpty())
+ m_lastPasswordSent = password;
+ return rv;
+}
+
+// This is called from the UI thread.
+NS_IMETHODIMP
+nsImapProtocol::OnPromptStart(bool *aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+
+ *aResult = false;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ nsCString password = m_lastPasswordSent;
+ rv = imapServer->PromptPassword(msgWindow, password);
+ m_password = password;
+ m_passwordStatus = rv;
+ if (!m_password.IsEmpty())
+ *aResult = true;
+
+ // Notify the imap thread that we have a password.
+ m_passwordObtained = true;
+ ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
+ passwordMon.Notify();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapProtocol::OnPromptAuthAvailable()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_passwordStatus = imapServer->GetPassword(m_password);
+ // Notify the imap thread that we have a password.
+ m_passwordObtained = true;
+ ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
+ passwordMon.Notify();
+ return m_passwordStatus;
+}
+
+NS_IMETHODIMP
+nsImapProtocol::OnPromptCanceled()
+{
+ // A prompt was cancelled, so notify the imap thread.
+ m_passwordStatus = NS_MSG_PASSWORD_PROMPT_CANCELLED;
+ ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
+ passwordMon.Notify();
+ return NS_OK;
+}
+
+bool nsImapProtocol::TryToLogon()
+{
+ MOZ_LOG(IMAP, LogLevel::Debug, ("try to log in"));
+ NS_ENSURE_TRUE(m_imapServerSink, false);
+ bool loginSucceeded = false;
+ bool skipLoop = false;
+ nsAutoCString password;
+ nsAutoCString userName;
+
+ nsresult rv = ChooseAuthMethod();
+ if (NS_FAILED(rv)) // all methods failed
+ {
+ // are there any matching login schemes at all?
+ if (!(GetServerStateParser().GetCapabilityFlag() & m_prefAuthMethods))
+ {
+ // Pref doesn't match server. Now, find an appropriate error msg.
+
+ // pref has plaintext pw & server claims to support encrypted pw
+ if (m_prefAuthMethods == (kHasAuthOldLoginCapability |
+ kHasAuthLoginCapability | kHasAuthPlainCapability) &&
+ GetServerStateParser().GetCapabilityFlag() & kHasCRAMCapability)
+ // tell user to change to encrypted pw
+ AlertUserEventUsingName("imapAuthChangePlainToEncrypt");
+ // pref has encrypted pw & server claims to support plaintext pw
+ else if (m_prefAuthMethods == kHasCRAMCapability &&
+ GetServerStateParser().GetCapabilityFlag() &
+ (kHasAuthOldLoginCapability | kHasAuthLoginCapability |
+ kHasAuthPlainCapability))
+ {
+ // have SSL
+ if (m_socketType == nsMsgSocketType::SSL ||
+ m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ // tell user to change to plaintext pw
+ AlertUserEventUsingName("imapAuthChangeEncryptToPlainSSL");
+ else
+ // tell user to change to plaintext pw, with big warning
+ AlertUserEventUsingName("imapAuthChangeEncryptToPlainNoSSL");
+ }
+ else
+ // just "change auth method"
+ AlertUserEventUsingName("imapAuthMechNotSupported");
+
+ skipLoop = true;
+ }
+ else
+ {
+ // try to reset failed methods and try them again
+ ResetAuthMethods();
+ rv = ChooseAuthMethod();
+ if (NS_FAILED(rv)) // all methods failed
+ {
+ MOZ_LOG(IMAP, LogLevel::Error, ("huch? there are auth methods, and we resetted failed ones, but ChooseAuthMethod still fails."));
+ return false;
+ }
+ }
+ }
+
+ // Get username, either the stored one or from user
+ rv = m_imapServerSink->GetLoginUsername(userName);
+ if (NS_FAILED(rv) || userName.IsEmpty())
+ {
+ // The user hit "Cancel" on the dialog box
+ skipLoop = true;
+ }
+
+ /*
+ * Login can fail for various reasons:
+ * 1. Server claims to support GSSAPI, but it really doesn't.
+ * Or the client doesn't support GSSAPI, or is not logged in yet.
+ * (GSSAPI is a mechanism without password in apps).
+ * 2. Server claims to support CRAM-MD5, but it's broken and will fail despite correct password.
+ * 2.1. Some servers say they support CRAM but are so badly broken that trying it causes
+ * all subsequent login attempts to fail during this connection (bug 231303).
+ * So we use CRAM/NTLM/MSN only if enabled in prefs.
+ * Update: if it affects only some ISPs, we can maybe use the ISP DB
+ * and disable CRAM specifically for these.
+ * 3. Prefs are set to require auth methods which the server doesn't support
+ * (per CAPS or we tried and they failed).
+ * 4. User provided wrong password.
+ * 5. We tried too often and the server shut us down, so even a correct attempt
+ * will now (currently) fail.
+ * The above problems may overlap, e.g. 3. with 1. and 2., and we can't differentiate
+ * between 2. and 4., which is really unfortunate.
+ */
+
+ bool newPasswordRequested = false;
+ // remember the msgWindow before we start trying to logon, because if the
+ // server drops the connection on errors, TellThreadToDie will null out the
+ // protocolsink and we won't be able to get the msgWindow.
+ AutoProxyReleaseMsgWindow msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+
+ // This loops over 1) auth methods (only one per loop) and 2) password tries (with UI)
+ while (!loginSucceeded && !skipLoop && !DeathSignalReceived())
+ {
+ // Get password
+ if (m_currentAuthMethod != kHasAuthGssApiCapability && // GSSAPI uses no pw in apps
+ m_currentAuthMethod != kHasAuthExternalCapability &&
+#ifdef MOZ_MAILNEWS_OAUTH2
+ m_currentAuthMethod != kHasXOAuth2Capability &&
+#endif
+ m_currentAuthMethod != kHasAuthNoneCapability)
+ {
+ rv = GetPassword(password, newPasswordRequested);
+ newPasswordRequested = false;
+ if (rv == NS_MSG_PASSWORD_PROMPT_CANCELLED || NS_FAILED(rv))
+ {
+ MOZ_LOG(IMAP, LogLevel::Error, ("IMAP: password prompt failed or user canceled it"));
+ break;
+ }
+ MOZ_LOG(IMAP, LogLevel::Debug, ("got new password"));
+ }
+
+ bool lastReportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false); // turn off errors - we'll put up our own.
+
+ rv = AuthLogin(userName.get(), password, m_currentAuthMethod);
+
+ GetServerStateParser().SetReportingErrors(lastReportingErrors); // restore error reports
+ loginSucceeded = NS_SUCCEEDED(rv);
+
+ if (!loginSucceeded)
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("authlogin failed"));
+ MarkAuthMethodAsFailed(m_currentAuthMethod);
+ rv = ChooseAuthMethod(); // change m_currentAuthMethod to try other one next round
+
+ if (NS_FAILED(rv)) // all methods failed
+ {
+ if (m_prefAuthMethods == kHasAuthGssApiCapability)
+ {
+ // GSSAPI failed, and it's the only available method,
+ // and it's password-less, so nothing left to do.
+ AlertUserEventUsingName("imapAuthGssapiFailed");
+ break;
+ }
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ if (m_prefAuthMethods & kHasXOAuth2Capability)
+ {
+ // OAuth2 failed. We don't have an error message for this, and we
+ // in a string freeze, so use a generic error message. Entering
+ // a password does not help.
+ AlertUserEventUsingName("imapUnknownHostError");
+ break;
+ }
+#endif
+
+ // The reason that we failed might be a wrong password, so
+ // ask user what to do
+ MOZ_LOG(IMAP, LogLevel::Warning, ("IMAP: ask user what to do (after login failed): new passwort, retry, cancel"));
+ if (!m_imapServerSink)
+ break;
+ // if there's no msg window, don't forget the password
+ if (!msgWindow)
+ break;
+ int32_t buttonPressed = 1;
+ rv = m_imapServerSink->PromptLoginFailed(msgWindow,
+ &buttonPressed);
+ if (NS_FAILED(rv))
+ break;
+ if (buttonPressed == 2) // 'New password' button
+ {
+ MOZ_LOG(IMAP, LogLevel::Warning, ("new password button pressed."));
+ // Forget the current password
+ password.Truncate();
+ m_hostSessionList->SetPasswordForHost(GetImapServerKey(), nullptr);
+ m_imapServerSink->ForgetPassword();
+ m_password.Truncate();
+ MOZ_LOG(IMAP, LogLevel::Warning, ("password resetted (nulled)"));
+ newPasswordRequested = true;
+ // Will call GetPassword() in beginning of next loop
+
+ // Try all possible auth methods again with the new password.
+ ResetAuthMethods();
+ }
+ else if (buttonPressed == 0) // Retry button
+ {
+ MOZ_LOG(IMAP, LogLevel::Warning, ("retry button pressed"));
+ // Try all possible auth methods again
+ ResetAuthMethods();
+ }
+ else if (buttonPressed == 1) // Cancel button
+ {
+ MOZ_LOG(IMAP, LogLevel::Warning, ("cancel button pressed"));
+ break; // Abort quickly
+ }
+
+ // TODO what is this for? When does it get set to != unknown again?
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
+ SendSetBiffIndicatorEvent(m_currentBiffState);
+ } // all methods failed
+ } // login failed
+ } // while
+
+ if (loginSucceeded)
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("login succeeded"));
+ bool passwordAlreadyVerified;
+ m_hostSessionList->SetPasswordForHost(GetImapServerKey(), password.get());
+ rv = m_hostSessionList->GetPasswordVerifiedOnline(GetImapServerKey(), passwordAlreadyVerified);
+ if (NS_SUCCEEDED(rv) && !passwordAlreadyVerified)
+ m_hostSessionList->SetPasswordVerifiedOnline(GetImapServerKey());
+ bool imapPasswordIsNew = !passwordAlreadyVerified;
+ if (imapPasswordIsNew)
+ {
+ if (m_currentBiffState == nsIMsgFolder::nsMsgBiffState_Unknown)
+ {
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
+ SendSetBiffIndicatorEvent(m_currentBiffState);
+ }
+ m_imapServerSink->SetUserAuthenticated(true);
+ }
+
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+ // We don't want to do any more processing if we're just
+ // verifying the ability to logon because it can leave us in
+ // a half-constructed state.
+ if (imapAction != nsIImapUrl::nsImapVerifylogon)
+ ProcessAfterAuthenticated();
+ }
+ else // login failed
+ {
+ MOZ_LOG(IMAP, LogLevel::Error, ("login failed entirely"));
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
+ SendSetBiffIndicatorEvent(m_currentBiffState);
+ HandleCurrentUrlError();
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ }
+
+ return loginSucceeded;
+}
+
+void nsImapProtocol::UpdateFolderQuotaData(nsCString& aQuotaRoot, uint32_t aUsed, uint32_t aMax)
+{
+ NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!");
+
+ m_imapMailFolderSink->SetFolderQuotaData(aQuotaRoot, aUsed, aMax);
+}
+
+void nsImapProtocol::GetQuotaDataIfSupported(const char *aBoxName)
+{
+ // If server doesn't have quota support, don't do anything
+ if (! (GetServerStateParser().GetCapabilityFlag() & kQuotaCapability))
+ return;
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(aBoxName, escapedName);
+
+ IncrementCommandTagNumber();
+
+ nsAutoCString quotacommand(GetServerCommandTag());
+ quotacommand.Append(NS_LITERAL_CSTRING(" getquotaroot \""));
+ quotacommand.Append(escapedName);
+ quotacommand.Append(NS_LITERAL_CSTRING("\"" CRLF));
+
+ NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!");
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->SetFolderQuotaCommandIssued(true);
+
+ nsresult quotarv = SendData(quotacommand.get());
+ if (NS_SUCCEEDED(quotarv))
+ ParseIMAPandCheckForNewMail(nullptr, true); // don't display errors.
+}
+
+bool
+nsImapProtocol::GetDeleteIsMoveToTrash()
+{
+ bool rv = false;
+ NS_ASSERTION (m_hostSessionList, "fatal... null host session list");
+ if (m_hostSessionList)
+ m_hostSessionList->GetDeleteIsMoveToTrashForHost(GetImapServerKey(), rv);
+ return rv;
+}
+
+bool
+nsImapProtocol::GetShowDeletedMessages()
+{
+ bool rv = false;
+ if (m_hostSessionList)
+ m_hostSessionList->GetShowDeletedMessagesForHost(GetImapServerKey(), rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapProtocol::OverrideConnectionInfo(const char16_t *pHost, uint16_t pPort, const char *pCookieData)
+{
+ m_logonHost = NS_LossyConvertUTF16toASCII(pHost);
+ m_logonPort = pPort;
+ m_logonCookie = pCookieData;
+ m_overRideUrlConnectionInfo = true;
+ return NS_OK;
+}
+
+bool nsImapProtocol::CheckNeeded()
+{
+ if (m_flagChangeCount >= kFlagChangesBeforeCheck)
+ return true;
+
+ int32_t deltaInSeconds;
+
+ PRTime2Seconds(PR_Now() - m_lastCheckTime, &deltaInSeconds);
+
+ return (deltaInSeconds >= kMaxSecondsBeforeCheck);
+}
+
+bool nsImapProtocol::UseCondStore()
+{
+ // Check that the server is capable of cond store, and the user
+ // hasn't disabled the use of constore for this server.
+ return m_useCondStore &&
+ GetServerStateParser().GetCapabilityFlag() & kHasCondStoreCapability &&
+ GetServerStateParser().fUseModSeq;
+}
+
+bool nsImapProtocol::UseCompressDeflate()
+{
+ // Check that the server is capable of compression, and the user
+ // hasn't disabled the use of compression for this server.
+ return m_useCompressDeflate &&
+ GetServerStateParser().GetCapabilityFlag() & kHasCompressDeflateCapability;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// The following is the implementation of nsImapMockChannel and an intermediary
+// imap steam listener. The stream listener is used to make a clean binding between the
+// imap mock channel and the memory cache channel (if we are reading from the cache)
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+// WARNING: the cache stream listener is intended to be accessed from the UI thread!
+// it will NOT create another proxy for the stream listener that gets passed in...
+class nsImapCacheStreamListener : public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsImapCacheStreamListener ();
+
+ nsresult Init(nsIStreamListener * aStreamListener, nsIImapMockChannel * aMockChannelToUse);
+protected:
+ virtual ~nsImapCacheStreamListener();
+ nsCOMPtr<nsIImapMockChannel> mChannelToUse;
+ nsCOMPtr<nsIStreamListener> mListener;
+};
+
+NS_IMPL_ADDREF(nsImapCacheStreamListener)
+NS_IMPL_RELEASE(nsImapCacheStreamListener)
+
+NS_INTERFACE_MAP_BEGIN(nsImapCacheStreamListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+NS_INTERFACE_MAP_END
+
+nsImapCacheStreamListener::nsImapCacheStreamListener()
+{
+}
+
+nsImapCacheStreamListener::~nsImapCacheStreamListener()
+{}
+
+nsresult nsImapCacheStreamListener::Init(nsIStreamListener * aStreamListener, nsIImapMockChannel * aMockChannelToUse)
+{
+ NS_ENSURE_ARG(aStreamListener);
+ NS_ENSURE_ARG(aMockChannelToUse);
+
+ mChannelToUse = aMockChannelToUse;
+ mListener = aStreamListener;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
+{
+ if (!mChannelToUse)
+ {
+ NS_ERROR("OnStartRequest called after OnStopRequest");
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
+ nsCOMPtr<nsIRequest> ourRequest = do_QueryInterface(mChannelToUse);
+ if (loadGroup)
+ loadGroup->AddRequest(ourRequest, nullptr /* context isupports */);
+ return mListener->OnStartRequest(ourRequest, aCtxt);
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnStopRequest(nsIRequest *request, nsISupports * aCtxt, nsresult aStatus)
+{
+ if (!mListener)
+ {
+ NS_ERROR("OnStopRequest called twice");
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsresult rv = mListener->OnStopRequest(mChannelToUse, aCtxt, aStatus);
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->RemoveRequest(mChannelToUse, nullptr, aStatus);
+
+ mListener = nullptr;
+ mChannelToUse->Close();
+ mChannelToUse = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, nsIInputStream * aInStream, uint64_t aSourceOffset, uint32_t aCount)
+{
+ return mListener->OnDataAvailable(mChannelToUse, aCtxt, aInStream, aSourceOffset, aCount);
+}
+
+NS_IMPL_ISUPPORTS(nsImapMockChannel, nsIImapMockChannel, nsIChannel,
+ nsIRequest, nsICacheEntryOpenCallback, nsITransportEventSink, nsISupportsWeakReference)
+
+
+nsImapMockChannel::nsImapMockChannel()
+{
+ m_channelContext = nullptr;
+ m_cancelStatus = NS_OK;
+ mLoadFlags = 0;
+ mChannelClosed = false;
+ mReadingFromCache = false;
+ mTryingToReadPart = false;
+}
+
+nsImapMockChannel::~nsImapMockChannel()
+{
+ // if we're offline, we may not get to close the channel correctly.
+ // we need to do this to send the url state change notification in
+ // the case of mem and disk cache reads.
+ NS_WARNING_ASSERTION(NS_IsMainThread(), "should only access mock channel on ui thread");
+ if (!mChannelClosed)
+ Close();
+}
+
+nsresult nsImapMockChannel::NotifyStartEndReadFromCache(bool start)
+{
+ nsresult rv = NS_OK;
+ mReadingFromCache = start;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
+ nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol);
+ if (imapUrl)
+ {
+ nsCOMPtr<nsIImapMailFolderSink> folderSink;
+ rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
+ if (folderSink)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
+ rv = folderSink->SetUrlState(nullptr /* we don't know the protocol */,
+ mailUrl, start, false, m_cancelStatus);
+
+ // Required for killing ImapProtocol thread
+ if (NS_FAILED(m_cancelStatus) && imapProtocol)
+ imapProtocol->TellThreadToDie(false);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Close()
+{
+ if (mReadingFromCache)
+ NotifyStartEndReadFromCache(false);
+ else
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (cacheEntry)
+ cacheEntry->MarkValid();
+ // remove the channel from the load group
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ GetLoadGroup(getter_AddRefs(loadGroup));
+ // if the mock channel wasn't initialized with a load group then
+ // use our load group (they may differ)
+ if (!loadGroup)
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->RemoveRequest((nsIRequest *) this, nullptr, NS_OK);
+ }
+ }
+
+ m_channelListener = nullptr;
+ mCacheRequest = nullptr;
+ if (mTryingToReadPart)
+ {
+ // clear mem cache entry on imap part url in case it's holding
+ // onto last reference in mem cache. Need to do this on ui thread
+ nsresult rv;
+ nsCOMPtr <nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
+ if (imapUrl)
+ {
+ nsCOMPtr <nsIImapMailFolderSink> folderSink;
+ rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
+ if (folderSink)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
+ rv = folderSink->ReleaseUrlCacheEntry(mailUrl);
+ }
+ }
+ }
+ mChannelClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetProgressEventSink(nsIProgressEventSink ** aProgressEventSink)
+{
+ *aProgressEventSink = mProgressEventSink;
+ NS_IF_ADDREF(*aProgressEventSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetProgressEventSink(nsIProgressEventSink * aProgressEventSink)
+{
+ mProgressEventSink = aProgressEventSink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetChannelListener(nsIStreamListener **aChannelListener)
+{
+ *aChannelListener = m_channelListener;
+ NS_IF_ADDREF(*aChannelListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetChannelContext(nsISupports **aChannelContext)
+{
+ *aChannelContext = m_channelContext;
+ NS_IF_ADDREF(*aChannelContext);
+ return NS_OK;
+}
+
+// now implement our mock implementation of the channel interface...we forward all calls to the real
+// channel if we have one...otherwise we return something bogus...
+
+NS_IMETHODIMP nsImapMockChannel::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+ m_loadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetLoadGroup(nsILoadGroup * *aLoadGroup)
+{
+ *aLoadGroup = m_loadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetLoadInfo(nsILoadInfo * *aLoadInfo)
+{
+ *aLoadInfo = m_loadInfo;
+ NS_IF_ADDREF(*aLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetLoadInfo(nsILoadInfo * aLoadInfo)
+{
+ m_loadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetOriginalURI(nsIURI* *aURI)
+{
+ // IMap does not seem to have the notion of an original URI :-(
+ // *aURI = m_originalUrl ? m_originalUrl : m_url;
+ *aURI = m_url;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetOriginalURI(nsIURI* aURI)
+{
+ // IMap does not seem to have the notion of an original URI :-(
+ // NS_NOTREACHED("nsImapMockChannel::SetOriginalURI");
+ // return NS_ERROR_NOT_IMPLEMENTED;
+ return NS_OK; // ignore
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetURI(nsIURI* *aURI)
+{
+ *aURI = m_url;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK ;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetURI(nsIURI* aURI)
+{
+ m_url = aURI;
+#ifdef DEBUG_bienvenu
+ if (!aURI)
+ printf("Clearing URI\n");
+#endif
+ if (m_url)
+ {
+ // if we don't have a progress event sink yet, get it from the url for now...
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl && !mProgressEventSink)
+ {
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ mailnewsUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ mProgressEventSink = do_QueryInterface(statusFeedback);
+ }
+ // If this is a fetch URL and we can, get the message size from the message
+ // header and set it to be the content length.
+ // Note that for an attachment URL, this will set the content length to be
+ // equal to the size of the entire message.
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url));
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ if (imapAction == nsIImapUrl::nsImapMsgFetch)
+ {
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
+ if (msgUrl)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ // A failure to get a message header isn't an error
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ uint32_t messageSize;
+ if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize)))
+ SetContentLength(messageSize);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Open(nsIInputStream **_retval)
+{
+ return NS_ImplementChannelOpen(this, _retval);
+}
+
+NS_IMETHODIMP nsImapMockChannel::Open2(nsIInputStream **_retval)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(_retval);
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnCacheEntryAvailable(nsICacheEntry *entry, bool aNew, nsIApplicationCache* aAppCache, nsresult status)
+{
+ nsresult rv = NS_OK;
+
+ // make sure we didn't close the channel before the async call back came in...
+ // hmmm....if we had write access and we canceled this mock channel then I wonder if we should
+ // be invalidating the cache entry before kicking out...
+ if (mChannelClosed)
+ {
+ entry->AsyncDoom(nullptr);
+ return NS_OK;
+ }
+
+ if (!m_url) {
+ // Something has gone terribly wrong.
+ NS_WARNING("m_url is null in OnCacheEntryAvailable");
+ return Cancel(NS_ERROR_UNEXPECTED);
+ }
+
+ do {
+ // For "normal" read/write access we always receive NS_OK here. aNew
+ // indicates whether the cache entry is new and needs to be written, or not
+ // new and can be read. If AsyncOpenURI() was called with access read-only,
+ // status==NS_ERROR_CACHE_KEY_NOT_FOUND can be received here and we just read
+ // the data directly.
+ if (NS_FAILED(status))
+ break;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+ mailnewsUrl->SetMemCacheEntry(entry);
+
+ // For URLs not related to parts, the processing is easy:
+ // aNew==true means that we need to write to the entry,
+ // aNew==false means that we can read it.
+ //
+ // Parts processing is a little complicated, we distinguish two cases:
+ // 1) The caller a) knows that the part is there or
+ // b) it is not there and can also not be read from the
+ // entire message.
+ // In this case, the URL used as cache key addresses the part and
+ // mTryingToReadPart==false.
+ // The caller has already set up part extraction.
+ // This case is no different to non-part processing.
+ // 2) The caller wants to try to extract the part from the cache entry
+ // of the entire message.
+ // In this case, the URL used as cache key addresses the message and
+ // mTryingToReadPart==true.
+ if (mTryingToReadPart)
+ {
+ // We are here with the URI of the entire message which we know exists.
+ MOZ_ASSERT(!aNew,
+ "Logic error: Trying to read part from entire message which doesn't exist");
+ if (!aNew)
+ {
+ // Check the meta data.
+ nsCString annotation;
+ rv = entry->GetMetaDataElement("ContentModified", getter_Copies(annotation));
+ if (NS_FAILED(rv) || !annotation.EqualsLiteral("Not Modified"))
+ {
+ // The cache entry is not marked "Not Modified", that means it doesn't
+ // contain the entire message, so we can't use it.
+ // Call OpenCacheEntry() a second time to get the part.
+ rv = OpenCacheEntry();
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ // Something has gone wrong, fall back to reading from the imap
+ // connection so the protocol doesn't hang.
+ break;
+ }
+ }
+ }
+
+ if (aNew)
+ {
+ // If we are writing, then insert a "stream listener Tee" into the flow
+ // to force data into the cache and to our current channel listener.
+ nsCOMPtr<nsIStreamListenerTee> tee = do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIOutputStream> out;
+ // This will fail with the cache turned off, so we need to fall through
+ // to ReadFromImapConnection instead of aborting with NS_ENSURE_SUCCESS(rv,rv).
+ rv = entry->OpenOutputStream(0, getter_AddRefs(out));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = tee->Init(m_channelListener, out, nullptr);
+ m_channelListener = do_QueryInterface(tee);
+ }
+ else
+ NS_WARNING("IMAP Protocol failed to open output stream to Necko cache");
+ }
+ }
+ else
+ {
+ rv = ReadFromMemCache(entry);
+ if (NS_SUCCEEDED(rv))
+ {
+ NotifyStartEndReadFromCache(true);
+ entry->MarkValid();
+ return NS_OK; // Kick out if reading from the cache succeeded.
+ }
+ entry->AsyncDoom(nullptr); // Doom entry if we failed to read from cache.
+ mailnewsUrl->SetMemCacheEntry(nullptr); // We aren't going to be reading from the cache.
+ }
+ } while (false);
+
+ // If reading from the cache failed or if we are writing into the cache, default to ReadFromImapConnection.
+ return ReadFromImapConnection();
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache,
+ uint32_t* aResult)
+{
+ *aResult = nsICacheEntryOpenCallback::ENTRY_WANTED;
+
+ // Check concurrent read: We can't read concurrently since we don't know
+ // that the entry will ever be written successfully. It may be aborted
+ // due to a size limitation. If reading concurrently, the following function
+ // will return NS_ERROR_IN_PROGRESS. Then we tell the cache to wait until
+ // the write is finished.
+ int64_t size = 0;
+ nsresult rv = entry->GetDataSize(&size);
+ if (rv == NS_ERROR_IN_PROGRESS)
+ *aResult = nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
+
+ return NS_OK;
+}
+
+nsresult nsImapMockChannel::OpenCacheEntry()
+{
+ nsresult rv;
+ // get the cache session from our imap service...
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ rv = imapService->GetCacheStorage(getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t uidValidity = -1;
+ nsCacheAccessMode cacheAccess = nsICacheStorage::OPEN_NORMALLY;
+
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool storeResultsOffline;
+ nsCOMPtr<nsIImapMailFolderSink> folderSink;
+
+ rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
+ if (folderSink)
+ folderSink->GetUidValidity(&uidValidity);
+ imapUrl->GetStoreResultsOffline(&storeResultsOffline);
+ // If we're storing the message in the offline store, don't
+ // write to the memory cache.
+ if (storeResultsOffline)
+ cacheAccess = nsICacheStorage::OPEN_READONLY;
+
+ // Use the uid validity as part of the cache key, so that if the uid validity
+ // changes, we won't re-use the wrong cache entries.
+ nsAutoCString extension;
+ extension.AppendInt(uidValidity, 16);
+
+ // Open a cache entry where the key is the potentially modified URL.
+ nsCOMPtr<nsIURI> newUri;
+ m_url->Clone(getter_AddRefs(newUri));
+ nsAutoCString path;
+ newUri->GetPath(path);
+
+ // First we need to "normalise" the URL by extracting ?part= and &filename.
+ // The path should only contain: ?part=x.y&filename=file.ext
+ // These are seen in the wild:
+ // /;section=2?part=1.2&filename=A01.JPG
+ // ?section=2?part=1.2&filename=A01.JPG&type=image/jpeg&filename=A01.JPG
+ // ?part=1.2&type=image/jpeg&filename=IMG_C0030.jpg
+ // ?header=quotebody&part=1.2&filename=lijbmghmkilicioj.png
+ nsAutoCString partQuery = MsgExtractQueryPart(path, "?part=");
+ if (partQuery.IsEmpty()) {
+ partQuery = MsgExtractQueryPart(path, "&part=");
+ if (!partQuery.IsEmpty()) {
+ // ? indicates a part query, so set the first character to that.
+ partQuery.SetCharAt('?', 0);
+ }
+ }
+ nsAutoCString filenameQuery = MsgExtractQueryPart(path, "&filename=");
+
+ // Truncate path at either /; or ?
+ int32_t ind = path.FindChar('?');
+ if (ind != kNotFound)
+ path.SetLength(ind);
+ ind = path.Find("/;");
+ if (ind != kNotFound)
+ path.SetLength(ind);
+
+ if (partQuery.IsEmpty())
+ {
+ // Not looking for a part. That's the easy part.
+ newUri->SetPath(path);
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ /**
+ * Part processing (rest of this function).
+ */
+ if (mTryingToReadPart)
+ {
+ // If mTryingToReadPart is set, we are here for the second time.
+ // We tried to read a part from the entire message but the meta data didn't
+ // allow it. So we come back here.
+ // Now request the part with its full URL.
+ mTryingToReadPart = false;
+
+ // Note that part extraction was already set the first time.
+ newUri->SetPath(path + partQuery + filenameQuery);
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ // First time processing. Set up part extraction.
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+
+ // Check whether part is in the cache.
+ bool exists = false;
+ newUri->SetPath(path + partQuery + filenameQuery);
+ rv = cacheStorage->Exists(newUri, extension, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ // Let's see whether we have the entire message instead.
+ newUri->SetPath(path);
+ rv = cacheStorage->Exists(newUri, extension, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) {
+ // The entire message is not in the cache. Request the part.
+ newUri->SetPath(path + partQuery + filenameQuery);
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ // This is where is gets complicated. The entire message is in the cache,
+ // but we don't know whether it's suitable for use. Its meta data
+ // might indicate that the message is incomplete.
+ mTryingToReadPart = true;
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+}
+
+nsresult nsImapMockChannel::ReadFromMemCache(nsICacheEntry *entry)
+{
+ NS_ENSURE_ARG(entry);
+
+ nsCString annotation;
+ nsAutoCString entryKey;
+ nsAutoCString contentType;
+ nsresult rv = NS_OK;
+ bool shouldUseCacheEntry = false;
+
+ entry->GetKey(entryKey);
+ if (entryKey.FindChar('?') != kNotFound)
+ {
+ // Part processing: If we have a part, then we should use the cache entry.
+ entry->GetMetaDataElement("contentType", getter_Copies(contentType));
+ if (!contentType.IsEmpty())
+ SetContentType(contentType);
+ shouldUseCacheEntry = true;
+ }
+ else
+ {
+ // Whole message processing: We should make sure the content isn't modified.
+ rv = entry->GetMetaDataElement("ContentModified", getter_Copies(annotation));
+ if (NS_SUCCEEDED(rv) && !annotation.IsEmpty())
+ shouldUseCacheEntry = annotation.EqualsLiteral("Not Modified");
+
+ // Compare cache entry size with message size.
+ if (shouldUseCacheEntry)
+ {
+ int64_t entrySize;
+
+ rv = entry->GetDataSize(&entrySize);
+ // We don't expect concurrent read here, so this call should always work.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
+ if (msgUrl)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ // A failure to get a message header isn't an error
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ uint32_t messageSize;
+ if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize)) &&
+ messageSize != entrySize)
+ {
+ MOZ_LOG(IMAP, LogLevel::Warning,
+ ("ReadFromMemCache size mismatch for %s: message %d, cache %ld\n",
+ entryKey.get(), messageSize, entrySize));
+ shouldUseCacheEntry = false;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Common processing for full messages and message parts.
+ */
+
+ // Check header of full message or part.
+ if (shouldUseCacheEntry)
+ {
+ nsCOMPtr<nsIInputStream> in;
+ uint32_t readCount;
+ rv = entry->OpenInputStream(0, getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+ const int kFirstBlockSize = 100;
+ char firstBlock[kFirstBlockSize + 1];
+
+ // Note: This will not work for a cache2 disk cache.
+ // (see bug 1302422 comment #4)
+ rv = in->Read(firstBlock, sizeof(firstBlock), &readCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ firstBlock[kFirstBlockSize] = '\0';
+ int32_t findPos = MsgFindCharInSet(nsDependentCString(firstBlock),
+ ":\n\r", 0);
+ // Check that the first line is a header line, i.e., with a ':' in it
+ // Or that it begins with "From " because some IMAP servers allow that,
+ // even though it's technically invalid.
+ shouldUseCacheEntry = ((findPos != -1 && firstBlock[findPos] == ':') ||
+ !(strncmp(firstBlock, "From ", 5)));
+ in->Close();
+ }
+
+ if (shouldUseCacheEntry)
+ {
+ nsCOMPtr<nsIInputStream> in;
+ rv = entry->OpenInputStream(0, getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // if mem cache entry is broken or empty, return error.
+ uint64_t bytesAvailable;
+ rv = in->Available(&bytesAvailable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bytesAvailable)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), in);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we are going to read from the cache, then create a mock stream listener class and use it
+ nsImapCacheStreamListener * cacheListener = new nsImapCacheStreamListener();
+ NS_ADDREF(cacheListener);
+ cacheListener->Init(m_channelListener, this);
+ rv = pump->AsyncRead(cacheListener, m_channelContext);
+ NS_RELEASE(cacheListener);
+
+ if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read should we return
+ {
+ mCacheRequest = pump;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ // if the msg is unread, we should mark it read on the server. This lets
+ // the code running this url we're loading from the cache, if it cares.
+ imapUrl->SetMsgLoadingFromCache(true);
+
+ // be sure to set the cache entry's security info status as our security info status...
+ nsCOMPtr<nsISupports> securityInfo;
+ entry->GetSecurityInfo(getter_AddRefs(securityInfo));
+ SetSecurityInfo(securityInfo);
+ return NS_OK;
+ } // if AsyncRead succeeded.
+ } // if content is not modified
+ else
+ {
+ // Content is modified so return an error so we try to open it the
+ // old fashioned way.
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+class nsReadFromImapConnectionFailure : public mozilla::Runnable
+{
+public:
+ explicit nsReadFromImapConnectionFailure(nsImapMockChannel *aChannel)
+ : mImapMockChannel(aChannel)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mImapMockChannel) {
+ mImapMockChannel->RunOnStopRequestFailure();
+ }
+ return NS_OK;
+ }
+private:
+ RefPtr<nsImapMockChannel> mImapMockChannel;
+};
+
+
+nsresult nsImapMockChannel::RunOnStopRequestFailure()
+{
+ if (m_channelListener) {
+ m_channelListener->OnStopRequest(this, m_channelContext,
+ NS_MSG_ERROR_MSG_NOT_OFFLINE);
+ }
+ return NS_OK;
+}
+
+// the requested url isn't in any of our caches so create an imap connection
+// to process it.
+nsresult nsImapMockChannel::ReadFromImapConnection()
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+
+ bool localOnly = false;
+ imapUrl->GetLocalFetchOnly(&localOnly);
+ if (localOnly)
+ {
+ // This will cause an OnStartRunningUrl, and the subsequent close
+ // will then cause an OnStopRunningUrl with the cancel status.
+ NotifyStartEndReadFromCache(true);
+ Cancel(NS_MSG_ERROR_MSG_NOT_OFFLINE);
+
+ // Dispatch error notification, so ReadFromImapConnection() returns *before*
+ // the error is sent to the listener's OnStopRequest(). This avoids
+ // endless recursion where the caller relies on async execution.
+ nsCOMPtr<nsIRunnable> event = new nsReadFromImapConnectionFailure(this);
+ NS_DispatchToCurrentThread(event);
+ return NS_MSG_ERROR_MSG_NOT_OFFLINE;
+ }
+
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ GetLoadGroup(getter_AddRefs(loadGroup));
+ if (!loadGroup) // if we don't have one, the url will snag one from the msg window...
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ // okay, add the mock channel to the load group..
+ if (loadGroup)
+ loadGroup->AddRequest((nsIRequest *) this, nullptr /* context isupports */);
+
+ // loading the url consists of asking the server to add the url to it's imap event queue....
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = mailnewsUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapIncomingServer> imapServer (do_QueryInterface(server, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Assume AsyncRead is always called from the UI thread.....
+ return imapServer->GetImapConnectionAndLoadUrl(imapUrl, m_channelListener);
+}
+
+// for messages stored in our offline cache, we have special code to handle that...
+// If it's in the local cache, we return true and we can abort the download because
+// this method does the rest of the work.
+bool nsImapMockChannel::ReadFromLocalCache()
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+
+ bool useLocalCache = false;
+ mailnewsUrl->GetMsgIsInLocalCache(&useLocalCache);
+ if (useLocalCache)
+ {
+ nsAutoCString messageIdString;
+
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+
+ imapUrl->GetListOfMessageIds(messageIdString);
+ nsCOMPtr <nsIMsgFolder> folder;
+ rv = mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ if (folder && NS_SUCCEEDED(rv))
+ {
+ // we want to create a file channel and read the msg from there.
+ nsCOMPtr<nsIInputStream> fileStream;
+ nsMsgKey msgKey = strtoul(messageIdString.get(), nullptr, 10);
+ uint32_t size;
+ int64_t offset;
+ rv = folder->GetOfflineFileStream(msgKey, &offset, &size, getter_AddRefs(fileStream));
+ // get the file channel from the folder, somehow (through the message or
+ // folder sink?) We also need to set the transfer offset to the message offset
+ if (fileStream && NS_SUCCEEDED(rv))
+ {
+ // dougt - This may break the ablity to "cancel" a read from offline mail reading.
+ // fileChannel->SetLoadGroup(m_loadGroup);
+ nsImapCacheStreamListener * cacheListener = new nsImapCacheStreamListener();
+ NS_ADDREF(cacheListener);
+ cacheListener->Init(m_channelListener, this);
+
+ // create a stream pump that will async read the specified amount of data.
+ // XXX make offset/size 64-bit ints
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), fileStream,
+ offset, (int64_t) size);
+ if (NS_SUCCEEDED(rv))
+ rv = pump->AsyncRead(cacheListener, m_channelContext);
+
+ NS_RELEASE(cacheListener);
+
+ if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read should we return
+ {
+ // if the msg is unread, we should mark it read on the server. This lets
+ // the code running this url we're loading from the cache, if it cares.
+ imapUrl->SetMsgLoadingFromCache(true);
+ return true;
+ }
+ } // if we got an offline file transport
+ } // if we got the folder for this url
+ } // if use local cache
+
+ return false;
+}
+
+NS_IMETHODIMP nsImapMockChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ nsresult rv = NS_OK;
+
+ int32_t port;
+ if (!m_url)
+ return NS_ERROR_NULL_POINTER;
+ rv = m_url->GetPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = NS_CheckPortSafety(port, "imap");
+ if (NS_FAILED(rv))
+ return rv;
+
+ // set the stream listener and then load the url
+ m_channelContext = ctxt;
+ NS_ASSERTION(!m_channelListener, "shouldn't already have a listener");
+ m_channelListener = listener;
+ nsCOMPtr<nsIImapUrl> imapUrl (do_QueryInterface(m_url));
+
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+
+ bool externalLink = true;
+ imapUrl->GetExternalLinkUrl(&externalLink);
+
+ if (externalLink)
+ {
+ // for security purposes, only allow imap urls originating from external sources
+ // perform a limited set of actions.
+ // Currently the allowed set includes:
+ // 1) folder selection
+ // 2) message fetch
+ // 3) message part fetch
+
+ if (! (imapAction == nsIImapUrl::nsImapSelectFolder || imapAction == nsIImapUrl::nsImapMsgFetch || imapAction == nsIImapUrl::nsImapOpenMimePart
+ || imapAction == nsIImapUrl::nsImapMsgFetchPeek))
+ return NS_ERROR_FAILURE; // abort the running of this url....it failed a security check
+ }
+
+ if (ReadFromLocalCache())
+ {
+ (void) NotifyStartEndReadFromCache(true);
+ return NS_OK;
+ }
+
+ // okay, it's not in the local cache, now check the memory cache...
+ // but we can't download for offline use from the memory cache
+ if (imapAction != nsIImapUrl::nsImapMsgDownloadForOffline)
+ {
+ rv = OpenCacheEntry();
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+ // if for some reason open cache entry failed then just default to opening an imap connection for the url
+ return ReadFromImapConnection();
+}
+
+NS_IMETHODIMP nsImapMockChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+nsresult nsImapMockChannel::SetupPartExtractorListener(nsIImapUrl * aUrl, nsIStreamListener * aConsumer)
+{
+ // if the url we are loading refers to a specific part then we need
+ // libmime to extract that part from the message for us.
+ bool refersToPart = false;
+ aUrl->GetMimePartSelectorDetected(&refersToPart);
+ if (refersToPart)
+ {
+ nsCOMPtr<nsIStreamConverterService> converter = do_GetService("@mozilla.org/streamConverters;1");
+ if (converter && aConsumer)
+ {
+ nsCOMPtr<nsIStreamListener> newConsumer;
+ converter->AsyncConvertData("message/rfc822", "*/*",
+ aConsumer, static_cast<nsIChannel *>(this), getter_AddRefs(newConsumer));
+ if (newConsumer)
+ m_channelListener = newConsumer;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ //*aLoadFlags = nsIRequest::LOAD_NORMAL;
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK; // don't fail when trying to set this
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetContentType(nsACString &aContentType)
+{
+ if (mContentType.IsEmpty())
+ {
+ nsImapAction imapAction = 0;
+ if (m_url)
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ if (imapUrl)
+ {
+ imapUrl->GetImapAction(&imapAction);
+ }
+ }
+ if (imapAction == nsIImapUrl::nsImapSelectFolder)
+ aContentType.AssignLiteral("x-application-imapfolder");
+ else
+ aContentType.AssignLiteral("message/rfc822");
+ }
+ else
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetContentType(const nsACString &aContentType)
+{
+ nsAutoCString charset;
+ nsresult rv = NS_ParseResponseContentType(aContentType, mContentType, charset);
+ if (NS_FAILED(rv) || mContentType.IsEmpty())
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ aContentCharset.Assign(mCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ mCharset.Assign(aContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetContentLength(int64_t * aContentLength)
+{
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetContentLength(int64_t aContentLength)
+{
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetOwner(nsISupports * *aPrincipal)
+{
+ *aPrincipal = mOwner;
+ NS_IF_ADDREF(*aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetOwner(nsISupports * aPrincipal)
+{
+ mOwner = aPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetSecurityInfo(nsISupports *aSecurityInfo)
+{
+ mSecurityInfo = aSecurityInfo;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// From nsIRequest
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsImapMockChannel::GetName(nsACString &result)
+{
+ if (m_url)
+ return m_url->GetSpec(result);
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::IsPending(bool *result)
+{
+ *result = m_channelListener != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetStatus(nsresult *status)
+{
+ *status = m_cancelStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetImapProtocol(nsIImapProtocol *aProtocol)
+{
+ mProtocol = do_GetWeakReference(aProtocol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Cancel(nsresult status)
+{
+ NS_WARNING_ASSERTION(NS_IsMainThread(),
+ "nsImapMockChannel::Cancel should only be called from UI thread");
+ m_cancelStatus = status;
+ nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol);
+
+ // if we aren't reading from the cache and we get canceled...doom our cache entry...
+ if (m_url)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ DoomCacheEntry(mailnewsUrl);
+ }
+
+ // Required for killing ImapProtocol thread
+ if (imapProtocol)
+ imapProtocol->TellThreadToDie(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Suspend()
+{
+ NS_NOTREACHED("nsImapMockChannel::Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Resume()
+{
+ NS_NOTREACHED("nsImapMockChannel::Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aNotificationCallbacks)
+{
+ *aNotificationCallbacks = mCallbacks.get();
+ NS_IF_ADDREF(*aNotificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
+{
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnTransportStatus(nsITransport *transport, nsresult status,
+ int64_t progress, int64_t progressMax)
+{
+ if (NS_FAILED(m_cancelStatus) || (mLoadFlags & LOAD_BACKGROUND) || !m_url)
+ return NS_OK;
+
+ // these transport events should not generate any status messages
+ if (status == NS_NET_STATUS_RECEIVING_FROM ||
+ status == NS_NET_STATUS_SENDING_TO)
+ return NS_OK;
+
+ if (!mProgressEventSink)
+ {
+ NS_QueryNotificationCallbacks(mCallbacks, m_loadGroup, mProgressEventSink);
+ if (!mProgressEventSink)
+ return NS_OK;
+ }
+
+ nsAutoCString host;
+ m_url->GetHost(host);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ mailnewsUrl->GetServer(getter_AddRefs(server));
+ if (server)
+ server->GetRealHostName(host);
+ }
+ mProgressEventSink->OnStatus(this, nullptr, status,
+ NS_ConvertUTF8toUTF16(host).get());
+
+ return NS_OK;
+}
+
+
+nsIMAPMailboxInfo::nsIMAPMailboxInfo(const nsACString &aName, char aDelimiter)
+{
+ mMailboxName.Assign(aName);
+ mDelimiter = aDelimiter;
+ mChildrenListed = false;
+}
+
+nsIMAPMailboxInfo::~nsIMAPMailboxInfo()
+{
+}
+
+void nsIMAPMailboxInfo::SetChildrenListed(bool childrenListed)
+{
+ mChildrenListed = childrenListed;
+}
+
+bool nsIMAPMailboxInfo::GetChildrenListed()
+{
+ return mChildrenListed;
+}
+
+const nsACString& nsIMAPMailboxInfo::GetMailboxName()
+{
+ return mMailboxName;
+}
+
+char nsIMAPMailboxInfo::GetDelimiter()
+{
+ return mDelimiter;
+}
diff --git a/mailnews/imap/src/nsImapProtocol.h b/mailnews/imap/src/nsImapProtocol.h
new file mode 100644
index 000000000..ba2594c89
--- /dev/null
+++ b/mailnews/imap/src/nsImapProtocol.h
@@ -0,0 +1,772 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImapProtocol_h___
+#define nsImapProtocol_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIImapProtocol.h"
+#include "nsIImapUrl.h"
+
+#include "nsMsgProtocol.h"
+#include "nsIStreamListener.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsImapCore.h"
+#include "nsStringGlue.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsISocketTransport.h"
+#include "nsIInputStreamPump.h"
+
+// imap event sinks
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIImapMessageSink.h"
+
+// UI Thread proxy helper
+#include "nsIImapProtocolSink.h"
+
+#include "nsImapServerResponseParser.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsIMAPNamespace.h"
+#include "nsTArray.h"
+#include "nsWeakPtr.h"
+#include "nsMsgLineBuffer.h" // we need this to use the nsMsgLineStreamBuffer helper class...
+#include "nsIInputStream.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsCOMArray.h"
+#include "nsIThread.h"
+#include "nsIRunnable.h"
+#include "nsIImapMockChannel.h"
+#include "nsILoadGroup.h"
+#include "nsCOMPtr.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIMsgWindow.h"
+#include "nsIImapHeaderXferInfo.h"
+#include "nsMsgLineBuffer.h"
+#include "nsIAsyncInputStream.h"
+#include "nsITimer.h"
+#include "nsAutoPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgAsyncPrompter.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "nsSyncRunnableHelpers.h"
+#include "nsICacheEntryOpenCallback.h"
+
+class nsIMAPMessagePartIDArray;
+class nsIMsgIncomingServer;
+class nsIPrefBranch;
+class nsIMAPMailboxInfo;
+
+#define kDownLoadCacheSize 16000 // was 1536 - try making it bigger
+
+
+typedef struct _msg_line_info {
+ const char *adoptedMessageLine;
+ uint32_t uidOfMessage;
+} msg_line_info;
+
+
+class nsMsgImapLineDownloadCache : public nsIImapHeaderInfo, public nsByteArray
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPHEADERINFO
+ nsMsgImapLineDownloadCache();
+ uint32_t CurrentUID();
+ uint32_t SpaceAvailable();
+ bool CacheEmpty();
+
+ msg_line_info *GetCurrentLineInfo();
+
+private:
+ virtual ~nsMsgImapLineDownloadCache();
+
+ msg_line_info *fLineInfo;
+ int32_t m_msgSize;
+};
+
+#define kNumHdrsToXfer 10
+
+class nsMsgImapHdrXferInfo : public nsIImapHeaderXferInfo
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPHEADERXFERINFO
+ nsMsgImapHdrXferInfo();
+ void ResetAll(); // reset HeaderInfos for re-use
+ void ReleaseAll(); // release HeaderInfos (frees up memory)
+ // this will return null if we're full, in which case the client code
+ // should transfer the headers and retry.
+ nsIImapHeaderInfo *StartNewHdr();
+ // call when we've finished adding lines to current hdr
+ void FinishCurrentHdr();
+private:
+ virtual ~nsMsgImapHdrXferInfo();
+ nsCOMArray<nsIImapHeaderInfo> m_hdrInfos;
+ int32_t m_nextFreeHdrInfo;
+};
+
+// State Flags (Note, I use the word state in terms of storing
+// state information about the connection (authentication, have we sent
+// commands, etc. I do not intend it to refer to protocol state)
+// Use these flags in conjunction with SetFlag/TestFlag/ClearFlag instead
+// of creating PRBools for everything....
+
+#define IMAP_RECEIVED_GREETING 0x00000001 /* should we pause for the next read */
+#define IMAP_CONNECTION_IS_OPEN 0x00000004 /* is the connection currently open? */
+#define IMAP_WAITING_FOR_DATA 0x00000008
+#define IMAP_CLEAN_UP_URL_STATE 0x00000010 // processing clean up url state
+#define IMAP_ISSUED_LANGUAGE_REQUEST 0x00000020 // make sure we only issue the language request once per connection...
+#define IMAP_ISSUED_COMPRESS_REQUEST 0x00000040 // make sure we only request compression once
+
+class nsImapProtocol : public nsIImapProtocol,
+ public nsIRunnable,
+ public nsIInputStreamCallback,
+ public nsSupportsWeakReference,
+ public nsMsgProtocol,
+ public nsIImapProtocolSink,
+ public nsIMsgAsyncPromptListener
+{
+public:
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ nsImapProtocol();
+
+ virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length) override;
+
+ // nsIRunnable method
+ NS_IMETHOD Run() override;
+
+ //////////////////////////////////////////////////////////////////////////////////
+ // we support the nsIImapProtocol interface
+ //////////////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIIMAPPROTOCOL
+
+ //////////////////////////////////////////////////////////////////////////////////
+ // we support the nsIImapProtocolSink interface
+ //////////////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIIMAPPROTOCOLSINK
+
+ NS_DECL_NSIMSGASYNCPROMPTLISTENER
+
+ // message id string utilities.
+ uint32_t CountMessagesInIdString(const char *idString);
+ static bool HandlingMultipleMessages(const nsCString &messageIdString);
+ // escape slashes and double quotes in username/passwords for insecure login.
+ static void EscapeUserNamePasswordString(const char *strToEscape, nsCString *resultStr);
+
+ // used to start fetching a message.
+ void GetShouldDownloadAllHeaders(bool *aResult);
+ void GetArbitraryHeadersToDownload(nsCString &aResult);
+ virtual void AdjustChunkSize();
+ virtual void FetchMessage(const nsCString &messageIds,
+ nsIMAPeFetchFields whatToFetch,
+ const char *fetchModifier = nullptr,
+ uint32_t startByte = 0, uint32_t numBytes = 0,
+ char *part = 0);
+ void FetchTryChunking(const nsCString &messageIds,
+ nsIMAPeFetchFields whatToFetch,
+ bool idIsUid,
+ char *part,
+ uint32_t downloadSize,
+ bool tryChunking);
+ virtual void PipelinedFetchMessageParts(nsCString &uid, nsIMAPMessagePartIDArray *parts);
+ void FallbackToFetchWholeMsg(const nsCString &messageId, uint32_t messageSize);
+ // used when streaming a message fetch
+ virtual nsresult BeginMessageDownLoad(uint32_t totalSize, // for user, headers and body
+ const char *contentType); // some downloads are header only
+ virtual void HandleMessageDownLoadLine(const char *line, bool isPartialLine, char *lineCopy=nullptr);
+ virtual void NormalMessageEndDownload();
+ virtual void AbortMessageDownLoad();
+ virtual void PostLineDownLoadEvent(const char *line, uint32_t uid);
+ void FlushDownloadCache();
+
+ virtual void SetMailboxDiscoveryStatus(EMailboxDiscoverStatus status);
+ virtual EMailboxDiscoverStatus GetMailboxDiscoveryStatus();
+
+ virtual void ProcessMailboxUpdate(bool handlePossibleUndo);
+ // Send log output...
+ void Log(const char *logSubName, const char *extraInfo, const char *logData);
+ static void LogImapUrl(const char *logMsg, nsIImapUrl *imapUrl);
+ // Comment from 4.5: We really need to break out the thread synchronizer from the
+ // connection class...Not sure what this means
+ bool GetPseudoInterrupted();
+ void PseudoInterrupt(bool the_interrupt);
+
+ uint32_t GetMessageSize(const char * messageId, bool idsAreUids);
+ bool GetSubscribingNow();
+
+ bool DeathSignalReceived();
+ void ResetProgressInfo();
+ void SetActive(bool active);
+ bool GetActive();
+
+ bool GetShowAttachmentsInline();
+
+ // Sets whether or not the content referenced by the current ActiveEntry has been modified.
+ // Used for MIME parts on demand.
+ void SetContentModified(IMAP_ContentModifiedType modified);
+ bool GetShouldFetchAllParts();
+ bool GetIgnoreExpunges() {return m_ignoreExpunges;}
+ // Generic accessors required by the imap parser
+ char * CreateNewLineFromSocket();
+ nsresult GetConnectionStatus();
+ void SetConnectionStatus(nsresult status);
+
+ // Cleanup the connection and shutdown the thread.
+ void TellThreadToDie();
+
+ const nsCString& GetImapHostName(); // return the host name from the url for the
+ // current connection
+ const nsCString& GetImapUserName(); // return the user name from the identity
+ const char* GetImapServerKey(); // return the user name from the incoming server;
+
+ // state set by the imap parser...
+ void NotifyMessageFlags(imapMessageFlagsType flags, const nsACString &keywords,
+ nsMsgKey key, uint64_t highestModSeq);
+ void NotifySearchHit(const char * hitLine);
+
+ // Event handlers for the imap parser.
+ void DiscoverMailboxSpec(nsImapMailboxSpec * adoptedBoxSpec);
+ void AlertUserEventUsingName(const char* aMessageId);
+ void AlertUserEvent(const char * message);
+ void AlertUserEventFromServer(const char * aServerEvent, bool aForIdle = false);
+
+ void ProgressEventFunctionUsingName(const char* aMsgId);
+ void ProgressEventFunctionUsingNameWithString(const char* aMsgName, const char *
+ aExtraInfo);
+ void PercentProgressUpdateEvent(char16_t *message, int64_t currentProgress, int64_t maxProgress);
+ void ShowProgress();
+
+ // utility function calls made by the server
+
+ void Copy(const char * messageList, const char *destinationMailbox,
+ bool idsAreUid);
+ void Search(const char * searchCriteria, bool useUID,
+ bool notifyHit = true);
+ // imap commands issued by the parser
+ void Store(const nsCString &aMessageList, const char * aMessageData, bool
+ aIdsAreUid);
+ void ProcessStoreFlags(const nsCString &messageIds,
+ bool idsAreUids,
+ imapMessageFlagsType flags,
+ bool addFlags);
+ void IssueUserDefinedMsgCommand(const char *command, const char * messageList);
+ void FetchMsgAttribute(const nsCString &messageIds, const nsCString &attribute);
+ void Expunge();
+ void UidExpunge(const nsCString &messageSet);
+ void Close(bool shuttingDown = false, bool waitForResponse = true);
+ void Check();
+ void SelectMailbox(const char *mailboxName);
+ // more imap commands
+ void Logout(bool shuttingDown = false, bool waitForResponse = true);
+ void Noop();
+ void XServerInfo();
+ void Netscape();
+ void XMailboxInfo(const char *mailboxName);
+ void XAOL_Option(const char *option);
+ void MailboxData();
+ void GetMyRightsForFolder(const char *mailboxName);
+ void Bodystructure(const nsCString &messageId, bool idIsUid);
+ void PipelinedFetchMessageParts(const char *uid, nsIMAPMessagePartIDArray *parts);
+
+
+ // this function does not ref count!!! be careful!!!
+ nsIImapUrl *GetCurrentUrl() {return m_runningUrl;}
+
+ // acl and namespace stuff
+ // notifies libmsg that we have a new personal/default namespace that we're using
+ void CommitNamespacesForHostEvent();
+ // notifies libmsg that we have new capability data for the current host
+ void CommitCapability();
+
+ // Adds a set of rights for a given user on a given mailbox on the current host.
+ // if userName is NULL, it means "me," or MYRIGHTS.
+ // rights is a single string of rights, as specified by RFC2086, the IMAP ACL extension.
+ void AddFolderRightsForUser(const char *mailboxName, const char *userName, const char *rights);
+ // Clears all rights for the current folder, for all users.
+ void ClearAllFolderRights();
+ void RefreshFolderACLView(const char *mailboxName, nsIMAPNamespace *nsForMailbox);
+
+ nsresult SetFolderAdminUrl(const char *mailboxName);
+ void HandleMemoryFailure();
+ void HandleCurrentUrlError();
+
+ // UIDPLUS extension
+ void SetCopyResponseUid(const char* msgIdString);
+
+ // Quota support
+ void UpdateFolderQuotaData(nsCString& aQuotaRoot, uint32_t aUsed, uint32_t aMax);
+
+ bool GetPreferPlainText() { return m_preferPlainText; }
+
+ int32_t GetCurFetchSize() { return m_curFetchSize; }
+
+ const nsString &GetEmptyMimePartString() { return m_emptyMimePartString; }
+private:
+ virtual ~nsImapProtocol();
+ // the following flag is used to determine when a url is currently being run. It is cleared when we
+ // finish processng a url and it is set whenever we call Load on a url
+ bool m_urlInProgress;
+ nsCOMPtr<nsIImapUrl> m_runningUrl; // the nsIImapURL that is currently running
+ nsCOMPtr<nsIImapUrl> m_runningUrlLatest;
+ nsImapAction m_imapAction; // current imap action associated with this connnection...
+
+ nsCString m_hostName;
+ nsCString m_userName;
+ nsCString m_serverKey;
+ nsCString m_realHostName;
+ char *m_dataOutputBuf;
+ RefPtr<nsMsgLineStreamBuffer> m_inputStreamBuffer;
+ uint32_t m_allocatedSize; // allocated size
+ uint32_t m_totalDataSize; // total data size
+ uint32_t m_curReadIndex; // current read index
+ nsCString m_trashFolderName;
+
+ // Ouput stream for writing commands to the socket
+ nsCOMPtr<nsISocketTransport> m_transport;
+
+ nsCOMPtr<nsIAsyncInputStream> m_channelInputStream;
+ nsCOMPtr<nsIAsyncOutputStream> m_channelOutputStream;
+ nsCOMPtr<nsIImapMockChannel> m_mockChannel; // this is the channel we should forward to people
+ uint32_t m_bytesToChannel;
+ bool m_fetchingWholeMessage;
+ //nsCOMPtr<nsIRequest> mAsyncReadRequest; // we're going to cancel this when we're done with the conn.
+
+
+ // ******* Thread support *******
+ nsCOMPtr<nsIThread> m_iThread;
+ PRThread *m_thread;
+ mozilla::ReentrantMonitor m_dataAvailableMonitor; // used to notify the arrival of data from the server
+ mozilla::ReentrantMonitor m_urlReadyToRunMonitor; // used to notify the arrival of a new url to be processed
+ mozilla::ReentrantMonitor m_pseudoInterruptMonitor;
+ mozilla::ReentrantMonitor m_dataMemberMonitor;
+ mozilla::ReentrantMonitor m_threadDeathMonitor;
+ mozilla::ReentrantMonitor m_waitForBodyIdsMonitor;
+ mozilla::ReentrantMonitor m_fetchBodyListMonitor;
+ mozilla::ReentrantMonitor m_passwordReadyMonitor;
+ mozilla::Mutex mLock;
+ // If we get an async password prompt, this is where the UI thread
+ // stores the password, before notifying the imap thread of the password
+ // via the m_passwordReadyMonitor.
+ nsCString m_password;
+ // Set to the result of nsImapServer::PromptPassword
+ nsresult m_passwordStatus;
+ bool m_passwordObtained;
+
+ bool m_imapThreadIsRunning;
+ void ImapThreadMainLoop(void);
+ nsresult m_connectionStatus;
+ nsCString m_connectionType;
+
+ bool m_nextUrlReadyToRun;
+ nsWeakPtr m_server;
+
+ RefPtr<ImapMailFolderSinkProxy> m_imapMailFolderSink;
+ RefPtr<ImapMessageSinkProxy> m_imapMessageSink;
+ RefPtr<ImapServerSinkProxy> m_imapServerSink;
+ RefPtr<ImapServerSinkProxy> m_imapServerSinkLatest;
+ RefPtr<ImapProtocolSinkProxy> m_imapProtocolSink;
+
+ // helper function to setup imap sink interface proxies
+ nsresult SetupSinkProxy();
+ // End thread support stuff
+
+ bool GetDeleteIsMoveToTrash();
+ bool GetShowDeletedMessages();
+ nsCString m_currentCommand;
+ nsImapServerResponseParser m_parser;
+ nsImapServerResponseParser& GetServerStateParser() { return m_parser; }
+
+ void HandleIdleResponses();
+ virtual bool ProcessCurrentURL();
+ void EstablishServerConnection();
+ virtual void ParseIMAPandCheckForNewMail(const char* commandString =
+ nullptr, bool ignoreBadNOResponses = false);
+ // biff
+ void PeriodicBiff();
+ void SendSetBiffIndicatorEvent(nsMsgBiffState newState);
+ bool CheckNewMail();
+
+ // folder opening and listing header functions
+ void FolderHeaderDump(uint32_t *msgUids, uint32_t msgCount);
+ void FolderMsgDump(uint32_t *msgUids, uint32_t msgCount, nsIMAPeFetchFields fields);
+ void FolderMsgDumpLoop(uint32_t *msgUids, uint32_t msgCount, nsIMAPeFetchFields fields);
+ void WaitForPotentialListOfBodysToFetch(uint32_t **msgIdList, uint32_t &msgCount);
+ void HeaderFetchCompleted();
+ void UploadMessageFromFile(nsIFile* file, const char* mailboxName, PRTime date,
+ imapMessageFlagsType flags, nsCString &keywords);
+
+ // mailbox name utilities.
+ void CreateEscapedMailboxName(const char *rawName, nsCString &escapedName);
+ void SetupMessageFlagsString(nsCString & flagString,
+ imapMessageFlagsType flags,
+ uint16_t userFlags);
+
+ // body fetching listing data
+ bool m_fetchBodyListIsNew;
+ uint32_t m_fetchBodyCount;
+ uint32_t *m_fetchBodyIdList;
+
+ // initialization function given a new url and transport layer
+ nsresult SetupWithUrl(nsIURI * aURL, nsISupports* aConsumer);
+ void ReleaseUrlState(bool rerunningUrl); // release any state that is stored on a per action basis.
+ /**
+ * Last ditch effort to run the url without using an imap connection.
+ * If it turns out that we don't need to run the url at all (e.g., we're
+ * trying to download a single message for offline use and it has already
+ * been downloaded, this function will send the appropriate notifications.
+ *
+ * @returns true if the url has been run locally, or doesn't need to be run.
+ */
+ bool TryToRunUrlLocally(nsIURI *aURL, nsISupports *aConsumer);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Communication methods --> Reading and writing protocol
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ // SendData not only writes the NULL terminated data in dataBuffer to our output stream
+ // but it also informs the consumer that the data has been written to the stream.
+ // aSuppressLogging --> set to true if you wish to suppress logging for this particular command.
+ // this is useful for making sure we don't log authenication information like the user's password (which was
+ // encoded anyway), but still we shouldn't add that information to the log.
+ nsresult SendData(const char * dataBuffer, bool aSuppressLogging = false) override;
+
+ // state ported over from 4.5
+ bool m_pseudoInterrupted;
+ bool m_active;
+ bool m_folderNeedsSubscribing;
+ bool m_folderNeedsACLRefreshed;
+
+ bool m_threadShouldDie;
+
+ // use to prevent re-entering TellThreadToDie.
+ bool m_inThreadShouldDie;
+ // if the UI thread has signalled the IMAP thread to die, and the
+ // connection has timed out, this will be set to FALSE.
+ bool m_safeToCloseConnection;
+
+ nsImapFlagAndUidState *m_flagState;
+ nsMsgBiffState m_currentBiffState;
+ // manage the IMAP server command tags
+ // 11 = enough memory for the decimal representation of MAX_UINT + trailing nul
+ char m_currentServerCommandTag[11];
+ uint32_t m_currentServerCommandTagNumber;
+ void IncrementCommandTagNumber();
+ const char *GetServerCommandTag();
+
+ void StartTLS();
+
+ // login related methods.
+ nsresult GetPassword(nsCString &password, bool aNewPasswordRequested);
+#ifdef MOZ_MAILNEWS_OAUTH2
+ void InitPrefAuthMethods(int32_t authMethodPrefValue, nsIMsgIncomingServer *aServer);
+#else
+ void InitPrefAuthMethods(int32_t authMethodPrefValue);
+#endif
+ nsresult ChooseAuthMethod();
+ void MarkAuthMethodAsFailed(eIMAPCapabilityFlags failedAuthMethod);
+ void ResetAuthMethods();
+
+ // All of these methods actually issue protocol
+ void Capability(); // query host for capabilities.
+ void ID(); // send RFC 2971 app info to server
+ void EnableCondStore();
+ void StartCompressDeflate();
+ nsresult BeginCompressing();
+ void Language(); // set the language on the server if it supports it
+ void Namespace();
+ void InsecureLogin(const char *userName, const nsCString &password);
+ nsresult AuthLogin(const char *userName, const nsCString &password, eIMAPCapabilityFlag flag);
+ void ProcessAuthenticatedStateURL();
+ void ProcessAfterAuthenticated();
+ void ProcessSelectedStateURL();
+ bool TryToLogon();
+
+ // Process Authenticated State Url used to be one giant if statement. I've broken out a set of actions
+ // based on the imap action passed into the url. The following functions are imap protocol handlers for
+ // each action. They are called by ProcessAuthenticatedStateUrl.
+ void OnLSubFolders();
+ void OnAppendMsgFromFile();
+
+ char *GetFolderPathString(); // OK to call from UI thread
+
+ char * OnCreateServerSourceFolderPathString();
+ char * OnCreateServerDestinationFolderPathString();
+ nsresult CreateServerSourceFolderPathString(char **result);
+ void OnCreateFolder(const char * aSourceMailbox);
+ void OnEnsureExistsFolder(const char * aSourceMailbox);
+ void OnSubscribe(const char * aSourceMailbox);
+ void OnUnsubscribe(const char * aSourceMailbox);
+ void RefreshACLForFolderIfNecessary(const char * mailboxName);
+ void RefreshACLForFolder(const char * aSourceMailbox);
+ void GetACLForFolder(const char *aMailboxName);
+ void OnRefreshAllACLs();
+ void OnListFolder(const char * aSourceMailbox, bool aBool);
+ void OnStatusForFolder(const char * sourceMailbox);
+ void OnDeleteFolder(const char * aSourceMailbox);
+ void OnRenameFolder(const char * aSourceMailbox);
+ void OnMoveFolderHierarchy(const char * aSourceMailbox);
+ void DeleteFolderAndMsgs(const char * aSourceMailbox);
+ void RemoveMsgsAndExpunge();
+ void FindMailboxesIfNecessary();
+ void CreateMailbox(const char *mailboxName);
+ void DeleteMailbox(const char *mailboxName);
+ void RenameMailbox(const char *existingName, const char *newName);
+ void RemoveHierarchyDelimiter(nsCString &mailboxName);
+ bool CreateMailboxRespectingSubscriptions(const char *mailboxName);
+ bool DeleteMailboxRespectingSubscriptions(const char *mailboxName);
+ bool RenameMailboxRespectingSubscriptions(const char *existingName,
+ const char *newName,
+ bool reallyRename);
+ // notify the fe that a folder was deleted
+ void FolderDeleted(const char *mailboxName);
+ // notify the fe that a folder creation failed
+ void FolderNotCreated(const char *mailboxName);
+ // notify the fe that a folder was deleted
+ void FolderRenamed(const char *oldName,
+ const char *newName);
+
+ bool FolderIsSelected(const char *mailboxName);
+
+ bool MailboxIsNoSelectMailbox(const char *mailboxName);
+ nsCString CreatePossibleTrashName(const char *prefix);
+ bool FolderNeedsACLInitialized(const char *folderName);
+ void DiscoverMailboxList();
+ void DiscoverAllAndSubscribedBoxes();
+ void MailboxDiscoveryFinished();
+ void NthLevelChildList(const char *onlineMailboxPrefix, int32_t depth);
+ // LIST SUBSCRIBED command (from RFC 5258) crashes some servers. so we need to
+ // identify those servers
+ bool GetListSubscribedIsBrokenOnServer();
+ bool IsExtraSelectNeeded();
+ void Lsub(const char *mailboxPattern, bool addDirectoryIfNecessary);
+ void List(const char *mailboxPattern, bool addDirectoryIfNecessary,
+ bool useXLIST = false);
+ void Subscribe(const char *mailboxName);
+ void Unsubscribe(const char *mailboxName);
+ void Idle();
+ void EndIdle(bool waitForResponse = true);
+ // Some imap servers include the mailboxName following the dir-separator in the list of
+ // subfolders of the mailboxName. In fact, they are the same. So we should decide if
+ // we should delete such subfolder and provide feedback if the delete operation succeed.
+ bool DeleteSubFolders(const char* aMailboxName, bool & aDeleteSelf);
+ bool RenameHierarchyByHand(const char *oldParentMailboxName,
+ const char *newParentMailboxName);
+ bool RetryUrl();
+
+ nsresult GlobalInitialization(nsIPrefBranch *aPrefBranch);
+ nsresult Configure(int32_t TooFastTime, int32_t IdealTime,
+ int32_t ChunkAddSize, int32_t ChunkSize, int32_t ChunkThreshold,
+ bool FetchByChunks);
+ nsresult GetMsgWindow(nsIMsgWindow ** aMsgWindow);
+ // End Process AuthenticatedState Url helper methods
+
+ virtual char const *GetType() override {return "imap";}
+
+ // Quota support
+ void GetQuotaDataIfSupported(const char *aBoxName);
+
+ // CondStore support - true if server supports it, and the user hasn't disabled it.
+ bool UseCondStore();
+ // false if pref "mail.server.serverxxx.use_condstore" is false;
+ bool m_useCondStore;
+ // COMPRESS=DEFLATE support - true if server supports it, and the user hasn't disabled it.
+ bool UseCompressDeflate();
+ // false if pref "mail.server.serverxxx.use_compress_deflate" is false;
+ bool m_useCompressDeflate;
+ // these come from the nsIDBFolderInfo in the msgDatabase and
+ // are initialized in nsImapProtocol::SetupWithUrl.
+ uint64_t mFolderLastModSeq;
+ int32_t mFolderTotalMsgCount;
+ uint32_t mFolderHighestUID;
+ uint32_t mFolderNumDeleted;
+
+ bool m_isGmailServer;
+ nsTArray<nsCString> mCustomDBHeaders;
+ nsTArray<nsCString> mCustomHeaders;
+ bool m_trackingTime;
+ PRTime m_startTime;
+ PRTime m_endTime;
+ PRTime m_lastActiveTime;
+ int32_t m_tooFastTime;
+ int32_t m_idealTime;
+ int32_t m_chunkAddSize;
+ int32_t m_chunkStartSize;
+ bool m_fetchByChunks;
+ bool m_sendID;
+ int32_t m_curFetchSize;
+ bool m_ignoreExpunges;
+ eIMAPCapabilityFlags m_prefAuthMethods; // set of capability flags (in nsImapCore.h) for auth methods
+ eIMAPCapabilityFlags m_failedAuthMethods; // ditto
+ eIMAPCapabilityFlag m_currentAuthMethod; // exactly one capability flag, or 0
+ int32_t m_socketType;
+ int32_t m_chunkSize;
+ int32_t m_chunkThreshold;
+ RefPtr<nsMsgImapLineDownloadCache> m_downloadLineCache;
+ RefPtr<nsMsgImapHdrXferInfo> m_hdrDownloadCache;
+ nsCOMPtr <nsIImapHeaderInfo> m_curHdrInfo;
+ // mapping between mailboxes and the corresponding folder flags
+ nsDataHashtable<nsCStringHashKey, int32_t> m_standardListMailboxes;
+ // mapping between special xlist mailboxes and the corresponding folder flags
+ nsDataHashtable<nsCStringHashKey, int32_t> m_specialXListMailboxes;
+
+
+ nsCOMPtr<nsIImapHostSessionList> m_hostSessionList;
+
+ bool m_fromHeaderSeen;
+
+ // these settings allow clients to override various pieces of the connection info from the url
+ bool m_overRideUrlConnectionInfo;
+
+ nsCString m_logonHost;
+ nsCString m_logonCookie;
+ int16_t m_logonPort;
+
+ nsString mAcceptLanguages;
+
+ // progress stuff
+ void SetProgressString(const char* stringName);
+
+ nsString m_progressString;
+ nsCString m_progressStringName;
+ int32_t m_progressIndex;
+ int32_t m_progressCount;
+ nsCString m_lastProgressStringName;
+ int32_t m_lastPercent;
+ int64_t m_lastProgressTime;
+
+ bool m_notifySearchHit;
+ bool m_checkForNewMailDownloadsHeaders;
+ bool m_needNoop;
+ bool m_idle;
+ bool m_useIdle;
+ int32_t m_noopCount;
+ bool m_autoSubscribe, m_autoUnsubscribe, m_autoSubscribeOnOpen;
+ bool m_closeNeededBeforeSelect;
+ bool m_retryUrlOnError;
+ bool m_preferPlainText;
+ nsCString m_forceSelectValue;
+ bool m_forceSelect;
+
+ int32_t m_uidValidity; // stored uid validity for the selected folder.
+
+ enum EMailboxHierarchyNameState {
+ kNoOperationInProgress,
+ kDiscoverBaseFolderInProgress,
+ kDiscoverTrashFolderInProgress,
+ kDeleteSubFoldersInProgress,
+ kListingForInfoOnly,
+ kListingForInfoAndDiscovery,
+ kDiscoveringNamespacesOnly,
+ kXListing,
+ kListingForFolderFlags,
+ kListingForCreate
+ };
+ EMailboxHierarchyNameState m_hierarchyNameState;
+ EMailboxDiscoverStatus m_discoveryStatus;
+ nsTArray<nsIMAPMailboxInfo*> m_listedMailboxList;
+ nsTArray<char*> * m_deletableChildren;
+ uint32_t m_flagChangeCount;
+ PRTime m_lastCheckTime;
+
+ bool CheckNeeded();
+
+ nsString m_emptyMimePartString;
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ RefPtr<mozilla::mailnews::OAuth2ThreadHelper> mOAuth2Support;
+#endif
+};
+
+// This small class is a "mock" channel because it is a mockery of the imap channel's implementation...
+// it's a light weight channel that we can return to necko when they ask for a channel on a url before
+// we actually have an imap protocol instance around which can run the url. Please see my comments in
+// nsIImapMockChannel.idl for more details..
+//
+// Threading concern: This class lives entirely in the UI thread.
+
+class nsICacheEntry;
+
+class nsImapMockChannel : public nsIImapMockChannel
+ , public nsICacheEntryOpenCallback
+ , public nsITransportEventSink
+ , public nsSupportsWeakReference
+{
+public:
+ friend class nsImapProtocol;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPMOCKCHANNEL
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+
+ nsImapMockChannel();
+ static nsresult Create (const nsIID& iid, void **result);
+ nsresult RunOnStopRequestFailure();
+
+protected:
+ virtual ~nsImapMockChannel();
+ nsCOMPtr <nsIURI> m_url;
+
+ nsCOMPtr<nsIURI> m_originalUrl;
+ nsCOMPtr<nsILoadGroup> m_loadGroup;
+ nsCOMPtr<nsILoadInfo> m_loadInfo;
+ nsCOMPtr<nsIStreamListener> m_channelListener;
+ nsISupports * m_channelContext;
+ nsresult m_cancelStatus;
+ nsLoadFlags mLoadFlags;
+ nsCOMPtr<nsIProgressEventSink> mProgressEventSink;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ nsCOMPtr<nsIRequest> mCacheRequest; // the request associated with a read from the cache
+ nsCString mContentType;
+ nsCString mCharset;
+ nsWeakPtr mProtocol;
+
+ bool mChannelClosed;
+ bool mReadingFromCache;
+ bool mTryingToReadPart;
+ int64_t mContentLength;
+
+ // cache related helper methods
+ nsresult OpenCacheEntry(); // makes a request to the cache service for a cache entry for a url
+ bool ReadFromLocalCache(); // attempts to read the url out of our local (offline) cache....
+ nsresult ReadFromImapConnection(); // creates a new imap connection to read the url
+ nsresult ReadFromMemCache(nsICacheEntry *entry); // attempts to read the url out of our memory cache
+ nsresult NotifyStartEndReadFromCache(bool start);
+
+ // we end up daisy chaining multiple nsIStreamListeners into the load process.
+ nsresult SetupPartExtractorListener(nsIImapUrl * aUrl, nsIStreamListener * aConsumer);
+};
+
+// This class contains the name of a mailbox and whether or not
+// its children have been listed.
+class nsIMAPMailboxInfo
+{
+public:
+ nsIMAPMailboxInfo(const nsACString &aName, char aDelimiter);
+ virtual ~nsIMAPMailboxInfo();
+
+ void SetChildrenListed(bool childrenListed);
+ bool GetChildrenListed();
+ const nsACString& GetMailboxName();
+ char GetDelimiter();
+
+protected:
+ nsCString mMailboxName;
+ bool mChildrenListed;
+ char mDelimiter;
+};
+
+#endif // nsImapProtocol_h___
diff --git a/mailnews/imap/src/nsImapSearchResults.cpp b/mailnews/imap/src/nsImapSearchResults.cpp
new file mode 100644
index 000000000..e5e4a76be
--- /dev/null
+++ b/mailnews/imap/src/nsImapSearchResults.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // for pre-compiled headers
+
+#include "nsImapCore.h"
+#include "nsImapSearchResults.h"
+#include "prmem.h"
+#include "nsCRT.h"
+
+nsImapSearchResultSequence::nsImapSearchResultSequence()
+{
+}
+
+nsImapSearchResultSequence *nsImapSearchResultSequence::CreateSearchResultSequence()
+{
+ return new nsImapSearchResultSequence;
+}
+
+void nsImapSearchResultSequence::Clear(void)
+{
+ int32_t i = Length();
+ while (0 <= --i)
+ {
+ char* string = ElementAt(i);
+ PR_Free(string);
+ }
+ nsTArray<char*>::Clear();
+}
+
+nsImapSearchResultSequence::~nsImapSearchResultSequence()
+{
+ Clear();
+}
+
+
+void nsImapSearchResultSequence::ResetSequence()
+{
+ Clear();
+}
+
+void nsImapSearchResultSequence::AddSearchResultLine(const char *searchLine)
+{
+ // The first add becomes node 2. Fix this.
+ char *copiedSequence = PL_strdup(searchLine + 9); // 9 == "* SEARCH "
+
+ if (copiedSequence) // if we can't allocate this then the search won't hit
+ AppendElement(copiedSequence);
+}
+
+
+nsImapSearchResultIterator::nsImapSearchResultIterator(nsImapSearchResultSequence &sequence) :
+fSequence(sequence)
+{
+ ResetIterator();
+}
+
+nsImapSearchResultIterator::~nsImapSearchResultIterator()
+{
+}
+
+void nsImapSearchResultIterator::ResetIterator()
+{
+ fSequenceIndex = 0;
+ fCurrentLine = (char *) fSequence.SafeElementAt(fSequenceIndex);
+ fPositionInCurrentLine = fCurrentLine;
+}
+
+int32_t nsImapSearchResultIterator::GetNextMessageNumber()
+{
+ int32_t returnValue = 0;
+ if (fPositionInCurrentLine)
+ {
+ returnValue = atoi(fPositionInCurrentLine);
+
+ // eat the current number
+ while (isdigit(*++fPositionInCurrentLine))
+ ;
+
+ if (*fPositionInCurrentLine == 0xD) // found CR, no more digits on line
+ {
+ fCurrentLine = (char *) fSequence.SafeElementAt(++fSequenceIndex);
+ fPositionInCurrentLine = fCurrentLine;
+ }
+ else // eat the space
+ fPositionInCurrentLine++;
+ }
+
+ return returnValue;
+}
diff --git a/mailnews/imap/src/nsImapSearchResults.h b/mailnews/imap/src/nsImapSearchResults.h
new file mode 100644
index 000000000..b03268d4d
--- /dev/null
+++ b/mailnews/imap/src/nsImapSearchResults.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImapSearchResults_h___
+#define nsImapSearchResults_h___
+
+#include "nsTArray.h"
+
+class nsImapSearchResultSequence : public nsTArray<char*>
+{
+public:
+ virtual ~nsImapSearchResultSequence();
+ static nsImapSearchResultSequence *CreateSearchResultSequence();
+
+ virtual void AddSearchResultLine(const char *searchLine);
+ virtual void ResetSequence();
+ void Clear();
+
+ friend class nsImapSearchResultIterator;
+private:
+ nsImapSearchResultSequence();
+};
+
+class nsImapSearchResultIterator {
+public:
+ explicit nsImapSearchResultIterator(nsImapSearchResultSequence &sequence);
+ virtual ~nsImapSearchResultIterator();
+
+ void ResetIterator();
+ int32_t GetNextMessageNumber(); // returns 0 at end of list
+private:
+ nsImapSearchResultSequence &fSequence;
+ int32_t fSequenceIndex;
+ char *fCurrentLine;
+ char *fPositionInCurrentLine;
+};
+
+
+
+#endif
diff --git a/mailnews/imap/src/nsImapServerResponseParser.cpp b/mailnews/imap/src/nsImapServerResponseParser.cpp
new file mode 100644
index 000000000..138ee596f
--- /dev/null
+++ b/mailnews/imap/src/nsImapServerResponseParser.cpp
@@ -0,0 +1,3483 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // for pre-compiled headers
+#include "nsMimeTypes.h"
+#include "nsImapCore.h"
+#include "nsImapProtocol.h"
+#include "nsImapServerResponseParser.h"
+#include "nsIMAPBodyShell.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsIMAPNamespace.h"
+#include "nsImapStringBundle.h"
+#include "nsImapUtils.h"
+#include "nsCRT.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Logging.h"
+
+////////////////// nsImapServerResponseParser /////////////////////////
+
+extern PRLogModuleInfo* IMAP;
+
+nsImapServerResponseParser::nsImapServerResponseParser(nsImapProtocol &imapProtocolConnection)
+ : nsIMAPGenericParser(),
+ fReportingErrors(true),
+ fCurrentFolderReadOnly(false),
+ fCurrentLineContainedFlagInfo(false),
+ fServerIsNetscape3xServer(false),
+ fNumberOfUnseenMessages(0),
+ fNumberOfExistingMessages(0),
+ fNumberOfRecentMessages(0),
+ fSizeOfMostRecentMessage(0),
+ fTotalDownloadSize(0),
+ fCurrentCommandTag(nullptr),
+ fSelectedMailboxName(nullptr),
+ fIMAPstate(kNonAuthenticated),
+ fLastChunk(false),
+ fNextChunkStartsWithNewline(false),
+ fServerConnection(imapProtocolConnection),
+ fHostSessionList(nullptr)
+{
+ fSearchResults = nsImapSearchResultSequence::CreateSearchResultSequence();
+ fFolderAdminUrl = nullptr;
+ fNetscapeServerVersionString = nullptr;
+ fXSenderInfo = nullptr;
+ fSupportsUserDefinedFlags = 0;
+ fSettablePermanentFlags = 0;
+ fCapabilityFlag = kCapabilityUndefined;
+ fLastAlert = nullptr;
+ fDownloadingHeaders = false;
+ fGotPermanentFlags = false;
+ fFolderUIDValidity = 0;
+ fHighestModSeq = 0;
+ fAuthChallenge = nullptr;
+ fStatusUnseenMessages = 0;
+ fStatusRecentMessages = 0;
+ fStatusNextUID = nsMsgKey_None;
+ fStatusExistingMessages = 0;
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ fCondStoreEnabled = false;
+}
+
+nsImapServerResponseParser::~nsImapServerResponseParser()
+{
+ PR_Free( fCurrentCommandTag );
+ delete fSearchResults;
+ PR_Free( fFolderAdminUrl );
+ PR_Free( fNetscapeServerVersionString );
+ PR_Free( fXSenderInfo );
+ PR_Free( fLastAlert );
+ PR_Free( fSelectedMailboxName );
+ PR_Free(fAuthChallenge);
+
+ NS_IF_RELEASE (fHostSessionList);
+}
+
+bool nsImapServerResponseParser::LastCommandSuccessful()
+{
+ return (!CommandFailed() &&
+ !fServerConnection.DeathSignalReceived() &&
+ nsIMAPGenericParser::LastCommandSuccessful());
+}
+
+// returns true if things look ok to continue
+bool nsImapServerResponseParser::GetNextLineForParser(char **nextLine)
+{
+ bool rv = true;
+ *nextLine = fServerConnection.CreateNewLineFromSocket();
+ if (fServerConnection.DeathSignalReceived() ||
+ NS_FAILED(fServerConnection.GetConnectionStatus()))
+ rv = false;
+ // we'd really like to try to silently reconnect, but we shouldn't put this
+ // message up just in the interrupt case
+ if (NS_FAILED(fServerConnection.GetConnectionStatus()) &&
+ !fServerConnection.DeathSignalReceived())
+ fServerConnection.AlertUserEventUsingName("imapServerDisconnected");
+ return rv;
+}
+
+bool nsImapServerResponseParser::CommandFailed()
+{
+ return fCurrentCommandFailed;
+}
+
+void nsImapServerResponseParser::SetCommandFailed(bool failed)
+{
+ fCurrentCommandFailed = failed;
+}
+
+void nsImapServerResponseParser::SetFlagState(nsIImapFlagAndUidState *state)
+{
+ fFlagState = state;
+}
+
+uint32_t nsImapServerResponseParser::SizeOfMostRecentMessage()
+{
+ return fSizeOfMostRecentMessage;
+}
+
+// Call this when adding a pipelined command to the session
+void nsImapServerResponseParser::IncrementNumberOfTaggedResponsesExpected(const char *newExpectedTag)
+{
+ fNumberOfTaggedResponsesExpected++;
+ PR_Free( fCurrentCommandTag );
+ fCurrentCommandTag = PL_strdup(newExpectedTag);
+ if (!fCurrentCommandTag)
+ HandleMemoryFailure();
+}
+
+void nsImapServerResponseParser::InitializeState()
+{
+ fCurrentCommandFailed = false;
+ fNumberOfRecentMessages = 0;
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+}
+
+// RFC3501: response = *(continue-req / response-data) response-done
+// response-data = "*" SP (resp-cond-state / resp-cond-bye /
+// mailbox-data / message-data / capability-data) CRLF
+// continue-req = "+" SP (resp-text / base64) CRLF
+void nsImapServerResponseParser::ParseIMAPServerResponse(const char *aCurrentCommand,
+ bool aIgnoreBadAndNOResponses,
+ char *aGreetingWithCapability)
+{
+
+ NS_ASSERTION(aCurrentCommand && *aCurrentCommand != '\r' &&
+ *aCurrentCommand != '\n' && *aCurrentCommand != ' ', "Invailid command string");
+ bool sendingIdleDone = !strcmp(aCurrentCommand, "DONE" CRLF);
+ if (sendingIdleDone)
+ fWaitingForMoreClientInput = false;
+
+ // Reinitialize the parser
+ SetConnected(true);
+ SetSyntaxError(false);
+
+ // Reinitialize our state
+ InitializeState();
+
+ // the default is to not pipeline
+ fNumberOfTaggedResponsesExpected = 1;
+ int numberOfTaggedResponsesReceived = 0;
+
+ nsCString copyCurrentCommand(aCurrentCommand);
+ if (!fServerConnection.DeathSignalReceived())
+ {
+ char *placeInTokenString = nullptr;
+ char *tagToken = nullptr;
+ const char *commandToken = nullptr;
+ bool inIdle = false;
+ if (!sendingIdleDone)
+ {
+ placeInTokenString = copyCurrentCommand.BeginWriting();
+ tagToken = NS_strtok(WHITESPACE, &placeInTokenString);
+ commandToken = NS_strtok(WHITESPACE, &placeInTokenString);
+ }
+ else
+ commandToken = "DONE";
+ if (tagToken)
+ {
+ PR_Free( fCurrentCommandTag );
+ fCurrentCommandTag = PL_strdup(tagToken);
+ if (!fCurrentCommandTag)
+ HandleMemoryFailure();
+ inIdle = commandToken && !strcmp(commandToken, "IDLE");
+ }
+
+ if (commandToken && ContinueParse())
+ PreProcessCommandToken(commandToken, aCurrentCommand);
+
+ // For checking expected response to IDLE command below.
+ bool untagged = false;
+
+ if (ContinueParse())
+ {
+ ResetLexAnalyzer();
+
+ if (aGreetingWithCapability)
+ {
+ PR_FREEIF(fCurrentLine);
+ fCurrentLine = aGreetingWithCapability;
+ }
+
+ do {
+ AdvanceToNextToken();
+
+ // untagged responses [RFC3501, Sec. 2.2.2]
+ while (ContinueParse() && fNextToken && *fNextToken == '*')
+ {
+ response_data();
+ if (ContinueParse())
+ {
+ if (!fAtEndOfLine)
+ SetSyntaxError(true);
+ else if (!inIdle && !fCurrentCommandFailed && !aGreetingWithCapability)
+ AdvanceToNextToken();
+ }
+ untagged = true;
+ }
+
+ // command continuation request [RFC3501, Sec. 7.5]
+ if (ContinueParse() && fNextToken && *fNextToken == '+') // never pipeline APPEND or AUTHENTICATE
+ {
+ NS_ASSERTION((fNumberOfTaggedResponsesExpected - numberOfTaggedResponsesReceived) == 1,
+ " didn't get the number of tagged responses we expected");
+ numberOfTaggedResponsesReceived = fNumberOfTaggedResponsesExpected;
+ if (commandToken && !PL_strcasecmp(commandToken, "authenticate") && placeInTokenString &&
+ (!PL_strncasecmp(placeInTokenString, "CRAM-MD5", strlen("CRAM-MD5"))
+ || !PL_strncasecmp(placeInTokenString, "NTLM", strlen("NTLM"))
+ || !PL_strncasecmp(placeInTokenString, "GSSAPI", strlen("GSSAPI"))
+ || !PL_strncasecmp(placeInTokenString, "MSN", strlen("MSN"))))
+ {
+ // we need to store the challenge from the server if we are using CRAM-MD5 or NTLM.
+ authChallengeResponse_data();
+ }
+ }
+ else
+ numberOfTaggedResponsesReceived++;
+
+ if (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected)
+ response_tagged();
+
+ } while (ContinueParse() && !inIdle && (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected));
+
+ // check and see if the server is waiting for more input
+ // it's possible that we ate this + while parsing certain responses (like cram data),
+ // in these cases, the parsing routine for that specific command will manually set
+ // fWaitingForMoreClientInput so we don't lose that information....
+ if ((fNextToken && *fNextToken == '+') || inIdle)
+ {
+ if (inIdle && !((fNextToken && *fNextToken == '+') || untagged))
+ {
+ // IDLE "response" + will not be "eaten" as described above since it
+ // is not an authentication response. So if IDLE response does not
+ // begin with '+' (continuation) or '*' (untagged and probably useful
+ // response) then something is wrong and it is probably a tagged
+ // NO or BAD due to transient error or bad configuration of the server.
+ if (!PL_strcmp(fCurrentCommandTag, fNextToken))
+ {
+ response_tagged();
+ }
+ else
+ {
+ // Expected tag doesn't match the received tag. Not good, start over.
+ response_fatal();
+ }
+ // Show an alert notication containing the server response to bad IDLE.
+ fServerConnection.AlertUserEventFromServer(fCurrentLine, true);
+ }
+ else
+ {
+ fWaitingForMoreClientInput = true;
+ }
+ }
+ // if we aren't still waiting for more input....
+ else if (!fWaitingForMoreClientInput && !aGreetingWithCapability)
+ {
+ if (ContinueParse())
+ response_done();
+
+ if (ContinueParse() && !CommandFailed())
+ {
+ // a successful command may change the eIMAPstate
+ ProcessOkCommand(commandToken);
+ }
+ else if (CommandFailed())
+ {
+ // a failed command may change the eIMAPstate
+ ProcessBadCommand(commandToken);
+ if (fReportingErrors && !aIgnoreBadAndNOResponses)
+ fServerConnection.AlertUserEventFromServer(fCurrentLine, false);
+ }
+ }
+ }
+ }
+ else
+ SetConnected(false);
+}
+
+void nsImapServerResponseParser::HandleMemoryFailure()
+{
+ fServerConnection.AlertUserEventUsingName("imapOutOfMemory");
+ nsIMAPGenericParser::HandleMemoryFailure();
+}
+
+
+// SEARCH is the only command that requires pre-processing for now.
+// others will be added here.
+void nsImapServerResponseParser::PreProcessCommandToken(const char *commandToken,
+ const char *currentCommand)
+{
+ fCurrentCommandIsSingleMessageFetch = false;
+ fWaitingForMoreClientInput = false;
+
+ if (!PL_strcasecmp(commandToken, "SEARCH"))
+ fSearchResults->ResetSequence();
+ else if (!PL_strcasecmp(commandToken, "SELECT") && currentCommand)
+ {
+ // the mailbox name must be quoted, so strip the quotes
+ const char *openQuote = PL_strchr(currentCommand, '"');
+ NS_ASSERTION(openQuote, "expected open quote in imap server response");
+ if (!openQuote)
+ { // ill formed select command
+ openQuote = PL_strchr(currentCommand, ' ');
+ }
+ PR_Free( fSelectedMailboxName);
+ fSelectedMailboxName = PL_strdup(openQuote + 1);
+ if (fSelectedMailboxName)
+ {
+ // strip the escape chars and the ending quote
+ char *currentChar = fSelectedMailboxName;
+ while (*currentChar)
+ {
+ if (*currentChar == '\\')
+ {
+ PL_strcpy(currentChar, currentChar+1);
+ currentChar++; // skip what we are escaping
+ }
+ else if (*currentChar == '\"')
+ *currentChar = 0; // end quote
+ else
+ currentChar++;
+ }
+ }
+ else
+ HandleMemoryFailure();
+
+ // we don't want bogus info for this new box
+ //delete fFlagState; // not our object
+ //fFlagState = nullptr;
+ }
+ else if (!PL_strcasecmp(commandToken, "CLOSE"))
+ {
+ return; // just for debugging
+ // we don't want bogus info outside the selected state
+ //delete fFlagState; // not our object
+ //fFlagState = nullptr;
+ }
+ else if (!PL_strcasecmp(commandToken, "UID"))
+ {
+ nsCString copyCurrentCommand(currentCommand);
+ if (!fServerConnection.DeathSignalReceived())
+ {
+ char *placeInTokenString = copyCurrentCommand.BeginWriting();
+ (void) NS_strtok(WHITESPACE, &placeInTokenString); // skip tag token
+ (void) NS_strtok(WHITESPACE, &placeInTokenString); // skip uid token
+ char *fetchToken = NS_strtok(WHITESPACE, &placeInTokenString);
+ if (!PL_strcasecmp(fetchToken, "FETCH") )
+ {
+ char *uidStringToken = NS_strtok(WHITESPACE, &placeInTokenString);
+ // , and : are uid delimiters
+ if (!PL_strchr(uidStringToken, ',') && !PL_strchr(uidStringToken, ':'))
+ fCurrentCommandIsSingleMessageFetch = true;
+ }
+ }
+ }
+}
+
+const char *nsImapServerResponseParser::GetSelectedMailboxName()
+{
+ return fSelectedMailboxName;
+}
+
+nsImapSearchResultIterator *nsImapServerResponseParser::CreateSearchResultIterator()
+{
+ return new nsImapSearchResultIterator(*fSearchResults);
+}
+
+nsImapServerResponseParser::eIMAPstate nsImapServerResponseParser::GetIMAPstate()
+{
+ return fIMAPstate;
+}
+
+void nsImapServerResponseParser::PreauthSetAuthenticatedState()
+{
+ fIMAPstate = kAuthenticated;
+}
+
+void nsImapServerResponseParser::ProcessOkCommand(const char *commandToken)
+{
+ if (!PL_strcasecmp(commandToken, "LOGIN") ||
+ !PL_strcasecmp(commandToken, "AUTHENTICATE"))
+ fIMAPstate = kAuthenticated;
+ else if (!PL_strcasecmp(commandToken, "LOGOUT"))
+ fIMAPstate = kNonAuthenticated;
+ else if (!PL_strcasecmp(commandToken, "SELECT") ||
+ !PL_strcasecmp(commandToken, "EXAMINE"))
+ fIMAPstate = kFolderSelected;
+ else if (!PL_strcasecmp(commandToken, "CLOSE"))
+ {
+ fIMAPstate = kAuthenticated;
+ // we no longer have a selected mailbox.
+ PR_FREEIF( fSelectedMailboxName );
+ }
+ else if ((!PL_strcasecmp(commandToken, "LIST")) ||
+ (!PL_strcasecmp(commandToken, "LSUB")) ||
+ (!PL_strcasecmp(commandToken, "XLIST")))
+ {
+ //fServerConnection.MailboxDiscoveryFinished();
+ // This used to be reporting that we were finished
+ // discovering folders for each time we issued a
+ // LIST or LSUB. So if we explicitly listed the
+ // INBOX, or Trash, or namespaces, we would get multiple
+ // "done" states, even though we hadn't finished.
+ // Move this to be called from the connection object
+ // itself.
+ }
+ else if (!PL_strcasecmp(commandToken, "FETCH"))
+ {
+ if (!fZeroLengthMessageUidString.IsEmpty())
+ {
+ // "Deleting zero length message");
+ fServerConnection.Store(fZeroLengthMessageUidString, "+Flags (\\Deleted)", true);
+ if (LastCommandSuccessful())
+ fServerConnection.Expunge();
+
+ fZeroLengthMessageUidString.Truncate();
+ }
+ }
+ if (GetFillingInShell())
+ {
+ // There is a BODYSTRUCTURE response. Now let's generate the stream...
+ // that is, if we're not doing it already
+ if (!m_shell->IsBeingGenerated())
+ {
+ nsImapProtocol *navCon = &fServerConnection;
+
+ char *imapPart = nullptr;
+
+ fServerConnection.GetCurrentUrl()->GetImapPartToFetch(&imapPart);
+ m_shell->Generate(imapPart);
+ PR_Free(imapPart);
+
+ if ((navCon && navCon->GetPseudoInterrupted())
+ || fServerConnection.DeathSignalReceived())
+ {
+ // we were pseudointerrupted or interrupted
+ // if it's not in the cache, then we were (pseudo)interrupted while generating
+ // for the first time. Release it.
+ if (!m_shell->IsShellCached())
+ m_shell = nullptr;
+ navCon->PseudoInterrupt(false);
+ }
+ else if (m_shell->GetIsValid())
+ {
+ // If we have a valid shell that has not already been cached, then cache it.
+ if (!m_shell->IsShellCached() && fHostSessionList) // cache is responsible for destroying it
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("BODYSHELL: Adding shell to cache."));
+ const char *serverKey = fServerConnection.GetImapServerKey();
+ fHostSessionList->AddShellToCacheForHost(
+ serverKey, m_shell);
+ }
+ }
+ m_shell = nullptr;
+ }
+ }
+}
+
+void nsImapServerResponseParser::ProcessBadCommand(const char *commandToken)
+{
+ if (!PL_strcasecmp(commandToken, "LOGIN") ||
+ !PL_strcasecmp(commandToken, "AUTHENTICATE"))
+ fIMAPstate = kNonAuthenticated;
+ else if (!PL_strcasecmp(commandToken, "LOGOUT"))
+ fIMAPstate = kNonAuthenticated; // ??
+ else if (!PL_strcasecmp(commandToken, "SELECT") ||
+ !PL_strcasecmp(commandToken, "EXAMINE"))
+ fIMAPstate = kAuthenticated; // nothing selected
+ else if (!PL_strcasecmp(commandToken, "CLOSE"))
+ fIMAPstate = kAuthenticated; // nothing selected
+ if (GetFillingInShell() && !m_shell->IsBeingGenerated())
+ m_shell = nullptr;
+}
+
+
+
+// RFC3501: response-data = "*" SP (resp-cond-state / resp-cond-bye /
+// mailbox-data / message-data / capability-data) CRLF
+// These are ``untagged'' responses [RFC3501, Sec. 2.2.2]
+/*
+ The RFC1730 grammar spec did not allow one symbol look ahead to determine
+ between mailbox_data / message_data so I combined the numeric possibilities
+ of mailbox_data and all of message_data into numeric_mailbox_data.
+
+ It is assumed that the initial "*" is already consumed before calling this
+ method. The production implemented here is
+ response_data ::= (resp_cond_state / resp_cond_bye /
+ mailbox_data / numeric_mailbox_data /
+ capability_data)
+ CRLF
+*/
+void nsImapServerResponseParser::response_data()
+{
+ AdvanceToNextToken();
+
+ if (ContinueParse())
+ {
+ // Instead of comparing lots of strings and make function calls, try to
+ // pre-flight the possibilities based on the first letter of the token.
+ switch (NS_ToUpper(fNextToken[0]))
+ {
+ case 'O': // OK
+ if (NS_ToUpper(fNextToken[1]) == 'K')
+ resp_cond_state(false);
+ else SetSyntaxError(true);
+ break;
+ case 'N': // NO
+ if (NS_ToUpper(fNextToken[1]) == 'O')
+ resp_cond_state(false);
+ else if (!PL_strcasecmp(fNextToken, "NAMESPACE"))
+ namespace_data();
+ else SetSyntaxError(true);
+ break;
+ case 'B': // BAD
+ if (!PL_strcasecmp(fNextToken, "BAD"))
+ resp_cond_state(false);
+ else if (!PL_strcasecmp(fNextToken, "BYE"))
+ resp_cond_bye();
+ else SetSyntaxError(true);
+ break;
+ case 'F':
+ if (!PL_strcasecmp(fNextToken, "FLAGS"))
+ mailbox_data();
+ else SetSyntaxError(true);
+ break;
+ case 'P':
+ if (PL_strcasecmp(fNextToken, "PERMANENTFLAGS"))
+ mailbox_data();
+ else SetSyntaxError(true);
+ break;
+ case 'L':
+ if (!PL_strcasecmp(fNextToken, "LIST") || !PL_strcasecmp(fNextToken, "LSUB"))
+ mailbox_data();
+ else if (!PL_strcasecmp(fNextToken, "LANGUAGE"))
+ language_data();
+ else
+ SetSyntaxError(true);
+ break;
+ case 'M':
+ if (!PL_strcasecmp(fNextToken, "MAILBOX"))
+ mailbox_data();
+ else if (!PL_strcasecmp(fNextToken, "MYRIGHTS"))
+ myrights_data(false);
+ else SetSyntaxError(true);
+ break;
+ case 'S':
+ if (!PL_strcasecmp(fNextToken, "SEARCH"))
+ mailbox_data();
+ else if (!PL_strcasecmp(fNextToken, "STATUS"))
+ {
+ AdvanceToNextToken();
+ if (fNextToken)
+ {
+ char *mailboxName = CreateAstring();
+ PL_strfree( mailboxName);
+ }
+ while (ContinueParse() && !fAtEndOfLine)
+ {
+ AdvanceToNextToken();
+ if (!fNextToken)
+ break;
+
+ if (*fNextToken == '(') fNextToken++;
+ if (!PL_strcasecmp(fNextToken, "UIDNEXT"))
+ {
+ AdvanceToNextToken();
+ if (fNextToken)
+ {
+ fStatusNextUID = strtoul(fNextToken, nullptr, 10);
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "MESSAGES"))
+ {
+ AdvanceToNextToken();
+ if (fNextToken)
+ {
+ fStatusExistingMessages = strtoul(fNextToken, nullptr, 10);
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "UNSEEN"))
+ {
+ AdvanceToNextToken();
+ if (fNextToken)
+ {
+ fStatusUnseenMessages = strtoul(fNextToken, nullptr, 10);
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "RECENT"))
+ {
+ AdvanceToNextToken();
+ if (fNextToken)
+ {
+ fStatusRecentMessages = strtoul(fNextToken, nullptr, 10);
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ }
+ }
+ else if (*fNextToken == ')')
+ break;
+ else if (!fAtEndOfLine)
+ SetSyntaxError(true);
+ }
+ } else SetSyntaxError(true);
+ break;
+ case 'C':
+ if (!PL_strcasecmp(fNextToken, "CAPABILITY"))
+ capability_data();
+ else SetSyntaxError(true);
+ break;
+ case 'V':
+ if (!PL_strcasecmp(fNextToken, "VERSION"))
+ {
+ // figure out the version of the Netscape server here
+ PR_FREEIF(fNetscapeServerVersionString);
+ AdvanceToNextToken();
+ if (! fNextToken)
+ SetSyntaxError(true);
+ else
+ {
+ fNetscapeServerVersionString = CreateAstring();
+ AdvanceToNextToken();
+ if (fNetscapeServerVersionString)
+ {
+ fServerIsNetscape3xServer = (*fNetscapeServerVersionString == '3');
+ }
+ }
+ skip_to_CRLF();
+ }
+ else SetSyntaxError(true);
+ break;
+ case 'A':
+ if (!PL_strcasecmp(fNextToken, "ACL"))
+ {
+ acl_data();
+ }
+ else if (!PL_strcasecmp(fNextToken, "ACCOUNT-URL"))
+ {
+ fMailAccountUrl.Truncate();
+ AdvanceToNextToken();
+ if (! fNextToken)
+ SetSyntaxError(true);
+ else
+ {
+ fMailAccountUrl.Adopt(CreateAstring());
+ AdvanceToNextToken();
+ }
+ }
+ else SetSyntaxError(true);
+ break;
+ case 'E':
+ if (!PL_strcasecmp(fNextToken, "ENABLED"))
+ enable_data();
+ break;
+ case 'X':
+ if (!PL_strcasecmp(fNextToken, "XSERVERINFO"))
+ xserverinfo_data();
+ else if (!PL_strcasecmp(fNextToken, "XMAILBOXINFO"))
+ xmailboxinfo_data();
+ else if (!PL_strcasecmp(fNextToken, "XAOL-OPTION"))
+ skip_to_CRLF();
+ else if (!PL_strcasecmp(fNextToken, "XLIST"))
+ mailbox_data();
+ else
+ {
+ // check if custom command
+ nsAutoCString customCommand;
+ fServerConnection.GetCurrentUrl()->GetCommand(customCommand);
+ if (customCommand.Equals(fNextToken))
+ {
+ nsAutoCString customCommandResponse;
+ while (Connected() && !fAtEndOfLine)
+ {
+ AdvanceToNextToken();
+ customCommandResponse.Append(fNextToken);
+ customCommandResponse.Append(" ");
+ }
+ fServerConnection.GetCurrentUrl()->SetCustomCommandResult(customCommandResponse);
+ }
+ else
+ SetSyntaxError(true);
+ }
+ break;
+ case 'Q':
+ if (!PL_strcasecmp(fNextToken, "QUOTAROOT") || !PL_strcasecmp(fNextToken, "QUOTA"))
+ quota_data();
+ else
+ SetSyntaxError(true);
+ break;
+ case 'I':
+ id_data();
+ break;
+ default:
+ if (IsNumericString(fNextToken))
+ numeric_mailbox_data();
+ else
+ SetSyntaxError(true);
+ break;
+ }
+
+ if (ContinueParse())
+ PostProcessEndOfLine();
+ }
+}
+
+
+void nsImapServerResponseParser::PostProcessEndOfLine()
+{
+ // for now we only have to do one thing here
+ // a fetch response to a 'uid store' command might return the flags
+ // before it returns the uid of the message. So we need both before
+ // we report the new flag info to the front end
+
+ // also check and be sure that there was a UID in the current response
+ if (fCurrentLineContainedFlagInfo && CurrentResponseUID())
+ {
+ fCurrentLineContainedFlagInfo = false;
+ nsCString customFlags;
+ fFlagState->GetCustomFlags(CurrentResponseUID(), getter_Copies(customFlags));
+ fServerConnection.NotifyMessageFlags(fSavedFlagInfo, customFlags,
+ CurrentResponseUID(), fHighestModSeq);
+ }
+}
+
+
+/*
+ mailbox_data ::= "FLAGS" SPACE flag_list /
+ "LIST" SPACE mailbox_list /
+ "LSUB" SPACE mailbox_list /
+ "XLIST" SPACE mailbox_list /
+ "MAILBOX" SPACE text /
+ "SEARCH" [SPACE 1#nz_number] /
+ number SPACE "EXISTS" / number SPACE "RECENT"
+
+This production was changed to accomodate predictive parsing
+
+ mailbox_data ::= "FLAGS" SPACE flag_list /
+ "LIST" SPACE mailbox_list /
+ "LSUB" SPACE mailbox_list /
+ "XLIST" SPACE mailbox_list /
+ "MAILBOX" SPACE text /
+ "SEARCH" [SPACE 1#nz_number]
+*/
+void nsImapServerResponseParser::mailbox_data()
+{
+ if (!PL_strcasecmp(fNextToken, "FLAGS"))
+ {
+ // this handles the case where we got the permanent flags response
+ // before the flags response, in which case, we want to ignore thes flags.
+ if (fGotPermanentFlags)
+ skip_to_CRLF();
+ else
+ parse_folder_flags();
+ }
+ else if (!PL_strcasecmp(fNextToken, "LIST") ||
+ !PL_strcasecmp(fNextToken, "XLIST"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ mailbox_list(false);
+ }
+ else if (!PL_strcasecmp(fNextToken, "LSUB"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ mailbox_list(true);
+ }
+ else if (!PL_strcasecmp(fNextToken, "MAILBOX"))
+ skip_to_CRLF();
+ else if (!PL_strcasecmp(fNextToken, "SEARCH"))
+ {
+ fSearchResults->AddSearchResultLine(fCurrentLine);
+ fServerConnection.NotifySearchHit(fCurrentLine);
+ skip_to_CRLF();
+ }
+}
+
+/*
+ mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
+ "\Noselect" / "\Unmarked" / flag_extension) ")"
+ SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
+*/
+
+void nsImapServerResponseParser::mailbox_list(bool discoveredFromLsub)
+{
+ nsImapMailboxSpec *boxSpec = new nsImapMailboxSpec;
+ NS_ADDREF(boxSpec);
+ bool needsToFreeBoxSpec = true;
+ if (!boxSpec)
+ HandleMemoryFailure();
+ else
+ {
+ boxSpec->mFolderSelected = false;
+ boxSpec->mBoxFlags = kNoFlags;
+ boxSpec->mAllocatedPathName.Truncate();
+ boxSpec->mHostName.Truncate();
+ boxSpec->mConnection = &fServerConnection;
+ boxSpec->mFlagState = nullptr;
+ boxSpec->mDiscoveredFromLsub = discoveredFromLsub;
+ boxSpec->mOnlineVerified = true;
+ boxSpec->mBoxFlags &= ~kNameSpace;
+
+ bool endOfFlags = false;
+ fNextToken++;// eat the first "("
+ do {
+ if (!PL_strncasecmp(fNextToken, "\\Marked", 7))
+ boxSpec->mBoxFlags |= kMarked;
+ else if (!PL_strncasecmp(fNextToken, "\\Unmarked", 9))
+ boxSpec->mBoxFlags |= kUnmarked;
+ else if (!PL_strncasecmp(fNextToken, "\\Noinferiors", 12))
+ {
+ boxSpec->mBoxFlags |= kNoinferiors;
+ // RFC 5258 \Noinferiors implies \HasNoChildren
+ if (fCapabilityFlag & kHasListExtendedCapability)
+ boxSpec->mBoxFlags |= kHasNoChildren;
+ }
+ else if (!PL_strncasecmp(fNextToken, "\\Noselect", 9))
+ boxSpec->mBoxFlags |= kNoselect;
+ else if (!PL_strncasecmp(fNextToken, "\\Drafts", 7))
+ boxSpec->mBoxFlags |= kImapDrafts;
+ else if (!PL_strncasecmp(fNextToken, "\\Trash", 6))
+ boxSpec->mBoxFlags |= kImapXListTrash;
+ else if (!PL_strncasecmp(fNextToken, "\\Sent", 5))
+ boxSpec->mBoxFlags |= kImapSent;
+ else if (!PL_strncasecmp(fNextToken, "\\Spam", 5) ||
+ !PL_strncasecmp(fNextToken, "\\Junk", 5))
+ boxSpec->mBoxFlags |= kImapSpam;
+ else if (!PL_strncasecmp(fNextToken, "\\Archive", 8))
+ boxSpec->mBoxFlags |= kImapArchive;
+ else if (!PL_strncasecmp(fNextToken, "\\All", 4) ||
+ !PL_strncasecmp(fNextToken, "\\AllMail", 8))
+ boxSpec->mBoxFlags |= kImapAllMail;
+ else if (!PL_strncasecmp(fNextToken, "\\Inbox", 6))
+ boxSpec->mBoxFlags |= kImapInbox;
+ else if (!PL_strncasecmp(fNextToken, "\\NonExistent", 11))
+ {
+ boxSpec->mBoxFlags |= kNonExistent;
+ // RFC 5258 \NonExistent implies \Noselect
+ boxSpec->mBoxFlags |= kNoselect;
+ }
+ else if (!PL_strncasecmp(fNextToken, "\\Subscribed", 10))
+ boxSpec->mBoxFlags |= kSubscribed;
+ else if (!PL_strncasecmp(fNextToken, "\\Remote", 6))
+ boxSpec->mBoxFlags |= kRemote;
+ else if (!PL_strncasecmp(fNextToken, "\\HasChildren", 11))
+ boxSpec->mBoxFlags |= kHasChildren;
+ else if (!PL_strncasecmp(fNextToken, "\\HasNoChildren", 13))
+ boxSpec->mBoxFlags |= kHasNoChildren;
+ // we ignore flag other extensions
+
+ endOfFlags = *(fNextToken + strlen(fNextToken) - 1) == ')';
+ AdvanceToNextToken();
+ } while (!endOfFlags && ContinueParse());
+
+ if (ContinueParse())
+ {
+ if (*fNextToken == '"')
+ {
+ fNextToken++;
+ if (*fNextToken == '\\') // handle escaped char
+ boxSpec->mHierarchySeparator = *(fNextToken + 1);
+ else
+ boxSpec->mHierarchySeparator = *fNextToken;
+ }
+ else // likely NIL. Discovered late in 4.02 that we do not handle literals here (e.g. {10} <10 chars>), although this is almost impossibly unlikely
+ boxSpec->mHierarchySeparator = kOnlineHierarchySeparatorNil;
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ // nsImapProtocol::DiscoverMailboxSpec() eventually frees the
+ // boxSpec
+ needsToFreeBoxSpec = false;
+ mailbox(boxSpec);
+ }
+ }
+ }
+ if (needsToFreeBoxSpec)
+ NS_RELEASE(boxSpec);
+}
+
+/* mailbox ::= "INBOX" / astring
+*/
+void nsImapServerResponseParser::mailbox(nsImapMailboxSpec *boxSpec)
+{
+ char *boxname = nullptr;
+ const char *serverKey = fServerConnection.GetImapServerKey();
+ bool xlistInbox = boxSpec->mBoxFlags & kImapInbox;
+
+ if (!PL_strcasecmp(fNextToken, "INBOX") || xlistInbox)
+ {
+ boxname = PL_strdup("INBOX");
+ if (xlistInbox)
+ PR_Free(CreateAstring());
+ AdvanceToNextToken();
+ }
+ else
+ {
+ boxname = CreateAstring();
+ AdvanceToNextToken();
+ }
+
+ if (boxname && fHostSessionList)
+ {
+ // should the namespace check go before or after the Utf7 conversion?
+ fHostSessionList->SetNamespaceHierarchyDelimiterFromMailboxForHost(
+ serverKey, boxname, boxSpec->mHierarchySeparator);
+
+
+ nsIMAPNamespace *ns = nullptr;
+ fHostSessionList->GetNamespaceForMailboxForHost(serverKey, boxname, ns);
+ if (ns)
+ {
+ switch (ns->GetType())
+ {
+ case kPersonalNamespace:
+ boxSpec->mBoxFlags |= kPersonalMailbox;
+ break;
+ case kPublicNamespace:
+ boxSpec->mBoxFlags |= kPublicMailbox;
+ break;
+ case kOtherUsersNamespace:
+ boxSpec->mBoxFlags |= kOtherUsersMailbox;
+ break;
+ default: // (kUnknownNamespace)
+ break;
+ }
+ boxSpec->mNamespaceForFolder = ns;
+ }
+
+ // char *convertedName =
+ // fServerConnection.CreateUtf7ConvertedString(boxname, false);
+ // char16_t *unicharName;
+ // unicharName = fServerConnection.CreatePRUnicharStringFromUTF7(boxname);
+ // PL_strfree(boxname);
+ // boxname = convertedName;
+ }
+
+ if (!boxname)
+ {
+ if (!fServerConnection.DeathSignalReceived())
+ HandleMemoryFailure();
+ }
+ else if (boxSpec->mConnection && boxSpec->mConnection->GetCurrentUrl())
+ {
+ boxSpec->mConnection->GetCurrentUrl()->AllocateCanonicalPath(boxname, boxSpec->mHierarchySeparator,
+ getter_Copies(boxSpec->mAllocatedPathName));
+ nsIURI *aURL = nullptr;
+ boxSpec->mConnection->GetCurrentUrl()->QueryInterface(NS_GET_IID(nsIURI), (void **) &aURL);
+ if (aURL)
+ aURL->GetHost(boxSpec->mHostName);
+
+ NS_IF_RELEASE(aURL);
+ if (boxname)
+ PL_strfree( boxname);
+ // storage for the boxSpec is now owned by server connection
+ fServerConnection.DiscoverMailboxSpec(boxSpec);
+
+ // if this was cancelled by the user,then we sure don't want to
+ // send more mailboxes their way
+ if (NS_FAILED(fServerConnection.GetConnectionStatus()))
+ SetConnected(false);
+ }
+}
+
+
+/*
+ message_data ::= nz_number SPACE ("EXPUNGE" /
+ ("FETCH" SPACE msg_fetch) / msg_obsolete)
+
+was changed to
+
+numeric_mailbox_data ::= number SPACE "EXISTS" / number SPACE "RECENT"
+ / nz_number SPACE ("EXPUNGE" /
+ ("FETCH" SPACE msg_fetch) / msg_obsolete)
+
+*/
+void nsImapServerResponseParser::numeric_mailbox_data()
+{
+ int32_t tokenNumber = atoi(fNextToken);
+ AdvanceToNextToken();
+
+ if (ContinueParse())
+ {
+ if (!PL_strcasecmp(fNextToken, "FETCH"))
+ {
+ fFetchResponseIndex = tokenNumber;
+ AdvanceToNextToken();
+ if (ContinueParse())
+ msg_fetch();
+ }
+ else if (!PL_strcasecmp(fNextToken, "EXISTS"))
+ {
+ fNumberOfExistingMessages = tokenNumber;
+ AdvanceToNextToken();
+ }
+ else if (!PL_strcasecmp(fNextToken, "RECENT"))
+ {
+ fNumberOfRecentMessages = tokenNumber;
+ AdvanceToNextToken();
+ }
+ else if (!PL_strcasecmp(fNextToken, "EXPUNGE"))
+ {
+ if (!fServerConnection.GetIgnoreExpunges())
+ fFlagState->ExpungeByIndex((uint32_t) tokenNumber);
+ skip_to_CRLF();
+ }
+ else
+ msg_obsolete();
+ }
+}
+
+/*
+msg_fetch ::= "(" 1#("BODY" SPACE body /
+"BODYSTRUCTURE" SPACE body /
+"BODY[" section "]" SPACE nstring /
+"ENVELOPE" SPACE envelope /
+"FLAGS" SPACE "(" #(flag / "\Recent") ")" /
+"INTERNALDATE" SPACE date_time /
+"MODSEQ" SPACE "(" nz_number ")" /
+"RFC822" [".HEADER" / ".TEXT"] SPACE nstring /
+"RFC822.SIZE" SPACE number /
+"UID" SPACE uniqueid) ")"
+
+*/
+
+void nsImapServerResponseParser::msg_fetch()
+{
+ bool bNeedEndMessageDownload = false;
+
+ // we have not seen a uid response or flags for this fetch, yet
+ fCurrentResponseUID = 0;
+ fCurrentLineContainedFlagInfo = false;
+ fSizeOfMostRecentMessage = 0;
+ // show any incremental progress, for instance, for header downloading
+ fServerConnection.ShowProgress();
+
+ fNextToken++; // eat the '(' character
+
+ // some of these productions are ignored for now
+ while (ContinueParse() && (*fNextToken != ')') )
+ {
+ if (!PL_strcasecmp(fNextToken, "FLAGS"))
+ {
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
+
+ AdvanceToNextToken();
+ if (ContinueParse())
+ flags();
+
+ if (ContinueParse())
+ { // eat the closing ')'
+ fNextToken++;
+ // there may be another ')' to close out
+ // msg_fetch. If there is then don't advance
+ if (*fNextToken != ')')
+ AdvanceToNextToken();
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "UID"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ fCurrentResponseUID = strtoul(fNextToken, nullptr, 10);
+ if (fCurrentResponseUID > fHighestRecordedUID)
+ fHighestRecordedUID = fCurrentResponseUID;
+ // size came before UID
+ if (fSizeOfMostRecentMessage)
+ fReceivedHeaderOrSizeForUID = CurrentResponseUID();
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ char lastTokenChar = *(fNextToken + strlen(fNextToken) - 1);
+ if (lastTokenChar == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ else if (lastTokenChar < '0' || lastTokenChar > '9')
+ {
+ // GIANT HACK
+ // this is a corrupt uid - see if it's pre 5.08 Zimbra omitting
+ // a space between the UID and MODSEQ
+ if (strlen(fNextToken) > 6 &&
+ !strcmp("MODSEQ", fNextToken + strlen(fNextToken) - 6))
+ fNextToken += strlen(fNextToken) - 6;
+ }
+ else
+ AdvanceToNextToken();
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "MODSEQ"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ fNextToken++; // eat '('
+ uint64_t modSeq = ParseUint64Str(fNextToken);
+ if (modSeq > fHighestModSeq)
+ fHighestModSeq = modSeq;
+
+ if (PL_strcasestr(fNextToken, ")"))
+ {
+ // eat token chars until we get the ')'
+ fNextToken = strchr(fNextToken, ')');
+ if (fNextToken)
+ {
+ fNextToken++;
+ if (*fNextToken != ')')
+ AdvanceToNextToken();
+ }
+ else
+ SetSyntaxError(true);
+ }
+ else
+ {
+ SetSyntaxError(true);
+ }
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "RFC822") ||
+ !PL_strcasecmp(fNextToken, "RFC822.HEADER") ||
+ !PL_strncasecmp(fNextToken, "BODY[HEADER",11) ||
+ !PL_strncasecmp(fNextToken, "BODY[]", 6) ||
+ !PL_strcasecmp(fNextToken, "RFC822.TEXT") ||
+ (!PL_strncasecmp(fNextToken, "BODY[", 5) &&
+ PL_strstr(fNextToken, "HEADER"))
+ )
+ {
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
+
+ if (!PL_strcasecmp(fNextToken, "RFC822.HEADER") ||
+ !PL_strcasecmp(fNextToken, "BODY[HEADER]"))
+ {
+ // all of this message's headers
+ AdvanceToNextToken();
+ fDownloadingHeaders = true;
+ BeginMessageDownload(MESSAGE_RFC822); // initialize header parser
+ bNeedEndMessageDownload = false;
+ if (ContinueParse())
+ msg_fetch_headers(nullptr);
+ }
+ else if (!PL_strncasecmp(fNextToken, "BODY[HEADER.FIELDS",19))
+ {
+ fDownloadingHeaders = true;
+ BeginMessageDownload(MESSAGE_RFC822); // initialize header parser
+ // specific message header fields
+ while (ContinueParse() && fNextToken[strlen(fNextToken)-1] != ']')
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ bNeedEndMessageDownload = false;
+ AdvanceToNextToken();
+ if (ContinueParse())
+ msg_fetch_headers(nullptr);
+ }
+ }
+ else
+ {
+ char *whereHeader = PL_strstr(fNextToken, "HEADER");
+ if (whereHeader)
+ {
+ const char *startPartNum = fNextToken + 5;
+ if (whereHeader > startPartNum)
+ {
+ int32_t partLength = whereHeader - startPartNum - 1; //-1 for the dot!
+ char *partNum = (char *)PR_CALLOC((partLength + 1) * sizeof (char));
+ if (partNum)
+ {
+ PL_strncpy(partNum, startPartNum, partLength);
+ if (ContinueParse())
+ {
+ if (PL_strstr(fNextToken, "FIELDS"))
+ {
+ while (ContinueParse() && fNextToken[strlen(fNextToken)-1] != ']')
+ AdvanceToNextToken();
+ }
+ if (ContinueParse())
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ msg_fetch_headers(partNum);
+ }
+ }
+ PR_Free(partNum);
+ }
+ }
+ else
+ SetSyntaxError(true);
+ }
+ else
+ {
+ fDownloadingHeaders = false;
+
+ bool chunk = false;
+ int32_t origin = 0;
+ if (!PL_strncasecmp(fNextToken, "BODY[]<", 7))
+ {
+ char *tokenCopy = 0;
+ tokenCopy = PL_strdup(fNextToken);
+ if (tokenCopy)
+ {
+ char *originString = tokenCopy + 7; // where the byte number starts
+ char *closeBracket = PL_strchr(tokenCopy,'>');
+ if (closeBracket && originString && *originString)
+ {
+ *closeBracket = 0;
+ origin = atoi(originString);
+ chunk = true;
+ }
+ PR_Free(tokenCopy);
+ }
+ }
+
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ msg_fetch_content(chunk, origin, MESSAGE_RFC822);
+ }
+ }
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "RFC822.SIZE") || !PL_strcasecmp(fNextToken, "XAOL.SIZE"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ bool sendEndMsgDownload = (GetDownloadingHeaders()
+ && fReceivedHeaderOrSizeForUID == CurrentResponseUID());
+ fSizeOfMostRecentMessage = strtoul(fNextToken, nullptr, 10);
+ fReceivedHeaderOrSizeForUID = CurrentResponseUID();
+ if (sendEndMsgDownload)
+ {
+ fServerConnection.NormalMessageEndDownload();
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ }
+
+ if (fSizeOfMostRecentMessage == 0 && CurrentResponseUID())
+ {
+ // on no, bogus Netscape 2.0 mail server bug
+ char uidString[100];
+ sprintf(uidString, "%ld", (long)CurrentResponseUID());
+
+ if (!fZeroLengthMessageUidString.IsEmpty())
+ fZeroLengthMessageUidString += ",";
+
+ fZeroLengthMessageUidString += uidString;
+ }
+
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ else
+ AdvanceToNextToken();
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "XSENDER"))
+ {
+ PR_FREEIF(fXSenderInfo);
+ AdvanceToNextToken();
+ if (! fNextToken)
+ SetSyntaxError(true);
+ else
+ {
+ fXSenderInfo = CreateAstring();
+ AdvanceToNextToken();
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "X-GM-MSGID"))
+ {
+ AdvanceToNextToken();
+ if (!fNextToken)
+ SetSyntaxError(true);
+ else
+ {
+ fMsgID = CreateAtom();
+ AdvanceToNextToken();
+ nsCString msgIDValue;
+ msgIDValue.Assign(fMsgID);
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
+ fFlagState->SetCustomAttribute(fCurrentResponseUID,
+ NS_LITERAL_CSTRING("X-GM-MSGID"), msgIDValue);
+ PR_FREEIF(fMsgID);
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "X-GM-THRID"))
+ {
+ AdvanceToNextToken();
+ if (!fNextToken)
+ SetSyntaxError(true);
+ else
+ {
+ fThreadID = CreateAtom();
+ AdvanceToNextToken();
+ nsCString threadIDValue;
+ threadIDValue.Assign(fThreadID);
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
+ fFlagState->SetCustomAttribute(fCurrentResponseUID,
+ NS_LITERAL_CSTRING("X-GM-THRID"), threadIDValue);
+ PR_FREEIF(fThreadID);
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "X-GM-LABELS"))
+ {
+ AdvanceToNextToken();
+ if (!fNextToken)
+ SetSyntaxError(true);
+ else
+ {
+ fLabels = CreateParenGroup();
+ nsCString labelsValue;
+ labelsValue.Assign(fLabels);
+ labelsValue.Cut(0, 1);
+ labelsValue.Cut(labelsValue.Length()-1, 1);
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
+ fFlagState->SetCustomAttribute(fCurrentResponseUID,
+ NS_LITERAL_CSTRING("X-GM-LABELS"), labelsValue);
+ PR_FREEIF(fLabels);
+ }
+ }
+
+ // I only fetch RFC822 so I should never see these BODY responses
+ else if (!PL_strcasecmp(fNextToken, "BODY"))
+ skip_to_CRLF(); // I never ask for this
+ else if (!PL_strcasecmp(fNextToken, "BODYSTRUCTURE"))
+ {
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
+ bodystructure_data();
+ }
+ else if (!PL_strncasecmp(fNextToken, "BODY[TEXT", 9))
+ {
+ mime_data();
+ }
+ else if (!PL_strncasecmp(fNextToken, "BODY[", 5) && PL_strncasecmp(fNextToken, "BODY[]", 6))
+ {
+ fDownloadingHeaders = false;
+ // A specific MIME part, or MIME part header
+ mime_data();
+ }
+ else if (!PL_strcasecmp(fNextToken, "ENVELOPE"))
+ {
+ fDownloadingHeaders = true;
+ bNeedEndMessageDownload = true;
+ BeginMessageDownload(MESSAGE_RFC822);
+ envelope_data();
+ }
+ else if (!PL_strcasecmp(fNextToken, "INTERNALDATE"))
+ {
+ fDownloadingHeaders = true; // we only request internal date while downloading headers
+ if (!bNeedEndMessageDownload)
+ BeginMessageDownload(MESSAGE_RFC822);
+ bNeedEndMessageDownload = true;
+ internal_date();
+ }
+ else if (!PL_strcasecmp(fNextToken, "XAOL-ENVELOPE"))
+ {
+ fDownloadingHeaders = true;
+ if (!bNeedEndMessageDownload)
+ BeginMessageDownload(MESSAGE_RFC822);
+ bNeedEndMessageDownload = true;
+ xaolenvelope_data();
+ }
+ else
+ {
+ nsImapAction imapAction;
+ if (!fServerConnection.GetCurrentUrl())
+ return;
+ fServerConnection.GetCurrentUrl()->GetImapAction(&imapAction);
+ nsAutoCString userDefinedFetchAttribute;
+ fServerConnection.GetCurrentUrl()->GetCustomAttributeToFetch(userDefinedFetchAttribute);
+ if ((imapAction == nsIImapUrl::nsImapUserDefinedFetchAttribute && !strcmp(userDefinedFetchAttribute.get(), fNextToken)) ||
+ imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand)
+ {
+ AdvanceToNextToken();
+ char *fetchResult;
+ if (fNextToken[0] == '(')
+ // look through the tokens until we find the closing ')'
+ // we can have a result like the following:
+ // ((A B) (C D) (E F))
+ fetchResult = CreateParenGroup();
+ else {
+ fetchResult = CreateAstring();
+ AdvanceToNextToken();
+ }
+ if (imapAction == nsIImapUrl::nsImapUserDefinedFetchAttribute)
+ fServerConnection.GetCurrentUrl()->SetCustomAttributeResult(nsDependentCString(fetchResult));
+ if (imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand)
+ fServerConnection.GetCurrentUrl()->SetCustomCommandResult(nsDependentCString(fetchResult));
+ PR_Free(fetchResult);
+ }
+ else
+ SetSyntaxError(true);
+ }
+
+ }
+
+ if (ContinueParse())
+ {
+ if (CurrentResponseUID() && CurrentResponseUID() != nsMsgKey_None
+ && fCurrentLineContainedFlagInfo && fFlagState)
+ {
+ fFlagState->AddUidFlagPair(CurrentResponseUID(), fSavedFlagInfo, fFetchResponseIndex - 1);
+ for (uint32_t i = 0; i < fCustomFlags.Length(); i++)
+ fFlagState->AddUidCustomFlagPair(CurrentResponseUID(), fCustomFlags[i].get());
+ fCustomFlags.Clear();
+ }
+
+ if (fFetchingAllFlags)
+ fCurrentLineContainedFlagInfo = false; // do not fire if in PostProcessEndOfLine
+
+ AdvanceToNextToken(); // eat the ')' ending token
+ // should be at end of line
+ if (bNeedEndMessageDownload)
+ {
+ if (ContinueParse())
+ {
+ // complete the message download
+ fServerConnection.NormalMessageEndDownload();
+ }
+ else
+ fServerConnection.AbortMessageDownLoad();
+ }
+
+ }
+}
+
+typedef enum _envelopeItemType
+{
+ envelopeString,
+ envelopeAddress
+} envelopeItemType;
+
+typedef struct
+{
+ const char * name;
+ envelopeItemType type;
+} envelopeItem;
+
+// RFC3501: envelope = "(" env-date SP env-subject SP env-from SP
+// env-sender SP env-reply-to SP env-to SP env-cc SP
+// env-bcc SP env-in-reply-to SP env-message-id ")"
+// env-date = nstring
+// env-subject = nstring
+// env-from = "(" 1*address ")" / nil
+// env-sender = "(" 1*address ")" / nil
+// env-reply-to= "(" 1*address ")" / nil
+// env-to = "(" 1*address ")" / nil
+// env-cc = "(" 1*address ")" / nil
+// env-bcc = "(" 1*address ")" / nil
+// env-in-reply-to = nstring
+// env-message-id = nstring
+
+static const envelopeItem EnvelopeTable[] =
+{
+ {"Date", envelopeString},
+ {"Subject", envelopeString},
+ {"From", envelopeAddress},
+ {"Sender", envelopeAddress},
+ {"Reply-to", envelopeAddress},
+ {"To", envelopeAddress},
+ {"Cc", envelopeAddress},
+ {"Bcc", envelopeAddress},
+ {"In-reply-to", envelopeString},
+ {"Message-id", envelopeString}
+};
+
+void nsImapServerResponseParser::envelope_data()
+{
+ AdvanceToNextToken();
+ fNextToken++; // eat '('
+ for (int tableIndex = 0; tableIndex < (int)(sizeof(EnvelopeTable) / sizeof(EnvelopeTable[0])); tableIndex++)
+ {
+ if (!ContinueParse())
+ break;
+ else if (*fNextToken == ')')
+ {
+ SetSyntaxError(true); // envelope too short
+ break;
+ }
+ else
+ {
+ nsAutoCString headerLine(EnvelopeTable[tableIndex].name);
+ headerLine += ": ";
+ bool headerNonNil = true;
+ if (EnvelopeTable[tableIndex].type == envelopeString)
+ {
+ nsAutoCString strValue;
+ strValue.Adopt(CreateNilString());
+ if (!strValue.IsEmpty())
+ headerLine.Append(strValue);
+ else
+ headerNonNil = false;
+ }
+ else
+ {
+ nsAutoCString address;
+ parse_address(address);
+ headerLine += address;
+ if (address.IsEmpty())
+ headerNonNil = false;
+ }
+ if (headerNonNil)
+ fServerConnection.HandleMessageDownLoadLine(headerLine.get(), false);
+ }
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+ // Now we should be at the end of the envelope and have *fToken == ')'.
+ // Skip this last parenthesis.
+ AdvanceToNextToken();
+}
+
+void nsImapServerResponseParser::xaolenvelope_data()
+{
+ // eat the opening '('
+ fNextToken++;
+
+ if (ContinueParse() && (*fNextToken != ')'))
+ {
+ AdvanceToNextToken();
+ fNextToken++; // eat '('
+ nsAutoCString subject;
+ subject.Adopt(CreateNilString());
+ nsAutoCString subjectLine("Subject: ");
+ subjectLine += subject;
+ fServerConnection.HandleMessageDownLoadLine(subjectLine.get(), false);
+ fNextToken++; // eat the next '('
+ if (ContinueParse())
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ nsAutoCString fromLine;
+ if (!strcmp(GetSelectedMailboxName(), "Sent Items"))
+ {
+ // xaol envelope switches the From with the To, so we switch them back and
+ // create a fake from line From: user@aol.com
+ fromLine.Append("To: ");
+ nsAutoCString fakeFromLine(NS_LITERAL_CSTRING("From: "));
+ fakeFromLine.Append(fServerConnection.GetImapUserName());
+ fakeFromLine.Append(NS_LITERAL_CSTRING("@aol.com"));
+ fServerConnection.HandleMessageDownLoadLine(fakeFromLine.get(), false);
+ }
+ else
+ {
+ fromLine.Append("From: ");
+ }
+ parse_address(fromLine);
+ fServerConnection.HandleMessageDownLoadLine(fromLine.get(), false);
+ if (ContinueParse())
+ {
+ AdvanceToNextToken(); // ge attachment size
+ int32_t attachmentSize = atoi(fNextToken);
+ if (attachmentSize != 0)
+ {
+ nsAutoCString attachmentLine("X-attachment-size: ");
+ attachmentLine.AppendInt(attachmentSize);
+ fServerConnection.HandleMessageDownLoadLine(attachmentLine.get(), false);
+ }
+ }
+ if (ContinueParse())
+ {
+ AdvanceToNextToken(); // skip image size
+ int32_t imageSize = atoi(fNextToken);
+ if (imageSize != 0)
+ {
+ nsAutoCString imageLine("X-image-size: ");
+ imageLine.AppendInt(imageSize);
+ fServerConnection.HandleMessageDownLoadLine(imageLine.get(), false);
+ }
+ }
+ if (ContinueParse())
+ AdvanceToNextToken(); // skip )
+ }
+ }
+ }
+}
+
+void nsImapServerResponseParser::parse_address(nsAutoCString &addressLine)
+{
+ if (!strcmp(fNextToken, "NIL"))
+ return;
+ bool firstAddress = true;
+ // should really look at chars here
+ NS_ASSERTION(*fNextToken == '(', "address should start with '('");
+ fNextToken++; // eat the next '('
+ while (ContinueParse() && *fNextToken == '(')
+ {
+ NS_ASSERTION(*fNextToken == '(', "address should start with '('");
+ fNextToken++; // eat the next '('
+
+ if (!firstAddress)
+ addressLine += ", ";
+
+ firstAddress = false;
+ char *personalName = CreateNilString();
+ AdvanceToNextToken();
+ char *atDomainList = CreateNilString();
+ if (ContinueParse())
+ {
+ AdvanceToNextToken();
+ char *mailboxName = CreateNilString();
+ if (ContinueParse())
+ {
+ AdvanceToNextToken();
+ char *hostName = CreateNilString();
+ AdvanceToNextToken();
+ addressLine += mailboxName;
+ if (hostName)
+ {
+ addressLine += '@';
+ addressLine += hostName;
+ NS_Free(hostName);
+ }
+ if (personalName)
+ {
+ addressLine += " (";
+ addressLine += personalName;
+ addressLine += ')';
+ }
+ }
+ }
+ PR_Free(personalName);
+ PR_Free(atDomainList);
+
+ if (*fNextToken == ')')
+ fNextToken++;
+ // if the next token isn't a ')' for the address term,
+ // then we must have another address pair left....so get the next
+ // token and continue parsing in this loop...
+ if ( *fNextToken == '\0' )
+ AdvanceToNextToken();
+
+ }
+ if (*fNextToken == ')')
+ fNextToken++;
+ // AdvanceToNextToken(); // skip "))"
+}
+
+void nsImapServerResponseParser::internal_date()
+{
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ nsAutoCString dateLine("Date: ");
+ char *strValue = CreateNilString();
+ if (strValue)
+ {
+ dateLine += strValue;
+ NS_Free(strValue);
+ }
+ fServerConnection.HandleMessageDownLoadLine(dateLine.get(), false);
+ }
+ // advance the parser.
+ AdvanceToNextToken();
+}
+
+void nsImapServerResponseParser::flags()
+{
+ imapMessageFlagsType messageFlags = kNoImapMsgFlag;
+ fCustomFlags.Clear();
+
+ // clear the custom flags for this message
+ // otherwise the old custom flags will stay around
+ // see bug #191042
+ if (fFlagState && CurrentResponseUID() != nsMsgKey_None)
+ fFlagState->ClearCustomFlags(CurrentResponseUID());
+
+ // eat the opening '('
+ fNextToken++;
+ while (ContinueParse() && (*fNextToken != ')'))
+ {
+ bool knownFlag = false;
+ if (*fNextToken == '\\')
+ {
+ switch (NS_ToUpper(fNextToken[1])) {
+ case 'S':
+ if (!PL_strncasecmp(fNextToken, "\\Seen",5))
+ {
+ messageFlags |= kImapMsgSeenFlag;
+ knownFlag = true;
+ }
+ break;
+ case 'A':
+ if (!PL_strncasecmp(fNextToken, "\\Answered",9))
+ {
+ messageFlags |= kImapMsgAnsweredFlag;
+ knownFlag = true;
+ }
+ break;
+ case 'F':
+ if (!PL_strncasecmp(fNextToken, "\\Flagged",8))
+ {
+ messageFlags |= kImapMsgFlaggedFlag;
+ knownFlag = true;
+ }
+ break;
+ case 'D':
+ if (!PL_strncasecmp(fNextToken, "\\Deleted",8))
+ {
+ messageFlags |= kImapMsgDeletedFlag;
+ knownFlag = true;
+ }
+ else if (!PL_strncasecmp(fNextToken, "\\Draft",6))
+ {
+ messageFlags |= kImapMsgDraftFlag;
+ knownFlag = true;
+ }
+ break;
+ case 'R':
+ if (!PL_strncasecmp(fNextToken, "\\Recent",7))
+ {
+ messageFlags |= kImapMsgRecentFlag;
+ knownFlag = true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ else if (*fNextToken == '$')
+ {
+ switch (NS_ToUpper(fNextToken[1])) {
+ case 'M':
+ if ((fSupportsUserDefinedFlags & (kImapMsgSupportUserFlag |
+ kImapMsgSupportMDNSentFlag))
+ && !PL_strncasecmp(fNextToken, "$MDNSent",8))
+ {
+ messageFlags |= kImapMsgMDNSentFlag;
+ knownFlag = true;
+ }
+ break;
+ case 'F':
+ if ((fSupportsUserDefinedFlags & (kImapMsgSupportUserFlag |
+ kImapMsgSupportForwardedFlag))
+ && !PL_strncasecmp(fNextToken, "$Forwarded",10))
+ {
+ messageFlags |= kImapMsgForwardedFlag;
+ knownFlag = true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (!knownFlag && fFlagState)
+ {
+ nsAutoCString flag(fNextToken);
+ int32_t parenIndex = flag.FindChar(')');
+ if (parenIndex > 0)
+ flag.SetLength(parenIndex);
+ messageFlags |= kImapMsgCustomKeywordFlag;
+ if (CurrentResponseUID() != nsMsgKey_None && CurrentResponseUID() != 0)
+ fFlagState->AddUidCustomFlagPair(CurrentResponseUID(), flag.get());
+ else
+ fCustomFlags.AppendElement(flag);
+ }
+ if (PL_strcasestr(fNextToken, ")"))
+ {
+ // eat token chars until we get the ')'
+ while (*fNextToken != ')')
+ fNextToken++;
+ }
+ else
+ AdvanceToNextToken();
+ }
+
+ if (ContinueParse())
+ while(*fNextToken != ')')
+ fNextToken++;
+
+ fCurrentLineContainedFlagInfo = true; // handled in PostProcessEndOfLine
+ fSavedFlagInfo = messageFlags;
+}
+
+// RFC3501: resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
+// ; Status condition
+void nsImapServerResponseParser::resp_cond_state(bool isTagged)
+{
+ // According to RFC3501, Sec. 7.1, the untagged NO response "indicates a
+ // warning; the command can still complete successfully."
+ // However, the untagged BAD response "indicates a protocol-level error for
+ // which the associated command can not be determined; it can also indicate an
+ // internal server failure."
+ // Thus, we flag an error for a tagged NO response and for any BAD response.
+ if ((isTagged && !PL_strcasecmp(fNextToken, "NO")) ||
+ !PL_strcasecmp(fNextToken, "BAD"))
+ fCurrentCommandFailed = true;
+
+ AdvanceToNextToken();
+ if (ContinueParse())
+ resp_text();
+}
+
+/*
+resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text)
+
+ was changed to in order to enable a one symbol look ahead predictive
+ parser.
+
+ resp_text ::= ["[" resp_text_code SPACE] (text_mime2 / text)
+*/
+void nsImapServerResponseParser::resp_text()
+{
+ if (ContinueParse() && (*fNextToken == '['))
+ resp_text_code();
+
+ if (ContinueParse())
+ {
+ if (!PL_strcmp(fNextToken, "=?"))
+ text_mime2();
+ else
+ text();
+ }
+}
+/*
+ text_mime2 ::= "=?" <charset> "?" <encoding> "?"
+ <encoded-text> "?="
+ ;; Syntax defined in [MIME-2]
+*/
+void nsImapServerResponseParser::text_mime2()
+{
+ skip_to_CRLF();
+}
+
+/*
+ text ::= 1*TEXT_CHAR
+
+*/
+void nsImapServerResponseParser::text()
+{
+ skip_to_CRLF();
+}
+
+void nsImapServerResponseParser::parse_folder_flags()
+{
+ uint16_t labelFlags = 0;
+
+ do
+ {
+ AdvanceToNextToken();
+ if (*fNextToken == '(')
+ fNextToken++;
+ if (!PL_strncasecmp(fNextToken, "$MDNSent", 8))
+ fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
+ else if (!PL_strncasecmp(fNextToken, "$Forwarded", 10))
+ fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
+ else if (!PL_strncasecmp(fNextToken, "\\Seen", 5))
+ fSettablePermanentFlags |= kImapMsgSeenFlag;
+ else if (!PL_strncasecmp(fNextToken, "\\Answered", 9))
+ fSettablePermanentFlags |= kImapMsgAnsweredFlag;
+ else if (!PL_strncasecmp(fNextToken, "\\Flagged", 8))
+ fSettablePermanentFlags |= kImapMsgFlaggedFlag;
+ else if (!PL_strncasecmp(fNextToken, "\\Deleted", 8))
+ fSettablePermanentFlags |= kImapMsgDeletedFlag;
+ else if (!PL_strncasecmp(fNextToken, "\\Draft", 6))
+ fSettablePermanentFlags |= kImapMsgDraftFlag;
+ else if (!PL_strncasecmp(fNextToken, "$Label1", 7))
+ labelFlags |= 1;
+ else if (!PL_strncasecmp(fNextToken, "$Label2", 7))
+ labelFlags |= 2;
+ else if (!PL_strncasecmp(fNextToken, "$Label3", 7))
+ labelFlags |= 4;
+ else if (!PL_strncasecmp(fNextToken, "$Label4", 7))
+ labelFlags |= 8;
+ else if (!PL_strncasecmp(fNextToken, "$Label5", 7))
+ labelFlags |= 16;
+ else if (!PL_strncasecmp(fNextToken, "\\*", 2))
+ {
+ fSupportsUserDefinedFlags |= kImapMsgSupportUserFlag;
+ fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
+ fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
+ fSupportsUserDefinedFlags |= kImapMsgLabelFlags;
+ }
+ } while (!fAtEndOfLine && ContinueParse());
+
+ if (labelFlags == 31)
+ fSupportsUserDefinedFlags |= kImapMsgLabelFlags;
+
+ if (fFlagState)
+ fFlagState->OrSupportedUserFlags(fSupportsUserDefinedFlags);
+}
+/*
+ resp_text_code ::= ("ALERT" / "PARSE" /
+ "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
+ "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
+ "UIDVALIDITY" SPACE nz_number /
+ "UNSEEN" SPACE nz_number /
+ "HIGHESTMODSEQ" SPACE nz_number /
+ "NOMODSEQ" /
+ atom [SPACE 1*<any TEXT_CHAR except "]">] )
+ "]"
+
+
+*/
+void nsImapServerResponseParser::resp_text_code()
+{
+ // this is a special case way of advancing the token
+ // strtok won't break up "[ALERT]" into separate tokens
+ if (strlen(fNextToken) > 1)
+ fNextToken++;
+ else
+ AdvanceToNextToken();
+
+ if (ContinueParse())
+ {
+ if (!PL_strcasecmp(fNextToken,"ALERT]"))
+ {
+ char *alertMsg = fCurrentTokenPlaceHolder; // advance past ALERT]
+ if (alertMsg && *alertMsg && (!fLastAlert || PL_strcmp(fNextToken, fLastAlert)))
+ {
+ fServerConnection.AlertUserEvent(alertMsg);
+ PR_Free(fLastAlert);
+ fLastAlert = PL_strdup(alertMsg);
+ }
+ AdvanceToNextToken();
+ }
+ else if (!PL_strcasecmp(fNextToken,"PARSE]"))
+ {
+ // do nothing for now
+ AdvanceToNextToken();
+ }
+ else if (!PL_strcasecmp(fNextToken,"NETSCAPE]"))
+ {
+ skip_to_CRLF();
+ }
+ else if (!PL_strcasecmp(fNextToken,"PERMANENTFLAGS"))
+ {
+ uint32_t saveSettableFlags = fSettablePermanentFlags;
+ fSupportsUserDefinedFlags = 0; // assume no unless told
+ fSettablePermanentFlags = 0; // assume none, unless told otherwise.
+ parse_folder_flags();
+ // if the server tells us there are no permanent flags, we're
+ // just going to pretend that the FLAGS response flags, if any, are
+ // permanent in case the server is broken. This will allow us
+ // to store delete and seen flag changes - if they're not permanent,
+ // they're not permanent, but at least we'll try to set them.
+ if (!fSettablePermanentFlags)
+ fSettablePermanentFlags = saveSettableFlags;
+ fGotPermanentFlags = true;
+ }
+ else if (!PL_strcasecmp(fNextToken,"READ-ONLY]"))
+ {
+ fCurrentFolderReadOnly = true;
+ AdvanceToNextToken();
+ }
+ else if (!PL_strcasecmp(fNextToken,"READ-WRITE]"))
+ {
+ fCurrentFolderReadOnly = false;
+ AdvanceToNextToken();
+ }
+ else if (!PL_strcasecmp(fNextToken,"TRYCREATE]"))
+ {
+ // do nothing for now
+ AdvanceToNextToken();
+ }
+ else if (!PL_strcasecmp(fNextToken,"UIDVALIDITY"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ fFolderUIDValidity = strtoul(fNextToken, nullptr, 10);
+ fHighestRecordedUID = 0;
+ AdvanceToNextToken();
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken,"UNSEEN"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ fNumberOfUnseenMessages = strtoul(fNextToken, nullptr, 10);
+ AdvanceToNextToken();
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken,"UIDNEXT"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ fStatusNextUID = strtoul(fNextToken, nullptr, 10);
+ AdvanceToNextToken();
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "APPENDUID"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ // ** jt -- the returned uidvalidity is the destination folder
+ // uidvalidity; don't use it for current folder
+ // fFolderUIDValidity = atoi(fNextToken);
+ // fHighestRecordedUID = 0; ??? this should be wrong
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ fCurrentResponseUID = strtoul(fNextToken, nullptr, 10);
+ AdvanceToNextToken();
+ }
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "COPYUID"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ // ** jt -- destination folder uidvalidity
+ // fFolderUIDValidity = atoi(fNextToken);
+ // original message set; ignore it
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ // the resulting message set; should be in the form of
+ // either uid or uid1:uid2
+ AdvanceToNextToken();
+ // clear copy response uid
+ fServerConnection.SetCopyResponseUid(fNextToken);
+ }
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "HIGHESTMODSEQ"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ fHighestModSeq = ParseUint64Str(fNextToken);
+ fUseModSeq = true;
+ AdvanceToNextToken();
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken, "NOMODSEQ]"))
+ {
+ fHighestModSeq = 0;
+ fUseModSeq = false;
+ skip_to_CRLF();
+ }
+ else if (!PL_strcasecmp(fNextToken, "CAPABILITY"))
+ {
+ capability_data();
+ }
+ else if (!PL_strcasecmp(fNextToken, "MYRIGHTS"))
+ {
+ myrights_data(true);
+ }
+ else // just text
+ {
+ // do nothing but eat tokens until we see the ] or CRLF
+ // we should see the ] but we don't want to go into an
+ // endless loop if the CRLF is not there
+ do
+ {
+ AdvanceToNextToken();
+ } while (!PL_strcasestr(fNextToken, "]") && !fAtEndOfLine
+ && ContinueParse());
+ }
+ }
+}
+
+// RFC3501: response-done = response-tagged / response-fatal
+ void nsImapServerResponseParser::response_done()
+ {
+ if (ContinueParse())
+ {
+ if (!PL_strcmp(fCurrentCommandTag, fNextToken))
+ response_tagged();
+ else
+ response_fatal();
+ }
+ }
+
+// RFC3501: response-tagged = tag SP resp-cond-state CRLF
+ void nsImapServerResponseParser::response_tagged()
+ {
+ // eat the tag
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ resp_cond_state(true);
+ if (ContinueParse())
+ {
+ if (!fAtEndOfLine)
+ SetSyntaxError(true);
+ else if (!fCurrentCommandFailed)
+ ResetLexAnalyzer();
+ }
+ }
+ }
+
+// RFC3501: response-fatal = "*" SP resp-cond-bye CRLF
+// ; Server closes connection immediately
+ void nsImapServerResponseParser::response_fatal()
+ {
+ // eat the "*"
+ AdvanceToNextToken();
+ if (ContinueParse())
+ resp_cond_bye();
+ }
+
+// RFC3501: resp-cond-bye = "BYE" SP resp-text
+void nsImapServerResponseParser::resp_cond_bye()
+{
+ SetConnected(false);
+ fIMAPstate = kNonAuthenticated;
+}
+
+
+void nsImapServerResponseParser::msg_fetch_headers(const char *partNum)
+{
+ if (GetFillingInShell())
+ {
+ char *headerData = CreateAstring();
+ AdvanceToNextToken();
+ m_shell->AdoptMessageHeaders(headerData, partNum);
+ }
+ else
+ {
+ msg_fetch_content(false, 0, MESSAGE_RFC822);
+ }
+}
+
+
+/* nstring ::= string / nil
+string ::= quoted / literal
+nil ::= "NIL"
+
+*/
+void nsImapServerResponseParser::msg_fetch_content(bool chunk, int32_t origin, const char *content_type)
+{
+ // setup the stream for downloading this message.
+ // Don't do it if we are filling in a shell or downloading a part.
+ // DO do it if we are downloading a whole message as a result of
+ // an invalid shell trying to generate.
+ if ((!chunk || (origin == 0)) && !GetDownloadingHeaders() &&
+ (GetFillingInShell() ? m_shell->GetGeneratingWholeMessage() : true))
+ {
+ if (NS_FAILED(BeginMessageDownload(content_type)))
+ return;
+ }
+
+ if (PL_strcasecmp(fNextToken, "NIL"))
+ {
+ if (*fNextToken == '"')
+ fLastChunk = msg_fetch_quoted();
+ else
+ fLastChunk = msg_fetch_literal(chunk, origin);
+ }
+ else
+ AdvanceToNextToken(); // eat "NIL"
+
+ if (fLastChunk && (GetFillingInShell() ? m_shell->GetGeneratingWholeMessage() : true))
+ {
+ // complete the message download
+ if (ContinueParse())
+ {
+ if (fReceivedHeaderOrSizeForUID == CurrentResponseUID())
+ {
+ fServerConnection.NormalMessageEndDownload();
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ }
+ else
+ fReceivedHeaderOrSizeForUID = CurrentResponseUID();
+ }
+ else
+ fServerConnection.AbortMessageDownLoad();
+ }
+}
+
+
+/*
+quoted ::= <"> *QUOTED_CHAR <">
+
+ QUOTED_CHAR ::= <any TEXT_CHAR except quoted_specials> /
+ "\" quoted_specials
+
+ quoted_specials ::= <"> / "\"
+*/
+
+bool nsImapServerResponseParser::msg_fetch_quoted()
+{
+ // *Should* never get a quoted string in response to a chunked download,
+ // but the RFCs don't forbid it
+ char *q = CreateQuoted();
+ if (q)
+ {
+ numberOfCharsInThisChunk = PL_strlen(q);
+ fServerConnection.HandleMessageDownLoadLine(q, false, q);
+ PR_Free(q);
+ }
+ else
+ numberOfCharsInThisChunk = 0;
+
+ AdvanceToNextToken();
+ bool lastChunk = ((fServerConnection.GetCurFetchSize() == 0) ||
+ (numberOfCharsInThisChunk != fServerConnection.GetCurFetchSize()));
+ return lastChunk;
+}
+
+/* msg_obsolete ::= "COPY" / ("STORE" SPACE msg_fetch)
+;; OBSOLETE untagged data responses */
+void nsImapServerResponseParser::msg_obsolete()
+{
+ if (!PL_strcasecmp(fNextToken, "COPY"))
+ AdvanceToNextToken();
+ else if (!PL_strcasecmp(fNextToken, "STORE"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ msg_fetch();
+ }
+ else
+ SetSyntaxError(true);
+}
+
+void nsImapServerResponseParser::capability_data()
+{
+ int32_t endToken = -1;
+ fCapabilityFlag = kCapabilityDefined | kHasAuthOldLoginCapability;
+ do {
+ AdvanceToNextToken();
+ if (fNextToken) {
+ nsCString token(fNextToken);
+ endToken = token.FindChar(']');
+ if (endToken >= 0)
+ token.SetLength(endToken);
+
+ if(token.Equals("AUTH=LOGIN", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasAuthLoginCapability;
+ else if(token.Equals("AUTH=PLAIN", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasAuthPlainCapability;
+ else if (token.Equals("AUTH=CRAM-MD5", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasCRAMCapability;
+ else if (token.Equals("AUTH=NTLM", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasAuthNTLMCapability;
+ else if (token.Equals("AUTH=GSSAPI", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasAuthGssApiCapability;
+ else if (token.Equals("AUTH=MSN", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasAuthMSNCapability;
+ else if (token.Equals("AUTH=EXTERNAL", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasAuthExternalCapability;
+#ifdef MOZ_MAILNEWS_OAUTH2
+ else if (token.Equals("AUTH=XOAUTH2", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasXOAuth2Capability;
+#endif
+ else if (token.Equals("STARTTLS", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasStartTLSCapability;
+ else if (token.Equals("LOGINDISABLED", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag &= ~kHasAuthOldLoginCapability; // remove flag
+ else if (token.Equals("XSENDER", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasXSenderCapability;
+ else if (token.Equals("IMAP4", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kIMAP4Capability;
+ else if (token.Equals("IMAP4rev1", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kIMAP4rev1Capability;
+ else if (Substring(token,0,5).Equals("IMAP4", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kIMAP4other;
+ else if (token.Equals("X-NO-ATOMIC-RENAME", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kNoHierarchyRename;
+ else if (token.Equals("X-NON-HIERARCHICAL-RENAME", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kNoHierarchyRename;
+ else if (token.Equals("NAMESPACE", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kNamespaceCapability;
+ else if (token.Equals("ID", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasIDCapability;
+ else if (token.Equals("ACL", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kACLCapability;
+ else if (token.Equals("XSERVERINFO", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kXServerInfoCapability;
+ else if (token.Equals("UIDPLUS", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kUidplusCapability;
+ else if (token.Equals("LITERAL+", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kLiteralPlusCapability;
+ else if (token.Equals("XAOL-OPTION", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kAOLImapCapability;
+ else if (token.Equals("X-GM-EXT-1", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kGmailImapCapability;
+ else if (token.Equals("QUOTA", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kQuotaCapability;
+ else if (token.Equals("LANGUAGE", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasLanguageCapability;
+ else if (token.Equals("IDLE", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasIdleCapability;
+ else if (token.Equals("CONDSTORE", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasCondStoreCapability;
+ else if (token.Equals("ENABLE", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasEnableCapability;
+ else if (token.Equals("LIST-EXTENDED", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasListExtendedCapability;
+ else if (token.Equals("XLIST", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasXListCapability;
+ else if (token.Equals("SPECIAL-USE", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasSpecialUseCapability;
+ else if (token.Equals("COMPRESS=DEFLATE", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasCompressDeflateCapability;
+ else if (token.Equals("MOVE", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasMoveCapability;
+ else if (token.Equals("HIGHESTMODSEQ", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasHighestModSeqCapability;
+ }
+ } while (fNextToken && endToken < 0 && !fAtEndOfLine && ContinueParse());
+
+ nsImapProtocol *navCon = &fServerConnection;
+ NS_ASSERTION(navCon, "null imap protocol connection while parsing capability response"); // we should always have this
+ if (navCon)
+ navCon->CommitCapability();
+ skip_to_CRLF();
+}
+
+void nsImapServerResponseParser::xmailboxinfo_data()
+{
+ AdvanceToNextToken();
+ if (!fNextToken)
+ return;
+
+ char *mailboxName = CreateAstring(); // PL_strdup(fNextToken);
+ if (mailboxName)
+ {
+ do
+ {
+ AdvanceToNextToken();
+ if (fNextToken)
+ {
+ if (!PL_strcmp("MANAGEURL", fNextToken))
+ {
+ AdvanceToNextToken();
+ fFolderAdminUrl = CreateAstring();
+ }
+ else if (!PL_strcmp("POSTURL", fNextToken))
+ {
+ AdvanceToNextToken();
+ // ignore this for now...
+ }
+ }
+ } while (fNextToken && !fAtEndOfLine && ContinueParse());
+ }
+}
+
+void nsImapServerResponseParser::xserverinfo_data()
+{
+ do
+ {
+ AdvanceToNextToken();
+ if (!fNextToken)
+ break;
+ if (!PL_strcmp("MANAGEACCOUNTURL", fNextToken))
+ {
+ AdvanceToNextToken();
+ fMailAccountUrl.Adopt(CreateNilString());
+ }
+ else if (!PL_strcmp("MANAGELISTSURL", fNextToken))
+ {
+ AdvanceToNextToken();
+ fManageListsUrl.Adopt(CreateNilString());
+ }
+ else if (!PL_strcmp("MANAGEFILTERSURL", fNextToken))
+ {
+ AdvanceToNextToken();
+ fManageFiltersUrl.Adopt(CreateNilString());
+ }
+ } while (fNextToken && !fAtEndOfLine && ContinueParse());
+}
+
+void nsImapServerResponseParser::enable_data()
+{
+ do
+ {
+ // eat each enable response;
+ AdvanceToNextToken();
+ if (!strcmp("CONDSTORE", fNextToken))
+ fCondStoreEnabled = true;
+ } while (fNextToken && !fAtEndOfLine && ContinueParse());
+
+}
+
+void nsImapServerResponseParser::language_data()
+{
+ // we may want to go out and store the language returned to us
+ // by the language command in the host info session stuff.
+
+ // for now, just eat the language....
+ do
+ {
+ // eat each language returned to us
+ AdvanceToNextToken();
+ } while (fNextToken && !fAtEndOfLine && ContinueParse());
+}
+
+// cram/auth response data ::= "+" SPACE challenge CRLF
+// the server expects more client data after issuing its challenge
+
+void nsImapServerResponseParser::authChallengeResponse_data()
+{
+ AdvanceToNextToken();
+ fAuthChallenge = strdup(fNextToken);
+ fWaitingForMoreClientInput = true;
+
+ skip_to_CRLF();
+}
+
+
+void nsImapServerResponseParser::namespace_data()
+{
+ EIMAPNamespaceType namespaceType = kPersonalNamespace;
+ bool namespacesCommitted = false;
+ const char* serverKey = fServerConnection.GetImapServerKey();
+ while ((namespaceType != kUnknownNamespace) && ContinueParse())
+ {
+ AdvanceToNextToken();
+ while (fAtEndOfLine && ContinueParse())
+ AdvanceToNextToken();
+ if (!PL_strcasecmp(fNextToken,"NIL"))
+ {
+ // No namespace for this type.
+ // Don't add anything to the Namespace object.
+ }
+ else if (fNextToken[0] == '(')
+ {
+ // There may be multiple namespaces of the same type.
+ // Go through each of them and add them to our Namespace object.
+
+ fNextToken++;
+ while (fNextToken[0] == '(' && ContinueParse())
+ {
+ // we have another namespace for this namespace type
+ fNextToken++;
+ if (fNextToken[0] != '"')
+ {
+ SetSyntaxError(true);
+ }
+ else
+ {
+ char *namespacePrefix = CreateQuoted(false);
+
+ AdvanceToNextToken();
+ const char *quotedDelimiter = fNextToken;
+ char namespaceDelimiter = '\0';
+
+ if (quotedDelimiter[0] == '"')
+ {
+ quotedDelimiter++;
+ namespaceDelimiter = quotedDelimiter[0];
+ }
+ else if (!PL_strncasecmp(quotedDelimiter, "NIL", 3))
+ {
+ // NIL hierarchy delimiter. Leave namespace delimiter nullptr.
+ }
+ else
+ {
+ // not quoted or NIL.
+ SetSyntaxError(true);
+ }
+ if (ContinueParse())
+ {
+ // add code to parse the TRANSLATE attribute if it is present....
+ // we'll also need to expand the name space code to take in the translated prefix name.
+
+ nsIMAPNamespace *newNamespace = new nsIMAPNamespace(namespaceType, namespacePrefix, namespaceDelimiter, false);
+ // add it to a temporary list in the host
+ if (newNamespace && fHostSessionList)
+ fHostSessionList->AddNewNamespaceForHost(
+ serverKey, newNamespace);
+
+ skip_to_close_paren(); // Ignore any extension data
+
+ bool endOfThisNamespaceType = (fNextToken[0] == ')');
+ if (!endOfThisNamespaceType && fNextToken[0] != '(') // no space between namespaces of the same type
+ {
+ SetSyntaxError(true);
+ }
+ }
+ PR_Free(namespacePrefix);
+ }
+ }
+ }
+ else
+ {
+ SetSyntaxError(true);
+ }
+ switch (namespaceType)
+ {
+ case kPersonalNamespace:
+ namespaceType = kOtherUsersNamespace;
+ break;
+ case kOtherUsersNamespace:
+ namespaceType = kPublicNamespace;
+ break;
+ default:
+ namespaceType = kUnknownNamespace;
+ break;
+ }
+ }
+ if (ContinueParse())
+ {
+ nsImapProtocol *navCon = &fServerConnection;
+ NS_ASSERTION(navCon, "null protocol connection while parsing namespace"); // we should always have this
+ if (navCon)
+ {
+ navCon->CommitNamespacesForHostEvent();
+ namespacesCommitted = true;
+ }
+ }
+ skip_to_CRLF();
+
+ if (!namespacesCommitted && fHostSessionList)
+ {
+ bool success;
+ fHostSessionList->FlushUncommittedNamespacesForHost(serverKey,
+ success);
+ }
+}
+
+void nsImapServerResponseParser::myrights_data(bool unsolicited)
+{
+ AdvanceToNextToken();
+ if (ContinueParse() && !fAtEndOfLine)
+ {
+ char *mailboxName;
+ // an unsolicited myrights response won't have the mailbox name in
+ // the response, so we use the selected mailbox name.
+ if (unsolicited)
+ {
+ mailboxName = strdup(fSelectedMailboxName);
+ }
+ else
+ {
+ mailboxName = CreateAstring();
+ if (mailboxName)
+ AdvanceToNextToken();
+ }
+ if (mailboxName)
+ {
+ if (ContinueParse())
+ {
+ char *myrights = CreateAstring();
+ if (myrights)
+ {
+ nsImapProtocol *navCon = &fServerConnection;
+ NS_ASSERTION(navCon, "null connection parsing my rights"); // we should always have this
+ if (navCon)
+ navCon->AddFolderRightsForUser(mailboxName, nullptr /* means "me" */, myrights);
+ PR_Free(myrights);
+ }
+ else
+ {
+ HandleMemoryFailure();
+ }
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+ PR_Free(mailboxName);
+ }
+ else
+ {
+ HandleMemoryFailure();
+ }
+ }
+ else
+ {
+ SetSyntaxError(true);
+ }
+}
+
+void nsImapServerResponseParser::acl_data()
+{
+ AdvanceToNextToken();
+ if (ContinueParse() && !fAtEndOfLine)
+ {
+ char *mailboxName = CreateAstring(); // PL_strdup(fNextToken);
+ if (mailboxName && ContinueParse())
+ {
+ AdvanceToNextToken();
+ while (ContinueParse() && !fAtEndOfLine)
+ {
+ char *userName = CreateAstring(); // PL_strdup(fNextToken);
+ if (userName && ContinueParse())
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ char *rights = CreateAstring(); // PL_strdup(fNextToken);
+ if (rights)
+ {
+ fServerConnection.AddFolderRightsForUser(mailboxName, userName, rights);
+ PR_Free(rights);
+ }
+ else
+ HandleMemoryFailure();
+
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+ PR_Free(userName);
+ }
+ else
+ HandleMemoryFailure();
+ }
+ PR_Free(mailboxName);
+ }
+ else
+ HandleMemoryFailure();
+ }
+}
+
+
+void nsImapServerResponseParser::mime_data()
+{
+ if (PL_strstr(fNextToken, "MIME"))
+ mime_header_data();
+ else
+ mime_part_data();
+}
+
+// mime_header_data should not be streamed out; rather, it should be
+// buffered in the nsIMAPBodyShell.
+// This is because we are still in the process of generating enough
+// information from the server (such as the MIME header's size) so that
+// we can construct the final output stream.
+void nsImapServerResponseParser::mime_header_data()
+{
+ char *partNumber = PL_strdup(fNextToken);
+ if (partNumber)
+ {
+ char *start = partNumber + 5, *end = partNumber + 5; // 5 == strlen("BODY[")
+ while (ContinueParse() && end && *end != 'M' && *end != 'm')
+ {
+ end++;
+ }
+ if (end && (*end == 'M' || *end == 'm'))
+ {
+ *(end-1) = 0;
+ AdvanceToNextToken();
+ char *mimeHeaderData = CreateAstring(); // is it really this simple?
+ AdvanceToNextToken();
+ if (m_shell)
+ {
+ m_shell->AdoptMimeHeader(start, mimeHeaderData);
+ }
+ }
+ else
+ {
+ SetSyntaxError(true);
+ }
+ PR_Free(partNumber); // partNumber is not adopted by the body shell.
+ }
+ else
+ {
+ HandleMemoryFailure();
+ }
+}
+
+// Actual mime parts are filled in on demand (either from shell generation
+// or from explicit user download), so we need to stream these out.
+void nsImapServerResponseParser::mime_part_data()
+{
+ char *checkOriginToken = PL_strdup(fNextToken);
+ if (checkOriginToken)
+ {
+ uint32_t origin = 0;
+ bool originFound = false;
+ char *whereStart = PL_strchr(checkOriginToken, '<');
+ if (whereStart)
+ {
+ char *whereEnd = PL_strchr(whereStart, '>');
+ if (whereEnd)
+ {
+ *whereEnd = 0;
+ whereStart++;
+ origin = atoi(whereStart);
+ originFound = true;
+ }
+ }
+ PR_Free(checkOriginToken);
+ AdvanceToNextToken();
+ msg_fetch_content(originFound, origin, MESSAGE_RFC822); // keep content type as message/rfc822, even though the
+ // MIME part might not be, because then libmime will
+ // still handle and decode it.
+ }
+ else
+ HandleMemoryFailure();
+}
+
+// parse FETCH BODYSTRUCTURE response, "a parenthesized list that describes
+// the [MIME-IMB] body structure of a message" [RFC 3501].
+void nsImapServerResponseParser::bodystructure_data()
+{
+ AdvanceToNextToken();
+ if (ContinueParse() && fNextToken && *fNextToken == '(') // It has to start with an open paren.
+ {
+ // Turn the BODYSTRUCTURE response into a form that the nsIMAPBodypartMessage can be constructed from.
+ // FIXME: Follow up on bug 384210 to investigate why the caller has to duplicate the two in-param strings.
+ nsIMAPBodypartMessage *message =
+ new nsIMAPBodypartMessage(NULL, NULL, true, strdup("message"),
+ strdup("rfc822"),
+ NULL, NULL, NULL, 0,
+ fServerConnection.GetPreferPlainText());
+ nsIMAPBodypart *body = bodystructure_part(PL_strdup("1"), message);
+ if (body)
+ message->SetBody(body);
+ else
+ {
+ delete message;
+ message = nullptr;
+ }
+ m_shell = new nsIMAPBodyShell(&fServerConnection, message, CurrentResponseUID(), GetSelectedMailboxName());
+ // ignore syntax errors in parsing the body structure response. If there's an error
+ // we'll just fall back to fetching the whole message.
+ SetSyntaxError(false);
+ }
+ else
+ SetSyntaxError(true);
+}
+
+// RFC3501: body = "(" (body-type-1part / body-type-mpart) ")"
+nsIMAPBodypart *
+nsImapServerResponseParser::bodystructure_part(char *partNum, nsIMAPBodypart *parentPart)
+{
+ // Check to see if this buffer is a leaf or container
+ // (Look at second character - if an open paren, then it is a container)
+ if (*fNextToken != '(')
+ {
+ NS_ASSERTION(false, "bodystructure_part must begin with '('");
+ return NULL;
+ }
+
+ if (fNextToken[1] == '(')
+ return bodystructure_multipart(partNum, parentPart);
+ else
+ return bodystructure_leaf(partNum, parentPart);
+}
+
+// RFC3501: body-type-1part = (body-type-basic / body-type-msg / body-type-text)
+// [SP body-ext-1part]
+nsIMAPBodypart *
+nsImapServerResponseParser::bodystructure_leaf(char *partNum, nsIMAPBodypart *parentPart)
+{
+ // historical note: this code was originally in nsIMAPBodypartLeaf::ParseIntoObjects()
+ char *bodyType = nullptr, *bodySubType = nullptr, *bodyID = nullptr, *bodyDescription = nullptr, *bodyEncoding = nullptr;
+ int32_t partLength = 0;
+ bool isValid = true;
+
+ // body type ("application", "text", "image", etc.)
+ if (ContinueParse())
+ {
+ fNextToken++; // eat the first '('
+ bodyType = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // body subtype ("gif", "html", etc.)
+ if (isValid && ContinueParse())
+ {
+ bodySubType = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // body parameter: parenthesized list
+ if (isValid && ContinueParse())
+ {
+ if (fNextToken[0] == '(')
+ {
+ fNextToken++;
+ skip_to_close_paren();
+ }
+ else if (!PL_strcasecmp(fNextToken, "NIL"))
+ AdvanceToNextToken();
+ }
+
+ // body id
+ if (isValid && ContinueParse())
+ {
+ bodyID = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // body description
+ if (isValid && ContinueParse())
+ {
+ bodyDescription = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // body encoding
+ if (isValid && ContinueParse())
+ {
+ bodyEncoding = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // body size
+ if (isValid && ContinueParse())
+ {
+ char *bodySizeString = CreateAtom();
+ if (!bodySizeString)
+ isValid = false;
+ else
+ {
+ partLength = atoi(bodySizeString);
+ PR_Free(bodySizeString);
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+ }
+
+ if (!isValid || !ContinueParse())
+ {
+ PR_FREEIF(partNum);
+ PR_FREEIF(bodyType);
+ PR_FREEIF(bodySubType);
+ PR_FREEIF(bodyID);
+ PR_FREEIF(bodyDescription);
+ PR_FREEIF(bodyEncoding);
+ }
+ else
+ {
+ if (PL_strcasecmp(bodyType, "message") || PL_strcasecmp(bodySubType, "rfc822"))
+ {
+ skip_to_close_paren();
+ return new nsIMAPBodypartLeaf(partNum, parentPart, bodyType, bodySubType,
+ bodyID, bodyDescription, bodyEncoding,
+ partLength,
+ fServerConnection.GetPreferPlainText());
+ }
+
+ // This part is of type "message/rfc822" (probably a forwarded message)
+ nsIMAPBodypartMessage *message =
+ new nsIMAPBodypartMessage(partNum, parentPart, false,
+ bodyType, bodySubType, bodyID, bodyDescription,
+ bodyEncoding, partLength,
+ fServerConnection.GetPreferPlainText());
+
+ // there are three additional fields: envelope structure, bodystructure, and size in lines
+ // historical note: this code was originally in nsIMAPBodypartMessage::ParseIntoObjects()
+
+ // envelope (ignored)
+ if (*fNextToken == '(')
+ {
+ fNextToken++;
+ skip_to_close_paren();
+ }
+ else
+ isValid = false;
+
+ // bodystructure
+ if (isValid && ContinueParse())
+ {
+ if (*fNextToken != '(')
+ isValid = false;
+ else
+ {
+ char *bodyPartNum = PR_smprintf("%s.1", partNum);
+ if (bodyPartNum)
+ {
+ nsIMAPBodypart *body = bodystructure_part(bodyPartNum, message);
+ if (body)
+ message->SetBody(body);
+ else
+ isValid = false;
+ }
+ }
+ }
+
+ // ignore "size in text lines"
+
+ if (isValid && ContinueParse()) {
+ skip_to_close_paren();
+ return message;
+ }
+ delete message;
+ }
+
+ // parsing failed, just move to the end of the parentheses group
+ if (ContinueParse())
+ skip_to_close_paren();
+ return nullptr;
+}
+
+
+// RFC3501: body-type-mpart = 1*body SP media-subtype
+// [SP body-ext-mpart]
+nsIMAPBodypart *
+nsImapServerResponseParser::bodystructure_multipart(char *partNum, nsIMAPBodypart *parentPart)
+{
+ nsIMAPBodypartMultipart *multipart = new nsIMAPBodypartMultipart(partNum, parentPart);
+ bool isValid = multipart->GetIsValid();
+ // historical note: this code was originally in nsIMAPBodypartMultipart::ParseIntoObjects()
+ if (ContinueParse())
+ {
+ fNextToken++; // eat the first '('
+ // Parse list of children
+ int childCount = 0;
+ while (isValid && fNextToken[0] == '(' && ContinueParse())
+ {
+ childCount++;
+ char *childPartNum = NULL;
+ // note: the multipart constructor does some magic on partNumber
+ if (PL_strcmp(multipart->GetPartNumberString(), "0")) // not top-level
+ childPartNum = PR_smprintf("%s.%d", multipart->GetPartNumberString(), childCount);
+ else // top-level
+ childPartNum = PR_smprintf("%d", childCount);
+ if (!childPartNum)
+ isValid = false;
+ else
+ {
+ nsIMAPBodypart *child = bodystructure_part(childPartNum, multipart);
+ if (child)
+ multipart->AppendPart(child);
+ else
+ isValid = false;
+ }
+ }
+
+ // RFC3501: media-subtype = string
+ // (multipart subtype: mixed, alternative, etc.)
+ if (isValid && ContinueParse())
+ {
+ char *bodySubType = CreateNilString();
+ multipart->SetBodySubType(bodySubType);
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // extension data:
+ // RFC3501: body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang
+ // [SP body-fld-loc *(SP body-extension)]]]
+
+ // body parameter parenthesized list (optional data), includes boundary parameter
+ // RFC3501: body-fld-param = "(" string SP string *(SP string SP string) ")" / nil
+ char *boundaryData = nullptr;
+ if (isValid && ContinueParse() && *fNextToken == '(')
+ {
+ fNextToken++;
+ while (ContinueParse() && *fNextToken != ')')
+ {
+ char *attribute = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ if (ContinueParse() && !PL_strcasecmp(attribute, "BOUNDARY"))
+ {
+ char *boundary = CreateNilString();
+ if (boundary)
+ boundaryData = PR_smprintf("--%s", boundary);
+ PR_FREEIF(boundary);
+ }
+ else if (ContinueParse())
+ {
+ char *value = CreateNilString();
+ PR_FREEIF(value);
+ }
+ PR_FREEIF(attribute);
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+ if (ContinueParse())
+ fNextToken++; // skip closing ')'
+ }
+ if (boundaryData)
+ multipart->SetBoundaryData(boundaryData);
+ else
+ isValid = false; // Actually, we should probably generate a boundary here.
+ }
+
+ // always move to closing ')', even if part was not successfully read
+ if (ContinueParse())
+ skip_to_close_paren();
+
+ if (isValid)
+ return multipart;
+ delete multipart;
+ return nullptr;
+}
+
+
+// RFC2087: quotaroot_response = "QUOTAROOT" SP astring *(SP astring)
+// quota_response = "QUOTA" SP astring SP quota_list
+// quota_list = "(" [quota_resource *(SP quota_resource)] ")"
+// quota_resource = atom SP number SP number
+// Only the STORAGE resource is considered. The current implementation is
+// slightly broken because it assumes that STORAGE is the first resource;
+// a reponse QUOTA (MESSAGE 5 100 STORAGE 10 512) would be ignored.
+void nsImapServerResponseParser::quota_data()
+{
+ if (!PL_strcasecmp(fNextToken, "QUOTAROOT"))
+ {
+ // ignore QUOTAROOT response
+ nsCString quotaroot;
+ AdvanceToNextToken();
+ while (ContinueParse() && !fAtEndOfLine)
+ {
+ quotaroot.Adopt(CreateAstring());
+ AdvanceToNextToken();
+ }
+ }
+ else if(!PL_strcasecmp(fNextToken, "QUOTA"))
+ {
+ uint32_t used, max;
+ char *parengroup;
+
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ nsCString quotaroot;
+ quotaroot.Adopt(CreateAstring());
+
+ if(ContinueParse() && !fAtEndOfLine)
+ {
+ AdvanceToNextToken();
+ if(fNextToken)
+ {
+ if(!PL_strcasecmp(fNextToken, "(STORAGE"))
+ {
+ parengroup = CreateParenGroup();
+ if(parengroup && (PR_sscanf(parengroup, "(STORAGE %lu %lu)", &used, &max) == 2) )
+ {
+ fServerConnection.UpdateFolderQuotaData(quotaroot, used, max);
+ skip_to_CRLF();
+ }
+ else
+ SetSyntaxError(true);
+
+ PR_Free(parengroup);
+ }
+ else
+ // Ignore other limits, we just check STORAGE for now
+ skip_to_CRLF();
+ }
+ else
+ SetSyntaxError(true);
+ }
+ else
+ HandleMemoryFailure();
+ }
+ }
+ else
+ SetSyntaxError(true);
+}
+
+void nsImapServerResponseParser::id_data()
+{
+ AdvanceToNextToken();
+ if (!PL_strcasecmp(fNextToken, "NIL"))
+ AdvanceToNextToken();
+ else
+ fServerIdResponse.Adopt(CreateParenGroup());
+ skip_to_CRLF();
+}
+
+bool nsImapServerResponseParser::GetFillingInShell()
+{
+ return (m_shell != nullptr);
+}
+
+bool nsImapServerResponseParser::GetDownloadingHeaders()
+{
+ return fDownloadingHeaders;
+}
+
+// Tells the server state parser to use a previously cached shell.
+void nsImapServerResponseParser::UseCachedShell(nsIMAPBodyShell *cachedShell)
+{
+ // We shouldn't already have another shell we're dealing with.
+ if (m_shell && cachedShell)
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("PARSER: Shell Collision"));
+ NS_ASSERTION(false, "shell collision");
+ }
+ m_shell = cachedShell;
+}
+
+
+void nsImapServerResponseParser::ResetCapabilityFlag()
+{
+}
+
+/*
+ literal ::= "{" number "}" CRLF *CHAR8
+ Number represents the number of CHAR8 octets
+ */
+
+// Processes a message body, header or message part fetch response. Typically
+// the full message, header or part are proccessed in one call (effectively, one
+// chunk), and parameter `chunk` is false and `origin` (offset into the
+// response) is 0. But under some conditions and larger messages, multiple calls
+// will occur to process the message in multiple chunks and parameter `chunk`
+// will be true and parameter `origin` will increase by the chunk size from
+// initially 0 with each call. This function returns true if this is the last or
+// only chunk. This signals the caller that the stream should be closed since
+// the message response has been processed.
+bool nsImapServerResponseParser::msg_fetch_literal(bool chunk, int32_t origin)
+{
+ numberOfCharsInThisChunk = atoi(fNextToken + 1);
+ // If we didn't request a specific size, or the server isn't returning exactly
+ // as many octets as we requested, this must be the last or only chunk
+ bool lastChunk = (!chunk ||
+ (numberOfCharsInThisChunk != fServerConnection.GetCurFetchSize()));
+
+ if (lastChunk)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: msg_fetch_literal() chunking=%s, requested=%d, receiving=%d",
+ chunk ? "true":"false", fServerConnection.GetCurFetchSize(),
+ numberOfCharsInThisChunk));
+
+ charsReadSoFar = 0;
+
+ while (ContinueParse() && !fServerConnection.DeathSignalReceived() && (charsReadSoFar < numberOfCharsInThisChunk))
+ {
+ AdvanceToNextLine();
+ if (ContinueParse())
+ {
+ // When "\r\n" (CRLF) is split across two chunks, the '\n' at the beginning of
+ // the next chunk might be set to an empty line consisting only of "\r\n".
+ // This is observed while running unit tests with the imap "fake" server.
+ // The unexpected '\r' is discarded here. However, with several real world
+ // servers tested, e.g., Dovecot, Gmail, Outlook, Yahoo etc., the leading
+ // '\r' is not inserted so the beginning line of the next chunk remains
+ // just '\n' and no discard is required.
+ // In any case, this "orphan" line is ignored and not processed below.
+ if (fNextChunkStartsWithNewline && (*fCurrentLine == '\r'))
+ {
+ // Cause fCurrentLine to point to '\n' which discards the '\r'.
+ char *usableCurrentLine = PL_strdup(fCurrentLine + 1);
+ PR_Free(fCurrentLine);
+ fCurrentLine = usableCurrentLine;
+ }
+
+ // strlen() *would* fail on data containing \0, but the above AdvanceToNextLine() in
+ // nsMsgLineStreamBuffer::ReadNextLine() we replace '\0' with ' ' (blank) because
+ // who cares about binary transparency, and anyway \0 in this context violates RFCs.
+ charsReadSoFar += strlen(fCurrentLine);
+ if (!fDownloadingHeaders && fCurrentCommandIsSingleMessageFetch)
+ {
+ fServerConnection.ProgressEventFunctionUsingName("imapDownloadingMessage");
+ if (fTotalDownloadSize > 0)
+ fServerConnection.PercentProgressUpdateEvent(0, charsReadSoFar + origin, fTotalDownloadSize);
+ }
+ if (charsReadSoFar > numberOfCharsInThisChunk)
+ {
+ // This is the last line of a chunk. "Literal" here means actual email data and
+ // its EOLs, without imap protocol elements and their EOLs. End of line is
+ // defined by two characters \r\n (i.e., CRLF, 0xd,0xa) specified by RFC822.
+ // Here is an example the most typical last good line of a chunk:
+ // "1s8AA5i4AAvF4QAG6+sAAD0bAPsAAAAA1OAAC)\r\n", where ")\r\n" are non-literals.
+ // This an example of the last "good" line of a chunk that terminates with \r\n
+ // "FxcA/wAAAALN2gADu80ACS0nAPpVVAD1wNAABF5YAPhAJgD31+QABAAAAP8oMQD+HBwA/umj\r\n"
+ // followed by another line of non-literal data:
+ // " UID 1004)\r\n". These two are concatenated into a single string pointed to
+ // by fCurrentLine. The extra "non-literal data" on the last chunk line makes
+ // the charsReadSoFar greater than numberOfCharsInThisChunk (the configured
+ // chunk size).
+ // A problem occurs if the \r\n of the long line above is split between
+ // chunks and \n is contained in the next chunk. For example, if last
+ // lines of chunk X are:
+ // "/gAOC/wA/QAAAAAAAAAA8wACCvz+AgIEAAD8/P4ABQUAAPoAAAD+AAEA/voHAAQGBQD/BAQA\r"
+ // ")\r\n"
+ // and the first two lines of chunk X+1 are:
+ // "\n"
+ // "APwAAAAAmZkA/wAAAAAREQD/AAAAAquVAAbk8QAHCBAAAPD0AAP5+wABRCoA+0BgAP0AAAAA\r\n"
+ // The missing '\n' on the last line of chunk X must be added back and the
+ // line consisting only of "\n" in chunk X+1 must be ignored in order to
+ // produce the the correct output. This is needed to insure that the signature
+ // verification of cryptographically signed emails does not fail due to missing
+ // or extra EOL characters. Otherwise, the extra or missing \n or \r doesn't
+ // really matter.
+ //
+ // Special case observed only with the "fake" imap server used with TB
+ // unit test. When the "\r\n" at the end of a chunk is split as described
+ // above, the \n at the beginning of the next chunk may actually be "\r\n"
+ // like this example:
+ // Last lines of chunk X
+ // "/gAOC/wA/QAAAAAAAAAA8wACCvz+AgIEAAD8/P4ABQUAAPoAAAD+AAEA/voHAAQGBQD/BAQA\r"
+ // ")\r\n"
+ // and the first two lines of chunk X+1:
+ // "\r\n" <-- The code changes this to just "\n" like it should be.
+ // "APwAAAAAmZkA/wAAAAAREQD/AAAAAquVAAbk8QAHCBAAAPD0AAP5+wABRCoA+0BgAP0AAAAA\r\n"
+ //
+ // Implementation:
+ // Obtain pointer to last literal in chunk X, e.g., 'C' in 1st example above,
+ // or to the \n or \r in the other examples.
+ char *displayEndOfLine =
+ (fCurrentLine + strlen(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk + 1));
+ // Save so original unmodified fCurrentLine is restored below.
+ char saveit1 = displayEndOfLine[1];
+ char saveit2 = 0; // Keep compiler happy.
+ // Determine if EOL is split such that Chunk X has the \r and chunk
+ // X+1 has the \n.
+ fNextChunkStartsWithNewline = (displayEndOfLine[0] == '\r');
+ if (fNextChunkStartsWithNewline)
+ {
+ saveit2 = displayEndOfLine[2];
+ // Add the missing newline and terminate the string.
+ displayEndOfLine[1] = '\n';
+ displayEndOfLine[2] = 0;
+ // This is a good thing to log.
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("PARSER: CR/LF split at chunk boundary"));
+ }
+ else
+ {
+ // Typical case where EOLs are not split. Terminate the string.
+ displayEndOfLine[1] = 0;
+ }
+ // Process this modified string pointed to by fCurrentLine.
+ fServerConnection.HandleMessageDownLoadLine(fCurrentLine, !lastChunk);
+ // Restore fCurrentLine's original content.
+ displayEndOfLine[1] = saveit1;
+ if (fNextChunkStartsWithNewline)
+ displayEndOfLine[2] = saveit2;
+ }
+ else
+ {
+ // Not the last line of a chunk.
+ bool processTheLine = true;
+ if (fNextChunkStartsWithNewline && origin > 0) {
+ // A split of the \r\n between chunks was detected. Ignore orphan \n
+ // on line by itself which can occur on the first line of a 2nd or
+ // later chunk. Line length should be 1 and the only character should
+ // be \n. Note: If previous message ended with just \r, don't expect
+ // the first chunk of a message (origin == 0) to begin with \n.
+ // (Typically, there is only one chunk required for a message or
+ // header response unless its size exceeds the chunking threshold.)
+ if (strlen(fCurrentLine) > 1 || fCurrentLine[0] != '\n') {
+ // In case expected orphan \n is not really there, go ahead and
+ // process the line. This should theoretically not occur but rarely,
+ // and for yet to be determined reasons, it does. Logging may help.
+ NS_WARNING(
+ "'\\n' is not the only character in this line as expected!");
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: expecting just '\\n' but line is = |%s|",
+ fCurrentLine));
+ } else {
+ // Discard the line containing only \n.
+ processTheLine = false;
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: discarding lone '\\n'"));
+ }
+ }
+ if (processTheLine) {
+ fServerConnection.HandleMessageDownLoadLine(fCurrentLine,
+ !lastChunk && (charsReadSoFar == numberOfCharsInThisChunk),
+ fCurrentLine);
+ }
+ fNextChunkStartsWithNewline = false;
+ }
+ }
+ }
+
+ if (ContinueParse())
+ {
+ if (charsReadSoFar > numberOfCharsInThisChunk)
+ {
+ // move the lexical analyzer state to the end of this message because this message
+ // fetch ends in the middle of this line.
+ AdvanceTokenizerStartingPoint(strlen(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk));
+ AdvanceToNextToken();
+ }
+ else
+ {
+ skip_to_CRLF();
+ AdvanceToNextToken();
+ }
+ } else {
+ // Don't typically (maybe never?) see this.
+ fNextChunkStartsWithNewline = false;
+ }
+ return lastChunk;
+}
+
+bool nsImapServerResponseParser::CurrentFolderReadOnly()
+{
+ return fCurrentFolderReadOnly;
+}
+
+int32_t nsImapServerResponseParser::NumberOfMessages()
+{
+ return fNumberOfExistingMessages;
+}
+
+int32_t nsImapServerResponseParser::NumberOfRecentMessages()
+{
+ return fNumberOfRecentMessages;
+}
+
+int32_t nsImapServerResponseParser::NumberOfUnseenMessages()
+{
+ return fNumberOfUnseenMessages;
+}
+
+int32_t nsImapServerResponseParser::FolderUID()
+{
+ return fFolderUIDValidity;
+}
+
+void nsImapServerResponseParser::SetCurrentResponseUID(uint32_t uid)
+{
+ if (uid > 0)
+ fCurrentResponseUID = uid;
+}
+
+uint32_t nsImapServerResponseParser::CurrentResponseUID()
+{
+ return fCurrentResponseUID;
+}
+
+uint32_t nsImapServerResponseParser::HighestRecordedUID()
+{
+ return fHighestRecordedUID;
+}
+
+bool nsImapServerResponseParser::IsNumericString(const char *string)
+{
+ int i;
+ for(i = 0; i < (int) PL_strlen(string); i++)
+ {
+ if (! isdigit(string[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+nsImapMailboxSpec *nsImapServerResponseParser::CreateCurrentMailboxSpec(const char *mailboxName /* = nullptr */)
+{
+ nsImapMailboxSpec *returnSpec = new nsImapMailboxSpec;
+ if (!returnSpec)
+ {
+ HandleMemoryFailure();
+ return nullptr;
+ }
+ NS_ADDREF(returnSpec);
+ const char *mailboxNameToConvert = (mailboxName) ? mailboxName : fSelectedMailboxName;
+ if (mailboxNameToConvert)
+ {
+ const char *serverKey = fServerConnection.GetImapServerKey();
+ nsIMAPNamespace *ns = nullptr;
+ if (serverKey && fHostSessionList)
+ fHostSessionList->GetNamespaceForMailboxForHost(serverKey, mailboxNameToConvert, ns); // for
+ // delimiter
+ returnSpec->mHierarchySeparator = (ns) ? ns->GetDelimiter(): '/';
+
+ }
+
+ returnSpec->mFolderSelected = !mailboxName; // if mailboxName is null, we're doing a Status
+ returnSpec->mFolder_UIDVALIDITY = fFolderUIDValidity;
+ returnSpec->mHighestModSeq = fHighestModSeq;
+ returnSpec->mNumOfMessages = (mailboxName) ? fStatusExistingMessages : fNumberOfExistingMessages;
+ returnSpec->mNumOfUnseenMessages = (mailboxName) ? fStatusUnseenMessages : fNumberOfUnseenMessages;
+ returnSpec->mNumOfRecentMessages = (mailboxName) ? fStatusRecentMessages : fNumberOfRecentMessages;
+ returnSpec->mNextUID = fStatusNextUID;
+
+ returnSpec->mSupportedUserFlags = fSupportsUserDefinedFlags;
+
+ returnSpec->mBoxFlags = kNoFlags; // stub
+ returnSpec->mOnlineVerified = false; // we're fabricating this. The flags aren't verified.
+ returnSpec->mAllocatedPathName.Assign(mailboxNameToConvert);
+ returnSpec->mConnection = &fServerConnection;
+ if (returnSpec->mConnection)
+ {
+ nsIURI * aUrl = nullptr;
+ nsresult rv = NS_OK;
+ returnSpec->mConnection->GetCurrentUrl()->QueryInterface(NS_GET_IID(nsIURI), (void **) &aUrl);
+ if (NS_SUCCEEDED(rv) && aUrl)
+ aUrl->GetHost(returnSpec->mHostName);
+
+ NS_IF_RELEASE(aUrl);
+ }
+ else
+ returnSpec->mHostName.Truncate();
+
+ if (fFlagState)
+ returnSpec->mFlagState = fFlagState; //copies flag state
+ else
+ returnSpec->mFlagState = nullptr;
+
+ return returnSpec;
+
+}
+// Reset the flag state.
+void nsImapServerResponseParser::ResetFlagInfo()
+{
+ if (fFlagState)
+ fFlagState->Reset();
+}
+
+
+bool nsImapServerResponseParser::GetLastFetchChunkReceived()
+{
+ return fLastChunk;
+}
+
+void nsImapServerResponseParser::ClearLastFetchChunkReceived()
+{
+ fLastChunk = false;
+}
+
+void nsImapServerResponseParser::SetHostSessionList(nsIImapHostSessionList*
+ aHostSessionList)
+{
+ NS_IF_RELEASE (fHostSessionList);
+ fHostSessionList = aHostSessionList;
+ NS_IF_ADDREF (fHostSessionList);
+}
+
+void nsImapServerResponseParser::SetSyntaxError(bool error, const char *msg)
+{
+ nsIMAPGenericParser::SetSyntaxError(error, msg);
+ if (error)
+ {
+ if (!fCurrentLine)
+ {
+ HandleMemoryFailure();
+ fServerConnection.Log("PARSER", ("Internal Syntax Error: %s: <no line>"), msg);
+ }
+ else
+ {
+ if (!strcmp(fCurrentLine, CRLF))
+ fServerConnection.Log("PARSER", "Internal Syntax Error: %s: <CRLF>", msg);
+ else
+ {
+ if (msg)
+ fServerConnection.Log("PARSER", "Internal Syntax Error: %s:", msg);
+ fServerConnection.Log("PARSER", "Internal Syntax Error on line: %s", fCurrentLine);
+ }
+ }
+ }
+}
+
+nsresult nsImapServerResponseParser::BeginMessageDownload(const char *content_type)
+{
+ nsresult rv = fServerConnection.BeginMessageDownLoad(fSizeOfMostRecentMessage,
+ content_type);
+ if (NS_FAILED(rv))
+ {
+ skip_to_CRLF();
+ fServerConnection.PseudoInterrupt(true);
+ fServerConnection.AbortMessageDownLoad();
+ }
+ return rv;
+}
diff --git a/mailnews/imap/src/nsImapServerResponseParser.h b/mailnews/imap/src/nsImapServerResponseParser.h
new file mode 100644
index 000000000..4a21de905
--- /dev/null
+++ b/mailnews/imap/src/nsImapServerResponseParser.h
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsIMAPServerResponseParser_H_
+#define _nsIMAPServerResponseParser_H_
+
+#include "mozilla/Attributes.h"
+#include "nsIMAPHostSessionList.h"
+#include "nsImapSearchResults.h"
+#include "nsStringGlue.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsImapUtils.h"
+#include "nsAutoPtr.h"
+
+class nsIMAPNamespace;
+class nsIMAPNamespaceList;
+class nsIMAPBodyShell;
+class nsIMAPBodypart;
+class nsImapSearchResultIterator;
+class nsImapFlagAndUidState;
+class nsCString;
+
+#include "nsIMAPGenericParser.h"
+
+class nsImapServerResponseParser : public nsIMAPGenericParser
+{
+public:
+ explicit nsImapServerResponseParser(nsImapProtocol &imapConnection);
+ virtual ~nsImapServerResponseParser();
+
+ // Overridden from the base parser class
+ virtual bool LastCommandSuccessful() override;
+ virtual void HandleMemoryFailure() override;
+
+ // aignoreBadAndNOResponses --> don't throw a error dialog if this command results in a NO or Bad response
+ // from the server..in other words the command is "exploratory" and we don't really care if it succeeds or fails.
+ // This value is typically FALSE for almost all cases.
+ virtual void ParseIMAPServerResponse(const char *aCurrentCommand,
+ bool aIgnoreBadAndNOResponses,
+ char *aGreetingWithCapability = NULL);
+ virtual void InitializeState();
+ bool CommandFailed();
+ void SetCommandFailed(bool failed);
+
+ enum eIMAPstate {
+ kNonAuthenticated,
+ kAuthenticated,
+ kFolderSelected
+ } ;
+
+ virtual eIMAPstate GetIMAPstate();
+ virtual bool WaitingForMoreClientInput() { return fWaitingForMoreClientInput; }
+ const char *GetSelectedMailboxName(); // can be NULL
+
+ // if we get a PREAUTH greeting from the server, initialize the parser to begin in
+ // the kAuthenticated state
+ void PreauthSetAuthenticatedState();
+
+ // these functions represent the state of the currently selected
+ // folder
+ bool CurrentFolderReadOnly();
+ int32_t NumberOfMessages();
+ int32_t NumberOfRecentMessages();
+ int32_t NumberOfUnseenMessages();
+ int32_t FolderUID();
+ uint32_t CurrentResponseUID();
+ uint32_t HighestRecordedUID();
+ void SetCurrentResponseUID(uint32_t uid);
+ bool IsNumericString(const char *string);
+ uint32_t SizeOfMostRecentMessage();
+ void SetTotalDownloadSize(int32_t newSize) { fTotalDownloadSize = newSize; }
+
+ nsImapSearchResultIterator *CreateSearchResultIterator();
+ void ResetSearchResultSequence() {fSearchResults->ResetSequence();}
+
+ // create a struct mailbox_spec from our info, used in
+ // libmsg c interface
+ nsImapMailboxSpec *CreateCurrentMailboxSpec(const char *mailboxName = nullptr);
+
+ // Resets the flags state.
+ void ResetFlagInfo();
+
+ // set this to false if you don't want to alert the user to server
+ // error messages
+ void SetReportingErrors(bool reportThem) { fReportingErrors=reportThem;}
+ bool GetReportingErrors() { return fReportingErrors; }
+
+ eIMAPCapabilityFlags GetCapabilityFlag() { return fCapabilityFlag; }
+ void SetCapabilityFlag(eIMAPCapabilityFlags capability) {fCapabilityFlag = capability;}
+ bool ServerHasIMAP4Rev1Capability() { return ((fCapabilityFlag & kIMAP4rev1Capability) != 0); }
+ bool ServerHasACLCapability() { return ((fCapabilityFlag & kACLCapability) != 0); }
+ bool ServerHasNamespaceCapability() { return ((fCapabilityFlag & kNamespaceCapability) != 0); }
+ bool ServerIsNetscape3xServer() { return fServerIsNetscape3xServer; }
+ bool ServerHasServerInfo() {return ((fCapabilityFlag & kXServerInfoCapability) != 0); }
+ bool ServerIsAOLServer() {return ((fCapabilityFlag & kAOLImapCapability) != 0); }
+ void SetFetchingFlags(bool aFetchFlags) { fFetchingAllFlags = aFetchFlags;}
+ void ResetCapabilityFlag() ;
+
+ nsCString& GetMailAccountUrl() { return fMailAccountUrl; }
+ const char *GetXSenderInfo() { return fXSenderInfo; }
+ void FreeXSenderInfo() { PR_FREEIF(fXSenderInfo); }
+ nsCString& GetManageListsUrl() { return fManageListsUrl; }
+ nsCString& GetManageFiltersUrl() {return fManageFiltersUrl;}
+ const char *GetManageFolderUrl() {return fFolderAdminUrl;}
+ nsCString &GetServerID() {return fServerIdResponse;}
+
+ // Call this when adding a pipelined command to the session
+ void IncrementNumberOfTaggedResponsesExpected(const char *newExpectedTag);
+
+ // Interrupt a Fetch, without really Interrupting (through netlib)
+ bool GetLastFetchChunkReceived();
+ void ClearLastFetchChunkReceived();
+ virtual uint16_t SupportsUserFlags() { return fSupportsUserDefinedFlags; }
+ virtual uint16_t SettablePermanentFlags() { return fSettablePermanentFlags;}
+ void SetFlagState(nsIImapFlagAndUidState *state);
+ bool GetDownloadingHeaders();
+ bool GetFillingInShell();
+ void UseCachedShell(nsIMAPBodyShell *cachedShell);
+ void SetHostSessionList(nsIImapHostSessionList *aHostSession);
+ char *fAuthChallenge; // the challenge returned by the server in
+ //response to authenticate using CRAM-MD5 or NTLM
+ bool fCondStoreEnabled;
+ bool fUseModSeq; // can use mod seq for currently selected folder
+ uint64_t fHighestModSeq;
+
+protected:
+ virtual void flags();
+ virtual void envelope_data();
+ virtual void xaolenvelope_data();
+ virtual void parse_address(nsAutoCString &addressLine);
+ virtual void internal_date();
+ virtual nsresult BeginMessageDownload(const char *content_type);
+
+ virtual void response_data();
+ virtual void resp_text();
+ virtual void resp_cond_state(bool isTagged);
+ virtual void text_mime2();
+ virtual void text();
+ virtual void parse_folder_flags();
+ virtual void enable_data();
+ virtual void language_data();
+ virtual void authChallengeResponse_data();
+ virtual void resp_text_code();
+ virtual void response_done();
+ virtual void response_tagged();
+ virtual void response_fatal();
+ virtual void resp_cond_bye();
+ virtual void id_data();
+ virtual void mailbox_data();
+ virtual void numeric_mailbox_data();
+ virtual void capability_data();
+ virtual void xserverinfo_data();
+ virtual void xmailboxinfo_data();
+ virtual void namespace_data();
+ virtual void myrights_data(bool unsolicited);
+ virtual void acl_data();
+ virtual void bodystructure_data();
+ nsIMAPBodypart *bodystructure_part(char *partNum, nsIMAPBodypart *parentPart);
+ nsIMAPBodypart *bodystructure_leaf(char *partNum, nsIMAPBodypart *parentPart);
+ nsIMAPBodypart *bodystructure_multipart(char *partNum, nsIMAPBodypart *parentPart);
+ virtual void mime_data();
+ virtual void mime_part_data();
+ virtual void mime_header_data();
+ virtual void quota_data();
+ virtual void msg_fetch();
+ virtual void msg_obsolete();
+ virtual void msg_fetch_headers(const char *partNum);
+ virtual void msg_fetch_content(bool chunk, int32_t origin, const char *content_type);
+ virtual bool msg_fetch_quoted();
+ virtual bool msg_fetch_literal(bool chunk, int32_t origin);
+ virtual void mailbox_list(bool discoveredFromLsub);
+ virtual void mailbox(nsImapMailboxSpec *boxSpec);
+
+ virtual void ProcessOkCommand(const char *commandToken);
+ virtual void ProcessBadCommand(const char *commandToken);
+ virtual void PreProcessCommandToken(const char *commandToken,
+ const char *currentCommand);
+ virtual void PostProcessEndOfLine();
+
+ // Overridden from the nsIMAPGenericParser, to retrieve the next line
+ // from the open socket.
+ virtual bool GetNextLineForParser(char **nextLine) override;
+ // overriden to do logging
+ virtual void SetSyntaxError(bool error, const char *msg = nullptr) override;
+
+private:
+ bool fCurrentCommandFailed;
+ bool fReportingErrors;
+
+
+ bool fCurrentFolderReadOnly;
+ bool fCurrentLineContainedFlagInfo;
+ bool fFetchingAllFlags;
+ bool fWaitingForMoreClientInput;
+ // Is the server a Netscape 3.x Messaging Server?
+ bool fServerIsNetscape3xServer;
+ bool fDownloadingHeaders;
+ bool fCurrentCommandIsSingleMessageFetch;
+ bool fGotPermanentFlags;
+ imapMessageFlagsType fSavedFlagInfo;
+ nsTArray<nsCString> fCustomFlags;
+
+ uint16_t fSupportsUserDefinedFlags;
+ uint16_t fSettablePermanentFlags;
+
+ int32_t fFolderUIDValidity;
+ int32_t fNumberOfUnseenMessages;
+ int32_t fNumberOfExistingMessages;
+ int32_t fNumberOfRecentMessages;
+ uint32_t fCurrentResponseUID;
+ uint32_t fHighestRecordedUID;
+ // used to handle server that sends msg size after headers
+ uint32_t fReceivedHeaderOrSizeForUID;
+ int32_t fSizeOfMostRecentMessage;
+ int32_t fTotalDownloadSize;
+
+ int32_t fStatusUnseenMessages;
+ int32_t fStatusRecentMessages;
+ uint32_t fStatusNextUID;
+ uint32_t fStatusExistingMessages;
+
+ int fNumberOfTaggedResponsesExpected;
+
+ char *fCurrentCommandTag;
+
+ nsCString fZeroLengthMessageUidString;
+
+ char *fSelectedMailboxName;
+
+ nsImapSearchResultSequence *fSearchResults;
+
+ nsCOMPtr <nsIImapFlagAndUidState> fFlagState; // NOT owned by us, it's a copy, do not destroy
+
+ eIMAPstate fIMAPstate;
+
+ eIMAPCapabilityFlags fCapabilityFlag;
+ nsCString fMailAccountUrl;
+ char *fNetscapeServerVersionString;
+ char *fXSenderInfo; /* changed per message download */
+ char *fLastAlert; /* used to avoid displaying the same alert over and over */
+ char *fMsgID; /* MessageID for Gmail only (X-GM-MSGID) */
+ char *fThreadID; /* ThreadID for Gmail only (X-GM-THRID) */
+ char *fLabels; /* Labels for Gmail only (X-GM-LABELS) [will include parens, removed while passing to hashTable ]*/
+ nsCString fManageListsUrl;
+ nsCString fManageFiltersUrl;
+ char *fFolderAdminUrl;
+ nsCString fServerIdResponse; // RFC
+
+ int32_t fFetchResponseIndex;
+
+ // used for aborting a fetch stream when we're pseudo-Interrupted
+ int32_t numberOfCharsInThisChunk;
+ int32_t charsReadSoFar;
+ bool fLastChunk;
+
+ // Flags split of \r and \n between chunks in msg_fetch_literal().
+ bool fNextChunkStartsWithNewline;
+
+ // points to the current body shell, if any
+ RefPtr<nsIMAPBodyShell> m_shell;
+
+ // The connection object
+ nsImapProtocol &fServerConnection;
+
+ nsIImapHostSessionList *fHostSessionList;
+ nsTArray<nsMsgKey> fCopyResponseKeyArray;
+};
+
+#endif
diff --git a/mailnews/imap/src/nsImapService.cpp b/mailnews/imap/src/nsImapService.cpp
new file mode 100644
index 000000000..60af2084a
--- /dev/null
+++ b/mailnews/imap/src/nsImapService.cpp
@@ -0,0 +1,3400 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nsMsgImapCID.h"
+
+#include "netCore.h"
+
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+
+#include "nsIIMAPHostSessionList.h"
+#include "nsImapService.h"
+
+#include "nsImapUrl.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIImapServerSink.h"
+#include "nsIImapMockChannel.h"
+#include "nsImapUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsILoadGroup.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsISubscribableServer.h"
+#include "nsIDirectoryService.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsIWebNavigation.h"
+#include "nsImapStringBundle.h"
+#include "plbase64.h"
+#include "nsImapOfflineSync.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgUtils.h"
+#include "nsICacheStorage.h"
+#include "nsICacheStorageService.h"
+#include "nsILoadContextInfo.h"
+#include "nsIStreamListenerTee.h"
+#include "nsNetCID.h"
+#include "nsMsgI18N.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsIMsgParseMailMsgState.h"
+#include "nsMsgLocalCID.h"
+#include "nsIOutputStream.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIMessengerWindowService.h"
+#include "nsIWindowMediator.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+#include "nsImapProtocol.h"
+#include "nsIMsgMailSession.h"
+#include "nsIStreamConverterService.h"
+#include "nsIAutoSyncManager.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgPluggableStore.h"
+#include "../../base/src/MailnewsLoadContextInfo.h"
+
+#define PREF_MAIL_ROOT_IMAP "mail.root.imap" // old - for backward compatibility only
+#define PREF_MAIL_ROOT_IMAP_REL "mail.root.imap-rel"
+
+static NS_DEFINE_CID(kImapUrlCID, NS_IMAPURL_CID);
+static NS_DEFINE_CID(kCImapMockChannel, NS_IMAPMOCKCHANNEL_CID);
+
+static const char sequenceString[] = "SEQUENCE";
+static const char uidString[] = "UID";
+
+static bool gInitialized = false;
+static int32_t gMIMEOnDemandThreshold = 15000;
+static bool gMIMEOnDemand = false;
+
+NS_IMPL_ISUPPORTS(nsImapService,
+ nsIImapService,
+ nsIMsgMessageService,
+ nsIProtocolHandler,
+ nsIMsgProtocolInfo,
+ nsIMsgMessageFetchPartService,
+ nsIContentHandler)
+
+nsImapService::nsImapService()
+{
+ mPrintingOperation = false;
+ if (!gInitialized)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && prefBranch)
+ {
+ prefBranch->GetBoolPref("mail.imap.mime_parts_on_demand", &gMIMEOnDemand);
+ prefBranch->GetIntPref("mail.imap.mime_parts_on_demand_threshold", &gMIMEOnDemandThreshold);
+ }
+
+ // initialize auto-sync service
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && autoSyncMgr)
+ {
+ // auto-sync manager initialization goes here
+ // assign new strategy objects here...
+ }
+ NS_ASSERTION(autoSyncMgr != nullptr, "*** Cannot initialize nsAutoSyncManager service.");
+
+ gInitialized = true;
+ }
+}
+
+nsImapService::~nsImapService()
+{
+}
+
+char nsImapService::GetHierarchyDelimiter(nsIMsgFolder *aMsgFolder)
+{
+ char delimiter = '/';
+ if (aMsgFolder)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aMsgFolder);
+ if (imapFolder)
+ imapFolder->GetHierarchyDelimiter(&delimiter);
+ }
+ return delimiter;
+}
+
+// N.B., this returns an escaped folder name, appropriate for putting in a url.
+nsresult nsImapService::GetFolderName(nsIMsgFolder *aImapFolder, nsACString &aFolderName)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgImapMailFolder> aFolder(do_QueryInterface(aImapFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString onlineName;
+ // online name is in imap utf-7 - leave it that way
+ rv = aFolder->GetOnlineName(onlineName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (onlineName.IsEmpty())
+ {
+ nsCString uri;
+ rv = aImapFolder->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString hostname;
+ rv = aImapFolder->GetHostname(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsImapURI2FullName(kImapRootURI, hostname.get(), uri.get(), getter_Copies(onlineName));
+ }
+ // if the hierarchy delimiter is not '/', then we want to escape slashes;
+ // otherwise, we do want to escape slashes.
+ // we want to escape slashes and '^' first, otherwise, nsEscape will lose them
+ bool escapeSlashes = (GetHierarchyDelimiter(aImapFolder) != '/');
+ if (escapeSlashes && !onlineName.IsEmpty())
+ {
+ char* escapedOnlineName;
+ rv = nsImapUrl::EscapeSlashes(onlineName.get(), &escapedOnlineName);
+ if (NS_SUCCEEDED(rv))
+ onlineName.Adopt(escapedOnlineName);
+ }
+ // need to escape everything else
+ MsgEscapeString(onlineName, nsINetUtil::ESCAPE_URL_PATH, aFolderName);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::SelectFolder(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ if (WeAreOffline())
+ return NS_MSG_ERROR_OFFLINE;
+
+ bool canOpenThisFolder = true;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aImapMailFolder);
+ if (imapFolder)
+ imapFolder->GetCanOpenFolder(&canOpenThisFolder);
+
+ if (!canOpenThisFolder)
+ return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aImapMailFolder, aUrlListener, urlSpec, hierarchyDelimiter);
+
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ // nsImapUrl::SetSpec() will set the imap action properly
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapSelectFolder);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ // if no msg window, we won't put up error messages (this is almost certainly a biff-inspired get new msgs)
+ if (!aMsgWindow)
+ mailNewsUrl->SetSuppressErrorMsgs(true);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ mailNewsUrl->SetUpdatingFolder(true);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append("/select>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ rv = mailNewsUrl->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ } // if we have a url to run....
+
+ return rv;
+}
+
+// lite select, used to verify UIDVALIDITY while going on/offline
+NS_IMETHODIMP nsImapService::LiteSelectFolder(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ "/liteselect>", nsIImapUrl::nsImapLiteSelectFolder, aMsgWindow, aURL);
+}
+
+NS_IMETHODIMP nsImapService::GetUrlForUri(const char *aMessageURI,
+ nsIURI **aURL,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsAutoCString messageURI(aMessageURI);
+
+ if (messageURI.Find(NS_LITERAL_CSTRING("&type=application/x-message-display")) != kNotFound)
+ return NS_NewURI(aURL, aMessageURI);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsresult rv = DecomposeImapURI(messageURI, getter_AddRefs(folder), msgKey);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(messageURI, getter_AddRefs(imapUrl), folder, nullptr, urlSpec, hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetImapUrlSink(folder, imapUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl);
+ bool useLocalCache = false;
+ folder->HasMsgOffline(strtoul(msgKey.get(), nullptr, 10), &useLocalCache);
+ mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+
+ nsCOMPtr<nsIURI> url = do_QueryInterface(imapUrl);
+ rv = url->GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ urlSpec.Append("fetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsAutoCString folderName;
+ GetFolderName(folder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(msgKey);
+ rv = url->SetSpec(urlSpec);
+ imapUrl->QueryInterface(NS_GET_IID(nsIURI), (void **) aURL);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::OpenAttachment(const char *aContentType,
+ const char *aFileName,
+ const char *aUrl,
+ const char *aMessageUri,
+ nsISupports *aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener)
+{
+ // okay this is a little tricky....we may have to fetch the mime part
+ // or it may already be downloaded for us....the only way i can tell to
+ // distinguish the two events is to search for ?section or ?part
+
+ nsAutoCString uri(aMessageUri);
+ nsAutoCString urlString(aUrl);
+ MsgReplaceSubstring(urlString, "/;section", "?section");
+
+ // more stuff i don't understand
+ int32_t sectionPos = urlString.Find("?section");
+ // if we have a section field then we must be dealing with a mime part we need to fetchf
+ if (sectionPos > 0)
+ {
+ uri.Append(Substring(urlString, sectionPos));
+ uri += "&type=";
+ uri += aContentType;
+ uri += "&filename=";
+ uri += aFileName;
+ }
+ else
+ {
+ // try to extract the specific part number out from the url string
+ const char *partStart = PL_strstr(aUrl, "part=");
+ if (!partStart)
+ return NS_ERROR_FAILURE;
+ nsDependentCString part(partStart);
+ uri += "?";
+ uri += Substring(part, 0, part.FindChar('&'));
+ uri += "&type=";
+ uri += aContentType;
+ uri += "&filename=";
+ uri += aFileName;
+ }
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsAutoCString uriMimePart;
+ nsAutoCString folderURI;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeImapURI(uri, getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsParseImapMessageURI(uri.get(), folderURI, &key, getter_Copies(uriMimePart));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(do_QueryInterface(folder, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(uri, getter_AddRefs(imapUrl), folder, aUrlListener, urlSpec, hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ urlSpec.Append("/fetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(folder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(msgKey);
+ urlSpec.Append(uriMimePart);
+
+ if (!uriMimePart.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl (do_QueryInterface(imapUrl));
+ if (mailUrl)
+ {
+ rv = mailUrl->SetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aFileName)
+ mailUrl->SetFileName(nsDependentCString(aFileName));
+ }
+ rv = FetchMimePart(imapUrl, nsIImapUrl::nsImapOpenMimePart, folder, imapMessageSink,
+ nullptr, aDisplayConsumer, msgKey, uriMimePart);
+ }
+ } // if we got a message sink
+ } // if we parsed the message uri
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::FetchMimePart(nsIURI *aURI,
+ const char *aMessageURI,
+ nsISupports *aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString messageURI(aMessageURI);
+ nsAutoCString msgKey;
+ nsAutoCString mimePart;
+ nsAutoCString folderURI;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeImapURI(messageURI, getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsParseImapMessageURI(aMessageURI, folderURI, &key, getter_Copies(mimePart));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(do_QueryInterface(folder, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aURI);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(aURI));
+
+ msgurl->SetMsgWindow(aMsgWindow);
+ msgurl->RegisterListener(aUrlListener);
+
+ if (!mimePart.IsEmpty())
+ {
+ return FetchMimePart(imapUrl, nsIImapUrl::nsImapMsgFetch, folder, imapMessageSink,
+ aURL, aDisplayConsumer, msgKey, mimePart);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DisplayMessage(const char *aMessageURI,
+ nsISupports *aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ const char *aCharsetOverride,
+ nsIURI **aURL)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsAutoCString mimePart;
+ nsAutoCString folderURI;
+ nsMsgKey key;
+ nsAutoCString messageURI(aMessageURI);
+
+ int32_t typeIndex = messageURI.Find("&type=application/x-message-display");
+ if (typeIndex != kNotFound)
+ {
+ // This happens with forward inline of a message/rfc822 attachment opened in
+ // a standalone msg window.
+ // So, just cut to the chase and call AsyncOpen on a channel.
+ nsCOMPtr <nsIURI> uri;
+ messageURI.Cut(typeIndex, sizeof("&type=application/x-message-display") - 1);
+ rv = NS_NewURI(getter_AddRefs(uri), messageURI.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aURL)
+ NS_IF_ADDREF(*aURL = uri);
+ nsCOMPtr<nsIStreamListener> aStreamListener = do_QueryInterface(aDisplayConsumer, &rv);
+ if (NS_SUCCEEDED(rv) && aStreamListener)
+ {
+ nsCOMPtr<nsIChannel> aChannel;
+ nsCOMPtr<nsILoadGroup> aLoadGroup;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(uri, &rv);
+ if (NS_SUCCEEDED(rv) && mailnewsUrl)
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(aLoadGroup));
+
+ rv = NewChannel(uri, getter_AddRefs(aChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> aCtxt = do_QueryInterface(uri);
+ // now try to open the channel passing in our display consumer as the listener
+ rv = aChannel->AsyncOpen(aStreamListener, aCtxt);
+ return rv;
+ }
+ }
+
+ rv = DecomposeImapURI(messageURI, getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (msgKey.IsEmpty())
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ rv = nsParseImapMessageURI(aMessageURI, folderURI, &key, getter_Copies(mimePart));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(do_QueryInterface(folder, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(messageURI, getter_AddRefs(imapUrl), folder, aUrlListener, urlSpec, hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mimePart.IsEmpty())
+ {
+ nsresult rv;
+ nsCOMPtr<nsIURI> url = do_QueryInterface(imapUrl);
+
+ rv = AddImapFetchToUrl(url, folder, msgKey + mimePart, EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return FetchMimePart(imapUrl, nsIImapUrl::nsImapMsgFetch, folder, imapMessageSink,
+ aURL, aDisplayConsumer, msgKey, mimePart);
+ }
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(imapUrl));
+ nsCOMPtr<nsIMsgI18NUrl> i18nurl (do_QueryInterface(imapUrl));
+ i18nurl->SetCharsetOverRide(aCharsetOverride);
+
+ uint32_t messageSize;
+ bool useMimePartsOnDemand = gMIMEOnDemand;
+ bool shouldStoreMsgOffline = false;
+ bool hasMsgOffline = false;
+
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+
+ if (imapMessageSink)
+ imapMessageSink->GetMessageSizeFromDB(msgKey.get(), &messageSize);
+
+ msgurl->SetMsgWindow(aMsgWindow);
+
+ rv = msgurl->GetServer(getter_AddRefs(aMsgIncomingServer));
+
+ if (NS_SUCCEEDED(rv) && aMsgIncomingServer)
+ {
+ nsCOMPtr<nsIImapIncomingServer> aImapServer(do_QueryInterface(aMsgIncomingServer, &rv));
+ if (NS_SUCCEEDED(rv) && aImapServer)
+ aImapServer->GetMimePartsOnDemand(&useMimePartsOnDemand);
+ }
+
+ nsAutoCString uriStr(aMessageURI);
+ int32_t keySeparator = uriStr.RFindChar('#');
+ if(keySeparator != -1)
+ {
+ int32_t keyEndSeparator = MsgFindCharInSet(uriStr, "/?&", keySeparator);
+ int32_t mpodFetchPos = MsgFind(uriStr, "fetchCompleteMessage=true", false, keyEndSeparator);
+ if (mpodFetchPos != -1)
+ useMimePartsOnDemand = false;
+ }
+
+ if (folder)
+ {
+ folder->ShouldStoreMsgOffline(key, &shouldStoreMsgOffline);
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ }
+ imapUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
+ imapUrl->SetFetchPartsOnDemand(
+ useMimePartsOnDemand && messageSize >= (uint32_t) gMIMEOnDemandThreshold);
+
+ if (hasMsgOffline)
+ msgurl->SetMsgIsInLocalCache(true);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ // Should the message fetch force a peek or a traditional fetch?
+ // Force peek if there is a delay in marking read (or no auto-marking at all).
+ // This is because a FETCH (BODY[]) will implicitly set tha \Seen flag on the msg,
+ // but a FETCH (BODY.PEEK[]) won't.
+ bool forcePeek = false;
+ if (NS_SUCCEEDED(rv) && prefBranch)
+ {
+ int32_t dontMarkAsReadPos = uriStr.Find("&markRead=false");
+ bool markReadAuto = true;
+ prefBranch->GetBoolPref("mailnews.mark_message_read.auto", &markReadAuto);
+ bool markReadDelay = false;
+ prefBranch->GetBoolPref("mailnews.mark_message_read.delay", &markReadDelay);
+ forcePeek = (!markReadAuto || markReadDelay || (dontMarkAsReadPos != kNotFound));
+ }
+
+ rv = FetchMessage(imapUrl, forcePeek ? nsIImapUrl::nsImapMsgFetchPeek : nsIImapUrl::nsImapMsgFetch,
+ folder, imapMessageSink, aMsgWindow, aDisplayConsumer, msgKey, false,
+ (mPrintingOperation) ? NS_LITERAL_CSTRING("print") : NS_LITERAL_CSTRING(""), aURL);
+ }
+ }
+ return rv;
+}
+
+
+nsresult nsImapService::FetchMimePart(nsIImapUrl *aImapUrl,
+ nsImapAction aImapAction,
+ nsIMsgFolder *aImapMailFolder,
+ nsIImapMessageSink *aImapMessage,
+ nsIURI **aURL,
+ nsISupports *aDisplayConsumer,
+ const nsACString &messageIdentifierList,
+ const nsACString &mimePart)
+{
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+ NS_ENSURE_ARG_POINTER(aImapMessage);
+
+ // create a protocol instance to handle the request.
+ // NOTE: once we start working with multiple connections, this step will be much more complicated...but for now
+ // just create a connection and process the request.
+ nsAutoCString urlSpec;
+ nsresult rv = SetImapUrlSink(aImapMailFolder, aImapUrl);
+ nsImapAction actionToUse = aImapAction;
+ if (actionToUse == nsImapUrl::nsImapOpenMimePart)
+ actionToUse = nsIImapUrl::nsImapMsgFetch;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(aImapUrl));
+ if (aImapMailFolder && msgurl && !messageIdentifierList.IsEmpty())
+ {
+ bool useLocalCache = false;
+ aImapMailFolder->HasMsgOffline(strtoul(nsCString(messageIdentifierList).get(), nullptr, 10),
+ &useLocalCache);
+ msgurl->SetMsgIsInLocalCache(useLocalCache);
+ }
+ rv = aImapUrl->SetImapMessageSink(aImapMessage);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> url = do_QueryInterface(aImapUrl);
+ if (aURL)
+ NS_IF_ADDREF(*aURL = url);
+
+ rv = url->GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // rhp: If we are displaying this message for the purpose of printing, we
+ // need to append the header=print option.
+ //
+ if (mPrintingOperation)
+ urlSpec.Append("?header=print");
+
+ rv = url->SetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aImapUrl->SetImapAction(actionToUse /* nsIImapUrl::nsImapMsgFetch */);
+ if (aImapMailFolder && aDisplayConsumer)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+ rv = aImapMailFolder->GetServer(getter_AddRefs(aMsgIncomingServer));
+ if (NS_SUCCEEDED(rv) && aMsgIncomingServer)
+ {
+ bool interrupted;
+ nsCOMPtr<nsIImapIncomingServer>
+ aImapServer(do_QueryInterface(aMsgIncomingServer, &rv));
+ if (NS_SUCCEEDED(rv) && aImapServer)
+ aImapServer->PseudoInterruptMsgLoad(aImapMailFolder, nullptr, &interrupted);
+ }
+ }
+ // if the display consumer is a docshell, then we should run the url in the docshell.
+ // otherwise, it should be a stream listener....so open a channel using AsyncRead
+ // and the provided stream listener....
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ if (NS_SUCCEEDED(rv) && docShell)
+ {
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ // DIRTY LITTLE HACK --> if we are opening an attachment we want the docshell to
+ // treat this load as if it were a user click event. Then the dispatching stuff will be much
+ // happier.
+ if (aImapAction == nsImapUrl::nsImapOpenMimePart)
+ {
+ docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadLink);
+ }
+
+ rv = docShell->LoadURI(url, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false);
+ }
+ else
+ {
+ nsCOMPtr<nsIStreamListener> aStreamListener = do_QueryInterface(aDisplayConsumer, &rv);
+ if (NS_SUCCEEDED(rv) && aStreamListener)
+ {
+ nsCOMPtr<nsIChannel> aChannel;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aImapUrl, &rv);
+ if (NS_SUCCEEDED(rv) && mailnewsUrl)
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ rv = NewChannel(url, getter_AddRefs(aChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we need a load group to hold onto the channel. When the request is finished,
+ // it'll get removed from the load group, and the channel will go away,
+ // which will free the load group.
+ if (!loadGroup)
+ loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+
+ aChannel->SetLoadGroup(loadGroup);
+
+ nsCOMPtr<nsISupports> aCtxt = do_QueryInterface(url);
+ // now try to open the channel passing in our display consumer as the listener
+ rv = aChannel->AsyncOpen(aStreamListener, aCtxt);
+ }
+ else // do what we used to do before
+ {
+ // I'd like to get rid of this code as I believe that we always get a docshell
+ // or stream listener passed into us in this method but i'm not sure yet...
+ // I'm going to use an assert for now to figure out if this is ever getting called
+#if defined(DEBUG_mscott) || defined(DEBUG_bienvenu)
+ NS_ERROR("oops...someone still is reaching this part of the code");
+#endif
+ rv = GetImapConnectionAndLoadUrl(aImapUrl, aDisplayConsumer, aURL);
+ }
+ }
+ }
+ return rv;
+}
+
+//
+// rhp: Right now, this is the same as simple DisplayMessage, but it will change
+// to support print rendering.
+//
+NS_IMETHODIMP nsImapService::DisplayMessageForPrinting(const char *aMessageURI,
+ nsISupports *aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ mPrintingOperation = true;
+ nsresult rv = DisplayMessage(aMessageURI, aDisplayConsumer, aMsgWindow, aUrlListener, nullptr, aURL);
+ mPrintingOperation = false;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::CopyMessage(const char *aSrcMailboxURI,
+ nsIStreamListener *aMailboxCopy,
+ bool moveMessage,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aSrcMailboxURI);
+ NS_ENSURE_ARG_POINTER(aMailboxCopy);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> streamSupport;
+ streamSupport = do_QueryInterface(aMailboxCopy, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ rv = DecomposeImapURI(nsDependentCString(aSrcMailboxURI), getter_AddRefs(folder), msgKey);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(do_QueryInterface(folder, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ bool hasMsgOffline = false;
+ nsMsgKey key = strtoul(msgKey.get(), nullptr, 10);
+
+ rv = CreateStartOfImapUrl(nsDependentCString(aSrcMailboxURI), getter_AddRefs(imapUrl),
+ folder, aUrlListener, urlSpec, hierarchyDelimiter);
+ if (folder)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(imapUrl));
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ if (msgurl)
+ msgurl->SetMsgIsInLocalCache(hasMsgOffline);
+ }
+ // now try to download the message
+ nsImapAction imapAction = nsIImapUrl::nsImapOnlineToOfflineCopy;
+ if (moveMessage)
+ imapAction = nsIImapUrl::nsImapOnlineToOfflineMove;
+ rv = FetchMessage(imapUrl,imapAction, folder, imapMessageSink,aMsgWindow,
+ streamSupport, msgKey, false, EmptyCString(), aURL);
+ } // if we got an imap message sink
+ } // if we decomposed the imap message
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::CopyMessages(uint32_t aNumKeys,
+ nsMsgKey*aKeys,
+ nsIMsgFolder *srcFolder,
+ nsIStreamListener *aMailboxCopy,
+ bool moveMessage,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aMailboxCopy);
+ NS_ENSURE_ARG_POINTER(aKeys);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> streamSupport = do_QueryInterface(aMailboxCopy, &rv);
+ if (!streamSupport || NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder = srcFolder;
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(do_QueryInterface(folder, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ // we generate the uri for the first message so that way on down the line,
+ // GetMessage in nsCopyMessageStreamListener will get an unescaped username
+ // and be able to find the msg hdr. See bug 259656 for details
+ nsCString uri;
+ srcFolder->GenerateMessageURI(aKeys[0], uri);
+
+ nsCString messageIds;
+ AllocateImapUidString(aKeys, aNumKeys, nullptr, messageIds);
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(uri, getter_AddRefs(imapUrl), folder, aUrlListener, urlSpec, hierarchyDelimiter);
+ nsImapAction action;
+ if (moveMessage) // don't use ?: syntax here, it seems to break the Mac.
+ action = nsIImapUrl::nsImapOnlineToOfflineMove;
+ else
+ action = nsIImapUrl::nsImapOnlineToOfflineCopy;
+ imapUrl->SetCopyState(aMailboxCopy);
+ // now try to display the message
+ rv = FetchMessage(imapUrl, action, folder, imapMessageSink, aMsgWindow,
+ streamSupport, messageIds, false, EmptyCString(), aURL);
+ // ### end of copy operation should know how to do the delete.if this is a move
+
+ } // if we got an imap message sink
+ } // if we decomposed the imap message
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::Search(nsIMsgSearchSession *aSearchSession,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgFolder *aMsgFolder,
+ const char *aSearchUri)
+{
+ NS_ENSURE_ARG_POINTER(aSearchUri);
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ nsresult rv;
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsCOMPtr <nsIUrlListener> urlListener = do_QueryInterface(aSearchSession, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(aMsgFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aMsgFolder, urlListener, urlSpec,
+ hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(imapUrl));
+
+ msgurl->SetMsgWindow(aMsgWindow);
+ msgurl->SetSearchSession(aSearchSession);
+ rv = SetImapUrlSink(aMsgFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString folderName;
+ GetFolderName(aMsgFolder, folderName);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ if (!aMsgWindow)
+ mailNewsUrl->SetSuppressErrorMsgs(true);
+
+ urlSpec.Append("/search>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ // escape aSearchUri so that IMAP special characters (i.e. '\')
+ // won't be replaced with '/' in NECKO.
+ // it will be unescaped in nsImapUrl::ParseUrl().
+ nsCString escapedSearchUri;
+
+ MsgEscapeString(nsDependentCString(aSearchUri), nsINetUtil::ESCAPE_XALPHAS, escapedSearchUri);
+ urlSpec.Append(escapedSearchUri);
+ rv = mailNewsUrl->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, nullptr);
+ }
+ return rv;
+}
+
+// just a helper method to break down imap message URIs....
+nsresult nsImapService::DecomposeImapURI(const nsACString &aMessageURI,
+ nsIMsgFolder **aFolder,
+ nsACString &aMsgKey)
+{
+ nsMsgKey msgKey;
+ nsresult rv = DecomposeImapURI(aMessageURI, aFolder, &msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (msgKey)
+ {
+ nsAutoCString messageIdString;
+ messageIdString.AppendInt(msgKey);
+ aMsgKey = messageIdString;
+ }
+
+ return rv;
+}
+
+// just a helper method to break down imap message URIs....
+nsresult nsImapService::DecomposeImapURI(const nsACString &aMessageURI,
+ nsIMsgFolder **aFolder,
+ nsMsgKey *aMsgKey)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aMsgKey);
+
+ nsAutoCString folderURI;
+ nsresult rv = nsParseImapMessageURI(PromiseFlatCString(aMessageURI).get(),
+ folderURI, aMsgKey, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(folderURI, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> msgFolder = do_QueryInterface(res);
+ NS_ENSURE_TRUE(msgFolder, NS_ERROR_FAILURE);
+
+ msgFolder.swap(*aFolder);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::SaveMessageToDisk(const char *aMessageURI,
+ nsIFile *aFile,
+ bool aAddDummyEnvelope,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ bool canonicalLineEnding,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString msgKey;
+
+ nsresult rv = DecomposeImapURI(nsDependentCString(aMessageURI),
+ getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMsgOffline = false;
+
+ if (folder)
+ folder->HasMsgOffline(strtoul(msgKey.get(), nullptr, 10), &hasMsgOffline);
+
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(nsDependentCString(aMessageURI), getter_AddRefs(imapUrl),
+ folder, aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(do_QueryInterface(folder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(imapUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgUrl->SetMessageFile(aFile);
+ msgUrl->SetAddDummyEnvelope(aAddDummyEnvelope);
+ msgUrl->SetCanonicalLineEnding(canonicalLineEnding);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(msgUrl);
+ if (mailnewsUrl)
+ mailnewsUrl->SetMsgIsInLocalCache(hasMsgOffline);
+
+ nsCOMPtr <nsIStreamListener> saveAsListener;
+ mailnewsUrl->GetSaveAsListener(aAddDummyEnvelope, aFile, getter_AddRefs(saveAsListener));
+
+ return FetchMessage(imapUrl, nsIImapUrl::nsImapSaveMessageToDisk, folder, imapMessageSink,
+ aMsgWindow, saveAsListener, msgKey, false, EmptyCString(), aURL);
+ }
+ return rv;
+}
+
+/* fetching RFC822 messages */
+/* imap4://HOST>fetch><UID>>MAILBOXPATH>x */
+/* 'x' is the message UID */
+/* will set the 'SEEN' flag */
+NS_IMETHODIMP nsImapService::AddImapFetchToUrl(nsIURI *aUrl,
+ nsIMsgFolder *aImapMailFolder,
+ const nsACString &aMessageIdentifierList,
+ const nsACString &aAdditionalHeader)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+
+ nsAutoCString urlSpec;
+ nsresult rv = aUrl->GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+
+ urlSpec.Append("fetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsAutoCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+
+ urlSpec.Append(">");
+ urlSpec.Append(aMessageIdentifierList);
+
+ if (!aAdditionalHeader.IsEmpty())
+ {
+ urlSpec.Append("?header=");
+ urlSpec.Append(aAdditionalHeader);
+ }
+
+ return aUrl->SetSpec(urlSpec);
+}
+
+NS_IMETHODIMP nsImapService::FetchMessage(nsIImapUrl *aImapUrl,
+ nsImapAction aImapAction,
+ nsIMsgFolder *aImapMailFolder,
+ nsIImapMessageSink *aImapMessage,
+ nsIMsgWindow *aMsgWindow,
+ nsISupports *aDisplayConsumer,
+ const nsACString &messageIdentifierList,
+ bool aConvertDataToText,
+ const nsACString &aAdditionalHeader,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+ NS_ENSURE_ARG_POINTER(aImapMessage);
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> url = do_QueryInterface(aImapUrl);
+
+ rv = AddImapFetchToUrl(url, aImapMailFolder, messageIdentifierList, aAdditionalHeader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (WeAreOffline())
+ {
+ bool msgIsInCache = false;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl(do_QueryInterface(aImapUrl));
+ msgUrl->GetMsgIsInLocalCache(&msgIsInCache);
+ if (!msgIsInCache)
+ IsMsgInMemCache(url, aImapMailFolder, &msgIsInCache);
+
+ // Display the "offline" message if we didn't find it in the memory cache either
+ if (!msgIsInCache)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = aImapMailFolder->GetServer(getter_AddRefs(server));
+ if (server && aDisplayConsumer)
+ rv = server->DisplayOfflineMsg(aMsgWindow);
+ return rv;
+ }
+ }
+
+ if (aURL)
+ NS_IF_ADDREF(*aURL = url);
+
+ return GetMessageFromUrl(aImapUrl, aImapAction, aImapMailFolder, aImapMessage,
+ aMsgWindow, aDisplayConsumer, aConvertDataToText, aURL);
+}
+
+nsresult nsImapService::GetMessageFromUrl(nsIImapUrl *aImapUrl,
+ nsImapAction aImapAction,
+ nsIMsgFolder *aImapMailFolder,
+ nsIImapMessageSink *aImapMessage,
+ nsIMsgWindow *aMsgWindow,
+ nsISupports *aDisplayConsumer,
+ bool aConvertDataToText,
+ nsIURI **aURL)
+{
+ nsresult rv = SetImapUrlSink(aImapMailFolder, aImapUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aImapUrl->SetImapMessageSink(aImapMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aImapUrl->SetImapAction(aImapAction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> url(do_QueryInterface(aImapUrl));
+
+ // if the display consumer is a docshell, then we should run the url in the docshell.
+ // otherwise, it should be a stream listener....so open a channel using AsyncRead
+ // and the provided stream listener....
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ if (aImapMailFolder && docShell)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+ rv = aImapMailFolder->GetServer(getter_AddRefs(aMsgIncomingServer));
+ if (NS_SUCCEEDED(rv) && aMsgIncomingServer)
+ {
+ bool interrupted;
+ nsCOMPtr<nsIImapIncomingServer>
+ aImapServer(do_QueryInterface(aMsgIncomingServer, &rv));
+ if (NS_SUCCEEDED(rv) && aImapServer)
+ aImapServer->PseudoInterruptMsgLoad(aImapMailFolder, aMsgWindow, &interrupted);
+ }
+ }
+ if (NS_SUCCEEDED(rv) && docShell)
+ {
+ NS_ASSERTION(!aConvertDataToText, "can't convert to text when using docshell");
+ rv = docShell->LoadURI(url, nullptr, nsIWebNavigation::LOAD_FLAGS_NONE, false);
+ }
+ else
+ {
+ nsCOMPtr<nsIStreamListener> streamListener = do_QueryInterface(aDisplayConsumer, &rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aImapUrl, &rv);
+ if (aMsgWindow && mailnewsUrl)
+ mailnewsUrl->SetMsgWindow(aMsgWindow);
+ if (NS_SUCCEEDED(rv) && streamListener)
+ {
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ if (NS_SUCCEEDED(rv) && mailnewsUrl)
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ rv = NewChannel(url, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we need a load group to hold onto the channel. When the request is finished,
+ // it'll get removed from the load group, and the channel will go away,
+ // which will free the load group.
+ if (!loadGroup)
+ loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+
+ rv = channel->SetLoadGroup(loadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aConvertDataToText)
+ {
+ nsCOMPtr<nsIStreamListener> conversionListener;
+ nsCOMPtr<nsIStreamConverterService> streamConverter = do_GetService("@mozilla.org/streamConverters;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = streamConverter->AsyncConvertData("message/rfc822",
+ "*/*", streamListener, channel, getter_AddRefs(conversionListener));
+ NS_ENSURE_SUCCESS(rv, rv);
+ streamListener = conversionListener; // this is our new listener.
+ }
+
+ nsCOMPtr<nsISupports> aCtxt = do_QueryInterface(url);
+ // now try to open the channel passing in our display consumer as the listener
+ rv = channel->AsyncOpen(streamListener, aCtxt);
+ }
+ else // do what we used to do before
+ {
+ // I'd like to get rid of this code as I believe that we always get a docshell
+ // or stream listener passed into us in this method but i'm not sure yet...
+ // I'm going to use an assert for now to figure out if this is ever getting called
+#if defined(DEBUG_mscott) || defined(DEBUG_bienvenu)
+ NS_ERROR("oops...someone still is reaching this part of the code");
+#endif
+ rv = GetImapConnectionAndLoadUrl(aImapUrl,
+ aDisplayConsumer, aURL);
+ }
+ }
+ return rv;
+}
+
+// this method streams a message to the passed in consumer, with an optional stream converter
+// and additional header (e.g., "header=filter")
+NS_IMETHODIMP nsImapService::StreamMessage(const char *aMessageURI,
+ nsISupports *aConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ bool aConvertData,
+ const nsACString &aAdditionalHeader,
+ bool aLocalOnly,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsAutoCString mimePart;
+ nsAutoCString folderURI;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeImapURI(nsDependentCString(aMessageURI), getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (msgKey.IsEmpty())
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ rv = nsParseImapMessageURI(aMessageURI, folderURI, &key, getter_Copies(mimePart));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(do_QueryInterface(folder, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(nsDependentCString(aMessageURI), getter_AddRefs(imapUrl),
+ folder, aUrlListener, urlSpec, hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(imapUrl));
+ nsCOMPtr<nsIURI> url(do_QueryInterface(imapUrl));
+
+ // This option is used by the JS Mime Emitter, in case we want a cheap
+ // streaming, for example, if we just want a quick look at some header,
+ // without having to download all the attachments...
+
+ uint32_t messageSize = 0;
+ imapMessageSink->GetMessageSizeFromDB(msgKey.get(), &messageSize);
+ nsAutoCString additionalHeader(aAdditionalHeader);
+ bool fetchOnDemand =
+ additionalHeader.Find("&fetchCompleteMessage=false") != kNotFound &&
+ messageSize > (uint32_t) gMIMEOnDemandThreshold;
+ imapUrl->SetFetchPartsOnDemand(fetchOnDemand);
+
+ // We need to add the fetch command here for the cache lookup to behave correctly
+ rv = AddImapFetchToUrl(url, folder, msgKey, additionalHeader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+
+ msgurl->SetMsgWindow(aMsgWindow);
+ rv = msgurl->GetServer(getter_AddRefs(aMsgIncomingServer));
+
+ // Try to check if the message is offline
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ msgurl->SetMsgIsInLocalCache(hasMsgOffline);
+ imapUrl->SetLocalFetchOnly(aLocalOnly);
+
+ // If we don't have the message available locally, and we can't get it over
+ // the network, return with an error
+ if (aLocalOnly || WeAreOffline())
+ {
+ bool isMsgInMemCache = false;
+ if (!hasMsgOffline)
+ {
+ rv = IsMsgInMemCache(url, folder, &isMsgInMemCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isMsgInMemCache)
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ bool shouldStoreMsgOffline = false;
+ folder->ShouldStoreMsgOffline(key, &shouldStoreMsgOffline);
+ imapUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
+ rv = GetMessageFromUrl(imapUrl, nsIImapUrl::nsImapMsgFetchPeek, folder,
+ imapMessageSink, aMsgWindow, aConsumer,
+ aConvertData, aURL);
+ }
+ }
+ return rv;
+}
+
+// this method streams a message's headers to the passed in consumer.
+NS_IMETHODIMP nsImapService::StreamHeaders(const char *aMessageURI,
+ nsIStreamListener *aConsumer,
+ nsIUrlListener *aUrlListener,
+ bool aLocalOnly,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+ NS_ENSURE_ARG_POINTER(aConsumer);
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsAutoCString folderURI;
+ nsCString mimePart;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeImapURI(nsDependentCString(aMessageURI), getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (msgKey.IsEmpty())
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ rv = nsParseImapMessageURI(aMessageURI, folderURI, &key, getter_Copies(mimePart));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ if (hasMsgOffline)
+ {
+ int64_t messageOffset;
+ uint32_t messageSize;
+ folder->GetOfflineFileStream(key, &messageOffset, &messageSize, getter_AddRefs(inputStream));
+ if (inputStream)
+ return MsgStreamMsgHeaders(inputStream, aConsumer);
+ }
+ }
+
+ if (aLocalOnly)
+ return NS_ERROR_FAILURE;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::IsMsgInMemCache(nsIURI *aUrl,
+ nsIMsgFolder *aImapMailFolder,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+ *aResult = false;
+
+ // Poke around in the memory cache
+ if (mCacheStorage)
+ {
+ nsAutoCString urlSpec;
+ aUrl->GetSpec(urlSpec);
+
+ // Strip any query qualifiers.
+ bool truncated = false;
+ int32_t ind = urlSpec.FindChar('?');
+ if (ind != kNotFound) {
+ urlSpec.SetLength(ind);
+ truncated = true;
+ }
+ ind = urlSpec.Find("/;");
+ if (ind != kNotFound) {
+ urlSpec.SetLength(ind);
+ truncated = true;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIImapMailFolderSink> folderSink(do_QueryInterface(aImapMailFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t uidValidity = -1;
+ folderSink->GetUidValidity(&uidValidity);
+ // stick the uid validity in front of the url, so that if the uid validity
+ // changes, we won't re-use the wrong cache entries.
+ nsAutoCString extension;
+ extension.AppendInt(uidValidity, 16);
+
+ bool exists;
+ if (truncated) {
+ nsCOMPtr<nsIURI> newUri;
+ aUrl->Clone(getter_AddRefs(newUri));
+ newUri->SetSpec(urlSpec);
+ rv = mCacheStorage->Exists(newUri, extension, &exists);
+ } else {
+ rv = mCacheStorage->Exists(aUrl, extension, &exists);
+ }
+ if (NS_SUCCEEDED(rv) && exists) {
+ *aResult = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsImapService::CreateStartOfImapUrl(const nsACString &aImapURI,
+ nsIImapUrl **imapUrl,
+ nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsACString &urlSpec,
+ char &hierarchyDelimiter)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsCString hostname;
+ nsCString username;
+ nsCString escapedUsername;
+
+ nsresult rv = aImapMailFolder->GetHostname(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aImapMailFolder->GetUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!username.IsEmpty())
+ MsgEscapeString(username, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+
+ int32_t port = nsIImapUrl::DEFAULT_IMAP_PORT;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = aImapMailFolder->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv))
+ {
+ server->GetPort(&port);
+ if (port == -1 || port == 0) port = nsIImapUrl::DEFAULT_IMAP_PORT;
+ }
+
+ // now we need to create an imap url to load into the connection. The url
+ // needs to represent a select folder action.
+ rv = CallCreateInstance(kImapUrlCID, imapUrl);
+ if (NS_SUCCEEDED(rv) && *imapUrl)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(*imapUrl, &rv);
+ if (NS_SUCCEEDED(rv) && mailnewsUrl && aUrlListener)
+ mailnewsUrl->RegisterListener(aUrlListener);
+ nsCOMPtr<nsIMsgMessageUrl> msgurl(do_QueryInterface(*imapUrl));
+ (*imapUrl)->SetExternalLinkUrl(false);
+ msgurl->SetUri(PromiseFlatCString(aImapURI).get());
+
+ urlSpec = "imap://";
+ urlSpec.Append(escapedUsername);
+ urlSpec.Append('@');
+ urlSpec.Append(hostname);
+ urlSpec.Append(':');
+
+ nsAutoCString portStr;
+ portStr.AppendInt(port);
+ urlSpec.Append(portStr);
+
+ // *** jefft - force to parse the urlSpec in order to search for
+ // the correct incoming server
+ rv = mailnewsUrl->SetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aImapMailFolder);
+ if (imapFolder)
+ imapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
+ }
+ return rv;
+}
+
+/* fetching the headers of RFC822 messages */
+/* imap4://HOST>header><UID/SEQUENCE>>MAILBOXPATH>x */
+/* 'x' is the message UID or sequence number list */
+/* will not affect the 'SEEN' flag */
+NS_IMETHODIMP nsImapService::GetHeaders(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ const nsACString &messageIdentifierList,
+ bool messageIdsAreUID)
+{
+ // create a protocol instance to handle the request.
+ // NOTE: once we start working with multiple connections, this step will be much more complicated...but for now
+ // just create a connection and process the request.
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aImapMailFolder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgFetch);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ urlSpec.Append("/header>");
+ urlSpec.Append(messageIdsAreUID ? uidString : sequenceString);
+ urlSpec.Append(">");
+ urlSpec.Append(char (hierarchyDelimiter));
+
+ nsCString folderName;
+
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(messageIdentifierList);
+ rv = uri->SetSpec(urlSpec);
+
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+
+/* peeking at the start of msg bodies */
+/* imap4://HOST>header><UID>>MAILBOXPATH>x>n */
+/* 'x' is the message UID */
+/* 'n' is the number of bytes to fetch */
+/* will not affect the 'SEEN' flag */
+NS_IMETHODIMP nsImapService::GetBodyStart(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ const nsACString &messageIdentifierList,
+ int32_t numBytes,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aImapMailFolder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgPreview);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ urlSpec.Append("/previewBody>");
+ urlSpec.Append(uidString);
+ urlSpec.Append(">");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(messageIdentifierList);
+ urlSpec.Append(">");
+ urlSpec.AppendInt(numBytes);
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapService::FolderCommand(nsIMsgFolder *imapMailFolder,
+ nsIUrlListener *urlListener,
+ const char *aCommand,
+ nsImapAction imapAction,
+ nsIMsgWindow *msgWindow,
+ nsIURI **url)
+{
+ NS_ENSURE_ARG_POINTER(imapMailFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(imapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ imapMailFolder, urlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ rv = imapUrl->SetImapAction(imapAction);
+ rv = SetImapUrlSink(imapMailFolder, imapUrl);
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+ if (mailnewsurl)
+ mailnewsurl->SetMsgWindow(msgWindow);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ urlSpec.Append(aCommand);
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(imapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapService::VerifyLogon(nsIMsgFolder *aFolder, nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow, nsIURI **aURL)
+{
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char delimiter = '/'; // shouldn't matter what is is.
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aFolder,
+ aUrlListener, urlSpec, delimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ mailNewsUrl->SetSuppressErrorMsgs(true);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ rv = SetImapUrlSink(aFolder, imapUrl);
+ urlSpec.Append("/verifyLogon");
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, nullptr);
+ if (aURL)
+ uri.forget(aURL);
+ }
+ return rv;
+}
+
+// Noop, used to update a folder (causes server to send changes).
+NS_IMETHODIMP nsImapService::Noop(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ "/selectnoop>", nsIImapUrl::nsImapSelectNoopFolder, nullptr, aURL);
+}
+
+// FolderStatus, used to update message counts
+NS_IMETHODIMP nsImapService::UpdateFolderStatus(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ "/folderstatus>", nsIImapUrl::nsImapFolderStatus, nullptr, aURL);
+}
+
+// Expunge, used to "compress" an imap folder,removes deleted messages.
+NS_IMETHODIMP nsImapService::Expunge(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ "/Expunge>", nsIImapUrl::nsImapExpungeFolder, aMsgWindow, aURL);
+}
+
+/* old-stle biff that doesn't download headers */
+NS_IMETHODIMP nsImapService::Biff(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ uint32_t uidHighWater)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ // static const char *formatString = "biff>%c%s>%ld";
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aImapMailFolder, aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapExpungeFolder);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+ if (NS_SUCCEEDED(rv))
+ {
+ urlSpec.Append("/Biff>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.AppendInt(uidHighWater);
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DeleteFolder(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ // If it's an aol server then use 'deletefolder' url to
+ // remove all msgs first and then remove the folder itself.
+ bool removeFolderAndMsgs = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(aImapMailFolder->GetServer(getter_AddRefs(server))) && server)
+ {
+ nsCOMPtr <nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ if (imapServer)
+ imapServer->GetIsAOLServer(&removeFolderAndMsgs);
+ }
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ removeFolderAndMsgs ? "/deletefolder>" : "/delete>",
+ nsIImapUrl::nsImapDeleteFolder, aMsgWindow, aURL);
+}
+
+NS_IMETHODIMP nsImapService::DeleteMessages(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ const nsACString &messageIdentifierList,
+ bool messageIdsAreUID)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ // create a protocol instance to handle the request.
+ // NOTE: once we start working with multiple connections, this step will be much more complicated...but for now
+ // just create a connection and process the request.
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aImapMailFolder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgFetch);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ urlSpec.Append("/deletemsg>");
+ urlSpec.Append(messageIdsAreUID ? uidString : sequenceString);
+ urlSpec.Append(">");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(messageIdentifierList);
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+// Delete all messages in a folder, used to empty trash
+NS_IMETHODIMP nsImapService::DeleteAllMessages(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ "/deleteallmsgs>", nsIImapUrl::nsImapSelectNoopFolder, nullptr, aURL);
+}
+
+NS_IMETHODIMP nsImapService::AddMessageFlags(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ const nsACString &messageIdentifierList,
+ imapMessageFlagsType flags,
+ bool messageIdsAreUID)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return DiddleFlags(aImapMailFolder, aUrlListener, aURL, messageIdentifierList,
+ "addmsgflags", flags, messageIdsAreUID);
+}
+
+NS_IMETHODIMP nsImapService::SubtractMessageFlags(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ const nsACString &messageIdentifierList,
+ imapMessageFlagsType flags,
+ bool messageIdsAreUID)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return DiddleFlags(aImapMailFolder, aUrlListener, aURL, messageIdentifierList,
+ "subtractmsgflags", flags, messageIdsAreUID);
+}
+
+NS_IMETHODIMP nsImapService::SetMessageFlags(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ const nsACString &messageIdentifierList,
+ imapMessageFlagsType flags,
+ bool messageIdsAreUID)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return DiddleFlags(aImapMailFolder, aUrlListener, aURL, messageIdentifierList,
+ "setmsgflags", flags, messageIdsAreUID);
+}
+
+nsresult nsImapService::DiddleFlags(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ const nsACString &messageIdentifierList,
+ const char *howToDiddle,
+ imapMessageFlagsType flags,
+ bool messageIdsAreUID)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ // create a protocol instance to handle the request.
+ // NOTE: once we start working with multiple connections,
+ // this step will be much more complicated...but for now
+ // just create a connection and process the request.
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aImapMailFolder, aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgFetch);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ urlSpec.Append('/');
+ urlSpec.Append(howToDiddle);
+ urlSpec.Append('>');
+ urlSpec.Append(messageIdsAreUID ? uidString : sequenceString);
+ urlSpec.Append(">");
+ urlSpec.Append(hierarchyDelimiter);
+ nsCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(messageIdentifierList);
+ urlSpec.Append('>');
+ urlSpec.AppendInt(flags);
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapService::SetImapUrlSink(nsIMsgFolder *aMsgFolder, nsIImapUrl *aImapUrl)
+{
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ nsCOMPtr<nsIImapServerSink> imapServerSink;
+
+ rv = aMsgFolder->GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ {
+ imapServerSink = do_QueryInterface(incomingServer);
+ if (imapServerSink)
+ aImapUrl->SetImapServerSink(imapServerSink);
+ }
+
+ nsCOMPtr<nsIImapMailFolderSink> imapMailFolderSink = do_QueryInterface(aMsgFolder);
+ if (NS_SUCCEEDED(rv) && imapMailFolderSink)
+ aImapUrl->SetImapMailFolderSink(imapMailFolderSink);
+
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink = do_QueryInterface(aMsgFolder);
+ if (NS_SUCCEEDED(rv) && imapMessageSink)
+ aImapUrl->SetImapMessageSink(imapMessageSink);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aImapUrl);
+ mailnewsUrl->SetFolder(aMsgFolder);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::DiscoverAllFolders(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aImapMailFolder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED (rv))
+ {
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+ if (mailnewsurl)
+ mailnewsurl->SetMsgWindow(aMsgWindow);
+ urlSpec.Append("/discoverallboxes");
+ nsCOMPtr <nsIURI> url = do_QueryInterface(imapUrl, &rv);
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DiscoverAllAndSubscribedFolders(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsCOMPtr<nsIImapUrl> aImapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(aImapUrl), aImapMailFolder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && aImapUrl)
+ {
+ rv = SetImapUrlSink(aImapMailFolder, aImapUrl);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(aImapUrl);
+ urlSpec.Append("/discoverallandsubscribedboxes");
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(aImapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DiscoverChildren(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ const nsACString &folderPath,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsCOMPtr<nsIImapUrl> aImapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(aImapUrl), aImapMailFolder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED (rv))
+ {
+ rv = SetImapUrlSink(aImapMailFolder, aImapUrl);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (!folderPath.IsEmpty())
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(aImapUrl);
+ urlSpec.Append("/discoverchildren>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderPath);
+ rv = uri->SetSpec(urlSpec);
+
+ // Make sure the uri has the same hierarchy separator as the one in msg folder
+ // obj if it's not kOnlineHierarchySeparatorUnknown (ie, '^').
+ char uriDelimiter;
+ nsresult rv1 = aImapUrl->GetOnlineSubDirSeparator(&uriDelimiter);
+ if (NS_SUCCEEDED (rv1) && hierarchyDelimiter != kOnlineHierarchySeparatorUnknown &&
+ uriDelimiter != hierarchyDelimiter)
+ aImapUrl->SetOnlineSubDirSeparator(hierarchyDelimiter);
+
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(aImapUrl, nullptr, aURL);
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::OnlineMessageCopy(nsIMsgFolder *aSrcFolder,
+ const nsACString &messageIds,
+ nsIMsgFolder *aDstFolder,
+ bool idsAreUids,
+ bool isMove,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ nsISupports *copyState,
+ nsIMsgWindow *aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aSrcFolder);
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> srcServer;
+ nsCOMPtr<nsIMsgIncomingServer> dstServer;
+
+ rv = aSrcFolder->GetServer(getter_AddRefs(srcServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aDstFolder->GetServer(getter_AddRefs(dstServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool sameServer;
+ rv = dstServer->Equals(srcServer, &sameServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!sameServer)
+ {
+ NS_ASSERTION(false, "can't use this method to copy across servers");
+ // *** can only take message from the same imap host and user accnt
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aSrcFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aSrcFolder, aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv))
+ {
+ SetImapUrlSink(aSrcFolder, imapUrl);
+ imapUrl->SetCopyState(copyState);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(imapUrl));
+
+ msgurl->SetMsgWindow(aMsgWindow);
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ if (isMove)
+ urlSpec.Append("/onlinemove>");
+ else
+ urlSpec.Append("/onlinecopy>");
+ if (idsAreUids)
+ urlSpec.Append(uidString);
+ else
+ urlSpec.Append(sequenceString);
+ urlSpec.Append('>');
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aSrcFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(messageIds);
+ urlSpec.Append('>');
+ urlSpec.Append(hierarchyDelimiter);
+ folderName.Adopt(strdup(""));
+ GetFolderName(aDstFolder, folderName);
+ urlSpec.Append(folderName);
+
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ return rv;
+}
+
+nsresult nsImapService::OfflineAppendFromFile(nsIFile *aFile,
+ nsIURI *aUrl,
+ nsIMsgFolder* aDstFolder,
+ const nsACString &messageId, // to be replaced
+ bool inSelectedState, // needs to be in
+ nsIUrlListener *aListener,
+ nsIURI **aURL,
+ nsISupports *aCopyState)
+{
+ nsCOMPtr<nsIMsgDatabase> destDB;
+ nsresult rv = aDstFolder->GetMsgDatabase(getter_AddRefs(destDB));
+ // ### might need to send some notifications instead of just returning
+
+ bool isLocked;
+ aDstFolder->GetLocked(&isLocked);
+ if (isLocked)
+ return NS_MSG_FOLDER_BUSY;
+
+ if (NS_SUCCEEDED(rv) && destDB)
+ {
+ nsMsgKey fakeKey;
+ destDB->GetNextFakeOfflineMsgKey(&fakeKey);
+
+ nsCOMPtr <nsIMsgOfflineImapOperation> op;
+ rv = destDB->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ nsCString destFolderUri;
+ aDstFolder->GetURI(destFolderUri);
+ op->SetOperation(nsIMsgOfflineImapOperation::kAppendDraft); // ### do we care if it's a template?
+ op->SetDestinationFolderURI(destFolderUri.get());
+ nsCOMPtr <nsIOutputStream> offlineStore;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsCOMPtr<nsIMsgIncomingServer> dstServer;
+ nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
+
+ aDstFolder->GetServer(getter_AddRefs(dstServer));
+ rv = dstServer->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = destDB->CreateNewHdr(fakeKey, getter_AddRefs(newMsgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aDstFolder->GetOfflineStoreOutputStream(newMsgHdr, getter_AddRefs(offlineStore));
+
+ if (NS_SUCCEEDED(rv) && offlineStore)
+ {
+ int64_t curOfflineStorePos = 0;
+ nsCOMPtr <nsISeekableStream> seekable = do_QueryInterface(offlineStore);
+ if (seekable)
+ seekable->Tell(&curOfflineStorePos);
+ else
+ {
+ NS_ERROR("needs to be a random store!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr <nsIInputStream> inputStream;
+ nsCOMPtr <nsIMsgParseMailMsgState> msgParser = do_CreateInstance(NS_PARSEMAILMSGSTATE_CONTRACTID, &rv);
+ msgParser->SetMailDB(destDB);
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+ if (NS_SUCCEEDED(rv) && inputStream)
+ {
+ // now, copy the temp file to the offline store for the dest folder.
+ RefPtr<nsMsgLineStreamBuffer> inputStreamBuffer =
+ new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE,
+ true, // allocate new lines
+ false); // leave CRLFs on the returned string
+ int64_t fileSize;
+ aFile->GetFileSize(&fileSize);
+ uint32_t bytesWritten;
+ rv = NS_OK;
+// rv = inputStream->Read(inputBuffer, inputBufferSize, &bytesRead);
+// if (NS_SUCCEEDED(rv) && bytesRead > 0)
+ msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
+ msgParser->SetNewMsgHdr(newMsgHdr);
+ // set the new key to fake key so the msg hdr will have that for a key
+ msgParser->SetNewKey(fakeKey);
+ bool needMoreData = false;
+ char * newLine = nullptr;
+ uint32_t numBytesInLine = 0;
+ do
+ {
+ newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine, needMoreData);
+ if (newLine)
+ {
+ msgParser->ParseAFolderLine(newLine, numBytesInLine);
+ rv = offlineStore->Write(newLine, numBytesInLine, &bytesWritten);
+ NS_Free(newLine);
+ }
+ } while (newLine);
+ msgParser->FinishHeader();
+
+ nsCOMPtr<nsIMsgDBHdr> fakeHdr;
+ msgParser->GetNewMsgHdr(getter_AddRefs(fakeHdr));
+ if (fakeHdr)
+ {
+ if (NS_SUCCEEDED(rv) && fakeHdr)
+ {
+ uint32_t resultFlags;
+ fakeHdr->SetMessageOffset(curOfflineStorePos);
+ fakeHdr->OrFlags(nsMsgMessageFlags::Offline | nsMsgMessageFlags::Read, &resultFlags);
+ fakeHdr->SetOfflineMessageSize(fileSize);
+ destDB->AddNewHdrToDB(fakeHdr, true /* notify */);
+ aDstFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
+ if (msgStore)
+ msgStore->FinishNewMessage(offlineStore, fakeHdr);
+ }
+ }
+ // tell the listener we're done.
+ inputStream->Close();
+ inputStream = nullptr;
+ aListener->OnStopRunningUrl(aUrl, NS_OK);
+ }
+ offlineStore->Close();
+ }
+ }
+ }
+
+ if (destDB)
+ destDB->Close(true);
+ return rv;
+}
+
+/* append message from file url */
+/* imap://HOST>appendmsgfromfile>DESTINATIONMAILBOXPATH */
+/* imap://HOST>appenddraftfromfile>DESTINATIONMAILBOXPATH>UID>messageId */
+NS_IMETHODIMP nsImapService::AppendMessageFromFile(nsIFile *aFile,
+ nsIMsgFolder *aDstFolder,
+ const nsACString &messageId, // to be replaced
+ bool idsAreUids,
+ bool inSelectedState, // needs to be in
+ nsIUrlListener *aListener,
+ nsIURI **aURL,
+ nsISupports *aCopyState,
+ nsIMsgWindow *aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aDstFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aDstFolder, aListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(imapUrl);
+ if (msgUrl && aMsgWindow)
+ {
+ // we get the loadGroup from msgWindow
+ msgUrl->SetMsgWindow(aMsgWindow);
+ }
+
+ SetImapUrlSink(aDstFolder, imapUrl);
+ imapUrl->SetMsgFile(aFile);
+ imapUrl->SetCopyState(aCopyState);
+
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ if (inSelectedState)
+ urlSpec.Append("/appenddraftfromfile>");
+ else
+ urlSpec.Append("/appendmsgfromfile>");
+
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aDstFolder, folderName);
+ urlSpec.Append(folderName);
+
+ if (inSelectedState)
+ {
+ urlSpec.Append('>');
+ if (idsAreUids)
+ urlSpec.Append(uidString);
+ else
+ urlSpec.Append(sequenceString);
+ urlSpec.Append('>');
+ if (!messageId.IsEmpty())
+ urlSpec.Append(messageId);
+ }
+
+ rv = uri->SetSpec(urlSpec);
+ if (WeAreOffline())
+ {
+ // handle offline append to drafts or templates folder here.
+ return OfflineAppendFromFile(aFile, uri, aDstFolder, messageId, inSelectedState, aListener, aURL, aCopyState);
+ }
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ return rv;
+}
+
+nsresult nsImapService::GetImapConnectionAndLoadUrl(nsIImapUrl *aImapUrl,
+ nsISupports *aConsumer,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+
+ bool isValidUrl;
+ aImapUrl->GetValidUrl(&isValidUrl);
+ if (!isValidUrl)
+ return NS_ERROR_FAILURE;
+
+ if (WeAreOffline())
+ {
+ nsImapAction imapAction;
+
+ // the only thing we can do offline is fetch messages.
+ // ### TODO - need to look at msg copy, save attachment, etc. when we
+ // have offline message bodies.
+ aImapUrl->GetImapAction(&imapAction);
+ if (imapAction != nsIImapUrl::nsImapMsgFetch && imapAction != nsIImapUrl::nsImapSaveMessageToDisk)
+ return NS_MSG_ERROR_OFFLINE;
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(aImapUrl);
+ nsresult rv = msgUrl->GetServer(getter_AddRefs(aMsgIncomingServer));
+
+ if (aURL)
+ {
+ nsCOMPtr<nsIURI> msgUrlUri = do_QueryInterface(msgUrl);
+ msgUrlUri.swap(*aURL);
+ }
+
+ if (NS_SUCCEEDED(rv) && aMsgIncomingServer)
+ {
+ nsCOMPtr<nsIImapIncomingServer> aImapServer(do_QueryInterface(aMsgIncomingServer, &rv));
+ if (NS_SUCCEEDED(rv) && aImapServer)
+ rv = aImapServer->GetImapConnectionAndLoadUrl(aImapUrl, aConsumer);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::MoveFolder(nsIMsgFolder *srcFolder,
+ nsIMsgFolder *dstFolder,
+ nsIUrlListener *urlListener,
+ nsIMsgWindow *msgWindow,
+ nsIURI **url)
+{
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ NS_ENSURE_ARG_POINTER(dstFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+
+ char default_hierarchyDelimiter = GetHierarchyDelimiter(dstFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), dstFolder,
+ urlListener, urlSpec, default_hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ rv = SetImapUrlSink(dstFolder, imapUrl);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ if (mailNewsUrl)
+ mailNewsUrl->SetMsgWindow(msgWindow);
+ char hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
+ nsCString folderName;
+
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+ GetFolderName(srcFolder, folderName);
+ urlSpec.Append("/movefolderhierarchy>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ GetFolderName(dstFolder, folderName);
+ if (!folderName.IsEmpty())
+ {
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ }
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ {
+ GetFolderName(srcFolder, folderName);
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::RenameLeaf(nsIMsgFolder *srcFolder,
+ const nsAString &newLeafName,
+ nsIUrlListener *urlListener,
+ nsIMsgWindow *msgWindow,
+ nsIURI **url)
+{
+ NS_ENSURE_ARG_POINTER(srcFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(srcFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), srcFolder,
+ urlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = SetImapUrlSink(srcFolder, imapUrl);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ if (mailNewsUrl)
+ mailNewsUrl->SetMsgWindow(msgWindow);
+ nsCString folderName;
+ GetFolderName(srcFolder, folderName);
+ urlSpec.Append("/rename>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(hierarchyDelimiter);
+ nsAutoCString cStrFolderName;
+ // Unescape the name before looking for parent path
+ MsgUnescapeString(folderName, 0, cStrFolderName);
+ int32_t leafNameStart = cStrFolderName.RFindChar(hierarchyDelimiter);
+ if (leafNameStart != -1)
+ {
+ cStrFolderName.SetLength(leafNameStart+1);
+ urlSpec.Append(cStrFolderName);
+ }
+
+ nsAutoCString utfNewName;
+ CopyUTF16toMUTF7(PromiseFlatString(newLeafName), utfNewName);
+ nsCString escapedNewName;
+ MsgEscapeString(utfNewName, nsINetUtil::ESCAPE_URL_PATH, escapedNewName);
+ nsCString escapedSlashName;
+ rv = nsImapUrl::EscapeSlashes(escapedNewName.get(), getter_Copies(escapedSlashName));
+ NS_ENSURE_SUCCESS(rv, rv);
+ urlSpec.Append(escapedSlashName);
+
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ } // if (NS_SUCCEEDED(rv))
+ } // if (NS_SUCCEEDED(rv) && imapUrl)
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::CreateFolder(nsIMsgFolder *parent,
+ const nsAString &newFolderName,
+ nsIUrlListener *urlListener,
+ nsIURI **url)
+{
+ NS_ENSURE_ARG_POINTER(parent);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(parent);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), parent,
+ urlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ rv = SetImapUrlSink(parent, imapUrl);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ nsCString folderName;
+ GetFolderName(parent, folderName);
+ urlSpec.Append("/create>");
+ urlSpec.Append(hierarchyDelimiter);
+ if (!folderName.IsEmpty())
+ {
+ nsCString canonicalName;
+ nsImapUrl::ConvertToCanonicalFormat(folderName.get(),
+ hierarchyDelimiter,
+ getter_Copies(canonicalName));
+ urlSpec.Append(canonicalName);
+ urlSpec.Append(hierarchyDelimiter);
+ }
+
+ nsAutoCString utfNewName;
+ rv = CopyUTF16toMUTF7(PromiseFlatString(newFolderName), utfNewName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString escapedFolderName;
+ MsgEscapeString(utfNewName, nsINetUtil::ESCAPE_URL_PATH, escapedFolderName);
+ urlSpec.Append(escapedFolderName);
+
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ } // if (NS_SUCCEEDED(rv))
+ } // if (NS_SUCCEEDED(rv) && imapUrl)
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::EnsureFolderExists(nsIMsgFolder *parent,
+ const nsAString &newFolderName,
+ nsIUrlListener *urlListener,
+ nsIURI **url)
+{
+ NS_ENSURE_ARG_POINTER(parent);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(parent);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), parent, urlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ rv = SetImapUrlSink(parent, imapUrl);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ nsCString folderName;
+ GetFolderName(parent, folderName);
+ urlSpec.Append("/ensureExists>");
+ urlSpec.Append(hierarchyDelimiter);
+ if (!folderName.IsEmpty())
+ {
+ urlSpec.Append(folderName);
+ urlSpec.Append(hierarchyDelimiter);
+ }
+ nsAutoCString utfNewName;
+ CopyUTF16toMUTF7(PromiseFlatString(newFolderName), utfNewName);
+ nsCString escapedFolderName;
+ MsgEscapeString(utfNewName, nsINetUtil::ESCAPE_URL_PATH, escapedFolderName);
+ urlSpec.Append(escapedFolderName);
+
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ } // if (NS_SUCCEEDED(rv))
+ } // if (NS_SUCCEEDED(rv) && imapUrl)
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::ListFolder(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ "/listfolder>", nsIImapUrl::nsImapListFolder, nullptr, aURL);
+}
+
+NS_IMETHODIMP nsImapService::GetScheme(nsACString &aScheme)
+{
+ aScheme.Assign("imap");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetDefaultPort(int32_t *aDefaultPort)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultPort);
+ *aDefaultPort = nsIImapUrl::DEFAULT_IMAP_PORT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_STD | URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ URI_DANGEROUS_TO_LOAD | ALLOWS_PROXY | URI_FORBIDS_COOKIE_ACCESS
+#ifdef IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ | ORIGIN_IS_FULL_SPEC
+#endif
+ ;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::AllowPort(int32_t port, const char *scheme, bool *aRetVal)
+{
+ // allow imap to run on any port
+ *aRetVal = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetDefaultDoBiff(bool *aDoBiff)
+{
+ NS_ENSURE_ARG_POINTER(aDoBiff);
+ // by default, do biff for IMAP servers
+ *aDoBiff = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetDefaultServerPort(bool isSecure, int32_t *aDefaultPort)
+{
+ nsresult rv = NS_OK;
+
+ // Return Secure IMAP Port if secure option chosen i.e., if isSecure is TRUE
+ if (isSecure)
+ *aDefaultPort = nsIImapUrl::DEFAULT_IMAPS_PORT;
+ else
+ rv = GetDefaultPort(aDefaultPort);
+
+ return rv;
+}
+
+// this method first tries to find an exact username and hostname match with the given url
+// then, tries to find any account on the passed in imap host in case this is a url to
+// a shared imap folder.
+nsresult nsImapService::GetServerFromUrl(nsIImapUrl *aImapUrl, nsIMsgIncomingServer **aServer)
+{
+ nsresult rv;
+ nsCString folderName;
+ nsAutoCString userPass;
+ nsAutoCString hostName;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aImapUrl);
+
+ // if we can't get a folder name out of the url then I think this is an error
+ aImapUrl->CreateCanonicalSourceFolderPathString(getter_Copies(folderName));
+ if (folderName.IsEmpty())
+ {
+ rv = mailnewsUrl->GetFileName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = accountManager->FindServerByURI(mailnewsUrl, false, aServer);
+
+ // look for server with any user name, in case we're trying to subscribe
+ // to a folder with some one else's user name like the following
+ // "IMAP://userSharingFolder@server1/SharedFolderName"
+ if (NS_FAILED(rv) || !aServer)
+ {
+ nsAutoCString turl;
+ nsCOMPtr<nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailnewsUrl->GetSpec(turl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = url->SetSpec(turl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ url->SetUserPass(EmptyCString());
+ rv = accountManager->FindServerByURI(url, false, aServer);
+ if (*aServer)
+ aImapUrl->SetExternalLinkUrl(true);
+ }
+
+ // if we can't extract the imap server from this url then give up!!!
+ NS_ENSURE_TRUE(*aServer, NS_ERROR_FAILURE);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::NewURI(const nsACString &aSpec,
+ const char *aOriginCharset, // ignored
+ nsIURI *aBaseURI,
+ nsIURI **aRetVal)
+{
+ NS_ENSURE_ARG_POINTER(aRetVal);
+
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> aImapUrl = do_CreateInstance(kImapUrlCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now extract lots of fun information...
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aImapUrl);
+ // nsAutoCString unescapedSpec(aSpec);
+ // nsUnescape(unescapedSpec.BeginWriting());
+
+ // set the spec
+ if (aBaseURI)
+ {
+ nsAutoCString newSpec;
+ aBaseURI->Resolve(aSpec, newSpec);
+ rv = mailnewsUrl->SetSpec(newSpec);
+ }
+ else
+ {
+ rv = mailnewsUrl->SetSpec(aSpec);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString folderName;
+ // if we can't get a folder name out of the url then I think this is an error
+ aImapUrl->CreateCanonicalSourceFolderPathString(getter_Copies(folderName));
+ if (folderName.IsEmpty())
+ {
+ rv = mailnewsUrl->GetFileName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServerFromUrl(aImapUrl, getter_AddRefs(server));
+ // if we can't extract the imap server from this url then give up!!!
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(server, NS_ERROR_FAILURE);
+
+ // now try to get the folder in question...
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder && !folderName.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ nsCOMPtr<nsIMsgImapMailFolder> subFolder;
+ if (imapRoot)
+ {
+ imapRoot->FindOnlineSubFolder(folderName, getter_AddRefs(subFolder));
+ folder = do_QueryInterface(subFolder);
+ }
+
+ // If we can't find the folder, we can still create the URI
+ // in this low-level service. Cloning URIs where the folder
+ // isn't found is common when folders are renamed or moved.
+ // We also ignore return statuses here.
+ if (folder)
+ {
+ nsCOMPtr<nsIImapMessageSink> msgSink = do_QueryInterface(folder);
+ (void) aImapUrl->SetImapMessageSink(msgSink);
+
+ nsCOMPtr<nsIMsgFolder> msgFolder = do_QueryInterface(folder);
+ (void) SetImapUrlSink(msgFolder, aImapUrl);
+
+ nsCString messageIdString;
+ aImapUrl->GetListOfMessageIds(messageIdString);
+ if (!messageIdString.IsEmpty())
+ {
+ bool useLocalCache = false;
+ msgFolder->HasMsgOffline(strtoul(messageIdString.get(), nullptr, 10),
+ &useLocalCache);
+ mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+ }
+ }
+ }
+
+ // if we are fetching a part, be sure to enable fetch parts on demand
+ bool mimePartSelectorDetected = false;
+ aImapUrl->GetMimePartSelectorDetected(&mimePartSelectorDetected);
+ if (mimePartSelectorDetected)
+ aImapUrl->SetFetchPartsOnDemand(true);
+
+ // we got an imap url, so be sure to return it...
+ nsCOMPtr<nsIURI> imapUri = do_QueryInterface(aImapUrl);
+
+ imapUri.swap(*aRetVal);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::NewChannel(nsIURI *aURI, nsIChannel **aRetVal)
+{
+ return NewChannel2(aURI, nullptr, aRetVal);
+}
+
+NS_IMETHODIMP nsImapService::NewChannel2(nsIURI *aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel **aRetVal)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ *aRetVal = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aURI, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // imap can't open and return a channel right away...the url needs to go in the imap url queue
+ // until we find a connection which can run the url..in order to satisfy necko, we're going to return
+ // a mock imap channel....
+ nsCOMPtr<nsIImapMockChannel> channel = do_CreateInstance(kCImapMockChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ channel->SetURI(aURI);
+
+ rv = channel->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIDocShell> msgDocShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(msgDocShell));
+ if (msgDocShell)
+ {
+ nsCOMPtr <nsIProgressEventSink> prevEventSink;
+ channel->GetProgressEventSink(getter_AddRefs(prevEventSink));
+ nsCOMPtr<nsIInterfaceRequestor> docIR(do_QueryInterface(msgDocShell));
+ channel->SetNotificationCallbacks(docIR);
+ // we want to use our existing event sink.
+ if (prevEventSink)
+ channel->SetProgressEventSink(prevEventSink);
+ }
+ }
+ imapUrl->SetMockChannel(channel); // the imap url holds a weak reference so we can pass the channel into the imap protocol when we actually run the url
+
+ bool externalLinkUrl;
+ imapUrl->GetExternalLinkUrl(&externalLinkUrl);
+ if (externalLinkUrl)
+ {
+ // everything after here is to handle clicking on an external link. We only want
+ // to do this if we didn't run the url through the various nsImapService methods,
+ // which we can tell by seeing if the sinks have been setup on the url or not.
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = GetServerFromUrl(imapUrl, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString folderName;
+ imapUrl->CreateCanonicalSourceFolderPathString(getter_Copies(folderName));
+ if (folderName.IsEmpty())
+ {
+ nsCString escapedFolderName;
+ rv = mailnewsUrl->GetFileName(escapedFolderName);
+ if (!escapedFolderName.IsEmpty()) {
+ MsgUnescapeString(escapedFolderName, 0, folderName);
+ }
+ }
+ // if the parent is null, then the folder doesn't really exist, so see if the user
+ // wants to subscribe to it./
+ nsCOMPtr<nsIMsgFolder> aFolder;
+ // now try to get the folder in question...
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ nsCOMPtr <nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ nsCOMPtr <nsIMsgImapMailFolder> subFolder;
+ if (imapRoot)
+ {
+ imapRoot->FindOnlineSubFolder(folderName, getter_AddRefs(subFolder));
+ aFolder = do_QueryInterface(subFolder);
+ }
+ nsCOMPtr <nsIMsgFolder> parent;
+ if (aFolder)
+ aFolder->GetParent(getter_AddRefs(parent));
+ nsCString serverKey;
+ nsAutoCString userPass;
+ rv = mailnewsUrl->GetUserPass(userPass);
+ server->GetKey(serverKey);
+ nsCString fullFolderName;
+ if (parent)
+ fullFolderName = folderName;
+ if (!parent && !folderName.IsEmpty() && imapRoot) // Check if this folder is another user's folder.
+ {
+ fullFolderName = nsIMAPNamespaceList::GenerateFullFolderNameWithDefaultNamespace(serverKey.get(),
+ folderName.get(),
+ userPass.get(),
+ kOtherUsersNamespace,
+ nullptr);
+ // if this is another user's folder, let's see if we're already subscribed to it.
+ rv = imapRoot->FindOnlineSubFolder(fullFolderName, getter_AddRefs(subFolder));
+ aFolder = do_QueryInterface(subFolder);
+ if (aFolder)
+ aFolder->GetParent(getter_AddRefs(parent));
+ }
+ // if we couldn't get the fullFolderName, then we probably couldn't find
+ // the other user's namespace, in which case, we shouldn't try to subscribe to it.
+ if (!parent && !folderName.IsEmpty() && !fullFolderName.IsEmpty())
+ {
+ // this folder doesn't exist - check if the user wants to subscribe to this folder.
+ nsCOMPtr<nsIPrompt> dialog;
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ wwatch->GetNewPrompter(nullptr, getter_AddRefs(dialog));
+
+ nsString statusString, confirmText;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // need to convert folder name from mod-utf7 to unicode
+ nsAutoString unescapedName;
+ if (NS_FAILED(CopyMUTF7toUTF16(fullFolderName, unescapedName)))
+ CopyASCIItoUTF16(fullFolderName, unescapedName);
+ const char16_t *formatStrings[1] = { unescapedName.get() };
+
+ rv = bundle->FormatStringFromName(
+ u"imapSubscribePrompt",
+ formatStrings, 1, getter_Copies(confirmText));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool confirmResult = false;
+ rv = dialog->Confirm(nullptr, confirmText.get(), &confirmResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (confirmResult)
+ {
+ nsCOMPtr <nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ if (imapServer)
+ {
+ nsCOMPtr <nsIURI> subscribeURI;
+ // now we have the real folder name to try to subscribe to. Let's try running
+ // a subscribe url and returning that as the uri we've created.
+ // We need to convert this to unicode because that's what subscribe wants :-(
+ // It's already in mod-utf7.
+ nsAutoString unicodeName;
+ CopyASCIItoUTF16(fullFolderName, unicodeName);
+ rv = imapServer->SubscribeToFolder(unicodeName, true, getter_AddRefs(subscribeURI));
+ if (NS_SUCCEEDED(rv) && subscribeURI)
+ {
+ nsCOMPtr <nsIImapUrl> imapSubscribeUrl = do_QueryInterface(subscribeURI);
+ if (imapSubscribeUrl)
+ imapSubscribeUrl->SetExternalLinkUrl(true);
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(subscribeURI);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgWindow> msgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (NS_SUCCEEDED(rv) && msgWindow)
+ {
+ mailnewsUrl->SetMsgWindow(msgWindow);
+ nsCOMPtr <nsIUrlListener> listener = do_QueryInterface(rootFolder);
+ if (listener)
+ mailnewsUrl->RegisterListener(listener);
+ }
+ }
+ }
+ }
+ }
+ // error out this channel, so it'll stop trying to run the url.
+ rv = NS_ERROR_FAILURE;
+ *aRetVal = nullptr;
+ }
+ // this folder exists - check if this is a click on a link to the folder
+ // in which case, we'll select it.
+ else if (!fullFolderName.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgFolder> imapFolder;
+ nsCOMPtr<nsIImapServerSink> serverSink;
+
+ mailnewsUrl->GetFolder(getter_AddRefs(imapFolder));
+ imapUrl->GetImapServerSink(getter_AddRefs(serverSink));
+ // need to see if this is a link click - one way is to check if the url is set up correctly
+ // if not, it's probably a url click. We need a better way of doing this.
+ if (!imapFolder)
+ {
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgWindow> msgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (NS_SUCCEEDED(rv) && msgWindow)
+ {
+ nsCString uri;
+ rootFolder->GetURI(uri);
+ uri.Append('/');
+ uri.Append(fullFolderName);
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ msgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectFolder(uri);
+ // error out this channel, so it'll stop trying to run the url.
+ *aRetVal = nullptr;
+ rv = NS_ERROR_FAILURE;
+ }
+ else
+ {
+ // make sure the imap action is selectFolder, so the content type
+ // will be x-application-imapfolder, so ::HandleContent will
+ // know to open a new 3 pane window.
+ imapUrl->SetImapAction(nsIImapUrl::nsImapSelectFolder);
+ }
+ }
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ NS_IF_ADDREF(*aRetVal = channel);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::SetDefaultLocalPath(nsIFile *aPath)
+{
+ NS_ENSURE_ARG_POINTER(aPath);
+
+ return NS_SetPersistentFile(PREF_MAIL_ROOT_IMAP_REL, PREF_MAIL_ROOT_IMAP, aPath);
+}
+
+NS_IMETHODIMP nsImapService::GetDefaultLocalPath(nsIFile **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ bool havePref;
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_GetPersistentFile(PREF_MAIL_ROOT_IMAP_REL,
+ PREF_MAIL_ROOT_IMAP,
+ NS_APP_IMAP_MAIL_50_DIR,
+ havePref,
+ getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(localFile, NS_ERROR_FAILURE);
+
+ bool exists;
+ rv = localFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!havePref || !exists)
+ {
+ rv = NS_SetPersistentFile(PREF_MAIL_ROOT_IMAP_REL, PREF_MAIL_ROOT_IMAP, localFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
+ }
+
+ localFile.swap(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetServerIID(nsIID **aServerIID)
+{
+ *aServerIID = new nsIID(NS_GET_IID(nsIImapIncomingServer));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetRequiresUsername(bool *aRequiresUsername)
+{
+ NS_ENSURE_ARG_POINTER(aRequiresUsername);
+
+ *aRequiresUsername = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress)
+{
+ NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress);
+
+ *aPreflightPrettyNameWithEmailAddress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp)
+{
+ NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp);
+ *aCanLoginAtStartUp = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetCanDelete(bool *aCanDelete)
+{
+ NS_ENSURE_ARG_POINTER(aCanDelete);
+ *aCanDelete = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetCanDuplicate(bool *aCanDuplicate)
+{
+ NS_ENSURE_ARG_POINTER(aCanDuplicate);
+ *aCanDuplicate = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetCanGetMessages(bool *aCanGetMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetMessages);
+ *aCanGetMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages);
+ *aCanGetIncomingMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetShowComposeMsgLink(bool *showComposeMsgLink)
+{
+ NS_ENSURE_ARG_POINTER(showComposeMsgLink);
+ *showComposeMsgLink = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetFoldersCreatedAsync(bool *aAsyncCreation)
+{
+ NS_ENSURE_ARG_POINTER(aAsyncCreation);
+ *aAsyncCreation = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetListOfFoldersWithPath(nsIImapIncomingServer *aServer,
+ nsIMsgWindow *aMsgWindow,
+ const nsACString &folderPath)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(aServer);
+ if (!server)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && rootMsgFolder, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIUrlListener> listener = do_QueryInterface(aServer, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!listener)
+ return NS_ERROR_FAILURE;
+
+ // Locate the folder so that the correct hierarchical delimiter is used in the folder
+ // pathnames, otherwise root's (ie, '^') is used and this is wrong.
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ if (rootMsgFolder && !folderPath.IsEmpty())
+ {
+ // If the folder path contains 'INBOX' of any forms, we need to convert it to uppercase
+ // before finding it under the root folder. We do the same in PossibleImapMailbox().
+ nsAutoCString tempFolderName(folderPath);
+ nsAutoCString tokenStr, remStr, changedStr;
+ int32_t slashPos = tempFolderName.FindChar('/');
+ if (slashPos > 0)
+ {
+ tokenStr = StringHead(tempFolderName, slashPos);
+ remStr = Substring(tempFolderName, slashPos);
+ }
+ else
+ tokenStr.Assign(tempFolderName);
+
+ if (tokenStr.LowerCaseEqualsLiteral("inbox") &&
+ !tokenStr.EqualsLiteral("INBOX"))
+ changedStr.Append("INBOX");
+ else
+ changedStr.Append(tokenStr);
+
+ if (slashPos > 0 )
+ changedStr.Append(remStr);
+
+ rv = rootMsgFolder->FindSubFolder(changedStr, getter_AddRefs(msgFolder));
+ }
+ return DiscoverChildren(msgFolder, listener, folderPath, nullptr);
+}
+
+NS_IMETHODIMP nsImapService::GetListOfFoldersOnServer(nsIImapIncomingServer *aServer,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(aServer);
+ if (!server)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!rootMsgFolder)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIUrlListener> listener = do_QueryInterface(aServer, &rv);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && listener, NS_ERROR_FAILURE);
+
+ return DiscoverAllAndSubscribedFolders(rootMsgFolder, listener, nullptr);
+}
+
+NS_IMETHODIMP nsImapService::SubscribeFolder(nsIMsgFolder *aFolder,
+ const nsAString &aFolderName,
+ nsIUrlListener *urlListener,
+ nsIURI **url)
+{
+
+ return ChangeFolderSubscription(aFolder, aFolderName,
+ "/subscribe>", urlListener, url);
+}
+
+nsresult nsImapService::ChangeFolderSubscription(nsIMsgFolder *folder,
+ const nsAString &folderName,
+ const char *command,
+ nsIUrlListener *urlListener,
+ nsIURI **url)
+{
+ NS_ENSURE_ARG_POINTER(folder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), folder, urlListener,
+ urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ rv = SetImapUrlSink(folder, imapUrl);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+ urlSpec.Append(command);
+ urlSpec.Append(hierarchyDelimiter);
+ nsAutoCString utfFolderName;
+ rv = CopyUTF16toMUTF7(PromiseFlatString(folderName), utfFolderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString escapedFolderName;
+ MsgEscapeString(utfFolderName, nsINetUtil::ESCAPE_URL_PATH, escapedFolderName);
+ urlSpec.Append(escapedFolderName);
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::UnsubscribeFolder(nsIMsgFolder *aFolder,
+ const nsAString &aFolderName,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aUrl)
+{
+
+ return ChangeFolderSubscription(aFolder, aFolderName,
+ "/unsubscribe>", aUrlListener, aUrl);
+}
+
+NS_IMETHODIMP nsImapService::GetFolderAdminUrl(nsIMsgFolder *aImapMailFolder,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ "/refreshfolderurls>", nsIImapUrl::nsImapRefreshFolderUrls, aMsgWindow, aURL);
+}
+
+NS_IMETHODIMP nsImapService::IssueCommandOnMsgs(nsIMsgFolder *anImapFolder,
+ nsIMsgWindow *aMsgWindow,
+ const nsACString &aCommand,
+ const nsACString &uids,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(anImapFolder);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+ char hierarchyDelimiter = GetHierarchyDelimiter(anImapFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), anImapFolder, nullptr, urlSpec, hierarchyDelimiter);
+
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ // nsImapUrl::SetSpec() will set the imap action properly
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapUserDefinedMsgCommand);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ mailNewsUrl->SetUpdatingFolder(true);
+ rv = SetImapUrlSink(anImapFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString folderName;
+ GetFolderName(anImapFolder, folderName);
+ urlSpec.Append("/");
+ urlSpec.Append(aCommand);
+ urlSpec.Append(">");
+ urlSpec.Append(uidString);
+ urlSpec.Append(">");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(uids);
+ rv = mailNewsUrl->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ } // if we have a url to run....
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::FetchCustomMsgAttribute(nsIMsgFolder *anImapFolder,
+ nsIMsgWindow *aMsgWindow,
+ const nsACString &aAttribute,
+ const nsACString &uids,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(anImapFolder);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+ char hierarchyDelimiter = GetHierarchyDelimiter(anImapFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), anImapFolder,
+ nullptr, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ // nsImapUrl::SetSpec() will set the imap action properly
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapUserDefinedFetchAttribute);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ mailNewsUrl->SetUpdatingFolder(true);
+ rv = SetImapUrlSink(anImapFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString folderName;
+ GetFolderName(anImapFolder, folderName);
+ urlSpec.Append("/customFetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(uids);
+ urlSpec.Append(">");
+ urlSpec.Append(aAttribute);
+ rv = mailNewsUrl->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ } // if we have a url to run....
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::StoreCustomKeywords(nsIMsgFolder *anImapFolder,
+ nsIMsgWindow *aMsgWindow,
+ const nsACString &flagsToAdd,
+ const nsACString &flagsToSubtract,
+ const nsACString &uids,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(anImapFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+ char hierarchyDelimiter = GetHierarchyDelimiter(anImapFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), anImapFolder, nullptr, urlSpec, hierarchyDelimiter);
+
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ // nsImapUrl::SetSpec() will set the imap action properly
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgStoreCustomKeywords);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ mailNewsUrl->SetUpdatingFolder(true);
+ rv = SetImapUrlSink(anImapFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString folderName;
+ GetFolderName(anImapFolder, folderName);
+ urlSpec.Append("/customKeywords>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(uids);
+ urlSpec.Append(">");
+ urlSpec.Append(flagsToAdd);
+ urlSpec.Append(">");
+ urlSpec.Append(flagsToSubtract);
+ rv = mailNewsUrl->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ } // if we have a url to run....
+
+ return rv;
+}
+
+
+NS_IMETHODIMP nsImapService::DownloadMessagesForOffline(const nsACString &messageIds,
+ nsIMsgFolder *aFolder,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+ char hierarchyDelimiter = GetHierarchyDelimiter(aFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aFolder, nullptr,
+ urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl)
+ {
+ nsCOMPtr<nsIURI> runningURI;
+ // need to pass in stream listener in order to get the channel created correctly
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(do_QueryInterface(aFolder, &rv));
+ rv = FetchMessage(imapUrl, nsImapUrl::nsImapMsgDownloadForOffline,aFolder,
+ imapMessageSink, aMsgWindow, nullptr, messageIds,
+ false, EmptyCString(), getter_AddRefs(runningURI));
+ if (runningURI && aUrlListener)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(runningURI));
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(runningURI));
+ if (msgurl)
+ msgurl->RegisterListener(aUrlListener);
+ if (imapUrl)
+ imapUrl->SetStoreResultsOffline(true);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::MessageURIToMsgHdr(const char *uri, nsIMsgDBHdr **aRetVal)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(aRetVal);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey msgKey;
+ nsresult rv = DecomposeImapURI(nsDependentCString(uri),
+ getter_AddRefs(folder), &msgKey);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = folder->GetMessageHeader(msgKey, aRetVal);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::PlaybackAllOfflineOperations(nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aListener,
+ nsISupports **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ nsImapOfflineSync *goOnline = new nsImapOfflineSync(aMsgWindow, aListener, nullptr);
+ if (goOnline)
+ {
+ rv = goOnline->QueryInterface(NS_GET_IID(nsISupports), (void **) aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_SUCCEEDED(rv) && *aResult)
+ return goOnline->ProcessNextOperation();
+ }
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsImapService::DownloadAllOffineImapFolders(nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aListener)
+{
+ nsImapOfflineDownloader *downloadForOffline = new nsImapOfflineDownloader(aMsgWindow, aListener);
+ if (downloadForOffline)
+ {
+ // hold reference to this so it won't get deleted out from under itself.
+ NS_ADDREF(downloadForOffline);
+ nsresult rv = downloadForOffline->ProcessNextOperation();
+ NS_RELEASE(downloadForOffline);
+ return rv;
+ }
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsImapService::GetCacheStorage(nsICacheStorage **result)
+{
+ nsresult rv = NS_OK;
+ if (!mCacheStorage)
+ {
+ nsCOMPtr<nsICacheStorageService> cacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<MailnewsLoadContextInfo> lci =
+ new MailnewsLoadContextInfo(false, false, mozilla::NeckoOriginAttributes());
+
+ rv = cacheStorageService->MemoryCacheStorage(lci, getter_AddRefs(mCacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*result = mCacheStorage);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::HandleContent(const char *aContentType,
+ nsIInterfaceRequestor *aWindowContext,
+ nsIRequest *request)
+{
+ NS_ENSURE_ARG_POINTER(request);
+
+ nsresult rv;
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (PL_strcasecmp(aContentType, "x-application-imapfolder") == 0)
+ {
+ nsCOMPtr<nsIURI> uri;
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (uri)
+ {
+ request->Cancel(NS_BINDING_ABORTED);
+ nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uriStr;
+ rv = uri->GetSpec(uriStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // imap uri's are unescaped, so unescape the url.
+ nsCString unescapedUriStr;
+ MsgUnescapeString(uriStr, 0, unescapedUriStr);
+ nsCOMPtr <nsIMessengerWindowService> messengerWindowService = do_GetService(NS_MESSENGERWINDOWSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = messengerWindowService->OpenMessengerWindowWithUri("mail:3pane", unescapedUriStr.get(), nsMsgKey_None);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ else
+ {
+ // The content-type was not x-application-imapfolder
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+
+ return rv;
+}
diff --git a/mailnews/imap/src/nsImapService.h b/mailnews/imap/src/nsImapService.h
new file mode 100644
index 000000000..dc6f1921a
--- /dev/null
+++ b/mailnews/imap/src/nsImapService.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImapService_h___
+#define nsImapService_h___
+
+#include "nsIImapService.h"
+#include "nsIMsgMessageService.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIProtocolHandler.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIContentHandler.h"
+#include "nsICacheStorage.h"
+
+class nsIImapHostSessionList;
+class nsCString;
+class nsIImapUrl;
+class nsIMsgFolder;
+class nsIMsgStatusFeedback;
+class nsIMsgIncomingServer;
+
+class nsImapService : public nsIImapService,
+ public nsIMsgMessageService,
+ public nsIMsgMessageFetchPartService,
+ public nsIProtocolHandler,
+ public nsIMsgProtocolInfo,
+ public nsIContentHandler
+{
+public:
+ nsImapService();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGPROTOCOLINFO
+ NS_DECL_NSIIMAPSERVICE
+ NS_DECL_NSIMSGMESSAGESERVICE
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIMSGMESSAGEFETCHPARTSERVICE
+ NS_DECL_NSICONTENTHANDLER
+
+protected:
+ virtual ~nsImapService();
+ char GetHierarchyDelimiter(nsIMsgFolder *aMsgFolder);
+
+ nsresult GetFolderName(nsIMsgFolder *aImapFolder, nsACString &aFolderName);
+
+ // This is called by both FetchMessage and StreamMessage
+ nsresult GetMessageFromUrl(nsIImapUrl *aImapUrl,
+ nsImapAction aImapAction,
+ nsIMsgFolder *aImapMailFolder,
+ nsIImapMessageSink *aImapMessage,
+ nsIMsgWindow *aMsgWindow,
+ nsISupports *aDisplayConsumer,
+ bool aConvertDataToText,
+ nsIURI **aURL);
+
+ nsresult CreateStartOfImapUrl(const nsACString &aImapURI, // a RDF URI for the current message/folder, can be empty
+ nsIImapUrl **imapUrl,
+ nsIMsgFolder *aImapFolder,
+ nsIUrlListener *aUrlListener,
+ nsACString &urlSpec,
+ char &hierarchyDelimiter);
+
+ nsresult GetImapConnectionAndLoadUrl(nsIImapUrl *aImapUrl,
+ nsISupports *aConsumer,
+ nsIURI **aURL);
+
+ nsresult SetImapUrlSink(nsIMsgFolder *aMsgFolder, nsIImapUrl *aImapUrl);
+
+ nsresult FetchMimePart(nsIImapUrl *aImapUrl,
+ nsImapAction aImapAction,
+ nsIMsgFolder *aImapMailFolder,
+ nsIImapMessageSink *aImapMessage,
+ nsIURI **aURL,
+ nsISupports *aDisplayConsumer,
+ const nsACString &messageIdentifierList,
+ const nsACString &mimePart);
+
+ nsresult FolderCommand(nsIMsgFolder *imapMailFolder,
+ nsIUrlListener *urlListener,
+ const char *aCommand,
+ nsImapAction imapAction,
+ nsIMsgWindow *msgWindow,
+ nsIURI **url);
+
+ nsresult ChangeFolderSubscription(nsIMsgFolder *folder,
+ const nsAString &folderName,
+ const char *aCommand,
+ nsIUrlListener *urlListener,
+ nsIURI **url);
+
+ nsresult DiddleFlags(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ const nsACString &messageIdentifierList,
+ const char *howToDiddle,
+ imapMessageFlagsType flags,
+ bool messageIdsAreUID);
+
+ nsresult OfflineAppendFromFile(nsIFile *aFile,
+ nsIURI *aUrl,
+ nsIMsgFolder *aDstFolder,
+ const nsACString &messageId, // to be replaced
+ bool inSelectedState, // needs to be in
+ nsIUrlListener *aListener,
+ nsIURI **aURL,
+ nsISupports *aCopyState);
+
+ nsresult GetServerFromUrl(nsIImapUrl *aImapUrl, nsIMsgIncomingServer **aServer);
+
+ // just a little helper method...maybe it should be a macro? which helps break down a imap message uri
+ // into the folder and message key equivalents
+ nsresult DecomposeImapURI(const nsACString &aMessageURI, nsIMsgFolder **aFolder, nsACString &msgKey);
+ nsresult DecomposeImapURI(const nsACString &aMessageURI, nsIMsgFolder **aFolder, nsMsgKey *msgKey);
+
+
+ nsCOMPtr<nsICacheStorage> mCacheStorage;
+ bool mPrintingOperation; // Flag for printing operations
+};
+
+#endif /* nsImapService_h___ */
diff --git a/mailnews/imap/src/nsImapStringBundle.cpp b/mailnews/imap/src/nsImapStringBundle.cpp
new file mode 100644
index 000000000..124b7f0b6
--- /dev/null
+++ b/mailnews/imap/src/nsImapStringBundle.cpp
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIStringBundle.h"
+#include "nsImapStringBundle.h"
+#include "nsIServiceManager.h"
+#include "nsIURI.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+
+#define IMAP_MSGS_URL "chrome://messenger/locale/imapMsgs.properties"
+
+extern "C"
+nsresult
+IMAPGetStringByName(const char* stringName, char16_t **aString)
+{
+ nsCOMPtr <nsIStringBundle> sBundle;
+ nsresult rv = IMAPGetStringBundle(getter_AddRefs(sBundle));
+ if (NS_SUCCEEDED(rv) && sBundle)
+ rv = sBundle->GetStringFromName(NS_ConvertASCIItoUTF16(stringName).get(),
+ aString);
+ return rv;
+}
+
+nsresult
+IMAPGetStringBundle(nsIStringBundle **aBundle)
+{
+ nsresult rv=NS_OK;
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (!stringService) return NS_ERROR_NULL_POINTER;
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ rv = stringService->CreateBundle(IMAP_MSGS_URL, getter_AddRefs(stringBundle));
+ *aBundle = stringBundle;
+ NS_IF_ADDREF(*aBundle);
+ return rv;
+}
diff --git a/mailnews/imap/src/nsImapStringBundle.h b/mailnews/imap/src/nsImapStringBundle.h
new file mode 100644
index 000000000..a5333f185
--- /dev/null
+++ b/mailnews/imap/src/nsImapStringBundle.h
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsImapStringBundle_H__
+#define _nsImapStringBundle_H__
+
+#include "nsIStringBundle.h"
+
+PR_BEGIN_EXTERN_C
+
+nsresult IMAPGetStringByName(const char* stringName, char16_t **aString);
+nsresult IMAPGetStringBundle(nsIStringBundle **aBundle);
+
+PR_END_EXTERN_C
+
+#endif /* _nsImapStringBundle_H__ */
diff --git a/mailnews/imap/src/nsImapUndoTxn.cpp b/mailnews/imap/src/nsImapUndoTxn.cpp
new file mode 100644
index 000000000..8d51dab35
--- /dev/null
+++ b/mailnews/imap/src/nsImapUndoTxn.cpp
@@ -0,0 +1,751 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // for precompiled headers
+#include "nsMsgImapCID.h"
+#include "nsIMsgHdr.h"
+#include "nsImapUndoTxn.h"
+#include "nsIIMAPHostSessionList.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsImapMailFolder.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgUtils.h"
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+
+nsImapMoveCopyMsgTxn::nsImapMoveCopyMsgTxn() :
+ m_idsAreUids(false), m_isMove(false), m_srcIsPop3(false)
+{
+}
+
+nsresult
+nsImapMoveCopyMsgTxn::Init(nsIMsgFolder* srcFolder, nsTArray<nsMsgKey>* srcKeyArray,
+ const char* srcMsgIdString, nsIMsgFolder* dstFolder,
+ bool idsAreUids, bool isMove)
+{
+ m_srcMsgIdString = srcMsgIdString;
+ m_idsAreUids = idsAreUids;
+ m_isMove = isMove;
+ m_srcFolder = do_GetWeakReference(srcFolder);
+ m_dstFolder = do_GetWeakReference(dstFolder);
+ m_srcKeyArray = *srcKeyArray;
+ m_dupKeyArray = *srcKeyArray;
+ nsCString uri;
+ nsresult rv = srcFolder->GetURI(uri);
+ nsCString protocolType(uri);
+ protocolType.SetLength(protocolType.FindChar(':'));
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t i, count = m_srcKeyArray.Length();
+ nsCOMPtr<nsIMsgDBHdr> srcHdr;
+ nsCOMPtr<nsIMsgDBHdr> copySrcHdr;
+ nsCString messageId;
+
+ for (i = 0; i < count; i++)
+ {
+ rv = srcDB->GetMsgHdrForKey(m_srcKeyArray[i],
+ getter_AddRefs(srcHdr));
+ if (NS_SUCCEEDED(rv))
+ {
+ // ** jt -- only do this for mailbox protocol
+ if (MsgLowerCaseEqualsLiteral(protocolType, "mailbox"))
+ {
+ m_srcIsPop3 = true;
+ uint32_t msgSize;
+ rv = srcHdr->GetMessageSize(&msgSize);
+ if (NS_SUCCEEDED(rv))
+ m_srcSizeArray.AppendElement(msgSize);
+ if (isMove)
+ {
+ rv = srcDB->CopyHdrFromExistingHdr(nsMsgKey_None, srcHdr, false,
+ getter_AddRefs(copySrcHdr));
+ nsMsgKey pseudoKey = nsMsgKey_None;
+ if (NS_SUCCEEDED(rv))
+ {
+ copySrcHdr->GetMessageKey(&pseudoKey);
+ m_srcHdrs.AppendObject(copySrcHdr);
+ }
+ m_dupKeyArray[i] = pseudoKey;
+ }
+ }
+ srcHdr->GetMessageId(getter_Copies(messageId));
+ m_srcMessageIds.AppendElement(messageId);
+ }
+ }
+ return nsMsgTxn::Init();
+}
+
+nsImapMoveCopyMsgTxn::~nsImapMoveCopyMsgTxn()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsImapMoveCopyMsgTxn, nsMsgTxn, nsIUrlListener)
+
+NS_IMETHODIMP
+nsImapMoveCopyMsgTxn::UndoTransaction(void)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool finishInOnStopRunningUrl = false;
+
+ if (m_isMove || !m_dstFolder)
+ {
+ if (m_srcIsPop3)
+ {
+ rv = UndoMailboxDelete();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder)
+ return rv;
+ nsCOMPtr<nsIUrlListener> srcListener = do_QueryInterface(srcFolder, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ m_onStopListener = do_GetWeakReference(srcListener);
+
+ // ** make sure we are in the selected state; use lite select
+ // folder so we won't hit performance hard
+ rv = imapService->LiteSelectFolder(srcFolder, srcListener, nullptr, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+ bool deletedMsgs = true; //default is true unless imapDelete model
+ nsMsgImapDeleteModel deleteModel;
+ rv = GetImapDeleteModel(srcFolder, &deleteModel);
+
+ // protect against a bogus undo txn without any source keys
+ // see bug #179856 for details
+ NS_ASSERTION(!m_srcKeyArray.IsEmpty(), "no source keys");
+ if (m_srcKeyArray.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ if (!m_srcMsgIdString.IsEmpty())
+ {
+ if (NS_SUCCEEDED(rv) && deleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ CheckForToggleDelete(srcFolder, m_srcKeyArray[0], &deletedMsgs);
+
+ if (deletedMsgs)
+ rv = imapService->SubtractMessageFlags(srcFolder,
+ this, nullptr,
+ m_srcMsgIdString,
+ kImapMsgDeletedFlag,
+ m_idsAreUids);
+ else
+ rv = imapService->AddMessageFlags(srcFolder,
+ srcListener, nullptr,
+ m_srcMsgIdString,
+ kImapMsgDeletedFlag,
+ m_idsAreUids);
+ if (NS_FAILED(rv))
+ return rv;
+
+ finishInOnStopRunningUrl = true;
+ if (deleteModel != nsMsgImapDeleteModels::IMAPDelete)
+ rv = imapService->GetHeaders(srcFolder, srcListener, nullptr,
+ m_srcMsgIdString, true);
+ }
+ }
+ }
+ if (!finishInOnStopRunningUrl && !m_dstMsgIdString.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ if (NS_FAILED(rv) || !dstFolder)
+ return rv;
+
+ nsCOMPtr<nsIUrlListener> dstListener;
+
+ dstListener = do_QueryInterface(dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ** make sure we are in the selected state; use lite select folder
+ // so we won't potentially download a bunch of headers.
+ rv = imapService->LiteSelectFolder(dstFolder,
+ dstListener, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->AddMessageFlags(dstFolder, dstListener,
+ nullptr, m_dstMsgIdString,
+ kImapMsgDeletedFlag, m_idsAreUids);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMoveCopyMsgTxn::RedoTransaction(void)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_isMove || !m_dstFolder)
+ {
+ if (m_srcIsPop3)
+ {
+ rv = RedoMailboxDelete();
+ if (NS_FAILED(rv)) return rv;
+ }
+ else if (!m_srcMsgIdString.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder)
+ return rv;
+ nsCOMPtr<nsIUrlListener> srcListener = do_QueryInterface(srcFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool deletedMsgs = false; //default will be false unless imapDeleteModel;
+ nsMsgImapDeleteModel deleteModel;
+ rv = GetImapDeleteModel(srcFolder, &deleteModel);
+
+ // protect against a bogus undo txn without any source keys
+ // see bug #179856 for details
+ NS_ASSERTION(!m_srcKeyArray.IsEmpty(), "no source keys");
+ if (m_srcKeyArray.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ if (NS_SUCCEEDED(rv) && deleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ rv = CheckForToggleDelete(srcFolder, m_srcKeyArray[0], &deletedMsgs);
+
+ // Make sure we are in the selected state; use lite select
+ // folder so performance won't suffer.
+ rv = imapService->LiteSelectFolder(srcFolder, srcListener, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (deletedMsgs)
+ {
+ rv = imapService->SubtractMessageFlags(srcFolder,
+ srcListener, nullptr,
+ m_srcMsgIdString,
+ kImapMsgDeletedFlag,
+ m_idsAreUids);
+ }
+ else
+ {
+ rv = imapService->AddMessageFlags(srcFolder,
+ srcListener, nullptr, m_srcMsgIdString,
+ kImapMsgDeletedFlag, m_idsAreUids);
+ }
+ }
+ }
+ if (!m_dstMsgIdString.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ if (NS_FAILED(rv) || !dstFolder) return rv;
+
+ nsCOMPtr<nsIUrlListener> dstListener;
+
+ dstListener = do_QueryInterface(dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ** make sure we are in the selected state; use lite select
+ // folder so we won't hit performance hard
+ rv = imapService->LiteSelectFolder(dstFolder, dstListener, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->SubtractMessageFlags(dstFolder,
+ dstListener, nullptr,
+ m_dstMsgIdString,
+ kImapMsgDeletedFlag,
+ m_idsAreUids);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgImapDeleteModel deleteModel;
+ rv = GetImapDeleteModel(dstFolder, &deleteModel);
+ if (NS_FAILED(rv) || deleteModel == nsMsgImapDeleteModels::MoveToTrash)
+ {
+ rv = imapService->GetHeaders(dstFolder, dstListener,
+ nullptr, m_dstMsgIdString, true);
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsImapMoveCopyMsgTxn::SetCopyResponseUid(const char* aMsgIdString)
+{
+ if (!aMsgIdString) return NS_ERROR_NULL_POINTER;
+ m_dstMsgIdString = aMsgIdString;
+ if (m_dstMsgIdString.Last() == ']')
+ {
+ int32_t len = m_dstMsgIdString.Length();
+ m_dstMsgIdString.SetLength(len - 1);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsImapMoveCopyMsgTxn::GetSrcKeyArray(nsTArray<nsMsgKey>& srcKeyArray)
+{
+ srcKeyArray = m_srcKeyArray;
+ return NS_OK;
+}
+
+nsresult
+nsImapMoveCopyMsgTxn::AddDstKey(nsMsgKey aKey)
+{
+ if (!m_dstMsgIdString.IsEmpty())
+ m_dstMsgIdString.Append(",");
+ m_dstMsgIdString.AppendInt((int32_t) aKey);
+ return NS_OK;
+}
+
+nsresult
+nsImapMoveCopyMsgTxn::UndoMailboxDelete()
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ // ** jt -- only do this for mailbox protocol
+ if (m_srcIsPop3)
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder) return rv;
+
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ if (NS_FAILED(rv) || !dstFolder) return rv;
+
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIMsgDatabase> dstDB;
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (NS_FAILED(rv)) return rv;
+ rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t count = m_srcKeyArray.Length();
+ uint32_t i;
+ nsCOMPtr<nsIMsgDBHdr> oldHdr;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ for (i = 0; i < count; i++)
+ {
+ oldHdr = m_srcHdrs[i];
+ NS_ASSERTION(oldHdr, "fatal ... cannot get old msg header\n");
+ rv = srcDB->CopyHdrFromExistingHdr(m_srcKeyArray[i],
+ oldHdr,true,
+ getter_AddRefs(newHdr));
+ NS_ASSERTION(newHdr, "fatal ... cannot create new header\n");
+
+ if (NS_SUCCEEDED(rv) && newHdr)
+ {
+ if (i < m_srcSizeArray.Length())
+ newHdr->SetMessageSize(m_srcSizeArray[i]);
+ srcDB->UndoDelete(newHdr);
+ }
+ }
+ srcDB->SetSummaryValid(true);
+ return NS_OK; // always return NS_OK
+ }
+ else
+ {
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+
+nsresult
+nsImapMoveCopyMsgTxn::RedoMailboxDelete()
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ if (m_srcIsPop3)
+ {
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder) return rv;
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (NS_SUCCEEDED(rv))
+ {
+ srcDB->DeleteMessages(m_srcKeyArray.Length(), m_srcKeyArray.Elements(), nullptr);
+ srcDB->SetSummaryValid(true);
+ }
+ return NS_OK; // always return NS_OK
+ }
+ else
+ {
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+nsresult nsImapMoveCopyMsgTxn::GetImapDeleteModel(nsIMsgFolder *aFolder, nsMsgImapDeleteModel *aDeleteModel)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (!aFolder)
+ return NS_ERROR_NULL_POINTER;
+ rv = aFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server, &rv);
+ if (NS_SUCCEEDED(rv) && imapServer)
+ rv = imapServer->GetDeleteModel(aDeleteModel);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMoveCopyMsgTxn::OnStartRunningUrl(nsIURI *aUrl)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMoveCopyMsgTxn::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryReferent(m_onStopListener);
+ if (urlListener)
+ urlListener->OnStopRunningUrl(aUrl, aExitCode);
+
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl);
+ if (imapUrl)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (imapAction == nsIImapUrl::nsImapSubtractMsgFlags)
+ {
+ int32_t extraStatus;
+ imapUrl->GetExtraStatus(&extraStatus);
+ if (extraStatus != nsIImapUrl::ImapStatusNone)
+ {
+ // If subtracting the deleted flag didn't work, try
+ // moving the message back from the target folder to the src folder
+ if (!m_dstMsgIdString.IsEmpty())
+ imapService->OnlineMessageCopy(dstFolder,
+ m_dstMsgIdString,
+ srcFolder,
+ true,
+ true,
+ nullptr, /* listener */
+ nullptr,
+ nullptr,
+ nullptr);
+ else
+ {
+ // server doesn't support COPYUID, so we're going to update the dest
+ // folder, and when that's done, use the db to find the messages
+ // to move back, looking them up by message-id.
+ nsCOMPtr<nsIMsgImapMailFolder> imapDest = do_QueryInterface(dstFolder);
+ if (imapDest)
+ imapDest->UpdateFolderWithListener(nullptr, this);
+ }
+ }
+ else if (!m_dstMsgIdString.IsEmpty())
+ {
+ nsCOMPtr<nsIUrlListener> dstListener;
+
+ dstListener = do_QueryInterface(dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ** make sure we are in the selected state; use lite select folder
+ // so we won't potentially download a bunch of headers.
+ rv = imapService->LiteSelectFolder(dstFolder, dstListener, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->AddMessageFlags(dstFolder, dstListener,
+ nullptr, m_dstMsgIdString,
+ kImapMsgDeletedFlag, m_idsAreUids);
+ }
+ }
+ else if (imapAction == nsIImapUrl::nsImapSelectFolder)
+ {
+ // Now we should have the headers from the dest folder.
+ // Look them up and move them back to the source folder.
+ uint32_t count = m_srcMessageIds.Length();
+ uint32_t i;
+ nsCString messageId;
+ nsTArray<nsMsgKey> dstKeys;
+ nsCOMPtr<nsIMsgDatabase> destDB;
+ nsCOMPtr<nsIMsgDBHdr> dstHdr;
+
+ rv = dstFolder->GetMsgDatabase(getter_AddRefs(destDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (i = 0; i < count; i++)
+ {
+ rv = destDB->GetMsgHdrForMessageID(m_srcMessageIds[i].get(), getter_AddRefs(dstHdr));
+ if (NS_SUCCEEDED(rv) && dstHdr)
+ {
+ nsMsgKey dstKey;
+ dstHdr->GetMessageKey(&dstKey);
+ dstKeys.AppendElement(dstKey);
+ }
+ }
+ if (dstKeys.Length())
+ {
+ nsAutoCString uids;
+ nsImapMailFolder::AllocateUidStringFromKeys(dstKeys.Elements(), dstKeys.Length(), uids);
+ rv = imapService->OnlineMessageCopy(dstFolder, uids, srcFolder,
+ true, true, nullptr,
+ nullptr, nullptr, nullptr);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsImapOfflineTxn::nsImapOfflineTxn(nsIMsgFolder* srcFolder, nsTArray<nsMsgKey>* srcKeyArray,
+ const char *srcMsgIdString, nsIMsgFolder* dstFolder,
+ bool isMove, nsOfflineImapOperationType opType,
+ nsCOMArray<nsIMsgDBHdr> &srcHdrs)
+{
+ Init(srcFolder, srcKeyArray, srcMsgIdString, dstFolder, true,
+ isMove);
+
+ m_opType = opType;
+ m_flags = 0;
+ m_addFlags = false;
+ if (opType == nsIMsgOfflineImapOperation::kDeletedMsg)
+ {
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+
+ nsresult rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(srcDB));
+ if (NS_SUCCEEDED(rv) && srcDB)
+ {
+ nsMsgKey pseudoKey;
+ nsCOMPtr <nsIMsgDBHdr> copySrcHdr;
+
+ // Imap protocols have conflated key/UUID so we cannot use
+ // auto key with them.
+ nsCString protocolType;
+ srcFolder->GetURI(protocolType);
+ protocolType.SetLength(protocolType.FindChar(':'));
+ for (int32_t i = 0; i < srcHdrs.Count(); i++)
+ {
+ if (protocolType.EqualsLiteral("imap"))
+ {
+ srcDB->GetNextPseudoMsgKey(&pseudoKey);
+ pseudoKey--;
+ }
+ else
+ {
+ pseudoKey = nsMsgKey_None;
+ }
+ rv = srcDB->CopyHdrFromExistingHdr(pseudoKey, srcHdrs[i], false, getter_AddRefs(copySrcHdr));
+ if (NS_SUCCEEDED(rv))
+ {
+ copySrcHdr->GetMessageKey(&pseudoKey);
+ m_srcHdrs.AppendObject(copySrcHdr);
+ }
+ m_dupKeyArray[i] = pseudoKey;
+ }
+ }
+ }
+ else
+ m_srcHdrs.AppendObjects(srcHdrs);
+}
+
+nsImapOfflineTxn::~nsImapOfflineTxn()
+{
+}
+
+// Open the database and find the key for the offline operation that we want to
+// undo, then remove it from the database, we also hold on to this
+// data for a redo operation.
+NS_IMETHODIMP nsImapOfflineTxn::UndoTransaction(void)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder)
+ return rv;
+ nsCOMPtr <nsIMsgOfflineImapOperation> op;
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ nsCOMPtr <nsIMsgDatabase> srcDB;
+ nsCOMPtr <nsIMsgDatabase> destDB;
+
+ rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(srcDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (m_opType)
+ {
+ case nsIMsgOfflineImapOperation::kMsgMoved:
+ case nsIMsgOfflineImapOperation::kMsgCopy:
+ case nsIMsgOfflineImapOperation::kAddedHeader:
+ case nsIMsgOfflineImapOperation::kFlagsChanged:
+ case nsIMsgOfflineImapOperation::kDeletedMsg:
+ {
+ if (m_srcHdrs.IsEmpty())
+ {
+ NS_ASSERTION(false, "No msg header to apply undo.");
+ break;
+ }
+ nsCOMPtr<nsIMsgDBHdr> firstHdr = m_srcHdrs[0];
+ nsMsgKey hdrKey;
+ firstHdr->GetMessageKey(&hdrKey);
+ rv = srcDB->GetOfflineOpForKey(hdrKey, false, getter_AddRefs(op));
+ bool offlineOpPlayedBack = true;
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ op->GetPlayingBack(&offlineOpPlayedBack);
+ srcDB->RemoveOfflineOp(op);
+ op = nullptr;
+ }
+ if (!WeAreOffline() && offlineOpPlayedBack)
+ {
+ // couldn't find offline op - it must have been played back already
+ // so we should undo the transaction online.
+ return nsImapMoveCopyMsgTxn::UndoTransaction();
+ }
+
+ if (!firstHdr)
+ break;
+ nsMsgKey msgKey;
+ if (m_opType == nsIMsgOfflineImapOperation::kAddedHeader)
+ {
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++)
+ {
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ nsCOMPtr<nsIMsgDBHdr> mailHdr;
+ rv = srcDB->GetMsgHdrForKey(msgKey, getter_AddRefs(mailHdr));
+ if (mailHdr)
+ srcDB->DeleteHeader(mailHdr, nullptr, false, false);
+ }
+ srcDB->Commit(true);
+ }
+ else if (m_opType == nsIMsgOfflineImapOperation::kDeletedMsg)
+ {
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> undeletedHdr = m_srcHdrs[i];
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ if (undeletedHdr)
+ {
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ srcDB->CopyHdrFromExistingHdr (msgKey, undeletedHdr, true, getter_AddRefs(newHdr));
+ }
+ }
+ srcDB->Close(true);
+ srcFolder->SummaryChanged();
+ }
+ break;
+ }
+ case nsIMsgOfflineImapOperation::kMsgMarkedDeleted:
+ {
+ nsMsgKey msgKey;
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++)
+ {
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ srcDB->MarkImapDeleted(msgKey, false, nullptr);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ srcDB->Close(true);
+ srcFolder->SummaryChanged();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapOfflineTxn::RedoTransaction(void)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder)
+ return rv;
+ nsCOMPtr <nsIMsgOfflineImapOperation> op;
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ nsCOMPtr <nsIMsgDatabase> srcDB;
+ nsCOMPtr <nsIMsgDatabase> destDB;
+ rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(srcDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (m_opType)
+ {
+ case nsIMsgOfflineImapOperation::kMsgMoved:
+ case nsIMsgOfflineImapOperation::kMsgCopy:
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++)
+ {
+ nsMsgKey hdrKey;
+ m_srcHdrs[i]->GetMessageKey(&hdrKey);
+ rv = srcDB->GetOfflineOpForKey(hdrKey, false, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ if (dstFolder)
+ {
+ nsCString folderURI;
+ dstFolder->GetURI(folderURI);
+
+ if (m_opType == nsIMsgOfflineImapOperation::kMsgMoved)
+ op->SetDestinationFolderURI(folderURI.get()); // offline move
+ if (m_opType == nsIMsgOfflineImapOperation::kMsgCopy)
+ {
+ op->SetOperation(nsIMsgOfflineImapOperation::kMsgMoved);
+ op->AddMessageCopyOperation(folderURI.get()); // offline copy
+ }
+ dstFolder->SummaryChanged();
+ }
+ }
+ else if (!WeAreOffline())
+ {
+ // couldn't find offline op - it must have been played back already
+ // so we should redo the transaction online.
+ return nsImapMoveCopyMsgTxn::RedoTransaction();
+ }
+ }
+ break;
+ case nsIMsgOfflineImapOperation::kAddedHeader:
+ {
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(destDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> restoreHdr;
+ nsMsgKey msgKey;
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ destDB->CopyHdrFromExistingHdr (msgKey, m_srcHdrs[i], true, getter_AddRefs(restoreHdr));
+ rv = destDB->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ nsCString folderURI;
+ srcFolder->GetURI(folderURI);
+ op->SetSourceFolderURI(folderURI.get());
+ }
+ }
+ dstFolder->SummaryChanged();
+ destDB->Close(true);
+ }
+ break;
+ case nsIMsgOfflineImapOperation::kDeletedMsg:
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++)
+ {
+ nsMsgKey msgKey;
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ srcDB->DeleteMessage(msgKey, nullptr, true);
+ }
+ break;
+ case nsIMsgOfflineImapOperation::kMsgMarkedDeleted:
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++)
+ {
+ nsMsgKey msgKey;
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ srcDB->MarkImapDeleted(msgKey, true, nullptr);
+ }
+ break;
+ case nsIMsgOfflineImapOperation::kFlagsChanged:
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++)
+ {
+ nsMsgKey msgKey;
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ rv = srcDB->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ imapMessageFlagsType newMsgFlags;
+ op->GetNewFlags(&newMsgFlags);
+ if (m_addFlags)
+ op->SetFlagOperation(newMsgFlags | m_flags);
+ else
+ op->SetFlagOperation(newMsgFlags & ~m_flags);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ srcDB->Close(true);
+ srcDB = nullptr;
+ srcFolder->SummaryChanged();
+ return NS_OK;
+}
diff --git a/mailnews/imap/src/nsImapUndoTxn.h b/mailnews/imap/src/nsImapUndoTxn.h
new file mode 100644
index 000000000..cc910d768
--- /dev/null
+++ b/mailnews/imap/src/nsImapUndoTxn.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImapUndoTxn_h__
+#define nsImapUndoTxn_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIMsgFolder.h"
+#include "nsImapCore.h"
+#include "nsIImapService.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIUrlListener.h"
+#include "nsMsgTxn.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIMsgOfflineImapOperation.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+#include "nsCOMArray.h"
+
+class nsImapMoveCopyMsgTxn : public nsMsgTxn, nsIUrlListener
+{
+public:
+
+ nsImapMoveCopyMsgTxn();
+ nsImapMoveCopyMsgTxn(nsIMsgFolder* srcFolder, nsTArray<nsMsgKey>* srcKeyArray,
+ const char* srcMsgIdString, nsIMsgFolder* dstFolder,
+ bool isMove);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIURLLISTENER
+
+ NS_IMETHOD UndoTransaction(void) override;
+ NS_IMETHOD RedoTransaction(void) override;
+
+ // helper
+ nsresult SetCopyResponseUid(const char *msgIdString);
+ nsresult GetSrcKeyArray(nsTArray<nsMsgKey>& srcKeyArray);
+ void GetSrcMsgIds(nsCString &srcMsgIds) {srcMsgIds = m_srcMsgIdString;}
+ nsresult AddDstKey(nsMsgKey aKey);
+ nsresult UndoMailboxDelete();
+ nsresult RedoMailboxDelete();
+ nsresult Init(nsIMsgFolder* srcFolder, nsTArray<nsMsgKey>* srcKeyArray,
+ const char* srcMsgIdString, nsIMsgFolder* dstFolder,
+ bool idsAreUids, bool isMove);
+
+protected:
+ virtual ~nsImapMoveCopyMsgTxn();
+
+ nsWeakPtr m_srcFolder;
+ nsCOMArray<nsIMsgDBHdr> m_srcHdrs;
+ nsTArray<nsMsgKey> m_dupKeyArray;
+ nsTArray<nsMsgKey> m_srcKeyArray;
+ nsTArray<nsCString> m_srcMessageIds;
+ nsCString m_srcMsgIdString;
+ nsWeakPtr m_dstFolder;
+ nsCString m_dstMsgIdString;
+ bool m_idsAreUids;
+ bool m_isMove;
+ bool m_srcIsPop3;
+ nsTArray<uint32_t> m_srcSizeArray;
+ // this is used when we chain urls for imap undo, since "this" needs
+ // to be the listener, but the folder may need to also be notified.
+ nsWeakPtr m_onStopListener;
+
+ nsresult GetImapDeleteModel(nsIMsgFolder* aFolder, nsMsgImapDeleteModel *aDeleteModel);
+};
+
+class nsImapOfflineTxn : public nsImapMoveCopyMsgTxn
+{
+public:
+ nsImapOfflineTxn(nsIMsgFolder* srcFolder, nsTArray<nsMsgKey>* srcKeyArray,
+ const char* srcMsgIdString,
+ nsIMsgFolder* dstFolder,
+ bool isMove,
+ nsOfflineImapOperationType opType,
+ nsCOMArray<nsIMsgDBHdr> &srcHdrs);
+
+ NS_IMETHOD UndoTransaction(void) override;
+ NS_IMETHOD RedoTransaction(void) override;
+ void SetAddFlags(bool addFlags) {m_addFlags = addFlags;}
+ void SetFlags(uint32_t flags) {m_flags = flags;}
+protected:
+ virtual ~nsImapOfflineTxn();
+ nsOfflineImapOperationType m_opType;
+ // these two are used to undo flag changes, which we don't currently do.
+ bool m_addFlags;
+ uint32_t m_flags;
+};
+#endif
diff --git a/mailnews/imap/src/nsImapUrl.cpp b/mailnews/imap/src/nsImapUrl.cpp
new file mode 100644
index 000000000..8fd9f2f65
--- /dev/null
+++ b/mailnews/imap/src/nsImapUrl.cpp
@@ -0,0 +1,1561 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nsMsgImapCID.h"
+
+#include "nsIURL.h"
+#include "nsImapUrl.h"
+#include "nsIIMAPHostSessionList.h"
+#include "nsThreadUtils.h"
+#include "nsStringGlue.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+#include "nsMemory.h"
+#include "nsCOMPtr.h"
+#include "nsIImapIncomingServer.h"
+#include "nsMsgBaseCID.h"
+#include "nsImapUtils.h"
+#include "nsIMAPNamespace.h"
+#include "nsICacheEntry.h"
+#include "nsIMsgFolder.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgHdr.h"
+#include "nsIProgressEventSink.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+
+static NS_DEFINE_CID(kCImapHostSessionListCID, NS_IIMAPHOSTSESSIONLIST_CID);
+
+nsImapUrl::nsImapUrl() : mLock("nsImapUrl.mLock")
+{
+ m_listOfMessageIds = nullptr;
+ m_sourceCanonicalFolderPathSubString = nullptr;
+ m_destinationCanonicalFolderPathSubString = nullptr;
+ m_listOfMessageIds = nullptr;
+ m_tokenPlaceHolder = nullptr;
+ m_searchCriteriaString = nullptr;
+ m_idsAreUids = false;
+ m_mimePartSelectorDetected = false;
+ m_allowContentChange = true; // assume we can do MPOD.
+ m_fetchPartsOnDemand = false; // but assume we're not doing it :-)
+ m_msgLoadingFromCache = false;
+ m_storeResultsOffline = false;
+ m_storeOfflineOnFallback = false;
+ m_localFetchOnly = false;
+ m_rerunningUrl = false;
+ m_moreHeadersToDownload = false;
+ m_externalLinkUrl = true; // we'll start this at true, and set it false in nsImapService::CreateStartOfImapUrl
+ m_contentModified = IMAP_CONTENT_NOT_MODIFIED;
+ m_validUrl = true; // assume the best.
+ m_flags = 0;
+ m_extraStatus = ImapStatusNone;
+ m_onlineSubDirSeparator = '/';
+
+ // ** jt - the following are not ref counted
+ m_copyState = nullptr;
+ m_file = nullptr;
+ m_imapMailFolderSink = nullptr;
+ m_imapMessageSink = nullptr;
+ m_addDummyEnvelope = false;
+ m_canonicalLineEnding = false;
+}
+
+nsImapUrl::~nsImapUrl()
+{
+ PR_FREEIF(m_listOfMessageIds);
+ PR_FREEIF(m_destinationCanonicalFolderPathSubString);
+ PR_FREEIF(m_sourceCanonicalFolderPathSubString);
+ PR_FREEIF(m_searchCriteriaString);
+}
+
+NS_IMPL_ADDREF_INHERITED(nsImapUrl, nsMsgMailNewsUrl)
+
+NS_IMPL_RELEASE_INHERITED(nsImapUrl, nsMsgMailNewsUrl)
+
+NS_INTERFACE_MAP_BEGIN(nsImapUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIImapUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgMessageUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgI18NUrl)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgMailNewsUrl)
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIImapUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsImapUrl::GetRequiredImapState(nsImapState * aImapUrlState)
+{
+ if (aImapUrlState)
+ {
+ // the imap action determines the state we must be in...check the
+ // the imap action.
+
+ if (m_imapAction & 0x10000000)
+ *aImapUrlState = nsImapSelectedState;
+ else
+ *aImapUrlState = nsImapAuthenticatedState;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetImapAction(nsImapAction * aImapAction)
+{
+ *aImapAction = m_imapAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetImapAction(nsImapAction aImapAction)
+{
+ m_imapAction = aImapAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetFolder(nsIMsgFolder **aMsgFolder)
+{
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_ENSURE_ARG_POINTER(m_imapFolder);
+
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_imapFolder);
+ NS_IF_ADDREF(*aMsgFolder = folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetFolder(nsIMsgFolder * aMsgFolder)
+{
+ nsresult rv;
+ m_imapFolder = do_GetWeakReference(aMsgFolder, &rv);
+ if (aMsgFolder)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ aMsgFolder->GetServer(getter_AddRefs(incomingServer));
+ if (incomingServer)
+ incomingServer->GetKey(m_serverKey);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::GetImapMailFolderSink(nsIImapMailFolderSink **
+ aImapMailFolderSink)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolderSink);
+ if (!m_imapMailFolderSink)
+ return NS_ERROR_NULL_POINTER; // no assert, so don't use NS_ENSURE_POINTER.
+
+ nsCOMPtr<nsIImapMailFolderSink> folderSink = do_QueryReferent(m_imapMailFolderSink);
+ *aImapMailFolderSink = folderSink;
+ NS_IF_ADDREF(*aImapMailFolderSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetImapMailFolderSink(nsIImapMailFolderSink * aImapMailFolderSink)
+{
+ nsresult rv;
+ m_imapMailFolderSink = do_GetWeakReference(aImapMailFolderSink, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::GetImapMessageSink(nsIImapMessageSink ** aImapMessageSink)
+{
+ NS_ENSURE_ARG_POINTER(aImapMessageSink);
+ NS_ENSURE_ARG_POINTER(m_imapMessageSink);
+
+ nsCOMPtr<nsIImapMessageSink> messageSink = do_QueryReferent(m_imapMessageSink);
+ *aImapMessageSink = messageSink;
+ NS_IF_ADDREF(*aImapMessageSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetImapMessageSink(nsIImapMessageSink * aImapMessageSink)
+{
+ nsresult rv;
+ m_imapMessageSink = do_GetWeakReference(aImapMessageSink, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::GetImapServerSink(nsIImapServerSink ** aImapServerSink)
+{
+ NS_ENSURE_ARG_POINTER(aImapServerSink);
+ NS_ENSURE_ARG_POINTER(m_imapServerSink);
+
+ nsCOMPtr<nsIImapServerSink> serverSink = do_QueryReferent(m_imapServerSink);
+ *aImapServerSink = serverSink;
+ NS_IF_ADDREF(*aImapServerSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetImapServerSink(nsIImapServerSink * aImapServerSink)
+{
+ nsresult rv;
+ m_imapServerSink = do_GetWeakReference(aImapServerSink, &rv);
+ return rv;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////
+// End nsIImapUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsImapUrl::SetSpec(const nsACString &aSpec)
+{
+ nsresult rv = nsMsgMailNewsUrl::SetSpec(aSpec);
+ if (NS_SUCCEEDED(rv))
+ {
+ m_validUrl = true; // assume the best.
+ rv = ParseUrl();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::SetQuery(const nsACString &aQuery)
+{
+ nsresult rv = nsMsgMailNewsUrl::SetQuery(aQuery);
+ if (NS_SUCCEEDED(rv))
+ rv = ParseUrl();
+ return rv;
+}
+
+nsresult nsImapUrl::ParseUrl()
+{
+ nsresult rv = NS_OK;
+ // extract the user name
+ GetUserPass(m_userName);
+
+ nsAutoCString imapPartOfUrl;
+ rv = GetPath(imapPartOfUrl);
+ nsAutoCString unescapedImapPartOfUrl;
+ MsgUnescapeString(imapPartOfUrl, 0, unescapedImapPartOfUrl);
+ if (NS_SUCCEEDED(rv) && !unescapedImapPartOfUrl.IsEmpty())
+ {
+ ParseImapPart(unescapedImapPartOfUrl.BeginWriting()+1); // GetPath leaves leading '/' in the path!!!
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::CreateSearchCriteriaString(char ** aResult)
+{
+ // this method should only be called from the imap thread...
+ // o.t. add lock protection..
+ if (nullptr == aResult || !m_searchCriteriaString)
+ return NS_ERROR_NULL_POINTER;
+ *aResult = strdup(m_searchCriteriaString);
+ return NS_OK;
+}
+
+// this method gets called from the UI thread and the imap thread
+NS_IMETHODIMP nsImapUrl::GetListOfMessageIds(nsACString &aResult)
+{
+ MutexAutoLock mon(mLock);
+ if (!m_listOfMessageIds)
+ return NS_ERROR_NULL_POINTER;
+
+ int32_t bytesToCopy = strlen(m_listOfMessageIds);
+
+ // mime may have glommed a "&part=" for a part download
+ // we return the entire message and let mime extract
+ // the part. Pop and news work this way also.
+ // this algorithm truncates the "&part" string.
+ char *currentChar = m_listOfMessageIds;
+ while (*currentChar && (*currentChar != '?'))
+ currentChar++;
+ if (*currentChar == '?')
+ bytesToCopy = currentChar - m_listOfMessageIds;
+
+ // we should also strip off anything after "/;section="
+ // since that can specify an IMAP MIME part
+ char *wherePart = PL_strstr(m_listOfMessageIds, "/;section=");
+ if (wherePart)
+ bytesToCopy = std::min(bytesToCopy, int32_t(wherePart - m_listOfMessageIds));
+
+ aResult.Assign(m_listOfMessageIds, bytesToCopy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCommand(nsACString &result)
+{
+ result = m_command;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImapUrl::GetCustomAttributeToFetch(nsACString &result)
+{
+ result = m_msgFetchAttribute;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCustomAttributeResult(nsACString &result)
+{
+ result = m_customAttributeResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetCustomAttributeResult(const nsACString &result)
+{
+ m_customAttributeResult = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCustomCommandResult(nsACString &result)
+{
+ result = m_customCommandResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetCustomCommandResult(const nsACString &result)
+{
+ m_customCommandResult = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCustomAddFlags(nsACString &aResult)
+{
+ aResult = m_customAddFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCustomSubtractFlags(nsACString &aResult)
+{
+ aResult = m_customSubtractFlags;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImapUrl::GetImapPartToFetch(char **result)
+{
+ // here's the old code....
+
+ // unforunately an imap part can have the form: /;section= OR
+ // it can have the form ?section=. We need to look for both.
+ if (m_listOfMessageIds)
+ {
+ char *wherepart = PL_strstr(m_listOfMessageIds, ";section=");
+ if (!wherepart) // look for ?section too....
+ wherepart = PL_strstr(m_listOfMessageIds, "?section=");
+ if (wherepart)
+ {
+ wherepart += 9; // strlen("/;section=")
+ char *wherelibmimepart = PL_strstr(wherepart, "&part=");
+ if (!wherelibmimepart)
+ wherelibmimepart = PL_strstr(wherepart, "?part=");
+ int numCharsToCopy = (wherelibmimepart) ? wherelibmimepart - wherepart :
+ PL_strlen(m_listOfMessageIds) - (wherepart - m_listOfMessageIds);
+ if (numCharsToCopy)
+ {
+ *result = (char *) PR_Malloc(sizeof(char) * (numCharsToCopy + 1));
+ if (*result)
+ {
+ PL_strncpy(*result, wherepart, numCharsToCopy + 1); // appends a \0
+ (*result)[numCharsToCopy] = '\0';
+ }
+ }
+ } // if we got a wherepart
+ } // if we got a m_listOfMessageIds
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetOnlineSubDirSeparator(char* separator)
+{
+ if (separator)
+ {
+ *separator = m_onlineSubDirSeparator;
+ return NS_OK;
+ }
+ else
+ {
+ return NS_ERROR_NULL_POINTER;
+ }
+}
+
+NS_IMETHODIMP nsImapUrl::GetNumBytesToFetch(int32_t *aNumBytesToFetch)
+{
+ NS_ENSURE_ARG_POINTER(aNumBytesToFetch);
+ *aNumBytesToFetch = m_numBytesToFetch;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapUrl::SetOnlineSubDirSeparator(char onlineDirSeparator)
+{
+ m_onlineSubDirSeparator = onlineDirSeparator;
+ return NS_OK;
+}
+
+// this method is only called from the imap thread
+NS_IMETHODIMP nsImapUrl::MessageIdsAreUids(bool *result)
+{
+ *result = m_idsAreUids;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapUrl::SetExtraStatus(int32_t aExtraStatus)
+{
+ m_extraStatus = aExtraStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetExtraStatus(int32_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_extraStatus;
+ return NS_OK;
+}
+
+// this method is only called from the imap thread
+NS_IMETHODIMP nsImapUrl::GetMsgFlags(imapMessageFlagsType *result) // kAddMsgFlags or kSubtractMsgFlags only
+{
+ *result = m_flags;
+ return NS_OK;
+}
+
+void nsImapUrl::ParseImapPart(char *imapPartOfUrl)
+{
+ m_tokenPlaceHolder = imapPartOfUrl;
+ m_urlidSubString = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder) : (char *)NULL;
+
+ if (!m_urlidSubString)
+ {
+ m_validUrl = false;
+ return;
+ }
+
+ if (!PL_strcasecmp(m_urlidSubString, "fetch"))
+ {
+ m_imapAction = nsImapMsgFetch;
+ ParseUidChoice();
+ PR_FREEIF(m_sourceCanonicalFolderPathSubString);
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ // if fetched by spam filter, the action will be changed to nsImapMsgFetchPeek
+ }
+ else
+ {
+ if (!PL_strcasecmp(m_urlidSubString, "header"))
+ {
+ m_imapAction = nsImapMsgHeader;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "customFetch"))
+ {
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseCustomMsgFetchAttribute();
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "previewBody"))
+ {
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseNumBytes();
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "deletemsg"))
+ {
+ m_imapAction = nsImapDeleteMsg;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "uidexpunge"))
+ {
+ m_imapAction = nsImapUidExpunge;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "deleteallmsgs"))
+ {
+ m_imapAction = nsImapDeleteAllMsgs;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "addmsgflags"))
+ {
+ m_imapAction = nsImapAddMsgFlags;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseMsgFlags();
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "subtractmsgflags"))
+ {
+ m_imapAction = nsImapSubtractMsgFlags;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseMsgFlags();
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "setmsgflags"))
+ {
+ m_imapAction = nsImapSetMsgFlags;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseMsgFlags();
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "onlinecopy"))
+ {
+ m_imapAction = nsImapOnlineCopy;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "onlinemove"))
+ {
+ m_imapAction = nsImapOnlineMove;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "onlinetoofflinecopy"))
+ {
+ m_imapAction = nsImapOnlineToOfflineCopy;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "onlinetoofflinemove"))
+ {
+ m_imapAction = nsImapOnlineToOfflineMove;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "offlinetoonlinecopy"))
+ {
+ m_imapAction = nsImapOfflineToOnlineMove;
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "search"))
+ {
+ m_imapAction = nsImapSearch;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseSearchCriteriaString();
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "test"))
+ {
+ m_imapAction = nsImapTest;
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "select"))
+ {
+ m_imapAction = nsImapSelectFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ if (m_tokenPlaceHolder && *m_tokenPlaceHolder)
+ ParseListOfMessageIds();
+ else
+ m_listOfMessageIds = PL_strdup("");
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "liteselect"))
+ {
+ m_imapAction = nsImapLiteSelectFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "selectnoop"))
+ {
+ m_imapAction = nsImapSelectNoopFolder;
+ m_listOfMessageIds = PL_strdup("");
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "expunge"))
+ {
+ m_imapAction = nsImapExpungeFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ m_listOfMessageIds = PL_strdup(""); // no ids to UNDO
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "create"))
+ {
+ m_imapAction = nsImapCreateFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "ensureExists"))
+ {
+ m_imapAction = nsImapEnsureExistsFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "discoverchildren"))
+ {
+ m_imapAction = nsImapDiscoverChildrenUrl;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "discoverallboxes"))
+ {
+ m_imapAction = nsImapDiscoverAllBoxesUrl;
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "discoverallandsubscribedboxes"))
+ {
+ m_imapAction = nsImapDiscoverAllAndSubscribedBoxesUrl;
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "delete"))
+ {
+ m_imapAction = nsImapDeleteFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "deletefolder"))
+ {
+ m_imapAction = nsImapDeleteFolderAndMsgs;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "rename"))
+ {
+ m_imapAction = nsImapRenameFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "movefolderhierarchy"))
+ {
+ m_imapAction = nsImapMoveFolderHierarchy;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ if (m_tokenPlaceHolder && *m_tokenPlaceHolder) // handle promote to root
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "list"))
+ {
+ m_imapAction = nsImapLsubFolders;
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "biff"))
+ {
+ m_imapAction = nsImapBiff;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "netscape"))
+ {
+ m_imapAction = nsImapGetMailAccountUrl;
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "appendmsgfromfile"))
+ {
+ m_imapAction = nsImapAppendMsgFromFile;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "appenddraftfromfile"))
+ {
+ m_imapAction = nsImapAppendDraftFromFile;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseUidChoice();
+ if (m_tokenPlaceHolder && *m_tokenPlaceHolder)
+ ParseListOfMessageIds();
+ else
+ m_listOfMessageIds = strdup("");
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "subscribe"))
+ {
+ m_imapAction = nsImapSubscribe;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "unsubscribe"))
+ {
+ m_imapAction = nsImapUnsubscribe;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "refreshacl"))
+ {
+ m_imapAction = nsImapRefreshACL;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "refreshfolderurls"))
+ {
+ m_imapAction = nsImapRefreshFolderUrls;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "refreshallacls"))
+ {
+ m_imapAction = nsImapRefreshAllACLs;
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "listfolder"))
+ {
+ m_imapAction = nsImapListFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "upgradetosubscription"))
+ {
+ m_imapAction = nsImapUpgradeToSubscription;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "folderstatus"))
+ {
+ m_imapAction = nsImapFolderStatus;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ }
+ else if (!PL_strcasecmp(m_urlidSubString, "verifyLogon"))
+ {
+ m_imapAction = nsImapVerifylogon;
+ }
+ else if (m_imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand)
+ {
+ m_command = m_urlidSubString; // save this
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ }
+ else if (m_imapAction == nsIImapUrl::nsImapMsgStoreCustomKeywords)
+ {
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ bool addKeyword = (m_tokenPlaceHolder && *m_tokenPlaceHolder != '>');
+ // if we're not adding a keyword, m_tokenPlaceHolder will now look like >keywordToSubtract>
+ // and strtok will leave flagsPtr pointing to keywordToSubtract. So detect this
+ // case and only set the customSubtractFlags.
+ char *flagsPtr = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder) : (char *)nullptr;
+ if (addKeyword)
+ {
+ m_customAddFlags.Assign(flagsPtr);
+ flagsPtr = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder) : (char *)nullptr;
+ }
+ m_customSubtractFlags.Assign(flagsPtr);
+ }
+ else
+ {
+ m_validUrl = false;
+ }
+ }
+}
+
+
+// Returns NULL if nothing was done.
+// Otherwise, returns a newly allocated name.
+NS_IMETHODIMP nsImapUrl::AddOnlineDirectoryIfNecessary(const char *onlineMailboxName, char ** directory)
+{
+ nsresult rv;
+ nsString onlineDirString;
+ char *newOnlineName = nullptr;
+
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ rv = hostSessionList->GetOnlineDirForHost(m_serverKey.get(), onlineDirString);
+ nsAutoCString onlineDir;
+ LossyCopyUTF16toASCII(onlineDirString, onlineDir);
+
+ nsIMAPNamespace *ns = nullptr;
+ rv = hostSessionList->GetNamespaceForMailboxForHost(m_serverKey.get(),
+ onlineMailboxName, ns);
+ if (!ns)
+ hostSessionList->GetDefaultNamespaceOfTypeForHost(m_serverKey.get(),
+ kPersonalNamespace, ns);
+
+ if (onlineDir.IsEmpty() && ns)
+ onlineDir = ns->GetPrefix();
+
+ // If this host has an online server directory configured
+ if (onlineMailboxName && !onlineDir.IsEmpty())
+ {
+ if (PL_strcasecmp(onlineMailboxName, "INBOX"))
+ {
+ NS_ASSERTION(ns, "couldn't find namespace for host");
+ nsAutoCString onlineDirWithDelimiter(onlineDir);
+ // make sure the onlineDir ends with the hierarchy delimiter
+ if (ns)
+ {
+ char delimiter = ns->GetDelimiter();
+ if ( delimiter && delimiter != kOnlineHierarchySeparatorUnknown )
+ {
+ // try to change the canonical online dir name to real dir name first
+ MsgReplaceChar(onlineDirWithDelimiter, '/', delimiter);
+ // make sure the last character is the delimiter
+ if ( onlineDirWithDelimiter.Last() != delimiter )
+ onlineDirWithDelimiter += delimiter;
+ if ( !*onlineMailboxName )
+ onlineDirWithDelimiter.SetLength(onlineDirWithDelimiter.Length()-1);
+ }
+ }
+ if (ns && (PL_strlen(ns->GetPrefix()) != 0) && !onlineDirWithDelimiter.Equals(ns->GetPrefix()))
+ {
+ // check that onlineMailboxName doesn't start with the namespace. If that's the case,
+ // we don't want to prepend the online dir.
+ if (PL_strncmp(onlineMailboxName, ns->GetPrefix(), PL_strlen(ns->GetPrefix())))
+ {
+ // The namespace for this mailbox is the root ("").
+ // Prepend the online server directory
+ int finalLen = onlineDirWithDelimiter.Length() +
+ strlen(onlineMailboxName) + 1;
+ newOnlineName = (char *)PR_Malloc(finalLen);
+ if (newOnlineName)
+ {
+ PL_strcpy(newOnlineName, onlineDirWithDelimiter.get());
+ PL_strcat(newOnlineName, onlineMailboxName);
+ }
+ }
+ }
+ // just prepend the online server directory if it doesn't start with it already
+ else if (strncmp(onlineMailboxName, onlineDirWithDelimiter.get(), onlineDirWithDelimiter.Length()))
+ {
+ newOnlineName = (char *)PR_Malloc(strlen(onlineMailboxName) + onlineDirWithDelimiter.Length() + 1);
+ if (newOnlineName)
+ {
+ PL_strcpy(newOnlineName, onlineDirWithDelimiter.get());
+ PL_strcat(newOnlineName, onlineMailboxName);
+ }
+ }
+ }
+ }
+ if (directory)
+ *directory = newOnlineName;
+ else if (newOnlineName)
+ NS_Free(newOnlineName);
+ return rv;
+}
+
+// Converts from canonical format (hierarchy is indicated by '/' and all real slashes ('/') are escaped)
+// to the real online name on the server.
+NS_IMETHODIMP nsImapUrl::AllocateServerPath(const char * canonicalPath, char onlineDelimiter, char ** aAllocatedPath)
+{
+ nsresult retVal = NS_OK;
+ char *rv = NULL;
+ char delimiterToUse = onlineDelimiter;
+ if (onlineDelimiter == kOnlineHierarchySeparatorUnknown)
+ GetOnlineSubDirSeparator(&delimiterToUse);
+ NS_ASSERTION(delimiterToUse != kOnlineHierarchySeparatorUnknown, "hierarchy separator unknown");
+ if (canonicalPath)
+ rv = ReplaceCharsInCopiedString(canonicalPath, '/', delimiterToUse);
+ else
+ rv = strdup("");
+
+ if (delimiterToUse != '/')
+ UnescapeSlashes(rv);
+ char *onlineNameAdded = nullptr;
+ AddOnlineDirectoryIfNecessary(rv, &onlineNameAdded);
+ if (onlineNameAdded)
+ {
+ NS_Free(rv);
+ rv = onlineNameAdded;
+ }
+
+ if (aAllocatedPath)
+ *aAllocatedPath = rv;
+ else
+ NS_Free(rv);
+
+ return retVal;
+}
+
+// escape '/' as ^, ^ -> ^^ - use UnescapeSlashes to revert
+/* static */ nsresult nsImapUrl::EscapeSlashes(const char *sourcePath, char **resultPath)
+{
+ NS_ENSURE_ARG(sourcePath);
+ NS_ENSURE_ARG(resultPath);
+ int32_t extra = 0;
+ int32_t len = strlen(sourcePath);
+ const char *src = sourcePath;
+ int32_t i;
+ for ( i = 0; i < len; i++)
+ {
+ if (*src == '^')
+ extra += 1; /* ^ -> ^^ */
+ src++;
+ }
+ char* result = (char *)moz_xmalloc(len + extra + 1);
+ if (!result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ unsigned char* dst = (unsigned char *) result;
+ src = sourcePath;
+ for (i = 0; i < len; i++)
+ {
+ unsigned char c = *src++;
+ if (c == '/')
+ *dst++ = '^';
+ else if (c == '^')
+ {
+ *dst++ = '^';
+ *dst++ = '^';
+ }
+ else
+ *dst++ = c;
+ }
+ *dst = '\0'; /* tack on eos */
+ *resultPath = result;
+ return NS_OK;
+}
+
+/* static */ nsresult nsImapUrl::UnescapeSlashes(char *sourcePath)
+{
+ char *src = sourcePath;
+ char *dst = sourcePath;
+
+ while (*src)
+ {
+ if (*src == '^')
+ {
+ if (*(src + 1) == '^')
+ {
+ *dst++ = '^';
+ src++; // skip over second '^'
+ }
+ else
+ *dst++ = '/';
+ src++;
+ }
+ else
+ *dst++ = *src++;
+ }
+
+ *dst = 0;
+ return NS_OK;
+}
+
+/* static */ nsresult nsImapUrl::ConvertToCanonicalFormat(const char *folderName, char onlineDelimiter, char **resultingCanonicalPath)
+{
+ // Now, start the conversion to canonical form.
+
+ char *canonicalPath;
+ if (onlineDelimiter != '/')
+ {
+ nsCString escapedPath;
+
+ EscapeSlashes(folderName, getter_Copies(escapedPath));
+ canonicalPath = ReplaceCharsInCopiedString(escapedPath.get(), onlineDelimiter , '/');
+ }
+ else
+ {
+ canonicalPath = strdup(folderName);
+ }
+ if (canonicalPath)
+ *resultingCanonicalPath = canonicalPath;
+
+ return (canonicalPath) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+// Converts the real online name on the server to canonical format:
+// result is hierarchy is indicated by '/' and all real slashes ('/') are escaped.
+// The caller has already converted m-utf-7 to 8 bit ascii, which is a problem.
+// this method is only called from the imap thread
+NS_IMETHODIMP nsImapUrl::AllocateCanonicalPath(const char *serverPath, char onlineDelimiter, char **allocatedPath )
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ char delimiterToUse = onlineDelimiter;
+ char *serverKey = nullptr;
+ nsString aString;
+ char *currentPath = (char *) serverPath;
+ nsAutoCString onlineDir;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList =
+ do_GetService(kCImapHostSessionListCID, &rv);
+
+ *allocatedPath = nullptr;
+
+ if (onlineDelimiter == kOnlineHierarchySeparatorUnknown ||
+ onlineDelimiter == 0)
+ GetOnlineSubDirSeparator(&delimiterToUse);
+
+ NS_ASSERTION (serverPath, "Oops... null serverPath");
+
+ if (!serverPath || NS_FAILED(rv))
+ goto done;
+
+ hostSessionList->GetOnlineDirForHost(m_serverKey.get(), aString);
+ // First we have to check to see if we should strip off an online server
+ // subdirectory
+ // If this host has an online server directory configured
+ LossyCopyUTF16toASCII(aString, onlineDir);
+
+ if (currentPath && !onlineDir.IsEmpty())
+ {
+ // By definition, the online dir must be at the root.
+ if (delimiterToUse && delimiterToUse != kOnlineHierarchySeparatorUnknown)
+ {
+ // try to change the canonical online dir name to real dir name first
+ MsgReplaceChar(onlineDir, '/', delimiterToUse);
+ // Add the delimiter
+ if (onlineDir.Last() != delimiterToUse)
+ onlineDir += delimiterToUse;
+ }
+ int len = onlineDir.Length();
+ if (!PL_strncmp(onlineDir.get(), currentPath, len))
+ {
+ // This online path begins with the server sub directory
+ currentPath += len;
+
+ // This might occur, but it's most likely something not good.
+ // Basically, it means we're doing something on the online sub directory itself.
+ NS_ASSERTION (*currentPath, "Oops ... null currentPath");
+ // Also make sure that the first character in the mailbox name is not '/'.
+ NS_ASSERTION (*currentPath != '/',
+ "Oops ... currentPath starts with a slash");
+ }
+ }
+
+
+ if (!currentPath)
+ goto done;
+
+ rv = ConvertToCanonicalFormat(currentPath, delimiterToUse, allocatedPath);
+
+done:
+ PR_Free(serverKey);
+ return rv;
+}
+
+// this method is only called from the imap thread
+NS_IMETHODIMP nsImapUrl::CreateServerSourceFolderPathString(char **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ AllocateServerPath(m_sourceCanonicalFolderPathSubString, kOnlineHierarchySeparatorUnknown, result);
+ return NS_OK;
+}
+
+// this method is called from the imap thread AND the UI thread...
+NS_IMETHODIMP nsImapUrl::CreateCanonicalSourceFolderPathString(char **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ MutexAutoLock mon(mLock);
+ *result = strdup(m_sourceCanonicalFolderPathSubString ? m_sourceCanonicalFolderPathSubString : "");
+ return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+// this method is called from the imap thread AND the UI thread...
+NS_IMETHODIMP nsImapUrl::CreateServerDestinationFolderPathString(char **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ MutexAutoLock mon(mLock);
+ nsresult rv = AllocateServerPath(m_destinationCanonicalFolderPathSubString,
+ kOnlineHierarchySeparatorUnknown,
+ result);
+ return (*result) ? rv : NS_ERROR_OUT_OF_MEMORY;
+}
+
+// for enabling or disabling mime parts on demand. Setting this to true says we
+// can use mime parts on demand, if we chose.
+NS_IMETHODIMP nsImapUrl::SetAllowContentChange(bool allowContentChange)
+{
+ m_allowContentChange = allowContentChange;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetContentModified(nsImapContentModifiedType contentModified)
+{
+ m_contentModified = contentModified;
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ nsresult res = GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (NS_SUCCEEDED(res) && cacheEntry)
+ {
+ const char *contentModifiedAnnotation = "";
+ switch (m_contentModified)
+ {
+ case IMAP_CONTENT_NOT_MODIFIED:
+ contentModifiedAnnotation = "Not Modified";
+ break;
+ case IMAP_CONTENT_MODIFIED_VIEW_INLINE:
+ contentModifiedAnnotation = "Modified View Inline";
+ break;
+ case IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS:
+ contentModifiedAnnotation = "Modified View As Link";
+ break;
+ case IMAP_CONTENT_FORCE_CONTENT_NOT_MODIFIED:
+ contentModifiedAnnotation = "Force Content Not Modified";
+ break;
+ }
+ cacheEntry->SetMetaDataElement("ContentModified", contentModifiedAnnotation);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetContentModified(nsImapContentModifiedType *contentModified)
+{
+ if (!contentModified) return NS_ERROR_NULL_POINTER;
+
+ *contentModified = m_contentModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetFetchPartsOnDemand(bool fetchPartsOnDemand)
+{
+ m_fetchPartsOnDemand = fetchPartsOnDemand;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetFetchPartsOnDemand(bool *fetchPartsOnDemand)
+{
+ if (!fetchPartsOnDemand) return NS_ERROR_NULL_POINTER;
+
+ *fetchPartsOnDemand = m_fetchPartsOnDemand;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImapUrl::SetMimePartSelectorDetected(bool mimePartSelectorDetected)
+{
+ m_mimePartSelectorDetected = mimePartSelectorDetected;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetMimePartSelectorDetected(bool *mimePartSelectorDetected)
+{
+ if (!mimePartSelectorDetected) return NS_ERROR_NULL_POINTER;
+
+ *mimePartSelectorDetected = m_mimePartSelectorDetected;
+ return NS_OK;
+}
+
+
+// this method is only called from the UI thread.
+NS_IMETHODIMP nsImapUrl::SetCopyState(nsISupports* copyState)
+{
+ MutexAutoLock mon(mLock);
+ m_copyState = copyState;
+ return NS_OK;
+}
+
+//this method is only called from the imap thread..but we still
+// need a monitor 'cause the setter is called from the UI thread.
+NS_IMETHODIMP nsImapUrl::GetCopyState(nsISupports** copyState)
+{
+ NS_ENSURE_ARG_POINTER(copyState);
+ MutexAutoLock mon(mLock);
+ *copyState = m_copyState;
+ NS_IF_ADDREF(*copyState);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapUrl::SetMsgFile(nsIFile* aFile)
+{
+ nsresult rv = NS_OK;
+ MutexAutoLock mon(mLock);
+ m_file = aFile;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapUrl::GetMsgFile(nsIFile** aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ MutexAutoLock mon(mLock);
+ NS_IF_ADDREF(*aFile = m_file);
+ return NS_OK;
+}
+
+// this method is called from the UI thread..
+NS_IMETHODIMP nsImapUrl::GetMockChannel(nsIImapMockChannel ** aChannel)
+{
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_WARNING_ASSERTION(NS_IsMainThread(), "should only access mock channel on ui thread");
+ *aChannel = nullptr;
+ nsCOMPtr<nsIImapMockChannel> channel(do_QueryReferent(m_channelWeakPtr));
+ channel.swap(*aChannel);
+ return *aChannel ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsImapUrl::SetMockChannel(nsIImapMockChannel * aChannel)
+{
+ NS_WARNING_ASSERTION(NS_IsMainThread(), "should only access mock channel on ui thread");
+ m_channelWeakPtr = do_GetWeakReference(aChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetAllowContentChange(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_allowContentChange;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef,
+ nsIURI** _retval)
+{
+ nsresult rv =
+ nsMsgMailNewsUrl::CloneInternal(aRefHandlingMode, newRef, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // also clone the mURI member, because GetUri below won't work if
+ // mURI isn't set due to escaping issues.
+ nsCOMPtr <nsIMsgMessageUrl> clonedUrl = do_QueryInterface(*_retval);
+ if (clonedUrl)
+ clonedUrl->SetUri(mURI.get());
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::GetPrincipalSpec(nsACString& aPrincipalSpec)
+{
+ // URLs look like this:
+ // imap://user@domain@server:port/fetch>UID>folder>nn
+ // We simply strip any query part beginning with ? & or /;
+ // Normalised spec: imap://user@domain@server:port/fetch>UID>folder>nn
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsURL;
+ QueryInterface(NS_GET_IID(nsIMsgMailNewsUrl), getter_AddRefs(mailnewsURL));
+
+ nsAutoCString spec;
+ mailnewsURL->GetSpecIgnoringRef(spec);
+
+ // Strip any query part beginning with ? or /;
+ int32_t ind = spec.Find("/;");
+ if (ind != kNotFound)
+ spec.SetLength(ind);
+
+ ind = spec.FindChar('?');
+ if (ind != kNotFound)
+ spec.SetLength(ind);
+
+ aPrincipalSpec.Assign(spec);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetUri(const char * aURI)
+{
+ mURI= aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetUri(char** aURI)
+{
+ nsresult rv = NS_OK;
+ if (!mURI.IsEmpty())
+ *aURI = ToNewCString(mURI);
+ else
+ {
+ *aURI = nullptr;
+ uint32_t key = m_listOfMessageIds ? strtoul(m_listOfMessageIds, nullptr, 10) : 0;
+ nsCString canonicalPath;
+ AllocateCanonicalPath(m_sourceCanonicalFolderPathSubString, m_onlineSubDirSeparator, (getter_Copies(canonicalPath)));
+ nsCString fullFolderPath("/");
+ fullFolderPath.Append(m_userName);
+ nsAutoCString hostName;
+ rv = GetHost(hostName);
+ fullFolderPath.Append('@');
+ fullFolderPath.Append(hostName);
+ fullFolderPath.Append('/');
+ fullFolderPath.Append(canonicalPath);
+
+ nsCString baseMessageURI;
+ nsCreateImapBaseMessageURI(fullFolderPath, baseMessageURI);
+ nsAutoCString uriStr;
+ rv = nsBuildImapMessageURI(baseMessageURI.get(), key, uriStr);
+ *aURI = ToNewCString(uriStr);
+ }
+ return rv;
+}
+
+NS_IMPL_GETSET(nsImapUrl, AddDummyEnvelope, bool, m_addDummyEnvelope)
+NS_IMPL_GETSET(nsImapUrl, CanonicalLineEnding, bool, m_canonicalLineEnding)
+NS_IMETHODIMP nsImapUrl::GetMsgLoadingFromCache(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_msgLoadingFromCache;
+ return NS_OK;
+}
+NS_IMPL_GETSET(nsImapUrl, LocalFetchOnly, bool, m_localFetchOnly)
+NS_IMPL_GETSET(nsImapUrl, ExternalLinkUrl, bool, m_externalLinkUrl)
+NS_IMPL_GETSET(nsImapUrl, RerunningUrl, bool, m_rerunningUrl)
+NS_IMPL_GETSET(nsImapUrl, ValidUrl, bool, m_validUrl)
+NS_IMPL_GETSET(nsImapUrl, MoreHeadersToDownload, bool, m_moreHeadersToDownload)
+
+NS_IMETHODIMP nsImapUrl::SetMsgLoadingFromCache(bool loadingFromCache)
+{
+ nsresult rv = NS_OK;
+ m_msgLoadingFromCache = loadingFromCache;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::SetMessageFile(nsIFile * aFile)
+{
+ m_messageFile = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetMessageFile(nsIFile ** aFile)
+{
+ if (aFile)
+ NS_IF_ADDREF(*aFile = m_messageFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::IsUrlType(uint32_t type, bool *isType)
+{
+ NS_ENSURE_ARG(isType);
+
+ switch(type)
+ {
+ case nsIMsgMailNewsUrl::eCopy:
+ *isType = ((m_imapAction == nsIImapUrl::nsImapOnlineCopy) ||
+ (m_imapAction == nsIImapUrl::nsImapOnlineToOfflineCopy) ||
+ (m_imapAction == nsIImapUrl::nsImapOfflineToOnlineCopy));
+ break;
+ case nsIMsgMailNewsUrl::eMove:
+ *isType = ((m_imapAction == nsIImapUrl::nsImapOnlineMove) ||
+ (m_imapAction == nsIImapUrl::nsImapOnlineToOfflineMove) ||
+ (m_imapAction == nsIImapUrl::nsImapOfflineToOnlineMove));
+ break;
+ case nsIMsgMailNewsUrl::eDisplay:
+ *isType = (m_imapAction == nsIImapUrl::nsImapMsgFetch ||
+ m_imapAction == nsIImapUrl::nsImapMsgFetchPeek);
+ break;
+ default:
+ *isType = false;
+ };
+
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP
+nsImapUrl::GetOriginalSpec(char ** aSpec)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsImapUrl::SetOriginalSpec(const char *aSpec)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+char *nsImapUrl::ReplaceCharsInCopiedString(const char *stringToCopy, char oldChar, char newChar)
+{
+ char oldCharString[2];
+ *oldCharString = oldChar;
+ *(oldCharString+1) = 0;
+
+ char *translatedString = PL_strdup(stringToCopy);
+ char *currentSeparator = PL_strstr(translatedString, oldCharString);
+
+ while(currentSeparator)
+ {
+ *currentSeparator = newChar;
+ currentSeparator = PL_strstr(currentSeparator+1, oldCharString);
+ }
+
+ return translatedString;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////
+// End of functions which should be made obsolete after modifying nsIURI
+////////////////////////////////////////////////////////////////////////////////////
+
+void nsImapUrl::ParseFolderPath(char **resultingCanonicalPath)
+{
+ char *resultPath = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder) : (char *)NULL;
+
+ if (!resultPath)
+ {
+ m_validUrl = false;
+ return;
+ }
+ NS_ASSERTION(*resultingCanonicalPath == nullptr, "whoops, mem leak");
+
+ char dirSeparator = *resultPath;
+
+ nsCString unescapedResultingCanonicalPath;
+ MsgUnescapeString(nsDependentCString(resultPath + 1), 0, unescapedResultingCanonicalPath);
+ *resultingCanonicalPath = ToNewCString(unescapedResultingCanonicalPath);
+ // The delimiter will be set for a given URL, but will not be statically available
+ // from an arbitrary URL. It is the creator's responsibility to fill in the correct
+ // delimiter from the folder's namespace when creating the URL.
+ if (dirSeparator != kOnlineHierarchySeparatorUnknown)
+ SetOnlineSubDirSeparator( dirSeparator);
+
+ // if dirSeparator == kOnlineHierarchySeparatorUnknown, then this must be a create
+ // of a top level imap box. If there is an online subdir, we will automatically
+ // use its separator. If there is not an online subdir, we don't need a separator.
+}
+
+void nsImapUrl::ParseSearchCriteriaString()
+{
+ if (m_tokenPlaceHolder)
+ {
+ int quotedFlag = false;
+
+ //skip initial separator
+ while (*m_tokenPlaceHolder == *IMAP_URL_TOKEN_SEPARATOR)
+ m_tokenPlaceHolder++;
+
+ char *saveTokenPlaceHolder = m_tokenPlaceHolder;
+
+// m_searchCriteriaString = m_tokenPlaceHolder;
+
+ //looking for another separator outside quoted string
+ while (*m_tokenPlaceHolder)
+ {
+ if (*m_tokenPlaceHolder == '\\' && *(m_tokenPlaceHolder+1) == '"')
+ m_tokenPlaceHolder++;
+ else if (*m_tokenPlaceHolder == '"')
+ quotedFlag = !quotedFlag;
+ else if (!quotedFlag && *m_tokenPlaceHolder == *IMAP_URL_TOKEN_SEPARATOR)
+ {
+ *m_tokenPlaceHolder = '\0';
+ m_tokenPlaceHolder++;
+ break;
+ }
+ m_tokenPlaceHolder++;
+ }
+ m_searchCriteriaString = PL_strdup(saveTokenPlaceHolder);
+ if (*m_tokenPlaceHolder == '\0')
+ m_tokenPlaceHolder = NULL;
+
+ if (*m_searchCriteriaString == '\0')
+ m_searchCriteriaString = (char *)NULL;
+ }
+ else
+ m_searchCriteriaString = (char *)NULL;
+ if (!m_searchCriteriaString)
+ m_validUrl = false;
+}
+
+
+void nsImapUrl::ParseUidChoice()
+{
+ char *uidChoiceString = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder) : (char *)NULL;
+ if (!uidChoiceString)
+ m_validUrl = false;
+ else
+ m_idsAreUids = strcmp(uidChoiceString, "UID") == 0;
+}
+
+void nsImapUrl::ParseMsgFlags()
+{
+ char *flagsPtr = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder) : (char *)NULL;
+ if (flagsPtr)
+ {
+ // the url is encodes the flags byte as ascii
+ int intFlags = atoi(flagsPtr);
+ m_flags = (imapMessageFlagsType) intFlags; // cast here
+ }
+ else
+ m_flags = 0;
+}
+
+void nsImapUrl::ParseListOfMessageIds()
+{
+ m_listOfMessageIds = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder) : (char *)NULL;
+ if (!m_listOfMessageIds)
+ m_validUrl = false;
+ else
+ {
+ m_listOfMessageIds = strdup(m_listOfMessageIds);
+ m_mimePartSelectorDetected = PL_strstr(m_listOfMessageIds, "&part=") != 0 || PL_strstr(m_listOfMessageIds, "?part=") != 0;
+
+ // if we're asking for just the body, don't download the whole message. see
+ // nsMsgQuote::QuoteMessage() for the "header=" settings when replying to msgs.
+ if (!m_fetchPartsOnDemand)
+ m_fetchPartsOnDemand = (PL_strstr(m_listOfMessageIds, "?header=quotebody") != 0 ||
+ PL_strstr(m_listOfMessageIds, "?header=only") != 0);
+ // if it's a spam filter trying to fetch the msg, don't let it get marked read.
+ if (PL_strstr(m_listOfMessageIds,"?header=filter") != 0)
+ m_imapAction = nsImapMsgFetchPeek;
+ }
+}
+
+void nsImapUrl::ParseCustomMsgFetchAttribute()
+{
+ m_msgFetchAttribute = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder) : (char *)nullptr;
+}
+
+void nsImapUrl::ParseNumBytes()
+{
+ const char *numBytes = (m_tokenPlaceHolder) ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder) : 0;
+ m_numBytesToFetch = numBytes ? atoi(numBytes) : 0;
+}
+
+// nsIMsgI18NUrl support
+
+nsresult nsImapUrl::GetMsgFolder(nsIMsgFolder **msgFolder)
+{
+ // if we have a RDF URI, then try to get the folder for that URI and then ask the folder
+ // for it's charset....
+
+ nsCString uri;
+ GetUri(getter_Copies(uri));
+ NS_ENSURE_TRUE(!uri.IsEmpty(), NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgDBHdr> msg;
+ GetMsgDBHdrFromURI(uri.get(), getter_AddRefs(msg));
+ NS_ENSURE_TRUE(msg, NS_ERROR_FAILURE);
+ nsresult rv = msg->GetFolder(msgFolder);
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ENSURE_TRUE(msgFolder, NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetFolderCharset(char ** aCharacterSet)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetMsgFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCString tmpStr;
+ folder->GetCharset(tmpStr);
+ *aCharacterSet = ToNewCString(tmpStr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetFolderCharsetOverride(bool * aCharacterSetOverride)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetMsgFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ENSURE_TRUE(folder, NS_ERROR_FAILURE);
+ folder->GetCharsetOverride(aCharacterSetOverride);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCharsetOverRide(char ** aCharacterSet)
+{
+ if (!mCharsetOverride.IsEmpty())
+ *aCharacterSet = ToNewCString(mCharsetOverride);
+ else
+ *aCharacterSet = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetCharsetOverRide(const char * aCharacterSet)
+{
+ mCharsetOverride = aCharacterSet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetStoreResultsOffline(bool *aStoreResultsOffline)
+{
+ NS_ENSURE_ARG_POINTER(aStoreResultsOffline);
+ *aStoreResultsOffline = m_storeResultsOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetStoreResultsOffline(bool aStoreResultsOffline)
+{
+ m_storeResultsOffline = aStoreResultsOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetStoreOfflineOnFallback(bool *aStoreOfflineOnFallback)
+{
+ NS_ENSURE_ARG_POINTER(aStoreOfflineOnFallback);
+ *aStoreOfflineOnFallback = m_storeOfflineOnFallback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetStoreOfflineOnFallback(bool aStoreOfflineOnFallback)
+{
+ m_storeOfflineOnFallback = aStoreOfflineOnFallback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetMessageHeader(nsIMsgDBHdr ** aMsgHdr)
+{
+ nsCString uri;
+ nsresult rv = GetUri(getter_Copies(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return GetMsgDBHdrFromURI(uri.get(), aMsgHdr);
+}
+
+NS_IMETHODIMP nsImapUrl::SetMessageHeader(nsIMsgDBHdr *aMsgHdr)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/mailnews/imap/src/nsImapUrl.h b/mailnews/imap/src/nsImapUrl.h
new file mode 100644
index 000000000..d1925d87a
--- /dev/null
+++ b/mailnews/imap/src/nsImapUrl.h
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImapUrl_h___
+#define nsImapUrl_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIImapUrl.h"
+#include "nsIImapMockChannel.h"
+#include "nsCOMPtr.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIImapMessageSink.h"
+
+#include "nsWeakPtr.h"
+#include "nsIFile.h"
+#include "mozilla/Mutex.h"
+
+class nsImapUrl : public nsIImapUrl, public nsMsgMailNewsUrl, public nsIMsgMessageUrl, public nsIMsgI18NUrl
+{
+public:
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIURI override
+ NS_IMETHOD SetSpec(const nsACString &aSpec) override;
+ NS_IMETHOD SetQuery(const nsACString &aQuery) override;
+ NS_IMETHOD CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef, nsIURI **_retval) override;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // we support the nsIImapUrl interface
+ //////////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIIMAPURL
+
+ // nsIMsgMailNewsUrl overrides
+ NS_IMETHOD IsUrlType(uint32_t type, bool *isType) override;
+ NS_IMETHOD GetFolder(nsIMsgFolder **aFolder) override;
+ NS_IMETHOD SetFolder(nsIMsgFolder *aFolder) override;
+ // nsIMsgMessageUrl
+ NS_DECL_NSIMSGMESSAGEURL
+ NS_DECL_NSIMSGI18NURL
+
+ // nsImapUrl
+ nsImapUrl();
+
+ static nsresult ConvertToCanonicalFormat(const char *folderName, char onlineDelimiter, char **resultingCanonicalPath);
+ static nsresult EscapeSlashes(const char *sourcePath, char **resultPath);
+ static nsresult UnescapeSlashes(char *path);
+ static char * ReplaceCharsInCopiedString(const char *stringToCopy, char oldChar, char newChar);
+
+protected:
+ virtual ~nsImapUrl();
+ virtual nsresult ParseUrl();
+
+ char *m_listOfMessageIds;
+
+ // handle the imap specific parsing
+ void ParseImapPart(char *imapPartOfUrl);
+
+ void ParseFolderPath(char **resultingCanonicalPath);
+ void ParseSearchCriteriaString();
+ void ParseUidChoice();
+ void ParseMsgFlags();
+ void ParseListOfMessageIds();
+ void ParseCustomMsgFetchAttribute();
+ void ParseNumBytes();
+
+ nsresult GetMsgFolder(nsIMsgFolder **msgFolder);
+
+ char *m_sourceCanonicalFolderPathSubString;
+ char *m_destinationCanonicalFolderPathSubString;
+ char *m_tokenPlaceHolder;
+ char *m_urlidSubString;
+ char m_onlineSubDirSeparator;
+ char *m_searchCriteriaString; // should we use m_search, or is this special?
+ nsCString m_command; // for custom commands
+ nsCString m_msgFetchAttribute; // for fetching custom msg attributes
+ nsCString m_customAttributeResult; // for fetching custom msg attributes
+ nsCString m_customCommandResult; // custom command response
+ nsCString m_customAddFlags; // these two are for setting and clearing custom flags
+ nsCString m_customSubtractFlags;
+ int32_t m_numBytesToFetch; // when doing a msg body preview, how many bytes to read
+ bool m_validUrl;
+ bool m_runningUrl;
+ bool m_idsAreUids;
+ bool m_mimePartSelectorDetected;
+ bool m_allowContentChange; // if false, we can't use Mime parts on demand
+ bool m_fetchPartsOnDemand; // if true, we should fetch leave parts on server.
+ bool m_msgLoadingFromCache; // if true, we might need to mark read on server
+ bool m_externalLinkUrl; // if true, we're running this url because the user
+ // True if the fetch results should be put in the offline store.
+ bool m_storeResultsOffline;
+ bool m_storeOfflineOnFallback;
+ bool m_localFetchOnly;
+ bool m_rerunningUrl; // first attempt running this failed with connection error; retrying
+ bool m_moreHeadersToDownload;
+ nsImapContentModifiedType m_contentModified;
+
+ int32_t m_extraStatus;
+
+ nsCString m_userName;
+ nsCString m_serverKey;
+ // event sinks
+ imapMessageFlagsType m_flags;
+ nsImapAction m_imapAction;
+
+ nsWeakPtr m_imapFolder;
+ nsWeakPtr m_imapMailFolderSink;
+ nsWeakPtr m_imapMessageSink;
+
+ nsWeakPtr m_imapServerSink;
+
+ // online message copy support; i don't have a better solution yet
+ nsCOMPtr <nsISupports> m_copyState; // now, refcounted.
+ nsCOMPtr<nsIFile> m_file;
+ nsWeakPtr m_channelWeakPtr;
+
+ // used by save message to disk
+ nsCOMPtr<nsIFile> m_messageFile;
+ bool m_addDummyEnvelope;
+ bool m_canonicalLineEnding; // CRLF
+
+ nsCString mURI; // the RDF URI associated with this url.
+ nsCString mCharsetOverride; // used by nsIMsgI18NUrl...
+ mozilla::Mutex mLock;
+};
+
+#endif /* nsImapUrl_h___ */
diff --git a/mailnews/imap/src/nsImapUtils.cpp b/mailnews/imap/src/nsImapUtils.cpp
new file mode 100644
index 000000000..b213d33f8
--- /dev/null
+++ b/mailnews/imap/src/nsImapUtils.cpp
@@ -0,0 +1,373 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsImapUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "prsystem.h"
+#include "prprf.h"
+#include "nsNetCID.h"
+
+// stuff for temporary root folder hack
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIImapIncomingServer.h"
+#include "nsMsgBaseCID.h"
+#include "nsImapCore.h"
+#include "nsMsgUtils.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsIMAPNamespace.h"
+#include "nsIImapFlagAndUidState.h"
+
+nsresult
+nsImapURI2FullName(const char* rootURI, const char* hostName, const char* uriStr,
+ char **name)
+{
+ nsAutoCString uri(uriStr);
+ nsAutoCString fullName;
+ if (uri.Find(rootURI) != 0)
+ return NS_ERROR_FAILURE;
+ fullName = Substring(uri, strlen(rootURI));
+ uri = fullName;
+ int32_t hostStart = uri.Find(hostName);
+ if (hostStart <= 0)
+ return NS_ERROR_FAILURE;
+ fullName = Substring(uri, hostStart);
+ uri = fullName;
+ int32_t hostEnd = uri.FindChar('/');
+ if (hostEnd <= 0)
+ return NS_ERROR_FAILURE;
+ fullName = Substring(uri, hostEnd + 1);
+ if (fullName.IsEmpty())
+ return NS_ERROR_FAILURE;
+ *name = ToNewCString(fullName);
+ return NS_OK;
+}
+
+/* parses ImapMessageURI */
+nsresult nsParseImapMessageURI(const char* uri, nsCString& folderURI, uint32_t *key, char **part)
+{
+ if(!key)
+ return NS_ERROR_NULL_POINTER;
+
+ nsAutoCString uriStr(uri);
+ int32_t folderEnd = -1;
+ // imap-message uri's can have imap:// url strings tacked on the end,
+ // e.g., when opening/saving attachments. We don't want to look for '#'
+ // in that part of the uri, if the attachment name contains '#',
+ // so check for that here.
+ if (StringBeginsWith(uriStr, NS_LITERAL_CSTRING("imap-message")))
+ folderEnd = uriStr.Find("imap://");
+
+ int32_t keySeparator = MsgRFindChar(uriStr, '#', folderEnd);
+ if(keySeparator != -1)
+ {
+ int32_t keyEndSeparator = MsgFindCharInSet(uriStr, "/?&", keySeparator);
+ nsAutoString folderPath;
+ folderURI = StringHead(uriStr, keySeparator);
+ folderURI.Cut(4, 8); // cut out the _message part of imap-message:
+ // folder uri's don't have fully escaped usernames.
+ int32_t atPos = folderURI.FindChar('@');
+ if (atPos != -1)
+ {
+ nsCString unescapedName, escapedName;
+ int32_t userNamePos = folderURI.Find("//") + 2;
+ uint32_t origUserNameLen = atPos - userNamePos;
+ if (NS_SUCCEEDED(MsgUnescapeString(Substring(folderURI, userNamePos,
+ origUserNameLen),
+ 0, unescapedName)))
+ {
+ // Re-escape the username, matching the way we do it in uris, not the
+ // way necko escapes urls. See nsMsgIncomingServer::GetServerURI.
+ MsgEscapeString(unescapedName, nsINetUtil::ESCAPE_XALPHAS, escapedName);
+ folderURI.Replace(userNamePos, origUserNameLen, escapedName);
+ }
+ }
+ nsAutoCString keyStr;
+ if (keyEndSeparator != -1)
+ keyStr = Substring(uriStr, keySeparator + 1, keyEndSeparator - (keySeparator + 1));
+ else
+ keyStr = Substring(uriStr, keySeparator + 1);
+
+ *key = strtoul(keyStr.get(), nullptr, 10);
+
+ if (part && keyEndSeparator != -1)
+ {
+ int32_t partPos = MsgFind(uriStr, "part=", false, keyEndSeparator);
+ if (partPos != -1)
+ {
+ *part = ToNewCString(Substring(uriStr, keyEndSeparator));
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsBuildImapMessageURI(const char *baseURI, uint32_t key, nsCString& uri)
+{
+ uri.Append(baseURI);
+ uri.Append('#');
+ uri.AppendInt(key);
+ return NS_OK;
+}
+
+nsresult nsCreateImapBaseMessageURI(const nsACString& baseURI, nsCString &baseMessageURI)
+{
+ nsAutoCString tailURI(baseURI);
+ // chop off imap:/
+ if (tailURI.Find(kImapRootURI) == 0)
+ tailURI.Cut(0, PL_strlen(kImapRootURI));
+ baseMessageURI = kImapMessageRootURI;
+ baseMessageURI += tailURI;
+ return NS_OK;
+}
+
+// nsImapMailboxSpec definition
+NS_IMPL_ISUPPORTS(nsImapMailboxSpec, nsIMailboxSpec)
+
+nsImapMailboxSpec::nsImapMailboxSpec()
+{
+ mFolder_UIDVALIDITY = 0;
+ mHighestModSeq = 0;
+ mNumOfMessages = 0;
+ mNumOfUnseenMessages = 0;
+ mNumOfRecentMessages = 0;
+ mNextUID = 0;
+
+ mBoxFlags = 0;
+ mSupportedUserFlags = 0;
+
+ mHierarchySeparator = '\0';
+
+ mFolderSelected = false;
+ mDiscoveredFromLsub = false;
+
+ mOnlineVerified = false;
+ mNamespaceForFolder = nullptr;
+}
+
+nsImapMailboxSpec::~nsImapMailboxSpec()
+{
+}
+
+NS_IMPL_GETSET(nsImapMailboxSpec, Folder_UIDVALIDITY, int32_t, mFolder_UIDVALIDITY)
+NS_IMPL_GETSET(nsImapMailboxSpec, HighestModSeq, uint64_t, mHighestModSeq)
+NS_IMPL_GETSET(nsImapMailboxSpec, NumMessages, int32_t, mNumOfMessages)
+NS_IMPL_GETSET(nsImapMailboxSpec, NumUnseenMessages, int32_t, mNumOfUnseenMessages)
+NS_IMPL_GETSET(nsImapMailboxSpec, NumRecentMessages, int32_t, mNumOfRecentMessages)
+NS_IMPL_GETSET(nsImapMailboxSpec, NextUID, int32_t, mNextUID)
+NS_IMPL_GETSET(nsImapMailboxSpec, HierarchyDelimiter, char, mHierarchySeparator)
+NS_IMPL_GETSET(nsImapMailboxSpec, FolderSelected, bool, mFolderSelected)
+NS_IMPL_GETSET(nsImapMailboxSpec, DiscoveredFromLsub, bool, mDiscoveredFromLsub)
+NS_IMPL_GETSET(nsImapMailboxSpec, OnlineVerified, bool, mOnlineVerified)
+NS_IMPL_GETSET(nsImapMailboxSpec, SupportedUserFlags, uint32_t, mSupportedUserFlags)
+NS_IMPL_GETSET(nsImapMailboxSpec, Box_flags, uint32_t, mBoxFlags)
+NS_IMPL_GETSET(nsImapMailboxSpec, NamespaceForFolder, nsIMAPNamespace *, mNamespaceForFolder)
+
+NS_IMETHODIMP nsImapMailboxSpec::GetAllocatedPathName(nsACString &aAllocatedPathName)
+{
+ aAllocatedPathName = mAllocatedPathName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::SetAllocatedPathName(const nsACString &aAllocatedPathName)
+{
+ mAllocatedPathName = aAllocatedPathName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::GetUnicharPathName(nsAString &aUnicharPathName)
+{
+ aUnicharPathName = aUnicharPathName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::SetUnicharPathName(const nsAString &aUnicharPathName)
+{
+ mUnicharPathName = aUnicharPathName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::GetHostName(nsACString &aHostName)
+{
+ aHostName = mHostName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::SetHostName(const nsACString &aHostName)
+{
+ mHostName = aHostName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::GetFlagState(nsIImapFlagAndUidState ** aFlagState)
+{
+ NS_ENSURE_ARG_POINTER(aFlagState);
+ NS_IF_ADDREF(*aFlagState = mFlagState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::SetFlagState(nsIImapFlagAndUidState * aFlagState)
+{
+ NS_ENSURE_ARG_POINTER(aFlagState);
+ mFlagState = aFlagState;
+ return NS_OK;
+}
+
+nsImapMailboxSpec& nsImapMailboxSpec::operator= (const nsImapMailboxSpec& aCopy)
+{
+ mFolder_UIDVALIDITY = aCopy.mFolder_UIDVALIDITY;
+ mHighestModSeq = aCopy.mHighestModSeq;
+ mNumOfMessages = aCopy.mNumOfMessages;
+ mNumOfUnseenMessages = aCopy.mNumOfUnseenMessages;
+ mNumOfRecentMessages = aCopy.mNumOfRecentMessages;
+
+ mBoxFlags = aCopy.mBoxFlags;
+ mSupportedUserFlags = aCopy.mSupportedUserFlags;
+
+ mAllocatedPathName.Assign(aCopy.mAllocatedPathName);
+ mUnicharPathName.Assign(aCopy.mUnicharPathName);
+ mHostName.Assign(aCopy.mHostName);
+
+ mFlagState = aCopy.mFlagState;
+ mNamespaceForFolder = aCopy.mNamespaceForFolder;
+
+ mFolderSelected = aCopy.mFolderSelected;
+ mDiscoveredFromLsub = aCopy.mDiscoveredFromLsub;
+
+ mOnlineVerified = aCopy.mOnlineVerified;
+
+ return *this;
+}
+
+// use the flagState to determine if the gaps in the msgUids correspond to gaps in the mailbox,
+// in which case we can still use ranges. If flagState is null, we won't do this.
+void AllocateImapUidString(uint32_t *msgUids, uint32_t &msgCount,
+ nsImapFlagAndUidState *flagState, nsCString &returnString)
+{
+ uint32_t startSequence = (msgCount > 0) ? msgUids[0] : 0xFFFFFFFF;
+ uint32_t curSequenceEnd = startSequence;
+ uint32_t total = msgCount;
+ int32_t curFlagStateIndex = -1;
+
+ // a partial fetch flag state doesn't help us, so don't use it.
+ if (flagState && flagState->GetPartialUIDFetch())
+ flagState = nullptr;
+
+
+ for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++)
+ {
+ uint32_t curKey = msgUids[keyIndex];
+ uint32_t nextKey = (keyIndex + 1 < total) ? msgUids[keyIndex + 1] : 0xFFFFFFFF;
+ bool lastKey = (nextKey == 0xFFFFFFFF);
+
+ if (lastKey)
+ curSequenceEnd = curKey;
+
+ if (!lastKey)
+ {
+ if (nextKey == curSequenceEnd + 1)
+ {
+ curSequenceEnd = nextKey;
+ curFlagStateIndex++;
+ continue;
+ }
+ if (flagState)
+ {
+ if (curFlagStateIndex == -1)
+ {
+ bool foundIt;
+ flagState->GetMessageFlagsFromUID(curSequenceEnd, &foundIt, &curFlagStateIndex);
+ if (!foundIt)
+ {
+ NS_WARNING("flag state missing key");
+ // The start of this sequence is missing from flag state, so move
+ // on to the next key.
+ curFlagStateIndex = -1;
+ curSequenceEnd = startSequence = nextKey;
+ continue;
+ }
+ }
+ curFlagStateIndex++;
+ uint32_t nextUidInFlagState;
+ nsresult rv = flagState->GetUidOfMessage(curFlagStateIndex, &nextUidInFlagState);
+ if (NS_SUCCEEDED(rv) && nextUidInFlagState == nextKey)
+ {
+ curSequenceEnd = nextKey;
+ continue;
+ }
+ }
+ }
+ if (curSequenceEnd > startSequence)
+ {
+ returnString.AppendInt((int64_t) startSequence);
+ returnString += ':';
+ returnString.AppendInt((int64_t) curSequenceEnd);
+ startSequence = nextKey;
+ curSequenceEnd = startSequence;
+ curFlagStateIndex = -1;
+ }
+ else
+ {
+ startSequence = nextKey;
+ curSequenceEnd = startSequence;
+ returnString.AppendInt((int64_t) msgUids[keyIndex]);
+ curFlagStateIndex = -1;
+ }
+ // check if we've generated too long a string - if there's no flag state,
+ // it means we just need to go ahead and generate a too long string
+ // because the calling code won't handle breaking up the strings.
+ if (flagState && returnString.Length() > 950)
+ {
+ msgCount = keyIndex;
+ break;
+ }
+ // If we are not the last item then we need to add the comma
+ // but it's important we do it here, after the length check
+ if (!lastKey)
+ returnString += ',';
+ }
+}
+
+void ParseUidString(const char *uidString, nsTArray<nsMsgKey> &keys)
+{
+ // This is in the form <id>,<id>, or <id1>:<id2>
+ if (!uidString)
+ return;
+
+ char curChar = *uidString;
+ bool isRange = false;
+ uint32_t curToken;
+ uint32_t saveStartToken = 0;
+
+ for (const char *curCharPtr = uidString; curChar && *curCharPtr;)
+ {
+ const char *currentKeyToken = curCharPtr;
+ curChar = *curCharPtr;
+ while (curChar != ':' && curChar != ',' && curChar != '\0')
+ curChar = *curCharPtr++;
+
+ // we don't need to null terminate currentKeyToken because strtoul
+ // stops at non-numeric chars.
+ curToken = strtoul(currentKeyToken, nullptr, 10);
+ if (isRange)
+ {
+ while (saveStartToken < curToken)
+ keys.AppendElement(saveStartToken++);
+ }
+ keys.AppendElement(curToken);
+ isRange = (curChar == ':');
+ if (isRange)
+ saveStartToken = curToken + 1;
+ }
+}
+
+void AppendUid(nsCString &msgIds, uint32_t uid)
+{
+ char buf[20];
+ PR_snprintf(buf, sizeof(buf), "%u", uid);
+ msgIds.Append(buf);
+}
diff --git a/mailnews/imap/src/nsImapUtils.h b/mailnews/imap/src/nsImapUtils.h
new file mode 100644
index 000000000..b24a38a00
--- /dev/null
+++ b/mailnews/imap/src/nsImapUtils.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_IMAPUTILS_H
+#define NS_IMAPUTILS_H
+
+#include "nsStringGlue.h"
+#include "nsIMsgIncomingServer.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIMailboxSpec.h"
+#include "nsCOMPtr.h"
+
+class nsImapFlagAndUidState;
+class nsImapProtocol;
+
+static const char kImapRootURI[] = "imap:/";
+static const char kImapMessageRootURI[] = "imap-message:/";
+static const char kModSeqPropertyName[] = "highestModSeq";
+static const char kHighestRecordedUIDPropertyName[] = "highestRecordedUID";
+static const char kDeletedHdrCountPropertyName[] = "numDeletedHeaders";
+
+extern nsresult
+nsImapURI2FullName(const char* rootURI, const char* hostname, const char* uriStr,
+ char **name);
+
+extern nsresult
+nsParseImapMessageURI(const char* uri, nsCString& folderURI, uint32_t *key, char **part);
+
+extern nsresult
+nsBuildImapMessageURI(const char *baseURI, uint32_t key, nsCString& uri);
+
+extern nsresult
+nsCreateImapBaseMessageURI(const nsACString& baseURI, nsCString& baseMessageURI);
+
+void AllocateImapUidString(uint32_t *msgUids, uint32_t &msgCount, nsImapFlagAndUidState *flagState, nsCString &returnString);
+void ParseUidString(const char *uidString, nsTArray<nsMsgKey> &keys);
+void AppendUid(nsCString &msgIds, uint32_t uid);
+
+class nsImapMailboxSpec : public nsIMailboxSpec
+{
+public:
+ nsImapMailboxSpec();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMAILBOXSPEC
+
+ nsImapMailboxSpec& operator= (const nsImapMailboxSpec& aCopy);
+
+ nsCOMPtr<nsIImapFlagAndUidState> mFlagState;
+ nsIMAPNamespace *mNamespaceForFolder;
+
+ uint32_t mBoxFlags;
+ uint32_t mSupportedUserFlags;
+ int32_t mFolder_UIDVALIDITY;
+ uint64_t mHighestModSeq;
+ int32_t mNumOfMessages;
+ int32_t mNumOfUnseenMessages;
+ int32_t mNumOfRecentMessages;
+ int32_t mNextUID;
+ nsCString mAllocatedPathName;
+ nsCString mHostName;
+ nsString mUnicharPathName;
+ char mHierarchySeparator;
+ bool mFolderSelected;
+ bool mDiscoveredFromLsub;
+ bool mOnlineVerified;
+
+ nsImapProtocol *mConnection; // do we need this? It seems evil
+
+private:
+ virtual ~nsImapMailboxSpec();
+};
+
+#endif //NS_IMAPUTILS_H
diff --git a/mailnews/imap/src/nsSyncRunnableHelpers.cpp b/mailnews/imap/src/nsSyncRunnableHelpers.cpp
new file mode 100644
index 000000000..2928df8eb
--- /dev/null
+++ b/mailnews/imap/src/nsSyncRunnableHelpers.cpp
@@ -0,0 +1,601 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSyncRunnableHelpers.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgWindow.h"
+#include "nsImapMailFolder.h"
+
+#include "mozilla/Monitor.h"
+
+NS_IMPL_ISUPPORTS(StreamListenerProxy, nsIStreamListener)
+NS_IMPL_ISUPPORTS(ImapMailFolderSinkProxy, nsIImapMailFolderSink)
+NS_IMPL_ISUPPORTS(ImapServerSinkProxy, nsIImapServerSink)
+NS_IMPL_ISUPPORTS(ImapMessageSinkProxy,
+ nsIImapMessageSink)
+NS_IMPL_ISUPPORTS(ImapProtocolSinkProxy,
+ nsIImapProtocolSink)
+namespace {
+
+// Traits class for a reference type, specialized for parameters which are
+// already references.
+template<typename T>
+struct RefType
+{
+ typedef T& type;
+};
+
+template<>
+struct RefType<nsAString&>
+{
+ typedef nsAString& type;
+};
+
+template<>
+struct RefType<const nsAString&>
+{
+ typedef const nsAString& type;
+};
+
+template<>
+struct RefType<nsACString&>
+{
+ typedef nsACString& type;
+};
+
+template<>
+struct RefType<const nsACString&>
+{
+ typedef const nsACString& type;
+};
+
+template<>
+struct RefType<const nsIID&>
+{
+ typedef const nsIID& type;
+};
+
+class SyncRunnableBase : public mozilla::Runnable
+{
+public:
+ nsresult Result() {
+ return mResult;
+ }
+
+ mozilla::Monitor& Monitor() {
+ return mMonitor;
+ }
+
+protected:
+ SyncRunnableBase()
+ : mResult(NS_ERROR_UNEXPECTED)
+ , mMonitor("SyncRunnableBase")
+ { }
+
+ nsresult mResult;
+ mozilla::Monitor mMonitor;
+};
+
+template<typename Receiver>
+class SyncRunnable0 : public SyncRunnableBase
+{
+public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)();
+
+ SyncRunnable0(Receiver* receiver, ReceiverMethod method)
+ : mReceiver(receiver)
+ , mMethod(method)
+ { }
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)();
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+};
+
+
+template<typename Receiver, typename Arg1>
+class SyncRunnable1 : public SyncRunnableBase
+{
+public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1);
+ typedef typename RefType<Arg1>::type Arg1Ref;
+
+ SyncRunnable1(Receiver* receiver, ReceiverMethod method,
+ Arg1Ref arg1)
+ : mReceiver(receiver)
+ , mMethod(method)
+ , mArg1(arg1)
+ { }
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)(mArg1);
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+ Arg1Ref mArg1;
+};
+
+template<typename Receiver, typename Arg1, typename Arg2>
+class SyncRunnable2 : public SyncRunnableBase
+{
+public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2);
+ typedef typename RefType<Arg1>::type Arg1Ref;
+ typedef typename RefType<Arg2>::type Arg2Ref;
+
+ SyncRunnable2(Receiver* receiver, ReceiverMethod method,
+ Arg1Ref arg1, Arg2Ref arg2)
+ : mReceiver(receiver)
+ , mMethod(method)
+ , mArg1(arg1)
+ , mArg2(arg2)
+ { }
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)(mArg1, mArg2);
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+ Arg1Ref mArg1;
+ Arg2Ref mArg2;
+};
+
+template<typename Receiver, typename Arg1, typename Arg2, typename Arg3>
+class SyncRunnable3 : public SyncRunnableBase
+{
+public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3);
+ typedef typename RefType<Arg1>::type Arg1Ref;
+ typedef typename RefType<Arg2>::type Arg2Ref;
+ typedef typename RefType<Arg3>::type Arg3Ref;
+
+ SyncRunnable3(Receiver* receiver, ReceiverMethod method,
+ Arg1Ref arg1, Arg2Ref arg2, Arg3Ref arg3)
+ : mReceiver(receiver)
+ , mMethod(method)
+ , mArg1(arg1)
+ , mArg2(arg2)
+ , mArg3(arg3)
+ { }
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)(mArg1, mArg2, mArg3);
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+ Arg1Ref mArg1;
+ Arg2Ref mArg2;
+ Arg3Ref mArg3;
+};
+
+template<typename Receiver, typename Arg1, typename Arg2, typename Arg3,
+ typename Arg4>
+class SyncRunnable4 : public SyncRunnableBase
+{
+public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3, Arg4);
+ typedef typename RefType<Arg1>::type Arg1Ref;
+ typedef typename RefType<Arg2>::type Arg2Ref;
+ typedef typename RefType<Arg3>::type Arg3Ref;
+ typedef typename RefType<Arg4>::type Arg4Ref;
+
+ SyncRunnable4(Receiver* receiver, ReceiverMethod method,
+ Arg1Ref arg1, Arg2Ref arg2, Arg3Ref arg3, Arg4Ref arg4)
+ : mReceiver(receiver)
+ , mMethod(method)
+ , mArg1(arg1)
+ , mArg2(arg2)
+ , mArg3(arg3)
+ , mArg4(arg4)
+ { }
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)(mArg1, mArg2, mArg3, mArg4);
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+ Arg1Ref mArg1;
+ Arg2Ref mArg2;
+ Arg3Ref mArg3;
+ Arg4Ref mArg4;
+};
+
+template<typename Receiver, typename Arg1, typename Arg2, typename Arg3,
+ typename Arg4, typename Arg5>
+class SyncRunnable5 : public SyncRunnableBase
+{
+public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3, Arg4, Arg5);
+ typedef typename RefType<Arg1>::type Arg1Ref;
+ typedef typename RefType<Arg2>::type Arg2Ref;
+ typedef typename RefType<Arg3>::type Arg3Ref;
+ typedef typename RefType<Arg4>::type Arg4Ref;
+ typedef typename RefType<Arg5>::type Arg5Ref;
+
+ SyncRunnable5(Receiver* receiver, ReceiverMethod method,
+ Arg1Ref arg1, Arg2Ref arg2, Arg3Ref arg3, Arg4Ref arg4, Arg5Ref arg5)
+ : mReceiver(receiver)
+ , mMethod(method)
+ , mArg1(arg1)
+ , mArg2(arg2)
+ , mArg3(arg3)
+ , mArg4(arg4)
+ , mArg5(arg5)
+ { }
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)(mArg1, mArg2, mArg3, mArg4, mArg5);
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+ Arg1Ref mArg1;
+ Arg2Ref mArg2;
+ Arg3Ref mArg3;
+ Arg4Ref mArg4;
+ Arg5Ref mArg5;
+};
+
+nsresult
+DispatchSyncRunnable(SyncRunnableBase* r)
+{
+ if (NS_IsMainThread()) {
+ r->Run();
+ }
+ else {
+ mozilla::MonitorAutoLock lock(r->Monitor());
+ nsresult rv = NS_DispatchToMainThread(r);
+ if (NS_FAILED(rv))
+ return rv;
+ lock.Wait();
+ }
+ return r->Result();
+}
+
+} // anonymous namespace
+
+#define NS_SYNCRUNNABLEMETHOD0(iface, method) \
+ NS_IMETHODIMP iface##Proxy::method() { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable0<nsI##iface> \
+ (mReceiver, &nsI##iface::method); \
+ return DispatchSyncRunnable(r); \
+ }
+
+
+#define NS_SYNCRUNNABLEMETHOD1(iface, method, \
+ arg1) \
+ NS_IMETHODIMP iface##Proxy::method(arg1 a1) { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable1<nsI##iface, arg1> \
+ (mReceiver, &nsI##iface::method, a1); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEMETHOD2(iface, method, \
+ arg1, arg2) \
+ NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2) { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable2<nsI##iface, arg1, arg2> \
+ (mReceiver, &nsI##iface::method, a1, a2); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEMETHOD3(iface, method, \
+ arg1, arg2, arg3) \
+ NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2, arg3 a3) { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable3<nsI##iface, arg1, arg2, arg3> \
+ (mReceiver, &nsI##iface::method, \
+ a1, a2, a3); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEMETHOD4(iface, method, \
+ arg1, arg2, arg3, arg4) \
+ NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2, arg3 a3, arg4 a4) { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable4<nsI##iface, arg1, arg2, arg3, arg4> \
+ (mReceiver, &nsI##iface::method, \
+ a1, a2, a3, a4); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEMETHOD5(iface, method, \
+ arg1, arg2, arg3, arg4, arg5) \
+ NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2, arg3 a3, arg4 a4, arg5 a5) { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable5<nsI##iface, arg1, arg2, arg3, arg4, arg5> \
+ (mReceiver, &nsI##iface::method, \
+ a1, a2, a3, a4, a5); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEATTRIBUTE(iface, attribute, \
+ type) \
+NS_IMETHODIMP iface##Proxy::Get##attribute(type *a1) { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable1<nsI##iface, type *> \
+ (mReceiver, &nsI##iface::Get##attribute, a1); \
+ return DispatchSyncRunnable(r); \
+ } \
+NS_IMETHODIMP iface##Proxy::Set##attribute(type a1) { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable1<nsI##iface, type> \
+ (mReceiver, &nsI##iface::Set##attribute, a1); \
+ return DispatchSyncRunnable(r); \
+ }
+
+
+#define NS_NOTIMPLEMENTED \
+ { NS_RUNTIMEABORT("Not implemented"); return NS_ERROR_UNEXPECTED; }
+
+NS_SYNCRUNNABLEMETHOD5(StreamListener, OnDataAvailable,
+ nsIRequest *, nsISupports *, nsIInputStream *, uint64_t, uint32_t)
+
+NS_SYNCRUNNABLEMETHOD2(StreamListener, OnStartRequest,
+ nsIRequest *, nsISupports *)
+
+NS_SYNCRUNNABLEMETHOD3(StreamListener, OnStopRequest,
+ nsIRequest *, nsISupports *, nsresult)
+
+NS_SYNCRUNNABLEMETHOD2(ImapProtocolSink, GetUrlWindow, nsIMsgMailNewsUrl *,
+ nsIMsgWindow **)
+
+NS_SYNCRUNNABLEMETHOD0(ImapProtocolSink, CloseStreams)
+NS_SYNCRUNNABLEMETHOD0(ImapProtocolSink, SetupMainThreadProxies)
+
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderNeedsACLListed, bool)
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderNeedsSubscribing, bool)
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderNeedsAdded, bool)
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, AclFlags, uint32_t)
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, UidValidity, int32_t)
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderQuotaCommandIssued, bool)
+NS_SYNCRUNNABLEMETHOD3(ImapMailFolderSink, SetFolderQuotaData, const nsACString &, uint32_t, uint32_t)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, GetShouldDownloadAllHeaders, bool *)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, GetOnlineDelimiter, char *)
+NS_SYNCRUNNABLEMETHOD0(ImapMailFolderSink, OnNewIdleMessages)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, UpdateImapMailboxStatus, nsIImapProtocol *, nsIMailboxSpec *)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, UpdateImapMailboxInfo, nsIImapProtocol *, nsIMailboxSpec *)
+NS_SYNCRUNNABLEMETHOD4(ImapMailFolderSink, GetMsgHdrsToDownload, bool *, int32_t *, uint32_t *, nsMsgKey **)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, ParseMsgHdrs, nsIImapProtocol *, nsIImapHeaderXferInfo *)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, AbortHeaderParseStream, nsIImapProtocol *)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, OnlineCopyCompleted, nsIImapProtocol *, ImapOnlineCopyState)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, StartMessage, nsIMsgMailNewsUrl *)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, EndMessage, nsIMsgMailNewsUrl *, nsMsgKey)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, NotifySearchHit, nsIMsgMailNewsUrl *, const char *)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, CopyNextStreamMessage, bool, nsISupports *)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, CloseMockChannel, nsIImapMockChannel *)
+NS_SYNCRUNNABLEMETHOD5(ImapMailFolderSink, SetUrlState, nsIImapProtocol *, nsIMsgMailNewsUrl *,
+ bool, bool, nsresult)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, ReleaseUrlCacheEntry, nsIMsgMailNewsUrl *)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, HeaderFetchCompleted, nsIImapProtocol *)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, SetBiffStateAndUpdate, int32_t)
+NS_SYNCRUNNABLEMETHOD3(ImapMailFolderSink, ProgressStatusString, nsIImapProtocol*, const char*, const char16_t *)
+NS_SYNCRUNNABLEMETHOD4(ImapMailFolderSink, PercentProgress, nsIImapProtocol*, const char16_t *, int64_t, int64_t)
+NS_SYNCRUNNABLEMETHOD0(ImapMailFolderSink, ClearFolderRights)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, SetCopyResponseUid, const char *, nsIImapUrl *)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, SetAppendMsgUid, nsMsgKey, nsIImapUrl *)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, GetMessageId, nsIImapUrl *, nsACString &)
+
+NS_SYNCRUNNABLEMETHOD2(ImapMessageSink, SetupMsgWriteStream, nsIFile *, bool)
+NS_SYNCRUNNABLEMETHOD3(ImapMessageSink, ParseAdoptedMsgLine, const char *, nsMsgKey, nsIImapUrl *)
+NS_SYNCRUNNABLEMETHOD4(ImapMessageSink, NormalEndMsgWriteStream, nsMsgKey, bool, nsIImapUrl *, int32_t)
+NS_SYNCRUNNABLEMETHOD0(ImapMessageSink, AbortMsgWriteStream)
+NS_SYNCRUNNABLEMETHOD0(ImapMessageSink, BeginMessageUpload)
+NS_SYNCRUNNABLEMETHOD4(ImapMessageSink, NotifyMessageFlags, uint32_t, const nsACString &, nsMsgKey, uint64_t)
+NS_SYNCRUNNABLEMETHOD3(ImapMessageSink, NotifyMessageDeleted, const char *, bool, const char *)
+NS_SYNCRUNNABLEMETHOD2(ImapMessageSink, GetMessageSizeFromDB, const char *, uint32_t *)
+NS_SYNCRUNNABLEMETHOD2(ImapMessageSink, SetContentModified, nsIImapUrl *, nsImapContentModifiedType)
+NS_SYNCRUNNABLEMETHOD4(ImapMessageSink, GetCurMoveCopyMessageInfo, nsIImapUrl *, PRTime *, nsACString &, uint32_t *)
+
+NS_SYNCRUNNABLEMETHOD4(ImapServerSink, PossibleImapMailbox, const nsACString &, char, int32_t, bool *)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FolderNeedsACLInitialized, const nsACString &, bool *)
+NS_SYNCRUNNABLEMETHOD3(ImapServerSink, AddFolderRights, const nsACString &, const nsACString &, const nsACString &)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, RefreshFolderRights, const nsACString &)
+NS_SYNCRUNNABLEMETHOD0(ImapServerSink, DiscoveryDone)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, OnlineFolderDelete, const nsACString &)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, OnlineFolderCreateFailed, const nsACString &)
+NS_SYNCRUNNABLEMETHOD3(ImapServerSink, OnlineFolderRename, nsIMsgWindow *, const nsACString &, const nsACString &)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FolderIsNoSelect, const nsACString &, bool *)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, SetFolderAdminURL, const nsACString &, const nsACString &)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FolderVerifiedOnline, const nsACString &, bool *)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetCapability, eIMAPCapabilityFlags)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerID, const nsACString &)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, LoadNextQueuedUrl, nsIImapProtocol *, bool *)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, PrepareToRetryUrl, nsIImapUrl *, nsIImapMockChannel **)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SuspendUrl, nsIImapUrl *)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, RetryUrl, nsIImapUrl *, nsIImapMockChannel *)
+NS_SYNCRUNNABLEMETHOD0(ImapServerSink, AbortQueuedUrls)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, GetImapStringByName, const char*, nsAString &)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, PromptLoginFailed, nsIMsgWindow *, int32_t *)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FEAlert, const nsAString &, nsIMsgMailNewsUrl *)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FEAlertWithName, const char*, nsIMsgMailNewsUrl *)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FEAlertFromServer, const nsACString &, nsIMsgMailNewsUrl *)
+NS_SYNCRUNNABLEMETHOD0(ImapServerSink, CommitNamespaces)
+NS_SYNCRUNNABLEMETHOD3(ImapServerSink, AsyncGetPassword, nsIImapProtocol *, bool, nsACString &)
+NS_SYNCRUNNABLEATTRIBUTE(ImapServerSink, UserAuthenticated, bool)
+NS_SYNCRUNNABLEMETHOD3(ImapServerSink, SetMailServerUrls, const nsACString &, const nsACString &, const nsACString &)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetArbitraryHeaders, nsACString &)
+NS_SYNCRUNNABLEMETHOD0(ImapServerSink, ForgetPassword)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetShowAttachmentsInline, bool *)
+NS_SYNCRUNNABLEMETHOD3(ImapServerSink, CramMD5Hash, const char *, const char *, char **)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetLoginUsername, nsACString &)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, UpdateTrySTARTTLSPref, bool)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetOriginalUsername, nsACString &)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetServerKey, nsACString &)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetServerPassword, nsACString &)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, RemoveServerConnection, nsIImapProtocol *)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetServerShuttingDown, bool *)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, ResetServerConnection, const nsACString &)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerDoingLsub, bool)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerForceSelect, const nsACString &)
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+namespace mozilla {
+namespace mailnews {
+
+NS_IMPL_ISUPPORTS(OAuth2ThreadHelper, msgIOAuth2ModuleListener)
+
+OAuth2ThreadHelper::OAuth2ThreadHelper(nsIMsgIncomingServer *aServer)
+: mMonitor("OAuth thread lock"),
+ mServer(aServer)
+{
+}
+
+OAuth2ThreadHelper::~OAuth2ThreadHelper()
+{
+ if (mOAuth2Support)
+ {
+ NS_ReleaseOnMainThread(mOAuth2Support.forget());
+ }
+}
+
+bool OAuth2ThreadHelper::SupportsOAuth2()
+{
+ // Acquire a lock early, before reading anything. Guarantees memory visibility
+ // issues.
+ MonitorAutoLock lockGuard(mMonitor);
+
+ // If we don't have a server, we can't init, and therefore, we don't support
+ // OAuth2.
+ if (!mServer)
+ return false;
+
+ // If we have this, then we support OAuth2.
+ if (mOAuth2Support)
+ return true;
+
+ // Initialize. This needs to be done on-main-thread: if we're off that thread,
+ // synchronously dispatch to the main thread.
+ if (NS_IsMainThread())
+ {
+ MonitorAutoUnlock lockGuard(mMonitor);
+ Init();
+ }
+ else
+ {
+ nsCOMPtr<nsIRunnable> runInit =
+ NewRunnableMethod(this, &OAuth2ThreadHelper::Init);
+ NS_DispatchToMainThread(runInit);
+ mMonitor.Wait();
+ }
+
+ // After synchronously initializing, if we didn't get an object, then we don't
+ // support XOAuth2.
+ return mOAuth2Support != nullptr;
+}
+
+void OAuth2ThreadHelper::GetXOAuth2String(nsACString &base64Str)
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "This method cannot run on the main thread");
+
+ // Acquire a lock early, before reading anything. Guarantees memory visibility
+ // issues.
+ MonitorAutoLock lockGuard(mMonitor);
+
+ // Umm... what are you trying to do?
+ if (!mOAuth2Support)
+ return;
+
+ nsCOMPtr<nsIRunnable> runInit =
+ NewRunnableMethod(this, &OAuth2ThreadHelper::Connect);
+ NS_DispatchToMainThread(runInit);
+ mMonitor.Wait();
+
+ // Now we either have the string, or we failed (in which case the string is
+ // empty).
+ base64Str = mOAuth2String;
+}
+
+void OAuth2ThreadHelper::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+ MonitorAutoLock lockGuard(mMonitor);
+
+ // Create the OAuth2 helper module and initialize it. If the preferences are
+ // not set up on this server, we don't support OAuth2, and we nullify our
+ // members to indicate this.
+ mOAuth2Support = do_CreateInstance(MSGIOAUTH2MODULE_CONTRACTID);
+ if (mOAuth2Support)
+ {
+ bool supportsOAuth = false;
+ mOAuth2Support->InitFromMail(mServer, &supportsOAuth);
+ if (!supportsOAuth)
+ mOAuth2Support = nullptr;
+ }
+
+ // There is now no longer any need for the server. Kill it now--this helps
+ // prevent us from maintaining a refcount cycle.
+ mServer = nullptr;
+
+ // Notify anyone waiting that we're done.
+ mMonitor.Notify();
+}
+
+void OAuth2ThreadHelper::Connect()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+ MOZ_ASSERT(mOAuth2Support, "Should not be here if no OAuth2 support");
+
+ // OK to delay lock since mOAuth2Support is only written on main thread.
+ nsresult rv = mOAuth2Support->Connect(true, this);
+ // If the method failed, we'll never get a callback, so notify the monitor
+ // immediately so that IMAP can react.
+ if (NS_FAILED(rv))
+ {
+ MonitorAutoLock lockGuard(mMonitor);
+ mMonitor.Notify();
+ }
+}
+
+nsresult OAuth2ThreadHelper::OnSuccess(const nsACString &aAccessToken)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+ MonitorAutoLock lockGuard(mMonitor);
+
+ MOZ_ASSERT(mOAuth2Support, "Should not be here if no OAuth2 support");
+ mOAuth2Support->BuildXOAuth2String(mOAuth2String);
+ mMonitor.Notify();
+ return NS_OK;
+}
+
+nsresult OAuth2ThreadHelper::OnFailure(nsresult aError)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+ MonitorAutoLock lockGuard(mMonitor);
+
+ mOAuth2String.Truncate();
+ mMonitor.Notify();
+ return NS_OK;
+}
+
+} // namespace mailnews
+} // namespace mozilla
+#endif \ No newline at end of file
diff --git a/mailnews/imap/src/nsSyncRunnableHelpers.h b/mailnews/imap/src/nsSyncRunnableHelpers.h
new file mode 100644
index 000000000..273810a70
--- /dev/null
+++ b/mailnews/imap/src/nsSyncRunnableHelpers.h
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSyncRunnableHelpers_h
+#define nsSyncRunnableHelpers_h
+
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+#include "mozilla/Monitor.h"
+#include "msgIOAuth2Module.h"
+#endif
+
+#include "nsIStreamListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIImapProtocolSink.h"
+#include "nsIImapMessageSink.h"
+
+// The classes in this file proxy method calls to the main thread
+// synchronously. The main thread must not block on this thread, or a
+// deadlock condition can occur.
+
+class StreamListenerProxy final : public nsIStreamListener
+{
+public:
+ explicit StreamListenerProxy(nsIStreamListener* receiver)
+ : mReceiver(receiver)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+private:
+ ~StreamListenerProxy() {
+ NS_ReleaseOnMainThread(mReceiver.forget());
+ }
+ nsCOMPtr<nsIStreamListener> mReceiver;
+};
+
+class ImapMailFolderSinkProxy final : public nsIImapMailFolderSink
+{
+public:
+ explicit ImapMailFolderSinkProxy(nsIImapMailFolderSink* receiver)
+ : mReceiver(receiver)
+ {
+ NS_ASSERTION(receiver, "Don't allow receiver is nullptr");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPMAILFOLDERSINK
+
+private:
+ ~ImapMailFolderSinkProxy() {
+ NS_ReleaseOnMainThread(mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapMailFolderSink> mReceiver;
+};
+
+class ImapServerSinkProxy final : public nsIImapServerSink
+{
+public:
+ explicit ImapServerSinkProxy(nsIImapServerSink* receiver)
+ : mReceiver(receiver)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPSERVERSINK
+
+private:
+ ~ImapServerSinkProxy() {
+ NS_ReleaseOnMainThread(mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapServerSink> mReceiver;
+};
+
+
+class ImapMessageSinkProxy final : public nsIImapMessageSink
+{
+public:
+ explicit ImapMessageSinkProxy(nsIImapMessageSink* receiver)
+ : mReceiver(receiver)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPMESSAGESINK
+
+private:
+ ~ImapMessageSinkProxy() {
+ NS_ReleaseOnMainThread(mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapMessageSink> mReceiver;
+};
+
+class ImapProtocolSinkProxy final : public nsIImapProtocolSink
+{
+public:
+ explicit ImapProtocolSinkProxy(nsIImapProtocolSink* receiver)
+ : mReceiver(receiver)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPPROTOCOLSINK
+
+private:
+ ~ImapProtocolSinkProxy() {
+ NS_ReleaseOnMainThread(mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapProtocolSink> mReceiver;
+};
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+class msgIOAuth2Module;
+class nsIMsgIncomingServer;
+class nsIVariant;
+class nsIWritableVariant;
+
+namespace mozilla {
+namespace mailnews {
+
+class OAuth2ThreadHelper final : public msgIOAuth2ModuleListener
+{
+public:
+ OAuth2ThreadHelper(nsIMsgIncomingServer *aServer);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MSGIOAUTH2MODULELISTENER
+
+ bool SupportsOAuth2();
+ void GetXOAuth2String(nsACString &base64Str);
+
+private:
+ ~OAuth2ThreadHelper();
+ void Init();
+ void Connect();
+
+ Monitor mMonitor;
+ nsCOMPtr<msgIOAuth2Module> mOAuth2Support;
+ nsCOMPtr<nsIMsgIncomingServer> mServer;
+ nsCString mOAuth2String;
+};
+
+} // namespace mailnews
+} // namespace mozilla
+#endif
+
+#endif // nsSyncRunnableHelpers_h
diff --git a/mailnews/import/applemail/src/moz.build b/mailnews/import/applemail/src/moz.build
new file mode 100644
index 000000000..1955cf30a
--- /dev/null
+++ b/mailnews/import/applemail/src/moz.build
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsAppleMailImport.cpp',
+]
+
+SOURCES += [
+ 'nsEmlxHelperUtils.mm',
+]
+
+FINAL_LIBRARY = 'import'
+
diff --git a/mailnews/import/applemail/src/nsAppleMailImport.cpp b/mailnews/import/applemail/src/nsAppleMailImport.cpp
new file mode 100644
index 000000000..3097b7df5
--- /dev/null
+++ b/mailnews/import/applemail/src/nsAppleMailImport.cpp
@@ -0,0 +1,623 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIImportService.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIImportGeneric.h"
+#include "nsIFile.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIMutableArray.h"
+#include "nsNetUtil.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+#include "nsEmlxHelperUtils.h"
+#include "nsAppleMailImport.h"
+#include "nsIOutputStream.h"
+
+PRLogModuleInfo *APPLEMAILLOGMODULE = nullptr;
+
+// some hard-coded strings
+#define DEFAULT_MAIL_FOLDER "~/Library/Mail/"
+#define POP_MBOX_SUFFIX ".mbox"
+#define IMAP_MBOX_SUFFIX ".imapmbox"
+
+// stringbundle URI
+#define APPLEMAIL_MSGS_URL "chrome://messenger/locale/appleMailImportMsgs.properties"
+
+// magic constants
+#define kAccountMailboxID 1234
+
+nsAppleMailImportModule::nsAppleMailImportModule()
+{
+ // Init logging module.
+ if (!APPLEMAILLOGMODULE)
+ APPLEMAILLOGMODULE = PR_NewLogModule("APPLEMAILIMPORTLOG");
+
+ IMPORT_LOG0("nsAppleMailImportModule Created");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (bundleService)
+ bundleService->CreateBundle(APPLEMAIL_MSGS_URL, getter_AddRefs(mBundle));
+}
+
+
+nsAppleMailImportModule::~nsAppleMailImportModule()
+{
+ IMPORT_LOG0("nsAppleMailImportModule Deleted");
+}
+
+
+NS_IMPL_ISUPPORTS(nsAppleMailImportModule, nsIImportModule)
+
+
+NS_IMETHODIMP nsAppleMailImportModule::GetName(char16_t **aName)
+{
+ return mBundle ?
+ mBundle->GetStringFromName(u"ApplemailImportName", aName) : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetDescription(char16_t **aName)
+{
+ return mBundle ?
+ mBundle->GetStringFromName(u"ApplemailImportDescription", aName) : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetSupports(char **aSupports)
+{
+ NS_ENSURE_ARG_POINTER(aSupports);
+ *aSupports = strdup(NS_IMPORT_MAIL_STR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetSupportsUpgrade(bool *aUpgrade)
+{
+ NS_ENSURE_ARG_POINTER(aUpgrade);
+ *aUpgrade = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetImportInterface(const char *aImportType, nsISupports **aInterface)
+{
+ NS_ENSURE_ARG_POINTER(aImportType);
+ NS_ENSURE_ARG_POINTER(aInterface);
+ *aInterface = nullptr;
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ if (!strcmp(aImportType, "mail")) {
+ nsCOMPtr<nsIImportMail> mail(do_CreateInstance(NS_APPLEMAILIMPL_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportGeneric> generic;
+ rv = impSvc->CreateNewGenericMail(getter_AddRefs(generic));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString name;
+ rv = mBundle->GetStringFromName(u"ApplemailImportName", getter_Copies(name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsString> nameString(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nameString->SetData(name);
+
+ generic->SetData("name", nameString);
+ generic->SetData("mailInterface", mail);
+
+ generic.forget(aInterface);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+#pragma mark -
+
+nsAppleMailImportMail::nsAppleMailImportMail() : mProgress(0), mCurDepth(0)
+{
+ IMPORT_LOG0("nsAppleMailImportMail created");
+}
+
+nsresult nsAppleMailImportMail::Initialize()
+{
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ return bundleService->CreateBundle(APPLEMAIL_MSGS_URL, getter_AddRefs(mBundle));
+}
+
+nsAppleMailImportMail::~nsAppleMailImportMail()
+{
+ IMPORT_LOG0("nsAppleMailImportMail destroyed");
+}
+
+NS_IMPL_ISUPPORTS(nsAppleMailImportMail, nsIImportMail)
+
+NS_IMETHODIMP nsAppleMailImportMail::GetDefaultLocation(nsIFile **aLocation, bool *aFound, bool *aUserVerify)
+{
+ NS_ENSURE_ARG_POINTER(aFound);
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(aUserVerify);
+
+ *aLocation = nullptr;
+ *aFound = false;
+ *aUserVerify = true;
+
+ // try to find current user's top-level Mail folder
+ nsCOMPtr<nsIFile> mailFolder(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+ if (mailFolder) {
+ nsresult rv = mailFolder->InitWithNativePath(NS_LITERAL_CSTRING(DEFAULT_MAIL_FOLDER));
+ if (NS_SUCCEEDED(rv)) {
+ *aFound = true;
+ *aUserVerify = false;
+ mailFolder.forget(aLocation);
+ }
+ }
+
+ return NS_OK;
+}
+
+// this is the method that initiates all searching for mailboxes.
+// it will assume that it has a directory like ~/Library/Mail/
+NS_IMETHODIMP nsAppleMailImportMail::FindMailboxes(nsIFile *aMailboxFile, nsIArray **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aMailboxFile);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ IMPORT_LOG0("FindMailboxes for Apple mail invoked");
+
+ bool exists = false;
+ nsresult rv = aMailboxFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIImportService> importService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> resultsArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (!resultsArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mCurDepth = 1;
+
+ // 1. look for accounts with mailboxes
+ FindAccountMailDirs(aMailboxFile, resultsArray, importService);
+ mCurDepth--;
+
+ if (NS_SUCCEEDED(rv)) {
+ // 2. look for "global" mailboxes, that don't belong to any specific account. they are inside the
+ // root's Mailboxes/ folder
+ nsCOMPtr<nsIFile> mailboxesDir(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ mailboxesDir->InitWithFile(aMailboxFile);
+ rv = mailboxesDir->Append(NS_LITERAL_STRING("Mailboxes"));
+ if (NS_SUCCEEDED(rv)) {
+ IMPORT_LOG0("Looking for global Apple mailboxes");
+
+ mCurDepth++;
+ rv = FindMboxDirs(mailboxesDir, resultsArray, importService);
+ mCurDepth--;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && resultsArray)
+ resultsArray.forget(aResult);
+
+ return rv;
+}
+
+// operates on the Mail/ directory root, trying to find accounts (which are folders named something like "POP-hwaara@gmail.com")
+// and add their .mbox dirs
+void nsAppleMailImportMail::FindAccountMailDirs(nsIFile *aRoot, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService)
+{
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ nsresult rv = aRoot->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ if (NS_FAILED(rv))
+ return;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && hasMore) {
+
+ // get the next file entry
+ nsCOMPtr<nsIFile> currentEntry;
+ {
+ nsCOMPtr<nsISupports> rawSupports;
+ directoryEnumerator->GetNext(getter_AddRefs(rawSupports));
+ if (!rawSupports)
+ continue;
+ currentEntry = do_QueryInterface(rawSupports);
+ if (!currentEntry)
+ continue;
+ }
+
+ // make sure it's a directory
+ bool isDirectory = false;
+ currentEntry->IsDirectory(&isDirectory);
+
+ if (isDirectory) {
+ // now let's see if it's an account folder. if so, we want to traverse it for .mbox children
+ nsAutoString folderName;
+ currentEntry->GetLeafName(folderName);
+ bool isAccountFolder = false;
+
+ if (StringBeginsWith(folderName, NS_LITERAL_STRING("POP-"))) {
+ // cut off "POP-" prefix so we get a nice folder name
+ folderName.Cut(0, 4);
+ isAccountFolder = true;
+ }
+ else if (StringBeginsWith(folderName, NS_LITERAL_STRING("IMAP-"))) {
+ // cut off "IMAP-" prefix so we get a nice folder name
+ folderName.Cut(0, 5);
+ isAccountFolder = true;
+ }
+
+ if (isAccountFolder) {
+ IMPORT_LOG1("Found account: %s\n", NS_ConvertUTF16toUTF8(folderName).get());
+
+ // create a mailbox for this account, so we get a parent for "Inbox", "Sent Messages", etc.
+ nsCOMPtr<nsIImportMailboxDescriptor> desc;
+ nsresult rv = aImportService->CreateNewMailboxDescriptor(getter_AddRefs(desc));
+ desc->SetSize(1);
+ desc->SetDepth(mCurDepth);
+ desc->SetDisplayName(folderName.get());
+ desc->SetIdentifier(kAccountMailboxID);
+
+ nsCOMPtr<nsIFile> mailboxDescFile;
+ rv = desc->GetFile(getter_AddRefs(mailboxDescFile));
+ if (!mailboxDescFile)
+ continue;
+
+ mailboxDescFile->InitWithFile(currentEntry);
+
+ // add this mailbox descriptor to the list
+ aMailboxDescs->AppendElement(desc, false);
+
+ // now add all the children mailboxes
+ mCurDepth++;
+ FindMboxDirs(currentEntry, aMailboxDescs, aImportService);
+ mCurDepth--;
+ }
+ }
+ }
+}
+
+// adds the specified file as a mailboxdescriptor to the array
+nsresult nsAppleMailImportMail::AddMboxDir(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService)
+{
+ nsAutoString folderName;
+ aFolder->GetLeafName(folderName);
+
+ // cut off the suffix, if any, or prefix if this is an account folder.
+ if (StringEndsWith(folderName, NS_LITERAL_STRING(POP_MBOX_SUFFIX)))
+ folderName.SetLength(folderName.Length()-5);
+ else if (StringEndsWith(folderName, NS_LITERAL_STRING(IMAP_MBOX_SUFFIX)))
+ folderName.SetLength(folderName.Length()-9);
+ else if (StringBeginsWith(folderName, NS_LITERAL_STRING("POP-")))
+ folderName.Cut(4, folderName.Length());
+ else if (StringBeginsWith(folderName, NS_LITERAL_STRING("IMAP-")))
+ folderName.Cut(5, folderName.Length());
+
+ nsCOMPtr<nsIImportMailboxDescriptor> desc;
+ nsresult rv = aImportService->CreateNewMailboxDescriptor(getter_AddRefs(desc));
+ if (NS_SUCCEEDED(rv)) {
+ // find out number of messages in this .mbox
+ uint32_t numMessages = 0;
+ {
+ // move to the .mbox's Messages folder
+ nsCOMPtr<nsIFile> messagesFolder;
+ aFolder->Clone(getter_AddRefs(messagesFolder));
+ nsresult rv = messagesFolder->Append(NS_LITERAL_STRING("Messages"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // count the number of messages in this folder. it sucks that we have to iterate through the folder
+ // but XPCOM doesn't give us any way to just get the file count, unfortunately. :-(
+ nsCOMPtr<nsISimpleEnumerator> dirEnumerator;
+ messagesFolder->GetDirectoryEntries(getter_AddRefs(dirEnumerator));
+ if (dirEnumerator) {
+ bool hasMore = false;
+ while (NS_SUCCEEDED(dirEnumerator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> rawSupports;
+ dirEnumerator->GetNext(getter_AddRefs(rawSupports));
+ if (!rawSupports)
+ continue;
+
+ nsCOMPtr<nsIFile> file(do_QueryInterface(rawSupports));
+ if (file) {
+ bool isFile = false;
+ file->IsFile(&isFile);
+ if (isFile)
+ numMessages++;
+ }
+ }
+ }
+ }
+
+ desc->SetSize(numMessages);
+ desc->SetDisplayName(folderName.get());
+ desc->SetDepth(mCurDepth);
+
+ IMPORT_LOG3("Will import %s with approx %d messages, depth is %d", NS_ConvertUTF16toUTF8(folderName).get(), numMessages, mCurDepth);
+
+ // XXX: this is silly. there's no setter for the mailbox descriptor's file, so we need to get it, and then modify it.
+ nsCOMPtr<nsIFile> mailboxDescFile;
+ rv = desc->GetFile(getter_AddRefs(mailboxDescFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mailboxDescFile)
+ mailboxDescFile->InitWithFile(aFolder);
+
+ // add this mailbox descriptor to the list
+ aMailboxDescs->AppendElement(desc, false);
+ }
+
+ return NS_OK;
+}
+
+// Starts looking for .mbox dirs in the specified dir. The .mbox dirs contain messages and can be considered leafs in a tree of
+// nested mailboxes (subfolders).
+//
+// If a mailbox has sub-mailboxes, they are contained in a sibling folder with the same name without the ".mbox" part.
+// example:
+// MyParentMailbox.mbox/
+// MyParentMailbox/
+// MyChildMailbox.mbox/
+// MyOtherChildMailbox.mbox/
+//
+nsresult nsAppleMailImportMail::FindMboxDirs(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aMailboxDescs);
+ NS_ENSURE_ARG_POINTER(aImportService);
+
+ // make sure this is a directory.
+ bool isDir = false;
+ if (NS_FAILED(aFolder->IsDirectory(&isDir)) || !isDir)
+ return NS_ERROR_FAILURE;
+
+ // iterate through the folder contents
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ nsresult rv = aFolder->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ if (NS_FAILED(rv) || !directoryEnumerator)
+ return rv;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && hasMore) {
+
+ // get the next file entry
+ nsCOMPtr<nsIFile> currentEntry;
+ {
+ nsCOMPtr<nsISupports> rawSupports;
+ directoryEnumerator->GetNext(getter_AddRefs(rawSupports));
+ if (!rawSupports)
+ continue;
+ currentEntry = do_QueryInterface(rawSupports);
+ if (!currentEntry)
+ continue;
+ }
+
+ // we only care about directories...
+ if (NS_FAILED(currentEntry->IsDirectory(&isDir)) || !isDir)
+ continue;
+
+ // now find out if this is a .mbox dir
+ nsAutoString currentFolderName;
+ if (NS_SUCCEEDED(currentEntry->GetLeafName(currentFolderName)) &&
+ (StringEndsWith(currentFolderName, NS_LITERAL_STRING(POP_MBOX_SUFFIX)) ||
+ StringEndsWith(currentFolderName, NS_LITERAL_STRING(IMAP_MBOX_SUFFIX)))) {
+ IMPORT_LOG1("Adding .mbox dir: %s", NS_ConvertUTF16toUTF8(currentFolderName).get());
+
+ // add this .mbox
+ rv = AddMboxDir(currentEntry, aMailboxDescs, aImportService);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("Couldn't add .mbox for import: %s ... continuing anyway", NS_ConvertUTF16toUTF8(currentFolderName).get());
+ continue;
+ }
+
+ // see if this .mbox dir has any sub-mailboxes
+ nsAutoString siblingMailboxDirPath;
+ currentEntry->GetPath(siblingMailboxDirPath);
+
+ // cut off suffix
+ if (StringEndsWith(siblingMailboxDirPath, NS_LITERAL_STRING(IMAP_MBOX_SUFFIX)))
+ siblingMailboxDirPath.SetLength(siblingMailboxDirPath.Length()-9);
+ else if (StringEndsWith(siblingMailboxDirPath, NS_LITERAL_STRING(POP_MBOX_SUFFIX)))
+ siblingMailboxDirPath.SetLength(siblingMailboxDirPath.Length()-5);
+
+ IMPORT_LOG1("trying to locate a '%s'", NS_ConvertUTF16toUTF8(siblingMailboxDirPath).get());
+ nsCOMPtr<nsIFile> siblingMailboxDir(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ rv = siblingMailboxDir->InitWithPath(siblingMailboxDirPath);
+ bool reallyExists = false;
+ siblingMailboxDir->Exists(&reallyExists);
+
+ if (NS_SUCCEEDED(rv) && reallyExists) {
+ IMPORT_LOG1("Found what looks like an .mbox container: %s", NS_ConvertUTF16toUTF8(currentFolderName).get());
+
+ // traverse this folder for other .mboxes
+ mCurDepth++;
+ FindMboxDirs(siblingMailboxDir, aMailboxDescs, aImportService);
+ mCurDepth--;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppleMailImportMail::ImportMailbox(nsIImportMailboxDescriptor *aMailbox,
+ nsIMsgFolder *aDstFolder,
+ char16_t **aErrorLog,
+ char16_t **aSuccessLog, bool *aFatalError)
+{
+ nsAutoString errorLog, successLog;
+
+ // reset progress
+ mProgress = 0;
+
+ nsAutoString mailboxName;
+ aMailbox->GetDisplayName(getter_Copies(mailboxName));
+
+ nsCOMPtr<nsIFile> mboxFolder;
+ nsresult rv = aMailbox->GetFile(getter_AddRefs(mboxFolder));
+ if (NS_FAILED(rv) || !mboxFolder) {
+ ReportStatus(u"ApplemailImportMailboxConverterror", mailboxName, errorLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ // if we're an account mailbox, nothing do. if we're a real mbox
+ // then we've got some messages to import!
+ uint32_t mailboxIdentifier;
+ aMailbox->GetIdentifier(&mailboxIdentifier);
+
+ if (mailboxIdentifier != kAccountMailboxID) {
+ // move to the .mbox's Messages folder
+ nsCOMPtr<nsIFile> messagesFolder;
+ mboxFolder->Clone(getter_AddRefs(messagesFolder));
+ rv = messagesFolder->Append(NS_LITERAL_STRING("Messages"));
+ if (NS_FAILED(rv)) {
+ // even if there are no messages, it might still be a valid mailbox, or even
+ // a parent for other mailboxes.
+ //
+ // just indicate that we're done, using the same number that we used to estimate
+ // number of messages earlier.
+ uint32_t finalSize;
+ aMailbox->GetSize(&finalSize);
+ mProgress = finalSize;
+
+ // report that we successfully imported this mailbox
+ ReportStatus(u"ApplemailImportMailboxSuccess", mailboxName, successLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_OK;
+ }
+
+ // let's import the messages!
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ rv = messagesFolder->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ if (NS_FAILED(rv)) {
+ ReportStatus(u"ApplemailImportMailboxConvertError", mailboxName, errorLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ // prepare an outstream to the destination file
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = aDstFolder->GetMsgStore(getter_AddRefs(msgStore));
+ if (!msgStore || NS_FAILED(rv)) {
+ ReportStatus(u"ApplemailImportMailboxConverterror", mailboxName, errorLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ bool hasMore = false;
+ nsCOMPtr<nsIOutputStream> outStream;
+
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && hasMore) {
+ // get the next file entry
+ nsCOMPtr<nsIFile> currentEntry;
+ {
+ nsCOMPtr<nsISupports> rawSupports;
+ directoryEnumerator->GetNext(getter_AddRefs(rawSupports));
+ if (!rawSupports)
+ continue;
+ currentEntry = do_QueryInterface(rawSupports);
+ if (!currentEntry)
+ continue;
+ }
+
+ // make sure it's an .emlx file
+ bool isFile = false;
+ currentEntry->IsFile(&isFile);
+ if (!isFile)
+ continue;
+
+ nsAutoString leafName;
+ currentEntry->GetLeafName(leafName);
+ if (!StringEndsWith(leafName, NS_LITERAL_STRING(".emlx")))
+ continue;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ bool reusable;
+ rv = msgStore->GetNewMsgOutputStream(aDstFolder, getter_AddRefs(msgHdr),
+ &reusable,
+ getter_AddRefs(outStream));
+ if (NS_FAILED(rv))
+ break;
+
+ // add the data to the mbox stream
+ if (NS_SUCCEEDED(nsEmlxHelperUtils::AddEmlxMessageToStream(currentEntry, outStream))) {
+ mProgress++;
+ msgStore->FinishNewMessage(outStream, msgHdr);
+ }
+ else {
+ msgStore->DiscardNewMessage(outStream, msgHdr);
+ break;
+ }
+ if (!reusable)
+ outStream->Close();
+ }
+ if (outStream)
+ outStream->Close();
+ }
+ // just indicate that we're done, using the same number that we used to estimate
+ // number of messages earlier.
+ uint32_t finalSize;
+ aMailbox->GetSize(&finalSize);
+ mProgress = finalSize;
+
+ // report that we successfully imported this mailbox
+ ReportStatus(u"ApplemailImportMailboxSuccess", mailboxName, successLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+
+ return NS_OK;
+}
+
+void nsAppleMailImportMail::ReportStatus(const char16_t* aErrorName, nsString &aName,
+ nsAString &aStream)
+{
+ // get (and format, if needed) the error string from the bundle
+ nsAutoString outString;
+ const char16_t *fmt = { aName.get() };
+ nsresult rv = mBundle->FormatStringFromName(aErrorName, &fmt, 1, getter_Copies(outString));
+ // write it out the stream
+ if (NS_SUCCEEDED(rv)) {
+ aStream.Append(outString);
+ aStream.Append(char16_t('\n'));
+ }
+}
+
+void nsAppleMailImportMail::SetLogs(const nsAString &aSuccess, const nsAString &aError, char16_t **aOutSuccess, char16_t **aOutError)
+{
+ if (aOutError && !*aOutError)
+ *aOutError = ToNewUnicode(aError);
+ if (aOutSuccess && !*aOutSuccess)
+ *aOutSuccess = ToNewUnicode(aSuccess);
+}
+
+NS_IMETHODIMP nsAppleMailImportMail::GetImportProgress(uint32_t *aDoneSoFar)
+{
+ NS_ENSURE_ARG_POINTER(aDoneSoFar);
+ *aDoneSoFar = mProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAppleMailImportMail::TranslateFolderName(const nsAString &aFolderName, nsAString &aResult)
+{
+ aResult = aFolderName;
+ return NS_OK;
+}
diff --git a/mailnews/import/applemail/src/nsAppleMailImport.h b/mailnews/import/applemail/src/nsAppleMailImport.h
new file mode 100644
index 000000000..b906aecf5
--- /dev/null
+++ b/mailnews/import/applemail/src/nsAppleMailImport.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAppleMailImport_h___
+#define nsAppleMailImport_h___
+
+#include "mozilla/Logging.h"
+#include "nsIImportModule.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+#include "nsIImportMail.h"
+
+// logging facilities
+extern PRLogModuleInfo *APPLEMAILLOGMODULE;
+
+#define IMPORT_LOG0(x) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (x))
+#define IMPORT_LOG1(x, y) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (x, y))
+#define IMPORT_LOG2(x, y, z) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (x, y, z))
+#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d))
+
+#define NS_APPLEMAILIMPL_CID \
+{ 0x9117a1ea, 0xe012, 0x43b5, { 0xa0, 0x20, 0xcb, 0x8a, 0x66, 0xcc, 0x09, 0xe1 } }
+
+#define NS_APPLEMAILIMPORT_CID \
+{ 0x6d3f101c, 0x70ec, 0x4e04, { 0xb6, 0x8d, 0x99, 0x08, 0xd1, 0xae, 0xdd, 0xf3 } }
+
+#define NS_APPLEMAILIMPL_CONTRACTID "@mozilla.org/import/import-appleMailImpl;1"
+
+#define kAppleMailSupportsString "mail"
+
+class nsIImportService;
+class nsIMutableArray;
+
+class nsAppleMailImportModule : public nsIImportModule
+{
+ public:
+
+ nsAppleMailImportModule();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMPORTMODULE
+
+ private:
+ virtual ~nsAppleMailImportModule();
+
+ nsCOMPtr<nsIStringBundle> mBundle;
+};
+
+class nsAppleMailImportMail : public nsIImportMail
+{
+ public:
+
+ nsAppleMailImportMail();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMPORTMAIL
+
+ nsresult Initialize();
+
+ private:
+ virtual ~nsAppleMailImportMail();
+
+ void FindAccountMailDirs(nsIFile *aRoot, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService);
+ nsresult FindMboxDirs(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService);
+ nsresult AddMboxDir(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService);
+
+ // aInfoString is the format to a "foo %s" string. It may be NULL if the error string needs no such format.
+ void ReportStatus(const char16_t* aErrorName, nsString &aName, nsAString &aStream);
+ static void SetLogs(const nsAString& success, const nsAString& error, char16_t **aOutErrorLog, char16_t **aSuccessLog);
+
+ nsCOMPtr<nsIStringBundle> mBundle;
+ uint32_t mProgress;
+ uint16_t mCurDepth;
+};
+
+#endif /* nsAppleMailImport_h___ */
diff --git a/mailnews/import/applemail/src/nsEmlxHelperUtils.h b/mailnews/import/applemail/src/nsEmlxHelperUtils.h
new file mode 100644
index 000000000..728b725b6
--- /dev/null
+++ b/mailnews/import/applemail/src/nsEmlxHelperUtils.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsEmlxHelperUtils_h___
+#define nsEmlxHelperUtils_h___
+
+#include "nscore.h"
+#include "nsStringGlue.h"
+
+class nsIOutputStream;
+class nsIFile;
+
+class nsEmlxHelperUtils {
+ /* All emlx messages have a "flags" number in the metadata.
+ These are the masks to decode that, found via http://jwz.livejournal.com/505711.html */
+ enum EmlxMetadataMask {
+ kRead = 1 << 0, // read
+ // 1 << 1, // deleted
+ kAnswered = 1 << 2, // answered
+ // 1 << 3, // encrypted
+ kFlagged = 1 << 4, // flagged
+ // 1 << 5, // recent
+ // 1 << 6, // draft
+ // 1 << 7, // initial (no longer used)
+ kForwarded = 1 << 8, // forwarded
+ // 1 << 9, // redirected
+ // 3F << 10, // attachment count (6 bits)
+ // 7F << 16, // priority level (7 bits)
+ // 1 << 23, // signed
+ // 1 << 24, // is junk
+ // 1 << 25, // is not junk
+ // 1 << 26, // font size delta 7 (3 bits)
+ // 1 << 29, // junk mail level recorded
+ // 1 << 30, // highlight text in toc
+ // 1 << 31 // (unused)
+ };
+
+ // This method will scan the raw EMLX message buffer for "dangerous" so-called "From-lines" that we need to escape.
+ // If it needs to modify any lines, it will return a non-NULL aOutBuffer. If aOutBuffer is NULL, no modification needed
+ // to be made.
+ static nsresult ConvertToMboxRD(const char *aMessageBufferStart, const char *aMessageBufferEnd, nsCString &aOutBuffer);
+
+ // returns an int representing the X-Mozilla-Status flags set (e.g. "read", "flagged") converted from EMLX flags.
+ static nsresult ConvertToMozillaStatusFlags(const char *aXMLBufferStart, const char *aXMLBufferEnd, uint32_t *aMozillaStatusFlags);
+
+ public:
+
+ // add an .emlx message to the mbox output
+ static nsresult AddEmlxMessageToStream(nsIFile *aEmlxFile, nsIOutputStream *aOutoutStream);
+
+};
+
+#endif // nsEmlxHelperUtils_h___
diff --git a/mailnews/import/applemail/src/nsEmlxHelperUtils.mm b/mailnews/import/applemail/src/nsEmlxHelperUtils.mm
new file mode 100644
index 000000000..d2feb166e
--- /dev/null
+++ b/mailnews/import/applemail/src/nsEmlxHelperUtils.mm
@@ -0,0 +1,240 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsEmlxHelperUtils.h"
+#include "nsIFileStreams.h"
+#include "nsIBufferedStreams.h"
+#include "nsIOutputStream.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsObjCExceptions.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "msgCore.h"
+#include "nsTArray.h"
+#include "nsAppleMailImport.h"
+#include "prprf.h"
+#include "nsIFile.h"
+
+#import <Cocoa/Cocoa.h>
+
+
+nsresult nsEmlxHelperUtils::ConvertToMozillaStatusFlags(const char *aXMLBufferStart,
+ const char *aXMLBufferEnd,
+ uint32_t *aMozillaStatusFlags)
+{
+ // create a NSData wrapper around the buffer, so we can use the Cocoa call below
+ NSData *metadata =
+ [[[NSData alloc] initWithBytesNoCopy:(void *)aXMLBufferStart length:(aXMLBufferEnd-aXMLBufferStart) freeWhenDone:NO] autorelease];
+
+ // get the XML data as a dictionary
+ NSPropertyListFormat format;
+ id plist = [NSPropertyListSerialization propertyListWithData:metadata
+ options:NSPropertyListImmutable
+ format:&format
+ error:NULL];
+
+ if (!plist)
+ return NS_ERROR_FAILURE;
+
+ // find the <flags>...</flags> value and convert to int
+ const uint32_t emlxMessageFlags = [[(NSDictionary *)plist objectForKey:@"flags"] intValue];
+
+ if (emlxMessageFlags == 0)
+ return NS_ERROR_FAILURE;
+
+ if (emlxMessageFlags & nsEmlxHelperUtils::kRead)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Read;
+ if (emlxMessageFlags & nsEmlxHelperUtils::kForwarded)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Forwarded;
+ if (emlxMessageFlags & nsEmlxHelperUtils::kAnswered)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Replied;
+ if (emlxMessageFlags & nsEmlxHelperUtils::kFlagged)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Marked;
+
+ return NS_OK;
+}
+
+nsresult nsEmlxHelperUtils::ConvertToMboxRD(const char *aMessageBufferStart, const char *aMessageBufferEnd, nsCString &aOutBuffer)
+{
+ nsTArray<const char *> foundFromLines;
+
+ const char *cur = aMessageBufferStart;
+ while (cur < aMessageBufferEnd) {
+
+ const char *foundFromStr = strnstr(cur, "From ", aMessageBufferEnd-cur);
+
+ if (foundFromStr) {
+ // skip all prepending '>' chars
+ const char *fromLineStart = foundFromStr;
+ while (fromLineStart-- >= aMessageBufferStart) {
+ if (*fromLineStart == '\n' || fromLineStart == aMessageBufferStart) {
+ if (fromLineStart > aMessageBufferStart)
+ fromLineStart++;
+ foundFromLines.AppendElement(fromLineStart);
+ break;
+ }
+ else if (*fromLineStart != '>')
+ break;
+ }
+
+ // advance past the last found From string.
+ cur = foundFromStr + 5;
+
+ // look for more From lines.
+ continue;
+ }
+
+ break;
+ }
+
+ // go through foundFromLines
+ if (foundFromLines.Length()) {
+
+ const char *chunkStart = aMessageBufferStart;
+ for (unsigned i=0; i<foundFromLines.Length(); ++i) {
+ aOutBuffer.Append(chunkStart, (foundFromLines[i]-chunkStart));
+ aOutBuffer.Append(NS_LITERAL_CSTRING(">"));
+
+ chunkStart = foundFromLines[i];
+ }
+ aOutBuffer.Append(chunkStart, (aMessageBufferEnd - chunkStart));
+ }
+
+ return NS_OK;
+}
+
+nsresult nsEmlxHelperUtils::AddEmlxMessageToStream(nsIFile *aMessage, nsIOutputStream *aOut)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // needed to be sure autoreleased objects are released too, which they might not
+ // in a C++ environment where the main event loop has no autorelease pool (e.g on a XPCOM thread)
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsAutoCString path;
+ aMessage->GetNativePath(path);
+
+ NSData *data = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:path.get()]];
+ if (!data) {
+ [pool release];
+ return NS_ERROR_FAILURE;
+ }
+
+ char *startOfMessageData = NULL;
+ uint32_t actualBytesWritten = 0;
+
+ // The anatomy of an EMLX file:
+ //
+ // -------------------------------
+ // < A number describing how many bytes ahead there is message data >
+ // < Message data >
+ // < XML metadata for this message >
+ // -------------------------------
+
+ // read the first line of the emlx file, which is a number of how many bytes ahead the actual
+ // message data is.
+ uint64_t numberOfBytesToRead = strtol((char *)[data bytes], &startOfMessageData, 10);
+ if (numberOfBytesToRead <= 0 || !startOfMessageData) {
+ [pool release];
+ return NS_ERROR_FAILURE;
+ }
+
+ // skip whitespace
+ while (*startOfMessageData == ' ' ||
+ *startOfMessageData == '\n' ||
+ *startOfMessageData == '\r' ||
+ *startOfMessageData == '\t')
+ ++startOfMessageData;
+
+ NS_NAMED_LITERAL_CSTRING(kBogusFromLine, "From \n");
+ NS_NAMED_LITERAL_CSTRING(kEndOfMessage, "\n\n");
+
+ // write the bogus "From " line which is a magic separator in the mbox format
+ rv = aOut->Write(kBogusFromLine.get(), kBogusFromLine.Length(), &actualBytesWritten);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // now read the XML metadata, so we can extract info like which flags (read? replied? flagged? etc) this message has.
+ const char *startOfXMLMetadata = startOfMessageData + numberOfBytesToRead;
+ const char *endOfXMLMetadata = (char *)[data bytes] + [data length];
+
+ uint32_t x_mozilla_flags = 0;
+ ConvertToMozillaStatusFlags(startOfXMLMetadata, endOfXMLMetadata, &x_mozilla_flags);
+
+ // write the X-Mozilla-Status header according to which flags we've gathered above.
+ uint32_t dummyRv;
+ nsAutoCString buf(PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, x_mozilla_flags));
+ NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status header");
+ if (buf.IsEmpty()) {
+ [pool release];
+ return rv;
+ }
+
+ rv = aOut->Write(buf.get(), buf.Length(), &dummyRv);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // write out X-Mozilla-Keywords header as well to reserve some space for it
+ // in the mbox file.
+ rv = aOut->Write(X_MOZILLA_KEYWORDS, X_MOZILLA_KEYWORDS_LEN, &dummyRv);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // write out empty X-Mozilla_status2 header
+ buf.Adopt(PR_smprintf(X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, 0));
+ NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status2 header");
+ if (buf.IsEmpty()) {
+ [pool release];
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = aOut->Write(buf.get(), buf.Length(), &dummyRv);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // do any conversion needed for the mbox data to be valid mboxrd.
+ nsCString convertedData;
+ rv = ConvertToMboxRD(startOfMessageData, (startOfMessageData + numberOfBytesToRead), convertedData);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // write the actual message data.
+ if (convertedData.IsEmpty())
+ rv = aOut->Write(startOfMessageData, (uint32_t)numberOfBytesToRead, &actualBytesWritten);
+ else {
+ IMPORT_LOG1("Escaped From-lines in %s!", path.get());
+ rv = aOut->Write(convertedData.get(), convertedData.Length(), &actualBytesWritten);
+ }
+
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ NS_ASSERTION(actualBytesWritten == (convertedData.IsEmpty() ? numberOfBytesToRead : convertedData.Length()),
+ "Didn't write as many bytes as expected for .emlx file?");
+
+ // add newlines to denote the end of this message in the mbox
+ rv = aOut->Write(kEndOfMessage.get(), kEndOfMessage.Length(), &actualBytesWritten);
+
+ [pool release];
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/mailnews/import/becky/src/moz.build b/mailnews/import/becky/src/moz.build
new file mode 100644
index 000000000..ce7651aa8
--- /dev/null
+++ b/mailnews/import/becky/src/moz.build
@@ -0,0 +1,16 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ 'nsBeckyAddressBooks.cpp',
+ 'nsBeckyFilters.cpp',
+ 'nsBeckyImport.cpp',
+ 'nsBeckyMail.cpp',
+ 'nsBeckySettings.cpp',
+ 'nsBeckyStringBundle.cpp',
+ 'nsBeckyUtils.cpp',
+]
+
+FINAL_LIBRARY = 'import'
diff --git a/mailnews/import/becky/src/nsBeckyAddressBooks.cpp b/mailnews/import/becky/src/nsBeckyAddressBooks.cpp
new file mode 100644
index 000000000..86654ac0e
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyAddressBooks.cpp
@@ -0,0 +1,383 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsIMutableArray.h"
+#include "nsStringGlue.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbManager.h"
+#include "nsIImportService.h"
+#include "nsIImportABDescriptor.h"
+#include "nsMsgUtils.h"
+#include "nsIStringBundle.h"
+#include "nsVCardAddress.h"
+
+#include "nsBeckyAddressBooks.h"
+#include "nsBeckyStringBundle.h"
+#include "nsBeckyUtils.h"
+
+NS_IMPL_ISUPPORTS(nsBeckyAddressBooks, nsIImportAddressBooks)
+
+nsresult
+nsBeckyAddressBooks::Create(nsIImportAddressBooks **aImport)
+{
+ NS_ENSURE_ARG_POINTER(aImport);
+
+ *aImport = new nsBeckyAddressBooks();
+
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+nsBeckyAddressBooks::nsBeckyAddressBooks()
+: mReadBytes(0)
+{
+}
+
+nsBeckyAddressBooks::~nsBeckyAddressBooks()
+{
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetSupportsMultiple(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetAutoFind(char16_t **aDescription,
+ bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(aDescription);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *aDescription =
+ nsBeckyStringBundle::GetStringByName(u"BeckyImportDescription");
+ *_retval = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetNeedsFieldMap(nsIFile *aLocation, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = false;
+ return NS_OK;
+}
+
+nsresult
+nsBeckyAddressBooks::FindAddressBookDirectory(nsIFile **aAddressBookDirectory)
+{
+ nsCOMPtr<nsIFile> userDirectory;
+ nsresult rv = nsBeckyUtils::FindUserDirectory(getter_AddRefs(userDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = userDirectory->Append(NS_LITERAL_STRING("AddrBook"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = userDirectory->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isDirectory = false;
+ rv = userDirectory->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDirectory)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ userDirectory.forget(aAddressBookDirectory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetDefaultLocation(nsIFile **aLocation,
+ bool *aFound,
+ bool *aUserVerify)
+{
+ NS_ENSURE_ARG_POINTER(aFound);
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(aUserVerify);
+
+ *aLocation = nullptr;
+ *aFound = false;
+ *aUserVerify = true;
+
+ if (NS_SUCCEEDED(nsBeckyAddressBooks::FindAddressBookDirectory(aLocation))) {
+ *aFound = true;
+ *aUserVerify = false;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyAddressBooks::CreateAddressBookDescriptor(nsIImportABDescriptor **aDescriptor)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImportService> importService = do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return importService->CreateNewABDescriptor(aDescriptor);
+}
+
+bool
+nsBeckyAddressBooks::IsAddressBookFile(nsIFile *aFile)
+{
+ if (!aFile)
+ return false;
+
+ nsresult rv;
+ bool isFile = false;
+ rv = aFile->IsFile(&isFile);
+ if (NS_FAILED(rv) && !isFile)
+ return false;
+
+ nsAutoString name;
+ rv = aFile->GetLeafName(name);
+ return StringEndsWith(name, NS_LITERAL_STRING(".bab"));
+}
+
+bool
+nsBeckyAddressBooks::HasAddressBookFile(nsIFile *aDirectory)
+{
+ if (!aDirectory)
+ return false;
+
+ nsresult rv;
+ bool isDirectory = false;
+ rv = aDirectory->IsDirectory(&isDirectory);
+ if (NS_FAILED(rv) || !isDirectory)
+ return false;
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool more;
+ nsCOMPtr<nsISupports> entry;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ rv = entries->GetNext(getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (IsAddressBookFile(file))
+ return true;
+ }
+
+ return false;
+}
+
+uint32_t
+nsBeckyAddressBooks::CountAddressBookSize(nsIFile *aDirectory)
+{
+ if (!aDirectory)
+ return 0;
+
+ nsresult rv;
+ bool isDirectory = false;
+ rv = aDirectory->IsDirectory(&isDirectory);
+ if (NS_FAILED(rv) || !isDirectory)
+ return 0;
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ uint32_t total = 0;
+ bool more;
+ nsCOMPtr<nsISupports> entry;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ rv = entries->GetNext(getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ int64_t size;
+ file->GetFileSize(&size);
+ if (total + size > std::numeric_limits<uint32_t>::max())
+ return std::numeric_limits<uint32_t>::max();
+
+ total += static_cast<uint32_t>(size);
+ }
+
+ return total;
+}
+
+nsresult
+nsBeckyAddressBooks::AppendAddressBookDescriptor(nsIFile *aEntry,
+ nsIMutableArray *aCollected)
+{
+ NS_ENSURE_ARG_POINTER(aCollected);
+
+ if (!HasAddressBookFile(aEntry))
+ return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIImportABDescriptor> descriptor;
+ rv = CreateAddressBookDescriptor(getter_AddRefs(descriptor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t size = CountAddressBookSize(aEntry);
+ descriptor->SetSize(size);
+ descriptor->SetAbFile(aEntry);
+
+ nsAutoString name;
+ aEntry->GetLeafName(name);
+ descriptor->SetPreferredName(name);
+
+ return aCollected->AppendElement(descriptor, false);
+}
+
+nsresult
+nsBeckyAddressBooks::CollectAddressBooks(nsIFile *aTarget,
+ nsIMutableArray *aCollected)
+{
+ nsresult rv = AppendAddressBookDescriptor(aTarget, aCollected);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = aTarget->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ nsCOMPtr<nsISupports> entry;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ rv = entries->GetNext(getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory = false;
+ rv = file->IsDirectory(&isDirectory);
+ if (NS_SUCCEEDED(rv) && isDirectory)
+ rv = CollectAddressBooks(file, aCollected);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::FindAddressBooks(nsIFile *aLocation,
+ nsIArray **_retval)
+{
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory = false;
+ rv = aLocation->IsDirectory(&isDirectory);
+ if (NS_FAILED(rv) || !isDirectory)
+ return NS_ERROR_FAILURE;
+
+ rv = CollectAddressBooks(aLocation, array);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ array.forget(_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::InitFieldMap(nsIImportFieldMap *aFieldMap)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::ImportAddressBook(nsIImportABDescriptor *aSource,
+ nsIAddrDatabase *aDestination,
+ nsIImportFieldMap *aFieldMap,
+ nsISupports *aSupportService,
+ char16_t **aErrorLog,
+ char16_t **aSuccessLog,
+ bool *aFatalError)
+{
+ NS_ENSURE_ARG_POINTER(aSource);
+ NS_ENSURE_ARG_POINTER(aDestination);
+ NS_ENSURE_ARG_POINTER(aErrorLog);
+ NS_ENSURE_ARG_POINTER(aSuccessLog);
+ NS_ENSURE_ARG_POINTER(aFatalError);
+
+ mReadBytes = 0;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aSource->GetAbFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = file->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ nsCOMPtr<nsISupports> entry;
+ nsAutoString error;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ rv = entries->GetNext(getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!IsAddressBookFile(file))
+ continue;
+
+ bool aborted = false;
+ nsAutoString name;
+ aSource->GetPreferredName(name);
+ nsVCardAddress vcard;
+ rv = vcard.ImportAddresses(&aborted, name.get(), file, aDestination, error, &mReadBytes);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+
+ if (!error.IsEmpty())
+ *aErrorLog = ToNewUnicode(error);
+ else
+ *aSuccessLog = nsBeckyStringBundle::GetStringByName(u"BeckyImportAddressSuccess");
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetImportProgress(uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mReadBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::SetSampleLocation(nsIFile *aLocation)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetSampleData(int32_t aRecordNumber,
+ bool *aRecordExists,
+ char16_t **_retval)
+{
+ return NS_ERROR_FAILURE;
+}
+
diff --git a/mailnews/import/becky/src/nsBeckyAddressBooks.h b/mailnews/import/becky/src/nsBeckyAddressBooks.h
new file mode 100644
index 000000000..83eb4c895
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyAddressBooks.h
@@ -0,0 +1,35 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBeckyAddressBooks_h___
+#define nsBeckyAddressBooks_h___
+
+#include "nsIImportAddressBooks.h"
+
+class nsBeckyAddressBooks final : public nsIImportAddressBooks
+{
+public:
+ nsBeckyAddressBooks();
+ static nsresult Create(nsIImportAddressBooks **aImport);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTADDRESSBOOKS
+
+private:
+ virtual ~nsBeckyAddressBooks();
+
+ uint32_t mReadBytes;
+
+ nsresult CollectAddressBooks(nsIFile *aTarget, nsIMutableArray *aCollected);
+ nsresult FindAddressBookDirectory(nsIFile **aAddressBookDirectory);
+ nsresult AppendAddressBookDescriptor(nsIFile *aEntry,
+ nsIMutableArray *aCollected);
+ uint32_t CountAddressBookSize(nsIFile *aDirectory);
+ bool HasAddressBookFile(nsIFile *aDirectory);
+ bool IsAddressBookFile(nsIFile *aFile);
+ nsresult CreateAddressBookDescriptor(nsIImportABDescriptor **aDescriptor);
+};
+
+#endif /* nsBeckyAddressBooks_h___ */
diff --git a/mailnews/import/becky/src/nsBeckyFilters.cpp b/mailnews/import/becky/src/nsBeckyFilters.cpp
new file mode 100644
index 000000000..97b99fec2
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyFilters.cpp
@@ -0,0 +1,800 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsArrayUtils.h"
+#include "nsILineInputStream.h"
+#include "nsIStringBundle.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgFilterList.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgAccount.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgFolder.h"
+#include "nsCOMPtr.h"
+#include "nsMsgSearchCore.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgUtils.h"
+#include "msgCore.h"
+
+#include "nsBeckyFilters.h"
+#include "nsBeckyStringBundle.h"
+#include "nsBeckyUtils.h"
+
+NS_IMPL_ISUPPORTS(nsBeckyFilters, nsIImportFilters)
+
+nsresult
+nsBeckyFilters::Create(nsIImportFilters **aImport)
+{
+ NS_ENSURE_ARG_POINTER(aImport);
+
+ *aImport = new nsBeckyFilters();
+
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+nsBeckyFilters::nsBeckyFilters()
+: mLocation(nullptr),
+ mServer(nullptr),
+ mConvertedFile(nullptr)
+{
+}
+
+nsBeckyFilters::~nsBeckyFilters()
+{
+}
+
+nsresult
+nsBeckyFilters::GetDefaultFilterLocation(nsIFile **aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> filterDir;
+ rv = nsBeckyUtils::GetDefaultMailboxDirectory(getter_AddRefs(filterDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ filterDir.forget(aFile);
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::GetFilterFile(bool aIncoming, nsIFile *aLocation, nsIFile **aFile)
+{
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ // We assume the caller has already checked that aLocation is a directory,
+ // otherwise it would not make sense to call us.
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> filter;
+ aLocation->Clone(getter_AddRefs(filter));
+ if (aIncoming)
+ rv = filter->Append(NS_LITERAL_STRING("IFilter.def"));
+ else
+ rv = filter->Append(NS_LITERAL_STRING("OFilter.def"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = filter->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ filter.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyFilters::AutoLocate(char16_t **aDescription,
+ nsIFile **aLocation,
+ bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (aDescription) {
+ *aDescription =
+ nsBeckyStringBundle::GetStringByName(u"BeckyImportDescription");
+ }
+ *aLocation = nullptr;
+ *_retval = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> location;
+ rv = GetDefaultFilterLocation(getter_AddRefs(location));
+ if (NS_FAILED(rv))
+ location = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ else
+ *_retval = true;
+
+ location.forget(aLocation);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyFilters::SetLocation(nsIFile *aLocation)
+{
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ bool exists = false;
+ nsresult rv = aLocation->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ mLocation = aLocation;
+ return NS_OK;
+}
+
+static nsMsgSearchAttribValue
+ConvertSearchKeyToAttrib(const nsACString &aKey)
+{
+ if (aKey.EqualsLiteral("From") ||
+ aKey.EqualsLiteral("Sender") ||
+ aKey.EqualsLiteral("From, Sender, X-Sender")) {
+ return nsMsgSearchAttrib::Sender;
+ } else if (aKey.EqualsLiteral("Subject")) {
+ return nsMsgSearchAttrib::Subject;
+ } else if (aKey.EqualsLiteral("[body]")) {
+ return nsMsgSearchAttrib::Body;
+ } else if (aKey.EqualsLiteral("Date")) {
+ return nsMsgSearchAttrib::Date;
+ } else if (aKey.EqualsLiteral("To")) {
+ return nsMsgSearchAttrib::To;
+ } else if (aKey.EqualsLiteral("Cc")) {
+ return nsMsgSearchAttrib::CC;
+ } else if (aKey.EqualsLiteral("To, Cc, Bcc:")) {
+ return nsMsgSearchAttrib::ToOrCC;
+ }
+ return -1;
+}
+
+static nsMsgSearchOpValue
+ConvertSearchFlagsToOperator(const nsACString &aFlags)
+{
+ nsCString flags(aFlags);
+ int32_t lastTabPosition = flags.RFindChar('\t');
+ if ((lastTabPosition == -1) ||
+ ((int32_t)aFlags.Length() == lastTabPosition - 1)) {
+ return -1;
+ }
+
+ switch (aFlags.CharAt(0)) {
+ case 'X':
+ return nsMsgSearchOp::DoesntContain;
+ case 'O':
+ if (aFlags.FindChar('T', lastTabPosition + 1) >= 0)
+ return nsMsgSearchOp::BeginsWith;
+ return nsMsgSearchOp::Contains;
+ default:
+ return -1;
+ }
+}
+
+nsresult
+nsBeckyFilters::ParseRuleLine(const nsCString &aLine,
+ nsMsgSearchAttribValue *aSearchAttribute,
+ nsMsgSearchOpValue *aSearchOperator,
+ nsString &aSearchKeyword)
+{
+ int32_t firstColonPosition = aLine.FindChar(':');
+ if (firstColonPosition == -1 ||
+ (int32_t)aLine.Length() == firstColonPosition - 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t secondColonPosition = aLine.FindChar(':', firstColonPosition + 1);
+ if (secondColonPosition == -1 ||
+ (int32_t)aLine.Length() == secondColonPosition - 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t length = secondColonPosition - firstColonPosition - 1;
+ nsMsgSearchAttribValue searchAttribute;
+ searchAttribute = ConvertSearchKeyToAttrib(Substring(aLine, firstColonPosition + 1, length));
+ if (searchAttribute < 0)
+ return NS_ERROR_FAILURE;
+
+ int32_t tabPosition = aLine.FindChar('\t');
+ if (tabPosition == -1 ||
+ (int32_t)aLine.Length() == tabPosition - 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsMsgSearchOpValue searchOperator;
+ searchOperator = ConvertSearchFlagsToOperator(Substring(aLine, tabPosition + 1));
+ if (searchOperator < 0)
+ return NS_ERROR_FAILURE;
+
+ *aSearchOperator = searchOperator;
+ *aSearchAttribute = searchAttribute;
+ length = tabPosition - secondColonPosition - 1;
+ CopyUTF8toUTF16(Substring(aLine, secondColonPosition + 1, length), aSearchKeyword);
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::SetSearchTerm(const nsCString &aLine, nsIMsgFilter *aFilter)
+{
+ NS_ENSURE_ARG_POINTER(aFilter);
+
+ nsresult rv;
+ nsMsgSearchAttribValue searchAttribute = -1;
+ nsMsgSearchOpValue searchOperator = -1;
+ nsAutoString searchKeyword;
+ rv = ParseRuleLine(aLine, &searchAttribute, &searchOperator, searchKeyword);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgSearchTerm> term;
+ rv = aFilter->CreateTerm(getter_AddRefs(term));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = term->SetAttrib(searchAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = term->SetOp(searchOperator);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgSearchValue> value;
+ rv = term->GetValue(getter_AddRefs(value));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = value->SetAttrib(searchAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = value->SetStr(searchKeyword);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = term->SetValue(value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = term->SetBooleanAnd(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!searchKeyword.IsEmpty())
+ rv = aFilter->SetFilterName(searchKeyword);
+ else
+ rv = aFilter->SetFilterName(NS_LITERAL_STRING("No name"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aFilter->AppendTerm(term);
+}
+
+nsresult
+nsBeckyFilters::CreateRuleAction(nsIMsgFilter *aFilter,
+ nsMsgRuleActionType actionType,
+ nsIMsgRuleAction **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = aFilter->CreateAction(getter_AddRefs(action));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = action->SetType(actionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ action.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::GetActionTarget(const nsCString &aLine,
+ nsCString &aTarget)
+{
+ int32_t firstColonPosition = aLine.FindChar(':');
+ if (firstColonPosition < -1 ||
+ aLine.Length() == static_cast<uint32_t>(firstColonPosition)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aTarget.Assign(Substring(aLine, firstColonPosition + 1));
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::GetResendTarget(const nsCString &aLine,
+ nsCString &aTemplate,
+ nsCString &aTargetAddress)
+{
+ nsresult rv;
+ nsAutoCString target;
+ rv = GetActionTarget(aLine, target);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t asteriskPosition = target.FindChar('*');
+ if (asteriskPosition < 0) {
+ aTemplate.Assign(target);
+ return NS_OK;
+ }
+
+ if (target.Length() == static_cast<uint32_t>(asteriskPosition))
+ return NS_ERROR_FAILURE;
+
+ aTemplate.Assign(StringHead(target, asteriskPosition - 1));
+ aTargetAddress.Assign(Substring(target, asteriskPosition + 1));
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::CreateResendAction(const nsCString &aLine,
+ nsIMsgFilter *aFilter,
+ const nsMsgRuleActionType &aActionType,
+ nsIMsgRuleAction **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = CreateRuleAction(aFilter, aActionType, getter_AddRefs(action));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString templateString;
+ nsAutoCString targetAddress;
+ rv = GetResendTarget(aLine, templateString, targetAddress);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aActionType == nsMsgFilterAction::Forward)
+ rv = action->SetStrValue(targetAddress);
+ else
+ rv = action->SetStrValue(templateString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ action.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::GetFolderNameFromTarget(const nsCString &aTarget, nsAString &aName)
+{
+ int32_t backslashPosition = aTarget.RFindChar('\\');
+ if (backslashPosition > 0) {
+ NS_ConvertUTF8toUTF16 utf16String(Substring(aTarget, backslashPosition + 1));
+ nsBeckyUtils::TranslateFolderName(utf16String, aName);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::GetDistributeTarget(const nsCString &aLine,
+ nsCString &aTargetFolder)
+{
+ nsresult rv;
+ nsAutoCString target;
+ rv = GetActionTarget(aLine, target);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ target.Trim("\\", false, true);
+ nsAutoString folderName;
+ rv = GetFolderNameFromTarget(target, folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgFolder> folder;
+ rv = GetMessageFolder(folderName, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!folder) {
+ rv = mServer->GetRootMsgFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return folder->GetURI(aTargetFolder);
+}
+
+nsresult
+nsBeckyFilters::CreateDistributeAction(const nsCString &aLine,
+ nsIMsgFilter *aFilter,
+ const nsMsgRuleActionType &aActionType,
+ nsIMsgRuleAction **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = CreateRuleAction(aFilter, aActionType, getter_AddRefs(action));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString targetFolder;
+ rv = GetDistributeTarget(aLine, targetFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = action->SetTargetFolderUri(targetFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ action.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::CreateLeaveOrDeleteAction(const nsCString &aLine,
+ nsIMsgFilter *aFilter,
+ nsIMsgRuleAction **_retval)
+{
+ nsresult rv;
+ nsMsgRuleActionType actionType;
+ if (aLine.CharAt(3) == '0') {
+ actionType = nsMsgFilterAction::LeaveOnPop3Server;
+ } else if (aLine.CharAt(3) == '1') {
+ if (aLine.CharAt(5) == '1')
+ actionType = nsMsgFilterAction::Delete;
+ else
+ actionType = nsMsgFilterAction::DeleteFromPop3Server;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = CreateRuleAction(aFilter, actionType, getter_AddRefs(action));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ action.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::SetRuleAction(const nsCString &aLine, nsIMsgFilter *aFilter)
+{
+ if (!aFilter || aLine.Length() < 4)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgRuleAction> action;
+ switch (aLine.CharAt(1)) {
+ case 'R': // Reply
+ rv = CreateResendAction(aLine,
+ aFilter,
+ nsMsgFilterAction::Reply,
+ getter_AddRefs(action));
+ break;
+ case 'F': // Forward
+ rv = CreateResendAction(aLine,
+ aFilter,
+ nsMsgFilterAction::Forward,
+ getter_AddRefs(action));
+ break;
+ case 'L': // Leave or delete
+ rv = CreateLeaveOrDeleteAction(aLine, aFilter, getter_AddRefs(action));
+ break;
+ case 'Y': // Copy
+ rv = CreateDistributeAction(aLine,
+ aFilter,
+ nsMsgFilterAction::CopyToFolder,
+ getter_AddRefs(action));
+ break;
+ case 'M': // Move
+ rv = CreateDistributeAction(aLine,
+ aFilter,
+ nsMsgFilterAction::MoveToFolder,
+ getter_AddRefs(action));
+ break;
+ case 'G': // Set flag
+ if (aLine.CharAt(3) == 'R') // Read
+ rv = CreateRuleAction(aFilter, nsMsgFilterAction::MarkRead, getter_AddRefs(action));
+ break;
+ default:
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (action) {
+ rv = aFilter->AppendAction(action);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::CreateFilter(bool aIncoming, nsIMsgFilter **_retval)
+{
+ NS_ENSURE_STATE(mServer);
+
+ nsCOMPtr <nsIMsgFilterList> filterList;
+ nsresult rv = mServer->GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFilter> filter;
+ rv = filterList->CreateFilter(EmptyString(), getter_AddRefs(filter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aIncoming)
+ filter->SetFilterType(nsMsgFilterType::InboxRule | nsMsgFilterType::Manual);
+ else
+ filter->SetFilterType(nsMsgFilterType::PostOutgoing | nsMsgFilterType::Manual);
+
+ filter->SetEnabled(true);
+ filter.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::AppendFilter(nsIMsgFilter *aFilter)
+{
+ NS_ENSURE_STATE(mServer);
+
+ nsCOMPtr <nsIMsgFilterList> filterList;
+ nsresult rv = mServer->GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ rv = filterList->GetFilterCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return filterList->InsertFilterAt(count, aFilter);
+}
+
+nsresult
+nsBeckyFilters::ParseFilterFile(nsIFile *aFile, bool aIncoming)
+{
+ nsresult rv;
+ nsCOMPtr<nsILineInputStream> lineStream;
+ rv = nsBeckyUtils::CreateLineInputStream(aFile, getter_AddRefs(lineStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsAutoCString line;
+
+ nsCOMPtr<nsIMsgFilter> filter;
+ while (NS_SUCCEEDED(rv) && more) {
+ rv = lineStream->ReadLine(line, &more);
+
+ switch (line.CharAt(0)) {
+ case ':':
+ if (line.EqualsLiteral(":Begin \"\"")) {
+ CreateFilter(aIncoming, getter_AddRefs(filter));
+ } else if (line.EqualsLiteral(":End \"\"")) {
+ if (filter)
+ AppendFilter(filter);
+ filter = nullptr;
+ }
+ break;
+ case '!':
+ SetRuleAction(line, filter);
+ break;
+ case '@':
+ SetSearchTerm(line, filter);
+ break;
+ case '$': // $X: disabled
+ if (StringBeginsWith(line, NS_LITERAL_CSTRING("$X")) && filter) {
+ filter->SetEnabled(false);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyFilters::Import(char16_t **aError,
+ bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(aError);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // If mLocation is null, set it to the default filter directory.
+ // If mLocation is a file, we import it as incoming folder.
+ // If mLocation is a directory, we try to import incoming and outgoing folders
+ // from it (in default files).
+
+ *_retval = false;
+ nsresult rv;
+ nsCOMPtr<nsIFile> filterFile;
+
+ bool haveFile = false;
+
+ if (!mLocation) {
+ bool retval = false;
+ rv = AutoLocate(nullptr, getter_AddRefs(mLocation), &retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!retval)
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ // What type of location do we have?
+ bool isDirectory = false;
+ rv = mLocation->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isDirectory) {
+ haveFile = false;
+ } else {
+ bool isFile = false;
+ rv = mLocation->IsFile(&isFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isFile) {
+ haveFile = true;
+ mLocation->Clone(getter_AddRefs(filterFile));
+ } else {
+ // mLocation is neither file nor directory.
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ bool haveIncoming = true;
+ if (haveFile) {
+ // If the passed filename equals OFilter.def, import as outgoing filters.
+ // Everything else is considered incoming.
+ nsAutoString fileName;
+ rv = mLocation->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (fileName.EqualsLiteral("OFilter.def"))
+ haveIncoming = false;
+ }
+
+ // Try importing from the passed in file or the default incoming filters file.
+ if ((haveFile && haveIncoming) || (!haveFile &&
+ NS_SUCCEEDED(GetFilterFile(true, mLocation, getter_AddRefs(filterFile)))))
+ {
+ rv = CollectServers();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsBeckyUtils::ConvertToUTF8File(filterFile, getter_AddRefs(mConvertedFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ParseFilterFile(mConvertedFile, true);
+ if (NS_SUCCEEDED(rv))
+ *_retval = true;
+
+ (void)RemoveConvertedFile();
+ }
+
+ // If we didn't have a file passed (but a directory), try finding also outgoing filters.
+ if ((haveFile && !haveIncoming) || (!haveFile &&
+ NS_SUCCEEDED(GetFilterFile(false, mLocation, getter_AddRefs(filterFile)))))
+ {
+ rv = CollectServers();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsBeckyUtils::ConvertToUTF8File(filterFile, getter_AddRefs(mConvertedFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ParseFilterFile(mConvertedFile, false);
+ if (NS_SUCCEEDED(rv))
+ *_retval = true;
+
+ (void)RemoveConvertedFile();
+ }
+
+ return rv;
+}
+
+nsresult
+nsBeckyFilters::FindMessageFolder(const nsAString &aName,
+ nsIMsgFolder *aParentFolder,
+ nsIMsgFolder **_retval)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> found;
+ rv = aParentFolder->GetChildNamed(aName, getter_AddRefs(found));
+ if (found) {
+ found.forget(_retval);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> children;
+ rv = aParentFolder->GetSubFolders(getter_AddRefs(children));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ nsCOMPtr<nsISupports> entry;
+ while (NS_SUCCEEDED(children->HasMoreElements(&more)) && more) {
+ rv = children->GetNext(getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> child = do_QueryInterface(entry, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = FindMessageFolder(aName, child, getter_AddRefs(found));
+ if (found) {
+ found.forget(_retval);
+ return NS_OK;
+ }
+ }
+
+ return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+}
+
+nsresult
+nsBeckyFilters::FindMessageFolderInServer(const nsAString &aName,
+ nsIMsgIncomingServer *aServer,
+ nsIMsgFolder **_retval)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ rv = aServer->GetRootMsgFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return FindMessageFolder(aName, rootFolder, _retval);
+}
+
+nsresult
+nsBeckyFilters::GetMessageFolder(const nsAString &aName,
+ nsIMsgFolder **_retval)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager;
+ accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIArray> accounts;
+ rv = accountManager->GetAccounts(getter_AddRefs(accounts));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t accountCount;
+ rv = accounts->GetLength(&accountCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> found;
+ for (uint32_t i = 0; i < accountCount; i++) {
+ nsCOMPtr<nsIMsgAccount> account(do_QueryElementAt(accounts, i));
+ if (!account)
+ continue;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ account->GetIncomingServer(getter_AddRefs(server));
+ if (!server)
+ continue;
+ FindMessageFolderInServer(aName, server, getter_AddRefs(found));
+ if (found)
+ break;
+ }
+
+ if (!found) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ FindMessageFolderInServer(aName, server, getter_AddRefs(found));
+ }
+
+ if (!found)
+ return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ found.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::CollectServers()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager;
+ accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> defaultAccount;
+ rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (defaultAccount)
+ return defaultAccount->GetIncomingServer(getter_AddRefs(mServer));
+
+ // We can also import filters into the Local Folders account.
+ rv = accountManager->GetLocalFoldersServer(getter_AddRefs(mServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mServer)
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyFilters::RemoveConvertedFile()
+{
+ nsresult rv = NS_OK;
+ if (mConvertedFile) {
+ bool exists = false;
+ mConvertedFile->Exists(&exists);
+ if (exists) {
+ rv = mConvertedFile->Remove(false);
+ if (NS_SUCCEEDED(rv))
+ mConvertedFile = nullptr;
+ }
+ }
+ return rv;
+}
+
diff --git a/mailnews/import/becky/src/nsBeckyFilters.h b/mailnews/import/becky/src/nsBeckyFilters.h
new file mode 100644
index 000000000..20dd6d5ee
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyFilters.h
@@ -0,0 +1,77 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBeckyFilters_h___
+#define nsBeckyFilters_h___
+
+#include "nsIImportFilters.h"
+#include "nsIFile.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsMsgFilterCore.h"
+
+class nsIMsgFilter;
+class nsIMsgRuleAction;
+class nsCString;
+
+class nsBeckyFilters final : public nsIImportFilters
+{
+public:
+ nsBeckyFilters();
+ static nsresult Create(nsIImportFilters **aImport);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTFILTERS
+
+private:
+ virtual ~nsBeckyFilters();
+
+ nsCOMPtr<nsIFile> mLocation;
+ nsCOMPtr<nsIMsgIncomingServer> mServer;
+ nsCOMPtr<nsIFile> mConvertedFile;
+
+ nsresult GetDefaultFilterLocation(nsIFile **aFile);
+ nsresult GetFilterFile(bool aIncoming, nsIFile *aLocation, nsIFile **aFile);
+ nsresult ParseFilterFile(nsIFile *aFile, bool aIncoming);
+ nsresult ParseRuleLine(const nsCString &aLine,
+ nsMsgSearchAttribValue *aSearchAttribute,
+ nsMsgSearchOpValue *aSearchOperator,
+ nsString &aSearchKeyword);
+ nsresult CollectServers();
+ nsresult FindMessageFolder(const nsAString& aName,
+ nsIMsgFolder *aParantFolder,
+ nsIMsgFolder **_retval);
+ nsresult FindMessageFolderInServer(const nsAString& aName,
+ nsIMsgIncomingServer *aServer,
+ nsIMsgFolder **_retval);
+ nsresult GetMessageFolder(const nsAString& aName, nsIMsgFolder **_retval);
+ nsresult GetActionTarget(const nsCString &aLine, nsCString &aTarget);
+ nsresult GetFolderNameFromTarget(const nsCString &aTarget, nsAString &aName);
+ nsresult GetDistributeTarget(const nsCString &aLine,
+ nsCString &aTargetFolder);
+ nsresult GetResendTarget(const nsCString &aLine,
+ nsCString &aTemplate,
+ nsCString &aTargetAddress);
+ nsresult CreateRuleAction(nsIMsgFilter *aFilter,
+ nsMsgRuleActionType actionType,
+ nsIMsgRuleAction **_retval);
+ nsresult CreateDistributeAction(const nsCString &aLine,
+ nsIMsgFilter *aFilter,
+ const nsMsgRuleActionType &aActionType,
+ nsIMsgRuleAction **_retval);
+ nsresult CreateLeaveOrDeleteAction(const nsCString &aLine,
+ nsIMsgFilter *aFilter,
+ nsIMsgRuleAction **_retval);
+ nsresult CreateResendAction(const nsCString &aLine,
+ nsIMsgFilter *aFilter,
+ const nsMsgRuleActionType &aActionType,
+ nsIMsgRuleAction **_retval);
+ nsresult CreateFilter(bool aIncoming, nsIMsgFilter **_retval);
+ nsresult AppendFilter(nsIMsgFilter *aFilter);
+ nsresult SetRuleAction(const nsCString &aLine, nsIMsgFilter *aFilter);
+ nsresult SetSearchTerm(const nsCString &aLine, nsIMsgFilter *aFilter);
+ nsresult RemoveConvertedFile();
+};
+
+#endif /* nsBeckyFilters_h___ */
diff --git a/mailnews/import/becky/src/nsBeckyImport.cpp b/mailnews/import/becky/src/nsBeckyImport.cpp
new file mode 100644
index 000000000..d4820528a
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyImport.cpp
@@ -0,0 +1,168 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "nsIServiceManager.h"
+#include "nsIImportService.h"
+#include "nsIComponentManager.h"
+#include "nsIMemory.h"
+#include "nsIImportMail.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIImportGeneric.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIImportSettings.h"
+#include "nsIImportFilters.h"
+#include "nsIImportFieldMap.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIOutputStream.h"
+#include "nsIAddrDatabase.h"
+#include "nsTextFormatter.h"
+#include "nsIStringBundle.h"
+#include "nsUnicharUtils.h"
+#include "nsIMsgTagService.h"
+#include "nsMsgBaseCID.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+
+#include "nsBeckyImport.h"
+#include "nsBeckyMail.h"
+#include "nsBeckyAddressBooks.h"
+#include "nsBeckySettings.h"
+#include "nsBeckyFilters.h"
+#include "nsBeckyStringBundle.h"
+
+nsBeckyImport::nsBeckyImport()
+{
+}
+
+nsBeckyImport::~nsBeckyImport()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsBeckyImport, nsIImportModule)
+
+NS_IMETHODIMP
+nsBeckyImport::GetName(char16_t **aName)
+{
+ NS_ENSURE_ARG_POINTER(aName);
+ *aName =
+ nsBeckyStringBundle::GetStringByName(u"BeckyImportName");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyImport::GetDescription(char16_t **aDescription)
+{
+ NS_ENSURE_ARG_POINTER(aDescription);
+ *aDescription =
+ nsBeckyStringBundle::GetStringByName(u"BeckyImportDescription");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyImport::GetSupports(char **aSupports)
+{
+ NS_ENSURE_ARG_POINTER(aSupports);
+ *aSupports = strdup(kBeckySupportsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyImport::GetSupportsUpgrade(bool *aUpgrade)
+{
+ NS_ENSURE_ARG_POINTER(aUpgrade);
+ *aUpgrade = true;
+ return NS_OK;
+}
+
+nsresult
+nsBeckyImport::GetMailImportInterface(nsISupports **aInterface)
+{
+ nsCOMPtr<nsIImportMail> importer;
+ nsresult rv = nsBeckyMail::Create(getter_AddRefs(importer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImportService> importService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImportGeneric> generic;
+ rv = importService->CreateNewGenericMail(getter_AddRefs(generic));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ generic->SetData("mailInterface", importer);
+
+ nsString name;
+ name.Adopt(nsBeckyStringBundle::GetStringByName(u"BeckyImportName"));
+
+ nsCOMPtr<nsISupportsString> nameString(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nameString->SetData(name);
+ generic->SetData("name", nameString);
+
+ return CallQueryInterface(generic, aInterface);
+}
+
+nsresult
+nsBeckyImport::GetAddressBookImportInterface(nsISupports **aInterface)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImportAddressBooks> importer;
+ rv = nsBeckyAddressBooks::Create(getter_AddRefs(importer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImportService> importService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImportGeneric> generic;
+ rv = importService->CreateNewGenericAddressBooks(getter_AddRefs(generic));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ generic->SetData("addressInterface", importer);
+ return CallQueryInterface(generic, aInterface);
+}
+
+nsresult
+nsBeckyImport::GetSettingsImportInterface(nsISupports **aInterface)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImportSettings> importer;
+ rv = nsBeckySettings::Create(getter_AddRefs(importer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(importer, aInterface);
+}
+
+nsresult
+nsBeckyImport::GetFiltersImportInterface(nsISupports **aInterface)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImportFilters> importer;
+ rv = nsBeckyFilters::Create(getter_AddRefs(importer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(importer, aInterface);
+}
+
+NS_IMETHODIMP
+nsBeckyImport::GetImportInterface(const char *aImportType, nsISupports **aInterface)
+{
+ NS_ENSURE_ARG_POINTER(aImportType);
+ NS_ENSURE_ARG_POINTER(aInterface);
+
+ *aInterface = nullptr;
+ if (!strcmp(aImportType, "mail"))
+ return GetMailImportInterface(aInterface);
+ if (!strcmp(aImportType, "addressbook"))
+ return GetAddressBookImportInterface(aInterface);
+ if (!strcmp(aImportType, "settings"))
+ return GetSettingsImportInterface(aInterface);
+ if (!strcmp(aImportType, "filters"))
+ return GetFiltersImportInterface(aInterface);
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
diff --git a/mailnews/import/becky/src/nsBeckyImport.h b/mailnews/import/becky/src/nsBeckyImport.h
new file mode 100644
index 000000000..60d81c18c
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyImport.h
@@ -0,0 +1,36 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBeckyImport_h___
+#define nsBeckyImport_h___
+
+#include "nsIImportModule.h"
+
+#define NS_BECKYIMPORT_CID \
+{ \
+ 0x7952a6cf, 0x2442,0x4c04, \
+ {0x9f, 0x02, 0x15, 0x0b, 0x15, 0xa0, 0xa8, 0x41}}
+
+#define kBeckySupportsString NS_IMPORT_MAIL_STR "," NS_IMPORT_ADDRESS_STR "," NS_IMPORT_SETTINGS_STR "," NS_IMPORT_FILTERS_STR
+
+class nsBeckyImport final : public nsIImportModule
+{
+public:
+ nsBeckyImport();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTMODULE
+
+private:
+ virtual ~nsBeckyImport();
+
+ nsresult GetMailImportInterface(nsISupports **aInterface);
+ nsresult GetAddressBookImportInterface(nsISupports **aInterface);
+ nsresult GetSettingsImportInterface(nsISupports **aInterface);
+ nsresult GetFiltersImportInterface(nsISupports **aInterface);
+
+};
+
+#endif /* nsBeckyImport_h___ */
diff --git a/mailnews/import/becky/src/nsBeckyMail.cpp b/mailnews/import/becky/src/nsBeckyMail.cpp
new file mode 100644
index 000000000..9c837d190
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyMail.cpp
@@ -0,0 +1,641 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsILineInputStream.h"
+#include "nsNetUtil.h"
+#include "nsIArray.h"
+#include "nsIImportService.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIMutableArray.h"
+#include "nsMsgUtils.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMsgMessageFlags.h"
+#include "nsTArray.h"
+#include "nspr.h"
+#include "nsIStringBundle.h"
+#include "nsThreadUtils.h"
+
+#include "nsBeckyMail.h"
+#include "nsBeckyUtils.h"
+#include "nsBeckyStringBundle.h"
+
+#define FROM_LINE "From - Mon Jan 1 00:00:00 1965" MSG_LINEBREAK
+#define X_BECKY_STATUS_HEADER "X-Becky-Status"
+#define X_BECKY_INCLUDE_HEADER "X-Becky-Include"
+
+enum {
+ BECKY_STATUS_READ = 1 << 0,
+ BECKY_STATUS_FORWARDED = 1 << 1,
+ BECKY_STATUS_REPLIED = 1 << 2
+};
+
+NS_IMPL_ISUPPORTS(nsBeckyMail, nsIImportMail)
+
+nsresult
+nsBeckyMail::Create(nsIImportMail **aImport)
+{
+ NS_ENSURE_ARG_POINTER(aImport);
+
+ *aImport = new nsBeckyMail();
+
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+nsBeckyMail::nsBeckyMail()
+: mReadBytes(0)
+{
+}
+
+nsBeckyMail::~nsBeckyMail()
+{
+}
+
+NS_IMETHODIMP
+nsBeckyMail::GetDefaultLocation(nsIFile **aLocation,
+ bool *aFound,
+ bool *aUserVerify)
+{
+ NS_ENSURE_ARG_POINTER(aFound);
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(aUserVerify);
+
+ *aLocation = nullptr;
+ *aUserVerify = true;
+ *aFound = false;
+ if (NS_SUCCEEDED(nsBeckyUtils::GetDefaultMailboxDirectory(aLocation)))
+ *aFound = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyMail::CreateMailboxDescriptor(nsIImportMailboxDescriptor **aDescriptor)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImportService> importService;
+ importService = do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return importService->CreateNewMailboxDescriptor(aDescriptor);
+}
+
+nsresult
+nsBeckyMail::GetMailboxName(nsIFile *aMailbox, nsAString &aName)
+{
+ nsCOMPtr<nsIFile> iniFile;
+ nsBeckyUtils::GetMailboxINIFile(aMailbox, getter_AddRefs(iniFile));
+ if (iniFile) {
+ nsCOMPtr<nsIFile> convertedFile;
+ nsBeckyUtils::ConvertToUTF8File(iniFile, getter_AddRefs(convertedFile));
+ if (convertedFile) {
+ nsAutoCString utf8Name;
+ nsBeckyUtils::GetMailboxNameFromINIFile(convertedFile, utf8Name);
+ convertedFile->Remove(false);
+ CopyUTF8toUTF16(utf8Name, aName);
+ }
+ }
+
+ if (aName.IsEmpty()) {
+ nsAutoString name;
+ aMailbox->GetLeafName(name);
+ name.Trim("!", true, false);
+ aName.Assign(name);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyMail::AppendMailboxDescriptor(nsIFile *aEntry,
+ const nsString &aName,
+ uint32_t aDepth,
+ nsIMutableArray *aCollected)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImportMailboxDescriptor> descriptor;
+ rv = CreateMailboxDescriptor(getter_AddRefs(descriptor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t size;
+ rv = aEntry->GetFileSize(&size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = descriptor->SetSize(size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = descriptor->SetDisplayName(aName.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> mailboxFile;
+ rv = descriptor->GetFile(getter_AddRefs(mailboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ descriptor->SetDepth(aDepth);
+
+ mailboxFile->InitWithFile(aEntry);
+ aCollected->AppendElement(descriptor, false);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyMail::CollectMailboxesInFolderListFile(nsIFile *aListFile,
+ uint32_t aDepth,
+ nsIMutableArray *aCollected)
+{
+ nsresult rv;
+ nsCOMPtr<nsILineInputStream> lineStream;
+ rv = nsBeckyUtils::CreateLineInputStream(aListFile,
+ getter_AddRefs(lineStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> parent;
+ rv = aListFile->GetParent(getter_AddRefs(parent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsAutoCString folderName;
+ bool isEmpty = true;
+ while (more && NS_SUCCEEDED(rv)) {
+ rv = lineStream->ReadLine(folderName, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (folderName.IsEmpty())
+ continue;
+
+ nsCOMPtr<nsIFile> folder;
+ rv = parent->Clone(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = folder->AppendNative(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ isEmpty = false;
+ rv = CollectMailboxesInDirectory(folder, aDepth + 1, aCollected);
+ }
+
+ return isEmpty ? NS_ERROR_FILE_NOT_FOUND : NS_OK;
+}
+
+nsresult
+nsBeckyMail::CollectMailboxesInDirectory(nsIFile *aDirectory,
+ uint32_t aDepth,
+ nsIMutableArray *aCollected)
+{
+ nsAutoString mailboxName;
+ nsresult rv = GetMailboxName(aDirectory, mailboxName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDepth != 0)
+ AppendMailboxDescriptor(aDirectory, mailboxName, aDepth, aCollected);
+
+ nsCOMPtr<nsIFile> folderListFile;
+ rv = nsBeckyUtils::GetFolderListFile(aDirectory, getter_AddRefs(folderListFile));
+ bool folderListExists = false;
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = CollectMailboxesInFolderListFile(folderListFile, aDepth, aCollected);
+ folderListExists = true;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> entry;
+ rv = entries->GetNext(getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString name;
+ rv = file->GetLeafName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (StringEndsWith(name, NS_LITERAL_STRING(".bmf"))) {
+ AppendMailboxDescriptor(file, mailboxName, aDepth, aCollected);
+ }
+
+ // The Folder.lst file is not created if there is only one sub folder,
+ // so we need to find the sub folder by our hands.
+ // The folder name does not begin with # or ! maybe. Yes, maybe...
+ if (!folderListExists) {
+ if (StringBeginsWith(name, NS_LITERAL_STRING("#")) ||
+ StringBeginsWith(name, NS_LITERAL_STRING("!")))
+ continue;
+
+ bool isDirectory = false;
+ rv = file->IsDirectory(&isDirectory);
+ if (isDirectory) {
+ CollectMailboxesInDirectory(file, aDepth + 1, aCollected);
+ continue;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyMail::FindMailboxes(nsIFile *aLocation, nsIArray **_retval)
+{
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CollectMailboxesInDirectory(aLocation, 0, array);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ array.forget(_retval);
+
+ return NS_OK;
+}
+
+static nsresult
+GetBeckyStatusValue(const nsCString &aHeader, nsACString &aValue)
+{
+ int32_t valueStartPosition;
+
+ valueStartPosition = aHeader.FindChar(':');
+ if (valueStartPosition < 0)
+ return NS_ERROR_UNEXPECTED;
+
+ valueStartPosition++;
+
+ int32_t commaPosition = aHeader.FindChar(',', valueStartPosition);
+ if (commaPosition < 0)
+ return NS_ERROR_UNEXPECTED;
+
+ nsAutoCString value(Substring(aHeader,
+ valueStartPosition,
+ commaPosition - valueStartPosition));
+ value.Trim(" \t");
+
+ aValue.Assign(value);
+
+ return NS_OK;
+}
+
+static nsresult
+GetBeckyIncludeValue(const nsCString &aHeader, nsACString &aValue)
+{
+ int32_t valueStartPosition;
+
+ valueStartPosition = aHeader.FindChar(':');
+ if (valueStartPosition < 0)
+ return NS_ERROR_FAILURE;
+
+ valueStartPosition++;
+ nsAutoCString value(Substring(aHeader, valueStartPosition));
+ value.Trim(" \t");
+
+ aValue.Assign(value);
+
+ return NS_OK;
+}
+
+static bool
+ConvertBeckyStatusToMozillaStatus(const nsCString &aHeader,
+ nsMsgMessageFlagType *aMozillaStatusFlag)
+{
+ nsresult rv;
+ nsAutoCString statusString;
+ rv = GetBeckyStatusValue(aHeader, statusString);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsresult errorCode;
+ uint32_t beckyStatusFlag = static_cast<uint32_t>(statusString.ToInteger(&errorCode, 16));
+ if (NS_FAILED(errorCode))
+ return false;
+
+ if (beckyStatusFlag & BECKY_STATUS_READ)
+ *aMozillaStatusFlag |= nsMsgMessageFlags::Read;
+ if (beckyStatusFlag & BECKY_STATUS_FORWARDED)
+ *aMozillaStatusFlag |= nsMsgMessageFlags::Forwarded;
+ if (beckyStatusFlag & BECKY_STATUS_REPLIED)
+ *aMozillaStatusFlag |= nsMsgMessageFlags::Replied;
+
+ return true;
+}
+
+static inline bool
+CheckHeaderKey(const nsCString &aHeader, const char *aKeyString)
+{
+ nsAutoCString key(StringHead(aHeader, aHeader.FindChar(':')));
+ key.Trim(" \t");
+ return key.Equals(aKeyString);
+}
+
+static inline bool
+IsBeckyStatusHeader(const nsCString &aHeader)
+{
+ return CheckHeaderKey(aHeader, X_BECKY_STATUS_HEADER);
+}
+
+static inline bool
+IsBeckyIncludeLine(const nsCString &aLine)
+{
+ return CheckHeaderKey(aLine, X_BECKY_INCLUDE_HEADER);
+}
+
+static inline bool
+IsEndOfHeaders(const nsCString &aLine)
+{
+ return aLine.IsEmpty();
+}
+
+static inline bool
+IsEndOfMessage(const nsCString &aLine)
+{
+ return aLine.Equals(".");
+}
+
+class ImportMessageRunnable: public mozilla::Runnable
+{
+public:
+ ImportMessageRunnable(nsIFile *aMessageFile,
+ nsIMsgFolder *aFolder);
+ NS_DECL_NSIRUNNABLE
+private:
+ nsresult WriteHeaders(nsCString &aHeaders, nsIOutputStream *aOutputStream);
+ nsresult HandleHeaderLine(const nsCString &aHeaderLine, nsACString &aHeaders);
+ nsresult GetAttachmentFile(nsIFile *aMailboxFile,
+ const nsCString &aHeader,
+ nsIFile **_retval);
+ nsresult WriteAttachmentFile(nsIFile *aMailboxFile,
+ const nsCString &aHeader,
+ nsIOutputStream *aOutputStream);
+
+ nsCOMPtr<nsIFile> mMessageFile;
+ nsCOMPtr<nsIMsgFolder> mFolder;
+};
+
+ImportMessageRunnable::ImportMessageRunnable(nsIFile *aMessageFile,
+ nsIMsgFolder *aFolder) :
+ mMessageFile(aMessageFile), mFolder(aFolder)
+{
+}
+
+nsresult
+ImportMessageRunnable::WriteHeaders(nsCString &aHeaders,
+ nsIOutputStream *aOutputStream)
+{
+ nsresult rv;
+ uint32_t writtenBytes = 0;
+
+ rv = aOutputStream->Write(FROM_LINE, strlen(FROM_LINE), &writtenBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aOutputStream->Write(aHeaders.get(), aHeaders.Length(), &writtenBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aOutputStream->Write(MSG_LINEBREAK, strlen(MSG_LINEBREAK), &writtenBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aHeaders.Truncate();
+
+ return NS_OK;
+}
+
+nsresult
+ImportMessageRunnable::HandleHeaderLine(const nsCString &aHeaderLine,
+ nsACString &aHeaders)
+{
+ aHeaders.Append(aHeaderLine);
+ aHeaders.AppendLiteral(MSG_LINEBREAK);
+
+ nsMsgMessageFlagType flag = 0;
+ if (IsBeckyStatusHeader(aHeaderLine) &&
+ ConvertBeckyStatusToMozillaStatus(aHeaderLine, &flag)) {
+ char *statusLine;
+ statusLine = PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, flag);
+ aHeaders.Append(statusLine);
+ PR_smprintf_free(statusLine);
+ aHeaders.AppendLiteral(X_MOZILLA_KEYWORDS);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ImportMessageRunnable::GetAttachmentFile(nsIFile *aMailboxFile,
+ const nsCString &aHeader,
+ nsIFile **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> attachmentFile;
+
+ rv = aMailboxFile->Clone(getter_AddRefs(attachmentFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = attachmentFile->Append(NS_LITERAL_STRING("#Attach"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString nativeAttachmentPath;
+ rv = GetBeckyIncludeValue(aHeader, nativeAttachmentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = attachmentFile->AppendRelativeNativePath(nativeAttachmentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ attachmentFile->Exists(&exists);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ attachmentFile.forget(_retval);
+ return NS_OK;
+}
+
+nsresult
+ImportMessageRunnable::WriteAttachmentFile(nsIFile *aMailboxFile,
+ const nsCString &aHeader,
+ nsIOutputStream *aOutputStream)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> parentDirectory;
+ rv = aMailboxFile->GetParent(getter_AddRefs(parentDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> attachmentFile;
+ rv = GetAttachmentFile(parentDirectory,
+ aHeader,
+ getter_AddRefs(attachmentFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ attachmentFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char buffer[FILE_IO_BUFFER_SIZE];
+ uint32_t readBytes = 0;
+ uint32_t writtenBytes = 0;
+ rv = aOutputStream->Write(MSG_LINEBREAK, strlen(MSG_LINEBREAK), &writtenBytes);
+ while (NS_SUCCEEDED(inputStream->Read(buffer, sizeof(buffer), &readBytes)) &&
+ readBytes > 0) {
+ rv = aOutputStream->Write(buffer, readBytes, &writtenBytes);
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP ImportMessageRunnable::Run()
+{
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = mFolder->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILineInputStream> lineStream;
+ rv = nsBeckyUtils::CreateLineInputStream(mMessageFile,
+ getter_AddRefs(lineStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool reusable;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = msgStore->GetNewMsgOutputStream(mFolder, getter_AddRefs(msgHdr), &reusable,
+ getter_AddRefs(outputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool inHeader = true;
+ bool more = true;
+ nsAutoCString headers;
+ while (NS_SUCCEEDED(rv) && more) {
+ nsAutoCString line;
+ rv = lineStream->ReadLine(line, &more);
+ if (NS_FAILED(rv))
+ break;
+
+ if (inHeader) {
+ if (IsEndOfHeaders(line)) {
+ inHeader = false;
+ rv = WriteHeaders(headers, outputStream);
+ } else {
+ rv = HandleHeaderLine(line, headers);
+ }
+ } else if (IsEndOfMessage(line)) {
+ inHeader = true;
+ rv = msgStore->FinishNewMessage(outputStream, msgHdr);
+ if (!reusable)
+ outputStream->Close();
+ rv = msgStore->GetNewMsgOutputStream(mFolder, getter_AddRefs(msgHdr), &reusable,
+ getter_AddRefs(outputStream));
+ } else if (IsBeckyIncludeLine(line)) {
+ rv = WriteAttachmentFile(mMessageFile, line, outputStream);
+ } else {
+ uint32_t writtenBytes = 0;
+ if (StringBeginsWith(line, NS_LITERAL_CSTRING("..")))
+ line.Cut(0, 1);
+ else if (CheckHeaderKey(line, "From"))
+ line.Insert('>', 0);
+
+ line.AppendLiteral(MSG_LINEBREAK);
+ rv = outputStream->Write(line.get(), line.Length(), &writtenBytes);
+ }
+ }
+
+ if (outputStream) {
+ if (NS_FAILED(rv))
+ msgStore->DiscardNewMessage(outputStream, msgHdr);
+ outputStream->Close();
+ }
+
+ return rv;
+}
+
+static
+nsresult ProxyImportMessage(nsIFile *aMessageFile,
+ nsIMsgFolder *aFolder)
+{
+ RefPtr<ImportMessageRunnable> importMessage =
+ new ImportMessageRunnable(aMessageFile, aFolder);
+ return NS_DispatchToMainThread(importMessage, NS_DISPATCH_SYNC);
+}
+
+nsresult
+nsBeckyMail::ImportMailFile(nsIFile *aMailFile,
+ nsIMsgFolder *aDestination)
+{
+ int64_t size;
+ aMailFile->GetFileSize(&size);
+ if (size == 0)
+ return NS_OK;
+
+ return ProxyImportMessage(aMailFile, aDestination);
+}
+
+NS_IMETHODIMP
+nsBeckyMail::ImportMailbox(nsIImportMailboxDescriptor *aSource,
+ nsIMsgFolder *aDestination,
+ char16_t **aErrorLog,
+ char16_t **aSuccessLog,
+ bool *aFatalError)
+{
+ NS_ENSURE_ARG_POINTER(aSource);
+ NS_ENSURE_ARG_POINTER(aDestination);
+ NS_ENSURE_ARG_POINTER(aErrorLog);
+ NS_ENSURE_ARG_POINTER(aSuccessLog);
+ NS_ENSURE_ARG_POINTER(aFatalError);
+
+ mReadBytes = 0;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> mailboxFolder;
+ rv = aSource->GetFile(getter_AddRefs(mailboxFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ImportMailFile(mailboxFolder, aDestination);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t finalSize;
+ aSource->GetSize(&finalSize);
+ mReadBytes = finalSize;
+
+ nsAutoString name;
+ aSource->GetDisplayName(getter_Copies(name));
+
+ nsAutoString successMessage;
+ const char16_t *format = { name.get() };
+ rv =
+ nsBeckyStringBundle::FormatStringFromName(u"BeckyImportMailboxSuccess",
+ &format,
+ 1,
+ getter_Copies(successMessage));
+ successMessage.AppendLiteral("\n");
+ *aSuccessLog = ToNewUnicode(successMessage);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBeckyMail::GetImportProgress(uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mReadBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyMail::TranslateFolderName(const nsAString & aFolderName,
+ nsAString & _retval)
+{
+ return nsBeckyUtils::TranslateFolderName(aFolderName, _retval);
+}
+
diff --git a/mailnews/import/becky/src/nsBeckyMail.h b/mailnews/import/becky/src/nsBeckyMail.h
new file mode 100644
index 000000000..ae287a05f
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyMail.h
@@ -0,0 +1,45 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBeckyMail_h___
+#define nsBeckyMail_h___
+
+#include "nsIImportMail.h"
+
+class nsIFile;
+class nsIMutableArray;
+class nsIMsgFolder;
+
+class nsBeckyMail final : public nsIImportMail
+{
+public:
+ nsBeckyMail();
+ static nsresult Create(nsIImportMail **aImport);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTMAIL
+
+private:
+ virtual ~nsBeckyMail();
+
+ uint32_t mReadBytes;
+
+ nsresult CollectMailboxesInDirectory(nsIFile *aDirectory,
+ uint32_t aDepth,
+ nsIMutableArray *aCollected);
+ nsresult CollectMailboxesInFolderListFile(nsIFile *aListFile,
+ uint32_t aDepth,
+ nsIMutableArray *aCollected);
+ nsresult AppendMailboxDescriptor(nsIFile *aEntry,
+ const nsString &aName,
+ uint32_t aDepth,
+ nsIMutableArray *aCollected);
+ nsresult ImportMailFile(nsIFile *aMailFile,
+ nsIMsgFolder *aDestination);
+ nsresult CreateMailboxDescriptor(nsIImportMailboxDescriptor **aDescriptor);
+ nsresult GetMailboxName(nsIFile *aMailbox, nsAString &aName);
+};
+
+#endif /* nsBeckyMail_h___ */
diff --git a/mailnews/import/becky/src/nsBeckySettings.cpp b/mailnews/import/becky/src/nsBeckySettings.cpp
new file mode 100644
index 000000000..8e1cab960
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckySettings.cpp
@@ -0,0 +1,471 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgBaseCID.h"
+#include "nsMsgCompCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIINIParser.h"
+#include "nsISmtpService.h"
+#include "nsISmtpServer.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsIStringEnumerator.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsILineInputStream.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "msgCore.h"
+#include "nsIStringBundle.h"
+
+#include "nsBeckySettings.h"
+#include "nsBeckyStringBundle.h"
+#include "nsBeckyUtils.h"
+
+NS_IMPL_ISUPPORTS(nsBeckySettings, nsIImportSettings)
+
+nsresult
+nsBeckySettings::Create(nsIImportSettings **aImport)
+{
+ NS_ENSURE_ARG_POINTER(aImport);
+
+ *aImport = new nsBeckySettings();
+
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+nsBeckySettings::nsBeckySettings()
+{
+}
+
+nsBeckySettings::~nsBeckySettings()
+{
+}
+
+NS_IMETHODIMP
+nsBeckySettings::AutoLocate(char16_t **aDescription,
+ nsIFile **aLocation,
+ bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(aDescription);
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *aDescription =
+ nsBeckyStringBundle::GetStringByName(u"BeckyImportName");
+ *aLocation = nullptr;
+ *_retval = false;
+
+ nsCOMPtr<nsIFile> location;
+ nsresult rv = nsBeckyUtils::GetDefaultMailboxINIFile(getter_AddRefs(location));
+ if (NS_FAILED(rv))
+ location = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ else
+ *_retval = true;
+
+ location.forget(aLocation);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckySettings::SetLocation(nsIFile *aLocation)
+{
+ mLocation = aLocation;
+ return NS_OK;
+}
+
+nsresult
+nsBeckySettings::CreateParser()
+{
+ if (!mLocation) {
+ nsresult rv = nsBeckyUtils::GetDefaultMailboxINIFile(getter_AddRefs(mLocation));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // nsIINIParser accepts only UTF-8 encoding, so we need to convert the file
+ // first.
+ nsresult rv;
+ rv = nsBeckyUtils::ConvertToUTF8File(mLocation, getter_AddRefs(mConvertedFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nsBeckyUtils::CreateINIParserForFile(mConvertedFile,
+ getter_AddRefs(mParser));
+}
+
+nsresult
+nsBeckySettings::CreateSmtpServer(const nsCString &aUserName,
+ const nsCString &aServerName,
+ nsISmtpServer **aServer,
+ bool *existing)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsISmtpService> smtpService = do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISmtpServer> server;
+ rv = smtpService->FindServer(aUserName.get(),
+ aServerName.get(),
+ getter_AddRefs(server));
+
+ if (NS_FAILED(rv) || !server) {
+ rv = smtpService->CreateServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ server->SetHostname(aServerName);
+ server->SetUsername(aUserName);
+ *existing = false;
+ } else {
+ *existing = true;
+ }
+
+ server.forget(aServer);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckySettings::CreateIncomingServer(const nsCString &aUserName,
+ const nsCString &aServerName,
+ const nsCString &aProtocol,
+ nsIMsgIncomingServer **aServer)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = accountManager->FindServer(aUserName,
+ aServerName,
+ aProtocol,
+ getter_AddRefs(incomingServer));
+
+ if (NS_FAILED(rv) || !incomingServer) {
+ rv = accountManager->CreateIncomingServer(aUserName,
+ aServerName,
+ aProtocol,
+ getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ incomingServer.forget(aServer);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckySettings::SetupSmtpServer(nsISmtpServer **aServer)
+{
+ nsresult rv;
+ nsAutoCString userName, serverName;
+
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("SMTPServer"),
+ serverName);
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("UserID"),
+ userName);
+
+ nsCOMPtr<nsISmtpServer> server;
+ bool existing = false;
+ rv = CreateSmtpServer(userName, serverName, getter_AddRefs(server), &existing);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we already have an existing server, do not touch it's settings.
+ if (existing) {
+ server.forget(aServer);
+ return NS_OK;
+ }
+
+ nsAutoCString value;
+ rv = mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("SMTPPort"),
+ value);
+ int32_t port = 25;
+ if (NS_SUCCEEDED(rv)) {
+ nsresult errorCode;
+ port = value.ToInteger(&errorCode, 10);
+ }
+ server->SetPort(port);
+
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("SSLSMTP"),
+ value);
+ if (value.Equals("1"))
+ server->SetSocketType(nsMsgSocketType::SSL);
+
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("SMTPAUTH"),
+ value);
+ if (value.Equals("1")) {
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("SMTPAUTHMODE"),
+ value);
+ nsMsgAuthMethodValue authMethod = nsMsgAuthMethod::none;
+ if (value.Equals("1")) {
+ authMethod = nsMsgAuthMethod::passwordEncrypted;
+ } else if (value.Equals("2") ||
+ value.Equals("4") ||
+ value.Equals("6")) {
+ authMethod = nsMsgAuthMethod::passwordCleartext;
+ } else {
+ authMethod = nsMsgAuthMethod::anything;
+ }
+ server->SetAuthMethod(authMethod);
+ }
+
+ server.forget(aServer);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckySettings::SetPop3ServerProperties(nsIMsgIncomingServer *aServer)
+{
+ nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(aServer);
+
+ nsAutoCString value;
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("POP3Auth"),
+ value); // 0: plain, 1: APOP, 2: CRAM-MD5, 3: NTLM
+ nsMsgAuthMethodValue authMethod;
+ if (value.IsEmpty() || value.Equals("0")) {
+ authMethod = nsMsgAuthMethod::passwordCleartext;
+ } else if (value.Equals("1")) {
+ authMethod = nsMsgAuthMethod::old;
+ } else if (value.Equals("2")) {
+ authMethod = nsMsgAuthMethod::passwordEncrypted;
+ } else if (value.Equals("3")) {
+ authMethod = nsMsgAuthMethod::NTLM;
+ } else {
+ authMethod = nsMsgAuthMethod::none;
+ }
+ aServer->SetAuthMethod(authMethod);
+
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("LeaveServer"),
+ value);
+ if (value.Equals("1")) {
+ pop3Server->SetLeaveMessagesOnServer(true);
+ nsresult rv = mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("KeepDays"),
+ value);
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ nsresult errorCode;
+ int32_t leftDays = value.ToInteger(&errorCode, 10);
+ if (NS_SUCCEEDED(errorCode)) {
+ pop3Server->SetNumDaysToLeaveOnServer(leftDays);
+ pop3Server->SetDeleteByAgeFromServer(true);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckySettings::SetupIncomingServer(nsIMsgIncomingServer **aServer)
+{
+ nsAutoCString value;
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("Protocol"),
+ value);
+ nsCString protocol;
+ if (value.Equals("1")) {
+ protocol = NS_LITERAL_CSTRING("imap");
+ } else {
+ protocol = NS_LITERAL_CSTRING("pop3");
+ }
+
+ nsAutoCString userName, serverName;
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("MailServer"),
+ serverName);
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("UserID"),
+ userName);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = CreateIncomingServer(userName, serverName, protocol, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isSecure = false;
+ int32_t port = 0;
+ nsresult errorCode;
+ if (protocol.EqualsLiteral("pop3")) {
+ SetPop3ServerProperties(server);
+ rv = mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("POP3Port"),
+ value);
+ if (NS_SUCCEEDED(rv))
+ port = value.ToInteger(&errorCode, 10);
+ else
+ port = 110;
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("SSLPOP"),
+ value);
+ if (value.Equals("1"))
+ isSecure = true;
+ } else if (protocol.EqualsLiteral("imap")) {
+ rv = mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("IMAP4Port"),
+ value);
+ if (NS_SUCCEEDED(rv))
+ port = value.ToInteger(&errorCode, 10);
+ else
+ port = 143;
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("SSLIMAP"),
+ value);
+ if (value.Equals("1"))
+ isSecure = true;
+ }
+
+ server->SetPort(port);
+ if (isSecure)
+ server->SetSocketType(nsMsgSocketType::SSL);
+
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("CheckInt"),
+ value);
+ if (value.Equals("1"))
+ server->SetDoBiff(true);
+ rv = mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("CheckEvery"),
+ value);
+ if (NS_SUCCEEDED(rv)) {
+ int32_t minutes = value.ToInteger(&errorCode, 10);
+ if (NS_SUCCEEDED(errorCode))
+ server->SetBiffMinutes(minutes);
+ }
+
+ server.forget(aServer);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckySettings::CreateIdentity(nsIMsgIdentity **aIdentity)
+{
+ nsAutoCString email, fullName, identityName, bccAddress;
+
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("Name"),
+ identityName);
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("YourName"),
+ fullName);
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("MailAddress"),
+ email);
+ mParser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("PermBcc"),
+ bccAddress);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = accountManager->CreateIdentity(getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ identity->SetLabel(NS_ConvertUTF8toUTF16(identityName));
+ identity->SetFullName(NS_ConvertUTF8toUTF16(fullName));
+ identity->SetEmail(email);
+ if (!bccAddress.IsEmpty()) {
+ identity->SetDoBcc(true);
+ identity->SetDoBccList(bccAddress);
+ }
+
+ identity.forget(aIdentity);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckySettings::CreateAccount(nsIMsgIdentity *aIdentity,
+ nsIMsgIncomingServer *aIncomingServer,
+ nsIMsgAccount **aAccount)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = accountManager->CreateAccount(getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = account->AddIdentity(aIdentity);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = account->SetIncomingServer(aIncomingServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ account.forget(aAccount);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckySettings::RemoveConvertedFile()
+{
+ if (mConvertedFile) {
+ bool exists;
+ mConvertedFile->Exists(&exists);
+ if (exists)
+ mConvertedFile->Remove(false);
+ mConvertedFile = nullptr;
+ }
+ return NS_OK;
+}
+
+#define NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(expr, rv) \
+ if (NS_FAILED(expr)) { \
+ RemoveConvertedFile(); \
+ return rv; \
+ }
+
+NS_IMETHODIMP
+nsBeckySettings::Import(nsIMsgAccount **aLocalMailAccount,
+ bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(aLocalMailAccount);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv = CreateParser();
+ NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = SetupIncomingServer(getter_AddRefs(incomingServer));
+ NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv);
+
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = SetupSmtpServer(getter_AddRefs(smtpServer));
+ NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = CreateIdentity(getter_AddRefs(identity));
+ NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv);
+
+ nsAutoCString smtpKey;
+ smtpServer->GetKey(getter_Copies(smtpKey));
+ identity->SetSmtpServerKey(smtpKey);
+
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = CreateAccount(identity, incomingServer, getter_AddRefs(account));
+ NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv);
+
+ RemoveConvertedFile();
+ if (aLocalMailAccount)
+ account.forget(aLocalMailAccount);
+ *_retval = true;
+ return NS_OK;
+}
+
diff --git a/mailnews/import/becky/src/nsBeckySettings.h b/mailnews/import/becky/src/nsBeckySettings.h
new file mode 100644
index 000000000..19e7d45ed
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckySettings.h
@@ -0,0 +1,52 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBeckySettings_h___
+#define nsBeckySettings_h___
+
+#include "nsIImportSettings.h"
+#include "nsIFile.h"
+#include "nsIINIParser.h"
+
+class nsIMsgIncomingServer;
+class nsIMsgIdentity;
+class nsISmtpServer;
+
+class nsBeckySettings final : public nsIImportSettings
+{
+public:
+ nsBeckySettings();
+ static nsresult Create(nsIImportSettings **aImport);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTSETTINGS
+
+private:
+ virtual ~nsBeckySettings();
+
+ nsCOMPtr<nsIFile> mLocation;
+ nsCOMPtr<nsIFile> mConvertedFile;
+ nsCOMPtr<nsIINIParser> mParser;
+
+ nsresult CreateParser();
+ nsresult CreateIdentity(nsIMsgIdentity **aIdentity);
+ nsresult CreateAccount(nsIMsgIdentity *aIdentity,
+ nsIMsgIncomingServer *aIncomingServer,
+ nsIMsgAccount **aAccount);
+ nsresult CreateSmtpServer(const nsCString &aUserName,
+ const nsCString &aServerName,
+ nsISmtpServer **aServer,
+ bool *existing);
+ nsresult CreateIncomingServer(const nsCString &aUserName,
+ const nsCString &aServerName,
+ const nsCString &aProtocol,
+ nsIMsgIncomingServer **aServer);
+ nsresult SetupIncomingServer(nsIMsgIncomingServer **aServer);
+ nsresult SetupSmtpServer(nsISmtpServer **aServer);
+ nsresult SetPop3ServerProperties(nsIMsgIncomingServer *aServer);
+ nsresult RemoveConvertedFile();
+};
+
+#endif /* nsBeckySettings_h___ */
diff --git a/mailnews/import/becky/src/nsBeckyStringBundle.cpp b/mailnews/import/becky/src/nsBeckyStringBundle.cpp
new file mode 100644
index 000000000..41209dff5
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyStringBundle.cpp
@@ -0,0 +1,74 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+#include "nsIServiceManager.h"
+#include "nsIURI.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXPCOMCIDInternal.h"
+
+#include "nsBeckyStringBundle.h"
+
+#define BECKY_MESSAGES_URL "chrome://messenger/locale/beckyImportMsgs.properties"
+
+nsIStringBundle *nsBeckyStringBundle::mBundle = nullptr;
+
+nsIStringBundle *
+nsBeckyStringBundle::GetStringBundle(void)
+{
+ if (mBundle)
+ return mBundle;
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && bundleService)
+ rv = bundleService->CreateBundle(BECKY_MESSAGES_URL, &mBundle);
+
+ return mBundle;
+}
+
+void
+nsBeckyStringBundle::EnsureStringBundle(void)
+{
+ if (!mBundle)
+ (void) GetStringBundle();
+}
+
+char16_t *
+nsBeckyStringBundle::GetStringByName(const char16_t *aName)
+{
+ EnsureStringBundle();
+
+ char16_t *string = nullptr;
+ if (mBundle)
+ mBundle->GetStringFromName(aName, &string);
+
+ return string;
+}
+
+nsresult
+nsBeckyStringBundle::FormatStringFromName(const char16_t *name,
+ const char16_t **params,
+ uint32_t length,
+ char16_t **_retval)
+{
+ EnsureStringBundle();
+
+ return mBundle->FormatStringFromName(name,
+ params,
+ length,
+ _retval);
+}
+
+void
+nsBeckyStringBundle::Cleanup(void)
+{
+ if (mBundle)
+ mBundle->Release();
+ mBundle = nullptr;
+}
diff --git a/mailnews/import/becky/src/nsBeckyStringBundle.h b/mailnews/import/becky/src/nsBeckyStringBundle.h
new file mode 100644
index 000000000..190208c9d
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyStringBundle.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsBeckyStringBundle_H__
+#define _nsBeckyStringBundle_H__
+
+#include "nsString.h"
+
+class nsIStringBundle;
+
+class nsBeckyStringBundle final {
+public:
+ static char16_t *GetStringByName(const char16_t *name);
+ static nsresult FormatStringFromName(const char16_t *name,
+ const char16_t **params,
+ uint32_t length,
+ char16_t **_retval);
+ static nsIStringBundle * GetStringBundle(void); // don't release
+ static void EnsureStringBundle(void);
+ static void Cleanup(void);
+private:
+ static nsIStringBundle *mBundle;
+};
+
+#define BECKYIMPORT_NAME 2000
+#define BECKYIMPORT_DESCRIPTION 2001
+#define BECKYIMPORT_MAILBOX_SUCCESS 2002
+#define BECKYIMPORT_MAILBOX_BADPARAM 2003
+#define BECKYIMPORT_MAILBOX_CONVERTERROR 2004
+#define BECKYIMPORT_ADDRESS_SUCCESS 2005
+
+
+#endif /* _nsBeckyStringBundle_H__ */
diff --git a/mailnews/import/becky/src/nsBeckyUtils.cpp b/mailnews/import/becky/src/nsBeckyUtils.cpp
new file mode 100644
index 000000000..2e9af84a5
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyUtils.cpp
@@ -0,0 +1,334 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsString.h"
+#include "nsIUTF8ConverterService.h"
+#include "nsUConvCID.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsILineInputStream.h"
+#include "nsIConverterInputStream.h"
+#include "nsIConverterOutputStream.h"
+#include "nsMsgI18N.h"
+#include "nsNetUtil.h"
+#include "nsIINIParser.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsMsgUtils.h"
+#include "msgCore.h"
+#include "nsIImportMail.h"
+#include "nsThreadUtils.h"
+
+#include "nsBeckyUtils.h"
+
+nsresult
+nsBeckyUtils::FindUserDirectoryOnWindows7(nsIFile **aLocation)
+{
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> directory;
+ rv = GetSpecialDirectoryWithFileName(NS_WIN_DOCUMENTS_DIR,
+ "Becky",
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = directory->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isDirectory = false;
+ rv = directory->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDirectory)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ directory.forget(aLocation);
+ return NS_OK;
+}
+
+nsresult
+nsBeckyUtils::FindUserDirectoryOnWindowsXP(nsIFile **aLocation)
+{
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> directory = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->InitWithPath(NS_LITERAL_STRING("C:\\Becky!"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = directory->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isDirectory = false;
+ rv = directory->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDirectory)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ nsCOMPtr<nsISupports> entry;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ rv = entries->GetNext(getter_AddRefs(entry));
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory = false;
+ rv = file->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isDirectory) {
+ file.forget(aLocation);
+ return NS_OK;
+ }
+ }
+
+ directory.forget(aLocation);
+ return NS_OK;
+}
+
+nsresult
+nsBeckyUtils::FindUserDirectory(nsIFile **aLocation)
+{
+ nsresult rv = FindUserDirectoryOnWindows7(aLocation);
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ rv = FindUserDirectoryOnWindowsXP(aLocation);
+ }
+ return rv;
+}
+
+nsresult
+nsBeckyUtils::ConvertNativeStringToUTF8(const nsACString& aOriginal,
+ nsACString& _retval)
+{
+ nsresult rv;
+ nsAutoString unicodeString;
+ rv = NS_CopyNativeToUnicode(aOriginal, unicodeString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF16toUTF8(unicodeString, _retval);
+ return NS_OK;
+}
+
+nsresult
+nsBeckyUtils::CreateLineInputStream(nsIFile *aFile,
+ nsILineInputStream **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(inputStream, _retval);
+}
+
+nsresult
+nsBeckyUtils::GetFolderListFile(nsIFile *aLocation, nsIFile **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> folderListFile;
+ rv = aLocation->Clone(getter_AddRefs(folderListFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = folderListFile->Append(NS_LITERAL_STRING("Folder.lst"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = folderListFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ folderListFile.forget(_retval);
+ return NS_OK;
+}
+
+nsresult
+nsBeckyUtils::GetDefaultFolderName(nsIFile *aFolderListFile, nsACString& name)
+{
+ nsresult rv;
+ nsCOMPtr<nsILineInputStream> lineStream;
+ rv = CreateLineInputStream(aFolderListFile, getter_AddRefs(lineStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ rv = lineStream->ReadLine(name, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsBeckyUtils::GetDefaultMailboxDirectory(nsIFile **_retval)
+{
+ nsCOMPtr<nsIFile> userDirectory;
+ nsresult rv = FindUserDirectory(getter_AddRefs(userDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> folderListFile;
+ rv = GetFolderListFile(userDirectory, getter_AddRefs(folderListFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString defaultFolderName;
+ rv = GetDefaultFolderName(folderListFile, defaultFolderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = userDirectory->AppendNative(defaultFolderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = userDirectory->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isDirectory = false;
+ rv = userDirectory->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDirectory)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ userDirectory.forget(_retval);
+ return NS_OK;
+}
+
+nsresult
+nsBeckyUtils::GetDefaultMailboxINIFile(nsIFile **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> mailboxDirectory;
+ rv = GetDefaultMailboxDirectory(getter_AddRefs(mailboxDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetMailboxINIFile(mailboxDirectory, _retval);
+}
+
+nsresult
+nsBeckyUtils::GetMailboxINIFile(nsIFile *aDirectory, nsIFile **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> target;
+ rv = aDirectory->Clone(getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = target->Append(NS_LITERAL_STRING("Mailbox.ini"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists;
+ rv = target->Exists(&exists);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ target.forget(_retval);
+ return NS_OK;
+}
+
+nsresult
+nsBeckyUtils::CreateINIParserForFile(nsIFile *aFile,
+ nsIINIParser **aParser)
+{
+ nsresult rv;
+ nsCOMPtr<nsIINIParserFactory> factory =
+ do_GetService("@mozilla.org/xpcom/ini-processor-factory;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return factory->CreateINIParser(aFile, aParser);
+}
+
+nsresult
+nsBeckyUtils::GetMailboxNameFromINIFile(nsIFile *aFile, nsCString &aName)
+{
+ nsresult rv;
+ nsCOMPtr<nsIINIParser> parser;
+ rv = CreateINIParserForFile(aFile, getter_AddRefs(parser));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return parser->GetString(NS_LITERAL_CSTRING("Account"),
+ NS_LITERAL_CSTRING("Name"),
+ aName);
+}
+
+nsresult
+nsBeckyUtils::ConvertToUTF8File(nsIFile *aSourceFile,
+ nsIFile **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> convertedFile;
+ rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "thunderbird-becky-import",
+ getter_AddRefs(convertedFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = convertedFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> source;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(source), aSourceFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> destination;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(destination),
+ convertedFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const uint32_t kBlock = 8192;
+
+ nsCOMPtr<nsIConverterInputStream> convertedInput =
+ do_CreateInstance("@mozilla.org/intl/converter-input-stream;1");
+ convertedInput->Init(source, nsMsgI18NFileSystemCharset(), kBlock, 0x0000);
+
+ nsCOMPtr<nsIConverterOutputStream> convertedOutput =
+ do_CreateInstance("@mozilla.org/intl/converter-output-stream;1");
+ convertedOutput->Init(destination, "UTF-8", kBlock, 0x0000);
+
+ char16_t *line = (char16_t *)moz_xmalloc(kBlock);
+ uint32_t readBytes = kBlock;
+ bool writtenBytes;
+ while (readBytes == kBlock) {
+ rv = convertedInput->Read(line, kBlock, &readBytes);
+ rv = convertedOutput->Write(readBytes, line, &writtenBytes);
+ }
+ convertedOutput->Close();
+ convertedInput->Close();
+
+ convertedFile.forget(_retval);
+ return NS_OK;
+}
+
+nsresult
+nsBeckyUtils::TranslateFolderName(const nsAString & aFolderName,
+ nsAString & _retval)
+{
+ if (aFolderName.LowerCaseEqualsLiteral("!trash"))
+ _retval = NS_LITERAL_STRING(kDestTrashFolderName);
+ else if (aFolderName.LowerCaseEqualsLiteral("!!!!inbox"))
+ _retval = NS_LITERAL_STRING(kDestInboxFolderName);
+ else if (aFolderName.LowerCaseEqualsLiteral("!!!!outbox"))
+ _retval = NS_LITERAL_STRING(kDestSentFolderName);
+ else if (aFolderName.LowerCaseEqualsLiteral("!!!!unsent"))
+ _retval = NS_LITERAL_STRING(kDestUnsentMessagesFolderName);
+ else
+ _retval = aFolderName;
+
+ return NS_OK;
+}
diff --git a/mailnews/import/becky/src/nsBeckyUtils.h b/mailnews/import/becky/src/nsBeckyUtils.h
new file mode 100644
index 000000000..8b6e3a542
--- /dev/null
+++ b/mailnews/import/becky/src/nsBeckyUtils.h
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsBeckyUtils_H__
+#define _nsBeckyUtils_H__
+
+class nsIFile;
+class nsILineInputStream;
+class nsIINIParser;
+
+class nsBeckyUtils final {
+public:
+ static nsresult FindUserDirectoryOnWindows7(nsIFile **aLocation);
+ static nsresult FindUserDirectoryOnWindowsXP(nsIFile **aLocation);
+ static nsresult FindUserDirectory(nsIFile **aFile);
+ static nsresult ConvertNativeStringToUTF8(const nsACString& aOriginal,
+ nsACString& _retval);
+ static nsresult CreateLineInputStream(nsIFile *aFile,
+ nsILineInputStream **_retval);
+ static nsresult GetDefaultMailboxDirectory(nsIFile **_retval);
+ static nsresult GetFolderListFile(nsIFile *aLocation,
+ nsIFile **_retval);
+ static nsresult GetDefaultFolderName(nsIFile *aFolderListFile,
+ nsACString& name);
+ static nsresult GetDefaultMailboxINIFile(nsIFile **_retval);
+ static nsresult GetMailboxINIFile(nsIFile *aDirectory, nsIFile **_retval);
+ static nsresult CreateINIParserForFile(nsIFile *aFile,
+ nsIINIParser **aParser);
+ static nsresult GetMailboxNameFromINIFile(nsIFile *aFile, nsCString &aName);
+ static nsresult ConvertToUTF8File(nsIFile *aSourceFile,
+ nsIFile **_retval);
+ static nsresult TranslateFolderName(const nsAString & aFolderName,
+ nsAString & _retval);
+};
+
+
+#endif /* _nsBeckyUtils_H__ */
diff --git a/mailnews/import/build/moz.build b/mailnews/import/build/moz.build
new file mode 100644
index 000000000..56a6eb8c1
--- /dev/null
+++ b/mailnews/import/build/moz.build
@@ -0,0 +1,61 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsImportModule.cpp',
+]
+
+USE_LIBS += [
+ 'nspr',
+]
+
+if CONFIG['MOZ_INCOMPLETE_EXTERNAL_LINKAGE']:
+ XPCOMBinaryComponent('import')
+ USE_LIBS += [
+ 'msgbsutl_s',
+ 'rdfutil_external_s',
+ 'unicharutil_external_s',
+ 'xpcomglue_s',
+ 'xul',
+ ]
+else:
+ Library('import')
+ FINAL_LIBRARY = 'xul'
+
+# js needs to come after xul for now, because it is an archive and its content
+# is discarded when it comes first.
+USE_LIBS += [
+ 'js',
+]
+
+LOCAL_INCLUDES += [
+ '../src',
+ '../text/src',
+ '../vcard/src',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '../applemail/src',
+ ]
+ OS_LIBS += CONFIG['TK_LIBS']
+ OS_LIBS += ['-framework Cocoa']
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ LOCAL_INCLUDES += [
+ ]
+ if not CONFIG['GNU_CC']:
+ LOCAL_INCLUDES += [
+ '../becky/src',
+ '../outlook/src',
+ '../winlivemail',
+ ]
+ if CONFIG['MOZ_MAPI_SUPPORT']:
+ LOCAL_INCLUDES += [
+ '../outlook/src',
+ ]
+else:
+ OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
+
diff --git a/mailnews/import/build/nsImportModule.cpp b/mailnews/import/build/nsImportModule.cpp
new file mode 100644
index 000000000..f8f3814e6
--- /dev/null
+++ b/mailnews/import/build/nsImportModule.cpp
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+////////////////////////////////////////////////////////////////////////////////
+// Core Module Include Files
+////////////////////////////////////////////////////////////////////////////////
+#include "nsCOMPtr.h"
+#include "mozilla/ModuleUtils.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// core import Include Files
+////////////////////////////////////////////////////////////////////////////////
+#include "nsImportService.h"
+#include "nsImportMimeEncode.h"
+#include "nsImportStringBundle.h"
+
+NS_DEFINE_NAMED_CID(NS_IMPORTSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_IMPORTMIMEENCODE_CID);
+////////////////////////////////////////////////////////////////////////////////
+// text import Include Files
+////////////////////////////////////////////////////////////////////////////////
+#include "nsTextImport.h"
+
+NS_DEFINE_NAMED_CID(NS_TEXTIMPORT_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// vCard import Include Files
+////////////////////////////////////////////////////////////////////////////////
+#include "nsVCardImport.h"
+
+NS_DEFINE_NAMED_CID(NS_VCARDIMPORT_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// Apple Mail import Include Files
+////////////////////////////////////////////////////////////////////////////////
+#if defined(XP_MACOSX)
+#include "nsAppleMailImport.h"
+
+NS_DEFINE_NAMED_CID(NS_APPLEMAILIMPORT_CID);
+NS_DEFINE_NAMED_CID(NS_APPLEMAILIMPL_CID);
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// outlook import Include Files
+////////////////////////////////////////////////////////////////////////////////
+#ifdef XP_WIN
+#ifdef MOZ_MAPI_SUPPORT
+#include "nsOutlookImport.h"
+#include "nsOutlookStringBundle.h"
+#endif
+#include "nsWMImport.h"
+#include "nsWMStringBundle.h"
+
+NS_DEFINE_NAMED_CID(NS_WMIMPORT_CID);
+#ifdef MOZ_MAPI_SUPPORT
+NS_DEFINE_NAMED_CID(NS_OUTLOOKIMPORT_CID);
+#endif
+#endif // XP_WIN
+
+////////////////////////////////////////////////////////////////////////////////
+// becky import Include Files
+////////////////////////////////////////////////////////////////////////////////
+#ifdef XP_WIN
+#include "nsBeckyImport.h"
+#include "nsBeckyStringBundle.h"
+
+NS_DEFINE_NAMED_CID(NS_BECKYIMPORT_CID);
+#endif // XP_WIN
+
+////////////////////////////////////////////////////////////////////////////////
+// core import factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsImportService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsIImportMimeEncodeImpl)
+
+////////////////////////////////////////////////////////////////////////////////
+// text import factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTextImport)
+
+////////////////////////////////////////////////////////////////////////////////
+// vcard import factories
+////////////////////////////////////////////////////////////////////////////////
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsVCardImport)
+
+////////////////////////////////////////////////////////////////////////////////
+// apple mail import factories
+////////////////////////////////////////////////////////////////////////////////
+#if defined(XP_MACOSX)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAppleMailImportModule)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppleMailImportMail, Initialize)
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// outlook import factories
+////////////////////////////////////////////////////////////////////////////////
+#ifdef XP_WIN
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWMImport)
+#ifdef MOZ_MAPI_SUPPORT
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsOutlookImport)
+#endif
+#endif // XP_WIN
+////////////////////////////////////////////////////////////////////////////////
+// becky import factory
+////////////////////////////////////////////////////////////////////////////////
+#ifdef XP_WIN
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBeckyImport)
+#endif // XP_WIN
+
+static const mozilla::Module::CategoryEntry kMailNewsImportCategories[] = {
+ // XXX These CIDs should match the explicit CIDs defined in the header files,
+ // or be changed so that they are contract IDs (with appropriate code updates)
+ { "mailnewsimport", "{A5991D01-ADA7-11d3-A9C2-00A0CC26DA63}", NS_IMPORT_ADDRESS_STR },
+ { "mailnewsimport", "{0eb034a3-964a-4e2f-92eb-cc55d9ae9dd2}", NS_IMPORT_ADDRESS_STR },
+#ifdef XP_WIN
+ { "mailnewsimport", "{42bc82bc-8e9f-4597-8b6e-e529daaf3af1}", kWMSupportsString },
+ { "mailnewsimport", "{7952a6cf-2442-4c04-9f02-150b15a0a841}", kBeckySupportsString },
+#ifdef MOZ_MAPI_SUPPORT
+ { "mailnewsimport", "{1DB469A0-8B00-11d3-A206-00A0CC26DA63}", kOutlookSupportsString },
+#endif
+#endif
+#if defined(XP_MACOSX)
+ { "mailnewsimport", "{6d3f101c-70ec-4e04-b68d-9908d1aeddf3}", kAppleMailSupportsString },
+#endif
+ { NULL }
+};
+
+const mozilla::Module::CIDEntry kMailNewsImportCIDs[] = {
+ { &kNS_IMPORTSERVICE_CID, false, NULL, nsImportServiceConstructor },
+ { &kNS_IMPORTMIMEENCODE_CID, false, NULL, nsIImportMimeEncodeImplConstructor },
+ { &kNS_TEXTIMPORT_CID, false, NULL, nsTextImportConstructor },
+ { &kNS_VCARDIMPORT_CID, false, NULL, nsVCardImportConstructor },
+#if defined(XP_MACOSX)
+ { &kNS_APPLEMAILIMPORT_CID, false, NULL, nsAppleMailImportModuleConstructor },
+ { &kNS_APPLEMAILIMPL_CID, false, NULL, nsAppleMailImportMailConstructor },
+#endif
+
+#ifdef XP_WIN
+ { &kNS_WMIMPORT_CID, false, NULL, nsWMImportConstructor },
+ { &kNS_BECKYIMPORT_CID, false, NULL, nsBeckyImportConstructor },
+#ifdef MOZ_MAPI_SUPPORT
+ { &kNS_OUTLOOKIMPORT_CID, false, NULL, nsOutlookImportConstructor },
+#endif
+#endif
+ { NULL }
+};
+
+const mozilla::Module::ContractIDEntry kMailNewsImportContracts[] = {
+ { NS_IMPORTSERVICE_CONTRACTID, &kNS_IMPORTSERVICE_CID },
+ { "@mozilla.org/import/import-mimeencode;1", &kNS_IMPORTMIMEENCODE_CID },
+ { "@mozilla.org/import/import-text;1", &kNS_TEXTIMPORT_CID },
+ { "@mozilla.org/import/import-vcard;1", &kNS_VCARDIMPORT_CID },
+#if defined(XP_MACOSX)
+ { "@mozilla.org/import/import-applemail;1", &kNS_APPLEMAILIMPORT_CID },
+ { NS_APPLEMAILIMPL_CONTRACTID, &kNS_APPLEMAILIMPL_CID },
+#endif
+
+#ifdef XP_WIN
+ { "@mozilla.org/import/import-wm;1", &kNS_WMIMPORT_CID },
+ { "@mozilla.org/import/import-becky;1", &kNS_BECKYIMPORT_CID },
+#ifdef MOZ_MAPI_SUPPORT
+ { "@mozilla.org/import/import-outlook;1", &kNS_OUTLOOKIMPORT_CID },
+#endif
+#endif
+ { NULL }
+};
+
+
+static void importModuleDtor()
+{
+#ifdef XP_WIN
+ nsWMStringBundle::Cleanup();
+ nsBeckyStringBundle::Cleanup();
+#ifdef MOZ_MAPI_SUPPORT
+ nsOutlookStringBundle::Cleanup();
+#endif
+#endif
+}
+
+static const mozilla::Module kMailNewsImportModule = {
+ mozilla::Module::kVersion,
+ kMailNewsImportCIDs,
+ kMailNewsImportContracts,
+ kMailNewsImportCategories,
+ NULL,
+ NULL,
+ importModuleDtor
+};
+
+NSMODULE_DEFN(nsImportServiceModule) = &kMailNewsImportModule;
+
+
diff --git a/mailnews/import/content/fieldMapImport.js b/mailnews/import/content/fieldMapImport.js
new file mode 100644
index 000000000..cd0f2d5da
--- /dev/null
+++ b/mailnews/import/content/fieldMapImport.js
@@ -0,0 +1,186 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var importService;
+var fieldMap = null;
+var recordNum = 0;
+var addInterface = null;
+var dialogResult = null;
+var gPreviousButton;
+var gNextButton;
+var gMoveUpButton;
+var gMoveDownButton;
+var gListbox;
+var gSkipFirstRecordButton;
+
+function OnLoadFieldMapImport()
+{
+ top.importService = Components.classes["@mozilla.org/import/import-service;1"]
+ .getService(Components.interfaces.nsIImportService);
+
+ // We need a field map object...
+ // assume we have one passed in? or just make one?
+ if (window.arguments && window.arguments[0]) {
+ top.fieldMap = window.arguments[0].fieldMap;
+ top.addInterface = window.arguments[0].addInterface;
+ top.dialogResult = window.arguments[0].result;
+ }
+ if (top.fieldMap == null) {
+ top.fieldMap = top.importService.CreateNewFieldMap();
+ top.fieldMap.DefaultFieldMap( top.fieldMap.numMozFields);
+ }
+
+ gMoveUpButton = document.getElementById("upButton");
+ gMoveDownButton = document.getElementById("downButton");
+ gPreviousButton = document.getElementById("previous");
+ gNextButton = document.getElementById("next");
+ gListbox = document.getElementById("fieldList");
+ gSkipFirstRecordButton = document.getElementById("skipFirstRecord");
+
+ // Set the state of the skip first record button
+ gSkipFirstRecordButton.checked = top.fieldMap.skipFirstRecord;
+
+ ListFields();
+ Browse(1);
+ gListbox.selectedItem = gListbox.getItemAtIndex(0);
+ disableMoveButtons();
+}
+
+function IndexInMap( index)
+{
+ var count = top.fieldMap.mapSize;
+ for (var i = 0; i < count; i++) {
+ if (top.fieldMap.GetFieldMap( i) == index)
+ return( true);
+ }
+
+ return( false);
+}
+
+function ListFields() {
+ if (top.fieldMap == null)
+ return;
+
+ var count = top.fieldMap.mapSize;
+ var index;
+ var i;
+ for (i = 0; i < count; i++) {
+ index = top.fieldMap.GetFieldMap( i);
+ AddFieldToList(top.fieldMap.GetFieldDescription( index), index, top.fieldMap.GetFieldActive( i));
+ }
+
+ count = top.fieldMap.numMozFields;
+ for (i = 0; i < count; i++) {
+ if (!IndexInMap( i))
+ AddFieldToList(top.fieldMap.GetFieldDescription( i), i, false);
+ }
+}
+
+function CreateField( name, index, on)
+{
+ var item = document.createElement('listitem');
+ item.setAttribute('field-index', index);
+ item.setAttribute('type', "checkbox");
+ var cell = document.createElement('listcell');
+ var cCell = document.createElement( 'listcell');
+ cCell.setAttribute('type', "checkbox");
+ cCell.setAttribute( 'label', name);
+ if (on == true)
+ cCell.setAttribute( 'checked', "true");
+ item.appendChild( cCell);
+ cell.setAttribute( "class", "importsampledata");
+ cell.setAttribute( 'label', "");
+ item.appendChild( cell);
+ return( item);
+}
+
+function AddFieldToList(name, index, on)
+{
+ var item = CreateField(name, index, on);
+ gListbox.appendChild(item);
+}
+
+function itemClicked(event)
+{
+ if (event.button == 0) {
+ var on = gListbox.selectedItem.firstChild.getAttribute('checked');
+ gListbox.selectedItem.firstChild.setAttribute('checked', (on != "true"));
+ }
+}
+
+// The "Move Up/Move Down" buttons should move the items in the left column
+// up/down but the values in the right column should not change.
+function moveItem(up)
+{
+ var selectedItem = gListbox.selectedItem;
+ var swapPartner = (up ? gListbox.getPreviousItem(selectedItem, 1)
+ : gListbox.getNextItem(selectedItem, 1));
+
+ var tmpLabel = swapPartner.lastChild.getAttribute('label');
+ swapPartner.lastChild.setAttribute('label', selectedItem.lastChild.getAttribute('label'));
+ selectedItem.lastChild.setAttribute('label', tmpLabel);
+
+ var newItemPosition = (up ? selectedItem.nextSibling : selectedItem);
+ gListbox.insertBefore(swapPartner, newItemPosition);
+ gListbox.ensureElementIsVisible(selectedItem);
+ disableMoveButtons();
+}
+
+function disableMoveButtons()
+{
+ var selectedIndex = gListbox.selectedIndex;
+ gMoveUpButton.disabled = (selectedIndex == 0);
+ gMoveDownButton.disabled = (selectedIndex == (gListbox.getRowCount() - 1));
+}
+
+function ShowSampleData(data)
+{
+ var fields = data.split("\n");
+ for (var i = 0; i < gListbox.getRowCount(); i++)
+ gListbox.getItemAtIndex(i).lastChild.setAttribute('label', (i < fields.length) ? fields[i] : '');
+}
+
+function FetchSampleData(num)
+{
+ if (!top.addInterface)
+ return false;
+
+ var data = top.addInterface.GetData( "sampleData-" + num);
+ if (!(data instanceof Components.interfaces.nsISupportsString))
+ return false;
+ ShowSampleData( data.data);
+ return true;
+}
+
+function Browse(step)
+{
+ recordNum += step;
+ if (FetchSampleData(recordNum - 1))
+ document.getElementById('recordNumber').setAttribute('value', ("" + recordNum));
+
+ gPreviousButton.disabled = (recordNum == 1);
+ gNextButton.disabled = (addInterface.GetData("sampleData-" + recordNum) == null);
+}
+
+function FieldImportOKButton()
+{
+ var max = gListbox.getRowCount();
+ var fIndex;
+ var on;
+ // Ensure field map is the right size
+ top.fieldMap.SetFieldMapSize(max);
+
+ for (var i = 0; i < max; i++) {
+ fIndex = gListbox.getItemAtIndex(i).getAttribute( 'field-index');
+ on = gListbox.getItemAtIndex(i).firstChild.getAttribute('checked');
+ top.fieldMap.SetFieldMap( i, fIndex);
+ top.fieldMap.SetFieldActive( i, (on == "true"));
+ }
+
+ top.fieldMap.skipFirstRecord = gSkipFirstRecordButton.checked;
+
+ top.dialogResult.ok = true;
+
+ return true;
+}
diff --git a/mailnews/import/content/fieldMapImport.xul b/mailnews/import/content/fieldMapImport.xul
new file mode 100644
index 000000000..abaca10ba
--- /dev/null
+++ b/mailnews/import/content/fieldMapImport.xul
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/fieldMapImport.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons="accept,cancel"
+ title="&fieldMapImport.title;"
+ style="&fieldMapImport.size;"
+ ondialogaccept="FieldImportOKButton();"
+ onload="OnLoadFieldMapImport();">
+
+ <script type="application/javascript" src="chrome://messenger/content/fieldMapImport.js"/>
+
+ <hbox align="center">
+ <label value="&fieldMapImport.recordNumber;"/>
+ <label id="recordNumber"/>
+ <spacer flex="1"/>
+ <button id="previous" oncommand="Browse(-1);"
+ label="&fieldMapImport.previous.label;"
+ accesskey="&fieldMapImport.previous.accesskey;"/>
+ <button id="next" oncommand="Browse(1);"
+ label="&fieldMapImport.next.label;"
+ accesskey="&fieldMapImport.next.accesskey;"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="skipFirstRecord"
+ label="&fieldMapImport.skipFirstRecord.label;"
+ accesskey="&fieldMapImport.skipFirstRecord.accessKey;"/>
+ </hbox>
+
+ <separator class="thin"/>
+ <label control="fieldList">&fieldMapImport.text;</label>
+ <separator class="thin"/>
+
+ <!-- field list -->
+ <hbox flex="1">
+ <listbox id="fieldList" flex="1" onselect="disableMoveButtons();"
+ onclick="itemClicked(event);">
+ <listcols>
+ <listcol flex="7"/>
+ <listcol flex="13"/>
+ </listcols>
+
+ <listhead>
+ <listheader id="fieldNameHeader" label="&fieldMapImport.fieldListTitle;"/>
+ <listheader id="sampleDataHeader" label="&fieldMapImport.dataTitle;"/>
+ </listhead>
+ </listbox>
+
+ <vbox>
+ <spacer flex="1"/>
+ <button id="upButton" class="up" label="&fieldMapImport.up.label;"
+ accesskey="&fieldMapImport.up.accesskey;"
+ oncommand="moveItem(true);"/>
+ <button id="downButton" class="down" label="&fieldMapImport.down.label;"
+ accesskey="&fieldMapImport.down.accesskey;"
+ oncommand="moveItem(false);"/>
+ <spacer flex="1"/>
+ </vbox>
+ </hbox>
+
+</dialog>
diff --git a/mailnews/import/content/import-test.html b/mailnews/import/content/import-test.html
new file mode 100644
index 000000000..ef9c5ed7f
--- /dev/null
+++ b/mailnews/import/content/import-test.html
@@ -0,0 +1,36 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<html>
+<body>
+
+<script type="application/javascript">
+
+function toImport(importType)
+{
+ /*
+ window.openDialog("chrome:/messenger/content/fieldMapImport.xul",
+ "fieldMapImportDialog",
+ "chrome,modal");
+ */
+ window.openDialog("chrome:/messenger/content/importDialog.xul",
+ "",
+ "chrome,modal",
+ {importType:importType});
+
+}
+
+
+</script>
+
+<p>
+<form name="form">
+<input type="button" value="Import Address Books" onclick="toImport( 'addressbook');"><br>
+<input type="button" value="Import Mail" onclick="toImport( 'mail');"><br>
+<input type="button" value="Import Settings" onclick="toImport( 'settings');"><br>
+<input type="button" value="Import Filters" onclick="toImport( 'filters');"><br>
+<form>
+
+</body>
+</html>
diff --git a/mailnews/import/content/importDialog.js b/mailnews/import/content/importDialog.js
new file mode 100644
index 000000000..2ad7c6fb3
--- /dev/null
+++ b/mailnews/import/content/importDialog.js
@@ -0,0 +1,1066 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gImportType = null;
+var gImportMsgsBundle;
+var gFeedsBundle;
+var gImportService = null;
+var gSuccessStr = null;
+var gErrorStr = null;
+var gInputStr = null;
+var gProgressInfo = null;
+var gSelectedModuleName = null;
+var gAddInterface = null;
+var gNewFeedAcctCreated = false;
+
+var Ci = Components.interfaces;
+var nsISupportsString = Ci.nsISupportsString;
+
+function OnLoadImportDialog()
+{
+ gImportMsgsBundle = document.getElementById("bundle_importMsgs");
+ gFeedsBundle = document.getElementById("bundle_feeds");
+ gImportService = Components.classes["@mozilla.org/import/import-service;1"]
+ .getService(Ci.nsIImportService);
+
+ gProgressInfo = { };
+ gProgressInfo.progressWindow = null;
+ gProgressInfo.importInterface = null;
+ gProgressInfo.mainWindow = window;
+ gProgressInfo.intervalState = 0;
+ gProgressInfo.importSuccess = false;
+ gProgressInfo.importType = null;
+ gProgressInfo.localFolderExists = false;
+
+ gSuccessStr = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ gErrorStr = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ gInputStr = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+
+ // look in arguments[0] for parameters
+ if (("arguments" in window) && window.arguments.length >= 1 &&
+ ("importType" in window.arguments[0]) && window.arguments[0].importType)
+ {
+ // keep parameters in global for later
+ gImportType = window.arguments[0].importType;
+ gProgressInfo.importType = gImportType;
+ }
+ else
+ {
+ gImportType = "all";
+ gProgressInfo.importType = "all";
+ }
+
+ SetUpImportType();
+
+ // on startup, set the focus to the control element
+ // for accessibility reasons.
+ // if we used the wizardOverlay, we would get this for free.
+ // see bug #101874
+ document.getElementById("importFields").focus();
+}
+
+
+function SetUpImportType()
+{
+ // set dialog title
+ document.getElementById("importFields").value = gImportType;
+
+ // Mac migration not working right now, so disable it.
+ if (Services.appinfo.OS == "Darwin")
+ {
+ document.getElementById("allRadio").setAttribute("disabled", "true");
+ if (gImportType == "all")
+ document.getElementById("importFields").value = "addressbook";
+ }
+
+ let descriptionDeck = document.getElementById("selectDescriptionDeck");
+ descriptionDeck.selectedIndex = 0;
+ if (gImportType == "feeds")
+ {
+ descriptionDeck.selectedIndex = 1;
+ ListFeedAccounts();
+ }
+ else
+ ListModules();
+}
+
+
+function SetDivText(id, text)
+{
+ var div = document.getElementById(id);
+
+ if (div) {
+ if (!div.hasChildNodes()) {
+ var textNode = document.createTextNode(text);
+ div.appendChild(textNode);
+ }
+ else if (div.childNodes.length == 1) {
+ div.childNodes[0].nodeValue = text;
+ }
+ }
+}
+
+function CheckIfLocalFolderExists()
+{
+ try {
+ if (MailServices.accounts.localFoldersServer)
+ gProgressInfo.localFolderExists = true;
+ }
+ catch (ex) {
+ gProgressInfo.localFolderExists = false;
+ }
+}
+
+function ImportDialogOKButton()
+{
+ var listbox = document.getElementById("moduleList");
+ var deck = document.getElementById("stateDeck");
+ var header = document.getElementById("header");
+ var progressMeterEl = document.getElementById("progressMeter");
+ progressMeterEl.mode = "determined";
+ var progressStatusEl = document.getElementById("progressStatus");
+ var progressTitleEl = document.getElementById("progressTitle");
+
+ // better not mess around with navigation at this point
+ var nextButton = document.getElementById("forward");
+ nextButton.setAttribute("disabled", "true");
+ var backButton = document.getElementById("back");
+ backButton.setAttribute("disabled", "true");
+
+ if (listbox && (listbox.selectedCount == 1))
+ {
+ let module = "";
+ let name = "";
+ gImportType = document.getElementById("importFields").value;
+ let index = listbox.selectedItem.getAttribute("list-index");
+ if (index == -1)
+ return false;
+ if (gImportType == "feeds")
+ module = "Feeds";
+ else
+ {
+ module = gImportService.GetModule(gImportType, index);
+ name = gImportService.GetModuleName(gImportType, index);
+ }
+ gSelectedModuleName = name;
+ if (module)
+ {
+ // Fix for Bug 57839 & 85219
+ // We use localFoldersServer(in nsIMsgAccountManager) to check if Local Folder exists.
+ // We need to check localFoldersServer before importing "mail", "settings", or "filters".
+ // Reason: We will create an account with an incoming server of type "none" after
+ // importing "mail", so the localFoldersServer is valid even though the Local Folder
+ // is not created.
+ if (gImportType == "mail" || gImportType == "settings" || gImportType == "filters")
+ CheckIfLocalFolderExists();
+
+ let meterText = "";
+ let error = {};
+ switch(gImportType)
+ {
+ case "mail":
+ if (ImportMail(module, gSuccessStr, gErrorStr))
+ {
+ // We think it was a success, either, we need to
+ // wait for the import to finish
+ // or we are done!
+ if (gProgressInfo.importInterface == null) {
+ ShowImportResults(true, 'Mail');
+ return true;
+ }
+ else {
+ meterText = gImportMsgsBundle.getFormattedString('MailProgressMeterText',
+ [ name ]);
+ header.setAttribute("description", meterText);
+
+ progressStatusEl.setAttribute("label", "");
+ progressTitleEl.setAttribute("label", meterText);
+
+ deck.selectedIndex = 2;
+ gProgressInfo.progressWindow = window;
+ gProgressInfo.intervalState = setInterval(ContinueImportCallback, 100);
+ return true;
+ }
+ }
+ else
+ {
+ ShowImportResults(false, 'Mail');
+ // Re-enable the next button, as we are here, because the user cancelled the picking.
+ // Enable next, so they can try again.
+ nextButton.removeAttribute("disabled");
+ // Also enable back button so that users can pick other import options.
+ backButton.removeAttribute("disabled");
+ return false;
+ }
+ break;
+
+ case "feeds":
+ if (ImportFeeds())
+ {
+ // Successful completion of pre processing and launch of async import.
+ meterText = document.getElementById("description").textContent;
+ header.setAttribute("description", meterText);
+
+ progressStatusEl.setAttribute("label", "");
+ progressTitleEl.setAttribute("label", meterText);
+ progressMeterEl.mode = "undetermined";
+
+ deck.selectedIndex = 2;
+ return true;
+ }
+ else
+ {
+ // Re-enable the next button, as we are here, because the user cancelled the picking.
+ // Enable next, so they can try again.
+ nextButton.removeAttribute("disabled");
+ // Also enable back button so that users can pick other import options.
+ backButton.removeAttribute("disabled");
+ return false;
+ }
+ break;
+
+ case "addressbook":
+ if (ImportAddress(module, gSuccessStr, gErrorStr)) {
+ // We think it was a success, either, we need to
+ // wait for the import to finish
+ // or we are done!
+ if (gProgressInfo.importInterface == null) {
+ ShowImportResults(true, 'Address');
+ return true;
+ }
+ else {
+ meterText = gImportMsgsBundle.getFormattedString('AddrProgressMeterText',
+ [ name ]);
+ header.setAttribute("description", meterText);
+
+ progressStatusEl.setAttribute("label", "");
+ progressTitleEl.setAttribute("label", meterText);
+
+ deck.selectedIndex = 2;
+ gProgressInfo.progressWindow = window;
+ gProgressInfo.intervalState = setInterval(ContinueImportCallback, 100);
+
+ return true;
+ }
+ }
+ else
+ {
+ ShowImportResults(false, 'Address');
+ // Re-enable the next button, as we are here, because the user cancelled the picking.
+ // Enable next, so they can try again.
+ nextButton.removeAttribute("disabled");
+ // Also enable back button so that users can pick other import options.
+ backButton.removeAttribute("disabled");
+ return false;
+ }
+ break;
+
+ case "settings":
+ error.value = null;
+ let newAccount = {};
+ if (!ImportSettings(module, newAccount, error))
+ {
+ if (error.value)
+ ShowImportResultsRaw(gImportMsgsBundle.getString("ImportSettingsFailed"),
+ null, false);
+ // Re-enable the next button, as we are here, because the user cancelled the picking.
+ // Enable next, so they can try again.
+ nextButton.removeAttribute("disabled");
+ // Also enable back button so that users can pick other import options.
+ backButton.removeAttribute("disabled");
+ return false;
+ }
+ else
+ ShowImportResultsRaw(
+ gImportMsgsBundle.getFormattedString("ImportSettingsSuccess", [ name ]),
+ null, true);
+ break;
+
+ case "filters":
+ error.value = null;
+ if (!ImportFilters(module, error))
+ {
+ if (error.value)
+ ShowImportResultsRaw(
+ gImportMsgsBundle.getFormattedString("ImportFiltersFailed", [ name ]),
+ error.value, false);
+ // Re-enable the next button, as we are here, because the user cancelled the picking.
+ // Enable next, so they can try again.
+ nextButton.removeAttribute("disabled");
+ // Also enable back button so that users can pick other import options.
+ backButton.removeAttribute("disabled");
+ return false;
+ }
+ else
+ {
+ if (error.value)
+ ShowImportResultsRaw(
+ gImportMsgsBundle.getFormattedString('ImportFiltersPartial', [ name ]),
+ error.value, true);
+ else
+ ShowImportResultsRaw(
+ gImportMsgsBundle.getFormattedString('ImportFiltersSuccess', [ name ]),
+ null, true);
+ }
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+function SetStatusText(val)
+{
+ var progressStatus = document.getElementById("progressStatus");
+ progressStatus.setAttribute("label", val);
+}
+
+function SetProgress(val)
+{
+ var progressMeter = document.getElementById("progressMeter");
+ progressMeter.value = val;
+}
+
+function ContinueImportCallback()
+{
+ gProgressInfo.mainWindow.ContinueImport(gProgressInfo);
+}
+
+function ImportSelectionChanged()
+{
+ let listbox = document.getElementById('moduleList');
+ let acctNameBox = document.getElementById('acctName-box');
+ if (listbox && (listbox.selectedCount == 1))
+ {
+ let index = listbox.selectedItem.getAttribute("list-index");
+ if (index == -1)
+ return;
+ acctNameBox.setAttribute('style', 'visibility: hidden;');
+ if (gImportType == "feeds")
+ {
+ if (index == 0)
+ {
+ SetDivText('description', gFeedsBundle.getString('ImportFeedsNewAccount'));
+ let defaultName = gFeedsBundle.getString("feeds-accountname");
+ document.getElementById("acctName").value = defaultName;
+ acctNameBox.removeAttribute('style');
+ }
+ else
+ SetDivText('description', gFeedsBundle.getString('ImportFeedsExistingAccount'));
+ }
+ else
+ SetDivText("description", gImportService.GetModuleDescription(gImportType, index));
+ }
+}
+
+function CompareImportModuleName(a, b)
+{
+ if (a.name > b.name)
+ return 1;
+ if (a.name < b.name)
+ return -1;
+ return 0;
+}
+
+function ListModules() {
+ if (gImportService == null)
+ return;
+
+ var body = document.getElementById("moduleList");
+ while (body.hasChildNodes()) {
+ body.lastChild.remove();
+ }
+
+ var count = gImportService.GetModuleCount(gImportType);
+ var i;
+
+ var moduleArray = new Array(count);
+ for (i = 0; i < count; i++) {
+ moduleArray[i] = { name: gImportService.GetModuleName(gImportType, i), index: i };
+ }
+
+ // sort the array of modules by name, so that they'll show up in the right order
+ moduleArray.sort(CompareImportModuleName);
+
+ for (i = 0; i < count; i++) {
+ AddModuleToList(moduleArray[i].name, moduleArray[i].index);
+ }
+}
+
+function AddModuleToList(moduleName, index)
+{
+ var body = document.getElementById("moduleList");
+
+ var item = document.createElement('listitem');
+ item.setAttribute('label', moduleName);
+
+ // Temporarily skip Outlook Import which are busted (Bug 1175055).
+ if (moduleName == "Outlook") {
+ item.setAttribute('list-index', -1);
+ item.setAttribute('disabled', true);
+ item.setAttribute('tooltiptext', "Currently disabled due to bug 1175055");
+ } else {
+ item.setAttribute('list-index', index);
+ }
+ body.appendChild(item);
+}
+
+function ListFeedAccounts() {
+ let body = document.getElementById("moduleList");
+ while (body.hasChildNodes())
+ body.lastChild.remove();
+
+ // Add item to allow for new account creation.
+ let item = document.createElement("listitem");
+ item.setAttribute("label", gFeedsBundle.getString('ImportFeedsCreateNewListItem'));
+ item.setAttribute("list-index", 0);
+ body.appendChild(item);
+
+ let index = 0;
+ let feedRootFolders = FeedUtils.getAllRssServerRootFolders();
+
+ feedRootFolders.forEach(function(rootFolder) {
+ item = document.createElement("listitem");
+ item.setAttribute("label", rootFolder.prettyName);
+ item.setAttribute("list-index", ++index);
+ item.server = rootFolder.server;
+ body.appendChild(item);
+ }, this);
+
+ if (index)
+ // If there is an existing feed account, select the first one.
+ body.selectedIndex = 1;
+}
+
+function ContinueImport(info) {
+ var isMail = info.importType == 'mail';
+ var clear = true;
+ var deck;
+ var pcnt;
+
+ if (info.importInterface) {
+ if (!info.importInterface.ContinueImport()) {
+ info.importSuccess = false;
+ clearInterval(info.intervalState);
+ if (info.progressWindow != null) {
+ deck = document.getElementById("stateDeck");
+ deck.selectedIndex = 3;
+ info.progressWindow = null;
+ }
+
+ ShowImportResults(false, isMail ? 'Mail' : 'Address');
+ }
+ else if ((pcnt = info.importInterface.GetProgress()) < 100) {
+ clear = false;
+ if (info.progressWindow != null) {
+ if (pcnt < 5)
+ pcnt = 5;
+ SetProgress(pcnt);
+ if (isMail) {
+ let mailName = info.importInterface.GetData("currentMailbox");
+ if (mailName) {
+ mailName = mailName.QueryInterface(Ci.nsISupportsString);
+ if (mailName)
+ SetStatusText(mailName.data);
+ }
+ }
+ }
+ }
+ else {
+ dump("*** WARNING! sometimes this shows results too early. \n");
+ dump(" something screwy here. this used to work fine.\n");
+ clearInterval(info.intervalState);
+ info.importSuccess = true;
+ if (info.progressWindow) {
+ deck = document.getElementById("stateDeck");
+ deck.selectedIndex = 3;
+ info.progressWindow = null;
+ }
+
+ ShowImportResults(true, isMail ? 'Mail' : 'Address');
+ }
+ }
+ if (clear) {
+ info.intervalState = null;
+ info.importInterface = null;
+ }
+}
+
+
+function ShowResults(doesWantProgress, result)
+{
+ if (result)
+ {
+ if (doesWantProgress)
+ {
+ let deck = document.getElementById("stateDeck");
+ let header = document.getElementById("header");
+ let progressStatusEl = document.getElementById("progressStatus");
+ let progressTitleEl = document.getElementById("progressTitle");
+
+ let meterText = gImportMsgsBundle.getFormattedString("AddrProgressMeterText", [ name ]);
+ header.setAttribute("description", meterText);
+
+ progressStatusEl.setAttribute("label", "");
+ progressTitleEl.setAttribute("label", meterText);
+
+ deck.selectedIndex = 2;
+ gProgressInfo.progressWindow = window;
+ gProgressInfo.intervalState = setInterval(ContinueImportCallback, 100);
+ }
+ else
+ {
+ ShowImportResults(true, 'Address');
+ }
+ }
+ else
+ {
+ ShowImportResults(false, 'Address');
+ }
+
+ return true;
+}
+
+function ShowImportResults(good, module)
+{
+ // String keys for ImportSettingsSuccess, ImportSettingsFailed,
+ // ImportMailSuccess, ImportMailFailed, ImportAddressSuccess,
+ // ImportAddressFailed, ImportFiltersSuccess, and ImportFiltersFailed.
+ var modSuccess = 'Import' + module + 'Success';
+ var modFailed = 'Import' + module + 'Failed';
+
+ // The callers seem to set 'good' to true even if there's something
+ // in the error log. So we should only make it a success case if
+ // error log/str is empty.
+ var results, title;
+ var moduleName = gSelectedModuleName ? gSelectedModuleName : "";
+ if (good && !gErrorStr.data) {
+ title = gImportMsgsBundle.getFormattedString(modSuccess, [ moduleName ]);
+ results = gSuccessStr.data;
+ }
+ else if (gErrorStr.data) {
+ title = gImportMsgsBundle.getFormattedString(modFailed, [ moduleName ]);
+ results = gErrorStr.data;
+ }
+
+ if (results && title)
+ ShowImportResultsRaw(title, results, good);
+}
+
+function ShowImportResultsRaw(title, results, good)
+{
+ SetDivText("status", title);
+ var header = document.getElementById("header");
+ header.setAttribute("description", title);
+ dump("*** results = " + results + "\n");
+ attachStrings("results", results);
+ var deck = document.getElementById("stateDeck");
+ deck.selectedIndex = 3;
+ var nextButton = document.getElementById("forward");
+ nextButton.label = nextButton.getAttribute("finishedval");
+ nextButton.removeAttribute("disabled");
+ var cancelButton = document.getElementById("cancel");
+ cancelButton.setAttribute("disabled", "true");
+ var backButton = document.getElementById("back");
+ backButton.setAttribute("disabled", "true");
+
+ // If the Local Folder doesn't exist, create it after successfully
+ // importing "mail" and "settings"
+ var checkLocalFolder = (gProgressInfo.importType == "mail" ||
+ gProgressInfo.importType == "settings");
+ if (good && checkLocalFolder && !gProgressInfo.localFolderExists) {
+ MailServices.accounts.createLocalMailAccount();
+ }
+}
+
+function attachStrings(aNode, aString)
+{
+ var attachNode = document.getElementById(aNode);
+ if (!aString) {
+ attachNode.parentNode.setAttribute("hidden", "true");
+ return;
+ }
+ var strings = aString.split("\n");
+ for (let string of strings) {
+ if (string) {
+ let currNode = document.createTextNode(string);
+ attachNode.appendChild(currNode);
+ let br = document.createElementNS("http://www.w3.org/1999/xhtml", 'br');
+ attachNode.appendChild(br);
+ }
+ }
+}
+
+/*
+ Import Settings from a specific module, returns false if it failed
+ and true if successful. A "local mail" account is returned in newAccount.
+ This is only useful in upgrading - import the settings first, then
+ import mail into the account returned from ImportSettings, then
+ import address books.
+ An error string is returned as error.value
+*/
+function ImportSettings(module, newAccount, error) {
+ var setIntf = module.GetImportInterface("settings");
+ if (!(setIntf instanceof Ci.nsIImportSettings)) {
+ error.value = gImportMsgsBundle.getString('ImportSettingsBadModule');
+ return false;
+ }
+
+ // determine if we can auto find the settings or if we need to ask the user
+ var location = {};
+ var description = {};
+ var result = setIntf.AutoLocate(description, location);
+ if (!result) {
+ // In this case, we couldn't find the settings
+ if (location.value != null) {
+ // Settings were not found, however, they are specified
+ // in a file, so ask the user for the settings file.
+ let filePicker = Components.classes["@mozilla.org/filepicker;1"].createInstance();
+ if (filePicker instanceof Ci.nsIFilePicker) {
+ let file = null;
+ try {
+ filePicker.init(window,
+ gImportMsgsBundle.getString("ImportSelectSettings"),
+ filePicker.modeOpen);
+ filePicker.appendFilters(filePicker.filterAll);
+ filePicker.show();
+ file = filePicker.file;
+ }
+ catch(ex) {
+ file = null;
+ error.value = null;
+ return false;
+ }
+ if (file != null) {
+ setIntf.SetLocation(file);
+ }
+ else {
+ error.value = null;
+ return false;
+ }
+ }
+ else {
+ error.value = gImportMsgsBundle.getString('ImportSettingsNotFound');
+ return false;
+ }
+ }
+ else {
+ error.value = gImportMsgsBundle.getString('ImportSettingsNotFound');
+ return false;
+ }
+ }
+
+ // interesting, we need to return the account that new
+ // mail should be imported into?
+ // that's really only useful for "Upgrade"
+ result = setIntf.Import(newAccount);
+ if (!result) {
+ error.value = gImportMsgsBundle.getString('ImportSettingsFailed');
+ }
+ return result;
+}
+
+function ImportMail(module, success, error) {
+ if (gProgressInfo.importInterface || gProgressInfo.intervalState) {
+ error.data = gImportMsgsBundle.getString('ImportAlreadyInProgress');
+ return false;
+ }
+
+ gProgressInfo.importSuccess = false;
+
+ var mailInterface = module.GetImportInterface("mail");
+ if (!(mailInterface instanceof Ci.nsIImportGeneric)) {
+ error.data = gImportMsgsBundle.getString('ImportMailBadModule');
+ return false;
+ }
+
+ var loc = mailInterface.GetData("mailLocation");
+
+ if (loc == null) {
+ // No location found, check to see if we can ask the user.
+ if (mailInterface.GetStatus("canUserSetLocation") != 0) {
+ let filePicker = Components.classes["@mozilla.org/filepicker;1"].createInstance();
+ if (filePicker instanceof Ci.nsIFilePicker) {
+ try {
+ filePicker.init(window,
+ gImportMsgsBundle.getString("ImportSelectMailDir"),
+ filePicker.modeGetFolder);
+ filePicker.appendFilters(filePicker.filterAll);
+ filePicker.show();
+ if (filePicker.file && (filePicker.file.path.length > 0))
+ mailInterface.SetData("mailLocation", filePicker.file);
+ else
+ return false;
+ } catch(ex) {
+ // don't show an error when we return!
+ return false;
+ }
+ }
+ else {
+ error.data = gImportMsgsBundle.getString('ImportMailNotFound');
+ return false;
+ }
+ }
+ else {
+ error.data = gImportMsgsBundle.getString('ImportMailNotFound');
+ return false;
+ }
+ }
+
+ if (mailInterface.WantsProgress()) {
+ if (mailInterface.BeginImport(success, error)) {
+ gProgressInfo.importInterface = mailInterface;
+ // intervalState = setInterval(ContinueImport, 100);
+ return true;
+ }
+ else
+ return false;
+ }
+ else
+ return mailInterface.BeginImport(success, error);
+}
+
+
+// The address import! A little more complicated than the mail import
+// due to field maps...
+function ImportAddress(module, success, error) {
+ if (gProgressInfo.importInterface || gProgressInfo.intervalState) {
+ error.data = gImportMsgsBundle.getString('ImportAlreadyInProgress');
+ return false;
+ }
+
+ gProgressInfo.importSuccess = false;
+
+ gAddInterface = module.GetImportInterface("addressbook");
+ if (!(gAddInterface instanceof Ci.nsIImportGeneric)) {
+ error.data = gImportMsgsBundle.getString('ImportAddressBadModule');
+ return false;
+ }
+
+ var loc = gAddInterface.GetStatus("autoFind");
+ if (loc == 0) {
+ loc = gAddInterface.GetData("addressLocation");
+ if ((loc instanceof Ci.nsIFile) && !loc.exists)
+ loc = null;
+ }
+
+ if (loc == null) {
+ // Couldn't find the address book, see if we can
+ // as the user for the location or not?
+ if (gAddInterface.GetStatus("canUserSetLocation") == 0) {
+ // an autofind address book that could not be found!
+ error.data = gImportMsgsBundle.getString('ImportAddressNotFound');
+ return false;
+ }
+
+ let filePicker = Components.classes["@mozilla.org/filepicker;1"].createInstance();
+ if (!(filePicker instanceof Ci.nsIFilePicker)) {
+ error.data = gImportMsgsBundle.getString('ImportAddressNotFound');
+ return false;
+ }
+
+ // The address book location was not found.
+ // Determine if we need to ask for a directory
+ // or a single file.
+ let file = null;
+ let fileIsDirectory = false;
+ if (gAddInterface.GetStatus("supportsMultiple") != 0) {
+ // ask for dir
+ try {
+ filePicker.init(window,
+ gImportMsgsBundle.getString("ImportSelectAddrDir"),
+ filePicker.modeGetFolder);
+ filePicker.appendFilters(filePicker.filterAll);
+ filePicker.show();
+ if (filePicker.file && (filePicker.file.path.length > 0)) {
+ file = filePicker.file;
+ fileIsDirectory = true;
+ }
+ else {
+ file = null;
+ }
+ } catch(ex) {
+ file = null;
+ }
+ }
+ else {
+ // ask for file
+ try {
+ filePicker.init(window,
+ gImportMsgsBundle.getString("ImportSelectAddrFile"),
+ filePicker.modeOpen);
+ let addressbookBundle = document.getElementById("bundle_addressbook");
+ if (gSelectedModuleName ==
+ document.getElementById("bundle_vcardImportMsgs")
+ .getString("vCardImportName")) {
+ filePicker.appendFilter(addressbookBundle.getString('VCFFiles'), "*.vcf");
+ } else {
+ filePicker.appendFilter(addressbookBundle.getString('LDIFFiles'), "*.ldi; *.ldif");
+ filePicker.appendFilter(addressbookBundle.getString('CSVFiles'), "*.csv");
+ filePicker.appendFilter(addressbookBundle.getString('TABFiles'), "*.tab; *.txt");
+ filePicker.appendFilters(filePicker.filterAll);
+ }
+
+ if (filePicker.show() == filePicker.returnCancel)
+ return false;
+
+ if (filePicker.file && (filePicker.file.path.length > 0))
+ file = filePicker.file;
+ else
+ file = null;
+ } catch(ex) {
+ dump("ImportAddress(): failure when picking a file to import: " + ex + "\n");
+ file = null;
+ }
+ }
+
+ if (file == null) {
+ return false;
+ }
+
+ if (!fileIsDirectory && (file.fileSize == 0)) {
+ let errorText = gImportMsgsBundle.getFormattedString("ImportEmptyAddressBook",
+ [filePicker.file.leafName]);
+
+ Services.prompt.alert(window, document.title, errorText);
+ return false;
+ }
+ gAddInterface.SetData("addressLocation", file);
+ }
+
+ var map = gAddInterface.GetData("fieldMap");
+ if (map instanceof Ci.nsIImportFieldMap) {
+ let result = {};
+ result.ok = false;
+ window.openDialog(
+ "chrome://messenger/content/fieldMapImport.xul",
+ "",
+ "chrome,modal,titlebar",
+ { fieldMap: map,
+ addInterface: gAddInterface,
+ result: result });
+
+ if (!result.ok)
+ return false;
+ }
+
+ if (gAddInterface.WantsProgress()) {
+ if (gAddInterface.BeginImport(success, error)) {
+ gProgressInfo.importInterface = gAddInterface;
+ // intervalState = setInterval(ContinueImport, 100);
+ return true;
+ }
+ return false;
+ }
+
+ return gAddInterface.BeginImport(success, error);
+}
+
+/*
+ Import filters from a specific module.
+ Returns false if it failed and true if it succeeded.
+ An error string is returned as error.value.
+*/
+function ImportFilters(module, error)
+{
+ if (gProgressInfo.importInterface || gProgressInfo.intervalState) {
+ error.data = gImportMsgsBundle.getString('ImportAlreadyInProgress');
+ return false;
+ }
+
+ gProgressInfo.importSuccess = false;
+
+ var filtersInterface = module.GetImportInterface("filters");
+ if (!(filtersInterface instanceof Ci.nsIImportFilters)) {
+ error.data = gImportMsgsBundle.getString('ImportFiltersBadModule');
+ return false;
+ }
+
+ return filtersInterface.Import(error);
+}
+
+/*
+ Import feeds.
+*/
+function ImportFeeds()
+{
+ // Get file to open from filepicker.
+ let openFile = FeedSubscriptions.opmlPickOpenFile();
+ if (!openFile)
+ return false;
+
+ let acctName;
+ let acctNewExist = gFeedsBundle.getString("ImportFeedsExisting");
+ let fileName = openFile.path;
+ let server = document.getElementById("moduleList").selectedItem.server;
+ gNewFeedAcctCreated = false;
+
+ if (!server)
+ {
+ // Create a new Feeds account.
+ acctName = document.getElementById("acctName").value;
+ server = FeedUtils.createRssAccount(acctName).incomingServer;
+ acctNewExist = gFeedsBundle.getString("ImportFeedsNew");
+ gNewFeedAcctCreated = true;
+ }
+
+ acctName = server.rootFolder.prettyName;
+
+ let callback = function(aStatusReport, aLastFolder , aFeedWin)
+ {
+ let message = gFeedsBundle.getFormattedString("ImportFeedsDone",
+ [fileName, acctNewExist, acctName]);
+ ShowImportResultsRaw(message + " " + aStatusReport, null, true);
+ document.getElementById("back").removeAttribute("disabled");
+
+ let subscriptionsWindow = Services.wm.getMostRecentWindow("Mail:News-BlogSubscriptions");
+ if (subscriptionsWindow)
+ {
+ let feedWin = subscriptionsWindow.FeedSubscriptions;
+ if (aLastFolder)
+ feedWin.FolderListener.folderAdded(aLastFolder);
+ feedWin.mActionMode = null;
+ feedWin.updateButtons(feedWin.mView.currentItem);
+ feedWin.clearStatusInfo();
+ feedWin.updateStatusItem("statusText", aStatusReport);
+ }
+ }
+
+ if (!FeedSubscriptions.importOPMLFile(openFile, server, callback))
+ return false;
+
+ let subscriptionsWindow = Services.wm.getMostRecentWindow("Mail:News-BlogSubscriptions");
+ if (subscriptionsWindow)
+ {
+ let feedWin = subscriptionsWindow.FeedSubscriptions;
+ feedWin.mActionMode = feedWin.kImportingOPML;
+ feedWin.updateButtons(null);
+ let statusReport = gFeedsBundle.getString("subscribe-loading");
+ feedWin.updateStatusItem("statusText", statusReport);
+ feedWin.updateStatusItem("progressMeter", "?");
+ }
+
+ return true;
+}
+
+function SwitchType(newType)
+{
+ if (gImportType == newType)
+ return;
+
+ gImportType = newType;
+ gProgressInfo.importType = newType;
+
+ SetUpImportType();
+
+ SetDivText('description', "");
+}
+
+
+function next()
+{
+ var deck = document.getElementById("stateDeck");
+ switch (deck.selectedIndex) {
+ case "0":
+ let backButton = document.getElementById("back");
+ backButton.removeAttribute("disabled");
+ let radioGroup = document.getElementById("importFields");
+
+ if (radioGroup.value == "all")
+ {
+ let args = { closeMigration: true };
+ let SEAMONKEY_ID = "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}";
+ if (Services.appinfo.ID == SEAMONKEY_ID) {
+ window.openDialog("chrome://communicator/content/migration/migration.xul",
+ "", "chrome,dialog,modal,centerscreen");
+ } else {
+ // Running as Thunderbird or its clone.
+ window.openDialog("chrome://messenger/content/migration/migration.xul",
+ "", "chrome,dialog,modal,centerscreen", null, null, null, args);
+ }
+ if (args.closeMigration)
+ close();
+ }
+ else
+ {
+ SwitchType(radioGroup.value);
+ deck.selectedIndex = 1;
+ document.getElementById("modulesFound").selectedIndex =
+ (document.getElementById("moduleList").itemCount > 0) ? 0 : 1;
+ SelectFirstItem();
+ enableAdvance();
+ }
+ break;
+ case "1":
+ ImportDialogOKButton();
+ break;
+ case "3":
+ close();
+ break;
+ }
+}
+
+function SelectFirstItem()
+{
+ var listbox = document.getElementById("moduleList");
+ if ((listbox.selectedIndex == -1) && (listbox.itemCount > 0))
+ listbox.selectedIndex = 0;
+ ImportSelectionChanged();
+}
+
+function enableAdvance()
+{
+ var listbox = document.getElementById("moduleList");
+ var nextButton = document.getElementById("forward");
+ if (listbox.selectedCount > 0)
+ nextButton.removeAttribute("disabled");
+ else
+ nextButton.setAttribute("disabled", "true");
+}
+
+function back()
+{
+ var deck = document.getElementById("stateDeck");
+ var backButton = document.getElementById("back");
+ var nextButton = document.getElementById("forward");
+ switch (deck.selectedIndex) {
+ case "1":
+ backButton.setAttribute("disabled", "true");
+ nextButton.label = nextButton.getAttribute("nextval");
+ nextButton.removeAttribute("disabled");
+ deck.selectedIndex = 0;
+ break;
+ case "3":
+ // Clear out the results box.
+ let results = document.getElementById("results");
+ while (results.hasChildNodes())
+ results.lastChild.remove();
+
+ // Reset the next button.
+ nextButton.label = nextButton.getAttribute("nextval");
+ nextButton.removeAttribute("disabled");
+
+ // Enable the cancel button again.
+ document.getElementById("cancel").removeAttribute("disabled");
+
+ // If a new Feed account has been created, rebuild the list.
+ if (gNewFeedAcctCreated)
+ ListFeedAccounts();
+
+ // Now go back to the second page.
+ deck.selectedIndex = 1;
+ break;
+ }
+}
diff --git a/mailnews/import/content/importDialog.xul b/mailnews/import/content/importDialog.xul
new file mode 100644
index 000000000..383585f83
--- /dev/null
+++ b/mailnews/import/content/importDialog.xul
@@ -0,0 +1,143 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % importDTD SYSTEM "chrome://messenger/locale/importDialog.dtd" >
+%importDTD;
+]>
+
+<window xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="OnLoadImportDialog()"
+#ifdef XP_MACOSX
+ style="width: &window.macWidth; !important;"
+#else
+ style="width: &window.width; !important;"
+#endif
+ title="&importDialog.windowTitle;">
+
+ <stringbundle id="bundle_importMsgs" src="chrome://messenger/locale/importMsgs.properties"/>
+ <stringbundle id="bundle_addressbook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+ <stringbundle id="bundle_vcardImportMsgs" src="chrome://messenger/locale/vCardImportMsgs.properties"/>
+ <stringbundle id="bundle_feeds" src="chrome://messenger-newsblog/locale/newsblog.properties"/>
+ <script type="application/javascript" src="chrome://messenger/content/importDialog.js"/>
+ <script type="application/javascript" src="chrome://messenger-newsblog/content/feed-subscriptions.js"/>
+
+ <keyset id="dialogKeys"/>
+
+ <hbox class="box-header" id="header"
+ title="&importTitle.label;"
+ description="&importShortDesc.label;"/>
+
+ <deck id="stateDeck" selectedIndex="0" style="min-height: 30em">
+ <vbox class="wizard-box">
+ <description>&importDescription1.label;</description>
+ <description>&importDescription2.label;</description>
+ <separator/>
+ <radiogroup id="importFields">
+ <radio id="allRadio"
+ value="all"
+ label="&importAll.label;"
+ accesskey="&importAll.accesskey;"/>
+ <separator/>
+ <label control="importFields">&select.label;</label>
+ <separator class="thin"/>
+ <vbox class="indent">
+ <radio id="addressbookRadio"
+ value="addressbook"
+ label="&importAddressbook.label;"
+ accesskey="&importAddressbook.accesskey;"/>
+ <radio id="mailRadio"
+ value="mail"
+ label="&importMail.label;"
+ accesskey="&importMail.accesskey;"/>
+ <radio id="feedsRadio"
+ value="feeds"
+ label="&importFeeds.label;"
+ accesskey="&importFeeds.accesskey;"/>
+ <radio id="settingsRadio"
+ value="settings"
+ label="&importSettings.label;"
+ accesskey="&importSettings.accesskey;"/>
+ <radio id="filtersRadio"
+ value="filters"
+ label="&importFilters.label;"
+ accesskey="&importFilters.accesskey;"/>
+ </vbox>
+ </radiogroup>
+ </vbox>
+ <vbox class="wizard-box">
+ <deck id="modulesFound"
+ selectedIndex="0">
+ <vbox>
+ <deck id="selectDescriptionDeck"
+ selectedIndex="0">
+ <label control="moduleList"
+ value="&selectDescription.label;"
+ accesskey="&selectDescription.accesskey;"/>
+ <label control="moduleList"
+ value="&selectDescriptionB.label;"
+ accesskey="&selectDescription.accesskey;"/>
+ </deck>
+ <listbox id="moduleList" flex="3"
+ onselect="ImportSelectionChanged(); enableAdvance();"/>
+ </vbox>
+ <label>&noModulesFound.label;</label>
+ </deck>
+ <grid flex="1">
+ <columns><column flex="1"/></columns>
+ <rows>
+ <row>
+ <description control="moduleList" id="description" class="box-padded"/>
+ </row>
+ <row>
+ <hbox id="acctName-box" flex="1" style="visibility: hidden;">
+ <label control="acctName" class="box-padded"
+ accesskey="&acctName.accesskey;"
+ value="&acctName.label;"/>
+ <textbox id="acctName" clickSelectsAll="true"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ <vbox class="wizard-box">
+ <spacer flex="1"/>
+ <groupbox>
+ <caption id="progressTitle" label="&title.label;"/>
+ <label class="indent" id="progressStatus" value="&processing.label;"/>
+ <vbox class="box-padded">
+ <progressmeter id="progressMeter" mode="determined" value="5"/>
+ </vbox>
+ </groupbox>
+ </vbox>
+ <vbox class="wizard-box">
+ <description id="status"/>
+ <hbox style="overflow: auto" class="inset" flex="1">
+ <description id="results" flex="1"/>
+ </hbox>
+ </vbox>
+ </deck>
+
+ <separator/>
+
+ <separator class="groove"/>
+
+ <hbox class="box-padded">
+ <spacer flex="1"/>
+ <button id="back" label="&back.label;" disabled="true"
+ oncommand="back();"/>
+ <button id="forward" label="&forward.label;" nextval="&forward.label;" finishedval="&finish.label;"
+ oncommand="next();"/>
+ <separator orient="vertical"/>
+ <button id="cancel" label="&cancel.label;"
+ oncommand="close();"/>
+ </hbox>
+
+</window>
diff --git a/mailnews/import/outlook/src/MapiApi.cpp b/mailnews/import/outlook/src/MapiApi.cpp
new file mode 100644
index 000000000..d23b44dd1
--- /dev/null
+++ b/mailnews/import/outlook/src/MapiApi.cpp
@@ -0,0 +1,1937 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MapiDbgLog.h"
+#include "MapiApi.h"
+
+#include <sstream>
+#include "rtfMailDecoder.h"
+
+#include "prprf.h"
+#include "nsMemory.h"
+#include "nsMsgUtils.h"
+#include "nsUnicharUtils.h"
+
+int CMapiApi::m_clients = 0;
+BOOL CMapiApi::m_initialized = false;
+nsTArray<CMsgStore*> *CMapiApi::m_pStores = NULL;
+LPMAPISESSION CMapiApi::m_lpSession = NULL;
+LPMDB CMapiApi::m_lpMdb = NULL;
+HRESULT CMapiApi::m_lastError;
+char16_t * CMapiApi::m_pUniBuff = NULL;
+int CMapiApi::m_uniBuffLen = 0;
+/*
+Type: 1, name: Calendar, class: IPF.Appointment
+Type: 1, name: Contacts, class: IPF.Contact
+Type: 1, name: Journal, class: IPF.Journal
+Type: 1, name: Notes, class: IPF.StickyNote
+Type: 1, name: Tasks, class: IPF.Task
+Type: 1, name: Drafts, class: IPF.Note
+*/
+
+HINSTANCE CMapiApi::m_hMapi32 = NULL;
+
+LPMAPIUNINITIALIZE gpMapiUninitialize = NULL;
+LPMAPIINITIALIZE gpMapiInitialize = NULL;
+LPMAPIALLOCATEBUFFER gpMapiAllocateBuffer = NULL;
+LPMAPIFREEBUFFER gpMapiFreeBuffer = NULL;
+LPMAPILOGONEX gpMapiLogonEx = NULL;
+LPOPENSTREAMONFILE gpMapiOpenStreamOnFile = NULL;
+
+typedef HRESULT (STDMETHODCALLTYPE WRAPCOMPRESSEDRTFSTREAM) (
+ LPSTREAM lpCompressedRTFStream, ULONG ulFlags, LPSTREAM FAR *lpUncompressedRTFStream);
+typedef WRAPCOMPRESSEDRTFSTREAM *LPWRAPCOMPRESSEDRTFSTREAM;
+LPWRAPCOMPRESSEDRTFSTREAM gpWrapCompressedRTFStream = NULL;
+
+// WrapCompressedRTFStreamEx related stuff - see http://support.microsoft.com/kb/839560
+typedef struct {
+ ULONG size;
+ ULONG ulFlags;
+ ULONG ulInCodePage;
+ ULONG ulOutCodePage;
+} RTF_WCSINFO;
+typedef struct {
+ ULONG size;
+ ULONG ulStreamFlags;
+} RTF_WCSRETINFO;
+
+typedef HRESULT (STDMETHODCALLTYPE WRAPCOMPRESSEDRTFSTREAMEX) (
+ LPSTREAM lpCompressedRTFStream, CONST RTF_WCSINFO * pWCSInfo,
+ LPSTREAM * lppUncompressedRTFStream, RTF_WCSRETINFO * pRetInfo);
+typedef WRAPCOMPRESSEDRTFSTREAMEX *LPWRAPCOMPRESSEDRTFSTREAMEX;
+LPWRAPCOMPRESSEDRTFSTREAMEX gpWrapCompressedRTFStreamEx = NULL;
+
+BOOL CMapiApi::LoadMapiEntryPoints(void)
+{
+ if (!(gpMapiUninitialize = (LPMAPIUNINITIALIZE) GetProcAddress(
+ m_hMapi32, "MAPIUninitialize")))
+ return FALSE;
+ if (!(gpMapiInitialize = (LPMAPIINITIALIZE) GetProcAddress(
+ m_hMapi32, "MAPIInitialize")))
+ return FALSE;
+ if (!(gpMapiAllocateBuffer = (LPMAPIALLOCATEBUFFER) GetProcAddress(
+ m_hMapi32, "MAPIAllocateBuffer")))
+ return FALSE;
+ if (!(gpMapiFreeBuffer = (LPMAPIFREEBUFFER) GetProcAddress(
+ m_hMapi32, "MAPIFreeBuffer")))
+ return FALSE;
+ if (!(gpMapiLogonEx = (LPMAPILOGONEX) GetProcAddress(m_hMapi32,
+ "MAPILogonEx")))
+ return FALSE;
+ if (!(gpMapiOpenStreamOnFile = (LPOPENSTREAMONFILE) GetProcAddress(
+ m_hMapi32, "OpenStreamOnFile")))
+ return FALSE;
+
+ // Available from the Outlook 2002 post-SP3 hotfix (http://support.microsoft.com/kb/883924/)
+ // Exported by msmapi32.dll; so it's unavailable to us using mapi32.dll
+ gpWrapCompressedRTFStreamEx = (LPWRAPCOMPRESSEDRTFSTREAMEX) GetProcAddress(
+ m_hMapi32, "WrapCompressedRTFStreamEx");
+ // Available always
+ gpWrapCompressedRTFStream = (LPWRAPCOMPRESSEDRTFSTREAM) GetProcAddress(
+ m_hMapi32, "WrapCompressedRTFStream");
+
+ return TRUE;
+}
+
+// Gets the PR_RTF_COMPRESSED tag property
+// Codepage is used only if the WrapCompressedRTFStreamEx is available
+BOOL CMapiApi::GetRTFPropertyDecodedAsUTF16(LPMAPIPROP pProp, nsString& val,
+ unsigned long& nativeBodyType,
+ unsigned long codepage)
+{
+ if (!m_hMapi32 || !(gpWrapCompressedRTFStreamEx || gpWrapCompressedRTFStream))
+ return FALSE; // Fallback to the default processing
+
+ LPSTREAM icstream = 0; // for the compressed stream
+ LPSTREAM iunstream = 0; // for the uncompressed stream
+ HRESULT hr = pProp->OpenProperty(PR_RTF_COMPRESSED,
+ &IID_IStream, STGM_READ | STGM_DIRECT,
+ 0, (LPUNKNOWN *)&icstream);
+ if (HR_FAILED(hr))
+ return FALSE;
+
+ if (gpWrapCompressedRTFStreamEx) { // Impossible - we use mapi32.dll!
+ RTF_WCSINFO wcsinfo = {0};
+ RTF_WCSRETINFO retinfo = {0};
+
+ retinfo.size = sizeof(RTF_WCSRETINFO);
+
+ wcsinfo.size = sizeof(RTF_WCSINFO);
+ wcsinfo.ulFlags = MAPI_NATIVE_BODY;
+ wcsinfo.ulInCodePage = codepage;
+ wcsinfo.ulOutCodePage = CP_UTF8;
+
+ if(HR_SUCCEEDED(hr = gpWrapCompressedRTFStreamEx(icstream, &wcsinfo,
+ &iunstream, &retinfo)))
+ nativeBodyType = retinfo.ulStreamFlags;
+ }
+ else { // mapi32.dll
+ gpWrapCompressedRTFStream(icstream,0,&iunstream);
+ }
+ icstream->Release();
+
+ if(iunstream) { // Succeeded
+ std::string streamData;
+ // Stream.Stat doesn't work for this stream!
+ bool done = false;
+ while (!done) {
+ // I think 10K is a good guess to minimize the number of reads while keeping memory usage low
+ const int bufsize = 10240;
+ char buf[bufsize];
+ ULONG read;
+ hr = iunstream->Read(buf, bufsize, &read);
+ done = (read < bufsize) || (hr != S_OK);
+ if (read)
+ streamData.append(buf, read);
+ }
+ iunstream->Release();
+ // if rtf -> convert to plain text.
+ if (!gpWrapCompressedRTFStreamEx ||
+ (nativeBodyType==MAPI_NATIVE_BODY_TYPE_RTF)) {
+ std::stringstream s(streamData);
+ CRTFMailDecoder decoder;
+ DecodeRTF(s, decoder);
+ if (decoder.mode() == CRTFMailDecoder::mHTML)
+ nativeBodyType = MAPI_NATIVE_BODY_TYPE_HTML;
+ else if (decoder.mode() == CRTFMailDecoder::mText)
+ nativeBodyType = MAPI_NATIVE_BODY_TYPE_PLAINTEXT;
+ else
+ nativeBodyType = MAPI_NATIVE_BODY_TYPE_RTF;
+ val.Assign(decoder.text(), decoder.textSize());
+ }
+ else { // WrapCompressedRTFStreamEx available and original type is not rtf
+ CopyUTF8toUTF16(nsDependentCString(streamData.c_str()), val);
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void CMapiApi::MAPIUninitialize(void)
+{
+ if (m_hMapi32 && gpMapiUninitialize)
+ (*gpMapiUninitialize)();
+}
+
+HRESULT CMapiApi::MAPIInitialize(LPVOID lpInit)
+{
+ return (m_hMapi32 && gpMapiInitialize) ? (*gpMapiInitialize)(lpInit) :
+ MAPI_E_NOT_INITIALIZED;
+}
+
+SCODE CMapiApi::MAPIAllocateBuffer(ULONG cbSize, LPVOID FAR * lppBuffer)
+{
+ return (m_hMapi32 && gpMapiAllocateBuffer) ?
+ (*gpMapiAllocateBuffer)(cbSize, lppBuffer) : MAPI_E_NOT_INITIALIZED;
+}
+
+ULONG CMapiApi::MAPIFreeBuffer(LPVOID lpBuff)
+{
+ return (m_hMapi32 && gpMapiFreeBuffer) ? (*gpMapiFreeBuffer)(lpBuff) :
+ MAPI_E_NOT_INITIALIZED;
+}
+
+HRESULT CMapiApi::MAPILogonEx(ULONG ulUIParam, LPTSTR lpszProfileName,
+ LPTSTR lpszPassword, FLAGS flFlags,
+ LPMAPISESSION FAR * lppSession)
+{
+ return (m_hMapi32 && gpMapiLogonEx) ?
+ (*gpMapiLogonEx)(ulUIParam, lpszProfileName, lpszPassword, flFlags, lppSession) :
+ MAPI_E_NOT_INITIALIZED;
+}
+
+HRESULT CMapiApi::OpenStreamOnFile(LPALLOCATEBUFFER lpAllocateBuffer,
+ LPFREEBUFFER lpFreeBuffer, ULONG ulFlags,
+ LPTSTR lpszFileName, LPTSTR lpszPrefix,
+ LPSTREAM FAR * lppStream)
+{
+ return (m_hMapi32 && gpMapiOpenStreamOnFile) ?
+ (*gpMapiOpenStreamOnFile)(lpAllocateBuffer, lpFreeBuffer, ulFlags,
+ lpszFileName, lpszPrefix, lppStream) :
+ MAPI_E_NOT_INITIALIZED;
+}
+
+void CMapiApi::FreeProws(LPSRowSet prows)
+{
+ ULONG irow;
+ if (!prows)
+ return;
+ for (irow = 0; irow < prows->cRows; ++irow)
+ MAPIFreeBuffer(prows->aRow[irow].lpProps);
+ MAPIFreeBuffer(prows);
+}
+
+BOOL CMapiApi::LoadMapi(void)
+{
+ if (m_hMapi32)
+ return TRUE;
+
+ HINSTANCE hInst = ::LoadLibrary("MAPI32.DLL");
+ if (!hInst)
+ return FALSE;
+ FARPROC pProc = GetProcAddress(hInst, "MAPIGetNetscapeVersion");
+ if (pProc) {
+ ::FreeLibrary(hInst);
+ hInst = ::LoadLibrary("MAPI32BAK.DLL");
+ if (!hInst)
+ return FALSE;
+ }
+
+ m_hMapi32 = hInst;
+ return LoadMapiEntryPoints();
+}
+
+void CMapiApi::UnloadMapi(void)
+{
+ if (m_hMapi32)
+ ::FreeLibrary(m_hMapi32);
+ m_hMapi32 = NULL;
+}
+
+CMapiApi::CMapiApi()
+{
+ m_clients++;
+ LoadMapi();
+ if (!m_pStores)
+ m_pStores = new nsTArray<CMsgStore*>();
+}
+
+CMapiApi::~CMapiApi()
+{
+ m_clients--;
+ if (!m_clients) {
+ HRESULT hr;
+
+ ClearMessageStores();
+ delete m_pStores;
+ m_pStores = NULL;
+
+ m_lpMdb = NULL;
+
+ if (m_lpSession) {
+ hr = m_lpSession->Logoff(NULL, 0, 0);
+ if (FAILED(hr)) {
+ MAPI_TRACE2("Logoff failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ }
+ m_lpSession->Release();
+ m_lpSession = NULL;
+ }
+
+ if (m_initialized) {
+ MAPIUninitialize();
+ m_initialized = FALSE;
+ }
+
+ UnloadMapi();
+
+ if (m_pUniBuff)
+ delete [] m_pUniBuff;
+ m_pUniBuff = NULL;
+ m_uniBuffLen = 0;
+ }
+}
+
+void CMapiApi::CStrToUnicode(const char *pStr, nsString& result)
+{
+ result.Truncate();
+ int wLen = MultiByteToWideChar(CP_ACP, 0, pStr, -1, wwc(m_pUniBuff), 0);
+ if (wLen >= m_uniBuffLen) {
+ if (m_pUniBuff)
+ delete [] m_pUniBuff;
+ m_pUniBuff = new char16_t[wLen + 64];
+ m_uniBuffLen = wLen + 64;
+ }
+ if (wLen) {
+ MultiByteToWideChar(CP_ACP, 0, pStr, -1, wwc(m_pUniBuff), m_uniBuffLen);
+ result = m_pUniBuff;
+ }
+}
+
+BOOL CMapiApi::Initialize(void)
+{
+ if (m_initialized)
+ return TRUE;
+
+ HRESULT hr;
+
+ hr = MAPIInitialize(NULL);
+
+ if (FAILED(hr)) {
+ MAPI_TRACE2("MAPI Initialize failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ m_initialized = TRUE;
+ MAPI_TRACE0("MAPI Initialized\n");
+
+ return TRUE;
+}
+
+BOOL CMapiApi::LogOn(void)
+{
+ if (!m_initialized) {
+ MAPI_TRACE0("Tried to LogOn before initializing MAPI\n");
+ return FALSE;
+ }
+
+ if (m_lpSession)
+ return TRUE;
+
+ HRESULT hr;
+
+ hr = MAPILogonEx( 0, // might need to be passed in HWND
+ NULL, // profile name, 64 char max (LPTSTR)
+ NULL, // profile password, 64 char max (LPTSTR)
+ // MAPI_NEW_SESSION | MAPI_NO_MAIL | MAPI_LOGON_UI | MAPI_EXPLICIT_PROFILE,
+ // MAPI_NEW_SESSION | MAPI_NO_MAIL | MAPI_LOGON_UI,
+ // MAPI_NO_MAIL | MAPI_LOGON_UI,
+ MAPI_NO_MAIL | MAPI_USE_DEFAULT | MAPI_EXTENDED | MAPI_NEW_SESSION,
+ &m_lpSession);
+
+ if (FAILED(hr)) {
+ m_lpSession = NULL;
+ MAPI_TRACE2("LogOn failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ MAPI_TRACE0("MAPI Logged on\n");
+ return TRUE;
+}
+
+class CGetStoreFoldersIter : public CMapiHierarchyIter {
+public:
+ CGetStoreFoldersIter(CMapiApi *pApi, CMapiFolderList& folders, int depth, BOOL isMail = TRUE);
+
+ virtual BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry);
+
+protected:
+ BOOL ExcludeFolderClass(const char16_t *pName);
+
+ BOOL m_isMail;
+ CMapiApi * m_pApi;
+ CMapiFolderList * m_pList;
+ int m_depth;
+};
+
+CGetStoreFoldersIter::CGetStoreFoldersIter(CMapiApi *pApi, CMapiFolderList& folders, int depth, BOOL isMail)
+{
+ m_pApi = pApi;
+ m_pList = &folders;
+ m_depth = depth;
+ m_isMail = isMail;
+}
+
+BOOL CGetStoreFoldersIter::ExcludeFolderClass(const char16_t *pName)
+{
+ BOOL bResult;
+ nsDependentString pNameStr(pName);
+ if (m_isMail) {
+ bResult = FALSE;
+ if (pNameStr.EqualsLiteral("IPF.Appointment"))
+ bResult = TRUE;
+ else if (pNameStr.EqualsLiteral("IPF.Contact"))
+ bResult = TRUE;
+ else if (pNameStr.EqualsLiteral("IPF.Journal"))
+ bResult = TRUE;
+ else if (pNameStr.EqualsLiteral("IPF.StickyNote"))
+ bResult = TRUE;
+ else if (pNameStr.EqualsLiteral("IPF.Task"))
+ bResult = TRUE;
+ // Skip IMAP folders
+ else if (pNameStr.EqualsLiteral("IPF.Imap"))
+ bResult = TRUE;
+ // else if (!stricmp(pName, "IPF.Note"))
+ // bResult = TRUE;
+ }
+ else {
+ bResult = TRUE;
+ if (pNameStr.EqualsLiteral("IPF.Contact"))
+ bResult = FALSE;
+ }
+
+ return bResult;
+}
+
+BOOL CGetStoreFoldersIter::HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry)
+{
+ if (oType == MAPI_FOLDER) {
+ LPMAPIFOLDER pFolder;
+ if (m_pApi->OpenEntry(cb, pEntry, (LPUNKNOWN *) &pFolder)) {
+ LPSPropValue pVal;
+ nsString name;
+
+ pVal = m_pApi->GetMapiProperty(pFolder, PR_CONTAINER_CLASS);
+ if (pVal)
+ m_pApi->GetStringFromProp(pVal, name);
+ else
+ name.Truncate();
+
+ if ((name.IsEmpty() && m_isMail) || (!ExcludeFolderClass(name.get()))) {
+ pVal = m_pApi->GetMapiProperty(pFolder, PR_DISPLAY_NAME);
+ m_pApi->GetStringFromProp(pVal, name);
+ CMapiFolder *pNewFolder = new CMapiFolder(name.get(), cb, pEntry, m_depth);
+ m_pList->AddItem(pNewFolder);
+
+ pVal = m_pApi->GetMapiProperty(pFolder, PR_FOLDER_TYPE);
+ MAPI_TRACE2("Type: %d, name: %s\n",
+ m_pApi->GetLongFromProp(pVal), name.get());
+ // m_pApi->ListProperties(pFolder);
+
+ CGetStoreFoldersIter nextIter(m_pApi, *m_pList, m_depth + 1, m_isMail);
+ m_pApi->IterateHierarchy(&nextIter, pFolder);
+ }
+ pFolder->Release();
+ }
+ else {
+ MAPI_TRACE0("GetStoreFolders - HandleHierarchyItem: Error opening folder entry.\n");
+ return FALSE;
+ }
+ }
+ else
+ MAPI_TRACE1("GetStoreFolders - HandleHierarchyItem: Unhandled ObjectType: %ld\n", oType);
+ return TRUE;
+}
+
+BOOL CMapiApi::GetStoreFolders(ULONG cbEid, LPENTRYID lpEid, CMapiFolderList& folders, int startDepth)
+{
+ // Fill in the array with the folders in the given store
+ if (!m_initialized || !m_lpSession) {
+ MAPI_TRACE0("MAPI not initialized for GetStoreFolders\n");
+ return FALSE;
+ }
+
+ m_lpMdb = NULL;
+
+ CMsgStore * pStore = FindMessageStore(cbEid, lpEid);
+ BOOL bResult = FALSE;
+ LPSPropValue pVal;
+
+ if (pStore && pStore->Open(m_lpSession, &m_lpMdb)) {
+ // Successful open, do the iteration of the store
+ pVal = GetMapiProperty(m_lpMdb, PR_IPM_SUBTREE_ENTRYID);
+ if (pVal) {
+ ULONG cbEntry;
+ LPENTRYID pEntry;
+ LPMAPIFOLDER lpSubTree = NULL;
+
+ if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) {
+ // Open up the folder!
+ bResult = OpenEntry(cbEntry, pEntry, (LPUNKNOWN *)&lpSubTree);
+ MAPIFreeBuffer(pEntry);
+ if (bResult && lpSubTree) {
+ // Iterate the subtree with the results going into the folder list
+ CGetStoreFoldersIter iterHandler(this, folders, startDepth);
+ bResult = IterateHierarchy(&iterHandler, lpSubTree);
+ lpSubTree->Release();
+ }
+ else {
+ MAPI_TRACE0("GetStoreFolders: Error opening sub tree.\n");
+ }
+ }
+ else {
+ MAPI_TRACE0("GetStoreFolders: Error getting entryID from sub tree property val.\n");
+ }
+ }
+ else {
+ MAPI_TRACE0("GetStoreFolders: Error getting sub tree property.\n");
+ }
+ }
+ else {
+ MAPI_TRACE0("GetStoreFolders: Error opening message store.\n");
+ }
+
+ return bResult;
+}
+
+BOOL CMapiApi::GetStoreAddressFolders(ULONG cbEid, LPENTRYID lpEid, CMapiFolderList& folders)
+{
+ // Fill in the array with the folders in the given store
+ if (!m_initialized || !m_lpSession) {
+ MAPI_TRACE0("MAPI not initialized for GetStoreAddressFolders\n");
+ return FALSE;
+ }
+
+ m_lpMdb = NULL;
+
+ CMsgStore * pStore = FindMessageStore(cbEid, lpEid);
+ BOOL bResult = FALSE;
+ LPSPropValue pVal;
+
+ if (pStore && pStore->Open(m_lpSession, &m_lpMdb)) {
+ // Successful open, do the iteration of the store
+ pVal = GetMapiProperty(m_lpMdb, PR_IPM_SUBTREE_ENTRYID);
+ if (pVal) {
+ ULONG cbEntry;
+ LPENTRYID pEntry;
+ LPMAPIFOLDER lpSubTree = NULL;
+
+ if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) {
+ // Open up the folder!
+ bResult = OpenEntry(cbEntry, pEntry, (LPUNKNOWN *)&lpSubTree);
+ MAPIFreeBuffer(pEntry);
+ if (bResult && lpSubTree) {
+ // Iterate the subtree with the results going into the folder list
+ CGetStoreFoldersIter iterHandler(this, folders, 1, FALSE);
+ bResult = IterateHierarchy(&iterHandler, lpSubTree);
+ lpSubTree->Release();
+ }
+ else {
+ MAPI_TRACE0("GetStoreAddressFolders: Error opening sub tree.\n");
+ }
+ }
+ else {
+ MAPI_TRACE0("GetStoreAddressFolders: Error getting entryID from sub tree property val.\n");
+ }
+ }
+ else {
+ MAPI_TRACE0("GetStoreAddressFolders: Error getting sub tree property.\n");
+ }
+ }
+ else
+ MAPI_TRACE0("GetStoreAddressFolders: Error opening message store.\n");
+
+ return bResult;
+}
+
+
+BOOL CMapiApi::OpenStore(ULONG cbEid, LPENTRYID lpEid, LPMDB *ppMdb)
+{
+ if (!m_lpSession) {
+ MAPI_TRACE0("OpenStore called before a session was opened\n");
+ return FALSE;
+ }
+
+ CMsgStore * pStore = FindMessageStore(cbEid, lpEid);
+ if (pStore && pStore->Open(m_lpSession, ppMdb))
+ return TRUE;
+ return FALSE;
+}
+
+
+BOOL CMapiApi::OpenEntry(ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN *ppOpen)
+{
+ if (!m_lpMdb) {
+ MAPI_TRACE0("OpenEntry called before the message store is open\n");
+ return FALSE;
+ }
+
+ return OpenMdbEntry(m_lpMdb, cbEntry, pEntryId, ppOpen);
+}
+
+BOOL CMapiApi::OpenMdbEntry(LPMDB lpMdb, ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN *ppOpen)
+{
+ ULONG ulObjType;
+ HRESULT hr;
+ hr = m_lpSession->OpenEntry(cbEntry,
+ pEntryId,
+ NULL,
+ 0,
+ &ulObjType,
+ (LPUNKNOWN *) ppOpen);
+ if (FAILED(hr)) {
+ MAPI_TRACE2("OpenMdbEntry failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+enum {
+ ieidPR_ENTRYID = 0,
+ ieidPR_OBJECT_TYPE,
+ ieidMax
+};
+
+static const SizedSPropTagArray(ieidMax, ptaEid)=
+{
+ ieidMax,
+ {
+ PR_ENTRYID,
+ PR_OBJECT_TYPE,
+ }
+};
+
+BOOL CMapiApi::IterateContents(CMapiContentIter *pIter, LPMAPIFOLDER pFolder, ULONG flags)
+{
+ // flags can be 0 or MAPI_ASSOCIATED
+ // MAPI_ASSOCIATED is usually used for forms and views
+
+ HRESULT hr;
+ LPMAPITABLE lpTable;
+ hr = pFolder->GetContentsTable(flags, &lpTable);
+ if (FAILED(hr)) {
+ MAPI_TRACE2("GetContentsTable failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ ULONG rowCount;
+ hr = lpTable->GetRowCount(0, &rowCount);
+ if (!rowCount) {
+ MAPI_TRACE0(" Empty Table\n");
+ }
+
+ hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0);
+ if (FAILED(hr)) {
+ lpTable->Release();
+ MAPI_TRACE2("SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
+ if (FAILED(hr)) {
+ lpTable->Release();
+ MAPI_TRACE2("SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ int cNumRows = 0;
+ LPSRowSet lpRow;
+ BOOL keepGoing = TRUE;
+ BOOL bResult = TRUE;
+ do {
+ lpRow = NULL;
+ hr = lpTable->QueryRows(1, 0, &lpRow);
+ if(HR_FAILED(hr)) {
+ MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ bResult = FALSE;
+ break;
+ }
+
+ if(lpRow) {
+ cNumRows = lpRow->cRows;
+ if (cNumRows) {
+ LPENTRYID lpEID = (LPENTRYID) lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb;
+ ULONG cbEID = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb;
+ ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul;
+ keepGoing = HandleContentsItem(oType, cbEID, lpEID);
+ MAPI_TRACE1(" ObjectType: %ld\n", oType);
+ }
+ FreeProws(lpRow);
+ }
+
+ } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing);
+
+ lpTable->Release();
+ return bResult;
+}
+
+BOOL CMapiApi::HandleContentsItem(ULONG oType, ULONG cb, LPENTRYID pEntry)
+{
+ if (oType == MAPI_MESSAGE) {
+ LPMESSAGE pMsg;
+ if (OpenEntry(cb, pEntry, (LPUNKNOWN *) &pMsg)) {
+ LPSPropValue pVal;
+ pVal = GetMapiProperty(pMsg, PR_SUBJECT);
+ ReportStringProp("PR_SUBJECT:", pVal);
+ pVal = GetMapiProperty(pMsg, PR_DISPLAY_BCC);
+ ReportStringProp("PR_DISPLAY_BCC:", pVal);
+ pVal = GetMapiProperty(pMsg, PR_DISPLAY_CC);
+ ReportStringProp("PR_DISPLAY_CC:", pVal);
+ pVal = GetMapiProperty(pMsg, PR_DISPLAY_TO);
+ ReportStringProp("PR_DISPLAY_TO:", pVal);
+ pVal = GetMapiProperty(pMsg, PR_MESSAGE_CLASS);
+ ReportStringProp("PR_MESSAGE_CLASS:", pVal);
+ ListProperties(pMsg);
+ pMsg->Release();
+ }
+ else {
+ MAPI_TRACE0(" Folder type - error opening\n");
+ }
+ }
+ else
+ MAPI_TRACE1(" ObjectType: %ld\n", oType);
+
+ return TRUE;
+}
+
+void CMapiApi::ListProperties(LPMAPIPROP lpProp, BOOL getValues)
+{
+ LPSPropTagArray pArray;
+ HRESULT hr = lpProp->GetPropList(0, &pArray);
+ if (FAILED(hr)) {
+ MAPI_TRACE0(" Unable to retrieve property list\n");
+ return;
+ }
+ ULONG count = 0;
+ LPMAPINAMEID FAR * lppPropNames;
+ SPropTagArray tagArray;
+ LPSPropTagArray lpTagArray = &tagArray;
+ tagArray.cValues = (ULONG)1;
+ nsCString desc;
+ for (ULONG i = 0; i < pArray->cValues; i++) {
+ GetPropTagName(pArray->aulPropTag[i], desc);
+ if (getValues) {
+ tagArray.aulPropTag[0] = pArray->aulPropTag[i];
+ hr = lpProp->GetNamesFromIDs(&lpTagArray, nullptr, 0, &count, &lppPropNames);
+ if (hr == S_OK)
+ MAPIFreeBuffer(lppPropNames);
+
+ LPSPropValue pVal = GetMapiProperty(lpProp, pArray->aulPropTag[i]);
+ if (pVal) {
+ desc += ", ";
+ ListPropertyValue(pVal, desc);
+ MAPIFreeBuffer(pVal);
+ }
+ }
+ MAPI_TRACE2(" Tag #%d: %s\n", (int) i, desc.get());
+ }
+
+ MAPIFreeBuffer(pArray);
+}
+
+ULONG CMapiApi::GetEmailPropertyTag(LPMAPIPROP lpProp, LONG nameID)
+{
+static GUID emailGUID = {
+ 0x00062004, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
+};
+
+ MAPINAMEID mapiNameID;
+ mapiNameID.lpguid = &emailGUID;
+ mapiNameID.ulKind = MNID_ID;
+ mapiNameID.Kind.lID = nameID;
+
+ LPMAPINAMEID lpMapiNames = &mapiNameID;
+ LPSPropTagArray lpMailTagArray = nullptr;
+
+ HRESULT result = lpProp->GetIDsFromNames(1L, &lpMapiNames, 0, &lpMailTagArray);
+ if (result == S_OK)
+ {
+ ULONG lTag = lpMailTagArray->aulPropTag[0];
+ MAPIFreeBuffer(lpMailTagArray);
+ return lTag;
+ }
+ else
+ return 0L;
+}
+
+BOOL CMapiApi::HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry)
+{
+ if (oType == MAPI_FOLDER) {
+ LPMAPIFOLDER pFolder;
+ if (OpenEntry(cb, pEntry, (LPUNKNOWN *) &pFolder)) {
+ LPSPropValue pVal;
+ pVal = GetMapiProperty(pFolder, PR_DISPLAY_NAME);
+ ReportStringProp("Folder name:", pVal);
+ IterateContents(NULL, pFolder);
+ IterateHierarchy(NULL, pFolder);
+ pFolder->Release();
+ }
+ else {
+ MAPI_TRACE0(" Folder type - error opening\n");
+ }
+ }
+ else
+ MAPI_TRACE1(" ObjectType: %ld\n", oType);
+
+ return TRUE;
+}
+
+BOOL CMapiApi::IterateHierarchy(CMapiHierarchyIter *pIter, LPMAPIFOLDER pFolder, ULONG flags)
+{
+ // flags can be CONVENIENT_DEPTH or 0
+ // CONVENIENT_DEPTH will return all depths I believe instead
+ // of just children
+ HRESULT hr;
+ LPMAPITABLE lpTable;
+ hr = pFolder->GetHierarchyTable(flags, &lpTable);
+ if (HR_FAILED(hr)) {
+ m_lastError = hr;
+ MAPI_TRACE2("IterateHierarchy: GetContentsTable failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ ULONG rowCount;
+ hr = lpTable->GetRowCount(0, &rowCount);
+ if (!rowCount) {
+ lpTable->Release();
+ return TRUE;
+ }
+
+ hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0);
+ if (HR_FAILED(hr)) {
+ m_lastError = hr;
+ lpTable->Release();
+ MAPI_TRACE2("IterateHierarchy: SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
+ if (HR_FAILED(hr)) {
+ m_lastError = hr;
+ lpTable->Release();
+ MAPI_TRACE2("IterateHierarchy: SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ int cNumRows = 0;
+ LPSRowSet lpRow;
+ BOOL keepGoing = TRUE;
+ BOOL bResult = TRUE;
+ do {
+ lpRow = NULL;
+ hr = lpTable->QueryRows(1, 0, &lpRow);
+
+ if(HR_FAILED(hr)) {
+ MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ m_lastError = hr;
+ bResult = FALSE;
+ break;
+ }
+
+ if(lpRow) {
+ cNumRows = lpRow->cRows;
+
+ if (cNumRows) {
+ LPENTRYID lpEntry = (LPENTRYID) lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb;
+ ULONG cb = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb;
+ ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul;
+
+ if (pIter)
+ keepGoing = pIter->HandleHierarchyItem(oType, cb, lpEntry);
+ else
+ keepGoing = HandleHierarchyItem(oType, cb, lpEntry);
+
+ }
+ FreeProws(lpRow);
+ }
+ } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing);
+
+ lpTable->Release();
+
+ if (bResult && !keepGoing)
+ bResult = FALSE;
+
+ return bResult;
+}
+
+
+enum {
+ itblPR_DISPLAY_NAME,
+ itblPR_ENTRYID,
+ itblMax
+};
+
+static const SizedSPropTagArray(itblMax, ptaTbl)=
+{
+ itblMax,
+ {
+ PR_DISPLAY_NAME,
+ PR_ENTRYID,
+ }
+};
+
+BOOL CMapiApi::IterateStores(CMapiFolderList& stores)
+{
+ stores.ClearAll();
+
+ if (!m_lpSession) {
+ MAPI_TRACE0("IterateStores called before session is open\n");
+ m_lastError = E_UNEXPECTED;
+ return FALSE;
+ }
+
+
+ HRESULT hr;
+
+ /* -- Some Microsoft sample code just to see if things are working --- *//*
+
+ ULONG cbEIDStore;
+ LPENTRYID lpEIDStore;
+
+ hr = HrMAPIFindDefaultMsgStore(m_lpSession, &cbEIDStore, &lpEIDStore);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE0("Default message store not found\n");
+ // MessageBoxW(NULL, L"Message Store Not Found", NULL, MB_OK);
+ }
+ else {
+ LPMDB lpStore;
+ MAPI_TRACE0("Default Message store FOUND\n");
+ hr = m_lpSession->OpenMsgStore(NULL, cbEIDStore,
+ lpEIDStore, NULL,
+ MDB_NO_MAIL | MDB_NO_DIALOG, &lpStore);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE1("Unable to open default message store: 0x%lx\n", hr);
+ }
+ else {
+ MAPI_TRACE0("Default message store OPENED\n");
+ lpStore->Release();
+ }
+ }
+ */
+
+
+
+ LPMAPITABLE lpTable;
+
+ hr = m_lpSession->GetMsgStoresTable(0, &lpTable);
+ if (FAILED(hr)) {
+ MAPI_TRACE0("GetMsgStoresTable failed\n");
+ m_lastError = hr;
+ return FALSE;
+ }
+
+
+ ULONG rowCount;
+ hr = lpTable->GetRowCount(0, &rowCount);
+ MAPI_TRACE1("MsgStores Table rowCount: %ld\n", rowCount);
+
+ hr = lpTable->SetColumns((LPSPropTagArray)&ptaTbl, 0);
+ if (FAILED(hr)) {
+ lpTable->Release();
+ MAPI_TRACE2("SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ m_lastError = hr;
+ return FALSE;
+ }
+
+ hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
+ if (FAILED(hr)) {
+ lpTable->Release();
+ MAPI_TRACE2("SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ m_lastError = hr;
+ return FALSE;
+ }
+
+ int cNumRows = 0;
+ LPSRowSet lpRow;
+ BOOL keepGoing = TRUE;
+ BOOL bResult = TRUE;
+ do {
+ lpRow = NULL;
+ hr = lpTable->QueryRows(1, 0, &lpRow);
+
+ if(HR_FAILED(hr)) {
+ MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ bResult = FALSE;
+ m_lastError = hr;
+ break;
+ }
+
+ if(lpRow) {
+ cNumRows = lpRow->cRows;
+
+ if (cNumRows) {
+ LPCTSTR lpStr = (LPCTSTR) lpRow->aRow[0].lpProps[itblPR_DISPLAY_NAME].Value.LPSZ;
+ LPENTRYID lpEID = (LPENTRYID) lpRow->aRow[0].lpProps[itblPR_ENTRYID].Value.bin.lpb;
+ ULONG cbEID = lpRow->aRow[0].lpProps[itblPR_ENTRYID].Value.bin.cb;
+
+ // In the future, GetStoreInfo needs to somehow return
+ // whether or not the store is from an IMAP server.
+ // Currently, GetStoreInfo opens the store and attempts
+ // to get the hierarchy tree. If the tree is empty or
+ // does not exist, then szContents will be zero. We'll
+ // assume that any store that doesn't have anything in
+ // it's hierarchy tree is not a store we want to import -
+ // there would be nothing to import from anyway!
+ // Currently, this does exclude IMAP server accounts
+ // which is the desired behaviour.
+
+ int strLen = strlen(lpStr);
+ char16_t * pwszStr = (char16_t *) moz_xmalloc((strLen + 1) * sizeof(WCHAR));
+ if (!pwszStr) {
+ // out of memory
+ FreeProws(lpRow);
+ lpTable->Release();
+ return FALSE;
+ }
+ ::MultiByteToWideChar(CP_ACP, 0, lpStr, strlen(lpStr) + 1, wwc(pwszStr), (strLen + 1) * sizeof(WCHAR));
+ CMapiFolder *pFolder = new CMapiFolder(pwszStr, cbEID, lpEID, 0, MAPI_STORE);
+ free(pwszStr);
+
+ long szContents = 1;
+ GetStoreInfo(pFolder, &szContents);
+
+ MAPI_TRACE1(" DisplayName: %s\n", lpStr);
+ if (szContents)
+ stores.AddItem(pFolder);
+ else {
+ delete pFolder;
+ MAPI_TRACE0(" ^^^^^ Not added to store list\n");
+ }
+
+ keepGoing = TRUE;
+ }
+ FreeProws(lpRow);
+ }
+ } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing);
+
+ lpTable->Release();
+
+ return bResult;
+}
+
+void CMapiApi::GetStoreInfo(CMapiFolder *pFolder, long *pSzContents)
+{
+ HRESULT hr;
+ LPMDB lpMdb;
+
+ if (pSzContents)
+ *pSzContents = 0;
+
+ if (!OpenStore(pFolder->GetCBEntryID(), pFolder->GetEntryID(), &lpMdb))
+ return;
+
+ LPSPropValue pVal;
+ /*
+ pVal = GetMapiProperty(lpMdb, PR_DISPLAY_NAME);
+ ReportStringProp(" Message store name:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_MDB_PROVIDER);
+ ReportUIDProp(" Message store provider:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_COMMENT);
+ ReportStringProp(" Message comment:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_ACCESS_LEVEL);
+ ReportLongProp(" Message store Access Level:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_STORE_SUPPORT_MASK);
+ ReportLongProp(" Message store support mask:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_STORE_STATE);
+ ReportLongProp(" Message store state:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_OBJECT_TYPE);
+ ReportLongProp(" Message store object type:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_VALID_FOLDER_MASK);
+ ReportLongProp(" Message store valid folder mask:", pVal);
+
+ pVal = GetMapiProperty(lpMdb, 0x8001001e);
+ ReportStringProp(" Message prop 0x8001001e:", pVal);
+
+ // This key appears to be the OMI Account Manager account that corresponds
+ // to this message store. This is important for IMAP accounts
+ // since we may not want to import messages from an IMAP store!
+ // Seems silly if you ask me!
+ // In order to test this, we'll need the registry key to look under to determine
+ // if it contains the "IMAP Server" value, if it does then we are an
+ // IMAP store, if not, then we are a non-IMAP store - which may always mean
+ // a regular store that should be imported.
+
+ pVal = GetMapiProperty(lpMdb, 0x80000003);
+ ReportLongProp(" Message prop 0x80000003:", pVal);
+
+ // ListProperties(lpMdb);
+ */
+
+ pVal = GetMapiProperty(lpMdb, PR_IPM_SUBTREE_ENTRYID);
+ if (pVal) {
+ ULONG cbEntry;
+ LPENTRYID pEntry;
+ LPMAPIFOLDER lpSubTree = NULL;
+
+ if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) {
+ // Open up the folder!
+ ULONG ulObjType;
+ hr = lpMdb->OpenEntry(cbEntry, pEntry, NULL, 0, &ulObjType, (LPUNKNOWN *) &lpSubTree);
+ MAPIFreeBuffer(pEntry);
+ if (SUCCEEDED(hr) && lpSubTree) {
+ // Find out if there are any contents in the
+ // tree.
+ LPMAPITABLE lpTable;
+ hr = lpSubTree->GetHierarchyTable(0, &lpTable);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE2("GetStoreInfo: GetHierarchyTable failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ }
+ else {
+ ULONG rowCount;
+ hr = lpTable->GetRowCount(0, &rowCount);
+ lpTable->Release();
+ if (SUCCEEDED(hr) && pSzContents)
+ *pSzContents = (long) rowCount;
+ }
+
+ lpSubTree->Release();
+ }
+ }
+ }
+}
+
+
+void CMapiApi::ClearMessageStores(void)
+{
+ if (m_pStores) {
+ CMsgStore * pStore;
+ for (size_t i = 0; i < m_pStores->Length(); i++) {
+ pStore = m_pStores->ElementAt(i);
+ delete pStore;
+ }
+ m_pStores->Clear();
+ }
+}
+
+void CMapiApi::AddMessageStore(CMsgStore *pStore)
+{
+ if (m_pStores)
+ m_pStores->AppendElement(pStore);
+}
+
+CMsgStore * CMapiApi::FindMessageStore(ULONG cbEid, LPENTRYID lpEid)
+{
+ if (!m_lpSession) {
+ MAPI_TRACE0("FindMessageStore called before session is open\n");
+ m_lastError = E_UNEXPECTED;
+ return NULL;
+ }
+
+ ULONG result;
+ HRESULT hr;
+ CMsgStore * pStore;
+ for (size_t i = 0; i < m_pStores->Length(); i++) {
+ pStore = m_pStores->ElementAt(i);
+ hr = m_lpSession->CompareEntryIDs(cbEid, lpEid, pStore->GetCBEntryID(), pStore->GetLPEntryID(),
+ 0, &result);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE2("CompareEntryIDs failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ m_lastError = hr;
+ return NULL;
+ }
+ if (result) {
+ return pStore;
+ }
+ }
+
+ pStore = new CMsgStore(cbEid, lpEid);
+ AddMessageStore(pStore);
+ return pStore;
+}
+
+// --------------------------------------------------------------------
+// Utility stuff
+// --------------------------------------------------------------------
+
+LPSPropValue CMapiApi::GetMapiProperty(LPMAPIPROP pProp, ULONG tag)
+{
+ if (!pProp)
+ return NULL;
+
+ int sz = CbNewSPropTagArray(1);
+ SPropTagArray *pTag = (SPropTagArray *) new char[sz];
+ pTag->cValues = 1;
+ pTag->aulPropTag[0] = tag;
+ LPSPropValue lpProp = NULL;
+ ULONG cValues = 0;
+ HRESULT hr = pProp->GetProps(pTag, 0, &cValues, &lpProp);
+ delete [] pTag;
+ if (HR_FAILED(hr) || (cValues != 1)) {
+ if (lpProp)
+ MAPIFreeBuffer(lpProp);
+ return NULL;
+ }
+ else {
+ if (PROP_TYPE(lpProp->ulPropTag) == PT_ERROR) {
+ if (lpProp->Value.l == MAPI_E_NOT_FOUND) {
+ MAPIFreeBuffer(lpProp);
+ lpProp = NULL;
+ }
+ }
+ }
+
+ return lpProp;
+}
+
+BOOL CMapiApi::IsLargeProperty(LPSPropValue pVal)
+{
+ return ((PROP_TYPE(pVal->ulPropTag) == PT_ERROR) && (pVal->Value.l == E_OUTOFMEMORY));
+}
+
+// The output buffer (result) must be freed with operator delete[]
+BOOL CMapiApi::GetLargeProperty(LPMAPIPROP pProp, ULONG tag, void** result)
+{
+ LPSTREAM lpStream;
+ HRESULT hr = pProp->OpenProperty(tag, &IID_IStream, 0, 0, (LPUNKNOWN *)&lpStream);
+ if (HR_FAILED(hr))
+ return FALSE;
+ STATSTG st;
+ BOOL bResult = TRUE;
+ hr = lpStream->Stat(&st, STATFLAG_NONAME);
+ if (HR_FAILED(hr))
+ bResult = FALSE;
+ else {
+ if (!st.cbSize.QuadPart)
+ st.cbSize.QuadPart = 1;
+ char *pVal = new char[ (int) st.cbSize.QuadPart + 2];
+ if (pVal) {
+ ULONG sz;
+ hr = lpStream->Read(pVal, (ULONG) st.cbSize.QuadPart, &sz);
+ if (HR_FAILED(hr)) {
+ bResult = FALSE;
+ delete[] pVal;
+ }
+ else {
+ // Just in case it's a UTF16 string
+ pVal[(int) st.cbSize.QuadPart] = pVal[(int) st.cbSize.QuadPart+1] = 0;
+ *result = pVal;
+ }
+ }
+ else
+ bResult = FALSE;
+ }
+
+ lpStream->Release();
+
+ return bResult;
+}
+
+BOOL CMapiApi::GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, nsCString& val)
+{
+ void* result;
+ if (!GetLargeProperty(pProp, tag, &result))
+ return FALSE;
+ if (PROP_TYPE(tag) == PT_UNICODE) // unicode string
+ LossyCopyUTF16toASCII(nsDependentString(static_cast<wchar_t*>(result)), val);
+ else // either PT_STRING8 or some other binary - use as is
+ val.Assign(static_cast<char*>(result));
+ delete[] result;
+ return TRUE;
+}
+
+BOOL CMapiApi::GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, nsString& val)
+{
+ void* result;
+ if (!GetLargeProperty(pProp, tag, &result))
+ return FALSE;
+ if (PROP_TYPE(tag) == PT_UNICODE) // We already get the unicode string
+ val.Assign(static_cast<wchar_t*>(result));
+ else // either PT_STRING8 or some other binary
+ CStrToUnicode(static_cast<char*>(result), val);
+ delete[] result;
+ return TRUE;
+}
+// If the value is a string, get it...
+BOOL CMapiApi::GetEntryIdFromProp(LPSPropValue pVal, ULONG& cbEntryId,
+ LPENTRYID& lpEntryId, BOOL delVal)
+{
+ if (!pVal)
+ return FALSE;
+
+ BOOL bResult = TRUE;
+ switch(PROP_TYPE(pVal->ulPropTag)) {
+ case PT_BINARY:
+ cbEntryId = pVal->Value.bin.cb;
+ MAPIAllocateBuffer(cbEntryId, (LPVOID *) &lpEntryId);
+ memcpy(lpEntryId, pVal->Value.bin.lpb, cbEntryId);
+ break;
+
+ default:
+ MAPI_TRACE0("EntryId not in BINARY prop value\n");
+ bResult = FALSE;
+ break;
+ }
+
+ if (pVal && delVal)
+ MAPIFreeBuffer(pVal);
+
+ return bResult;
+}
+
+BOOL CMapiApi::GetStringFromProp(LPSPropValue pVal, nsCString& val, BOOL delVal)
+{
+ BOOL bResult = TRUE;
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_STRING8))
+ val = pVal->Value.lpszA;
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE))
+ LossyCopyUTF16toASCII(nsDependentString(pVal->Value.lpszW), val);
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL))
+ val.Truncate();
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ val.Truncate();
+ bResult = FALSE;
+ }
+ else {
+ if (pVal) {
+ MAPI_TRACE1("GetStringFromProp: invalid value, expecting string - %d\n", (int) PROP_TYPE(pVal->ulPropTag));
+ }
+ else {
+ MAPI_TRACE0("GetStringFromProp: invalid value, expecting string, got null pointer\n");
+ }
+ val.Truncate();
+ bResult = FALSE;
+ }
+ if (pVal && delVal)
+ MAPIFreeBuffer(pVal);
+
+ return bResult;
+}
+
+BOOL CMapiApi::GetStringFromProp(LPSPropValue pVal, nsString& val, BOOL delVal)
+{
+ BOOL bResult = TRUE;
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_STRING8)) {
+ CStrToUnicode((const char *)pVal->Value.lpszA, val);
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE)) {
+ val = (char16_t *) pVal->Value.lpszW;
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) {
+ val.Truncate();
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ val.Truncate();
+ bResult = FALSE;
+ }
+ else {
+ if (pVal) {
+ MAPI_TRACE1("GetStringFromProp: invalid value, expecting string - %d\n", (int) PROP_TYPE(pVal->ulPropTag));
+ }
+ else {
+ MAPI_TRACE0("GetStringFromProp: invalid value, expecting string, got null pointer\n");
+ }
+ val.Truncate();
+ bResult = FALSE;
+ }
+ if (pVal && delVal)
+ MAPIFreeBuffer(pVal);
+
+ return bResult;
+}
+
+
+LONG CMapiApi::GetLongFromProp(LPSPropValue pVal, BOOL delVal)
+{
+ LONG val = 0;
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_LONG)) {
+ val = pVal->Value.l;
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) {
+ val = 0;
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ val = 0;
+ MAPI_TRACE0("GetLongFromProp: Error retrieving property\n");
+ }
+ else {
+ MAPI_TRACE0("GetLongFromProp: invalid value, expecting long\n");
+ }
+ if (pVal && delVal)
+ MAPIFreeBuffer(pVal);
+
+ return val;
+}
+
+
+void CMapiApi::ReportUIDProp(const char *pTag, LPSPropValue pVal)
+{
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_BINARY)) {
+ if (pVal->Value.bin.cb != 16) {
+ MAPI_TRACE1("%s - INVALID, expecting 16 bytes of binary data for UID\n", pTag);
+ }
+ else {
+ nsIID uid;
+ memcpy(&uid, pVal->Value.bin.lpb, 16);
+ char * pStr = uid.ToString();
+ if (pStr) {
+ MAPI_TRACE2("%s %s\n", pTag, (const char *)pStr);
+ NS_Free(pStr);
+ }
+ }
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) {
+ MAPI_TRACE1("%s {NULL}\n", pTag);
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ MAPI_TRACE1("%s {Error retrieving property}\n", pTag);
+ }
+ else {
+ MAPI_TRACE1("%s invalid value, expecting binary\n", pTag);
+ }
+ if (pVal)
+ MAPIFreeBuffer(pVal);
+}
+
+void CMapiApi::ReportLongProp(const char *pTag, LPSPropValue pVal)
+{
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_LONG)) {
+ nsCString num;
+ nsCString num2;
+
+ num.AppendInt((int32_t) pVal->Value.l);
+ num2.AppendInt((int32_t) pVal->Value.l, 16);
+ MAPI_TRACE3("%s %s, 0x%s\n", pTag, num, num2);
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) {
+ MAPI_TRACE1("%s {NULL}\n", pTag);
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ MAPI_TRACE1("%s {Error retrieving property}\n", pTag);
+ }
+ else {
+ MAPI_TRACE1("%s invalid value, expecting long\n", pTag);
+ }
+ if (pVal)
+ MAPIFreeBuffer(pVal);
+}
+
+void CMapiApi::ReportStringProp(const char *pTag, LPSPropValue pVal)
+{
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_TSTRING)) {
+ nsCString val((LPCTSTR) (pVal->Value.LPSZ));
+ MAPI_TRACE2("%s %s\n", pTag, val.get());
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) {
+ MAPI_TRACE1("%s {NULL}\n", pTag);
+ }
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ MAPI_TRACE1("%s {Error retrieving property}\n", pTag);
+ }
+ else {
+ MAPI_TRACE1("%s invalid value, expecting string\n", pTag);
+ }
+ if (pVal)
+ MAPIFreeBuffer(pVal);
+}
+
+void CMapiApi::GetPropTagName(ULONG tag, nsCString& s)
+{
+ char numStr[256];
+ PR_snprintf(numStr, 256, "0x%lx, %ld", tag, tag);
+ s = numStr;
+ switch(tag) {
+#include "mapitagstrs.cpp"
+ }
+ s += ", data: ";
+ switch(PROP_TYPE(tag)) {
+ case PT_UNSPECIFIED: s += "PT_UNSPECIFIED"; break;
+ case PT_NULL: s += "PT_NULL"; break;
+ case PT_I2: s += "PT_I2"; break;
+ case PT_LONG: s += "PT_LONG"; break;
+ case PT_R4: s += "PT_R4"; break;
+ case PT_DOUBLE: s += "PT_DOUBLE"; break;
+ case PT_CURRENCY: s += "PT_CURRENCY"; break;
+ case PT_APPTIME: s += "PT_APPTIME"; break;
+ case PT_ERROR: s += "PT_ERROR"; break;
+ case PT_BOOLEAN: s += "PT_BOOLEAN"; break;
+ case PT_OBJECT: s += "PT_OBJECT"; break;
+ case PT_I8: s += "PT_I8"; break;
+ case PT_STRING8: s += "PT_STRING8"; break;
+ case PT_UNICODE: s += "PT_UNICODE"; break;
+ case PT_SYSTIME: s += "PT_SYSTIME"; break;
+ case PT_CLSID: s += "PT_CLSID"; break;
+ case PT_BINARY: s += "PT_BINARY"; break;
+ case PT_MV_I2: s += "PT_MV_I2"; break;
+ case PT_MV_LONG: s += "PT_MV_LONG"; break;
+ case PT_MV_R4: s += "PT_MV_R4"; break;
+ case PT_MV_DOUBLE: s += "PT_MV_DOUBLE"; break;
+ case PT_MV_CURRENCY: s += "PT_MV_CURRENCY"; break;
+ case PT_MV_APPTIME: s += "PT_MV_APPTIME"; break;
+ case PT_MV_SYSTIME: s += "PT_MV_SYSTIME"; break;
+ case PT_MV_STRING8: s += "PT_MV_STRING8"; break;
+ case PT_MV_BINARY: s += "PT_MV_BINARY"; break;
+ case PT_MV_UNICODE: s += "PT_MV_UNICODE"; break;
+ case PT_MV_CLSID: s += "PT_MV_CLSID"; break;
+ case PT_MV_I8: s += "PT_MV_I8"; break;
+ default:
+ s += "Unknown";
+ }
+}
+
+void CMapiApi::ListPropertyValue(LPSPropValue pVal, nsCString& s)
+{
+ nsCString strVal;
+ char nBuff[64];
+
+ s += "value: ";
+ switch (PROP_TYPE(pVal->ulPropTag)) {
+ case PT_STRING8:
+ GetStringFromProp(pVal, strVal, FALSE);
+ if (strVal.Length() > 60) {
+ strVal.SetLength(60);
+ strVal += "...";
+ }
+ MsgReplaceSubstring(strVal, "\r", "\\r");
+ MsgReplaceSubstring(strVal, "\n", "\\n");
+ s += strVal;
+ break;
+ case PT_LONG:
+ s.AppendInt((int32_t) pVal->Value.l);
+ s += ", 0x";
+ s.AppendInt((int32_t) pVal->Value.l, 16);
+ s += nBuff;
+ break;
+ case PT_BOOLEAN:
+ if (pVal->Value.b)
+ s += "True";
+ else
+ s += "False";
+ break;
+ case PT_NULL:
+ s += "--NULL--";
+ break;
+ case PT_SYSTIME: {
+ /*
+ COleDateTime tm(pVal->Value.ft);
+ s += tm.Format();
+ */
+ s += "-- Figure out how to format time in mozilla, PT_SYSTIME --";
+ }
+ break;
+ default:
+ s += "?";
+ }
+}
+
+
+
+// -------------------------------------------------------------------
+// Folder list stuff
+// -------------------------------------------------------------------
+CMapiFolderList::CMapiFolderList()
+{
+}
+
+CMapiFolderList::~CMapiFolderList()
+{
+ ClearAll();
+}
+
+void CMapiFolderList::AddItem(CMapiFolder *pFolder)
+{
+ EnsureUniqueName(pFolder);
+ GenerateFilePath(pFolder);
+ m_array.AppendElement(pFolder);
+}
+
+void CMapiFolderList::ChangeName(nsString& name)
+{
+ if (name.IsEmpty()) {
+ name.AssignLiteral("1");
+ return;
+ }
+ char16_t lastC = name.Last();
+ if ((lastC >= '0') && (lastC <= '9')) {
+ lastC++;
+ if (lastC > '9') {
+ lastC = '1';
+ name.SetCharAt(lastC, name.Length() - 1);
+ name.AppendLiteral("0");
+ }
+ else {
+ name.SetCharAt(lastC, name.Length() - 1);
+ }
+ }
+ else {
+ name.AppendLiteral(" 2");
+ }
+}
+
+void CMapiFolderList::EnsureUniqueName(CMapiFolder *pFolder)
+{
+ // For everybody in the array before me with the SAME
+ // depth, my name must be unique
+ CMapiFolder * pCurrent;
+ int i;
+ BOOL done;
+ nsString name;
+ nsString cName;
+
+ pFolder->GetDisplayName(name);
+ do {
+ done = TRUE;
+ i = m_array.Length() - 1;
+ while (i >= 0) {
+ pCurrent = GetAt(i);
+ if (pCurrent->GetDepth() == pFolder->GetDepth()) {
+ pCurrent->GetDisplayName(cName);
+ if (cName.Equals(name, nsCaseInsensitiveStringComparator())) {
+ ChangeName(name);
+ pFolder->SetDisplayName(name.get());
+ done = FALSE;
+ break;
+ }
+ }
+ else if (pCurrent->GetDepth() < pFolder->GetDepth())
+ break;
+ i--;
+ }
+ } while (!done);
+}
+
+void CMapiFolderList::GenerateFilePath(CMapiFolder *pFolder)
+{
+ // A file path, includes all of my parent's path, plus mine
+ nsString name;
+ nsString path;
+ if (!pFolder->GetDepth()) {
+ pFolder->GetDisplayName(name);
+ pFolder->SetFilePath(name.get());
+ return;
+ }
+
+ CMapiFolder * pCurrent;
+ int i = m_array.Length() - 1;
+ while (i >= 0) {
+ pCurrent = GetAt(i);
+ if (pCurrent->GetDepth() == (pFolder->GetDepth() - 1)) {
+ pCurrent->GetFilePath(path);
+ path.AppendLiteral(".sbd\\");
+ pFolder->GetDisplayName(name);
+ path += name;
+ pFolder->SetFilePath(path.get());
+ return;
+ }
+ i--;
+ }
+ pFolder->GetDisplayName(name);
+ pFolder->SetFilePath(name.get());
+}
+
+void CMapiFolderList::ClearAll(void)
+{
+ CMapiFolder *pFolder;
+ for (size_t i = 0; i < m_array.Length(); i++) {
+ pFolder = GetAt(i);
+ delete pFolder;
+ }
+ m_array.Clear();
+}
+
+void CMapiFolderList::DumpList(void)
+{
+ CMapiFolder *pFolder;
+ nsString str;
+ int depth;
+ char prefix[256];
+
+ MAPI_TRACE0("Folder List ---------------------------------\n");
+ for (size_t i = 0; i < m_array.Length(); i++) {
+ pFolder = GetAt(i);
+ depth = pFolder->GetDepth();
+ pFolder->GetDisplayName(str);
+ depth *= 2;
+ if (depth > 255)
+ depth = 255;
+ memset(prefix, ' ', depth);
+ prefix[depth] = 0;
+#ifdef MAPI_DEBUG
+ char *ansiStr = ToNewCString(str);
+ MAPI_TRACE2("%s%s: ", prefix, ansiStr);
+ NS_Free(ansiStr);
+#endif
+ pFolder->GetFilePath(str);
+#ifdef MAPI_DEBUG
+ ansiStr = ToNewCString(str);
+ MAPI_TRACE2("depth=%d, filePath=%s\n", pFolder->GetDepth(), ansiStr);
+ NS_Free(ansiStr);
+#endif
+ }
+ MAPI_TRACE0("---------------------------------------------\n");
+}
+
+
+CMapiFolder::CMapiFolder()
+{
+ m_objectType = MAPI_FOLDER;
+ m_cbEid = 0;
+ m_lpEid = NULL;
+ m_depth = 0;
+ m_doImport = TRUE;
+}
+
+CMapiFolder::CMapiFolder(const char16_t *pDisplayName, ULONG cbEid, LPENTRYID lpEid, int depth, LONG oType)
+{
+ m_cbEid = 0;
+ m_lpEid = NULL;
+ SetDisplayName(pDisplayName);
+ SetEntryID(cbEid, lpEid);
+ SetDepth(depth);
+ SetObjectType(oType);
+ SetDoImport(TRUE);
+}
+
+CMapiFolder::CMapiFolder(const CMapiFolder *pCopyFrom)
+{
+ m_lpEid = NULL;
+ m_cbEid = 0;
+ SetDoImport(pCopyFrom->GetDoImport());
+ SetDisplayName(pCopyFrom->m_displayName.get());
+ SetObjectType(pCopyFrom->GetObjectType());
+ SetEntryID(pCopyFrom->GetCBEntryID(), pCopyFrom->GetEntryID());
+ SetDepth(pCopyFrom->GetDepth());
+ SetFilePath(pCopyFrom->m_mailFilePath.get());
+}
+
+CMapiFolder::~CMapiFolder()
+{
+ if (m_lpEid)
+ delete m_lpEid;
+}
+
+void CMapiFolder::SetEntryID(ULONG cbEid, LPENTRYID lpEid)
+{
+ if (m_lpEid)
+ delete m_lpEid;
+ m_lpEid = NULL;
+ m_cbEid = cbEid;
+ if (cbEid) {
+ m_lpEid = new BYTE[cbEid];
+ memcpy(m_lpEid, lpEid, cbEid);
+ }
+}
+
+// ---------------------------------------------------------------------
+// Message store stuff
+// ---------------------------------------------------------------------
+
+
+CMsgStore::CMsgStore(ULONG cbEid, LPENTRYID lpEid)
+{
+ m_lpEid = NULL;
+ m_lpMdb = NULL;
+ SetEntryID(cbEid, lpEid);
+}
+
+CMsgStore::~CMsgStore()
+{
+ if (m_lpEid)
+ delete m_lpEid;
+
+ if (m_lpMdb) {
+ ULONG flags = LOGOFF_NO_WAIT;
+ HRESULT hr = m_lpMdb->StoreLogoff(&flags);
+ m_lpMdb->Release();
+ m_lpMdb = NULL;
+ }
+}
+
+void CMsgStore::SetEntryID(ULONG cbEid, LPENTRYID lpEid)
+{
+ HRESULT hr;
+
+ if (m_lpEid)
+ delete m_lpEid;
+
+ m_lpEid = NULL;
+ if (cbEid) {
+ m_lpEid = new BYTE[cbEid];
+ memcpy(m_lpEid, lpEid, cbEid);
+ }
+ m_cbEid = cbEid;
+
+ if (m_lpMdb) {
+ ULONG flags = LOGOFF_NO_WAIT;
+ hr = m_lpMdb->StoreLogoff(&flags);
+ m_lpMdb->Release();
+ m_lpMdb = NULL;
+ }
+}
+
+BOOL CMsgStore::Open(LPMAPISESSION pSession, LPMDB *ppMdb)
+{
+ if (m_lpMdb) {
+ if (ppMdb)
+ *ppMdb = m_lpMdb;
+ return TRUE;
+ }
+
+ BOOL bResult = TRUE;
+ HRESULT hr = pSession->OpenMsgStore(NULL, m_cbEid, (LPENTRYID)m_lpEid, NULL, MDB_NO_MAIL, &m_lpMdb); // MDB pointer
+ if (HR_FAILED(hr)) {
+ m_lpMdb = NULL;
+ MAPI_TRACE2("OpenMsgStore failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ bResult = FALSE;
+ }
+
+ if (ppMdb)
+ *ppMdb = m_lpMdb;
+ return bResult;
+}
+
+
+
+// ------------------------------------------------------------
+// Contents Iterator
+// -----------------------------------------------------------
+
+
+CMapiFolderContents::CMapiFolderContents(LPMDB lpMdb, ULONG cbEid, LPENTRYID lpEid)
+{
+ m_lpMdb = lpMdb;
+ m_fCbEid = cbEid;
+ m_fLpEid = new BYTE[cbEid];
+ memcpy(m_fLpEid, lpEid, cbEid);
+ m_count = 0;
+ m_iterCount = 0;
+ m_failure = FALSE;
+ m_lastError = 0;
+ m_lpFolder = NULL;
+ m_lpTable = NULL;
+ m_lastLpEid = NULL;
+ m_lastCbEid = 0;
+}
+
+CMapiFolderContents::~CMapiFolderContents()
+{
+ if (m_lastLpEid)
+ delete m_lastLpEid;
+ delete m_fLpEid;
+ if (m_lpTable)
+ m_lpTable->Release();
+ if (m_lpFolder)
+ m_lpFolder->Release();
+}
+
+
+BOOL CMapiFolderContents::SetUpIter(void)
+{
+ // First, open up the MAPIFOLDER object
+ ULONG ulObjType;
+ HRESULT hr;
+ hr = m_lpMdb->OpenEntry(m_fCbEid, (LPENTRYID) m_fLpEid, NULL, 0, &ulObjType, (LPUNKNOWN *) &m_lpFolder);
+
+ if (FAILED(hr) || !m_lpFolder) {
+ m_lpFolder = NULL;
+ m_lastError = hr;
+ MAPI_TRACE2("CMapiFolderContents OpenEntry failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ if (ulObjType != MAPI_FOLDER) {
+ m_lastError = E_UNEXPECTED;
+ MAPI_TRACE0("CMapiFolderContents - bad object type, not a folder.\n");
+ return FALSE;
+ }
+
+
+ hr = m_lpFolder->GetContentsTable(0, &m_lpTable);
+ if (FAILED(hr) || !m_lpTable) {
+ m_lastError = hr;
+ m_lpTable = NULL;
+ MAPI_TRACE2("CMapiFolderContents - GetContentsTable failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ hr = m_lpTable->GetRowCount(0, &m_count);
+ if (FAILED(hr)) {
+ m_lastError = hr;
+ MAPI_TRACE0("CMapiFolderContents - GetRowCount failed\n");
+ return FALSE;
+ }
+
+ hr = m_lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0);
+ if (FAILED(hr)) {
+ m_lastError = hr;
+ MAPI_TRACE2("CMapiFolderContents - SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ hr = m_lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
+ if (FAILED(hr)) {
+ m_lastError = hr;
+ MAPI_TRACE2("CMapiFolderContents - SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+BOOL CMapiFolderContents::GetNext(ULONG *pcbEid, LPENTRYID *ppEid, ULONG *poType, BOOL *pDone)
+{
+ *pDone = FALSE;
+ if (m_failure)
+ return FALSE;
+ if (!m_lpFolder) {
+ if (!SetUpIter()) {
+ m_failure = TRUE;
+ return FALSE;
+ }
+ if (!m_count) {
+ *pDone = TRUE;
+ return TRUE;
+ }
+ }
+
+ int cNumRows = 0;
+ LPSRowSet lpRow = NULL;
+ HRESULT hr = m_lpTable->QueryRows(1, 0, &lpRow);
+
+ if(HR_FAILED(hr)) {
+ m_lastError = hr;
+ m_failure = TRUE;
+ MAPI_TRACE2("CMapiFolderContents - QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ if(lpRow) {
+ cNumRows = lpRow->cRows;
+ if (cNumRows) {
+ LPENTRYID lpEID = (LPENTRYID) lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb;
+ ULONG cbEID = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb;
+ ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul;
+
+ if (m_lastCbEid != cbEID) {
+ if (m_lastLpEid)
+ delete m_lastLpEid;
+ m_lastLpEid = new BYTE[cbEID];
+ m_lastCbEid = cbEID;
+ }
+ memcpy(m_lastLpEid, lpEID, cbEID);
+
+ *ppEid = (LPENTRYID) m_lastLpEid;
+ *pcbEid = cbEID;
+ *poType = oType;
+ }
+ else
+ *pDone = TRUE;
+ CMapiApi::FreeProws(lpRow);
+ }
+ else
+ *pDone = TRUE;
+
+ return TRUE;
+}
+
diff --git a/mailnews/import/outlook/src/MapiApi.h b/mailnews/import/outlook/src/MapiApi.h
new file mode 100644
index 000000000..8b59b80d8
--- /dev/null
+++ b/mailnews/import/outlook/src/MapiApi.h
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef MapiApi_h___
+#define MapiApi_h___
+
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsTArray.h"
+
+#include <stdio.h>
+
+#include <windows.h>
+#include <mapi.h>
+#include <mapix.h>
+#include <mapidefs.h>
+#include <mapicode.h>
+#include <mapitags.h>
+#include <mapiutil.h>
+// wabutil.h expects mapiutil to define _MAPIUTIL_H but it actually
+// defines _MAPIUTIL_H_
+#define _MAPIUTIL_H
+
+#ifndef PR_INTERNET_CPID
+#define PR_INTERNET_CPID (PROP_TAG(PT_LONG,0x3FDE))
+#endif
+#ifndef MAPI_NATIVE_BODY
+#define MAPI_NATIVE_BODY (0x00010000)
+#endif
+#ifndef MAPI_NATIVE_BODY_TYPE_RTF
+#define MAPI_NATIVE_BODY_TYPE_RTF (0x00000001)
+#endif
+#ifndef MAPI_NATIVE_BODY_TYPE_HTML
+#define MAPI_NATIVE_BODY_TYPE_HTML (0x00000002)
+#endif
+#ifndef MAPI_NATIVE_BODY_TYPE_PLAINTEXT
+#define MAPI_NATIVE_BODY_TYPE_PLAINTEXT (0x00000004)
+#endif
+#ifndef PR_BODY_HTML_A
+#define PR_BODY_HTML_A (PROP_TAG(PT_STRING8,0x1013))
+#endif
+#ifndef PR_BODY_HTML_W
+#define PR_BODY_HTML_W (PROP_TAG(PT_UNICODE,0x1013))
+#endif
+#ifndef PR_BODY_HTML
+#define PR_BODY_HTML (PROP_TAG(PT_TSTRING,0x1013))
+#endif
+
+class CMapiFolderList;
+class CMsgStore;
+class CMapiFolder;
+
+class CMapiContentIter {
+public:
+ virtual BOOL HandleContentItem(ULONG oType, ULONG cb, LPENTRYID pEntry) = 0;
+};
+
+class CMapiHierarchyIter {
+public:
+ virtual BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry) = 0;
+};
+
+class CMapiApi {
+public:
+ CMapiApi();
+ ~CMapiApi();
+
+ static BOOL LoadMapi(void);
+ static BOOL LoadMapiEntryPoints(void);
+ static void UnloadMapi(void);
+
+ static HINSTANCE m_hMapi32;
+
+ static void MAPIUninitialize(void);
+ static HRESULT MAPIInitialize(LPVOID lpInit);
+ static SCODE MAPIAllocateBuffer(ULONG cbSize, LPVOID FAR * lppBuffer);
+ static ULONG MAPIFreeBuffer(LPVOID lpBuff);
+ static HRESULT MAPILogonEx(ULONG ulUIParam, LPTSTR lpszProfileName, LPTSTR lpszPassword, FLAGS flFlags, LPMAPISESSION FAR * lppSession);
+ static HRESULT OpenStreamOnFile(LPALLOCATEBUFFER lpAllocateBuffer, LPFREEBUFFER lpFreeBuffer, ULONG ulFlags, LPTSTR lpszFileName, LPTSTR lpszPrefix, LPSTREAM FAR * lppStream);
+ static void FreeProws(LPSRowSet prows);
+
+
+ BOOL Initialize(void);
+ BOOL LogOn(void);
+
+ void AddMessageStore(CMsgStore *pStore);
+ void SetCurrentMsgStore(LPMDB lpMdb) { m_lpMdb = lpMdb;}
+
+ // Open any given entry from the current Message Store
+ BOOL OpenEntry(ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN *ppOpen);
+ static BOOL OpenMdbEntry(LPMDB lpMdb, ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN *ppOpen);
+
+ // Fill in the folders list with the hierarchy from the given
+ // message store.
+ BOOL GetStoreFolders(ULONG cbEid, LPENTRYID lpEid, CMapiFolderList& folders, int startDepth);
+ BOOL GetStoreAddressFolders(ULONG cbEid, LPENTRYID lpEid, CMapiFolderList& folders);
+ BOOL OpenStore(ULONG cbEid, LPENTRYID lpEid, LPMDB *ppMdb);
+
+ // Iteration
+ BOOL IterateStores(CMapiFolderList& list);
+ BOOL IterateContents(CMapiContentIter *pIter, LPMAPIFOLDER pFolder, ULONG flags = 0);
+ BOOL IterateHierarchy(CMapiHierarchyIter *pIter, LPMAPIFOLDER pFolder, ULONG flags = 0);
+
+ // Properties
+ static LPSPropValue GetMapiProperty(LPMAPIPROP pProp, ULONG tag);
+ // If delVal is true, functions will call CMapiApi::MAPIFreeBuffer on pVal.
+ static BOOL GetEntryIdFromProp(LPSPropValue pVal, ULONG& cbEntryId,
+ LPENTRYID& lpEntryId, BOOL delVal = TRUE);
+ static BOOL GetStringFromProp(LPSPropValue pVal, nsCString& val, BOOL delVal = TRUE);
+ static BOOL GetStringFromProp(LPSPropValue pVal, nsString& val, BOOL delVal = TRUE);
+ static LONG GetLongFromProp(LPSPropValue pVal, BOOL delVal = TRUE);
+ static BOOL GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, nsCString& val);
+ static BOOL GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, nsString& val);
+ static BOOL IsLargeProperty(LPSPropValue pVal);
+ static ULONG GetEmailPropertyTag(LPMAPIPROP lpProp, LONG nameID);
+
+ static BOOL GetRTFPropertyDecodedAsUTF16(LPMAPIPROP pProp, nsString& val,
+ unsigned long& nativeBodyType,
+ unsigned long codepage = 0);
+
+ // Debugging & reporting stuff
+ static void ListProperties(LPMAPIPROP lpProp, BOOL getValues = TRUE);
+ static void ListPropertyValue(LPSPropValue pVal, nsCString& s);
+
+protected:
+ BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry);
+ BOOL HandleContentsItem(ULONG oType, ULONG cb, LPENTRYID pEntry);
+ void GetStoreInfo(CMapiFolder *pFolder, long *pSzContents);
+
+ // array of available message stores, cached so that
+ // message stores are only opened once, preventing multiple
+ // logon's by the user if the store requires a logon.
+ CMsgStore * FindMessageStore(ULONG cbEid, LPENTRYID lpEid);
+ void ClearMessageStores(void);
+
+ static void CStrToUnicode(const char *pStr, nsString& result);
+
+ // Debugging & reporting stuff
+ static void GetPropTagName(ULONG tag, nsCString& s);
+ static void ReportStringProp(const char *pTag, LPSPropValue pVal);
+ static void ReportUIDProp(const char *pTag, LPSPropValue pVal);
+ static void ReportLongProp(const char *pTag, LPSPropValue pVal);
+
+
+private:
+ static int m_clients;
+ static BOOL m_initialized;
+ static nsTArray<CMsgStore*> * m_pStores;
+ static LPMAPISESSION m_lpSession;
+ static LPMDB m_lpMdb;
+ static HRESULT m_lastError;
+ static char16_t * m_pUniBuff;
+ static int m_uniBuffLen;
+
+ static BOOL GetLargeProperty(LPMAPIPROP pProp, ULONG tag, void** result);
+};
+
+class CMapiFolder {
+public:
+ CMapiFolder();
+ CMapiFolder(const CMapiFolder *pCopyFrom);
+ CMapiFolder(const char16_t *pDisplayName, ULONG cbEid, LPENTRYID lpEid, int depth, LONG oType = MAPI_FOLDER);
+ ~CMapiFolder();
+
+ void SetDoImport(BOOL doIt) { m_doImport = doIt;}
+ void SetObjectType(long oType) { m_objectType = oType;}
+ void SetDisplayName(const char16_t *pDisplayName) { m_displayName = pDisplayName;}
+ void SetEntryID(ULONG cbEid, LPENTRYID lpEid);
+ void SetDepth(int depth) { m_depth = depth;}
+ void SetFilePath(const char16_t *pFilePath) { m_mailFilePath = pFilePath;}
+
+ BOOL GetDoImport(void) const { return m_doImport;}
+ LONG GetObjectType(void) const { return m_objectType;}
+ void GetDisplayName(nsString& name) const { name = m_displayName;}
+ void GetFilePath(nsString& path) const { path = m_mailFilePath;}
+ BOOL IsStore(void) const { return m_objectType == MAPI_STORE;}
+ BOOL IsFolder(void) const { return m_objectType == MAPI_FOLDER;}
+ int GetDepth(void) const { return m_depth;}
+
+ LPENTRYID GetEntryID(ULONG *pCb = NULL) const { if (pCb) *pCb = m_cbEid; return (LPENTRYID) m_lpEid;}
+ ULONG GetCBEntryID(void) const { return m_cbEid;}
+
+private:
+ LONG m_objectType;
+ ULONG m_cbEid;
+ BYTE * m_lpEid;
+ nsString m_displayName;
+ int m_depth;
+ nsString m_mailFilePath;
+ BOOL m_doImport;
+
+};
+
+class CMapiFolderList {
+public:
+ CMapiFolderList();
+ ~CMapiFolderList();
+
+ void AddItem(CMapiFolder *pFolder);
+ CMapiFolder * GetItem(int index) { if ((index >= 0) && (index < (int)m_array.Length())) return GetAt(index); else return NULL;}
+ void ClearAll(void);
+
+ // Debugging and reporting
+ void DumpList(void);
+
+ CMapiFolder * GetAt(int index) { return m_array.ElementAt(index);}
+ int GetSize(void) { return m_array.Length();}
+
+protected:
+ void EnsureUniqueName(CMapiFolder *pFolder);
+ void GenerateFilePath(CMapiFolder *pFolder);
+ void ChangeName(nsString& name);
+
+private:
+ nsTArray<CMapiFolder*> m_array;
+};
+
+
+class CMsgStore {
+public:
+ CMsgStore(ULONG cbEid = 0, LPENTRYID lpEid = NULL);
+ ~CMsgStore();
+
+ void SetEntryID(ULONG cbEid, LPENTRYID lpEid);
+ BOOL Open(LPMAPISESSION pSession, LPMDB *ppMdb);
+
+ ULONG GetCBEntryID(void) { return m_cbEid;}
+ LPENTRYID GetLPEntryID(void) { return (LPENTRYID) m_lpEid;}
+
+private:
+ ULONG m_cbEid;
+ BYTE * m_lpEid;
+ LPMDB m_lpMdb;
+};
+
+
+class CMapiFolderContents {
+public:
+ CMapiFolderContents(LPMDB lpMdb, ULONG cbEID, LPENTRYID lpEid);
+ ~CMapiFolderContents();
+
+ BOOL GetNext(ULONG *pcbEid, LPENTRYID *ppEid, ULONG *poType, BOOL *pDone);
+
+ ULONG GetCount(void) { return m_count;}
+
+protected:
+ BOOL SetUpIter(void);
+
+private:
+ HRESULT m_lastError;
+ BOOL m_failure;
+ LPMDB m_lpMdb;
+ LPMAPIFOLDER m_lpFolder;
+ LPMAPITABLE m_lpTable;
+ ULONG m_fCbEid;
+ BYTE * m_fLpEid;
+ ULONG m_count;
+ ULONG m_iterCount;
+ BYTE * m_lastLpEid;
+ ULONG m_lastCbEid;
+};
+
+
+#endif /* MapiApi_h__ */
diff --git a/mailnews/import/outlook/src/MapiDbgLog.h b/mailnews/import/outlook/src/MapiDbgLog.h
new file mode 100644
index 000000000..668418c42
--- /dev/null
+++ b/mailnews/import/outlook/src/MapiDbgLog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MapiDbgLog_h___
+#define MapiDbgLog_h___
+
+/*
+#ifdef NS_DEBUG
+#define MAPI_DEBUG 1
+#endif
+*/
+
+#ifdef MAPI_DEBUG
+#include <stdio.h>
+
+#define MAPI_DUMP_STRING(x) printf("%s", (const char *)x)
+#define MAPI_TRACE0(x) printf(x)
+#define MAPI_TRACE1(x, y) printf(x, y)
+#define MAPI_TRACE2(x, y, z) printf(x, y, z)
+#define MAPI_TRACE3(x, y, z, a) printf(x, y, z, a)
+#define MAPI_TRACE4(x, y, z, a, b) printf(x, y, z, a, b)
+
+
+#else
+
+#define MAPI_DUMP_STRING(x)
+#define MAPI_TRACE0(x)
+#define MAPI_TRACE1(x, y)
+#define MAPI_TRACE2(x, y, z)
+#define MAPI_TRACE3(x, y, z, a)
+#define MAPI_TRACE4(x, y, z, a, b)
+
+#endif
+
+
+
+#endif /* MapiDbgLog_h___ */
+
diff --git a/mailnews/import/outlook/src/MapiMessage.cpp b/mailnews/import/outlook/src/MapiMessage.cpp
new file mode 100644
index 000000000..6b02db314
--- /dev/null
+++ b/mailnews/import/outlook/src/MapiMessage.cpp
@@ -0,0 +1,1473 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef INITGUID
+#define INITGUID
+#endif
+
+#ifndef USES_IID_IMessage
+#define USES_IID_IMessage
+#endif
+
+#include "nscore.h"
+#include <time.h>
+#include "nsStringGlue.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsMsgUtils.h"
+#include "nsMimeTypes.h"
+#include "nsIOutputStream.h"
+
+#include "nsMsgCompCID.h"
+#include "nsIMutableArray.h"
+#include "MapiDbgLog.h"
+#include "MapiApi.h"
+
+#include "MapiMimeTypes.h"
+
+#include "nsMsgI18N.h"
+#include "nsICharsetConverterManager.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "MapiMessage.h"
+
+#include "nsOutlookMail.h"
+
+// needed for the call the OpenStreamOnFile
+extern LPMAPIALLOCATEBUFFER gpMapiAllocateBuffer;
+extern LPMAPIFREEBUFFER gpMapiFreeBuffer;
+
+// Sample From line: From - 1 Jan 1965 00:00:00
+
+typedef const char * PC_S8;
+
+static const char * kWhitespace = "\b\t\r\n ";
+static const char * sFromLine = "From - ";
+static const char * sFromDate = "Mon Jan 1 00:00:00 1965";
+static const char * sDaysOfWeek[7] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static const char *sMonths[12] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+CMapiMessage::CMapiMessage(LPMESSAGE lpMsg)
+ : m_lpMsg(lpMsg), m_dldStateHeadersOnly(false), m_msgFlags(0)
+{
+ nsresult rv;
+ m_pIOService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return;
+
+ FetchHeaders();
+ if (ValidState()) {
+ BuildFromLine();
+ FetchFlags();
+ GetDownloadState();
+ if (FullMessageDownloaded()) {
+ FetchBody();
+ ProcessAttachments();
+ }
+ }
+}
+
+CMapiMessage::~CMapiMessage()
+{
+ ClearAttachments();
+ if (m_lpMsg)
+ m_lpMsg->Release();
+}
+
+void CMapiMessage::FormatDateTime(SYSTEMTIME& tm, nsCString& s, bool includeTZ)
+{
+ long offset = _timezone;
+ s += sDaysOfWeek[tm.wDayOfWeek];
+ s += ", ";
+ s.AppendInt((int32_t) tm.wDay);
+ s += " ";
+ s += sMonths[tm.wMonth - 1];
+ s += " ";
+ s.AppendInt((int32_t) tm.wYear);
+ s += " ";
+ int val = tm.wHour;
+ if (val < 10)
+ s += "0";
+ s.AppendInt((int32_t) val);
+ s += ":";
+ val = tm.wMinute;
+ if (val < 10)
+ s += "0";
+ s.AppendInt((int32_t) val);
+ s += ":";
+ val = tm.wSecond;
+ if (val < 10)
+ s += "0";
+ s.AppendInt((int32_t) val);
+ if (includeTZ) {
+ s += " ";
+ if (offset < 0) {
+ offset *= -1;
+ s += "+";
+ }
+ else
+ s += "-";
+ offset /= 60;
+ val = (int) (offset / 60);
+ if (val < 10)
+ s += "0";
+ s.AppendInt((int32_t) val);
+ val = (int) (offset % 60);
+ if (val < 10)
+ s += "0";
+ s.AppendInt((int32_t) val);
+ }
+}
+
+bool CMapiMessage::EnsureHeader(CMapiMessageHeaders::SpecialHeader special,
+ ULONG mapiTag)
+{
+ if (m_headers.Value(special))
+ return true;
+
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, mapiTag);
+ bool success = false;
+ if (pVal) {
+ if (PROP_TYPE(pVal->ulPropTag) == PT_STRING8) {
+ if (pVal->Value.lpszA && strlen(pVal->Value.lpszA)) {
+ m_headers.SetValue(special, pVal->Value.lpszA);
+ success = true;
+ }
+ }
+ else if (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) {
+ if (pVal->Value.lpszW && wcslen(pVal->Value.lpszW)) {
+ m_headers.SetValue(special, NS_ConvertUTF16toUTF8(pVal->Value.lpszW).get());
+ success = true;
+ }
+ }
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+
+ return success;
+}
+
+bool CMapiMessage::EnsureDate()
+{
+ if (m_headers.Value(CMapiMessageHeaders::hdrDate))
+ return true;
+
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_DELIVERY_TIME);
+ if (!pVal)
+ pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_CREATION_TIME);
+ if (pVal) {
+ SYSTEMTIME st;
+ // the following call returns UTC
+ ::FileTimeToSystemTime(&(pVal->Value.ft), &st);
+ CMapiApi::MAPIFreeBuffer(pVal);
+ // FormatDateTime would append the local time zone, so don't use it.
+ // Instead, we just append +0000 for GMT/UTC here.
+ nsCString str;
+ FormatDateTime(st, str, false);
+ str += " +0000";
+ m_headers.SetValue(CMapiMessageHeaders::hdrDate, str.get());
+ return true;
+ }
+
+ return false;
+}
+
+void CMapiMessage::BuildFromLine(void)
+{
+ m_fromLine = sFromLine;
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_CREATION_TIME);
+ if (pVal) {
+ SYSTEMTIME st;
+ ::FileTimeToSystemTime(&(pVal->Value.ft), &st);
+ CMapiApi::MAPIFreeBuffer(pVal);
+ FormatDateTime(st, m_fromLine, FALSE);
+ }
+ else
+ m_fromLine += sFromDate;
+
+ m_fromLine += "\x0D\x0A";
+}
+
+#ifndef dispidHeaderItem
+#define dispidHeaderItem 0x8578
+#endif
+DEFINE_OLEGUID(PSETID_Common, MAKELONG(0x2000+(8),0x0006),0,0);
+
+void CMapiMessage::GetDownloadState()
+{
+ // See http://support.microsoft.com/kb/912239
+ HRESULT hRes = S_OK;
+ ULONG ulVal = 0;
+ LPSPropValue lpPropVal = NULL;
+ LPSPropTagArray lpNamedPropTag = NULL;
+ MAPINAMEID NamedID = {0};
+ LPMAPINAMEID lpNamedID = NULL;
+
+ NamedID.lpguid = (LPGUID) &PSETID_Common;
+ NamedID.ulKind = MNID_ID;
+ NamedID.Kind.lID = dispidHeaderItem;
+ lpNamedID = &NamedID;
+
+ hRes = m_lpMsg->GetIDsFromNames(1, &lpNamedID, NULL, &lpNamedPropTag);
+
+ if (lpNamedPropTag && 1 == lpNamedPropTag->cValues)
+ {
+ lpNamedPropTag->aulPropTag[0] = CHANGE_PROP_TYPE(lpNamedPropTag->aulPropTag[0], PT_LONG);
+
+ //Get the value of the property.
+ hRes = m_lpMsg->GetProps(lpNamedPropTag, 0, &ulVal, &lpPropVal);
+ if (lpPropVal && 1 == ulVal && PT_LONG == PROP_TYPE(lpPropVal->ulPropTag) &&
+ lpPropVal->Value.ul)
+ m_dldStateHeadersOnly = true;
+ }
+
+ CMapiApi::MAPIFreeBuffer(lpPropVal);
+ CMapiApi::MAPIFreeBuffer(lpNamedPropTag);
+}
+
+// Headers - fetch will get PR_TRANSPORT_MESSAGE_HEADERS
+// or if they do not exist will build a header from
+// PR_DISPLAY_TO, _CC, _BCC
+// PR_SUBJECT
+// PR_MESSAGE_RECIPIENTS
+// and PR_CREATION_TIME if needed?
+bool CMapiMessage::FetchHeaders(void)
+{
+ ULONG tag = PR_TRANSPORT_MESSAGE_HEADERS_A;
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, tag);
+ if (!pVal)
+ pVal = CMapiApi::GetMapiProperty(m_lpMsg, tag = PR_TRANSPORT_MESSAGE_HEADERS_W);
+ if (pVal) {
+ if (CMapiApi::IsLargeProperty(pVal)) {
+ nsCString headers;
+ CMapiApi::GetLargeStringProperty(m_lpMsg, tag, headers);
+ m_headers.Assign(headers.get());
+ }
+ else if ((PROP_TYPE(pVal->ulPropTag) == PT_STRING8) &&
+ (pVal->Value.lpszA) && (*(pVal->Value.lpszA)))
+ m_headers.Assign(pVal->Value.lpszA);
+ else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
+ (pVal->Value.lpszW) && (*(pVal->Value.lpszW))) {
+ nsCString headers;
+ LossyCopyUTF16toASCII(nsDependentString(pVal->Value.lpszW), headers);
+ m_headers.Assign(headers.get());
+ }
+
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+
+ EnsureDate();
+ if (!EnsureHeader(CMapiMessageHeaders::hdrFrom, PR_SENDER_NAME_W))
+ EnsureHeader(CMapiMessageHeaders::hdrFrom, PR_SENDER_EMAIL_ADDRESS_W);
+ EnsureHeader(CMapiMessageHeaders::hdrSubject, PR_SUBJECT_W);
+ EnsureHeader(CMapiMessageHeaders::hdrTo, PR_DISPLAY_TO_W);
+ EnsureHeader(CMapiMessageHeaders::hdrCc, PR_DISPLAY_CC_W);
+ EnsureHeader(CMapiMessageHeaders::hdrBcc, PR_DISPLAY_BCC_W);
+
+ ProcessContentType();
+
+ return !m_headers.IsEmpty();
+}
+
+// Mime-Version: 1.0
+// Content-Type: text/plain; charset="US-ASCII"
+// Content-Type: multipart/mixed; boundary="=====================_874475278==_"
+
+void CMapiMessage::ProcessContentType()
+{
+ m_mimeContentType.Truncate();
+ m_mimeBoundary.Truncate();
+ m_mimeCharset.Truncate();
+
+ const char* contentType = m_headers.Value(CMapiMessageHeaders::hdrContentType);
+ if (!contentType)
+ return;
+
+ const char *begin = contentType, *end;
+ nsCString tStr;
+
+ // Note: this isn't a complete parser, the content type
+ // we extract could have rfc822 comments in it
+ while (*begin && IsSpace(*begin))
+ begin++;
+ if (!(*begin))
+ return;
+ end = begin;
+ while (*end && (*end != ';'))
+ end++;
+ m_mimeContentType.Assign(begin, end-begin);
+ if (!(*end))
+ return;
+ // look for "boundary="
+ begin = end + 1;
+ bool haveB;
+ bool haveC;
+ while (*begin) {
+ haveB = false;
+ haveC = false;
+ while (*begin && IsSpace(*begin))
+ begin++;
+ if (!(*begin))
+ return;
+ end = begin;
+ while (*end && (*end != '='))
+ end++;
+ if (end - begin) {
+ tStr.Assign(begin, end-begin);
+ if (tStr.LowerCaseEqualsLiteral("boundary"))
+ haveB = true;
+ else if (tStr.LowerCaseEqualsLiteral("charset"))
+ haveC = true;
+ }
+ if (!(*end))
+ return;
+ begin = end+1;
+ while (*begin && IsSpace(*begin))
+ begin++;
+ if (*begin == '"') {
+ begin++;
+ bool slash = false;
+ tStr.Truncate();
+ while (*begin) {
+ if (slash) {
+ slash = false;
+ tStr.Append(*begin);
+ }
+ else if (*begin == '"')
+ break;
+ else if (*begin != '\\')
+ tStr.Append(*begin);
+ else
+ slash = true;
+ begin++;
+ }
+ if (haveB) {
+ m_mimeBoundary = tStr;
+ haveB = false;
+ }
+ if (haveC) {
+ m_mimeCharset = tStr;
+ haveC = false;
+ }
+ if (!(*begin))
+ return;
+ begin++;
+ }
+ tStr.Truncate();
+ while (*begin && (*begin != ';')) {
+ tStr.Append(*(begin++));
+ }
+ if (haveB) {
+ tStr.Trim(kWhitespace);
+ m_mimeBoundary = tStr;
+ }
+ if (haveC) {
+ tStr.Trim(kWhitespace);
+ m_mimeCharset = tStr;
+ }
+ if (*begin)
+ begin++;
+ }
+}
+
+const char* CpToCharset(unsigned int cp)
+{
+ struct CODEPAGE_TO_CHARSET {
+ unsigned long cp;
+ const char* charset;
+ };
+
+ // This table is based on http://msdn.microsoft.com/en-us/library/dd317756(v=VS.85).aspx#1;
+ // Please extend as appropriate. The codepage values are sorted ascending.
+ static const CODEPAGE_TO_CHARSET cptocharset[] =
+ {
+ {37, "IBM037"}, // IBM EBCDIC US-Canada
+ {437, "IBM437"}, //OEM United States
+ {500, "IBM500"}, //IBM EBCDIC International
+ {708, "ASMO-708"}, //Arabic (ASMO 708)
+ //709 Arabic (ASMO-449+, BCON V4)
+ //710 Arabic - Transparent Arabic
+ {720, "DOS-720"}, //Arabic (Transparent ASMO); Arabic (DOS)
+ {737, "ibm737"}, // OEM Greek (formerly 437G); Greek (DOS)
+ {775, "ibm775"}, // OEM Baltic; Baltic (DOS)
+ {850, "ibm850"}, // OEM Multilingual Latin 1; Western European (DOS)
+ {852, "ibm852"}, // OEM Latin 2; Central European (DOS)
+ {855, "IBM855"}, // OEM Cyrillic (primarily Russian)
+ {857, "ibm857"}, // OEM Turkish; Turkish (DOS)
+ {858, "IBM00858"}, // OEM Multilingual Latin 1 + Euro symbol
+ {860, "IBM860"}, // OEM Portuguese; Portuguese (DOS)
+ {861, "ibm861"}, // OEM Icelandic; Icelandic (DOS)
+ {862, "DOS-862"}, // OEM Hebrew; Hebrew (DOS)
+ {863, "IBM863"}, // OEM French Canadian; French Canadian (DOS)
+ {864, "IBM864"}, // OEM Arabic; Arabic (864)
+ {865, "IBM865"}, // OEM Nordic; Nordic (DOS)
+ {866, "cp866"}, // OEM Russian; Cyrillic (DOS)
+ {869, "ibm869"}, // OEM Modern Greek; Greek, Modern (DOS)
+ {870, "IBM870"}, // IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2
+ {874, "windows-874"}, // ANSI/OEM Thai (same as 28605, ISO 8859-15); Thai (Windows)
+ {875, "cp875"}, // IBM EBCDIC Greek Modern
+ {932, "shift_jis"}, // ANSI/OEM Japanese; Japanese (Shift-JIS)
+ {936, "gb2312"}, // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
+ {949, "ks_c_5601-1987"}, // ANSI/OEM Korean (Unified Hangul Code)
+ {950, "big5"}, // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)
+ {1026, "IBM1026"}, // IBM EBCDIC Turkish (Latin 5)
+ {1047, "IBM01047"}, // IBM EBCDIC Latin 1/Open System
+ {1140, "IBM01140"}, // IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)
+ {1141, "IBM01141"}, // IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)
+ {1142, "IBM01142"}, // IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)
+ {1143, "IBM01143"}, // IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)
+ {1144, "IBM01144"}, // IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)
+ {1145, "IBM01145"}, // IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)
+ {1146, "IBM01146"}, // IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)
+ {1147, "IBM01147"}, // IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)
+ {1148, "IBM01148"}, // IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)
+ {1149, "IBM01149"}, // IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)
+ {1200, "utf-16"}, // Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications
+ {1201, "unicodeFFFE"}, // Unicode UTF-16, big endian byte order; available only to managed applications
+ {1250, "windows-1250"}, // ANSI Central European; Central European (Windows)
+ {1251, "windows-1251"}, // ANSI Cyrillic; Cyrillic (Windows)
+ {1252, "windows-1252"}, // ANSI Latin 1; Western European (Windows)
+ {1253, "windows-1253"}, // ANSI Greek; Greek (Windows)
+ {1254, "windows-1254"}, // ANSI Turkish; Turkish (Windows)
+ {1255, "windows-1255"}, // ANSI Hebrew; Hebrew (Windows)
+ {1256, "windows-1256"}, // ANSI Arabic; Arabic (Windows)
+ {1257, "windows-1257"}, // ANSI Baltic; Baltic (Windows)
+ {1258, "windows-1258"}, // ANSI/OEM Vietnamese; Vietnamese (Windows)
+ {1361, "Johab"}, // Korean (Johab)
+ {10000, "macintosh"}, // MAC Roman; Western European (Mac)
+ {10001, "x-mac-japanese"}, // Japanese (Mac)
+ {10002, "x-mac-chinesetrad"}, // MAC Traditional Chinese (Big5); Chinese Traditional (Mac)
+ {10003, "x-mac-korean"}, // Korean (Mac)
+ {10004, "x-mac-arabic"}, // Arabic (Mac)
+ {10005, "x-mac-hebrew"}, // Hebrew (Mac)
+ {10006, "x-mac-greek"}, // Greek (Mac)
+ {10007, "x-mac-cyrillic"}, // Cyrillic (Mac)
+ {10008, "x-mac-chinesesimp"}, // MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac)
+ {10010, "x-mac-romanian"}, // Romanian (Mac)
+ {10017, "x-mac-ukrainian"}, // Ukrainian (Mac)
+ {10021, "x-mac-thai"}, // Thai (Mac)
+ {10029, "x-mac-ce"}, // MAC Latin 2; Central European (Mac)
+ {10079, "x-mac-icelandic"}, // Icelandic (Mac)
+ {10081, "x-mac-turkish"}, // Turkish (Mac)
+ {10082, "x-mac-croatian"}, // Croatian (Mac)
+ // Unicode UTF-32, little endian byte order; available only to managed applications
+ // impossible in 8-bit mail
+ {12000, "utf-32"},
+ // Unicode UTF-32, big endian byte order; available only to managed applications
+ // impossible in 8-bit mail
+ {12001, "utf-32BE"},
+ {20000, "x-Chinese_CNS"}, // CNS Taiwan; Chinese Traditional (CNS)
+ {20001, "x-cp20001"}, // TCA Taiwan
+ {20002, "x_Chinese-Eten"}, // Eten Taiwan; Chinese Traditional (Eten)
+ {20003, "x-cp20003"}, // IBM5550 Taiwan
+ {20004, "x-cp20004"}, // TeleText Taiwan
+ {20005, "x-cp20005"}, // Wang Taiwan
+ {20105, "x-IA5"}, // IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5)
+ {20106, "x-IA5-German"}, // IA5 German (7-bit)
+ {20107, "x-IA5-Swedish"}, // IA5 Swedish (7-bit)
+ {20108, "x-IA5-Norwegian"}, // IA5 Norwegian (7-bit)
+ {20127, "us-ascii"}, // US-ASCII (7-bit)
+ {20261, "x-cp20261"}, // T.61
+ {20269, "x-cp20269"}, // ISO 6937 Non-Spacing Accent
+ {20273, "IBM273"}, // IBM EBCDIC Germany
+ {20277, "IBM277"}, // IBM EBCDIC Denmark-Norway
+ {20278, "IBM278"}, // IBM EBCDIC Finland-Sweden
+ {20280, "IBM280"}, // IBM EBCDIC Italy
+ {20284, "IBM284"}, // IBM EBCDIC Latin America-Spain
+ {20285, "IBM285"}, // IBM EBCDIC United Kingdom
+ {20290, "IBM290"}, // IBM EBCDIC Japanese Katakana Extended
+ {20297, "IBM297"}, // IBM EBCDIC France
+ {20420, "IBM420"}, // IBM EBCDIC Arabic
+ {20423, "IBM423"}, // IBM EBCDIC Greek
+ {20424, "IBM424"}, // IBM EBCDIC Hebrew
+ {20833, "x-EBCDIC-KoreanExtended"}, // IBM EBCDIC Korean Extended
+ {20838, "IBM-Thai"}, // IBM EBCDIC Thai
+ {20866, "koi8-r"}, // Russian (KOI8-R); Cyrillic (KOI8-R)
+ {20871, "IBM871"}, // IBM EBCDIC Icelandic
+ {20880, "IBM880"}, // IBM EBCDIC Cyrillic Russian
+ {20905, "IBM905"}, // IBM EBCDIC Turkish
+ {20924, "IBM00924"}, // IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
+ {20932, "EUC-JP"}, // Japanese (JIS 0208-1990 and 0121-1990)
+ {20936, "x-cp20936"}, // Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)
+ {20949, "x-cp20949"}, // Korean Wansung
+ {21025, "cp1025"}, // IBM EBCDIC Cyrillic Serbian-Bulgarian
+ //21027 (deprecated)
+ {21866, "koi8-u"}, // Ukrainian (KOI8-U); Cyrillic (KOI8-U)
+ {28591, "iso-8859-1"}, // ISO 8859-1 Latin 1; Western European (ISO)
+ {28592, "iso-8859-2"}, // ISO 8859-2 Central European; Central European (ISO)
+ {28593, "iso-8859-3"}, // ISO 8859-3 Latin 3
+ {28594, "iso-8859-4"}, // ISO 8859-4 Baltic
+ {28595, "iso-8859-5"}, // ISO 8859-5 Cyrillic
+ {28596, "iso-8859-6"}, // ISO 8859-6 Arabic
+ {28597, "iso-8859-7"}, // ISO 8859-7 Greek
+ {28598, "iso-8859-8"}, // ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
+ {28599, "iso-8859-9"}, // ISO 8859-9 Turkish
+ {28603, "iso-8859-13"}, // ISO 8859-13 Estonian
+ {28605, "iso-8859-15"}, // ISO 8859-15 Latin 9
+ {29001, "x-Europa"}, // Europa 3
+ {38598, "iso-8859-8-i"}, // ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
+ {50220, "iso-2022-jp"}, // ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)
+ {50221, "csISO2022JP"}, // ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)
+ {50222, "iso-2022-jp"}, // ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)
+ {50225, "iso-2022-kr"}, // ISO 2022 Korean
+ {50227, "x-cp50227"}, // ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)
+ //50229 ISO 2022 Traditional Chinese
+ //50930 EBCDIC Japanese (Katakana) Extended
+ //50931 EBCDIC US-Canada and Japanese
+ //50933 EBCDIC Korean Extended and Korean
+ //50935 EBCDIC Simplified Chinese Extended and Simplified Chinese
+ //50936 EBCDIC Simplified Chinese
+ //50937 EBCDIC US-Canada and Traditional Chinese
+ //50939 EBCDIC Japanese (Latin) Extended and Japanese
+ {51932, "euc-jp"}, // EUC Japanese
+ {51936, "EUC-CN"}, // EUC Simplified Chinese; Chinese Simplified (EUC)
+ {51949, "euc-kr"}, // EUC Korean
+ //51950 EUC Traditional Chinese
+ {52936, "hz-gb-2312"}, // HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
+ {54936, "GB18030"}, // Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)
+ {57002, "x-iscii-de"}, // ISCII Devanagari
+ {57003, "x-iscii-be"}, // ISCII Bengali
+ {57004, "x-iscii-ta"}, // ISCII Tamil
+ {57005, "x-iscii-te"}, // ISCII Telugu
+ {57006, "x-iscii-as"}, // ISCII Assamese
+ {57007, "x-iscii-or"}, // ISCII Oriya (Odia)
+ {57008, "x-iscii-ka"}, // ISCII Kannada
+ {57009, "x-iscii-ma"}, // ISCII Malayalam
+ {57010, "x-iscii-gu"}, // ISCII Gujarati
+ {57011, "x-iscii-pa"}, // ISCII Punjabi
+ {65000, "utf-7"}, // Unicode (UTF-7)
+ {65001, "utf-8"}, // Unicode (UTF-8)
+ };
+
+ // Binary search
+ int begin = 0, end = sizeof(cptocharset)/sizeof(cptocharset[0])-1;
+ while (begin <= end) {
+ int mid = (begin+end)/2;
+ unsigned int mid_cp = cptocharset[mid].cp;
+ if (cp == mid_cp)
+ return cptocharset[mid].charset;
+ if (cp < mid_cp)
+ end = mid - 1;
+ else // cp > cptocharset[mid].cp
+ begin = mid + 1;
+ }
+ return 0; // not found
+}
+
+// We don't use nsMsgI18Ncheck_data_in_charset_range because it returns true
+// even if there's no such charset:
+// 1. result initialized by true and returned if, eg, GetUnicodeEncoderRaw fail
+// 2. it uses GetUnicodeEncoderRaw(), not GetUnicodeEncoder() (to normalize the
+// charset string) (see nsMsgI18N.cpp)
+// This function returns true only if the unicode (utf-16) text can be
+// losslessly represented in specified charset
+bool CMapiMessage::CheckBodyInCharsetRange(const char* charset)
+{
+ if (m_body.IsEmpty())
+ return true;
+ if (!_stricmp(charset, "utf-8"))
+ return true;
+ if (!_stricmp(charset, "utf-7"))
+ return true;
+
+ nsresult rv;
+ static nsCOMPtr<nsICharsetConverterManager> ccm =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIUnicodeEncoder> encoder;
+
+ // get an unicode converter
+ rv = ccm->GetUnicodeEncoder(charset, getter_AddRefs(encoder));
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = encoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Signal, nullptr, 0);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ const char16_t *txt = m_body.get();
+ int32_t txtLen = m_body.Length();
+ const char16_t *currentSrcPtr = txt;
+ int srcLength;
+ int dstLength;
+ char localbuf[512];
+ int consumedLen = 0;
+
+ // convert
+ while (consumedLen < txtLen) {
+ srcLength = txtLen - consumedLen;
+ dstLength = sizeof(localbuf)/sizeof(localbuf[0]);
+ rv = encoder->Convert(currentSrcPtr, &srcLength, localbuf, &dstLength);
+ if (rv == NS_ERROR_UENC_NOMAPPING)
+ return false;
+ if (NS_FAILED(rv) || dstLength == 0)
+ break;
+
+ currentSrcPtr += srcLength;
+ consumedLen = currentSrcPtr - txt; // src length used so far
+ }
+ return true;
+}
+
+bool CaseInsensitiveComp (wchar_t elem1, wchar_t elem2)
+{
+ return _wcsnicmp(&elem1, &elem2, 1) == 0;
+}
+
+void ExtractMetaCharset(const wchar_t* body, int bodySz, /*out*/nsCString& charset)
+{
+ charset.Truncate();
+ const wchar_t* body_end = body+bodySz;
+ const wchar_t str_eohd[] = L"/head";
+ const wchar_t *str_eohd_end = str_eohd+sizeof(str_eohd)/sizeof(str_eohd[0])-1;
+ const wchar_t* eohd_pos = std::search(body, body_end, str_eohd, str_eohd_end,
+ CaseInsensitiveComp);
+ if (eohd_pos == body_end) // No header!
+ return;
+ const wchar_t str_chset[] = L"charset=";
+ const wchar_t *str_chset_end =
+ str_chset + sizeof(str_chset)/sizeof(str_chset[0])-1;
+ const wchar_t* chset_pos = std::search(body, eohd_pos, str_chset,
+ str_chset_end, CaseInsensitiveComp);
+ if (chset_pos == eohd_pos) // No charset!
+ return;
+ chset_pos += 8;
+
+ // remove everything from the string after the next ; or " or space,
+ // whichever comes first.
+ // The inital sting looks something like
+ // <META content="text/html; charset=utf-8" http-equiv=Content-Type>
+ // <META content="text/html; charset=utf-8;" http-equiv=Content-Type>
+ // <META content="text/html; charset=utf-8 ;" http-equiv=Content-Type>
+ // <META content="text/html; charset=utf-8 " http-equiv=Content-Type>
+ const wchar_t term[] = L";\" ", *term_end= term+sizeof(term)/sizeof(term[0])-1;
+ const wchar_t* chset_end = std::find_first_of(chset_pos, eohd_pos, term,
+ term_end);
+ if (chset_end != eohd_pos)
+ LossyCopyUTF16toASCII(Substring(wwc(const_cast<wchar_t *>(chset_pos)),
+ wwc(const_cast<wchar_t *>(chset_end))),
+ charset);
+}
+
+bool CMapiMessage::FetchBody(void)
+{
+ m_bodyIsHtml = false;
+ m_body.Truncate();
+
+ // Get the Outlook codepage info; if unsuccessful then it defaults to 0 (CP_ACP) -> system default
+ // Maybe we can use this info later?
+ unsigned int codepage=0;
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_INTERNET_CPID);
+ if (pVal) {
+ if (PROP_TYPE(pVal->ulPropTag) == PT_LONG)
+ codepage = pVal->Value.l;
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+
+ unsigned long nativeBodyType = 0;
+ if (CMapiApi::GetRTFPropertyDecodedAsUTF16(m_lpMsg, m_body, nativeBodyType,
+ codepage)) {
+ m_bodyIsHtml = nativeBodyType == MAPI_NATIVE_BODY_TYPE_HTML;
+ }
+ else { // Cannot get RTF version
+ // Is it html?
+ pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_HTML_W);
+ if (pVal) {
+ if (CMapiApi::IsLargeProperty(pVal))
+ CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_HTML_W, m_body);
+ else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
+ (pVal->Value.lpszW) && (*(pVal->Value.lpszW)))
+ m_body.Assign(pVal->Value.lpszW);
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+
+ // Kind-hearted Outlook will give us html even for a plain text message.
+ // But it will include a comment saying it did the conversion.
+ // We'll use this as a hack to really use the plain text part.
+ //
+ // Sadly there are cases where this string is returned despite the fact
+ // that the message is indeed HTML.
+ //
+ // To detect the "true" plain text messages, we look for our string
+ // immediately following the <BODY> tag.
+ if (!m_body.IsEmpty() &&
+ m_body.Find("<BODY>\r\n<!-- Converted from text/plain format -->") ==
+ kNotFound) {
+ m_bodyIsHtml = true;
+ }
+ else {
+ pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_W);
+ if (pVal) {
+ if (CMapiApi::IsLargeProperty(pVal))
+ CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_W, m_body);
+ else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
+ (pVal->Value.lpszW) && (*(pVal->Value.lpszW)))
+ m_body.Assign(pVal->Value.lpszW);
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+ }
+ }
+
+ // OK, now let's restore the original encoding!
+ // 1. We may have a header defining the charset (we already called the FetchHeaders(), and there ProcessHeaders();
+ // in this case, the m_mimeCharset is set. See nsOutlookMail::ImportMailbox())
+ // 2. We may have the codepage walue provided by Outlook ("codepage" at the very beginning of this function)
+ // 3. We may have an HTML charset header.
+
+ bool bFoundCharset = false;
+
+ if (!m_mimeCharset.IsEmpty()) // The top-level header data
+ bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get());
+ // No valid charset in the message header - try the HTML header.
+ // arguably may be useless
+ if (!bFoundCharset && m_bodyIsHtml) {
+ ExtractMetaCharset(m_body.get(), m_body.Length(), m_mimeCharset);
+ if (!m_mimeCharset.IsEmpty())
+ bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get());
+ }
+ // Get from Outlook (seems like it keeps the MIME part header encoding info)
+ if (!bFoundCharset && codepage) {
+ const char* charset = CpToCharset(codepage);
+ if (charset) {
+ bFoundCharset = CheckBodyInCharsetRange(charset);
+ if (bFoundCharset)
+ m_mimeCharset.Assign(charset);
+ }
+ }
+ if (!bFoundCharset) { // Use system default
+ const char* charset = nsMsgI18NFileSystemCharset();
+ if (charset) {
+ bFoundCharset = CheckBodyInCharsetRange(charset);
+ if (bFoundCharset)
+ m_mimeCharset.Assign(charset);
+ }
+ }
+ if (!bFoundCharset) // Everything else failed, let's use the lossless utf-8...
+ m_mimeCharset.Assign("utf-8");
+
+ MAPI_DUMP_STRING(m_body.get());
+ MAPI_TRACE0("\r\n");
+
+ return true;
+}
+
+void CMapiMessage::GetBody(nsCString& dest) const
+{
+ nsMsgI18NConvertFromUnicode(m_mimeCharset.get(), m_body, dest);
+}
+
+void CMapiMessage::FetchFlags(void)
+{
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_FLAGS);
+ if (pVal)
+ m_msgFlags = CMapiApi::GetLongFromProp(pVal);
+ pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_LAST_VERB_EXECUTED);
+ if (pVal)
+ m_msgLastVerb = CMapiApi::GetLongFromProp(pVal);
+}
+
+enum {
+ ieidPR_ATTACH_NUM = 0,
+ ieidAttachMax
+};
+
+static const SizedSPropTagArray(ieidAttachMax, ptaEid)=
+{
+ ieidAttachMax,
+ {
+ PR_ATTACH_NUM
+ }
+};
+
+bool CMapiMessage::IterateAttachTable(LPMAPITABLE lpTable)
+{
+ ULONG rowCount;
+ HRESULT hr = lpTable->GetRowCount(0, &rowCount);
+ if (!rowCount) {
+ return true;
+ }
+
+ hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0);
+ if (FAILED(hr)) {
+ MAPI_TRACE2("SetColumns for attachment table failed: 0x%lx, %d\r\n", (long)hr, (int)hr);
+ return false;
+ }
+
+ hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
+ if (FAILED(hr)) {
+ MAPI_TRACE2("SeekRow for attachment table failed: 0x%lx, %d\r\n", (long)hr, (int)hr);
+ return false;
+ }
+
+ int cNumRows = 0;
+ LPSRowSet lpRow;
+ bool bResult = true;
+ do {
+
+ lpRow = NULL;
+ hr = lpTable->QueryRows(1, 0, &lpRow);
+
+ if(HR_FAILED(hr)) {
+ MAPI_TRACE2("QueryRows for attachment table failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ bResult = false;
+ break;
+ }
+
+ if (lpRow) {
+ cNumRows = lpRow->cRows;
+
+ if (cNumRows) {
+ DWORD aNum = lpRow->aRow[0].lpProps[ieidPR_ATTACH_NUM].Value.ul;
+ AddAttachment(aNum);
+ MAPI_TRACE1("\t\t****Attachment found - #%d\r\n", (int)aNum);
+ }
+ CMapiApi::FreeProws(lpRow);
+ }
+
+ } while (SUCCEEDED(hr) && cNumRows && lpRow);
+
+ return bResult;
+}
+
+bool CMapiMessage::GetTmpFile(/*out*/ nsIFile **aResult)
+{
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "mapiattach.tmp",
+ getter_AddRefs(tmpFile));
+ if (NS_FAILED(rv))
+ return false;
+
+ rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv))
+ return false;
+
+ tmpFile.forget(aResult);
+ return true;
+}
+
+bool CMapiMessage::CopyMsgAttachToFile(LPATTACH lpAttach, /*out*/ nsIFile **tmp_file)
+{
+ bool bResult = true;
+ LPMESSAGE lpMsg;
+ HRESULT hr = lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, 0,
+ reinterpret_cast<LPUNKNOWN *>(&lpMsg));
+ if (HR_FAILED(hr))
+ return false;
+
+ if (!GetTmpFile(tmp_file))
+ return false;
+
+ nsCOMPtr<nsIOutputStream> destOutputStream;
+ nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(destOutputStream), *tmp_file, -1, 0600);
+ if (NS_SUCCEEDED(rv))
+ rv = nsOutlookMail::ImportMessage(lpMsg, destOutputStream, nsIMsgSend::nsMsgSaveAsDraft);
+
+ if (NS_FAILED(rv)) {
+ (*tmp_file)->Remove(false);
+ (*tmp_file)->Release();
+ *tmp_file = 0;
+ }
+
+ return NS_SUCCEEDED(rv);
+}
+
+bool CMapiMessage::CopyBinAttachToFile(LPATTACH lpAttach,
+ nsIFile **tmp_file)
+{
+ nsCOMPtr<nsIFile> _tmp_file;
+ nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "mapiattach.tmp",
+ getter_AddRefs(_tmp_file));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = _tmp_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCString tmpPath;
+ _tmp_file->GetNativePath(tmpPath);
+ LPSTREAM lpStreamFile;
+ HRESULT hr = CMapiApi::OpenStreamOnFile(gpMapiAllocateBuffer, gpMapiFreeBuffer, STGM_READWRITE | STGM_CREATE,
+ const_cast<char*>(tmpPath.get()), NULL, &lpStreamFile);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE1("~~ERROR~~ OpenStreamOnFile failed - temp path: %s\r\n",
+ tmpPath.get());
+ return false;
+ }
+
+ bool bResult = true;
+ LPSTREAM lpAttachStream;
+ hr = lpAttach->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0, (LPUNKNOWN *)&lpAttachStream);
+
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE0("~~ERROR~~ OpenProperty failed for PR_ATTACH_DATA_BIN.\r\n");
+ lpAttachStream = NULL;
+ bResult = false;
+ }
+ else {
+ STATSTG st;
+ hr = lpAttachStream->Stat(&st, STATFLAG_NONAME);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE0("~~ERROR~~ Stat failed for attachment stream\r\n");
+ bResult = false;
+ }
+ else {
+ hr = lpAttachStream->CopyTo(lpStreamFile, st.cbSize, NULL, NULL);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE0("~~ERROR~~ Attach Stream CopyTo temp file failed.\r\n");
+ bResult = false;
+ }
+ }
+ }
+
+ if (lpAttachStream)
+ lpAttachStream->Release();
+ lpStreamFile->Release();
+ if (!bResult)
+ _tmp_file->Remove(false);
+ else
+ _tmp_file.forget(tmp_file);
+
+ return bResult;
+}
+
+bool CMapiMessage::GetURL(nsIFile *aFile, nsIURI **url)
+{
+ if (!m_pIOService)
+ return false;
+
+ nsresult rv = m_pIOService->NewFileURI(aFile, url);
+ return NS_SUCCEEDED(rv);
+}
+
+bool CMapiMessage::AddAttachment(DWORD aNum)
+{
+ LPATTACH lpAttach = NULL;
+ HRESULT hr = m_lpMsg->OpenAttach(aNum, NULL, 0, &lpAttach);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE2("\t\t****Attachment error, unable to open attachment: %d, 0x%lx\r\n", idx, hr);
+ return false;
+ }
+
+ bool bResult = false;
+ attach_data *data = new attach_data;
+ ULONG aMethod;
+ if (data) {
+ bResult = true;
+
+ // 1. Get the file that contains the attachment data
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_METHOD);
+ if (pVal) {
+ aMethod = CMapiApi::GetLongFromProp(pVal);
+ switch (aMethod) {
+ case ATTACH_BY_VALUE:
+ MAPI_TRACE1("\t\t** Attachment #%d by value.\r\n", aNum);
+ bResult = CopyBinAttachToFile(lpAttach, getter_AddRefs(data->tmp_file));
+ data->delete_file = true;
+ break;
+ case ATTACH_BY_REFERENCE:
+ case ATTACH_BY_REF_RESOLVE:
+ case ATTACH_BY_REF_ONLY:
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_PATHNAME_W);
+ if (pVal) {
+ nsCString path;
+ CMapiApi::GetStringFromProp(pVal, path);
+ nsresult rv;
+ data->tmp_file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_FAILED(rv) || !data->tmp_file) {
+ MAPI_TRACE0("*** Error creating file spec for attachment\n");
+ bResult = false;
+ }
+ else data->tmp_file->InitWithNativePath(path);
+ }
+ MAPI_TRACE2("\t\t** Attachment #%d by ref: %s\r\n",
+ aNum, m_attachPath.get());
+ break;
+ case ATTACH_EMBEDDED_MSG:
+ MAPI_TRACE1("\t\t** Attachment #%d by Embedded Message??\r\n", aNum);
+ // Convert the embedded IMessage from PR_ATTACH_DATA_OBJ to rfc822 attachment
+ // (see http://msdn.microsoft.com/en-us/library/cc842329.aspx)
+ // This is a recursive call.
+ bResult = CopyMsgAttachToFile(lpAttach, getter_AddRefs(data->tmp_file));
+ data->delete_file = true;
+ break;
+ case ATTACH_OLE:
+ MAPI_TRACE1("\t\t** Attachment #%d by OLE - yuck!!!\r\n", aNum);
+ break;
+ default:
+ MAPI_TRACE2("\t\t** Attachment #%d unknown attachment method - 0x%lx\r\n", aNum, aMethod);
+ bResult = false;
+ }
+ }
+ else
+ bResult = false;
+
+ if (bResult)
+ bResult = data->tmp_file;
+
+ if (bResult) {
+ bool isFile = false;
+ bool exists = false;
+ data->tmp_file->Exists(&exists);
+ data->tmp_file->IsFile(&isFile);
+
+ if (!exists || !isFile) {
+ bResult = false;
+ MAPI_TRACE0("Attachment file does not exist\n");
+ }
+ }
+
+ if (bResult)
+ bResult = GetURL(data->tmp_file, getter_AddRefs(data->orig_url));
+
+ if (bResult) {
+ // Now we have the file; proceed to the other properties
+
+ data->encoding = NS_strdup(ENCODING_BINARY);
+
+ nsString fname, fext;
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_LONG_FILENAME_W);
+ if (!pVal)
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FILENAME_W);
+ CMapiApi::GetStringFromProp(pVal, fname);
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_EXTENSION_W);
+ CMapiApi::GetStringFromProp(pVal, fext);
+ MAPI_TRACE2("\t\t\t--- File name: %s, extension: %s\r\n",
+ fname.get(), fext.get());
+
+ if (fext.IsEmpty()) {
+ int idx = fname.RFindChar(L'.');
+ if (idx != -1)
+ fext = Substring(fname, idx);
+ }
+ else if (fname.RFindChar(L'.') == -1) {
+ fname += L".";
+ fname += fext;
+ }
+ if (fname.IsEmpty()) {
+ // If no description use "Attachment i" format.
+ fname = L"Attachment ";
+ fname.AppendInt(static_cast<uint32_t>(aNum));
+ }
+ data->real_name = ToNewUTF8String(fname);
+
+ nsCString tmp;
+ // We have converted it to the rfc822 document
+ if (aMethod == ATTACH_EMBEDDED_MSG) {
+ data->type = NS_strdup(MESSAGE_RFC822);
+ } else {
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_MIME_TAG_A);
+ CMapiApi::GetStringFromProp(pVal, tmp);
+ MAPI_TRACE1("\t\t\t--- Mime type: %s\r\n", tmp.get());
+ if (tmp.IsEmpty()) {
+ uint8_t *pType = NULL;
+ if (!fext.IsEmpty()) {
+ pType = CMimeTypes::GetMimeType(fext);
+ }
+ if (pType)
+ data->type = NS_strdup((PC_S8)pType);
+ else
+ data->type = NS_strdup(APPLICATION_OCTET_STREAM);
+ }
+ else
+ data->type = ToNewCString(tmp);
+ }
+
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_CONTENT_ID_A);
+ CMapiApi::GetStringFromProp(pVal, tmp);
+ if (!tmp.IsEmpty())
+ data->cid = ToNewCString(tmp);
+ }
+ if (bResult) {
+ // Now we need to decide if this attachment is embedded or not.
+ // At first, I tried to simply check for the presence of the Content-Id.
+ // But it turned out that this method is unreliable, since there exist cases
+ // when an attachment has a Content-Id while isn't embedded (even in a message
+ // with a plain-text body!). So next I tried to look for <img> tags that contain
+ // the found Content-Id. But this is unreliable, too, because there exist cases
+ // where other places of HTML reference the embedded messages (e.g. it may be
+ // a background of a table cell, or some CSS; further, it is possible that the
+ // reference to an embedded object is not in the main body, but in another
+ // embedded object - like body references a CSS attachment that in turn references
+ // a picture as a background of its element). From the other hand, it's unreliable
+ // to relax the search criteria to any occurence of the Content-Id string in the body -
+ // partly because the string may be simply in a text or other non-referencing part,
+ // partly because of the abovementioned possibility that the reference is outside
+ // the body at all.
+ // There exist the PR_ATTACH_FLAGS property of the attachment. The MS documentation
+ // tells about two possible flags in it: ATT_INVISIBLE_IN_HTML and ATT_INVISIBLE_IN_RTF.
+ // There is at least one more undocumented flag: ATT_MHTML_REF. Some sources in Internet
+ // suggest simply check for the latter flag to distinguish between the embedded
+ // and ordinary attachments. But my observations indicate that even if the flags
+ // don't include ATT_MHTML_REF, the attachment is still may be embedded.
+ // However, my observations always show that the message is embedded if the flags
+ // is not 0.
+ // So now I will simply test for the non-zero flags to decide whether the attachment
+ // is embedded or not. Possible advantage is reliability (I hope).
+ // Another advantage is that it's much faster than search the body for Content-Id.
+
+ DWORD flags = 0;
+
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FLAGS);
+ if (pVal)
+ flags = CMapiApi::GetLongFromProp(pVal);
+ if (m_bodyIsHtml && data->cid && (flags != 0)) // this is the embedded attachment
+ m_embattachments.push_back(data);
+ else // this is ordinary attachment
+ m_stdattachments.push_back(data);
+ }
+ else {
+ delete data;
+ }
+ }
+
+ lpAttach->Release();
+ return bResult;
+}
+
+void CMapiMessage::ClearAttachment(attach_data* data)
+{
+ if (data->delete_file && data->tmp_file)
+ data->tmp_file->Remove(false);
+
+ if (data->type)
+ NS_Free(data->type);
+ if (data->encoding)
+ NS_Free(data->encoding);
+ if (data->real_name)
+ NS_Free(data->real_name);
+ if (data->cid)
+ NS_Free(data->cid);
+
+ delete data;
+}
+
+void CMapiMessage::ClearAttachments()
+{
+ std::for_each(m_stdattachments.begin(), m_stdattachments.end(), ClearAttachment);
+ m_stdattachments.clear();
+ std::for_each(m_embattachments.begin(), m_embattachments.end(), ClearAttachment);
+ m_embattachments.clear();
+}
+
+// This method must be called AFTER the retrieval of the body,
+// since the decision if an attachment is embedded or not is made
+// based on the body type and contents
+void CMapiMessage::ProcessAttachments()
+{
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_HASATTACH);
+ bool hasAttach = true;
+
+ if (pVal) {
+ if (PROP_TYPE(pVal->ulPropTag) == PT_BOOLEAN)
+ hasAttach = (pVal->Value.b != 0);
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+
+ if (!hasAttach)
+ return;
+
+ // Get the attachment table?
+ LPMAPITABLE pTable = NULL;
+ HRESULT hr = m_lpMsg->GetAttachmentTable(0, &pTable);
+ if (FAILED(hr) || !pTable)
+ return;
+ IterateAttachTable(pTable);
+ pTable->Release();
+}
+
+nsresult CMapiMessage::GetAttachments(nsIArray **aArray)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> attachments (do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_IF_ADDREF(*aArray = attachments);
+
+ for (std::vector<attach_data*>::const_iterator it = m_stdattachments.begin();
+ it != m_stdattachments.end(); it++) {
+ nsCOMPtr<nsIMsgAttachedFile> a(do_CreateInstance(NS_MSGATTACHEDFILE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ a->SetOrigUrl((*it)->orig_url);
+ a->SetTmpFile((*it)->tmp_file);
+ a->SetEncoding(nsDependentCString((*it)->encoding));
+ a->SetRealName(nsDependentCString((*it)->real_name));
+ a->SetType(nsDependentCString((*it)->type));
+ attachments->AppendElement(a, false);
+ }
+ return rv;
+}
+
+bool CMapiMessage::GetEmbeddedAttachmentInfo(unsigned int i, nsIURI **uri,
+ const char **cid,
+ const char **name) const
+{
+ if ((i < 0) || (i >= m_embattachments.size()))
+ return false;
+ attach_data* data = m_embattachments[i];
+ if (!data)
+ return false;
+ *uri = data->orig_url;
+ *cid = data->cid;
+ *name = data->real_name;
+ return true;
+}
+
+//////////////////////////////////////////////////////
+
+// begin and end MUST point to the same string
+char* dup(const char* begin, const char* end)
+{
+ if (begin >= end)
+ return 0;
+ char* str = new char[end-begin+1];
+ memcpy(str, begin, (end-begin)*sizeof(begin[0]));
+ str[end - begin] = 0;
+ return str;
+}
+
+// See RFC822
+// 9 = '\t', 32 = ' '.
+inline bool IsPrintableASCII(char c) { return (c > ' ') && (c < 127); }
+inline bool IsWSP(char c) { return (c == ' ') || (c == '\t'); }
+
+CMapiMessageHeaders::CHeaderField::CHeaderField(const char* begin, int len)
+ : m_fname(0), m_fbody(0), m_fbody_utf8(false)
+{
+ const char *end = begin+len, *fname_end = begin;
+ while ((fname_end < end) && IsPrintableASCII(*fname_end) && (*fname_end != ':'))
+ ++fname_end;
+ if ((fname_end == end) || (*fname_end != ':'))
+ return; // Not a valid header!
+ m_fname = dup(begin, fname_end+1); // including colon
+ m_fbody = dup(fname_end+1, end);
+}
+
+CMapiMessageHeaders::CHeaderField::CHeaderField(const char* name, const char* body, bool utf8)
+ : m_fname(dup(name, name+strlen(name))), m_fbody(dup(body, body+strlen(body))), m_fbody_utf8(utf8)
+{
+}
+
+CMapiMessageHeaders::CHeaderField::~CHeaderField()
+{
+ delete[] m_fname;
+ delete[] m_fbody;
+}
+
+void CMapiMessageHeaders::CHeaderField::set_fbody(const char* txt)
+{
+ if (m_fbody == txt)
+ return; // to avoid assigning to self
+ char* oldbody = m_fbody;
+ m_fbody = dup(txt, txt+strlen(txt));
+ delete[] oldbody;
+ m_fbody_utf8 = true;
+}
+
+void CMapiMessageHeaders::CHeaderField::GetUnfoldedString(nsString& dest,
+ const char* fallbackCharset) const
+{
+ dest.Truncate();
+ if (!m_fbody)
+ return;
+
+ nsCString unfolded;
+ const char* pos = m_fbody;
+ while (*pos) {
+ if ((*pos == nsCRT::CR) && (*(pos+1) == nsCRT::LF) && IsWSP(*(pos+2)))
+ pos += 2; // Skip CRLF if it is followed by SPACE or TAB
+ else
+ unfolded.Append(*(pos++));
+ }
+ if (m_fbody_utf8)
+ CopyUTF8toUTF16(unfolded, dest);
+ else
+ nsMsgI18NConvertToUnicode(fallbackCharset, unfolded, dest);
+}
+
+////////////////////////////////////////
+
+const char* CMapiMessageHeaders::Specials[hdrMax] = {
+ "Date:",
+ "From:",
+ "Sender:",
+ "Reply-To:",
+ "To:",
+ "Cc:",
+ "Bcc:",
+ "Message-ID:",
+ "Subject:",
+ "Mime-Version:",
+ "Content-Type:",
+ "Content-Transfer-Encoding:"
+};
+
+CMapiMessageHeaders::~CMapiMessageHeaders()
+{
+ ClearHeaderFields();
+}
+
+void Delete(void* p) { delete p; }
+
+void CMapiMessageHeaders::ClearHeaderFields()
+{
+ std::for_each(m_headerFields.begin(), m_headerFields.end(), Delete);
+ m_headerFields.clear();
+}
+
+void CMapiMessageHeaders::Assign(const char* headers)
+{
+ for (int i=0; i<hdrMax; i++)
+ m_SpecialHeaders[i] = 0;
+ ClearHeaderFields();
+ if (!headers)
+ return;
+
+ const char *start=headers, *end=headers;
+ while (*end) {
+ if ((*end == nsCRT::CR) && (*(end+1) == nsCRT::LF)) {
+ if (!IsWSP(*(end+2))) { // Not SPACE nor TAB (avoid FSP) -> next header or EOF
+ Add(new CHeaderField(start, end-start));
+ start = ++end + 1;
+ }
+ }
+ ++end;
+ }
+
+ if (start < end) { // Last header left
+ Add(new CHeaderField(start, end-start));
+ }
+}
+
+void CMapiMessageHeaders::Add(CHeaderField* f)
+{
+ if (!f)
+ return;
+ if (!f->Valid()) {
+ delete f;
+ return;
+ }
+
+ SpecialHeader idx = CheckSpecialHeader(f->fname());
+ if (idx != hdrNone) {
+ // Now check if the special header was already inserted;
+ // if so, remove previous and add this new
+ CHeaderField* PrevSpecial = m_SpecialHeaders[idx];
+ if (PrevSpecial) {
+ std::vector<CHeaderField*>::iterator iter = std::find(m_headerFields.begin(), m_headerFields.end(), PrevSpecial);
+ if (iter != m_headerFields.end())
+ m_headerFields.erase(iter);
+ delete PrevSpecial;
+ }
+ m_SpecialHeaders[idx] = f;
+ }
+ m_headerFields.push_back(f);
+}
+
+CMapiMessageHeaders::SpecialHeader CMapiMessageHeaders::CheckSpecialHeader(const char* fname)
+{
+ for (int i = hdrFirst; i < hdrMax; i++)
+ if (stricmp(fname, Specials[i]) == 0)
+ return static_cast<SpecialHeader>(i);
+
+ return hdrNone;
+}
+
+const CMapiMessageHeaders::CHeaderField* CMapiMessageHeaders::CFind(const char* name) const
+{
+ SpecialHeader special = CheckSpecialHeader(name);
+ if ((special > hdrNone) && (special < hdrMax))
+ return m_SpecialHeaders[special]; // No need to search further, because it MUST be here
+
+ std::vector<CHeaderField*>::const_iterator iter = std::find_if(m_headerFields.begin(), m_headerFields.end(), fname_equals(name));
+ if (iter == m_headerFields.end())
+ return 0;
+ return *iter;
+}
+
+const char* CMapiMessageHeaders::SpecialName(SpecialHeader special)
+{
+ if ((special <= hdrNone) || (special >= hdrMax))
+ return 0;
+ return Specials[special];
+}
+
+const char* CMapiMessageHeaders::Value(SpecialHeader special) const
+{
+ if ((special <= hdrNone) || (special >= hdrMax))
+ return 0;
+ return (m_SpecialHeaders[special]) ? m_SpecialHeaders[special]->fbody() : 0;
+}
+
+const char* CMapiMessageHeaders::Value(const char* name) const
+{
+ const CHeaderField* result = CFind(name);
+ return result ? result->fbody() : 0;
+}
+
+void CMapiMessageHeaders::UnfoldValue(const char* name, nsString& dest, const char* fallbackCharset) const
+{
+ const CHeaderField* result = CFind(name);
+ if (result)
+ result->GetUnfoldedString(dest, fallbackCharset);
+ else
+ dest.Truncate();
+}
+
+void CMapiMessageHeaders::UnfoldValue(SpecialHeader special, nsString& dest, const char* fallbackCharset) const
+{
+ if ((special <= hdrNone) || (special >= hdrMax) || (!m_SpecialHeaders[special]))
+ dest.Truncate();
+ else
+ m_SpecialHeaders[special]->GetUnfoldedString(dest, fallbackCharset);
+}
+
+int CMapiMessageHeaders::SetValue(const char* name, const char* value, bool replace)
+{
+ if (!replace) {
+ CHeaderField* result = Find(name);
+ if (result) {
+ result->set_fbody(value);
+ return 0;
+ }
+ }
+ Add(new CHeaderField(name, value, true));
+ return 0; // No sensible result is returned; maybe do something senseful later
+}
+
+int CMapiMessageHeaders::SetValue(SpecialHeader special, const char* value)
+{
+ CHeaderField* result = m_SpecialHeaders[special];
+ if (result)
+ result->set_fbody(value);
+ else
+ Add(new CHeaderField(Specials[special], value, true));
+ return 0;
+}
+
+void CMapiMessageHeaders::write_to_stream::operator () (const CHeaderField* f)
+{
+ if (!f || NS_FAILED(m_rv))
+ return;
+
+ uint32_t written;
+ m_rv = m_pDst->Write(f->fname(), strlen(f->fname()), &written);
+ NS_ENSURE_SUCCESS_VOID(m_rv);
+ if (f->fbody()) {
+ m_rv = m_pDst->Write(f->fbody(), strlen(f->fbody()), &written);
+ NS_ENSURE_SUCCESS_VOID(m_rv);
+ }
+ m_rv = m_pDst->Write("\x0D\x0A", 2, &written);
+}
+
+nsresult CMapiMessageHeaders::ToStream(nsIOutputStream *pDst) const
+{
+ nsresult rv = std::for_each(m_headerFields.begin(), m_headerFields.end(),
+ write_to_stream(pDst));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t written;
+ rv = pDst->Write("\x0D\x0A", 2, &written); // Separator line
+ }
+ return rv;
+}
diff --git a/mailnews/import/outlook/src/MapiMessage.h b/mailnews/import/outlook/src/MapiMessage.h
new file mode 100644
index 000000000..dc6a6cda1
--- /dev/null
+++ b/mailnews/import/outlook/src/MapiMessage.h
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef MapiMessage_h___
+#define MapiMessage_h___
+
+#include "nsTArray.h"
+#include "nsStringGlue.h"
+#include "nsIFile.h"
+#include "MapiApi.h"
+#include "nsIMsgSend.h"
+
+#include <vector>
+
+#ifndef PR_LAST_VERB_EXECUTED
+#define PR_LAST_VERB_EXECUTED PROP_TAG(PT_LONG, 0x1081)
+#endif
+
+#define EXCHIVERB_REPLYTOSENDER (102)
+#define EXCHIVERB_REPLYTOALL (103)
+#define EXCHIVERB_FORWARD (104)
+
+#ifndef PR_ATTACH_CONTENT_ID
+#define PR_ATTACH_CONTENT_ID PROP_TAG(PT_TSTRING, 0x3712)
+#endif
+#ifndef PR_ATTACH_CONTENT_ID_W
+#define PR_ATTACH_CONTENT_ID_W PROP_TAG(PT_UNICODE, 0x3712)
+#endif
+#ifndef PR_ATTACH_CONTENT_ID_A
+#define PR_ATTACH_CONTENT_ID_A PROP_TAG(PT_STRING8, 0x3712)
+#endif
+
+#ifndef PR_ATTACH_FLAGS
+#define PR_ATTACH_FLAGS PROP_TAG(PT_LONG, 0x3714)
+#endif
+
+#ifndef ATT_INVISIBLE_IN_HTML
+#define ATT_INVISIBLE_IN_HTML (0x1)
+#endif
+#ifndef ATT_INVISIBLE_IN_RTF
+#define ATT_INVISIBLE_IN_RTF (0x2)
+#endif
+#ifndef ATT_MHTML_REF
+#define ATT_MHTML_REF (0x4)
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+
+class CMapiMessageHeaders {
+public:
+ // Special headers that MUST appear at most once (see RFC822)
+ enum SpecialHeader { hdrNone=-1, hdrFirst = 0, // utility values
+ hdrDate=hdrFirst,
+ hdrFrom,
+ hdrSender,
+ hdrReplyTo,
+ hdrTo,
+ hdrCc,
+ hdrBcc,
+ hdrMessageID,
+ hdrSubject,
+ hdrMimeVersion,
+ hdrContentType,
+ hdrContentTransferEncoding,
+ hdrMax // utility value
+ };
+
+ CMapiMessageHeaders(const char* headers = 0) { Assign(headers); }
+ ~CMapiMessageHeaders();
+ void Assign(const char* headers);
+
+ inline bool IsEmpty() const { return m_headerFields.empty(); }
+ // if no such header exists then 0 is returned, else the first value returned
+ const char* Value(const char* name) const;
+ // if no such header exists then 0 is returned
+ const char* Value(SpecialHeader special) const;
+
+ void UnfoldValue(const char* name, nsString& dest, const char* fallbackCharset) const;
+ void UnfoldValue(SpecialHeader special, nsString& dest, const char* fallbackCharset) const;
+
+ // value must be utf-8 or 7-bit; supposed that this function will be called
+ // when the charset of the value is known
+ // TODO: if replace is set, then all headers with this name will be removed
+ // and one with this value will be added, otherwise a new header is added
+ // (Unnecessary for now)
+ int SetValue(const char* name, const char* value, bool replace = true);
+ int SetValue(SpecialHeader special, const char* value);
+
+ static const char* SpecialName(SpecialHeader special);
+
+ nsresult ToStream(nsIOutputStream *pDst) const;
+private:
+ class CHeaderField {
+ public:
+ CHeaderField(const char* begin, int len);
+ CHeaderField(const char* name, const char* body, bool utf8 = false);
+ ~CHeaderField();
+ inline bool Valid() const { return m_fname; }
+ inline const char* fname() const { return m_fname; }
+ inline const char* fbody() const { return m_fbody; }
+
+ // txt must be utf-8 or 7-bit; supposed that this function will be called
+ // when the charset of the txt is known
+ void set_fbody(const char* txt);
+
+ void GetUnfoldedString(nsString& dest, const char* fallbackCharset) const;
+ private:
+ char* m_fname;
+ char* m_fbody;
+ bool m_fbody_utf8;
+ }; //class HeaderField
+
+ class write_to_stream {
+ public:
+ write_to_stream(nsIOutputStream *pDst) : m_pDst(pDst), m_rv(NS_OK) {}
+ void operator () (const CHeaderField* f);
+ inline operator nsresult() const { return m_rv; }
+ private:
+ nsIOutputStream *m_pDst;
+ nsresult m_rv;
+ };
+
+ // Search helper
+ class fname_equals {
+ public:
+ fname_equals(const char* search) : m_search(search) {}
+ inline bool operator () (const CHeaderField* f) const { return stricmp(f->fname(), m_search) == 0; }
+ private:
+ const char* m_search;
+ }; // class fname_equals
+
+ // The common array of special headers' names
+ static const char* Specials[hdrMax];
+
+ std::vector<CHeaderField*> m_headerFields;
+ CHeaderField* m_SpecialHeaders[hdrMax]; // Pointers into the m_headerFields
+
+ void ClearHeaderFields();
+ void Add(CHeaderField* f);
+ static SpecialHeader CheckSpecialHeader(const char* fname);
+ const CHeaderField* CFind(const char* name) const;
+ inline CHeaderField* Find(const char* name) { return const_cast<CHeaderField*>(CFind(name)); }
+
+}; // class CMapiMessageHeaders
+
+//////////////////////////////////////////////////////
+
+class CMapiMessage {
+public:
+ CMapiMessage(LPMESSAGE lpMsg);
+ ~CMapiMessage();
+
+ // Attachments
+ // Ordinary (not embedded) attachments.
+ nsresult GetAttachments(nsIArray **aArray);
+ // Embedded attachments
+ size_t EmbeddedAttachmentsCount() const { return m_embattachments.size(); }
+ bool GetEmbeddedAttachmentInfo(unsigned int i, nsIURI **uri, const char **cid,
+ const char **name) const;
+ // We don't check MSGFLAG_HASATTACH, since it returns true even if there are
+ // only embedded attachmentsin the message. TB only counts the ordinary
+ // attachments when shows the message status, so here we check only for the
+ // ordinary attachments.
+ inline bool HasAttach() const { return !m_stdattachments.empty(); }
+
+ // Retrieve info for message
+ inline bool BodyIsHtml(void) const { return m_bodyIsHtml;}
+ const char *GetFromLine(int& len) const {
+ if (m_fromLine.IsEmpty())
+ return NULL;
+ else {
+ len = m_fromLine.Length();
+ return m_fromLine.get();}
+ }
+ inline CMapiMessageHeaders *GetHeaders() { return &m_headers; }
+ inline const wchar_t *GetBody(void) const { return m_body.get(); }
+ inline size_t GetBodyLen(void) const { return m_body.Length(); }
+ void GetBody(nsCString& dest) const;
+ inline const char *GetBodyCharset(void) const { return m_mimeCharset.get();}
+ inline bool IsRead() const { return m_msgFlags & MSGFLAG_READ; }
+ inline bool IsReplied() const {
+ return (m_msgLastVerb == EXCHIVERB_REPLYTOSENDER) ||
+ (m_msgLastVerb == EXCHIVERB_REPLYTOALL); }
+ inline bool IsForvarded() const {
+ return m_msgLastVerb == EXCHIVERB_FORWARD; }
+
+ bool HasContentHeader(void) const {
+ return !m_mimeContentType.IsEmpty();}
+ bool HasMimeVersion(void) const {
+ return m_headers.Value(CMapiMessageHeaders::hdrMimeVersion); }
+ const char *GetMimeContent(void) const { return m_mimeContentType.get();}
+ int32_t GetMimeContentLen(void) const { return m_mimeContentType.Length();}
+ const char *GetMimeBoundary(void) const { return m_mimeBoundary.get();}
+
+ // The only required part of a message is its header
+ inline bool ValidState() const { return !m_headers.IsEmpty(); }
+ inline bool FullMessageDownloaded() const { return !m_dldStateHeadersOnly; }
+
+private:
+ struct attach_data {
+ nsCOMPtr<nsIURI> orig_url;
+ nsCOMPtr<nsIFile> tmp_file;
+ char *type;
+ char *encoding;
+ char *real_name;
+ char *cid;
+ bool delete_file;
+ attach_data() : type(0), encoding(0), real_name(0), cid(0), delete_file(false) {}
+ };
+
+ static const nsCString m_whitespace;
+
+ LPMESSAGE m_lpMsg;
+
+ bool m_dldStateHeadersOnly; // if the message has not been downloaded yet
+ CMapiMessageHeaders m_headers;
+ nsCString m_fromLine; // utf-8
+ nsCString m_mimeContentType; // utf-8
+ nsCString m_mimeBoundary; // utf-8
+ nsCString m_mimeCharset; // utf-8
+
+ std::vector<attach_data*> m_stdattachments;
+ std::vector<attach_data*> m_embattachments; // Embedded
+
+ nsString m_body; // to be converted from UTF-16 using m_mimeCharset
+ bool m_bodyIsHtml;
+
+ uint32_t m_msgFlags;
+ uint32_t m_msgLastVerb;
+
+ nsCOMPtr<nsIIOService> m_pIOService;
+
+ void GetDownloadState();
+
+ // Headers - fetch will get PR_TRANSPORT_MESSAGE_HEADERS
+ // or if they do not exist will build a header from
+ // PR_DISPLAY_TO, _CC, _BCC
+ // PR_SUBJECT
+ // PR_MESSAGE_RECIPIENTS
+ // and PR_CREATION_TIME if needed?
+ bool FetchHeaders(void);
+ bool FetchBody(void);
+ void FetchFlags(void);
+
+ static bool GetTmpFile(/*out*/ nsIFile **aResult);
+ static bool CopyMsgAttachToFile(LPATTACH lpAttach, /*out*/ nsIFile **tmp_file);
+ static bool CopyBinAttachToFile(LPATTACH lpAttach, nsIFile **tmp_file);
+
+ static void ClearAttachment(attach_data* data);
+ void ClearAttachments();
+ bool AddAttachment(DWORD aNum);
+ bool IterateAttachTable(LPMAPITABLE tbl);
+ bool GetURL(nsIFile *aFile, nsIURI **url);
+ void ProcessAttachments();
+
+ bool EnsureHeader(CMapiMessageHeaders::SpecialHeader special, ULONG mapiTag);
+ bool EnsureDate();
+
+ void ProcessContentType();
+ bool CheckBodyInCharsetRange(const char* charset);
+ void FormatDateTime(SYSTEMTIME& tm, nsCString& s, bool includeTZ = true);
+ void BuildFromLine(void);
+
+ inline static bool IsSpace(char c) {
+ return c == ' ' || c == '\r' || c == '\n' || c == '\b' || c == '\t';}
+ inline static bool IsSpace(wchar_t c) {
+ return ((c & 0xFF) == c) && IsSpace(static_cast<char>(c)); } // Avoid false detections
+};
+
+#endif /* MapiMessage_h__ */
diff --git a/mailnews/import/outlook/src/MapiMimeTypes.cpp b/mailnews/import/outlook/src/MapiMimeTypes.cpp
new file mode 100644
index 000000000..38b2046db
--- /dev/null
+++ b/mailnews/import/outlook/src/MapiMimeTypes.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "MapiMimeTypes.h"
+
+uint8_t CMimeTypes::m_mimeBuffer[kMaxMimeTypeSize];
+
+
+BOOL CMimeTypes::GetKey(HKEY root, LPCTSTR pName, PHKEY pKey)
+{
+ LONG result = RegOpenKeyEx(root, pName, 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, pKey);
+ return result == ERROR_SUCCESS;
+}
+
+BOOL CMimeTypes::GetValueBytes(HKEY rootKey, LPCTSTR pValName, LPBYTE *ppBytes)
+{
+ LONG err;
+ DWORD bufSz;
+
+ *ppBytes = NULL;
+ // Get the installed directory
+ err = RegQueryValueEx(rootKey, pValName, NULL, NULL, NULL, &bufSz);
+ if (err == ERROR_SUCCESS) {
+ *ppBytes = new BYTE[bufSz];
+ err = RegQueryValueEx(rootKey, pValName, NULL, NULL, *ppBytes, &bufSz);
+ if (err == ERROR_SUCCESS) {
+ return TRUE;
+ }
+ delete *ppBytes;
+ *ppBytes = NULL;
+ }
+ return FALSE;
+}
+
+void CMimeTypes::ReleaseValueBytes(LPBYTE pBytes)
+{
+ if (pBytes)
+ delete pBytes;
+}
+
+BOOL CMimeTypes::GetMimeTypeFromReg(const nsCString& ext, LPBYTE *ppBytes)
+{
+ HKEY extensionKey;
+ BOOL result = FALSE;
+ *ppBytes = NULL;
+ if (GetKey(HKEY_CLASSES_ROOT, ext.get(), &extensionKey)) {
+ result = GetValueBytes(extensionKey, "Content Type", ppBytes);
+ RegCloseKey(extensionKey);
+ }
+
+ return result;
+}
+
+uint8_t * CMimeTypes::GetMimeType(const nsString& theExt)
+{
+ nsCString ext;
+ LossyCopyUTF16toASCII(theExt, ext);
+ return GetMimeType(ext);
+}
+
+uint8_t * CMimeTypes::GetMimeType(const nsCString& theExt)
+{
+ nsCString ext = theExt;
+ if (ext.Length()) {
+ if (ext.First() != '.') {
+ ext = ".";
+ ext += theExt;
+ }
+ }
+
+
+ BOOL result = FALSE;
+ int len;
+
+ if (!ext.Length())
+ return NULL;
+ LPBYTE pByte;
+ if (GetMimeTypeFromReg(ext, &pByte)) {
+ len = strlen((const char *) pByte);
+ if (len && (len < kMaxMimeTypeSize)) {
+ memcpy(m_mimeBuffer, pByte, len);
+ m_mimeBuffer[len] = 0;
+ result = TRUE;
+ }
+ ReleaseValueBytes(pByte);
+ }
+
+ if (result)
+ return m_mimeBuffer;
+
+ return NULL;
+}
diff --git a/mailnews/import/outlook/src/MapiMimeTypes.h b/mailnews/import/outlook/src/MapiMimeTypes.h
new file mode 100644
index 000000000..61a90dd19
--- /dev/null
+++ b/mailnews/import/outlook/src/MapiMimeTypes.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MapiMimeTypes_h___
+#define MapiMimeTypes_h___
+
+#include <windows.h>
+
+#define kMaxMimeTypeSize 256
+
+class CMimeTypes {
+public:
+
+static uint8_t * GetMimeType(const nsCString& theExt);
+static uint8_t * GetMimeType(const nsString& theExt);
+
+protected:
+ // Registry stuff
+static BOOL GetKey(HKEY root, LPCTSTR pName, PHKEY pKey);
+static BOOL GetValueBytes(HKEY rootKey, LPCTSTR pValName, LPBYTE *ppBytes);
+static void ReleaseValueBytes(LPBYTE pBytes);
+static BOOL GetMimeTypeFromReg(const nsCString& ext, LPBYTE *ppBytes);
+
+
+static uint8_t m_mimeBuffer[kMaxMimeTypeSize];
+};
+
+#endif /* MapiMimeTypes_h__ */
+
diff --git a/mailnews/import/outlook/src/MapiTagStrs.cpp b/mailnews/import/outlook/src/MapiTagStrs.cpp
new file mode 100644
index 000000000..217b2186c
--- /dev/null
+++ b/mailnews/import/outlook/src/MapiTagStrs.cpp
@@ -0,0 +1,1070 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Message envelope properties
+ */
+
+case PR_ACKNOWLEDGEMENT_MODE:
+ s = "PR_ACKNOWLEDGEMENT_MODE"; break;
+case PR_ALTERNATE_RECIPIENT_ALLOWED:
+ s = "PR_ALTERNATE_RECIPIENT_ALLOWED"; break;
+case PR_AUTHORIZING_USERS:
+ s = "PR_AUTHORIZING_USERS"; break;
+case PR_AUTO_FORWARD_COMMENT:
+ s = "PR_AUTO_FORWARD_COMMENT"; break;
+case PR_AUTO_FORWARDED:
+ s = "PR_AUTO_FORWARDED"; break;
+case PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID:
+ s = "PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID"; break;
+case PR_CONTENT_CORRELATOR:
+ s = "PR_CONTENT_CORRELATOR"; break;
+case PR_CONTENT_IDENTIFIER:
+ s = "PR_CONTENT_IDENTIFIER"; break;
+case PR_CONTENT_LENGTH:
+ s = "PR_CONTENT_LENGTH"; break;
+case PR_CONTENT_RETURN_REQUESTED:
+ s = "PR_CONTENT_RETURN_REQUESTED"; break;
+
+
+
+case PR_CONVERSATION_KEY:
+ s = "PR_CONVERSATION_KEY"; break;
+
+case PR_CONVERSION_EITS:
+ s = "PR_CONVERSION_EITS"; break;
+case PR_CONVERSION_WITH_LOSS_PROHIBITED:
+ s = "PR_CONVERSION_WITH_LOSS_PROHIBITED"; break;
+case PR_CONVERTED_EITS:
+ s = "PR_CONVERTED_EITS"; break;
+case PR_DEFERRED_DELIVERY_TIME:
+ s = "PR_DEFERRED_DELIVERY_TIME"; break;
+case PR_DELIVER_TIME:
+ s = "PR_DELIVER_TIME"; break;
+case PR_DISCARD_REASON:
+ s = "PR_DISCARD_REASON"; break;
+case PR_DISCLOSURE_OF_RECIPIENTS:
+ s = "PR_DISCLOSURE_OF_RECIPIENTS"; break;
+case PR_DL_EXPANSION_HISTORY:
+ s = "PR_DL_EXPANSION_HISTORY"; break;
+case PR_DL_EXPANSION_PROHIBITED:
+ s = "PR_DL_EXPANSION_PROHIBITED"; break;
+case PR_EXPIRY_TIME:
+ s = "PR_EXPIRY_TIME"; break;
+case PR_IMPLICIT_CONVERSION_PROHIBITED:
+ s = "PR_IMPLICIT_CONVERSION_PROHIBITED"; break;
+case PR_IMPORTANCE:
+ s = "PR_IMPORTANCE"; break;
+case PR_IPM_ID:
+ s = "PR_IPM_ID"; break;
+case PR_LATEST_DELIVERY_TIME:
+ s = "PR_LATEST_DELIVERY_TIME"; break;
+case PR_MESSAGE_CLASS:
+ s = "PR_MESSAGE_CLASS"; break;
+case PR_MESSAGE_DELIVERY_ID:
+ s = "PR_MESSAGE_DELIVERY_ID"; break;
+
+
+
+
+
+case PR_MESSAGE_SECURITY_LABEL:
+ s = "PR_MESSAGE_SECURITY_LABEL"; break;
+case PR_OBSOLETED_IPMS:
+ s = "PR_OBSOLETED_IPMS"; break;
+case PR_ORIGINALLY_INTENDED_RECIPIENT_NAME:
+ s = "PR_ORIGINALLY_INTENDED_RECIPIENT_NAME"; break;
+case PR_ORIGINAL_EITS:
+ s = "PR_ORIGINAL_EITS"; break;
+case PR_ORIGINATOR_CERTIFICATE:
+ s = "PR_ORIGINATOR_CERTIFICATE"; break;
+case PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED:
+ s = "PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED"; break;
+case PR_ORIGINATOR_RETURN_ADDRESS:
+ s = "PR_ORIGINATOR_RETURN_ADDRESS"; break;
+
+
+
+case PR_PARENT_KEY:
+ s = "PR_PARENT_KEY"; break;
+case PR_PRIORITY:
+ s = "PR_PRIORITY"; break;
+
+
+
+case PR_ORIGIN_CHECK:
+ s = "PR_ORIGIN_CHECK"; break;
+case PR_PROOF_OF_SUBMISSION_REQUESTED:
+ s = "PR_PROOF_OF_SUBMISSION_REQUESTED"; break;
+case PR_READ_RECEIPT_REQUESTED:
+ s = "PR_READ_RECEIPT_REQUESTED"; break;
+case PR_RECEIPT_TIME:
+ s = "PR_RECEIPT_TIME"; break;
+case PR_RECIPIENT_REASSIGNMENT_PROHIBITED:
+ s = "PR_RECIPIENT_REASSIGNMENT_PROHIBITED"; break;
+case PR_REDIRECTION_HISTORY:
+ s = "PR_REDIRECTION_HISTORY"; break;
+case PR_RELATED_IPMS:
+ s = "PR_RELATED_IPMS"; break;
+case PR_ORIGINAL_SENSITIVITY:
+ s = "PR_ORIGINAL_SENSITIVITY"; break;
+case PR_LANGUAGES:
+ s = "PR_LANGUAGES"; break;
+case PR_REPLY_TIME:
+ s = "PR_REPLY_TIME"; break;
+case PR_REPORT_TAG:
+ s = "PR_REPORT_TAG"; break;
+case PR_REPORT_TIME:
+ s = "PR_REPORT_TIME"; break;
+case PR_RETURNED_IPM:
+ s = "PR_RETURNED_IPM"; break;
+case PR_SECURITY:
+ s = "PR_SECURITY"; break;
+case PR_INCOMPLETE_COPY:
+ s = "PR_INCOMPLETE_COPY"; break;
+case PR_SENSITIVITY:
+ s = "PR_SENSITIVITY"; break;
+case PR_SUBJECT:
+ s = "PR_SUBJECT"; break;
+case PR_SUBJECT_IPM:
+ s = "PR_SUBJECT_IPM"; break;
+case PR_CLIENT_SUBMIT_TIME:
+ s = "PR_CLIENT_SUBMIT_TIME"; break;
+case PR_REPORT_NAME:
+ s = "PR_REPORT_NAME"; break;
+case PR_SENT_REPRESENTING_SEARCH_KEY:
+ s = "PR_SENT_REPRESENTING_SEARCH_KEY"; break;
+case PR_X400_CONTENT_TYPE:
+ s = "PR_X400_CONTENT_TYPE"; break;
+case PR_SUBJECT_PREFIX:
+ s = "PR_SUBJECT_PREFIX"; break;
+case PR_NON_RECEIPT_REASON:
+ s = "PR_NON_RECEIPT_REASON"; break;
+case PR_RECEIVED_BY_ENTRYID:
+ s = "PR_RECEIVED_BY_ENTRYID"; break;
+case PR_RECEIVED_BY_NAME:
+ s = "PR_RECEIVED_BY_NAME"; break;
+case PR_SENT_REPRESENTING_ENTRYID:
+ s = "PR_SENT_REPRESENTING_ENTRYID"; break;
+case PR_SENT_REPRESENTING_NAME:
+ s = "PR_SENT_REPRESENTING_NAME"; break;
+case PR_RCVD_REPRESENTING_ENTRYID:
+ s = "PR_RCVD_REPRESENTING_ENTRYID"; break;
+case PR_RCVD_REPRESENTING_NAME:
+ s = "PR_RCVD_REPRESENTING_NAME"; break;
+case PR_REPORT_ENTRYID:
+ s = "PR_REPORT_ENTRYID"; break;
+case PR_READ_RECEIPT_ENTRYID:
+ s = "PR_READ_RECEIPT_ENTRYID"; break;
+case PR_MESSAGE_SUBMISSION_ID:
+ s = "PR_MESSAGE_SUBMISSION_ID"; break;
+case PR_PROVIDER_SUBMIT_TIME:
+ s = "PR_PROVIDER_SUBMIT_TIME"; break;
+case PR_ORIGINAL_SUBJECT:
+ s = "PR_ORIGINAL_SUBJECT"; break;
+case PR_DISC_VAL:
+ s = "PR_DISC_VAL"; break;
+case PR_ORIG_MESSAGE_CLASS:
+ s = "PR_ORIG_MESSAGE_CLASS"; break;
+case PR_ORIGINAL_AUTHOR_ENTRYID:
+ s = "PR_ORIGINAL_AUTHOR_ENTRYID"; break;
+case PR_ORIGINAL_AUTHOR_NAME:
+ s = "PR_ORIGINAL_AUTHOR_NAME"; break;
+case PR_ORIGINAL_SUBMIT_TIME:
+ s = "PR_ORIGINAL_SUBMIT_TIME"; break;
+case PR_REPLY_RECIPIENT_ENTRIES:
+ s = "PR_REPLY_RECIPIENT_ENTRIES"; break;
+case PR_REPLY_RECIPIENT_NAMES:
+ s = "PR_REPLY_RECIPIENT_NAMES"; break;
+
+case PR_RECEIVED_BY_SEARCH_KEY:
+ s = "PR_RECEIVED_BY_SEARCH_KEY"; break;
+case PR_RCVD_REPRESENTING_SEARCH_KEY:
+ s = "PR_RCVD_REPRESENTING_SEARCH_KEY"; break;
+case PR_READ_RECEIPT_SEARCH_KEY:
+ s = "PR_READ_RECEIPT_SEARCH_KEY"; break;
+case PR_REPORT_SEARCH_KEY:
+ s = "PR_REPORT_SEARCH_KEY"; break;
+case PR_ORIGINAL_DELIVERY_TIME:
+ s = "PR_ORIGINAL_DELIVERY_TIME"; break;
+case PR_ORIGINAL_AUTHOR_SEARCH_KEY:
+ s = "PR_ORIGINAL_AUTHOR_SEARCH_KEY"; break;
+
+case PR_MESSAGE_TO_ME:
+ s = "PR_MESSAGE_TO_ME"; break;
+case PR_MESSAGE_CC_ME:
+ s = "PR_MESSAGE_CC_ME"; break;
+case PR_MESSAGE_RECIP_ME:
+ s = "PR_MESSAGE_RECIP_ME"; break;
+
+case PR_ORIGINAL_SENDER_NAME:
+ s = "PR_ORIGINAL_SENDER_NAME"; break;
+case PR_ORIGINAL_SENDER_ENTRYID:
+ s = "PR_ORIGINAL_SENDER_ENTRYID"; break;
+case PR_ORIGINAL_SENDER_SEARCH_KEY:
+ s = "PR_ORIGINAL_SENDER_SEARCH_KEY"; break;
+case PR_ORIGINAL_SENT_REPRESENTING_NAME:
+ s = "PR_ORIGINAL_SENT_REPRESENTING_NAME"; break;
+case PR_ORIGINAL_SENT_REPRESENTING_ENTRYID:
+ s = "PR_ORIGINAL_SENT_REPRESENTING_ENTRYID"; break;
+case PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY:
+ s = "PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY"; break;
+
+case PR_START_DATE:
+ s = "PR_START_DATE"; break;
+case PR_END_DATE:
+ s = "PR_END_DATE"; break;
+case PR_OWNER_APPT_ID:
+ s = "PR_OWNER_APPT_ID"; break;
+case PR_RESPONSE_REQUESTED:
+ s = "PR_RESPONSE_REQUESTED"; break;
+
+case PR_SENT_REPRESENTING_ADDRTYPE:
+ s = "PR_SENT_REPRESENTING_ADDRTYPE"; break;
+case PR_SENT_REPRESENTING_EMAIL_ADDRESS:
+ s = "PR_SENT_REPRESENTING_EMAIL_ADDRESS"; break;
+
+case PR_ORIGINAL_SENDER_ADDRTYPE:
+ s = "PR_ORIGINAL_SENDER_ADDRTYPE"; break;
+case PR_ORIGINAL_SENDER_EMAIL_ADDRESS:
+ s = "PR_ORIGINAL_SENDER_EMAIL_ADDRESS"; break;
+
+case PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE:
+ s = "PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE"; break;
+case PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS:
+ s = "PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS"; break;
+
+case PR_CONVERSATION_TOPIC:
+ s = "PR_CONVERSATION_TOPIC"; break;
+case PR_CONVERSATION_INDEX:
+ s = "PR_CONVERSATION_INDEX"; break;
+
+case PR_ORIGINAL_DISPLAY_BCC:
+ s = "PR_ORIGINAL_DISPLAY_BCC"; break;
+case PR_ORIGINAL_DISPLAY_CC:
+ s = "PR_ORIGINAL_DISPLAY_CC"; break;
+case PR_ORIGINAL_DISPLAY_TO:
+ s = "PR_ORIGINAL_DISPLAY_TO"; break;
+
+case PR_RECEIVED_BY_ADDRTYPE:
+ s = "PR_RECEIVED_BY_ADDRTYPE"; break;
+case PR_RECEIVED_BY_EMAIL_ADDRESS:
+ s = "PR_RECEIVED_BY_EMAIL_ADDRESS"; break;
+
+case PR_RCVD_REPRESENTING_ADDRTYPE:
+ s = "PR_RCVD_REPRESENTING_ADDRTYPE"; break;
+case PR_RCVD_REPRESENTING_EMAIL_ADDRESS:
+ s = "PR_RCVD_REPRESENTING_EMAIL_ADDRESS"; break;
+
+case PR_ORIGINAL_AUTHOR_ADDRTYPE:
+ s = "PR_ORIGINAL_AUTHOR_ADDRTYPE"; break;
+case PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS:
+ s = "PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS"; break;
+
+case PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE:
+ s = "PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE"; break;
+case PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS:
+ s = "PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS"; break;
+
+case PR_TRANSPORT_MESSAGE_HEADERS:
+ s = "PR_TRANSPORT_MESSAGE_HEADERS"; break;
+
+case PR_DELEGATION:
+ s = "PR_DELEGATION"; break;
+
+case PR_TNEF_CORRELATION_KEY:
+ s = "PR_TNEF_CORRELATION_KEY"; break;
+
+
+
+/*
+ * Message content properties
+ */
+
+case PR_BODY:
+ s = "PR_BODY"; break;
+case PR_REPORT_TEXT:
+ s = "PR_REPORT_TEXT"; break;
+case PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY:
+ s = "PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY"; break;
+case PR_REPORTING_DL_NAME:
+ s = "PR_REPORTING_DL_NAME"; break;
+case PR_REPORTING_MTA_CERTIFICATE:
+ s = "PR_REPORTING_MTA_CERTIFICATE"; break;
+
+/* Removed PR_REPORT_ORIGIN_AUTHENTICATION_CHECK with DCR 3865, use PR_ORIGIN_CHECK */
+
+case PR_RTF_SYNC_BODY_CRC:
+ s = "PR_RTF_SYNC_BODY_CRC"; break;
+case PR_RTF_SYNC_BODY_COUNT:
+ s = "PR_RTF_SYNC_BODY_COUNT"; break;
+case PR_RTF_SYNC_BODY_TAG:
+ s = "PR_RTF_SYNC_BODY_TAG"; break;
+case PR_RTF_COMPRESSED:
+ s = "PR_RTF_COMPRESSED"; break;
+case PR_RTF_SYNC_PREFIX_COUNT:
+ s = "PR_RTF_SYNC_PREFIX_COUNT"; break;
+case PR_RTF_SYNC_TRAILING_COUNT:
+ s = "PR_RTF_SYNC_TRAILING_COUNT"; break;
+case PR_ORIGINALLY_INTENDED_RECIP_ENTRYID:
+ s = "PR_ORIGINALLY_INTENDED_RECIP_ENTRYID"; break;
+
+/*
+ * Reserved 0x1100-0x1200
+ */
+
+
+/*
+ * Message recipient properties
+ */
+
+case PR_CONTENT_INTEGRITY_CHECK:
+ s = "PR_CONTENT_INTEGRITY_CHECK"; break;
+case PR_EXPLICIT_CONVERSION:
+ s = "PR_EXPLICIT_CONVERSION"; break;
+case PR_IPM_RETURN_REQUESTED:
+ s = "PR_IPM_RETURN_REQUESTED"; break;
+case PR_MESSAGE_TOKEN:
+ s = "PR_MESSAGE_TOKEN"; break;
+case PR_NDR_REASON_CODE:
+ s = "PR_NDR_REASON_CODE"; break;
+case PR_NDR_DIAG_CODE:
+ s = "PR_NDR_DIAG_CODE"; break;
+case PR_NON_RECEIPT_NOTIFICATION_REQUESTED:
+ s = "PR_NON_RECEIPT_NOTIFICATION_REQUESTED"; break;
+case PR_DELIVERY_POINT:
+ s = "PR_DELIVERY_POINT"; break;
+
+case PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED:
+ s = "PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED"; break;
+case PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT:
+ s = "PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT"; break;
+case PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY:
+ s = "PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY"; break;
+case PR_PHYSICAL_DELIVERY_MODE:
+ s = "PR_PHYSICAL_DELIVERY_MODE"; break;
+case PR_PHYSICAL_DELIVERY_REPORT_REQUEST:
+ s = "PR_PHYSICAL_DELIVERY_REPORT_REQUEST"; break;
+case PR_PHYSICAL_FORWARDING_ADDRESS:
+ s = "PR_PHYSICAL_FORWARDING_ADDRESS"; break;
+case PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED:
+ s = "PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED"; break;
+case PR_PHYSICAL_FORWARDING_PROHIBITED:
+ s = "PR_PHYSICAL_FORWARDING_PROHIBITED"; break;
+case PR_PHYSICAL_RENDITION_ATTRIBUTES:
+ s = "PR_PHYSICAL_RENDITION_ATTRIBUTES"; break;
+case PR_PROOF_OF_DELIVERY:
+ s = "PR_PROOF_OF_DELIVERY"; break;
+case PR_PROOF_OF_DELIVERY_REQUESTED:
+ s = "PR_PROOF_OF_DELIVERY_REQUESTED"; break;
+case PR_RECIPIENT_CERTIFICATE:
+ s = "PR_RECIPIENT_CERTIFICATE"; break;
+case PR_RECIPIENT_NUMBER_FOR_ADVICE:
+ s = "PR_RECIPIENT_NUMBER_FOR_ADVICE"; break;
+case PR_RECIPIENT_TYPE:
+ s = "PR_RECIPIENT_TYPE"; break;
+case PR_REGISTERED_MAIL_TYPE:
+ s = "PR_REGISTERED_MAIL_TYPE"; break;
+case PR_REPLY_REQUESTED:
+ s = "PR_REPLY_REQUESTED"; break;
+case PR_REQUESTED_DELIVERY_METHOD:
+ s = "PR_REQUESTED_DELIVERY_METHOD"; break;
+case PR_SENDER_ENTRYID:
+ s = "PR_SENDER_ENTRYID"; break;
+case PR_SENDER_NAME:
+ s = "PR_SENDER_NAME"; break;
+case PR_SUPPLEMENTARY_INFO:
+ s = "PR_SUPPLEMENTARY_INFO"; break;
+case PR_TYPE_OF_MTS_USER:
+ s = "PR_TYPE_OF_MTS_USER"; break;
+case PR_SENDER_SEARCH_KEY:
+ s = "PR_SENDER_SEARCH_KEY"; break;
+case PR_SENDER_ADDRTYPE:
+ s = "PR_SENDER_ADDRTYPE"; break;
+case PR_SENDER_EMAIL_ADDRESS:
+ s = "PR_SENDER_EMAIL_ADDRESS"; break;
+
+/*
+ * Message non-transmittable properties
+ */
+
+/*
+ * The two tags, PR_MESSAGE_RECIPIENTS and PR_MESSAGE_ATTACHMENTS,
+ * are to be used in the exclude list passed to
+ * IMessage::CopyTo when the caller wants either the recipients or attachments
+ * of the message to not get copied. It is also used in the ProblemArray
+ * return from IMessage::CopyTo when an error is encountered copying them
+ */
+
+case PR_CURRENT_VERSION:
+ s = "PR_CURRENT_VERSION"; break;
+case PR_DELETE_AFTER_SUBMIT:
+ s = "PR_DELETE_AFTER_SUBMIT"; break;
+case PR_DISPLAY_BCC:
+ s = "PR_DISPLAY_BCC"; break;
+case PR_DISPLAY_CC:
+ s = "PR_DISPLAY_CC"; break;
+case PR_DISPLAY_TO:
+ s = "PR_DISPLAY_TO"; break;
+case PR_PARENT_DISPLAY:
+ s = "PR_PARENT_DISPLAY"; break;
+case PR_MESSAGE_DELIVERY_TIME:
+ s = "PR_MESSAGE_DELIVERY_TIME"; break;
+case PR_MESSAGE_FLAGS:
+ s = "PR_MESSAGE_FLAGS"; break;
+case PR_MESSAGE_SIZE:
+ s = "PR_MESSAGE_SIZE"; break;
+case PR_PARENT_ENTRYID:
+ s = "PR_PARENT_ENTRYID"; break;
+case PR_SENTMAIL_ENTRYID:
+ s = "PR_SENTMAIL_ENTRYID"; break;
+case PR_CORRELATE:
+ s = "PR_CORRELATE"; break;
+case PR_CORRELATE_MTSID:
+ s = "PR_CORRELATE_MTSID"; break;
+case PR_DISCRETE_VALUES:
+ s = "PR_DISCRETE_VALUES"; break;
+case PR_RESPONSIBILITY:
+ s = "PR_RESPONSIBILITY"; break;
+case PR_SPOOLER_STATUS:
+ s = "PR_SPOOLER_STATUS"; break;
+case PR_TRANSPORT_STATUS:
+ s = "PR_TRANSPORT_STATUS"; break;
+case PR_MESSAGE_RECIPIENTS:
+ s = "PR_MESSAGE_RECIPIENTS"; break;
+case PR_MESSAGE_ATTACHMENTS:
+ s = "PR_MESSAGE_ATTACHMENTS"; break;
+case PR_SUBMIT_FLAGS:
+ s = "PR_SUBMIT_FLAGS"; break;
+case PR_RECIPIENT_STATUS:
+ s = "PR_RECIPIENT_STATUS"; break;
+case PR_TRANSPORT_KEY:
+ s = "PR_TRANSPORT_KEY"; break;
+case PR_MSG_STATUS:
+ s = "PR_MSG_STATUS"; break;
+case PR_MESSAGE_DOWNLOAD_TIME:
+ s = "PR_MESSAGE_DOWNLOAD_TIME"; break;
+case PR_CREATION_VERSION:
+ s = "PR_CREATION_VERSION"; break;
+case PR_MODIFY_VERSION:
+ s = "PR_MODIFY_VERSION"; break;
+case PR_HASATTACH:
+ s = "PR_HASATTACH"; break;
+case PR_BODY_CRC:
+ s = "PR_BODY_CRC"; break;
+case PR_NORMALIZED_SUBJECT:
+ s = "PR_NORMALIZED_SUBJECT"; break;
+case PR_RTF_IN_SYNC:
+ s = "PR_RTF_IN_SYNC"; break;
+case PR_ATTACH_SIZE:
+ s = "PR_ATTACH_SIZE"; break;
+case PR_ATTACH_NUM:
+ s = "PR_ATTACH_NUM"; break;
+case PR_PREPROCESS:
+ s = "PR_PREPROCESS"; break;
+
+/* PR_ORIGINAL_DISPLAY_TO, _CC, and _BCC moved to transmittible range 03/09/95 */
+
+case PR_ORIGINATING_MTA_CERTIFICATE:
+ s = "PR_ORIGINATING_MTA_CERTIFICATE"; break;
+case PR_PROOF_OF_SUBMISSION:
+ s = "PR_PROOF_OF_SUBMISSION"; break;
+
+
+/*
+ * The range of non-message and non-recipient property IDs (0x3000 - 0x3FFF) is
+ * further broken down into ranges to make assigning new property IDs easier.
+ *
+ * From To Kind of property
+ * --------------------------------
+ * 3000 32FF MAPI_defined common property
+ * 3200 33FF MAPI_defined form property
+ * 3400 35FF MAPI_defined message store property
+ * 3600 36FF MAPI_defined Folder or AB Container property
+ * 3700 38FF MAPI_defined attachment property
+ * 3900 39FF MAPI_defined address book property
+ * 3A00 3BFF MAPI_defined mailuser property
+ * 3C00 3CFF MAPI_defined DistList property
+ * 3D00 3DFF MAPI_defined Profile Section property
+ * 3E00 3EFF MAPI_defined Status property
+ * 3F00 3FFF MAPI_defined display table property
+ */
+
+/*
+ * Properties common to numerous MAPI objects.
+ *
+ * Those properties that can appear on messages are in the
+ * non-transmittable range for messages. They start at the high
+ * end of that range and work down.
+ *
+ * Properties that never appear on messages are defined in the common
+ * property range (see above).
+ */
+
+/*
+ * properties that are common to multiple objects (including message objects)
+ * -- these ids are in the non-transmittable range
+ */
+
+case PR_ENTRYID:
+ s = "PR_ENTRYID"; break;
+case PR_OBJECT_TYPE:
+ s = "PR_OBJECT_TYPE"; break;
+case PR_ICON:
+ s = "PR_ICON"; break;
+case PR_MINI_ICON:
+ s = "PR_MINI_ICON"; break;
+case PR_STORE_ENTRYID:
+ s = "PR_STORE_ENTRYID"; break;
+case PR_STORE_RECORD_KEY:
+ s = "PR_STORE_RECORD_KEY"; break;
+case PR_RECORD_KEY:
+ s = "PR_RECORD_KEY"; break;
+case PR_MAPPING_SIGNATURE:
+ s = "PR_MAPPING_SIGNATURE"; break;
+case PR_ACCESS_LEVEL:
+ s = "PR_ACCESS_LEVEL"; break;
+case PR_INSTANCE_KEY:
+ s = "PR_INSTANCE_KEY"; break;
+case PR_ROW_TYPE:
+ s = "PR_ROW_TYPE"; break;
+case PR_ACCESS:
+ s = "PR_ACCESS"; break;
+
+/*
+ * properties that are common to multiple objects (usually not including message objects)
+ * -- these ids are in the transmittable range
+ */
+
+case PR_ROWID:
+ s = "PR_ROWID"; break;
+case PR_DISPLAY_NAME:
+ s = "PR_DISPLAY_NAME"; break;
+case PR_ADDRTYPE:
+ s = "PR_ADDRTYPE"; break;
+case PR_EMAIL_ADDRESS:
+ s = "PR_EMAIL_ADDRESS"; break;
+case PR_COMMENT:
+ s = "PR_COMMENT"; break;
+case PR_DEPTH:
+ s = "PR_DEPTH"; break;
+case PR_PROVIDER_DISPLAY:
+ s = "PR_PROVIDER_DISPLAY"; break;
+case PR_CREATION_TIME:
+ s = "PR_CREATION_TIME"; break;
+case PR_LAST_MODIFICATION_TIME:
+ s = "PR_LAST_MODIFICATION_TIME"; break;
+case PR_RESOURCE_FLAGS:
+ s = "PR_RESOURCE_FLAGS"; break;
+case PR_PROVIDER_DLL_NAME:
+ s = "PR_PROVIDER_DLL_NAME"; break;
+case PR_SEARCH_KEY:
+ s = "PR_SEARCH_KEY"; break;
+case PR_PROVIDER_UID:
+ s = "PR_PROVIDER_UID"; break;
+case PR_PROVIDER_ORDINAL:
+ s = "PR_PROVIDER_ORDINAL"; break;
+
+/*
+ * MAPI Form properties
+ */
+case PR_FORM_VERSION:
+ s = "PR_FORM_VERSION"; break;
+case PR_FORM_CLSID:
+ s = "PR_FORM_CLSID"; break;
+case PR_FORM_CONTACT_NAME:
+ s = "PR_FORM_CONTACT_NAME"; break;
+case PR_FORM_CATEGORY:
+ s = "PR_FORM_CATEGORY"; break;
+case PR_FORM_CATEGORY_SUB:
+ s = "PR_FORM_CATEGORY_SUB"; break;
+case PR_FORM_HOST_MAP:
+ s = "PR_FORM_HOST_MAP"; break;
+case PR_FORM_HIDDEN:
+ s = "PR_FORM_HIDDEN"; break;
+case PR_FORM_DESIGNER_NAME:
+ s = "PR_FORM_DESIGNER_NAME"; break;
+case PR_FORM_DESIGNER_GUID:
+ s = "PR_FORM_DESIGNER_GUID"; break;
+case PR_FORM_MESSAGE_BEHAVIOR:
+ s = "PR_FORM_MESSAGE_BEHAVIOR"; break;
+
+/*
+ * Message store properties
+ */
+
+case PR_DEFAULT_STORE:
+ s = "PR_DEFAULT_STORE"; break;
+case PR_STORE_SUPPORT_MASK:
+ s = "PR_STORE_SUPPORT_MASK"; break;
+case PR_STORE_STATE:
+ s = "PR_STORE_STATE"; break;
+
+case PR_IPM_SUBTREE_SEARCH_KEY:
+ s = "PR_IPM_SUBTREE_SEARCH_KEY"; break;
+case PR_IPM_OUTBOX_SEARCH_KEY:
+ s = "PR_IPM_OUTBOX_SEARCH_KEY"; break;
+case PR_IPM_WASTEBASKET_SEARCH_KEY:
+ s = "PR_IPM_WASTEBASKET_SEARCH_KEY"; break;
+case PR_IPM_SENTMAIL_SEARCH_KEY:
+ s = "PR_IPM_SENTMAIL_SEARCH_KEY"; break;
+case PR_MDB_PROVIDER:
+ s = "PR_MDB_PROVIDER"; break;
+case PR_RECEIVE_FOLDER_SETTINGS:
+ s = "PR_RECEIVE_FOLDER_SETTINGS"; break;
+
+case PR_VALID_FOLDER_MASK:
+ s = "PR_VALID_FOLDER_MASK"; break;
+case PR_IPM_SUBTREE_ENTRYID:
+ s = "PR_IPM_SUBTREE_ENTRYID"; break;
+
+case PR_IPM_OUTBOX_ENTRYID:
+ s = "PR_IPM_OUTBOX_ENTRYID"; break;
+case PR_IPM_WASTEBASKET_ENTRYID:
+ s = "PR_IPM_WASTEBASKET_ENTRYID"; break;
+case PR_IPM_SENTMAIL_ENTRYID:
+ s = "PR_IPM_SENTMAIL_ENTRYID"; break;
+case PR_VIEWS_ENTRYID:
+ s = "PR_VIEWS_ENTRYID"; break;
+case PR_COMMON_VIEWS_ENTRYID:
+ s = "PR_COMMON_VIEWS_ENTRYID"; break;
+case PR_FINDER_ENTRYID:
+ s = "PR_FINDER_ENTRYID"; break;
+
+/* Proptags 0x35E8-0x35FF reserved for folders "guaranteed" by PR_VALID_FOLDER_MASK */
+
+
+/*
+ * Folder and AB Container properties
+ */
+
+case PR_CONTAINER_FLAGS:
+ s = "PR_CONTAINER_FLAGS"; break;
+case PR_FOLDER_TYPE:
+ s = "PR_FOLDER_TYPE"; break;
+case PR_CONTENT_COUNT:
+ s = "PR_CONTENT_COUNT"; break;
+case PR_CONTENT_UNREAD:
+ s = "PR_CONTENT_UNREAD"; break;
+case PR_CREATE_TEMPLATES:
+ s = "PR_CREATE_TEMPLATES"; break;
+case PR_DETAILS_TABLE:
+ s = "PR_DETAILS_TABLE"; break;
+case PR_SEARCH:
+ s = "PR_SEARCH"; break;
+case PR_SELECTABLE:
+ s = "PR_SELECTABLE"; break;
+case PR_SUBFOLDERS:
+ s = "PR_SUBFOLDERS"; break;
+case PR_STATUS:
+ s = "PR_STATUS"; break;
+case PR_ANR:
+ s = "PR_ANR"; break;
+case PR_CONTENTS_SORT_ORDER:
+ s = "PR_CONTENTS_SORT_ORDER"; break;
+case PR_CONTAINER_HIERARCHY:
+ s = "PR_CONTAINER_HIERARCHY"; break;
+case PR_CONTAINER_CONTENTS:
+ s = "PR_CONTAINER_CONTENTS"; break;
+case PR_FOLDER_ASSOCIATED_CONTENTS:
+ s = "PR_FOLDER_ASSOCIATED_CONTENTS"; break;
+case PR_DEF_CREATE_DL:
+ s = "PR_DEF_CREATE_DL"; break;
+case PR_DEF_CREATE_MAILUSER:
+ s = "PR_DEF_CREATE_MAILUSER"; break;
+case PR_CONTAINER_CLASS:
+ s = "PR_CONTAINER_CLASS"; break;
+case PR_CONTAINER_MODIFY_VERSION:
+ s = "PR_CONTAINER_MODIFY_VERSION"; break;
+case PR_AB_PROVIDER_ID:
+ s = "PR_AB_PROVIDER_ID"; break;
+case PR_DEFAULT_VIEW_ENTRYID:
+ s = "PR_DEFAULT_VIEW_ENTRYID"; break;
+case PR_ASSOC_CONTENT_COUNT:
+ s = "PR_ASSOC_CONTENT_COUNT"; break;
+
+/* Reserved 0x36C0-0x36FF */
+
+/*
+ * Attachment properties
+ */
+
+case PR_ATTACHMENT_X400_PARAMETERS:
+ s = "PR_ATTACHMENT_X400_PARAMETERS"; break;
+case PR_ATTACH_DATA_OBJ:
+ s = "PR_ATTACH_DATA_OBJ"; break;
+case PR_ATTACH_DATA_BIN:
+ s = "PR_ATTACH_DATA_BIN"; break;
+case PR_ATTACH_ENCODING:
+ s = "PR_ATTACH_ENCODING"; break;
+case PR_ATTACH_EXTENSION:
+ s = "PR_ATTACH_EXTENSION"; break;
+case PR_ATTACH_FILENAME:
+ s = "PR_ATTACH_FILENAME"; break;
+case PR_ATTACH_METHOD:
+ s = "PR_ATTACH_METHOD"; break;
+case PR_ATTACH_LONG_FILENAME:
+ s = "PR_ATTACH_LONG_FILENAME"; break;
+case PR_ATTACH_PATHNAME:
+ s = "PR_ATTACH_PATHNAME"; break;
+case PR_ATTACH_RENDERING:
+ s = "PR_ATTACH_RENDERING"; break;
+case PR_ATTACH_TAG:
+ s = "PR_ATTACH_TAG"; break;
+case PR_RENDERING_POSITION:
+ s = "PR_RENDERING_POSITION"; break;
+case PR_ATTACH_TRANSPORT_NAME:
+ s = "PR_ATTACH_TRANSPORT_NAME"; break;
+case PR_ATTACH_LONG_PATHNAME:
+ s = "PR_ATTACH_LONG_PATHNAME"; break;
+case PR_ATTACH_MIME_TAG:
+ s = "PR_ATTACH_MIME_TAG"; break;
+case PR_ATTACH_ADDITIONAL_INFO:
+ s = "PR_ATTACH_ADDITIONAL_INFO"; break;
+
+/*
+ * AB Object properties
+ */
+
+case PR_DISPLAY_TYPE:
+ s = "PR_DISPLAY_TYPE"; break;
+case PR_TEMPLATEID:
+ s = "PR_TEMPLATEID"; break;
+case PR_PRIMARY_CAPABILITY:
+ s = "PR_PRIMARY_CAPABILITY"; break;
+
+
+/*
+ * Mail user properties
+ */
+case PR_7BIT_DISPLAY_NAME:
+ s = "PR_7BIT_DISPLAY_NAME"; break;
+case PR_ACCOUNT:
+ s = "PR_ACCOUNT"; break;
+case PR_ALTERNATE_RECIPIENT:
+ s = "PR_ALTERNATE_RECIPIENT"; break;
+case PR_CALLBACK_TELEPHONE_NUMBER:
+ s = "PR_CALLBACK_TELEPHONE_NUMBER"; break;
+case PR_CONVERSION_PROHIBITED:
+ s = "PR_CONVERSION_PROHIBITED"; break;
+case PR_DISCLOSE_RECIPIENTS:
+ s = "PR_DISCLOSE_RECIPIENTS"; break;
+case PR_GENERATION:
+ s = "PR_GENERATION"; break;
+case PR_GIVEN_NAME:
+ s = "PR_GIVEN_NAME"; break;
+case PR_GOVERNMENT_ID_NUMBER:
+ s = "PR_GOVERNMENT_ID_NUMBER"; break;
+case PR_BUSINESS_TELEPHONE_NUMBER:
+ s = "PR_BUSINESS_TELEPHONE_NUMBER or PR_OFFICE_TELEPHONE_NUMBER"; break;
+case PR_HOME_TELEPHONE_NUMBER:
+ s = "PR_HOME_TELEPHONE_NUMBER"; break;
+case PR_INITIALS:
+ s = "PR_INITIALS"; break;
+case PR_KEYWORD:
+ s = "PR_KEYWORD"; break;
+case PR_LANGUAGE:
+ s = "PR_LANGUAGE"; break;
+case PR_LOCATION:
+ s = "PR_LOCATION"; break;
+case PR_MAIL_PERMISSION:
+ s = "PR_MAIL_PERMISSION"; break;
+case PR_MHS_COMMON_NAME:
+ s = "PR_MHS_COMMON_NAME"; break;
+case PR_ORGANIZATIONAL_ID_NUMBER:
+ s = "PR_ORGANIZATIONAL_ID_NUMBER"; break;
+case PR_SURNAME:
+ s = "PR_SURNAME"; break;
+case PR_ORIGINAL_ENTRYID:
+ s = "PR_ORIGINAL_ENTRYID"; break;
+case PR_ORIGINAL_DISPLAY_NAME:
+ s = "PR_ORIGINAL_DISPLAY_NAME"; break;
+case PR_ORIGINAL_SEARCH_KEY:
+ s = "PR_ORIGINAL_SEARCH_KEY"; break;
+case PR_POSTAL_ADDRESS:
+ s = "PR_POSTAL_ADDRESS"; break;
+case PR_COMPANY_NAME:
+ s = "PR_COMPANY_NAME"; break;
+case PR_TITLE:
+ s = "PR_TITLE"; break;
+case PR_DEPARTMENT_NAME:
+ s = "PR_DEPARTMENT_NAME"; break;
+case PR_OFFICE_LOCATION:
+ s = "PR_OFFICE_LOCATION"; break;
+case PR_PRIMARY_TELEPHONE_NUMBER:
+ s = "PR_PRIMARY_TELEPHONE_NUMBER"; break;
+case PR_BUSINESS2_TELEPHONE_NUMBER:
+ s = "PR_BUSINESS2_TELEPHONE_NUMBER or PR_OFFICE2_TELEPHONE_NUMBER"; break;
+case PR_MOBILE_TELEPHONE_NUMBER:
+ s = "PR_MOBILE_TELEPHONE_NUMBER or PR_CELLULAR_TELEPHONE_NUMBER"; break;
+case PR_RADIO_TELEPHONE_NUMBER:
+ s = "PR_RADIO_TELEPHONE_NUMBER"; break;
+case PR_CAR_TELEPHONE_NUMBER:
+ s = "PR_CAR_TELEPHONE_NUMBER"; break;
+case PR_OTHER_TELEPHONE_NUMBER:
+ s = "PR_OTHER_TELEPHONE_NUMBER"; break;
+case PR_TRANSMITABLE_DISPLAY_NAME:
+ s = "PR_TRANSMITABLE_DISPLAY_NAME"; break;
+case PR_PAGER_TELEPHONE_NUMBER:
+ s = "PR_PAGER_TELEPHONE_NUMBER or PR_BEEPER_TELEPHONE_NUMBER"; break;
+case PR_USER_CERTIFICATE:
+ s = "PR_USER_CERTIFICATE"; break;
+case PR_PRIMARY_FAX_NUMBER:
+ s = "PR_PRIMARY_FAX_NUMBER"; break;
+case PR_BUSINESS_FAX_NUMBER:
+ s = "PR_BUSINESS_FAX_NUMBER"; break;
+case PR_HOME_FAX_NUMBER:
+ s = "PR_HOME_FAX_NUMBER"; break;
+case PR_COUNTRY:
+ s = "PR_COUNTRY or PR_BUSINESS_ADDRESS_COUNTRY"; break;
+
+case PR_LOCALITY:
+ s = "PR_LOCALITY or PR_BUSINESS_ADDRESS_CITY"; break;
+
+case PR_STATE_OR_PROVINCE:
+ s = "PR_STATE_OR_PROVINCE or PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE"; break;
+
+case PR_STREET_ADDRESS:
+ s = "PR_STREET_ADDRESS or PR_BUSINESS_ADDRESS_STREET"; break;
+
+case PR_POSTAL_CODE:
+ s = "PR_POSTAL_CODE or PR_BUSINESS_ADDRESS_POSTAL_CODE"; break;
+
+
+case PR_POST_OFFICE_BOX:
+ s = "PR_POST_OFFICE_BOX or PR_BUSINESS_ADDRESS_POST_OFFICE_BOX"; break;
+
+
+case PR_TELEX_NUMBER:
+ s = "PR_TELEX_NUMBER"; break;
+case PR_ISDN_NUMBER:
+ s = "PR_ISDN_NUMBER"; break;
+case PR_ASSISTANT_TELEPHONE_NUMBER:
+ s = "PR_ASSISTANT_TELEPHONE_NUMBER"; break;
+case PR_HOME2_TELEPHONE_NUMBER:
+ s = "PR_HOME2_TELEPHONE_NUMBER"; break;
+case PR_ASSISTANT:
+ s = "PR_ASSISTANT"; break;
+case PR_SEND_RICH_INFO:
+ s = "PR_SEND_RICH_INFO"; break;
+
+case PR_WEDDING_ANNIVERSARY:
+ s = "PR_WEDDING_ANNIVERSARY"; break;
+case PR_BIRTHDAY:
+ s = "PR_BIRTHDAY"; break;
+
+
+case PR_HOBBIES:
+ s = "PR_HOBBIES"; break;
+
+case PR_MIDDLE_NAME:
+ s = "PR_MIDDLE_NAME"; break;
+
+case PR_DISPLAY_NAME_PREFIX:
+ s = "PR_DISPLAY_NAME_PREFIX"; break;
+
+case PR_PROFESSION:
+ s = "PR_PROFESSION"; break;
+
+case PR_PREFERRED_BY_NAME:
+ s = "PR_PREFERRED_BY_NAME"; break;
+
+case PR_SPOUSE_NAME:
+ s = "PR_SPOUSE_NAME"; break;
+
+case PR_COMPUTER_NETWORK_NAME:
+ s = "PR_COMPUTER_NETWORK_NAME"; break;
+
+case PR_CUSTOMER_ID:
+ s = "PR_CUSTOMER_ID"; break;
+
+case PR_TTYTDD_PHONE_NUMBER:
+ s = "PR_TTYTDD_PHONE_NUMBER"; break;
+
+case PR_FTP_SITE:
+ s = "PR_FTP_SITE"; break;
+
+case PR_GENDER:
+ s = "PR_GENDER"; break;
+
+case PR_MANAGER_NAME:
+ s = "PR_MANAGER_NAME"; break;
+
+case PR_NICKNAME:
+ s = "PR_NICKNAME"; break;
+
+case PR_PERSONAL_HOME_PAGE:
+ s = "PR_PERSONAL_HOME_PAGE"; break;
+
+
+case PR_BUSINESS_HOME_PAGE:
+ s = "PR_BUSINESS_HOME_PAGE"; break;
+
+case PR_CONTACT_VERSION:
+ s = "PR_CONTACT_VERSION"; break;
+case PR_CONTACT_ENTRYIDS:
+ s = "PR_CONTACT_ENTRYIDS"; break;
+
+case PR_CONTACT_ADDRTYPES:
+ s = "PR_CONTACT_ADDRTYPES"; break;
+
+case PR_CONTACT_DEFAULT_ADDRESS_INDEX:
+ s = "PR_CONTACT_DEFAULT_ADDRESS_INDEX"; break;
+
+case PR_CONTACT_EMAIL_ADDRESSES:
+ s = "PR_CONTACT_EMAIL_ADDRESSES"; break;
+
+
+case PR_COMPANY_MAIN_PHONE_NUMBER:
+ s = "PR_COMPANY_MAIN_PHONE_NUMBER"; break;
+
+case PR_CHILDRENS_NAMES:
+ s = "PR_CHILDRENS_NAMES"; break;
+
+
+
+case PR_HOME_ADDRESS_CITY:
+ s = "PR_HOME_ADDRESS_CITY"; break;
+
+case PR_HOME_ADDRESS_COUNTRY:
+ s = "PR_HOME_ADDRESS_COUNTRY"; break;
+
+case PR_HOME_ADDRESS_POSTAL_CODE:
+ s = "PR_HOME_ADDRESS_POSTAL_CODE"; break;
+
+case PR_HOME_ADDRESS_STATE_OR_PROVINCE:
+ s = "PR_HOME_ADDRESS_STATE_OR_PROVINCE"; break;
+
+case PR_HOME_ADDRESS_STREET:
+ s = "PR_HOME_ADDRESS_STREET"; break;
+
+case PR_HOME_ADDRESS_POST_OFFICE_BOX:
+ s = "PR_HOME_ADDRESS_POST_OFFICE_BOX"; break;
+
+case PR_OTHER_ADDRESS_CITY:
+ s = "PR_OTHER_ADDRESS_CITY"; break;
+
+case PR_OTHER_ADDRESS_COUNTRY:
+ s = "PR_OTHER_ADDRESS_COUNTRY"; break;
+
+case PR_OTHER_ADDRESS_POSTAL_CODE:
+ s = "PR_OTHER_ADDRESS_POSTAL_CODE"; break;
+
+case PR_OTHER_ADDRESS_STATE_OR_PROVINCE:
+ s = "PR_OTHER_ADDRESS_STATE_OR_PROVINCE"; break;
+
+case PR_OTHER_ADDRESS_STREET:
+ s = "PR_OTHER_ADDRESS_STREET"; break;
+
+case PR_OTHER_ADDRESS_POST_OFFICE_BOX:
+ s = "PR_OTHER_ADDRESS_POST_OFFICE_BOX"; break;
+
+
+/*
+ * Profile section properties
+ */
+
+case PR_STORE_PROVIDERS:
+ s = "PR_STORE_PROVIDERS"; break;
+case PR_AB_PROVIDERS:
+ s = "PR_AB_PROVIDERS"; break;
+case PR_TRANSPORT_PROVIDERS:
+ s = "PR_TRANSPORT_PROVIDERS"; break;
+
+case PR_DEFAULT_PROFILE:
+ s = "PR_DEFAULT_PROFILE"; break;
+case PR_AB_SEARCH_PATH:
+ s = "PR_AB_SEARCH_PATH"; break;
+case PR_AB_DEFAULT_DIR:
+ s = "PR_AB_DEFAULT_DIR"; break;
+case PR_AB_DEFAULT_PAB:
+ s = "PR_AB_DEFAULT_PAB"; break;
+
+case PR_FILTERING_HOOKS:
+ s = "PR_FILTERING_HOOKS"; break;
+case PR_SERVICE_NAME:
+ s = "PR_SERVICE_NAME"; break;
+case PR_SERVICE_DLL_NAME:
+ s = "PR_SERVICE_DLL_NAME"; break;
+case PR_SERVICE_ENTRY_NAME:
+ s = "PR_SERVICE_ENTRY_NAME"; break;
+case PR_SERVICE_UID:
+ s = "PR_SERVICE_UID"; break;
+case PR_SERVICE_EXTRA_UIDS:
+ s = "PR_SERVICE_EXTRA_UIDS"; break;
+case PR_SERVICES:
+ s = "PR_SERVICES"; break;
+case PR_SERVICE_SUPPORT_FILES:
+ s = "PR_SERVICE_SUPPORT_FILES"; break;
+case PR_SERVICE_DELETE_FILES:
+ s = "PR_SERVICE_DELETE_FILES"; break;
+case PR_AB_SEARCH_PATH_UPDATE:
+ s = "PR_AB_SEARCH_PATH_UPDATE"; break;
+case PR_PROFILE_NAME:
+ s = "PR_PROFILE_NAME"; break;
+
+/*
+ * Status object properties
+ */
+
+case PR_IDENTITY_DISPLAY:
+ s = "PR_IDENTITY_DISPLAY"; break;
+case PR_IDENTITY_ENTRYID:
+ s = "PR_IDENTITY_ENTRYID"; break;
+case PR_RESOURCE_METHODS:
+ s = "PR_RESOURCE_METHODS"; break;
+case PR_RESOURCE_TYPE:
+ s = "PR_RESOURCE_TYPE"; break;
+case PR_STATUS_CODE:
+ s = "PR_STATUS_CODE"; break;
+case PR_IDENTITY_SEARCH_KEY:
+ s = "PR_IDENTITY_SEARCH_KEY"; break;
+case PR_OWN_STORE_ENTRYID:
+ s = "PR_OWN_STORE_ENTRYID"; break;
+case PR_RESOURCE_PATH:
+ s = "PR_RESOURCE_PATH"; break;
+case PR_STATUS_STRING:
+ s = "PR_STATUS_STRING"; break;
+case PR_X400_DEFERRED_DELIVERY_CANCEL:
+ s = "PR_X400_DEFERRED_DELIVERY_CANCEL"; break;
+case PR_HEADER_FOLDER_ENTRYID:
+ s = "PR_HEADER_FOLDER_ENTRYID"; break;
+case PR_REMOTE_PROGRESS:
+ s = "PR_REMOTE_PROGRESS"; break;
+case PR_REMOTE_PROGRESS_TEXT:
+ s = "PR_REMOTE_PROGRESS_TEXT"; break;
+case PR_REMOTE_VALIDATE_OK:
+ s = "PR_REMOTE_VALIDATE_OK"; break;
+
+/*
+ * Display table properties
+ */
+
+case PR_CONTROL_FLAGS:
+ s = "PR_CONTROL_FLAGS"; break;
+case PR_CONTROL_STRUCTURE:
+ s = "PR_CONTROL_STRUCTURE"; break;
+case PR_CONTROL_TYPE:
+ s = "PR_CONTROL_TYPE"; break;
+case PR_DELTAX:
+ s = "PR_DELTAX"; break;
+case PR_DELTAY:
+ s = "PR_DELTAY"; break;
+case PR_XPOS:
+ s = "PR_XPOS"; break;
+case PR_YPOS:
+ s = "PR_YPOS"; break;
+case PR_CONTROL_ID:
+ s = "PR_CONTROL_ID"; break;
+case PR_INITIAL_DETAILS_PANE:
+ s = "PR_INITIAL_DETAILS_PANE"; break;
+/*
+ * Secure property id range
+ */
+
+case PROP_ID_SECURE_MIN:
+ s = "PROP_ID_SECURE_MIN"; break;
+case PROP_ID_SECURE_MAX:
+ s = "PROP_ID_SECURE_MAX"; break;
diff --git a/mailnews/import/outlook/src/OutlookDebugLog.h b/mailnews/import/outlook/src/OutlookDebugLog.h
new file mode 100644
index 000000000..5b189bf9b
--- /dev/null
+++ b/mailnews/import/outlook/src/OutlookDebugLog.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef OutlookDebugLog_h___
+#define OutlookDebugLog_h___
+
+#ifdef NS_DEBUG
+#define IMPORT_DEBUG 1
+#endif
+
+// Use PR_LOG for logging.
+#include "mozilla/Logging.h"
+extern PRLogModuleInfo *OUTLOOKLOGMODULE; // Logging module
+
+#define IMPORT_LOG0(x) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (x))
+#define IMPORT_LOG1(x, y) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (x, y))
+#define IMPORT_LOG2(x, y, z) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (x, y, z))
+#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d))
+
+
+
+#endif /* OutlookDebugLog_h___ */
diff --git a/mailnews/import/outlook/src/moz.build b/mailnews/import/outlook/src/moz.build
new file mode 100644
index 000000000..4ffbad572
--- /dev/null
+++ b/mailnews/import/outlook/src/moz.build
@@ -0,0 +1,24 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'MapiApi.cpp',
+ 'MapiMessage.cpp',
+ 'MapiMimeTypes.cpp',
+ 'nsOutlookCompose.cpp',
+ 'nsOutlookImport.cpp',
+ 'nsOutlookMail.cpp',
+ 'nsOutlookSettings.cpp',
+ 'nsOutlookStringBundle.cpp',
+ 'rtfDecoder.cpp',
+ 'rtfMailDecoder.cpp',
+]
+
+FINAL_LIBRARY = 'import'
+
+LOCAL_INCLUDES += [
+ '../../src'
+]
+
diff --git a/mailnews/import/outlook/src/nsOutlookCompose.cpp b/mailnews/import/outlook/src/nsOutlookCompose.cpp
new file mode 100644
index 000000000..96c0824aa
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookCompose.cpp
@@ -0,0 +1,814 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "prthread.h"
+#include "nsStringGlue.h"
+#include "nsMsgUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsIIOService.h"
+#include "nsIURI.h"
+#include "nsMsgI18N.h"
+#include "nsINetUtil.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsMsgAttachmentData.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgCompCID.h"
+#include "nsIArray.h"
+#include "nsIMsgCompose.h"
+#include "nsIMsgCompFields.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgSend.h"
+#include "nsIMutableArray.h"
+#include "nsImportEmbeddedImageData.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsOutlookCompose.h"
+
+#include "OutlookDebugLog.h"
+
+#include "nsMimeTypes.h"
+#include "nsMsgUtils.h"
+
+#include "nsAutoPtr.h"
+
+#include "nsMsgMessageFlags.h"
+#include "nsMsgLocalFolderHdrs.h"
+
+
+static NS_DEFINE_CID(kMsgSendCID, NS_MSGSEND_CID);
+static NS_DEFINE_CID(kMsgCompFieldsCID, NS_MSGCOMPFIELDS_CID);
+
+// We need to do some calculations to set these numbers to something reasonable!
+// Unless of course, CreateAndSendMessage will NEVER EVER leave us in the lurch
+#define kHungCount 100000
+#define kHungAbortCount 1000
+
+#ifdef IMPORT_DEBUG
+static const char *p_test_headers =
+"Received: from netppl.invalid (IDENT:monitor@get.freebsd.because.microsoftsucks.invalid [209.3.31.115])\n\
+ by mail4.sirius.invalid (8.9.1/8.9.1) with SMTP id PAA27232;\n\
+ Mon, 17 May 1999 15:27:43 -0700 (PDT)\n\
+Message-ID: <ikGD3jRTsKklU.Ggm2HmE2A1Jsqd0p@netppl.invalid>\n\
+From: \"adsales@qualityservice.invalid\" <adsales@qualityservice.invalid>\n\
+Subject: Re: Your College Diploma (36822)\n\
+Date: Mon, 17 May 1999 15:09:29 -0400 (EDT)\n\
+MIME-Version: 1.0\n\
+Content-Type: TEXT/PLAIN; charset=\"US-ASCII\"\n\
+Content-Transfer-Encoding: 7bit\n\
+X-UIDL: 19990517.152941\n\
+Status: RO";
+
+static const char *p_test_body =
+"Hello world?\n\
+";
+#else
+#define p_test_headers nullptr
+#define p_test_body nullptr
+#endif
+
+#define kWhitespace "\b\t\r\n "
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+// A replacement for SimpleBufferTonyRCopiedTwice round-robin buffer and ReadFileState classes
+class CCompositionFile {
+public:
+ // fifoBuffer is used for memory allocation optimization
+ // convertCRs controls if we want to convert standalone CRs to CRLFs
+ CCompositionFile(nsIFile* aFile, void* fifoBuffer, uint32_t fifoBufferSize, bool convertCRs=false);
+
+ operator bool() const { return m_fileSize && m_pInputStream; }
+
+ // Reads up to and including the term sequence, or entire file if term isn't found
+ // termSize may be used to include NULLs in the terminator sequences.
+ // termSize value of -1 means "zero-terminated string" -> size is calculated with strlen
+ nsresult ToString(nsCString& dest, const char* term=0, int termSize=-1);
+ nsresult ToStream(nsIOutputStream *dest, const char* term=0, int termSize=-1);
+ char LastChar() { return m_lastChar; }
+private:
+ nsCOMPtr<nsIFile> m_pFile;
+ nsCOMPtr<nsIInputStream> m_pInputStream;
+ int64_t m_fileSize;
+ int64_t m_fileReadPos;
+ char* m_fifoBuffer;
+ uint32_t m_fifoBufferSize;
+ char* m_fifoBufferReadPos; // next character to read
+ char* m_fifoBufferWrittenPos; // if we have read less than buffer size then this will show it
+ bool m_convertCRs;
+ char m_lastChar;
+
+ nsresult EnsureHasDataInBuffer();
+ template <class _OutFn> nsresult ToDest(_OutFn dest, const char* term, int termSize);
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+// First off, a listener
+class OutlookSendListener : public nsIMsgSendListener
+{
+public:
+ OutlookSendListener() {
+ m_done = false;
+ m_location = nullptr;
+ }
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /* void OnStartSending (in string aMsgID, in uint32_t aMsgSize); */
+ NS_IMETHOD OnStartSending(const char *aMsgID, uint32_t aMsgSize) {return NS_OK;}
+
+ /* void OnProgress (in string aMsgID, in uint32_t aProgress, in uint32_t aProgressMax); */
+ NS_IMETHOD OnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax) {return NS_OK;}
+
+ /* void OnStatus (in string aMsgID, in wstring aMsg); */
+ NS_IMETHOD OnStatus(const char *aMsgID, const char16_t *aMsg) {return NS_OK;}
+
+ /* void OnStopSending (in string aMsgID, in nsresult aStatus, in wstring aMsg, in nsIFile returnFile); */
+ NS_IMETHOD OnStopSending(const char *aMsgID, nsresult aStatus, const char16_t *aMsg,
+ nsIFile *returnFile) {
+ m_done = true;
+ NS_IF_ADDREF(m_location = returnFile);
+ return NS_OK;
+ }
+
+ /* void OnSendNotPerformed */
+ NS_IMETHOD OnSendNotPerformed(const char *aMsgID, nsresult aStatus) {return NS_OK;}
+
+ /* void OnGetDraftFolderURI (); */
+ NS_IMETHOD OnGetDraftFolderURI(const char *aFolderURI) {return NS_OK;}
+
+ static nsresult CreateSendListener(nsIMsgSendListener **ppListener);
+ void Reset() { m_done = false; NS_IF_RELEASE(m_location);}
+
+public:
+ virtual ~OutlookSendListener() { NS_IF_RELEASE(m_location); }
+
+ bool m_done;
+ nsIFile * m_location;
+};
+
+NS_IMPL_ISUPPORTS(OutlookSendListener, nsIMsgSendListener)
+
+nsresult OutlookSendListener::CreateSendListener(nsIMsgSendListener **ppListener)
+{
+ NS_PRECONDITION(ppListener != nullptr, "null ptr");
+ NS_ENSURE_ARG_POINTER(ppListener);
+
+ *ppListener = new OutlookSendListener();
+ if (! *ppListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*ppListener);
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+
+#define hackBeginA "begin"
+#define hackBeginW u"begin"
+#define hackEndA "\015\012end"
+#define hackEndW u"\015\012end"
+#define hackCRLFA "crlf"
+#define hackCRLFW u"crlf"
+#define hackAmpersandA "amp"
+#define hackAmpersandW u"amp"
+
+nsOutlookCompose::nsOutlookCompose()
+{
+ m_pListener = nullptr;
+ m_pMsgFields = nullptr;
+
+ m_optimizationBuffer = new char[FILE_IO_BUFFER_SIZE];
+}
+
+nsOutlookCompose::~nsOutlookCompose()
+{
+ NS_IF_RELEASE(m_pListener);
+ NS_IF_RELEASE(m_pMsgFields);
+ if (m_pIdentity) {
+ nsresult rv = m_pIdentity->ClearAllValues();
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to clear values");
+ if (NS_FAILED(rv))
+ return;
+ }
+ delete[] m_optimizationBuffer;
+}
+
+nsIMsgIdentity * nsOutlookCompose::m_pIdentity = nullptr;
+
+nsresult nsOutlookCompose::CreateIdentity(void)
+{
+ if (m_pIdentity)
+ return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accMgr =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accMgr->CreateIdentity(&m_pIdentity);
+ nsString name;
+ name.AssignLiteral("Import Identity");
+ if (m_pIdentity) {
+ m_pIdentity->SetFullName(name);
+ m_pIdentity->SetEmail(NS_LITERAL_CSTRING("import@service.invalid"));
+ }
+ return rv;
+}
+
+void nsOutlookCompose::ReleaseIdentity()
+{
+ NS_IF_RELEASE(m_pIdentity);
+}
+
+nsresult nsOutlookCompose::CreateComponents(void)
+{
+ nsresult rv = NS_OK;
+
+ NS_IF_RELEASE(m_pMsgFields);
+ if (!m_pListener && NS_SUCCEEDED(rv))
+ rv = OutlookSendListener::CreateSendListener(&m_pListener);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = CallCreateInstance(kMsgCompFieldsCID, &m_pMsgFields);
+ if (NS_SUCCEEDED(rv) && m_pMsgFields) {
+ // IMPORT_LOG0("nsOutlookCompose - CreateComponents succeeded\n");
+ m_pMsgFields->SetForcePlainText(false);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsOutlookCompose::ComposeTheMessage(nsMsgDeliverMode mode, CMapiMessage &msg, nsIFile **pMsg)
+{
+ nsresult rv = CreateComponents();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = CreateIdentity();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // IMPORT_LOG0("Outlook Compose created necessary components\n");
+
+ CMapiMessageHeaders* headers = msg.GetHeaders();
+
+ nsString unival;
+ headers->UnfoldValue(CMapiMessageHeaders::hdrFrom, unival, msg.GetBodyCharset());
+ m_pMsgFields->SetFrom(unival);
+ headers->UnfoldValue(CMapiMessageHeaders::hdrTo, unival, msg.GetBodyCharset());
+ m_pMsgFields->SetTo(unival);
+ headers->UnfoldValue(CMapiMessageHeaders::hdrSubject, unival, msg.GetBodyCharset());
+ m_pMsgFields->SetSubject(unival);
+ m_pMsgFields->SetCharacterSet(msg.GetBodyCharset());
+ headers->UnfoldValue(CMapiMessageHeaders::hdrCc, unival, msg.GetBodyCharset());
+ m_pMsgFields->SetCc(unival);
+ headers->UnfoldValue(CMapiMessageHeaders::hdrReplyTo, unival, msg.GetBodyCharset());
+ m_pMsgFields->SetReplyTo(unival);
+ m_pMsgFields->SetMessageId(headers->Value(CMapiMessageHeaders::hdrMessageID));
+
+ // We only use those headers that may need to be processed by Thunderbird
+ // to create a good rfc822 document, or need to be encoded (like To and Cc).
+ // These will replace the originals on import. All the other headers
+ // will be copied to the destination unaltered in CopyComposedMessage().
+
+ nsCOMPtr<nsIArray> pAttach;
+ msg.GetAttachments(getter_AddRefs(pAttach));
+
+ nsString bodyW;
+ // Bug 593907
+ if (GenerateHackSequence(msg.GetBody(), msg.GetBodyLen()))
+ HackBody(msg.GetBody(), msg.GetBodyLen(), bodyW);
+ else
+ bodyW = msg.GetBody();
+ // End Bug 593907
+
+ nsCOMPtr<nsIMutableArray> embeddedObjects;
+
+ if (msg.BodyIsHtml()) {
+ for (unsigned int i = 0; i <msg.EmbeddedAttachmentsCount(); i++) {
+ nsIURI *uri;
+ const char* cid;
+ const char* name;
+ if (msg.GetEmbeddedAttachmentInfo(i, &uri, &cid, &name)) {
+ if (!embeddedObjects) {
+ embeddedObjects = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCOMPtr<nsIMsgEmbeddedImageData> imageData =
+ new nsImportEmbeddedImageData(uri, nsDependentCString(cid),
+ nsDependentCString(name));
+ embeddedObjects->AppendElement(imageData, false);
+ }
+ }
+ }
+
+ nsCString bodyA;
+ nsMsgI18NConvertFromUnicode(msg.GetBodyCharset(), bodyW, bodyA);
+
+ nsCOMPtr<nsIImportService> impService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = impService->CreateRFC822Message(
+ m_pIdentity, // dummy identity
+ m_pMsgFields, // message fields
+ msg.BodyIsHtml() ? "text/html" : "text/plain",
+ bodyA, // body pointer
+ mode == nsIMsgSend::nsMsgSaveAsDraft,
+ pAttach, // local attachments
+ embeddedObjects,
+ m_pListener); // listener
+
+ OutlookSendListener *pListen = (OutlookSendListener *)m_pListener;
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("*** Error, CreateAndSendMessage FAILED: 0x%lx\n", rv);
+ }
+ else {
+ // wait for the listener to get done!
+ int32_t abortCnt = 0;
+ int32_t cnt = 0;
+ int32_t sleepCnt = 1;
+ while (!pListen->m_done && (abortCnt < kHungAbortCount)) {
+ PR_Sleep(sleepCnt);
+ cnt++;
+ if (cnt > kHungCount) {
+ abortCnt++;
+ sleepCnt *= 2;
+ cnt = 0;
+ }
+ }
+
+ if (abortCnt >= kHungAbortCount) {
+ IMPORT_LOG0("**** Create and send message hung\n");
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+
+ if (pListen->m_location) {
+ pListen->m_location->Clone(pMsg);
+ rv = NS_OK;
+ }
+ else {
+ rv = NS_ERROR_FAILURE;
+ IMPORT_LOG0("*** Error, Outlook compose unsuccessful\n");
+ }
+
+ pListen->Reset();
+ return rv;
+}
+
+nsresult nsOutlookCompose::CopyComposedMessage(nsIFile *pSrc,
+ nsIOutputStream *pDst,
+ CMapiMessage& origMsg)
+{
+ // I'm unsure if we really need the convertCRs feature here.
+ // The headers in the file are generated by TB, the body was generated by rtf reader that always used CRLF,
+ // and the attachments were processed by TB either... However, I let it stay as it was in the original code.
+ CCompositionFile f(pSrc, m_optimizationBuffer, FILE_IO_BUFFER_SIZE, true);
+ if (!f) {
+ IMPORT_LOG0("*** Error, unexpected zero file size for composed message\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ // The "From ..." separates the messages. Without it, TB cannot see the messages in the mailbox file.
+ // Thus, the lines that look like "From ..." in the message must be escaped (see EscapeFromSpaceLine())
+ int fromLineLen;
+ const char* fromLine = origMsg.GetFromLine(fromLineLen);
+ uint32_t written;
+ nsresult rv = pDst->Write(fromLine, fromLineLen, &written);
+
+ // Bug 219269
+ // Write out the x-mozilla-status headers.
+ char statusLine[50];
+ uint32_t msgFlags = 0;
+ if (origMsg.IsRead())
+ msgFlags |= nsMsgMessageFlags::Read;
+ if (!origMsg.FullMessageDownloaded())
+ msgFlags |= nsMsgMessageFlags::Partial;
+ if (origMsg.IsForvarded())
+ msgFlags |= nsMsgMessageFlags::Forwarded;
+ if (origMsg.IsReplied())
+ msgFlags |= nsMsgMessageFlags::Replied;
+ if (origMsg.HasAttach())
+ msgFlags |= nsMsgMessageFlags::Attachment;
+ _snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF);
+ rv = pDst->Write(statusLine, strlen(statusLine), &written);
+ _snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000);
+ rv = pDst->Write(statusLine, strlen(statusLine), &written);
+ // End Bug 219269
+
+ // well, isn't this a hoot!
+ // Read the headers from the new message, get the ones we like
+ // and write out only the headers we want from the new message,
+ // along with all of the other headers from the "old" message!
+
+ nsCString newHeadersStr;
+ rv = f.ToString(newHeadersStr, MSG_LINEBREAK MSG_LINEBREAK); // Read all the headers
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateHeaders(*origMsg.GetHeaders(), newHeadersStr.get());
+ rv = origMsg.GetHeaders()->ToStream(pDst);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bug 593907
+ if (!m_hackedPostfix.IsEmpty()) {
+ nsCString hackedPartEnd;
+ LossyCopyUTF16toASCII(m_hackedPostfix, hackedPartEnd);
+ hackedPartEnd.Insert(hackEndA, 0);
+ nsCString body;
+ rv = f.ToString(body, hackedPartEnd.get(), hackedPartEnd.Length());
+ UnhackBody(body);
+ EscapeFromSpaceLine(pDst, const_cast<char*>(body.get()), body.get()+body.Length());
+ }
+ // End Bug 593907
+
+ // I use the terminating sequence here to avoid a possible situation when a "From " line
+ // gets split over two sequential reads and thus will not be escaped.
+ // This is done by reading up to CRLF (one line every time), though it may be slower
+
+ // Here I revert the changes that were made when the multipart/related message
+ // was composed in nsMsgSend::ProcessMultipartRelated() - the Content-Ids of
+ // attachments were replaced with new ones.
+ nsCString line;
+ while (NS_SUCCEEDED(f.ToString(line, MSG_LINEBREAK))) {
+ EscapeFromSpaceLine(pDst, const_cast<char*>(line.get()), line.get()+line.Length());
+ }
+
+ if (f.LastChar() != nsCRT::LF) {
+ rv = pDst->Write(MSG_LINEBREAK, 2, &written);
+ if (written != 2)
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult nsOutlookCompose::ProcessMessage(nsMsgDeliverMode mode,
+ CMapiMessage &msg,
+ nsIOutputStream *pDst)
+{
+ nsCOMPtr<nsIFile> compositionFile;
+ nsresult rv = ComposeTheMessage(mode, msg, getter_AddRefs(compositionFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = CopyComposedMessage(compositionFile, pDst, msg);
+ compositionFile->Remove(false);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error copying composed message to destination mailbox\n");
+ }
+ return rv;
+}
+
+void nsOutlookCompose::UpdateHeader(CMapiMessageHeaders& oldHeaders,
+ const CMapiMessageHeaders& newHeaders,
+ CMapiMessageHeaders::SpecialHeader header,
+ bool addIfAbsent)
+{
+ const char* oldVal = oldHeaders.Value(header);
+ if (!addIfAbsent && !oldVal)
+ return;
+ const char* newVal = newHeaders.Value(header);
+ if (!newVal)
+ return;
+ // Bug 145150 - Turn "Content-Type: application/ms-tnef" into "Content-Type: text/plain"
+ // so the body text can be displayed normally (instead of in an attachment).
+ if (header == CMapiMessageHeaders::hdrContentType)
+ if (stricmp(newVal, "application/ms-tnef") == 0)
+ newVal = "text/plain";
+ // End Bug 145150
+ if (oldVal) {
+ if (strcmp(oldVal, newVal) == 0)
+ return;
+ // Backup the old header value
+ nsCString backupHdrName("X-MozillaBackup-");
+ backupHdrName += CMapiMessageHeaders::SpecialName(header);
+ oldHeaders.SetValue(backupHdrName.get(), oldVal, false);
+ }
+ // Now replace it with new value
+ oldHeaders.SetValue(header, newVal);
+}
+
+void nsOutlookCompose::UpdateHeaders(CMapiMessageHeaders& oldHeaders, const CMapiMessageHeaders& newHeaders)
+{
+ // Well, ain't this a peach?
+ // This is rather disgusting but there really isn't much to be done about it....
+
+ // 1. For each "old" header, replace it with the new one if we want,
+ // then right it out.
+ // 2. Then if we haven't written the "important" new headers, write them out
+ // 3. Terminate the headers with an extra eol.
+
+ // Important headers:
+ // "Content-type",
+ // "MIME-Version",
+ // "Content-transfer-encoding"
+ // consider "X-Accept-Language"?
+
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrContentType);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrMimeVersion);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrContentTransferEncoding);
+
+ // Other replaced headers (only if they exist):
+ // "From",
+ // "To",
+ // "Subject",
+ // "Reply-to",
+ // "Cc"
+
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrFrom, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrTo, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrSubject, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrReplyTo, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrCc, false);
+}
+
+// Bug 593907
+// This is just a workaround of the deficiency of the nsMsgComposeAndSend::EnsureLineBreaks().
+// The import from Outlook will stay OK (I hope), but other messages may still suffer.
+// However, I cannot deny the possibility that the (possible) recode of the body
+// may interfere with this hack. A possible scenario is if a multi-byte character will either
+// contain 0x0D 0x0A sequence, or end with 0x0D, after which MAC-style standalone LF will go.
+// I hope that this possibility is insignificant (eg, utf-8 doesn't contain such sequences).
+// This hack will slow down the import, but as the import is one-time procedure, I hope that
+// the user will agree to wait a little longer to get better results.
+
+// The process of composing the message differs depending on whether the editor is present or not.
+// If the editor is absent, the "attachment1_body" parameter of CreateAndSendMessage() is taken as is,
+// while in the presence o the editor, the body that is taken from it is further processed in the
+// nsMsgComposeAndSend::GetBodyFromEditor(). Specifically, the TXTToHTML::ScanHTML() first calls
+// UnescapeStr() to properly handle a limited number of HTML character entities (namely &amp; &lt; &gt; &quot;)
+// and then calls ScanTXT() where escapes all ampersands and quotes again. As the UnescapeStr() works so
+// selectively (i.e. handling only a subset of valid entities), the so often seen "&nbsp;" becomes "&amp;nbsp;"
+// in the resulting body, which leads to text "&nbsp;" interspersed all over the imported mail. The same
+// applies to html &#XXXX; (where XXXX is unicode codepoint).
+// See also Bug 503690, where the same issue in Eudora import is reported.
+// By the way, the root of the Bug 359303 lies in the same place - the nsMsgComposeAndSend::GetBodyFromEditor()
+// changes the 0xA0 codes to 0x20 when it converts the body to plain text.
+// We scan the body here to find all the & and convert them to the safe character sequense to revert later.
+
+void nsOutlookCompose::HackBody(const wchar_t* orig, size_t origLen, nsString& hack)
+{
+#ifdef MOZILLA_INTERNAL_API
+ hack.SetCapacity(static_cast<size_t>(origLen*1.4));
+#endif
+ hack.Assign(hackBeginW);
+ hack.Append(m_hackedPostfix);
+
+ while (*orig) {
+ if (*orig == L'&') {
+ hack.Append(hackAmpersandW);
+ hack.Append(m_hackedPostfix);
+ } else if ((*orig == L'\x0D') && (*(orig+1) == L'\x0A')) {
+ hack.Append(hackCRLFW);
+ hack.Append(m_hackedPostfix);
+ ++orig;
+ } else
+ hack.Append(*orig);
+ ++orig;
+ }
+
+ hack.Append(hackEndW);
+ hack.Append(m_hackedPostfix);
+}
+
+void nsOutlookCompose::UnhackBody(nsCString& txt)
+{
+ nsCString hackedPostfixA;
+ LossyCopyUTF16toASCII(m_hackedPostfix, hackedPostfixA);
+
+ nsCString hackedString(hackBeginA);
+ hackedString.Append(hackedPostfixA);
+ int32_t begin = txt.Find(hackedString);
+ if (begin == kNotFound)
+ return;
+ txt.Cut(begin, hackedString.Length());
+
+ hackedString.Assign(hackEndA);
+ hackedString.Append(hackedPostfixA);
+ int32_t end = MsgFind(txt, hackedString, false, begin);
+ if (end == kNotFound)
+ return; // ?
+ txt.Cut(end, hackedString.Length());
+
+ nsCString range;
+ range.Assign(Substring(txt, begin, end - begin));
+ // 1. Remove all CRLFs from the selected range
+ MsgReplaceSubstring(range, MSG_LINEBREAK, "");
+ // 2. Restore the original CRLFs
+ hackedString.Assign(hackCRLFA);
+ hackedString.Append(hackedPostfixA);
+ MsgReplaceSubstring(range, hackedString.get(), MSG_LINEBREAK);
+
+ // 3. Restore the original ampersands
+ hackedString.Assign(hackAmpersandA);
+ hackedString.Append(hackedPostfixA);
+ MsgReplaceSubstring(range, hackedString.get(), "&");
+
+ txt.Replace(begin, end - begin, range);
+}
+
+bool nsOutlookCompose::GenerateHackSequence(const wchar_t* body, size_t origLen)
+{
+ nsDependentString nsBody(body, origLen);
+ const wchar_t* hack_base = L"hacked";
+ int i = 0;
+ do {
+ if (++i == 0) { // Cycle complete :) - could not generate an unique string
+ m_hackedPostfix.Truncate();
+ return false;
+ }
+ m_hackedPostfix.Assign(hack_base);
+ m_hackedPostfix.AppendInt(i);
+ } while (nsBody.Find(m_hackedPostfix) != kNotFound);
+
+ return true;
+}
+// End Bug 593907
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+CCompositionFile::CCompositionFile(nsIFile* aFile, void* fifoBuffer,
+ uint32_t fifoBufferSize, bool convertCRs)
+ : m_pFile(aFile), m_fileSize(0), m_fileReadPos(0),
+ m_fifoBuffer(static_cast<char*>(fifoBuffer)),
+ m_fifoBufferSize(fifoBufferSize),
+ m_fifoBufferReadPos(static_cast<char*>(fifoBuffer)),
+ m_fifoBufferWrittenPos(static_cast<char*>(fifoBuffer)),
+ m_convertCRs(convertCRs),
+ m_lastChar(0)
+{
+ m_pFile->GetFileSize(&m_fileSize);
+ if (!m_fileSize) {
+ IMPORT_LOG0("*** Error, unexpected zero file size for composed message\n");
+ return;
+ }
+
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(m_pInputStream), m_pFile);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error, unable to open composed message file\n");
+ return;
+ }
+}
+
+nsresult CCompositionFile::EnsureHasDataInBuffer()
+{
+ if (m_fifoBufferReadPos < m_fifoBufferWrittenPos)
+ return NS_OK;
+ // Populate the buffer with new data!
+ uint32_t count = m_fifoBufferSize;
+ if ((m_fileReadPos + count) > m_fileSize)
+ count = m_fileSize - m_fileReadPos;
+ if (!count)
+ return NS_ERROR_FAILURE; // Isn't there a "No more data" error?
+
+ uint32_t bytesRead = 0;
+ nsresult rv = m_pInputStream->Read(m_fifoBuffer, count, &bytesRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bytesRead || (bytesRead > count))
+ return NS_ERROR_FAILURE;
+ m_fifoBufferWrittenPos = m_fifoBuffer+bytesRead;
+ m_fifoBufferReadPos = m_fifoBuffer;
+ m_fileReadPos += bytesRead;
+
+ return NS_OK;
+}
+
+class CTermGuard {
+public:
+ CTermGuard(const char* term, int termSize)
+ : m_term(term),
+ m_termSize(term ? ((termSize!=-1) ? termSize : strlen(term)) : 0),
+ m_matchPos(0)
+ {}
+
+ // if the guard triggered
+ inline bool IsTriggered() const {
+ return m_termSize && (m_matchPos == m_termSize); }
+ // indicates if the guard has something to check
+ inline bool IsChecking() const { return m_termSize; }
+
+ bool Check(char c) // returns true only if the whole sequence passed
+ {
+ if (!m_termSize) // no guard
+ return false;
+ if (m_matchPos >= m_termSize) // check past success!
+ m_matchPos = 0;
+ if (m_term[m_matchPos] != c) // Reset sequence
+ m_matchPos = 0;
+ if (m_term[m_matchPos] == c) { // Sequence continues
+ return ++m_matchPos == m_termSize; // If equal then sequence complete!
+ }
+ // Sequence broken
+ return false;
+ }
+private:
+ const char* m_term;
+ int m_termSize;
+ int m_matchPos;
+};
+
+template <class _OutFn>
+nsresult CCompositionFile::ToDest(_OutFn dest, const char* term, int termSize)
+{
+ CTermGuard guard(term, termSize);
+
+#ifdef MOZILLA_INTERNAL_API
+ // We already know the required string size, so reduce future reallocations
+ if (!guard.IsChecking() && !m_convertCRs)
+ dest.SetCapacity(m_fileSize - m_fileReadPos);
+#endif
+
+ bool wasCR = false;
+ char c = 0;
+ nsresult rv;
+ while (NS_SUCCEEDED(rv = EnsureHasDataInBuffer())) {
+ if (!guard.IsChecking() && !m_convertCRs) { // Use efficient algorithm
+ dest.Append(m_fifoBufferReadPos, m_fifoBufferWrittenPos-m_fifoBufferReadPos);
+ }
+ else { // Check character by character to convert CRs and find terminating sequence
+ while (m_fifoBufferReadPos < m_fifoBufferWrittenPos) {
+ c = *m_fifoBufferReadPos;
+ if (m_convertCRs && wasCR) {
+ wasCR = false;
+ if (c != nsCRT::LF) {
+ const char kTmpLF = nsCRT::LF;
+ dest.Append(&kTmpLF, 1);
+ if (guard.Check(nsCRT::LF)) {
+ c = nsCRT::LF; // save last char
+ break;
+ }
+ }
+ }
+ dest.Append(&c, 1);
+ m_fifoBufferReadPos++;
+
+ if (guard.Check(c))
+ break;
+
+ if (m_convertCRs && (c == nsCRT::CR))
+ wasCR = true;
+ }
+ if (guard.IsTriggered())
+ break;
+ }
+ }
+
+ // check for trailing CR (only if caller didn't specify the terminating sequence that ends with CR -
+ // in this case he knows what he does!)
+ if (m_convertCRs && !guard.IsTriggered() && (c == nsCRT::CR)) {
+ c = nsCRT::LF;
+ dest.Append(&c, 1);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_lastChar = c;
+ return NS_OK;
+}
+
+class dest_nsCString {
+public:
+ dest_nsCString(nsCString& str) : m_str(str) { m_str.Truncate(); }
+#ifdef MOZILLA_INTERNAL_API
+ void SetCapacity(int32_t sz) { m_str.SetCapacity(sz); }
+#endif
+ nsresult Append(const char* buf, uint32_t count) {
+ m_str.Append(buf, count); return NS_OK; }
+private:
+ nsCString& m_str;
+};
+
+class dest_Stream {
+public:
+ dest_Stream(nsIOutputStream *dest) : m_stream(dest) {}
+#ifdef MOZILLA_INTERNAL_API
+ void SetCapacity(int32_t) { /*do nothing*/ }
+#endif
+ // const_cast here is due to the poor design of the EscapeFromSpaceLine()
+ // that requires a non-constant pointer while doesn't modify its data
+ nsresult Append(const char* buf, uint32_t count) {
+ return EscapeFromSpaceLine(m_stream, const_cast<char*>(buf), buf+count); }
+private:
+ nsIOutputStream *m_stream;
+};
+
+nsresult CCompositionFile::ToString(nsCString& dest, const char* term,
+ int termSize)
+{
+ return ToDest(dest_nsCString(dest), term, termSize);
+}
+
+nsresult CCompositionFile::ToStream(nsIOutputStream *dest, const char* term,
+ int termSize)
+{
+ return ToDest(dest_Stream(dest), term, termSize);
+}
diff --git a/mailnews/import/outlook/src/nsOutlookCompose.h b/mailnews/import/outlook/src/nsOutlookCompose.h
new file mode 100644
index 000000000..68f07f754
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookCompose.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOutlookCompose_h__
+#define nsOutlookCompose_h__
+
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsIFile.h"
+#include "nsIImportService.h"
+
+class nsIMsgSend;
+class nsIMsgCompFields;
+class nsIMsgIdentity;
+class nsIMsgSendListener;
+class nsIIOService;
+
+#include "nsIMsgSend.h"
+#include "nsNetUtil.h"
+
+#include "MapiMessage.h"
+
+#include <list>
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+class nsOutlookCompose {
+public:
+ nsOutlookCompose();
+ ~nsOutlookCompose();
+
+ nsresult ProcessMessage(nsMsgDeliverMode mode, CMapiMessage &msg, nsIOutputStream *pDst);
+ static nsresult CreateIdentity(void);
+ static void ReleaseIdentity(void);
+private:
+ nsresult CreateComponents(void);
+
+ void UpdateHeader(CMapiMessageHeaders& oldHeaders, const CMapiMessageHeaders& newHeaders, CMapiMessageHeaders::SpecialHeader header, bool addIfAbsent = true);
+ void UpdateHeaders(CMapiMessageHeaders& oldHeaders, const CMapiMessageHeaders& newHeaders);
+
+ nsresult ComposeTheMessage(nsMsgDeliverMode mode, CMapiMessage &msg, nsIFile **pMsg);
+ nsresult CopyComposedMessage(nsIFile *pSrc, nsIOutputStream *pDst, CMapiMessage& origMsg);
+
+ // Bug 593907
+ void HackBody(const wchar_t* orig, size_t origLen, nsString& hack);
+ void UnhackBody(nsCString& body);
+ bool GenerateHackSequence(const wchar_t* body, size_t origLen);
+ // End Bug 593907
+
+private:
+ nsIMsgSendListener * m_pListener;
+ nsIMsgCompFields * m_pMsgFields;
+ static nsIMsgIdentity * m_pIdentity;
+ char* m_optimizationBuffer;
+ nsCOMPtr<nsIImportService> m_pImportService;
+
+ // Bug 593907
+ nsString m_hackedPostfix;
+ // End Bug 593907
+};
+
+
+#endif /* nsOutlookCompose_h__ */
diff --git a/mailnews/import/outlook/src/nsOutlookImport.cpp b/mailnews/import/outlook/src/nsOutlookImport.cpp
new file mode 100644
index 000000000..eaaf24fc3
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookImport.cpp
@@ -0,0 +1,589 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Outlook Express (Win32) import mail and addressbook interfaces
+*/
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsMsgUtils.h"
+#include "nsIServiceManager.h"
+#include "nsIImportService.h"
+#include "nsIComponentManager.h"
+#include "nsOutlookImport.h"
+#include "nsIMemory.h"
+#include "nsIImportService.h"
+#include "nsIImportMail.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIImportGeneric.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIImportFieldMap.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIOutputStream.h"
+#include "nsIAddrDatabase.h"
+#include "nsOutlookSettings.h"
+#include "nsTextFormatter.h"
+#include "nsOutlookStringBundle.h"
+#include "nsIStringBundle.h"
+#include "OutlookDebugLog.h"
+#include "nsUnicharUtils.h"
+
+#include "nsOutlookMail.h"
+
+#include "MapiApi.h"
+
+static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
+PRLogModuleInfo *OUTLOOKLOGMODULE = nullptr;
+
+class ImportOutlookMailImpl : public nsIImportMail
+{
+public:
+ ImportOutlookMailImpl();
+
+ static nsresult Create(nsIImportMail** aImport);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIImportmail interface
+
+ /* void GetDefaultLocation (out nsIFile location, out boolean found, out boolean userVerify); */
+ NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found, bool *userVerify);
+
+ /* nsIArray FindMailboxes (in nsIFile location); */
+ NS_IMETHOD FindMailboxes(nsIFile *location, nsIArray **_retval);
+
+ NS_IMETHOD ImportMailbox(nsIImportMailboxDescriptor *source,
+ nsIMsgFolder *dstFolder,
+ char16_t **pErrorLog, char16_t **pSuccessLog,
+ bool *fatalError);
+
+ /* unsigned long GetImportProgress (); */
+ NS_IMETHOD GetImportProgress(uint32_t *_retval);
+
+ NS_IMETHOD TranslateFolderName(const nsAString & aFolderName, nsAString & _retval);
+
+public:
+ static void ReportSuccess(nsString& name, int32_t count, nsString *pStream);
+ static void ReportError(int32_t errorNum, nsString& name, nsString *pStream);
+ static void AddLinebreak(nsString *pStream);
+ static void SetLogs(nsString& success, nsString& error, char16_t **pError, char16_t **pSuccess);
+
+private:
+ virtual ~ImportOutlookMailImpl();
+ nsOutlookMail m_mail;
+ uint32_t m_bytesDone;
+};
+
+
+class ImportOutlookAddressImpl : public nsIImportAddressBooks
+{
+public:
+ ImportOutlookAddressImpl();
+
+ static nsresult Create(nsIImportAddressBooks** aImport);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIImportAddressBooks interface
+
+ NS_IMETHOD GetSupportsMultiple(bool *_retval) { *_retval = true; return NS_OK;}
+
+ NS_IMETHOD GetAutoFind(char16_t **description, bool *_retval);
+
+ NS_IMETHOD GetNeedsFieldMap(nsIFile *location, bool *_retval) { *_retval = false; return NS_OK;}
+
+ NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found, bool *userVerify)
+ { return NS_ERROR_FAILURE;}
+
+ NS_IMETHOD FindAddressBooks(nsIFile *location, nsIArray **_retval);
+
+ NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap)
+ { return NS_ERROR_FAILURE; }
+
+ NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source,
+ nsIAddrDatabase *destination,
+ nsIImportFieldMap *fieldMap,
+ nsISupports *aSupportService,
+ char16_t **errorLog,
+ char16_t **successLog,
+ bool *fatalError);
+
+ NS_IMETHOD GetImportProgress(uint32_t *_retval);
+
+ NS_IMETHOD GetSampleData(int32_t index, bool *pFound, char16_t **pStr)
+ { return NS_ERROR_FAILURE;}
+
+ NS_IMETHOD SetSampleLocation(nsIFile *) { return NS_OK; }
+
+private:
+ virtual ~ImportOutlookAddressImpl();
+ void ReportSuccess(nsString& name, nsString *pStream);
+
+private:
+ uint32_t m_msgCount;
+ uint32_t m_msgTotal;
+ nsOutlookMail m_address;
+};
+////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////
+
+
+nsOutlookImport::nsOutlookImport()
+{
+ // Init logging module.
+ if (!OUTLOOKLOGMODULE)
+ OUTLOOKLOGMODULE = PR_NewLogModule("IMPORT");
+
+ IMPORT_LOG0("nsOutlookImport Module Created\n");
+
+ nsOutlookStringBundle::GetStringBundle();
+}
+
+
+nsOutlookImport::~nsOutlookImport()
+{
+ IMPORT_LOG0("nsOutlookImport Module Deleted\n");
+}
+
+NS_IMPL_ISUPPORTS(nsOutlookImport, nsIImportModule)
+
+NS_IMETHODIMP nsOutlookImport::GetName(char16_t **name)
+{
+ NS_PRECONDITION(name != nullptr, "null ptr");
+ if (! name)
+ return NS_ERROR_NULL_POINTER;
+
+ *name = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookImport::GetDescription(char16_t **name)
+{
+ NS_PRECONDITION(name != nullptr, "null ptr");
+ if (!name)
+ return NS_ERROR_NULL_POINTER;
+
+ *name = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_DESCRIPTION);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookImport::GetSupports(char **supports)
+{
+ NS_PRECONDITION(supports != nullptr, "null ptr");
+ if (! supports)
+ return NS_ERROR_NULL_POINTER;
+
+ *supports = strdup(kOutlookSupportsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookImport::GetSupportsUpgrade(bool *pUpgrade)
+{
+ NS_PRECONDITION(pUpgrade != nullptr, "null ptr");
+ if (! pUpgrade)
+ return NS_ERROR_NULL_POINTER;
+
+ *pUpgrade = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookImport::GetImportInterface(const char *pImportType, nsISupports **ppInterface)
+{
+ NS_PRECONDITION(pImportType != nullptr, "null ptr");
+ if (! pImportType)
+ return NS_ERROR_NULL_POINTER;
+ NS_PRECONDITION(ppInterface != nullptr, "null ptr");
+ if (! ppInterface)
+ return NS_ERROR_NULL_POINTER;
+
+ *ppInterface = nullptr;
+ nsresult rv;
+ if (!strcmp(pImportType, "mail")) {
+ // create the nsIImportMail interface and return it!
+ nsIImportMail * pMail = nullptr;
+ nsIImportGeneric *pGeneric = nullptr;
+ rv = ImportOutlookMailImpl::Create(&pMail);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = impSvc->CreateNewGenericMail(&pGeneric);
+ if (NS_SUCCEEDED(rv)) {
+ pGeneric->SetData("mailInterface", pMail);
+ nsString name;
+ nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME, name);
+ nsCOMPtr<nsISupportsString> nameString (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nameString->SetData(name);
+ pGeneric->SetData("name", nameString);
+ rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface);
+ }
+ }
+ }
+ }
+ NS_IF_RELEASE(pMail);
+ NS_IF_RELEASE(pGeneric);
+ return rv;
+ }
+
+ if (!strcmp(pImportType, "addressbook")) {
+ // create the nsIImportAddressBook interface and return it!
+ nsIImportAddressBooks * pAddress = nullptr;
+ nsIImportGeneric * pGeneric = nullptr;
+ rv = ImportOutlookAddressImpl::Create(&pAddress);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = impSvc->CreateNewGenericAddressBooks(&pGeneric);
+ if (NS_SUCCEEDED(rv)) {
+ pGeneric->SetData("addressInterface", pAddress);
+ rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface);
+ }
+ }
+ }
+ NS_IF_RELEASE(pAddress);
+ NS_IF_RELEASE(pGeneric);
+ return rv;
+ }
+
+ if (!strcmp(pImportType, "settings")) {
+ nsIImportSettings *pSettings = nullptr;
+ rv = nsOutlookSettings::Create(&pSettings);
+ if (NS_SUCCEEDED(rv))
+ pSettings->QueryInterface(kISupportsIID, (void **)ppInterface);
+ NS_IF_RELEASE(pSettings);
+ return rv;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+nsresult ImportOutlookMailImpl::Create(nsIImportMail** aImport)
+{
+ NS_PRECONDITION(aImport != nullptr, "null ptr");
+ if (! aImport)
+ return NS_ERROR_NULL_POINTER;
+
+ *aImport = new ImportOutlookMailImpl();
+ if (! *aImport)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+ImportOutlookMailImpl::ImportOutlookMailImpl()
+{
+ nsOutlookCompose::CreateIdentity();
+}
+
+ImportOutlookMailImpl::~ImportOutlookMailImpl()
+{
+ nsOutlookCompose::ReleaseIdentity();
+}
+
+NS_IMPL_ISUPPORTS(ImportOutlookMailImpl, nsIImportMail)
+
+NS_IMETHODIMP ImportOutlookMailImpl::GetDefaultLocation(nsIFile **ppLoc, bool *found, bool *userVerify)
+{
+ NS_PRECONDITION(ppLoc != nullptr, "null ptr");
+ NS_PRECONDITION(found != nullptr, "null ptr");
+ NS_PRECONDITION(userVerify != nullptr, "null ptr");
+ if (!ppLoc || !found || !userVerify)
+ return NS_ERROR_NULL_POINTER;
+
+ *found = false;
+ *ppLoc = nullptr;
+ *userVerify = false;
+ // We need to verify here that we can get the mail, if true then
+ // return a dummy location, otherwise return no location
+ CMapiApi mapi;
+ if (!mapi.Initialize())
+ return NS_OK;
+ if (!mapi.LogOn())
+ return NS_OK;
+
+ CMapiFolderList store;
+ if (!mapi.IterateStores(store))
+ return NS_OK;
+
+ if (store.GetSize() == 0)
+ return NS_OK;
+
+
+ nsresult rv;
+ nsCOMPtr <nsIFile> resultFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ *found = true;
+ NS_IF_ADDREF(*ppLoc = resultFile);
+ *userVerify = false;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP ImportOutlookMailImpl::FindMailboxes(nsIFile *pLoc, nsIArray **ppArray)
+{
+ NS_PRECONDITION(pLoc != nullptr, "null ptr");
+ NS_PRECONDITION(ppArray != nullptr, "null ptr");
+ if (!pLoc || !ppArray)
+ return NS_ERROR_NULL_POINTER;
+ return m_mail.GetMailFolders(ppArray);
+}
+
+void ImportOutlookMailImpl::AddLinebreak(nsString *pStream)
+{
+ if (pStream)
+ pStream->Append(char16_t('\n'));
+}
+
+void ImportOutlookMailImpl::ReportSuccess(nsString& name, int32_t count, nsString *pStream)
+{
+ if (!pStream)
+ return;
+ // load the success string
+ char16_t *pFmt = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_MAILBOX_SUCCESS);
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get(), count);
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ nsOutlookStringBundle::FreeString(pFmt);
+ AddLinebreak(pStream);
+}
+
+void ImportOutlookMailImpl::ReportError(int32_t errorNum, nsString& name, nsString *pStream)
+{
+ if (!pStream)
+ return;
+ // load the error string
+ char16_t *pFmt = nsOutlookStringBundle::GetStringByID(errorNum);
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get());
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ nsOutlookStringBundle::FreeString(pFmt);
+ AddLinebreak(pStream);
+}
+
+
+void ImportOutlookMailImpl::SetLogs(nsString& success, nsString& error, char16_t **pError, char16_t **pSuccess)
+{
+ if (pError)
+ *pError = ToNewUnicode(error);
+ if (pSuccess)
+ *pSuccess = ToNewUnicode(success);
+}
+
+NS_IMETHODIMP
+ImportOutlookMailImpl::ImportMailbox(nsIImportMailboxDescriptor *pSource,
+ nsIMsgFolder *dstFolder,
+ char16_t **pErrorLog,
+ char16_t **pSuccessLog,
+ bool *fatalError)
+{
+ NS_ENSURE_ARG_POINTER(pSource);
+ NS_ENSURE_ARG_POINTER(dstFolder);
+ NS_ENSURE_ARG_POINTER(fatalError);
+
+ nsString success;
+ nsString error;
+ bool abort = false;
+ nsString name;
+ char16_t *pName;
+ if (NS_SUCCEEDED( pSource->GetDisplayName( &pName))) {
+ name = pName;
+ NS_Free( pName);
+ }
+
+ uint32_t mailSize = 0;
+ pSource->GetSize(&mailSize);
+ if (mailSize == 0) {
+ ReportSuccess(name, 0, &success);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_OK;
+ }
+
+ uint32_t index = 0;
+ pSource->GetIdentifier(&index);
+ int32_t msgCount = 0;
+ nsresult rv = NS_OK;
+
+ m_bytesDone = 0;
+
+ rv = m_mail.ImportMailbox(&m_bytesDone, &abort, (int32_t)index, name.get(),
+ dstFolder, &msgCount);
+
+ if (NS_SUCCEEDED(rv))
+ ReportSuccess(name, msgCount, &success);
+ else
+ ReportError(OUTLOOKIMPORT_MAILBOX_CONVERTERROR, name, &error);
+
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+
+ return rv;
+}
+
+
+NS_IMETHODIMP ImportOutlookMailImpl::GetImportProgress(uint32_t *pDoneSoFar)
+{
+ NS_PRECONDITION(pDoneSoFar != nullptr, "null ptr");
+ if (! pDoneSoFar)
+ return NS_ERROR_NULL_POINTER;
+
+ *pDoneSoFar = m_bytesDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportOutlookMailImpl::TranslateFolderName(const nsAString & aFolderName, nsAString & _retval)
+{
+ if (aFolderName.LowerCaseEqualsLiteral("deleted items"))
+ _retval = NS_LITERAL_STRING(kDestTrashFolderName);
+ else if (aFolderName.LowerCaseEqualsLiteral("sent items"))
+ _retval = NS_LITERAL_STRING(kDestSentFolderName);
+ else if (aFolderName.LowerCaseEqualsLiteral("outbox"))
+ _retval = NS_LITERAL_STRING(kDestUnsentMessagesFolderName);
+ else
+ _retval = aFolderName;
+ return NS_OK;
+}
+
+nsresult ImportOutlookAddressImpl::Create(nsIImportAddressBooks** aImport)
+{
+ NS_PRECONDITION(aImport != nullptr, "null ptr");
+ if (! aImport)
+ return NS_ERROR_NULL_POINTER;
+
+ *aImport = new ImportOutlookAddressImpl();
+ if (! *aImport)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+ImportOutlookAddressImpl::ImportOutlookAddressImpl()
+{
+ m_msgCount = 0;
+ m_msgTotal = 0;
+}
+
+ImportOutlookAddressImpl::~ImportOutlookAddressImpl()
+{
+}
+
+NS_IMPL_ISUPPORTS(ImportOutlookAddressImpl, nsIImportAddressBooks)
+
+NS_IMETHODIMP ImportOutlookAddressImpl::GetAutoFind(char16_t **description, bool *_retval)
+{
+ NS_PRECONDITION(description != nullptr, "null ptr");
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (! description || !_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = true;
+ nsString str;
+ nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRNAME, str);
+ *description = ToNewUnicode(str);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportOutlookAddressImpl::FindAddressBooks(nsIFile *location, nsIArray **_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ return m_address.GetAddressBooks(_retval);
+}
+
+NS_IMETHODIMP ImportOutlookAddressImpl::ImportAddressBook(nsIImportABDescriptor *source,
+ nsIAddrDatabase *destination,
+ nsIImportFieldMap *fieldMap,
+ nsISupports *aSupportService,
+ char16_t **pErrorLog,
+ char16_t **pSuccessLog,
+ bool *fatalError)
+{
+ m_msgCount = 0;
+ m_msgTotal = 0;
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ NS_PRECONDITION(destination != nullptr, "null ptr");
+ NS_PRECONDITION(fatalError != nullptr, "null ptr");
+
+ nsString success;
+ nsString error;
+ if (!source || !destination || !fatalError) {
+ IMPORT_LOG0("*** Bad param passed to outlook address import\n");
+ nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRESS_BADPARAM, error);
+ if (fatalError)
+ *fatalError = true;
+ ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsString name;
+ source->GetPreferredName(name);
+
+ uint32_t id;
+ if (NS_FAILED(source->GetIdentifier(&id))) {
+ ImportOutlookMailImpl::ReportError(OUTLOOKIMPORT_ADDRESS_BADSOURCEFILE, name, &error);
+ ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_OK;
+ rv = m_address.ImportAddresses(&m_msgCount, &m_msgTotal, name.get(), id, destination, error);
+ if (NS_SUCCEEDED(rv) && error.IsEmpty())
+ ReportSuccess(name, &success);
+ else
+ ImportOutlookMailImpl::ReportError(OUTLOOKIMPORT_ADDRESS_CONVERTERROR, name, &error);
+
+ ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog);
+ IMPORT_LOG0("*** Returning from outlook address import\n");
+ return destination->Commit(nsAddrDBCommitType::kLargeCommit);
+}
+
+
+NS_IMETHODIMP ImportOutlookAddressImpl::GetImportProgress(uint32_t *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ uint32_t result = m_msgCount;
+ if (m_msgTotal) {
+ result *= 100;
+ result /= m_msgTotal;
+ }
+ else
+ result = 0;
+
+ if (result > 100)
+ result = 100;
+
+ *_retval = result;
+
+ return NS_OK;
+}
+
+void ImportOutlookAddressImpl::ReportSuccess(nsString& name, nsString *pStream)
+{
+ if (!pStream)
+ return;
+ // load the success string
+ char16_t *pFmt = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRESS_SUCCESS);
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get());
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ nsOutlookStringBundle::FreeString(pFmt);
+ ImportOutlookMailImpl::AddLinebreak(pStream);
+}
diff --git a/mailnews/import/outlook/src/nsOutlookImport.h b/mailnews/import/outlook/src/nsOutlookImport.h
new file mode 100644
index 000000000..e017d6efc
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookImport.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOutlookImport_h___
+#define nsOutlookImport_h___
+
+#include "nsIImportModule.h"
+#include "nsCOMPtr.h"
+
+
+#define NS_OUTLOOKIMPORT_CID \
+{ /* 1DB469A0-8B00-11d3-A206-00A0CC26DA63 */ \
+ 0x1db469a0, 0x8b00, 0x11d3, \
+ {0xa2, 0x6, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63 }}
+
+
+
+
+#define kOutlookSupportsString NS_IMPORT_MAIL_STR "," NS_IMPORT_ADDRESS_STR "," NS_IMPORT_SETTINGS_STR
+
+class nsOutlookImport : public nsIImportModule
+{
+public:
+
+ nsOutlookImport();
+
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we suppport the nsIImportModule interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ NS_DECL_NSIIMPORTMODULE
+
+protected:
+ virtual ~nsOutlookImport();
+};
+
+
+
+
+#endif /* nsOutlookImport_h___ */
diff --git a/mailnews/import/outlook/src/nsOutlookMail.cpp b/mailnews/import/outlook/src/nsOutlookMail.cpp
new file mode 100644
index 000000000..b9a851ce8
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookMail.cpp
@@ -0,0 +1,863 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Outlook mail import
+*/
+
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsMsgUtils.h"
+#include "nsIServiceManager.h"
+#include "nsIImportService.h"
+#include "nsIImportFieldMap.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIMutableArray.h"
+#include "nsOutlookStringBundle.h"
+#include "nsABBaseCID.h"
+#include "nsIAbCard.h"
+#include "mdb.h"
+#include "OutlookDebugLog.h"
+#include "nsOutlookMail.h"
+#include "nsUnicharUtils.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgI18N.h"
+#include "nsNetUtil.h"
+
+static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
+
+/* ------------ Address book stuff ----------------- */
+typedef struct {
+ int32_t mozField;
+ int32_t multiLine;
+ ULONG mapiTag;
+} MAPIFields;
+
+/*
+ Fields in MAPI, not in Mozilla
+ PR_OFFICE_LOCATION
+ FIX - PR_BIRTHDAY - stored as PT_SYSTIME - FIX to extract for moz address book birthday
+ PR_DISPLAY_NAME_PREFIX - Mr., Mrs. Dr., etc.
+ PR_SPOUSE_NAME
+ PR_GENDER - integer, not text
+ FIX - PR_CONTACT_EMAIL_ADDRESSES - multiuline strings for email addresses, needs
+ parsing to get secondary email address for mozilla
+*/
+
+#define kIsMultiLine -2
+#define kNoMultiLine -1
+
+static MAPIFields gMapiFields[] = {
+ { 35, kIsMultiLine, PR_BODY},
+ { 6, kNoMultiLine, PR_BUSINESS_TELEPHONE_NUMBER},
+ { 7, kNoMultiLine, PR_HOME_TELEPHONE_NUMBER},
+ { 25, kNoMultiLine, PR_COMPANY_NAME},
+ { 23, kNoMultiLine, PR_TITLE},
+ { 10, kNoMultiLine, PR_CELLULAR_TELEPHONE_NUMBER},
+ { 9, kNoMultiLine, PR_PAGER_TELEPHONE_NUMBER},
+ { 8, kNoMultiLine, PR_BUSINESS_FAX_NUMBER},
+ { 8, kNoMultiLine, PR_HOME_FAX_NUMBER},
+ { 22, kNoMultiLine, PR_COUNTRY},
+ { 19, kNoMultiLine, PR_LOCALITY},
+ { 20, kNoMultiLine, PR_STATE_OR_PROVINCE},
+ { 17, 18, PR_STREET_ADDRESS},
+ { 21, kNoMultiLine, PR_POSTAL_CODE},
+ { 27, kNoMultiLine, PR_PERSONAL_HOME_PAGE},
+ { 26, kNoMultiLine, PR_BUSINESS_HOME_PAGE},
+ { 13, kNoMultiLine, PR_HOME_ADDRESS_CITY},
+ { 16, kNoMultiLine, PR_HOME_ADDRESS_COUNTRY},
+ { 15, kNoMultiLine, PR_HOME_ADDRESS_POSTAL_CODE},
+ { 14, kNoMultiLine, PR_HOME_ADDRESS_STATE_OR_PROVINCE},
+ { 11, 12, PR_HOME_ADDRESS_STREET},
+ { 24, kNoMultiLine, PR_DEPARTMENT_NAME}
+};
+/* ---------------------------------------------------- */
+
+
+#define kCopyBufferSize (16 * 1024)
+
+// The email address in Outlook Contacts doesn't have a named
+// property, we need to use this mapi name ID to access the email
+// The MAPINAMEID for email address has ulKind=MNID_ID
+// Outlook stores each email address in two IDs, 32899/32900 for Email1
+// 32915/32916 for Email2, 32931/32932 for Email3
+// Current we use OUTLOOK_EMAIL1_MAPI_ID1 for primary email
+// OUTLOOK_EMAIL2_MAPI_ID1 for secondary email
+#define OUTLOOK_EMAIL1_MAPI_ID1 32899
+#define OUTLOOK_EMAIL1_MAPI_ID2 32900
+#define OUTLOOK_EMAIL2_MAPI_ID1 32915
+#define OUTLOOK_EMAIL2_MAPI_ID2 32916
+#define OUTLOOK_EMAIL3_MAPI_ID1 32931
+#define OUTLOOK_EMAIL3_MAPI_ID2 32932
+
+nsOutlookMail::nsOutlookMail()
+{
+ m_gotAddresses = false;
+ m_gotFolders = false;
+ m_haveMapi = CMapiApi::LoadMapi();
+ m_lpMdb = NULL;
+}
+
+nsOutlookMail::~nsOutlookMail()
+{
+// EmptyAttachments();
+}
+
+nsresult nsOutlookMail::GetMailFolders(nsIArray **pArray)
+{
+ if (!m_haveMapi) {
+ IMPORT_LOG0("GetMailFolders called before Mapi is initialized\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("FAILED to allocate the nsIMutableArray for the mail folder list\n");
+ return rv;
+ }
+
+ nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ m_gotFolders = true;
+
+ m_folderList.ClearAll();
+
+ m_mapi.Initialize();
+ m_mapi.LogOn();
+
+ if (m_storeList.GetSize() == 0)
+ m_mapi.IterateStores(m_storeList);
+
+ int i = 0;
+ CMapiFolder *pFolder;
+ if (m_storeList.GetSize() > 1) {
+ while ((pFolder = m_storeList.GetItem(i))) {
+ CMapiFolder *pItem = new CMapiFolder(pFolder);
+ pItem->SetDepth(1);
+ m_folderList.AddItem(pItem);
+ if (!m_mapi.GetStoreFolders(pItem->GetCBEntryID(), pItem->GetEntryID(), m_folderList, 2)) {
+ IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i);
+ }
+ i++;
+ }
+ }
+ else {
+ if ((pFolder = m_storeList.GetItem(i))) {
+ if (!m_mapi.GetStoreFolders(pFolder->GetCBEntryID(), pFolder->GetEntryID(), m_folderList, 1)) {
+ IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i);
+ }
+ }
+ }
+
+ // Create the mailbox descriptors for the list of folders
+ nsIImportMailboxDescriptor * pID;
+ nsISupports * pInterface;
+ nsString name;
+ nsString uniName;
+
+ for (i = 0; i < m_folderList.GetSize(); i++) {
+ pFolder = m_folderList.GetItem(i);
+ rv = impSvc->CreateNewMailboxDescriptor(&pID);
+ if (NS_SUCCEEDED(rv)) {
+ pID->SetDepth(pFolder->GetDepth());
+ pID->SetIdentifier(i);
+
+ pFolder->GetDisplayName(name);
+ pID->SetDisplayName(name.get());
+
+ pID->SetSize(1000);
+ rv = pID->QueryInterface(kISupportsIID, (void **) &pInterface);
+ array->AppendElement(pInterface, false);
+ pInterface->Release();
+ pID->Release();
+ }
+ }
+ array.forget(pArray);
+ return NS_OK;
+}
+
+bool nsOutlookMail::IsAddressBookNameUnique(nsString& name, nsString& list)
+{
+ nsString usedName;
+ usedName.AppendLiteral("[");
+ usedName.Append(name);
+ usedName.AppendLiteral("],");
+
+ return list.Find(usedName) == -1;
+}
+
+void nsOutlookMail::MakeAddressBookNameUnique(nsString& name, nsString& list)
+{
+ nsString newName;
+ int idx = 1;
+
+ newName = name;
+ while (!IsAddressBookNameUnique(newName, list)) {
+ newName = name;
+ newName.Append(char16_t(' '));
+ newName.AppendInt((int32_t) idx);
+ idx++;
+ }
+
+ name = newName;
+ list.AppendLiteral("[");
+ list.Append(name);
+ list.AppendLiteral("],");
+}
+
+nsresult nsOutlookMail::GetAddressBooks(nsIArray **pArray)
+{
+ if (!m_haveMapi) {
+ IMPORT_LOG0("GetAddressBooks called before Mapi is initialized\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("FAILED to allocate the nsIMutableArray for the address book list\n");
+ return rv;
+ }
+
+ nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ m_gotAddresses = true;
+
+ m_addressList.ClearAll();
+ m_mapi.Initialize();
+ m_mapi.LogOn();
+ if (m_storeList.GetSize() == 0)
+ m_mapi.IterateStores(m_storeList);
+
+ int i = 0;
+ CMapiFolder *pFolder;
+ if (m_storeList.GetSize() > 1) {
+ while ((pFolder = m_storeList.GetItem(i))) {
+ CMapiFolder *pItem = new CMapiFolder(pFolder);
+ pItem->SetDepth(1);
+ m_addressList.AddItem(pItem);
+ if (!m_mapi.GetStoreAddressFolders(pItem->GetCBEntryID(), pItem->GetEntryID(), m_addressList)) {
+ IMPORT_LOG1("GetStoreAddressFolders for index %d failed.\n", i);
+ }
+ i++;
+ }
+ }
+ else {
+ if ((pFolder = m_storeList.GetItem(i))) {
+ if (!m_mapi.GetStoreAddressFolders(pFolder->GetCBEntryID(), pFolder->GetEntryID(), m_addressList)) {
+ IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i);
+ }
+ }
+ }
+
+ // Create the mailbox descriptors for the list of folders
+ nsIImportABDescriptor * pID;
+ nsISupports * pInterface;
+ nsString name;
+ nsString list;
+
+ for (i = 0; i < m_addressList.GetSize(); i++) {
+ pFolder = m_addressList.GetItem(i);
+ if (!pFolder->IsStore()) {
+ rv = impSvc->CreateNewABDescriptor(&pID);
+ if (NS_SUCCEEDED(rv)) {
+ pID->SetIdentifier(i);
+ pFolder->GetDisplayName(name);
+ MakeAddressBookNameUnique(name, list);
+ pID->SetPreferredName(name);
+ pID->SetSize(100);
+ rv = pID->QueryInterface(kISupportsIID, (void **) &pInterface);
+ array->AppendElement(pInterface, false);
+ pInterface->Release();
+ pID->Release();
+ }
+ }
+ }
+ array.forget(pArray);
+ return NS_OK;
+}
+
+void nsOutlookMail::OpenMessageStore(CMapiFolder *pNextFolder)
+{
+ // Open the store specified
+ if (pNextFolder->IsStore()) {
+ if (!m_mapi.OpenStore(pNextFolder->GetCBEntryID(), pNextFolder->GetEntryID(), &m_lpMdb)) {
+ m_lpMdb = NULL;
+ IMPORT_LOG0("CMapiApi::OpenStore failed\n");
+ }
+
+ return;
+ }
+
+ // Check to see if we should open the one and only store
+ if (!m_lpMdb) {
+ if (m_storeList.GetSize() == 1) {
+ CMapiFolder * pFolder = m_storeList.GetItem(0);
+ if (pFolder) {
+ if (!m_mapi.OpenStore(pFolder->GetCBEntryID(), pFolder->GetEntryID(), &m_lpMdb)) {
+ m_lpMdb = NULL;
+ IMPORT_LOG0("CMapiApi::OpenStore failed\n");
+ }
+ }
+ else {
+ IMPORT_LOG0("Error retrieving the one & only message store\n");
+ }
+ }
+ else {
+ IMPORT_LOG0("*** Error importing a folder without a valid message store\n");
+ }
+ }
+}
+
+// Roles and responsibilities:
+// nsOutlookMail
+// - Connect to Outlook
+// - Enumerate the mailboxes
+// - Iterate the mailboxes
+// - For each mail, create one nsOutlookCompose object
+// - For each mail, create one CMapiMessage object
+//
+// nsOutlookCompose
+// - Establish a TB session
+// - Connect to all required services
+// - Perform the composition of the RC822 document from the data gathered by CMapiMessage
+// - Save the composed message to the TB mailbox
+// - Ensure the proper cleanup
+//
+// CMapiMessage
+// - Encapsulate the MAPI message interface
+// - Gather the information required to (re)compose the message
+
+nsresult nsOutlookMail::ImportMailbox(uint32_t *pDoneSoFar, bool *pAbort,
+ int32_t index, const char16_t *pName,
+ nsIMsgFolder *dstFolder,
+ int32_t *pMsgCount)
+{
+ if ((index < 0) || (index >= m_folderList.GetSize())) {
+ IMPORT_LOG0("*** Bad mailbox identifier, unable to import\n");
+ *pAbort = true;
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t dummyMsgCount = 0;
+ if (pMsgCount)
+ *pMsgCount = 0;
+ else
+ pMsgCount = &dummyMsgCount;
+
+ CMapiFolder *pFolder = m_folderList.GetItem(index);
+ OpenMessageStore(pFolder);
+ if (!m_lpMdb) {
+ IMPORT_LOG1("*** Unable to obtain mapi message store for mailbox: %S\n", pName);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (pFolder->IsStore())
+ return NS_OK;
+
+ // now what?
+ CMapiFolderContents contents(m_lpMdb, pFolder->GetCBEntryID(), pFolder->GetEntryID());
+
+ BOOL done = FALSE;
+ ULONG cbEid;
+ LPENTRYID lpEid;
+ ULONG oType;
+ LPMESSAGE lpMsg = nullptr;
+ ULONG totalCount;
+ double doneCalc;
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = dstFolder->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (!done) {
+ if (!contents.GetNext(&cbEid, &lpEid, &oType, &done)) {
+ IMPORT_LOG1("*** Error iterating mailbox: %S\n", pName);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ bool reusable;
+
+ rv = msgStore->GetNewMsgOutputStream(dstFolder, getter_AddRefs(msgHdr), &reusable,
+ getter_AddRefs(outputStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("*** Error getting nsIOutputStream of mailbox: %S\n", pName);
+ return rv;
+ }
+ totalCount = contents.GetCount();
+ doneCalc = *pMsgCount;
+ doneCalc /= totalCount;
+ doneCalc *= 1000;
+ if (pDoneSoFar) {
+ *pDoneSoFar = (uint32_t) doneCalc;
+ if (*pDoneSoFar > 1000)
+ *pDoneSoFar = 1000;
+ }
+
+ if (!done && (oType == MAPI_MESSAGE)) {
+ if (!m_mapi.OpenMdbEntry(m_lpMdb, cbEid, lpEid, (LPUNKNOWN *) &lpMsg)) {
+ IMPORT_LOG1("*** Error opening messages in mailbox: %S\n", pName);
+ return NS_ERROR_FAILURE;
+ }
+
+ // See if it's a drafts folder. Outlook doesn't allow drafts
+ // folder to be configured so it's ok to hard code it here.
+ nsAutoString folderName(pName);
+ nsMsgDeliverMode mode = nsIMsgSend::nsMsgDeliverNow;
+ mode = nsIMsgSend::nsMsgSaveAsDraft;
+ if (folderName.LowerCaseEqualsLiteral("drafts"))
+ mode = nsIMsgSend::nsMsgSaveAsDraft;
+
+ rv = ImportMessage(lpMsg, outputStream, mode);
+ if (NS_SUCCEEDED(rv)){ // No errors & really imported
+ (*pMsgCount)++;
+ msgStore->FinishNewMessage(outputStream, msgHdr);
+ }
+ else {
+ IMPORT_LOG1( "*** Error reading message from mailbox: %S\n", pName);
+ msgStore->DiscardNewMessage(outputStream, msgHdr);
+ }
+ if (!reusable)
+ outputStream->Close();
+ }
+ }
+
+ if (outputStream)
+ outputStream->Close();
+ return NS_OK;
+}
+
+nsresult nsOutlookMail::ImportMessage(LPMESSAGE lpMsg, nsIOutputStream *pDest, nsMsgDeliverMode mode)
+{
+ CMapiMessage msg(lpMsg);
+ // If we wanted to skip messages that were downloaded in header only mode, we
+ // would return NS_ERROR_FAILURE if !msg.FullMessageDownloaded. However, we
+ // don't do this because it may cause seemingly wrong import results.
+ // A user will get less mails in his imported folder than were in the original folder,
+ // and this may make user feel like TB import is bad.
+ // In reality, the skipped messages are those that have not been downloaded yet, because
+ // they were downloaded in the "headers-only" mode. This is different from the case when
+ // the message is downloaded completely, but consists only of headers - in this case
+ // the message will be imported anyway.
+
+ if (!msg.ValidState())
+ return NS_ERROR_FAILURE;
+
+ // I have to create a composer for each message, since it turns out that if we create
+ // one composer for several messages, the Send Proxy object that is shared between those messages
+ // isn't reset properly (at least in the current implementation), which leads to crash.
+ // If there's a proper way to reinitialize the Send Proxy object,
+ // then we could slightly optimize the send process.
+ nsOutlookCompose compose;
+ nsresult rv = compose.ProcessMessage(mode, msg, pDest);
+
+ // Just for YUCKS, let's try an extra endline
+ WriteData(pDest, "\x0D\x0A", 2);
+
+ return rv;
+}
+
+BOOL nsOutlookMail::WriteData(nsIOutputStream *pDest, const char *pData, int32_t len)
+{
+ uint32_t written;
+ nsresult rv = pDest->Write(pData, len, &written);
+ return NS_SUCCEEDED(rv) && written == len;
+}
+
+nsresult nsOutlookMail::ImportAddresses(uint32_t *pCount, uint32_t *pTotal, const char16_t *pName, uint32_t id, nsIAddrDatabase *pDb, nsString& errors)
+{
+ if (id >= (uint32_t)(m_addressList.GetSize())) {
+ IMPORT_LOG0("*** Bad address identifier, unable to import\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t dummyCount = 0;
+ if (pCount)
+ *pCount = 0;
+ else
+ pCount = &dummyCount;
+
+ CMapiFolder *pFolder;
+ if (id > 0) {
+ int32_t idx = (int32_t) id;
+ idx--;
+ while (idx >= 0) {
+ pFolder = m_addressList.GetItem(idx);
+ if (pFolder->IsStore()) {
+ OpenMessageStore(pFolder);
+ break;
+ }
+ idx--;
+ }
+ }
+
+ pFolder = m_addressList.GetItem(id);
+ OpenMessageStore(pFolder);
+ if (!m_lpMdb) {
+ IMPORT_LOG1("*** Unable to obtain mapi message store for address book: %S\n", pName);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (pFolder->IsStore())
+ return NS_OK;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIImportFieldMap> pFieldMap;
+
+ nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = impSvc->CreateNewFieldMap(getter_AddRefs(pFieldMap));
+ }
+
+ CMapiFolderContents contents(m_lpMdb, pFolder->GetCBEntryID(), pFolder->GetEntryID());
+
+ BOOL done = FALSE;
+ ULONG cbEid;
+ LPENTRYID lpEid;
+ ULONG oType;
+ LPMESSAGE lpMsg;
+ nsCString type;
+ LPSPropValue pVal;
+ nsString subject;
+
+ while (!done) {
+ (*pCount)++;
+
+ if (!contents.GetNext(&cbEid, &lpEid, &oType, &done)) {
+ IMPORT_LOG1("*** Error iterating address book: %S\n", pName);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (pTotal && (*pTotal == 0))
+ *pTotal = contents.GetCount();
+
+ if (!done && (oType == MAPI_MESSAGE)) {
+ if (!m_mapi.OpenMdbEntry(m_lpMdb, cbEid, lpEid, (LPUNKNOWN *) &lpMsg)) {
+ IMPORT_LOG1("*** Error opening messages in mailbox: %S\n", pName);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the PR_MESSAGE_CLASS attribute,
+ // ensure that it is IPM.Contact
+ pVal = m_mapi.GetMapiProperty(lpMsg, PR_MESSAGE_CLASS);
+ if (pVal) {
+ type.Truncate();
+ m_mapi.GetStringFromProp(pVal, type);
+ if (type.EqualsLiteral("IPM.Contact")) {
+ // This is a contact, add it to the address book!
+ subject.Truncate();
+ pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT);
+ if (pVal)
+ m_mapi.GetStringFromProp(pVal, subject);
+
+ nsIMdbRow* newRow = nullptr;
+ pDb->GetNewRow(&newRow);
+ // FIXME: Check with Candice about releasing the newRow if it
+ // isn't added to the database. Candice's code in nsAddressBook
+ // never releases it but that doesn't seem right to me!
+ if (newRow) {
+ if (BuildCard(subject.get(), pDb, newRow, lpMsg, pFieldMap)) {
+ pDb->AddCardRowToDB(newRow);
+ }
+ }
+ }
+ else if (type.EqualsLiteral("IPM.DistList"))
+ {
+ // This is a list/group, add it to the address book!
+ subject.Truncate();
+ pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT);
+ if (pVal)
+ m_mapi.GetStringFromProp(pVal, subject);
+ CreateList(subject.get(), pDb, lpMsg, pFieldMap);
+ }
+ }
+
+ lpMsg->Release();
+ }
+ }
+
+ rv = pDb->Commit(nsAddrDBCommitType::kLargeCommit);
+ return rv;
+}
+nsresult nsOutlookMail::CreateList(const char16_t * pName,
+ nsIAddrDatabase *pDb,
+ LPMAPIPROP pUserList,
+ nsIImportFieldMap *pFieldMap)
+{
+ // If no name provided then we're done.
+ if (!pName || !(*pName))
+ return NS_OK;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ // Make sure we have db to work with.
+ if (!pDb)
+ return rv;
+
+ nsCOMPtr <nsIMdbRow> newListRow;
+ rv = pDb->GetNewListRow(getter_AddRefs(newListRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString column;
+ LossyCopyUTF16toASCII(nsDependentString(pName), column);
+ rv = pDb->AddListName(newListRow, column.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ HRESULT hr;
+ LPSPropValue value = NULL;
+ ULONG valueCount = 0;
+
+ LPSPropTagArray properties = NULL;
+ m_mapi.MAPIAllocateBuffer(CbNewSPropTagArray(1),
+ (void **)&properties);
+ properties->cValues = 1;
+ properties->aulPropTag [0] = m_mapi.GetEmailPropertyTag(pUserList, 0x8054);
+ hr = pUserList->GetProps(properties, 0, &valueCount, &value);
+ m_mapi.MAPIFreeBuffer(properties);
+ if (HR_FAILED(hr))
+ return NS_ERROR_FAILURE;
+ if (!value)
+ return NS_ERROR_NOT_AVAILABLE;
+ // XXX from here out, value must be freed with MAPIFreeBuffer
+
+ SBinaryArray *sa=(SBinaryArray *)&value->Value.bin;
+ if (!sa || !sa->lpbin) {
+ m_mapi.MAPIFreeBuffer(value);
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ LPENTRYID lpEid;
+ ULONG cbEid;
+ ULONG idx;
+ LPMESSAGE lpMsg;
+ nsCString type;
+ LPSPropValue pVal;
+ nsString subject;
+ ULONG total;
+
+ total = sa->cValues;
+ for (idx = 0; idx < total; idx++)
+ {
+ lpEid= (LPENTRYID) sa->lpbin[idx].lpb;
+ cbEid = sa->lpbin[idx].cb;
+
+ if (!m_mapi.OpenEntry(cbEid, lpEid, (LPUNKNOWN *) &lpMsg))
+ {
+
+ IMPORT_LOG1("*** Error opening messages in mailbox: %S\n", pName);
+ m_mapi.MAPIFreeBuffer(value);
+ return NS_ERROR_FAILURE;
+ }
+ // This is a contact, add it to the address book!
+ subject.Truncate();
+ pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT);
+ if (pVal)
+ m_mapi.GetStringFromProp(pVal, subject);
+
+ nsCOMPtr <nsIMdbRow> newRow;
+ nsCOMPtr <nsIMdbRow> oldRow;
+ pDb->GetNewRow(getter_AddRefs(newRow));
+ if (newRow) {
+ if (BuildCard(subject.get(), pDb, newRow, lpMsg, pFieldMap))
+ {
+ nsCOMPtr <nsIAbCard> userCard;
+ nsCOMPtr <nsIAbCard> newCard;
+ userCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ pDb->InitCardFromRow(userCard,newRow);
+
+ //add card to db
+ pDb->FindRowByCard(userCard,getter_AddRefs(oldRow));
+ if (oldRow)
+ newRow = oldRow;
+ else
+ pDb->AddCardRowToDB(newRow);
+
+ //add card list
+ pDb->AddListCardColumnsToRow(userCard,
+ newListRow,idx+1, getter_AddRefs(newCard),
+ true, nullptr, nullptr);
+ }
+ }
+ }
+ m_mapi.MAPIFreeBuffer(value);
+
+ rv = pDb->AddCardRowToDB(newListRow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pDb->SetListAddressTotal(newListRow, (uint32_t)total);
+ rv = pDb->AddListDirNode(newListRow);
+ return rv;
+}
+
+void nsOutlookMail::SanitizeValue(nsString& val)
+{
+ MsgReplaceSubstring(val, NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING(", "));
+ MsgReplaceChar(val, "\r\n", ',');
+}
+
+void nsOutlookMail::SplitString(nsString& val1, nsString& val2)
+{
+ // Find the last line if there is more than one!
+ int32_t idx = val1.RFind("\x0D\x0A");
+ int32_t cnt = 2;
+ if (idx == -1) {
+ cnt = 1;
+ idx = val1.RFindChar(13);
+ }
+ if (idx == -1)
+ idx= val1.RFindChar(10);
+ if (idx != -1) {
+ val2 = Substring(val1, idx + cnt);
+ val1.SetLength(idx);
+ SanitizeValue(val1);
+ }
+}
+
+bool nsOutlookMail::BuildCard(const char16_t *pName, nsIAddrDatabase *pDb, nsIMdbRow *newRow, LPMAPIPROP pUser, nsIImportFieldMap *pFieldMap)
+{
+
+ nsString lastName;
+ nsString firstName;
+ nsString eMail;
+ nsString nickName;
+ nsString middleName;
+ nsString secondEMail;
+ ULONG emailTag;
+
+ LPSPropValue pProp = m_mapi.GetMapiProperty(pUser, PR_EMAIL_ADDRESS);
+ if (!pProp) {
+ emailTag = m_mapi.GetEmailPropertyTag(pUser, OUTLOOK_EMAIL1_MAPI_ID1);
+ if (emailTag) {
+ pProp = m_mapi.GetMapiProperty(pUser, emailTag);
+ }
+ }
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, eMail);
+ SanitizeValue(eMail);
+ }
+
+ // for secondary email
+ emailTag = m_mapi.GetEmailPropertyTag(pUser, OUTLOOK_EMAIL2_MAPI_ID1);
+ if (emailTag) {
+ pProp = m_mapi.GetMapiProperty(pUser, emailTag);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, secondEMail);
+ SanitizeValue(secondEMail);
+ }
+ }
+
+ pProp = m_mapi.GetMapiProperty(pUser, PR_GIVEN_NAME);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, firstName);
+ SanitizeValue(firstName);
+ }
+ pProp = m_mapi.GetMapiProperty(pUser, PR_SURNAME);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, lastName);
+ SanitizeValue(lastName);
+ }
+ pProp = m_mapi.GetMapiProperty(pUser, PR_MIDDLE_NAME);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, middleName);
+ SanitizeValue(middleName);
+ }
+ pProp = m_mapi.GetMapiProperty(pUser, PR_NICKNAME);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, nickName);
+ SanitizeValue(nickName);
+ }
+ if (firstName.IsEmpty() && lastName.IsEmpty()) {
+ firstName = pName;
+ }
+
+ nsString displayName;
+ pProp = m_mapi.GetMapiProperty(pUser, PR_DISPLAY_NAME);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, displayName);
+ SanitizeValue(displayName);
+ }
+ if (displayName.IsEmpty()) {
+ if (firstName.IsEmpty())
+ displayName = pName;
+ else {
+ displayName = firstName;
+ if (!middleName.IsEmpty()) {
+ displayName.Append(char16_t(' '));
+ displayName.Append(middleName);
+ }
+ if (!lastName.IsEmpty()) {
+ displayName.Append(char16_t(' '));
+ displayName.Append(lastName);
+ }
+ }
+ }
+
+ // We now have the required fields
+ // write them out followed by any optional fields!
+ if (!displayName.IsEmpty()) {
+ pDb->AddDisplayName(newRow, NS_ConvertUTF16toUTF8(displayName).get());
+ }
+ if (!firstName.IsEmpty()) {
+ pDb->AddFirstName(newRow, NS_ConvertUTF16toUTF8(firstName).get());
+ }
+ if (!lastName.IsEmpty()) {
+ pDb->AddLastName(newRow, NS_ConvertUTF16toUTF8(lastName).get());
+ }
+ if (!nickName.IsEmpty()) {
+ pDb->AddNickName(newRow, NS_ConvertUTF16toUTF8(nickName).get());
+ }
+ if (!eMail.IsEmpty()) {
+ pDb->AddPrimaryEmail(newRow, NS_ConvertUTF16toUTF8(eMail).get());
+ }
+ if (!secondEMail.IsEmpty()) {
+ pDb->Add2ndEmail(newRow, NS_ConvertUTF16toUTF8(secondEMail).get());
+ }
+
+ // Do all of the extra fields!
+
+ nsString value;
+ nsString line2;
+
+ if (pFieldMap) {
+ int max = sizeof(gMapiFields) / sizeof(MAPIFields);
+ for (int i = 0; i < max; i++) {
+ pProp = m_mapi.GetMapiProperty(pUser, gMapiFields[i].mapiTag);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, value);
+ if (!value.IsEmpty()) {
+ if (gMapiFields[i].multiLine == kNoMultiLine) {
+ SanitizeValue(value);
+ pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].mozField, value.get());
+ }
+ else if (gMapiFields[i].multiLine == kIsMultiLine) {
+ pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].mozField, value.get());
+ }
+ else {
+ line2.Truncate();
+ SplitString(value, line2);
+ if (!value.IsEmpty())
+ pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].mozField, value.get());
+ if (!line2.IsEmpty())
+ pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].multiLine, line2.get());
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/mailnews/import/outlook/src/nsOutlookMail.h b/mailnews/import/outlook/src/nsOutlookMail.h
new file mode 100644
index 000000000..3aa317ece
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookMail.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOutlookMail_h___
+#define nsOutlookMail_h___
+
+#include "nsIArray.h"
+#include "nsStringGlue.h"
+#include "nsOutlookCompose.h"
+#include "nsIFile.h"
+#include "MapiApi.h"
+#include "MapiMessage.h"
+#include "nsIAddrDatabase.h"
+
+class nsIAddrDatabase;
+class nsIImportFieldMap;
+
+class nsOutlookMail {
+public:
+ nsOutlookMail();
+ ~nsOutlookMail();
+
+ nsresult GetMailFolders(nsIArray **pArray);
+ nsresult GetAddressBooks(nsIArray **pArray);
+ nsresult ImportMailbox(uint32_t *pDoneSoFar, bool *pAbort, int32_t index,
+ const char16_t *pName, nsIMsgFolder *pDest,
+ int32_t *pMsgCount);
+ static nsresult ImportMessage(LPMESSAGE lpMsg, nsIOutputStream *destOutputStream, nsMsgDeliverMode mode);
+ nsresult ImportAddresses(uint32_t *pCount, uint32_t *pTotal, const char16_t *pName, uint32_t id, nsIAddrDatabase *pDb, nsString& errors);
+private:
+ void OpenMessageStore(CMapiFolder *pNextFolder);
+ static BOOL WriteData(nsIOutputStream *pDest, const char *pData, int32_t len);
+
+ bool IsAddressBookNameUnique(nsString& name, nsString& list);
+ void MakeAddressBookNameUnique(nsString& name, nsString& list);
+ void SanitizeValue(nsString& val);
+ void SplitString(nsString& val1, nsString& val2);
+ bool BuildCard(const char16_t *pName, nsIAddrDatabase *pDb, nsIMdbRow *newRow, LPMAPIPROP pUser, nsIImportFieldMap *pFieldMap);
+ nsresult CreateList(const char16_t * pName, nsIAddrDatabase *pDb, LPMAPIPROP pUserList, nsIImportFieldMap *pFieldMap);
+
+private:
+ bool m_gotFolders;
+ bool m_gotAddresses;
+ bool m_haveMapi;
+ CMapiApi m_mapi;
+ CMapiFolderList m_folderList;
+ CMapiFolderList m_addressList;
+ CMapiFolderList m_storeList;
+ LPMDB m_lpMdb;
+};
+
+#endif /* nsOutlookMail_h___ */
diff --git a/mailnews/import/outlook/src/nsOutlookSettings.cpp b/mailnews/import/outlook/src/nsOutlookSettings.cpp
new file mode 100644
index 000000000..ec03c8ee2
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookSettings.cpp
@@ -0,0 +1,567 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ Outlook (Win32) settings
+
+*/
+
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsMsgUtils.h"
+#include "nsOutlookImport.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsIImportService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgAccount.h"
+#include "nsIImportSettings.h"
+#include "nsOutlookSettings.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgCompCID.h"
+#include "nsISmtpService.h"
+#include "nsISmtpServer.h"
+#include "nsOutlookStringBundle.h"
+#include "OutlookDebugLog.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsMsgI18N.h"
+#include <windows.h>
+#include "nsIWindowsRegKey.h"
+#include "nsComponentManagerUtils.h"
+#ifdef MOZILLA_INTERNAL_API
+#include "nsNativeCharsetUtils.h"
+#else
+#include "nsMsgI18N.h"
+#define NS_CopyNativeToUnicode(source, dest) \
+ nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), source, dest)
+#define NS_CopyUnicodeToNative(source, dest) \
+ nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(), source, dest)
+#endif
+
+class OutlookSettings {
+public:
+ static nsresult FindAccountsKey(nsIWindowsRegKey **aKey);
+ static nsresult QueryAccountSubKey(nsIWindowsRegKey **aKey);
+ static nsresult GetDefaultMailAccountName(nsAString &aName);
+
+ static bool DoImport(nsIMsgAccount **aAccount);
+
+ static bool DoIMAPServer(nsIMsgAccountManager *aMgr,
+ nsIWindowsRegKey *aKey,
+ const nsString &aServerName,
+ nsIMsgAccount **aAccount);
+ static bool DoPOP3Server(nsIMsgAccountManager *aMgr,
+ nsIWindowsRegKey *aKey,
+ const nsString &aServerName,
+ nsIMsgAccount **aAccount);
+
+ static void SetIdentities(nsIMsgAccountManager *pMgr,
+ nsIMsgAccount *pAcc,
+ nsIWindowsRegKey *aKey);
+
+ static nsresult SetSmtpServer(nsIMsgAccountManager *aMgr,
+ nsIMsgAccount *aAcc,
+ nsIMsgIdentity *aId,
+ const nsString &aServer,
+ const nsString &aUser);
+ static nsresult SetSmtpServerKey(nsIMsgIdentity *aId,
+ nsISmtpServer *aServer);
+ static nsresult GetAccountName(nsIWindowsRegKey *aKey,
+ const nsString &aDefaultName,
+ nsAString &aAccountName);
+};
+
+#define OUTLOOK2003_REGISTRY_KEY "Software\\Microsoft\\Office\\Outlook\\OMI Account Manager"
+#define OUTLOOK98_REGISTRY_KEY "Software\\Microsoft\\Office\\8.0\\Outlook\\OMI Account Manager"
+
+////////////////////////////////////////////////////////////////////////
+nsresult nsOutlookSettings::Create(nsIImportSettings** aImport)
+{
+ NS_PRECONDITION(aImport != nullptr, "null ptr");
+ if (! aImport)
+ return NS_ERROR_NULL_POINTER;
+
+ *aImport = new nsOutlookSettings();
+ if (! *aImport)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+nsOutlookSettings::nsOutlookSettings()
+{
+}
+
+nsOutlookSettings::~nsOutlookSettings()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsOutlookSettings, nsIImportSettings)
+
+NS_IMETHODIMP nsOutlookSettings::AutoLocate(char16_t **description, nsIFile **location, bool *_retval)
+{
+ NS_PRECONDITION(description != nullptr, "null ptr");
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!description || !_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *description = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME);
+ *_retval = false;
+
+ if (location)
+ *location = nullptr;
+
+ // look for the registry key for the accounts
+ nsCOMPtr<nsIWindowsRegKey> key;
+ *_retval = NS_SUCCEEDED(OutlookSettings::FindAccountsKey(getter_AddRefs(key)));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookSettings::SetLocation(nsIFile *location)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookSettings::Import(nsIMsgAccount **localMailAccount, bool *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+
+ if (OutlookSettings::DoImport(localMailAccount)) {
+ *_retval = true;
+ IMPORT_LOG0("Settings import appears successful\n");
+ }
+ else {
+ *_retval = false;
+ IMPORT_LOG0("Settings import returned FALSE\n");
+ }
+
+ return NS_OK;
+}
+
+
+nsresult OutlookSettings::FindAccountsKey(nsIWindowsRegKey **aKey)
+{
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> key =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING(OUTLOOK2003_REGISTRY_KEY),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE |
+ nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS);
+
+ if (NS_FAILED(rv)) {
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING(OUTLOOK98_REGISTRY_KEY),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE |
+ nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS);
+ }
+
+ if (NS_SUCCEEDED(rv))
+ key.forget(aKey);
+
+ return rv;
+}
+
+nsresult OutlookSettings::QueryAccountSubKey(nsIWindowsRegKey **aKey)
+{
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> key =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING(OUTLOOK2003_REGISTRY_KEY),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE |
+ nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS);
+ if (NS_SUCCEEDED(rv)) {
+ key.forget(aKey);
+ return rv;
+ }
+
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING(OUTLOOK98_REGISTRY_KEY),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE |
+ nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS);
+ if (NS_SUCCEEDED(rv)) {
+ key.forget(aKey);
+ return rv;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult OutlookSettings::GetDefaultMailAccountName(nsAString &aName)
+{
+ nsCOMPtr<nsIWindowsRegKey> key;
+ nsresult rv = QueryAccountSubKey(getter_AddRefs(key));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return key->ReadStringValue(NS_LITERAL_STRING("Default Mail Account"), aName);
+}
+
+bool OutlookSettings::DoImport(nsIMsgAccount **aAccount)
+{
+ nsCOMPtr<nsIWindowsRegKey> key;
+ nsresult rv = OutlookSettings::FindAccountsKey(getter_AddRefs(key));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error finding Outlook registry account keys\n");
+ return false;
+ }
+
+ nsCOMPtr<nsIMsgAccountManager> accMgr =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create a account manager!\n");
+ return false;
+ }
+
+ nsAutoString defMailName;
+ rv = GetDefaultMailAccountName(defMailName);
+
+ uint32_t childCount;
+ key->GetChildCount(&childCount);
+
+ uint32_t accounts = 0;
+ uint32_t popCount = 0;
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsAutoString keyName;
+ key->GetChildName(i, keyName);
+ nsCOMPtr<nsIWindowsRegKey> subKey;
+ rv = key->OpenChild(keyName,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE,
+ getter_AddRefs(subKey));
+ if (NS_FAILED(rv))
+ continue;
+
+ // Get the values for this account.
+ nsAutoCString nativeKeyName;
+ NS_CopyUnicodeToNative(keyName, nativeKeyName);
+ IMPORT_LOG1("Opened Outlook account: %s\n", nativeKeyName.get());
+
+ nsCOMPtr<nsIMsgAccount> account;
+ nsAutoString value;
+ rv = subKey->ReadStringValue(NS_LITERAL_STRING("IMAP Server"), value);
+ if (NS_SUCCEEDED(rv) &&
+ DoIMAPServer(accMgr, subKey, value, getter_AddRefs(account)))
+ accounts++;
+
+ rv = subKey->ReadStringValue(NS_LITERAL_STRING("POP3 Server"), value);
+ if (NS_SUCCEEDED(rv) &&
+ DoPOP3Server(accMgr, subKey, value, getter_AddRefs(account))) {
+ popCount++;
+ accounts++;
+ if (aAccount && account) {
+ // If we created a mail account, get rid of it since
+ // we have 2 POP accounts!
+ if (popCount > 1)
+ NS_RELEASE(*aAccount);
+ else
+ NS_ADDREF(*aAccount = account);
+ }
+ }
+
+ // Is this the default account?
+ if (account && keyName.Equals(defMailName))
+ accMgr->SetDefaultAccount(account);
+ }
+
+ // Now save the new acct info to pref file.
+ rv = accMgr->SaveAccountInfo();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file");
+
+ return accounts != 0;
+}
+
+nsresult OutlookSettings::GetAccountName(nsIWindowsRegKey *aKey,
+ const nsString &aDefaultName,
+ nsAString &aAccountName)
+{
+ nsresult rv;
+ rv = aKey->ReadStringValue(NS_LITERAL_STRING("Account Name"), aAccountName);
+ if (NS_FAILED(rv))
+ aAccountName.Assign(aDefaultName);
+
+ return NS_OK;
+}
+
+bool OutlookSettings::DoIMAPServer(nsIMsgAccountManager *aMgr,
+ nsIWindowsRegKey *aKey,
+ const nsString &aServerName,
+ nsIMsgAccount **aAccount)
+{
+ nsAutoString userName;
+ nsresult rv;
+ rv = aKey->ReadStringValue(NS_LITERAL_STRING("IMAP User Name"), userName);
+ if (NS_FAILED(rv))
+ return false;
+
+ bool result = false;
+
+ // I now have a user name/server name pair, find out if it already exists?
+ nsAutoCString nativeUserName;
+ NS_CopyUnicodeToNative(userName, nativeUserName);
+ nsAutoCString nativeServerName;
+ NS_CopyUnicodeToNative(aServerName, nativeServerName);
+ nsCOMPtr<nsIMsgIncomingServer> in;
+ rv = aMgr->FindServer(nativeUserName,
+ nativeServerName,
+ NS_LITERAL_CSTRING("imap"),
+ getter_AddRefs(in));
+ if (NS_FAILED(rv) || (in == nullptr)) {
+ // Create the incoming server and an account for it?
+ rv = aMgr->CreateIncomingServer(nativeUserName,
+ nativeServerName,
+ NS_LITERAL_CSTRING("imap"),
+ getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv) && in) {
+ rv = in->SetType(NS_LITERAL_CSTRING("imap"));
+ // TODO SSL, auth method
+
+ IMPORT_LOG2("Created IMAP server named: %s, userName: %s\n",
+ nativeServerName.get(), nativeUserName.get());
+
+ nsAutoString prettyName;
+ if (NS_SUCCEEDED(GetAccountName(aKey, aServerName, prettyName)))
+ rv = in->SetPrettyName(prettyName);
+ // We have a server, create an account.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = aMgr->CreateAccount(getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ rv = account->SetIncomingServer(in);
+
+ IMPORT_LOG0("Created an account and set the IMAP server as the incoming server\n");
+
+ // Fiddle with the identities
+ SetIdentities(aMgr, account, aKey);
+ result = true;
+ if (aAccount)
+ account.forget(aAccount);
+ }
+ }
+ }
+ else
+ result = true;
+
+ return result;
+}
+
+bool OutlookSettings::DoPOP3Server(nsIMsgAccountManager *aMgr,
+ nsIWindowsRegKey *aKey,
+ const nsString &aServerName,
+ nsIMsgAccount **aAccount)
+{
+ nsAutoString userName;
+ nsresult rv;
+ rv = aKey->ReadStringValue(NS_LITERAL_STRING("POP3 User Name"), userName);
+ if (NS_FAILED(rv))
+ return false;
+
+ // I now have a user name/server name pair, find out if it already exists?
+ nsAutoCString nativeUserName;
+ NS_CopyUnicodeToNative(userName, nativeUserName);
+ nsAutoCString nativeServerName;
+ NS_CopyUnicodeToNative(aServerName, nativeServerName);
+ nsCOMPtr<nsIMsgIncomingServer> in;
+ rv = aMgr->FindServer(nativeUserName,
+ nativeServerName,
+ NS_LITERAL_CSTRING("pop3"),
+ getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv))
+ return true;
+
+ // Create the incoming server and an account for it?
+ rv = aMgr->CreateIncomingServer(nativeUserName,
+ nativeServerName,
+ NS_LITERAL_CSTRING("pop3"),
+ getter_AddRefs(in));
+ rv = in->SetType(NS_LITERAL_CSTRING("pop3"));
+
+ // TODO SSL, auth method
+
+ nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(in);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // set local folders as the Inbox to use for this POP3 server
+ nsCOMPtr<nsIMsgIncomingServer> localFoldersServer;
+ aMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer));
+
+ if (!localFoldersServer) {
+ // XXX: We may need to move this local folder creation code to the generic nsImportSettings code
+ // if the other import modules end up needing to do this too.
+ // if Local Folders does not exist already, create it
+ rv = aMgr->CreateLocalMailAccount();
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create Local Folders!\n");
+ return false;
+ }
+ aMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer));
+ }
+
+ // now get the account for this server
+ nsCOMPtr<nsIMsgAccount> localFoldersAccount;
+ aMgr->FindAccountForServer(localFoldersServer, getter_AddRefs(localFoldersAccount));
+ if (localFoldersAccount) {
+ nsCString localFoldersAcctKey;
+ localFoldersAccount->GetKey(localFoldersAcctKey);
+ pop3Server->SetDeferredToAccount(localFoldersAcctKey);
+ pop3Server->SetDeferGetNewMail(true);
+ }
+
+ IMPORT_LOG2("Created POP3 server named: %s, userName: %s\n",
+ nativeServerName.get(),
+ nativeUserName.get());
+
+ nsString prettyName;
+ rv = GetAccountName(aKey, aServerName, prettyName);
+ if (NS_FAILED(rv))
+ return false;
+
+ rv = in->SetPrettyName(prettyName);
+ // We have a server, create an account.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = aMgr->CreateAccount(getter_AddRefs(account));
+ if (NS_FAILED(rv))
+ return false;
+
+ rv = account->SetIncomingServer(in);
+
+ IMPORT_LOG0("Created a new account and set the incoming server to the POP3 server.\n");
+
+ uint32_t leaveOnServer;
+ rv = aKey->ReadIntValue(NS_LITERAL_STRING("Leave Mail On Server"), &leaveOnServer);
+ if (NS_SUCCEEDED(rv))
+ pop3Server->SetLeaveMessagesOnServer(leaveOnServer == 1 ? true : false);
+
+ // Fiddle with the identities
+ SetIdentities(aMgr, account, aKey);
+
+ if (aAccount)
+ account.forget(aAccount);
+
+ return true;
+}
+
+void OutlookSettings::SetIdentities(nsIMsgAccountManager *aMgr,
+ nsIMsgAccount *aAcc,
+ nsIWindowsRegKey *aKey)
+{
+ // Get the relevant information for an identity
+ nsAutoString name;
+ aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Display Name"), name);
+
+ nsAutoString server;
+ aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Server"), server);
+
+ nsAutoString email;
+ aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Email Address"), email);
+
+ nsAutoString reply;
+ aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Reply To Email Address"), reply);
+
+ nsAutoString userName;
+ aKey->ReadStringValue(NS_LITERAL_STRING("SMTP User Name"), userName);
+
+ nsAutoString orgName;
+ aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Organization Name"), orgName);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgIdentity> id;
+ if (!email.IsEmpty() && !name.IsEmpty() && !server.IsEmpty()) {
+ // The default identity, nor any other identities matched,
+ // create a new one and add it to the account.
+ rv = aMgr->CreateIdentity(getter_AddRefs(id));
+ if (id) {
+ id->SetFullName(name);
+ id->SetOrganization(orgName);
+
+ nsAutoCString nativeEmail;
+ NS_CopyUnicodeToNative(email, nativeEmail);
+ id->SetEmail(nativeEmail);
+ if (!reply.IsEmpty()) {
+ nsAutoCString nativeReply;
+ NS_CopyUnicodeToNative(reply, nativeReply);
+ id->SetReplyTo(nativeReply);
+ }
+ aAcc->AddIdentity(id);
+
+ nsAutoCString nativeName;
+ NS_CopyUnicodeToNative(name, nativeName);
+ IMPORT_LOG0("Created identity and added to the account\n");
+ IMPORT_LOG1("\tname: %s\n", nativeName.get());
+ IMPORT_LOG1("\temail: %s\n", nativeEmail.get());
+ }
+ }
+
+ if (userName.IsEmpty()) {
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = aAcc->GetIncomingServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer) {
+ nsAutoCString nativeUserName;
+ rv = incomingServer->GetUsername(nativeUserName);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get UserName from incomingServer");
+ NS_CopyNativeToUnicode(nativeUserName, userName);
+ }
+ }
+
+ SetSmtpServer(aMgr, aAcc, id, server, userName);
+}
+
+nsresult OutlookSettings::SetSmtpServerKey(nsIMsgIdentity *aId,
+ nsISmtpServer *aServer)
+{
+ nsAutoCString smtpServerKey;
+ aServer->GetKey(getter_Copies(smtpServerKey));
+ return aId->SetSmtpServerKey(smtpServerKey);
+}
+
+nsresult OutlookSettings::SetSmtpServer(nsIMsgAccountManager *aMgr,
+ nsIMsgAccount *aAcc,
+ nsIMsgIdentity *aId,
+ const nsString &aServer,
+ const nsString &aUser)
+{
+ nsresult rv;
+ nsCOMPtr<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString nativeUserName;
+ NS_CopyUnicodeToNative(aUser, nativeUserName);
+ nsAutoCString nativeServerName;
+ NS_CopyUnicodeToNative(aServer, nativeServerName);
+ nsCOMPtr<nsISmtpServer> foundServer;
+ rv = smtpService->FindServer(nativeUserName.get(),
+ nativeServerName.get(),
+ getter_AddRefs(foundServer));
+ if (NS_SUCCEEDED(rv) && foundServer) {
+ if (aId)
+ SetSmtpServerKey(aId, foundServer);
+ IMPORT_LOG1("SMTP server already exists: %s\n",
+ nativeServerName.get());
+ return rv;
+ }
+
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = smtpService->CreateServer(getter_AddRefs(smtpServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ smtpServer->SetHostname(nativeServerName);
+ if (!aUser.IsEmpty())
+ smtpServer->SetUsername(nativeUserName);
+
+ if (aId)
+ SetSmtpServerKey(aId, smtpServer);
+
+ // TODO SSL, auth method
+ IMPORT_LOG1("Ceated new SMTP server: %s\n",
+ nativeServerName.get());
+ return NS_OK;
+}
+
diff --git a/mailnews/import/outlook/src/nsOutlookSettings.h b/mailnews/import/outlook/src/nsOutlookSettings.h
new file mode 100644
index 000000000..ea074e5b4
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookSettings.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOutlookSettings_h___
+#define nsOutlookSettings_h___
+
+#include "nsIImportSettings.h"
+
+
+class nsOutlookSettings : public nsIImportSettings {
+public:
+ nsOutlookSettings();
+
+ static nsresult Create(nsIImportSettings** aImport);
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsIImportSettings interface
+ NS_DECL_NSIIMPORTSETTINGS
+
+private:
+ virtual ~nsOutlookSettings();
+
+};
+
+#endif /* nsOutlookSettings_h___ */
diff --git a/mailnews/import/outlook/src/nsOutlookStringBundle.cpp b/mailnews/import/outlook/src/nsOutlookStringBundle.cpp
new file mode 100644
index 000000000..826e30e40
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookStringBundle.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsMsgUtils.h"
+#include "nsIStringBundle.h"
+#include "nsOutlookStringBundle.h"
+#include "nsIServiceManager.h"
+#include "nsIURI.h"
+#include "mozilla/Services.h"
+
+#define OUTLOOK_MSGS_URL "chrome://messenger/locale/outlookImportMsgs.properties"
+
+nsIStringBundle * nsOutlookStringBundle::m_pBundle = nullptr;
+
+nsIStringBundle *nsOutlookStringBundle::GetStringBundle(void)
+{
+ if (m_pBundle)
+ return m_pBundle;
+
+ char* propertyURL = OUTLOOK_MSGS_URL;
+ nsIStringBundle* sBundle = nullptr;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ if (sBundleService) {
+ sBundleService->CreateBundle(propertyURL, &sBundle);
+ }
+
+ m_pBundle = sBundle;
+
+ return sBundle;
+}
+
+void nsOutlookStringBundle::GetStringByID(int32_t stringID, nsString& result)
+{
+ char16_t *ptrv = GetStringByID(stringID);
+ result = ptrv;
+ FreeString(ptrv);
+}
+
+char16_t *nsOutlookStringBundle::GetStringByID(int32_t stringID)
+{
+ if (m_pBundle)
+ m_pBundle = GetStringBundle();
+
+ if (m_pBundle) {
+ char16_t *ptrv = nullptr;
+ nsresult rv = m_pBundle->GetStringFromID(stringID, &ptrv);
+
+ if (NS_SUCCEEDED(rv) && ptrv)
+ return ptrv;
+ }
+
+ nsString resultString;
+ resultString.AppendLiteral("[StringID ");
+ resultString.AppendInt(stringID);
+ resultString.AppendLiteral("?]");
+
+ return ToNewUnicode(resultString);
+}
+
+void nsOutlookStringBundle::Cleanup(void)
+{
+ if (m_pBundle)
+ m_pBundle->Release();
+ m_pBundle = nullptr;
+}
diff --git a/mailnews/import/outlook/src/nsOutlookStringBundle.h b/mailnews/import/outlook/src/nsOutlookStringBundle.h
new file mode 100644
index 000000000..1351088d4
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookStringBundle.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsOutlookStringBundle_H__
+#define _nsOutlookStringBundle_H__
+
+#include "nsCRTGlue.h"
+#include "nsStringGlue.h"
+
+class nsIStringBundle;
+
+class nsOutlookStringBundle {
+public:
+ static char16_t * GetStringByID(int32_t stringID);
+ static void GetStringByID(int32_t stringID, nsString& result);
+ static nsIStringBundle * GetStringBundle(void); // don't release
+ static void FreeString(char16_t *pStr) { NS_Free(pStr);}
+ static void Cleanup(void);
+private:
+ static nsIStringBundle * m_pBundle;
+};
+
+
+
+#define OUTLOOKIMPORT_NAME 2000
+#define OUTLOOKIMPORT_DESCRIPTION 2010
+#define OUTLOOKIMPORT_MAILBOX_SUCCESS 2002
+#define OUTLOOKIMPORT_MAILBOX_BADPARAM 2003
+#define OUTLOOKIMPORT_MAILBOX_CONVERTERROR 2004
+#define OUTLOOKIMPORT_ADDRNAME 2005
+#define OUTLOOKIMPORT_ADDRESS_SUCCESS 2006
+#define OUTLOOKIMPORT_ADDRESS_BADPARAM 2007
+#define OUTLOOKIMPORT_ADDRESS_BADSOURCEFILE 2008
+#define OUTLOOKIMPORT_ADDRESS_CONVERTERROR 2009
+
+
+#endif /* _nsOutlookStringBundle_H__ */
diff --git a/mailnews/import/outlook/src/rtfDecoder.cpp b/mailnews/import/outlook/src/rtfDecoder.cpp
new file mode 100644
index 000000000..837beec0b
--- /dev/null
+++ b/mailnews/import/outlook/src/rtfDecoder.cpp
@@ -0,0 +1,520 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stack>
+#include <map>
+#include <sstream>
+#include "Windows.h"
+#include "rtfDecoder.h"
+
+#define SIZEOF(x) (sizeof(x)/sizeof((x)[0]))
+#define IS_DIGIT(i) ((i) >= '0' && (i) <= '9')
+#define IS_ALPHA(VAL) (((VAL) >= 'a' && (VAL) <= 'z') || ((VAL) >= 'A' && (VAL) <= 'Z'))
+
+inline int HexToInt(char ch)
+{
+ switch (ch) {
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
+ return ch-'0';
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ return ch-'A'+10;
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ return ch-'a'+10;
+ default:
+ return 0;
+ }
+}
+
+inline int CharsetToCP(int charset)
+{
+ // We don't know the Code page for the commented out charsets.
+ switch (charset) {
+ case 0: return 1252; // ANSI
+ case 1: return 0; // Default
+//case 2: return 42; // Symbol
+ case 2: return 1252; // Symbol
+ case 77: return 10000; // Mac Roman
+ case 78: return 10001; // Mac Shift Jis
+ case 79: return 10003; // Mac Hangul
+ case 80: return 10008; // Mac GB2312
+ case 81: return 10002; // Mac Big5
+//case 82: Mac Johab (old)
+ case 83: return 10005; // Mac Hebrew
+ case 84: return 10004; // Mac Arabic
+ case 85: return 10006; // Mac Greek
+ case 86: return 10081; // Mac Turkish
+ case 87: return 10021; // Mac Thai
+ case 88: return 10029; // Mac East Europe
+ case 89: return 10007; // Mac Russian
+ case 128: return 932; // Shift JIS
+ case 129: return 949; // Hangul
+ case 130: return 1361; // Johab
+ case 134: return 936; // GB2312
+ case 136: return 950; // Big5
+ case 161: return 1253; // Greek
+ case 162: return 1254; // Turkish
+ case 163: return 1258; // Vietnamese
+ case 177: return 1255; // Hebrew
+ case 178: return 1256; // Arabic
+//case 179: Arabic Traditional (old)
+//case 180: Arabic user (old)
+//case 181: Hebrew user (old)
+ case 186: return 1257; // Baltic
+ case 204: return 1251; // Russian
+ case 222: return 874; // Thai
+ case 238: return 1250; // Eastern European
+ case 254: return 437; // PC 437
+ case 255: return 850; // OEM
+ default: return CP_ACP;
+ }
+}
+
+struct FontInfo {
+ enum Options {has_fcharset = 0x0001,
+ has_cpg = 0x0002};
+ unsigned int options;
+ int fcharset;
+ unsigned int cpg;
+ FontInfo() : options(0), fcharset(0), cpg(0xFFFFFFFF) {}
+ unsigned int Codepage()
+ {
+ if (options & has_cpg)
+ return cpg;
+ else if (options & has_fcharset)
+ return CharsetToCP(fcharset);
+ else return 0xFFFFFFFF;
+ }
+};
+typedef std::map<int, FontInfo> Fonttbl;
+
+struct LocalState {
+ bool fonttbl; // When fonts are being defined
+ int f; // Index of the font being defined/used; defines the codepage if no \cpg
+ unsigned int uc; // ucN keyword value; its default is 1
+ unsigned int codepage;// defined by \cpg
+};
+typedef std::stack<LocalState> StateStack;
+
+struct GlobalState {
+ enum Pcdata_state { pcdsno, pcdsin, pcdsfinished };
+ std::istream& stream;
+ Fonttbl fonttbl;
+ StateStack stack;
+ unsigned int codepage; // defined by \ansi, \mac, \pc, \pca, and \ansicpgN
+ int deff;
+ std::stringstream pcdata_a;
+ unsigned int pcdata_a_codepage;
+ Pcdata_state pcdata_a_state;
+
+ GlobalState(std::istream& s)
+ : stream(s), codepage(CP_ACP), deff(-1), pcdata_a_state(pcdsno)
+ {
+ LocalState st;
+ st.fonttbl = false;
+ st.f = -1;
+ st.uc = 1;
+ st.codepage = 0xFFFFFFFF;
+ stack.push(st);
+ }
+ unsigned int GetCurrentCP()
+ {
+ if (stack.top().codepage != 0xFFFFFFFF) // \cpg in use
+ return stack.top().codepage;
+ // \cpg not used; use font settings
+ int f = (stack.top().f != -1) ? stack.top().f : deff;
+ if (f != -1) {
+ Fonttbl::iterator iter = fonttbl.find(f);
+ if (iter != fonttbl.end()) {
+ unsigned int cp = iter->second.Codepage();
+ if (cp != 0xFFFFFFFF)
+ return cp;
+ }
+ }
+ return codepage; // No overrides; use the top-level legacy setting
+ }
+};
+
+struct Keyword {
+ char name[33];
+ bool hasVal;
+ int val;
+};
+
+class Lexem {
+public:
+ enum Type {ltGroupBegin, ltGroupEnd, ltKeyword, ltPCDATA_A, ltPCDATA_W,
+ ltBDATA, ltEOF, ltError};
+ Lexem(Type t=ltError) : m_type(t) {}
+ Lexem(Lexem& from) // Move pointers when copying
+ {
+ switch (m_type = from.m_type) {
+ case ltKeyword:
+ m_keyword = from.m_keyword;
+ break;
+ case ltPCDATA_A:
+ m_pcdata_a = from.m_pcdata_a;
+ break;
+ case ltPCDATA_W:
+ m_pcdata_w = from.m_pcdata_w;
+ break;
+ case ltBDATA:
+ m_bdata = from.m_bdata;
+ from.m_type = ltError;
+ break;
+ }
+ }
+ ~Lexem() { Clear(); }
+ Lexem& operator = (Lexem& from)
+ {
+ if (&from != this) {
+ Clear();
+ switch (m_type = from.m_type) {
+ case ltKeyword:
+ m_keyword = from.m_keyword;
+ break;
+ case ltPCDATA_A:
+ m_pcdata_a = from.m_pcdata_a;
+ break;
+ case ltPCDATA_W:
+ m_pcdata_w = from.m_pcdata_w;
+ break;
+ case ltBDATA:
+ m_bdata = from.m_bdata;
+ from.m_type = ltError;
+ break;
+ }
+ }
+ return *this;
+ }
+ Type type() const { return m_type; }
+ void SetPCDATA_A(char chdata)
+ {
+ Clear();
+ m_pcdata_a = chdata;
+ m_type = ltPCDATA_A;
+ }
+ void SetPCDATA_W(wchar_t chdata)
+ {
+ Clear();
+ m_pcdata_w = chdata;
+ m_type = ltPCDATA_W;
+ }
+ void SetBDATA(const char* data, int sz)
+ {
+ char* tmp = new char[sz]; // to allow getting the data from itself
+ if (tmp) {
+ memcpy(tmp, data, sz);
+ Clear();
+ m_bdata.data = tmp;
+ m_bdata.sz = sz;
+ m_type = ltBDATA;
+ }
+ else m_type = ltError;
+ }
+ void SetKeyword(const Keyword& src)
+ {
+ Clear();
+ m_type = ltKeyword;
+ m_keyword = src;
+ }
+ void SetKeyword(const char* name, bool hasVal=false, int val=0)
+ {
+ char tmp[SIZEOF(m_keyword.name)];
+ strncpy(tmp, name, SIZEOF(m_keyword.name)-1); // to allow copy drom itself
+ tmp[SIZEOF(m_keyword.name)-1]=0;
+ Clear();
+ m_type = ltKeyword;
+ memcpy(m_keyword.name, tmp, SIZEOF(m_keyword.name));
+ m_keyword.hasVal=hasVal;
+ m_keyword.val=val;
+ }
+ const char* KeywordName() const {
+ return (m_type == ltKeyword) ? m_keyword.name : 0; }
+ const int* KeywordVal() const {
+ return ((m_type == ltKeyword) && m_keyword.hasVal) ? &m_keyword.val : 0; }
+ char pcdata_a() const { return (m_type == ltPCDATA_A) ? m_pcdata_a : 0; }
+ wchar_t pcdata_w() const { return (m_type == ltPCDATA_W) ? m_pcdata_w : 0; }
+ const char* bdata() const { return (m_type == ltBDATA) ? m_bdata.data : 0; }
+ int bdata_sz() const { return (m_type == ltBDATA) ? m_bdata.sz : 0; }
+ static Lexem eof;
+ static Lexem groupBegin;
+ static Lexem groupEnd;
+ static Lexem error;
+private:
+ struct BDATA {
+ size_t sz;
+ char* data;
+ };
+
+ Type m_type;
+ union {
+ Keyword m_keyword;
+ char m_pcdata_a;
+ wchar_t m_pcdata_w;
+ BDATA m_bdata;
+ };
+ // This function leaves the object in the broken state. Must be followed
+ // by a correct initialization.
+ void Clear()
+ {
+ switch (m_type) {
+ case ltBDATA:
+ delete[] m_bdata.data;
+ break;
+ }
+// m_type = ltError;
+ }
+};
+
+Lexem Lexem::eof(ltEOF);
+Lexem Lexem::groupBegin(ltGroupBegin);
+Lexem Lexem::groupEnd(ltGroupEnd);
+Lexem Lexem::error(ltError);
+
+// This function moves pos. When calling the function, pos must be next to the
+// backslash; pos must be in the same sequence and before end!
+Keyword GetKeyword(std::istream& stream)
+{
+ Keyword keyword = {"", false, 0};
+ char ch;
+ if (stream.get(ch).eof())
+ return keyword;
+ // Control word; maybe delimiter and value
+ if (IS_ALPHA(ch)) {
+ int i = 0;
+ do {
+ // We take up to 32 characters into account, skipping over extra
+ // characters (allowing for some non-conformant implementation).
+ if (i < 32)
+ keyword.name[i++] = ch;
+ } while (!stream.get(ch).eof() && IS_ALPHA(ch));
+ keyword.name[i] = 0; // NULL-terminating
+ if (!stream.eof() && (IS_DIGIT(ch) || (ch == '-'))) { // Value begin
+ keyword.hasVal = true;
+ bool negative = (ch == '-');
+ if (negative) stream.get(ch);
+ i = 0;
+ while (!stream.eof() && IS_DIGIT(ch)) {
+ // We take into account only 10 digits, skip other. Older specs stated
+ // that we must be ready for an arbitrary number of digits.
+ if (i++ < 10)
+ keyword.val = keyword.val*10 + (ch - '0');
+ stream.get(ch);
+ }
+ if (negative) keyword.val = -keyword.val;
+ }
+ // End of control word; the space is just a delimiter - skip it
+ if (!stream.eof() && !(ch == ' '))
+ stream.unget();
+ }
+ else { // Control symbol
+ keyword.name[0] = ch, keyword.name[1] = 0;
+ }
+ return keyword;
+}
+
+Lexem GetLexem(std::istream& stream)
+{
+ Lexem result;
+ // We always stay at the beginning of the next lexem or a crlf
+ // If it's a brace then it's group begin/end
+ // If it's a backslash -> Preprocess
+ // - if it's a \u or \' -> make UTF16 character
+ // - else it's a keyword -> Process (e.g., remember the codepage)
+ // - (if the keyword is \bin then the following is #BDATA)
+ // If it's some other character -> Preprocess
+ // - if it's 0x09 -> it's the keyword \tab
+ // - else it's a PCDATA
+ char ch;
+ while (!stream.get(ch).eof() && ((ch == '\n') || (ch == '\r'))); // Skip crlf
+ if (stream.eof())
+ result = Lexem::eof;
+ else {
+ switch (ch) {
+ case '{': // Group begin
+ case '}': // Group end
+ result = (ch == '{') ? Lexem::groupBegin : Lexem::groupEnd;
+ break;
+ case '\\': // Keyword
+ result.SetKeyword(GetKeyword(stream));
+ break;
+ case '\t': // tab
+ result.SetKeyword("tab");
+ break;
+ default: // PSDATA?
+ result.SetPCDATA_A(ch);
+ break;
+ }
+ }
+ return result;
+}
+
+void PreprocessLexem(/*inout*/Lexem& lexem, std::istream& stream, int uc)
+{
+ if (lexem.type() == Lexem::ltKeyword) {
+ if (lexem.KeywordName()[0] == 0) // Empty keyword - maybe eof?
+ lexem = Lexem::error;
+ else if (eq(lexem.KeywordName(), "u")) {
+ // Unicode character - get the UTF16 and skip the uc characters
+ if (const int* val = lexem.KeywordVal()) {
+ lexem.SetPCDATA_W(*val);
+ stream.ignore(uc);
+ }
+ else lexem = Lexem::error;
+ }
+ else if (eq(lexem.KeywordName(), "'")) {
+ // 8-bit character (\'hh) -> use current codepage
+ char ch, ch1;
+ if (!stream.get(ch).eof()) ch1 = HexToInt(ch);
+ if (!stream.get(ch).eof()) (ch1 <<= 4) += HexToInt(ch);
+ lexem.SetPCDATA_A(ch1);
+ }
+ else if (eq(lexem.KeywordName(), "\\") || eq(lexem.KeywordName(), "{") ||
+ eq(lexem.KeywordName(), "}")) // escaped characters
+ lexem.SetPCDATA_A(lexem.KeywordName()[0]);
+ else if (eq(lexem.KeywordName(), "bin")) {
+ if (const int* i = lexem.KeywordVal()) {
+ char* data = new char[*i];
+ if (data) {
+ stream.read(data, *i);
+ if (stream.fail())
+ lexem = Lexem::error;
+ else
+ lexem.SetBDATA(data, *i);
+ delete[] data;
+ }
+ else lexem = Lexem::error;
+ }
+ else lexem = Lexem::error;
+ }
+ else if (eq(lexem.KeywordName(), "\n") || eq(lexem.KeywordName(), "\r")) {
+ // escaped cr or lf
+ lexem.SetKeyword("par");
+ }
+ }
+}
+
+void UpdateState(const Lexem& lexem, /*inout*/GlobalState& globalState)
+{
+ switch (globalState.pcdata_a_state) {
+ case GlobalState::pcdsfinished: // Last time we finished the pcdata
+ globalState.pcdata_a_state = GlobalState::pcdsno;
+ break;
+ case GlobalState::pcdsin:
+ // to be reset later if still in the pcdata
+ globalState.pcdata_a_state = GlobalState::pcdsfinished;
+ break;
+ }
+
+ switch (lexem.type()) {
+ case Lexem::ltGroupBegin:
+ globalState.stack.push(globalState.stack.top());
+ break;
+ case Lexem::ltGroupEnd:
+ globalState.stack.pop();
+ break;
+ case Lexem::ltKeyword:
+ {
+ const int* val = lexem.KeywordVal();
+ if (eq(lexem.KeywordName(), "ansi")) globalState.codepage = CP_ACP;
+ else if (eq(lexem.KeywordName(), "mac")) globalState.codepage = CP_MACCP;
+ else if (eq(lexem.KeywordName(), "pc")) globalState.codepage = 437;
+ else if (eq(lexem.KeywordName(), "pca")) globalState.codepage = 850;
+ else if (eq(lexem.KeywordName(), "ansicpg") && val)
+ globalState.codepage = static_cast<unsigned int>(*val);
+ else if (eq(lexem.KeywordName(), "deff") && val)
+ globalState.deff = *val;
+ else if (eq(lexem.KeywordName(), "fonttbl")) globalState.stack.top().fonttbl = true;
+ else if (eq(lexem.KeywordName(), "f") && val) {
+ globalState.stack.top().f = *val;
+ }
+ else if (eq(lexem.KeywordName(), "fcharset") &&
+ globalState.stack.top().fonttbl &&
+ (globalState.stack.top().f != -1) && val) {
+ FontInfo& f = globalState.fonttbl[globalState.stack.top().f];
+ f.options |= FontInfo::has_fcharset;
+ f.fcharset = *val;
+ }
+ else if (eq(lexem.KeywordName(), "cpg") && val) {
+ if (globalState.stack.top().fonttbl && (globalState.stack.top().f != -1)) { // Defining a font
+ FontInfo& f = globalState.fonttbl[globalState.stack.top().f];
+ f.options |= FontInfo::has_cpg;
+ f.cpg = *val;
+ }
+ else { // Overriding the codepage for the block - may be in filenames
+ globalState.stack.top().codepage = *val;
+ }
+ }
+ else if (eq(lexem.KeywordName(), "plain"))
+ globalState.stack.top().f = -1;
+ else if (eq(lexem.KeywordName(), "uc") && val)
+ globalState.stack.top().uc = *val;
+ }
+ break;
+ case Lexem::ltPCDATA_A:
+ if (globalState.pcdata_a_state == GlobalState::pcdsno) // Beginning of the pcdata
+ globalState.pcdata_a_codepage = globalState.GetCurrentCP(); // to use later to convert to utf16
+ globalState.pcdata_a_state = GlobalState::pcdsin;
+ globalState.pcdata_a << lexem.pcdata_a();
+ break;
+ }
+}
+
+void DecodeRTF(std::istream& rtf, CRTFDecoder& decoder)
+{
+ // Check if this is the rtf
+ Lexem lexem = GetLexem(rtf);
+ if (lexem.type() != Lexem::ltGroupBegin)
+ return;
+ decoder.BeginGroup();
+ lexem = GetLexem(rtf);
+ if ((lexem.type() != Lexem::ltKeyword) || !eq(lexem.KeywordName(), "rtf") ||
+ !lexem.KeywordVal() || (*lexem.KeywordVal() != 1))
+ return;
+ decoder.Keyword(lexem.KeywordName(), lexem.KeywordVal());
+
+ GlobalState state(rtf);
+ // Level is the count of elements in the stack
+
+ while (!state.stream.eof() && (state.stack.size()>0)) { // Don't go past the global group
+ lexem = GetLexem(state.stream);
+ PreprocessLexem(lexem, state.stream, state.stack.top().uc);
+ UpdateState(lexem, state);
+
+ if (state.pcdata_a_state == GlobalState::pcdsfinished) {
+ std::string s = state.pcdata_a.str();
+ int sz = ::MultiByteToWideChar(state.pcdata_a_codepage, 0, s.c_str(), s.size(), 0, 0);
+ if (sz) {
+ wchar_t* data = new wchar_t[sz];
+ ::MultiByteToWideChar(state.pcdata_a_codepage, 0, s.c_str(), s.size(), data, sz);
+ decoder.PCDATA(data, sz);
+ delete[] data;
+ }
+ state.pcdata_a.str(""); // reset
+ }
+
+ switch (lexem.type()) {
+ case Lexem::ltGroupBegin:
+ decoder.BeginGroup();
+ break;
+ case Lexem::ltGroupEnd:
+ decoder.EndGroup();
+ break;
+ case Lexem::ltKeyword:
+ decoder.Keyword(lexem.KeywordName(), lexem.KeywordVal());
+ break;
+ case Lexem::ltPCDATA_W:
+ {
+ wchar_t ch = lexem.pcdata_w();
+ decoder.PCDATA(&ch, 1);
+ }
+ break;
+ case Lexem::ltBDATA:
+ decoder.BDATA(lexem.bdata(), lexem.bdata_sz());
+ break;
+ case Lexem::ltError:
+ break; // Just silently skip the erroneous data - basic error recovery
+ }
+ } // while
+} // DecodeRTF
diff --git a/mailnews/import/outlook/src/rtfDecoder.h b/mailnews/import/outlook/src/rtfDecoder.h
new file mode 100644
index 000000000..85a17721d
--- /dev/null
+++ b/mailnews/import/outlook/src/rtfDecoder.h
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <istream>
+
+template <size_t len>
+inline bool eq(const char* str1, const char (&str2)[len])
+{
+ return ::strncmp(str1, str2, len) == 0;
+};
+
+class CRTFDecoder {
+public:
+ virtual void BeginGroup() = 0;
+ virtual void EndGroup() = 0;
+ virtual void Keyword(const char* name, const int* Val) = 0;
+ virtual void PCDATA(const wchar_t* data, size_t cch) = 0;
+ virtual void BDATA(const char* data, size_t sz) = 0;
+};
+
+void DecodeRTF(std::istream& rtf, CRTFDecoder& decoder);
diff --git a/mailnews/import/outlook/src/rtfMailDecoder.cpp b/mailnews/import/outlook/src/rtfMailDecoder.cpp
new file mode 100644
index 000000000..9a6b34725
--- /dev/null
+++ b/mailnews/import/outlook/src/rtfMailDecoder.cpp
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "rtfMailDecoder.h"
+
+void CRTFMailDecoder::BeginGroup()
+{
+ ClearState(sAsterisk);
+ SetState(sBeginGroup);
+ if (m_skipLevel)
+ ++m_skipLevel;
+}
+
+void CRTFMailDecoder::EndGroup()
+{
+ ClearState(sAsterisk|sBeginGroup);
+ if (m_skipLevel)
+ --m_skipLevel;
+}
+
+void CRTFMailDecoder::AddText(const wchar_t* txt, size_t cch)
+{
+ if (!IsHtmlRtf()) {
+ if (cch == static_cast<size_t>(-1))
+ m_text += txt;
+ else
+ m_text.append(txt, cch);
+ }
+}
+
+void CRTFMailDecoder::Keyword(const char* name, const int* Val)
+{
+ bool asterisk = IsAsterisk(); ClearState(sAsterisk); // for inside use only
+ bool beginGroup = IsBeginGroup(); ClearState(sBeginGroup); // for inside use only
+ if (!m_skipLevel) {
+ if (eq(name, "*") && beginGroup) SetState(sAsterisk);
+ else if (asterisk) {
+ if (eq(name, "htmltag") && (m_mode == mHTML)) { // \*\htmltag -> don't ignore; include the following text
+ }
+ else ++m_skipLevel;
+ }
+ else if (eq(name, "htmlrtf")) {
+ if (Val && (*Val==0))
+ ClearState(sHtmlRtf);
+ else
+ SetState(sHtmlRtf);
+ }
+ else if (eq(name, "par") || eq(name, "line")) {
+ AddText(L"\r\n");
+ }
+ else if (eq(name, "tab")) {
+ AddText(L"\t");
+ }
+ else if (eq(name, "rquote")) {
+ AddText(L"\x2019"); // Unicode right single quotation mark
+ }
+ else if (eq(name, "fromtext") && (m_mode==mNone)) { // avoid double "fromX"
+ m_mode = mText;
+ }
+ else if (eq(name, "fromhtml") && (m_mode==mNone)) { // avoid double "fromX"
+ m_mode = mHTML;
+ }
+ else if (eq(name, "fonttbl") || eq(name, "colortbl") || eq(name, "stylesheet") || eq(name, "pntext"))
+ ++m_skipLevel;
+ }
+}
+
+void CRTFMailDecoder::PCDATA(const wchar_t* data, size_t cch)
+{
+ ClearState(sAsterisk|sBeginGroup);
+ if (!m_skipLevel)
+ AddText(data, cch);
+}
+
+void CRTFMailDecoder::BDATA(const char* data, size_t sz)
+{
+ ClearState(sAsterisk|sBeginGroup);
+}
diff --git a/mailnews/import/outlook/src/rtfMailDecoder.h b/mailnews/import/outlook/src/rtfMailDecoder.h
new file mode 100644
index 000000000..2b621577f
--- /dev/null
+++ b/mailnews/import/outlook/src/rtfMailDecoder.h
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include <string>
+#include "rtfDecoder.h"
+
+class CRTFMailDecoder: public CRTFDecoder {
+public:
+ enum Mode {mNone, mText, mHTML};
+ CRTFMailDecoder() : m_mode(mNone), m_state(sNormal), m_skipLevel(0) {}
+ void BeginGroup() override;
+ void EndGroup() override;
+ void Keyword(const char* name, const int* Val) override;
+ void PCDATA(const wchar_t* data, size_t cch) override;
+ void BDATA(const char* data, size_t sz) override;
+ const wchar_t* text() { return m_text.c_str(); }
+ std::wstring::size_type textSize() { return m_text.size(); }
+ Mode mode() { return m_mode; }
+private:
+ enum State {sNormal = 0x0000,
+ sBeginGroup = 0x0001,
+ sAsterisk = 0x0002,
+ sHtmlRtf = 0x0004};
+
+ std::wstring m_text;
+ Mode m_mode;
+ unsigned int m_state; // bitmask of State
+// bool m_beginGroup; // true just after the {
+//bool m_asterisk; // true just after the {\*
+ int m_skipLevel; // if >0 then we ignore everything
+// bool m_htmlrtf;
+ inline void SetState(unsigned int s) { m_state |= s; }
+ inline void ClearState(unsigned int s) { m_state &= ~s; }
+ inline bool CheckState(State s) { return (m_state & s) != 0; }
+ inline bool IsAsterisk() { return CheckState(sAsterisk); }
+ inline bool IsBeginGroup() { return CheckState(sBeginGroup); }
+ inline bool IsHtmlRtf() { return CheckState(sHtmlRtf); }
+ void AddText(const wchar_t* txt, size_t cch=static_cast<size_t>(-1));
+};
diff --git a/mailnews/import/public/moz.build b/mailnews/import/public/moz.build
new file mode 100644
index 000000000..69efefb66
--- /dev/null
+++ b/mailnews/import/public/moz.build
@@ -0,0 +1,21 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIImportABDescriptor.idl',
+ 'nsIImportAddressBooks.idl',
+ 'nsIImportFieldMap.idl',
+ 'nsIImportFilters.idl',
+ 'nsIImportGeneric.idl',
+ 'nsIImportMail.idl',
+ 'nsIImportMailboxDescriptor.idl',
+ 'nsIImportMimeEncode.idl',
+ 'nsIImportModule.idl',
+ 'nsIImportService.idl',
+ 'nsIImportSettings.idl',
+]
+
+XPIDL_MODULE = 'import'
+
diff --git a/mailnews/import/public/nsIImportABDescriptor.idl b/mailnews/import/public/nsIImportABDescriptor.idl
new file mode 100644
index 000000000..fe0ca1b31
--- /dev/null
+++ b/mailnews/import/public/nsIImportABDescriptor.idl
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ Interface for importing mail - ui provided by the import module. If
+ you wish to provide your own UI then implement the nsIImportGeneric
+ interface.
+
+ Can I get an attribute set method to take a const value???
+
+ */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+/**
+ * Implementation Note:
+ *
+ * The default implementation can be obtained from
+ * nsIImportService::CreateNewABDescriptor();
+ *
+ * You should only be interested in using this class if you implement
+ * the nsIImportAddressBooks interface in which case, just using the service to
+ * create new ones should work fine for you. If not, implement your
+ * own.
+ */
+[scriptable, uuid(2d8983b2-cea6-4ae2-9145-eb772481fa18)]
+interface nsIImportABDescriptor : nsISupports
+{
+ /**
+ * use the following 2 attributes however you'd like to
+ * refer to a specific address book
+ */
+ attribute unsigned long identifier;
+ attribute unsigned long ref;
+
+ /**
+ * Doesn't have to be accurate, this is merely used to report progress.
+ * If you're importing a file, using file size and reporting progress
+ * as the number of bytes processed so far makes sense. For other formats
+ * returning the number of records may make more sense.
+ */
+ attribute unsigned long size;
+
+ /**
+ * The preferred name for this address book. Depending upon how the
+ * user selected import, the caller of the nsIImportAddressBooks interface
+ * may use this name to create the destination address book or it may
+ * ignore it. However, this must be provided in all cases as it is
+ * also displayed in the UI to the user.
+ */
+ attribute AString preferredName;
+
+ /**
+ * For address books that want a file descriptor to locate the address book.
+ * For formats that do not, use identifier & ref to refer to the address book
+ * OR implement your own nsIImportABDescriptor that contains additional data
+ * necessary to identify specific address books,
+ */
+ attribute nsIFile abFile;
+
+ /**
+ * Set by the UI to indicate whether or not this address book should be imported.
+ */
+ attribute boolean import;
+};
diff --git a/mailnews/import/public/nsIImportAddressBooks.idl b/mailnews/import/public/nsIImportAddressBooks.idl
new file mode 100644
index 000000000..a6f9a7800
--- /dev/null
+++ b/mailnews/import/public/nsIImportAddressBooks.idl
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Interface for importing address books using the standard UI. Address
+ book import occurs in several forms (yuck!).
+ The destination can be 1..n new address books corresponding to the source
+ format. For instance a text file would import into a new address book
+ with the same name as the text file.
+ The destination can be 1 pre-defined address book, all entries will be
+ added to the supplied address book - this allows the address book UI so provide
+ an import command specific for an individual address book.
+
+ The source can import 1 or multiple address books.
+ The address books can be auto-discoverable or user specified.
+ The address books can require field mapping or not.
+
+ All of this is rather complicated but it should work out OK.
+ 1) The first UI
+ panel will allow selection of the address book and will indicate to the user
+ if the address book will be imported into an existing address book or new address
+ books. (This could be 2 separate xul UI's?).
+ 2) The second panel will show field mapping if it is required - if it is required then
+ there will be one panel per address book for formats that support multiple
+ address books. If it is not required then there will be no second panel.
+ 3) Show the progress dialog for the import - this could be per address book if
+ mapping is required? what to do, what to doooooo.....
+ 4) All done, maybe a what was done panel??
+*/
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIArray;
+interface nsIImportABDescriptor;
+interface nsIAddrDatabase;
+interface nsIImportFieldMap;
+
+[scriptable, uuid(6bba48be-331c-41e3-bc9f-c2ea3754d977)]
+interface nsIImportAddressBooks : nsISupports
+{
+
+ /*
+ Does this interface supports 1 or 1..n address books. You only
+ get to choose 1 location so for formats where 1..n address books
+ are imported from a directory, then return true. For a 1 to 1 relationship
+ between location and address books return false.
+ */
+ boolean GetSupportsMultiple();
+
+ /*
+ If the address book is not found via a file location.then return true
+ along with a description string of how or where the address book is
+ located. If it is a file location then return false.
+ If true, return a string like: "Outlook Express standard address book,
+ also known as the Windows address book" or just "Outlook Express address book".
+ If false, GetDefaultLocation will be called.
+ */
+
+ boolean GetAutoFind( out wstring description);
+
+ /*
+ Returns true if the address book needs the user to specify a field map
+ for address books imported from this format.
+ */
+ boolean GetNeedsFieldMap( in nsIFile location);
+
+ /*
+ If found and userVerify BOTH return false, then it is assumed that this
+ means an error - address book cannot be found on this machine.
+ If userVerify is true, the user will have an opportunity to specify
+ a different location to import address book from.
+ */
+ void GetDefaultLocation( out nsIFile location,
+ out boolean found,
+ out boolean userVerify);
+ /*
+ Returns an nsIArray which contains an nsIImportABDescriptor for each
+ address book. The array is not sorted before display to the user.
+ location is null if GetAutoFind returned true.
+ */
+ nsIArray FindAddressBooks(in nsIFile location);
+
+ /*
+ Fill in defaults (if any) for a field map for importing address
+ books from this location.
+ */
+ void InitFieldMap(in nsIImportFieldMap fieldMap);
+
+ /**
+ * Import a specific address book into the destination file supplied.
+ * If an error occurs that is non-fatal, the destination will be deleted and
+ * other adress book will be imported. If a fatal error occurs, the
+ * destination will be deleted and the import operation will abort.
+ *
+ * @param aSource The source data for the import.
+ * @param aDestination The proxy database for the destination of the
+ * import.
+ * @param aFieldMap The field map containing the mapping of fields to be
+ * used in cvs and tab type imports.
+ * @param aSupportService An optional proxy support service (nullptr is
+ * acceptable if it is not required), may be required
+ * for certain import types (e.g. nsIAbLDIFService for
+ * LDIF import).
+ * @param aErrorLog The error log from the import.
+ * @param aSuccessLog The success log from the import.
+ * @param aFatalError True if there was a fatal error doing the import.
+ */
+ void ImportAddressBook(in nsIImportABDescriptor aSource,
+ in nsIAddrDatabase aDestination,
+ in nsIImportFieldMap aFieldMap,
+ in nsISupports aSupportService,
+ out wstring aErrorLog,
+ out wstring aSuccessLog,
+ out boolean aFatalError);
+
+ /*
+ Return the amount of the address book that has been imported so far. This number
+ is used to present progress information and must never be larger than the
+ size specified in nsIImportABDescriptor.GetSize(); May be called from
+ a different thread than ImportAddressBook()
+ */
+ unsigned long GetImportProgress();
+
+ /*
+ Set the location for reading sample data, this should be the same
+ as what is passed later to FindAddressBooks
+ */
+ void SetSampleLocation( in nsIFile location);
+
+ /*
+ * Return a string of sample data for a record, each field
+ * is separated by a newline (which means no newlines in the fields!)
+ * This is only supported by address books which use field maps and
+ * is used by the field map UI to allow the user to properly
+ * align fields to be imported.
+ *
+ * @param recordNumber index of the recrds, starting from 0.
+ * @param recordExists true if the record exists.
+ *
+ * @returns a string of sample data for the desired record
+ */
+ wstring GetSampleData(in long recordNumber, out boolean recordExists);
+
+};
+
+
+
+%{ C++
+
+%}
diff --git a/mailnews/import/public/nsIImportFieldMap.idl b/mailnews/import/public/nsIImportFieldMap.idl
new file mode 100644
index 000000000..693e4be87
--- /dev/null
+++ b/mailnews/import/public/nsIImportFieldMap.idl
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Field map interface for importing address books
+
+ A field map is an arbitrary sized list of mozilla address book fields.
+ The field map is used by import to map fields from the import format
+ to mozilla fields.
+ For export, the map contains the ordered list of mozilla fields to
+ export!
+*/
+
+#include "nsISupports.idl"
+
+interface nsIAddrDatabase;
+interface nsIMdbRow;
+interface nsIAbCard;
+
+[scriptable, uuid(deee9264-1fe3-47b1-b745-47b22de454e2)]
+interface nsIImportFieldMap : nsISupports
+{
+ /*
+ Flag to indicate whether or not to skip the first record,
+ for instance csv files often have field names as the first
+ record
+ */
+ attribute boolean skipFirstRecord;
+
+ readonly attribute long numMozFields;
+ readonly attribute long mapSize;
+
+ wstring GetFieldDescription( in long index);
+
+ /*
+ Set the size of the field map, all unpopulated entries
+ will default to -1
+ */
+ void SetFieldMapSize( in long size);
+
+ /*
+ Initialize the field map to a given size with default values
+ */
+ void DefaultFieldMap( in long size);
+
+ /*
+ Return the field number that this index maps to, -1 for no field
+ */
+ long GetFieldMap( in long index);
+
+ /*
+ Set the field that this index maps to, -1 for no field
+ */
+ void SetFieldMap( in long index, in long fieldNum);
+
+ /*
+ Return if this field is "active" in the map.
+ */
+ boolean GetFieldActive( in long index);
+
+ /*
+ Set the active state of this field
+ */
+ void SetFieldActive( in long index, in boolean active);
+
+ /*
+ Set the value of the given field in the database row
+ */
+ void SetFieldValue( in nsIAddrDatabase database, in nsIMdbRow row, in long fieldNum, in wstring value);
+};
diff --git a/mailnews/import/public/nsIImportFilters.idl b/mailnews/import/public/nsIImportFilters.idl
new file mode 100644
index 000000000..d8e480617
--- /dev/null
+++ b/mailnews/import/public/nsIImportFilters.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Interface for importing filters.
+*/
+
+#include "nsISupports.idl"
+
+interface nsIMsgAccount;
+interface nsIFile;
+
+[scriptable, uuid(f2680ccf-d110-4b5b-954d-e072d4a16129)]
+interface nsIImportFilters : nsISupports
+{
+ boolean AutoLocate( out wstring aDescription, out nsIFile aLocation);
+
+ void SetLocation( in nsIFile aLocation);
+
+ /*
+ Import filters and put any problems in the error out parameter.
+ */
+ boolean Import( out wstring aError);
+};
+
+
+
+%{ C++
+
+%}
diff --git a/mailnews/import/public/nsIImportGeneric.idl b/mailnews/import/public/nsIImportGeneric.idl
new file mode 100644
index 000000000..1496bac65
--- /dev/null
+++ b/mailnews/import/public/nsIImportGeneric.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Interface for importing anything. You are responsible for opening
+ up UI and doing all of the work to make it happen.
+
+*/
+
+#include "nsISupports.idl"
+
+interface nsISupportsString;
+
+[scriptable, uuid(469d7d5f-144c-4f07-9661-e49e40156348)]
+interface nsIImportGeneric : nsISupports
+{
+ /* Use these to prepare for the import */
+ /*
+ "mailInterface" - nsIImportMail interface
+ "mailBoxes" - nsIArray of nsIImportMailboxDescriptors
+ "mailLocation" - nsIFile, source location for mail
+
+ "addressInterface" - nsIImportAddressBooks interface
+ "addressBooks" - nsIArray of nsIImportABDescriptors
+ "addressLocation" - src location of address books (if needed!)
+ "addressDestination" - uri of destination address book or null if
+ new address books will be created.
+ */
+ nsISupports GetData( in string dataId);
+
+ void SetData( in string dataId, in nsISupports pData);
+
+ /*
+ "isInstalled" - if true then mail can be automatically located.
+ "canUserSetLocation" - if true then the user can specify the location
+ to look for mail. If both are false, then there is no way
+ to import mail from this format!
+ TBD: How to specify whether or not a file or a directory
+ should be specified?
+ "autoFind" - for address books, is the address book located without
+ using the file system - i.e. addressLocation is irrelevant.
+ "supportsMultiple" - 1 or 1..n address books are imported by this format?
+
+ */
+ long GetStatus( in string statusKind);
+
+ /*
+ When you are ready to import call this. If it returns TRUE then
+ you must call BeginImport and then repeatedly call GetProgress until
+ it returns 100 % done or until ContinueImport returns FALSE.
+ If this returns FALSE then BeginImport will begin and finish the import
+ before it returns.
+ */
+ boolean WantsProgress();
+
+ /* Use these for the actual import */
+ /* Begin import is expected to start a new thread UNLESS WantsProgress returned
+ FALSE. It is REQUIRED to call WantsProgress before calling BeginImport.
+ If WantsProgress was false then this will return the success or
+ failure of the import. Failure can be reported even if WantsProgress
+ returned TRUE.
+ */
+ boolean BeginImport(in nsISupportsString successLog,
+ in nsISupportsString errorLog);
+ /*
+ If WantsProgress returned TRUE then this will indicate if the import should
+ continue. If this returns FALSE then no other methods should be called
+ and the error log should be shown to the user.
+ */
+ boolean ContinueImport();
+ /*
+ Returns the percentage done. When this returns 100 then the import is done.
+ (only valid if WantsProgress returned true)
+ */
+ long GetProgress();
+ /*
+ Cancel an import in progress. Again, this is only valid if WantsProgress
+ returned true.
+ */
+ void CancelImport();
+};
+
+
+
+%{ C++
+
+%}
diff --git a/mailnews/import/public/nsIImportMail.idl b/mailnews/import/public/nsIImportMail.idl
new file mode 100644
index 000000000..dcc4dd7ba
--- /dev/null
+++ b/mailnews/import/public/nsIImportMail.idl
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ Interface for importing mail - ui provided by the import module. If
+ you wish to provide your own UI then implement the nsIImportGeneric
+ interface.
+
+*/
+
+/*
+ If you support this interface then the standard mailbox import UI
+ can be used to drive your import of mailboxes, which means you don't have
+ to worry about anything other than implementing this interface
+ (and nsIImportModule) to import mailboxes.
+*/
+
+/*
+ The general process is:
+ 1) Do you know where the mail is located
+ 2) Do you want the user to "verify" this location and have
+ the option of specifying a different mail directory?
+ 3) Given a directory (either specified in 1 or 2) build a list
+ of all of the mailboxes to be imported.
+ 4) Import each mail box to the destination provided!
+ 5) Update the portion of the mailbox imported so far. This should
+ always be less than the mailbox size until you are done. This
+ is used for progress bar updating and MAY BE CALLED FROM ANOTHER
+ THREAD!
+
+*/
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIArray;
+interface nsIImportMailboxDescriptor;
+interface nsIMsgFolder;
+
+[scriptable, uuid(a14a3308-0849-420b-86d3-13a2948b5504)]
+interface nsIImportMail : nsISupports
+{
+
+ /*
+ If found and userVerify BOTH return false, then it is assumed that this
+ means an error - mail cannot be found on this machine.
+ If userVerify is true, the user will have an opportunity to specify
+ a different location to import mail from.
+ */
+ void GetDefaultLocation( out nsIFile location,
+ out boolean found,
+ out boolean userVerify);
+ /*
+ Returns an nsIArray which contains an nsIImportMailboxID for each
+ mailbox. The array is not sorted before display to the user.
+ */
+ nsIArray FindMailboxes(in nsIFile location);
+
+ /*
+ Import a specific mailbox into the destination folder supplied. If an error
+ occurs that is non-fatal, the destination will be deleted and other mailboxes
+ will be imported. If a fatal error occurs, the destination will be deleted
+ and the import operation will abort.
+ */
+ void ImportMailbox(in nsIImportMailboxDescriptor source,
+ in nsIMsgFolder dstFolder,
+ out wstring errorLog,
+ out wstring successLog,
+ out boolean fatalError);
+
+ /*
+ Return the amount of the mailbox that has been imported so far. This number
+ is used to present progress information and must never be larger than the
+ size specified in nsIImportMailboxID.GetSize(); May be called from
+ a different thread than ImportMailbox()
+ */
+ unsigned long GetImportProgress();
+
+ /*
+ * When migrating the local folders from the import source into mozilla,
+ * we want to translate reserved folder names from the import source to
+ * equivalent values for Mozilla.
+ * Localization Impact is unknown here.
+ */
+ AString translateFolderName(in AString aFolderName);
+};
+
+
+
+%{ C++
+#define kDestTrashFolderName "Trash"
+#define kDestUnsentMessagesFolderName "Unsent Messages"
+#define kDestSentFolderName "Sent"
+#define kDestInboxFolderName "Inbox"
+%}
diff --git a/mailnews/import/public/nsIImportMailboxDescriptor.idl b/mailnews/import/public/nsIImportMailboxDescriptor.idl
new file mode 100644
index 000000000..24d24e694
--- /dev/null
+++ b/mailnews/import/public/nsIImportMailboxDescriptor.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ Interface for importing mail - ui provided by the import module. If
+ you wish to provide your own UI then implement the nsIImportGeneric
+ interface.
+
+ */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(69eba744-9c4f-4f79-a964-2134746b3656)]
+interface nsIImportMailboxDescriptor : nsISupports
+{
+ attribute unsigned long identifier;
+ attribute unsigned long depth;
+ attribute unsigned long size;
+
+ wstring GetDisplayName();
+ void SetDisplayName( [const] in wstring name);
+
+ attribute boolean import;
+ readonly attribute nsIFile file;
+};
+
+
+
+%{ C++
+
+/*
+ The default implementation can be obtained from
+ nsIImportService::CreateNewMailboxDescriptor();
+
+ You should only be interested in using this class if you implement
+ the nsIImportMail interface in which case, just using the service to
+ create new ones should work fine for you. If not, implement your
+ own.
+*/
+
+%}
diff --git a/mailnews/import/public/nsIImportMimeEncode.idl b/mailnews/import/public/nsIImportMimeEncode.idl
new file mode 100644
index 000000000..a03646831
--- /dev/null
+++ b/mailnews/import/public/nsIImportMimeEncode.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Encodes a file from disk into an output stream including properly
+ encoded mime headers.
+*/
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+[noscript, uuid(1d63892f-f660-465c-a550-95d4cb089de4)]
+interface nsIImportMimeEncode : nsISupports
+{
+
+ void EncodeFile( in nsIFile inFile, in nsIFile outFile, [const] in string fileName, [const] in string mimeType);
+
+ boolean DoWork( out boolean done);
+
+ long NumBytesProcessed();
+
+ boolean DoEncoding();
+ void Initialize( in nsIFile inFile, in nsIFile outFile, [const] in string fileName, [const] in string mimeType);
+
+};
+
+
+
+%{ C++
+
+#define NS_IMPORTMIMEENCODE_CID \
+{ /* e4a1a340-8de2-11d3-a206-00a0cc26da63 */ \
+ 0xe4a1a340, \
+ 0x8de2, \
+ 0x11d3, \
+ {0xa2, 0x06, 0x00, 0xa0, 0xcc, 0x26, 0xda, 0x63} \
+}
+
+%}
diff --git a/mailnews/import/public/nsIImportModule.idl b/mailnews/import/public/nsIImportModule.idl
new file mode 100644
index 000000000..116cad767
--- /dev/null
+++ b/mailnews/import/public/nsIImportModule.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ An import module.
+
+ */
+
+#include "nsISupports.idl"
+
+
+[scriptable, uuid(624f0280-173f-11d3-a206-00a0cc26da63)]
+interface nsIImportModule : nsISupports
+{
+ readonly attribute wstring name;
+ readonly attribute wstring description;
+ readonly attribute string supports;
+ readonly attribute boolean supportsUpgrade;
+
+ nsISupports GetImportInterface( in string importType);
+};
+
+
+%{ C++
+#define NS_IMPORT_MAIL_STR "mail"
+#define NS_IMPORT_ADDRESS_STR "addressbook"
+#define NS_IMPORT_SETTINGS_STR "settings"
+#define NS_IMPORT_FILTERS_STR "filters"
+%}
diff --git a/mailnews/import/public/nsIImportService.idl b/mailnews/import/public/nsIImportService.idl
new file mode 100644
index 000000000..160fdccdf
--- /dev/null
+++ b/mailnews/import/public/nsIImportService.idl
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ The import service.
+
+ */
+
+#include "nsISupports.idl"
+
+interface nsIImportModule;
+interface nsIImportMailboxDescriptor;
+interface nsIImportABDescriptor;
+interface nsIImportGeneric;
+interface nsIImportFieldMap;
+interface nsIMsgSendListener;
+interface nsIMsgIdentity;
+interface nsIMsgCompFields;
+interface nsIArray;
+
+[scriptable, uuid(d0ed4c50-5997-49c9-8a6a-045f0680ed29)]
+interface nsIImportService : nsISupports
+{
+ void DiscoverModules();
+
+ long GetModuleCount( in string filter);
+ void GetModuleInfo( in string filter, in long index, out wstring name, out wstring description);
+ wstring GetModuleName( in string filter, in long index);
+ wstring GetModuleDescription( in string filter, in long index);
+ nsIImportModule GetModule( in string filter, in long index);
+ nsIImportModule GetModuleWithCID( in nsCIDRef cid);
+
+ nsIImportFieldMap CreateNewFieldMap();
+ nsIImportMailboxDescriptor CreateNewMailboxDescriptor();
+ nsIImportABDescriptor CreateNewABDescriptor();
+ nsIImportGeneric CreateNewGenericMail();
+ nsIImportGeneric CreateNewGenericAddressBooks();
+ void CreateRFC822Message(in nsIMsgIdentity aIdentity,
+ in nsIMsgCompFields aMsgFields,
+ in string aBodytype,
+ in ACString aBody,
+ in boolean aCreateAsDraft,
+ in nsIArray aLoadedAttachments,
+ in nsIArray aEmbeddedObjects,
+ in nsIMsgSendListener aListener);
+
+};
+
+%{ C++
+#define NS_IMPORTSERVICE_CID \
+{ /* 5df96d60-1726-11d3-a206-00a0cc26da63 */ \
+ 0x5df96d60, 0x1726, 0x11d3, \
+ {0xa2, 0x06, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63}}
+
+#define NS_IMPORTSERVICE_CONTRACTID "@mozilla.org/import/import-service;1"
+%}
diff --git a/mailnews/import/public/nsIImportSettings.idl b/mailnews/import/public/nsIImportSettings.idl
new file mode 100644
index 000000000..9ff90d168
--- /dev/null
+++ b/mailnews/import/public/nsIImportSettings.idl
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ Interface for importing settings. Settings can be auto-located or
+ specified by a specific file. Depends upon the app that the settings
+ are coming from.
+
+*/
+
+#include "nsISupports.idl"
+
+interface nsIMsgAccount;
+interface nsIFile;
+
+[scriptable, uuid(1c0e3012-bc4d-4fb2-be6a-0335c7bab9ac)]
+interface nsIImportSettings : nsISupports
+{
+ boolean AutoLocate( out wstring description, out nsIFile location);
+
+ void SetLocation( in nsIFile location);
+
+ /*
+ Create all of the accounts, identities, and servers. Return an
+ account where any local mail from this app should be imported.
+ The returned account can be null which indicates that no suitable
+ account for local mail was created and a new account specifically for
+ the imported mail should be created.
+ */
+ boolean Import( out nsIMsgAccount localMailAccount);
+};
+
+
+
+%{ C++
+
+%}
diff --git a/mailnews/import/src/ImportCharSet.cpp b/mailnews/import/src/ImportCharSet.cpp
new file mode 100644
index 000000000..a8bc48d19
--- /dev/null
+++ b/mailnews/import/src/ImportCharSet.cpp
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "ImportCharSet.h"
+
+char ImportCharSet::m_upperCaseMap[256];
+char ImportCharSet::m_Ascii[256] = {0}; // the initialiser makes it strong
+
+class UInitMaps {
+public:
+ UInitMaps();
+};
+
+UInitMaps gInitMaps;
+
+UInitMaps::UInitMaps()
+{
+ int i;
+
+ for (i = 0; i < 256; i++)
+ ImportCharSet::m_upperCaseMap[i] = i;
+ for (i = 'a'; i <= 'z'; i++)
+ ImportCharSet::m_upperCaseMap[i] = i - 'a' + 'A';
+
+ for (i = 0; i < 256; i++)
+ ImportCharSet::m_Ascii[i] = 0;
+
+ for (i = ImportCharSet::cUpperAChar; i <= ImportCharSet::cUpperZChar; i++)
+ ImportCharSet::m_Ascii[i] |= (ImportCharSet::cAlphaNumChar | ImportCharSet::cAlphaChar);
+ for (i = ImportCharSet::cLowerAChar; i <= ImportCharSet::cLowerZChar; i++)
+ ImportCharSet::m_Ascii[i] |= (ImportCharSet::cAlphaNumChar | ImportCharSet::cAlphaChar);
+ for (i = ImportCharSet::cZeroChar; i <= ImportCharSet::cNineChar; i++)
+ ImportCharSet::m_Ascii[i] |= (ImportCharSet::cAlphaNumChar | ImportCharSet::cDigitChar);
+
+ ImportCharSet::m_Ascii[ImportCharSet::cTabChar] |= ImportCharSet::cWhiteSpaceChar;
+ ImportCharSet::m_Ascii[ImportCharSet::cCRChar] |= ImportCharSet::cWhiteSpaceChar;
+ ImportCharSet::m_Ascii[ImportCharSet::cLinefeedChar] |= ImportCharSet::cWhiteSpaceChar;
+ ImportCharSet::m_Ascii[ImportCharSet::cSpaceChar] |= ImportCharSet::cWhiteSpaceChar;
+
+ ImportCharSet::m_Ascii[uint8_t('(')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t(')')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('<')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('>')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('@')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t(',')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t(';')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t(':')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('\\')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('"')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('.')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('[')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t(']')] |= ImportCharSet::c822SpecialChar;
+
+
+}
diff --git a/mailnews/import/src/ImportCharSet.h b/mailnews/import/src/ImportCharSet.h
new file mode 100644
index 000000000..c1f649ef2
--- /dev/null
+++ b/mailnews/import/src/ImportCharSet.h
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ImportCharSet_h___
+#define ImportCharSet_h___
+
+#include "nscore.h"
+
+
+// Some useful ASCII values
+// 'A' = 65, 0x41
+// 'Z' = 90, 0x5a
+// '_' = 95, 0x5f
+// 'a' = 97, 0x61
+// 'z' = 122, 0x7a
+// '0' = 48, 0x30
+// '1' = 49, 0x31
+// '9' = 57, 0x39
+// ' ' = 32, 0x20
+// whitespace, 10, 13, 32, 9 (linefeed, cr, space, tab) - 0x0a, 0x0d, 0x20, 0x09
+// ':' = 58, 0x3a
+
+
+// a typedef enum would be nicer but some compilers still have trouble with treating
+// enum's as plain numbers when needed
+
+class ImportCharSet {
+public:
+ enum {
+ cTabChar = 9,
+ cLinefeedChar = 10,
+ cCRChar = 13,
+ cSpaceChar = 32,
+ cUpperAChar = 65,
+ cUpperZChar = 90,
+ cUnderscoreChar = 95,
+ cLowerAChar = 97,
+ cLowerZChar = 122,
+ cZeroChar = 48,
+ cNineChar = 57,
+
+ cAlphaNumChar = 1,
+ cAlphaChar = 2,
+ cWhiteSpaceChar = 4,
+ cDigitChar = 8,
+ c822SpecialChar = 16
+ };
+
+ static char m_upperCaseMap[256];
+ static char m_Ascii[256];
+
+ inline static bool IsUSAscii(uint8_t ch) { return (((ch & (uint8_t)0x80) == 0));}
+ inline static bool Is822CtlChar(uint8_t ch) { return (ch < 32);}
+ inline static bool Is822SpecialChar(uint8_t ch) { return ((m_Ascii[ch] & c822SpecialChar) == c822SpecialChar);}
+ inline static bool IsWhiteSpace(uint8_t ch) { return ((m_Ascii[ch] & cWhiteSpaceChar) == cWhiteSpaceChar); }
+ inline static bool IsAlphaNum(uint8_t ch) { return ((m_Ascii[ch] & cAlphaNumChar) == cAlphaNumChar); }
+ inline static bool IsDigit(uint8_t ch) { return ((m_Ascii[ch] & cDigitChar) == cDigitChar); }
+
+ inline static uint8_t ToLower(uint8_t ch) { if ((m_Ascii[ch] & cAlphaChar) == cAlphaChar) { return cLowerAChar + (m_upperCaseMap[ch] - cUpperAChar); } else return ch; }
+
+ inline static long AsciiToLong(const uint8_t * pChar, uint32_t len) {
+ long num = 0;
+ while (len) {
+ if ((m_Ascii[*pChar] & cDigitChar) == 0)
+ return num;
+ num *= 10;
+ num += (*pChar - cZeroChar);
+ len--;
+ pChar++;
+ }
+ return num;
+ }
+
+ inline static void ByteToHex(uint8_t byte, uint8_t * pHex) {
+ uint8_t val = byte;
+ val /= 16;
+ if (val < 10)
+ *pHex = '0' + val;
+ else
+ *pHex = 'A' + (val - 10);
+ pHex++;
+ val = byte;
+ val &= 0x0F;
+ if (val < 10)
+ *pHex = '0' + val;
+ else
+ *pHex = 'A' + (val - 10);
+ }
+
+ inline static void LongToHexBytes(uint32_t type, uint8_t * pStr) {
+ ByteToHex((uint8_t) (type >> 24), pStr);
+ pStr += 2;
+ ByteToHex((uint8_t) ((type >> 16) & 0x0FF), pStr);
+ pStr += 2;
+ ByteToHex((uint8_t) ((type >> 8) & 0x0FF), pStr);
+ pStr += 2;
+ ByteToHex((uint8_t) (type & 0x0FF), pStr);
+ }
+
+ inline static void SkipWhiteSpace(const uint8_t * & pChar, uint32_t & pos, uint32_t max) {
+ while ((pos < max) && (IsWhiteSpace(*pChar))) {
+ pos++; pChar++;
+ }
+ }
+
+ inline static void SkipSpaceTab(const uint8_t * & pChar, uint32_t& pos, uint32_t max) {
+ while ((pos < max) && ((*pChar == (uint8_t)cSpaceChar) || (*pChar == (uint8_t)cTabChar))) {
+ pos++; pChar++;
+ }
+ }
+
+ inline static void SkipTilSpaceTab(const uint8_t * & pChar, uint32_t& pos, uint32_t max) {
+ while ((pos < max) && (*pChar != (uint8_t)cSpaceChar) && (*pChar != (uint8_t)cTabChar)) {
+ pos++;
+ pChar++;
+ }
+ }
+
+ inline static bool StrNICmp(const uint8_t * pChar, const uint8_t * pSrc, uint32_t len) {
+ while (len && (m_upperCaseMap[*pChar] == m_upperCaseMap[*pSrc])) {
+ pChar++; pSrc++; len--;
+ }
+ return len == 0;
+ }
+
+ inline static bool StrNCmp(const uint8_t * pChar, const uint8_t *pSrc, uint32_t len) {
+ while (len && (*pChar == *pSrc)) {
+ pChar++; pSrc++; len--;
+ }
+ return len == 0;
+ }
+
+ inline static int FindChar(const uint8_t * pChar, uint8_t ch, uint32_t max) {
+ uint32_t pos = 0;
+ while ((pos < max) && (*pChar != ch)) {
+ pos++; pChar++;
+ }
+ if (pos < max)
+ return (int) pos;
+ else
+ return -1;
+ }
+
+ inline static bool NextChar(const uint8_t * & pChar, uint8_t ch, uint32_t& pos, uint32_t max) {
+ if ((pos < max) && (*pChar == ch)) {
+ pos++;
+ pChar++;
+ return true;
+ }
+ return false;
+ }
+
+ inline static int32_t strcmp(const char * pS1, const char * pS2) {
+ while (*pS1 && *pS2 && (*pS1 == *pS2)) {
+ pS1++;
+ pS2++;
+ }
+ return *pS1 - *pS2;
+ }
+
+ inline static int32_t stricmp(const char * pS1, const char * pS2) {
+ while (*pS1 && *pS2 && (m_upperCaseMap[uint8_t(*pS1)] == m_upperCaseMap[uint8_t(*pS2)])) {
+ pS1++;
+ pS2++;
+ }
+ return m_upperCaseMap[uint8_t(*pS1)] - m_upperCaseMap[uint8_t(*pS2)];
+ }
+
+};
+
+
+#endif /* ImportCharSet_h__ */
+
diff --git a/mailnews/import/src/ImportDebug.h b/mailnews/import/src/ImportDebug.h
new file mode 100644
index 000000000..b905aa4a9
--- /dev/null
+++ b/mailnews/import/src/ImportDebug.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ImportDebug_h___
+#define ImportDebug_h___
+
+#ifdef NS_DEBUG
+#define IMPORT_DEBUG 1
+#endif
+
+// Use MOZ_LOG for logging.
+#include "mozilla/Logging.h"
+extern PRLogModuleInfo *IMPORTLOGMODULE; // Logging module
+
+#define IMPORT_LOG0(x) MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x))
+#define IMPORT_LOG1(x, y) MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y))
+#define IMPORT_LOG2(x, y, z) MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y, z))
+#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d))
+
+#endif
diff --git a/mailnews/import/src/ImportOutFile.cpp b/mailnews/import/src/ImportOutFile.cpp
new file mode 100644
index 000000000..beeb8903a
--- /dev/null
+++ b/mailnews/import/src/ImportOutFile.cpp
@@ -0,0 +1,299 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "prio.h"
+#include "nsNetUtil.h"
+#include "nsISeekableStream.h"
+#include "nsMsgUtils.h"
+#include "ImportOutFile.h"
+#include "ImportCharSet.h"
+
+#include "ImportDebug.h"
+
+/*
+#ifdef _MAC
+#define kMacNoCreator '????'
+#define kMacTextFile 'TEXT'
+#else
+#define kMacNoCreator 0
+#define kMacTextFile 0
+#endif
+*/
+
+ImportOutFile::ImportOutFile()
+{
+ m_ownsFileAndBuffer = false;
+ m_pos = 0;
+ m_pBuf = nullptr;
+ m_bufSz = 0;
+ m_pTrans = nullptr;
+ m_pTransOut = nullptr;
+ m_pTransBuf = nullptr;
+}
+
+ImportOutFile::ImportOutFile(nsIFile *pFile, uint8_t * pBuf, uint32_t sz)
+{
+ m_pTransBuf = nullptr;
+ m_pTransOut = nullptr;
+ m_pTrans = nullptr;
+ m_ownsFileAndBuffer = false;
+ InitOutFile(pFile, pBuf, sz);
+}
+
+ImportOutFile::~ImportOutFile()
+{
+ if (m_ownsFileAndBuffer)
+ {
+ Flush();
+ delete [] m_pBuf;
+ }
+
+ delete m_pTrans;
+ delete m_pTransOut;
+ delete [] m_pTransBuf;
+}
+
+bool ImportOutFile::Set8bitTranslator(nsImportTranslator *pTrans)
+{
+ if (!Flush())
+ return false;
+
+ m_engaged = false;
+ m_pTrans = pTrans;
+ m_supports8to7 = pTrans->Supports8bitEncoding();
+
+
+ return true;
+}
+
+bool ImportOutFile::End8bitTranslation(bool *pEngaged, nsCString& useCharset, nsCString& encoding)
+{
+ if (!m_pTrans)
+ return false;
+
+
+ bool bResult = Flush();
+ if (m_supports8to7 && m_pTransOut) {
+ if (bResult)
+ bResult = m_pTrans->FinishConvertToFile(m_pTransOut);
+ if (bResult)
+ bResult = Flush();
+ }
+
+ if (m_supports8to7) {
+ m_pTrans->GetCharset(useCharset);
+ m_pTrans->GetEncoding(encoding);
+ }
+ else
+ useCharset.Truncate();
+ *pEngaged = m_engaged;
+ delete m_pTrans;
+ m_pTrans = nullptr;
+ delete m_pTransOut;
+ m_pTransOut = nullptr;
+ delete [] m_pTransBuf;
+ m_pTransBuf = nullptr;
+
+ return bResult;
+}
+
+bool ImportOutFile::InitOutFile(nsIFile *pFile, uint32_t bufSz)
+{
+ if (!bufSz)
+ bufSz = 32 * 1024;
+ if (!m_pBuf)
+ m_pBuf = new uint8_t[ bufSz];
+
+ if (!m_outputStream)
+ {
+ nsresult rv;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream),
+ pFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0644);
+
+ if (NS_FAILED(rv))
+ {
+ IMPORT_LOG0("Couldn't create outfile\n");
+ delete [] m_pBuf;
+ m_pBuf = nullptr;
+ return false;
+ }
+ }
+ m_pFile = pFile;
+ m_ownsFileAndBuffer = true;
+ m_pos = 0;
+ m_bufSz = bufSz;
+ return true;
+}
+
+void ImportOutFile::InitOutFile(nsIFile *pFile, uint8_t * pBuf, uint32_t sz)
+{
+ m_ownsFileAndBuffer = false;
+ m_pFile = pFile;
+ m_pBuf = pBuf;
+ m_bufSz = sz;
+ m_pos = 0;
+}
+
+
+
+bool ImportOutFile::Flush(void)
+{
+ if (!m_pos)
+ return true;
+
+ uint32_t transLen;
+ bool duddleyDoWrite = false;
+
+ // handle translations if appropriate
+ if (m_pTrans) {
+ if (m_engaged && m_supports8to7) {
+ // Markers can get confused by this crap!!!
+ // TLR: FIXME: Need to update the markers based on
+ // the difference between the translated len and untranslated len
+
+ if (!m_pTrans->ConvertToFile( m_pBuf, m_pos, m_pTransOut, &transLen))
+ return false;
+ if (!m_pTransOut->Flush())
+ return false;
+ // now update our buffer...
+ if (transLen < m_pos) {
+ memcpy(m_pBuf, m_pBuf + transLen, m_pos - transLen);
+ }
+ m_pos -= transLen;
+ }
+ else if (m_engaged) {
+ // does not actually support translation!
+ duddleyDoWrite = true;
+ }
+ else {
+ // should we engage?
+ uint8_t * pChar = m_pBuf;
+ uint32_t len = m_pos;
+ while (len) {
+ if (!ImportCharSet::IsUSAscii(*pChar))
+ break;
+ pChar++;
+ len--;
+ }
+ if (len) {
+ m_engaged = true;
+ if (m_supports8to7) {
+ // allocate our translation output buffer and file...
+ m_pTransBuf = new uint8_t[m_bufSz];
+ m_pTransOut = new ImportOutFile(m_pFile, m_pTransBuf, m_bufSz);
+ return Flush();
+ }
+ else
+ duddleyDoWrite = true;
+ }
+ else {
+ duddleyDoWrite = true;
+ }
+ }
+ }
+ else
+ duddleyDoWrite = true;
+
+ if (duddleyDoWrite) {
+ uint32_t written = 0;
+ nsresult rv = m_outputStream->Write((const char *)m_pBuf, (int32_t)m_pos, &written);
+ if (NS_FAILED(rv) || ((uint32_t)written != m_pos))
+ return false;
+ m_pos = 0;
+ }
+
+ return true;
+}
+
+bool ImportOutFile::WriteU8NullTerm(const uint8_t * pSrc, bool includeNull)
+{
+ while (*pSrc) {
+ if (m_pos >= m_bufSz) {
+ if (!Flush())
+ return false;
+ }
+ *(m_pBuf + m_pos) = *pSrc;
+ m_pos++;
+ pSrc++;
+ }
+ if (includeNull) {
+ if (m_pos >= m_bufSz) {
+ if (!Flush())
+ return false;
+ }
+ *(m_pBuf + m_pos) = 0;
+ m_pos++;
+ }
+
+ return true;
+}
+
+bool ImportOutFile::SetMarker(int markerID)
+{
+ if (!Flush()) {
+ return false;
+ }
+
+ if (markerID < kMaxMarkers) {
+ int64_t pos = 0;
+ if (m_outputStream)
+ {
+ // do we need to flush for the seek to give us the right pos?
+ m_outputStream->Flush();
+ nsresult rv;
+ nsCOMPtr <nsISeekableStream> seekStream = do_QueryInterface(m_outputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = seekStream->Tell(&pos);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error, Tell failed on output stream\n");
+ return false;
+ }
+ }
+ m_markers[markerID] = (uint32_t)pos + m_pos;
+ }
+
+ return true;
+}
+
+void ImportOutFile::ClearMarker(int markerID)
+{
+ if (markerID < kMaxMarkers)
+ m_markers[markerID] = 0;
+}
+
+bool ImportOutFile::WriteStrAtMarker(int markerID, const char *pStr)
+{
+ if (markerID >= kMaxMarkers)
+ return false;
+
+ if (!Flush())
+ return false;
+ int64_t pos;
+ m_outputStream->Flush();
+ nsresult rv;
+ nsCOMPtr <nsISeekableStream> seekStream = do_QueryInterface(m_outputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = seekStream->Tell(&pos);
+ if (NS_FAILED(rv))
+ return false;
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, (int32_t) m_markers[markerID]);
+ if (NS_FAILED(rv))
+ return false;
+ uint32_t written;
+ rv = m_outputStream->Write(pStr, strlen(pStr), &written);
+ if (NS_FAILED(rv))
+ return false;
+
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, pos);
+ if (NS_FAILED(rv))
+ return false;
+
+ return true;
+}
+
diff --git a/mailnews/import/src/ImportOutFile.h b/mailnews/import/src/ImportOutFile.h
new file mode 100644
index 000000000..461728c22
--- /dev/null
+++ b/mailnews/import/src/ImportOutFile.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef ImportOutFile_h___
+#define ImportOutFile_h___
+
+#include "nsImportTranslator.h"
+#include "nsIOutputStream.h"
+#include "nsIFile.h"
+
+#define kMaxMarkers 10
+
+class ImportOutFile;
+
+class ImportOutFile {
+public:
+ ImportOutFile();
+ ImportOutFile(nsIFile *pFile, uint8_t * pBuf, uint32_t sz);
+ ~ImportOutFile();
+
+ bool InitOutFile(nsIFile *pFile, uint32_t bufSz = 4096);
+ void InitOutFile(nsIFile *pFile, uint8_t * pBuf, uint32_t sz);
+ inline bool WriteData(const uint8_t * pSrc, uint32_t len);
+ inline bool WriteByte(uint8_t byte);
+ bool WriteStr(const char *pStr) {return WriteU8NullTerm((const uint8_t *) pStr, false); }
+ bool WriteU8NullTerm(const uint8_t * pSrc, bool includeNull);
+ bool WriteEol(void) { return WriteStr("\x0D\x0A"); }
+ bool Done(void) {return Flush();}
+
+ // Marker support
+ bool SetMarker(int markerID);
+ void ClearMarker(int markerID);
+ bool WriteStrAtMarker(int markerID, const char *pStr);
+
+ // 8-bit to 7-bit translation
+ bool Set8bitTranslator(nsImportTranslator *pTrans);
+ bool End8bitTranslation(bool *pEngaged, nsCString& useCharset, nsCString& encoding);
+
+protected:
+ bool Flush(void);
+
+protected:
+ nsCOMPtr <nsIFile> m_pFile;
+ nsCOMPtr <nsIOutputStream> m_outputStream;
+ uint8_t * m_pBuf;
+ uint32_t m_bufSz;
+ uint32_t m_pos;
+ bool m_ownsFileAndBuffer;
+
+ // markers
+ uint32_t m_markers[kMaxMarkers];
+
+ // 8 bit to 7 bit translations
+ nsImportTranslator * m_pTrans;
+ bool m_engaged;
+ bool m_supports8to7;
+ ImportOutFile * m_pTransOut;
+ uint8_t * m_pTransBuf;
+};
+
+inline bool ImportOutFile::WriteData(const uint8_t * pSrc, uint32_t len) {
+ while ((len + m_pos) > m_bufSz) {
+ if ((m_bufSz - m_pos)) {
+ memcpy(m_pBuf + m_pos, pSrc, m_bufSz - m_pos);
+ len -= (m_bufSz - m_pos);
+ pSrc += (m_bufSz - m_pos);
+ m_pos = m_bufSz;
+ }
+ if (!Flush())
+ return false;
+ }
+
+ if (len) {
+ memcpy(m_pBuf + m_pos, pSrc, len);
+ m_pos += len;
+ }
+
+ return true;
+}
+
+inline bool ImportOutFile::WriteByte(uint8_t byte) {
+ if (m_pos == m_bufSz) {
+ if (!Flush())
+ return false;
+ }
+ *(m_pBuf + m_pos) = byte;
+ m_pos++;
+ return true;
+}
+
+#endif /* ImportOutFile_h__ */
+
+
diff --git a/mailnews/import/src/ImportTranslate.cpp b/mailnews/import/src/ImportTranslate.cpp
new file mode 100644
index 000000000..f7f32f552
--- /dev/null
+++ b/mailnews/import/src/ImportTranslate.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ImportTranslate.h"
+
+int ImportTranslate::m_useTranslator = -1;
+
+
+bool ImportTranslate::ConvertString(const nsCString& inStr, nsCString& outStr, bool mimeHeader)
+{
+ if (inStr.IsEmpty()) {
+ outStr = inStr;
+ return true;
+ }
+
+ nsImportTranslator *pTrans = GetTranslator();
+ // int maxLen = (int) pTrans->GetMaxBufferSize(inStr.Length());
+ // int hLen = 0;
+ nsCString set;
+ nsCString lang;
+
+ if (mimeHeader) {
+ // add the charset and language
+ pTrans->GetCharset(set);
+ pTrans->GetLanguage(lang);
+ }
+
+ // Unfortunatly, we didn't implement ConvertBuffer for all translators,
+ // just ConvertToFile. This means that this data will not always
+ // be converted to the charset of pTrans. In that case...
+ // We don't always have the data in the same charset as the current
+ // translator...
+ // It is safer to leave the charset and language field blank
+ set.Truncate();
+ lang.Truncate();
+
+ uint8_t * pBuf;
+ /*
+ pBuf = (P_U8) outStr.GetBuffer(maxLen);
+ if (!pBuf) {
+ delete pTrans;
+ return FALSE;
+ }
+ pTrans->ConvertBuffer((PC_U8)(PC_S8)inStr, inStr.GetLength(), pBuf);
+ outStr.ReleaseBuffer();
+ */
+ outStr = inStr;
+ delete pTrans;
+
+
+ // Now I need to run the string through the mime-header special char
+ // encoder.
+
+ pTrans = new CMHTranslator;
+ pBuf = new uint8_t[pTrans->GetMaxBufferSize(outStr.Length())];
+ pTrans->ConvertBuffer((const uint8_t *)(outStr.get()), outStr.Length(), pBuf);
+ delete pTrans;
+ outStr.Truncate();
+ if (mimeHeader) {
+ outStr = set;
+ outStr += "'";
+ outStr += lang;
+ outStr += "'";
+ }
+ outStr += (const char *)pBuf;
+ delete [] pBuf;
+
+ return true;
+}
+
+
+nsImportTranslator *ImportTranslate::GetTranslator(void)
+{
+ if (m_useTranslator == -1) {
+ // get the translator to use...
+ // CString trans;
+ // trans.LoadString(IDS_LANGUAGE_TRANSLATION);
+ m_useTranslator = 0;
+ // if (!trans.CompareNoCase("iso-2022-jp"))
+ // gWizData.m_useTranslator = 1;
+ }
+
+ switch(m_useTranslator) {
+ case 0:
+ return new nsImportTranslator;
+ //case 1:
+ // return new CSJis2JisTranslator;
+ default:
+ return new nsImportTranslator;
+ }
+}
+
+nsImportTranslator *ImportTranslate::GetMatchingTranslator(const char *pCharSet)
+{
+/*
+ CString jp = "iso-2022-jp";
+ if (!jp.CompareNoCase(pCharSet))
+ return new CSJis2JisTranslator;
+*/
+
+ return nullptr;
+}
+
diff --git a/mailnews/import/src/ImportTranslate.h b/mailnews/import/src/ImportTranslate.h
new file mode 100644
index 000000000..3e6c596d4
--- /dev/null
+++ b/mailnews/import/src/ImportTranslate.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ImportTranslate_h___
+#define ImportTranslate_h___
+
+#include "nsStringGlue.h"
+#include "nsImportTranslator.h"
+
+class ImportTranslate {
+public:
+ static bool ConvertString(const nsCString& inStr, nsCString& outStr, bool mimeHeader);
+ static nsImportTranslator *GetTranslator(void);
+ static nsImportTranslator *GetMatchingTranslator(const char *pCharSet);
+
+protected:
+ static int m_useTranslator;
+};
+
+
+#endif /* ImportTranslate_h__ */
diff --git a/mailnews/import/src/moz.build b/mailnews/import/src/moz.build
new file mode 100644
index 000000000..2df4926d5
--- /dev/null
+++ b/mailnews/import/src/moz.build
@@ -0,0 +1,25 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'ImportCharSet.cpp',
+ 'ImportOutFile.cpp',
+ 'ImportTranslate.cpp',
+ 'nsImportABDescriptor.cpp',
+ 'nsImportAddressBooks.cpp',
+ 'nsImportEmbeddedImageData.cpp',
+ 'nsImportEncodeScan.cpp',
+ 'nsImportFieldMap.cpp',
+ 'nsImportMail.cpp',
+ 'nsImportMailboxDescriptor.cpp',
+ 'nsImportMimeEncode.cpp',
+ 'nsImportScanFile.cpp',
+ 'nsImportService.cpp',
+ 'nsImportStringBundle.cpp',
+ 'nsImportTranslator.cpp',
+]
+
+FINAL_LIBRARY = 'import'
+
diff --git a/mailnews/import/src/nsImportABDescriptor.cpp b/mailnews/import/src/nsImportABDescriptor.cpp
new file mode 100644
index 000000000..05605d09e
--- /dev/null
+++ b/mailnews/import/src/nsImportABDescriptor.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nscore.h"
+#include "nsImportABDescriptor.h"
+
+////////////////////////////////////////////////////////////////////////
+
+NS_METHOD nsImportABDescriptor::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsImportABDescriptor *it = new nsImportABDescriptor();
+ if (it == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(it);
+ nsresult rv = it->QueryInterface(aIID, aResult);
+ NS_RELEASE(it);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsImportABDescriptor, nsIImportABDescriptor)
+
+nsImportABDescriptor::nsImportABDescriptor()
+ : mId(0), mRef(0), mSize(0), mImport(true)
+{
+}
diff --git a/mailnews/import/src/nsImportABDescriptor.h b/mailnews/import/src/nsImportABDescriptor.h
new file mode 100644
index 000000000..a8fa6fbad
--- /dev/null
+++ b/mailnews/import/src/nsImportABDescriptor.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImportABDescriptor_h___
+#define nsImportABDescriptor_h___
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+
+////////////////////////////////////////////////////////////////////////
+
+class nsImportABDescriptor : public nsIImportABDescriptor
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD GetIdentifier(uint32_t *pIdentifier) override {
+ *pIdentifier = mId;
+ return NS_OK;
+ }
+ NS_IMETHOD SetIdentifier(uint32_t ident) override {
+ mId = ident;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetRef(uint32_t *pRef) override {
+ *pRef = mRef;
+ return NS_OK;
+ }
+ NS_IMETHOD SetRef(uint32_t ref) override {
+ mRef = ref;
+ return NS_OK;
+ }
+
+ /* attribute unsigned long size; */
+ NS_IMETHOD GetSize(uint32_t *pSize) override {
+ *pSize = mSize;
+ return NS_OK;
+ }
+ NS_IMETHOD SetSize(uint32_t theSize) override {
+ mSize = theSize;
+ return NS_OK;
+ }
+
+ /* attribute AString displayName; */
+ NS_IMETHOD GetPreferredName(nsAString &aName) override {
+ aName = mDisplayName;
+ return NS_OK;
+ }
+ NS_IMETHOD SetPreferredName(const nsAString &aName) override {
+ mDisplayName = aName;
+ return NS_OK;
+ }
+
+ /* readonly attribute nsIFile fileSpec; */
+ NS_IMETHOD GetAbFile(nsIFile **aFile) override {
+ if (!mFile)
+ return NS_ERROR_NULL_POINTER;
+
+ return mFile->Clone(aFile);
+ }
+
+ NS_IMETHOD SetAbFile(nsIFile *aFile) override {
+ if (!aFile) {
+ mFile = nullptr;
+ return NS_OK;
+ }
+
+ return aFile->Clone(getter_AddRefs(mFile));
+ }
+
+ /* attribute boolean import; */
+ NS_IMETHOD GetImport(bool *pImport) override {
+ *pImport = mImport;
+ return NS_OK;
+ }
+ NS_IMETHOD SetImport(bool doImport) override {
+ mImport = doImport;
+ return NS_OK;
+ }
+
+ nsImportABDescriptor();
+
+ static NS_METHOD Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+private:
+ virtual ~nsImportABDescriptor() {}
+ uint32_t mId; // used by creator of the structure
+ uint32_t mRef; // depth in the hierarchy
+ nsString mDisplayName; // name of this mailbox
+ nsCOMPtr<nsIFile> mFile; // source file (if applicable)
+ uint32_t mSize; // size
+ bool mImport; // import it or not?
+};
+
+
+#endif
diff --git a/mailnews/import/src/nsImportAddressBooks.cpp b/mailnews/import/src/nsImportAddressBooks.cpp
new file mode 100644
index 000000000..8791efb42
--- /dev/null
+++ b/mailnews/import/src/nsImportAddressBooks.cpp
@@ -0,0 +1,894 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prprf.h"
+#include "plstr.h"
+#include "nsCOMPtr.h"
+#include "nsMsgUtils.h"
+#include "nsIImportService.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportGeneric.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIImportFieldMap.h"
+#include "nsStringGlue.h"
+#include "nsIFile.h"
+#include "nsIAddrDatabase.h"
+#include "nsIAbManager.h"
+#include "nsIAbLDIFService.h"
+#include "nsAbBaseCID.h"
+#include "nsIStringBundle.h"
+#include "nsImportStringBundle.h"
+#include "nsTextFormatter.h"
+#include "nsServiceManagerUtils.h"
+#include "msgCore.h"
+#include "ImportDebug.h"
+#include "nsIAbMDBDirectory.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIArray.h"
+#include "nsCOMArray.h"
+#include "nsArrayUtils.h"
+
+static void ImportAddressThread(void *stuff);
+
+class AddressThreadData;
+
+class nsImportGenericAddressBooks : public nsIImportGeneric
+{
+public:
+
+ nsImportGenericAddressBooks();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /* nsISupports GetData (in string dataId); */
+ NS_IMETHOD GetData(const char *dataId, nsISupports **_retval) override;
+
+ NS_IMETHOD SetData(const char *dataId, nsISupports *pData) override;
+
+ NS_IMETHOD GetStatus(const char *statusKind, int32_t *_retval) override;
+
+ NS_IMETHOD WantsProgress(bool *_retval) override;
+
+ NS_IMETHOD BeginImport(nsISupportsString *successLog, nsISupportsString *errorLog, bool *_retval) override;
+
+ NS_IMETHOD ContinueImport(bool *_retval) override;
+
+ NS_IMETHOD GetProgress(int32_t *_retval) override;
+
+ NS_IMETHOD CancelImport(void) override;
+
+private:
+ virtual ~nsImportGenericAddressBooks();
+ void GetDefaultLocation(void);
+ void GetDefaultBooks(void);
+ void GetDefaultFieldMap(void);
+
+public:
+ static void SetLogs(nsString& success, nsString& error, nsISupportsString *pSuccess, nsISupportsString *pError);
+ static void ReportError(const char16_t *pName, nsString *pStream,
+ nsIStringBundle *aBundle);
+
+private:
+ nsIImportAddressBooks * m_pInterface;
+ nsCOMPtr<nsIArray> m_Books;
+ nsCOMArray<nsIAddrDatabase> m_DBs;
+ nsCOMPtr <nsIFile> m_pLocation;
+ nsIImportFieldMap * m_pFieldMap;
+ bool m_autoFind;
+ char16_t * m_description;
+ bool m_gotLocation;
+ bool m_found;
+ bool m_userVerify;
+ nsISupportsString * m_pSuccessLog;
+ nsISupportsString * m_pErrorLog;
+ uint32_t m_totalSize;
+ bool m_doImport;
+ AddressThreadData * m_pThreadData;
+ char * m_pDestinationUri;
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+};
+
+class AddressThreadData {
+public:
+ bool driverAlive;
+ bool threadAlive;
+ bool abort;
+ bool fatalError;
+ uint32_t currentTotal;
+ uint32_t currentSize;
+ nsIArray *books;
+ nsCOMArray<nsIAddrDatabase>* dBs;
+ nsCOMPtr<nsIAbLDIFService> ldifService;
+ nsIImportAddressBooks * addressImport;
+ nsIImportFieldMap * fieldMap;
+ nsISupportsString * successLog;
+ nsISupportsString * errorLog;
+ char * pDestinationUri;
+ nsIStringBundle* stringBundle;
+
+ AddressThreadData();
+ ~AddressThreadData();
+};
+
+
+nsresult NS_NewGenericAddressBooks(nsIImportGeneric** aImportGeneric)
+{
+ NS_PRECONDITION(aImportGeneric != nullptr, "null ptr");
+ if (! aImportGeneric)
+ return NS_ERROR_NULL_POINTER;
+
+ nsImportGenericAddressBooks *pGen = new nsImportGenericAddressBooks();
+
+ if (pGen == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(pGen);
+ nsresult rv = pGen->QueryInterface(NS_GET_IID(nsIImportGeneric), (void **)aImportGeneric);
+ NS_RELEASE(pGen);
+
+ return rv;
+}
+
+nsImportGenericAddressBooks::nsImportGenericAddressBooks()
+{
+ m_pInterface = nullptr;
+ m_pSuccessLog = nullptr;
+ m_pErrorLog = nullptr;
+ m_totalSize = 0;
+ m_doImport = false;
+ m_pThreadData = nullptr;
+ m_pDestinationUri = nullptr;
+ m_pFieldMap = nullptr;
+
+ m_autoFind = false;
+ m_description = nullptr;
+ m_gotLocation = false;
+ m_found = false;
+ m_userVerify = false;
+
+ nsImportStringBundle::GetStringBundle(IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle));
+}
+
+
+nsImportGenericAddressBooks::~nsImportGenericAddressBooks()
+{
+ if (m_pDestinationUri)
+ NS_Free(m_pDestinationUri);
+
+ if (m_description)
+ NS_Free(m_description);
+
+ NS_IF_RELEASE(m_pFieldMap);
+ NS_IF_RELEASE(m_pInterface);
+ NS_IF_RELEASE(m_pSuccessLog);
+ NS_IF_RELEASE(m_pErrorLog);
+}
+
+
+
+NS_IMPL_ISUPPORTS(nsImportGenericAddressBooks, nsIImportGeneric)
+
+
+NS_IMETHODIMP nsImportGenericAddressBooks::GetData(const char *dataId, nsISupports **_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ *_retval = nullptr;
+ if (!PL_strcasecmp(dataId, "addressInterface")) {
+ *_retval = m_pInterface;
+ NS_IF_ADDREF(m_pInterface);
+ }
+
+ if (!PL_strcasecmp(dataId, "addressLocation")) {
+ if (!m_pLocation)
+ GetDefaultLocation();
+ NS_IF_ADDREF(*_retval = m_pLocation);
+ }
+
+ if (!PL_strcasecmp(dataId, "addressBooks")) {
+ if (!m_pLocation)
+ GetDefaultLocation();
+ if (!m_Books)
+ GetDefaultBooks();
+ *_retval = m_Books;
+ }
+
+ if (!PL_strcasecmp(dataId, "addressDestination")) {
+ if (m_pDestinationUri) {
+ nsCOMPtr<nsISupportsCString> abString = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ abString->SetData(nsDependentCString(m_pDestinationUri));
+ NS_IF_ADDREF(*_retval = abString);
+ }
+ }
+
+ if (!PL_strcasecmp(dataId, "fieldMap")) {
+ if (m_pFieldMap) {
+ *_retval = m_pFieldMap;
+ m_pFieldMap->AddRef();
+ }
+ else {
+ if (m_pInterface && m_pLocation) {
+ bool needsIt = false;
+ m_pInterface->GetNeedsFieldMap(m_pLocation, &needsIt);
+ if (needsIt) {
+ GetDefaultFieldMap();
+ if (m_pFieldMap) {
+ *_retval = m_pFieldMap;
+ m_pFieldMap->AddRef();
+ }
+ }
+ }
+ }
+ }
+
+ if (!PL_strncasecmp(dataId, "sampleData-", 11)) {
+ // extra the record number
+ const char *pNum = dataId + 11;
+ int32_t rNum = 0;
+ while (*pNum) {
+ rNum *= 10;
+ rNum += (*pNum - '0');
+ pNum++;
+ }
+ IMPORT_LOG1("Requesting sample data #: %ld\n", (long)rNum);
+ if (m_pInterface) {
+ nsCOMPtr<nsISupportsString> data = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ char16_t * pData = nullptr;
+ bool found = false;
+ rv = m_pInterface->GetSampleData(rNum, &found, &pData);
+ if (NS_FAILED(rv))
+ return rv;
+ if (found) {
+ data->SetData(nsDependentString(pData));
+ *_retval = data;
+ NS_ADDREF(*_retval);
+ }
+ NS_Free(pData);
+ }
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImportGenericAddressBooks::SetData(const char *dataId, nsISupports *item)
+{
+ NS_PRECONDITION(dataId != nullptr, "null ptr");
+ if (!dataId)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!PL_strcasecmp(dataId, "addressInterface")) {
+ NS_IF_RELEASE(m_pInterface);
+ if (item)
+ item->QueryInterface(NS_GET_IID(nsIImportAddressBooks), (void **) &m_pInterface);
+ }
+ if (!PL_strcasecmp(dataId, "addressBooks")) {
+ if (item)
+ item->QueryInterface(NS_GET_IID(nsIArray), (void **) &m_Books);
+ }
+
+ if (!PL_strcasecmp(dataId, "addressLocation")) {
+ m_pLocation = nullptr;
+
+ if (item) {
+ nsresult rv;
+ m_pLocation = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (m_pInterface)
+ m_pInterface->SetSampleLocation(m_pLocation);
+ }
+
+ if (!PL_strcasecmp(dataId, "addressDestination")) {
+ if (item) {
+ nsCOMPtr<nsISupportsCString> abString;
+ item->QueryInterface(NS_GET_IID(nsISupportsCString), getter_AddRefs(abString));
+ if (abString) {
+ if (m_pDestinationUri)
+ NS_Free(m_pDestinationUri);
+ m_pDestinationUri = nullptr;
+ nsAutoCString tempUri;
+ abString->GetData(tempUri);
+ m_pDestinationUri = ToNewCString(tempUri);
+ }
+ }
+ }
+
+ if (!PL_strcasecmp(dataId, "fieldMap")) {
+ NS_IF_RELEASE(m_pFieldMap);
+ if (item)
+ item->QueryInterface(NS_GET_IID(nsIImportFieldMap), (void **) &m_pFieldMap);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportGenericAddressBooks::GetStatus(const char *statusKind, int32_t *_retval)
+{
+ NS_PRECONDITION(statusKind != nullptr, "null ptr");
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!statusKind || !_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = 0;
+
+ if (!PL_strcasecmp(statusKind, "isInstalled")) {
+ GetDefaultLocation();
+ *_retval = (int32_t) m_found;
+ }
+
+ if (!PL_strcasecmp(statusKind, "canUserSetLocation")) {
+ GetDefaultLocation();
+ *_retval = (int32_t) m_userVerify;
+ }
+
+ if (!PL_strcasecmp(statusKind, "autoFind")) {
+ GetDefaultLocation();
+ *_retval = (int32_t) m_autoFind;
+ }
+
+ if (!PL_strcasecmp(statusKind, "supportsMultiple")) {
+ bool multi = false;
+ if (m_pInterface)
+ m_pInterface->GetSupportsMultiple(&multi);
+ *_retval = (int32_t) multi;
+ }
+
+ if (!PL_strcasecmp(statusKind, "needsFieldMap")) {
+ bool needs = false;
+ if (m_pInterface && m_pLocation)
+ m_pInterface->GetNeedsFieldMap(m_pLocation, &needs);
+ *_retval = (int32_t) needs;
+ }
+
+ return NS_OK;
+}
+
+void nsImportGenericAddressBooks::GetDefaultLocation(void)
+{
+ if (!m_pInterface)
+ return;
+
+ if ((m_pLocation && m_gotLocation) || m_autoFind)
+ return;
+
+ if (m_description)
+ NS_Free(m_description);
+ m_description = nullptr;
+ m_pInterface->GetAutoFind(&m_description, &m_autoFind);
+ m_gotLocation = true;
+ if (m_autoFind) {
+ m_found = true;
+ m_userVerify = false;
+ return;
+ }
+
+ nsCOMPtr <nsIFile> pLoc;
+ m_pInterface->GetDefaultLocation(getter_AddRefs(pLoc), &m_found, &m_userVerify);
+ if (!m_pLocation)
+ m_pLocation = pLoc;
+}
+
+void nsImportGenericAddressBooks::GetDefaultBooks(void)
+{
+ if (!m_pInterface || m_Books)
+ return;
+
+ if (!m_pLocation && !m_autoFind)
+ return;
+
+ nsresult rv = m_pInterface->FindAddressBooks(m_pLocation, getter_AddRefs(m_Books));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error: FindAddressBooks failed\n");
+ }
+}
+
+void nsImportGenericAddressBooks::GetDefaultFieldMap(void)
+{
+ if (!m_pInterface || !m_pLocation)
+ return;
+
+ NS_IF_RELEASE(m_pFieldMap);
+
+ nsresult rv;
+ nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Unable to get nsIImportService.\n");
+ return;
+ }
+
+ rv = impSvc->CreateNewFieldMap(&m_pFieldMap);
+ if (NS_FAILED(rv))
+ return;
+
+ int32_t sz = 0;
+ rv = m_pFieldMap->GetNumMozFields(&sz);
+ if (NS_SUCCEEDED(rv))
+ rv = m_pFieldMap->DefaultFieldMap(sz);
+ if (NS_SUCCEEDED(rv))
+ rv = m_pInterface->InitFieldMap(m_pFieldMap);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error: Unable to initialize field map\n");
+ NS_IF_RELEASE(m_pFieldMap);
+ }
+}
+
+
+NS_IMETHODIMP nsImportGenericAddressBooks::WantsProgress(bool *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ GetDefaultLocation();
+ GetDefaultBooks();
+
+ bool result = false;
+
+ if (m_Books) {
+ uint32_t count = 0;
+ uint32_t i;
+ bool import;
+ uint32_t size;
+ uint32_t totalSize = 0;
+
+ m_Books->GetLength(&count);
+
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsIImportABDescriptor> book = do_QueryElementAt(m_Books, i);
+ if (book) {
+ import = false;
+ size = 0;
+ nsresult rv = book->GetImport(&import);
+ if (NS_SUCCEEDED(rv) && import) {
+ (void) book->GetSize(&size);
+ result = true;
+ }
+ totalSize += size;
+ }
+ }
+
+ m_totalSize = totalSize;
+ }
+
+ m_doImport = result;
+
+ *_retval = result;
+
+ return NS_OK;
+}
+
+void nsImportGenericAddressBooks::SetLogs(nsString& success, nsString& error, nsISupportsString *pSuccess, nsISupportsString *pError)
+{
+ nsAutoString str;
+ if (pSuccess) {
+ pSuccess->GetData(str);
+ str.Append(success);
+ pSuccess->SetData(success);
+ }
+ if (pError) {
+ pError->GetData(str);
+ str.Append(error);
+ pError->SetData(error);
+ }
+}
+
+already_AddRefed<nsIAddrDatabase> GetAddressBookFromUri(const char *pUri)
+{
+ if (!pUri)
+ return nullptr;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID);
+ if (!abManager)
+ return nullptr;
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ abManager->GetDirectory(nsDependentCString(pUri),
+ getter_AddRefs(directory));
+ if (!directory)
+ return nullptr;
+
+ nsCOMPtr<nsIAbMDBDirectory> mdbDirectory = do_QueryInterface(directory);
+ if (!mdbDirectory)
+ return nullptr;
+
+ nsCOMPtr<nsIAddrDatabase> pDatabase;
+ mdbDirectory->GetDatabase(getter_AddRefs(pDatabase));
+ return pDatabase.forget();
+}
+
+already_AddRefed<nsIAddrDatabase> GetAddressBook(const char16_t *name,
+ bool makeNew)
+{
+ if (!makeNew) {
+ // FIXME: How do I get the list of address books and look for a
+ // specific name. Major bogosity!
+ // For now, assume we didn't find anything with that name
+ }
+
+ IMPORT_LOG0("In GetAddressBook\n");
+
+ nsresult rv;
+ nsCOMPtr<nsIAddrDatabase> pDatabase;
+ nsCOMPtr<nsIFile> dbPath;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ /* Get the profile directory */
+ rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath));
+ if (NS_SUCCEEDED(rv))
+ {
+ // Create a new address book file - we don't care what the file
+ // name is, as long as it's unique
+ rv = dbPath->Append(NS_LITERAL_STRING("impab.mab"));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ IMPORT_LOG0("Getting the address database factory\n");
+
+ nsCOMPtr<nsIAddrDatabase> addrDBFactory =
+ do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ IMPORT_LOG0("Opening the new address book\n");
+ rv = addrDBFactory->Open(dbPath, true, true,
+ getter_AddRefs(pDatabase));
+ }
+ }
+ }
+ }
+ if (NS_FAILED(rv))
+ {
+ IMPORT_LOG0("Failed to get the user profile directory from the address book session\n");
+ }
+
+ if (pDatabase && dbPath)
+ {
+ // We made a database, add it to the UI?!?!?!?!?!?!
+ // This is major bogosity again! Why doesn't the address book
+ // just handle this properly for me? Uggggg...
+
+ nsCOMPtr<nsIAbDirectory> parentDir;
+ abManager->GetDirectory(NS_LITERAL_CSTRING(kAllDirectoryRoot),
+ getter_AddRefs(parentDir));
+ if (parentDir)
+ {
+ nsAutoCString URI("moz-abmdbdirectory://");
+ nsAutoCString leafName;
+ rv = dbPath->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv))
+ IMPORT_LOG0("*** Error: Unable to get name of database file\n");
+ else
+ {
+ URI.Append(leafName);
+ rv = parentDir->CreateDirectoryByURI(nsDependentString(name), URI);
+ if (NS_FAILED(rv))
+ IMPORT_LOG0("*** Error: Unable to create address book directory\n");
+ }
+ }
+
+ if (NS_SUCCEEDED(rv))
+ IMPORT_LOG0("Added new address book to the UI\n");
+ else
+ IMPORT_LOG0("*** Error: An error occurred while adding the address book to the UI\n");
+ }
+
+ return pDatabase.forget();
+}
+
+NS_IMETHODIMP nsImportGenericAddressBooks::BeginImport(nsISupportsString *successLog, nsISupportsString *errorLog, bool *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ nsString success;
+ nsString error;
+
+ if (!m_doImport) {
+ *_retval = true;
+ nsImportStringBundle::GetStringByID(IMPORT_NO_ADDRBOOKS, m_stringBundle,
+ success);
+ SetLogs(success, error, successLog, errorLog);
+ return NS_OK;
+ }
+
+ if (!m_pInterface || !m_Books) {
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_AB_NOTINITIALIZED,
+ m_stringBundle, error);
+ SetLogs(success, error, successLog, errorLog);
+ *_retval = false;
+ return NS_OK;
+ }
+
+ bool needsFieldMap = false;
+
+ if (NS_FAILED(m_pInterface->GetNeedsFieldMap(m_pLocation, &needsFieldMap)) ||
+ (needsFieldMap && !m_pFieldMap)) {
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_AB_NOTINITIALIZED,
+ m_stringBundle, error);
+ SetLogs(success, error, successLog, errorLog);
+ *_retval = false;
+ return NS_OK;
+ }
+
+ NS_IF_RELEASE(m_pSuccessLog);
+ NS_IF_RELEASE(m_pErrorLog);
+ m_pSuccessLog = successLog;
+ m_pErrorLog = errorLog;
+ NS_IF_ADDREF(m_pSuccessLog);
+ NS_IF_ADDREF(m_pErrorLog);
+
+
+ // create the info need to drive address book import. We're
+ // not going to create a new thread for this since address books
+ // don't tend to be large, and import is rare.
+ m_pThreadData = new AddressThreadData();
+ m_pThreadData->books = m_Books;
+ NS_ADDREF(m_Books);
+ m_pThreadData->addressImport = m_pInterface;
+ NS_ADDREF(m_pInterface);
+ m_pThreadData->fieldMap = m_pFieldMap;
+ NS_IF_ADDREF(m_pFieldMap);
+ m_pThreadData->errorLog = m_pErrorLog;
+ NS_IF_ADDREF(m_pErrorLog);
+ m_pThreadData->successLog = m_pSuccessLog;
+ NS_IF_ADDREF(m_pSuccessLog);
+ if (m_pDestinationUri)
+ m_pThreadData->pDestinationUri = strdup(m_pDestinationUri);
+
+ uint32_t count = 0;
+ m_Books->GetLength(&count);
+ // Create/obtain any address books that we need here, so that we don't need
+ // to do so inside the import thread which would just proxy the create
+ // operations back to the main thread anyway.
+ nsCOMPtr<nsIAddrDatabase> db = GetAddressBookFromUri(m_pDestinationUri);
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ nsCOMPtr<nsIImportABDescriptor> book = do_QueryElementAt(m_Books, i);
+ if (book)
+ {
+ if (!db)
+ {
+ nsString name;
+ book->GetPreferredName(name);
+ db = GetAddressBook(name.get(), true);
+ }
+ m_DBs.AppendObject(db);
+ }
+ }
+ m_pThreadData->dBs = &m_DBs;
+
+ NS_IF_ADDREF(m_pThreadData->stringBundle = m_stringBundle);
+
+ nsresult rv;
+ m_pThreadData->ldifService = do_GetService(NS_ABLDIFSERVICE_CONTRACTID, &rv);
+
+ ImportAddressThread(m_pThreadData);
+ delete m_pThreadData;
+ m_pThreadData = nullptr;
+ *_retval = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportGenericAddressBooks::ContinueImport(bool *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = true;
+ if (m_pThreadData) {
+ if (m_pThreadData->fatalError)
+ *_retval = false;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImportGenericAddressBooks::GetProgress(int32_t *_retval)
+{
+ // This returns the progress from the the currently
+ // running import mail or import address book thread.
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!m_pThreadData || !(m_pThreadData->threadAlive)) {
+ *_retval = 100;
+ return NS_OK;
+ }
+
+ uint32_t sz = 0;
+ if (m_pThreadData->currentSize && m_pInterface) {
+ if (NS_FAILED(m_pInterface->GetImportProgress(&sz)))
+ sz = 0;
+ }
+
+ if (m_totalSize)
+ *_retval = ((m_pThreadData->currentTotal + sz) * 100) / m_totalSize;
+ else
+ *_retval = 0;
+
+ // never return less than 5 so it looks like we are doing something!
+ if (*_retval < 5)
+ *_retval = 5;
+
+ // as long as the thread is alive don't return completely
+ // done.
+ if (*_retval > 99)
+ *_retval = 99;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImportGenericAddressBooks::CancelImport(void)
+{
+ if (m_pThreadData) {
+ m_pThreadData->abort = true;
+ m_pThreadData = nullptr;
+ }
+
+ return NS_OK;
+}
+
+
+AddressThreadData::AddressThreadData()
+{
+ fatalError = false;
+ driverAlive = true;
+ threadAlive = true;
+ abort = false;
+ currentTotal = 0;
+ currentSize = 0;
+ books = nullptr;
+ addressImport = nullptr;
+ successLog = nullptr;
+ errorLog = nullptr;
+ pDestinationUri = nullptr;
+ fieldMap = nullptr;
+ stringBundle = nullptr;
+ ldifService = nullptr;
+}
+
+AddressThreadData::~AddressThreadData()
+{
+ if (pDestinationUri)
+ NS_Free(pDestinationUri);
+
+ NS_IF_RELEASE(books);
+ NS_IF_RELEASE(addressImport);
+ NS_IF_RELEASE(errorLog);
+ NS_IF_RELEASE(successLog);
+ NS_IF_RELEASE(fieldMap);
+ NS_IF_RELEASE(stringBundle);
+}
+
+void nsImportGenericAddressBooks::ReportError(const char16_t *pName,
+ nsString *pStream,
+ nsIStringBundle* aBundle)
+{
+ if (!pStream)
+ return;
+ // load the error string
+ char16_t *pFmt = nsImportStringBundle::GetStringByID(IMPORT_ERROR_GETABOOK, aBundle);
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, pName);
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ NS_Free(pFmt);
+ pStream->AppendLiteral(MSG_LINEBREAK);
+}
+
+static void ImportAddressThread(void *stuff)
+{
+ IMPORT_LOG0("In Begin ImportAddressThread\n");
+
+ AddressThreadData *pData = (AddressThreadData *)stuff;
+ uint32_t count = 0;
+ uint32_t i;
+ bool import;
+ uint32_t size;
+
+ nsString success;
+ nsString error;
+
+ (void) pData->books->GetLength(&count);
+
+ for (i = 0; (i < count) && !(pData->abort); i++) {
+ nsCOMPtr<nsIImportABDescriptor> book =
+ do_QueryElementAt(pData->books, i);
+
+ if (book) {
+ import = false;
+ size = 0;
+ nsresult rv = book->GetImport(&import);
+ if (NS_SUCCEEDED(rv) && import)
+ rv = book->GetSize(&size);
+
+ if (NS_SUCCEEDED(rv) && size && import) {
+ nsString name;
+ book->GetPreferredName(name);
+
+ nsCOMPtr<nsIAddrDatabase> db = pData->dBs->ObjectAt(i);
+
+ bool fatalError = false;
+ pData->currentSize = size;
+ if (db) {
+ char16_t *pSuccess = nullptr;
+ char16_t *pError = nullptr;
+
+ /*
+ if (pData->fieldMap) {
+ int32_t sz = 0;
+ int32_t mapIndex;
+ bool active;
+ pData->fieldMap->GetMapSize(&sz);
+ IMPORT_LOG1("**** Field Map Size: %d\n", (int) sz);
+ for (int32_t i = 0; i < sz; i++) {
+ pData->fieldMap->GetFieldMap(i, &mapIndex);
+ pData->fieldMap->GetFieldActive(i, &active);
+ IMPORT_LOG3("Field map #%d: index=%d, active=%d\n", (int) i, (int) mapIndex, (int) active);
+ }
+ }
+ */
+
+ rv = pData->addressImport->ImportAddressBook(book,
+ db,
+ pData->fieldMap,
+ pData->ldifService,
+ &pError,
+ &pSuccess,
+ &fatalError);
+ if (NS_SUCCEEDED(rv) && pSuccess) {
+ success.Append(pSuccess);
+ NS_Free(pSuccess);
+ }
+ if (pError) {
+ error.Append(pError);
+ NS_Free(pError);
+ }
+ }
+ else {
+ nsImportGenericAddressBooks::ReportError(name.get(), &error, pData->stringBundle);
+ }
+
+ pData->currentSize = 0;
+ pData->currentTotal += size;
+
+ if (db)
+ db->Close(true);
+
+ if (fatalError) {
+ pData->fatalError = true;
+ break;
+ }
+ }
+ }
+ }
+
+
+ nsImportGenericAddressBooks::SetLogs(success, error, pData->successLog, pData->errorLog);
+
+ if (pData->abort || pData->fatalError) {
+ // FIXME: do what is necessary to get rid of what has been imported so far.
+ // Nothing if we went into an existing address book! Otherwise, delete
+ // the ones we created?
+ }
+
+}
diff --git a/mailnews/import/src/nsImportEmbeddedImageData.cpp b/mailnews/import/src/nsImportEmbeddedImageData.cpp
new file mode 100644
index 000000000..526f9c21c
--- /dev/null
+++ b/mailnews/import/src/nsImportEmbeddedImageData.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsImportEmbeddedImageData.h"
+
+NS_IMPL_ISUPPORTS(nsImportEmbeddedImageData, nsIMsgEmbeddedImageData)
+
+nsImportEmbeddedImageData::nsImportEmbeddedImageData()
+{
+}
+
+nsImportEmbeddedImageData::nsImportEmbeddedImageData(
+ nsIURI *aUri, const nsACString &aCid) : m_uri(aUri), m_cid(aCid)
+{
+}
+
+nsImportEmbeddedImageData::nsImportEmbeddedImageData(
+ nsIURI *aUri, const nsACString &aCid, const nsACString &aName)
+ : m_uri(aUri), m_cid(aCid), m_name(aName)
+{
+}
+
+nsImportEmbeddedImageData::~nsImportEmbeddedImageData()
+{
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::GetUri(nsIURI **aUri)
+{
+ NS_ENSURE_ARG_POINTER(aUri);
+ NS_IF_ADDREF(*aUri = m_uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::SetUri(nsIURI *aUri)
+{
+ m_uri = aUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::GetCid(nsACString &aCid)
+{
+ aCid = m_cid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::SetCid(const nsACString &aCid)
+{
+ m_cid = aCid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::GetName(nsACString &aName)
+{
+ aName = m_name;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::SetName(const nsACString &aName)
+{
+ m_name = aName;
+ return NS_OK;
+}
diff --git a/mailnews/import/src/nsImportEmbeddedImageData.h b/mailnews/import/src/nsImportEmbeddedImageData.h
new file mode 100644
index 000000000..5635dc512
--- /dev/null
+++ b/mailnews/import/src/nsImportEmbeddedImageData.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __IMPORTEMBEDDEDIMAGETDATA_H__
+#define __IMPORTEMBEDDEDIMAGETDATA_H__
+
+#include "nsIMsgSend.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+
+class nsImportEmbeddedImageData final : public nsIMsgEmbeddedImageData
+{
+public:
+ nsImportEmbeddedImageData(nsIURI *aUri, const nsACString &aCID);
+ nsImportEmbeddedImageData(nsIURI *aUri, const nsACString &aCID, const nsACString &aName);
+ nsImportEmbeddedImageData();
+ NS_DECL_NSIMSGEMBEDDEDIMAGEDATA
+ NS_DECL_ISUPPORTS
+
+ nsCOMPtr<nsIURI> m_uri;
+ nsCString m_cid;
+ nsCString m_name;
+
+private:
+ ~nsImportEmbeddedImageData();
+};
+
+
+#endif
diff --git a/mailnews/import/src/nsImportEncodeScan.cpp b/mailnews/import/src/nsImportEncodeScan.cpp
new file mode 100644
index 000000000..77e89198d
--- /dev/null
+++ b/mailnews/import/src/nsImportEncodeScan.cpp
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "nsImportEncodeScan.h"
+#include "nsNetUtil.h"
+
+#define kBeginAppleSingle 0
+#define kBeginDataFork 1
+#define kBeginResourceFork 2
+#define kAddEntries 3
+#define kScanningDataFork 4
+#define kScanningRsrcFork 5
+#define kDoneWithFile 6
+
+uint32_t gAppleSingleHeader[6] = {0x00051600, 0x00020000, 0, 0, 0, 0};
+#define kAppleSingleHeaderSize (6 * sizeof(uint32_t))
+
+#ifdef _MAC_IMPORT_CODE
+#include "MoreFilesExtras.h"
+#include "MoreDesktopMgr.h"
+
+CInfoPBRec gCatInfoPB;
+U32 g2000Secs = 0;
+long gGMTDelta = 0;
+
+long GetGmtDelta(void);
+U32 Get2000Secs(void);
+
+
+long GetGmtDelta(void)
+{
+ MachineLocation myLocation;
+ ReadLocation(&myLocation);
+ long myDelta = BitAnd(myLocation.u.gmtDelta, 0x00FFFFFF);
+ if (BitTst(&myDelta, 23))
+ myDelta = BitOr(myDelta, 0xFF000000);
+ return myDelta;
+}
+
+U32 Get2000Secs(void)
+{
+ DateTimeRec dr;
+ dr.year = 2000;
+ dr.month = 1;
+ dr.day = 1;
+ dr.hour = 0;
+ dr.minute = 0;
+ dr.second = 0;
+ dr.dayOfWeek = 0;
+ U32 result;
+ DateToSeconds(&dr, &result);
+ return result;
+}
+#endif
+
+nsImportEncodeScan::nsImportEncodeScan()
+{
+ m_isAppleSingle = false;
+ m_encodeScanState = 0;
+ m_resourceForkSize = 0;
+ m_dataForkSize = 0;
+}
+
+nsImportEncodeScan::~nsImportEncodeScan()
+{
+}
+
+bool nsImportEncodeScan::InitEncodeScan(bool appleSingleEncode, nsIFile *fileLoc, const char *pName, uint8_t * pBuf, uint32_t sz)
+{
+ CleanUpEncodeScan();
+ m_isAppleSingle = appleSingleEncode;
+ m_encodeScanState = kBeginAppleSingle;
+ m_pInputFile = do_QueryInterface(fileLoc);
+ m_useFileName = pName;
+ m_pBuf = pBuf;
+ m_bufSz = sz;
+ if (!m_isAppleSingle)
+ {
+ if (!m_inputStream)
+ {
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(m_inputStream), m_pInputFile);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ InitScan(m_inputStream, pBuf, sz);
+ }
+ else {
+ #ifdef _MAC_IMPORT_CODE
+ // Fill in the file sizes
+ m_resourceForkSize = fileLoc.GetMacFileSize(UFileLocation::eResourceFork);
+ m_dataForkSize = fileLoc.GetMacFileSize(UFileLocation::eDataFork);
+ #endif
+ }
+
+ return true;
+}
+
+void nsImportEncodeScan::CleanUpEncodeScan(void)
+{
+ m_pInputStream->Close();
+ m_pInputStream = nullptr;
+ m_pInputFile = nullptr;
+}
+
+
+// 26 + 12 per entry
+
+void nsImportEncodeScan::FillInEntries(int numEntries)
+{
+#ifdef _MAC_IMPORT_CODE
+ int len = m_useFileName.GetLength();
+ if (len < 32)
+ len = 32;
+ long entry[3];
+ long fileOffset = 26 + (12 * numEntries);
+ entry[0] = 3;
+ entry[1] = fileOffset;
+ entry[2] = m_useFileName.GetLength();
+ fileOffset += len;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+
+
+ Str255 comment;
+ comment[0] = 0;
+ OSErr err = FSpDTGetComment(m_inputFileLoc, comment);
+ if (comment[0] > 200)
+ comment[0] = 200;
+ entry[0] = 4;
+ entry[1] = fileOffset;
+ entry[2] = comment[0];
+ fileOffset += 200;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+
+
+ entry[0] = 8;
+ entry[1] = fileOffset;
+ entry[2] = 16;
+ fileOffset += 16;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+
+ entry[0] = 9;
+ entry[1] = fileOffset;
+ entry[2] = 32;
+ fileOffset += 32;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+
+
+ entry[0] = 10;
+ entry[1] = fileOffset;
+ entry[2] = 4;
+ fileOffset += 4;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+
+ if (m_resourceForkSize) {
+ entry[0] = 2;
+ entry[1] = fileOffset;
+ entry[2] = m_resourceForkSize;
+ fileOffset += m_resourceForkSize;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+ }
+
+ if (m_dataForkSize) {
+ entry[0] = 1;
+ entry[1] = fileOffset;
+ entry[2] = m_dataForkSize;
+ fileOffset += m_dataForkSize;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+ }
+
+#endif
+}
+
+bool nsImportEncodeScan::AddEntries(void)
+{
+#ifdef _MAC_IMPORT_CODE
+ if (!g2000Secs) {
+ g2000Secs = Get2000Secs();
+ gGMTDelta = GetGmtDelta();
+ }
+ MemCpy(m_pBuf + m_bytesInBuf, (PC_S8) m_useFileName, m_useFileName.GetLength());
+ m_bytesInBuf += m_useFileName.GetLength();
+ if (m_useFileName.GetLength() < 32) {
+ int len = m_useFileName.GetLength();
+ while (len < 32) {
+ *((P_S8)m_pBuf + m_bytesInBuf) = 0;
+ m_bytesInBuf++;
+ len++;
+ }
+ }
+
+ Str255 comment;
+ comment[0] = 0;
+ OSErr err = FSpDTGetComment(m_inputFileLoc, comment);
+ comment[0] = 200;
+ MemCpy(m_pBuf + m_bytesInBuf, &(comment[1]), comment[0]);
+ m_bytesInBuf += comment[0];
+
+ long dates[4];
+ dates[0] = gCatInfoPB.hFileInfo.ioFlCrDat;
+ dates[1] = gCatInfoPB.hFileInfo.ioFlMdDat;
+ dates[2] = gCatInfoPB.hFileInfo.ioFlBkDat;
+ dates[3] = 0x80000000;
+ for (short i = 0; i < 3; i++) {
+ dates[i] -= g2000Secs;
+ dates[i] += gGMTDelta;
+ }
+ MemCpy(m_pBuf + m_bytesInBuf, dates, 16);
+ m_bytesInBuf += 16;
+
+
+ FInfo fInfo = gCatInfoPB.hFileInfo.ioFlFndrInfo;
+ FXInfo fxInfo = gCatInfoPB.hFileInfo.ioFlXFndrInfo;
+ fInfo.fdFlags = 0;
+ fInfo.fdLocation.h = 0;
+ fInfo.fdLocation.v = 0;
+ fInfo.fdFldr = 0;
+ MemSet(&fxInfo, 0, sizeof(fxInfo));
+ MemCpy(m_pBuf + m_bytesInBuf, &fInfo, 16);
+ m_bytesInBuf += 16;
+ MemCpy(m_pBuf + m_bytesInBuf, &fxInfo, 16);
+ m_bytesInBuf += 16;
+
+
+ dates[0] = 0;
+ if ((gCatInfoPB.hFileInfo.ioFlAttrib & 1) != 0)
+ dates[0] |= 1;
+ MemCpy(m_pBuf + m_bytesInBuf, dates, 4);
+ m_bytesInBuf += 4;
+
+
+#endif
+ return true;
+}
+
+bool nsImportEncodeScan::Scan(bool *pDone)
+{
+ nsresult rv;
+
+ *pDone = false;
+ if (m_isAppleSingle) {
+ // Stuff the buffer with things needed to encode the file...
+ // then just allow UScanFile to handle each fork, but be careful
+ // when handling eof.
+ switch(m_encodeScanState) {
+ case kBeginAppleSingle: {
+#ifdef _MAC_IMPORT_CODE
+ OSErr err = GetCatInfoNoName(m_inputFileLoc.GetVRefNum(), m_inputFileLoc.GetParID(), m_inputFileLoc.GetFileNamePtr(), &gCatInfoPB);
+ if (err != noErr)
+ return FALSE;
+#endif
+ m_eof = false;
+ m_pos = 0;
+ memcpy(m_pBuf, gAppleSingleHeader, kAppleSingleHeaderSize);
+ m_bytesInBuf = kAppleSingleHeaderSize;
+ int numEntries = 5;
+ if (m_dataForkSize)
+ numEntries++;
+ if (m_resourceForkSize)
+ numEntries++;
+ memcpy(m_pBuf + m_bytesInBuf, &numEntries, sizeof(numEntries));
+ m_bytesInBuf += sizeof(numEntries);
+ FillInEntries(numEntries);
+ m_encodeScanState = kAddEntries;
+ return ScanBuffer(pDone);
+ }
+ break;
+
+ case kBeginDataFork: {
+ if (!m_dataForkSize) {
+ m_encodeScanState = kDoneWithFile;
+ return true;
+ }
+ // Initialize the scan of the data fork...
+ if (!m_inputStream)
+ {
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(m_inputStream), m_pInputFile);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+ m_encodeScanState = kScanningDataFork;
+ return true;
+ }
+ break;
+
+ case kScanningDataFork: {
+ bool result = FillBufferFromFile();
+ if (!result)
+ return false;
+ if (m_eof) {
+ m_eof = false;
+ result = ScanBuffer(pDone);
+ if (!result)
+ return false;
+ m_inputStream->Close();
+ m_inputStream = nullptr;
+ m_encodeScanState = kDoneWithFile;
+ return true;
+ }
+ else
+ return ScanBuffer(pDone);
+ }
+ break;
+
+ case kScanningRsrcFork: {
+ bool result = FillBufferFromFile();
+ if (!result)
+ return false;
+ if (m_eof) {
+ m_eof = false;
+ result = ScanBuffer(pDone);
+ if (!result)
+ return false;
+ m_inputStream->Close();
+ m_inputStream = nullptr;
+ m_encodeScanState = kBeginDataFork;
+ return true;
+ }
+ else
+ return ScanBuffer(pDone);
+ }
+ break;
+
+ case kBeginResourceFork: {
+ if (!m_resourceForkSize) {
+ m_encodeScanState = kBeginDataFork;
+ return true;
+ }
+ /*
+ // FIXME: Open the resource fork on the Mac!!!
+ m_fH = UFile::OpenRsrcFileRead(m_inputFileLoc);
+ if (m_fH == TR_FILE_ERROR)
+ return FALSE;
+ */
+ m_encodeScanState = kScanningRsrcFork;
+ return true;
+ }
+ break;
+
+ case kAddEntries: {
+ ShiftBuffer();
+ if (!AddEntries())
+ return false;
+ m_encodeScanState = kBeginResourceFork;
+ return ScanBuffer(pDone);
+ }
+ break;
+
+ case kDoneWithFile: {
+ ShiftBuffer();
+ m_eof = true;
+ if (!ScanBuffer(pDone))
+ return false;
+ *pDone = true;
+ return true;
+ }
+ break;
+ }
+
+ }
+ else
+ return nsImportScanFile::Scan(pDone);
+
+ return false;
+}
+
diff --git a/mailnews/import/src/nsImportEncodeScan.h b/mailnews/import/src/nsImportEncodeScan.h
new file mode 100644
index 000000000..3f0e246f1
--- /dev/null
+++ b/mailnews/import/src/nsImportEncodeScan.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImportEncodeScan_h___
+#define nsImportEncodeScan_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIFile.h"
+#include "nsImportScanFile.h"
+#include "nsStringGlue.h"
+
+class nsImportEncodeScan : public nsImportScanFile {
+public:
+ nsImportEncodeScan();
+ ~nsImportEncodeScan();
+
+ bool InitEncodeScan(bool appleSingleEncode, nsIFile *pFile, const char *pName, uint8_t * pBuf, uint32_t sz);
+ void CleanUpEncodeScan(void);
+
+ virtual bool Scan(bool *pDone) override;
+
+protected:
+ void FillInEntries(int numEntries);
+ bool AddEntries(void);
+
+protected:
+ bool m_isAppleSingle;
+ nsCOMPtr<nsIFile> m_pInputFile;
+ nsCOMPtr<nsIInputStream> m_inputStream;
+ int m_encodeScanState;
+ long m_resourceForkSize;
+ long m_dataForkSize;
+ nsCString m_useFileName;
+};
+
+#endif /* nsImportEncodeScan_h__ */
+
diff --git a/mailnews/import/src/nsImportFieldMap.cpp b/mailnews/import/src/nsImportFieldMap.cpp
new file mode 100644
index 000000000..d5e9748dc
--- /dev/null
+++ b/mailnews/import/src/nsImportFieldMap.cpp
@@ -0,0 +1,384 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nscore.h"
+#include "nsIStringBundle.h"
+#include "nsImportFieldMap.h"
+#include "nsImportStringBundle.h"
+#include "nsCRTGlue.h"
+#include "ImportDebug.h"
+#include "nsCOMPtr.h"
+
+////////////////////////////////////////////////////////////////////////
+
+NS_METHOD nsImportFieldMap::Create(nsIStringBundle *aBundle, nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsImportFieldMap *it = new nsImportFieldMap(aBundle);
+ if (it == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(it);
+ nsresult rv = it->QueryInterface(aIID, aResult);
+ NS_RELEASE(it);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsImportFieldMap, nsIImportFieldMap)
+
+NS_IMETHODIMP nsImportFieldMap::GetSkipFirstRecord(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_skipFirstRecord;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::SetSkipFirstRecord(bool aResult)
+{
+ m_skipFirstRecord = aResult;
+ return NS_OK;
+}
+
+nsImportFieldMap::nsImportFieldMap(nsIStringBundle *aBundle)
+{
+ m_numFields = 0;
+ m_pFields = nullptr;
+ m_pActive = nullptr;
+ m_allocated = 0;
+ // need to init the description array
+ m_mozFieldCount = 0;
+ m_skipFirstRecord = false;
+ nsCOMPtr<nsIStringBundle> pBundle = aBundle;
+
+ nsString *pStr;
+ for (int32_t i = IMPORT_FIELD_DESC_START; i <= IMPORT_FIELD_DESC_END; i++, m_mozFieldCount++) {
+ pStr = new nsString();
+ if (pBundle) {
+ nsImportStringBundle::GetStringByID(i, pBundle, *pStr);
+ }
+ else
+ pStr->AppendInt(i);
+ m_descriptions.AppendElement(pStr);
+ }
+}
+
+nsImportFieldMap::~nsImportFieldMap()
+{
+ if (m_pFields)
+ delete [] m_pFields;
+ if (m_pActive)
+ delete [] m_pActive;
+
+ nsString * pStr;
+ for (int32_t i = 0; i < m_mozFieldCount; i++) {
+ pStr = m_descriptions.ElementAt(i);
+ delete pStr;
+ }
+ m_descriptions.Clear();
+}
+
+
+NS_IMETHODIMP nsImportFieldMap::GetNumMozFields(int32_t *aNumFields)
+{
+ NS_PRECONDITION(aNumFields != nullptr, "null ptr");
+ if (!aNumFields)
+ return NS_ERROR_NULL_POINTER;
+
+ *aNumFields = m_mozFieldCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::GetMapSize(int32_t *aNumFields)
+{
+ NS_PRECONDITION(aNumFields != nullptr, "null ptr");
+ if (!aNumFields)
+ return NS_ERROR_NULL_POINTER;
+
+ *aNumFields = m_numFields;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::GetFieldDescription(int32_t index, char16_t **_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = nullptr;
+ if ((index < 0) || ((size_t)index >= m_descriptions.Length()))
+ return NS_ERROR_FAILURE;
+
+ *_retval = ToNewUnicode(*(m_descriptions.ElementAt(index)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::SetFieldMapSize(int32_t size)
+{
+ nsresult rv = Allocate(size);
+ if (NS_FAILED(rv))
+ return rv;
+
+ m_numFields = size;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImportFieldMap::DefaultFieldMap(int32_t size)
+{
+ nsresult rv = SetFieldMapSize(size);
+ if (NS_FAILED(rv))
+ return rv;
+ for (int32_t i = 0; i < size; i++) {
+ m_pFields[i] = i;
+ m_pActive[i] = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::GetFieldMap(int32_t index, int32_t *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+
+ if ((index < 0) || (index >= m_numFields))
+ return NS_ERROR_FAILURE;
+
+ *_retval = m_pFields[index];
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::SetFieldMap(int32_t index, int32_t fieldNum)
+{
+ if (index == -1) {
+ nsresult rv = Allocate(m_numFields + 1);
+ if (NS_FAILED(rv))
+ return rv;
+ index = m_numFields;
+ m_numFields++;
+ }
+ else {
+ if ((index < 0) || (index >= m_numFields))
+ return NS_ERROR_FAILURE;
+ }
+
+ if ((fieldNum != -1) && ((fieldNum < 0) || (fieldNum >= m_mozFieldCount)))
+ return NS_ERROR_FAILURE;
+
+ m_pFields[index] = fieldNum;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::GetFieldActive(int32_t index, bool *active)
+{
+ NS_PRECONDITION(active != nullptr, "null ptr");
+ if (!active)
+ return NS_ERROR_NULL_POINTER;
+ if ((index < 0) || (index >= m_numFields))
+ return NS_ERROR_FAILURE;
+
+ *active = m_pActive[index];
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::SetFieldActive(int32_t index, bool active)
+{
+ if ((index < 0) || (index >= m_numFields))
+ return NS_ERROR_FAILURE;
+
+ m_pActive[index] = active;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImportFieldMap::SetFieldValue(nsIAddrDatabase *database, nsIMdbRow *row, int32_t fieldNum, const char16_t *value)
+{
+ NS_PRECONDITION(database != nullptr, "null ptr");
+ NS_PRECONDITION(row != nullptr, "null ptr");
+ NS_PRECONDITION(value != nullptr, "null ptr");
+ if (!database || !row || !value)
+ return NS_ERROR_NULL_POINTER;
+
+ // Allow the special value for a null field
+ if (fieldNum == -1)
+ return NS_OK;
+
+ if ((fieldNum < 0) || (fieldNum >= m_mozFieldCount))
+ return NS_ERROR_FAILURE;
+
+ // UGGG!!!!! lot's of typing here!
+ nsresult rv;
+
+ nsString str(value);
+ char *pVal = ToNewUTF8String(str);
+
+ switch(fieldNum) {
+ case 0:
+ rv = database->AddFirstName(row, pVal);
+ break;
+ case 1:
+ rv = database->AddLastName(row, pVal);
+ break;
+ case 2:
+ rv = database->AddDisplayName(row, pVal);
+ break;
+ case 3:
+ rv = database->AddNickName(row, pVal);
+ break;
+ case 4:
+ rv = database->AddPrimaryEmail(row, pVal);
+ break;
+ case 5:
+ rv = database->Add2ndEmail(row, pVal);
+ break;
+ case 6:
+ rv = database->AddWorkPhone(row, pVal);
+ break;
+ case 7:
+ rv = database->AddHomePhone(row, pVal);
+ break;
+ case 8:
+ rv = database->AddFaxNumber(row, pVal);
+ break;
+ case 9:
+ rv = database->AddPagerNumber(row, pVal);
+ break;
+ case 10:
+ rv = database->AddCellularNumber(row, pVal);
+ break;
+ case 11:
+ rv = database->AddHomeAddress(row, pVal);
+ break;
+ case 12:
+ rv = database->AddHomeAddress2(row, pVal);
+ break;
+ case 13:
+ rv = database->AddHomeCity(row, pVal);
+ break;
+ case 14:
+ rv = database->AddHomeState(row, pVal);
+ break;
+ case 15:
+ rv = database->AddHomeZipCode(row, pVal);
+ break;
+ case 16:
+ rv = database->AddHomeCountry(row, pVal);
+ break;
+ case 17:
+ rv = database->AddWorkAddress(row, pVal);
+ break;
+ case 18:
+ rv = database->AddWorkAddress2(row, pVal);
+ break;
+ case 19:
+ rv = database->AddWorkCity(row, pVal);
+ break;
+ case 20:
+ rv = database->AddWorkState(row, pVal);
+ break;
+ case 21:
+ rv = database->AddWorkZipCode(row, pVal);
+ break;
+ case 22:
+ rv = database->AddWorkCountry(row, pVal);
+ break;
+ case 23:
+ rv = database->AddJobTitle(row, pVal);
+ break;
+ case 24:
+ rv = database->AddDepartment(row, pVal);
+ break;
+ case 25:
+ rv = database->AddCompany(row, pVal);
+ break;
+ case 26:
+ rv = database->AddWebPage1(row, pVal);
+ break;
+ case 27:
+ rv = database->AddWebPage2(row, pVal);
+ break;
+ case 28:
+ rv = database->AddBirthYear(row, pVal);
+ break;
+ case 29:
+ rv = database->AddBirthMonth(row, pVal);
+ break;
+ case 30:
+ rv = database->AddBirthDay(row, pVal);
+ break;
+ case 31:
+ rv = database->AddCustom1(row, pVal);
+ break;
+ case 32:
+ rv = database->AddCustom2(row, pVal);
+ break;
+ case 33:
+ rv = database->AddCustom3(row, pVal);
+ break;
+ case 34:
+ rv = database->AddCustom4(row, pVal);
+ break;
+ case 35:
+ rv = database->AddNotes(row, pVal);
+ break;
+ case 36:
+ rv = database->AddAimScreenName(row, pVal);
+ break;
+ default:
+ /* Get the field description, and add it as an anonymous attr? */
+ /* OR WHAT???? */
+ {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+
+ NS_Free(pVal);
+
+ return rv;
+}
+
+
+nsresult nsImportFieldMap::Allocate(int32_t newSize)
+{
+ if (newSize <= m_allocated)
+ return NS_OK;
+
+ int32_t sz = m_allocated;
+ while (sz < newSize)
+ sz += 30;
+
+ int32_t *pData = new int32_t[ sz];
+ if (!pData)
+ return NS_ERROR_OUT_OF_MEMORY;
+ bool *pActive = new bool[sz];
+ if (!pActive) {
+ delete [] pData;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ int32_t i;
+ for (i = 0; i < sz; i++) {
+ pData[i] = -1;
+ pActive[i] = true;
+ }
+ if (m_numFields) {
+ for (i = 0; i < m_numFields; i++) {
+ pData[i] = m_pFields[i];
+ pActive[i] = m_pActive[i];
+ }
+ delete [] m_pFields;
+ delete [] m_pActive;
+ }
+ m_allocated = sz;
+ m_pFields = pData;
+ m_pActive = pActive;
+ return NS_OK;
+}
diff --git a/mailnews/import/src/nsImportFieldMap.h b/mailnews/import/src/nsImportFieldMap.h
new file mode 100644
index 000000000..314abd800
--- /dev/null
+++ b/mailnews/import/src/nsImportFieldMap.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImportFieldMap_h___
+#define nsImportFieldMap_h___
+
+#include "nscore.h"
+#include "nsIImportFieldMap.h"
+#include "nsIAddrDatabase.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+
+////////////////////////////////////////////////////////////////////////
+
+class nsIStringBundle;
+
+class nsImportFieldMap : public nsIImportFieldMap
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIIMPORTFIELDMAP
+
+ explicit nsImportFieldMap(nsIStringBundle *aBundle);
+
+ static NS_METHOD Create(nsIStringBundle *aBundle, nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+private:
+ virtual ~nsImportFieldMap();
+ nsresult Allocate(int32_t newSize);
+
+private:
+ int32_t m_numFields;
+ int32_t * m_pFields;
+ bool * m_pActive;
+ int32_t m_allocated;
+ nsTArray<nsString*> m_descriptions;
+ int32_t m_mozFieldCount;
+ bool m_skipFirstRecord;
+};
+
+
+#endif
diff --git a/mailnews/import/src/nsImportMail.cpp b/mailnews/import/src/nsImportMail.cpp
new file mode 100644
index 000000000..9e0f6bcd2
--- /dev/null
+++ b/mailnews/import/src/nsImportMail.cpp
@@ -0,0 +1,1208 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+*/
+
+#include "prthread.h"
+#include "prprf.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+
+#include "nsIImportMail.h"
+#include "nsIImportGeneric.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIImportMailboxDescriptor.h"
+
+#include "nsStringGlue.h"
+#include "nsUnicharUtils.h"
+
+#include "nsMsgUtils.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgFolder.h"
+#include "nsImportStringBundle.h"
+#include "nsIStringBundle.h"
+#include "nsTextFormatter.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIImportService.h"
+#include "ImportDebug.h"
+#include "plstr.h"
+#include "MailNewsTypes.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+
+#define IMPORT_MSGS_URL "chrome://messenger/locale/importMsgs.properties"
+
+////////////////////////////////////////////////////////////////////////
+
+static void ImportMailThread(void *stuff);
+
+class ImportThreadData;
+
+class nsImportGenericMail : public nsIImportGeneric
+{
+public:
+
+ nsImportGenericMail();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /* nsISupports GetData (in string dataId); */
+ NS_IMETHOD GetData(const char *dataId, nsISupports **_retval) override;
+
+ NS_IMETHOD SetData(const char *dataId, nsISupports *pData) override;
+
+ NS_IMETHOD GetStatus(const char *statusKind, int32_t *_retval) override;
+
+ NS_IMETHOD WantsProgress(bool *_retval) override;
+
+ NS_IMETHODIMP BeginImport(nsISupportsString *successLog, nsISupportsString *errorLog, bool *_retval) override;
+
+ NS_IMETHOD ContinueImport(bool *_retval) override;
+
+ NS_IMETHOD GetProgress(int32_t *_retval) override;
+
+ NS_IMETHOD CancelImport(void) override;
+
+private:
+ virtual ~nsImportGenericMail();
+ bool CreateFolder(nsIMsgFolder **ppFolder);
+ void GetDefaultMailboxes(void);
+ void GetDefaultLocation(void);
+ void GetDefaultDestination(void);
+ void GetMailboxName(uint32_t index, nsISupportsString *pStr);
+
+public:
+ static void SetLogs(nsString& success, nsString& error, nsISupportsString *pSuccess, nsISupportsString *pError);
+ static void ReportError(int32_t id, const char16_t *pName, nsString *pStream, nsIStringBundle* aBundle);
+
+private:
+ nsString m_pName; // module name that created this interface
+ nsIMsgFolder * m_pDestFolder;
+ bool m_deleteDestFolder;
+ bool m_createdFolder;
+ nsCOMPtr <nsIFile> m_pSrcLocation;
+ bool m_gotLocation;
+ bool m_found;
+ bool m_userVerify;
+ nsIImportMail *m_pInterface;
+ nsIArray * m_pMailboxes;
+ nsISupportsString *m_pSuccessLog;
+ nsISupportsString *m_pErrorLog;
+ uint32_t m_totalSize;
+ bool m_doImport;
+ ImportThreadData * m_pThreadData;
+ bool m_performingMigration;
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+};
+
+class ImportThreadData {
+public:
+ bool driverAlive;
+ bool threadAlive;
+ bool abort;
+ bool fatalError;
+ uint32_t currentTotal;
+ uint32_t currentSize;
+ nsIMsgFolder * destRoot;
+ bool ownsDestRoot;
+ nsIArray *boxes;
+ nsIImportMail * mailImport;
+ nsISupportsString * successLog;
+ nsISupportsString * errorLog;
+ uint32_t currentMailbox;
+ bool performingMigration;
+ nsIStringBundle *stringBundle;
+
+ ImportThreadData();
+ ~ImportThreadData();
+ void DriverDelete();
+ void ThreadDelete();
+ void DriverAbort();
+};
+
+// forward decl for proxy methods
+nsresult ProxyGetSubFolders(nsIMsgFolder *aFolder);
+nsresult ProxyGetChildNamed(nsIMsgFolder *aFolder,const nsAString & aName,
+ nsIMsgFolder **aChild);
+nsresult ProxyGetParent(nsIMsgFolder *aFolder, nsIMsgFolder **aParent);
+nsresult ProxyContainsChildNamed(nsIMsgFolder *aFolder, const nsAString &aName,
+ bool *aResult);
+nsresult ProxyGenerateUniqueSubfolderName(nsIMsgFolder *aFolder,
+ const nsAString& aPrefix,
+ nsIMsgFolder *aOtherFolder,
+ nsAString& aName);
+nsresult ProxyCreateSubfolder(nsIMsgFolder *aFolder, const nsAString &aName);
+nsresult ProxyForceDBClosed(nsIMsgFolder *aFolder);
+
+nsresult NS_NewGenericMail(nsIImportGeneric** aImportGeneric)
+{
+ NS_PRECONDITION(aImportGeneric != nullptr, "null ptr");
+ if (! aImportGeneric)
+ return NS_ERROR_NULL_POINTER;
+
+ nsImportGenericMail *pGen = new nsImportGenericMail();
+
+ if (pGen == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(pGen);
+ nsresult rv = pGen->QueryInterface(NS_GET_IID(nsIImportGeneric), (void **)aImportGeneric);
+ NS_RELEASE(pGen);
+
+ return rv;
+}
+
+nsImportGenericMail::nsImportGenericMail()
+{
+ m_found = false;
+ m_userVerify = false;
+ m_gotLocation = false;
+ m_pInterface = nullptr;
+ m_pMailboxes = nullptr;
+ m_pSuccessLog = nullptr;
+ m_pErrorLog = nullptr;
+ m_totalSize = 0;
+ m_doImport = false;
+ m_pThreadData = nullptr;
+
+ m_pDestFolder = nullptr;
+ m_deleteDestFolder = false;
+ m_createdFolder = false;
+ m_performingMigration = false;
+
+ // Init logging module.
+ if (!IMPORTLOGMODULE)
+ IMPORTLOGMODULE = PR_NewLogModule("IMPORT");
+
+ nsresult rv = nsImportStringBundle::GetStringBundle(IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle));
+ if (NS_FAILED(rv))
+ IMPORT_LOG0("Failed to get string bundle for Importing Mail");
+}
+
+
+nsImportGenericMail::~nsImportGenericMail()
+{
+ if (m_pThreadData) {
+ m_pThreadData->DriverAbort();
+ m_pThreadData = nullptr;
+ }
+
+ NS_IF_RELEASE(m_pDestFolder);
+ NS_IF_RELEASE(m_pInterface);
+ NS_IF_RELEASE(m_pMailboxes);
+ NS_IF_RELEASE(m_pSuccessLog);
+ NS_IF_RELEASE(m_pErrorLog);
+}
+
+
+
+NS_IMPL_ISUPPORTS(nsImportGenericMail, nsIImportGeneric)
+
+
+NS_IMETHODIMP nsImportGenericMail::GetData(const char *dataId, nsISupports **_retval)
+{
+ nsresult rv = NS_OK;
+
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = nullptr;
+ if (!PL_strcasecmp(dataId, "mailInterface")) {
+ *_retval = m_pInterface;
+ NS_IF_ADDREF(m_pInterface);
+ }
+
+ if (!PL_strcasecmp(dataId, "mailBoxes")) {
+ if (!m_pMailboxes)
+ GetDefaultMailboxes();
+ *_retval = m_pMailboxes;
+ NS_IF_ADDREF(m_pMailboxes);
+ }
+
+ if (!PL_strcasecmp(dataId, "mailLocation")) {
+ if (!m_pSrcLocation)
+ GetDefaultLocation();
+ NS_IF_ADDREF(*_retval = m_pSrcLocation);
+ }
+
+ if (!PL_strcasecmp(dataId, "mailDestination")) {
+ if (!m_pDestFolder)
+ GetDefaultDestination();
+ NS_IF_ADDREF(*_retval = m_pDestFolder);
+ }
+
+ if (!PL_strcasecmp(dataId, "migration")) {
+ nsCOMPtr<nsISupportsPRBool> migrationString = do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ migrationString->SetData(m_performingMigration);
+ NS_IF_ADDREF(*_retval = migrationString);
+ }
+
+ if (!PL_strcasecmp(dataId, "currentMailbox")) {
+ // create an nsISupportsString, get the current mailbox
+ // name being imported and put it in the string
+ nsCOMPtr<nsISupportsString> data = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ if (m_pThreadData) {
+ GetMailboxName(m_pThreadData->currentMailbox, data);
+ }
+ NS_ADDREF(*_retval = data);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImportGenericMail::SetData(const char *dataId, nsISupports *item)
+{
+ nsresult rv = NS_OK;
+ NS_PRECONDITION(dataId != nullptr, "null ptr");
+ if (!dataId)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!PL_strcasecmp(dataId, "mailInterface")) {
+ NS_IF_RELEASE(m_pInterface);
+ if (item)
+ item->QueryInterface(NS_GET_IID(nsIImportMail), (void **) &m_pInterface);
+ }
+ if (!PL_strcasecmp(dataId, "mailBoxes")) {
+ NS_IF_RELEASE(m_pMailboxes);
+ if (item)
+ item->QueryInterface(NS_GET_IID(nsIArray), (void **) &m_pMailboxes);
+ }
+
+ if (!PL_strcasecmp(dataId, "mailLocation")) {
+ NS_IF_RELEASE(m_pMailboxes);
+ m_pSrcLocation = nullptr;
+ if (item) {
+ nsresult rv;
+ nsCOMPtr <nsIFile> location = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_pSrcLocation = location;
+ }
+ }
+
+ if (!PL_strcasecmp(dataId, "mailDestination")) {
+ NS_IF_RELEASE(m_pDestFolder);
+ if (item)
+ item->QueryInterface(NS_GET_IID(nsIMsgFolder), (void **) &m_pDestFolder);
+ m_deleteDestFolder = false;
+ }
+
+ if (!PL_strcasecmp(dataId, "name")) {
+ nsCOMPtr<nsISupportsString> nameString;
+ if (item) {
+ item->QueryInterface(NS_GET_IID(nsISupportsString), getter_AddRefs(nameString));
+ rv = nameString->GetData(m_pName);
+ }
+ }
+
+ if (!PL_strcasecmp(dataId, "migration")) {
+ nsCOMPtr<nsISupportsPRBool> migrationString;
+ if (item) {
+ item->QueryInterface(NS_GET_IID(nsISupportsPRBool), getter_AddRefs(migrationString));
+ rv = migrationString->GetData(&m_performingMigration);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImportGenericMail::GetStatus(const char *statusKind, int32_t *_retval)
+{
+ NS_PRECONDITION(statusKind != nullptr, "null ptr");
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!statusKind || !_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = 0;
+
+ if (!PL_strcasecmp(statusKind, "isInstalled")) {
+ GetDefaultLocation();
+ *_retval = (int32_t) m_found;
+ }
+
+ if (!PL_strcasecmp(statusKind, "canUserSetLocation")) {
+ GetDefaultLocation();
+ *_retval = (int32_t) m_userVerify;
+ }
+
+ return NS_OK;
+}
+
+
+void nsImportGenericMail::GetDefaultLocation(void)
+{
+ if (!m_pInterface)
+ return;
+
+ if (m_pSrcLocation && m_gotLocation)
+ return;
+
+ m_gotLocation = true;
+
+ nsCOMPtr <nsIFile> pLoc;
+ m_pInterface->GetDefaultLocation(getter_AddRefs(pLoc), &m_found, &m_userVerify);
+ if (!m_pSrcLocation)
+ m_pSrcLocation = pLoc;
+}
+
+void nsImportGenericMail::GetDefaultMailboxes(void)
+{
+ if (!m_pInterface || m_pMailboxes || !m_pSrcLocation)
+ return;
+
+ m_pInterface->FindMailboxes(m_pSrcLocation, &m_pMailboxes);
+}
+
+void nsImportGenericMail::GetDefaultDestination(void)
+{
+ if (m_pDestFolder)
+ return;
+ if (!m_pInterface)
+ return;
+
+ nsIMsgFolder * rootFolder;
+ m_deleteDestFolder = false;
+ m_createdFolder = false;
+ if (CreateFolder(&rootFolder)) {
+ m_pDestFolder = rootFolder;
+ m_deleteDestFolder = true;
+ m_createdFolder = true;
+ return;
+ }
+ IMPORT_LOG0("*** GetDefaultDestination: Failed to create a default import destination folder.");
+}
+
+NS_IMETHODIMP nsImportGenericMail::WantsProgress(bool *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (m_pThreadData) {
+ m_pThreadData->DriverAbort();
+ m_pThreadData = nullptr;
+ }
+
+ if (!m_pMailboxes) {
+ GetDefaultLocation();
+ GetDefaultMailboxes();
+ }
+
+ if (!m_pDestFolder) {
+ GetDefaultDestination();
+ }
+
+ bool result = false;
+
+ if (m_pMailboxes) {
+ uint32_t i;
+ bool import;
+ uint32_t count = 0;
+ uint32_t size;
+ uint32_t totalSize = 0;
+
+ (void) m_pMailboxes->GetLength(&count);
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsIImportMailboxDescriptor> box =
+ do_QueryElementAt(m_pMailboxes, i);
+ if (box) {
+ import = false;
+ size = 0;
+ nsresult rv = box->GetImport(&import);
+ if (NS_SUCCEEDED(rv) && import) {
+ (void) box->GetSize(&size);
+ result = true;
+ }
+ totalSize += size;
+ }
+ }
+
+ m_totalSize = totalSize;
+ }
+
+ m_doImport = result;
+
+ *_retval = result;
+
+ return NS_OK;
+}
+
+void nsImportGenericMail::GetMailboxName(uint32_t index, nsISupportsString *pStr)
+{
+ if (m_pMailboxes) {
+ nsCOMPtr<nsIImportMailboxDescriptor> box(do_QueryElementAt(m_pMailboxes, index));
+ if (box) {
+ nsAutoString name;
+ box->GetDisplayName(getter_Copies(name));
+ if (!name.IsEmpty()) {
+ pStr->SetData(name);
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP nsImportGenericMail::BeginImport(nsISupportsString *successLog, nsISupportsString *errorLog, bool *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ nsString success;
+ nsString error;
+
+ if (!m_doImport) {
+ nsImportStringBundle::GetStringByID(IMPORT_NO_MAILBOXES,
+ m_stringBundle, success);
+ SetLogs(success, error, successLog, errorLog);
+ *_retval = true;
+ return NS_OK;
+ }
+
+ if (!m_pInterface || !m_pMailboxes) {
+ IMPORT_LOG0("*** BeginImport: Either the interface or source mailbox is not set properly.");
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NOTINITIALIZED,
+ m_stringBundle, error);
+ SetLogs(success, error, successLog, errorLog);
+ *_retval = false;
+ return NS_OK;
+ }
+
+ if (!m_pDestFolder) {
+ IMPORT_LOG0("*** BeginImport: The destination mailbox is not set properly.");
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NODESTFOLDER,
+ m_stringBundle, error);
+ SetLogs(success, error, successLog, errorLog);
+ *_retval = false;
+ return NS_OK;
+ }
+
+ if (m_pThreadData) {
+ m_pThreadData->DriverAbort();
+ m_pThreadData = nullptr;
+ }
+
+ NS_IF_RELEASE(m_pSuccessLog);
+ NS_IF_RELEASE(m_pErrorLog);
+ m_pSuccessLog = successLog;
+ m_pErrorLog = errorLog;
+ NS_IF_ADDREF(m_pSuccessLog);
+ NS_IF_ADDREF(m_pErrorLog);
+
+
+ // kick off the thread to do the import!!!!
+ m_pThreadData = new ImportThreadData();
+ m_pThreadData->boxes = m_pMailboxes;
+ NS_ADDREF(m_pMailboxes);
+ m_pThreadData->mailImport = m_pInterface;
+ NS_ADDREF(m_pInterface);
+ m_pThreadData->errorLog = m_pErrorLog;
+ NS_IF_ADDREF(m_pErrorLog);
+ m_pThreadData->successLog = m_pSuccessLog;
+ NS_IF_ADDREF(m_pSuccessLog);
+
+ m_pThreadData->ownsDestRoot = m_deleteDestFolder;
+ m_pThreadData->destRoot = m_pDestFolder;
+ m_pThreadData->performingMigration = m_performingMigration;
+ NS_IF_ADDREF(m_pDestFolder);
+
+ NS_IF_ADDREF(m_pThreadData->stringBundle = m_stringBundle);
+
+ PRThread *pThread = PR_CreateThread(PR_USER_THREAD, &ImportMailThread, m_pThreadData,
+ PR_PRIORITY_NORMAL,
+ PR_LOCAL_THREAD,
+ PR_UNJOINABLE_THREAD,
+ 0);
+ if (!pThread) {
+ m_pThreadData->ThreadDelete();
+ m_pThreadData->abort = true;
+ m_pThreadData->DriverAbort();
+ m_pThreadData = nullptr;
+ *_retval = false;
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NOTHREAD,
+ m_stringBundle, error);
+ SetLogs(success, error, successLog, errorLog);
+ }
+ else
+ *_retval = true;
+
+ return NS_OK;
+
+}
+
+
+NS_IMETHODIMP nsImportGenericMail::ContinueImport(bool *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = true;
+ if (m_pThreadData) {
+ if (m_pThreadData->fatalError)
+ *_retval = false;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImportGenericMail::GetProgress(int32_t *_retval)
+{
+ // This returns the progress from the the currently
+ // running import mail or import address book thread.
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!m_pThreadData || !(m_pThreadData->threadAlive)) {
+ *_retval = 100;
+ return NS_OK;
+ }
+
+ uint32_t sz = 0;
+ if (m_pThreadData->currentSize && m_pInterface) {
+ if (NS_FAILED(m_pInterface->GetImportProgress(&sz)))
+ sz = 0;
+ }
+
+
+ // *_retval = (int32_t) (((uint32_t)(m_pThreadData->currentTotal + sz) * (uint32_t)100) / m_totalSize);
+
+ if (m_totalSize) {
+ double perc;
+ perc = (double) m_pThreadData->currentTotal;
+ perc += sz;
+ perc *= 100;
+ perc /= m_totalSize;
+ *_retval = (int32_t) perc;
+ if (*_retval > 100)
+ *_retval = 100;
+ }
+ else
+ *_retval = 0;
+
+ // never return 100% while the thread is still alive
+ if (*_retval > 99)
+ *_retval = 99;
+
+ return NS_OK;
+}
+
+void nsImportGenericMail::ReportError(int32_t id, const char16_t *pName, nsString *pStream, nsIStringBundle *aBundle)
+{
+ if (!pStream)
+ return;
+
+ // load the error string
+ char16_t *pFmt = nsImportStringBundle::GetStringByID(id, aBundle);
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, pName);
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ NS_Free(pFmt);
+ pStream->Append(NS_ConvertASCIItoUTF16(MSG_LINEBREAK));
+}
+
+
+void nsImportGenericMail::SetLogs(nsString& success, nsString& error, nsISupportsString *pSuccess, nsISupportsString *pError)
+{
+ nsAutoString str;
+ if (pSuccess) {
+ pSuccess->GetData(str);
+ str.Append(success);
+ pSuccess->SetData(str);
+ }
+ if (pError) {
+ pError->GetData(str);
+ str.Append(error);
+ pError->SetData(str);
+ }
+}
+
+NS_IMETHODIMP nsImportGenericMail::CancelImport(void)
+{
+ if (m_pThreadData) {
+ m_pThreadData->abort = true;
+ m_pThreadData->DriverAbort();
+ m_pThreadData = nullptr;
+ }
+
+ return NS_OK;
+}
+
+
+ImportThreadData::ImportThreadData()
+{
+ fatalError = false;
+ driverAlive = true;
+ threadAlive = true;
+ abort = false;
+ currentTotal = 0;
+ currentSize = 0;
+ destRoot = nullptr;
+ ownsDestRoot = false;
+ boxes = nullptr;
+ mailImport = nullptr;
+ successLog = nullptr;
+ errorLog = nullptr;
+ stringBundle = nullptr;
+}
+
+ImportThreadData::~ImportThreadData()
+{
+ NS_IF_RELEASE(destRoot);
+ NS_IF_RELEASE(boxes);
+ NS_IF_RELEASE(mailImport);
+ NS_IF_RELEASE(errorLog);
+ NS_IF_RELEASE(successLog);
+ NS_IF_RELEASE(stringBundle);
+}
+
+void ImportThreadData::DriverDelete(void)
+{
+ driverAlive = false;
+ if (!driverAlive && !threadAlive)
+ delete this;
+}
+
+void ImportThreadData::ThreadDelete()
+{
+ threadAlive = false;
+ if (!driverAlive && !threadAlive)
+ delete this;
+}
+
+void ImportThreadData::DriverAbort()
+{
+ if (abort && !threadAlive && destRoot) {
+ if (ownsDestRoot) {
+ destRoot->RecursiveDelete(true, nullptr);
+ }
+ else {
+ // FIXME: just delete the stuff we created?
+ }
+ }
+ else
+ abort = true;
+ DriverDelete();
+}
+
+
+
+static void
+ImportMailThread(void *stuff)
+{
+ ImportThreadData *pData = (ImportThreadData *)stuff;
+
+ IMPORT_LOG0("ImportMailThread: Starting...");
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> destRoot(pData->destRoot);
+
+ uint32_t count = 0;
+ rv = pData->boxes->GetLength(&count);
+
+ uint32_t i;
+ bool import;
+ uint32_t size;
+ uint32_t depth = 1;
+ uint32_t newDepth;
+ nsString lastName;
+ char16_t * pName;
+
+ nsCOMPtr<nsIMsgFolder> curFolder(destRoot);
+
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ nsCOMPtr<nsIMsgFolder> subFolder;
+
+ bool exists;
+
+ nsString success;
+ nsString error;
+
+ // GetSubFolders() will initialize folders if they are not already initialized.
+ ProxyGetSubFolders(curFolder);
+
+ IMPORT_LOG1("ImportMailThread: Total number of folders to import = %d.", count);
+
+ // Note that the front-end js script only displays one import result string so
+ // we combine both good and bad import status into one string (in var 'success').
+
+ for (i = 0; (i < count) && !(pData->abort); i++) {
+ nsCOMPtr<nsIImportMailboxDescriptor> box =
+ do_QueryElementAt(pData->boxes, i);
+ if (box) {
+ pData->currentMailbox = i;
+
+ import = false;
+ size = 0;
+ rv = box->GetImport(&import);
+ if (import)
+ rv = box->GetSize(&size);
+ rv = box->GetDepth(&newDepth);
+ if (newDepth > depth) {
+ // OK, we are going to add a subfolder under the last/previous folder we processed, so
+ // find this folder (stored in 'lastName') who is going to be the new parent folder.
+ IMPORT_LOG1("ImportMailThread: Processing child folder '%s'.", NS_ConvertUTF16toUTF8(lastName).get());
+ rv = ProxyGetChildNamed(curFolder, lastName, getter_AddRefs(subFolder));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("*** ImportMailThread: Failed to get the interface for child folder '%s'.", NS_ConvertUTF16toUTF8(lastName).get());
+ nsImportGenericMail::ReportError(IMPORT_ERROR_MB_FINDCHILD,
+ lastName.get(),
+ &error, pData->stringBundle);
+ pData->fatalError = true;
+ break;
+ }
+ curFolder = subFolder;
+ // Make sure this new parent folder obj has the correct subfolder list so far.
+ rv = ProxyGetSubFolders(curFolder);
+ }
+ else if (newDepth < depth) {
+ rv = NS_OK;
+ while ((newDepth < depth) && NS_SUCCEEDED(rv)) {
+ rv = curFolder->GetParent(getter_AddRefs(curFolder));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("*** ImportMailThread: Failed to get the interface for parent folder '%s'.", lastName.get());
+ nsImportGenericMail::ReportError(IMPORT_ERROR_MB_FINDCHILD,
+ lastName.get(), &error,
+ pData->stringBundle);
+ pData->fatalError = true;
+ break;
+ }
+ depth--;
+ }
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("*** ImportMailThread: Failed to get the proxy interface for parent folder '%s'.", lastName.get());
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NOPROXY,
+ pData->stringBundle, error);
+ pData->fatalError = true;
+ break;
+ }
+ }
+ depth = newDepth;
+ pName = nullptr;
+ box->GetDisplayName(&pName);
+ if (pName) {
+ lastName = pName;
+ NS_Free(pName);
+ }
+ else
+ lastName.AssignLiteral("Unknown!");
+
+ // translate the folder name if we are doing migration, but
+ // only for special folders which are at the root level
+ if (pData->performingMigration && depth == 1)
+ pData->mailImport->TranslateFolderName(lastName, lastName);
+
+ exists = false;
+ rv = ProxyContainsChildNamed(curFolder, lastName, &exists);
+
+ // If we are performing profile migration (as opposed to importing) then we are starting
+ // with empty local folders. In that case, always choose to over-write the existing local folder
+ // with this name. Don't create a unique subfolder name. Otherwise you end up with "Inbox, Inbox0"
+ // or "Unsent Folders, UnsentFolders0"
+ if (exists && !pData->performingMigration) {
+ nsString subName;
+ ProxyGenerateUniqueSubfolderName(curFolder, lastName, nullptr, subName);
+ if (!subName.IsEmpty())
+ lastName.Assign(subName);
+ }
+
+ IMPORT_LOG1("ImportMailThread: Creating new import folder '%s'.", NS_ConvertUTF16toUTF8(lastName).get());
+ ProxyCreateSubfolder(curFolder, lastName); // this may fail if the folder already exists..that's ok
+
+ rv = ProxyGetChildNamed(curFolder, lastName, getter_AddRefs(newFolder));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("*** ImportMailThread: Failed to locate subfolder '%s' after it's been created.", lastName.get());
+ nsImportGenericMail::ReportError(IMPORT_ERROR_MB_CREATE, lastName.get(),
+ &error, pData->stringBundle);
+ }
+
+ if (size && import && newFolder && NS_SUCCEEDED(rv)) {
+ bool fatalError = false;
+ pData->currentSize = size;
+ char16_t *pSuccess = nullptr;
+ char16_t *pError = nullptr;
+ rv = pData->mailImport->ImportMailbox(box, newFolder, &pError, &pSuccess, &fatalError);
+ if (pError) {
+ error.Append(pError);
+ NS_Free(pError);
+ }
+ if (pSuccess) {
+ success.Append(pSuccess);
+ NS_Free(pSuccess);
+ }
+
+ pData->currentSize = 0;
+ pData->currentTotal += size;
+
+ // commit to the db synchronously, but using a proxy since it doesn't like being used
+ // elsewhere than from the main thread.
+ // OK, we've copied the actual folder/file over if the folder size is not 0
+ // (ie, the msg summary is no longer valid) so close the msg database so that
+ // when the folder is reopened the folder db can be reconstructed (which
+ // validates msg summary and forces folder to be reparsed).
+ rv = ProxyForceDBClosed(newFolder);
+ fatalError = NS_FAILED(rv);
+
+ if (fatalError) {
+ IMPORT_LOG1("*** ImportMailThread: ImportMailbox returned fatalError, mailbox #%d\n", (int) i);
+ pData->fatalError = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // Now save the new acct info to pref file.
+ nsCOMPtr <nsIMsgAccountManager> accMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && accMgr) {
+ rv = accMgr->SaveAccountInfo();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file");
+ }
+
+ nsImportGenericMail::SetLogs(success, error, pData->successLog, pData->errorLog);
+
+ if (pData->abort || pData->fatalError) {
+ IMPORT_LOG0("*** ImportMailThread: Abort or fatalError flag was set\n");
+ if (pData->ownsDestRoot) {
+ IMPORT_LOG0("Calling destRoot->RecursiveDelete\n");
+ destRoot->RecursiveDelete(true, nullptr);
+ }
+ else {
+ // FIXME: just delete the stuff we created?
+ }
+ }
+
+ IMPORT_LOG1("Import mailbox thread done: %d\n", (int) pData->currentTotal);
+
+ pData->ThreadDelete();
+
+}
+
+// Creates a folder in Local Folders with the module name + mail
+// for e.g: Outlook Mail
+bool nsImportGenericMail::CreateFolder(nsIMsgFolder **ppFolder)
+{
+ nsresult rv;
+ *ppFolder = nullptr;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService)
+ return false;
+ rv = bundleService->CreateBundle(IMPORT_MSGS_URL, getter_AddRefs(bundle));
+ if (NS_FAILED(rv))
+ return false;
+ nsString folderName;
+ if (!m_pName.IsEmpty()) {
+ const char16_t *moduleName[] = { m_pName.get() };
+ rv = bundle->FormatStringFromName(u"ImportModuleFolderName",
+ moduleName, 1,
+ getter_Copies(folderName));
+ }
+ else {
+ rv = bundle->GetStringFromName(u"DefaultFolderName",
+ getter_Copies(folderName));
+ }
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to get Folder Name!\n");
+ return false;
+ }
+ nsCOMPtr <nsIMsgAccountManager> accMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create account manager!\n");
+ return false;
+ }
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = accMgr->GetLocalFoldersServer(getter_AddRefs(server));
+ // if Local Folders does not exist already, create it
+ if (NS_FAILED(rv) || !server)
+ {
+ rv = accMgr->CreateLocalMailAccount();
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create Local Folders!\n");
+ return false;
+ }
+
+ rv = accMgr->GetLocalFoldersServer(getter_AddRefs(server));
+ }
+
+ if (NS_SUCCEEDED(rv) && server) {
+ nsCOMPtr <nsIMsgFolder> localRootFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(localRootFolder));
+ if (localRootFolder) {
+ // we need to call GetSubFolders() so that the folders get initialized
+ // if they are not initialized yet.
+ nsCOMPtr<nsISimpleEnumerator> aEnumerator;
+ rv = localRootFolder->GetSubFolders(getter_AddRefs(aEnumerator));
+ if (NS_SUCCEEDED(rv)) {
+ // check if the folder name we picked already exists.
+ bool exists = false;
+ rv = localRootFolder->ContainsChildNamed(folderName, &exists);
+ if (exists) {
+ nsString name;
+ localRootFolder->GenerateUniqueSubfolderName(folderName, nullptr, name);
+ if (!name.IsEmpty())
+ folderName.Assign(name);
+ else {
+ IMPORT_LOG0("*** Failed to find a unique folder name!\n");
+ return false;
+ }
+ }
+ IMPORT_LOG1("Creating folder for importing mail: '%s'\n", NS_ConvertUTF16toUTF8(folderName).get());
+
+ // Bug 564162 identifies a dataloss design flaw.
+ // A working Thunderbird client can have mail in Local Folders and a
+ // subsequent import 'Everything' will trigger a migration which
+ // overwrites existing mailboxes with the imported mailboxes.
+ rv = localRootFolder->CreateSubfolder(folderName, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ rv = localRootFolder->GetChildNamed(folderName, ppFolder);
+ if (*ppFolder) {
+ IMPORT_LOG1("Folder '%s' created successfully\n", NS_ConvertUTF16toUTF8(folderName).get());
+ return true;
+ }
+ }
+ }
+ } // if localRootFolder
+ } // if server
+ IMPORT_LOG0("****** FAILED TO CREATE FOLDER FOR IMPORT\n");
+ return false;
+}
+
+/**
+ * These are the proxy objects we use to proxy nsIMsgFolder methods back
+ * the the main thread. Since there are only five, we can hand roll them.
+ * A better design might be a co-routine-ish design where the ui thread
+ * hands off each folder to the import thread and when the thread finishes
+ * the folder, the main thread hands it the next folder.
+ */
+
+class GetSubFoldersRunnable : public mozilla::Runnable
+{
+public:
+ explicit GetSubFoldersRunnable(nsIMsgFolder *aFolder);
+ NS_DECL_NSIRUNNABLE
+private:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+};
+
+GetSubFoldersRunnable::GetSubFoldersRunnable(nsIMsgFolder *aFolder) :
+ m_folder(aFolder)
+{
+}
+
+NS_IMETHODIMP GetSubFoldersRunnable::Run()
+{
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ return m_folder->GetSubFolders(getter_AddRefs(dummy));
+}
+
+
+nsresult ProxyGetSubFolders(nsIMsgFolder *aFolder)
+{
+ RefPtr<GetSubFoldersRunnable> getSubFolders =
+ new GetSubFoldersRunnable(aFolder);
+ return NS_DispatchToMainThread(getSubFolders, NS_DISPATCH_SYNC);
+}
+
+class GetChildNamedRunnable : public mozilla::Runnable
+{
+public:
+ GetChildNamedRunnable(nsIMsgFolder *aFolder, const nsAString& aName, nsIMsgFolder **aChild);
+ NS_DECL_NSIRUNNABLE
+protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsString m_name;
+ nsIMsgFolder **m_child;
+};
+
+GetChildNamedRunnable::GetChildNamedRunnable(nsIMsgFolder *aFolder,
+ const nsAString & aName,
+ nsIMsgFolder **aChild) :
+ m_folder(aFolder), m_name(aName), m_child(aChild)
+{
+}
+
+NS_IMETHODIMP GetChildNamedRunnable::Run()
+{
+ return m_folder->GetChildNamed(m_name, m_child);
+}
+
+
+nsresult ProxyGetChildNamed(nsIMsgFolder *aFolder, const nsAString & aName,
+ nsIMsgFolder **aChild)
+{
+ RefPtr<GetChildNamedRunnable> getChildNamed =
+ new GetChildNamedRunnable(aFolder, aName, aChild);
+ return NS_DispatchToMainThread(getChildNamed, NS_DISPATCH_SYNC);
+}
+
+class GetParentRunnable : public mozilla::Runnable
+{
+public:
+ GetParentRunnable(nsIMsgFolder *aFolder, nsIMsgFolder **aParent);
+ NS_DECL_NSIRUNNABLE
+protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsIMsgFolder **m_parent;
+};
+
+GetParentRunnable::GetParentRunnable(nsIMsgFolder *aFolder, nsIMsgFolder **aParent) :
+ m_folder(aFolder), m_parent(aParent)
+{
+}
+
+NS_IMETHODIMP GetParentRunnable::Run()
+{
+ return m_folder->GetParent(m_parent);
+}
+
+
+nsresult ProxyGetParent(nsIMsgFolder *aFolder, nsIMsgFolder **aParent)
+{
+ RefPtr<GetParentRunnable> getParent =
+ new GetParentRunnable(aFolder, aParent);
+ return NS_DispatchToMainThread(getParent, NS_DISPATCH_SYNC);
+}
+
+class ContainsChildNamedRunnable : public mozilla::Runnable
+{
+public:
+ ContainsChildNamedRunnable(nsIMsgFolder *aFolder, const nsAString& aName, bool *aResult);
+ NS_DECL_NSIRUNNABLE
+protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsString m_name;
+ bool *m_result;
+};
+
+ContainsChildNamedRunnable::ContainsChildNamedRunnable(nsIMsgFolder *aFolder,
+ const nsAString &aName,
+ bool *aResult) :
+ m_folder(aFolder), m_name(aName), m_result(aResult)
+{
+}
+
+NS_IMETHODIMP ContainsChildNamedRunnable::Run()
+{
+ return m_folder->ContainsChildNamed(m_name, m_result);
+}
+
+
+nsresult ProxyContainsChildNamed(nsIMsgFolder *aFolder, const nsAString &aName,
+ bool *aResult)
+{
+ RefPtr<ContainsChildNamedRunnable> containsChildNamed =
+ new ContainsChildNamedRunnable(aFolder, aName, aResult);
+ return NS_DispatchToMainThread(containsChildNamed, NS_DISPATCH_SYNC);
+}
+
+
+class GenerateUniqueSubfolderNameRunnable : public mozilla::Runnable
+{
+public:
+ GenerateUniqueSubfolderNameRunnable(nsIMsgFolder *aFolder,
+ const nsAString& prefix,
+ nsIMsgFolder *otherFolder,
+ nsAString& name);
+ NS_DECL_NSIRUNNABLE
+protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsString m_prefix;
+ nsCOMPtr<nsIMsgFolder> m_otherFolder;
+ nsString m_name;
+};
+
+GenerateUniqueSubfolderNameRunnable::GenerateUniqueSubfolderNameRunnable(
+ nsIMsgFolder *aFolder, const nsAString& aPrefix, nsIMsgFolder *aOtherFolder,
+ nsAString& aName)
+ : m_folder(aFolder), m_prefix(aPrefix), m_otherFolder(aOtherFolder), m_name(aName)
+{
+}
+
+NS_IMETHODIMP GenerateUniqueSubfolderNameRunnable::Run()
+{
+ return m_folder->GenerateUniqueSubfolderName(m_prefix, m_otherFolder, m_name);
+}
+
+
+nsresult ProxyGenerateUniqueSubfolderName(nsIMsgFolder *aFolder,
+ const nsAString& aPrefix,
+ nsIMsgFolder *aOtherFolder,
+ nsAString& aName)
+
+{
+ RefPtr<GenerateUniqueSubfolderNameRunnable> generateUniqueSubfolderName =
+ new GenerateUniqueSubfolderNameRunnable(aFolder, aPrefix, aOtherFolder, aName);
+ return NS_DispatchToMainThread(generateUniqueSubfolderName, NS_DISPATCH_SYNC);
+}
+
+class CreateSubfolderRunnable : public mozilla::Runnable
+{
+public:
+ CreateSubfolderRunnable(nsIMsgFolder *aFolder, const nsAString& aName);
+ NS_DECL_NSIRUNNABLE
+protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsString m_name;
+};
+
+CreateSubfolderRunnable::CreateSubfolderRunnable(nsIMsgFolder *aFolder,
+ const nsAString &aName) :
+ m_folder(aFolder), m_name(aName)
+{
+}
+
+NS_IMETHODIMP CreateSubfolderRunnable::Run()
+{
+ return m_folder->CreateSubfolder(m_name, nullptr);
+}
+
+
+nsresult ProxyCreateSubfolder(nsIMsgFolder *aFolder, const nsAString &aName)
+{
+ RefPtr<CreateSubfolderRunnable> createSubfolder =
+ new CreateSubfolderRunnable(aFolder, aName);
+ return NS_DispatchToMainThread(createSubfolder, NS_DISPATCH_SYNC);
+}
+
+class ForceDBClosedRunnable : public mozilla::Runnable
+{
+public:
+ explicit ForceDBClosedRunnable(nsIMsgFolder *aFolder);
+ NS_DECL_NSIRUNNABLE
+protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+};
+
+ForceDBClosedRunnable::ForceDBClosedRunnable(nsIMsgFolder *aFolder) :
+ m_folder(aFolder)
+{
+}
+
+NS_IMETHODIMP ForceDBClosedRunnable::Run()
+{
+ return m_folder->ForceDBClosed();
+}
+
+nsresult ProxyForceDBClosed(nsIMsgFolder *aFolder)
+{
+ RefPtr<ForceDBClosedRunnable> forceDBClosed =
+ new ForceDBClosedRunnable(aFolder);
+ return NS_DispatchToMainThread(forceDBClosed, NS_DISPATCH_SYNC);
+}
+
+
diff --git a/mailnews/import/src/nsImportMailboxDescriptor.cpp b/mailnews/import/src/nsImportMailboxDescriptor.cpp
new file mode 100644
index 000000000..ab0ea5db4
--- /dev/null
+++ b/mailnews/import/src/nsImportMailboxDescriptor.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nscore.h"
+#include "nsImportMailboxDescriptor.h"
+#include "nsComponentManagerUtils.h"
+
+////////////////////////////////////////////////////////////////////////
+
+
+
+NS_METHOD nsImportMailboxDescriptor::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsImportMailboxDescriptor *it = new nsImportMailboxDescriptor();
+ if (it == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(it);
+ nsresult rv = it->QueryInterface(aIID, aResult);
+ NS_RELEASE(it);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsImportMailboxDescriptor, nsIImportMailboxDescriptor)
+
+nsImportMailboxDescriptor::nsImportMailboxDescriptor()
+{
+ m_import = true;
+ m_size = 0;
+ m_depth = 0;
+ m_id = 0;
+ m_pFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+}
diff --git a/mailnews/import/src/nsImportMailboxDescriptor.h b/mailnews/import/src/nsImportMailboxDescriptor.h
new file mode 100644
index 000000000..1f4c30b31
--- /dev/null
+++ b/mailnews/import/src/nsImportMailboxDescriptor.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImportMailboxDescriptor_h___
+#define nsImportMailboxDescriptor_h___
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+
+////////////////////////////////////////////////////////////////////////
+
+
+class nsImportMailboxDescriptor : public nsIImportMailboxDescriptor
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD GetIdentifier(uint32_t *pIdentifier) override { *pIdentifier = m_id; return NS_OK;}
+ NS_IMETHOD SetIdentifier(uint32_t ident) override { m_id = ident; return NS_OK;}
+
+ /* attribute unsigned long depth; */
+ NS_IMETHOD GetDepth(uint32_t *pDepth) override { *pDepth = m_depth; return NS_OK;}
+ NS_IMETHOD SetDepth(uint32_t theDepth) override { m_depth = theDepth; return NS_OK;}
+
+ /* attribute unsigned long size; */
+ NS_IMETHOD GetSize(uint32_t *pSize) override { *pSize = m_size; return NS_OK;}
+ NS_IMETHOD SetSize(uint32_t theSize) override { m_size = theSize; return NS_OK;}
+
+ /* attribute wstring displayName; */
+ NS_IMETHOD GetDisplayName(char16_t **pName) override { *pName = ToNewUnicode(m_displayName); return NS_OK;}
+ NS_IMETHOD SetDisplayName(const char16_t * pName) override { m_displayName = pName; return NS_OK;}
+
+ /* attribute boolean import; */
+ NS_IMETHOD GetImport(bool *pImport) override { *pImport = m_import; return NS_OK;}
+ NS_IMETHOD SetImport(bool doImport) override { m_import = doImport; return NS_OK;}
+
+ /* readonly attribute nsIFile file; */
+ NS_IMETHOD GetFile(nsIFile * *aFile) override { if (m_pFile) { NS_ADDREF(*aFile = m_pFile); return NS_OK;} else return NS_ERROR_FAILURE; }
+
+
+
+ nsImportMailboxDescriptor();
+
+ static NS_METHOD Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+private:
+ virtual ~nsImportMailboxDescriptor() {}
+ uint32_t m_id; // used by creator of the structure
+ uint32_t m_depth; // depth in the hierarchy
+ nsString m_displayName;// name of this mailbox
+ nsCOMPtr <nsIFile> m_pFile; // source file (if applicable)
+ uint32_t m_size;
+ bool m_import; // import it or not?
+};
+
+
+#endif
diff --git a/mailnews/import/src/nsImportMimeEncode.cpp b/mailnews/import/src/nsImportMimeEncode.cpp
new file mode 100644
index 000000000..e13ea1f94
--- /dev/null
+++ b/mailnews/import/src/nsImportMimeEncode.cpp
@@ -0,0 +1,411 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "nsImportMimeEncode.h"
+
+#include "ImportCharSet.h"
+#include "ImportTranslate.h"
+
+#define kNoState 0
+#define kStartState 1
+#define kEncodeState 2
+#define kDoneState 3
+
+#define kEncodeBufferSz (8192 * 8)
+
+nsImportMimeEncode::nsImportMimeEncode()
+{
+ m_pOut = nullptr;
+ m_state = kNoState;
+ m_bytesProcessed = 0;
+ m_pInputBuf = nullptr;
+}
+
+nsImportMimeEncode::~nsImportMimeEncode()
+{
+ delete [] m_pInputBuf;
+}
+
+void nsImportMimeEncode::EncodeFile(nsIFile *pInFile, ImportOutFile *pOut, const char *pFileName, const char *pMimeType)
+{
+ m_fileName = pFileName;
+ m_mimeType = pMimeType;
+
+ m_pMimeFile = pInFile;
+
+ m_pOut = pOut;
+ m_state = kStartState;
+}
+
+void nsImportMimeEncode::CleanUp(void)
+{
+ CleanUpEncodeScan();
+}
+
+bool nsImportMimeEncode::SetUpEncode(void)
+{
+ nsCString errStr;
+ if (!m_pInputBuf) {
+ m_pInputBuf = new uint8_t[kEncodeBufferSz];
+ }
+
+ m_appleSingle = false;
+
+#ifdef _MAC_IMPORT_CODE
+ // First let's see just what kind of beast we have?
+ // For files with only a data fork and a known mime type
+ // proceed with normal mime encoding just as on the PC.
+ // For unknown mime types and files with both forks,
+ // encode as AppleSingle format.
+ if (m_filePath.GetMacFileSize(UFileLocation::eResourceFork) || !pMimeType) {
+ m_appleSingle = TRUE;
+ m_mimeType = "application/applefile";
+ }
+#endif
+
+ if (!InitEncodeScan(m_appleSingle, m_pMimeFile, m_fileName.get(), m_pInputBuf, kEncodeBufferSz)) {
+ return false;
+ }
+
+ m_state = kEncodeState;
+ m_lineLen = 0;
+
+ // Write out the boundary header
+ bool bResult = true;
+ bResult = m_pOut->WriteStr("Content-type: ");
+ if (bResult)
+ bResult = m_pOut->WriteStr(m_mimeType.get());
+
+#ifdef _MAC_IMPORT_CODE
+ // include the type an creator here
+ if (bResult)
+ bResult = m_pOut->WriteStr("; x-mac-type=\"");
+ U8 hex[8];
+ LongToHexBytes(m_filePath.GetFileType(), hex);
+ if (bResult)
+ bResult = m_pOut->WriteData(hex, 8);
+ LongToHexBytes(m_filePath.GetFileCreator(), hex);
+ if (bResult)
+ bResult = m_pOut->WriteStr("\"; x-mac-creator=\"");
+ if (bResult)
+ bResult = m_pOut->WriteData(hex, 8);
+ if (bResult)
+ bResult = m_pOut->WriteStr("\"");
+#endif
+
+ /*
+ if (bResult)
+ bResult = m_pOut->WriteStr(gMimeTypeFileName);
+ */
+ if (bResult)
+ bResult = m_pOut->WriteStr(";\x0D\x0A");
+
+ nsCString fName;
+ bool trans = TranslateFileName(m_fileName, fName);
+ if (bResult)
+ bResult = WriteFileName(fName, trans, "name");
+ if (bResult)
+ bResult = m_pOut->WriteStr("Content-transfer-encoding: base64");
+ if (bResult)
+ bResult = m_pOut->WriteEol();
+ if (bResult)
+ bResult = m_pOut->WriteStr("Content-Disposition: attachment;\x0D\x0A");
+ if (bResult)
+ bResult = WriteFileName(fName, trans, "filename");
+ if (bResult)
+ bResult = m_pOut->WriteEol();
+
+ if (!bResult) {
+ CleanUp();
+ }
+
+ return bResult;
+}
+
+bool nsImportMimeEncode::DoWork(bool *pDone)
+{
+ *pDone = false;
+ switch(m_state) {
+ case kNoState:
+ return false;
+ break;
+ case kStartState:
+ return SetUpEncode();
+ break;
+ case kEncodeState:
+ if (!Scan(pDone)) {
+ CleanUp();
+ return false;
+ }
+ if (*pDone) {
+ *pDone = false;
+ m_state = kDoneState;
+ }
+ break;
+ case kDoneState:
+ CleanUp();
+ m_state = kNoState;
+ *pDone = true;
+ break;
+ }
+
+ return true;
+}
+
+static uint8_t gBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+bool nsImportMimeEncode::ScanBuffer(bool *pDone)
+{
+
+ uint32_t pos = m_pos;
+ uint32_t start = pos;
+ uint8_t * pChar = m_pBuf + pos;
+ uint32_t max = m_bytesInBuf;
+ uint8_t byte[4];
+ uint32_t lineLen = m_lineLen;
+
+ while ((pos + 2) < max) {
+ // Encode 3 bytes
+ byte[0] = gBase64[*pChar >> 2];
+ byte[1] = gBase64[(((*pChar) & 0x3)<< 4) | (((*(pChar + 1)) & 0xF0) >> 4)];
+ pChar++;
+ byte[2] = gBase64[(((*pChar) & 0xF) << 2) | (((*(pChar + 1)) & 0xC0) >>6)];
+ pChar++;
+ byte[3] = gBase64[(*pChar) & 0x3F];
+ if (!m_pOut->WriteData(byte, 4))
+ return false;
+ pos += 3;
+ pChar++;
+ lineLen += 4;
+ if (lineLen > 71) {
+ if (!m_pOut->WriteEol())
+ return false;
+ lineLen = 0;
+ }
+ }
+
+ if ((pos < max) && m_eof) {
+ // Get the last few bytes!
+ byte[0] = gBase64[*pChar >> 2];
+ pos++;
+ if (pos < max) {
+ byte[1] = gBase64[(((*pChar) & 0x3)<< 4) | (((*(pChar + 1)) & 0xF0) >> 4)];
+ pChar++;
+ pos++;
+ if (pos < max) {
+ // Should be dead code!! (Then why is it here doofus?)
+ byte[2] = gBase64[(((*pChar) & 0xF) << 2) | (((*(pChar + 1)) & 0xC0) >>6)];
+ pChar++;
+ byte[3] = gBase64[(*pChar) & 0x3F];
+ pos++;
+ }
+ else {
+ byte[2] = gBase64[(((*pChar) & 0xF) << 2)];
+ byte[3] = '=';
+ }
+ }
+ else {
+ byte[1] = gBase64[(((*pChar) & 0x3)<< 4)];
+ byte[2] = '=';
+ byte[3] = '=';
+ }
+
+ if (!m_pOut->WriteData(byte, 4))
+ return false;
+ if (!m_pOut->WriteEol())
+ return false;
+ }
+ else if (m_eof) {
+ /*
+ byte[0] = '=';
+ if (!m_pOut->WriteData(byte, 1))
+ return FALSE;
+ */
+ if (!m_pOut->WriteEol())
+ return false;
+ }
+
+ m_lineLen = (int) lineLen;
+ m_pos = pos;
+ m_bytesProcessed += (pos - start);
+ return true;
+}
+
+bool nsImportMimeEncode::TranslateFileName(nsCString& inFile, nsCString& outFile)
+{
+ const uint8_t * pIn = (const uint8_t *) inFile.get();
+ int len = inFile.Length();
+
+ while (len) {
+ if (!ImportCharSet::IsUSAscii(*pIn))
+ break;
+ len--;
+ pIn++;
+ }
+ if (len) {
+ // non US ascii!
+ // assume this string needs translating...
+ if (!ImportTranslate::ConvertString(inFile, outFile, true)) {
+ outFile = inFile;
+ return false;
+ }
+ else {
+ return true;
+ }
+ }
+ else {
+ outFile = inFile;
+ return false;
+ }
+}
+
+bool nsImportMimeEncode::WriteFileName(nsCString& fName, bool wasTrans, const char *pTag)
+{
+ int tagNum = 0;
+ int idx = 0;
+ bool result = true;
+ int len;
+ nsCString numStr;
+
+ while ((((fName.Length() - idx) + strlen(pTag)) > 70) && result) {
+ len = 68 - strlen(pTag) - 5;
+ if (wasTrans) {
+ if (fName.CharAt(idx + len - 1) == '%')
+ len--;
+ else if (fName.CharAt(idx + len - 2) == '%')
+ len -= 2;
+ }
+
+ if (result)
+ result = m_pOut->WriteStr("\x09");
+ if (result)
+ result = m_pOut->WriteStr(pTag);
+ numStr = "*";
+ numStr.AppendInt(tagNum);
+ if (result)
+ result = m_pOut->WriteStr(numStr.get());
+ if (wasTrans && result)
+ result = m_pOut->WriteStr("*=");
+ else if (result)
+ result = m_pOut->WriteStr("=\"");
+ if (result)
+ result = m_pOut->WriteData(((const uint8_t *)fName.get()) + idx, len);
+ if (wasTrans && result)
+ result = m_pOut->WriteStr("\x0D\x0A");
+ else if (result)
+ result = m_pOut->WriteStr("\"\x0D\x0A");
+ idx += len;
+ tagNum++;
+ }
+
+ if (idx) {
+ if ((fName.Length() - idx) > 0) {
+ if (result)
+ result = m_pOut->WriteStr("\x09");
+ if (result)
+ result = m_pOut->WriteStr(pTag);
+ numStr = "*";
+ numStr.AppendInt(tagNum);
+ if (result)
+ result = m_pOut->WriteStr(numStr.get());
+ if (wasTrans && result)
+ result = m_pOut->WriteStr("*=");
+ else if (result)
+ result = m_pOut->WriteStr("=\"");
+ if (result)
+ result = m_pOut->WriteData(((const uint8_t *)fName.get()) + idx, fName.Length() - idx);
+ if (wasTrans && result)
+ result = m_pOut->WriteStr("\x0D\x0A");
+ else if (result)
+ result = m_pOut->WriteStr("\"\x0D\x0A");
+ }
+ }
+ else {
+ if (result)
+ result = m_pOut->WriteStr("\x09");
+ if (result)
+ result = m_pOut->WriteStr(pTag);
+ if (wasTrans && result)
+ result = m_pOut->WriteStr("*=");
+ else if (result)
+ result = m_pOut->WriteStr("=\"");
+ if (result)
+ result = m_pOut->WriteStr(fName.get());
+ if (wasTrans && result)
+ result = m_pOut->WriteStr("\x0D\x0A");
+ else if (result)
+ result = m_pOut->WriteStr("\"\x0D\x0A");
+ }
+
+ return result;
+
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+nsIImportMimeEncodeImpl::nsIImportMimeEncodeImpl()
+{
+ m_pOut = nullptr;
+ m_pEncode = nullptr;
+}
+
+nsIImportMimeEncodeImpl::~nsIImportMimeEncodeImpl()
+{
+ if (m_pOut)
+ delete m_pOut;
+ if (m_pEncode)
+ delete m_pEncode;
+}
+
+NS_IMPL_ISUPPORTS(nsIImportMimeEncodeImpl, nsIImportMimeEncode)
+
+NS_METHOD nsIImportMimeEncodeImpl::EncodeFile(nsIFile *inFile, nsIFile *outFile, const char *fileName, const char *mimeType)
+{
+ return Initialize(inFile, outFile, fileName, mimeType);
+}
+
+NS_METHOD nsIImportMimeEncodeImpl::DoWork(bool *done, bool *_retval)
+{
+ if (done && _retval && m_pEncode) {
+ *_retval = m_pEncode->DoWork(done);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_METHOD nsIImportMimeEncodeImpl::NumBytesProcessed(int32_t *_retval)
+{
+ if (m_pEncode && _retval)
+ *_retval = m_pEncode->NumBytesProcessed();
+ return NS_OK;
+}
+
+NS_METHOD nsIImportMimeEncodeImpl::DoEncoding(bool *_retval)
+{
+ if (_retval && m_pEncode) {
+ bool done = false;
+ while (m_pEncode->DoWork(&done) && !done);
+ *_retval = done;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_METHOD nsIImportMimeEncodeImpl::Initialize(nsIFile *inFile, nsIFile *outFile, const char *fileName, const char *mimeType)
+{
+ delete m_pEncode;
+ delete m_pOut;
+
+ m_pOut = new ImportOutFile();
+ m_pOut->InitOutFile(outFile);
+
+ m_pEncode = new nsImportMimeEncode();
+ m_pEncode->EncodeFile(inFile, m_pOut, fileName, mimeType);
+
+ return NS_OK;
+}
+
diff --git a/mailnews/import/src/nsImportMimeEncode.h b/mailnews/import/src/nsImportMimeEncode.h
new file mode 100644
index 000000000..1447d11c4
--- /dev/null
+++ b/mailnews/import/src/nsImportMimeEncode.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsImportMimeEncode_h__
+#define nsImportMimeEncode_h__
+
+#include "mozilla/Attributes.h"
+#include "nsImportScanFile.h"
+#include "ImportOutFile.h"
+#include "nsImportEncodeScan.h"
+#include "nsStringGlue.h"
+#include "nsIImportMimeEncode.h"
+
+
+// Content-Type: image/gif; name="blah.xyz"
+// Content-Transfer-Encoding: base64
+// Content-Disposition: attachment; filename="blah.xyz"
+
+class nsImportMimeEncode : public nsImportEncodeScan {
+public:
+ nsImportMimeEncode();
+ ~nsImportMimeEncode();
+
+ void EncodeFile(nsIFile *pInFile, ImportOutFile *pOut, const char *pFileName, const char *pMimeType);
+
+ bool DoWork(bool *pDone);
+
+ long NumBytesProcessed(void) { long val = m_bytesProcessed; m_bytesProcessed = 0; return val;}
+
+protected:
+ void CleanUp(void);
+ bool SetUpEncode(void);
+ bool WriteFileName(nsCString& fName, bool wasTrans, const char *pTag);
+ bool TranslateFileName(nsCString& inFile, nsCString& outFile);
+
+
+ virtual bool ScanBuffer(bool *pDone) override;
+
+
+protected:
+ nsCString m_fileName;
+ nsCOMPtr <nsIFile> m_pMimeFile;
+ ImportOutFile * m_pOut;
+ nsCString m_mimeType;
+
+ int m_state;
+ long m_bytesProcessed;
+ uint8_t * m_pInputBuf;
+ bool m_appleSingle;
+
+ // Actual encoding variables
+ int m_lineLen;
+};
+
+
+class nsIImportMimeEncodeImpl : public nsIImportMimeEncode {
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIIMPORTMIMEENCODE
+
+ nsIImportMimeEncodeImpl();
+
+private:
+ virtual ~nsIImportMimeEncodeImpl();
+ ImportOutFile * m_pOut;
+ nsImportMimeEncode * m_pEncode;
+};
+
+
+#endif /* nsImportMimeEncode_h__ */
+
diff --git a/mailnews/import/src/nsImportScanFile.cpp b/mailnews/import/src/nsImportScanFile.cpp
new file mode 100644
index 000000000..c4eefef3b
--- /dev/null
+++ b/mailnews/import/src/nsImportScanFile.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "nsIFile.h"
+#include "nsImportScanFile.h"
+#include "ImportCharSet.h"
+
+nsImportScanFile::nsImportScanFile()
+{
+ m_allocated = false;
+ m_eof = false;
+ m_pBuf = nullptr;
+}
+
+nsImportScanFile::~nsImportScanFile()
+{
+ if (m_allocated)
+ CleanUpScan();
+}
+
+void nsImportScanFile::InitScan(nsIInputStream *pInputStream, uint8_t * pBuf, uint32_t sz)
+{
+ m_pInputStream = pInputStream;
+ m_pBuf = pBuf;
+ m_bufSz = sz;
+ m_bytesInBuf = 0;
+ m_pos = 0;
+}
+
+void nsImportScanFile::CleanUpScan(void)
+{
+ m_pInputStream = nullptr;
+ if (m_allocated) {
+ delete [] m_pBuf;
+ m_pBuf = NULL;
+ }
+}
+
+void nsImportScanFile::ShiftBuffer(void)
+{
+ uint8_t * pTop;
+ uint8_t * pCurrent;
+
+ if (m_pos < m_bytesInBuf) {
+ pTop = m_pBuf;
+ pCurrent = pTop + m_pos;
+ uint32_t cnt = m_bytesInBuf - m_pos;
+ while (cnt) {
+ *pTop = *pCurrent;
+ pTop++; pCurrent++;
+ cnt--;
+ }
+ }
+
+ m_bytesInBuf -= m_pos;
+ m_pos = 0;
+}
+
+bool nsImportScanFile::FillBufferFromFile(void)
+{
+ uint64_t available;
+ nsresult rv = m_pInputStream->Available(&available);
+ if (NS_FAILED(rv))
+ return false;
+
+ // Fill up a buffer and scan it
+ ShiftBuffer();
+
+ // Read in some more bytes
+ uint32_t cnt = m_bufSz - m_bytesInBuf;
+ // To distinguish from disk errors
+ // Check first for end of file?
+ // Set a done flag if true...
+ uint32_t read;
+ char *pBuf = (char *)m_pBuf;
+ pBuf += m_bytesInBuf;
+ rv = m_pInputStream->Read(pBuf, (int32_t) cnt, &read);
+
+ if (NS_FAILED(rv))
+ return false;
+ rv = m_pInputStream->Available(&available);
+ if (NS_FAILED(rv))
+ m_eof = true;
+
+ m_bytesInBuf += cnt;
+ return true;
+}
+
+bool nsImportScanFile::Scan(bool *pDone)
+{
+ uint64_t available;
+ nsresult rv = m_pInputStream->Available(&available);
+ if (NS_FAILED(rv))
+ {
+ if (m_pos < m_bytesInBuf)
+ ScanBuffer(pDone);
+ *pDone = true;
+ return true;
+ }
+
+ // Fill up a buffer and scan it
+ if (!FillBufferFromFile())
+ return false;
+
+ return ScanBuffer(pDone);
+}
+
+bool nsImportScanFile::ScanBuffer(bool *)
+{
+ return true;
+}
+
+
+bool nsImportScanFileLines::ScanBuffer(bool *pDone)
+{
+ // m_pos, m_bytesInBuf, m_eof, m_pBuf are relevant
+
+ uint32_t pos = m_pos;
+ uint32_t max = m_bytesInBuf;
+ uint8_t * pChar = m_pBuf + pos;
+ uint32_t startPos;
+
+ while (pos < max) {
+ if (m_needEol) {
+ // Find the next eol...
+ while ((pos < max) && (*pChar != ImportCharSet::cCRChar) && (*pChar != ImportCharSet::cLinefeedChar)) {
+ pos++;
+ pChar++;
+ }
+ m_pos = pos;
+ if (pos < max)
+ m_needEol = false;
+ if (pos == max) // need more buffer for an end of line
+ break;
+ }
+ // Skip past any eol characters
+ while ((pos < max) && ((*pChar == ImportCharSet::cCRChar) || (*pChar == ImportCharSet::cLinefeedChar))) {
+ pos++;
+ pChar++;
+ }
+ m_pos = pos;
+ if (pos == max)
+ break;
+ // Make sure we can find either the eof or the
+ // next end of line
+ startPos = pos;
+ while ((pos < max) && (*pChar != ImportCharSet::cCRChar) && (*pChar != ImportCharSet::cLinefeedChar)) {
+ pos++;
+ pChar++;
+ }
+
+ // Is line too big for our buffer?
+ if ((pos == max) && !m_eof) {
+ if (!m_pos) { // line too big for our buffer
+ m_pos = pos;
+ m_needEol = true;
+ }
+ break;
+ }
+
+ if (!ProcessLine(m_pBuf + startPos, pos - startPos, pDone)) {
+ return false;
+ }
+ m_pos = pos;
+ }
+
+ return true;
+}
+
diff --git a/mailnews/import/src/nsImportScanFile.h b/mailnews/import/src/nsImportScanFile.h
new file mode 100644
index 000000000..abe5b1cdd
--- /dev/null
+++ b/mailnews/import/src/nsImportScanFile.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImportScanFile_h__
+#define nsImportScanFile_h__
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+
+class nsImportScanFile {
+public:
+ nsImportScanFile();
+ virtual ~nsImportScanFile();
+
+ void InitScan(nsIInputStream *pInputStream, uint8_t * pBuf, uint32_t sz);
+
+ void CleanUpScan(void);
+
+ virtual bool Scan(bool *pDone);
+
+protected:
+ void ShiftBuffer(void);
+ bool FillBufferFromFile(void);
+ virtual bool ScanBuffer(bool *pDone);
+
+protected:
+ nsCOMPtr <nsIInputStream> m_pInputStream;
+ uint8_t * m_pBuf;
+ uint32_t m_bufSz;
+ uint32_t m_bytesInBuf;
+ uint32_t m_pos;
+ bool m_eof;
+ bool m_allocated;
+};
+
+class nsImportScanFileLines : public nsImportScanFile {
+public:
+ nsImportScanFileLines() {m_needEol = false;}
+
+ void ResetLineScan(void) { m_needEol = false;}
+
+ virtual bool ProcessLine(uint8_t * /* pLine */, uint32_t /* len */, bool * /* pDone */) {return true;}
+
+protected:
+ virtual bool ScanBuffer(bool *pDone) override;
+
+ bool m_needEol;
+
+};
+
+
+#endif /* nsImportScanFile_h__ */
diff --git a/mailnews/import/src/nsImportService.cpp b/mailnews/import/src/nsImportService.cpp
new file mode 100644
index 000000000..0013c1146
--- /dev/null
+++ b/mailnews/import/src/nsImportService.cpp
@@ -0,0 +1,583 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICharsetConverterManager.h"
+#include "nsIPlatformCharset.h"
+#include "nsICharsetConverterManager.h"
+
+#include "nsStringGlue.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsMemory.h"
+#include "nsIImportModule.h"
+#include "nsIImportService.h"
+#include "nsImportMailboxDescriptor.h"
+#include "nsImportABDescriptor.h"
+#include "nsIImportGeneric.h"
+#include "nsImportFieldMap.h"
+#include "nsICategoryManager.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "nsMsgCompCID.h"
+#include "nsThreadUtils.h"
+#include "nsIEditor.h"
+#include "ImportDebug.h"
+#include "nsImportService.h"
+#include "nsImportStringBundle.h"
+#include "nsCRTGlue.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsIArray.h"
+#include "nsIMsgSend.h"
+#include "nsMsgUtils.h"
+
+PRLogModuleInfo *IMPORTLOGMODULE = nullptr;
+
+static nsIImportService * gImportService = nullptr;
+static const char * kWhitespace = "\b\t\r\n ";
+
+
+////////////////////////////////////////////////////////////////////////
+
+
+nsImportService::nsImportService() : m_pModules(nullptr)
+{
+ // Init logging module.
+ if (!IMPORTLOGMODULE)
+ IMPORTLOGMODULE = PR_NewLogModule("IMPORT");
+ IMPORT_LOG0("* nsImport Service Created\n");
+
+ m_didDiscovery = false;
+ m_pDecoder = nullptr;
+ m_pEncoder = nullptr;
+
+ nsresult rv = nsImportStringBundle::GetStringBundle(IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle));
+ if (NS_FAILED(rv))
+ IMPORT_LOG0("Failed to get string bundle for Importing Mail");
+}
+
+
+nsImportService::~nsImportService()
+{
+ NS_IF_RELEASE(m_pDecoder);
+ NS_IF_RELEASE(m_pEncoder);
+
+ gImportService = nullptr;
+
+ if (m_pModules != nullptr)
+ delete m_pModules;
+
+ IMPORT_LOG0("* nsImport Service Deleted\n");
+}
+
+
+
+NS_IMPL_ISUPPORTS(nsImportService, nsIImportService)
+
+
+NS_IMETHODIMP nsImportService::DiscoverModules(void)
+{
+ m_didDiscovery = false;
+ return DoDiscover();
+}
+
+NS_IMETHODIMP nsImportService::CreateNewFieldMap(nsIImportFieldMap **_retval)
+{
+ return nsImportFieldMap::Create(m_stringBundle, nullptr, NS_GET_IID(nsIImportFieldMap), (void**)_retval);
+}
+
+NS_IMETHODIMP nsImportService::CreateNewMailboxDescriptor(nsIImportMailboxDescriptor **_retval)
+{
+ return nsImportMailboxDescriptor::Create(nullptr, NS_GET_IID(nsIImportMailboxDescriptor), (void**)_retval);
+}
+
+NS_IMETHODIMP nsImportService::CreateNewABDescriptor(nsIImportABDescriptor **_retval)
+{
+ return nsImportABDescriptor::Create(nullptr, NS_GET_IID(nsIImportABDescriptor), (void**)_retval);
+}
+
+extern nsresult NS_NewGenericMail(nsIImportGeneric** aImportGeneric);
+
+NS_IMETHODIMP nsImportService::CreateNewGenericMail(nsIImportGeneric **_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (! _retval)
+ return NS_ERROR_NULL_POINTER;
+
+ return NS_NewGenericMail(_retval);
+}
+
+extern nsresult NS_NewGenericAddressBooks(nsIImportGeneric** aImportGeneric);
+
+NS_IMETHODIMP nsImportService::CreateNewGenericAddressBooks(nsIImportGeneric **_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (! _retval)
+ return NS_ERROR_NULL_POINTER;
+
+ return NS_NewGenericAddressBooks(_retval);
+}
+
+
+NS_IMETHODIMP nsImportService::GetModuleCount(const char *filter, int32_t *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (! _retval)
+ return NS_ERROR_NULL_POINTER;
+
+ DoDiscover();
+
+ if (m_pModules != nullptr) {
+ ImportModuleDesc * pDesc;
+ int32_t count = 0;
+ for (int32_t i = 0; i < m_pModules->GetCount(); i++) {
+ pDesc = m_pModules->GetModuleDesc(i);
+ if (pDesc->SupportsThings(filter))
+ count++;
+ }
+ *_retval = count;
+ }
+ else
+ *_retval = 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportService::GetModuleWithCID(const nsCID& cid, nsIImportModule **ppModule)
+{
+ NS_PRECONDITION(ppModule != nullptr, "null ptr");
+ if (!ppModule)
+ return NS_ERROR_NULL_POINTER;
+
+ *ppModule = nullptr;
+ nsresult rv = DoDiscover();
+ if (NS_FAILED(rv))
+ return rv;
+ if (m_pModules == nullptr)
+ return NS_ERROR_FAILURE;
+ int32_t cnt = m_pModules->GetCount();
+ ImportModuleDesc *pDesc;
+ for (int32_t i = 0; i < cnt; i++) {
+ pDesc = m_pModules->GetModuleDesc(i);
+ if (!pDesc)
+ return NS_ERROR_FAILURE;
+ if (pDesc->GetCID().Equals(cid)) {
+ *ppModule = pDesc->GetModule();
+
+ IMPORT_LOG0("* nsImportService::GetSpecificModule - attempted to load module\n");
+
+ if (*ppModule == nullptr)
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+ }
+ }
+
+ IMPORT_LOG0("* nsImportService::GetSpecificModule - module not found\n");
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsImportService::GetModuleInfo(const char *filter, int32_t index, char16_t **name, char16_t **moduleDescription)
+{
+ NS_PRECONDITION(name != nullptr, "null ptr");
+ NS_PRECONDITION(moduleDescription != nullptr, "null ptr");
+ if (!name || !moduleDescription)
+ return NS_ERROR_NULL_POINTER;
+
+ *name = nullptr;
+ *moduleDescription = nullptr;
+
+ DoDiscover();
+ if (!m_pModules)
+ return NS_ERROR_FAILURE;
+
+ if ((index < 0) || (index >= m_pModules->GetCount()))
+ return NS_ERROR_FAILURE;
+
+ ImportModuleDesc * pDesc;
+ int32_t count = 0;
+ for (int32_t i = 0; i < m_pModules->GetCount(); i++) {
+ pDesc = m_pModules->GetModuleDesc(i);
+ if (pDesc->SupportsThings(filter)) {
+ if (count == index) {
+ *name = NS_strdup(pDesc->GetName());
+ *moduleDescription = NS_strdup(pDesc->GetDescription());
+ return NS_OK;
+ }
+ else
+ count++;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsImportService::GetModuleName(const char *filter, int32_t index, char16_t **_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = nullptr;
+
+ DoDiscover();
+ if (!m_pModules)
+ return NS_ERROR_FAILURE;
+
+ if ((index < 0) || (index >= m_pModules->GetCount()))
+ return NS_ERROR_FAILURE;
+
+ ImportModuleDesc * pDesc;
+ int32_t count = 0;
+ for (int32_t i = 0; i < m_pModules->GetCount(); i++) {
+ pDesc = m_pModules->GetModuleDesc(i);
+ if (pDesc->SupportsThings(filter)) {
+ if (count == index) {
+ *_retval = NS_strdup(pDesc->GetName());
+ return NS_OK;
+ }
+ else
+ count++;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+
+NS_IMETHODIMP nsImportService::GetModuleDescription(const char *filter, int32_t index, char16_t **_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = nullptr;
+
+ DoDiscover();
+ if (!m_pModules)
+ return NS_ERROR_FAILURE;
+
+ if ((index < 0) || (index >= m_pModules->GetCount()))
+ return NS_ERROR_FAILURE;
+
+ ImportModuleDesc * pDesc;
+ int32_t count = 0;
+ for (int32_t i = 0; i < m_pModules->GetCount(); i++) {
+ pDesc = m_pModules->GetModuleDesc(i);
+ if (pDesc->SupportsThings(filter)) {
+ if (count == index) {
+ *_retval = NS_strdup(pDesc->GetDescription());
+ return NS_OK;
+ }
+ else
+ count++;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+class nsProxySendRunnable : public mozilla::Runnable
+{
+public:
+ nsProxySendRunnable(nsIMsgIdentity *aIdentity,
+ nsIMsgCompFields *aMsgFields,
+ const char *attachment1_type,
+ const nsACString &attachment1_body,
+ bool aIsDraft,
+ nsIArray *aLoadedAttachments,
+ nsIArray *aEmbeddedAttachments,
+ nsIMsgSendListener *aListener);
+ NS_DECL_NSIRUNNABLE
+private:
+ nsCOMPtr<nsIMsgIdentity> m_identity;
+ nsCOMPtr<nsIMsgCompFields> m_compFields;
+ bool m_isDraft;
+ nsCString m_bodyType;
+ nsCString m_body;
+ nsCOMPtr<nsIArray> m_loadedAttachments;
+ nsCOMPtr<nsIArray> m_embeddedAttachments;
+ nsCOMPtr<nsIMsgSendListener> m_listener;
+
+};
+
+nsProxySendRunnable::nsProxySendRunnable(nsIMsgIdentity *aIdentity,
+ nsIMsgCompFields *aMsgFields,
+ const char *aBodyType,
+ const nsACString &aBody,
+ bool aIsDraft,
+ nsIArray *aLoadedAttachments,
+ nsIArray *aEmbeddedAttachments,
+ nsIMsgSendListener *aListener) :
+ m_identity(aIdentity), m_compFields(aMsgFields),
+ m_isDraft(aIsDraft), m_bodyType(aBodyType),
+ m_body(aBody), m_loadedAttachments(aLoadedAttachments),
+ m_embeddedAttachments(aEmbeddedAttachments),
+ m_listener(aListener)
+{
+}
+
+NS_IMETHODIMP nsProxySendRunnable::Run()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgSend> msgSend = do_CreateInstance(NS_MSGSEND_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return msgSend->CreateRFC822Message(m_identity, m_compFields,
+ m_bodyType.get(), m_body,
+ m_isDraft, m_loadedAttachments,
+ m_embeddedAttachments,
+ m_listener);
+}
+
+
+NS_IMETHODIMP
+nsImportService::CreateRFC822Message(nsIMsgIdentity *aIdentity,
+ nsIMsgCompFields *aMsgFields,
+ const char *aBodyType,
+ const nsACString &aBody,
+ bool aIsDraft,
+ nsIArray *aLoadedAttachments,
+ nsIArray *aEmbeddedAttachments,
+ nsIMsgSendListener *aListener)
+{
+ RefPtr<nsProxySendRunnable> runnable =
+ new nsProxySendRunnable(aIdentity,
+ aMsgFields,
+ aBodyType,
+ aBody,
+ aIsDraft,
+ aLoadedAttachments,
+ aEmbeddedAttachments,
+ aListener);
+ // invoke the callback
+ return NS_DispatchToMainThread(runnable);
+}
+
+NS_IMETHODIMP nsImportService::GetModule(const char *filter, int32_t index, nsIImportModule **_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!_retval)
+ return NS_ERROR_NULL_POINTER;
+ *_retval = nullptr;
+
+ DoDiscover();
+ if (!m_pModules)
+ return NS_ERROR_FAILURE;
+
+ if ((index < 0) || (index >= m_pModules->GetCount()))
+ return NS_ERROR_FAILURE;
+
+ ImportModuleDesc * pDesc;
+ int32_t count = 0;
+ for (int32_t i = 0; i < m_pModules->GetCount(); i++) {
+ pDesc = m_pModules->GetModuleDesc(i);
+ if (pDesc->SupportsThings(filter)) {
+ if (count == index) {
+ *_retval = pDesc->GetModule();
+ break;
+ }
+ else
+ count++;
+ }
+ }
+ if (! (*_retval))
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+
+nsresult nsImportService::DoDiscover(void)
+{
+ if (m_didDiscovery)
+ return NS_OK;
+
+ if (m_pModules != nullptr)
+ m_pModules->ClearList();
+
+ nsresult rv;
+
+ nsCOMPtr<nsICategoryManager> catMan = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> e;
+ rv = catMan->EnumerateCategory("mailnewsimport", getter_AddRefs(e));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsISupportsCString> contractid;
+ rv = e->GetNext(getter_AddRefs(supports));
+ while (NS_SUCCEEDED(rv) && supports)
+ {
+ contractid = do_QueryInterface(supports);
+ if (!contractid)
+ break;
+
+ nsCString contractIdStr;
+ contractid->ToString(getter_Copies(contractIdStr));
+ nsCString supportsStr;
+ rv = catMan->GetCategoryEntry("mailnewsimport", contractIdStr.get(), getter_Copies(supportsStr));
+ if (NS_SUCCEEDED(rv))
+ LoadModuleInfo(contractIdStr.get(), supportsStr.get());
+ rv = e->GetNext(getter_AddRefs(supports));
+ }
+
+ m_didDiscovery = true;
+
+ return NS_OK;
+}
+
+nsresult nsImportService::LoadModuleInfo(const char *pClsId, const char *pSupports)
+{
+ if (!pClsId || !pSupports)
+ return NS_OK;
+
+ if (m_pModules == nullptr)
+ m_pModules = new nsImportModuleList();
+
+ // load the component and get all of the info we need from it....
+ // then call AddModule
+ nsresult rv;
+
+ nsCID clsId;
+ clsId.Parse(pClsId);
+ nsIImportModule * module;
+ rv = CallCreateInstance(clsId, &module);
+ if (NS_FAILED(rv)) return rv;
+
+ nsString theTitle;
+ nsString theDescription;
+ rv = module->GetName(getter_Copies(theTitle));
+ if (NS_FAILED(rv))
+ theTitle.AssignLiteral("Unknown");
+
+ rv = module->GetDescription(getter_Copies(theDescription));
+ if (NS_FAILED(rv))
+ theDescription.AssignLiteral("Unknown description");
+
+ // call the module to get the info we need
+ m_pModules->AddModule(clsId, pSupports, theTitle.get(), theDescription.get());
+
+ module->Release();
+
+ return NS_OK;
+}
+
+
+nsIImportModule *ImportModuleDesc::GetModule(bool keepLoaded)
+{
+ if (m_pModule)
+ {
+ m_pModule->AddRef();
+ return m_pModule;
+ }
+
+ nsresult rv;
+ rv = CallCreateInstance(m_cid, &m_pModule);
+ if (NS_FAILED(rv))
+ {
+ m_pModule = nullptr;
+ return nullptr;
+ }
+
+ if (keepLoaded)
+ {
+ m_pModule->AddRef();
+ return m_pModule;
+ }
+ else
+ {
+ nsIImportModule *pModule = m_pModule;
+ m_pModule = nullptr;
+ return pModule;
+ }
+}
+
+void ImportModuleDesc::ReleaseModule(void)
+{
+ if (m_pModule)
+ {
+ m_pModule->Release();
+ m_pModule = nullptr;
+ }
+}
+
+bool ImportModuleDesc::SupportsThings(const char *pThings)
+{
+ if (!pThings || !*pThings)
+ return true;
+
+ nsCString thing(pThings);
+ nsCString item;
+ int32_t idx;
+
+ while ((idx = thing.FindChar(',')) != -1)
+ {
+ item = StringHead(thing, idx);
+ item.Trim(kWhitespace);
+ ToLowerCase(item);
+ if (item.Length() && (m_supports.Find(item) == -1))
+ return false;
+ thing = Substring(thing, idx + 1);
+ }
+ thing.Trim(kWhitespace);
+ ToLowerCase(thing);
+ return thing.IsEmpty() || (m_supports.Find(thing) != -1);
+}
+
+void nsImportModuleList::ClearList(void)
+{
+ if (m_pList)
+ {
+ for (int i = 0; i < m_count; i++)
+ {
+ delete m_pList[i];
+ m_pList[i] = nullptr;
+ }
+ m_count = 0;
+ delete [] m_pList;
+ m_pList = nullptr;
+ m_alloc = 0;
+ }
+
+}
+
+void nsImportModuleList::AddModule(const nsCID& cid, const char *pSupports, const char16_t *pName, const char16_t *pDesc)
+{
+ if (!m_pList)
+ {
+ m_alloc = 10;
+ m_pList = new ImportModuleDesc *[m_alloc];
+ m_count = 0;
+ memset(m_pList, 0, sizeof(ImportModuleDesc *) * m_alloc);
+ }
+
+ if (m_count == m_alloc)
+ {
+ ImportModuleDesc **pList = new ImportModuleDesc *[m_alloc + 10];
+ memset(&(pList[m_alloc]), 0, sizeof(ImportModuleDesc *) * 10);
+ memcpy(pList, m_pList, sizeof(ImportModuleDesc *) * m_alloc);
+ for(int i = 0; i < m_count; i++)
+ delete m_pList[i];
+ delete [] m_pList;
+ m_pList = pList;
+ m_alloc += 10;
+ }
+
+ m_pList[m_count] = new ImportModuleDesc();
+ m_pList[m_count]->SetCID(cid);
+ m_pList[m_count]->SetSupports(pSupports);
+ m_pList[m_count]->SetName(pName);
+ m_pList[m_count]->SetDescription(pDesc);
+
+ m_count++;
+#ifdef IMPORT_DEBUG
+ IMPORT_LOG3("* nsImportService registered import module: %s, %s, %s\n", NS_LossyConvertUTF16toASCII(pName).get(), NS_LossyConvertUTF16toASCII(pDesc).get(), pSupports);
+#endif
+}
+
diff --git a/mailnews/import/src/nsImportService.h b/mailnews/import/src/nsImportService.h
new file mode 100644
index 000000000..889cfa6c4
--- /dev/null
+++ b/mailnews/import/src/nsImportService.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImportService_h__
+#define nsImportService_h__
+
+#include "nsICharsetConverterManager.h"
+
+#include "nsStringGlue.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsMemory.h"
+#include "nsIImportModule.h"
+#include "nsIImportService.h"
+#include "nsICategoryManager.h"
+#include "nsIStringBundle.h"
+
+class nsImportModuleList;
+
+class nsImportService : public nsIImportService
+{
+public:
+
+ nsImportService();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIIMPORTSERVICE
+
+private:
+ virtual ~nsImportService();
+ nsresult LoadModuleInfo(const char*pClsId, const char *pSupports);
+ nsresult DoDiscover(void);
+
+private:
+ nsImportModuleList * m_pModules;
+ bool m_didDiscovery;
+ nsCString m_sysCharset;
+ nsIUnicodeDecoder * m_pDecoder;
+ nsIUnicodeEncoder * m_pEncoder;
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+};
+
+class ImportModuleDesc {
+public:
+ ImportModuleDesc() { m_pModule = nullptr;}
+ ~ImportModuleDesc() { ReleaseModule(); }
+
+ void SetCID(const nsCID& cid) { m_cid = cid;}
+ void SetName(const char16_t *pName) { m_name = pName;}
+ void SetDescription(const char16_t *pDesc) { m_description = pDesc;}
+ void SetSupports(const char *pSupports) { m_supports = pSupports;}
+
+ nsCID GetCID(void) { return m_cid;}
+ const char16_t *GetName(void) { return m_name.get();}
+ const char16_t *GetDescription(void) { return m_description.get();}
+ const char * GetSupports(void) { return m_supports.get();}
+
+ nsIImportModule * GetModule(bool keepLoaded = false); // Adds ref
+ void ReleaseModule(void);
+
+ bool SupportsThings(const char *pThings);
+
+private:
+ nsCID m_cid;
+ nsString m_name;
+ nsString m_description;
+ nsCString m_supports;
+ nsIImportModule *m_pModule;
+};
+
+class nsImportModuleList {
+public:
+ nsImportModuleList() { m_pList = nullptr; m_alloc = 0; m_count = 0;}
+ ~nsImportModuleList() { ClearList(); }
+
+ void AddModule(const nsCID& cid, const char *pSupports, const char16_t *pName, const char16_t *pDesc);
+
+ void ClearList(void);
+
+ int32_t GetCount(void) { return m_count;}
+
+ ImportModuleDesc * GetModuleDesc(int32_t idx)
+ { if ((idx < 0) || (idx >= m_count)) return nullptr; else return m_pList[idx];}
+
+private:
+
+private:
+ ImportModuleDesc ** m_pList;
+ int32_t m_alloc;
+ int32_t m_count;
+};
+
+#endif // nsImportService_h__
diff --git a/mailnews/import/src/nsImportStringBundle.cpp b/mailnews/import/src/nsImportStringBundle.cpp
new file mode 100644
index 000000000..3adb6655a
--- /dev/null
+++ b/mailnews/import/src/nsImportStringBundle.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+#include "nsImportStringBundle.h"
+#include "nsIServiceManager.h"
+#include "nsIURI.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Services.h"
+
+nsresult nsImportStringBundle::GetStringBundle(const char *aPropertyURL,
+ nsIStringBundle **aBundle)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+ rv = sBundleService->CreateBundle(aPropertyURL, aBundle);
+
+ return rv;
+}
+
+void nsImportStringBundle::GetStringByID(int32_t aStringID,
+ nsIStringBundle *aBundle,
+ nsString &aResult)
+{
+ aResult.Adopt(GetStringByID(aStringID, aBundle));
+}
+
+char16_t *nsImportStringBundle::GetStringByID(int32_t aStringID,
+ nsIStringBundle *aBundle)
+{
+ if (aBundle)
+ {
+ char16_t *ptrv = nullptr;
+ nsresult rv = aBundle->GetStringFromID(aStringID, &ptrv);
+
+ if (NS_SUCCEEDED(rv) && ptrv)
+ return ptrv;
+ }
+
+ nsString resultString(NS_LITERAL_STRING("[StringID "));
+ resultString.AppendInt(aStringID);
+ resultString.AppendLiteral("?]");
+
+ return ToNewUnicode(resultString);
+}
+
+void nsImportStringBundle::GetStringByName(const char *aName,
+ nsIStringBundle *aBundle,
+ nsString &aResult)
+{
+ aResult.Adopt(GetStringByName(aName, aBundle));
+}
+
+char16_t *nsImportStringBundle::GetStringByName(const char *aName,
+ nsIStringBundle *aBundle)
+{
+ if (aBundle)
+ {
+ char16_t *ptrv = nullptr;
+ nsresult rv = aBundle->GetStringFromName(
+ NS_ConvertUTF8toUTF16(aName).get(), &ptrv);
+
+ if (NS_SUCCEEDED(rv) && ptrv)
+ return ptrv;
+ }
+
+ nsString resultString(NS_LITERAL_STRING("[StringName "));
+ resultString.Append(NS_ConvertUTF8toUTF16(aName).get());
+ resultString.AppendLiteral("?]");
+
+ return ToNewUnicode(resultString);
+}
diff --git a/mailnews/import/src/nsImportStringBundle.h b/mailnews/import/src/nsImportStringBundle.h
new file mode 100644
index 000000000..c9db012e6
--- /dev/null
+++ b/mailnews/import/src/nsImportStringBundle.h
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsImportStringBundle_H__
+#define _nsImportStringBundle_H__
+
+#include "nsStringGlue.h"
+
+class nsIStringBundle;
+
+class nsImportStringBundle
+{
+public:
+ static char16_t* GetStringByID(int32_t aStringID,
+ nsIStringBundle *aBundle = nullptr);
+ static void GetStringByID(int32_t aStringID,
+ nsIStringBundle *aBundle,
+ nsString &aResult);
+ static char16_t* GetStringByName(const char *aName,
+ nsIStringBundle *aBundle = nullptr);
+ static void GetStringByName(const char *aName,
+ nsIStringBundle *aBundle,
+ nsString &aResult);
+ static nsresult GetStringBundle(const char *aPropertyURL,
+ nsIStringBundle **aBundle);
+};
+
+#define IMPORT_MSGS_URL "chrome://messenger/locale/importMsgs.properties"
+
+
+#define IMPORT_NO_ADDRBOOKS 2000
+#define IMPORT_ERROR_AB_NOTINITIALIZED 2001
+#define IMPORT_ERROR_AB_NOTHREAD 2002
+#define IMPORT_ERROR_GETABOOK 2003
+#define IMPORT_NO_MAILBOXES 2004
+#define IMPORT_ERROR_MB_NOTINITIALIZED 2005
+#define IMPORT_ERROR_MB_NOTHREAD 2006
+#define IMPORT_ERROR_MB_NOPROXY 2007
+#define IMPORT_ERROR_MB_FINDCHILD 2008
+#define IMPORT_ERROR_MB_CREATE 2009
+#define IMPORT_ERROR_MB_NODESTFOLDER 2010
+
+#define IMPORT_FIELD_DESC_START 2100
+#define IMPORT_FIELD_DESC_END 2136
+
+
+#endif /* _nsImportStringBundle_H__ */
diff --git a/mailnews/import/src/nsImportTranslator.cpp b/mailnews/import/src/nsImportTranslator.cpp
new file mode 100644
index 000000000..beec8b93a
--- /dev/null
+++ b/mailnews/import/src/nsImportTranslator.cpp
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ImportOutFile.h"
+#include "nsImportTranslator.h"
+
+#include "ImportCharSet.h"
+
+
+bool nsImportTranslator::ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed)
+{
+ if (pProcessed)
+ *pProcessed = inLen;
+ return (pOutFile->WriteData(pIn, inLen));
+}
+
+void CMHTranslator::ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t * pOut)
+{
+ while (inLen) {
+ if (!ImportCharSet::IsUSAscii(*pIn) || ImportCharSet::Is822SpecialChar(*pIn) || ImportCharSet::Is822CtlChar(*pIn) ||
+ (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '*') || (*pIn == '\'') ||
+ (*pIn == '%')) {
+ // needs to be encode as %hex val
+ *pOut = '%'; pOut++;
+ ImportCharSet::ByteToHex(*pIn, pOut);
+ pOut += 2;
+ }
+ else {
+ *pOut = *pIn;
+ pOut++;
+ }
+ pIn++; inLen--;
+ }
+ *pOut = 0;
+}
+
+bool CMHTranslator::ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed)
+{
+ uint8_t hex[2];
+ while (inLen) {
+ if (!ImportCharSet::IsUSAscii(*pIn) || ImportCharSet::Is822SpecialChar(*pIn) || ImportCharSet::Is822CtlChar(*pIn) ||
+ (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '*') || (*pIn == '\'') ||
+ (*pIn == '%')) {
+ // needs to be encode as %hex val
+ if (!pOutFile->WriteByte('%'))
+ return false;
+ ImportCharSet::ByteToHex(*pIn, hex);
+ if (!pOutFile->WriteData(hex, 2))
+ return false;
+ }
+ else {
+ if (!pOutFile->WriteByte(*pIn))
+ return false;
+ }
+ pIn++; inLen--;
+ }
+
+ if (pProcessed)
+ *pProcessed = inLen;
+
+ return true;
+}
+
+
+bool C2047Translator::ConvertToFileQ(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed)
+{
+ if (!inLen)
+ return true;
+
+ int maxLineLen = 64;
+ int curLineLen = m_startLen;
+ bool startLine = true;
+
+ uint8_t hex[2];
+ while (inLen) {
+ if (startLine) {
+ if (!pOutFile->WriteStr(" =?"))
+ return false;
+ if (!pOutFile->WriteStr(m_charset.get()))
+ return false;
+ if (!pOutFile->WriteStr("?q?"))
+ return false;
+ curLineLen += (6 + m_charset.Length());
+ startLine = false;
+ }
+
+ if (!ImportCharSet::IsUSAscii(*pIn) || ImportCharSet::Is822SpecialChar(*pIn) || ImportCharSet::Is822CtlChar(*pIn) ||
+ (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '?') || (*pIn == '=')) {
+ // needs to be encode as =hex val
+ if (!pOutFile->WriteByte('='))
+ return false;
+ ImportCharSet::ByteToHex(*pIn, hex);
+ if (!pOutFile->WriteData(hex, 2))
+ return false;
+ curLineLen += 3;
+ }
+ else {
+ if (!pOutFile->WriteByte(*pIn))
+ return false;
+ curLineLen++;
+ }
+ pIn++; inLen--;
+ if (curLineLen > maxLineLen) {
+ if (!pOutFile->WriteStr("?="))
+ return false;
+ if (inLen) {
+ if (!pOutFile->WriteStr("\x0D\x0A "))
+ return false;
+ }
+
+ startLine = true;
+ curLineLen = 0;
+ }
+ }
+
+ if (!startLine) {
+ // end the encoding!
+ if (!pOutFile->WriteStr("?="))
+ return false;
+ }
+
+ if (pProcessed)
+ *pProcessed = inLen;
+
+ return true;
+}
+
+bool C2047Translator::ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed)
+{
+ if (m_useQuotedPrintable)
+ return ConvertToFileQ(pIn, inLen, pOutFile, pProcessed);
+
+ if (!inLen)
+ return true;
+
+ int maxLineLen = 64;
+ int curLineLen = m_startLen;
+ bool startLine = true;
+ int encodeMax;
+ uint8_t * pEncoded = new uint8_t[maxLineLen * 2];
+
+ while (inLen) {
+ if (startLine) {
+ if (!pOutFile->WriteStr(" =?")) {
+ delete [] pEncoded;
+ return false;
+ }
+ if (!pOutFile->WriteStr(m_charset.get())) {
+ delete [] pEncoded;
+ return false;
+ }
+ if (!pOutFile->WriteStr("?b?")) {
+ delete [] pEncoded;
+ return false;
+ }
+ curLineLen += (6 + m_charset.Length());
+ startLine = false;
+ }
+ encodeMax = maxLineLen - curLineLen;
+ encodeMax *= 3;
+ encodeMax /= 4;
+ if ((uint32_t)encodeMax > inLen)
+ encodeMax = (int)inLen;
+
+ // encode the line, end the line
+ // then continue. Update curLineLen, pIn, startLine, and inLen
+ UMimeEncode::ConvertBuffer(pIn, encodeMax, pEncoded, maxLineLen, maxLineLen, "\x0D\x0A");
+
+ if (!pOutFile->WriteStr((const char *)pEncoded)) {
+ delete [] pEncoded;
+ return false;
+ }
+
+ pIn += encodeMax;
+ inLen -= encodeMax;
+ startLine = true;
+ curLineLen = 0;
+ if (!pOutFile->WriteStr("?=")) {
+ delete [] pEncoded;
+ return false;
+ }
+ if (inLen) {
+ if (!pOutFile->WriteStr("\x0D\x0A ")) {
+ delete [] pEncoded;
+ return false;
+ }
+ }
+ }
+
+ delete [] pEncoded;
+
+ if (pProcessed)
+ *pProcessed = inLen;
+
+ return true;
+}
+
+
+uint32_t UMimeEncode::GetBufferSize(uint32_t inBytes)
+{
+ // it takes 4 base64 bytes to represent 3 regular bytes
+ inBytes += 3;
+ inBytes /= 3;
+ inBytes *= 4;
+ // This should be plenty, but just to be safe
+ inBytes += 4;
+
+ // now allow for end of line characters
+ inBytes += ((inBytes + 39) / 40) * 4;
+
+ return inBytes;
+}
+
+static uint8_t gBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+uint32_t UMimeEncode::ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t * pOut, uint32_t maxLen, uint32_t firstLineLen, const char * pEolStr)
+{
+
+ uint32_t pos = 0;
+ uint32_t len = 0;
+ uint32_t lineLen = 0;
+ uint32_t maxLine = firstLineLen;
+ int eolLen = 0;
+ if (pEolStr)
+ eolLen = strlen(pEolStr);
+
+ while ((pos + 2) < inLen) {
+ // Encode 3 bytes
+ *pOut = gBase64[*pIn >> 2];
+ pOut++; len++; lineLen++;
+ *pOut = gBase64[(((*pIn) & 0x3)<< 4) | (((*(pIn + 1)) & 0xF0) >> 4)];
+ pIn++; pOut++; len++; lineLen++;
+ *pOut = gBase64[(((*pIn) & 0xF) << 2) | (((*(pIn + 1)) & 0xC0) >>6)];
+ pIn++; pOut++; len++; lineLen++;
+ *pOut = gBase64[(*pIn) & 0x3F];
+ pIn++; pOut++; len++; lineLen++;
+ pos += 3;
+ if (lineLen >= maxLine) {
+ lineLen = 0;
+ maxLine = maxLen;
+ if (pEolStr) {
+ memcpy(pOut, pEolStr, eolLen);
+ pOut += eolLen;
+ len += eolLen;
+ }
+ }
+ }
+
+ if ((pos < inLen) && ((lineLen + 3) > maxLine)) {
+ lineLen = 0;
+ maxLine = maxLen;
+ if (pEolStr) {
+ memcpy(pOut, pEolStr, eolLen);
+ pOut += eolLen;
+ len += eolLen;
+ }
+ }
+
+ if (pos < inLen) {
+ // Get the last few bytes!
+ *pOut = gBase64[*pIn >> 2];
+ pOut++; len++;
+ pos++;
+ if (pos < inLen) {
+ *pOut = gBase64[(((*pIn) & 0x3)<< 4) | (((*(pIn + 1)) & 0xF0) >> 4)];
+ pIn++; pOut++; pos++; len++;
+ if (pos < inLen) {
+ // Should be dead code!! (Then why is it here doofus?)
+ *pOut = gBase64[(((*pIn) & 0xF) << 2) | (((*(pIn + 1)) & 0xC0) >>6)];
+ pIn++; pOut++; len++;
+ *pOut = gBase64[(*pIn) & 0x3F];
+ pos++; pOut++; len++;
+ }
+ else {
+ *pOut = gBase64[(((*pIn) & 0xF) << 2)];
+ pOut++; len++;
+ *pOut = '=';
+ pOut++; len++;
+ }
+ }
+ else {
+ *pOut = gBase64[(((*pIn) & 0x3)<< 4)];
+ pOut++; len++;
+ *pOut = '=';
+ pOut++; len++;
+ *pOut = '=';
+ pOut++; len++;
+ }
+ }
+
+ *pOut = 0;
+
+ return len;
+}
diff --git a/mailnews/import/src/nsImportTranslator.h b/mailnews/import/src/nsImportTranslator.h
new file mode 100644
index 000000000..998616063
--- /dev/null
+++ b/mailnews/import/src/nsImportTranslator.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImportTranslator_h___
+#define nsImportTranslator_h___
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+
+class ImportOutFile;
+
+class UMimeEncode {
+public:
+ static uint32_t GetBufferSize(uint32_t inByes);
+ static uint32_t ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t *pOut, uint32_t maxLen = 72, uint32_t firstLineLen = 72, const char * pEolStr = nullptr);
+};
+
+
+class nsImportTranslator {
+public:
+ virtual ~nsImportTranslator() {}
+ virtual bool Supports8bitEncoding(void) { return false;}
+ virtual uint32_t GetMaxBufferSize(uint32_t inLen) { return inLen + 1;}
+ virtual void ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t * pOut) { memcpy(pOut, pIn, inLen); pOut[inLen] = 0;}
+ virtual bool ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed = nullptr);
+ virtual bool FinishConvertToFile(ImportOutFile * /* pOutFile */) { return true;}
+
+ virtual void GetCharset(nsCString& charSet) { charSet = "us-ascii";}
+ virtual void GetLanguage(nsCString& lang) { lang = "en";}
+ virtual void GetEncoding(nsCString& encoding) { encoding.Truncate();}
+};
+
+// Specialized encoder, not a vaild language translator, used for Mime headers.
+// rfc2231
+class CMHTranslator : public nsImportTranslator {
+public:
+ virtual uint32_t GetMaxBufferSize(uint32_t inLen) override { return (inLen * 3) + 1;}
+ virtual void ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t * pOut) override;
+ virtual bool ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed = nullptr) override;
+};
+
+// Specialized encoder, not a vaild language translator, used for mail headers
+// rfc2047
+class C2047Translator : public nsImportTranslator {
+public:
+ virtual ~C2047Translator() {}
+
+ C2047Translator(const char *pCharset, uint32_t headerLen) { m_charset = pCharset; m_startLen = headerLen; m_useQuotedPrintable = false;}
+
+ void SetUseQuotedPrintable(void) { m_useQuotedPrintable = true;}
+
+ virtual bool ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed = nullptr) override;
+ bool ConvertToFileQ(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed);
+
+protected:
+ bool m_useQuotedPrintable;
+ nsCString m_charset;
+ uint32_t m_startLen;
+};
+
+#endif /* nsImportTranslator_h__ */
+
diff --git a/mailnews/import/text/src/TextDebugLog.h b/mailnews/import/text/src/TextDebugLog.h
new file mode 100644
index 000000000..3f9bf1ec4
--- /dev/null
+++ b/mailnews/import/text/src/TextDebugLog.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TextDebugLog_h___
+#define TextDebugLog_h___
+
+// Use PR_LOG for logging.
+#include "mozilla/Logging.h"
+extern PRLogModuleInfo *TEXTIMPORTLOGMODULE; // Logging module
+
+#define IMPORT_LOG0(x) MOZ_LOG(TEXTIMPORTLOGMODULE, mozilla::LogLevel::Debug, (x))
+#define IMPORT_LOG1(x, y) MOZ_LOG(TEXTIMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y))
+#define IMPORT_LOG2(x, y, z) MOZ_LOG(TEXTIMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y, z))
+#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(TEXTIMPORTLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d))
+
+
+
+#endif /* TextDebugLog_h___ */
diff --git a/mailnews/import/text/src/moz.build b/mailnews/import/text/src/moz.build
new file mode 100644
index 000000000..1bdbb07c9
--- /dev/null
+++ b/mailnews/import/text/src/moz.build
@@ -0,0 +1,16 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsTextAddress.cpp',
+ 'nsTextImport.cpp',
+]
+
+FINAL_LIBRARY = 'import'
+
+LOCAL_INCLUDES += [
+ '../../src'
+]
+
diff --git a/mailnews/import/text/src/nsTextAddress.cpp b/mailnews/import/text/src/nsTextAddress.cpp
new file mode 100644
index 000000000..8a22498d2
--- /dev/null
+++ b/mailnews/import/text/src/nsTextAddress.cpp
@@ -0,0 +1,470 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTextAddress.h"
+#include "nsIAddrDatabase.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsILineInputStream.h"
+#include "nsNetUtil.h"
+#include "nsMsgI18N.h"
+#include "nsMsgUtils.h"
+#include "mdb.h"
+#include "nsIConverterInputStream.h"
+#include "nsIUnicharLineInputStream.h"
+#include "nsMsgUtils.h"
+
+#include "TextDebugLog.h"
+#include "plstr.h"
+#include "msgCore.h"
+
+#ifndef MOZILLA_INTERNAL_API
+#include "nsMsgI18N.h"
+#define NS_CopyNativeToUnicode(source, dest) \
+ nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), source, dest)
+#endif
+
+#define kWhitespace " \t\b\r\n"
+
+nsTextAddress::nsTextAddress()
+{
+ m_database = nullptr;
+ m_fieldMap = nullptr;
+ m_LFCount = 0;
+ m_CRCount = 0;
+}
+
+nsTextAddress::~nsTextAddress()
+{
+ NS_IF_RELEASE(m_database);
+ NS_IF_RELEASE(m_fieldMap);
+}
+
+nsresult nsTextAddress::GetUnicharLineStreamForFile(nsIFile *aFile,
+ nsIInputStream *aInputStream,
+ nsIUnicharLineInputStream **aStream)
+{
+ nsAutoCString charset;
+ nsresult rv = MsgDetectCharsetFromFile(aFile, charset);
+ if (NS_FAILED(rv)) {
+ charset.Assign(nsMsgI18NFileSystemCharset());
+ }
+
+ nsCOMPtr<nsIConverterInputStream> converterStream =
+ do_CreateInstance("@mozilla.org/intl/converter-input-stream;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = converterStream->Init(aInputStream,
+ charset.get(),
+ 8192,
+ nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER);
+ }
+
+ return CallQueryInterface(converterStream, aStream);
+}
+
+nsresult nsTextAddress::ImportAddresses(bool *pAbort, const char16_t *pName, nsIFile *pSrc, nsIAddrDatabase *pDb, nsIImportFieldMap *fieldMap, nsString& errors, uint32_t *pProgress)
+{
+ // Open the source file for reading, read each line and process it!
+ NS_IF_RELEASE(m_database);
+ NS_IF_RELEASE(m_fieldMap);
+ m_database = pDb;
+ m_fieldMap = fieldMap;
+ NS_ADDREF(m_fieldMap);
+ NS_ADDREF(m_database);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ // Here we use this to work out the size of the file, so we can update
+ // an integer as we go through the file which will update a progress
+ // bar if required by the caller.
+ uint64_t bytesLeft = 0;
+ rv = inputStream->Available(&bytesLeft);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error checking address file for size\n");
+ inputStream->Close();
+ return rv;
+ }
+
+ uint64_t totalBytes = bytesLeft;
+ bool skipRecord = false;
+
+ rv = m_fieldMap->GetSkipFirstRecord(&skipRecord);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error checking to see if we should skip the first record\n");
+ return rv;
+ }
+
+ nsCOMPtr<nsIUnicharLineInputStream> lineStream;
+ rv = GetUnicharLineStreamForFile(pSrc, inputStream, getter_AddRefs(lineStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening converter stream for importer\n");
+ return rv;
+ }
+
+ bool more = true;
+ nsAutoString line;
+
+ // Skip the first record if the user has requested it.
+ if (skipRecord)
+ rv = ReadRecord(lineStream, line, &more);
+
+ while (!(*pAbort) && more && NS_SUCCEEDED(rv)) {
+ // Read the line in
+ rv = ReadRecord(lineStream, line, &more);
+ if (NS_SUCCEEDED(rv)) {
+ // Now proces it to add it to the database
+ rv = ProcessLine(line, errors);
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error processing text record.\n");
+ }
+ }
+ if (NS_SUCCEEDED(rv) && pProgress) {
+ // This won't be totally accurate, but its the best we can do
+ // considering that lineStream won't give us how many bytes
+ // are actually left.
+ bytesLeft -= line.Length();
+ *pProgress = std::min(totalBytes - bytesLeft, uint64_t(PR_UINT32_MAX));
+ }
+ }
+
+ inputStream->Close();
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error reading the address book - probably incorrect ending\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ return pDb->Commit(nsAddrDBCommitType::kLargeCommit);
+}
+
+nsresult nsTextAddress::ReadRecord(nsIUnicharLineInputStream *aLineStream,
+ nsAString &aLine,
+ bool *aMore)
+{
+ bool more = true;
+ uint32_t numQuotes = 0;
+ nsresult rv;
+ nsAutoString line;
+
+ // ensure aLine is empty
+ aLine.Truncate();
+
+ do {
+ if (!more) {
+ // No more, so we must have an incorrect file.
+ rv = NS_ERROR_FAILURE;
+ }
+ else {
+ // Read the line and append it
+ rv = aLineStream->ReadLine(line, &more);
+ if (NS_SUCCEEDED(rv)) {
+ if (!aLine.IsEmpty())
+ aLine.AppendLiteral(MSG_LINEBREAK);
+ aLine.Append(line);
+
+ numQuotes += MsgCountChar(line, char16_t('"'));
+ }
+ }
+ // Continue whilst everything is ok, and we have an odd number of quotes.
+ } while (NS_SUCCEEDED(rv) && (numQuotes % 2 != 0));
+
+ *aMore = more;
+ return rv;
+}
+
+nsresult nsTextAddress::ReadRecordNumber(nsIFile *aSrc, nsAString &aLine, int32_t rNum)
+{
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ int32_t rIndex = 0;
+ uint64_t bytesLeft = 0;
+
+ rv = inputStream->Available(&bytesLeft);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error checking address file for eof\n");
+ inputStream->Close();
+ return rv;
+ }
+
+ nsCOMPtr<nsIUnicharLineInputStream> lineStream;
+ rv = GetUnicharLineStreamForFile(aSrc, inputStream, getter_AddRefs(lineStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening converter stream for importer\n");
+ return rv;
+ }
+
+ bool more = true;
+
+ while (more && (rIndex <= rNum)) {
+ rv = ReadRecord(lineStream, aLine, &more);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ if (rIndex == rNum) {
+ inputStream->Close();
+ return NS_OK;
+ }
+
+ rIndex++;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+int32_t nsTextAddress::CountFields(const nsAString &aLine, char16_t delim)
+{
+ int32_t pos = 0;
+ int32_t maxLen = aLine.Length();
+ int32_t count = 0;
+ char16_t tab = char16_t('\t');
+ char16_t doubleQuote = char16_t('"');
+
+ if (delim == tab)
+ tab = char16_t('\0');
+
+ while (pos < maxLen) {
+ while (((aLine[pos] == char16_t(' ')) || (aLine[pos] == tab)) &&
+ (pos < maxLen)) {
+ pos++;
+ }
+ if ((pos < maxLen) && (aLine[pos] == doubleQuote)) {
+ pos++;
+ while ((pos < maxLen) && (aLine[pos] != doubleQuote)) {
+ pos++;
+ if (((pos + 1) < maxLen) &&
+ (aLine[pos] == doubleQuote) &&
+ (aLine[pos + 1] == doubleQuote)) {
+ pos += 2;
+ }
+ }
+ if (pos < maxLen)
+ pos++;
+ }
+ while ((pos < maxLen) && (aLine[pos] != delim))
+ pos++;
+
+ count++;
+ pos++;
+ }
+
+ return count;
+}
+
+bool nsTextAddress::GetField(const nsAString &aLine,
+ int32_t index,
+ nsString &field,
+ char16_t delim)
+{
+ bool result = false;
+ int32_t pos = 0;
+ int32_t maxLen = aLine.Length();
+ char16_t tab = char16_t('\t');
+ char16_t doubleQuote = char16_t('"');
+
+ field.Truncate();
+
+ if (delim == tab)
+ tab = 0;
+
+ while (index && (pos < maxLen)) {
+ while (((aLine[pos] == char16_t(' ')) || (aLine[pos] == tab)) &&
+ (pos < maxLen)) {
+ pos++;
+ }
+ if (pos >= maxLen)
+ break;
+ if (aLine[pos] == doubleQuote) {
+ do {
+ pos++;
+ if (((pos + 1) < maxLen) &&
+ (aLine[pos] == doubleQuote) &&
+ (aLine[pos + 1] == doubleQuote)) {
+ pos += 2;
+ }
+ } while ((pos < maxLen) && (aLine[pos] != doubleQuote));
+ if (pos < maxLen)
+ pos++;
+ }
+ if (pos >= maxLen)
+ break;
+
+ while ((pos < maxLen) && (aLine[pos] != delim))
+ pos++;
+
+ if (pos >= maxLen)
+ break;
+
+ index--;
+ pos++;
+ }
+
+ if (pos >= maxLen)
+ return result;
+
+ result = true;
+
+ while ((pos < maxLen) && ((aLine[pos] == ' ') || (aLine[pos] == tab)))
+ pos++;
+
+ int32_t fLen = 0;
+ int32_t startPos = pos;
+ bool quoted = false;
+ if (aLine[pos] == '"') {
+ startPos++;
+ fLen = -1;
+ do {
+ pos++;
+ fLen++;
+ if (((pos + 1) < maxLen) &&
+ (aLine[pos] == doubleQuote) &&
+ (aLine[pos + 1] == doubleQuote)) {
+ quoted = true;
+ pos += 2;
+ fLen += 2;
+ }
+ } while ((pos < maxLen) && (aLine[pos] != doubleQuote));
+ }
+ else {
+ while ((pos < maxLen) && (aLine[pos] != delim)) {
+ pos++;
+ fLen++;
+ }
+ }
+
+ if (!fLen) {
+ return result;
+ }
+
+ field.Append(nsDependentSubstring(aLine, startPos, fLen));
+ field.Trim(kWhitespace);
+
+ if (quoted) {
+ int32_t offset = field.Find("\"\"");
+ while (offset != -1) {
+ field.Cut(offset, 1);
+ offset = MsgFind(field, "\"\"", false, offset + 1);
+ }
+ }
+
+ return result;
+}
+
+nsresult nsTextAddress::DetermineDelim(nsIFile *aSrc)
+{
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ int32_t lineCount = 0;
+ int32_t tabCount = 0;
+ int32_t commaCount = 0;
+ int32_t tabLines = 0;
+ int32_t commaLines = 0;
+ nsAutoString line;
+ bool more = true;
+
+ nsCOMPtr<nsIUnicharLineInputStream> lineStream;
+ rv = GetUnicharLineStreamForFile(aSrc, inputStream, getter_AddRefs(lineStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening converter stream for importer\n");
+ return rv;
+ }
+
+ while (more && NS_SUCCEEDED(rv) && (lineCount < 100)) {
+ rv = lineStream->ReadLine(line, &more);
+ if (NS_SUCCEEDED(rv)) {
+ tabCount = CountFields(line, char16_t('\t'));
+ commaCount = CountFields(line, char16_t(','));
+ if (tabCount > commaCount)
+ tabLines++;
+ else if (commaCount)
+ commaLines++;
+ }
+ lineCount++;
+ }
+
+ rv = inputStream->Close();
+
+ if (tabLines > commaLines)
+ m_delim = char16_t('\t');
+ else
+ m_delim = char16_t(',');
+
+ IMPORT_LOG2( "Tab count = %d, Comma count = %d\n", tabLines, commaLines);
+
+ return rv;
+}
+
+/*
+ This is where the real work happens!
+ Go through the field map and set the data in a new database row
+*/
+nsresult nsTextAddress::ProcessLine(const nsAString &aLine, nsString& errors)
+{
+ if (!m_fieldMap) {
+ IMPORT_LOG0("*** Error, text import needs a field map\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ // Wait until we get our first non-empty field, then create a new row,
+ // fill in the data, then add the row to the database.
+ nsCOMPtr<nsIMdbRow> newRow;
+ nsAutoString fieldVal;
+ int32_t fieldNum;
+ int32_t numFields = 0;
+ bool active;
+ rv = m_fieldMap->GetMapSize(&numFields);
+ for (int32_t i = 0; (i < numFields) && NS_SUCCEEDED(rv); i++) {
+ active = false;
+ rv = m_fieldMap->GetFieldMap(i, &fieldNum);
+ if (NS_SUCCEEDED(rv))
+ rv = m_fieldMap->GetFieldActive(i, &active);
+ if (NS_SUCCEEDED(rv) && active) {
+ if (GetField(aLine, i, fieldVal, m_delim)) {
+ if (!fieldVal.IsEmpty()) {
+ if (!newRow) {
+ rv = m_database->GetNewRow(getter_AddRefs(newRow));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error getting new address database row\n");
+ }
+ }
+ if (newRow) {
+ rv = m_fieldMap->SetFieldValue(m_database, newRow, fieldNum, fieldVal.get());
+ }
+ }
+ }
+ else
+ break;
+ }
+ else if (active) {
+ IMPORT_LOG1("*** Error getting field map for index %ld\n", (long) i);
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && newRow)
+ rv = m_database->AddCardRowToDB(newRow);
+
+ return rv;
+}
+
diff --git a/mailnews/import/text/src/nsTextAddress.h b/mailnews/import/text/src/nsTextAddress.h
new file mode 100644
index 000000000..69a311be4
--- /dev/null
+++ b/mailnews/import/text/src/nsTextAddress.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTextAddress_h__
+#define nsTextAddress_h__
+
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIImportFieldMap.h"
+#include "nsIImportService.h"
+
+class nsIAddrDatabase;
+class nsIFile;
+class nsIInputStream;
+class nsIUnicharLineInputStream;
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+
+class nsTextAddress {
+public:
+ nsTextAddress();
+ virtual ~nsTextAddress();
+
+ nsresult ImportAddresses(bool *pAbort, const char16_t *pName, nsIFile *pSrc, nsIAddrDatabase *pDb, nsIImportFieldMap *fieldMap, nsString& errors, uint32_t *pProgress);
+
+ nsresult DetermineDelim(nsIFile *pSrc);
+ char16_t GetDelim(void) { return m_delim; }
+
+ static nsresult ReadRecordNumber(nsIFile *pSrc, nsAString &aLine, int32_t rNum);
+ static bool GetField(const nsAString &aLine, int32_t index, nsString &field, char16_t delim);
+
+private:
+ nsresult ProcessLine(const nsAString &aLine, nsString &errors);
+
+ static int32_t CountFields(const nsAString &aLine, char16_t delim);
+ static nsresult ReadRecord(nsIUnicharLineInputStream *pSrc, nsAString &aLine, bool *aMore);
+ static nsresult GetUnicharLineStreamForFile(nsIFile *aFile,
+ nsIInputStream *aInputStream,
+ nsIUnicharLineInputStream **aStream);
+
+ char16_t m_delim;
+ int32_t m_LFCount;
+ int32_t m_CRCount;
+ nsIAddrDatabase *m_database;
+ nsIImportFieldMap *m_fieldMap;
+ nsCOMPtr<nsIImportService> m_pService;
+};
+
+
+
+#endif /* nsTextAddress_h__ */
+
diff --git a/mailnews/import/text/src/nsTextImport.cpp b/mailnews/import/text/src/nsTextImport.cpp
new file mode 100644
index 000000000..0b99e652b
--- /dev/null
+++ b/mailnews/import/text/src/nsTextImport.cpp
@@ -0,0 +1,714 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/*
+
+ Text import addressbook interfaces
+
+*/
+#include "nscore.h"
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "nsIImportService.h"
+#include "nsMsgI18N.h"
+#include "nsIComponentManager.h"
+#include "nsTextImport.h"
+#include "nsIMemory.h"
+#include "nsIMutableArray.h"
+#include "nsIImportGeneric.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIImportFieldMap.h"
+#include "nsIOutputStream.h"
+#include "nsIAddrDatabase.h"
+#include "nsIAbLDIFService.h"
+#include "nsAbBaseCID.h"
+#include "nsTextFormatter.h"
+#include "nsImportStringBundle.h"
+#include "nsTextAddress.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "TextDebugLog.h"
+#include "nsNetUtil.h"
+#include "nsMsgUtils.h"
+
+#define TEXT_MSGS_URL "chrome://messenger/locale/textImportMsgs.properties"
+#define TEXTIMPORT_NAME 2000
+#define TEXTIMPORT_DESCRIPTION 2001
+#define TEXTIMPORT_ADDRESS_NAME 2002
+#define TEXTIMPORT_ADDRESS_SUCCESS 2003
+#define TEXTIMPORT_ADDRESS_BADPARAM 2004
+#define TEXTIMPORT_ADDRESS_BADSOURCEFILE 2005
+#define TEXTIMPORT_ADDRESS_CONVERTERROR 2006
+
+static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
+PRLogModuleInfo* TEXTIMPORTLOGMODULE;
+
+class ImportAddressImpl final : public nsIImportAddressBooks
+{
+public:
+ explicit ImportAddressImpl(nsIStringBundle* aStringBundle);
+
+ static nsresult Create(nsIImportAddressBooks** aImport,
+ nsIStringBundle *aStringBundle);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIImportAddressBooks interface
+
+ NS_IMETHOD GetSupportsMultiple(bool *_retval) override { *_retval = false; return NS_OK;}
+
+ NS_IMETHOD GetAutoFind(char16_t **description, bool *_retval) override;
+
+ NS_IMETHOD GetNeedsFieldMap(nsIFile *location, bool *_retval) override;
+
+ NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found, bool *userVerify) override;
+
+ NS_IMETHOD FindAddressBooks(nsIFile *location, nsIArray **_retval) override;
+
+ NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap) override;
+
+ NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source,
+ nsIAddrDatabase *destination,
+ nsIImportFieldMap *fieldMap,
+ nsISupports *aSupportService,
+ char16_t **errorLog,
+ char16_t **successLog,
+ bool *fatalError) override;
+
+ NS_IMETHOD GetImportProgress(uint32_t *_retval) override;
+
+ NS_IMETHOD GetSampleData(int32_t index, bool *pFound, char16_t **pStr) override;
+
+ NS_IMETHOD SetSampleLocation(nsIFile *) override;
+
+private:
+ void ClearSampleFile(void);
+ void SaveFieldMap(nsIImportFieldMap *pMap);
+
+ static void ReportSuccess(nsString& name, nsString *pStream,
+ nsIStringBundle* pBundle);
+ static void SetLogs(nsString& success, nsString& error, char16_t **pError,
+ char16_t **pSuccess);
+ static void ReportError(int32_t errorNum, nsString& name, nsString *pStream,
+ nsIStringBundle* pBundle);
+ static void SanitizeSampleData(nsString& val);
+
+private:
+ ~ImportAddressImpl() {}
+ nsTextAddress m_text;
+ bool m_haveDelim;
+ nsCOMPtr<nsIFile> m_fileLoc;
+ nsCOMPtr<nsIStringBundle> m_notProxyBundle;
+ char16_t m_delim;
+ uint32_t m_bytesImported;
+};
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+
+nsTextImport::nsTextImport()
+{
+ // Init logging module.
+ if (!TEXTIMPORTLOGMODULE)
+ TEXTIMPORTLOGMODULE = PR_NewLogModule("IMPORT");
+ IMPORT_LOG0("nsTextImport Module Created\n");
+
+ nsImportStringBundle::GetStringBundle(TEXT_MSGS_URL,
+ getter_AddRefs(m_stringBundle));
+}
+
+nsTextImport::~nsTextImport()
+{
+ IMPORT_LOG0("nsTextImport Module Deleted\n");
+}
+
+NS_IMPL_ISUPPORTS(nsTextImport, nsIImportModule)
+
+NS_IMETHODIMP nsTextImport::GetName(char16_t **name)
+{
+ NS_ENSURE_ARG_POINTER(name);
+ *name = nsImportStringBundle::GetStringByID(TEXTIMPORT_NAME, m_stringBundle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTextImport::GetDescription(char16_t **name)
+{
+ NS_ENSURE_ARG_POINTER(name);
+ *name = nsImportStringBundle::GetStringByID(TEXTIMPORT_DESCRIPTION,
+ m_stringBundle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTextImport::GetSupports(char **supports)
+{
+ NS_ENSURE_ARG_POINTER(supports);
+ *supports = strdup(kTextSupportsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTextImport::GetSupportsUpgrade(bool *pUpgrade)
+{
+ NS_PRECONDITION(pUpgrade != nullptr, "null ptr");
+ if (! pUpgrade)
+ return NS_ERROR_NULL_POINTER;
+
+ *pUpgrade = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTextImport::GetImportInterface(const char *pImportType, nsISupports **ppInterface)
+{
+ NS_ENSURE_ARG_POINTER(pImportType);
+ NS_ENSURE_ARG_POINTER(ppInterface);
+
+ *ppInterface = nullptr;
+ nsresult rv;
+
+ if (!strcmp(pImportType, "addressbook")) {
+ // create the nsIImportMail interface and return it!
+ nsIImportAddressBooks * pAddress = nullptr;
+ nsIImportGeneric * pGeneric = nullptr;
+ rv = ImportAddressImpl::Create(&pAddress, m_stringBundle);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = impSvc->CreateNewGenericAddressBooks(&pGeneric);
+ if (NS_SUCCEEDED(rv)) {
+ pGeneric->SetData("addressInterface", pAddress);
+ rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface);
+ }
+ }
+ }
+ NS_IF_RELEASE(pAddress);
+ NS_IF_RELEASE(pGeneric);
+ return rv;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+
+
+
+nsresult ImportAddressImpl::Create(nsIImportAddressBooks** aImport,
+ nsIStringBundle* aStringBundle)
+{
+ NS_ENSURE_ARG_POINTER(aImport);
+ *aImport = new ImportAddressImpl(aStringBundle);
+ if (! *aImport)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+ImportAddressImpl::ImportAddressImpl(nsIStringBundle* aStringBundle) :
+ m_notProxyBundle(aStringBundle)
+{
+ m_haveDelim = false;
+}
+
+NS_IMPL_ISUPPORTS(ImportAddressImpl, nsIImportAddressBooks)
+
+
+NS_IMETHODIMP ImportAddressImpl::GetAutoFind(char16_t **addrDescription, bool *_retval)
+{
+ NS_PRECONDITION(addrDescription != nullptr, "null ptr");
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (! addrDescription || !_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ nsString str;
+ *_retval = false;
+
+ if (!m_notProxyBundle)
+ return NS_ERROR_FAILURE;
+
+ nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_NAME, m_notProxyBundle, str);
+ *addrDescription = ToNewUnicode(str);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP ImportAddressImpl::GetDefaultLocation(nsIFile **ppLoc, bool *found, bool *userVerify)
+{
+ NS_PRECONDITION(found != nullptr, "null ptr");
+ NS_PRECONDITION(ppLoc != nullptr, "null ptr");
+ NS_PRECONDITION(userVerify != nullptr, "null ptr");
+ if (! found || !userVerify || !ppLoc)
+ return NS_ERROR_NULL_POINTER;
+
+ *ppLoc = nullptr;
+ *found = false;
+ *userVerify = true;
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP ImportAddressImpl::FindAddressBooks(nsIFile *pLoc, nsIArray **ppArray)
+{
+ NS_PRECONDITION(pLoc != nullptr, "null ptr");
+ NS_PRECONDITION(ppArray != nullptr, "null ptr");
+ if (!pLoc || !ppArray)
+ return NS_ERROR_NULL_POINTER;
+
+ ClearSampleFile();
+
+ *ppArray = nullptr;
+ bool exists = false;
+ nsresult rv = pLoc->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return NS_ERROR_FAILURE;
+
+ bool isFile = false;
+ rv = pLoc->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile)
+ return NS_ERROR_FAILURE;
+
+ rv = m_text.DetermineDelim(pLoc);
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error determining delimitter\n");
+ return rv;
+ }
+ m_haveDelim = true;
+ m_delim = m_text.GetDelim();
+
+ m_fileLoc = do_QueryInterface(pLoc);
+
+ /* Build an address book descriptor based on the file passed in! */
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("FAILED to allocate the nsIMutableArray\n");
+ return rv;
+ }
+
+ nsString name;
+ m_fileLoc->GetLeafName(name);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed getting leaf name of file\n");
+ return rv;
+ }
+
+ int32_t idx = name.RFindChar('.');
+ if ((idx != -1) && (idx > 0) && ((name.Length() - idx - 1) < 5)) {
+ name.SetLength(idx);
+ }
+
+ nsCOMPtr<nsIImportABDescriptor> desc;
+ nsISupports * pInterface;
+
+ nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to obtain the import service\n");
+ return rv;
+ }
+
+ rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc));
+ if (NS_SUCCEEDED(rv)) {
+ int64_t sz = 0;
+ pLoc->GetFileSize(&sz);
+ desc->SetPreferredName(name);
+ desc->SetSize((uint32_t) sz);
+ desc->SetAbFile(m_fileLoc);
+ rv = desc->QueryInterface(kISupportsIID, (void **) &pInterface);
+ array->AppendElement(pInterface, false);
+ pInterface->Release();
+ }
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error creating address book descriptor for text import\n");
+ return rv;
+ }
+ array.forget(ppArray);
+ return NS_OK;
+}
+
+void ImportAddressImpl::ReportSuccess(nsString& name, nsString *pStream,
+ nsIStringBundle* pBundle)
+{
+ if (!pStream)
+ return;
+
+ // load the success string
+ char16_t *pFmt =
+ nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_SUCCESS, pBundle);
+
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get());
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ NS_Free(pFmt);
+ pStream->Append(char16_t('\n'));
+}
+
+void ImportAddressImpl::ReportError(int32_t errorNum, nsString& name,
+ nsString *pStream, nsIStringBundle* pBundle)
+{
+ if (!pStream)
+ return;
+
+ // load the error string
+ char16_t *pFmt = nsImportStringBundle::GetStringByID(errorNum, pBundle);
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get());
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ NS_Free(pFmt);
+ pStream->Append(char16_t('\n'));
+}
+
+void ImportAddressImpl::SetLogs(nsString& success, nsString& error, char16_t **pError, char16_t **pSuccess)
+{
+ if (pError)
+ *pError = ToNewUnicode(error);
+ if (pSuccess)
+ *pSuccess = ToNewUnicode(success);
+}
+
+
+NS_IMETHODIMP
+ImportAddressImpl::ImportAddressBook(nsIImportABDescriptor *pSource,
+ nsIAddrDatabase *pDestination,
+ nsIImportFieldMap *fieldMap,
+ nsISupports *aSupportService,
+ char16_t ** pErrorLog,
+ char16_t ** pSuccessLog,
+ bool * fatalError)
+{
+ NS_PRECONDITION(pSource != nullptr, "null ptr");
+ NS_PRECONDITION(pDestination != nullptr, "null ptr");
+ NS_PRECONDITION(fatalError != nullptr, "null ptr");
+
+ m_bytesImported = 0;
+
+ nsString success, error;
+ if (!pSource || !pDestination || !fatalError) {
+ IMPORT_LOG0("*** Bad param passed to text address import\n");
+ nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_BADPARAM,
+ m_notProxyBundle,
+ error);
+
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+
+ if (fatalError)
+ *fatalError = true;
+
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ ClearSampleFile();
+
+ bool addrAbort = false;
+ nsString name;
+ pSource->GetPreferredName(name);
+
+ uint32_t addressSize = 0;
+ pSource->GetSize(&addressSize);
+ if (addressSize == 0) {
+ IMPORT_LOG0("Address book size is 0, skipping import.\n");
+ ReportSuccess(name, &success, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> inFile;
+ if (NS_FAILED(pSource->GetAbFile(getter_AddRefs(inFile)))) {
+ ReportError(TEXTIMPORT_ADDRESS_BADSOURCEFILE, name, &error, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aSupportService) {
+ IMPORT_LOG0("Missing support service to import call");
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isLDIF = false;
+ nsresult rv;
+ nsCOMPtr<nsIAbLDIFService> ldifService(do_QueryInterface(aSupportService, &rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = ldifService->IsLDIFFile(inFile, &isLDIF);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error reading address file\n");
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ ReportError(TEXTIMPORT_ADDRESS_CONVERTERROR, name, &error, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return rv;
+ }
+
+ if (isLDIF) {
+ if (ldifService)
+ rv = ldifService->ImportLDIFFile(pDestination, inFile, false, &m_bytesImported);
+ else
+ return NS_ERROR_FAILURE;
+ }
+ else {
+ rv = m_text.ImportAddresses(&addrAbort, name.get(), inFile, pDestination, fieldMap, error, &m_bytesImported);
+ SaveFieldMap(fieldMap);
+ }
+
+ if (NS_SUCCEEDED(rv) && error.IsEmpty()) {
+ ReportSuccess(name, &success, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ }
+ else {
+ ReportError(TEXTIMPORT_ADDRESS_CONVERTERROR, name, &error, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ }
+
+ IMPORT_LOG0("*** Text address import done\n");
+ return rv;
+}
+
+
+NS_IMETHODIMP ImportAddressImpl::GetImportProgress(uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_bytesImported;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP ImportAddressImpl::GetNeedsFieldMap(nsIFile *aLocation, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ *_retval = true;
+ bool exists = false;
+ bool isFile = false;
+
+ nsresult rv = aLocation->Exists(&exists);
+ rv = aLocation->IsFile(&isFile);
+
+ if (!exists || !isFile)
+ return NS_ERROR_FAILURE;
+
+ bool isLDIF = false;
+ nsCOMPtr<nsIAbLDIFService> ldifService = do_GetService(NS_ABLDIFSERVICE_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv))
+ rv = ldifService->IsLDIFFile(aLocation, &isLDIF);
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error determining if file is of type LDIF\n");
+ return rv;
+ }
+
+ if (isLDIF)
+ *_retval = false;
+
+ return NS_OK;
+}
+
+void ImportAddressImpl::SanitizeSampleData(nsString& val)
+{
+ // remove any line-feeds...
+ int32_t offset = val.Find(NS_LITERAL_STRING("\x0D\x0A"));
+ while (offset != -1) {
+ val.Replace(offset, 2, NS_LITERAL_STRING(", "));
+ offset = val.Find(NS_LITERAL_STRING("\x0D\x0A"), offset + 2);
+ }
+ offset = val.FindChar(13);
+ while (offset != -1) {
+ val.Replace(offset, 1, ',');
+ offset = val.FindChar(13, offset + 2);
+ }
+ offset = val.FindChar(10);
+ while (offset != -1) {
+ val.Replace(offset, 1, ',');
+ offset = val.FindChar(10, offset + 2);
+ }
+}
+
+NS_IMETHODIMP ImportAddressImpl::GetSampleData(int32_t index, bool *pFound, char16_t **pStr)
+{
+ NS_PRECONDITION(pFound != nullptr, "null ptr");
+ NS_PRECONDITION(pStr != nullptr, "null ptr");
+ if (!pFound || !pStr)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!m_fileLoc) {
+ IMPORT_LOG0("*** Error, called GetSampleData before SetSampleLocation\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ *pStr = nullptr;
+ char16_t term = 0;
+
+ if (!m_haveDelim) {
+ rv = m_text.DetermineDelim(m_fileLoc);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_haveDelim = true;
+ m_delim = m_text.GetDelim();
+ }
+
+ bool fileExists;
+ rv = m_fileLoc->Exists(&fileExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!fileExists) {
+ *pFound = false;
+ *pStr = NS_strdup(&term);
+ return NS_OK;
+ }
+
+ nsAutoString line;
+ rv = nsTextAddress::ReadRecordNumber(m_fileLoc, line, index);
+ if (NS_SUCCEEDED(rv)) {
+ nsString str;
+ nsString field;
+ int32_t fNum = 0;
+ while (nsTextAddress::GetField(line, fNum, field, m_delim)) {
+ if (fNum)
+ str.Append(char16_t('\n'));
+ SanitizeSampleData(field);
+ str.Append(field);
+ fNum++;
+ field.Truncate();
+ }
+
+ *pStr = ToNewUnicode(str);
+ *pFound = true;
+
+ /* IMPORT_LOG1("Sample data: %S\n", str.get()); */
+ }
+ else {
+ *pFound = false;
+ *pStr = NS_strdup(&term);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportAddressImpl::SetSampleLocation(nsIFile *pLocation)
+{
+ NS_ENSURE_ARG_POINTER(pLocation);
+
+ m_fileLoc = do_QueryInterface(pLocation);
+ m_haveDelim = false;
+ return NS_OK;
+}
+
+void ImportAddressImpl::ClearSampleFile(void)
+{
+ m_fileLoc = nullptr;
+ m_haveDelim = false;
+}
+
+NS_IMETHODIMP ImportAddressImpl::InitFieldMap(nsIImportFieldMap *fieldMap)
+{
+ // Let's remember the last one the user used!
+ // This should be normal for someone importing multiple times, it's usually
+ // from the same file format.
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCString prefStr;
+ rv = prefs->GetCharPref("mailnews.import.text.fieldmap", getter_Copies(prefStr));
+ if (NS_SUCCEEDED(rv)) {
+ const char *pStr = prefStr.get();
+ if (pStr) {
+ fieldMap->SetFieldMapSize(0);
+ long fNum;
+ bool active;
+ long fIndex = 0;
+ while (*pStr) {
+ while (*pStr && (*pStr != '+') && (*pStr != '-'))
+ pStr++;
+ if (*pStr == '+')
+ active = true;
+ else if (*pStr == '-')
+ active = false;
+ else
+ break;
+ fNum = 0;
+ while (*pStr && ((*pStr < '0') || (*pStr > '9')))
+ pStr++;
+ if (!(*pStr))
+ break;
+ while (*pStr && (*pStr >= '0') && (*pStr <= '9')) {
+ fNum *= 10;
+ fNum += (*pStr - '0');
+ pStr++;
+ }
+ while (*pStr && (*pStr != ','))
+ pStr++;
+ if (*pStr == ',')
+ pStr++;
+ fieldMap->SetFieldMap(-1, fNum);
+ fieldMap->SetFieldActive(fIndex, active);
+ fIndex++;
+ }
+ if (!fIndex) {
+ int num;
+ fieldMap->GetNumMozFields(&num);
+ fieldMap->DefaultFieldMap(num);
+ }
+ }
+ }
+
+ // Now also get the last used skip first record value.
+ bool skipFirstRecord = false;
+ rv = prefs->GetBoolPref("mailnews.import.text.skipfirstrecord", &skipFirstRecord);
+ if (NS_SUCCEEDED(rv))
+ fieldMap->SetSkipFirstRecord(skipFirstRecord);
+ }
+
+ return NS_OK;
+}
+
+
+void ImportAddressImpl::SaveFieldMap(nsIImportFieldMap *pMap)
+{
+ if (!pMap)
+ return;
+
+ int size;
+ int index;
+ bool active;
+ nsCString str;
+
+ pMap->GetMapSize(&size);
+ for (long i = 0; i < size; i++) {
+ index = i;
+ active = false;
+ pMap->GetFieldMap(i, &index);
+ pMap->GetFieldActive(i, &active);
+ if (active)
+ str.Append('+');
+ else
+ str.Append('-');
+
+ str.AppendInt(index);
+ str.Append(',');
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCString prefStr;
+ rv = prefs->GetCharPref("mailnews.import.text.fieldmap", getter_Copies(prefStr));
+ if (NS_FAILED(rv) || !str.Equals(prefStr))
+ rv = prefs->SetCharPref("mailnews.import.text.fieldmap", str.get());
+ }
+
+ // Now also save last used skip first record value.
+ bool skipFirstRecord = false;
+ rv = pMap->GetSkipFirstRecord(&skipFirstRecord);
+ if (NS_SUCCEEDED(rv))
+ prefs->SetBoolPref("mailnews.import.text.skipfirstrecord", skipFirstRecord);
+}
diff --git a/mailnews/import/text/src/nsTextImport.h b/mailnews/import/text/src/nsTextImport.h
new file mode 100644
index 000000000..4c3c440e0
--- /dev/null
+++ b/mailnews/import/text/src/nsTextImport.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTextImport_h___
+#define nsTextImport_h___
+
+#include "nsIImportModule.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+
+#define NS_TEXTIMPORT_CID \
+{ /* A5991D01-ADA7-11d3-A9C2-00A0CC26DA63 */ \
+ 0xa5991d01, 0xada7, 0x11d3, \
+ {0xa9, 0xc2, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63 }}
+
+#define kTextSupportsString NS_IMPORT_ADDRESS_STR
+
+class nsTextImport : public nsIImportModule
+{
+public:
+ nsTextImport();
+
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we suppport the nsIImportModule interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ NS_DECL_NSIIMPORTMODULE
+
+protected:
+ virtual ~nsTextImport();
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+};
+
+#endif /* nsTextImport_h___ */
diff --git a/mailnews/import/vcard/src/moz.build b/mailnews/import/vcard/src/moz.build
new file mode 100644
index 000000000..9e6c49698
--- /dev/null
+++ b/mailnews/import/vcard/src/moz.build
@@ -0,0 +1,20 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsVCardAddress.cpp',
+ 'nsVCardImport.cpp',
+]
+
+EXPORTS += [
+ 'nsVCardAddress.h',
+]
+
+FINAL_LIBRARY = 'import'
+
+LOCAL_INCLUDES += [
+ '../../src'
+]
+
diff --git a/mailnews/import/vcard/src/nsVCardAddress.cpp b/mailnews/import/vcard/src/nsVCardAddress.cpp
new file mode 100644
index 000000000..7495d4c26
--- /dev/null
+++ b/mailnews/import/vcard/src/nsVCardAddress.cpp
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAbBaseCID.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsNetUtil.h"
+#include "nsVCardAddress.h"
+
+#include "nsIAbCard.h"
+#include "nsIAbManager.h"
+#include "nsIAddrDatabase.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsILineInputStream.h"
+
+#include "plstr.h"
+#include "msgCore.h"
+#include "nsMsgUtils.h"
+
+nsVCardAddress::nsVCardAddress()
+{
+}
+
+nsVCardAddress::~nsVCardAddress()
+{
+}
+
+nsresult nsVCardAddress::ImportAddresses(
+ bool *pAbort,
+ const char16_t *pName,
+ nsIFile *pSrc,
+ nsIAddrDatabase *pDb,
+ nsString& errors,
+ uint32_t *pProgress)
+{
+ // Open the source file for reading, read each line and process it!
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ // Open the source file for reading, read each line and process it!
+ // Here we use this to work out the size of the file, so we can update
+ // an integer as we go through the file which will update a progress
+ // bar if required by the caller.
+ uint64_t bytesLeft = 0;
+ rv = inputStream->Available(&bytesLeft);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error checking address file for size\n");
+ inputStream->Close();
+ return rv;
+ }
+
+ uint64_t totalBytes = bytesLeft;
+ nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(inputStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbManager> ab = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsCString record;
+ while (!(*pAbort) && more && NS_SUCCEEDED(rv)) {
+ rv = ReadRecord(lineStream, record, &more);
+ if (NS_SUCCEEDED(rv) && !record.IsEmpty()) {
+ // Parse the vCard and build an nsIAbCard from it
+ nsCOMPtr<nsIAbCard> cardFromVCard;
+ rv = ab->EscapedVCardToAbCard(record.get(), getter_AddRefs(cardFromVCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pDb->CreateNewCardAndAddToDB(cardFromVCard, false, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error processing vCard record.\n");
+ }
+ }
+ if (NS_SUCCEEDED(rv) && pProgress) {
+ // This won't be totally accurate, but its the best we can do
+ // considering that lineStream won't give us how many bytes
+ // are actually left.
+ bytesLeft -= record.Length();
+ *pProgress = totalBytes - bytesLeft;
+ }
+ }
+ inputStream->Close();
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error reading the address book - probably incorrect ending\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ return pDb->Commit(nsAddrDBCommitType::kLargeCommit);
+}
+
+nsresult nsVCardAddress::ReadRecord(
+ nsILineInputStream *aLineStream, nsCString &aRecord, bool *aMore)
+{
+ bool more = true;
+ nsresult rv;
+ nsCString line;
+
+ aRecord.Truncate();
+
+ // remove the empty lines.
+ do {
+ rv = aLineStream->ReadLine(line, aMore);
+ }
+ while (line.IsEmpty() && *aMore);
+ if (!*aMore)
+ return rv;
+
+ // read BEGIN:VCARD
+ if (!line.LowerCaseEqualsLiteral("begin:vcard")) {
+ IMPORT_LOG0("*** Expected case-insensitive BEGIN:VCARD at start of vCard\n");
+ rv = NS_ERROR_FAILURE;
+ *aMore = more;
+ return rv;
+ }
+ aRecord.Append(line);
+
+ // read until END:VCARD
+ do {
+ if (!more) {
+ IMPORT_LOG0("*** Expected case-insensitive END:VCARD at start of vCard\n");
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ rv = aLineStream->ReadLine(line, &more);
+ aRecord.AppendLiteral(MSG_LINEBREAK);
+ aRecord.Append(line);
+ } while (!line.LowerCaseEqualsLiteral("end:vcard"));
+
+ *aMore = more;
+ return rv;
+}
diff --git a/mailnews/import/vcard/src/nsVCardAddress.h b/mailnews/import/vcard/src/nsVCardAddress.h
new file mode 100644
index 000000000..bc5e2bd06
--- /dev/null
+++ b/mailnews/import/vcard/src/nsVCardAddress.h
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsVCardAddress_h__
+#define nsVCardAddress_h__
+
+#include "mozilla/Logging.h"
+
+extern PRLogModuleInfo *VCARDLOGMODULE; // Logging module
+
+#define IMPORT_LOG0(x) MOZ_LOG(VCARDLOGMODULE, mozilla::LogLevel::Debug, (x))
+#define IMPORT_LOG1(x, y) MOZ_LOG(VCARDLOGMODULE, mozilla::LogLevel::Debug, (x, y))
+#define IMPORT_LOG2(x, y, z) MOZ_LOG(VCARDLOGMODULE, mozilla::LogLevel::Debug, (x, y, z))
+#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(VCARDLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d))
+
+class nsIAddrDatabase;
+class nsIFile;
+class nsILineInputStream;
+
+class nsVCardAddress {
+public:
+ nsVCardAddress();
+ virtual ~nsVCardAddress();
+
+ nsresult ImportAddresses(
+ bool *pAbort,
+ const char16_t *pName,
+ nsIFile *pSrc,
+ nsIAddrDatabase *pDb,
+ nsString& errors,
+ uint32_t *pProgress);
+
+private:
+ static nsresult ReadRecord(
+ nsILineInputStream *aLineStream, nsCString &aRecord, bool *aMore);
+};
+
+#endif /* nsVCardAddress_h__ */
+
diff --git a/mailnews/import/vcard/src/nsVCardImport.cpp b/mailnews/import/vcard/src/nsVCardImport.cpp
new file mode 100644
index 000000000..fc674e496
--- /dev/null
+++ b/mailnews/import/vcard/src/nsVCardImport.cpp
@@ -0,0 +1,398 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ VCard import addressbook interfaces
+*/
+#include "nscore.h"
+#include "nsIAddrDatabase.h"
+#include "nsIFile.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportFieldMap.h"
+#include "nsIImportGeneric.h"
+#include "nsIMutableArray.h"
+#include "nsCOMPtr.h"
+#include "nsIImportService.h"
+#include "nsIFile.h"
+#include "nsImportStringBundle.h"
+#include "nsMsgUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTextFormatter.h"
+#include "nsVCardAddress.h"
+#include "nsVCardImport.h"
+
+PRLogModuleInfo *VCARDLOGMODULE = nullptr;
+static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
+
+class ImportVCardAddressImpl : public nsIImportAddressBooks
+{
+public:
+ explicit ImportVCardAddressImpl(nsIStringBundle* aStringBundle);
+
+ static nsresult Create(
+ nsIImportAddressBooks** aImport, nsIStringBundle* aStringBundle);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIImportAddressBooks interface
+
+ // TODO: support multiple vCard files in future - shouldn't be too hard,
+ // since you just import each file in turn.
+ NS_IMETHOD GetSupportsMultiple(bool *_retval) override
+ { *_retval = false; return NS_OK;}
+
+ NS_IMETHOD GetAutoFind(char16_t **description, bool *_retval) override;
+
+ NS_IMETHOD GetNeedsFieldMap(nsIFile *location, bool *_retval) override
+ { *_retval = false; return NS_OK;}
+
+ NS_IMETHOD GetDefaultLocation(
+ nsIFile **location, bool *found, bool *userVerify) override;
+
+ NS_IMETHOD FindAddressBooks(nsIFile *location, nsIArray **_retval) override;
+
+ NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap) override
+ { return NS_ERROR_FAILURE;}
+
+ NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source,
+ nsIAddrDatabase *destination,
+ nsIImportFieldMap *fieldMap,
+ nsISupports *aSupportService,
+ char16_t **errorLog,
+ char16_t **successLog,
+ bool *fatalError) override;
+
+ NS_IMETHOD GetImportProgress(uint32_t *_retval) override;
+
+ NS_IMETHOD GetSampleData(int32_t index, bool *pFound, char16_t **pStr) override
+ { return NS_ERROR_FAILURE;}
+
+ NS_IMETHOD SetSampleLocation(nsIFile *) override
+ { return NS_ERROR_FAILURE; }
+
+private:
+ virtual ~ImportVCardAddressImpl();
+ static void ReportSuccess(
+ nsString& name, nsString *pStream, nsIStringBundle* pBundle);
+ static void SetLogs(
+ nsString& success, nsString& error,
+ char16_t **pError, char16_t **pSuccess);
+ static void ReportError(
+ const char *errorName, nsString& name, nsString *pStream,
+ nsIStringBundle* pBundle);
+
+private:
+ nsVCardAddress m_vCard;
+ nsCOMPtr<nsIFile> m_fileLoc;
+ uint32_t m_bytesImported;
+ nsCOMPtr<nsIStringBundle> m_notProxyBundle;
+};
+
+nsVCardImport::nsVCardImport()
+{
+ if (!VCARDLOGMODULE)
+ VCARDLOGMODULE = PR_NewLogModule("IMPORT");
+
+ nsImportStringBundle::GetStringBundle(
+ VCARDIMPORT_MSGS_URL, getter_AddRefs(m_stringBundle));
+
+ IMPORT_LOG0("nsVCardImport Module Created\n");
+}
+
+nsVCardImport::~nsVCardImport()
+{
+ IMPORT_LOG0("nsVCardImport Module Deleted\n");
+}
+
+NS_IMPL_ISUPPORTS(nsVCardImport, nsIImportModule)
+
+NS_IMETHODIMP nsVCardImport::GetName(char16_t **name)
+{
+ NS_ENSURE_ARG_POINTER(name);
+ *name = nsImportStringBundle::GetStringByName(
+ "vCardImportName", m_stringBundle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVCardImport::GetDescription(char16_t **name)
+{
+ NS_ENSURE_ARG_POINTER(name);
+ *name = nsImportStringBundle::GetStringByName(
+ "vCardImportDescription", m_stringBundle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVCardImport::GetSupports(char **supports)
+{
+ NS_ENSURE_ARG_POINTER(supports);
+ *supports = strdup(NS_IMPORT_ADDRESS_STR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVCardImport::GetSupportsUpgrade(bool *pUpgrade)
+{
+ NS_ENSURE_ARG_POINTER(pUpgrade);
+ *pUpgrade = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVCardImport::GetImportInterface(
+ const char *pImportType, nsISupports **ppInterface)
+{
+ NS_ENSURE_ARG_POINTER(pImportType);
+ NS_ENSURE_ARG_POINTER(ppInterface);
+ *ppInterface = nullptr;
+ if (!strcmp(pImportType, "addressbook")) {
+ nsresult rv;
+ // create the nsIImportMail interface and return it!
+ nsIImportAddressBooks *pAddress = nullptr;
+ nsIImportGeneric *pGeneric = nullptr;
+ rv = ImportVCardAddressImpl::Create(&pAddress, m_stringBundle);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = impSvc->CreateNewGenericAddressBooks(&pGeneric);
+ if (NS_SUCCEEDED(rv)) {
+ pGeneric->SetData("addressInterface", pAddress);
+ rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface);
+ }
+ }
+ }
+ NS_IF_RELEASE(pAddress);
+ NS_IF_RELEASE(pGeneric);
+ return rv;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult ImportVCardAddressImpl::Create(
+ nsIImportAddressBooks** aImport, nsIStringBundle* aStringBundle)
+{
+ NS_ENSURE_ARG_POINTER(aImport);
+ *aImport = new ImportVCardAddressImpl(aStringBundle);
+ if (!*aImport)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+ImportVCardAddressImpl::ImportVCardAddressImpl(
+ nsIStringBundle* aStringBundle) : m_notProxyBundle(aStringBundle)
+{
+}
+
+ImportVCardAddressImpl::~ImportVCardAddressImpl()
+{
+}
+
+NS_IMPL_ISUPPORTS(ImportVCardAddressImpl, nsIImportAddressBooks)
+
+NS_IMETHODIMP ImportVCardAddressImpl::GetAutoFind(
+ char16_t **addrDescription, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(addrDescription);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsString str;
+ *_retval = false;
+
+ if (!m_notProxyBundle)
+ return NS_ERROR_FAILURE;
+
+ nsImportStringBundle::GetStringByName("vCardImportAddressName", m_notProxyBundle, str);
+ *addrDescription = ToNewUnicode(str);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::GetDefaultLocation(
+ nsIFile **ppLoc, bool *found, bool *userVerify)
+{
+ NS_ENSURE_ARG_POINTER(found);
+ NS_ENSURE_ARG_POINTER(ppLoc);
+ NS_ENSURE_ARG_POINTER(userVerify);
+
+ *ppLoc = nullptr;
+ *found = false;
+ *userVerify = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::FindAddressBooks(
+ nsIFile *pLoc, nsIArray **ppArray)
+{
+ NS_ENSURE_ARG_POINTER(pLoc);
+ NS_ENSURE_ARG_POINTER(ppArray);
+
+ *ppArray = nullptr;
+ bool exists = false;
+ nsresult rv = pLoc->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return NS_ERROR_FAILURE;
+
+ bool isFile = false;
+ rv = pLoc->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile)
+ return NS_ERROR_FAILURE;
+
+ m_fileLoc = do_QueryInterface(pLoc);
+
+ /* Build an address book descriptor based on the file passed in! */
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("FAILED to allocate the nsIMutableArray\n");
+ return rv;
+ }
+
+ nsString name;
+ m_fileLoc->GetLeafName(name);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed getting leaf name of file\n");
+ return rv;
+ }
+
+ int32_t idx = name.RFindChar('.');
+ if ((idx != -1) && (idx > 0) && ((name.Length() - idx - 1) < 5)) {
+ name.SetLength(idx);
+ }
+
+ nsCOMPtr<nsIImportABDescriptor> desc;
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to obtain the import service\n");
+ return rv;
+ }
+
+ rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc));
+ if (NS_SUCCEEDED(rv)) {
+ int64_t sz = 0;
+ pLoc->GetFileSize(&sz);
+ desc->SetPreferredName(name);
+ desc->SetSize((uint32_t) sz);
+ desc->SetAbFile(m_fileLoc);
+ nsCOMPtr<nsISupports> pInterface(do_QueryInterface(desc, &rv));
+ array->AppendElement(pInterface, false);
+ }
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0(
+ "*** Error creating address book descriptor for vCard import\n");
+ return rv;
+ }
+
+ array.forget(ppArray);
+ return NS_OK;
+}
+
+void ImportVCardAddressImpl::ReportSuccess(
+ nsString& name, nsString *pStream, nsIStringBundle* pBundle)
+{
+ if (!pStream)
+ return;
+
+ // load the success string
+ char16_t *pFmt = nsImportStringBundle::GetStringByName(
+ "vCardImportAddressSuccess", pBundle);
+
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get());
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ NS_Free(pFmt);
+ pStream->Append(char16_t('\n'));
+}
+
+void ImportVCardAddressImpl::ReportError(
+ const char *errorName, nsString& name, nsString *pStream,
+ nsIStringBundle* pBundle)
+{
+ if (!pStream)
+ return;
+
+ // load the error string
+ char16_t *pFmt = nsImportStringBundle::GetStringByName(errorName, pBundle);
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get());
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ NS_Free(pFmt);
+ pStream->Append(char16_t('\n'));
+}
+
+void ImportVCardAddressImpl::SetLogs(
+ nsString& success, nsString& error,
+ char16_t **pError, char16_t **pSuccess)
+{
+ if (pError)
+ *pError = ToNewUnicode(error);
+ if (pSuccess)
+ *pSuccess = ToNewUnicode(success);
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::ImportAddressBook(
+ nsIImportABDescriptor *pSource,
+ nsIAddrDatabase *pDestination,
+ nsIImportFieldMap *fieldMap,
+ nsISupports *aSupportService,
+ char16_t ** pErrorLog,
+ char16_t ** pSuccessLog,
+ bool * fatalError)
+{
+ NS_ENSURE_ARG_POINTER(pSource);
+ NS_ENSURE_ARG_POINTER(pDestination);
+ NS_ENSURE_ARG_POINTER(fatalError);
+
+ if (!m_notProxyBundle)
+ return NS_ERROR_FAILURE;
+
+ m_bytesImported = 0;
+ nsString success, error;
+ bool addrAbort = false;
+ nsString name;
+ pSource->GetPreferredName(name);
+
+ uint32_t addressSize = 0;
+ pSource->GetSize(&addressSize);
+ if (addressSize == 0) {
+ IMPORT_LOG0("Address book size is 0, skipping import.\n");
+ ReportSuccess(name, &success, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> inFile;
+ if (NS_FAILED(pSource->GetAbFile(getter_AddRefs(inFile)))) {
+ ReportError("vCardImportAddressBadSourceFile", name, &error, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aSupportService) {
+ IMPORT_LOG0("Missing support service to import call\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = m_vCard.ImportAddresses(
+ &addrAbort, name.get(), inFile, pDestination, error, &m_bytesImported);
+
+ if (NS_SUCCEEDED(rv) && error.IsEmpty()) {
+ ReportSuccess(name, &success, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ }
+ else {
+ ReportError("vCardImportAddressConvertError", name, &error, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ }
+
+ IMPORT_LOG0("*** VCard address import done\n");
+ return rv;
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::GetImportProgress(uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_bytesImported;
+ return NS_OK;
+}
diff --git a/mailnews/import/vcard/src/nsVCardImport.h b/mailnews/import/vcard/src/nsVCardImport.h
new file mode 100644
index 000000000..3204412a2
--- /dev/null
+++ b/mailnews/import/vcard/src/nsVCardImport.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsVCardImport_h___
+#define nsVCardImport_h___
+
+#include "nsIImportModule.h"
+#include "nsIStringBundle.h"
+#include "nsCOMPtr.h"
+
+#define NS_VCARDIMPORT_CID \
+{ /* 0EB034A3-964A-4E2F-92EBCC55D9AE9DD2 */ \
+ 0x0eb034a3, 0x964a, 0x4e2f, \
+ {0x92, 0xeb, 0xcc, 0x55, 0xd9, 0xae, 0x9d, 0xd2}}
+
+#define VCARDIMPORT_MSGS_URL "chrome://messenger/locale/vCardImportMsgs.properties"
+
+class nsVCardImport : public nsIImportModule
+{
+public:
+
+ nsVCardImport();
+
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we suppport the nsIImportModule interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ NS_DECL_NSIIMPORTMODULE
+
+protected:
+ virtual ~nsVCardImport();
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+};
+
+#endif /* nsVCardImport_h___ */
diff --git a/mailnews/import/winlivemail/WMDebugLog.h b/mailnews/import/winlivemail/WMDebugLog.h
new file mode 100644
index 000000000..c565880a5
--- /dev/null
+++ b/mailnews/import/winlivemail/WMDebugLog.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WMDebugLog_h___
+#define WMDebugLog_h___
+
+// Use PR_LOG for logging.
+#include "mozilla/Logging.h"
+extern PRLogModuleInfo *WMLOGMODULE; // Logging module
+
+#define IMPORT_LOG0(x) MOZ_LOG(WMLOGMODULE, mozilla::LogLevel::Debug, (x))
+#define IMPORT_LOG1(x, y) MOZ_LOG(WMLOGMODULE, mozilla::LogLevel::Debug, (x, y))
+#define IMPORT_LOG2(x, y, z) MOZ_LOG(WMLOGMODULE, mozilla::LogLevel::Debug, (x, y, z))
+#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(WMLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d))
+
+
+
+#endif /* WMDebugLog_h___ */
diff --git a/mailnews/import/winlivemail/moz.build b/mailnews/import/winlivemail/moz.build
new file mode 100644
index 000000000..cb69b1548
--- /dev/null
+++ b/mailnews/import/winlivemail/moz.build
@@ -0,0 +1,14 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsWMImport.cpp',
+ 'nsWMSettings.cpp',
+ 'nsWMStringBundle.cpp',
+ 'nsWMUtils.cpp',
+]
+
+FINAL_LIBRARY = 'import'
+
diff --git a/mailnews/import/winlivemail/nsWMImport.cpp b/mailnews/import/winlivemail/nsWMImport.cpp
new file mode 100644
index 000000000..f9795816a
--- /dev/null
+++ b/mailnews/import/winlivemail/nsWMImport.cpp
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/*
+
+ Windows Live Mail (Win32) import mail and addressbook interfaces
+
+*/
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsMsgUtils.h"
+#include "nsIServiceManager.h"
+#include "nsIImportService.h"
+#include "nsWMImport.h"
+#include "nsIMemory.h"
+#include "nsIImportService.h"
+#include "nsIImportMail.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIImportGeneric.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIImportFieldMap.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIOutputStream.h"
+#include "nsIAddrDatabase.h"
+#include "nsWMSettings.h"
+#include "nsTextFormatter.h"
+#include "nsWMStringBundle.h"
+#include "nsIStringBundle.h"
+#include "nsUnicharUtils.h"
+
+#include "WMDebugLog.h"
+
+static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
+PRLogModuleInfo *WMLOGMODULE = nullptr;
+
+class ImportWMMailImpl : public nsIImportMail
+{
+public:
+ ImportWMMailImpl();
+
+ static nsresult Create(nsIImportMail** aImport);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIImportmail interface
+
+ /* void GetDefaultLocation (out nsIFile location, out boolean found, out boolean userVerify); */
+ NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found, bool *userVerify);
+
+ /* nsIArray FindMailboxes (in nsIFile location); */
+ NS_IMETHOD FindMailboxes(nsIFile *location, nsIArray **_retval);
+
+ NS_IMETHOD ImportMailbox(nsIImportMailboxDescriptor *source,
+ nsIMsgFolder *dstFolder,
+ char16_t **pErrorLog, char16_t **pSuccessLog,
+ bool *fatalError);
+
+ /* unsigned long GetImportProgress (); */
+ NS_IMETHOD GetImportProgress(uint32_t *_retval);
+
+ NS_IMETHOD TranslateFolderName(const nsAString & aFolderName, nsAString & _retval);
+
+public:
+ static void ReportSuccess(nsString& name, int32_t count, nsString *pStream);
+ static void ReportError(int32_t errorNum, nsString& name, nsString *pStream);
+ static void AddLinebreak(nsString *pStream);
+ static void SetLogs(nsString& success, nsString& error, char16_t **pError, char16_t **pSuccess);
+
+private:
+ virtual ~ImportWMMailImpl();
+ uint32_t m_bytesDone;
+};
+
+nsWMImport::nsWMImport()
+{
+ // Init logging module.
+ if (!WMLOGMODULE)
+ WMLOGMODULE = PR_NewLogModule("IMPORT");
+ IMPORT_LOG0("nsWMImport Module Created\n");
+ nsWMStringBundle::GetStringBundle();
+}
+
+nsWMImport::~nsWMImport()
+{
+ IMPORT_LOG0("nsWMImport Module Deleted\n");
+}
+
+NS_IMPL_ISUPPORTS(nsWMImport, nsIImportModule)
+
+NS_IMETHODIMP nsWMImport::GetName(char16_t **name)
+{
+ NS_ENSURE_ARG_POINTER(name);
+ // nsString title = "Windows Live Mail";
+ // *name = ToNewUnicode(title);
+ *name = nsWMStringBundle::GetStringByID(WMIMPORT_NAME);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMImport::GetDescription(char16_t **name)
+{
+ NS_ENSURE_ARG_POINTER(name);
+
+ // nsString desc = "Windows Live Mail mail and address books";
+ // *name = ToNewUnicode(desc);
+ *name = nsWMStringBundle::GetStringByID(WMIMPORT_DESCRIPTION);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMImport::GetSupports(char **supports)
+{
+ NS_PRECONDITION(supports != nullptr, "null ptr");
+ if (! supports)
+ return NS_ERROR_NULL_POINTER;
+
+ *supports = strdup(kWMSupportsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMImport::GetSupportsUpgrade(bool *pUpgrade)
+{
+ NS_PRECONDITION(pUpgrade != nullptr, "null ptr");
+ if (! pUpgrade)
+ return NS_ERROR_NULL_POINTER;
+
+ *pUpgrade = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMImport::GetImportInterface(const char *pImportType,
+ nsISupports **ppInterface)
+{
+ NS_ENSURE_ARG_POINTER(pImportType);
+ NS_ENSURE_ARG_POINTER(ppInterface);
+
+ *ppInterface = nullptr;
+ nsresult rv;
+
+ if (!strcmp(pImportType, "settings")) {
+ nsIImportSettings *pSettings = nullptr;
+ rv = nsWMSettings::Create(&pSettings);
+ if (NS_SUCCEEDED(rv)) {
+ pSettings->QueryInterface(kISupportsIID, (void **)ppInterface);
+ }
+ NS_IF_RELEASE(pSettings);
+ return rv;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+nsresult ImportWMMailImpl::Create(nsIImportMail** aImport)
+{
+ NS_ENSURE_ARG_POINTER(aImport);
+ *aImport = new ImportWMMailImpl();
+ NS_ENSURE_TRUE(*aImport, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+ImportWMMailImpl::ImportWMMailImpl()
+{
+}
+
+ImportWMMailImpl::~ImportWMMailImpl()
+{
+}
+
+NS_IMPL_ISUPPORTS(ImportWMMailImpl, nsIImportMail)
+
+NS_IMETHODIMP ImportWMMailImpl::TranslateFolderName(const nsAString & aFolderName, nsAString & _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ImportWMMailImpl::GetDefaultLocation(nsIFile **ppLoc, bool *found,
+ bool *userVerify)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ImportWMMailImpl::FindMailboxes(nsIFile *pLoc,
+ nsIArray **ppArray)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void ImportWMMailImpl::AddLinebreak(nsString *pStream)
+{
+ if (pStream)
+ pStream->Append(char16_t('\n'));
+}
+
+void ImportWMMailImpl::ReportSuccess(nsString& name, int32_t count, nsString *pStream)
+{
+ if (!pStream)
+ return;
+ // load the success string
+ char16_t *pFmt = nsWMStringBundle::GetStringByID(WMIMPORT_MAILBOX_SUCCESS);
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get(), count);
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ nsWMStringBundle::FreeString(pFmt);
+ AddLinebreak(pStream);
+}
+
+void ImportWMMailImpl::ReportError(int32_t errorNum, nsString& name, nsString *pStream)
+{
+ if (!pStream)
+ return;
+ // load the error string
+ char16_t *pFmt = nsWMStringBundle::GetStringByID(errorNum);
+ char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get());
+ pStream->Append(pText);
+ nsTextFormatter::smprintf_free(pText);
+ nsWMStringBundle::FreeString(pFmt);
+ AddLinebreak(pStream);
+}
+
+void ImportWMMailImpl::SetLogs(nsString& success, nsString& error,
+ char16_t **pError, char16_t **pSuccess)
+{
+ if (pError)
+ *pError = ToNewUnicode(error);
+ if (pSuccess)
+ *pSuccess = ToNewUnicode(success);
+}
+
+NS_IMETHODIMP ImportWMMailImpl::ImportMailbox(nsIImportMailboxDescriptor *pSource,
+ nsIMsgFolder *pDstFolder,
+ char16_t **pErrorLog,
+ char16_t **pSuccessLog,
+ bool *fatalError)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ImportWMMailImpl::GetImportProgress(uint32_t *pDoneSoFar)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/mailnews/import/winlivemail/nsWMImport.h b/mailnews/import/winlivemail/nsWMImport.h
new file mode 100644
index 000000000..60b34047c
--- /dev/null
+++ b/mailnews/import/winlivemail/nsWMImport.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWMImport_h___
+#define nsWMImport_h___
+
+#include "nsIImportModule.h"
+#include "nsCOMPtr.h"
+
+#define NS_WMIMPORT_CID \
+{ /* 42bc82bc-8e9f-4597-8b6e-e529daaf3af1 */ \
+ 0x42bc82bc, 0x8e9f, 0x4597, \
+ {0x8b, 0x6e, 0xe5, 0x29, 0xda, 0xaf, 0x3a, 0xf1}}
+
+// currently only support setting import
+#define kWMSupportsString NS_IMPORT_SETTINGS_STR
+
+class nsWMImport : public nsIImportModule
+{
+public:
+
+ nsWMImport();
+
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we suppport the nsIImportModule interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ NS_DECL_NSIIMPORTMODULE
+
+protected:
+ virtual ~nsWMImport();
+};
+
+#endif /* nsWMImport_h___ */
diff --git a/mailnews/import/winlivemail/nsWMSettings.cpp b/mailnews/import/winlivemail/nsWMSettings.cpp
new file mode 100644
index 000000000..383a31bb8
--- /dev/null
+++ b/mailnews/import/winlivemail/nsWMSettings.cpp
@@ -0,0 +1,758 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ Windows Live Mail (Win32) settings
+
+*/
+
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsMsgUtils.h"
+#include "nsWMImport.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgAccount.h"
+#include "nsIImportSettings.h"
+#include "nsWMSettings.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgCompCID.h"
+#include "nsMsgI18N.h"
+#include "nsISmtpService.h"
+#include "nsISmtpServer.h"
+#include "nsWMStringBundle.h"
+#include "WMDebugLog.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsIImapIncomingServer.h"
+#include "nsINntpIncomingServer.h"
+#include "stdlib.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIMutableArray.h"
+#include "nsIDOMDocument.h"
+#include "nsNetUtil.h"
+#include "nsIDOMNodeList.h"
+#include "nsIFileStreams.h"
+#include "nsIDOMParser.h"
+#include "nsIDOMElement.h"
+#include "nsTArray.h"
+#include <windows.h>
+#include "nsIWindowsRegKey.h"
+#include "nsCOMArray.h"
+#include "nsWMUtils.h"
+
+class WMSettings {
+public:
+ static bool DoImport(nsIMsgAccount **ppAccount);
+ static bool DoIMAPServer(nsIMsgAccountManager *pMgr,
+ nsIDOMDocument *xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount **ppAccount);
+ static bool DoPOP3Server(nsIMsgAccountManager *pMgr,
+ nsIDOMDocument *xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount **ppAccount);
+ static bool DoNNTPServer(nsIMsgAccountManager *pMgr,
+ nsIDOMDocument *xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount **ppAccount);
+ static void SetIdentities(nsIMsgAccountManager *pMgr, nsIMsgAccount *pAcc,
+ nsIDOMDocument *xmlDoc, nsAutoString &userName,
+ int32_t authMethodIncoming, bool isNNTP);
+ static void SetSmtpServer(nsIDOMDocument *xmlDoc, nsIMsgIdentity *id,
+ nsAutoString& inUserName, int32_t authMethodIncoming);
+};
+
+static int32_t checkNewMailTime;// WM global setting, let's default to 30
+static bool checkNewMail; // WM global setting, let's default to false
+ // This won't cause unwanted autodownloads-
+ // user can set prefs after import
+
+////////////////////////////////////////////////////////////////////////
+nsresult nsWMSettings::Create(nsIImportSettings** aImport)
+{
+ NS_PRECONDITION(aImport != nullptr, "null ptr");
+ if (! aImport)
+ return NS_ERROR_NULL_POINTER;
+
+ *aImport = new nsWMSettings();
+ if (! *aImport)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aImport);
+ return NS_OK;
+}
+
+nsWMSettings::nsWMSettings()
+{
+}
+
+nsWMSettings::~nsWMSettings()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsWMSettings, nsIImportSettings)
+
+NS_IMETHODIMP nsWMSettings::AutoLocate(char16_t **description,
+ nsIFile **location, bool *_retval)
+{
+ NS_PRECONDITION(description != nullptr, "null ptr");
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (!description || !_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *description = nsWMStringBundle::GetStringByID(WMIMPORT_NAME);
+ *_retval = false;
+
+ if (location)
+ *location = nullptr;
+ nsCOMPtr<nsIWindowsRegKey> key;
+ if (NS_SUCCEEDED(nsWMUtils::FindWMKey(getter_AddRefs(key))))
+ *_retval = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMSettings::SetLocation(nsIFile *location)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMSettings::Import(nsIMsgAccount **localMailAccount,
+ bool *_retval)
+{
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+
+ if (WMSettings::DoImport(localMailAccount)) {
+ *_retval = true;
+ IMPORT_LOG0("Settings import appears successful\n");
+ }
+ else {
+ *_retval = false;
+ IMPORT_LOG0("Settings import returned FALSE\n");
+ }
+
+ return NS_OK;
+}
+
+bool WMSettings::DoImport(nsIMsgAccount **ppAccount)
+{
+ // do the windows registry stuff first
+ nsCOMPtr<nsIWindowsRegKey> key;
+ if (NS_FAILED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) {
+ IMPORT_LOG0("*** Error finding Windows Live Mail registry account keys\n");
+ return false;
+ }
+ // 'poll for messages' setting in WM is a global setting-Like OE
+ // for all accounts dword ==0xffffffff for don't poll else 1/60000 = minutes
+ checkNewMailTime = 30;
+ checkNewMail = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> subKey;
+ if (NS_SUCCEEDED(key->OpenChild(NS_LITERAL_STRING("mail"),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE,
+ getter_AddRefs(subKey)))) {
+ uint32_t dwordResult = -1;
+ rv = subKey->ReadIntValue(NS_LITERAL_STRING("Poll For Mail"), &dwordResult); // reg_dword
+ subKey->Close();
+ if (NS_SUCCEEDED(rv) && dwordResult != -1){
+ checkNewMail = true;
+ checkNewMailTime = dwordResult / 60000;
+ }
+ }
+ // these are in main windowsmail key and if they don't exist-not to worry
+ // (less than 64 chars) e.g. account{4A18B81E-83CA-472A-8D7F-5301C0B97B8D}.oeaccount
+ nsAutoString defMailAcct, defNewsAcct;
+ key->ReadStringValue(NS_LITERAL_STRING("Default Mail Account"), defMailAcct); // ref_sz
+ key->ReadStringValue(NS_LITERAL_STRING("Default News Account"), defNewsAcct); // ref_sz
+
+ nsCOMPtr<nsIMsgAccountManager> accMgr =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create an account manager!\n");
+ return false;
+ }
+
+ nsCOMArray<nsIFile> fileArray;
+ if (NS_FAILED(nsWMUtils::GetOEAccountFiles(fileArray))) {
+ IMPORT_LOG0("*** Failed to get .oeaccount file!\n");
+ return false;
+ }
+
+ // Loop through *.oeaccounts files looking for POP3 & IMAP & NNTP accounts
+ // Ignore LDAP for now!
+ int accounts = 0;
+ nsCOMPtr<nsIDOMDocument> xmlDoc;
+
+ for (int32_t i = fileArray.Count() - 1 ; i >= 0; i--){
+ nsWMUtils::MakeXMLdoc(getter_AddRefs(xmlDoc), fileArray[i]);
+
+ nsAutoCString name;
+ fileArray[i]->GetNativeLeafName(name);
+ nsAutoString value;
+ nsCOMPtr<nsIMsgAccount> anAccount;
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "IMAP_Server",
+ value)))
+ if (DoIMAPServer(accMgr, xmlDoc, value, getter_AddRefs(anAccount)))
+ accounts++;
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "NNTP_Server",
+ value)))
+ if (DoNNTPServer(accMgr, xmlDoc, value, getter_AddRefs(anAccount)))
+ accounts++;
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "POP3_Server",
+ value)))
+ if (DoPOP3Server(accMgr, xmlDoc, value, getter_AddRefs(anAccount)))
+ accounts++;
+
+ if (anAccount) {
+ nsString name;
+ // Is this the default account?
+ fileArray[i]->GetLeafName(name);
+ if (defMailAcct.Equals(name))
+ accMgr->SetDefaultAccount(anAccount);
+ }
+ }
+
+ // Now save the new acct info to pref file.
+ rv = accMgr->SaveAccountInfo();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file");
+
+ return accounts != 0;
+}
+
+bool WMSettings::DoIMAPServer(nsIMsgAccountManager *pMgr,
+ nsIDOMDocument *xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount **ppAccount)
+{
+ int32_t authMethod; // Secure Password Authentication (SPA)
+ nsresult errorCode;
+ if (ppAccount)
+ *ppAccount = nullptr;
+
+ nsAutoString userName, value;
+ if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc,
+ "IMAP_User_Name",
+ userName)))
+ return false;
+ bool result = false;
+ // I now have a user name/server name pair, find out if it already exists?
+ nsCOMPtr<nsIMsgIncomingServer> in;
+ nsresult rv = pMgr->FindServer(NS_ConvertUTF16toUTF8(userName),
+ NS_ConvertUTF16toUTF8(serverName),
+ NS_LITERAL_CSTRING("imap"),
+ getter_AddRefs(in));
+ if (NS_FAILED(rv) || (in == nullptr)) {
+ // Create the incoming server and an account for it?
+ rv = pMgr->CreateIncomingServer(NS_ConvertUTF16toUTF8(userName),
+ NS_ConvertUTF16toUTF8(serverName),
+ NS_LITERAL_CSTRING("imap"),
+ getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv) && in) {
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(in);
+ if (!imapServer){
+ IMPORT_LOG1("*** Failed to create nsIImapIncomingServer for %S!\n",
+ serverName.get());
+ return false;
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "IMAP_Root_Folder",
+ value))) {
+ imapServer->SetServerDirectory(NS_ConvertUTF16toUTF8(value));
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "IMAP_Secure_Connection",
+ value))) {
+ if (value.ToInteger(&errorCode, 16))
+ in->SetSocketType(nsMsgSocketType::SSL);
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "IMAP_Use_Sicily",
+ value))) {
+ bool secAuth = (bool)value.ToInteger(&errorCode, 16);
+ authMethod = secAuth ? nsMsgAuthMethod::secure :
+ nsMsgAuthMethod::passwordCleartext;
+ in->SetAuthMethod(authMethod);
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "IMAP_Port",
+ value))) {
+ in->SetPort(value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "Account_Name",
+ value))) {
+ rv = in->SetPrettyName(value);
+ }
+ in->SetDoBiff(checkNewMail);
+ in->SetBiffMinutes(checkNewMailTime);
+
+ IMPORT_LOG2("Created IMAP server named: %S, userName: %S\n",
+ serverName.get(), userName.get());
+
+ // We have a server, create an account.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->CreateAccount(getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ rv = account->SetIncomingServer(in);
+
+ IMPORT_LOG0("Created an account and set the IMAP server "
+ "as the incoming server\n");
+
+ // Fiddle with the identities
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false);
+ result = true;
+ if (ppAccount)
+ account.forget(ppAccount);
+ }
+ }
+ }
+ else if (NS_SUCCEEDED(rv) && in) {
+ // for an existing server we create another identity,
+ // TB lists under 'manage identities'
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->FindAccountForServer(in, getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ IMPORT_LOG0("Created an identity and added to existing "
+ "IMAP incoming server\n");
+ // Fiddle with the identities
+ in->GetAuthMethod(&authMethod);
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false);
+ result = true;
+ if (ppAccount)
+ account.forget(ppAccount);
+ }
+ }
+ else
+ result = true;
+ return result;
+}
+
+bool WMSettings::DoPOP3Server(nsIMsgAccountManager *pMgr,
+ nsIDOMDocument *xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount **ppAccount)
+{
+ int32_t authMethod; // Secure Password Authentication (SPA)
+ nsresult errorCode;
+ if (ppAccount)
+ *ppAccount = nullptr;
+
+ nsAutoString userName, value;
+ if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc,
+ "POP3_User_Name",
+ userName)))
+ return false;
+ bool result = false;
+ // I now have a user name/server name pair, find out if it already exists?
+ nsCOMPtr<nsIMsgIncomingServer> in;
+ nsresult rv = pMgr->FindServer(NS_ConvertUTF16toUTF8(userName),
+ NS_ConvertUTF16toUTF8(serverName),
+ NS_LITERAL_CSTRING("pop3"),
+ getter_AddRefs(in));
+ if (NS_FAILED(rv) || (in == nullptr)) {
+ // Create the incoming server and an account for it?
+ rv = pMgr->CreateIncomingServer(NS_ConvertUTF16toUTF8(userName),
+ NS_ConvertUTF16toUTF8(serverName),
+ NS_LITERAL_CSTRING("pop3"),
+ getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv) && in) {
+ nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(in);
+ if (!pop3Server){
+ IMPORT_LOG1("*** Failed to create nsIPop3IncomingServer for %S!\n",
+ serverName.get());
+ return false;
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "POP3_Secure_Connection",
+ value)) &&
+ value.ToInteger(&errorCode, 16)) {
+ in->SetSocketType(nsMsgSocketType::SSL);
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "POP3_Use_Sicily",
+ value))) {
+ bool secAuth = (bool)value.ToInteger(&errorCode, 16);
+ authMethod = secAuth ? nsMsgAuthMethod::secure :
+ nsMsgAuthMethod::passwordCleartext;
+ in->SetAuthMethod(authMethod);
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "POP3_Port",
+ value))) {
+ in->SetPort(value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "POP3_Skip_Account",
+ value))) {
+ if (!value.IsEmpty())
+ // OE:0=='Include this account when receiving mail or synchronizing'==
+ // TB:1==ActMgr:Server:advanced:Include this server when getting new mail
+ pop3Server->SetDeferGetNewMail(value.ToInteger(&errorCode, 16) == 0);
+ else
+ pop3Server->SetDeferGetNewMail(false);
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "Leave_Mail_On_Server",
+ value))) {
+ pop3Server->SetLeaveMessagesOnServer((bool)value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "Remove_When_Deleted",
+ value))) {
+ pop3Server->SetDeleteMailLeftOnServer((bool)value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "Remove_When_Expired",
+ value))) {
+ pop3Server->SetDeleteByAgeFromServer((bool)value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "Expire_Days",
+ value))) {
+ pop3Server->SetNumDaysToLeaveOnServer(value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "Account_Name",
+ value))) {
+ rv = in->SetPrettyName(value);
+ }
+
+ in->SetDoBiff(checkNewMail);
+ in->SetBiffMinutes(checkNewMailTime);
+
+ // set local folders as the Inbox to use for this POP3 server
+ nsCOMPtr<nsIMsgIncomingServer> localFoldersServer;
+ pMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer));
+ if (!localFoldersServer) {
+ // XXX: We may need to move this local folder creation
+ // code to the generic nsImportSettings code
+ // if the other import modules end up needing to do this too.
+ // if Local Folders does not exist already, create it
+ rv = pMgr->CreateLocalMailAccount();
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create Local Folders!\n");
+ return false;
+ }
+ pMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer));
+ }
+
+ // now get the account for this server
+ nsCOMPtr<nsIMsgAccount> localFoldersAccount;
+ pMgr->FindAccountForServer(localFoldersServer,
+ getter_AddRefs(localFoldersAccount));
+ if (localFoldersAccount) {
+ nsCString localFoldersAcctKey;
+ localFoldersAccount->GetKey(localFoldersAcctKey);
+ pop3Server->SetDeferredToAccount(localFoldersAcctKey);
+ }
+
+ IMPORT_LOG2("Created POP3 server named: %S, userName: %S\n",
+ serverName.get(), userName.get());
+
+ // We have a server, create an account.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->CreateAccount(getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ rv = account->SetIncomingServer(in);
+ IMPORT_LOG0("Created a new account and set the incoming "
+ "server to the POP3 server.\n");
+
+ // Fiddle with the identities
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false);
+ result = true;
+ if (ppAccount)
+ account.forget(ppAccount);
+ }
+ }
+ }
+ else if (NS_SUCCEEDED(rv) && in) {
+ IMPORT_LOG2("Existing POP3 server named: %S, userName: %S\n",
+ serverName.get(), userName.get());
+ // for an existing server we create another identity,
+ // TB listed under 'manage identities'
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->FindAccountForServer(in, getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ IMPORT_LOG0("Created identity and added to existing POP3 incoming server.\n");
+ // Fiddle with the identities
+ in->GetAuthMethod(&authMethod);
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false);
+ result = true;
+ if (ppAccount)
+ account.forget(ppAccount);
+ }
+ }
+ else
+ result = true;
+ return result;
+}
+
+bool WMSettings::DoNNTPServer(nsIMsgAccountManager *pMgr,
+ nsIDOMDocument *xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount **ppAccount)
+{
+ int32_t authMethod;
+ nsresult errorCode;
+ if (ppAccount)
+ *ppAccount = nullptr;
+
+ nsAutoString userName, value;
+ // this only exists if NNTP server requires it or not, anonymous login
+ nsWMUtils::GetValueForTag(xmlDoc, "NNTP_User_Name", userName);
+ bool result = false;
+
+ // I now have a user name/server name pair, find out if it already exists?
+ // NNTP can have empty user name. This is wild card in findserver
+ nsCOMPtr<nsIMsgIncomingServer> in;
+ nsresult rv = pMgr->FindServer(EmptyCString(),
+ NS_ConvertUTF16toUTF8(serverName),
+ NS_LITERAL_CSTRING("nntp"),
+ getter_AddRefs(in));
+ if (NS_FAILED(rv) || (in == nullptr)) {
+ // Create the incoming server and an account for it?
+ rv = pMgr->CreateIncomingServer(nsDependentCString(""),
+ NS_ConvertUTF16toUTF8(serverName),
+ NS_LITERAL_CSTRING("nntp"),
+ getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv) && in) {
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(in);
+ if (!nntpServer) {
+ IMPORT_LOG1("*** Failed to create nsINnntpIncomingServer for %S!\n",
+ serverName.get());
+ return false;
+ }
+ if (!userName.IsEmpty()) { // if username req'd then auth req'd
+ nntpServer->SetPushAuth(true);
+ in->SetUsername(NS_ConvertUTF16toUTF8(userName));
+ }
+
+ nsAutoString value;
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "NNTP_Port",
+ value))) {
+ in->SetPort(value.ToInteger(&errorCode, 16));
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "Account_Name",
+ value))) {
+ in->SetPrettyName(value);
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "NNTP_Use_Sicily",
+ value))) {
+ bool secAuth = (bool)value.ToInteger(&errorCode, 16);
+ authMethod = secAuth ? nsMsgAuthMethod::secure :
+ nsMsgAuthMethod::passwordCleartext;
+ in->SetAuthMethod(authMethod);
+ }
+
+ IMPORT_LOG2("Created NNTP server named: %S, userName: %S\n",
+ serverName.get(), userName.get());
+
+ // We have a server, create an account.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->CreateAccount(getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ rv = account->SetIncomingServer(in);
+
+ IMPORT_LOG0("Created an account and set the NNTP server "
+ "as the incoming server\n");
+
+ // Fiddle with the identities
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, true);
+ result = true;
+ if (ppAccount)
+ account.forget(ppAccount);
+ }
+ }
+ }
+ else if (NS_SUCCEEDED(rv) && in) {
+ // for the existing server...
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->FindAccountForServer(in, getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ IMPORT_LOG0("Using existing account and set the "
+ "NNTP server as the incoming server\n");
+ // Fiddle with the identities
+ in->GetAuthMethod(&authMethod);
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, true);
+ result = true;
+ if (ppAccount)
+ account.forget(ppAccount);
+ }
+ }
+ else
+ result = true;
+ return result;
+}
+
+void WMSettings::SetIdentities(nsIMsgAccountManager *pMgr, nsIMsgAccount *pAcc,
+ nsIDOMDocument *xmlDoc, nsAutoString &inUserName,
+ int32_t authMethodIncoming, bool isNNTP)
+{
+ // Get the relevant information for an identity
+ nsresult rv;
+ nsAutoString value;
+
+ nsCOMPtr<nsIMsgIdentity> id;
+ rv = pMgr->CreateIdentity(getter_AddRefs(id));
+ if (id) {
+ IMPORT_LOG0("Created identity and added to the account\n");
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ isNNTP ?
+ "NNTP_Display_Name" :
+ "SMTP_Display_Name",
+ value))) {
+ id->SetFullName(value);
+ IMPORT_LOG1("\tname: %S\n", value.get());
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ isNNTP ?
+ "NNTP_Organization_Name" :
+ "SMTP_Organization_Name",
+ value))) {
+ id->SetOrganization(value);
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ isNNTP ?
+ "NNTP_Email_Address" :
+ "SMTP_Email_Address",
+ value))) {
+ id->SetEmail(NS_ConvertUTF16toUTF8(value));
+ IMPORT_LOG1("\temail: %S\n", value.get());
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ isNNTP ?
+ "NNTP_Reply_To_Email_Address" :
+ "SMTP_Reply_To_Email_Address",
+ value))) {
+ id->SetReplyTo(NS_ConvertUTF16toUTF8(value));
+ }
+
+ // Windows users are used to top style quoting.
+ id->SetReplyOnTop(isNNTP ? 0 : 1);
+ pAcc->AddIdentity(id);
+ }
+
+ if (!isNNTP) // NNTP does not use SMTP in OE or TB
+ SetSmtpServer(xmlDoc, id, inUserName, authMethodIncoming);
+}
+
+void WMSettings::SetSmtpServer(nsIDOMDocument *xmlDoc, nsIMsgIdentity *id,
+ nsAutoString& inUserName, int32_t authMethodIncoming)
+{
+ nsresult errorCode;
+
+ // set the id.smtpserver accordingly
+ if (!id)
+ return;
+ nsCString smtpServerKey, userName;
+ nsAutoString value, smtpName;
+ if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc, "SMTP_Server", smtpName)))
+ return;
+
+ // first we have to calculate the smtp user name which is based on sicily
+ // smtp user name depends on sicily which may or not exist
+ int32_t useSicily = 0;
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "SMTP_Use_Sicily",
+ value))) {
+ useSicily = (int32_t)value.ToInteger(&errorCode,16);
+ }
+ switch (useSicily) {
+ case 1 : case 3 :
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "SMTP_User_Name",
+ value))) {
+ CopyUTF16toUTF8(value, userName);
+ }
+ else {
+ CopyUTF16toUTF8(inUserName, userName);
+ }
+ break;
+ case 2 :
+ CopyUTF16toUTF8(inUserName, userName);
+ break;
+ default :
+ break; // initial userName == ""
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISmtpService>
+ smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && smtpService) {
+ nsCOMPtr<nsISmtpServer> extgServer;
+ // don't try to make another server
+ // regardless if username doesn't match
+ rv = smtpService->FindServer(userName.get(),
+ NS_ConvertUTF16toUTF8(smtpName).get(),
+ getter_AddRefs(extgServer));
+ if (NS_SUCCEEDED(rv) && extgServer) {
+ // set our account keyed to this smptserver key
+ extgServer->GetKey(getter_Copies(smtpServerKey));
+ id->SetSmtpServerKey(smtpServerKey);
+
+ IMPORT_LOG1("SMTP server already exists: %S\n", smtpName);
+ }
+ else {
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = smtpService->CreateServer(getter_AddRefs(smtpServer));
+ if (NS_SUCCEEDED(rv) && smtpServer) {
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "SMTP_Port",
+ value))) {
+ smtpServer->SetPort(value.ToInteger(&errorCode,16));
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc,
+ "SMTP_Secure_Connection",
+ value))) {
+ if (value.ToInteger(&errorCode, 16) == 1)
+ smtpServer->SetSocketType(nsMsgSocketType::SSL);
+ else
+ smtpServer->SetSocketType(nsMsgSocketType::plain);
+ }
+ smtpServer->SetUsername(userName);
+ switch (useSicily) {
+ case 1 :
+ smtpServer->SetAuthMethod(nsMsgAuthMethod::secure);
+ break;
+ case 2 : // requires SMTP authentication to use the incoming server settings
+ smtpServer->SetAuthMethod(authMethodIncoming);
+ break;
+ case 3 :
+ smtpServer->SetAuthMethod(nsMsgAuthMethod::passwordCleartext);
+ break;
+ default:
+ smtpServer->SetAuthMethod(nsMsgAuthMethod::none);
+ }
+
+ smtpServer->SetHostname(NS_ConvertUTF16toUTF8(smtpName));
+
+ smtpServer->GetKey(getter_Copies(smtpServerKey));
+ id->SetSmtpServerKey(smtpServerKey);
+
+ IMPORT_LOG1("Created new SMTP server: %S\n", smtpName);
+ }
+ }
+ }
+}
diff --git a/mailnews/import/winlivemail/nsWMSettings.h b/mailnews/import/winlivemail/nsWMSettings.h
new file mode 100644
index 000000000..3a17e7999
--- /dev/null
+++ b/mailnews/import/winlivemail/nsWMSettings.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWMSettings_h___
+#define nsWMSettings_h___
+
+#include "nsIImportSettings.h"
+
+class nsWMSettings : public nsIImportSettings {
+public:
+ nsWMSettings();
+ static nsresult Create(nsIImportSettings** aImport);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTSETTINGS
+
+private:
+ virtual ~nsWMSettings();
+};
+
+#endif /* nsWMSettings_h___ */
diff --git a/mailnews/import/winlivemail/nsWMStringBundle.cpp b/mailnews/import/winlivemail/nsWMStringBundle.cpp
new file mode 100644
index 000000000..8edd21513
--- /dev/null
+++ b/mailnews/import/winlivemail/nsWMStringBundle.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsMsgUtils.h"
+#include "nsIStringBundle.h"
+#include "nsWMStringBundle.h"
+#include "nsIServiceManager.h"
+#include "nsIURI.h"
+#include "mozilla/Services.h"
+
+#define WM_MSGS_URL "chrome://messenger/locale/wmImportMsgs.properties"
+
+nsIStringBundle * nsWMStringBundle::m_pBundle = nullptr;
+
+nsIStringBundle *nsWMStringBundle::GetStringBundle(void)
+{
+ if (m_pBundle)
+ return m_pBundle;
+
+ char* propertyURL = WM_MSGS_URL;
+ nsIStringBundle* sBundle = nullptr;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ if (sBundleService) {
+ sBundleService->CreateBundle(propertyURL, &sBundle);
+ }
+
+ m_pBundle = sBundle;
+
+ return sBundle;
+}
+
+void nsWMStringBundle::GetStringByID(int32_t stringID, nsString& result)
+{
+ char16_t *ptrv = GetStringByID(stringID);
+ result = ptrv;
+ FreeString(ptrv);
+}
+
+char16_t *nsWMStringBundle::GetStringByID(int32_t stringID)
+{
+ if (!m_pBundle)
+ m_pBundle = GetStringBundle();
+
+ if (m_pBundle) {
+ char16_t *ptrv = nullptr;
+ nsresult rv = m_pBundle->GetStringFromID(stringID, &ptrv);
+
+ if (NS_SUCCEEDED(rv) && ptrv)
+ return ptrv;
+ }
+
+ nsString resultString;
+ resultString.AppendLiteral("[StringID ");
+ resultString.AppendInt(stringID);
+ resultString.AppendLiteral("?]");
+
+ return ToNewUnicode(resultString);
+}
+
+void nsWMStringBundle::Cleanup(void)
+{
+ if (m_pBundle)
+ m_pBundle->Release();
+ m_pBundle = nullptr;
+}
diff --git a/mailnews/import/winlivemail/nsWMStringBundle.h b/mailnews/import/winlivemail/nsWMStringBundle.h
new file mode 100644
index 000000000..414d66435
--- /dev/null
+++ b/mailnews/import/winlivemail/nsWMStringBundle.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsWMStringBundle_H__
+#define _nsWMStringBundle_H__
+
+#include "nsStringGlue.h"
+
+class nsIStringBundle;
+
+class nsWMStringBundle {
+public:
+ static char16_t * GetStringByID(int32_t stringID);
+ static void GetStringByID(int32_t stringID, nsString& result);
+ static nsIStringBundle * GetStringBundle(void); // don't release
+ static void FreeString(char16_t *pStr) { NS_Free(pStr);}
+ static void Cleanup(void);
+
+private:
+ static nsIStringBundle * m_pBundle;
+};
+
+
+
+#define WMIMPORT_NAME 2000
+#define WMIMPORT_DESCRIPTION 2001
+#define WMIMPORT_MAILBOX_SUCCESS 2002
+#define WMIMPORT_MAILBOX_BADPARAM 2003
+#define WMIMPORT_MAILBOX_BADSOURCEFILE 2004
+#define WMIMPORT_MAILBOX_CONVERTERROR 2005
+#define WMIMPORT_DEFAULT_NAME 2006
+#define WMIMPORT_AUTOFIND 2007
+#define WMIMPORT_ADDRESS_SUCCESS 2008
+#define WMIMPORT_ADDRESS_CONVERTERROR 2009
+#define WMIMPORT_ADDRESS_BADPARAM 2010
+
+#endif /* _nsWMStringBundle_H__ */
diff --git a/mailnews/import/winlivemail/nsWMUtils.cpp b/mailnews/import/winlivemail/nsWMUtils.cpp
new file mode 100644
index 000000000..3e60597b8
--- /dev/null
+++ b/mailnews/import/winlivemail/nsWMUtils.cpp
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsStringGlue.h"
+#include "nsWMUtils.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMParser.h"
+#include "nsIFileStreams.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "WMDebugLog.h"
+#include "prio.h"
+
+nsresult
+nsWMUtils::FindWMKey(nsIWindowsRegKey **aKey)
+{
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> key =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("Software\\Microsoft\\Windows Live Mail"),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ADDREF(*aKey = key);
+ return rv;
+ }
+
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("Software\\Microsoft\\Windows Mail"),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ key.forget(aKey);
+ return rv;
+}
+
+nsresult
+nsWMUtils::GetRootFolder(nsIFile **aRootFolder)
+{
+ nsCOMPtr<nsIWindowsRegKey> key;
+ if (NS_FAILED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) {
+ IMPORT_LOG0("*** Error finding Windows Live Mail registry account keys\n");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ // This is essential to proceed; it is the location on disk of xml-type account files;
+ // it is in reg_expand_sz so it will need expanding to absolute path.
+ nsString storeRoot;
+ nsresult rv = key->ReadStringValue(NS_LITERAL_STRING("Store Root"), storeRoot);
+ key->Close(); // Finished with windows registry key. We do not want to return before this closing
+ if (NS_FAILED(rv) || storeRoot.IsEmpty()) {
+ IMPORT_LOG0("*** Error finding Windows Live Mail Store Root\n");
+ return rv;
+ }
+
+ uint32_t size = ::ExpandEnvironmentStringsW((LPCWSTR)storeRoot.get(), nullptr, 0);
+ nsString expandedStoreRoot;
+ expandedStoreRoot.SetLength(size - 1);
+ if (expandedStoreRoot.Length() != size - 1)
+ return NS_ERROR_FAILURE;
+ ::ExpandEnvironmentStringsW((LPCWSTR)storeRoot.get(),
+ (LPWSTR)expandedStoreRoot.BeginWriting(),
+ size);
+ storeRoot = expandedStoreRoot;
+
+ nsCOMPtr<nsIFile> rootFolder(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = rootFolder->InitWithPath(storeRoot);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rootFolder.forget(aRootFolder);
+
+ return NS_OK;
+}
+
+nsresult
+nsWMUtils::GetOEAccountFiles(nsCOMArray<nsIFile> &aFileArray)
+{
+ nsCOMPtr<nsIFile> rootFolder;
+
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetOEAccountFilesInFolder(rootFolder, aFileArray);
+}
+
+nsresult
+nsWMUtils::GetOEAccountFilesInFolder(nsIFile *aFolder,
+ nsCOMArray<nsIFile> &aFileArray)
+{
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ nsresult rv = aFolder->GetDirectoryEntries(getter_AddRefs(entries));
+ if (NS_FAILED(rv) || !entries)
+ return NS_ERROR_FAILURE;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> supports;
+ rv = entries->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory;
+ rv = file->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isDirectory) {
+ GetOEAccountFilesInFolder(file, aFileArray);
+ }
+ else {
+ nsString name;
+ rv = file->GetLeafName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (StringEndsWith(name, NS_LITERAL_STRING(".oeaccount")))
+ aFileArray.AppendObject(file);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsWMUtils::MakeXMLdoc(nsIDOMDocument **aXmlDoc,
+ nsIFile *aFile)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileInputStream> stream =
+ do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stream->Init(aFile, PR_RDONLY, -1, 0);
+ nsCOMPtr<nsIDOMParser> parser = do_CreateInstance(NS_DOMPARSER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t filesize;
+ aFile->GetFileSize(&filesize);
+ return parser->ParseFromStream(stream, nullptr, int32_t(filesize),
+ "application/xml", aXmlDoc);
+}
+
+nsresult
+nsWMUtils::GetValueForTag(nsIDOMDocument *aXmlDoc,
+ const char *aTagName,
+ nsAString &aValue)
+{
+ nsAutoString tagName;
+ tagName.AssignASCII(aTagName);
+ nsCOMPtr<nsIDOMNodeList> list;
+ if (NS_FAILED(aXmlDoc->GetElementsByTagName(tagName, getter_AddRefs(list))))
+ return NS_ERROR_FAILURE;
+ nsCOMPtr<nsIDOMNode> domNode;
+ list->Item(0, getter_AddRefs(domNode));
+ if (!domNode)
+ return NS_ERROR_FAILURE;
+ return domNode->GetTextContent(aValue);
+}
+
diff --git a/mailnews/import/winlivemail/nsWMUtils.h b/mailnews/import/winlivemail/nsWMUtils.h
new file mode 100644
index 000000000..e1bf54286
--- /dev/null
+++ b/mailnews/import/winlivemail/nsWMUtils.h
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWMUtils_h___
+#define nsWMUtils_h___
+
+#include <windows.h>
+#include "nsIWindowsRegKey.h"
+
+class nsIDOMDocument;
+
+class nsWMUtils {
+public:
+ static nsresult FindWMKey(nsIWindowsRegKey **aKey);
+ static nsresult GetRootFolder(nsIFile **aRootFolder);
+ static nsresult GetOEAccountFiles(nsCOMArray<nsIFile> &aFileArray);
+ static nsresult GetOEAccountFilesInFolder(nsIFile *aFolder,
+ nsCOMArray<nsIFile> &aFileArray);
+ static nsresult MakeXMLdoc(nsIDOMDocument **aXmlDoc,
+ nsIFile *aFile);
+ static nsresult GetValueForTag(nsIDOMDocument *aXmlDoc,
+ const char *aTagName,
+ nsAString &aValue);
+};
+
+#endif /* nsWMUtils_h___ */
diff --git a/mailnews/intl/charsetData.properties b/mailnews/intl/charsetData.properties
new file mode 100644
index 000000000..66bdacf7c
--- /dev/null
+++ b/mailnews/intl/charsetData.properties
@@ -0,0 +1,120 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+## Rule of this file:
+## 1. key should always be in lower case ascii so we can do case insensitive
+## comparison in the code faster.
+
+## Format of this file:
+##
+## charset_name.isInternal = anything - specifies that this charset should
+## not be exposed to web content because of the vulnerability to XSS attacks
+## or some other reasons
+##
+## charset_name.LangGroup =
+##
+## charset_name.isMultibyte = multi byte charsets
+
+x-mac-arabic.isInternal = true
+x-mac-farsi.isInternal = true
+x-mac-hebrew.isInternal = true
+x-imap4-modified-utf7.isInternal = true
+replacement.isInternal = true
+
+# XXX : todo: move to something based on BCP 47 (RFC 5646);
+# these should primarily specify script (and sometimes region),
+# but NOT language.
+# See also https://bugzilla.mozilla.org/show_bug.cgi?id=756022
+# e.g. x-western -> *-Latn-155 (Western Europe),
+# *-Latn-151 (Eastern Europe),
+# *-Latn-154 (Northern Europe),
+# *-Latn-TR
+# x-cyrillic -> *-Cyrl
+# zh-TW -> *-Hant-TW
+# zh-HK -> *-Hant-HK
+# zh-CN -> *-Hans
+# ja -> *-Jpan
+# ko -> *-Hang
+# he -> *-Hebr
+# ar -> *-Arab
+# etc
+
+big5.LangGroup = zh-TW
+x-x-big5.LangGroup = zh-TW
+big5-hkscs.LangGroup = zh-HK
+euc-jp.LangGroup = ja
+euc-kr.LangGroup = ko
+gb2312.LangGroup = zh-CN
+gb18030.LangGroup = zh-CN
+gb18030.2000-0.LangGroup = zh-CN
+gb18030.2000-1.LangGroup = zh-CN
+hkscs-1.LangGroup = zh-HK
+ibm866.LangGroup = x-cyrillic
+ibm1125.LangGroup = x-cyrillic
+ibm1131.LangGroup = x-cyrillic
+iso-2022-jp.LangGroup = ja
+iso-8859-1.LangGroup = x-western
+iso-8859-10.LangGroup = x-western
+iso-8859-14.LangGroup = x-western
+iso-8859-15.LangGroup = x-western
+iso-8859-2.LangGroup = x-western
+iso-8859-16.LangGroup = x-western
+iso-8859-3.LangGroup = x-western
+iso-8859-4.LangGroup = x-western
+iso-8859-13.LangGroup = x-western
+iso-8859-5.LangGroup = x-cyrillic
+iso-8859-6.LangGroup = ar
+iso-8859-7.LangGroup = el
+iso-8859-8.LangGroup = he
+iso-8859-8-i.LangGroup = he
+jis_0208-1983.LangGroup = ja
+koi8-r.LangGroup = x-cyrillic
+koi8-u.LangGroup = x-cyrillic
+shift_jis.LangGroup = ja
+windows-874.LangGroup = th
+utf-8.LangGroup = x-unicode
+utf-16.LangGroup = x-unicode
+utf-16be.LangGroup = x-unicode
+utf-16le.LangGroup = x-unicode
+utf-7.LangGroup = x-unicode
+x-imap4-modified-utf7.LangGroup = x-unicode
+replacement.LangGroup = x-unicode
+windows-1250.LangGroup = x-western
+windows-1251.LangGroup = x-cyrillic
+windows-1252.LangGroup = x-western
+windows-1253.LangGroup = el
+windows-1254.LangGroup = x-western
+windows-1255.LangGroup = he
+windows-1256.LangGroup = ar
+windows-1257.LangGroup = x-western
+windows-1258.LangGroup = x-western
+gbk.LangGroup = zh-CN
+x-mac-ce.LangGroup = x-western
+x-mac-croatian.LangGroup = x-western
+x-mac-cyrillic.LangGroup = x-cyrillic
+x-mac-devanagari.LangGroup = x-devanagari
+x-mac-farsi.LangGroup = ar
+x-mac-greek.LangGroup = el
+x-mac-gujarati.LangGroup = x-gujr
+x-mac-gurmukhi.LangGroup = x-guru
+x-mac-icelandic.LangGroup = x-western
+macintosh.LangGroup = x-western
+x-mac-turkish.LangGroup = x-western
+x-mac-ukrainian.LangGroup = x-cyrillic
+x-mac-romanian.LangGroup = x-western
+x-user-defined.LangGroup = x-unicode
+ks_c_5601-1987.LangGroup = ko
+x-mac-hebrew.LangGroup = he
+x-mac-arabic.LangGroup = ar
+
+iso-2022-jp.isMultibyte = true
+shift_jis.isMultibyte = true
+euc-jp.isMultibyte = true
+big5.isMultibyte = true
+big5-hkscs.isMultibyte = true
+gb2312.isMultibyte = true
+euc-kr.isMultibyte = true
+utf-7.isMultibyte = true
+utf-8.isMultibyte = true
+replacement.isMultibyte = true
diff --git a/mailnews/intl/charsetalias.properties b/mailnews/intl/charsetalias.properties
new file mode 100644
index 000000000..b3edb83b9
--- /dev/null
+++ b/mailnews/intl/charsetalias.properties
@@ -0,0 +1,99 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Rule of this file:
+# 1. key should always be in lower case ascii so we can do case insensitive
+# comparison in the code faster.
+# 2. value should be the one used in unicode converter
+# 3. If the charset is not used for document charset, but font charset
+# (e.g. XLFD charset- such as JIS x0201, JIS x0208), don't put here
+#
+# This file contains mainly aliases. Actual labels for encodings are in
+# labelsencodings.properties. Besides aliases it contains labels for charsets
+# that are not part of the HTML5 world, but still are supported for e-mail.
+
+646=windows-1252
+iso-8859-1=ISO-8859-1
+utf-16=UTF-16
+utf-7=UTF-7
+
+# Netscape private ...
+x-imap4-modified-utf7=x-imap4-modified-utf7
+x-mac-ce=x-mac-ce
+x-mac-turkish=x-mac-turkish
+x-mac-greek=x-mac-greek
+x-mac-icelandic=x-mac-icelandic
+x-mac-croatian=x-mac-croatian
+x-mac-romanian=x-mac-romanian
+x-mac-hebrew=x-mac-hebrew
+x-mac-arabic=x-mac-arabic
+x-mac-farsi=x-mac-farsi
+x-mac-devanagari=x-mac-devanagari
+x-mac-gujarati=x-mac-gujarati
+x-mac-gurmukhi=x-mac-gurmukhi
+iso-10646-ucs-2=UTF-16BE
+x-iso-10646-ucs-2-be=UTF-16BE
+x-iso-10646-ucs-2-le=UTF-16LE
+
+# Aliases for ISO-8859-1
+latin1=ISO-8859-1
+iso_8859-1=ISO-8859-1
+iso8859-1=ISO-8859-1
+iso_8859-1:1987=ISO-8859-1
+iso-ir-100=ISO-8859-1
+l1=ISO-8859-1
+cp819=ISO-8859-1
+csisolatin1=ISO-8859-1
+
+# Aliases for ISO-8859-8-I
+iso-8859-8i=ISO-8859-8-I
+
+# Aliases for Shift_JIS
+cp932=Shift_JIS
+
+# Aliases for ISO-2022-JP
+# The following are really not aliases ISO-2022-JP, but sharing the same decoder
+iso-2022-jp-2=ISO-2022-JP
+csiso2022jp2=ISO-2022-JP
+
+# Aliases for Big5
+# x-x-big5 is not really a alias for Big5, add it only for MS FrontPage
+# Sun Solaris
+
+zh_tw-big5=Big5
+
+# Aliases for EUC-KR
+5601=EUC-KR
+
+# Aliases for windows-874
+tis620=windows-874
+
+# Aliases for IBM866
+cp-866=IBM866
+
+# Aliases for UTF-7
+x-unicode-2-0-utf-7=UTF-7
+unicode-2-0-utf-7=UTF-7
+unicode-1-1-utf-7=UTF-7
+csunicode11utf7=UTF-7
+
+# Aliases for ISO-10646-UCS-2
+csunicode=UTF-16BE
+csunicode11=UTF-16BE
+iso-10646-ucs-basic=UTF-16BE
+csunicodeascii=UTF-16BE
+iso-10646-unicode-latin1=UTF-16BE
+csunicodelatin1=UTF-16BE
+iso-10646=UTF-16BE
+iso-10646-j-1=UTF-16BE
+
+# Following names appears in unix nl_langinfo(CODESET)
+# They can be compiled as platform specific if necessary
+# DONT put things here if it does not look generic enough (like hp15CN)
+iso88591=ISO-8859-1
+iso885912=ISO-8859-12
+windows-936=gbk
+ansi-1251=windows-1251
+
+cp936=gbk
diff --git a/mailnews/intl/jar.mn b/mailnews/intl/jar.mn
new file mode 100644
index 000000000..ab02275d9
--- /dev/null
+++ b/mailnews/intl/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+ res/charsetData.properties (charsetData.properties)
diff --git a/mailnews/intl/moz.build b/mailnews/intl/moz.build
new file mode 100644
index 000000000..10412b119
--- /dev/null
+++ b/mailnews/intl/moz.build
@@ -0,0 +1,35 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsICharsetConverterManager.idl',
+]
+
+UNIFIED_SOURCES += [
+ 'nsCharsetAlias.cpp',
+ 'nsCharsetConverterManager.cpp',
+ 'nsMUTF7ToUnicode.cpp',
+ 'nsUnicodeToMUTF7.cpp',
+ 'nsUnicodeToUTF7.cpp',
+ 'nsUTF7ToUnicode.cpp',
+]
+
+XPIDL_MODULE = 'commuconv'
+
+LOCAL_INCLUDES += [
+ '/intl/locale',
+]
+
+GENERATED_FILES += [
+ 'charsetalias.properties.h',
+]
+charsetalias = GENERATED_FILES['charsetalias.properties.h']
+charsetalias.script = '../../intl/locale/props2arrays.py'
+charsetalias.inputs = ['charsetalias.properties']
+
+FINAL_LIBRARY = 'mail'
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/mailnews/intl/nsCharsetAlias.cpp b/mailnews/intl/nsCharsetAlias.cpp
new file mode 100644
index 000000000..c10725596
--- /dev/null
+++ b/mailnews/intl/nsCharsetAlias.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/EncodingUtils.h"
+
+#include "nsCharsetAlias.h"
+
+// for NS_ERROR_UCONV_NOCONV
+#include "nsCharsetConverterManager.h"
+
+#include "nsUConvPropertySearch.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//
+static const nsUConvProp kAliases[] = {
+#include "charsetalias.properties.h"
+};
+
+//--------------------------------------------------------------
+// static
+nsresult
+nsCharsetAlias::GetPreferredInternal(const nsACString& aAlias,
+ nsACString& oResult)
+{
+ // First check charsetalias.properties and if there is no match, continue to
+ // call EncodingUtils::FindEncodingForLabel.
+ nsAutoCString key(aAlias);
+ ToLowerCase(key);
+
+ nsresult rv = nsUConvPropertySearch::SearchPropertyValue(kAliases,
+ ArrayLength(kAliases), key, oResult);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ return EncodingUtils::FindEncodingForLabel(key, oResult) ?
+ NS_OK: NS_ERROR_NOT_AVAILABLE;
+}
+
+//--------------------------------------------------------------
+// static
+nsresult
+nsCharsetAlias::GetPreferred(const nsACString& aAlias,
+ nsACString& oResult)
+{
+ if (aAlias.IsEmpty()) return NS_ERROR_NULL_POINTER;
+
+ nsresult res = GetPreferredInternal(aAlias, oResult);
+ if (NS_FAILED(res))
+ return res;
+
+ if (nsCharsetConverterManager::IsInternal(oResult))
+ return NS_ERROR_UCONV_NOCONV;
+
+ return res;
+}
+
+//--------------------------------------------------------------
+// static
+nsresult
+nsCharsetAlias::Equals(const nsACString& aCharset1,
+ const nsACString& aCharset2, bool* oResult)
+{
+ nsresult res = NS_OK;
+
+ if(aCharset1.Equals(aCharset2, nsCaseInsensitiveCStringComparator())) {
+ *oResult = true;
+ return res;
+ }
+
+ if(aCharset1.IsEmpty() || aCharset2.IsEmpty()) {
+ *oResult = false;
+ return res;
+ }
+
+ *oResult = false;
+ nsAutoCString name1;
+ res = GetPreferredInternal(aCharset1, name1);
+ if (NS_FAILED(res))
+ return res;
+
+ nsAutoCString name2;
+ res = GetPreferredInternal(aCharset2, name2);
+ if (NS_FAILED(res))
+ return res;
+
+ *oResult = name1.Equals(name2);
+ return NS_OK;
+}
diff --git a/mailnews/intl/nsCharsetAlias.h b/mailnews/intl/nsCharsetAlias.h
new file mode 100644
index 000000000..c792d8de1
--- /dev/null
+++ b/mailnews/intl/nsCharsetAlias.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCharsetAlias_h___
+#define nsCharsetAlias_h___
+
+#include "nscore.h"
+#include "nsString.h"
+
+class nsCharsetConverterManager;
+class nsScriptableUnicodeConverter;
+
+class nsCharsetAlias
+{
+ friend class nsCharsetConverterManager;
+ friend class nsScriptableUnicodeConverter;
+ static nsresult GetPreferredInternal(const nsACString& aAlias, nsACString& aResult);
+public:
+ static nsresult GetPreferred(const nsACString& aAlias, nsACString& aResult);
+ static nsresult Equals(const nsACString& aCharset1, const nsACString& aCharset2, bool* aResult);
+};
+
+#endif /* nsCharsetAlias_h___ */
diff --git a/mailnews/intl/nsCharsetConverterManager.cpp b/mailnews/intl/nsCharsetConverterManager.cpp
new file mode 100644
index 000000000..3d668fbf1
--- /dev/null
+++ b/mailnews/intl/nsCharsetConverterManager.cpp
@@ -0,0 +1,352 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsCharsetAlias.h"
+#include "nsICategoryManager.h"
+#include "nsICharsetConverterManager.h"
+#include "nsEncoderDecoderUtils.h"
+#include "nsIStringBundle.h"
+#include "nsTArray.h"
+#include "nsStringEnumerator.h"
+#include "mozilla/Services.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "nsServiceManagerUtils.h"
+
+// just for CONTRACTIDs
+#include "nsCharsetConverterManager.h"
+
+static nsCOMPtr<nsIStringBundle> sDataBundle;
+static nsCOMPtr<nsIStringBundle> sTitleBundle;
+
+// Class nsCharsetConverterManager [implementation]
+
+NS_IMPL_ISUPPORTS(nsCharsetConverterManager, nsICharsetConverterManager)
+
+nsCharsetConverterManager::nsCharsetConverterManager()
+{
+}
+
+nsCharsetConverterManager::~nsCharsetConverterManager()
+{
+ sDataBundle = nullptr;
+ sTitleBundle = nullptr;
+}
+
+static
+nsresult LoadBundle(const char* aBundleURLSpec, nsIStringBundle ** aResult)
+{
+ nsCOMPtr<nsIStringBundleService> sbServ =
+ mozilla::services::GetStringBundleService();
+ if (!sbServ)
+ return NS_ERROR_FAILURE;
+
+ return sbServ->CreateBundle(aBundleURLSpec, aResult);
+}
+
+static
+nsresult GetBundleValue(nsIStringBundle * aBundle,
+ const char * aName,
+ const nsAFlatString& aProp,
+ char16_t ** aResult)
+{
+ nsAutoString key;
+
+ key.AssignWithConversion(aName);
+ ToLowerCase(key); // we lowercase the main comparison key
+ key.Append(aProp);
+
+ return aBundle->GetStringFromName(key.get(), aResult);
+}
+
+static
+nsresult GetBundleValue(nsIStringBundle * aBundle,
+ const char * aName,
+ const nsAFlatString& aProp,
+ nsAString& aResult)
+{
+ nsresult rv = NS_OK;
+
+ nsXPIDLString value;
+ rv = GetBundleValue(aBundle, aName, aProp, getter_Copies(value));
+ if (NS_FAILED(rv))
+ return rv;
+
+ aResult = value;
+
+ return NS_OK;
+}
+
+static
+nsresult GetCharsetDataImpl(const char * aCharset, const char16_t * aProp,
+ nsAString& aResult)
+{
+ NS_ENSURE_ARG_POINTER(aCharset);
+ // aProp can be nullptr
+
+ if (!sDataBundle) {
+ nsresult rv = LoadBundle("resource://gre-resources/charsetData.properties",
+ getter_AddRefs(sDataBundle));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return GetBundleValue(sDataBundle, aCharset, nsDependentString(aProp), aResult);
+}
+
+//static
+bool nsCharsetConverterManager::IsInternal(const nsACString& aCharset)
+{
+ nsAutoString str;
+ // fully qualify to possibly avoid vtable call
+ nsresult rv = GetCharsetDataImpl(PromiseFlatCString(aCharset).get(),
+ u".isInternal",
+ str);
+
+ return NS_SUCCEEDED(rv);
+}
+
+
+//----------------------------------------------------------------------------//----------------------------------------------------------------------------
+// Interface nsICharsetConverterManager [implementation]
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetUnicodeEncoder(const char * aDest,
+ nsIUnicodeEncoder ** aResult)
+{
+ // resolve the charset first
+ nsAutoCString charset;
+
+ // fully qualify to possibly avoid vtable call
+ nsCharsetConverterManager::GetCharsetAlias(aDest, charset);
+
+ return nsCharsetConverterManager::GetUnicodeEncoderRaw(charset.get(),
+ aResult);
+}
+
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetUnicodeEncoderRaw(const char * aDest,
+ nsIUnicodeEncoder ** aResult)
+{
+ *aResult= nullptr;
+ nsCOMPtr<nsIUnicodeEncoder> encoder;
+
+ nsresult rv = NS_OK;
+
+ nsAutoCString
+ contractid(NS_LITERAL_CSTRING(NS_UNICODEENCODER_CONTRACTID_BASE) +
+ nsDependentCString(aDest));
+
+ // Always create an instance since encoders hold state.
+ encoder = do_CreateInstance(contractid.get(), &rv);
+
+ if (NS_FAILED(rv))
+ rv = NS_ERROR_UCONV_NOCONV;
+ else
+ {
+ *aResult = encoder.get();
+ NS_ADDREF(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetUnicodeDecoder(const char * aSrc,
+ nsIUnicodeDecoder ** aResult)
+{
+ // resolve the charset first
+ nsAutoCString charset;
+
+ // fully qualify to possibly avoid vtable call
+ if (NS_FAILED(nsCharsetConverterManager::GetCharsetAlias(aSrc, charset)))
+ return NS_ERROR_UCONV_NOCONV;
+
+ return nsCharsetConverterManager::GetUnicodeDecoderRaw(charset.get(),
+ aResult);
+}
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetUnicodeDecoderInternal(const char * aSrc,
+ nsIUnicodeDecoder ** aResult)
+{
+ // resolve the charset first
+ nsAutoCString charset;
+
+ nsresult rv = nsCharsetAlias::GetPreferredInternal(nsDependentCString(aSrc),
+ charset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nsCharsetConverterManager::GetUnicodeDecoderRaw(charset.get(),
+ aResult);
+}
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetUnicodeDecoderRaw(const char * aSrc,
+ nsIUnicodeDecoder ** aResult)
+{
+ *aResult= nullptr;
+ nsCOMPtr<nsIUnicodeDecoder> decoder;
+
+ nsresult rv = NS_OK;
+
+ NS_NAMED_LITERAL_CSTRING(contractbase, NS_UNICODEDECODER_CONTRACTID_BASE);
+ nsDependentCString src(aSrc);
+
+ decoder = do_CreateInstance(PromiseFlatCString(contractbase + src).get(),
+ &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UCONV_NOCONV);
+
+ decoder.forget(aResult);
+ return rv;
+}
+
+static
+nsresult GetList(const nsACString& aCategory,
+ const nsACString& aPrefix,
+ nsIUTF8StringEnumerator** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ nsresult rv;
+
+ nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsTArray<nsCString>* array = new nsTArray<nsCString>;
+ if (!array)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ catman->EnumerateCategory(PromiseFlatCString(aCategory).get(),
+ getter_AddRefs(enumerator));
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> supports;
+ if (NS_FAILED(enumerator->GetNext(getter_AddRefs(supports))))
+ continue;
+
+ nsCOMPtr<nsISupportsCString> supStr = do_QueryInterface(supports);
+ if (!supStr)
+ continue;
+
+ nsAutoCString name;
+ if (NS_FAILED(supStr->GetData(name)))
+ continue;
+
+ nsAutoCString fullName(aPrefix);
+ fullName.Append(name);
+ NS_ENSURE_TRUE(array->AppendElement(fullName), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ return NS_NewAdoptingUTF8StringEnumerator(aResult, array);
+}
+
+// we should change the interface so that we can just pass back a enumerator!
+NS_IMETHODIMP
+nsCharsetConverterManager::GetDecoderList(nsIUTF8StringEnumerator ** aResult)
+{
+ return GetList(NS_LITERAL_CSTRING(NS_UNICODEDECODER_NAME),
+ EmptyCString(), aResult);
+}
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetEncoderList(nsIUTF8StringEnumerator ** aResult)
+{
+ return GetList(NS_LITERAL_CSTRING(NS_UNICODEENCODER_NAME),
+ EmptyCString(), aResult);
+}
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetCharsetDetectorList(nsIUTF8StringEnumerator** aResult)
+{
+ return GetList(NS_LITERAL_CSTRING("charset-detectors"),
+ NS_LITERAL_CSTRING("chardet."), aResult);
+}
+
+// XXX Improve the implementation of this method. Right now, it is build on
+// top of the nsCharsetAlias service. We can make the nsCharsetAlias
+// better, with its own hash table (not the StringBundle anymore) and
+// a nicer file format.
+NS_IMETHODIMP
+nsCharsetConverterManager::GetCharsetAlias(const char * aCharset,
+ nsACString& aResult)
+{
+ NS_ENSURE_ARG_POINTER(aCharset);
+
+ // We try to obtain the preferred name for this charset from the charset
+ // aliases.
+ nsresult rv;
+
+ rv = nsCharsetAlias::GetPreferred(nsDependentCString(aCharset), aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetCharsetTitle(const char * aCharset,
+ nsAString& aResult)
+{
+ NS_ENSURE_ARG_POINTER(aCharset);
+
+ if (!sTitleBundle) {
+ nsresult rv = LoadBundle("chrome://messenger/locale/charsetTitles.properties",
+ getter_AddRefs(sTitleBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return GetBundleValue(sTitleBundle, aCharset, NS_LITERAL_STRING(".title"), aResult);
+}
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetCharsetData(const char * aCharset,
+ const char16_t * aProp,
+ nsAString& aResult)
+{
+ return GetCharsetDataImpl(aCharset, aProp, aResult);
+}
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetCharsetLangGroup(const char * aCharset,
+ nsIAtom** aResult)
+{
+ // resolve the charset first
+ nsAutoCString charset;
+
+ nsresult rv = GetCharsetAlias(aCharset, charset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // fully qualify to possibly avoid vtable call
+ return nsCharsetConverterManager::GetCharsetLangGroupRaw(charset.get(),
+ aResult);
+}
+
+NS_IMETHODIMP
+nsCharsetConverterManager::GetCharsetLangGroupRaw(const char * aCharset,
+ nsIAtom** aResult)
+{
+
+ *aResult = nullptr;
+ nsAutoString langGroup;
+ // fully qualify to possibly avoid vtable call
+ nsresult rv = nsCharsetConverterManager::GetCharsetData(
+ aCharset, u".LangGroup", langGroup);
+
+ if (NS_SUCCEEDED(rv)) {
+ ToLowerCase(langGroup); // use lowercase for all language atoms
+ *aResult = NS_Atomize(langGroup).take();
+ }
+
+ return rv;
+}
diff --git a/mailnews/intl/nsCharsetConverterManager.h b/mailnews/intl/nsCharsetConverterManager.h
new file mode 100644
index 000000000..36a4bdddb
--- /dev/null
+++ b/mailnews/intl/nsCharsetConverterManager.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsCharsetConverterManager_h__
+#define nsCharsetConverterManager_h__
+
+#include "nsICharsetConverterManager.h"
+
+class nsCharsetAlias;
+
+class nsCharsetConverterManager : public nsICharsetConverterManager
+{
+ friend class nsCharsetAlias;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICHARSETCONVERTERMANAGER
+
+public:
+ nsCharsetConverterManager();
+
+private:
+ virtual ~nsCharsetConverterManager();
+
+ static bool IsInternal(const nsACString& aCharset);
+};
+
+#endif // nsCharsetConverterManager_h__
+
+
diff --git a/mailnews/intl/nsCommUConvCID.h b/mailnews/intl/nsCommUConvCID.h
new file mode 100644
index 000000000..4f1686098
--- /dev/null
+++ b/mailnews/intl/nsCommUConvCID.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Class ID for our UTF7ToUnicode charset converter
+// {77CFAAF1-1CF4-11d3-8AAF-00600811A836}
+#define NS_UTF7TOUNICODE_CID \
+ { 0x77cfaaf1, 0x1cf4, 0x11d3, {0x8a, 0xaf, 0x0, 0x60, 0x8, 0x11, 0xa8, 0x36}}
+
+// Class ID for our MUTF7ToUnicode charset converter
+// {B57F97C1-0D70-11d3-8AAE-00600811A836}
+#define NS_MUTF7TOUNICODE_CID \
+ { 0xb57f97c1, 0xd70, 0x11d3, {0x8a, 0xae, 0x0, 0x60, 0x8, 0x11, 0xa8, 0x36}}
+
+// Class ID for our UnicodeToUTF7 charset converter
+// {77CFAAF2-1CF4-11d3-8AAF-00600811A836}
+#define NS_UNICODETOUTF7_CID \
+ { 0x77cfaaf2, 0x1cf4, 0x11d3, {0x8a, 0xaf, 0x0, 0x60, 0x8, 0x11, 0xa8, 0x36}}
+
+// Class ID for our UnicodeToMUTF7 charset converter
+// {B57F97C2-0D70-11d3-8AAE-00600811A836}
+#define NS_UNICODETOMUTF7_CID \
+ { 0xb57f97c2, 0xd70, 0x11d3, {0x8a, 0xae, 0x0, 0x60, 0x8, 0x11, 0xa8, 0x36}}
+
+
diff --git a/mailnews/intl/nsICharsetConverterManager.idl b/mailnews/intl/nsICharsetConverterManager.idl
new file mode 100644
index 000000000..026f0887c
--- /dev/null
+++ b/mailnews/intl/nsICharsetConverterManager.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIAtom.idl"
+
+%{ C++
+#include "nsIUnicodeDecoder.h"
+#include "nsIUnicodeEncoder.h"
+
+// XXX change to NS_CHARSETCONVERTERMANAGER_CID
+#define NS_ICHARSETCONVERTERMANAGER_CID \
+ {0x3c1c0163, 0x9bd0, 0x11d3, { 0x9d, 0x9, 0x0, 0x50, 0x4, 0x0, 0x7, 0xb2}}
+
+#define NS_CHARSETCONVERTERMANAGER_CONTRACTID "@mozilla.org/charset-converter-manager;1"
+%}
+
+interface nsIUnicodeDecoder;
+interface nsIUnicodeEncoder;
+interface nsIUTF8StringEnumerator;
+
+/**
+ * DON'T ADD NEW USES OF THIS INTERFACE TO MOZILLA-CENTRAL. Use
+ * mozilla::dom::EncodingUtils instead.
+ *
+ * Here Charsets are identified by ASCII strings. Charset alias
+ * resolution is provided by default in most methods. "Raw"
+ * versions that do not need this resolution are also provided.
+ *
+ * @deprecated Use mozilla::dom::EncodingUtils in mozilla-central instead.
+ * @created 21/Feb/2000
+ * @author Catalin Rotaru [CATA]
+ */
+[scriptable, uuid(a0550d46-8d9c-47dd-acc7-c083620dff12)]
+interface nsICharsetConverterManager : nsISupports
+{
+ /**
+ * Get the Unicode decoder for the given charset.
+ * The "Raw" version skips charset alias resolution
+ */
+ [noscript] nsIUnicodeDecoder getUnicodeDecoder(in string charset);
+ [noscript] nsIUnicodeDecoder getUnicodeDecoderRaw(in string charset);
+ [noscript] nsIUnicodeDecoder getUnicodeDecoderInternal(in string charset);
+
+ /**
+ * Get the Unicode encoder for the given charset.
+ * The "Raw" version skips charset alias resolution
+ */
+ [noscript] nsIUnicodeEncoder getUnicodeEncoder(in string charset);
+ [noscript] nsIUnicodeEncoder getUnicodeEncoderRaw(in string charset);
+
+ /**
+ * A shortcut to calling nsICharsetAlias to do alias resolution
+ * @throws if aCharset is an unknown charset.
+ */
+ ACString getCharsetAlias(in string aCharset);
+
+ /**
+ * Get the complete list of available decoders.
+ */
+ nsIUTF8StringEnumerator getDecoderList();
+
+ /**
+ * Get the complete list of available encoders.
+ */
+ nsIUTF8StringEnumerator getEncoderList();
+
+ /**
+ * Get the complete list of available charset detectors.
+ */
+ nsIUTF8StringEnumerator GetCharsetDetectorList();
+
+ /**
+ * Get the human-readable name for the given charset.
+ * @throws if aCharset is an unknown charset.
+ */
+ AString getCharsetTitle(in string aCharset);
+
+ /**
+ * Get some data about the given charset. This includes whether the
+ * character encoding may be used for certain purposes, if it is
+ * multi-byte, and the language code for it. See charsetData.properties
+ * for the source of this data. Some known property names:
+ * LangGroup - language code for charset, e.g. 'he' and 'zh-CN'.
+ * isMultibyte - is this a multi-byte charset?
+ * isInternal - not to be used in untrusted web content.
+ *
+ * @param aCharset name of the character encoding, e.g. 'iso-8859-15'.
+ * @param aProp property desired for the character encoding.
+ * @throws if aCharset is an unknown charset.
+ * @return the value of the property, for the character encoding.
+ */
+ AString getCharsetData(in string aCharset,
+ in wstring aProp);
+
+ /**
+ * Get the language group for the given charset. This is similar to
+ * calling <tt>getCharsetData</tt> with the <tt>prop</tt> "LangGroup".
+ *
+ * @param aCharset name of the character encoding, e.g. 'iso-8859-15'.
+ * @throws if aCharset is an unknown charset.
+ * @return the language code for the character encoding.
+ */
+ nsIAtom getCharsetLangGroup(in string aCharset);
+ nsIAtom getCharsetLangGroupRaw(in string aCharset);
+};
diff --git a/mailnews/intl/nsMUTF7ToUnicode.cpp b/mailnews/intl/nsMUTF7ToUnicode.cpp
new file mode 100644
index 000000000..c513e4fb0
--- /dev/null
+++ b/mailnews/intl/nsMUTF7ToUnicode.cpp
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMUTF7ToUnicode.h"
+
+//----------------------------------------------------------------------
+// Class nsMUTF7ToUnicode [implementation]
+
+nsMUTF7ToUnicode::nsMUTF7ToUnicode()
+: nsBasicUTF7Decoder(',', '&')
+{
+}
diff --git a/mailnews/intl/nsMUTF7ToUnicode.h b/mailnews/intl/nsMUTF7ToUnicode.h
new file mode 100644
index 000000000..1b5046e82
--- /dev/null
+++ b/mailnews/intl/nsMUTF7ToUnicode.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMUTF7ToUnicode_h___
+#define nsMUTF7ToUnicode_h___
+
+#include "nsUTF7ToUnicode.h"
+
+//----------------------------------------------------------------------
+// Class nsMUTF7ToUnicode [declaration]
+
+/**
+ * A character set converter from Modified UTF7 to Unicode.
+ *
+ * @created 18/May/1999
+ * @author Catalin Rotaru [CATA]
+ */
+class nsMUTF7ToUnicode : public nsBasicUTF7Decoder
+{
+public:
+
+ /**
+ * Class constructor.
+ */
+ nsMUTF7ToUnicode();
+
+};
+
+#endif /* nsMUTF7ToUnicode_h___ */
diff --git a/mailnews/intl/nsUTF7ToUnicode.cpp b/mailnews/intl/nsUTF7ToUnicode.cpp
new file mode 100644
index 000000000..201bcccb9
--- /dev/null
+++ b/mailnews/intl/nsUTF7ToUnicode.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsUTF7ToUnicode.h"
+
+#define ENC_DIRECT 0
+#define ENC_BASE64 1
+
+//----------------------------------------------------------------------
+// Class nsBasicUTF7Decoder [implementation]
+
+nsBasicUTF7Decoder::nsBasicUTF7Decoder(char aLastChar, char aEscChar)
+: nsBufferDecoderSupport(1)
+{
+ mLastChar = aLastChar;
+ mEscChar = aEscChar;
+ Reset();
+}
+
+nsresult nsBasicUTF7Decoder::DecodeDirect(
+ const char * aSrc,
+ int32_t * aSrcLength,
+ char16_t * aDest,
+ int32_t * aDestLength)
+{
+ const char * srcEnd = aSrc + *aSrcLength;
+ const char * src = aSrc;
+ char16_t * destEnd = aDest + *aDestLength;
+ char16_t * dest = aDest;
+ nsresult res = NS_OK;
+ char ch;
+
+ while (src < srcEnd) {
+ ch = *src;
+
+ // stop when we meet other chars or end of direct encoded seq.
+ // if (!(DirectEncodable(ch)) || (ch == mEscChar)) {
+ // but we are decoding; so we should be lax; pass everything until escchar
+ if (ch == mEscChar) {
+ res = NS_ERROR_UDEC_ILLEGALINPUT;
+ break;
+ }
+
+ if (dest >= destEnd) {
+ res = NS_OK_UDEC_MOREOUTPUT;
+ break;
+ } else {
+ *dest++ = ch;
+ src++;
+ }
+ }
+
+ *aSrcLength = src - aSrc;
+ *aDestLength = dest - aDest;
+ return res;
+}
+
+nsresult nsBasicUTF7Decoder::DecodeBase64(
+ const char * aSrc,
+ int32_t * aSrcLength,
+ char16_t * aDest,
+ int32_t * aDestLength)
+{
+ const char * srcEnd = aSrc + *aSrcLength;
+ const char * src = aSrc;
+ char16_t * destEnd = aDest + *aDestLength;
+ char16_t * dest = aDest;
+ nsresult res = NS_OK;
+ char ch;
+ uint32_t value;
+
+ while (src < srcEnd) {
+ ch = *src;
+
+ // stop when we meet other chars or end of direct encoded seq.
+ value = CharToValue(ch);
+ if (value > 0xff) {
+ res = NS_ERROR_UDEC_ILLEGALINPUT;
+ break;
+ }
+
+ switch (mEncStep) {
+ case 0:
+ mEncBits = value << 10;
+ break;
+ case 1:
+ mEncBits += value << 4;
+ break;
+ case 2:
+ if (dest >= destEnd) {
+ res = NS_OK_UDEC_MOREOUTPUT;
+ break;
+ }
+ mEncBits += value >> 2;
+ *(dest++) = (char16_t) mEncBits;
+ mEncBits = (value & 0x03) << 14;
+ break;
+ case 3:
+ mEncBits += value << 8;
+ break;
+ case 4:
+ mEncBits += value << 2;
+ break;
+ case 5:
+ if (dest >= destEnd) {
+ res = NS_OK_UDEC_MOREOUTPUT;
+ break;
+ }
+ mEncBits += value >> 4;
+ *(dest++) = (char16_t) mEncBits;
+ mEncBits = (value & 0x0f) << 12;
+ break;
+ case 6:
+ mEncBits += value << 6;
+ break;
+ case 7:
+ if (dest >= destEnd) {
+ res = NS_OK_UDEC_MOREOUTPUT;
+ break;
+ }
+ mEncBits += value;
+ *(dest++) = (char16_t) mEncBits;
+ mEncBits = 0;
+ break;
+ }
+
+ if (res != NS_OK) break;
+
+ src++;
+ (++mEncStep)%=8;
+ }
+
+ *aSrcLength = src - aSrc;
+ *aDestLength = dest - aDest;
+ return res;
+}
+
+uint32_t nsBasicUTF7Decoder::CharToValue(char aChar) {
+ if ((aChar>='A')&&(aChar<='Z'))
+ return (uint8_t)(aChar-'A');
+ else if ((aChar>='a')&&(aChar<='z'))
+ return (uint8_t)(26+aChar-'a');
+ else if ((aChar>='0')&&(aChar<='9'))
+ return (uint8_t)(26+26+aChar-'0');
+ else if (aChar=='+')
+ return (uint8_t)(26+26+10);
+ else if (aChar==mLastChar)
+ return (uint8_t)(26+26+10+1);
+ else
+ return 0xffff;
+}
+
+//----------------------------------------------------------------------
+// Subclassing of nsBufferDecoderSupport class [implementation]
+
+NS_IMETHODIMP nsBasicUTF7Decoder::ConvertNoBuff(const char * aSrc,
+ int32_t * aSrcLength,
+ char16_t * aDest,
+ int32_t * aDestLength)
+{
+ const char * srcEnd = aSrc + *aSrcLength;
+ const char * src = aSrc;
+ char16_t * destEnd = aDest + *aDestLength;
+ char16_t * dest = aDest;
+ int32_t bcr,bcw;
+ nsresult res = NS_OK;
+
+ while (src < srcEnd) {
+
+ // fist, attept to decode in the current mode
+ bcr = srcEnd - src;
+ bcw = destEnd - dest;
+ if (mEncoding == ENC_DIRECT)
+ res = DecodeDirect(src, &bcr, dest, &bcw);
+ else if ((mFreshBase64) && (*src == '-')) {
+ *dest = mEscChar;
+ bcr = 0;
+ bcw = 1;
+ res = NS_ERROR_UDEC_ILLEGALINPUT;
+ } else {
+ mFreshBase64 = false;
+ res = DecodeBase64(src, &bcr, dest, &bcw);
+ }
+ src += bcr;
+ dest += bcw;
+
+ // if an illegal char was encountered, test if it is an escape seq.
+ if (res == NS_ERROR_UDEC_ILLEGALINPUT) {
+ if (mEncoding == ENC_DIRECT) {
+ if (*src == mEscChar) {
+ mEncoding = ENC_BASE64;
+ mFreshBase64 = true;
+ mEncBits = 0;
+ mEncStep = 0;
+ src++;
+ res = NS_OK;
+ } else break;
+ } else {
+ mEncoding = ENC_DIRECT;
+ res = NS_OK;
+ // absorbe end of escape sequence
+ if (*src == '-') src++;
+ }
+ } else if (res != NS_OK) break;
+ }
+
+ *aSrcLength = src - aSrc;
+ *aDestLength = dest - aDest;
+ return res;
+}
+
+NS_IMETHODIMP nsBasicUTF7Decoder::Reset()
+{
+ mEncoding = ENC_DIRECT;
+ mEncBits = 0;
+ mEncStep = 0;
+ return nsBufferDecoderSupport::Reset();
+}
+
+//----------------------------------------------------------------------
+// Class nsUTF7ToUnicode [implementation]
+
+nsUTF7ToUnicode::nsUTF7ToUnicode()
+: nsBasicUTF7Decoder('/', '+')
+{
+}
diff --git a/mailnews/intl/nsUTF7ToUnicode.h b/mailnews/intl/nsUTF7ToUnicode.h
new file mode 100644
index 000000000..74bf295a0
--- /dev/null
+++ b/mailnews/intl/nsUTF7ToUnicode.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUTF7ToUnicode_h___
+#define nsUTF7ToUnicode_h___
+
+#include "nsUCSupport.h"
+
+//----------------------------------------------------------------------
+// Class nsBasicUTF7Decoder [declaration]
+
+/**
+ * Basic class for a character set converter from UTF-7 to Unicode.
+ *
+ * @created 03/Jun/1999
+ * @author Catalin Rotaru [CATA]
+ */
+class nsBasicUTF7Decoder : public nsBufferDecoderSupport
+{
+public:
+
+ /**
+ * Class constructor.
+ */
+ nsBasicUTF7Decoder(char aLastChar, char aEscChar);
+
+protected:
+
+ int32_t mEncoding; // current encoding
+ uint32_t mEncBits;
+ int32_t mEncStep;
+ char mLastChar;
+ char mEscChar;
+ bool mFreshBase64;
+
+ nsresult DecodeDirect(const char * aSrc, int32_t * aSrcLength,
+ char16_t * aDest, int32_t * aDestLength);
+ nsresult DecodeBase64(const char * aSrc, int32_t * aSrcLength,
+ char16_t * aDest, int32_t * aDestLength);
+ uint32_t CharToValue(char aChar);
+
+ //--------------------------------------------------------------------
+ // Subclassing of nsBufferDecoderSupport class [declaration]
+
+ NS_IMETHOD ConvertNoBuff(const char * aSrc, int32_t * aSrcLength,
+ char16_t * aDest, int32_t * aDestLength);
+ NS_IMETHOD Reset();
+};
+
+//----------------------------------------------------------------------
+// Class nsUTF7ToUnicode [declaration]
+
+/**
+ * A character set converter from Modified UTF7 to Unicode.
+ *
+ * @created 18/May/1999
+ * @author Catalin Rotaru [CATA]
+ */
+class nsUTF7ToUnicode : public nsBasicUTF7Decoder
+{
+public:
+
+ /**
+ * Class constructor.
+ */
+ nsUTF7ToUnicode();
+
+};
+
+#endif /* nsUTF7ToUnicode_h___ */
diff --git a/mailnews/intl/nsUnicodeToMUTF7.cpp b/mailnews/intl/nsUnicodeToMUTF7.cpp
new file mode 100644
index 000000000..a9c368ce8
--- /dev/null
+++ b/mailnews/intl/nsUnicodeToMUTF7.cpp
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsUnicodeToMUTF7.h"
+
+//----------------------------------------------------------------------
+// Class nsUnicodeToMUTF7 [implementation]
+
+nsUnicodeToMUTF7::nsUnicodeToMUTF7()
+: nsBasicUTF7Encoder(',', '&')
+{
+}
diff --git a/mailnews/intl/nsUnicodeToMUTF7.h b/mailnews/intl/nsUnicodeToMUTF7.h
new file mode 100644
index 000000000..83aea6ce4
--- /dev/null
+++ b/mailnews/intl/nsUnicodeToMUTF7.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUnicodeToMUTF7_h___
+#define nsUnicodeToMUTF7_h___
+
+#include "nsUnicodeToUTF7.h"
+
+//----------------------------------------------------------------------
+// Class nsUnicodeToMUTF7 [declaration]
+
+/**
+ * A character set converter from Unicode to Modified UTF-7.
+ *
+ * @created 18/May/1999
+ * @author Catalin Rotaru [CATA]
+ */
+class nsUnicodeToMUTF7 : public nsBasicUTF7Encoder
+{
+public:
+
+ /**
+ * Class constructor.
+ */
+ nsUnicodeToMUTF7();
+
+};
+
+#endif /* nsUnicodeToMUTF7_h___ */
diff --git a/mailnews/intl/nsUnicodeToUTF7.cpp b/mailnews/intl/nsUnicodeToUTF7.cpp
new file mode 100644
index 000000000..5db623a17
--- /dev/null
+++ b/mailnews/intl/nsUnicodeToUTF7.cpp
@@ -0,0 +1,298 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsUnicodeToUTF7.h"
+#include <string.h>
+
+//----------------------------------------------------------------------
+// Global functions and data [declaration]
+
+#define ENC_DIRECT 0
+#define ENC_BASE64 1
+
+//----------------------------------------------------------------------
+// Class nsBasicUTF7Encoder [implementation]
+
+nsBasicUTF7Encoder::nsBasicUTF7Encoder(char aLastChar, char aEscChar)
+: nsEncoderSupport(5)
+{
+ mLastChar = aLastChar;
+ mEscChar = aEscChar;
+ Reset();
+}
+
+nsresult nsBasicUTF7Encoder::ShiftEncoding(int32_t aEncoding,
+ char * aDest,
+ int32_t * aDestLength)
+{
+ if (aEncoding == mEncoding) {
+ *aDestLength = 0;
+ return NS_OK;
+ }
+
+ nsresult res = NS_OK;
+ char * dest = aDest;
+ char * destEnd = aDest + *aDestLength;
+
+ if (mEncStep != 0) {
+ if (dest >= destEnd) return NS_OK_UENC_MOREOUTPUT;
+ *(dest++)=ValueToChar(mEncBits);
+ mEncStep = 0;
+ mEncBits = 0;
+ }
+
+ if (dest >= destEnd) {
+ res = NS_OK_UENC_MOREOUTPUT;
+ } else {
+ switch (aEncoding) {
+ case 0:
+ *(dest++) = '-';
+ mEncStep = 0;
+ mEncBits = 0;
+ break;
+ case 1:
+ *(dest++) = mEscChar;
+ break;
+ }
+ mEncoding = aEncoding;
+ }
+
+ *aDestLength = dest - aDest;
+ return res;
+}
+
+nsresult nsBasicUTF7Encoder::EncodeDirect(
+ const char16_t * aSrc,
+ int32_t * aSrcLength,
+ char * aDest,
+ int32_t * aDestLength)
+{
+ nsresult res = NS_OK;
+ const char16_t * src = aSrc;
+ const char16_t * srcEnd = aSrc + *aSrcLength;
+ char * dest = aDest;
+ char * destEnd = aDest + *aDestLength;
+ char16_t ch;
+
+ while (src < srcEnd) {
+ ch = *src;
+
+ // stop when we reach Unicode chars
+ if (!DirectEncodable(ch)) break;
+
+ if (ch == mEscChar) {
+ // special case for the escape char
+ if (destEnd - dest < 1) {
+ res = NS_OK_UENC_MOREOUTPUT;
+ break;
+ } else {
+ *dest++ = (char)ch;
+ *dest++ = (char)'-';
+ src++;
+ }
+ } else {
+ //classic direct encoding
+ if (dest >= destEnd) {
+ res = NS_OK_UENC_MOREOUTPUT;
+ break;
+ } else {
+ *dest++ = (char)ch;
+ src++;
+ }
+ }
+ }
+
+ *aSrcLength = src - aSrc;
+ *aDestLength = dest - aDest;
+ return res;
+}
+
+nsresult nsBasicUTF7Encoder::EncodeBase64(
+ const char16_t * aSrc,
+ int32_t * aSrcLength,
+ char * aDest,
+ int32_t * aDestLength)
+{
+ nsresult res = NS_OK;
+ const char16_t * src = aSrc;
+ const char16_t * srcEnd = aSrc + *aSrcLength;
+ char * dest = aDest;
+ char * destEnd = aDest + *aDestLength;
+ char16_t ch;
+ uint32_t value;
+
+ while (src < srcEnd) {
+ ch = *src;
+
+ // stop when we reach printable US-ASCII chars
+ if (DirectEncodable(ch)) break;
+
+ switch (mEncStep) {
+ case 0:
+ if (destEnd - dest < 2) {
+ res = NS_OK_UENC_MOREOUTPUT;
+ break;
+ }
+ value=ch>>10;
+ *(dest++)=ValueToChar(value);
+ value=(ch>>4)&0x3f;
+ *(dest++)=ValueToChar(value);
+ mEncBits=(ch&0x0f)<<2;
+ break;
+ case 1:
+ if (destEnd - dest < 3) {
+ res = NS_OK_UENC_MOREOUTPUT;
+ break;
+ }
+ value=mEncBits+(ch>>14);
+ *(dest++)=ValueToChar(value);
+ value=(ch>>8)&0x3f;
+ *(dest++)=ValueToChar(value);
+ value=(ch>>2)&0x3f;
+ *(dest++)=ValueToChar(value);
+ mEncBits=(ch&0x03)<<4;
+ break;
+ case 2:
+ if (destEnd - dest < 3) {
+ res = NS_OK_UENC_MOREOUTPUT;
+ break;
+ }
+ value=mEncBits+(ch>>12);
+ *(dest++)=ValueToChar(value);
+ value=(ch>>6)&0x3f;
+ *(dest++)=ValueToChar(value);
+ value=ch&0x3f;
+ *(dest++)=ValueToChar(value);
+ mEncBits=0;
+ break;
+ }
+
+ if (res != NS_OK) break;
+
+ src++;
+ (++mEncStep)%=3;
+ }
+
+ *aSrcLength = src - aSrc;
+ *aDestLength = dest - aDest;
+ return res;
+}
+
+char nsBasicUTF7Encoder::ValueToChar(uint32_t aValue) {
+ if (aValue < 26)
+ return (char)('A'+aValue);
+ else if (aValue < 26 + 26)
+ return (char)('a' + aValue - 26);
+ else if (aValue < 26 + 26 + 10)
+ return (char)('0' + aValue - 26 - 26);
+ else if (aValue == 26 + 26 + 10)
+ return '+';
+ else if (aValue == 26 + 26 + 10 + 1)
+ return mLastChar;
+ else
+ return -1;
+}
+
+bool nsBasicUTF7Encoder::DirectEncodable(char16_t aChar) {
+ // spec says: printable US-ASCII chars
+ if ((aChar >= 0x20) && (aChar <= 0x7e)) return true;
+ else return false;
+}
+
+//----------------------------------------------------------------------
+// Subclassing of nsEncoderSupport class [implementation]
+
+NS_IMETHODIMP nsBasicUTF7Encoder::ConvertNoBuffNoErr(
+ const char16_t * aSrc,
+ int32_t * aSrcLength,
+ char * aDest,
+ int32_t * aDestLength)
+{
+ nsresult res = NS_OK;
+ const char16_t * src = aSrc;
+ const char16_t * srcEnd = aSrc + *aSrcLength;
+ char * dest = aDest;
+ char * destEnd = aDest + *aDestLength;
+ int32_t bcr,bcw;
+ char16_t ch;
+ int32_t enc;
+
+ while (src < srcEnd) {
+ // find the encoding for the next char
+ ch = *src;
+ if (DirectEncodable(ch))
+ enc = ENC_DIRECT;
+ else
+ enc = ENC_BASE64;
+
+ // if necessary, shift into the required encoding
+ bcw = destEnd - dest;
+ res = ShiftEncoding(enc, dest, &bcw);
+ dest += bcw;
+ if (res != NS_OK) break;
+
+ // now encode (as much as you can)
+ bcr = srcEnd - src;
+ bcw = destEnd - dest;
+ if (enc == ENC_DIRECT)
+ res = EncodeDirect(src, &bcr, dest, &bcw);
+ else
+ res = EncodeBase64(src, &bcr, dest, &bcw);
+ src += bcr;
+ dest += bcw;
+
+ if (res != NS_OK) break;
+ }
+
+ *aSrcLength = src - aSrc;
+ *aDestLength = dest - aDest;
+ return res;
+}
+
+NS_IMETHODIMP nsBasicUTF7Encoder::FinishNoBuff(char * aDest,
+ int32_t * aDestLength)
+{
+ return ShiftEncoding(ENC_DIRECT, aDest, aDestLength);
+}
+
+NS_IMETHODIMP nsBasicUTF7Encoder::Reset()
+{
+ mEncoding = ENC_DIRECT;
+ mEncBits = 0;
+ mEncStep = 0;
+ return nsEncoderSupport::Reset();
+}
+
+//----------------------------------------------------------------------
+// Class nsUnicodeToUTF7 [implementation]
+
+nsUnicodeToUTF7::nsUnicodeToUTF7()
+: nsBasicUTF7Encoder('/', '+')
+{
+}
+
+
+bool nsUnicodeToUTF7::DirectEncodable(char16_t aChar) {
+ if ((aChar >= 'A') && (aChar <= 'Z')) return true;
+ else if ((aChar >= 'a') && (aChar <= 'z')) return true;
+ else if ((aChar >= '0') && (aChar <= '9')) return true;
+ else if ((aChar >= 39) && (aChar <= 41)) return true;
+ else if ((aChar >= 44) && (aChar <= 47)) return true;
+ else if (aChar == 58) return true;
+ else if (aChar == 63) return true;
+ else if (aChar == ' ') return true;
+ else if (aChar == 9) return true;
+ else if (aChar == 13) return true;
+ else if (aChar == 10) return true;
+ else if (aChar == 60) return true; // '<'
+ else if (aChar == 33) return true; // '!'
+ else if (aChar == 34) return true; // '"'
+ else if (aChar == 62) return true; // '>'
+ else if (aChar == 61) return true; // '='
+ else if (aChar == 59) return true; // ';'
+ else if (aChar == 91) return true; // '['
+ else if (aChar == 93) return true; // ']'
+ else return false;
+}
diff --git a/mailnews/intl/nsUnicodeToUTF7.h b/mailnews/intl/nsUnicodeToUTF7.h
new file mode 100644
index 000000000..dc8cc678e
--- /dev/null
+++ b/mailnews/intl/nsUnicodeToUTF7.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUnicodeToUTF7_h___
+#define nsUnicodeToUTF7_h___
+
+#include "nsUCSupport.h"
+
+//----------------------------------------------------------------------
+// Class nsBasicUTF7Encoder [declaration]
+
+/**
+ * Basic class for a character set converter from Unicode to UTF-7.
+ *
+ * @created 03/Jun/1999
+ * @author Catalin Rotaru [CATA]
+ */
+class nsBasicUTF7Encoder : public nsEncoderSupport
+{
+public:
+
+ /**
+ * Class constructor.
+ */
+ nsBasicUTF7Encoder(char aLastChar, char aEscChar);
+
+protected:
+
+ int32_t mEncoding; // current encoding
+ uint32_t mEncBits;
+ int32_t mEncStep;
+ char mLastChar;
+ char mEscChar;
+
+ nsresult ShiftEncoding(int32_t aEncoding, char * aDest,
+ int32_t * aDestLength);
+ nsresult EncodeDirect(const char16_t * aSrc, int32_t * aSrcLength,
+ char * aDest, int32_t * aDestLength);
+ nsresult EncodeBase64(const char16_t * aSrc, int32_t * aSrcLength,
+ char * aDest, int32_t * aDestLength);
+ char ValueToChar(uint32_t aValue);
+ virtual bool DirectEncodable(char16_t aChar);
+
+ //--------------------------------------------------------------------
+ // Subclassing of nsEncoderSupport class [declaration]
+
+ NS_IMETHOD ConvertNoBuffNoErr(const char16_t * aSrc, int32_t * aSrcLength,
+ char * aDest, int32_t * aDestLength);
+ NS_IMETHOD FinishNoBuff(char * aDest, int32_t * aDestLength);
+ NS_IMETHOD Reset();
+};
+
+//----------------------------------------------------------------------
+// Class nsUnicodeToUTF7 [declaration]
+
+/**
+ * A character set converter from Unicode to UTF-7.
+ *
+ * @created 03/Jun/1999
+ * @author Catalin Rotaru [CATA]
+ */
+class nsUnicodeToUTF7 : public nsBasicUTF7Encoder
+{
+public:
+
+ /**
+ * Class constructor.
+ */
+ nsUnicodeToUTF7();
+
+protected:
+
+ virtual bool DirectEncodable(char16_t aChar);
+};
+
+#endif /* nsUnicodeToUTF7_h___ */
diff --git a/mailnews/jar.mn b/mailnews/jar.mn
new file mode 100644
index 000000000..71539e9aa
--- /dev/null
+++ b/mailnews/jar.mn
@@ -0,0 +1,138 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+messenger.jar:
+ content/messenger/addressbook/pref-directory-add.js (addrbook/prefs/content/pref-directory-add.js)
+ content/messenger/addressbook/pref-directory-add.xul (addrbook/prefs/content/pref-directory-add.xul)
+ content/messenger/addressbook/pref-editdirectories.js (addrbook/prefs/content/pref-editdirectories.js)
+ content/messenger/addressbook/pref-editdirectories.xul (addrbook/prefs/content/pref-editdirectories.xul)
+ content/messenger/addressbook/abAddressBookNameDialog.js (addrbook/content/abAddressBookNameDialog.js)
+ content/messenger/addressbook/abAddressBookNameDialog.xul (addrbook/content/abAddressBookNameDialog.xul)
+ content/messenger/addressbook/abEditCardDialog.xul (addrbook/content/abEditCardDialog.xul)
+ content/messenger/addressbook/abNewCardDialog.xul (addrbook/content/abNewCardDialog.xul)
+ content/messenger/addressbook/abResultsPaneOverlay.xul (addrbook/content/abResultsPaneOverlay.xul)
+ content/messenger/addressbook/abResultsPane.js (addrbook/content/abResultsPane.js)
+ content/messenger/addressbook/addrbookWidgets.xml (addrbook/content/addrbookWidgets.xml)
+ content/messenger/addressbook/abDragDrop.js (addrbook/content/abDragDrop.js)
+ content/messenger/addressbook/abMailListDialog.js (addrbook/content/abMailListDialog.js)
+ content/messagebody/addressbook/print.css (addrbook/content/print.css)
+* content/messenger/AccountManager.xul (base/prefs/content/AccountManager.xul)
+ content/messenger/AccountManager.js (base/prefs/content/AccountManager.js)
+ content/messenger/am-main.xul (base/prefs/content/am-main.xul)
+ content/messenger/am-main.js (base/prefs/content/am-main.js)
+ content/messenger/am-help.js (base/prefs/content/am-help.js)
+* content/messenger/am-server.xul (base/prefs/content/am-server.xul)
+ content/messenger/am-serverwithnoidentities.xul (base/prefs/content/am-serverwithnoidentities.xul)
+ content/messenger/am-serverwithnoidentities.js (base/prefs/content/am-serverwithnoidentities.js)
+* content/messenger/am-server.js (base/prefs/content/am-server.js)
+ content/messenger/am-server-top.xul (base/prefs/content/am-server-top.xul)
+ content/messenger/am-copies.xul (base/prefs/content/am-copies.xul)
+ content/messenger/am-copies.js (base/prefs/content/am-copies.js)
+ content/messenger/am-junk.xul (base/prefs/content/am-junk.xul)
+ content/messenger/am-junk.js (base/prefs/content/am-junk.js)
+ content/messenger/am-offline.xul (base/prefs/content/am-offline.xul)
+ content/messenger/am-offline.js (base/prefs/content/am-offline.js)
+ content/messenger/am-addressing.xul (base/prefs/content/am-addressing.xul)
+* content/messenger/am-addressingOverlay.xul (base/prefs/content/am-addressingOverlay.xul)
+ content/messenger/am-addressing.js (base/prefs/content/am-addressing.js)
+ content/messenger/am-server-advanced.xul (base/prefs/content/am-server-advanced.xul)
+ content/messenger/am-server-advanced.js (base/prefs/content/am-server-advanced.js)
+ content/messenger/am-smtp.xul (base/prefs/content/am-smtp.xul)
+* content/messenger/am-smtp.js (base/prefs/content/am-smtp.js)
+ content/messenger/am-prefs.js (base/prefs/content/am-prefs.js)
+ content/messenger/am-identities-list.js (base/prefs/content/am-identities-list.js)
+ content/messenger/am-identities-list.xul (base/prefs/content/am-identities-list.xul)
+ content/messenger/am-identity-edit.js (base/prefs/content/am-identity-edit.js)
+ content/messenger/am-identity-edit.xul (base/prefs/content/am-identity-edit.xul)
+ content/messenger/am-copiesOverlay.xul (base/prefs/content/am-copiesOverlay.xul)
+ content/messenger/am-archiveoptions.xul (base/prefs/content/am-archiveoptions.xul)
+ content/messenger/am-archiveoptions.js (base/prefs/content/am-archiveoptions.js)
+* content/messenger/AccountWizard.xul (base/prefs/content/AccountWizard.xul)
+ content/messenger/AccountWizard.js (base/prefs/content/AccountWizard.js)
+ content/messenger/aw-accounttype.js (base/prefs/content/aw-accounttype.js)
+ content/messenger/aw-identity.js (base/prefs/content/aw-identity.js)
+ content/messenger/aw-incoming.js (base/prefs/content/aw-incoming.js)
+ content/messenger/aw-outgoing.js (base/prefs/content/aw-outgoing.js)
+ content/messenger/aw-accname.js (base/prefs/content/aw-accname.js)
+ content/messenger/aw-done.js (base/prefs/content/aw-done.js)
+ content/messenger/accountUtils.js (base/prefs/content/accountUtils.js)
+ content/messenger/amUtils.js (base/prefs/content/amUtils.js)
+ content/messenger/ispUtils.js (base/prefs/content/ispUtils.js)
+ content/messenger/SmtpServerEdit.xul (base/prefs/content/SmtpServerEdit.xul)
+ content/messenger/SmtpServerEdit.js (base/prefs/content/SmtpServerEdit.js)
+* content/messenger/smtpEditOverlay.xul (base/prefs/content/smtpEditOverlay.xul)
+* content/messenger/smtpEditOverlay.js (base/prefs/content/smtpEditOverlay.js)
+ content/messenger/removeAccount.xul (base/prefs/content/removeAccount.xul)
+ content/messenger/removeAccount.js (base/prefs/content/removeAccount.js)
+#if defined(MOZ_THUNDERBIRD) && defined(HYPE_ICEDOVE)
+* content/messenger/accountcreation/accountConfig.js (base/prefs/content/accountcreation/accountConfig.js)
+* content/messenger/accountcreation/createInBackend.js (base/prefs/content/accountcreation/createInBackend.js)
+* content/messenger/accountcreation/emailWizard.js (base/prefs/content/accountcreation/emailWizard.js)
+* content/messenger/accountcreation/emailWizard.xul (base/prefs/content/accountcreation/emailWizard.xul)
+ content/messenger/accountcreation/fetchConfig.js (base/prefs/content/accountcreation/fetchConfig.js)
+ content/messenger/accountcreation/fetchhttp.js (base/prefs/content/accountcreation/fetchhttp.js)
+* content/messenger/accountcreation/guessConfig.js (base/prefs/content/accountcreation/guessConfig.js)
+ content/messenger/accountcreation/MyBadCertHandler.js (base/prefs/content/accountcreation/MyBadCertHandler.js)
+* content/messenger/accountcreation/readFromXML.js (base/prefs/content/accountcreation/readFromXML.js)
+ content/messenger/accountcreation/sanitizeDatatypes.js (base/prefs/content/accountcreation/sanitizeDatatypes.js)
+ content/messenger/accountcreation/util.js (base/prefs/content/accountcreation/util.js)
+* content/messenger/accountcreation/verifyConfig.js (base/prefs/content/accountcreation/verifyConfig.js)
+#endif
+ content/messenger/msgSynchronize.xul (base/content/msgSynchronize.xul)
+ content/messenger/msgSynchronize.js (base/content/msgSynchronize.js)
+ content/messenger/folderProps.xul (base/content/folderProps.xul)
+ content/messenger/folderProps.js (base/content/folderProps.js)
+ content/messenger/folderWidgets.xml (base/content/folderWidgets.xml)
+ content/messenger/charsetList.xml (base/content/charsetList.xml)
+ content/messenger/charsetList.css (base/content/charsetList.css)
+ content/messenger/retention.js (base/content/retention.js)
+ content/messenger/shareglue.js (base/content/shareglue.js)
+ content/messenger/newFolderDialog.xul (base/content/newFolderDialog.xul)
+ content/messenger/newFolderDialog.js (base/content/newFolderDialog.js)
+* content/messenger/msgAccountCentral.xul (base/content/msgAccountCentral.xul)
+ content/messenger/msgAccountCentral.js (base/content/msgAccountCentral.js)
+ content/messenger/msgFolderPickerOverlay.js (base/content/msgFolderPickerOverlay.js)
+ content/messenger/renameFolderDialog.xul (base/content/renameFolderDialog.xul)
+ content/messenger/renameFolderDialog.js (base/content/renameFolderDialog.js)
+ content/messenger/virtualFolderProperties.xul (base/content/virtualFolderProperties.xul)
+ content/messenger/virtualFolderProperties.js (base/content/virtualFolderProperties.js)
+ content/messenger/virtualFolderListDialog.xul (base/content/virtualFolderListDialog.xul)
+ content/messenger/virtualFolderListDialog.js (base/content/virtualFolderListDialog.js)
+ content/messenger/msgPrintEngine.js (base/content/msgPrintEngine.js)
+* content/messenger/junkMailInfo.xul (base/content/junkMailInfo.xul)
+ content/messenger/junkCommands.js (base/content/junkCommands.js)
+ content/messenger/junkLog.xul (base/content/junkLog.xul)
+ content/messenger/junkLog.js (base/content/junkLog.js)
+ content/messenger/jsTreeView.js (base/content/jsTreeView.js)
+ content/messenger/searchTermOverlay.js (base/search/content/searchTermOverlay.js)
+ content/messenger/searchTermOverlay.xul (base/search/content/searchTermOverlay.xul)
+ content/messenger/CustomHeaders.xul (base/search/content/CustomHeaders.xul)
+ content/messenger/CustomHeaders.js (base/search/content/CustomHeaders.js)
+ content/messenger/FilterEditor.xul (base/search/content/FilterEditor.xul)
+ content/messenger/FilterEditor.js (base/search/content/FilterEditor.js)
+ content/messenger/searchWidgets.xml (base/search/content/searchWidgets.xml)
+ content/messenger/viewLog.xul (base/search/content/viewLog.xul)
+ content/messenger/viewLog.js (base/search/content/viewLog.js)
+ content/messenger/messengercompose/askSendFormat.js (compose/content/askSendFormat.js)
+ content/messenger/messengercompose/askSendFormat.xul (compose/content/askSendFormat.xul)
+ content/messenger/messengercompose/sendProgress.xul (compose/content/sendProgress.xul)
+ content/messenger/messengercompose/sendProgress.js (compose/content/sendProgress.js)
+ content/messenger/messengercompose/mailComposeEditorOverlay.xul (compose/content/mailComposeEditorOverlay.xul)
+ content/messenger/messengercompose/menulistCompactBindings.xml (compose/content/menulistCompactBindings.xml)
+ content/messenger/importDialog.js (import/content/importDialog.js)
+* content/messenger/importDialog.xul (import/content/importDialog.xul)
+ content/messenger/fieldMapImport.xul (import/content/fieldMapImport.xul)
+ content/messenger/fieldMapImport.js (import/content/fieldMapImport.js)
+ content/messenger/downloadheaders.js (news/content/downloadheaders.js)
+ content/messenger/downloadheaders.xul (news/content/downloadheaders.xul)
+ content/messenger/markByDate.js (base/content/markByDate.js)
+ content/messenger/markByDate.xul (base/content/markByDate.xul)
+ content/messenger/dateFormat.js (base/content/dateFormat.js)
+ content/messenger/shutdownWindow.xul (base/content/shutdownWindow.xul)
+ content/messenger/shutdownWindow.js (base/content/shutdownWindow.js)
+#ifndef XP_MACOSX
+ content/messenger/newmailalert.css (base/content/newmailalert.css)
+ content/messenger/newmailalert.js (base/content/newmailalert.js)
+ content/messenger/newmailalert.xul (base/content/newmailalert.xul)
+#endif
diff --git a/mailnews/jsaccount/modules/JSAccountUtils.jsm b/mailnews/jsaccount/modules/JSAccountUtils.jsm
new file mode 100644
index 000000000..b5ce34682
--- /dev/null
+++ b/mailnews/jsaccount/modules/JSAccountUtils.jsm
@@ -0,0 +1,285 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file implements helper methods to make the transition of base mailnews
+ * objects from JS to C++ easier, and also to allow creating specialized
+ * versions of those accounts using only JS XPCOM implementations.
+ *
+ * In C++ land, the XPCOM component is a generic C++ class that does nothing
+ * but delegate any calls to interfaces known in C++ to either the generic
+ * C++ implementation (such as nsMsgIncomingServer.cpp) or a JavaScript
+ * implementation of those methods. Those delegations could be used for either
+ * method-by-method replacement of the generic C++ methods with JavaScript
+ * versions, or for specialization of the generic class using JavaScript to
+ * implement a particular class type. We use a C++ class as the main XPCOM
+ * version for two related reasons: First, we do not want to go through a
+ * C++->js->C++ XPCOM transition just to execute a C++ method. Second, C++
+ * inheritance is different from JS inheritance, and sometimes the C++ code
+ * will ignore the XPCOM parts of the JS, and just execute using C++
+ * inheritance.
+ *
+ * In JavaScript land, the implementation currently uses the XPCOM object for
+ * JavaScript calls, with the last object in the prototype chain defaulting
+ * to calling using the CPP object, specified in an instance-specific
+ * this.cppBase object.
+ *
+ * Examples of use can be found in the test files for jsaccount stuff.
+ */
+
+const EXPORTED_SYMBOLS = ["JSAccountUtils"];
+var JSAccountUtils = {};
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Logger definitions.
+const LOGGER_NAME = "JsAccount";
+const PREF_BRANCH_LOG = "mailnews.jsaccount.log.";
+const PREF_LOG_LEVEL = PREF_BRANCH_LOG + "level";
+const PREF_LOG_DUMP = PREF_BRANCH_LOG + "dump";
+
+// Set default logging levels.
+const LOG_LEVEL_DEFAULT = "Info"
+const LOG_DUMP_DEFAULT = true;
+
+// Logging usage: set mailnews.jsaccount.log.level to the word "Debug" to
+// increase logging level.
+
+var log = configureLogging();
+
+/**
+ *
+ * Generic factory to create XPCOM components under JsAccount.
+ *
+ * @param aProperties This a a const JS object that describes the specific
+ * details of a particular JsAccount XPCOM object:
+ * {
+ * baseContractID: string contractID used to create the base generic C++
+ * object. This object must implement the interfaces in
+ * baseInterfaces, plus msgIOverride.
+ *
+ * baseInterfaces: JS array of interfaces implemented by the base, generic
+ * C++ object.
+ *
+ * extraInterfaces: JS array of additional interfaces implemented by the
+ * component (accessed using getInterface())
+ *
+ * contractID: string contract ID for the JS object that will be
+ * created by the factory.
+ *
+ * classID: Components.ID(CID) for the JS object that will be
+ * created by the factory, where CID is a string uuid.
+ * }
+ *
+ * @param aJsDelegateConstructor: a JS contructor class, called using new,
+ * that will create the JS object to which
+ * XPCOM methods calls will be delegated.
+ */
+
+JSAccountUtils.jaFactory = function (aProperties, aJsDelegateConstructor)
+{
+ let factory = {};
+ factory.QueryInterface = XPCOMUtils.generateQI([Ci.nsIFactory]);
+ factory.lockFactory = function() {};
+
+ factory.createInstance = function(outer, iid)
+ {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+
+ // C++ delegator class.
+ let delegator = Cc[aProperties.baseContractID]
+ .createInstance(Ci.msgIOverride);
+
+ // Make sure the delegator JS wrapper knows its interfaces.
+ aProperties.baseInterfaces.forEach(iface => delegator instanceof iface);
+
+ // JavaScript overrides of base class functions.
+ let jsDelegate = new aJsDelegateConstructor(delegator, aProperties.baseInterfaces);
+ delegator.jsDelegate = jsDelegate;
+
+ // Get the delegate list for this current class. Use OwnProperty in case it
+ // inherits from another JsAccount class.
+
+ let delegateList = null;
+ if (Object.getPrototypeOf(jsDelegate).hasOwnProperty("delegateList")) {
+ delegateList = Object.getPrototypeOf(jsDelegate).delegateList;
+ }
+ if (delegateList instanceof Ci.msgIDelegateList) {
+ delegator.methodsToDelegate = delegateList;
+ } else {
+ // Lazily create and populate the list of methods to delegate.
+ log.info("creating delegate list for contractID " + aProperties.contractID);
+ let delegateList = delegator.methodsToDelegate;
+ Object.keys(delegator).forEach(name => {log.debug("delegator has key " + name);});
+
+ // jsMethods contains the methods that may be targets of the C++ delegation to JS.
+ let jsMethods = Object.getPrototypeOf(delegator.jsDelegate.wrappedJSObject);
+ for (let name in jsMethods)
+ {
+ log.debug("processing jsDelegate method: " + name);
+ if (name[0] == '_') { // don't bother with methods explicitly marked as internal.
+ log.debug("skipping " + name);
+ continue;
+ }
+ // Other methods to skip.
+ if (["QueryInterface", // nsISupports
+ "methodsToDelegate", "jsDelegate", "cppBase", // msgIOverride
+ "delegateList", "wrappedJSObject", // non-XPCOM methods to skip
+ ].includes(name)) {
+ log.debug("skipping " + name);
+ continue;
+ }
+
+ let jsDescriptor = getPropertyDescriptor(jsMethods, name);
+ if (!jsDescriptor) {
+ log.debug("no jsDescriptor for " + name);
+ continue;
+ }
+ let cppDescriptor = Object.getOwnPropertyDescriptor(delegator, name);
+ if (!cppDescriptor) {
+ log.debug("no cppDescriptor found for " + name);
+ // It is OK for jsMethods to have methods that are not used in override of C++.
+ continue;
+ }
+
+ let upperCaseName = name[0].toUpperCase() + name.substr(1);
+ if ('value' in jsDescriptor) {
+ log.info("delegating " + upperCaseName);
+ delegateList.add(upperCaseName);
+ }
+ else {
+ if (jsDescriptor.set) {
+ log.info("delegating Set" + upperCaseName);
+ delegateList.add("Set" + upperCaseName);
+ }
+ if (jsDescriptor.get) {
+ log.info("delegating Get" + upperCaseName);
+ delegateList.add("Get" + upperCaseName);
+ }
+ }
+ }
+
+ // Save the delegate list for reuse, statically for all instances.
+ Object.getPrototypeOf(jsDelegate).delegateList = delegateList;
+ }
+
+ for (let iface of aProperties.baseInterfaces)
+ if (iid.equals(iface)) {
+ log.debug("Successfully returning delegator " + delegator);
+ return delegator;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ };
+
+ return factory;
+}
+
+/**
+ * Create a JS object that contains calls to each of the methods in a CPP
+ * base class, that will reference the cpp object defined on a particular
+ * instance of the object. This is intended to be the last item in the
+ * prototype chain for a JsAccount implementation.
+ *
+ * @param aProperties see definition in jsFactory above
+ *
+ * @returns a JS object suitable as the prototype of a JsAccount implementation.
+ */
+JSAccountUtils.makeCppDelegator = function(aProperties)
+{
+ log.info("Making cppDelegator for contractID " + aProperties.contractID);
+ let cppDelegator = {};
+ let cppDummy = Cc[aProperties.baseContractID].createInstance(Ci.nsISupports);
+ // Add methods from all interfaces.
+ for (let iface of aProperties.baseInterfaces)
+ cppDummy instanceof Ci[iface];
+
+ for (let method in cppDummy) {
+ // skip nsISupports and msgIOverride methods
+ if (["QueryInterface", "methodsToDelegate", "jsDelegate", "cppBase", "getInterface"].includes(method)) {
+ log.config("Skipping " + method + "\n");
+ continue;
+ }
+ log.config("processing " + method + "\n");
+ let descriptor = Object.getOwnPropertyDescriptor(cppDummy, method);
+ let property = { enumerable: true };
+ // We must use Immediately Invoked Function Expressions to pass method, otherwise it is
+ // a closure containing just the last value it was set to.
+ if ('value' in descriptor) {
+ log.debug("Adding value for " + method);
+ property.value = function(aMethod) {
+ return function(...args) {
+ return Reflect.apply(this.cppBase[aMethod], undefined, args);
+ };
+ }(method);
+ }
+ if (descriptor.set) {
+ log.debug("Adding setter for " + method);
+ property.set = function(aMethod) {
+ return function(aVal) {
+ this.cppBase[aMethod] = aVal;
+ };
+ }(method);
+ }
+ if (descriptor.get) {
+ log.debug("Adding getter for " + method);
+ property.get = function(aMethod) {
+ return function() {
+ return this.cppBase[aMethod];
+ };
+ }(method);
+ }
+ Object.defineProperty(cppDelegator, method, property);
+ }
+ return cppDelegator;
+}
+
+// Utility functions.
+
+// Iterate over an object and its prototypes to get a property descriptor.
+function getPropertyDescriptor(obj, name)
+{
+ let descriptor = null;
+
+ // Eventually we will hit an object that will delegate JS calls to a CPP
+ // object, which are not JS overrides of CPP methods. Locate this item, and
+ // skip, because it will not have _JsPrototypeToDelegate defined.
+ while (obj && ("_JsPrototypeToDelegate" in obj)) {
+ descriptor = Object.getOwnPropertyDescriptor(obj, name);
+ if (descriptor)
+ break;
+ obj = Object.getPrototypeOf(obj);
+ }
+ return descriptor;
+}
+
+// Configure the logger based on the preferences.
+function configureLogging()
+{
+ let log = Log.repository.getLogger(LOGGER_NAME);
+
+ // Log messages need to go to the browser console.
+ let consoleAppender = new Log.ConsoleAppender(new Log.BasicFormatter());
+ log.addAppender(consoleAppender);
+
+ // Make sure the logger keeps up with the logging level preference.
+ log.level = Log.Level[Preferences.get(PREF_LOG_LEVEL, LOG_LEVEL_DEFAULT)];
+
+ // If enabled in the preferences, add a dump appender.
+ let logDumping = Preferences.get(PREF_LOG_DUMP, LOG_DUMP_DEFAULT);
+ if (logDumping) {
+ let dumpAppender = new Log.DumpAppender(new Log.BasicFormatter());
+ log.addAppender(dumpAppender);
+ }
+ return log;
+}
diff --git a/mailnews/jsaccount/modules/JaBaseUrl.jsm b/mailnews/jsaccount/modules/JaBaseUrl.jsm
new file mode 100644
index 000000000..21a10847a
--- /dev/null
+++ b/mailnews/jsaccount/modules/JaBaseUrl.jsm
@@ -0,0 +1,81 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const EXPORTED_SYMBOLS = ["JaBaseUrlProperties", "JaBaseUrl"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/jsaccount/JSAccountUtils.jsm");
+
+// A partial JavaScript implementation of the base server methods.
+
+const JaBaseUrlProperties = {
+
+ // The CPP object that delgates to CPP or JS.
+ baseContractID: "@mozilla.org/jacppurldelegator;1",
+
+ // Interfaces implemented by the base CPP version of this object.
+ baseInterfaces: [ Ci.nsIURI,
+ Ci.nsIURL,
+ Ci.nsIMsgMailNewsUrl,
+ Ci.nsIMsgMessageUrl,
+ Ci.msgIOverride,
+ Ci.nsISupports,
+ Ci.nsIInterfaceRequestor,
+ ],
+
+ // We don't typically define this as a creatable component, but if we do use
+ // these. Subclasses for particular account types require these defined for
+ // that type.
+ contractID: "@mozilla.org/jsaccount/jaurl;1",
+ classID: Components.ID("{1E7B42CA-E6D9-408F-A4E4-8D2F82AECBBD}"),
+};
+
+function JaBaseUrl(aDelegator, aBaseInterfaces) {
+
+// Typical boilerplate to include in all implementations.
+
+ // Object delegating method calls to the appropriate XPCOM object.
+ // Weak because it owns us.
+ this._delegatorWeak = Cu.getWeakReference(aDelegator);
+
+ // Base implementation of methods with no overrides.
+ this.cppBase = aDelegator.cppBase;
+
+ // cppBase class sees all interfaces
+ aBaseInterfaces.forEach(iface => this.cppBase instanceof iface);
+}
+
+JaBaseUrl.prototype = {
+// Typical boilerplate to include in all implementations.
+ __proto__: JSAccountUtils.makeCppDelegator(JaBaseUrlProperties),
+
+ // Flag this item as CPP needs to delegate to JS.
+ _JsPrototypeToDelegate: true,
+
+ // QI to the interfaces.
+ QueryInterface: XPCOMUtils.generateQI(JaBaseUrlProperties.baseInterfaces),
+
+ // Used to access an instance as JS, bypassing XPCOM.
+ get wrappedJSObject() {
+ return this;
+ },
+
+ // Accessor to the weak cpp delegator.
+ get delegator() {
+ return this._delegatorWeak.get();
+ },
+
+ // Dynamically-generated list of delegate methods.
+ delegateList: null,
+
+ // Implementation in JS (if any) of methods in XPCOM interfaces.
+
+};
diff --git a/mailnews/jsaccount/moz.build b/mailnews/jsaccount/moz.build
new file mode 100644
index 000000000..6e080b863
--- /dev/null
+++ b/mailnews/jsaccount/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'public',
+ 'src'
+]
+
+EXTRA_JS_MODULES.jsaccount += [
+ 'modules/JaBaseUrl.jsm',
+ 'modules/JSAccountUtils.jsm',
+]
diff --git a/mailnews/jsaccount/public/moz.build b/mailnews/jsaccount/public/moz.build
new file mode 100644
index 000000000..a635ffca9
--- /dev/null
+++ b/mailnews/jsaccount/public/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'msgIDelegateList.idl',
+ 'msgIOverride.idl',
+]
+
+EXPORTS += [
+ 'msgJsAccountCID.h',
+]
+
+XPIDL_MODULE = 'msgjsaccount'
+
diff --git a/mailnews/jsaccount/public/msgIDelegateList.idl b/mailnews/jsaccount/public/msgIDelegateList.idl
new file mode 100644
index 000000000..dce1b0bb6
--- /dev/null
+++ b/mailnews/jsaccount/public/msgIDelegateList.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface provides a list of methods that should be delegated to
+ * a JsObject rather than a C++ XPCOM base object in JsAccount classes.
+ */
+
+[scriptable, builtinclass, uuid(627D3A34-F8A3-40eb-91FE-E413D6638D27)]
+interface msgIDelegateList : nsISupports
+{
+ /// Method name to delegate to JavaScript.
+ void add(in string aMethod);
+};
diff --git a/mailnews/jsaccount/public/msgIOverride.idl b/mailnews/jsaccount/public/msgIOverride.idl
new file mode 100644
index 000000000..cad1bf415
--- /dev/null
+++ b/mailnews/jsaccount/public/msgIOverride.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "msgIDelegateList.idl"
+
+/**
+ * Mailnews code typically has a C++ base class for objects, which is then
+ * specialized for each account type with a C++ subclass of the base class.
+ *
+ * This interface provides the ability of JavaScript-based account
+ * implementations to use the same C++ base classes as core objects, but
+ * use JavaScript to override methods instead of C++.
+ */
+
+[scriptable, uuid(68075269-8BBD-4a09-AC04-3241BF44F633)]
+interface msgIOverride : nsISupports
+{
+ /**
+ *
+ * A list of methods in the C++ base class that will be delegated to the JS
+ * delegate. This is calculated once, and then a fixed value is set to
+ * all subsequent instances so that it does not need to be recalculated each
+ * time. If the value has not yet been set, this will return a new instance.
+ */
+ attribute msgIDelegateList methodsToDelegate;
+
+ /**
+ * JavaScript-based xpcom object that overrides C++ methods.
+ */
+ attribute nsISupports jsDelegate;
+
+ /**
+ * C++ class used to implement default functionality. This is used when
+ * JavaScript methods want to call the base class default action, bypassing a
+ * possible JS override.
+ */
+ readonly attribute nsISupports cppBase;
+};
diff --git a/mailnews/jsaccount/public/msgJsAccountCID.h b/mailnews/jsaccount/public/msgJsAccountCID.h
new file mode 100644
index 000000000..330569312
--- /dev/null
+++ b/mailnews/jsaccount/public/msgJsAccountCID.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Contains the definitions of contract IDs for modules in JsAccounts.
+
+#ifndef _msgJsAccountCID_H_
+#define _msgJsAccountCID_H_
+
+#define JACPPURLDELEGATOR_CID \
+{ 0x1a0b778c, 0x2fe6, 0x4012, { 0xb4, 0xf3, 0xe8, 0x1c, 0xc, 0x11, 0x64, 0x9 } }
+#define JACPPURLDELEGATOR_CONTRACTID "@mozilla.org/jacppurldelegator;1"
+
+#define JACPPABDIRECTORYDELEGATOR_CID \
+{ 0x77b5592c, 0x5018, 0x436d, { 0xa4, 0x66, 0xc4, 0xe5, 0x44, 0x3a, 0x16, 0x69 } }
+#define JACPPABDIRECTORYDELEGATOR_CONTRACTID "@mozilla.org/jacppabdirectorydelegator;1"
+
+#define JACPPINCOMINGSERVERDELEGATOR_CID \
+{ 0x7aa11dd3, 0x5590, 0x4e01, { 0xbd, 0x87, 0x91, 0xf6, 0x02, 0x72, 0xd0, 0x1a } }
+#define JACPPINCOMINGSERVERDELEGATOR_CONTRACTID "@mozilla.org/jacppincomingserverdelegator;1"
+
+#define JACPPCOMPOSEDELEGATOR_CID \
+{ 0xcfcd1caa, 0x00d9, 0x40d0, { 0x83, 0x1e, 0x67, 0x38, 0x20, 0xe0, 0x4f, 0xc6 } }
+#define JACPPCOMPOSEDELEGATOR_CONTRACTID "@mozilla.org/jacppcomposedelegator;1"
+
+#define JACPPMSGFOLDERDELEGATOR_CID \
+{ 0xd6bd81fa, 0xb1d4, 0x424a, { 0x88, 0xea, 0xbb, 0x3e, 0xa8, 0x38, 0x1d, 0x50 } }
+#define JACPPMSGFOLDERDELEGATOR_CONTRACTID "@mozilla.org/jacppmsgfolderdelegator;1"
+
+#define JACPPSENDDELEGATOR_CID \
+{ 0x36fcb887, 0x2b04, 0x42d4, { 0x92, 0xc9, 0xae, 0xd2, 0xac, 0xa1, 0xd4, 0x5b } }
+#define JACPPSENDDELEGATOR_CONTRACTID "@mozilla.org/jacppsenddelegator;1"
+#endif
diff --git a/mailnews/jsaccount/readme.html b/mailnews/jsaccount/readme.html
new file mode 100644
index 000000000..317b3c227
--- /dev/null
+++ b/mailnews/jsaccount/readme.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>JsAccount Usage and Architecture</title>
+ </head>
+ <body>
+ <h1>Overview</h1>
+ <p>JsAccount is a technology that allows message account types to be created
+ in Mozilla Mailnews code using JavaScript. Although this is primarily
+ targeted at allowing extensions to create new accounts, it might also be
+ useful as a bridge to convert existing account types from being C++ based
+ to JavaScript based.</p>
+ <h2>Existing C++-based architecture of mailnews accounts</h2>
+ <p>In mailnews code, an account type is a set of classes that allow
+ implementation of a messaging particular protocol. The account type is
+ given a short string identifier ("imap", "news", "pop3") and is then used
+ to create objects of the appropriate type by appending that string to the
+ end of a base XPCOM contractID. So, for example, to create an imap server,
+ you generate a contractID using a base ID,
+ NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX
+ "@mozilla.org/messenger/server;1?type=", then append "imap" to get:</p>
+ <p>@mozilla.org/messenger/server;1?type=imap</p>
+ <p>In the C++ code, there is a base object implementing shared
+ functionality. An account-specific class inherits that base functionality,
+ then extends it to represent the account-specific behavior that is needed.
+ This same basic concept is used to represent a whole series of classes
+ that are necessary to implement a specific mailnews account type.</p>
+ <p>For the server example, there is a base class named
+ nsMsgIncomingServer.cpp that implements that base interface
+ nsIMsgIncomingServer.idl. For imap, there is a specific class
+ nsImapIncomingServer.cpp that inherits from nsMsgIncomingServer.cpp,
+ overrides some of the methods in nsIMsgIncomingServer.idl, and also
+ implements an imap-specific interface nsIImapIncomingServer.idl. All of
+ this works fine using C++ inheritance and polymorphism.</p>
+ <p>Although JsAccount is intended mostly for mailnews accounts, the same
+ basic method of using a base class extended for specific types is also used
+ in other ways in mailnews code, including for addressbook types and views.
+ The technology may also be applied to those other object types as well.</p>
+ <h2>Role of JsAccount</h2>
+ <p>The JavaScript class system works very differently than the C++ system,
+ and you cannot use normal language constructs to override a C++ class with
+ a JavaScript class. What JsAccount allows you to do is to create XPCOM
+ objects in JavaScript, and use those objects to override or extend the
+ methods from the C++ base class in a way that will function correctly
+ whether those objects are executed from within C++ code or JavaScript
+ code. This allows you to create a new account using JavaScript code, while
+ using the same base class functionality that is used by the core C++
+ account types. Thus a new account type may be created in JavaScript-based
+ extension. The technology may also be used to create JavaScript
+ versions of existing account types in an incremental manner, slowly
+ converting methods from C++ to JavaScript.</p>
+ <p><br>
+ </p>
+ </body>
+</html>
diff --git a/mailnews/jsaccount/src/DelegateList.cpp b/mailnews/jsaccount/src/DelegateList.cpp
new file mode 100644
index 000000000..7d2bae30c
--- /dev/null
+++ b/mailnews/jsaccount/src/DelegateList.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DelegateList.h"
+
+// This class is used within JsAccount to allow static storage of a list
+// of methods to be overridden by JS implementations, in a way that can
+// be stored and manipulated in JS, but used efficiently in C++.
+
+namespace mozilla {
+namespace mailnews {
+
+NS_IMPL_ISUPPORTS(DelegateList, msgIDelegateList)
+
+NS_IMETHODIMP DelegateList::Add(const char *aMethodName)
+{
+ // __FUNCTION__ is the undecorated function name in gcc, but decorated in
+ // Windows. __func__ will resolve this when supported in VS 2015.
+ nsCString prettyFunction;
+#if defined (_MSC_VER)
+ prettyFunction.Append(mPrefix);
+#endif
+ prettyFunction.Append(nsDependentCString(aMethodName));
+
+ mMethods.Put(prettyFunction, true);
+ return NS_OK;
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/mailnews/jsaccount/src/DelegateList.h b/mailnews/jsaccount/src/DelegateList.h
new file mode 100644
index 000000000..aa7fc74c8
--- /dev/null
+++ b/mailnews/jsaccount/src/DelegateList.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _DelegateList_H_
+#define _DelegateList_H_
+
+#include "nsISupports.h"
+#include "msgIDelegateList.h"
+#include "nsDataHashtable.h"
+
+namespace mozilla {
+namespace mailnews {
+
+// This class provides a list of method names to delegate to another object.
+class DelegateList : public msgIDelegateList
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MSGIDELEGATELIST
+ explicit DelegateList(const char *aWindowsPrefix) :
+ mPrefix(aWindowsPrefix)
+ { }
+ nsDataHashtable<nsCStringHashKey, bool> mMethods;
+
+protected:
+ virtual ~DelegateList() { }
+ nsCString mPrefix; // Windows decorated method prefix.
+};
+
+} // namespace mailnews
+} // namespace mozilla
+
+/*
+ * This macro is used in forwarding functions.
+ * _interface: the interface being forwarded.
+ * _jsdelegate: the name of the JS pointer that implements a particular
+ * interface.
+ *
+ * You must follow the naming convention:
+ * 1) use mCppBase as the name of the C++ base class instance.
+ * 2) use mMethod as the name of the DelegateList object.
+ **/
+
+#define DELEGATE_JS(_interface, _jsdelegate) (\
+ _jsdelegate && mMethods && \
+ mMethods->Contains(nsLiteralCString(__FUNCTION__)) ? \
+ _jsdelegate : nsCOMPtr<_interface>(do_QueryInterface(mCppBase)))
+
+#endif
diff --git a/mailnews/jsaccount/src/JaAbDirectory.cpp b/mailnews/jsaccount/src/JaAbDirectory.cpp
new file mode 100644
index 000000000..7d9c16d78
--- /dev/null
+++ b/mailnews/jsaccount/src/JaAbDirectory.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "JaAbDirectory.h"
+#include "nsISupportsUtils.h"
+#include "nsIFile.h"
+#include "nsIMsgHdr.h"
+#include "nsIMessenger.h"
+#include "nsMsgBaseCID.h"
+#include "nsComponentManagerUtils.h"
+
+namespace mozilla {
+namespace mailnews {
+
+NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppAbDirectory, nsAbDirProperty,
+ nsIInterfaceRequestor)
+
+// nsIInterfaceRequestor implementation
+NS_IMETHODIMP JaBaseCppAbDirectory::GetInterface(const nsIID & aIID, void **aSink)
+{
+ return QueryInterface(aIID, aSink);
+}
+
+// Delegator
+NS_IMPL_ISUPPORTS_INHERITED(JaCppAbDirectoryDelegator,
+ JaBaseCppAbDirectory,
+ msgIOverride)
+
+// Delegator object to bypass JS method override.
+NS_IMPL_ISUPPORTS(JaCppAbDirectoryDelegator::Super,
+ nsIAbDirectory,
+ nsIAbCollection,
+ nsIAbItem,
+ nsIInterfaceRequestor)
+
+JaCppAbDirectoryDelegator::JaCppAbDirectoryDelegator() :
+ mCppBase(new Super(this)),
+ mMethods(nullptr)
+{ }
+
+NS_IMETHODIMP JaCppAbDirectoryDelegator::SetMethodsToDelegate(msgIDelegateList* aDelegateList)
+{
+ if (!aDelegateList)
+ {
+ NS_WARNING("Null delegate list");
+ return NS_ERROR_NULL_POINTER;
+ }
+ // We static_cast since we want to use the hash object directly.
+ mDelegateList = static_cast<DelegateList*> (aDelegateList);
+ mMethods = &(mDelegateList->mMethods);
+ return NS_OK;
+}
+NS_IMETHODIMP JaCppAbDirectoryDelegator::GetMethodsToDelegate(msgIDelegateList** aDelegateList)
+{
+ if (!mDelegateList)
+ mDelegateList = new DelegateList("mozilla::mailnews::JaCppAbDirectoryDelegator::");
+ mMethods = &(mDelegateList->mMethods);
+ NS_ADDREF(*aDelegateList = mDelegateList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP JaCppAbDirectoryDelegator::SetJsDelegate(nsISupports* aJsDelegate)
+{
+ // If these QIs fail, then overrides are not provided for methods in that
+ // interface, which is OK.
+ mJsISupports = aJsDelegate;
+ mJsIAbDirectory = do_QueryInterface(aJsDelegate);
+ mJsIAbCollection = do_QueryInterface(aJsDelegate);
+ mJsIAbItem = do_QueryInterface(aJsDelegate);
+ mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate);
+ return NS_OK;
+}
+NS_IMETHODIMP JaCppAbDirectoryDelegator::GetJsDelegate(nsISupports **aJsDelegate)
+{
+ NS_ENSURE_ARG_POINTER(aJsDelegate);
+ if (mJsISupports)
+ {
+ NS_ADDREF(*aJsDelegate = mJsISupports);
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP JaCppAbDirectoryDelegator::GetCppBase(nsISupports** aCppBase)
+{
+ nsCOMPtr<nsISupports> cppBaseSupports;
+ cppBaseSupports = NS_ISUPPORTS_CAST(nsIAbDirectory*, mCppBase);
+ NS_ENSURE_STATE(cppBaseSupports);
+ cppBaseSupports.forget(aCppBase);
+
+ return NS_OK;
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/mailnews/jsaccount/src/JaAbDirectory.h b/mailnews/jsaccount/src/JaAbDirectory.h
new file mode 100644
index 000000000..8c7e37ff7
--- /dev/null
+++ b/mailnews/jsaccount/src/JaAbDirectory.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JaAbDirectory_H_
+#define _JaAbDirectory_H_
+
+#include "DelegateList.h"
+#include "msgIOverride.h"
+#include "nsAbDirProperty.h"
+#include "nsAutoPtr.h"
+#include "nsDataHashtable.h"
+#include "nsIInterfaceRequestor.h"
+
+namespace mozilla {
+namespace mailnews {
+
+/* Header file */
+
+// This class is an XPCOM component, usable in JS, that calls the methods
+// in the C++ base class (bypassing any JS override).
+class JaBaseCppAbDirectory : public nsAbDirProperty,
+ public nsIInterfaceRequestor
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINTERFACEREQUESTOR
+ JaBaseCppAbDirectory() { }
+
+protected:
+ virtual ~JaBaseCppAbDirectory() { }
+
+};
+
+class JaCppAbDirectoryDelegator : public JaBaseCppAbDirectory,
+ public msgIOverride
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_MSGIOVERRIDE
+
+ NS_FORWARD_NSIABDIRECTORY(DELEGATE_JS(nsIAbDirectory, mJsIAbDirectory)->)
+ NS_FORWARD_NSIABCOLLECTION(DELEGATE_JS(nsIAbCollection, mJsIAbCollection)->)
+ NS_FORWARD_NSIABITEM(DELEGATE_JS(nsIAbItem, mJsIAbItem)->)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(DELEGATE_JS(nsIInterfaceRequestor, mJsIInterfaceRequestor)->)
+
+ JaCppAbDirectoryDelegator();
+
+private:
+ virtual ~JaCppAbDirectoryDelegator() {
+ }
+
+ class Super : public nsIAbDirectory,
+ public nsIInterfaceRequestor
+ {
+ public:
+ explicit Super(JaCppAbDirectoryDelegator* aFakeThis) {mFakeThis = aFakeThis;}
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIABDIRECTORY(mFakeThis->JaBaseCppAbDirectory::)
+ NS_FORWARD_NSIABCOLLECTION(mFakeThis->JaBaseCppAbDirectory::)
+ NS_FORWARD_NSIABITEM(mFakeThis->JaBaseCppAbDirectory::)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppAbDirectory::)
+ private:
+ virtual ~Super() {}
+ JaCppAbDirectoryDelegator* mFakeThis;
+ };
+
+ // Interfaces that may be overridden by JS.
+ nsCOMPtr<nsIAbDirectory> mJsIAbDirectory;
+ nsCOMPtr<nsIAbCollection> mJsIAbCollection;
+ nsCOMPtr<nsIAbItem> mJsIAbItem;
+ nsCOMPtr<nsIInterfaceRequestor> mJsIInterfaceRequestor;
+
+ nsCOMPtr<nsISupports> mJsISupports;
+
+ // Class to bypass JS delegates. nsCOMPtr for when we do cycle collection.
+ nsCOMPtr<nsIAbDirectory> mCppBase;
+
+ RefPtr<DelegateList> mDelegateList;
+ nsDataHashtable<nsCStringHashKey, bool>* mMethods;
+
+};
+
+} // namespace mailnews
+} // namespace mozilla
+
+#endif
diff --git a/mailnews/jsaccount/src/JaCompose.cpp b/mailnews/jsaccount/src/JaCompose.cpp
new file mode 100644
index 000000000..a6c1d59f3
--- /dev/null
+++ b/mailnews/jsaccount/src/JaCompose.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "JaCompose.h"
+#include "nsISupportsUtils.h"
+#include "nsComponentManagerUtils.h"
+
+// This file specifies the implementation of nsIMsgCompose.idl objects
+// in the JsAccount system.
+
+namespace mozilla {
+namespace mailnews {
+
+NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppCompose,
+ nsMsgCompose,
+ nsIInterfaceRequestor)
+
+// nsIInterfaceRequestor implementation
+NS_IMETHODIMP
+JaBaseCppCompose::GetInterface(const nsIID & aIID, void **aSink)
+{
+ return QueryInterface(aIID, aSink);
+}
+
+// Delegator object to bypass JS method override.
+
+JaCppComposeDelegator::JaCppComposeDelegator() {
+ mCppBase = do_QueryInterface(
+ NS_ISUPPORTS_CAST(nsIMsgCompose*, new Super(this)));
+ mMethods = nullptr;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(JaCppComposeDelegator,
+ JaBaseCppCompose,
+ msgIOverride)
+
+NS_IMPL_ISUPPORTS(JaCppComposeDelegator::Super,
+ nsIMsgCompose,
+ nsIMsgSendListener,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+JaCppComposeDelegator::SetMethodsToDelegate(msgIDelegateList* aDelegateList)
+{
+ if (!aDelegateList)
+ {
+ NS_WARNING("Null delegate list");
+ return NS_ERROR_NULL_POINTER;
+ }
+ // We static_cast since we want to use the hash object directly.
+ mDelegateList = static_cast<DelegateList*> (aDelegateList);
+ mMethods = &(mDelegateList->mMethods);
+ return NS_OK;
+}
+NS_IMETHODIMP
+JaCppComposeDelegator::GetMethodsToDelegate(msgIDelegateList** aDelegateList)
+{
+ if (!mDelegateList)
+ mDelegateList = new DelegateList("mozilla::mailnews::JaCppComposeDelegator::");
+ mMethods = &(mDelegateList->mMethods);
+ NS_ADDREF(*aDelegateList = mDelegateList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+JaCppComposeDelegator::SetJsDelegate(nsISupports* aJsDelegate)
+{
+ // If these QIs fail, then overrides are not provided for methods in that
+ // interface, which is OK.
+ mJsISupports = aJsDelegate;
+ mJsIMsgCompose = do_QueryInterface(aJsDelegate);
+ mJsIMsgSendListener = do_QueryInterface(aJsDelegate);
+ mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate);
+ return NS_OK;
+}
+NS_IMETHODIMP
+JaCppComposeDelegator::GetJsDelegate(nsISupports **aJsDelegate)
+{
+ NS_ENSURE_ARG_POINTER(aJsDelegate);
+ if (mJsISupports)
+ {
+ NS_ADDREF(*aJsDelegate = mJsISupports);
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP
+JaCppComposeDelegator::GetCppBase(nsISupports** aCppBase)
+{
+ nsCOMPtr<nsISupports> cppBaseSupports;
+ cppBaseSupports = NS_ISUPPORTS_CAST(nsIMsgCompose*, mCppBase);
+ NS_ENSURE_STATE(cppBaseSupports);
+ cppBaseSupports.forget(aCppBase);
+
+ return NS_OK;
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/mailnews/jsaccount/src/JaCompose.h b/mailnews/jsaccount/src/JaCompose.h
new file mode 100644
index 000000000..800d1975e
--- /dev/null
+++ b/mailnews/jsaccount/src/JaCompose.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JaCompose_H_
+#define _JaCompose_H_
+
+#include "DelegateList.h"
+#include "msgIOverride.h"
+#include "nsMsgCompose.h"
+#include "nsAutoPtr.h"
+#include "nsDataHashtable.h"
+#include "nsIInterfaceRequestor.h"
+
+// This file specifies the definition of nsIMsgCompose.idl objects
+// in the JsAccount system.
+
+namespace mozilla {
+namespace mailnews {
+
+/* Header file */
+
+// This class is an XPCOM component, usable in JS, that calls the methods
+// in the C++ base class (bypassing any JS override).
+class JaBaseCppCompose : public nsMsgCompose,
+ public nsIInterfaceRequestor
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINTERFACEREQUESTOR
+ JaBaseCppCompose() { }
+
+protected:
+ virtual ~JaBaseCppCompose() { }
+
+};
+
+class JaCppComposeDelegator : public JaBaseCppCompose,
+ public msgIOverride
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_MSGIOVERRIDE
+
+ NS_FORWARD_NSIMSGCOMPOSE(DELEGATE_JS(nsIMsgCompose, mJsIMsgCompose)->)
+ NS_FORWARD_NSIMSGSENDLISTENER(DELEGATE_JS(nsIMsgSendListener, mJsIMsgSendListener)->)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(DELEGATE_JS(nsIInterfaceRequestor, mJsIInterfaceRequestor)->)
+
+ JaCppComposeDelegator();
+
+private:
+ virtual ~JaCppComposeDelegator() {
+ }
+
+ // This class will call a method on the delegator, but force the use of the
+ // C++ cppBase class, bypassing any JS Delegate.
+ class Super : public nsIMsgCompose,
+ public nsIInterfaceRequestor
+ {
+ public:
+ explicit Super(JaCppComposeDelegator* aFakeThis) {mFakeThis = aFakeThis;}
+ NS_DECL_ISUPPORTS
+ // Forward all overridable methods, bypassing JS override.
+ NS_FORWARD_NSIMSGCOMPOSE(mFakeThis->JaBaseCppCompose::)
+ NS_FORWARD_NSIMSGSENDLISTENER(mFakeThis->JaBaseCppCompose::)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppCompose::)
+ private:
+ virtual ~Super() {};
+ JaCppComposeDelegator* mFakeThis;
+ };
+
+ // Interfaces that may be overridden by JS.
+ nsCOMPtr<nsIMsgCompose> mJsIMsgCompose;
+ nsCOMPtr<nsIMsgSendListener> mJsIMsgSendListener;
+ nsCOMPtr<nsIInterfaceRequestor> mJsIInterfaceRequestor;
+
+ nsCOMPtr<nsISupports> mJsISupports;
+
+ // Class to bypass JS delegates.
+ nsCOMPtr<nsIMsgCompose> mCppBase;
+
+ RefPtr<DelegateList> mDelegateList;
+ nsDataHashtable<nsCStringHashKey, bool>* mMethods;
+
+};
+
+} // namespace mailnews
+} // namespace mozilla
+
+#endif
diff --git a/mailnews/jsaccount/src/JaIncomingServer.cpp b/mailnews/jsaccount/src/JaIncomingServer.cpp
new file mode 100644
index 000000000..74296ac06
--- /dev/null
+++ b/mailnews/jsaccount/src/JaIncomingServer.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "JaIncomingServer.h"
+#include "nsISupportsUtils.h"
+#include "nsComponentManagerUtils.h"
+
+// This file specifies the implementation of nsIMsgIncomingServer.idl objects
+// in the JsAccount system.
+
+namespace mozilla {
+namespace mailnews {
+
+NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppIncomingServer,
+ nsMsgIncomingServer,
+ nsIInterfaceRequestor)
+
+// nsMsgIncomingServer overrides
+nsresult
+JaBaseCppIncomingServer::CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIInterfaceRequestor implementation
+NS_IMETHODIMP
+JaBaseCppIncomingServer::GetInterface(const nsIID & aIID, void **aSink)
+{
+ return QueryInterface(aIID, aSink);
+}
+
+// Delegator object to bypass JS method override.
+
+JaCppIncomingServerDelegator::JaCppIncomingServerDelegator() {
+ mCppBase = do_QueryInterface(
+ NS_ISUPPORTS_CAST(nsIMsgIncomingServer*, new Super(this)));
+ mMethods = nullptr;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(JaCppIncomingServerDelegator,
+ JaBaseCppIncomingServer,
+ msgIOverride)
+
+NS_IMPL_ISUPPORTS(JaCppIncomingServerDelegator::Super,
+ nsIMsgIncomingServer,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+JaCppIncomingServerDelegator::SetMethodsToDelegate(msgIDelegateList* aDelegateList)
+{
+ if (!aDelegateList)
+ {
+ NS_WARNING("Null delegate list");
+ return NS_ERROR_NULL_POINTER;
+ }
+ // We static_cast since we want to use the hash object directly.
+ mDelegateList = static_cast<DelegateList*> (aDelegateList);
+ mMethods = &(mDelegateList->mMethods);
+ return NS_OK;
+}
+NS_IMETHODIMP
+JaCppIncomingServerDelegator::GetMethodsToDelegate(msgIDelegateList** aDelegateList)
+{
+ if (!mDelegateList)
+ mDelegateList = new DelegateList("mozilla::mailnews::JaCppIncomingServerDelegator::");
+ mMethods = &(mDelegateList->mMethods);
+ NS_ADDREF(*aDelegateList = mDelegateList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+JaCppIncomingServerDelegator::SetJsDelegate(nsISupports* aJsDelegate)
+{
+ // If these QIs fail, then overrides are not provided for methods in that
+ // interface, which is OK.
+ mJsISupports = aJsDelegate;
+ mJsIMsgIncomingServer = do_QueryInterface(aJsDelegate);
+ mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate);
+ return NS_OK;
+}
+NS_IMETHODIMP
+JaCppIncomingServerDelegator::GetJsDelegate(nsISupports **aJsDelegate)
+{
+ NS_ENSURE_ARG_POINTER(aJsDelegate);
+ if (mJsISupports)
+ {
+ NS_ADDREF(*aJsDelegate = mJsISupports);
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP
+JaCppIncomingServerDelegator::GetCppBase(nsISupports** aCppBase)
+{
+ nsCOMPtr<nsISupports> cppBaseSupports;
+ cppBaseSupports = NS_ISUPPORTS_CAST(nsIMsgIncomingServer*, mCppBase);
+ NS_ENSURE_STATE(cppBaseSupports);
+ cppBaseSupports.forget(aCppBase);
+
+ return NS_OK;
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/mailnews/jsaccount/src/JaIncomingServer.h b/mailnews/jsaccount/src/JaIncomingServer.h
new file mode 100644
index 000000000..cac0dfce4
--- /dev/null
+++ b/mailnews/jsaccount/src/JaIncomingServer.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JaIncomingServer_H_
+#define _JaIncomingServer_H_
+
+#include "DelegateList.h"
+#include "msgIOverride.h"
+#include "nsMsgIncomingServer.h"
+#include "nsAutoPtr.h"
+#include "nsDataHashtable.h"
+#include "nsIInterfaceRequestor.h"
+
+// This file specifies the definition of nsIMsgIncomingServer.idl objects
+// in the JsAccount system.
+
+namespace mozilla {
+namespace mailnews {
+
+/* Header file */
+
+// This class is an XPCOM component, usable in JS, that calls the methods
+// in the C++ base class (bypassing any JS override).
+class JaBaseCppIncomingServer : public nsMsgIncomingServer,
+ public nsIInterfaceRequestor
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINTERFACEREQUESTOR
+ JaBaseCppIncomingServer() { }
+
+ // nsMsgIncomingServer overrides
+ nsresult CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder) override;
+
+protected:
+ virtual ~JaBaseCppIncomingServer() { }
+
+};
+
+class JaCppIncomingServerDelegator : public JaBaseCppIncomingServer,
+ public msgIOverride
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_MSGIOVERRIDE
+
+ NS_FORWARD_NSIMSGINCOMINGSERVER(DELEGATE_JS(nsIMsgIncomingServer, mJsIMsgIncomingServer)->)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(DELEGATE_JS(nsIInterfaceRequestor, mJsIInterfaceRequestor)->)
+
+ JaCppIncomingServerDelegator();
+
+private:
+ virtual ~JaCppIncomingServerDelegator() {
+ }
+
+ // This class will call a method on the delegator, but force the use of the
+ // C++ parent class, bypassing any JS Delegate.
+ class Super : public nsIMsgIncomingServer,
+ public nsIInterfaceRequestor
+ {
+ public:
+ explicit Super(JaCppIncomingServerDelegator* aFakeThis) {mFakeThis = aFakeThis;}
+ NS_DECL_ISUPPORTS
+ // Forward all overridable methods, bypassing JS override.
+ NS_FORWARD_NSIMSGINCOMINGSERVER(mFakeThis->JaBaseCppIncomingServer::)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppIncomingServer::)
+ private:
+ virtual ~Super() {};
+ JaCppIncomingServerDelegator* mFakeThis;
+ };
+
+ // Interfaces that may be overridden by JS.
+ nsCOMPtr<nsIMsgIncomingServer> mJsIMsgIncomingServer;
+ nsCOMPtr<nsIInterfaceRequestor> mJsIInterfaceRequestor;
+
+ nsCOMPtr<nsISupports> mJsISupports;
+
+ // Class to bypass JS delegates.
+ nsCOMPtr<nsIMsgIncomingServer> mCppBase;
+
+ RefPtr<DelegateList> mDelegateList;
+ nsDataHashtable<nsCStringHashKey, bool>* mMethods;
+
+
+};
+
+} // namespace mailnews
+} // namespace mozilla
+
+#endif
diff --git a/mailnews/jsaccount/src/JaMsgFolder.cpp b/mailnews/jsaccount/src/JaMsgFolder.cpp
new file mode 100644
index 000000000..e51528e11
--- /dev/null
+++ b/mailnews/jsaccount/src/JaMsgFolder.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "JaMsgFolder.h"
+#include "nsISupportsUtils.h"
+#include "nsMsgBaseCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIComponentRegistrar.h"
+#include "nsMsgDBCID.h"
+
+#define MAILDATABASE_CONTRACTID_BASE "@mozilla.org/nsMsgDatabase/msgDB-"
+
+namespace mozilla {
+namespace mailnews {
+
+NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppMsgFolder, nsMsgDBFolder,
+ nsIInterfaceRequestor)
+
+// nsIInterfaceRequestor implementation
+NS_IMETHODIMP
+JaBaseCppMsgFolder::GetInterface(const nsIID & aIID, void **aSink)
+{
+ return QueryInterface(aIID, aSink);
+}
+
+// Definition of abstract nsMsgDBFolder methods.
+nsresult
+JaBaseCppMsgFolder::GetDatabase()
+{
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ {
+
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the database, keeping it if it is "out of date"
+ rv = msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ {
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+ NS_ENSURE_STATE(mDatabase);
+ // not sure about this ... the issue is that if the summary is not valid, then
+ // the db does not get added to the cache in the future, and reindexes
+ // do not show all of the messages.
+ //mDatabase->SetSummaryValid(true);
+ mDatabase->SetSummaryValid(false);
+ CreateDummyFile(this);
+ }
+
+ if (rv != NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ NS_ENSURE_SUCCESS(rv, rv);
+ else if (mDatabase)
+ {
+ // Not going to warn here, because on initialization we set all
+ // databases as invalid.
+ //NS_WARNING("Mail Summary database is out of date");
+ // Grrr, the only way to get this into the cache is to set the db as valid,
+ // close, reopen, then set as invalid.
+ mDatabase->SetSummaryValid(true);
+ msgDBService->ForceFolderDBClosed(this);
+ rv = msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase));
+ if (mDatabase)
+ mDatabase->SetSummaryValid(false);
+ }
+
+ if (mDatabase)
+ {
+ //
+ // When I inadvertently deleted the out-of-date database, I hit this code with
+ // the db's m_dbFolderInfo as null from the delete, yet the local mDatabase
+ // reference kept the database alive. So I hit an assert when I tried to open
+ // the database. Be careful if you try to fix the out-of-date issues!
+ //
+ //UpdateNewMessages();
+ if(mAddListener)
+ mDatabase->AddListener(this);
+ // UpdateSummaryTotals can null mDatabase during initialization, so we save a local copy
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ UpdateSummaryTotals(true);
+ mDatabase = database;
+
+ }
+ }
+
+ return rv;
+}
+
+/*
+ * The utility function GetSummaryFileLocation takes a folder file,
+ * then appends .msf to come up with the name of the database file. So
+ * we need a placeholder file with simply the folder name. This method
+ * creates an appropriate file as a placeholder, or you may use the file if
+ * appropriate.
+ */
+nsresult
+JaBaseCppMsgFolder::CreateDummyFile(nsIMsgFolder* aMailFolder)
+{
+ nsresult rv;
+ if (!aMailFolder)
+ return NS_OK;
+ nsCOMPtr <nsIFile> path;
+ // need to make sure folder exists...
+ aMailFolder->GetFilePath(getter_AddRefs(path));
+ if (path)
+ {
+ bool exists;
+ rv = path->Exists(&exists);
+ if (!exists)
+ {
+ rv = path->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+// AFAICT this is unused in mailnews code.
+nsresult
+JaBaseCppMsgFolder::CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// Delegator object to bypass JS method override.
+
+JaCppMsgFolderDelegator::JaCppMsgFolderDelegator() :
+ mCppBase(new Super(this)),
+ mMethods(nullptr)
+{ }
+
+NS_IMPL_ISUPPORTS_INHERITED(JaCppMsgFolderDelegator, JaBaseCppMsgFolder,
+ msgIOverride)
+
+NS_IMPL_ISUPPORTS(JaCppMsgFolderDelegator::Super,
+ nsIMsgFolder,
+ nsIRDFResource,
+ nsIRDFNode,
+ nsIDBChangeListener,
+ nsIUrlListener,
+ nsIJunkMailClassificationListener,
+ nsIMsgTraitClassificationListener,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+JaCppMsgFolderDelegator::SetMethodsToDelegate(msgIDelegateList* aDelegateList)
+{
+ if (!aDelegateList)
+ {
+ NS_WARNING("Null delegate list");
+ return NS_ERROR_NULL_POINTER;
+ }
+ // We static_cast since we want to use the hash object directly.
+ mDelegateList = static_cast<DelegateList*> (aDelegateList);
+ mMethods = &(mDelegateList->mMethods);
+ return NS_OK;
+}
+NS_IMETHODIMP
+JaCppMsgFolderDelegator::GetMethodsToDelegate(msgIDelegateList** aDelegateList)
+{
+ if (!mDelegateList)
+ mDelegateList = new DelegateList("mozilla::mailnews::JaCppMsgFolderDelegator::");
+ mMethods = &(mDelegateList->mMethods);
+ NS_ADDREF(*aDelegateList = mDelegateList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP JaCppMsgFolderDelegator::SetJsDelegate(nsISupports* aJsDelegate)
+{
+ // If these QIs fail, then overrides are not provided for methods in that
+ // interface, which is OK.
+ mJsISupports = aJsDelegate;
+ mJsIMsgFolder = do_QueryInterface(aJsDelegate);
+ mJsIDBChangeListener = do_QueryInterface(aJsDelegate);
+ mJsIUrlListener = do_QueryInterface(aJsDelegate);
+ mJsIJunkMailClassificationListener = do_QueryInterface(aJsDelegate);
+ mJsIMsgTraitClassificationListener = do_QueryInterface(aJsDelegate);
+ mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate);
+ return NS_OK;
+}
+NS_IMETHODIMP JaCppMsgFolderDelegator::GetJsDelegate(nsISupports **aJsDelegate)
+{
+ NS_ENSURE_ARG_POINTER(aJsDelegate);
+ if (mJsISupports)
+ {
+ NS_ADDREF(*aJsDelegate = mJsISupports);
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP JaCppMsgFolderDelegator::GetCppBase(nsISupports** aCppBase)
+{
+ nsCOMPtr<nsISupports> cppBaseSupports;
+ cppBaseSupports = NS_ISUPPORTS_CAST(nsIMsgFolder*, mCppBase);
+ NS_ENSURE_STATE(cppBaseSupports);
+ cppBaseSupports.forget(aCppBase);
+
+ return NS_OK;
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/mailnews/jsaccount/src/JaMsgFolder.h b/mailnews/jsaccount/src/JaMsgFolder.h
new file mode 100644
index 000000000..d1b7d37aa
--- /dev/null
+++ b/mailnews/jsaccount/src/JaMsgFolder.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JaMsgFolder_H_
+#define _JaMsgFolder_H_
+
+#include "DelegateList.h"
+#include "msgIOverride.h"
+#include "nsMsgDBFolder.h"
+#include "nsAutoPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDataHashtable.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsWeakReference.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace mailnews {
+
+/* Header file */
+
+// This class is an XPCOM component, usable in JS, that calls the methods
+// in the C++ base class (bypassing any JS override).
+class JaBaseCppMsgFolder : public nsMsgDBFolder,
+ public nsIInterfaceRequestor
+
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINTERFACEREQUESTOR
+ JaBaseCppMsgFolder() { }
+
+ // nsMsgDBFolder overrides
+
+ nsresult CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder) override;
+ nsresult GetDatabase() override;
+
+ // Local Utility Functions
+
+ // Create a placeholder file to represent a folder.
+ nsresult CreateDummyFile(nsIMsgFolder* aMailFolder);
+
+protected:
+ virtual ~JaBaseCppMsgFolder() { }
+
+};
+
+class JaCppMsgFolderDelegator : public JaBaseCppMsgFolder,
+ public msgIOverride
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_MSGIOVERRIDE
+
+ // Note that we do not support override of RDF methods.
+ NS_FORWARD_NSIRDFRESOURCE(JaBaseCppMsgFolder::)
+ NS_FORWARD_NSIRDFNODE(JaBaseCppMsgFolder::)
+ NS_FORWARD_NSIMSGFOLDER(DELEGATE_JS(nsIMsgFolder, mJsIMsgFolder)->)
+ NS_FORWARD_NSIDBCHANGELISTENER(
+ DELEGATE_JS(nsIDBChangeListener, mJsIDBChangeListener)->)
+ NS_FORWARD_NSIURLLISTENER(DELEGATE_JS(nsIUrlListener, mJsIUrlListener)->)
+ NS_FORWARD_NSIJUNKMAILCLASSIFICATIONLISTENER(
+ DELEGATE_JS(nsIJunkMailClassificationListener,
+ mJsIJunkMailClassificationListener)->)
+ NS_FORWARD_NSIMSGTRAITCLASSIFICATIONLISTENER(
+ DELEGATE_JS(nsIMsgTraitClassificationListener,
+ mJsIMsgTraitClassificationListener)->)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(
+ DELEGATE_JS(nsIInterfaceRequestor, mJsIInterfaceRequestor)->)
+
+ JaCppMsgFolderDelegator();
+
+private:
+ virtual ~JaCppMsgFolderDelegator() {
+ }
+
+ class Super : public nsIMsgFolder,
+ public nsIRDFResource,
+ public nsIDBChangeListener,
+ public nsIUrlListener,
+ public nsIJunkMailClassificationListener,
+ public nsIMsgTraitClassificationListener,
+ public nsIInterfaceRequestor
+ {
+ public:
+ // Why fake this? Because this method is fully owned by
+ // JaCppMsgFolderDelegator, and this reference is to the "this" of the
+ // main method. But it is not really the local "this".
+ explicit Super(JaCppMsgFolderDelegator* aFakeThis) {mFakeThis = aFakeThis;}
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIMSGFOLDER(mFakeThis->JaBaseCppMsgFolder::)
+ NS_FORWARD_NSIRDFRESOURCE(mFakeThis->JaBaseCppMsgFolder::)
+ NS_FORWARD_NSIRDFNODE(mFakeThis->JaBaseCppMsgFolder::)
+ NS_FORWARD_NSIDBCHANGELISTENER(mFakeThis->JaBaseCppMsgFolder::)
+ NS_FORWARD_NSIURLLISTENER(mFakeThis->JaBaseCppMsgFolder::)
+ NS_FORWARD_NSIJUNKMAILCLASSIFICATIONLISTENER(mFakeThis->JaBaseCppMsgFolder::)
+ NS_FORWARD_NSIMSGTRAITCLASSIFICATIONLISTENER(mFakeThis->JaBaseCppMsgFolder::)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppMsgFolder::)
+ private:
+ virtual ~Super() {}
+ JaCppMsgFolderDelegator* mFakeThis;
+ };
+
+ // Interfaces that may be overridden by JS.
+ nsCOMPtr<nsIMsgFolder> mJsIMsgFolder;
+ nsCOMPtr<nsIDBChangeListener> mJsIDBChangeListener;
+ nsCOMPtr<nsIUrlListener> mJsIUrlListener;
+ nsCOMPtr<nsIJunkMailClassificationListener> mJsIJunkMailClassificationListener;
+ nsCOMPtr<nsIMsgTraitClassificationListener> mJsIMsgTraitClassificationListener;
+ nsCOMPtr<nsIInterfaceRequestor> mJsIInterfaceRequestor;
+
+ nsCOMPtr<nsISupports> mJsISupports;
+
+ nsCOMPtr<nsIMsgFolder> mCppBase;
+ RefPtr<DelegateList> mDelegateList;
+ nsDataHashtable<nsCStringHashKey, bool>* mMethods;
+
+};
+
+} // namespace mailnews
+} // namespace mozilla
+
+#endif
diff --git a/mailnews/jsaccount/src/JaSend.cpp b/mailnews/jsaccount/src/JaSend.cpp
new file mode 100644
index 000000000..5a473d2f8
--- /dev/null
+++ b/mailnews/jsaccount/src/JaSend.cpp
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "JaSend.h"
+#include "nsISupportsUtils.h"
+#include "nsComponentManagerUtils.h"
+
+// This file specifies the implementation of nsIMsgSend.idl objects
+// in the JsAccount system.
+
+namespace mozilla {
+namespace mailnews {
+
+NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppSend,
+ nsMsgComposeAndSend,
+ nsIInterfaceRequestor)
+
+// nsIInterfaceRequestor implementation
+NS_IMETHODIMP
+JaBaseCppSend::GetInterface(const nsIID & aIID, void **aSink)
+{
+ return QueryInterface(aIID, aSink);
+}
+
+// Delegator object to bypass JS method override.
+
+JaCppSendDelegator::JaCppSendDelegator() {
+ mCppBase = do_QueryInterface(
+ NS_ISUPPORTS_CAST(nsIMsgSend*, new Super(this)));
+ mMethods = nullptr;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(JaCppSendDelegator,
+ JaBaseCppSend,
+ msgIOverride)
+
+NS_IMPL_ISUPPORTS(JaCppSendDelegator::Super,
+ nsIMsgSend,
+ nsIMsgOperationListener,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+JaCppSendDelegator::SetMethodsToDelegate(msgIDelegateList* aDelegateList)
+{
+ if (!aDelegateList)
+ {
+ NS_WARNING("Null delegate list");
+ return NS_ERROR_NULL_POINTER;
+ }
+ // We static_cast since we want to use the hash object directly.
+ mDelegateList = static_cast<DelegateList*> (aDelegateList);
+ mMethods = &(mDelegateList->mMethods);
+ return NS_OK;
+}
+NS_IMETHODIMP
+JaCppSendDelegator::GetMethodsToDelegate(msgIDelegateList** aDelegateList)
+{
+ if (!mDelegateList)
+ mDelegateList = new DelegateList("mozilla::mailnews::JaCppSendDelegator::");
+ mMethods = &(mDelegateList->mMethods);
+ NS_ADDREF(*aDelegateList = mDelegateList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+JaCppSendDelegator::SetJsDelegate(nsISupports* aJsDelegate)
+{
+ // If these QIs fail, then overrides are not provided for methods in that
+ // interface, which is OK.
+ mJsISupports = aJsDelegate;
+ mJsIMsgSend = do_QueryInterface(aJsDelegate);
+ mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate);
+ return NS_OK;
+}
+NS_IMETHODIMP
+JaCppSendDelegator::GetJsDelegate(nsISupports **aJsDelegate)
+{
+ NS_ENSURE_ARG_POINTER(aJsDelegate);
+ if (mJsISupports)
+ {
+ NS_ADDREF(*aJsDelegate = mJsISupports);
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP
+JaCppSendDelegator::GetCppBase(nsISupports** aCppBase)
+{
+ nsCOMPtr<nsISupports> cppBaseSupports;
+ cppBaseSupports = NS_ISUPPORTS_CAST(nsIMsgSend*, mCppBase);
+ NS_ENSURE_STATE(cppBaseSupports);
+ cppBaseSupports.forget(aCppBase);
+
+ return NS_OK;
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/mailnews/jsaccount/src/JaSend.h b/mailnews/jsaccount/src/JaSend.h
new file mode 100644
index 000000000..42984798c
--- /dev/null
+++ b/mailnews/jsaccount/src/JaSend.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JaSend_H_
+#define _JaSend_H_
+
+#include "DelegateList.h"
+#include "msgIOverride.h"
+#include "nsMsgSend.h"
+#include "nsAutoPtr.h"
+#include "nsDataHashtable.h"
+#include "nsIInterfaceRequestor.h"
+
+// This file specifies the definition of nsIMsgSend.idl objects
+// in the JsAccount system.
+
+namespace mozilla {
+namespace mailnews {
+
+/* Header file */
+
+// This class is an XPCOM component, usable in JS, that calls the methods
+// in the C++ base class (bypassing any JS override).
+class JaBaseCppSend : public nsMsgComposeAndSend,
+ public nsIInterfaceRequestor
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINTERFACEREQUESTOR
+ JaBaseCppSend() { }
+
+protected:
+ virtual ~JaBaseCppSend() { }
+
+};
+
+class JaCppSendDelegator : public JaBaseCppSend,
+ public msgIOverride
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_MSGIOVERRIDE
+
+ NS_FORWARD_NSIMSGSEND(DELEGATE_JS(nsIMsgSend, mJsIMsgSend)->)
+ NS_FORWARD_NSIMSGOPERATIONLISTENER(
+ DELEGATE_JS(nsIMsgOperationListener, mJsIMsgOperationListener)->)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(
+ DELEGATE_JS(nsIInterfaceRequestor, mJsIInterfaceRequestor)->)
+
+ JaCppSendDelegator();
+
+private:
+ virtual ~JaCppSendDelegator() {
+ }
+
+ // This class will call a method on the delegator, but force the use of the
+ // C++ parent class, bypassing any JS Delegate.
+ class Super : public nsIMsgSend,
+ public nsIMsgOperationListener,
+ public nsIInterfaceRequestor
+ {
+ public:
+ explicit Super(JaCppSendDelegator* aFakeThis) {mFakeThis = aFakeThis;}
+ NS_DECL_ISUPPORTS
+ // Forward all overridable methods, bypassing JS override.
+ NS_FORWARD_NSIMSGSEND(mFakeThis->JaBaseCppSend::)
+ NS_FORWARD_NSIMSGOPERATIONLISTENER(mFakeThis->JaBaseCppSend::)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppSend::)
+ private:
+ virtual ~Super() {};
+ JaCppSendDelegator* mFakeThis;
+ };
+
+ // Interfaces that may be overridden by JS.
+ nsCOMPtr<nsIMsgSend> mJsIMsgSend;
+ nsCOMPtr<nsIMsgOperationListener> mJsIMsgOperationListener;
+ nsCOMPtr<nsIInterfaceRequestor> mJsIInterfaceRequestor;
+
+ nsCOMPtr<nsISupports> mJsISupports;
+
+ // Class to bypass JS delegates.
+ nsCOMPtr<nsIMsgSend> mCppBase;
+
+ RefPtr<DelegateList> mDelegateList;
+ nsDataHashtable<nsCStringHashKey, bool>* mMethods;
+
+
+};
+
+} // namespace mailnews
+} // namespace mozilla
+
+#endif
diff --git a/mailnews/jsaccount/src/JaUrl.cpp b/mailnews/jsaccount/src/JaUrl.cpp
new file mode 100644
index 000000000..f103460c6
--- /dev/null
+++ b/mailnews/jsaccount/src/JaUrl.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "JaUrl.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIFile.h"
+#include "nsIMessenger.h"
+#include "nsIMsgHdr.h"
+#include "nsISupportsUtils.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgUtils.h"
+
+// This file contains an implementation of mailnews URLs in JsAccount.
+
+namespace mozilla {
+namespace mailnews {
+
+NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppUrl, nsMsgMailNewsUrl,
+ nsIMsgMessageUrl,
+ nsIInterfaceRequestor,
+ nsISupportsWeakReference)
+
+// nsIMsgMailNewsUrl overrides
+NS_IMETHODIMP JaBaseCppUrl::GetFolder(nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_IF_ADDREF(*aFolder = mFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP JaBaseCppUrl::SetFolder(nsIMsgFolder *aFolder)
+{
+ mFolder = aFolder;
+ return NS_OK;
+}
+
+// nsIMsgMessageUrl implementation
+NS_IMETHODIMP JaBaseCppUrl::GetUri(char **aUri)
+{
+ if (!mUri.IsEmpty())
+ *aUri = ToNewCString(mUri);
+ else
+ return NS_ERROR_NOT_INITIALIZED;
+ return NS_OK;
+}
+NS_IMETHODIMP JaBaseCppUrl::SetUri(const char *aUri)
+{
+ mUri = aUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JaBaseCppUrl::GetMessageFile(nsIFile **aMessageFile)
+{
+ NS_ENSURE_ARG_POINTER(aMessageFile);
+ NS_IF_ADDREF(*aMessageFile = mMessageFile);
+ return NS_OK;
+}
+NS_IMETHODIMP JaBaseCppUrl::SetMessageFile(nsIFile *aMessageFile)
+{
+ mMessageFile = aMessageFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JaBaseCppUrl::GetAddDummyEnvelope(bool *aAddDummyEnvelope)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP JaBaseCppUrl::SetAddDummyEnvelope(bool aAddDummyEnvelope)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP JaBaseCppUrl::GetCanonicalLineEnding(bool *aCanonicalLineEnding)
+{
+ NS_ENSURE_ARG_POINTER(aCanonicalLineEnding);
+ *aCanonicalLineEnding = mCanonicalLineEnding;
+ return NS_OK;
+}
+NS_IMETHODIMP JaBaseCppUrl::SetCanonicalLineEnding(bool aCanonicalLineEnding)
+{
+ mCanonicalLineEnding = aCanonicalLineEnding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JaBaseCppUrl::GetOriginalSpec(char **aOriginalSpec)
+{
+ if (!aOriginalSpec || mOriginalSpec.IsEmpty())
+ return NS_ERROR_NULL_POINTER;
+ *aOriginalSpec = ToNewCString(mOriginalSpec);
+ return NS_OK;
+}
+NS_IMETHODIMP JaBaseCppUrl::SetOriginalSpec(const char *aOriginalSpec)
+{
+ mOriginalSpec = aOriginalSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JaBaseCppUrl::GetPrincipalSpec(nsACString& aPrincipalSpec)
+{
+ // URLs contain a lot of query parts. We want need a normalised form:
+ // scheme://server/folder?number=123
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsURL;
+ QueryInterface(NS_GET_IID(nsIMsgMailNewsUrl), getter_AddRefs(mailnewsURL));
+
+ nsAutoCString spec;
+ mailnewsURL->GetSpecIgnoringRef(spec);
+
+ nsAutoCString queryPart = MsgExtractQueryPart(spec, "number=");
+
+ // Strip any query part beginning with ? or /;
+ int32_t ind = spec.Find("/;");
+ if (ind != kNotFound)
+ spec.SetLength(ind);
+
+ ind = spec.FindChar('?');
+ if (ind != kNotFound)
+ spec.SetLength(ind);
+
+ if (!queryPart.IsEmpty())
+ spec += NS_LITERAL_CSTRING("?") + queryPart;
+
+ aPrincipalSpec.Assign(spec);
+ return NS_OK;
+}
+
+NS_IMETHODIMP JaBaseCppUrl::GetMessageHeader(nsIMsgDBHdr **aMessageHeader)
+{
+ // This routine does a lookup using messenger, assumming that the message URI
+ // has been set in mUri.
+ NS_ENSURE_TRUE(!mUri.IsEmpty(), NS_ERROR_NOT_INITIALIZED);
+ nsresult rv;
+ nsCOMPtr<nsIMessenger> messenger(do_CreateInstance(NS_MESSENGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = messenger->MsgHdrFromURI(mUri, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgHdr.forget(aMessageHeader);
+ return NS_OK;
+}
+
+NS_IMETHODIMP JaBaseCppUrl::SetMessageHeader(nsIMsgDBHdr *aMsgHdr)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIInterfaceRequestor implementation
+NS_IMETHODIMP JaBaseCppUrl::GetInterface(const nsIID & aIID, void **aSink)
+{
+ return QueryInterface(aIID, aSink);
+}
+
+// Delegator
+NS_IMPL_ISUPPORTS_INHERITED(JaCppUrlDelegator,
+ JaBaseCppUrl,
+ msgIOverride)
+
+// Delegator object to bypass JS method override.
+NS_IMPL_ISUPPORTS(JaCppUrlDelegator::Super,
+ nsIMsgMailNewsUrl,
+ nsIMsgMessageUrl,
+ nsIURI,
+ nsIURL,
+ nsIInterfaceRequestor)
+
+JaCppUrlDelegator::JaCppUrlDelegator() :
+ mCppBase(new Super(this)),
+ mMethods(nullptr)
+{ }
+
+NS_IMETHODIMP JaCppUrlDelegator::SetMethodsToDelegate(msgIDelegateList *aDelegateList)
+{
+ if (!aDelegateList)
+ {
+ NS_WARNING("Null delegate list");
+ return NS_ERROR_NULL_POINTER;
+ }
+ // We static_cast since we want to use the hash object directly.
+ mDelegateList = static_cast<DelegateList*> (aDelegateList);
+ mMethods = &(mDelegateList->mMethods);
+ return NS_OK;
+}
+NS_IMETHODIMP JaCppUrlDelegator::GetMethodsToDelegate(msgIDelegateList **aDelegateList)
+{
+ if (!mDelegateList)
+ mDelegateList = new DelegateList("mozilla::mailnews::JaCppUrlDelegator::");
+ mMethods = &(mDelegateList->mMethods);
+ NS_ADDREF(*aDelegateList = mDelegateList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP JaCppUrlDelegator::SetJsDelegate(nsISupports *aJsDelegate)
+{
+ // If these QIs fail, then overrides are not provided for methods in that
+ // interface, which is OK.
+ mJsISupports = aJsDelegate;
+ mJsIMsgMailNewsUrl = do_QueryInterface(aJsDelegate);
+ mJsIURI = do_QueryInterface(aJsDelegate);
+ mJsIURL = do_QueryInterface(aJsDelegate);
+ mJsIMsgMessageUrl = do_QueryInterface(aJsDelegate);
+ mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate);
+ return NS_OK;
+}
+NS_IMETHODIMP JaCppUrlDelegator::GetJsDelegate(nsISupports **aJsDelegate)
+{
+ NS_ENSURE_ARG_POINTER(aJsDelegate);
+ if (mJsISupports)
+ {
+ NS_ADDREF(*aJsDelegate = mJsISupports);
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP JaCppUrlDelegator::GetCppBase(nsISupports **aCppBase)
+{
+ nsCOMPtr<nsISupports> cppBaseSupports;
+ cppBaseSupports = NS_ISUPPORTS_CAST(nsIMsgMailNewsUrl*, mCppBase);
+ NS_ENSURE_STATE(cppBaseSupports);
+ cppBaseSupports.forget(aCppBase);
+
+ return NS_OK;
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/mailnews/jsaccount/src/JaUrl.h b/mailnews/jsaccount/src/JaUrl.h
new file mode 100644
index 000000000..c66f88f68
--- /dev/null
+++ b/mailnews/jsaccount/src/JaUrl.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JaUrl_H_
+#define _JaUrl_H_
+
+#include "DelegateList.h"
+#include "msgCore.h"
+#include "msgIOverride.h"
+#include "nsAutoPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDataHashtable.h"
+#include "nsIFile.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMsgFolder.h"
+#include "nsISupports.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace mailnews {
+
+/* Header file */
+
+// This class is an XPCOM component, usable in JS, that calls the methods
+// in the C++ base class (bypassing any JS override).
+class JaBaseCppUrl : public nsMsgMailNewsUrl,
+ public nsIMsgMessageUrl,
+ public nsIInterfaceRequestor,
+ public nsSupportsWeakReference
+
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMSGMESSAGEURL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ JaBaseCppUrl() { }
+
+ // nsIMsgMailNewsUrl overrides
+ NS_IMETHOD GetFolder(nsIMsgFolder **aFolder) override;
+ NS_IMETHOD SetFolder(nsIMsgFolder *aFolder) override;
+
+protected:
+ virtual ~JaBaseCppUrl() { }
+
+ // nsIMsgMailUrl variables.
+
+ nsCOMPtr<nsIMsgFolder> mFolder;
+
+ // nsIMsgMessageUrl variables.
+
+ // the uri for the original message, like ews-message://server/folder#123
+ nsCString mUri;
+ nsCOMPtr<nsIFile> mMessageFile;
+ bool mCanonicalLineEnding;
+ nsCString mOriginalSpec;
+};
+
+class JaCppUrlDelegator : public JaBaseCppUrl,
+ public msgIOverride
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_MSGIOVERRIDE
+
+ NS_FORWARD_NSIMSGMAILNEWSURL(DELEGATE_JS(nsIMsgMailNewsUrl, mJsIMsgMailNewsUrl)->)
+ NS_FORWARD_NSIURI(DELEGATE_JS(nsIURI, mJsIURI)->)
+ NS_FORWARD_NSIURL(DELEGATE_JS(nsIURL, mJsIURL)->)
+ NS_FORWARD_NSIMSGMESSAGEURL(DELEGATE_JS(nsIMsgMessageUrl, mJsIMsgMessageUrl)->)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(DELEGATE_JS(nsIInterfaceRequestor, mJsIInterfaceRequestor)->)
+
+ JaCppUrlDelegator();
+
+ class Super : public nsIMsgMailNewsUrl,
+ public nsIMsgMessageUrl,
+ public nsIInterfaceRequestor
+ {
+ public:
+ explicit Super(JaCppUrlDelegator *aFakeThis) {mFakeThis = aFakeThis;}
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIMSGMAILNEWSURL(mFakeThis->JaBaseCppUrl::)
+ NS_FORWARD_NSIURI(mFakeThis->JaBaseCppUrl::)
+ NS_FORWARD_NSIURL(mFakeThis->JaBaseCppUrl::)
+ NS_FORWARD_NSIMSGMESSAGEURL(mFakeThis->JaBaseCppUrl::)
+ NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppUrl::)
+ private:
+ virtual ~Super() {}
+ JaCppUrlDelegator *mFakeThis;
+ };
+
+private:
+ virtual ~JaCppUrlDelegator() {
+ }
+
+ // Interfaces that may be overridden by JS.
+ nsCOMPtr<nsIMsgMailNewsUrl> mJsIMsgMailNewsUrl;
+ nsCOMPtr<nsIURI> mJsIURI;
+ nsCOMPtr<nsIURL> mJsIURL;
+ nsCOMPtr<nsIMsgMessageUrl> mJsIMsgMessageUrl;
+ nsCOMPtr<nsIInterfaceRequestor> mJsIInterfaceRequestor;
+
+ // Owning reference to the JS override.
+ nsCOMPtr<nsISupports> mJsISupports;
+
+ // Class to bypass JS delegates. nsCOMPtr for when we do cycle collection.
+ nsCOMPtr<nsIMsgMailNewsUrl> mCppBase;
+
+ RefPtr<DelegateList> mDelegateList;
+ nsDataHashtable<nsCStringHashKey, bool> *mMethods;
+};
+
+} // namespace mailnews
+} // namespace mozilla
+
+#endif
diff --git a/mailnews/jsaccount/src/moz.build b/mailnews/jsaccount/src/moz.build
new file mode 100644
index 000000000..761bb04ca
--- /dev/null
+++ b/mailnews/jsaccount/src/moz.build
@@ -0,0 +1,28 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'DelegateList.cpp',
+ 'JaAbDirectory.cpp',
+ 'JaCompose.cpp',
+ 'JaIncomingServer.cpp',
+ 'JaMsgFolder.cpp',
+ 'JaSend.cpp',
+ 'JaUrl.cpp',
+]
+
+EXPORTS += [
+ 'DelegateList.h',
+ 'JaAbDirectory.h',
+ 'JaCompose.h',
+ 'JaIncomingServer.h',
+ 'JaMsgFolder.h',
+ 'JaSend.h',
+ 'JaUrl.h',
+]
+
+Library('JsAccount')
+FINAL_LIBRARY = 'mail'
diff --git a/mailnews/local/public/moz.build b/mailnews/local/public/moz.build
new file mode 100644
index 000000000..0c656b24b
--- /dev/null
+++ b/mailnews/local/public/moz.build
@@ -0,0 +1,31 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsILocalMailIncomingServer.idl',
+ 'nsIMailboxService.idl',
+ 'nsIMailboxUrl.idl',
+ 'nsIMovemailIncomingServer.idl',
+ 'nsIMovemailService.idl',
+ 'nsIMsgLocalMailFolder.idl',
+ 'nsIMsgParseMailMsgState.idl',
+ 'nsINewsBlogFeedDownloader.idl',
+ 'nsINoIncomingServer.idl',
+ 'nsINoneService.idl',
+ 'nsIPop3IncomingServer.idl',
+ 'nsIPop3Protocol.idl',
+ 'nsIPop3Service.idl',
+ 'nsIPop3Sink.idl',
+ 'nsIPop3URL.idl',
+ 'nsIRssIncomingServer.idl',
+ 'nsIRssService.idl',
+]
+
+XPIDL_MODULE = 'msglocal'
+
+EXPORTS += [
+ 'nsMsgLocalCID.h',
+]
+
diff --git a/mailnews/local/public/nsILocalMailIncomingServer.idl b/mailnews/local/public/nsILocalMailIncomingServer.idl
new file mode 100644
index 000000000..05bcc0ff2
--- /dev/null
+++ b/mailnews/local/public/nsILocalMailIncomingServer.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIMsgWindow;
+interface nsIUrlListener;
+interface nsIMsgFolder;
+
+[scriptable, uuid(f465a3ee-5b29-4da6-8b2e-d764bcba468e)]
+interface nsILocalMailIncomingServer : nsISupports
+{
+ /// Create the necessary default folders that must always exist in an account (e.g. Inbox/Trash).
+ void createDefaultMailboxes();
+
+ /// Set special folder flags on the default folders.
+ void setFlagsOnDefaultMailboxes();
+
+ nsIURI getNewMail(in nsIMsgWindow aMsgWindow, in nsIUrlListener aUrlListener, in nsIMsgFolder aInbox);
+};
+
diff --git a/mailnews/local/public/nsIMailboxService.idl b/mailnews/local/public/nsIMailboxService.idl
new file mode 100644
index 000000000..13d998844
--- /dev/null
+++ b/mailnews/local/public/nsIMailboxService.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIUrlListener.idl"
+
+interface nsIURI;
+interface nsIStreamListener;
+interface nsIMsgWindow;
+interface nsIFile;
+
+[scriptable, uuid(809FCD02-B9EA-4DC0-84F0-3FBC55AE11F1)]
+interface nsIMailboxService : nsISupports {
+
+ /*
+ * All of these functions build mailbox urls and run them. If you want a
+ * handle on the running task, pass in a valid nsIURI ptr. You can later
+ * interrupt this action by asking the netlib service manager to interrupt
+ * the url you are given back. Remember to release aURL when you are done
+ * with it. Pass nullptr in for aURL if you don't care about the returned URL.
+ */
+
+ /*
+ * Pass in a file path for the mailbox you wish to parse. You also need to
+ * pass in a mailbox parser (the consumer). The url listener can be null
+ * if you have no interest in tracking the url.
+ */
+ nsIURI ParseMailbox(in nsIMsgWindow aMsgWindow, in nsIFile aMailboxPath,
+ in nsIStreamListener aMailboxParser,
+ in nsIUrlListener aUrlListener);
+
+};
diff --git a/mailnews/local/public/nsIMailboxUrl.idl b/mailnews/local/public/nsIMailboxUrl.idl
new file mode 100644
index 000000000..d301db6c5
--- /dev/null
+++ b/mailnews/local/public/nsIMailboxUrl.idl
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIStreamListener;
+interface nsIMsgDBHdr;
+
+typedef long nsMailboxAction;
+
+[scriptable, uuid(2ac72280-90f4-4d80-8af1-5e7a1997e2a8)]
+interface nsIMailboxUrl : nsISupports {
+
+ // Mailbox urls which parse a mailbox folder require a consumer of the
+ // stream which will represent the mailbox. This consumer is the mailbox
+ // parser. As data from the mailbox folder is read in, the data will be
+ // written to a stream and the consumer will be notified through
+ // nsIStreamListenter::OnDataAvailable that the stream has data
+ // available...
+ // mscott: I wonder if the caller should be allowed to create and set
+ // the stream they want the data written to as well? Hmm....
+
+ attribute nsIStreamListener mailboxParser;
+
+ /////////////////////////////////////////////////////////////////////////
+ // Copy/Move mailbox urls require a mailbox copy handler which actually
+ // performs the copy.
+ /////////////////////////////////////////////////////////////////////////
+ attribute nsIStreamListener mailboxCopyHandler;
+
+ // Some mailbox urls include a message key for the message in question.
+ readonly attribute nsMsgKey messageKey;
+
+ // this is to support multiple msg move/copy in one url
+ void setMoveCopyMsgKeys(out nsMsgKey keysToFlag, in long numKeys);
+ void getMoveCopyMsgHdrForIndex(in unsigned long msgIndex, out nsIMsgDBHdr msgHdr);
+ readonly attribute unsigned long numMoveCopyMsgs;
+ attribute unsigned long curMoveCopyMsgIndex;
+ // mailbox urls to fetch a mail message can specify the size of
+ // the message...
+ // this saves us the trouble of having to open up the msg db and ask
+ // ourselves...
+ attribute unsigned long messageSize;
+
+ attribute nsMailboxAction mailboxAction;
+
+ /* these are nsMailboxActions */
+ const long ActionParseMailbox = 0;
+ const long ActionFetchMessage = 1;
+ const long ActionCopyMessage = 2;
+ const long ActionMoveMessage = 3;
+ const long ActionSaveMessageToDisk = 4;
+ const long ActionAppendMessageToDisk = 5;
+ const long ActionFetchPart = 6;
+};
+
diff --git a/mailnews/local/public/nsIMovemailIncomingServer.idl b/mailnews/local/public/nsIMovemailIncomingServer.idl
new file mode 100644
index 000000000..623ee83b3
--- /dev/null
+++ b/mailnews/local/public/nsIMovemailIncomingServer.idl
@@ -0,0 +1,11 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(0dbfa510-1dd2-11b2-9f0e-849c5a241ab6)]
+interface nsIMovemailIncomingServer : nsISupports {
+};
+
diff --git a/mailnews/local/public/nsIMovemailService.idl b/mailnews/local/public/nsIMovemailService.idl
new file mode 100644
index 000000000..564c06413
--- /dev/null
+++ b/mailnews/local/public/nsIMovemailService.idl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgWindow;
+interface nsIMsgFolder;
+interface nsIMovemailIncomingServer;
+interface nsIUrlListener;
+interface nsIURI;
+
+[scriptable, uuid(4c7786a4-1dd2-11b2-9fbe-c59d742de59b)]
+interface nsIMovemailService : nsISupports {
+
+ nsIURI GetNewMail(in nsIMsgWindow aMsgWindow,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgFolder aMsgFolder,
+ in nsIMovemailIncomingServer movemailServer);
+
+ nsIURI CheckForNewMail(in nsIUrlListener aUrlListener,
+ in nsIMsgFolder inbox,
+ in nsIMovemailIncomingServer movemailServer);
+};
diff --git a/mailnews/local/public/nsIMsgLocalMailFolder.idl b/mailnews/local/public/nsIMsgLocalMailFolder.idl
new file mode 100644
index 000000000..6bf1c5c5b
--- /dev/null
+++ b/mailnews/local/public/nsIMsgLocalMailFolder.idl
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIArray;
+interface nsIMsgWindow;
+interface nsIUrlListener;
+interface nsIMsgDatabase;
+interface nsIMsgDBHdr;
+interface nsIMsgFolder;
+interface nsIMsgCopyServiceListener;
+
+[ptr] native nsLocalFolderScanState(nsLocalFolderScanState);
+
+%{C++
+/* flags for markMsgsOnPop3Server */
+#define POP3_NONE 0
+#define POP3_DELETE 1
+#define POP3_FETCH_BODY 2
+#define POP3_FORCE_DEL 3
+
+struct nsLocalFolderScanState;
+%}
+
+[scriptable, uuid(ebf7576c-e15f-4aba-b021-cc6e9266e90c)]
+interface nsIMsgLocalMailFolder : nsISupports {
+ /**
+ * Set the default flags on the subfolders of this folder, such as
+ * Drafts, Templates, etc.
+ * @param flags bitwise OR matching the type of mailboxes you want to flag.
+ * This function will be smart and find the right names.
+ * E.g. nsMsgFolderFlags::Inbox | nsMsgFolderFlags::Drafts
+ */
+ void setFlagsOnDefaultMailboxes(in unsigned long flags);
+
+ /*
+ * This will return null if the db is out of date
+ */
+ nsIMsgDatabase getDatabaseWOReparse();
+
+ /*
+ * This will kick off a url to reparse the db if it's out of date.
+ * If aReparseUrlListener is null, folder will use itself as the listener
+ */
+ nsIMsgDatabase getDatabaseWithReparse(in nsIUrlListener aReparseUrlListener, in nsIMsgWindow aMsgWindow);
+ void parseFolder(in nsIMsgWindow aMsgWindow, in nsIUrlListener listener);
+ void copyFolderLocal(in nsIMsgFolder srcFolder, in boolean isMove, in nsIMsgWindow msgWindow, in nsIMsgCopyServiceListener listener );
+ void copyAllSubFolders(in nsIMsgFolder srcFolder, in nsIMsgWindow msgWindow, in nsIMsgCopyServiceListener listener );
+ void onCopyCompleted(in nsISupports aSrcSupport, in boolean aMoveCopySucceeded);
+ attribute boolean checkForNewMessagesAfterParsing;
+ void markMsgsOnPop3Server(in nsIArray aMessages, in int32_t aMark);
+
+ /**
+ * File size on disk has possibly changed - update and notify.
+ */
+ void refreshSizeOnDisk();
+
+ /**
+ * Creates a subfolder to the current folder with the passed in folder name.
+ * @param aFolderName name of the folder to create.
+ * @return newly created folder.
+ */
+ nsIMsgFolder createLocalSubfolder(in AString aFolderName);
+
+ /**
+ * Adds a message to the end of the folder, parsing it as it goes, and
+ * applying filters, if applicable.
+ * @param aMessage string containing the entire body of the message to add
+ * @return the nsIMsgDBHdr of the added message
+ */
+ nsIMsgDBHdr addMessage(in string aMessage);
+
+ /**
+ * Add one or more messages to the end of the folder in a single batch. Each
+ * batch requires an fsync() on the mailbox file so it is a good idea to
+ * try and minimize the number of calls you make to this method or addMessage.
+ *
+ * Filters are applied, if applicable.
+ *
+ * @param aMessageCount The number of messages.
+ * @param aMessages An array of pointers to strings containing entire message
+ * bodies.
+ * @return an array of nsIMsgDBHdr of the added messages
+ */
+ nsIArray addMessageBatch(in uint32_t aMessageCount,
+ [array, size_is(aMessageCount)] in string aMessages);
+
+ /**
+ * Functions for updating the UI while running DownloadMessagesForOffline:
+ * delete the old message before adding its newly downloaded body, and
+ * select the new message after it has replaced the old one
+ */
+ void deleteDownloadMsg(in nsIMsgDBHdr aMsgHdr, out boolean aDoSelect);
+ void selectDownloadMsg();
+ void notifyDelete();
+
+ /**
+ * Functions for grubbing through a folder to find the Uidl for a
+ * given msgDBHdr.
+ */
+ [noscript] void getFolderScanState(in nsLocalFolderScanState aState);
+ [noscript] void getUidlFromFolder(in nsLocalFolderScanState aState, in nsIMsgDBHdr aMsgHdr);
+
+
+ /**
+ * Shows warning if there is not enough space in the message store
+ * for a message of the given size.
+ */
+ boolean warnIfLocalFileTooBig(in nsIMsgWindow aWindow,
+ [optional] in long long aSpaceRequested);
+
+ /**
+ * Update properties on a new header from an old header, for cases where
+ * a partial message will be replaced with a full message.
+ *
+ * @param aOldHdr message header used as properties source
+ * @param aNewHdr message header used as properties destination
+ */
+ void updateNewMsgHdr(in nsIMsgDBHdr aOldHdr, in nsIMsgDBHdr aNewHdr);
+};
diff --git a/mailnews/local/public/nsIMsgParseMailMsgState.idl b/mailnews/local/public/nsIMsgParseMailMsgState.idl
new file mode 100644
index 000000000..94f3ac317
--- /dev/null
+++ b/mailnews/local/public/nsIMsgParseMailMsgState.idl
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl" // for nsMsgKey typedef
+
+interface nsIMsgDatabase;
+interface nsIMsgDBHdr;
+
+typedef long nsMailboxParseState;
+
+[scriptable, uuid(0d44646c-0759-43a2-954d-dc2a9a9660ec)]
+interface nsIMsgParseMailMsgState : nsISupports {
+ attribute unsigned long long envelopePos;
+ void SetMailDB(in nsIMsgDatabase aDatabase);
+ /*
+ * Set a backup mail database, whose data will be read during parsing to
+ * attempt to recover message metadata
+ *
+ * @param aDatabase the backup database
+ */
+ void SetBackupMailDB(in nsIMsgDatabase aDatabase);
+ void Clear();
+
+ void ParseAFolderLine(in string line, in unsigned long lineLength);
+ /// db header for message we're currently parsing
+ attribute nsIMsgDBHdr newMsgHdr;
+ void FinishHeader();
+
+ long GetAllHeaders(out string headers);
+ readonly attribute string headers;
+ attribute nsMailboxParseState state;
+ /* these are nsMailboxParseState */
+ const long ParseEnvelopeState=0;
+ const long ParseHeadersState=1;
+ const long ParseBodyState=2;
+
+ /**
+ * Set the key to be used for the new message header.
+ *
+ * @param aNewKey the new db key
+ *
+ */
+ void setNewKey(in nsMsgKey aKey);
+};
+
diff --git a/mailnews/local/public/nsINewsBlogFeedDownloader.idl b/mailnews/local/public/nsINewsBlogFeedDownloader.idl
new file mode 100644
index 000000000..5bad134bd
--- /dev/null
+++ b/mailnews/local/public/nsINewsBlogFeedDownloader.idl
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgFolder;
+interface nsIUrlListener;
+interface nsIMsgWindow;
+
+[scriptable, uuid(86e5bd0e-c324-11e3-923a-00269e4fddc1)]
+interface nsINewsBlogFeedDownloader : nsISupports
+{
+ void downloadFeed(in nsIMsgFolder aFolder,
+ in nsIUrlListener aUrlListener,
+ in bool aIsBiff,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * A convient method to subscribe to feeds without going through the Subscribe
+ * dialog; used by drag and drop and commandline.
+ */
+ void subscribeToFeed(in string aUrl,
+ in nsIMsgFolder aFolder,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Called when the RSS Incoming Server detects a change to an RSS folder name,
+ * such as delete (move to trash), move/copy, or rename. We then need to update
+ * the feeds.rdf subscriptions data source.
+ *
+ * @param nsIMsgFolder aFolder - the folder, new if rename or target of
+ * move/copy folder (new parent)
+ * @param nsIMsgFolder aOrigFolder - original folder
+ * @param string aAction - "move" or "copy" or "rename"
+ */
+ void updateSubscriptionsDS(in nsIMsgFolder aFolder,
+ in nsIMsgFolder aOrigFolder,
+ in string aAction);
+};
+
diff --git a/mailnews/local/public/nsINoIncomingServer.idl b/mailnews/local/public/nsINoIncomingServer.idl
new file mode 100644
index 000000000..717689f58
--- /dev/null
+++ b/mailnews/local/public/nsINoIncomingServer.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(dacd4917-ddbe-47bb-8a49-6a8d91c49a86)]
+interface nsINoIncomingServer : nsISupports {
+ /**
+ * Copy the default messages (e.g. Templates) from
+ * bin/defaults/messenger/<folderNameOnDisk> to <rootFolder>/<folderNameOnDisk>.
+ * This is useful when first creating the standard folders (like Templates).
+ */
+ void copyDefaultMessages(in string folderNameOnDisk);
+};
diff --git a/mailnews/local/public/nsINoneService.idl b/mailnews/local/public/nsINoneService.idl
new file mode 100644
index 000000000..045a018ff
--- /dev/null
+++ b/mailnews/local/public/nsINoneService.idl
@@ -0,0 +1,11 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(14714890-1dd2-11b2-87de-d265839520d6)]
+interface nsINoneService : nsISupports {
+ /* nothing yet, but soon. */
+};
diff --git a/mailnews/local/public/nsIPop3IncomingServer.idl b/mailnews/local/public/nsIPop3IncomingServer.idl
new file mode 100644
index 000000000..3499789a9
--- /dev/null
+++ b/mailnews/local/public/nsIPop3IncomingServer.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPop3Protocol;
+interface nsIMsgFolder;
+interface nsIUrlListener;
+interface nsIMsgWindow;
+
+[scriptable, uuid(8494584a-49b7-49df-9001-80ccdd0b50aa)]
+interface nsIPop3IncomingServer : nsISupports {
+ attribute boolean leaveMessagesOnServer;
+ attribute boolean headersOnly;
+ attribute boolean deleteMailLeftOnServer;
+ attribute boolean dotFix;
+ attribute unsigned long pop3CapabilityFlags;
+ attribute boolean deleteByAgeFromServer;
+ attribute long numDaysToLeaveOnServer;
+ attribute nsIPop3Protocol runningProtocol;
+ // client adds uidls to mark one by one, then calls markMessages
+ void addUidlToMark(in string aUidl, in int32_t newStatus);
+ void markMessages();
+ attribute boolean authenticated;
+ /* account to which this server defers storage, for global inbox */
+ attribute ACString deferredToAccount;
+ // whether get new mail in deferredToAccount gets
+ // new mail with this server.
+ attribute boolean deferGetNewMail;
+ void downloadMailFromServers(
+ [array, size_is(count)]in nsIPop3IncomingServer aServers,
+ in unsigned long count, in nsIMsgWindow aMsgWindow,
+ in nsIMsgFolder aFolder, in nsIUrlListener aListener);
+};
+
+
diff --git a/mailnews/local/public/nsIPop3Protocol.idl b/mailnews/local/public/nsIPop3Protocol.idl
new file mode 100644
index 000000000..9bcdb28d2
--- /dev/null
+++ b/mailnews/local/public/nsIPop3Protocol.idl
@@ -0,0 +1,25 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[ptr] native Pop3UidlEntryArrayRef(nsTArray<Pop3UidlEntry*>);
+
+%{C++
+#include "nsTArray.h"
+struct Pop3UidlEntry;
+%}
+
+[scriptable, uuid(3aff0550-87de-4337-9bc1-c84eb5462afe)]
+interface nsIPop3Protocol : nsISupports {
+ /* aUidl is an array of pointers to Pop3UidlEntry's. That structure is
+ * currently defined in nsPop3Protocol.h, perhaps it should be here
+ * instead...
+ */
+ [noscript] void markMessages(in Pop3UidlEntryArrayRef aUidl);
+ boolean checkMessage(in string aUidl);
+};
+
+
diff --git a/mailnews/local/public/nsIPop3Service.idl b/mailnews/local/public/nsIPop3Service.idl
new file mode 100644
index 000000000..4c3e9a28e
--- /dev/null
+++ b/mailnews/local/public/nsIPop3Service.idl
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIUrlListener.idl"
+#include "nsIPop3IncomingServer.idl"
+#include "nsIMsgFolder.idl"
+
+interface nsIURI;
+interface nsIMsgWindow;
+interface nsIMsgFolder;
+
+[scriptable, uuid(7302fd8e-946f-4ae3-9468-0fb3a7706c51)]
+interface nsIPop3ServiceListener : nsISupports {
+ /**
+ * Notification that a pop3 download has started.
+ *
+ * @param aFolder folder in which the download is started.
+ */
+ void onDownloadStarted(in nsIMsgFolder aFolder);
+
+ /**
+ * Notification about download progress.
+ *
+ * @param aFolder folder in which the download is happening.
+ * @param aNumDownloaded number of the messages that have been downloaded.
+ * @param aTotalToDownload total number of messages to download.
+ */
+ void onDownloadProgress(in nsIMsgFolder aFolder,
+ in unsigned long aNumDownloaded,
+ in unsigned long aTotalToDownload);
+
+ /**
+ * Notification that a download has completed.
+ *
+ * @param aFolder folder to which the download has completed.
+ * @param aNumberOfMessages number of the messages that were downloaded.
+ */
+ void onDownloadCompleted(in nsIMsgFolder aFolder,
+ in unsigned long aNumberOfMessages);
+};
+
+/*
+ * The Pop3 Service is an interface designed to make building and running
+ * pop3 urls easier.
+ */
+[scriptable, uuid(96d3cc14-a842-4cdf-98f8-a4cc695f8b3b)]
+interface nsIPop3Service : nsISupports {
+ /*
+ * All of these functions build pop3 urls and run them. If you want
+ * a handle on the running task, pass in a valid nsIURI ptr. You can later
+ * interrupt this action by asking the netlib service manager to interrupt
+ * the url you are given back. Remember to release aURL when you are
+ * done with it. Pass nullptr in for aURL if you
+ * don't care about the returned URL.
+ */
+
+ /*
+ * right now getting new mail doesn't require any user specific data.
+ * We use the default current identity for this information. I suspect that
+ * we'll eventually pass in an identity to this call so you can get
+ * mail on different pop3 accounts....
+ */
+
+ nsIURI GetNewMail(in nsIMsgWindow aMsgWindow, in nsIUrlListener aUrlListener,
+ in nsIMsgFolder aInbox, in nsIPop3IncomingServer popServer);
+
+ nsIURI CheckForNewMail(in nsIMsgWindow aMsgWindow, in nsIUrlListener aUrlListener,
+ in nsIMsgFolder inbox, in nsIPop3IncomingServer popServer);
+
+ /**
+ * Verify that we can logon
+ *
+ * @param aServer - pop3 server we're logging on to.
+ * @param aUrlListener - gets called back with success or failure.
+ * @param aMsgWindow - nsIMsgWindow to use for notification callbacks.
+ * @return - the url that we run.
+ *
+ */
+ nsIURI verifyLogon(in nsIMsgIncomingServer aServer,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Add a listener for pop3 events like message download. This is
+ * used by the activity manager.
+ *
+ * @param aListener listener that gets notified of pop3 events.
+ */
+ void addListener(in nsIPop3ServiceListener aListener);
+
+ /**
+ * Remove a listener for pop3 events like message download.
+ *
+ * @param aListener listener to remove.
+ */
+ void removeListener(in nsIPop3ServiceListener aListener);
+
+ /**
+ * Send the notification that a pop3 download has started.
+ * This is called from the nsIPop3Sink code.
+ *
+ * @param aFolder folder in which the download is started.
+ */
+ void notifyDownloadStarted(in nsIMsgFolder aFolder);
+
+ /**
+ * Send notification about download progress.
+ *
+ * @param aFolder folder in which the download is happening.
+ * @param aNumDownloaded number of the messages that have been downloaded.
+ * @param aTotalToDownload total number of messages to download.
+ */
+ void notifyDownloadProgress(in nsIMsgFolder aFolder,
+ in unsigned long aNumDownloaded,
+ in unsigned long aTotalToDownload);
+ /**
+ * Send the notification that a download has completed.
+ * This is called from the nsIPop3Sink code.
+ *
+ * @param aFolder folder to which the download has completed.
+ * @param aNumberOfMessages number of the messages that were downloaded.
+ */
+ void notifyDownloadCompleted(in nsIMsgFolder aFolder,
+ in unsigned long aNumberOfMessages);
+};
diff --git a/mailnews/local/public/nsIPop3Sink.idl b/mailnews/local/public/nsIPop3Sink.idl
new file mode 100644
index 000000000..00fc4ccba
--- /dev/null
+++ b/mailnews/local/public/nsIPop3Sink.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIPop3IncomingServer.idl"
+#include "nsIMsgFolder.idl"
+
+interface nsIURI;
+
+[scriptable, uuid(ceabfc6b-f139-4c25-890f-efb7c3069d40)]
+interface nsIPop3Sink : nsISupports {
+
+ attribute boolean userAuthenticated;
+ attribute ACString mailAccountURL;
+ attribute boolean buildMessageUri;
+ attribute string messageUri;
+ attribute string baseMessageUri;
+
+ /// message uri for header-only message version
+ attribute ACString origMessageUri;
+
+ boolean BeginMailDelivery(in boolean uidlDownload, in nsIMsgWindow msgWindow);
+ void endMailDelivery(in nsIPop3Protocol protocol);
+ void AbortMailDelivery(in nsIPop3Protocol protocol);
+
+ /* returns a closure ? */
+ [noscript] voidPtr IncorporateBegin(in string uidlString, in nsIURI aURL,
+ in unsigned long flags);
+
+ [noscript] void IncorporateWrite(in string block,
+ in long length);
+
+ [noscript] void IncorporateComplete(in nsIMsgWindow aMsgWindow, in int32_t aSize);
+ [noscript] void IncorporateAbort(in boolean uidlDownload);
+
+ void BiffGetNewMail();
+
+ /**
+ * Tell the pop3sink how many messages we're going to download.
+ *
+ * @param aNumMessages how many messages we're going to download.
+ */
+ void setMsgsToDownload(in unsigned long aNumMessages);
+
+ void SetBiffStateAndUpdateFE(in unsigned long biffState, in long numNewMessages, in boolean notify);
+
+ [noscript] void SetSenderAuthedFlag(in voidPtr closure, in boolean authed);
+
+ attribute nsIPop3IncomingServer popServer;
+ attribute nsIMsgFolder folder;
+};
diff --git a/mailnews/local/public/nsIPop3URL.idl b/mailnews/local/public/nsIPop3URL.idl
new file mode 100644
index 000000000..c992fef2d
--- /dev/null
+++ b/mailnews/local/public/nsIPop3URL.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIPop3Sink.idl"
+
+[scriptable, uuid(5fb87ae7-a3a0-440a-8b49-6bca42fb7ff2)]
+interface nsIPop3URL : nsISupports {
+ attribute nsIPop3Sink pop3Sink;
+ attribute string messageUri;
+
+ /// Constant for the default POP3 port number
+ const int32_t DEFAULT_POP3_PORT = 110;
+
+ /// Constant for the default POP3 over ssl port number
+ const int32_t DEFAULT_POP3S_PORT = 995;
+};
diff --git a/mailnews/local/public/nsIRssIncomingServer.idl b/mailnews/local/public/nsIRssIncomingServer.idl
new file mode 100644
index 000000000..c20c3d97f
--- /dev/null
+++ b/mailnews/local/public/nsIRssIncomingServer.idl
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(6d744e7f-2218-45c6-8734-998a56cb3c6d)]
+interface nsIRssIncomingServer : nsISupports {
+ // Path to the subscriptions data source for this RSS server
+ readonly attribute nsIFile subscriptionsDataSourcePath;
+
+ // Path to the feed items data source for this RSS server
+ readonly attribute nsIFile feedItemsDataSourcePath;
+};
diff --git a/mailnews/local/public/nsIRssService.idl b/mailnews/local/public/nsIRssService.idl
new file mode 100644
index 000000000..19a1d1f34
--- /dev/null
+++ b/mailnews/local/public/nsIRssService.idl
@@ -0,0 +1,10 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(4b31bdd9-6f4c-46d4-a766-a94f11b599bc)]
+interface nsIRssService : nsISupports
+{
+};
diff --git a/mailnews/local/public/nsMsgLocalCID.h b/mailnews/local/public/nsMsgLocalCID.h
new file mode 100644
index 000000000..260ba985e
--- /dev/null
+++ b/mailnews/local/public/nsMsgLocalCID.h
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgLocalCID_h__
+#define nsMsgLocalCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+#include "nsMsgBaseCID.h"
+
+#define NS_POP3INCOMINGSERVER_TYPE "pop3"
+
+//
+// nsLocalMailFolderResourceCID
+//
+#define NS_LOCALMAILFOLDERRESOURCE_CONTRACTID \
+ NS_RDF_RESOURCE_FACTORY_CONTRACTID_PREFIX "mailbox"
+#define NS_LOCALMAILFOLDERRESOURCE_CID \
+{ /* e490d22c-cd67-11d2-8cca-0060b0fc14a3 */ \
+ 0xe490d22c, \
+ 0xcd67, \
+ 0x11d2, \
+ {0x8c, 0xca, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \
+}
+
+//
+// nsPop3IncomingServer
+//
+#define NS_POP3INCOMINGSERVER_CONTRACTID \
+ NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX NS_POP3INCOMINGSERVER_TYPE
+
+#define NS_POP3INCOMINGSERVER_CID \
+{ /* D2876E51-E62C-11d2-B7FC-00805F05FFA5 */ \
+ 0xd2876e51, 0xe62c, 0x11d2, \
+ {0xb7, 0xfc, 0x0, 0x80, 0x5f, 0x5, 0xff, 0xa5 }}
+
+#ifdef HAVE_MOVEMAIL
+//
+// nsMovemailIncomingServer
+//
+#define NS_MOVEMAILINCOMINGSERVER_CONTRACTID \
+ NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX "movemail"
+
+#define NS_MOVEMAILINCOMINGSERVER_CID \
+{ /* efbb77e4-1dd2-11b2-bbcf-961563396fec */ \
+ 0xefbb77e4, 0x1dd2, 0x11b2, \
+ {0xbb, 0xcf, 0x96, 0x15, 0x63, 0x39, 0x6f, 0xec }}
+
+#endif /* HAVE_MOVEMAIL */
+
+//
+// nsNoIncomingServer
+//
+#define NS_NOINCOMINGSERVER_CONTRACTID \
+ NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX "none"
+
+#define NS_NOINCOMINGSERVER_CID \
+{ /* {ca5ffe7e-5f47-11d3-9a51-004005263078} */ \
+ 0xca5ffe7e, 0x5f47, 0x11d3, \
+ {0x9a, 0x51, 0x00, 0x40, 0x05, 0x26, 0x30, 0x78}}
+
+
+//
+// nsMsgMailboxService
+#define NS_MAILBOXSERVICE_CONTRACTID1 \
+ "@mozilla.org/messenger/mailboxservice;1"
+
+#define NS_MAILBOXSERVICE_CONTRACTID2 \
+ "@mozilla.org/messenger/messageservice;1?type=mailbox"
+
+#define NS_MAILBOXSERVICE_CONTRACTID3 \
+ "@mozilla.org/messenger/messageservice;1?type=mailbox-message"
+
+#define NS_MAILBOXSERVICE_CONTRACTID4 \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "mailbox"
+
+#define NS_MAILBOXSERVICE_CID \
+{ /* EEF82462-CB69-11d2-8065-006008128C4E */ \
+ 0xeef82462, 0xcb69, 0x11d2, \
+ {0x80, 0x65, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e}}
+
+
+//
+// nsMailboxUrl
+//
+#define NS_MAILBOXURL_CONTRACTID \
+ "@mozilla.org/messenger/mailboxurl;1"
+
+/* 46EFCB10-CB6D-11d2-8065-006008128C4E */
+#define NS_MAILBOXURL_CID \
+{ 0x46efcb10, 0xcb6d, 0x11d2, \
+ { 0x80, 0x65, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e } }
+
+
+//
+// nsPop3Url
+//
+#define NS_POP3URL_CONTRACTID \
+ "@mozilla.org/messenger/popurl;1"
+
+/* EA1B0A11-E6F4-11d2-8070-006008128C4E */
+#define NS_POP3URL_CID \
+{ 0xea1b0a11, 0xe6f4, 0x11d2, \
+ { 0x80, 0x70, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e } }
+
+
+//
+// nsPop3Service
+//
+
+#define NS_POP3SERVICE_CONTRACTID1 \
+ "@mozilla.org/messenger/popservice;1"
+
+#define NS_POP3SERVICE_CONTRACTID2 \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "pop"
+
+// Mailnews has used "pop" as the protocol scheme for pop3 in some places,
+// but "pop3" in others. Necko code needs to be able to locate protocolInfo
+// based on pop3 to get proxy information.
+//
+// TODO: fix the mailnews code to use a consistent POP3 protocol scheme.
+
+#define NS_POP3SERVICE_CONTRACTID3 \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "pop3"
+
+#define NS_POP3PROTOCOLINFO_CONTRACTID \
+ NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX NS_POP3INCOMINGSERVER_TYPE
+
+#define NS_POP3SERVICE_CID \
+{ /* 3BB459E3-D746-11d2-806A-006008128C4E */ \
+ 0x3bb459e3, 0xd746, 0x11d2, \
+ { 0x80, 0x6a, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e }}
+
+//
+// nsNoneService
+//
+
+#define NS_NONESERVICE_CONTRACTID \
+ "@mozilla.org/messenger/noneservice;1"
+
+#define NS_NONEPROTOCOLINFO_CONTRACTID \
+ NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX "none"
+
+#define NS_NONESERVICE_CID \
+{ /* 75b63b46-1dd2-11b2-9873-bb375e1550fa */ \
+ 0x75b63b46, 0x1dd2, 0x11b2, \
+ { 0x98, 0x73, 0xbb, 0x37, 0x5e, 0x15, 0x50, 0xfa }}
+
+#ifdef HAVE_MOVEMAIL
+//
+// nsMovemailService
+//
+
+#define NS_MOVEMAILSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/movemailservice;1"
+
+#define NS_MOVEMAILPROTOCOLINFO_CONTRACTID \
+ NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX "movemail"
+
+#define NS_MOVEMAILSERVICE_CID \
+{ /* 0e4db62e-1dd2-11b2-a5e4-f128fe4f1b69 */ \
+ 0x0e4db62e, 0x1dd2, 0x11b2, \
+ { 0xa5, 0xe4, 0xf1, 0x28, 0xfe, 0x4f, 0x1b, 0x69 }}
+#endif /* HAVE_MOVEMAIL */
+
+//
+// nsParseMailMsgState
+//
+#define NS_PARSEMAILMSGSTATE_CONTRACTID \
+ "@mozilla.org/messenger/messagestateparser;1"
+
+#define NS_PARSEMAILMSGSTATE_CID \
+{ /* 2B79AC51-1459-11d3-8097-006008128C4E */ \
+ 0x2b79ac51, 0x1459, 0x11d3, \
+ {0x80, 0x97, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e} }
+
+//
+// nsMsgMailboxParser
+//
+
+#define NS_MAILBOXPARSER_CONTRACTID \
+ "@mozilla.org/messenger/mailboxparser;1"
+
+/* 46EFCB10-CB6D-11d2-8065-006008128C4E */
+#define NS_MAILBOXPARSER_CID \
+{ 0x8597ab60, 0xd4e2, 0x11d2, \
+ { 0x80, 0x69, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e } }
+
+#define NS_RSSSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/rssservice;1"
+
+#define NS_RSSPROTOCOLINFO_CONTRACTID \
+ NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX "rss"
+
+#define NS_RSSSERVICE_CID \
+{ /* 44aef4ce-475b-42e3-bc42-7730d5ce7365 */ \
+ 0x44aef4ce, 0x475b, 0x42e3, \
+ { 0xbc, 0x42, 0x77, 0x30, 0xd5, 0xce, 0x73, 0x65 }}
+
+#define NS_RSSINCOMINGSERVER_CONTRACTID \
+ NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX "rss"
+
+#define NS_RSSINCOMINGSERVER_CID \
+{ /* 3a874285-5520-41a0-bcda-a3dee3dbf4f3 */ \
+ 0x3a874285, 0x5520, 0x41a0, \
+ {0xbc, 0xda, 0xa3, 0xde, 0xe3, 0xdb, 0xf4, 0xf3 }}
+
+#define NS_BRKMBOXSTORE_CID \
+{ /* 36358199-a0e4-4b68-929f-77c01de34c67 */ \
+ 0x36358199, 0xa0e4, 0x4b68, \
+ {0x92, 0x9f, 0x77, 0xc0, 0x1d, 0xe3, 0x4c, 0x67}}
+
+#define NS_BRKMBOXSTORE_CONTRACTID \
+ "@mozilla.org/msgstore/berkeleystore;1"
+
+#define NS_MAILDIRSTORE_CID \
+{ /* 1F993EDA-7DD9-11DF-819A-6257DFD72085 */ \
+ 0x1F993EDA, 0x7DD9, 0x11DF, \
+ { 0x81, 0x9A, 0x62, 0x57, 0xDF, 0xD7, 0x20, 0x85 }}
+
+#define NS_MAILDIRSTORE_CONTRACTID \
+ "@mozilla.org/msgstore/maildirstore;1"
+
+#endif // nsMsgLocalCID_h__
diff --git a/mailnews/local/src/moz.build b/mailnews/local/src/moz.build
new file mode 100644
index 000000000..5f999ce46
--- /dev/null
+++ b/mailnews/local/src/moz.build
@@ -0,0 +1,36 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsLocalMailFolder.cpp',
+ 'nsLocalUndoTxn.cpp',
+ 'nsLocalUtils.cpp',
+ 'nsMailboxProtocol.cpp',
+ 'nsMailboxServer.cpp',
+ 'nsMailboxService.cpp',
+ 'nsMailboxUrl.cpp',
+ 'nsMsgBrkMBoxStore.cpp',
+ 'nsMsgLocalStoreUtils.cpp',
+ 'nsMsgMaildirStore.cpp',
+ 'nsNoIncomingServer.cpp',
+ 'nsNoneService.cpp',
+ 'nsParseMailbox.cpp',
+ 'nsPop3IncomingServer.cpp',
+ 'nsPop3Protocol.cpp',
+ 'nsPop3Service.cpp',
+ 'nsPop3Sink.cpp',
+ 'nsPop3URL.cpp',
+ 'nsRssIncomingServer.cpp',
+ 'nsRssService.cpp',
+]
+
+if CONFIG['MOZ_MOVEMAIL']:
+ SOURCES += [
+ 'nsMovemailIncomingServer.cpp',
+ 'nsMovemailService.cpp',
+ ]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/local/src/nsLocalMailFolder.cpp b/mailnews/local/src/nsLocalMailFolder.cpp
new file mode 100644
index 000000000..78991ebfd
--- /dev/null
+++ b/mailnews/local/src/nsLocalMailFolder.cpp
@@ -0,0 +1,3815 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "prlog.h"
+
+#include "msgCore.h" // precompiled header...
+#include "nsArrayEnumerator.h"
+#include "nsLocalMailFolder.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsIArray.h"
+#include "nsIServiceManager.h"
+#include "nsIMailboxService.h"
+#include "nsParseMailbox.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgWindow.h"
+#include "nsCOMPtr.h"
+#include "nsIRDFService.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgUtils.h"
+#include "nsLocalUtils.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsILocalMailIncomingServer.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgLocalCID.h"
+#include "nsStringGlue.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsUnicharUtils.h"
+#include "nsMsgUtils.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsIMsgCopyService.h"
+#include "nsMsgTxn.h"
+#include "nsIMessenger.h"
+#include "nsMsgBaseCID.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIDocShell.h"
+#include "nsIPrompt.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPop3URL.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsNetCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsISpamSettings.h"
+#include "nsINoIncomingServer.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsMailHeaders.h"
+#include "nsCOMArray.h"
+#include "nsILineInputStream.h"
+#include "nsIFileStreams.h"
+#include "nsAutoPtr.h"
+#include "nsIRssIncomingServer.h"
+#include "nsNetUtil.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsReadLine.h"
+#include "nsArrayUtils.h"
+#include "nsIMsgTraitService.h"
+#include "nsIStringEnumerator.h"
+#include "mozilla/Services.h"
+
+//////////////////////////////////////////////////////////////////////////////
+// nsLocal
+/////////////////////////////////////////////////////////////////////////////
+
+nsLocalMailCopyState::nsLocalMailCopyState() :
+ m_flags(0),
+ m_lastProgressTime(PR_IntervalToMilliseconds(PR_IntervalNow())),
+ m_curDstKey(nsMsgKey_None),
+ m_curCopyIndex(0),
+ m_totalMsgCount(0),
+ m_dataBufferSize(0),
+ m_leftOver(0),
+ m_isMove(false),
+ m_dummyEnvelopeNeeded(false),
+ m_fromLineSeen(false),
+ m_writeFailed(false),
+ m_notifyFolderLoaded(false)
+{
+}
+
+nsLocalMailCopyState::~nsLocalMailCopyState()
+{
+ PR_Free(m_dataBuffer);
+ if (m_fileStream)
+ m_fileStream->Close();
+ if (m_messageService)
+ {
+ nsCOMPtr <nsIMsgFolder> srcFolder = do_QueryInterface(m_srcSupport);
+ if (srcFolder && m_message)
+ {
+ nsCString uri;
+ srcFolder->GetUriForMsg(m_message, uri);
+ }
+ }
+}
+
+nsLocalFolderScanState::nsLocalFolderScanState() : m_uidl(nullptr)
+{
+}
+
+nsLocalFolderScanState::~nsLocalFolderScanState()
+{
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsMsgLocalMailFolder interface
+///////////////////////////////////////////////////////////////////////////////
+
+nsMsgLocalMailFolder::nsMsgLocalMailFolder(void)
+ : mCopyState(nullptr), mHaveReadNameFromDB(false),
+ mInitialized(false),
+ mCheckForNewMessagesAfterParsing(false), m_parsingFolder(false),
+ mDownloadState(DOWNLOAD_STATE_NONE)
+{
+}
+
+nsMsgLocalMailFolder::~nsMsgLocalMailFolder(void)
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgLocalMailFolder,
+ nsMsgDBFolder,
+ nsICopyMessageListener,
+ nsIMsgLocalMailFolder)
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::Init(const char* aURI)
+{
+ return nsMsgDBFolder::Init(aURI);
+}
+
+nsresult nsMsgLocalMailFolder::CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder)
+{
+ nsMsgLocalMailFolder *newFolder = new nsMsgLocalMailFolder;
+ if (!newFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*folder = newFolder);
+ newFolder->Init(uri.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::CreateLocalSubfolder(const nsAString &aFolderName,
+ nsIMsgFolder **aChild)
+{
+ NS_ENSURE_ARG_POINTER(aChild);
+ nsresult rv = CreateSubfolderInternal(aFolderName, nullptr, aChild);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderAdded(*aChild);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetManyHeadersToDownload(bool *retval)
+{
+ bool isLocked;
+ // if the folder is locked, we're probably reparsing - let's build the
+ // view when we've finished reparsing.
+ GetLocked(&isLocked);
+ if (isLocked)
+ {
+ *retval = true;
+ return NS_OK;
+ }
+
+ return nsMsgDBFolder::GetManyHeadersToDownload(retval);
+}
+
+//run the url to parse the mailbox
+NS_IMETHODIMP nsMsgLocalMailFolder::ParseFolder(nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aListener)
+{
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aListener != this)
+ mReparseListener = aListener;
+ // if parsing is synchronous, we need to set m_parsingFolder to
+ // true before starting. And we need to open the db before
+ // setting m_parsingFolder to true.
+// OpenDatabase();
+ rv = msgStore->RebuildIndex(this, mDatabase, aMsgWindow, this);
+ if (NS_SUCCEEDED(rv))
+ m_parsingFolder = true;
+
+ return rv;
+}
+
+// this won't force a reparse of the folder if the db is invalid.
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase)
+{
+ return GetDatabaseWOReparse(aMsgDatabase);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetSubFolders(nsISimpleEnumerator **aResult)
+{
+ if (!mInitialized)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ // need to set this flag here to avoid infinite recursion
+ mInitialized = true;
+ rv = server->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // This should add all existing folders as sub-folders of this folder.
+ rv = msgStore->DiscoverSubFolders(this, true);
+
+ nsCOMPtr<nsIFile> path;
+ rv = GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool directory;
+ path->IsDirectory(&directory);
+ if (directory)
+ {
+ SetFlag(nsMsgFolderFlags::Mail | nsMsgFolderFlags::Elided |
+ nsMsgFolderFlags::Directory);
+
+ bool isServer;
+ GetIsServer(&isServer);
+ if (isServer)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ nsCOMPtr<nsILocalMailIncomingServer> localMailServer;
+ localMailServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ // first create the folders on disk (as empty files)
+ rv = localMailServer->CreateDefaultMailboxes();
+ if (NS_FAILED(rv) && rv != NS_MSG_FOLDER_EXISTS)
+ return rv;
+
+ // must happen after CreateSubFolders, or the folders won't exist.
+ rv = localMailServer->SetFlagsOnDefaultMailboxes();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+ UpdateSummaryTotals(false);
+ }
+
+ return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER;
+}
+
+nsresult nsMsgLocalMailFolder::GetDatabase()
+{
+ nsCOMPtr <nsIMsgDatabase> msgDB;
+ return GetDatabaseWOReparse(getter_AddRefs(msgDB));
+}
+
+//we treat failure as null db returned
+NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWOReparse(nsIMsgDatabase **aDatabase)
+{
+ NS_ENSURE_ARG(aDatabase);
+ if (m_parsingFolder)
+ return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+
+ nsresult rv = NS_OK;
+ if (!mDatabase)
+ {
+ rv = OpenDatabase();
+ if (mDatabase)
+ {
+ mDatabase->AddListener(this);
+ UpdateNewMessages();
+ }
+ }
+ NS_IF_ADDREF(*aDatabase = mDatabase);
+ if (mDatabase)
+ mDatabase->SetLastUseTime(PR_Now());
+ return rv;
+}
+
+
+// Makes sure the database is open and exists. If the database is out of date,
+// then this call will run an async url to reparse the folder. The passed in
+// url listener will get called when the url is done.
+NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWithReparse(nsIUrlListener *aReparseUrlListener, nsIMsgWindow *aMsgWindow,
+ nsIMsgDatabase **aMsgDatabase)
+{
+ nsresult rv = NS_OK;
+ // if we're already reparsing, just remember the listener so we can notify it
+ // when we've finished.
+ if (m_parsingFolder)
+ {
+ NS_ASSERTION(!mReparseListener, "can't have an existing listener");
+ mReparseListener = aReparseUrlListener;
+ return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+
+ if (!mDatabase)
+ {
+ nsCOMPtr <nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool exists;
+ rv = pathFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!exists)
+ return NS_ERROR_NULL_POINTER; //mDatabase will be null at this point.
+
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult folderOpen = msgDBService->OpenFolderDB(this, true,
+ getter_AddRefs(mDatabase));
+ if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ {
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr <nsIDBFolderInfo> transferInfo;
+ if (mDatabase)
+ {
+ mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo)
+ {
+ dbFolderInfo->SetNumMessages(0);
+ dbFolderInfo->SetNumUnreadMessages(0);
+ dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo));
+ }
+ dbFolderInfo = nullptr;
+
+ // A backup message database might have been created earlier, for example
+ // if the user requested a reindex. We'll use the earlier one if we can,
+ // otherwise we'll try to backup at this point.
+ if (NS_FAILED(OpenBackupMsgDatabase()))
+ {
+ CloseAndBackupFolderDB(EmptyCString());
+ if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase)
+ {
+ mBackupDatabase->RemoveListener(this);
+ mBackupDatabase = nullptr;
+ }
+ }
+ else
+ mDatabase->ForceClosed();
+
+ mDatabase = nullptr;
+ }
+ nsCOMPtr <nsIFile> summaryFile;
+ rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Remove summary file.
+ summaryFile->Remove(false);
+
+ // if it's out of date then reopen with upgrade.
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (transferInfo && mDatabase)
+ {
+ SetDBTransferInfo(transferInfo);
+ mDatabase->SetSummaryValid(false);
+ }
+ }
+ else if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ {
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+ }
+
+ if (mDatabase)
+ {
+ if (mAddListener)
+ mDatabase->AddListener(this);
+
+ // if we have to regenerate the folder, run the parser url.
+ if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
+ folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ {
+ if (NS_FAILED(rv = ParseFolder(aMsgWindow, aReparseUrlListener)))
+ {
+ if (rv == NS_MSG_FOLDER_BUSY)
+ {
+ mDatabase->RemoveListener(this); //we need to null out the db so that parsing gets kicked off again.
+ mDatabase = nullptr;
+ ThrowAlertMsg("parsingFolderFailed", aMsgWindow);
+ }
+ return rv;
+ }
+
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // We have a valid database so lets extract necessary info.
+ UpdateSummaryTotals(true);
+ }
+ }
+ NS_IF_ADDREF(*aMsgDatabase = mDatabase);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::UpdateFolder(nsIMsgWindow *aWindow)
+{
+ (void) RefreshSizeOnDisk();
+ nsresult rv;
+
+ if (!PromptForMasterPasswordIfNecessary())
+ return NS_ERROR_FAILURE;
+
+ //If we don't currently have a database, get it. Otherwise, the folder has been updated (presumably this
+ //changes when we download headers when opening inbox). If it's updated, send NotifyFolderLoaded.
+ if (!mDatabase)
+ {
+ // return of NS_ERROR_NOT_INITIALIZED means running parsing URL
+ // We don't need the return value, and assigning it to mDatabase which
+ // is already set internally leaks.
+ nsCOMPtr<nsIMsgDatabase> returnedDb;
+ rv = GetDatabaseWithReparse(this, aWindow, getter_AddRefs(returnedDb));
+ if (NS_SUCCEEDED(rv))
+ NotifyFolderEvent(mFolderLoadedAtom);
+ }
+ else
+ {
+ bool valid;
+ rv = mDatabase->GetSummaryValid(&valid);
+ // don't notify folder loaded or try compaction if db isn't valid
+ // (we're probably reparsing or copying msgs to it)
+ if (NS_SUCCEEDED(rv) && valid)
+ NotifyFolderEvent(mFolderLoadedAtom);
+ else if (mCopyState)
+ mCopyState->m_notifyFolderLoaded = true; //defer folder loaded notification
+ else if (!m_parsingFolder)// if the db was already open, it's probably OK to load it if not parsing
+ NotifyFolderEvent(mFolderLoadedAtom);
+ }
+ bool filtersRun;
+ bool hasNewMessages;
+ GetHasNewMessages(&hasNewMessages);
+ if (mDatabase)
+ ApplyRetentionSettings();
+ // if we have new messages, try the filter plugins.
+ if (NS_SUCCEEDED(rv) && hasNewMessages)
+ (void) CallFilterPlugins(aWindow, &filtersRun);
+ // Callers should rely on folder loaded event to ensure completion of loading. So we'll
+ // return NS_OK even if parsing is still in progress
+ if (rv == NS_ERROR_NOT_INITIALIZED)
+ rv = NS_OK;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetMessages(nsISimpleEnumerator **result)
+{
+ nsCOMPtr <nsIMsgDatabase> msgDB;
+ nsresult rv = GetDatabaseWOReparse(getter_AddRefs(msgDB));
+ return NS_SUCCEEDED(rv) ? msgDB->EnumerateMessages(result) : rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetFolderURL(nsACString& aUrl)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> path;
+ rv = GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = NS_GetURLSpecFromFile(path, aUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aUrl.Replace(0, strlen("file:"), "mailbox:");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::CreateStorageIfMissing(nsIUrlListener* aUrlListener)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ GetParent(getter_AddRefs(msgParent));
+
+ // parent is probably not set because *this* was probably created by rdf
+ // and not by folder discovery. So, we have to compute the parent.
+ if (!msgParent)
+ {
+ nsAutoCString folderName(mURI);
+ nsAutoCString uri;
+ int32_t leafPos = folderName.RFindChar('/');
+ nsAutoCString parentName(folderName);
+ if (leafPos > 0)
+ {
+ // If there is a hierarchy, there is a parent.
+ // Don't strip off slash if it's the first character
+ parentName.SetLength(leafPos);
+ // get the corresponding RDF resource
+ // RDF will create the folder resource if it doesn't already exist
+ nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIRDFResource> resource;
+ rv = rdf->GetResource(parentName, getter_AddRefs(resource));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ msgParent = do_QueryInterface(resource, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+
+ if (msgParent)
+ {
+ nsString folderName;
+ GetName(folderName);
+ rv = msgParent->CreateSubfolder(folderName, nullptr);
+ // by definition, this is OK.
+ if (rv == NS_MSG_FOLDER_EXISTS)
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::CreateSubfolder(const nsAString& folderName, nsIMsgWindow *msgWindow)
+{
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ nsresult rv = CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderAdded(newFolder);
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgLocalMailFolder::CreateSubfolderInternal(const nsAString& folderName,
+ nsIMsgWindow *msgWindow,
+ nsIMsgFolder **aNewFolder)
+{
+ nsresult rv = CheckIfFolderExists(folderName, this, msgWindow);
+ // No need for an assertion: we already throw an alert.
+ if (NS_FAILED(rv))
+ return rv;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgStore->CreateFolder(this, folderName, aNewFolder);
+ if (rv == NS_MSG_ERROR_INVALID_FOLDER_NAME)
+ {
+ ThrowAlertMsg("folderCreationFailed", msgWindow);
+ }
+ else if (rv == NS_MSG_FOLDER_EXISTS)
+ {
+ ThrowAlertMsg("folderExists", msgWindow);
+ }
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> child = *aNewFolder;
+ //we need to notify explicitly the flag change because it failed when we did AddSubfolder
+ child->OnFlagChange(mFlags);
+ child->SetPrettyName(folderName); //because empty trash will create a new trash folder
+ NotifyItemAdded(child);
+ if (aNewFolder)
+ child.swap(*aNewFolder);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::CompactAll(nsIUrlListener *aListener,
+ nsIMsgWindow *aMsgWindow,
+ bool aCompactOfflineAlso)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> folderArray;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIArray> allDescendents;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ GetMsgStore(getter_AddRefs(msgStore));
+ bool storeSupportsCompaction;
+ msgStore->GetSupportsCompaction(&storeSupportsCompaction);
+ if (!storeSupportsCompaction)
+ return NotifyCompactCompleted();
+
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ {
+ rv = rootFolder->GetDescendants(getter_AddRefs(allDescendents));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t cnt = 0;
+ rv = allDescendents->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ folderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ int64_t expungedBytes = 0;
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ expungedBytes = 0;
+ if (folder)
+ rv = folder->GetExpungedBytes(&expungedBytes);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (expungedBytes > 0)
+ rv = folderArray->AppendElement(folder, false);
+ }
+ rv = folderArray->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (cnt == 0)
+ return NotifyCompactCompleted();
+ }
+ nsCOMPtr <nsIMsgFolderCompactor> folderCompactor = do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderCompactor->CompactFolders(folderArray, nullptr,
+ aListener, aMsgWindow);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
+{
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool supportsCompaction;
+ msgStore->GetSupportsCompaction(&supportsCompaction);
+ if (supportsCompaction)
+ return msgStore->CompactFolder(this, aListener, aMsgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::EmptyTrash(nsIMsgWindow *msgWindow,
+ nsIUrlListener *aListener)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t flags;
+ trashFolder->GetFlags(&flags);
+ int32_t totalMessages = 0;
+ rv = trashFolder->GetTotalMessages(true, &totalMessages);
+ if (totalMessages <= 0)
+ {
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = trashFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv,rv);
+ // Any folders to deal with?
+ bool hasMore;
+ rv = enumerator->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv) || !hasMore)
+ return NS_OK;
+ }
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ rv = trashFolder->GetParent(getter_AddRefs(parentFolder));
+ if (NS_SUCCEEDED(rv) && parentFolder)
+ {
+ nsCOMPtr <nsIDBFolderInfo> transferInfo;
+ trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo));
+ trashFolder->SetParent(nullptr);
+ parentFolder->PropagateDelete(trashFolder, true, msgWindow);
+ parentFolder->CreateSubfolder(NS_LITERAL_STRING("Trash"), nullptr);
+ nsCOMPtr<nsIMsgFolder> newTrashFolder;
+ rv = GetTrashFolder(getter_AddRefs(newTrashFolder));
+ if (NS_SUCCEEDED(rv) && newTrashFolder)
+ {
+ nsCOMPtr <nsIMsgLocalMailFolder> localTrash = do_QueryInterface(newTrashFolder);
+ newTrashFolder->SetDBTransferInfo(transferInfo);
+ if (localTrash)
+ localTrash->RefreshSizeOnDisk();
+ // update the summary totals so the front end will
+ // show the right thing for the new trash folder
+ // see bug #161999
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ newTrashFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db));
+ if (dbFolderInfo)
+ {
+ dbFolderInfo->SetNumUnreadMessages(0);
+ dbFolderInfo->SetNumMessages(0);
+ }
+ newTrashFolder->UpdateSummaryTotals(true);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::IsChildOfTrash(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ uint32_t parentFlags = 0;
+ *result = false;
+ bool isServer;
+ nsresult rv = GetIsServer(&isServer);
+ if (NS_FAILED(rv) || isServer)
+ return NS_OK;
+
+ rv= GetFlags(&parentFlags); //this is the parent folder
+ if (parentFlags & nsMsgFolderFlags::Trash)
+ {
+ *result = true;
+ return rv;
+ }
+
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ nsCOMPtr<nsIMsgFolder> thisFolder;
+ rv = QueryInterface(NS_GET_IID(nsIMsgFolder), (void **) getter_AddRefs(thisFolder));
+
+ while (!isServer)
+ {
+ thisFolder->GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder)
+ return NS_OK;
+
+ rv = parentFolder->GetIsServer(&isServer);
+ if (NS_FAILED(rv) || isServer)
+ return NS_OK;
+
+ rv = parentFolder->GetFlags(&parentFlags);
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ if (parentFlags & nsMsgFolderFlags::Trash)
+ {
+ *result = true;
+ return rv;
+ }
+
+ thisFolder = parentFolder;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::DeleteSubFolders(nsIArray *folders, nsIMsgWindow *msgWindow)
+{
+ nsresult rv;
+ bool isChildOfTrash;
+ IsChildOfTrash(&isChildOfTrash);
+
+ // we don't allow multiple folder selection so this is ok.
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(folders, 0);
+ uint32_t folderFlags = 0;
+ if (folder)
+ folder->GetFlags(&folderFlags);
+ // when deleting from trash, or virtual folder, just delete it.
+ if (isChildOfTrash || folderFlags & nsMsgFolderFlags::Virtual)
+ return nsMsgDBFolder::DeleteSubFolders(folders, msgWindow);
+
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv))
+ {
+ if (folder)
+ {
+ nsCOMPtr<nsIMsgCopyService> copyService(do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyFolders(folders, trashFolder, true, nullptr, msgWindow);
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::ConfirmFolderDeletion(nsIMsgWindow *aMsgWindow,
+ nsIMsgFolder *aFolder, bool *aResult)
+{
+ NS_ENSURE_ARG(aResult);
+ NS_ENSURE_ARG(aMsgWindow);
+ NS_ENSURE_ARG(aFolder);
+ nsCOMPtr<nsIDocShell> docShell;
+ aMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (docShell)
+ {
+ bool confirmDeletion = true;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pPrefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash", &confirmDeletion);
+ if (confirmDeletion)
+ {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = aFolder->GetName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ const char16_t *formatStrings[1] = { folderName.get() };
+
+ nsAutoString deleteFolderDialogTitle;
+ rv = bundle->GetStringFromName(
+ u"pop3DeleteFolderDialogTitle",
+ getter_Copies(deleteFolderDialogTitle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString deleteFolderButtonLabel;
+ rv = bundle->GetStringFromName(
+ u"pop3DeleteFolderButtonLabel",
+ getter_Copies(deleteFolderButtonLabel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString confirmationStr;
+ rv = bundle->FormatStringFromName(
+ u"pop3MoveFolderToTrash", formatStrings, 1,
+ getter_Copies(confirmationStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ if (dialog)
+ {
+ int32_t buttonPressed = 0;
+ // Default the dialog to "cancel".
+ const uint32_t buttonFlags =
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);
+ bool dummyValue = false;
+ rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(), confirmationStr.get(),
+ buttonFlags, deleteFolderButtonLabel.get(),
+ nullptr, nullptr, nullptr, &dummyValue,
+ &buttonPressed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aResult = !buttonPressed; // "ok" is in position 0
+ }
+ }
+ else
+ *aResult = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::Rename(const nsAString& aNewName, nsIMsgWindow *msgWindow)
+{
+ // Renaming to the same name is easy
+ if (mName.Equals(aNewName))
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ nsresult rv = GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder)
+ return NS_ERROR_NULL_POINTER;
+
+ rv = CheckIfFolderExists(aNewName, parentFolder, msgWindow);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgStore->RenameFolder(this, aNewName, getter_AddRefs(newFolder));
+ if (NS_FAILED(rv))
+ {
+ if (msgWindow)
+ (void) ThrowAlertMsg((rv == NS_MSG_FOLDER_EXISTS) ?
+ "folderExists" : "folderRenameFailed", msgWindow);
+ return rv;
+ }
+
+ int32_t count = mSubFolders.Count();
+ if (newFolder)
+ {
+ // Because we just renamed the db, w/o setting the pretty name in it,
+ // we need to force the pretty name to be correct.
+ // SetPrettyName won't write the name to the db if it doesn't think the
+ // name has changed. This hack forces the pretty name to get set in the db.
+ // We could set the new pretty name on the db before renaming the .msf file,
+ // but if the rename failed, it would be out of sync.
+ newFolder->SetPrettyName(EmptyString());
+ newFolder->SetPrettyName(aNewName);
+ bool changed = false;
+ MatchOrChangeFilterDestination(newFolder, true /*caseInsenstive*/, &changed);
+ if (changed)
+ AlertFilterChanged(msgWindow);
+
+ if (count > 0)
+ newFolder->RenameSubFolders(msgWindow, this);
+
+ // Discover the subfolders inside this folder (this is recursive)
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ newFolder->GetSubFolders(getter_AddRefs(dummy));
+
+ // the newFolder should have the same flags
+ newFolder->SetFlags(mFlags);
+ if (parentFolder)
+ {
+ SetParent(nullptr);
+ parentFolder->PropagateDelete(this, false, msgWindow);
+ parentFolder->NotifyItemAdded(newFolder);
+ }
+ SetFilePath(nullptr); // forget our path, since this folder object renamed itself
+ nsCOMPtr<nsIAtom> folderRenameAtom = MsgGetAtom("RenameCompleted");
+ newFolder->NotifyFolderEvent(folderRenameAtom);
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderRenamed(this, newFolder);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::RenameSubFolders(nsIMsgWindow *msgWindow, nsIMsgFolder *oldFolder)
+{
+ nsresult rv =NS_OK;
+ mInitialized = true;
+
+ uint32_t flags;
+ oldFolder->GetFlags(&flags);
+ SetFlags(flags);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = oldFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item));
+ if (!msgFolder)
+ continue;
+
+ nsString folderName;
+ rv = msgFolder->GetName(folderName);
+ nsCOMPtr <nsIMsgFolder> newFolder;
+ AddSubfolder(folderName, getter_AddRefs(newFolder));
+ if (newFolder)
+ {
+ newFolder->SetPrettyName(folderName);
+ bool changed = false;
+ msgFolder->MatchOrChangeFilterDestination(newFolder, true /*caseInsenstive*/, &changed);
+ if (changed)
+ msgFolder->AlertFilterChanged(msgWindow);
+ newFolder->RenameSubFolders(msgWindow, msgFolder);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetPrettyName(nsAString& prettyName)
+{
+ return nsMsgDBFolder::GetPrettyName(prettyName);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::SetPrettyName(const nsAString& aName)
+{
+ nsresult rv = nsMsgDBFolder::SetPrettyName(aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString folderName;
+ rv = GetStringProperty("folderName", folderName);
+ NS_ConvertUTF16toUTF8 utf8FolderName(mName);
+ return NS_FAILED(rv) || !folderName.Equals(utf8FolderName) ? SetStringProperty("folderName", utf8FolderName) : rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetName(nsAString& aName)
+{
+ ReadDBFolderInfo(false);
+ return nsMsgDBFolder::GetName(aName);
+}
+
+nsresult nsMsgLocalMailFolder::OpenDatabase()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIFile> file;
+ rv = GetFilePath(getter_AddRefs(file));
+
+ rv = msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ {
+ // check if we're a real folder by looking at the parent folder.
+ nsCOMPtr<nsIMsgFolder> parent;
+ GetParent(getter_AddRefs(parent));
+ if (parent)
+ {
+ // This little dance creates an empty .msf file and then checks
+ // if the db is valid - this works if the folder is empty, which
+ // we don't have a direct way of checking.
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(db));
+ if (db)
+ {
+ UpdateSummaryTotals(true);
+ db->Close(true);
+ mDatabase = nullptr;
+ db = nullptr;
+ rv = msgDBService->OpenFolderDB(this, false,
+ getter_AddRefs(mDatabase));
+ if (NS_FAILED(rv))
+ mDatabase = nullptr;
+ }
+ }
+ }
+ else if (NS_FAILED(rv))
+ mDatabase = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db)
+{
+ if (!db || !folderInfo || !mPath || mIsServer)
+ return NS_ERROR_NULL_POINTER; //ducarroz: should we use NS_ERROR_INVALID_ARG?
+
+ nsresult rv;
+ if (mDatabase)
+ rv = NS_OK;
+ else
+ {
+ rv = OpenDatabase();
+
+ if (mAddListener && mDatabase)
+ mDatabase->AddListener(this);
+ }
+
+ NS_IF_ADDREF(*db = mDatabase);
+ if (NS_SUCCEEDED(rv) && *db)
+ rv = (*db)->GetDBFolderInfo(folderInfo);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element)
+{
+ NS_ENSURE_ARG_POINTER(element);
+ nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString utf8Name;
+ rv = element->GetStringProperty("folderName", utf8Name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(utf8Name, mName);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::WriteToFolderCacheElem(nsIMsgFolderCacheElement *element)
+{
+ NS_ENSURE_ARG_POINTER(element);
+ nsMsgDBFolder::WriteToFolderCacheElem(element);
+ return element->SetStringProperty("folderName", NS_ConvertUTF16toUTF8(mName));
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetDeletable(bool *deletable)
+{
+ NS_ENSURE_ARG_POINTER(deletable);
+
+ bool isServer;
+ GetIsServer(&isServer);
+ *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::RefreshSizeOnDisk()
+{
+ int64_t oldFolderSize = mFolderSize;
+ // we set this to unknown to force it to get recalculated from disk
+ mFolderSize = kSizeUnknown;
+ if (NS_SUCCEEDED(GetSizeOnDisk(&mFolderSize)))
+ NotifyIntPropertyChanged(kFolderSizeAtom, oldFolderSize, mFolderSize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetSizeOnDisk(int64_t *aSize)
+{
+ NS_ENSURE_ARG_POINTER(aSize);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ // If this is the rootFolder, return 0 as a safe value.
+ if (NS_FAILED(rv) || isServer)
+ mFolderSize = 0;
+
+ if (mFolderSize == kSizeUnknown)
+ {
+ nsCOMPtr<nsIFile> file;
+ rv = GetFilePath(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Use a temporary variable so that we keep mFolderSize on kSizeUnknown
+ // if GetFileSize() fails.
+ int64_t folderSize;
+ rv = file->GetFileSize(&folderSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mFolderSize = folderSize;
+ }
+ *aSize = mFolderSize;
+ return NS_OK;
+}
+
+nsresult
+nsMsgLocalMailFolder::GetTrashFolder(nsIMsgFolder** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv))
+ {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, result);
+ if (!*result)
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::DeleteMessages(nsIArray *messages,
+ nsIMsgWindow *msgWindow,
+ bool deleteStorage, bool isMove,
+ nsIMsgCopyServiceListener* listener, bool allowUndo)
+{
+ NS_ENSURE_ARG_POINTER(messages);
+
+ uint32_t messageCount;
+ nsresult rv = messages->GetLength(&messageCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // shift delete case - (delete to trash is handled in EndMove)
+ // this is also the case when applying retention settings.
+ if (deleteStorage && !isMove)
+ {
+ MarkMsgsOnPop3Server(messages, POP3_DELETE);
+ }
+
+ bool isTrashFolder = mFlags & nsMsgFolderFlags::Trash;
+
+ // notify on delete from trash and shift-delete
+ if (!isMove && (deleteStorage || isTrashFolder))
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsDeleted(messages);
+ }
+
+ if (!deleteStorage && !isTrashFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return copyService->CopyMessages(this, messages, trashFolder,
+ true, listener, msgWindow, allowUndo);
+ }
+ }
+ else
+ {
+ nsCOMPtr <nsIMsgDatabase> msgDB;
+ rv = GetDatabaseWOReparse(getter_AddRefs(msgDB));
+ if (NS_SUCCEEDED(rv))
+ {
+ if (deleteStorage && isMove && GetDeleteFromServerOnMove())
+ MarkMsgsOnPop3Server(messages, POP3_DELETE);
+
+ nsCOMPtr<nsISupports> msgSupport;
+ rv = EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = msgStore->DeleteMessages(messages);
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
+ for (uint32_t i = 0; i < messageCount; ++i)
+ {
+ msgDBHdr = do_QueryElementAt(messages, i, &rv);
+ rv = msgDB->DeleteHeader(msgDBHdr, nullptr, false, true);
+ }
+ }
+ }
+ else if (rv == NS_MSG_FOLDER_BUSY)
+ ThrowAlertMsg("deletingMsgsFailed", msgWindow);
+
+ // we are the source folder here for a move or shift delete
+ //enable notifications because that will close the file stream
+ // we've been caching, mark the db as valid, and commit it.
+ EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/);
+ if (!isMove)
+ NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom : mDeleteOrMoveMsgFailedAtom);
+ if (msgWindow && !isMove)
+ AutoCompact(msgWindow);
+ }
+ }
+
+ if (msgWindow && !isMove && (deleteStorage || isTrashFolder)) {
+ // Clear undo and redo stack.
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr) {
+ txnMgr->Clear();
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag)
+{
+ nsMsgMessageFlagType msgFlag = 0;
+ switch (aDispositionFlag) {
+ case nsIMsgFolder::nsMsgDispositionState_Replied:
+ msgFlag = nsMsgMessageFlags::Replied;
+ break;
+ case nsIMsgFolder::nsMsgDispositionState_Forwarded:
+ msgFlag = nsMsgMessageFlags::Forwarded;
+ break;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ messages->AppendElement(aMessage, false);
+ return msgStore->ChangeFlags(messages, msgFlag, true);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::MarkMessagesRead(nsIArray *aMessages, bool aMarkRead)
+{
+ nsresult rv = nsMsgDBFolder::MarkMessagesRead(aMessages, aMarkRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Read, aMarkRead);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::MarkMessagesFlagged(nsIArray *aMessages,
+ bool aMarkFlagged)
+{
+ nsresult rv = nsMsgDBFolder::MarkMessagesFlagged(aMessages, aMarkFlagged);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Marked,
+ aMarkFlagged);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::MarkAllMessagesRead(nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey *thoseMarked = nullptr;
+ uint32_t numMarked = 0;
+ EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/);
+ rv = mDatabase->MarkAllRead(&numMarked, &thoseMarked);
+ EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/);
+ if (NS_FAILED(rv) || !numMarked || !thoseMarked)
+ return rv;
+
+ do {
+ nsCOMPtr<nsIMutableArray> messages;
+ rv = MsgGetHdrsFromKeys(mDatabase, thoseMarked, numMarked, getter_AddRefs(messages));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_FAILED(rv))
+ break;
+
+ rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true);
+ if (NS_FAILED(rv))
+ break;
+
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+
+ // Setup a undo-state
+ if (aMsgWindow)
+ rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked, numMarked);
+ } while (false);
+
+ free(thoseMarked);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::MarkThreadRead(nsIMsgThread *thread)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey *thoseMarked = nullptr;
+ uint32_t numMarked = 0;
+ rv = mDatabase->MarkThreadRead(thread, nullptr, &numMarked, &thoseMarked);
+ if (NS_FAILED(rv) || !numMarked || !thoseMarked)
+ return rv;
+
+ do {
+ nsCOMPtr<nsIMutableArray> messages;
+ rv = MsgGetHdrsFromKeys(mDatabase, thoseMarked, numMarked, getter_AddRefs(messages));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_FAILED(rv))
+ break;
+
+ rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true);
+ if (NS_FAILED(rv))
+ break;
+
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ } while (false);
+
+ free(thoseMarked);
+ return rv;
+}
+
+nsresult
+nsMsgLocalMailFolder::InitCopyState(nsISupports* aSupport,
+ nsIArray* messages,
+ bool isMove,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow *msgWindow, bool isFolder,
+ bool allowUndo)
+{
+ nsCOMPtr<nsIFile> path;
+
+ NS_ASSERTION(!mCopyState, "already copying a msg into this folder");
+ if (mCopyState)
+ return NS_ERROR_FAILURE; // already has a copy in progress
+
+ // get mDatabase set, so we can use it to add new hdrs to this db.
+ // calling GetDatabase will set mDatabase - we use the comptr
+ // here to avoid doubling the refcnt on mDatabase. We don't care if this
+ // fails - we just want to give it a chance. It will definitely fail in
+ // nsLocalMailFolder::EndCopy because we will have written data to the folder
+ // and changed its size.
+ nsCOMPtr <nsIMsgDatabase> msgDB;
+ GetDatabaseWOReparse(getter_AddRefs(msgDB));
+ bool isLocked;
+
+ GetLocked(&isLocked);
+ if (isLocked)
+ return NS_MSG_FOLDER_BUSY;
+
+ AcquireSemaphore(static_cast<nsIMsgLocalMailFolder*>(this));
+
+ mCopyState = new nsLocalMailCopyState();
+ NS_ENSURE_TRUE(mCopyState, NS_ERROR_OUT_OF_MEMORY);
+
+ mCopyState->m_dataBuffer = (char*) PR_CALLOC(COPY_BUFFER_SIZE+1);
+ NS_ENSURE_TRUE(mCopyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ mCopyState->m_dataBufferSize = COPY_BUFFER_SIZE;
+ mCopyState->m_destDB = msgDB;
+
+ //Before we continue we should verify that there is enough diskspace.
+ //XXX How do we do this?
+ nsresult rv;
+ mCopyState->m_srcSupport = do_QueryInterface(aSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCopyState->m_messages = messages;
+ mCopyState->m_curCopyIndex = 0;
+ mCopyState->m_isMove = isMove;
+ mCopyState->m_isFolder = isFolder;
+ mCopyState->m_allowUndo = allowUndo;
+ mCopyState->m_msgWindow = msgWindow;
+ rv = messages->GetLength(&mCopyState->m_totalMsgCount);
+ if (listener)
+ mCopyState->m_listener = do_QueryInterface(listener, &rv);
+ mCopyState->m_copyingMultipleMessages = false;
+ mCopyState->m_wholeMsgInStream = false;
+
+ // If we have source messages then we need destination messages too.
+ if (messages)
+ mCopyState->m_destMessages = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
+{
+ if (mCopyState)
+ mCopyState->m_destDB = nullptr;
+ return nsMsgDBFolder::OnAnnouncerGoingAway(instigator);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::OnCopyCompleted(nsISupports *srcSupport, bool moveCopySucceeded)
+{
+ if (mCopyState && mCopyState->m_notifyFolderLoaded)
+ NotifyFolderEvent(mFolderLoadedAtom);
+
+ (void) RefreshSizeOnDisk();
+ // we are the destination folder for a move/copy
+ bool haveSemaphore;
+ nsresult rv = TestSemaphore(static_cast<nsIMsgLocalMailFolder*>(this), &haveSemaphore);
+ if (NS_SUCCEEDED(rv) && haveSemaphore)
+ ReleaseSemaphore(static_cast<nsIMsgLocalMailFolder*>(this));
+
+ if (mCopyState && !mCopyState->m_newMsgKeywords.IsEmpty() &&
+ mCopyState->m_newHdr)
+ {
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(messageArray, rv);
+ messageArray->AppendElement(mCopyState->m_newHdr, false);
+ AddKeywordsToMessages(messageArray, mCopyState->m_newMsgKeywords);
+ }
+ if (moveCopySucceeded && mDatabase)
+ {
+ mDatabase->SetSummaryValid(true);
+ (void) CloseDBIfFolderNotOpen();
+ }
+
+ delete mCopyState;
+ mCopyState = nullptr;
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return copyService->NotifyCompletion(srcSupport, this, moveCopySucceeded ? NS_OK : NS_ERROR_FAILURE);
+}
+
+bool nsMsgLocalMailFolder::CheckIfSpaceForCopy(nsIMsgWindow *msgWindow,
+ nsIMsgFolder *srcFolder,
+ nsISupports *srcSupports,
+ bool isMove,
+ int64_t totalMsgSize)
+{
+ bool spaceNotAvailable = true;
+ nsresult rv = WarnIfLocalFileTooBig(msgWindow, totalMsgSize, &spaceNotAvailable);
+ if (NS_FAILED(rv) || spaceNotAvailable)
+ {
+ if (isMove && srcFolder)
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ OnCopyCompleted(srcSupports, false);
+ return false;
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::CopyMessages(nsIMsgFolder* srcFolder, nsIArray*
+ messages, bool isMove,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener,
+ bool isFolder, bool allowUndo)
+{
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ bool isServer;
+ nsresult rv = GetIsServer(&isServer);
+ if (NS_SUCCEEDED(rv) && isServer)
+ {
+ NS_ERROR("Destination is the root folder. Cannot move/copy here");
+ if (isMove)
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ return OnCopyCompleted(srcSupport, false);
+ }
+
+ UpdateTimestamps(allowUndo);
+ nsCString protocolType;
+ rv = srcFolder->GetURI(protocolType);
+ protocolType.SetLength(protocolType.FindChar(':'));
+
+ bool needOfflineBody = (WeAreOffline() &&
+ (MsgLowerCaseEqualsLiteral(protocolType, "imap") ||
+ MsgLowerCaseEqualsLiteral(protocolType, "news")));
+ int64_t totalMsgSize = 0;
+ uint32_t numMessages = 0;
+ messages->GetLength(&numMessages);
+ for (uint32_t i = 0; i < numMessages; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message(do_QueryElementAt(messages, i, &rv));
+ if (NS_SUCCEEDED(rv) && message)
+ {
+ nsMsgKey key;
+ uint32_t msgSize;
+ message->GetMessageSize(&msgSize);
+
+ /* 200 is a per-message overhead to account for any extra data added
+ to the message.
+ */
+ totalMsgSize += msgSize + 200;
+
+ if (needOfflineBody)
+ {
+ bool hasMsgOffline = false;
+ message->GetMessageKey(&key);
+ srcFolder->HasMsgOffline(key, &hasMsgOffline);
+ if (!hasMsgOffline)
+ {
+ if (isMove)
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ ThrowAlertMsg("cantMoveMsgWOBodyOffline", msgWindow);
+ return OnCopyCompleted(srcSupport, false);
+ }
+ }
+ }
+ }
+
+ if (!CheckIfSpaceForCopy(msgWindow, srcFolder, srcSupport, isMove,
+ totalMsgSize))
+ return NS_OK;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool storeDidCopy = false;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsITransaction> undoTxn;
+ nsCOMPtr<nsIArray> dstHdrs;
+ rv = msgStore->CopyMessages(isMove, messages, this, listener,
+ getter_AddRefs(dstHdrs), getter_AddRefs(undoTxn),
+ &storeDidCopy);
+ if (storeDidCopy)
+ {
+ NS_ASSERTION(undoTxn, "if store does copy, it needs to add undo action");
+ if (msgWindow && undoTxn)
+ {
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr)
+ txnMgr->DoTransaction(undoTxn);
+ }
+ if (isMove)
+ srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom :
+ mDeleteOrMoveMsgFailedAtom);
+
+ if (NS_SUCCEEDED(rv)) {
+ // If the store did the copy, like maildir, we need to mark messages on the server.
+ // Otherwise that's done in EndMove().
+ nsCOMPtr <nsIMsgLocalMailFolder> localDstFolder;
+ QueryInterface(NS_GET_IID(nsIMsgLocalMailFolder), getter_AddRefs(localDstFolder));
+ if (localDstFolder)
+ {
+ // If we are the trash and a local msg is being moved to us, mark the source
+ // for delete from server, if so configured.
+ if (mFlags & nsMsgFolderFlags::Trash)
+ {
+ // If we're deleting on all moves, we'll mark this message for deletion when
+ // we call DeleteMessages on the source folder. So don't mark it for deletion
+ // here, in that case.
+ if (!GetDeleteFromServerOnMove())
+ localDstFolder->MarkMsgsOnPop3Server(dstHdrs, POP3_DELETE);
+ }
+ }
+ }
+ return rv;
+ }
+ // If the store doesn't do the copy, we'll stream the source messages into
+ // the target folder, using getMsgInputStream and getNewMsgOutputStream.
+
+ // don't update the counts in the dest folder until it is all over
+ EnableNotifications(allMessageCountNotifications, false, false /*dbBatching*/); //dest folder doesn't need db batching
+
+ // sort the message array by key
+ uint32_t numMsgs = 0;
+ messages->GetLength(&numMsgs);
+ nsTArray<nsMsgKey> keyArray(numMsgs);
+ if (numMsgs > 1)
+ {
+ for (uint32_t i = 0; i < numMsgs; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> aMessage = do_QueryElementAt(messages, i, &rv);
+ if (NS_SUCCEEDED(rv) && aMessage)
+ {
+ nsMsgKey key;
+ aMessage->GetMessageKey(&key);
+ keyArray.AppendElement(key);
+ }
+ }
+
+ keyArray.Sort();
+
+ nsCOMPtr<nsIMutableArray> sortedMsgs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitCopyState(srcSupport, sortedMsgs, isMove, listener, msgWindow, isFolder, allowUndo);
+ }
+ else
+ rv = InitCopyState(srcSupport, messages, isMove, listener, msgWindow, isFolder, allowUndo);
+
+ if (NS_FAILED(rv))
+ {
+ ThrowAlertMsg("operationFailedFolderBusy", msgWindow);
+ (void) OnCopyCompleted(srcSupport, false);
+ return rv;
+ }
+
+ if (!MsgLowerCaseEqualsLiteral(protocolType, "mailbox"))
+ {
+ mCopyState->m_dummyEnvelopeNeeded = true;
+ nsParseMailMessageState* parseMsgState = new nsParseMailMessageState();
+ if (parseMsgState)
+ {
+ nsCOMPtr<nsIMsgDatabase> msgDb;
+ mCopyState->m_parseMsgState = parseMsgState;
+ GetDatabaseWOReparse(getter_AddRefs(msgDb));
+ if (msgDb)
+ parseMsgState->SetMailDB(msgDb);
+ }
+ }
+
+ // undo stuff
+ if (allowUndo) //no undo for folder move/copy or or move/copy from search window
+ {
+ RefPtr<nsLocalMoveCopyMsgTxn> msgTxn = new nsLocalMoveCopyMsgTxn;
+ if (msgTxn && NS_SUCCEEDED(msgTxn->Init(srcFolder, this, isMove)))
+ {
+ msgTxn->SetMsgWindow(msgWindow);
+ if (isMove)
+ {
+ if (mFlags & nsMsgFolderFlags::Trash)
+ msgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ else
+ msgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ }
+ else
+ msgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ msgTxn.swap(mCopyState->m_undoMsgTxn);
+ }
+ }
+
+ if (numMsgs > 1 && ((MsgLowerCaseEqualsLiteral(protocolType, "imap") && !WeAreOffline()) ||
+ MsgLowerCaseEqualsLiteral(protocolType, "mailbox")))
+ {
+ mCopyState->m_copyingMultipleMessages = true;
+ rv = CopyMessagesTo(mCopyState->m_messages, keyArray, msgWindow, this, isMove);
+ if (NS_FAILED(rv))
+ {
+ NS_ERROR("copy message failed");
+ (void) OnCopyCompleted(srcSupport, false);
+ }
+ }
+ else
+ {
+ nsCOMPtr<nsISupports> msgSupport = do_QueryElementAt(mCopyState->m_messages, 0);
+ if (msgSupport)
+ {
+ rv = CopyMessageTo(msgSupport, this, msgWindow, isMove);
+ if (NS_FAILED(rv))
+ {
+ NS_ASSERTION(false, "copy message failed");
+ (void) OnCopyCompleted(srcSupport, false);
+ }
+ }
+ }
+ // if this failed immediately, need to turn back on notifications and inform FE.
+ if (NS_FAILED(rv))
+ {
+ if (isMove)
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/); //dest folder doesn't need db batching
+ }
+ return rv;
+}
+// for srcFolder that are on different server than the dstFolder.
+// "this" is the parent of the new dest folder.
+nsresult
+nsMsgLocalMailFolder::CopyFolderAcrossServer(nsIMsgFolder* srcFolder, nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener *listener )
+{
+ mInitialized = true;
+
+ nsString folderName;
+ srcFolder->GetName(folderName);
+
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ nsresult rv = CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> messages;
+ rv = srcFolder->GetMessages(getter_AddRefs(messages));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+
+ bool hasMoreElements = false;
+ nsCOMPtr<nsISupports> aSupport;
+
+ if (messages)
+ rv = messages->HasMoreElements(&hasMoreElements);
+
+ while (NS_SUCCEEDED(rv) && hasMoreElements)
+ {
+ rv = messages->GetNext(getter_AddRefs(aSupport));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgArray->AppendElement(aSupport, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = messages->HasMoreElements(&hasMoreElements);
+ }
+
+ uint32_t numMsgs=0;
+ msgArray->GetLength(&numMsgs);
+
+ if (numMsgs > 0 ) //if only srcFolder has messages..
+ newMsgFolder->CopyMessages(srcFolder, msgArray, false, msgWindow, listener, true /* is folder*/, false /* allowUndo */);
+ else
+ {
+ nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(newMsgFolder);
+ if (localFolder)
+ {
+ // normally these would get called from ::EndCopy when the last message
+ // was finished copying. But since there are no messages, we have to call
+ // them explicitly.
+ nsCOMPtr<nsISupports> srcSupports = do_QueryInterface(newMsgFolder);
+ localFolder->CopyAllSubFolders(srcFolder, msgWindow, listener);
+ return localFolder->OnCopyCompleted(srcSupports, true);
+ }
+ }
+ return NS_OK; // otherwise the front-end will say Exception::CopyFolder
+}
+
+nsresult //copy the sub folders
+nsMsgLocalMailFolder::CopyAllSubFolders(nsIMsgFolder *srcFolder,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener *listener )
+{
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = srcFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item));
+ if (folder)
+ CopyFolderAcrossServer(folder, msgWindow, listener);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::CopyFolder( nsIMsgFolder* srcFolder, bool isMoveFolder,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener)
+{
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ // isMoveFolder == true when "this" and srcFolder are on same server
+ return isMoveFolder ? CopyFolderLocal(srcFolder, isMoveFolder, msgWindow, listener ) :
+ CopyFolderAcrossServer(srcFolder, msgWindow, listener );
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::CopyFolderLocal(nsIMsgFolder *srcFolder,
+ bool isMoveFolder,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener *aListener)
+{
+ mInitialized = true;
+ bool isChildOfTrash;
+ nsresult rv = IsChildOfTrash(&isChildOfTrash);
+ if (NS_SUCCEEDED(rv) && isChildOfTrash)
+ {
+ // do it just for the parent folder (isMoveFolder is true for parent only) if we are deleting/moving a folder tree
+ // don't confirm for rss folders.
+ if (isMoveFolder)
+ {
+ // if there's a msgWindow, confirm the deletion
+ if (msgWindow)
+ {
+
+ bool okToDelete = false;
+ ConfirmFolderDeletion(msgWindow, srcFolder, &okToDelete);
+ if (!okToDelete)
+ return NS_MSG_ERROR_COPY_FOLDER_ABORTED;
+ }
+ // if we are moving a favorite folder to trash, we should clear the favorites flag
+ // so it gets removed from the view.
+ srcFolder->ClearFlag(nsMsgFolderFlags::Favorite);
+ }
+
+ bool match = false;
+ rv = srcFolder->MatchOrChangeFilterDestination(nullptr, false, &match);
+ if (match && msgWindow)
+ {
+ bool confirmed = false;
+ srcFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirmed);
+ if (!confirmed)
+ return NS_MSG_ERROR_COPY_FOLDER_ABORTED;
+ }
+ }
+
+ nsAutoString newFolderName;
+ nsAutoString folderName;
+ rv = srcFolder->GetName(folderName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isMoveFolder) {
+ rv = CheckIfFolderExists(folderName, this, msgWindow);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ else
+ {
+ // If folder name already exists in destination, generate a new unique name.
+ bool containsChild = true;
+ uint32_t i;
+ for (i = 1; containsChild; i++) {
+ newFolderName.Assign(folderName);
+ if (i > 1) {
+ // This could be localizable but Toolkit is fine without it, see
+ // mozilla/toolkit/content/contentAreaUtils.js::uniqueFile()
+ newFolderName.Append('(');
+ newFolderName.AppendInt(i);
+ newFolderName.Append(')');
+ }
+ rv = ContainsChildNamed(newFolderName, &containsChild);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // 'i' is one more than the number of iterations done
+ // and the number tacked onto the name of the folder.
+ if (i > 2 && !isChildOfTrash) {
+ // Folder name already exists, ask if rename is OK.
+ // If moving to Trash, don't ask and do it.
+ if (!ConfirmAutoFolderRename(msgWindow, folderName, newFolderName))
+ return NS_MSG_ERROR_COPY_FOLDER_ABORTED;
+ }
+ }
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return msgStore->CopyFolder(srcFolder, this, isMoveFolder, msgWindow,
+ aListener, newFolderName);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::CopyFileMessage(nsIFile* aFile,
+ nsIMsgDBHdr *msgToReplace,
+ bool isDraftOrTemplate,
+ uint32_t newMsgFlags,
+ const nsACString &aNewMsgKeywords,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ nsParseMailMessageState* parseMsgState = nullptr;
+ int64_t fileSize = 0;
+
+ nsCOMPtr<nsISupports> fileSupport(do_QueryInterface(aFile, &rv));
+
+ aFile->GetFileSize(&fileSize);
+ if (!CheckIfSpaceForCopy(msgWindow, nullptr, fileSupport, false, fileSize))
+ return NS_OK;
+
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID));
+
+ if (msgToReplace)
+ messages->AppendElement(msgToReplace, false);
+
+ rv = InitCopyState(fileSupport, messages, msgToReplace ? true : false,
+ listener, msgWindow, false, false);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mCopyState)
+ mCopyState->m_newMsgKeywords = aNewMsgKeywords;
+
+ parseMsgState = new nsParseMailMessageState();
+ NS_ENSURE_TRUE(parseMsgState, NS_ERROR_OUT_OF_MEMORY);
+ nsCOMPtr<nsIMsgDatabase> msgDb;
+ mCopyState->m_parseMsgState = parseMsgState;
+ GetDatabaseWOReparse(getter_AddRefs(msgDb));
+ if (msgDb)
+ parseMsgState->SetMailDB(msgDb);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+
+ // All or none for adding a message file to the store
+ if (NS_SUCCEEDED(rv) && fileSize > PR_INT32_MAX)
+ rv = NS_ERROR_ILLEGAL_VALUE; // may need error code for max msg size
+
+ if (NS_SUCCEEDED(rv) && inputStream)
+ {
+ char buffer[5];
+ uint32_t readCount;
+ rv = inputStream->Read(buffer, 5, &readCount);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (strncmp(buffer, "From ", 5))
+ mCopyState->m_dummyEnvelopeNeeded = true;
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(inputStream, &rv);
+ if (NS_SUCCEEDED(rv))
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+ }
+
+ mCopyState->m_wholeMsgInStream = true;
+ if (NS_SUCCEEDED(rv))
+ rv = BeginCopy(nullptr);
+
+ if (NS_SUCCEEDED(rv))
+ rv = CopyData(inputStream, (int32_t) fileSize);
+
+ if (NS_SUCCEEDED(rv))
+ rv = EndCopy(true);
+
+ //mDatabase should have been initialized above - if we got msgDb
+ // If we were going to delete, here is where we would do it. But because
+ // existing code already supports doing those deletes, we are just going
+ // to end the copy.
+ if (NS_SUCCEEDED(rv) && msgToReplace && mDatabase)
+ rv = OnCopyCompleted(fileSupport, true);
+
+ if (inputStream)
+ inputStream->Close();
+ }
+
+ if (NS_FAILED(rv))
+ (void) OnCopyCompleted(fileSupport, false);
+
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::DeleteMessage(nsISupports *message,
+ nsIMsgWindow *msgWindow,
+ bool deleteStorage, bool commit)
+{
+ nsresult rv = NS_OK;
+ if (deleteStorage)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr(do_QueryInterface(message, &rv));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ GetDatabase();
+ if (mDatabase)
+ rv = mDatabase->DeleteHeader(msgDBHdr, nullptr, commit, true);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetNewMessages(nsIMsgWindow *aWindow, nsIUrlListener *aListener)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ nsCOMPtr<nsILocalMailIncomingServer> localMailServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ // XXX todo, move all this into nsILocalMailIncomingServer's GetNewMail
+ // so that we don't have to have RSS foo here.
+ nsCOMPtr<nsIRssIncomingServer> rssServer = do_QueryInterface(server, &rv);
+ if (NS_SUCCEEDED(rv))
+ return localMailServer->GetNewMail(aWindow, aListener, this, nullptr);
+
+ nsCOMPtr<nsIMsgFolder> inbox;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inbox));
+ }
+ nsCOMPtr<nsIMsgLocalMailFolder> localInbox = do_QueryInterface(inbox, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ bool valid = false;
+ nsCOMPtr <nsIMsgDatabase> db;
+ // this will kick off a reparse if the db is out of date.
+ rv = localInbox->GetDatabaseWithReparse(nullptr, aWindow, getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv))
+ {
+ db->GetSummaryValid(&valid);
+ rv = valid ? localMailServer->GetNewMail(aWindow, aListener, inbox, nullptr) :
+ localInbox->SetCheckForNewMessagesAfterParsing(true);
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::WriteStartOfNewMessage()
+{
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(mCopyState->m_fileStream);
+ int64_t filePos;
+ seekableStream->Tell(&filePos);
+
+ // CopyFileMessage() and CopyMessages() from servers other than pop3
+ if (mCopyState->m_parseMsgState)
+ {
+ if (mCopyState->m_parseMsgState->m_newMsgHdr)
+ mCopyState->m_parseMsgState->m_newMsgHdr->GetMessageKey(&mCopyState->m_curDstKey);
+ mCopyState->m_parseMsgState->SetEnvelopePos(filePos);
+ mCopyState->m_parseMsgState->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
+ }
+ if (mCopyState->m_dummyEnvelopeNeeded)
+ {
+ nsCString result;
+ nsAutoCString nowStr;
+ MsgGenerateNowStr(nowStr);
+ result.AppendLiteral("From - ");
+ result.Append(nowStr);
+ result.Append(MSG_LINEBREAK);
+
+ // *** jt - hard code status line for now; come back later
+ nsresult rv;
+ nsCOMPtr <nsIMsgDBHdr> curSourceMessage = do_QueryElementAt(mCopyState->m_messages,
+ mCopyState->m_curCopyIndex, &rv);
+
+ char statusStrBuf[50];
+ if (curSourceMessage)
+ {
+ uint32_t dbFlags = 0;
+ curSourceMessage->GetFlags(&dbFlags);
+
+ // write out x-mozilla-status, but make sure we don't write out nsMsgMessageFlags::Offline
+ PR_snprintf(statusStrBuf, sizeof(statusStrBuf), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK,
+ dbFlags & ~(nsMsgMessageFlags::RuntimeOnly | nsMsgMessageFlags::Offline) & 0x0000FFFF);
+ }
+ else
+ strcpy(statusStrBuf, "X-Mozilla-Status: 0001" MSG_LINEBREAK);
+ uint32_t bytesWritten;
+ mCopyState->m_fileStream->Write(result.get(), result.Length(), &bytesWritten);
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->ParseAFolderLine(
+ result.get(), result.Length());
+ mCopyState->m_fileStream->Write(statusStrBuf, strlen(statusStrBuf), &bytesWritten);
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->ParseAFolderLine(
+ statusStrBuf, strlen(statusStrBuf));
+ result = "X-Mozilla-Status2: 00000000" MSG_LINEBREAK;
+ mCopyState->m_fileStream->Write(result.get(), result.Length(), &bytesWritten);
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->ParseAFolderLine(
+ result.get(), result.Length());
+ result = X_MOZILLA_KEYWORDS;
+ mCopyState->m_fileStream->Write(result.get(), result.Length(), &bytesWritten);
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->ParseAFolderLine(
+ result.get(), result.Length());
+ mCopyState->m_fromLineSeen = true;
+ }
+ else
+ mCopyState->m_fromLineSeen = false;
+
+ mCopyState->m_curCopyIndex++;
+ return NS_OK;
+}
+
+nsresult nsMsgLocalMailFolder::InitCopyMsgHdrAndFileStream()
+{
+ nsresult rv = GetMsgStore(getter_AddRefs(mCopyState->m_msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool reusable;
+ rv = mCopyState->m_msgStore->
+ GetNewMsgOutputStream(this, getter_AddRefs(mCopyState->m_newHdr),
+ &reusable,
+ getter_AddRefs(mCopyState->m_fileStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->SetNewMsgHdr(mCopyState->m_newHdr);
+ return rv;
+}
+
+//nsICopyMessageListener
+NS_IMETHODIMP nsMsgLocalMailFolder::BeginCopy(nsIMsgDBHdr *message)
+{
+ if (!mCopyState)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ if (!mCopyState->m_copyingMultipleMessages)
+ {
+ rv = InitCopyMsgHdrAndFileStream();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(mCopyState->m_fileStream, &rv);
+
+ // XXX ToDo: When copying multiple messages from a non-offline-enabled IMAP
+ // server, this fails. (The copy succeeds because the file stream is created
+ // subsequently in StartMessage) We should not be warning on an expected error.
+ // Perhaps there are unexpected consequences of returning early?
+ NS_ENSURE_SUCCESS(rv, rv);
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+
+ int32_t messageIndex = (mCopyState->m_copyingMultipleMessages) ? mCopyState->m_curCopyIndex - 1 : mCopyState->m_curCopyIndex;
+ NS_ASSERTION(!mCopyState->m_copyingMultipleMessages || messageIndex >= 0, "messageIndex invalid");
+ // by the time we get here, m_curCopyIndex is 1 relative because WriteStartOfNewMessage increments it
+ mCopyState->m_messages->QueryElementAt(messageIndex, NS_GET_IID(nsIMsgDBHdr),
+ (void **)getter_AddRefs(mCopyState->m_message));
+ // The flags of the source message can get changed when it is deleted, so
+ // save them here.
+ if (mCopyState->m_message)
+ mCopyState->m_message->GetFlags(&(mCopyState->m_flags));
+ DisplayMoveCopyStatusMsg();
+ if (mCopyState->m_listener)
+ mCopyState->m_listener->OnProgress(mCopyState->m_curCopyIndex, mCopyState->m_totalMsgCount);
+ // if we're copying more than one message, StartMessage will handle this.
+ return !mCopyState->m_copyingMultipleMessages ? WriteStartOfNewMessage() : rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::CopyData(nsIInputStream *aIStream, int32_t aLength)
+{
+ //check to make sure we have control of the write.
+ bool haveSemaphore;
+ nsresult rv = NS_OK;
+
+ rv = TestSemaphore(static_cast<nsIMsgLocalMailFolder*>(this), &haveSemaphore);
+ if (NS_FAILED(rv))
+ return rv;
+ if (!haveSemaphore)
+ return NS_MSG_FOLDER_BUSY;
+
+ if (!mCopyState)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ uint32_t readCount;
+ //allocate one extra byte for '\0' at the end and another extra byte at the
+ //front to insert a '>' if we have a "From" line
+ //allocate 2 more for crlf that may be needed for those without crlf at end of file
+ if ( aLength + mCopyState->m_leftOver + 4 > mCopyState->m_dataBufferSize )
+ {
+ char *newBuffer = (char *) PR_REALLOC(mCopyState->m_dataBuffer, aLength + mCopyState->m_leftOver + 4);
+ if (!newBuffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mCopyState->m_dataBuffer = newBuffer;
+ mCopyState->m_dataBufferSize = aLength + mCopyState->m_leftOver + 3;
+ }
+
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(mCopyState->m_fileStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+
+ rv = aIStream->Read(mCopyState->m_dataBuffer + mCopyState->m_leftOver + 1, aLength, &readCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCopyState->m_leftOver += readCount;
+ mCopyState->m_dataBuffer[mCopyState->m_leftOver + 1] ='\0';
+ char *start = mCopyState->m_dataBuffer + 1;
+ char *endBuffer = mCopyState->m_dataBuffer + mCopyState->m_leftOver + 1;
+
+ uint32_t lineLength;
+ uint32_t bytesWritten;
+
+ while (1)
+ {
+ char *end = PL_strnpbrk(start, "\r\n", endBuffer - start);
+ if (!end)
+ {
+ mCopyState->m_leftOver -= (start - mCopyState->m_dataBuffer - 1);
+ // In CopyFileMessage, a complete message is being copied in a single
+ // call to CopyData, and if it does not have a LINEBREAK at the EOF,
+ // then end will be null after reading the last line, and we need
+ // to append the LINEBREAK to the buffer to enable transfer of the last line.
+ if (mCopyState->m_wholeMsgInStream)
+ {
+ end = start + mCopyState->m_leftOver;
+ memcpy(end, MSG_LINEBREAK "\0", MSG_LINEBREAK_LEN + 1);
+ }
+ else
+ {
+ memmove (mCopyState->m_dataBuffer + 1, start, mCopyState->m_leftOver);
+ break;
+ }
+ }
+
+ //need to set the linebreak_len each time
+ uint32_t linebreak_len = 1; //assume CR or LF
+ if (*end == '\r' && *(end+1) == '\n')
+ linebreak_len = 2; //CRLF
+
+ if (!mCopyState->m_fromLineSeen)
+ {
+ mCopyState->m_fromLineSeen = true;
+ NS_ASSERTION(strncmp(start, "From ", 5) == 0,
+ "Fatal ... bad message format\n");
+ }
+ else if (strncmp(start, "From ", 5) == 0)
+ {
+ //if we're at the beginning of the buffer, we've reserved a byte to
+ //insert a '>'. If we're in the middle, we're overwriting the previous
+ //line ending, but we've already written it to m_fileStream, so it's OK.
+ *--start = '>';
+ }
+
+ lineLength = end - start + linebreak_len;
+ rv = mCopyState->m_fileStream->Write(start, lineLength, &bytesWritten);
+ if (bytesWritten != lineLength || NS_FAILED(rv))
+ {
+ ThrowAlertMsg("copyMsgWriteFailed", mCopyState->m_msgWindow);
+ mCopyState->m_writeFailed = true;
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->ParseAFolderLine(start, lineLength);
+
+ start = end + linebreak_len;
+ if (start >= endBuffer)
+ {
+ mCopyState->m_leftOver = 0;
+ break;
+ }
+ }
+ return rv;
+}
+
+void nsMsgLocalMailFolder::CopyPropertiesToMsgHdr(nsIMsgDBHdr *destHdr,
+ nsIMsgDBHdr *srcHdr,
+ bool aIsMove)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCString dontPreserve;
+
+ // These preferences exist so that extensions can control which properties
+ // are preserved in the database when a message is moved or copied. All
+ // properties are preserved except those listed in these preferences
+ if (aIsMove)
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnMove",
+ getter_Copies(dontPreserve));
+ else
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy",
+ getter_Copies(dontPreserve));
+
+ CopyHdrPropertiesWithSkipList(destHdr, srcHdr, dontPreserve);
+}
+
+void
+nsMsgLocalMailFolder::CopyHdrPropertiesWithSkipList(nsIMsgDBHdr *destHdr,
+ nsIMsgDBHdr *srcHdr,
+ const nsCString &skipList)
+{
+ nsCOMPtr<nsIUTF8StringEnumerator> propertyEnumerator;
+ nsresult rv = srcHdr->GetPropertyEnumerator(getter_AddRefs(propertyEnumerator));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // We'll add spaces at beginning and end so we can search for space-name-space
+ nsCString dontPreserveEx(NS_LITERAL_CSTRING(" "));
+ dontPreserveEx.Append(skipList);
+ dontPreserveEx.AppendLiteral(" ");
+
+ nsAutoCString property;
+ nsCString sourceString;
+ bool hasMore;
+ while (NS_SUCCEEDED(propertyEnumerator->HasMore(&hasMore)) && hasMore)
+ {
+ propertyEnumerator->GetNext(property);
+ nsAutoCString propertyEx(NS_LITERAL_CSTRING(" "));
+ propertyEx.Append(property);
+ propertyEx.AppendLiteral(" ");
+ if (dontPreserveEx.Find(propertyEx) != -1) // -1 is not found
+ continue;
+
+ srcHdr->GetStringProperty(property.get(), getter_Copies(sourceString));
+ destHdr->SetStringProperty(property.get(), sourceString.get());
+ }
+
+ nsMsgLabelValue label = 0;
+ srcHdr->GetLabel(&label);
+ destHdr->SetLabel(label);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::EndCopy(bool aCopySucceeded)
+{
+ if (!mCopyState)
+ return NS_OK;
+
+ // we are the destination folder for a move/copy
+ nsresult rv = aCopySucceeded ? NS_OK : NS_ERROR_FAILURE;
+
+ if (!aCopySucceeded || mCopyState->m_writeFailed)
+ {
+ if (mCopyState->m_fileStream)
+ {
+ if (mCopyState->m_curDstKey != nsMsgKey_None)
+ mCopyState->m_msgStore->DiscardNewMessage(mCopyState->m_fileStream,
+ mCopyState->m_newHdr);
+ mCopyState->m_fileStream->Close();
+ }
+
+ if (!mCopyState->m_isMove)
+ {
+ // passing true because the messages that have been successfully
+ // copied have their corresponding hdrs in place. The message that has
+ // failed has been truncated so the msf file and berkeley mailbox
+ // are in sync.
+ (void) OnCopyCompleted(mCopyState->m_srcSupport, true);
+ // enable the dest folder
+ EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/); //dest folder doesn't need db batching
+ }
+ return NS_OK;
+ }
+
+ bool multipleCopiesFinished = (mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount);
+
+ RefPtr<nsLocalMoveCopyMsgTxn> localUndoTxn = mCopyState->m_undoMsgTxn;
+
+ NS_ASSERTION(mCopyState->m_leftOver == 0, "whoops, something wrong with previous copy");
+ mCopyState->m_leftOver = 0; // reset to 0.
+ // need to reset this in case we're move/copying multiple msgs.
+ mCopyState->m_fromLineSeen = false;
+
+ // flush the copied message. We need a close at the end to get the
+ // file size and time updated correctly.
+ //
+ // These filestream closes are handled inconsistently in the code. In some
+ // cases, this is done in EndMessage, while in others it is done here in
+ // EndCopy. When we do the close in EndMessage, we'll set
+ // mCopyState->m_fileStream to null since it is no longer needed, and detect
+ // here the null stream so we know that we don't have to close it here.
+ //
+ // Similarly, m_parseMsgState->GetNewMsgHdr() returns a null hdr if the hdr
+ // has already been processed by EndMessage so it is not doubly added here.
+
+ nsCOMPtr <nsISeekableStream> seekableStream(do_QueryInterface(mCopyState->m_fileStream));
+ if (seekableStream)
+ {
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+ rv = FinishNewLocalMessage(mCopyState->m_fileStream, mCopyState->m_newHdr,
+ mCopyState->m_msgStore,
+ mCopyState->m_parseMsgState);
+ if (NS_SUCCEEDED(rv) && mCopyState->m_newHdr)
+ mCopyState->m_newHdr->GetMessageKey(&mCopyState->m_curDstKey);
+ if (multipleCopiesFinished)
+ mCopyState->m_fileStream->Close();
+ else
+ mCopyState->m_fileStream->Flush();
+ }
+ //Copy the header to the new database
+ if (mCopyState->m_message)
+ {
+ // CopyMessages() goes here, and CopyFileMessages() with metadata to save;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ if (!mCopyState->m_parseMsgState)
+ {
+ if (mCopyState->m_destDB)
+ {
+ if (mCopyState->m_newHdr)
+ {
+ newHdr = mCopyState->m_newHdr;
+ CopyHdrPropertiesWithSkipList(newHdr, mCopyState->m_message,
+ NS_LITERAL_CSTRING("storeToken msgOffset"));
+// UpdateNewMsgHdr(mCopyState->m_message, newHdr);
+ // We need to copy more than just what UpdateNewMsgHdr does. In fact,
+ // I think we want to copy almost every property other than
+ // storeToken and msgOffset.
+ mCopyState->m_destDB->AddNewHdrToDB(newHdr, true);
+ }
+ else
+ {
+ rv = mCopyState->m_destDB->CopyHdrFromExistingHdr(mCopyState->m_curDstKey,
+ mCopyState->m_message, true,
+ getter_AddRefs(newHdr));
+ }
+ uint32_t newHdrFlags;
+ if (newHdr)
+ {
+ // turn off offline flag - it's not valid for local mail folders.
+ newHdr->AndFlags(~nsMsgMessageFlags::Offline, &newHdrFlags);
+ mCopyState->m_destMessages->AppendElement(newHdr, false);
+ }
+ }
+ // we can do undo with the dest folder db, see bug #198909
+ //else
+ // mCopyState->m_undoMsgTxn = nullptr; //null out the transaction because we can't undo w/o the msg db
+ }
+
+ // if we plan on allowing undo, (if we have a mCopyState->m_parseMsgState or not)
+ // we need to save the source and dest keys on the undo txn.
+ // see bug #179856 for details
+ bool isImap;
+ if (NS_SUCCEEDED(rv) && localUndoTxn) {
+ localUndoTxn->GetSrcIsImap(&isImap);
+ if (!isImap || !mCopyState->m_copyingMultipleMessages)
+ {
+ nsMsgKey aKey;
+ uint32_t statusOffset;
+ mCopyState->m_message->GetMessageKey(&aKey);
+ mCopyState->m_message->GetStatusOffset(&statusOffset);
+ localUndoTxn->AddSrcKey(aKey);
+ localUndoTxn->AddSrcStatusOffset(statusOffset);
+ localUndoTxn->AddDstKey(mCopyState->m_curDstKey);
+ }
+ }
+ }
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ // CopyFileMessage() and CopyMessages() from servers other than mailbox
+ if (mCopyState->m_parseMsgState)
+ {
+ nsCOMPtr<nsIMsgDatabase> msgDb;
+ mCopyState->m_parseMsgState->FinishHeader();
+ GetDatabaseWOReparse(getter_AddRefs(msgDb));
+ if (msgDb)
+ {
+ nsresult result = mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr));
+ // we need to copy newHdr because mCopyState will get cleared
+ // in OnCopyCompleted, but we need OnCopyCompleted to know about
+ // the newHdr, via mCopyState. And we send a notification about newHdr
+ // after OnCopyCompleted.
+ mCopyState->m_newHdr = newHdr;
+ if (NS_SUCCEEDED(result) && newHdr)
+ {
+ // Copy message metadata.
+ if (mCopyState->m_message)
+ {
+ // Propagate the new flag on an imap to local folder filter action
+ // Flags may get changed when deleting the original source message in
+ // IMAP. We have a copy of the original flags, but parseMsgState has
+ // already tried to decide what those flags should be. Who to believe?
+ // Let's only deal here with the flags that might get changed, Read
+ // and New, and trust upstream code for everything else.
+ uint32_t readAndNew = nsMsgMessageFlags::New | nsMsgMessageFlags::Read;
+ uint32_t newFlags;
+ newHdr->GetFlags(&newFlags);
+ newHdr->SetFlags( (newFlags & ~readAndNew) |
+ ((mCopyState->m_flags) & readAndNew));
+
+ // Copy other message properties.
+ CopyPropertiesToMsgHdr(newHdr, mCopyState->m_message, mCopyState->m_isMove);
+ }
+ msgDb->AddNewHdrToDB(newHdr, true);
+ if (localUndoTxn)
+ {
+ // ** jt - recording the message size for possible undo use; the
+ // message size is different for pop3 and imap4 messages
+ uint32_t msgSize;
+ newHdr->GetMessageSize(&msgSize);
+ localUndoTxn->AddDstMsgSize(msgSize);
+ }
+
+ mCopyState->m_destMessages->AppendElement(newHdr, false);
+ }
+ // msgDb->SetSummaryValid(true);
+ // msgDb->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ else
+ mCopyState->m_undoMsgTxn = nullptr; //null out the transaction because we can't undo w/o the msg db
+
+ mCopyState->m_parseMsgState->Clear();
+ if (mCopyState->m_listener) // CopyFileMessage() only
+ mCopyState->m_listener->SetMessageKey(mCopyState->m_curDstKey);
+ }
+
+ if (!multipleCopiesFinished && !mCopyState->m_copyingMultipleMessages)
+ {
+ // CopyMessages() goes here; CopyFileMessage() never gets in here because
+ // curCopyIndex will always be less than the mCopyState->m_totalMsgCount
+ nsCOMPtr<nsISupports> aSupport = do_QueryElementAt(mCopyState->m_messages, mCopyState->m_curCopyIndex);
+ rv = CopyMessageTo(aSupport, this, mCopyState->m_msgWindow, mCopyState->m_isMove);
+ }
+ else
+ {
+ // If we have some headers, then there is a source, so notify itemMoveCopyCompleted.
+ // If we don't have any headers already, (eg save as draft, send) then notify itemAdded.
+ // This notification is done after the messages are deleted, so that saving a new draft
+ // of a message works correctly -- first an itemDeleted is sent for the old draft, then
+ // an itemAdded for the new draft.
+ uint32_t numHdrs;
+ mCopyState->m_messages->GetLength(&numHdrs);
+
+ if (multipleCopiesFinished && numHdrs && !mCopyState->m_isFolder)
+ {
+ // we need to send this notification before we delete the source messages,
+ // because deleting the source messages clears out the src msg db hdr.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsMoveCopyCompleted(mCopyState->m_isMove,
+ mCopyState->m_messages,
+ this, mCopyState->m_destMessages);
+ }
+
+ if (!mCopyState->m_isMove)
+ {
+ if (multipleCopiesFinished)
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ srcFolder = do_QueryInterface(mCopyState->m_srcSupport);
+ if (mCopyState->m_isFolder)
+ CopyAllSubFolders(srcFolder, nullptr, nullptr); //Copy all subfolders then notify completion
+
+ if (mCopyState->m_msgWindow && mCopyState->m_undoMsgTxn)
+ {
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ mCopyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr)
+ txnMgr->DoTransaction(mCopyState->m_undoMsgTxn);
+ }
+
+ // enable the dest folder
+ EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/); //dest folder doesn't need db batching
+ if (srcFolder && !mCopyState->m_isFolder)
+ {
+ // I'm not too sure of the proper location of this event. It seems to need to be
+ // after the EnableNotifications, or the folder counts can be incorrect
+ // during the mDeleteOrMoveMsgCompletedAtom call.
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ }
+ (void) OnCopyCompleted(mCopyState->m_srcSupport, true);
+ }
+ }
+ // Send the itemAdded notification in case we didn't send the itemMoveCopyCompleted notification earlier.
+ // Posting news messages involves this, yet doesn't have the newHdr initialized, so don't send any
+ // notifications in that case.
+ if (!numHdrs && newHdr)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ {
+ notifier->NotifyMsgAdded(newHdr);
+ // We do not appear to trigger classification in this case, so let's
+ // paper over the abyss by just sending the classification notification.
+ nsCOMPtr <nsIMutableArray> oneHeaderArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+ oneHeaderArray->AppendElement(newHdr, false);
+ notifier->NotifyMsgsClassified(oneHeaderArray, false, false);
+ // (We do not add the NotReportedClassified processing flag since we
+ // just reported it!)
+ }
+ }
+ }
+ return rv;
+}
+
+static bool gGotGlobalPrefs;
+static bool gDeleteFromServerOnMove;
+
+bool nsMsgLocalMailFolder::GetDeleteFromServerOnMove()
+{
+ if (!gGotGlobalPrefs)
+ {
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (pPrefBranch)
+ {
+ pPrefBranch->GetBoolPref("mail.pop3.deleteFromServerOnMove", &gDeleteFromServerOnMove);
+ gGotGlobalPrefs = true;
+ }
+ }
+ return gDeleteFromServerOnMove;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::EndMove(bool moveSucceeded)
+{
+ nsresult rv;
+ if (!mCopyState)
+ return NS_OK;
+
+ if (!moveSucceeded || mCopyState->m_writeFailed)
+ {
+ //Notify that a completion finished.
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+
+ /* passing true because the messages that have been successfully copied have their corressponding
+ hdrs in place. The message that has failed has been truncated so the msf file and berkeley mailbox
+ are in sync*/
+
+ (void) OnCopyCompleted(mCopyState->m_srcSupport, true);
+ // enable the dest folder
+ EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/ ); //dest folder doesn't need db batching
+ return NS_OK;
+ }
+
+ if (mCopyState && mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount)
+ {
+ //Notify that a completion finished.
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgLocalMailFolder> localSrcFolder = do_QueryInterface(srcFolder);
+ if (localSrcFolder)
+ {
+ // if we are the trash and a local msg is being moved to us, mark the source
+ // for delete from server, if so configured.
+ if (mFlags & nsMsgFolderFlags::Trash)
+ {
+ // if we're deleting on all moves, we'll mark this message for deletion when
+ // we call DeleteMessages on the source folder. So don't mark it for deletion
+ // here, in that case.
+ if (!GetDeleteFromServerOnMove())
+ localSrcFolder->MarkMsgsOnPop3Server(mCopyState->m_messages, POP3_DELETE);
+ }
+ }
+ // lets delete these all at once - much faster that way
+ rv = srcFolder->DeleteMessages(mCopyState->m_messages, mCopyState->m_msgWindow, true, true, nullptr, mCopyState->m_allowUndo);
+ AutoCompact(mCopyState->m_msgWindow);
+
+ // enable the dest folder
+ EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/); //dest folder doesn't need db batching
+ // I'm not too sure of the proper location of this event. It seems to need to be
+ // after the EnableNotifications, or the folder counts can be incorrect
+ // during the mDeleteOrMoveMsgCompletedAtom call.
+ srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom : mDeleteOrMoveMsgFailedAtom);
+
+ if (NS_SUCCEEDED(rv) && mCopyState->m_msgWindow && mCopyState->m_undoMsgTxn)
+ {
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ mCopyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr)
+ txnMgr->DoTransaction(mCopyState->m_undoMsgTxn);
+ }
+ (void) OnCopyCompleted(mCopyState->m_srcSupport, NS_SUCCEEDED(rv) ? true : false); //clear the copy state so that the next message from a different folder can be move
+ }
+
+ return NS_OK;
+
+}
+
+// this is the beginning of the next message copied
+NS_IMETHODIMP nsMsgLocalMailFolder::StartMessage()
+{
+ // We get crashes that we don't understand (bug 284876), so stupidly prevent that.
+ NS_ENSURE_ARG_POINTER(mCopyState);
+ nsresult rv = InitCopyMsgHdrAndFileStream();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return WriteStartOfNewMessage();
+}
+
+// just finished the current message.
+NS_IMETHODIMP nsMsgLocalMailFolder::EndMessage(nsMsgKey key)
+{
+ NS_ENSURE_ARG_POINTER(mCopyState);
+
+ RefPtr<nsLocalMoveCopyMsgTxn> localUndoTxn = mCopyState->m_undoMsgTxn;
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsresult rv;
+
+ if (localUndoTxn)
+ {
+ localUndoTxn->GetMsgWindow(getter_AddRefs(msgWindow));
+ localUndoTxn->AddSrcKey(key);
+ localUndoTxn->AddDstKey(mCopyState->m_curDstKey);
+ }
+
+ // I think this is always true for online to offline copy
+ mCopyState->m_dummyEnvelopeNeeded = true;
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(mCopyState->m_fileStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+ rv = FinishNewLocalMessage(mCopyState->m_fileStream, mCopyState->m_newHdr,
+ mCopyState->m_msgStore,
+ mCopyState->m_parseMsgState);
+ mCopyState->m_fileStream->Close();
+ mCopyState->m_fileStream = nullptr; // all done with the file stream
+
+ // CopyFileMessage() and CopyMessages() from servers other than mailbox
+ if (mCopyState->m_parseMsgState)
+ {
+ nsCOMPtr<nsIMsgDatabase> msgDb;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ mCopyState->m_parseMsgState->FinishHeader();
+
+ rv = mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr));
+ if (NS_SUCCEEDED(rv) && newHdr)
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (srcDB)
+ {
+ nsCOMPtr <nsIMsgDBHdr> srcMsgHdr;
+ srcDB->GetMsgHdrForKey(key, getter_AddRefs(srcMsgHdr));
+ if (srcMsgHdr)
+ CopyPropertiesToMsgHdr(newHdr, srcMsgHdr, mCopyState->m_isMove);
+ }
+ rv = GetDatabaseWOReparse(getter_AddRefs(msgDb));
+ if (NS_SUCCEEDED(rv) && msgDb)
+ {
+ msgDb->AddNewHdrToDB(newHdr, true);
+ if (localUndoTxn)
+ {
+ // ** jt - recording the message size for possible undo use; the
+ // message size is different for pop3 and imap4 messages
+ uint32_t msgSize;
+ newHdr->GetMessageSize(&msgSize);
+ localUndoTxn->AddDstMsgSize(msgSize);
+ }
+ }
+ else
+ mCopyState->m_undoMsgTxn = nullptr; //null out the transaction because we can't undo w/o the msg db
+ }
+ mCopyState->m_parseMsgState->Clear();
+
+ if (mCopyState->m_listener) // CopyFileMessage() only
+ mCopyState->m_listener->SetMessageKey(mCopyState->m_curDstKey);
+ }
+
+ if (mCopyState->m_fileStream)
+ mCopyState->m_fileStream->Flush();
+ return NS_OK;
+}
+
+
+nsresult nsMsgLocalMailFolder::CopyMessagesTo(nsIArray *messages, nsTArray<nsMsgKey> &keyArray,
+ nsIMsgWindow *aMsgWindow, nsIMsgFolder *dstFolder,
+ bool isMove)
+{
+ if (!mCopyState)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+
+ nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsICopyMessageListener> copyListener(do_QueryInterface(dstFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+
+ nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(mCopyState->m_srcSupport, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+
+ rv = copyStreamListener->Init(srcFolder, copyListener, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!mCopyState->m_messageService)
+ {
+ nsCString uri;
+ srcFolder->GetURI(uri);
+ rv = GetMessageServiceFromURI(uri, getter_AddRefs(mCopyState->m_messageService));
+ }
+
+ if (NS_SUCCEEDED(rv) && mCopyState->m_messageService)
+ {
+ nsCOMPtr<nsIStreamListener> streamListener(do_QueryInterface(copyStreamListener, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+
+ mCopyState->m_curCopyIndex = 0;
+ // we need to kick off the first message - subsequent messages
+ // are kicked off by nsMailboxProtocol when it finishes a message
+ // before starting the next message. Only do this if the source folder
+ // is a local folder, however. IMAP will handle calling StartMessage for
+ // each message that gets downloaded, and news doesn't go through here
+ // because news only downloads one message at a time, and this routine
+ // is for multiple message copy.
+ nsCOMPtr <nsIMsgLocalMailFolder> srcLocalFolder = do_QueryInterface(srcFolder);
+ if (srcLocalFolder)
+ StartMessage();
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = mCopyState->m_messageService->CopyMessages(keyArray.Length(),
+ keyArray.Elements(),
+ srcFolder, streamListener,
+ isMove, nullptr, aMsgWindow,
+ getter_AddRefs(dummyNull));
+ }
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::CopyMessageTo(nsISupports *message,
+ nsIMsgFolder *dstFolder /* dst same as "this" */,
+ nsIMsgWindow *aMsgWindow,
+ bool isMove)
+{
+ if (!mCopyState)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryInterface(message, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+
+ mCopyState->m_message = do_QueryInterface(msgHdr, &rv);
+
+ nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(mCopyState->m_srcSupport, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+ nsCString uri;
+ srcFolder->GetUriForMsg(msgHdr, uri);
+
+ nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsICopyMessageListener> copyListener(do_QueryInterface(dstFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+
+ rv = copyStreamListener->Init(srcFolder, copyListener, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!mCopyState->m_messageService)
+ rv = GetMessageServiceFromURI(uri, getter_AddRefs(mCopyState->m_messageService));
+
+ if (NS_SUCCEEDED(rv) && mCopyState->m_messageService)
+ {
+ nsCOMPtr<nsIStreamListener> streamListener(do_QueryInterface(copyStreamListener, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = mCopyState->m_messageService->CopyMessage(uri.get(), streamListener, isMove, nullptr, aMsgWindow,
+ getter_AddRefs(dummyNull));
+ }
+ return rv;
+}
+
+// A message is being deleted from a POP3 mail file, so check and see if we have the message
+// being deleted in the server. If so, then we need to remove the message from the server as well.
+// We have saved the UIDL of the message in the popstate.dat file and we must match this uidl, so
+// read the message headers and see if we have it, then mark the message for deletion from the server.
+// The next time we look at mail the message will be deleted from the server.
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::MarkMsgsOnPop3Server(nsIArray *aMessages, int32_t aMark)
+{
+ nsLocalFolderScanState folderScanState;
+ nsCOMPtr<nsIPop3IncomingServer> curFolderPop3MailServer;
+ nsCOMArray<nsIPop3IncomingServer> pop3Servers; // servers with msgs deleted...
+
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ nsresult rv = GetServer(getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // I wonder if we should run through the pop3 accounts and see if any of them have
+ // leave on server set. If not, we could short-circuit some of this.
+
+ curFolderPop3MailServer = do_QueryInterface(incomingServer, &rv);
+ rv = GetFolderScanState(&folderScanState);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t srcCount;
+ aMessages->GetLength(&srcCount);
+
+ // Filter delete requests are always honored, others are subject
+ // to the deleteMailLeftOnServer preference.
+ int32_t mark;
+ mark = (aMark == POP3_FORCE_DEL) ? POP3_DELETE : aMark;
+
+ for (uint32_t i = 0; i < srcCount; i++)
+ {
+ /* get uidl for this message */
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr (do_QueryElementAt(aMessages, i, &rv));
+
+ uint32_t flags = 0;
+
+ if (msgDBHdr)
+ {
+ msgDBHdr->GetFlags(&flags);
+ nsCOMPtr <nsIPop3IncomingServer> msgPop3Server = curFolderPop3MailServer;
+ bool leaveOnServer = false;
+ bool deleteMailLeftOnServer = false;
+ // set up defaults, in case there's no x-mozilla-account header
+ if (curFolderPop3MailServer)
+ {
+ curFolderPop3MailServer->GetDeleteMailLeftOnServer(&deleteMailLeftOnServer);
+ curFolderPop3MailServer->GetLeaveMessagesOnServer(&leaveOnServer);
+ }
+
+ rv = GetUidlFromFolder(&folderScanState, msgDBHdr);
+ if (!NS_SUCCEEDED(rv))
+ continue;
+
+ if (folderScanState.m_uidl)
+ {
+ nsCOMPtr <nsIMsgAccount> account;
+ rv = accountManager->GetAccount(folderScanState.m_accountKey, getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account)
+ {
+ account->GetIncomingServer(getter_AddRefs(incomingServer));
+ nsCOMPtr<nsIPop3IncomingServer> curMsgPop3MailServer = do_QueryInterface(incomingServer);
+ if (curMsgPop3MailServer)
+ {
+ msgPop3Server = curMsgPop3MailServer;
+ msgPop3Server->GetDeleteMailLeftOnServer(&deleteMailLeftOnServer);
+ msgPop3Server->GetLeaveMessagesOnServer(&leaveOnServer);
+ }
+ }
+ }
+ // ignore this header if not partial and leaveOnServer not set...
+ // or if we can't find the pop3 server.
+ if (!msgPop3Server || (! (flags & nsMsgMessageFlags::Partial) && !leaveOnServer))
+ continue;
+ // if marking deleted, ignore header if we're not deleting from
+ // server when deleting locally.
+ if (aMark == POP3_DELETE && leaveOnServer && !deleteMailLeftOnServer)
+ continue;
+ if (folderScanState.m_uidl)
+ {
+ msgPop3Server->AddUidlToMark(folderScanState.m_uidl, mark);
+ // remember this pop server in list of servers with msgs deleted
+ if (pop3Servers.IndexOfObject(msgPop3Server) == -1)
+ pop3Servers.AppendObject(msgPop3Server);
+ }
+ }
+ }
+ if (folderScanState.m_inputStream)
+ folderScanState.m_inputStream->Close();
+ // need to do this for all pop3 mail servers that had messages deleted.
+ uint32_t serverCount = pop3Servers.Count();
+ for (uint32_t index = 0; index < serverCount; index++)
+ pop3Servers[index]->MarkMessages();
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::DeleteDownloadMsg(nsIMsgDBHdr *aMsgHdr, bool *aDoSelect)
+{
+ uint32_t numMsgs;
+ char *newMsgId;
+
+ // This method is only invoked thru DownloadMessagesForOffline()
+ if (mDownloadState != DOWNLOAD_STATE_NONE)
+ {
+ // We only remember the first key, no matter how many
+ // messages were originally selected.
+ if (mDownloadState == DOWNLOAD_STATE_INITED)
+ {
+ aMsgHdr->GetMessageKey(&mDownloadSelectKey);
+ mDownloadState = DOWNLOAD_STATE_GOTMSG;
+ }
+
+ aMsgHdr->GetMessageId(&newMsgId);
+
+ // Walk through all the selected headers, looking for a matching
+ // Message-ID.
+ numMsgs = mDownloadMessages.Length();
+ for (uint32_t i = 0; i < numMsgs; i++)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr = mDownloadMessages[i];
+ char *oldMsgId = nullptr;
+ msgDBHdr->GetMessageId(&oldMsgId);
+
+ // Delete the first match and remove it from the array
+ if (!PL_strcmp(newMsgId, oldMsgId))
+ {
+ rv = GetDatabase();
+ if (!mDatabase)
+ return rv;
+
+ UpdateNewMsgHdr(msgDBHdr, aMsgHdr);
+
+#if DOWNLOAD_NOTIFY_STYLE == DOWNLOAD_NOTIFY_LAST
+ msgDBHdr->GetMessageKey(&mDownloadOldKey);
+ msgDBHdr->GetThreadParent(&mDownloadOldParent);
+ msgDBHdr->GetFlags(&mDownloadOldFlags);
+ mDatabase->DeleteHeader(msgDBHdr, nullptr, false, false);
+ // Tell caller we want to select this message
+ if (aDoSelect)
+ *aDoSelect = true;
+#else
+ mDatabase->DeleteHeader(msgDBHdr, nullptr, false, true);
+ // Tell caller we want to select this message
+ if (aDoSelect && mDownloadState == DOWNLOAD_STATE_GOTMSG)
+ *aDoSelect = true;
+#endif
+ mDownloadMessages.RemoveElementAt(i);
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::SelectDownloadMsg()
+{
+#if DOWNLOAD_NOTIFY_STYLE == DOWNLOAD_NOTIFY_LAST
+ if (mDownloadState >= DOWNLOAD_STATE_GOTMSG)
+ {
+ nsresult rv = GetDatabase();
+ if (!mDatabase)
+ return rv;
+ }
+ mDatabase->NotifyKeyDeletedAll(mDownloadOldKey, mDownloadOldParent, mDownloadOldFlags, nullptr);
+ }
+#endif
+
+ if (mDownloadState == DOWNLOAD_STATE_GOTMSG && mDownloadWindow)
+ {
+ nsAutoCString newuri;
+ nsBuildLocalMessageURI(mBaseMessageURI.get(), mDownloadSelectKey, newuri);
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ mDownloadWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectMessage(newuri);
+ mDownloadState = DOWNLOAD_STATE_DIDSEL;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::DownloadMessagesForOffline(nsIArray *aMessages, nsIMsgWindow *aWindow)
+{
+ if (mDownloadState != DOWNLOAD_STATE_NONE)
+ return NS_ERROR_FAILURE; // already has a download in progress
+
+ // We're starting a download...
+ mDownloadState = DOWNLOAD_STATE_INITED;
+
+ MarkMsgsOnPop3Server(aMessages, POP3_FETCH_BODY);
+
+ // Pull out all the PARTIAL messages into a new array
+ uint32_t srcCount;
+ aMessages->GetLength(&srcCount);
+
+ nsresult rv;
+ for (uint32_t i = 0; i < srcCount; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr (do_QueryElementAt(aMessages, i, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t flags = 0;
+ msgDBHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial)
+ mDownloadMessages.AppendElement(msgDBHdr);
+ }
+ }
+ mDownloadWindow = aWindow;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ nsCOMPtr<nsILocalMailIncomingServer> localMailServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+ return localMailServer->GetNewMail(aWindow, this, this, nullptr);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::NotifyDelete()
+{
+ NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ return NS_OK;
+}
+
+// TODO: once we move certain code into the IncomingServer (search for TODO)
+// this method will go away.
+// sometimes this gets called when we don't have the server yet, so
+// that's why we're not calling GetServer()
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetIncomingServerType(nsACString& aServerType)
+{
+ nsresult rv;
+ if (mType.IsEmpty())
+ {
+ nsCOMPtr<nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = url->SetSpec(mURI);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // try "none" first
+ url->SetScheme(NS_LITERAL_CSTRING("none"));
+ rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ mType.AssignLiteral("none");
+ else
+ {
+ // next try "pop3"
+ url->SetScheme(NS_LITERAL_CSTRING("pop3"));
+ rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ mType.AssignLiteral("pop3");
+ else
+ {
+ // next try "rss"
+ url->SetScheme(NS_LITERAL_CSTRING("rss"));
+ rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ mType.AssignLiteral("rss");
+ else
+ {
+#ifdef HAVE_MOVEMAIL
+ // next try "movemail"
+ url->SetScheme(NS_LITERAL_CSTRING("movemail"));
+ rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ mType.AssignLiteral("movemail");
+#endif /* HAVE_MOVEMAIL */
+ }
+ }
+ }
+ }
+ aServerType = mType;
+ return NS_OK;
+}
+
+nsresult nsMsgLocalMailFolder::CreateBaseMessageURI(const nsACString& aURI)
+{
+ return nsCreateLocalBaseMessageURI(aURI, mBaseMessageURI);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::OnStartRunningUrl(nsIURI * aUrl)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(aUrl, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString aSpec;
+ rv = aUrl->GetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (strstr(aSpec.get(), "uidl="))
+ {
+ nsCOMPtr<nsIPop3Sink> popsink;
+ rv = popurl->GetPop3Sink(getter_AddRefs(popsink));
+ if (NS_SUCCEEDED(rv))
+ {
+ popsink->SetBaseMessageUri(mBaseMessageURI.get());
+ nsCString messageuri;
+ popurl->GetMessageUri(getter_Copies(messageuri));
+ popsink->SetOrigMessageUri(messageuri);
+ }
+ }
+ }
+ return nsMsgDBFolder::OnStartRunningUrl(aUrl);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode)
+{
+ // If we just finished a DownloadMessages call, reset...
+ if (mDownloadState != DOWNLOAD_STATE_NONE)
+ {
+ mDownloadState = DOWNLOAD_STATE_NONE;
+ mDownloadMessages.Clear();
+ mDownloadWindow = nullptr;
+ return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
+ }
+
+ nsresult rv;
+ if (NS_SUCCEEDED(aExitCode))
+ {
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ nsAutoCString aSpec;
+ if (aUrl) {
+ rv = aUrl->GetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (strstr(aSpec.get(), "uidl="))
+ {
+ nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(aUrl, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString messageuri;
+ rv = popurl->GetMessageUri(getter_Copies(messageuri));
+ if (NS_SUCCEEDED(rv))
+ {
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr;
+ rv = GetMsgDBHdrFromURI(messageuri.get(), getter_AddRefs(msgDBHdr));
+ if (NS_SUCCEEDED(rv))
+ {
+ GetDatabase();
+ if (mDatabase)
+ mDatabase->DeleteHeader(msgDBHdr, nullptr, true, true);
+ }
+
+ nsCOMPtr<nsIPop3Sink> pop3sink;
+ nsCString newMessageUri;
+ rv = popurl->GetPop3Sink(getter_AddRefs(pop3sink));
+ if (NS_SUCCEEDED(rv))
+ {
+ pop3sink->GetMessageUri(getter_Copies(newMessageUri));
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ msgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectMessage(newMessageUri);
+ }
+ }
+ }
+ }
+ }
+
+ if (mFlags & nsMsgFolderFlags::Inbox)
+ {
+ if (mDatabase && mCheckForNewMessagesAfterParsing)
+ {
+ bool valid = false; // GetSummaryValid may return without setting valid.
+ mDatabase->GetSummaryValid(&valid);
+ if (valid && msgWindow)
+ rv = GetNewMessages(msgWindow, nullptr);
+ mCheckForNewMessagesAfterParsing = false;
+ }
+ }
+ }
+
+ if (m_parsingFolder)
+ {
+ // Clear this before calling OnStopRunningUrl, in case the url listener
+ // tries to get the database.
+ m_parsingFolder = false;
+
+ // TODO: Updating the size should be pushed down into the msg store backend
+ // so that the size is recalculated as part of parsing the folder data
+ // (important for maildir), once GetSizeOnDisk is pushed into the msgStores
+ // (bug 1032360).
+ (void)RefreshSizeOnDisk();
+
+ // Update the summary totals so the front end will
+ // show the right thing.
+ UpdateSummaryTotals(true);
+
+ if (mReparseListener)
+ {
+ nsCOMPtr<nsIUrlListener> saveReparseListener = mReparseListener;
+ mReparseListener = nullptr;
+ saveReparseListener->OnStopRunningUrl(aUrl, aExitCode);
+ }
+ }
+ if (mFlags & nsMsgFolderFlags::Inbox)
+ {
+ // if we are the inbox and running pop url
+ nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(aUrl, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ GetServer(getter_AddRefs(server));
+ // this is the deferred to account, in the global inbox case
+ if (server)
+ server->SetPerformingBiff(false); //biff is over
+ }
+ }
+ return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
+}
+
+nsresult nsMsgLocalMailFolder::DisplayMoveCopyStatusMsg()
+{
+ nsresult rv = NS_OK;
+ if (mCopyState)
+ {
+ if (!mCopyState->m_statusFeedback)
+ {
+ // get msgWindow from undo txn
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ if (mCopyState->m_undoMsgTxn)
+ mCopyState->m_undoMsgTxn->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (!msgWindow)
+ return NS_OK; // not a fatal error.
+
+ msgWindow->GetStatusFeedback(getter_AddRefs(mCopyState->m_statusFeedback));
+ }
+
+ if (!mCopyState->m_stringBundle)
+ {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(mCopyState->m_stringBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (mCopyState->m_statusFeedback && mCopyState->m_stringBundle)
+ {
+ nsString folderName;
+ GetName(folderName);
+ nsAutoString numMsgSoFarString;
+ numMsgSoFarString.AppendInt((mCopyState->m_copyingMultipleMessages) ? mCopyState->m_curCopyIndex : 1);
+
+ nsAutoString totalMessagesString;
+ totalMessagesString.AppendInt(mCopyState->m_totalMsgCount);
+ nsString finalString;
+ const char16_t * stringArray[] = { numMsgSoFarString.get(), totalMessagesString.get(), folderName.get() };
+ rv = mCopyState->m_stringBundle->FormatStringFromName(
+ (mCopyState->m_isMove) ?
+ u"movingMessagesStatus" :
+ u"copyingMessagesStatus",
+ stringArray, 3, getter_Copies(finalString));
+ int64_t nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
+
+ // only update status/progress every half second
+ if (nowMS - mCopyState->m_lastProgressTime < 500 &&
+ mCopyState->m_curCopyIndex < mCopyState->m_totalMsgCount)
+ return NS_OK;
+
+ mCopyState->m_lastProgressTime = nowMS;
+ mCopyState->m_statusFeedback->ShowStatusString(finalString);
+ mCopyState->m_statusFeedback->ShowProgress(mCopyState->m_curCopyIndex * 100 / mCopyState->m_totalMsgCount);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::SetFlagsOnDefaultMailboxes(uint32_t flags)
+{
+ if (flags & nsMsgFolderFlags::Inbox)
+ setSubfolderFlag(NS_LITERAL_STRING("Inbox"), nsMsgFolderFlags::Inbox);
+
+ if (flags & nsMsgFolderFlags::SentMail)
+ setSubfolderFlag(NS_LITERAL_STRING("Sent"), nsMsgFolderFlags::SentMail);
+
+ if (flags & nsMsgFolderFlags::Drafts)
+ setSubfolderFlag(NS_LITERAL_STRING("Drafts"), nsMsgFolderFlags::Drafts);
+
+ if (flags & nsMsgFolderFlags::Templates)
+ setSubfolderFlag(NS_LITERAL_STRING("Templates"), nsMsgFolderFlags::Templates);
+
+ if (flags & nsMsgFolderFlags::Trash)
+ setSubfolderFlag(NS_LITERAL_STRING("Trash"), nsMsgFolderFlags::Trash);
+
+ if (flags & nsMsgFolderFlags::Queue)
+ setSubfolderFlag(NS_LITERAL_STRING("Unsent Messages"), nsMsgFolderFlags::Queue);
+
+ if (flags & nsMsgFolderFlags::Junk)
+ setSubfolderFlag(NS_LITERAL_STRING("Junk"), nsMsgFolderFlags::Junk);
+
+ if (flags & nsMsgFolderFlags::Archive)
+ setSubfolderFlag(NS_LITERAL_STRING("Archives"), nsMsgFolderFlags::Archive);
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgLocalMailFolder::setSubfolderFlag(const nsAString& aFolderName, uint32_t flags)
+{
+ // FindSubFolder() expects the folder name to be escaped
+ // see bug #192043
+ nsAutoCString escapedFolderName;
+ nsresult rv = NS_MsgEscapeEncodeURLPath(aFolderName, escapedFolderName);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = FindSubFolder(escapedFolderName, getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we only want to do this if the folder *really* exists,
+ // so check if it has a parent. Otherwise, we'll create the
+ // .msf file when we don't want to.
+ nsCOMPtr <nsIMsgFolder> parent;
+ msgFolder->GetParent(getter_AddRefs(parent));
+ if (!parent)
+ return NS_ERROR_FAILURE;
+
+ rv = msgFolder->SetFlag(flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgFolder->SetPrettyName(aFolderName);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetCheckForNewMessagesAfterParsing(bool *aCheckForNewMessagesAfterParsing)
+{
+ NS_ENSURE_ARG_POINTER(aCheckForNewMessagesAfterParsing);
+ *aCheckForNewMessagesAfterParsing = mCheckForNewMessagesAfterParsing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::SetCheckForNewMessagesAfterParsing(bool aCheckForNewMessagesAfterParsing)
+{
+ mCheckForNewMessagesAfterParsing = aCheckForNewMessagesAfterParsing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::NotifyCompactCompleted()
+{
+ mExpungedBytes = 0;
+ m_newMsgs.Clear(); // if compacted, m_newMsgs probably aren't valid.
+ // if compacted, processing flags probably also aren't valid.
+ ClearProcessingFlags();
+ (void) RefreshSizeOnDisk();
+ (void) CloseDBIfFolderNotOpen();
+ nsCOMPtr <nsIAtom> compactCompletedAtom;
+ compactCompletedAtom = MsgGetAtom("CompactCompleted");
+ NotifyFolderEvent(compactCompletedAtom);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::Shutdown(bool shutdownChildren)
+{
+ mInitialized = false;
+ return nsMsgDBFolder::Shutdown(shutdownChildren);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::OnMessageClassified(const char *aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent)
+
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString spamFolderURI;
+ rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aMsgURI) // not end of batch
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ rv = msgHdr->GetMessageKey(&msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if this message needs junk classification
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+
+ if (processingFlags & nsMsgProcessingFlags::ClassifyJunk)
+ {
+ nsMsgDBFolder::OnMessageClassified(aMsgURI, aClassification, aJunkPercent);
+
+ if (aClassification == nsIJunkMailPlugin::JUNK)
+ {
+ bool willMoveMessage = false;
+
+ // don't do the move when we are opening up
+ // the junk mail folder or the trash folder
+ // or when manually classifying messages in those folders
+ if (!(mFlags & nsMsgFolderFlags::Junk || mFlags & nsMsgFolderFlags::Trash))
+ {
+ bool moveOnSpam = false;
+ rv = spamSettings->GetMoveOnSpam(&moveOnSpam);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (moveOnSpam)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetExistingFolder(spamFolderURI, getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder)
+ {
+ rv = folder->SetFlag(nsMsgFolderFlags::Junk);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mSpamKeysToMove.AppendElement(msgKey);
+ willMoveMessage = true;
+ }
+ else
+ {
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // the listener should do
+ // rv = folder->SetFlag(nsMsgFolderFlags::Junk);
+ // NS_ENSURE_SUCCESS(rv,rv);
+ // mSpamKeysToMove.AppendElement(msgKey);
+ // willMoveMessage = true;
+ rv = GetOrCreateFolder(spamFolderURI, nullptr /* aListener */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateFolder failed");
+ }
+ }
+ }
+ rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ }
+
+ else // end of batch
+ {
+ // Parent will apply post bayes filters.
+ nsMsgDBFolder::OnMessageClassified(nullptr, nsIJunkMailPlugin::UNCLASSIFIED, 0);
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ if (!mSpamKeysToMove.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ if (!spamFolderURI.IsEmpty())
+ rv = GetExistingFolder(spamFolderURI, getter_AddRefs(folder));
+ for (uint32_t keyIndex = 0; keyIndex < mSpamKeysToMove.Length(); keyIndex++)
+ {
+ // If an upstream filter moved this message, don't move it here.
+ nsMsgKey msgKey = mSpamKeysToMove.ElementAt(keyIndex);
+ nsMsgProcessingFlagType processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+ if (folder && !(processingFlags & nsMsgProcessingFlags::FilterToMove))
+ {
+ nsCOMPtr<nsIMsgDBHdr> mailHdr;
+ rv = GetMessageHeader(msgKey, getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr)
+ messages->AppendElement(mailHdr, false);
+ }
+ else
+ {
+ // We don't need the processing flag any more.
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::FilterToMove);
+ }
+ }
+
+ if (folder)
+ {
+ nsCOMPtr<nsIMsgCopyService> copySvc = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = copySvc->CopyMessages(this, messages, folder, true,
+ /*nsIMsgCopyServiceListener* listener*/ nullptr, nullptr, false /*allowUndo*/);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CopyMessages failed");
+ if (NS_FAILED(rv))
+ {
+ nsAutoCString logMsg("failed to copy junk messages to junk folder rv = ");
+ logMsg.AppendInt(static_cast<uint32_t>(rv), 16);
+ spamSettings->LogJunkString(logMsg.get());
+ }
+ }
+ }
+ int32_t numNewMessages;
+ GetNumNewMessages(false, &numNewMessages);
+ uint32_t length;
+ messages->GetLength(&length);
+ SetNumNewMessages(numNewMessages - length);
+ mSpamKeysToMove.Clear();
+ // check if this is the inbox first...
+ if (mFlags & nsMsgFolderFlags::Inbox)
+ PerformBiffNotifications();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetFolderScanState(nsLocalFolderScanState *aState)
+{
+ NS_ENSURE_ARG_POINTER(aState);
+
+ nsresult rv = GetMsgStore(getter_AddRefs(aState->m_msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ aState->m_uidl = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetUidlFromFolder(nsLocalFolderScanState *aState, nsIMsgDBHdr *aMsgDBHdr)
+{
+ bool more = false;
+ uint32_t size = 0, len = 0;
+ const char *accountKey = nullptr;
+ nsresult rv = GetMsgInputStream(aMsgDBHdr, &aState->m_streamReusable,
+ getter_AddRefs(aState->m_inputStream));
+ aState->m_seekableStream = do_QueryInterface(aState->m_inputStream);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>);
+ NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ aState->m_uidl = nullptr;
+
+ aMsgDBHdr->GetMessageSize(&len);
+ while (len > 0)
+ {
+ rv = NS_ReadLine(aState->m_inputStream.get(), lineBuffer.get(), aState->m_header, &more);
+ if (NS_SUCCEEDED(rv))
+ {
+ size = aState->m_header.Length();
+ if (!size)
+ break;
+ // this isn't quite right - need to account for line endings
+ len -= size;
+ // account key header will always be before X_UIDL header
+ if (!accountKey)
+ {
+ accountKey = strstr(aState->m_header.get(), HEADER_X_MOZILLA_ACCOUNT_KEY);
+ if (accountKey)
+ {
+ accountKey += strlen(HEADER_X_MOZILLA_ACCOUNT_KEY) + 2;
+ aState->m_accountKey = accountKey;
+ }
+ }
+ else
+ {
+ aState->m_uidl = strstr(aState->m_header.get(), X_UIDL);
+ if (aState->m_uidl)
+ {
+ aState->m_uidl += X_UIDL_LEN + 2; // skip UIDL: header
+ break;
+ }
+ }
+ }
+ }
+ if (!aState->m_streamReusable)
+ {
+ aState->m_inputStream->Close();
+ aState->m_inputStream = nullptr;
+ }
+ lineBuffer = nullptr;
+ return rv;
+}
+
+/**
+ * Adds a message to the end of the folder, parsing it as it goes, and
+ * applying filters, if applicable.
+ */
+NS_IMETHODIMP
+nsMsgLocalMailFolder::AddMessage(const char *aMessage, nsIMsgDBHdr **aHdr)
+{
+ const char *aMessages[] = {aMessage};
+ nsCOMPtr<nsIArray> hdrs;
+ nsresult rv = AddMessageBatch(1, aMessages, getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBHdr> hdr(do_QueryElementAt(hdrs, 0, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ hdr.forget(aHdr);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::AddMessageBatch(uint32_t aMessageCount,
+ const char **aMessages,
+ nsIArray **aHdrArray)
+{
+ NS_ENSURE_ARG_POINTER(aHdrArray);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsCOMPtr <nsIOutputStream> outFileStream;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ rv = server->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isLocked;
+
+ GetLocked(&isLocked);
+ if (isLocked)
+ return NS_MSG_FOLDER_BUSY;
+
+ AcquireSemaphore(static_cast<nsIMsgLocalMailFolder*>(this));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMutableArray> hdrArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < aMessageCount; i++)
+ {
+ RefPtr<nsParseNewMailState> newMailParser = new nsParseNewMailState;
+ NS_ENSURE_TRUE(newMailParser, NS_ERROR_OUT_OF_MEMORY);
+ if (!mGettingNewMessages)
+ newMailParser->DisableFilters();
+ bool reusable;
+ rv = msgStore->GetNewMsgOutputStream(this, getter_AddRefs(newHdr),
+ &reusable,
+ getter_AddRefs(outFileStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get a msgWindow. Proceed without one, but filter actions to imap folders
+ // will silently fail if not signed in and no window for a prompt.
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+
+ rv = newMailParser->Init(rootFolder, this,
+ msgWindow, newHdr, outFileStream);
+
+ uint32_t bytesWritten, messageLen = strlen(aMessages[i]);
+ outFileStream->Write(aMessages[i], messageLen, &bytesWritten);
+ newMailParser->BufferInput(aMessages[i], messageLen);
+
+ FinishNewLocalMessage(outFileStream, newHdr, msgStore, newMailParser);
+ outFileStream->Close();
+ outFileStream = nullptr;
+ newMailParser->OnStopRequest(nullptr, nullptr, NS_OK);
+ newMailParser->EndMsgDownload();
+ hdrArray->AppendElement(newHdr, false);
+ }
+ NS_ADDREF(*aHdrArray = hdrArray);
+ }
+ ReleaseSemaphore(static_cast<nsIMsgLocalMailFolder*>(this));
+ return rv;
+}
+
+nsresult
+nsMsgLocalMailFolder::FinishNewLocalMessage(nsIOutputStream *aOutputStream,
+ nsIMsgDBHdr *aNewHdr,
+ nsIMsgPluggableStore *aMsgStore,
+ nsParseMailMessageState *aParseMsgState)
+{
+ uint32_t bytesWritten;
+ aOutputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten);
+ if (aParseMsgState)
+ aParseMsgState->ParseAFolderLine(MSG_LINEBREAK, MSG_LINEBREAK_LEN);
+ return aMsgStore->FinishNewMessage(aOutputStream, aNewHdr);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::WarnIfLocalFileTooBig(nsIMsgWindow *aWindow,
+ int64_t aSpaceRequested,
+ bool *aTooBig)
+{
+ NS_ENSURE_ARG_POINTER(aTooBig);
+
+ *aTooBig = true;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool spaceAvailable = false;
+ // check if we have a reasonable amount of space left
+ rv = msgStore->HasSpaceAvailable(this, aSpaceRequested, &spaceAvailable);
+ if (NS_SUCCEEDED(rv) && spaceAvailable) {
+ *aTooBig = false;
+ } else if (rv == NS_ERROR_FILE_TOO_BIG) {
+ ThrowAlertMsg("mailboxTooLarge", aWindow);
+ } else {
+ ThrowAlertMsg("outOfDiskSpace", aWindow);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::FetchMsgPreviewText(nsMsgKey *aKeysToFetch, uint32_t aNumKeys,
+ bool aLocalOnly, nsIUrlListener *aUrlListener,
+ bool *aAsyncResults)
+{
+ NS_ENSURE_ARG_POINTER(aKeysToFetch);
+ NS_ENSURE_ARG_POINTER(aAsyncResults);
+
+ *aAsyncResults = false;
+ nsCOMPtr <nsIInputStream> inputStream;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < aNumKeys; i++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsCString prevBody;
+ rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ignore messages that already have a preview body.
+ msgHdr->GetStringProperty("preview", getter_Copies(prevBody));
+ if (!prevBody.IsEmpty())
+ continue;
+
+ bool reusable;
+ rv = GetMsgInputStream(msgHdr, &reusable, getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = GetMsgPreviewTextFromStream(msgHdr, inputStream);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords)
+{
+ return ChangeKeywordForMessages(aMessages, aKeywords, true /* add */);
+}
+nsresult nsMsgLocalMailFolder::ChangeKeywordForMessages(nsIArray *aMessages, const nsACString& aKeywords, bool add)
+{
+ nsresult rv = (add) ? nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords)
+ : nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->ChangeKeywords(aMessages, aKeywords, add);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords)
+{
+ return ChangeKeywordForMessages(aMessages, aKeywords, false /* remove */);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::UpdateNewMsgHdr(nsIMsgDBHdr* aOldHdr, nsIMsgDBHdr* aNewHdr)
+{
+ NS_ENSURE_ARG_POINTER(aOldHdr);
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+ // Preserve any properties set on the message.
+ CopyPropertiesToMsgHdr(aNewHdr, aOldHdr, true);
+
+ // Preserve keywords manually, since they are set as don't preserve.
+ nsCString keywordString;
+ aOldHdr->GetStringProperty("keywords", getter_Copies(keywordString));
+ aNewHdr->SetStringProperty("keywords", keywordString.get());
+
+ // If the junk score was set by the plugin, remove junkscore to force a new
+ // junk analysis, this time using the body.
+ nsCString junkScoreOrigin;
+ aOldHdr->GetStringProperty("junkscoreorigin", getter_Copies(junkScoreOrigin));
+ if (junkScoreOrigin.EqualsLiteral("plugin"))
+ aNewHdr->SetStringProperty("junkscore", "");
+
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsLocalMailFolder.h b/mailnews/local/src/nsLocalMailFolder.h
new file mode 100644
index 000000000..1ed964b16
--- /dev/null
+++ b/mailnews/local/src/nsLocalMailFolder.h
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ Interface for representing Local Mail folders.
+*/
+
+#ifndef nsMsgLocalMailFolder_h__
+#define nsMsgLocalMailFolder_h__
+
+#include "mozilla/Attributes.h"
+#include "nsMsgDBFolder.h" /* include the interface we are going to support */
+#include "nsAutoPtr.h"
+#include "nsICopyMessageListener.h"
+#include "nsIFileStreams.h"
+#include "nsIPop3IncomingServer.h" // need this for an interface ID
+#include "nsMsgTxn.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgParseMailMsgState.h"
+#include "nsITransactionManager.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsISeekableStream.h"
+#include "nsIMutableArray.h"
+#include "nsLocalUndoTxn.h"
+
+#define COPY_BUFFER_SIZE 16384
+
+class nsParseMailMessageState;
+
+struct nsLocalMailCopyState
+{
+ nsLocalMailCopyState();
+ virtual ~nsLocalMailCopyState();
+
+ nsCOMPtr <nsIOutputStream> m_fileStream;
+ nsCOMPtr<nsIMsgPluggableStore> m_msgStore;
+ nsCOMPtr<nsISupports> m_srcSupport;
+ /// Source nsIMsgDBHdr instances.
+ nsCOMPtr<nsIArray> m_messages;
+ /// Destination nsIMsgDBHdr instances.
+ nsCOMPtr<nsIMutableArray> m_destMessages;
+ RefPtr<nsLocalMoveCopyMsgTxn> m_undoMsgTxn;
+ nsCOMPtr<nsIMsgDBHdr> m_message; // current copy message
+ nsMsgMessageFlagType m_flags; // current copy message flags
+ RefPtr<nsParseMailMessageState> m_parseMsgState;
+ nsCOMPtr<nsIMsgCopyServiceListener> m_listener;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ nsCOMPtr<nsIMsgDatabase> m_destDB;
+
+ // for displaying status;
+ nsCOMPtr <nsIMsgStatusFeedback> m_statusFeedback;
+ nsCOMPtr <nsIStringBundle> m_stringBundle;
+ int64_t m_lastProgressTime;
+
+ nsMsgKey m_curDstKey;
+ uint32_t m_curCopyIndex;
+ nsCOMPtr <nsIMsgMessageService> m_messageService;
+ /// The number of messages in m_messages.
+ uint32_t m_totalMsgCount;
+ char *m_dataBuffer;
+ uint32_t m_dataBufferSize;
+ uint32_t m_leftOver;
+ bool m_isMove;
+ bool m_isFolder; // isFolder move/copy
+ bool m_dummyEnvelopeNeeded;
+ bool m_copyingMultipleMessages;
+ bool m_fromLineSeen;
+ bool m_allowUndo;
+ bool m_writeFailed;
+ bool m_notifyFolderLoaded;
+ bool m_wholeMsgInStream;
+ nsCString m_newMsgKeywords;
+ nsCOMPtr <nsIMsgDBHdr> m_newHdr;
+};
+
+struct nsLocalFolderScanState
+{
+ nsLocalFolderScanState();
+ ~nsLocalFolderScanState();
+
+ nsCOMPtr<nsIInputStream> m_inputStream;
+ nsCOMPtr<nsISeekableStream> m_seekableStream;
+ nsCOMPtr<nsIMsgPluggableStore> m_msgStore;
+ nsCString m_header;
+ nsCString m_accountKey;
+ const char *m_uidl; // memory is owned by m_header
+ // false if we need a new input stream for each message
+ bool m_streamReusable;
+};
+
+class nsMsgLocalMailFolder : public nsMsgDBFolder,
+ public nsIMsgLocalMailFolder,
+ public nsICopyMessageListener
+{
+public:
+ nsMsgLocalMailFolder(void);
+ NS_DECL_NSICOPYMESSAGELISTENER
+ NS_DECL_NSIMSGLOCALMAILFOLDER
+ NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER
+ NS_DECL_ISUPPORTS_INHERITED
+ // nsIRDFResource methods:
+ NS_IMETHOD Init(const char *aURI) override;
+
+ // nsIUrlListener methods
+ NS_IMETHOD OnStartRunningUrl(nsIURI * aUrl) override;
+ NS_IMETHOD OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode) override;
+
+ // nsIMsgFolder methods:
+ NS_IMETHOD GetSubFolders(nsISimpleEnumerator* *aResult) override;
+ NS_IMETHOD GetMsgDatabase(nsIMsgDatabase **aMsgDatabase) override;
+
+ NS_IMETHOD OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) override;
+ NS_IMETHOD GetMessages(nsISimpleEnumerator **result) override;
+ NS_IMETHOD UpdateFolder(nsIMsgWindow *aWindow) override;
+
+ NS_IMETHOD CreateSubfolder(const nsAString& folderName ,nsIMsgWindow *msgWindow) override;
+
+ NS_IMETHOD Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD CompactAll(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow, bool aCompactOfflineAlso) override;
+ NS_IMETHOD EmptyTrash(nsIMsgWindow *msgWindow, nsIUrlListener *aListener) override;
+ NS_IMETHOD DeleteSubFolders(nsIArray *folders, nsIMsgWindow *msgWindow) override;
+ NS_IMETHOD CreateStorageIfMissing(nsIUrlListener* urlListener) override;
+ NS_IMETHOD Rename (const nsAString& aNewName, nsIMsgWindow *msgWindow) override;
+ NS_IMETHOD RenameSubFolders (nsIMsgWindow *msgWindow, nsIMsgFolder *oldFolder) override;
+
+ NS_IMETHOD GetPrettyName(nsAString& prettyName) override; // Override of the base, for top-level mail folder
+ NS_IMETHOD SetPrettyName(const nsAString& aName) override;
+
+ NS_IMETHOD GetFolderURL(nsACString& url) override;
+
+ NS_IMETHOD GetManyHeadersToDownload(bool *retval) override;
+
+ NS_IMETHOD GetDeletable (bool *deletable) override;
+ NS_IMETHOD GetSizeOnDisk(int64_t *size) override;
+
+ NS_IMETHOD GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db) override;
+
+ NS_IMETHOD DeleteMessages(nsIArray *messages,
+ nsIMsgWindow *msgWindow, bool
+ deleteStorage, bool isMove,
+ nsIMsgCopyServiceListener* listener, bool allowUndo) override;
+ NS_IMETHOD CopyMessages(nsIMsgFolder *srcFolder, nsIArray* messages,
+ bool isMove, nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener, bool isFolder, bool allowUndo) override;
+ NS_IMETHOD CopyFolder(nsIMsgFolder *srcFolder, bool isMoveFolder, nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener) override;
+ NS_IMETHOD CopyFileMessage(nsIFile* aFile, nsIMsgDBHdr* msgToReplace,
+ bool isDraftOrTemplate,
+ uint32_t newMsgFlags,
+ const nsACString &aNewMsgKeywords,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener) override;
+
+ NS_IMETHOD AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag) override;
+ NS_IMETHOD MarkMessagesRead(nsIArray *aMessages, bool aMarkRead) override;
+ NS_IMETHOD MarkMessagesFlagged(nsIArray *aMessages, bool aMarkFlagged) override;
+ NS_IMETHOD MarkAllMessagesRead(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD MarkThreadRead(nsIMsgThread *thread) override;
+ NS_IMETHOD GetNewMessages(nsIMsgWindow *aWindow, nsIUrlListener *aListener) override;
+ NS_IMETHOD NotifyCompactCompleted() override;
+ NS_IMETHOD Shutdown(bool shutdownChildren) override;
+
+ NS_IMETHOD WriteToFolderCacheElem(nsIMsgFolderCacheElement *element) override;
+ NS_IMETHOD ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element) override;
+
+ NS_IMETHOD GetName(nsAString& aName) override;
+
+ // Used when headers_only is TRUE
+ NS_IMETHOD DownloadMessagesForOffline(nsIArray *aMessages, nsIMsgWindow *aWindow) override;
+ NS_IMETHOD FetchMsgPreviewText(nsMsgKey *aKeysToFetch, uint32_t aNumKeys,
+ bool aLocalOnly, nsIUrlListener *aUrlListener,
+ bool *aAsyncResults) override;
+ NS_IMETHOD AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords) override;
+ NS_IMETHOD RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords) override;
+ NS_IMETHOD GetIncomingServerType(nsACString& serverType) override;
+
+protected:
+ virtual ~nsMsgLocalMailFolder();
+ nsresult CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder) override;
+ nsresult CopyFolderAcrossServer(nsIMsgFolder *srcFolder, nsIMsgWindow *msgWindow,nsIMsgCopyServiceListener* listener);
+
+ nsresult CreateSubFolders(nsIFile *path);
+ nsresult GetTrashFolder(nsIMsgFolder** trashFolder);
+ nsresult WriteStartOfNewMessage();
+
+ // CreateSubfolder, but without the nsIMsgFolderListener notification
+ nsresult CreateSubfolderInternal(const nsAString& folderName, nsIMsgWindow *msgWindow,
+ nsIMsgFolder **aNewFolder);
+
+ nsresult IsChildOfTrash(bool *result);
+ nsresult RecursiveSetDeleteIsMoveTrash(bool bVal);
+ nsresult ConfirmFolderDeletion(nsIMsgWindow *aMsgWindow, nsIMsgFolder *aFolder, bool *aResult);
+
+ nsresult DeleteMessage(nsISupports *message, nsIMsgWindow *msgWindow,
+ bool deleteStorage, bool commit);
+ nsresult GetDatabase() override;
+ // this will set mDatabase, if successful. It will also create a .msf file
+ // for an empty local mail folder. It will leave invalid DBs in place, and
+ // return an error.
+ nsresult OpenDatabase();
+
+ // copy message helper
+ nsresult DisplayMoveCopyStatusMsg();
+
+ nsresult CopyMessageTo(nsISupports *message, nsIMsgFolder *dstFolder,
+ nsIMsgWindow *msgWindow, bool isMove);
+
+ /**
+ * Checks if there's room in the target folder to copy message(s) into.
+ * If not, handles alerting the user, and sending the copy notifications.
+ */
+ bool CheckIfSpaceForCopy(nsIMsgWindow *msgWindow,
+ nsIMsgFolder *srcFolder,
+ nsISupports *srcSupports,
+ bool isMove,
+ int64_t totalMsgSize);
+
+ // copy multiple messages at a time from this folder
+ nsresult CopyMessagesTo(nsIArray *messages, nsTArray<nsMsgKey> &keyArray,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgFolder *dstFolder,
+ bool isMove);
+ nsresult InitCopyState(nsISupports* aSupport, nsIArray* messages,
+ bool isMove, nsIMsgCopyServiceListener* listener, nsIMsgWindow *msgWindow, bool isMoveFolder, bool allowUndo);
+ nsresult InitCopyMsgHdrAndFileStream();
+ // preserve message metadata when moving or copying messages
+ void CopyPropertiesToMsgHdr(nsIMsgDBHdr *destHdr, nsIMsgDBHdr *srcHdr, bool isMove);
+ virtual nsresult CreateBaseMessageURI(const nsACString& aURI) override;
+ nsresult ChangeKeywordForMessages(nsIArray *aMessages, const nsACString& aKeyword, bool add);
+ bool GetDeleteFromServerOnMove();
+ void CopyHdrPropertiesWithSkipList(nsIMsgDBHdr *destHdr,
+ nsIMsgDBHdr *srcHdr,
+ const nsCString &skipList);
+ nsresult FinishNewLocalMessage(nsIOutputStream *outputStream,
+ nsIMsgDBHdr *newHdr,
+ nsIMsgPluggableStore *msgStore,
+ nsParseMailMessageState *parseMsgState);
+
+protected:
+ nsLocalMailCopyState *mCopyState; //We only allow one of these at a time
+ nsCString mType;
+ bool mHaveReadNameFromDB;
+ bool mInitialized;
+ bool mCheckForNewMessagesAfterParsing;
+ bool m_parsingFolder;
+ nsCOMPtr<nsIUrlListener> mReparseListener;
+ nsTArray<nsMsgKey> mSpamKeysToMove;
+ nsresult setSubfolderFlag(const nsAString& aFolderName, uint32_t flags);
+
+ // state variables for DownloadMessagesForOffline
+
+ // Do we notify the owning window of Delete's before or after
+ // Adding the new msg?
+#define DOWNLOAD_NOTIFY_FIRST 1
+#define DOWNLOAD_NOTIFY_LAST 2
+
+#ifndef DOWNLOAD_NOTIFY_STYLE
+#define DOWNLOAD_NOTIFY_STYLE DOWNLOAD_NOTIFY_FIRST
+#endif
+
+ nsCOMArray<nsIMsgDBHdr> mDownloadMessages;
+ nsCOMPtr<nsIMsgWindow> mDownloadWindow;
+ nsMsgKey mDownloadSelectKey;
+ uint32_t mDownloadState;
+#define DOWNLOAD_STATE_NONE 0
+#define DOWNLOAD_STATE_INITED 1
+#define DOWNLOAD_STATE_GOTMSG 2
+#define DOWNLOAD_STATE_DIDSEL 3
+
+#if DOWNLOAD_NOTIFY_STYLE == DOWNLOAD_NOTIFY_LAST
+ nsMsgKey mDownloadOldKey;
+ nsMsgKey mDownloadOldParent;
+ uint32_t mDownloadOldFlags;
+#endif
+};
+
+#endif // nsMsgLocalMailFolder_h__
diff --git a/mailnews/local/src/nsLocalUndoTxn.cpp b/mailnews/local/src/nsLocalUndoTxn.cpp
new file mode 100644
index 000000000..a53ac992e
--- /dev/null
+++ b/mailnews/local/src/nsLocalUndoTxn.cpp
@@ -0,0 +1,560 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsIMsgHdr.h"
+#include "nsLocalUndoTxn.h"
+#include "nsImapCore.h"
+#include "nsMsgImapCID.h"
+#include "nsIImapService.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsThreadUtils.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMutableArray.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+
+NS_IMPL_ISUPPORTS_INHERITED(nsLocalMoveCopyMsgTxn, nsMsgTxn, nsIFolderListener)
+
+nsLocalMoveCopyMsgTxn::nsLocalMoveCopyMsgTxn() : m_srcIsImap4(false),
+ m_canUndelete(false)
+{
+}
+
+nsLocalMoveCopyMsgTxn::~nsLocalMoveCopyMsgTxn()
+{
+}
+
+nsresult
+nsLocalMoveCopyMsgTxn::Init(nsIMsgFolder* srcFolder, nsIMsgFolder* dstFolder,
+ bool isMove)
+{
+ nsresult rv;
+ rv = SetSrcFolder(srcFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetDstFolder(dstFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_isMove = isMove;
+
+ mUndoFolderListener = nullptr;
+
+ nsCString protocolType;
+ rv = srcFolder->GetURI(protocolType);
+ protocolType.SetLength(protocolType.FindChar(':'));
+ if (MsgLowerCaseEqualsLiteral(protocolType, "imap"))
+ m_srcIsImap4 = true;
+ return nsMsgTxn::Init();
+}
+nsresult
+nsLocalMoveCopyMsgTxn::GetSrcIsImap(bool *isImap)
+{
+ *isImap = m_srcIsImap4;
+ return NS_OK;
+}
+nsresult
+nsLocalMoveCopyMsgTxn::SetSrcFolder(nsIMsgFolder* srcFolder)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ if (srcFolder)
+ m_srcFolder = do_GetWeakReference(srcFolder, &rv);
+ return rv;
+}
+
+nsresult
+nsLocalMoveCopyMsgTxn::SetDstFolder(nsIMsgFolder* dstFolder)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ if (dstFolder)
+ m_dstFolder = do_GetWeakReference(dstFolder, &rv);
+ return rv;
+}
+
+nsresult
+nsLocalMoveCopyMsgTxn::AddSrcKey(nsMsgKey aKey)
+{
+ m_srcKeyArray.AppendElement(aKey);
+ return NS_OK;
+}
+
+nsresult
+nsLocalMoveCopyMsgTxn::AddSrcStatusOffset(uint32_t aStatusOffset)
+{
+ m_srcStatusOffsetArray.AppendElement(aStatusOffset);
+ return NS_OK;
+}
+
+
+nsresult
+nsLocalMoveCopyMsgTxn::AddDstKey(nsMsgKey aKey)
+{
+ m_dstKeyArray.AppendElement(aKey);
+ return NS_OK;
+}
+
+nsresult
+nsLocalMoveCopyMsgTxn::AddDstMsgSize(uint32_t msgSize)
+{
+ m_dstSizeArray.AppendElement(msgSize);
+ return NS_OK;
+}
+
+nsresult
+nsLocalMoveCopyMsgTxn::UndoImapDeleteFlag(nsIMsgFolder* folder,
+ nsTArray<nsMsgKey>& keyArray,
+ bool deleteFlag)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ if (m_srcIsImap4)
+ {
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIUrlListener> urlListener;
+ nsCString msgIds;
+ uint32_t i, count = keyArray.Length();
+ urlListener = do_QueryInterface(folder, &rv);
+ for (i=0; i < count; i++)
+ {
+ if (!msgIds.IsEmpty())
+ msgIds.Append(',');
+ msgIds.AppendInt((int32_t) keyArray[i]);
+ }
+ // This is to make sure that we are in the selected state
+ // when executing the imap url; we don't want to load the
+ // folder so use lite select to do the trick
+ rv = imapService->LiteSelectFolder(folder,
+ urlListener, nullptr, nullptr);
+ if (!deleteFlag)
+ rv =imapService->AddMessageFlags(folder,
+ urlListener, nullptr,
+ msgIds,
+ kImapMsgDeletedFlag,
+ true);
+ else
+ rv = imapService->SubtractMessageFlags(folder,
+ urlListener, nullptr,
+ msgIds,
+ kImapMsgDeletedFlag,
+ true);
+ if (NS_SUCCEEDED(rv) && m_msgWindow)
+ folder->UpdateFolder(m_msgWindow);
+ rv = NS_OK; // always return NS_OK to indicate that the src is imap
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalMoveCopyMsgTxn::UndoTransaction()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgDatabase> dstDB;
+
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgLocalMailFolder> dstlocalMailFolder = do_QueryReferent(m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dstlocalMailFolder->GetDatabaseWOReparse(getter_AddRefs(dstDB));
+
+ if (!dstDB)
+ {
+ // This will listen for the db reparse finishing, and the corresponding
+ // FolderLoadedNotification. When it gets that, it will then call
+ // UndoTransactionInternal.
+ mUndoFolderListener = new nsLocalUndoFolderListener(this, dstFolder);
+ if (!mUndoFolderListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(mUndoFolderListener);
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mailSession->AddFolderListener(mUndoFolderListener, nsIFolderListener::event);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else
+ rv = UndoTransactionInternal();
+ return rv;
+}
+
+nsresult
+nsLocalMoveCopyMsgTxn::UndoTransactionInternal()
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (mUndoFolderListener)
+ {
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mailSession->RemoveFolderListener(mUndoFolderListener);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_RELEASE(mUndoFolderListener);
+ mUndoFolderListener = nullptr;
+ }
+
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIMsgDatabase> dstDB;
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if(NS_FAILED(rv)) return rv;
+
+ rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t count = m_srcKeyArray.Length();
+ uint32_t i;
+ nsCOMPtr<nsIMsgDBHdr> oldHdr;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ // protect against a bogus undo txn without any source keys
+ // see bug #179856 for details
+ NS_ASSERTION(count, "no source keys");
+ if (!count)
+ return NS_ERROR_UNEXPECTED;
+
+ if (m_isMove)
+ {
+ if (m_srcIsImap4)
+ {
+ bool deleteFlag = true; //message has been deleted -we are trying to undo it
+ CheckForToggleDelete(srcFolder, m_srcKeyArray[0], &deleteFlag); //there could have been a toggle.
+ rv = UndoImapDeleteFlag(srcFolder, m_srcKeyArray, deleteFlag);
+ }
+ else if (m_canUndelete)
+ {
+ nsCOMPtr<nsIMutableArray> srcMessages =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+ nsCOMPtr<nsIMutableArray> dstMessages =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ srcDB->StartBatch();
+ for (i = 0; i < count; i++)
+ {
+ rv = dstDB->GetMsgHdrForKey(m_dstKeyArray[i],
+ getter_AddRefs(oldHdr));
+ NS_ASSERTION(oldHdr, "fatal ... cannot get old msg header\n");
+ if (NS_SUCCEEDED(rv) && oldHdr)
+ {
+ rv = srcDB->CopyHdrFromExistingHdr(m_srcKeyArray[i],
+ oldHdr, true,
+ getter_AddRefs(newHdr));
+ NS_ASSERTION(newHdr,
+ "fatal ... cannot create new msg header\n");
+ if (NS_SUCCEEDED(rv) && newHdr)
+ {
+ newHdr->SetStatusOffset(m_srcStatusOffsetArray[i]);
+ srcDB->UndoDelete(newHdr);
+ srcMessages->AppendElement(newHdr, false);
+ // (we want to keep these two lists in sync)
+ dstMessages->AppendElement(oldHdr, false);
+ }
+ }
+ }
+ srcDB->EndBatch();
+
+ nsCOMPtr<nsIMsgFolderNotificationService>
+ notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ {
+ // Remember that we're actually moving things back from the destination
+ // to the source!
+ notifier->NotifyMsgsMoveCopyCompleted(true, dstMessages,
+ srcFolder, srcMessages);
+ }
+
+ nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(srcFolder);
+ if (localFolder)
+ localFolder->MarkMsgsOnPop3Server(srcMessages, POP3_NONE /*deleteMsgs*/);
+ }
+ else // undoing a move means moving the messages back.
+ {
+ nsCOMPtr<nsIMutableArray> dstMessages =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+ m_numHdrsCopied = 0;
+ m_srcKeyArray.Clear();
+ for (i = 0; i < count; i++)
+ {
+ // GetMsgHdrForKey is not a test for whether the key exists, so check.
+ bool hasKey = false;
+ dstDB->ContainsKey(m_dstKeyArray[i], &hasKey);
+ nsCOMPtr<nsIMsgDBHdr> dstHdr;
+ if (hasKey)
+ dstDB->GetMsgHdrForKey(m_dstKeyArray[i], getter_AddRefs(dstHdr));
+ if (dstHdr)
+ {
+ nsCString messageId;
+ dstHdr->GetMessageId(getter_Copies(messageId));
+ dstMessages->AppendElement(dstHdr, false);
+ m_copiedMsgIds.AppendElement(messageId);
+ }
+ else
+ {
+ NS_WARNING("Cannot get old msg header");
+ }
+ }
+ if (m_copiedMsgIds.Length())
+ {
+ srcFolder->AddFolderListener(this);
+ m_undoing = true;
+ return srcFolder->CopyMessages(dstFolder, dstMessages,
+ true, nullptr, nullptr, false,
+ false);
+ }
+ else
+ {
+ // Nothing to do, probably because original messages were deleted.
+ NS_WARNING("Undo did not find any messages to move");
+ }
+ }
+ srcDB->SetSummaryValid(true);
+ }
+
+ dstDB->DeleteMessages(m_dstKeyArray.Length(), m_dstKeyArray.Elements(), nullptr);
+ dstDB->SetSummaryValid(true);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalMoveCopyMsgTxn::RedoTransaction()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIMsgDatabase> dstDB;
+
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if(NS_FAILED(rv)) return rv;
+ rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t count = m_srcKeyArray.Length();
+ uint32_t i;
+ nsCOMPtr<nsIMsgDBHdr> oldHdr;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ nsCOMPtr<nsIMutableArray> srcMessages = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ nsCOMPtr <nsISupports> msgSupports;
+
+ for (i=0; i<count; i++)
+ {
+ rv = srcDB->GetMsgHdrForKey(m_srcKeyArray[i],
+ getter_AddRefs(oldHdr));
+ NS_ASSERTION(oldHdr, "fatal ... cannot get old msg header\n");
+
+ if (NS_SUCCEEDED(rv) && oldHdr)
+ {
+ msgSupports =do_QueryInterface(oldHdr);
+ srcMessages->AppendElement(msgSupports, false);
+
+ if (m_canUndelete)
+ {
+ rv = dstDB->CopyHdrFromExistingHdr(m_dstKeyArray[i],
+ oldHdr, true,
+ getter_AddRefs(newHdr));
+ NS_ASSERTION(newHdr, "fatal ... cannot get new msg header\n");
+ if (NS_SUCCEEDED(rv) && newHdr)
+ {
+ if (i < m_dstSizeArray.Length())
+ rv = newHdr->SetMessageSize(m_dstSizeArray[i]);
+ dstDB->UndoDelete(newHdr);
+ }
+ }
+ }
+ }
+ dstDB->SetSummaryValid(true);
+
+ if (m_isMove)
+ {
+ if (m_srcIsImap4)
+ {
+ // protect against a bogus undo txn without any source keys
+ // see bug #179856 for details
+ NS_ASSERTION(!m_srcKeyArray.IsEmpty(), "no source keys");
+ if (m_srcKeyArray.IsEmpty())
+ return NS_ERROR_UNEXPECTED;
+
+ bool deleteFlag = false; //message is un-deleted- we are trying to redo
+ CheckForToggleDelete(srcFolder, m_srcKeyArray[0], &deleteFlag); // there could have been a toggle
+ rv = UndoImapDeleteFlag(srcFolder, m_srcKeyArray, deleteFlag);
+ }
+ else if (m_canUndelete)
+ {
+ nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(srcFolder);
+ if (localFolder)
+ localFolder->MarkMsgsOnPop3Server(srcMessages, POP3_DELETE /*deleteMsgs*/);
+
+ rv = srcDB->DeleteMessages(m_srcKeyArray.Length(), m_srcKeyArray.Elements(), nullptr);
+ srcDB->SetSummaryValid(true);
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgDBHdr> srcHdr;
+ m_numHdrsCopied = 0;
+ m_dstKeyArray.Clear();
+ for (i = 0; i < count; i++)
+ {
+ srcDB->GetMsgHdrForKey(m_srcKeyArray[i], getter_AddRefs(srcHdr));
+ NS_ASSERTION(srcHdr, "fatal ... cannot get old msg header\n");
+ if (srcHdr)
+ {
+ nsCString messageId;
+ srcHdr->GetMessageId(getter_Copies(messageId));
+ m_copiedMsgIds.AppendElement(messageId);
+ }
+ }
+ dstFolder->AddFolderListener(this);
+ m_undoing = false;
+ return dstFolder->CopyMessages(srcFolder, srcMessages, true, nullptr,
+ nullptr, false, false);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryInterface(item));
+ if (msgHdr)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_undoing ? m_srcFolder :
+ m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ if (m_copiedMsgIds.Contains(messageId))
+ {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (m_undoing)
+ m_srcKeyArray.AppendElement(msgKey);
+ else
+ m_dstKeyArray.AppendElement(msgKey);
+ if (++m_numHdrsCopied == m_copiedMsgIds.Length())
+ {
+ folder->RemoveFolderListener(this);
+ m_copiedMsgIds.Clear();
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char *oldValue, const char *newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemIntPropertyChanged(nsIMsgFolder *item, nsIAtom *property, int64_t oldValue, int64_t newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemBoolPropertyChanged(nsIMsgFolder *item, nsIAtom *property, bool oldValue, bool newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemUnicharPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char16_t *oldValue, const char16_t *newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemEvent(nsIMsgFolder *aItem, nsIAtom *aEvent)
+{
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsLocalUndoFolderListener, nsIFolderListener)
+
+nsLocalUndoFolderListener::nsLocalUndoFolderListener(nsLocalMoveCopyMsgTxn *aTxn, nsIMsgFolder *aFolder)
+{
+ mTxn = aTxn;
+ mFolder = aFolder;
+}
+
+nsLocalUndoFolderListener::~nsLocalUndoFolderListener()
+{
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnItemPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char *oldValue, const char *newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnItemIntPropertyChanged(nsIMsgFolder *item, nsIAtom *property, int64_t oldValue, int64_t newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnItemBoolPropertyChanged(nsIMsgFolder *item, nsIAtom *property, bool oldValue, bool newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnItemUnicharPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char16_t *oldValue, const char16_t *newValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnItemEvent(nsIMsgFolder *aItem, nsIAtom *aEvent)
+{
+ if (mTxn && mFolder && aItem == mFolder) {
+ bool isEqual = false;
+ aEvent->ScriptableEquals(NS_LITERAL_STRING("FolderLoaded"), &isEqual);
+ if (isEqual)
+ return mTxn->UndoTransactionInternal();
+ }
+
+ return NS_ERROR_FAILURE;
+}
diff --git a/mailnews/local/src/nsLocalUndoTxn.h b/mailnews/local/src/nsLocalUndoTxn.h
new file mode 100644
index 000000000..62f93f20a
--- /dev/null
+++ b/mailnews/local/src/nsLocalUndoTxn.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLocalUndoTxn_h__
+#define nsLocalUndoTxn_h__
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsIMsgFolder.h"
+#include "nsMailboxService.h"
+#include "nsMsgTxn.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsIUrlListener.h"
+#include "nsIWeakReference.h"
+#include "nsIWeakReferenceUtils.h"
+
+class nsLocalUndoFolderListener;
+
+class nsLocalMoveCopyMsgTxn : public nsIFolderListener, public nsMsgTxn
+{
+public:
+ nsLocalMoveCopyMsgTxn();
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFOLDERLISTENER
+
+ // overloading nsITransaction methods
+ NS_IMETHOD UndoTransaction(void) override;
+ NS_IMETHOD RedoTransaction(void) override;
+
+ // helper
+ nsresult AddSrcKey(nsMsgKey aKey);
+ nsresult AddSrcStatusOffset(uint32_t statusOffset);
+ nsresult AddDstKey(nsMsgKey aKey);
+ nsresult AddDstMsgSize(uint32_t msgSize);
+ nsresult SetSrcFolder(nsIMsgFolder* srcFolder);
+ nsresult GetSrcIsImap(bool *isImap);
+ nsresult SetDstFolder(nsIMsgFolder* dstFolder);
+ nsresult Init(nsIMsgFolder* srcFolder,
+ nsIMsgFolder* dstFolder, bool isMove);
+ nsresult UndoImapDeleteFlag(nsIMsgFolder* aFolder,
+ nsTArray<nsMsgKey>& aKeyArray,
+ bool deleteFlag);
+ nsresult UndoTransactionInternal();
+ // If the store using this undo transaction can "undelete" a message,
+ // it will call this function on the transaction; This makes undo/redo
+ // easy because message keys don't change after undo/redo. Otherwise,
+ // we need to adjust the src or dst keys after every undo/redo action
+ // to note the new keys.
+ void SetCanUndelete(bool canUndelete) {m_canUndelete = canUndelete;}
+
+private:
+ virtual ~nsLocalMoveCopyMsgTxn();
+ nsWeakPtr m_srcFolder;
+ nsTArray<nsMsgKey> m_srcKeyArray; // used when src is local or imap
+ nsTArray<uint32_t> m_srcStatusOffsetArray; // used when src is local
+ nsWeakPtr m_dstFolder;
+ nsTArray<nsMsgKey> m_dstKeyArray;
+ bool m_isMove;
+ bool m_srcIsImap4;
+ bool m_canUndelete;
+ nsTArray<uint32_t> m_dstSizeArray;
+ bool m_undoing; // if false, re-doing
+ uint32_t m_numHdrsCopied;
+ nsTArray<nsCString> m_copiedMsgIds;
+ nsLocalUndoFolderListener *mUndoFolderListener;
+};
+
+class nsLocalUndoFolderListener : public nsIFolderListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFOLDERLISTENER
+
+ nsLocalUndoFolderListener(nsLocalMoveCopyMsgTxn *aTxn, nsIMsgFolder *aFolder);
+
+private:
+ virtual ~nsLocalUndoFolderListener();
+ nsLocalMoveCopyMsgTxn *mTxn;
+ nsIMsgFolder *mFolder;
+};
+
+#endif
diff --git a/mailnews/local/src/nsLocalUtils.cpp b/mailnews/local/src/nsLocalUtils.cpp
new file mode 100644
index 000000000..14a6a2f21
--- /dev/null
+++ b/mailnews/local/src/nsLocalUtils.cpp
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsLocalUtils.h"
+#include "nsIServiceManager.h"
+#include "prsystem.h"
+#include "nsCOMPtr.h"
+#include "prmem.h"
+// stuff for temporary root folder hack
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsINoIncomingServer.h"
+#include "nsMsgBaseCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsMsgUtils.h"
+#include "nsNetCID.h"
+
+// it would be really cool to:
+// - cache the last hostname->path match
+// - if no such server exists, behave like an old-style mailbox URL
+// (i.e. return the mail.directory preference or something)
+static nsresult
+nsGetMailboxServer(const char *uriStr, nsIMsgIncomingServer** aResult)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIURL> aUrl = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aUrl->SetSpec(nsDependentCString(uriStr));
+ if (NS_FAILED(rv)) return rv;
+
+ // retrieve the AccountManager
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // find all local mail "no servers" matching the given hostname
+ nsCOMPtr<nsIMsgIncomingServer> none_server;
+ aUrl->SetScheme(NS_LITERAL_CSTRING("none"));
+ // No unescaping of username or hostname done here.
+ // The unescaping is done inside of FindServerByURI
+ rv = accountManager->FindServerByURI(aUrl, false,
+ getter_AddRefs(none_server));
+ if (NS_SUCCEEDED(rv)) {
+ NS_ADDREF(*aResult = none_server);
+ return rv;
+ }
+
+ // if that fails, look for the rss hosts matching the given hostname
+ nsCOMPtr<nsIMsgIncomingServer> rss_server;
+ aUrl->SetScheme(NS_LITERAL_CSTRING("rss"));
+ rv = accountManager->FindServerByURI(aUrl, false,
+ getter_AddRefs(rss_server));
+ if (NS_SUCCEEDED(rv))
+ {
+ NS_ADDREF(*aResult = rss_server);
+ return rv;
+ }
+#ifdef HAVE_MOVEMAIL
+ // find all movemail "servers" matching the given hostname
+ nsCOMPtr<nsIMsgIncomingServer> movemail_server;
+ aUrl->SetScheme(NS_LITERAL_CSTRING("movemail"));
+ rv = accountManager->FindServerByURI(aUrl, false,
+ getter_AddRefs(movemail_server));
+ if (NS_SUCCEEDED(rv)) {
+ NS_ADDREF(*aResult = movemail_server);
+ return rv;
+ }
+#endif /* HAVE_MOVEMAIL */
+
+ // if that fails, look for the pop hosts matching the given hostname
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_FAILED(rv))
+ {
+ aUrl->SetScheme(NS_LITERAL_CSTRING("pop3"));
+ rv = accountManager->FindServerByURI(aUrl, false,
+ getter_AddRefs(server));
+
+ // if we can't find a pop server, maybe it's a local message
+ // in an imap hierarchy. look for an imap server.
+ if (NS_FAILED(rv))
+ {
+ aUrl->SetScheme(NS_LITERAL_CSTRING("imap"));
+ rv = accountManager->FindServerByURI(aUrl, false,
+ getter_AddRefs(server));
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ NS_ADDREF(*aResult = server);
+ return rv;
+ }
+
+// if you fail after looking at all "pop3", "movemail" and "none" servers, you fail.
+return rv;
+}
+
+static nsresult
+nsLocalURI2Server(const char* uriStr,
+ nsIMsgIncomingServer ** aResult)
+{
+ nsresult rv;
+
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = nsGetMailboxServer(uriStr, getter_AddRefs(server));
+
+ NS_IF_ADDREF(*aResult = server);
+
+ return rv;
+}
+
+// given rootURI and rootURI##folder, return on-disk path of folder
+nsresult
+nsLocalURI2Path(const char* rootURI, const char* uriStr,
+ nsCString& pathResult)
+{
+ nsresult rv;
+
+ // verify that rootURI starts with "mailbox:/" or "mailbox-message:/"
+ if ((PL_strcmp(rootURI, kMailboxRootURI) != 0) &&
+ (PL_strcmp(rootURI, kMailboxMessageRootURI) != 0)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // verify that uristr starts with rooturi
+ nsAutoCString uri(uriStr);
+ if (uri.Find(rootURI) != 0)
+ return NS_ERROR_FAILURE;
+
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = nsLocalURI2Server(uriStr, getter_AddRefs(server));
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ // now ask the server what it's root is
+ // and begin pathResult with the mailbox root
+ nsCOMPtr<nsIFile> localPath;
+ rv = server->GetLocalPath(getter_AddRefs(localPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString localNativePath;
+
+ localPath->GetNativePath(localNativePath);
+ nsEscapeNativePath(localNativePath);
+ pathResult = localNativePath.get();
+ const char *curPos = uriStr + PL_strlen(rootURI);
+ if (curPos) {
+ // advance past hostname
+ while ((*curPos)=='/') curPos++;
+ while (*curPos && (*curPos)!='/') curPos++;
+
+ nsAutoCString newPath("");
+
+ // Unescape folder name
+ if (curPos) {
+ nsCString unescapedStr;
+ MsgUnescapeString(nsDependentCString(curPos), 0, unescapedStr);
+ NS_MsgCreatePathStringFromFolderURI(unescapedStr.get(), newPath, NS_LITERAL_CSTRING("none"));
+ } else
+ NS_MsgCreatePathStringFromFolderURI(curPos, newPath, NS_LITERAL_CSTRING("none"));
+
+ pathResult.Append('/');
+ pathResult.Append(newPath);
+ }
+
+ return NS_OK;
+}
+
+/* parses LocalMessageURI
+ * mailbox-message://folder1/folder2#123?header=none or
+ * mailbox-message://folder1/folder2#1234&part=1.2
+ *
+ * puts folder URI in folderURI (mailbox://folder1/folder2)
+ * message key number in key
+ */
+nsresult nsParseLocalMessageURI(const char* uri,
+ nsCString& folderURI,
+ nsMsgKey *key)
+{
+ if(!key)
+ return NS_ERROR_NULL_POINTER;
+
+ nsAutoCString uriStr(uri);
+ int32_t keySeparator = uriStr.FindChar('#');
+ if(keySeparator != -1)
+ {
+ int32_t keyEndSeparator = MsgFindCharInSet(uriStr, "?&", keySeparator);
+ folderURI = StringHead(uriStr, keySeparator);
+ folderURI.Cut(7, 8); // cut out the -message part of mailbox-message:
+
+ nsAutoCString keyStr;
+ if (keyEndSeparator != -1)
+ keyStr = Substring(uriStr, keySeparator + 1,
+ keyEndSeparator - (keySeparator + 1));
+ else
+ keyStr = StringTail(uriStr, uriStr.Length() - (keySeparator + 1));
+
+ *key = msgKeyFromInt(ParseUint64Str(keyStr.get()));
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+
+}
+
+nsresult nsBuildLocalMessageURI(const char *baseURI, nsMsgKey key, nsCString& uri)
+{
+ // need to convert mailbox://hostname/.. to mailbox-message://hostname/..
+ uri.Append(baseURI);
+ uri.Append('#');
+ uri.AppendInt(key);
+ return NS_OK;
+}
+
+nsresult nsCreateLocalBaseMessageURI(const nsACString& baseURI, nsCString &baseMessageURI)
+{
+ nsAutoCString tailURI(baseURI);
+
+ // chop off mailbox:/
+ if (tailURI.Find(kMailboxRootURI) == 0)
+ tailURI.Cut(0, PL_strlen(kMailboxRootURI));
+
+ baseMessageURI = kMailboxMessageRootURI;
+ baseMessageURI += tailURI;
+
+ return NS_OK;
+}
+
+void nsEscapeNativePath(nsCString& nativePath)
+{
+#if defined(XP_WIN)
+ nativePath.Insert('/', 0);
+ MsgReplaceChar(nativePath, '\\', '/');
+#endif
+}
diff --git a/mailnews/local/src/nsLocalUtils.h b/mailnews/local/src/nsLocalUtils.h
new file mode 100644
index 000000000..4419e6baa
--- /dev/null
+++ b/mailnews/local/src/nsLocalUtils.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_LOCALUTILS_H
+#define NS_LOCALUTILS_H
+
+#include "nsStringGlue.h"
+#include "nsIMsgIncomingServer.h"
+
+static const char kMailboxRootURI[] = "mailbox:/";
+static const char kMailboxMessageRootURI[] = "mailbox-message:/";
+
+nsresult
+nsLocalURI2Path(const char* rootURI, const char* uriStr, nsCString& pathResult);
+
+nsresult
+nsParseLocalMessageURI(const char* uri, nsCString& folderURI, nsMsgKey *key);
+
+nsresult
+nsBuildLocalMessageURI(const char* baseURI, nsMsgKey key, nsCString& uri);
+
+nsresult
+nsCreateLocalBaseMessageURI(const nsACString& baseURI, nsCString &baseMessageURI);
+
+void
+nsEscapeNativePath(nsCString& nativePath);
+
+#endif //NS_LOCALUTILS_H
diff --git a/mailnews/local/src/nsMailboxProtocol.cpp b/mailnews/local/src/nsMailboxProtocol.cpp
new file mode 100644
index 000000000..0148133b3
--- /dev/null
+++ b/mailnews/local/src/nsMailboxProtocol.cpp
@@ -0,0 +1,721 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+
+#include "nsMailboxProtocol.h"
+#include "nscore.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgLineBuffer.h"
+#include "nsMsgDBCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsMsgMessageFlags.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "prerror.h"
+#include "prprf.h"
+#include "nspr.h"
+
+PRLogModuleInfo *MAILBOX;
+#include "nsIFileStreams.h"
+#include "nsIStreamTransportService.h"
+#include "nsIStreamConverterService.h"
+#include "nsIIOService.h"
+#include "nsNetUtil.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgWindow.h"
+#include "nsIMimeHeaders.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsISeekableStream.h"
+
+#include "nsIMsgMdnGenerator.h"
+
+/* the output_buffer_size must be larger than the largest possible line
+ * 2000 seems good for news
+ *
+ * jwz: I increased this to 4k since it must be big enough to hold the
+ * entire button-bar HTML, and with the new "mailto" format, that can
+ * contain arbitrarily long header fields like "references".
+ *
+ * fortezza: proxy auth is huge, buffer increased to 8k (sigh).
+ */
+#define OUTPUT_BUFFER_SIZE (4096*2)
+
+nsMailboxProtocol::nsMailboxProtocol(nsIURI * aURI)
+ : nsMsgProtocol(aURI)
+{
+ // initialize the pr log if it hasn't been initialiezed already
+ if (!MAILBOX)
+ MAILBOX = PR_NewLogModule("MAILBOX");
+}
+
+nsMailboxProtocol::~nsMailboxProtocol()
+{
+}
+
+nsresult nsMailboxProtocol::OpenMultipleMsgTransport(uint64_t offset, int32_t size)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamTransportService> serv =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX 64-bit
+ rv = serv->CreateInputTransport(m_multipleMsgMoveCopyStream, int64_t(offset),
+ int64_t(size), false,
+ getter_AddRefs(m_transport));
+
+ return rv;
+}
+
+nsresult nsMailboxProtocol::Initialize(nsIURI * aURL)
+{
+ NS_PRECONDITION(aURL, "invalid URL passed into MAILBOX Protocol");
+ nsresult rv = NS_OK;
+ if (aURL)
+ {
+ rv = aURL->QueryInterface(NS_GET_IID(nsIMailboxUrl), (void **) getter_AddRefs(m_runningUrl));
+ if (NS_SUCCEEDED(rv) && m_runningUrl)
+ {
+ nsCOMPtr <nsIMsgWindow> window;
+ rv = m_runningUrl->GetMailboxAction(&m_mailboxAction);
+ // clear stopped flag on msg window, because we care.
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl)
+ {
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(window));
+ if (window)
+ window->SetStopped(false);
+ }
+
+ if (m_mailboxAction == nsIMailboxUrl::ActionParseMailbox)
+ {
+ // Set the length of the file equal to the max progress
+ nsCOMPtr<nsIFile> file;
+ GetFileFromURL(aURL, getter_AddRefs(file));
+ if (file)
+ {
+ int64_t fileSize = 0;
+ file->GetFileSize(&fileSize);
+ mailnewsUrl->SetMaxProgress(fileSize);
+ }
+
+ rv = OpenFileSocket(aURL, 0, -1 /* read in all the bytes in the file */);
+ }
+ else
+ {
+ // we need to specify a byte range to read in so we read in JUST the message we want.
+ rv = SetupMessageExtraction();
+ if (NS_FAILED(rv)) return rv;
+ uint32_t aMsgSize = 0;
+ rv = m_runningUrl->GetMessageSize(&aMsgSize);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "oops....i messed something up");
+ SetContentLength(aMsgSize);
+ mailnewsUrl->SetMaxProgress(aMsgSize);
+
+ if (RunningMultipleMsgUrl())
+ {
+ // if we're running multiple msg url, we clear the event sink because the multiple
+ // msg urls will handle setting the progress.
+ mProgressEventSink = nullptr;
+ }
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr)
+ {
+ rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder)
+ {
+ nsCOMPtr<nsIInputStream> stream;
+ int64_t offset = 0;
+ bool reusable = false;
+
+ rv = folder->GetMsgInputStream(msgHdr, &reusable, getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISeekableStream> seekableStream(do_QueryInterface(stream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ seekableStream->Tell(&offset);
+ // create input stream transport
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ m_readCount = aMsgSize;
+ // Save the stream for reuse, but only for multiple URLs.
+ if (reusable && RunningMultipleMsgUrl())
+ m_multipleMsgMoveCopyStream = stream;
+ else
+ reusable = false;
+ rv = sts->CreateInputTransport(stream, offset,
+ int64_t(aMsgSize), !reusable,
+ getter_AddRefs(m_transport));
+
+ m_socketIsOpen = false;
+ }
+ }
+ if (!folder) // must be a .eml file
+ rv = OpenFileSocket(aURL, 0, aMsgSize);
+ }
+ NS_ASSERTION(NS_SUCCEEDED(rv), "oops....i messed something up");
+ }
+ }
+ }
+
+ m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true);
+
+ m_nextState = MAILBOX_READ_FOLDER;
+ m_initialState = MAILBOX_READ_FOLDER;
+ mCurrentProgress = 0;
+
+ // do we really need both?
+ m_tempMessageFile = m_tempMsgFile;
+ return rv;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// we suppport the nsIStreamListener interface
+////////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMailboxProtocol::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ // extract the appropriate event sinks from the url and initialize them in our protocol data
+ // the URL should be queried for a nsINewsURL. If it doesn't support a news URL interface then
+ // we have an error.
+ if (m_nextState == MAILBOX_READ_FOLDER && m_mailboxParser)
+ {
+ // we need to inform our mailbox parser that it's time to start...
+ m_mailboxParser->OnStartRequest(request, ctxt);
+ }
+ return nsMsgProtocol::OnStartRequest(request, ctxt);
+}
+
+bool nsMailboxProtocol::RunningMultipleMsgUrl()
+{
+ if (m_mailboxAction == nsIMailboxUrl::ActionCopyMessage || m_mailboxAction == nsIMailboxUrl::ActionMoveMessage)
+ {
+ uint32_t numMoveCopyMsgs;
+ nsresult rv = m_runningUrl->GetNumMoveCopyMsgs(&numMoveCopyMsgs);
+ if (NS_SUCCEEDED(rv) && numMoveCopyMsgs > 1)
+ return true;
+ }
+ return false;
+}
+
+// stop binding is a "notification" informing us that the stream associated with aURL is going away.
+NS_IMETHODIMP nsMailboxProtocol::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus)
+{
+ nsresult rv;
+ if (m_nextState == MAILBOX_READ_FOLDER && m_mailboxParser)
+ {
+ // we need to inform our mailbox parser that there is no more incoming data...
+ m_mailboxParser->OnStopRequest(request, ctxt, aStatus);
+ }
+ else if (m_nextState == MAILBOX_READ_MESSAGE)
+ {
+ DoneReadingMessage();
+ }
+ // I'm not getting cancel status - maybe the load group still has the status.
+ bool stopped = false;
+ if (m_runningUrl)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr <nsIMsgWindow> window;
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(window));
+ if (window)
+ window->GetStopped(&stopped);
+ }
+
+ if (!stopped && NS_SUCCEEDED(aStatus) && (m_mailboxAction == nsIMailboxUrl::ActionCopyMessage || m_mailboxAction == nsIMailboxUrl::ActionMoveMessage))
+ {
+ uint32_t numMoveCopyMsgs;
+ uint32_t curMoveCopyMsgIndex;
+ rv = m_runningUrl->GetNumMoveCopyMsgs(&numMoveCopyMsgs);
+ if (NS_SUCCEEDED(rv) && numMoveCopyMsgs > 0)
+ {
+ m_runningUrl->GetCurMoveCopyMsgIndex(&curMoveCopyMsgIndex);
+ if (++curMoveCopyMsgIndex < numMoveCopyMsgs)
+ {
+ if (!mSuppressListenerNotifications && m_channelListener)
+ {
+ nsCOMPtr<nsICopyMessageStreamListener> listener = do_QueryInterface(m_channelListener, &rv);
+ if (listener)
+ {
+ listener->EndCopy(ctxt, aStatus);
+ listener->StartMessage(); // start next message.
+ }
+ }
+ m_runningUrl->SetCurMoveCopyMsgIndex(curMoveCopyMsgIndex);
+ nsCOMPtr <nsIMsgDBHdr> nextMsg;
+ rv = m_runningUrl->GetMoveCopyMsgHdrForIndex(curMoveCopyMsgIndex, getter_AddRefs(nextMsg));
+ if (NS_SUCCEEDED(rv) && nextMsg)
+ {
+ uint32_t msgSize = 0;
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+ nextMsg->GetFolder(getter_AddRefs(msgFolder));
+ NS_ASSERTION(msgFolder, "couldn't get folder for next msg in multiple msg local copy");
+ if (msgFolder)
+ {
+ nsCString uri;
+ msgFolder->GetUriForMsg(nextMsg, uri);
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl);
+ if (msgUrl)
+ {
+ msgUrl->SetOriginalSpec(uri.get());
+ msgUrl->SetUri(uri.get());
+
+ uint64_t msgOffset;
+ nextMsg->GetMessageOffset(&msgOffset);
+ nextMsg->GetMessageSize(&msgSize);
+ // now we have to seek to the right position in the file and
+ // basically re-initialize the transport with the correct message size.
+ // then, we have to make sure the url keeps running somehow.
+ nsCOMPtr<nsISupports> urlSupports = do_QueryInterface(m_runningUrl);
+ //
+ // put us in a state where we are always notified of incoming data
+ //
+
+ m_transport = nullptr; // open new stream transport
+ m_inputStream = nullptr;
+ m_outputStream = nullptr;
+
+ if (m_multipleMsgMoveCopyStream)
+ {
+ rv = OpenMultipleMsgTransport(msgOffset, msgSize);
+ }
+ else
+ {
+ nsCOMPtr<nsIInputStream> stream;
+ bool reusable = false;
+ rv = msgFolder->GetMsgInputStream(nextMsg, &reusable,
+ getter_AddRefs(stream));
+ NS_ASSERTION(!reusable, "We thought streams were not reusable!");
+
+ if (NS_SUCCEEDED(rv))
+ {
+ // create input stream transport
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ m_readCount = msgSize;
+ rv = sts->CreateInputTransport(stream, msgOffset,
+ int64_t(msgSize), true,
+ getter_AddRefs(m_transport));
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv))
+ {
+ if (!m_inputStream)
+ rv = m_transport->OpenInputStream(0, 0, 0, getter_AddRefs(m_inputStream));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), m_inputStream);
+ if (NS_SUCCEEDED(rv)) {
+ rv = pump->AsyncRead(this, urlSupports);
+ if (NS_SUCCEEDED(rv))
+ m_request = pump;
+ }
+ }
+ }
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncRead failed");
+ if (m_loadGroup)
+ m_loadGroup->RemoveRequest(static_cast<nsIRequest *>(this), nullptr, aStatus);
+ m_socketIsOpen = true; // mark the channel as open
+ return aStatus;
+ }
+ }
+ }
+ }
+ else
+ {
+ }
+ }
+ }
+ }
+ // and we want to mark ourselves for deletion or some how inform our protocol manager that we are
+ // available for another url if there is one.
+
+ // mscott --> maybe we should set our state to done because we don't run multiple urls in a mailbox
+ // protocol connection....
+ m_nextState = MAILBOX_DONE;
+
+ // the following is for smoke test purposes. QA is looking at this "Mailbox Done" string which
+ // is printed out to the console and determining if the mail app loaded up correctly...obviously
+ // this solution is not very good so we should look at something better, but don't remove this
+ // line before talking to me (mscott) and mailnews QA....
+
+ MOZ_LOG(MAILBOX, mozilla::LogLevel::Info, ("Mailbox Done\n"));
+
+ // when on stop binding is called, we as the protocol are done...let's close down the connection
+ // releasing all of our interfaces. It's important to remember that this on stop binding call
+ // is coming from netlib so they are never going to ping us again with on data available. This means
+ // we'll never be going through the Process loop...
+
+ if (m_multipleMsgMoveCopyStream)
+ {
+ m_multipleMsgMoveCopyStream->Close();
+ m_multipleMsgMoveCopyStream = nullptr;
+ }
+ nsMsgProtocol::OnStopRequest(request, ctxt, aStatus);
+ return CloseSocket();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// End of nsIStreamListenerSupport
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsMailboxProtocol::DoneReadingMessage()
+{
+ nsresult rv = NS_OK;
+ // and close the article file if it was open....
+
+ if (m_mailboxAction == nsIMailboxUrl::ActionSaveMessageToDisk && m_msgFileOutputStream)
+ rv = m_msgFileOutputStream->Close();
+
+ return rv;
+}
+
+nsresult nsMailboxProtocol::SetupMessageExtraction()
+{
+ // Determine the number of bytes we are going to need to read out of the
+ // mailbox url....
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = NS_OK;
+
+ NS_ASSERTION(m_runningUrl, "Not running a url");
+ if (m_runningUrl)
+ {
+ uint32_t messageSize = 0;
+ m_runningUrl->GetMessageSize(&messageSize);
+ if (!messageSize)
+ {
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr)
+ {
+ msgHdr->GetMessageSize(&messageSize);
+ m_runningUrl->SetMessageSize(messageSize);
+ msgHdr->GetMessageOffset(&m_msgOffset);
+ }
+ else
+ NS_ASSERTION(false, "couldn't get message header");
+ }
+ }
+ return rv;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Begin protocol state machine functions...
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsMailboxProtocol::LoadUrl(nsIURI * aURL, nsISupports * aConsumer)
+{
+ nsresult rv = NS_OK;
+ // if we were already initialized with a consumer, use it...
+ nsCOMPtr<nsIStreamListener> consumer = do_QueryInterface(aConsumer);
+ if (consumer)
+ m_channelListener = consumer;
+
+ if (aURL)
+ {
+ m_runningUrl = do_QueryInterface(aURL);
+ if (m_runningUrl)
+ {
+ // find out from the url what action we are supposed to perform...
+ rv = m_runningUrl->GetMailboxAction(&m_mailboxAction);
+
+ bool convertData = false;
+
+ // need to check if we're fetching an rfc822 part in order to
+ // quote a message.
+ if (m_mailboxAction == nsIMailboxUrl::ActionFetchMessage)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(m_runningUrl, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString queryStr;
+ rv = msgUrl->GetQuery(queryStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // check if this is a filter plugin requesting the message.
+ // in that case, set up a text converter
+ convertData = (queryStr.Find("header=filter") != -1 ||
+ queryStr.Find("header=attach") != -1);
+ }
+ else if (m_mailboxAction == nsIMailboxUrl::ActionFetchPart)
+ {
+ // when fetching a part, we need to insert a converter into the listener chain order to
+ // force just the part out of the message. Our channel listener is the consumer we'll
+ // pass in to AsyncConvertData.
+ convertData = true;
+ consumer = m_channelListener;
+ }
+ if (convertData)
+ {
+ nsCOMPtr<nsIStreamConverterService> streamConverter = do_GetService("@mozilla.org/streamConverters;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIStreamListener> conversionListener;
+ nsCOMPtr<nsIChannel> channel;
+ QueryInterface(NS_GET_IID(nsIChannel), getter_AddRefs(channel));
+
+ rv = streamConverter->AsyncConvertData("message/rfc822",
+ "*/*",
+ consumer, channel, getter_AddRefs(m_channelListener));
+ }
+
+ if (NS_SUCCEEDED(rv))
+ {
+ switch (m_mailboxAction)
+ {
+ case nsIMailboxUrl::ActionParseMailbox:
+ // extract the mailbox parser..
+ rv = m_runningUrl->GetMailboxParser(getter_AddRefs(m_mailboxParser));
+ m_nextState = MAILBOX_READ_FOLDER;
+ break;
+ case nsIMailboxUrl::ActionSaveMessageToDisk:
+ // ohhh, display message already writes a msg to disk (as part of a hack)
+ // so we can piggy back off of that!! We just need to change m_tempMessageFile
+ // to be the name of our save message to disk file. Since save message to disk
+ // urls are run without a docshell to display the msg into, we won't be trying
+ // to display the message after we write it to disk...
+ {
+ nsCOMPtr<nsIMsgMessageUrl> messageUrl = do_QueryInterface(m_runningUrl, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ messageUrl->GetMessageFile(getter_AddRefs(m_tempMessageFile));
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_msgFileOutputStream), m_tempMessageFile, -1, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool addDummyEnvelope = false;
+ messageUrl->GetAddDummyEnvelope(&addDummyEnvelope);
+ if (addDummyEnvelope)
+ SetFlag(MAILBOX_MSG_PARSE_FIRST_LINE);
+ else
+ ClearFlag(MAILBOX_MSG_PARSE_FIRST_LINE);
+ }
+ }
+ m_nextState = MAILBOX_READ_MESSAGE;
+ break;
+ case nsIMailboxUrl::ActionCopyMessage:
+ case nsIMailboxUrl::ActionMoveMessage:
+ case nsIMailboxUrl::ActionFetchMessage:
+ ClearFlag(MAILBOX_MSG_PARSE_FIRST_LINE);
+ m_nextState = MAILBOX_READ_MESSAGE;
+ break;
+ case nsIMailboxUrl::ActionFetchPart:
+ m_nextState = MAILBOX_READ_MESSAGE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ rv = nsMsgProtocol::LoadUrl(aURL, m_channelListener);
+
+ } // if we received an MAILBOX url...
+ } // if we received a url!
+
+ return rv;
+}
+
+int32_t nsMailboxProtocol::ReadFolderResponse(nsIInputStream * inputStream, uint64_t sourceOffset, uint32_t length)
+{
+ // okay we are doing a folder read in 8K chunks of a mail folder....
+ // this is almost too easy....we can just forward the data in this stream on to our
+ // folder parser object!!!
+
+ nsresult rv = NS_OK;
+ mCurrentProgress += length;
+
+ if (m_mailboxParser)
+ {
+ nsCOMPtr <nsIURI> url = do_QueryInterface(m_runningUrl);
+ rv = m_mailboxParser->OnDataAvailable(nullptr, url, inputStream, sourceOffset, length); // let the parser deal with it...
+ }
+ if (NS_FAILED(rv))
+ {
+ m_nextState = MAILBOX_ERROR_DONE; // drop out of the loop....
+ return -1;
+ }
+
+ // now wait for the next 8K chunk to come in.....
+ SetFlag(MAILBOX_PAUSE_FOR_READ);
+
+ // leave our state alone so when the next chunk of the mailbox comes in we jump to this state
+ // and repeat....how does this process end? Well when the file is done being read in, core net lib
+ // will issue an ::OnStopRequest to us...we'll use that as our sign to drop out of this state and to
+ // close the protocol instance...
+
+ return 0;
+}
+
+int32_t nsMailboxProtocol::ReadMessageResponse(nsIInputStream * inputStream, uint64_t sourceOffset, uint32_t length)
+{
+ char *line = nullptr;
+ uint32_t status = 0;
+ nsresult rv = NS_OK;
+ mCurrentProgress += length;
+
+ // if we are doing a move or a copy, forward the data onto the copy handler...
+ // if we want to display the message then parse the incoming data...
+
+ if (m_channelListener)
+ {
+ // just forward the data we read in to the listener...
+ rv = m_channelListener->OnDataAvailable(this, m_channelContext, inputStream, sourceOffset, length);
+ }
+ else
+ {
+ bool pauseForMoreData = false;
+ bool canonicalLineEnding = false;
+ nsCOMPtr<nsIMsgMessageUrl> msgurl = do_QueryInterface(m_runningUrl);
+
+ if (msgurl)
+ msgurl->GetCanonicalLineEnding(&canonicalLineEnding);
+
+ while ((line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData)) &&
+ !pauseForMoreData)
+ {
+ /* When we're sending this line to a converter (ie,
+ it's a message/rfc822) use the local line termination
+ convention, not CRLF. This makes text articles get
+ saved with the local line terminators. Since SMTP
+ and NNTP mandate the use of CRLF, it is expected that
+ the local system will convert that to the local line
+ terminator as it is read.
+ */
+ // mscott - the firstline hack is aimed at making sure we don't write
+ // out the dummy header when we are trying to display the message.
+ // The dummy header is the From line with the date tag on it.
+ if (m_msgFileOutputStream && TestFlag(MAILBOX_MSG_PARSE_FIRST_LINE))
+ {
+ uint32_t count = 0;
+ rv = m_msgFileOutputStream->Write(line, PL_strlen(line), &count);
+ if (NS_FAILED(rv))
+ break;
+
+ if (canonicalLineEnding)
+ rv = m_msgFileOutputStream->Write(CRLF, 2, &count);
+ else
+ rv = m_msgFileOutputStream->Write(MSG_LINEBREAK,
+ MSG_LINEBREAK_LEN, &count);
+
+ if (NS_FAILED(rv))
+ break;
+ }
+ else
+ SetFlag(MAILBOX_MSG_PARSE_FIRST_LINE);
+ PR_Free(line);
+ }
+ PR_Free(line);
+ }
+
+ SetFlag(MAILBOX_PAUSE_FOR_READ); // wait for more data to become available...
+ if (mProgressEventSink && m_runningUrl)
+ {
+ int64_t maxProgress;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
+ mailnewsUrl->GetMaxProgress(&maxProgress);
+ mProgressEventSink->OnProgress(this, m_channelContext,
+ mCurrentProgress,
+ maxProgress);
+ }
+
+ if (NS_FAILED(rv)) return -1;
+
+ return 0;
+}
+
+
+/*
+ * returns negative if the transfer is finished or error'd out
+ *
+ * returns zero or more if the transfer needs to be continued.
+ */
+nsresult nsMailboxProtocol::ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream, uint64_t offset, uint32_t length)
+{
+ nsresult rv = NS_OK;
+ int32_t status = 0;
+ ClearFlag(MAILBOX_PAUSE_FOR_READ); /* already paused; reset */
+
+ while(!TestFlag(MAILBOX_PAUSE_FOR_READ))
+ {
+
+ switch(m_nextState)
+ {
+ case MAILBOX_READ_MESSAGE:
+ if (inputStream == nullptr)
+ SetFlag(MAILBOX_PAUSE_FOR_READ);
+ else
+ status = ReadMessageResponse(inputStream, offset, length);
+ break;
+ case MAILBOX_READ_FOLDER:
+ if (inputStream == nullptr)
+ SetFlag(MAILBOX_PAUSE_FOR_READ); // wait for file socket to read in the next chunk...
+ else
+ status = ReadFolderResponse(inputStream, offset, length);
+ break;
+ case MAILBOX_DONE:
+ case MAILBOX_ERROR_DONE:
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> anotherUrl = do_QueryInterface(m_runningUrl);
+ rv = m_nextState == MAILBOX_DONE ? NS_OK : NS_ERROR_FAILURE;
+ anotherUrl->SetUrlState(false, rv);
+ m_nextState = MAILBOX_FREE;
+ }
+ break;
+
+ case MAILBOX_FREE:
+ // MAILBOX is a one time use connection so kill it if we get here...
+ CloseSocket();
+ return rv; /* final end */
+
+ default: /* should never happen !!! */
+ m_nextState = MAILBOX_ERROR_DONE;
+ break;
+ }
+
+ /* check for errors during load and call error
+ * state if found
+ */
+ if(status < 0 && m_nextState != MAILBOX_FREE)
+ {
+ m_nextState = MAILBOX_ERROR_DONE;
+ /* don't exit! loop around again and do the free case */
+ ClearFlag(MAILBOX_PAUSE_FOR_READ);
+ }
+ } /* while(!MAILBOX_PAUSE_FOR_READ) */
+
+ return rv;
+}
+
+nsresult nsMailboxProtocol::CloseSocket()
+{
+ // how do you force a release when closing the connection??
+ nsMsgProtocol::CloseSocket();
+ m_runningUrl = nullptr;
+ m_mailboxParser = nullptr;
+ return NS_OK;
+}
+
+// vim: ts=2 sw=2
diff --git a/mailnews/local/src/nsMailboxProtocol.h b/mailnews/local/src/nsMailboxProtocol.h
new file mode 100644
index 000000000..36c892069
--- /dev/null
+++ b/mailnews/local/src/nsMailboxProtocol.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMailboxProtocol_h___
+#define nsMailboxProtocol_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMsgProtocol.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIOutputStream.h"
+#include "nsIMailboxUrl.h"
+// State Flags (Note, I use the word state in terms of storing
+// state information about the connection (authentication, have we sent
+// commands, etc. I do not intend it to refer to protocol state)
+
+#define MAILBOX_PAUSE_FOR_READ 0x00000001 /* should we pause for the next read */
+#define MAILBOX_MSG_PARSE_FIRST_LINE 0x00000002 /* have we read in the first line of the msg */
+
+/* states of the machine
+ */
+typedef enum _MailboxStatesEnum {
+ MAILBOX_READ_FOLDER,
+ MAILBOX_FINISH_OPEN_FOLDER,
+ MAILBOX_OPEN_MESSAGE,
+ MAILBOX_OPEN_STREAM,
+ MAILBOX_READ_MESSAGE,
+ MAILBOX_COMPRESS_FOLDER,
+ MAILBOX_FINISH_COMPRESS_FOLDER,
+ MAILBOX_BACKGROUND,
+ MAILBOX_NULL,
+ MAILBOX_NULL2,
+ MAILBOX_DELIVER_QUEUED,
+ MAILBOX_FINISH_DELIVER_QUEUED,
+ MAILBOX_DONE,
+ MAILBOX_ERROR_DONE,
+ MAILBOX_FREE,
+ MAILBOX_COPY_MESSAGES,
+ MAILBOX_FINISH_COPY_MESSAGES
+} MailboxStatesEnum;
+
+class nsMsgLineStreamBuffer;
+
+class nsMailboxProtocol : public nsMsgProtocol
+{
+public:
+ // Creating a protocol instance requires the URL which needs to be run AND it requires
+ // a transport layer.
+ explicit nsMailboxProtocol(nsIURI * aURL);
+ virtual ~nsMailboxProtocol();
+
+ // initialization function given a new url and transport layer
+ nsresult Initialize(nsIURI * aURL);
+
+ // the consumer of the url might be something like an nsIDocShell....
+ virtual nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer) override;
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we suppport the nsIStreamListener interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ NS_IMETHOD OnStartRequest(nsIRequest *request, nsISupports *ctxt) override;
+ NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus) override;
+
+private:
+ nsCOMPtr<nsIMailboxUrl> m_runningUrl; // the nsIMailboxURL that is currently running
+ nsMailboxAction m_mailboxAction; // current mailbox action associated with this connnection...
+ uint64_t m_msgOffset;
+ // Event sink handles
+ nsCOMPtr<nsIStreamListener> m_mailboxParser;
+
+ // Local state for the current operation
+ RefPtr<nsMsgLineStreamBuffer> m_lineStreamBuffer; // used to efficiently extract lines from the incoming data stream
+
+ // Generic state information -- What state are we in? What state do we want to go to
+ // after the next response? What was the last response code? etc.
+ MailboxStatesEnum m_nextState;
+ MailboxStatesEnum m_initialState;
+
+ int64_t mCurrentProgress;
+
+ // can we just use the base class m_tempMsgFile?
+ nsCOMPtr<nsIFile> m_tempMessageFile;
+ nsCOMPtr<nsIOutputStream> m_msgFileOutputStream;
+
+ // this is used to hold the source mailbox file open when move/copying
+ // multiple messages.
+ nsCOMPtr<nsIInputStream> m_multipleMsgMoveCopyStream;
+
+ virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length) override;
+ virtual nsresult CloseSocket() override;
+
+ nsresult SetupMessageExtraction();
+ nsresult OpenMultipleMsgTransport(uint64_t offset, int32_t size);
+ bool RunningMultipleMsgUrl();
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Protocol Methods --> This protocol is state driven so each protocol method is
+ // designed to re-act to the current "state". I've attempted to
+ // group them together based on functionality.
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ // When parsing a mailbox folder in chunks, this protocol state reads in the current chunk
+ // and forwards it to the mailbox parser.
+ int32_t ReadFolderResponse(nsIInputStream * inputStream, uint64_t sourceOffset, uint32_t length);
+ int32_t ReadMessageResponse(nsIInputStream * inputStream, uint64_t sourceOffset, uint32_t length);
+ nsresult DoneReadingMessage();
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // End of Protocol Methods
+ ////////////////////////////////////////////////////////////////////////////////////////
+};
+
+#endif // nsMailboxProtocol_h___
+
+
+
+
+
+
diff --git a/mailnews/local/src/nsMailboxServer.cpp b/mailnews/local/src/nsMailboxServer.cpp
new file mode 100644
index 000000000..005204f0d
--- /dev/null
+++ b/mailnews/local/src/nsMailboxServer.cpp
@@ -0,0 +1,33 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMailboxServer.h"
+#include "nsLocalMailFolder.h"
+
+NS_IMETHODIMP
+nsMailboxServer::GetLocalStoreType(nsACString& type)
+{
+ type.AssignLiteral("mailbox");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailboxServer::GetLocalDatabaseType(nsACString& type)
+{
+ type.AssignLiteral("mailbox");
+ return NS_OK;
+}
+
+nsresult
+nsMailboxServer::CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder)
+{
+ nsMsgLocalMailFolder *newRootFolder = new nsMsgLocalMailFolder;
+ if (!newRootFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*rootFolder = newRootFolder);
+ newRootFolder->Init(serverUri.get());
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsMailboxServer.h b/mailnews/local/src/nsMailboxServer.h
new file mode 100644
index 000000000..70e914f52
--- /dev/null
+++ b/mailnews/local/src/nsMailboxServer.h
@@ -0,0 +1,22 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMailboxServer_h__
+#define nsMailboxServer_h__
+
+#include "mozilla/Attributes.h"
+#include "nsMsgIncomingServer.h"
+
+class nsMailboxServer : public nsMsgIncomingServer
+{
+public:
+ NS_IMETHOD GetLocalStoreType(nsACString& type) override;
+ NS_IMETHOD GetLocalDatabaseType(nsACString& type) override;
+protected:
+ virtual nsresult CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder) override;
+};
+
+#endif
diff --git a/mailnews/local/src/nsMailboxService.cpp b/mailnews/local/src/nsMailboxService.cpp
new file mode 100644
index 000000000..00a0d87c8
--- /dev/null
+++ b/mailnews/local/src/nsMailboxService.cpp
@@ -0,0 +1,677 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nsCOMPtr.h"
+
+#include "nsMailboxService.h"
+#include "nsMailboxUrl.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMailboxProtocol.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgDBCID.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsLocalUtils.h"
+#include "nsMsgLocalCID.h"
+#include "nsMsgBaseCID.h"
+#include "nsIDocShell.h"
+#include "nsIPop3Service.h"
+#include "nsMsgUtils.h"
+#include "nsNetUtil.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIWebNavigation.h"
+#include "prprf.h"
+#include "nsIMsgHdr.h"
+#include "nsIFileURL.h"
+#include "mozilla/RefPtr.h"
+
+nsMailboxService::nsMailboxService()
+{
+ mPrintingOperation = false;
+}
+
+nsMailboxService::~nsMailboxService()
+{}
+
+NS_IMPL_ISUPPORTS(nsMailboxService, nsIMailboxService, nsIMsgMessageService, nsIProtocolHandler, nsIMsgMessageFetchPartService)
+
+nsresult nsMailboxService::ParseMailbox(nsIMsgWindow *aMsgWindow, nsIFile *aMailboxPath, nsIStreamListener *aMailboxParser,
+ nsIUrlListener * aUrlListener, nsIURI ** aURL)
+{
+ NS_ENSURE_ARG_POINTER(aMailboxPath);
+
+ nsresult rv;
+ nsCOMPtr<nsIMailboxUrl> mailboxurl =
+ do_CreateInstance(NS_MAILBOXURL_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && mailboxurl)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(mailboxurl);
+ // okay now generate the url string
+ nsCString mailboxPath;
+
+ aMailboxPath->GetNativePath(mailboxPath);
+ nsAutoCString buf;
+ MsgEscapeURL(mailboxPath,
+ nsINetUtil::ESCAPE_URL_MINIMAL | nsINetUtil::ESCAPE_URL_FORCED, buf);
+ nsEscapeNativePath(buf);
+ url->SetUpdatingFolder(true);
+ url->SetMsgWindow(aMsgWindow);
+ nsAutoCString uriSpec("mailbox://");
+ uriSpec.Append(buf);
+ rv = url->SetSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mailboxurl->SetMailboxParser(aMailboxParser);
+ if (aUrlListener)
+ url->RegisterListener(aUrlListener);
+
+ rv = RunMailboxUrl(url, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aURL)
+ {
+ *aURL = url;
+ NS_IF_ADDREF(*aURL);
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsMailboxService::CopyMessage(const char * aSrcMailboxURI,
+ nsIStreamListener * aMailboxCopyHandler,
+ bool moveMessage,
+ nsIUrlListener * aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ nsMailboxAction mailboxAction = nsIMailboxUrl::ActionMoveMessage;
+ if (!moveMessage)
+ mailboxAction = nsIMailboxUrl::ActionCopyMessage;
+ return FetchMessage(aSrcMailboxURI, aMailboxCopyHandler, aMsgWindow, aUrlListener, nullptr, mailboxAction, nullptr, aURL);
+}
+
+nsresult nsMailboxService::CopyMessages(uint32_t aNumKeys,
+ nsMsgKey* aMsgKeys,
+ nsIMsgFolder *srcFolder,
+ nsIStreamListener * aMailboxCopyHandler,
+ bool moveMessage,
+ nsIUrlListener * aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG(srcFolder);
+ NS_ENSURE_ARG(aMsgKeys);
+ nsCOMPtr<nsIMailboxUrl> mailboxurl;
+
+ nsMailboxAction actionToUse = nsIMailboxUrl::ActionMoveMessage;
+ if (!moveMessage)
+ actionToUse = nsIMailboxUrl::ActionCopyMessage;
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsCOMPtr <nsIMsgDatabase> db;
+ srcFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (db)
+ {
+ db->GetMsgHdrForKey(aMsgKeys[0], getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ nsCString uri;
+ srcFolder->GetUriForMsg(msgHdr, uri);
+ rv = PrepareMessageUrl(uri.get(), aUrlListener, actionToUse , getter_AddRefs(mailboxurl), aMsgWindow);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIURI> url = do_QueryInterface(mailboxurl);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(url));
+ nsCOMPtr<nsIMailboxUrl> mailboxUrl (do_QueryInterface(url));
+ msgUrl->SetMsgWindow(aMsgWindow);
+
+ mailboxUrl->SetMoveCopyMsgKeys(aMsgKeys, aNumKeys);
+ rv = RunMailboxUrl(url, aMailboxCopyHandler);
+ }
+ }
+ }
+ if (aURL && mailboxurl)
+ CallQueryInterface(mailboxurl, aURL);
+
+ return rv;
+}
+
+nsresult nsMailboxService::FetchMessage(const char* aMessageURI,
+ nsISupports * aDisplayConsumer,
+ nsIMsgWindow * aMsgWindow,
+ nsIUrlListener * aUrlListener,
+ const char * aFileName, /* only used by open attachment... */
+ nsMailboxAction mailboxAction,
+ const char * aCharsetOverride,
+ nsIURI ** aURL)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMailboxUrl> mailboxurl;
+ nsMailboxAction actionToUse = mailboxAction;
+ nsCOMPtr<nsIURI> url;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
+ nsAutoCString uriString(aMessageURI);
+
+ if (!strncmp(aMessageURI, "file:", 5))
+ {
+ int64_t fileSize;
+ nsCOMPtr<nsIURI> fileUri;
+ rv = NS_NewURI(getter_AddRefs(fileUri), aMessageURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFileURL> fileUrl = do_QueryInterface(fileUri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ file->GetFileSize(&fileSize);
+ uriString.Replace(0, 5, NS_LITERAL_CSTRING("mailbox:"));
+ uriString.Append(NS_LITERAL_CSTRING("&number=0"));
+ rv = NS_NewURI(getter_AddRefs(url), uriString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgUrl = do_QueryInterface(url);
+ if (msgUrl)
+ {
+ msgUrl->SetMsgWindow(aMsgWindow);
+ nsCOMPtr <nsIMailboxUrl> mailboxUrl = do_QueryInterface(msgUrl, &rv);
+ mailboxUrl->SetMessageSize((uint32_t) fileSize);
+ nsCOMPtr <nsIMsgHeaderSink> headerSink;
+ // need to tell the header sink to capture some headers to create a fake db header
+ // so we can do reply to a .eml file or a rfc822 msg attachment.
+ if (aMsgWindow)
+ aMsgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink));
+ if (headerSink)
+ {
+ nsCOMPtr <nsIMsgDBHdr> dummyHeader;
+ headerSink->GetDummyMsgHeader(getter_AddRefs(dummyHeader));
+ if (dummyHeader)
+ dummyHeader->SetMessageSize((uint32_t) fileSize);
+ }
+ }
+ }
+ else
+ {
+ // this happens with forward inline of message/rfc822 attachment
+ // opened in a stand-alone msg window.
+ int32_t typeIndex = uriString.Find("&type=application/x-message-display");
+ if (typeIndex != -1)
+ {
+ uriString.Cut(typeIndex, sizeof("&type=application/x-message-display") - 1);
+ rv = NS_NewURI(getter_AddRefs(url), uriString.get());
+ mailboxurl = do_QueryInterface(url);
+ }
+ else
+ rv = PrepareMessageUrl(aMessageURI, aUrlListener, actionToUse , getter_AddRefs(mailboxurl), aMsgWindow);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ url = do_QueryInterface(mailboxurl);
+ msgUrl = do_QueryInterface(url);
+ msgUrl->SetMsgWindow(aMsgWindow);
+ if (aFileName)
+ msgUrl->SetFileName(nsDependentCString(aFileName));
+ }
+ }
+
+ nsCOMPtr<nsIMsgI18NUrl> i18nurl(do_QueryInterface(msgUrl));
+ if (i18nurl)
+ i18nurl->SetCharsetOverRide(aCharsetOverride);
+
+ // instead of running the mailbox url like we used to, let's try to run the url in the docshell...
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ // if we were given a docShell, run the url in the docshell..otherwise just run it normally.
+ if (NS_SUCCEEDED(rv) && docShell)
+ {
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ // DIRTY LITTLE HACK --> if we are opening an attachment we want the docshell to
+ // treat this load as if it were a user click event. Then the dispatching stuff will be much
+ // happier.
+ if (mailboxAction == nsIMailboxUrl::ActionFetchPart)
+ {
+ docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadLink);
+ }
+ rv = docShell->LoadURI(url, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false);
+ }
+ else
+ rv = RunMailboxUrl(url, aDisplayConsumer);
+
+ if (aURL && mailboxurl)
+ CallQueryInterface(mailboxurl, aURL);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxService::FetchMimePart(nsIURI *aURI, const char *aMessageURI, nsISupports *aDisplayConsumer, nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener, nsIURI **aURL)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(aURI, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgUrl->SetMsgWindow(aMsgWindow);
+
+ // set up the url listener
+ if (aUrlListener)
+ msgUrl->RegisterListener(aUrlListener);
+
+ return RunMailboxUrl(msgUrl, aDisplayConsumer);
+}
+
+NS_IMETHODIMP nsMailboxService::DisplayMessage(const char* aMessageURI,
+ nsISupports * aDisplayConsumer,
+ nsIMsgWindow * aMsgWindow,
+ nsIUrlListener * aUrlListener,
+ const char * aCharsetOveride,
+ nsIURI ** aURL)
+{
+ return FetchMessage(aMessageURI, aDisplayConsumer,
+ aMsgWindow,aUrlListener, nullptr,
+ nsIMailboxUrl::ActionFetchMessage, aCharsetOveride, aURL);
+}
+
+NS_IMETHODIMP
+nsMailboxService::StreamMessage(const char *aMessageURI,
+ nsISupports *aConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ bool /* aConvertData */,
+ const nsACString &aAdditionalHeader,
+ bool aLocalOnly,
+ nsIURI **aURL)
+{
+ // The mailbox protocol object will look for "header=filter" or
+ // "header=attach" to decide if it wants to convert the data instead of
+ // using aConvertData. It turns out to be way too hard to pass aConvertData
+ // all the way over to the mailbox protocol object.
+ nsAutoCString aURIString(aMessageURI);
+ if (!aAdditionalHeader.IsEmpty())
+ {
+ aURIString.FindChar('?') == -1 ? aURIString += "?" : aURIString += "&";
+ aURIString += "header=";
+ aURIString += aAdditionalHeader;
+ }
+
+ return FetchMessage(aURIString.get(), aConsumer, aMsgWindow, aUrlListener, nullptr,
+ nsIMailboxUrl::ActionFetchMessage, nullptr, aURL);
+}
+
+NS_IMETHODIMP nsMailboxService::StreamHeaders(const char *aMessageURI,
+ nsIStreamListener *aConsumer,
+ nsIUrlListener *aUrlListener,
+ bool aLocalOnly,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+ NS_ENSURE_ARG_POINTER(aConsumer);
+ nsAutoCString folderURI;
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = DecomposeMailboxURI(aMessageURI, getter_AddRefs(folder), &msgKey);
+ if (msgKey == nsMsgKey_None)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ int64_t messageOffset;
+ uint32_t messageSize;
+ rv = folder->GetOfflineFileStream(msgKey, &messageOffset, &messageSize, getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // GetOfflineFileStream returns NS_OK but null inputStream when there is an error getting the database
+ if (!inputStream)
+ return NS_ERROR_FAILURE;
+ return MsgStreamMsgHeaders(inputStream, aConsumer);
+}
+
+
+NS_IMETHODIMP nsMailboxService::IsMsgInMemCache(nsIURI *aUrl,
+ nsIMsgFolder *aFolder,
+ bool *aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMailboxService::OpenAttachment(const char *aContentType,
+ const char *aFileName,
+ const char *aUrl,
+ const char *aMessageUri,
+ nsISupports *aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener)
+{
+ nsCOMPtr <nsIURI> URL;
+ nsAutoCString urlString(aUrl);
+ urlString += "&type=";
+ urlString += aContentType;
+ urlString += "&filename=";
+ urlString += aFileName;
+ CreateStartupUrl(urlString.get(), getter_AddRefs(URL));
+ nsresult rv;
+
+ // try to run the url in the docshell...
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ // if we were given a docShell, run the url in the docshell..otherwise just run it normally.
+ if (NS_SUCCEEDED(rv) && docShell)
+ {
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ // DIRTY LITTLE HACK --> since we are opening an attachment we want the docshell to
+ // treat this load as if it were a user click event. Then the dispatching stuff will be much
+ // happier.
+ docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadLink);
+ return docShell->LoadURI(URL, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false);
+ }
+ return RunMailboxUrl(URL, aDisplayConsumer);
+
+}
+
+
+NS_IMETHODIMP
+nsMailboxService::SaveMessageToDisk(const char *aMessageURI,
+ nsIFile *aFile,
+ bool aAddDummyEnvelope,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ bool canonicalLineEnding,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMailboxUrl> mailboxurl;
+
+ rv = PrepareMessageUrl(aMessageURI, aUrlListener, nsIMailboxUrl::ActionSaveMessageToDisk, getter_AddRefs(mailboxurl), aMsgWindow);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(mailboxurl);
+ if (msgUrl)
+ {
+ msgUrl->SetMessageFile(aFile);
+ msgUrl->SetAddDummyEnvelope(aAddDummyEnvelope);
+ msgUrl->SetCanonicalLineEnding(canonicalLineEnding);
+ }
+
+ nsCOMPtr<nsIURI> url = do_QueryInterface(mailboxurl);
+ rv = RunMailboxUrl(url);
+ }
+
+ if (aURL && mailboxurl)
+ CallQueryInterface(mailboxurl, aURL);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxService::GetUrlForUri(const char *aMessageURI, nsIURI **aURL, nsIMsgWindow *aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aURL);
+ if (!strncmp(aMessageURI, "file:", 5) || PL_strstr(aMessageURI, "type=application/x-message-display")
+ || !strncmp(aMessageURI, "mailbox:", 8))
+ return NS_NewURI(aURL, aMessageURI);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMailboxUrl> mailboxurl;
+ rv = PrepareMessageUrl(aMessageURI, nullptr, nsIMailboxUrl::ActionFetchMessage, getter_AddRefs(mailboxurl), aMsgWindow);
+ if (NS_SUCCEEDED(rv) && mailboxurl)
+ rv = CallQueryInterface(mailboxurl, aURL);
+ return rv;
+}
+
+// Takes a mailbox url, this method creates a protocol instance and loads the url
+// into the protocol instance.
+nsresult nsMailboxService::RunMailboxUrl(nsIURI * aMailboxUrl, nsISupports * aDisplayConsumer)
+{
+ // create a protocol instance to run the url..
+ nsresult rv = NS_OK;
+ nsMailboxProtocol * protocol = new nsMailboxProtocol(aMailboxUrl);
+
+ if (protocol)
+ {
+ rv = protocol->Initialize(aMailboxUrl);
+ if (NS_FAILED(rv))
+ {
+ delete protocol;
+ return rv;
+ }
+ NS_ADDREF(protocol);
+ rv = protocol->LoadUrl(aMailboxUrl, aDisplayConsumer);
+ NS_RELEASE(protocol); // after loading, someone else will have a ref cnt on the mailbox
+ }
+
+ return rv;
+}
+
+// This function takes a message uri, converts it into a file path & msgKey
+// pair. It then turns that into a mailbox url object. It also registers a url
+// listener if appropriate. AND it can take in a mailbox action and set that field
+// on the returned url as well.
+nsresult nsMailboxService::PrepareMessageUrl(const char * aSrcMsgMailboxURI, nsIUrlListener * aUrlListener,
+ nsMailboxAction aMailboxAction, nsIMailboxUrl ** aMailboxUrl,
+ nsIMsgWindow *msgWindow)
+{
+ nsresult rv = CallCreateInstance(NS_MAILBOXURL_CONTRACTID, aMailboxUrl);
+ if (NS_SUCCEEDED(rv) && aMailboxUrl && *aMailboxUrl)
+ {
+ // okay now generate the url string
+ char * urlSpec;
+ nsAutoCString folderURI;
+ nsMsgKey msgKey;
+ nsCString folderPath;
+ const char *part = PL_strstr(aSrcMsgMailboxURI, "part=");
+ const char *header = PL_strstr(aSrcMsgMailboxURI, "header=");
+ rv = nsParseLocalMessageURI(aSrcMsgMailboxURI, folderURI, &msgKey);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = nsLocalURI2Path(kMailboxRootURI, folderURI.get(), folderPath);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ // set up the url spec and initialize the url with it.
+ nsAutoCString buf;
+ MsgEscapeURL(folderPath,
+ nsINetUtil::ESCAPE_URL_DIRECTORY | nsINetUtil::ESCAPE_URL_FORCED, buf);
+ if (mPrintingOperation)
+ urlSpec = PR_smprintf("mailbox://%s?number=%lu&header=print", buf.get(), msgKey);
+ else if (part)
+ urlSpec = PR_smprintf("mailbox://%s?number=%lu&%s", buf.get(), msgKey, part);
+ else if (header)
+ urlSpec = PR_smprintf("mailbox://%s?number=%lu&%s", buf.get(), msgKey, header);
+ else
+ urlSpec = PR_smprintf("mailbox://%s?number=%lu", buf.get(), msgKey);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> url = do_QueryInterface(*aMailboxUrl);
+ rv = url->SetSpec(nsDependentCString(urlSpec));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PR_smprintf_free(urlSpec);
+
+ (*aMailboxUrl)->SetMailboxAction(aMailboxAction);
+
+ // set up the url listener
+ if (aUrlListener)
+ rv = url->RegisterListener(aUrlListener);
+
+ url->SetMsgWindow(msgWindow);
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(url);
+ if (msgUrl)
+ {
+ msgUrl->SetOriginalSpec(aSrcMsgMailboxURI);
+ msgUrl->SetUri(aSrcMsgMailboxURI);
+ }
+
+ } // if we got a url
+ } // if we got a url
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxService::GetScheme(nsACString &aScheme)
+{
+ aScheme = "mailbox";
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxService::GetDefaultPort(int32_t *aDefaultPort)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultPort);
+ *aDefaultPort = -1; // mailbox doesn't use a port!!!!!
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxService::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxService::GetProtocolFlags(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = URI_STD | URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ URI_DANGEROUS_TO_LOAD | URI_FORBIDS_COOKIE_ACCESS
+#ifdef IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ | ORIGIN_IS_FULL_SPEC
+#endif
+ ;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxService::NewURI(const nsACString &aSpec,
+ const char *aOriginCharset,
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = 0;
+ nsresult rv;
+ nsCOMPtr<nsIURI> aMsgUri = do_CreateInstance(NS_MAILBOXURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // SetSpec calls below may fail if the mailbox url is of the form
+ // mailbox://<account>/<mailbox name>?... instead of
+ // mailbox://<path to folder>?.... This is the case for pop3 download urls.
+ // We know this, and the failure is harmless.
+ if (aBaseURI)
+ {
+ nsAutoCString newSpec;
+ rv = aBaseURI->Resolve(aSpec, newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ (void) aMsgUri->SetSpec(newSpec);
+ }
+ else
+ {
+ (void) aMsgUri->SetSpec(aSpec);
+ }
+ aMsgUri.swap(*_retval);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxService::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+NS_IMETHODIMP nsMailboxService::NewChannel2(nsIURI *aURI,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel **_retval)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv = NS_OK;
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (spec.Find("?uidl=") >= 0 || spec.Find("&uidl=") >= 0)
+ {
+ nsCOMPtr<nsIProtocolHandler> handler =
+ do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIURI> pop3Uri;
+
+ rv = handler->NewURI(spec, "" /* ignored */, aURI, getter_AddRefs(pop3Uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return handler->NewChannel2(pop3Uri, aLoadInfo, _retval);
+ }
+ }
+
+ RefPtr<nsMailboxProtocol> protocol = new nsMailboxProtocol(aURI);
+ if (!protocol) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = protocol->Initialize(aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = protocol->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(protocol, _retval);
+}
+
+nsresult nsMailboxService::DisplayMessageForPrinting(const char* aMessageURI,
+ nsISupports * aDisplayConsumer,
+ nsIMsgWindow * aMsgWindow,
+ nsIUrlListener * aUrlListener,
+ nsIURI ** aURL)
+{
+ mPrintingOperation = true;
+ nsresult rv = FetchMessage(aMessageURI, aDisplayConsumer, aMsgWindow,aUrlListener, nullptr,
+ nsIMailboxUrl::ActionFetchMessage, nullptr, aURL);
+ mPrintingOperation = false;
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxService::Search(nsIMsgSearchSession *aSearchSession, nsIMsgWindow *aMsgWindow, nsIMsgFolder *aMsgFolder, const char *aMessageUri)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsMailboxService::DecomposeMailboxURI(const char * aMessageURI, nsIMsgFolder ** aFolder, nsMsgKey *aMsgKey)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aMsgKey);
+
+ nsresult rv = NS_OK;
+ nsAutoCString folderURI;
+ rv = nsParseLocalMessageURI(aMessageURI, folderURI, aMsgKey);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(folderURI, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = res->QueryInterface(NS_GET_IID(nsIMsgFolder), (void **) aFolder);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailboxService::MessageURIToMsgHdr(const char *uri, nsIMsgDBHdr **_retval)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey msgKey;
+
+ rv = DecomposeMailboxURI(uri, getter_AddRefs(folder), &msgKey);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = folder->GetMessageHeader(msgKey, _retval);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsMailboxService.h b/mailnews/local/src/nsMailboxService.h
new file mode 100644
index 000000000..c7fc759ee
--- /dev/null
+++ b/mailnews/local/src/nsMailboxService.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMailboxService_h___
+#define nsMailboxService_h___
+
+#include "nscore.h"
+#include "nsISupports.h"
+
+#include "nsIMailboxService.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMailboxUrl.h"
+#include "nsIURL.h"
+#include "nsIUrlListener.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsIProtocolHandler.h"
+#include "nsIRDFService.h"
+
+class nsMailboxService : public nsIMailboxService, public nsIMsgMessageService, public nsIMsgMessageFetchPartService, public nsIProtocolHandler
+{
+public:
+
+ nsMailboxService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMAILBOXSERVICE
+ NS_DECL_NSIMSGMESSAGESERVICE
+ NS_DECL_NSIMSGMESSAGEFETCHPARTSERVICE
+ NS_DECL_NSIPROTOCOLHANDLER
+
+protected:
+ virtual ~nsMailboxService();
+ bool mPrintingOperation;
+
+ // helper functions used by the service
+ nsresult PrepareMessageUrl(const char * aSrcMsgMailboxURI, nsIUrlListener * aUrlListener,
+ nsMailboxAction aMailboxAction, nsIMailboxUrl ** aMailboxUrl,
+ nsIMsgWindow *msgWindow);
+
+ nsresult RunMailboxUrl(nsIURI * aMailboxUrl, nsISupports * aDisplayConsumer = nullptr);
+
+ nsresult FetchMessage(const char* aMessageURI,
+ nsISupports * aDisplayConsumer,
+ nsIMsgWindow * aMsgWindow,
+ nsIUrlListener * aUrlListener,
+ const char * aFileName, /* only used by open attachment */
+ nsMailboxAction mailboxAction,
+ const char * aCharsetOverride,
+ nsIURI ** aURL);
+
+ nsresult DecomposeMailboxURI(const char * aMessageURI, nsIMsgFolder ** aFolder, nsMsgKey *aMsgKey);
+};
+
+#endif /* nsMailboxService_h___ */
diff --git a/mailnews/local/src/nsMailboxUrl.cpp b/mailnews/local/src/nsMailboxUrl.cpp
new file mode 100644
index 000000000..25fe4f35f
--- /dev/null
+++ b/mailnews/local/src/nsMailboxUrl.cpp
@@ -0,0 +1,556 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+
+#include "nsIURI.h"
+#include "nsIMailboxUrl.h"
+#include "nsMailboxUrl.h"
+
+#include "nsStringGlue.h"
+#include "nsLocalUtils.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgHdr.h"
+
+#include "nsIMsgFolder.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsIMsgMailSession.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+
+// this is totally lame and MUST be removed by M6
+// the real fix is to attach the URI to the URL as it runs through netlib
+// then grab it and use it on the other side
+#include "nsCOMPtr.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+// helper function for parsing the search field of a url
+char * extractAttributeValue(const char * searchString, const char * attributeName);
+
+nsMailboxUrl::nsMailboxUrl()
+{
+ m_mailboxAction = nsIMailboxUrl::ActionParseMailbox;
+ m_filePath = nullptr;
+ m_messageID = nullptr;
+ m_messageKey = nsMsgKey_None;
+ m_messageSize = 0;
+ m_messageFile = nullptr;
+ m_addDummyEnvelope = false;
+ m_canonicalLineEnding = false;
+ m_curMsgIndex = 0;
+}
+
+nsMailboxUrl::~nsMailboxUrl()
+{
+ PR_Free(m_messageID);
+}
+
+NS_IMPL_ADDREF_INHERITED(nsMailboxUrl, nsMsgMailNewsUrl)
+NS_IMPL_RELEASE_INHERITED(nsMailboxUrl, nsMsgMailNewsUrl)
+
+NS_INTERFACE_MAP_BEGIN(nsMailboxUrl)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMailboxUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMailboxUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgMessageUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgI18NUrl)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgMailNewsUrl)
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIMailboxUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+nsresult nsMailboxUrl::SetMailboxParser(nsIStreamListener * aMailboxParser)
+{
+ if (aMailboxParser)
+ m_mailboxParser = aMailboxParser;
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::GetMailboxParser(nsIStreamListener ** aConsumer)
+{
+ NS_ENSURE_ARG_POINTER(aConsumer);
+
+ NS_IF_ADDREF(*aConsumer = m_mailboxParser);
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::SetMailboxCopyHandler(nsIStreamListener * aMailboxCopyHandler)
+{
+ if (aMailboxCopyHandler)
+ m_mailboxCopyHandler = aMailboxCopyHandler;
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::GetMailboxCopyHandler(nsIStreamListener ** aMailboxCopyHandler)
+{
+ NS_ENSURE_ARG_POINTER(aMailboxCopyHandler);
+
+ if (aMailboxCopyHandler)
+ {
+ *aMailboxCopyHandler = m_mailboxCopyHandler;
+ NS_IF_ADDREF(*aMailboxCopyHandler);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::GetMessageKey(nsMsgKey* aMessageKey)
+{
+ *aMessageKey = m_messageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetMessageSize(uint32_t * aMessageSize)
+{
+ if (aMessageSize)
+ {
+ *aMessageSize = m_messageSize;
+ return NS_OK;
+ }
+ else
+ return NS_ERROR_NULL_POINTER;
+}
+
+nsresult nsMailboxUrl::SetMessageSize(uint32_t aMessageSize)
+{
+ m_messageSize = aMessageSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetPrincipalSpec(nsACString& aPrincipalSpec)
+{
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsURL;
+ QueryInterface(NS_GET_IID(nsIMsgMailNewsUrl), getter_AddRefs(mailnewsURL));
+
+ nsAutoCString spec;
+ mailnewsURL->GetSpecIgnoringRef(spec);
+
+ // mailbox: URLs contain a lot of query parts. We want need a normalised form:
+ // mailbox:///path/to/folder?number=nn.
+ // We also need to translate the second form mailbox://user@domain@server/folder?number=nn.
+
+ char* messageKey = extractAttributeValue(spec.get(), "number=");
+
+ // Strip any query part beginning with ? or /;
+ int32_t ind = spec.Find("/;");
+ if (ind != kNotFound)
+ spec.SetLength(ind);
+
+ ind = spec.FindChar('?');
+ if (ind != kNotFound)
+ spec.SetLength(ind);
+
+ // Check for format lacking absolute path.
+ if (spec.Find("///") == kNotFound) {
+ nsCString folderPath;
+ nsresult rv = nsLocalURI2Path(kMailboxRootURI, spec.get(), folderPath);
+ if (NS_SUCCEEDED (rv)) {
+ nsAutoCString buf;
+ MsgEscapeURL(folderPath,
+ nsINetUtil::ESCAPE_URL_DIRECTORY | nsINetUtil::ESCAPE_URL_FORCED, buf);
+ spec = NS_LITERAL_CSTRING("mailbox://") + buf;
+ }
+ }
+
+ spec += NS_LITERAL_CSTRING("?number=");
+ spec.Append(messageKey);
+ PR_Free(messageKey);
+
+ aPrincipalSpec.Assign(spec);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::SetUri(const char * aURI)
+{
+ mURI= aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef,
+ nsIURI **_retval)
+{
+ nsresult rv = nsMsgMailNewsUrl::CloneInternal(aRefHandlingMode,
+ newRef, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // also clone the mURI member, because GetUri below won't work if
+ // mURI isn't set due to nsIFile fun.
+ nsCOMPtr <nsIMsgMessageUrl> clonedUrl = do_QueryInterface(*_retval);
+ if (clonedUrl)
+ clonedUrl->SetUri(mURI.get());
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetUri(char ** aURI)
+{
+ // if we have been given a uri to associate with this url, then use it
+ // otherwise try to reconstruct a URI on the fly....
+
+ if (!mURI.IsEmpty())
+ *aURI = ToNewCString(mURI);
+ else
+ {
+ if (m_filePath)
+ {
+ nsAutoCString baseUri;
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we blow off errors here so that we can open attachments
+ // in .eml files.
+ (void) accountManager->FolderUriForPath(m_filePath, baseUri);
+ if (baseUri.IsEmpty()) {
+ rv = m_baseURL->GetSpec(baseUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCString baseMessageURI;
+ nsCreateLocalBaseMessageURI(baseUri, baseMessageURI);
+ nsAutoCString uriStr;
+ nsBuildLocalMessageURI(baseMessageURI.get(), m_messageKey, uriStr);
+ *aURI = ToNewCString(uriStr);
+ }
+ else
+ *aURI = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::GetMsgHdrForKey(nsMsgKey msgKey, nsIMsgDBHdr ** aMsgHdr)
+{
+ nsresult rv = NS_OK;
+ if (aMsgHdr && m_filePath)
+ {
+ nsCOMPtr<nsIMsgDatabase> mailDBFactory;
+ nsCOMPtr<nsIMsgDatabase> mailDB;
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+
+ if (msgDBService)
+ rv = msgDBService->OpenMailDBFromFile(m_filePath, nullptr, false,
+ false, getter_AddRefs(mailDB));
+ if (NS_SUCCEEDED(rv) && mailDB) // did we get a db back?
+ rv = mailDB->GetMsgHdrForKey(msgKey, aMsgHdr);
+ else
+ {
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ if (!msgWindow)
+ {
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ }
+
+ // maybe this is .eml file we're trying to read. See if we can get a header from the header sink.
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIMsgHeaderSink> headerSink;
+ msgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink));
+ if (headerSink)
+ {
+ rv = headerSink->GetDummyMsgHeader(aMsgHdr);
+ if (NS_SUCCEEDED(rv))
+ {
+ int64_t fileSize = 0;
+ m_filePath->GetFileSize(&fileSize);
+ (*aMsgHdr)->SetMessageSize(fileSize);
+ }
+ }
+ }
+ }
+ }
+ else
+ rv = NS_ERROR_NULL_POINTER;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetMessageHeader(nsIMsgDBHdr ** aMsgHdr)
+{
+ if (m_dummyHdr)
+ {
+ NS_IF_ADDREF(*aMsgHdr = m_dummyHdr);
+ return NS_OK;
+ }
+ return GetMsgHdrForKey(m_messageKey, aMsgHdr);
+}
+
+NS_IMETHODIMP nsMailboxUrl::SetMessageHeader(nsIMsgDBHdr *aMsgHdr)
+{
+ m_dummyHdr = aMsgHdr;
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsMailboxUrl, AddDummyEnvelope, bool, m_addDummyEnvelope)
+NS_IMPL_GETSET(nsMailboxUrl, CanonicalLineEnding, bool, m_canonicalLineEnding)
+
+NS_IMETHODIMP
+nsMailboxUrl::GetOriginalSpec(char **aSpec)
+{
+ if (!aSpec || m_originalSpec.IsEmpty())
+ return NS_ERROR_NULL_POINTER;
+ *aSpec = ToNewCString(m_originalSpec);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailboxUrl::SetOriginalSpec(const char *aSpec)
+{
+ m_originalSpec = aSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::SetMessageFile(nsIFile * aFile)
+{
+ m_messageFile = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetMessageFile(nsIFile ** aFile)
+{
+ // why don't we return an error for null aFile?
+ if (aFile)
+ NS_IF_ADDREF(*aFile = m_messageFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::IsUrlType(uint32_t type, bool *isType)
+{
+ NS_ENSURE_ARG(isType);
+
+ switch(type)
+ {
+ case nsIMsgMailNewsUrl::eCopy:
+ *isType = (m_mailboxAction == nsIMailboxUrl::ActionCopyMessage);
+ break;
+ case nsIMsgMailNewsUrl::eMove:
+ *isType = (m_mailboxAction == nsIMailboxUrl::ActionMoveMessage);
+ break;
+ case nsIMsgMailNewsUrl::eDisplay:
+ *isType = (m_mailboxAction == nsIMailboxUrl::ActionFetchMessage ||
+ m_mailboxAction == nsIMailboxUrl::ActionFetchPart);
+ break;
+ default:
+ *isType = false;
+ };
+
+ return NS_OK;
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// End nsIMailboxUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+// possible search part phrases include: MessageID=id&number=MessageKey
+
+nsresult nsMailboxUrl::ParseSearchPart()
+{
+ nsAutoCString searchPart;
+ nsresult rv = GetQuery(searchPart);
+ // add code to this function to decompose everything past the '?'.....
+ if (NS_SUCCEEDED(rv) && !searchPart.IsEmpty())
+ {
+ // the action for this mailbox must be a display message...
+ char * msgPart = extractAttributeValue(searchPart.get(), "part=");
+ if (msgPart) // if we have a part in the url then we must be fetching just the part.
+ m_mailboxAction = nsIMailboxUrl::ActionFetchPart;
+ else
+ m_mailboxAction = nsIMailboxUrl::ActionFetchMessage;
+
+ char * messageKey = extractAttributeValue(searchPart.get(), "number=");
+ m_messageID = extractAttributeValue(searchPart.get(),"messageid=");
+ if (messageKey)
+ m_messageKey = (nsMsgKey) ParseUint64Str(messageKey); // convert to a uint32_t...
+
+ PR_Free(msgPart);
+ PR_Free(messageKey);
+ }
+ else
+ m_mailboxAction = nsIMailboxUrl::ActionParseMailbox;
+
+ return rv;
+}
+
+// warning: don't assume when parsing the url that the protocol part is "news"...
+nsresult nsMailboxUrl::ParseUrl()
+{
+ GetFilePath(m_file);
+
+ ParseSearchPart();
+ // ### fix me.
+ // this hack is to avoid asserting on every local message loaded because the security manager
+ // is creating an empty "mailbox://" uri for every message.
+ if (m_file.Length() < 2)
+ m_filePath = nullptr;
+ else
+ {
+ nsCString fileUri("file://");
+ fileUri.Append(m_file);
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr <nsIURI> uri;
+ rv = ioService->NewURI(fileUri, nullptr, nullptr, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFileURL> fileURL = do_QueryInterface(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIFile> fileURLFile;
+ fileURL->GetFile(getter_AddRefs(fileURLFile));
+ m_filePath = do_QueryInterface(fileURLFile, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ GetPath(m_file);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::SetSpec(const nsACString &aSpec)
+{
+ nsresult rv = nsMsgMailNewsUrl::SetSpec(aSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = ParseUrl();
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxUrl::SetQuery(const nsACString &aQuery)
+{
+ nsresult rv = nsMsgMailNewsUrl::SetQuery(aQuery);
+ if (NS_SUCCEEDED(rv))
+ rv = ParseUrl();
+ return rv;
+}
+
+// takes a string like ?messageID=fooo&number=MsgKey and returns a new string
+// containing just the attribute value. i.e you could pass in this string with
+// an attribute name of messageID and I'll return fooo. Use PR_Free to delete
+// this string...
+
+// Assumption: attribute pairs in the string are separated by '&'.
+char * extractAttributeValue(const char * searchString, const char * attributeName)
+{
+ char * attributeValue = nullptr;
+
+ if (searchString && attributeName)
+ {
+ // search the string for attributeName
+ uint32_t attributeNameSize = PL_strlen(attributeName);
+ char * startOfAttribute = PL_strcasestr(searchString, attributeName);
+ if (startOfAttribute)
+ {
+ startOfAttribute += attributeNameSize; // skip over the attributeName
+ if (startOfAttribute) // is there something after the attribute name
+ {
+ char * endOfAttribute = startOfAttribute ? PL_strchr(startOfAttribute, '&') : nullptr;
+ nsDependentCString attributeValueStr;
+ if (startOfAttribute && endOfAttribute) // is there text after attribute value
+ attributeValueStr.Assign(startOfAttribute, endOfAttribute - startOfAttribute);
+ else // there is nothing left so eat up rest of line.
+ attributeValueStr.Assign(startOfAttribute);
+
+ // now unescape the string...
+ nsCString unescapedValue;
+ MsgUnescapeString(attributeValueStr, 0, unescapedValue);
+ attributeValue = PL_strdup(unescapedValue.get());
+ } // if we have a attribute value
+
+ } // if we have a attribute name
+ } // if we got non-null search string and attribute name values
+
+ return attributeValue;
+}
+
+// nsIMsgI18NUrl support
+
+nsresult nsMailboxUrl::GetFolder(nsIMsgFolder **msgFolder)
+{
+ // if we have a RDF URI, then try to get the folder for that URI and then ask the folder
+ // for it's charset....
+ nsCString uri;
+ GetUri(getter_Copies(uri));
+ NS_ENSURE_TRUE(!uri.IsEmpty(), NS_ERROR_FAILURE);
+ nsCOMPtr<nsIMsgDBHdr> msg;
+ GetMsgDBHdrFromURI(uri.get(), getter_AddRefs(msg));
+ if (!msg)
+ return NS_ERROR_FAILURE;
+ return msg->GetFolder(msgFolder);
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetFolderCharset(char ** aCharacterSet)
+{
+ NS_ENSURE_ARG_POINTER(aCharacterSet);
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetFolder(getter_AddRefs(folder));
+
+ // In cases where a file is not associated with a folder, for
+ // example standalone .eml files, failure is normal.
+ if (NS_FAILED(rv))
+ return rv;
+ nsCString tmpStr;
+ folder->GetCharset(tmpStr);
+ *aCharacterSet = ToNewCString(tmpStr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetFolderCharsetOverride(bool * aCharacterSetOverride)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ENSURE_TRUE(folder, NS_ERROR_FAILURE);
+ folder->GetCharsetOverride(aCharacterSetOverride);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetCharsetOverRide(char ** aCharacterSet)
+{
+ if (!mCharsetOverride.IsEmpty())
+ *aCharacterSet = ToNewCString(mCharsetOverride);
+ else
+ *aCharacterSet = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::SetCharsetOverRide(const char * aCharacterSet)
+{
+ mCharsetOverride = aCharacterSet;
+ return NS_OK;
+}
+
+/* void setMoveCopyMsgKeys (out nsMsgKey keysToFlag, in long numKeys); */
+NS_IMETHODIMP nsMailboxUrl::SetMoveCopyMsgKeys(nsMsgKey *keysToFlag, int32_t numKeys)
+{
+ m_keys.ReplaceElementsAt(0, m_keys.Length(), keysToFlag, numKeys);
+ if (!m_keys.IsEmpty() && m_messageKey == nsMsgKey_None)
+ m_messageKey = m_keys[0];
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetMoveCopyMsgHdrForIndex(uint32_t msgIndex, nsIMsgDBHdr **msgHdr)
+{
+ NS_ENSURE_ARG(msgHdr);
+ if (msgIndex < m_keys.Length())
+ {
+ nsMsgKey nextKey = m_keys[msgIndex];
+ return GetMsgHdrForKey(nextKey, msgHdr);
+ }
+ return NS_MSG_MESSAGE_NOT_FOUND;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetNumMoveCopyMsgs(uint32_t *numMsgs)
+{
+ NS_ENSURE_ARG(numMsgs);
+ *numMsgs = m_keys.Length();
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsMailboxUrl.h b/mailnews/local/src/nsMailboxUrl.h
new file mode 100644
index 000000000..63973a916
--- /dev/null
+++ b/mailnews/local/src/nsMailboxUrl.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMailboxUrl_h__
+#define nsMailboxUrl_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIMailboxUrl.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+
+class nsMailboxUrl : public nsIMailboxUrl, public nsMsgMailNewsUrl, public nsIMsgMessageUrl, public nsIMsgI18NUrl
+{
+public:
+ // nsIURI over-ride...
+ NS_IMETHOD SetSpec(const nsACString &aSpec) override;
+ NS_IMETHOD SetQuery(const nsACString &aQuery) override;
+
+ // from nsIMailboxUrl:
+ NS_IMETHOD SetMailboxParser(nsIStreamListener * aConsumer) override;
+ NS_IMETHOD GetMailboxParser(nsIStreamListener ** aConsumer) override;
+ NS_IMETHOD SetMailboxCopyHandler(nsIStreamListener * aConsumer) override;
+ NS_IMETHOD GetMailboxCopyHandler(nsIStreamListener ** aConsumer) override;
+
+ NS_IMETHOD GetMessageKey(nsMsgKey* aMessageKey) override;
+ NS_IMETHOD GetMessageSize(uint32_t *aMessageSize) override;
+ NS_IMETHOD SetMessageSize(uint32_t aMessageSize) override;
+ NS_IMETHOD GetMailboxAction(nsMailboxAction *result) override
+ {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_mailboxAction;
+ return NS_OK;
+ }
+ NS_IMETHOD SetMailboxAction(nsMailboxAction aAction) override
+ {
+ m_mailboxAction = aAction;
+ return NS_OK;
+ }
+ NS_IMETHOD IsUrlType(uint32_t type, bool *isType) override;
+ NS_IMETHOD SetMoveCopyMsgKeys(nsMsgKey *keysToFlag, int32_t numKeys) override;
+ NS_IMETHOD GetMoveCopyMsgHdrForIndex(uint32_t msgIndex, nsIMsgDBHdr **msgHdr) override;
+ NS_IMETHOD GetNumMoveCopyMsgs(uint32_t *numMsgs) override;
+ NS_IMETHOD GetCurMoveCopyMsgIndex(uint32_t *result) override
+ {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_curMsgIndex;
+ return NS_OK;
+ }
+ NS_IMETHOD SetCurMoveCopyMsgIndex(uint32_t aIndex) override
+ {
+ m_curMsgIndex = aIndex;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetFolder(nsIMsgFolder **msgFolder) override;
+
+ // nsIMsgMailNewsUrl override
+ NS_IMETHOD CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef,
+ nsIURI **_retval) override;
+
+ // nsMailboxUrl
+ nsMailboxUrl();
+ NS_DECL_NSIMSGMESSAGEURL
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMSGI18NURL
+
+protected:
+ virtual ~nsMailboxUrl();
+ // protocol specific code to parse a url...
+ virtual nsresult ParseUrl();
+ nsresult GetMsgHdrForKey(nsMsgKey msgKey, nsIMsgDBHdr ** aMsgHdr);
+
+ // mailboxurl specific state
+ nsCOMPtr<nsIStreamListener> m_mailboxParser;
+ nsCOMPtr<nsIStreamListener> m_mailboxCopyHandler;
+
+ nsMailboxAction m_mailboxAction; // the action this url represents...parse mailbox, display messages, etc.
+ nsCOMPtr <nsIFile> m_filePath;
+ char *m_messageID;
+ uint32_t m_messageSize;
+ nsMsgKey m_messageKey;
+ nsCString m_file;
+ // This is currently only set when we're doing something with a .eml file.
+ // If that changes, we should change the name of this var.
+ nsCOMPtr<nsIMsgDBHdr> m_dummyHdr;
+
+ // used by save message to disk
+ nsCOMPtr<nsIFile> m_messageFile;
+ bool m_addDummyEnvelope;
+ bool m_canonicalLineEnding;
+ nsresult ParseSearchPart();
+
+ // for multiple msg move/copy
+ nsTArray<nsMsgKey> m_keys;
+ int32_t m_curMsgIndex;
+
+ // truncated message support
+ nsCString m_originalSpec;
+ nsCString mURI; // the RDF URI associated with this url.
+ nsCString mCharsetOverride; // used by nsIMsgI18NUrl...
+};
+
+#endif // nsMailboxUrl_h__
diff --git a/mailnews/local/src/nsMovemailIncomingServer.cpp b/mailnews/local/src/nsMovemailIncomingServer.cpp
new file mode 100644
index 000000000..cee915e25
--- /dev/null
+++ b/mailnews/local/src/nsMovemailIncomingServer.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgLocalCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMovemailService.h"
+#include "nsIFile.h"
+#include "msgCore.h" // pre-compiled headers
+#include "nsMovemailIncomingServer.h"
+#include "nsServiceManagerUtils.h"
+
+
+static NS_DEFINE_CID(kCMovemailServiceCID, NS_MOVEMAILSERVICE_CID);
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMovemailIncomingServer,
+ nsMsgIncomingServer,
+ nsIMovemailIncomingServer,
+ nsILocalMailIncomingServer)
+
+
+
+nsMovemailIncomingServer::nsMovemailIncomingServer()
+{
+ m_canHaveFilters = true;
+}
+
+nsMovemailIncomingServer::~nsMovemailIncomingServer()
+{
+}
+
+NS_IMETHODIMP
+nsMovemailIncomingServer::PerformBiff(nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMovemailService> movemailService(do_GetService(
+ kCMovemailServiceCID, &rv));
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<nsIMsgFolder> inbox;
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsCOMPtr<nsIUrlListener> urlListener;
+ rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ if(NS_SUCCEEDED(rv) && rootMsgFolder)
+ {
+ rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(inbox));
+ if (!inbox) return NS_ERROR_FAILURE;
+ }
+
+ SetPerformingBiff(true);
+ urlListener = do_QueryInterface(inbox);
+
+ bool downloadOnBiff = false;
+ rv = GetDownloadOnBiff(&downloadOnBiff);
+ if (downloadOnBiff)
+ {
+ nsCOMPtr <nsIMsgLocalMailFolder> localInbox = do_QueryInterface(inbox,
+ &rv);
+ if (localInbox && NS_SUCCEEDED(rv))
+ {
+ bool valid = false;
+ nsCOMPtr <nsIMsgDatabase> db;
+ rv = inbox->GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db)
+ {
+ rv = db->GetSummaryValid(&valid);
+ }
+ if (NS_SUCCEEDED(rv) && valid)
+ {
+ rv = movemailService->GetNewMail(aMsgWindow, urlListener, inbox,
+ this, nullptr);
+ }
+ else
+ {
+ bool isLocked;
+ inbox->GetLocked(&isLocked);
+ if (!isLocked)
+ {
+ rv = localInbox->ParseFolder(aMsgWindow, urlListener);
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = localInbox->SetCheckForNewMessagesAfterParsing(true);
+ }
+ }
+ }
+ }
+ else
+ {
+ movemailService->CheckForNewMail(urlListener, inbox, this, nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailIncomingServer::SetFlagsOnDefaultMailboxes()
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
+ do_QueryInterface(rootFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::SpecialUse);
+}
+
+NS_IMETHODIMP nsMovemailIncomingServer::CreateDefaultMailboxes()
+{
+ nsresult rv = CreateLocalFolder(NS_LITERAL_STRING("Inbox"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CreateLocalFolder(NS_LITERAL_STRING("Trash"));
+}
+
+
+NS_IMETHODIMP
+nsMovemailIncomingServer::GetNewMail(nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ nsIMsgFolder *aMsgFolder,
+ nsIURI **aResult)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMovemailService> movemailService =
+ do_GetService(kCMovemailServiceCID, &rv);
+
+ if (NS_FAILED(rv)) return rv;
+
+ rv = movemailService->GetNewMail(aMsgWindow, aUrlListener,
+ aMsgFolder, this, aResult);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMovemailIncomingServer::GetDownloadMessagesAtStartup(bool *getMessagesAtStartup)
+{
+ NS_ENSURE_ARG_POINTER(getMessagesAtStartup);
+ *getMessagesAtStartup = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailIncomingServer::GetCanBeDefaultServer(bool *aCanBeDefaultServer)
+{
+ NS_ENSURE_ARG_POINTER(aCanBeDefaultServer);
+ *aCanBeDefaultServer = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailIncomingServer::GetCanSearchMessages(bool *canSearchMessages)
+{
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ *canSearchMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff)
+{
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ *aServerRequiresPasswordForBiff = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailIncomingServer::GetAccountManagerChrome(nsAString& aResult)
+{
+ aResult.AssignLiteral("am-main.xul");
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsMovemailIncomingServer.h b/mailnews/local/src/nsMovemailIncomingServer.h
new file mode 100644
index 000000000..7fefec965
--- /dev/null
+++ b/mailnews/local/src/nsMovemailIncomingServer.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsMovemailIncomingServer_h
+#define __nsMovemailIncomingServer_h
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsIMovemailIncomingServer.h"
+#include "nsILocalMailIncomingServer.h"
+#include "nsMailboxServer.h"
+
+/* get some implementation from nsMsgIncomingServer */
+class nsMovemailIncomingServer : public nsMailboxServer,
+ public nsIMovemailIncomingServer,
+ public nsILocalMailIncomingServer
+
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMOVEMAILINCOMINGSERVER
+ NS_DECL_NSILOCALMAILINCOMINGSERVER
+
+ nsMovemailIncomingServer();
+
+ NS_IMETHOD PerformBiff(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD GetDownloadMessagesAtStartup(bool *getMessages) override;
+ NS_IMETHOD GetCanBeDefaultServer(bool *canBeDefaultServer) override;
+ NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override;
+ NS_IMETHOD GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) override;
+ NS_IMETHOD GetAccountManagerChrome(nsAString& aResult) override;
+
+private:
+ virtual ~nsMovemailIncomingServer();
+};
+
+
+#endif
diff --git a/mailnews/local/src/nsMovemailService.cpp b/mailnews/local/src/nsMovemailService.cpp
new file mode 100644
index 000000000..a4280c975
--- /dev/null
+++ b/mailnews/local/src/nsMovemailService.cpp
@@ -0,0 +1,694 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <unistd.h> // for link(), used in spool-file locking
+
+#include "prenv.h"
+#include "private/pprio.h" // for our kernel-based locking
+#include "nspr.h"
+
+#include "msgCore.h" // precompiled header...
+
+#include "nsMovemailService.h"
+#include "nsIMovemailService.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMovemailIncomingServer.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsParseMailbox.h"
+#include "nsIMsgFolder.h"
+#include "nsIPrompt.h"
+
+#include "nsIFile.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsMsgUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsMsgFolderFlags.h"
+
+#include "nsILineInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsNetUtil.h"
+#include "nsAutoPtr.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgPluggableStore.h"
+#include "mozilla/Services.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+
+#include "mozilla/Logging.h"
+#if defined(PR_LOGGING)
+//
+// export NSPR_LOG_MODULES=Movemail:5
+//
+static PRLogModuleInfo *gMovemailLog = nullptr;
+#define LOG(args) MOZ_LOG(gMovemailLog, mozilla::LogLevel::Debug, args)
+#else
+#define LOG(args)
+#endif
+
+#define PREF_MAIL_ROOT_MOVEMAIL "mail.root.movemail" // old - for backward compatibility only
+#define PREF_MAIL_ROOT_MOVEMAIL_REL "mail.root.movemail-rel"
+
+#define LOCK_SUFFIX ".lock"
+#define MOZLOCK_SUFFIX ".mozlock"
+
+const char * gDefaultSpoolPaths[] = {
+ "/var/spool/mail/",
+ "/usr/spool/mail/",
+ "/var/mail/",
+ "/usr/mail/"
+};
+#define NUM_DEFAULT_SPOOL_PATHS (sizeof(gDefaultSpoolPaths)/sizeof(gDefaultSpoolPaths[0]))
+
+namespace {
+class MOZ_STACK_CLASS SpoolLock
+{
+public:
+ /**
+ * Try to create a lock for the spool file while we operate on it.
+ *
+ * @param aSpoolName The path to the spool file.
+ * @param aSeconds The number of seconds to retry the locking.
+ * @param aMovemail The movemail service requesting the lock.
+ * @param aServer The nsIMsgIncomingServer requesting the lock.
+ */
+ SpoolLock(nsACString *aSpoolPath, int aSeconds, nsMovemailService &aMovemail,
+ nsIMsgIncomingServer *aServer);
+
+ ~SpoolLock();
+
+ bool isLocked();
+
+private:
+ bool mLocked;
+ nsCString mSpoolName;
+ bool mUsingLockFile;
+ RefPtr<nsMovemailService> mOwningService;
+ nsCOMPtr<nsIMsgIncomingServer> mServer;
+
+ bool ObtainSpoolLock(unsigned int aSeconds);
+ bool YieldSpoolLock();
+};
+
+}
+
+nsMovemailService::nsMovemailService()
+{
+#if defined(PR_LOGGING)
+ if (!gMovemailLog)
+ gMovemailLog = PR_NewLogModule("Movemail");
+#endif
+ LOG(("nsMovemailService created: 0x%x\n", this));
+}
+
+nsMovemailService::~nsMovemailService()
+{}
+
+
+NS_IMPL_ISUPPORTS(nsMovemailService,
+ nsIMovemailService,
+ nsIMsgProtocolInfo)
+
+
+NS_IMETHODIMP
+nsMovemailService::CheckForNewMail(nsIUrlListener * aUrlListener,
+ nsIMsgFolder *inbox,
+ nsIMovemailIncomingServer *movemailServer,
+ nsIURI ** aURL)
+{
+ nsresult rv = NS_OK;
+ LOG(("nsMovemailService::CheckForNewMail\n"));
+ return rv;
+}
+
+void
+nsMovemailService::Error(const char* errorCode,
+ const char16_t **params,
+ uint32_t length)
+{
+ if (!mMsgWindow) return;
+
+ nsCOMPtr<nsIPrompt> dialog;
+ nsresult rv = mMsgWindow->GetPromptDialog(getter_AddRefs(dialog));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService)
+ return;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle));
+ if (NS_FAILED(rv))
+ return;
+
+ nsString errStr;
+ // Format the error string if necessary
+ if (params)
+ bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(errorCode).get(),
+ params, length, getter_Copies(errStr));
+ else
+ bundle->GetStringFromName(NS_ConvertASCIItoUTF16(errorCode).get(),
+ getter_Copies(errStr));
+
+ if (!errStr.IsEmpty()) {
+ dialog->Alert(nullptr, errStr.get());
+ }
+}
+
+SpoolLock::SpoolLock(nsACString *aSpoolName, int aSeconds,
+ nsMovemailService &aMovemail,
+ nsIMsgIncomingServer *aServer)
+: mLocked(false),
+ mSpoolName(*aSpoolName),
+ mOwningService(&aMovemail),
+ mServer(aServer)
+{
+ if (!ObtainSpoolLock(aSeconds)) {
+ NS_ConvertUTF8toUTF16 lockFile(mSpoolName);
+ lockFile.AppendLiteral(LOCK_SUFFIX);
+ const char16_t* params[] = { lockFile.get() };
+ mOwningService->Error("movemailCantCreateLock", params, 1);
+ return;
+ }
+ mServer->SetServerBusy(true);
+ mLocked = true;
+}
+
+SpoolLock::~SpoolLock() {
+ if (mLocked && !YieldSpoolLock()) {
+ NS_ConvertUTF8toUTF16 lockFile(mSpoolName);
+ lockFile.AppendLiteral(LOCK_SUFFIX);
+ const char16_t* params[] = { lockFile.get() };
+ mOwningService->Error("movemailCantDeleteLock", params, 1);
+ }
+ mServer->SetServerBusy(false);
+}
+
+bool
+SpoolLock::isLocked() {
+ return mLocked;
+}
+
+bool
+SpoolLock::ObtainSpoolLock(unsigned int aSeconds /* number of seconds to retry */)
+{
+ /*
+ * Locking procedures:
+ * If the directory is not writable, we want to use the appropriate system
+ * utilites to lock the file.
+ * If the directory is writable, we want to go through the create-and-link
+ * locking procedures to make it atomic for certain networked file systems.
+ * This involves creating a .mozlock file and attempting to hard-link it to
+ * the customary .lock file.
+ */
+ nsCOMPtr<nsIFile> spoolFile;
+ nsresult rv = NS_NewNativeLocalFile(mSpoolName,
+ true,
+ getter_AddRefs(spoolFile));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIFile> directory;
+ rv = spoolFile->GetParent(getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = directory->IsWritable(&mUsingLockFile);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (!mUsingLockFile) {
+ LOG(("Attempting to use kernel file lock"));
+ PRFileDesc *fd;
+ rv = spoolFile->OpenNSPRFileDesc(PR_RDWR, 0, &fd);
+ NS_ENSURE_SUCCESS(rv, false);
+ PRStatus lock_result;
+ unsigned int retry_count = 0;
+
+ do {
+ lock_result = PR_TLockFile(fd);
+
+ retry_count++;
+ LOG(("Attempt %d of %d to lock file", retry_count, aSeconds));
+ if (aSeconds > 0 && lock_result == PR_FAILURE) {
+ // pause 1sec, waiting for .lock to go away
+ PRIntervalTime sleepTime = 1000; // 1 second
+ PR_Sleep(sleepTime);
+ }
+ } while (lock_result == PR_FAILURE && retry_count < aSeconds);
+ LOG(("Lock result: %d", lock_result));
+ PR_Close(fd);
+ return lock_result == PR_SUCCESS;
+ }
+ // How to lock using files:
+ // step 1: create SPOOLNAME.mozlock
+ // 1a: can remove it if it already exists (probably crash-droppings)
+ // step 2: hard-link SPOOLNAME.mozlock to SPOOLNAME.lock for NFS atomicity
+ // 2a: if SPOOLNAME.lock is >60sec old then nuke it from orbit
+ // 2b: repeat step 2 until retry-count expired or hard-link succeeds
+ // step 3: remove SPOOLNAME.mozlock
+ // step 4: If step 2 hard-link failed, fail hard; we do not hold the lock
+ // DONE.
+ //
+ // (step 2a not yet implemented)
+
+ nsAutoCString mozlockstr(mSpoolName);
+ mozlockstr.AppendLiteral(MOZLOCK_SUFFIX);
+ nsAutoCString lockstr(mSpoolName);
+ lockstr.AppendLiteral(LOCK_SUFFIX);
+
+ // Create nsIFile for the spool.mozlock file
+ nsCOMPtr<nsIFile> tmplocfile;
+ rv = NS_NewNativeLocalFile(mozlockstr, true, getter_AddRefs(tmplocfile));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // THOUGHT: hmm, perhaps use MakeUnique to generate us a unique mozlock?
+ // ... perhaps not, MakeUnique implementation looks racey -- use mktemp()?
+
+ // step 1: create SPOOLNAME.mozlock
+ rv = tmplocfile->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ // can't create our .mozlock file... game over already
+ LOG(("Failed to create file %s\n", mozlockstr.get()));
+ return false;
+ }
+
+ // step 2: hard-link .mozlock file to .lock file (this wackiness
+ // is necessary for non-racey locking on NFS-mounted spool dirs)
+ // n.b. XPCOM utilities don't support hard-linking yet, so we
+ // skip out to <unistd.h> and the POSIX interface for link()
+ int link_result = 0;
+ unsigned int retry_count = 0;
+
+ do {
+ link_result = link(mozlockstr.get(), lockstr.get());
+
+ retry_count++;
+ LOG(("Attempt %d of %d to create lock file", retry_count, aSeconds));
+
+ if (aSeconds > 0 && link_result == -1) {
+ // pause 1sec, waiting for .lock to go away
+ PRIntervalTime sleepTime = 1000; // 1 second
+ PR_Sleep(sleepTime);
+ }
+ } while (link_result == -1 && retry_count < aSeconds);
+ LOG(("Link result: %d", link_result));
+
+ // step 3: remove .mozlock file, in any case
+ rv = tmplocfile->Remove(false /* non-recursive */);
+ if (NS_FAILED(rv)) {
+ // Could not delete our .mozlock file... very unusual, but
+ // not fatal.
+ LOG(("Unable to delete %s", mozlockstr.get()));
+ }
+
+ // step 4: now we know whether we succeeded or failed
+ return link_result == 0;
+}
+
+
+/**
+ * Remove our mail-spool-file lock (n.b. we should only try this if
+ * we're the ones who made the lock in the first place! I.e. if mLocked is true.)
+ */
+bool
+SpoolLock::YieldSpoolLock()
+{
+ LOG(("YieldSpoolLock(%s)", mSpoolName.get()));
+
+ if (!mUsingLockFile) {
+ nsCOMPtr<nsIFile> spoolFile;
+ nsresult rv = NS_NewNativeLocalFile(mSpoolName,
+ true,
+ getter_AddRefs(spoolFile));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ PRFileDesc *fd;
+ rv = spoolFile->OpenNSPRFileDesc(PR_RDWR, 0, &fd);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool unlockSucceeded = PR_UnlockFile(fd) == PR_SUCCESS;
+ PR_Close(fd);
+ if (unlockSucceeded)
+ LOG(("YieldSpoolLock was successful."));
+ return unlockSucceeded;
+ }
+
+ nsAutoCString lockstr(mSpoolName);
+ lockstr.AppendLiteral(LOCK_SUFFIX);
+
+ nsresult rv;
+
+ // Create nsIFile for the spool.lock file
+ nsCOMPtr<nsIFile> locklocfile;
+ rv = NS_NewNativeLocalFile(lockstr, true, getter_AddRefs(locklocfile));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Check if the lock file exists
+ bool exists;
+ rv = locklocfile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Delete the file if it exists
+ if (exists) {
+ rv = locklocfile->Remove(false /* non-recursive */);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ LOG(("YieldSpoolLock was successful."));
+
+ // Success.
+ return true;
+}
+
+static nsresult
+LocateSpoolFile(nsACString & spoolPath)
+{
+ bool isFile;
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> spoolFile;
+ rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(spoolFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char * mailEnv = PR_GetEnv("MAIL");
+ char * userEnv = PR_GetEnv("USER");
+ if (!userEnv)
+ userEnv = PR_GetEnv("USERNAME");
+
+ if (mailEnv) {
+ rv = spoolFile->InitWithNativePath(nsDependentCString(mailEnv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = spoolFile->IsFile(&isFile);
+ if (NS_SUCCEEDED(rv) && isFile)
+ spoolPath = mailEnv;
+ }
+ else if (userEnv) {
+ // Try to build the mailbox path from the username and a number
+ // of guessed spool directory paths.
+ nsAutoCString tmpPath;
+ uint32_t i;
+ for (i = 0; i < NUM_DEFAULT_SPOOL_PATHS; i++) {
+ tmpPath = gDefaultSpoolPaths[i];
+ tmpPath += userEnv;
+ rv = spoolFile->InitWithNativePath(tmpPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = spoolFile->IsFile(&isFile);
+ if (NS_SUCCEEDED(rv) && isFile) {
+ spoolPath = tmpPath;
+ break;
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsMovemailService::GetNewMail(nsIMsgWindow *aMsgWindow,
+ nsIUrlListener* /* aUrlListener */,
+ nsIMsgFolder* /* aMsgFolder */,
+ nsIMovemailIncomingServer *aMovemailServer,
+ nsIURI ** /* aURL */)
+{
+ LOG(("nsMovemailService::GetNewMail"));
+
+ NS_ENSURE_ARG_POINTER(aMovemailServer);
+ // It is OK if aMsgWindow is null.
+ mMsgWindow = aMsgWindow;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> in_server =
+ do_QueryInterface(aMovemailServer, &rv);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && in_server,
+ NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ // Attempt to locate the mail spool file
+ nsAutoCString spoolPath;
+ rv = in_server->GetCharValue("spoolDir", spoolPath);
+ if (NS_FAILED(rv) || spoolPath.IsEmpty())
+ rv = LocateSpoolFile(spoolPath);
+ if (NS_FAILED(rv) || spoolPath.IsEmpty()) {
+ Error("movemailSpoolFileNotFound", nullptr, 0);
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ConvertUTF8toUTF16 wideSpoolPath(spoolPath);
+ const char16_t* spoolPathString[] = { wideSpoolPath.get() };
+
+ // Create an input stream for the spool file
+ nsCOMPtr<nsIFile> spoolFile;
+ rv = NS_NewNativeLocalFile(spoolPath, true, getter_AddRefs(spoolFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStream> spoolInputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(spoolInputStream), spoolFile);
+ if (NS_FAILED(rv)) {
+ Error("movemailCantOpenSpoolFile", spoolPathString, 1);
+ return rv;
+ }
+
+ // Get a line input interface for the spool file
+ nsCOMPtr<nsILineInputStream> lineInputStream =
+ do_QueryInterface(spoolInputStream, &rv);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && lineInputStream, rv);
+
+ nsCOMPtr<nsIMsgFolder> serverFolder;
+ nsCOMPtr<nsIMsgFolder> inbox;
+
+ rv = in_server->GetRootFolder(getter_AddRefs(serverFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = serverFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(inbox));
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && inbox, rv);
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = in_server->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create a new mail parser
+ RefPtr<nsParseNewMailState> newMailParser = new nsParseNewMailState;
+
+ // Try and obtain the lock for the spool file.
+ SpoolLock lock(&spoolPath, 5, *this, in_server);
+ if (!lock.isLocked())
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ nsCOMPtr<nsIOutputStream> outputStream;
+
+ // MIDDLE of the FUN : consume the mailbox data.
+ bool isMore = true;
+ nsAutoCString buffer;
+ uint32_t bytesWritten = 0;
+ while (isMore &&
+ NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore)))
+ {
+ // If first string is empty and we're now at EOF then abort parsing.
+ if (buffer.IsEmpty() && !isMore && !bytesWritten) {
+ LOG(("Empty spool file"));
+ break;
+ }
+
+ buffer.AppendLiteral(MSG_LINEBREAK);
+
+ if (isMore && StringBeginsWith(buffer, NS_LITERAL_CSTRING("From "))) {
+ // Finish previous header and message, if any.
+ if (newHdr) {
+ outputStream->Flush();
+ newMailParser->PublishMsgHeader(nullptr);
+ rv = msgStore->FinishNewMessage(outputStream, newHdr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newMailParser->Clear();
+ }
+ bool reusable;
+ rv = msgStore->GetNewMsgOutputStream(inbox, getter_AddRefs(newHdr),
+ &reusable, getter_AddRefs(outputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = newMailParser->Init(serverFolder, inbox,
+ nullptr, newHdr, outputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (!outputStream) {
+ // If we do not have outputStream here, something bad happened.
+ // We probably didn't find the proper message start delimiter "From "
+ // and are now reading in the middle of a message. Bail out.
+ Error("movemailCantParseSpool", spoolPathString, 1);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ newMailParser->HandleLine(buffer.BeginWriting(), buffer.Length());
+ rv = outputStream->Write(buffer.get(), buffer.Length(), &bytesWritten);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (bytesWritten == buffer.Length()),
+ NS_ERROR_FAILURE);
+
+ // "From " lines delimit messages, start a new one here.
+ if (isMore && StringBeginsWith(buffer, NS_LITERAL_CSTRING("From "))) {
+ buffer.AssignLiteral("X-Mozilla-Status: 8000" MSG_LINEBREAK);
+ newMailParser->HandleLine(buffer.BeginWriting(), buffer.Length());
+ rv = outputStream->Write(buffer.get(), buffer.Length(), &bytesWritten);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (bytesWritten == buffer.Length()),
+ NS_ERROR_FAILURE);
+
+ buffer.AssignLiteral("X-Mozilla-Status2: 00000000" MSG_LINEBREAK);
+ newMailParser->HandleLine(buffer.BeginWriting(), buffer.Length());
+ rv = outputStream->Write(buffer.get(), buffer.Length(), &bytesWritten);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (bytesWritten == buffer.Length()),
+ NS_ERROR_FAILURE);
+ }
+ }
+ if (outputStream) {
+ outputStream->Flush();
+ newMailParser->PublishMsgHeader(nullptr);
+ newMailParser->OnStopRequest(nullptr, nullptr, NS_OK);
+ rv = msgStore->FinishNewMessage(outputStream, newHdr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ outputStream->Close();
+ }
+ // Truncate the spool file as we parsed it successfully.
+ rv = spoolFile->SetFileSize(0);
+ if (NS_FAILED(rv)) {
+ Error("movemailCantTruncateSpoolFile", spoolPathString, 1);
+ }
+
+ LOG(("GetNewMail returning rv=%d", rv));
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsMovemailService::SetDefaultLocalPath(nsIFile *aPath)
+{
+ NS_ENSURE_ARG(aPath);
+ return NS_SetPersistentFile(PREF_MAIL_ROOT_MOVEMAIL_REL, PREF_MAIL_ROOT_MOVEMAIL, aPath);
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetDefaultLocalPath(nsIFile ** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ nsresult rv;
+ bool havePref;
+ nsCOMPtr<nsIFile> localFile;
+ rv = NS_GetPersistentFile(PREF_MAIL_ROOT_MOVEMAIL_REL,
+ PREF_MAIL_ROOT_MOVEMAIL,
+ NS_APP_MAIL_50_DIR,
+ havePref,
+ getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ rv = localFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!havePref || !exists) {
+ rv = NS_SetPersistentFile(PREF_MAIL_ROOT_MOVEMAIL_REL, PREF_MAIL_ROOT_MOVEMAIL, localFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
+ }
+
+ NS_IF_ADDREF(*aResult = localFile);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMovemailService::GetServerIID(nsIID* *aServerIID)
+{
+ *aServerIID = new nsIID(NS_GET_IID(nsIMovemailIncomingServer));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetRequiresUsername(bool *aRequiresUsername)
+{
+ NS_ENSURE_ARG_POINTER(aRequiresUsername);
+ *aRequiresUsername = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress)
+{
+ NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress);
+ *aPreflightPrettyNameWithEmailAddress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp)
+{
+ NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp);
+ *aCanLoginAtStartUp = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetCanDelete(bool *aCanDelete)
+{
+ NS_ENSURE_ARG_POINTER(aCanDelete);
+ *aCanDelete = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetCanGetMessages(bool *aCanGetMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetMessages);
+ *aCanGetMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages);
+ *aCanGetIncomingMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetCanDuplicate(bool *aCanDuplicate)
+{
+ NS_ENSURE_ARG_POINTER(aCanDuplicate);
+ *aCanDuplicate = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetDefaultDoBiff(bool *aDoBiff)
+{
+ NS_ENSURE_ARG_POINTER(aDoBiff);
+ // by default, do biff for movemail
+ *aDoBiff = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetDefaultServerPort(bool isSecure, int32_t *aDefaultPort)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultPort);
+ *aDefaultPort = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetShowComposeMsgLink(bool *showComposeMsgLink)
+{
+ NS_ENSURE_ARG_POINTER(showComposeMsgLink);
+ *showComposeMsgLink = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMovemailService::GetFoldersCreatedAsync(bool *aAsyncCreation)
+{
+ NS_ENSURE_ARG_POINTER(aAsyncCreation);
+ *aAsyncCreation = false;
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsMovemailService.h b/mailnews/local/src/nsMovemailService.h
new file mode 100644
index 000000000..5b1ae1053
--- /dev/null
+++ b/mailnews/local/src/nsMovemailService.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMovemailService_h___
+#define nsMovemailService_h___
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+
+#include "nsIMovemailService.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIMsgWindow.h"
+
+class nsMovemailService : public nsIMsgProtocolInfo, public nsIMovemailService
+{
+public:
+ nsMovemailService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMOVEMAILSERVICE
+ NS_DECL_NSIMSGPROTOCOLINFO
+
+ void Error(const char* errorCode, const char16_t **params, uint32_t length);
+
+private:
+ virtual ~nsMovemailService();
+ nsCOMPtr<nsIMsgWindow> mMsgWindow;
+};
+
+#endif /* nsMovemailService_h___ */
diff --git a/mailnews/local/src/nsMsgBrkMBoxStore.cpp b/mailnews/local/src/nsMsgBrkMBoxStore.cpp
new file mode 100644
index 000000000..739d49d0a
--- /dev/null
+++ b/mailnews/local/src/nsMsgBrkMBoxStore.cpp
@@ -0,0 +1,1131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ Class for handling Berkeley Mailbox stores.
+*/
+
+#include "prlog.h"
+#include "msgCore.h"
+#include "nsMsgBrkMBoxStore.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "nsILocalMailIncomingServer.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsCOMArray.h"
+#include "nsIFile.h"
+#include "nsIMsgHdr.h"
+#include "nsNetUtil.h"
+#include "nsIMsgDatabase.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsMsgUtils.h"
+#include "nsMsgDBCID.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMailHeaders.h"
+#include "nsReadLine.h"
+#include "nsParseMailbox.h"
+#include "nsIMailboxService.h"
+#include "nsMsgLocalCID.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "mozilla/Preferences.h"
+#include "prprf.h"
+#include <cstdlib> // for std::abs(int/long)
+#include <cmath> // for std::abs(float/double)
+
+nsMsgBrkMBoxStore::nsMsgBrkMBoxStore()
+{
+}
+
+nsMsgBrkMBoxStore::~nsMsgBrkMBoxStore()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsMsgBrkMBoxStore, nsIMsgPluggableStore)
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::DiscoverSubFolders(nsIMsgFolder *aParentFolder,
+ bool aDeep)
+{
+ NS_ENSURE_ARG_POINTER(aParentFolder);
+
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aParentFolder->GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool exists;
+ path->Exists(&exists);
+ if (!exists) {
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return AddSubFolders(aParentFolder, path, aDeep);
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::CreateFolder(nsIMsgFolder *aParent,
+ const nsAString &aFolderName,
+ nsIMsgFolder **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (aFolderName.IsEmpty())
+ return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ nsCOMPtr<nsIFile> path;
+ nsCOMPtr<nsIMsgFolder> child;
+ nsresult rv = aParent->GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv))
+ return rv;
+ //Get a directory based on our current path.
+ rv = CreateDirectoryForFolder(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Now we have a valid directory or we have returned.
+ // Make sure the new folder name is valid
+ nsAutoString safeFolderName(aFolderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+
+ path->Append(safeFolderName);
+ bool exists;
+ path->Exists(&exists);
+ if (exists) //check this because localized names are different from disk names
+ return NS_MSG_FOLDER_EXISTS;
+
+ rv = path->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ //GetFlags and SetFlags in AddSubfolder will fail because we have no db at
+ // this point but mFlags is set.
+ rv = aParent->AddSubfolder(safeFolderName, getter_AddRefs(child));
+ if (!child || NS_FAILED(rv))
+ {
+ path->Remove(false);
+ return rv;
+ }
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ if (msgDBService)
+ {
+ nsCOMPtr<nsIMsgDatabase> unusedDB;
+ rv = msgDBService->OpenFolderDB(child, true, getter_AddRefs(unusedDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ rv = msgDBService->CreateNewDB(child, getter_AddRefs(unusedDB));
+
+ if ((NS_SUCCEEDED(rv) || rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) &&
+ unusedDB)
+ {
+ //need to set the folder name
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (NS_SUCCEEDED(rv))
+ folderInfo->SetMailboxName(safeFolderName);
+
+ unusedDB->SetSummaryValid(true);
+ unusedDB->Close(true);
+ aParent->UpdateSummaryTotals(true);
+ }
+ else
+ {
+ path->Remove(false);
+ rv = NS_MSG_CANT_CREATE_FOLDER;
+ }
+ }
+ child.forget(aResult);
+ return rv;
+}
+
+// Get the current attributes of the mbox file, corrected for caching
+void nsMsgBrkMBoxStore::GetMailboxModProperties(nsIMsgFolder *aFolder,
+ int64_t *aSize, uint32_t *aDate)
+{
+ // We'll simply return 0 on errors.
+ *aDate = 0;
+ *aSize = 0;
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = pathFile->GetFileSize(aSize);
+ if (NS_FAILED(rv))
+ return; // expected result for virtual folders
+
+ PRTime lastModTime;
+ rv = pathFile->GetLastModifiedTime(&lastModTime);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ *aDate = (uint32_t) (lastModTime / PR_MSEC_PER_SEC);
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::HasSpaceAvailable(nsIMsgFolder *aFolder,
+ int64_t aSpaceRequested,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool allow4GBfolders = mozilla::Preferences::GetBool("mailnews.allowMboxOver4GB", true);
+
+ if (!allow4GBfolders) {
+ // Allow the mbox to only reach 0xFFC00000 = 4 GiB - 4 MiB.
+ int64_t fileSize;
+ rv = pathFile->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = ((fileSize + aSpaceRequested) < 0xFFC00000LL);
+ if (!*aResult)
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ *aResult = DiskSpaceAvailableInStore(pathFile, aSpaceRequested);
+ if (!*aResult)
+ return NS_ERROR_FILE_DISK_FULL;
+
+ return NS_OK;
+}
+
+static bool gGotGlobalPrefs = false;
+static int32_t gTimeStampLeeway = 60;
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::IsSummaryFileValid(nsIMsgFolder *aFolder,
+ nsIMsgDatabase *aDB,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aDB);
+ NS_ENSURE_ARG_POINTER(aResult);
+ // We only check local folders for db validity.
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder(do_QueryInterface(aFolder));
+ if (!localFolder)
+ {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = aDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t folderSize;
+ uint32_t folderDate;
+ int32_t numUnreadMessages;
+
+ *aResult = false;
+
+ folderInfo->GetNumUnreadMessages(&numUnreadMessages);
+ folderInfo->GetFolderSize(&folderSize);
+ folderInfo->GetFolderDate(&folderDate);
+
+ int64_t fileSize = 0;
+ uint32_t actualFolderTimeStamp = 0;
+ GetMailboxModProperties(aFolder, &fileSize, &actualFolderTimeStamp);
+
+ if (folderSize == fileSize && numUnreadMessages >= 0)
+ {
+ if (!folderSize)
+ {
+ *aResult = true;
+ return NS_OK;
+ }
+ if (!gGotGlobalPrefs)
+ {
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (pPrefBranch)
+ {
+ rv = pPrefBranch->GetIntPref("mail.db_timestamp_leeway", &gTimeStampLeeway);
+ gGotGlobalPrefs = true;
+ }
+ }
+ // if those values are ok, check time stamp
+ if (gTimeStampLeeway == 0)
+ *aResult = folderDate == actualFolderTimeStamp;
+ else
+ *aResult = std::abs((int32_t) (actualFolderTimeStamp - folderDate)) <= gTimeStampLeeway;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::SetSummaryFileValid(nsIMsgFolder *aFolder,
+ nsIMsgDatabase *aDB,
+ bool aValid)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aDB);
+ // We only need to do this for local folders.
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder(do_QueryInterface(aFolder));
+ if (!localFolder)
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = aDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists;
+ pathFile->Exists(&exists);
+ if (!exists)
+ return NS_MSG_ERROR_FOLDER_MISSING;
+
+ if (aValid)
+ {
+ uint32_t actualFolderTimeStamp;
+ int64_t fileSize;
+ GetMailboxModProperties(aFolder, &fileSize, &actualFolderTimeStamp);
+ folderInfo->SetFolderSize(fileSize);
+ folderInfo->SetFolderDate(actualFolderTimeStamp);
+ }
+ else
+ {
+ folderInfo->SetVersion(0); // that ought to do the trick.
+ }
+ aDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::DeleteFolder(nsIMsgFolder *aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ bool exists;
+
+ // Delete mbox file.
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ exists = false;
+ pathFile->Exists(&exists);
+ if (exists) {
+ rv = pathFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Delete any subfolders (.sbd-suffixed directories).
+ AddDirectorySeparator(pathFile);
+ exists = false;
+ pathFile->Exists(&exists);
+ if (exists) {
+ rv = pathFile->Remove(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::RenameFolder(nsIMsgFolder *aFolder,
+ const nsAString & aNewName,
+ nsIMsgFolder **aNewFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aNewFolder);
+
+ uint32_t numChildren;
+ aFolder->GetNumSubFolders(&numChildren);
+ nsString existingName;
+ aFolder->GetName(existingName);
+
+ nsCOMPtr<nsIFile> oldPathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(oldPathFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ rv = aFolder->GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsISupports> parentSupport = do_QueryInterface(parentFolder);
+
+ nsCOMPtr<nsIFile> oldSummaryFile;
+ rv = aFolder->GetSummaryFile(getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dirFile;
+ oldPathFile->Clone(getter_AddRefs(dirFile));
+
+ if (numChildren > 0)
+ {
+ rv = CreateDirectoryForFolder(dirFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoString safeName(aNewName);
+ NS_MsgHashIfNecessary(safeName);
+
+ nsAutoCString oldLeafName;
+ oldPathFile->GetNativeLeafName(oldLeafName);
+
+ nsCOMPtr<nsIFile> parentPathFile;
+ parentFolder->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool isDirectory = false;
+ parentPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory)
+ {
+ nsAutoString leafName;
+ parentPathFile->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ rv = parentPathFile->SetLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aFolder->ForceDBClosed();
+ //save off dir name before appending .msf
+ rv = oldPathFile->MoveTo(nullptr, safeName);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsString dbName(safeName);
+ dbName += NS_LITERAL_STRING(SUMMARY_SUFFIX);
+ oldSummaryFile->MoveTo(nullptr, dbName);
+
+ if (numChildren > 0)
+ {
+ // rename "*.sbd" directory
+ nsAutoString newNameDirStr(safeName);
+ newNameDirStr += NS_LITERAL_STRING(".sbd");
+ dirFile->MoveTo(nullptr, newNameDirStr);
+ }
+
+ return parentFolder->AddSubfolder(safeName, aNewFolder);
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::CopyFolder(nsIMsgFolder *aSrcFolder,
+ nsIMsgFolder *aDstFolder,
+ bool aIsMoveFolder,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgCopyServiceListener *aListener,
+ const nsAString &aNewName)
+{
+ NS_ENSURE_ARG_POINTER(aSrcFolder);
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+
+ nsAutoString folderName;
+ if (aNewName.IsEmpty())
+ aSrcFolder->GetName(folderName);
+ else
+ folderName.Assign(aNewName);
+
+ nsAutoString safeFolderName(folderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+ nsCOMPtr<nsIMsgLocalMailFolder> localSrcFolder(do_QueryInterface(aSrcFolder));
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ if (localSrcFolder)
+ localSrcFolder->GetDatabaseWOReparse(getter_AddRefs(srcDB));
+ bool summaryValid = !!srcDB;
+ srcDB = nullptr;
+ aSrcFolder->ForceDBClosed();
+
+ nsCOMPtr<nsIFile> oldPath;
+ nsresult rv = aSrcFolder->GetFilePath(getter_AddRefs(oldPath));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIFile> summaryFile;
+ GetSummaryFileLocation(oldPath, getter_AddRefs(summaryFile));
+
+ nsCOMPtr<nsIFile> newPath;
+ rv = aDstFolder->GetFilePath(getter_AddRefs(newPath));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool newPathIsDirectory = false;
+ newPath->IsDirectory(&newPathIsDirectory);
+ if (!newPathIsDirectory)
+ {
+ AddDirectorySeparator(newPath);
+ rv = newPath->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv == NS_ERROR_FILE_ALREADY_EXISTS)
+ rv = NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIFile> origPath;
+ oldPath->Clone(getter_AddRefs(origPath));
+
+ //copying necessary for aborting.... if failure return
+ rv = oldPath->CopyTo(newPath, safeFolderName);
+ NS_ENSURE_SUCCESS(rv, rv); // Will fail if a file by that name exists
+
+ // Copy to dir can fail if filespec does not exist. If copy fails, we test
+ // if the filespec exist or not, if it does not that's ok, we continue
+ // without copying it. If it fails and filespec exist and is not zero sized
+ // there is real problem
+ // Copy the file to the new dir
+ nsAutoString dbName(safeFolderName);
+ dbName += NS_LITERAL_STRING(SUMMARY_SUFFIX);
+ rv = summaryFile->CopyTo(newPath, dbName);
+ if (NS_FAILED(rv)) // Test if the copy is successful
+ {
+ // Test if the filespec has data
+ bool exists;
+ int64_t fileSize;
+ summaryFile->Exists(&exists);
+ summaryFile->GetFileSize(&fileSize);
+ if (exists && fileSize > 0)
+ NS_ENSURE_SUCCESS(rv, rv); // Yes, it should have worked !
+ // else case is filespec is zero sized, no need to copy it,
+ // not an error
+ }
+
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ rv = aDstFolder->AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // linux and mac are not good about maintaining the file stamp when copying
+ // folders around. So if the source folder db is good, set the dest db as
+ // good too.
+ nsCOMPtr<nsIMsgDatabase> destDB;
+ if (summaryValid)
+ {
+ nsAutoString folderLeafName;
+ origPath->GetLeafName(folderLeafName);
+ newPath->Append(folderLeafName);
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenMailDBFromFile(newPath, newMsgFolder, false,
+ true, getter_AddRefs(destDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE && destDB)
+ destDB->SetSummaryValid(true);
+ }
+ newMsgFolder->SetPrettyName(folderName);
+ uint32_t flags;
+ aSrcFolder->GetFlags(&flags);
+ newMsgFolder->SetFlags(flags);
+ bool changed = false;
+ rv = aSrcFolder->MatchOrChangeFilterDestination(newMsgFolder, true, &changed);
+ if (changed)
+ aSrcFolder->AlertFilterChanged(aMsgWindow);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = aSrcFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy subfolders to the new location
+ nsresult copyStatus = NS_OK;
+ nsCOMPtr<nsIMsgLocalMailFolder> localNewFolder(do_QueryInterface(newMsgFolder, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore &&
+ NS_SUCCEEDED(copyStatus))
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item));
+ if (!folder)
+ continue;
+
+ copyStatus = localNewFolder->CopyFolderLocal(folder, false,
+ aMsgWindow, aListener);
+ // Test if the call succeeded, if not we have to stop recursive call
+ if (NS_FAILED(copyStatus))
+ {
+ // Copy failed we have to notify caller to handle the error and stop
+ // moving the folders. In case this happens to the topmost level of
+ // recursive call, then we just need to break from the while loop and
+ // go to error handling code.
+ if (!aIsMoveFolder)
+ return copyStatus;
+ break;
+ }
+ }
+ }
+
+ if (aIsMoveFolder && NS_SUCCEEDED(copyStatus))
+ {
+ if (localNewFolder)
+ {
+ nsCOMPtr<nsISupports> srcSupport(do_QueryInterface(aSrcFolder));
+ localNewFolder->OnCopyCompleted(srcSupport, true);
+ }
+
+ // Notify the "folder" that was dragged and dropped has been created. No
+ // need to do this for its subfolders. isMoveFolder will be true for folder.
+ aDstFolder->NotifyItemAdded(newMsgFolder);
+
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ aSrcFolder->GetParent(getter_AddRefs(msgParent));
+ aSrcFolder->SetParent(nullptr);
+ if (msgParent)
+ {
+ // The files have already been moved, so delete storage false
+ msgParent->PropagateDelete(aSrcFolder, false, aMsgWindow);
+ oldPath->Remove(false); //berkeley mailbox
+ // We need to force closed the source db
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ aSrcFolder->Delete();
+
+ nsCOMPtr<nsIFile> parentPath;
+ rv = msgParent->GetFilePath(getter_AddRefs(parentPath));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ AddDirectorySeparator(parentPath);
+ nsCOMPtr<nsISimpleEnumerator> children;
+ parentPath->GetDirectoryEntries(getter_AddRefs(children));
+ bool more;
+ // checks if the directory is empty or not
+ if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more)
+ parentPath->Remove(true);
+ }
+ }
+ else
+ {
+ // This is the case where the copy of a subfolder failed.
+ // We have to delete the newDirectory tree to make a "rollback".
+ // Someone should add a popup to warn the user that the move was not
+ // possible.
+ if (aIsMoveFolder && NS_FAILED(copyStatus))
+ {
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ newMsgFolder->ForceDBClosed();
+ newMsgFolder->GetParent(getter_AddRefs(msgParent));
+ newMsgFolder->SetParent(nullptr);
+ if (msgParent)
+ {
+ msgParent->PropagateDelete(newMsgFolder, false, aMsgWindow);
+ newMsgFolder->Delete();
+ newMsgFolder->ForceDBClosed();
+ AddDirectorySeparator(newPath);
+ newPath->Remove(true); //berkeley mailbox
+ }
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::GetNewMsgOutputStream(nsIMsgFolder *aFolder,
+ nsIMsgDBHdr **aNewMsgHdr,
+ bool *aReusable,
+ nsIOutputStream **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aNewMsgHdr);
+ NS_ENSURE_ARG_POINTER(aReusable);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+#ifdef _DEBUG
+ NS_ASSERTION(m_streamOutstandingFolder != aFolder, "didn't finish prev msg");
+ m_streamOutstandingFolder = aFolder;
+#endif
+ *aReusable = true;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> mboxFile;
+ rv = aFolder->GetFilePath(getter_AddRefs(mboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDatabase> db;
+ aFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (!db && !*aNewMsgHdr)
+ NS_WARNING("no db, and no message header");
+ bool exists = false;
+ mboxFile->Exists(&exists);
+ if (!exists) {
+ rv = mboxFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString URI;
+ aFolder->GetURI(URI);
+ nsCOMPtr<nsISeekableStream> seekable;
+ if (m_outputStreams.Get(URI, aResult))
+ {
+ seekable = do_QueryInterface(*aResult, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_END, 0);
+ if (NS_FAILED(rv))
+ {
+ m_outputStreams.Remove(URI);
+ NS_RELEASE(*aResult);
+ }
+ }
+ if (!*aResult)
+ {
+ rv = MsgGetFileStream(mboxFile, aResult);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed opening offline store for output");
+ if (NS_FAILED(rv))
+ printf("failed opening offline store for %s\n", URI.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ seekable = do_QueryInterface(*aResult, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_END, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_outputStreams.Put(URI, *aResult);
+ }
+ int64_t filePos;
+ seekable->Tell(&filePos);
+
+ if (db && !*aNewMsgHdr)
+ {
+ db->CreateNewHdr(nsMsgKey_None, aNewMsgHdr);
+ }
+
+ if (*aNewMsgHdr)
+ {
+ char storeToken[100];
+ PR_snprintf(storeToken, sizeof(storeToken), "%lld", filePos);
+ (*aNewMsgHdr)->SetMessageOffset(filePos);
+ (*aNewMsgHdr)->SetStringProperty("storeToken", storeToken);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::DiscardNewMessage(nsIOutputStream *aOutputStream,
+ nsIMsgDBHdr *aNewHdr)
+{
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+#ifdef _DEBUG
+ m_streamOutstandingFolder = nullptr;
+#endif
+ uint64_t hdrOffset;
+ aNewHdr->GetMessageOffset(&hdrOffset);
+ aOutputStream->Close();
+ nsCOMPtr<nsIFile> mboxFile;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetFilePath(getter_AddRefs(mboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mboxFile->SetFileSize(hdrOffset);
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::FinishNewMessage(nsIOutputStream *aOutputStream,
+ nsIMsgDBHdr *aNewHdr)
+{
+#ifdef _DEBUG
+ m_streamOutstandingFolder = nullptr;
+#endif
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+// NS_ENSURE_ARG_POINTER(aNewHdr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::MoveNewlyDownloadedMessage(nsIMsgDBHdr *aNewHdr,
+ nsIMsgFolder *aDestFolder,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+ NS_ENSURE_ARG_POINTER(aDestFolder);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::GetMsgInputStream(nsIMsgFolder *aMsgFolder,
+ const nsACString &aMsgToken,
+ int64_t *aOffset,
+ nsIMsgDBHdr *aMsgHdr,
+ bool *aReusable,
+ nsIInputStream **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aOffset);
+
+ // If there is no store token, then we set it to the existing message offset.
+ if (aMsgToken.IsEmpty())
+ {
+ uint64_t offset;
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ aMsgHdr->GetMessageOffset(&offset);
+ *aOffset = int64_t(offset);
+ char storeToken[100];
+ PR_snprintf(storeToken, sizeof(storeToken), "%lld", *aOffset);
+ aMsgHdr->SetStringProperty("storeToken", storeToken);
+ }
+ else
+ *aOffset = ParseUint64Str(PromiseFlatCString(aMsgToken).get());
+ *aReusable = true;
+ nsCOMPtr<nsIFile> mboxFile;
+
+ nsresult rv = aMsgFolder->GetFilePath(getter_AddRefs(mboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_NewLocalFileInputStream(aResult, mboxFile);
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::DeleteMessages(nsIArray *aHdrArray)
+{
+ return ChangeFlags(aHdrArray, nsMsgMessageFlags::Expunged, true);
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::CopyMessages(bool isMove, nsIArray *aHdrArray,
+ nsIMsgFolder *aDstFolder,
+ nsIMsgCopyServiceListener *aListener,
+ nsIArray **aDstHdrs,
+ nsITransaction **aUndoAction,
+ bool *aCopyDone)
+{
+ NS_ENSURE_ARG_POINTER(aHdrArray);
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+ NS_ENSURE_ARG_POINTER(aDstHdrs);
+ NS_ENSURE_ARG_POINTER(aCopyDone);
+ *aDstHdrs = nullptr;
+ *aUndoAction = nullptr;
+ *aCopyDone = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::GetSupportsCompaction(bool *aSupportsCompaction)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsCompaction);
+ *aSupportsCompaction = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::CompactFolder(nsIMsgFolder *aFolder,
+ nsIUrlListener *aListener,
+ nsIMsgWindow *aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t expungedBytes = 0;
+ aFolder->GetExpungedBytes(&expungedBytes);
+ // check if we need to compact the folder
+ return (expungedBytes > 0) ?
+ folderCompactor->Compact(aFolder, false, aListener, aMsgWindow) :
+ aFolder->NotifyCompactCompleted();
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::RebuildIndex(nsIMsgFolder *aFolder,
+ nsIMsgDatabase *aMsgDB,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool isLocked;
+ aFolder->GetLocked(&isLocked);
+ if (isLocked)
+ {
+ NS_ASSERTION(false, "Could not get folder lock");
+ return NS_MSG_FOLDER_BUSY;
+ }
+
+ nsCOMPtr<nsIMailboxService> mailboxService =
+ do_GetService(NS_MAILBOXSERVICE_CONTRACTID1, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsMsgMailboxParser> parser = new nsMsgMailboxParser(aFolder);
+ NS_ENSURE_TRUE(parser, NS_ERROR_OUT_OF_MEMORY);
+ rv = parser->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailboxService->ParseMailbox(aMsgWindow, pathFile, parser, aListener,
+ nullptr);
+ if (NS_SUCCEEDED(rv))
+ ResetForceReparse(aMsgDB);
+ return rv;
+}
+
+nsresult
+nsMsgBrkMBoxStore::GetOutputStream(nsIArray *aHdrArray,
+ nsCOMPtr<nsIOutputStream> &outputStream,
+ nsCOMPtr<nsISeekableStream> &seekableStream,
+ int64_t &restorePos)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(aHdrArray, 0, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> folder;
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString URI;
+ folder->GetURI(URI);
+ restorePos = -1;
+ if (m_outputStreams.Get(URI, getter_AddRefs(outputStream)))
+ {
+ seekableStream = do_QueryInterface(outputStream);
+ rv = seekableStream->Tell(&restorePos);
+ if (NS_FAILED(rv))
+ {
+ outputStream = nullptr;
+ m_outputStreams.Remove(URI);
+ }
+ }
+ if (!outputStream)
+ {
+ nsCOMPtr<nsIFile> mboxFile;
+ rv = folder->GetFilePath(getter_AddRefs(mboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = MsgGetFileStream(mboxFile, getter_AddRefs(outputStream));
+ seekableStream = do_QueryInterface(outputStream);
+ if (NS_SUCCEEDED(rv))
+ m_outputStreams.Put(URI, outputStream);
+ }
+ return rv;
+}
+
+void nsMsgBrkMBoxStore::SetDBValid(nsIMsgDBHdr *aHdr)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ aHdr->GetFolder(getter_AddRefs(folder));
+ if (folder)
+ {
+ nsCOMPtr<nsIMsgDatabase> db;
+ folder->GetMsgDatabase(getter_AddRefs(db));
+ if (db)
+ SetSummaryFileValid(folder, db, true);
+ }
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::ChangeFlags(nsIArray *aHdrArray,
+ uint32_t aFlags,
+ bool aSet)
+{
+ NS_ENSURE_ARG_POINTER(aHdrArray);
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsCOMPtr<nsISeekableStream> seekableStream;
+ int64_t restoreStreamPos;
+
+ uint32_t messageCount;
+ nsresult rv = aHdrArray->GetLength(&messageCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!messageCount)
+ return NS_ERROR_INVALID_ARG;
+
+ rv = GetOutputStream(aHdrArray, outputStream, seekableStream,
+ restoreStreamPos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ for (uint32_t i = 0; i < messageCount; i++)
+ {
+ msgHdr = do_QueryElementAt(aHdrArray, i, &rv);
+ // Seek to x-mozilla-status offset and rewrite value.
+ rv = UpdateFolderFlag(msgHdr, aSet, aFlags, outputStream);
+ if (NS_FAILED(rv))
+ {
+ NS_WARNING("updateFolderFlag failed");
+ break;
+ }
+ }
+ if (restoreStreamPos != -1)
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, restoreStreamPos);
+ else if (outputStream)
+ outputStream->Close();
+ if (messageCount > 0)
+ {
+ msgHdr = do_QueryElementAt(aHdrArray, 0);
+ SetDBValid(msgHdr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::ChangeKeywords(nsIArray *aHdrArray,
+ const nsACString &aKeywords,
+ bool aAdd)
+{
+ NS_ENSURE_ARG_POINTER(aHdrArray);
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsCOMPtr<nsISeekableStream> seekableStream;
+ int64_t restoreStreamPos;
+
+ uint32_t messageCount;
+ nsresult rv = aHdrArray->GetLength(&messageCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!messageCount)
+ return NS_ERROR_INVALID_ARG;
+
+ rv = GetOutputStream(aHdrArray, outputStream, seekableStream,
+ restoreStreamPos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(outputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>);
+ NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ // For each message, we seek to the beginning of the x-mozilla-status header,
+ // and start reading lines, looking for x-mozilla-keys: headers; If we're
+ // adding the keyword and we find
+ // a header with the desired keyword already in it, we don't need to
+ // do anything. Likewise, if removing keyword and we don't find it,
+ // we don't need to do anything. Otherwise, if adding, we need to
+ // see if there's an x-mozilla-keys
+ // header with room for the new keyword. If so, we replace the
+ // corresponding number of spaces with the keyword. If no room,
+ // we can't do anything until the folder is compacted and another
+ // x-mozilla-keys header is added. In that case, we set a property
+ // on the header, which the compaction code will check.
+
+ nsTArray<nsCString> keywordArray;
+ ParseString(aKeywords, ' ', keywordArray);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ for (uint32_t i = 0; i < messageCount; ++i) // for each message
+ {
+ msgHdr = do_QueryElementAt(aHdrArray, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint64_t messageOffset;
+ msgHdr->GetMessageOffset(&messageOffset);
+ uint32_t statusOffset = 0;
+ (void)msgHdr->GetStatusOffset(&statusOffset);
+ uint64_t desiredOffset = messageOffset + statusOffset;
+
+ ChangeKeywordsHelper(msgHdr, desiredOffset, lineBuffer, keywordArray,
+ aAdd, outputStream, seekableStream, inputStream);
+ }
+ lineBuffer = nullptr;
+ if (restoreStreamPos != -1)
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, restoreStreamPos);
+ else if (outputStream)
+ outputStream->Close();
+ if (messageCount > 0)
+ {
+ msgHdr = do_QueryElementAt(aHdrArray, 0);
+ SetDBValid(msgHdr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::GetStoreType(nsACString& aType)
+{
+ aType.AssignLiteral("mbox");
+ return NS_OK;
+}
+
+// Iterates over the files in the "path" directory, and adds subfolders to
+// parent for each mailbox file found.
+nsresult
+nsMsgBrkMBoxStore::AddSubFolders(nsIMsgFolder *parent, nsCOMPtr<nsIFile> &path,
+ bool deep)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> tmp; // at top level so we can safely assign to path
+ bool isDirectory;
+ path->IsDirectory(&isDirectory);
+ if (!isDirectory)
+ {
+ rv = path->Clone(getter_AddRefs(tmp));
+ path = tmp;
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(".sbd");
+ path->SetLeafName(leafName);
+ path->IsDirectory(&isDirectory);
+ }
+ if (!isDirectory)
+ return NS_OK;
+ // first find out all the current subfolders and files, before using them
+ // while creating new subfolders; we don't want to modify and iterate the same
+ // directory at once.
+ nsCOMArray<nsIFile> currentDirEntries;
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ nsCOMPtr<nsISupports> aSupport;
+ directoryEnumerator->GetNext(getter_AddRefs(aSupport));
+ nsCOMPtr<nsIFile> currentFile(do_QueryInterface(aSupport, &rv));
+ if (currentFile)
+ currentDirEntries.AppendObject(currentFile);
+ }
+
+ // add the folders
+ int32_t count = currentDirEntries.Count();
+ for (int32_t i = 0; i < count; ++i)
+ {
+ nsCOMPtr<nsIFile> currentFile(currentDirEntries[i]);
+
+ nsAutoString leafName;
+ currentFile->GetLeafName(leafName);
+ directoryEnumerator->HasMoreElements(&hasMore);
+ // here we should handle the case where the current file is a .sbd directory
+ // w/o a matching folder file, or a directory w/o the name .sbd
+ if (nsShouldIgnoreFile(leafName))
+ continue;
+
+ nsCOMPtr<nsIMsgFolder> child;
+ rv = parent->AddSubfolder(leafName, getter_AddRefs(child));
+ if (child)
+ {
+ nsString folderName;
+ child->GetName(folderName); // try to get it from cache/db
+ if (folderName.IsEmpty())
+ child->SetPrettyName(leafName);
+ if (deep)
+ {
+ nsCOMPtr<nsIFile> path;
+ rv = child->GetFilePath(getter_AddRefs(path));
+ AddSubFolders(child, path, true);
+ }
+ }
+ }
+ return rv == NS_MSG_FOLDER_EXISTS ? NS_OK : rv;
+}
+
+/* Finds the directory associated with this folder. That is if the path is
+ c:\Inbox, it will return c:\Inbox.sbd if it succeeds. If that path doesn't
+ currently exist then it will create it. Path is strictly an out parameter.
+ */
+nsresult nsMsgBrkMBoxStore::CreateDirectoryForFolder(nsIFile *path)
+{
+ nsresult rv = NS_OK;
+
+ bool pathIsDirectory = false;
+ path->IsDirectory(&pathIsDirectory);
+ if (!pathIsDirectory)
+ {
+ // If the current path isn't a directory, add directory separator
+ // and test it out.
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ rv = path->SetLeafName(leafName);
+ if (NS_FAILED(rv))
+ return rv;
+
+ //If that doesn't exist, then we have to create this directory
+ pathIsDirectory = false;
+ path->IsDirectory(&pathIsDirectory);
+ if (!pathIsDirectory)
+ {
+ bool pathExists;
+ path->Exists(&pathExists);
+ //If for some reason there's a file with the directory separator
+ //then we are going to fail.
+ rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY :
+ path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ }
+ return rv;
+}
+
diff --git a/mailnews/local/src/nsMsgBrkMBoxStore.h b/mailnews/local/src/nsMsgBrkMBoxStore.h
new file mode 100644
index 000000000..4d10e672d
--- /dev/null
+++ b/mailnews/local/src/nsMsgBrkMBoxStore.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ Class for handling Berkeley Mailbox stores.
+*/
+
+#ifndef nsMsgBrkMboxStore_h__
+#define nsMsgBrkMboxStore_h__
+
+#include "nsMsgLocalStoreUtils.h"
+#include "nsIFile.h"
+#include "nsInterfaceHashtable.h"
+#include "nsISeekableStream.h"
+
+class nsMsgBrkMBoxStore final : public nsMsgLocalStoreUtils, nsIMsgPluggableStore
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPLUGGABLESTORE
+
+ nsMsgBrkMBoxStore();
+
+private:
+ ~nsMsgBrkMBoxStore();
+
+protected:
+ nsresult AddSubFolders(nsIMsgFolder *parent, nsCOMPtr<nsIFile> &path, bool deep);
+ nsresult CreateDirectoryForFolder(nsIFile *path);
+ nsresult GetOutputStream(nsIArray *aHdrArray,
+ nsCOMPtr<nsIOutputStream> &outputStream,
+ nsCOMPtr<nsISeekableStream> &seekableStream,
+ int64_t &restorePos);
+ void GetMailboxModProperties(nsIMsgFolder *aFolder,
+ int64_t *aSize, uint32_t *aDate);
+ void SetDBValid(nsIMsgDBHdr *aHdr);
+ // We don't want to keep re-opening an output stream when downloading
+ // multiple pop3 messages, or adjusting x-mozilla-status headers, so
+ // we cache output streams based on folder uri's. If the caller has closed
+ // the stream, we'll get a new one.
+ nsInterfaceHashtable<nsCStringHashKey, nsIOutputStream> m_outputStreams;
+
+#ifdef _DEBUG
+ nsCOMPtr<nsIMsgFolder> m_streamOutstandingFolder;
+#endif
+};
+
+#endif
diff --git a/mailnews/local/src/nsMsgLocalStoreUtils.cpp b/mailnews/local/src/nsMsgLocalStoreUtils.cpp
new file mode 100644
index 000000000..17d9b0f29
--- /dev/null
+++ b/mailnews/local/src/nsMsgLocalStoreUtils.cpp
@@ -0,0 +1,345 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nsMsgLocalStoreUtils.h"
+#include "nsIFile.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgDatabase.h"
+#include "prprf.h"
+
+#define EXTRA_SAFETY_SPACE 0x400000 // (4MiB)
+
+nsMsgLocalStoreUtils::nsMsgLocalStoreUtils()
+{
+}
+
+nsresult
+nsMsgLocalStoreUtils::AddDirectorySeparator(nsIFile *path)
+{
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ return path->SetLeafName(leafName);
+}
+
+bool
+nsMsgLocalStoreUtils::nsShouldIgnoreFile(nsAString& name)
+{
+ char16_t firstChar = name.First();
+ if (firstChar == '.' || firstChar == '#' ||
+ name.CharAt(name.Length() - 1) == '~')
+ return true;
+
+ if (name.LowerCaseEqualsLiteral("msgfilterrules.dat") ||
+ name.LowerCaseEqualsLiteral("rules.dat") ||
+ name.LowerCaseEqualsLiteral("filterlog.html") ||
+ name.LowerCaseEqualsLiteral("junklog.html") ||
+ name.LowerCaseEqualsLiteral("rulesbackup.dat"))
+ return true;
+
+ // don't add summary files to the list of folders;
+ // don't add popstate files to the list either, or rules (sort.dat).
+ if (StringEndsWith(name, NS_LITERAL_STRING(".snm")) ||
+ name.LowerCaseEqualsLiteral("popstate.dat") ||
+ name.LowerCaseEqualsLiteral("sort.dat") ||
+ name.LowerCaseEqualsLiteral("mailfilt.log") ||
+ name.LowerCaseEqualsLiteral("filters.js") ||
+ StringEndsWith(name, NS_LITERAL_STRING(".toc")))
+ return true;
+
+ // ignore RSS data source files
+ if (name.LowerCaseEqualsLiteral("feeds.rdf") ||
+ name.LowerCaseEqualsLiteral("feeditems.rdf") ||
+ StringBeginsWith(name, NS_LITERAL_STRING("feeditems_error")))
+ return true;
+
+ // The .mozmsgs dir is for spotlight support
+ return (StringEndsWith(name, NS_LITERAL_STRING(".mozmsgs")) ||
+ StringEndsWith(name, NS_LITERAL_STRING(".sbd")) ||
+ StringEndsWith(name, NS_LITERAL_STRING(SUMMARY_SUFFIX)));
+}
+
+/**
+ * We're passed a stream positioned at the start of the message.
+ * We start reading lines, looking for x-mozilla-keys: headers; If we're
+ * adding the keyword and we find a header with the desired keyword already
+ * in it, we don't need to do anything. Likewise, if removing keyword and we
+ * don't find it,we don't need to do anything. Otherwise, if adding, we need
+ * to see if there's an x-mozilla-keys header with room for the new keyword.
+ * If so, we replace the corresponding number of spaces with the keyword.
+ * If no room, we can't do anything until the folder is compacted and another
+ * x-mozilla-keys header is added. In that case, we set a property
+ * on the header, which the compaction code will check.
+ * This is not true for maildir, however, since it won't require compaction.
+ */
+
+void
+nsMsgLocalStoreUtils::ChangeKeywordsHelper(nsIMsgDBHdr *message,
+ uint64_t desiredOffset,
+ nsLineBuffer<char> *lineBuffer,
+ nsTArray<nsCString> &keywordArray,
+ bool aAdd,
+ nsIOutputStream *outputStream,
+ nsISeekableStream *seekableStream,
+ nsIInputStream *inputStream)
+{
+ uint32_t bytesWritten;
+
+ for (uint32_t i = 0; i < keywordArray.Length(); i++)
+ {
+ nsAutoCString header;
+ nsAutoCString keywords;
+ bool done = false;
+ uint32_t len = 0;
+ nsAutoCString keywordToWrite(" ");
+
+ keywordToWrite.Append(keywordArray[i]);
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, desiredOffset);
+ // need to reset lineBuffer, which is cheaper than creating a new one.
+ lineBuffer->start = lineBuffer->end = lineBuffer->buf;
+ bool inKeywordHeader = false;
+ bool foundKeyword = false;
+ int64_t offsetToAddKeyword = 0;
+ bool more;
+ message->GetMessageSize(&len);
+ // loop through
+ while (!done)
+ {
+ int64_t lineStartPos;
+ seekableStream->Tell(&lineStartPos);
+ // we need to adjust the linestart pos by how much extra the line
+ // buffer has read from the stream.
+ lineStartPos -= (lineBuffer->end - lineBuffer->start);
+ // NS_ReadLine doesn't return line termination chars.
+ nsCString keywordHeaders;
+ nsresult rv = NS_ReadLine(inputStream, lineBuffer, keywordHeaders, &more);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (keywordHeaders.IsEmpty())
+ break; // passed headers; no x-mozilla-keywords header; give up.
+ if (StringBeginsWith(keywordHeaders,
+ NS_LITERAL_CSTRING(HEADER_X_MOZILLA_KEYWORDS)))
+ inKeywordHeader = true;
+ else if (inKeywordHeader && (keywordHeaders.CharAt(0) == ' ' ||
+ keywordHeaders.CharAt(0) == '\t'))
+ ; // continuation header line
+ else if (inKeywordHeader)
+ break;
+ else
+ continue;
+ uint32_t keywordHdrLength = keywordHeaders.Length();
+ int32_t startOffset, keywordLength;
+ // check if we have the keyword
+ if (MsgFindKeyword(keywordArray[i], keywordHeaders, &startOffset,
+ &keywordLength))
+ {
+ foundKeyword = true;
+ if (!aAdd) // if we're removing, remove it, and break;
+ {
+ keywordHeaders.Cut(startOffset, keywordLength);
+ for (int32_t j = keywordLength; j > 0; j--)
+ keywordHeaders.Append(' ');
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, lineStartPos);
+ outputStream->Write(keywordHeaders.get(), keywordHeaders.Length(),
+ &bytesWritten);
+ }
+ offsetToAddKeyword = 0;
+ // if adding and we already have the keyword, done
+ done = true;
+ break;
+ }
+ // argh, we need to check all the lines to see if we already have the
+ // keyword, but if we don't find it, we want to remember the line and
+ // position where we have room to add the keyword.
+ if (aAdd)
+ {
+ nsAutoCString curKeywordHdr(keywordHeaders);
+ // strip off line ending spaces.
+ curKeywordHdr.Trim(" ", false, true);
+ if (!offsetToAddKeyword && curKeywordHdr.Length() +
+ keywordToWrite.Length() < keywordHdrLength)
+ offsetToAddKeyword = lineStartPos + curKeywordHdr.Length();
+ }
+ }
+ }
+ if (aAdd && !foundKeyword)
+ {
+ if (!offsetToAddKeyword)
+ message->SetUint32Property("growKeywords", 1);
+ else
+ {
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET,
+ offsetToAddKeyword);
+ outputStream->Write(keywordToWrite.get(), keywordToWrite.Length(),
+ &bytesWritten);
+ }
+ }
+ }
+}
+
+nsresult
+nsMsgLocalStoreUtils::UpdateFolderFlag(nsIMsgDBHdr *mailHdr, bool bSet,
+ nsMsgMessageFlagType flag,
+ nsIOutputStream *fileStream)
+{
+ uint32_t statusOffset;
+ uint64_t msgOffset;
+ nsresult rv = mailHdr->GetStatusOffset(&statusOffset);
+ // This probably means there's no x-mozilla-status header, so
+ // we just ignore this.
+ if (NS_FAILED(rv) || (statusOffset == 0))
+ return NS_OK;
+ rv = mailHdr->GetMessageOffset(&msgOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint64_t statusPos = msgOffset + statusOffset;
+ nsCOMPtr<nsISeekableStream> seekableStream(do_QueryInterface(fileStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, statusPos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ char buf[50];
+ buf[0] = '\0';
+ nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(fileStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t bytesRead;
+ if (NS_SUCCEEDED(inputStream->Read(buf, X_MOZILLA_STATUS_LEN + 6,
+ &bytesRead)))
+ {
+ buf[bytesRead] = '\0';
+ if (strncmp(buf, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN) == 0 &&
+ strncmp(buf + X_MOZILLA_STATUS_LEN, ": ", 2) == 0 &&
+ strlen(buf) >= X_MOZILLA_STATUS_LEN + 6)
+ {
+ uint32_t flags;
+ uint32_t bytesWritten;
+ (void)mailHdr->GetFlags(&flags);
+ if (!(flags & nsMsgMessageFlags::Expunged))
+ {
+ char *p = buf + X_MOZILLA_STATUS_LEN + 2;
+
+ nsresult errorCode = NS_OK;
+ flags = nsDependentCString(p).ToInteger(&errorCode, 16);
+
+ uint32_t curFlags;
+ (void)mailHdr->GetFlags(&curFlags);
+ flags = (flags & nsMsgMessageFlags::Queued) |
+ (curFlags & ~nsMsgMessageFlags::RuntimeOnly);
+ if (bSet)
+ flags |= flag;
+ else
+ flags &= ~flag;
+ }
+ else
+ {
+ flags &= ~nsMsgMessageFlags::RuntimeOnly;
+ }
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, statusPos);
+ // We are filing out x-mozilla-status flags here
+ PR_snprintf(buf, sizeof(buf), X_MOZILLA_STATUS_FORMAT,
+ flags & 0x0000FFFF);
+ int32_t lineLen = PL_strlen(buf);
+ uint64_t status2Pos = statusPos + lineLen;
+ fileStream->Write(buf, lineLen, &bytesWritten);
+
+ if (flag & 0xFFFF0000)
+ {
+ // Time to update x-mozilla-status2,
+ // first find it by finding end of previous line, see bug 234935.
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos);
+ do
+ {
+ rv = inputStream->Read(buf, 1, &bytesRead);
+ status2Pos++;
+ } while (NS_SUCCEEDED(rv) && (*buf == '\n' || *buf == '\r'));
+ status2Pos--;
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos);
+ if (NS_SUCCEEDED(inputStream->Read(buf, X_MOZILLA_STATUS2_LEN + 10,
+ &bytesRead)))
+ {
+ if (strncmp(buf, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN) == 0 &&
+ strncmp(buf + X_MOZILLA_STATUS2_LEN, ": ", 2) == 0 &&
+ strlen(buf) >= X_MOZILLA_STATUS2_LEN + 10)
+ {
+ uint32_t dbFlags;
+ (void)mailHdr->GetFlags(&dbFlags);
+ dbFlags &= 0xFFFF0000;
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos);
+ PR_snprintf(buf, sizeof(buf), X_MOZILLA_STATUS2_FORMAT, dbFlags);
+ fileStream->Write(buf, PL_strlen(buf), &bytesWritten);
+ }
+ }
+ }
+ }
+ else
+ {
+#ifdef DEBUG
+ printf("Didn't find %s where expected at position %ld\n"
+ "instead, found %s.\n",
+ X_MOZILLA_STATUS, (long) statusPos, buf);
+#endif
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+ return rv;
+}
+
+/**
+ * Returns true if there is enough space on disk.
+ *
+ * @param aFile Any file in the message store that is on a logical
+ * disk volume so that it can be queried for disk space.
+ * @param aSpaceRequested The size of free space there must be on the disk
+ * to return true.
+ */
+bool
+nsMsgLocalStoreUtils::DiskSpaceAvailableInStore(nsIFile *aFile, uint64_t aSpaceRequested)
+{
+ int64_t diskFree;
+ nsresult rv = aFile->GetDiskSpaceAvailable(&diskFree);
+ if (NS_SUCCEEDED(rv)) {
+#ifdef DEBUG
+ printf("GetDiskSpaceAvailable returned: %lld bytes\n", (long long)diskFree);
+#endif
+ // When checking for disk space available, take into consideration
+ // possible database changes, therefore ask for a little more
+ // (EXTRA_SAFETY_SPACE) than what the requested size is. Also, due to disk
+ // sector sizes, allocation blocks, etc. The space "available" may be greater
+ // than the actual space usable.
+ return ((aSpaceRequested + EXTRA_SAFETY_SPACE) < (uint64_t) diskFree);
+ } else if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ // The call to GetDiskSpaceAvailable is not implemented!
+ // This will happen on certain platforms where GetDiskSpaceAvailable
+ // is not implemented. Since people on those platforms still need
+ // to download mail, we will simply bypass the disk-space check.
+ //
+ // We'll leave a debug message to warn people.
+#ifdef DEBUG
+ printf("Call to GetDiskSpaceAvailable FAILED because it is not implemented!\n");
+#endif
+ return true;
+ } else {
+ printf("Call to GetDiskSpaceAvailable FAILED!\n");
+ return false;
+ }
+}
+
+/**
+ * Resets forceReparse in the database.
+ *
+ * @param aMsgDb The database to reset.
+ */
+void
+nsMsgLocalStoreUtils::ResetForceReparse(nsIMsgDatabase *aMsgDB)
+{
+ if (aMsgDB)
+ {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ aMsgDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (folderInfo)
+ folderInfo->SetBooleanProperty("forceReparse", false);
+ }
+}
diff --git a/mailnews/local/src/nsMsgLocalStoreUtils.h b/mailnews/local/src/nsMsgLocalStoreUtils.h
new file mode 100644
index 000000000..182e0d15a
--- /dev/null
+++ b/mailnews/local/src/nsMsgLocalStoreUtils.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsMsgLocalStoreUtils_h__
+#define nsMsgLocalStoreUtils_h__
+
+#include "msgCore.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsStringGlue.h"
+#include "nsReadLine.h"
+#include "nsISeekableStream.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMailHeaders.h"
+#include "nsMsgUtils.h"
+#include "nsIOutputStream.h"
+#include "nsMsgMessageFlags.h"
+
+/**
+ * Utility Class for handling local mail stores. Berkeley Mailbox
+ * and MailDir stores inherit from this class to share some code.
+*/
+
+class nsMsgLocalStoreUtils
+{
+public:
+ nsMsgLocalStoreUtils();
+
+ static nsresult AddDirectorySeparator(nsIFile *path);
+ static bool nsShouldIgnoreFile(nsAString& name);
+ static void ChangeKeywordsHelper(nsIMsgDBHdr *message,
+ uint64_t desiredOffset,
+ nsLineBuffer<char> *lineBuffer,
+ nsTArray<nsCString> &keywordArray,
+ bool aAdd,
+ nsIOutputStream *outputStream,
+ nsISeekableStream *seekableStream,
+ nsIInputStream *inputStream);
+ static void ResetForceReparse(nsIMsgDatabase *aMsgDB);
+
+ nsresult UpdateFolderFlag(nsIMsgDBHdr *mailHdr, bool bSet,
+ nsMsgMessageFlagType flag,
+ nsIOutputStream *fileStream);
+ bool DiskSpaceAvailableInStore(nsIFile *aFile,
+ uint64_t aSpaceRequested);
+};
+
+#endif
diff --git a/mailnews/local/src/nsMsgMaildirStore.cpp b/mailnews/local/src/nsMsgMaildirStore.cpp
new file mode 100644
index 000000000..6f3228938
--- /dev/null
+++ b/mailnews/local/src/nsMsgMaildirStore.cpp
@@ -0,0 +1,1466 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ Class for handling Maildir stores.
+*/
+
+#include "prprf.h"
+#include "mozilla/Logging.h"
+#include "msgCore.h"
+#include "nsMsgMaildirStore.h"
+#include "nsIMsgFolder.h"
+#include "nsISimpleEnumerator.h"
+#include "nsMsgFolderFlags.h"
+#include "nsILocalMailIncomingServer.h"
+#include "nsCOMArray.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsIMsgDatabase.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsMsgUtils.h"
+#include "nsMsgDBCID.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsMailHeaders.h"
+#include "nsParseMailbox.h"
+#include "nsIMailboxService.h"
+#include "nsMsgLocalCID.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsITimer.h"
+#include "nsIMailboxUrl.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsLocalUndoTxn.h"
+#include "nsIMessenger.h"
+
+static PRLogModuleInfo* MailDirLog;
+
+nsMsgMaildirStore::nsMsgMaildirStore()
+{
+ MailDirLog = PR_NewLogModule("MailDirStore");
+}
+
+nsMsgMaildirStore::~nsMsgMaildirStore()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsMsgMaildirStore, nsIMsgPluggableStore)
+
+// Iterates over the folders in the "path" directory, and adds subfolders to
+// parent for each Maildir folder found.
+nsresult nsMsgMaildirStore::AddSubFolders(nsIMsgFolder *parent, nsIFile *path,
+ bool deep)
+{
+ nsCOMArray<nsIFile> currentDirEntries;
+
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ nsresult rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ nsCOMPtr<nsISupports> aSupport;
+ directoryEnumerator->GetNext(getter_AddRefs(aSupport));
+ nsCOMPtr<nsIFile> currentFile(do_QueryInterface(aSupport, &rv));
+ if (currentFile) {
+ nsAutoString leafName;
+ currentFile->GetLeafName(leafName);
+ bool isDirectory = false;
+ currentFile->IsDirectory(&isDirectory);
+ // Make sure this really is a mail folder dir (i.e., a directory that
+ // contains cur and tmp sub-dirs, and not a .sbd or .mozmsgs dir).
+ if (isDirectory && !nsShouldIgnoreFile(leafName))
+ currentDirEntries.AppendObject(currentFile);
+ }
+ }
+
+ // add the folders
+ int32_t count = currentDirEntries.Count();
+ for (int32_t i = 0; i < count; ++i)
+ {
+ nsCOMPtr<nsIFile> currentFile(currentDirEntries[i]);
+
+ nsAutoString leafName;
+ currentFile->GetLeafName(leafName);
+
+ nsCOMPtr<nsIMsgFolder> child;
+ rv = parent->AddSubfolder(leafName, getter_AddRefs(child));
+ if (child)
+ {
+ nsString folderName;
+ child->GetName(folderName); // try to get it from cache/db
+ if (folderName.IsEmpty())
+ child->SetPrettyName(leafName);
+ if (deep)
+ {
+ nsCOMPtr<nsIFile> path;
+ rv = child->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Construct the .sbd directory path for the possible children of the
+ // folder.
+ GetDirectoryForFolder(path);
+ bool directory = false;
+ // Check that <folder>.sbd really is a directory.
+ path->IsDirectory(&directory);
+ if (directory)
+ AddSubFolders(child, path, true);
+ }
+ }
+ }
+ return rv == NS_MSG_FOLDER_EXISTS ? NS_OK : rv;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::DiscoverSubFolders(nsIMsgFolder *aParentFolder,
+ bool aDeep)
+{
+ NS_ENSURE_ARG_POINTER(aParentFolder);
+
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aParentFolder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isServer, directory = false;
+ aParentFolder->GetIsServer(&isServer);
+ if (!isServer)
+ GetDirectoryForFolder(path);
+
+ path->IsDirectory(&directory);
+ if (directory)
+ rv = AddSubFolders(aParentFolder, path, aDeep);
+
+ return (rv == NS_MSG_FOLDER_EXISTS) ? NS_OK : rv;
+}
+
+/**
+ * Create if missing a Maildir-style folder with "tmp" and "cur" subfolders
+ * but no "new" subfolder, because it doesn't make sense in the mail client
+ * context. ("new" directory is for messages on the server that haven't been
+* seen by a mail client).
+ * aFolderName is already "safe" - it has been through NS_MsgHashIfNecessary.
+ */
+nsresult nsMsgMaildirStore::CreateMaildir(nsIFile *path)
+{
+ nsresult rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
+ {
+ NS_WARNING("Could not create root directory for message folder");
+ return rv;
+ }
+
+ // Create tmp, cur leaves
+ nsCOMPtr<nsIFile> leaf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ leaf->InitWithFile(path);
+
+ leaf->AppendNative(NS_LITERAL_CSTRING("tmp"));
+ rv = leaf->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
+ {
+ NS_WARNING("Could not create tmp directory for message folder");
+ return rv;
+ }
+
+ leaf->SetNativeLeafName(NS_LITERAL_CSTRING("cur"));
+ rv = leaf->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
+ {
+ NS_WARNING("Could not create cur directory for message folder");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::CreateFolder(nsIMsgFolder *aParent,
+ const nsAString &aFolderName,
+ nsIMsgFolder **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (aFolderName.IsEmpty())
+ return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ nsCOMPtr <nsIFile> path;
+ nsresult rv = aParent->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get a directory based on our current path
+ bool isServer;
+ aParent->GetIsServer(&isServer);
+ rv = CreateDirectoryForFolder(path, isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure the new folder name is valid
+ nsAutoString safeFolderName(aFolderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+
+ path->Append(safeFolderName);
+ bool exists;
+ path->Exists(&exists);
+ if (exists) //check this because localized names are different from disk names
+ return NS_MSG_FOLDER_EXISTS;
+
+ rv = CreateMaildir(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> child;
+ // GetFlags and SetFlags in AddSubfolder will fail because we have no db at
+ // this point but mFlags is set.
+ rv = aParent->AddSubfolder(safeFolderName, getter_AddRefs(child));
+ if (!child || NS_FAILED(rv))
+ {
+ path->Remove(true); // recursive
+ return rv;
+ }
+
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ if (msgDBService)
+ {
+ nsCOMPtr<nsIMsgDatabase> unusedDB;
+ rv = msgDBService->OpenFolderDB(child, true, getter_AddRefs(unusedDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ rv = msgDBService->CreateNewDB(child, getter_AddRefs(unusedDB));
+
+ if ((NS_SUCCEEDED(rv) || rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) &&
+ unusedDB)
+ {
+ //need to set the folder name
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (NS_SUCCEEDED(rv))
+ folderInfo->SetMailboxName(safeFolderName);
+
+ unusedDB->SetSummaryValid(true);
+ unusedDB->Close(true);
+ aParent->UpdateSummaryTotals(true);
+ }
+ else
+ {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("CreateFolder - failed creating db for new folder\n"));
+ path->Remove(true); // recursive
+ rv = NS_MSG_CANT_CREATE_FOLDER;
+ }
+ }
+ child.swap(*aResult);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::HasSpaceAvailable(nsIMsgFolder *aFolder,
+ int64_t aSpaceRequested,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = DiskSpaceAvailableInStore(pathFile, aSpaceRequested);
+ if (!*aResult)
+ return NS_ERROR_FILE_DISK_FULL;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::IsSummaryFileValid(nsIMsgFolder *aFolder,
+ nsIMsgDatabase *aDB,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aDB);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ nsresult rv = dbFolderInfo->GetBooleanProperty("maildirValid", false,
+ aResult);
+ if (!*aResult)
+ {
+ nsCOMPtr<nsIFile> newFile;
+ rv = aFolder->GetFilePath(getter_AddRefs(newFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ newFile->Append(NS_LITERAL_STRING("cur"));
+
+ // If the "cur" sub-dir doesn't exist, and there are no messages
+ // in the db, then the folder is probably new and the db is valid.
+ bool exists;
+ newFile->Exists(&exists);
+ if (!exists)
+ {
+ int32_t numMessages;
+ dbFolderInfo->GetNumMessages(&numMessages);
+ if (!numMessages)
+ *aResult = true;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::SetSummaryFileValid(nsIMsgFolder *aFolder,
+ nsIMsgDatabase *aDB,
+ bool aValid)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aDB);
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ NS_ENSURE_STATE(dbFolderInfo);
+ return dbFolderInfo->SetBooleanProperty("maildirValid", aValid);
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::DeleteFolder(nsIMsgFolder *aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ bool exists;
+
+ // Delete the Maildir itself.
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ exists = false;
+ pathFile->Exists(&exists);
+ if (exists) {
+ rv = pathFile->Remove(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Delete any subfolders (.sbd-suffixed directories).
+ AddDirectorySeparator(pathFile);
+ exists = false;
+ pathFile->Exists(&exists);
+ if (exists) {
+ rv = pathFile->Remove(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::RenameFolder(nsIMsgFolder *aFolder,
+ const nsAString & aNewName,
+ nsIMsgFolder **aNewFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aNewFolder);
+
+ // old path
+ nsCOMPtr<nsIFile> oldPathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(oldPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // old sbd directory
+ nsCOMPtr<nsIFile> sbdPathFile;
+ uint32_t numChildren;
+ aFolder->GetNumSubFolders(&numChildren);
+ if (numChildren > 0)
+ {
+ sbdPathFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sbdPathFile->InitWithFile(oldPathFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ GetDirectoryForFolder(sbdPathFile);
+ }
+
+ // old summary
+ nsCOMPtr<nsIFile> oldSummaryFile;
+ rv = aFolder->GetSummaryFile(getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Validate new name
+ nsAutoString safeName(aNewName);
+ NS_MsgHashIfNecessary(safeName);
+
+ aFolder->ForceDBClosed();
+
+ // rename folder
+ rv = oldPathFile->MoveTo(nullptr, safeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (numChildren > 0)
+ {
+ // rename "*.sbd" directory
+ nsAutoString sbdName = safeName;
+ sbdName += NS_LITERAL_STRING(FOLDER_SUFFIX);
+ sbdPathFile->MoveTo(nullptr, sbdName);
+ }
+
+ // rename summary
+ nsAutoString summaryName(safeName);
+ summaryName += NS_LITERAL_STRING(SUMMARY_SUFFIX);
+ oldSummaryFile->MoveTo(nullptr, summaryName);
+
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ rv = aFolder->GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder)
+ return NS_ERROR_NULL_POINTER;
+
+ return parentFolder->AddSubfolder(safeName, aNewFolder);
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::CopyFolder(nsIMsgFolder *aSrcFolder,
+ nsIMsgFolder *aDstFolder,
+ bool aIsMoveFolder,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgCopyServiceListener *aListener,
+ const nsAString &aNewName)
+{
+ NS_ENSURE_ARG_POINTER(aSrcFolder);
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+
+ nsAutoString folderName;
+ if (aNewName.IsEmpty())
+ aSrcFolder->GetName(folderName);
+ else
+ folderName.Assign(aNewName);
+
+ nsAutoString safeFolderName(folderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+ nsCOMPtr<nsIMsgLocalMailFolder> localSrcFolder(do_QueryInterface(aSrcFolder));
+ aSrcFolder->ForceDBClosed();
+
+ nsCOMPtr<nsIFile> oldPath;
+ nsresult rv = aSrcFolder->GetFilePath(getter_AddRefs(oldPath));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIFile> summaryFile;
+ GetSummaryFileLocation(oldPath, getter_AddRefs(summaryFile));
+
+ nsCOMPtr<nsIFile> newPath;
+ rv = aDstFolder->GetFilePath(getter_AddRefs(newPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create target directory based on our current path
+ bool isServer;
+ aDstFolder->GetIsServer(&isServer);
+ rv = CreateDirectoryForFolder(newPath, isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> origPath;
+ oldPath->Clone(getter_AddRefs(origPath));
+
+ rv = oldPath->CopyTo(newPath, safeFolderName);
+ NS_ENSURE_SUCCESS(rv, rv); //will fail if a file by that name exists
+
+ // Copy to dir can fail if file does not exist. If copy fails, we test
+ // if the file exists or not, if it does not that's ok, we continue
+ // without copying it. If it fails and file exist and is not zero sized
+ // there is real problem.
+ nsAutoString dbName(safeFolderName);
+ dbName += NS_LITERAL_STRING(SUMMARY_SUFFIX);
+ rv = summaryFile->CopyTo(newPath, dbName);
+ if (!NS_SUCCEEDED(rv))
+ {
+ // Test if the file is not empty
+ bool exists;
+ int64_t fileSize;
+ summaryFile->Exists(&exists);
+ summaryFile->GetFileSize(&fileSize);
+ if (exists && fileSize > 0)
+ NS_ENSURE_SUCCESS(rv, rv); // Yes, it should have worked!
+ // else case is file is zero sized, no need to copy it,
+ // not an error
+ // else case is file does not exist - not an error
+ }
+
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ rv = aDstFolder->AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newMsgFolder->SetPrettyName(folderName);
+ uint32_t flags;
+ aSrcFolder->GetFlags(&flags);
+ newMsgFolder->SetFlags(flags);
+ bool changed = false;
+ rv = aSrcFolder->MatchOrChangeFilterDestination(newMsgFolder, true, &changed);
+ if (changed)
+ aSrcFolder->AlertFilterChanged(aMsgWindow);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = aSrcFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy subfolders to the new location
+ nsresult copyStatus = NS_OK;
+ nsCOMPtr<nsIMsgLocalMailFolder> localNewFolder(do_QueryInterface(newMsgFolder, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore &&
+ NS_SUCCEEDED(copyStatus))
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item));
+ if (!folder)
+ continue;
+
+ copyStatus = localNewFolder->CopyFolderLocal(folder, false, aMsgWindow,
+ aListener);
+ // Test if the call succeeded, if not we have to stop recursive call
+ if (NS_FAILED(copyStatus))
+ {
+ // Copy failed we have to notify caller to handle the error and stop
+ // moving the folders. In case this happens to the topmost level of
+ // recursive call, then we just need to break from the while loop and
+ // go to error handling code.
+ if (!aIsMoveFolder)
+ return copyStatus;
+ break;
+ }
+ }
+ }
+
+ if (aIsMoveFolder && NS_SUCCEEDED(copyStatus))
+ {
+ if (localNewFolder)
+ {
+ nsCOMPtr<nsISupports> srcSupport(do_QueryInterface(aSrcFolder));
+ localNewFolder->OnCopyCompleted(srcSupport, true);
+ }
+
+ // Notify that the folder that was dragged and dropped has been created.
+ // No need to do this for its subfolders - isMoveFolder will be true for folder.
+ aDstFolder->NotifyItemAdded(newMsgFolder);
+
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ aSrcFolder->GetParent(getter_AddRefs(msgParent));
+ aSrcFolder->SetParent(nullptr);
+ if (msgParent)
+ {
+ // The files have already been moved, so delete storage false
+ msgParent->PropagateDelete(aSrcFolder, false, aMsgWindow);
+ oldPath->Remove(true);
+ nsCOMPtr<nsIMsgDatabase> srcDB; // we need to force closed the source db
+ aSrcFolder->Delete();
+
+ nsCOMPtr<nsIFile> parentPath;
+ rv = msgParent->GetFilePath(getter_AddRefs(parentPath));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ AddDirectorySeparator(parentPath);
+ nsCOMPtr<nsISimpleEnumerator> children;
+ parentPath->GetDirectoryEntries(getter_AddRefs(children));
+ bool more;
+ // checks if the directory is empty or not
+ if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more)
+ parentPath->Remove(true);
+ }
+ }
+ else
+ {
+ // This is the case where the copy of a subfolder failed.
+ // We have to delete the newDirectory tree to make a "rollback".
+ // Someone should add a popup to warn the user that the move was not
+ // possible.
+ if (aIsMoveFolder && NS_FAILED(copyStatus))
+ {
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ newMsgFolder->ForceDBClosed();
+ newMsgFolder->GetParent(getter_AddRefs(msgParent));
+ newMsgFolder->SetParent(nullptr);
+ if (msgParent)
+ {
+ msgParent->PropagateDelete(newMsgFolder, false, aMsgWindow);
+ newMsgFolder->Delete();
+ newMsgFolder->ForceDBClosed();
+ AddDirectorySeparator(newPath);
+ newPath->Remove(true); //berkeley mailbox
+ }
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::GetNewMsgOutputStream(nsIMsgFolder *aFolder,
+ nsIMsgDBHdr **aNewMsgHdr,
+ bool *aReusable,
+ nsIOutputStream **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aNewMsgHdr);
+ NS_ENSURE_ARG_POINTER(aReusable);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aReusable = false; // message per file
+
+ nsCOMPtr<nsIMsgDatabase> db;
+ aFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (!db)
+ NS_ERROR("no db");
+
+ nsresult rv;
+
+ if (!*aNewMsgHdr)
+ {
+ rv = db->CreateNewHdr(nsMsgKey_None, aNewMsgHdr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ }
+ (*aNewMsgHdr)->SetMessageOffset(0);
+ // path to the message download folder
+ nsCOMPtr<nsIFile> newFile;
+ rv = aFolder->GetFilePath(getter_AddRefs(newFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ newFile->Append(NS_LITERAL_STRING("tmp"));
+
+ // let's check if the folder exists
+ bool exists;
+ newFile->Exists(&exists);
+ if (!exists) {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("GetNewMsgOutputStream - tmp subfolder does not exist!!\n"));
+ rv = newFile->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // generate new file name
+ nsAutoCString newName;
+ newName.AppendInt(static_cast<int64_t>(PR_Now()));
+ newFile->AppendNative(newName);
+ // CreateUnique, in case we get more than one message per millisecond :-)
+ rv = newFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newFile->GetNativeLeafName(newName);
+ // save the file name in the message header - otherwise no way to retrieve it
+ (*aNewMsgHdr)->SetStringProperty("storeToken", newName.get());
+ return MsgNewBufferedFileOutputStream(aResult, newFile,
+ PR_WRONLY | PR_CREATE_FILE, 00600);
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::DiscardNewMessage(nsIOutputStream *aOutputStream,
+ nsIMsgDBHdr *aNewHdr)
+{
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+
+ aOutputStream->Close();
+ // file path is stored in message header property "storeToken"
+ nsAutoCString fileName;
+ aNewHdr->GetStringProperty("storeToken", getter_Copies(fileName));
+ if (fileName.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> path;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // path to the message download folder
+ path->Append(NS_LITERAL_STRING("tmp"));
+ path->AppendNative(fileName);
+
+ return path->Remove(false);
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::FinishNewMessage(nsIOutputStream *aOutputStream,
+ nsIMsgDBHdr *aNewHdr)
+{
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+
+ aOutputStream->Close();
+
+ nsCOMPtr<nsIFile> folderPath;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // file path is stored in message header property
+ nsAutoCString fileName;
+ aNewHdr->GetStringProperty("storeToken", getter_Copies(fileName));
+ if (fileName.IsEmpty())
+ {
+ NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ // path to the new destination
+ nsCOMPtr<nsIFile> toPath;
+ folderPath->Clone(getter_AddRefs(toPath));
+ toPath->Append(NS_LITERAL_STRING("cur"));
+
+ // let's check if the folder exists
+ bool exists;
+ toPath->Exists(&exists);
+ if (!exists)
+ {
+ rv = toPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // path to the downloaded message
+ nsCOMPtr<nsIFile> fromPath;
+ folderPath->Clone(getter_AddRefs(fromPath));
+ fromPath->Append(NS_LITERAL_STRING("tmp"));
+ fromPath->AppendNative(fileName);
+
+ // let's check if the tmp file exists
+ fromPath->Exists(&exists);
+ if (!exists)
+ {
+ // Perhaps the message has already moved. See bug 1028372 to fix this.
+ toPath->AppendNative(fileName);
+ toPath->Exists(&exists);
+ if (exists) // then there is nothing to do
+ return NS_OK;
+
+ NS_ERROR("FinishNewMessage - oops! file does not exist!");
+ return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+ }
+
+ nsCOMPtr<nsIFile> existingPath;
+ toPath->Clone(getter_AddRefs(existingPath));
+ existingPath->AppendNative(fileName);
+ existingPath->Exists(&exists);
+
+ if (exists) {
+ rv = existingPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ existingPath->GetNativeLeafName(fileName);
+ aNewHdr->SetStringProperty("storeToken", fileName.get());
+ }
+
+ return fromPath->MoveToNative(toPath, fileName);
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::MoveNewlyDownloadedMessage(nsIMsgDBHdr *aHdr,
+ nsIMsgFolder *aDestFolder,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aHdr);
+ NS_ENSURE_ARG_POINTER(aDestFolder);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsIFile> folderPath;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // file path is stored in message header property
+ nsAutoCString fileName;
+ aHdr->GetStringProperty("storeToken", getter_Copies(fileName));
+ if (fileName.IsEmpty())
+ {
+ NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ // path to the downloaded message
+ nsCOMPtr<nsIFile> fromPath;
+ folderPath->Clone(getter_AddRefs(fromPath));
+ fromPath->Append(NS_LITERAL_STRING("cur"));
+ fromPath->AppendNative(fileName);
+
+ // let's check if the tmp file exists
+ bool exists;
+ fromPath->Exists(&exists);
+ if (!exists)
+ {
+ NS_ERROR("FinishNewMessage - oops! file does not exist!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // move to the "cur" subfolder
+ nsCOMPtr<nsIFile> toPath;
+ aDestFolder->GetFilePath(getter_AddRefs(folderPath));
+ folderPath->Clone(getter_AddRefs(toPath));
+ toPath->Append(NS_LITERAL_STRING("cur"));
+
+ // let's check if the folder exists
+ toPath->Exists(&exists);
+ if (!exists)
+ {
+ rv = toPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIMsgDatabase> destMailDB;
+ rv = aDestFolder->GetMsgDatabase(getter_AddRefs(destMailDB));
+ NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv),
+ "failed to open mail db moving message");
+
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ if (destMailDB)
+ rv = destMailDB->CopyHdrFromExistingHdr(nsMsgKey_None, aHdr, true,
+ getter_AddRefs(newHdr));
+ if (NS_SUCCEEDED(rv) && !newHdr)
+ rv = NS_ERROR_UNEXPECTED;
+
+ if (NS_FAILED(rv)) {
+ aDestFolder->ThrowAlertMsg("filterFolderHdrAddFailed", nullptr);
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> existingPath;
+ toPath->Clone(getter_AddRefs(existingPath));
+ existingPath->AppendNative(fileName);
+ existingPath->Exists(&exists);
+
+ if (exists) {
+ rv = existingPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ existingPath->GetNativeLeafName(fileName);
+ newHdr->SetStringProperty("storeToken", fileName.get());
+ }
+
+ rv = fromPath->MoveToNative(toPath, fileName);
+ *aResult = NS_SUCCEEDED(rv);
+ if (NS_FAILED(rv))
+ aDestFolder->ThrowAlertMsg("filterFolderWriteFailed", nullptr);
+
+ if (NS_FAILED(rv)) {
+ if (destMailDB)
+ destMailDB->Close(true);
+
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+
+ bool movedMsgIsNew = false;
+ // if we have made it this far then the message has successfully been
+ // written to the new folder now add the header to the destMailDB.
+
+ uint32_t newFlags;
+ newHdr->GetFlags(&newFlags);
+ nsMsgKey msgKey;
+ newHdr->GetMessageKey(&msgKey);
+ if (!(newFlags & nsMsgMessageFlags::Read))
+ {
+ nsCString junkScoreStr;
+ (void) newHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ if (atoi(junkScoreStr.get()) != nsIJunkMailPlugin::IS_SPAM_SCORE) {
+ newHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
+ destMailDB->AddToNewList(msgKey);
+ movedMsgIsNew = true;
+ }
+ }
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgAdded(newHdr);
+
+ if (movedMsgIsNew) {
+ aDestFolder->SetHasNewMessages(true);
+
+ // Notify the message was moved.
+ if (notifier) {
+ notifier->NotifyItemEvent(folder,
+ NS_LITERAL_CSTRING("UnincorporatedMessageMoved"),
+ newHdr);
+ }
+ }
+
+ nsCOMPtr<nsIMsgDatabase> sourceDB;
+ rv = folder->GetMsgDatabase(getter_AddRefs(sourceDB));
+
+ if (NS_SUCCEEDED(rv) && sourceDB)
+ sourceDB->RemoveHeaderMdbRow(aHdr);
+
+ destMailDB->SetSummaryValid(true);
+ aDestFolder->UpdateSummaryTotals(true);
+ destMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::GetMsgInputStream(nsIMsgFolder *aMsgFolder,
+ const nsACString &aMsgToken,
+ int64_t *aOffset,
+ nsIMsgDBHdr *aMsgHdr,
+ bool *aReusable,
+ nsIInputStream **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_ENSURE_ARG_POINTER(aOffset);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aReusable = false; // message per file
+ *aOffset = 0;
+
+ // construct path to file
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aMsgFolder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aMsgToken.IsEmpty())
+ {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("GetMsgInputStream - empty storeToken!!\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ path->Append(NS_LITERAL_STRING("cur"));
+
+ // let's check if the folder exists
+ bool exists;
+ path->Exists(&exists);
+ if (!exists) {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("GetMsgInputStream - oops! cur subfolder does not exist!\n"));
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ path->AppendNative(aMsgToken);
+ return NS_NewLocalFileInputStream(aResult, path);
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::DeleteMessages(nsIArray *aHdrArray)
+{
+ uint32_t messageCount;
+ nsresult rv = aHdrArray->GetLength(&messageCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ for (uint32_t i = 0; i < messageCount; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(aHdrArray, i, &rv);
+ if (NS_FAILED(rv))
+ continue;
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ nsCOMPtr<nsIFile> path;
+ rv = folder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString fileName;
+ msgHdr->GetStringProperty("storeToken", getter_Copies(fileName));
+
+ if (fileName.IsEmpty())
+ {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("DeleteMessages - empty storeToken!!\n"));
+ // Perhaps an offline store has not downloaded this particular message.
+ continue;
+ }
+
+ path->Append(NS_LITERAL_STRING("cur"));
+ path->AppendNative(fileName);
+
+ // Let's check if the message exists.
+ bool exists;
+ path->Exists(&exists);
+ if (!exists)
+ {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("DeleteMessages - file does not exist !!\n"));
+ // Perhaps an offline store has not downloaded this particular message.
+ continue;
+ }
+ path->Remove(false);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::CopyMessages(bool aIsMove, nsIArray *aHdrArray,
+ nsIMsgFolder *aDstFolder,
+ nsIMsgCopyServiceListener *aListener,
+ nsIArray **aDstHdrs,
+ nsITransaction **aUndoAction,
+ bool *aCopyDone)
+{
+ NS_ENSURE_ARG_POINTER(aHdrArray);
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+ NS_ENSURE_ARG_POINTER(aCopyDone);
+ NS_ENSURE_ARG_POINTER(aUndoAction);
+
+ *aCopyDone = false;
+
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(aHdrArray, 0, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgHdr->GetFolder(getter_AddRefs(srcFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Both source and destination folders must use maildir type store.
+ nsCOMPtr<nsIMsgPluggableStore> srcStore;
+ nsAutoCString srcType;
+ srcFolder->GetMsgStore(getter_AddRefs(srcStore));
+ if (srcStore)
+ srcStore->GetStoreType(srcType);
+ nsCOMPtr<nsIMsgPluggableStore> dstStore;
+ nsAutoCString dstType;
+ aDstFolder->GetMsgStore(getter_AddRefs(dstStore));
+ if (dstStore)
+ dstStore->GetStoreType(dstType);
+ if (!srcType.EqualsLiteral("maildir") || !dstType.EqualsLiteral("maildir"))
+ return NS_OK;
+
+ // Both source and destination must be local folders. In theory we could
+ // do efficient copies of the offline store of IMAP, but this is not
+ // supported yet. For that, we need to deal with both correct handling
+ // of deletes from the src server, and msgKey = UIDL in the dst folder.
+ nsCOMPtr<nsIMsgLocalMailFolder> destLocalFolder(do_QueryInterface(aDstFolder));
+ if (!destLocalFolder)
+ return NS_OK;
+ nsCOMPtr<nsIMsgLocalMailFolder> srcLocalFolder(do_QueryInterface(srcFolder));
+ if (!srcLocalFolder)
+ return NS_OK;
+
+ // We should be able to use a file move for an efficient copy.
+
+ nsCOMPtr<nsIFile> destFolderPath;
+ nsCOMPtr<nsIMsgDatabase> destDB;
+ aDstFolder->GetMsgDatabase(getter_AddRefs(destDB));
+ rv = aDstFolder->GetFilePath(getter_AddRefs(destFolderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+ destFolderPath->Append(NS_LITERAL_STRING("cur"));
+
+ nsCOMPtr<nsIFile> srcFolderPath;
+ rv = srcFolder->GetFilePath(getter_AddRefs(srcFolderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+ srcFolderPath->Append(NS_LITERAL_STRING("cur"));
+
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ RefPtr<nsLocalMoveCopyMsgTxn> msgTxn = new nsLocalMoveCopyMsgTxn;
+ NS_ENSURE_TRUE(msgTxn, NS_ERROR_OUT_OF_MEMORY);
+ if (NS_SUCCEEDED(msgTxn->Init(srcFolder, aDstFolder, aIsMove)))
+ {
+ if (aIsMove)
+ msgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ else
+ msgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ }
+
+ if (aListener)
+ aListener->OnStartCopy();
+
+ nsCOMPtr<nsIMutableArray> dstHdrs(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t messageCount;
+ rv = aHdrArray->GetLength(&messageCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < messageCount; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> srcHdr = do_QueryElementAt(aHdrArray, i, &rv);
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("srcHdr null\n"));
+ continue;
+ }
+ nsMsgKey srcKey;
+ srcHdr->GetMessageKey(&srcKey);
+ msgTxn->AddSrcKey(srcKey);
+ nsAutoCString fileName;
+ srcHdr->GetStringProperty("storeToken", getter_Copies(fileName));
+ if (fileName.IsEmpty())
+ {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("GetMsgInputStream - empty storeToken!!\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> srcFile;
+ rv = srcFolderPath->Clone(getter_AddRefs(srcFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ srcFile->AppendNative(fileName);
+
+ nsCOMPtr<nsIFile> destFile;
+ destFolderPath->Clone(getter_AddRefs(destFile));
+ destFile->AppendNative(fileName);
+ bool exists;
+ destFile->Exists(&exists);
+ if (exists)
+ {
+ rv = destFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ destFile->GetNativeLeafName(fileName);
+ }
+ if (aIsMove)
+ rv = srcFile->MoveToNative(destFolderPath, fileName);
+ else
+ rv = srcFile->CopyToNative(destFolderPath, fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> destHdr;
+ if (destDB)
+ {
+ rv = destDB->CopyHdrFromExistingHdr(nsMsgKey_None, srcHdr, true, getter_AddRefs(destHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ destHdr->SetStringProperty("storeToken", fileName.get());
+ dstHdrs->AppendElement(destHdr, false);
+ nsMsgKey dstKey;
+ destHdr->GetMessageKey(&dstKey);
+ msgTxn->AddDstKey(dstKey);
+ if (aListener)
+ aListener->SetMessageKey(dstKey);
+ }
+ }
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsMoveCopyCompleted(aIsMove, aHdrArray, aDstFolder,
+ dstHdrs);
+
+ // For now, we only support local dest folders, and for those we are done and
+ // can delete the messages. Perhaps this should be moved into the folder
+ // when we try to support other folder types.
+ if (aIsMove)
+ {
+ for (uint32_t i = 0; i < messageCount; ++i)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr(do_QueryElementAt(aHdrArray, i, &rv));
+ rv = srcDB->DeleteHeader(msgDBHdr, nullptr, false, true);
+ }
+ }
+
+ *aCopyDone = true;
+ nsCOMPtr<nsISupports> srcSupports(do_QueryInterface(srcFolder));
+ if (destLocalFolder)
+ destLocalFolder->OnCopyCompleted(srcSupports, true);
+ if (aListener)
+ aListener->OnStopCopy(NS_OK);
+ msgTxn.forget(aUndoAction);
+ dstHdrs.forget(aDstHdrs);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::GetSupportsCompaction(bool *aSupportsCompaction)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsCompaction);
+ *aSupportsCompaction = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::CompactFolder(nsIMsgFolder *aFolder,
+ nsIUrlListener *aListener,
+ nsIMsgWindow *aMsgWindow)
+{
+ return NS_OK;
+}
+
+class MaildirStoreParser
+{
+public:
+ MaildirStoreParser(nsIMsgFolder *aFolder, nsIMsgDatabase *aMsgDB,
+ nsISimpleEnumerator *aDirectoryEnumerator,
+ nsIUrlListener *aUrlListener);
+ virtual ~MaildirStoreParser();
+
+ nsresult ParseNextMessage(nsIFile *aFile);
+ static void TimerCallback(nsITimer *aTimer, void *aClosure);
+ nsresult StartTimer();
+
+ nsCOMPtr<nsISimpleEnumerator> m_directoryEnumerator;
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsCOMPtr<nsIMsgDatabase> m_db;
+ nsCOMPtr<nsITimer> m_timer;
+ nsCOMPtr<nsIUrlListener> m_listener;
+};
+
+MaildirStoreParser::MaildirStoreParser(nsIMsgFolder *aFolder,
+ nsIMsgDatabase *aMsgDB,
+ nsISimpleEnumerator *aDirEnum,
+ nsIUrlListener *aUrlListener)
+{
+ m_folder = aFolder;
+ m_db = aMsgDB;
+ m_directoryEnumerator = aDirEnum;
+ m_listener = aUrlListener;
+}
+
+MaildirStoreParser::~MaildirStoreParser()
+{
+}
+
+nsresult MaildirStoreParser::ParseNextMessage(nsIFile *aFile)
+{
+ nsresult rv;
+ NS_ENSURE_TRUE(m_db, NS_ERROR_NULL_POINTER);
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIMsgParseMailMsgState> msgParser =
+ do_CreateInstance(NS_PARSEMAILMSGSTATE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgParser->SetMailDB(m_db);
+ nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
+ rv = m_db->CreateNewHdr(nsMsgKey_None, getter_AddRefs(newMsgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newMsgHdr->SetMessageOffset(0);
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+ if (NS_SUCCEEDED(rv) && inputStream)
+ {
+ nsMsgLineStreamBuffer *inputStreamBuffer =
+ new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE, true, false);
+ int64_t fileSize;
+ aFile->GetFileSize(&fileSize);
+ msgParser->SetNewMsgHdr(newMsgHdr);
+ msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
+ msgParser->SetEnvelopePos(0);
+ bool needMoreData = false;
+ char * newLine = nullptr;
+ uint32_t numBytesInLine = 0;
+ // we only have to read the headers, because we know the message size
+ // from the file size. So we can do this in one time slice.
+ do
+ {
+ newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine,
+ needMoreData);
+ if (newLine)
+ {
+ msgParser->ParseAFolderLine(newLine, numBytesInLine);
+ NS_Free(newLine);
+ }
+ } while (newLine && numBytesInLine > 0);
+
+ msgParser->FinishHeader();
+ // A single message needs to be less than 4GB
+ newMsgHdr->SetMessageSize((uint32_t) fileSize);
+ m_db->AddNewHdrToDB(newMsgHdr, true);
+ nsAutoCString storeToken;
+ aFile->GetNativeLeafName(storeToken);
+ newMsgHdr->SetStringProperty("storeToken", storeToken.get());
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+void MaildirStoreParser::TimerCallback(nsITimer *aTimer, void *aClosure)
+{
+ MaildirStoreParser *parser = (MaildirStoreParser *) aClosure;
+ bool hasMore;
+ parser->m_directoryEnumerator->HasMoreElements(&hasMore);
+ if (!hasMore)
+ {
+ nsCOMPtr<nsIMsgPluggableStore> store;
+ parser->m_folder->GetMsgStore(getter_AddRefs(store));
+ parser->m_timer->Cancel();
+ parser->m_db->SetSummaryValid(true);
+// store->SetSummaryFileValid(parser->m_folder, parser->m_db, true);
+ if (parser->m_listener)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMailboxUrl> mailboxurl =
+ do_CreateInstance(NS_MAILBOXURL_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && mailboxurl)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(mailboxurl);
+ url->SetUpdatingFolder(true);
+ nsAutoCString uriSpec("mailbox://");
+ // ### TODO - what if SetSpec fails?
+ (void) url->SetSpec(uriSpec);
+ parser->m_listener->OnStopRunningUrl(url, NS_OK);
+ }
+ }
+ // Parsing complete and timer cancelled, so we release the parser object.
+ delete parser;
+ return;
+ }
+ nsCOMPtr<nsISupports> aSupport;
+ parser->m_directoryEnumerator->GetNext(getter_AddRefs(aSupport));
+ nsresult rv;
+ nsCOMPtr<nsIFile> currentFile(do_QueryInterface(aSupport, &rv));
+ if (NS_SUCCEEDED(rv))
+ rv = parser->ParseNextMessage(currentFile);
+ if (NS_FAILED(rv) && parser->m_listener)
+ parser->m_listener->OnStopRunningUrl(nullptr, NS_ERROR_FAILURE);
+}
+
+nsresult MaildirStoreParser::StartTimer()
+{
+ nsresult rv;
+ m_timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_timer->InitWithFuncCallback(TimerCallback, (void *) this, 0,
+ nsITimer::TYPE_REPEATING_SLACK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::RebuildIndex(nsIMsgFolder *aFolder,
+ nsIMsgDatabase *aMsgDB,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ // This code needs to iterate over the maildir files, and parse each
+ // file and add a msg hdr to the db for the file.
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+ path->Append(NS_LITERAL_STRING("cur"));
+
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MaildirStoreParser *fileParser = new MaildirStoreParser(aFolder, aMsgDB,
+ directoryEnumerator,
+ aListener);
+ NS_ENSURE_TRUE(fileParser, NS_ERROR_OUT_OF_MEMORY);
+ fileParser->StartTimer();
+ ResetForceReparse(aMsgDB);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::ChangeFlags(nsIArray *aHdrArray,
+ uint32_t aFlags,
+ bool aSet)
+{
+ NS_ENSURE_ARG_POINTER(aHdrArray);
+
+ uint32_t messageCount;
+ nsresult rv = aHdrArray->GetLength(&messageCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < messageCount; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(aHdrArray, i, &rv);
+ // get output stream for header
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = GetOutputStream(msgHdr, outputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Seek to x-mozilla-status offset and rewrite value.
+ rv = UpdateFolderFlag(msgHdr, aSet, aFlags, outputStream);
+ if (NS_FAILED(rv))
+ NS_WARNING("updateFolderFlag failed");
+ }
+ return NS_OK;
+}
+
+// get output stream from header
+nsresult
+nsMsgMaildirStore::GetOutputStream(nsIMsgDBHdr *aHdr,
+ nsCOMPtr<nsIOutputStream> &aOutputStream)
+{
+ // file name is stored in message header property "storeToken"
+ nsAutoCString fileName;
+ aHdr->GetStringProperty("storeToken", getter_Copies(fileName));
+ if (fileName.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> folderPath;
+ rv = folder->GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> maildirFile;
+ folderPath->Clone(getter_AddRefs(maildirFile));
+ maildirFile->Append(NS_LITERAL_STRING("cur"));
+ maildirFile->AppendNative(fileName);
+
+ return MsgGetFileStream(maildirFile, getter_AddRefs(aOutputStream));
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::ChangeKeywords(nsIArray *aHdrArray,
+ const nsACString &aKeywords,
+ bool aAdd)
+{
+ NS_ENSURE_ARG_POINTER(aHdrArray);
+ NS_ENSURE_ARG_POINTER(aHdrArray);
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsCOMPtr<nsISeekableStream> seekableStream;
+
+ uint32_t messageCount;
+ nsresult rv = aHdrArray->GetLength(&messageCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!messageCount)
+ return NS_ERROR_INVALID_ARG;
+
+ nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>);
+ NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ nsTArray<nsCString> keywordArray;
+ ParseString(aKeywords, ' ', keywordArray);
+
+ for (uint32_t i = 0; i < messageCount; ++i) // for each message
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aHdrArray, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // get output stream for header
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = GetOutputStream(message, outputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIInputStream> inputStream = do_QueryInterface(outputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsISeekableStream> seekableStream(do_QueryInterface(inputStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t statusOffset = 0;
+ (void)message->GetStatusOffset(&statusOffset);
+ uint64_t desiredOffset = statusOffset;
+
+ ChangeKeywordsHelper(message, desiredOffset, lineBuffer, keywordArray,
+ aAdd, outputStream, seekableStream, inputStream);
+ if (inputStream)
+ inputStream->Close();
+ // ### TODO - if growKeywords property is set on the message header,
+ // we need to rewrite the message file with extra room for the keywords,
+ // or schedule some sort of background task to do this.
+ }
+ lineBuffer = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::GetStoreType(nsACString& aType)
+{
+ aType.AssignLiteral("maildir");
+ return NS_OK;
+}
+
+/**
+ * Finds the directory associated with this folder. That is if the path is
+ * c:\Inbox, it will return c:\Inbox.sbd if it succeeds. Path is strictly
+ * an out parameter.
+ */
+nsresult nsMsgMaildirStore::GetDirectoryForFolder(nsIFile *path)
+{
+ // add directory separator to the path
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ return path->SetLeafName(leafName);
+}
+
+nsresult nsMsgMaildirStore::CreateDirectoryForFolder(nsIFile *path,
+ bool aIsServer)
+{
+ nsresult rv = NS_OK;
+ if (!aIsServer)
+ {
+ rv = GetDirectoryForFolder(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ bool pathIsDirectory = false;
+ path->IsDirectory(&pathIsDirectory);
+ if (!pathIsDirectory)
+ {
+ bool pathExists;
+ path->Exists(&pathExists);
+ //If for some reason there's a file with the directory separator
+ //then we are going to fail.
+ rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY :
+ path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ return rv;
+}
diff --git a/mailnews/local/src/nsMsgMaildirStore.h b/mailnews/local/src/nsMsgMaildirStore.h
new file mode 100644
index 000000000..f15944e5d
--- /dev/null
+++ b/mailnews/local/src/nsMsgMaildirStore.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ Class for handling Maildir stores.
+*/
+
+#ifndef nsMsgMaildirStore_h__
+#define nsMsgMaildirStore_h__
+
+#include "nsMsgLocalStoreUtils.h"
+#include "nsIFile.h"
+#include "nsMsgMessageFlags.h"
+
+class nsMsgMaildirStore final : public nsMsgLocalStoreUtils, nsIMsgPluggableStore
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPLUGGABLESTORE
+
+ nsMsgMaildirStore();
+
+private:
+ ~nsMsgMaildirStore();
+
+protected:
+ nsresult GetDirectoryForFolder(nsIFile *path);
+ nsresult CreateDirectoryForFolder(nsIFile *path, bool aIsServer);
+
+ nsresult CreateMaildir(nsIFile *path);
+ nsresult AddSubFolders(nsIMsgFolder *parent, nsIFile *path, bool deep);
+ nsresult GetOutputStream(nsIMsgDBHdr *aHdr,
+ nsCOMPtr<nsIOutputStream> &aOutputStream);
+
+};
+#endif
diff --git a/mailnews/local/src/nsNoIncomingServer.cpp b/mailnews/local/src/nsNoIncomingServer.cpp
new file mode 100644
index 000000000..1c18bf7fd
--- /dev/null
+++ b/mailnews/local/src/nsNoIncomingServer.cpp
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // pre-compiled headers
+
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+#include "nsNoIncomingServer.h"
+#include "nsMsgLocalCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNoIncomingServer,
+ nsMsgIncomingServer,
+ nsINoIncomingServer,
+ nsILocalMailIncomingServer)
+
+nsNoIncomingServer::nsNoIncomingServer()
+{
+}
+
+nsNoIncomingServer::~nsNoIncomingServer()
+{
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetLocalStoreType(nsACString& type)
+{
+ type.AssignLiteral("mailbox");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetLocalDatabaseType(nsACString& type)
+{
+ type.AssignLiteral("mailbox");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetAccountManagerChrome(nsAString& aResult)
+{
+ aResult.AssignLiteral("am-serverwithnoidentities.xul");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::SetFlagsOnDefaultMailboxes()
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
+ do_QueryInterface(rootFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // None server may have an inbox if it's deferred to,
+ // or if it's the smart mailboxes account.
+ localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::SpecialUse);
+
+ return NS_OK;
+}
+
+// TODO: make this work with maildir message store, bug 890742.
+NS_IMETHODIMP nsNoIncomingServer::CopyDefaultMessages(const char *folderNameOnDisk)
+{
+ NS_ENSURE_ARG(folderNameOnDisk);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get defaults directory for messenger files. MailSession service appends 'messenger' to the
+ // the app defaults folder and returns it. Locale will be added to the path, if there is one.
+ nsCOMPtr<nsIFile> defaultMessagesFile;
+ rv = mailSession->GetDataFilesDir("messenger", getter_AddRefs(defaultMessagesFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if bin/defaults/messenger/<folderNameOnDisk>
+ // (or bin/defaults/messenger/<locale>/<folderNameOnDisk> if we had a locale provide) exists.
+ // it doesn't have to exist. if it doesn't, return
+ rv = defaultMessagesFile->AppendNative(nsDependentCString(folderNameOnDisk));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = defaultMessagesFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> parentDir;
+ rv = GetLocalPath(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if parentDir/<folderNameOnDisk> exists
+ {
+ nsCOMPtr<nsIFile> testDir;
+ rv = parentDir->Clone(getter_AddRefs(testDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = testDir->AppendNative(nsDependentCString(folderNameOnDisk));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = testDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // if it exists add to the end, else copy
+ if (exists)
+ {
+#ifdef DEBUG
+ printf("append default %s (unimplemented)\n", folderNameOnDisk);
+#endif
+ // todo for bug #1181 (the bug ID seems wrong...)
+ // open folderFile, seek to end
+ // read defaultMessagesFile, write to folderFile
+ }
+ else {
+#ifdef DEBUG
+ printf("copy default %s\n",folderNameOnDisk);
+#endif
+ rv = defaultMessagesFile->CopyTo(parentDir, EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsNoIncomingServer::CreateDefaultMailboxes()
+{
+ nsresult rv;
+ bool isHidden = false;
+ GetHidden(&isHidden);
+ if (isHidden)
+ return NS_OK;
+
+ // notice, no Inbox, unless we're deferred to...
+ bool isDeferredTo;
+ if (NS_SUCCEEDED(GetIsDeferredTo(&isDeferredTo)) && isDeferredTo)
+ {
+ rv = CreateLocalFolder(NS_LITERAL_STRING("Inbox"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = CreateLocalFolder(NS_LITERAL_STRING("Trash"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // copy the default templates into the Templates folder
+ rv = CopyDefaultMessages("Templates");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CreateLocalFolder(NS_LITERAL_STRING("Unsent Messages"));
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetNewMail(nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener, nsIMsgFolder *aInbox, nsIURI **aResult)
+{
+ nsCOMArray<nsIPop3IncomingServer> deferredServers;
+ nsresult rv = GetDeferredServers(this, deferredServers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!deferredServers.IsEmpty())
+ {
+ rv = deferredServers[0]->DownloadMailFromServers(deferredServers.Elements(),
+ deferredServers.Length(), aMsgWindow, aInbox, aUrlListener);
+ }
+ // listener might be counting on us to send a notification.
+ else if (aUrlListener)
+ aUrlListener->OnStopRunningUrl(nullptr, NS_OK);
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetCanSearchMessages(bool *canSearchMessages)
+{
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ *canSearchMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff)
+{
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ *aServerRequiresPasswordForBiff = false; // for local folders, we don't require a password
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetSortOrder(int32_t* aSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = 200000000;
+ return NS_OK;
+}
+
diff --git a/mailnews/local/src/nsNoIncomingServer.h b/mailnews/local/src/nsNoIncomingServer.h
new file mode 100644
index 000000000..809531627
--- /dev/null
+++ b/mailnews/local/src/nsNoIncomingServer.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsNoIncomingServer_h
+#define __nsNoIncomingServer_h
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsINoIncomingServer.h"
+#include "nsILocalMailIncomingServer.h"
+#include "nsMsgIncomingServer.h"
+#include "nsMailboxServer.h"
+
+/* get some implementation from nsMsgIncomingServer */
+class nsNoIncomingServer : public nsMailboxServer,
+ public nsINoIncomingServer,
+ public nsILocalMailIncomingServer
+
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINOINCOMINGSERVER
+ NS_DECL_NSILOCALMAILINCOMINGSERVER
+
+ nsNoIncomingServer();
+
+ NS_IMETHOD GetLocalStoreType(nsACString& type) override;
+ NS_IMETHOD GetLocalDatabaseType(nsACString& type) override;
+ NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override;
+ NS_IMETHOD GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) override;
+ NS_IMETHOD GetAccountManagerChrome(nsAString& aResult) override;
+ NS_IMETHOD GetSortOrder(int32_t* aSortOrder) override;
+
+private:
+ virtual ~nsNoIncomingServer();
+};
+
+
+#endif
diff --git a/mailnews/local/src/nsNoneService.cpp b/mailnews/local/src/nsNoneService.cpp
new file mode 100644
index 000000000..dad56c063
--- /dev/null
+++ b/mailnews/local/src/nsNoneService.cpp
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+
+#include "nsNoneService.h"
+#include "nsINoIncomingServer.h"
+#include "nsINoneService.h"
+#include "nsIMsgProtocolInfo.h"
+
+#include "nsMsgLocalCID.h"
+#include "nsMsgBaseCID.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsMsgUtils.h"
+
+#include "nsIDirectoryService.h"
+#include "nsMailDirServiceDefs.h"
+
+#define PREF_MAIL_ROOT_NONE "mail.root.none" // old - for backward compatibility only
+#define PREF_MAIL_ROOT_NONE_REL "mail.root.none-rel"
+
+nsNoneService::nsNoneService()
+{
+}
+
+nsNoneService::~nsNoneService()
+{}
+
+NS_IMPL_ISUPPORTS(nsNoneService, nsINoneService, nsIMsgProtocolInfo)
+
+NS_IMETHODIMP
+nsNoneService::SetDefaultLocalPath(nsIFile *aPath)
+{
+ NS_ENSURE_ARG(aPath);
+ return NS_SetPersistentFile(PREF_MAIL_ROOT_NONE_REL, PREF_MAIL_ROOT_NONE, aPath);
+}
+
+NS_IMETHODIMP
+nsNoneService::GetDefaultLocalPath(nsIFile ** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ bool havePref;
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_GetPersistentFile(PREF_MAIL_ROOT_NONE_REL,
+ PREF_MAIL_ROOT_NONE,
+ NS_APP_MAIL_50_DIR,
+ havePref,
+ getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ rv = localFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!havePref || !exists)
+ {
+ rv = NS_SetPersistentFile(PREF_MAIL_ROOT_NONE_REL, PREF_MAIL_ROOT_NONE, localFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
+ }
+
+ NS_IF_ADDREF(*aResult = localFile);
+ return NS_OK;
+
+}
+
+
+NS_IMETHODIMP
+nsNoneService::GetServerIID(nsIID* *aServerIID)
+{
+ *aServerIID = new nsIID(NS_GET_IID(nsINoIncomingServer));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetRequiresUsername(bool *aRequiresUsername)
+{
+ NS_ENSURE_ARG_POINTER(aRequiresUsername);
+ *aRequiresUsername = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress)
+{
+ NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress);
+ *aPreflightPrettyNameWithEmailAddress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp)
+{
+ NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp);
+ *aCanLoginAtStartUp = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetCanDelete(bool *aCanDelete)
+{
+ NS_ENSURE_ARG_POINTER(aCanDelete);
+ *aCanDelete = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetCanDuplicate(bool *aCanDuplicate)
+{
+ NS_ENSURE_ARG_POINTER(aCanDuplicate);
+ *aCanDuplicate = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetCanGetMessages(bool *aCanGetMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetMessages);
+ *aCanGetMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages);
+ *aCanGetIncomingMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetDefaultDoBiff(bool *aDoBiff)
+{
+ NS_ENSURE_ARG_POINTER(aDoBiff);
+ // by default, don't do biff for "none" servers
+ *aDoBiff = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetDefaultServerPort(bool isSecure, int32_t *aDefaultPort)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultPort);
+ *aDefaultPort = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetShowComposeMsgLink(bool *showComposeMsgLink)
+{
+ NS_ENSURE_ARG_POINTER(showComposeMsgLink);
+ *showComposeMsgLink = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetFoldersCreatedAsync(bool *aAsyncCreation)
+{
+ NS_ENSURE_ARG_POINTER(aAsyncCreation);
+ *aAsyncCreation = false;
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsNoneService.h b/mailnews/local/src/nsNoneService.h
new file mode 100644
index 000000000..fe8495528
--- /dev/null
+++ b/mailnews/local/src/nsNoneService.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNoneService_h___
+#define nsNoneService_h___
+
+#include "nscore.h"
+
+#include "nsIMsgProtocolInfo.h"
+#include "nsINoneService.h"
+
+class nsNoneService : public nsIMsgProtocolInfo, public nsINoneService
+{
+public:
+
+ nsNoneService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPROTOCOLINFO
+ NS_DECL_NSINONESERVICE
+
+private:
+ virtual ~nsNoneService();
+};
+
+#endif /* nsNoneService_h___ */
diff --git a/mailnews/local/src/nsParseMailbox.cpp b/mailnews/local/src/nsParseMailbox.cpp
new file mode 100644
index 000000000..da51c0322
--- /dev/null
+++ b/mailnews/local/src/nsParseMailbox.cpp
@@ -0,0 +1,2632 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nsIURI.h"
+#include "nsParseMailbox.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIInputStream.h"
+#include "nsIFile.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgDBCID.h"
+#include "nsIMailboxUrl.h"
+#include "nsNetUtil.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgFolder.h"
+#include "nsIURL.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgFilterList.h"
+#include "nsIMsgFilter.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsRDFCID.h"
+#include "nsIRDFService.h"
+#include "nsMsgI18N.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsMsgUtils.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsISeekableStream.h"
+#include "nsIMimeHeaders.h"
+#include "nsIMsgMdnGenerator.h"
+#include "nsMsgSearchCore.h"
+#include "nsMailHeaders.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgComposeParams.h"
+#include "nsMsgCompCID.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDocShell.h"
+#include "nsIMsgCompose.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIMsgComposeService.h"
+#include "nsIMsgCopyService.h"
+#include "nsICryptoHash.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsIMsgFilterCustomAction.h"
+#include <ctype.h>
+#include "nsIMsgPluggableStore.h"
+#include "mozilla/Services.h"
+#include "nsQueryObject.h"
+#include "nsIOutputStream.h"
+#include "mozilla/Attributes.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+/* the following macros actually implement addref, release and query interface for our component. */
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgMailboxParser,
+ nsParseMailMessageState,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+// Whenever data arrives from the connection, core netlib notifices the protocol by calling
+// OnDataAvailable. We then read and process the incoming data from the input stream.
+NS_IMETHODIMP nsMsgMailboxParser::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *aIStream, uint64_t sourceOffset, uint32_t aLength)
+{
+ // right now, this really just means turn around and process the url
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> url = do_QueryInterface(ctxt, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = ProcessMailboxInputStream(url, aIStream, aLength);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailboxParser::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ m_startTime = PR_Now();
+
+
+ // extract the appropriate event sinks from the url and initialize them in our protocol data
+ // the URL should be queried for a nsIMailboxURL. If it doesn't support a mailbox URL interface then
+ // we have an error.
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIIOService> ioServ =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(ioServ, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIMailboxUrl> runningUrl = do_QueryInterface(ctxt, &rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(ctxt);
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
+
+ if (NS_SUCCEEDED(rv) && runningUrl && folder)
+ {
+ url->GetStatusFeedback(getter_AddRefs(m_statusFeedback));
+
+ // okay, now fill in our event sinks...Note that each getter ref counts before
+ // it returns the interface to us...we'll release when we are done
+
+ folder->GetName(m_folderName);
+
+ nsCOMPtr<nsIFile> path;
+ folder->GetFilePath(getter_AddRefs(path));
+
+ if (path)
+ {
+ int64_t fileSize;
+ path->GetFileSize(&fileSize);
+ // the size of the mailbox file is our total base line for measuring progress
+ m_graph_progress_total = fileSize;
+ UpdateStatusText("buildingSummary");
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ if (msgDBService)
+ {
+ // Use OpenFolderDB to always open the db so that db's m_folder
+ // is set correctly.
+ rv = msgDBService->OpenFolderDB(folder, true,
+ getter_AddRefs(m_mailDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ rv = msgDBService->CreateNewDB(folder,
+ getter_AddRefs(m_mailDB));
+
+ if (m_mailDB)
+ m_mailDB->AddListener(this);
+ }
+ NS_ASSERTION(m_mailDB, "failed to open mail db parsing folder");
+
+ // try to get a backup message database
+ nsresult rvignore = folder->GetBackupMsgDatabase(
+ getter_AddRefs(m_backupMailDB));
+
+ // We'll accept failures and move on, as we're dealing with some
+ // sort of unknown problem to begin with.
+ if (NS_FAILED(rvignore))
+ {
+ if (m_backupMailDB)
+ m_backupMailDB->RemoveListener(this);
+ m_backupMailDB = nullptr;
+ }
+ else if (m_backupMailDB)
+ {
+ m_backupMailDB->AddListener(this);
+ }
+ }
+ }
+
+ // need to get the mailbox name out of the url and call SetMailboxName with it.
+ // then, we need to open the mail db for this parser.
+ return rv;
+}
+
+// stop binding is a "notification" informing us that the stream associated with aURL is going away.
+NS_IMETHODIMP nsMsgMailboxParser::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus)
+{
+ DoneParsingFolder(aStatus);
+ // what can we do? we can close the stream?
+ m_urlInProgress = false; // don't close the connection...we may be re-using it.
+
+ if (m_mailDB)
+ m_mailDB->RemoveListener(this);
+ // and we want to mark ourselves for deletion or some how inform our protocol manager that we are
+ // available for another url if there is one....
+
+ ReleaseFolderLock();
+ // be sure to clear any status text and progress info..
+ m_graph_progress_received = 0;
+ UpdateProgressPercent();
+ UpdateStatusText("localStatusDocumentDone");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParseMailMessageState::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange,
+ bool aPreChange, uint32_t *aStatus, nsIDBChangeListener * aInstigator)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsParseMailMessageState::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged,
+ uint32_t aOldFlags, uint32_t aNewFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParseMailMessageState::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged,
+ nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParseMailMessageState::OnHdrAdded(nsIMsgDBHdr *aHdrAdded,
+ nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void OnParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in nsMsgKey newParent, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP
+nsParseMailMessageState::OnParentChanged(nsMsgKey aKeyChanged,
+ nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void OnAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */
+NS_IMETHODIMP
+nsParseMailMessageState::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
+{
+ if (m_backupMailDB && m_backupMailDB == instigator)
+ {
+ m_backupMailDB->RemoveListener(this);
+ m_backupMailDB = nullptr;
+ }
+ else if (m_mailDB)
+ {
+ m_mailDB->RemoveListener(this);
+ m_mailDB = nullptr;
+ m_newMsgHdr = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
+/* void OnReadChanged (in nsIDBChangeListener instigator); */
+NS_IMETHODIMP
+nsParseMailMessageState::OnReadChanged(nsIDBChangeListener *instigator)
+{
+ return NS_OK;
+}
+
+/* void OnJunkScoreChanged (in nsIDBChangeListener instigator); */
+NS_IMETHODIMP
+nsParseMailMessageState::OnJunkScoreChanged(nsIDBChangeListener *instigator)
+{
+ return NS_OK;
+}
+
+nsMsgMailboxParser::nsMsgMailboxParser() : nsMsgLineBuffer(nullptr, false)
+{
+ Init();
+}
+
+nsMsgMailboxParser::nsMsgMailboxParser(nsIMsgFolder *aFolder) : nsMsgLineBuffer(nullptr, false)
+{
+ m_folder = do_GetWeakReference(aFolder);
+}
+
+nsMsgMailboxParser::~nsMsgMailboxParser()
+{
+ ReleaseFolderLock();
+}
+
+nsresult nsMsgMailboxParser::Init()
+{
+ m_obuffer = nullptr;
+ m_obuffer_size = 0;
+ m_graph_progress_total = 0;
+ m_graph_progress_received = 0;
+ return AcquireFolderLock();
+}
+
+void nsMsgMailboxParser::UpdateStatusText (const char* stringName)
+{
+ if (m_statusFeedback)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService)
+ return;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle));
+ if (NS_FAILED(rv))
+ return;
+ nsString finalString;
+ const char16_t * stringArray[] = { m_folderName.get() };
+ rv = bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(stringName).get(),
+ stringArray, 1, getter_Copies(finalString));
+ m_statusFeedback->ShowStatusString(finalString);
+ }
+}
+
+void nsMsgMailboxParser::UpdateProgressPercent ()
+{
+ if (m_statusFeedback && m_graph_progress_total != 0)
+ {
+ // prevent overflow by dividing both by 100
+ int64_t progressTotal = m_graph_progress_total / 100;
+ int64_t progressReceived = m_graph_progress_received / 100;
+ if (progressTotal > 0)
+ m_statusFeedback->ShowProgress((100 *(progressReceived)) / progressTotal);
+ }
+}
+
+nsresult nsMsgMailboxParser::ProcessMailboxInputStream(nsIURI* aURL, nsIInputStream *aIStream, uint32_t aLength)
+{
+ nsresult ret = NS_OK;
+
+ uint32_t bytesRead = 0;
+
+ if (NS_SUCCEEDED(m_inputStream.GrowBuffer(aLength)))
+ {
+ // OK, this sucks, but we're going to have to copy into our
+ // own byte buffer, and then pass that to the line buffering code,
+ // which means a couple buffer copies.
+ ret = aIStream->Read(m_inputStream.GetBuffer(), aLength, &bytesRead);
+ if (NS_SUCCEEDED(ret))
+ ret = BufferInput(m_inputStream.GetBuffer(), bytesRead);
+ }
+ if (m_graph_progress_total > 0)
+ {
+ if (NS_SUCCEEDED(ret))
+ m_graph_progress_received += bytesRead;
+ }
+ return (ret);
+}
+
+void nsMsgMailboxParser::DoneParsingFolder(nsresult status)
+{
+ /* End of file. Flush out any partial line remaining in the buffer. */
+ FlushLastLine();
+ PublishMsgHeader(nullptr);
+
+ // only mark the db valid if we've succeeded.
+ if (NS_SUCCEEDED(status) && m_mailDB) // finished parsing, so flush db folder info
+ UpdateDBFolderInfo();
+ else if (m_mailDB)
+ m_mailDB->SetSummaryValid(false);
+
+ // remove the backup database
+ if (m_backupMailDB)
+ {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
+ if (folder)
+ folder->RemoveBackupMsgDatabase();
+ m_backupMailDB = nullptr;
+ }
+
+ // if (m_folder != nullptr)
+ // m_folder->SummaryChanged();
+ FreeBuffers();
+}
+
+void nsMsgMailboxParser::FreeBuffers()
+{
+ /* We're done reading the folder - we don't need these things
+ any more. */
+ PR_FREEIF (m_obuffer);
+ m_obuffer_size = 0;
+}
+
+void nsMsgMailboxParser::UpdateDBFolderInfo()
+{
+ UpdateDBFolderInfo(m_mailDB);
+}
+
+// update folder info in db so we know not to reparse.
+void nsMsgMailboxParser::UpdateDBFolderInfo(nsIMsgDatabase *mailDB)
+{
+ mailDB->SetSummaryValid(true);
+}
+
+// Tell the world about the message header (add to db, and view, if any)
+int32_t nsMsgMailboxParser::PublishMsgHeader(nsIMsgWindow *msgWindow)
+{
+ FinishHeader();
+ if (m_newMsgHdr)
+ {
+ char storeToken[100];
+ PR_snprintf(storeToken, sizeof(storeToken), "%lld", m_envelope_pos);
+ m_newMsgHdr->SetStringProperty("storeToken", storeToken);
+
+ uint32_t flags;
+ (void)m_newMsgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Expunged)
+ {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ m_mailDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ uint32_t size;
+ (void)m_newMsgHdr->GetMessageSize(&size);
+ folderInfo->ChangeExpungedBytes(size);
+ m_newMsgHdr = nullptr;
+ }
+ else if (m_mailDB)
+ {
+ // add hdr but don't notify - shouldn't be requiring notifications
+ // during summary file rebuilding
+ m_mailDB->AddNewHdrToDB(m_newMsgHdr, false);
+ m_newMsgHdr = nullptr;
+ }
+ else
+ NS_ASSERTION(false, "no database while parsing local folder"); // should have a DB, no?
+ }
+ else if (m_mailDB)
+ {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ m_mailDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (folderInfo)
+ folderInfo->ChangeExpungedBytes(m_position - m_envelope_pos);
+ }
+ return 0;
+}
+
+void nsMsgMailboxParser::AbortNewHeader()
+{
+ if (m_newMsgHdr && m_mailDB)
+ m_newMsgHdr = nullptr;
+}
+
+void nsMsgMailboxParser::OnNewMessage(nsIMsgWindow *msgWindow)
+{
+ PublishMsgHeader(msgWindow);
+ Clear();
+}
+
+nsresult nsMsgMailboxParser::HandleLine(const char *line, uint32_t lineLength)
+{
+ /* If this is the very first line of a non-empty folder, make sure it's an envelope */
+ if (m_graph_progress_received == 0)
+ {
+ /* This is the first block from the file. Check to see if this
+ looks like a mail file. */
+ const char *s = line;
+ const char *end = s + lineLength;
+ while (s < end && IS_SPACE(*s))
+ s++;
+ if ((end - s) < 20 || !IsEnvelopeLine(s, end - s))
+ {
+// char buf[500];
+// PR_snprintf (buf, sizeof(buf),
+// XP_GetString(MK_MSG_NON_MAIL_FILE_READ_QUESTION),
+// folder_name);
+// else if (!FE_Confirm (m_context, buf))
+// return NS_MSG_NOT_A_MAIL_FOLDER; /* #### NOT_A_MAIL_FILE */
+ }
+ }
+// m_graph_progress_received += lineLength;
+
+ // mailbox parser needs to do special stuff when it finds an envelope
+ // after parsing a message body. So do that.
+ if (line[0] == 'F' && IsEnvelopeLine(line, lineLength))
+ {
+ // **** This used to be
+ // PR_ASSERT (m_parseMsgState->m_state == nsMailboxParseBodyState);
+ // **** I am not sure this is a right thing to do. This happens when
+ // going online, downloading a message while playing back append
+ // draft/template offline operation. We are mixing
+ // nsMailboxParseBodyState &&
+ // nsMailboxParseHeadersState. David I need your help here too. **** jt
+
+ NS_ASSERTION (m_state == nsIMsgParseMailMsgState::ParseBodyState ||
+ m_state == nsIMsgParseMailMsgState::ParseHeadersState, "invalid parse state"); /* else folder corrupted */
+ OnNewMessage(nullptr);
+ nsresult rv = StartNewEnvelope(line, lineLength);
+ NS_ASSERTION(NS_SUCCEEDED(rv), " error starting envelope parsing mailbox");
+ // at the start of each new message, update the progress bar
+ UpdateProgressPercent();
+ return rv;
+ }
+
+ // otherwise, the message parser can handle it completely.
+ if (m_mailDB != nullptr) // if no DB, do we need to parse at all?
+ return ParseFolderLine(line, lineLength);
+
+ return NS_ERROR_NULL_POINTER; // need to error out if we don't have a db.
+}
+
+void
+nsMsgMailboxParser::ReleaseFolderLock()
+{
+ nsresult result;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
+ if (!folder)
+ return;
+ bool haveSemaphore;
+ nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMsgParseMailMsgState*>(this));
+ result = folder->TestSemaphore(supports, &haveSemaphore);
+ if (NS_SUCCEEDED(result) && haveSemaphore)
+ (void) folder->ReleaseSemaphore(supports);
+}
+
+nsresult
+nsMsgMailboxParser::AcquireFolderLock()
+{
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
+ if (!folder)
+ return NS_ERROR_NULL_POINTER;
+ nsCOMPtr<nsISupports> supports = do_QueryObject(this);
+ return folder->AcquireSemaphore(supports);
+}
+
+NS_IMPL_ISUPPORTS(nsParseMailMessageState, nsIMsgParseMailMsgState, nsIDBChangeListener)
+
+nsParseMailMessageState::nsParseMailMessageState()
+{
+ m_position = 0;
+ m_new_key = nsMsgKey_None;
+ m_IgnoreXMozillaStatus = false;
+ m_state = nsIMsgParseMailMsgState::ParseBodyState;
+
+ // setup handling of custom db headers, headers that are added to .msf files
+ // as properties of the nsMsgHdr objects, controlled by the
+ // pref mailnews.customDBHeaders, a space-delimited list of headers.
+ // E.g., if mailnews.customDBHeaders is "X-Spam-Score", and we're parsing
+ // a mail message with the X-Spam-Score header, we'll set the
+ // "x-spam-score" property of nsMsgHdr to the value of the header.
+ m_customDBHeaderValues = nullptr;
+ nsCString customDBHeaders; // not shown in search UI
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (pPrefBranch)
+ {
+ pPrefBranch->GetCharPref("mailnews.customDBHeaders", getter_Copies(customDBHeaders));
+ ToLowerCase(customDBHeaders);
+ if (customDBHeaders.Find("content-base") == -1)
+ customDBHeaders.Insert(NS_LITERAL_CSTRING("content-base "), 0);
+ ParseString(customDBHeaders, ' ', m_customDBHeaders);
+
+ // now add customHeaders
+ nsCString customHeadersString; // shown in search UI
+ nsTArray<nsCString> customHeadersArray;
+ pPrefBranch->GetCharPref("mailnews.customHeaders", getter_Copies(customHeadersString));
+ ToLowerCase(customHeadersString);
+ customHeadersString.StripWhitespace();
+ ParseString(customHeadersString, ':', customHeadersArray);
+ for (uint32_t i = 0; i < customHeadersArray.Length(); i++)
+ {
+ if (!m_customDBHeaders.Contains(customHeadersArray[i]))
+ m_customDBHeaders.AppendElement(customHeadersArray[i]);
+ }
+
+ if (m_customDBHeaders.Length())
+ {
+ m_customDBHeaderValues = new struct message_header [m_customDBHeaders.Length()];
+ if (!m_customDBHeaderValues)
+ m_customDBHeaders.Clear();
+ }
+ }
+ Clear();
+}
+
+nsParseMailMessageState::~nsParseMailMessageState()
+{
+ ClearAggregateHeader (m_toList);
+ ClearAggregateHeader (m_ccList);
+ delete [] m_customDBHeaderValues;
+}
+
+void nsParseMailMessageState::Init(uint64_t fileposition)
+{
+ m_state = nsIMsgParseMailMsgState::ParseBodyState;
+ m_position = fileposition;
+ m_newMsgHdr = nullptr;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::Clear()
+{
+ m_message_id.length = 0;
+ m_references.length = 0;
+ m_date.length = 0;
+ m_delivery_date.length = 0;
+ m_from.length = 0;
+ m_sender.length = 0;
+ m_newsgroups.length = 0;
+ m_subject.length = 0;
+ m_status.length = 0;
+ m_mozstatus.length = 0;
+ m_mozstatus2.length = 0;
+ m_envelope_from.length = 0;
+ m_envelope_date.length = 0;
+ m_priority.length = 0;
+ m_keywords.length = 0;
+ m_mdn_dnt.length = 0;
+ m_return_path.length = 0;
+ m_account_key.length = 0;
+ m_in_reply_to.length = 0;
+ m_replyTo.length = 0;
+ m_content_type.length = 0;
+ m_mdn_original_recipient.length = 0;
+ m_bccList.length = 0;
+ m_body_lines = 0;
+ m_lastLineBlank = 0;
+ m_newMsgHdr = nullptr;
+ m_envelope_pos = 0;
+ m_new_key = nsMsgKey_None;
+ ClearAggregateHeader (m_toList);
+ ClearAggregateHeader (m_ccList);
+ m_headers.ResetWritePos();
+ m_envelope.ResetWritePos();
+ m_receivedTime = 0;
+ m_receivedValue.Truncate();
+ for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++)
+ m_customDBHeaderValues[i].length = 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetState(nsMailboxParseState aState)
+{
+ m_state = aState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::GetState(nsMailboxParseState *aState)
+{
+ if (!aState)
+ return NS_ERROR_NULL_POINTER;
+
+ *aState = m_state;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParseMailMessageState::GetEnvelopePos(uint64_t *aEnvelopePos)
+{
+ NS_ENSURE_ARG_POINTER(aEnvelopePos);
+
+ *aEnvelopePos = m_envelope_pos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetEnvelopePos(uint64_t aEnvelopePos)
+{
+ m_envelope_pos = aEnvelopePos;
+ m_position = m_envelope_pos;
+ m_headerstartpos = m_position;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::GetNewMsgHdr(nsIMsgDBHdr ** aMsgHeader)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHeader);
+ NS_IF_ADDREF(*aMsgHeader = m_newMsgHdr);
+ return m_newMsgHdr ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetNewMsgHdr(nsIMsgDBHdr *aMsgHeader)
+{
+ m_newMsgHdr = aMsgHeader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::ParseAFolderLine(const char *line, uint32_t lineLength)
+{
+ ParseFolderLine(line, lineLength);
+ return NS_OK;
+}
+
+nsresult nsParseMailMessageState::ParseFolderLine(const char *line, uint32_t lineLength)
+{
+ nsresult rv;
+
+ if (m_state == nsIMsgParseMailMsgState::ParseHeadersState)
+ {
+ if (EMPTY_MESSAGE_LINE(line))
+ {
+ /* End of headers. Now parse them. */
+ rv = ParseHeaders();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error parsing headers parsing mailbox");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = FinalizeHeaders();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error finalizing headers parsing mailbox");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_state = nsIMsgParseMailMsgState::ParseBodyState;
+ }
+ else
+ {
+ /* Otherwise, this line belongs to a header. So append it to the
+ header data, and stay in MBOX `MIME_PARSE_HEADERS' state.
+ */
+ m_headers.AppendBuffer(line, lineLength);
+ }
+ }
+ else if ( m_state == nsIMsgParseMailMsgState::ParseBodyState)
+ {
+ m_body_lines++;
+ // See comment in msgCore.h for why we use `IS_MSG_LINEBREAK` rather than
+ // just comparing `line` to `MSG_LINEBREAK`.
+ m_lastLineBlank = IS_MSG_LINEBREAK(line);
+ }
+
+ m_position += lineLength;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetMailDB(nsIMsgDatabase *mailDB)
+{
+ m_mailDB = mailDB;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetBackupMailDB(nsIMsgDatabase *aBackupMailDB)
+{
+ m_backupMailDB = aBackupMailDB;
+ if (m_backupMailDB)
+ m_backupMailDB->AddListener(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetNewKey(nsMsgKey aKey)
+{
+ m_new_key = aKey;
+ return NS_OK;
+}
+
+/* #define STRICT_ENVELOPE */
+
+bool
+nsParseMailMessageState::IsEnvelopeLine(const char *buf, int32_t buf_size)
+{
+#ifdef STRICT_ENVELOPE
+ /* The required format is
+ From jwz Fri Jul 1 09:13:09 1994
+ But we should also allow at least:
+ From jwz Fri, Jul 01 09:13:09 1994
+ From jwz Fri Jul 1 09:13:09 1994 PST
+ From jwz Fri Jul 1 09:13:09 1994 (+0700)
+
+ We can't easily call XP_ParseTimeString() because the string is not
+ null terminated (ok, we could copy it after a quick check...) but
+ XP_ParseTimeString() may be too lenient for our purposes.
+
+ DANGER!! The released version of 2.0b1 was (on some systems,
+ some Unix, some NT, possibly others) writing out envelope lines
+ like "From - 10/13/95 11:22:33" which STRICT_ENVELOPE will reject!
+ */
+ const char *date, *end;
+
+ if (buf_size < 29) return false;
+ if (*buf != 'F') return false;
+ if (strncmp(buf, "From ", 5)) return false;
+
+ end = buf + buf_size;
+ date = buf + 5;
+
+ /* Skip horizontal whitespace between "From " and user name. */
+ while ((*date == ' ' || *date == '\t') && date < end)
+ date++;
+
+ /* If at the end, it doesn't match. */
+ if (IS_SPACE(*date) || date == end)
+ return false;
+
+ /* Skip over user name. */
+ while (!IS_SPACE(*date) && date < end)
+ date++;
+
+ /* Skip horizontal whitespace between user name and date. */
+ while ((*date == ' ' || *date == '\t') && date < end)
+ date++;
+
+ /* Don't want this to be localized. */
+# define TMP_ISALPHA(x) (((x) >= 'A' && (x) <= 'Z') || \
+ ((x) >= 'a' && (x) <= 'z'))
+
+ /* take off day-of-the-week. */
+ if (date >= end - 3)
+ return false;
+ if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2]))
+ return false;
+ date += 3;
+ /* Skip horizontal whitespace (and commas) between dotw and month. */
+ if (*date != ' ' && *date != '\t' && *date != ',')
+ return false;
+ while ((*date == ' ' || *date == '\t' || *date == ',') && date < end)
+ date++;
+
+ /* take off month. */
+ if (date >= end - 3)
+ return false;
+ if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2]))
+ return false;
+ date += 3;
+ /* Skip horizontal whitespace between month and dotm. */
+ if (date == end || (*date != ' ' && *date != '\t'))
+ return false;
+ while ((*date == ' ' || *date == '\t') && date < end)
+ date++;
+
+ /* Skip over digits and whitespace. */
+ while (((*date >= '0' && *date <= '9') || *date == ' ' || *date == '\t') &&
+ date < end)
+ date++;
+ /* Next character should be a colon. */
+ if (date >= end || *date != ':')
+ return false;
+
+ /* Ok, that ought to be enough... */
+
+# undef TMP_ISALPHA
+
+#else /* !STRICT_ENVELOPE */
+
+ if (buf_size < 5) return false;
+ if (*buf != 'F') return false;
+ if (strncmp(buf, "From ", 5)) return false;
+
+#endif /* !STRICT_ENVELOPE */
+
+ return true;
+}
+
+// We've found the start of the next message, so finish this one off.
+NS_IMETHODIMP nsParseMailMessageState::FinishHeader()
+{
+ if (m_newMsgHdr)
+ {
+ m_newMsgHdr->SetMessageOffset(m_envelope_pos);
+ if (m_lastLineBlank)
+ m_body_lines--;
+ m_newMsgHdr->SetMessageSize(m_position - m_envelope_pos - m_lastLineBlank);
+ m_newMsgHdr->SetLineCount(m_body_lines);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::GetAllHeaders(char ** pHeaders, int32_t *pHeadersSize)
+{
+ if (!pHeaders || !pHeadersSize)
+ return NS_ERROR_NULL_POINTER;
+ *pHeaders = m_headers.GetBuffer();
+ *pHeadersSize = m_headers.GetBufferPos();
+ return NS_OK;
+}
+
+// generate headers as a string, with CRLF between the headers
+NS_IMETHODIMP nsParseMailMessageState::GetHeaders(char ** pHeaders)
+{
+ NS_ENSURE_ARG_POINTER(pHeaders);
+ nsCString crlfHeaders;
+ char *curHeader = m_headers.GetBuffer();
+ for (uint32_t headerPos = 0; headerPos < m_headers.GetBufferPos();)
+ {
+ crlfHeaders.Append(curHeader);
+ crlfHeaders.Append(CRLF);
+ int32_t headerLen = strlen(curHeader);
+ curHeader += headerLen + 1;
+ headerPos += headerLen + 1;
+ }
+ *pHeaders = ToNewCString(crlfHeaders);
+ return NS_OK;
+}
+
+struct message_header *nsParseMailMessageState::GetNextHeaderInAggregate (nsTArray<struct message_header*> &list)
+{
+ // When parsing a message with multiple To or CC header lines, we're storing each line in a
+ // list, where the list represents the "aggregate" total of all the header. Here we get a new
+ // line for the list
+
+ struct message_header *header = (struct message_header*) PR_Calloc (1, sizeof(struct message_header));
+ list.AppendElement (header);
+ return header;
+}
+
+void nsParseMailMessageState::GetAggregateHeader (nsTArray<struct message_header*> &list, struct message_header *outHeader)
+{
+ // When parsing a message with multiple To or CC header lines, we're storing each line in a
+ // list, where the list represents the "aggregate" total of all the header. Here we combine
+ // all the lines together, as though they were really all found on the same line
+
+ struct message_header *header = nullptr;
+ int length = 0;
+ size_t i;
+
+ // Count up the bytes required to allocate the aggregated header
+ for (i = 0; i < list.Length(); i++)
+ {
+ header = list.ElementAt(i);
+ length += (header->length + 1); //+ for ","
+ }
+
+ if (length > 0)
+ {
+ char *value = (char*) PR_CALLOC (length + 1); //+1 for null term
+ if (value)
+ {
+ // Catenate all the To lines together, separated by commas
+ value[0] = '\0';
+ size_t size = list.Length();
+ for (i = 0; i < size; i++)
+ {
+ header = list.ElementAt(i);
+ PL_strncat (value, header->value, header->length);
+ if (i + 1 < size)
+ PL_strcat (value, ",");
+ }
+ outHeader->length = length;
+ outHeader->value = value;
+ }
+ }
+ else
+ {
+ outHeader->length = 0;
+ outHeader->value = nullptr;
+ }
+}
+
+void nsParseMailMessageState::ClearAggregateHeader (nsTArray<struct message_header*> &list)
+{
+ // Reset the aggregate headers. Free only the message_header struct since
+ // we don't own the value pointer
+
+ for (size_t i = 0; i < list.Length(); i++)
+ PR_Free (list.ElementAt(i));
+ list.Clear();
+}
+
+// We've found a new envelope to parse.
+nsresult nsParseMailMessageState::StartNewEnvelope(const char *line, uint32_t lineLength)
+{
+ m_envelope_pos = m_position;
+ m_state = nsIMsgParseMailMsgState::ParseHeadersState;
+ m_position += lineLength;
+ m_headerstartpos = m_position;
+ return ParseEnvelope (line, lineLength);
+}
+
+/* largely lifted from mimehtml.c, which does similar parsing, sigh...
+*/
+nsresult nsParseMailMessageState::ParseHeaders ()
+{
+ char *buf = m_headers.GetBuffer();
+ uint32_t buf_length = m_headers.GetBufferPos();
+ if (buf_length == 0)
+ {
+ // No header of an expected type is present. Consider this a successful
+ // parse so email still shows on summary and can be accessed and deleted.
+ return NS_OK;
+ }
+ char *buf_end = buf + buf_length;
+ if (!(buf_length > 1 && (buf[buf_length - 1] == '\r' ||
+ buf[buf_length - 1] == '\n')))
+ {
+ NS_WARNING("Header text should always end in a newline");
+ return NS_ERROR_UNEXPECTED;
+ }
+ while (buf < buf_end)
+ {
+ char *colon = PL_strnchr(buf, ':', buf_end - buf);
+ char *end;
+ char *value = 0;
+ struct message_header *header = 0;
+ struct message_header receivedBy;
+
+ if (!colon)
+ break;
+
+ end = colon;
+
+ switch (buf [0])
+ {
+ case 'B': case 'b':
+ if (!PL_strncasecmp ("BCC", buf, end - buf))
+ header = &m_bccList;
+ break;
+ case 'C': case 'c':
+ if (!PL_strncasecmp ("CC", buf, end - buf))
+ header = GetNextHeaderInAggregate(m_ccList);
+ else if (!PL_strncasecmp ("Content-Type", buf, end - buf))
+ header = &m_content_type;
+ break;
+ case 'D': case 'd':
+ if (!PL_strncasecmp ("Date", buf, end - buf))
+ header = &m_date;
+ else if (!PL_strncasecmp("Disposition-Notification-To", buf, end - buf))
+ header = &m_mdn_dnt;
+ else if (!PL_strncasecmp("Delivery-date", buf, end - buf))
+ header = &m_delivery_date;
+ break;
+ case 'F': case 'f':
+ if (!PL_strncasecmp ("From", buf, end - buf))
+ header = &m_from;
+ break;
+ case 'I' : case 'i':
+ if (!PL_strncasecmp ("In-Reply-To", buf, end - buf))
+ header = &m_in_reply_to;
+ break;
+ case 'M': case 'm':
+ if (!PL_strncasecmp ("Message-ID", buf, end - buf))
+ header = &m_message_id;
+ break;
+ case 'N': case 'n':
+ if (!PL_strncasecmp ("Newsgroups", buf, end - buf))
+ header = &m_newsgroups;
+ break;
+ case 'O': case 'o':
+ if (!PL_strncasecmp ("Original-Recipient", buf, end - buf))
+ header = &m_mdn_original_recipient;
+ break;
+ case 'R': case 'r':
+ if (!PL_strncasecmp ("References", buf, end - buf))
+ header = &m_references;
+ else if (!PL_strncasecmp ("Return-Path", buf, end - buf))
+ header = &m_return_path;
+ // treat conventional Return-Receipt-To as MDN
+ // Disposition-Notification-To
+ else if (!PL_strncasecmp ("Return-Receipt-To", buf, end - buf))
+ header = &m_mdn_dnt;
+ else if (!PL_strncasecmp("Reply-To", buf, end - buf))
+ header = &m_replyTo;
+ else if (!PL_strncasecmp("Received", buf, end - buf))
+ {
+ header = &receivedBy;
+ header->length = 0;
+ }
+ break;
+ case 'S': case 's':
+ if (!PL_strncasecmp ("Subject", buf, end - buf) && !m_subject.length)
+ header = &m_subject;
+ else if (!PL_strncasecmp ("Sender", buf, end - buf))
+ header = &m_sender;
+ else if (!PL_strncasecmp ("Status", buf, end - buf))
+ header = &m_status;
+ break;
+ case 'T': case 't':
+ if (!PL_strncasecmp ("To", buf, end - buf))
+ header = GetNextHeaderInAggregate(m_toList);
+ break;
+ case 'X':
+ if (X_MOZILLA_STATUS2_LEN == end - buf &&
+ !PL_strncasecmp(X_MOZILLA_STATUS2, buf, end - buf) &&
+ !m_IgnoreXMozillaStatus && !m_mozstatus2.length)
+ header = &m_mozstatus2;
+ else if ( X_MOZILLA_STATUS_LEN == end - buf &&
+ !PL_strncasecmp(X_MOZILLA_STATUS, buf, end - buf) && !m_IgnoreXMozillaStatus
+ && !m_mozstatus.length)
+ header = &m_mozstatus;
+ else if (!PL_strncasecmp(HEADER_X_MOZILLA_ACCOUNT_KEY, buf, end - buf)
+ && !m_account_key.length)
+ header = &m_account_key;
+ // we could very well care what the priority header was when we
+ // remember its value. If so, need to remember it here. Also,
+ // different priority headers can appear in the same message,
+ // but we only rememeber the last one that we see.
+ else if (!PL_strncasecmp("X-Priority", buf, end - buf)
+ || !PL_strncasecmp("Priority", buf, end - buf))
+ header = &m_priority;
+ else if (!PL_strncasecmp(HEADER_X_MOZILLA_KEYWORDS, buf, end - buf)
+ && !m_keywords.length)
+ header = &m_keywords;
+ break;
+ }
+ if (!header && m_customDBHeaders.Length())
+ {
+#ifdef MOZILLA_INTERNAL_API
+ nsDependentCSubstring headerStr(buf, end);
+#else
+ nsDependentCSubstring headerStr(buf, end - buf);
+#endif
+
+ ToLowerCase(headerStr);
+ size_t customHeaderIndex = m_customDBHeaders.IndexOf(headerStr);
+ if (customHeaderIndex != m_customDBHeaders.NoIndex)
+ header = & m_customDBHeaderValues[customHeaderIndex];
+ }
+
+ buf = colon + 1;
+ uint32_t writeOffset = 0; // number of characters replaced with a folded space
+
+SEARCH_NEWLINE:
+ // move past any non terminating characters, rewriting them if folding white space
+ // exists
+ while (buf < buf_end && *buf != '\r' && *buf != '\n')
+ {
+ if (writeOffset)
+ *(buf - writeOffset) = *buf;
+ buf++;
+ }
+
+ /* If "\r\n " or "\r\n\t" is next, that doesn't terminate the header. */
+ if ((buf + 2 < buf_end && (buf[0] == '\r' && buf[1] == '\n') &&
+ (buf[2] == ' ' || buf[2] == '\t')) ||
+ /* If "\r " or "\r\t" or "\n " or "\n\t" is next, that doesn't terminate
+ the header either. */
+ (buf + 1 < buf_end && (buf[0] == '\r' || buf[0] == '\n') &&
+ (buf[1] == ' ' || buf[1] == '\t')))
+ {
+ // locate the proper location for a folded space by eliminating any
+ // leading spaces before the end-of-line character
+ char* foldedSpace = buf;
+ while (*(foldedSpace - 1) == ' ' || *(foldedSpace - 1) == '\t')
+ foldedSpace--;
+
+ // put a single folded space character
+ *(foldedSpace - writeOffset) = ' ';
+ writeOffset += (buf - foldedSpace);
+ buf++;
+
+ // eliminate any additional white space
+ while (buf < buf_end &&
+ (*buf == '\n' || *buf == '\r' || *buf == ' ' || *buf == '\t'))
+ {
+ buf++;
+ writeOffset++;
+ }
+
+ // If we get here, the message headers ended in an empty line, like:
+ // To: blah blah blah<CR><LF> <CR><LF>[end of buffer]. The code below
+ // requires buf to land on a newline to properly null-terminate the
+ // string, so back up a tad so that it is pointing to one.
+ if (buf == buf_end)
+ {
+ --buf;
+ MOZ_ASSERT(*buf == '\n' || *buf == '\r',
+ "Header text should always end in a newline.");
+ }
+ goto SEARCH_NEWLINE;
+ }
+
+ if (header)
+ {
+ value = colon + 1;
+ // eliminate trailing blanks after the colon
+ while (value < (buf - writeOffset) && (*value == ' ' || *value == '\t'))
+ value++;
+
+ header->value = value;
+ header->length = buf - header->value - writeOffset;
+ if (header->length < 0)
+ header->length = 0;
+ }
+ if (*buf == '\r' || *buf == '\n')
+ {
+ char *last = buf - writeOffset;
+ char *saveBuf = buf;
+ if (*buf == '\r' && buf + 1 < buf_end && buf[1] == '\n')
+ buf++;
+ buf++;
+ // null terminate the left-over slop so we don't confuse msg filters.
+ *saveBuf = 0;
+ *last = 0; /* short-circuit const, and null-terminate header. */
+ }
+
+ if (header)
+ {
+ /* More const short-circuitry... */
+ /* strip trailing whitespace */
+ while (header->length > 0 &&
+ IS_SPACE (header->value [header->length - 1]))
+ ((char *) header->value) [--header->length] = 0;
+ if (header == &receivedBy)
+ {
+ if (m_receivedTime == 0)
+ {
+ // parse Received: header for date.
+ // We trust the first header as that is closest to recipient,
+ // and less likely to be spoofed.
+ nsAutoCString receivedHdr(header->value, header->length);
+ int32_t lastSemicolon = receivedHdr.RFindChar(';');
+ if (lastSemicolon != -1)
+ {
+ nsAutoCString receivedDate;
+ receivedDate = Substring(receivedHdr, lastSemicolon + 1);
+ receivedDate.Trim(" \t\b\r\n");
+ PRTime resultTime;
+ if (PR_ParseTimeString (receivedDate.get(), false, &resultTime) == PR_SUCCESS)
+ m_receivedTime = resultTime;
+ else
+ NS_WARNING("PR_ParseTimeString failed in ParseHeaders().");
+ }
+ }
+ // Someone might want the received header saved.
+ if (m_customDBHeaders.Length())
+ {
+ if (m_customDBHeaders.Contains(NS_LITERAL_CSTRING("received")))
+ {
+ if (!m_receivedValue.IsEmpty())
+ m_receivedValue.Append(' ');
+ m_receivedValue.Append(header->value, header->length);
+ }
+ }
+ }
+
+ MOZ_ASSERT(header->value[header->length] == 0,
+ "Non-null-terminated strings cause very, very bad problems");
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsParseMailMessageState::ParseEnvelope (const char *line, uint32_t line_size)
+{
+ const char *end;
+ char *s;
+
+ m_envelope.AppendBuffer(line, line_size);
+ end = m_envelope.GetBuffer() + line_size;
+ s = m_envelope.GetBuffer() + 5;
+
+ while (s < end && IS_SPACE (*s))
+ s++;
+ m_envelope_from.value = s;
+ while (s < end && !IS_SPACE (*s))
+ s++;
+ m_envelope_from.length = s - m_envelope_from.value;
+
+ while (s < end && IS_SPACE (*s))
+ s++;
+ m_envelope_date.value = s;
+ m_envelope_date.length = (uint16_t) (line_size - (s - m_envelope.GetBuffer()));
+
+ while (m_envelope_date.length > 0 &&
+ IS_SPACE (m_envelope_date.value [m_envelope_date.length - 1]))
+ m_envelope_date.length--;
+
+ /* #### short-circuit const */
+ ((char *) m_envelope_from.value) [m_envelope_from.length] = 0;
+ ((char *) m_envelope_date.value) [m_envelope_date.length] = 0;
+
+ return NS_OK;
+}
+
+nsresult nsParseMailMessageState::InternSubject (struct message_header *header)
+{
+ if (!header || header->length == 0)
+ {
+ m_newMsgHdr->SetSubject("");
+ return NS_OK;
+ }
+
+ const char *key = header->value;
+
+ uint32_t flags;
+ (void)m_newMsgHdr->GetFlags(&flags);
+ /* strip "Re: " */
+ /**
+ We trust the X-Mozilla-Status line to be the smartest in almost
+ all things. One exception, however, is the HAS_RE flag. Since
+ we just parsed the subject header anyway, we expect that parsing
+ to be smartest. (After all, what if someone just went in and
+ edited the subject line by hand?)
+ */
+ nsCString modifiedSubject;
+ if (NS_MsgStripRE(nsDependentCString(key), modifiedSubject))
+ flags |= nsMsgMessageFlags::HasRe;
+ else
+ flags &= ~nsMsgMessageFlags::HasRe;
+ m_newMsgHdr->SetFlags(flags); // this *does not* update the mozilla-status header in the local folder
+
+ // Condense the subject text into as few MIME-2 encoded words as possible.
+ m_newMsgHdr->SetSubject(modifiedSubject.IsEmpty() ? key : modifiedSubject.get());
+
+ return NS_OK;
+}
+
+// we've reached the end of the envelope, and need to turn all our accumulated message_headers
+// into a single nsIMsgDBHdr to store in a database.
+nsresult nsParseMailMessageState::FinalizeHeaders()
+{
+ nsresult rv;
+ struct message_header *sender;
+ struct message_header *recipient;
+ struct message_header *subject;
+ struct message_header *id;
+ struct message_header *inReplyTo;
+ struct message_header *replyTo;
+ struct message_header *references;
+ struct message_header *date;
+ struct message_header *deliveryDate;
+ struct message_header *statush;
+ struct message_header *mozstatus;
+ struct message_header *mozstatus2;
+ struct message_header *priority;
+ struct message_header *keywords;
+ struct message_header *account_key;
+ struct message_header *ccList;
+ struct message_header *bccList;
+ struct message_header *mdn_dnt;
+ struct message_header md5_header;
+ struct message_header *content_type;
+ char md5_data [50];
+
+ uint32_t flags = 0;
+ uint32_t delta = 0;
+ nsMsgPriorityValue priorityFlags = nsMsgPriority::notSet;
+ uint32_t labelFlags = 0;
+
+ if (!m_mailDB) // if we don't have a valid db, skip the header.
+ return NS_OK;
+
+ struct message_header to;
+ GetAggregateHeader (m_toList, &to);
+ struct message_header cc;
+ GetAggregateHeader (m_ccList, &cc);
+ // we don't aggregate bcc, as we only generate it locally,
+ // and we don't use multiple lines
+
+ sender = (m_from.length ? &m_from :
+ m_sender.length ? &m_sender :
+ m_envelope_from.length ? &m_envelope_from :
+ 0);
+ recipient = (to.length ? &to :
+ cc.length ? &cc :
+ m_newsgroups.length ? &m_newsgroups :
+ 0);
+ ccList = (cc.length ? &cc : 0);
+ bccList = (m_bccList.length ? &m_bccList : 0);
+ subject = (m_subject.length ? &m_subject : 0);
+ id = (m_message_id.length ? &m_message_id : 0);
+ references = (m_references.length ? &m_references : 0);
+ statush = (m_status.length ? &m_status : 0);
+ mozstatus = (m_mozstatus.length ? &m_mozstatus : 0);
+ mozstatus2 = (m_mozstatus2.length ? &m_mozstatus2 : 0);
+ date = (m_date.length ? &m_date :
+ m_envelope_date.length ? &m_envelope_date :
+ 0);
+ deliveryDate = (m_delivery_date.length ? &m_delivery_date : 0);
+ priority = (m_priority.length ? &m_priority : 0);
+ keywords = (m_keywords.length ? &m_keywords : 0);
+ mdn_dnt = (m_mdn_dnt.length ? &m_mdn_dnt : 0);
+ inReplyTo = (m_in_reply_to.length ? &m_in_reply_to : 0);
+ replyTo = (m_replyTo.length ? &m_replyTo : 0);
+ content_type = (m_content_type.length ? &m_content_type : 0);
+ account_key = (m_account_key.length ? &m_account_key :0);
+
+ if (mozstatus)
+ {
+ if (mozstatus->length == 4)
+ {
+ NS_ASSERTION(MsgIsHex(mozstatus->value, 4), "Expected 4 hex digits for flags.");
+ flags = MsgUnhex(mozstatus->value, 4);
+ // strip off and remember priority bits.
+ flags &= ~nsMsgMessageFlags::RuntimeOnly;
+ priorityFlags = (nsMsgPriorityValue) ((flags & nsMsgMessageFlags::Priorities) >> 13);
+ flags &= ~nsMsgMessageFlags::Priorities;
+ }
+ delta = (m_headerstartpos +
+ (mozstatus->value - m_headers.GetBuffer()) -
+ (2 + X_MOZILLA_STATUS_LEN) /* 2 extra bytes for ": ". */
+ ) - m_envelope_pos;
+ }
+
+ if (mozstatus2)
+ {
+ uint32_t flags2 = 0;
+ sscanf(mozstatus2->value, " %x ", &flags2);
+ flags |= flags2;
+ }
+
+ if (!(flags & nsMsgMessageFlags::Expunged)) // message was deleted, don't bother creating a hdr.
+ {
+ // We'll need the message id first to recover data from the backup database
+ nsAutoCString rawMsgId;
+ /* Take off <> around message ID. */
+ if (id)
+ {
+ if (id->length > 0 && id->value[0] == '<')
+ id->length--, id->value++;
+
+ NS_WARNING_ASSERTION(id->length > 0, "id->length failure in FinalizeHeaders().");
+
+ if (id->length > 0 && id->value[id->length - 1] == '>')
+ /* generate a new null-terminated string without the final > */
+ rawMsgId.Assign(id->value, id->length - 1);
+ else
+ rawMsgId.Assign(id->value);
+ }
+
+ /*
+ * Try to copy the data from the backup database, referencing the MessageID
+ * If that fails, just create a new header
+ */
+ nsCOMPtr<nsIMsgDBHdr> oldHeader;
+ nsresult ret = NS_OK;
+
+ if (m_backupMailDB && !rawMsgId.IsEmpty())
+ ret = m_backupMailDB->GetMsgHdrForMessageID(
+ rawMsgId.get(), getter_AddRefs(oldHeader));
+
+ // m_new_key is set in nsImapMailFolder::ParseAdoptedHeaderLine to be
+ // the UID of the message, so that the key can get created as UID. That of
+ // course is extremely confusing, and we really need to clean that up. We
+ // really should not conflate the meaning of envelope position, key, and
+ // UID.
+ if (NS_SUCCEEDED(ret) && oldHeader)
+ ret = m_mailDB->CopyHdrFromExistingHdr(m_new_key,
+ oldHeader, false, getter_AddRefs(m_newMsgHdr));
+ else if (!m_newMsgHdr)
+ {
+ // Should assert that this is not a local message
+ ret = m_mailDB->CreateNewHdr(m_new_key, getter_AddRefs(m_newMsgHdr));
+ }
+
+ if (NS_SUCCEEDED(ret) && m_newMsgHdr)
+ {
+ uint32_t origFlags;
+ (void)m_newMsgHdr->GetFlags(&origFlags);
+ if (origFlags & nsMsgMessageFlags::HasRe)
+ flags |= nsMsgMessageFlags::HasRe;
+ else
+ flags &= ~nsMsgMessageFlags::HasRe;
+
+ flags &= ~nsMsgMessageFlags::Offline; // don't keep nsMsgMessageFlags::Offline for local msgs
+ if (mdn_dnt && !(origFlags & nsMsgMessageFlags::Read) &&
+ !(origFlags & nsMsgMessageFlags::MDNReportSent) &&
+ !(flags & nsMsgMessageFlags::MDNReportSent))
+ flags |= nsMsgMessageFlags::MDNReportNeeded;
+
+ m_newMsgHdr->SetFlags(flags);
+ if (priorityFlags != nsMsgPriority::notSet)
+ m_newMsgHdr->SetPriority(priorityFlags);
+
+ // if we have a reply to header, and it's different from the from: header,
+ // set the "replyTo" attribute on the msg hdr.
+ if (replyTo && (!sender || replyTo->length != sender->length || strncmp(replyTo->value, sender->value, sender->length)))
+ m_newMsgHdr->SetStringProperty("replyTo", replyTo->value);
+ // convert the flag values (0xE000000) to label values (0-5)
+ if (mozstatus2) // only do this if we have a mozstatus2 header
+ {
+ labelFlags = ((flags & nsMsgMessageFlags::Labels) >> 25);
+ m_newMsgHdr->SetLabel(labelFlags);
+ }
+ if (delta < 0xffff)
+ { /* Only use if fits in 16 bits. */
+ m_newMsgHdr->SetStatusOffset((uint16_t) delta);
+ if (!m_IgnoreXMozillaStatus) { // imap doesn't care about X-MozillaStatus
+ uint32_t offset;
+ (void)m_newMsgHdr->GetStatusOffset(&offset);
+ NS_ASSERTION(offset < 10000, "invalid status offset"); /* ### Debugging hack */
+ }
+ }
+ if (sender)
+ m_newMsgHdr->SetAuthor(sender->value);
+ if (recipient == &m_newsgroups)
+ {
+ /* In the case where the recipient is a newsgroup, truncate the string
+ at the first comma. This is used only for presenting the thread list,
+ and newsgroup lines tend to be long and non-shared, and tend to bloat
+ the string table. So, by only showing the first newsgroup, we can
+ reduce memory and file usage at the expense of only showing the one
+ group in the summary list, and only being able to sort on the first
+ group rather than the whole list. It's worth it. */
+ char * ch;
+ ch = PL_strchr(recipient->value, ',');
+ if (ch)
+ {
+ /* generate a new string that terminates before the , */
+ nsAutoCString firstGroup;
+ firstGroup.Assign(recipient->value, ch - recipient->value);
+ m_newMsgHdr->SetRecipients(firstGroup.get());
+ }
+ m_newMsgHdr->SetRecipients(recipient->value);
+ }
+ else if (recipient)
+ {
+ m_newMsgHdr->SetRecipients(recipient->value);
+ }
+ if (ccList)
+ {
+ m_newMsgHdr->SetCcList(ccList->value);
+ }
+
+ if (bccList)
+ {
+ m_newMsgHdr->SetBccList(bccList->value);
+ }
+
+ rv = InternSubject (subject);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (!id)
+ {
+ // what to do about this? we used to do a hash of all the headers...
+ nsAutoCString hash;
+ const char *md5_b64 = "dummy.message.id";
+ nsresult rv;
+ nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (NS_SUCCEEDED(hasher->Init(nsICryptoHash::MD5)) &&
+ NS_SUCCEEDED(hasher->Update((const uint8_t*) m_headers.GetBuffer(), m_headers.GetSize())) &&
+ NS_SUCCEEDED(hasher->Finish(true, hash)))
+ md5_b64 = hash.get();
+ }
+ PR_snprintf (md5_data, sizeof(md5_data), "<md5:%s>", md5_b64);
+ md5_header.value = md5_data;
+ md5_header.length = strlen(md5_data);
+ id = &md5_header;
+ }
+
+ if (!rawMsgId.IsEmpty())
+ m_newMsgHdr->SetMessageId(rawMsgId.get());
+ else
+ m_newMsgHdr->SetMessageId(id->value);
+ m_mailDB->UpdatePendingAttributes(m_newMsgHdr);
+
+ if (!mozstatus && statush)
+ {
+ /* Parse a little bit of the Berkeley Mail status header. */
+ for (const char *s = statush->value; *s; s++) {
+ uint32_t msgFlags = 0;
+ (void)m_newMsgHdr->GetFlags(&msgFlags);
+ switch (*s)
+ {
+ case 'R': case 'r':
+ m_newMsgHdr->SetFlags(msgFlags | nsMsgMessageFlags::Read);
+ break;
+ case 'D': case 'd':
+ /* msg->flags |= nsMsgMessageFlags::Expunged; ### Is this reasonable? */
+ break;
+ case 'N': case 'n':
+ case 'U': case 'u':
+ m_newMsgHdr->SetFlags(msgFlags & ~nsMsgMessageFlags::Read);
+ break;
+ default: // Should check for corrupt file.
+ NS_ERROR("Corrupt file. Should not happen.");
+ break;
+ }
+ }
+ }
+
+ if (account_key != nullptr)
+ m_newMsgHdr->SetAccountKey(account_key->value);
+ // use in-reply-to header as references, if there's no references header
+ if (references != nullptr)
+ m_newMsgHdr->SetReferences(references->value);
+ else if (inReplyTo != nullptr)
+ m_newMsgHdr->SetReferences(inReplyTo->value);
+
+ // 'Received' should be as reliable an indicator of the receipt
+ // date+time as possible, whilst always giving something *from
+ // the message*. It won't use PR_Now() under any circumstance.
+ // Therefore, the fall-thru order for 'Received' is:
+ // Received: -> Delivery-date: -> date
+ // 'Date' uses:
+ // date -> PR_Now()
+ //
+ // date is:
+ // Date: -> m_envelope_date
+
+ uint32_t rcvTimeSecs = 0;
+ if (date)
+ { // Date:
+ PRTime resultTime;
+ PRStatus timeStatus = PR_ParseTimeString (date->value, false, &resultTime);
+ if (PR_SUCCESS == timeStatus)
+ {
+ m_newMsgHdr->SetDate(resultTime);
+ PRTime2Seconds(resultTime, &rcvTimeSecs);
+ }
+ else
+ NS_WARNING("PR_ParseTimeString of date failed in FinalizeHeader().");
+ }
+ else
+ { // PR_Now()
+ // If there was some problem parsing the Date header *AND* we
+ // couldn't get a valid envelope date, use now as the time.
+ // PR_ParseTimeString won't touch resultTime unless it succeeds.
+ // This doesn't affect local (POP3) messages, because we use the envelope
+ // date if there's no Date: header, but it will affect IMAP msgs
+ // w/o a Date: hdr or Received: headers.
+ PRTime resultTime = PR_Now();
+ m_newMsgHdr->SetDate(resultTime);
+ }
+ if (m_receivedTime != 0)
+ { // Upgrade 'Received' to Received: ?
+ PRTime2Seconds(m_receivedTime, &rcvTimeSecs);
+ }
+ else if (deliveryDate)
+ { // Upgrade 'Received' to Delivery-date: ?
+ PRTime resultTime;
+ PRStatus timeStatus = PR_ParseTimeString (deliveryDate->value, false, &resultTime);
+ if (PR_SUCCESS == timeStatus)
+ PRTime2Seconds(resultTime, &rcvTimeSecs);
+ else // TODO/FIXME: We need to figure out what to do in this case!
+ NS_WARNING("PR_ParseTimeString of delivery date failed in FinalizeHeader().");
+ }
+ m_newMsgHdr->SetUint32Property("dateReceived", rcvTimeSecs);
+
+ if (priority)
+ m_newMsgHdr->SetPriorityString(priority->value);
+ else if (priorityFlags == nsMsgPriority::notSet)
+ m_newMsgHdr->SetPriority(nsMsgPriority::none);
+ if (keywords)
+ {
+ // When there are many keywords, some may not have been written
+ // to the message file, so add extra keywords from the backup
+ nsAutoCString oldKeywords;
+ m_newMsgHdr->GetStringProperty("keywords", getter_Copies(oldKeywords));
+ nsTArray<nsCString> newKeywordArray, oldKeywordArray;
+ ParseString(Substring(keywords->value, keywords->value + keywords->length), ' ', newKeywordArray);
+ ParseString(oldKeywords, ' ', oldKeywordArray);
+ for (uint32_t i = 0; i < oldKeywordArray.Length(); i++)
+ if (!newKeywordArray.Contains(oldKeywordArray[i]))
+ newKeywordArray.AppendElement(oldKeywordArray[i]);
+ nsAutoCString newKeywords;
+ for (uint32_t i = 0; i < newKeywordArray.Length(); i++)
+ {
+ if (i)
+ newKeywords.Append(" ");
+ newKeywords.Append(newKeywordArray[i]);
+ }
+ m_newMsgHdr->SetStringProperty("keywords", newKeywords.get());
+ }
+ for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++)
+ {
+ if (m_customDBHeaderValues[i].length)
+ m_newMsgHdr->SetStringProperty(m_customDBHeaders[i].get(), m_customDBHeaderValues[i].value);
+ // The received header is accumulated separately
+ if (m_customDBHeaders[i].EqualsLiteral("received") && !m_receivedValue.IsEmpty())
+ m_newMsgHdr->SetStringProperty("received", m_receivedValue.get());
+ }
+ if (content_type)
+ {
+ char *substring = PL_strstr(content_type->value, "charset");
+ if (substring)
+ {
+ char *charset = PL_strchr (substring, '=');
+ if (charset)
+ {
+ charset++;
+ /* strip leading whitespace and double-quote */
+ while (*charset && (IS_SPACE (*charset) || '\"' == *charset))
+ charset++;
+ /* strip trailing whitespace and double-quote */
+ char *end = charset;
+ while (*end && !IS_SPACE (*end) && '\"' != *end && ';' != *end)
+ end++;
+ if (*charset)
+ {
+ if (*end != '\0') {
+ // if we're not at the very end of the line, we need
+ // to generate a new string without the trailing crud
+ nsAutoCString rawCharSet;
+ rawCharSet.Assign(charset, end - charset);
+ m_newMsgHdr->SetCharset(rawCharSet.get());
+ } else {
+ m_newMsgHdr->SetCharset(charset);
+ }
+ }
+ }
+ }
+ substring = PL_strcasestr(content_type->value, "multipart/mixed");
+ if (substring)
+ {
+ uint32_t newFlags;
+ m_newMsgHdr->OrFlags(nsMsgMessageFlags::Attachment, &newFlags);
+ }
+ }
+ }
+ }
+ else
+ {
+ NS_ASSERTION(false, "error creating message header");
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ else
+ rv = NS_OK;
+
+ //### why is this stuff const?
+ char *tmp = (char*) to.value;
+ PR_Free(tmp);
+ tmp = (char*) cc.value;
+ PR_Free(tmp);
+
+ return rv;
+}
+
+nsParseNewMailState::nsParseNewMailState()
+ : m_disableFilters(false)
+{
+ m_ibuffer = nullptr;
+ m_ibuffer_size = 0;
+ m_ibuffer_fp = 0;
+ m_numNotNewMessages = 0;
+ }
+
+NS_IMPL_ISUPPORTS_INHERITED(nsParseNewMailState, nsMsgMailboxParser, nsIMsgFilterHitNotify)
+
+nsresult
+nsParseNewMailState::Init(nsIMsgFolder *serverFolder, nsIMsgFolder *downloadFolder,
+ nsIMsgWindow *aMsgWindow, nsIMsgDBHdr *aHdr,
+ nsIOutputStream *aOutputStream)
+{
+ nsresult rv;
+ Clear();
+ m_rootFolder = serverFolder;
+ m_msgWindow = aMsgWindow;
+ m_downloadFolder = downloadFolder;
+
+ m_newMsgHdr = aHdr;
+ m_outputStream = aOutputStream;
+ // the new mail parser isn't going to get the stream input, it seems, so we can't use
+ // the OnStartRequest mechanism the mailbox parser uses. So, let's open the db right now.
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ if (msgDBService && !m_mailDB)
+ rv = msgDBService->OpenFolderDB(downloadFolder, false,
+ getter_AddRefs(m_mailDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgFolder> rootMsgFolder = do_QueryInterface(serverFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = rootMsgFolder->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = server->GetFilterList(aMsgWindow, getter_AddRefs(m_filterList));
+
+ if (m_filterList)
+ rv = server->ConfigureTemporaryFilters(m_filterList);
+ // check if this server defers to another server, in which case
+ // we'll use that server's filters as well.
+ nsCOMPtr <nsIMsgFolder> deferredToRootFolder;
+ server->GetRootMsgFolder(getter_AddRefs(deferredToRootFolder));
+ if (rootMsgFolder != deferredToRootFolder)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> deferredToServer;
+ deferredToRootFolder->GetServer(getter_AddRefs(deferredToServer));
+ if (deferredToServer)
+ deferredToServer->GetFilterList(aMsgWindow, getter_AddRefs(m_deferredToServerFilterList));
+ }
+ }
+ m_disableFilters = false;
+ return NS_OK;
+}
+
+nsParseNewMailState::~nsParseNewMailState()
+{
+ if (m_mailDB)
+ m_mailDB->Close(true);
+ if (m_backupMailDB)
+ m_backupMailDB->ForceClosed();
+#ifdef DOING_JSFILTERS
+ JSFilter_cleanup();
+#endif
+}
+
+// not an IMETHOD so we don't need to do error checking or return an error.
+// We only have one caller.
+void nsParseNewMailState::GetMsgWindow(nsIMsgWindow **aMsgWindow)
+{
+ NS_IF_ADDREF(*aMsgWindow = m_msgWindow);
+}
+
+
+// This gets called for every message because libnet calls IncorporateBegin,
+// IncorporateWrite (once or more), and IncorporateComplete for every message.
+void nsParseNewMailState::DoneParsingFolder(nsresult status)
+{
+ /* End of file. Flush out any partial line remaining in the buffer. */
+ if (m_ibuffer_fp > 0)
+ {
+ ParseFolderLine(m_ibuffer, m_ibuffer_fp);
+ m_ibuffer_fp = 0;
+ }
+ PublishMsgHeader(nullptr);
+ if (m_mailDB) // finished parsing, so flush db folder info
+ UpdateDBFolderInfo();
+
+ /* We're done reading the folder - we don't need these things
+ any more. */
+ PR_FREEIF (m_ibuffer);
+ m_ibuffer_size = 0;
+ PR_FREEIF (m_obuffer);
+ m_obuffer_size = 0;
+}
+
+void nsParseNewMailState::OnNewMessage(nsIMsgWindow *msgWindow)
+{
+}
+
+int32_t nsParseNewMailState::PublishMsgHeader(nsIMsgWindow *msgWindow)
+{
+ bool moved = false;
+ FinishHeader();
+
+ if (m_newMsgHdr)
+ {
+ uint32_t newFlags, oldFlags;
+ m_newMsgHdr->GetFlags(&oldFlags);
+ if (!(oldFlags & nsMsgMessageFlags::Read)) // don't mark read messages as new.
+ m_newMsgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
+
+ if (!m_disableFilters)
+ {
+ uint64_t msgOffset;
+ (void) m_newMsgHdr->GetMessageOffset(&msgOffset);
+ m_curHdrOffset = msgOffset;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_rootFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, 0);
+ int32_t duplicateAction;
+ server->GetIncomingDuplicateAction(&duplicateAction);
+ if (duplicateAction != nsIMsgIncomingServer::keepDups)
+ {
+ bool isDup;
+ server->IsNewHdrDuplicate(m_newMsgHdr, &isDup);
+ if (isDup)
+ {
+ // we want to do something similar to applying filter hits.
+ // if a dup is marked read, it shouldn't trigger biff.
+ // Same for deleting it or moving it to trash.
+ switch (duplicateAction)
+ {
+ case nsIMsgIncomingServer::deleteDups:
+ {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv =
+ m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = msgStore->DiscardNewMessage(m_outputStream, m_newMsgHdr);
+ if (NS_FAILED(rv))
+ m_rootFolder->ThrowAlertMsg("dupDeleteFolderTruncateFailed", msgWindow);
+ }
+ m_mailDB->RemoveHeaderMdbRow(m_newMsgHdr);
+ }
+ break;
+
+ case nsIMsgIncomingServer::moveDupsToTrash:
+ {
+ nsCOMPtr <nsIMsgFolder> trash;
+ GetTrashFolder(getter_AddRefs(trash));
+ if (trash) {
+ uint32_t newFlags;
+ bool msgMoved;
+ m_newMsgHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_SUCCEEDED(rv))
+ rv = msgStore->MoveNewlyDownloadedMessage(m_newMsgHdr, trash, &msgMoved);
+ if (NS_SUCCEEDED(rv) && !msgMoved) {
+ rv = MoveIncorporatedMessage(m_newMsgHdr, m_mailDB, trash,
+ nullptr, msgWindow);
+ if (NS_SUCCEEDED(rv))
+ rv = m_mailDB->RemoveHeaderMdbRow(m_newMsgHdr);
+ }
+ if (NS_FAILED(rv))
+ NS_WARNING("moveDupsToTrash failed for some reason.");
+ }
+ }
+ break;
+ case nsIMsgIncomingServer::markDupsRead:
+ MarkFilteredMessageRead(m_newMsgHdr);
+ break;
+ }
+ int32_t numNewMessages;
+ m_downloadFolder->GetNumNewMessages(false, &numNewMessages);
+ m_downloadFolder->SetNumNewMessages(numNewMessages - 1);
+
+ m_newMsgHdr = nullptr;
+ return 0;
+ }
+ }
+
+ ApplyFilters(&moved, msgWindow, msgOffset);
+ }
+ if (!moved)
+ {
+ if (m_mailDB)
+ {
+ m_mailDB->AddNewHdrToDB(m_newMsgHdr, true);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgAdded(m_newMsgHdr);
+ // mark the header as not yet reported classified
+ nsMsgKey msgKey;
+ m_newMsgHdr->GetMessageKey(&msgKey);
+ m_downloadFolder->OrProcessingFlags(
+ msgKey, nsMsgProcessingFlags::NotReportedClassified);
+ }
+ } // if it was moved by imap filter, m_parseMsgState->m_newMsgHdr == nullptr
+ m_newMsgHdr = nullptr;
+ }
+ return 0;
+}
+
+// We've found the start of the next message, so finish this one off.
+NS_IMETHODIMP nsParseNewMailState::FinishHeader()
+{
+ if (m_newMsgHdr)
+ {
+ if (m_lastLineBlank)
+ m_body_lines--;
+ m_newMsgHdr->SetMessageSize(m_position - m_envelope_pos - m_lastLineBlank);
+ m_newMsgHdr->SetLineCount(m_body_lines);
+ }
+ return NS_OK;
+}
+
+nsresult nsParseNewMailState::GetTrashFolder(nsIMsgFolder **pTrashFolder)
+{
+ nsresult rv=NS_ERROR_UNEXPECTED;
+ if (!pTrashFolder)
+ return NS_ERROR_NULL_POINTER;
+
+ if (m_downloadFolder)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ m_downloadFolder->GetServer(getter_AddRefs(incomingServer));
+ nsCOMPtr <nsIMsgFolder> rootMsgFolder;
+ incomingServer->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ if (rootMsgFolder)
+ {
+ rv = rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, pTrashFolder);
+ if (!*pTrashFolder)
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ return rv;
+}
+
+void nsParseNewMailState::ApplyFilters(bool *pMoved, nsIMsgWindow *msgWindow, uint64_t msgOffset)
+{
+ m_msgMovedByFilter = m_msgCopiedByFilter = false;
+ m_curHdrOffset = msgOffset;
+
+ if (!m_disableFilters)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr;
+ nsCOMPtr<nsIMsgFolder> downloadFolder = m_downloadFolder;
+ nsCOMPtr <nsIMsgFolder> rootMsgFolder = do_QueryInterface(m_rootFolder);
+ if (rootMsgFolder)
+ {
+ if (!downloadFolder)
+ rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(downloadFolder));
+ if (downloadFolder)
+ downloadFolder->GetURI(m_inboxUri);
+ char * headers = m_headers.GetBuffer();
+ uint32_t headersSize = m_headers.GetBufferPos();
+ if (m_filterList)
+ (void) m_filterList->
+ ApplyFiltersToHdr(nsMsgFilterType::InboxRule, msgHdr, downloadFolder,
+ m_mailDB, headers, headersSize, this, msgWindow);
+ if (!m_msgMovedByFilter && m_deferredToServerFilterList)
+ {
+ (void) m_deferredToServerFilterList->
+ ApplyFiltersToHdr(nsMsgFilterType::InboxRule, msgHdr, downloadFolder,
+ m_mailDB, headers, headersSize, this, msgWindow);
+ }
+ }
+ }
+ if (pMoved)
+ *pMoved = m_msgMovedByFilter;
+}
+
+NS_IMETHODIMP nsParseNewMailState::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindow *msgWindow, bool *applyMore)
+{
+ NS_ENSURE_ARG_POINTER(filter);
+ NS_ENSURE_ARG_POINTER(applyMore);
+
+ uint32_t newFlags;
+ nsresult rv = NS_OK;
+
+ *applyMore = true;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr;
+
+ nsCOMPtr<nsIArray> filterActionList;
+
+ rv = filter->GetSortedActionList(getter_AddRefs(filterActionList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numActions;
+ rv = filterActionList->GetLength(&numActions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool loggingEnabled = false;
+ if (m_filterList && numActions)
+ m_filterList->GetLoggingEnabled(&loggingEnabled);
+
+ bool msgIsNew = true;
+ for (uint32_t actionIndex = 0; actionIndex < numActions && *applyMore; actionIndex++)
+ {
+ nsCOMPtr<nsIMsgRuleAction> filterAction;
+ rv = filterActionList->QueryElementAt(actionIndex, NS_GET_IID(nsIMsgRuleAction),
+ getter_AddRefs(filterAction));
+ if (NS_FAILED(rv) || !filterAction)
+ continue;
+
+ nsMsgRuleActionType actionType;
+ if (NS_SUCCEEDED(filterAction->GetType(&actionType)))
+ {
+ if (loggingEnabled)
+ (void)filter->LogRuleHit(filterAction, msgHdr);
+
+ nsCString actionTargetFolderUri;
+ if (actionType == nsMsgFilterAction::MoveToFolder ||
+ actionType == nsMsgFilterAction::CopyToFolder)
+ {
+
+ rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
+ if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty())
+ {
+ NS_ASSERTION(false, "actionTargetFolderUri is empty");
+ continue;
+ }
+ }
+ switch (actionType)
+ {
+ case nsMsgFilterAction::Delete:
+ {
+ nsCOMPtr <nsIMsgFolder> trash;
+ // set value to trash folder
+ rv = GetTrashFolder(getter_AddRefs(trash));
+ if (NS_SUCCEEDED(rv) && trash)
+ rv = trash->GetURI(actionTargetFolderUri);
+
+ msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags); // mark read in trash.
+ msgIsNew = false;
+ }
+
+ // FALLTHROUGH
+ MOZ_FALLTHROUGH;
+ case nsMsgFilterAction::MoveToFolder:
+ // if moving to a different file, do it.
+ if (actionTargetFolderUri.get() && !m_inboxUri.Equals(actionTargetFolderUri,
+ nsCaseInsensitiveCStringComparator()))
+ {
+ nsresult err;
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &err));
+ NS_ENSURE_SUCCESS(err, err);
+ nsCOMPtr<nsIRDFResource> res;
+ err = rdf->GetResource(actionTargetFolderUri, getter_AddRefs(res));
+ if (NS_FAILED(err))
+ return err;
+
+ nsCOMPtr<nsIMsgFolder> destIFolder(do_QueryInterface(res, &err));
+ if (NS_FAILED(err))
+ return err;
+ bool msgMoved = false;
+ // If we're moving to an imap folder, or this message has already
+ // has a pending copy action, use the imap coalescer so that
+ // we won't truncate the inbox before the copy fires.
+ if (m_msgCopiedByFilter ||
+ StringBeginsWith(actionTargetFolderUri, NS_LITERAL_CSTRING("imap:")))
+ {
+ if (!m_moveCoalescer)
+ m_moveCoalescer = new nsImapMoveCoalescer(m_downloadFolder, m_msgWindow);
+ NS_ENSURE_TRUE(m_moveCoalescer, NS_ERROR_OUT_OF_MEMORY);
+ nsMsgKey msgKey;
+ (void) msgHdr->GetMessageKey(&msgKey);
+ m_moveCoalescer->AddMove(destIFolder, msgKey);
+ err = NS_OK;
+ msgIsNew = false;
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ err = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_SUCCEEDED(err))
+ err = msgStore->MoveNewlyDownloadedMessage(msgHdr, destIFolder, &msgMoved);
+ if (NS_SUCCEEDED(err) && !msgMoved)
+ err = MoveIncorporatedMessage(msgHdr, m_mailDB, destIFolder,
+ filter, msgWindow);
+ m_msgMovedByFilter = NS_SUCCEEDED(err);
+ if (!m_msgMovedByFilter /* == NS_FAILED(err) */)
+ {
+ // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands.
+ if (loggingEnabled)
+ (void) filter->LogRuleHitFail(filterAction, msgHdr, err, "Move failed");
+ }
+ }
+ }
+ *applyMore = false;
+ break;
+
+ case nsMsgFilterAction::CopyToFolder:
+ {
+ nsCString uri;
+ rv = m_rootFolder->GetURI(uri);
+
+ if (!actionTargetFolderUri.IsEmpty() && !actionTargetFolderUri.Equals(uri))
+ {
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ messageArray->AppendElement(msgHdr, false);
+
+ nsCOMPtr<nsIMsgFolder> dstFolder;
+ nsCOMPtr<nsIMsgCopyService> copyService;
+ rv = GetExistingFolder(actionTargetFolderUri,
+ getter_AddRefs(dstFolder));
+ if (NS_SUCCEEDED(rv)) {
+ copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ }
+ else {
+ // Let's show a more specific warning.
+ NS_WARNING("Target Folder does not exist.");
+ return rv;
+ }
+ if (NS_SUCCEEDED(rv))
+ rv = copyService->CopyMessages(m_downloadFolder, messageArray, dstFolder,
+ false, nullptr, msgWindow, false);
+
+ if (NS_FAILED(rv)) {
+ // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands.
+ if (loggingEnabled)
+ (void) filter->LogRuleHitFail(filterAction, msgHdr, rv, "Copy failed");
+ }
+ else
+ m_msgCopiedByFilter = true;
+ }
+ }
+ break;
+ case nsMsgFilterAction::MarkRead:
+ msgIsNew = false;
+ MarkFilteredMessageRead(msgHdr);
+ break;
+ case nsMsgFilterAction::MarkUnread:
+ msgIsNew = true;
+ MarkFilteredMessageUnread(msgHdr);
+ break;
+ case nsMsgFilterAction::KillThread:
+ msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored);
+ break;
+ case nsMsgFilterAction::KillSubthread:
+ msgHdr->OrFlags(nsMsgMessageFlags::Ignored, &newFlags);
+ break;
+ case nsMsgFilterAction::WatchThread:
+ msgHdr->OrFlags(nsMsgMessageFlags::Watched, &newFlags);
+ break;
+ case nsMsgFilterAction::MarkFlagged:
+ {
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ messageArray->AppendElement(msgHdr, false);
+ m_downloadFolder->MarkMessagesFlagged(messageArray, true);
+ }
+ break;
+ case nsMsgFilterAction::ChangePriority:
+ nsMsgPriorityValue filterPriority;
+ filterAction->GetPriority(&filterPriority);
+ msgHdr->SetPriority(filterPriority);
+ break;
+ case nsMsgFilterAction::AddTag:
+ {
+ nsCString keyword;
+ filterAction->GetStrValue(keyword);
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ messageArray->AppendElement(msgHdr, false);
+ m_downloadFolder->AddKeywordsToMessages(messageArray, keyword);
+ break;
+ }
+ case nsMsgFilterAction::Label:
+ nsMsgLabelValue filterLabel;
+ filterAction->GetLabel(&filterLabel);
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ m_mailDB->SetLabel(msgKey, filterLabel);
+ break;
+ case nsMsgFilterAction::JunkScore:
+ {
+ nsAutoCString junkScoreStr;
+ int32_t junkScore;
+ filterAction->GetJunkScore(&junkScore);
+ junkScoreStr.AppendInt(junkScore);
+ if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE)
+ msgIsNew = false;
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->SetStringProperty("junkscore", junkScoreStr.get());
+ msgHdr->SetStringProperty("junkscoreorigin", "filter");
+ break;
+ }
+ case nsMsgFilterAction::Forward:
+ {
+ nsCString forwardTo;
+ filterAction->GetStrValue(forwardTo);
+ m_forwardTo.AppendElement(forwardTo);
+ m_msgToForwardOrReply = msgHdr;
+ }
+ break;
+ case nsMsgFilterAction::Reply:
+ {
+ nsCString replyTemplateUri;
+ filterAction->GetStrValue(replyTemplateUri);
+ m_replyTemplateUri.AppendElement(replyTemplateUri);
+ m_msgToForwardOrReply = msgHdr;
+ m_ruleAction = filterAction;
+ m_filter = filter;
+ }
+ break;
+ case nsMsgFilterAction::DeleteFromPop3Server:
+ {
+ uint32_t flags = 0;
+ nsCOMPtr <nsIMsgFolder> downloadFolder;
+ msgHdr->GetFolder(getter_AddRefs(downloadFolder));
+ nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(downloadFolder);
+ msgHdr->GetFlags(&flags);
+ if (localFolder)
+ {
+ nsCOMPtr<nsIMutableArray> messages = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ messages->AppendElement(msgHdr, false);
+ // This action ignores the deleteMailLeftOnServer preference
+ localFolder->MarkMsgsOnPop3Server(messages, POP3_FORCE_DEL);
+
+ // If this is just a header, throw it away. It's useless now
+ // that the server copy is being deleted.
+ if (flags & nsMsgMessageFlags::Partial)
+ {
+ m_msgMovedByFilter = true;
+ msgIsNew = false;
+ }
+ }
+ }
+ break;
+ case nsMsgFilterAction::FetchBodyFromPop3Server:
+ {
+ uint32_t flags = 0;
+ nsCOMPtr <nsIMsgFolder> downloadFolder;
+ msgHdr->GetFolder(getter_AddRefs(downloadFolder));
+ nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(downloadFolder);
+ msgHdr->GetFlags(&flags);
+ if (localFolder && (flags & nsMsgMessageFlags::Partial))
+ {
+ nsCOMPtr<nsIMutableArray> messages = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ messages->AppendElement(msgHdr, false);
+ localFolder->MarkMsgsOnPop3Server(messages, POP3_FETCH_BODY);
+ // Don't add this header to the DB, we're going to replace it
+ // with the full message.
+ m_msgMovedByFilter = true;
+ msgIsNew = false;
+ // Don't do anything else in this filter, wait until we
+ // have the full message.
+ *applyMore = false;
+ }
+ }
+ break;
+
+ case nsMsgFilterAction::StopExecution:
+ {
+ // don't apply any more filters
+ *applyMore = false;
+ }
+ break;
+
+ case nsMsgFilterAction::Custom:
+ {
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString value;
+ filterAction->GetStrValue(value);
+
+ nsCOMPtr<nsIMutableArray> messageArray(
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(messageArray, rv);
+ if (NS_SUCCEEDED(rv))
+ rv = messageArray->AppendElement(msgHdr, false);
+
+
+ if (NS_SUCCEEDED(rv))
+ rv = customAction->Apply(messageArray, value, nullptr,
+ nsMsgFilterType::InboxRule, msgWindow);
+ if (NS_FAILED(rv)) {
+ // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands.
+ if (loggingEnabled)
+ (void) filter->LogRuleHitFail(filterAction, msgHdr, rv, "Copy failed");
+ }
+ }
+ break;
+
+
+ default:
+ // XXX should not be reached. Check in debug build.
+ NS_ERROR("This default should not be reached.");
+ break;
+ }
+ }
+ }
+ if (!msgIsNew)
+ {
+ int32_t numNewMessages;
+ m_downloadFolder->GetNumNewMessages(false, &numNewMessages);
+ if (numNewMessages > 0)
+ m_downloadFolder->SetNumNewMessages(numNewMessages - 1);
+ m_numNotNewMessages++;
+ }
+ return rv;
+}
+
+// this gets run in a second pass, after apply filters to a header.
+nsresult nsParseNewMailState::ApplyForwardAndReplyFilter(nsIMsgWindow *msgWindow)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgIncomingServer> server;
+
+ uint32_t i;
+ uint32_t count = m_forwardTo.Length();
+ for (i = 0; i < count; i++)
+ {
+ if (!m_forwardTo[i].IsEmpty())
+ {
+ nsAutoString forwardStr;
+ CopyASCIItoUTF16(m_forwardTo[i], forwardStr);
+ rv = m_rootFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ {
+ nsCOMPtr<nsIMsgComposeService> compService =
+ do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = compService->ForwardMessage(forwardStr, m_msgToForwardOrReply,
+ msgWindow, server,
+ nsIMsgComposeService::kForwardAsDefault);
+ }
+ }
+ }
+ m_forwardTo.Clear();
+
+ count = m_replyTemplateUri.Length();
+ for (i = 0; i < count; i++)
+ {
+ if (!m_replyTemplateUri[i].IsEmpty())
+ {
+ // copy this and truncate the original, so we don't accidentally re-use it on the next hdr.
+ rv = m_rootFolder->GetServer(getter_AddRefs(server));
+ if (server)
+ {
+ nsCOMPtr <nsIMsgComposeService> compService = do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID) ;
+ if (compService) {
+ rv = compService->ReplyWithTemplate(m_msgToForwardOrReply,
+ m_replyTemplateUri[i].get(),
+ msgWindow, server);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("ReplyWithTemplate failed");
+ if (rv == NS_ERROR_ABORT) {
+ m_filter->LogRuleHitFail(m_ruleAction, m_msgToForwardOrReply, rv,
+ "Sending reply aborted");
+ } else {
+ m_filter->LogRuleHitFail(m_ruleAction, m_msgToForwardOrReply, rv,
+ "Error sending reply");
+ }
+ }
+ }
+ }
+ }
+ }
+ m_replyTemplateUri.Clear();
+ m_msgToForwardOrReply = nullptr;
+ return rv;
+}
+
+void nsParseNewMailState::MarkFilteredMessageRead(nsIMsgDBHdr *msgHdr)
+{
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ messageArray->AppendElement(msgHdr, false);
+ m_downloadFolder->MarkMessagesRead(messageArray, true);
+}
+
+void nsParseNewMailState::MarkFilteredMessageUnread(nsIMsgDBHdr *msgHdr)
+{
+ uint32_t newFlags;
+ if (m_mailDB)
+ {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ m_mailDB->AddToNewList(msgKey);
+ }
+ else
+ {
+ msgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
+ }
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ messageArray->AppendElement(msgHdr, false);
+ m_downloadFolder->MarkMessagesRead(messageArray, false);
+}
+
+nsresult nsParseNewMailState::EndMsgDownload()
+{
+ if (m_moveCoalescer)
+ m_moveCoalescer->PlaybackMoves();
+
+ // need to do this for all folders that had messages filtered into them
+ uint32_t serverCount = m_filterTargetFolders.Count();
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && session) // don't use NS_ENSURE_SUCCESS here - we need to release semaphore below
+ {
+ for (uint32_t index = 0; index < serverCount; index++)
+ {
+ bool folderOpen;
+ session->IsFolderOpenInWindow(m_filterTargetFolders[index], &folderOpen);
+ if (!folderOpen)
+ {
+ uint32_t folderFlags;
+ m_filterTargetFolders[index]->GetFlags(&folderFlags);
+ if (! (folderFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
+ {
+ bool filtersRun;
+ m_filterTargetFolders[index]->CallFilterPlugins(nullptr, &filtersRun);
+ if (!filtersRun)
+ m_filterTargetFolders[index]->SetMsgDatabase(nullptr);
+ }
+ }
+ }
+ }
+ m_filterTargetFolders.Clear();
+ return rv;
+}
+
+nsresult nsParseNewMailState::AppendMsgFromStream(nsIInputStream *fileStream,
+ nsIMsgDBHdr *aHdr,
+ uint32_t length,
+ nsIMsgFolder *destFolder)
+{
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(fileStream);
+ nsCOMPtr<nsIMsgPluggableStore> store;
+ nsCOMPtr<nsIOutputStream> destOutputStream;
+ nsresult rv = destFolder->GetMsgStore(getter_AddRefs(store));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool reusable;
+ rv = store->GetNewMsgOutputStream(destFolder, &aHdr, &reusable,
+ getter_AddRefs(destOutputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_ibuffer)
+ {
+ m_ibuffer_size = FILE_IO_BUFFER_SIZE;
+ m_ibuffer = (char *) PR_Malloc(m_ibuffer_size);
+ NS_ASSERTION(m_ibuffer != nullptr, "couldn't get memory to move msg");
+ }
+ m_ibuffer_fp = 0;
+
+ while (length > 0 && m_ibuffer)
+ {
+ uint32_t nRead;
+ fileStream->Read (m_ibuffer, length > m_ibuffer_size ? m_ibuffer_size : length, &nRead);
+ if (nRead == 0)
+ break;
+
+ uint32_t bytesWritten;
+ // Check the number of bytes actually written to the stream.
+ destOutputStream->Write(m_ibuffer, nRead, &bytesWritten);
+ if (bytesWritten != nRead)
+ {
+ destOutputStream->Close();
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+
+ length -= nRead;
+ }
+
+ NS_ASSERTION(length == 0, "didn't read all of original message in filter move");
+
+ // non-reusable streams will get closed by the store.
+ if (reusable)
+ destOutputStream->Close();
+ return store->FinishNewMessage(destOutputStream, aHdr);
+}
+
+/*
+ * Moves message pointed to by mailHdr into folder destIFolder.
+ * After successful move mailHdr is no longer usable by the caller.
+ */
+nsresult nsParseNewMailState::MoveIncorporatedMessage(nsIMsgDBHdr *mailHdr,
+ nsIMsgDatabase *sourceDB,
+ nsIMsgFolder *destIFolder,
+ nsIMsgFilter *filter,
+ nsIMsgWindow *msgWindow)
+{
+ NS_ENSURE_ARG_POINTER(destIFolder);
+ nsresult rv = NS_OK;
+
+ // check if the destination is a real folder (by checking for null parent)
+ // and if it can file messages (e.g., servers or news folders can't file messages).
+ // Or read only imap folders...
+ bool canFileMessages = true;
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ destIFolder->GetParent(getter_AddRefs(parentFolder));
+ if (parentFolder)
+ destIFolder->GetCanFileMessages(&canFileMessages);
+ if (!parentFolder || !canFileMessages)
+ {
+ if (filter)
+ {
+ filter->SetEnabled(false);
+ // we need to explicitly save the filter file.
+ if (m_filterList)
+ m_filterList->SaveToDefaultFile();
+ destIFolder->ThrowAlertMsg("filterDisabled", msgWindow);
+ }
+ return NS_MSG_NOT_A_MAIL_FOLDER;
+ }
+
+ uint32_t messageLength;
+ mailHdr->GetMessageSize(&messageLength);
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(destIFolder);
+ if (localFolder) {
+ bool destFolderTooBig = true;
+ rv = localFolder->WarnIfLocalFileTooBig(msgWindow, messageLength,
+ &destFolderTooBig);
+ if (NS_FAILED(rv) || destFolderTooBig)
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+
+ nsCOMPtr<nsISupports> myISupports =
+ do_QueryInterface(static_cast<nsIMsgParseMailMsgState*>(this));
+
+ // Make sure no one else is writing into this folder
+ if (NS_FAILED(rv = destIFolder->AcquireSemaphore (myISupports)))
+ {
+ destIFolder->ThrowAlertMsg("filterFolderDeniedLocked", msgWindow);
+ return rv;
+ }
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool reusable;
+ rv = m_downloadFolder->GetMsgInputStream(mailHdr, &reusable, getter_AddRefs(inputStream));
+ if (!inputStream)
+ {
+ NS_ERROR("couldn't get source msg input stream in move filter");
+ destIFolder->ReleaseSemaphore (myISupports);
+ return NS_MSG_FOLDER_UNREADABLE; // ### dmb
+ }
+
+ nsCOMPtr<nsIMsgDatabase> destMailDB;
+
+ if (!localFolder)
+ return NS_MSG_POP_FILTER_TARGET_ERROR;
+
+ // don't force upgrade in place - open the db here before we start writing to the
+ // destination file because XP_Stat can return file size including bytes written...
+ rv = localFolder->GetDatabaseWOReparse(getter_AddRefs(destMailDB));
+ NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv),
+ "failed to open mail db parsing folder");
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ if (destMailDB)
+ rv = destMailDB->CopyHdrFromExistingHdr(m_new_key, mailHdr, true,
+ getter_AddRefs(newHdr));
+ if (NS_SUCCEEDED(rv) && !newHdr)
+ rv = NS_ERROR_UNEXPECTED;
+
+ if (NS_FAILED(rv))
+ {
+ destIFolder->ThrowAlertMsg("filterFolderHdrAddFailed", msgWindow);
+ } else {
+ rv = AppendMsgFromStream(inputStream, newHdr, messageLength, destIFolder);
+ if (NS_FAILED(rv))
+ destIFolder->ThrowAlertMsg("filterFolderWriteFailed", msgWindow);
+ }
+
+ if (NS_FAILED(rv))
+ {
+ if (destMailDB)
+ destMailDB->Close(true);
+
+ destIFolder->ReleaseSemaphore(myISupports);
+
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+
+ bool movedMsgIsNew = false;
+ // if we have made it this far then the message has successfully been written to the new folder
+ // now add the header to the destMailDB.
+
+ uint32_t newFlags;
+ newHdr->GetFlags(&newFlags);
+ nsMsgKey msgKey;
+ newHdr->GetMessageKey(&msgKey);
+ if (!(newFlags & nsMsgMessageFlags::Read))
+ {
+ nsCString junkScoreStr;
+ (void) newHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ if (atoi(junkScoreStr.get()) == nsIJunkMailPlugin::IS_HAM_SCORE)
+ {
+ newHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
+ destMailDB->AddToNewList(msgKey);
+ movedMsgIsNew = true;
+ }
+ }
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgAdded(newHdr);
+ // mark the header as not yet reported classified
+ destIFolder->OrProcessingFlags(
+ msgKey, nsMsgProcessingFlags::NotReportedClassified);
+ m_msgToForwardOrReply = newHdr;
+
+ if (movedMsgIsNew)
+ destIFolder->SetHasNewMessages(true);
+ if (!m_filterTargetFolders.Contains(destIFolder))
+ m_filterTargetFolders.AppendObject(destIFolder);
+
+ destIFolder->ReleaseSemaphore (myISupports);
+
+ (void) localFolder->RefreshSizeOnDisk();
+
+ // Notify the message was moved.
+ if (notifier) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = mailHdr->GetFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv)) {
+ notifier->NotifyItemEvent(folder,
+ NS_LITERAL_CSTRING("UnincorporatedMessageMoved"),
+ newHdr);
+ } else {
+ NS_WARNING("Can't get folder for message that was moved.");
+ }
+ }
+
+ nsCOMPtr<nsIMsgPluggableStore> store;
+ rv = m_downloadFolder->GetMsgStore(getter_AddRefs(store));
+ if (store)
+ store->DiscardNewMessage(m_outputStream, mailHdr);
+ if (sourceDB)
+ sourceDB->RemoveHeaderMdbRow(mailHdr);
+
+ // update the folder size so we won't reparse.
+ UpdateDBFolderInfo(destMailDB);
+ destIFolder->UpdateSummaryTotals(true);
+
+ destMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ return rv;
+}
diff --git a/mailnews/local/src/nsParseMailbox.h b/mailnews/local/src/nsParseMailbox.h
new file mode 100644
index 000000000..646cfd533
--- /dev/null
+++ b/mailnews/local/src/nsParseMailbox.h
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsParseMailbox_H
+#define nsParseMailbox_H
+
+#include "mozilla/Attributes.h"
+#include "nsIURI.h"
+#include "nsIMsgParseMailMsgState.h"
+#include "nsIStreamListener.h"
+#include "nsMsgLineBuffer.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIDBChangeListener.h"
+#include "nsIWeakReference.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIMsgWindow.h"
+#include "nsImapMoveCoalescer.h"
+#include "nsAutoPtr.h"
+#include "nsStringGlue.h"
+#include "nsIMsgFilterList.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgFilterHitNotify.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsTArray.h"
+
+class nsByteArray;
+class nsOutputFileStream;
+class nsIOFileStream;
+class nsInputFileStream;
+class nsIMsgFilter;
+class MSG_FolderInfoMail;
+class nsIMsgFilterList;
+class nsIMsgFolder;
+
+/* Used for the various things that parse RFC822 headers...
+ */
+typedef struct message_header
+{
+ const char *value; /* The contents of a header (after ": ") */
+ int32_t length; /* The length of the data (it is not NULL-terminated.) */
+} message_header;
+
+// This object maintains the parse state for a single mail message.
+class nsParseMailMessageState : public nsIMsgParseMailMsgState, public nsIDBChangeListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPARSEMAILMSGSTATE
+ NS_DECL_NSIDBCHANGELISTENER
+
+ nsParseMailMessageState();
+
+ void Init(uint64_t fileposition);
+ virtual nsresult ParseFolderLine(const char *line, uint32_t lineLength);
+ virtual nsresult StartNewEnvelope(const char *line, uint32_t lineLength);
+ nsresult ParseHeaders();
+ nsresult FinalizeHeaders();
+ nsresult ParseEnvelope (const char *line, uint32_t line_size);
+ nsresult InternSubject (struct message_header *header);
+
+ static bool IsEnvelopeLine(const char *buf, int32_t buf_size);
+
+ nsCOMPtr<nsIMsgDBHdr> m_newMsgHdr; /* current message header we're building */
+ nsCOMPtr<nsIMsgDatabase> m_mailDB;
+ nsCOMPtr<nsIMsgDatabase> m_backupMailDB;
+
+ nsMailboxParseState m_state;
+ int64_t m_position;
+ uint64_t m_envelope_pos;
+ uint64_t m_headerstartpos;
+ nsMsgKey m_new_key; // DB key for the new header.
+
+ nsByteArray m_headers;
+
+ nsByteArray m_envelope;
+
+ struct message_header m_message_id;
+ struct message_header m_references;
+ struct message_header m_date;
+ struct message_header m_delivery_date;
+ struct message_header m_from;
+ struct message_header m_sender;
+ struct message_header m_newsgroups;
+ struct message_header m_subject;
+ struct message_header m_status;
+ struct message_header m_mozstatus;
+ struct message_header m_mozstatus2;
+ struct message_header m_in_reply_to;
+ struct message_header m_replyTo;
+ struct message_header m_content_type;
+ struct message_header m_bccList;
+
+ // Support for having multiple To or Cc header lines in a message
+ nsTArray<struct message_header*> m_toList;
+ nsTArray<struct message_header*> m_ccList;
+ struct message_header *GetNextHeaderInAggregate (nsTArray<struct message_header*> &list);
+ void GetAggregateHeader (nsTArray<struct message_header*> &list, struct message_header *);
+ void ClearAggregateHeader (nsTArray<struct message_header*> &list);
+
+ struct message_header m_envelope_from;
+ struct message_header m_envelope_date;
+ struct message_header m_priority;
+ struct message_header m_account_key;
+ struct message_header m_keywords;
+ // Mdn support
+ struct message_header m_mdn_original_recipient;
+ struct message_header m_return_path;
+ struct message_header m_mdn_dnt; /* MDN Disposition-Notification-To: header */
+
+ PRTime m_receivedTime;
+ uint16_t m_body_lines;
+ uint16_t m_lastLineBlank;
+
+ bool m_IgnoreXMozillaStatus;
+
+ // this enables extensions to add the values of particular headers to
+ // the .msf file as properties of nsIMsgHdr. It is initialized from a
+ // pref, mailnews.customDBHeaders
+ nsTArray<nsCString> m_customDBHeaders;
+ struct message_header *m_customDBHeaderValues;
+ nsCString m_receivedValue; // accumulated received header
+protected:
+ virtual ~nsParseMailMessageState();
+};
+
+// This class is part of the mailbox parsing state machine
+class nsMsgMailboxParser : public nsIStreamListener, public nsParseMailMessageState, public nsMsgLineBuffer
+{
+public:
+ explicit nsMsgMailboxParser(nsIMsgFolder *);
+ nsMsgMailboxParser();
+ nsresult Init();
+
+ bool IsRunningUrl() { return m_urlInProgress;} // returns true if we are currently running a url and false otherwise...
+ NS_DECL_ISUPPORTS_INHERITED
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we suppport the nsIStreamListener interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ void SetDB (nsIMsgDatabase *mailDB) {m_mailDB = mailDB; }
+
+ // message socket libnet callbacks, which come through folder pane
+ nsresult ProcessMailboxInputStream(nsIURI* aURL, nsIInputStream *aIStream, uint32_t aLength);
+
+ virtual void DoneParsingFolder(nsresult status);
+ virtual void AbortNewHeader();
+
+ // for nsMsgLineBuffer
+ virtual nsresult HandleLine(const char *line, uint32_t line_length) override;
+
+ void UpdateDBFolderInfo();
+ void UpdateDBFolderInfo(nsIMsgDatabase *mailDB);
+ void UpdateStatusText(const char* stringName);
+
+ // Update the progress bar based on what we know.
+ virtual void UpdateProgressPercent ();
+ virtual void OnNewMessage(nsIMsgWindow *msgWindow);
+
+protected:
+ virtual ~nsMsgMailboxParser();
+ nsCOMPtr<nsIMsgStatusFeedback> m_statusFeedback;
+
+ virtual int32_t PublishMsgHeader(nsIMsgWindow *msgWindow);
+ void FreeBuffers();
+
+ // data
+ nsString m_folderName;
+ nsCString m_inboxUri;
+ nsByteArray m_inputStream;
+ int32_t m_obuffer_size;
+ char *m_obuffer;
+ uint64_t m_graph_progress_total;
+ uint64_t m_graph_progress_received;
+ bool m_parsingDone;
+ PRTime m_startTime;
+private:
+ // the following flag is used to determine when a url is currently being run. It is cleared on calls
+ // to ::StopBinding and it is set whenever we call Load on a url
+ bool m_urlInProgress;
+ nsWeakPtr m_folder;
+ void ReleaseFolderLock();
+ nsresult AcquireFolderLock();
+
+};
+
+class nsParseNewMailState : public nsMsgMailboxParser
+, public nsIMsgFilterHitNotify
+{
+public:
+ nsParseNewMailState();
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD FinishHeader() override;
+
+ nsresult Init(nsIMsgFolder *rootFolder, nsIMsgFolder *downloadFolder,
+ nsIMsgWindow *aMsgWindow, nsIMsgDBHdr *aHdr,
+ nsIOutputStream *aOutputStream);
+
+ virtual void DoneParsingFolder(nsresult status) override;
+
+ void DisableFilters() {m_disableFilters = true;}
+
+ NS_DECL_NSIMSGFILTERHITNOTIFY
+
+ nsOutputFileStream *GetLogFile();
+ virtual int32_t PublishMsgHeader(nsIMsgWindow *msgWindow) override;
+ void GetMsgWindow(nsIMsgWindow **aMsgWindow);
+ nsresult EndMsgDownload();
+
+ nsresult AppendMsgFromStream(nsIInputStream *fileStream, nsIMsgDBHdr *aHdr,
+ uint32_t length, nsIMsgFolder *destFolder);
+
+ virtual void ApplyFilters(bool *pMoved, nsIMsgWindow *msgWindow,
+ uint64_t msgOffset);
+ nsresult ApplyForwardAndReplyFilter(nsIMsgWindow *msgWindow);
+ virtual void OnNewMessage(nsIMsgWindow *msgWindow) override;
+
+ // this keeps track of how many messages we downloaded that
+ // aren't new - e.g., marked read, or moved to an other server.
+ int32_t m_numNotNewMessages;
+protected:
+ virtual ~nsParseNewMailState();
+ virtual nsresult GetTrashFolder(nsIMsgFolder **pTrashFolder);
+ virtual nsresult MoveIncorporatedMessage(nsIMsgDBHdr *mailHdr,
+ nsIMsgDatabase *sourceDB,
+ nsIMsgFolder *destIFolder,
+ nsIMsgFilter *filter,
+ nsIMsgWindow *msgWindow);
+ virtual void MarkFilteredMessageRead(nsIMsgDBHdr *msgHdr);
+ virtual void MarkFilteredMessageUnread(nsIMsgDBHdr *msgHdr);
+
+ nsCOMPtr <nsIMsgFilterList> m_filterList;
+ nsCOMPtr <nsIMsgFilterList> m_deferredToServerFilterList;
+ nsCOMPtr <nsIMsgFolder> m_rootFolder;
+ nsCOMPtr <nsIMsgWindow> m_msgWindow;
+ nsCOMPtr <nsIMsgFolder> m_downloadFolder;
+ nsCOMPtr<nsIOutputStream> m_outputStream;
+ nsCOMArray <nsIMsgFolder> m_filterTargetFolders;
+
+ RefPtr<nsImapMoveCoalescer> m_moveCoalescer;
+
+ bool m_msgMovedByFilter;
+ bool m_msgCopiedByFilter;
+ bool m_disableFilters;
+ uint32_t m_ibuffer_fp;
+ char *m_ibuffer;
+ uint32_t m_ibuffer_size;
+ // used for applying move filters, because in the case of using a temporary
+ // download file, the offset/key in the msg hdr is not right.
+ uint64_t m_curHdrOffset;
+
+ // we have to apply the reply/forward filters in a second pass, after
+ // msg quarantining and moving to other local folders, so we remember the
+ // info we'll need to apply them with these vars.
+ // these need to be arrays in case we have multiple reply/forward filters.
+ nsTArray<nsCString> m_forwardTo;
+ nsTArray<nsCString> m_replyTemplateUri;
+ nsCOMPtr<nsIMsgDBHdr> m_msgToForwardOrReply;
+ nsCOMPtr<nsIMsgFilter> m_filter;
+ nsCOMPtr<nsIMsgRuleAction> m_ruleAction;
+};
+
+#endif
diff --git a/mailnews/local/src/nsPop3IncomingServer.cpp b/mailnews/local/src/nsPop3IncomingServer.cpp
new file mode 100644
index 000000000..261d141a4
--- /dev/null
+++ b/mailnews/local/src/nsPop3IncomingServer.cpp
@@ -0,0 +1,744 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsPop3IncomingServer.h"
+#include "nsIPop3Service.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgLocalCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsPop3Protocol.h"
+#include "nsAutoPtr.h"
+#include "nsMsgKeyArray.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsMsgUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Likely.h"
+
+static NS_DEFINE_CID(kCPop3ServiceCID, NS_POP3SERVICE_CID);
+
+class nsPop3GetMailChainer final : public nsIUrlListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+
+ nsPop3GetMailChainer();
+ nsresult GetNewMailForServers(nsIPop3IncomingServer** servers, uint32_t count,
+ nsIMsgWindow *msgWindow,
+ nsIMsgFolder *folderToDownloadTo, nsIUrlListener *listener);
+ nsresult RunNextGetNewMail();
+protected:
+ ~nsPop3GetMailChainer();
+ nsCOMPtr <nsIMsgFolder> m_folderToDownloadTo;
+ nsCOMPtr <nsIMsgWindow> m_downloadingMsgWindow;
+ nsCOMArray<nsIPop3IncomingServer> m_serversToGetNewMailFor;
+ nsCOMPtr <nsIUrlListener> m_listener;
+};
+
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPop3IncomingServer,
+ nsMsgIncomingServer,
+ nsIPop3IncomingServer,
+ nsILocalMailIncomingServer)
+
+nsPop3IncomingServer::nsPop3IncomingServer()
+{
+ m_capabilityFlags =
+ POP3_AUTH_MECH_UNDEFINED |
+ POP3_HAS_AUTH_USER | // should be always there
+ POP3_GURL_UNDEFINED |
+ POP3_UIDL_UNDEFINED |
+ POP3_TOP_UNDEFINED |
+ POP3_XTND_XLST_UNDEFINED;
+
+ m_canHaveFilters = true;
+ m_authenticated = false;
+}
+
+nsPop3IncomingServer::~nsPop3IncomingServer()
+{
+}
+
+NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer,
+ LeaveMessagesOnServer,
+ "leave_on_server")
+
+NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer,
+ HeadersOnly,
+ "headers_only")
+
+NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer,
+ DeleteMailLeftOnServer,
+ "delete_mail_left_on_server")
+
+NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer,
+ DotFix,
+ "dot_fix")
+
+NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer,
+ DeleteByAgeFromServer,
+ "delete_by_age_from_server")
+
+NS_IMPL_SERVERPREF_INT(nsPop3IncomingServer,
+ NumDaysToLeaveOnServer,
+ "num_days_to_leave_on_server")
+
+
+NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer,
+ DeferGetNewMail,
+ "defer_get_new_mail")
+
+NS_IMETHODIMP nsPop3IncomingServer::GetDeferredToAccount(nsACString& aRetVal)
+{
+ nsresult rv = GetCharValue("deferred_to_account", aRetVal);
+ if (aRetVal.IsEmpty())
+ return rv;
+ // We need to repair broken profiles that defer to hidden or invalid servers,
+ // so find out if the deferred to account has a valid non-hidden server, and
+ // if not, defer to the local folders inbox.
+ nsCOMPtr<nsIMsgAccountManager> acctMgr =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID);
+ bool invalidAccount = true;
+ if (acctMgr)
+ {
+ nsCOMPtr<nsIMsgAccount> account;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = acctMgr->GetAccount(aRetVal, getter_AddRefs(account));
+ if (account)
+ {
+ account->GetIncomingServer(getter_AddRefs(server));
+ if (server)
+ server->GetHidden(&invalidAccount);
+ }
+ if (invalidAccount)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> localServer;
+ nsCOMPtr<nsIMsgAccount> localAccount;
+
+ rv = acctMgr->GetLocalFoldersServer(getter_AddRefs(localServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Try to copy any folders that have been stranded in the hidden account
+ // into the local folders account.
+ if (server)
+ {
+ nsCOMPtr<nsIMsgFolder> hiddenRootFolder;
+ nsCOMPtr<nsIMsgFolder> localFoldersRoot;
+ server->GetRootFolder(getter_AddRefs(hiddenRootFolder));
+ localServer->GetRootFolder(getter_AddRefs(localFoldersRoot));
+ if (hiddenRootFolder && localFoldersRoot)
+ {
+ // We're going to iterate over the folders in Local Folders-1,
+ // though I suspect only the Inbox will have messages. I don't
+ // think Sent Mail could end up here, but if any folders have
+ // messages, might as well copy them to the real Local Folders
+ // account.
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = hiddenRootFolder->GetSubFolders(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+ nsCOMPtr<nsIMsgFolder> subFolder(do_QueryInterface(item));
+ if (subFolder)
+ {
+ nsCOMPtr<nsIMsgDatabase> subFolderDB;
+ subFolder->GetMsgDatabase(getter_AddRefs(subFolderDB));
+ if (subFolderDB)
+ {
+ // Copy any messages in this sub-folder of the hidden
+ // account to the corresponding folder in Local Folders.
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ rv = subFolderDB->ListAllKeys(keys);
+ nsCOMPtr<nsIMutableArray> hdrsToCopy(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ MsgGetHeadersFromKeys(subFolderDB, keys->m_keys, hdrsToCopy);
+ uint32_t numHdrs = 0;
+ if (hdrsToCopy)
+ hdrsToCopy->GetLength(&numHdrs);
+ if (numHdrs)
+ {
+ // Look for a folder with the same name in Local Folders.
+ nsCOMPtr<nsIMsgFolder> dest;
+ nsString folderName;
+ subFolder->GetName(folderName);
+ localFoldersRoot->GetChildNamed(folderName,
+ getter_AddRefs(dest));
+ if (dest)
+ dest->CopyMessages(subFolder, hdrsToCopy, false,
+ nullptr, nullptr, false,false);
+ // Should we copy the folder if the dest doesn't exist?
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ rv = acctMgr->FindAccountForServer(localServer, getter_AddRefs(localAccount));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!localAccount)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ localAccount->GetKey(aRetVal);
+ // Can't call SetDeferredToAccount because it calls GetDeferredToAccount.
+ return SetCharValue("deferred_to_account", aRetVal);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsPop3IncomingServer::SetDeferredToAccount(const nsACString& aAccountKey)
+{
+ nsCString deferredToAccount;
+ GetDeferredToAccount(deferredToAccount);
+ m_rootMsgFolder = nullptr; // clear this so we'll recalculate it on demand.
+ //Notify listeners who listen to every folder
+
+ nsresult rv = SetCharValue("deferred_to_account", aAccountKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ // use GetRootFolder, because that returns the real
+ // root, not the deferred to root.
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ {
+ // if isDeferred state has changed, send notification
+ if (aAccountKey.IsEmpty() != deferredToAccount.IsEmpty())
+ {
+ nsCOMPtr <nsIAtom> deferAtom = MsgGetAtom("isDeferred");
+ nsCOMPtr <nsIAtom> canFileAtom = MsgGetAtom("CanFileMessages");
+ folderListenerManager->OnItemBoolPropertyChanged(rootFolder, deferAtom,
+ !deferredToAccount.IsEmpty(), deferredToAccount.IsEmpty());
+ folderListenerManager->OnItemBoolPropertyChanged(rootFolder, canFileAtom,
+ deferredToAccount.IsEmpty(), !deferredToAccount.IsEmpty());
+
+ // this hack causes the account manager ds to send notifications to the
+ // xul content builder that make the changed acct appear or disappear
+ // from the folder pane and related menus.
+ nsCOMPtr<nsIMsgAccountManager> acctMgr =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID);
+ if (acctMgr)
+ {
+ acctMgr->NotifyServerUnloaded(this);
+ acctMgr->NotifyServerLoaded(this);
+ // check if this newly deferred to account is the local folders account
+ // and needs to have a newly created INBOX.
+ if (!aAccountKey.IsEmpty())
+ {
+ nsCOMPtr <nsIMsgAccount> account;
+ acctMgr->GetAccount(aAccountKey, getter_AddRefs(account));
+ if (account)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ account->GetIncomingServer(getter_AddRefs(server));
+ if (server)
+ {
+ nsCOMPtr <nsILocalMailIncomingServer> incomingLocalServer = do_QueryInterface(server);
+ if (incomingLocalServer)
+ {
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // this will fail if it already exists, which is fine.
+ rootFolder->CreateSubfolder(NS_LITERAL_STRING("Inbox"), nullptr);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+//NS_IMPL_GETSET(nsPop3IncomingServer, Authenticated, bool, m_authenticated);
+
+NS_IMETHODIMP nsPop3IncomingServer::GetAuthenticated(bool *aAuthenticated)
+{
+ NS_ENSURE_ARG_POINTER(aAuthenticated);
+ *aAuthenticated = m_authenticated;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3IncomingServer::SetAuthenticated(bool aAuthenticated)
+{
+ m_authenticated = aAuthenticated;
+ return NS_OK;
+}
+
+nsresult
+nsPop3IncomingServer::GetPop3CapabilityFlags(uint32_t *flags)
+{
+ *flags = m_capabilityFlags;
+ return NS_OK;
+}
+
+nsresult
+nsPop3IncomingServer::SetPop3CapabilityFlags(uint32_t flags)
+{
+ m_capabilityFlags = flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3IncomingServer::GetRootMsgFolder(nsIMsgFolder **aRootMsgFolder)
+{
+ NS_ENSURE_ARG_POINTER(aRootMsgFolder);
+ nsresult rv = NS_OK;
+ if (!m_rootMsgFolder)
+ {
+ nsCString deferredToAccount;
+ GetDeferredToAccount(deferredToAccount);
+ if (deferredToAccount.IsEmpty())
+ {
+ rv = CreateRootFolder();
+ m_rootMsgFolder = m_rootFolder;
+ }
+ else
+ {
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr <nsIMsgAccount> account;
+ rv = accountManager->GetAccount(deferredToAccount, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (account)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = account->GetIncomingServer(getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+ // make sure we're not deferred to ourself...
+ if (incomingServer && incomingServer != this)
+ rv = incomingServer->GetRootMsgFolder(getter_AddRefs(m_rootMsgFolder));
+ else
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ NS_IF_ADDREF(*aRootMsgFolder = m_rootMsgFolder);
+ return m_rootMsgFolder ? rv : NS_ERROR_FAILURE;
+}
+
+nsresult nsPop3IncomingServer::GetInbox(nsIMsgWindow *msgWindow, nsIMsgFolder **inbox)
+{
+ NS_ENSURE_ARG_POINTER(inbox);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootMsgFolder(getter_AddRefs(rootFolder));
+ if(NS_SUCCEEDED(rv) && rootFolder)
+ {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, inbox);
+ }
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localInbox = do_QueryInterface(*inbox, &rv);
+ if (NS_SUCCEEDED(rv) && localInbox)
+ {
+ nsCOMPtr <nsIMsgDatabase> db;
+ rv = (*inbox)->GetMsgDatabase(getter_AddRefs(db));
+ if (NS_FAILED(rv))
+ {
+ (*inbox)->SetMsgDatabase(nullptr);
+ (void) localInbox->SetCheckForNewMessagesAfterParsing(true);
+ // this will cause a reparse of the mail folder.
+ localInbox->GetDatabaseWithReparse(nullptr, msgWindow, getter_AddRefs(db));
+ rv = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsPop3IncomingServer::PerformBiff(nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(kCPop3ServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> inbox;
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsCOMPtr<nsIUrlListener> urlListener;
+ rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_TRUE(rootMsgFolder, NS_ERROR_FAILURE);
+
+ rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(inbox));
+ if (!inbox)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ inbox->GetServer(getter_AddRefs(server));
+
+ server->SetPerformingBiff(true);
+
+ urlListener = do_QueryInterface(inbox);
+
+ bool downloadOnBiff = false;
+ rv = GetDownloadOnBiff(&downloadOnBiff);
+ if (downloadOnBiff)
+ {
+ nsCOMPtr <nsIMsgLocalMailFolder> localInbox = do_QueryInterface(inbox, &rv);
+ if (localInbox && NS_SUCCEEDED(rv))
+ {
+ bool valid = false;
+ nsCOMPtr <nsIMsgDatabase> db;
+ rv = inbox->GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db)
+ rv = db->GetSummaryValid(&valid);
+ if (NS_SUCCEEDED(rv) && valid)
+ rv = pop3Service->GetNewMail(aMsgWindow, urlListener, inbox, this, nullptr);
+ else
+ {
+ bool isLocked;
+ inbox->GetLocked(&isLocked);
+ if (!isLocked)
+ rv = localInbox->GetDatabaseWithReparse(urlListener, aMsgWindow, getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv))
+ rv = localInbox->SetCheckForNewMessagesAfterParsing(true);
+ }
+ }
+ }
+ else
+ rv = pop3Service->CheckForNewMail(aMsgWindow, urlListener, inbox, this, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3IncomingServer::SetFlagsOnDefaultMailboxes()
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(rootFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pop3 gets an inbox, but no queue (unsent messages)
+ localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::SpecialUse &
+ ~nsMsgFolderFlags::Queue);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsPop3IncomingServer::CreateDefaultMailboxes()
+{
+ nsresult rv = CreateLocalFolder(NS_LITERAL_STRING("Inbox"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CreateLocalFolder(NS_LITERAL_STRING("Trash"));
+}
+
+// override this so we can say that deferred accounts can't have messages
+// filed to them, which will remove them as targets of all the move/copy
+// menu items.
+NS_IMETHODIMP
+nsPop3IncomingServer::GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer)
+{
+ NS_ENSURE_ARG_POINTER(aCanFileMessagesOnServer);
+
+ nsCString deferredToAccount;
+ GetDeferredToAccount(deferredToAccount);
+ *aCanFileMessagesOnServer = deferredToAccount.IsEmpty();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsPop3IncomingServer::GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer)
+{
+ NS_ENSURE_ARG_POINTER(aCanCreateFoldersOnServer);
+
+ nsCString deferredToAccount;
+ GetDeferredToAccount(deferredToAccount);
+ *aCanCreateFoldersOnServer = deferredToAccount.IsEmpty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3IncomingServer::VerifyLogon(nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow, nsIURI **aURL)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPop3Service> pop3Service = do_GetService(kCPop3ServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return pop3Service->VerifyLogon(this, aUrlListener, aMsgWindow, aURL);
+}
+
+NS_IMETHODIMP nsPop3IncomingServer::DownloadMailFromServers(nsIPop3IncomingServer** aServers,
+ uint32_t aCount,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgFolder *aFolder,
+ nsIUrlListener *aUrlListener)
+{
+ nsPop3GetMailChainer *getMailChainer = new nsPop3GetMailChainer;
+ NS_ENSURE_TRUE(getMailChainer, NS_ERROR_OUT_OF_MEMORY);
+ getMailChainer->AddRef(); // this object owns itself and releases when done.
+ return getMailChainer->GetNewMailForServers(aServers, aCount, aMsgWindow, aFolder, aUrlListener);
+}
+
+NS_IMETHODIMP nsPop3IncomingServer::GetNewMail(nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ nsIMsgFolder *aInbox,
+ nsIURI **aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPop3Service> pop3Service = do_GetService(kCPop3ServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return pop3Service->GetNewMail(aMsgWindow, aUrlListener, aInbox, this, aResult);
+}
+
+// user has clicked get new messages on this server. If other servers defer to this server,
+// we need to get new mail for them. But if this server defers to an other server,
+// I think we only get new mail for this server.
+NS_IMETHODIMP
+nsPop3IncomingServer::GetNewMessages(nsIMsgFolder *aFolder, nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIPop3Service> pop3Service = do_GetService(kCPop3ServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgFolder> inbox;
+ rv = GetInbox(aMsgWindow, getter_AddRefs(inbox));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIURI> url;
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ nsCOMArray<nsIPop3IncomingServer> deferredServers;
+ nsCString deferredToAccount;
+ GetDeferredToAccount(deferredToAccount);
+
+ if (deferredToAccount.IsEmpty())
+ {
+ aFolder->GetServer(getter_AddRefs(server));
+ GetDeferredServers(server, deferredServers);
+ }
+ if (deferredToAccount.IsEmpty() && !deferredServers.IsEmpty())
+ {
+ nsPop3GetMailChainer *getMailChainer = new nsPop3GetMailChainer;
+ NS_ENSURE_TRUE(getMailChainer, NS_ERROR_OUT_OF_MEMORY);
+ getMailChainer->AddRef(); // this object owns itself and releases when done.
+ deferredServers.InsertElementAt(0, this);
+ return getMailChainer->GetNewMailForServers(deferredServers.Elements(),
+ deferredServers.Length(), aMsgWindow, inbox, aUrlListener);
+ }
+ if (m_runningProtocol)
+ return NS_MSG_FOLDER_BUSY;
+
+ return pop3Service->GetNewMail(aMsgWindow, aUrlListener, inbox, this, getter_AddRefs(url));
+}
+
+NS_IMETHODIMP
+nsPop3IncomingServer::GetDownloadMessagesAtStartup(bool *getMessagesAtStartup)
+{
+ NS_ENSURE_ARG_POINTER(getMessagesAtStartup);
+ // GetMessages is not automatically done for pop servers at startup.
+ // We need to trigger that action. Return true.
+ *getMessagesAtStartup = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3IncomingServer::GetCanBeDefaultServer(bool *canBeDefaultServer)
+{
+ NS_ENSURE_ARG_POINTER(canBeDefaultServer);
+ *canBeDefaultServer = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3IncomingServer::GetCanSearchMessages(bool *canSearchMessages)
+{
+ // this will return false if this server is deferred, which is what we want.
+ return GetCanFileMessagesOnServer(canSearchMessages);
+}
+
+NS_IMETHODIMP
+nsPop3IncomingServer::CloseCachedConnections()
+{
+ nsCOMPtr<nsIRequest> channel = do_QueryInterface(m_runningProtocol);
+ if (channel)
+ channel->Cancel(NS_ERROR_ABORT);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3IncomingServer::GetOfflineSupportLevel(int32_t *aSupportLevel)
+{
+ NS_ENSURE_ARG_POINTER(aSupportLevel);
+
+ nsresult rv;
+ rv = GetIntValue("offline_support_level", aSupportLevel);
+ if (*aSupportLevel != OFFLINE_SUPPORT_LEVEL_UNDEFINED) return rv;
+
+ // set default value
+ *aSupportLevel = OFFLINE_SUPPORT_LEVEL_NONE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3IncomingServer::SetRunningProtocol(nsIPop3Protocol *aProtocol)
+{
+ NS_ASSERTION(!aProtocol || !m_runningProtocol, "overriding running protocol");
+ m_runningProtocol = aProtocol;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3IncomingServer::GetRunningProtocol(nsIPop3Protocol **aProtocol)
+{
+ NS_ENSURE_ARG_POINTER(aProtocol);
+ NS_IF_ADDREF(*aProtocol = m_runningProtocol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3IncomingServer::AddUidlToMark(const char *aUidl, int32_t aMark)
+{
+ NS_ENSURE_ARG_POINTER(aUidl);
+
+ Pop3UidlEntry *uidlEntry = PR_NEWZAP(Pop3UidlEntry);
+ NS_ENSURE_TRUE(uidlEntry, NS_ERROR_OUT_OF_MEMORY);
+
+ uidlEntry->uidl = strdup(aUidl);
+ if (MOZ_UNLIKELY(!uidlEntry->uidl)) {
+ PR_Free(uidlEntry);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uidlEntry->status = (aMark == POP3_DELETE) ? DELETE_CHAR :
+ (aMark == POP3_FETCH_BODY) ? FETCH_BODY : KEEP;
+ m_uidlsToMark.AppendElement(uidlEntry);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3IncomingServer::MarkMessages()
+{
+ nsresult rv;
+ if (m_runningProtocol)
+ rv = m_runningProtocol->MarkMessages(&m_uidlsToMark);
+ else
+ {
+ nsCString hostName;
+ nsCString userName;
+ nsCOMPtr<nsIFile> localPath;
+
+ GetLocalPath(getter_AddRefs(localPath));
+
+ GetHostName(hostName);
+ GetUsername(userName);
+ // do it all in one fell swoop
+ rv = nsPop3Protocol::MarkMsgForHost(hostName.get(), userName.get(), localPath, m_uidlsToMark);
+ }
+ uint32_t count = m_uidlsToMark.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ Pop3UidlEntry *ue = m_uidlsToMark[i];
+ PR_Free(ue->uidl);
+ PR_Free(ue);
+ }
+ m_uidlsToMark.Clear();
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsPop3GetMailChainer, nsIUrlListener)
+
+nsPop3GetMailChainer::nsPop3GetMailChainer()
+{
+}
+nsPop3GetMailChainer::~nsPop3GetMailChainer()
+{
+}
+
+nsresult nsPop3GetMailChainer::GetNewMailForServers(nsIPop3IncomingServer** servers,
+ uint32_t count, nsIMsgWindow *msgWindow,
+ nsIMsgFolder *folderToDownloadTo, nsIUrlListener *listener)
+{
+ NS_ENSURE_ARG_POINTER(folderToDownloadTo);
+
+ m_serversToGetNewMailFor.AppendElements(servers, count);
+ m_folderToDownloadTo = folderToDownloadTo;
+ m_downloadingMsgWindow = msgWindow;
+ m_listener = listener;
+ nsCOMPtr <nsIMsgDatabase> destFolderDB;
+
+ nsresult rv = folderToDownloadTo->GetMsgDatabase(getter_AddRefs(destFolderDB));
+ if (NS_FAILED(rv) || !destFolderDB)
+ {
+ nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folderToDownloadTo);
+ if (localFolder)
+ {
+ localFolder->GetDatabaseWithReparse(this, msgWindow, getter_AddRefs(destFolderDB));
+ return NS_OK;
+ }
+ }
+ return RunNextGetNewMail();
+}
+
+NS_IMETHODIMP
+nsPop3GetMailChainer::OnStartRunningUrl(nsIURI *url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3GetMailChainer::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ return RunNextGetNewMail();
+}
+
+nsresult nsPop3GetMailChainer::RunNextGetNewMail()
+{
+ nsresult rv;
+ uint32_t numServersLeft = m_serversToGetNewMailFor.Count();
+
+ for (; numServersLeft > 0;)
+ {
+ nsCOMPtr<nsIPop3IncomingServer> popServer(m_serversToGetNewMailFor[0]);
+ m_serversToGetNewMailFor.RemoveObjectAt(0);
+ numServersLeft--;
+ if (popServer)
+ {
+ bool deferGetNewMail = false;
+ nsCOMPtr <nsIMsgIncomingServer> downloadingToServer;
+ m_folderToDownloadTo->GetServer(getter_AddRefs(downloadingToServer));
+ popServer->GetDeferGetNewMail(&deferGetNewMail);
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(popServer);
+ nsCOMPtr<nsIPop3Protocol> protocol;
+ popServer->GetRunningProtocol(getter_AddRefs(protocol));
+ if ((deferGetNewMail || downloadingToServer == server) && !protocol)
+ {
+ // have to call routine that just gets mail for one server,
+ // and ignores deferred servers...
+ if (server)
+ {
+ nsCOMPtr <nsIURI> url;
+ nsCOMPtr<nsIPop3Service> pop3Service = do_GetService(kCPop3ServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return pop3Service->GetNewMail(m_downloadingMsgWindow, this, m_folderToDownloadTo, popServer, getter_AddRefs(url));
+ }
+ }
+ }
+ }
+ rv = m_listener ? m_listener->OnStopRunningUrl(nullptr, NS_OK) : NS_OK;
+ Release(); // release ref to ourself.
+ return rv;
+}
diff --git a/mailnews/local/src/nsPop3IncomingServer.h b/mailnews/local/src/nsPop3IncomingServer.h
new file mode 100644
index 000000000..3c98f3bd2
--- /dev/null
+++ b/mailnews/local/src/nsPop3IncomingServer.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsPop3IncomingServer_h
+#define __nsPop3IncomingServer_h
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsILocalMailIncomingServer.h"
+#include "nsMsgIncomingServer.h"
+#include "nsIPop3Protocol.h"
+#include "nsIMsgWindow.h"
+#include "nsMailboxServer.h"
+
+/* get some implementation from nsMsgIncomingServer */
+class nsPop3IncomingServer : public nsMailboxServer,
+ public nsIPop3IncomingServer,
+ public nsILocalMailIncomingServer
+
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPOP3INCOMINGSERVER
+ NS_DECL_NSILOCALMAILINCOMINGSERVER
+
+ nsPop3IncomingServer();
+
+ NS_IMETHOD PerformBiff(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD GetDownloadMessagesAtStartup(bool *getMessages) override;
+ NS_IMETHOD GetCanBeDefaultServer(bool *canBeDefaultServer) override;
+ NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override;
+ NS_IMETHOD GetOfflineSupportLevel(int32_t *aSupportLevel) override;
+ NS_IMETHOD CloseCachedConnections() override;
+ NS_IMETHOD GetRootMsgFolder(nsIMsgFolder **aRootMsgFolder) override;
+ NS_IMETHOD GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer) override;
+ NS_IMETHOD GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer) override;
+ NS_IMETHOD VerifyLogon(nsIUrlListener *aUrlListener, nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL) override;
+ NS_IMETHOD GetNewMessages(nsIMsgFolder *aFolder, nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener) override;
+
+protected:
+ virtual ~nsPop3IncomingServer();
+ nsresult GetInbox(nsIMsgWindow *msgWindow, nsIMsgFolder **inbox);
+
+private:
+ uint32_t m_capabilityFlags;
+ bool m_authenticated;
+ nsCOMPtr <nsIPop3Protocol> m_runningProtocol;
+ nsCOMPtr <nsIMsgFolder> m_rootMsgFolder;
+ nsTArray<Pop3UidlEntry*> m_uidlsToMark;
+};
+
+#endif
diff --git a/mailnews/local/src/nsPop3Protocol.cpp b/mailnews/local/src/nsPop3Protocol.cpp
new file mode 100644
index 000000000..5d9d9145a
--- /dev/null
+++ b/mailnews/local/src/nsPop3Protocol.cpp
@@ -0,0 +1,4172 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Jason Eager <jce2@po.cwru.edu>
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ * 06/07/2000 Jason Eager Added check for out of disk space
+ */
+
+#include "nscore.h"
+#include "msgCore.h" // precompiled header...
+#include "nsNetUtil.h"
+#include "nspr.h"
+#include "plbase64.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsISafeOutputStream.h"
+#include "nsPop3Protocol.h"
+#include "MailNewsTypes.h"
+#include "nsStringGlue.h"
+#include "nsIPrompt.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsTextFormatter.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIDocShell.h"
+#include "nsMsgUtils.h"
+#include "nsISocketTransport.h"
+#include "nsISSLSocketControl.h"
+#include "nsILineInputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgBaseCID.h"
+#include "nsIProxyInfo.h"
+#include "nsCRT.h"
+#include "mozilla/Services.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Attributes.h"
+
+using namespace mozilla;
+
+PRLogModuleInfo *POP3LOGMODULE = nullptr;
+#define POP3LOG(str) "%s: [this=%p] " str, POP3LOGMODULE->name, this
+
+static int
+net_pop3_remove_messages_marked_delete(PLHashEntry* he,
+ int msgindex,
+ void *arg)
+{
+ Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value;
+ return (uidlEntry->status == DELETE_CHAR)
+ ? HT_ENUMERATE_REMOVE : HT_ENUMERATE_NEXT;
+}
+
+uint32_t TimeInSecondsFromPRTime(PRTime prTime)
+{
+ return (uint32_t)(prTime / PR_USEC_PER_SEC);
+}
+
+static void
+put_hash(PLHashTable* table, const char* key, char value, uint32_t dateReceived)
+{
+ // don't put not used slots or empty uid into hash
+ if (key && *key)
+ {
+ Pop3UidlEntry* tmp = PR_NEWZAP(Pop3UidlEntry);
+ if (tmp)
+ {
+ tmp->uidl = PL_strdup(key);
+ if (tmp->uidl)
+ {
+ tmp->dateReceived = dateReceived;
+ tmp->status = value;
+ PL_HashTableAdd(table, (const void *)tmp->uidl, (void*) tmp);
+ }
+ else
+ PR_Free(tmp);
+ }
+ }
+}
+
+static int
+net_pop3_copy_hash_entries(PLHashEntry* he, int msgindex, void *arg)
+{
+ Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value;
+ put_hash((PLHashTable *) arg, uidlEntry->uidl, uidlEntry->status, uidlEntry->dateReceived);
+ return HT_ENUMERATE_NEXT;
+}
+
+static void *
+AllocUidlTable(void * /* pool */, size_t size)
+{
+ return PR_MALLOC(size);
+}
+
+static void
+FreeUidlTable(void * /* pool */, void *item)
+{
+ PR_Free(item);
+}
+
+static PLHashEntry *
+AllocUidlInfo(void *pool, const void *key)
+{
+ return PR_NEWZAP(PLHashEntry);
+}
+
+static void
+FreeUidlInfo(void * /* pool */, PLHashEntry *he, unsigned flag)
+{
+ if (flag == HT_FREE_ENTRY)
+ {
+ Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value;
+ if (uidlEntry)
+ {
+ PR_Free(uidlEntry->uidl);
+ PR_Free(uidlEntry);
+ }
+ PR_Free(he);
+ }
+}
+
+static PLHashAllocOps gHashAllocOps = {
+ AllocUidlTable, FreeUidlTable,
+ AllocUidlInfo, FreeUidlInfo
+};
+
+
+static Pop3UidlHost*
+net_pop3_load_state(const char* searchhost,
+ const char* searchuser,
+ nsIFile *mailDirectory)
+{
+ Pop3UidlHost* result = nullptr;
+ Pop3UidlHost* current = nullptr;
+ Pop3UidlHost* tmp;
+
+ result = PR_NEWZAP(Pop3UidlHost);
+ if (!result)
+ return nullptr;
+ result->host = PL_strdup(searchhost);
+ result->user = PL_strdup(searchuser);
+ result->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, &gHashAllocOps, nullptr);
+
+ if (!result->host || !result->user || !result->hash)
+ {
+ PR_Free(result->host);
+ PR_Free(result->user);
+ if (result->hash)
+ PL_HashTableDestroy(result->hash);
+ PR_Free(result);
+ return nullptr;
+ }
+
+ nsCOMPtr <nsIFile> popState;
+ mailDirectory->Clone(getter_AddRefs(popState));
+ if (!popState)
+ return nullptr;
+ popState->AppendNative(NS_LITERAL_CSTRING("popstate.dat"));
+
+ nsCOMPtr<nsIInputStream> fileStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), popState);
+ NS_ENSURE_SUCCESS(rv, result);
+
+ nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv));
+ NS_ENSURE_SUCCESS(rv, result);
+
+ bool more = true;
+ nsCString line;
+
+ while (more && NS_SUCCEEDED(rv))
+ {
+ lineInputStream->ReadLine(line, &more);
+ if (line.IsEmpty())
+ continue;
+ char firstChar = line.CharAt(0);
+ if (firstChar == '#')
+ continue;
+ if (firstChar == '*') {
+ /* It's a host&user line. */
+ current = nullptr;
+ char *lineBuf = line.BeginWriting() + 1; // ok because we know the line isn't empty
+ char *host = NS_strtok(" \t\r\n", &lineBuf);
+ /* without space to also get realnames - see bug 225332 */
+ char *user = NS_strtok("\t\r\n", &lineBuf);
+ if (!host || !user)
+ continue;
+ for (tmp = result ; tmp ; tmp = tmp->next)
+ {
+ if (!strcmp(host, tmp->host) && !strcmp(user, tmp->user))
+ {
+ current = tmp;
+ break;
+ }
+ }
+ if (!current)
+ {
+ current = PR_NEWZAP(Pop3UidlHost);
+ if (current)
+ {
+ current->host = strdup(host);
+ current->user = strdup(user);
+ current->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, &gHashAllocOps, nullptr);
+ if (!current->host || !current->user || !current->hash)
+ {
+ PR_Free(current->host);
+ PR_Free(current->user);
+ if (current->hash)
+ PL_HashTableDestroy(current->hash);
+ PR_Free(current);
+ }
+ else
+ {
+ current->next = result->next;
+ result->next = current;
+ }
+ }
+ }
+ }
+ else
+ {
+ /* It's a line with a UIDL on it. */
+ if (current)
+ {
+ for (int32_t pos = line.FindChar('\t'); pos != -1; pos = line.FindChar('\t', pos))
+ line.Replace(pos, 1, ' ');
+
+ nsTArray<nsCString> lineElems;
+ ParseString(line, ' ', lineElems);
+ if (lineElems.Length() < 2)
+ continue;
+ nsCString *flags = &lineElems[0];
+ nsCString *uidl = &lineElems[1];
+ uint32_t dateReceived = TimeInSecondsFromPRTime(PR_Now()); // if we don't find a date str, assume now.
+ if (lineElems.Length() > 2)
+ dateReceived = atoi(lineElems[2].get());
+ if (!flags->IsEmpty() && !uidl->IsEmpty())
+ {
+ char flag = flags->CharAt(0);
+ if ((flag == KEEP) || (flag == DELETE_CHAR) ||
+ (flag == TOO_BIG) || (flag == FETCH_BODY))
+ {
+ put_hash(current->hash, uidl->get(), flag, dateReceived);
+ }
+ else
+ {
+ NS_ASSERTION(false, "invalid flag in popstate.dat");
+ }
+ }
+ }
+ }
+ }
+ fileStream->Close();
+
+ return result;
+}
+
+static int
+hash_clear_mapper(PLHashEntry* he, int msgindex, void* arg)
+{
+ Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value;
+ PR_Free(uidlEntry->uidl);
+ PR_Free(uidlEntry);
+ he->value = nullptr;
+
+ return HT_ENUMERATE_REMOVE;
+}
+
+static int
+hash_empty_mapper(PLHashEntry* he, int msgindex, void* arg)
+{
+ *((bool*) arg) = false;
+ return HT_ENUMERATE_STOP;
+}
+
+static bool
+hash_empty(PLHashTable* hash)
+{
+ bool result = true;
+ PL_HashTableEnumerateEntries(hash, hash_empty_mapper, (void *)&result);
+ return result;
+}
+
+
+static int
+net_pop3_write_mapper(PLHashEntry* he, int msgindex, void* arg)
+{
+ nsIOutputStream* file = (nsIOutputStream*) arg;
+ Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value;
+ NS_ASSERTION((uidlEntry->status == KEEP) ||
+ (uidlEntry->status == DELETE_CHAR) ||
+ (uidlEntry->status == FETCH_BODY) ||
+ (uidlEntry->status == TOO_BIG), "invalid status");
+ char* tmpBuffer = PR_smprintf("%c %s %d" MSG_LINEBREAK, uidlEntry->status, (char*)
+ uidlEntry->uidl, uidlEntry->dateReceived);
+ PR_ASSERT(tmpBuffer);
+ uint32_t numBytesWritten;
+ file->Write(tmpBuffer, strlen(tmpBuffer), &numBytesWritten);
+ PR_Free(tmpBuffer);
+ return HT_ENUMERATE_NEXT;
+}
+
+static int
+net_pop3_delete_old_msgs_mapper(PLHashEntry* he, int msgindex, void* arg)
+{
+ PRTime cutOffDate = (PRTime) arg;
+ Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value;
+ if (uidlEntry->dateReceived < cutOffDate)
+ uidlEntry->status = DELETE_CHAR; // mark for deletion
+ return HT_ENUMERATE_NEXT;
+}
+
+static void
+net_pop3_write_state(Pop3UidlHost* host, nsIFile *mailDirectory)
+{
+ int32_t len = 0;
+ nsCOMPtr <nsIFile> popState;
+
+ mailDirectory->Clone(getter_AddRefs(popState));
+ if (!popState)
+ return;
+ popState->AppendNative(NS_LITERAL_CSTRING("popstate.dat"));
+
+ nsCOMPtr<nsIOutputStream> fileOutputStream;
+ nsresult rv = MsgNewSafeBufferedFileOutputStream(getter_AddRefs(fileOutputStream), popState, -1, 00600);
+ if (NS_FAILED(rv))
+ return;
+
+ const char tmpBuffer[] =
+ "# POP3 State File" MSG_LINEBREAK
+ "# This is a generated file! Do not edit." MSG_LINEBREAK
+ MSG_LINEBREAK;
+
+ uint32_t numBytesWritten;
+ fileOutputStream->Write(tmpBuffer, strlen(tmpBuffer), &numBytesWritten);
+
+ for (; host && (len >= 0); host = host->next)
+ {
+ if (!hash_empty(host->hash))
+ {
+ fileOutputStream->Write("*", 1, &numBytesWritten);
+ fileOutputStream->Write(host->host, strlen(host->host), &numBytesWritten);
+ fileOutputStream->Write(" ", 1, &numBytesWritten);
+ fileOutputStream->Write(host->user, strlen(host->user), &numBytesWritten);
+ fileOutputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &numBytesWritten);
+ PL_HashTableEnumerateEntries(host->hash, net_pop3_write_mapper, (void *)fileOutputStream);
+ }
+ }
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(fileOutputStream);
+ NS_ASSERTION(safeStream, "expected a safe output stream!");
+ if (safeStream) {
+ rv = safeStream->Finish();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to save pop state! possible data loss");
+ }
+ }
+}
+
+static void
+net_pop3_free_state(Pop3UidlHost* host)
+{
+ Pop3UidlHost* h;
+ while (host)
+ {
+ h = host->next;
+ PR_Free(host->host);
+ PR_Free(host->user);
+ PL_HashTableDestroy(host->hash);
+ PR_Free(host);
+ host = h;
+ }
+}
+
+/*
+Look for a specific UIDL string in our hash tables, if we have it then we need
+to mark the message for deletion so that it can be deleted later. If the uidl of the
+message is not found, then the message was downloaded completely and already deleted
+from the server. So this only applies to messages kept on the server or too big
+for download. */
+/* static */
+void nsPop3Protocol::MarkMsgInHashTable(PLHashTable *hashTable, const Pop3UidlEntry *uidlE, bool *changed)
+{
+ if (uidlE->uidl)
+ {
+ Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(hashTable, uidlE->uidl);
+ if (uidlEntry)
+ {
+ if (uidlEntry->status != uidlE->status)
+ {
+ uidlEntry->status = uidlE->status;
+ *changed = true;
+ }
+ }
+ }
+}
+
+/* static */
+nsresult
+nsPop3Protocol::MarkMsgForHost(const char *hostName, const char *userName,
+ nsIFile *mailDirectory,
+ nsTArray<Pop3UidlEntry*> &UIDLArray)
+{
+ if (!hostName || !userName || !mailDirectory)
+ return NS_ERROR_NULL_POINTER;
+
+ Pop3UidlHost *uidlHost = net_pop3_load_state(hostName, userName, mailDirectory);
+ if (!uidlHost)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ bool changed = false;
+
+ uint32_t count = UIDLArray.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ MarkMsgInHashTable(uidlHost->hash, UIDLArray[i], &changed);
+ }
+
+ if (changed)
+ net_pop3_write_state(uidlHost, mailDirectory);
+ net_pop3_free_state(uidlHost);
+ return NS_OK;
+}
+
+
+
+NS_IMPL_ADDREF_INHERITED(nsPop3Protocol, nsMsgProtocol)
+NS_IMPL_RELEASE_INHERITED(nsPop3Protocol, nsMsgProtocol)
+
+
+
+NS_INTERFACE_MAP_BEGIN(nsPop3Protocol)
+ NS_INTERFACE_MAP_ENTRY(nsIPop3Protocol)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol)
+
+// nsPop3Protocol class implementation
+
+nsPop3Protocol::nsPop3Protocol(nsIURI* aURL)
+: nsMsgProtocol(aURL),
+ m_bytesInMsgReceived(0),
+ m_totalFolderSize(0),
+ m_totalDownloadSize(0),
+ m_totalBytesReceived(0),
+ m_pop3ConData(nullptr)
+{
+}
+
+nsresult nsPop3Protocol::Initialize(nsIURI * aURL)
+{
+ nsresult rv = NS_OK;
+ if (!POP3LOGMODULE)
+ POP3LOGMODULE = PR_NewLogModule("POP3");
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Initialize()")));
+
+ m_pop3ConData = (Pop3ConData *)PR_NEWZAP(Pop3ConData);
+ if(!m_pop3ConData)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ m_totalBytesReceived = 0;
+ m_bytesInMsgReceived = 0;
+ m_totalFolderSize = 0;
+ m_totalDownloadSize = 0;
+ m_totalBytesReceived = 0;
+ m_tlsEnabled = false;
+ m_socketType = nsMsgSocketType::trySTARTTLS;
+ m_prefAuthMethods = POP3_AUTH_MECH_UNDEFINED;
+ m_failedAuthMethods = 0;
+ m_password_already_sent = false;
+ m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED;
+ m_needToRerunUrl = false;
+
+ if (aURL)
+ {
+ // extract out message feedback if there is any.
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ mailnewsUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_TRUE(server, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ rv = server->GetSocketType(&m_socketType);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t authMethod = 0;
+ rv = server->GetAuthMethod(&authMethod);
+ NS_ENSURE_SUCCESS(rv,rv);
+ InitPrefAuthMethods(authMethod);
+
+ m_pop3Server = do_QueryInterface(server);
+ if (m_pop3Server)
+ m_pop3Server->GetPop3CapabilityFlags(&m_pop3ConData->capability_flags);
+ }
+
+ m_url = do_QueryInterface(aURL);
+
+ // When we are making a secure connection, we need to make sure that we
+ // pass an interface requestor down to the socket transport so that PSM can
+ // retrieve a nsIPrompt instance if needed.
+ nsCOMPtr<nsIInterfaceRequestor> ir;
+ if (m_socketType != nsMsgSocketType::plain)
+ {
+ nsCOMPtr<nsIMsgWindow> msgwin;
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(msgwin));
+ if (!msgwin)
+ GetTopmostMsgWindow(getter_AddRefs(msgwin));
+ if (msgwin)
+ {
+ nsCOMPtr<nsIDocShell> docshell;
+ msgwin->GetRootDocShell(getter_AddRefs(docshell));
+ ir = do_QueryInterface(docshell);
+ nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
+ msgwin->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks));
+ if (notificationCallbacks)
+ {
+ nsCOMPtr<nsIInterfaceRequestor> aggregrateIR;
+ MsgNewInterfaceRequestorAggregation(notificationCallbacks, ir, getter_AddRefs(aggregrateIR));
+ ir = aggregrateIR;
+ }
+ }
+ }
+
+ int32_t port = 0;
+ nsCString hostName;
+ aURL->GetPort(&port);
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ if (server)
+ server->GetRealHostName(hostName);
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ rv = MsgExamineForProxy(this, getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) proxyInfo = nullptr;
+
+ const char *connectionType = nullptr;
+ if (m_socketType == nsMsgSocketType::SSL)
+ connectionType = "ssl";
+ else if (m_socketType == nsMsgSocketType::trySTARTTLS ||
+ m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ connectionType = "starttls";
+
+ rv = OpenNetworkSocketWithInfo(hostName.get(), port, connectionType, proxyInfo, ir);
+ if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS)
+ {
+ m_socketType = nsMsgSocketType::plain;
+ rv = OpenNetworkSocketWithInfo(hostName.get(), port, nullptr, proxyInfo, ir);
+ }
+
+ if(NS_FAILED(rv))
+ return rv;
+ } // if we got a url...
+
+ m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true);
+ if(!m_lineStreamBuffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ return bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(mLocalBundle));
+}
+
+nsPop3Protocol::~nsPop3Protocol()
+{
+ Cleanup();
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("~nsPop3Protocol()")));
+}
+
+void nsPop3Protocol::Cleanup()
+{
+ if (m_pop3ConData->newuidl)
+ {
+ PL_HashTableDestroy(m_pop3ConData->newuidl);
+ m_pop3ConData->newuidl = nullptr;
+ }
+
+ net_pop3_free_state(m_pop3ConData->uidlinfo);
+
+ FreeMsgInfo();
+ PR_Free(m_pop3ConData->only_uidl);
+ PR_Free(m_pop3ConData);
+}
+
+void nsPop3Protocol::SetCapFlag(uint32_t flag)
+{
+ m_pop3ConData->capability_flags |= flag;
+}
+
+void nsPop3Protocol::ClearCapFlag(uint32_t flag)
+{
+ m_pop3ConData->capability_flags &= ~flag;
+}
+
+bool nsPop3Protocol::TestCapFlag(uint32_t flag)
+{
+ return m_pop3ConData->capability_flags & flag;
+}
+
+uint32_t nsPop3Protocol::GetCapFlags()
+{
+ return m_pop3ConData->capability_flags;
+}
+
+nsresult nsPop3Protocol::FormatCounterString(const nsString &stringName,
+ uint32_t count1,
+ uint32_t count2,
+ nsString &resultString)
+{
+ nsAutoString count1String;
+ count1String.AppendInt(count1);
+
+ nsAutoString count2String;
+ count2String.AppendInt(count2);
+
+ const char16_t *formatStrings[] = {
+ count1String.get(),
+ count2String.get()
+ };
+
+ return mLocalBundle->FormatStringFromName(stringName.get(),
+ formatStrings, 2,
+ getter_Copies(resultString));
+}
+
+void nsPop3Protocol::UpdateStatus(const char16_t *aStatusName)
+{
+ nsString statusMessage;
+ mLocalBundle->GetStringFromName(aStatusName,
+ getter_Copies(statusMessage));
+ UpdateStatusWithString(statusMessage.get());
+}
+
+void nsPop3Protocol::UpdateStatusWithString(const char16_t *aStatusString)
+{
+ if (mProgressEventSink)
+ {
+ mozilla::DebugOnly<nsresult> rv =
+ mProgressEventSink->OnStatus(this, m_channelContext,
+ NS_OK, aStatusString); // XXX i18n message
+ NS_ASSERTION(NS_SUCCEEDED(rv), "dropping error result");
+ }
+}
+
+void nsPop3Protocol::UpdateProgressPercent(int64_t totalDone, int64_t total)
+{
+ if (mProgressEventSink)
+ mProgressEventSink->OnProgress(this, m_channelContext, totalDone, total);
+}
+
+// note: SetUsername() expects an unescaped string
+// do not pass in an escaped string
+void nsPop3Protocol::SetUsername(const char* name)
+{
+ NS_ASSERTION(name, "no name specified!");
+ if (name)
+ m_username = name;
+}
+
+nsresult nsPop3Protocol::RerunUrl()
+{
+ nsCOMPtr<nsIURI> url = do_QueryInterface(m_url);
+ ClearFlag(POP3_PASSWORD_FAILED);
+ m_pop3Server->SetRunningProtocol(nullptr);
+ Cleanup();
+ return LoadUrl(url, nullptr);
+}
+
+Pop3StatesEnum nsPop3Protocol::GetNextPasswordObtainState()
+{
+ switch (m_pop3ConData->next_state)
+ {
+ case POP3_OBTAIN_PASSWORD_EARLY:
+ return POP3_FINISH_OBTAIN_PASSWORD_EARLY;
+ case POP3_SEND_USERNAME:
+ case POP3_OBTAIN_PASSWORD_BEFORE_USERNAME:
+ return POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME;
+ case POP3_SEND_PASSWORD:
+ case POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD:
+ return POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD;
+ default:
+ // Should never get here.
+ NS_NOTREACHED("Invalid next_state in GetNextPasswordObtainState");
+ }
+ return POP3_ERROR_DONE;
+}
+
+nsresult nsPop3Protocol::StartGetAsyncPassword(Pop3StatesEnum aNextState)
+{
+ nsresult rv;
+
+ // Try and avoid going async if possible - if we haven't got into a password
+ // failure state and the server has a password stored for this session, then
+ // use it.
+ if (!TestFlag(POP3_PASSWORD_FAILED))
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server =
+ do_QueryInterface(m_pop3Server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = server->GetPassword(m_passwordResult);
+ if (NS_SUCCEEDED(rv) && !m_passwordResult.IsEmpty())
+ {
+ m_pop3ConData->next_state = GetNextPasswordObtainState();
+ return NS_OK;
+ }
+ }
+
+ // We're now going to need to do something that will end up with us either
+ // poking the login manger or prompting the user. We need to ensure we only
+ // do one prompt at a time (and loging manager could cause a master password
+ // prompt), so we need to use the async prompter.
+ nsCOMPtr<nsIMsgAsyncPrompter> asyncPrompter =
+ do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_pop3ConData->next_state = aNextState;
+
+ // Although we're not actually pausing for a read, we'll do so anyway to let
+ // the async prompt run. Once it is our turn again we'll call back into
+ // ProcessProtocolState.
+ m_pop3ConData->pause_for_read = true;
+
+ nsCString server("unknown");
+ m_url->GetPrePath(server);
+
+ rv = asyncPrompter->QueueAsyncAuthPrompt(server, false, this);
+ // Explict NS_ENSURE_SUCCESS for debug purposes as errors tend to get
+ // hidden.
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsPop3Protocol::OnPromptStart(bool *aResult)
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("OnPromptStart()")));
+
+ *aResult = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString passwordResult;
+
+ // pass the failed password into the password prompt so that
+ // it will be pre-filled, in case it failed because of a
+ // server problem and not because it was wrong.
+ if (!m_lastPasswordSent.IsEmpty())
+ passwordResult = m_lastPasswordSent;
+
+ // Set up some items that we're going to need for the prompting.
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ if (mailnewsUrl)
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+
+ nsCString userName;
+ server->GetRealUsername(userName);
+
+ nsCString hostName;
+ server->GetRealHostName(hostName);
+
+ nsString passwordPrompt;
+ NS_ConvertUTF8toUTF16 userNameUTF16(userName);
+ NS_ConvertUTF8toUTF16 hostNameUTF16(hostName);
+ const char16_t* passwordParams[] = { userNameUTF16.get(),
+ hostNameUTF16.get() };
+
+ // if the last prompt got us a bad password then show a special dialog
+ if (TestFlag(POP3_PASSWORD_FAILED))
+ {
+ // Biff case (no msgWindow) shouldn't cause prompts or passwords to get forgotten at all
+ // TODO shouldn't we skip the new password prompt below as well for biff? Just exit here?
+ if (msgWindow)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Warning,
+ (POP3LOG("POP: ask user what to do (after password failed): new password, retry or cancel")));
+
+ int32_t buttonPressed = 0;
+ if (NS_SUCCEEDED(MsgPromptLoginFailed(msgWindow, hostName,
+ &buttonPressed)))
+ {
+ if (buttonPressed == 1) // Cancel button
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, (POP3LOG("cancel button pressed")));
+ // Abort quickly and stop trying for now.
+
+ // If we haven't actually connected yet (i.e. we're doing an early
+ // attempt to get the username/password but we've previously failed
+ // for some reason), then skip straight to POP3_FREE as it isn't an
+ // error in this connection, and just ends up with us closing the
+ // socket and saying we've aborted the bind. Otherwise, pretend this
+ // is an error and move on.
+ m_pop3ConData->next_state =
+ m_pop3ConData->next_state == POP3_OBTAIN_PASSWORD_EARLY ?
+ POP3_FREE : POP3_ERROR_DONE;
+
+ // Clear the password we're going to return to force failure in
+ // the get mail instance.
+ passwordResult.Truncate();
+
+ // We also have to clear the password failed flag, otherwise we'll
+ // automatically try again.
+ ClearFlag(POP3_PASSWORD_FAILED);
+
+ // As we're async, calling ProcessProtocolState gets things going
+ // again.
+ ProcessProtocolState(nullptr, nullptr, 0, 0);
+ return NS_OK;
+ }
+ else if (buttonPressed == 2) // "New password" button
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, (POP3LOG("new password button pressed")));
+ // Forget the stored password
+ // and we'll prompt for a new one next time around.
+ rv = server->ForgetPassword();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // try all methods again with new password
+ ResetAuthMethods();
+ // ... apart from GSSAPI, which doesn't care about passwords
+ MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI);
+ if (m_needToRerunUrl)
+ return RerunUrl();
+ }
+ else if (buttonPressed == 0) // "Retry" button
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, (POP3LOG("retry button pressed")));
+ // try all methods again, including GSSAPI
+ ResetAuthMethods();
+ ClearFlag(POP3_PASSWORD_FAILED|POP3_AUTH_FAILURE);
+
+ if (m_needToRerunUrl)
+ return RerunUrl();
+
+ // It is a bit strange that we're going onto the next state that
+ // would essentially send the password. However in resetting the
+ // auth methods above, we're setting up SendUsername, SendPassword
+ // and friends to abort and return to the POP3_SEND_CAPA state.
+ // Hence we can do this safely.
+ m_pop3ConData->next_state = GetNextPasswordObtainState();
+ // As we're async, calling ProcessProtocolState gets things going
+ // again.
+ ProcessProtocolState(nullptr, nullptr, 0, 0);
+ return NS_OK;
+ }
+ }
+ }
+ mLocalBundle->FormatStringFromName(
+ u"pop3PreviouslyEnteredPasswordIsInvalidPrompt",
+ passwordParams, 2, getter_Copies(passwordPrompt));
+ }
+ else
+ // Otherwise this is the first time we've asked about the server's
+ // password so show a first time prompt.
+ mLocalBundle->FormatStringFromName(
+ u"pop3EnterPasswordPrompt",
+ passwordParams, 2, getter_Copies(passwordPrompt));
+
+ nsString passwordTitle;
+ mLocalBundle->GetStringFromName(
+ u"pop3EnterPasswordPromptTitle",
+ getter_Copies(passwordTitle));
+
+ // Now go and get the password.
+ if (!passwordPrompt.IsEmpty() && !passwordTitle.IsEmpty())
+ rv = server->GetPasswordWithUI(passwordPrompt, passwordTitle,
+ msgWindow, passwordResult);
+ ClearFlag(POP3_PASSWORD_FAILED|POP3_AUTH_FAILURE);
+
+ // If it failed or the user cancelled the prompt, just abort the
+ // connection.
+ if (NS_FAILED(rv) ||
+ rv == NS_MSG_PASSWORD_PROMPT_CANCELLED)
+ {
+ m_pop3ConData->next_state = POP3_ERROR_DONE;
+ m_passwordResult.Truncate();
+ *aResult = false;
+ }
+ else
+ {
+ m_passwordResult = passwordResult;
+ m_pop3ConData->next_state = GetNextPasswordObtainState();
+ *aResult = true;
+ }
+ // Because this was done asynchronously, now call back into
+ // ProcessProtocolState to get the protocol going again.
+ ProcessProtocolState(nullptr, nullptr, 0, 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Protocol::OnPromptAuthAvailable()
+{
+ NS_NOTREACHED("Did not expect to get POP3 protocol queuing up auth "
+ "connections for same server");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Protocol::OnPromptCanceled()
+{
+ // A prompt was cancelled, so just abort out the connection
+ m_pop3ConData->next_state = POP3_ERROR_DONE;
+ // As we're async, calling ProcessProtocolState gets things going again.
+ ProcessProtocolState(nullptr, nullptr, 0, 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Protocol::OnTransportStatus(nsITransport *aTransport, nsresult aStatus, int64_t aProgress, int64_t aProgressMax)
+{
+ return nsMsgProtocol::OnTransportStatus(aTransport, aStatus, aProgress, aProgressMax);
+}
+
+// stop binding is a "notification" informing us that the stream associated with aURL is going away.
+NS_IMETHODIMP nsPop3Protocol::OnStopRequest(nsIRequest *aRequest, nsISupports * aContext, nsresult aStatus)
+{
+ // If the server dropped the connection, m_socketIsOpen will be true, before
+ // we call nsMsgProtocol::OnStopRequest. The call will force a close socket,
+ // but we still want to go through the state machine one more time to cleanup
+ // the protocol object.
+ if (m_socketIsOpen)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(m_url);
+
+ // Check if the connection was dropped before getting back an auth error.
+ // If we got the auth error, the next state would be
+ // POP3_OBTAIN_PASSWORD_EARLY.
+ if ((m_pop3ConData->next_state_after_response == POP3_NEXT_AUTH_STEP ||
+ m_pop3ConData->next_state_after_response == POP3_AUTH_LOGIN_RESPONSE) &&
+ m_pop3ConData->next_state != POP3_OBTAIN_PASSWORD_EARLY)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("dropped connection before auth error")));
+ SetFlag(POP3_AUTH_FAILURE);
+ m_pop3ConData->command_succeeded = false;
+ m_needToRerunUrl = true;
+ m_pop3ConData->next_state = POP3_NEXT_AUTH_STEP;
+ ProcessProtocolState(nullptr, nullptr, 0, 0);
+ }
+ // We can't call nsMsgProtocol::OnStopRequest because it calls SetUrlState,
+ // which notifies the URLListeners, but we need to do a bit of cleanup
+ // before running the url again.
+ CloseSocket();
+ if (m_loadGroup)
+ m_loadGroup->RemoveRequest(static_cast<nsIRequest *>(this), nullptr, aStatus);
+ m_pop3ConData->next_state = POP3_ERROR_DONE;
+ ProcessProtocolState(nullptr, nullptr, 0, 0);
+
+ if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED)
+ nsMsgProtocol::ShowAlertMessage(msgUrl, aStatus);
+
+ return NS_OK;
+ }
+ nsresult rv = nsMsgProtocol::OnStopRequest(aRequest, aContext, aStatus);
+
+ // turn off the server busy flag on stop request - we know we're done, right?
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ if (server)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("Clearing server busy in nsPop3Protocol::OnStopRequest")));
+ server->SetServerBusy(false); // the server is not busy
+ }
+ if(m_pop3ConData->list_done)
+ CommitState(true);
+ if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED)
+ Abort();
+ return rv;
+}
+
+void nsPop3Protocol::Abort()
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Abort")));
+
+ if(m_pop3ConData->msg_closure)
+ {
+ m_nsIPop3Sink->IncorporateAbort(m_pop3ConData->only_uidl != nullptr);
+ m_pop3ConData->msg_closure = nullptr;
+ }
+ // need this to close the stream on the inbox.
+ m_nsIPop3Sink->AbortMailDelivery(this);
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("Clearing running protocol in nsPop3Protocol::Abort()")));
+ m_pop3Server->SetRunningProtocol(nullptr);
+}
+
+NS_IMETHODIMP nsPop3Protocol::Cancel(nsresult status) // handle stop button
+{
+ Abort();
+ return nsMsgProtocol::Cancel(NS_BINDING_ABORTED);
+}
+
+
+nsresult nsPop3Protocol::LoadUrl(nsIURI* aURL, nsISupports * /* aConsumer */)
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("LoadUrl()")));
+
+ nsresult rv = Initialize(aURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aURL)
+ m_url = do_QueryInterface(aURL);
+ else
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURL, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t port;
+ rv = url->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_CheckPortSafety(port, "pop");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString queryPart;
+ rv = url->GetQuery(queryPart);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get the url spect");
+
+ m_pop3ConData->only_check_for_new_mail = (PL_strcasestr(queryPart.get(), "check") != nullptr);
+ m_pop3ConData->verify_logon = (PL_strcasestr(queryPart.get(), "verifyLogon") != nullptr);
+ m_pop3ConData->get_url = (PL_strcasestr(queryPart.get(), "gurl") != nullptr);
+
+ bool deleteByAgeFromServer = false;
+ int32_t numDaysToLeaveOnServer = -1;
+ if (!m_pop3ConData->verify_logon)
+ {
+ // Pick up pref setting regarding leave messages on server, message size limit
+
+ m_pop3Server->GetLeaveMessagesOnServer(&m_pop3ConData->leave_on_server);
+ m_pop3Server->GetHeadersOnly(&m_pop3ConData->headers_only);
+ bool limitMessageSize = false;
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ if (server)
+ {
+ // size limits are superseded by headers_only mode
+ if (!m_pop3ConData->headers_only)
+ {
+ server->GetLimitOfflineMessageSize(&limitMessageSize);
+ if (limitMessageSize)
+ {
+ int32_t max_size = 0; // default size
+ server->GetMaxMessageSize(&max_size);
+ m_pop3ConData->size_limit = (max_size) ? max_size * 1024 : 50 * 1024;
+ }
+ }
+ m_pop3Server->GetDeleteByAgeFromServer(&deleteByAgeFromServer);
+ if (deleteByAgeFromServer)
+ m_pop3Server->GetNumDaysToLeaveOnServer(&numDaysToLeaveOnServer);
+ }
+ }
+
+ // UIDL stuff
+ nsCOMPtr<nsIPop3URL> pop3Url = do_QueryInterface(m_url);
+ if (pop3Url)
+ pop3Url->GetPop3Sink(getter_AddRefs(m_nsIPop3Sink));
+
+ nsCOMPtr<nsIFile> mailDirectory;
+
+ nsCString hostName;
+ nsCString userName;
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ if (server)
+ {
+ rv = server->GetLocalPath(getter_AddRefs(mailDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ server->SetServerBusy(true); // the server is now busy
+ server->GetHostName(hostName);
+ server->GetUsername(userName);
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
+ (POP3LOG("Connecting to server %s:%d"), hostName.get(), port));
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("Setting server busy in nsPop3Protocol::LoadUrl()")));
+ }
+
+ if (!m_pop3ConData->verify_logon)
+ m_pop3ConData->uidlinfo = net_pop3_load_state(hostName.get(), userName.get(), mailDirectory);
+
+ m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_NoMail;
+
+ if (m_pop3ConData->uidlinfo && numDaysToLeaveOnServer > 0)
+ {
+ uint32_t nowInSeconds = TimeInSecondsFromPRTime(PR_Now());
+ uint32_t cutOffDay = nowInSeconds - (60 * 60 * 24 * numDaysToLeaveOnServer);
+
+ PL_HashTableEnumerateEntries(m_pop3ConData->uidlinfo->hash, net_pop3_delete_old_msgs_mapper, (void *)(uintptr_t) cutOffDay);
+ }
+ const char* uidl = PL_strcasestr(queryPart.get(), "uidl=");
+ PR_FREEIF(m_pop3ConData->only_uidl);
+
+ if (uidl)
+ {
+ uidl += 5;
+ nsCString unescapedData;
+ MsgUnescapeString(nsDependentCString(uidl), 0, unescapedData);
+ m_pop3ConData->only_uidl = PL_strdup(unescapedData.get());
+
+ mSuppressListenerNotifications = true; // suppress on start and on stop because this url won't have any content to display
+ }
+
+ m_pop3ConData->next_state = POP3_START_CONNECT;
+ m_pop3ConData->next_state_after_response = POP3_FINISH_CONNECT;
+ if (NS_SUCCEEDED(rv))
+ {
+ m_pop3Server->SetRunningProtocol(this);
+ return nsMsgProtocol::LoadUrl(aURL);
+ }
+ else
+ return rv;
+}
+
+void
+nsPop3Protocol::FreeMsgInfo()
+{
+ int i;
+ if (m_pop3ConData->msg_info)
+ {
+ for (i=0 ; i<m_pop3ConData->number_of_messages ; i++)
+ {
+ if (m_pop3ConData->msg_info[i].uidl)
+ PR_Free(m_pop3ConData->msg_info[i].uidl);
+ m_pop3ConData->msg_info[i].uidl = nullptr;
+ }
+ PR_Free(m_pop3ConData->msg_info);
+ m_pop3ConData->msg_info = nullptr;
+ }
+}
+
+int32_t
+nsPop3Protocol::WaitForStartOfConnectionResponse(nsIInputStream* aInputStream,
+ uint32_t length)
+{
+ char * line = nullptr;
+ uint32_t line_length = 0;
+ bool pauseForMoreData = false;
+ nsresult rv;
+ line = m_lineStreamBuffer->ReadNextLine(aInputStream, line_length, pauseForMoreData, &rv);
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
+ if (NS_FAILED(rv))
+ return -1;
+
+ if(pauseForMoreData || !line)
+ {
+ m_pop3ConData->pause_for_read = true; /* pause */
+ PR_Free(line);
+ return(line_length);
+ }
+
+ if(*line == '+')
+ {
+ m_pop3ConData->command_succeeded = true;
+ if(PL_strlen(line) > 4)
+ m_commandResponse = line + 4;
+ else
+ m_commandResponse = line;
+
+ if (m_prefAuthMethods & POP3_HAS_AUTH_APOP)
+ {
+ if (NS_SUCCEEDED(GetApopTimestamp()))
+ SetCapFlag(POP3_HAS_AUTH_APOP);
+ }
+ else
+ ClearCapFlag(POP3_HAS_AUTH_APOP);
+
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+
+ m_pop3ConData->next_state = POP3_PROCESS_AUTH;
+ m_pop3ConData->pause_for_read = false; /* don't pause */
+ }
+
+ PR_Free(line);
+ return(1); /* everything ok */
+}
+
+int32_t
+nsPop3Protocol::WaitForResponse(nsIInputStream* inputStream, uint32_t length)
+{
+ char * line;
+ uint32_t ln = 0;
+ bool pauseForMoreData = false;
+ nsresult rv;
+ line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
+ if (NS_FAILED(rv))
+ return -1;
+
+ if(pauseForMoreData || !line)
+ {
+ m_pop3ConData->pause_for_read = true; /* pause */
+
+ PR_Free(line);
+ return(ln);
+ }
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
+
+ if(*line == '+')
+ {
+ m_pop3ConData->command_succeeded = true;
+ if(PL_strlen(line) > 4)
+ {
+ if(!PL_strncasecmp(line, "+OK", 3))
+ m_commandResponse = line + 4;
+ else // challenge answer to AUTH CRAM-MD5 and LOGIN username/password
+ m_commandResponse = line + 2;
+ }
+ else
+ m_commandResponse = line;
+ }
+ else
+ {
+ m_pop3ConData->command_succeeded = false;
+ if(PL_strlen(line) > 5)
+ m_commandResponse = line + 5;
+ else
+ m_commandResponse = line;
+
+ // search for the response codes (RFC 2449, chapter 8 and RFC 3206)
+ if(TestCapFlag(POP3_HAS_RESP_CODES | POP3_HAS_AUTH_RESP_CODE))
+ {
+ // code for authentication failure due to the user's credentials
+ if(m_commandResponse.Find("[AUTH", true) >= 0)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("setting auth failure")));
+ SetFlag(POP3_AUTH_FAILURE);
+ }
+
+ // codes for failures due to other reasons
+ if(m_commandResponse.Find("[LOGIN-DELAY", true) >= 0 ||
+ m_commandResponse.Find("[IN-USE", true) >= 0 ||
+ m_commandResponse.Find("[SYS", true) >= 0)
+ SetFlag(POP3_STOPLOGIN);
+
+ // remove the codes from the response string presented to the user
+ int32_t i = m_commandResponse.FindChar(']');
+ if(i >= 0)
+ m_commandResponse.Cut(0, i + 2);
+ }
+ }
+
+ m_pop3ConData->next_state = m_pop3ConData->next_state_after_response;
+ m_pop3ConData->pause_for_read = false; /* don't pause */
+
+ PR_Free(line);
+ return(1); /* everything ok */
+}
+
+int32_t
+nsPop3Protocol::Error(const char* err_code,
+ const char16_t **params,
+ uint32_t length)
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("ERROR: %s"), err_code));
+
+ // the error code is just the resource name for the error string...
+ // so print out that error message!
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ nsString accountName;
+ nsresult rv = server->GetPrettyName(accountName);
+ NS_ENSURE_SUCCESS(rv, -1);
+ const char16_t *titleParams[] = { accountName.get() };
+ nsString dialogTitle;
+ mLocalBundle->FormatStringFromName(
+ u"pop3ErrorDialogTitle",
+ titleParams, 1, getter_Copies(dialogTitle));
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+ // we handle "pop3TmpDownloadError" earlier...
+ if (strcmp(err_code, "pop3TmpDownloadError") && NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIPrompt> dialog;
+ rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); //it is ok to have null msgWindow, for example when biffing
+ if (NS_SUCCEEDED(rv) && msgWindow)
+ {
+ rv = msgWindow->GetPromptDialog(getter_AddRefs(dialog));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString alertString;
+ // Format the alert string if parameter list isn't empty
+ if (params)
+ mLocalBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(err_code).get(),
+ params, length, getter_Copies(alertString));
+ else
+ mLocalBundle->GetStringFromName(NS_ConvertASCIItoUTF16(err_code).get(),
+ getter_Copies(alertString));
+ if (m_pop3ConData->command_succeeded) //not a server error message
+ dialog->Alert(dialogTitle.get(), alertString.get());
+ else
+ {
+ nsString serverSaidPrefix;
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ nsCString hostName;
+ // Fomat string with hostname.
+ if (server)
+ rv = server->GetRealHostName(hostName);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoString hostStr;
+ CopyASCIItoUTF16(hostName, hostStr);
+ const char16_t *params[] = { hostStr.get() };
+ mLocalBundle->FormatStringFromName(
+ u"pop3ServerSaid",
+ params, 1, getter_Copies(serverSaidPrefix));
+ }
+
+ nsAutoString message(alertString);
+ message.AppendLiteral(" ");
+ message.Append(serverSaidPrefix);
+ message.AppendLiteral(" ");
+ message.Append(NS_ConvertASCIItoUTF16(m_commandResponse));
+ dialog->Alert(dialogTitle.get(), message.get());
+ }
+ }
+ }
+ }
+ m_pop3ConData->next_state = POP3_ERROR_DONE;
+ m_pop3ConData->pause_for_read = false;
+ return -1;
+}
+
+int32_t nsPop3Protocol::Pop3SendData(const char * dataBuffer, bool aSuppressLogging)
+{
+ // remove any leftover bytes in the line buffer
+ // this can happen if the last message line doesn't end with a (CR)LF
+ // or a server sent two reply lines
+ m_lineStreamBuffer->ClearBuffer();
+
+ nsresult result = nsMsgProtocol::SendData(dataBuffer);
+
+ if (!aSuppressLogging)
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("SEND: %s"), dataBuffer));
+ else
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
+ (POP3LOG("Logging suppressed for this command (it probably contained authentication information)")));
+
+ if (NS_SUCCEEDED(result))
+ {
+ m_pop3ConData->pause_for_read = true;
+ m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE;
+ return 0;
+ }
+
+ m_pop3ConData->next_state = POP3_ERROR_DONE;
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("Pop3SendData failed: %lx"), result));
+ return -1;
+}
+
+/*
+ * POP3 AUTH extension
+ */
+
+int32_t nsPop3Protocol::SendAuth()
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendAuth()")));
+
+ if(!m_pop3ConData->command_succeeded)
+ return Error("pop3ServerError");
+
+ nsAutoCString command("AUTH" CRLF);
+
+ m_pop3ConData->next_state_after_response = POP3_AUTH_RESPONSE;
+ return Pop3SendData(command.get());
+}
+
+int32_t nsPop3Protocol::AuthResponse(nsIInputStream* inputStream,
+ uint32_t length)
+{
+ char * line;
+ uint32_t ln = 0;
+ nsresult rv;
+
+ if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED))
+ {
+ ClearCapFlag(POP3_AUTH_MECH_UNDEFINED);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+
+ if (!m_pop3ConData->command_succeeded)
+ {
+ /* AUTH command not implemented
+ * so no secure mechanisms available
+ */
+ m_pop3ConData->command_succeeded = true;
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ m_pop3ConData->next_state = POP3_SEND_CAPA;
+ return 0;
+ }
+
+ bool pauseForMoreData = false;
+ line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
+ if (NS_FAILED(rv))
+ return -1;
+
+ if(pauseForMoreData || !line)
+ {
+ m_pop3ConData->pause_for_read = true; /* pause */
+ PR_Free(line);
+ return(0);
+ }
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
+
+ if (!PL_strcmp(line, "."))
+ {
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+
+ // now that we've read all the AUTH responses, go for it
+ m_pop3ConData->next_state = POP3_SEND_CAPA;
+ m_pop3ConData->pause_for_read = false; /* don't pause */
+ }
+ else if (!PL_strcasecmp (line, "CRAM-MD5"))
+ SetCapFlag(POP3_HAS_AUTH_CRAM_MD5);
+ else if (!PL_strcasecmp (line, "NTLM"))
+ SetCapFlag(POP3_HAS_AUTH_NTLM);
+ else if (!PL_strcasecmp (line, "MSN"))
+ SetCapFlag(POP3_HAS_AUTH_NTLM|POP3_HAS_AUTH_MSN);
+ else if (!PL_strcasecmp (line, "GSSAPI"))
+ SetCapFlag(POP3_HAS_AUTH_GSSAPI);
+ else if (!PL_strcasecmp (line, "PLAIN"))
+ SetCapFlag(POP3_HAS_AUTH_PLAIN);
+ else if (!PL_strcasecmp (line, "LOGIN"))
+ SetCapFlag(POP3_HAS_AUTH_LOGIN);
+
+ PR_Free(line);
+ return 0;
+}
+
+/*
+ * POP3 CAPA extension, see RFC 2449, chapter 5
+ */
+
+int32_t nsPop3Protocol::SendCapa()
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendCapa()")));
+ if(!m_pop3ConData->command_succeeded)
+ return Error("pop3ServerError");
+
+ nsAutoCString command("CAPA" CRLF);
+
+ m_pop3ConData->next_state_after_response = POP3_CAPA_RESPONSE;
+ return Pop3SendData(command.get());
+}
+
+int32_t nsPop3Protocol::CapaResponse(nsIInputStream* inputStream,
+ uint32_t length)
+{
+ char * line;
+ uint32_t ln = 0;
+
+ if (!m_pop3ConData->command_succeeded)
+ {
+ /* CAPA command not implemented */
+ m_pop3ConData->command_succeeded = true;
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ m_pop3ConData->next_state = POP3_PROCESS_AUTH;
+ return 0;
+ }
+
+ bool pauseForMoreData = false;
+ nsresult rv;
+ line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
+ if (NS_FAILED(rv))
+ return -1;
+
+ if(pauseForMoreData || !line)
+ {
+ m_pop3ConData->pause_for_read = true; /* pause */
+ PR_Free(line);
+ return(0);
+ }
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
+
+ if (!PL_strcmp(line, "."))
+ {
+ // now that we've read all the CAPA responses, go for it
+ m_pop3ConData->next_state = POP3_PROCESS_AUTH;
+ m_pop3ConData->pause_for_read = false; /* don't pause */
+ }
+ else
+ if (!PL_strcasecmp(line, "XSENDER"))
+ {
+ SetCapFlag(POP3_HAS_XSENDER);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+ else
+ // see RFC 2449, chapter 6.4
+ if (!PL_strcasecmp(line, "RESP-CODES"))
+ {
+ SetCapFlag(POP3_HAS_RESP_CODES);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+ else
+ // see RFC 3206, chapter 6
+ if (!PL_strcasecmp(line, "AUTH-RESP-CODE"))
+ {
+ SetCapFlag(POP3_HAS_AUTH_RESP_CODE);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+ else
+ // see RFC 2595, chapter 4
+ if (!PL_strcasecmp(line, "STLS"))
+ {
+ SetCapFlag(POP3_HAS_STLS);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+ else
+ // see RFC 2449, chapter 6.3
+ if (!PL_strncasecmp(line, "SASL", 4) && strlen(line) > 6)
+ {
+ nsAutoCString responseLine;
+ responseLine.Assign(line + 5);
+
+ if (responseLine.Find("PLAIN", CaseInsensitiveCompare) >= 0)
+ SetCapFlag(POP3_HAS_AUTH_PLAIN);
+
+ if (responseLine.Find("LOGIN", CaseInsensitiveCompare) >= 0)
+ SetCapFlag(POP3_HAS_AUTH_LOGIN);
+
+ if (responseLine.Find("GSSAPI", CaseInsensitiveCompare) >= 0)
+ SetCapFlag(POP3_HAS_AUTH_GSSAPI);
+
+ if (responseLine.Find("CRAM-MD5", CaseInsensitiveCompare) >= 0)
+ SetCapFlag(POP3_HAS_AUTH_CRAM_MD5);
+
+ if (responseLine.Find("NTLM", CaseInsensitiveCompare) >= 0)
+ SetCapFlag(POP3_HAS_AUTH_NTLM);
+
+ if (responseLine.Find("MSN", CaseInsensitiveCompare) >= 0)
+ SetCapFlag(POP3_HAS_AUTH_NTLM|POP3_HAS_AUTH_MSN);
+
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+
+ PR_Free(line);
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Capability entry processed")));
+ return 0;
+}
+
+int32_t nsPop3Protocol::SendTLSResponse()
+{
+ // only tear down our existing connection and open a new one if we received
+ // a +OK response from the pop server after we issued the STLS command
+ nsresult rv = NS_OK;
+ if (m_pop3ConData->command_succeeded)
+ {
+ nsCOMPtr<nsISupports> secInfo;
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport, &rv);
+ if (NS_FAILED(rv))
+ return -1;
+
+ rv = strans->GetSecurityInfo(getter_AddRefs(secInfo));
+
+ if (NS_SUCCEEDED(rv) && secInfo)
+ {
+ nsCOMPtr<nsISSLSocketControl> sslControl = do_QueryInterface(secInfo, &rv);
+
+ if (NS_SUCCEEDED(rv) && sslControl)
+ rv = sslControl->StartTLS();
+ }
+
+ if (NS_SUCCEEDED(rv))
+ {
+ m_pop3ConData->next_state = POP3_SEND_AUTH;
+ m_tlsEnabled = true;
+
+ // certain capabilities like POP3_HAS_AUTH_APOP should be
+ // preserved across the connections.
+ uint32_t preservedCapFlags = m_pop3ConData->capability_flags & POP3_HAS_AUTH_APOP;
+ m_pop3ConData->capability_flags = // resetting the flags
+ POP3_AUTH_MECH_UNDEFINED |
+ POP3_HAS_AUTH_USER | // should be always there
+ POP3_GURL_UNDEFINED |
+ POP3_UIDL_UNDEFINED |
+ POP3_TOP_UNDEFINED |
+ POP3_XTND_XLST_UNDEFINED |
+ preservedCapFlags;
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ return 0;
+ }
+ }
+
+ ClearFlag(POP3_HAS_STLS);
+ m_pop3ConData->next_state = POP3_PROCESS_AUTH;
+
+ return (NS_SUCCEEDED(rv) ? 0 : -1);
+}
+
+void nsPop3Protocol::InitPrefAuthMethods(int32_t authMethodPrefValue)
+{
+ // for m_prefAuthMethods, using the same flags as server capablities.
+ switch (authMethodPrefValue)
+ {
+ case nsMsgAuthMethod::none:
+ m_prefAuthMethods = POP3_HAS_AUTH_NONE;
+ break;
+ case nsMsgAuthMethod::old:
+ m_prefAuthMethods = POP3_HAS_AUTH_USER;
+ break;
+ case nsMsgAuthMethod::passwordCleartext:
+ m_prefAuthMethods = POP3_HAS_AUTH_USER |
+ POP3_HAS_AUTH_LOGIN | POP3_HAS_AUTH_PLAIN;
+ break;
+ case nsMsgAuthMethod::passwordEncrypted:
+ m_prefAuthMethods = POP3_HAS_AUTH_CRAM_MD5 |
+ POP3_HAS_AUTH_APOP;
+ break;
+ case nsMsgAuthMethod::NTLM:
+ m_prefAuthMethods = POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN;
+ break;
+ case nsMsgAuthMethod::GSSAPI:
+ m_prefAuthMethods = POP3_HAS_AUTH_GSSAPI;
+ break;
+ case nsMsgAuthMethod::secure:
+ m_prefAuthMethods = POP3_HAS_AUTH_APOP |
+ POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_GSSAPI |
+ POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN;
+ break;
+ default:
+ NS_ASSERTION(false, "POP: authMethod pref invalid");
+ // TODO log to error console
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
+ (POP3LOG("POP: bad pref authMethod = %d\n"), authMethodPrefValue));
+ // fall to any
+ MOZ_FALLTHROUGH;
+ case nsMsgAuthMethod::anything:
+ m_prefAuthMethods = POP3_HAS_AUTH_USER |
+ POP3_HAS_AUTH_LOGIN | POP3_HAS_AUTH_PLAIN |
+ POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP |
+ POP3_HAS_AUTH_GSSAPI |
+ POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN;
+ // TODO needed?
+ break;
+ }
+ NS_ASSERTION(m_prefAuthMethods != POP3_AUTH_MECH_UNDEFINED,
+ "POP: InitPrefAuthMethods() didn't work");
+}
+
+/**
+ * Changes m_currentAuthMethod to pick the best one
+ * which is allowed by server and prefs and not marked failed.
+ * The order of preference and trying of auth methods is encoded here.
+ */
+nsresult nsPop3Protocol::ChooseAuthMethod()
+{
+ int32_t availCaps = GetCapFlags() & m_prefAuthMethods & ~m_failedAuthMethods;
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("POP auth: server caps 0x%X, pref 0x%X, failed 0x%X, avail caps 0x%X"),
+ GetCapFlags(), m_prefAuthMethods, m_failedAuthMethods, availCaps));
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("(GSSAPI = 0x%X, CRAM = 0x%X, APOP = 0x%X, NTLM = 0x%X, "
+ "MSN = 0x%X, PLAIN = 0x%X, LOGIN = 0x%X, USER/PASS = 0x%X)"),
+ POP3_HAS_AUTH_GSSAPI, POP3_HAS_AUTH_CRAM_MD5, POP3_HAS_AUTH_APOP,
+ POP3_HAS_AUTH_NTLM, POP3_HAS_AUTH_MSN, POP3_HAS_AUTH_PLAIN,
+ POP3_HAS_AUTH_LOGIN, POP3_HAS_AUTH_USER));
+
+ if (POP3_HAS_AUTH_GSSAPI & availCaps)
+ m_currentAuthMethod = POP3_HAS_AUTH_GSSAPI;
+ else if (POP3_HAS_AUTH_CRAM_MD5 & availCaps)
+ m_currentAuthMethod = POP3_HAS_AUTH_CRAM_MD5;
+ else if (POP3_HAS_AUTH_APOP & availCaps)
+ m_currentAuthMethod = POP3_HAS_AUTH_APOP;
+ else if (POP3_HAS_AUTH_NTLM & availCaps)
+ m_currentAuthMethod = POP3_HAS_AUTH_NTLM;
+ else if (POP3_HAS_AUTH_MSN & availCaps)
+ m_currentAuthMethod = POP3_HAS_AUTH_MSN;
+ else if (POP3_HAS_AUTH_PLAIN & availCaps)
+ m_currentAuthMethod = POP3_HAS_AUTH_PLAIN;
+ else if (POP3_HAS_AUTH_LOGIN & availCaps)
+ m_currentAuthMethod = POP3_HAS_AUTH_LOGIN;
+ else if (POP3_HAS_AUTH_USER & availCaps)
+ m_currentAuthMethod = POP3_HAS_AUTH_USER;
+ else
+ {
+ // there are no matching login schemes at all, per server and prefs
+ m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED;
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("no auth method remaining")));
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("trying auth method 0x%X"), m_currentAuthMethod));
+ return NS_OK;
+}
+
+void nsPop3Protocol::MarkAuthMethodAsFailed(int32_t failedAuthMethod)
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("marking auth method 0x%X failed"), failedAuthMethod));
+ m_failedAuthMethods |= failedAuthMethod;
+}
+
+/**
+ * Start over, trying all auth methods again
+ */
+void nsPop3Protocol::ResetAuthMethods()
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("resetting (failed) auth methods")));
+ m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED;
+ m_failedAuthMethods = 0;
+}
+
+/**
+ * state POP3_PROCESS_AUTH
+ * Called when we should try to authenticate to the server.
+ * Also called when one auth method fails and we want to try and start
+ * the next best auth method.
+ */
+int32_t nsPop3Protocol::ProcessAuth()
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("ProcessAuth()")));
+
+ // Try to upgrade to STARTTLS -- TODO move into its own function
+ if (!m_tlsEnabled)
+ {
+ if(TestCapFlag(POP3_HAS_STLS))
+ {
+ if (m_socketType == nsMsgSocketType::trySTARTTLS ||
+ m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ nsAutoCString command("STLS" CRLF);
+
+ m_pop3ConData->next_state_after_response = POP3_TLS_RESPONSE;
+ return Pop3SendData(command.get());
+ }
+ }
+ else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ m_pop3ConData->next_state = POP3_ERROR_DONE;
+ return Error("nsErrorCouldNotConnectViaTls");
+ }
+ }
+
+ m_password_already_sent = false;
+
+ nsresult rv = ChooseAuthMethod();
+ if (NS_FAILED(rv))
+ {
+ // Pref doesn't match server. Now, find an appropriate error msg.
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("ProcessAuth() early exit because no auth methods")));
+
+ // AuthGSSAPI* falls in here in case of an auth failure.
+ // If Kerberos was the only method, assume that
+ // the user is just not logged in yet, and show an appropriate error.
+ if (m_prefAuthMethods == POP3_HAS_AUTH_GSSAPI &&
+ m_failedAuthMethods == POP3_HAS_AUTH_GSSAPI)
+ return Error("pop3GssapiFailure");
+
+ // pref has plaintext pw & server claims to support encrypted pw
+ if (m_prefAuthMethods == (POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN |
+ POP3_HAS_AUTH_PLAIN) &&
+ GetCapFlags() & (POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP))
+ // tell user to change to encrypted pw
+ return Error("pop3AuthChangePlainToEncrypt");
+ // pref has encrypted pw & server claims to support plaintext pw
+ else if (m_prefAuthMethods == (POP3_HAS_AUTH_CRAM_MD5 |
+ POP3_HAS_AUTH_APOP) &&
+ GetCapFlags() & (POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN |
+ POP3_HAS_AUTH_PLAIN))
+ {
+ // have SSL
+ if (m_socketType == nsMsgSocketType::SSL ||
+ m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ // tell user to change to plaintext pw
+ return Error("pop3AuthChangeEncryptToPlainSSL");
+ else
+ // tell user to change to plaintext pw, with big warning
+ return Error("pop3AuthChangeEncryptToPlainNoSSL");
+ }
+ else
+ // just "change auth method"
+ return Error("pop3AuthMechNotSupported");
+ }
+
+ switch (m_currentAuthMethod)
+ {
+ case POP3_HAS_AUTH_GSSAPI:
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP GSSAPI")));
+ m_pop3ConData->next_state = POP3_AUTH_GSSAPI;
+ break;
+ case POP3_HAS_AUTH_APOP:
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP APOP")));
+ m_pop3ConData->next_state = POP3_SEND_PASSWORD;
+ break;
+ case POP3_HAS_AUTH_CRAM_MD5:
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP CRAM")));
+ MOZ_FALLTHROUGH;
+ case POP3_HAS_AUTH_PLAIN:
+ case POP3_HAS_AUTH_USER:
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP username")));
+ m_pop3ConData->next_state = POP3_SEND_USERNAME;
+ break;
+ case POP3_HAS_AUTH_LOGIN:
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP AUTH=LOGIN")));
+ m_pop3ConData->next_state = POP3_AUTH_LOGIN;
+ break;
+ case POP3_HAS_AUTH_NTLM:
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP NTLM")));
+ m_pop3ConData->next_state = POP3_AUTH_NTLM;
+ break;
+ case POP3_HAS_AUTH_NONE:
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP no auth")));
+ m_pop3ConData->command_succeeded = true;
+ m_pop3ConData->next_state = POP3_NEXT_AUTH_STEP;
+ break;
+ default:
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
+ (POP3LOG("POP: m_currentAuthMethod has unknown value")));
+ return Error("pop3AuthMechNotSupported");
+ }
+
+ m_pop3ConData->pause_for_read = false;
+
+ return 0;
+}
+
+/**
+ * state POP3_NEXT_AUTH_STEP
+ * This is called when we finished one auth step (e.g. sending username
+ * or password are separate steps, similarly for AUTH LOGIN, NTLM etc.)
+ * and want to proceed to the next one.
+ */
+int32_t nsPop3Protocol::NextAuthStep()
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("NextAuthStep()")));
+ if (m_pop3ConData->command_succeeded)
+ {
+ if (m_password_already_sent || // (also true for GSSAPI)
+ m_currentAuthMethod == POP3_HAS_AUTH_NONE)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("login succeeded")));
+ m_nsIPop3Sink->SetUserAuthenticated(true);
+ ClearFlag(POP3_PASSWORD_FAILED);
+ if (m_pop3ConData->verify_logon)
+ m_pop3ConData->next_state = POP3_SEND_QUIT;
+ else
+ m_pop3ConData->next_state = (m_pop3ConData->get_url)
+ ? POP3_SEND_GURL : POP3_SEND_STAT;
+ }
+ else
+ m_pop3ConData->next_state = POP3_SEND_PASSWORD;
+ }
+ else
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("command did not succeed")));
+ // response code received shows that login failed not because of
+ // wrong credential -> stop login without retry or pw dialog, only alert
+ // parameter list -> user
+ nsCString userName;
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ nsresult rv = server->GetRealUsername(userName);
+ NS_ENSURE_SUCCESS(rv, -1);
+ NS_ConvertUTF8toUTF16 userNameUTF16(userName);
+ const char16_t* params[] = { userNameUTF16.get() };
+ if (TestFlag(POP3_STOPLOGIN))
+ {
+ if (m_password_already_sent)
+ return Error("pop3PasswordFailed", params, 1);
+
+ return Error("pop3UsernameFailure");
+ }
+ // response code received shows that server is certain about the
+ // credential was wrong -> no fallback, show alert and pw dialog
+ if (TestFlag(POP3_AUTH_FAILURE))
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("auth failure, setting password failed")));
+ if (m_password_already_sent)
+ Error("pop3PasswordFailed", params, 1);
+ else
+ Error("pop3UsernameFailure");
+ SetFlag(POP3_PASSWORD_FAILED);
+ ClearFlag(POP3_AUTH_FAILURE);
+ return 0;
+ }
+
+ // We have no certain response code -> fallback and try again.
+ // Mark the auth method failed, to use a different method next round.
+ MarkAuthMethodAsFailed(m_currentAuthMethod);
+
+ if (m_currentAuthMethod == POP3_HAS_AUTH_USER &&
+ !m_password_already_sent)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("USER username failed")));
+ // if USER auth method failed before sending the password,
+ // the username was wrong.
+ // no fallback but return error
+ return Error("pop3UsernameFailure");
+ }
+
+ // If we have no auth method left, ask user to try with new password
+ rv = ChooseAuthMethod();
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
+ (POP3LOG("POP: no auth methods remaining, setting password failure")));
+ /* Sever the connection and go back to the `read password' state,
+ which, upon success, will re-open the connection. Set a flag
+ which causes the prompt to be different that time (to indicate
+ that the old password was bogus.)
+
+ But if we're just checking for new mail (biff) then don't bother
+ prompting the user for a password: just fail silently.
+ */
+ SetFlag(POP3_PASSWORD_FAILED);
+ Error("pop3PasswordFailed", params, 1);
+ return 0;
+ }
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("still have some auth methods to try")));
+
+ // TODO needed?
+ //m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+
+ m_pop3ConData->command_succeeded = true;
+
+ m_pop3ConData->next_state = POP3_PROCESS_AUTH;
+ }
+
+ if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED))
+ {
+ ClearCapFlag(POP3_AUTH_MECH_UNDEFINED);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+
+ m_pop3ConData->pause_for_read = false;
+
+ return 0;
+}
+
+// LOGIN consists of three steps not two as USER/PASS or CRAM-MD5,
+// so we've to start here and continue in SendUsername if the server
+// responds + to "AUTH LOGIN"
+int32_t nsPop3Protocol::AuthLogin()
+{
+ nsAutoCString command("AUTH LOGIN" CRLF);
+ m_pop3ConData->next_state_after_response = POP3_AUTH_LOGIN_RESPONSE;
+ m_pop3ConData->pause_for_read = true;
+
+ return Pop3SendData(command.get());
+}
+
+int32_t nsPop3Protocol::AuthLoginResponse()
+{
+ // need the test to be here instead in NextAuthStep() to
+ // differentiate between command AUTH LOGIN failed and
+ // sending username using LOGIN mechanism failed.
+ if (!m_pop3ConData->command_succeeded)
+ {
+ // we failed with LOGIN, remove it
+ MarkAuthMethodAsFailed(POP3_HAS_AUTH_LOGIN);
+ m_pop3ConData->next_state = POP3_PROCESS_AUTH;
+ }
+ else
+ m_pop3ConData->next_state = POP3_SEND_USERNAME;
+
+ m_pop3ConData->pause_for_read = false;
+
+ return 0;
+}
+
+// NTLM, like LOGIN consists of three steps not two as USER/PASS or CRAM-MD5,
+// so we've to start here and continue in SendUsername if the server
+// responds + to "AUTH NTLM"
+int32_t nsPop3Protocol::AuthNtlm()
+{
+ nsAutoCString command (m_currentAuthMethod == POP3_HAS_AUTH_MSN
+ ? "AUTH MSN" CRLF : "AUTH NTLM" CRLF);
+ m_pop3ConData->next_state_after_response = POP3_AUTH_NTLM_RESPONSE;
+ m_pop3ConData->pause_for_read = true;
+
+ return Pop3SendData(command.get());
+}
+
+int32_t nsPop3Protocol::AuthNtlmResponse()
+{
+ // need the test to be here instead in NextAuthStep() to
+ // differentiate between command AUTH NTLM failed and
+ // sending username using NTLM mechanism failed.
+ if (!m_pop3ConData->command_succeeded)
+ {
+ MarkAuthMethodAsFailed(POP3_HAS_AUTH_NTLM);
+ MarkAuthMethodAsFailed(POP3_HAS_AUTH_MSN);
+ m_pop3ConData->next_state = POP3_PROCESS_AUTH;
+ }
+ else
+ m_pop3ConData->next_state = POP3_SEND_USERNAME;
+
+ m_pop3ConData->pause_for_read = false;
+
+ return 0;
+}
+
+int32_t nsPop3Protocol::AuthGSSAPI()
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("AuthGSSAPI()")));
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ if (server) {
+ nsAutoCString cmd;
+ nsAutoCString service("pop@");
+ nsCString hostName;
+ nsresult rv;
+ server->GetRealHostName(hostName);
+ service.Append(hostName);
+ rv = DoGSSAPIStep1(service.get(), m_username.get(), cmd);
+ if (NS_SUCCEEDED(rv)) {
+ m_GSSAPICache.Assign(cmd);
+ m_pop3ConData->next_state_after_response = POP3_AUTH_GSSAPI_FIRST;
+ m_pop3ConData->pause_for_read = true;
+ return Pop3SendData("AUTH GSSAPI" CRLF);
+ }
+ }
+
+ MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI);
+ m_pop3ConData->next_state = POP3_PROCESS_AUTH;
+ m_pop3ConData->pause_for_read = false;
+ return 0;
+}
+
+int32_t nsPop3Protocol::AuthGSSAPIResponse(bool first)
+{
+ if (!m_pop3ConData->command_succeeded)
+ {
+ if (first)
+ m_GSSAPICache.Truncate();
+ MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI);
+ m_pop3ConData->next_state = POP3_PROCESS_AUTH;
+ m_pop3ConData->pause_for_read = false;
+ return 0;
+ }
+
+ int32_t result;
+
+ m_pop3ConData->next_state_after_response = POP3_AUTH_GSSAPI_STEP;
+ m_pop3ConData->pause_for_read = true;
+
+ if (first) {
+ m_GSSAPICache += CRLF;
+ result = Pop3SendData(m_GSSAPICache.get());
+ m_GSSAPICache.Truncate();
+ }
+ else {
+ nsAutoCString cmd;
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("GSSAPI step 2")));
+ nsresult rv = DoGSSAPIStep2(m_commandResponse, cmd);
+ if (NS_FAILED(rv))
+ cmd = "*";
+ if (rv == NS_SUCCESS_AUTH_FINISHED) {
+ m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
+ m_password_already_sent = true;
+ }
+ cmd += CRLF;
+ result = Pop3SendData(cmd.get());
+ }
+
+ return result;
+}
+
+int32_t nsPop3Protocol::SendUsername()
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendUsername()")));
+ if(m_username.IsEmpty())
+ return Error("pop3UsernameUndefined");
+
+ // <copied from="SendPassword()">
+ // Needed for NTLM
+
+ // The POP3_SEND_PASSWORD/POP3_WAIT_SEND_PASSWORD states have already
+ // got the password - they will have cancelled if necessary.
+ // If the password is still empty here, don't try to go on.
+ if (m_passwordResult.IsEmpty())
+ {
+ m_pop3ConData->next_state = POP3_ERROR_DONE;
+ return Error("pop3PasswordUndefined");
+ }
+ // </copied>
+
+ nsAutoCString cmd;
+
+ if (m_currentAuthMethod == POP3_HAS_AUTH_NTLM)
+ (void) DoNtlmStep1(m_username.get(), m_passwordResult.get(), cmd);
+ else if (m_currentAuthMethod == POP3_HAS_AUTH_CRAM_MD5)
+ cmd = "AUTH CRAM-MD5";
+ else if (m_currentAuthMethod == POP3_HAS_AUTH_PLAIN)
+ cmd = "AUTH PLAIN";
+ else if (m_currentAuthMethod == POP3_HAS_AUTH_LOGIN)
+ {
+ char *base64Str = PL_Base64Encode(m_username.get(), m_username.Length(), nullptr);
+ cmd = base64Str;
+ PR_Free(base64Str);
+ }
+ else if (m_currentAuthMethod == POP3_HAS_AUTH_USER)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("USER login")));
+ cmd = "USER ";
+ cmd += m_username;
+ }
+ else
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
+ (POP3LOG("In nsPop3Protocol::SendUsername(), m_currentAuthMethod is 0x%X, "
+ "but that is unexpected"), m_currentAuthMethod));
+ return Error("pop3AuthInternalError");
+ }
+
+ cmd += CRLF;
+
+ m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
+
+ m_pop3ConData->pause_for_read = true;
+
+ return Pop3SendData(cmd.get());
+}
+
+int32_t nsPop3Protocol::SendPassword()
+{
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendPassword()")));
+ if (m_username.IsEmpty())
+ return Error("pop3UsernameUndefined");
+
+ // <copied to="SendUsername()">
+ // Needed here, too, because APOP skips SendUsername()
+ // The POP3_SEND_PASSWORD/POP3_WAIT_SEND_PASSWORD states have already
+ // got the password - they will have cancelled if necessary.
+ // If the password is still empty here, don't try to go on.
+ if (m_passwordResult.IsEmpty())
+ {
+ m_pop3ConData->next_state = POP3_ERROR_DONE;
+ return Error("pop3PasswordUndefined");
+ }
+ // </copied>
+
+ nsAutoCString cmd;
+ nsresult rv;
+
+ if (m_currentAuthMethod == POP3_HAS_AUTH_NTLM)
+ rv = DoNtlmStep2(m_commandResponse, cmd);
+ else if (m_currentAuthMethod == POP3_HAS_AUTH_CRAM_MD5)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("CRAM login")));
+ char buffer[512]; // TODO nsAutoCString
+ unsigned char digest[DIGEST_LENGTH];
+
+ char *decodedChallenge = PL_Base64Decode(m_commandResponse.get(),
+ m_commandResponse.Length(), nullptr);
+
+ if (decodedChallenge)
+ rv = MSGCramMD5(decodedChallenge, strlen(decodedChallenge),
+ m_passwordResult.get(), m_passwordResult.Length(), digest);
+ else
+ rv = NS_ERROR_NULL_POINTER;
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString encodedDigest;
+ char hexVal[8];
+
+ for (uint32_t j = 0; j < 16; j++)
+ {
+ PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)digest[j]);
+ encodedDigest.Append(hexVal);
+ }
+
+ PR_snprintf(buffer, sizeof(buffer), "%s %s", m_username.get(),
+ encodedDigest.get());
+ char *base64Str = PL_Base64Encode(buffer, strlen(buffer), nullptr);
+ cmd = base64Str;
+ PR_Free(base64Str);
+ }
+
+ if (NS_FAILED(rv))
+ cmd = "*";
+ }
+ else if (m_currentAuthMethod == POP3_HAS_AUTH_APOP)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("APOP login")));
+ char buffer[512];
+ unsigned char digest[DIGEST_LENGTH];
+
+ rv = MSGApopMD5(m_ApopTimestamp.get(), m_ApopTimestamp.Length(),
+ m_passwordResult.get(), m_passwordResult.Length(), digest);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString encodedDigest;
+ char hexVal[8];
+
+ for (uint32_t j=0; j<16; j++)
+ {
+ PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)digest[j]);
+ encodedDigest.Append(hexVal);
+ }
+
+ PR_snprintf(buffer, sizeof(buffer), "APOP %s %s", m_username.get(),
+ encodedDigest.get());
+ cmd = buffer;
+ }
+
+ if (NS_FAILED(rv))
+ cmd = "*";
+ }
+ else if (m_currentAuthMethod == POP3_HAS_AUTH_PLAIN)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("PLAIN login")));
+ // workaround for IPswitch's IMail server software
+ // this server goes into LOGIN mode even if we send "AUTH PLAIN"
+ // "VXNlc" is the beginning of the base64 encoded prompt ("Username:") for LOGIN
+ if (StringBeginsWith(m_commandResponse, NS_LITERAL_CSTRING("VXNlc")))
+ {
+ // disable PLAIN and enable LOGIN (in case it's not already enabled)
+ ClearCapFlag(POP3_HAS_AUTH_PLAIN);
+ SetCapFlag(POP3_HAS_AUTH_LOGIN);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+
+ // reenter authentication again at LOGIN response handler
+ m_pop3ConData->next_state = POP3_AUTH_LOGIN_RESPONSE;
+ m_pop3ConData->pause_for_read = false;
+ return 0;
+ }
+
+ char plain_string[512]; // TODO nsCString
+ int len = 1; /* first <NUL> char */
+ memset(plain_string, 0, 512);
+ PR_snprintf(&plain_string[1], 510, "%s", m_username.get());
+ len += m_username.Length();
+ len++; /* second <NUL> char */
+ PR_snprintf(&plain_string[len], 511-len, "%s", m_passwordResult.get());
+ len += m_passwordResult.Length();
+
+ char *base64Str = PL_Base64Encode(plain_string, len, nullptr);
+ cmd = base64Str;
+ PR_Free(base64Str);
+ }
+ else if (m_currentAuthMethod == POP3_HAS_AUTH_LOGIN)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("LOGIN password")));
+ char * base64Str =
+ PL_Base64Encode(m_passwordResult.get(), m_passwordResult.Length(),
+ nullptr);
+ cmd = base64Str;
+ PR_Free(base64Str);
+ }
+ else if (m_currentAuthMethod == POP3_HAS_AUTH_USER)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("PASS password")));
+ cmd = "PASS ";
+ cmd += m_passwordResult;
+ }
+ else
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
+ (POP3LOG("In nsPop3Protocol::SendPassword(), m_currentAuthMethod is %X, "
+ "but that is unexpected"), m_currentAuthMethod));
+ return Error("pop3AuthInternalError");
+ }
+
+ cmd += CRLF;
+
+ // TODO needed?
+ //m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+
+ m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
+
+ m_pop3ConData->pause_for_read = true;
+
+ m_password_already_sent = true;
+ m_lastPasswordSent = m_passwordResult;
+ return Pop3SendData(cmd.get(), true);
+}
+
+int32_t nsPop3Protocol::SendStatOrGurl(bool sendStat)
+{
+ nsAutoCString cmd;
+ if (sendStat)
+ {
+ cmd = "STAT" CRLF;
+ m_pop3ConData->next_state_after_response = POP3_GET_STAT;
+ }
+ else
+ {
+ cmd = "GURL" CRLF;
+ m_pop3ConData->next_state_after_response = POP3_GURL_RESPONSE;
+ }
+ return Pop3SendData(cmd.get());
+}
+
+
+int32_t
+nsPop3Protocol::SendStat()
+{
+ return SendStatOrGurl(true);
+}
+
+
+int32_t
+nsPop3Protocol::GetStat()
+{
+ // check stat response
+ if (!m_pop3ConData->command_succeeded)
+ return Error("pop3StatFail");
+
+ /* stat response looks like: %d %d
+ * The first number is the number of articles
+ * The second number is the number of bytes
+ *
+ * grab the first and second arg of stat response
+ */
+ nsCString oldStr (m_commandResponse);
+ char *newStr = oldStr.BeginWriting();
+ char *num = NS_strtok(" ", &newStr); // msg num
+ if (num)
+ {
+ m_pop3ConData->number_of_messages = atol(num); // bytes
+ num = NS_strtok(" ", &newStr);
+ m_commandResponse = newStr;
+ if (num)
+ m_totalFolderSize = nsCRT::atoll(num); //we always initialize m_totalFolderSize to 0
+ }
+ else
+ m_pop3ConData->number_of_messages = 0;
+
+ m_pop3ConData->really_new_messages = 0;
+ m_pop3ConData->real_new_counter = 1;
+
+ m_totalDownloadSize = -1; // Means we need to calculate it, later.
+
+ if (m_pop3ConData->number_of_messages <= 0)
+ {
+ // We're all done. We know we have no mail.
+ m_pop3ConData->next_state = POP3_SEND_QUIT;
+ PL_HashTableEnumerateEntries(m_pop3ConData->uidlinfo->hash, hash_clear_mapper, nullptr);
+ // Hack - use nsPop3Sink to wipe out any stale Partial messages
+ m_nsIPop3Sink->BeginMailDelivery(false, nullptr, nullptr);
+ m_nsIPop3Sink->AbortMailDelivery(this);
+ return(0);
+ }
+
+ /* We're just checking for new mail, and we're not playing any games that
+ involve keeping messages on the server. Therefore, we now know enough
+ to finish up. If we had no messages, that would have been handled
+ above; therefore, we know we have some new messages.
+ */
+ if (m_pop3ConData->only_check_for_new_mail && !m_pop3ConData->leave_on_server)
+ {
+ m_nsIPop3Sink->SetBiffStateAndUpdateFE(nsIMsgFolder::nsMsgBiffState_NewMail,
+ m_pop3ConData->number_of_messages,
+ true);
+ m_pop3ConData->next_state = POP3_SEND_QUIT;
+ return(0);
+ }
+
+
+ if (!m_pop3ConData->only_check_for_new_mail)
+ {
+ /* The following was added to prevent the loss of Data when we try and
+ write to somewhere we don't have write access error to (See bug 62480)
+ (Note: This is only a temp hack until the underlying XPCOM is fixed
+ to return errors) */
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl)
+ rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+// NS_ASSERTION(NS_SUCCEEDED(rv) && msgWindow, "no msg window");
+
+ rv = m_nsIPop3Sink->BeginMailDelivery(m_pop3ConData->only_uidl != nullptr, msgWindow,
+ &m_pop3ConData->msg_del_started);
+ if (NS_FAILED(rv))
+ {
+ m_nsIPop3Sink->AbortMailDelivery(this);
+ if (rv == NS_MSG_FOLDER_BUSY) {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ nsString accountName;
+ rv = server->GetPrettyName(accountName);
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ const char16_t *params[] = { accountName.get() };
+ return Error("pop3ServerBusy", params, 1);
+ }
+
+ return Error("pop3MessageWriteError");
+ }
+
+ if(!m_pop3ConData->msg_del_started)
+ return Error("pop3MessageWriteError");
+ }
+
+ m_pop3ConData->next_state = POP3_SEND_LIST;
+ return 0;
+}
+
+
+
+int32_t
+nsPop3Protocol::SendGurl()
+{
+ if (m_pop3ConData->capability_flags == POP3_CAPABILITY_UNDEFINED ||
+ TestCapFlag(POP3_GURL_UNDEFINED | POP3_HAS_GURL))
+ return SendStatOrGurl(false);
+ else
+ return -1;
+}
+
+
+int32_t
+nsPop3Protocol::GurlResponse()
+{
+ ClearCapFlag(POP3_GURL_UNDEFINED);
+
+ if (m_pop3ConData->command_succeeded)
+ {
+ SetCapFlag(POP3_HAS_GURL);
+ if (m_nsIPop3Sink)
+ m_nsIPop3Sink->SetMailAccountURL(m_commandResponse);
+ }
+ else
+ {
+ ClearCapFlag(POP3_HAS_GURL);
+ }
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ m_pop3ConData->next_state = POP3_SEND_QUIT;
+
+ return 0;
+}
+
+int32_t nsPop3Protocol::SendList()
+{
+ // check for server returning number of messages that will cause the calculation
+ // of the size of the block for msg_info to
+ // overflow a 32 bit int, in turn causing us to allocate a block of memory much
+ // smaller than we think we're allocating, and
+ // potentially allowing the server to make us overwrite memory outside our heap
+ // block.
+
+ if (m_pop3ConData->number_of_messages > (int) (0xFFFFF000 / sizeof(Pop3MsgInfo)))
+ return MK_OUT_OF_MEMORY;
+
+
+ m_pop3ConData->msg_info = (Pop3MsgInfo *)
+ PR_CALLOC(sizeof(Pop3MsgInfo) * m_pop3ConData->number_of_messages);
+ if (!m_pop3ConData->msg_info)
+ return(MK_OUT_OF_MEMORY);
+ m_pop3ConData->next_state_after_response = POP3_GET_LIST;
+ m_listpos = 0;
+ return Pop3SendData("LIST" CRLF);
+}
+
+
+
+int32_t
+nsPop3Protocol::GetList(nsIInputStream* inputStream,
+ uint32_t length)
+{
+ /* check list response
+ * This will get called multiple times
+ * but it's alright since command_succeeded
+ * will remain constant
+ */
+ if(!m_pop3ConData->command_succeeded)
+ return Error("pop3ListFailure");
+
+ uint32_t ln = 0;
+ bool pauseForMoreData = false;
+ nsresult rv;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
+ if (NS_FAILED(rv))
+ return -1;
+
+ if (pauseForMoreData || !line)
+ {
+ m_pop3ConData->pause_for_read = true;
+ PR_Free(line);
+ return(ln);
+ }
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
+
+ /* parse the line returned from the list command
+ * it looks like
+ * #msg_number #bytes
+ *
+ * list data is terminated by a ".CRLF" line
+ */
+ if (!PL_strcmp(line, "."))
+ {
+ // limit the list if fewer entries than given in STAT response
+ if(m_listpos < m_pop3ConData->number_of_messages)
+ m_pop3ConData->number_of_messages = m_listpos;
+ m_pop3ConData->next_state = POP3_SEND_UIDL_LIST;
+ m_pop3ConData->pause_for_read = false;
+ PR_Free(line);
+ return(0);
+ }
+
+ char *newStr = line;
+ char *token = NS_strtok(" ", &newStr);
+ if (token)
+ {
+ int32_t msg_num = atol(token);
+
+ if (++m_listpos <= m_pop3ConData->number_of_messages)
+ {
+ token = NS_strtok(" ", &newStr);
+ if (token)
+ {
+ m_pop3ConData->msg_info[m_listpos-1].size = atol(token);
+ m_pop3ConData->msg_info[m_listpos-1].msgnum = msg_num;
+ }
+ }
+ }
+
+ PR_Free(line);
+ return(0);
+}
+
+
+/* UIDL and XTND are both unsupported for this mail server.
+ If not enabled any advanced features, we're able to live
+ without them. We're simply downloading and deleting everything
+ on the server.
+
+ Advanced features are:
+ *'Keep Mail on Server' with aging or deletion support
+ *'Fetch Headers Only'
+ *'Limit Message Size'
+ *only download a specific UID
+
+ These require knowledge of of all messages UID's on the server at
+ least when it comes to deleting deleting messages on server that
+ have been deleted on client or vice versa. TOP doesn't help here
+ without generating huge traffic and is mostly not supported at all
+ if the server lacks UIDL and XTND XLST.
+
+ In other cases the user has to join the 20th century.
+ Tell the user this, and refuse to download any messages until
+ they've gone into preferences and turned off any of the above
+ prefs.
+*/
+int32_t nsPop3Protocol::HandleNoUidListAvailable()
+{
+ m_pop3ConData->pause_for_read = false;
+
+ if(!m_pop3ConData->leave_on_server &&
+ !m_pop3ConData->headers_only &&
+ m_pop3ConData->size_limit <= 0 &&
+ !m_pop3ConData->only_uidl)
+ {
+ m_pop3ConData->next_state = POP3_GET_MSG;
+ return 0;
+ }
+ m_pop3ConData->next_state = POP3_SEND_QUIT;
+ nsCString hostName;
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ nsresult rv = server->GetRealHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, -1);
+ NS_ConvertASCIItoUTF16 hostNameUnicode(hostName);
+ const char16_t *params[] = { hostNameUnicode.get() };
+ return Error("pop3ServerDoesNotSupportUidlEtc", params, 1);
+}
+
+
+/* km
+ *
+ * net_pop3_send_xtnd_xlst_msgid
+ *
+ * Process state: POP3_SEND_XTND_XLST_MSGID
+ *
+ * If we get here then UIDL is not supported by the mail server.
+ * Some mail servers support a similar command:
+ *
+ * XTND XLST Message-Id
+ *
+ * Here is a sample transaction from a QUALCOMM server
+
+ >>XTND XLST Message-Id
+ <<+OK xlst command accepted; headers coming.
+ <<1 Message-ID: <3117E4DC.2699@netscape.invalid>
+ <<2 Message-Id: <199602062335.PAA19215@lemon.example.com>
+
+ * This function will send the xtnd command and put us into the
+ * POP3_GET_XTND_XLST_MSGID state
+ *
+*/
+int32_t nsPop3Protocol::SendXtndXlstMsgid()
+{
+ if (TestCapFlag(POP3_HAS_XTND_XLST | POP3_XTND_XLST_UNDEFINED))
+ {
+ m_pop3ConData->next_state_after_response = POP3_GET_XTND_XLST_MSGID;
+ m_pop3ConData->pause_for_read = true;
+ m_listpos = 0;
+ return Pop3SendData("XTND XLST Message-Id" CRLF);
+ }
+ else
+ return HandleNoUidListAvailable();
+}
+
+
+/* km
+ *
+ * net_pop3_get_xtnd_xlst_msgid
+ *
+ * This code was created from the net_pop3_get_uidl_list boiler plate.
+ * The difference is that the XTND reply strings have one more token per
+ * string than the UIDL reply strings do.
+ *
+ */
+
+int32_t
+nsPop3Protocol::GetXtndXlstMsgid(nsIInputStream* inputStream,
+ uint32_t length)
+{
+ /* check list response
+ * This will get called multiple times
+ * but it's alright since command_succeeded
+ * will remain constant
+ */
+ ClearCapFlag(POP3_XTND_XLST_UNDEFINED);
+
+ if (!m_pop3ConData->command_succeeded)
+ {
+ ClearCapFlag(POP3_HAS_XTND_XLST);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ HandleNoUidListAvailable();
+ return(0);
+ }
+ else
+ {
+ SetCapFlag(POP3_HAS_XTND_XLST);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+
+ uint32_t ln = 0;
+ bool pauseForMoreData = false;
+ nsresult rv;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
+ if (NS_FAILED(rv))
+ return -1;
+
+ if (pauseForMoreData || !line)
+ {
+ m_pop3ConData->pause_for_read = true;
+ PR_Free(line);
+ return ln;
+ }
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
+
+ /* parse the line returned from the list command
+ * it looks like
+ * 1 Message-ID: <3117E4DC.2699@example.com>
+ *
+ * list data is terminated by a ".CRLF" line
+ */
+ if (!PL_strcmp(line, "."))
+ {
+ // limit the list if fewer entries than given in STAT response
+ if(m_listpos < m_pop3ConData->number_of_messages)
+ m_pop3ConData->number_of_messages = m_listpos;
+ m_pop3ConData->list_done = true;
+ m_pop3ConData->next_state = POP3_GET_MSG;
+ m_pop3ConData->pause_for_read = false;
+ PR_Free(line);
+ return(0);
+ }
+
+ char *newStr = line;
+ char *token = NS_strtok(" ", &newStr); // msg num
+ if (token)
+ {
+ int32_t msg_num = atol(token);
+ if (++m_listpos <= m_pop3ConData->number_of_messages)
+ {
+ NS_strtok(" ", &newStr); // eat message ID token
+ const char *uid = NS_strtok(" ", &newStr); // not really a UID but a unique token -km
+ if (!uid)
+ /* This is bad. The server didn't give us a UIDL for this message.
+ I've seen this happen when somehow the mail spool has a message
+ that contains a header that reads "X-UIDL: \n". But how that got
+ there, I have no idea; must be a server bug. Or something. */
+ uid = "";
+
+ // seeking right entry, but try the one that should it be first
+ int32_t i;
+ if(m_pop3ConData->msg_info[m_listpos - 1].msgnum == msg_num)
+ i = m_listpos - 1;
+ else
+ for(i = 0; i < m_pop3ConData->number_of_messages &&
+ m_pop3ConData->msg_info[i].msgnum != msg_num; i++)
+ ;
+
+ // only if found a matching slot
+ if (i < m_pop3ConData->number_of_messages)
+ {
+ // to protect us from memory leak in case of getting a msg num twice
+ m_pop3ConData->msg_info[i].uidl = PL_strdup(uid);
+ if (!m_pop3ConData->msg_info[i].uidl)
+ {
+ PR_Free(line);
+ return MK_OUT_OF_MEMORY;
+ }
+ }
+ }
+ }
+
+ PR_Free(line);
+ return(0);
+}
+
+
+int32_t nsPop3Protocol::SendUidlList()
+{
+ if (TestCapFlag(POP3_HAS_UIDL | POP3_UIDL_UNDEFINED))
+ {
+ m_pop3ConData->next_state_after_response = POP3_GET_UIDL_LIST;
+ m_pop3ConData->pause_for_read = true;
+ m_listpos = 0;
+ return Pop3SendData("UIDL" CRLF);
+ }
+ else
+ return SendXtndXlstMsgid();
+}
+
+
+int32_t nsPop3Protocol::GetUidlList(nsIInputStream* inputStream,
+ uint32_t length)
+{
+ /* check list response
+ * This will get called multiple times
+ * but it's alright since command_succeeded
+ * will remain constant
+ */
+ ClearCapFlag(POP3_UIDL_UNDEFINED);
+
+ if (!m_pop3ConData->command_succeeded)
+ {
+ m_pop3ConData->next_state = POP3_SEND_XTND_XLST_MSGID;
+ m_pop3ConData->pause_for_read = false;
+ ClearCapFlag(POP3_HAS_UIDL);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ return(0);
+ }
+ else
+ {
+ SetCapFlag(POP3_HAS_UIDL);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+
+ uint32_t ln = 0;
+ bool pauseForMoreData = false;
+ nsresult rv;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv);
+ if (NS_FAILED(rv))
+ return -1;
+
+ if (pauseForMoreData || !line)
+ {
+ PR_Free(line);
+ m_pop3ConData->pause_for_read = true;
+ return ln;
+ }
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
+
+ /* parse the line returned from the list command
+ * it looks like
+ * #msg_number uidl
+ *
+ * list data is terminated by a ".CRLF" line
+ */
+ if (!PL_strcmp(line, "."))
+ {
+ // limit the list if fewer entries than given in STAT response
+ if (m_listpos < m_pop3ConData->number_of_messages)
+ m_pop3ConData->number_of_messages = m_listpos;
+ m_pop3ConData->list_done = true;
+ m_pop3ConData->next_state = POP3_GET_MSG;
+ m_pop3ConData->pause_for_read = false;
+ PR_Free(line);
+ return(0);
+ }
+
+ char *newStr = line;
+ char *token = NS_strtok(" ", &newStr); // msg num
+ if (token)
+ {
+ int32_t msg_num = atol(token);
+ if (++m_listpos <= m_pop3ConData->number_of_messages)
+ {
+ const char *uid = NS_strtok(" ", &newStr); // UID
+ if (!uid)
+ /* This is bad. The server didn't give us a UIDL for this message.
+ I've seen this happen when somehow the mail spool has a message
+ that contains a header that reads "X-UIDL: \n". But how that got
+ there, I have no idea; must be a server bug. Or something. */
+ uid = "";
+
+ // seeking right entry, but try the one that should it be first
+ int32_t i;
+ if(m_pop3ConData->msg_info[m_listpos - 1].msgnum == msg_num)
+ i = m_listpos - 1;
+ else
+ for(i = 0; i < m_pop3ConData->number_of_messages &&
+ m_pop3ConData->msg_info[i].msgnum != msg_num; i++)
+ ;
+
+ // only if found a matching slot
+ if (i < m_pop3ConData->number_of_messages)
+ {
+ // to protect us from memory leak in case of getting a msg num twice
+ m_pop3ConData->msg_info[i].uidl = PL_strdup(uid);
+ if (!m_pop3ConData->msg_info[i].uidl)
+ {
+ PR_Free(line);
+ return MK_OUT_OF_MEMORY;
+ }
+ }
+ }
+ }
+ PR_Free(line);
+ return(0);
+}
+
+
+
+/* this function decides if we are going to do a
+ * normal RETR or a TOP. The first time, it also decides the total number
+ * of bytes we're probably going to get.
+ */
+int32_t nsPop3Protocol::GetMsg()
+{
+ int32_t popstateTimestamp = TimeInSecondsFromPRTime(PR_Now());
+
+ if (m_pop3ConData->last_accessed_msg >= m_pop3ConData->number_of_messages)
+ {
+ /* Oh, gee, we're all done. */
+ if(m_pop3ConData->msg_del_started)
+ {
+ if (!m_pop3ConData->only_uidl)
+ {
+ if (m_pop3ConData->only_check_for_new_mail)
+ m_nsIPop3Sink->SetBiffStateAndUpdateFE(m_pop3ConData->biffstate, m_pop3ConData->really_new_messages, true);
+ /* update old style biff */
+ else
+ m_nsIPop3Sink->SetBiffStateAndUpdateFE(nsIMsgFolder::nsMsgBiffState_NewMail, m_pop3ConData->really_new_messages, false);
+ }
+ m_nsIPop3Sink->EndMailDelivery(this);
+ }
+
+ m_pop3ConData->next_state = POP3_SEND_QUIT;
+ return 0;
+ }
+
+ if (m_totalDownloadSize < 0)
+ {
+ /* First time. Figure out how many bytes we're about to get.
+ If we didn't get any message info, then we are going to get
+ everything, and it's easy. Otherwise, if we only want one
+ uidl, than that's the only one we'll get. Otherwise, go
+ through each message info, decide if we're going to get that
+ message, and add the number of bytes for it. When a message is too
+ large (per user's preferences) only add the size we are supposed
+ to get. */
+ m_pop3ConData->really_new_messages = 0;
+ m_pop3ConData->real_new_counter = 1;
+ if (m_pop3ConData->msg_info)
+ {
+ m_totalDownloadSize = 0;
+ for (int32_t i = 0; i < m_pop3ConData->number_of_messages; i++)
+ {
+ if (m_pop3ConData->only_uidl)
+ {
+ if (m_pop3ConData->msg_info[i].uidl &&
+ !PL_strcmp(m_pop3ConData->msg_info[i].uidl, m_pop3ConData->only_uidl))
+ {
+ m_totalDownloadSize = m_pop3ConData->msg_info[i].size;
+ m_pop3ConData->really_new_messages = 1;
+ // we are only getting one message
+ m_pop3ConData->real_new_counter = 1;
+ break;
+ }
+ continue;
+ }
+
+ char c = 0;
+ popstateTimestamp = TimeInSecondsFromPRTime(PR_Now());
+ if (m_pop3ConData->msg_info[i].uidl)
+ {
+ Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->uidlinfo->hash,
+ m_pop3ConData->msg_info[i].uidl);
+ if (uidlEntry)
+ {
+ c = uidlEntry->status;
+ popstateTimestamp = uidlEntry->dateReceived;
+ }
+ }
+ if ((c == KEEP) && !m_pop3ConData->leave_on_server)
+ { /* This message has been downloaded but kept on server, we
+ * no longer want to keep it there */
+ if (!m_pop3ConData->newuidl)
+ {
+ m_pop3ConData->newuidl = PL_NewHashTable(20, PL_HashString, PL_CompareStrings,
+ PL_CompareValues, &gHashAllocOps, nullptr);
+ if (!m_pop3ConData->newuidl)
+ return MK_OUT_OF_MEMORY;
+ }
+ c = DELETE_CHAR;
+ // Mark message to be deleted in new table
+ put_hash(m_pop3ConData->newuidl,
+ m_pop3ConData->msg_info[i].uidl, DELETE_CHAR, popstateTimestamp);
+ // and old one too
+ put_hash(m_pop3ConData->uidlinfo->hash,
+ m_pop3ConData->msg_info[i].uidl, DELETE_CHAR, popstateTimestamp);
+ }
+ if ((c != KEEP) && (c != DELETE_CHAR) && (c != TOO_BIG))
+ { // message left on server
+ m_totalDownloadSize += m_pop3ConData->msg_info[i].size;
+ m_pop3ConData->really_new_messages++;
+ // a message we will really download
+ }
+ }
+ }
+ else
+ {
+ m_totalDownloadSize = m_totalFolderSize;
+ }
+ if (m_pop3ConData->only_check_for_new_mail)
+ {
+ if (m_totalDownloadSize > 0)
+ {
+ m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_NewMail;
+ m_nsIPop3Sink->SetBiffStateAndUpdateFE(nsIMsgFolder::nsMsgBiffState_NewMail, m_pop3ConData->really_new_messages, true);
+ }
+ m_pop3ConData->next_state = POP3_SEND_QUIT;
+ return(0);
+ }
+ /* get the amount of available space on the drive
+ * and make sure there is enough
+ */
+ if (m_totalDownloadSize > 0) // skip all this if there aren't any messages
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ // Get the current mailbox folder
+ NS_ENSURE_TRUE(m_nsIPop3Sink, -1);
+ nsresult rv = m_nsIPop3Sink->GetFolder(getter_AddRefs(folder));
+ if (NS_FAILED(rv))
+ return -1;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+ if (NS_FAILED(rv) || !mailnewsUrl)
+ return -1;
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+
+ bool spaceNotAvailable = true;
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder(do_QueryInterface(folder, &rv));
+ if (NS_FAILED(rv) || !localFolder)
+ return -1;
+
+ // check if we have a reasonable amount of space left
+ rv = localFolder->WarnIfLocalFileTooBig(msgWindow, m_totalDownloadSize, &spaceNotAvailable);
+ if (NS_FAILED(rv) || spaceNotAvailable) {
+#ifdef DEBUG
+ printf("Not enough disk space! Raising error!\n");
+#endif
+ return -1;
+ }
+
+ // Here we know how many messages we're going to download, so let
+ // the pop3 sink know.
+ rv = m_nsIPop3Sink->SetMsgsToDownload(m_pop3ConData->really_new_messages);
+ }
+ }
+
+ /* Look at this message, and decide whether to ignore it, get it, just get
+ the TOP of it, or delete it. */
+
+ // if this is a message we've seen for the first time, we won't find it in
+ // m_pop3ConData-uidlinfo->hash. By default, we retrieve messages, unless they have a status,
+ // or are too big, in which case we figure out what to do.
+ if (m_prefAuthMethods != POP3_HAS_AUTH_USER && TestCapFlag(POP3_HAS_XSENDER))
+ m_pop3ConData->next_state = POP3_SEND_XSENDER;
+ else
+ m_pop3ConData->next_state = POP3_SEND_RETR;
+ m_pop3ConData->truncating_cur_msg = false;
+ m_pop3ConData->pause_for_read = false;
+ if (m_pop3ConData->msg_info)
+ {
+ Pop3MsgInfo* info = m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg;
+ if (m_pop3ConData->only_uidl)
+ {
+ if (info->uidl == NULL || PL_strcmp(info->uidl, m_pop3ConData->only_uidl))
+ m_pop3ConData->next_state = POP3_GET_MSG;
+ else
+ m_pop3ConData->next_state = POP3_SEND_RETR;
+ }
+ else
+ {
+ char c = 0;
+ if (!m_pop3ConData->newuidl)
+ {
+ m_pop3ConData->newuidl = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, &gHashAllocOps, nullptr);
+ if (!m_pop3ConData->newuidl)
+ return MK_OUT_OF_MEMORY;
+ }
+ if (info->uidl)
+ {
+ Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, info->uidl);
+ if (uidlEntry)
+ {
+ c = uidlEntry->status;
+ popstateTimestamp = uidlEntry->dateReceived;
+ }
+ }
+ if (c == DELETE_CHAR)
+ {
+ m_pop3ConData->next_state = POP3_SEND_DELE;
+ }
+ else if (c == KEEP)
+ {
+ // this is a message we've already downloaded and left on server;
+ // Advance to next message.
+ m_pop3ConData->next_state = POP3_GET_MSG;
+ }
+ else if (c == FETCH_BODY)
+ {
+ m_pop3ConData->next_state = POP3_SEND_RETR;
+ PL_HashTableRemove (m_pop3ConData->uidlinfo->hash, (void*)info->uidl);
+ }
+ else if ((c != TOO_BIG) &&
+ (TestCapFlag(POP3_TOP_UNDEFINED | POP3_HAS_TOP)) &&
+ (m_pop3ConData->headers_only ||
+ ((m_pop3ConData->size_limit > 0) &&
+ (info->size > m_pop3ConData->size_limit) &&
+ !m_pop3ConData->only_uidl)) &&
+ info->uidl && *info->uidl)
+ {
+ // message is too big
+ m_pop3ConData->truncating_cur_msg = true;
+ m_pop3ConData->next_state = POP3_SEND_TOP;
+ put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG, popstateTimestamp);
+ }
+ else if (c == TOO_BIG)
+ {
+ /* message previously left on server, see if the max download size
+ has changed, because we may want to download the message this time
+ around. Otherwise ignore the message, we have the header. */
+ if ((m_pop3ConData->size_limit > 0) && (info->size <=
+ m_pop3ConData->size_limit))
+ PL_HashTableRemove (m_pop3ConData->uidlinfo->hash, (void*)info->uidl);
+ // remove from our table, and download
+ else
+ {
+ m_pop3ConData->truncating_cur_msg = true;
+ m_pop3ConData->next_state = POP3_GET_MSG;
+ // ignore this message and get next one
+ put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG, popstateTimestamp);
+ }
+ }
+
+ if (m_pop3ConData->next_state != POP3_SEND_DELE &&
+ info->uidl)
+ {
+ /* This is a message we have decided to keep on the server. Notate
+ that now for the future. (Don't change the popstate file at all
+ if only_uidl is set; in that case, there might be brand new messages
+ on the server that we *don't* want to mark KEEP; we just want to
+ leave them around until the user next does a GetNewMail.) */
+
+ /* If this is a message we already know about (i.e., it was
+ in popstate.dat already), we need to maintain the original
+ date the message was downloaded. */
+ if (m_pop3ConData->truncating_cur_msg)
+ put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG, popstateTimestamp);
+ else
+ put_hash(m_pop3ConData->newuidl, info->uidl, KEEP, popstateTimestamp);
+ }
+ }
+ if (m_pop3ConData->next_state == POP3_GET_MSG)
+ m_pop3ConData->last_accessed_msg++;
+ // Make sure we check the next message next time!
+ }
+ return 0;
+}
+
+
+/* start retreiving just the first 20 lines
+ */
+int32_t nsPop3Protocol::SendTop()
+{
+ char * cmd = PR_smprintf( "TOP %ld %d" CRLF,
+ m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum,
+ m_pop3ConData->headers_only ? 0 : 20);
+ int32_t status = -1;
+ if (cmd)
+ {
+ m_pop3ConData->next_state_after_response = POP3_TOP_RESPONSE;
+ m_pop3ConData->cur_msg_size = -1;
+
+ /* zero the bytes received in message in preparation for
+ * the next
+ */
+ m_bytesInMsgReceived = 0;
+ status = Pop3SendData(cmd);
+ }
+ PR_Free(cmd);
+ return status;
+}
+
+/* send the xsender command
+ */
+int32_t nsPop3Protocol::SendXsender()
+{
+ char * cmd = PR_smprintf("XSENDER %ld" CRLF, m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum);
+ int32_t status = -1;
+ if (cmd)
+ {
+ m_pop3ConData->next_state_after_response = POP3_XSENDER_RESPONSE;
+ status = Pop3SendData(cmd);
+ PR_Free(cmd);
+ }
+ return status;
+}
+
+int32_t nsPop3Protocol::XsenderResponse()
+{
+ m_pop3ConData->seenFromHeader = false;
+ m_senderInfo = "";
+
+ if (m_pop3ConData->command_succeeded) {
+ if (m_commandResponse.Length() > 4)
+ m_senderInfo = m_commandResponse;
+ }
+ else {
+ ClearCapFlag(POP3_HAS_XSENDER);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+
+ if (m_pop3ConData->truncating_cur_msg)
+ m_pop3ConData->next_state = POP3_SEND_TOP;
+ else
+ m_pop3ConData->next_state = POP3_SEND_RETR;
+ return 0;
+}
+
+/* retreive the whole message
+ */
+int32_t
+nsPop3Protocol::SendRetr()
+{
+
+ char * cmd = PR_smprintf("RETR %ld" CRLF, m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum);
+ int32_t status = -1;
+ if (cmd)
+ {
+ m_pop3ConData->next_state_after_response = POP3_RETR_RESPONSE;
+ m_pop3ConData->cur_msg_size = -1;
+
+
+ /* zero the bytes received in message in preparation for
+ * the next
+ */
+ m_bytesInMsgReceived = 0;
+
+ if (m_pop3ConData->only_uidl)
+ {
+ /* Display bytes if we're only downloading one message. */
+ PR_ASSERT(!m_pop3ConData->graph_progress_bytes_p);
+ UpdateProgressPercent(0, m_totalDownloadSize);
+ m_pop3ConData->graph_progress_bytes_p = true;
+ }
+ else
+ {
+ nsString finalString;
+ mozilla::DebugOnly<nsresult> rv =
+ FormatCounterString(NS_LITERAL_STRING("receivingMessages"),
+ m_pop3ConData->real_new_counter,
+ m_pop3ConData->really_new_messages,
+ finalString);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "couldn't format string");
+ if (mProgressEventSink) {
+ rv = mProgressEventSink->OnStatus(this, m_channelContext, NS_OK,
+ finalString.get());
+ NS_ASSERTION(NS_SUCCEEDED(rv), "dropping error result");
+ }
+ }
+
+ status = Pop3SendData(cmd);
+ } // if cmd
+ PR_Free(cmd);
+ return status;
+}
+
+/* digest the message
+ */
+int32_t
+nsPop3Protocol::RetrResponse(nsIInputStream* inputStream,
+ uint32_t length)
+{
+ uint32_t buffer_size;
+ int32_t flags = 0;
+ char *uidl = NULL;
+ nsresult rv;
+ uint32_t status = 0;
+
+ if(m_pop3ConData->cur_msg_size == -1)
+ {
+ /* this is the beginning of a message
+ * get the response code and byte size
+ */
+ if(!m_pop3ConData->command_succeeded)
+ return Error("pop3RetrFailure");
+
+ /* a successful RETR response looks like: #num_bytes Junk
+ from TOP we only get the +OK and data
+ */
+ if (m_pop3ConData->truncating_cur_msg)
+ { /* TOP, truncated message */
+ flags |= nsMsgMessageFlags::Partial;
+ }
+ else
+ {
+ nsCString cmdResp(m_commandResponse);
+ char *newStr = cmdResp.BeginWriting();
+ char *num = NS_strtok( " ", &newStr);
+ if (num)
+ m_pop3ConData->cur_msg_size = atol(num);
+ m_commandResponse = newStr;
+ }
+
+ /* RETR complete message */
+ if (!m_senderInfo.IsEmpty())
+ flags |= nsMsgMessageFlags::SenderAuthed;
+
+ if(m_pop3ConData->cur_msg_size <= 0)
+ {
+ if (m_pop3ConData->msg_info)
+ m_pop3ConData->cur_msg_size = m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].size;
+ else
+ m_pop3ConData->cur_msg_size = 0;
+ }
+
+ if (m_pop3ConData->msg_info &&
+ m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].uidl)
+ uidl = m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].uidl;
+
+ m_pop3ConData->parsed_bytes = 0;
+ m_pop3ConData->pop3_size = m_pop3ConData->cur_msg_size;
+ m_pop3ConData->assumed_end = false;
+
+ m_pop3Server->GetDotFix(&m_pop3ConData->dot_fix);
+
+ MOZ_LOG(POP3LOGMODULE,LogLevel::Info,
+ (POP3LOG("Opening message stream: MSG_IncorporateBegin")));
+
+ /* open the message stream so we have someplace
+ * to put the data
+ */
+ m_pop3ConData->real_new_counter++;
+ /* (rb) count only real messages being downloaded */
+ rv = m_nsIPop3Sink->IncorporateBegin(uidl, m_url, flags,
+ &m_pop3ConData->msg_closure);
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("Done opening message stream!")));
+
+ if(!m_pop3ConData->msg_closure || NS_FAILED(rv))
+ return Error("pop3MessageWriteError");
+ }
+
+ m_pop3ConData->pause_for_read = true;
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv, true);
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
+ if (NS_FAILED(rv))
+ return -1;
+
+ buffer_size = status;
+
+ if (status == 0 && !line) // no bytes read in...
+ return (0);
+
+ if (m_pop3ConData->msg_closure) /* not done yet */
+ {
+ // buffer the line we just read in, and buffer all remaining lines in the stream
+ status = buffer_size;
+ do
+ {
+ if (m_pop3ConData->msg_closure)
+ {
+ rv = HandleLine(line, buffer_size);
+ if (NS_FAILED(rv))
+ return Error("pop3MessageWriteError");
+
+ // buffer_size already includes MSG_LINEBREAK_LEN so
+ // subtract and add CRLF
+ // but not really sure we always had CRLF in input since
+ // we also treat a single LF as line ending!
+ m_pop3ConData->parsed_bytes += buffer_size - MSG_LINEBREAK_LEN + 2;
+ }
+
+ // now read in the next line
+ PR_Free(line);
+ line = m_lineStreamBuffer->ReadNextLine(inputStream, buffer_size,
+ pauseForMoreData, &rv, true);
+ if (NS_FAILED(rv))
+ return -1;
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line));
+ // buffer_size already includes MSG_LINEBREAK_LEN so
+ // subtract and add CRLF
+ // but not really sure we always had CRLF in input since
+ // we also treat a single LF as line ending!
+ status += buffer_size - MSG_LINEBREAK_LEN + 2;
+ } while (line);
+ }
+
+ buffer_size = status; // status holds # bytes we've actually buffered so far...
+
+ /* normal read. Yay! */
+ if ((int32_t) (m_bytesInMsgReceived + buffer_size) > m_pop3ConData->cur_msg_size)
+ buffer_size = m_pop3ConData->cur_msg_size - m_bytesInMsgReceived;
+
+ m_bytesInMsgReceived += buffer_size;
+ m_totalBytesReceived += buffer_size;
+
+ // *** jefft in case of the message size that server tells us is different
+ // from the actual message size
+ if (pauseForMoreData && m_pop3ConData->dot_fix &&
+ m_pop3ConData->assumed_end && m_pop3ConData->msg_closure)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ if (NS_SUCCEEDED(rv))
+ rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ rv = m_nsIPop3Sink->IncorporateComplete(msgWindow,
+ m_pop3ConData->truncating_cur_msg ? m_pop3ConData->cur_msg_size : 0);
+
+ // The following was added to prevent the loss of Data when we try
+ // and write to somewhere we don't have write access error to (See
+ // bug 62480)
+ // (Note: This is only a temp hack until the underlying XPCOM is
+ // fixed to return errors)
+
+ if (NS_FAILED(rv))
+ return Error((rv == NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD) ?
+ "pop3TmpDownloadError" :
+ "pop3MessageWriteError");
+
+ m_pop3ConData->msg_closure = nullptr;
+ }
+
+ if (!m_pop3ConData->msg_closure)
+ /* meaning _handle_line read ".\r\n" at end-of-msg */
+ {
+ m_pop3ConData->pause_for_read = false;
+
+ if (m_pop3ConData->truncating_cur_msg ||
+ m_pop3ConData->leave_on_server )
+ {
+ Pop3UidlEntry *uidlEntry = NULL;
+ Pop3MsgInfo* info = m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg;
+
+ /* Check for filter actions - FETCH or DELETE */
+ if ((m_pop3ConData->newuidl) && (info->uidl))
+ uidlEntry = (Pop3UidlEntry *)PL_HashTableLookup(m_pop3ConData->newuidl, info->uidl);
+
+ if (uidlEntry && uidlEntry->status == FETCH_BODY &&
+ m_pop3ConData->truncating_cur_msg)
+ {
+ /* A filter decided to retrieve this full msg.
+ Use GetMsg() so the popstate will update correctly,
+ but don't let this msg get counted twice. */
+ m_pop3ConData->next_state = POP3_GET_MSG;
+ m_pop3ConData->real_new_counter--;
+ /* Make sure we don't try to come through here again. */
+ PL_HashTableRemove (m_pop3ConData->newuidl, (void*)info->uidl);
+ put_hash(m_pop3ConData->uidlinfo->hash, info->uidl, FETCH_BODY, uidlEntry->dateReceived);
+
+ } else if (uidlEntry && uidlEntry->status == DELETE_CHAR)
+ {
+ // A filter decided to delete this msg from the server
+ m_pop3ConData->next_state = POP3_SEND_DELE;
+ } else
+ {
+ /* We've retrieved all or part of this message, but we want to
+ keep it on the server. Go on to the next message. */
+ m_pop3ConData->last_accessed_msg++;
+ m_pop3ConData->next_state = POP3_GET_MSG;
+ }
+ if (m_pop3ConData->only_uidl)
+ {
+ /* GetMsg didn't update this field. Do it now */
+ uidlEntry = (Pop3UidlEntry *)PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, m_pop3ConData->only_uidl);
+ NS_ASSERTION(uidlEntry, "uidl not found in uidlinfo");
+ if (uidlEntry)
+ put_hash(m_pop3ConData->uidlinfo->hash, m_pop3ConData->only_uidl, KEEP, uidlEntry->dateReceived);
+ }
+ }
+ else
+ {
+ m_pop3ConData->next_state = POP3_SEND_DELE;
+ }
+
+ /* if we didn't get the whole message add the bytes that we didn't get
+ to the bytes received part so that the progress percent stays sane.
+ */
+ if (m_bytesInMsgReceived < m_pop3ConData->cur_msg_size)
+ m_totalBytesReceived += (m_pop3ConData->cur_msg_size -
+ m_bytesInMsgReceived);
+ }
+
+ /* set percent done to portion of total bytes of all messages
+ that we're going to download. */
+ if (m_totalDownloadSize)
+ UpdateProgressPercent(m_totalBytesReceived, m_totalDownloadSize);
+
+ PR_Free(line);
+ return(0);
+}
+
+
+int32_t
+nsPop3Protocol::TopResponse(nsIInputStream* inputStream, uint32_t length)
+{
+ if (TestCapFlag(POP3_TOP_UNDEFINED))
+ {
+ ClearCapFlag(POP3_TOP_UNDEFINED);
+ if (m_pop3ConData->command_succeeded)
+ SetCapFlag(POP3_HAS_TOP);
+ else
+ ClearCapFlag(POP3_HAS_TOP);
+ m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
+ }
+
+ if(m_pop3ConData->cur_msg_size == -1 && /* first line after TOP command sent */
+ !m_pop3ConData->command_succeeded) /* and TOP command failed */
+ {
+ /* TOP doesn't work so we can't retrieve the first part of this msg.
+ So just go download the whole thing, and warn the user.
+
+ Note that the progress bar will not be accurate in this case.
+ Oops. #### */
+ m_pop3ConData->truncating_cur_msg = false;
+
+ nsString statusTemplate;
+ mLocalBundle->GetStringFromName(
+ u"pop3ServerDoesNotSupportTopCommand",
+ getter_Copies(statusTemplate));
+ if (!statusTemplate.IsEmpty())
+ {
+ nsAutoCString hostName;
+ char16_t * statusString = nullptr;
+ m_url->GetHost(hostName);
+
+ statusString = nsTextFormatter::smprintf(statusTemplate.get(), hostName.get());
+ UpdateStatusWithString(statusString);
+ nsTextFormatter::smprintf_free(statusString);
+ }
+
+ if (m_prefAuthMethods != POP3_HAS_AUTH_USER &&
+ TestCapFlag(POP3_HAS_XSENDER))
+ m_pop3ConData->next_state = POP3_SEND_XSENDER;
+ else
+ m_pop3ConData->next_state = POP3_SEND_RETR;
+ return(0);
+ }
+
+ /* If TOP works, we handle it in the same way as RETR. */
+ return RetrResponse(inputStream, length);
+}
+
+/* line is handed over as null-terminated string with MSG_LINEBREAK */
+nsresult
+nsPop3Protocol::HandleLine(char *line, uint32_t line_length)
+{
+ nsresult rv = NS_OK;
+
+ NS_ASSERTION(m_pop3ConData->msg_closure, "m_pop3ConData->msg_closure is null in nsPop3Protocol::HandleLine()");
+ if (!m_pop3ConData->msg_closure)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!m_senderInfo.IsEmpty() && !m_pop3ConData->seenFromHeader)
+ {
+ if (line_length > 6 && !PL_strncasecmp("From: ", line, 6))
+ {
+ m_pop3ConData->seenFromHeader = true;
+ if (PL_strstr(line, m_senderInfo.get()) == NULL)
+ m_nsIPop3Sink->SetSenderAuthedFlag(m_pop3ConData->msg_closure,
+ false);
+ }
+ }
+
+ // line contains only a single dot and linebreak -> message end
+ if (line_length == 1 + MSG_LINEBREAK_LEN && line[0] == '.')
+ {
+ m_pop3ConData->assumed_end = true; /* in case byte count from server is */
+ /* wrong, mark we may have had the end */
+ if (!m_pop3ConData->dot_fix || m_pop3ConData->truncating_cur_msg ||
+ (m_pop3ConData->parsed_bytes >= (m_pop3ConData->pop3_size -3)))
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ if (NS_SUCCEEDED(rv))
+ rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ rv = m_nsIPop3Sink->IncorporateComplete(msgWindow,
+ m_pop3ConData->truncating_cur_msg ? m_pop3ConData->cur_msg_size : 0);
+
+ // The following was added to prevent the loss of Data when we try
+ // and write to somewhere we don't have write access error to (See
+ // bug 62480)
+ // (Note: This is only a temp hack until the underlying XPCOM is
+ // fixed to return errors)
+
+ if (NS_FAILED(rv)) {
+ Error((rv == NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD) ?
+ "pop3TmpDownloadError" :
+ "pop3MessageWriteError");
+ return rv;
+ }
+
+ m_pop3ConData->msg_closure = nullptr;
+ return rv;
+ }
+ }
+ /* Check if the line begins with the termination octet. If so
+ and if another termination octet follows, we step over the
+ first occurence of it. */
+ else if (line_length > 1 && line[0] == '.' && line[1] == '.') {
+ line++;
+ line_length--;
+
+ }
+
+ return m_nsIPop3Sink->IncorporateWrite(line, line_length);
+}
+
+int32_t nsPop3Protocol::SendDele()
+{
+ /* increment the last accessed message since we have now read it
+ */
+ char * cmd = PR_smprintf("DELE %ld" CRLF, m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum);
+ m_pop3ConData->last_accessed_msg++;
+ int32_t status = -1;
+ if (cmd)
+ {
+ m_pop3ConData->next_state_after_response = POP3_DELE_RESPONSE;
+ status = Pop3SendData(cmd);
+ }
+ PR_Free(cmd);
+ return status;
+}
+
+int32_t nsPop3Protocol::DeleResponse()
+{
+ Pop3UidlHost *host = NULL;
+
+ host = m_pop3ConData->uidlinfo;
+
+ /* the return from the delete will come here
+ */
+ if(!m_pop3ConData->command_succeeded)
+ return Error("pop3DeleFailure");
+
+
+ /* ###chrisf
+ the delete succeeded. Write out state so that we
+ keep track of all the deletes which have not yet been
+ committed on the server. Flush this state upon successful
+ QUIT.
+
+ We will do this by adding each successfully deleted message id
+ to a list which we will write out to popstate.dat in
+ net_pop3_write_state().
+ */
+ if (host)
+ {
+ if (m_pop3ConData->msg_info &&
+ m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl)
+ {
+ if (m_pop3ConData->newuidl)
+ if (m_pop3ConData->leave_on_server)
+ {
+ PL_HashTableRemove(m_pop3ConData->newuidl, (void*)
+ m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl);
+ }
+ else
+ {
+ put_hash(m_pop3ConData->newuidl,
+ m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl, DELETE_CHAR, 0);
+ /* kill message in new hash table */
+ }
+ else
+ PL_HashTableRemove(host->hash,
+ (void*) m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl);
+ }
+ }
+
+ m_pop3ConData->next_state = POP3_GET_MSG;
+ m_pop3ConData->pause_for_read = false;
+
+ return(0);
+}
+
+
+int32_t
+nsPop3Protocol::CommitState(bool remove_last_entry)
+{
+ // only use newuidl if we successfully finished looping through all the
+ // messages in the inbox.
+ if (m_pop3ConData->newuidl)
+ {
+ if (m_pop3ConData->last_accessed_msg >= m_pop3ConData->number_of_messages)
+ {
+ PL_HashTableDestroy(m_pop3ConData->uidlinfo->hash);
+ m_pop3ConData->uidlinfo->hash = m_pop3ConData->newuidl;
+ m_pop3ConData->newuidl = nullptr;
+ }
+ else
+ {
+ /* If we are leaving messages on the server, pull out the last
+ uidl from the hash, because it might have been put in there before
+ we got it into the database.
+ */
+ if (remove_last_entry && m_pop3ConData->msg_info &&
+ !m_pop3ConData->only_uidl && m_pop3ConData->newuidl->nentries > 0)
+ {
+ Pop3MsgInfo* info = m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg;
+ if (info && info->uidl)
+ {
+ mozilla::DebugOnly<bool> val = PL_HashTableRemove(m_pop3ConData->newuidl,
+ info->uidl);
+ NS_ASSERTION(val, "uidl not in hash table");
+ }
+ }
+
+ // Add the entries in newuidl to m_pop3ConData->uidlinfo->hash to keep
+ // track of the messages we *did* download in this session.
+ PL_HashTableEnumerateEntries(m_pop3ConData->newuidl, net_pop3_copy_hash_entries, (void *)m_pop3ConData->uidlinfo->hash);
+ }
+ }
+
+ if (!m_pop3ConData->only_check_for_new_mail)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIFile> mailDirectory;
+
+ // get the mail directory
+ nsCOMPtr<nsIMsgIncomingServer> server =
+ do_QueryInterface(m_pop3Server, &rv);
+ if (NS_FAILED(rv)) return -1;
+
+ rv = server->GetLocalPath(getter_AddRefs(mailDirectory));
+ if (NS_FAILED(rv)) return -1;
+
+ // write the state in the mail directory
+ net_pop3_write_state(m_pop3ConData->uidlinfo, mailDirectory.get());
+ }
+ return 0;
+}
+
+
+/* NET_process_Pop3 will control the state machine that
+ * loads messages from a pop3 server
+ *
+ * returns negative if the transfer is finished or error'd out
+ *
+ * returns zero or more if the transfer needs to be continued.
+ */
+nsresult nsPop3Protocol::ProcessProtocolState(nsIURI * url, nsIInputStream * aInputStream,
+ uint64_t sourceOffset, uint32_t aLength)
+{
+ int32_t status = 0;
+ bool urlStatusSet = false;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_url);
+
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("Entering NET_ProcessPop3 %d"),
+ aLength));
+
+ m_pop3ConData->pause_for_read = false; /* already paused; reset */
+
+ if(m_username.IsEmpty())
+ {
+ // net_pop3_block = false;
+ Error("pop3UsernameUndefined");
+ return NS_MSG_SERVER_USERNAME_MISSING;
+ }
+
+ while(!m_pop3ConData->pause_for_read)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
+ (POP3LOG("Entering state: %d"), m_pop3ConData->next_state));
+
+ switch(m_pop3ConData->next_state)
+ {
+ case POP3_READ_PASSWORD:
+ // This is a separate state so that we're waiting for the user to type
+ // in a password while we don't actually have a connection to the pop
+ // server open; this saves us from having to worry about the server
+ // timing out on us while we wait for user input.
+ if (NS_FAILED(StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_EARLY)))
+ status = -1;
+ break;
+ case POP3_FINISH_OBTAIN_PASSWORD_EARLY:
+ {
+ if (m_passwordResult.IsEmpty() || m_username.IsEmpty())
+ {
+ status = MK_POP3_PASSWORD_UNDEFINED;
+ m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_Unknown;
+ m_nsIPop3Sink->SetBiffStateAndUpdateFE(m_pop3ConData->biffstate, 0, false);
+
+ /* update old style biff */
+ m_pop3ConData->next_state = POP3_FREE;
+ m_pop3ConData->pause_for_read = false;
+ break;
+ }
+
+ m_pop3ConData->pause_for_read = false;
+ // we are already connected so just go on and send the username
+ if (m_prefAuthMethods == POP3_HAS_AUTH_USER)
+ {
+ m_currentAuthMethod = POP3_HAS_AUTH_USER;
+ m_pop3ConData->next_state = POP3_SEND_USERNAME;
+ }
+ else
+ {
+ if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED))
+ m_pop3ConData->next_state = POP3_SEND_AUTH;
+ else
+ m_pop3ConData->next_state = POP3_SEND_CAPA;
+ }
+ break;
+ }
+
+
+ case POP3_START_CONNECT:
+ {
+ m_pop3ConData->next_state = POP3_FINISH_CONNECT;
+ m_pop3ConData->pause_for_read = false;
+ break;
+ }
+
+ case POP3_FINISH_CONNECT:
+ {
+ m_pop3ConData->pause_for_read = false;
+ m_pop3ConData->next_state = POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE;
+ break;
+ }
+
+ case POP3_WAIT_FOR_RESPONSE:
+ status = WaitForResponse(aInputStream, aLength);
+ break;
+
+ case POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE:
+ {
+ status = WaitForStartOfConnectionResponse(aInputStream, aLength);
+
+ if(status)
+ {
+ if (m_prefAuthMethods == POP3_HAS_AUTH_USER)
+ {
+ m_currentAuthMethod = POP3_HAS_AUTH_USER;
+ m_pop3ConData->next_state = POP3_SEND_USERNAME;
+ }
+ else
+ {
+ if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED))
+ m_pop3ConData->next_state = POP3_SEND_AUTH;
+ else
+ m_pop3ConData->next_state = POP3_SEND_CAPA;
+ }
+ }
+
+ break;
+ }
+
+ case POP3_SEND_AUTH:
+ status = SendAuth();
+ break;
+
+ case POP3_AUTH_RESPONSE:
+ status = AuthResponse(aInputStream, aLength);
+ break;
+
+ case POP3_SEND_CAPA:
+ status = SendCapa();
+ break;
+
+ case POP3_CAPA_RESPONSE:
+ status = CapaResponse(aInputStream, aLength);
+ break;
+
+ case POP3_TLS_RESPONSE:
+ status = SendTLSResponse();
+ break;
+
+ case POP3_PROCESS_AUTH:
+ status = ProcessAuth();
+ break;
+
+ case POP3_NEXT_AUTH_STEP:
+ status = NextAuthStep();
+ break;
+
+ case POP3_AUTH_LOGIN:
+ status = AuthLogin();
+ break;
+
+ case POP3_AUTH_LOGIN_RESPONSE:
+ status = AuthLoginResponse();
+ break;
+
+ case POP3_AUTH_NTLM:
+ status = AuthNtlm();
+ break;
+
+ case POP3_AUTH_NTLM_RESPONSE:
+ status = AuthNtlmResponse();
+ break;
+
+ case POP3_AUTH_GSSAPI:
+ status = AuthGSSAPI();
+ break;
+
+ case POP3_AUTH_GSSAPI_FIRST:
+ UpdateStatus(u"hostContact");
+ status = AuthGSSAPIResponse(true);
+ break;
+
+ case POP3_AUTH_GSSAPI_STEP:
+ status = AuthGSSAPIResponse(false);
+ break;
+
+ case POP3_SEND_USERNAME:
+ if (NS_FAILED(StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_BEFORE_USERNAME)))
+ status = -1;
+ break;
+
+ case POP3_OBTAIN_PASSWORD_BEFORE_USERNAME:
+ status = -1;
+ break;
+
+ case POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME:
+ UpdateStatus(u"hostContact");
+ status = SendUsername();
+ break;
+
+ case POP3_SEND_PASSWORD:
+ if (NS_FAILED(StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD)))
+ status = -1;
+ break;
+
+ case POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD:
+ status = SendPassword();
+ break;
+
+ case POP3_SEND_GURL:
+ status = SendGurl();
+ break;
+
+ case POP3_GURL_RESPONSE:
+ status = GurlResponse();
+ break;
+
+ case POP3_SEND_STAT:
+ status = SendStat();
+ break;
+
+ case POP3_GET_STAT:
+ status = GetStat();
+ break;
+
+ case POP3_SEND_LIST:
+ status = SendList();
+ break;
+
+ case POP3_GET_LIST:
+ status = GetList(aInputStream, aLength);
+ break;
+
+ case POP3_SEND_UIDL_LIST:
+ status = SendUidlList();
+ break;
+
+ case POP3_GET_UIDL_LIST:
+ status = GetUidlList(aInputStream, aLength);
+ break;
+
+ case POP3_SEND_XTND_XLST_MSGID:
+ status = SendXtndXlstMsgid();
+ break;
+
+ case POP3_GET_XTND_XLST_MSGID:
+ status = GetXtndXlstMsgid(aInputStream, aLength);
+ break;
+
+ case POP3_GET_MSG:
+ status = GetMsg();
+ break;
+
+ case POP3_SEND_TOP:
+ status = SendTop();
+ break;
+
+ case POP3_TOP_RESPONSE:
+ status = TopResponse(aInputStream, aLength);
+ break;
+
+ case POP3_SEND_XSENDER:
+ status = SendXsender();
+ break;
+
+ case POP3_XSENDER_RESPONSE:
+ status = XsenderResponse();
+ break;
+
+ case POP3_SEND_RETR:
+ status = SendRetr();
+ break;
+
+ case POP3_RETR_RESPONSE:
+ status = RetrResponse(aInputStream, aLength);
+ break;
+
+ case POP3_SEND_DELE:
+ status = SendDele();
+ break;
+
+ case POP3_DELE_RESPONSE:
+ status = DeleResponse();
+ break;
+
+ case POP3_SEND_QUIT:
+ /* attempt to send a server quit command. Since this means
+ everything went well, this is a good time to update the
+ status file and the FE's biff state.
+ */
+ if (!m_pop3ConData->only_uidl)
+ {
+ /* update old style biff */
+ if (!m_pop3ConData->only_check_for_new_mail)
+ {
+ /* We don't want to pop up a warning message any more (see
+ bug 54116), so instead we put the "no new messages" or
+ "retrieved x new messages"
+ in the status line. Unfortunately, this tends to be running
+ in a progress pane, so we try to get the real pane and
+ show the message there. */
+
+ if (m_totalDownloadSize <= 0)
+ {
+ UpdateStatus(u"noNewMessages");
+ /* There are no new messages. */
+ }
+ else
+ {
+ nsString statusString;
+ nsresult rv = FormatCounterString(NS_LITERAL_STRING("receivedMsgs"),
+ m_pop3ConData->real_new_counter - 1,
+ m_pop3ConData->really_new_messages,
+ statusString);
+ if (NS_SUCCEEDED(rv))
+ UpdateStatusWithString(statusString.get());
+ }
+ }
+ }
+
+ status = Pop3SendData("QUIT" CRLF);
+ m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE;
+ m_pop3ConData->next_state_after_response = POP3_QUIT_RESPONSE;
+ break;
+
+ case POP3_QUIT_RESPONSE:
+ if(m_pop3ConData->command_succeeded)
+ {
+ /* the QUIT succeeded. We can now flush the state in popstate.dat which
+ keeps track of any uncommitted DELE's */
+
+ /* clear the hash of all our uncommitted deletes */
+ if (!m_pop3ConData->leave_on_server && m_pop3ConData->newuidl)
+ {
+ PL_HashTableEnumerateEntries(m_pop3ConData->newuidl,
+ net_pop3_remove_messages_marked_delete,
+ (void *)m_pop3ConData);
+ }
+ m_pop3ConData->next_state = POP3_DONE;
+ }
+ else
+ {
+ m_pop3ConData->next_state = POP3_ERROR_DONE;
+ }
+ break;
+
+ case POP3_DONE:
+ CommitState(false);
+ m_pop3ConData->urlStatus = NS_OK;
+ urlStatusSet = true;
+ m_pop3ConData->next_state = POP3_FREE;
+ break;
+
+ case POP3_ERROR_DONE:
+ /* write out the state */
+ if(m_pop3ConData->list_done)
+ CommitState(true);
+
+ if(m_pop3ConData->msg_closure)
+ {
+ m_nsIPop3Sink->IncorporateAbort(m_pop3ConData->only_uidl != nullptr);
+ m_pop3ConData->msg_closure = NULL;
+ m_nsIPop3Sink->AbortMailDelivery(this);
+ }
+
+ if(m_pop3ConData->msg_del_started)
+ {
+ nsString statusString;
+ nsresult rv = FormatCounterString(NS_LITERAL_STRING("receivedMsgs"),
+ m_pop3ConData->real_new_counter - 1,
+ m_pop3ConData->really_new_messages,
+ statusString);
+ if (NS_SUCCEEDED(rv))
+ UpdateStatusWithString(statusString.get());
+
+ NS_ASSERTION (!TestFlag(POP3_PASSWORD_FAILED), "POP3_PASSWORD_FAILED set when del_started");
+ m_nsIPop3Sink->AbortMailDelivery(this);
+ }
+ { // this brace is to avoid compiler error about vars in switch case.
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+
+ if (mailnewsurl)
+ mailnewsurl->GetMsgWindow(getter_AddRefs(msgWindow));
+ // no msgWindow means no re-prompt, so treat as error.
+ if (TestFlag(POP3_PASSWORD_FAILED) && msgWindow)
+ {
+ // We get here because the password was wrong.
+ if (!m_socketIsOpen && mailnewsurl)
+ {
+ // The server dropped the connection, so we're going
+ // to re-run the url.
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+ (POP3LOG("need to rerun url because connection dropped during auth")));
+ m_needToRerunUrl = true;
+ return NS_OK;
+ }
+ m_pop3ConData->next_state = POP3_READ_PASSWORD;
+ m_pop3ConData->command_succeeded = true;
+ status = 0;
+ break;
+ }
+ else
+ /* Else we got a "real" error, so finish up. */
+ m_pop3ConData->next_state = POP3_FREE;
+ }
+ m_pop3ConData->urlStatus = NS_ERROR_FAILURE;
+ urlStatusSet = true;
+ m_pop3ConData->pause_for_read = false;
+ break;
+
+ case POP3_FREE:
+ {
+ UpdateProgressPercent(0,0); // clear out the progress meter
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server);
+ if (server)
+ {
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Clearing server busy in POP3_FREE")));
+ server->SetServerBusy(false); // the server is now not busy
+ }
+ MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Clearing running protocol in POP3_FREE")));
+ CloseSocket();
+ m_pop3Server->SetRunningProtocol(nullptr);
+ if (mailnewsurl && urlStatusSet)
+ mailnewsurl->SetUrlState(false, m_pop3ConData->urlStatus);
+
+ m_url = nullptr;
+ return NS_OK;
+ }
+ default:
+ NS_ERROR("Got to unexpected state in nsPop3Protocol::ProcessProtocolState");
+ status = -1;
+ } /* end switch */
+
+ if((status < 0) && m_pop3ConData->next_state != POP3_FREE)
+ {
+ m_pop3ConData->pause_for_read = false;
+ m_pop3ConData->next_state = POP3_ERROR_DONE;
+ }
+
+ } /* end while */
+
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP nsPop3Protocol::MarkMessages(nsTArray<Pop3UidlEntry*> *aUIDLArray)
+{
+ NS_ENSURE_ARG_POINTER(aUIDLArray);
+ uint32_t count = aUIDLArray->Length();
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ bool changed;
+ if (m_pop3ConData->newuidl)
+ MarkMsgInHashTable(m_pop3ConData->newuidl, aUIDLArray->ElementAt(i), &changed);
+ if (m_pop3ConData->uidlinfo)
+ MarkMsgInHashTable(m_pop3ConData->uidlinfo->hash, aUIDLArray->ElementAt(i), &changed);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Protocol::CheckMessage(const char *aUidl, bool *aBool)
+{
+ Pop3UidlEntry *uidlEntry = nullptr;
+
+ if (aUidl)
+ {
+ if (m_pop3ConData->newuidl)
+ uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->newuidl, aUidl);
+ else if (m_pop3ConData->uidlinfo)
+ uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, aUidl);
+ }
+
+ *aBool = uidlEntry ? true : false;
+ return NS_OK;
+}
+
+
+/* Function for finding an APOP Timestamp and simple check
+ it for its validity. If returning NS_OK m_ApopTimestamp
+ contains the validated substring of m_commandResponse. */
+nsresult nsPop3Protocol::GetApopTimestamp()
+{
+ int32_t startMark = m_commandResponse.Length(), endMark = -1;
+
+ while (true)
+ {
+ // search for previous <
+ if ((startMark = MsgRFindChar(m_commandResponse, '<', startMark - 1)) < 0)
+ return NS_ERROR_FAILURE;
+
+ // search for next >
+ if ((endMark = m_commandResponse.FindChar('>', startMark)) < 0)
+ continue;
+
+ // look for an @ between start and end as a raw test
+ int32_t at = m_commandResponse.FindChar('@', startMark);
+ if (at < 0 || at >= endMark)
+ continue;
+
+ // now test if sub only consists of chars in ASCII range
+ nsCString sub(Substring(m_commandResponse, startMark, endMark - startMark + 1));
+ if (NS_IsAscii(sub.get()))
+ {
+ // set m_ApopTimestamp to the validated substring
+ m_ApopTimestamp.Assign(sub);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsPop3Protocol.h b/mailnews/local/src/nsPop3Protocol.h
new file mode 100644
index 000000000..43a8aa4e8
--- /dev/null
+++ b/mailnews/local/src/nsPop3Protocol.h
@@ -0,0 +1,402 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPop3Protocol_h__
+#define nsPop3Protocol_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsIPop3URL.h"
+#include "nsIPop3Sink.h"
+#include "nsMsgLineBuffer.h"
+#include "nsMsgProtocol.h"
+#include "nsIPop3Protocol.h"
+#include "MailNewsTypes.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsIAuthModule.h"
+#include "nsITimer.h"
+#include "nsIMsgAsyncPrompter.h"
+
+#include "prerror.h"
+#include "plhash.h"
+#include "nsCOMPtr.h"
+
+/* A more guaranteed way of making sure that we never get duplicate messages
+is to always get each message's UIDL (if the server supports it)
+and use these for storing up deletes which were not committed on the
+server. Based on our experience, it looks like we do NOT need to
+do this (it has performance tradeoffs, etc.). To turn it on, three
+things need to happen: #define POP_ALWAYS_USE_UIDL_FOR_DUPLICATES, verify that
+the uidl's are correctly getting added when the delete response is received,
+and change the POP3_QUIT_RESPONSE state to flush the newly committed deletes. */
+
+/*
+ * Cannot have the following line uncommented. It is defined.
+ * #ifdef POP_ALWAYS_USE_UIDL_FOR_DUPLICATES will always be evaluated
+ * as true.
+ *
+#define POP_ALWAYS_USE_UIDL_FOR_DUPLICATES 0
+ *
+ */
+
+#define MK_OUT_OF_MEMORY -207
+#define MK_POP3_PASSWORD_UNDEFINED -313
+#define XP_NO_ANSWER 14401
+#define XP_THE_PREVIOUSLY_ENTERED_PASSWORD_IS_INVALID_ETC 14405
+#define XP_PASSWORD_FOR_POP3_USER 14590
+
+#define OUTPUT_BUFFER_SIZE 8192 // maximum size of command string
+
+/* structure to hold data pertaining to the active state of
+ * a transfer in progress.
+ *
+ */
+
+enum Pop3CapabilityEnum {
+ POP3_CAPABILITY_UNDEFINED = 0x00000000,
+ POP3_HAS_XSENDER = 0x00000001,
+ POP3_GURL_UNDEFINED = 0x00000002,
+ POP3_HAS_GURL = 0x00000004,
+ POP3_UIDL_UNDEFINED = 0x00000008,
+ POP3_HAS_UIDL = 0x00000010,
+ POP3_XTND_XLST_UNDEFINED = 0x00000020,
+ POP3_HAS_XTND_XLST = 0x00000040,
+ POP3_TOP_UNDEFINED = 0x00000080,
+ POP3_HAS_TOP = 0x00000100,
+ POP3_AUTH_MECH_UNDEFINED = 0x00000200,
+ POP3_HAS_AUTH_USER = 0x00000400,
+ POP3_HAS_AUTH_LOGIN = 0x00000800,
+ POP3_HAS_AUTH_PLAIN = 0x00001000,
+ POP3_HAS_AUTH_CRAM_MD5 = 0x00002000,
+ POP3_HAS_AUTH_APOP = 0x00004000,
+ POP3_HAS_AUTH_NTLM = 0x00008000,
+ POP3_HAS_AUTH_MSN = 0x00010000,
+ POP3_HAS_RESP_CODES = 0x00020000,
+ POP3_HAS_AUTH_RESP_CODE = 0x00040000,
+ POP3_HAS_STLS = 0x00080000,
+ POP3_HAS_AUTH_GSSAPI = 0x00100000
+};
+
+// TODO use value > 0?
+#define POP3_HAS_AUTH_NONE 0
+#define POP3_HAS_AUTH_ANY 0x00001C00
+#define POP3_HAS_AUTH_ANY_SEC 0x0011E000
+
+enum Pop3StatesEnum {
+ POP3_READ_PASSWORD, // 0
+ //
+ POP3_START_CONNECT, // 1
+ POP3_FINISH_CONNECT, // 2
+ POP3_WAIT_FOR_RESPONSE, // 3
+ POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE, // 4
+ POP3_SEND_USERNAME, // 5
+
+ POP3_SEND_PASSWORD, // 6
+ POP3_SEND_STAT, // 7
+ POP3_GET_STAT, // 8
+ POP3_SEND_LIST, // 9
+ POP3_GET_LIST, // 10
+
+ POP3_SEND_UIDL_LIST, // 11
+ POP3_GET_UIDL_LIST, // 12
+ POP3_SEND_XTND_XLST_MSGID, // 13
+ POP3_GET_XTND_XLST_MSGID, // 14
+ POP3_GET_MSG, // 15
+
+ POP3_SEND_TOP, // 16
+ POP3_TOP_RESPONSE, // 17
+ POP3_SEND_RETR, // 18
+ POP3_RETR_RESPONSE, // 19
+ POP3_SEND_DELE, // 20
+
+ POP3_DELE_RESPONSE, // 21
+ POP3_SEND_QUIT, // 22
+ POP3_DONE, // 23
+ POP3_ERROR_DONE, // 24
+ POP3_FREE, // 25
+ POP3_SEND_AUTH, // 26
+ POP3_AUTH_RESPONSE, // 27
+ POP3_SEND_CAPA, // 28
+ POP3_CAPA_RESPONSE, // 29
+ POP3_PROCESS_AUTH, // 30
+ POP3_NEXT_AUTH_STEP, // 31
+
+ POP3_AUTH_LOGIN, // 32
+ POP3_AUTH_LOGIN_RESPONSE, // 33
+ POP3_AUTH_NTLM, // 34
+ POP3_AUTH_NTLM_RESPONSE, // 35
+ POP3_SEND_XSENDER, // 36
+ POP3_XSENDER_RESPONSE, // 37
+ POP3_SEND_GURL, // 38
+
+ POP3_GURL_RESPONSE, // 39
+ POP3_QUIT_RESPONSE, // 40
+ POP3_TLS_RESPONSE, // 41
+
+ POP3_AUTH_GSSAPI, // 42
+ POP3_AUTH_GSSAPI_FIRST, // 43
+ POP3_AUTH_GSSAPI_STEP, // 44
+
+ /**
+ * Async wait to obtain the password and deal with the result.
+ * The *PREOBTAIN* states are used for where we try and get the password
+ * before we've initiated a connection to the server.
+ */
+ POP3_OBTAIN_PASSWORD_EARLY, // 45
+ POP3_FINISH_OBTAIN_PASSWORD_EARLY, // 46
+ POP3_OBTAIN_PASSWORD_BEFORE_USERNAME, // 47
+ POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME,// 48
+ POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD, // 49
+ POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD // 50
+};
+
+
+#define KEEP 'k' /* If we want to keep this item on server. */
+#define DELETE_CHAR 'd' /* If we want to delete this item. */
+#define TOO_BIG 'b' /* item left on server because it was too big */
+#define FETCH_BODY 'f' /* Fetch full body of a partial msg */
+
+typedef struct Pop3UidlEntry { /* information about this message */
+ char* uidl;
+ char status; // KEEP=='k', DELETE='d' TOO_BIG='b' FETCH_BODY='f'
+ uint32_t dateReceived; // time message received, used for aging
+} Pop3UidlEntry;
+
+typedef struct Pop3UidlHost {
+ char* host;
+ char* user;
+ PLHashTable * hash;
+ Pop3UidlEntry* uidlEntries;
+ struct Pop3UidlHost* next;
+} Pop3UidlHost;
+
+typedef struct Pop3MsgInfo {
+ int32_t msgnum;
+ int32_t size;
+ char* uidl;
+} Pop3MsgInfo;
+
+typedef struct _Pop3ConData {
+ bool leave_on_server; /* Whether we're supposed to leave messages
+ on server. */
+ bool headers_only; /* Whether to just fetch headers on initial
+ downloads. */
+ int32_t size_limit; /* Leave messages bigger than this on the
+ server and only download a partial
+ message. */
+ uint32_t capability_flags; /* What capability this server has? */
+
+ Pop3StatesEnum next_state; /* the next state or action to be taken */
+ Pop3StatesEnum next_state_after_response;
+ bool pause_for_read; /* Pause now for next read? */
+
+ bool command_succeeded; /* did the last command succeed? */
+ bool list_done; /* did we get the complete list of msgIDs? */
+ int32_t first_msg;
+
+ uint32_t obuffer_size;
+ uint32_t obuffer_fp;
+
+ int32_t really_new_messages;
+ int32_t real_new_counter;
+ int32_t number_of_messages;
+ Pop3MsgInfo *msg_info; /* Message sizes and uidls (used only if we
+ are playing games that involve leaving
+ messages on the server). */
+ int32_t last_accessed_msg;
+ int32_t cur_msg_size;
+ bool truncating_cur_msg; /* are we using top and uidl? */
+ bool msg_del_started; /* True if MSG_BeginMailDel...
+ * called
+ */
+ bool only_check_for_new_mail;
+ nsMsgBiffState biffstate; /* If just checking for, what the answer is. */
+
+ bool verify_logon; /* true if we're just seeing if we can logon */
+
+ void *msg_closure;
+
+ bool graph_progress_bytes_p; /* whether we should display info about
+ the bytes transferred (usually we
+ display info about the number of
+ messages instead.) */
+
+ Pop3UidlHost *uidlinfo;
+ PLHashTable *newuidl;
+ char *only_uidl; /* If non-NULL, then load only this UIDL. */
+
+ bool get_url;
+ bool seenFromHeader;
+ int32_t parsed_bytes;
+ int32_t pop3_size;
+ bool dot_fix;
+ bool assumed_end;
+ nsresult urlStatus;
+} Pop3ConData;
+
+// State Flags (Note, I use the word state in terms of storing
+// state information about the connection (authentication, have we sent
+// commands, etc. I do not intend it to refer to protocol state)
+#define POP3_PAUSE_FOR_READ 0x00000001 /* should we pause for the next read */
+#define POP3_PASSWORD_FAILED 0x00000002
+#define POP3_STOPLOGIN 0x00000004 /* error loging in, so stop here */
+#define POP3_AUTH_FAILURE 0x00000008 /* extended code said authentication failed */
+
+
+class nsPop3Protocol : public nsMsgProtocol,
+ public nsIPop3Protocol,
+ public nsIMsgAsyncPromptListener
+{
+public:
+ explicit nsPop3Protocol(nsIURI* aURL);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPOP3PROTOCOL
+ NS_DECL_NSIMSGASYNCPROMPTLISTENER
+
+ nsresult Initialize(nsIURI * aURL);
+ virtual nsresult LoadUrl(nsIURI *aURL, nsISupports * aConsumer = nullptr) override;
+ void Cleanup();
+
+ const char* GetUsername() { return m_username.get(); }
+ void SetUsername(const char* name);
+
+ nsresult StartGetAsyncPassword(Pop3StatesEnum aNextState);
+
+ NS_IMETHOD OnTransportStatus(nsITransport *transport, nsresult status, int64_t progress, int64_t progressMax) override;
+ NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports * aContext, nsresult aStatus) override;
+ NS_IMETHOD Cancel(nsresult status) override;
+
+ static void MarkMsgInHashTable(PLHashTable *hashTable, const Pop3UidlEntry *uidl,
+ bool *changed);
+
+ static nsresult MarkMsgForHost(const char *hostName, const char *userName,
+ nsIFile *mailDirectory,
+ nsTArray<Pop3UidlEntry*> &UIDLArray);
+private:
+ virtual ~nsPop3Protocol();
+ nsCString m_ApopTimestamp;
+ nsCOMPtr<nsIStringBundle> mLocalBundle;
+
+ nsCString m_username;
+ nsCString m_senderInfo;
+ nsCString m_commandResponse;
+ nsCString m_GSSAPICache;
+
+ // Used for asynchronous password prompts to store the password temporarily.
+ nsCString m_passwordResult;
+
+ // progress state information
+ void UpdateProgressPercent(int64_t totalDone, int64_t total);
+ void UpdateStatus(const char16_t *aStatusName);
+ void UpdateStatusWithString(const char16_t *aString);
+ nsresult FormatCounterString(const nsString &stringName,
+ uint32_t count1,
+ uint32_t count2,
+ nsString &resultString);
+
+ int32_t m_bytesInMsgReceived;
+ int64_t m_totalFolderSize;
+ int64_t m_totalDownloadSize; /* Number of bytes we're going to
+ download. Might be much less
+ than the total_folder_size. */
+ int64_t m_totalBytesReceived; // total # bytes received for the connection
+
+ virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length) override;
+ virtual int32_t Pop3SendData(const char * dataBuffer, bool aSuppressLogging = false);
+
+ virtual const char* GetType() override {return "pop3";}
+
+ nsCOMPtr<nsIURI> m_url;
+ nsCOMPtr<nsIPop3Sink> m_nsIPop3Sink;
+ nsCOMPtr<nsIPop3IncomingServer> m_pop3Server;
+
+ RefPtr<nsMsgLineStreamBuffer> m_lineStreamBuffer; // used to efficiently extract lines from the incoming data stream
+ Pop3ConData* m_pop3ConData;
+ void FreeMsgInfo();
+ void Abort();
+
+ bool m_tlsEnabled;
+ int32_t m_socketType;
+ bool m_password_already_sent;
+ bool m_needToRerunUrl;
+
+ void SetCapFlag(uint32_t flag);
+ void ClearCapFlag(uint32_t flag);
+ bool TestCapFlag(uint32_t flag);
+ uint32_t GetCapFlags();
+
+ void InitPrefAuthMethods(int32_t authMethodPrefValue);
+ nsresult ChooseAuthMethod();
+ void MarkAuthMethodAsFailed(int32_t failedAuthMethod);
+ void ResetAuthMethods();
+ int32_t m_prefAuthMethods; // set of capability flags for auth methods
+ int32_t m_failedAuthMethods; // ditto
+ int32_t m_currentAuthMethod; // exactly one capability flag, or 0
+
+ int32_t m_listpos;
+
+ nsresult HandleLine(char *line, uint32_t line_length);
+
+ nsresult GetApopTimestamp();
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ // Begin Pop3 protocol state handlers
+ //////////////////////////////////////////////////////////////////////////////////////////
+ int32_t WaitForStartOfConnectionResponse(nsIInputStream* inputStream,
+ uint32_t length);
+ int32_t WaitForResponse(nsIInputStream* inputStream,
+ uint32_t length);
+ int32_t Error(const char* err_code, const char16_t **params = nullptr,
+ uint32_t length = 0);
+ int32_t SendAuth();
+ int32_t AuthResponse(nsIInputStream* inputStream, uint32_t length);
+ int32_t SendCapa();
+ int32_t CapaResponse(nsIInputStream* inputStream, uint32_t length);
+ int32_t SendTLSResponse();
+ int32_t ProcessAuth();
+ int32_t NextAuthStep();
+ int32_t AuthLogin();
+ int32_t AuthLoginResponse();
+ int32_t AuthNtlm();
+ int32_t AuthNtlmResponse();
+ int32_t AuthGSSAPI();
+ int32_t AuthGSSAPIResponse(bool first);
+ int32_t SendUsername();
+ int32_t SendPassword();
+ int32_t SendStatOrGurl(bool sendStat);
+ int32_t SendStat();
+ int32_t GetStat();
+ int32_t SendGurl();
+ int32_t GurlResponse();
+ int32_t SendList();
+ int32_t GetList(nsIInputStream* inputStream, uint32_t length);
+ int32_t HandleNoUidListAvailable();
+ int32_t SendXtndXlstMsgid();
+ int32_t GetXtndXlstMsgid(nsIInputStream* inputStream, uint32_t length);
+ int32_t SendUidlList();
+ int32_t GetUidlList(nsIInputStream* inputStream, uint32_t length);
+ int32_t GetMsg();
+ int32_t SendTop();
+ int32_t SendXsender();
+ int32_t XsenderResponse();
+ int32_t SendRetr();
+
+ int32_t RetrResponse(nsIInputStream* inputStream, uint32_t length);
+ int32_t TopResponse(nsIInputStream* inputStream, uint32_t length);
+ int32_t SendDele();
+ int32_t DeleResponse();
+ int32_t CommitState(bool remove_last_entry);
+
+ Pop3StatesEnum GetNextPasswordObtainState();
+ nsresult RerunUrl();
+};
+
+#endif /* nsPop3Protocol_h__ */
diff --git a/mailnews/local/src/nsPop3Service.cpp b/mailnews/local/src/nsPop3Service.cpp
new file mode 100644
index 000000000..d0177b559
--- /dev/null
+++ b/mailnews/local/src/nsPop3Service.cpp
@@ -0,0 +1,711 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+
+#include "nsPop3Service.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIPop3IncomingServer.h"
+
+#include "nsPop3URL.h"
+#include "nsPop3Sink.h"
+#include "nsPop3Protocol.h"
+#include "nsMsgLocalCID.h"
+#include "nsMsgBaseCID.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgWindow.h"
+#include "nsINetUtil.h"
+
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsIDirectoryService.h"
+#include "nsMailDirServiceDefs.h"
+#include "prprf.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgAccount.h"
+#include "nsLocalMailFolder.h"
+#include "nsIMailboxUrl.h"
+#include "nsIPrompt.h"
+#include "nsINetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+
+#define PREF_MAIL_ROOT_POP3 "mail.root.pop3" // old - for backward compatibility only
+#define PREF_MAIL_ROOT_POP3_REL "mail.root.pop3-rel"
+
+static NS_DEFINE_CID(kPop3UrlCID, NS_POP3URL_CID);
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+nsPop3Service::nsPop3Service()
+{
+}
+
+nsPop3Service::~nsPop3Service()
+{}
+
+NS_IMPL_ISUPPORTS(nsPop3Service,
+ nsIPop3Service,
+ nsIProtocolHandler,
+ nsIMsgProtocolInfo)
+
+NS_IMETHODIMP nsPop3Service::CheckForNewMail(nsIMsgWindow* aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ nsIMsgFolder *aInbox,
+ nsIPop3IncomingServer *aPopServer,
+ nsIURI **aURL)
+{
+ return GetMail(false /* don't download, just check */,
+ aMsgWindow, aUrlListener, aInbox, aPopServer, aURL);
+}
+
+
+nsresult nsPop3Service::GetNewMail(nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ nsIMsgFolder *aInbox,
+ nsIPop3IncomingServer *aPopServer,
+ nsIURI **aURL)
+{
+ return GetMail(true /* download */,
+ aMsgWindow, aUrlListener, aInbox, aPopServer, aURL);
+}
+
+nsresult nsPop3Service::GetMail(bool downloadNewMail,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ nsIMsgFolder *aInbox,
+ nsIPop3IncomingServer *aPopServer,
+ nsIURI **aURL)
+{
+
+ NS_ENSURE_ARG_POINTER(aInbox);
+ int32_t popPort = -1;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsIURI> url;
+
+ server = do_QueryInterface(aPopServer);
+ NS_ENSURE_TRUE(server, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ nsCOMPtr<nsIMsgLocalMailFolder> destLocalFolder = do_QueryInterface(aInbox);
+ if (destLocalFolder)
+ {
+ // We don't know the needed size yet, so at least check
+ // if there is some free space (1MB) in the message store.
+ bool destFolderTooBig;
+ destLocalFolder->WarnIfLocalFileTooBig(aMsgWindow, 0xFFFF, &destFolderTooBig);
+ if (destFolderTooBig)
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+
+ nsCString popHost;
+ nsCString popUser;
+ nsresult rv = server->GetHostName(popHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (popHost.IsEmpty())
+ return NS_MSG_INVALID_OR_MISSING_SERVER;
+
+ rv = server->GetPort(&popPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = server->GetUsername(popUser);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (popUser.IsEmpty())
+ return NS_MSG_SERVER_USERNAME_MISSING;
+
+ nsCString escapedUsername;
+ MsgEscapeString(popUser, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+
+ if (NS_SUCCEEDED(rv) && aPopServer)
+ {
+ // now construct a pop3 url...
+ // we need to escape the username because it may contain
+ // characters like / % or @
+ char * urlSpec = (downloadNewMail)
+ ? PR_smprintf("pop3://%s@%s:%d", escapedUsername.get(), popHost.get(), popPort)
+ : PR_smprintf("pop3://%s@%s:%d/?check", escapedUsername.get(), popHost.get(), popPort);
+ rv = BuildPop3Url(urlSpec, aInbox, aPopServer, aUrlListener, getter_AddRefs(url), aMsgWindow);
+ PR_smprintf_free(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ENSURE_TRUE(url, rv);
+
+ if (NS_SUCCEEDED(rv))
+ rv = RunPopUrl(server, url);
+
+ if (aURL) // we already have a ref count on pop3url...
+ NS_IF_ADDREF(*aURL = url);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsPop3Service::VerifyLogon(nsIMsgIncomingServer *aServer,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+ nsCString popHost;
+ nsCString popUser;
+ int32_t popPort = -1;
+
+ nsresult rv = aServer->GetHostName(popHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (popHost.IsEmpty())
+ return NS_MSG_INVALID_OR_MISSING_SERVER;
+
+ rv = aServer->GetPort(&popPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aServer->GetUsername(popUser);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (popUser.IsEmpty())
+ return NS_MSG_SERVER_USERNAME_MISSING;
+
+ nsCString escapedUsername;
+ MsgEscapeString(popUser, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+
+ nsCOMPtr<nsIPop3IncomingServer> popServer = do_QueryInterface(aServer, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // now construct a pop3 url...
+ // we need to escape the username because it may contain
+ // characters like / % or @
+ char *urlSpec = PR_smprintf("pop3://%s@%s:%d/?verifyLogon",
+ escapedUsername.get(), popHost.get(), popPort);
+ NS_ENSURE_TRUE(urlSpec, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMPtr<nsIURI> url;
+ rv = BuildPop3Url(urlSpec, nullptr, popServer, aUrlListener,
+ getter_AddRefs(url), aMsgWindow);
+ PR_smprintf_free(urlSpec);
+
+ if (NS_SUCCEEDED(rv) && url)
+ {
+ rv = RunPopUrl(aServer, url);
+ if (NS_SUCCEEDED(rv) && aURL)
+ url.forget(aURL);
+ }
+
+ return rv;
+}
+
+nsresult nsPop3Service::BuildPop3Url(const char *urlSpec,
+ nsIMsgFolder *inbox,
+ nsIPop3IncomingServer *server,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aUrl,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+
+ nsPop3Sink *pop3Sink = new nsPop3Sink();
+
+ NS_ENSURE_TRUE(pop3Sink, NS_ERROR_OUT_OF_MEMORY);
+
+ pop3Sink->SetPopServer(server);
+ pop3Sink->SetFolder(inbox);
+
+ // now create a pop3 url and a protocol instance to run the url....
+ nsCOMPtr<nsIPop3URL> pop3Url = do_CreateInstance(kPop3UrlCID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ pop3Url->SetPop3Sink(pop3Sink);
+
+ rv = CallQueryInterface(pop3Url, aUrl);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = (*aUrl)->SetSpec(nsDependentCString(urlSpec));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(pop3Url);
+ if (mailnewsurl)
+ {
+ if (aUrlListener)
+ mailnewsurl->RegisterListener(aUrlListener);
+ if (aMsgWindow)
+ mailnewsurl->SetMsgWindow(aMsgWindow);
+ }
+
+ return rv;
+}
+
+nsresult nsPop3Service::RunPopUrl(nsIMsgIncomingServer *aServer, nsIURI *aUrlToRun)
+{
+
+ NS_ENSURE_ARG_POINTER(aServer);
+ NS_ENSURE_ARG_POINTER(aUrlToRun);
+
+ nsCString userName;
+
+ // load up required server information
+ // we store the username unescaped in the server
+ // so there is no need to unescape it
+ nsresult rv = aServer->GetRealUsername(userName);
+
+ // find out if the server is busy or not...if the server is busy, we are
+ // *NOT* going to run the url
+ bool serverBusy = false;
+ rv = aServer->GetServerBusy(&serverBusy);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!serverBusy)
+ {
+ RefPtr<nsPop3Protocol> protocol = new nsPop3Protocol(aUrlToRun);
+ if (protocol)
+ {
+ // the protocol stores the unescaped username, so there is no need to escape it.
+ protocol->SetUsername(userName.get());
+ rv = protocol->LoadUrl(aUrlToRun);
+ if (NS_FAILED(rv))
+ aServer->SetServerBusy(false);
+ }
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(aUrlToRun);
+ if (url)
+ AlertServerBusy(url);
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP nsPop3Service::GetScheme(nsACString &aScheme)
+{
+ aScheme.AssignLiteral("pop3");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Service::GetDefaultPort(int32_t *aDefaultPort)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultPort);
+ *aDefaultPort = nsIPop3URL::DEFAULT_POP3_PORT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Service::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ *_retval = true; // allow pop on any port
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Service::GetDefaultDoBiff(bool *aDoBiff)
+{
+ NS_ENSURE_ARG_POINTER(aDoBiff);
+ // by default, do biff for POP3 servers
+ *aDoBiff = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Service::GetProtocolFlags(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = URI_NORELATIVE | URI_DANGEROUS_TO_LOAD | ALLOWS_PROXY |
+ URI_FORBIDS_COOKIE_ACCESS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Service::NewURI(const nsACString &aSpec,
+ const char *aOriginCharset, // ignored
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsAutoCString folderUri(aSpec);
+ nsCOMPtr<nsIRDFResource> resource;
+ int32_t offset = folderUri.FindChar('?');
+ if (offset != kNotFound)
+ folderUri.SetLength(offset);
+
+ const char *uidl = PL_strstr(nsCString(aSpec).get(), "uidl=");
+ NS_ENSURE_TRUE(uidl, NS_ERROR_FAILURE);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIRDFService> rdfService(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = rdfService->GetResource(folderUri, getter_AddRefs(resource));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(resource, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ nsLocalFolderScanState folderScanState;
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder);
+ nsCOMPtr<nsIMailboxUrl> mailboxUrl = do_QueryInterface(aBaseURI);
+
+ if (mailboxUrl && localFolder)
+ {
+ rv = localFolder->GetFolderScanState(&folderScanState);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsMsgKey msgKey;
+ mailboxUrl->GetMessageKey(&msgKey);
+ folder->GetMessageHeader(msgKey, getter_AddRefs(msgHdr));
+ // we do this to get the account key
+ if (msgHdr)
+ localFolder->GetUidlFromFolder(&folderScanState, msgHdr);
+ if (!folderScanState.m_accountKey.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (accountManager)
+ {
+ nsCOMPtr<nsIMsgAccount> account;
+ accountManager->GetAccount(folderScanState.m_accountKey, getter_AddRefs(account));
+ if (account)
+ account->GetIncomingServer(getter_AddRefs(server));
+ }
+ }
+ }
+
+ if (!server)
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPop3IncomingServer> popServer = do_QueryInterface(server,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString hostname;
+ nsCString username;
+ server->GetHostName(hostname);
+ server->GetUsername(username);
+
+ int32_t port;
+ server->GetPort(&port);
+ if (port == -1) port = nsIPop3URL::DEFAULT_POP3_PORT;
+
+ // we need to escape the username because it may contain
+ // characters like / % or @
+ nsCString escapedUsername;
+ MsgEscapeString(username, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+
+ nsAutoCString popSpec("pop://");
+ popSpec += escapedUsername;
+ popSpec += "@";
+ popSpec += hostname;
+ popSpec += ":";
+ popSpec.AppendInt(port);
+ popSpec += "?";
+ popSpec += uidl;
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = BuildPop3Url(popSpec.get(), folder, popServer,
+ urlListener, _retval, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(*_retval, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ // escape the username before we call SetUsername(). we do this because GetUsername()
+ // will unescape the username
+ mailnewsurl->SetUsername(escapedUsername);
+ }
+
+ nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(mailnewsurl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString messageUri (aSpec);
+ if (!strncmp(messageUri.get(), "mailbox:", 8))
+ messageUri.Replace(0, 8, "mailbox-message:");
+ offset = messageUri.Find("?number=");
+ if (offset != kNotFound)
+ messageUri.Replace(offset, 8, "#");
+ offset = messageUri.FindChar('&');
+ if (offset != kNotFound)
+ messageUri.SetLength(offset);
+ popurl->SetMessageUri(messageUri.get());
+ nsCOMPtr<nsIPop3Sink> pop3Sink;
+ rv = popurl->GetPop3Sink(getter_AddRefs(pop3Sink));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pop3Sink->SetBuildMessageUri(true);
+
+ return NS_OK;
+}
+
+void nsPop3Service::AlertServerBusy(nsIMsgMailNewsUrl *url)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService)
+ return;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIPrompt> dialog;
+ rv = url->GetMsgWindow(getter_AddRefs(msgWindow)); //it is ok to have null msgWindow, for example when biffing
+ if (NS_FAILED(rv) || !msgWindow)
+ return;
+
+ rv = msgWindow->GetPromptDialog(getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsString accountName;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = url->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = server->GetPrettyName(accountName);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ const char16_t *params[] = { accountName.get() };
+ nsString alertString;
+ nsString dialogTitle;
+ bundle->FormatStringFromName(
+ u"pop3ServerBusy",
+ params, 1, getter_Copies(alertString));
+ bundle->FormatStringFromName(
+ u"pop3ErrorDialogTitle",
+ params, 1, getter_Copies(dialogTitle));
+ if (!alertString.IsEmpty())
+ dialog->Alert(dialogTitle.get(), alertString.get());
+}
+
+NS_IMETHODIMP nsPop3Service::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+NS_IMETHODIMP nsPop3Service::NewChannel2(nsIURI *aURI,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel **_retval)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(aURI, &rv);
+ nsCString realUserName;
+ if (NS_SUCCEEDED(rv) && url)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ url->GetServer(getter_AddRefs(server));
+ if (server)
+ {
+ // find out if the server is busy or not...if the server is busy, we are
+ // *NOT* going to run the url. The error code isn't quite right...
+ // We might want to put up an error right here.
+ bool serverBusy = false;
+ rv = server->GetServerBusy(&serverBusy);
+ if (serverBusy)
+ {
+ AlertServerBusy(url);
+ return NS_MSG_FOLDER_BUSY;
+ }
+ server->GetRealUsername(realUserName);
+ }
+ }
+
+ RefPtr<nsPop3Protocol> protocol = new nsPop3Protocol(aURI);
+ NS_ENSURE_TRUE(protocol, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = protocol->Initialize(aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = protocol->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ protocol->SetUsername(realUserName.get());
+
+ return CallQueryInterface(protocol, _retval);
+}
+
+
+NS_IMETHODIMP
+nsPop3Service::SetDefaultLocalPath(nsIFile *aPath)
+{
+ NS_ENSURE_ARG(aPath);
+ return NS_SetPersistentFile(PREF_MAIL_ROOT_POP3_REL, PREF_MAIL_ROOT_POP3, aPath);
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetDefaultLocalPath(nsIFile **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ bool havePref;
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_GetPersistentFile(PREF_MAIL_ROOT_POP3_REL,
+ PREF_MAIL_ROOT_POP3,
+ NS_APP_MAIL_50_DIR,
+ havePref,
+ getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = localFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!havePref || !exists) {
+ rv = NS_SetPersistentFile(PREF_MAIL_ROOT_POP3_REL, PREF_MAIL_ROOT_POP3, localFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
+ }
+
+ NS_IF_ADDREF(*aResult = localFile);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsPop3Service::GetServerIID(nsIID **aServerIID)
+{
+ *aServerIID = new nsIID(NS_GET_IID(nsIPop3IncomingServer));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetRequiresUsername(bool *aRequiresUsername)
+{
+ NS_ENSURE_ARG_POINTER(aRequiresUsername);
+ *aRequiresUsername = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress)
+{
+ NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress);
+ *aPreflightPrettyNameWithEmailAddress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp)
+{
+ NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp);
+ *aCanLoginAtStartUp = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetCanDelete(bool *aCanDelete)
+{
+ NS_ENSURE_ARG_POINTER(aCanDelete);
+ *aCanDelete = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetCanDuplicate(bool *aCanDuplicate)
+{
+ NS_ENSURE_ARG_POINTER(aCanDuplicate);
+ *aCanDuplicate = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetCanGetMessages(bool *aCanGetMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetMessages);
+ *aCanGetMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages);
+ *aCanGetIncomingMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetShowComposeMsgLink(bool *showComposeMsgLink)
+{
+ NS_ENSURE_ARG_POINTER(showComposeMsgLink);
+ *showComposeMsgLink = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetFoldersCreatedAsync(bool *aAsyncCreation)
+{
+ NS_ENSURE_ARG_POINTER(aAsyncCreation);
+ *aAsyncCreation = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::GetDefaultServerPort(bool isSecure, int32_t *aPort)
+{
+ NS_ENSURE_ARG_POINTER(aPort);
+
+ if (!isSecure)
+ return GetDefaultPort(aPort);
+
+ *aPort = nsIPop3URL::DEFAULT_POP3S_PORT;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::NotifyDownloadStarted(nsIMsgFolder *aFolder)
+{
+ nsTObserverArray<nsCOMPtr<nsIPop3ServiceListener> >::ForwardIterator
+ iter(mListeners);
+ nsCOMPtr<nsIPop3ServiceListener> listener;
+ while (iter.HasMore()) {
+ listener = iter.GetNext();
+ listener->OnDownloadStarted(aFolder);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::NotifyDownloadProgress(nsIMsgFolder *aFolder,
+ uint32_t aNumMessages,
+ uint32_t aNumTotalMessages)
+{
+ nsTObserverArray<nsCOMPtr<nsIPop3ServiceListener> >::ForwardIterator
+ iter(mListeners);
+ nsCOMPtr<nsIPop3ServiceListener> listener;
+ while (iter.HasMore()) {
+ listener = iter.GetNext();
+ listener->OnDownloadProgress(aFolder, aNumMessages, aNumTotalMessages);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Service::NotifyDownloadCompleted(nsIMsgFolder *aFolder,
+ uint32_t aNumMessages)
+{
+ nsTObserverArray<nsCOMPtr<nsIPop3ServiceListener> >::ForwardIterator
+ iter(mListeners);
+ nsCOMPtr<nsIPop3ServiceListener> listener;
+ while (iter.HasMore()) {
+ listener = iter.GetNext();
+ listener->OnDownloadCompleted(aFolder, aNumMessages);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Service::AddListener(nsIPop3ServiceListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ mListeners.AppendElementUnlessExists(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Service::RemoveListener(nsIPop3ServiceListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsPop3Service.h b/mailnews/local/src/nsPop3Service.h
new file mode 100644
index 000000000..61dd9cf5c
--- /dev/null
+++ b/mailnews/local/src/nsPop3Service.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPop3Service_h___
+#define nsPop3Service_h___
+
+#include "nscore.h"
+
+#include "nsIPop3Service.h"
+#include "nsIPop3URL.h"
+#include "nsIUrlListener.h"
+#include "nsIStreamListener.h"
+#include "nsIProtocolHandler.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsTObserverArray.h"
+
+class nsIMsgMailNewsUrl;
+
+class nsPop3Service : public nsIPop3Service,
+ public nsIProtocolHandler,
+ public nsIMsgProtocolInfo
+{
+public:
+
+ nsPop3Service();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPOP3SERVICE
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIMSGPROTOCOLINFO
+
+protected:
+ virtual ~nsPop3Service();
+ nsresult GetMail(bool downloadNewMail,
+ nsIMsgWindow* aMsgWindow,
+ nsIUrlListener * aUrlListener,
+ nsIMsgFolder *inbox,
+ nsIPop3IncomingServer *popServer,
+ nsIURI ** aURL);
+ // convience function to make constructing of the pop3 url easier...
+ nsresult BuildPop3Url(const char * urlSpec, nsIMsgFolder *inbox,
+ nsIPop3IncomingServer *, nsIUrlListener * aUrlListener,
+ nsIURI ** aUrl, nsIMsgWindow *aMsgWindow);
+
+ nsresult RunPopUrl(nsIMsgIncomingServer * aServer, nsIURI * aUrlToRun);
+ void AlertServerBusy(nsIMsgMailNewsUrl *url);
+ nsTObserverArray<nsCOMPtr<nsIPop3ServiceListener> > mListeners;
+};
+
+#endif /* nsPop3Service_h___ */
diff --git a/mailnews/local/src/nsPop3Sink.cpp b/mailnews/local/src/nsPop3Sink.cpp
new file mode 100644
index 000000000..af68ea2be
--- /dev/null
+++ b/mailnews/local/src/nsPop3Sink.cpp
@@ -0,0 +1,1026 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nsPop3Sink.h"
+#include "prprf.h"
+#include "prlog.h"
+#include "nscore.h"
+#include <stdio.h>
+#include <time.h>
+#include "nsParseMailbox.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsLocalUtils.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsMailHeaders.h"
+#include "nsIMsgAccountManager.h"
+#include "nsILineInputStream.h"
+#include "nsIPop3Protocol.h"
+#include "nsLocalMailFolder.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIPrompt.h"
+#include "nsIPromptService.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDocShell.h"
+#include "mozIDOMWindow.h"
+#include "nsEmbedCID.h"
+#include "nsMsgUtils.h"
+#include "nsMsgBaseCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIPop3Service.h"
+#include "nsMsgLocalCID.h"
+#include "mozilla/Services.h"
+#include "mozilla/Logging.h"
+
+/* for logging to Error Console */
+#include "nsIScriptError.h"
+#include "nsIConsoleService.h"
+
+extern PRLogModuleInfo *POP3LOGMODULE; // defined in nsPop3Protocol.cpp
+#define POP3LOG(str) "%s sink: [this=%p] " str, POP3LOGMODULE->name, this
+
+NS_IMPL_ISUPPORTS(nsPop3Sink, nsIPop3Sink)
+
+nsPop3Sink::nsPop3Sink()
+{
+ m_authed = false;
+ m_downloadingToTempFile = false;
+ m_biffState = 0;
+ m_numNewMessages = 0;
+ m_numNewMessagesInFolder = 0;
+ m_numMsgsDownloaded = 0;
+ m_senderAuthed = false;
+ m_outFileStream = nullptr;
+ m_uidlDownload = false;
+ m_buildMessageUri = false;
+ if (!POP3LOGMODULE)
+ POP3LOGMODULE = PR_NewLogModule("POP3");
+}
+
+nsPop3Sink::~nsPop3Sink()
+{
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("Calling ReleaseFolderLock from ~nsPop3Sink")));
+ ReleaseFolderLock();
+}
+
+nsresult
+nsPop3Sink::SetUserAuthenticated(bool authed)
+{
+ m_authed = authed;
+ m_popServer->SetAuthenticated(authed);
+ return NS_OK;
+}
+
+nsresult
+nsPop3Sink::GetUserAuthenticated(bool* authed)
+{
+ return m_popServer->GetAuthenticated(authed);
+}
+
+nsresult
+nsPop3Sink::SetSenderAuthedFlag(void* closure, bool authed)
+{
+ m_authed = authed;
+ return NS_OK;
+}
+
+nsresult
+nsPop3Sink::SetMailAccountURL(const nsACString &urlString)
+{
+ m_accountUrl.Assign(urlString);
+ return NS_OK;
+}
+
+nsresult
+nsPop3Sink::GetMailAccountURL(nsACString &urlString)
+{
+ urlString.Assign(m_accountUrl);
+ return NS_OK;
+}
+
+partialRecord::partialRecord() :
+ m_msgDBHdr(nullptr)
+{
+}
+
+partialRecord::~partialRecord()
+{
+}
+
+// Walk through all the messages in this folder and look for any
+// PARTIAL messages. For each of those, dig thru the mailbox and
+// find the Account that the message belongs to. If that Account
+// matches the current Account, then look for the Uidl and save
+// this message for later processing.
+nsresult
+nsPop3Sink::FindPartialMessages()
+{
+ nsCOMPtr<nsISimpleEnumerator> messages;
+ bool hasMore = false;
+ bool isOpen = false;
+ nsLocalFolderScanState folderScanState;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+ m_folder->GetMsgDatabase(getter_AddRefs(db));
+ if (!localFolder || !db)
+ return NS_ERROR_FAILURE; // we need it to grub thru the folder
+
+ nsresult rv = db->EnumerateMessages(getter_AddRefs(messages));
+ if (messages)
+ messages->HasMoreElements(&hasMore);
+ while(hasMore && NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsISupports> aSupport;
+ uint32_t flags = 0;
+ rv = messages->GetNext(getter_AddRefs(aSupport));
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr(do_QueryInterface(aSupport, &rv));
+ msgDBHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial)
+ {
+ // Open the various streams we need to seek and read from the mailbox
+ if (!isOpen)
+ {
+ rv = localFolder->GetFolderScanState(&folderScanState);
+ if (NS_SUCCEEDED(rv))
+ isOpen = true;
+ else
+ break;
+ }
+ rv = localFolder->GetUidlFromFolder(&folderScanState, msgDBHdr);
+ if (!NS_SUCCEEDED(rv))
+ break;
+
+ // If we got the uidl, see if this partial message belongs to this
+ // account. Add it to the array if so...
+ if (folderScanState.m_uidl &&
+ m_accountKey.Equals(folderScanState.m_accountKey, nsCaseInsensitiveCStringComparator()))
+ {
+ partialRecord *partialMsg = new partialRecord();
+ if (partialMsg)
+ {
+ partialMsg->m_uidl = folderScanState.m_uidl;
+ partialMsg->m_msgDBHdr = msgDBHdr;
+ m_partialMsgsArray.AppendElement(partialMsg);
+ }
+ }
+ }
+ messages->HasMoreElements(&hasMore);
+ }
+ if (isOpen && folderScanState.m_inputStream)
+ folderScanState.m_inputStream->Close();
+ return rv;
+}
+
+// For all the partial messages saved by FindPartialMessages,
+// ask the protocol handler if they still exist on the server.
+// Any messages that don't exist any more are deleted from the
+// msgDB.
+void
+nsPop3Sink::CheckPartialMessages(nsIPop3Protocol *protocol)
+{
+ uint32_t count = m_partialMsgsArray.Length();
+ bool deleted = false;
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ partialRecord *partialMsg;
+ bool found = true;
+ partialMsg = m_partialMsgsArray.ElementAt(i);
+ protocol->CheckMessage(partialMsg->m_uidl.get(), &found);
+ if (!found && partialMsg->m_msgDBHdr)
+ {
+ if (m_newMailParser)
+ m_newMailParser->m_mailDB->DeleteHeader(partialMsg->m_msgDBHdr, nullptr, false, true);
+ deleted = true;
+ }
+ delete partialMsg;
+ }
+ m_partialMsgsArray.Clear();
+ if (deleted)
+ {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+ if (localFolder)
+ localFolder->NotifyDelete();
+ }
+}
+
+nsresult
+nsPop3Sink::BeginMailDelivery(bool uidlDownload, nsIMsgWindow *aMsgWindow, bool* aBool)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer);
+ if (!server)
+ return NS_ERROR_UNEXPECTED;
+
+ m_window = aMsgWindow;
+
+ nsCOMPtr <nsIMsgAccountManager> acctMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ nsCOMPtr <nsIMsgAccount> account;
+ NS_ENSURE_SUCCESS(rv, rv);
+ acctMgr->FindAccountForServer(server, getter_AddRefs(account));
+ if (account)
+ account->GetKey(m_accountKey);
+
+ bool isLocked;
+ nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIPop3Sink*>(this));
+ m_folder->GetLocked(&isLocked);
+ if(!isLocked)
+ {
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("BeginMailDelivery acquiring semaphore")));
+ m_folder->AcquireSemaphore(supports);
+ }
+ else
+ {
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("BeginMailDelivery folder locked")));
+ return NS_MSG_FOLDER_BUSY;
+ }
+ m_uidlDownload = uidlDownload;
+ if (!uidlDownload)
+ FindPartialMessages();
+
+ m_folder->GetNumNewMessages(false, &m_numNewMessagesInFolder);
+
+#ifdef DEBUG
+ printf("Begin mail message delivery.\n");
+#endif
+ nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pop3Service->NotifyDownloadStarted(m_folder);
+ if (aBool)
+ *aBool = true;
+ return NS_OK;
+}
+
+nsresult
+nsPop3Sink::EndMailDelivery(nsIPop3Protocol *protocol)
+{
+ CheckPartialMessages(protocol);
+
+ if (m_newMailParser)
+ {
+ if (m_outFileStream)
+ m_outFileStream->Flush(); // try this.
+ m_newMailParser->OnStopRequest(nullptr, nullptr, NS_OK);
+ m_newMailParser->EndMsgDownload();
+ }
+ if (m_outFileStream)
+ {
+ m_outFileStream->Close();
+ m_outFileStream = nullptr;
+ }
+
+ if (m_downloadingToTempFile)
+ m_tmpDownloadFile->Remove(false);
+
+ // tell the parser to mark the db valid *after* closing the mailbox.
+ if (m_newMailParser)
+ m_newMailParser->UpdateDBFolderInfo();
+
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("Calling ReleaseFolderLock from EndMailDelivery")));
+ nsresult rv = ReleaseFolderLock();
+ NS_ASSERTION(NS_SUCCEEDED(rv),"folder lock not released successfully");
+
+ bool filtersRun;
+ m_folder->CallFilterPlugins(nullptr, &filtersRun); // ??? do we need msgWindow?
+ int32_t numNewMessagesInFolder;
+ // if filters have marked msgs read or deleted, the num new messages count
+ // will go negative by the number of messages marked read or deleted,
+ // so if we add that number to the number of msgs downloaded, that will give
+ // us the number of actual new messages.
+ m_folder->GetNumNewMessages(false, &numNewMessagesInFolder);
+ m_numNewMessages -= (m_numNewMessagesInFolder - numNewMessagesInFolder);
+ m_folder->SetNumNewMessages(m_numNewMessages); // we'll adjust this for spam later
+ if (!filtersRun && m_numNewMessages > 0)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ m_folder->GetServer(getter_AddRefs(server));
+ if (server)
+ {
+ server->SetPerformingBiff(true);
+ m_folder->SetBiffState(m_biffState);
+ server->SetPerformingBiff(false);
+ }
+ }
+ // note that size on disk has possibly changed.
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+ if (localFolder)
+ (void) localFolder->RefreshSizeOnDisk();
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer);
+ if (server)
+ {
+ nsCOMPtr <nsIMsgFilterList> filterList;
+ rv = server->GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (filterList)
+ (void) filterList->FlushLogIfNecessary();
+ }
+
+ // fix for bug #161999
+ // we should update the summary totals for the folder (inbox)
+ // in case it's not the open folder
+ m_folder->UpdateSummaryTotals(true);
+
+ // check if the folder open in this window is not the current folder, and if it has new
+ // message, in which case we need to try to run the filter plugin.
+ if (m_newMailParser)
+ {
+ nsCOMPtr <nsIMsgWindow> msgWindow;
+ m_newMailParser->GetMsgWindow(getter_AddRefs(msgWindow));
+ // this breaks down if it's biff downloading new mail because
+ // there's no msgWindow...
+ if (msgWindow)
+ {
+ nsCOMPtr <nsIMsgFolder> openFolder;
+ (void) msgWindow->GetOpenFolder(getter_AddRefs(openFolder));
+ if (openFolder && openFolder != m_folder)
+ {
+ // only call filter plugins if folder is a local folder, because only
+ // local folders get messages filtered into them synchronously by pop3.
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(openFolder);
+ if (localFolder)
+ {
+ bool hasNew, isLocked;
+ (void) openFolder->GetHasNewMessages(&hasNew);
+ if (hasNew)
+ {
+ // if the open folder is locked, we shouldn't run the spam filters
+ // on it because someone is using the folder. see 218433.
+ // Ideally, the filter plugin code would try to grab the folder lock
+ // and hold onto it until done, but that's more difficult and I think
+ // this will actually fix the problem.
+ openFolder->GetLocked(&isLocked);
+ if(!isLocked)
+ openFolder->CallFilterPlugins(nullptr, &filtersRun);
+ }
+ }
+ }
+ }
+ }
+#ifdef DEBUG
+ printf("End mail message delivery.\n");
+#endif
+ nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pop3Service->NotifyDownloadCompleted(m_folder, m_numNewMessages);
+ return NS_OK;
+}
+
+nsresult
+nsPop3Sink::ReleaseFolderLock()
+{
+ nsresult result = NS_OK;
+ if (!m_folder)
+ return result;
+ bool haveSemaphore;
+ nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIPop3Sink*>(this));
+ result = m_folder->TestSemaphore(supports, &haveSemaphore);
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("ReleaseFolderLock haveSemaphore = %s"), haveSemaphore ? "TRUE" : "FALSE"));
+
+ if(NS_SUCCEEDED(result) && haveSemaphore)
+ result = m_folder->ReleaseSemaphore(supports);
+ return result;
+}
+
+nsresult
+nsPop3Sink::AbortMailDelivery(nsIPop3Protocol *protocol)
+{
+ CheckPartialMessages(protocol);
+
+ // ### PS TODO - discard any new message?
+
+ if (m_outFileStream)
+ {
+ m_outFileStream->Close();
+ m_outFileStream = nullptr;
+ }
+
+ if (m_downloadingToTempFile && m_tmpDownloadFile)
+ m_tmpDownloadFile->Remove(false);
+
+ /* tell the parser to mark the db valid *after* closing the mailbox.
+ we have truncated the inbox, so berkeley mailbox and msf file are in sync*/
+ if (m_newMailParser)
+ m_newMailParser->UpdateDBFolderInfo();
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("Calling ReleaseFolderLock from AbortMailDelivery")));
+
+ nsresult rv = ReleaseFolderLock();
+ NS_ASSERTION(NS_SUCCEEDED(rv),"folder lock not released successfully");
+
+#ifdef DEBUG
+ printf("Abort mail message delivery.\n");
+#endif
+ nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pop3Service->NotifyDownloadCompleted(m_folder, 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::IncorporateBegin(const char* uidlString,
+ nsIURI* aURL,
+ uint32_t flags,
+ void** closure)
+{
+#ifdef DEBUG
+ printf("Incorporate message begin:\n");
+ if (uidlString)
+ printf("uidl string: %s\n", uidlString);
+#endif
+ nsCOMPtr<nsIFile> path;
+
+ m_folder->GetFilePath(getter_AddRefs(path));
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (pPrefBranch)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ m_folder->GetServer(getter_AddRefs(server));
+ nsCString plugStoreContract;
+ server->GetCharValue("storeContractID", plugStoreContract);
+ // Maildir doesn't care about quaranting, but other stores besides berkeley
+ // mailbox might. We should probably make this an attribute on the pluggable
+ // store, though.
+ if (plugStoreContract.Equals(
+ NS_LITERAL_CSTRING("@mozilla.org/msgstore/berkeleystore;1")))
+ pPrefBranch->GetBoolPref("mailnews.downloadToTempFile", &m_downloadingToTempFile);
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer);
+ if (!server)
+ return NS_ERROR_UNEXPECTED;
+
+ if (m_downloadingToTempFile)
+ {
+ // need to create an nsIOFileStream from a temp file...
+ nsCOMPtr<nsIFile> tmpDownloadFile;
+ rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "newmsg",
+ getter_AddRefs(tmpDownloadFile));
+
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "writing tmp pop3 download file: failed to append filename");
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!m_tmpDownloadFile)
+ {
+ //need a unique tmp file to prevent dataloss in multiuser environment
+ rv = tmpDownloadFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_tmpDownloadFile = do_QueryInterface(tmpDownloadFile, &rv);
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = MsgGetFileStream(m_tmpDownloadFile, getter_AddRefs(m_outFileStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ else
+ {
+ rv = server->GetMsgStore(getter_AddRefs(m_msgStore));
+ bool reusable;
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_msgStore->GetNewMsgOutputStream(m_folder, getter_AddRefs(newHdr),
+ &reusable, getter_AddRefs(m_outFileStream));
+ }
+ // The following (!m_outFileStream etc) was added to make sure that we don't
+ // write somewhere where for some reason or another we can't write to and
+ // lose the messages. See bug 62480
+ if (!m_outFileStream)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsISeekableStream> seekableOutStream = do_QueryInterface(m_outFileStream);
+
+ // create a new mail parser
+ if (!m_newMailParser)
+ m_newMailParser = new nsParseNewMailState;
+ NS_ENSURE_TRUE(m_newMailParser, NS_ERROR_OUT_OF_MEMORY);
+ if (m_uidlDownload)
+ m_newMailParser->DisableFilters();
+
+ nsCOMPtr <nsIMsgFolder> serverFolder;
+ rv = GetServerFolder(getter_AddRefs(serverFolder));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = m_newMailParser->Init(serverFolder, m_folder,
+ m_window, newHdr, m_outFileStream);
+ // If we failed to initialize the parser, then just don't use it!!!
+ // We can still continue without one.
+
+ if (NS_FAILED(rv))
+ {
+ m_newMailParser = nullptr;
+ rv = NS_OK;
+ }
+
+ if (closure)
+ *closure = (void*) this;
+
+#ifdef DEBUG
+ // Debugging, see bug 1116055.
+ int64_t first_pre_seek_pos;
+ nsresult rv3 = seekableOutStream->Tell(&first_pre_seek_pos);
+#endif
+
+ // XXX Handle error such as network error for remote file system.
+ seekableOutStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+
+#ifdef DEBUG
+ // Debugging, see bug 1116055.
+ int64_t first_post_seek_pos;
+ nsresult rv4 = seekableOutStream->Tell(&first_post_seek_pos);
+ if (NS_SUCCEEDED(rv3) && NS_SUCCEEDED(rv4)) {
+ if (first_pre_seek_pos != first_post_seek_pos) {
+ nsCOMPtr<nsIMsgFolder> localFolder = do_QueryInterface(m_folder);
+ nsString folderName;
+ if (localFolder)
+ localFolder->GetPrettiestName(folderName);
+ if (!folderName.IsEmpty()) {
+ fprintf(stderr,"(seekdebug) Seek was necessary in IncorporateBegin() for folder %s.\n",
+ NS_ConvertUTF16toUTF8(folderName).get());
+ } else {
+ fprintf(stderr,"(seekdebug) Seek was necessary in IncorporateBegin().\n");
+ }
+ fprintf(stderr,"(seekdebug) first_pre_seek_pos = 0x%016llx, first_post_seek_pos=0x%016llx\n",
+ (unsigned long long) first_pre_seek_pos, (unsigned long long) first_post_seek_pos);
+ }
+ }
+#endif
+
+ nsCString outputString(GetDummyEnvelope());
+ rv = WriteLineToMailbox(outputString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Write out account-key before UIDL so the code that looks for
+ // UIDL will find the account first and know it can stop looking
+ // once it finds the UIDL line.
+ if (!m_accountKey.IsEmpty())
+ {
+ outputString.AssignLiteral(HEADER_X_MOZILLA_ACCOUNT_KEY ": ");
+ outputString.Append(m_accountKey);
+ outputString.AppendLiteral(MSG_LINEBREAK);
+ rv = WriteLineToMailbox(outputString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (uidlString)
+ {
+ outputString.AssignLiteral("X-UIDL: ");
+ outputString.Append(uidlString);
+ outputString.AppendLiteral(MSG_LINEBREAK);
+ rv = WriteLineToMailbox(outputString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // WriteLineToMailbox("X-Mozilla-Status: 8000" MSG_LINEBREAK);
+ char *statusLine = PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, flags);
+ outputString.Assign(statusLine);
+ rv = WriteLineToMailbox(outputString);
+ PR_smprintf_free(statusLine);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = WriteLineToMailbox(NS_LITERAL_CSTRING("X-Mozilla-Status2: 00000000" MSG_LINEBREAK));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // leave space for 60 bytes worth of keys/tags
+ rv = WriteLineToMailbox(NS_LITERAL_CSTRING(X_MOZILLA_KEYWORDS));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::SetPopServer(nsIPop3IncomingServer *server)
+{
+ m_popServer = server;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::GetPopServer(nsIPop3IncomingServer **aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+ NS_IF_ADDREF(*aServer = m_popServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Sink::GetFolder(nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_IF_ADDREF(*aFolder = m_folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Sink::SetFolder(nsIMsgFolder * aFolder)
+{
+ m_folder = aFolder;
+ return NS_OK;
+}
+
+nsresult
+nsPop3Sink::GetServerFolder(nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ if (m_popServer)
+ {
+ // not sure what this is used for - might be wrong if we have a deferred account.
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer = do_QueryInterface(m_popServer);
+ if (incomingServer)
+ return incomingServer->GetRootFolder(aFolder);
+ }
+ *aFolder = nullptr;
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsPop3Sink::SetMsgsToDownload(uint32_t aNumMessages)
+{
+ m_numNewMessages = aNumMessages;
+ return NS_OK;
+}
+
+char*
+nsPop3Sink::GetDummyEnvelope(void)
+{
+ static char result[75];
+ char *ct;
+ time_t now = time ((time_t *) 0);
+#if defined (XP_WIN)
+ if (now < 0 || now > 0x7FFFFFFF)
+ now = 0x7FFFFFFF;
+#endif
+ ct = ctime(&now);
+ PR_ASSERT(ct[24] == '\r' || ct[24] == '\n');
+ ct[24] = 0;
+ /* This value must be in ctime() format, with English abbreviations.
+ strftime("... %c ...") is no good, because it is localized. */
+ PL_strcpy(result, "From - ");
+ PL_strcpy(result + 7, ct);
+ PL_strcpy(result + 7 + 24, MSG_LINEBREAK);
+ return result;
+}
+
+nsresult
+nsPop3Sink::IncorporateWrite(const char* block,
+ int32_t length)
+{
+ m_outputBuffer.Truncate();
+ if (!strncmp(block, "From ", 5))
+ m_outputBuffer.Assign('>');
+
+ m_outputBuffer.Append(block);
+
+ return WriteLineToMailbox(m_outputBuffer);
+}
+
+nsresult nsPop3Sink::WriteLineToMailbox(const nsACString& buffer)
+{
+ if (!buffer.IsEmpty())
+ {
+ uint32_t bufferLen = buffer.Length();
+ if (m_newMailParser)
+ m_newMailParser->HandleLine(buffer.BeginReading(), bufferLen);
+ // The following (!m_outFileStream etc) was added to make sure that we don't write somewhere
+ // where for some reason or another we can't write to and lose the messages
+ // See bug 62480
+ NS_ENSURE_TRUE(m_outFileStream, NS_ERROR_OUT_OF_MEMORY);
+
+ // To remove seeking to the end for each line to be written, remove the
+ // following line. See bug 1116055 for details.
+#define SEEK_TO_END
+#ifdef SEEK_TO_END
+ // seek to the end in case someone else has seeked elsewhere in our stream.
+ nsCOMPtr <nsISeekableStream> seekableOutStream = do_QueryInterface(m_outFileStream);
+
+ int64_t before_seek_pos;
+ nsresult rv2 = seekableOutStream->Tell(&before_seek_pos);
+ MOZ_ASSERT(NS_SUCCEEDED(rv2), "seekableOutStream->Tell(&before_seek_pos) failed");
+
+ // XXX Handle error such as network error for remote file system.
+ seekableOutStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+
+ int64_t after_seek_pos;
+ nsresult rv3 = seekableOutStream->Tell(&after_seek_pos);
+ MOZ_ASSERT(NS_SUCCEEDED(rv3), "seekableOutStream->Tell(&after_seek_pos) failed");
+
+ if (NS_SUCCEEDED(rv2) && NS_SUCCEEDED(rv3)) {
+ if (before_seek_pos != after_seek_pos) {
+ nsCOMPtr<nsIMsgFolder> localFolder = do_QueryInterface(m_folder);
+ nsString folderName;
+ if (localFolder)
+ localFolder->GetPrettiestName(folderName);
+ // This merits a console message, it's poor man's telemetry.
+ MsgLogToConsole4(
+ NS_LITERAL_STRING("Unexpected file position change detected") +
+ (folderName.IsEmpty() ? EmptyString() : NS_LITERAL_STRING(" in folder ")) +
+ (folderName.IsEmpty() ? EmptyString() : folderName) + NS_LITERAL_STRING(". "
+ "If you can reliably reproduce this, please report the steps "
+ "you used to dev-apps-thunderbird@lists.mozilla.org or to bug 1308335 at bugzilla.mozilla.org. "
+ "Resolving this problem will allow speeding up message downloads."),
+ NS_LITERAL_STRING(__FILE__), __LINE__, nsIScriptError::errorFlag);
+#ifdef DEBUG
+ // Debugging, see bug 1116055.
+ if (!folderName.IsEmpty()) {
+ fprintf(stderr,"(seekdebug) WriteLineToMailbox() detected an unexpected file position change in folder %s.\n",
+ NS_ConvertUTF16toUTF8(folderName).get());
+ } else {
+ fprintf(stderr,"(seekdebug) WriteLineToMailbox() detected an unexpected file position change.\n");
+ }
+ fprintf(stderr,"(seekdebug) before_seek_pos=0x%016llx, after_seek_pos=0x%016llx\n",
+ (long long unsigned int) before_seek_pos, (long long unsigned int) after_seek_pos);
+#endif
+ }
+ }
+#endif
+
+ uint32_t bytesWritten;
+ m_outFileStream->Write(buffer.BeginReading(), bufferLen, &bytesWritten);
+ NS_ENSURE_TRUE(bytesWritten == bufferLen, NS_ERROR_FAILURE);
+ }
+ return NS_OK;
+}
+
+nsresult nsPop3Sink::HandleTempDownloadFailed(nsIMsgWindow *msgWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString fromStr, subjectStr, confirmString;
+
+ m_newMailParser->m_newMsgHdr->GetMime2DecodedSubject(subjectStr);
+ m_newMailParser->m_newMsgHdr->GetMime2DecodedAuthor(fromStr);
+ const char16_t *params[] = { fromStr.get(), subjectStr.get() };
+ bundle->FormatStringFromName(
+ u"pop3TmpDownloadError",
+ params, 2, getter_Copies(confirmString));
+ nsCOMPtr<mozIDOMWindowProxy> parentWindow;
+ nsCOMPtr<nsIPromptService> promptService = do_GetService(NS_PROMPTSERVICE_CONTRACTID);
+ nsCOMPtr<nsIDocShell> docShell;
+ if (msgWindow)
+ {
+ (void) msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ parentWindow = do_QueryInterface(docShell);
+ }
+ if (promptService && !confirmString.IsEmpty())
+ {
+ int32_t dlgResult = -1;
+ bool dummyValue = false;
+ rv = promptService->ConfirmEx(parentWindow, nullptr, confirmString.get(),
+ nsIPromptService::STD_YES_NO_BUTTONS,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ &dummyValue,
+ &dlgResult);
+ m_newMailParser->m_newMsgHdr = nullptr;
+
+ return (dlgResult == 0) ? NS_OK : NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD;
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsPop3Sink::IncorporateComplete(nsIMsgWindow *aMsgWindow, int32_t aSize)
+{
+ if (m_buildMessageUri && !m_baseMessageUri.IsEmpty() && m_newMailParser &&
+ m_newMailParser->m_newMsgHdr)
+ {
+ nsMsgKey msgKey;
+ m_newMailParser->m_newMsgHdr->GetMessageKey(&msgKey);
+ m_messageUri.Truncate();
+ nsBuildLocalMessageURI(m_baseMessageUri.get(), msgKey, m_messageUri);
+ }
+
+ nsresult rv = WriteLineToMailbox(NS_LITERAL_CSTRING(MSG_LINEBREAK));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool leaveOnServer = false;
+ m_popServer->GetLeaveMessagesOnServer(&leaveOnServer);
+ // We need to flush the output stream, in case mail filters move
+ // the new message, which relies on all the data being flushed.
+ rv = m_outFileStream->Flush(); // Make sure the message is written to the disk
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(m_newMailParser, "could not get m_newMailParser");
+ if (m_newMailParser)
+ {
+ // PublishMsgHdr clears m_newMsgHdr, so we need a comptr to
+ // hold onto it.
+ nsCOMPtr<nsIMsgDBHdr> hdr = m_newMailParser->m_newMsgHdr;
+ NS_ASSERTION(hdr, "m_newMailParser->m_newMsgHdr wasn't set");
+ if (!hdr)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+ bool doSelect = false;
+
+ // aSize is only set for partial messages. For full messages,
+ // check to see if we're replacing an old partial message.
+ if (!aSize && localFolder)
+ (void) localFolder->DeleteDownloadMsg(hdr, &doSelect);
+
+ // If a header already exists for this message (for example, when
+ // getting a complete message when a partial exists), then update the new
+ // header from the old.
+ if (!m_origMessageUri.IsEmpty() && localFolder)
+ {
+ nsCOMPtr <nsIMsgDBHdr> oldMsgHdr;
+ rv = GetMsgDBHdrFromURI(m_origMessageUri.get(), getter_AddRefs(oldMsgHdr));
+ if (NS_SUCCEEDED(rv) && oldMsgHdr)
+ localFolder->UpdateNewMsgHdr(oldMsgHdr, hdr);
+ }
+
+ if (m_downloadingToTempFile)
+ {
+ // close file to give virus checkers a chance to do their thing...
+ m_outFileStream->Flush();
+ m_outFileStream->Close();
+ m_newMailParser->FinishHeader();
+ // need to re-open the inbox file stream.
+ bool exists;
+ m_tmpDownloadFile->Exists(&exists);
+ if (!exists)
+ return HandleTempDownloadFailed(aMsgWindow);
+
+ nsCOMPtr <nsIInputStream> inboxInputStream = do_QueryInterface(m_outFileStream);
+ rv = MsgReopenFileStream(m_tmpDownloadFile, inboxInputStream);
+ NS_ENSURE_SUCCESS(rv, HandleTempDownloadFailed(aMsgWindow));
+ if (m_outFileStream)
+ {
+ int64_t tmpDownloadFileSize;
+ uint32_t msgSize;
+ hdr->GetMessageSize(&msgSize);
+ // we need to clone because nsLocalFileUnix caches its stat result,
+ // so it doesn't realize the file has changed size.
+ nsCOMPtr <nsIFile> tmpClone;
+ rv = m_tmpDownloadFile->Clone(getter_AddRefs(tmpClone));
+ NS_ENSURE_SUCCESS(rv, rv);
+ tmpClone->GetFileSize(&tmpDownloadFileSize);
+
+ if (msgSize > tmpDownloadFileSize)
+ rv = NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ else
+ rv = m_newMailParser->AppendMsgFromStream(inboxInputStream, hdr,
+ msgSize, m_folder);
+ if (NS_FAILED(rv))
+ return HandleTempDownloadFailed(aMsgWindow);
+
+ m_outFileStream->Close(); // close so we can truncate.
+ m_tmpDownloadFile->SetFileSize(0);
+ }
+ else
+ {
+ return HandleTempDownloadFailed(aMsgWindow);
+ // need to give an error here.
+ }
+ }
+ else
+ {
+ m_msgStore->FinishNewMessage(m_outFileStream, hdr);
+ }
+ m_newMailParser->PublishMsgHeader(aMsgWindow);
+ // run any reply/forward filter after we've finished with the
+ // temp quarantine file, and/or moved the message to another folder.
+ m_newMailParser->ApplyForwardAndReplyFilter(aMsgWindow);
+ if (aSize)
+ hdr->SetUint32Property("onlineSize", aSize);
+
+ // if DeleteDownloadMsg requested it, select the new message
+ else if (doSelect)
+ (void) localFolder->SelectDownloadMsg();
+ }
+
+#ifdef DEBUG
+ printf("Incorporate message complete.\n");
+#endif
+ nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pop3Service->NotifyDownloadProgress(m_folder, ++m_numMsgsDownloaded, m_numNewMessages);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::IncorporateAbort(bool uidlDownload)
+{
+ nsresult rv = m_outFileStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!m_downloadingToTempFile && m_msgStore && m_newMailParser &&
+ m_newMailParser->m_newMsgHdr)
+ {
+ m_msgStore->DiscardNewMessage(m_outFileStream,
+ m_newMailParser->m_newMsgHdr);
+ }
+#ifdef DEBUG
+ printf("Incorporate message abort.\n");
+#endif
+ return rv;
+}
+
+nsresult
+nsPop3Sink::BiffGetNewMail()
+{
+#ifdef DEBUG
+ printf("Biff get new mail.\n");
+#endif
+ return NS_OK;
+}
+
+nsresult
+nsPop3Sink::SetBiffStateAndUpdateFE(uint32_t aBiffState, int32_t numNewMessages, bool notify)
+{
+ m_biffState = aBiffState;
+ if (m_newMailParser)
+ numNewMessages -= m_newMailParser->m_numNotNewMessages;
+
+ if (notify && m_folder && numNewMessages > 0 && numNewMessages != m_numNewMessages
+ && aBiffState == nsIMsgFolder::nsMsgBiffState_NewMail)
+ {
+ m_folder->SetNumNewMessages(numNewMessages);
+ m_folder->SetBiffState(aBiffState);
+ }
+ m_numNewMessages = numNewMessages;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::GetBuildMessageUri(bool *bVal)
+{
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_buildMessageUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::SetBuildMessageUri(bool bVal)
+{
+ m_buildMessageUri = bVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::GetMessageUri(char **messageUri)
+{
+ NS_ENSURE_ARG_POINTER(messageUri);
+ NS_ENSURE_TRUE(!m_messageUri.IsEmpty(), NS_ERROR_FAILURE);
+ *messageUri = ToNewCString(m_messageUri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::SetMessageUri(const char *messageUri)
+{
+ NS_ENSURE_ARG_POINTER(messageUri);
+ m_messageUri = messageUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::GetBaseMessageUri(char ** baseMessageUri)
+{
+ NS_ENSURE_ARG_POINTER(baseMessageUri);
+ NS_ENSURE_TRUE(!m_baseMessageUri.IsEmpty(), NS_ERROR_FAILURE);
+ *baseMessageUri = ToNewCString(m_baseMessageUri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::SetBaseMessageUri(const char *baseMessageUri)
+{
+ NS_ENSURE_ARG_POINTER(baseMessageUri);
+ m_baseMessageUri = baseMessageUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::GetOrigMessageUri(nsACString& aOrigMessageUri)
+{
+ aOrigMessageUri.Assign(m_origMessageUri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::SetOrigMessageUri(const nsACString& aOrigMessageUri)
+{
+ m_origMessageUri.Assign(aOrigMessageUri);
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsPop3Sink.h b/mailnews/local/src/nsPop3Sink.h
new file mode 100644
index 000000000..bd11eba35
--- /dev/null
+++ b/mailnews/local/src/nsPop3Sink.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsPop3Sink_h__
+#define nsPop3Sink_h__
+
+#include "nscore.h"
+#include "nsIURL.h"
+#include "nsIPop3Sink.h"
+#include "nsIOutputStream.h"
+#include "prmem.h"
+#include "prio.h"
+#include "plstr.h"
+#include "prenv.h"
+#include "nsIMsgFolder.h"
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+#include "nsStringGlue.h"
+
+class nsParseNewMailState;
+class nsIMsgFolder;
+
+struct partialRecord
+{
+ partialRecord();
+ ~partialRecord();
+
+ nsCOMPtr<nsIMsgDBHdr> m_msgDBHdr;
+ nsCString m_uidl;
+};
+
+class nsPop3Sink : public nsIPop3Sink
+{
+public:
+ nsPop3Sink();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPOP3SINK
+ nsresult GetServerFolder(nsIMsgFolder **aFolder);
+ nsresult FindPartialMessages();
+ void CheckPartialMessages(nsIPop3Protocol *protocol);
+
+ static char* GetDummyEnvelope(void);
+
+protected:
+ virtual ~nsPop3Sink();
+ nsresult WriteLineToMailbox(const nsACString& buffer);
+ nsresult ReleaseFolderLock();
+ nsresult HandleTempDownloadFailed(nsIMsgWindow *msgWindow);
+
+ bool m_authed;
+ nsCString m_accountUrl;
+ uint32_t m_biffState;
+ int32_t m_numNewMessages;
+ int32_t m_numNewMessagesInFolder;
+ int32_t m_numMsgsDownloaded;
+ bool m_senderAuthed;
+ nsCString m_outputBuffer;
+ nsCOMPtr<nsIPop3IncomingServer> m_popServer;
+ //Currently the folder we want to update about biff info
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ RefPtr<nsParseNewMailState> m_newMailParser;
+ nsCOMPtr <nsIOutputStream> m_outFileStream; // the file we write to, which may be temporary
+ nsCOMPtr<nsIMsgPluggableStore> m_msgStore;
+ bool m_uidlDownload;
+ bool m_buildMessageUri;
+ bool m_downloadingToTempFile;
+ nsCOMPtr <nsIFile> m_tmpDownloadFile;
+ nsCOMPtr<nsIMsgWindow> m_window;
+ nsCString m_messageUri;
+ nsCString m_baseMessageUri;
+ nsCString m_origMessageUri;
+ nsCString m_accountKey;
+ nsTArray<partialRecord*> m_partialMsgsArray;
+};
+
+#endif
diff --git a/mailnews/local/src/nsPop3URL.cpp b/mailnews/local/src/nsPop3URL.cpp
new file mode 100644
index 000000000..f3d724793
--- /dev/null
+++ b/mailnews/local/src/nsPop3URL.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+
+#include "nsIURL.h"
+#include "nsPop3URL.h"
+#include "nsPop3Protocol.h"
+#include "nsStringGlue.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+
+nsPop3URL::nsPop3URL(): nsMsgMailNewsUrl()
+{
+}
+
+nsPop3URL::~nsPop3URL()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPop3URL, nsMsgMailNewsUrl, nsIPop3URL)
+
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIPop3URL specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsPop3URL::SetPop3Sink(nsIPop3Sink* aPop3Sink)
+{
+ if (aPop3Sink)
+ m_pop3Sink = aPop3Sink;
+ return NS_OK;
+}
+
+nsresult nsPop3URL::GetPop3Sink(nsIPop3Sink** aPop3Sink)
+{
+ if (aPop3Sink)
+ {
+ *aPop3Sink = m_pop3Sink;
+ NS_IF_ADDREF(*aPop3Sink);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3URL::GetMessageUri(char ** aMessageUri)
+{
+ if(!aMessageUri || m_messageUri.IsEmpty())
+ return NS_ERROR_NULL_POINTER;
+ *aMessageUri = ToNewCString(m_messageUri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3URL::SetMessageUri(const char *aMessageUri)
+{
+ if (aMessageUri)
+ m_messageUri = aMessageUri;
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsPop3URL.h b/mailnews/local/src/nsPop3URL.h
new file mode 100644
index 000000000..4c6ec6b0a
--- /dev/null
+++ b/mailnews/local/src/nsPop3URL.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPop3URL_h__
+#define nsPop3URL_h__
+
+#include "nsIPop3URL.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsCOMPtr.h"
+
+class nsPop3URL : public nsIPop3URL, public nsMsgMailNewsUrl
+{
+public:
+ NS_DECL_NSIPOP3URL
+ nsPop3URL();
+ NS_DECL_ISUPPORTS_INHERITED
+
+protected:
+ virtual ~nsPop3URL();
+
+ nsCString m_messageUri;
+
+ /* Pop3 specific event sinks */
+ nsCOMPtr<nsIPop3Sink> m_pop3Sink;
+};
+
+#endif // nsPop3URL_h__
diff --git a/mailnews/local/src/nsRssIncomingServer.cpp b/mailnews/local/src/nsRssIncomingServer.cpp
new file mode 100644
index 000000000..fe1e8abe3
--- /dev/null
+++ b/mailnews/local/src/nsRssIncomingServer.cpp
@@ -0,0 +1,260 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRssService.h"
+#include "nsRssIncomingServer.h"
+#include "nsMsgFolderFlags.h"
+#include "nsINewsBlogFeedDownloader.h"
+#include "nsMsgBaseCID.h"
+#include "nsIFile.h"
+#include "nsIMsgFolderNotificationService.h"
+
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIDBFolderInfo.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsArrayUtils.h"
+#include "nsMsgUtils.h"
+
+nsrefcnt nsRssIncomingServer::gInstanceCount = 0;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsRssIncomingServer,
+ nsMsgIncomingServer,
+ nsIRssIncomingServer,
+ nsIMsgFolderListener,
+ nsILocalMailIncomingServer)
+
+nsRssIncomingServer::nsRssIncomingServer()
+{
+ m_canHaveFilters = true;
+
+ if (gInstanceCount == 0)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolderNotificationService> notifyService =
+ do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ notifyService->AddListener(this,
+ nsIMsgFolderNotificationService::folderAdded |
+ nsIMsgFolderNotificationService::folderDeleted |
+ nsIMsgFolderNotificationService::folderMoveCopyCompleted |
+ nsIMsgFolderNotificationService::folderRenamed);
+ }
+
+ gInstanceCount++;
+}
+
+nsRssIncomingServer::~nsRssIncomingServer()
+{
+ gInstanceCount--;
+
+ if (gInstanceCount == 0)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolderNotificationService> notifyService =
+ do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ notifyService->RemoveListener(this);
+ }
+}
+
+nsresult nsRssIncomingServer::FillInDataSourcePath(const nsAString& aDataSourceName,
+ nsIFile ** aLocation)
+{
+ nsresult rv;
+ // Get the local path for this server.
+ nsCOMPtr<nsIFile> localFile;
+ rv = GetLocalPath(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Append the name of the subscriptions data source.
+ rv = localFile->Append(aDataSourceName);
+ NS_IF_ADDREF(*aLocation = localFile);
+ return rv;
+}
+
+// nsIRSSIncomingServer methods
+NS_IMETHODIMP nsRssIncomingServer::GetSubscriptionsDataSourcePath(nsIFile ** aLocation)
+{
+ return FillInDataSourcePath(NS_LITERAL_STRING("feeds.rdf"), aLocation);
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetFeedItemsDataSourcePath(nsIFile ** aLocation)
+{
+ return FillInDataSourcePath(NS_LITERAL_STRING("feeditems.rdf"), aLocation);
+}
+
+NS_IMETHODIMP nsRssIncomingServer::CreateDefaultMailboxes()
+{
+ // For Feeds, all we have is Trash.
+ return CreateLocalFolder(NS_LITERAL_STRING("Trash"));
+}
+
+NS_IMETHODIMP nsRssIncomingServer::SetFlagsOnDefaultMailboxes()
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(rootFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::Trash);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::PerformBiff(nsIMsgWindow *aMsgWindow)
+{
+ // Get the account root (server) folder and pass it on.
+ nsCOMPtr<nsIMsgFolder> rootRSSFolder;
+ GetRootMsgFolder(getter_AddRefs(rootRSSFolder));
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(rootRSSFolder);
+ nsresult rv;
+ bool isBiff = true;
+ nsCOMPtr<nsINewsBlogFeedDownloader> rssDownloader =
+ do_GetService("@mozilla.org/newsblog-feed-downloader;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rssDownloader->DownloadFeed(rootRSSFolder, urlListener, isBiff, aMsgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetNewMail(nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ nsIMsgFolder *aFolder,
+ nsIURI **_retval)
+{
+ // Pass the selected folder on to the downloader.
+ NS_ENSURE_ARG_POINTER(aFolder);
+ nsresult rv;
+ bool isBiff = false;
+ nsCOMPtr<nsINewsBlogFeedDownloader> rssDownloader =
+ do_GetService("@mozilla.org/newsblog-feed-downloader;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rssDownloader->DownloadFeed(aFolder, aUrlListener, isBiff, aMsgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetAccountManagerChrome(nsAString& aResult)
+{
+ aResult.AssignLiteral("am-newsblog.xul");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetOfflineSupportLevel(int32_t *aSupportLevel)
+{
+ NS_ENSURE_ARG_POINTER(aSupportLevel);
+ *aSupportLevel = OFFLINE_SUPPORT_LEVEL_NONE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetSupportsDiskSpace(bool *aSupportsDiskSpace)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsDiskSpace);
+ *aSupportsDiskSpace = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff)
+{
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ // For Feed folders, we don't require a password.
+ *aServerRequiresPasswordForBiff = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetCanSearchMessages(bool *canSearchMessages)
+{
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ *canSearchMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgAdded(nsIMsgDBHdr *aMsg)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgsClassified(nsIArray *aMsgs,
+ bool aJunkProcessed,
+ bool aTraitProcessed)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgsDeleted(nsIArray *aMsgs)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgsMoveCopyCompleted(bool aMove,
+ nsIArray *aSrcMsgs,
+ nsIMsgFolder *aDestFolder,
+ nsIArray *aDestMsgs)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgKeyChanged(nsMsgKey aOldKey,
+ nsIMsgDBHdr *aNewHdr)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderAdded(nsIMsgFolder *aFolder)
+{
+ // Nothing to do. Not necessary for new folder adds, as a new folder never
+ // has a subscription.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderDeleted(nsIMsgFolder *aFolder)
+{
+ // Not necessary for folder deletes, which are move to Trash and handled by
+ // movecopy. Virtual folder or trash folder deletes send a folderdeleted,
+ // but these should have no subscriptions already.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderMoveCopyCompleted(bool aMove,
+ nsIMsgFolder *aSrcFolder,
+ nsIMsgFolder *aDestFolder)
+{
+ return FolderChanged(aDestFolder, aSrcFolder, (aMove ? "move" : "copy"));
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderRenamed(nsIMsgFolder *aOrigFolder,
+ nsIMsgFolder *aNewFolder)
+{
+ return FolderChanged(aNewFolder, aOrigFolder, "rename");
+}
+
+NS_IMETHODIMP nsRssIncomingServer::ItemEvent(nsISupports *aItem,
+ const nsACString &aEvent,
+ nsISupports *aData)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsRssIncomingServer::FolderChanged(nsIMsgFolder *aFolder,
+ nsIMsgFolder *aOrigFolder,
+ const char *aAction)
+{
+ if (!aFolder)
+ return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsINewsBlogFeedDownloader> rssDownloader =
+ do_GetService("@mozilla.org/newsblog-feed-downloader;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rssDownloader->UpdateSubscriptionsDS(aFolder, aOrigFolder, aAction);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsRssIncomingServer::GetSortOrder(int32_t* aSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = 400000000;
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsRssIncomingServer.h b/mailnews/local/src/nsRssIncomingServer.h
new file mode 100644
index 000000000..67406f19a
--- /dev/null
+++ b/mailnews/local/src/nsRssIncomingServer.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsRssIncomingServer_h
+#define __nsRssIncomingServer_h
+
+#include "mozilla/Attributes.h"
+#include "nsIRssIncomingServer.h"
+#include "nsILocalMailIncomingServer.h"
+#include "nsMsgIncomingServer.h"
+#include "nsIMsgFolderListener.h"
+#include "nsMailboxServer.h"
+
+class nsRssIncomingServer : public nsMailboxServer,
+ public nsIRssIncomingServer,
+ public nsILocalMailIncomingServer,
+ public nsIMsgFolderListener
+
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRSSINCOMINGSERVER
+ NS_DECL_NSILOCALMAILINCOMINGSERVER
+ NS_DECL_NSIMSGFOLDERLISTENER
+
+ NS_IMETHOD GetOfflineSupportLevel(int32_t *aSupportLevel) override;
+ NS_IMETHOD GetSupportsDiskSpace(bool *aSupportsDiskSpace) override;
+ NS_IMETHOD GetAccountManagerChrome(nsAString& aResult) override;
+ NS_IMETHOD PerformBiff(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) override;
+ NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override;
+ NS_IMETHOD GetSortOrder(int32_t* aSortOrder) override;
+
+ nsRssIncomingServer();
+protected:
+ virtual ~nsRssIncomingServer();
+ nsresult FolderChanged(nsIMsgFolder *aFolder, nsIMsgFolder *aOrigFolder, const char *aAction);
+ nsresult FillInDataSourcePath(const nsAString& aDataSourceName, nsIFile ** aLocation);
+ static nsrefcnt gInstanceCount;
+};
+
+#endif /* __nsRssIncomingServer_h */
diff --git a/mailnews/local/src/nsRssService.cpp b/mailnews/local/src/nsRssService.cpp
new file mode 100644
index 000000000..f5caac257
--- /dev/null
+++ b/mailnews/local/src/nsRssService.cpp
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRssService.h"
+#include "nsIRssIncomingServer.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsIProperties.h"
+#include "nsServiceManagerUtils.h"
+
+nsRssService::nsRssService()
+{
+}
+
+nsRssService::~nsRssService()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsRssService,
+ nsIRssService,
+ nsIMsgProtocolInfo)
+
+NS_IMETHODIMP nsRssService::GetDefaultLocalPath(nsIFile * *aDefaultLocalPath)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultLocalPath);
+ *aDefaultLocalPath = nullptr;
+
+ nsCOMPtr<nsIFile> localFile;
+ nsCOMPtr<nsIProperties> dirService(do_GetService("@mozilla.org/file/directory_service;1"));
+ if (!dirService) return NS_ERROR_FAILURE;
+ dirService->Get(NS_APP_MAIL_50_DIR, NS_GET_IID(nsIFile), getter_AddRefs(localFile));
+ if (!localFile) return NS_ERROR_FAILURE;
+
+ bool exists;
+ nsresult rv = localFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_IF_ADDREF(*aDefaultLocalPath = localFile);
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP nsRssService::SetDefaultLocalPath(nsIFile * aDefaultLocalPath)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssService::GetServerIID(nsIID * *aServerIID)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssService::GetRequiresUsername(bool *aRequiresUsername)
+{
+ NS_ENSURE_ARG_POINTER(aRequiresUsername);
+ *aRequiresUsername = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssService::GetCanDelete(bool *aCanDelete)
+{
+ NS_ENSURE_ARG_POINTER(aCanDelete);
+ *aCanDelete = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp)
+{
+ NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp);
+ *aCanLoginAtStartUp = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetCanDuplicate(bool *aCanDuplicate)
+{
+ NS_ENSURE_ARG_POINTER(aCanDuplicate);
+ *aCanDuplicate = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetDefaultServerPort(bool isSecure, int32_t *_retval)
+{
+ *_retval = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetCanGetMessages(bool *aCanGetMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetMessages);
+ *aCanGetMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages);
+ *aCanGetIncomingMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetDefaultDoBiff(bool *aDefaultDoBiff)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultDoBiff);
+ // by default, do biff for RSS feeds
+ *aDefaultDoBiff = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetShowComposeMsgLink(bool *aShowComposeMsgLink)
+{
+ NS_ENSURE_ARG_POINTER(aShowComposeMsgLink);
+ *aShowComposeMsgLink = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetFoldersCreatedAsync(bool *aAsyncCreation)
+{
+ NS_ENSURE_ARG_POINTER(aAsyncCreation);
+ *aAsyncCreation = false;
+ return NS_OK;
+}
diff --git a/mailnews/local/src/nsRssService.h b/mailnews/local/src/nsRssService.h
new file mode 100644
index 000000000..45893bba7
--- /dev/null
+++ b/mailnews/local/src/nsRssService.h
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRssService_h___
+#define nsRssService_h___
+
+#include "nsIRssService.h"
+#include "nsIMsgProtocolInfo.h"
+
+class nsRssService : public nsIMsgProtocolInfo, public nsIRssService
+{
+public:
+
+ nsRssService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRSSSERVICE
+ NS_DECL_NSIMSGPROTOCOLINFO
+
+private:
+ virtual ~nsRssService();
+};
+
+#endif /* nsRssService_h___ */
diff --git a/mailnews/mailnews.js b/mailnews/mailnews.js
new file mode 100644
index 000000000..705a0a08a
--- /dev/null
+++ b/mailnews/mailnews.js
@@ -0,0 +1,964 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// SpaceHit() function: whether spacebar advances to next unread message.
+pref("mail.advance_on_spacebar", true);
+
+//mailnews.timeline_is_enabled should be set to true ONLY for perf measurement-timeline builds.
+pref("mailnews.timeline_is_enabled", false);
+
+pref("mailnews.logComposePerformance", false);
+
+pref("mail.wrap_long_lines", true);
+pref("mail.inline_attachments", true);
+pref("mail.reply_quote_inline", false);
+// When in a message the List-Post header contains the content of the Reply-To
+// (which is called "Reply-To Munging") we override the Reply-To header with
+// the From header.
+pref("mail.override_list_reply_to", true);
+
+// hidden pref for controlling if the user agent string
+// is displayed in the message pane or not...
+pref("mailnews.headers.showUserAgent", false);
+
+// hidden pref for controlling if the organization string
+// is displayed in the message pane or not...
+pref("mailnews.headers.showOrganization", false);
+
+// hidden pref for controlling if the references header
+// is displayed in the message pane or not...
+pref("mailnews.headers.showReferences", false);
+
+// hidden pref for controlling if the message-id header
+// is displayed in the message pane or not...
+pref("mailnews.headers.showMessageId", false);
+
+// hidden pref for controlling if the message to a message-id
+// is opened in a new window or in the same window
+pref("mailnews.messageid.openInNewWindow", false);
+
+// hidden pref for url which will be used to open message-ids
+// in browser (%mid ist replaced with the message-id)
+pref("mailnews.messageid_browser.url", "chrome://messenger-region/locale/region.properties");
+
+
+// hidden pref for whether or not to warn when deleting filters. Default YES
+pref("mailnews.filters.confirm_delete", true);
+
+// space-delimited list of extra headers to show in msg header display area.
+pref("mailnews.headers.extraExpandedHeaders", "");
+
+// default sort order settings (when creating new folder views)
+// sort_order is an int value reflecting nsMsgViewSortOrder values
+// as defined in nsIMsgDBView.idl (ascending = 1, descending = 2)
+// sort_type is an int value reflecting nsMsgViewSortType values
+// as defined in nsIMsgDBView.idl (byDate = 18, byId = 21 etc.)
+pref("mailnews.default_sort_order", 1); // for Mail/RSS/... (nsMsgDatabase)
+pref("mailnews.default_sort_type", 18); //
+pref("mailnews.default_news_sort_order", 1); // for News (nsNewsDatabase)
+pref("mailnews.default_news_sort_type", 21); //
+
+// hidden pref for whether "sort by date" and "sort by received date" in
+// threaded mode should be based on the newest message in the thread, or on
+// the thread root
+pref("mailnews.sort_threads_by_root", false);
+
+// default view flags for new folders
+// both flags are int values reflecting nsMsgViewFlagsType values
+// as defined in nsIMsgDBView.idl (kNone = 0, kThreadedDisplay = 1 etc.)
+pref("mailnews.default_view_flags", 0); // for Mail/RSS/... (nsMsgDatabase)
+pref("mailnews.default_news_view_flags", 1); // for News (nsNewsDatabase)
+
+// If true, delete will use the direction of the sort order
+// in determining the next message to select.
+pref("mail.delete_matches_sort_order", false);
+
+// mailnews tcp read+write timeout in seconds.
+pref("mailnews.tcptimeout", 100);
+
+pref("mailnews.headers.showSender", false);
+
+// set to 0 if you don't want to ignore timestamp differences between
+// local mail folders and the value stored in the corresponding .msf file.
+// 0 was the default up to and including 1.5. I've made the default
+// be greater than one hour so daylight savings time changes don't affect us.
+// We will still always regenerate .msf files if the file size changes.
+pref("mail.db_timestamp_leeway", 4000);
+// How long should we leave idle db's open, in milliseconds.
+pref("mail.db.idle_limit", 300000);
+// How many db's should we leave open? LRU db's will be closed first
+pref("mail.db.max_open", 30);
+
+// Should we allow folders over 4GB in size?
+pref("mailnews.allowMboxOver4GB", true);
+
+// For IMAP caching lift the limits since they are designed for HTML pages.
+// Note that the maximum size of a cache entry is limited by
+// max_entry_size and (capacity >> 3), so devided by 8.
+// Larger messages or attachments won't be cached.
+pref("browser.cache.memory.max_entry_size", 25000); // 25 MB
+pref("browser.cache.memory.capacity", 200000); // 200 MB = 8*25 MB
+
+pref("mail.imap.chunk_size", 65536);
+pref("mail.imap.min_chunk_size_threshold", 98304);
+pref("mail.imap.chunk_fast", 2);
+pref("mail.imap.chunk_ideal", 4);
+pref("mail.imap.chunk_add", 8192);
+pref("mail.imap.hide_other_users", false);
+pref("mail.imap.hide_unused_namespaces", true);
+pref("mail.imap.auto_unsubscribe_from_noselect_folders", true);
+pref("mail.imap.mime_parts_on_demand", true);
+pref("mail.imap.mime_parts_on_demand_threshold", 30000);
+pref("mail.imap.use_literal_plus", true);
+pref("mail.imap.expunge_after_delete", false);
+pref("mail.imap.check_deleted_before_expunge", false);
+pref("mail.imap.expunge_option", 0);
+pref("mail.imap.expunge_threshold_number", 20);
+pref("mail.imap.hdr_chunk_size", 200);
+// Should we filter imap messages based on new messages since the previous
+// highest UUID seen instead of unread?
+pref("mail.imap.filter_on_new", true);
+
+// if true, we assume that a user access a folder in the other users namespace
+// is acting as a delegate for that folder, and wishes to use the other users
+// identity when acting on messages in other users folders.
+pref("mail.imap.delegateOtherUsersFolders", false);
+pref("mail.thread_without_re", false); // if false, only thread by subject if Re:
+pref("mail.strict_threading", true); // if true, don't thread by subject at all
+pref("mail.correct_threading", true); // if true, makes sure threading works correctly always (see bug 181446)
+pref("mail.pop3.deleteFromServerOnMove", false);
+pref("mail.fixed_width_messages", true);
+pref("mail.citation_color", "#000000"); // quoted color
+pref("mail.strip_sig_on_reply", true); // If true, remove the everything after the "-- \n" signature delimiter when replying.
+pref("mail.quoted_style", 0); // 0=plain, 1=bold, 2=italic, 3=bolditalic
+pref("mail.quoted_size", 0); // 0=normal, 1=big, 2=small
+pref("mail.quoted_graphical", true); // use HTML-style quoting for displaying plain text
+pref("mail.quoteasblock", true); // use HTML-style quoting for quoting plain text
+pref("mail.strictly_mime", false);
+pref("mail.strictly_mime_headers", true);
+// 0/1 (name param is encoded in a legacy way), 2(RFC 2231 only)
+// 0 the name param is never separated to multiple lines.
+pref("mail.strictly_mime.parm_folding", 1);
+pref("mail.label_ascii_only_mail_as_us_ascii", false);
+pref("mail.file_attach_binary", false);
+pref("mail.show_headers", 1); // some
+pref("mailnews.p7m_external", false); // S/MIME parts are not external (but inline decrypted).
+pref("mailnews.p7m_subparts_external", false); // S/MIME child parts are external. Protect against efail.
+pref("mail.pane_config.dynamic", 0);
+pref("mail.addr_book.mapit_url.format", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.1.name", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.1.format", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.2.name", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.2.format", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.3.name", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.3.format", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.4.name", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.4.format", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.5.name", "chrome://messenger-region/locale/region.properties");
+pref("mail.addr_book.mapit_url.5.format", "chrome://messenger-region/locale/region.properties");
+#ifdef MOZ_SUITE
+pref("mailnews.start_page.url", "chrome://messenger-region/locale/region.properties");
+pref("messenger.throbber.url", "chrome://messenger-region/locale/region.properties");
+pref("compose.throbber.url", "chrome://messenger-region/locale/region.properties");
+pref("addressbook.throbber.url", "chrome://messenger-region/locale/region.properties");
+pref("mail.accountwizard.deferstorage", false);
+// |false|: Show both name and address, even for people in my addressbook.
+pref("mail.showCondensedAddresses", false);
+#endif
+
+// mail.addr_book.quicksearchquery.format is the model query used for:
+// * TB: AB Quick Search and composition's Contact Side Bar
+// * SM: AB Quick Search and composition's Select Addresses dialogue
+//
+// The format for "mail.addr_book.quicksearchquery.format" is:
+// @V == the escaped value typed in the quick search bar in the address book
+// c == contains | bw == beginsWith | ...
+//
+// Note, changing the fields searched might require changing labels:
+// SearchNameOrEmail.label in messenger.dtd,
+// searchNameAndEmail.emptytext in abMainWindow.dtd, etc.
+//
+// mail.addr_book.quicksearchquery.format will be used if mail.addr_book.show_phonetic_fields is "false"
+pref("mail.addr_book.quicksearchquery.format", "(or(DisplayName,c,@V)(FirstName,c,@V)(LastName,c,@V)(NickName,c,@V)(PrimaryEmail,c,@V)(SecondEmail,c,@V)(and(IsMailList,=,TRUE)(Notes,c,@V))(Company,c,@V)(Department,c,@V)(JobTitle,c,@V)(WebPage1,c,@V)(WebPage2,c,@V))");
+// mail.addr_book.quicksearchquery.format.phonetic will be used if mail.addr_book.show_phonetic_fields is "true"
+pref("mail.addr_book.quicksearchquery.format.phonetic", "(or(DisplayName,c,@V)(FirstName,c,@V)(LastName,c,@V)(NickName,c,@V)(PrimaryEmail,c,@V)(SecondEmail,c,@V)(and(IsMailList,=,TRUE)(Notes,c,@V))(Company,c,@V)(Department,c,@V)(JobTitle,c,@V)(WebPage1,c,@V)(WebPage2,c,@V)(PhoneticFirstName,c,@V)(PhoneticLastName,c,@V))");
+
+// mail.addr_book.autocompletequery.format is the model query used for:
+// * TB: Recipient Autocomplete (composition, mailing list properties dialogue)
+// * SM: Recipient Autocomplete (composition, mailing list properties dialogue)
+//
+// mail.addr_book.autocompletequery.format will be used if mail.addr_book.show_phonetic_fields is "false"
+pref("mail.addr_book.autocompletequery.format", "(or(DisplayName,c,@V)(FirstName,c,@V)(LastName,c,@V)(NickName,c,@V)(PrimaryEmail,c,@V)(SecondEmail,c,@V)(and(IsMailList,=,TRUE)(Notes,c,@V)))");
+// mail.addr_book.autocompletequery.format.phonetic will be used if mail.addr_book.show_phonetic_fields is "true"
+pref("mail.addr_book.autocompletequery.format.phonetic", "(or(DisplayName,c,@V)(FirstName,c,@V)(LastName,c,@V)(NickName,c,@V)(PrimaryEmail,c,@V)(SecondEmail,c,@V)(and(IsMailList,=,TRUE)(Notes,c,@V))(PhoneticFirstName,c,@V)(PhoneticLastName,c,@V))");
+
+// values for "mail.addr_book.lastnamefirst" are:
+//0=displayname, 1=lastname first, 2=firstname first
+pref("mail.addr_book.lastnamefirst", 0);
+pref("mail.addr_book.displayName.autoGeneration", true);
+pref("mail.addr_book.displayName.lastnamefirst", "chrome://messenger/locale/messenger.properties");
+pref("mail.addr_book.show_phonetic_fields", "chrome://messenger/locale/messenger.properties");
+pref("mail.html_compose", true);
+// you can specify multiple, option headers
+// this will show up in the address picker in the compose window
+// examples: "X-Face" or "Approved,X-No-Archive"
+pref("mail.compose.other.header", "");
+pref("mail.compose.autosave", true);
+pref("mail.compose.autosaveinterval", 5); // in minutes
+pref("mail.compose.default_to_paragraph", false);
+
+// true: If the message has practically no HTML formatting, bypass recipient-centric
+// auto-detection of delivery format; auto-downgrade and silently send as plain text.
+// false: Don't auto-downgrade; use recipient-centric auto-detection of best delivery format,
+// including send options.
+pref("mailnews.sendformat.auto_downgrade", true);
+pref("mail.default_html_action", 0); // 0=ask, 1=plain, 2=html, 3=both
+
+pref("mail.mdn.report.not_in_to_cc", 2); // 0: Never 1: Always 2: Ask me
+pref("mail.mdn.report.outside_domain", 2); // 0: Never 1: Always 2: Ask me
+pref("mail.mdn.report.other", 2); // 0: Never 1: Always 2: Ask me 3: Denial
+
+pref("mail.incorporate.return_receipt", 0); // 0: Inbox/filter 1: Sent folder
+pref("mail.request.return_receipt", 2); // 1: DSN 2: MDN 3: Both
+pref("mail.receipt.request_header_type", 0); // 0: MDN-DNT header 1: RRT header 2: Both (MC)
+pref("mail.receipt.request_return_receipt_on", false);
+pref("mail.mdn.report.enabled", true); // false: Never send true: Send sometimes
+
+pref("mail.dsn.always_request_on", false);
+pref("mail.dsn.request_on_success_on", true); // DSN request is sent with SUCCESS option
+pref("mail.dsn.request_on_failure_on", true); // DSN request is sent with FAILURE option
+pref("mail.dsn.request_on_delay_on", true); // DSN request is sent with DELAY option
+pref("mail.dsn.request_never_on", false); // DSN request is not sent with NEVER option
+pref("mail.dsn.ret_full_on", true); // DSN request is sent with RET FULL option
+
+pref("news.show_size_in_lines", true);
+pref("news.update_unread_on_expand", true);
+pref("news.get_messages_on_select", true);
+
+pref("mailnews.wraplength", 72);
+
+// 0=no header, 1="<author> wrote:", 2="On <date> <author> wrote:", 3="<author> wrote On <date>:", 4=user specified
+pref("mailnews.reply_header_type", 1);
+// locale which affects date format, set empty string to use application default locale
+pref("mailnews.reply_header_locale", "");
+pref("mailnews.reply_header_authorwrotesingle", "chrome://messenger/locale/messengercompose/composeMsgs.properties");
+pref("mailnews.reply_header_ondateauthorwrote", "chrome://messenger/locale/messengercompose/composeMsgs.properties");
+pref("mailnews.reply_header_authorwroteondate", "chrome://messenger/locale/messengercompose/composeMsgs.properties");
+pref("mailnews.reply_header_originalmessage", "chrome://messenger/locale/messengercompose/composeMsgs.properties");
+pref("mailnews.forward_header_originalmessage", "chrome://messenger/locale/messengercompose/composeMsgs.properties");
+
+pref("mailnews.reply_to_self_check_all_ident", false);
+
+pref("mailnews.reply_quoting_selection", true);
+pref("mailnews.reply_quoting_selection.only_if_chars", "");
+pref("mailnews.reply_quoting_selection.multi_word", true);
+
+pref("mail.operate_on_msgs_in_collapsed_threads", false);
+pref("mail.warn_on_collapsed_thread_operation", true);
+pref("mail.warn_on_shift_delete", true);
+pref("news.warn_on_delete", true);
+pref("mail.warn_on_delete_from_trash", true);
+pref("mail.purge_threshhold_mb", 20);
+pref("mail.prompt_purge_threshhold", true);
+pref("mail.purge.ask", true);
+
+pref("mailnews.offline_sync_mail", false);
+pref("mailnews.offline_sync_news", false);
+pref("mailnews.offline_sync_send_unsent", true);
+pref("mailnews.offline_sync_work_offline", false);
+pref("mailnews.force_ascii_search", false);
+
+pref("mailnews.send_default_charset", "chrome://messenger/locale/messenger.properties");
+pref("mailnews.view_default_charset", "chrome://messenger/locale/messenger.properties");
+pref("mailnews.force_charset_override", false); // ignore specified MIME encoding and use the default encoding for display
+pref("mailnews.reply_in_default_charset", false);
+// mailnews.disable_fallback_to_utf8.<charset>
+// don't fallback from <charset> to UTF-8 even if some characters are not found in <charset>.
+// those characters will be crippled.
+pref("mailnews.disable_fallback_to_utf8.ISO-2022-JP", false);
+pref("mailnews.localizedRe", "chrome://messenger-region/locale/region.properties");
+
+pref("mailnews.search_date_format", "chrome://messenger/locale/messenger.properties");
+pref("mailnews.search_date_separator", "chrome://messenger/locale/messenger.properties");
+pref("mailnews.search_date_leading_zeros", "chrome://messenger/locale/messenger.properties");
+
+pref("mailnews.quotingPrefs.version", 0); // used to decide whether to migrate global quoting prefs
+
+// the first time, we'll warn the user about the blind send, and they can disable the warning if they want.
+pref("mapi.blind-send.enabled", true);
+
+pref("offline.autoDetect", false); // automatically move the user offline or online based on the network connection
+
+pref("ldap_2.autoComplete.useDirectory", false);
+pref("ldap_2.autoComplete.directoryServer", "");
+
+pref("ldap_2.servers.pab.position", 1);
+pref("ldap_2.servers.pab.description", "chrome://messenger/locale/addressbook/addressBook.properties");
+pref("ldap_2.servers.pab.dirType", 2);
+pref("ldap_2.servers.pab.filename", "abook.mab");
+pref("ldap_2.servers.pab.isOffline", false);
+
+pref("ldap_2.servers.history.position", 2);
+pref("ldap_2.servers.history.description", "chrome://messenger/locale/addressbook/addressBook.properties");
+pref("ldap_2.servers.history.dirType", 2);
+pref("ldap_2.servers.history.filename", "history.mab");
+pref("ldap_2.servers.history.isOffline", false);
+
+// default mapping of addressbook properties to ldap attributes
+pref("ldap_2.servers.default.attrmap.FirstName", "givenName");
+pref("ldap_2.servers.default.attrmap.LastName", "sn,surname");
+pref("ldap_2.servers.default.attrmap.DisplayName", "cn,commonname");
+pref("ldap_2.servers.default.attrmap.NickName", "mozillaNickname,xmozillanickname");
+pref("ldap_2.servers.default.attrmap.PrimaryEmail", "mail");
+pref("ldap_2.servers.default.attrmap.SecondEmail", "mozillaSecondEmail,xmozillasecondemail");
+pref("ldap_2.servers.default.attrmap.WorkPhone", "telephoneNumber");
+pref("ldap_2.servers.default.attrmap.HomePhone", "homePhone");
+pref("ldap_2.servers.default.attrmap.FaxNumber", "facsimiletelephonenumber,fax");
+pref("ldap_2.servers.default.attrmap.PagerNumber", "pager,pagerphone");
+pref("ldap_2.servers.default.attrmap.CellularNumber", "mobile,cellphone,carphone");
+pref("ldap_2.servers.default.attrmap.WorkAddress", "street,streetaddress,postOfficeBox");
+pref("ldap_2.servers.default.attrmap.HomeAddress", "mozillaHomeStreet");
+pref("ldap_2.servers.default.attrmap.WorkAddress2", "mozillaWorkStreet2");
+pref("ldap_2.servers.default.attrmap.HomeAddress2", "mozillaHomeStreet2");
+pref("ldap_2.servers.default.attrmap.WorkCity", "l,locality");
+pref("ldap_2.servers.default.attrmap.HomeCity", "mozillaHomeLocalityName");
+pref("ldap_2.servers.default.attrmap.WorkState", "st,region");
+pref("ldap_2.servers.default.attrmap.HomeState", "mozillaHomeState");
+pref("ldap_2.servers.default.attrmap.WorkZipCode", "postalCode,zip");
+pref("ldap_2.servers.default.attrmap.HomeZipCode", "mozillaHomePostalCode");
+pref("ldap_2.servers.default.attrmap.WorkCountry", "c,countryname");
+pref("ldap_2.servers.default.attrmap.HomeCountry", "mozillaHomeCountryName");
+pref("ldap_2.servers.default.attrmap.JobTitle", "title");
+pref("ldap_2.servers.default.attrmap.Department", "ou,department,departmentnumber,orgunit");
+pref("ldap_2.servers.default.attrmap.Company", "o,company");
+pref("ldap_2.servers.default.attrmap._AimScreenName", "nsAIMid,nscpaimscreenname");
+pref("ldap_2.servers.default.attrmap.WebPage1", "mozillaWorkUrl,workurl,labeledURI");
+pref("ldap_2.servers.default.attrmap.WebPage2", "mozillaHomeUrl,homeurl");
+pref("ldap_2.servers.default.attrmap.BirthYear", "birthyear");
+pref("ldap_2.servers.default.attrmap.BirthMonth", "birthmonth");
+pref("ldap_2.servers.default.attrmap.BirthDay", "birthday");
+pref("ldap_2.servers.default.attrmap.Custom1", "mozillaCustom1,custom1");
+pref("ldap_2.servers.default.attrmap.Custom2", "mozillaCustom2,custom2");
+pref("ldap_2.servers.default.attrmap.Custom3", "mozillaCustom3,custom3");
+pref("ldap_2.servers.default.attrmap.Custom4", "mozillaCustom4,custom4");
+pref("ldap_2.servers.default.attrmap.Notes", "description,notes");
+pref("ldap_2.servers.default.attrmap.PreferMailFormat", "mozillaUseHtmlMail,xmozillausehtmlmail");
+pref("ldap_2.servers.default.attrmap.LastModifiedDate", "modifytimestamp");
+
+pref("ldap_2.user_id", 0);
+pref("ldap_2.version", 3); /* Update kCurrentListVersion in include/dirprefs.h if you change this */
+
+pref("mailnews.confirm.moveFoldersToTrash", true);
+
+// space-delimited list of extra headers to add to .msf file
+pref("mailnews.customDBHeaders", "");
+
+// close standalone message window when deleting the displayed message
+pref("mail.close_message_window.on_delete", false);
+
+#ifdef MOZ_SUITE
+pref("mailnews.reuse_message_window", true);
+#endif
+
+pref("mailnews.open_window_warning", 10); // warn user if they attempt to open more than this many messages at once
+pref("mailnews.open_tab_warning", 20); // warn user if they attempt to open more than this many messages at once
+
+pref("mailnews.start_page.enabled", true);
+
+pref("mailnews.remember_selected_message", true);
+pref("mailnews.scroll_to_new_message", true);
+
+// if true, any click on a column header other than the thread column will unthread the view
+pref("mailnews.thread_pane_column_unthreads", false);
+
+pref("mailnews.account_central_page.url", "chrome://messenger/locale/messenger.properties");
+
+/* default prefs for Mozilla 5.0 */
+pref("mail.identity.default.compose_html", true);
+pref("mail.identity.default.valid", true);
+pref("mail.identity.default.fcc", true);
+pref("mail.identity.default.fcc_folder", "mailbox://nobody@Local%20Folders/Sent");
+pref("mail.identity.default.fcc_reply_follows_parent", false);
+pref("mail.identity.default.autocompleteToMyDomain", false);
+
+pref("mail.identity.default.archive_enabled", true);
+// archive into 0: single folder, 1: yearly folder, 2: year/year-month folder
+pref("mail.identity.default.archive_granularity", 1);
+pref("mail.identity.default.archive_keep_folder_structure", false);
+
+// keep these defaults for backwards compatibility and migration
+
+// but .doBcc and .doBccList are the right ones from now on.
+pref("mail.identity.default.bcc_self", false);
+pref("mail.identity.default.bcc_others", false);
+pref("mail.identity.default.bcc_list", "");
+
+pref("mail.identity.default.draft_folder", "mailbox://nobody@Local%20Folders/Drafts");
+pref("mail.identity.default.stationery_folder", "mailbox://nobody@Local%20Folders/Templates");
+pref("mail.identity.default.directoryServer", "");
+pref("mail.identity.default.overrideGlobal_Pref", false);
+pref("mail.identity.default.auto_quote", true);
+pref("mail.identity.default.reply_on_top", 0); // 0=bottom 1=top 2=select
+pref("mail.identity.default.sig_bottom", true); // true=below quoted false=above quoted
+pref("mail.identity.default.sig_on_fwd", false); // Include signature on fwd?
+pref("mail.identity.default.sig_on_reply", true); // Include signature on re?
+
+// Suppress double-dash signature separator
+pref("mail.identity.default.suppress_signature_separator", false);
+
+// default to archives folder on same server.
+pref("mail.identity.default.archives_folder_picker_mode", "0");
+
+// Headers to always add to outgoing mail
+// examples: "header1,header2"
+// pref("mail.identity.id1.headers", "header1");
+// user_pref("mail.identity.id1.header.header1", "X-Mozilla-Rocks: True")
+pref("mail.identity.default.headers", "");
+
+// by default, only collect addresses the user sends to (outgoing)
+// incoming is all spam anyways
+#ifdef MOZ_SUITE
+pref("mail.collect_email_address_incoming", false);
+pref("mail.collect_email_address_newsgroup", false);
+#endif
+pref("mail.collect_email_address_outgoing", true);
+// by default, use the Collected Addressbook for collection
+pref("mail.collect_addressbook", "moz-abmdbdirectory://history.mab");
+
+pref("mail.default_sendlater_uri", "mailbox://nobody@Local%20Folders/Unsent%20Messages");
+
+pref("mail.smtpservers", "");
+pref("mail.accountmanager.accounts", "");
+
+// Last used account key value
+pref("mail.account.lastKey", 0);
+
+pref("mail.server.default.port", -1);
+pref("mail.server.default.offline_support_level", -1);
+pref("mail.server.default.leave_on_server", false);
+pref("mail.server.default.download_on_biff", false);
+pref("mail.server.default.check_time", 10);
+pref("mail.server.default.delete_by_age_from_server", false);
+pref("mail.server.default.num_days_to_leave_on_server", 7);
+pref("mail.server.default.dot_fix", true);
+pref("mail.server.default.limit_offline_message_size", false);
+pref("mail.server.default.max_size", 50);
+pref("mail.server.default.delete_mail_left_on_server", false);
+pref("mail.server.default.valid", true);
+pref("mail.server.default.abbreviate", true);
+pref("mail.server.default.isSecure", false);
+pref("mail.server.default.authMethod", 3); // cleartext password. @see nsIMsgIncomingServer.authMethod.
+pref("mail.server.default.socketType", 0); // @see nsIMsgIncomingServer.socketType
+pref("mail.server.default.override_namespaces", true);
+pref("mail.server.default.deferred_to_account", "");
+
+pref("mail.server.default.delete_model", 1);
+pref("mail.server.default.fetch_by_chunks", true);
+pref("mail.server.default.mime_parts_on_demand", true);
+// Send IMAP RFC 2971 ID Info to server
+pref("mail.server.default.send_client_info", true);
+pref("mail.server.default.always_authenticate", false);
+pref("mail.server.default.singleSignon", true);
+pref("mail.server.default.max_articles", 500);
+pref("mail.server.default.notify.on", true);
+pref("mail.server.default.mark_old_read", false);
+pref("mail.server.default.empty_trash_on_exit", false);
+// 0 = Keep Dupes, leave them alone
+// 1 = delete dupes
+// 2 = Move Dupes to trash
+// 3 = Mark Dupes as Read
+pref("mail.server.default.dup_action", 0);
+pref("mail.server.default.hidden", false);
+
+pref("mail.server.default.using_subscription", true);
+pref("mail.server.default.dual_use_folders", true);
+pref("mail.server.default.canDelete", false);
+pref("mail.server.default.login_at_startup", false);
+pref("mail.server.default.allows_specialfolders_usage", true);
+pref("mail.server.default.canCreateFolders", true);
+pref("mail.server.default.canFileMessages", true);
+
+// special enhancements for IMAP servers
+pref("mail.server.default.is_gmail", false);
+pref("mail.server.default.use_idle", true);
+// in case client or server has bugs in condstore implementation
+pref("mail.server.default.use_condstore", false);
+// in case client or server has bugs in compress implementation
+pref("mail.server.default.use_compress_deflate", true);
+// for spam
+pref("mail.server.default.spamLevel", 100); // 0 off, 100 on. not doing bool since we might have real levels one day.
+pref("mail.server.default.moveOnSpam", false);
+pref("mail.server.default.moveTargetMode", 0); // 0 == "Junk" on server, 1 == specific folder
+pref("mail.server.default.spamActionTargetAccount", "");
+pref("mail.server.default.spamActionTargetFolder", "");
+pref("mail.server.default.useWhiteList", true);
+pref("mail.server.default.whiteListAbURI", "moz-abmdbdirectory://abook.mab"); // the Personal addressbook.
+pref("mail.server.default.useServerFilter", false);
+pref("mail.server.default.serverFilterName", "SpamAssassin");
+pref("mail.server.default.serverFilterTrustFlags", 1); // 1 == trust positives, 2 == trust negatives, 3 == trust both
+pref("mail.server.default.purgeSpam", false);
+pref("mail.server.default.purgeSpamInterval", 14); // 14 days
+pref("mail.server.default.check_all_folders_for_new", false);
+// should we inhibit whitelisting of the email addresses for a server's identities?
+pref("mail.server.default.inhibitWhiteListingIdentityUser", true);
+// should we inhibit whitelisting of the domain for a server's identities?
+pref("mail.server.default.inhibitWhiteListingIdentityDomain", false);
+
+// When force_select is "auto" the ID response for the server will be compared to
+// force_select_detect below and, if they compare, an extra imap select will
+// be sent when checking for new mail. If force_select is "no", the extra
+// select will never occur, and, if "yes" it will always occur when checking for
+// new email (both regardless of the ID response string).
+// The extra select insures that new emails are automatically detected by servers
+// requiring it. Also, if a server does not support IDLE, setting this to "yes"
+// can insure messages are marked as "read" after being read in other email clients.
+pref("mail.server.default.force_select", "auto");
+
+// Specify imap ID response substrings that must occur to cause the extra/forced
+// imap select for server(s). Substrings are comma separated within a given server
+// (all substrings within a server must be found in the ID response string) and
+// servers are semicolon separated. Currently only 1 server type is known
+// to require the extra select -- Openwave server used by Charter-Spectrum ISP.
+pref("mail.imap.force_select_detect", "\"name\" \"Email Mx\",\"vendor\" \"Openwave Messaging\"");
+// Example if ever another server requires the extra select (ID substrings from Yahoo! added):
+//pref("mail.imap.force_select_detect", "\"name\" \"Email Mx\",\"vendor\" \"Openwave Messaging\";\"vendor\" \"Yahoo! Inc.\",\"name\" \"Y!IMAP\";");
+
+// to activate auto-sync feature (preemptive message download for imap) by default
+pref("mail.server.default.autosync_offline_stores",true);
+pref("mail.server.default.offline_download",true);
+
+// -1 means no limit, no purging of offline stores.
+pref("mail.server.default.autosync_max_age_days", -1);
+
+// can we change the store type?
+pref("mail.server.default.canChangeStoreType", false);
+
+// This is the default store contractID for newly created servers.
+// We don't use mail.server.default because we want to ensure that the
+// store contract id is always written out to prefs.js
+pref("mail.serverDefaultStoreContractID", "@mozilla.org/msgstore/berkeleystore;1");
+// the probablilty threshold over which messages are classified as junk
+// this number is divided by 100 before it is used. The classifier can be fine tuned
+// by changing this pref. Typical values are .99, .95, .90, .5, etc.
+pref("mail.adaptivefilters.junk_threshold", 90);
+pref("mail.spam.version", 0); // used to determine when to migrate global spam settings
+pref("mail.spam.logging.enabled", false);
+pref("mail.spam.manualMark", false);
+pref("mail.spam.markAsReadOnSpam", false);
+pref("mail.spam.manualMarkMode", 0); // 0 == "move to junk folder", 1 == "delete"
+pref("mail.spam.markAsNotJunkMarksUnRead", true);
+pref("mail.spam.display.sanitize", true); // display simple html for html junk messages
+// the number of allowed bayes tokens before the database is shrunk
+pref("mailnews.bayesian_spam_filter.junk_maxtokens", 100000);
+
+// pref to warn the users of exceeding the size of the message being composed. (Default 20MB).
+pref("mailnews.message_warning_size", 20971520);
+
+// set default traits for junk and good. Index should match the values in nsIJunkMailPlugin
+pref("mailnews.traits.id.1", "mailnews@mozilla.org#good");
+pref("mailnews.traits.name.1", "Good");
+pref("mailnews.traits.enabled.1", false);
+pref("mailnews.traits.id.2", "mailnews@mozilla.org#junk");
+pref("mailnews.traits.name.2", "Junk");
+pref("mailnews.traits.enabled.2", true);
+pref("mailnews.traits.antiId.2", "mailnews@mozilla.org#good");
+// traits 3 - 1000 are reserved for use by mailnews@mozilla.org
+// the first externally defined trait will have index 1001
+pref("mailnews.traits.lastIndex", 1000);
+
+pref("mail.autoComplete.highlightNonMatches", true);
+
+// if true, we'll use the password from an incoming server with
+// matching username and domain
+pref("mail.smtp.useMatchingDomainServer", false);
+
+// if true, we'll use the password from an incoming server with
+// matching username and host name
+pref("mail.smtp.useMatchingHostNameServer", false);
+
+pref("mail.smtpserver.default.authMethod", 3); // cleartext password. @see nsIMsgIncomingServer.authMethod.
+pref("mail.smtpserver.default.try_ssl", 0); // @see nsISmtpServer.socketType
+
+// For the next 3 prefs, see <http://www.bucksch.org/1/projects/mozilla/16507>
+pref("mail.display_glyph", true); // TXT->HTML :-) etc. in viewer
+pref("mail.display_struct", true); // TXT->HTML *bold* etc. in viewer; ditto
+pref("mail.send_struct", false); // HTML->HTML *bold* etc. during Send; ditto
+// display time and date in message pane using senders timezone
+pref("mailnews.display.date_senders_timezone", false);
+// For the next 4 prefs, see <http://www.bucksch.org/1/projects/mozilla/108153>
+pref("mailnews.display.prefer_plaintext", false); // Ignore HTML parts in multipart/alternative
+pref("mailnews.display.html_as", 0); // How to display HTML/MIME parts. 0 = Render the sender's HTML; 1 = HTML->TXT->HTML; 2 = Show HTML source; 3 = Sanitize HTML; 4 = Show all body parts
+pref("mailnews.display.show_all_body_parts_menu", false); // Whether the View > Message body as > All body parts menu item is available
+pref("mailnews.display.html_sanitizer.drop_non_css_presentation", true); // whether to drop <font>, <center>, align='...', etc.
+pref("mailnews.display.html_sanitizer.drop_media", false); // whether to drop <img>, <video> and <audio>
+pref("mailnews.display.disallow_mime_handlers", 0); /* Let only a few classes process incoming data. This protects from bugs (e.g. buffer overflows) and from security loopholes (e.g. allowing unchecked HTML in some obscure classes, although the user has html_as > 0).
+This option is mainly for the UI of html_as.
+0 = allow all available classes
+1 = Use hardcoded blacklist to avoid rendering (incoming) HTML
+2 = ... and inline images
+3 = ... and some other uncommon content types
+100 = Use hardcoded whitelist to avoid even more bugs(buffer overflows).
+ This mode will limit the features available (e.g. uncommon
+ attachment types and inline images) and is for paranoid users.
+*/
+// RSS rendering options, see prior 4 prefs above.
+pref("rss.display.prefer_plaintext", false);
+pref("rss.display.html_as", 0);
+pref("rss.display.disallow_mime_handlers", 0);
+
+// Feed message display (summary or web page), on select.
+// 0 - global override, load web page
+// 1 - global override, load summary
+// 2 - use default feed folder setting from Subscribe dialog; if no setting default to 1
+pref("rss.show.summary", 1);
+
+// Feed message display (summary or web page), on open.
+// Action on double click or enter in threadpane for a feed message.
+// 0 - open content-base url in new window
+// 1 - open summary in new window
+// 2 - toggle load summary and content-base url in message pane
+// 3 - load content-base url in browser
+pref("rss.show.content-base", 0);
+
+// Feed message additional web page display.
+// 0 - no action
+// 1 - load web page in default browser, on select
+pref("rss.message.loadWebPageOnSelect", 0);
+
+// Feeds system logging, uses log4moz conventions.
+pref("Feeds.logging.console", "Info");
+
+pref("mail.forward_message_mode", 0); // 0=default as attachment 2=forward as inline with attachments, (obsolete 4.x value)1=forward as quoted (mapped to 2 in mozilla)
+pref("mail.forward_add_extension", true); // add .eml extension when forwarding as attachment
+// Prefix of for mail forwards. E.g. "Fwd" -> subject will be Fwd: <subject>
+pref("mail.forward_subject_prefix", "Fwd");
+
+pref("mail.startup.enabledMailCheckOnce", false);
+
+pref("mailnews.send_plaintext_flowed", true); // RFC 2646=======
+pref("mailnews.display.disable_format_flowed_support", false);
+pref("mailnews.nav_crosses_folders", 1); // prompt user when crossing folders
+
+// these two news.cancel.* prefs are for use by QA for automated testing. see bug #31057
+pref("news.cancel.confirm", true);
+pref("news.cancel.alert_on_success", true);
+pref("mail.SpellCheckBeforeSend", false);
+pref("mail.spellcheck.inline", true);
+pref("mail.phishing.detection.enabled", true); // enable / disable phishing detection for link clicks
+pref("mail.warn_on_send_accel_key", true);
+pref("mail.enable_autocomplete", true);
+pref("mailnews.html_domains", "");
+pref("mailnews.plaintext_domains", "");
+pref("mailnews.global_html_domains.version", 1);
+
+/////////////////////////////////////////////////////////////////
+// Privacy Controls for Handling Remote Content
+/////////////////////////////////////////////////////////////////
+// Specific plugins pref just for message content. RSS is not covered by this.
+pref("mailnews.message_display.allow_plugins", false);
+pref("mailnews.message_display.disable_remote_image", true);
+
+/////////////////////////////////////////////////////////////////
+// Trusted Mail Domains
+//
+// Specific domains can be white listed to bypass various privacy controls in Thunderbird
+// such as blocking remote images, the phishing detector, etc. This is particularly
+// useful for business deployments where images or links reference servers inside a
+// corporate intranet. For multiple domains, separate them with a comma. i.e.
+// pref("mail.trusteddomains", "mozilla.org,mozillafoundation.org");
+/////////////////////////////////////////////////////////////////
+pref("mail.trusteddomains", "");
+
+pref("mail.imap.use_status_for_biff", true);
+
+pref("mail.quota.mainwindow_threshold.show", 75); // in percent. when the quota meter starts showing up at all. decrease this for it to be more than a warning.
+pref("mail.quota.mainwindow_threshold.warning", 80); // when it gets yellow
+pref("mail.quota.mainwindow_threshold.critical", 95); // when it gets red
+
+// Pref controlling the updates on the pre-configured accounts.
+// In order to add new pre-configured accounts (after a version),
+// increase the following version number besides updating the
+// pref mail.accountmanager.appendaccounts
+pref("mailnews.append_preconfig_accounts.version", 1);
+
+// Pref controlling the updates on the pre-configured smtp servers.
+// In order to add new pre-configured smtp servers (after a version),
+// increase the following version number besides updating the
+// pref mail.smtpservers.appendsmtpservers
+pref("mail.append_preconfig_smtpservers.version", 1);
+
+pref("mail.biff.alert.show_preview", true);
+pref("mail.biff.alert.show_subject", true);
+pref("mail.biff.alert.show_sender", true);
+pref("mail.biff.alert.preview_length", 40);
+
+#ifdef XP_MACOSX
+pref("mail.biff.play_sound", false);
+#else
+pref("mail.biff.play_sound", true);
+#endif
+// 0 == default system sound, 1 == user specified wav
+pref("mail.biff.play_sound.type", 0);
+// _moz_mailbeep is a magic key, for the default sound.
+// otherwise, this needs to be a file url
+pref("mail.biff.play_sound.url", "");
+pref("mail.biff.show_alert", true);
+#ifdef XP_WIN
+pref("mail.biff.show_tray_icon", true);
+pref("mail.biff.show_balloon", false);
+#elifdef XP_MACOSX
+pref("mail.biff.animate_dock_icon", false);
+#elifdef XP_UNIX
+pref("mail.biff.use_system_alert", false);
+#endif
+
+// add jitter to biff interval
+pref("mail.biff.add_interval_jitter", true);
+
+#ifdef MOZ_SUITE
+// if true, check for new mail even when opening non-mail windows
+pref("mail.biff.on_new_window", true);
+#endif
+
+#ifdef XP_MACOSX
+// If true, the number used in the Mac OS X dock notification will be the
+// the number of "new" messages, as per the classic Thunderbird definition.
+// Defaults to false, which notifies about the number of unread messages.
+pref("mail.biff.use_new_count_in_mac_dock", false);
+#endif
+
+// For feed account serverType=rss sound on biff; if true, mail.biff.play_sound.* settings are used.
+pref("mail.feed.play_sound", false);
+
+// Content disposition for attachments (except binary files and vcards).
+// 0= Content-Disposition: inline
+// 1= Content-Disposition: attachment
+pref("mail.content_disposition_type", 1);
+
+// Experimental option to send message in the background - don't wait to close window.
+pref("mailnews.sendInBackground", false);
+// Will show a progress dialog when saving or sending a message
+pref("mailnews.show_send_progress", true);
+pref("mail.server.default.retainBy", 1);
+
+pref("mailnews.ui.junk.firstuse", true);
+pref("mailnews.ui.junk.manualMarkAsJunkMarksRead", true);
+
+// for manual upgrades of certain UI features.
+// 1 -> 2 is for the folder pane tree landing, to hide the
+// unread and total columns, see msgMail3PaneWindow.js
+pref("mail.ui.folderpane.version", 1);
+
+// for manual upgrades of certain UI features.
+// 1 -> 2 is for the ab results pane tree landing
+// to hide the non default columns in the addressbook dialog
+// see abCommon.js and addressbook.js
+pref("mailnews.ui.addressbook_results.version", 1);
+// for manual upgrades of certain UI features.
+// 1 -> 2 is for the ab results pane tree landing
+// to hide the non default columns in the addressbook sidebar panel
+// see abCommon.js and addressbook-panel.js
+pref("mailnews.ui.addressbook_panel_results.version", 1);
+// for manual upgrades of certain UI features.
+// 1 -> 2 is for the ab results pane tree landing
+// to hide the non default columns in the select addresses dialog
+// see abCommon.js and abSelectAddressesDialog.js
+pref("mailnews.ui.select_addresses_results.version", 1);
+// for manual upgrades of certain UI features.
+// 1 -> 2 is for the ab results pane
+// to hide the non default columns in the advanced directory search dialog
+// see abCommon.js and ABSearchDialog.js
+pref("mailnews.ui.advanced_directory_search_results.version", 1);
+
+// default description and color prefs for tags
+// (we keep the .labels. names for backwards compatibility)
+pref("mailnews.labels.description.1", "chrome://messenger/locale/messenger.properties");
+pref("mailnews.labels.description.2", "chrome://messenger/locale/messenger.properties");
+pref("mailnews.labels.description.3", "chrome://messenger/locale/messenger.properties");
+pref("mailnews.labels.description.4", "chrome://messenger/locale/messenger.properties");
+pref("mailnews.labels.description.5", "chrome://messenger/locale/messenger.properties");
+pref("mailnews.labels.color.1", "#FF0000"); // default: red
+pref("mailnews.labels.color.2", "#FF9900"); // default: orange
+pref("mailnews.labels.color.3", "#009900"); // default: green
+pref("mailnews.labels.color.4", "#3333FF"); // default: blue
+pref("mailnews.labels.color.5", "#993399"); // default: purple
+
+// Whether the colors from tags should be applied only to the message(s)
+// actually tagged, or also to any collapsed threads which contain tagged
+// messages.
+pref("mailnews.display_reply_tag_colors_for_collapsed_threads", true);
+
+//default null headers
+//example "X-Warn: XReply", list of hdrs separated by ": "
+pref("mailnews.customHeaders", "");
+
+// default msg compose font prefs
+pref("msgcompose.font_face", "");
+pref("msgcompose.font_size", "medium");
+pref("msgcompose.text_color", "#000000");
+pref("msgcompose.background_color", "#FFFFFF");
+
+// When there is no disclosed recipients (only bcc), we should address the message to empty group
+// to prevent some mail server to disclose the bcc recipients
+pref("mail.compose.add_undisclosed_recipients", true);
+
+pref("mail.compose.dontWarnMail2Newsgroup", false);
+
+// Attach http image resources to composed messages.
+pref("mail.compose.attach_http_images", false);
+
+// these prefs (in minutes) are here to help QA test this feature
+// "mail.purge.min_delay", never purge a junk folder more than once every 480 minutes (60 mins/hour * 8 hours)
+// "mail.purge.timer_interval", fire the purge timer every 5 minutes, starting 5 minutes after we load accounts
+pref("mail.purge.min_delay", 480);
+pref("mail.purge.timer_interval", 5);
+
+// Set to false if opening a message in the standalone message window or viewing
+// it in the message pane should never mark it as read.
+pref("mailnews.mark_message_read.auto", true);
+
+// Set to true if viewing a message should mark it as read after the msg is
+// viewed in the message pane for a specified time interval in seconds.
+pref("mailnews.mark_message_read.delay", false);
+pref("mailnews.mark_message_read.delay.interval", 5); // measured in seconds
+
+// delay after which messages are showed when moving through them with cursors
+// during thread pane navigation
+pref("mailnews.threadpane_select_delay", 250); // measured in milliseconds
+
+// require a password before showing imap or local headers in thread pane
+pref("mail.password_protect_local_cache", false);
+
+// import option to skip the first record, recorded so that we can save
+// the users last used preference.
+pref("mailnews.import.text.skipfirstrecord", true);
+
+#ifdef MOZ_SUITE
+// automatically scale attached images that are displayed inline
+pref("mail.enable_automatic_image_resizing", true);
+
+#ifdef XP_WIN
+pref("ldap_2.servers.oe.uri", "moz-aboutlookdirectory://oe/");
+pref("ldap_2.servers.oe.description", "chrome://messenger/locale/addressbook/addressBook.properties");
+pref("ldap_2.servers.oe.dirType", 3);
+#endif
+#endif
+#ifdef XP_MACOSX
+pref("ldap_2.servers.osx.uri", "moz-abosxdirectory:///");
+pref("ldap_2.servers.osx.description", "chrome://messenger/locale/addressbook/addressBook.properties");
+pref("ldap_2.servers.osx.dirType", 3);
+pref("mail.notification.sound", "");
+pref("mail.notification.count.inbox_only", true);
+// Work around bug 482811 by disabling slow script warning for chrome scripts on Mac
+pref("dom.max_chrome_script_run_time", 0);
+#endif
+
+// gtk2 (*nix) lacks transparent/translucent drag support (bug 376238), so we
+// want to disable it so people can see where they are dragging things.
+// (Stock gtk drag icons will be used instead.)
+#ifdef MOZ_WIDGET_GTK
+pref("nglayout.enable_drag_images", false);
+#endif
+
+// For the Empty Junk/Trash confirmation dialogs.
+pref("mailnews.emptyJunk.dontAskAgain", false);
+pref("mailnews.emptyTrash.dontAskAgain", false);
+
+// where to fetch auto config information from.
+pref("mailnews.auto_config_url", "https://live.mozillamessaging.com/autoconfig/v1.1/");
+// Added in bug 551519. Remove when bug 545866 is fixed.
+pref("mailnews.mx_service_url", "https://live.mozillamessaging.com/dns/mx/");
+// Allow to contact the ISP (email address domain).
+// This may happen via insecure means (HTTP) susceptible to eavesdropping
+// and MitM (see mailnews.auto_config.fetchFromISP.sslOnly below).
+pref("mailnews.auto_config.fetchFromISP.enabled", true);
+// Allow the username to be sent to the ISP when fetching.
+// Note that the username will leak in plaintext if a non-SSL fetch is
+// performed.
+pref("mailnews.auto_config.fetchFromISP.sendEmailAddress", true);
+// Allow only SSL channels when fetching config from ISP.
+// If false, an active attacker can block SSL fetches and then
+// MITM the HTTP fetch, determining the config that is shown to the user.
+// However:
+// 1. The user still needs to explicitly approve the false config.
+// 2. Most hosters that offer this ISP config do so on HTTP and not on HTTPS.
+// That's because they direct customer domains (HTTP) to their provider
+// config (HTTPS). If you set this to true, you simply break this mechanism.
+// You will simply not get most configs.
+// 3. There are guess config and AutoDiscover config mechanisms which
+// have the exact same problem. In order to mitigate those additional
+// vectors, set the following prefs accordingly:
+// * mailnews.auto_config.guess.sslOnly = true
+// * mailnews.auto_config.fetchFromExchange.enabled = false
+// Not all mail servers support SSL so enabling this option might lock
+// you out from your ISP. This especially affect internal mail servers.
+pref("mailnews.auto_config.fetchFromISP.sslOnly", false);
+// Whether we will attempt to guess the account configuration based on
+// protocol default ports and common domain practices
+// (e.g. {mail,pop,imap,smtp}.<email-domain>).
+pref("mailnews.auto_config.guess.enabled", true);
+// Allow only SSL configs when guessing.
+// An attacker could block SSL to force plaintext and thus be able to
+// eavesdrop. Compared to mailnews.auto_config.fetchFromISP.sslOnly
+// the attacker cannot determine the config, just pick which one it
+// likes best among those Thunderbird generates for the user based on
+// the email address.
+// Not all mail servers support SSL so enabling this option might lock
+// you out from your ISP. This especially affect internal mail servers.
+pref("mailnews.auto_config.guess.sslOnly", false);
+// The timeout (in seconds) for each guess
+pref("mailnews.auto_config.guess.timeout", 10);
+
+// -- Summary Database options
+// dontPreserveOnCopy: a space separated list of properties that are not
+// copied to the new nsIMsgHdr when a message is copied.
+// Allows extensions to control preservation of properties.
+pref("mailnews.database.summary.dontPreserveOnCopy",
+ "account msgOffset threadParent msgThreadId statusOfset flags size numLines ProtoThreadFlags label gloda-id gloda-dirty storeToken");
+
+// dontPreserveOnMove: a space separated list of properties that are not
+// copied to the new nsIMsgHdr when a message is moved.
+// Allows extensions to control preservation of properties.
+pref("mailnews.database.summary.dontPreserveOnMove",
+ "account msgOffset threadParent msgThreadId statusOfset flags size numLines ProtoThreadFlags label storeToken");
+// Should we output dbcache log via dump? Set to "Debug" to show.
+pref("mailnews.database.dbcache.logging.dump", "None");
+// Should we output dbcache log to the "error console"? Set to "Debug" to show.
+pref("mailnews.database.dbcache.logging.console", "None");
+
+// -- Global Database (gloda) options
+// Should the indexer be enabled?
+pref("mailnews.database.global.indexer.enabled", false);
+// Should we output warnings and errors to the "error console"?
+pref("mailnews.database.global.logging.console", false);
+// Should we output all output levels to stdout via dump?
+pref("mailnews.database.global.logging.dump", false);
+// Should we consider outputting all levels via the network?
+pref("mailnews.database.global.logging.net", false);
+// Rate of growth of the gloda cache, whose maximum value is 8 MiB and max is 64 MiB.
+// See more: https://developer.mozilla.org/en/Thunderbird/gloda#Cache_Size"
+pref("mailnews.database.global.datastore.cache_to_memory_permillage", 10);
+
+// default field order in the fieldmap
+pref("mailnews.import.text.fieldmap", "+0,+1,+2,+3,+4,+5,+36,+6,+7,+8,+9,+10,+11,+12,+13,+14,+15,+16,+17,+18,+19,+20,+21,+22,+23,+24,+25,+26,+27,+28,+29,+30,+31,+32,+33,+34,+35");
+
+// On networks deploying QoS, it is recommended that these be lockpref()'d,
+// since inappropriate marking can easily overwhelm bandwidth reservations
+// for certain services (i.e. EF for VoIP, AF4x for interactive video,
+// AF3x for broadcast/streaming video, etc)
+
+// default value for SMTP and POP3.
+// in a DSCP environment this should be 48 (0x30, or AF12) per RFC-4594,
+// Section 4.8 "High-Throughput Data Service Class"
+pref("mail.pop3.qos", 0);
+pref("mail.smtp.qos", 0);
+pref("mail.nntp.qos", 0);
+
+// default value for IMAP4
+// in a DSCP environment this should be 56 (0x38, or AF13), ibid.
+pref("mail.imap.qos", 0);
+
+// PgpMime Addon
+pref("mail.pgpmime.addon_url", "https://addons.mozilla.org/addon/enigmail/");
diff --git a/mailnews/mailnews.mozbuild b/mailnews/mailnews.mozbuild
new file mode 100644
index 000000000..12ae6cd2e
--- /dev/null
+++ b/mailnews/mailnews.mozbuild
@@ -0,0 +1,16 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['MOZ_LDAP_XPCOM']:
+ DIRS += [
+ '/ldap',
+ '/ldap/xpcom',
+ ]
+
+if CONFIG['MOZ_MORK']:
+ DIRS += ['/db']
+
+DIRS += ['/mailnews']
+
diff --git a/mailnews/mapi/mapiDll/Makefile.in b/mailnews/mapi/mapiDll/Makefile.in
new file mode 100644
index 000000000..b6769e314
--- /dev/null
+++ b/mailnews/mapi/mapiDll/Makefile.in
@@ -0,0 +1,6 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EMBED_MANIFEST_AT = 2
diff --git a/mailnews/mapi/mapiDll/Mapi32.DEF b/mailnews/mapi/mapiDll/Mapi32.DEF
new file mode 100644
index 000000000..5e95396b0
--- /dev/null
+++ b/mailnews/mapi/mapiDll/Mapi32.DEF
@@ -0,0 +1,21 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+LIBRARY mozMapi32.dll
+
+EXPORTS
+ MAPILogon
+ MAPILogoff
+ MAPISendMail
+ MAPISendDocuments
+ MAPIFindNext
+ MAPIReadMail
+ MAPISaveMail
+ MAPIDeleteMail
+ MAPIAddress
+ MAPIDetails
+ MAPIResolveName
+ MAPIFreeBuffer
+ GetMapiDllVersion
+
diff --git a/mailnews/mapi/mapiDll/MapiDll.cpp b/mailnews/mapi/mapiDll/MapiDll.cpp
new file mode 100644
index 000000000..ee527df4a
--- /dev/null
+++ b/mailnews/mapi/mapiDll/MapiDll.cpp
@@ -0,0 +1,480 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma warning (disable : 4996) // MAPILogoff is deprecated
+
+#include <windows.h>
+#include <tchar.h>
+#include <mapidefs.h>
+#include <mapi.h>
+#include "msgMapi.h"
+
+#define MAX_RECIPS 2000
+#define MAX_FILES 100
+
+
+#define MAX_NAME_LEN 256
+#define MAX_PW_LEN 256
+#define MAX_MSGINFO_LEN 512
+#define MAX_POINTERS 32
+
+const CLSID CLSID_CMapiImp = {0x29f458be, 0x8866, 0x11d5,
+ {0xa3, 0xdd, 0x0, 0xb0, 0xd0, 0xf3, 0xba, 0xa7}};
+const IID IID_nsIMapi = {0x6EDCD38E,0x8861,0x11d5,
+ {0xA3,0xDD,0x00,0xB0,0xD0,0xF3,0xBA,0xA7}};
+
+DWORD tId = 0;
+
+#define MAPI_MESSAGE_TYPE 0
+#define MAPI_RECIPIENT_TYPE 1
+
+typedef struct {
+ LPVOID lpMem;
+ UCHAR memType;
+} memTrackerType;
+
+
+// this can't be right.
+memTrackerType memArray[MAX_POINTERS];
+
+//
+// For remembering memory...how ironic.
+//
+void
+SetPointerArray(LPVOID ptr, BYTE type)
+{
+int i;
+
+ for (i=0; i<MAX_POINTERS; i++)
+ {
+ if (memArray[i].lpMem == NULL)
+ {
+ memArray[i].lpMem = ptr;
+ memArray[i].memType = type;
+ break;
+ }
+ }
+}
+
+
+BOOL WINAPI DllMain(HINSTANCE aInstance, DWORD aReason, LPVOID aReserved)
+{
+ switch (aReason)
+ {
+ case DLL_PROCESS_ATTACH : tId = TlsAlloc();
+ if (tId == 0xFFFFFFFF)
+ return FALSE;
+ break;
+
+ case DLL_PROCESS_DETACH : TlsFree(tId);
+ break;
+ }
+ return TRUE;
+}
+
+BOOL InitMozillaReference(nsIMapi **aRetValue)
+{
+ // Check whether this thread has a valid Interface
+ // by looking into thread-specific-data variable
+
+ *aRetValue = (nsIMapi *)TlsGetValue(tId);
+
+ // Check whether the pointer actually resolves to
+ // a valid method call; otherwise mozilla is not running
+
+ if ((*aRetValue) && (*aRetValue)->IsValid() == S_OK)
+ return TRUE;
+
+ HRESULT hRes = ::CoInitialize(NULL) ;
+
+ hRes = ::CoCreateInstance(CLSID_CMapiImp, NULL, CLSCTX_LOCAL_SERVER,
+ IID_nsIMapi, (LPVOID *)aRetValue);
+
+ if (hRes == S_OK && (*aRetValue)->Initialize() == S_OK)
+ if (TlsSetValue(tId, (LPVOID)(*aRetValue)))
+ return TRUE;
+
+ // Either CoCreate or TlsSetValue failed; so return FALSE
+
+ if ((*aRetValue))
+ (*aRetValue)->Release();
+
+ ::CoUninitialize();
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// The MAPILogon function begins a Simple MAPI session, loading the default message ////
+// store and address book providers ////
+////////////////////////////////////////////////////////////////////////////////////////
+
+ULONG FAR PASCAL MAPILogon(ULONG aUIParam, LPTSTR aProfileName,
+ LPTSTR aPassword, FLAGS aFlags,
+ ULONG aReserved, LPLHANDLE aSession)
+{
+ HRESULT hr = 0;
+ ULONG nSessionId = 0;
+ nsIMapi *pNsMapi = NULL;
+
+ if (!InitMozillaReference(&pNsMapi))
+ return MAPI_E_FAILURE;
+
+ if (!(aFlags & MAPI_UNICODE))
+ {
+ // Need to convert the parameters to Unicode.
+
+ char *pUserName = (char *) aProfileName;
+ char *pPassWord = (char *) aPassword;
+
+ TCHAR ProfileName[MAX_NAME_LEN] = {0};
+ TCHAR PassWord[MAX_PW_LEN] = {0};
+
+ if (pUserName != NULL)
+ {
+ if (!MultiByteToWideChar(CP_ACP, 0, pUserName, -1, ProfileName,
+ MAX_NAME_LEN))
+ return MAPI_E_FAILURE;
+ }
+
+ if (pPassWord != NULL)
+ {
+ if (!MultiByteToWideChar(CP_ACP, 0, pPassWord, -1, PassWord,
+ MAX_NAME_LEN))
+ return MAPI_E_FAILURE;
+ }
+
+ hr = pNsMapi->Login(aUIParam, ProfileName, PassWord, aFlags,
+ &nSessionId);
+ }
+ else
+ hr = pNsMapi->Login(aUIParam, aProfileName, aPassword,
+ aFlags, &nSessionId);
+ if (hr == S_OK)
+ (*aSession) = (LHANDLE) nSessionId;
+ else
+ return nSessionId;
+
+ return SUCCESS_SUCCESS;
+}
+
+ULONG FAR PASCAL MAPILogoff (LHANDLE aSession, ULONG aUIParam,
+ FLAGS aFlags, ULONG aReserved)
+{
+ nsIMapi *pNsMapi = (nsIMapi *)TlsGetValue(tId);
+ if (pNsMapi != NULL)
+ {
+ if (pNsMapi->Logoff((ULONG) aSession) == S_OK)
+ pNsMapi->Release();
+ pNsMapi = NULL;
+ }
+
+ TlsSetValue(tId, NULL);
+
+ ::CoUninitialize();
+
+ return SUCCESS_SUCCESS;
+}
+
+ULONG FAR PASCAL MAPISendMail (LHANDLE lhSession, ULONG ulUIParam, nsMapiMessage *lpMessage,
+ FLAGS flFlags, ULONG ulReserved )
+{
+ HRESULT hr = 0;
+ BOOL bTempSession = FALSE ;
+ nsIMapi *pNsMapi = NULL;
+
+ if (!InitMozillaReference(&pNsMapi))
+ return MAPI_E_FAILURE;
+
+ if (lpMessage->nRecipCount > MAX_RECIPS)
+ return MAPI_E_TOO_MANY_RECIPIENTS ;
+
+ if (lpMessage->nFileCount > MAX_FILES)
+ return MAPI_E_TOO_MANY_FILES ;
+
+ if ( (!(flFlags & MAPI_DIALOG)) && (lpMessage->lpRecips == NULL) )
+ return MAPI_E_UNKNOWN_RECIPIENT ;
+
+ if (!lhSession || pNsMapi->IsValidSession(lhSession) != S_OK)
+ {
+ FLAGS LoginFlag ;
+ if ( (flFlags & MAPI_LOGON_UI) && (flFlags & MAPI_NEW_SESSION) )
+ LoginFlag = MAPI_LOGON_UI | MAPI_NEW_SESSION ;
+ else if (flFlags & MAPI_LOGON_UI)
+ LoginFlag = MAPI_LOGON_UI ;
+
+ hr = MAPILogon (ulUIParam, (LPTSTR) NULL, (LPTSTR) NULL, LoginFlag, 0, &lhSession) ;
+ if (hr != SUCCESS_SUCCESS)
+ return MAPI_E_LOGIN_FAILURE ;
+ bTempSession = TRUE ;
+ }
+
+ hr = pNsMapi->SendMail(lhSession, lpMessage, flFlags, ulReserved);
+
+ // we are seeing a problem when using Word, although we return success from the MAPI support
+ // MS COM interface in mozilla, we are getting this error here. This is a temporary hack !!
+ if (hr == 0x800703e6)
+ hr = SUCCESS_SUCCESS;
+
+ if (bTempSession)
+ MAPILogoff (lhSession, ulUIParam, 0,0) ;
+
+ return hr ;
+}
+
+
+ULONG FAR PASCAL MAPISendDocuments(ULONG ulUIParam, LPTSTR lpszDelimChar, LPTSTR lpszFilePaths,
+ LPTSTR lpszFileNames, ULONG ulReserved)
+{
+ LHANDLE lhSession ;
+ nsIMapi *pNsMapi = NULL;
+
+ if (!InitMozillaReference(&pNsMapi))
+ return MAPI_E_FAILURE;
+
+ unsigned long result = MAPILogon (ulUIParam, (LPTSTR) NULL, (LPTSTR) NULL, MAPI_LOGON_UI, 0, &lhSession) ;
+ if (result != SUCCESS_SUCCESS)
+ return MAPI_E_LOGIN_FAILURE ;
+
+ HRESULT hr;
+
+ hr = pNsMapi->SendDocuments(lhSession, (LPTSTR) lpszDelimChar, (LPTSTR) lpszFilePaths,
+ (LPTSTR) lpszFileNames, ulReserved) ;
+
+ MAPILogoff (lhSession, ulUIParam, 0,0) ;
+
+ return hr ;
+}
+
+ULONG FAR PASCAL MAPIFindNext(LHANDLE lhSession, ULONG ulUIParam, LPTSTR lpszMessageType,
+ LPTSTR lpszSeedMessageID, FLAGS flFlags, ULONG ulReserved,
+ unsigned char lpszMessageID[64])
+{
+ nsIMapi *pNsMapi = NULL;
+
+ if (!InitMozillaReference(&pNsMapi))
+ return MAPI_E_FAILURE;
+
+ if (lhSession == 0)
+ return MAPI_E_INVALID_SESSION;
+
+ if (!lpszMessageType)
+ lpszMessageType = L"";
+
+ if (!lpszSeedMessageID)
+ lpszSeedMessageID = L"";
+
+ return pNsMapi->FindNext(lhSession, ulUIParam, lpszMessageType,
+ lpszSeedMessageID, flFlags, ulReserved,
+ lpszMessageID) ;
+}
+
+
+ULONG FAR PASCAL MAPIReadMail(LHANDLE lhSession, ULONG ulUIParam, LPTSTR lpszMessageID,
+ FLAGS flFlags, ULONG ulReserved, nsMapiMessage **lppMessage)
+{
+ nsIMapi *pNsMapi = NULL;
+
+ if (!InitMozillaReference(&pNsMapi))
+ return MAPI_E_FAILURE;
+
+ if (lhSession == 0)
+ return MAPI_E_INVALID_SESSION;
+
+ return pNsMapi->ReadMail(lhSession, ulUIParam,
+ lpszMessageID, flFlags, ulReserved,
+ lppMessage) ;
+
+}
+
+ULONG FAR PASCAL MAPISaveMail(LHANDLE lhSession, ULONG ulUIParam, lpnsMapiMessage lpMessage,
+ FLAGS flFlags, ULONG ulReserved, LPTSTR lpszMessageID)
+{
+ nsIMapi *pNsMapi = NULL;
+
+ if (lhSession == 0)
+ return MAPI_E_INVALID_SESSION;
+
+ if (!InitMozillaReference(&pNsMapi))
+ return MAPI_E_FAILURE;
+
+ return MAPI_E_FAILURE;
+}
+
+ULONG FAR PASCAL MAPIDeleteMail(LHANDLE lhSession, ULONG ulUIParam, LPTSTR lpszMessageID,
+ FLAGS flFlags, ULONG ulReserved)
+{
+ nsIMapi *pNsMapi = NULL;
+
+ if (lhSession == 0)
+ return MAPI_E_INVALID_SESSION;
+
+ if (!InitMozillaReference(&pNsMapi))
+ return MAPI_E_FAILURE;
+
+ return pNsMapi->DeleteMail(lhSession, ulUIParam,
+ lpszMessageID, flFlags, ulReserved) ;
+}
+
+ULONG FAR PASCAL MAPIAddress(LHANDLE lhSession, ULONG ulUIParam, LPTSTR lpszCaption,
+ ULONG nEditFields, LPTSTR lpszLabels, ULONG nRecips,
+ lpMapiRecipDesc lpRecips, FLAGS flFlags,
+ ULONG ulReserved, LPULONG lpnNewRecips,
+ lpMapiRecipDesc FAR *lppNewRecips)
+{
+ return MAPI_E_NOT_SUPPORTED;
+}
+
+ULONG FAR PASCAL MAPIDetails(LHANDLE lhSession, ULONG ulUIParam, lpMapiRecipDesc lpRecip,
+ FLAGS flFlags, ULONG ulReserved)
+{
+ return MAPI_E_NOT_SUPPORTED;
+}
+
+ULONG FAR PASCAL MAPIResolveName(LHANDLE lhSession, ULONG ulUIParam, LPTSTR lpszName,
+ FLAGS flFlags, ULONG ulReserved, lpMapiRecipDesc FAR *lppRecip)
+{
+ char* lpszRecipName = new char[(strlen((const char*)lpszName) + 1)];
+ if (lpszRecipName == NULL)
+ return MAPI_E_INSUFFICIENT_MEMORY;
+ char* lpszRecipAddress = new char[(strlen((const char*)lpszName) + 6)];
+ if (!lpszRecipAddress) {
+ delete[] lpszRecipName;
+ return MAPI_E_INSUFFICIENT_MEMORY;
+ }
+ strcpy(lpszRecipName, (const char*)lpszName);
+ strcpy(lpszRecipAddress, (const char*)lpszName);
+ (*lppRecip) = (lpMapiRecipDesc FAR)malloc(sizeof(MapiRecipDesc));
+ if (!(*lppRecip)) {
+ delete[] lpszRecipName;
+ delete[] lpszRecipAddress;
+ return MAPI_E_INSUFFICIENT_MEMORY;
+ }
+ (*lppRecip)->ulRecipClass = 1;
+ (*lppRecip)->lpszName = lpszRecipName;
+ (*lppRecip)->lpszAddress = lpszRecipAddress;
+ (*lppRecip)->ulEIDSize = 0;
+ (*lppRecip)->lpEntryID = 0;
+ return SUCCESS_SUCCESS;
+}
+
+void FreeMAPIRecipient(lpMapiRecipDesc pv);
+void FreeMAPIMessage(lpMapiMessage pv);
+
+ULONG FAR PASCAL MAPIFreeBuffer(LPVOID pv)
+{
+ int i;
+
+ if (!pv)
+ return S_OK;
+
+ for (i=0; i<MAX_POINTERS; i++)
+ {
+ if (pv == memArray[i].lpMem)
+ {
+ if (memArray[i].memType == MAPI_MESSAGE_TYPE)
+ {
+ FreeMAPIMessage((MapiMessage *)pv);
+ memArray[i].lpMem = NULL;
+ }
+ else if (memArray[i].memType == MAPI_RECIPIENT_TYPE)
+ {
+ FreeMAPIRecipient((MapiRecipDesc *)pv);
+ memArray[i].lpMem = NULL;
+ }
+ }
+ }
+
+ pv = NULL;
+ return S_OK;
+}
+
+ULONG FAR PASCAL GetMapiDllVersion()
+{
+ return 94;
+}
+
+void
+FreeMAPIFile(lpMapiFileDesc pv)
+{
+ if (!pv)
+ return;
+
+ if (pv->lpszPathName != NULL)
+ free(pv->lpszPathName);
+
+ if (pv->lpszFileName != NULL)
+ free(pv->lpszFileName);
+}
+
+void
+FreeMAPIMessage(lpMapiMessage pv)
+{
+ ULONG i;
+
+ if (!pv)
+ return;
+
+ if (pv->lpszSubject != NULL)
+ free(pv->lpszSubject);
+
+ if (pv->lpszNoteText)
+ free(pv->lpszNoteText);
+
+ if (pv->lpszMessageType)
+ free(pv->lpszMessageType);
+
+ if (pv->lpszDateReceived)
+ free(pv->lpszDateReceived);
+
+ if (pv->lpszConversationID)
+ free(pv->lpszConversationID);
+
+ if (pv->lpOriginator)
+ FreeMAPIRecipient(pv->lpOriginator);
+
+ for (i=0; i<pv->nRecipCount; i++)
+ {
+ if (&(pv->lpRecips[i]) != NULL)
+ {
+ FreeMAPIRecipient(&(pv->lpRecips[i]));
+ }
+ }
+
+ if (pv->lpRecips != NULL)
+ {
+ free(pv->lpRecips);
+ }
+
+ for (i=0; i<pv->nFileCount; i++)
+ {
+ if (&(pv->lpFiles[i]) != NULL)
+ {
+ FreeMAPIFile(&(pv->lpFiles[i]));
+ }
+ }
+
+ if (pv->lpFiles != NULL)
+ {
+ free(pv->lpFiles);
+ }
+
+ free(pv);
+ pv = NULL;
+}
+
+void
+FreeMAPIRecipient(lpMapiRecipDesc pv)
+{
+ if (!pv)
+ return;
+
+ if (pv->lpszName != NULL)
+ free(pv->lpszName);
+
+ if (pv->lpszAddress != NULL)
+ free(pv->lpszAddress);
+
+ if (pv->lpEntryID != NULL)
+ free(pv->lpEntryID);
+}
diff --git a/mailnews/mapi/mapiDll/module.ver b/mailnews/mapi/mapiDll/module.ver
new file mode 100644
index 000000000..d62b9e99c
--- /dev/null
+++ b/mailnews/mapi/mapiDll/module.ver
@@ -0,0 +1,7 @@
+WIN32_MODULE_FILEVERSION=0,8,0,0
+WIN32_MODULE_FILEVERSION_STRING=0.8
+WIN32_MODULE_COPYRIGHT=©Thunderbird and Mozilla Developers, according to the MPL 1.1/GPL 2.0/LGPL 2.1 licenses, as applicable.
+WIN32_MODULE_COMPANYNAME=Mozilla.org
+WIN32_MODULE_TRADEMARKS=Mozilla
+WIN32_MODULE_COMMENT=Mozilla Thunderbird MAPI Dll
+
diff --git a/mailnews/mapi/mapiDll/moz.build b/mailnews/mapi/mapiDll/moz.build
new file mode 100644
index 000000000..d501c2308
--- /dev/null
+++ b/mailnews/mapi/mapiDll/moz.build
@@ -0,0 +1,24 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Statically link against the CRT, so that we don't go hunting around for it
+# and not find it when we're loaded into explorer.exe or similar
+GeckoSharedLibrary('mozMapi32', msvcrt='static', linkage=None)
+
+SOURCES += [
+ 'MapiDll.cpp',
+]
+
+OS_LIBS += [
+ 'ole32',
+]
+
+DEFINES['UNICODE'] = True
+DEFINES['_UNICODE'] = True
+
+if not CONFIG['MOZ_INCOMPLETE_EXTERNAL_LINKAGE']:
+ DEFINES['MOZILLA_INTERNAL_API'] = True
+
+DEFFILE = SRCDIR + '/Mapi32.def'
diff --git a/mailnews/mapi/mapihook/build/Makefile.in b/mailnews/mapi/mapihook/build/Makefile.in
new file mode 100644
index 000000000..0614220e9
--- /dev/null
+++ b/mailnews/mapi/mapihook/build/Makefile.in
@@ -0,0 +1,36 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MIDL_GENERATED_FILES = msgMapi.h msgMapi_p.c msgMapi_i.c dlldata.c
+
+INSTALL_TARGETS += msgmapi
+msgmapi_FILES := msgMapi.h
+msgmapi_DEST = $(DIST)/include
+msgmapi_TARGET := export
+
+SRCDIR_CSRCS = $(addprefix $(srcdir)/,$(CSRCS))
+
+GARBAGE += $(MIDL_GENERATED_FILES) done_gen $(CSRCS) $(SRCDIR_CSRCS)
+
+EMBED_MANIFEST_AT = 2
+
+CSRCS += \
+ dlldata.c \
+ msgMapi_i.c \
+ msgMapi_p.c \
+ $(NULL)
+
+include $(topsrcdir)/config/rules.mk
+
+$(MIDL_GENERATED_FILES): done_gen
+
+done_gen: msgMapi.idl
+ $(RM) $(SRCDIR_CSRCS)
+ $(MIDL) $(MIDL_FLAGS) $(UNICODE_FLAGS) $(srcdir)/msgMapi.idl
+ touch $@
+
+export:: done_gen
+
+
diff --git a/mailnews/mapi/mapihook/build/MapiProxy.def b/mailnews/mapi/mapihook/build/MapiProxy.def
new file mode 100644
index 000000000..4da08eb7d
--- /dev/null
+++ b/mailnews/mapi/mapihook/build/MapiProxy.def
@@ -0,0 +1,13 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+LIBRARY MapiProxy.dll
+
+EXPORTS
+ DllGetClassObject PRIVATE
+ DllCanUnloadNow PRIVATE
+ GetProxyDllInfo PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
+
diff --git a/mailnews/mapi/mapihook/build/module.ver b/mailnews/mapi/mapihook/build/module.ver
new file mode 100644
index 000000000..7691020c2
--- /dev/null
+++ b/mailnews/mapi/mapihook/build/module.ver
@@ -0,0 +1,6 @@
+WIN32_MODULE_FILEVERSION=0,8,0,0
+WIN32_MODULE_FILEVERSION_STRING=0.8
+WIN32_MODULE_COPYRIGHT=©Thunderbird and Mozilla Developers, according to the MPL 1.1/GPL 2.0/LGPL 2.1 licenses, as applicable.
+WIN32_MODULE_COMPANYNAME=Mozilla.org
+WIN32_MODULE_TRADEMARKS=Mozilla
+WIN32_MODULE_COMMENT=Mozilla Thunderbird Thunderbird MAPI Proxy Dll
diff --git a/mailnews/mapi/mapihook/build/moz.build b/mailnews/mapi/mapihook/build/moz.build
new file mode 100644
index 000000000..5c42875cc
--- /dev/null
+++ b/mailnews/mapi/mapihook/build/moz.build
@@ -0,0 +1,22 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SharedLibrary('MapiProxy')
+
+OS_LIBS += [
+ 'rpcrt4',
+]
+
+for var in ('REGISTER_PROXY_DLL', 'UNICODE', '_UNICODE'):
+ DEFINES[var] = True
+
+# This produces a compile warning mozilla-config.h(145): warning C4005: '_WIN32_WINNT': macro redefinition
+#DEFINES['_WIN32_WINNT'] = '0x400'
+
+if not CONFIG['MOZ_INCOMPLETE_EXTERNAL_LINKAGE']:
+ DEFINES['MOZILLA_INTERNAL_API'] = True
+
+DEFFILE = SRCDIR + '/MapiProxy.def'
+
diff --git a/mailnews/mapi/mapihook/build/msgMapi.idl b/mailnews/mapi/mapihook/build/msgMapi.idl
new file mode 100644
index 000000000..e669b5452
--- /dev/null
+++ b/mailnews/mapi/mapihook/build/msgMapi.idl
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This idl will be compiled by MIDL. MS-COM is used
+// as bridge between MAPI clients and the Mozilla.
+
+import "unknwn.idl";
+
+typedef wchar_t LOGIN_PW_TYPE[256];
+
+typedef struct
+{
+ unsigned long ulReserved;
+ unsigned long flFlags; /* Flags */
+ unsigned long nPosition_NotUsed; /* character in text to be replaced by attachment */
+ LPSTR lpszPathName; /* Full path name including file name */
+ LPSTR lpszFileName; /* Real (original) file name */
+ unsigned char * lpFileType_NotUsed ;
+} nsMapiFileDesc, * lpnsMapiFileDesc;
+
+
+typedef struct
+{
+ unsigned long ulReserved;
+ unsigned long ulRecipClass; /* MAPI_TO, MAPI_CC, MAPI_BCC, MAPI_ORIG */
+ LPSTR lpszName; /* Recipient name to display */
+ LPSTR lpszAddress; /* Recipient email address */
+ unsigned long ulEIDSize_NotUsed;
+ unsigned char * lpEntryID_NotUsed ;
+} nsMapiRecipDesc, * lpnsMapiRecipDesc;
+
+typedef struct
+{
+ unsigned long ulReserved;
+ LPSTR lpszSubject; /* Message Subject */
+ LPSTR lpszNoteText; /* Message Text */
+ LPSTR lpszMessageType;
+ LPSTR lpszDateReceived; /* in YYYY/MM/DD HH:MM format */
+ LPSTR lpszConversationID_NotUsed; /* conversation thread ID */
+ unsigned long flFlags; /* unread,return receipt */
+ lpnsMapiRecipDesc lpOriginator; /* Originator descriptor */
+ unsigned long nRecipCount; /* Number of recipients */
+ [size_is (nRecipCount)] lpnsMapiRecipDesc lpRecips;/* Recipient descriptors */
+ unsigned long nFileCount; /* # of file attachments */
+ [size_is (nFileCount)] lpnsMapiFileDesc lpFiles; /* Attachment descriptors */
+} nsMapiMessage, * lpnsMapiMessage;
+
+[
+ object,
+ uuid(6EDCD38E-8861-11d5-A3DD-00B0D0F3BAA7),
+ helpstring("nsIMapi Inteface"),
+ pointer_default(unique)
+]
+
+interface nsIMapi : IUnknown
+{
+ HRESULT Login([in] unsigned long aUIArg, [in, unique] LOGIN_PW_TYPE aLogin,
+ [in, unique] LOGIN_PW_TYPE aPassWord, [in] unsigned long aFlags,
+ [out] unsigned long *aSessionId);
+
+ HRESULT Initialize();
+ HRESULT IsValid();
+ HRESULT IsValidSession([in] unsigned long aSession);
+
+ HRESULT SendMail([in] unsigned long aSession, [in, unique] lpnsMapiMessage aMessage,
+ [in] unsigned long aFlags, [in] unsigned long aReserved) ;
+
+ HRESULT SendDocuments( [in] unsigned long aSession,
+ [in, unique] LPTSTR aDelimChar, [in, unique] LPTSTR aFilePaths,
+ [in, unique] LPTSTR aFileNames, [in] ULONG aFlags ) ;
+
+ HRESULT FindNext( [in] unsigned long aSession, [in] ULONG ulUIParam, [in, unique] LPTSTR lpszMessageType,
+ [in, unique] LPTSTR lpszSeedMessageID, [in] ULONG flFlags, [in] ULONG ulReserved,
+ [in] [out] char lpszMessageID[64] ) ;
+
+ HRESULT ReadMail( [in] unsigned long lhSession, [in] ULONG ulUIParam, [in, unique] LPTSTR lpszMessageID,
+ [in] ULONG flFlags, [in] ULONG ulReserved, [out] lpnsMapiMessage *lppMessage);
+
+ HRESULT DeleteMail( [in] unsigned long lhSession, [in] ULONG ulUIParam, [in, unique] LPTSTR lpszMessageID,
+ [in] ULONG flFlags, [in] ULONG ulReserved);
+
+ HRESULT SaveMail( [in] unsigned long lhSession, [in] ULONG ulUIParam, [in, unique] lpnsMapiMessage lppMessage,
+ [in] ULONG flFlags, [in] ULONG ulReserved, [in, unique] LPTSTR lpszMessageID);
+
+ HRESULT Logoff (unsigned long aSession);
+ HRESULT CleanUp();
+};
+
+
+
diff --git a/mailnews/mapi/mapihook/moz.build b/mailnews/mapi/mapihook/moz.build
new file mode 100644
index 000000000..65ca38934
--- /dev/null
+++ b/mailnews/mapi/mapihook/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'public',
+ 'build',
+ 'src',
+]
+
diff --git a/mailnews/mapi/mapihook/public/moz.build b/mailnews/mapi/mapihook/public/moz.build
new file mode 100644
index 000000000..a2d2b2ef9
--- /dev/null
+++ b/mailnews/mapi/mapihook/public/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIMapiSupport.idl',
+]
+
+XPIDL_MODULE = 'mapihook'
+
diff --git a/mailnews/mapi/mapihook/public/nsIMapiSupport.idl b/mailnews/mapi/mapihook/public/nsIMapiSupport.idl
new file mode 100644
index 000000000..e160566a4
--- /dev/null
+++ b/mailnews/mapi/mapihook/public/nsIMapiSupport.idl
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface provides support for registering Mozilla as a COM component
+ * for extending the use of Mail/News through Simple MAPI.
+ *
+ */
+
+[noscript, uuid(2907B676-C4BD-49af-880A-E27A0616291E)]
+interface nsIMapiSupport : nsISupports {
+
+ /** Initiates MAPI support
+ */
+
+ void initializeMAPISupport();
+
+ /** Shuts down the MAPI support
+ */
+
+ void shutdownMAPISupport();
+
+ /** registerServer - register the mapi DLL with the desktop
+ * Typically called by the window shell service when we are
+ * made the default mail app
+ */
+ void registerServer();
+
+ /** unRegisterServer - unregister the mapi DLL with the desktop
+ * Typically called by the window shell service when we are
+ * removed as the default mail app.
+ */
+ void unRegisterServer();
+};
+
+%{C++
+#define NS_IMAPISUPPORT_CONTRACTID "@mozilla.org/mapisupport;1"
+#define NS_IMAPISUPPORT_CLASSNAME "Mozilla MAPI Support"
+%}
+
+
diff --git a/mailnews/mapi/mapihook/src/Makefile.in b/mailnews/mapi/mapihook/src/Makefile.in
new file mode 100644
index 000000000..10e6172bf
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/Makefile.in
@@ -0,0 +1,6 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+CSRCS += ../build/msgMapi_i.c
diff --git a/mailnews/mapi/mapihook/src/Registry.cpp b/mailnews/mapi/mapihook/src/Registry.cpp
new file mode 100644
index 000000000..31db7520b
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/Registry.cpp
@@ -0,0 +1,291 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#undef _UNICODE
+#undef UNICODE
+
+#include <objbase.h>
+#include "nsStringGlue.h"
+#include "Registry.h"
+
+#define MAPI_PROXY_DLL_NAME "MapiProxy.dll"
+#define MAPI_STARTUP_ARG " /MAPIStartUp"
+#define MAX_SIZE 2048
+
+// Size of a CLSID as a string
+const int CLSID_STRING_SIZE = 39;
+
+// Proxy/Stub Dll Routines
+
+typedef HRESULT (__stdcall ProxyServer)();
+
+
+// Convert a CLSID to a char string.
+
+BOOL CLSIDtochar(const CLSID& clsid, char* szCLSID,
+ int length)
+{
+ LPOLESTR wszCLSID = NULL;
+
+ // Get CLSID
+ HRESULT hr = StringFromCLSID(clsid, &wszCLSID);
+ if (FAILED(hr))
+ return FALSE;
+
+ // Covert from wide characters to non-wide.
+ wcstombs(szCLSID, wszCLSID, length);
+
+ // Free memory.
+ CoTaskMemFree(wszCLSID);
+
+ return TRUE;
+}
+
+// Create a key and set its value.
+
+BOOL setKeyAndValue(nsAutoCString keyName, const char* subKey,
+ const char* theValue)
+{
+ HKEY hKey;
+ BOOL retValue = TRUE;
+
+ nsAutoCString theKey(keyName);
+ if (subKey != NULL)
+ {
+ theKey += "\\";
+ theKey += subKey;
+ }
+
+ // Create and open key and subkey.
+ long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT, theKey.get(),
+ 0, NULL, REG_OPTION_NON_VOLATILE,
+ KEY_ALL_ACCESS, NULL, &hKey, NULL);
+ if (lResult != ERROR_SUCCESS)
+ return FALSE ;
+
+ // Set the Value.
+ if (theValue != NULL)
+ {
+ lResult = RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)theValue,
+ strlen(theValue)+1);
+ if (lResult != ERROR_SUCCESS)
+ retValue = FALSE;
+ }
+
+ RegCloseKey(hKey);
+ return TRUE;
+}
+
+// Delete a key and all of its descendents.
+
+LONG recursiveDeleteKey(HKEY hKeyParent, // Parent of key to delete
+ const char* lpszKeyChild) // Key to delete
+{
+ // Open the child.
+ HKEY hKeyChild ;
+ LONG lRes = RegOpenKeyEx(hKeyParent, lpszKeyChild, 0,
+ KEY_ALL_ACCESS, &hKeyChild) ;
+ if (lRes != ERROR_SUCCESS)
+ {
+ return lRes ;
+ }
+
+ // Enumerate all of the decendents of this child.
+ FILETIME time ;
+ char szBuffer[MAX_SIZE] ;
+ DWORD dwSize = MAX_SIZE ;
+ while (RegEnumKeyEx(hKeyChild, 0, szBuffer, &dwSize, NULL,
+ NULL, NULL, &time) == S_OK)
+ {
+ // Delete the decendents of this child.
+ lRes = recursiveDeleteKey(hKeyChild, szBuffer) ;
+ if (lRes != ERROR_SUCCESS)
+ {
+ // Cleanup before exiting.
+ RegCloseKey(hKeyChild) ;
+ return lRes;
+ }
+ dwSize = MAX_SIZE;
+ }
+
+ // Close the child.
+ RegCloseKey(hKeyChild) ;
+
+ // Delete this child.
+ return RegDeleteKey(hKeyParent, lpszKeyChild) ;
+}
+
+void RegisterProxy()
+{
+ HINSTANCE h = NULL;
+ ProxyServer *RegisterFunc = NULL;
+
+ char szModule[MAX_SIZE];
+ char *pTemp = NULL;
+
+ HMODULE hModule = GetModuleHandle(NULL);
+ DWORD dwResult = ::GetModuleFileName(hModule, szModule,
+ sizeof(szModule)/sizeof(char));
+ if (dwResult == 0)
+ return;
+
+ pTemp = strrchr(szModule, '\\');
+ if (pTemp == NULL)
+ return;
+
+ *pTemp = '\0';
+ nsAutoCString proxyPath(szModule);
+
+ proxyPath += "\\";
+ proxyPath += MAPI_PROXY_DLL_NAME;
+
+ h = LoadLibrary(proxyPath.get());
+ if (h == NULL)
+ return;
+
+ RegisterFunc = (ProxyServer *) GetProcAddress(h, "DllRegisterServer");
+ if (RegisterFunc)
+ RegisterFunc();
+
+ FreeLibrary(h);
+}
+
+void UnRegisterProxy()
+{
+ HINSTANCE h = NULL;
+ ProxyServer *UnRegisterFunc = NULL;
+
+ char szModule[MAX_SIZE];
+ char *pTemp = NULL;
+
+ HMODULE hModule = GetModuleHandle(NULL);
+ DWORD dwResult = ::GetModuleFileName(hModule, szModule,
+ sizeof(szModule)/sizeof(char));
+ if (dwResult == 0)
+ return;
+
+ pTemp = strrchr(szModule, '\\');
+ if (pTemp == NULL)
+ return;
+
+ *pTemp = '\0';
+ nsAutoCString proxyPath(szModule);
+
+ proxyPath += "\\";
+ proxyPath += MAPI_PROXY_DLL_NAME;
+
+ h = LoadLibrary(proxyPath.get());
+ if (h == NULL)
+ return;
+
+ UnRegisterFunc = (ProxyServer *) GetProcAddress(h, "DllUnregisterServer");
+ if (UnRegisterFunc)
+ UnRegisterFunc();
+
+ FreeLibrary(h);
+}
+
+// Register the component in the registry.
+
+HRESULT RegisterServer(const CLSID& clsid, // Class ID
+ const char* szFriendlyName, // Friendly Name
+ const char* szVerIndProgID, // Programmatic
+ const char* szProgID) // IDs
+{
+ HMODULE hModule = GetModuleHandle(NULL);
+ char szModuleName[MAX_SIZE];
+ char szCLSID[CLSID_STRING_SIZE];
+
+ nsAutoCString independentProgId(szVerIndProgID);
+ nsAutoCString progId(szProgID);
+
+ DWORD dwResult = ::GetModuleFileName(hModule, szModuleName,
+ sizeof(szModuleName)/sizeof(char));
+
+ if (dwResult == 0)
+ return S_FALSE;
+
+ nsAutoCString moduleName(szModuleName);
+ nsAutoCString registryKey("CLSID\\");
+
+ moduleName += MAPI_STARTUP_ARG;
+
+ // Convert the CLSID into a char.
+
+ if (!CLSIDtochar(clsid, szCLSID, sizeof(szCLSID)))
+ return S_FALSE;
+ registryKey += szCLSID;
+
+ // Add the CLSID to the registry.
+ if (!setKeyAndValue(registryKey, NULL, szFriendlyName))
+ return S_FALSE;
+
+ if (!setKeyAndValue(registryKey, "LocalServer32", moduleName.get()))
+ return S_FALSE;
+
+ // Add the ProgID subkey under the CLSID key.
+ if (!setKeyAndValue(registryKey, "ProgID", szProgID))
+ return S_FALSE;
+
+ // Add the version-independent ProgID subkey under CLSID key.
+ if (!setKeyAndValue(registryKey, "VersionIndependentProgID", szVerIndProgID))
+ return S_FALSE;
+
+ // Add the version-independent ProgID subkey under HKEY_CLASSES_ROOT.
+ if (!setKeyAndValue(independentProgId, NULL, szFriendlyName))
+ return S_FALSE;
+ if (!setKeyAndValue(independentProgId, "CLSID", szCLSID))
+ return S_FALSE;
+ if (!setKeyAndValue(independentProgId, "CurVer", szProgID))
+ return S_FALSE;
+
+ // Add the versioned ProgID subkey under HKEY_CLASSES_ROOT.
+ if (!setKeyAndValue(progId, NULL, szFriendlyName))
+ return S_FALSE;
+ if (!setKeyAndValue(progId, "CLSID", szCLSID))
+ return S_FALSE;
+
+ RegisterProxy();
+
+ return S_OK;
+}
+
+LONG UnregisterServer(const CLSID& clsid, // Class ID
+ const char* szVerIndProgID, // Programmatic
+ const char* szProgID) // IDs
+{
+ LONG lResult = S_OK;
+
+ // Convert the CLSID into a char.
+
+ char szCLSID[CLSID_STRING_SIZE];
+ if (!CLSIDtochar(clsid, szCLSID, sizeof(szCLSID)))
+ return S_FALSE;
+
+ UnRegisterProxy();
+
+ nsAutoCString registryKey("CLSID\\");
+ registryKey += szCLSID;
+
+ lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, registryKey.get());
+ if (lResult == ERROR_SUCCESS || lResult == ERROR_FILE_NOT_FOUND)
+ return lResult;
+
+ registryKey += "\\LocalServer32";
+
+ // Delete only the path for this server.
+
+ lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, registryKey.get());
+ if (lResult != ERROR_SUCCESS && lResult != ERROR_FILE_NOT_FOUND)
+ return lResult;
+
+ // Delete the version-independent ProgID Key.
+ lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, szVerIndProgID);
+ if (lResult != ERROR_SUCCESS && lResult != ERROR_FILE_NOT_FOUND)
+ return lResult;
+
+ lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, szProgID);
+
+ return lResult;
+}
diff --git a/mailnews/mapi/mapihook/src/Registry.h b/mailnews/mapi/mapihook/src/Registry.h
new file mode 100644
index 000000000..34a4efca7
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/Registry.h
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _REGISTRY_H_
+#define _REGISTRY_H_
+
+#include <objbase.h>
+
+// This function will register a component in the Registry.
+
+HRESULT RegisterServer(const CLSID& clsid,
+ const char* szFriendlyName,
+ const char* szVerIndProgID,
+ const char* szProgID) ;
+
+// This function will unregister a component.
+
+HRESULT UnregisterServer(const CLSID& clsid,
+ const char* szVerIndProgID,
+ const char* szProgID) ;
+
+#endif
diff --git a/mailnews/mapi/mapihook/src/moz.build b/mailnews/mapi/mapihook/src/moz.build
new file mode 100644
index 000000000..3e8cd3450
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/moz.build
@@ -0,0 +1,25 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'msgMapiFactory.cpp',
+ 'msgMapiHook.cpp',
+ 'msgMapiImp.cpp',
+ 'msgMapiMain.cpp',
+ 'msgMapiSupport.cpp',
+ 'Registry.cpp',
+]
+
+if CONFIG['MOZ_INCOMPLETE_EXTERNAL_LINKAGE']:
+ XPCOMBinaryComponent('msgMapi')
+else:
+ FINAL_LIBRARY = 'xul'
+
+OS_LIBS += [
+ 'ole32',
+]
+
+DEFINES['UNICODE'] = True
+DEFINES['_UNICODE'] = True
diff --git a/mailnews/mapi/mapihook/src/msgMapiFactory.cpp b/mailnews/mapi/mapihook/src/msgMapiFactory.cpp
new file mode 100644
index 000000000..829279f62
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/msgMapiFactory.cpp
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#undef UNICODE
+#undef _UNICODE
+
+#include "msgMapiFactory.h"
+#include "msgMapiImp.h"
+#include "msgMapi.h"
+
+CMapiFactory ::CMapiFactory()
+: m_cRef(1)
+{
+}
+
+CMapiFactory::~CMapiFactory()
+{
+}
+
+STDMETHODIMP CMapiFactory::QueryInterface(const IID& aIid, void** aPpv)
+{
+ if ((aIid == IID_IUnknown) || (aIid == IID_IClassFactory))
+ {
+ *aPpv = static_cast<IClassFactory*>(this);
+ }
+ else
+ {
+ *aPpv = nullptr;
+ return E_NOINTERFACE;
+ }
+ reinterpret_cast<IUnknown*>(*aPpv)->AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) CMapiFactory::AddRef()
+{
+ return ++m_cRef;
+}
+
+STDMETHODIMP_(ULONG) CMapiFactory::Release()
+{
+ int32_t temp = --m_cRef;
+ if (m_cRef == 0)
+ {
+ delete this;
+ return 0;
+ }
+
+ return temp;
+}
+
+STDMETHODIMP CMapiFactory::CreateInstance(IUnknown* aUnknownOuter,
+ const IID& aIid,
+ void** aPpv)
+{
+ // Cannot aggregate.
+
+ if (aUnknownOuter != nullptr)
+ {
+ return CLASS_E_NOAGGREGATION ;
+ }
+
+ // Create component.
+
+ CMapiImp* pImp = new CMapiImp();
+ if (pImp == nullptr)
+ {
+ return E_OUTOFMEMORY ;
+ }
+
+ // Get the requested interface.
+ HRESULT hr = pImp->QueryInterface(aIid, aPpv);
+
+ // Release the IUnknown pointer.
+ // (If QueryInterface failed, component will delete itself.)
+
+ pImp->Release();
+ return hr;
+}
+
+STDMETHODIMP CMapiFactory::LockServer(BOOL aLock)
+{
+ return S_OK ;
+}
diff --git a/mailnews/mapi/mapihook/src/msgMapiFactory.h b/mailnews/mapi/mapihook/src/msgMapiFactory.h
new file mode 100644
index 000000000..7ae2f2ed3
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/msgMapiFactory.h
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MSG_MAPI_FACTORY_H
+#define MSG_MAPI_FACTORY_H
+
+#include <windows.h>
+#include <objbase.h>
+#include "nspr.h"
+#include "nsISupportsImpl.h" // ThreadSafeAutoRefCnt
+#include <stdint.h>
+
+
+class CMapiFactory : public IClassFactory
+{
+public :
+
+ // IUnknown
+
+ STDMETHODIMP QueryInterface (REFIID aIid, void** aPpv);
+ STDMETHODIMP_(ULONG) AddRef(void);
+ STDMETHODIMP_(ULONG) Release(void);
+
+ // IClassFactory
+
+ STDMETHODIMP CreateInstance (LPUNKNOWN aUnkOuter, REFIID aIid, void **aPpv);
+ STDMETHODIMP LockServer (BOOL aLock);
+
+ CMapiFactory ();
+
+private:
+ mozilla::ThreadSafeAutoRefCnt m_cRef;
+
+ ~CMapiFactory ();
+};
+
+#endif // MSG_MAPI_FACTORY_H
+
diff --git a/mailnews/mapi/mapihook/src/msgMapiHook.cpp b/mailnews/mapi/mapihook/src/msgMapiHook.cpp
new file mode 100644
index 000000000..54aae7954
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/msgMapiHook.cpp
@@ -0,0 +1,748 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define MAPI_STARTUP_ARG "/MAPIStartUp"
+
+#include <mapidefs.h>
+#include <mapi.h>
+#include <tchar.h>
+#include <direct.h>
+#include "nsCOMPtr.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsISupports.h"
+#include "nsIPromptService.h"
+#include "nsIAppStartup.h"
+#include "nsIAppShellService.h"
+#include "mozIDOMWindow.h"
+#include "nsINativeAppSupport.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsIStringBundle.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsStringGlue.h"
+#include "nsUnicharUtils.h"
+#include "nsIMsgAttachment.h"
+#include "nsIMsgCompFields.h"
+#include "nsIMsgComposeParams.h"
+#include "nsIMsgCompose.h"
+#include "nsMsgCompCID.h"
+#include "nsIMsgSend.h"
+#include "nsIMsgComposeService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIDirectoryService.h"
+#include "nsMsgI18N.h"
+#include "msgMapi.h"
+#include "msgMapiHook.h"
+#include "msgMapiSupport.h"
+#include "msgMapiMain.h"
+#include "nsThreadUtils.h"
+#include "nsMsgUtils.h"
+#include "nsNetUtil.h"
+#include "mozilla/Services.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+#include "nsEmbedCID.h"
+#include "mozilla/Logging.h"
+
+extern PRLogModuleInfo *MAPI;
+
+class nsMAPISendListener : public nsIMsgSendListener
+{
+public:
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /* void OnStartSending (in string aMsgID, in uint32_t aMsgSize); */
+ NS_IMETHOD OnStartSending(const char *aMsgID, uint32_t aMsgSize) { return NS_OK; }
+
+ /* void OnProgress (in string aMsgID, in uint32_t aProgress, in uint32_t aProgressMax); */
+ NS_IMETHOD OnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax) { return NS_OK;}
+
+ /* void OnStatus (in string aMsgID, in wstring aMsg); */
+ NS_IMETHOD OnStatus(const char *aMsgID, const char16_t *aMsg) { return NS_OK;}
+
+ /* void OnStopSending (in string aMsgID, in nsresult aStatus, in wstring aMsg, in nsIFile returnFile); */
+ NS_IMETHOD OnStopSending(const char *aMsgID, nsresult aStatus, const char16_t *aMsg,
+ nsIFile *returnFile) {
+ PR_CEnterMonitor(this);
+ PR_CNotifyAll(this);
+ m_done = true;
+ PR_CExitMonitor(this);
+ return NS_OK ;
+ }
+
+ /* void OnSendNotPerformed */
+ NS_IMETHOD OnSendNotPerformed(const char *aMsgID, nsresult aStatus)
+ {
+ return OnStopSending(aMsgID, aStatus, nullptr, nullptr) ;
+ }
+
+ /* void OnGetDraftFolderURI (); */
+ NS_IMETHOD OnGetDraftFolderURI(const char *aFolderURI) {return NS_OK;}
+
+ static nsresult CreateMAPISendListener( nsIMsgSendListener **ppListener);
+
+ bool IsDone() { return m_done ; }
+
+protected :
+ nsMAPISendListener() {
+ m_done = false;
+ }
+
+ bool m_done;
+private:
+ virtual ~nsMAPISendListener() { }
+
+};
+
+
+NS_IMPL_ISUPPORTS(nsMAPISendListener, nsIMsgSendListener)
+
+nsresult nsMAPISendListener::CreateMAPISendListener( nsIMsgSendListener **ppListener)
+{
+ NS_ENSURE_ARG_POINTER(ppListener) ;
+
+ *ppListener = new nsMAPISendListener();
+ if (! *ppListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*ppListener);
+ return NS_OK;
+}
+
+bool nsMapiHook::isMapiService = false;
+
+void nsMapiHook::CleanUp()
+{
+ // This routine will be fully implemented in future
+ // to cleanup mapi related stuff inside mozilla code.
+}
+
+bool nsMapiHook::DisplayLoginDialog(bool aLogin, char16_t **aUsername,
+ char16_t **aPassword)
+{
+ nsresult rv;
+ bool btnResult = false;
+
+ nsCOMPtr<nsIPromptService> dlgService(do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && dlgService)
+ {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService) return false;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(MAPI_PROPERTIES_CHROME, getter_AddRefs(bundle));
+ if (NS_FAILED(rv) || !bundle) return false;
+
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle(
+ "chrome://branding/locale/brand.properties",
+ getter_AddRefs(brandBundle));
+ if (NS_FAILED(rv)) return false;
+
+ nsString brandName;
+ rv = brandBundle->GetStringFromName(
+ u"brandFullName",
+ getter_Copies(brandName));
+ if (NS_FAILED(rv)) return false;
+
+ nsString loginTitle;
+ const char16_t *brandStrings[] = { brandName.get() };
+ NS_NAMED_LITERAL_STRING(loginTitlePropertyTag, "loginTitle");
+ const char16_t *dTitlePropertyTag = loginTitlePropertyTag.get();
+ rv = bundle->FormatStringFromName(dTitlePropertyTag, brandStrings, 1,
+ getter_Copies(loginTitle));
+ if (NS_FAILED(rv)) return false;
+
+ if (aLogin)
+ {
+ nsString loginText;
+ rv = bundle->GetStringFromName(u"loginTextwithName",
+ getter_Copies(loginText));
+ if (NS_FAILED(rv) || loginText.IsEmpty()) return false;
+
+ bool dummyValue = false;
+ rv = dlgService->PromptUsernameAndPassword(nullptr, loginTitle.get(),
+ loginText.get(), aUsername, aPassword,
+ nullptr, &dummyValue, &btnResult);
+ }
+ else
+ {
+ //nsString loginString;
+ nsString loginText;
+ const char16_t *userNameStrings[] = { *aUsername };
+
+ NS_NAMED_LITERAL_STRING(loginTextPropertyTag, "loginText");
+ const char16_t *dpropertyTag = loginTextPropertyTag.get();
+ rv = bundle->FormatStringFromName(dpropertyTag, userNameStrings, 1,
+ getter_Copies(loginText));
+ if (NS_FAILED(rv)) return false;
+
+ bool dummyValue = false;
+ rv = dlgService->PromptPassword(nullptr, loginTitle.get(), loginText.get(),
+ aPassword, nullptr, &dummyValue, &btnResult);
+ }
+ }
+
+ return btnResult;
+}
+
+bool nsMapiHook::VerifyUserName(const nsString& aUsername, nsCString& aIdKey)
+{
+ nsresult rv;
+
+ if (aUsername.IsEmpty())
+ return false;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager(do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return false;
+ nsCOMPtr<nsIArray> identities;
+ rv = accountManager->GetAllIdentities(getter_AddRefs(identities));
+ if (NS_FAILED(rv)) return false;
+
+ uint32_t numIndentities = 0;
+ identities->GetLength(&numIndentities);
+
+ for (uint32_t i = 0; i < numIndentities; i++)
+ {
+ nsCOMPtr<nsIMsgIdentity> thisIdentity(do_QueryElementAt(identities, i, &rv));
+ if (NS_SUCCEEDED(rv) && thisIdentity)
+ {
+ nsCString email;
+ rv = thisIdentity->GetEmail(email);
+ if (NS_FAILED(rv)) continue;
+
+ // get the username from the email and compare with the username
+ int32_t index = email.FindChar('@');
+ if (index != -1)
+ email.SetLength(index);
+
+ if (aUsername.Equals(NS_ConvertASCIItoUTF16(email)))
+ return NS_SUCCEEDED(thisIdentity->GetKey(aIdKey));
+ }
+ }
+
+ return false;
+}
+
+bool
+nsMapiHook::IsBlindSendAllowed()
+{
+ bool enabled = false;
+ bool warn = true;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ prefBranch->GetBoolPref(PREF_MAPI_WARN_PRIOR_TO_BLIND_SEND, &warn);
+ prefBranch->GetBoolPref(PREF_MAPI_BLIND_SEND_ENABLED, &enabled);
+ }
+ if (!enabled)
+ return false;
+
+ if (!warn)
+ return true; // Everything is okay.
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService) return false;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(MAPI_PROPERTIES_CHROME, getter_AddRefs(bundle));
+ if (NS_FAILED(rv) || !bundle) return false;
+
+ nsString warningMsg;
+ rv = bundle->GetStringFromName(u"mapiBlindSendWarning",
+ getter_Copies(warningMsg));
+ if (NS_FAILED(rv)) return false;
+
+ nsString dontShowAgainMessage;
+ rv = bundle->GetStringFromName(u"mapiBlindSendDontShowAgain",
+ getter_Copies(dontShowAgainMessage));
+ if (NS_FAILED(rv)) return false;
+
+ nsCOMPtr<nsIPromptService> dlgService(do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv) || !dlgService) return false;
+
+ bool continueToWarn = true;
+ bool okayToContinue = false;
+ dlgService->ConfirmCheck(nullptr, nullptr, warningMsg.get(), dontShowAgainMessage.get(), &continueToWarn, &okayToContinue);
+
+ if (!continueToWarn && okayToContinue && prefBranch)
+ prefBranch->SetBoolPref(PREF_MAPI_WARN_PRIOR_TO_BLIND_SEND, false);
+
+ return okayToContinue;
+}
+
+// this is used for Send without UI
+nsresult nsMapiHook::BlindSendMail (unsigned long aSession, nsIMsgCompFields * aCompFields)
+{
+ nsresult rv = NS_OK ;
+
+ if (!IsBlindSendAllowed())
+ return NS_ERROR_FAILURE;
+
+ /** create nsIMsgComposeParams obj and other fields to populate it **/
+
+ nsCOMPtr<mozIDOMWindowProxy> hiddenWindow;
+ // get parent window
+ nsCOMPtr<nsIAppShellService> appService = do_GetService( "@mozilla.org/appshell/appShellService;1", &rv);
+ if (NS_FAILED(rv)|| (!appService) ) return rv ;
+
+ rv = appService->GetHiddenDOMWindow(getter_AddRefs(hiddenWindow));
+ if ( NS_FAILED(rv) ) return rv ;
+ // smtp password and Logged in used IdKey from MapiConfig (session obj)
+ nsMAPIConfiguration * pMapiConfig = nsMAPIConfiguration::GetMAPIConfiguration() ;
+ if (!pMapiConfig) return NS_ERROR_FAILURE ; // get the singelton obj
+ char16_t * password = pMapiConfig->GetPassword(aSession) ;
+ // password
+ nsAutoCString smtpPassword;
+ LossyCopyUTF16toASCII(password, smtpPassword);
+
+ // Id key
+ nsCString MsgIdKey;
+ pMapiConfig->GetIdKey(aSession, MsgIdKey);
+
+ // get the MsgIdentity for the above key using AccountManager
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService (NS_MSGACCOUNTMANAGER_CONTRACTID) ;
+ if (NS_FAILED(rv) || (!accountManager) ) return rv ;
+
+ nsCOMPtr <nsIMsgIdentity> pMsgId ;
+ rv = accountManager->GetIdentity (MsgIdKey, getter_AddRefs(pMsgId)) ;
+ if (NS_FAILED(rv) ) return rv ;
+
+ // create a send listener to get back the send status
+ nsCOMPtr <nsIMsgSendListener> sendListener ;
+ rv = nsMAPISendListener::CreateMAPISendListener(getter_AddRefs(sendListener)) ;
+ if (NS_FAILED(rv) || (!sendListener) ) return rv;
+
+ // create the compose params object
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv));
+ if (NS_FAILED(rv) || (!pMsgComposeParams) ) return rv ;
+
+ // populate the compose params
+ bool forcePlainText;
+ aCompFields->GetForcePlainText(&forcePlainText);
+ pMsgComposeParams->SetType(nsIMsgCompType::New);
+ pMsgComposeParams->SetFormat(forcePlainText ? nsIMsgCompFormat::PlainText : nsIMsgCompFormat::HTML);
+ pMsgComposeParams->SetIdentity(pMsgId);
+ pMsgComposeParams->SetComposeFields(aCompFields);
+ pMsgComposeParams->SetSendListener(sendListener) ;
+ pMsgComposeParams->SetSmtpPassword(smtpPassword.get());
+
+ // create the nsIMsgCompose object to send the object
+ nsCOMPtr<nsIMsgCompose> pMsgCompose (do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv));
+ if (NS_FAILED(rv) || (!pMsgCompose) ) return rv ;
+
+ /** initialize nsIMsgCompose, Send the message, wait for send completion response **/
+
+ rv = pMsgCompose->Initialize(pMsgComposeParams, hiddenWindow, nullptr);
+ if (NS_FAILED(rv)) return rv ;
+
+ // If we're in offline mode, we'll need to queue it for later. No point in trying to send it.
+ return pMsgCompose->SendMsg(WeAreOffline() ? nsIMsgSend::nsMsgQueueForLater : nsIMsgSend::nsMsgDeliverNow,
+ pMsgId, nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv)) return rv ;
+
+ // assign to interface pointer from nsCOMPtr to facilitate typecast below
+ nsIMsgSendListener * pSendListener = sendListener ;
+
+ // we need to wait here to make sure that we return only after send is completed
+ // so we will have a event loop here which will process the events till the Send IsDone.
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+ while ( !((nsMAPISendListener *) pSendListener)->IsDone() )
+ {
+ PR_CEnterMonitor(pSendListener);
+ PR_CWait(pSendListener, PR_MicrosecondsToInterval(1000UL));
+ PR_CExitMonitor(pSendListener);
+ NS_ProcessPendingEvents(thread);
+ }
+
+ return rv ;
+}
+
+nsresult nsMapiHook::HandleAttachments (nsIMsgCompFields * aCompFields, int32_t aFileCount,
+ lpnsMapiFileDesc aFiles)
+{
+ nsresult rv = NS_OK ;
+ // Do nothing if there are no files to process.
+ if (!aFiles || aFileCount <= 0)
+ return NS_OK;
+
+ nsAutoCString Attachments ;
+ nsAutoCString TempFiles ;
+
+ nsCOMPtr <nsIFile> pFile = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID, &rv) ;
+ if (NS_FAILED(rv) || (!pFile) ) return rv ;
+ nsCOMPtr <nsIFile> pTempDir = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID, &rv) ;
+ if (NS_FAILED(rv) || (!pTempDir) ) return rv ;
+
+ for (int i=0 ; i < aFileCount ; i++)
+ {
+ if (aFiles[i].lpszPathName)
+ {
+ // check if attachment exists
+ pFile->InitWithNativePath(nsDependentCString((const char*)aFiles[i].lpszPathName));
+
+ bool bExist ;
+ rv = pFile->Exists(&bExist) ;
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("nsMapiHook::HandleAttachments: filename: %s path: %s exists = %s \n", (const char*)aFiles[i].lpszFileName, (const char*)aFiles[i].lpszPathName, bExist ? "true" : "false"));
+ if (NS_FAILED(rv) || (!bExist) ) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST ;
+
+ //Temp Directory
+ nsCOMPtr <nsIFile> pTempDir;
+ NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(pTempDir));
+
+ // create a new sub directory called moz_mapi underneath the temp directory
+ pTempDir->AppendRelativePath(NS_LITERAL_STRING("moz_mapi"));
+ pTempDir->Exists (&bExist) ;
+ if (!bExist)
+ {
+ rv = pTempDir->Create(nsIFile::DIRECTORY_TYPE, 777) ;
+ if (NS_FAILED(rv)) return rv ;
+ }
+
+ // rename or copy the existing temp file with the real file name
+
+ nsAutoString leafName ;
+ nsAutoCString platformCharSet;
+ // convert to Unicode using Platform charset
+ // leafName already contains a unicode leafName from lpszPathName. If we were given
+ // a value for lpszFileName, use it. Otherwise stick with leafName
+ if (aFiles[i].lpszFileName)
+ {
+ nsAutoString wholeFileName;
+ if (platformCharSet.IsEmpty())
+ platformCharSet.Assign(nsMsgI18NFileSystemCharset());
+ rv = ConvertToUnicode(platformCharSet.get(), (char *) aFiles[i].lpszFileName, wholeFileName);
+ // need to find the last '\' and find the leafname from that.
+ int32_t lastSlash = wholeFileName.RFindChar(char16_t('\\'));
+ if (lastSlash != kNotFound)
+ leafName.Assign(Substring(wholeFileName, lastSlash + 1));
+ else
+ leafName.Assign(wholeFileName);
+ }
+ else
+ pFile->GetLeafName (leafName);
+
+ nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ attachment->SetName(leafName);
+
+ nsCOMPtr<nsIFile> pTempFile;
+ rv = pTempDir->Clone(getter_AddRefs(pTempFile));
+ if (NS_FAILED(rv) || !pTempFile)
+ return rv;
+
+ pTempFile->Append(leafName);
+ pTempFile->Exists(&bExist);
+ if (bExist)
+ {
+ rv = pTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0777);
+ NS_ENSURE_SUCCESS(rv, rv);
+ pTempFile->Remove(false); // remove so we can copy over it.
+ pTempFile->GetLeafName(leafName);
+ }
+ // copy the file to its new location and file name
+ pFile->CopyTo(pTempDir, leafName);
+ // point pFile to the new location of the attachment
+ pFile->InitWithFile(pTempDir);
+ pFile->Append(leafName);
+
+ // create MsgCompose attachment object
+ attachment->SetTemporary(true); // this one is a temp file so set the flag for MsgCompose
+
+ // now set the attachment object
+ nsAutoCString pURL ;
+ NS_GetURLSpecFromFile(pFile, pURL);
+ attachment->SetUrl(pURL);
+
+ // set the file size
+ int64_t fileSize;
+ pFile->GetFileSize(&fileSize);
+ attachment->SetSize(fileSize);
+
+ // add the attachment
+ rv = aCompFields->AddAttachment (attachment);
+ if (NS_FAILED(rv))
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("nsMapiHook::HandleAttachments: AddAttachment rv = %lx\n", rv));
+ }
+ }
+ return rv ;
+}
+
+
+// this is used to convert non Unicode data and then populate comp fields
+nsresult nsMapiHook::PopulateCompFieldsWithConversion(lpnsMapiMessage aMessage,
+ nsIMsgCompFields * aCompFields)
+{
+ nsresult rv = NS_OK;
+
+ if (aMessage->lpOriginator)
+ {
+ nsAutoString From;
+ From.Append(NS_ConvertASCIItoUTF16((char *) aMessage->lpOriginator->lpszAddress));
+ aCompFields->SetFrom (From);
+ }
+
+ nsAutoString To;
+ nsAutoString Cc;
+ nsAutoString Bcc;
+ NS_NAMED_LITERAL_STRING(Comma, ",");
+ if (aMessage->lpRecips)
+ {
+ for (int i=0 ; i < (int) aMessage->nRecipCount ; i++)
+ {
+ if (aMessage->lpRecips[i].lpszAddress || aMessage->lpRecips[i].lpszName)
+ {
+ const char *addressWithoutType = (aMessage->lpRecips[i].lpszAddress)
+ ? aMessage->lpRecips[i].lpszAddress : aMessage->lpRecips[i].lpszName;
+ if (!PL_strncasecmp(addressWithoutType, "SMTP:", 5))
+ addressWithoutType += 5;
+
+ switch (aMessage->lpRecips[i].ulRecipClass)
+ {
+ case MAPI_TO :
+ if (!To.IsEmpty())
+ To += Comma ;
+ To.Append(NS_ConvertASCIItoUTF16(addressWithoutType));
+ break ;
+
+ case MAPI_CC :
+ if (!Cc.IsEmpty())
+ Cc += Comma ;
+ Cc.Append(NS_ConvertASCIItoUTF16(addressWithoutType));
+ break ;
+
+ case MAPI_BCC :
+ if (!Bcc.IsEmpty())
+ Bcc += Comma ;
+ Bcc.Append(NS_ConvertASCIItoUTF16(addressWithoutType));
+ break ;
+ }
+ }
+ }
+ }
+
+ // set To, Cc, Bcc
+ aCompFields->SetTo (To) ;
+ aCompFields->SetCc (Cc) ;
+ aCompFields->SetBcc (Bcc) ;
+
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("to: %s cc: %s bcc: %s \n", NS_ConvertUTF16toUTF8(To).get(), NS_ConvertUTF16toUTF8(Cc).get(), NS_ConvertUTF16toUTF8(Bcc).get()));
+
+ nsAutoCString platformCharSet;
+ // set subject
+ if (aMessage->lpszSubject)
+ {
+ nsAutoString Subject ;
+ if (platformCharSet.IsEmpty())
+ platformCharSet.Assign(nsMsgI18NFileSystemCharset());
+ rv = ConvertToUnicode(platformCharSet.get(), (char *) aMessage->lpszSubject, Subject);
+ if (NS_FAILED(rv)) return rv;
+ aCompFields->SetSubject(Subject);
+ }
+
+ // handle attachments as File URL
+ rv = HandleAttachments(aCompFields, aMessage->nFileCount, aMessage->lpFiles);
+ if (NS_FAILED(rv)) return rv ;
+
+ // set body
+ if (aMessage->lpszNoteText)
+ {
+ nsAutoString Body ;
+ if (platformCharSet.IsEmpty())
+ platformCharSet.Assign(nsMsgI18NFileSystemCharset());
+ rv = ConvertToUnicode(platformCharSet.get(), (char *) aMessage->lpszNoteText, Body);
+ if (NS_FAILED(rv)) return rv ;
+ if (Body.IsEmpty() || Body.Last() != '\n')
+ Body.AppendLiteral(CRLF);
+
+ // This is needed when Simple MAPI is used without a compose window.
+ // See bug 1366196.
+ if (Body.Find("<html>") == kNotFound)
+ aCompFields->SetForcePlainText(true);
+
+ rv = aCompFields->SetBody(Body) ;
+ }
+
+#ifdef RAJIV_DEBUG
+ // testing what all was set in CompFields
+ printf ("To : %S \n", To.get()) ;
+ printf ("CC : %S \n", Cc.get() ) ;
+ printf ("BCC : %S \n", Bcc.get() ) ;
+#endif
+
+ return rv ;
+}
+
+// this is used to populate the docs as attachments in the Comp fields for Send Documents
+nsresult nsMapiHook::PopulateCompFieldsForSendDocs(nsIMsgCompFields * aCompFields, ULONG aFlags,
+ LPTSTR aDelimChar, LPTSTR aFilePaths)
+{
+ nsAutoString strDelimChars ;
+ nsString strFilePaths;
+ nsresult rv = NS_OK ;
+ bool bExist ;
+
+ if (aDelimChar)
+ strDelimChars.Assign(aDelimChar);
+ if (aFilePaths)
+ strFilePaths.Assign(aFilePaths);
+
+ // check for comma in filename
+ if (strDelimChars.FindChar(',') == kNotFound) // if comma is not in the delimiter specified by user
+ {
+ if (strFilePaths.FindChar(',') != kNotFound) // if comma found in filenames return error
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ nsCString Attachments ;
+
+ // only 1 file is to be sent, no delim specified
+ if (strDelimChars.IsEmpty())
+ strDelimChars.AssignLiteral(";");
+
+ int32_t offset = 0 ;
+ int32_t FilePathsLen = strFilePaths.Length() ;
+ if (FilePathsLen)
+ {
+ nsAutoString Subject ;
+
+ // multiple files to be sent, delim specified
+ nsCOMPtr <nsIFile> pFile = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID, &rv) ;
+ if (NS_FAILED(rv) || (!pFile) ) return rv ;
+
+ char16_t * newFilePaths = (char16_t *) strFilePaths.get() ;
+ while (offset != kNotFound)
+ {
+ //Temp Directory
+ nsCOMPtr <nsIFile> pTempDir;
+ NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(pTempDir));
+
+ // if not already existing, create another temp dir for mapi within Win temp dir
+ // this is windows only so we can do "\\"
+ pTempDir->AppendRelativePath (NS_LITERAL_STRING("moz_mapi"));
+ pTempDir->Exists(&bExist) ;
+ if (!bExist)
+ {
+ rv = pTempDir->Create(nsIFile::DIRECTORY_TYPE, 777) ;
+ if (NS_FAILED(rv)) return rv ;
+ }
+
+ nsString RemainingPaths ;
+ RemainingPaths.Assign(newFilePaths) ;
+ offset = RemainingPaths.Find (strDelimChars) ;
+ if (offset != kNotFound)
+ {
+ RemainingPaths.SetLength (offset) ;
+ if ((offset + (int32_t)strDelimChars.Length()) < FilePathsLen)
+ newFilePaths += offset + strDelimChars.Length() ;
+ else
+ offset = kNotFound;
+ FilePathsLen -= offset + strDelimChars.Length();
+ }
+
+ if (RemainingPaths[1] != ':' && RemainingPaths[1] != '\\')
+ {
+ char cwd[MAX_PATH];
+ if (_getdcwd(_getdrive(), cwd, MAX_PATH))
+ {
+ nsAutoString cwdStr;
+ CopyASCIItoUTF16(cwd, cwdStr);
+ cwdStr.Append('\\');
+ RemainingPaths.Insert(cwdStr, 0);
+ }
+ }
+
+ pFile->InitWithPath (RemainingPaths) ;
+
+ rv = pFile->Exists(&bExist) ;
+ if (NS_FAILED(rv) || (!bExist) ) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST ;
+
+ // filename of the file attachment
+ nsAutoString leafName ;
+ pFile->GetLeafName (leafName) ;
+ if(NS_FAILED(rv) || leafName.IsEmpty()) return rv ;
+
+ if (!Subject.IsEmpty())
+ Subject.AppendLiteral(", ");
+ Subject += leafName;
+
+ // create MsgCompose attachment object
+ nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsDependentString fileNameNative(leafName.get());
+ rv = pFile->CopyTo(pTempDir, fileNameNative);
+ if (NS_FAILED(rv)) return rv;
+
+ // now turn pTempDir into a full file path to the temp file
+ pTempDir->Append(fileNameNative);
+
+ // this one is a temp file so set the flag for MsgCompose
+ attachment->SetTemporary(true);
+
+ // now set the attachment object
+ nsAutoCString pURL;
+ NS_GetURLSpecFromFile(pTempDir, pURL);
+ attachment->SetUrl(pURL);
+
+ // set the file size
+ int64_t fileSize;
+ pFile->GetFileSize(&fileSize);
+ attachment->SetSize(fileSize);
+
+ // add the attachment
+ rv = aCompFields->AddAttachment (attachment);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = aCompFields->SetBody(Subject) ;
+ }
+
+ return rv ;
+}
+
+// this used for Send with UI
+nsresult nsMapiHook::ShowComposerWindow (unsigned long aSession, nsIMsgCompFields * aCompFields)
+{
+ nsresult rv = NS_OK ;
+
+ // create a send listener to get back the send status
+ nsCOMPtr <nsIMsgSendListener> sendListener ;
+ rv = nsMAPISendListener::CreateMAPISendListener(getter_AddRefs(sendListener)) ;
+ if (NS_FAILED(rv) || (!sendListener) ) return rv ;
+
+ // create the compose params object
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv));
+ if (NS_FAILED(rv) || (!pMsgComposeParams) ) return rv ;
+
+ // If we found HTML, compose in HTML.
+ bool forcePlainText;
+ aCompFields->GetForcePlainText(&forcePlainText);
+ pMsgComposeParams->SetFormat(forcePlainText ? nsIMsgCompFormat::Default : nsIMsgCompFormat::HTML);
+
+ // populate the compose params
+ pMsgComposeParams->SetType(nsIMsgCompType::New);
+
+ // Never force to plain text, the default format will take care of that.
+ // Undo the forcing that happened in PopulateCompFields/PopulateCompFieldsWithConversion.
+ // See bug 1095629 and bug 1366196.
+ aCompFields->SetForcePlainText(false);
+ pMsgComposeParams->SetComposeFields(aCompFields);
+ pMsgComposeParams->SetSendListener(sendListener);
+
+ /** get the nsIMsgComposeService object to open the compose window **/
+ nsCOMPtr <nsIMsgComposeService> compService = do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID) ;
+ if (NS_FAILED(rv)|| (!compService) ) return rv ;
+
+ rv = compService->OpenComposeWindowWithParams(nullptr, pMsgComposeParams) ;
+ if (NS_FAILED(rv)) return rv ;
+
+ return rv ;
+}
diff --git a/mailnews/mapi/mapihook/src/msgMapiHook.h b/mailnews/mapi/mapihook/src/msgMapiHook.h
new file mode 100644
index 000000000..296aef459
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/msgMapiHook.h
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MSG_MAPI_HOOK_H_
+#define MSG_MAPI_HOOK_H_
+
+#include "prtypes.h"
+
+class nsMapiHook
+{
+ public :
+
+ static bool DisplayLoginDialog(bool aLogin, char16_t **aUsername,
+ char16_t **aPassword);
+ static bool VerifyUserName(const nsString& aUsername, nsCString& aIdKey);
+
+ static bool IsBlindSendAllowed () ;
+ static nsresult BlindSendMail (unsigned long aSession, nsIMsgCompFields * aCompFields) ;
+ static nsresult ShowComposerWindow (unsigned long aSession, nsIMsgCompFields * aCompFields) ;
+ static nsresult PopulateCompFieldsWithConversion(lpnsMapiMessage aMessage,
+ nsIMsgCompFields * aCompFields) ;
+ static nsresult PopulateCompFieldsForSendDocs(nsIMsgCompFields * aCompFields,
+ ULONG aFlags, LPTSTR aDelimChar, LPTSTR aFilePaths) ;
+ static nsresult HandleAttachments (nsIMsgCompFields * aCompFields, int32_t aFileCount,
+ lpnsMapiFileDesc aFiles) ;
+ static void CleanUp();
+
+ static bool isMapiService;
+};
+
+#endif // MSG_MAPI_HOOK_H_
diff --git a/mailnews/mapi/mapihook/src/msgMapiImp.cpp b/mailnews/mapi/mapihook/src/msgMapiImp.cpp
new file mode 100644
index 000000000..39193701c
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/msgMapiImp.cpp
@@ -0,0 +1,884 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <mapidefs.h>
+#include <mapi.h>
+#include "msgMapi.h"
+#include "msgMapiImp.h"
+#include "msgMapiFactory.h"
+#include "msgMapiMain.h"
+
+#include "nsIMsgCompFields.h"
+#include "msgMapiHook.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsMsgCompCID.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgHdr.h"
+#include "MailNewsTypes.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgImapMailFolder.h"
+#include <time.h>
+#include "nsIInputStream.h"
+#include "nsILineInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsNetCID.h"
+#include "nsMsgMessageFlags.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla::mailnews;
+
+PRLogModuleInfo *MAPI;
+
+CMapiImp::CMapiImp()
+: m_cRef(1)
+{
+ m_Lock = PR_NewLock();
+ if (!MAPI)
+ MAPI = PR_NewLogModule("MAPI");
+}
+
+CMapiImp::~CMapiImp()
+{
+ if (m_Lock)
+ PR_DestroyLock(m_Lock);
+}
+
+STDMETHODIMP CMapiImp::QueryInterface(const IID& aIid, void** aPpv)
+{
+ if (aIid == IID_IUnknown)
+ {
+ *aPpv = static_cast<nsIMapi*>(this);
+ }
+ else if (aIid == IID_nsIMapi)
+ {
+ *aPpv = static_cast<nsIMapi*>(this);
+ }
+ else
+ {
+ *aPpv = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ reinterpret_cast<IUnknown*>(*aPpv)->AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) CMapiImp::AddRef()
+{
+ return ++m_cRef;
+}
+
+STDMETHODIMP_(ULONG) CMapiImp::Release()
+{
+ int32_t temp = --m_cRef;
+ if (m_cRef == 0)
+ {
+ delete this;
+ return 0;
+ }
+
+ return temp;
+}
+
+STDMETHODIMP CMapiImp::IsValid()
+{
+ return S_OK;
+}
+
+STDMETHODIMP CMapiImp::IsValidSession(unsigned long aSession)
+{
+ nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
+ if (pConfig && pConfig->IsSessionValid(aSession))
+ return S_OK;
+
+ return E_FAIL;
+}
+
+STDMETHODIMP CMapiImp::Initialize()
+{
+ HRESULT hr = E_FAIL;
+
+ if (!m_Lock)
+ return E_FAIL;
+
+ PR_Lock(m_Lock);
+
+ // Initialize MAPI Configuration
+
+ nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
+ if (pConfig != nullptr)
+ hr = S_OK;
+
+ PR_Unlock(m_Lock);
+
+ return hr;
+}
+
+STDMETHODIMP CMapiImp::Login(unsigned long aUIArg, LOGIN_PW_TYPE aLogin, LOGIN_PW_TYPE aPassWord,
+ unsigned long aFlags, unsigned long *aSessionId)
+{
+ HRESULT hr = E_FAIL;
+ bool bNewSession = false;
+ nsCString id_key;
+
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::Login using flags %d\n", aFlags));
+ if (aFlags & MAPI_NEW_SESSION)
+ bNewSession = true;
+
+ // Check For Profile Name
+ if (aLogin != nullptr && aLogin[0] != '\0')
+ {
+ if (!nsMapiHook::VerifyUserName(nsString(aLogin), id_key))
+ {
+ *aSessionId = MAPI_E_LOGIN_FAILURE;
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::Login failed for username %s\n", aLogin));
+ NS_ASSERTION(false, "failed verifying user name");
+ return hr;
+ }
+ }
+ else
+ {
+ // get default account
+ nsresult rv;
+ nsCOMPtr <nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,MAPI_E_LOGIN_FAILURE);
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = accountManager->GetDefaultAccount(getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv,MAPI_E_LOGIN_FAILURE);
+ if (!account)
+ return MAPI_E_LOGIN_FAILURE;
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = account->GetDefaultIdentity(getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv,MAPI_E_LOGIN_FAILURE);
+ if (!identity)
+ return MAPI_E_LOGIN_FAILURE;
+ identity->GetKey(id_key);
+ }
+
+ // finally register(create) the session.
+ uint32_t nSession_Id;
+ int16_t nResult = 0;
+
+ nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
+ if (pConfig != nullptr)
+ nResult = pConfig->RegisterSession(aUIArg, wwc(aLogin), wwc(aPassWord),
+ (aFlags & MAPI_FORCE_DOWNLOAD), bNewSession,
+ &nSession_Id, id_key.get());
+ switch (nResult)
+ {
+ case -1 :
+ {
+ *aSessionId = MAPI_E_TOO_MANY_SESSIONS;
+ return hr;
+ }
+ case 0 :
+ {
+ *aSessionId = MAPI_E_INSUFFICIENT_MEMORY;
+ return hr;
+ }
+ default :
+ {
+ *aSessionId = nSession_Id;
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::Login succeeded\n"));
+ break;
+ }
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP CMapiImp::SendMail( unsigned long aSession, lpnsMapiMessage aMessage,
+ unsigned long aFlags, unsigned long aReserved)
+{
+ nsresult rv = NS_OK ;
+
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::SendMail using flags %d\n", aFlags));
+
+ // Handle possible nullptr argument.
+ nsMapiMessage Message;
+ memset(&Message, 0, sizeof(nsMapiMessage));
+
+ if (!aMessage)
+ {
+ aMessage = &Message;
+ }
+
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::SendMail flags=%x subject: %s sender: %s\n",
+ aFlags, (char *) aMessage->lpszSubject, (aMessage->lpOriginator) ? aMessage->lpOriginator->lpszAddress : ""));
+
+ /** create nsIMsgCompFields obj and populate it **/
+ nsCOMPtr<nsIMsgCompFields> pCompFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv) ;
+ if (NS_FAILED(rv) || (!pCompFields) ) return MAPI_E_INSUFFICIENT_MEMORY ;
+
+ rv = nsMapiHook::PopulateCompFieldsWithConversion(aMessage, pCompFields);
+
+ if (NS_SUCCEEDED (rv))
+ {
+ // see flag to see if UI needs to be brought up
+ if (!(aFlags & MAPI_DIALOG))
+ {
+ rv = nsMapiHook::BlindSendMail(aSession, pCompFields);
+ }
+ else
+ {
+ rv = nsMapiHook::ShowComposerWindow(aSession, pCompFields);
+ }
+ }
+
+ return nsMAPIConfiguration::GetMAPIErrorFromNSError (rv) ;
+}
+
+
+STDMETHODIMP CMapiImp::SendDocuments( unsigned long aSession, LPTSTR aDelimChar,
+ LPTSTR aFilePaths, LPTSTR aFileNames, ULONG aFlags)
+{
+ nsresult rv = NS_OK ;
+
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::SendDocument using flags %d\n", aFlags));
+ /** create nsIMsgCompFields obj and populate it **/
+ nsCOMPtr<nsIMsgCompFields> pCompFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv) ;
+ if (NS_FAILED(rv) || (!pCompFields) ) return MAPI_E_INSUFFICIENT_MEMORY ;
+
+ if (aFilePaths)
+ {
+ rv = nsMapiHook::PopulateCompFieldsForSendDocs(pCompFields, aFlags, aDelimChar, aFilePaths) ;
+ }
+
+ if (NS_SUCCEEDED (rv))
+ rv = nsMapiHook::ShowComposerWindow(aSession, pCompFields);
+ else
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::SendDocument error rv = %lx, paths = %s names = %s\n", rv, aFilePaths, aFileNames));
+
+ return nsMAPIConfiguration::GetMAPIErrorFromNSError (rv) ;
+}
+
+nsresult CMapiImp::GetDefaultInbox(nsIMsgFolder **inboxFolder)
+{
+ // get default account
+ nsresult rv;
+ nsCOMPtr <nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgAccount> account;
+ rv = accountManager->GetDefaultAccount(getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!account)
+ return NS_ERROR_FAILURE;
+
+ // get incoming server
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = account->GetIncomingServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString type;
+ rv = server->GetType(type);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // we only care about imap and pop3
+ if (type.EqualsLiteral("imap") || type.EqualsLiteral("pop3"))
+ {
+ // imap and pop3 account should have an Inbox
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!rootMsgFolder)
+ return NS_ERROR_FAILURE;
+
+ rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, inboxFolder);
+ if (!*inboxFolder)
+ return NS_ERROR_FAILURE;
+
+ }
+ return NS_OK;
+}
+
+//*****************************************************************************
+// Encapsulate the XP DB stuff required to enumerate messages
+
+class MsgMapiListContext
+{
+public:
+ MsgMapiListContext () {}
+ ~MsgMapiListContext ();
+
+ nsresult OpenDatabase (nsIMsgFolder *folder);
+
+ nsMsgKey GetNext ();
+ nsresult MarkRead (nsMsgKey key, bool read);
+
+ lpnsMapiMessage GetMessage (nsMsgKey, unsigned long flFlags);
+ bool IsIMAPHost(void);
+ bool DeleteMessage(nsMsgKey key);
+
+protected:
+
+ char *ConvertDateToMapiFormat (time_t);
+ char *ConvertBodyToMapiFormat (nsIMsgDBHdr *hdr);
+ void ConvertRecipientsToMapiFormat(const nsCOMArray<msgIAddressObject> &ourRecips,
+ lpnsMapiRecipDesc mapiRecips,
+ int mapiRecipClass);
+
+ nsCOMPtr <nsIMsgFolder> m_folder;
+ nsCOMPtr <nsIMsgDatabase> m_db;
+ nsCOMPtr <nsISimpleEnumerator> m_msgEnumerator;
+};
+
+
+LONG CMapiImp::InitContext(unsigned long session, MsgMapiListContext **listContext)
+{
+ nsMAPIConfiguration * pMapiConfig = nsMAPIConfiguration::GetMAPIConfiguration() ;
+ if (!pMapiConfig)
+ return MAPI_E_FAILURE ; // get the singelton obj
+ *listContext = (MsgMapiListContext *) pMapiConfig->GetMapiListContext(session);
+ // This is the first message
+ if (!*listContext)
+ {
+ nsCOMPtr <nsIMsgFolder> inboxFolder;
+ nsresult rv = GetDefaultInbox(getter_AddRefs(inboxFolder));
+ if (NS_FAILED(rv))
+ {
+ NS_ASSERTION(false, "in init context, no inbox");
+ return(MAPI_E_NO_MESSAGES);
+ }
+
+ *listContext = new MsgMapiListContext;
+ if (!*listContext)
+ return MAPI_E_INSUFFICIENT_MEMORY;
+
+ rv = (*listContext)->OpenDatabase(inboxFolder);
+ if (NS_FAILED(rv))
+ {
+ pMapiConfig->SetMapiListContext(session, NULL);
+ delete *listContext;
+ NS_ASSERTION(false, "in init context, unable to open db");
+ return MAPI_E_NO_MESSAGES;
+ }
+ else
+ pMapiConfig->SetMapiListContext(session, *listContext);
+ }
+ return SUCCESS_SUCCESS;
+}
+
+STDMETHODIMP CMapiImp::FindNext(unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageType,
+ LPTSTR lpszSeedMessageID, unsigned long flFlags, unsigned long ulReserved,
+ unsigned char lpszMessageID[64])
+
+{
+ //
+ // If this is true, then this is the first call to this FindNext function
+ // and we should start the enumeration operation.
+ //
+
+ *lpszMessageID = '\0';
+ nsMAPIConfiguration * pMapiConfig = nsMAPIConfiguration::GetMAPIConfiguration() ;
+ if (!pMapiConfig)
+ {
+ NS_ASSERTION(false, "failed to get config in findnext");
+ return MAPI_E_FAILURE ; // get the singelton obj
+ }
+ MsgMapiListContext *listContext;
+ LONG ret = InitContext(aSession, &listContext);
+ if (ret != SUCCESS_SUCCESS)
+ {
+ NS_ASSERTION(false, "init context failed");
+ return ret;
+ }
+ NS_ASSERTION(listContext, "initContext returned null context");
+ if (listContext)
+ {
+// NS_ASSERTION(false, "find next init context succeeded");
+ nsMsgKey nextKey = listContext->GetNext();
+ if (nextKey == nsMsgKey_None)
+ {
+ pMapiConfig->SetMapiListContext(aSession, NULL);
+ delete listContext;
+ return(MAPI_E_NO_MESSAGES);
+ }
+
+// TRACE("MAPI: ProcessMAPIFindNext() Found message id = %d\n", nextKey);
+
+ sprintf((char *) lpszMessageID, "%d", nextKey);
+ }
+
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::FindNext returning key %s\n", (char *) lpszMessageID));
+ return(SUCCESS_SUCCESS);
+}
+
+STDMETHODIMP CMapiImp::ReadMail(unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageID,
+ unsigned long flFlags, unsigned long ulReserved, lpnsMapiMessage *lppMessage)
+{
+ nsresult irv;
+ nsAutoCString keyString((char *) lpszMessageID);
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::ReadMail asking for key %s\n", (char *) lpszMessageID));
+ nsMsgKey msgKey = keyString.ToInteger(&irv);
+ if (NS_FAILED(irv))
+ {
+ NS_ASSERTION(false, "invalid lpszMessageID");
+ return MAPI_E_INVALID_MESSAGE;
+ }
+ MsgMapiListContext *listContext;
+ LONG ret = InitContext(aSession, &listContext);
+ if (ret != SUCCESS_SUCCESS)
+ {
+ NS_ASSERTION(false, "init context failed in ReadMail");
+ return ret;
+ }
+ *lppMessage = listContext->GetMessage (msgKey, flFlags);
+ NS_ASSERTION(*lppMessage, "get message failed");
+
+ return (*lppMessage) ? SUCCESS_SUCCESS : E_FAIL;
+}
+
+
+STDMETHODIMP CMapiImp::DeleteMail(unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageID,
+ unsigned long flFlags, unsigned long ulReserved)
+{
+ nsresult irv;
+ nsAutoCString keyString((char *) lpszMessageID);
+ nsMsgKey msgKey = keyString.ToInteger(&irv);
+ // XXX Why do we return success on failure?
+ if (NS_FAILED(irv))
+ return SUCCESS_SUCCESS;
+ MsgMapiListContext *listContext;
+ LONG ret = InitContext(aSession, &listContext);
+ if (ret != SUCCESS_SUCCESS)
+ return ret;
+ return (listContext->DeleteMessage(msgKey)) ? SUCCESS_SUCCESS : MAPI_E_INVALID_MESSAGE;
+}
+
+STDMETHODIMP CMapiImp::SaveMail(unsigned long aSession, unsigned long ulUIParam, lpnsMapiMessage lppMessage,
+ unsigned long flFlags, unsigned long ulReserved, LPTSTR lpszMessageID)
+{
+ MsgMapiListContext *listContext;
+ LONG ret = InitContext(aSession, &listContext);
+ if (ret != SUCCESS_SUCCESS)
+ return ret;
+ return S_OK;
+}
+
+
+STDMETHODIMP CMapiImp::Logoff (unsigned long aSession)
+{
+ nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
+
+ if (pConfig->UnRegisterSession((uint32_t)aSession))
+ return S_OK;
+
+ return E_FAIL;
+}
+
+STDMETHODIMP CMapiImp::CleanUp()
+{
+ nsMapiHook::CleanUp();
+ return S_OK;
+}
+
+
+#define MAX_NAME_LEN 256
+
+
+MsgMapiListContext::~MsgMapiListContext ()
+{
+ if (m_db)
+ m_db->Close(false);
+}
+
+
+nsresult MsgMapiListContext::OpenDatabase (nsIMsgFolder *folder)
+{
+ nsresult dbErr = NS_ERROR_FAILURE;
+ if (folder)
+ {
+ m_folder = folder;
+ dbErr = folder->GetMsgDatabase(getter_AddRefs(m_db));
+ if (m_db)
+ dbErr = m_db->EnumerateMessages(getter_AddRefs(m_msgEnumerator));
+ }
+ return dbErr;
+}
+
+bool
+MsgMapiListContext::IsIMAPHost(void)
+{
+ if (!m_folder)
+ return FALSE;
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder);
+
+ return imapFolder != nullptr;
+}
+
+nsMsgKey MsgMapiListContext::GetNext ()
+{
+ nsMsgKey key = nsMsgKey_None;
+ bool keepTrying = TRUE;
+
+// NS_ASSERTION (m_msgEnumerator && m_db, "need enumerator and db");
+ if (m_msgEnumerator && m_db)
+ {
+
+ do
+ {
+ keepTrying = FALSE;
+ nsCOMPtr <nsISupports> hdrISupports;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ if (NS_SUCCEEDED(m_msgEnumerator->GetNext(getter_AddRefs(hdrISupports))) && hdrISupports)
+ {
+ msgHdr = do_QueryInterface(hdrISupports);
+ msgHdr->GetMessageKey(&key);
+
+ // Check here for IMAP message...if not, just return...
+ if (!IsIMAPHost())
+ return key;
+
+ // If this is an IMAP message, we have to make sure we have a valid
+ // body to work with.
+ uint32_t flags = 0;
+
+ (void) msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Offline)
+ return key;
+
+ // Ok, if we get here, we have an IMAP message without a body!
+ // We need to keep trying by calling the GetNext member recursively...
+ keepTrying = TRUE;
+ }
+ } while (keepTrying);
+ }
+
+ return key;
+}
+
+
+nsresult MsgMapiListContext::MarkRead (nsMsgKey key, bool read)
+{
+ nsresult err = NS_ERROR_FAILURE;
+ NS_ASSERTION(m_db, "no db");
+ if (m_db)
+ err = m_db->MarkRead (key, read, nullptr);
+ return err;
+}
+
+
+lpnsMapiMessage MsgMapiListContext::GetMessage (nsMsgKey key, unsigned long flFlags)
+{
+ lpnsMapiMessage message = (lpnsMapiMessage) CoTaskMemAlloc (sizeof(nsMapiMessage));
+ memset(message, 0, sizeof(nsMapiMessage));
+ if (message)
+ {
+ nsCString subject;
+ nsCString author;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = m_db->GetMsgHdrForKey (key, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ msgHdr->GetSubject (getter_Copies(subject));
+ message->lpszSubject = (char *) CoTaskMemAlloc(subject.Length() + 1);
+ strcpy((char *) message->lpszSubject, subject.get());
+ uint32_t date;
+ (void) msgHdr->GetDateInSeconds(&date);
+ message->lpszDateReceived = ConvertDateToMapiFormat (date);
+
+ // Pull out the flags info
+ // anything to do with MAPI_SENT? Since we're only reading the Inbox, I guess not
+ uint32_t ourFlags;
+ (void) msgHdr->GetFlags(&ourFlags);
+ if (!(ourFlags & nsMsgMessageFlags::Read))
+ message->flFlags |= MAPI_UNREAD;
+ if (ourFlags & (nsMsgMessageFlags::MDNReportNeeded | nsMsgMessageFlags::MDNReportSent))
+ message->flFlags |= MAPI_RECEIPT_REQUESTED;
+
+ // Pull out the author/originator info
+ message->lpOriginator = (lpnsMapiRecipDesc) CoTaskMemAlloc (sizeof(nsMapiRecipDesc));
+ memset(message->lpOriginator, 0, sizeof(nsMapiRecipDesc));
+ if (message->lpOriginator)
+ {
+ msgHdr->GetAuthor (getter_Copies(author));
+ ConvertRecipientsToMapiFormat(EncodedHeader(author),
+ message->lpOriginator, MAPI_ORIG);
+ }
+ // Pull out the To/CC info
+ nsCString recipients, ccList;
+ msgHdr->GetRecipients(getter_Copies(recipients));
+ msgHdr->GetCcList(getter_Copies(ccList));
+
+ nsCOMArray<msgIAddressObject> parsedToRecips = EncodedHeader(recipients);
+ nsCOMArray<msgIAddressObject> parsedCCRecips = EncodedHeader(ccList);
+ uint32_t numToRecips = parsedToRecips.Length();
+ uint32_t numCCRecips = parsedCCRecips.Length();
+
+ message->lpRecips = (lpnsMapiRecipDesc) CoTaskMemAlloc ((numToRecips + numCCRecips) * sizeof(MapiRecipDesc));
+ memset(message->lpRecips, 0, (numToRecips + numCCRecips) * sizeof(MapiRecipDesc));
+ if (message->lpRecips)
+ {
+ ConvertRecipientsToMapiFormat(parsedToRecips, message->lpRecips,
+ MAPI_TO);
+ ConvertRecipientsToMapiFormat(parsedCCRecips,
+ &message->lpRecips[numToRecips], MAPI_CC);
+ }
+
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("MsgMapiListContext::GetMessage flags=%x subject %s date %s sender %s\n",
+ flFlags, (char *) message->lpszSubject,(char *) message->lpszDateReceived, author.get()) );
+
+ // Convert any body text that we have locally
+ if (!(flFlags & MAPI_ENVELOPE_ONLY))
+ message->lpszNoteText = (char *) ConvertBodyToMapiFormat (msgHdr);
+
+ }
+ if (! (flFlags & (MAPI_PEEK | MAPI_ENVELOPE_ONLY)))
+ m_db->MarkRead(key, true, nullptr);
+ }
+ return message;
+}
+
+
+char *MsgMapiListContext::ConvertDateToMapiFormat (time_t ourTime)
+{
+ char *date = (char*) CoTaskMemAlloc(32);
+ if (date)
+ {
+ // MAPI time format is YYYY/MM/DD HH:MM
+ // Note that we're not using XP_StrfTime because that localizes the time format,
+ // and the way I read the MAPI spec is that their format is canonical, not localized.
+ struct tm *local = localtime (&ourTime);
+ if (local)
+ strftime (date, 32, "%Y/%m/%d %I:%M", local); //use %H if hours should be 24 hour format
+ }
+ return date;
+}
+
+
+void MsgMapiListContext::ConvertRecipientsToMapiFormat(
+ const nsCOMArray<msgIAddressObject> &recipients,
+ lpnsMapiRecipDesc mapiRecips, int mapiRecipClass)
+{
+ nsTArray<nsCString> names, addresses;
+ ExtractAllAddresses(recipients, UTF16ArrayAdapter<>(names),
+ UTF16ArrayAdapter<>(addresses));
+
+ size_t numAddresses = names.Length();
+ for (size_t i = 0; i < numAddresses; i++)
+ {
+ if (!names[i].IsEmpty())
+ {
+ mapiRecips[i].lpszName = (char *) CoTaskMemAlloc(names[i].Length() + 1);
+ if (mapiRecips[i].lpszName)
+ strcpy((char *)mapiRecips[i].lpszName, names[i].get());
+ }
+ if (!addresses[i].IsEmpty())
+ {
+ mapiRecips[i].lpszName = (char *) CoTaskMemAlloc(addresses[i].Length() + 1);
+ if (mapiRecips[i].lpszName)
+ strcpy((char *)mapiRecips[i].lpszName, addresses[i].get());
+ }
+ mapiRecips[i].ulRecipClass = mapiRecipClass;
+ }
+}
+
+
+char *MsgMapiListContext::ConvertBodyToMapiFormat (nsIMsgDBHdr *hdr)
+{
+ const int kBufLen = 64000; // I guess we only return the first 64K of a message.
+#define EMPTY_MESSAGE_LINE(buf) (buf[0] == '\r' || buf[0] == '\n' || buf[0] == '\0')
+
+ nsCOMPtr <nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ if (!folder)
+ return nullptr;
+
+ nsCOMPtr <nsIInputStream> inputStream;
+ nsCOMPtr <nsIFile> localFile;
+ folder->GetFilePath(getter_AddRefs(localFile));
+
+ nsresult rv;
+ nsCOMPtr<nsIFileInputStream> fileStream = do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ rv = fileStream->Init(localFile, PR_RDONLY, 0664, false); //just have to read the messages
+ inputStream = do_QueryInterface(fileStream);
+
+ if (inputStream)
+ {
+ nsCOMPtr <nsILineInputStream> fileLineStream = do_QueryInterface(inputStream);
+ if (!fileLineStream)
+ return nullptr;
+ // ### really want to skip past headers...
+ uint64_t messageOffset;
+ uint32_t lineCount;
+ hdr->GetMessageOffset(&messageOffset);
+ hdr->GetLineCount(&lineCount);
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(inputStream);
+ seekableStream->Seek(PR_SEEK_SET, messageOffset);
+ bool hasMore = true;
+ nsAutoCString curLine;
+ nsresult rv = NS_OK;
+ while (hasMore) // advance past message headers
+ {
+ nsresult rv = fileLineStream->ReadLine(curLine, &hasMore);
+ if (NS_FAILED(rv) || EMPTY_MESSAGE_LINE(curLine))
+ break;
+ }
+ uint32_t msgSize;
+ hdr->GetMessageSize(&msgSize);
+ if (msgSize > kBufLen)
+ msgSize = kBufLen - 1;
+ // this is too big, since it includes the msg hdr size...oh well
+ char *body = (char*) CoTaskMemAlloc (msgSize + 1);
+
+ if (!body)
+ return nullptr;
+ int32_t bytesCopied = 0;
+ for (hasMore = TRUE; lineCount > 0 && hasMore && NS_SUCCEEDED(rv); lineCount--)
+ {
+ rv = fileLineStream->ReadLine(curLine, &hasMore);
+ if (NS_FAILED(rv))
+ break;
+ curLine.Append(CRLF);
+ // make sure we have room left
+ if (bytesCopied + curLine.Length() < msgSize)
+ {
+ strcpy(body + bytesCopied, curLine.get());
+ bytesCopied += curLine.Length();
+ }
+ }
+ MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("ConvertBodyToMapiFormat size=%x allocated size %x body = %100.100s\n",
+ bytesCopied, msgSize + 1, (char *) body) );
+ body[bytesCopied] = '\0'; // rhp - fix last line garbage...
+ return body;
+ }
+ return nullptr;
+}
+
+
+//*****************************************************************************
+// MSGMAPI API implementation
+
+
+
+static void msg_FreeMAPIFile(lpMapiFileDesc f)
+{
+ if (f)
+ {
+ CoTaskMemFree(f->lpszPathName);
+ CoTaskMemFree(f->lpszFileName);
+ }
+}
+
+static void msg_FreeMAPIRecipient(lpMapiRecipDesc rd)
+{
+ if (rd)
+ {
+ if (rd->lpszName)
+ CoTaskMemFree(rd->lpszName);
+ if (rd->lpszAddress)
+ CoTaskMemFree(rd->lpszAddress);
+ // CoTaskMemFree(rd->lpEntryID);
+ }
+}
+
+extern "C" void MSG_FreeMapiMessage (lpMapiMessage msg)
+{
+ ULONG i;
+
+ if (msg)
+ {
+ CoTaskMemFree(msg->lpszSubject);
+ CoTaskMemFree(msg->lpszNoteText);
+ CoTaskMemFree(msg->lpszMessageType);
+ CoTaskMemFree(msg->lpszDateReceived);
+ CoTaskMemFree(msg->lpszConversationID);
+
+ if (msg->lpOriginator)
+ msg_FreeMAPIRecipient(msg->lpOriginator);
+
+ for (i=0; i<msg->nRecipCount; i++)
+ if (&(msg->lpRecips[i]) != nullptr)
+ msg_FreeMAPIRecipient(&(msg->lpRecips[i]));
+
+ CoTaskMemFree(msg->lpRecips);
+
+ for (i=0; i<msg->nFileCount; i++)
+ if (&(msg->lpFiles[i]) != nullptr)
+ msg_FreeMAPIFile(&(msg->lpFiles[i]));
+
+ CoTaskMemFree(msg->lpFiles);
+
+ CoTaskMemFree(msg);
+ }
+}
+
+
+extern "C" bool MsgMarkMapiMessageRead (nsIMsgFolder *folder, nsMsgKey key, bool read)
+{
+ bool success = FALSE;
+ MsgMapiListContext *context = new MsgMapiListContext();
+ if (context)
+ {
+ if (NS_SUCCEEDED(context->OpenDatabase(folder)))
+ {
+ if (NS_SUCCEEDED(context->MarkRead (key, read)))
+ success = TRUE;
+ }
+ delete context;
+ }
+ return success;
+}
+
+bool
+MsgMapiListContext::DeleteMessage(nsMsgKey key)
+{
+ if (!m_db)
+ return FALSE;
+
+ if ( !IsIMAPHost() )
+ {
+ return NS_SUCCEEDED((m_db->DeleteMessages(1, &key, nullptr)));
+ }
+#if 0
+ else if ( m_folder->GetIMAPFolderInfoMail() )
+ {
+ AutoTArray<nsMsgKey, 1> messageKeys;
+ messageKeys.AppendElement(key);
+
+ (m_folder->GetIMAPFolderInfoMail())->DeleteSpecifiedMessages(pane, messageKeys, nsMsgKey_None);
+ m_db->DeleteMessage(key, nullptr, FALSE);
+ return TRUE;
+ }
+#endif
+ else
+ {
+ return FALSE;
+ }
+}
+
+/* Return TRUE on success, FALSE on failure */
+extern "C" bool MSG_DeleteMapiMessage(nsIMsgFolder *folder, nsMsgKey key)
+{
+ bool success = FALSE;
+ MsgMapiListContext *context = new MsgMapiListContext();
+ if (context)
+ {
+ if (NS_SUCCEEDED(context->OpenDatabase(folder)))
+ {
+ success = context->DeleteMessage(key);
+ }
+
+ delete context;
+ }
+
+ return success;
+}
+
diff --git a/mailnews/mapi/mapihook/src/msgMapiImp.h b/mailnews/mapi/mapihook/src/msgMapiImp.h
new file mode 100644
index 000000000..562ab6cad
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/msgMapiImp.h
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MSG_MAPI_IMP_H
+#define MSG_MAPI_IMP_H
+
+#include <windows.h>
+#include <mapi.h>
+#include "msgMapi.h"
+#include "nspr.h"
+#include "nscore.h"
+#include "nsISupportsImpl.h" // ThreadSafeAutoRefCnt
+
+class nsIMsgFolder;
+class MsgMapiListContext;
+
+const CLSID CLSID_CMapiImp = {0x29f458be, 0x8866, 0x11d5, {0xa3, 0xdd, 0x0, 0xb0, 0xd0, 0xf3, 0xba, 0xa7}};
+
+// this class implements the MS COM interface nsIMapi that provides the methods
+// called by mapi32.dll to perform the mail operations as specified by MAPI.
+// These class methods in turn use the Mozilla Mail XPCOM interfaces to do so.
+class CMapiImp : public nsIMapi
+{
+
+public :
+
+ // IUnknown
+
+ STDMETHODIMP QueryInterface(const IID& aIid, void** aPpv);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ // Interface INsMapi
+
+ STDMETHODIMP Login(unsigned long aUIArg, LOGIN_PW_TYPE aLogin,
+ LOGIN_PW_TYPE aPassWord, unsigned long aFlags,
+ unsigned long *aSessionId);
+
+ STDMETHODIMP SendMail( unsigned long aSession, lpnsMapiMessage aMessage,
+ unsigned long aFlags, unsigned long aReserved) ;
+
+ STDMETHODIMP SendDocuments( unsigned long aSession, LPTSTR aDelimChar,
+ LPTSTR aFilePaths, LPTSTR aFileNames, ULONG aFlags);
+
+ STDMETHODIMP FindNext( unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageType,
+ LPTSTR lpszSeedMessageID, unsigned long flFlags, unsigned long ulReserved,
+ unsigned char lpszMessageID[64] );
+
+ STDMETHODIMP ReadMail(unsigned long lhSession, unsigned long ulUIParam, LPTSTR lpszMessageID,
+ unsigned long flFlags, unsigned long ulReserved, lpnsMapiMessage *lppMessage);
+ STDMETHODIMP DeleteMail(unsigned long lhSession, unsigned long ulUIParam, LPTSTR lpszMessageID,
+ unsigned long flFlags, unsigned long ulReserved);
+ STDMETHODIMP SaveMail(unsigned long lhSession, unsigned long ulUIParam, lpnsMapiMessage lppMessage,
+ unsigned long flFlags, unsigned long ulReserved, LPTSTR lpszMessageID);
+
+ STDMETHODIMP Initialize();
+ STDMETHODIMP IsValid();
+ STDMETHODIMP IsValidSession(unsigned long aSession);
+
+ STDMETHODIMP Logoff (unsigned long aSession);
+ STDMETHODIMP CleanUp();
+
+ CMapiImp();
+ ~CMapiImp();
+
+ LONG InitContext(unsigned long session, MsgMapiListContext **listContext);
+ nsresult GetDefaultInbox(nsIMsgFolder **inboxFolder);
+
+private :
+ PRLock *m_Lock;
+ mozilla::ThreadSafeAutoRefCnt m_cRef;
+};
+
+#endif // MSG_MAPI_IMP_H
diff --git a/mailnews/mapi/mapihook/src/msgMapiMain.cpp b/mailnews/mapi/mapihook/src/msgMapiMain.cpp
new file mode 100644
index 000000000..adb5cc14c
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/msgMapiMain.cpp
@@ -0,0 +1,306 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <mapidefs.h>
+#include <mapi.h>
+
+#include "msgCore.h"
+#include "nsComposeStrings.h"
+#include "msgMapiMain.h"
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+
+nsMAPIConfiguration *nsMAPIConfiguration::m_pSelfRef = nullptr;
+uint32_t nsMAPIConfiguration::session_generator = 0;
+uint32_t nsMAPIConfiguration::sessionCount = 0;
+
+nsMAPIConfiguration *nsMAPIConfiguration::GetMAPIConfiguration()
+{
+ if (m_pSelfRef == nullptr)
+ m_pSelfRef = new nsMAPIConfiguration();
+
+ return m_pSelfRef;
+}
+
+nsMAPIConfiguration::nsMAPIConfiguration()
+: m_nMaxSessions(MAX_SESSIONS)
+{
+ m_Lock = PR_NewLock();
+}
+
+nsMAPIConfiguration::~nsMAPIConfiguration()
+{
+ if (m_Lock)
+ PR_DestroyLock(m_Lock);
+}
+
+void nsMAPIConfiguration::OpenConfiguration()
+{
+ // No. of max. sessions is set to MAX_SESSIONS. In future
+ // if it is decided to have configuration (registry)
+ // parameter, this function can be used to set the
+ // max sessions;
+
+ return;
+}
+
+int16_t nsMAPIConfiguration::RegisterSession(uint32_t aHwnd,
+ const char16_t *aUserName, const char16_t *aPassword,
+ bool aForceDownLoad, bool aNewSession,
+ uint32_t *aSession, const char *aIdKey)
+{
+ int16_t nResult = 0;
+ uint32_t n_SessionId = 0;
+
+ PR_Lock(m_Lock);
+
+ // Check whether max sessions is exceeded
+
+ if (sessionCount >= m_nMaxSessions)
+ {
+ PR_Unlock(m_Lock);
+ return -1;
+ }
+
+ if (aUserName != nullptr && aUserName[0] != '\0')
+ m_ProfileMap.Get(nsDependentString(aUserName), &n_SessionId);
+
+ // try to share a session; if not create a session
+ if (n_SessionId > 0)
+ {
+ nsMAPISession *pTemp = nullptr;
+ m_SessionMap.Get(n_SessionId, &pTemp);
+ if (pTemp != nullptr)
+ {
+ pTemp->IncrementSession();
+ *aSession = n_SessionId;
+ nResult = 1;
+ }
+ }
+ else if (aNewSession || n_SessionId == 0) // checking for n_SessionId is a concession
+ {
+ // create a new session; if new session is specified OR there is no session
+ nsMAPISession *pTemp = nullptr;
+ pTemp = new nsMAPISession(aHwnd, aUserName,
+ aPassword, aForceDownLoad, aIdKey);
+
+ if (pTemp != nullptr)
+ {
+ session_generator++;
+
+ // I don't think there will be (2 power 32) sessions alive
+ // in a cycle. This is an assumption
+
+ if (session_generator == 0)
+ session_generator++;
+ m_SessionMap.Put(session_generator, pTemp);
+ if (aUserName != nullptr && aUserName[0] != '\0')
+ m_ProfileMap.Put(nsDependentString(aUserName), session_generator);
+ *aSession = session_generator;
+ sessionCount++;
+ nResult = 1;
+ }
+ }
+
+ PR_Unlock(m_Lock);
+ return nResult;
+}
+
+bool nsMAPIConfiguration::UnRegisterSession(uint32_t aSessionID)
+{
+ bool bResult = false;
+
+ PR_Lock(m_Lock);
+
+ if (aSessionID != 0)
+ {
+ nsMAPISession *pTemp = nullptr;
+ m_SessionMap.Get(aSessionID, &pTemp);
+
+ if (pTemp != nullptr)
+ {
+ if (pTemp->DecrementSession() == 0)
+ {
+ if (pTemp->m_pProfileName.get() != nullptr)
+ m_ProfileMap.Remove(pTemp->m_pProfileName);
+ m_SessionMap.Remove(aSessionID);
+ sessionCount--;
+ bResult = true;
+ }
+ }
+ }
+
+ PR_Unlock(m_Lock);
+ return bResult;
+}
+
+bool nsMAPIConfiguration::IsSessionValid(uint32_t aSessionID)
+{
+ if (aSessionID == 0)
+ return false;
+ bool retValue = false;
+ PR_Lock(m_Lock);
+ retValue = m_SessionMap.Get(aSessionID, NULL);
+ PR_Unlock(m_Lock);
+ return retValue;
+}
+
+char16_t *nsMAPIConfiguration::GetPassword(uint32_t aSessionID)
+{
+ char16_t *pResult = nullptr;
+
+ PR_Lock(m_Lock);
+
+ if (aSessionID != 0)
+ {
+ nsMAPISession *pTemp = nullptr;
+ m_SessionMap.Get(aSessionID, &pTemp);
+
+ if (pTemp)
+ pResult = pTemp->GetPassword();
+ }
+ PR_Unlock(m_Lock);
+ return pResult;
+}
+
+void *nsMAPIConfiguration::GetMapiListContext(uint32_t aSessionID)
+{
+ void *pResult = nullptr;
+
+ PR_Lock(m_Lock);
+
+ if (aSessionID != 0)
+ {
+ nsMAPISession *pTemp = nullptr;
+ m_SessionMap.Get(aSessionID, &pTemp);
+ if (pTemp)
+ pResult = pTemp->GetMapiListContext();
+ }
+
+ PR_Unlock(m_Lock);
+ return pResult;
+}
+
+void nsMAPIConfiguration::SetMapiListContext(uint32_t aSessionID, void *mapiListContext)
+{
+ PR_Lock(m_Lock);
+
+ if (aSessionID != 0)
+ {
+ nsMAPISession *pTemp = nullptr;
+ m_SessionMap.Get(aSessionID, &pTemp);
+ if (pTemp)
+ pTemp->SetMapiListContext(mapiListContext);
+ }
+
+ PR_Unlock(m_Lock);
+}
+
+void nsMAPIConfiguration::GetIdKey(uint32_t aSessionID, nsCString& aKey)
+{
+ PR_Lock(m_Lock);
+ if (aSessionID != 0)
+ {
+ nsMAPISession *pTemp = nullptr;
+ m_SessionMap.Get(aSessionID, &pTemp);
+ if (pTemp)
+ pTemp->GetIdKey(aKey);
+ }
+ PR_Unlock(m_Lock);
+ return;
+}
+
+// util func
+HRESULT nsMAPIConfiguration::GetMAPIErrorFromNSError (nsresult res)
+{
+ HRESULT hr = SUCCESS_SUCCESS;
+
+ if (NS_SUCCEEDED (res)) return hr;
+
+ // if failure return the related MAPI failure code
+ switch (res)
+ {
+ case NS_MSG_NO_RECIPIENTS :
+ hr = MAPI_E_BAD_RECIPTYPE;
+ break;
+ case NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS :
+ hr = MAPI_E_INVALID_RECIPS;
+ break;
+ case NS_ERROR_SMTP_AUTH_FAILURE :
+ case NS_ERROR_SMTP_AUTH_GSSAPI :
+ case NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED :
+ case NS_ERROR_SMTP_AUTH_NOT_SUPPORTED :
+ case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL :
+ case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL :
+ case NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT :
+ hr = MAPI_E_LOGIN_FAILURE;
+ break;
+ case NS_MSG_UNABLE_TO_OPEN_FILE :
+ case NS_MSG_UNABLE_TO_OPEN_TMP_FILE :
+ case NS_MSG_COULDNT_OPEN_FCC_FOLDER :
+ case NS_ERROR_FILE_INVALID_PATH :
+ hr = MAPI_E_ATTACHMENT_OPEN_FAILURE;
+ break;
+ case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST :
+ hr = MAPI_E_ATTACHMENT_NOT_FOUND;
+ break;
+ case NS_MSG_CANCELLING :
+ hr = MAPI_E_USER_ABORT;
+ break;
+ case NS_MSG_ERROR_WRITING_FILE :
+ case NS_MSG_UNABLE_TO_SAVE_TEMPLATE :
+ case NS_MSG_UNABLE_TO_SAVE_DRAFT :
+ hr = MAPI_E_ATTACHMENT_WRITE_FAILURE;
+ break;
+ default:
+ hr = MAPI_E_FAILURE;
+ break;
+ }
+
+ return hr;
+}
+
+
+nsMAPISession::nsMAPISession(uint32_t aHwnd, const char16_t *aUserName,
+ const char16_t *aPassword,
+ bool aForceDownLoad, const char *aKey)
+: m_bIsForcedDownLoad(aForceDownLoad),
+ m_hAppHandle(aHwnd),
+ m_nShared(1),
+ m_pIdKey(aKey)
+{
+ m_listContext = NULL;
+ m_pProfileName.Assign(aUserName);
+ m_pPassword.Assign(aPassword);
+}
+
+nsMAPISession::~nsMAPISession()
+{
+}
+
+uint32_t nsMAPISession::IncrementSession()
+{
+ return ++m_nShared;
+}
+
+uint32_t nsMAPISession::DecrementSession()
+{
+ return --m_nShared;
+}
+
+uint32_t nsMAPISession::GetSessionCount()
+{
+ return m_nShared;
+}
+
+char16_t *nsMAPISession::GetPassword()
+{
+ return (char16_t *)m_pPassword.get();
+}
+
+void nsMAPISession::GetIdKey(nsCString& aKey)
+{
+ aKey = m_pIdKey;
+ return;
+}
diff --git a/mailnews/mapi/mapihook/src/msgMapiMain.h b/mailnews/mapi/mapihook/src/msgMapiMain.h
new file mode 100644
index 000000000..be74c5db1
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/msgMapiMain.h
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MSG_MAPI_MAIN_H_
+#define NSG_MAPI_MAIN_H_
+
+#define MAX_NAME_LEN 256
+#define MAX_PW_LEN 256
+#define MAX_SESSIONS 50
+#define MAPI_SENDCOMPLETE_EVENT "SendCompletionEvent"
+
+#define MAPI_PROPERTIES_CHROME "chrome://messenger-mapi/locale/mapi.properties"
+#define PREF_MAPI_WARN_PRIOR_TO_BLIND_SEND "mapi.blind-send.warn"
+#define PREF_MAPI_BLIND_SEND_ENABLED "mapi.blind-send.enabled"
+
+#include "nspr.h"
+#include "nsDataHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsStringGlue.h"
+
+class nsMAPISession;
+
+class nsMAPIConfiguration
+{
+private :
+
+ static uint32_t session_generator;
+ static uint32_t sessionCount;
+ static nsMAPIConfiguration *m_pSelfRef;
+ PRLock *m_Lock;
+ uint32_t m_nMaxSessions;
+
+ nsDataHashtable<nsStringHashKey, uint32_t> m_ProfileMap;
+ nsClassHashtable<nsUint32HashKey, nsMAPISession> m_SessionMap;
+ nsMAPIConfiguration();
+ ~nsMAPIConfiguration();
+
+public :
+ static nsMAPIConfiguration *GetMAPIConfiguration();
+ void OpenConfiguration();
+ int16_t RegisterSession(uint32_t aHwnd, const char16_t *aUserName, \
+ const char16_t *aPassword, bool aForceDownLoad, \
+ bool aNewSession, uint32_t *aSession, const char *aIdKey);
+ bool IsSessionValid(uint32_t aSessionID);
+ bool UnRegisterSession(uint32_t aSessionID);
+ char16_t *GetPassword(uint32_t aSessionID);
+ void GetIdKey(uint32_t aSessionID, nsCString& aKey);
+ void *GetMapiListContext(uint32_t aSessionID);
+ void SetMapiListContext(uint32_t aSessionID, void *mapiListContext);
+
+ // a util func
+ static HRESULT GetMAPIErrorFromNSError (nsresult res) ;
+};
+
+class nsMAPISession
+{
+ friend class nsMAPIConfiguration;
+
+ private :
+ bool m_bIsForcedDownLoad;
+ bool m_bApp_or_Service;
+ uint32_t m_hAppHandle;
+ uint32_t m_nShared;
+ nsCString m_pIdKey;
+ nsString m_pProfileName;
+ nsString m_pPassword;
+ int32_t m_messageIndex;
+ void *m_listContext; // used by findNext
+
+ public :
+ nsMAPISession(uint32_t aHwnd, const char16_t *aUserName, \
+ const char16_t *aPassword, \
+ bool aForceDownLoad, const char *aKey);
+ uint32_t IncrementSession();
+ uint32_t DecrementSession();
+ uint32_t GetSessionCount();
+ char16_t *nsMAPISession::GetPassword();
+ void GetIdKey(nsCString& aKey);
+ ~nsMAPISession();
+ // For enumerating Messages...
+ void SetMapiListContext( void *listContext) { m_listContext = listContext; }
+ void *GetMapiListContext( ) { return m_listContext; }
+};
+
+#endif // MSG_MAPI_MAIN_H_
diff --git a/mailnews/mapi/mapihook/src/msgMapiSupport.cpp b/mailnews/mapi/mapihook/src/msgMapiSupport.cpp
new file mode 100644
index 000000000..5c57f087d
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/msgMapiSupport.cpp
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "objbase.h"
+#include "nsISupports.h"
+
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "nsIAppStartupNotifier.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+#include "nsICategoryManager.h"
+#include "Registry.h"
+#include "msgMapiSupport.h"
+
+#include "msgMapiImp.h"
+
+/** Implementation of the nsIMapiSupport interface.
+ * Use standard implementation of nsISupports stuff.
+ */
+
+NS_IMPL_ISUPPORTS(nsMapiSupport, nsIMapiSupport, nsIObserver)
+
+NS_IMETHODIMP
+nsMapiSupport::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
+{
+ nsresult rv = NS_OK ;
+
+ if (!strcmp(aTopic, "profile-after-change"))
+ return InitializeMAPISupport();
+
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID))
+ return ShutdownMAPISupport();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ rv = observerService->AddObserver(this,"profile-after-change", false);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ if (NS_FAILED(rv)) return rv;
+
+ return rv;
+}
+
+
+nsMapiSupport::nsMapiSupport()
+: m_dwRegister(0),
+ m_nsMapiFactory(nullptr)
+{
+}
+
+nsMapiSupport::~nsMapiSupport()
+{
+}
+
+NS_IMETHODIMP
+nsMapiSupport::InitializeMAPISupport()
+{
+ ::OleInitialize(nullptr) ;
+
+ if (m_nsMapiFactory == nullptr) // No Registering if already done. Sanity Check!!
+ {
+ m_nsMapiFactory = new CMapiFactory();
+
+ if (m_nsMapiFactory != nullptr)
+ {
+ HRESULT hr = ::CoRegisterClassObject(CLSID_CMapiImp, \
+ m_nsMapiFactory, \
+ CLSCTX_LOCAL_SERVER, \
+ REGCLS_MULTIPLEUSE, \
+ &m_dwRegister);
+
+ if (FAILED(hr))
+ {
+ m_nsMapiFactory->Release() ;
+ m_nsMapiFactory = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMapiSupport::ShutdownMAPISupport()
+{
+ if (m_dwRegister != 0)
+ ::CoRevokeClassObject(m_dwRegister);
+
+ if (m_nsMapiFactory != nullptr)
+ {
+ m_nsMapiFactory->Release();
+ m_nsMapiFactory = nullptr;
+ }
+
+ ::OleUninitialize();
+
+ return NS_OK ;
+}
+
+NS_IMETHODIMP
+nsMapiSupport::RegisterServer()
+{
+ // TODO: Figure out what kind of error propogation to pass back
+ ::RegisterServer(CLSID_CMapiImp, "Mozilla MAPI", "MozillaMapi", "MozillaMapi.1");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMapiSupport::UnRegisterServer()
+{
+ // TODO: Figure out what kind of error propogation to pass back
+ ::UnregisterServer(CLSID_CMapiImp, "MozillaMapi", "MozillaMapi.1");
+ return NS_OK;
+}
+
+NS_DEFINE_NAMED_CID(NS_IMAPISUPPORT_CID);
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMapiSupport)
+
+static const mozilla::Module::CategoryEntry kMAPICategories[] = {
+ { APPSTARTUP_CATEGORY, "Mapi Support", "service," NS_IMAPISUPPORT_CONTRACTID, },
+ { NULL }
+};
+
+const mozilla::Module::CIDEntry kMAPICIDs[] = {
+ { &kNS_IMAPISUPPORT_CID, false, NULL, nsMapiSupportConstructor },
+ { NULL }
+};
+
+const mozilla::Module::ContractIDEntry kMAPIContracts[] = {
+ { NS_IMAPISUPPORT_CONTRACTID, &kNS_IMAPISUPPORT_CID },
+ { NULL }
+};
+
+static const mozilla::Module kMAPIModule = {
+ mozilla::Module::kVersion,
+ kMAPICIDs,
+ kMAPIContracts,
+ kMAPICategories
+};
+
+NSMODULE_DEFN(msgMapiModule) = &kMAPIModule;
+
+
diff --git a/mailnews/mapi/mapihook/src/msgMapiSupport.h b/mailnews/mapi/mapihook/src/msgMapiSupport.h
new file mode 100644
index 000000000..ff7ffca9f
--- /dev/null
+++ b/mailnews/mapi/mapihook/src/msgMapiSupport.h
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MSG_MAPI_SUPPORT_H_
+#define MSG_MAPI_SUPPORT_H_
+
+#include "nsIObserver.h"
+#include "nsIMapiSupport.h"
+#include "msgMapiFactory.h"
+
+#define NS_IMAPISUPPORT_CID \
+ {0x8967fed2, 0xc8bb, 0x11d5, \
+ { 0xa3, 0xe9, 0x00, 0xb0, 0xd0, 0xf3, 0xba, 0xa7 }}
+
+class nsMapiSupport : public nsIMapiSupport,
+ public nsIObserver
+{
+ public :
+ nsMapiSupport();
+
+ // Declare all interface methods we must implement.
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMAPISUPPORT
+
+ private :
+ ~nsMapiSupport();
+
+ DWORD m_dwRegister;
+ CMapiFactory *m_nsMapiFactory;
+};
+
+#endif // MSG_MAPI_SUPPORT_H_
diff --git a/mailnews/mime/cthandlers/glue/mimexpcom.cpp b/mailnews/mime/cthandlers/glue/mimexpcom.cpp
new file mode 100644
index 000000000..094f61e37
--- /dev/null
+++ b/mailnews/mime/cthandlers/glue/mimexpcom.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsIComponentManager.h"
+#include "nsIMimeObjectClassAccess.h"
+#include "nsMsgMimeCID.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+static NS_DEFINE_CID(kMimeObjectClassAccessCID, NS_MIME_OBJECT_CLASS_ACCESS_CID);
+
+/*
+ * These calls are necessary to expose the object class hierarchy
+ * to externally developed content type handlers.
+ */
+extern "C" void *
+COM_GetmimeInlineTextClass(void)
+{
+ void *ptr = NULL;
+
+ nsresult res;
+ nsCOMPtr<nsIMimeObjectClassAccess> objAccess =
+ do_CreateInstance(kMimeObjectClassAccessCID, &res);
+ if (NS_SUCCEEDED(res) && objAccess)
+ objAccess->GetmimeInlineTextClass(&ptr);
+
+ return ptr;
+}
+
+extern "C" void *
+COM_GetmimeLeafClass(void)
+{
+ void *ptr = NULL;
+
+ nsresult res;
+ nsCOMPtr<nsIMimeObjectClassAccess> objAccess =
+ do_CreateInstance(kMimeObjectClassAccessCID, &res);
+ if (NS_SUCCEEDED(res) && objAccess)
+ objAccess->GetmimeLeafClass(&ptr);
+
+ return ptr;
+}
+
+extern "C" void *
+COM_GetmimeObjectClass(void)
+{
+ void *ptr = NULL;
+
+ nsresult res;
+ nsCOMPtr<nsIMimeObjectClassAccess> objAccess =
+ do_CreateInstance(kMimeObjectClassAccessCID, &res);
+ if (NS_SUCCEEDED(res) && objAccess)
+ objAccess->GetmimeObjectClass(&ptr);
+
+ return ptr;
+}
+
+extern "C" void *
+COM_GetmimeContainerClass(void)
+{
+ void *ptr = NULL;
+
+ nsresult res;
+ nsCOMPtr<nsIMimeObjectClassAccess> objAccess =
+ do_CreateInstance(kMimeObjectClassAccessCID, &res);
+ if (NS_SUCCEEDED(res) && objAccess)
+ objAccess->GetmimeContainerClass(&ptr);
+
+ return ptr;
+}
+
+extern "C" void *
+COM_GetmimeMultipartClass(void)
+{
+ void *ptr = NULL;
+
+ nsresult res;
+ nsCOMPtr<nsIMimeObjectClassAccess> objAccess =
+ do_CreateInstance(kMimeObjectClassAccessCID, &res);
+ if (NS_SUCCEEDED(res) && objAccess)
+ objAccess->GetmimeMultipartClass(&ptr);
+
+ return ptr;
+}
+
+extern "C" void *
+COM_GetmimeMultipartSignedClass(void)
+{
+ void *ptr = NULL;
+
+ nsresult res;
+ nsCOMPtr<nsIMimeObjectClassAccess> objAccess =
+ do_CreateInstance(kMimeObjectClassAccessCID, &res);
+ if (NS_SUCCEEDED(res) && objAccess)
+ objAccess->GetmimeMultipartSignedClass(&ptr);
+
+ return ptr;
+}
+
+extern "C" int
+COM_MimeObject_write(void *mimeObject, char *data, int32_t length,
+ bool user_visible_p)
+{
+ int32_t rc = -1;
+
+ nsresult res;
+ nsCOMPtr<nsIMimeObjectClassAccess> objAccess =
+ do_CreateInstance(kMimeObjectClassAccessCID, &res);
+ if (NS_SUCCEEDED(res) && objAccess)
+ {
+ if (NS_SUCCEEDED(objAccess->MimeObjectWrite(mimeObject, data, length, user_visible_p)))
+ rc = length;
+ else
+ rc = -1;
+ }
+
+ return rc;
+}
+
+extern "C" void *
+COM_MimeCreate(char * content_type, void * hdrs, void * opts)
+{
+ void *ptr = NULL;
+
+ nsresult res;
+ nsCOMPtr<nsIMimeObjectClassAccess> objAccess =
+ do_CreateInstance(kMimeObjectClassAccessCID, &res);
+ if (NS_SUCCEEDED(res) && objAccess)
+ objAccess->MimeCreate(content_type, hdrs, opts, &ptr);
+
+ return ptr;
+}
diff --git a/mailnews/mime/cthandlers/glue/mimexpcom.h b/mailnews/mime/cthandlers/glue/mimexpcom.h
new file mode 100644
index 000000000..9468b22b4
--- /dev/null
+++ b/mailnews/mime/cthandlers/glue/mimexpcom.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This is the definitions for the Content Type Handler plugins to
+ * access internals of libmime via XP-COM calls
+ */
+#ifndef _MIMEXPCOM_H_
+#define _MIMEXPCOM_H_
+
+/*
+ This header exposes functions that are necessary to access the
+ object hierarchy for the mime chart. The class hierarchy is:
+
+ MimeObject (abstract)
+ |
+ |--- MimeContainer (abstract)
+ | |
+ | |--- MimeMultipart (abstract)
+ | | |
+ | | |--- MimeMultipartMixed
+ | | |
+ | | |--- MimeMultipartDigest
+ | | |
+ | | |--- MimeMultipartParallel
+ | | |
+ | | |--- MimeMultipartAlternative
+ | | |
+ | | |--- MimeMultipartRelated
+ | | |
+ | | |--- MimeMultipartAppleDouble
+ | | |
+ | | |--- MimeSunAttachment
+ | | |
+ | | |--- MimeMultipartSigned (abstract)
+ | | |
+ | | |--- MimeMultipartSigned
+ | |
+ | |--- MimeXlateed (abstract)
+ | | |
+ | | |--- MimeXlateed
+ | |
+ | |--- MimeMessage
+ | |
+ | |--- MimeUntypedText
+ |
+ |--- MimeLeaf (abstract)
+ | |
+ | |--- MimeInlineText (abstract)
+ | | |
+ | | |--- MimeInlineTextPlain
+ | | |
+ | | |--- MimeInlineTextHTML
+ | | |
+ | | |--- MimeInlineTextRichtext
+ | | | |
+ | | | |--- MimeInlineTextEnriched
+ | | |
+ | | |--- MimeInlineTextVCard
+ | |
+ | |--- MimeInlineImage
+ | |
+ | |--- MimeExternalObject
+ |
+ |--- MimeExternalBody
+ */
+
+/*
+ * These functions are exposed by libmime to be used by content type
+ * handler plugins for processing stream data.
+ */
+/*
+ * This is the write call for outputting processed stream data.
+ */
+extern "C" int COM_MimeObject_write(void *mimeObject, const char *data,
+ int32_t length,
+ bool user_visible_p);
+/*
+ * The following group of calls expose the pointers for the object
+ * system within libmime.
+ */
+extern "C" void *COM_GetmimeInlineTextClass(void);
+extern "C" void *COM_GetmimeLeafClass(void);
+extern "C" void *COM_GetmimeObjectClass(void);
+extern "C" void *COM_GetmimeContainerClass(void);
+extern "C" void *COM_GetmimeMultipartClass(void);
+extern "C" void *COM_GetmimeMultipartSignedClass(void);
+
+extern "C" void *COM_MimeCreate(char * content_type, void * hdrs, void * opts);
+
+#endif /* _MIMEXPCOM_H_ */
diff --git a/mailnews/mime/cthandlers/glue/moz.build b/mailnews/mime/cthandlers/glue/moz.build
new file mode 100644
index 000000000..f51518ca9
--- /dev/null
+++ b/mailnews/mime/cthandlers/glue/moz.build
@@ -0,0 +1,18 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'nsMimeContentTypeHandler.h',
+]
+
+SOURCES += [
+ 'mimexpcom.cpp',
+ 'nsMimeContentTypeHandler.cpp',
+]
+
+FINAL_LIBRARY = 'mail'
+
+Library('mimecthglue_s')
+
diff --git a/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp
new file mode 100644
index 000000000..369f8c4bf
--- /dev/null
+++ b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <stdio.h>
+#include "nscore.h"
+#include "plstr.h"
+//#include "mimecth.h"
+#include "nsMimeContentTypeHandler.h"
+
+/*
+ * The following macros actually implement addref, release and
+ * query interface for our component.
+ */
+NS_IMPL_ISUPPORTS(nsMimeContentTypeHandler, nsIMimeContentTypeHandler)
+
+/*
+ * nsIMimeEmitter definitions....
+ */
+nsMimeContentTypeHandler::nsMimeContentTypeHandler(const char *aMimeType,
+ MCTHCreateCTHClass callback)
+{
+ NS_ASSERTION(aMimeType, "nsMimeContentTypeHandler should be initialized with non-null mime type");
+ NS_ASSERTION(callback, "nsMimeContentTypeHandler should be initialized with non-null callback");
+ mimeType = PL_strdup(aMimeType);
+ realCreateContentTypeHandlerClass = callback;
+}
+
+nsMimeContentTypeHandler::~nsMimeContentTypeHandler(void)
+{
+ if (mimeType) {
+ NS_Free(mimeType);
+ mimeType = 0;
+ }
+ realCreateContentTypeHandlerClass = 0;
+}
+
+// Get the content type if necessary
+nsresult
+nsMimeContentTypeHandler::GetContentType(char **contentType)
+{
+ *contentType = PL_strdup(mimeType);
+ return NS_OK;
+}
+
+// Set the output stream for processed data.
+nsresult
+nsMimeContentTypeHandler::CreateContentTypeHandlerClass(const char *content_type,
+ contentTypeHandlerInitStruct *initStruct,
+ MimeObjectClass **objClass)
+{
+ *objClass = realCreateContentTypeHandlerClass(content_type, initStruct);
+ if (!*objClass)
+ return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */
+ else
+ return NS_OK;
+}
+
+
+
diff --git a/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h
new file mode 100644
index 000000000..8cc142268
--- /dev/null
+++ b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This interface is implemented by content type handlers that will be
+ * called upon by libmime to process various attachments types. The primary
+ * purpose of these handlers will be to represent the attached data in a
+ * viewable HTML format that is useful for the user
+ *
+ * Note: These will all register by their content type prefixed by the
+ * following: mimecth:text/vcard
+ *
+ * libmime will then use the XPCOM Component Manager to
+ * locate the appropriate Content Type handler
+ */
+#ifndef nsMimeContentTypeHandler_h_
+#define nsMimeContentTypeHandler_h_
+
+#include "mozilla/Attributes.h"
+#include "nsIMimeContentTypeHandler.h"
+
+typedef MimeObjectClass *
+(* MCTHCreateCTHClass)(const char *content_type,
+ contentTypeHandlerInitStruct *initStruct);
+
+class nsMimeContentTypeHandler : public nsIMimeContentTypeHandler {
+public:
+ nsMimeContentTypeHandler (const char *aMimeType,
+ MCTHCreateCTHClass callback);
+
+ /* this macro defines QueryInterface, AddRef and Release for this class */
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD GetContentType(char **contentType) override;
+
+ NS_IMETHOD CreateContentTypeHandlerClass(const char *content_type,
+ contentTypeHandlerInitStruct *initStruct,
+ MimeObjectClass **objClass) override;
+ private:
+ virtual ~nsMimeContentTypeHandler();
+ char *mimeType;
+ MCTHCreateCTHClass realCreateContentTypeHandlerClass;
+};
+
+#endif /* nsMimeContentTypeHandler_h_ */
diff --git a/mailnews/mime/cthandlers/moz.build b/mailnews/mime/cthandlers/moz.build
new file mode 100644
index 000000000..31807499d
--- /dev/null
+++ b/mailnews/mime/cthandlers/moz.build
@@ -0,0 +1,12 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# pgpmime depends on glue.
+DIRS += [
+ 'glue',
+ 'vcard',
+ 'pgpmime',
+]
+
diff --git a/mailnews/mime/cthandlers/pgpmime/moz.build b/mailnews/mime/cthandlers/pgpmime/moz.build
new file mode 100644
index 000000000..878bd5bfa
--- /dev/null
+++ b/mailnews/mime/cthandlers/pgpmime/moz.build
@@ -0,0 +1,20 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'nsPgpMimeProxy.h',
+]
+
+SOURCES += [
+ 'nsPgpMimeProxy.cpp',
+]
+
+FINAL_LIBRARY = 'mail'
+
+Library('pgpmime_s')
+
+LOCAL_INCLUDES += [
+ '../glue',
+]
diff --git a/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp
new file mode 100644
index 000000000..5c56a1809
--- /dev/null
+++ b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp
@@ -0,0 +1,634 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPgpMimeProxy.h"
+#include "nspr.h"
+#include "plstr.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "mozilla/Services.h"
+#include "nsIRequest.h"
+#include "nsIStringBundle.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIURI.h"
+#include "mimexpcom.h"
+#include "nsMsgUtils.h"
+
+#include "nsMsgMimeCID.h"
+
+#include "mimecth.h"
+#include "mimemoz2.h"
+#include "nspr.h"
+#include "plstr.h"
+#include "nsIPgpMimeProxy.h"
+#include "nsComponentManagerUtils.h"
+
+#define MIME_SUPERCLASS mimeEncryptedClass
+MimeDefClass(MimeEncryptedPgp, MimeEncryptedPgpClass,
+ mimeEncryptedPgpClass, &MIME_SUPERCLASS);
+
+#define kCharMax 1024
+
+extern "C" MimeObjectClass *
+MIME_PgpMimeCreateContentTypeHandlerClass(
+ const char *content_type,
+ contentTypeHandlerInitStruct *initStruct)
+{
+ MimeObjectClass *objClass = (MimeObjectClass *) &mimeEncryptedPgpClass;
+
+ initStruct->force_inline_display = false;
+
+ return objClass;
+}
+
+static void *MimePgpe_init(MimeObject *,
+ int (*output_fn) (const char *, int32_t, void *),
+ void *);
+static int MimePgpe_write (const char *, int32_t, void *);
+static int MimePgpe_eof (void *, bool);
+static char* MimePgpe_generate (void *);
+static void MimePgpe_free (void *);
+
+/* Returns a string describing the location of the part (like "2.5.3").
+ This is not a full URL, just a part-number.
+ */
+static nsCString determineMimePart(MimeObject* obj);
+
+
+#define PGPMIME_PROPERTIES_URL "chrome://messenger/locale/pgpmime.properties"
+#define PGPMIME_STR_NOT_SUPPORTED_ID u"pgpMimeNeedsAddon"
+#define PGPMIME_URL_PREF "mail.pgpmime.addon_url"
+
+static void PgpMimeGetNeedsAddonString(nsCString &aResult)
+{
+ aResult.AssignLiteral("???");
+
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ nsresult rv = stringBundleService->CreateBundle(PGPMIME_PROPERTIES_URL,
+ getter_AddRefs(stringBundle));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCString url;
+ if (NS_FAILED(prefs->GetCharPref("mail.pgpmime.addon_url",
+ getter_Copies(url))))
+ return;
+
+ NS_ConvertUTF8toUTF16 url16(url);
+ const char16_t *formatStrings[] = { url16.get() };
+
+ nsString result;
+ rv = stringBundle->FormatStringFromName(PGPMIME_STR_NOT_SUPPORTED_ID,
+ formatStrings, 1, getter_Copies(result));
+ if (NS_FAILED(rv))
+ return;
+ aResult = NS_ConvertUTF16toUTF8(result);
+}
+
+static int
+MimeEncryptedPgpClassInitialize(MimeEncryptedPgpClass *clazz)
+{
+ mozilla::DebugOnly<MimeObjectClass *> oclass = (MimeObjectClass *) clazz;
+ NS_ASSERTION(!oclass->class_initialized, "oclass is not initialized");
+
+ MimeEncryptedClass *eclass = (MimeEncryptedClass *) clazz;
+
+ eclass->crypto_init = MimePgpe_init;
+ eclass->crypto_write = MimePgpe_write;
+ eclass->crypto_eof = MimePgpe_eof;
+ eclass->crypto_generate_html = MimePgpe_generate;
+ eclass->crypto_free = MimePgpe_free;
+
+ return 0;
+}
+
+class MimePgpeData : public nsISupports
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ int (*output_fn) (const char *buf, int32_t buf_size, void *output_closure);
+ void *output_closure;
+ MimeObject *self;
+
+ nsCOMPtr<nsIPgpMimeProxy> mimeDecrypt;
+
+ MimePgpeData()
+ : output_fn(nullptr),
+ output_closure(nullptr)
+ {
+ }
+
+private:
+ virtual ~MimePgpeData()
+ {
+ }
+};
+
+NS_IMPL_ISUPPORTS0(MimePgpeData)
+
+static void*
+MimePgpe_init(MimeObject *obj,
+ int (*output_fn) (const char *buf, int32_t buf_size,
+ void *output_closure),
+ void *output_closure)
+{
+ if (!(obj && obj->options && output_fn))
+ return nullptr;
+
+ MimePgpeData* data = new MimePgpeData();
+ NS_ENSURE_TRUE(data, nullptr);
+
+ data->self = obj;
+ data->output_fn = output_fn;
+ data->output_closure = output_closure;
+ data->mimeDecrypt = nullptr;
+
+ nsresult rv;
+ data->mimeDecrypt = do_CreateInstance(NS_PGPMIMEPROXY_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return data;
+
+ char *ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, false, false);
+
+ rv = (ct ? data->mimeDecrypt->SetContentType(nsDependentCString(ct))
+ : data->mimeDecrypt->SetContentType(EmptyCString()));
+
+ PR_Free(ct);
+
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ nsCString mimePart = determineMimePart(obj);
+
+ rv = data->mimeDecrypt->SetMimePart(mimePart);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ mime_stream_data *msd = (mime_stream_data *) (data->self->options->stream_closure);
+ nsIChannel *channel = msd->channel;
+
+ nsCOMPtr<nsIURI> uri;
+ if (channel)
+ channel->GetURI(getter_AddRefs(uri));
+
+ if (NS_FAILED(data->mimeDecrypt->SetMimeCallback(output_fn, output_closure, uri)))
+ return nullptr;
+
+ return data;
+}
+
+static int
+MimePgpe_write(const char *buf, int32_t buf_size, void *output_closure)
+{
+ MimePgpeData* data = (MimePgpeData *) output_closure;
+
+ if (!data || !data->output_fn)
+ return -1;
+
+ if (!data->mimeDecrypt)
+ return 0;
+
+ return (NS_SUCCEEDED(data->mimeDecrypt->Write(buf, buf_size)) ? 0 : -1);
+}
+
+static int
+MimePgpe_eof(void* output_closure, bool abort_p)
+{
+ MimePgpeData* data = (MimePgpeData *) output_closure;
+
+ if (!data || !data->output_fn)
+ return -1;
+
+ if (NS_FAILED(data->mimeDecrypt->Finish()))
+ return -1;
+
+ data->mimeDecrypt = nullptr;
+ return 0;
+}
+
+static char*
+MimePgpe_generate(void *output_closure)
+{
+ const char htmlMsg[] = "<html><body><b>GEN MSG<b></body></html>";
+ char* msg = (char *) PR_MALLOC(strlen(htmlMsg) + 1);
+ if (msg)
+ PL_strcpy(msg, htmlMsg);
+
+ return msg;
+}
+
+static void
+MimePgpe_free(void *output_closure)
+{
+}
+
+/* Returns a string describing the location of the part (like "2.5.3").
+ This is not a full URL, just a part-number.
+ */
+static nsCString
+determineMimePart(MimeObject* obj)
+{
+ char mimePartNum[20];
+ MimeObject *kid;
+ MimeContainer *cont;
+ int32_t i;
+
+ nsCString mimePart;
+
+ while (obj->parent) {
+ cont = (MimeContainer *) obj->parent;
+ for (i = 0; i < cont->nchildren; i++) {
+ kid = cont->children[i];
+ if (kid == obj) {
+ sprintf(mimePartNum, ".%d", i + 1);
+ mimePart.Insert(mimePartNum, 0);
+ }
+ }
+ obj = obj->parent;
+ }
+
+ // remove leading "."
+ if (mimePart.Length() > 0)
+ mimePart.Cut(0, 1);
+
+ return mimePart;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+NS_IMPL_ISUPPORTS(nsPgpMimeProxy,
+ nsIPgpMimeProxy,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIRequest,
+ nsIInputStream)
+
+// nsPgpMimeProxy implementation
+nsPgpMimeProxy::nsPgpMimeProxy()
+ : mInitialized(false),
+ mDecryptor(nullptr),
+ mLoadGroup(nullptr),
+ mLoadFlags(LOAD_NORMAL),
+ mCancelStatus(NS_OK)
+{
+}
+
+nsPgpMimeProxy::~nsPgpMimeProxy()
+{
+ Finalize();
+}
+
+nsresult
+nsPgpMimeProxy::Finalize()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::SetMimeCallback(MimeDecodeCallbackFun outputFun,
+ void* outputClosure,
+ nsIURI* myUri)
+{
+ if (!outputFun || !outputClosure)
+ return NS_ERROR_NULL_POINTER;
+
+ mOutputFun = outputFun;
+ mOutputClosure = outputClosure;
+ mInitialized = true;
+
+ mStreamOffset = 0;
+ mByteBuf.Truncate();
+
+ if (mDecryptor)
+ return mDecryptor->OnStartRequest((nsIRequest*) this, myUri);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::Init()
+{
+ mByteBuf.Truncate();
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ mDecryptor = do_CreateInstance(PGPMIME_JS_DECRYPTOR_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ mDecryptor = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::Write(const char *buf, uint32_t buf_size)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ mByteBuf.Assign(buf, buf_size);
+ mStreamOffset = 0;
+
+ if (mDecryptor)
+ return mDecryptor->OnDataAvailable((nsIRequest*) this, nullptr, (nsIInputStream*) this,
+ 0, buf_size);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::Finish() {
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ if (mDecryptor) {
+ return mDecryptor->OnStopRequest((nsIRequest*) this, nullptr, NS_OK);
+ }
+ else {
+ nsCString temp;
+ temp.AppendLiteral("Content-Type: text/html; Charset=utf-8\r\n\r\n<html><body>");
+ temp.AppendLiteral("<BR><text=\"#000000\" bgcolor=\"#FFFFFF\" link=\"#FF0000\" vlink=\"#800080\" alink=\"#0000FF\">");
+ temp.AppendLiteral("<center><table BORDER=1 ><tr><td><CENTER>");
+
+ nsCString tString;
+ PgpMimeGetNeedsAddonString(tString);
+ temp.Append(tString);
+ temp.AppendLiteral("</CENTER></td></tr></table></center><BR></body></html>\r\n");
+
+ PR_SetError(0,0);
+ int status = mOutputFun(temp.get(), temp.Length(), mOutputClosure);
+ if (status < 0) {
+ PR_SetError(status, 0);
+ mOutputFun = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::GetDecryptor(nsIStreamListener **aDecryptor)
+{
+ NS_IF_ADDREF(*aDecryptor = mDecryptor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::SetDecryptor(nsIStreamListener *aDecryptor)
+{
+ mDecryptor = aDecryptor;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsPgpMimeProxy::GetContentType(nsACString &aContentType)
+{
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsPgpMimeProxy::SetContentType(const nsACString &aContentType)
+{
+ mContentType = aContentType;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::GetMimePart(nsACString &aMimePart)
+{
+ aMimePart = mMimePart;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsPgpMimeProxy::SetMimePart(const nsACString &aMimePart)
+{
+ mMimePart = aMimePart;
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods
+///////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsPgpMimeProxy::GetName(nsACString &result)
+{
+ result = "pgpmimeproxy";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::IsPending(bool *result)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ *result = NS_SUCCEEDED(mCancelStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::GetStatus(nsresult *status)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ *status = mCancelStatus;
+ return NS_OK;
+}
+
+// NOTE: We assume that OnStopRequest should not be called if
+// request is canceled. This may be wrong!
+NS_IMETHODIMP
+nsPgpMimeProxy::Cancel(nsresult status)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ // Need a non-zero status code to cancel
+ if (NS_SUCCEEDED(status))
+ return NS_ERROR_FAILURE;
+
+ if (NS_SUCCEEDED(mCancelStatus))
+ mCancelStatus = status;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::Suspend(void)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::Resume(void)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::GetLoadGroup(nsILoadGroup * *aLoadGroup)
+{
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIInputStream methods
+///////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsPgpMimeProxy::Available(uint64_t* _retval)
+{
+ NS_ENSURE_ARG(_retval);
+
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ *_retval = (mByteBuf.Length() > mStreamOffset) ?
+ mByteBuf.Length() - mStreamOffset : 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::Read(char* buf, uint32_t count,
+ uint32_t *readCount)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ if (!buf || !readCount)
+ return NS_ERROR_NULL_POINTER;
+
+ int32_t avail = (mByteBuf.Length() > mStreamOffset) ?
+ mByteBuf.Length() - mStreamOffset : 0;
+
+ uint32_t readyCount = ((uint32_t) avail > count) ? count : avail;
+
+ if (readyCount) {
+ memcpy(buf, mByteBuf.get()+mStreamOffset, readyCount);
+ *readCount = readyCount;
+ }
+
+ mStreamOffset += *readCount;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::ReadSegments(nsWriteSegmentFun writer,
+ void * aClosure, uint32_t count,
+ uint32_t *readCount)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::IsNonBlocking(bool *aNonBlocking)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ *aNonBlocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::Close()
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ mStreamOffset = 0;
+ mByteBuf.Truncate();
+
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIStreamListener methods
+///////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsPgpMimeProxy::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPgpMimeProxy::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
+ nsresult aStatus)
+{
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIStreamListener method
+///////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsPgpMimeProxy::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_ARG(aInputStream);
+
+ char buf[kCharMax];
+ uint32_t readCount, readMax;
+
+ while (aLength > 0) {
+ readMax = (aLength < kCharMax) ? aLength : kCharMax;
+
+ nsresult rv;
+ rv = aInputStream->Read((char *) buf, readMax, &readCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int status = mOutputFun(buf, readCount, mOutputClosure);
+ if (status < 0) {
+ PR_SetError(status, 0);
+ mOutputFun = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ aLength -= readCount;
+ }
+
+ return NS_OK;
+}
diff --git a/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h
new file mode 100644
index 000000000..41e7f59cc
--- /dev/null
+++ b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsPgpmimeDecrypt_h_
+#define _nsPgpmimeDecrypt_h_
+
+#include "mimecth.h"
+#include "nsIPgpMimeProxy.h"
+#include "nsCOMPtr.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsILoadGroup.h"
+
+#define PGPMIME_JS_DECRYPTOR_CONTRACTID "@mozilla.org/mime/pgp-mime-js-decrypt;1"
+
+typedef struct MimeEncryptedPgpClass MimeEncryptedPgpClass;
+typedef struct MimeEncryptedPgp MimeEncryptedPgp;
+
+struct MimeEncryptedPgpClass {
+ MimeEncryptedClass encrypted;
+};
+
+struct MimeEncryptedPgp {
+ MimeEncrypted encrypted;
+};
+
+class nsPgpMimeProxy : public nsIPgpMimeProxy,
+ public nsIRequest,
+ public nsIInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPGPMIMEPROXY
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIINPUTSTREAM
+
+ nsPgpMimeProxy();
+
+ // Define a Create method to be used with a factory:
+ static NS_METHOD
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ virtual ~nsPgpMimeProxy();
+ bool mInitialized;
+ nsCOMPtr<nsIStreamListener> mDecryptor;
+
+ MimeDecodeCallbackFun mOutputFun;
+ void* mOutputClosure;
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsLoadFlags mLoadFlags;
+ nsresult mCancelStatus;
+
+ uint32_t mStreamOffset;
+ nsCString mByteBuf;
+ nsCString mContentType;
+ nsCString mMimePart;
+
+ nsresult Finalize();
+};
+
+#define MimeEncryptedPgpClassInitializer(ITYPE,CSUPER) \
+ { MimeEncryptedClassInitializer(ITYPE,CSUPER) }
+
+#endif
diff --git a/mailnews/mime/cthandlers/vcard/mimevcrd.cpp b/mailnews/mime/cthandlers/vcard/mimevcrd.cpp
new file mode 100644
index 000000000..140afb5aa
--- /dev/null
+++ b/mailnews/mime/cthandlers/vcard/mimevcrd.cpp
@@ -0,0 +1,378 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimevcrd.h"
+
+#include "mimecth.h"
+#include "mimexpcom.h"
+#include "nsIMsgVCardService.h"
+#include "nsINetUtil.h"
+#include "nsMsgUtils.h"
+#include "prmem.h"
+#include "prprf.h"
+#include "nsServiceManagerUtils.h"
+
+static int MimeInlineTextVCard_parse_line (const char *, int32_t, MimeObject *);
+static int MimeInlineTextVCard_parse_eof (MimeObject *, bool);
+static int MimeInlineTextVCard_parse_begin (MimeObject *obj);
+
+static int s_unique = 0;
+
+static int BeginVCard (MimeObject *obj);
+static int EndVCard (MimeObject *obj);
+static int WriteOutVCard (MimeObject *obj, VObject* v);
+
+static int GenerateVCardData(MimeObject * aMimeObj, VObject* aVcard);
+static int OutputVcardAttribute(MimeObject *aMimeObj, VObject *aVcard, const char* id, nsACString& vCardOutput);
+static int OutputBasicVcard(MimeObject *aMimeObj, VObject *aVcard, nsACString& vCardOutput);
+
+typedef struct
+ {
+ const char *attributeName;
+ int resourceId;
+ } AttributeName;
+
+#define kNumAttributes 12
+
+#define MSGVCARDSERVICE_CONTRACT_ID "@mozilla.org/addressbook/msgvcardservice;1"
+
+/* This is the object definition. Note: we will set the superclass
+ to NULL and manually set this on the class creation */
+MimeDefClass(MimeInlineTextVCard, MimeInlineTextVCardClass,
+ mimeInlineTextVCardClass, NULL);
+
+extern "C" MimeObjectClass *
+MIME_VCardCreateContentTypeHandlerClass(const char *content_type,
+ contentTypeHandlerInitStruct *initStruct)
+{
+ MimeObjectClass *clazz = (MimeObjectClass *)&mimeInlineTextVCardClass;
+ /*
+ * Must set the superclass by hand.
+ */
+ if (!COM_GetmimeInlineTextClass())
+ return NULL;
+
+ clazz->superclass = (MimeObjectClass *)COM_GetmimeInlineTextClass();
+ initStruct->force_inline_display = true;
+ return clazz;
+}
+
+/*
+ * Implementation of VCard clazz
+ */
+static int
+MimeInlineTextVCardClassInitialize(MimeInlineTextVCardClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:11");
+ oclass->parse_begin = MimeInlineTextVCard_parse_begin;
+ oclass->parse_line = MimeInlineTextVCard_parse_line;
+ oclass->parse_eof = MimeInlineTextVCard_parse_eof;
+ return 0;
+}
+
+static int
+MimeInlineTextVCard_parse_begin (MimeObject *obj)
+{
+ int status = ((MimeObjectClass*)COM_GetmimeLeafClass())->parse_begin(obj);
+ MimeInlineTextVCardClass *clazz;
+ if (status < 0) return status;
+
+ if (!obj->output_p) return 0;
+ if (!obj->options || !obj->options->write_html_p) return 0;
+
+ /* This is a fine place to write out any HTML before the real meat begins.
+ In this sample code, we tell it to start a table. */
+
+ clazz = ((MimeInlineTextVCardClass *) obj->clazz);
+ /* initialize vcard string to empty; */
+ NS_MsgSACopy(&(clazz->vCardString), "");
+
+ obj->options->state->separator_suppressed_p = true;
+ return 0;
+}
+
+char *strcpySafe (char *dest, const char *src, size_t destLength)
+{
+ char *result = strncpy (dest, src, --destLength);
+ dest[destLength] = '\0';
+ return result;
+}
+
+static int
+MimeInlineTextVCard_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ // This routine gets fed each line of data, one at a time.
+ char* linestring;
+ MimeInlineTextVCardClass *clazz = ((MimeInlineTextVCardClass *) obj->clazz);
+
+ if (!obj->output_p) return 0;
+ if (!obj->options || !obj->options->output_fn) return 0;
+ if (!obj->options->write_html_p)
+ {
+ return COM_MimeObject_write(obj, line, length, true);
+ }
+
+ linestring = (char *) PR_MALLOC (length + 1);
+ memset(linestring, 0, (length + 1));
+
+ if (linestring)
+ {
+ strcpySafe((char *)linestring, line, length + 1);
+ NS_MsgSACat (&clazz->vCardString, linestring);
+ PR_Free (linestring);
+ }
+
+ return 0;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+static int
+MimeInlineTextVCard_parse_eof (MimeObject *obj, bool abort_p)
+{
+ nsCOMPtr<nsIMsgVCardService> vCardService =
+ do_GetService(MSGVCARDSERVICE_CONTRACT_ID);
+ if (!vCardService)
+ return -1;
+
+ int status = 0;
+ MimeInlineTextVCardClass *clazz = ((MimeInlineTextVCardClass *) obj->clazz);
+
+ VObject *t, *v;
+
+ if (obj->closed_p) return 0;
+
+ /* Run parent method first, to flush out any buffered data. */
+ // status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ status = ((MimeObjectClass*)COM_GetmimeInlineTextClass())->parse_eof(obj, abort_p);
+ if (status < 0) return status;
+
+ // Don't quote vCards...
+ if ( (obj->options) &&
+ ((obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting) ||
+ (obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting))
+ )
+ return 0;
+
+ if (!clazz->vCardString) return 0;
+
+ v = vCardService->Parse_MIME(clazz->vCardString, strlen(clazz->vCardString));
+ NS_ASSERTION(v, "parse of vCard failed");
+
+ if (clazz->vCardString) {
+ PR_Free ((char*) clazz->vCardString);
+ clazz->vCardString = NULL;
+ }
+
+ if (obj->output_p && obj->options && obj->options->write_html_p &&
+ obj->options->headers != MimeHeadersCitation) {
+ /* This is a fine place to write any closing HTML. In fact, you may
+ want all the writing to be here, and all of the above would just
+ collect data into datastructures, though that isn't very
+ "streaming". */
+ t = v;
+ while (v && status >= 0) {
+ /* write out html */
+ status = WriteOutVCard (obj, v);
+ /* parse next vcard incase they're embedded */
+ v = vCardService->NextVObjectInList(v);
+ }
+
+ (void)vCardService->CleanVObject(t);
+ }
+
+ if (status < 0)
+ return status;
+
+ return 0;
+}
+
+static int EndVCard (MimeObject *obj)
+{
+ int status = 0;
+
+ /* Scribble HTML-ending stuff into the stream */
+ char htmlFooters[32];
+ PR_snprintf (htmlFooters, sizeof(htmlFooters), "</BODY>%s</HTML>%s", MSG_LINEBREAK, MSG_LINEBREAK);
+ status = COM_MimeObject_write(obj, htmlFooters, strlen(htmlFooters), false);
+
+ if (status < 0) return status;
+
+ return 0;
+}
+
+static int BeginVCard (MimeObject *obj)
+{
+ int status = 0;
+
+ /* Scribble HTML-starting stuff into the stream */
+ char htmlHeaders[32];
+
+ s_unique++;
+ PR_snprintf (htmlHeaders, sizeof(htmlHeaders), "<HTML>%s<BODY>%s", MSG_LINEBREAK, MSG_LINEBREAK);
+ status = COM_MimeObject_write(obj, htmlHeaders, strlen(htmlHeaders), true);
+
+ if (status < 0) return status;
+
+ return 0;
+}
+
+
+static int WriteOutVCard (MimeObject * aMimeObj, VObject* aVcard)
+{
+ BeginVCard (aMimeObj);
+
+ GenerateVCardData(aMimeObj, aVcard);
+
+ return EndVCard (aMimeObj);
+}
+
+
+static int GenerateVCardData(MimeObject * aMimeObj, VObject* aVcard)
+{
+ // style is driven from CSS not here. Just layout the minimal vCard data
+ nsCString vCardOutput;
+
+ vCardOutput = "<table class=\"moz-vcard-table\"> <tr> "; // outer table plus the first (and only row) we use for this table
+
+ // we need to get an escaped vCard url to bind to our add to address book button
+ nsCOMPtr<nsIMsgVCardService> vCardService = do_GetService(MSGVCARDSERVICE_CONTRACT_ID);
+ if (!vCardService)
+ return -1;
+
+ nsAutoCString vCard;
+ nsAutoCString vEscCard;
+ int len = 0;
+
+ vCard.Adopt(vCardService->WriteMemoryVObjects(0, &len, aVcard, false));
+ MsgEscapeString(vCard, nsINetUtil::ESCAPE_XALPHAS, vEscCard);
+
+ // first cell in the outer table row is a clickable image which brings up the rich address book UI for the vcard
+ vCardOutput += "<td valign=\"top\"> <a class=\"moz-vcard-badge\" href=\"addbook:add?action=add?vcard=";
+ vCardOutput += vEscCard; // the href is the vCard
+ vCardOutput += "\"></a></td>";
+
+ // the 2nd cell in the outer table row is a nested table containing the actual vCard properties
+ vCardOutput += "<td> <table id=\"moz-vcard-properties-table\"> <tr> ";
+
+ OutputBasicVcard(aMimeObj, aVcard, vCardOutput);
+
+ // close the properties table
+ vCardOutput += "</table> </td> ";
+
+ // 2nd cell in the outer table is our vCard image
+
+ vCardOutput += "</tr> </table>";
+
+ // now write out the vCard
+ return COM_MimeObject_write(aMimeObj, (char *) vCardOutput.get(), vCardOutput.Length(), true);
+}
+
+
+static int OutputBasicVcard(MimeObject *aMimeObj, VObject *aVcard, nsACString& vCardOutput)
+{
+ VObject *prop = NULL;
+ nsAutoCString urlstring;
+ nsAutoCString namestring;
+ nsAutoCString emailstring;
+
+ nsCOMPtr<nsIMsgVCardService> vCardService = do_GetService(MSGVCARDSERVICE_CONTRACT_ID);
+ if (!vCardService)
+ return -1;
+
+ /* get the name and email */
+ prop = vCardService->IsAPropertyOf(aVcard, VCFullNameProp);
+ if (prop)
+ {
+ if (VALUE_TYPE(prop))
+ {
+ if (VALUE_TYPE(prop) != VCVT_RAW)
+ namestring.Adopt(vCardService->FakeCString(prop));
+ else
+ namestring.Adopt(vCardService->VObjectAnyValue(prop));
+
+ if (!namestring.IsEmpty())
+ {
+ vCardOutput += "<td class=\"moz-vcard-title-property\"> ";
+
+ prop = vCardService->IsAPropertyOf(aVcard, VCURLProp);
+ if (prop)
+ {
+ urlstring.Adopt(vCardService->FakeCString(prop));
+ if (urlstring.IsEmpty())
+ vCardOutput += namestring;
+ else
+ {
+ char buf[512];
+ PR_snprintf(buf, 512, "<a href=""%s"" private>%s</a>", urlstring.get(), namestring.get());
+ vCardOutput.Append(buf);
+ }
+ }
+ else
+ vCardOutput += namestring;
+
+ /* get the email address */
+ prop = vCardService->IsAPropertyOf(aVcard, VCEmailAddressProp);
+ if (prop)
+ {
+ emailstring.Adopt(vCardService->FakeCString(prop));
+ if (!emailstring.IsEmpty())
+ {
+ char buf[512];
+ PR_snprintf(buf, 512, "&nbsp;&lt;<a href=""mailto:%s"" private>%s</a>&gt;", emailstring.get(), emailstring.get());
+ vCardOutput.Append(buf);
+ }
+ } // if email address property
+
+ vCardOutput += "</td> </tr> "; // end the cell for the name/email address
+ } // if we have a name property
+ }
+ } // if full name property
+
+ // now each basic property goes on its own line
+
+ // title
+ (void) OutputVcardAttribute (aMimeObj, aVcard, VCTitleProp, vCardOutput);
+
+ // org name and company name
+ prop = vCardService->IsAPropertyOf(aVcard, VCOrgProp);
+ if (prop)
+ {
+ OutputVcardAttribute (aMimeObj, prop, VCOrgUnitProp, vCardOutput);
+ OutputVcardAttribute (aMimeObj, prop, VCOrgNameProp, vCardOutput);
+ }
+
+ return 0;
+}
+
+static int OutputVcardAttribute(MimeObject *aMimeObj, VObject *aVcard, const char* id, nsACString& vCardOutput)
+{
+ VObject *prop = NULL;
+ nsAutoCString string;
+
+ nsCOMPtr<nsIMsgVCardService> vCardService = do_GetService(MSGVCARDSERVICE_CONTRACT_ID);
+ if (!vCardService)
+ return -1;
+
+ prop = vCardService->IsAPropertyOf(aVcard, id);
+ if (prop)
+ if (VALUE_TYPE(prop))
+ {
+ if (VALUE_TYPE(prop) != VCVT_RAW)
+ string.Adopt(vCardService->FakeCString(prop));
+ else
+ string.Adopt(vCardService->VObjectAnyValue(prop));
+
+ if (!string.IsEmpty())
+ {
+ vCardOutput += "<tr> <td class=\"moz-vcard-property\">";
+ vCardOutput += string;
+ vCardOutput += "</td> </tr> ";
+ }
+ }
+
+ return 0;
+}
diff --git a/mailnews/mime/cthandlers/vcard/mimevcrd.h b/mailnews/mime/cthandlers/vcard/mimevcrd.h
new file mode 100644
index 000000000..6e731f555
--- /dev/null
+++ b/mailnews/mime/cthandlers/vcard/mimevcrd.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEVCRD_H_
+#define _MIMEVCRD_H_
+
+#include "mimetext.h"
+#include "nsCOMPtr.h"
+
+/* The MimeInlineTextHTML class implements the text/x-vcard and (maybe?
+ someday?) the application/directory MIME content types.
+ */
+
+typedef struct MimeInlineTextVCardClass MimeInlineTextVCardClass;
+typedef struct MimeInlineTextVCard MimeInlineTextVCard;
+
+struct MimeInlineTextVCardClass {
+ MimeInlineTextClass text;
+ char *vCardString;
+};
+
+extern MimeInlineTextVCardClass mimeInlineTextVCardClass;
+
+struct MimeInlineTextVCard {
+ MimeInlineText text;
+};
+
+#define MimeInlineTextVCardClassInitializer(ITYPE,CSUPER) \
+ { MimeInlineTextClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEVCRD_H_ */
diff --git a/mailnews/mime/cthandlers/vcard/moz.build b/mailnews/mime/cthandlers/vcard/moz.build
new file mode 100644
index 000000000..55de22391
--- /dev/null
+++ b/mailnews/mime/cthandlers/vcard/moz.build
@@ -0,0 +1,14 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'mimevcrd.cpp',
+]
+
+FINAL_LIBRARY = 'mail'
+
+LOCAL_INCLUDES += [
+ '../glue',
+]
diff --git a/mailnews/mime/emitters/moz.build b/mailnews/mime/emitters/moz.build
new file mode 100644
index 000000000..b1e3390bd
--- /dev/null
+++ b/mailnews/mime/emitters/moz.build
@@ -0,0 +1,21 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'nsMimeEmitterCID.h',
+]
+
+SOURCES += [
+ 'nsEmitterUtils.cpp',
+ 'nsMimeBaseEmitter.cpp',
+ 'nsMimeHtmlEmitter.cpp',
+ 'nsMimePlainEmitter.cpp',
+ 'nsMimeRawEmitter.cpp',
+ 'nsMimeRebuffer.cpp',
+ 'nsMimeXmlEmitter.cpp',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/mime/emitters/nsEmitterUtils.cpp b/mailnews/mime/emitters/nsEmitterUtils.cpp
new file mode 100644
index 000000000..2867596ab
--- /dev/null
+++ b/mailnews/mime/emitters/nsEmitterUtils.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "nsMailHeaders.h"
+#include "nsIMimeEmitter.h"
+#include "nsIStringBundle.h"
+#include "nsIServiceManager.h"
+#include "nsIIOService.h"
+#include "nsIURI.h"
+#include "prprf.h"
+
+
+extern "C" bool
+EmitThisHeaderForPrefSetting(int32_t dispType, const char *header)
+{
+ if (nsMimeHeaderDisplayTypes::AllHeaders == dispType)
+ return true;
+
+ if ((!header) || (!*header))
+ return false;
+
+ if (nsMimeHeaderDisplayTypes::MicroHeaders == dispType)
+ {
+ if (
+ (!strcmp(header, HEADER_SUBJECT)) ||
+ (!strcmp(header, HEADER_FROM)) ||
+ (!strcmp(header, HEADER_DATE))
+ )
+ return true;
+ else
+ return false;
+ }
+
+ if (nsMimeHeaderDisplayTypes::NormalHeaders == dispType)
+ {
+ if (
+ (!strcmp(header, HEADER_DATE)) ||
+ (!strcmp(header, HEADER_TO)) ||
+ (!strcmp(header, HEADER_SUBJECT)) ||
+ (!strcmp(header, HEADER_SENDER)) ||
+ (!strcmp(header, HEADER_RESENT_TO)) ||
+ (!strcmp(header, HEADER_RESENT_SENDER)) ||
+ (!strcmp(header, HEADER_RESENT_FROM)) ||
+ (!strcmp(header, HEADER_RESENT_CC)) ||
+ (!strcmp(header, HEADER_REPLY_TO)) ||
+ (!strcmp(header, HEADER_REFERENCES)) ||
+ (!strcmp(header, HEADER_NEWSGROUPS)) ||
+ (!strcmp(header, HEADER_MESSAGE_ID)) ||
+ (!strcmp(header, HEADER_FROM)) ||
+ (!strcmp(header, HEADER_FOLLOWUP_TO)) ||
+ (!strcmp(header, HEADER_CC)) ||
+ (!strcmp(header, HEADER_ORGANIZATION)) ||
+ (!strcmp(header, HEADER_BCC))
+ )
+ return true;
+ else
+ return false;
+ }
+
+ return true;
+}
+
diff --git a/mailnews/mime/emitters/nsEmitterUtils.h b/mailnews/mime/emitters/nsEmitterUtils.h
new file mode 100644
index 000000000..965c1e33c
--- /dev/null
+++ b/mailnews/mime/emitters/nsEmitterUtils.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsEmitterUtils_h_
+#define _nsEmitterUtils_h_
+
+#include "prmem.h"
+#include "plstr.h"
+
+extern "C" bool EmitThisHeaderForPrefSetting(int32_t dispType, const char *header);
+
+#endif // _nsEmitterUtils_h_
+
diff --git a/mailnews/mime/emitters/nsMimeBaseEmitter.cpp b/mailnews/mime/emitters/nsMimeBaseEmitter.cpp
new file mode 100644
index 000000000..3395ab1c1
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeBaseEmitter.cpp
@@ -0,0 +1,1091 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include <stdio.h>
+#include "nsMimeBaseEmitter.h"
+#include "nsMailHeaders.h"
+#include "nscore.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIServiceManager.h"
+#include "prmem.h"
+#include "nsEmitterUtils.h"
+#include "nsMimeStringResources.h"
+#include "msgCore.h"
+#include "nsIComponentManager.h"
+#include "nsEmitterUtils.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgMimeCID.h"
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include "nsIMimeHeaders.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsDateTimeFormatCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "nsTextFormatter.h"
+#include "mozilla/Services.h"
+
+static PRLogModuleInfo * gMimeEmitterLogModule = nullptr;
+
+#define MIME_HEADER_URL "chrome://messenger/locale/mimeheader.properties"
+#define MIME_URL "chrome://messenger/locale/mime.properties"
+
+NS_IMPL_ISUPPORTS(nsMimeBaseEmitter, nsIMimeEmitter, nsIInterfaceRequestor)
+
+nsMimeBaseEmitter::nsMimeBaseEmitter()
+{
+ // Initialize data output vars...
+ mFirstHeaders = true;
+
+ mBufferMgr = nullptr;
+ mTotalWritten = 0;
+ mTotalRead = 0;
+ mInputStream = nullptr;
+ mOutStream = nullptr;
+ mOutListener = nullptr;
+
+ // Display output control vars...
+ mDocHeader = false;
+ m_stringBundle = nullptr;
+ mURL = nullptr;
+ mHeaderDisplayType = nsMimeHeaderDisplayTypes::NormalHeaders;
+
+ // Setup array for attachments
+ mAttachCount = 0;
+ mAttachArray = new nsTArray<attachmentInfoType*>();
+ mCurrentAttachment = nullptr;
+
+ // Header cache...
+ mHeaderArray = new nsTArray<headerInfoType*>();
+
+ // Embedded Header Cache...
+ mEmbeddedHeaderArray = nullptr;
+
+ // HTML Header Data...
+// mHTMLHeaders = "";
+// mCharset = "";
+
+ // Init the body...
+ mBodyStarted = false;
+// mBody = "";
+
+ // This is needed for conversion of I18N Strings...
+ mUnicodeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID);
+
+ if (!gMimeEmitterLogModule)
+ gMimeEmitterLogModule = PR_NewLogModule("MIME");
+
+ // Do prefs last since we can live without this if it fails...
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (pPrefBranch)
+ pPrefBranch->GetIntPref("mail.show_headers", &mHeaderDisplayType);
+}
+
+nsMimeBaseEmitter::~nsMimeBaseEmitter(void)
+{
+ // Delete the buffer manager...
+ if (mBufferMgr)
+ {
+ delete mBufferMgr;
+ mBufferMgr = nullptr;
+ }
+
+ // Clean up the attachment array structures...
+ if (mAttachArray)
+ {
+ for (size_t i = 0; i < mAttachArray->Length(); i++)
+ {
+ attachmentInfoType *attachInfo = mAttachArray->ElementAt(i);
+ if (!attachInfo)
+ continue;
+
+ PR_FREEIF(attachInfo->contentType);
+ if (attachInfo->displayName)
+ NS_Free(attachInfo->displayName);
+ PR_FREEIF(attachInfo->urlSpec);
+ PR_FREEIF(attachInfo);
+ }
+ delete mAttachArray;
+ }
+
+ // Cleanup allocated header arrays...
+ CleanupHeaderArray(mHeaderArray);
+ mHeaderArray = nullptr;
+
+ CleanupHeaderArray(mEmbeddedHeaderArray);
+ mEmbeddedHeaderArray = nullptr;
+}
+
+NS_IMETHODIMP nsMimeBaseEmitter::GetInterface(const nsIID & aIID, void * *aInstancePtr)
+{
+ NS_ENSURE_ARG_POINTER(aInstancePtr);
+ return QueryInterface(aIID, aInstancePtr);
+}
+
+void
+nsMimeBaseEmitter::CleanupHeaderArray(nsTArray<headerInfoType*> *aArray)
+{
+ if (!aArray)
+ return;
+
+ for (size_t i = 0; i < aArray->Length(); i++)
+ {
+ headerInfoType *headerInfo = aArray->ElementAt(i);
+ if (!headerInfo)
+ continue;
+
+ PR_FREEIF(headerInfo->name);
+ PR_FREEIF(headerInfo->value);
+ PR_FREEIF(headerInfo);
+ }
+
+ delete aArray;
+}
+
+static int32_t MapHeaderNameToID(const char *header)
+{
+ // emitter passes UPPERCASE for header names
+ if (!strcmp(header, "DATE"))
+ return MIME_MHTML_DATE;
+ else if (!strcmp(header, "FROM"))
+ return MIME_MHTML_FROM;
+ else if (!strcmp(header, "SUBJECT"))
+ return MIME_MHTML_SUBJECT;
+ else if (!strcmp(header, "TO"))
+ return MIME_MHTML_TO;
+ else if (!strcmp(header, "SENDER"))
+ return MIME_MHTML_SENDER;
+ else if (!strcmp(header, "RESENT-TO"))
+ return MIME_MHTML_RESENT_TO;
+ else if (!strcmp(header, "RESENT-SENDER"))
+ return MIME_MHTML_RESENT_SENDER;
+ else if (!strcmp(header, "RESENT-FROM"))
+ return MIME_MHTML_RESENT_FROM;
+ else if (!strcmp(header, "RESENT-CC"))
+ return MIME_MHTML_RESENT_CC;
+ else if (!strcmp(header, "REPLY-TO"))
+ return MIME_MHTML_REPLY_TO;
+ else if (!strcmp(header, "REFERENCES"))
+ return MIME_MHTML_REFERENCES;
+ else if (!strcmp(header, "NEWSGROUPS"))
+ return MIME_MHTML_NEWSGROUPS;
+ else if (!strcmp(header, "MESSAGE-ID"))
+ return MIME_MHTML_MESSAGE_ID;
+ else if (!strcmp(header, "FOLLOWUP-TO"))
+ return MIME_MHTML_FOLLOWUP_TO;
+ else if (!strcmp(header, "CC"))
+ return MIME_MHTML_CC;
+ else if (!strcmp(header, "ORGANIZATION"))
+ return MIME_MHTML_ORGANIZATION;
+ else if (!strcmp(header, "BCC"))
+ return MIME_MHTML_BCC;
+
+ return 0;
+}
+
+char *
+nsMimeBaseEmitter::MimeGetStringByName(const char *aHeaderName)
+{
+ nsresult res = NS_OK;
+
+ if (!m_headerStringBundle)
+ {
+ static const char propertyURL[] = MIME_HEADER_URL;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ if (sBundleService)
+ {
+ res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(m_headerStringBundle));
+ }
+ }
+
+ if (m_headerStringBundle)
+ {
+ nsString val;
+
+ res = m_headerStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aHeaderName).get(),
+ getter_Copies(val));
+
+ if (NS_FAILED(res))
+ return nullptr;
+
+ // Here we need to return a new copy of the string
+ // This returns a UTF-8 string so the caller needs to perform a conversion
+ // if this is used as UCS-2 (e.g. cannot do nsString(utfStr);
+ //
+ return ToNewUTF8String(val);
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+char *
+nsMimeBaseEmitter::MimeGetStringByID(int32_t aID)
+{
+ nsresult res = NS_OK;
+
+ if (!m_stringBundle)
+ {
+ static const char propertyURL[] = MIME_URL;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ if (sBundleService)
+ res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(m_stringBundle));
+ }
+
+ if (m_stringBundle)
+ {
+ nsString val;
+
+ res = m_stringBundle->GetStringFromID(aID, getter_Copies(val));
+
+ if (NS_FAILED(res))
+ return nullptr;
+
+ return ToNewUTF8String(val);
+ }
+ else
+ return nullptr;
+}
+
+//
+// This will search a string bundle (eventually) to find a descriptive header
+// name to match what was found in the mail message. aHeaderName is passed in
+// in all caps and a dropback default name is provided. The caller needs to free
+// the memory returned by this function.
+//
+char *
+nsMimeBaseEmitter::LocalizeHeaderName(const char *aHeaderName, const char *aDefaultName)
+{
+ char *retVal = nullptr;
+
+ // prefer to use translated strings if not for quoting
+ if (mFormat != nsMimeOutput::nsMimeMessageQuoting &&
+ mFormat != nsMimeOutput::nsMimeMessageBodyQuoting)
+ {
+ // map name to id and get the translated string
+ int32_t id = MapHeaderNameToID(aHeaderName);
+ if (id > 0)
+ retVal = MimeGetStringByID(id);
+ }
+
+ // get the string from the other bundle (usually not translated)
+ if (!retVal)
+ retVal = MimeGetStringByName(aHeaderName);
+
+ if (retVal)
+ return retVal;
+ else
+ return strdup(aDefaultName);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// nsMimeBaseEmitter Interface
+///////////////////////////////////////////////////////////////////////////
+NS_IMETHODIMP
+nsMimeBaseEmitter::SetPipe(nsIInputStream * aInputStream, nsIOutputStream *outStream)
+{
+ mInputStream = aInputStream;
+ mOutStream = outStream;
+ return NS_OK;
+}
+
+// Note - these is setup only...you should not write
+// anything to the stream since these may be image data
+// output streams, etc...
+NS_IMETHODIMP
+nsMimeBaseEmitter::Initialize(nsIURI *url, nsIChannel * aChannel, int32_t aFormat)
+{
+ // set the url
+ mURL = url;
+ mChannel = aChannel;
+
+ // Create rebuffering object
+ delete mBufferMgr;
+ mBufferMgr = new MimeRebuffer();
+
+ // Counters for output stream
+ mTotalWritten = 0;
+ mTotalRead = 0;
+ mFormat = aFormat;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::SetOutputListener(nsIStreamListener *listener)
+{
+ mOutListener = listener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::GetOutputListener(nsIStreamListener **listener)
+{
+ NS_ENSURE_ARG_POINTER(listener);
+
+ NS_IF_ADDREF(*listener = mOutListener);
+ return NS_OK;
+}
+
+
+// Attachment handling routines
+nsresult
+nsMimeBaseEmitter::StartAttachment(const nsACString &name,
+ const char *contentType,
+ const char *url,
+ bool aIsExternalAttachment)
+{
+ // Ok, now we will setup the attachment info
+ mCurrentAttachment = (attachmentInfoType *) PR_NEWZAP(attachmentInfoType);
+ if ( (mCurrentAttachment) && mAttachArray)
+ {
+ ++mAttachCount;
+
+ mCurrentAttachment->displayName = ToNewCString(name);
+ mCurrentAttachment->urlSpec = strdup(url);
+ mCurrentAttachment->contentType = strdup(contentType);
+ mCurrentAttachment->isExternalAttachment = aIsExternalAttachment;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::EndAttachment()
+{
+ // Ok, add the attachment info to the attachment array...
+ if ( (mCurrentAttachment) && (mAttachArray) )
+ {
+ mAttachArray->AppendElement(mCurrentAttachment);
+ mCurrentAttachment = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::EndAllAttachments()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::AddAttachmentField(const char *field, const char *value)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::UtilityWrite(const char *buf)
+{
+ NS_ENSURE_ARG_POINTER(buf);
+
+ uint32_t written;
+ Write(nsDependentCString(buf), &written);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::UtilityWrite(const nsACString &buf)
+{
+ uint32_t written;
+ Write(buf, &written);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::UtilityWriteCRLF(const char *buf)
+{
+ NS_ENSURE_ARG_POINTER(buf);
+
+ uint32_t written;
+ Write(nsDependentCString(buf), &written);
+ Write(NS_LITERAL_CSTRING(CRLF), &written);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::Write(const nsACString &buf, uint32_t *amountWritten)
+{
+ unsigned int written = 0;
+ nsresult rv = NS_OK;
+ uint32_t needToWrite;
+
+#ifdef DEBUG_BenB
+ // If you want to see libmime output...
+ printf("%s", buf);
+#endif
+
+ MOZ_LOG(gMimeEmitterLogModule, mozilla::LogLevel::Info, ("%s", PromiseFlatCString(buf).get()));
+ //
+ // Make sure that the buffer we are "pushing" into has enough room
+ // for the write operation. If not, we have to buffer, return, and get
+ // it on the next time through
+ //
+ *amountWritten = 0;
+
+ needToWrite = mBufferMgr->GetSize();
+ // First, handle any old buffer data...
+ if (needToWrite > 0)
+ {
+ rv = WriteHelper(mBufferMgr->GetBuffer(), &written);
+
+ mTotalWritten += written;
+ mBufferMgr->ReduceBuffer(written);
+ *amountWritten = written;
+
+ // if we couldn't write all the old data, buffer the new data
+ // and return
+ if (mBufferMgr->GetSize() > 0)
+ {
+ mBufferMgr->IncreaseBuffer(buf);
+ return rv;
+ }
+ }
+
+
+ // if we get here, we are dealing with new data...try to write
+ // and then do the right thing...
+ rv = WriteHelper(buf, &written);
+ *amountWritten = written;
+ mTotalWritten += written;
+
+ if (written < buf.Length()) {
+ const nsACString &remainder = Substring(buf, written);
+ mBufferMgr->IncreaseBuffer(remainder);
+ }
+
+ return rv;
+}
+
+nsresult
+nsMimeBaseEmitter::WriteHelper(const nsACString &buf, uint32_t *countWritten)
+{
+ NS_ENSURE_TRUE(mOutStream, NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv = mOutStream->Write(buf.BeginReading(), buf.Length(), countWritten);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ // pipe is full, push contents of pipe to listener...
+ uint64_t avail;
+ rv = mInputStream->Available(&avail);
+ if (NS_SUCCEEDED(rv) && avail) {
+ mOutListener->OnDataAvailable(mChannel, mURL, mInputStream, 0,
+ std::min(avail, uint64_t(PR_UINT32_MAX)));
+
+ // try writing again...
+ rv = mOutStream->Write(buf.BeginReading(), buf.Length(), countWritten);
+ }
+ }
+ return rv;
+}
+
+//
+// Find a cached header! Note: Do NOT free this value!
+//
+const char *
+nsMimeBaseEmitter::GetHeaderValue(const char *aHeaderName)
+{
+ char *retVal = nullptr;
+ nsTArray<headerInfoType*> *array = mDocHeader? mHeaderArray : mEmbeddedHeaderArray;
+
+ if (!array)
+ return nullptr;
+
+ for (size_t i = 0; i < array->Length(); i++)
+ {
+ headerInfoType *headerInfo = array->ElementAt(i);
+ if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) )
+ continue;
+
+ if (!PL_strcasecmp(aHeaderName, headerInfo->name))
+ {
+ retVal = headerInfo->value;
+ break;
+ }
+ }
+
+ return retVal;
+}
+
+//
+// This is called at the start of the header block for all header information in ANY
+// AND ALL MESSAGES (yes, quoted, attached, etc...)
+//
+// NOTE: This will be called even when headers are will not follow. This is
+// to allow us to be notified of the charset of the original message. This is
+// important for forward and reply operations
+//
+NS_IMETHODIMP
+nsMimeBaseEmitter::StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID,
+ const char *outCharset)
+{
+ NS_ENSURE_ARG_POINTER(outCharset);
+
+ mDocHeader = rootMailHeader;
+
+ // If this is not the mail messages header, then we need to create
+ // the mEmbeddedHeaderArray structure for use with this internal header
+ // structure.
+ if (!mDocHeader)
+ {
+ if (mEmbeddedHeaderArray)
+ CleanupHeaderArray(mEmbeddedHeaderArray);
+
+ mEmbeddedHeaderArray = new nsTArray<headerInfoType*>();
+ NS_ENSURE_TRUE(mEmbeddedHeaderArray, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // If the main doc, check on updated character set
+ if (mDocHeader)
+ UpdateCharacterSet(outCharset);
+ CopyASCIItoUTF16(nsDependentCString(outCharset), mCharset);
+ return NS_OK;
+}
+
+// Ok, if we are here, and we have a aCharset passed in that is not
+// UTF-8 or US-ASCII, then we should tag the mChannel member with this
+// charset. This is because replying to messages with specified charset's
+// need to be tagged as that charset by default.
+//
+NS_IMETHODIMP
+nsMimeBaseEmitter::UpdateCharacterSet(const char *aCharset)
+{
+ if (aCharset)
+ {
+ nsAutoCString contentType;
+
+ if (NS_SUCCEEDED(mChannel->GetContentType(contentType)) && !contentType.IsEmpty())
+ {
+ char *cBegin = contentType.BeginWriting();
+
+ const char *cPtr = PL_strcasestr(cBegin, "charset=");
+
+ if (cPtr)
+ {
+ char *ptr = cBegin;
+ while (*ptr)
+ {
+ if ( (*ptr == ' ') || (*ptr == ';') )
+ {
+ if ((ptr + 1) >= cPtr)
+ {
+ *ptr = '\0';
+ break;
+ }
+ }
+
+ ++ptr;
+ }
+ }
+
+ // have to set content-type since it could have an embedded null byte
+ mChannel->SetContentType(nsDependentCString(cBegin));
+ if (PL_strcasecmp(aCharset, "US-ASCII") == 0) {
+ mChannel->SetContentCharset(NS_LITERAL_CSTRING("ISO-8859-1"));
+ } else {
+ mChannel->SetContentCharset(nsDependentCString(aCharset));
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//
+// This will be called for every header field regardless if it is in an
+// internal body or the outer message.
+//
+NS_IMETHODIMP
+nsMimeBaseEmitter::AddHeaderField(const char *field, const char *value)
+{
+ if ( (!field) || (!value) )
+ return NS_OK;
+
+ nsTArray<headerInfoType*> *tPtr;
+ if (mDocHeader)
+ tPtr = mHeaderArray;
+ else
+ tPtr = mEmbeddedHeaderArray;
+
+ // This is a header so we need to cache and output later.
+ // Ok, now we will setup the header info for the header array!
+ headerInfoType *ptr = (headerInfoType *) PR_NEWZAP(headerInfoType);
+ if ( (ptr) && tPtr)
+ {
+ ptr->name = strdup(field);
+ ptr->value = strdup(value);
+ tPtr->AppendElement(ptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::AddAllHeaders(const nsACString &allheaders)
+{
+ if (mDocHeader) //We want to set only the main headers of a message, not the potentially embedded one
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(mURL));
+ if (msgurl)
+ {
+ nsCOMPtr<nsIMimeHeaders> mimeHeaders = do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mimeHeaders->Initialize(allheaders);
+ msgurl->SetMimeHeaders(mimeHeaders);
+ }
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The following code is responsible for formatting headers in a manner that is
+// identical to the normal XUL output.
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsMimeBaseEmitter::GenerateDateString(const char * dateString,
+ nsACString &formattedDate,
+ bool showDateForToday)
+{
+ nsresult rv = NS_OK;
+
+ if (!mDateFormatter) {
+ mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ /**
+ * See if the user wants to have the date displayed in the senders
+ * timezone (including the timezone offset).
+ * We also evaluate the pref original_date which was introduced
+ * as makeshift in bug 118899.
+ */
+ bool displaySenderTimezone = false;
+ bool displayOriginalDate = false;
+
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> dateFormatPrefs;
+ rv = prefs->GetBranch("mailnews.display.", getter_AddRefs(dateFormatPrefs));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dateFormatPrefs->GetBoolPref("date_senders_timezone", &displaySenderTimezone);
+ dateFormatPrefs->GetBoolPref("original_date", &displayOriginalDate);
+ // migrate old pref to date_senders_timezone
+ if (displayOriginalDate && !displaySenderTimezone)
+ dateFormatPrefs->SetBoolPref("date_senders_timezone", true);
+
+ PRExplodedTime explodedMsgTime;
+
+ // Bogus date string may leave some fields uninitialized, so take precaution.
+ memset(&explodedMsgTime, 0, sizeof (PRExplodedTime));
+
+ if (PR_ParseTimeStringToExplodedTime(dateString, false, &explodedMsgTime) != PR_SUCCESS)
+ return NS_ERROR_FAILURE;
+
+ /**
+ * To determine the date format to use, comparison of current and message
+ * time has to be made. If displaying in local time, both timestamps have
+ * to be in local time. If displaying in senders time zone, leave the compare
+ * time in that time zone.
+ * Otherwise in TZ+0100 on 2009-03-12 a message from 2009-03-11T20:49-0700
+ * would be displayed as "20:49 -0700" though it in fact is not from the
+ * same day.
+ */
+ PRExplodedTime explodedCompTime;
+ if (displaySenderTimezone)
+ explodedCompTime = explodedMsgTime;
+ else
+ PR_ExplodeTime(PR_ImplodeTime(&explodedMsgTime), PR_LocalTimeParameters, &explodedCompTime);
+
+ PRExplodedTime explodedCurrentTime;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &explodedCurrentTime);
+
+ // If we want short dates, check if the message is from today, and if so
+ // only show the time (e.g. 3:15 pm).
+ nsDateFormatSelector dateFormat = kDateFormatShort;
+ if (!showDateForToday &&
+ explodedCurrentTime.tm_year == explodedCompTime.tm_year &&
+ explodedCurrentTime.tm_month == explodedCompTime.tm_month &&
+ explodedCurrentTime.tm_mday == explodedCompTime.tm_mday)
+ {
+ // same day...
+ dateFormat = kDateFormatNone;
+ }
+
+ nsAutoString formattedDateString;
+
+ rv = mDateFormatter->FormatPRExplodedTime(nullptr /* nsILocale* locale */,
+ dateFormat,
+ kTimeFormatNoSeconds,
+ &explodedCompTime,
+ formattedDateString);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ if (displaySenderTimezone)
+ {
+ // offset of local time from UTC in minutes
+ int32_t senderoffset = (explodedMsgTime.tm_params.tp_gmt_offset +
+ explodedMsgTime.tm_params.tp_dst_offset) / 60;
+ // append offset to date string
+ char16_t *tzstring =
+ nsTextFormatter::smprintf(u" %+05d",
+ (senderoffset / 60 * 100) +
+ (senderoffset % 60));
+ formattedDateString.Append(tzstring);
+ nsTextFormatter::smprintf_free(tzstring);
+ }
+
+ CopyUTF16toUTF8(formattedDateString, formattedDate);
+ }
+
+ return rv;
+}
+
+char*
+nsMimeBaseEmitter::GetLocalizedDateString(const char * dateString)
+{
+ char *i18nValue = nullptr;
+
+ bool displayOriginalDate = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ if (prefBranch)
+ prefBranch->GetBoolPref("mailnews.display.original_date",
+ &displayOriginalDate);
+
+ if (!displayOriginalDate)
+ {
+ nsAutoCString convertedDateString;
+ nsresult rv = GenerateDateString(dateString, convertedDateString, true);
+ if (NS_SUCCEEDED(rv))
+ i18nValue = strdup(convertedDateString.get());
+ else
+ i18nValue = strdup(dateString);
+ }
+ else
+ i18nValue = strdup(dateString);
+
+ return i18nValue;
+}
+
+nsresult
+nsMimeBaseEmitter::WriteHeaderFieldHTML(const char *field, const char *value)
+{
+ char *newValue = nullptr;
+ char *i18nValue = nullptr;
+
+ if ( (!field) || (!value) )
+ return NS_OK;
+
+ //
+ // This is a check to see what the pref is for header display. If
+ // We should only output stuff that corresponds with that setting.
+ //
+ if (!EmitThisHeaderForPrefSetting(mHeaderDisplayType, field))
+ return NS_OK;
+
+ //
+ // If we encounter the 'Date' header we try to convert it's value
+ // into localized format.
+ //
+ if ( strcmp(field, "Date") == 0 )
+ i18nValue = GetLocalizedDateString(value);
+ else
+ i18nValue = strdup(value);
+
+ if ( (mUnicodeConverter) && (mFormat != nsMimeOutput::nsMimeMessageSaveAs) )
+ {
+ nsCString tValue;
+
+ // we're going to need a converter to convert
+ nsresult rv = mUnicodeConverter->DecodeMimeHeaderToUTF8(
+ nsDependentCString(i18nValue), nullptr, false, true, tValue);
+ if (NS_SUCCEEDED(rv) && !tValue.IsEmpty())
+ newValue = MsgEscapeHTML(tValue.get());
+ else
+ newValue = MsgEscapeHTML(i18nValue);
+ }
+ else
+ {
+ newValue = MsgEscapeHTML(i18nValue);
+ }
+
+ free(i18nValue);
+
+ if (!newValue)
+ return NS_OK;
+
+ mHTMLHeaders.Append("<tr>");
+ mHTMLHeaders.Append("<td>");
+
+ if (mFormat == nsMimeOutput::nsMimeMessageSaveAs)
+ mHTMLHeaders.Append("<b>");
+ else
+ mHTMLHeaders.Append("<div class=\"headerdisplayname\" style=\"display:inline;\">");
+
+ // Here is where we are going to try to L10N the tagName so we will always
+ // get a field name next to an emitted header value. Note: Default will always
+ // be the name of the header itself.
+ //
+ nsCString newTagName(field);
+ newTagName.StripWhitespace();
+ ToUpperCase(newTagName);
+
+ char *l10nTagName = LocalizeHeaderName(newTagName.get(), field);
+ if ( (!l10nTagName) || (!*l10nTagName) )
+ mHTMLHeaders.Append(field);
+ else
+ {
+ mHTMLHeaders.Append(l10nTagName);
+ PR_FREEIF(l10nTagName);
+ }
+
+ mHTMLHeaders.Append(": ");
+ if (mFormat == nsMimeOutput::nsMimeMessageSaveAs)
+ mHTMLHeaders.Append("</b>");
+ else
+ mHTMLHeaders.Append("</div>");
+
+ // Now write out the actual value itself and move on!
+ //
+ mHTMLHeaders.Append(newValue);
+ mHTMLHeaders.Append("</td>");
+
+ mHTMLHeaders.Append("</tr>");
+
+ PR_FREEIF(newValue);
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::WriteHeaderFieldHTMLPrefix(const nsACString &name)
+{
+ if (
+ ( (mFormat == nsMimeOutput::nsMimeMessageSaveAs) && (mFirstHeaders) ) ||
+ ( (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) && (mFirstHeaders) )
+ )
+ /* DO NOTHING */ ; // rhp: Do nothing...leaving the conditional like this so its
+ // easier to see the logic of what is going on.
+ else {
+ mHTMLHeaders.Append("<br><fieldset class=\"mimeAttachmentHeader\">");
+ if (!name.IsEmpty()) {
+ mHTMLHeaders.Append("<legend class=\"mimeAttachmentHeaderName\">");
+ nsCString escapedName;
+ escapedName.Adopt(MsgEscapeHTML(nsCString(name).get()));
+ mHTMLHeaders.Append(escapedName);
+ mHTMLHeaders.Append("</legend>");
+ }
+ mHTMLHeaders.Append("</fieldset>");
+ }
+
+ mFirstHeaders = false;
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::WriteHeaderFieldHTMLPostfix()
+{
+ mHTMLHeaders.Append("<br>");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::WriteHTMLHeaders(const nsACString &name)
+{
+ WriteHeaderFieldHTMLPrefix(name);
+
+ // Start with the subject, from date info!
+ DumpSubjectFromDate();
+
+ // Continue with the to and cc headers
+ DumpToCC();
+
+ // Do the rest of the headers, but these will only be written if
+ // the user has the "show all headers" pref set
+ if (mHeaderDisplayType == nsMimeHeaderDisplayTypes::AllHeaders)
+ DumpRestOfHeaders();
+
+ WriteHeaderFieldHTMLPostfix();
+
+ // Now, we need to either append the headers we built up to the
+ // overall body or output to the stream.
+ UtilityWriteCRLF(mHTMLHeaders.get());
+
+ mHTMLHeaders = "";
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::DumpSubjectFromDate()
+{
+ mHTMLHeaders.Append("<table border=0 cellspacing=0 cellpadding=0 width=\"100%\" class=\"header-part1\">");
+
+ // This is the envelope information
+ OutputGenericHeader(HEADER_SUBJECT);
+ OutputGenericHeader(HEADER_FROM);
+ OutputGenericHeader(HEADER_DATE);
+
+ // If we are Quoting a message, then we should dump the To: also
+ if ( ( mFormat == nsMimeOutput::nsMimeMessageQuoting ) ||
+ ( mFormat == nsMimeOutput::nsMimeMessageBodyQuoting ) )
+ OutputGenericHeader(HEADER_TO);
+
+ mHTMLHeaders.Append("</table>");
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::DumpToCC()
+{
+ const char * toField = GetHeaderValue(HEADER_TO);
+ const char * ccField = GetHeaderValue(HEADER_CC);
+ const char * bccField = GetHeaderValue(HEADER_BCC);
+ const char * newsgroupField = GetHeaderValue(HEADER_NEWSGROUPS);
+
+ // only dump these fields if we have at least one of them! When displaying news
+ // messages that didn't have a To or Cc field, we'd always get an empty box
+ // which looked weird.
+ if (toField || ccField || bccField || newsgroupField)
+ {
+ mHTMLHeaders.Append("<table border=0 cellspacing=0 cellpadding=0 width=\"100%\" class=\"header-part2\">");
+
+ if (toField)
+ WriteHeaderFieldHTML(HEADER_TO, toField);
+ if (ccField)
+ WriteHeaderFieldHTML(HEADER_CC, ccField);
+ if (bccField)
+ WriteHeaderFieldHTML(HEADER_BCC, bccField);
+ if (newsgroupField)
+ WriteHeaderFieldHTML(HEADER_NEWSGROUPS, newsgroupField);
+
+ mHTMLHeaders.Append("</table>");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::DumpRestOfHeaders()
+{
+ nsTArray<headerInfoType*> *array = mDocHeader? mHeaderArray : mEmbeddedHeaderArray;
+
+ mHTMLHeaders.Append("<table border=0 cellspacing=0 cellpadding=0 width=\"100%\" class=\"header-part3\">");
+
+ for (size_t i = 0; i < array->Length(); i++)
+ {
+ headerInfoType *headerInfo = array->ElementAt(i);
+ if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) ||
+ (!headerInfo->value) || (!(*headerInfo->value)))
+ continue;
+
+ if ( (!PL_strcasecmp(HEADER_SUBJECT, headerInfo->name)) ||
+ (!PL_strcasecmp(HEADER_DATE, headerInfo->name)) ||
+ (!PL_strcasecmp(HEADER_FROM, headerInfo->name)) ||
+ (!PL_strcasecmp(HEADER_TO, headerInfo->name)) ||
+ (!PL_strcasecmp(HEADER_CC, headerInfo->name)) )
+ continue;
+
+ WriteHeaderFieldHTML(headerInfo->name, headerInfo->value);
+ }
+
+ mHTMLHeaders.Append("</table>");
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::OutputGenericHeader(const char *aHeaderVal)
+{
+ const char *val = GetHeaderValue(aHeaderVal);
+
+ if (val)
+ return WriteHeaderFieldHTML(aHeaderVal, val);
+
+ return NS_ERROR_FAILURE;
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+// These are the methods that should be implemented by the child class!
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+//
+// This should be implemented by the child class if special processing
+// needs to be done when the entire message is read.
+//
+NS_IMETHODIMP
+nsMimeBaseEmitter::Complete()
+{
+ // If we are here and still have data to write, we should try
+ // to flush it...if we try and fail, we should probably return
+ // an error!
+ uint32_t written;
+
+ nsresult rv = NS_OK;
+ while ( NS_SUCCEEDED(rv) && (mBufferMgr) && (mBufferMgr->GetSize() > 0))
+ rv = Write(EmptyCString(), &written);
+
+ if (mOutListener)
+ {
+ uint64_t bytesInStream = 0;
+ mozilla::DebugOnly<nsresult> rv2 = mInputStream->Available(&bytesInStream);
+ NS_ASSERTION(NS_SUCCEEDED(rv2), "Available failed");
+ if (bytesInStream)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mChannel);
+ mOutListener->OnDataAvailable(request, mURL, mInputStream, 0, std::min(bytesInStream, uint64_t(PR_UINT32_MAX)));
+ }
+ }
+
+ return NS_OK;
+}
+
+//
+// This needs to do the right thing with the stored information. It only
+// has to do the output functions, this base class will take care of the
+// memory cleanup
+//
+NS_IMETHODIMP
+nsMimeBaseEmitter::EndHeader(const nsACString &name)
+{
+ return NS_OK;
+}
+
+// body handling routines
+NS_IMETHODIMP
+nsMimeBaseEmitter::StartBody(bool bodyOnly, const char *msgID, const char *outCharset)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::WriteBody(const nsACString &buf, uint32_t *amountWritten)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::EndBody()
+{
+ return NS_OK;
+}
diff --git a/mailnews/mime/emitters/nsMimeBaseEmitter.h b/mailnews/mime/emitters/nsMimeBaseEmitter.h
new file mode 100644
index 000000000..c33bc2687
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeBaseEmitter.h
@@ -0,0 +1,147 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsMimeBaseEmitter_h_
+#define _nsMimeBaseEmitter_h_
+
+#include "prio.h"
+#include "nsIMimeEmitter.h"
+#include "nsMimeRebuffer.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsIPipe.h"
+#include "nsIStringBundle.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIMimeConverter.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDateTimeFormat.h"
+
+//
+// The base emitter will serve as the place to do all of the caching,
+// sorting, etc... of mail headers and bodies for this internally developed
+// emitter library. The other emitter classes in this file (nsMimeHTMLEmitter, etc.)
+// will only be concerned with doing output processing ONLY.
+//
+
+//
+// Used for keeping track of the attachment information...
+//
+typedef struct {
+ char *displayName;
+ char *urlSpec;
+ char *contentType;
+ bool isExternalAttachment;
+} attachmentInfoType;
+
+//
+// For header info...
+//
+typedef struct {
+ char *name;
+ char *value;
+} headerInfoType;
+
+class nsMimeBaseEmitter : public nsIMimeEmitter,
+ public nsIInterfaceRequestor
+{
+public:
+ nsMimeBaseEmitter ();
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIMIMEEMITTER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ // Utility output functions...
+ NS_IMETHOD UtilityWrite(const nsACString &buf);
+ NS_IMETHOD UtilityWriteCRLF(const char *buf);
+
+ // For string bundle usage...
+ char *MimeGetStringByName(const char *aHeaderName);
+ char *MimeGetStringByID(int32_t aID);
+ char *LocalizeHeaderName(const char *aHeaderName, const char *aDefaultName);
+
+ // For header processing...
+ const char *GetHeaderValue(const char *aHeaderName);
+
+ // To write out a stored header array as HTML
+ virtual nsresult WriteHeaderFieldHTMLPrefix(const nsACString &name);
+ virtual nsresult WriteHeaderFieldHTML(const char *field, const char *value);
+ virtual nsresult WriteHeaderFieldHTMLPostfix();
+
+protected:
+ virtual ~nsMimeBaseEmitter();
+ // Internal methods...
+ void CleanupHeaderArray(nsTArray<headerInfoType*> *aArray);
+
+ // For header output...
+ nsresult DumpSubjectFromDate();
+ nsresult DumpToCC();
+ nsresult DumpRestOfHeaders();
+ nsresult OutputGenericHeader(const char *aHeaderVal);
+
+ nsresult WriteHelper(const nsACString &buf, uint32_t *countWritten);
+
+ // For string bundle usage...
+ nsCOMPtr<nsIStringBundle> m_stringBundle; // for translated strings
+ nsCOMPtr<nsIStringBundle> m_headerStringBundle; // for non-translated header strings
+
+ // For buffer management on output
+ MimeRebuffer *mBufferMgr;
+
+ // mscott
+ // don't ref count the streams....the emitter is owned by the converter
+ // which owns these streams...
+ //
+ nsIOutputStream *mOutStream;
+ nsIInputStream *mInputStream;
+ nsIStreamListener *mOutListener;
+ nsCOMPtr<nsIChannel> mChannel;
+
+ // For gathering statistics on processing...
+ uint32_t mTotalWritten;
+ uint32_t mTotalRead;
+
+ // Output control and info...
+ bool mDocHeader; // For header determination...
+ nsIURI *mURL; // the url for the data being processed...
+ int32_t mHeaderDisplayType; // The setting for header output...
+ nsCString mHTMLHeaders; // HTML Header Data...
+
+ // For attachment processing...
+ int32_t mAttachCount;
+ nsTArray<attachmentInfoType*> *mAttachArray;
+ attachmentInfoType *mCurrentAttachment;
+
+ // For header caching...
+ nsTArray<headerInfoType*> *mHeaderArray;
+ nsTArray<headerInfoType*> *mEmbeddedHeaderArray;
+
+ // For body caching...
+ bool mBodyStarted;
+ nsCString mBody;
+ bool mFirstHeaders;
+
+ // For the format being used...
+ int32_t mFormat;
+
+ // For I18N Conversion...
+ nsCOMPtr<nsIMimeConverter> mUnicodeConverter;
+ nsString mCharset;
+ nsCOMPtr<nsIDateTimeFormat> mDateFormatter;
+ nsresult GenerateDateString(const char * dateString, nsACString& formattedDate,
+ bool showDateForToday);
+ // The caller is expected to free the result of GetLocalizedDateString
+ char* GetLocalizedDateString(const char * dateString);
+};
+
+#endif /* _nsMimeBaseEmitter_h_ */
diff --git a/mailnews/mime/emitters/nsMimeEmitterCID.h b/mailnews/mime/emitters/nsMimeEmitterCID.h
new file mode 100644
index 000000000..f2e8c6039
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeEmitterCID.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMimeEmitterCID_h__
+#define nsMimeEmitterCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+#define NS_MIME_EMITTER_CONTRACTID_PREFIX \
+ "@mozilla.org/messenger/mimeemitter;1?type="
+
+#define NS_HTML_MIME_EMITTER_CONTRACTID \
+ NS_MIME_EMITTER_CONTRACTID_PREFIX "text/html"
+// {F0A8AF16-DCCE-11d2-A411-00805F613C79}
+#define NS_HTML_MIME_EMITTER_CID \
+ { 0xf0a8af16, 0xdcce, 0x11d2, \
+ { 0xa4, 0x11, 0x0, 0x80, 0x5f, 0x61, 0x3c, 0x79 } }
+
+#define NS_XML_MIME_EMITTER_CONTRACTID \
+ NS_MIME_EMITTER_CONTRACTID_PREFIX "text/xml"
+// {977E418F-E392-11d2-A2AC-00A024A7D144}
+#define NS_XML_MIME_EMITTER_CID \
+ { 0x977e418f, 0xe392, 0x11d2, \
+ { 0xa2, 0xac, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } }
+
+#define NS_RAW_MIME_EMITTER_CONTRACTID \
+ NS_MIME_EMITTER_CONTRACTID_PREFIX "raw"
+// {F0A8AF16-DCFF-11d2-A411-00805F613C79}
+#define NS_RAW_MIME_EMITTER_CID \
+ { 0xf0a8af16, 0xdcff, 0x11d2, \
+ { 0xa4, 0x11, 0x0, 0x80, 0x5f, 0x61, 0x3c, 0x79 } }
+
+#define NS_XUL_MIME_EMITTER_CONTRACTID \
+ NS_MIME_EMITTER_CONTRACTID_PREFIX "application/vnd.mozilla.xul+xml"
+// {FAA8AF16-DCFF-11d2-A411-00805F613C19}
+#define NS_XUL_MIME_EMITTER_CID \
+ { 0xfaa8af16, 0xdcff, 0x11d2, \
+ { 0xa4, 0x11, 0x0, 0x80, 0x5f, 0x61, 0x3c, 0x19 } }
+
+#define NS_PLAIN_MIME_EMITTER_CONTRACTID \
+ NS_MIME_EMITTER_CONTRACTID_PREFIX "text/plain"
+// {E8892265-7653-46c5-A290-307F3404D0F3}
+#define NS_PLAIN_MIME_EMITTER_CID \
+ { 0xe8892265, 0x7653, 0x46c5, \
+ { 0xa2, 0x90, 0x30, 0x7f, 0x34, 0x4, 0xd0, 0xf3 } }
+
+#endif // nsMimeEmitterCID_h__
diff --git a/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp b/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp
new file mode 100644
index 000000000..d68d7f15c
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp
@@ -0,0 +1,543 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include <stdio.h>
+#include "nsMimeRebuffer.h"
+#include "nsMimeHtmlEmitter.h"
+#include "plstr.h"
+#include "nsMailHeaders.h"
+#include "nscore.h"
+#include "nsEmitterUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMimeTypes.h"
+#include "prtime.h"
+#include "prprf.h"
+#include "nsIStringEnumerator.h"
+#include "nsServiceManagerUtils.h"
+// hack: include this to fix opening news attachments.
+#include "nsINntpUrl.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgMimeCID.h"
+#include "nsMsgUtils.h"
+#include "nsAutoPtr.h"
+#include "nsINetUtil.h"
+#include "nsMemory.h"
+#include "mozilla/Services.h"
+
+#define VIEW_ALL_HEADERS 2
+
+/**
+ * A helper class to implement nsIUTF8StringEnumerator
+ */
+
+class nsMimeStringEnumerator final : public nsIUTF8StringEnumerator {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ nsMimeStringEnumerator() : mCurrentIndex(0) {}
+
+ template<class T>
+ nsCString* Append(T value) { return mValues.AppendElement(value); }
+
+protected:
+ ~nsMimeStringEnumerator() {}
+ nsTArray<nsCString> mValues;
+ uint32_t mCurrentIndex; // consumers expect first-in first-out enumeration
+};
+
+NS_IMPL_ISUPPORTS(nsMimeStringEnumerator, nsIUTF8StringEnumerator)
+
+NS_IMETHODIMP
+nsMimeStringEnumerator::HasMore(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = mCurrentIndex < mValues.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeStringEnumerator::GetNext(nsACString& result)
+{
+ if (mCurrentIndex >= mValues.Length())
+ return NS_ERROR_UNEXPECTED;
+
+ result = mValues[mCurrentIndex++];
+ return NS_OK;
+}
+
+/*
+ * nsMimeHtmlEmitter definitions....
+ */
+nsMimeHtmlDisplayEmitter::nsMimeHtmlDisplayEmitter() : nsMimeBaseEmitter()
+{
+ mFirst = true;
+ mSkipAttachment = false;
+}
+
+nsMimeHtmlDisplayEmitter::~nsMimeHtmlDisplayEmitter(void)
+{
+}
+
+nsresult nsMimeHtmlDisplayEmitter::Init()
+{
+ return NS_OK;
+}
+
+bool nsMimeHtmlDisplayEmitter::BroadCastHeadersAndAttachments()
+{
+ // try to get a header sink if there is one....
+ nsCOMPtr<nsIMsgHeaderSink> headerSink;
+ nsresult rv = GetHeaderSink(getter_AddRefs(headerSink));
+ if (NS_SUCCEEDED(rv) && headerSink && mDocHeader)
+ return true;
+ else
+ return false;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTMLPrefix(const nsACString &name)
+{
+ if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput))
+ return nsMimeBaseEmitter::WriteHeaderFieldHTMLPrefix(name);
+ else
+ return NS_OK;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTML(const char *field, const char *value)
+{
+ if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput))
+ return nsMimeBaseEmitter::WriteHeaderFieldHTML(field, value);
+ else
+ return NS_OK;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTMLPostfix()
+{
+ if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput))
+ return nsMimeBaseEmitter::WriteHeaderFieldHTMLPostfix();
+ else
+ return NS_OK;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::GetHeaderSink(nsIMsgHeaderSink ** aHeaderSink)
+{
+ nsresult rv = NS_OK;
+ if ( (mChannel) && (!mHeaderSink) )
+ {
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ if (uri)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(uri));
+ if (msgurl)
+ {
+ msgurl->GetMsgHeaderSink(getter_AddRefs(mHeaderSink));
+ if (!mHeaderSink) // if the url is not overriding the header sink, then just get the one from the msg window
+ {
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ msgurl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ msgWindow->GetMsgHeaderSink(getter_AddRefs(mHeaderSink));
+ }
+ }
+ }
+ }
+
+ *aHeaderSink = mHeaderSink;
+ NS_IF_ADDREF(*aHeaderSink);
+ return rv;
+}
+
+nsresult nsMimeHtmlDisplayEmitter::BroadcastHeaders(nsIMsgHeaderSink * aHeaderSink, int32_t aHeaderMode, bool aFromNewsgroup)
+{
+ // two string enumerators to pass out to the header sink
+ RefPtr<nsMimeStringEnumerator> headerNameEnumerator = new nsMimeStringEnumerator();
+ NS_ENSURE_TRUE(headerNameEnumerator, NS_ERROR_OUT_OF_MEMORY);
+ RefPtr<nsMimeStringEnumerator> headerValueEnumerator = new nsMimeStringEnumerator();
+ NS_ENSURE_TRUE(headerValueEnumerator, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCString extraExpandedHeaders;
+ nsTArray<nsCString> extraExpandedHeadersArray;
+ nsAutoCString convertedDateString;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (pPrefBranch)
+ {
+ pPrefBranch->GetCharPref("mailnews.headers.extraExpandedHeaders", getter_Copies(extraExpandedHeaders));
+ // todo - should make this upper case
+ if (!extraExpandedHeaders.IsEmpty())
+ {
+ ToLowerCase(extraExpandedHeaders);
+ ParseString(extraExpandedHeaders, ' ', extraExpandedHeadersArray);
+ }
+ }
+
+ for (size_t i = 0; i < mHeaderArray->Length(); i++)
+ {
+ headerInfoType * headerInfo = mHeaderArray->ElementAt(i);
+ if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) || (!headerInfo->value) || (!(*headerInfo->value)))
+ continue;
+
+ const char * headerValue = headerInfo->value;
+
+ // optimization: if we aren't in view all header view mode, we only show a small set of the total # of headers.
+ // don't waste time sending those out to the UI since the UI is going to ignore them anyway.
+ if (aHeaderMode != VIEW_ALL_HEADERS && (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer))
+ {
+ nsDependentCString headerStr(headerInfo->name);
+ if (PL_strcasecmp("to", headerInfo->name) && PL_strcasecmp("from", headerInfo->name) &&
+ PL_strcasecmp("cc", headerInfo->name) && PL_strcasecmp("newsgroups", headerInfo->name) &&
+ PL_strcasecmp("bcc", headerInfo->name) && PL_strcasecmp("followup-to", headerInfo->name) &&
+ PL_strcasecmp("reply-to", headerInfo->name) && PL_strcasecmp("subject", headerInfo->name) &&
+ PL_strcasecmp("organization", headerInfo->name) && PL_strcasecmp("user-agent", headerInfo->name) &&
+ PL_strcasecmp("content-base", headerInfo->name) && PL_strcasecmp("sender", headerInfo->name) &&
+ PL_strcasecmp("date", headerInfo->name) && PL_strcasecmp("x-mailer", headerInfo->name) &&
+ PL_strcasecmp("content-type", headerInfo->name) && PL_strcasecmp("message-id", headerInfo->name) &&
+ PL_strcasecmp("x-newsreader", headerInfo->name) && PL_strcasecmp("x-mimeole", headerInfo->name) &&
+ PL_strcasecmp("references", headerInfo->name) && PL_strcasecmp("in-reply-to", headerInfo->name) &&
+ PL_strcasecmp("list-post", headerInfo->name) && PL_strcasecmp("delivered-to", headerInfo->name) &&
+ // make headerStr lower case because IndexOf is case-sensitive
+ (!extraExpandedHeadersArray.Length() || (ToLowerCase(headerStr),
+ !extraExpandedHeadersArray.Contains(headerStr))))
+ continue;
+ }
+
+ headerNameEnumerator->Append(headerInfo->name);
+ headerValueEnumerator->Append(headerValue);
+
+ // Add a localized version of the date header if we encounter it.
+ if (!PL_strcasecmp("Date", headerInfo->name))
+ {
+ headerNameEnumerator->Append("X-Mozilla-LocalizedDate");
+ GenerateDateString(headerValue, convertedDateString, false);
+ headerValueEnumerator->Append(convertedDateString);
+ }
+ }
+
+ aHeaderSink->ProcessHeaders(headerNameEnumerator, headerValueEnumerator, aFromNewsgroup);
+ return rv;
+}
+
+NS_IMETHODIMP nsMimeHtmlDisplayEmitter::WriteHTMLHeaders(const nsACString &name)
+{
+ // if we aren't broadcasting headers OR printing...just do whatever
+ // our base class does...
+ if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)
+ {
+ return nsMimeBaseEmitter::WriteHTMLHeaders(name);
+ }
+ else if (!BroadCastHeadersAndAttachments() || !mDocHeader)
+ {
+ // This needs to be here to correct the output format if we are
+ // not going to broadcast headers to the XUL document.
+ if (mFormat == nsMimeOutput::nsMimeMessageBodyDisplay)
+ mFormat = nsMimeOutput::nsMimeMessagePrintOutput;
+
+ return nsMimeBaseEmitter::WriteHTMLHeaders(name);
+ }
+ else
+ mFirstHeaders = false;
+
+ bool bFromNewsgroups = false;
+ for (size_t j = 0; j < mHeaderArray->Length(); j++)
+ {
+ headerInfoType *headerInfo = mHeaderArray->ElementAt(j);
+ if (!(headerInfo && headerInfo->name && *headerInfo->name))
+ continue;
+
+ if (!PL_strcasecmp("Newsgroups", headerInfo->name))
+ {
+ bFromNewsgroups = true;
+ break;
+ }
+ }
+
+ // try to get a header sink if there is one....
+ nsCOMPtr<nsIMsgHeaderSink> headerSink;
+ nsresult rv = GetHeaderSink(getter_AddRefs(headerSink));
+
+ if (headerSink)
+ {
+ int32_t viewMode = 0;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (pPrefBranch)
+ rv = pPrefBranch->GetIntPref("mail.show_headers", &viewMode);
+
+ rv = BroadcastHeaders(headerSink, viewMode, bFromNewsgroups);
+ } // if header Sink
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::EndHeader(const nsACString &name)
+{
+ if (mDocHeader && (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer))
+ {
+ UtilityWriteCRLF("<html>");
+ UtilityWriteCRLF("<head>");
+
+ const char * val = GetHeaderValue(HEADER_SUBJECT); // do not free this value
+ if (val)
+ {
+ char * subject = MsgEscapeHTML(val);
+ if (subject)
+ {
+ int32_t bufLen = strlen(subject) + 16;
+ char *buf = new char[bufLen];
+ if (!buf)
+ return NS_ERROR_OUT_OF_MEMORY;
+ PR_snprintf(buf, bufLen, "<title>%s</title>", subject);
+ UtilityWriteCRLF(buf);
+ delete [] buf;
+ free(subject);
+ }
+ }
+
+ // Stylesheet info!
+ UtilityWriteCRLF("<link rel=\"important stylesheet\" href=\"chrome://messagebody/skin/messageBody.css\">");
+
+ UtilityWriteCRLF("</head>");
+ UtilityWriteCRLF("<body>");
+ }
+
+ WriteHTMLHeaders(name);
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::StartAttachment(const nsACString &name,
+ const char *contentType,
+ const char *url,
+ bool aIsExternalAttachment)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgHeaderSink> headerSink;
+ rv = GetHeaderSink(getter_AddRefs(headerSink));
+
+ if (NS_SUCCEEDED(rv) && headerSink)
+ {
+ nsCString uriString;
+
+ nsCOMPtr<nsIMsgMessageUrl> msgurl (do_QueryInterface(mURL, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ // HACK: news urls require us to use the originalSpec. Everyone
+ // else uses GetURI to get the RDF resource which describes the message.
+ nsCOMPtr<nsINntpUrl> nntpUrl (do_QueryInterface(mURL, &rv));
+ if (NS_SUCCEEDED(rv) && nntpUrl)
+ rv = msgurl->GetOriginalSpec(getter_Copies(uriString));
+ else
+ rv = msgurl->GetUri(getter_Copies(uriString));
+ }
+
+ // we need to convert the attachment name from UTF-8 to unicode before
+ // we emit it. The attachment name has already been rfc2047 processed
+ // upstream of us. (Namely, mime_decode_filename has been called, deferring
+ // to nsIMimeHeaderParam.decodeParameter.)
+ nsString unicodeHeaderValue;
+ CopyUTF8toUTF16(name, unicodeHeaderValue);
+
+ headerSink->HandleAttachment(contentType, url /* was escapedUrl */,
+ unicodeHeaderValue.get(), uriString.get(),
+ aIsExternalAttachment);
+
+ mSkipAttachment = false;
+ }
+ else if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)
+ {
+ // then we need to deal with the attachments in the body by inserting
+ // them into a table..
+ rv = StartAttachmentInBody(name, contentType, url);
+ }
+ else
+ {
+ // If we don't need or cannot broadcast attachment info, just ignore it
+ mSkipAttachment = true;
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+// Attachment handling routines
+// Ok, we are changing the way we handle these now...It used to be that we output
+// HTML to make a clickable link, etc... but now, this should just be informational
+// and only show up during printing
+// XXX should they also show up during quoting?
+nsresult
+nsMimeHtmlDisplayEmitter::StartAttachmentInBody(const nsACString &name,
+ const char *contentType,
+ const char *url)
+{
+ mSkipAttachment = false;
+ bool p7mExternal = false;
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ prefs->GetBoolPref("mailnews.p7m_external", &p7mExternal);
+
+ if ( (contentType) &&
+ ((!p7mExternal && !strcmp(contentType, APPLICATION_XPKCS7_MIME)) ||
+ (!p7mExternal && !strcmp(contentType, APPLICATION_PKCS7_MIME)) ||
+ (!strcmp(contentType, APPLICATION_XPKCS7_SIGNATURE)) ||
+ (!strcmp(contentType, APPLICATION_PKCS7_SIGNATURE)) ||
+ (!strcmp(contentType, TEXT_VCARD)))
+ )
+ {
+ mSkipAttachment = true;
+ return NS_OK;
+ }
+
+ if (mFirst)
+ {
+ UtilityWrite("<br><fieldset class=\"mimeAttachmentHeader\">");
+ if (!name.IsEmpty())
+ {
+ nsresult rv;
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString attachmentsHeader;
+ bundle->GetStringFromName(u"attachmentsPrintHeader",
+ getter_Copies(attachmentsHeader));
+
+ UtilityWrite("<legend class=\"mimeAttachmentHeaderName\">");
+ nsCString escapedName;
+ escapedName.Adopt(MsgEscapeHTML(NS_ConvertUTF16toUTF8(attachmentsHeader).get()));
+ UtilityWrite(escapedName.get());
+ UtilityWrite("</legend>");
+ }
+ UtilityWrite("</fieldset>");
+ UtilityWrite("<div class=\"mimeAttachmentWrap\">");
+ UtilityWrite("<table class=\"mimeAttachmentTable\">");
+ }
+
+ UtilityWrite("<tr>");
+
+ UtilityWrite("<td class=\"mimeAttachmentFile\">");
+ UtilityWrite(name);
+ UtilityWrite("</td>");
+
+ mFirst = false;
+ return NS_OK;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::AddAttachmentField(const char *field, const char *value)
+{
+ if (mSkipAttachment)
+ return NS_OK;
+
+ // Don't let bad things happen
+ if ( !value || !*value )
+ return NS_OK;
+
+ // Don't output this ugly header...
+ if (!strcmp(field, HEADER_X_MOZILLA_PART_URL))
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgHeaderSink> headerSink;
+ nsresult rv = GetHeaderSink(getter_AddRefs(headerSink));
+ if (NS_SUCCEEDED(rv) && headerSink)
+ {
+ headerSink->AddAttachmentField(field, value);
+ }
+ else
+ {
+ // Currently, we only care about the part size.
+ if (strcmp(field, HEADER_X_MOZILLA_PART_SIZE))
+ return NS_OK;
+
+ uint64_t size = atoi(value);
+ nsAutoString sizeString;
+ rv = FormatFileSize(size, false, sizeString);
+ UtilityWrite("<td class=\"mimeAttachmentSize\">");
+ UtilityWrite(NS_ConvertUTF16toUTF8(sizeString).get());
+ UtilityWrite("</td>");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::EndAttachment()
+{
+ if (mSkipAttachment)
+ return NS_OK;
+
+ mSkipAttachment = false; // reset it for next attachment round
+
+ if (BroadCastHeadersAndAttachments())
+ return NS_OK;
+
+ if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)
+ UtilityWrite("</tr>");
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::EndAllAttachments()
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgHeaderSink> headerSink;
+ rv = GetHeaderSink(getter_AddRefs(headerSink));
+ if (headerSink)
+ headerSink->OnEndAllAttachments();
+
+ if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)
+ {
+ UtilityWrite("</table>");
+ UtilityWrite("</div>");
+ }
+
+ return rv;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::WriteBody(const nsACString &buf,
+ uint32_t *amountWritten)
+{
+ Write(buf, amountWritten);
+ return NS_OK;
+}
+
+nsresult
+nsMimeHtmlDisplayEmitter::EndBody()
+{
+ if (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer)
+ {
+ UtilityWriteCRLF("</body>");
+ UtilityWriteCRLF("</html>");
+ }
+ nsCOMPtr<nsIMsgHeaderSink> headerSink;
+ nsresult rv = GetHeaderSink(getter_AddRefs(headerSink));
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl (do_QueryInterface(mURL, &rv));
+ if (headerSink)
+ headerSink->OnEndMsgHeaders(mailnewsUrl);
+
+ return NS_OK;
+}
+
+
diff --git a/mailnews/mime/emitters/nsMimeHtmlEmitter.h b/mailnews/mime/emitters/nsMimeHtmlEmitter.h
new file mode 100644
index 000000000..886687763
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeHtmlEmitter.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsMimeHtmlEmitter_h_
+#define _nsMimeHtmlEmitter_h_
+
+#include "mozilla/Attributes.h"
+#include "prio.h"
+#include "nsMimeBaseEmitter.h"
+#include "nsMimeRebuffer.h"
+#include "nsIStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsIMimeConverter.h"
+
+class nsMimeHtmlDisplayEmitter : public nsMimeBaseEmitter {
+public:
+ nsMimeHtmlDisplayEmitter ();
+ nsresult Init();
+
+ virtual ~nsMimeHtmlDisplayEmitter (void);
+
+ // Header handling routines.
+ NS_IMETHOD EndHeader(const nsACString &name) override;
+
+ // Attachment handling routines
+ NS_IMETHOD StartAttachment(const nsACString &name,
+ const char *contentType, const char *url,
+ bool aIsExternalAttachment) override;
+ NS_IMETHOD AddAttachmentField(const char *field, const char *value) override;
+ NS_IMETHOD EndAttachment() override;
+ NS_IMETHOD EndAllAttachments() override;
+
+ // Body handling routines
+ NS_IMETHOD WriteBody(const nsACString &buf, uint32_t *amountWritten) override;
+ NS_IMETHOD EndBody() override;
+ NS_IMETHOD WriteHTMLHeaders(const nsACString &name) override;
+
+ virtual nsresult WriteHeaderFieldHTMLPrefix(const nsACString &name
+ ) override;
+ virtual nsresult WriteHeaderFieldHTML(const char *field,
+ const char *value) override;
+ virtual nsresult WriteHeaderFieldHTMLPostfix() override;
+
+protected:
+ bool mFirst; // Attachment flag...
+ bool mSkipAttachment; // attachments we shouldn't show...
+
+ nsCOMPtr<nsIMsgHeaderSink> mHeaderSink;
+
+ nsresult GetHeaderSink(nsIMsgHeaderSink ** aHeaderSink);
+ bool BroadCastHeadersAndAttachments();
+ nsresult StartAttachmentInBody(const nsACString &name,
+ const char *contentType, const char *url);
+
+ nsresult BroadcastHeaders(nsIMsgHeaderSink * aHeaderSink, int32_t aHeaderMode, bool aFromNewsgroup);
+};
+
+
+#endif /* _nsMimeHtmlEmitter_h_ */
diff --git a/mailnews/mime/emitters/nsMimePlainEmitter.cpp b/mailnews/mime/emitters/nsMimePlainEmitter.cpp
new file mode 100644
index 000000000..8e6fae742
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimePlainEmitter.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include "nsMimeRebuffer.h"
+#include "nsMimePlainEmitter.h"
+#include "plstr.h"
+#include "nsMailHeaders.h"
+#include "nscore.h"
+#include "prmem.h"
+#include "nsEmitterUtils.h"
+#include "nsCOMPtr.h"
+#include "nsUnicharUtils.h"
+
+/*
+ * nsMimePlainEmitter definitions....
+ */
+nsMimePlainEmitter::nsMimePlainEmitter()
+{
+}
+
+
+nsMimePlainEmitter::~nsMimePlainEmitter(void)
+{
+}
+
+
+// Header handling routines.
+nsresult
+nsMimePlainEmitter::StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID,
+ const char *outCharset)
+{
+ mDocHeader = rootMailHeader;
+ return NS_OK;
+}
+
+nsresult
+nsMimePlainEmitter::AddHeaderField(const char *field, const char *value)
+{
+ if ( (!field) || (!value) )
+ return NS_OK;
+
+ UtilityWrite(field);
+ UtilityWrite(":\t");
+ UtilityWriteCRLF(value);
+ return NS_OK;
+}
+
+nsresult
+nsMimePlainEmitter::EndHeader(const nsACString &name)
+{
+ UtilityWriteCRLF("");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimePlainEmitter::WriteBody(const nsACString &buf, uint32_t *amountWritten)
+{
+ Write(buf, amountWritten);
+ return NS_OK;
+}
+
diff --git a/mailnews/mime/emitters/nsMimePlainEmitter.h b/mailnews/mime/emitters/nsMimePlainEmitter.h
new file mode 100644
index 000000000..94cc0cc47
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimePlainEmitter.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsMimePlainEmitter_h_
+#define _nsMimePlainEmitter_h_
+
+#include "mozilla/Attributes.h"
+#include "prio.h"
+#include "nsMimeBaseEmitter.h"
+#include "nsMimeRebuffer.h"
+#include "nsIStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsIURI.h"
+#include "nsIChannel.h"
+
+class nsMimePlainEmitter : public nsMimeBaseEmitter {
+public:
+ nsMimePlainEmitter ();
+ virtual ~nsMimePlainEmitter (void);
+
+ // Header handling routines.
+ NS_IMETHOD StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID,
+ const char *outCharset) override;
+ NS_IMETHOD AddHeaderField(const char *field, const char *value) override;
+ NS_IMETHOD EndHeader(const nsACString &buf) override;
+
+ NS_IMETHOD WriteBody(const nsACString &buf, uint32_t *amountWritten) override;
+};
+
+#endif /* _nsMimePlainEmitter_h_ */
diff --git a/mailnews/mime/emitters/nsMimeRawEmitter.cpp b/mailnews/mime/emitters/nsMimeRawEmitter.cpp
new file mode 100644
index 000000000..28e5f53ec
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeRawEmitter.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include <stdio.h>
+#include "nsMimeRebuffer.h"
+#include "nsMimeRawEmitter.h"
+#include "plstr.h"
+#include "nsIMimeEmitter.h"
+#include "nsMailHeaders.h"
+#include "nscore.h"
+#include "prmem.h"
+#include "nsEmitterUtils.h"
+
+/*
+ * nsMimeRawEmitter definitions....
+ */
+nsMimeRawEmitter::nsMimeRawEmitter()
+{
+}
+
+
+nsMimeRawEmitter::~nsMimeRawEmitter(void)
+{
+}
+
+NS_IMETHODIMP
+nsMimeRawEmitter::WriteBody(const nsACString &buf, uint32_t *amountWritten)
+{
+ Write(buf, amountWritten);
+ return NS_OK;
+}
+
diff --git a/mailnews/mime/emitters/nsMimeRawEmitter.h b/mailnews/mime/emitters/nsMimeRawEmitter.h
new file mode 100644
index 000000000..07542efb9
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeRawEmitter.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsMimeRawEmitter_h_
+#define _nsMimeRawEmitter_h_
+
+#include "mozilla/Attributes.h"
+#include "prio.h"
+#include "nsMimeBaseEmitter.h"
+#include "nsMimeRebuffer.h"
+#include "nsIStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsIURI.h"
+#include "nsIChannel.h"
+
+class nsMimeRawEmitter : public nsMimeBaseEmitter {
+public:
+ nsMimeRawEmitter ();
+ virtual ~nsMimeRawEmitter (void);
+
+ NS_IMETHOD WriteBody(const nsACString &buf,
+ uint32_t *amountWritten) override;
+
+protected:
+};
+
+
+#endif /* _nsMimeRawEmitter_h_ */
diff --git a/mailnews/mime/emitters/nsMimeRebuffer.cpp b/mailnews/mime/emitters/nsMimeRebuffer.cpp
new file mode 100644
index 000000000..0e68a586c
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeRebuffer.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <string.h>
+#include "nsMimeRebuffer.h"
+#include "prmem.h"
+
+MimeRebuffer::MimeRebuffer(void)
+{
+}
+
+MimeRebuffer::~MimeRebuffer(void)
+{
+}
+
+uint32_t
+MimeRebuffer::GetSize()
+{
+ return mBuf.Length();
+}
+
+uint32_t
+MimeRebuffer::IncreaseBuffer(const nsACString &addBuf)
+{
+ mBuf.Append(addBuf);
+ return mBuf.Length();
+}
+
+uint32_t
+MimeRebuffer::ReduceBuffer(uint32_t numBytes)
+{
+ if (numBytes == 0)
+ return mBuf.Length();
+
+ if (numBytes >= mBuf.Length())
+ {
+ mBuf.Truncate();
+ return 0;
+ }
+
+ mBuf.Cut(0, numBytes);
+ return mBuf.Length();
+}
+
+nsACString &
+MimeRebuffer::GetBuffer()
+{
+ return mBuf;
+}
diff --git a/mailnews/mime/emitters/nsMimeRebuffer.h b/mailnews/mime/emitters/nsMimeRebuffer.h
new file mode 100644
index 000000000..568960206
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeRebuffer.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _rebuffer_h_
+#define _rebuffer_h_
+
+#include <stdint.h>
+#include "nsStringGlue.h"
+
+//////////////////////////////////////////////////////////////
+// A rebuffering class necessary for stream output buffering
+//////////////////////////////////////////////////////////////
+
+class MimeRebuffer {
+public:
+ MimeRebuffer (void);
+ virtual ~MimeRebuffer (void);
+
+ uint32_t GetSize();
+ uint32_t IncreaseBuffer(const nsACString &addBuf);
+ uint32_t ReduceBuffer(uint32_t numBytes);
+ nsACString & GetBuffer();
+
+protected:
+ nsCString mBuf;
+};
+
+#endif /* _rebuffer_h_ */
diff --git a/mailnews/mime/emitters/nsMimeXmlEmitter.cpp b/mailnews/mime/emitters/nsMimeXmlEmitter.cpp
new file mode 100644
index 000000000..f9cd1ece2
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeXmlEmitter.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include "nsMimeRebuffer.h"
+#include "nsMimeXmlEmitter.h"
+#include "plstr.h"
+#include "nsMailHeaders.h"
+#include "nscore.h"
+#include "prmem.h"
+#include "nsEmitterUtils.h"
+#include "nsCOMPtr.h"
+#include "nsUnicharUtils.h"
+#include "nsMsgUtils.h"
+
+/*
+ * nsMimeXmlEmitter definitions....
+ */
+nsMimeXmlEmitter::nsMimeXmlEmitter()
+{
+}
+
+
+nsMimeXmlEmitter::~nsMimeXmlEmitter(void)
+{
+}
+
+
+// Note - this is teardown only...you should not write
+// anything to the stream since these may be image data
+// output streams, etc...
+nsresult
+nsMimeXmlEmitter::Complete()
+{
+ char buf[16];
+
+ // Now write out the total count of attachments for this message
+ UtilityWrite("<mailattachcount>");
+ sprintf(buf, "%d", mAttachCount);
+ UtilityWrite(buf);
+ UtilityWrite("</mailattachcount>");
+
+ UtilityWrite("</message>");
+
+ return nsMimeBaseEmitter::Complete();
+
+}
+
+nsresult
+nsMimeXmlEmitter::WriteXMLHeader(const char *msgID)
+{
+ if ( (!msgID) || (!*msgID) )
+ msgID = "none";
+
+ char *newValue = MsgEscapeHTML(msgID);
+ if (!newValue)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ UtilityWrite("<?xml version=\"1.0\"?>");
+
+ UtilityWriteCRLF("<?xml-stylesheet href=\"chrome://messagebody/skin/messageBody.css\" type=\"text/css\"?>");
+
+ UtilityWrite("<message id=\"");
+ UtilityWrite(newValue);
+ UtilityWrite("\">");
+
+ mXMLHeaderStarted = true;
+ PR_FREEIF(newValue);
+ return NS_OK;
+}
+
+nsresult
+nsMimeXmlEmitter::WriteXMLTag(const char *tagName, const char *value)
+{
+ if ( (!value) || (!*value) )
+ return NS_OK;
+
+ char *upCaseTag = NULL;
+ char *newValue = MsgEscapeHTML(value);
+ if (!newValue)
+ return NS_OK;
+
+ nsCString newTagName(tagName);
+ newTagName.StripWhitespace();
+ ToUpperCase(newTagName);
+ upCaseTag = ToNewCString(newTagName);
+
+ UtilityWrite("<header field=\"");
+ UtilityWrite(upCaseTag);
+ UtilityWrite("\">");
+
+ // Here is where we are going to try to L10N the tagName so we will always
+ // get a field name next to an emitted header value. Note: Default will always
+ // be the name of the header itself.
+ //
+ UtilityWrite("<headerdisplayname>");
+ char *l10nTagName = LocalizeHeaderName(upCaseTag, tagName);
+ if ( (!l10nTagName) || (!*l10nTagName) )
+ UtilityWrite(tagName);
+ else
+ {
+ UtilityWrite(l10nTagName);
+ }
+ PR_FREEIF(l10nTagName);
+
+ UtilityWrite(": ");
+ UtilityWrite("</headerdisplayname>");
+
+ // Now write out the actual value itself and move on!
+ //
+ UtilityWrite(newValue);
+ UtilityWrite("</header>");
+
+ NS_Free(upCaseTag);
+ PR_FREEIF(newValue);
+
+ return NS_OK;
+}
+
+// Header handling routines.
+nsresult
+nsMimeXmlEmitter::StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID,
+ const char *outCharset)
+{
+ mDocHeader = rootMailHeader;
+ WriteXMLHeader(msgID);
+ UtilityWrite("<mailheader>");
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeXmlEmitter::AddHeaderField(const char *field, const char *value)
+{
+ if ( (!field) || (!value) )
+ return NS_OK;
+
+ WriteXMLTag(field, value);
+ return NS_OK;
+}
+
+nsresult
+nsMimeXmlEmitter::EndHeader(const nsACString &name)
+{
+ UtilityWrite("</mailheader>");
+ return NS_OK;
+}
+
+
+// Attachment handling routines
+nsresult
+nsMimeXmlEmitter::StartAttachment(const nsACString &name,
+ const char *contentType,
+ const char *url,
+ bool aIsExternalAttachment)
+{
+ char buf[128];
+
+ ++mAttachCount;
+
+ sprintf(buf, "<mailattachment id=\"%d\">", mAttachCount);
+ UtilityWrite(buf);
+
+ AddAttachmentField(HEADER_PARM_FILENAME, PromiseFlatCString(name).get());
+ return NS_OK;
+}
+
+nsresult
+nsMimeXmlEmitter::AddAttachmentField(const char *field, const char *value)
+{
+ WriteXMLTag(field, value);
+ return NS_OK;
+}
+
+nsresult
+nsMimeXmlEmitter::EndAttachment()
+{
+ UtilityWrite("</mailattachment>");
+ return NS_OK;
+}
+
+
diff --git a/mailnews/mime/emitters/nsMimeXmlEmitter.h b/mailnews/mime/emitters/nsMimeXmlEmitter.h
new file mode 100644
index 000000000..f9e83948d
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeXmlEmitter.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _nsMimeXmlEmitter_h_
+#define _nsMimeXmlEmitter_h_
+
+#include "mozilla/Attributes.h"
+#include "prio.h"
+#include "nsMimeBaseEmitter.h"
+#include "nsMimeRebuffer.h"
+#include "nsIStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsIURI.h"
+#include "nsIChannel.h"
+
+class nsMimeXmlEmitter : public nsMimeBaseEmitter {
+public:
+ nsMimeXmlEmitter ();
+ virtual ~nsMimeXmlEmitter (void);
+
+ NS_IMETHOD Complete() override;
+
+ // Header handling routines.
+ NS_IMETHOD StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID,
+ const char *outCharset) override;
+ NS_IMETHOD AddHeaderField(const char *field, const char *value) override;
+ NS_IMETHOD EndHeader(const nsACString &buf) override;
+
+ // Attachment handling routines
+ NS_IMETHOD StartAttachment(const nsACString &name,
+ const char *contentType, const char *url,
+ bool aIsExternalAttachment) override;
+ NS_IMETHOD AddAttachmentField(const char *field, const char *value) override;
+ NS_IMETHOD EndAttachment() override;
+
+ NS_IMETHOD WriteXMLHeader(const char *msgID);
+ NS_IMETHOD WriteXMLTag(const char *tagName, const char *value);
+
+protected:
+
+ // For header determination...
+ bool mXMLHeaderStarted;
+ int32_t mAttachCount;
+};
+
+#endif /* _nsMimeXmlEmitter_h_ */
diff --git a/mailnews/mime/jsmime/LICENSE b/mailnews/mime/jsmime/LICENSE
new file mode 100644
index 000000000..9ddc547d9
--- /dev/null
+++ b/mailnews/mime/jsmime/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Joshua Cranmer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/mailnews/mime/jsmime/README.md b/mailnews/mime/jsmime/README.md
new file mode 100644
index 000000000..a418516ea
--- /dev/null
+++ b/mailnews/mime/jsmime/README.md
@@ -0,0 +1,59 @@
+Code Layout
+===========
+
+JSMime is a MIME parsing and composition library that is written completely in
+JavaScript using ES6 functionality and WebAPIs (where such APIs exist). There
+are a few features for which a standardized WebAPI does not exist; for these,
+external JavaScript libraries are used.
+
+The MIME parser consists of three logical phases of translation:
+
+1. Build the MIME (and pseudo-MIME) tree.
+2. Convert the MIME tree into a list of body parts and attachments.
+3. Use the result to drive a displayed version of the message.
+
+The first stage is located in `mimeparser.js`. The latter stages have yet to be
+implemented.
+
+Dependencies
+============
+
+This code depends on the following ES6 features and Web APIs:
+* ES6 generators
+* ES6 Map and Set
+* ES6 @@iterator support (especially for Map and Set)
+* ES6 let
+* ES6 let-destructuring
+* ES6 const
+* Typed arrays (predominantly Uint8Array)
+* btoa, atob (found on global Windows or WorkerScopes)
+* TextDecoder
+
+Versions and API stability
+==========================
+
+As APIs require some use and experimentation to get a feel for what works best,
+the APIs may change between successive version updates as uses indicate
+substandard or error-prone APIs. Therefore, there will be no guarantee of API
+stability until version 1.0 is released.
+
+This code is being initially developed as an effort to replace the MIME library
+within Thunderbird. New versions will be released as needed to bring new support
+into the Thunderbird codebase; version 1.0 will correspond to the version where
+feature-parity with the old MIME library is reached. The set of features which
+will be added before 1.0 are the following:
+* S/MIME encryption and decryption
+* PGP encryption and decryption
+* IMAP parts-on-demand support
+* Support for text/plain to HTML conversion for display
+* Support for HTML downgrading and sanitization for display
+* Support for all major multipart types
+* Ability to convert HTML documents to text/plain and multipart/related
+* Support for building outgoing messages
+* Support for IDN and EAI
+* yEnc and uuencode decoding support
+* Support for date and Message-ID/References-like headers
+
+Other features than these may be added before version 1.0 is released (most
+notably TNEF decoding support), but they are not considered necessary to release
+a version 1.0.
diff --git a/mailnews/mime/jsmime/jsmime.js b/mailnews/mime/jsmime/jsmime.js
new file mode 100644
index 000000000..9e0682428
--- /dev/null
+++ b/mailnews/mime/jsmime/jsmime.js
@@ -0,0 +1,3348 @@
+(function (root, fn) {
+ if (typeof define === 'function' && define.amd) {
+ define(fn);
+ } else if (typeof module !== 'undefined' && module.exports) {
+ module.exports = fn();
+ } else {
+ root.jsmime = fn();
+ }
+}(this, function() {
+ var mods = {};
+ function req(id) {
+ return mods[id.replace(/^\.\//, '')];
+ }
+
+ function def(id, fn) {
+ mods[id] = fn(req);
+ }
+def('mimeutils', function() {
+"use strict";
+
+/**
+ * Decode a quoted-printable buffer into a binary string.
+ *
+ * @param buffer {BinaryString} The string to decode.
+ * @param more {Boolean} This argument is ignored.
+ * @returns {Array(BinaryString, BinaryString)} The first element of the array
+ * is the decoded string. The second element is always the empty
+ * string.
+ */
+function decode_qp(buffer, more) {
+ // Unlike base64, quoted-printable isn't stateful across multiple lines, so
+ // there is no need to buffer input, so we can always ignore more.
+ let decoded = buffer.replace(
+ // Replace either =<hex><hex> or =<wsp>CRLF
+ /=([0-9A-F][0-9A-F]|[ \t]*(\r\n|[\r\n]|$))/gi,
+ function replace_chars(match, param) {
+ // If trailing text matches [ \t]*CRLF, drop everything, since it's a
+ // soft line break.
+ if (param.trim().length == 0)
+ return '';
+ return String.fromCharCode(parseInt(param, 16));
+ });
+ return [decoded, ''];
+}
+
+/**
+ * Decode a base64 buffer into a binary string. Unlike window.atob, the buffer
+ * may contain non-base64 characters that will be ignored.
+ *
+ * @param buffer {BinaryString} The string to decode.
+ * @param more {Boolean} If true, we expect that this function could be
+ * called again and should retain extra data. If
+ * false, we should flush all pending output.
+ * @returns {Array(BinaryString, BinaryString)} The first element of the array
+ * is the decoded string. The second element contains the data that
+ * could not be decoded and needs to be retained for the next call.
+ */
+function decode_base64(buffer, more) {
+ // Drop all non-base64 characters
+ let sanitize = buffer.replace(/[^A-Za-z0-9+\/=]/g,'');
+ // Remove harmful `=' chars in the middle.
+ sanitize = sanitize.replace(/=+([A-Za-z0-9+\/])/g, '$1');
+ // We need to encode in groups of 4 chars. If we don't have enough, leave the
+ // excess for later. If there aren't any more, drop enough to make it 4.
+ let excess = sanitize.length % 4;
+ if (excess != 0 && more)
+ buffer = sanitize.slice(-excess);
+ else
+ buffer = '';
+ sanitize = sanitize.substring(0, sanitize.length - excess);
+ // Delete all unnecessary '====' in padding.
+ sanitize = sanitize.replace(/(====)+$/g, '');
+ // Use the atob function we (ought to) have in global scope.
+ return [atob(sanitize), buffer];
+}
+
+/**
+ * Converts a binary string into a Uint8Array buffer.
+ *
+ * @param buffer {BinaryString} The string to convert.
+ * @returns {Uint8Array} The converted data.
+ */
+function stringToTypedArray(buffer) {
+ var typedarray = new Uint8Array(buffer.length);
+ for (var i = 0; i < buffer.length; i++)
+ typedarray[i] = buffer.charCodeAt(i);
+ return typedarray;
+}
+
+/**
+ * Converts a Uint8Array buffer to a binary string.
+ *
+ * @param buffer {BinaryString} The string to convert.
+ * @returns {Uint8Array} The converted data.
+ */
+function typedArrayToString(buffer) {
+ var string = '';
+ for (var i = 0; i < buffer.length; i+= 100)
+ string += String.fromCharCode.apply(undefined, buffer.subarray(i, i + 100));
+ return string;
+}
+
+/** A list of month names for Date parsing. */
+var kMonthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
+ "Sep", "Oct", "Nov", "Dec"];
+
+return {
+ decode_base64: decode_base64,
+ decode_qp: decode_qp,
+ kMonthNames: kMonthNames,
+ stringToTypedArray: stringToTypedArray,
+ typedArrayToString: typedArrayToString,
+};
+});
+/**
+ * This file implements knowledge of how to encode or decode structured headers
+ * for several key headers. It is not meant to be used externally to jsmime.
+ */
+
+def('structuredHeaders', function (require) {
+"use strict";
+
+var structuredDecoders = new Map();
+var structuredEncoders = new Map();
+var preferredSpellings = new Map();
+
+function addHeader(name, decoder, encoder) {
+ var lowerName = name.toLowerCase();
+ structuredDecoders.set(lowerName, decoder);
+ structuredEncoders.set(lowerName, encoder);
+ preferredSpellings.set(lowerName, name);
+}
+
+
+// Addressing headers: We assume that they can be specified in 1* form (this is
+// false for From, but it's close enough to the truth that it shouldn't matter).
+// There is no need to specialize the results for the header, so just pun it
+// back to parseAddressingHeader.
+function parseAddress(value) {
+ let results = [];
+ let headerparser = this;
+ return value.reduce(function (results, header) {
+ return results.concat(headerparser.parseAddressingHeader(header, true));
+ }, []);
+}
+function writeAddress(value) {
+ // Make sure the input is an array (accept a single entry)
+ if (!Array.isArray(value))
+ value = [value];
+ this.addAddresses(value);
+}
+
+// Addressing headers from RFC 5322:
+addHeader("Bcc", parseAddress, writeAddress);
+addHeader("Cc", parseAddress, writeAddress);
+addHeader("From", parseAddress, writeAddress);
+addHeader("Reply-To", parseAddress, writeAddress);
+addHeader("Resent-Bcc", parseAddress, writeAddress);
+addHeader("Resent-Cc", parseAddress, writeAddress);
+addHeader("Resent-From", parseAddress, writeAddress);
+addHeader("Resent-Reply-To", parseAddress, writeAddress);
+addHeader("Resent-Sender", parseAddress, writeAddress);
+addHeader("Resent-To", parseAddress, writeAddress);
+addHeader("Sender", parseAddress, writeAddress);
+addHeader("To", parseAddress, writeAddress);
+// From RFC 5536:
+addHeader("Approved", parseAddress, writeAddress);
+// From RFC 3798:
+addHeader("Disposition-Notification-To", parseAddress, writeAddress);
+// Non-standard headers:
+addHeader("Delivered-To", parseAddress, writeAddress);
+addHeader("Return-Receipt-To", parseAddress, writeAddress);
+
+// http://cr.yp.to/proto/replyto.html
+addHeader("Mail-Reply-To", parseAddress, writeAddress);
+addHeader("Mail-Followup-To", parseAddress, writeAddress);
+
+// Parameter-based headers. Note that all parameters are slightly different, so
+// we use slightly different variants here.
+function parseParameterHeader(value, do2231, do2047) {
+ // Only use the first header for parameters; ignore subsequent redefinitions.
+ return this.parseParameterHeader(value[0], do2231, do2047);
+}
+
+// RFC 2045
+function parseContentType(value) {
+ let params = parseParameterHeader.call(this, value, false, false);
+ let origtype = params.preSemi;
+ let parts = origtype.split('/');
+ if (parts.length != 2) {
+ // Malformed. Return to text/plain. Evil, ain't it?
+ params = new Map();
+ parts = ["text", "plain"];
+ }
+ let mediatype = parts[0].toLowerCase();
+ let subtype = parts[1].toLowerCase();
+ let type = mediatype + '/' + subtype;
+ let structure = new Map();
+ structure.mediatype = mediatype;
+ structure.subtype = subtype;
+ structure.type = type;
+ params.forEach(function (value, name) {
+ structure.set(name.toLowerCase(), value);
+ });
+ return structure;
+}
+structuredDecoders.set("Content-Type", parseContentType);
+
+// Unstructured headers (just decode RFC 2047 for the first header value)
+function parseUnstructured(values) {
+ return this.decodeRFC2047Words(values[0]);
+}
+function writeUnstructured(value) {
+ this.addUnstructured(value);
+}
+
+// Message-ID headers.
+function parseMessageID(values) {
+ // TODO: Proper parsing support for these headers is currently unsupported).
+ return this.decodeRFC2047Words(values[0]);
+}
+function writeMessageID(value) {
+ // TODO: Proper parsing support for these headers is currently unsupported).
+ this.addUnstructured(value);
+}
+
+// RFC 5322
+addHeader("Comments", parseUnstructured, writeUnstructured);
+addHeader("Keywords", parseUnstructured, writeUnstructured);
+addHeader("Subject", parseUnstructured, writeUnstructured);
+
+// RFC 2045
+addHeader("MIME-Version", parseUnstructured, writeUnstructured);
+addHeader("Content-Description", parseUnstructured, writeUnstructured);
+
+// RFC 7231
+addHeader("User-Agent", parseUnstructured, writeUnstructured);
+
+// Date headers
+function parseDate(values) { return this.parseDateHeader(values[0]); }
+function writeDate(value) { this.addDate(value); }
+
+// RFC 5322
+addHeader("Date", parseDate, writeDate);
+addHeader("Resent-Date", parseDate, writeDate);
+// RFC 5536
+addHeader("Expires", parseDate, writeDate);
+addHeader("Injection-Date", parseDate, writeDate);
+addHeader("NNTP-Posting-Date", parseDate, writeDate);
+
+// RFC 5322
+addHeader("Message-ID", parseMessageID, writeMessageID);
+addHeader("Resent-Message-ID", parseMessageID, writeMessageID);
+
+// Miscellaneous headers (those that don't fall under the above schemes):
+
+// RFC 2047
+structuredDecoders.set("Content-Transfer-Encoding", function (values) {
+ return values[0].toLowerCase();
+});
+structuredEncoders.set("Content-Transfer-Encoding", writeUnstructured);
+
+// Some clients like outlook.com send non-compliant References headers that
+// separate values using commas. Also, some clients don't separate References
+// with spaces, since these are optional according to RFC2822. So here we
+// preprocess these headers (see bug 1154521 and bug 1197686).
+function preprocessMessageIDs(values) {
+ let msgId = /<[^>]*>/g;
+ let match, ids = [];
+ while ((match = msgId.exec(values)) !== null) {
+ ids.push(match[0]);
+ }
+ return ids.join(' ');
+}
+structuredDecoders.set("References", preprocessMessageIDs);
+structuredDecoders.set("In-Reply-To", preprocessMessageIDs);
+
+return Object.freeze({
+ decoders: structuredDecoders,
+ encoders: structuredEncoders,
+ spellings: preferredSpellings,
+});
+
+});
+def('headerparser', function(require) {
+/**
+ * This file implements the structured decoding of message header fields. It is
+ * part of the same system as found in mimemimeutils.js, and occasionally makes
+ * references to globals defined in that file or other dependencies thereof. See
+ * documentation in that file for more information about external dependencies.
+ */
+
+"use strict";
+var mimeutils = require('./mimeutils');
+
+/**
+ * This is the API that we ultimately return.
+ *
+ * We define it as a global here, because we need to pass it as a |this|
+ * argument to a few functions.
+ */
+var headerparser = {};
+
+/**
+ * Tokenizes a message header into a stream of tokens as a generator.
+ *
+ * The low-level tokens are meant to be loosely correspond to the tokens as
+ * defined in RFC 5322. For reasons of saner error handling, however, the two
+ * definitions are not exactly equivalent. The tokens we emit are the following:
+ * 1. Special delimiters: Any char in the delimiters string is emitted as a
+ * string by itself. Parsing parameter headers, for example, would use ";="
+ * for the delimiter string.
+ * 2. Quoted-strings (if opt.qstring is true): A string which is surrounded by
+ * double quotes. Escapes in the string are omitted when returning.
+ * 3. Domain Literals (if opt.dliteral is true): A string which matches the
+ * dliteral construct in RFC 5322. Escapes here are NOT omitted.
+ * 4. Comments (if opt.comments is true): Comments are handled specially. In
+ * practice, decoding the comments in To headers appears to be necessary, so
+ * comments are not stripped in the output value. Instead, they are emitted
+ * as if they are a special delimiter. However, all delimiters found within a
+ * comment are returned as if they were a quoted string, so that consumers
+ * ignore delimiters within comments. If ignoring comment text completely is
+ * desired, upon seeing a "(" token, consumers should ignore all tokens until
+ * a matching ")" is found (note that comments can be nested).
+ * 5. RFC 2047 encoded-words (if opts.rfc2047 is true): These are strings which
+ * are the decoded contents of RFC 2047's =?UTF-8?Q?blah?=-style words.
+ * 6. Atoms: Atoms are defined not in the RFC 5322 sense, but rather as the
+ * longest sequence of characters that is neither whitespace nor any of the
+ * special characters above.
+ *
+ * The intended interpretation of the stream of output tokens is that they are
+ * the portions of text which can be safely wrapped in whitespace with no ill
+ * effect. The output tokens are either strings (which represent individual
+ * delimiter tokens) or instances of a class that has a customized .toString()
+ * for output (for quoted strings, atoms, domain literals, and encoded-words).
+ * Checking for a delimiter MUST use the strictly equals operator (===). For
+ * example, the proper way to call this method is as follows:
+ *
+ * for (let token of getHeaderTokens(rest, ";=", opts)) {
+ * if (token === ';') {
+ * // This represents a literal ';' in the string
+ * } else if (token === '=') {
+ * // This represents a literal '=' in the string
+ * } else {
+ * // If a ";" qstring was parsed, we fall through to here!
+ * token = token.toString();
+ * }
+ * }
+ *
+ * This method does not properly tokenize 5322 in all corner cases; however,
+ * this is equivalent in those corner cases to an older header parsing
+ * algorithm, so the algorithm should be correct for all real-world cases. The
+ * corner cases are as follows:
+ * 1. Quoted-strings and domain literals are parsed even if they are within a
+ * comment block (we effectively treat ctext as containing qstring).
+ * 2. WSP need not be between a qstring and an atom (a"b" produces two tokens,
+ * a and b). This is an error case, though.
+ * 3. Legacy comments as display names: We recognize address fields with
+ * comments, and (a) either drop them if inside addr-spec or (b) preserve
+ * them as part of the display-name if not. If the display-name is empty
+ * while the last comment is not, we assume it's the legacy form above and
+ * take the comment content as the display-name.
+ *
+ * @param {String} value The header value, post charset conversion but
+ * before RFC 2047 decoding, to be parsed.
+ * @param {String} delimiters A set of delimiters to include as individual
+ * tokens.
+ * @param {Object} opts A set of options selecting what to parse.
+ * @param {Boolean} [opts.qstring] If true, recognize quoted strings.
+ * @param {Boolean} [opts.dliteral] If true, recognize domain literals.
+ * @param {Boolean} [opts.comments] If true, recognize comments.
+ * @param {Boolean} [opts.rfc2047] If true, parse and decode RFC 2047
+ * encoded-words.
+ * @returns {(Token|String)[]} An array of Token objects (which have a toString
+ * method returning their value) or String objects
+ * (representing delimiters).
+ */
+function getHeaderTokens(value, delimiters, opts) {
+ // The array of parsed tokens. This method used to be a generator, but it
+ // appears that generators are poorly optimized in current engines, so it was
+ // converted to not be one.
+ let tokenList = [];
+
+ // Represents a non-delimiter token.
+ function Token(token) {
+ // Replace problematic characters so we don't get unexpected behavior
+ // down the line. These fall into a few categories:
+ // A) "Separator, space" (Zs),
+ // B) "Mark, Nonspacing" (Mn)
+ // C) "Other, Control" (Cc)
+ // D) "Other, Format" (Cf)
+ // Unfortuantely, no support for the needed regexp Unicode property escapes
+ // in our engine. So we need to hand-roll it. Used the regexpu tool for
+ // that: https://mothereff.in/regexpu.
+ // This should be updated regularly, to take into account new additions
+ // to the unicode standard. Last updated July 2019.
+ // For a full list of categories, see http://unicode.org/Public//5.0.0/ucd/UCD.html.
+
+ // -- case A: /\p{Zs}/u
+ // https://www.fileformat.info/info/unicode/category/Zs/list.htm
+ // https://mothereff.in/regexpu#input=/\p{Zs}/u&unicodePropertyEscape=1
+ token = token.replace(/[\xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/g, " ");
+
+ // -- case B: /\p{Mn}/u
+ // https://www.fileformat.info/info/unicode/category/Mn/list.htm
+ // https://mothereff.in/regexpu#input=/\p{Mn}/u&unicodePropertyEscape=1
+ // This is a bit more complicated as some of them could be "real", so we'll
+ // only remove the ones that are known to show as blank.
+ token = token.replace(/[\u034F\u17B4\u17B5\u180B-\u180D\uFE00-\uFE0F]/g, "");
+ // \uE0100-\uE01EF need to be written using their surrogate code point pairs
+ // until extended Unicode escapes are supported in regexps.
+ // https://www.fileformat.info/info/unicode/char/e0100/index.htm says \uDB40\uDD00.
+ // https://www.fileformat.info/info/unicode/char/e01ef/index.htm says \uDB40\uDDEF.
+ token = token.replace(/\uDB40[\uDD00-\uDDEF]/g, "");
+
+ // -- case C: /\p{Cc}/u, except Tab/LF/CR
+ // https://www.fileformat.info/info/unicode/category/Cc/list.htm
+ // https://mothereff.in/regexpu#input=/\p{Cc}/u&unicodePropertyEscape=1
+ // eslint-disable-next-line no-control-regex
+ token = token.replace(/(?![\t\n\r])[\0-\x1F\x7F-\x9F]/g, "");
+
+ // -- case D: /\p{Cf}/u
+ // https://www.fileformat.info/info/unicode/category/Cf/list.htm
+ // https://mothereff.in/regexpu#input=/\p{Cf}/u&unicodePropertyEscape=1
+ // Remove all of these except for \u0600-\u0605.
+ token = token.replace(/(?:[\xAD\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD80D[\uDC30-\uDC38]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F])/g, "");
+ // Unescape all quoted pairs. Any trailing \ is deleted.
+ this.token = token.replace(/\\(.?)/g, "$1");
+ }
+ Token.prototype.toString = function () { return this.token; };
+
+ // The start of the current token (e.g., atoms, strings)
+ let tokenStart = undefined;
+ // The set of whitespace characters, as defined by RFC 5322
+ let wsp = " \t\r\n";
+ // If we are a domain literal ([]) or a quoted string ("), this is set to the
+ // character to look for at the end.
+ let endQuote = undefined;
+ // The current depth of comments, since they can be nested. A value 0 means we
+ // are not in a comment.
+ let commentDepth = 0;
+
+ // Iterate over every character one character at a time.
+ let length = value.length;
+ for (let i = 0; i < length; i++) {
+ let ch = value[i];
+ // If we see a \, no matter what context we are in, ignore the next
+ // character.
+ if (ch == '\\') {
+ i++;
+ continue;
+ }
+
+ // If we are in a qstring or a dliteral, process the character only if it is
+ // what we are looking for to end the quote.
+ if (endQuote !== undefined) {
+ if (ch == endQuote && ch == '"') {
+ // Quoted strings don't include their delimiters.
+ let text = value.slice(tokenStart + 1, i);
+
+ // If RFC 2047 is enabled, always decode the qstring.
+ if (opts.rfc2047)
+ text = decodeRFC2047Words(text);
+
+ tokenList.push(new Token(text));
+ endQuote = undefined;
+ tokenStart = undefined;
+ } else if (ch == endQuote && ch == ']') {
+ // Domain literals include their delimiters.
+ tokenList.push(new Token(value.slice(tokenStart, i + 1)));
+ endQuote = undefined;
+ tokenStart = undefined;
+ }
+ // Avoid any further processing.
+ continue;
+ }
+
+ // If we can match the RFC 2047 encoded-word pattern, we need to decode the
+ // entire word or set of words.
+ if (opts.rfc2047 && ch == '=' && i + 1 < value.length && value[i + 1] == '?') {
+ // RFC 2047 tokens separated only by whitespace are conceptually part of
+ // the same output token, so we need to decode them all at once.
+ let encodedWordsRE = /([ \t\r\n]*=\?[^?]*\?[BbQq]\?[^?]*\?=)+/;
+ let result = encodedWordsRE.exec(value.slice(i));
+ if (result !== null) {
+ // If we were in the middle of a prior token (i.e., something like
+ // foobar=?UTF-8?Q?blah?=), yield the previous segment as a token.
+ if (tokenStart !== undefined) {
+ tokenList.push(new Token(value.slice(tokenStart, i)));
+ tokenStart = undefined;
+ }
+
+ // Find out how much we need to decode...
+ let encWordsLen = result[0].length;
+ let string = decodeRFC2047Words(value.slice(i, i + encWordsLen),
+ "UTF-8");
+ // Don't make a new Token variable, since we do not want to unescape the
+ // decoded string.
+ tokenList.push({ toString: function() { return string; }});
+
+ // Skip everything we decoded. The -1 is because we don't want to
+ // include the starting character.
+ i += encWordsLen - 1;
+ continue;
+ }
+
+ // If we are here, then we failed to match the simple 2047 encoded-word
+ // regular expression, despite the fact that it matched the =? at the
+ // beginning. Fall through and treat the text as if we aren't trying to
+ // decode RFC 2047.
+ }
+
+ // If we reach this point, we're not inside of quoted strings, domain
+ // literals, or RFC 2047 encoded-words. This means that the characters we
+ // parse are potential delimiters (unless we're in comments, where
+ // everything starts to go really wonky). Several things could happen,
+ // depending on the kind of character we read and whether or not we were in
+ // the middle of a token. The three values here tell us what we could need
+ // to do at this point:
+ // tokenIsEnding: The current character is not able to be accumulated to an
+ // atom, so we need to flush the atom if there is one.
+ // tokenIsStarting: The current character could begin an atom (or
+ // anything that requires us to mark the starting point), so we need to save
+ // the location.
+ // isSpecial: The current character is a delimiter that needs to be output.
+ let tokenIsEnding = false, tokenIsStarting = false, isSpecial = false;
+ if (wsp.includes(ch)) {
+ // Whitespace ends current tokens, doesn't emit anything.
+ tokenIsEnding = true;
+ } else if (commentDepth == 0 && delimiters.includes(ch)) {
+ // Delimiters end the current token, and need to be output. They do not
+ // apply within comments.
+ tokenIsEnding = true;
+ isSpecial = true;
+ } else if (opts.qstring && ch == '"') {
+ // Quoted strings end the last token and start a new one.
+ tokenIsEnding = true;
+ tokenIsStarting = true;
+ endQuote = ch;
+ } else if (opts.dliteral && ch == '[') {
+ // Domain literals end the last token and start a new one.
+ tokenIsEnding = true;
+ tokenIsStarting = true;
+ endQuote = ']';
+ } else if (opts.comments && ch == '(') {
+ // Comments are nested (oh joy). We only really care for the outer
+ // delimiter, though, which also ends the prior token and needs to be
+ // output if the consumer requests it.
+ commentDepth++;
+ if (commentDepth == 1) {
+ tokenIsEnding = true;
+ isSpecial = true;
+ } else {
+ tokenIsStarting = true;
+ }
+ } else if (opts.comments && ch == ')') {
+ // Comments are nested (oh joy). We only really care for the outer
+ // delimiter, though, which also ends the prior token and needs to be
+ // output if the consumer requests it.
+ if (commentDepth > 0)
+ commentDepth--;
+ if (commentDepth == 0) {
+ tokenIsEnding = true;
+ isSpecial = true;
+ } else {
+ tokenIsStarting = true;
+ }
+ } else {
+ // Not a delimiter, whitespace, comment, domain literal, or quoted string.
+ // Must be part of an atom then!
+ tokenIsStarting = true;
+ }
+
+ // If our analysis concluded that we closed an open token, and there is an
+ // open token, then yield that token.
+ if (tokenIsEnding && tokenStart !== undefined) {
+ tokenList.push(new Token(value.slice(tokenStart, i)));
+ tokenStart = undefined;
+ }
+ // If we need to output a delimiter, do so.
+ if (isSpecial)
+ tokenList.push(ch);
+ // If our analysis concluded that we could open a token, and no token is
+ // opened yet, then start the token.
+ if (tokenIsStarting && tokenStart === undefined) {
+ tokenStart = i;
+ }
+ }
+
+ // That concludes the loop! If there is a currently open token, close that
+ // token now.
+ if (tokenStart !== undefined) {
+ // Error case: a partially-open quoted string is assumed to have a trailing
+ // " character.
+ if (endQuote == '"')
+ tokenList.push(new Token(value.slice(tokenStart + 1)));
+ else
+ tokenList.push(new Token(value.slice(tokenStart)));
+ }
+
+ return tokenList;
+}
+
+/**
+ * Convert a header value into UTF-16 strings by attempting to decode as UTF-8
+ * or another legacy charset. If the header is valid UTF-8, it will be decoded
+ * as UTF-8; if it is not, the fallbackCharset will be attempted instead.
+ *
+ * @param {String} headerValue The header (as a binary string) to attempt
+ * to convert to UTF-16.
+ * @param {String} [fallbackCharset] The optional charset to try if UTF-8
+ * doesn't work.
+ * @returns {String} The UTF-16 representation of the string above.
+ */
+function convert8BitHeader(headerValue, fallbackCharset) {
+ // Only attempt to convert the headerValue if it contains non-ASCII
+ // characters.
+ if (/[\x80-\xff]/.exec(headerValue)) {
+ // First convert the value to a typed-array for TextDecoder.
+ let typedarray = mimeutils.stringToTypedArray(headerValue);
+
+ // Don't try UTF-8 as fallback (redundant), and don't try UTF-16 or UTF-32
+ // either, since they radically change header interpretation.
+ // If we have a fallback charset, we want to know if decoding will fail;
+ // otherwise, we want to replace with substitution chars.
+ let hasFallback = fallbackCharset &&
+ !fallbackCharset.toLowerCase().startsWith("utf");
+ let utf8Decoder = new TextDecoder("utf-8", {fatal: hasFallback});
+ try {
+ headerValue = utf8Decoder.decode(typedarray);
+ } catch (e) {
+ // Failed, try the fallback
+ let decoder = new TextDecoder(fallbackCharset, {fatal: false});
+ headerValue = decoder.decode(typedarray);
+ }
+ }
+ return headerValue;
+}
+
+/**
+ * Decodes all RFC 2047 encoded-words in the input string. The string does not
+ * necessarily have to contain any such words. This is useful, for example, for
+ * parsing unstructured headers.
+ *
+ * @param {String} headerValue The header which may contain RFC 2047 encoded-
+ * words.
+ * @returns {String} A full UTF-16 string with all encoded words expanded.
+ */
+function decodeRFC2047Words(headerValue) {
+ // Unfortunately, many implementations of RFC 2047 encoding are actually wrong
+ // in that they split over-long encoded words without regard for whether or
+ // not the split point is in the middle of a multibyte character. Therefore,
+ // we need to be able to handle these situations gracefully. This is done by
+ // using the decoder in streaming mode so long as the next token is another
+ // 2047 token with the same charset.
+ let lastCharset = '', currentDecoder = undefined;
+
+ /**
+ * Decode a single RFC 2047 token. This function is inline so that we can
+ * easily close over the lastCharset/currentDecoder variables, needed for
+ * handling bad RFC 2047 productions properly.
+ */
+ function decode2047Token(token, isLastToken) {
+ let tokenParts = token.split("?");
+
+ // If it's obviously not a valid token, return false immediately.
+ if (tokenParts.length != 5 || tokenParts[4] != '=')
+ return false;
+
+ // The charset parameter is defined in RFC 2231 to be charset or
+ // charset*language. We only care about the charset here, so ignore any
+ // language parameter that gets passed in.
+ let charset = tokenParts[1].split('*', 1)[0];
+ let encoding = tokenParts[2], text = tokenParts[3];
+
+ let buffer;
+ if (encoding == 'B' || encoding == 'b') {
+ // Decode base64. If there's any non-base64 data, treat the string as
+ // an illegal token.
+ if (/[^ A-Za-z0-9+\/=]/.exec(text))
+ return false;
+
+ // Decode the string
+ buffer = mimeutils.decode_base64(text, false)[0];
+ } else if (encoding == 'Q' || encoding == 'q') {
+ // Q encoding here looks a lot like quoted-printable text. The differences
+ // between quoted-printable and this are that quoted-printable allows you
+ // to quote newlines (this doesn't), while this replaces spaces with _.
+ // We can reuse the decode_qp code here, since newlines are already
+ // stripped from the header. There is one edge case that could trigger a
+ // false positive, namely when you have a single = or an = followed by
+ // whitespace at the end of the string. Such an input string is already
+ // malformed to begin with, so stripping the = and following input in that
+ // case should not be an important loss.
+ buffer = mimeutils.decode_qp(text.replace(/_/g, ' '), false)[0];
+ } else {
+ return false;
+ }
+
+ // Make the buffer be a typed array for what follows
+ let stringBuffer = buffer;
+ buffer = mimeutils.stringToTypedArray(buffer);
+
+ // If we cannot reuse the last decoder, flush out whatever remains.
+ var output = '';
+ if (charset != lastCharset && currentDecoder) {
+ output += currentDecoder.decode();
+ currentDecoder = null;
+ }
+
+ // Initialize the decoder for this token.
+ lastCharset = charset;
+ if (!currentDecoder) {
+ try {
+ currentDecoder = new TextDecoder(charset, {fatal: false});
+ } catch (e) {
+ // We don't recognize the charset, so give up.
+ return false;
+ }
+ }
+
+ // Convert this token with the buffer. Note the stream parameter--although
+ // RFC 2047 tokens aren't supposed to break in the middle of a multibyte
+ // character, a lot of software messes up and does so because it's hard not
+ // to (see headeremitter.js for exactly how hard!).
+ // We must not stream ISO-2022-JP if the buffer switches back to
+ // the ASCII state, that is, ends in "ESC(B".
+ // Also, we shouldn't do streaming on the last token.
+ let doStreaming;
+ if (isLastToken ||
+ (charset.toUpperCase() == "ISO-2022-JP" &&
+ stringBuffer.endsWith("\x1B(B")))
+ doStreaming = {stream: false};
+ else
+ doStreaming = {stream: true};
+ return output + currentDecoder.decode(buffer, doStreaming);
+ }
+
+ // The first step of decoding is to split the string into RFC 2047 and
+ // non-RFC 2047 tokens. RFC 2047 tokens look like the following:
+ // =?charset?c?text?=, where c is one of B, b, Q, and q. The split regex does
+ // some amount of semantic checking, so that malformed RFC 2047 tokens will
+ // get ignored earlier.
+ let components = headerValue.split(/(=\?[^?]*\?[BQbq]\?[^?]*\?=)/);
+
+ // Find last RFC 2047 token.
+ let lastRFC2047Index = -1;
+ for (let i = 0; i < components.length; i++) {
+ if (components[i].substring(0, 2) == "=?")
+ lastRFC2047Index = i;
+ }
+ for (let i = 0; i < components.length; i++) {
+ if (components[i].substring(0, 2) == "=?") {
+ let decoded = decode2047Token(components[i], i == lastRFC2047Index);
+ if (decoded !== false) {
+ // If 2047 decoding succeeded for this bit, rewrite the original value
+ // with the proper decoding.
+ components[i] = decoded;
+
+ // We're done processing, so continue to the next link.
+ continue;
+ }
+ } else if (/^[ \t\r\n]*$/.exec(components[i])) {
+ // Whitespace-only tokens get squashed into nothing, so 2047 tokens will
+ // be concatenated together.
+ components[i] = '';
+ continue;
+ }
+
+ // If there was stuff left over from decoding the last 2047 token, flush it
+ // out.
+ lastCharset = '';
+ if (currentDecoder) {
+ components[i] = currentDecoder.decode() + components[i];
+ currentDecoder = null;
+ }
+ }
+
+ // After the for loop, we'll have a set of decoded strings. Concatenate them
+ // together to make the return value.
+ return components.join('');
+}
+
+///////////////////////////////
+// Structured field decoders //
+///////////////////////////////
+
+/**
+ * Extract a list of addresses from a header which matches the RFC 5322
+ * address-list production, possibly doing RFC 2047 decoding along the way.
+ *
+ * The output of this method is an array of elements corresponding to the
+ * addresses and the groups in the input header. An address is represented by
+ * an object of the form:
+ * {
+ * name: The display name of the address
+ * email: The address of the object
+ * }
+ * while a group is represented by an object of the form:
+ * {
+ * name: The display name of the group
+ * group: An array of address object for members in the group.
+ * }
+ *
+ * @param {String} header The MIME header text to be parsed
+ * @param {Boolean} doRFC2047 If true, decode RFC 2047 parameters found in the
+ * header.
+ * @returns {(Address|Group)[]} An array of the addresses found in the header,
+ * where each element is of the form mentioned
+ * above.
+ */
+function parseAddressingHeader(header, doRFC2047) {
+ // Default to true
+ if (doRFC2047 === undefined)
+ doRFC2047 = true;
+
+ // The final (top-level) results list to append to.
+ let results = [];
+ // Temporary results
+ let addrlist = [];
+
+ // Build up all of the values
+ let name = '', groupName = '', localPart = '', address = '', comment = '';
+ // Indicators of current state
+ let inAngle = false, inComment = false, needsSpace = false;
+ let preserveSpace = false;
+ let commentClosed = false;
+
+ // RFC 5322 §3.4 notes that legacy implementations exist which use a simple
+ // recipient form where the addr-spec appears without the angle brackets,
+ // but includes the name of the recipient in parentheses as a comment
+ // following the addr-spec. While we do not create this format, we still
+ // want to recognize it, though.
+ // Furthermore, despite allowing comments in addresses, RFC 5322 §3.4 notes
+ // that legacy implementations may interpret the comment, and thus it
+ // recommends not to use them. (Also, they may be illegal as per RFC 5321.)
+ // While we do not create address fields with comments, we recognize such
+ // comments during parsing and (a) either drop them if inside addr-spec or
+ // (b) preserve them as part of the display-name if not.
+ // If the display-name is empty while the last comment is not, we assume it's
+ // the legacy form above and take the comment content as the display-name.
+ //
+ // When parsing the address field, we at first do not know whether any
+ // strings belong to the display-name (which may include comments) or to the
+ // local-part of an addr-spec (where we ignore comments) until we find an
+ // '@' or an '<' token. Thus, we collect both variants until the fog lifts,
+ // plus the last comment seen.
+ let lastComment = '';
+
+ /**
+ * Add the parsed mailbox object to the address list.
+ * If it's in the legacy form above, correct the display-name.
+ * Also reset any faked flags.
+ * @param {String} displayName display-name as per RFC 5322
+ * @param {String} addrSpec addr-spec as per RFC 5322
+ */
+ function addToAddrList(displayName, addrSpec) {
+ // Keep the local-part quoted if it needs to be.
+ let lp = addrSpec.substring(0, addrSpec.lastIndexOf("@"));
+ if (/[ !()<>\[\]:;@\\,"]/.exec(lp) !== null) {
+ addrSpec = '"' + lp.replace(/([\\"])/g, "\\$1") + '"' +
+ addrSpec.substring(addrSpec.lastIndexOf("@"));
+ }
+
+ // Replace all whitespace characters with a single whitespace,
+ // to avoid consecutive whitespace and also to normalize tabs and newlines.
+ displayName = displayName.replace(/\s+/g, " ").trim();
+
+ if (displayName === '' && lastComment !== '') {
+ // Take last comment content as the display-name.
+ let offset = lastComment[0] === ' ' ? 2 : 1;
+ displayName = lastComment.substr(offset, lastComment.length - offset - 1);
+ }
+ if (displayName !== '' || addrSpec !== '') {
+ addrlist.push({name: displayName, email: addrSpec});
+ }
+ // Clear pending flags and variables.
+ name = localPart = address = lastComment = '';
+ inAngle = inComment = needsSpace = false;
+ }
+
+ // Main parsing loop
+ for (let token of getHeaderTokens(header, ":,;<>@",
+ {qstring: true, comments: true, dliteral: true, rfc2047: doRFC2047})) {
+ if (token === ':') {
+ groupName = name;
+ name = '';
+ localPart = '';
+ // If we had prior email address results, commit them to the top-level.
+ if (addrlist.length > 0)
+ results = results.concat(addrlist);
+ addrlist = [];
+ } else if (token === '<') {
+ if (inAngle) {
+ // Interpret the address we were parsing as a name.
+ if (address.length > 0) {
+ name = address;
+ }
+ localPart = address = '';
+ } else {
+ inAngle = true;
+ }
+ } else if (token === '>') {
+ inAngle = false;
+ // Forget addr-spec comments.
+ lastComment = '';
+ } else if (token === '(') {
+ inComment = true;
+ // The needsSpace flag may not always be set even if it should be,
+ // e.g. for a comment behind an angle-addr.
+ // Also, we need to restore the needsSpace flag if we ignore the comment.
+ preserveSpace = needsSpace;
+ if (!needsSpace)
+ needsSpace = name !== '' && name.substr(-1) !== ' ';
+ comment = needsSpace ? ' (' : '(';
+ commentClosed = false;
+ } else if (token === ')') {
+ inComment = false;
+ comment += ')';
+ lastComment = comment;
+ // The comment may be part of the name, but not of the local-part.
+ // Enforce a space behind the comment only when not ignoring it.
+ if (inAngle) {
+ needsSpace = preserveSpace;
+ } else {
+ name += comment;
+ needsSpace = true;
+ }
+ commentClosed = true;
+ continue;
+ } else if (token === '@') {
+ // An @ means we see an email address. If we're not within <> brackets,
+ // then we just parsed an email address instead of a display name. Empty
+ // out the display name for the current production.
+ if (!inAngle) {
+ address = localPart;
+ name = '';
+ localPart = '';
+ // The remainder of this mailbox is part of an addr-spec.
+ inAngle = true;
+ }
+ address += '@';
+ } else if (token === ',') {
+ // A comma ends the current name. If we have something that's kind of a
+ // name, add it to the result list. If we don't, then our input looks like
+ // To: , , -> don't bother adding an empty entry.
+ addToAddrList(name, address);
+ } else if (token === ';') {
+ // Add pending name to the list
+ addToAddrList(name, address);
+
+ // If no group name was found, treat the ';' as a ','. In any case, we
+ // need to copy the results of addrlist into either a new group object or
+ // the main list.
+ if (groupName === '') {
+ results = results.concat(addrlist);
+ } else {
+ results.push({
+ name: groupName,
+ group: addrlist
+ });
+ }
+ // ... and reset every other variable.
+ addrlist = [];
+ groupName = '';
+ } else {
+ // This is either comment content, a quoted-string, or some span of
+ // dots and atoms.
+
+ // Ignore the needs space if we're a "close" delimiter token.
+ let spacedToken = token;
+ if (needsSpace && (token.toString().length > 0) && token.toString()[0] != ".")
+ spacedToken = ' ' + spacedToken;
+
+ // Which field do we add this data to?
+ if (inComment) {
+ comment += spacedToken;
+ } else if (inAngle) {
+ address += spacedToken;
+ } else {
+ name += spacedToken;
+ // Never add a space to the local-part, if we just ignored a comment.
+ if (commentClosed) {
+ localPart += token;
+ commentClosed = false;
+ } else {
+ localPart += spacedToken;
+ }
+ }
+
+ // We need space for the next token if we aren't some kind of comment or
+ // . delimiter.
+ needsSpace = token.toString()[0] != '.';
+ // The fall-through case after this resets needsSpace to false, and we
+ // don't want that!
+ continue;
+ }
+
+ // If we just parsed a delimiter, we don't need any space for the next
+ // token.
+ needsSpace = false;
+ }
+
+ // If we're missing the final ';' of a group, assume it was present. Also, add
+ // in the details of any email/address that we previously saw.
+ addToAddrList(name, address);
+ if (groupName !== '') {
+ results.push({name: groupName, group: addrlist});
+ addrlist = [];
+ }
+
+ // Add the current address list build-up to the list of addresses, and return
+ // the whole array to the caller.
+ return results.concat(addrlist);
+}
+
+/**
+ * Extract parameters from a header which is a series of ;-separated
+ * attribute=value tokens.
+ *
+ * @param {String} headerValue The MIME header value to parse.
+ * @param {Boolean} doRFC2047 If true, decode RFC 2047 encoded-words.
+ * @param {Boolean} doRFC2231 If true, decode RFC 2231 encoded parameters.
+ * @return {Map(String -> String)} A map of parameter names to parameter values.
+ * The property preSemi is set to the token that
+ * precedes the first semicolon.
+ */
+function parseParameterHeader(headerValue, doRFC2047, doRFC2231) {
+ // The basic syntax of headerValue is token [; token = token-or-qstring]*
+ // Copying more or less liberally from nsMIMEHeaderParamImpl:
+ // The first token is the text to the first whitespace or semicolon.
+ var semi = headerValue.indexOf(";");
+ if (semi < 0) {
+ var start = headerValue;
+ var rest = '';
+ } else {
+ var start = headerValue.substring(0, semi);
+ var rest = headerValue.substring(semi); // Include the semicolon
+ }
+ // Strip start to be <WSP><nowsp><WSP>.
+ start = start.trim().split(/[ \t\r\n]/)[0];
+
+ // Decode the the parameter tokens.
+ let opts = {qstring: true, rfc2047: doRFC2047};
+ // Name is the name of the parameter, inName is true iff we don't have a name
+ // yet.
+ let name = '', inName = true;
+ // Matches is a list of [name, value] pairs, where we found something that
+ // looks like name=value in the input string.
+ let matches = [];
+ for (let token of getHeaderTokens(rest, ";=", opts)) {
+ if (token === ';') {
+ // If we didn't find a name yet (we have ... tokenA; tokenB), push the
+ // name with an empty token instead.
+ if (name != '' && inName == false)
+ matches.push([name, '']);
+ name = '';
+ inName = true;
+ } else if (token === '=') {
+ inName = false;
+ } else if (inName && name == '') {
+ name = token.toString();
+ } else if (!inName && name != '') {
+ token = token.toString();
+ // RFC 2231 doesn't make it clear if %-encoding is supposed to happen
+ // within a quoted string, but this is very much required in practice. If
+ // it ends with a '*', then the string is an extended-value, which means
+ // that its value may be %-encoded.
+ if (doRFC2231 && name.endsWith('*')) {
+ token = token.replace(/%([0-9A-Fa-f]{2})/g,
+ function percent_deencode(match, hexchars) {
+ return String.fromCharCode(parseInt(hexchars, 16));
+ });
+ }
+ matches.push([name, token]);
+ // Clear the name, so we ignore anything afterwards.
+ name = '';
+ } else if (inName) {
+ // We have ...; tokenA tokenB ... -> ignore both tokens
+ name = ''; // Error recovery, ignore this one
+ }
+ }
+ // If we have a leftover ...; tokenA, push the tokenA
+ if (name != '' && inName == false)
+ matches.push([name, '']);
+
+ // Now matches holds the parameters, so clean up for RFC 2231. There are three
+ // cases: param=val, param*=us-ascii'en-US'blah, and param*n= variants. The
+ // order of preference is to pick the middle, then the last, then the first.
+ // Note that we already unpacked %-encoded values.
+
+ // simpleValues is just a straight parameter -> value map.
+ // charsetValues is the parameter -> value map, although values are stored
+ // before charset decoding happens.
+ // continuationValues maps parameter -> array of values, with extra properties
+ // valid (if we decided we couldn't do anything anymore) and hasCharset (which
+ // records if we need to decode the charset parameter or not).
+ var simpleValues = new Map(), charsetValues = new Map(),
+ continuationValues = new Map();
+ for (let pair of matches) {
+ let name = pair[0];
+ let value = pair[1];
+ // Get first index, not last index, so we match param*0*= like param*0=.
+ let star = name.indexOf('*');
+ if (star == -1) {
+ // This is the case of param=val. Select the first value here, if there
+ // are multiple ones.
+ if (!simpleValues.has(name))
+ simpleValues.set(name, value);
+ } else if (star == name.length - 1) {
+ // This is the case of param*=us-ascii'en-US'blah.
+ name = name.substring(0, star);
+ // Again, select only the first value here.
+ if (!charsetValues.has(name))
+ charsetValues.set(name, value);
+ } else {
+ // This is the case of param*0= or param*0*=.
+ let param = name.substring(0, star);
+ let entry = continuationValues.get(param);
+ // Did we previously find this one to be bungled? Then ignore it.
+ if (continuationValues.has(param) && !entry.valid)
+ continue;
+
+ // If we haven't seen it yet, set up entry already. Note that entries are
+ // not straight string values but rather [valid, hasCharset, param0, ... ]
+ if (!continuationValues.has(param)) {
+ entry = new Array();
+ entry.valid = true;
+ entry.hasCharset = undefined;
+ continuationValues.set(param, entry);
+ }
+
+ // When the string ends in *, we need to charset decoding.
+ // Note that the star is only meaningful for the *0*= case.
+ let lastStar = name[name.length - 1] == '*';
+ let number = name.substring(star + 1, name.length - (lastStar ? 1 : 0));
+ if (number == '0')
+ entry.hasCharset = lastStar;
+
+ // Is the continuation number illegal?
+ else if ((number[0] == '0' && number != '0') ||
+ !(/^[0-9]+$/.test(number))) {
+ entry.valid = false;
+ continue;
+ }
+ // Normalize to an integer
+ number = parseInt(number, 10);
+
+ // Is this a repeat? If so, bail.
+ if (entry[number] !== undefined) {
+ entry.valid = false;
+ continue;
+ }
+
+ // Set the value for this continuation index. JS's magic array setter will
+ // expand the array if necessary.
+ entry[number] = value;
+ }
+ }
+
+ // Build the actual parameter array from the parsed values
+ var values = new Map();
+ // Simple values have lowest priority, so just add everything into the result
+ // now.
+ for (let pair of simpleValues) {
+ values.set(pair[0], pair[1]);
+ }
+
+ if (doRFC2231) {
+ // Continuation values come next
+ for (let pair of continuationValues) {
+ let name = pair[0];
+ let entry = pair[1];
+ // If we never saw a param*0= or param*0*= value, then we can't do any
+ // reasoning about what it looks like, so bail out now.
+ if (entry.hasCharset === undefined) continue;
+
+ // Use as many entries in the array as are valid--if we are missing an
+ // entry, stop there.
+ let valid = true;
+ for (var i = 0; valid && i < entry.length; i++)
+ if (entry[i] === undefined)
+ valid = false;
+
+ // Concatenate as many parameters as are valid. If we need to decode thec
+ // charset, do so now.
+ var value = entry.slice(0, i).join('');
+ if (entry.hasCharset) {
+ try {
+ value = decode2231Value(value);
+ } catch (e) {
+ // Bad charset, don't add anything.
+ continue;
+ }
+ }
+ // Finally, add this to the output array.
+ values.set(name, value);
+ }
+
+ // Highest priority is the charset conversion.
+ for (let pair of charsetValues) {
+ try {
+ values.set(pair[0], decode2231Value(pair[1]));
+ } catch (e) {
+ // Bad charset, don't add anything.
+ }
+ }
+ }
+
+ // Finally, return the values computed above.
+ values.preSemi = start;
+ return values;
+}
+
+/**
+ * Convert a RFC 2231-encoded string parameter into a Unicode version of the
+ * string. This assumes that percent-decoding has already been applied.
+ *
+ * @param {String} value The RFC 2231-encoded string to decode.
+ * @return The Unicode version of the string.
+ */
+function decode2231Value(value) {
+ let quote1 = value.indexOf("'");
+ let quote2 = quote1 >= 0 ? value.indexOf("'", quote1 + 1) : -1;
+
+ let charset = (quote1 >= 0 ? value.substring(0, quote1) : "");
+ // It turns out that the language isn't useful anywhere in our codebase for
+ // the present time, so we will safely ignore it.
+ //var language = (quote2 >= 0 ? value.substring(quote1 + 2, quote2) : "");
+ value = value.substring(Math.max(quote1, quote2) + 1);
+
+ // Convert the value into a typed array for decoding
+ let typedarray = mimeutils.stringToTypedArray(value);
+
+ // Decode the charset. If the charset isn't found, we throw an error. Try to
+ // fallback in that case.
+ return new TextDecoder(charset, {fatal: true})
+ .decode(typedarray, {stream: false});
+}
+
+// This is a map of known timezone abbreviations, for fallback in obsolete Date
+// productions.
+var kKnownTZs = {
+ // The following timezones are explicitly listed in RFC 5322.
+ "UT": "+0000", "GMT": "+0000",
+ "EST": "-0500", "EDT": "-0400",
+ "CST": "-0600", "CDT": "-0500",
+ "MST": "-0700", "MDT": "-0600",
+ "PST": "-0800", "PDT": "-0700",
+ // The following are time zones copied from NSPR's prtime.c
+ "AST": "-0400", // Atlantic Standard Time
+ "NST": "-0330", // Newfoundland Standard Time
+ "BST": "+0100", // British Summer Time
+ "MET": "+0100", // Middle Europe Time
+ "EET": "+0200", // Eastern Europe Time
+ "JST": "+0900" // Japan Standard Time
+};
+
+/**
+ * Parse a header that contains a date-time definition according to RFC 5322.
+ * The result is a JS date object with the same timestamp as the header.
+ *
+ * The dates returned by this parser cannot be reliably converted back into the
+ * original header for two reasons. First, JS date objects cannot retain the
+ * timezone information they were initialized with, so reserializing a date
+ * header would necessarily produce a date in either the current timezone or in
+ * UTC. Second, JS dates measure time as seconds elapsed from the POSIX epoch
+ * excluding leap seconds. Any timestamp containing a leap second is instead
+ * converted into one that represents the next second.
+ *
+ * Dates that do not match the RFC 5322 production are instead attempted to
+ * parse using the Date.parse function. The strings that are accepted by
+ * Date.parse are not fully defined by the standard, but most implementations
+ * should accept strings that look rather close to RFC 5322 strings. Truly
+ * invalid dates produce a formulation that results in an invalid date,
+ * detectable by having its .getTime() method return NaN.
+ *
+ * @param {String} header The MIME header value to parse.
+ * @returns {Date} The date contained within the header, as described
+ * above.
+ */
+function parseDateHeader(header) {
+ let tokens = getHeaderTokens(header, ",:", {}).map(x => x.toString());
+ // What does a Date header look like? In practice, most date headers devolve
+ // into Date: [dow ,] dom mon year hh:mm:ss tzoff [(abbrev)], with the day of
+ // week mostly present and the timezone abbreviation mostly absent.
+
+ // First, ignore the day-of-the-week if present. This would be the first two
+ // tokens.
+ if (tokens.length > 1 && tokens[1] === ',')
+ tokens = tokens.slice(2);
+
+ // If there are too few tokens, the date is obviously invalid.
+ if (tokens.length < 8)
+ return new Date(NaN);
+
+ // Save off the numeric tokens
+ let day = parseInt(tokens[0]);
+ // month is tokens[1]
+ let year = parseInt(tokens[2]);
+ let hours = parseInt(tokens[3]);
+ // tokens[4] === ':'
+ let minutes = parseInt(tokens[5]);
+ // tokens[6] === ':'
+ let seconds = parseInt(tokens[7]);
+
+ // Compute the month. Check only the first three digits for equality; this
+ // allows us to accept, e.g., "January" in lieu of "Jan."
+ let month = mimeutils.kMonthNames.indexOf(tokens[1].slice(0, 3));
+ // If the month name is not recognized, make the result illegal.
+ if (month < 0)
+ month = NaN;
+
+ // Compute the full year if it's only 2 digits. RFC 5322 states that the
+ // cutoff is 50 instead of 70.
+ if (year < 100) {
+ year += year < 50 ? 2000 : 1900;
+ }
+
+ // Compute the timezone offset. If it's not in the form ±hhmm, convert it to
+ // that form.
+ let tzoffset = tokens[8];
+ if (tzoffset in kKnownTZs)
+ tzoffset = kKnownTZs[tzoffset];
+ let decompose = /^([+-])(\d\d)(\d\d)$/.exec(tzoffset);
+ // Unknown? Make it +0000
+ if (decompose === null)
+ decompose = ['+0000', '+', '00', '00'];
+ let tzOffsetInMin = parseInt(decompose[2]) * 60 + parseInt(decompose[3]);
+ if (decompose[1] == '-')
+ tzOffsetInMin = -tzOffsetInMin;
+
+ // How do we make the date at this point? Well, the JS date's constructor
+ // builds the time in terms of the local timezone. To account for the offset
+ // properly, we need to build in UTC.
+ let finalDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds)
+ - tzOffsetInMin * 60 * 1000);
+
+ // Suppose our header was mangled and we couldn't read it--some of the fields
+ // became undefined. In that case, the date would become invalid, and the
+ // indication that it is so is that the underlying number is a NaN. In that
+ // scenario, we could build attempt to use JS Date parsing as a last-ditch
+ // attempt. But it's not clear that such messages really exist in practice,
+ // and the valid formats for Date in ES6 are unspecified.
+ return finalDate;
+}
+
+////////////////////////////////////////
+// Structured header decoding support //
+////////////////////////////////////////
+
+// Load the default structured decoders
+var structuredDecoders = new Map();
+var structuredHeaders = require('./structuredHeaders');
+var preferredSpellings = structuredHeaders.spellings;
+var forbiddenHeaders = new Set();
+for (let pair of structuredHeaders.decoders) {
+ addStructuredDecoder(pair[0], pair[1]);
+ forbiddenHeaders.add(pair[0].toLowerCase());
+}
+
+/**
+ * Use an already-registered structured decoder to parse the value of the header
+ * into a structured representation.
+ *
+ * As this method is designed to be used for the internal MIME Parser to convert
+ * the raw header values to well-structured values, value is intended to be an
+ * array consisting of all occurences of the header in order. However, for ease
+ * of use by other callers, it can also be treated as a string.
+ *
+ * If the decoder for the header is not found, an exception will be thrown.
+ *
+ * A large set of headers have pre-defined structured decoders; these decoders
+ * cannot be overrided with addStructuredDecoder, as doing so could prevent the
+ * MIME or message parsers from working properly. The pre-defined structured
+ * headers break down into five clases of results, plus some ad-hoc
+ * representations. They are:
+ *
+ * Addressing headers (results are the same as parseAddressingHeader):
+ * - Approved
+ * - Bcc
+ * - Cc
+ * - Delivered-To
+ * - Disposition-Notification-To
+ * - From
+ * - Mail-Reply-To
+ * - Mail-Followup-To
+ * - Reply-To
+ * - Resent-Bcc
+ * - Resent-Cc
+ * - Resent-From
+ * - Resent-Reply-To
+ * - Resent-Sender
+ * - Resent-To
+ * - Return-Receipt-To
+ * - Sender
+ * - To
+ *
+ * Date headers (results are the same as parseDateHeader):
+ * - Date
+ * - Expires
+ * - Injection-Date
+ * - NNTP-Posting-Date
+ * - Resent-Date
+ *
+ * References headers (results are the same as parseReferencesHeader):
+ * - (TODO: Parsing support for these headers is currently unsupported)
+ *
+ * Message-ID headers (results are the first entry of the result of
+ * parseReferencesHeader):
+ * - (TODO: Parsing support for these headers is currently unsupported)
+ *
+ * Unstructured headers (results are merely decoded according to RFC 2047):
+ * - Comments
+ * - Content-Description
+ * - Keywords
+ * - Subject
+ *
+ * The ad-hoc headers and their resulting formats are as follows:
+ * Content-Type: returns a JS Map of parameter names (in lower case) to their
+ * values, along with the following extra properties defined on the map:
+ * - mediatype: the type to the left of '/' (e.g., 'text', 'message')
+ * - subtype: the type to the right of '/' (e.g., 'plain', 'rfc822')
+ * - type: the full typename (e.g., 'text/plain')
+ * RFC 2047 and RFC 2231 decoding is applied where appropriate. The values of
+ * the type, mediatype, and subtype attributes are all normalized to lower-case,
+ * as are the names of all parameters.
+ *
+ * Content-Transfer-Encoding: the first value is converted to lower-case.
+ *
+ * @param {String} header The name of the header of the values.
+ * @param {String|Array} value The value(s) of the headers, after charset
+ * conversion (if any) has been applied. If it is
+ * an array, the headers are listed in the order
+ * they appear in the message.
+ * @returns {Object} A structured representation of the header values.
+ */
+function parseStructuredHeader(header, value) {
+ // Enforce that the parameter is an array. If it's a string, make it a
+ // 1-element array.
+ if (typeof value === "string" || value instanceof String)
+ value = [value];
+ if (!Array.isArray(value))
+ throw new TypeError("Header value is not an array: " + value);
+
+ // Lookup the header in our decoders; if present, use that to decode the
+ // header.
+ let lowerHeader = header.toLowerCase();
+ if (structuredDecoders.has(lowerHeader)) {
+ return structuredDecoders.get(lowerHeader).call(headerparser, value);
+ }
+
+ // If not present, throw an exception.
+ throw new Error("Unknown structured header: " + header);
+}
+
+/**
+ * Add a custom structured MIME decoder to the set of known decoders. These
+ * decoders are used for {@link parseStructuredHeader} and similar functions to
+ * encode richer, more structured values instead of relying on string
+ * representations everywhere.
+ *
+ * Structured decoders are functions which take in a single parameter consisting
+ * of an array of the string values of the header, in order that they appear in
+ * the message. These headers have had the charset conversion (if necessary)
+ * applied to them already. The this parameter of the function is set to be the
+ * jsmime.headerparser module.
+ *
+ * There is a large set of structured decoders built-in to the jsmime library
+ * already. As these headers are fundamental to the workings of jsmime,
+ * attempting to replace them with a custom version will instead produce an
+ * exception.
+ *
+ * @param {String} header The header name (in any case)
+ * for which the decoder will be
+ * used.
+ * @param {Function(String[] -> Object)} decoder The structured decoder
+ * function.
+ */
+function addStructuredDecoder(header, decoder) {
+ let lowerHeader = header.toLowerCase();
+ if (forbiddenHeaders.has(lowerHeader))
+ throw new Error("Cannot override header: " + header);
+ structuredDecoders.set(lowerHeader, decoder);
+ if (!preferredSpellings.has(lowerHeader))
+ preferredSpellings.set(lowerHeader, header);
+}
+
+headerparser.addStructuredDecoder = addStructuredDecoder;
+headerparser.convert8BitHeader = convert8BitHeader;
+headerparser.decodeRFC2047Words = decodeRFC2047Words;
+headerparser.getHeaderTokens = getHeaderTokens;
+headerparser.parseAddressingHeader = parseAddressingHeader;
+headerparser.parseDateHeader = parseDateHeader;
+headerparser.parseParameterHeader = parseParameterHeader;
+headerparser.parseStructuredHeader = parseStructuredHeader;
+return Object.freeze(headerparser);
+
+});
+
+////////////////////////////////////////////////////////////////////////////////
+// JavaScript Raw MIME Parser //
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The parser implemented in this file produces a MIME part tree for a given
+ * input message via a streaming callback interface. It does not, by itself,
+ * understand concepts like attachments (hence the term 'Raw'); the consumer
+ * must translate output into such a format.
+ *
+ * Charsets:
+ * The MIME specifications permit a single message to contain multiple charsets
+ * (or perhaps none) as raw octets. As JavaScript strings are implicitly
+ * implemented in UTF-16, it is possible that some engines will attempt to
+ * convert these strings using an incorrect charset or simply fail to convert
+ * them at all. This parser assumes that its input is in the form of a "binary
+ * string", a string that uses only the first 256 characters of Unicode to
+ * represent the individual octets. To verify that charsets are not getting
+ * mangled elsewhere in the pipeline, the auxiliary test file test/data/charsets
+ * can be used.
+ *
+ * This parser attempts to hide the charset details from clients as much as
+ * possible. The resulting values of structured headers are always converted
+ * into proper Unicode strings before being exposed to clients; getting at the
+ * raw binary string data can only be done via getRawHeader. The .charset
+ * parameter on header objects, if changed, changes the fallback charset used
+ * for headers. It is initialized to the presumed charset of the corresponding
+ * part, taking into account the charset and force-charset options of the
+ * parser. Body parts are only converted into Unicode strings if the strformat
+ * option is set to Unicode. Even then, only the bodies of parts with a media
+ * type of text are converted to Unicode strings using available charset data;
+ * other parts are retained as Uint8Array objects.
+ *
+ * Part numbering:
+ * Since the output is a streaming format, individual parts are identified by a
+ * numbering scheme. The intent of the numbering scheme for parts is to comply
+ * with the part numbers as dictated by RFC 3501 as much possible; however,
+ * that scheme does have several edge cases which would, if strictly followed,
+ * make it impossible to refer to certain parts of the message. In addition, we
+ * wish to make it possible to refer to parts which are not discoverable in the
+ * original MIME tree but are still viewable as parts. The part numbering
+ * scheme is as follows:
+ * - Individual sections of a multipart/* body are numbered in increasing order
+ * sequentially, starting from 1. Note that the prologue and the epilogue of
+ * a multipart/* body are not considered entities and are therefore not
+ * included in the part numbering scheme (there is no way to refer to them).
+ * - The numbers of multipart/* parts are separated by `.' characters.
+ * - The outermost message is referred to by use of the empty string.
+ * --> The following segments are not accounted for by IMAP part numbering. <--
+ * - The body of any message/rfc822 or similar part is distinguished from the
+ * message part as a whole by appending a `$' character. This does not apply
+ * to the outermost message/rfc822 envelope.
+ */
+
+def('mimeparser', function(require) {
+"use strict";
+
+var mimeutils = require('./mimeutils');
+var headerparser = require('./headerparser');
+var spellings = require('./structuredHeaders').spellings;
+
+/**
+ * An object that represents the structured MIME headers for a message.
+ *
+ * This class is primarily used as the 'headers' parameter in the startPart
+ * callback on handlers for MimeParser. As such, it is designed to do the right
+ * thing in common cases as much as possible, with some advanced customization
+ * possible for clients that need such flexibility.
+ *
+ * In a nutshell, this class stores the raw headers as an internal Map. The
+ * structured headers are not computed until they are actually used, which means
+ * that potentially expensive structuring (e.g., doing manual DKIM validation)
+ * can be performed as a structured decoder without impeding performance for
+ * those who just want a few common headers.
+ *
+ * The outer API of this class is intended to be similar to a read-only Map
+ * object (complete with iterability support), with a few extra properties to
+ * represent things that are hard to determine properly from headers. The keys
+ * used are "preferred spellings" of the headers, although the get and has
+ * methods will accept header parameters of any case. Preferred spellings are
+ * derived from the name passed to addStructuredDecoder/addStructuredEncoder; if
+ * no structured decoder has been registered, then the name capitalizes the
+ * first letter of every word in the header name.
+ *
+ * Extra properties compared to a Map object are:
+ * - charset: This field represents the assumed charset of the associated MIME
+ * body. It is prefilled using a combination of the charset and force-charset
+ * options on the associated MimeParser instance as well as attempting to find
+ * a charset parameter in the Content-Type header.
+ *
+ * If the force-charset option is false, the charset is guessed first using
+ * the Content-Type header's charset parameter, falling back to the charset
+ * option if it is present. If the force-charset option is true, the charset
+ * is initially set to the charset option. This initial guessed value can be
+ * overridden at any time by simply setting the field on this object.
+ *
+ * The charset is better reflected as a parameter of the body rather than the
+ * headers; this is ultimately the charset parameter that will be used if a
+ * body part is being converted to a Unicode strformat. Headers are converted
+ * using headerparser.convert8BitHeader, and this field is used as the
+ * fallbackCharset parameter, which will always to attempt to decode as UTF-8
+ * first (in accordance with RFC 6532) and will refuse to decode as UTF-16 or
+ * UTF-32, as ASCII is not a subset of those charsets.
+ *
+ * - rawHeaderText: This read-only field contains the original header text from
+ * which headers were parsed, preserving case and whitespace (including
+ * alternate line endings instead of CRLF) exactly. If the header text begins
+ * with the mbox delimiter (i.e., a line that begins with "From "), then that
+ * is excluded from the rawHeaderText value and is not reflected anywhere in
+ * this object.
+ *
+ * - contentType: This field contains the structured representation of the
+ * Content-Type header, if it is present. If it is not present, it is set to
+ * the structured representation of the default Content-Type for a part (as
+ * this data is not easily guessed given only MIME tree events).
+ *
+ * The constructor for these objects is not externally exported, and thus they
+ * can only be created via MimeParser.
+ *
+ * @param rawHeaderText {BinaryString} The contents of the MIME headers to be
+ * parsed.
+ * @param options {Object} Options for the header parser.
+ * @param options.stripcontinuations {Boolean} If true, elide CRLFs from the
+ * raw header output.
+ */
+function StructuredHeaders(rawHeaderText, options) {
+ // An individual header is terminated by a CRLF, except if the CRLF is
+ // followed by a SP or TAB. Use negative lookahead to capture the latter case,
+ // and don't capture the strings or else split results get nasty.
+ let values = rawHeaderText.split(/(?:\r\n|\n)(?![ \t])|\r(?![ \t\n])/);
+
+ // Ignore the first "header" if it begins with an mbox delimiter
+ if (values.length > 0 && values[0].substring(0, 5) == "From ") {
+ values.shift();
+ // Elide the mbox delimiter from this._headerData
+ if (values.length == 0)
+ rawHeaderText = '';
+ else
+ rawHeaderText = rawHeaderText.substring(rawHeaderText.indexOf(values[0]));
+ }
+
+ let headers = new Map();
+ for (let i = 0; i < values.length; i++) {
+ // Look for a colon. If it's not present, this header line is malformed,
+ // perhaps by premature EOF or similar.
+ let colon = values[i].indexOf(":");
+ if (colon >= 0) {
+ var header = values[i].substring(0, colon);
+ var val = values[i].substring(colon + 1).trim();
+ if (options.stripcontinuations)
+ val = val.replace(/[\r\n]/g, '');
+ } else {
+ var header = values[i];
+ var val = '';
+ }
+
+ // Canonicalize the header in lower-case form.
+ header = header.trim().toLowerCase();
+ // Omit "empty" headers
+ if (header == '')
+ continue;
+
+ // We keep an array of values for each header, since a given header may be
+ // repeated multiple times.
+ if (headers.has(header)) {
+ headers.get(header).push(val);
+ } else {
+ headers.set(header, [val]);
+ }
+ }
+
+ /**
+ * A map of header names to arrays of raw values found in this header block.
+ * @private
+ */
+ this._rawHeaders = headers;
+ /**
+ * Cached results of structured header parsing.
+ * @private
+ */
+ this._cachedHeaders = new Map();
+ Object.defineProperty(this, "rawHeaderText",
+ {get: function () { return rawHeaderText; }});
+ Object.defineProperty(this, "size",
+ {get: function () { return this._rawHeaders.size; }});
+ Object.defineProperty(this, "charset", {
+ get: function () { return this._charset; },
+ set: function (value) {
+ this._charset = value;
+ // Clear the cached headers, since this could change their values
+ this._cachedHeaders.clear();
+ }
+ });
+
+ // Default to the charset, until the message parser overrides us.
+ if ('charset' in options)
+ this._charset = options.charset;
+ else
+ this._charset = null;
+
+ // If we have a Content-Type header, set contentType to return the structured
+ // representation. We don't set the value off the bat, since we want to let
+ // someone who changes the charset affect the values of 8-bit parameters.
+ Object.defineProperty(this, "contentType", {
+ configurable: true,
+ get: function () { return this.get('Content-Type'); }
+ });
+}
+
+/**
+ * Get a raw header.
+ *
+ * Raw headers are an array of the header values, listed in order that they were
+ * specified in the header block, and without any attempt to convert charsets or
+ * apply RFC 2047 decoding. For example, in the following message (where the
+ * <XX> is meant to represent binary-octets):
+ *
+ * X-Header: Value A
+ * X-Header: V<C3><A5>lue B
+ * Header2: Q
+ *
+ * the result of calling getRawHeader('X-Header') or getRawHeader('x-header')
+ * would be ['Value A', 'V\xC3\xA5lue B'] and the result of
+ * getRawHeader('Header2') would be ['Q'].
+ *
+ * @param headerName {String} The header name for which to get header values.
+ * @returns {BinaryString[]} The raw header values (with no charset conversion
+ * applied).
+ */
+StructuredHeaders.prototype.getRawHeader = function (headerName) {
+ return this._rawHeaders.get(headerName.toLowerCase());
+};
+
+/**
+ * Retrieve a structured version of the header.
+ *
+ * If there is a registered structured decoder (registration happens via
+ * headerparser.addStructuredDecoder), then the result of calling that decoder
+ * on the charset-corrected version of the header is returned. Otherwise, the
+ * values are charset-corrected and RFC 2047 decoding is applied as if the
+ * header were an unstructured header.
+ *
+ * A substantial set of headers have pre-registed structured decoders, which, in
+ * some cases, are unable to be overridden due to their importance in the
+ * functioning of the parser code itself.
+ *
+ * @param headerName {String} The header name for which to get the header value.
+ * @returns The structured header value of the output.
+ */
+StructuredHeaders.prototype.get = function (headerName) {
+ // Normalize the header name to lower case
+ headerName = headerName.toLowerCase();
+
+ // First, check the cache for the header value
+ if (this._cachedHeaders.has(headerName))
+ return this._cachedHeaders.get(headerName);
+
+ // Not cached? Grab it [propagating lack of header to caller]
+ let headerValue = this._rawHeaders.get(headerName);
+ if (headerValue === undefined)
+ return headerValue;
+
+ // Convert the header to Unicode
+ let charset = this.charset;
+ headerValue = headerValue.map(function (value) {
+ return headerparser.convert8BitHeader(value, charset);
+ });
+
+ // If there is a structured decoder, use that; otherwise, assume that the
+ // header is unstructured and only do RFC 2047 conversion
+ let structured;
+ try {
+ structured = headerparser.parseStructuredHeader(headerName, headerValue);
+ } catch (e) {
+ structured = headerValue.map(function (value) {
+ return headerparser.decodeRFC2047Words(value);
+ });
+ }
+
+ // Cache the result and return it
+ this._cachedHeaders.set(headerName, structured);
+ return structured;
+};
+
+/**
+ * Check if the message has the given header.
+ *
+ * @param headerName {String} The header name for which to get the header value.
+ * @returns {Boolean} True if the header is present in this header block.
+ */
+StructuredHeaders.prototype.has = function (headerName) {
+ // Check for presence in the raw headers instead of cached headers.
+ return this._rawHeaders.has(headerName.toLowerCase());
+};
+
+// Make a custom iterator. Presently, support for Symbol isn't yet present in
+// SpiderMonkey (or V8 for that matter), so type-pun the name for now.
+var JS_HAS_SYMBOLS = typeof Symbol === "function";
+var ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
+
+/**
+ * An equivalent of Map.@@iterator, applied to the structured header
+ * representations. This is the function that makes
+ * for (let [header, value] of headers) work properly.
+ */
+StructuredHeaders.prototype[ITERATOR_SYMBOL] = function*() {
+ // Iterate over all the raw headers, and use the cached headers to retrieve
+ // them.
+ for (let headerName of this.keys()) {
+ yield [headerName, this.get(headerName)];
+ }
+};
+
+/**
+ * An equivalent of Map.forEach, applied to the structured header
+ * representations.
+ *
+ * @param callback {Function(value, name, headers)} The callback to call for
+ * each header/value combo.
+ * @param thisarg {Object} The parameter that will be
+ * the |this| of the callback.
+ */
+StructuredHeaders.prototype.forEach = function (callback, thisarg) {
+ for (let [header, value] of this) {
+ callback.call(thisarg, value, header, this);
+ }
+};
+
+/**
+ * An equivalent of Map.entries, applied to the structured header
+ * representations.
+ */
+StructuredHeaders.prototype.entries =
+ StructuredHeaders.prototype[Symbol.iterator];
+
+/// This function maps lower case names to a pseudo-preferred spelling.
+function capitalize(headerName) {
+ return headerName.replace(/\b[a-z]/g, function (match) {
+ return match.toUpperCase();
+ });
+}
+
+/**
+ * An equivalent of Map.keys, applied to the structured header representations.
+ */
+StructuredHeaders.prototype.keys = function*() {
+ for (let name of this._rawHeaders.keys()) {
+ yield spellings.get(name) || capitalize(name);
+ }
+};
+
+/**
+ * An equivalent of Map.values, applied to the structured header
+ * representations.
+ */
+StructuredHeaders.prototype.values = function* () {
+ for (let [, value] of this) {
+ yield value;
+ }
+};
+
+
+/**
+ * A MIME parser.
+ *
+ * The inputs to the constructor consist of a callback object which receives
+ * information about the output data and an optional object containing the
+ * settings for the parser.
+ *
+ * The first parameter, emitter, is an object which contains several callbacks.
+ * Note that any and all of these methods are optional; the parser will not
+ * crash if one is missing. The callbacks are as follows:
+ * startMessage()
+ * Called when the stream to be parsed has started delivering data. This
+ * will be called exactly once, before any other call.
+ * endMessage()
+ * Called after all data has been delivered and the message parsing has
+ * been completed. This will be called exactly once, after any other call.
+ * startPart(string partNum, object headers)
+ * Called after the headers for a body part (including the top-level
+ * message) have been parsed. The first parameter is the part number (see
+ * the discussion on part numbering). The second parameter is an instance
+ * of StructuredHeaders that represents all of the headers for the part.
+ * endPart(string partNum)
+ * Called after all of the data for a body part (including sub-parts) has
+ * been parsed. The first parameter is the part number.
+ * deliverPartData(string partNum, {string,typedarray} data)
+ * Called when some data for a body part has been delivered. The first
+ * parameter is the part number. The second parameter is the data which is
+ * being delivered; the exact type of this data depends on the options
+ * used. Note that data is only delivered for leaf body parts.
+ *
+ * The second parameter, options, is an optional object containing the options
+ * for the parser. The following are the options that the parser may use:
+ * pruneat: <string> [default=""]
+ * Treat the message as starting at the given part number, so that no parts
+ * above <string> are returned.
+ * bodyformat: one of {none, raw, nodecode, decode} [default=nodecode]
+ * How to return the bodies of parts:
+ * none: no part data is returned
+ * raw: the body of the part is passed through raw
+ * nodecode: the body is passed through without decoding QP/Base64
+ * decode: quoted-printable and base64 are fully decoded
+ * strformat: one of {binarystring, unicode, typedarray} [default=binarystring]
+ * How to treat output strings:
+ * binarystring: Data is a JS string with chars in the range [\x00-\xff]
+ * unicode: Data for text parts is converted to UTF-16; data for other
+ * parts is a typed array buffer, akin to typedarray.
+ * typedarray: Data is a JS typed array buffer
+ * charset: <string> [default=""]
+ * What charset to assume if no charset information is explicitly provided.
+ * This only matters if strformat is unicode. See above note on charsets
+ * for more details.
+ * force-charset: <boolean> [default=false]
+ * If true, this coerces all types to use the charset option, even if the
+ * message specifies a different content-type.
+ * stripcontinuations: <boolean> [default=true]
+ * If true, then the newlines in headers are removed in the returned
+ * header objects.
+ * onerror: <function(thrown error)> [default = nop-function]
+ * An error function that is called if an emitter callback throws an error.
+ * By default, such errors are swallowed by the parser. If you want the
+ * parser itself to throw an error, rethrow it via the onerror function.
+ */
+function MimeParser(emitter, options) {
+ /// The actual emitter
+ this._emitter = emitter;
+ /// Options for the parser (those listed here are defaults)
+ this._options = {
+ pruneat: "",
+ bodyformat: "nodecode",
+ strformat: "binarystring",
+ stripcontinuations: true,
+ charset: "",
+ "force-charset": false,
+ onerror: function swallow(error) {}
+ };
+ // Load the options as a copy here (prevents people from changing on the fly).
+ if (options)
+ for (var opt in options) {
+ this._options[opt] = options[opt];
+ }
+
+ // Ensure that the error function is in fact a function
+ if (typeof this._options.onerror != "function")
+ throw new Exception("onerror callback must be a function");
+
+ // Reset the parser
+ this.resetParser();
+}
+
+/**
+ * Resets the parser to read a new message. This method need not be called
+ * immediately after construction.
+ */
+MimeParser.prototype.resetParser = function () {
+ /// Current parser state
+ this._state = PARSING_HEADERS;
+ /// Input data that needs to be held for buffer conditioning
+ this._holdData = '';
+ /// Complete collection of headers (also used to accumulate _headerData)
+ this._headerData = '';
+ /// Whether or not emitter.startMessage has been called
+ this._triggeredCall = false;
+
+ /// Splitting input
+ this._splitRegex = this._handleSplit = undefined;
+ /// Subparsing
+ this._subparser = this._subPartNum = undefined;
+ /// Data that has yet to be consumed by _convertData
+ this._savedBuffer = '';
+ /// Convert data
+ this._convertData = undefined;
+ /// String decoder
+ this._decoder = undefined;
+};
+
+/**
+ * Deliver a buffer of data to the parser.
+ *
+ * @param buffer {BinaryString} The raw data to add to the message.
+ */
+MimeParser.prototype.deliverData = function (buffer) {
+ // In ideal circumstances, we'd like to parse the message all at once. In
+ // reality, though, data will be coming to us in packets. To keep the amount
+ // of saved state low, we want to make basic guarantees about how packets get
+ // delivered. Our basic model is a twist on line-buffering, as the format of
+ // MIME and messages make it hard to not do so: we can handle multiple lines
+ // at once. To ensure this, we start by conditioning the packet by
+ // withholding data to make sure that the internal deliveries have the
+ // guarantees. This implies that we need to do the following steps:
+ // 1. We don't know if a `\r' comes from `\r\n' or the old mac line ending
+ // until we see the next character. So withhold the last `\r'.
+ // 2. Ensure that every packet ends on a newline. So scan for the end of the
+ // line and withhold until the \r\n comes through.
+ // [Note that this means that an input message that uses \r line endings and
+ // is being passed to us via a line-buffered input is going to have most of
+ // its data being withhold until the next buffer. Since \r is so uncommon of
+ // a line ending in modern times, this is acceptable lossage.]
+ // 3. Eliminate empty packets.
+
+ // Add in previously saved data
+ if (this._holdData) {
+ buffer = this._holdData + buffer;
+ this._holdData = '';
+ }
+
+ // Condition the input, so that we get the multiline-buffering mentioned in
+ // the above comment.
+ if (buffer.length > 0) {
+ [buffer, this._holdData] = conditionToEndOnCRLF(buffer);
+ }
+
+ // Ignore 0-length buffers.
+ if (buffer.length == 0)
+ return;
+
+ // Signal the beginning, if we haven't done so.
+ if (!this._triggeredCall) {
+ this._callEmitter("startMessage");
+ this._triggeredCall = true;
+ }
+
+ // Finally, send it the internal parser.
+ this._dispatchData("", buffer, true);
+}
+
+/**
+ * Ensure that a set of data always ends in an end-of-line character.
+ *
+ * @param buffer {BinaryString} The data with no guarantees about where it ends.
+ * @returns {BinaryString[]} An array of 2 binary strings where the first string
+ * ends in a newline and the last string contains the
+ * text in buffer following the first string.
+ */
+function conditionToEndOnCRLF(buffer) {
+ // Find the last occurrence of '\r' or '\n' to split the string. However, we
+ // don't want to consider '\r' if it is the very last character, as we need
+ // the next packet to tell if the '\r' is the beginning of a CRLF or a line
+ // ending by itself.
+ let lastCR = buffer.lastIndexOf('\r', buffer.length - 2);
+ let lastLF = buffer.lastIndexOf('\n');
+ let end = lastLF > lastCR ? lastLF : lastCR;
+ return [buffer.substring(0, end + 1), buffer.substring(end + 1)];
+};
+
+/**
+ * Tell the parser that all of the data has been delivered.
+ *
+ * This will flush all of the internal state of the parser.
+ */
+MimeParser.prototype.deliverEOF = function () {
+ // Start of input buffered too long? Call start message now.
+ if (!this._triggeredCall) {
+ this._triggeredCall = true;
+ this._callEmitter("startMessage");
+ }
+ // Force a flush of all of the data.
+ if (this._holdData)
+ this._dispatchData("", this._holdData, true);
+ this._dispatchEOF("");
+ // Signal to the emitter that we're done.
+ this._callEmitter("endMessage");
+};
+
+/**
+ * Calls a method on the emitter safely.
+ *
+ * This method ensures that errors in the emitter call won't cause the parser
+ * to exit with an error, unless the user wants it to.
+ *
+ * @param funcname {String} The function name to call on the emitter.
+ * @param args... Extra arguments to pass into the emitter callback.
+ */
+MimeParser.prototype._callEmitter = function (funcname) {
+ if (this._emitter && funcname in this._emitter) {
+ let args = Array.prototype.splice.call(arguments, 1);
+ if (args.length > 0 && this._willIgnorePart(args[0])) {
+ // partNum is always the first argument, so check to make sure that it
+ // satisfies our emitter's pruneat requirement.
+ return;
+ }
+ try {
+ this._emitter[funcname].apply(this._emitter, args);
+ } catch (e) {
+ // We ensure that the onerror attribute in options is a function, so this
+ // is always safe.
+ this._options.onerror(e);
+ }
+ }
+};
+
+/**
+ * Helper function to decide if a part's output will never be seen.
+ *
+ * @param part {String} The number of the part.
+ * @returns {Boolean} True if the emitter is not interested in this part.
+ */
+MimeParser.prototype._willIgnorePart = function (part) {
+ if (this._options["pruneat"]) {
+ let match = this._options["pruneat"];
+ let start = part.substr(0, match.length);
+ // It needs to start with and follow with a new part indicator
+ // (i.e., don't let 10 match with 1, but let 1.1 or 1$ do so)
+ if (start != match || (match.length < part.length &&
+ "$.".indexOf(part[match.length]) == -1))
+ return true;
+ }
+ return false;
+};
+
+//////////////////////
+// MIME parser core //
+//////////////////////
+
+// This MIME parser is a stateful parser; handling of the MIME tree is mostly
+// done by creating new parsers and feeding data to them manually. In parallel
+// to the externally-visible deliverData and deliverEOF, the two methods
+// _dispatchData and _dispatchEOF are the internal counterparts that do the
+// main work of moving data to where it needs to go; helper functions are used
+// to handle translation.
+//
+// The overall flow of the parser is this. First, it buffers all of the data
+// until the dual-CRLF pattern is noticed. Once that is found, it parses the
+// entire header chunk at once. As a result of header parsing, the parser enters
+// one of three modes for handling data, and uses a special regex to change
+// modes and handle state changes. Specific details about the states the parser
+// can be in are as follows:
+// PARSING_HEADERS: The input buffer is concatenated to the currently-received
+// text, which is then searched for the CRLFCRLF pattern. If found, the data
+// is split at this boundary; the first chunk is parsed using _parseHeaders,
+// and the second chunk will fall through to buffer processing. After
+// splitting, the headers are deliverd via the emitter, and _startBody is
+// called to set up state for the parser.
+// SEND_TO_BLACK_HOLE: All data in the input is ignored.
+// SEND_TO_EMITTER: All data is passed into the emitter, if it is desired.
+// Data can be optionally converted with this._convertData.
+// SEND_TO_SUBPARSER: All data is passed into the subparser's _dispatchData
+// method, using _subPartNum as the part number and _subparser as the object
+// to call. Data can be optionally converted first with this._convertData.
+//
+// Additional state modifications can be done using a regex in _splitRegex and
+// the callback method this._handleSplit(partNum, regexResult). The _handleSplit
+// callback is free to do any modification to the current parser, including
+// modifying the _splitRegex value. Packet conditioning guarantees that every
+// buffer string passed into _dispatchData will have started immediately after a
+// newline character in the fully assembled message.
+//
+// The this._convertData method, if present, is expected to return an array of
+// two values, [{typedarray, string} decoded_buffer, string unused_buffer], and
+// has as its arguments (string buffer, bool moreToCome).
+//
+// The header parsing by itself does very little parsing, only parsing as if all
+// headers were unstructured fields. Values are munged so that embedded newlines
+// are stripped and the result is also trimmed. Headers themselves are
+// canonicalized into lower-case.
+
+
+// Parser states. See the large comment above.
+var PARSING_HEADERS = 1;
+var SEND_TO_BLACK_HOLE = 2;
+var SEND_TO_EMITTER = 3;
+var SEND_TO_SUBPARSER = 4;
+
+/**
+ * Main dispatch for incoming packet data.
+ *
+ * The incoming data needs to have been sanitized so that each packet begins on
+ * a newline boundary. The part number for the current parser also needs to be
+ * passed in. The checkSplit parameter controls whether or not the data in
+ * buffer needs to be checked against _splitRegex; this is used internally for
+ * the mechanics of splitting and should otherwise always be true.
+ *
+ * @param partNum {String} The part number being currently parsed.
+ * @param buffer {BinaryString} The text (conditioned as mentioned above) to
+ * pass to the parser.
+ * @param checkSplit {Boolean} If true, split the text using _splitRegex.
+ * This is set to false internally to handle
+ * low-level splitting details.
+ */
+MimeParser.prototype._dispatchData = function (partNum, buffer, checkSplit) {
+ // Are we parsing headers?
+ if (this._state == PARSING_HEADERS) {
+ this._headerData += buffer;
+ // Find the end of the headers--either it's a CRLF at the beginning (in
+ // which case we have no headers), or it's a pair of CRLFs.
+ let result = /(?:^(?:\r\n|[\r\n]))|(\r\n|[\r\n])\1/.exec(this._headerData);
+ if (result != null) {
+ // If we found the end of headers, split the data at this point and send
+ // the stuff after the double-CRLF into the later body parsing.
+ let headers = this._headerData.substr(0, result.index);
+ buffer = this._headerData.substring(result.index + result[0].length);
+ this._headerData = headers;
+ this._headers = this._parseHeaders();
+ this._callEmitter("startPart", partNum, this._headers);
+ this._startBody(partNum);
+ } else {
+ return;
+ }
+ }
+
+ // We're in the middle of the body. Start by testing the split regex, to see
+ // if there are many things that need to be done.
+ if (checkSplit && this._splitRegex) {
+ let splitResult = this._splitRegex.exec(buffer);
+ if (splitResult) {
+ // Pass the text before the split through the current state.
+ let start = splitResult.index, len = splitResult[0].length;
+ if (start > 0)
+ this._dispatchData(partNum, buffer.substr(0, start), false);
+
+ // Tell the handler that we've seen the split. Note that this can change
+ // any method on `this'.
+ this._handleSplit(partNum, splitResult);
+
+ // Send the rest of the data to where it needs to go. There could be more
+ // splits in the data, so watch out!
+ buffer = buffer.substring(start + len);
+ if (buffer.length > 0)
+ this._dispatchData(partNum, buffer, true);
+ return;
+ }
+ }
+
+ // Where does the data go?
+ if (this._state == SEND_TO_BLACK_HOLE) {
+ // Don't send any data when going to the black hole.
+ return;
+ } else if (this._state == SEND_TO_EMITTER) {
+ // Don't pass body data if the format is to be none
+ let passData = this._options["bodyformat"] != "none";
+ if (!passData || this._willIgnorePart(partNum))
+ return;
+ buffer = this._applyDataConversion(buffer, this._options["strformat"]);
+ if (buffer.length > 0)
+ this._callEmitter("deliverPartData", partNum, buffer);
+ } else if (this._state == SEND_TO_SUBPARSER) {
+ buffer = this._applyDataConversion(buffer, "binarystring");
+ if (buffer.length > 0)
+ this._subparser._dispatchData(this._subPartNum, buffer, true);
+ }
+};
+
+/**
+ * Output data using the desired output format, saving data if data conversion
+ * needs extra data to be saved.
+ *
+ * @param buf {BinaryString} The data to be sent to the output.
+ * @param type {String} The type of the data to output. Valid values are
+ * the same as the strformat option.
+ * @returns Coerced and converted data that can be sent to the emitter or
+ * subparser.
+ */
+MimeParser.prototype._applyDataConversion = function (buf, type) {
+ // If we need to convert data, do so.
+ if (this._convertData) {
+ // Prepend leftover data from the last conversion.
+ buf = this._savedBuffer + buf;
+ [buf, this._savedBuffer] = this._convertData(buf, true);
+ }
+ return this._coerceData(buf, type, true);
+};
+
+/**
+ * Coerce the input buffer into the given output type.
+ *
+ * @param buffer {BinaryString|Uint8Array} The data to be converted.
+ * @param type {String} The type to convert the data to.
+ * @param more {boolean} If true, this function will never be
+ * called again.
+ * @returns {BinaryString|String|Uint8Array} The desired output format.
+ */
+/// Coerces the buffer (a string or typedarray) into a given type
+MimeParser.prototype._coerceData = function (buffer, type, more) {
+ if (typeof buffer == "string") {
+ // string -> binarystring is a nop
+ if (type == "binarystring")
+ return buffer;
+ // Either we're going to array or unicode. Both people need the array
+ var typedarray = mimeutils.stringToTypedArray(buffer);
+ // If it's unicode, do the coercion from the array
+ // If its typedarray, just return the synthesized one
+ return type == "unicode" ? this._coerceData(typedarray, "unicode", more)
+ : typedarray;
+ } else if (type == "binarystring") {
+ // Doing array -> binarystring
+ return mimeutils.typedArrayToString(buffer);
+ } else if (type == "unicode") {
+ // Doing array-> unicode: Use the decoder set up earlier to convert
+ if (this._decoder)
+ return this._decoder.decode(buffer, {stream: more});
+ // If there is no charset, just return the typed array instead.
+ return buffer;
+ }
+ throw new Error("Invalid type: " + type);
+};
+
+/**
+ * Signal that no more data will be dispatched to this parser.
+ *
+ * @param partNum {String} The part number being currently parsed.
+ */
+MimeParser.prototype._dispatchEOF = function (partNum) {
+ if (this._state == PARSING_HEADERS) {
+ // Unexpected EOF in headers. Parse them now and call startPart/endPart
+ this._headers = this._parseHeaders();
+ this._callEmitter("startPart", partNum, this._headers);
+ } else if (this._state == SEND_TO_SUBPARSER) {
+ // Pass in any lingering data
+ if (this._convertData && this._savedBuffer)
+ this._subparser._dispatchData(this._subPartNum,
+ this._convertData(this._savedBuffer, false)[0], true);
+ this._subparser._dispatchEOF(this._subPartNum);
+ // Clean up after ourselves
+ this._subparser = null;
+ } else if (this._convertData && this._savedBuffer) {
+ // Convert lingering data
+ let [buffer, ] = this._convertData(this._savedBuffer, false);
+ buffer = this._coerceData(buffer, this._options["strformat"], false);
+ if (buffer.length > 0)
+ this._callEmitter("deliverPartData", partNum, buffer);
+ }
+
+ // We've reached EOF for this part; tell the emitter
+ this._callEmitter("endPart", partNum);
+};
+
+/**
+ * Produce a dictionary of all headers as if they were unstructured fields.
+ *
+ * @returns {StructuredHeaders} The structured header objects for the header
+ * block.
+ */
+MimeParser.prototype._parseHeaders = function () {
+ let headers = new StructuredHeaders(this._headerData, this._options);
+
+ // Fill the headers.contentType parameter of headers.
+ let contentType = headers.get('Content-Type');
+ if (typeof contentType === "undefined") {
+ contentType = headerparser.parseStructuredHeader('Content-Type',
+ this._defaultContentType || 'text/plain');
+ Object.defineProperty(headers, "contentType", {
+ get: function () { return contentType; }
+ });
+ } else {
+ Object.defineProperty(headers, "contentType", { configurable: false });
+ }
+
+ // Find the charset for the current part. If the user requested a forced
+ // conversion, use that first. Otherwise, check the content-type for one and
+ // fallback to a default if it is not present.
+ let charset = '';
+ if (this._options["force-charset"])
+ charset = this._options["charset"];
+ else if (contentType.has("charset"))
+ charset = contentType.get("charset");
+ else
+ charset = this._options["charset"];
+ headers.charset = charset;
+
+ // Retain a copy of the charset so that users don't override our decision for
+ // decoding body parts.
+ this._charset = charset;
+ return headers;
+};
+
+/**
+ * Initialize the parser state for the body of this message.
+ *
+ * @param partNum {String} The part number being currently parsed.
+ */
+MimeParser.prototype._startBody = function Parser_startBody(partNum) {
+ let contentType = this._headers.contentType;
+
+ // Should the bodyformat be raw, we just want to pass through all data without
+ // trying to interpret it.
+ if (this._options["bodyformat"] == "raw" &&
+ partNum == this._options["pruneat"]) {
+ this._state = SEND_TO_EMITTER;
+ return;
+ }
+
+ // The output depents on the content-type. Basic rule of thumb:
+ // 1. Discrete media types (text, video, audio, image, application) are passed
+ // through with no alterations beyond Content-Transfer-Encoding unpacking.
+ // 2. Everything with a media type of multipart is treated the same.
+ // 3. Any message/* type that acts like a mail message (rfc822, news, global)
+ // is parsed as a header/body pair again. Most of the other message/* types
+ // have similar structures, but they don't have cascading child subparts,
+ // so it's better to pass their entire contents to the emitter and let the
+ // consumer deal with them.
+ // 4. For untyped data, there needs to be no Content-Type header. This helps
+ // avoid false positives.
+ if (contentType.mediatype == 'multipart') {
+ // If there's no boundary type, everything will be part of the prologue of
+ // the multipart message, so just feed everything into a black hole.
+ if (!contentType.has('boundary')) {
+ this._state = SEND_TO_BLACK_HOLE;
+ return;
+ }
+ // The boundary of a multipart message needs to start with -- and be at the
+ // beginning of the line. If -- is after the boundary, it represents the
+ // terminator of the multipart. After the line, there may be only whitespace
+ // and then the CRLF at the end. Since the CRLFs in here are necessary for
+ // distinguishing the parts, they are not included in the subparts, so we
+ // need to capture them in the regex as well to prevent them leaking out.
+ this._splitRegex = new RegExp('(\r\n|[\r\n]|^)--' +
+ contentType.get('boundary').replace(/[\\^$*+?.()|{}[\]]/g, '\\$&') +
+ '(--)?[ \t]*(?:\r\n|[\r\n]|$)');
+ this._handleSplit = this._whenMultipart;
+ this._subparser = new MimeParser(this._emitter, this._options);
+ // multipart/digest defaults to message/rfc822 instead of text/plain
+ if (contentType.subtype == "digest")
+ this._subparser._defaultContentType = "message/rfc822";
+
+ // All text before the first boundary and after the closing boundary are
+ // supposed to be ignored ("must be ignored", according to RFC 2046 §5.1.1);
+ // in accordance with these wishes, ensure they don't get passed to any
+ // deliverPartData.
+ this._state = SEND_TO_BLACK_HOLE;
+
+ // Multipart MIME messages stipulate that the final CRLF before the boundary
+ // delimiter is not matched. When the packet ends on a CRLF, we don't know
+ // if the next text could be the boundary. Therefore, we need to withhold
+ // the last line of text to be sure of what's going on. The _convertData is
+ // how we do this, even though we're not really converting any data.
+ this._convertData = function mpart_no_leak_crlf(buffer, more) {
+ let splitPoint = buffer.length;
+ if (more) {
+ if (buffer.charAt(splitPoint - 1) == '\n')
+ splitPoint--;
+ if (splitPoint >= 0 && buffer.charAt(splitPoint - 1) == '\r')
+ splitPoint--;
+ }
+ let res = conditionToEndOnCRLF(buffer.substring(0, splitPoint));
+ let preLF = res[0];
+ let rest = res[1];
+ return [preLF, rest + buffer.substring(splitPoint)];
+ }
+ } else if (contentType.type == 'message/rfc822' ||
+ contentType.type == 'message/global' ||
+ contentType.type == 'message/news') {
+ // The subpart is just another header/body pair that goes to EOF, so just
+ // return the parse from that blob
+ this._state = SEND_TO_SUBPARSER;
+ this._subPartNum = partNum + "$";
+ this._subparser = new MimeParser(this._emitter, this._options);
+
+ // So, RFC 6532 happily allows message/global types to have CTE applied.
+ // This means that subparts would need to be decoded to determine their
+ // contents properly. There seems to be some evidence that message/rfc822
+ // that is illegally-encoded exists in the wild, so be lenient and decode
+ // for any message/* type that gets here.
+ let cte = this._extractHeader('content-transfer-encoding', '');
+ if (cte in ContentDecoders)
+ this._convertData = ContentDecoders[cte];
+ } else {
+ // Okay, we just have to feed the data into the output
+ this._state = SEND_TO_EMITTER;
+ if (this._options["bodyformat"] == "decode") {
+ // If we wish to decode, look it up in one of our decoders.
+ let cte = this._extractHeader('content-transfer-encoding', '');
+ if (cte in ContentDecoders)
+ this._convertData = ContentDecoders[cte];
+ }
+ }
+
+ // Set up the encoder for charset conversions; only do this for text parts.
+ // Other parts are almost certainly binary, so no translation should be
+ // applied to them.
+ if (this._options["strformat"] == "unicode" &&
+ contentType.mediatype == "text") {
+ // If the charset is nonempty, initialize the decoder
+ if (this._charset !== "") {
+ this._decoder = new TextDecoder(this._charset);
+ } else {
+ // There's no charset we can use for decoding, so pass through as an
+ // identity encoder or otherwise this._coerceData will complain.
+ this._decoder = {
+ decode: function identity_decoder(buffer) {
+ return MimeParser.prototype._coerceData(buffer, "binarystring", true);
+ }
+ };
+ }
+ } else {
+ this._decoder = null;
+ }
+};
+
+// Internal split handling for multipart messages.
+/**
+ * When a multipary boundary is found, handle the process of managing the
+ * subparser state. This is meant to be used as a value for this._handleSplit.
+ *
+ * @param partNum {String} The part number being currently parsed.
+ * @param lastResult {Array} The result of the regular expression match.
+ */
+MimeParser.prototype._whenMultipart = function (partNum, lastResult) {
+ // Fix up the part number (don't do '' -> '.4' and don't do '1' -> '14')
+ if (partNum != "") partNum += ".";
+ if (!this._subPartNum) {
+ // No count? This means that this is the first time we've seen the boundary,
+ // so do some initialization for later here.
+ this._count = 1;
+ } else {
+ // If we did not match a CRLF at the beginning of the line, strip CRLF from
+ // the saved buffer. We do this in the else block because it is not
+ // necessary for the prologue, since that gets ignored anyways.
+ if (this._savedBuffer != '' && lastResult[1] === '') {
+ let useEnd = this._savedBuffer.length - 1;
+ if (this._savedBuffer[useEnd] == '\n')
+ useEnd--;
+ if (useEnd >= 0 && this._savedBuffer[useEnd] == '\r')
+ useEnd--;
+ this._savedBuffer = this._savedBuffer.substring(0, useEnd + 1);
+ }
+ // If we have saved data and we matched a CRLF, pass the saved data in.
+ if (this._savedBuffer != '')
+ this._subparser._dispatchData(this._subPartNum, this._savedBuffer, true);
+ // We've seen the boundary at least once before, so this must end a subpart.
+ // Tell that subpart that it has reached EOF.
+ this._subparser._dispatchEOF(this._subPartNum);
+ }
+ this._savedBuffer = '';
+
+ // The regex feeder has a capture on the (--)?, so if its result is present,
+ // then we have seen the terminator. Alternatively, the message may have been
+ // mangled to exclude the terminator, so also check if EOF has occurred.
+ if (lastResult[2] == undefined) {
+ this._subparser.resetParser();
+ this._state = SEND_TO_SUBPARSER;
+ this._subPartNum = partNum + this._count;
+ this._count += 1;
+ } else {
+ // Ignore the epilogue
+ this._splitRegex = null;
+ this._state = SEND_TO_BLACK_HOLE;
+ }
+};
+
+/**
+ * Return the structured header from the current header block, or a default if
+ * it is not present.
+ *
+ * @param name {String} The header name to get.
+ * @param dflt {String} The default MIME value of the header.
+ * @returns The structured representation of the header.
+ */
+MimeParser.prototype._extractHeader = function (name, dflt) {
+ name = name.toLowerCase(); // Normalize name
+ return this._headers.has(name) ? this._headers.get(name) :
+ headerparser.parseStructuredHeader(name, [dflt]);
+};
+
+var ContentDecoders = {};
+ContentDecoders['quoted-printable'] = mimeutils.decode_qp;
+ContentDecoders['base64'] = mimeutils.decode_base64;
+
+return MimeParser;
+});
+def('headeremitter', function(require) {
+/**
+ * This module implements the code for emitting structured representations of
+ * MIME headers into their encoded forms. The code here is a companion to,
+ * but completely independent of, jsmime.headerparser: the structured
+ * representations that are used as input to the functions in this file are the
+ * same forms that would be parsed.
+ */
+
+"use strict";
+
+var mimeutils = require('./mimeutils');
+
+// Get the default structured encoders and add them to the map
+var structuredHeaders = require('./structuredHeaders');
+var encoders = new Map();
+var preferredSpellings = structuredHeaders.spellings;
+for (let [header, encoder] of structuredHeaders.encoders) {
+ addStructuredEncoder(header, encoder);
+}
+
+/// Clamp a value in the range [min, max], defaulting to def if it is undefined.
+function clamp(value, min, max, def) {
+ if (value === undefined)
+ return def;
+ if (value < min)
+ return min;
+ if (value > max)
+ return max;
+ return value;
+}
+
+/**
+ * An object that can assemble structured header representations into their MIME
+ * representation.
+ *
+ * The character-counting portion of this class operates using individual JS
+ * characters as its representation of logical character, which is not the same
+ * as the number of octets used as UTF-8. If non-ASCII characters are to be
+ * included in headers without some form of encoding, then care should be taken
+ * to set the maximum line length to account for the mismatch between character
+ * counts and octet counts: the maximum line is 998 octets, which could be as
+ * few as 332 JS characters (non-BMP characters, although they take up 4 octets
+ * in UTF-8, count as 2 in JS strings).
+ *
+ * This code takes care to only insert line breaks at the higher-level breaking
+ * points in a header (as recommended by RFC 5322), but it may need to resort to
+ * including them more aggressively if this is not possible. If even aggressive
+ * line-breaking cannot allow a header to be emitted without violating line
+ * length restrictions, the methods will throw an exception to indicate this
+ * situation.
+ *
+ * In general, this code does not attempt to modify its input; for example, it
+ * does not attempt to change the case of any input characters, apply any
+ * Unicode normalization algorithms, or convert email addresses to ACE where
+ * applicable. The biggest exception to this rule is that most whitespace is
+ * collapsed to a single space, even in unstructured headers, while most leading
+ * and trailing whitespace is trimmed from inputs.
+ *
+ * @param {StreamHandler} handler The handler to which all output is sent.
+ * @param {Function(String)} handler.deliverData Receives encoded data.
+ * @param {Function()} handler.deliverEOF Sent when all text is sent.
+ * @param {Object} options Options for the emitter.
+ * @param [options.softMargin=78] {30 <= Integer <= 900}
+ * The ideal maximum number of logical characters to include in a line, not
+ * including the final CRLF pair. Lines may exceed this margin if parameters
+ * are excessively long.
+ * @param [options.hardMargin=332] {softMargin <= Integer <= 998}
+ * The maximum number of logical characters that can be included in a line,
+ * not including the final CRLF pair. If this count would be exceeded, then
+ * an error will be thrown and encoding will not be possible.
+ * @param [options.useASCII=true] {Boolean}
+ * If true, then RFC 2047 and RFC 2231 encoding of headers will be performed
+ * as needed to retain headers as ASCII.
+ */
+function HeaderEmitter(handler, options) {
+ /// The inferred value of options.useASCII
+ this._useASCII = options.useASCII === undefined ? true : options.useASCII;
+ /// The handler to use.
+ this._handler = handler;
+ /**
+ * The current line being built; note that we may insert a line break in the
+ * middle to keep under the maximum line length.
+ *
+ * @type String
+ * @private
+ */
+ this._currentLine = "";
+
+ // Our bounds for soft and margins are not completely arbitrary. The minimum
+ // amount we need to encode is 20 characters, which can encode a single
+ // non-BMP character with RFC 2047. The value of 30 is chosen to give some
+ // breathing room for delimiters or other unbreakable characters. The maximum
+ // length is 998 octets, per RFC 5322; soft margins are slightly lower to
+ // allow for breathing room as well. The default of 78 for the soft margin is
+ // recommended by RFC 5322; the default of 332 for the hard margin ensures
+ // that UTF-8 encoding the output never violates the 998 octet limit.
+ this._softMargin = clamp(options.softMargin, 30, 900, 78);
+ this._hardMargin = clamp(options.hardMargin, this._softMargin, 998, 332);
+
+ /**
+ * The index of the last preferred breakable position in the current line.
+ *
+ * @type Integer
+ * @private
+ */
+ this._preferredBreakpoint = 0;
+}
+
+
+///////////////////////
+// Low-level methods //
+///////////////////////
+
+// Explanation of the emitter internals:
+// RFC 5322 requires that we wrap our lines, ideally at 78 characters and at
+// least by 998 octets. We can't wrap in arbitrary places, but wherever CFWS is
+// valid... and ideally wherever clients are likely to expect it. In theory, we
+// can break between every token (this is how RFC 822 operates), but, in RFC
+// 5322, many of those breaks are relegated to obsolete productions, mostly
+// because it is common to not properly handle breaks in those locations.
+//
+// So how do we do line breaking? The algorithm we implement is greedy, to
+// simplify implementation. There are two margins: the soft margin, which we
+// want to keep within, and the hard margin, which we absolutely have to keep
+// within. There are also two kinds of break points: preferred and emergency.
+// As long as we keep the line within the hard margin, we will only break at
+// preferred breakpoints; emergency breakpoints are only used if we would
+// otherwise exceed the hard margin.
+//
+// For illustration, here is an example header and where these break points are
+// located:
+//
+// To: John "The Rock" Smith <jsmith@a.long.domain.invalid>
+// Preferred: ^ ^ ^
+// Emergency: ^ ^ ^ ^^ ^ ^ ^ ^ ^
+//
+// Preferred breakpoints are indicated by setting the mayBreakAfter parameter of
+// addText to true, while emergency breakpoints are set after every token passed
+// into addText. This is handled implicitly by only adding text to _currentLine
+// if it ends in an emergency breakpoint.
+//
+// Internally, the code keeps track of margins by use of two variables. The
+// _softMargin and _hardMargin variables encode the positions at which code must
+// absolutely break, and are set up from the initial options parameter. Breaking
+// happens when _currentLine.length approaches these values, as mentioned above.
+
+/**
+ * Send a header line consisting of the first N characters to the handler.
+ *
+ * If the count parameter is missing, then we presume that the current header
+ * value being emitted is done and therefore we should not send a continuation
+ * space. Otherwise, we presume that we're still working, so we will send the
+ * continuation space.
+ *
+ * @private
+ * @param [count] {Integer} The number of characters in the current line to
+ * include before wrapping.
+ */
+HeaderEmitter.prototype._commitLine = function (count) {
+ let isContinuing = typeof count !== "undefined";
+
+ // Split at the point, and lop off whitespace immediately before and after.
+ if (isContinuing) {
+ var firstN = this._currentLine.slice(0, count).trimRight();
+ var lastN = this._currentLine.slice(count).trimLeft();
+ } else {
+ var firstN = this._currentLine.trimRight();
+ var lastN = "";
+ }
+
+ // How many characters do we need to shift preferred/emergency breakpoints?
+ let shift = this._currentLine.length - lastN.length;
+
+ // Send the line plus the final CRLF.
+ this._handler.deliverData(firstN + '\r\n');
+
+ // Fill the start of the line with the new data.
+ this._currentLine = lastN;
+
+ // If this is a continuation, add an extra space at the beginning of the line.
+ // Adjust the breakpoint shift amount as well.
+ if (isContinuing) {
+ this._currentLine = ' ' + this._currentLine;
+ shift++;
+ }
+
+ // We will always break at a point at or after the _preferredBreakpoint, if it
+ // exists, so this always gets reset to 0.
+ this._preferredBreakpoint = 0;
+};
+
+/**
+ * Reserve at least length characters in the current line. If there aren't
+ * enough characters, insert a line break.
+ *
+ * @private
+ * @param length {Integer} The number of characters to reserve space for.
+ * @return {Boolean} Whether or not there is enough space for length characters.
+ */
+HeaderEmitter.prototype._reserveTokenSpace = function (length) {
+ // We are not going to do a sanity check that length is within the wrap
+ // margins. The rationale is that this lets code simply call this function to
+ // force a higher-level line break than normal preferred line breaks (see
+ // addAddress for an example use). The text that would be added may need to be
+ // itself broken up, so it might not need all the length anyways, but it
+ // starts the break already.
+
+ // If we have enough space, we don't need to do anything.
+ if (this._currentLine.length + length <= this._softMargin)
+ return true;
+
+ // If we have a preferred breakpoint, commit the line at that point, and see
+ // if that is sufficient line-breaking.
+ if (this._preferredBreakpoint > 0) {
+ this._commitLine(this._preferredBreakpoint);
+ if (this._currentLine.length + length <= this._softMargin)
+ return true;
+ }
+
+ // At this point, we can no longer keep within the soft margin. Let us see if
+ // we can fit within the hard margin.
+ if (this._currentLine.length + length <= this._hardMargin) {
+ return true;
+ }
+
+ // Adding the text to length would violate the hard margin as well. Break at
+ // the last emergency breakpoint.
+ if (this._currentLine.length > 0) {
+ this._commitLine(this._currentLine.length);
+ }
+
+ // At this point, if there is still insufficient room in the hard margin, we
+ // can no longer do anything to encode this word. Bail.
+ return this._currentLine.length + length <= this._hardMargin;
+};
+
+/**
+ * Adds a block of text to the current header, inserting a break if necessary.
+ * If mayBreakAfter is true and text does not end in whitespace, a single space
+ * character may be added to the output. If the text could not be added without
+ * violating line length restrictions, an error is thrown instead.
+ *
+ * @protected
+ * @param {String} text The text to add to the output.
+ * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred
+ * breakpoint.
+ */
+HeaderEmitter.prototype.addText = function (text, mayBreakAfter) {
+ // Try to reserve space for the tokens. If we can't, give up.
+ if (!this._reserveTokenSpace(text.length))
+ throw new Error("Cannot encode " + text + " due to length.");
+
+ this._currentLine += text;
+ if (mayBreakAfter) {
+ // Make sure that there is an extra space if text could break afterwards.
+ this._preferredBreakpoint = this._currentLine.length;
+ if (text[text.length - 1] != ' ') {
+ this._currentLine += ' ';
+ }
+ }
+};
+
+/**
+ * Adds a block of text that may need quoting if it contains some character in
+ * qchars. If it is already quoted, no quoting will be applied. If the text
+ * cannot be added without violating maximum line length, an error is thrown
+ * instead.
+ *
+ * @protected
+ * @param {String} text The text to add to the output.
+ * @param {String} qchars The set of characters that cannot appear
+ * outside of a quoted string.
+ * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred
+ * breakpoint.
+ */
+HeaderEmitter.prototype.addQuotable = function (text, qchars, mayBreakAfter) {
+ // No text -> no need to be quoted (prevents strict warning errors).
+ if (text.length == 0)
+ return;
+
+ // Figure out if we need to quote the string. Don't quote a string which
+ // already appears to be quoted.
+ let needsQuote = false;
+
+ if (!(text[0] == '"' && text[text.length - 1] == '"') && qchars != '') {
+ for (let i = 0; i < text.length; i++) {
+ if (qchars.includes(text[i])) {
+ needsQuote = true;
+ break;
+ }
+ }
+ }
+
+ if (needsQuote)
+ text = '"' + text.replace(/["\\]/g, "\\$&") + '"';
+ this.addText(text, mayBreakAfter);
+};
+
+/**
+ * Adds a block of text that corresponds to the phrase production in RFC 5322.
+ * Such text is a sequence of atoms, quoted-strings, or RFC-2047 encoded-words.
+ * This method will preprocess input to normalize all space sequences to a
+ * single space. If the text cannot be added without violating maximum line
+ * length, an error is thrown instead.
+ *
+ * @protected
+ * @param {String} text The text to add to the output.
+ * @param {String} qchars The set of characters that cannot appear
+ * outside of a quoted string.
+ * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred
+ * breakpoint.
+ */
+HeaderEmitter.prototype.addPhrase = function (text, qchars, mayBreakAfter) {
+ // Collapse all whitespace spans into a single whitespace node.
+ text = text.replace(/[ \t\r\n]+/g, " ");
+
+ // If we have non-ASCII text, encode it using RFC 2047.
+ if (this._useASCII && nonAsciiRe.test(text)) {
+ this.encodeRFC2047Phrase(text, mayBreakAfter);
+ return;
+ }
+
+ // If quoting the entire string at once could fit in the line length, then do
+ // so. The check here is very loose, but this will inform is if we are going
+ // to definitely overrun the soft margin.
+ if ((this._currentLine.length + text.length) < this._softMargin) {
+ try {
+ this.addQuotable(text, qchars, mayBreakAfter);
+ // If we don't have a breakpoint, and the text is encoded as a sequence of
+ // atoms (and not a quoted-string), then make the last space we added a
+ // breakpoint, regardless of the mayBreakAfter setting.
+ if (this._preferredBreakpoint == 0 && text.includes(" ")) {
+ if (this._currentLine[this._currentLine.length - 1] != '"')
+ this._preferredBreakpoint = this._currentLine.lastIndexOf(" ");
+ }
+ return;
+ } catch (e) {
+ // If we get an error at this point, we failed to add the quoted string
+ // because the string was too long. Fall through to the case where we know
+ // that the input was too long to begin with.
+ }
+ }
+
+ // If the text is too long, split the quotable string at space boundaries and
+ // add each word invidually. If we still can't add all those words, there is
+ // nothing that we can do.
+ let words = text.split(' ');
+ for (let i = 0; i < words.length; i++) {
+ this.addQuotable(words[i], qchars,
+ i == words.length - 1 ? mayBreakAfter : true);
+ }
+};
+
+/// A regular expression for characters that need to be encoded.
+var nonAsciiRe = /[^\x20-\x7e]/;
+
+/// The beginnings of RFC 2047 encoded-word
+var b64Prelude = "=?UTF-8?B?", qpPrelude = "=?UTF-8?Q?";
+
+/// A list of ASCII characters forbidden in RFC 2047 encoded-words
+var qpForbidden = "\"#$%&'(),.:;<=>?@[\\]^_`{|}~";
+
+var hexString = "0123456789abcdef";
+
+/**
+ * Add a block of text as a single RFC 2047 encoded word. This does not try to
+ * split words if they are too long.
+ *
+ * @private
+ * @param {Uint8Array} encodedText The octets to encode.
+ * @param {Boolean} useQP If true, use quoted-printable; if false,
+ * use base64.
+ * @param {Boolean} mayBreakAfter If true, the end of this text is a
+ * preferred breakpoint.
+ */
+HeaderEmitter.prototype._addRFC2047Word = function (encodedText, useQP,
+ mayBreakAfter) {
+ let binaryString = mimeutils.typedArrayToString(encodedText);
+ if (useQP) {
+ var token = qpPrelude;
+ for (let i = 0; i < encodedText.length; i++) {
+ if (encodedText[i] < 0x20 || encodedText[i] >= 0x7F ||
+ qpForbidden.includes(binaryString[i])) {
+ let ch = encodedText[i];
+ token += "=" + hexString[(ch & 0xf0) >> 4] + hexString[ch & 0x0f];
+ } else if (binaryString[i] == " ") {
+ token += "_";
+ } else {
+ token += binaryString[i];
+ }
+ }
+ token += "?=";
+ } else {
+ var token = b64Prelude + btoa(binaryString) + "?=";
+ }
+ this.addText(token, mayBreakAfter);
+};
+
+/**
+ * Add a block of text as potentially several RFC 2047 encoded-word tokens.
+ *
+ * @protected
+ * @param {String} text The text to add to the output.
+ * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred
+ * breakpoint.
+ */
+HeaderEmitter.prototype.encodeRFC2047Phrase = function (text, mayBreakAfter) {
+ // Start by encoding the text into UTF-8 directly.
+ let encodedText = new TextEncoder("UTF-8").encode(text);
+
+ // Make sure there's enough room for a single token.
+ let minLineLen = b64Prelude.length + 10; // Eight base64 characters plus ?=
+ if (!this._reserveTokenSpace(minLineLen)) {
+ this._commitLine(this._currentLine.length);
+ }
+
+ // Try to encode as much UTF-8 text as possible in each go.
+ let b64Len = 0, qpLen = 0, start = 0;
+ let maxChars = (this._softMargin - this._currentLine.length) -
+ (b64Prelude.length + 2);
+ for (let i = 0; i < encodedText.length; i++) {
+ let b64Inc = 0, qpInc = 0;
+ // The length we need for base64 is ceil(length / 3) * 4...
+ if ((i - start) % 3 == 0)
+ b64Inc += 4;
+
+ // The length for quoted-printable is 3 chars only if encoded
+ if (encodedText[i] < 0x20 || encodedText[i] >= 0x7f ||
+ qpForbidden.includes(String.fromCharCode(encodedText[i]))) {
+ qpInc = 3;
+ } else {
+ qpInc = 1;
+ }
+
+ if (b64Len + b64Inc > maxChars && qpLen + qpInc > maxChars) {
+ // Oops, we have too many characters! We need to encode everything through
+ // the current character. However, we can't split in the middle of a
+ // multibyte character. In UTF-8, characters that start with 10xx xxxx are
+ // the middle of multibyte characters, so backtrack until the start
+ // character is legal.
+ while ((encodedText[i] & 0xC0) == 0x80)
+ --i;
+
+ // Add this part of the word and then make a continuation.
+ this._addRFC2047Word(encodedText.subarray(start, i), b64Len >= qpLen,
+ true);
+
+ // Reset the array for parsing.
+ start = i;
+ --i; // Reparse this character as well
+ b64Len = qpLen = 0;
+ maxChars = this._softMargin - b64Prelude.length - 3;
+ } else {
+ // Add the counts for the current variable to the count to encode.
+ b64Len += b64Inc;
+ qpLen += qpInc;
+ }
+ }
+
+ // Add the entire array at this point.
+ this._addRFC2047Word(encodedText.subarray(start), b64Len >= qpLen,
+ mayBreakAfter);
+};
+
+////////////////////////
+// High-level methods //
+////////////////////////
+
+/**
+ * Add the header name, with the colon and trailing space, to the output.
+ *
+ * @public
+ * @param {String} name The name of the header.
+ */
+HeaderEmitter.prototype.addHeaderName = function (name) {
+ this._currentLine = this._currentLine.trimRight();
+ if (this._currentLine.length > 0) {
+ this._commitLine();
+ }
+ this.addText(name + ": ", false);
+};
+
+/**
+ * Add a header and its structured value to the output.
+ *
+ * The name can be any case-insensitive variant of a known structured header;
+ * the output will include the preferred name of the structure instead of the
+ * case put into the name. If no structured encoder can be found, and the input
+ * value is a string, then the header is assumed to be unstructured and the
+ * value is added as if {@link addUnstructured} were called.
+ *
+ * @public
+ * @param {String} name The name of the header.
+ * @param value The structured value of the header.
+ */
+HeaderEmitter.prototype.addStructuredHeader = function (name, value) {
+ let lowerName = name.toLowerCase();
+ if (encoders.has(lowerName)) {
+ this.addHeaderName(preferredSpellings.get(lowerName));
+ encoders.get(lowerName).call(this, value);
+ } else if (typeof value === "string") {
+ // Assume it's an unstructured header.
+ // All-lower-case-names are ugly, so capitalize first letters.
+ name = name.replace(/(^|-)[a-z]/g, function(match) {
+ return match.toUpperCase();
+ });
+ this.addHeaderName(name);
+ this.addUnstructured(value);
+ } else {
+ throw new Error("Unknown header " + name);
+ }
+};
+
+/**
+ * Add a single address to the header. The address is an object consisting of a
+ * possibly-empty display name and an email address.
+ *
+ * @public
+ * @param Address addr The address to be added.
+ * @param {String} addr.name The (possibly-empty) name of the address to add.
+ * @param {String} addr.email The email of the address to add.
+ * @see headerparser.parseAddressingHeader
+ */
+HeaderEmitter.prototype.addAddress = function (addr) {
+ // If we have a display name, add that first.
+ if (addr.name) {
+ // This is a simple estimate that keeps names on one line if possible.
+ this._reserveTokenSpace(addr.name.length + addr.email.length + 3);
+ this.addPhrase(addr.name, ",()<>[]:;@.\"", true);
+
+ // If we don't have an email address, don't write out the angle brackets for
+ // the address. It's already an abnormal situation should this appear, and
+ // this has better round-tripping properties.
+ if (!addr.email)
+ return;
+
+ this.addText("<", false);
+ }
+
+ // Find the local-part and domain of the address, since the local-part may
+ // need to be quoted separately. Note that the @ goes to the domain, so that
+ // the local-part may be quoted if it needs to be.
+ let at = addr.email.lastIndexOf("@");
+ let localpart = "", domain = ""
+ if (at == -1)
+ localpart = addr.email;
+ else {
+ localpart = addr.email.slice(0, at);
+ domain = addr.email.slice(at);
+ }
+
+ this.addQuotable(localpart, "()<>[]:;@\\,\" !", false);
+ this.addText(domain + (addr.name ? ">" : ""), false);
+};
+
+/**
+ * Add an array of addresses and groups to the output. Such an array may be
+ * found as the output of {@link headerparser.parseAddressingHeader}. Each
+ * element is either an address (an object with properties name and email), or a
+ * group (an object with properties name and group).
+ *
+ * @public
+ * @param {(Address|Group)[]} addrs A collection of addresses to add.
+ * @param {String} addrs[i].name The (possibly-empty) name of the
+ * address or the group to add.
+ * @param {String} [addrs[i].email] The email of the address to add.
+ * @param {Address[]} [addrs[i].group] A list of email addresses in the group.
+ * @see HeaderEmitter.addAddress
+ * @see headerparser.parseAddressingHeader
+ */
+HeaderEmitter.prototype.addAddresses = function (addresses) {
+ let needsComma = false;
+ for (let addr of addresses) {
+ // Add a comma if this is not the first element.
+ if (needsComma)
+ this.addText(", ", true);
+ needsComma = true;
+
+ if ("email" in addr) {
+ this.addAddress(addr);
+ } else {
+ // A group has format name: member, member;
+ // Note that we still add a comma after the group is completed.
+ this.addPhrase(addr.name, ",()<>[]:;@.\"", false);
+ this.addText(":", true);
+
+ this.addAddresses(addr.group);
+ this.addText(";", true);
+ }
+ }
+};
+
+/**
+ * Add an unstructured header value to the output. This effectively means only
+ * inserting line breaks were necessary, and using RFC 2047 encoding where
+ * necessary.
+ *
+ * @public
+ * @param {String} text The text to add to the output.
+ */
+HeaderEmitter.prototype.addUnstructured = function (text) {
+ if (text.length == 0)
+ return;
+
+ // Unstructured text is basically a phrase that can't be quoted. So, if we
+ // have nothing in qchars, nothing should be quoted.
+ this.addPhrase(text, "", false);
+};
+
+/** RFC 822 labels for days of the week. */
+var kDaysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+
+/**
+ * Formatting helper to output numbers between 0-9 as 00-09 instead.
+ */
+function padTo2Digits(num) {
+ return num < 10 ? "0" + num : num.toString();
+}
+
+/**
+ * Add a date/time field to the output, using the JS date object as the time
+ * representation. The value will be output using the timezone offset of the
+ * date object, which is usually the timezone of the user (modulo timezone and
+ * DST changes).
+ *
+ * Note that if the date is an invalid date (its internal date parameter is a
+ * NaN value), this method throws an error instead of generating an invalid
+ * string.
+ *
+ * @public
+ * @param {Date} date The date to be added to the output string.
+ */
+HeaderEmitter.prototype.addDate = function (date) {
+ // Rather than make a header plastered with NaN values, throw an error on
+ // specific invalid dates.
+ if (isNaN(date.getTime()))
+ throw new Error("Cannot encode an invalid date");
+
+ // RFC 5322 says years can't be before 1900. The after 9999 is a bit that
+ // derives from the specification saying that years have 4 digits.
+ if (date.getFullYear() < 1900 || date.getFullYear() > 9999)
+ throw new Error("Date year is out of encodable range");
+
+ // Start by computing the timezone offset for a day. We lack a good format, so
+ // the the 0-padding is done by hand. Note that the tzoffset we output is in
+ // the form ±hhmm, so we need to separate the offset (in minutes) into an hour
+ // and minute pair.
+ let tzOffset = date.getTimezoneOffset();
+ let tzOffHours = Math.abs(Math.trunc(tzOffset / 60));
+ let tzOffMinutes = Math.abs(tzOffset) % 60;
+ let tzOffsetStr = (tzOffset > 0 ? "-" : "+") +
+ padTo2Digits(tzOffHours) + padTo2Digits(tzOffMinutes);
+
+ // Convert the day-time figure into a single value to avoid unwanted line
+ // breaks in the middle.
+ let dayTime = [
+ kDaysOfWeek[date.getDay()] + ",",
+ date.getDate(),
+ mimeutils.kMonthNames[date.getMonth()],
+ date.getFullYear(),
+ padTo2Digits(date.getHours()) + ":" +
+ padTo2Digits(date.getMinutes()) + ":" +
+ padTo2Digits(date.getSeconds()),
+ tzOffsetStr
+ ].join(" ");
+ this.addText(dayTime, false);
+};
+
+/**
+ * Signal that the current header has been finished encoding.
+ *
+ * @public
+ * @param {Boolean} deliverEOF If true, signal to the handler that no more text
+ * will be arriving.
+ */
+HeaderEmitter.prototype.finish = function (deliverEOF) {
+ this._commitLine();
+ if (deliverEOF)
+ this._handler.deliverEOF();
+};
+
+/**
+ * Make a streaming header emitter that outputs on the given handler.
+ *
+ * @param {StreamHandler} handler The handler to consume output
+ * @param options Options to pass into the HeaderEmitter
+ * constructor.
+ * @returns {HeaderEmitter} A header emitter constructed with the given options.
+ */
+function makeStreamingEmitter(handler, options) {
+ return new HeaderEmitter(handler, options);
+}
+
+function StringHandler() {
+ this.value = "";
+ this.deliverData = function (str) { this.value += str; };
+ this.deliverEOF = function () { };
+}
+
+/**
+ * Given a header name and its structured value, output a string containing its
+ * MIME-encoded value. The trailing CRLF for the header is included.
+ *
+ * @param {String} name The name of the structured header.
+ * @param value The value of the structured header.
+ * @param options Options for the HeaderEmitter constructor.
+ * @returns {String} A MIME-encoded representation of the structured header.
+ * @see HeaderEmitter.addStructuredHeader
+ */
+function emitStructuredHeader(name, value, options) {
+ let handler = new StringHandler();
+ let emitter = new HeaderEmitter(handler, options);
+ emitter.addStructuredHeader(name, value);
+ emitter.finish(true);
+ return handler.value;
+}
+
+/**
+ * Given a map of header names and their structured values, output a string
+ * containing all of their headers and their MIME-encoded values.
+ *
+ * This method is designed to be able to emit header values given the headerData
+ * values produced by MIME parsing. Thus, the values of the map are arrays
+ * corresponding to header multiplicity.
+ *
+ * @param {Map(String->Object[])} headerValues A map of header names to arrays
+ * of their structured values.
+ * @param options Options for the HeaderEmitter
+ * constructor.
+ * @returns {String} A MIME-encoded representation of the structured header.
+ * @see HeaderEmitter.addStructuredHeader
+ */
+function emitStructuredHeaders(headerValues, options) {
+ let handler = new StringHandler();
+ let emitter = new HeaderEmitter(handler, options);
+ for (let instance of headerValues) {
+ instance[1].forEach(function (e) {
+ emitter.addStructuredHeader(instance[0], e)
+ });
+ }
+ emitter.finish(true);
+ return handler.value;
+}
+
+/**
+ * Add a custom structured MIME encoder to the set of known encoders. These
+ * encoders are used for {@link emitStructuredHeader} and similar functions to
+ * encode richer, more structured values instead of relying on string
+ * representations everywhere.
+ *
+ * Structured encoders are functions which take in a single parameter
+ * representing their structured value. The this parameter is set to be an
+ * instance of {@link HeaderEmitter}, and it is intended that the several public
+ * or protected methods on that class are useful for encoding values.
+ *
+ * There is a large set of structured encoders built-in to the jsmime library
+ * already.
+ *
+ * @param {String} header The header name (in its preferred case) for
+ * which the encoder will be used.
+ * @param {Function(Value)} encoder The structured encoder function.
+ */
+function addStructuredEncoder(header, encoder) {
+ let lowerName = header.toLowerCase();
+ encoders.set(lowerName, encoder);
+ if (!preferredSpellings.has(lowerName))
+ preferredSpellings.set(lowerName, header);
+}
+
+return Object.freeze({
+ addStructuredEncoder: addStructuredEncoder,
+ emitStructuredHeader: emitStructuredHeader,
+ emitStructuredHeaders: emitStructuredHeaders,
+ makeStreamingEmitter: makeStreamingEmitter
+});
+
+});
+
+def('jsmime', function(require) {
+ return {
+ MimeParser: require('./mimeparser'),
+ headerparser: require('./headerparser'),
+ headeremitter: require('./headeremitter')
+ }
+});
+ return mods['jsmime'];
+}));
diff --git a/mailnews/mime/moz.build b/mailnews/mime/moz.build
new file mode 100644
index 000000000..0a7865a3d
--- /dev/null
+++ b/mailnews/mime/moz.build
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'public',
+ 'src',
+ 'emitters',
+ 'cthandlers',
+]
+
+EXTRA_JS_MODULES.jsmime += [
+ 'jsmime/jsmime.js',
+]
diff --git a/mailnews/mime/public/MimeEncoder.h b/mailnews/mime/public/MimeEncoder.h
new file mode 100644
index 000000000..7883af70e
--- /dev/null
+++ b/mailnews/mime/public/MimeEncoder.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MimeEncoder_h__
+#define MimeEncoder_h__
+
+#include "nscore.h"
+
+namespace mozilla {
+namespace mailnews {
+
+/// A class for encoding the bodies of MIME parts.
+class MimeEncoder {
+public:
+ virtual ~MimeEncoder() {}
+
+ /// A callback for writing the encoded output
+ typedef nsresult (*OutputCallback)
+ (const char *buf, int32_t size, void *closure);
+
+ /// Encodes the string in the buffer and sends it to the callback
+ virtual nsresult Write(const char *buffer, int32_t size) = 0;
+ /// Flush all pending data when no more data exists
+ virtual nsresult Flush() { return NS_OK; }
+
+ /// Get an encoder that outputs Base64-encoded data
+ static MimeEncoder *GetBase64Encoder(OutputCallback callback, void *closure);
+ /// Get an encoder that outputs quoted-printable data
+ static MimeEncoder *GetQPEncoder(OutputCallback callback, void *closure);
+
+protected:
+ MimeEncoder(OutputCallback callback, void *closure);
+ OutputCallback mCallback;
+ void *mClosure;
+ uint32_t mCurrentColumn;
+};
+
+} // namespace mailnews
+} // namespace mozilla
+
+#endif
diff --git a/mailnews/mime/public/MimeHeaderParser.h b/mailnews/mime/public/MimeHeaderParser.h
new file mode 100644
index 000000000..d8cd3705a
--- /dev/null
+++ b/mailnews/mime/public/MimeHeaderParser.h
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MimeHeaderParser_h__
+#define MimeHeaderParser_h__
+
+#include "nsCOMArray.h"
+#include "nsStringGlue.h"
+#include "nsTArray.h"
+
+class msgIAddressObject;
+
+namespace mozilla {
+namespace mailnews {
+
+/**
+ * This is used to signal that the input header value has already been decoded
+ * according to RFC 2047 and is in UTF-16 form.
+ */
+nsCOMArray<msgIAddressObject> DecodedHeader(const nsAString &aHeader);
+
+/**
+ * This is used to signal that the input header value needs to be decoded
+ * according to RFC 2047. The charset parameter indicates the charset to assume
+ * that non-ASCII data is in; if the value is null (the default), then the
+ * charset is assumed to be UTF-8.
+ */
+nsCOMArray<msgIAddressObject> EncodedHeader(const nsACString &aHeader,
+ const char *aCharset = nullptr);
+
+/**
+ * Same deal, but we're starting with an nsAString.
+ */
+nsCOMArray<msgIAddressObject> EncodedHeaderW(const nsAString &aHeader);
+
+namespace detail {
+void DoConversion(const nsTArray<nsString> &aUTF16, nsTArray<nsCString> &aUTF8);
+};
+/**
+ * This is a class designed for use as temporaries so that methods can pass
+ * an nsTArray<nsCString> into methods that expect nsTArray<nsString> for out
+ * parameters (this does not work for in-parameters).
+ *
+ * It works by internally providing an nsTArray<nsString> which it uses for its
+ * external API operations. If the user requests an array of nsCString elements
+ * instead, it converts the UTF-16 array to a UTF-8 array on destruction.
+ */
+template <uint32_t N = 5>
+class UTF16ArrayAdapter
+{
+public:
+ explicit UTF16ArrayAdapter(nsTArray<nsCString> &aUTF8Array)
+ : mUTF8Array(aUTF8Array) {}
+ ~UTF16ArrayAdapter() { detail::DoConversion(mUTF16Array, mUTF8Array); }
+ operator nsTArray<nsString>&() { return mUTF16Array; }
+private:
+ nsTArray<nsCString> &mUTF8Array;
+ AutoTArray<nsString, N> mUTF16Array;
+};
+
+/**
+ * Given a name and an email, both encoded in UTF-8, produce a string suitable
+ * for writing in an email header by quoting where necessary.
+ *
+ * If name is not empty, the output string will be name <email>. If it is empty,
+ * the output string is just the email. Note that this DOES NOT do any RFC 2047
+ * encoding.
+ */
+void MakeMimeAddress(const nsACString &aName, const nsACString &aEmail,
+ nsACString &full);
+
+/**
+ * Given a name and an email, produce a string suitable for writing in an email
+ * header by quoting where necessary.
+ *
+ * If name is not empty, the output string will be name <email>. If it is empty,
+ * the output string is just the email. Note that this DOES NOT do any RFC 2047
+ * encoding.
+ */
+void MakeMimeAddress(const nsAString &aName, const nsAString &aEmail,
+ nsAString &full);
+
+/**
+ * Given a name and an email, both encoded in UTF-8, produce a string suitable
+ * for displaying in UI.
+ *
+ * If name is not empty, the output string will be name <email>. If it is empty,
+ * the output string is just the email.
+ */
+void MakeDisplayAddress(const nsAString &aName, const nsAString &aEmail,
+ nsAString &full);
+
+/**
+ * Returns a copy of the input which may have had some addresses removed.
+ * Addresses are removed if they are already in either of the supplied
+ * address lists.
+ *
+ * Addresses are considered to be the same if they contain the same email
+ * address parts, ignoring case. Display names or comments are not compared.
+ *
+ * @param aHeader The addresses to remove duplicates from.
+ * @param aOtherEmails Other addresses that the duplicate removal process also
+ * checks for duplicates against. Addresses in this list
+ * will not be added to the result.
+ * @return The original header with duplicate addresses removed.
+ */
+void RemoveDuplicateAddresses(const nsACString &aHeader,
+ const nsACString &aOtherEmails,
+ nsACString &result);
+
+/**
+ * Given a message header, extract all names and email addresses found in that
+ * header into the two arrays.
+ */
+void ExtractAllAddresses(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsTArray<nsString> &names, nsTArray<nsString> &emails);
+
+/**
+ * Given a raw message header value, extract display names for every address
+ * found in the header.
+ */
+void ExtractDisplayAddresses(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsTArray<nsString> &addresses);
+
+/**
+ * Given a raw message header value, extract all the email addresses into an
+ * array.
+ *
+ * Duplicate email addresses are not removed from the output list.
+ */
+void ExtractEmails(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsTArray<nsString> &emails);
+
+/**
+ * Given a raw message header value, extract the first name/email address found
+ * in the header. This is essentially equivalent to grabbing the first entry of
+ * ExtractAllAddresses.
+ */
+void ExtractFirstAddress(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsACString &name, nsACString &email);
+
+/**
+ * Given an RFC 2047-decoded message header value, extract the first name/email
+ * address found in the header. This is essentially equivalent to grabbing the
+ * first entry of ExtractAllAddresses.
+ */
+void ExtractFirstAddress(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsAString &name, nsACString &email);
+
+/**
+ * Given a raw message header value, extract the first email address found in
+ * the header.
+ */
+void ExtractEmail(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsACString &email);
+
+/**
+ * Given a raw message header value, extract and clean up the first display
+ * name found in the header. If there is no display name, the email address is
+ * used instead.
+ */
+void ExtractName(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsACString &name);
+
+/**
+ * Given an RFC 2047-decoded message header value, extract the first display
+ * name found in the header. If there is no display name, the email address is
+ * returned instead.
+ */
+void ExtractName(const nsCOMArray<msgIAddressObject> &aDecodedHeader,
+ nsAString &name);
+
+} // namespace mailnews
+} // namespace mozilla
+
+#endif
diff --git a/mailnews/mime/public/moz.build b/mailnews/mime/public/moz.build
new file mode 100644
index 000000000..58243068e
--- /dev/null
+++ b/mailnews/mime/public/moz.build
@@ -0,0 +1,36 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'msgIStructuredHeaders.idl',
+ 'nsICMSDecoder.idl',
+ 'nsICMSEncoder.idl',
+ 'nsICMSMessage.idl',
+ 'nsICMSMessage2.idl',
+ 'nsICMSMessageErrors.idl',
+ 'nsICMSSecureMessage.idl',
+ 'nsIMimeConverter.idl',
+ 'nsIMimeEmitter.idl',
+ 'nsIMimeHeaders.idl',
+ 'nsIMimeMiscStatus.idl',
+ 'nsIMimeStreamConverter.idl',
+ 'nsIMsgHeaderParser.idl',
+ 'nsIPgpMimeProxy.idl',
+ 'nsISimpleMimeConverter.idl',
+]
+
+XPIDL_MODULE = 'mime'
+
+EXPORTS += [
+ 'nsIMimeContentTypeHandler.h',
+ 'nsIMimeObjectClassAccess.h',
+ 'nsMailHeaders.h',
+ 'nsMsgMimeCID.h',
+]
+
+EXPORTS.mozilla.mailnews += [
+ 'MimeEncoder.h',
+ 'MimeHeaderParser.h',
+]
diff --git a/mailnews/mime/public/msgIStructuredHeaders.idl b/mailnews/mime/public/msgIStructuredHeaders.idl
new file mode 100644
index 000000000..9d3b3aa9a
--- /dev/null
+++ b/mailnews/mime/public/msgIStructuredHeaders.idl
@@ -0,0 +1,209 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include "nsCOMArray.h"
+
+#define NS_ISTRUCTUREDHEADERS_CONTRACTID \
+ "@mozilla.org/messenger/structuredheaders;1"
+%}
+
+interface msgIAddressObject;
+interface nsIUTF8StringEnumerator;
+
+/**
+ * A collection of MIME headers that are stored in a rich, structured format.
+ *
+ * The structured forms defined in this method use the structured decoder and
+ * encoder functionality found in jsmime to interconvert between the raw string
+ * forms found in the actual message text and the structured forms supported by
+ * this interface. Extensions can register their own custom structured headers
+ * by listing the source URL of their code under the category
+ * "custom-mime-encoder".
+ *
+ * The alternative modes of access for specific headers are expected to only
+ * work for the headers for which that mode of access is the correct one. For
+ * example, retrieving the "To" header from getUnstructuredHeader would fail,
+ * since the To header is not an unstructured header but an addressing header.
+ * They are provided mostly as a convenience to C++ which is much less able to
+ * utilize a fully generic format.
+ *
+ * With the exception of mismatched headers, the methods do not throw an
+ * exception if the header is missing but rather return an appropriate default
+ * value as indicated in their documentation.
+ */
+[scriptable, uuid(e109bf4f-788f-47ba-bfa8-1236ede05597)]
+interface msgIStructuredHeaders : nsISupports {
+ /**
+ * Retrieve the value of the header stored in this set of headers. If the
+ * header is not present, then undefined is returned.
+ *
+ * @param aHeaderName The name of the header to retrieve.
+ */
+ jsval getHeader(in string aHeaderName);
+
+ /**
+ * Return true if and only if the given header is already set.
+ *
+ * @param aHeaderName The name of the header to retrieve.
+ */
+ bool hasHeader(in string aHeaderName);
+
+ /**
+ * Retrieve the value of the header as if it is an unstructured header. Such
+ * headers include most notably the Subject header. If the header is not
+ * present, then null is returned. This is reflected in C++ as an empty string
+ * with IsVoid() set to true (distinguishing it from a header that is present
+ * but contains an empty string).
+ *
+ * @param aHeaderName The name of the header to retrieve.
+ */
+ AString getUnstructuredHeader(in string aHeaderName);
+
+ /**
+ * Retrieve the value of the header if it is an addressing header, such as the
+ * From or To headers. If the header is not present, then an empty array is
+ * returned.
+ *
+ * @param aHeaderName The name of the header to retrieve.
+ * @param aPreserveGroups If false (the default), then the result is a flat
+ * list of addresses, with all group annotations
+ * removed.
+ * If true, then some address objects may represent
+ * groups in the header, preserving the original header
+ * structure.
+ */
+ void getAddressingHeader(in string aHeaderName,
+ [optional] in boolean aPreserveGroups, [optional] out unsigned long count,
+ [array, size_is(count), retval] out msgIAddressObject addresses);
+
+ /**
+ * Retrieve a raw version of the header value as would be represented in MIME.
+ * This form does not include the header name and colon, trailing whitespace,
+ * nor embedded CRLF pairs in the case of very long header names.
+ *
+ * @param aHeaderName The name of the header to retrieve.
+ */
+ AUTF8String getRawHeader(in string aHeaderName);
+
+ /**
+ * Retrieve an enumerator of the names of all headers in this set of headers.
+ * The header names returned may be in different cases depending on the
+ * precise implementation of this interface, so implementations should not
+ * rely on an exact kind of case being returned.
+ */
+ readonly attribute nsIUTF8StringEnumerator headerNames;
+
+ /**
+ * Retrieve the MIME representation of all of the headers.
+ *
+ * The header values are emitted in an ASCII form, unless internationalized
+ * email addresses are involved. The extra CRLF indicating the end of headers
+ * is not included in this representation.
+ *
+ * This accessor is provided mainly for the benefit of C++ consumers of this
+ * interface, since the JSMime headeremitter functionality allows more
+ * fine-grained customization of the results.
+ */
+ AUTF8String buildMimeText();
+
+%{C++
+ /**
+ * A special variant of getAddressingHeader that is specialized better for C++
+ * users of this API.
+ */
+ nsresult GetAddressingHeader(const char *aPropertyName,
+ nsCOMArray<msgIAddressObject> &addrs,
+ bool aPreserveGroups = false)
+ {
+ msgIAddressObject **addrPtr;
+ uint32_t length;
+ nsresult rv = GetAddressingHeader(aPropertyName, aPreserveGroups, &length,
+ &addrPtr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ addrs.Adopt(addrPtr, length);
+ return NS_OK;
+ }
+%}
+
+};
+
+/**
+ * An interface that enhances msgIStructuredHeaders by allowing the values of
+ * headers to be modified.
+ */
+[scriptable, uuid(5dcbbef6-2356-45d8-86d7-b3e73f9c9a0c)]
+interface msgIWritableStructuredHeaders : msgIStructuredHeaders {
+ /**
+ * Store the given value for the given header, overwriting any previous value
+ * that was stored for said header.
+ *
+ * @param aHeaderName The name of the header to store.
+ * @param aValue The rich, structured value of the header to store.
+ */
+ void setHeader(in string aHeaderName, in jsval aValue);
+
+ /**
+ * Forget any previous value that was stored for the given header.
+ *
+ * @param aHeaderName The name of the header to delete.
+ */
+ void deleteHeader(in string aHeaderName);
+
+ /**
+ * Copy all of the structured values from another set of structured headers to
+ * the current one, overwriting any values that may have been specified
+ * locally. Note that the copy is a shallow copy of the value.
+ *
+ * @param aOtherHeaders A set of header values to be copied.
+ */
+ void addAllHeaders(in msgIStructuredHeaders aOtherHeaders);
+
+ /**
+ * Set the value of the header as if it were an unstructured header. Such
+ * headers include most notably the Subject header.
+ *
+ * @param aHeaderName The name of the header to store.
+ * @param aValue The value to store.
+ */
+ void setUnstructuredHeader(in string aHeaderName, in AString aValue);
+
+ /**
+ * Set the value of the header as if it were an addressing header, such as the
+ * From or To headers.
+ *
+ * @param aHeaderName The name of the header to store.
+ */
+ void setAddressingHeader(in string aHeaderName,
+ [array, size_is(aCount)] in msgIAddressObject aAddresses,
+ in unsigned long aCount);
+
+ /**
+ * Store the value of the header using a raw version as would be represented
+ * in MIME. So as to handle 8-bit headers properly, the charset needs to be
+ * specified, although it may be null.
+ *
+ * @param aHeaderName The name of the header to store.
+ * @param aValue The raw MIME header value to store.
+ * @param aCharset The expected charset of aValue.
+ */
+ void setRawHeader(in string aHeaderName, in ACString aValue,
+ in string aCharset);
+
+%{C++
+ /**
+ * A special variant of setAddressingHeader that is specialized better for C++
+ * users of this API.
+ */
+ nsresult SetAddressingHeader(const char *aPropertyName,
+ const nsCOMArray<msgIAddressObject> &addrs)
+ {
+ return SetAddressingHeader(aPropertyName,
+ const_cast<nsCOMArray<msgIAddressObject>&>(addrs).Elements(),
+ addrs.Length());
+ }
+%}
+};
diff --git a/mailnews/mime/public/nsICMSDecoder.idl b/mailnews/mime/public/nsICMSDecoder.idl
new file mode 100644
index 000000000..f67f1e640
--- /dev/null
+++ b/mailnews/mime/public/nsICMSDecoder.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+typedef void (*NSSCMSContentCallback)(void *arg, const char *buf, unsigned long len);
+
+#define NS_CMSDECODER_CONTRACTID "@mozilla.org/nsCMSDecoder;1"
+%}
+
+native NSSCMSContentCallback(NSSCMSContentCallback);
+
+interface nsICMSMessage;
+
+/**
+ * nsICMSDecoder
+ * Interface to decode an CMS message
+ */
+[uuid(c7c7033b-f341-4065-aadd-7eef55ce0dda)]
+interface nsICMSDecoder : nsISupports
+{
+ void start(in NSSCMSContentCallback cb, in voidPtr arg);
+ void update(in string aBuf, in long aLen);
+ void finish(out nsICMSMessage msg);
+};
+
diff --git a/mailnews/mime/public/nsICMSEncoder.idl b/mailnews/mime/public/nsICMSEncoder.idl
new file mode 100644
index 000000000..a82f5e0de
--- /dev/null
+++ b/mailnews/mime/public/nsICMSEncoder.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+typedef void (*NSSCMSContentCallback)(void *arg, const char *buf, unsigned long len);
+
+#define NS_CMSENCODER_CONTRACTID "@mozilla.org/nsCMSEncoder;1"
+%}
+
+native NSSCMSContentCallback(NSSCMSContentCallback);
+
+interface nsICMSMessage;
+
+/**
+ * nsICMSEncoder
+ * Interface to Encode an CMS message
+ */
+[uuid(17dc4fb4-e379-4e56-a4a4-57cdcc74816f)]
+interface nsICMSEncoder : nsISupports
+{
+ void start(in nsICMSMessage aMsg, in NSSCMSContentCallback cb, in voidPtr arg);
+ void update(in string aBuf, in long aLen);
+ void finish();
+ void encode(in nsICMSMessage aMsg);
+};
+
diff --git a/mailnews/mime/public/nsICMSMessage.idl b/mailnews/mime/public/nsICMSMessage.idl
new file mode 100644
index 000000000..ffa2aac7e
--- /dev/null
+++ b/mailnews/mime/public/nsICMSMessage.idl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+#define NS_CMSMESSAGE_CONTRACTID "@mozilla.org/nsCMSMessage;1"
+%}
+
+[ptr] native UnsignedCharPtr(unsigned char);
+
+interface nsIX509Cert;
+interface nsIArray;
+
+/**
+ * nsICMSMessage
+ * Interface to a CMS Message
+ */
+[uuid(c6d51c22-73e9-4dad-86b9-bde584e33c63)]
+interface nsICMSMessage : nsISupports
+{
+ void contentIsSigned(out boolean aSigned);
+ void contentIsEncrypted(out boolean aEncrypted);
+ void getSignerCommonName(out string aName);
+ void getSignerEmailAddress(out string aEmail);
+ void getSignerCert(out nsIX509Cert scert);
+ void getEncryptionCert(out nsIX509Cert ecert);
+ void verifySignature();
+ void verifyDetachedSignature(in UnsignedCharPtr aDigestData,
+ in unsigned long aDigestDataLen,
+ in int16_t aDigestType);
+ void CreateEncrypted(in nsIArray aRecipientCerts);
+
+ /* The parameter aDigestType must be one of the values in nsICryptoHash */
+ void CreateSigned(in nsIX509Cert scert, in nsIX509Cert ecert,
+ in UnsignedCharPtr aDigestData,
+ in unsigned long aDigestDataLen, in int16_t aDigestType);
+};
+
diff --git a/mailnews/mime/public/nsICMSMessage2.idl b/mailnews/mime/public/nsICMSMessage2.idl
new file mode 100644
index 000000000..f353c32a2
--- /dev/null
+++ b/mailnews/mime/public/nsICMSMessage2.idl
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsISMimeVerificationListener;
+
+[ptr] native UnsignedCharPtr(unsigned char);
+
+/*
+ * This interface is currently not marked scriptable,
+ * because its verification functions are meant to look like those
+ * in nsICMSMessage. At the time the ptr type is eliminated in both
+ * interfaces, both should be made scriptable.
+ */
+
+[uuid(b21a3636-2287-4b9f-9a22-25f245981ef0)]
+interface nsICMSMessage2 : nsISupports
+{
+ /**
+ * Async version of nsICMSMessage::VerifySignature.
+ * Code will be executed on a background thread and
+ * availability of results will be notified using a
+ * call to nsISMimeVerificationListener.
+ */
+ void asyncVerifySignature(in nsISMimeVerificationListener listener);
+
+ /**
+ * Async version of nsICMSMessage::VerifyDetachedSignature.
+ * Code will be executed on a background thread and
+ * availability of results will be notified using a
+ * call to nsISMimeVerificationListener.
+ *
+ * We are using "native unsigned char" ptr, because the function
+ * signatures of this one and nsICMSMessage::verifyDetachedSignature
+ * should be the identical. Cleaning up nsICMSMessages needs to be
+ * postponed, because this async version is needed on MOZILLA_1_8_BRANCH.
+ *
+ * Once both interfaces get cleaned up, the function signature should
+ * look like:
+ * [array, length_is(aDigestDataLen)]
+ * in octet aDigestData,
+ * in unsigned long aDigestDataLen);
+ *
+ * Set aDigestType to one of the values from nsICryptoHash.
+ */
+ void asyncVerifyDetachedSignature(in nsISMimeVerificationListener listener,
+ in UnsignedCharPtr aDigestData,
+ in unsigned long aDigestDataLen,
+ in int16_t aDigestType);
+};
+
+[uuid(5226d698-0773-4f25-b94c-7944b3fc01d3)]
+interface nsISMimeVerificationListener : nsISupports {
+
+ /**
+ * Notify that results are ready, that have been requested
+ * using nsICMSMessage2::asyncVerify[Detached]Signature()
+ *
+ * verificationResultCode matches synchronous result code from
+ * nsICMSMessage::verify[Detached]Signature
+ */
+ void notify(in nsICMSMessage2 verifiedMessage,
+ in nsresult verificationResultCode);
+};
+
diff --git a/mailnews/mime/public/nsICMSMessageErrors.idl b/mailnews/mime/public/nsICMSMessageErrors.idl
new file mode 100644
index 000000000..d429cc671
--- /dev/null
+++ b/mailnews/mime/public/nsICMSMessageErrors.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsICMSMessageErrors
+ * Scriptable error constants for nsICMSMessage
+ */
+[scriptable,uuid(267f1a5b-88f7-413b-bc49-487e745282f1)]
+interface nsICMSMessageErrors : nsISupports
+{
+ const long SUCCESS = 0;
+ const long GENERAL_ERROR = 1;
+ const long VERIFY_NOT_SIGNED = 1024;
+ const long VERIFY_NO_CONTENT_INFO = 1025;
+ const long VERIFY_BAD_DIGEST = 1026;
+ const long VERIFY_NOCERT = 1028;
+ const long VERIFY_UNTRUSTED = 1029;
+ const long VERIFY_ERROR_UNVERIFIED = 1031;
+ const long VERIFY_ERROR_PROCESSING = 1032;
+ const long VERIFY_BAD_SIGNATURE = 1033;
+ const long VERIFY_DIGEST_MISMATCH = 1034;
+ const long VERIFY_UNKNOWN_ALGO = 1035;
+ const long VERIFY_UNSUPPORTED_ALGO = 1036;
+ const long VERIFY_MALFORMED_SIGNATURE = 1037;
+ const long VERIFY_HEADER_MISMATCH = 1038;
+ const long VERIFY_NOT_YET_ATTEMPTED = 1039;
+ const long VERIFY_CERT_WITHOUT_ADDRESS = 1040;
+
+ const long ENCRYPT_NO_BULK_ALG = 1056;
+ const long ENCRYPT_INCOMPLETE = 1057;
+};
diff --git a/mailnews/mime/public/nsICMSSecureMessage.idl b/mailnews/mime/public/nsICMSSecureMessage.idl
new file mode 100644
index 000000000..6442e319a
--- /dev/null
+++ b/mailnews/mime/public/nsICMSSecureMessage.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIX509Cert;
+
+/**
+ * nsICMSManager (service)
+ * Interface to access users certificate store
+ */
+[scriptable, uuid(17103436-0111-4819-a751-0fc4aa6e3d79)]
+interface nsICMSSecureMessage : nsISupports
+{
+ /**
+ * getCertByPrefID - a BASE64 string representing a user's
+ * certificate (or NULL if there isn't one)
+ */
+ string getCertByPrefID(in string certID);
+
+ /**
+ * decodeCert - decode a BASE64 string into an X509Certificate object
+ */
+ nsIX509Cert decodeCert(in string value);
+
+ /**
+ * sendMessage - send a text message to the recipient indicated
+ * by the base64-encoded cert.
+ */
+ string sendMessage(in string msg, in string cert);
+
+ /**
+ * receiveMessage - receive an encrypted (enveloped) message
+ */
+ string receiveMessage(in string msg);
+};
+
+%{C++
+#define NS_CMSSECUREMESSAGE_CONTRACTID "@mozilla.org/nsCMSSecureMessage;1"
+%}
diff --git a/mailnews/mime/public/nsIMimeContentTypeHandler.h b/mailnews/mime/public/nsIMimeContentTypeHandler.h
new file mode 100644
index 000000000..f1b828673
--- /dev/null
+++ b/mailnews/mime/public/nsIMimeContentTypeHandler.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This interface is implemented by content type handlers that will be
+ * called upon by libmime to process various attachments types. The primary
+ * purpose of these handlers will be to represent the attached data in a
+ * viewable HTML format that is useful for the user
+ *
+ * Note: These will all register by their content type prefixed by the
+ * following: mimecth:text/vcard
+ *
+ * libmime will then use the XPCOM Component Manager to
+ * locate the appropriate Content Type handler
+ */
+#ifndef nsIMimeContentTypeHandler_h_
+#define nsIMimeContentTypeHandler_h_
+
+typedef struct {
+ bool force_inline_display;
+} contentTypeHandlerInitStruct;
+
+#include "nsISupports.h"
+#include "mimecth.h"
+
+// {20DABD99-F8B5-11d2-8EE0-00A024A7D144}
+#define NS_IMIME_CONTENT_TYPE_HANDLER_IID \
+ { 0x20dabd99, 0xf8b5, 0x11d2, \
+ { 0x8e, 0xe0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } }
+
+// {20DABDA1-F8B5-11d2-8EE0-00A024A7D144}
+#define NS_VCARD_CONTENT_TYPE_HANDLER_CID \
+ { 0x20dabda1, 0xf8b5, 0x11d2, \
+ { 0x8e, 0xe0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } }
+
+#define NS_SMIME_CONTENT_TYPE_HANDLER_CID \
+ { 0x20dabdac, 0xf8b5, 0x11d2, \
+ { 0xFF, 0xe0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } }
+
+#define NS_SIGNED_CONTENT_TYPE_HANDLER_CID \
+ { 0x20dabdac, 0xf8b5, 0x11d2, \
+ { 0xFF, 0xe0, 0x0, 0xaf, 0x19, 0xa7, 0xd1, 0x44 } }
+
+#define NS_PGPMIME_CONTENT_TYPE_HANDLER_CID \
+ { 0x212f415f, 0xf8b5, 0x11d2, \
+ { 0xFF, 0xe0, 0x0, 0xaf, 0x19, 0xa7, 0xd1, 0x44 } }
+
+
+class nsIMimeContentTypeHandler : public nsISupports {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMIME_CONTENT_TYPE_HANDLER_IID)
+
+ NS_IMETHOD GetContentType(char **contentType) = 0;
+
+ NS_IMETHOD CreateContentTypeHandlerClass(const char *content_type,
+ contentTypeHandlerInitStruct *initStruct,
+ MimeObjectClass **objClass) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMimeContentTypeHandler,
+ NS_IMIME_CONTENT_TYPE_HANDLER_IID)
+
+#endif /* nsIMimeContentTypeHandler_h_ */
diff --git a/mailnews/mime/public/nsIMimeConverter.idl b/mailnews/mime/public/nsIMimeConverter.idl
new file mode 100644
index 000000000..05aae7662
--- /dev/null
+++ b/mailnews/mime/public/nsIMimeConverter.idl
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Encode/decode mail headers (via libmime).
+ */
+[scriptable, uuid(0d3f5531-2dbe-40d3-9280-f6ac45a6f5e0)]
+interface nsIMimeConverter : nsISupports {
+ /**
+ * Suggested byte length limit for use when calling encodeMimePartIIStr_UTF8.
+ */
+ const long MIME_ENCODED_WORD_SIZE = 72;
+ const long MAX_CHARSET_NAME_LENGTH = 64;
+
+ /**
+ * Encode a UTF-8 string into a form containing only ASCII characters using
+ * RFC 2047 encoded words where necessary.
+ *
+ * Note that, although allowed for the present time, encoding to charsets
+ * other than UTF-8 is considered deprecated.
+ *
+ * @param aHeader UTF-8 header to encode.
+ * @param aAddressingHeader Is the header a list of email addresses?
+ * @param aMailCharset Charset to use when encoding (see above for note).
+ * @param aFieldNameLen Header field name length (ex: "From: " = 6)
+ * @param aMaxLineLen Maximum length of an individual line. Use
+ * MIME_ENCODED_WORD_SIZE for best results.
+ *
+ * @return The encoded header.
+ */
+ AUTF8String encodeMimePartIIStr_UTF8(in AUTF8String aHeader,
+ in boolean aAddressingHeader,
+ in string aMailCharset,
+ in long aFieldNameLen,
+ in long aMaxLineLen);
+
+ /**
+ * Decode a MIME header to UTF-8 if conversion is required. Marked as
+ * noscript because the return value may contain non-ASCII characters.
+ *
+ * @param header A (possibly encoded) header to decode.
+ * @param default_charset The charset to apply to un-labeled non-UTF-8 data.
+ * @param override_charset If true, default_charset is used instead of any
+ * charset labeling other than UTF-8.
+ * @param eatContinuations If true, unfold headers.
+ *
+ * @return UTF-8 encoded value if conversion was required, nullptr if no
+ * conversion was required.
+ */
+ AUTF8String decodeMimeHeaderToUTF8(in ACString header,
+ in string default_charset,
+ in boolean override_charset,
+ in boolean eatContinuations);
+
+ /**
+ * Decode a MIME header to UTF-16.
+ *
+ * @param header A (possibly encoded) header to decode.
+ * @param default_charset The charset to apply to un-labeled non-UTF-8 data.
+ * @param override_charset If true, default_charset is used instead of any
+ * charset labeling other than UTF-8.
+ * @param eatContinuations If true, unfold headers.
+ *
+ * @return UTF-16 encoded value as an AString.
+ */
+ AString decodeMimeHeader(in string header,
+ in string default_charset,
+ in boolean override_charset,
+ in boolean eatContinuations);
+};
+
diff --git a/mailnews/mime/public/nsIMimeEmitter.idl b/mailnews/mime/public/nsIMimeEmitter.idl
new file mode 100644
index 000000000..b129b4e86
--- /dev/null
+++ b/mailnews/mime/public/nsIMimeEmitter.idl
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+#include "nsrootidl.idl"
+
+interface nsIOutputStream;
+interface nsIInputStream;
+interface nsIURI;
+interface nsIStreamListener;
+interface nsIChannel;
+
+[scriptable, uuid(eb9beb09-44de-4ad2-a560-f572b1afd534)]
+interface nsMimeHeaderDisplayTypes
+{
+ const long MicroHeaders = 0;
+ const long NormalHeaders = 1;
+ const long AllHeaders = 2;
+};
+
+%{C++
+#define NS_IMIME_MISC_STATUS_KEY "@mozilla.org/MimeMiscStatus;1?type="
+%}
+
+[scriptable, uuid(7a57166f-2891-4122-9a74-6c3fab0caac3)]
+interface nsIMimeEmitter : nsISupports {
+
+ // Output listener to allow access to it from mime.
+ attribute nsIStreamListener outputListener;
+
+ // These will be called to start and stop the total operation.
+ void initialize(in nsIURI url, in nsIChannel aChannel, in long aFormat);
+ void complete();
+
+ // Set the output stream/listener for processed data.
+ void setPipe(in nsIInputStream inputStream, in nsIOutputStream outStream);
+
+ // Header handling routines.
+ void startHeader(in boolean rootMailHeader, in boolean headerOnly,
+ [const] in string msgID, [const] in string outCharset);
+ void addHeaderField([const] in string field, [const] in string value);
+ void addAllHeaders(in ACString allheaders);
+
+ /**
+ * Write the HTML Headers for the current attachment.
+ * Note: Book case this with an EndHeader call.
+ *
+ * @param name The name of this attachment.
+ */
+ void writeHTMLHeaders([const] in AUTF8String name);
+
+ /**
+ * Finish writing the headers for the current attachment.
+ *
+ * @param name The name of this attachment.
+ */
+ void endHeader([const] in AUTF8String name);
+
+ void updateCharacterSet([const] in string aCharset);
+
+ // Attachment handling routines.
+ void startAttachment([const] in AUTF8String name,
+ [const] in string contentType,
+ [const] in string url, in boolean aNotDownloaded);
+ void addAttachmentField([const] in string field, [const] in string value);
+ void endAttachment();
+
+ void endAllAttachments();
+
+ // Body handling routines.
+ void startBody(in boolean bodyOnly, [const] in string msgID, [const] in string outCharset);
+ void writeBody([const] in AUTF8String buf, out uint32_t amountWritten);
+ void endBody();
+
+ // Generic write routine. This is necessary for output that
+ // libmime needs to pass through without any particular parsing
+ // involved (i.e. decoded images, HTML Body Text, etc...
+ void write([const] in ACString buf, out uint32_t amountWritten);
+ void utilityWrite([const] in string buf);
+};
diff --git a/mailnews/mime/public/nsIMimeHeaders.idl b/mailnews/mime/public/nsIMimeHeaders.idl
new file mode 100644
index 000000000..3a0d05c49
--- /dev/null
+++ b/mailnews/mime/public/nsIMimeHeaders.idl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgIStructuredHeaders.idl"
+
+%{C++
+#define NS_IMIMEHEADERS_CONTRACTID \
+ "@mozilla.org/messenger/mimeheaders;1"
+%}
+
+/**
+ * An interface that can extract individual headers from a body of headers.
+ */
+[scriptable, uuid(a9222679-b991-4786-8314-f8819c3a2ba3)]
+interface nsIMimeHeaders : msgIStructuredHeaders {
+ /// Feed in the text of headers
+ void initialize(in ACString allHeaders);
+
+ /**
+ * Get the text of a header.
+ *
+ * Leading and trailing whitespace from headers will be stripped from the
+ * return value. If getAllOfThem is set to true, then the returned string will
+ * have all of the values of the header, in order, joined with the ',\r\n\t'.
+ *
+ * If the header is not present, then the returned value is NULL.
+ */
+ ACString extractHeader(in string headerName, in boolean getAllOfThem);
+
+ /**
+ * The current text of all header data.
+ *
+ * Unlike the asMimeText property, this result preserves the original
+ * representation of the header text, including alternative line endings or
+ * custom, non-8-bit text. For instances of this interface, this attribute is
+ * usually preferable to asMimeText.
+ */
+ readonly attribute ACString allHeaders;
+};
diff --git a/mailnews/mime/public/nsIMimeMiscStatus.idl b/mailnews/mime/public/nsIMimeMiscStatus.idl
new file mode 100644
index 000000000..3621cdb85
--- /dev/null
+++ b/mailnews/mime/public/nsIMimeMiscStatus.idl
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+#include "nsrootidl.idl"
+
+interface nsIChannel;
+interface nsIMsgMailNewsUrl;
+interface nsIUTF8StringEnumerator;
+interface nsIMsgDBHdr;
+interface nsIURI;
+interface nsIWritablePropertyBag2;
+
+[scriptable, uuid(4644FB25-5255-11d3-82B8-444553540002)]
+interface nsIMimeMiscStatus : nsISupports{
+
+ string GetWindowXULandJS();
+ string GetGlobalXULandJS();
+ string GetIndividualXUL(in string aName, in string aHeader, in string aEmail);
+
+ long GetMiscStatus(in string aName, in string aEmail);
+ string GetImageURL(in long aStatus);
+};
+
+// this is a simple interface which allows someone to listen to all the headers
+// that are discovered by mime. We can use this when displaying a message to update
+// the msg header in JS.
+[scriptable, uuid(e0e821f0-cecf-4cb3-be5b-ee58b6868343)]
+interface nsIMsgHeaderSink : nsISupports
+{
+ // You must finish consuming the iterators before returning from processHeaders. aHeaderNames and aHeaderValues will ALWAYS have the same
+ // number of elements in them
+ void processHeaders(in nsIUTF8StringEnumerator aHeaderNames, in nsIUTF8StringEnumerator aHeaderValues, in boolean dontCollectAddress);
+
+ void handleAttachment(in string contentType, in string url, in wstring displayName, in string uri,
+ in boolean aNotDownloaded);
+
+ /**
+ * Add a metadata field to the current attachment, e.g. "X-Mozilla-PartSize".
+ *
+ * @param field The field to add
+ * @param value The value of the field
+ */
+ void addAttachmentField(in string field, in string value);
+ void onEndAllAttachments();
+
+ // onEndMsgHeaders is called after libmime is done processing a message. At this point it is safe for
+ // elements like the UI to update junk status, process return receipts, etc.
+ void onEndMsgHeaders(in nsIMsgMailNewsUrl url);
+
+ // onEndMsgDownload is triggered when layout says it is actually done rendering
+ // the message body in the UI.
+ void onEndMsgDownload(in nsIMsgMailNewsUrl url);
+
+ attribute nsISupports securityInfo;
+
+ /**
+ * onMsgHasRemoteContent is called each time content policy encounters remote
+ * content that it will block from loading.
+ * @param aMsgHdr header of the message the content is located in
+ * @param aContentURI location that will be blocked.
+ * @param aCanOverride can the blocking be overridden or not
+ */
+ void onMsgHasRemoteContent(in nsIMsgDBHdr aMsgHdr, in nsIURI aContentURI, in boolean aCanOverride);
+
+ readonly attribute nsIMsgDBHdr dummyMsgHeader;
+
+ // used as a hook for extension mime content handlers to store data that can later
+ // be accessed by other parts of the code, e.g., UI code.
+ // TODO - Should replace securityInfo
+ readonly attribute nsIWritablePropertyBag2 properties;
+ // When streaming a new message, properties should be reset, so that there are
+ // not previous values lurking around.
+ void resetProperties();
+};
diff --git a/mailnews/mime/public/nsIMimeObjectClassAccess.h b/mailnews/mime/public/nsIMimeObjectClassAccess.h
new file mode 100644
index 000000000..55d7a86ad
--- /dev/null
+++ b/mailnews/mime/public/nsIMimeObjectClassAccess.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This interface is implemented by libmime. This interface is used by
+ * a Content-Type handler "Plug In" (i.e. vCard) for accessing various
+ * internal information about the object class system of libmime. When
+ * libmime progresses to a C++ object class, this would probably change.
+ */
+#ifndef nsIMimeObjectClassAccess_h_
+#define nsIMimeObjectClassAccess_h_
+
+// {C09EDB23-B7AF-11d2-B35E-525400E2D63A}
+#define NS_IMIME_OBJECT_CLASS_ACCESS_IID \
+ { 0xc09edb23, 0xb7af, 0x11d2, \
+ { 0xb3, 0x5e, 0x52, 0x54, 0x0, 0xe2, 0xd6, 0x3a } }
+
+class nsIMimeObjectClassAccess : public nsISupports {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMIME_OBJECT_CLASS_ACCESS_IID)
+
+ // These methods are all implemented by libmime to be used by
+ // content type handler plugins for processing stream data.
+
+ // This is the write call for outputting processed stream data.
+ NS_IMETHOD MimeObjectWrite(void *mimeObject,
+ char *data,
+ int32_t length,
+ bool user_visible_p) = 0;
+
+ // The following group of calls expose the pointers for the object
+ // system within libmime.
+ NS_IMETHOD GetmimeInlineTextClass(void **ptr) = 0;
+ NS_IMETHOD GetmimeLeafClass(void **ptr) = 0;
+ NS_IMETHOD GetmimeObjectClass(void **ptr) = 0;
+ NS_IMETHOD GetmimeContainerClass(void **ptr) = 0;
+ NS_IMETHOD GetmimeMultipartClass(void **ptr) = 0;
+ NS_IMETHOD GetmimeMultipartSignedClass(void **ptr) = 0;
+ NS_IMETHOD GetmimeEncryptedClass(void **ptr) = 0;
+
+ NS_IMETHOD MimeCreate(char* content_type, void * hdrs, void * opts, void **ptr) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMimeObjectClassAccess,
+ NS_IMIME_OBJECT_CLASS_ACCESS_IID)
+
+#endif /* nsIMimeObjectClassAccess_h_ */
diff --git a/mailnews/mime/public/nsIMimeStreamConverter.idl b/mailnews/mime/public/nsIMimeStreamConverter.idl
new file mode 100644
index 000000000..bde6175c4
--- /dev/null
+++ b/mailnews/mime/public/nsIMimeStreamConverter.idl
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsrootidl.idl"
+#include "nsIMimeHeaders.idl"
+#include "nsIMsgIdentity.idl"
+#include "nsIMsgHdr.idl"
+
+interface nsIURI;
+
+typedef long nsMimeOutputType;
+
+[scriptable, uuid(fdc2956e-d558-43fb-bfdd-fb9511229aa5)]
+interface nsMimeOutput
+{
+ const long nsMimeMessageSplitDisplay = 0;
+ const long nsMimeMessageHeaderDisplay = 1;
+ const long nsMimeMessageBodyDisplay = 2;
+ const long nsMimeMessageQuoting = 3;
+ const long nsMimeMessageBodyQuoting = 4;
+ const long nsMimeMessageRaw = 5;
+ const long nsMimeMessageDraftOrTemplate = 6;
+ const long nsMimeMessageEditorTemplate = 7;
+ const long nsMimeMessagePrintOutput = 9;
+ const long nsMimeMessageSaveAs = 10;
+ const long nsMimeMessageSource = 11;
+ const long nsMimeMessageFilterSniffer = 12;
+ const long nsMimeMessageDecrypt = 13;
+ const long nsMimeMessageAttach = 14;
+ const long nsMimeUnknown = 15;
+};
+
+[scriptable, uuid(FA81CAA0-6261-11d3-8311-00805F2A0107)]
+interface nsIMimeStreamConverterListener : nsISupports{
+ void onHeadersReady(in nsIMimeHeaders headers);
+};
+
+/**
+ * This interface contains mailnews mime specific information for stream
+ * converters. Most of the code is just stuff that has been moved out
+ * of nsIStreamConverter.idl to make it more generic.
+ */
+[scriptable, uuid(d894c833-29c5-495b-880c-9a9f847bfdc9)]
+interface nsIMimeStreamConverter : nsISupports {
+
+ /**
+ * Set the desired mime output type on the converer.
+ */
+ void SetMimeOutputType(in nsMimeOutputType aType);
+
+ void GetMimeOutputType(out nsMimeOutputType aOutFormat);
+
+ /**
+ * This is needed by libmime for MHTML link processing...the url is the URL
+ * string associated with this input stream.
+ */
+ void SetStreamURI(in nsIURI aURI);
+
+ /**
+ * Used to extract headers while parsing a message.
+ */
+ void SetMimeHeadersListener(in nsIMimeStreamConverterListener listener, in nsMimeOutputType aType);
+
+ /**
+ * This is used for forward inline, both as a filter action, and from the UI.
+ */
+ attribute boolean forwardInline;
+
+ /**
+ * This is used for a forward inline filter action. When streaming is done,
+ * we won't open a compose window with the editor contents.
+ */
+ attribute boolean forwardInlineFilter;
+
+ /**
+ * Address for the forward inline filter to forward the message to.
+ */
+ attribute AString forwardToAddress;
+ /**
+ * Use the opposite compose format, used for forward inline.
+ */
+ attribute boolean overrideComposeFormat;
+
+ /**
+ * This is used for OpenDraft, OpenEditorTemplate and Forward inline (which use OpenDraft)
+ */
+ attribute nsIMsgIdentity identity;
+ attribute string originalMsgURI;
+ attribute nsIMsgDBHdr origMsgHdr;
+};
diff --git a/mailnews/mime/public/nsIMsgHeaderParser.idl b/mailnews/mime/public/nsIMsgHeaderParser.idl
new file mode 100644
index 000000000..fd582b64c
--- /dev/null
+++ b/mailnews/mime/public/nsIMsgHeaderParser.idl
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#define NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID \
+ "@mozilla.org/messenger/headerparser;1"
+%}
+
+/**
+ * A structured representation of an address.
+ *
+ * This is meant to correspond to the address production from RFC 5322. As a
+ * result, an instance of this interface is either a single mailbox or a group
+ * of mailboxes. The difference between the two forms is in which attribute is
+ * undefined: mailboxes leave the members attribute undefined while groups leave
+ * the email attribute undefined.
+ *
+ * For example, an address like "John Doe <jdoe@machine.example>" will, when
+ * parsed, result in an instance with the name attribute set to "John Doe", the
+ * email attribute set to "jdoe@machine.example", and the members variable left
+ * undefined.
+ *
+ * A group like "undisclosed-recipients:;" will, when parsed, result in an
+ * instance with the name attribute set to "undisclosed-recipients", the email
+ * attribute left defined, and the members variable set to an empty array.
+ *
+ * In general, the attributes of this interface are always meant to be in a form
+ * suitable for display purposes, and not in a form usable for MIME emission. In
+ * particular, email addresses could be fully internationalized and non-ASCII,
+ * RFC 2047-encoded words may appear in names, and the name or email parameters
+ * are unquoted.
+ */
+[scriptable, uuid(b19f5636-ebc4-470e-b46c-98b5fc7e88c9)]
+interface msgIAddressObject : nsISupports {
+ /// The name of the mailbox or group.
+ readonly attribute AString name;
+
+ /// The email of the mailbox.
+ readonly attribute AString email;
+
+ /**
+ * The member mailboxes of this group, which may be an empty list.
+ *
+ * Due to the limitations of XPIDL, the type of this attribute cannot be
+ * properly reflected. It is actually an array of msgIAddressObject instances,
+ * although it is instead undefined if this object does not represent a group.
+ */
+ readonly attribute jsval group;
+
+ /// Return a string form of this object that is suitable for display.
+ AString toString();
+};
+
+/**
+ * A utility service for manipulating addressing headers in email messages.
+ *
+ * This interface is designed primarily for use from JavaScript code; code in
+ * C++ should use the methods in MimeHeaderParser.h instead, as it is better
+ * designed to take advantage of C++'s features, particularly with respect to
+ * arrays.
+ *
+ * There are two methods for parsing MIME headers, one for RFC 2047-decoded
+ * strings, and one for non-RFC 2047-decoded strings.
+ *
+ * In general, this API attempts to preserve the format of addresses as
+ * faithfully as possible. No case normalization is performed at any point.
+ * However, internationalized email addresses generally need extra processing to
+ * work properly, so while this API should handle them without issues, consumers
+ * of this API may fail to work properly when presented with such addresses. To
+ * ease use for such cases, future versions of the API may incorporate necessary
+ * normalization steps to make oblivious consumers more likely to work properly.
+ */
+[scriptable, uuid(af2f9dd1-0226-4835-b981-a4f88b5e97cc)]
+interface nsIMsgHeaderParser : nsISupports {
+ /**
+ * Parse an address-based header that has not yet been 2047-decoded.
+ *
+ * The result of this method is an array of objects described in the above
+ * comment. Note that the header is a binary string that will be decoded as if
+ * passed into nsIMimeConverter.
+ *
+ * @param aEncodedHeader The RFC 2047-encoded header to parse.
+ * @param aHeaderCharset The charset to assume for raw octets.
+ * @param aPreserveGroups If false (the default), the result is a flat array
+ * of mailbox objects, containing no group objects.
+ * @return An array corresponding to the header description.
+ */
+ void parseEncodedHeader(in ACString aEncodedHeader,
+ in string aHeaderCharset,
+ [optional] in bool aPreserveGroups,
+ [optional] out unsigned long length,
+ [retval, array, size_is(length)]
+ out msgIAddressObject addresses);
+
+ /**
+ * Parse an address-based header that has not yet been 2047-decoded and does not
+ * contain raw octets but instead wide (UTF-16) characters.
+ *
+ * @param aEncodedHeader The RFC 2047-encoded header to parse.
+ * @return An array corresponding to the header description.
+ */
+ void parseEncodedHeaderW(in AString aEncodedHeader,
+ [optional] out unsigned long length,
+ [retval, array, size_is(length)]
+ out msgIAddressObject addresses);
+
+/**
+ * Parse an address-based header that has been 2047-decoded.
+ *
+ * The result of this method is an array of objects described in the above
+ * comment. Note that the header is a binary string that will be decoded as if
+ * passed into nsIMimeConverter.
+ *
+ * @param aDecodedHeader The non-RFC 2047-encoded header to parse.
+ * @param aPreserveGroups If false (the default), the result is a flat array
+ * of mailbox objects, containing no group objects.
+ * @return An array corresponding to the header description.
+ */
+ void parseDecodedHeader(in AString aDecodedHeader,
+ [optional] in bool aPreserveGroups,
+ [optional] out unsigned long length,
+ [retval, array, size_is(length)]
+ out msgIAddressObject addresses);
+
+ /**
+ * Given an array of addresses, make a MIME header suitable for emission.
+ *
+ * The return value of this method is not directly suitable for use in a MIME
+ * message but rather needs to be passed through nsIMimeConverter first to
+ * have RFC-2047 encoding applied and the resulting output wrapped to adhere
+ * to maximum line length formats.
+ *
+ * @param aAddresses An array corresponding to the header description.
+ * @param aLength The length of said array of addresses.
+ * @return A string that is suitable for writing in a MIME message.
+ */
+ AString makeMimeHeader([array, size_is(aLength)]
+ in msgIAddressObject aAddresses,
+ in unsigned long aLength);
+
+ /**
+ * Return the first address in the list in a format suitable for display.
+ *
+ * This is largely a convience method for handling From headers (or similar),
+ * which are expected to only have a single element in them. It is exactly
+ * equivalent to saying (parseDecodedHeader(decodedHeader))[0].toString().
+ *
+ * @param decodedHeader The non-RFC 2047-encoded header to parse.
+ * @return The first address, suitable for display.
+ */
+ AString extractFirstName(in AString aDecodedHeader);
+
+ /**
+ * Returns a copy of the input which may have had some addresses removed.
+ * Addresses are removed if they are already in either of the supplied
+ * address lists.
+ *
+ * Addresses are considered to be the same if they contain the same email
+ * part (case-insensitive). Since the email part should never be RFC
+ * 2047-encoded, this method should work whether or not the header is
+ * RFC 2047-encoded.
+ *
+ * @param aAddrs The addresses to remove duplicates from.
+ * @param aOtherAddrs Other addresses that the duplicate removal process also
+ * checks for duplicates against. Addresses in this list
+ * will not be added to the result.
+ * @return The original header with duplicate addresses removed.
+ */
+ AUTF8String removeDuplicateAddresses(in AUTF8String aAddrs,
+ [optional] in AUTF8String aOtherAddrs);
+
+ /// Return a structured mailbox object having the given name and email.
+ msgIAddressObject makeMailboxObject(in AString aName, in AString aEmail);
+
+ /// Return a structured group object having the given name and members.
+ msgIAddressObject makeGroupObject(in AString aName,
+ [array, size_is(aLength)] in msgIAddressObject aMembers,
+ in unsigned long aLength);
+
+ /**
+ * Return an array of structured mailbox objects for the given display name
+ * string.
+ *
+ * The string is expected to be a comma-separated sequence of strings that
+ * would be produced by msgIAddressObject::toString(). For example, the string
+ * "Bond, James <agent007@mi5.invalid>" would produce one address object,
+ * while the string "webmaster@nowhere.invalid, child@nowhere.invalid" would
+ * produce two address objects.
+ */
+ void makeFromDisplayAddress(in AString aDisplayAddresses,
+ [optional] out unsigned long count,
+ [retval, array, size_is(count)] out msgIAddressObject addresses);
+
+ [deprecated] void parseHeadersWithArray(in wstring aLine,
+ [array, size_is(count)] out wstring aEmailAddresses,
+ [array, size_is(count)] out wstring aNames,
+ [array, size_is(count)] out wstring aFullNames,
+ [retval] out unsigned long count);
+
+
+ /**
+ * Given a string which contains a list of Header addresses, returns a
+ * comma-separated list of just the `mailbox' portions.
+ *
+ * @param aLine The header line to parse.
+ * @return A comma-separated list of just the mailbox parts
+ * of the email-addresses.
+ */
+ [deprecated] ACString extractHeaderAddressMailboxes(in ACString aLine);
+
+ /**
+ * Given a string which contains a list of Header addresses, returns a
+ * comma-separated list of just the `user name' portions. If any of
+ * the addresses doesn't have a name, then the mailbox is used instead.
+ *
+ * @param aLine The header line to parse.
+ * @return A comma-separated list of just the name parts
+ * of the addresses.
+ */
+ [deprecated] AUTF8String extractHeaderAddressNames(in AUTF8String aLine);
+
+ /*
+ * Like extractHeaderAddressNames, but only returns the first name in the
+ * header if there is one. This function will return unquoted strings suitable
+ * for display.
+ *
+ * @param aLine The header line to parse.
+ * @return The first name found in the list.
+ */
+ [deprecated] AUTF8String extractHeaderAddressName(in AUTF8String aLine);
+
+ /**
+ * Given a name and email address, produce a string that is suitable for
+ * emitting in a MIME header (after applying RFC 2047 encoding).
+ *
+ * @note This is a temporary method.
+ */
+ [deprecated] AString makeMimeAddress(in AString aName, in AString aEmail);
+};
+
diff --git a/mailnews/mime/public/nsIPgpMimeProxy.idl b/mailnews/mime/public/nsIPgpMimeProxy.idl
new file mode 100644
index 000000000..375a7b776
--- /dev/null
+++ b/mailnews/mime/public/nsIPgpMimeProxy.idl
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+#include "nsIURI.idl"
+
+%{C++
+typedef int (*MimeDecodeCallbackFun)(const char *buf, int32_t buf_size, void *output_closure);
+
+#define NS_PGPMIMEPROXY_CLASSNAME "PGP/Mime Decryption"
+#define NS_PGPMIMEPROXY_CONTRACTID "@mozilla.org/mime/pgp-mime-decrypt;1"
+
+#define NS_PGPMIMEPROXY_CID \
+{ /* 6b7e094f-536b-40dc-b3a4-e3d729205ce1 */ \
+ 0x6b7e094f, 0x536b, 0x40dc, \
+{0xb3, 0xa4, 0xe3, 0xd7, 0x29, 0x20, 0x5c, 0xe1 } }
+%}
+
+native MimeDecodeCallbackFun(MimeDecodeCallbackFun);
+
+/**
+ * nsIPgpMimeProxy is a proxy for a (JS-)addon for OpenPGP/MIME decryption
+ */
+
+[scriptable, uuid(6b7e094f-536b-40dc-b3a4-e3d729205ce1)]
+interface nsIPgpMimeProxy : nsIStreamListener
+{
+ /**
+ * set the decoder callback into mimelib
+ */
+ [noscript] void setMimeCallback(in MimeDecodeCallbackFun outputFun,
+ in voidPtr outputClosure,
+ in nsIURI myUri);
+
+ /**
+ * init function
+ */
+ void init();
+
+ /**
+ * process encoded data received from mimelib
+ */
+ void write(in string buf, in unsigned long count);
+
+ /**
+ * finish writing (EOF) from mimelib
+ */
+ void finish();
+
+ /**
+ * the listener that receives the OpenPGP/MIME data stream and decrypts
+ * the message
+ */
+ attribute nsIStreamListener decryptor;
+
+ attribute ACString contentType;
+
+ /**
+ * The particular part number of the multipart object we are working on. The
+ * numbering is the same as in URLs that use the form "...?part=1.1.2".
+ *
+ * The value stored in mimePart is only the number, e.g. "1" or "1.1.2"
+ */
+ attribute ACString mimePart;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
diff --git a/mailnews/mime/public/nsISimpleMimeConverter.idl b/mailnews/mime/public/nsISimpleMimeConverter.idl
new file mode 100644
index 000000000..907a1bb0e
--- /dev/null
+++ b/mailnews/mime/public/nsISimpleMimeConverter.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIURI;
+
+[scriptable, uuid(FC6E8234-BBF3-44A1-9802-5F023A929173)]
+interface nsISimpleMimeConverter : nsISupports
+{
+ // uri of message getting displayed
+ attribute nsIURI uri;
+ AUTF8String convertToHTML(in ACString contentType,
+ in AUTF8String data);
+};
+
+%{C++
+
+#define NS_SIMPLEMIMECONVERTERS_CATEGORY "simple-mime-converters"
+
+%}
diff --git a/mailnews/mime/public/nsMailHeaders.h b/mailnews/mime/public/nsMailHeaders.h
new file mode 100644
index 000000000..d8a3131a5
--- /dev/null
+++ b/mailnews/mime/public/nsMailHeaders.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This interface allows any module to access the encoder/decoder
+ * routines for RFC822 headers. This will allow any mail/news module
+ * to call on these routines.
+ */
+#ifndef nsMailHeaders_h_
+#define nsMailHeaders_h_
+
+/*
+ * These are the defines for standard header field names.
+ */
+#define HEADER_BCC "BCC"
+#define HEADER_CC "CC"
+#define HEADER_CONTENT_BASE "Content-Base"
+#define HEADER_CONTENT_LOCATION "Content-Location"
+#define HEADER_CONTENT_ID "Content-ID"
+#define HEADER_CONTENT_DESCRIPTION "Content-Description"
+#define HEADER_CONTENT_DISPOSITION "Content-Disposition"
+#define HEADER_CONTENT_ENCODING "Content-Encoding"
+#define HEADER_CONTENT_LANGUAGE "Content-Language"
+#define HEADER_CONTENT_LENGTH "Content-Length"
+#define HEADER_CONTENT_NAME "Content-Name"
+#define HEADER_CONTENT_TRANSFER_ENCODING "Content-Transfer-Encoding"
+#define HEADER_CONTENT_TYPE "Content-Type"
+#define HEADER_DATE "Date"
+#define HEADER_DISTRIBUTION "Distribution"
+#define HEADER_FCC "FCC"
+#define HEADER_FOLLOWUP_TO "Followup-To"
+#define HEADER_FROM "From"
+#define HEADER_STATUS "Status"
+#define HEADER_LINES "Lines"
+#define HEADER_LIST_POST "List-Post"
+#define HEADER_MAIL_FOLLOWUP_TO "Mail-Followup-To"
+#define HEADER_MAIL_REPLY_TO "Mail-Reply-To"
+#define HEADER_MESSAGE_ID "Message-ID"
+#define HEADER_MIME_VERSION "MIME-Version"
+#define HEADER_NEWSGROUPS "Newsgroups"
+#define HEADER_ORGANIZATION "Organization"
+#define HEADER_REFERENCES "References"
+#define HEADER_REPLY_TO "Reply-To"
+#define HEADER_RESENT_COMMENTS "Resent-Comments"
+#define HEADER_RESENT_DATE "Resent-Date"
+#define HEADER_RESENT_FROM "Resent-From"
+#define HEADER_RESENT_MESSAGE_ID "Resent-Message-ID"
+#define HEADER_RESENT_SENDER "Resent-Sender"
+#define HEADER_RESENT_TO "Resent-To"
+#define HEADER_RESENT_CC "Resent-CC"
+#define HEADER_SENDER "Sender"
+#define HEADER_SUBJECT "Subject"
+#define HEADER_TO "To"
+#define HEADER_APPROVED_BY "Approved-By"
+#define HEADER_X_MAILER "X-Mailer"
+#define HEADER_USER_AGENT "User-Agent"
+#define HEADER_X_NEWSREADER "X-Newsreader"
+#define HEADER_X_POSTING_SOFTWARE "X-Posting-Software"
+#define HEADER_X_MOZILLA_STATUS "X-Mozilla-Status"
+#define HEADER_X_MOZILLA_STATUS2 "X-Mozilla-Status2"
+#define HEADER_X_MOZILLA_NEWSHOST "X-Mozilla-News-Host"
+#define HEADER_X_MOZILLA_DRAFT_INFO "X-Mozilla-Draft-Info"
+#define HEADER_X_UIDL "X-UIDL"
+#define HEADER_XREF "XREF"
+#define HEADER_X_SUN_CHARSET "X-Sun-Charset"
+#define HEADER_X_SUN_CONTENT_LENGTH "X-Sun-Content-Length"
+#define HEADER_X_SUN_CONTENT_LINES "X-Sun-Content-Lines"
+#define HEADER_X_SUN_DATA_DESCRIPTION "X-Sun-Data-Description"
+#define HEADER_X_SUN_DATA_NAME "X-Sun-Data-Name"
+#define HEADER_X_SUN_DATA_TYPE "X-Sun-Data-Type"
+#define HEADER_X_SUN_ENCODING_INFO "X-Sun-Encoding-Info"
+#define HEADER_X_PRIORITY "X-Priority"
+
+#define HEADER_PARM_CHARSET "charset"
+#define HEADER_PARM_START "start"
+#define HEADER_PARM_BOUNDARY "BOUNDARY"
+#define HEADER_PARM_FILENAME "FILENAME"
+#define HEADER_PARM_NAME "NAME"
+#define HEADER_PARM_TYPE "TYPE"
+
+#define HEADER_X_MOZILLA_PART_URL "X-Mozilla-PartURL"
+#define HEADER_X_MOZILLA_PART_SIZE "X-Mozilla-PartSize"
+#define HEADER_X_MOZILLA_PART_DOWNLOADED "X-Mozilla-PartDownloaded"
+#define HEADER_X_MOZILLA_CLOUD_PART "X-Mozilla-Cloud-Part"
+#define HEADER_X_MOZILLA_IDENTITY_KEY "X-Identity-Key"
+#define HEADER_X_MOZILLA_ACCOUNT_KEY "X-Account-Key"
+#define HEADER_X_MOZILLA_KEYWORDS "X-Mozilla-Keys"
+#endif /* nsMailHeaders_h_ */
diff --git a/mailnews/mime/public/nsMsgMimeCID.h b/mailnews/mime/public/nsMsgMimeCID.h
new file mode 100644
index 000000000..07dec95e5
--- /dev/null
+++ b/mailnews/mime/public/nsMsgMimeCID.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMessageMimeCID_h__
+#define nsMessageMimeCID_h__
+
+#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID \
+ NS_ISTREAMCONVERTER_KEY "?from=message/rfc822&to=application/vnd.mozilla.xul+xml"
+
+#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID1 \
+ NS_ISTREAMCONVERTER_KEY "?from=message/rfc822&to=text/html"
+
+#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID2 \
+ NS_ISTREAMCONVERTER_KEY "?from=message/rfc822&to=*/*"
+
+#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CID \
+{ /* FAF4F9A6-60AD-11d3-989A-001083010E9B */ \
+ 0xfaf4f9a6, 0x60ad, 0x11d3, { 0x98, 0x9a, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } }
+
+#define NS_MIME_CONVERTER_CONTRACTID \
+ "@mozilla.org/messenger/mimeconverter;1"
+
+// {403B0540-B7C3-11d2-B35E-525400E2D63A}
+#define NS_MIME_OBJECT_CLASS_ACCESS_CID \
+ { 0x403b0540, 0xb7c3, 0x11d2, \
+ { 0xb3, 0x5e, 0x52, 0x54, 0x0, 0xe2, 0xd6, 0x3a } }
+
+#define NS_MIME_OBJECT_CONTRACTID \
+ "@mozilla.org/messenger/mimeobject;1"
+
+#endif // nsMessageMimeCID_h__
diff --git a/mailnews/mime/src/MimeHeaderParser.cpp b/mailnews/mime/src/MimeHeaderParser.cpp
new file mode 100644
index 000000000..15341de46
--- /dev/null
+++ b/mailnews/mime/src/MimeHeaderParser.cpp
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "mozilla/mailnews/Services.h"
+#include "mozilla/DebugOnly.h"
+#include "nsMemory.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgHeaderParser.h"
+
+namespace mozilla {
+namespace mailnews {
+
+void detail::DoConversion(const nsTArray<nsString> &aUTF16Array,
+ nsTArray<nsCString> &aUTF8Array)
+{
+ uint32_t count = aUTF16Array.Length();
+ aUTF8Array.SetLength(count);
+ for (uint32_t i = 0; i < count; ++i)
+ CopyUTF16toUTF8(aUTF16Array[i], aUTF8Array[i]);
+}
+
+void MakeMimeAddress(const nsACString &aName, const nsACString &aEmail,
+ nsACString &full)
+{
+ nsAutoString utf16Address;
+ MakeMimeAddress(NS_ConvertUTF8toUTF16(aName), NS_ConvertUTF8toUTF16(aEmail),
+ utf16Address);
+
+ CopyUTF16toUTF8(utf16Address, full);
+}
+
+void MakeMimeAddress(const nsAString &aName, const nsAString &aEmail,
+ nsAString &full)
+{
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser());
+
+ nsCOMPtr<msgIAddressObject> address;
+ headerParser->MakeMailboxObject(aName, aEmail, getter_AddRefs(address));
+ msgIAddressObject *obj = address;
+ headerParser->MakeMimeHeader(&obj, 1, full);
+}
+
+void MakeDisplayAddress(const nsAString &aName, const nsAString &aEmail,
+ nsAString &full)
+{
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser());
+
+ nsCOMPtr<msgIAddressObject> object;
+ headerParser->MakeMailboxObject(aName, aEmail, getter_AddRefs(object));
+ object->ToString(full);
+}
+
+void RemoveDuplicateAddresses(const nsACString &aHeader,
+ const nsACString &aOtherEmails,
+ nsACString &result)
+{
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser());
+
+ headerParser->RemoveDuplicateAddresses(aHeader, aOtherEmails, result);
+}
+
+/////////////////////////////////////////////
+// These are the core shim methods we need //
+/////////////////////////////////////////////
+
+nsCOMArray<msgIAddressObject> DecodedHeader(const nsAString &aHeader)
+{
+ nsCOMArray<msgIAddressObject> retval;
+ if (aHeader.IsEmpty()) {
+ return retval;
+ }
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser());
+ NS_ENSURE_TRUE(headerParser, retval);
+ msgIAddressObject **addresses = nullptr;
+ uint32_t length;
+ nsresult rv = headerParser->ParseDecodedHeader(aHeader, false,
+ &length, &addresses);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Javascript jsmime returned an error!");
+ if (NS_SUCCEEDED(rv) && length > 0 && addresses) {
+ retval.Adopt(addresses, length);
+ }
+ return retval;
+}
+
+nsCOMArray<msgIAddressObject> EncodedHeader(const nsACString &aHeader,
+ const char *aCharset)
+{
+ nsCOMArray<msgIAddressObject> retval;
+ if (aHeader.IsEmpty()) {
+ return retval;
+ }
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser());
+ NS_ENSURE_TRUE(headerParser, retval);
+ msgIAddressObject **addresses = nullptr;
+ uint32_t length;
+ nsresult rv = headerParser->ParseEncodedHeader(aHeader, aCharset,
+ false, &length, &addresses);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!");
+ if (NS_SUCCEEDED(rv) && length > 0 && addresses) {
+ retval.Adopt(addresses, length);
+ }
+ return retval;
+}
+
+nsCOMArray<msgIAddressObject> EncodedHeaderW(const nsAString &aHeader)
+{
+ nsCOMArray<msgIAddressObject> retval;
+ if (aHeader.IsEmpty()) {
+ return retval;
+ }
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser());
+ NS_ENSURE_TRUE(headerParser, retval);
+ msgIAddressObject **addresses = nullptr;
+ uint32_t length;
+ nsresult rv = headerParser->ParseEncodedHeaderW(aHeader, &length, &addresses);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!");
+ if (NS_SUCCEEDED(rv) && length > 0 && addresses) {
+ retval.Adopt(addresses, length);
+ }
+ return retval;
+}
+
+void ExtractAllAddresses(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsTArray<nsString> &names, nsTArray<nsString> &emails)
+{
+ uint32_t count = aHeader.Length();
+
+ // Prefill arrays before we start
+ names.SetLength(count);
+ emails.SetLength(count);
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ aHeader[i]->GetName(names[i]);
+ aHeader[i]->GetEmail(emails[i]);
+ }
+
+ if (count == 1 && names[0].IsEmpty() && emails[0].IsEmpty())
+ {
+ names.Clear();
+ emails.Clear();
+ }
+}
+
+void ExtractDisplayAddresses(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsTArray<nsString> &displayAddrs)
+{
+ uint32_t count = aHeader.Length();
+
+ displayAddrs.SetLength(count);
+ for (uint32_t i = 0; i < count; i++)
+ aHeader[i]->ToString(displayAddrs[i]);
+
+ if (count == 1 && displayAddrs[0].IsEmpty())
+ displayAddrs.Clear();
+}
+
+/////////////////////////////////////////////////
+// All of these are based on the above methods //
+/////////////////////////////////////////////////
+
+void ExtractEmails(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsTArray<nsString> &emails)
+{
+ nsTArray<nsString> names;
+ ExtractAllAddresses(aHeader, names, emails);
+}
+
+void ExtractEmail(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsACString &email)
+{
+ AutoTArray<nsString, 1> names;
+ AutoTArray<nsString, 1> emails;
+ ExtractAllAddresses(aHeader, names, emails);
+
+ if (emails.Length() > 0)
+ CopyUTF16toUTF8(emails[0], email);
+ else
+ email.Truncate();
+}
+
+void ExtractFirstAddress(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsACString &name, nsACString &email)
+{
+ AutoTArray<nsString, 1> names, emails;
+ ExtractAllAddresses(aHeader, names, emails);
+ if (names.Length() > 0)
+ {
+ CopyUTF16toUTF8(names[0], name);
+ CopyUTF16toUTF8(emails[0], email);
+ }
+ else
+ {
+ name.Truncate();
+ email.Truncate();
+ }
+}
+
+void ExtractFirstAddress(const nsCOMArray<msgIAddressObject> &aHeader,
+ nsAString &name, nsACString &email)
+{
+ AutoTArray<nsString, 1> names, emails;
+ ExtractAllAddresses(aHeader, names, emails);
+ if (names.Length() > 0)
+ {
+ name = names[0];
+ CopyUTF16toUTF8(emails[0], email);
+ }
+ else
+ {
+ name.Truncate();
+ email.Truncate();
+ }
+}
+
+void ExtractName(const nsCOMArray<msgIAddressObject> &aHeader, nsACString &name)
+{
+ nsCString email;
+ ExtractFirstAddress(aHeader, name, email);
+ if (name.IsEmpty())
+ name = email;
+}
+
+void ExtractName(const nsCOMArray<msgIAddressObject> &aHeader, nsAString &name)
+{
+ AutoTArray<nsString, 1> names;
+ AutoTArray<nsString, 1> emails;
+ ExtractAllAddresses(aHeader, names, emails);
+ if (names.Length() > 0)
+ {
+ if (names[0].IsEmpty())
+ name = emails[0];
+ else
+ name = names[0];
+ }
+ else
+ {
+ name.Truncate();
+ }
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/mailnews/mime/src/comi18n.cpp b/mailnews/mime/src/comi18n.cpp
new file mode 100644
index 000000000..7425e32ff
--- /dev/null
+++ b/mailnews/mime/src/comi18n.cpp
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "comi18n.h"
+#include "nsIStringCharsetDetector.h"
+#include "nsMsgUtils.h"
+#include "nsICharsetConverterManager.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgMimeCID.h"
+#include "nsIMimeConverter.h"
+
+
+////////////////////////////////////////////////////////////////////////////////
+// BEGIN PUBLIC INTERFACE
+extern "C" {
+
+
+void MIME_DecodeMimeHeader(const char *header, const char *default_charset,
+ bool override_charset, bool eatContinuations,
+ nsACString &result)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMimeConverter> mimeConverter =
+ do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ result.Truncate();
+ return;
+ }
+ mimeConverter->DecodeMimeHeaderToUTF8(nsDependentCString(header),
+ default_charset, override_charset,
+ eatContinuations, result);
+}
+
+// UTF-8 utility functions.
+//detect charset soly based on aBuf. return in aCharset
+nsresult
+MIME_detect_charset(const char *aBuf, int32_t aLength, const char** aCharset)
+{
+ nsresult res = NS_ERROR_UNEXPECTED;
+ nsString detector_name;
+ *aCharset = nullptr;
+
+ NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, "intl.charset.detector", EmptyString(), detector_name);
+
+ if (!detector_name.IsEmpty()) {
+ nsAutoCString detector_contractid;
+ detector_contractid.AssignLiteral(NS_STRCDETECTOR_CONTRACTID_BASE);
+ detector_contractid.Append(NS_ConvertUTF16toUTF8(detector_name));
+ nsCOMPtr<nsIStringCharsetDetector> detector = do_CreateInstance(detector_contractid.get(), &res);
+ if (NS_SUCCEEDED(res)) {
+ nsDetectionConfident oConfident;
+ res = detector->DoIt(aBuf, aLength, aCharset, oConfident);
+ if (NS_SUCCEEDED(res) && (eBestAnswer == oConfident || eSureAnswer == oConfident)) {
+ return NS_OK;
+ }
+ }
+ }
+ return res;
+}
+
+//Get unicode decoder(from inputcharset to unicode) for aInputCharset
+nsresult
+MIME_get_unicode_decoder(const char* aInputCharset, nsIUnicodeDecoder **aDecoder)
+{
+ nsresult res;
+
+ // get charset converters.
+ nsCOMPtr<nsICharsetConverterManager> ccm =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res);
+ if (NS_SUCCEEDED(res)) {
+
+ // create a decoder (conv to unicode), ok if failed if we do auto detection
+ if (!*aInputCharset || !PL_strcasecmp("us-ascii", aInputCharset))
+ res = ccm->GetUnicodeDecoderRaw("ISO-8859-1", aDecoder);
+ else
+ // GetUnicodeDecoderInternal in order to support UTF-7 messages
+ //
+ // XXX this means that even HTML messages in UTF-7 will be decoded
+ res = ccm->GetUnicodeDecoderInternal(aInputCharset, aDecoder);
+ }
+
+ return res;
+}
+
+//Get unicode encoder(from unicode to inputcharset) for aOutputCharset
+nsresult
+MIME_get_unicode_encoder(const char* aOutputCharset, nsIUnicodeEncoder **aEncoder)
+{
+ nsresult res;
+
+ // get charset converters.
+ nsCOMPtr<nsICharsetConverterManager> ccm =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res);
+ if (NS_SUCCEEDED(res) && *aOutputCharset) {
+ // create a encoder (conv from unicode)
+ res = ccm->GetUnicodeEncoder(aOutputCharset, aEncoder);
+ }
+
+ return res;
+}
+
+} /* end of extern "C" */
+// END PUBLIC INTERFACE
+
diff --git a/mailnews/mime/src/comi18n.h b/mailnews/mime/src/comi18n.h
new file mode 100644
index 000000000..6be89cfc5
--- /dev/null
+++ b/mailnews/mime/src/comi18n.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _COMI18N_LOADED_H_
+#define _COMI18N_LOADED_H_
+
+#include "msgCore.h"
+
+class nsIUnicodeDecoder;
+class nsIUnicodeEncoder;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Decode MIME header to UTF-8.
+ * Uses MIME_ConvertCharset if the decoded string needs a conversion.
+ *
+ *
+ * @param header [IN] A header to decode.
+ * @param default_charset [IN] Default charset to apply to ulabeled non-UTF-8 8bit data
+ * @param override_charset [IN] If true, default_charset used instead of any charset labeling other than UTF-8
+ * @param eatContinuations [IN] If true, unfold headers
+ * @param result [OUT] Decoded buffer
+ */
+void MIME_DecodeMimeHeader(const char *header, const char *default_charset,
+ bool override_charset, bool eatContinuations,
+ nsACString &result);
+
+nsresult MIME_detect_charset(const char *aBuf, int32_t aLength, const char** aCharset);
+nsresult MIME_get_unicode_decoder(const char* aInputCharset, nsIUnicodeDecoder **aDecoder);
+nsresult MIME_get_unicode_encoder(const char* aOutputCharset, nsIUnicodeEncoder **aEncoder);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif // _COMI18N_LOADED_H_
+
diff --git a/mailnews/mime/src/extraMimeParsers.jsm b/mailnews/mime/src/extraMimeParsers.jsm
new file mode 100644
index 000000000..101eddc20
--- /dev/null
+++ b/mailnews/mime/src/extraMimeParsers.jsm
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function parseNewsgroups(headers) {
+ let ng = [];
+ for (let header of headers) {
+ ng = ng.concat(header.split(/\s*,\s*/));
+ }
+ return ng;
+}
+
+function emitNewsgroups(groups) {
+ // Don't encode the newsgroups names in RFC 2047...
+ if (groups.length == 1)
+ this.addText(groups[0], false);
+ else {
+ this.addText(groups[0], false);
+ for (let i = 1; i < groups.length; i++) {
+ this.addText(",", false); // only comma, no space!
+ this.addText(groups[i], false);
+ }
+ }
+}
+
+jsmime.headerparser.addStructuredDecoder("Newsgroups", parseNewsgroups);
+jsmime.headerparser.addStructuredDecoder("Followup-To", parseNewsgroups);
+jsmime.headeremitter.addStructuredEncoder("Newsgroups", emitNewsgroups);
+jsmime.headeremitter.addStructuredEncoder("Followup-To", emitNewsgroups);
diff --git a/mailnews/mime/src/jsmime.jsm b/mailnews/mime/src/jsmime.jsm
new file mode 100644
index 000000000..70728fe9c
--- /dev/null
+++ b/mailnews/mime/src/jsmime.jsm
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+// vim:set ts=2 sw=2 sts=2 et ft=javascript:
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * This file exports the JSMime code, polyfilling code as appropriate for use in
+ * Gecko.
+ */
+
+// Load the core MIME parser. Since it doesn't define EXPORTED_SYMBOLS, we must
+// use the subscript loader instead.
+Services.scriptloader.loadSubScript("resource:///modules/jsmime/jsmime.js");
+
+var EXPORTED_SYMBOLS = ["jsmime"];
+
+
+// A polyfill to support non-encoding-spec charsets. Since the only converter
+// available to us from JavaScript has a very, very weak and inflexible API, we
+// choose to rely on the regular text decoder unless absolutely necessary.
+// support non-encoding-spec charsets.
+function FakeTextDecoder(label="UTF-8", options = {}) {
+ this._reset(label);
+ // So nsIScriptableUnicodeConverter only gives us fatal=false, unless we are
+ // using UTF-8, where we only get fatal=true. The internals of said class tell
+ // us to use a C++-only class if we need better behavior.
+}
+FakeTextDecoder.prototype = {
+ _reset: function (label) {
+ this._encoder = Components.classes[
+ "@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ this._encoder.isInternal = true;
+ let manager = Components.classes[
+ "@mozilla.org/charset-converter-manager;1"]
+ .createInstance(Components.interfaces.nsICharsetConverterManager);
+ this._encoder.charset = manager.getCharsetAlias(label);
+ },
+ get encoding() { return this._encoder.charset; },
+ decode: function (input, options = {}) {
+ let more = 'stream' in options ? options.stream : false;
+ let result = "";
+ if (input !== undefined) {
+ let data = new Uint8Array(input);
+ result = this._encoder.convertFromByteArray(data, data.length);
+ }
+ // This isn't quite right--it won't handle errors if there are a few
+ // remaining bytes in the buffer, but it's the best we can do.
+ if (!more)
+ this._reset(this.encoding);
+ return result;
+ },
+};
+
+var RealTextDecoder = TextDecoder;
+function FallbackTextDecoder(charset, options) {
+ try {
+ return new RealTextDecoder(charset, options);
+ } catch (e) {
+ return new FakeTextDecoder(charset, options);
+ }
+}
+
+TextDecoder = FallbackTextDecoder;
+
+
+// The following code loads custom MIME encoders.
+var CATEGORY_NAME = "custom-mime-encoder";
+Services.obs.addObserver(function (subject, topic, data) {
+ subject = subject.QueryInterface(Components.interfaces.nsISupportsCString)
+ .data;
+ if (data == CATEGORY_NAME) {
+ let url = catman.getCategoryEntry(CATEGORY_NAME, subject);
+ Services.scriptloader.loadSubScript(url, {}, "UTF-8");
+ }
+}, "xpcom-category-entry-added", false);
+
+var catman = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.nsICategoryManager);
+
+var entries = catman.enumerateCategory(CATEGORY_NAME);
+while (entries.hasMoreElements()) {
+ let string = entries.getNext()
+ .QueryInterface(Components.interfaces.nsISupportsCString)
+ .data;
+ let url = catman.getCategoryEntry(CATEGORY_NAME, string);
+ Services.scriptloader.loadSubScript(url, {}, "UTF-8");
+}
diff --git a/mailnews/mime/src/mime.def b/mailnews/mime/src/mime.def
new file mode 100644
index 000000000..6b1c36bf9
--- /dev/null
+++ b/mailnews/mime/src/mime.def
@@ -0,0 +1,7 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+LIBRARY mime.dll
+
+EXPORTS
diff --git a/mailnews/mime/src/mimeJSComponents.js b/mailnews/mime/src/mimeJSComponents.js
new file mode 100644
index 000000000..8e9d0684a
--- /dev/null
+++ b/mailnews/mime/src/mimeJSComponents.js
@@ -0,0 +1,515 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Deprecated.jsm");
+Components.utils.import("resource:///modules/jsmime.jsm");
+Components.utils.import("resource:///modules/mimeParser.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function HeaderHandler() {
+ this.value = "";
+ this.deliverData = function (str) { this.value += str; };
+ this.deliverEOF = function () {};
+}
+
+function StringEnumerator(iterator) {
+ this._iterator = iterator;
+ this._next = undefined;
+}
+StringEnumerator.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Components.interfaces.nsIUTF8StringEnumerator]),
+ _setNext: function () {
+ if (this._next !== undefined)
+ return;
+ this._next = this._iterator.next();
+ },
+ hasMore: function () {
+ this._setNext();
+ return !this._next.done;
+ },
+ getNext: function () {
+ this._setNext();
+ let result = this._next;
+ this._next = undefined;
+ if (result.done)
+ throw Components.results.NS_ERROR_UNEXPECTED;
+ return result.value;
+ }
+};
+
+/**
+ * If we get XPConnect-wrapped objects for msgIAddressObjects, we will have
+ * properties defined for 'group' that throws off jsmime. This function converts
+ * the addresses into the form that jsmime expects.
+ */
+function fixXpconnectAddresses(addrs) {
+ return addrs.map((addr) => {
+ // This is ideally !addr.group, but that causes a JS strict warning, if
+ // group is not in addr, since that's enabled in all chrome code now.
+ if (!('group' in addr) || addr.group === undefined || addr.group === null) {
+ return MimeAddressParser.prototype.makeMailboxObject(addr.name,
+ addr.email);
+ } else {
+ return MimeAddressParser.prototype.makeGroupObject(addr.name,
+ fixXpconnectAddresses(addr.group));
+ }
+ });
+}
+
+/**
+ * This is a base handler for supporting msgIStructuredHeaders, since we have
+ * two implementations that need the readable aspects of the interface.
+ */
+function MimeStructuredHeaders() {
+}
+MimeStructuredHeaders.prototype = {
+ getHeader: function (aHeaderName) {
+ let name = aHeaderName.toLowerCase();
+ return this._headers.get(name);
+ },
+
+ hasHeader: function (aHeaderName) {
+ return this._headers.has(aHeaderName.toLowerCase());
+ },
+
+ getUnstructuredHeader: function (aHeaderName) {
+ let result = this.getHeader(aHeaderName);
+ if (result === undefined || typeof result == "string")
+ return result;
+ throw Components.results.NS_ERROR_ILLEGAL_VALUE;
+ },
+
+ getAddressingHeader: function (aHeaderName, aPreserveGroups, count) {
+ let addrs = this.getHeader(aHeaderName);
+ if (addrs === undefined) {
+ addrs = [];
+ } else if (!Array.isArray(addrs)) {
+ throw Components.results.NS_ERROR_ILLEGAL_VALUE;
+ }
+ return fixArray(addrs, aPreserveGroups, count);
+ },
+
+ getRawHeader: function (aHeaderName) {
+ let result = this.getHeader(aHeaderName);
+ if (result === undefined)
+ return result;
+
+ let value = jsmime.headeremitter.emitStructuredHeader(aHeaderName,
+ result, {});
+ // Strip off the header name and trailing whitespace before returning...
+ value = value.substring(aHeaderName.length + 2).trim();
+ // ... as well as embedded newlines.
+ value = value.replace(/\r\n/g, '');
+ return value;
+ },
+
+ get headerNames() {
+ return new StringEnumerator(this._headers.keys());
+ },
+
+ buildMimeText: function () {
+ if (this._headers.size == 0) {
+ return "";
+ }
+ let handler = new HeaderHandler();
+ let emitter = jsmime.headeremitter.makeStreamingEmitter(handler, {
+ useASCII: true
+ });
+ for (let [value, header] of this._headers) {
+ emitter.addStructuredHeader(value, header);
+ }
+ emitter.finish();
+ return handler.value;
+ },
+};
+
+
+function MimeHeaders() {
+}
+MimeHeaders.prototype = {
+ __proto__: MimeStructuredHeaders.prototype,
+ classDescription: "Mime headers implementation",
+ classID: Components.ID("d1258011-f391-44fd-992e-c6f4b461a42f"),
+ contractID: "@mozilla.org/messenger/mimeheaders;1",
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMimeHeaders,
+ Components.interfaces.msgIStructuredHeaders]),
+
+ initialize: function MimeHeaders_initialize(allHeaders) {
+ this._headers = MimeParser.extractHeaders(allHeaders);
+ },
+
+ extractHeader: function MimeHeaders_extractHeader(header, getAll) {
+ if (!this._headers)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+ // Canonicalized to lower-case form
+ header = header.toLowerCase();
+ if (!this._headers.has(header))
+ return null;
+ var values = this._headers.getRawHeader(header);
+ if (getAll)
+ return values.join(",\r\n\t");
+ else
+ return values[0];
+ },
+
+ get allHeaders() {
+ return this._headers.rawHeaderText;
+ }
+};
+
+function MimeWritableStructuredHeaders() {
+ this._headers = new Map();
+}
+MimeWritableStructuredHeaders.prototype = {
+ __proto__: MimeStructuredHeaders.prototype,
+ classID: Components.ID("c560806a-425f-4f0f-bf69-397c58c599a7"),
+ QueryInterface: XPCOMUtils.generateQI([
+ Components.interfaces.msgIStructuredHeaders,
+ Components.interfaces.msgIWritableStructuredHeaders]),
+
+ setHeader: function (aHeaderName, aValue) {
+ this._headers.set(aHeaderName.toLowerCase(), aValue);
+ },
+
+ deleteHeader: function (aHeaderName) {
+ this._headers.delete(aHeaderName.toLowerCase());
+ },
+
+ addAllHeaders: function (aHeaders) {
+ let headerList = aHeaders.headerNames;
+ while (headerList.hasMore()) {
+ let header = headerList.getNext();
+ this.setHeader(header, aHeaders.getHeader(header));
+ }
+ },
+
+ setUnstructuredHeader: function (aHeaderName, aValue) {
+ this.setHeader(aHeaderName, aValue);
+ },
+
+ setAddressingHeader: function (aHeaderName, aAddresses, aCount) {
+ this.setHeader(aHeaderName, fixXpconnectAddresses(aAddresses));
+ },
+
+ setRawHeader: function (aHeaderName, aValue, aCharset) {
+ aValue = jsmime.headerparser.convert8BitHeader(aValue, aCharset);
+ try {
+ this.setHeader(aHeaderName,
+ jsmime.headerparser.parseStructuredHeader(aHeaderName, aValue));
+ } catch (e) {
+ // This means we don't have a structured encoder. Just assume it's a raw
+ // string value then.
+ this.setHeader(aHeaderName, aValue);
+ }
+ }
+};
+
+// These are prototypes for nsIMsgHeaderParser implementation
+var Mailbox = {
+ toString: function () {
+ return this.name ? this.name + " <" + this.email + ">" : this.email;
+ }
+};
+
+var EmailGroup = {
+ toString: function () {
+ return this.name + ": " + this.group.map(x => x.toString()).join(", ");
+ }
+};
+
+// A helper method for parse*Header that takes into account the desire to
+// preserve group and also tweaks the output to support the prototypes for the
+// XPIDL output.
+function fixArray(addresses, preserveGroups, count) {
+ function resetPrototype(obj, prototype) {
+ let prototyped = Object.create(prototype);
+ for (let key of Object.getOwnPropertyNames(obj)) {
+ if (typeof obj[key] == "string") {
+ prototyped[key] = obj[key].replace(/\x00/g, '');
+ } else {
+ prototyped[key] = obj[key];
+ }
+ }
+ return prototyped;
+ }
+ let outputArray = [];
+ for (let element of addresses) {
+ if ('group' in element) {
+ // Fix up the prototypes of the group and the list members
+ element = resetPrototype(element, EmailGroup);
+ element.group = element.group.map(e => resetPrototype(e, Mailbox));
+
+ // Add to the output array
+ if (preserveGroups)
+ outputArray.push(element);
+ else
+ outputArray = outputArray.concat(element.group);
+ } else {
+ element = resetPrototype(element, Mailbox);
+ outputArray.push(element);
+ }
+ }
+
+ if (count)
+ count.value = outputArray.length;
+ return outputArray;
+}
+
+function MimeAddressParser() {
+}
+MimeAddressParser.prototype = {
+ classID: Components.ID("96bd8769-2d0e-4440-963d-22b97fb3ba77"),
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgHeaderParser]),
+
+ parseEncodedHeader: function (aHeader, aCharset, aPreserveGroups, count) {
+ aHeader = aHeader || "";
+ let value = MimeParser.parseHeaderField(aHeader,
+ MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_ALL_I18N, aCharset);
+ return fixArray(value, aPreserveGroups, count);
+ },
+ parseEncodedHeaderW: function (aHeader, count) {
+ aHeader = aHeader || "";
+ let value = MimeParser.parseHeaderField(aHeader,
+ MimeParser.HEADER_ADDRESS |
+ MimeParser.HEADER_OPTION_DECODE_2231 |
+ MimeParser.HEADER_OPTION_DECODE_2047,
+ undefined);
+ return fixArray(value, false, count);
+ },
+ parseDecodedHeader: function (aHeader, aPreserveGroups, count) {
+ aHeader = aHeader || "";
+ let value = MimeParser.parseHeaderField(aHeader, MimeParser.HEADER_ADDRESS);
+ return fixArray(value, aPreserveGroups, count);
+ },
+
+ makeMimeHeader: function (addresses, length) {
+ addresses = fixXpconnectAddresses(addresses);
+ // Don't output any necessary continuations, so make line length as large as
+ // possible first.
+ let options = {
+ softMargin: 900,
+ hardMargin: 900,
+ useASCII: false // We don't want RFC 2047 encoding here.
+ };
+ let handler = new HeaderHandler();
+ let emitter = new jsmime.headeremitter.makeStreamingEmitter(handler,
+ options);
+ emitter.addAddresses(addresses);
+ emitter.finish(true);
+ return handler.value.replace(/\r\n( |$)/g, '');
+ },
+
+ extractFirstName: function (aHeader) {
+ let address = this.parseDecodedHeader(aHeader, false)[0];
+ return address.name || address.email;
+ },
+
+ removeDuplicateAddresses: function (aAddrs, aOtherAddrs) {
+ // This is actually a rather complicated algorithm, especially if we want to
+ // preserve group structure. Basically, we use a set to identify which
+ // headers we have seen and therefore want to remove. To work in several
+ // various forms of edge cases, we need to normalize the entries in that
+ // structure.
+ function normalize(email) {
+ // XXX: This algorithm doesn't work with IDN yet. It looks like we have to
+ // convert from IDN then do lower case, but I haven't confirmed yet.
+ return email.toLowerCase();
+ }
+
+ // The filtration function, which removes email addresses that are
+ // duplicates of those we have already seen.
+ function filterAccept(e) {
+ if ('email' in e) {
+ // If we've seen the address, don't keep this one; otherwise, add it to
+ // the list.
+ let key = normalize(e.email);
+ if (allAddresses.has(key))
+ return false;
+ allAddresses.add(key);
+ } else {
+ // Groups -> filter out all the member addresses.
+ e.group = e.group.filter(filterAccept);
+ }
+ return true;
+ }
+
+ // First, collect all of the emails to forcibly delete.
+ let allAddresses = new Set();
+ for (let element of this.parseDecodedHeader(aOtherAddrs, false)) {
+ allAddresses.add(normalize(element.email));
+ }
+
+ // The actual data to filter
+ let filtered = this.parseDecodedHeader(aAddrs, true).filter(filterAccept);
+ return this.makeMimeHeader(filtered);
+ },
+
+ makeMailboxObject: function (aName, aEmail) {
+ let object = Object.create(Mailbox);
+ object.name = aName;
+ object.email = aEmail ? aEmail.trim() : aEmail;
+ return object;
+ },
+
+ makeGroupObject: function (aName, aMembers) {
+ let object = Object.create(EmailGroup);
+ object.name = aName;
+ object.group = aMembers;
+ return object;
+ },
+
+ makeFromDisplayAddress: function (aDisplay, count) {
+ // The basic idea is to split on every comma, so long as there is a
+ // preceding @.
+ let output = [];
+ while (aDisplay.length) {
+ let at = aDisplay.indexOf('@');
+ let comma = aDisplay.indexOf(',', at + 1);
+ let addr;
+ if (comma > 0) {
+ addr = aDisplay.substr(0, comma);
+ aDisplay = aDisplay.substr(comma + 1);
+ } else {
+ addr = aDisplay;
+ aDisplay = "";
+ }
+ output.push(this._makeSingleAddress(addr.trimLeft()));
+ }
+ if (count)
+ count.value = output.length;
+ return output;
+ },
+
+ /// Construct a single email address from a name <local@domain> token.
+ _makeSingleAddress: function (aDisplayName) {
+ if (aDisplayName.includes('<')) {
+ let lbracket = aDisplayName.lastIndexOf('<');
+ let rbracket = aDisplayName.lastIndexOf('>');
+ return this.makeMailboxObject(
+ lbracket == 0 ? '' : aDisplayName.slice(0, lbracket).trim(),
+ aDisplayName.slice(lbracket + 1, rbracket));
+ } else {
+ return this.makeMailboxObject('', aDisplayName);
+ }
+ },
+
+ // What follows is the deprecated API that will be removed shortly.
+
+ parseHeadersWithArray: function (aHeader, aAddrs, aNames, aFullNames) {
+ let addrs = [], names = [], fullNames = [];
+ let allAddresses = this.parseEncodedHeader(aHeader, undefined, false);
+
+ // Don't index the dummy empty address.
+ if (aHeader.trim() == "")
+ allAddresses = [];
+ for (let address of allAddresses) {
+ addrs.push(address.email);
+ names.push(address.name || null);
+ fullNames.push(address.toString());
+ }
+
+ aAddrs.value = addrs;
+ aNames.value = names;
+ aFullNames.value = fullNames;
+ return allAddresses.length;
+ },
+
+ extractHeaderAddressMailboxes: function (aLine) {
+ return this.parseDecodedHeader(aLine).map(addr => addr.email).join(", ");
+ },
+
+ extractHeaderAddressNames: function (aLine) {
+ return this.parseDecodedHeader(aLine).map(addr => addr.name || addr.email)
+ .join(", ");
+ },
+
+ extractHeaderAddressName: function (aLine) {
+ let addrs = this.parseDecodedHeader(aLine).map(addr =>
+ addr.name || addr.email);
+ return addrs.length == 0 ? "" : addrs[0];
+ },
+
+ makeMimeAddress: function (aName, aEmail) {
+ let object = this.makeMailboxObject(aName, aEmail);
+ return this.makeMimeHeader([object]);
+ },
+};
+
+function MimeConverter() {
+}
+MimeConverter.prototype = {
+ classID: Components.ID("93f8c049-80ed-4dda-9000-94ad8daba44c"),
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMimeConverter]),
+
+ encodeMimePartIIStr_UTF8: function (aHeader, aStructured, aCharset,
+ aFieldNameLen, aLineLength) {
+ // The JSMime encoder only works in UTF-8, so if someone requests to not do
+ // it, they need to change their code.
+ if (aCharset.toLowerCase() != "utf-8") {
+ Deprecated.warning("Encoding to non-UTF-8 values is obsolete",
+ "http://bugzilla.mozilla.org/show_bug.cgi?id=790855");
+ }
+
+ // Compute the encoding options. The way our API is structured in this
+ // method is really horrendous and does not align with the way that JSMime
+ // handles it. Instead, we'll need to create a fake header to take into
+ // account the aFieldNameLen parameter.
+ let fakeHeader = '-'.repeat(aFieldNameLen);
+ let options = {
+ softMargin: aLineLength,
+ useASCII: true,
+ };
+ let handler = new HeaderHandler();
+ let emitter = new jsmime.headeremitter.makeStreamingEmitter(handler,
+ options);
+
+ // Add the text to the be encoded.
+ emitter.addHeaderName(fakeHeader);
+ if (aStructured) {
+ // Structured really means "this is an addressing header"
+ let addresses = MimeParser.parseHeaderField(aHeader,
+ MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_DECODE_2047);
+ // This happens in one of our tests if there is a "bare" email but no
+ // @ sign. Without it, the result disappears since our emission code
+ // assumes that an empty email is not worth emitting.
+ if (addresses.length === 1 && addresses[0].email === "" &&
+ addresses[0].name !== "") {
+ addresses[0].email = addresses[0].name;
+ addresses[0].name = "";
+ }
+ emitter.addAddresses(addresses);
+ } else {
+ emitter.addUnstructured(aHeader);
+ }
+
+ // Compute the output. We need to strip off the fake prefix added earlier
+ // and the extra CRLF at the end.
+ emitter.finish(true);
+ let value = handler.value;
+ value = value.replace(new RegExp(fakeHeader + ":\\s*"), "");
+ return value.substring(0, value.length - 2);
+ },
+
+ decodeMimeHeader: function (aHeader, aDefaultCharset, aOverride, aUnfold) {
+ let value = MimeParser.parseHeaderField(aHeader,
+ MimeParser.HEADER_UNSTRUCTURED | MimeParser.HEADER_OPTION_ALL_I18N,
+ aDefaultCharset);
+ if (aUnfold) {
+ value = value.replace(/[\r\n]\t/g, ' ')
+ .replace(/[\r\n]/g, '');
+ }
+ return value;
+ },
+
+ // This is identical to the above, except for factors that are handled by the
+ // xpconnect conversion process
+ decodeMimeHeaderToUTF8: function () {
+ return this.decodeMimeHeader.apply(this, arguments);
+ },
+};
+
+var components = [MimeHeaders, MimeWritableStructuredHeaders, MimeAddressParser,
+ MimeConverter];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/mime/src/mimeParser.jsm b/mailnews/mime/src/mimeParser.jsm
new file mode 100644
index 000000000..904675f10
--- /dev/null
+++ b/mailnews/mime/src/mimeParser.jsm
@@ -0,0 +1,258 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+// vim:set ts=2 sw=2 sts=2 et ft=javascript:
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/jsmime.jsm");
+
+var EXPORTED_SYMBOLS = ["MimeParser"];
+
+// Emitter helpers, for internal functions later on.
+var ExtractHeadersEmitter = {
+ startPart: function (partNum, headers) {
+ if (partNum == '') {
+ this.headers = headers;
+ }
+ }
+};
+
+var ExtractHeadersAndBodyEmitter = {
+ body: '',
+ startPart: ExtractHeadersEmitter.startPart,
+ deliverPartData: function (partNum, data) {
+ if (partNum == '')
+ this.body += data;
+ }
+};
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+/// Sets appropriate default options for chrome-privileged environments
+function setDefaultParserOptions(opts) {
+ if (!("onerror" in opts)) {
+ opts.onerror = Components.utils.reportError;
+ }
+}
+
+var MimeParser = {
+ /**
+ * Triggers an asynchronous parse of the given input.
+ *
+ * The input is an input stream; the stream will be read until EOF and then
+ * closed upon completion. Both blocking and nonblocking streams are
+ * supported by this implementation, but it is still guaranteed that the first
+ * callback will not happen before this method returns.
+ *
+ * @param input An input stream of text to parse.
+ * @param emitter The emitter to receive callbacks on.
+ * @param opts A set of options for the parser.
+ */
+ parseAsync: function MimeParser_parseAsync(input, emitter, opts) {
+ // Normalize the input into an input stream.
+ if (!(input instanceof Ci.nsIInputStream)) {
+ throw new Error("input is not a recognizable type!");
+ }
+
+ // We need a pump for the listener
+ var pump = Cc["@mozilla.org/network/input-stream-pump;1"]
+ .createInstance(Ci.nsIInputStreamPump);
+ pump.init(input, -1, -1, 0, 0, true);
+
+ // Make a stream listener with the given emitter and use it to read from
+ // the pump.
+ var parserListener = MimeParser.makeStreamListenerParser(emitter, opts);
+ pump.asyncRead(parserListener, null);
+ },
+
+ /**
+ * Triggers an synchronous parse of the given input.
+ *
+ * The input is a string that is immediately parsed, calling all functions on
+ * the emitter before this function returns.
+ *
+ * @param input A string or input stream of text to parse.
+ * @param emitter The emitter to receive callbacks on.
+ * @param opts A set of options for the parser.
+ */
+ parseSync: function MimeParser_parseSync(input, emitter, opts) {
+ // We only support string parsing if we are trying to do this parse
+ // synchronously.
+ if (typeof input != "string") {
+ throw new Error("input is not a recognizable type!");
+ }
+ setDefaultParserOptions(opts);
+ var parser = new jsmime.MimeParser(emitter, opts);
+ parser.deliverData(input);
+ parser.deliverEOF();
+ return;
+ },
+
+ /**
+ * Returns a stream listener that feeds data into a parser.
+ *
+ * In addition to the functions on the emitter that the parser may use, the
+ * generated stream listener will also make calls to onStartRequest and
+ * onStopRequest on the emitter (if they exist).
+ *
+ * @param emitter The emitter to receive callbacks on.
+ * @param opts A set of options for the parser.
+ */
+ makeStreamListenerParser: function MimeParser_makeSLParser(emitter, opts) {
+ var StreamListener = {
+ onStartRequest: function SLP_onStartRequest(aRequest, aContext) {
+ try {
+ if ("onStartRequest" in emitter)
+ emitter.onStartRequest(aRequest, aContext);
+ } finally {
+ this._parser.resetParser();
+ }
+ },
+ onStopRequest: function SLP_onStopRequest(aRequest, aContext, aStatus) {
+ this._parser.deliverEOF();
+ if ("onStopRequest" in emitter)
+ emitter.onStopRequest(aRequest, aContext, aStatus);
+ },
+ onDataAvailable: function SLP_onData(aRequest, aContext, aStream,
+ aOffset, aCount) {
+ var scriptIn = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ scriptIn.init(aStream);
+ // Use readBytes instead of read to handle embedded NULs properly.
+ this._parser.deliverData(scriptIn.readBytes(aCount));
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener,
+ Ci.nsIRequestObserver])
+ };
+ setDefaultParserOptions(opts);
+ StreamListener._parser = new jsmime.MimeParser(emitter, opts);
+ return StreamListener;
+ },
+
+ /**
+ * Returns a new raw MIME parser.
+ *
+ * Prefer one of the other methods where possible, since the input here must
+ * be driven manually.
+ *
+ * @param emitter The emitter to receive callbacks on.
+ * @param opts A set of options for the parser.
+ */
+ makeParser: function MimeParser_makeParser(emitter, opts) {
+ setDefaultParserOptions(opts);
+ return new jsmime.MimeParser(emitter, opts);
+ },
+
+ /**
+ * Returns a dictionary of headers for the given input.
+ *
+ * The input is any type of input that would be accepted by parseSync. What
+ * is returned is a JS object that represents the headers of the entire
+ * envelope as would be received by startPart when partNum is the empty
+ * string.
+ *
+ * @param input A string of text to parse.
+ */
+ extractHeaders: function MimeParser_extractHeaders(input) {
+ var emitter = Object.create(ExtractHeadersEmitter);
+ MimeParser.parseSync(input, emitter, {pruneat: '', bodyformat: 'none'});
+ return emitter.headers;
+ },
+
+ /**
+ * Returns the headers and body for the given input message.
+ *
+ * The return value is an array whose first element is the dictionary of
+ * headers (as would be returned by extractHeaders) and whose second element
+ * is a binary string of the entire body of the message.
+ *
+ * @param input A string of text to parse.
+ */
+ extractHeadersAndBody: function MimeParser_extractHeaders(input) {
+ var emitter = Object.create(ExtractHeadersAndBodyEmitter);
+ MimeParser.parseSync(input, emitter, {pruneat: '', bodyformat: 'raw'});
+ return [emitter.headers, emitter.body];
+ },
+
+ // Parameters for parseHeaderField
+
+ /**
+ * Parse the header as if it were unstructured.
+ *
+ * This results in the same string if no other options are specified. If other
+ * options are specified, this causes the string to be modified appropriately.
+ */
+ HEADER_UNSTRUCTURED: 0x00,
+ /**
+ * Parse the header as if it were in the form text; attr=val; attr=val.
+ *
+ * Such headers include Content-Type, Content-Disposition, and most other
+ * headers used by MIME as opposed to messages.
+ */
+ HEADER_PARAMETER: 0x02,
+ /**
+ * Parse the header as if it were a sequence of mailboxes.
+ */
+ HEADER_ADDRESS: 0x03,
+
+ /**
+ * This decodes parameter values according to RFC 2231.
+ *
+ * This flag means nothing if HEADER_PARAMETER is not specified.
+ */
+ HEADER_OPTION_DECODE_2231: 0x10,
+ /**
+ * This decodes the inline encoded-words that are in RFC 2047.
+ */
+ HEADER_OPTION_DECODE_2047: 0x20,
+ /**
+ * This converts the header from a raw string to proper Unicode.
+ */
+ HEADER_OPTION_ALLOW_RAW: 0x40,
+
+ /// Convenience for all three of the above.
+ HEADER_OPTION_ALL_I18N: 0x70,
+
+ /**
+ * Parse a header field according to the specification given by flags.
+ *
+ * Permissible flags begin with one of the HEADER_* flags, which may be or'd
+ * with any of the HEADER_OPTION_* flags to modify the result appropriately.
+ *
+ * If the option HEADER_OPTION_ALLOW_RAW is passed, the charset parameter, if
+ * present, is the charset to fallback to if the header is not decodable as
+ * UTF-8 text. If HEADER_OPTION_ALLOW_RAW is passed but the charset parameter
+ * is not provided, then no fallback decoding will be done. If
+ * HEADER_OPTION_ALLOW_RAW is not passed, then no attempt will be made to
+ * convert charsets.
+ *
+ * @param text The value of a MIME or message header to parse.
+ * @param flags A set of flags that controls interpretation of the header.
+ * @param charset A default charset to assume if no information may be found.
+ */
+ parseHeaderField: function MimeParser_parseHeaderField(text, flags, charset) {
+ // If we have a raw string, convert it to Unicode first
+ if (flags & MimeParser.HEADER_OPTION_ALLOW_RAW)
+ text = jsmime.headerparser.convert8BitHeader(text, charset);
+
+ // The low 4 bits indicate the type of the header we are parsing. All of the
+ // higher-order bits are flags.
+ switch (flags & 0x0f) {
+ case MimeParser.HEADER_UNSTRUCTURED:
+ if (flags & MimeParser.HEADER_OPTION_DECODE_2047)
+ text = jsmime.headerparser.decodeRFC2047Words(text);
+ return text;
+ case MimeParser.HEADER_PARAMETER:
+ return jsmime.headerparser.parseParameterHeader(text,
+ (flags & MimeParser.HEADER_OPTION_DECODE_2047) != 0,
+ (flags & MimeParser.HEADER_OPTION_DECODE_2231) != 0);
+ case MimeParser.HEADER_ADDRESS:
+ return jsmime.headerparser.parseAddressingHeader(text,
+ (flags & MimeParser.HEADER_OPTION_DECODE_2047) != 0);
+ default:
+ throw "Illegal type of header field";
+ }
+ },
+};
diff --git a/mailnews/mime/src/mimeTextHTMLParsed.cpp b/mailnews/mime/src/mimeTextHTMLParsed.cpp
new file mode 100644
index 000000000..2d45bd2c1
--- /dev/null
+++ b/mailnews/mime/src/mimeTextHTMLParsed.cpp
@@ -0,0 +1,150 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Most of this code is copied from mimethsa. If you find a bug here, check that class, too. */
+
+/* This runs the entire HTML document through the Mozilla HTML parser, and
+ then outputs it as string again. This ensures that the HTML document is
+ syntactically correct and complete and all tags and attributes are closed.
+
+ That prevents "MIME in the middle" attacks like efail.de.
+ The base problem is that we concatenate different MIME parts in the output
+ and render them all together as a single HTML document in the display.
+
+ The better solution would be to put each MIME part into its own <iframe type="content">.
+ during rendering. Unfortunately, we'd need <iframe seamless> for that.
+ That would remove the need for this workaround, and stop even more attack classes.
+*/
+
+#include "mimeTextHTMLParsed.h"
+#include "prmem.h"
+#include "prlog.h"
+#include "msgCore.h"
+#include "nsIDOMParser.h"
+#include "nsIDOMDocument.h"
+#include "nsIDocument.h"
+#include "nsIDocumentEncoder.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIPrefBranch.h"
+#include "mimethtm.h"
+
+#define MIME_SUPERCLASS mimeInlineTextHTMLClass
+MimeDefClass(MimeInlineTextHTMLParsed, MimeInlineTextHTMLParsedClass,
+ mimeInlineTextHTMLParsedClass, &MIME_SUPERCLASS);
+
+static int MimeInlineTextHTMLParsed_parse_line(const char *, int32_t,
+ MimeObject *);
+static int MimeInlineTextHTMLParsed_parse_begin(MimeObject *obj);
+static int MimeInlineTextHTMLParsed_parse_eof(MimeObject *, bool);
+static void MimeInlineTextHTMLParsed_finalize(MimeObject *obj);
+
+static int
+MimeInlineTextHTMLParsedClassInitialize(MimeInlineTextHTMLParsedClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *)clazz;
+ NS_ASSERTION(!oclass->class_initialized, "problem with superclass");
+ oclass->parse_line = MimeInlineTextHTMLParsed_parse_line;
+ oclass->parse_begin = MimeInlineTextHTMLParsed_parse_begin;
+ oclass->parse_eof = MimeInlineTextHTMLParsed_parse_eof;
+ oclass->finalize = MimeInlineTextHTMLParsed_finalize;
+
+ return 0;
+}
+
+static int
+MimeInlineTextHTMLParsed_parse_begin(MimeObject *obj)
+{
+ MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj;
+ me->complete_buffer = new nsString();
+ int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+ if (status < 0)
+ return status;
+
+ return 0;
+}
+
+static int
+MimeInlineTextHTMLParsed_parse_eof(MimeObject *obj, bool abort_p)
+{
+
+ if (obj->closed_p)
+ return 0;
+ int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0)
+ return status;
+ MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj;
+
+ // We have to cache all lines and parse the whole document at once.
+ // There's a useful sounding function parseFromStream(), but it only allows XML
+ // mimetypes, not HTML. Methinks that's because the HTML soup parser
+ // needs the entire doc to make sense of the gibberish that people write.
+ if (!me || !me->complete_buffer)
+ return 0;
+
+ nsString& rawHTML = *(me->complete_buffer);
+ if (rawHTML.IsEmpty())
+ return 0;
+ nsString parsed;
+ nsresult rv;
+
+ // Parse the HTML source.
+ nsCOMPtr<nsIDOMDocument> document;
+ nsCOMPtr<nsIDOMParser> parser = do_GetService(NS_DOMPARSER_CONTRACTID);
+ rv = parser->ParseFromString(rawHTML.get(), "text/html",
+ getter_AddRefs(document));
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ // Serialize it back to HTML source again.
+ nsCOMPtr<nsIDocumentEncoder> encoder = do_CreateInstance(
+ "@mozilla.org/layout/documentEncoder;1?type=text/html");
+ uint32_t aFlags = nsIDocumentEncoder::OutputRaw |
+ nsIDocumentEncoder::OutputDisallowLineBreaking;
+ rv = encoder->Init(document, NS_LITERAL_STRING("text/html"), aFlags);
+ NS_ENSURE_SUCCESS(rv, -1);
+ rv = encoder->EncodeToString(parsed);
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ // Write it out.
+ NS_ConvertUTF16toUTF8 resultCStr(parsed);
+ MimeInlineTextHTML_insert_lang_div(obj, resultCStr);
+ MimeInlineTextHTML_remove_plaintext_tag(obj, resultCStr);
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_line(
+ resultCStr.BeginWriting(), resultCStr.Length(), obj);
+ rawHTML.Truncate();
+ return status;
+}
+
+void
+MimeInlineTextHTMLParsed_finalize(MimeObject *obj)
+{
+ MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj;
+
+ if (me && me->complete_buffer)
+ {
+ obj->clazz->parse_eof(obj, false);
+ delete me->complete_buffer;
+ me->complete_buffer = NULL;
+ }
+
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj);
+}
+
+static int
+MimeInlineTextHTMLParsed_parse_line(const char *line, int32_t length,
+ MimeObject *obj)
+{
+ MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj;
+
+ if (!me || !(me->complete_buffer))
+ return -1;
+
+ nsCString linestr(line, length);
+ NS_ConvertUTF8toUTF16 line_ucs2(linestr.get());
+ if (length && line_ucs2.IsEmpty())
+ CopyASCIItoUTF16(linestr, line_ucs2);
+ (me->complete_buffer)->Append(line_ucs2);
+
+ return 0;
+}
diff --git a/mailnews/mime/src/mimeTextHTMLParsed.h b/mailnews/mime/src/mimeTextHTMLParsed.h
new file mode 100644
index 000000000..753a5153b
--- /dev/null
+++ b/mailnews/mime/src/mimeTextHTMLParsed.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMETEXTHTMLPARSED_H_
+#define _MIMETEXTHTMLPARSED_H_
+
+#include "mimethtm.h"
+
+typedef struct MimeInlineTextHTMLParsedClass MimeInlineTextHTMLParsedClass;
+typedef struct MimeInlineTextHTMLParsed MimeInlineTextHTMLParsed;
+
+struct MimeInlineTextHTMLParsedClass {
+ MimeInlineTextHTMLClass html;
+};
+
+extern MimeInlineTextHTMLParsedClass mimeInlineTextHTMLParsedClass;
+
+struct MimeInlineTextHTMLParsed {
+ MimeInlineTextHTML html;
+ nsString *complete_buffer; // Gecko parser expects wide strings
+};
+
+#define MimeInlineTextHTMLParsedClassInitializer(ITYPE,CSUPER) \
+ { MimeInlineTextHTMLClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMETEXTHTMLPARSED_H_ */
diff --git a/mailnews/mime/src/mimebuf.cpp b/mailnews/mime/src/mimebuf.cpp
new file mode 100644
index 000000000..f81047205
--- /dev/null
+++ b/mailnews/mime/src/mimebuf.cpp
@@ -0,0 +1,249 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+/*
+ * mimebuf.c - libmsg like buffer handling routines for libmime
+ */
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "msgCore.h"
+#include "nsMimeStringResources.h"
+
+extern "C" int
+mime_GrowBuffer (uint32_t desired_size, uint32_t element_size, uint32_t quantum,
+ char **buffer, int32_t *size)
+{
+ if ((uint32_t) *size <= desired_size)
+ {
+ char *new_buf;
+ uint32_t increment = desired_size - *size;
+ if (increment < quantum) /* always grow by a minimum of N bytes */
+ increment = quantum;
+
+ new_buf = (*buffer
+ ? (char *) PR_Realloc (*buffer, (*size + increment)
+ * (element_size / sizeof(char)))
+ : (char *) PR_MALLOC ((*size + increment)
+ * (element_size / sizeof(char))));
+ if (! new_buf)
+ return MIME_OUT_OF_MEMORY;
+ *buffer = new_buf;
+ *size += increment;
+ }
+ return 0;
+}
+
+/* The opposite of mime_LineBuffer(): takes small buffers and packs them
+ up into bigger buffers before passing them along.
+
+ Pass in a desired_buffer_size 0 to tell it to flush (for example, in
+ in the very last call to this function.)
+ */
+extern "C" int
+mime_ReBuffer (const char *net_buffer, int32_t net_buffer_size,
+ uint32_t desired_buffer_size,
+ char **bufferP, int32_t *buffer_sizeP, uint32_t *buffer_fpP,
+ int32_t (*per_buffer_fn) (char *buffer, uint32_t buffer_size,
+ void *closure),
+ void *closure)
+{
+ int status = 0;
+
+ if (desired_buffer_size >= (uint32_t) (*buffer_sizeP))
+ {
+ status = mime_GrowBuffer (desired_buffer_size, sizeof(char), 1024,
+ bufferP, buffer_sizeP);
+ if (status < 0) return status;
+ }
+
+ do
+ {
+ int32_t size = *buffer_sizeP - *buffer_fpP;
+ if (size > net_buffer_size)
+ size = net_buffer_size;
+ if (size > 0)
+ {
+ memcpy ((*bufferP) + (*buffer_fpP), net_buffer, size);
+ (*buffer_fpP) += size;
+ net_buffer += size;
+ net_buffer_size -= size;
+ }
+
+ if (*buffer_fpP > 0 &&
+ *buffer_fpP >= desired_buffer_size)
+ {
+ status = (*per_buffer_fn) ((*bufferP), (*buffer_fpP), closure);
+ *buffer_fpP = 0;
+ if (status < 0) return status;
+ }
+ }
+ while (net_buffer_size > 0);
+
+ return 0;
+}
+
+static int
+convert_and_send_buffer(char* buf, int length, bool convert_newlines_p,
+ int32_t (* per_line_fn) (char *line,
+ uint32_t line_length,
+ void *closure),
+ void *closure)
+{
+ /* Convert the line terminator to the native form.
+ */
+ char* newline;
+
+#if (MSG_LINEBREAK_LEN == 2)
+ /***
+ * This is a patch to support a mail DB corruption cause by earlier version that lead to a crash.
+ * What happened is that the line terminator is CR+NULL+LF. Therefore, we first process a line
+ * terminated by CR then a second line that contains only NULL+LF. We need to ignore this second
+ * line. See bug http://bugzilla.mozilla.org/show_bug.cgi?id=61412 for more information.
+ ***/
+ if (length == 2 && buf[0] == 0x00 && buf[1] == '\n')
+ return 0;
+#endif
+
+ NS_ASSERTION(buf && length > 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!buf || length <= 0) return -1;
+ newline = buf + length;
+ NS_ASSERTION(newline[-1] == '\r' || newline[-1] == '\n', "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (newline[-1] != '\r' && newline[-1] != '\n') return -1;
+
+ if (!convert_newlines_p)
+ {
+ }
+#if (MSG_LINEBREAK_LEN == 1)
+ else if ((newline - buf) >= 2 &&
+ newline[-2] == '\r' &&
+ newline[-1] == '\n')
+ {
+ /* CRLF -> CR or LF */
+ buf [length - 2] = MSG_LINEBREAK[0];
+ length--;
+ }
+ else if (newline > buf + 1 &&
+ newline[-1] != MSG_LINEBREAK[0])
+ {
+ /* CR -> LF or LF -> CR */
+ buf [length - 1] = MSG_LINEBREAK[0];
+ }
+#else
+ else if (((newline - buf) >= 2 && newline[-2] != '\r') ||
+ ((newline - buf) >= 1 && newline[-1] != '\n'))
+ {
+ /* LF -> CRLF or CR -> CRLF */
+ length++;
+ buf[length - 2] = MSG_LINEBREAK[0];
+ buf[length - 1] = MSG_LINEBREAK[1];
+ }
+#endif
+
+ return (*per_line_fn)(buf, length, closure);
+}
+
+extern "C" int
+mime_LineBuffer (const char *net_buffer, int32_t net_buffer_size,
+ char **bufferP, int32_t *buffer_sizeP, uint32_t *buffer_fpP,
+ bool convert_newlines_p,
+ int32_t (* per_line_fn) (char *line, uint32_t line_length,
+ void *closure),
+ void *closure)
+{
+ int status = 0;
+ if (*buffer_fpP > 0 && *bufferP && (*bufferP)[*buffer_fpP - 1] == '\r' &&
+ net_buffer_size > 0 && net_buffer[0] != '\n') {
+ /* The last buffer ended with a CR. The new buffer does not start
+ with a LF. This old buffer should be shipped out and discarded. */
+ NS_ASSERTION((uint32_t) *buffer_sizeP > *buffer_fpP, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if ((uint32_t) *buffer_sizeP <= *buffer_fpP) return -1;
+ status = convert_and_send_buffer(*bufferP, *buffer_fpP,
+ convert_newlines_p,
+ per_line_fn, closure);
+ if (status < 0) return status;
+ *buffer_fpP = 0;
+ }
+ while (net_buffer_size > 0)
+ {
+ const char *net_buffer_end = net_buffer + net_buffer_size;
+ const char *newline = 0;
+ const char *s;
+
+
+ for (s = net_buffer; s < net_buffer_end; s++)
+ {
+ /* Move forward in the buffer until the first newline.
+ Stop when we see CRLF, CR, or LF, or the end of the buffer.
+ *But*, if we see a lone CR at the *very end* of the buffer,
+ treat this as if we had reached the end of the buffer without
+ seeing a line terminator. This is to catch the case of the
+ buffers splitting a CRLF pair, as in "FOO\r\nBAR\r" "\nBAZ\r\n".
+ */
+ if (*s == '\r' || *s == '\n')
+ {
+ newline = s;
+ if (newline[0] == '\r')
+ {
+ if (s == net_buffer_end - 1)
+ {
+ /* CR at end - wait for the next character. */
+ newline = 0;
+ break;
+ }
+ else if (newline[1] == '\n')
+ /* CRLF seen; swallow both. */
+ newline++;
+ }
+ newline++;
+ break;
+ }
+ }
+
+ /* Ensure room in the net_buffer and append some or all of the current
+ chunk of data to it. */
+ {
+ const char *end = (newline ? newline : net_buffer_end);
+ uint32_t desired_size = (end - net_buffer) + (*buffer_fpP) + 1;
+
+ if (desired_size >= (uint32_t) (*buffer_sizeP))
+ {
+ status = mime_GrowBuffer (desired_size, sizeof(char), 1024,
+ bufferP, buffer_sizeP);
+ if (status < 0) return status;
+ }
+ memcpy ((*bufferP) + (*buffer_fpP), net_buffer, (end - net_buffer));
+ (*buffer_fpP) += (end - net_buffer);
+ (*bufferP)[*buffer_fpP] = '\0';
+ }
+
+ /* Now *bufferP contains either a complete line, or as complete
+ a line as we have read so far.
+
+ If we have a line, process it, and then remove it from `*bufferP'.
+ Then go around the loop again, until we drain the incoming data.
+ */
+ if (!newline)
+ return 0;
+
+ status = convert_and_send_buffer(*bufferP, *buffer_fpP,
+ convert_newlines_p,
+ per_line_fn, closure);
+ if (status < 0)
+ return status;
+
+ net_buffer_size -= (newline - net_buffer);
+ net_buffer = newline;
+ (*buffer_fpP) = 0;
+ }
+ return 0;
+}
diff --git a/mailnews/mime/src/mimebuf.h b/mailnews/mime/src/mimebuf.h
new file mode 100644
index 000000000..38b9a68c7
--- /dev/null
+++ b/mailnews/mime/src/mimebuf.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+#ifndef _MIMEBUF_H_
+#define _MIMEBUF_H_
+
+extern "C" int mime_GrowBuffer (uint32_t desired_size,
+ uint32_t element_size, uint32_t quantum,
+ char **buffer, int32_t *size);
+
+extern "C" int mime_LineBuffer (const char *net_buffer, int32_t net_buffer_size,
+ char **bufferP, int32_t *buffer_sizeP,
+ int32_t *buffer_fpP,
+ bool convert_newlines_p,
+ int32_t (* per_line_fn) (char *line, int32_t
+ line_length, void *closure),
+ void *closure);
+
+extern "C" int mime_ReBuffer (const char *net_buffer, int32_t net_buffer_size,
+ uint32_t desired_buffer_size,
+ char **bufferP, uint32_t *buffer_sizeP,
+ uint32_t *buffer_fpP,
+ int32_t (*per_buffer_fn) (char *buffer,
+ uint32_t buffer_size,
+ void *closure),
+ void *closure);
+
+
+#endif /* _MIMEBUF_H_ */
diff --git a/mailnews/mime/src/mimecms.cpp b/mailnews/mime/src/mimecms.cpp
new file mode 100644
index 000000000..6f7455c0a
--- /dev/null
+++ b/mailnews/mime/src/mimecms.cpp
@@ -0,0 +1,797 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICMSMessage.h"
+#include "nsICMSMessage2.h"
+#include "nsICMSMessageErrors.h"
+#include "nsICMSDecoder.h"
+#include "mimecms.h"
+#include "mimemcms.h"
+#include "mimemsig.h"
+#include "nspr.h"
+#include "mimemsg.h"
+#include "mimemoz2.h"
+#include "nsIURI.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsIMsgSMIMEHeaderSink.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsIX509Cert.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+using namespace mozilla::mailnews;
+
+// The name "mime encrypted" is misleading, because this code is used
+// both for CMS messages that are encrypted, and also for messages that
+// aren't encrypted, but only contain a signature.
+
+#define MIME_SUPERCLASS mimeEncryptedClass
+MimeDefClass(MimeEncryptedCMS, MimeEncryptedCMSClass,
+ mimeEncryptedCMSClass, &MIME_SUPERCLASS);
+
+static void *MimeCMS_init(MimeObject *, int (*output_fn) (const char *, int32_t, void *), void *);
+static int MimeCMS_write (const char *, int32_t, void *);
+static int MimeCMS_eof (void *, bool);
+static char * MimeCMS_generate (void *);
+static void MimeCMS_free (void *);
+
+extern int SEC_ERROR_CERT_ADDR_MISMATCH;
+
+static int MimeEncryptedCMSClassInitialize(MimeEncryptedCMSClass *clazz)
+{
+#ifdef DEBUG
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ NS_ASSERTION(!oclass->class_initialized, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59");
+#endif
+
+ MimeEncryptedClass *eclass = (MimeEncryptedClass *) clazz;
+ eclass->crypto_init = MimeCMS_init;
+ eclass->crypto_write = MimeCMS_write;
+ eclass->crypto_eof = MimeCMS_eof;
+ eclass->crypto_generate_html = MimeCMS_generate;
+ eclass->crypto_free = MimeCMS_free;
+
+ return 0;
+}
+
+
+typedef struct MimeCMSdata
+{
+ int (*output_fn) (const char *buf, int32_t buf_size, void *output_closure);
+ void *output_closure;
+ nsCOMPtr<nsICMSDecoder> decoder_context;
+ nsCOMPtr<nsICMSMessage> content_info;
+ bool ci_is_encrypted;
+ char *sender_addr;
+ bool decoding_failed;
+ bool skip_content;
+ uint32_t decoded_bytes;
+ MimeObject *self;
+ bool any_parent_is_encrypted_p;
+ bool any_parent_is_signed_p;
+ nsCOMPtr<nsIMsgSMIMEHeaderSink> smimeHeaderSink;
+ nsCString url;
+
+ MimeCMSdata()
+ :output_fn(nullptr),
+ output_closure(nullptr),
+ ci_is_encrypted(false),
+ sender_addr(nullptr),
+ decoding_failed(false),
+ skip_content(false),
+ decoded_bytes(0),
+ self(nullptr),
+ any_parent_is_encrypted_p(false),
+ any_parent_is_signed_p(false) {}
+
+ ~MimeCMSdata()
+ {
+ if(sender_addr)
+ PR_Free(sender_addr);
+
+ // Do an orderly release of nsICMSDecoder and nsICMSMessage //
+ if (decoder_context)
+ {
+ nsCOMPtr<nsICMSMessage> cinfo;
+ decoder_context->Finish(getter_AddRefs(cinfo));
+ }
+ }
+} MimeCMSdata;
+
+/* SEC_PKCS7DecoderContentCallback for SEC_PKCS7DecoderStart() */
+static void MimeCMS_content_callback (void *arg, const char *buf, unsigned long length)
+{
+ int status;
+ MimeCMSdata *data = (MimeCMSdata *) arg;
+ if (!data) return;
+
+ if (!data->output_fn)
+ return;
+
+ PR_SetError(0,0);
+ status = data->output_fn (buf, length, data->output_closure);
+ if (status < 0)
+ {
+ PR_SetError(status, 0);
+ data->output_fn = 0;
+ return;
+ }
+
+ data->decoded_bytes += length;
+}
+
+bool MimeEncryptedCMS_encrypted_p (MimeObject *obj)
+{
+ bool encrypted;
+
+ if (!obj) return false;
+ if (mime_typep(obj, (MimeObjectClass *) &mimeEncryptedCMSClass))
+ {
+ MimeEncrypted *enc = (MimeEncrypted *) obj;
+ MimeCMSdata *data = (MimeCMSdata *) enc->crypto_closure;
+ if (!data || !data->content_info) return false;
+ data->content_info->ContentIsEncrypted(&encrypted);
+ return encrypted;
+ }
+ return false;
+}
+
+bool MimeEncOrMP_CMS_signed_p(MimeObject *obj) {
+ bool is_signed;
+
+ if (!obj) return false;
+ if (mime_typep(obj, (MimeObjectClass *)&mimeMultipartSignedCMSClass)) {
+ return true;
+ }
+ if (mime_typep(obj, (MimeObjectClass *)&mimeEncryptedCMSClass)) {
+ MimeEncrypted *enc = (MimeEncrypted *)obj;
+ MimeCMSdata *data = (MimeCMSdata *)enc->crypto_closure;
+ if (!data || !data->content_info) return false;
+ data->content_info->ContentIsSigned(&is_signed);
+ return is_signed;
+ }
+ return false;
+}
+
+bool MimeAnyParentCMSEncrypted(MimeObject *obj)
+{
+ MimeObject *o2 = obj;
+ while (o2 && o2->parent) {
+ if (MimeEncryptedCMS_encrypted_p(o2->parent)) {
+ return true;
+ }
+ o2 = o2->parent;
+ }
+ return false;
+}
+
+bool MimeAnyParentCMSSigned(MimeObject *obj)
+{
+ MimeObject *o2 = obj;
+ while (o2 && o2->parent) {
+ if (MimeEncOrMP_CMS_signed_p(o2->parent)) {
+ return true;
+ }
+ o2 = o2->parent;
+ }
+ return false;
+}
+
+bool MimeCMSHeadersAndCertsMatch(nsICMSMessage *content_info,
+ nsIX509Cert *signerCert,
+ const char *from_addr,
+ const char *from_name,
+ const char *sender_addr,
+ const char *sender_name,
+ bool *signing_cert_without_email_address)
+{
+ nsCString cert_addr;
+ bool match = true;
+ bool foundFrom = false;
+ bool foundSender = false;
+
+ /* Find the name and address in the cert.
+ */
+ if (content_info)
+ {
+ // Extract any address contained in the cert.
+ // This will be used for testing, whether the cert contains no addresses at all.
+ content_info->GetSignerEmailAddress (getter_Copies(cert_addr));
+ }
+
+ if (signing_cert_without_email_address)
+ *signing_cert_without_email_address = cert_addr.IsEmpty();
+
+ /* Now compare them --
+ consider it a match if the address in the cert matches the
+ address in the From field (or as a fallback, the Sender field)
+ */
+
+ /* If there is no addr in the cert at all, it can not match and we fail. */
+ if (cert_addr.IsEmpty())
+ {
+ match = false;
+ }
+ else
+ {
+ if (signerCert)
+ {
+ if (from_addr && *from_addr)
+ {
+ NS_ConvertASCIItoUTF16 ucs2From(from_addr);
+ if (NS_FAILED(signerCert->ContainsEmailAddress(ucs2From, &foundFrom)))
+ {
+ foundFrom = false;
+ }
+ }
+ else if (sender_addr && *sender_addr)
+ {
+ NS_ConvertASCIItoUTF16 ucs2Sender(sender_addr);
+ if (NS_FAILED(signerCert->ContainsEmailAddress(ucs2Sender, &foundSender)))
+ {
+ foundSender = false;
+ }
+ }
+ }
+
+ if (!foundSender && !foundFrom)
+ {
+ match = false;
+ }
+ }
+
+ return match;
+}
+
+class nsSMimeVerificationListener : public nsISMimeVerificationListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISMIMEVERIFICATIONLISTENER
+
+ nsSMimeVerificationListener(const char *aFromAddr, const char *aFromName,
+ const char *aSenderAddr, const char *aSenderName,
+ nsIMsgSMIMEHeaderSink *aHeaderSink,
+ int32_t aMimeNestingLevel,
+ const nsCString &aMsgNeckoURL);
+
+protected:
+ virtual ~nsSMimeVerificationListener() {}
+
+ /**
+ * It is safe to declare this implementation as thread safe,
+ * despite not using a lock to protect the members.
+ * Because of the way the object will be used, we don't expect a race.
+ * After construction, the object is passed to another thread,
+ * but will no longer be accessed on the original thread.
+ * The other thread is unable to access/modify self's data members.
+ * When the other thread is finished, it will call into the "Notify"
+ * callback. Self's members will be accessed on the other thread,
+ * but this is fine, because there is no race with the original thread.
+ * Race-protection for XPCOM reference counting is sufficient.
+ */
+ bool mSinkIsNull;
+ nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> mHeaderSink;
+ int32_t mMimeNestingLevel;
+ nsCString mMsgNeckoURL;
+
+ nsCString mFromAddr;
+ nsCString mFromName;
+ nsCString mSenderAddr;
+ nsCString mSenderName;
+};
+
+class SignedStatusRunnable : public mozilla::Runnable
+{
+public:
+ SignedStatusRunnable(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink, int32_t aNestingLevel,
+ int32_t aSignatureStatus, nsIX509Cert *aSignerCert,
+ const nsCString &aMsgNeckoURL);
+ NS_DECL_NSIRUNNABLE
+protected:
+ nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> m_sink;
+ int32_t m_nestingLevel;
+ int32_t m_signatureStatus;
+ nsCOMPtr<nsIX509Cert> m_signerCert;
+ nsCString m_msgNeckoURL;
+};
+
+SignedStatusRunnable::SignedStatusRunnable(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink,
+ int32_t aNestingLevel,
+ int32_t aSignatureStatus,
+ nsIX509Cert *aSignerCert,
+ const nsCString &aMsgNeckoURL) :
+ m_sink(aSink), m_nestingLevel(aNestingLevel),
+ m_signatureStatus(aSignatureStatus), m_signerCert(aSignerCert),
+ m_msgNeckoURL(aMsgNeckoURL)
+{
+}
+
+NS_IMETHODIMP SignedStatusRunnable::Run()
+{
+ return m_sink->SignedStatus(m_nestingLevel, m_signatureStatus, m_signerCert,
+ m_msgNeckoURL);
+}
+
+
+nsresult ProxySignedStatus(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink,
+ int32_t aNestingLevel,
+ int32_t aSignatureStatus,
+ nsIX509Cert *aSignerCert,
+ const nsCString &aMsgNeckoURL)
+{
+ RefPtr<SignedStatusRunnable> signedStatus =
+ new SignedStatusRunnable(aSink, aNestingLevel, aSignatureStatus, aSignerCert, aMsgNeckoURL);
+ return NS_DispatchToMainThread(signedStatus, NS_DISPATCH_SYNC);
+}
+
+NS_IMPL_ISUPPORTS(nsSMimeVerificationListener, nsISMimeVerificationListener)
+
+nsSMimeVerificationListener::nsSMimeVerificationListener(const char *aFromAddr, const char *aFromName,
+ const char *aSenderAddr, const char *aSenderName,
+ nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel,
+ const nsCString &aMsgNeckoURL)
+ : mMsgNeckoURL(aMsgNeckoURL)
+{
+ mHeaderSink = new nsMainThreadPtrHolder<nsIMsgSMIMEHeaderSink>(aHeaderSink);
+ mSinkIsNull = !aHeaderSink;
+ mMimeNestingLevel = aMimeNestingLevel;
+
+ mFromAddr = aFromAddr;
+ mFromName = aFromName;
+ mSenderAddr = aSenderAddr;
+ mSenderName = aSenderName;
+}
+
+NS_IMETHODIMP nsSMimeVerificationListener::Notify(nsICMSMessage2 *aVerifiedMessage,
+ nsresult aVerificationResultCode)
+{
+ // Only continue if we have a valid pointer to the UI
+ NS_ENSURE_FALSE(mSinkIsNull, NS_OK);
+
+ NS_ENSURE_TRUE(aVerifiedMessage, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsICMSMessage> msg = do_QueryInterface(aVerifiedMessage);
+ NS_ENSURE_TRUE(msg, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIX509Cert> signerCert;
+ msg->GetSignerCert(getter_AddRefs(signerCert));
+
+ int32_t signature_status = nsICMSMessageErrors::GENERAL_ERROR;
+
+ if (NS_FAILED(aVerificationResultCode))
+ {
+ if (NS_ERROR_MODULE_SECURITY == NS_ERROR_GET_MODULE(aVerificationResultCode))
+ signature_status = NS_ERROR_GET_CODE(aVerificationResultCode);
+ else if (NS_ERROR_NOT_IMPLEMENTED == aVerificationResultCode)
+ signature_status = nsICMSMessageErrors::VERIFY_ERROR_PROCESSING;
+ }
+ else
+ {
+ bool signing_cert_without_email_address;
+
+ bool good_p = MimeCMSHeadersAndCertsMatch(msg, signerCert,
+ mFromAddr.get(), mFromName.get(),
+ mSenderAddr.get(), mSenderName.get(),
+ &signing_cert_without_email_address);
+ if (!good_p)
+ {
+ if (signing_cert_without_email_address)
+ signature_status = nsICMSMessageErrors::VERIFY_CERT_WITHOUT_ADDRESS;
+ else
+ signature_status = nsICMSMessageErrors::VERIFY_HEADER_MISMATCH;
+ }
+ else
+ signature_status = nsICMSMessageErrors::SUCCESS;
+ }
+
+ ProxySignedStatus(mHeaderSink, mMimeNestingLevel, signature_status,
+ signerCert, mMsgNeckoURL);
+
+ return NS_OK;
+}
+
+int MIMEGetRelativeCryptoNestLevel(MimeObject *obj)
+{
+ /*
+ the part id of any mimeobj is mime_part_address(obj)
+ our currently displayed crypto part is obj
+ the part shown as the toplevel object in the current window is
+ obj->options->part_to_load
+ possibly stored in the toplevel object only ???
+ but hopefully all nested mimeobject point to the same displayooptions
+
+ we need to find out the nesting level of our currently displayed crypto object
+ wrt the shown part in the toplevel window
+ */
+
+ // if we are showing the toplevel message, aTopMessageNestLevel == 0
+ int aTopMessageNestLevel = 0;
+ MimeObject *aTopShownObject = nullptr;
+ if (obj && obj->options->part_to_load) {
+ bool aAlreadyFoundTop = false;
+ for (MimeObject *walker = obj; walker; walker = walker->parent) {
+ if (aAlreadyFoundTop) {
+ if (!mime_typep(walker, (MimeObjectClass *) &mimeEncryptedClass)
+ && !mime_typep(walker, (MimeObjectClass *) &mimeMultipartSignedClass)) {
+ ++aTopMessageNestLevel;
+ }
+ }
+ if (!aAlreadyFoundTop && !strcmp(mime_part_address(walker), walker->options->part_to_load)) {
+ aAlreadyFoundTop = true;
+ aTopShownObject = walker;
+ }
+ if (!aAlreadyFoundTop && !walker->parent) {
+ // The mime part part_to_load is not a parent of the
+ // the crypto mime part passed in to this function as parameter obj.
+ // That means the crypto part belongs to another branch of the mime tree.
+ return -1;
+ }
+ }
+ }
+
+ bool CryptoObjectIsChildOfTopShownObject = false;
+ if (!aTopShownObject) {
+ // no sub part specified, top message is displayed, and
+ // our crypto object is definitively a child of it
+ CryptoObjectIsChildOfTopShownObject = true;
+ }
+
+ // if we are the child of the topmost message, aCryptoPartNestLevel == 1
+ int aCryptoPartNestLevel = 0;
+ if (obj) {
+ for (MimeObject *walker = obj; walker; walker = walker->parent) {
+ // Crypto mime objects are transparent wrt nesting.
+ if (!mime_typep(walker, (MimeObjectClass *) &mimeEncryptedClass)
+ && !mime_typep(walker, (MimeObjectClass *) &mimeMultipartSignedClass)) {
+ ++aCryptoPartNestLevel;
+ }
+ if (aTopShownObject && walker->parent == aTopShownObject) {
+ CryptoObjectIsChildOfTopShownObject = true;
+ }
+ }
+ }
+
+ if (!CryptoObjectIsChildOfTopShownObject) {
+ return -1;
+ }
+
+ return aCryptoPartNestLevel - aTopMessageNestLevel;
+}
+
+static void *MimeCMS_init(MimeObject *obj,
+ int (*output_fn) (const char *buf, int32_t buf_size, void *output_closure),
+ void *output_closure)
+{
+ MimeCMSdata *data;
+ nsresult rv;
+
+ if (!(obj && obj->options && output_fn)) return 0;
+
+ data = new MimeCMSdata;
+ if (!data) return 0;
+
+ data->self = obj;
+ data->output_fn = output_fn;
+ data->output_closure = output_closure;
+ PR_SetError(0, 0);
+
+ data->any_parent_is_signed_p = MimeAnyParentCMSSigned(obj);
+
+ if (data->any_parent_is_signed_p) {
+ // Parent is signed.
+ // We don't know yet if this child is signed or encrypted.
+ // (We'll know after decoding has completed and EOF is called.)
+ // We don't support "inner encrypt" with outer sign, because the
+ // inner encrypted part could have been produced by an attacker who
+ // stripped away a part containing the signature (S/MIME doesn't
+ // have integrity protection).
+ // A sign-then-sign encoding is confusing, too, because it could be
+ // an attempt to influence which signature is shown.
+ data->skip_content = true;
+ }
+
+ if (!data->skip_content) {
+ data->decoder_context = do_CreateInstance(NS_CMSDECODER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ delete data;
+ return 0;
+ }
+
+ rv = data->decoder_context->Start(MimeCMS_content_callback, data);
+ if (NS_FAILED(rv)) {
+ delete data;
+ return 0;
+ }
+ }
+
+ data->any_parent_is_encrypted_p = MimeAnyParentCMSEncrypted(obj);
+
+ mime_stream_data *msd = (mime_stream_data *) (data->self->options->stream_closure);
+ if (msd)
+ {
+ nsIChannel *channel = msd->channel; // note the lack of ref counting...
+ if (channel)
+ {
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIMsgHeaderSink> headerSink;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl;
+ nsCOMPtr<nsISupports> securityInfo;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri)
+ {
+ rv = uri->GetSpec(data->url);
+
+ // We only want to update the UI if the current mime transaction
+ // is intended for display.
+ // If the current transaction is intended for background processing,
+ // we can learn that by looking at the additional header=filter
+ // string contained in the URI.
+ //
+ // If we find something, we do not set smimeHeaderSink,
+ // which will prevent us from giving UI feedback.
+ //
+ // If we do not find header=filter, we assume the result of the
+ // processing will be shown in the UI.
+
+ if (!strstr(data->url.get(), "?header=filter") &&
+ !strstr(data->url.get(), "&header=filter") &&
+ !strstr(data->url.get(), "?header=attach") &&
+ !strstr(data->url.get(), "&header=attach"))
+ {
+ msgurl = do_QueryInterface(uri);
+ if (msgurl)
+ msgurl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ msgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink));
+ if (headerSink)
+ headerSink->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (securityInfo)
+ data->smimeHeaderSink = do_QueryInterface(securityInfo);
+ }
+ }
+ } // if channel
+ } // if msd
+
+ return data;
+}
+
+static int
+MimeCMS_write (const char *buf, int32_t buf_size, void *closure)
+{
+ MimeCMSdata *data = (MimeCMSdata *) closure;
+ nsresult rv;
+
+ if (!data || !data->output_fn || !data->decoder_context) return -1;
+
+ if (!data->decoding_failed && !data->skip_content) {
+ PR_SetError(0, 0);
+ rv = data->decoder_context->Update(buf, buf_size);
+ data->decoding_failed = NS_FAILED(rv);
+ }
+
+ return 0;
+}
+
+void MimeCMSGetFromSender(MimeObject *obj,
+ nsCString &from_addr,
+ nsCString &from_name,
+ nsCString &sender_addr,
+ nsCString &sender_name)
+{
+ MimeHeaders *msg_headers = 0;
+
+ /* Find the headers of the MimeMessage which is the parent (or grandparent)
+ of this object (remember, crypto objects nest.) */
+ MimeObject *o2 = obj;
+ msg_headers = o2->headers;
+ while (o2 &&
+ o2->parent &&
+ !mime_typep(o2->parent, (MimeObjectClass *) &mimeMessageClass))
+ {
+ o2 = o2->parent;
+ msg_headers = o2->headers;
+ }
+
+ if (!msg_headers)
+ return;
+
+ /* Find the names and addresses in the From and/or Sender fields.
+ */
+ nsCString s;
+
+ /* Extract the name and address of the "From:" field. */
+ s.Adopt(MimeHeaders_get(msg_headers, HEADER_FROM, false, false));
+ if (!s.IsEmpty())
+ ExtractFirstAddress(EncodedHeader(s), from_name, from_addr);
+
+ /* Extract the name and address of the "Sender:" field. */
+ s.Adopt(MimeHeaders_get(msg_headers, HEADER_SENDER, false, false));
+ if (!s.IsEmpty())
+ ExtractFirstAddress(EncodedHeader(s), sender_name, sender_addr);
+}
+
+void MimeCMSRequestAsyncSignatureVerification(nsICMSMessage *aCMSMsg,
+ const char *aFromAddr, const char *aFromName,
+ const char *aSenderAddr, const char *aSenderName,
+ nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel,
+ const nsCString &aMsgNeckoURL,
+ unsigned char* item_data, uint32_t item_len,
+ int16_t digest_type)
+{
+ nsCOMPtr<nsICMSMessage2> msg2 = do_QueryInterface(aCMSMsg);
+ if (!msg2)
+ return;
+
+ RefPtr<nsSMimeVerificationListener> listener =
+ new nsSMimeVerificationListener(aFromAddr, aFromName, aSenderAddr, aSenderName,
+ aHeaderSink, aMimeNestingLevel, aMsgNeckoURL);
+ if (!listener)
+ return;
+
+ if (item_data)
+ msg2->AsyncVerifyDetachedSignature(listener, item_data, item_len, digest_type);
+ else
+ msg2->AsyncVerifySignature(listener);
+}
+
+static int
+MimeCMS_eof (void *crypto_closure, bool abort_p)
+{
+ MimeCMSdata *data = (MimeCMSdata *) crypto_closure;
+ nsresult rv;
+ int32_t status = nsICMSMessageErrors::SUCCESS;
+
+ if (!data || !data->output_fn) {
+ return -1;
+ }
+
+ if (!data->skip_content && !data->decoder_context) {
+ // If we don't skip, we should have a context.
+ return -1;
+ }
+
+ int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self);
+
+ /* Hand an EOF to the crypto library. It may call data->output_fn.
+ (Today, the crypto library has no flushing to do, but maybe there
+ will be someday.)
+
+ We save away the value returned and will use it later to emit a
+ blurb about whether the signature validation was cool.
+ */
+
+ PR_SetError(0, 0);
+ if (!data->skip_content) {
+ rv = data->decoder_context->Finish(getter_AddRefs(data->content_info));
+ if (NS_FAILED(rv)) status = nsICMSMessageErrors::GENERAL_ERROR;
+
+ data->decoder_context = nullptr;
+ }
+
+ nsCOMPtr<nsIX509Cert> certOfInterest;
+
+ if (!data->smimeHeaderSink)
+ return 0;
+
+ if (aRelativeNestLevel < 0)
+ return 0;
+
+ int32_t maxNestLevel = 0;
+ data->smimeHeaderSink->MaxWantedNesting(&maxNestLevel);
+
+ if (aRelativeNestLevel > maxNestLevel)
+ return 0;
+
+ if (data->decoding_failed)
+ status = nsICMSMessageErrors::GENERAL_ERROR;
+
+ if (data->skip_content) {
+ // Skipping content means, we detected a forbidden combination
+ // of CMS objects, so let's make sure we replace the parent status
+ // with a bad status.
+ if (data->any_parent_is_signed_p) {
+ data->smimeHeaderSink->SignedStatus(aRelativeNestLevel,
+ nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url);
+ }
+ if (data->any_parent_is_encrypted_p) {
+ data->smimeHeaderSink->EncryptionStatus(aRelativeNestLevel,
+ nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url);
+ }
+ return 0;
+ }
+
+ if (!data->content_info)
+ {
+ if (!data->decoded_bytes)
+ {
+ // We were unable to decode any data.
+ status = nsICMSMessageErrors::GENERAL_ERROR;
+ }
+ else
+ {
+ // Some content got decoded, but we failed to decode
+ // the final summary, probably we got truncated data.
+ status = nsICMSMessageErrors::ENCRYPT_INCOMPLETE;
+ }
+
+ // Although a CMS message could be either encrypted or opaquely signed,
+ // what we see is most likely encrypted, because if it were
+ // signed only, we probably would have been able to decode it.
+
+ data->ci_is_encrypted = true;
+ }
+ else
+ {
+ rv = data->content_info->ContentIsEncrypted(&data->ci_is_encrypted);
+
+ if (NS_SUCCEEDED(rv) && data->ci_is_encrypted) {
+ data->content_info->GetEncryptionCert(getter_AddRefs(certOfInterest));
+ }
+ else {
+ // Existing logic in mimei assumes, if !ci_is_encrypted, then it is signed.
+ // Make sure it indeed is signed.
+
+ bool testIsSigned;
+ rv = data->content_info->ContentIsSigned(&testIsSigned);
+
+ if (NS_FAILED(rv) || !testIsSigned) {
+ // Neither signed nor encrypted?
+ // We are unable to understand what we got, do not try to indicate S/Mime status.
+ return 0;
+ }
+
+ nsCString from_addr;
+ nsCString from_name;
+ nsCString sender_addr;
+ nsCString sender_name;
+
+ MimeCMSGetFromSender(data->self,
+ from_addr, from_name,
+ sender_addr, sender_name);
+
+ MimeCMSRequestAsyncSignatureVerification(data->content_info,
+ from_addr.get(), from_name.get(),
+ sender_addr.get(), sender_name.get(),
+ data->smimeHeaderSink, aRelativeNestLevel,
+ data->url, nullptr, 0, 0);
+ }
+ }
+
+ if (data->ci_is_encrypted)
+ {
+ data->smimeHeaderSink->EncryptionStatus(
+ aRelativeNestLevel,
+ status,
+ certOfInterest,
+ data->url
+ );
+ }
+
+ return 0;
+}
+
+static void
+MimeCMS_free (void *crypto_closure)
+{
+ MimeCMSdata *data = (MimeCMSdata *) crypto_closure;
+ if (!data) return;
+
+ delete data;
+}
+
+static char *
+MimeCMS_generate (void *crypto_closure)
+{
+ return nullptr;
+}
+
diff --git a/mailnews/mime/src/mimecms.h b/mailnews/mime/src/mimecms.h
new file mode 100644
index 000000000..fa08b28b8
--- /dev/null
+++ b/mailnews/mime/src/mimecms.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMECMS_H_
+#define _MIMECMS_H_
+
+#include "mimecryp.h"
+
+class nsICMSMessage;
+
+/* The MimeEncryptedCMS class implements a type of MIME object where the
+ object is passed through a CMS decryption engine to decrypt or verify
+ signatures. That module returns a new MIME object, which is then presented
+ to the user. See mimecryp.h for details of the general mechanism on which
+ this is built.
+ */
+
+typedef struct MimeEncryptedCMSClass MimeEncryptedCMSClass;
+typedef struct MimeEncryptedCMS MimeEncryptedCMS;
+
+struct MimeEncryptedCMSClass {
+ MimeEncryptedClass encrypted;
+};
+
+extern MimeEncryptedCMSClass mimeEncryptedCMSClass;
+
+struct MimeEncryptedCMS {
+ MimeEncrypted encrypted; /* superclass variables */
+};
+
+#define MimeEncryptedCMSClassInitializer(ITYPE,CSUPER) \
+ { MimeEncryptedClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEPKCS_H_ */
diff --git a/mailnews/mime/src/mimecom.cpp b/mailnews/mime/src/mimecom.cpp
new file mode 100644
index 000000000..384542742
--- /dev/null
+++ b/mailnews/mime/src/mimecom.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mimei.h"
+#include "mimeobj.h" /* MimeObject (abstract) */
+#include "mimecont.h" /* |--- MimeContainer (abstract) */
+#include "mimemult.h" /* | |--- MimeMultipart (abstract) */
+#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/
+#include "mimetext.h" /* | |--- MimeInlineText (abstract) */
+#include "mimecryp.h"
+#include "mimecth.h"
+
+/*
+ * These calls are necessary to expose the object class hierarchy
+ * to externally developed content type handlers.
+ */
+extern "C" void *
+XPCOM_GetmimeInlineTextClass(void)
+{
+ return (void *) &mimeInlineTextClass;
+}
+
+extern "C" void *
+XPCOM_GetmimeLeafClass(void)
+{
+ return (void *) &mimeLeafClass;
+}
+
+extern "C" void *
+XPCOM_GetmimeObjectClass(void)
+{
+ return (void *) &mimeObjectClass;
+}
+
+extern "C" void *
+XPCOM_GetmimeContainerClass(void)
+{
+ return (void *) &mimeContainerClass;
+}
+
+extern "C" void *
+XPCOM_GetmimeMultipartClass(void)
+{
+ return (void *) &mimeMultipartClass;
+}
+
+extern "C" void *
+XPCOM_GetmimeMultipartSignedClass(void)
+{
+ return (void *) &mimeMultipartSignedClass;
+}
+
+extern "C" void *
+XPCOM_GetmimeEncryptedClass(void)
+{
+ return (void *) &mimeEncryptedClass;
+}
+
+extern "C" int
+XPCOM_MimeObject_write(void *mimeObject,
+ char *data,
+ int32_t length,
+ bool user_visible_p)
+{
+ return MIME_MimeObject_write((MimeObject *)mimeObject, data,
+ length, user_visible_p);
+}
+
+extern "C" void *
+XPCOM_Mime_create(char *content_type, void* hdrs, void* opts)
+{
+ return mime_create(content_type, (MimeHeaders *)hdrs, (MimeDisplayOptions *)opts);
+}
diff --git a/mailnews/mime/src/mimecom.h b/mailnews/mime/src/mimecom.h
new file mode 100644
index 000000000..83c654a34
--- /dev/null
+++ b/mailnews/mime/src/mimecom.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * XP-COM Bridges for C function calls
+ */
+#ifndef _MIMECOM_H_
+#define _MIMECOM_H_
+
+#include <stdint.h>
+
+/*
+ * These functions are exposed by libmime to be used by content type
+ * handler plugins for processing stream data.
+ */
+/*
+ * This is the write call for outputting processed stream data.
+ */
+extern "C" int XPCOM_MimeObject_write(void *mimeObject, const char *data,
+ int32_t length,
+ bool user_visible_p);
+/*
+ * The following group of calls expose the pointers for the object
+ * system within libmime.
+ */
+extern "C" void *XPCOM_GetmimeInlineTextClass(void);
+extern "C" void *XPCOM_GetmimeLeafClass(void);
+extern "C" void *XPCOM_GetmimeObjectClass(void);
+extern "C" void *XPCOM_GetmimeContainerClass(void);
+extern "C" void *XPCOM_GetmimeMultipartClass(void);
+extern "C" void *XPCOM_GetmimeMultipartSignedClass(void);
+extern "C" void *XPCOM_GetmimeEncryptedClass(void);
+
+extern "C" void *XPCOM_Mime_create(char *content_type, void* hdrs, void* opts);
+
+#endif /* _MIMECOM_H_ */
diff --git a/mailnews/mime/src/mimecont.cpp b/mailnews/mime/src/mimecont.cpp
new file mode 100644
index 000000000..c5755a87f
--- /dev/null
+++ b/mailnews/mime/src/mimecont.cpp
@@ -0,0 +1,218 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "prio.h"
+#include "mimecont.h"
+#include "nsMimeStringResources.h"
+
+#define MIME_SUPERCLASS mimeObjectClass
+MimeDefClass(MimeContainer, MimeContainerClass,
+ mimeContainerClass, &MIME_SUPERCLASS);
+
+static int MimeContainer_initialize (MimeObject *);
+static void MimeContainer_finalize (MimeObject *);
+static int MimeContainer_add_child (MimeObject *, MimeObject *);
+static int MimeContainer_parse_eof (MimeObject *, bool);
+static int MimeContainer_parse_end (MimeObject *, bool);
+static bool MimeContainer_displayable_inline_p (MimeObjectClass *clazz,
+ MimeHeaders *hdrs);
+
+#if defined(DEBUG) && defined(XP_UNIX)
+static int MimeContainer_debug_print (MimeObject *, PRFileDesc *, int32_t depth);
+#endif
+
+static int
+MimeContainerClassInitialize(MimeContainerClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) &clazz->object;
+
+ NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ oclass->initialize = MimeContainer_initialize;
+ oclass->finalize = MimeContainer_finalize;
+ oclass->parse_eof = MimeContainer_parse_eof;
+ oclass->parse_end = MimeContainer_parse_end;
+ oclass->displayable_inline_p = MimeContainer_displayable_inline_p;
+ clazz->add_child = MimeContainer_add_child;
+
+#if defined(DEBUG) && defined(XP_UNIX)
+ oclass->debug_print = MimeContainer_debug_print;
+#endif
+ return 0;
+}
+
+
+static int
+MimeContainer_initialize (MimeObject *object)
+{
+ /* This is an abstract class; it shouldn't be directly instantiated. */
+ NS_ASSERTION(object->clazz != (MimeObjectClass *) &mimeContainerClass, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object);
+}
+
+static void
+MimeContainer_finalize (MimeObject *object)
+{
+ MimeContainer *cont = (MimeContainer *) object;
+
+ /* Do this first so that children have their parse_eof methods called
+ in forward order (0-N) but are destroyed in backward order (N-0)
+ */
+ if (!object->closed_p)
+ object->clazz->parse_eof (object, false);
+ if (!object->parsed_p)
+ object->clazz->parse_end (object, false);
+
+ if (cont->children)
+ {
+ int i;
+ for (i = cont->nchildren-1; i >= 0; i--)
+ {
+ MimeObject *kid = cont->children[i];
+ if (kid)
+ mime_free(kid);
+ cont->children[i] = 0;
+ }
+ PR_FREEIF(cont->children);
+ cont->nchildren = 0;
+ }
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object);
+}
+
+static int
+MimeContainer_parse_eof (MimeObject *object, bool abort_p)
+{
+ MimeContainer *cont = (MimeContainer *) object;
+ int status;
+
+ /* We must run all of this object's parent methods first, to get all the
+ data flushed down its stream, so that the children's parse_eof methods
+ can access it. We do not access *this* object again after doing this,
+ only its children.
+ */
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(object, abort_p);
+ if (status < 0) return status;
+
+ if (cont->children)
+ {
+ int i;
+ for (i = 0; i < cont->nchildren; i++)
+ {
+ MimeObject *kid = cont->children[i];
+ if (kid && !kid->closed_p)
+ {
+ int lstatus = kid->clazz->parse_eof(kid, abort_p);
+ if (lstatus < 0) return lstatus;
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+MimeContainer_parse_end (MimeObject *object, bool abort_p)
+{
+ MimeContainer *cont = (MimeContainer *) object;
+ int status;
+
+ /* We must run all of this object's parent methods first, to get all the
+ data flushed down its stream, so that the children's parse_eof methods
+ can access it. We do not access *this* object again after doing this,
+ only its children.
+ */
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_end(object, abort_p);
+ if (status < 0) return status;
+
+ if (cont->children)
+ {
+ int i;
+ for (i = 0; i < cont->nchildren; i++)
+ {
+ MimeObject *kid = cont->children[i];
+ if (kid && !kid->parsed_p)
+ {
+ int lstatus = kid->clazz->parse_end(kid, abort_p);
+ if (lstatus < 0) return lstatus;
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+MimeContainer_add_child (MimeObject *parent, MimeObject *child)
+{
+ MimeContainer *cont = (MimeContainer *) parent;
+ MimeObject **old_kids, **new_kids;
+
+ NS_ASSERTION(parent && child, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!parent || !child) return -1;
+
+ old_kids = cont->children;
+ new_kids = (MimeObject **)PR_MALLOC(sizeof(MimeObject *) * (cont->nchildren + 1));
+ if (!new_kids) return MIME_OUT_OF_MEMORY;
+
+ if (cont->nchildren > 0)
+ memcpy(new_kids, old_kids, sizeof(MimeObject *) * cont->nchildren);
+ new_kids[cont->nchildren] = child;
+ PR_Free(old_kids);
+ cont->children = new_kids;
+ cont->nchildren++;
+
+ child->parent = parent;
+
+ /* Copy this object's options into the child. */
+ child->options = parent->options;
+
+ return 0;
+}
+
+static bool
+MimeContainer_displayable_inline_p (MimeObjectClass *clazz, MimeHeaders *hdrs)
+{
+ return true;
+}
+
+
+#if defined(DEBUG) && defined(XP_UNIX)
+static int
+MimeContainer_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth)
+{
+ MimeContainer *cont = (MimeContainer *) obj;
+ int i;
+ char *addr = mime_part_address(obj);
+ for (i=0; i < depth; i++)
+ PR_Write(stream, " ", 2);
+ /*
+ PR_Write(stream, "<%s %s (%d kid%s) 0x%08X>\n",
+ obj->clazz->class_name,
+ addr ? addr : "???",
+ cont->nchildren, (cont->nchildren == 1 ? "" : "s"),
+ (uint32_t) cont);
+ */
+ PR_FREEIF(addr);
+
+/*
+ if (cont->nchildren > 0)
+ fprintf(stream, "\n");
+ */
+
+ for (i = 0; i < cont->nchildren; i++)
+ {
+ MimeObject *kid = cont->children[i];
+ int status = kid->clazz->debug_print (kid, stream, depth+1);
+ if (status < 0) return status;
+ }
+
+/*
+ if (cont->nchildren > 0)
+ fprintf(stream, "\n");
+ */
+
+ return 0;
+}
+#endif
diff --git a/mailnews/mime/src/mimecont.h b/mailnews/mime/src/mimecont.h
new file mode 100644
index 000000000..8f8edc566
--- /dev/null
+++ b/mailnews/mime/src/mimecont.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMECONT_H_
+#define _MIMECONT_H_
+
+#include "mimeobj.h"
+
+/* MimeContainer is the class for the objects representing all MIME
+ types which can contain other MIME objects within them. In addition
+ to the methods inherited from MimeObject, it provides one method:
+
+ int add_child (MimeObject *parent, MimeObject *child)
+
+ Given a parent (a subclass of MimeContainer) this method adds the
+ child (any MIME object) to the parent's list of children.
+
+ The MimeContainer `finalize' method will finalize the children as well.
+ */
+
+typedef struct MimeContainerClass MimeContainerClass;
+typedef struct MimeContainer MimeContainer;
+
+struct MimeContainerClass {
+ MimeObjectClass object;
+ int (*add_child) (MimeObject *parent, MimeObject *child);
+};
+
+extern MimeContainerClass mimeContainerClass;
+
+struct MimeContainer {
+ MimeObject object; /* superclass variables */
+
+ MimeObject **children; /* list of contained objects */
+ int32_t nchildren; /* how many */
+};
+
+#define MimeContainerClassInitializer(ITYPE,CSUPER) \
+ { MimeObjectClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMECONT_H_ */
diff --git a/mailnews/mime/src/mimecryp.cpp b/mailnews/mime/src/mimecryp.cpp
new file mode 100644
index 000000000..fd4f8f8ae
--- /dev/null
+++ b/mailnews/mime/src/mimecryp.cpp
@@ -0,0 +1,562 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimecryp.h"
+#include "mimemoz2.h"
+#include "nsMimeTypes.h"
+#include "nsMimeStringResources.h"
+#include "mimebuf.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "mimemult.h"
+#include "nsIMimeConverter.h" // for MimeConverterOutputCallback
+
+
+#define MIME_SUPERCLASS mimeContainerClass
+MimeDefClass(MimeEncrypted, MimeEncryptedClass, mimeEncryptedClass,
+ &MIME_SUPERCLASS);
+
+static int MimeEncrypted_initialize (MimeObject *);
+static void MimeEncrypted_finalize (MimeObject *);
+static int MimeEncrypted_parse_begin (MimeObject *);
+static int MimeEncrypted_parse_buffer (const char *, int32_t, MimeObject *);
+static int MimeEncrypted_parse_line (const char *, int32_t, MimeObject *);
+static int MimeEncrypted_parse_decoded_buffer (const char *, int32_t, MimeObject *);
+static int MimeEncrypted_parse_eof (MimeObject *, bool);
+static int MimeEncrypted_parse_end (MimeObject *, bool);
+static int MimeEncrypted_add_child (MimeObject *, MimeObject *);
+
+static int MimeHandleDecryptedOutput (const char *, int32_t, void *);
+static int MimeHandleDecryptedOutputLine (char *, int32_t, MimeObject *);
+static int MimeEncrypted_close_headers (MimeObject *);
+static int MimeEncrypted_emit_buffered_child(MimeObject *);
+
+static int
+MimeEncryptedClassInitialize(MimeEncryptedClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeContainerClass *cclass = (MimeContainerClass *) clazz;
+
+ NS_ASSERTION(!oclass->class_initialized, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59");
+ oclass->initialize = MimeEncrypted_initialize;
+ oclass->finalize = MimeEncrypted_finalize;
+ oclass->parse_begin = MimeEncrypted_parse_begin;
+ oclass->parse_buffer = MimeEncrypted_parse_buffer;
+ oclass->parse_line = MimeEncrypted_parse_line;
+ oclass->parse_eof = MimeEncrypted_parse_eof;
+ oclass->parse_end = MimeEncrypted_parse_end;
+
+ cclass->add_child = MimeEncrypted_add_child;
+
+ clazz->parse_decoded_buffer = MimeEncrypted_parse_decoded_buffer;
+
+ return 0;
+}
+
+
+static int
+MimeEncrypted_initialize (MimeObject *obj)
+{
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj);
+}
+
+
+static int
+MimeEncrypted_parse_begin (MimeObject *obj)
+{
+ MimeEncrypted *enc = (MimeEncrypted *) obj;
+ MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0;
+
+ if (enc->crypto_closure)
+ return -1;
+
+ enc->crypto_closure = (((MimeEncryptedClass *) obj->clazz)->crypto_init) (obj, MimeHandleDecryptedOutput, obj);
+ if (!enc->crypto_closure)
+ return -1;
+
+
+ /* (Mostly duplicated from MimeLeaf, see comments in mimecryp.h.)
+ Initialize a decoder if necessary.
+ */
+ if (!obj->encoding)
+ ;
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_BASE64))
+ fn = &MimeB64DecoderInit;
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_QUOTED_PRINTABLE))
+ {
+ enc->decoder_data =
+ MimeQPDecoderInit (/* The (MimeConverterOutputCallback) cast is to turn the
+ `void' argument into `MimeObject'. */
+ ((MimeConverterOutputCallback)
+ ((MimeEncryptedClass *)obj->clazz)->parse_decoded_buffer),
+ obj);
+
+ if (!enc->decoder_data)
+ return MIME_OUT_OF_MEMORY;
+ }
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE) ||
+ !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE2) ||
+ !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE3) ||
+ !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE4))
+ fn = &MimeUUDecoderInit;
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_YENCODE))
+ fn = &MimeYDecoderInit;
+ if (fn)
+ {
+ enc->decoder_data =
+ fn (/* The (MimeConverterOutputCallback) cast is to turn the `void'
+ argument into `MimeObject'. */
+ ((MimeConverterOutputCallback)
+ ((MimeEncryptedClass *)obj->clazz)->parse_decoded_buffer),
+ obj);
+
+ if (!enc->decoder_data)
+ return MIME_OUT_OF_MEMORY;
+ }
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+}
+
+
+static int
+MimeEncrypted_parse_buffer (const char *buffer, int32_t size, MimeObject *obj)
+{
+ /* (Duplicated from MimeLeaf, see comments in mimecryp.h.)
+ */
+
+ MimeEncrypted *enc = (MimeEncrypted *) obj;
+
+ if (obj->closed_p) return -1;
+
+ /* Don't consult output_p here, since at this point we're behaving as a
+ simple container object -- the output_p decision should be made by
+ the child of this object. */
+
+ if (enc->decoder_data)
+ return MimeDecoderWrite (enc->decoder_data, buffer, size, nullptr);
+ else
+ return ((MimeEncryptedClass *)obj->clazz)->parse_decoded_buffer (buffer,
+ size,
+ obj);
+}
+
+
+static int
+MimeEncrypted_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ NS_ERROR("This method shouldn't ever be called.");
+ return -1;
+}
+
+static int
+MimeEncrypted_parse_decoded_buffer (const char *buffer, int32_t size, MimeObject *obj)
+{
+ MimeEncrypted *enc = (MimeEncrypted *) obj;
+ return
+ ((MimeEncryptedClass *) obj->clazz)->crypto_write (buffer, size,
+ enc->crypto_closure);
+}
+
+
+static int
+MimeEncrypted_parse_eof (MimeObject *obj, bool abort_p)
+{
+ int status = 0;
+ MimeEncrypted *enc = (MimeEncrypted *) obj;
+
+ if (obj->closed_p) return 0;
+ NS_ASSERTION(!obj->parsed_p, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59");
+
+ /* (Duplicated from MimeLeaf, see comments in mimecryp.h.)
+ Close off the decoder, to cause it to give up any buffered data that
+ it is still holding.
+ */
+ if (enc->decoder_data)
+ {
+ int status = MimeDecoderDestroy(enc->decoder_data, false);
+ enc->decoder_data = 0;
+ if (status < 0) return status;
+ }
+
+
+ /* If there is still data in the ibuffer, that means that the last
+ *decrypted* line of this part didn't end in a newline; so push it out
+ anyway (this means that the parse_line method will be called with a
+ string with no trailing newline, which isn't the usual case.) */
+ if (!abort_p &&
+ obj->ibuffer_fp > 0)
+ {
+ int status = MimeHandleDecryptedOutputLine (obj->ibuffer,
+ obj->ibuffer_fp, obj);
+ obj->ibuffer_fp = 0;
+ if (status < 0)
+ {
+ obj->closed_p = true;
+ return status;
+ }
+ }
+
+
+ /* Now run the superclass's parse_eof, which (because we've already taken
+ care of ibuffer in a way appropriate for this class, immediately above)
+ will ony set closed_p to true.
+ */
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof (obj, abort_p);
+ if (status < 0) return status;
+
+
+ /* Now close off the underlying crypto module. At this point, the crypto
+ module has all of the input. (DecoderDestroy called parse_decoded_buffer
+ which called crypto_write, with the last of the data.)
+ */
+ if (enc->crypto_closure)
+ {
+ status =
+ ((MimeEncryptedClass *) obj->clazz)->crypto_eof (enc->crypto_closure,
+ abort_p);
+ if (status < 0 && !abort_p)
+ return status;
+ }
+
+ /* Now we have the entire child part in the part buffer.
+ We are now able to verify its signature, emit a blurb, and then
+ emit the part.
+ */
+ if (abort_p)
+ return 0;
+ else
+ return MimeEncrypted_emit_buffered_child (obj);
+}
+
+
+static int
+MimeEncrypted_parse_end (MimeObject *obj, bool abort_p)
+{
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_end (obj, abort_p);
+}
+
+
+static void
+MimeEncrypted_cleanup (MimeObject *obj, bool finalizing_p)
+{
+ MimeEncrypted *enc = (MimeEncrypted *) obj;
+
+ if (enc->part_buffer)
+ {
+ MimePartBufferDestroy(enc->part_buffer);
+ enc->part_buffer = 0;
+ }
+
+ if (finalizing_p && enc->crypto_closure)
+ {
+ /* Don't free these until this object is really going away -- keep them
+ around for the lifetime of the MIME object, so that we can get at the
+ security info of sub-parts of the currently-displayed message. */
+ ((MimeEncryptedClass *) obj->clazz)->crypto_free (enc->crypto_closure);
+ enc->crypto_closure = 0;
+ }
+
+ /* (Duplicated from MimeLeaf, see comments in mimecryp.h.)
+ Free the decoder data, if it's still around. */
+ if (enc->decoder_data)
+ {
+ MimeDecoderDestroy(enc->decoder_data, true);
+ enc->decoder_data = 0;
+ }
+
+ if (enc->hdrs)
+ {
+ MimeHeaders_free(enc->hdrs);
+ enc->hdrs = 0;
+ }
+}
+
+
+static void
+MimeEncrypted_finalize (MimeObject *obj)
+{
+ MimeEncrypted_cleanup (obj, true);
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (obj);
+}
+
+
+static int
+MimeHandleDecryptedOutput (const char *buf, int32_t buf_size,
+ void *output_closure)
+{
+ /* This method is invoked by the underlying decryption module.
+ The module is assumed to return a MIME object, and its associated
+ headers. For example, if a text/plain document was encrypted,
+ the encryption module would return the following data:
+
+ Content-Type: text/plain
+
+ Decrypted text goes here.
+
+ This function will then extract a header block (up to the first
+ blank line, as usual) and will then handle the included data as
+ appropriate.
+ */
+ MimeObject *obj = (MimeObject *) output_closure;
+
+ /* Is it truly safe to use ibuffer here? I think so... */
+ return mime_LineBuffer (buf, buf_size,
+ &obj->ibuffer, &obj->ibuffer_size, &obj->ibuffer_fp,
+ true,
+ ((int (*) (char *, int32_t, void *))
+ /* This cast is to turn void into MimeObject */
+ MimeHandleDecryptedOutputLine),
+ obj);
+}
+
+static int
+MimeHandleDecryptedOutputLine (char *line, int32_t length, MimeObject *obj)
+{
+ /* Largely the same as MimeMessage_parse_line (the other MIME container
+ type which contains exactly one child.)
+ */
+ MimeEncrypted *enc = (MimeEncrypted *) obj;
+ int status = 0;
+
+ if (!line || !*line) return -1;
+
+ /* If we're supposed to write this object, but aren't supposed to convert
+ it to HTML, simply pass it through unaltered. */
+ if (obj->output_p &&
+ obj->options &&
+ !obj->options->write_html_p &&
+ obj->options->output_fn)
+ return MimeObject_write(obj, line, length, true);
+
+ /* If we already have a child object in the buffer, then we're done parsing
+ headers, and all subsequent lines get passed to the inferior object
+ without further processing by us. (Our parent will stop feeding us
+ lines when this MimeMessage part is out of data.)
+ */
+ if (enc->part_buffer)
+ return MimePartBufferWrite (enc->part_buffer, line, length);
+
+ /* Otherwise we don't yet have a child object in the buffer, which means
+ we're not done parsing our headers yet.
+ */
+ if (!enc->hdrs)
+ {
+ enc->hdrs = MimeHeaders_new();
+ if (!enc->hdrs) return MIME_OUT_OF_MEMORY;
+ }
+
+ status = MimeHeaders_parse_line(line, length, enc->hdrs);
+ if (status < 0) return status;
+
+ /* If this line is blank, we're now done parsing headers, and should
+ examine our content-type to create our "body" part.
+ */
+ if (*line == '\r' || *line == '\n')
+ {
+ status = MimeEncrypted_close_headers(obj);
+ if (status < 0) return status;
+ }
+
+ return 0;
+}
+
+static int
+MimeEncrypted_close_headers (MimeObject *obj)
+{
+ MimeEncrypted *enc = (MimeEncrypted *) obj;
+
+ // Notify the JS Mime Emitter that this was an encrypted part that it should
+ // hopefully not analyze for indexing...
+ if (obj->options->notify_nested_bodies)
+ mimeEmitterAddHeaderField(obj->options, "x-jsemitter-encrypted", "1");
+
+ if (enc->part_buffer) return -1;
+ enc->part_buffer = MimePartBufferCreate();
+ if (!enc->part_buffer)
+ return MIME_OUT_OF_MEMORY;
+
+ return 0;
+}
+
+
+static int
+MimeEncrypted_add_child (MimeObject *parent, MimeObject *child)
+{
+ MimeContainer *cont = (MimeContainer *) parent;
+ if (!parent || !child) return -1;
+
+ /* Encryption containers can only have one child. */
+ if (cont->nchildren != 0) return -1;
+
+ return ((MimeContainerClass*)&MIME_SUPERCLASS)->add_child (parent, child);
+}
+
+
+static int
+MimeEncrypted_emit_buffered_child(MimeObject *obj)
+{
+ MimeEncrypted *enc = (MimeEncrypted *) obj;
+ int status = 0;
+ char *ct = 0;
+ MimeObject *body;
+
+ NS_ASSERTION(enc->crypto_closure, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59");
+
+ /* Emit some HTML saying whether the signature was cool.
+ But don't emit anything if in FO_QUOTE_MESSAGE mode.
+
+ Also, don't emit anything if the enclosed object is itself a signed
+ object -- in the case of an encrypted object which contains a signed
+ object, we only emit the HTML once (since the normal way of encrypting
+ and signing is to nest the signature inside the crypto envelope.)
+ */
+ if (enc->crypto_closure &&
+ obj->options &&
+ obj->options->headers != MimeHeadersCitation &&
+ obj->options->write_html_p &&
+ obj->options->output_fn)
+ {
+ char *html;
+
+ /* Now that we have written out the crypto stamp, the outermost header
+ block is well and truly closed. If this is in fact the outermost
+ message, then run the post_header_html_fn now.
+ */
+ if (obj->options &&
+ obj->options->state &&
+ obj->options->generate_post_header_html_fn &&
+ !obj->options->state->post_header_html_run_p)
+ {
+ MimeHeaders *outer_headers = nullptr;
+ MimeObject *p;
+ for (p = obj; p->parent; p = p->parent)
+ outer_headers = p->headers;
+ NS_ASSERTION(obj->options->state->first_data_written_p, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59");
+ char *html = obj->options->generate_post_header_html_fn(
+ NULL,
+ obj->options->html_closure,
+ outer_headers);
+ obj->options->state->post_header_html_run_p = true;
+ if (html)
+ {
+ status = MimeObject_write(obj, html, strlen(html), false);
+ PR_FREEIF(html);
+ if (status < 0) return status;
+ }
+ }
+ }
+ else if (enc->crypto_closure &&
+ obj->options &&
+ obj->options->decrypt_p)
+ {
+ /* Do this just to cause `mime_set_crypto_stamp' to be called, and to
+ cause the various `decode_error' and `verify_error' slots to be set:
+ we don't actually use the returned HTML, because we're not emitting
+ HTML. It's maybe not such a good thing that the determination of
+ whether it was encrypted or not is tied up with generating HTML,
+ but oh well. */
+ char *html = (((MimeEncryptedClass *) obj->clazz)->crypto_generate_html
+ (enc->crypto_closure));
+ PR_FREEIF(html);
+ }
+
+ if (enc->hdrs)
+ ct = MimeHeaders_get (enc->hdrs, HEADER_CONTENT_TYPE, true, false);
+ body = mime_create((ct ? ct : TEXT_PLAIN), enc->hdrs, obj->options);
+
+#ifdef MIME_DRAFTS
+ if (obj->options->decompose_file_p) {
+ if (mime_typep (body, (MimeObjectClass*) &mimeMultipartClass) )
+ obj->options->is_multipart_msg = true;
+ else if (obj->options->decompose_file_init_fn)
+ obj->options->decompose_file_init_fn(obj->options->stream_closure,
+ enc->hdrs);
+ }
+#endif /* MIME_DRAFTS */
+
+ PR_FREEIF(ct);
+
+ if (!body) return MIME_OUT_OF_MEMORY;
+ status = ((MimeContainerClass *) obj->clazz)->add_child (obj, body);
+ if (status < 0)
+ {
+ mime_free(body);
+ return status;
+ }
+
+ /* Now that we've added this new object to our list of children,
+ start its parser going. */
+ status = body->clazz->parse_begin(body);
+ if (status < 0) return status;
+
+ /* If this object (or the parent) is being output, then by definition
+ the child is as well. (This is only necessary because this is such
+ a funny sort of container...)
+ */
+ if (!body->output_p &&
+ (obj->output_p ||
+ (obj->parent && obj->parent->output_p)))
+ body->output_p = true;
+
+ /* If the body is being written raw (not as HTML) then make sure to
+ write its headers as well. */
+ if (body->output_p && obj->output_p && !obj->options->write_html_p)
+ {
+ status = MimeObject_write(body, "", 0, false); /* initialize */
+ if (status < 0) return status;
+ status = MimeHeaders_write_raw_headers(body->headers, obj->options,
+ false);
+ if (status < 0) return status;
+ }
+
+ if (enc->part_buffer) /* part_buffer is 0 for 0-length encrypted data. */
+ {
+#ifdef MIME_DRAFTS
+ if (obj->options->decompose_file_p && !obj->options->is_multipart_msg)
+ {
+ status = MimePartBufferRead(enc->part_buffer,
+ /* The (MimeConverterOutputCallback) cast is to turn the `void'
+ argument into `MimeObject'. */
+ ((MimeConverterOutputCallback)
+ obj->options->decompose_file_output_fn),
+ obj->options->stream_closure);
+ }
+ else
+ {
+#endif /* MIME_DRAFTS */
+
+ status = MimePartBufferRead(enc->part_buffer,
+ /* The (MimeConverterOutputCallback) cast is to turn the `void'
+ argument into `MimeObject'. */
+ ((MimeConverterOutputCallback) body->clazz->parse_buffer),
+ body);
+#ifdef MIME_DRAFTS
+ }
+#endif /* MIME_DRAFTS */
+ }
+ if (status < 0) return status;
+
+ /* The child has been fully processed. Close it off.
+ */
+ status = body->clazz->parse_eof(body, false);
+ if (status < 0) return status;
+
+ status = body->clazz->parse_end(body, false);
+ if (status < 0) return status;
+
+#ifdef MIME_DRAFTS
+ if (obj->options->decompose_file_p && !obj->options->is_multipart_msg)
+ obj->options->decompose_file_close_fn(obj->options->stream_closure);
+#endif /* MIME_DRAFTS */
+
+ /* Put out a separator after every encrypted object. */
+ status = MimeObject_write_separator(obj);
+ if (status < 0) return status;
+
+ MimeEncrypted_cleanup (obj, false);
+
+ return 0;
+}
diff --git a/mailnews/mime/src/mimecryp.h b/mailnews/mime/src/mimecryp.h
new file mode 100644
index 000000000..c74770b18
--- /dev/null
+++ b/mailnews/mime/src/mimecryp.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMECRYP_H_
+#define _MIMECRYP_H_
+
+#include "mimecont.h"
+// #include "mimeenc.h"
+#include "modmimee.h"
+#include "mimepbuf.h"
+
+/* The MimeEncrypted class implements a type of MIME object where the object
+ is passed to some other routine, which then returns a new MIME object.
+ This is the basis of a decryption module.
+
+ Oddly, this class behaves both as a container and as a leaf: it acts as a
+ container in that it parses out data in order to eventually present a
+ contained object; however, it acts as a leaf in that this container may
+ itself have a Content-Transfer-Encoding applied to its body. This violates
+ the cardinal rule of MIME containers, which is that encodings don't nest,
+ and therefore containers can't have encodings. But, the fact that the
+ S/MIME spec doesn't follow the groundwork laid down by previous MIME specs
+ isn't something we can do anything about at this point...
+
+ Therefore, this class duplicates some of the work done by the MimeLeaf
+ class, to meet its dual goals of container-hood and leaf-hood. (We could
+ alternately have made this class be a subclass of leaf, and had it duplicate
+ the effort of MimeContainer, but that seemed like the harder approach.)
+
+ The MimeEncrypted class provides the following methods:
+
+ void *crypto_init(MimeObject *obj,
+ int (*output_fn) (const char *data, int32 data_size,
+ void *output_closure),
+ void *output_closure)
+
+ This is called with the MimeObject representing the encrypted data.
+ The obj->headers should be used to initialize the decryption engine.
+ NULL indicates failure; otherwise, an opaque closure object should
+ be returned.
+
+ output_fn is what the decryption module should use to write a new MIME
+ object (the decrypted data.) output_closure should be passed along to
+ every call to the output routine.
+
+ The data sent to output_fn should begin with valid MIME headers indicating
+ the type of the data. For example, if decryption resulted in a text
+ document, the data fed through to the output_fn might minimally look like
+
+ Content-Type: text/plain
+
+ This is the decrypted data.
+ It is only two lines long.
+
+ Of course, the data may be of any MIME type, including multipart/mixed.
+ Any returned MIME object will be recursively processed and presented
+ appropriately. (This also imples that encrypted objects may nest, and
+ thus that the underlying decryption module must be reentrant.)
+
+ int crypto_write (const char *data, int32 data_size, void *crypto_closure)
+
+ This is called with the raw encrypted data. This data might not come
+ in line-based chunks: if there was a Content-Transfer-Encoding applied
+ to the data (base64 or quoted-printable) then it will have been decoded
+ first (handing binary data to the filter_fn.) `crypto_closure' is the
+ object that `crypto_init' returned. This may return negative on error.
+
+ int crypto_eof (void *crypto_closure, bool abort_p)
+
+ This is called when no more data remains. It may call `output_fn' again
+ to flush out any buffered data. If `abort_p' is true, then it may choose
+ to discard any data rather than processing it, as we're terminating
+ abnormally.
+
+ char * crypto_generate_html (void *crypto_closure)
+
+ This is called after `crypto_eof' but before `crypto_free'. The crypto
+ module should return a newly-allocated string of HTML code which
+ explains the status of the decryption to the user (whether the signature
+ checked out, etc.)
+
+ void crypto_free (void *crypto_closure)
+
+ This will be called when we're all done, after `crypto_eof' and
+ `crypto_emit_html'. It is intended to free any data represented
+ by the crypto_closure. output_fn may not be called.
+
+
+ int (*parse_decoded_buffer) (const char *buf, int32 size, MimeObject *obj)
+
+ This method, of the same name as one in MimeLeaf, is a part of the
+ afforementioned leaf/container hybridization. This method is invoked
+ with the content-transfer-decoded body of this part (without line
+ buffering.) The default behavior of this method is to simply invoke
+ `crypto_write' on the data with which it is called. It's unlikely that
+ a subclass will need to specialize this.
+ */
+
+typedef struct MimeEncryptedClass MimeEncryptedClass;
+typedef struct MimeEncrypted MimeEncrypted;
+
+struct MimeEncryptedClass {
+ MimeContainerClass container;
+
+ /* Duplicated from MimeLeaf, see comments above.
+ This is the callback that is handed to the decoder. */
+ int (*parse_decoded_buffer) (const char *buf, int32_t size, MimeObject *obj);
+
+
+ /* Callbacks used by decryption module. */
+ void * (*crypto_init) (MimeObject *obj,
+ int (*output_fn) (const char *data, int32_t data_size,
+ void *output_closure),
+ void *output_closure);
+ int (*crypto_write) (const char *data, int32_t data_size,
+ void *crypto_closure);
+ int (*crypto_eof) (void *crypto_closure, bool abort_p);
+ char * (*crypto_generate_html) (void *crypto_closure);
+ void (*crypto_free) (void *crypto_closure);
+};
+
+extern MimeEncryptedClass mimeEncryptedClass;
+
+struct MimeEncrypted {
+ MimeContainer container; /* superclass variables */
+ void *crypto_closure; /* Opaque data used by decryption module. */
+ MimeDecoderData *decoder_data; /* Opaque data for the Transfer-Encoding
+ decoder. */
+ MimeHeaders *hdrs; /* Headers of the enclosed object (including
+ the type of the *decrypted* data.) */
+ MimePartBufferData *part_buffer; /* The data of the decrypted enclosed
+ object (see mimepbuf.h) */
+};
+
+#define MimeEncryptedClassInitializer(ITYPE,CSUPER) \
+ { MimeContainerClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMECRYP_H_ */
diff --git a/mailnews/mime/src/mimecth.cpp b/mailnews/mime/src/mimecth.cpp
new file mode 100644
index 000000000..1cfaba82c
--- /dev/null
+++ b/mailnews/mime/src/mimecth.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mimecth.h"
+
+/*
+ * These calls are necessary to expose the object class hierarchy
+ * to externally developed content type handlers.
+ */
+MimeInlineTextClass *
+MIME_GetmimeInlineTextClass(void)
+{
+ return &mimeInlineTextClass;
+}
+
+MimeLeafClass *
+MIME_GetmimeLeafClass(void)
+{
+ return &mimeLeafClass;
+}
+
+MimeObjectClass *
+MIME_GetmimeObjectClass(void)
+{
+ return &mimeObjectClass;
+}
+
+MimeContainerClass *
+MIME_GetmimeContainerClass(void)
+{
+ return &mimeContainerClass;
+}
+
+MimeMultipartClass *
+MIME_GetmimeMultipartClass(void)
+{
+ return &mimeMultipartClass;
+}
+
+MimeMultipartSignedClass *
+MIME_GetmimeMultipartSignedClass(void)
+{
+ return &mimeMultipartSignedClass;
+}
+
+MimeEncryptedClass *
+MIME_GetmimeEncryptedClass(void)
+{
+ return &mimeEncryptedClass;
+}
diff --git a/mailnews/mime/src/mimecth.h b/mailnews/mime/src/mimecth.h
new file mode 100644
index 000000000..1f5d89663
--- /dev/null
+++ b/mailnews/mime/src/mimecth.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This is the definitions for the Content Type Handler plugins for
+ * libmime. This will allow developers the dynamically add the ability
+ * for libmime to render new content types in the MHTML rendering of
+ * HTML messages.
+ */
+
+#ifndef _MIMECTH_H_
+#define _MIMECTH_H_
+
+#include "mimei.h"
+#include "mimeobj.h" /* MimeObject (abstract) */
+#include "mimecont.h" /* |--- MimeContainer (abstract) */
+#include "mimemult.h" /* | |--- MimeMultipart (abstract) */
+#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/
+#include "mimetext.h" /* | |--- MimeInlineText (abstract) */
+#include "mimecryp.h"
+
+/*
+ This header exposes functions that are necessary to access the
+ object hierarchy for the mime chart. The class hierarchy is:
+
+ MimeObject (abstract)
+ |
+ |--- MimeContainer (abstract)
+ | |
+ | |--- MimeMultipart (abstract)
+ | | |
+ | | |--- MimeMultipartMixed
+ | | |
+ | | |--- MimeMultipartDigest
+ | | |
+ | | |--- MimeMultipartParallel
+ | | |
+ | | |--- MimeMultipartAlternative
+ | | |
+ | | |--- MimeMultipartRelated
+ | | |
+ | | |--- MimeMultipartAppleDouble
+ | | |
+ | | |--- MimeSunAttachment
+ | | |
+ | | |--- MimeMultipartSigned (abstract)
+ | | |
+ | | |--- MimeMultipartSigned
+ | |
+ | |--- MimeXlateed (abstract)
+ | | |
+ | | |--- MimeXlateed
+ | |
+ | |--- MimeMessage
+ | |
+ | |--- MimeUntypedText
+ |
+ |--- MimeLeaf (abstract)
+ | |
+ | |--- MimeInlineText (abstract)
+ | | |
+ | | |--- MimeInlineTextPlain
+ | | |
+ | | |--- MimeInlineTextHTML
+ | | |
+ | | |--- MimeInlineTextRichtext
+ | | | |
+ | | | |--- MimeInlineTextEnriched
+ | | |
+ | | |--- MimeInlineTextVCard
+ | |
+ | |--- MimeInlineImage
+ | |
+ | |--- MimeExternalObject
+ |
+ |--- MimeExternalBody
+ */
+
+#include "nsIMimeContentTypeHandler.h"
+
+/*
+ * These functions are exposed by libmime to be used by content type
+ * handler plugins for processing stream data.
+ */
+/*
+ * This is the write call for outputting processed stream data.
+ */
+extern int MIME_MimeObject_write(MimeObject *,
+ const char *data,
+ int32_t length,
+ bool user_visible_p);
+/*
+ * The following group of calls expose the pointers for the object
+ * system within libmime.
+ */
+extern MimeInlineTextClass *MIME_GetmimeInlineTextClass(void);
+extern MimeLeafClass *MIME_GetmimeLeafClass(void);
+extern MimeObjectClass *MIME_GetmimeObjectClass(void);
+extern MimeContainerClass *MIME_GetmimeContainerClass(void);
+extern MimeMultipartClass *MIME_GetmimeMultipartClass(void);
+extern MimeMultipartSignedClass *MIME_GetmimeMultipartSignedClass(void);
+extern MimeEncryptedClass *MIME_GetmimeEncryptedClass(void);
+
+/*
+ * These are the functions that need to be implemented by the
+ * content type handler plugin. They will be called by by libmime
+ * when the module is loaded at runtime.
+ */
+
+/*
+ * MIME_GetContentType() is called by libmime to identify the content
+ * type handled by this plugin.
+ */
+extern "C"
+char *MIME_GetContentType(void);
+
+/*
+ * This will create the MimeObjectClass object to be used by the libmime
+ * object system.
+ */
+extern "C"
+MimeObjectClass *MIME_CreateContentTypeHandlerClass(const char *content_type,
+ contentTypeHandlerInitStruct *initStruct);
+
+/*
+ * Typedefs for libmime to use when locating and calling the above
+ * defined functions.
+ */
+typedef char * (*mime_get_ct_fn_type)(void);
+typedef MimeObjectClass * (*mime_create_class_fn_type)
+ (const char *, contentTypeHandlerInitStruct *);
+
+#endif /* _MIMECTH_H_ */
diff --git a/mailnews/mime/src/mimedrft.cpp b/mailnews/mime/src/mimedrft.cpp
new file mode 100644
index 000000000..90ca027d8
--- /dev/null
+++ b/mailnews/mime/src/mimedrft.cpp
@@ -0,0 +1,2084 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+#include "nsCOMPtr.h"
+#include "modmimee.h"
+#include "mimeobj.h"
+#include "modlmime.h"
+#include "mimei.h"
+#include "mimebuf.h"
+#include "mimemoz2.h"
+#include "mimemsg.h"
+#include "nsMimeTypes.h"
+#include <ctype.h>
+
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+#include "prio.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "msgCore.h"
+#include "nsIMsgSend.h"
+#include "nsMimeStringResources.h"
+#include "nsIIOService.h"
+#include "nsNetUtil.h"
+#include "comi18n.h"
+#include "nsIMsgAttachment.h"
+#include "nsIMsgCompFields.h"
+#include "nsMsgCompCID.h"
+#include "nsIMsgComposeService.h"
+#include "nsMsgAttachmentData.h"
+#include "nsMsgI18N.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIMIMEService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMimeConverter.h" // for MimeConverterOutputCallback
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+using namespace mozilla::mailnews;
+
+//
+// Header strings...
+//
+#define HEADER_NNTP_POSTING_HOST "NNTP-Posting-Host"
+#define MIME_HEADER_TABLE "<TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0 class=\"moz-email-headers-table\">"
+#define HEADER_START_JUNK "<TR><TH VALIGN=BASELINE ALIGN=RIGHT NOWRAP>"
+#define HEADER_MIDDLE_JUNK ": </TH><TD>"
+#define HEADER_END_JUNK "</TD></TR>"
+
+//
+// Forward declarations...
+//
+extern "C" char *MIME_StripContinuations(char *original);
+int mime_decompose_file_init_fn(void *stream_closure, MimeHeaders *headers);
+int mime_decompose_file_output_fn(const char *buf, int32_t size, void *stream_closure);
+int mime_decompose_file_close_fn(void *stream_closure);
+extern int MimeHeaders_build_heads_list(MimeHeaders *hdrs);
+
+// CID's
+static NS_DEFINE_CID(kCMsgComposeServiceCID, NS_MSGCOMPOSESERVICE_CID);
+
+mime_draft_data::mime_draft_data() : url_name(nullptr), format_out(0),
+ stream(nullptr), obj(nullptr), options(nullptr), headers(nullptr),
+ messageBody(nullptr), curAttachment(nullptr),
+ decoder_data(nullptr), mailcharset(nullptr), forwardInline(false),
+ forwardInlineFilter(false), overrideComposeFormat(false),
+ originalMsgURI(nullptr)
+{
+}
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+// THIS SHOULD ALL MOVE TO ANOTHER FILE AFTER LANDING!
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+
+// safe filename for all OSes
+#define SAFE_TMP_FILENAME "nsmime.tmp"
+
+//
+// Create a file for the a unique temp file
+// on the local machine. Caller must free memory
+//
+nsresult
+nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile)
+{
+ if (!tFileName || !*tFileName)
+ tFileName = SAFE_TMP_FILENAME;
+
+ nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ tFileName,
+ tFile);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = (*tFile)->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv))
+ NS_RELEASE(*tFile);
+
+ return rv;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+// END OF - THIS SHOULD ALL MOVE TO ANOTHER FILE AFTER LANDING!
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+
+typedef enum {
+ nsMsg_RETURN_RECEIPT_BOOL_HEADER_MASK = 0,
+ nsMsg_ENCRYPTED_BOOL_HEADER_MASK,
+ nsMsg_SIGNED_BOOL_HEADER_MASK,
+ nsMsg_UUENCODE_BINARY_BOOL_HEADER_MASK,
+ nsMsg_ATTACH_VCARD_BOOL_HEADER_MASK,
+ nsMsg_LAST_BOOL_HEADER_MASK // last boolean header mask; must be the last one
+ // DON'T remove.
+} nsMsgBoolHeaderSet;
+
+#ifdef NS_DEBUG
+extern "C" void
+mime_dump_attachments(nsMsgAttachmentData *attachData)
+{
+ int32_t i = 0;
+ class nsMsgAttachmentData *tmp = attachData;
+
+ while (tmp && tmp->m_url)
+ {
+ printf("Real Name : %s\n", tmp->m_realName.get());
+
+ if (tmp->m_url)
+ {
+ ;
+ printf("URL : %s\n", tmp->m_url->GetSpecOrDefault().get());
+ }
+
+ printf("Desired Type : %s\n", tmp->m_desiredType.get());
+ printf("Real Type : %s\n", tmp->m_realType.get());
+ printf("Real Encoding : %s\n", tmp->m_realEncoding.get());
+ printf("Description : %s\n", tmp->m_description.get());
+ printf("Mac Type : %s\n", tmp->m_xMacType.get());
+ printf("Mac Creator : %s\n", tmp->m_xMacCreator.get());
+ printf("Size in bytes : %d\n", tmp->m_size);
+ i++;
+ tmp++;
+ }
+}
+#endif
+
+nsresult CreateComposeParams(nsCOMPtr<nsIMsgComposeParams> &pMsgComposeParams,
+ nsIMsgCompFields * compFields,
+ nsMsgAttachmentData *attachmentList,
+ MSG_ComposeType composeType,
+ MSG_ComposeFormat composeFormat,
+ nsIMsgIdentity * identity,
+ const char *originalMsgURI,
+ nsIMsgDBHdr *origMsgHdr)
+{
+#ifdef NS_DEBUG
+ mime_dump_attachments(attachmentList);
+#endif
+
+ nsresult rv;
+ nsMsgAttachmentData *curAttachment = attachmentList;
+ if (curAttachment)
+ {
+ nsAutoCString spec;
+
+ while (curAttachment && curAttachment->m_url)
+ {
+ rv = curAttachment->m_url->GetSpec(spec);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && attachment)
+ {
+ nsAutoString nameStr;
+ rv = ConvertToUnicode("UTF-8", curAttachment->m_realName.get(), nameStr);
+ if (NS_FAILED(rv))
+ CopyASCIItoUTF16(curAttachment->m_realName, nameStr);
+ attachment->SetName(nameStr);
+ attachment->SetUrl(spec);
+ attachment->SetTemporary(true);
+ attachment->SetContentType(curAttachment->m_realType.get());
+ attachment->SetMacType(curAttachment->m_xMacType.get());
+ attachment->SetMacCreator(curAttachment->m_xMacCreator.get());
+ attachment->SetSize(curAttachment->m_size);
+ if (!curAttachment->m_cloudPartInfo.IsEmpty())
+ {
+ nsCString provider;
+ nsCString cloudUrl;
+ attachment->SetSendViaCloud(true);
+ provider.Adopt(
+ MimeHeaders_get_parameter(curAttachment->m_cloudPartInfo.get(),
+ "provider", nullptr, nullptr));
+ cloudUrl.Adopt(
+ MimeHeaders_get_parameter(curAttachment->m_cloudPartInfo.get(),
+ "url", nullptr, nullptr));
+ attachment->SetCloudProviderKey(provider);
+ attachment->SetContentLocation(cloudUrl);
+ }
+ compFields->AddAttachment(attachment);
+ }
+ }
+ curAttachment++;
+ }
+ }
+
+ MSG_ComposeFormat format = composeFormat; // Format to actually use.
+ if (identity && composeType == nsIMsgCompType::ForwardInline)
+ {
+ bool composeHtml = false;
+ identity->GetComposeHtml(&composeHtml);
+ if (composeHtml)
+ format = (composeFormat == nsIMsgCompFormat::OppositeOfDefault) ?
+ nsIMsgCompFormat::PlainText : nsIMsgCompFormat::HTML;
+ else
+ format = (composeFormat == nsIMsgCompFormat::OppositeOfDefault) ?
+ nsIMsgCompFormat::HTML : nsIMsgCompFormat::PlainText;
+ }
+
+ pMsgComposeParams = do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pMsgComposeParams->SetType(composeType);
+ pMsgComposeParams->SetFormat(format);
+ pMsgComposeParams->SetIdentity(identity);
+ pMsgComposeParams->SetComposeFields(compFields);
+ if (originalMsgURI)
+ pMsgComposeParams->SetOriginalMsgURI(originalMsgURI);
+ if (origMsgHdr)
+ pMsgComposeParams->SetOrigMsgHdr(origMsgHdr);
+ return NS_OK;
+}
+
+nsresult
+CreateTheComposeWindow(nsIMsgCompFields * compFields,
+ nsMsgAttachmentData *attachmentList,
+ MSG_ComposeType composeType,
+ MSG_ComposeFormat composeFormat,
+ nsIMsgIdentity * identity,
+ const char * originalMsgURI,
+ nsIMsgDBHdr * origMsgHdr
+ )
+{
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams;
+ nsresult rv = CreateComposeParams(pMsgComposeParams, compFields,
+ attachmentList,
+ composeType,
+ composeFormat,
+ identity,
+ originalMsgURI,
+ origMsgHdr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgComposeService> msgComposeService =
+ do_GetService(kCMsgComposeServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return msgComposeService->OpenComposeWindowWithParams(nullptr /* default chrome */, pMsgComposeParams);
+}
+
+nsresult
+ForwardMsgInline(nsIMsgCompFields *compFields,
+ nsMsgAttachmentData *attachmentList,
+ MSG_ComposeFormat composeFormat,
+ nsIMsgIdentity *identity,
+ const char *originalMsgURI,
+ nsIMsgDBHdr *origMsgHdr)
+{
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams;
+ nsresult rv = CreateComposeParams(pMsgComposeParams, compFields,
+ attachmentList,
+ nsIMsgCompType::ForwardInline,
+ composeFormat,
+ identity,
+ originalMsgURI,
+ origMsgHdr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgComposeService> msgComposeService =
+ do_GetService(kCMsgComposeServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // create the nsIMsgCompose object to send the object
+ nsCOMPtr<nsIMsgCompose> pMsgCompose(do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /** initialize nsIMsgCompose, Send the message, wait for send completion response **/
+ rv = pMsgCompose->Initialize(pMsgComposeParams, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, identity, nullptr, nullptr, nullptr);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> origFolder;
+ origMsgHdr->GetFolder(getter_AddRefs(origFolder));
+ if (origFolder)
+ origFolder->AddMessageDispositionState(
+ origMsgHdr, nsIMsgFolder::nsMsgDispositionState_Forwarded);
+ }
+ return rv;
+}
+
+nsresult
+CreateCompositionFields(const char *from,
+ const char *reply_to,
+ const char *to,
+ const char *cc,
+ const char *bcc,
+ const char *fcc,
+ const char *newsgroups,
+ const char *followup_to,
+ const char *organization,
+ const char *subject,
+ const char *references,
+ const char *priority,
+ const char *newspost_url,
+ char *charset,
+ nsIMsgCompFields **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+ *_retval = nullptr;
+
+ nsCOMPtr<nsIMsgCompFields> cFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(cFields, NS_ERROR_OUT_OF_MEMORY);
+
+ // Now set all of the passed in stuff...
+ cFields->SetCharacterSet(!PL_strcasecmp("us-ascii", charset) ? "ISO-8859-1" : charset);
+
+ nsAutoCString val;
+ nsAutoString outString;
+
+ if (from) {
+ ConvertRawBytesToUTF16(from, charset, outString);
+ cFields->SetFrom(outString);
+ }
+
+ if (subject) {
+ MIME_DecodeMimeHeader(subject, charset, false, true, val);
+ cFields->SetSubject(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : subject));
+ }
+
+ if (reply_to) {
+ ConvertRawBytesToUTF16(reply_to, charset, outString);
+ cFields->SetReplyTo(outString);
+ }
+
+ if (to) {
+ ConvertRawBytesToUTF16(to, charset, outString);
+ cFields->SetTo(outString);
+ }
+
+ if (cc) {
+ ConvertRawBytesToUTF16(cc, charset, outString);
+ cFields->SetCc(outString);
+ }
+
+ if (bcc) {
+ ConvertRawBytesToUTF16(bcc, charset, outString);
+ cFields->SetBcc(outString);
+ }
+
+ if (fcc) {
+ MIME_DecodeMimeHeader(fcc, charset, false, true, val);
+ cFields->SetFcc(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : fcc));
+ }
+
+ if (newsgroups) {
+ // fixme: the newsgroups header had better be decoded using the server-side
+ // character encoding,but this |charset| might be different from it.
+ MIME_DecodeMimeHeader(newsgroups, charset, false, true, val);
+ cFields->SetNewsgroups(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : newsgroups));
+ }
+
+ if (followup_to) {
+ MIME_DecodeMimeHeader(followup_to, charset, false, true, val);
+ cFields->SetFollowupTo(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : followup_to));
+ }
+
+ if (organization) {
+ MIME_DecodeMimeHeader(organization, charset, false, true, val);
+ cFields->SetOrganization(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : organization));
+ }
+
+ if (references) {
+ MIME_DecodeMimeHeader(references, charset, false, true, val);
+ cFields->SetReferences(!val.IsEmpty() ? val.get() : references);
+ }
+
+ if (priority) {
+ MIME_DecodeMimeHeader(priority, charset, false, true, val);
+ nsMsgPriorityValue priorityValue;
+ NS_MsgGetPriorityFromString(!val.IsEmpty() ? val.get() : priority, priorityValue);
+ nsAutoCString priorityName;
+ NS_MsgGetUntranslatedPriorityName(priorityValue, priorityName);
+ cFields->SetPriority(priorityName.get());
+ }
+
+ if (newspost_url) {
+ MIME_DecodeMimeHeader(newspost_url, charset, false, true, val);
+ cFields->SetNewspostUrl(!val.IsEmpty() ? val.get() : newspost_url);
+ }
+
+ *_retval = cFields;
+ NS_IF_ADDREF(*_retval);
+
+ return rv;
+}
+
+static int
+dummy_file_write(char *buf, int32_t size, void *fileHandle)
+{
+ if (!fileHandle)
+ return -1;
+
+ nsIOutputStream *tStream = (nsIOutputStream *)fileHandle;
+ uint32_t bytesWritten;
+ tStream->Write(buf, size, &bytesWritten);
+ return (int)bytesWritten;
+}
+
+static int
+mime_parse_stream_write(nsMIMESession *stream, const char *buf, int32_t size)
+{
+ mime_draft_data *mdd = (mime_draft_data *)stream->data_object;
+ NS_ASSERTION(mdd, "null mime draft data!");
+
+ if (!mdd || !mdd->obj)
+ return -1;
+
+ return mdd->obj->clazz->parse_buffer((char *)buf, size, mdd->obj);
+}
+
+static void
+mime_free_attachments(nsTArray<nsMsgAttachedFile *> &attachments)
+{
+ if (attachments.Length() <= 0)
+ return;
+
+ for (uint32_t i = 0; i < attachments.Length(); i++)
+ {
+ if (attachments[i]->m_tmpFile)
+ {
+ attachments[i]->m_tmpFile->Remove(false);
+ attachments[i]->m_tmpFile = nullptr;
+ }
+ delete attachments[i];
+ }
+}
+
+static nsMsgAttachmentData *
+mime_draft_process_attachments(mime_draft_data *mdd)
+{
+ if (!mdd)
+ return nullptr;
+
+ nsMsgAttachmentData *attachData = NULL, *tmp = NULL;
+ nsMsgAttachedFile *tmpFile = NULL;
+
+ //It's possible we must treat the message body as attachment!
+ bool bodyAsAttachment = false;
+ if (mdd->messageBody &&
+ !mdd->messageBody->m_type.IsEmpty() &&
+ mdd->messageBody->m_type.Find("text/html", CaseInsensitiveCompare) == -1 &&
+ mdd->messageBody->m_type.Find("text/plain", CaseInsensitiveCompare) == -1 &&
+ !mdd->messageBody->m_type.LowerCaseEqualsLiteral("text"))
+ bodyAsAttachment = true;
+
+ if (!mdd->attachments.Length() && !bodyAsAttachment)
+ return nullptr;
+
+ int32_t totalCount = mdd->attachments.Length();
+ if (bodyAsAttachment)
+ totalCount++;
+ attachData = new nsMsgAttachmentData[totalCount + 1];
+ if (!attachData)
+ return nullptr;
+
+ tmp = attachData;
+
+ for (int i = 0, attachmentsIndex = 0; i < totalCount; i++, tmp++)
+ {
+ if (bodyAsAttachment && i == 0)
+ tmpFile = mdd->messageBody;
+ else
+ tmpFile = mdd->attachments[attachmentsIndex++];
+
+ if (tmpFile->m_type.LowerCaseEqualsLiteral("text/x-vcard"))
+ tmp->m_realName = tmpFile->m_description;
+
+ if (tmpFile->m_origUrl)
+ {
+ nsAutoCString tmpSpec;
+ if (NS_FAILED(tmpFile->m_origUrl->GetSpec(tmpSpec)))
+ goto FAIL;
+
+ if (NS_FAILED(nsMimeNewURI(getter_AddRefs(tmp->m_url), tmpSpec.get(), nullptr)))
+ goto FAIL;
+
+ if (tmp->m_realName.IsEmpty())
+ {
+ if (!tmpFile->m_realName.IsEmpty())
+ tmp->m_realName = tmpFile->m_realName;
+ else {
+ if (tmpFile->m_type.Find(MESSAGE_RFC822, CaseInsensitiveCompare) != -1)
+ // we have the odd case of processing an e-mail that had an unnamed
+ // eml message attached
+ tmp->m_realName = "ForwardedMessage.eml";
+
+ else
+ tmp->m_realName = tmpSpec.get();
+ }
+ }
+ }
+
+ tmp->m_desiredType = tmpFile->m_type;
+ tmp->m_realType = tmpFile->m_type;
+ tmp->m_realEncoding = tmpFile->m_encoding;
+ tmp->m_description = tmpFile->m_description;
+ tmp->m_cloudPartInfo = tmpFile->m_cloudPartInfo;
+ tmp->m_xMacType = tmpFile->m_xMacType;
+ tmp->m_xMacCreator = tmpFile->m_xMacCreator;
+ tmp->m_size = tmpFile->m_size;
+ }
+ return attachData;
+
+FAIL:
+ delete [] attachData;
+ return nullptr;
+}
+
+static void
+mime_intl_insert_message_header_1(char **body,
+ const char *hdr_value,
+ const char *hdr_str,
+ const char *html_hdr_str,
+ const char *mailcharset,
+ bool htmlEdit)
+{
+ if (!body || !hdr_value || !hdr_str)
+ return;
+
+ if (htmlEdit)
+ {
+ NS_MsgSACat(body, HEADER_START_JUNK);
+ }
+ else
+ {
+ NS_MsgSACat(body, MSG_LINEBREAK);
+ }
+ if (!html_hdr_str)
+ html_hdr_str = hdr_str;
+ NS_MsgSACat(body, html_hdr_str);
+ if (htmlEdit)
+ {
+ NS_MsgSACat(body, HEADER_MIDDLE_JUNK);
+ }
+ else
+ NS_MsgSACat(body, ": ");
+
+ // MIME decode header
+ nsAutoCString utf8Value;
+ MIME_DecodeMimeHeader(hdr_value, mailcharset, false, true, utf8Value);
+ if (!utf8Value.IsEmpty()) {
+ char *escaped = nullptr;
+ if (htmlEdit)
+ escaped = MsgEscapeHTML(utf8Value.get());
+ NS_MsgSACat(body, escaped ? escaped : utf8Value.get());
+ NS_Free(escaped);
+ } else {
+ NS_MsgSACat(body, hdr_value); // raw MIME encoded string
+ }
+
+ if (htmlEdit)
+ NS_MsgSACat(body, HEADER_END_JUNK);
+}
+
+char *
+MimeGetNamedString(int32_t id)
+{
+ static char retString[256];
+
+ retString[0] = '\0';
+ char *tString = MimeGetStringByID(id);
+ if (tString)
+ {
+ PL_strncpy(retString, tString, sizeof(retString));
+ PR_Free(tString);
+ }
+ return retString;
+}
+
+void
+MimeGetForwardHeaderDelimiter(nsACString &retString)
+{
+ nsCString defaultValue;
+ defaultValue.Adopt(MimeGetStringByID(MIME_FORWARDED_MESSAGE_HTML_USER_WROTE));
+
+ nsString tmpRetString;
+ NS_GetLocalizedUnicharPreferenceWithDefault(nullptr,
+ "mailnews.forward_header_originalmessage",
+ NS_ConvertUTF8toUTF16(defaultValue),
+ tmpRetString);
+
+ CopyUTF16toUTF8(tmpRetString, retString);
+}
+
+/* given an address string passed though parameter "address", this one will be converted
+ and returned through the same parameter. The original string will be destroyed
+*/
+static void UnquoteMimeAddress(nsACString &mimeHeader, const char *charset)
+{
+ if (!mimeHeader.IsEmpty())
+ {
+ nsTArray<nsCString> addresses;
+ ExtractDisplayAddresses(EncodedHeader(mimeHeader, charset),
+ UTF16ArrayAdapter<>(addresses));
+ mimeHeader.Truncate();
+
+ uint32_t count = addresses.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ if (i != 0)
+ mimeHeader.AppendASCII(", ");
+ mimeHeader += addresses[i];
+ }
+ }
+}
+
+static void
+mime_insert_all_headers(char **body,
+ MimeHeaders *headers,
+ MSG_ComposeFormat composeFormat,
+ char *mailcharset)
+{
+ bool htmlEdit = (composeFormat == nsIMsgCompFormat::HTML);
+ char *newBody = NULL;
+ char *html_tag = nullptr;
+ if (*body && PL_strncasecmp(*body, "<HTML", 5) == 0)
+ html_tag = PL_strchr(*body, '>') + 1;
+ int i;
+
+ if (!headers->done_p)
+ {
+ MimeHeaders_build_heads_list(headers);
+ headers->done_p = true;
+ }
+
+ nsCString replyHeader;
+ MimeGetForwardHeaderDelimiter(replyHeader);
+ if (htmlEdit)
+ {
+ NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX);
+ NS_MsgSACat(&newBody, replyHeader.get());
+ NS_MsgSACat(&newBody, MIME_HEADER_TABLE);
+ }
+ else
+ {
+ NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK);
+ NS_MsgSACat(&newBody, replyHeader.get());
+ }
+
+ for (i = 0; i < headers->heads_size; i++)
+ {
+ char *head = headers->heads[i];
+ char *end = (i == headers->heads_size-1
+ ? headers->all_headers + headers->all_headers_fp
+ : headers->heads[i+1]);
+ char *colon, *ocolon;
+ char *contents;
+ char *name = 0;
+
+ // Hack for BSD Mailbox delimiter.
+ if (i == 0 && head[0] == 'F' && !strncmp(head, "From ", 5))
+ {
+ colon = head + 4;
+ contents = colon + 1;
+ }
+ else
+ {
+ /* Find the colon. */
+ for (colon = head; colon < end; colon++)
+ if (*colon == ':') break;
+
+ if (colon >= end) continue; /* junk */
+
+ /* Back up over whitespace before the colon. */
+ ocolon = colon;
+ for (; colon > head && IS_SPACE(colon[-1]); colon--)
+ ;
+
+ contents = ocolon + 1;
+ }
+
+ /* Skip over whitespace after colon. */
+ while (contents <= end && IS_SPACE(*contents))
+ contents++;
+
+ /* Take off trailing whitespace... */
+ while (end > contents && IS_SPACE(end[-1]))
+ end--;
+
+ name = (char *)PR_MALLOC(colon - head + 1);
+ if (!name)
+ return /* MIME_OUT_OF_MEMORY */;
+ memcpy(name, head, colon - head);
+ name[colon - head] = 0;
+
+ nsAutoCString headerValue;
+ headerValue.Assign(contents, end - contents);
+
+ /* Do not reveal bcc recipients when forwarding a message!
+ See http://bugzilla.mozilla.org/show_bug.cgi?id=41150
+ */
+ if (PL_strcasecmp(name, "bcc") != 0)
+ {
+ if (!PL_strcasecmp(name, "resent-from") || !PL_strcasecmp(name, "from") ||
+ !PL_strcasecmp(name, "resent-to") || !PL_strcasecmp(name, "to") ||
+ !PL_strcasecmp(name, "resent-cc") || !PL_strcasecmp(name, "cc") ||
+ !PL_strcasecmp(name, "reply-to"))
+ UnquoteMimeAddress(headerValue, mailcharset);
+
+ mime_intl_insert_message_header_1(&newBody, headerValue.get(), name, name,
+ mailcharset, htmlEdit);
+ }
+ PR_Free(name);
+ }
+
+ if (htmlEdit)
+ {
+ NS_MsgSACat(&newBody, "</TABLE>");
+ NS_MsgSACat(&newBody, MSG_LINEBREAK "<BR><BR>");
+ if (html_tag)
+ NS_MsgSACat(&newBody, html_tag);
+ else if (*body)
+ NS_MsgSACat(&newBody, *body);
+ }
+ else
+ {
+ NS_MsgSACat(&newBody, MSG_LINEBREAK MSG_LINEBREAK);
+ if (*body)
+ NS_MsgSACat(&newBody, *body);
+ }
+
+ if (newBody)
+ {
+ PR_FREEIF(*body);
+ *body = newBody;
+ }
+}
+
+static void
+mime_insert_normal_headers(char **body,
+ MimeHeaders *headers,
+ MSG_ComposeFormat composeFormat,
+ char *mailcharset)
+{
+ char *newBody = nullptr;
+ char *subject = MimeHeaders_get(headers, HEADER_SUBJECT, false, false);
+ char *resent_comments = MimeHeaders_get(headers, HEADER_RESENT_COMMENTS, false, false);
+ char *resent_date = MimeHeaders_get(headers, HEADER_RESENT_DATE, false, true);
+ nsCString resent_from(MimeHeaders_get(headers, HEADER_RESENT_FROM, false, true));
+ nsCString resent_to(MimeHeaders_get(headers, HEADER_RESENT_TO, false, true));
+ nsCString resent_cc(MimeHeaders_get(headers, HEADER_RESENT_CC, false, true));
+ char *date = MimeHeaders_get(headers, HEADER_DATE, false, true);
+ nsCString from(MimeHeaders_get(headers, HEADER_FROM, false, true));
+ nsCString reply_to(MimeHeaders_get(headers, HEADER_REPLY_TO, false, true));
+ char *organization = MimeHeaders_get(headers, HEADER_ORGANIZATION, false, false);
+ nsCString to(MimeHeaders_get(headers, HEADER_TO, false, true));
+ nsCString cc(MimeHeaders_get(headers, HEADER_CC, false, true));
+ char *newsgroups = MimeHeaders_get(headers, HEADER_NEWSGROUPS, false, true);
+ char *followup_to = MimeHeaders_get(headers, HEADER_FOLLOWUP_TO, false, true);
+ char *references = MimeHeaders_get(headers, HEADER_REFERENCES, false, true);
+ const char *html_tag = nullptr;
+ if (*body && PL_strncasecmp(*body, "<HTML", 5) == 0)
+ html_tag = PL_strchr(*body, '>') + 1;
+ bool htmlEdit = composeFormat == nsIMsgCompFormat::HTML;
+
+ if (from.IsEmpty())
+ from.Adopt(MimeHeaders_get(headers, HEADER_SENDER, false, true));
+ if (resent_from.IsEmpty())
+ resent_from.Adopt(MimeHeaders_get(headers, HEADER_RESENT_SENDER, false,
+ true));
+
+ UnquoteMimeAddress(resent_from, mailcharset);
+ UnquoteMimeAddress(resent_to, mailcharset);
+ UnquoteMimeAddress(resent_cc, mailcharset);
+ UnquoteMimeAddress(reply_to, mailcharset);
+ UnquoteMimeAddress(from, mailcharset);
+ UnquoteMimeAddress(to, mailcharset);
+ UnquoteMimeAddress(cc, mailcharset);
+
+ nsCString replyHeader;
+ MimeGetForwardHeaderDelimiter(replyHeader);
+ if (htmlEdit)
+ {
+ NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX);
+ NS_MsgSACat(&newBody, replyHeader.get());
+ NS_MsgSACat(&newBody, MIME_HEADER_TABLE);
+ }
+ else
+ {
+ NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK);
+ NS_MsgSACat(&newBody, replyHeader.get());
+ }
+ if (subject)
+ mime_intl_insert_message_header_1(&newBody, subject, HEADER_SUBJECT,
+ MimeGetNamedString(MIME_MHTML_SUBJECT),
+ mailcharset, htmlEdit);
+ if (resent_comments)
+ mime_intl_insert_message_header_1(&newBody, resent_comments,
+ HEADER_RESENT_COMMENTS,
+ MimeGetNamedString(MIME_MHTML_RESENT_COMMENTS),
+ mailcharset, htmlEdit);
+ if (resent_date)
+ mime_intl_insert_message_header_1(&newBody, resent_date,
+ HEADER_RESENT_DATE,
+ MimeGetNamedString(MIME_MHTML_RESENT_DATE),
+ mailcharset, htmlEdit);
+ if (!resent_from.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, resent_from.get(),
+ HEADER_RESENT_FROM,
+ MimeGetNamedString(MIME_MHTML_RESENT_FROM),
+ mailcharset, htmlEdit);
+ }
+ if (!resent_to.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, resent_to.get(),
+ HEADER_RESENT_TO,
+ MimeGetNamedString(MIME_MHTML_RESENT_TO),
+ mailcharset, htmlEdit);
+ }
+ if (!resent_cc.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, resent_cc.get(),
+ HEADER_RESENT_CC,
+ MimeGetNamedString(MIME_MHTML_RESENT_CC),
+ mailcharset, htmlEdit);
+ }
+ if (date)
+ mime_intl_insert_message_header_1(&newBody, date, HEADER_DATE,
+ MimeGetNamedString(MIME_MHTML_DATE),
+ mailcharset, htmlEdit);
+ if (!from.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, from.get(), HEADER_FROM,
+ MimeGetNamedString(MIME_MHTML_FROM),
+ mailcharset, htmlEdit);
+ }
+ if (!reply_to.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, reply_to.get(), HEADER_REPLY_TO,
+ MimeGetNamedString(MIME_MHTML_REPLY_TO),
+ mailcharset, htmlEdit);
+ }
+ if (organization)
+ mime_intl_insert_message_header_1(&newBody, organization,
+ HEADER_ORGANIZATION,
+ MimeGetNamedString(MIME_MHTML_ORGANIZATION),
+ mailcharset, htmlEdit);
+ if (!to.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, to.get(), HEADER_TO,
+ MimeGetNamedString(MIME_MHTML_TO),
+ mailcharset, htmlEdit);
+ }
+ if (!cc.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, cc.get(), HEADER_CC,
+ MimeGetNamedString(MIME_MHTML_CC),
+ mailcharset, htmlEdit);
+ }
+ /*
+ Do not reveal bcc recipients when forwarding a message!
+ See http://bugzilla.mozilla.org/show_bug.cgi?id=41150
+ */
+ if (newsgroups)
+ mime_intl_insert_message_header_1(&newBody, newsgroups, HEADER_NEWSGROUPS,
+ MimeGetNamedString(MIME_MHTML_NEWSGROUPS),
+ mailcharset, htmlEdit);
+ if (followup_to)
+ {
+ mime_intl_insert_message_header_1(&newBody, followup_to,
+ HEADER_FOLLOWUP_TO,
+ MimeGetNamedString(MIME_MHTML_FOLLOWUP_TO),
+ mailcharset, htmlEdit);
+ }
+ // only show references for newsgroups
+ if (newsgroups && references)
+ {
+ mime_intl_insert_message_header_1(&newBody, references,
+ HEADER_REFERENCES,
+ MimeGetNamedString(MIME_MHTML_REFERENCES),
+ mailcharset, htmlEdit);
+ }
+ if (htmlEdit)
+ {
+ NS_MsgSACat(&newBody, "</TABLE>");
+ NS_MsgSACat(&newBody, MSG_LINEBREAK "<BR><BR>");
+ if (html_tag)
+ NS_MsgSACat(&newBody, html_tag);
+ else if (*body)
+ NS_MsgSACat(&newBody, *body);
+ }
+ else
+ {
+ NS_MsgSACat(&newBody, MSG_LINEBREAK MSG_LINEBREAK);
+ if (*body)
+ NS_MsgSACat(&newBody, *body);
+ }
+ if (newBody)
+ {
+ PR_FREEIF(*body);
+ *body = newBody;
+ }
+ PR_FREEIF(subject);
+ PR_FREEIF(resent_comments);
+ PR_FREEIF(resent_date);
+ PR_FREEIF(date);
+ PR_FREEIF(organization);
+ PR_FREEIF(newsgroups);
+ PR_FREEIF(followup_to);
+ PR_FREEIF(references);
+}
+
+static void
+mime_insert_micro_headers(char **body,
+ MimeHeaders *headers,
+ MSG_ComposeFormat composeFormat,
+ char *mailcharset)
+{
+ char *newBody = NULL;
+ char *subject = MimeHeaders_get(headers, HEADER_SUBJECT, false, false);
+ nsCString from(MimeHeaders_get(headers, HEADER_FROM, false, true));
+ nsCString resent_from(MimeHeaders_get(headers, HEADER_RESENT_FROM, false, true));
+ char *date = MimeHeaders_get(headers, HEADER_DATE, false, true);
+ nsCString to(MimeHeaders_get(headers, HEADER_TO, false, true));
+ nsCString cc(MimeHeaders_get(headers, HEADER_CC, false, true));
+ char *newsgroups = MimeHeaders_get(headers, HEADER_NEWSGROUPS, false,
+ true);
+ const char *html_tag = nullptr;
+ if (*body && PL_strncasecmp(*body, "<HTML", 5) == 0)
+ html_tag = PL_strchr(*body, '>') + 1;
+ bool htmlEdit = composeFormat == nsIMsgCompFormat::HTML;
+
+ if (from.IsEmpty())
+ from.Adopt(MimeHeaders_get(headers, HEADER_SENDER, false, true));
+ if (resent_from.IsEmpty())
+ resent_from.Adopt(MimeHeaders_get(headers, HEADER_RESENT_SENDER, false, true));
+ if (!date)
+ date = MimeHeaders_get(headers, HEADER_RESENT_DATE, false, true);
+
+ UnquoteMimeAddress(resent_from, mailcharset);
+ UnquoteMimeAddress(from, mailcharset);
+ UnquoteMimeAddress(to, mailcharset);
+ UnquoteMimeAddress(cc, mailcharset);
+
+ nsCString replyHeader;
+ MimeGetForwardHeaderDelimiter(replyHeader);
+ if (htmlEdit)
+ {
+ NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX);
+ NS_MsgSACat(&newBody, replyHeader.get());
+ NS_MsgSACat(&newBody, MIME_HEADER_TABLE);
+ }
+ else
+ {
+ NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK);
+ NS_MsgSACat(&newBody, replyHeader.get());
+ }
+
+ if (!from.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, from.get(), HEADER_FROM,
+ MimeGetNamedString(MIME_MHTML_FROM),
+ mailcharset, htmlEdit);
+ }
+ if (subject)
+ mime_intl_insert_message_header_1(&newBody, subject, HEADER_SUBJECT,
+ MimeGetNamedString(MIME_MHTML_SUBJECT),
+ mailcharset, htmlEdit);
+/*
+ if (date)
+ mime_intl_insert_message_header_1(&newBody, date, HEADER_DATE,
+ MimeGetNamedString(MIME_MHTML_DATE),
+ mailcharset, htmlEdit);
+*/
+ if (!resent_from.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, resent_from.get(),
+ HEADER_RESENT_FROM,
+ MimeGetNamedString(MIME_MHTML_RESENT_FROM),
+ mailcharset, htmlEdit);
+ }
+ if (!to.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, to.get(), HEADER_TO,
+ MimeGetNamedString(MIME_MHTML_TO),
+ mailcharset, htmlEdit);
+ }
+ if (!cc.IsEmpty())
+ {
+ mime_intl_insert_message_header_1(&newBody, cc.get(), HEADER_CC,
+ MimeGetNamedString(MIME_MHTML_CC),
+ mailcharset, htmlEdit);
+ }
+ /*
+ Do not reveal bcc recipients when forwarding a message!
+ See http://bugzilla.mozilla.org/show_bug.cgi?id=41150
+ */
+ if (newsgroups)
+ mime_intl_insert_message_header_1(&newBody, newsgroups, HEADER_NEWSGROUPS,
+ MimeGetNamedString(MIME_MHTML_NEWSGROUPS),
+ mailcharset, htmlEdit);
+ if (htmlEdit)
+ {
+ NS_MsgSACat(&newBody, "</TABLE>");
+ NS_MsgSACat(&newBody, MSG_LINEBREAK "<BR><BR>");
+ if (html_tag)
+ NS_MsgSACat(&newBody, html_tag);
+ else if (*body)
+ NS_MsgSACat(&newBody, *body);
+ }
+ else
+ {
+ NS_MsgSACat(&newBody, MSG_LINEBREAK MSG_LINEBREAK);
+ if (*body)
+ NS_MsgSACat(&newBody, *body);
+ }
+ if (newBody)
+ {
+ PR_FREEIF(*body);
+ *body = newBody;
+ }
+ PR_FREEIF(subject);
+ PR_FREEIF(date);
+ PR_FREEIF(newsgroups);
+
+}
+
+// body has to be encoded in UTF-8
+static void
+mime_insert_forwarded_message_headers(char **body,
+ MimeHeaders *headers,
+ MSG_ComposeFormat composeFormat,
+ char *mailcharset)
+{
+ if (!body || !headers)
+ return;
+
+ int32_t show_headers = 0;
+ nsresult res;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &res));
+ if (NS_SUCCEEDED(res))
+ prefBranch->GetIntPref("mail.show_headers", &show_headers);
+
+ switch (show_headers)
+ {
+ case 0:
+ mime_insert_micro_headers(body, headers, composeFormat, mailcharset);
+ break;
+ default:
+ case 1:
+ mime_insert_normal_headers(body, headers, composeFormat, mailcharset);
+ break;
+ case 2:
+ mime_insert_all_headers(body, headers, composeFormat, mailcharset);
+ break;
+ }
+}
+
+static void
+mime_parse_stream_complete(nsMIMESession *stream)
+{
+ mime_draft_data *mdd = (mime_draft_data *)stream->data_object;
+ nsCOMPtr<nsIMsgCompFields> fields;
+ int htmlAction = 0;
+ int lineWidth = 0;
+
+ char *host = 0;
+ char *news_host = 0;
+ char *to_and_cc = 0;
+ char *re_subject = 0;
+ char *new_refs = 0;
+ char *from = 0;
+ char *repl = 0;
+ char *subj = 0;
+ char *id = 0;
+ char *refs = 0;
+ char *to = 0;
+ char *cc = 0;
+ char *bcc = 0;
+ char *fcc = 0;
+ char *org = 0;
+ char *grps = 0;
+ char *foll = 0;
+ char *priority = 0;
+ char *draftInfo = 0;
+ char *contentLanguage = 0;
+ char *identityKey = 0;
+
+ bool forward_inline = false;
+ bool bodyAsAttachment = false;
+ bool charsetOverride = false;
+
+ NS_ASSERTION(mdd, "null mime draft data");
+
+ if (!mdd) return;
+
+ if (mdd->obj)
+ {
+ int status;
+
+ status = mdd->obj->clazz->parse_eof(mdd->obj, false);
+ mdd->obj->clazz->parse_end(mdd->obj, status < 0 ? true : false);
+
+ // RICHIE
+ // We need to figure out how to pass the forwarded flag along with this
+ // operation.
+
+ //forward_inline = (mdd->format_out != FO_CMDLINE_ATTACHMENTS);
+ forward_inline = mdd->forwardInline;
+
+ NS_ASSERTION(mdd->options == mdd->obj->options, "mime draft options not same as obj->options");
+ mime_free(mdd->obj);
+ mdd->obj = 0;
+ if (mdd->options)
+ {
+ // save the override flag before it's unavailable
+ charsetOverride = mdd->options->override_charset;
+ if ((!mdd->mailcharset || charsetOverride) && mdd->options->default_charset)
+ {
+ PR_Free(mdd->mailcharset);
+ mdd->mailcharset = strdup(mdd->options->default_charset);
+ }
+
+ // mscott: aren't we leaking a bunch of strings here like the charset strings and such?
+ delete mdd->options;
+ mdd->options = 0;
+ }
+ if (mdd->stream)
+ {
+ mdd->stream->complete((nsMIMESession *)mdd->stream->data_object);
+ PR_Free(mdd->stream);
+ mdd->stream = 0;
+ }
+ }
+
+ //
+ // Now, process the attachments that we have gathered from the message
+ // on disk
+ //
+ nsMsgAttachmentData *newAttachData = mime_draft_process_attachments(mdd);
+
+ //
+ // time to bring up the compose windows with all the info gathered
+ //
+ if (mdd->headers)
+ {
+ subj = MimeHeaders_get(mdd->headers, HEADER_SUBJECT, false, false);
+ if (forward_inline)
+ {
+ if (subj)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString fwdPrefix;
+ prefBranch->GetCharPref("mail.forward_subject_prefix",
+ getter_Copies(fwdPrefix));
+ char *newSubj = PR_smprintf("%s: %s", !fwdPrefix.IsEmpty() ?
+ fwdPrefix.get(): "Fwd", subj);
+ if (newSubj)
+ {
+ PR_Free(subj);
+ subj = newSubj;
+ }
+ }
+ }
+ }
+ else
+ {
+ from = MimeHeaders_get(mdd->headers, HEADER_FROM, false, false);
+ repl = MimeHeaders_get(mdd->headers, HEADER_REPLY_TO, false, false);
+ to = MimeHeaders_get(mdd->headers, HEADER_TO, false, true);
+ cc = MimeHeaders_get(mdd->headers, HEADER_CC, false, true);
+ bcc = MimeHeaders_get(mdd->headers, HEADER_BCC, false, true);
+
+ /* These headers should not be RFC-1522-decoded. */
+ grps = MimeHeaders_get(mdd->headers, HEADER_NEWSGROUPS, false, true);
+ foll = MimeHeaders_get(mdd->headers, HEADER_FOLLOWUP_TO, false, true);
+
+ host = MimeHeaders_get(mdd->headers, HEADER_X_MOZILLA_NEWSHOST, false, false);
+ if (!host)
+ host = MimeHeaders_get(mdd->headers, HEADER_NNTP_POSTING_HOST, false, false);
+
+ id = MimeHeaders_get(mdd->headers, HEADER_MESSAGE_ID, false, false);
+ refs = MimeHeaders_get(mdd->headers, HEADER_REFERENCES, false, true);
+ priority = MimeHeaders_get(mdd->headers, HEADER_X_PRIORITY, false, false);
+
+
+ if (host)
+ {
+ char *secure = NULL;
+
+ secure = PL_strcasestr(host, "secure");
+ if (secure)
+ {
+ *secure = 0;
+ news_host = PR_smprintf ("snews://%s", host);
+ }
+ else
+ {
+ news_host = PR_smprintf ("news://%s", host);
+ }
+ }
+ }
+
+
+ CreateCompositionFields(from, repl, to, cc, bcc, fcc, grps, foll,
+ org, subj, refs, priority, news_host,
+ mdd->mailcharset,
+ getter_AddRefs(fields));
+
+ contentLanguage = MimeHeaders_get(mdd->headers, HEADER_CONTENT_LANGUAGE, false, false);
+ if (contentLanguage) {
+ fields->SetContentLanguage(contentLanguage);
+ }
+
+ draftInfo = MimeHeaders_get(mdd->headers, HEADER_X_MOZILLA_DRAFT_INFO, false, false);
+
+ // Keep the same message id when editing a draft unless we're
+ // editing a message "as new message" (template) or forwarding inline.
+ if (mdd->format_out != nsMimeOutput::nsMimeMessageEditorTemplate &&
+ fields && !forward_inline) {
+ fields->SetMessageId(id);
+ }
+
+ if (draftInfo && fields && !forward_inline)
+ {
+ char *parm = 0;
+ parm = MimeHeaders_get_parameter(draftInfo, "vcard", NULL, NULL);
+ fields->SetAttachVCard(parm && !strcmp(parm, "1"));
+ PR_FREEIF(parm);
+
+ parm = MimeHeaders_get_parameter(draftInfo, "receipt", NULL, NULL);
+ if (!parm || !strcmp(parm, "0"))
+ fields->SetReturnReceipt(false);
+ else
+ {
+ int receiptType = 0;
+ fields->SetReturnReceipt(true);
+ sscanf(parm, "%d", &receiptType);
+ // slight change compared to 4.x; we used to use receipt= to tell
+ // whether the draft/template has request for either MDN or DNS or both
+ // return receipt; since the DNS is out of the picture we now use the
+ // header type - 1 to tell whether user has requested the return receipt
+ fields->SetReceiptHeaderType(((int32_t)receiptType) - 1);
+ }
+ PR_FREEIF(parm);
+ parm = MimeHeaders_get_parameter(draftInfo, "DSN", NULL, NULL);
+ fields->SetDSN(parm && !strcmp(parm, "1"));
+ PR_Free(parm);
+ parm = MimeHeaders_get_parameter(draftInfo, "html", NULL, NULL);
+ if (parm)
+ sscanf(parm, "%d", &htmlAction);
+ PR_FREEIF(parm);
+ parm = MimeHeaders_get_parameter(draftInfo, "linewidth", NULL, NULL);
+ if (parm)
+ sscanf(parm, "%d", &lineWidth);
+ PR_FREEIF(parm);
+ parm = MimeHeaders_get_parameter(draftInfo, "attachmentreminder", NULL, NULL);
+ if (parm && !strcmp(parm, "1"))
+ fields->SetAttachmentReminder(true);
+ else
+ fields->SetAttachmentReminder(false);
+ PR_FREEIF(parm);
+ parm = MimeHeaders_get_parameter(draftInfo, "deliveryformat", NULL, NULL);
+ if (parm) {
+ int32_t deliveryFormat = nsIMsgCompSendFormat::AskUser;
+ sscanf(parm, "%d", &deliveryFormat);
+ fields->SetDeliveryFormat(deliveryFormat);
+ }
+ PR_FREEIF(parm);
+ }
+
+ // identity to prefer when opening the message in the compose window?
+ identityKey = MimeHeaders_get(mdd->headers, HEADER_X_MOZILLA_IDENTITY_KEY, false, false);
+ if (identityKey && *identityKey)
+ {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && accountManager)
+ {
+ nsCOMPtr<nsIMsgIdentity> overrulingIdentity;
+ rv = accountManager->GetIdentity(nsDependentCString(identityKey), getter_AddRefs(overrulingIdentity));
+
+ if (NS_SUCCEEDED(rv) && overrulingIdentity) {
+ mdd->identity = overrulingIdentity;
+ fields->SetCreatorIdentityKey(identityKey);
+ }
+ }
+ }
+
+ if (mdd->messageBody)
+ {
+ MSG_ComposeFormat composeFormat = nsIMsgCompFormat::Default;
+ if (!mdd->messageBody->m_type.IsEmpty())
+ {
+ if(mdd->messageBody->m_type.Find("text/html", CaseInsensitiveCompare) != -1)
+ composeFormat = nsIMsgCompFormat::HTML;
+ else if (mdd->messageBody->m_type.Find("text/plain", CaseInsensitiveCompare) != -1 ||
+ mdd->messageBody->m_type.LowerCaseEqualsLiteral("text"))
+ composeFormat = nsIMsgCompFormat::PlainText;
+ else
+ //We cannot use this kind of data for the message body! Therefore, move it as attachment
+ bodyAsAttachment = true;
+ }
+ else
+ composeFormat = nsIMsgCompFormat::PlainText;
+
+ char *body = nullptr;
+ uint32_t bodyLen = 0;
+
+ if (!bodyAsAttachment && mdd->messageBody->m_tmpFile)
+ {
+ int64_t fileSize;
+ nsCOMPtr<nsIFile> tempFileCopy;
+ mdd->messageBody->m_tmpFile->Clone(getter_AddRefs(tempFileCopy));
+ mdd->messageBody->m_tmpFile = do_QueryInterface(tempFileCopy);
+ tempFileCopy = nullptr;
+ mdd->messageBody->m_tmpFile->GetFileSize(&fileSize);
+
+ // The stream interface can only read up to 4GB (32bit uint).
+ // It is highly unlikely to encounter a body lager than that limit,
+ // so we just skip it instead of reading it in chunks.
+ if (fileSize < UINT32_MAX)
+ {
+ bodyLen = fileSize;
+ body = (char *)PR_MALLOC(bodyLen + 1);
+ }
+ if (body)
+ {
+ memset(body, 0, bodyLen+1);
+
+ uint32_t bytesRead;
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mdd->messageBody->m_tmpFile);
+ if (NS_FAILED(rv))
+ return;
+
+ inputStream->Read(body, bodyLen, &bytesRead);
+
+ inputStream->Close();
+
+ // Convert the body to UTF-8
+ char *mimeCharset = nullptr;
+ // Get a charset from the header if no override is set.
+ if (!charsetOverride)
+ mimeCharset = MimeHeaders_get_parameter(mdd->messageBody->m_type.get(), "charset", nullptr, nullptr);
+ // If no charset is specified in the header then use the default.
+ char *bodyCharset = mimeCharset ? mimeCharset : mdd->mailcharset;
+ if (bodyCharset)
+ {
+ nsAutoString tmpUnicodeBody;
+ rv = ConvertToUnicode(bodyCharset, body, tmpUnicodeBody);
+ if (NS_FAILED(rv)) // Tough luck, ASCII/ISO-8859-1 then...
+ CopyASCIItoUTF16(nsDependentCString(body), tmpUnicodeBody);
+
+ char *newBody = ToNewUTF8String(tmpUnicodeBody);
+ if (newBody)
+ {
+ PR_Free(body);
+ body = newBody;
+ }
+ }
+ PR_FREEIF(mimeCharset);
+ }
+ }
+
+ bool convertToPlainText = false;
+ if (forward_inline)
+ {
+ if (mdd->identity)
+ {
+ bool identityComposeHTML;
+ mdd->identity->GetComposeHtml(&identityComposeHTML);
+ if ((identityComposeHTML && !mdd->overrideComposeFormat) ||
+ (!identityComposeHTML && mdd->overrideComposeFormat))
+ {
+ // In the end, we're going to compose in HTML mode...
+
+ if (body && composeFormat == nsIMsgCompFormat::PlainText)
+ {
+ // ... but the message body is currently plain text.
+
+ //We need to convert the plain/text to HTML in order to escape any HTML markup
+ char *escapedBody = MsgEscapeHTML(body);
+ if (escapedBody)
+ {
+ PR_Free(body);
+ body = escapedBody;
+ bodyLen = strlen(body);
+ }
+
+ //+13 chars for <pre> & </pre> tags and CRLF
+ uint32_t newbodylen = bodyLen + 14;
+ char* newbody = (char *)PR_MALLOC (newbodylen);
+ if (newbody)
+ {
+ *newbody = 0;
+ PL_strcatn(newbody, newbodylen, "<PRE>");
+ PL_strcatn(newbody, newbodylen, body);
+ PL_strcatn(newbody, newbodylen, "</PRE>" CRLF);
+ PR_Free(body);
+ body = newbody;
+ }
+ }
+ // Body is now HTML, set the format too (so headers are inserted in
+ // correct format).
+ composeFormat = nsIMsgCompFormat::HTML;
+ }
+ else if ((identityComposeHTML && mdd->overrideComposeFormat) || !identityComposeHTML)
+ {
+ // In the end, we're going to compose in plain text mode...
+
+ if (composeFormat == nsIMsgCompFormat::HTML)
+ {
+ // ... but the message body is currently HTML.
+ // We'll do the conversion later on when headers have been
+ // inserted, body has been set and converted to unicode.
+ convertToPlainText = true;
+ }
+ }
+ }
+
+ mime_insert_forwarded_message_headers(&body, mdd->headers, composeFormat,
+ mdd->mailcharset);
+
+ }
+
+ // convert from UTF-8 to UTF-16
+ if (body)
+ {
+ fields->SetBody(NS_ConvertUTF8toUTF16(body));
+ PR_Free(body);
+ }
+
+ //
+ // At this point, we need to create a message compose window or editor
+ // window via XP-COM with the information that we have retrieved from
+ // the message store.
+ //
+ if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate)
+ {
+ MSG_ComposeType msgComposeType = PL_strstr(mdd->url_name,
+ "&redirect=true") ?
+ nsIMsgCompType::Redirect :
+ nsIMsgCompType::Template;
+ CreateTheComposeWindow(fields, newAttachData, msgComposeType,
+ composeFormat, mdd->identity,
+ mdd->originalMsgURI, mdd->origMsgHdr);
+ }
+ else
+ {
+ if (mdd->forwardInline)
+ {
+ if (convertToPlainText)
+ fields->ConvertBodyToPlainText();
+ if (mdd->overrideComposeFormat)
+ composeFormat = nsIMsgCompFormat::OppositeOfDefault;
+ if (mdd->forwardInlineFilter)
+ {
+ fields->SetTo(mdd->forwardToAddress);
+ ForwardMsgInline(fields, newAttachData, composeFormat,
+ mdd->identity, mdd->originalMsgURI,
+ mdd->origMsgHdr);
+ }
+ else
+ CreateTheComposeWindow(fields, newAttachData,
+ nsIMsgCompType::ForwardInline, composeFormat,
+ mdd->identity, mdd->originalMsgURI,
+ mdd->origMsgHdr);
+ }
+ else
+ {
+ fields->SetDraftId(mdd->url_name);
+ CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Draft, composeFormat, mdd->identity, mdd->originalMsgURI, mdd->origMsgHdr);
+ }
+ }
+ }
+ else
+ {
+ //
+ // At this point, we need to create a message compose window via
+ // XP-COM with the information that we have retrieved from the message store.
+ //
+ if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate)
+ {
+#ifdef NS_DEBUG
+ printf("RICHIE: Time to create the EDITOR with this template - NO body!!!!\n");
+#endif
+ CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Template, nsIMsgCompFormat::Default, mdd->identity, nullptr, mdd->origMsgHdr);
+ }
+ else
+ {
+#ifdef NS_DEBUG
+ printf("Time to create the composition window WITHOUT a body!!!!\n");
+#endif
+ if (mdd->forwardInline)
+ {
+ MSG_ComposeFormat composeFormat = (mdd->overrideComposeFormat) ?
+ nsIMsgCompFormat::OppositeOfDefault : nsIMsgCompFormat::Default;
+ CreateTheComposeWindow(fields, newAttachData,
+ nsIMsgCompType::ForwardInline, composeFormat,
+ mdd->identity, mdd->originalMsgURI,
+ mdd->origMsgHdr);
+ }
+ else
+ {
+ fields->SetDraftId(mdd->url_name);
+ CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Draft, nsIMsgCompFormat::Default, mdd->identity, nullptr, mdd->origMsgHdr);
+ }
+ }
+ }
+ }
+ else
+ {
+ CreateCompositionFields(from, repl, to, cc, bcc, fcc, grps, foll,
+ org, subj, refs, priority, news_host,
+ mdd->mailcharset,
+ getter_AddRefs(fields));
+ if (fields)
+ CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::New, nsIMsgCompFormat::Default, mdd->identity, nullptr, mdd->origMsgHdr);
+ }
+
+ if (mdd->headers)
+ MimeHeaders_free(mdd->headers);
+
+ //
+ // Free the original attachment structure...
+ // Make sure we only cleanup the local copy of the memory and not kill
+ // files we need on disk
+ //
+ if (bodyAsAttachment)
+ mdd->messageBody->m_tmpFile = nullptr;
+ else if (mdd->messageBody && mdd->messageBody->m_tmpFile)
+ mdd->messageBody->m_tmpFile->Remove(false);
+
+ delete mdd->messageBody;
+
+ for (uint32_t i = 0; i < mdd->attachments.Length(); i++)
+ mdd->attachments[i]->m_tmpFile = nullptr;
+
+ PR_FREEIF(mdd->mailcharset);
+
+ mdd->identity = nullptr;
+ PR_Free(mdd->url_name);
+ PR_Free(mdd->originalMsgURI);
+ mdd->origMsgHdr = nullptr;
+ PR_Free(mdd);
+
+ PR_FREEIF(host);
+ PR_FREEIF(to_and_cc);
+ PR_FREEIF(re_subject);
+ PR_FREEIF(new_refs);
+ PR_FREEIF(from);
+ PR_FREEIF(repl);
+ PR_FREEIF(subj);
+ PR_FREEIF(id);
+ PR_FREEIF(refs);
+ PR_FREEIF(to);
+ PR_FREEIF(cc);
+ PR_FREEIF(grps);
+ PR_FREEIF(foll);
+ PR_FREEIF(priority);
+ PR_FREEIF(draftInfo);
+ PR_Free(identityKey);
+
+ delete [] newAttachData;
+}
+
+static void
+mime_parse_stream_abort(nsMIMESession *stream, int status)
+{
+ mime_draft_data *mdd = (mime_draft_data *)stream->data_object;
+ NS_ASSERTION(mdd, "null mime draft data");
+
+ if (!mdd)
+ return;
+
+ if (mdd->obj)
+ {
+ int status=0;
+
+ if (!mdd->obj->closed_p)
+ status = mdd->obj->clazz->parse_eof(mdd->obj, true);
+ if (!mdd->obj->parsed_p)
+ mdd->obj->clazz->parse_end(mdd->obj, true);
+
+ NS_ASSERTION(mdd->options == mdd->obj->options, "draft display options not same as mime obj");
+ mime_free (mdd->obj);
+ mdd->obj = 0;
+ if (mdd->options)
+ {
+ delete mdd->options;
+ mdd->options = 0;
+ }
+
+ if (mdd->stream)
+ {
+ mdd->stream->abort((nsMIMESession *)mdd->stream->data_object, status);
+ PR_Free(mdd->stream);
+ mdd->stream = 0;
+ }
+ }
+
+ if (mdd->headers)
+ MimeHeaders_free(mdd->headers);
+
+
+ mime_free_attachments(mdd->attachments);
+
+ PR_FREEIF(mdd->mailcharset);
+
+ PR_Free(mdd);
+}
+
+static int
+make_mime_headers_copy(void *closure, MimeHeaders *headers)
+{
+ mime_draft_data *mdd = (mime_draft_data *)closure;
+
+ NS_ASSERTION(mdd && headers, "null mime draft data and/or headers");
+
+ if (!mdd || ! headers)
+ return 0;
+
+ NS_ASSERTION(mdd->headers == NULL , "non null mime draft data headers");
+
+ mdd->headers = MimeHeaders_copy(headers);
+ mdd->options->done_parsing_outer_headers = true;
+
+ return 0;
+}
+
+int
+mime_decompose_file_init_fn(void *stream_closure, MimeHeaders *headers)
+{
+ mime_draft_data *mdd = (mime_draft_data *)stream_closure;
+ nsMsgAttachedFile *newAttachment = 0;
+ int nAttachments = 0;
+ //char *hdr_value = NULL;
+ char *parm_value = NULL;
+ bool creatingMsgBody = true;
+
+ NS_ASSERTION(mdd && headers, "null mime draft data and/or headers");
+ if (!mdd || !headers)
+ return -1;
+
+ if (mdd->options->decompose_init_count)
+ {
+ mdd->options->decompose_init_count++;
+ NS_ASSERTION(mdd->curAttachment, "missing attachment in mime_decompose_file_init_fn");
+ if (mdd->curAttachment)
+ mdd->curAttachment->m_type.Adopt(MimeHeaders_get(headers,
+ HEADER_CONTENT_TYPE,
+ false, true));
+ return 0;
+ }
+ else
+ mdd->options->decompose_init_count++;
+
+ nAttachments = mdd->attachments.Length();
+
+ if (!nAttachments && !mdd->messageBody)
+ {
+ // if we've been told to use an override charset then do so....otherwise use the charset
+ // inside the message header...
+ if (mdd->options && mdd->options->override_charset)
+ mdd->mailcharset = strdup(mdd->options->default_charset);
+ else
+ {
+ char *contentType;
+ contentType = MimeHeaders_get(headers, HEADER_CONTENT_TYPE, false, false);
+ if (contentType)
+ {
+ mdd->mailcharset = MimeHeaders_get_parameter(contentType, "charset", NULL, NULL);
+ PR_FREEIF(contentType);
+ }
+ }
+
+ mdd->messageBody = new nsMsgAttachedFile;
+ if (!mdd->messageBody)
+ return MIME_OUT_OF_MEMORY;
+ newAttachment = mdd->messageBody;
+ creatingMsgBody = true;
+ }
+ else
+ {
+ /* always allocate one more extra; don't ask me why */
+ newAttachment = new nsMsgAttachedFile;
+ if (!newAttachment)
+ return MIME_OUT_OF_MEMORY;
+ mdd->attachments.AppendElement(newAttachment);
+ }
+
+ char *workURLSpec = nullptr;
+ char *contLoc = nullptr;
+
+ newAttachment->m_realName.Adopt(MimeHeaders_get_name(headers, mdd->options));
+ contLoc = MimeHeaders_get(headers, HEADER_CONTENT_LOCATION, false, false);
+ if (!contLoc)
+ contLoc = MimeHeaders_get(headers, HEADER_CONTENT_BASE, false, false);
+
+ if (!contLoc && !newAttachment->m_realName.IsEmpty())
+ workURLSpec = ToNewCString(newAttachment->m_realName);
+ if (contLoc && !workURLSpec)
+ workURLSpec = strdup(contLoc);
+
+ PR_FREEIF(contLoc);
+
+ mdd->curAttachment = newAttachment;
+ newAttachment->m_type.Adopt(MimeHeaders_get(headers, HEADER_CONTENT_TYPE, false, false));
+
+ //
+ // This is to handle the degenerated Apple Double attachment.
+ //
+ parm_value = MimeHeaders_get(headers, HEADER_CONTENT_TYPE, false, false);
+ if (parm_value)
+ {
+ char *boundary = NULL;
+ char *tmp_value = NULL;
+ boundary = MimeHeaders_get_parameter(parm_value, "boundary", NULL, NULL);
+ if (boundary)
+ tmp_value = PR_smprintf("; boundary=\"%s\"", boundary);
+ if (tmp_value)
+ newAttachment->m_type = tmp_value;
+ newAttachment->m_xMacType.Adopt(
+ MimeHeaders_get_parameter(parm_value, "x-mac-type", NULL, NULL));
+ newAttachment->m_xMacCreator.Adopt(
+ MimeHeaders_get_parameter(parm_value, "x-mac-creator", NULL, NULL));
+ PR_FREEIF(parm_value);
+ PR_FREEIF(boundary);
+ PR_FREEIF(tmp_value);
+ }
+
+ newAttachment->m_size = 0;
+ newAttachment->m_encoding.Adopt(MimeHeaders_get(headers, HEADER_CONTENT_TRANSFER_ENCODING,
+ false, false));
+ newAttachment->m_description.Adopt(MimeHeaders_get(headers, HEADER_CONTENT_DESCRIPTION,
+ false, false));
+ //
+ // If we came up empty for description or the orig URL, we should do something about it.
+ //
+ if (newAttachment->m_description.IsEmpty() && workURLSpec)
+ newAttachment->m_description = workURLSpec;
+
+ PR_FREEIF(workURLSpec); // resource leak otherwise
+
+ newAttachment->m_cloudPartInfo.Adopt(MimeHeaders_get(headers,
+ HEADER_X_MOZILLA_CLOUD_PART,
+ false, false));
+
+ // There's no file in the message if it's a cloud part.
+ if (!newAttachment->m_cloudPartInfo.IsEmpty())
+ {
+ nsAutoCString fileURL;
+ fileURL.Adopt(
+ MimeHeaders_get_parameter(newAttachment->m_cloudPartInfo.get(), "file",
+ nullptr, nullptr));
+ if (!fileURL.IsEmpty())
+ nsMimeNewURI(getter_AddRefs(newAttachment->m_origUrl), fileURL.get(),
+ nullptr);
+ mdd->tmpFile = nullptr;
+ return 0;
+ }
+
+ nsCOMPtr<nsIFile> tmpFile = nullptr;
+ {
+ // Let's build a temp file with an extension based on the content-type: nsmail.<extension>
+
+ nsAutoCString newAttachName("nsmail");
+ bool extensionAdded = false;
+ // the content type may contain a charset. i.e. text/html; ISO-2022-JP...we want to strip off the charset
+ // before we ask the mime service for a mime info for this content type.
+ nsAutoCString contentType(newAttachment->m_type);
+ int32_t pos = contentType.FindChar(';');
+ if (pos > 0)
+ contentType.SetLength(pos);
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMIMEService> mimeFinder(do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && mimeFinder)
+ {
+ nsAutoCString fileExtension;
+ rv = mimeFinder->GetPrimaryExtension(contentType, EmptyCString(), fileExtension);
+
+ if (NS_SUCCEEDED(rv) && !fileExtension.IsEmpty())
+ {
+ newAttachName.Append(".");
+ newAttachName.Append(fileExtension);
+ extensionAdded = true;
+ }
+ }
+
+ if (!extensionAdded)
+ {
+ newAttachName.Append(".tmp");
+ }
+
+ nsMsgCreateTempFile(newAttachName.get(), getter_AddRefs(tmpFile));
+ }
+ nsresult rv;
+
+ // This needs to be done so the attachment structure has a handle
+ // on the temp file for this attachment...
+ if (tmpFile)
+ {
+ nsAutoCString fileURL;
+ rv = NS_GetURLSpecFromFile(tmpFile, fileURL);
+ if (NS_SUCCEEDED(rv))
+ nsMimeNewURI(getter_AddRefs(newAttachment->m_origUrl),
+ fileURL.get(), nullptr);
+ }
+
+ if (!tmpFile)
+ return MIME_OUT_OF_MEMORY;
+
+ mdd->tmpFile = do_QueryInterface(tmpFile);
+
+ newAttachment->m_tmpFile = mdd->tmpFile;
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mdd->tmpFileStream), tmpFile,PR_WRONLY | PR_CREATE_FILE, 00600);
+ if (NS_FAILED(rv))
+ return MIME_UNABLE_TO_OPEN_TMP_FILE;
+
+ // For now, we are always going to decode all of the attachments
+ // for the message. This way, we have native data
+ if (creatingMsgBody)
+ {
+ MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0;
+
+ //
+ // Initialize a decoder if necessary.
+ //
+ if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_BASE64))
+ fn = &MimeB64DecoderInit;
+ else if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_QUOTED_PRINTABLE))
+ {
+ mdd->decoder_data = MimeQPDecoderInit (/* The (MimeConverterOutputCallback) cast is to turn the `void' argument into `MimeObject'. */
+ ((MimeConverterOutputCallback)dummy_file_write),
+ mdd->tmpFileStream);
+ if (!mdd->decoder_data)
+ return MIME_OUT_OF_MEMORY;
+ }
+ else if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE) ||
+ newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE2) ||
+ newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE3) ||
+ newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE4))
+ fn = &MimeUUDecoderInit;
+ else if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_YENCODE))
+ fn = &MimeYDecoderInit;
+
+ if (fn)
+ {
+ mdd->decoder_data = fn (/* The (MimeConverterOutputCallback) cast is to
+ turn the `void' argument into `MimeObject'. */
+ ((MimeConverterOutputCallback) dummy_file_write),
+ mdd->tmpFileStream);
+ if (!mdd->decoder_data)
+ return MIME_OUT_OF_MEMORY;
+ }
+ }
+
+ return 0;
+}
+
+int
+mime_decompose_file_output_fn(const char *buf,
+ int32_t size,
+ void *stream_closure)
+{
+ mime_draft_data *mdd = (mime_draft_data *)stream_closure;
+ int ret = 0;
+
+ NS_ASSERTION(mdd && buf, "missing mime draft data and/or buf");
+ if (!mdd || !buf) return -1;
+ if (!size) return 0;
+
+ if (!mdd->tmpFileStream)
+ return 0;
+
+ if (mdd->decoder_data) {
+ int32_t outsize;
+ ret = MimeDecoderWrite(mdd->decoder_data, buf, size, &outsize);
+ if (ret == -1) return -1;
+ mdd->curAttachment->m_size += outsize;
+ }
+ else
+ {
+ uint32_t bytesWritten;
+ mdd->tmpFileStream->Write(buf, size, &bytesWritten);
+ if ((int32_t)bytesWritten < size)
+ return MIME_ERROR_WRITING_FILE;
+ mdd->curAttachment->m_size += size;
+ }
+
+ return 0;
+}
+
+int
+mime_decompose_file_close_fn(void *stream_closure)
+{
+ mime_draft_data *mdd = (mime_draft_data *)stream_closure;
+
+ if (!mdd)
+ return -1;
+
+ if (--mdd->options->decompose_init_count > 0)
+ return 0;
+
+ if (mdd->decoder_data) {
+ MimeDecoderDestroy(mdd->decoder_data, false);
+ mdd->decoder_data = 0;
+ }
+
+ if (!mdd->tmpFileStream) {
+ // it's ok to have a null tmpFileStream if there's no tmpFile.
+ // This happens for cloud file attachments.
+ NS_ASSERTION(!mdd->tmpFile, "shouldn't have a tmp file bu no stream");
+ return 0;
+ }
+ mdd->tmpFileStream->Close();
+
+ mdd->tmpFileStream = nullptr;
+
+ mdd->tmpFile = nullptr;
+
+ return 0;
+}
+
+extern "C" void *
+mime_bridge_create_draft_stream(
+ nsIMimeEmitter *newEmitter,
+ nsStreamConverter *newPluginObj2,
+ nsIURI *uri,
+ nsMimeOutputType format_out)
+{
+ int status = 0;
+ nsMIMESession *stream = nullptr;
+ mime_draft_data *mdd = nullptr;
+ MimeObject *obj = nullptr;
+
+ if (!uri)
+ return nullptr;
+
+ mdd = new mime_draft_data;
+ if (!mdd)
+ return nullptr;
+
+ nsAutoCString turl;
+ nsCOMPtr<nsIMsgMessageService> msgService;
+ nsCOMPtr<nsIURI> aURL;
+ nsAutoCString urlString;
+ nsresult rv;
+
+ // first, convert the rdf msg uri into a url that represents the message...
+ if (NS_FAILED(uri->GetSpec(turl)))
+ goto FAIL;
+
+ rv = GetMessageServiceFromURI(turl, getter_AddRefs(msgService));
+ if (NS_FAILED(rv))
+ goto FAIL;
+
+ rv = msgService->GetUrlForUri(turl.get(), getter_AddRefs(aURL), nullptr);
+ if (NS_FAILED(rv))
+ goto FAIL;
+
+ if (NS_SUCCEEDED(aURL->GetSpec(urlString)))
+ {
+ int32_t typeIndex = urlString.Find("&type=application/x-message-display");
+ if (typeIndex != -1)
+ urlString.Cut(typeIndex, sizeof("&type=application/x-message-display") - 1);
+
+ mdd->url_name = ToNewCString(urlString);
+ if (!(mdd->url_name))
+ goto FAIL;
+ }
+
+ newPluginObj2->GetForwardInline(&mdd->forwardInline);
+ newPluginObj2->GetForwardInlineFilter(&mdd->forwardInlineFilter);
+ newPluginObj2->GetForwardToAddress(mdd->forwardToAddress);
+ newPluginObj2->GetOverrideComposeFormat(&mdd->overrideComposeFormat);
+ newPluginObj2->GetIdentity(getter_AddRefs(mdd->identity));
+ newPluginObj2->GetOriginalMsgURI(&mdd->originalMsgURI);
+ newPluginObj2->GetOrigMsgHdr(getter_AddRefs(mdd->origMsgHdr));
+ mdd->format_out = format_out;
+ mdd->options = new MimeDisplayOptions ;
+ if (!mdd->options)
+ goto FAIL;
+
+ mdd->options->url = strdup(mdd->url_name);
+ mdd->options->format_out = format_out; // output format
+ mdd->options->decompose_file_p = true; /* new field in MimeDisplayOptions */
+ mdd->options->stream_closure = mdd;
+ mdd->options->html_closure = mdd;
+ mdd->options->decompose_headers_info_fn = make_mime_headers_copy;
+ mdd->options->decompose_file_init_fn = mime_decompose_file_init_fn;
+ mdd->options->decompose_file_output_fn = mime_decompose_file_output_fn;
+ mdd->options->decompose_file_close_fn = mime_decompose_file_close_fn;
+
+ mdd->options->m_prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ goto FAIL;
+
+#ifdef ENABLE_SMIME
+ /* If we're attaching a message (for forwarding) then we must eradicate all
+ traces of xlateion from it, since forwarding someone else a message
+ that wasn't xlated for them doesn't work. We have to dexlate it
+ before sending it.
+ */
+ mdd->options->decrypt_p = true;
+#endif /* ENABLE_SMIME */
+
+ obj = mime_new((MimeObjectClass *)&mimeMessageClass, (MimeHeaders *)NULL, MESSAGE_RFC822);
+ if (!obj)
+ goto FAIL;
+
+ obj->options = mdd->options;
+ mdd->obj = obj;
+
+ stream = PR_NEWZAP(nsMIMESession);
+ if (!stream)
+ goto FAIL;
+
+ stream->name = "MIME To Draft Converter Stream";
+ stream->complete = mime_parse_stream_complete;
+ stream->abort = mime_parse_stream_abort;
+ stream->put_block = mime_parse_stream_write;
+ stream->data_object = mdd;
+
+ status = obj->clazz->initialize(obj);
+ if (status >= 0)
+ status = obj->clazz->parse_begin(obj);
+ if (status < 0)
+ goto FAIL;
+
+ return stream;
+
+FAIL:
+ if (mdd)
+ {
+ PR_Free(mdd->url_name);
+ PR_Free(mdd->originalMsgURI);
+ if (mdd->options)
+ delete mdd->options;
+ PR_Free(mdd);
+ }
+ PR_Free(stream);
+ PR_Free(obj);
+
+ return nullptr;
+}
diff --git a/mailnews/mime/src/mimeebod.cpp b/mailnews/mime/src/mimeebod.cpp
new file mode 100644
index 000000000..2ca056feb
--- /dev/null
+++ b/mailnews/mime/src/mimeebod.cpp
@@ -0,0 +1,509 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "nsIURL.h"
+#include "mimeebod.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "prio.h"
+#include "msgCore.h"
+#include "nsMimeStringResources.h"
+#include "mimemoz2.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "nsINetUtil.h"
+#include <ctype.h>
+
+#define MIME_SUPERCLASS mimeObjectClass
+MimeDefClass(MimeExternalBody, MimeExternalBodyClass,
+ mimeExternalBodyClass, &MIME_SUPERCLASS);
+
+#ifdef XP_MACOSX
+extern MimeObjectClass mimeMultipartAppleDoubleClass;
+#endif
+
+static int MimeExternalBody_initialize (MimeObject *);
+static void MimeExternalBody_finalize (MimeObject *);
+static int MimeExternalBody_parse_line (const char *, int32_t, MimeObject *);
+static int MimeExternalBody_parse_eof (MimeObject *, bool);
+static bool MimeExternalBody_displayable_inline_p (MimeObjectClass *clazz,
+ MimeHeaders *hdrs);
+
+#if 0
+#if defined(DEBUG) && defined(XP_UNIX)
+static int MimeExternalBody_debug_print (MimeObject *, PRFileDesc *, int32_t);
+#endif
+#endif /* 0 */
+
+static int
+MimeExternalBodyClassInitialize(MimeExternalBodyClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+
+ NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ oclass->initialize = MimeExternalBody_initialize;
+ oclass->finalize = MimeExternalBody_finalize;
+ oclass->parse_line = MimeExternalBody_parse_line;
+ oclass->parse_eof = MimeExternalBody_parse_eof;
+ oclass->displayable_inline_p = MimeExternalBody_displayable_inline_p;
+
+#if 0
+#if defined(DEBUG) && defined(XP_UNIX)
+ oclass->debug_print = MimeExternalBody_debug_print;
+#endif
+#endif /* 0 */
+
+ return 0;
+}
+
+
+static int
+MimeExternalBody_initialize (MimeObject *object)
+{
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object);
+}
+
+static void
+MimeExternalBody_finalize (MimeObject *object)
+{
+ MimeExternalBody *bod = (MimeExternalBody *) object;
+ if (bod->hdrs)
+ {
+ MimeHeaders_free(bod->hdrs);
+ bod->hdrs = 0;
+ }
+ PR_FREEIF(bod->body);
+
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object);
+}
+
+static int
+MimeExternalBody_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ MimeExternalBody *bod = (MimeExternalBody *) obj;
+ int status = 0;
+
+ NS_ASSERTION(line && *line, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!line || !*line) return -1;
+
+ if (!obj->output_p) return 0;
+
+ /* If we're supposed to write this object, but aren't supposed to convert
+ it to HTML, simply pass it through unaltered. */
+ if (obj->options &&
+ !obj->options->write_html_p &&
+ obj->options->output_fn)
+ return MimeObject_write(obj, line, length, true);
+
+
+ /* If we already have a `body' then we're done parsing headers, and all
+ subsequent lines get tacked onto the body. */
+ if (bod->body)
+ {
+ int L = strlen(bod->body);
+ char *new_str = (char *)PR_Realloc(bod->body, L + length + 1);
+ if (!new_str) return MIME_OUT_OF_MEMORY;
+ bod->body = new_str;
+ memcpy(bod->body + L, line, length);
+ bod->body[L + length] = 0;
+ return 0;
+ }
+
+ /* Otherwise we don't yet have a body, which means we're not done parsing
+ our headers.
+ */
+ if (!bod->hdrs)
+ {
+ bod->hdrs = MimeHeaders_new();
+ if (!bod->hdrs) return MIME_OUT_OF_MEMORY;
+ }
+
+ status = MimeHeaders_parse_line(line, length, bod->hdrs);
+ if (status < 0) return status;
+
+ /* If this line is blank, we're now done parsing headers, and should
+ create a dummy body to show that. Gag.
+ */
+ if (*line == '\r' || *line == '\n')
+ {
+ bod->body = strdup("");
+ if (!bod->body) return MIME_OUT_OF_MEMORY;
+ }
+
+ return 0;
+}
+
+
+char *
+MimeExternalBody_make_url(const char *ct,
+ const char *at, const char *lexp, const char *size,
+ const char *perm, const char *dir, const char *mode,
+ const char *name, const char *url, const char *site,
+ const char *svr, const char *subj, const char *body)
+{
+ char *s;
+ uint32_t slen;
+ if (!at)
+ {
+ return 0;
+ }
+ else if (!PL_strcasecmp(at, "ftp") || !PL_strcasecmp(at, "anon-ftp"))
+ {
+ if (!site || !name)
+ return 0;
+
+ slen = strlen(name) + strlen(site) + (dir ? strlen(dir) : 0) + 20;
+ s = (char *) PR_MALLOC(slen);
+
+ if (!s) return 0;
+ PL_strncpyz(s, "ftp://", slen);
+ PL_strcatn(s, slen, site);
+ PL_strcatn(s, slen, "/");
+ if (dir) PL_strcatn(s, slen, (dir[0] == '/' ? dir+1 : dir));
+ if (s[strlen(s)-1] != '/')
+ PL_strcatn(s, slen, "/");
+ PL_strcatn(s, slen, name);
+ return s;
+ }
+ else if (!PL_strcasecmp(at, "local-file") || !PL_strcasecmp(at, "afs"))
+ {
+ if (!name)
+ return 0;
+
+#ifdef XP_UNIX
+ if (!PL_strcasecmp(at, "afs")) /* only if there is a /afs/ directory */
+ {
+ nsCOMPtr <nsIFile> fs = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ bool exists = false;
+ if (fs)
+ {
+ fs->InitWithNativePath(NS_LITERAL_CSTRING("/afs/."));
+ fs->Exists(&exists);
+ }
+ if (!exists)
+ return 0;
+ }
+#else /* !XP_UNIX */
+ return 0; /* never, if not Unix. */
+#endif /* !XP_UNIX */
+
+ slen = (strlen(name) * 3 + 20);
+ s = (char *) PR_MALLOC(slen);
+ if (!s) return 0;
+ PL_strncpyz(s, "file:", slen);
+
+ nsCString s2;
+ MsgEscapeString(nsDependentCString(name), nsINetUtil::ESCAPE_URL_PATH, s2);
+ PL_strcatn(s, slen, s2.get());
+ return s;
+ }
+else if (!PL_strcasecmp(at, "mail-server"))
+{
+ if (!svr)
+ return 0;
+
+ slen = (strlen(svr)*4 + (subj ? strlen(subj)*4 : 0) +
+ (body ? strlen(body)*4 : 0) + 25); // dpv xxx: why 4x? %xx escaping should be 3x
+ s = (char *) PR_MALLOC(slen);
+ if (!s) return 0;
+ PL_strncpyz(s, "mailto:", slen);
+
+ nsCString s2;
+ MsgEscapeString(nsDependentCString(svr), nsINetUtil::ESCAPE_XALPHAS, s2);
+ PL_strcatn(s, slen, s2.get());
+
+ if (subj)
+ {
+ MsgEscapeString(nsDependentCString(subj), nsINetUtil::ESCAPE_XALPHAS, s2);
+ PL_strcatn(s, slen, "?subject=");
+ PL_strcatn(s, slen, s2.get());
+ }
+ if (body)
+ {
+ MsgEscapeString(nsDependentCString(body), nsINetUtil::ESCAPE_XALPHAS, s2);
+ PL_strcatn(s, slen, (subj ? "&body=" : "?body="));
+ PL_strcatn(s, slen, s2.get());
+ }
+ return s;
+}
+else if (!PL_strcasecmp(at, "url")) /* RFC 2017 */
+ {
+ if (url)
+ return strdup(url); /* it's already quoted and everything */
+ else
+ return 0;
+ }
+ else
+ return 0;
+}
+
+static int
+MimeExternalBody_parse_eof (MimeObject *obj, bool abort_p)
+{
+ int status = 0;
+ MimeExternalBody *bod = (MimeExternalBody *) obj;
+
+ if (obj->closed_p) return 0;
+
+ /* Run parent method first, to flush out any buffered data. */
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0) return status;
+
+#ifdef XP_MACOSX
+ if (obj->parent && mime_typep(obj->parent,
+ (MimeObjectClass*) &mimeMultipartAppleDoubleClass))
+ goto done;
+#endif /* XP_MACOSX */
+
+ if (!abort_p &&
+ obj->output_p &&
+ obj->options &&
+ obj->options->write_html_p)
+ {
+ bool all_headers_p = obj->options->headers == MimeHeadersAll;
+ MimeDisplayOptions *newopt = obj->options; /* copy it */
+
+ char *ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE,
+ false, false);
+ char *at, *lexp, *size, *perm;
+ char *url, *dir, *mode, *name, *site, *svr, *subj;
+ char *h = 0, *lname = 0, *lurl = 0, *body = 0;
+ MimeHeaders *hdrs = 0;
+
+ if (!ct) return MIME_OUT_OF_MEMORY;
+
+ at = MimeHeaders_get_parameter(ct, "access-type", NULL, NULL);
+ lexp = MimeHeaders_get_parameter(ct, "expiration", NULL, NULL);
+ size = MimeHeaders_get_parameter(ct, "size", NULL, NULL);
+ perm = MimeHeaders_get_parameter(ct, "permission", NULL, NULL);
+ dir = MimeHeaders_get_parameter(ct, "directory", NULL, NULL);
+ mode = MimeHeaders_get_parameter(ct, "mode", NULL, NULL);
+ name = MimeHeaders_get_parameter(ct, "name", NULL, NULL);
+ site = MimeHeaders_get_parameter(ct, "site", NULL, NULL);
+ svr = MimeHeaders_get_parameter(ct, "server", NULL, NULL);
+ subj = MimeHeaders_get_parameter(ct, "subject", NULL, NULL);
+ url = MimeHeaders_get_parameter(ct, "url", NULL, NULL);
+ PR_FREEIF(ct);
+
+ /* the *internal* content-type */
+ ct = MimeHeaders_get(bod->hdrs, HEADER_CONTENT_TYPE,
+ true, false);
+
+ uint32_t hlen = ((at ? strlen(at) : 0) +
+ (lexp ? strlen(lexp) : 0) +
+ (size ? strlen(size) : 0) +
+ (perm ? strlen(perm) : 0) +
+ (dir ? strlen(dir) : 0) +
+ (mode ? strlen(mode) : 0) +
+ (name ? strlen(name) : 0) +
+ (site ? strlen(site) : 0) +
+ (svr ? strlen(svr) : 0) +
+ (subj ? strlen(subj) : 0) +
+ (ct ? strlen(ct) : 0) +
+ (url ? strlen(url) : 0) + 100);
+
+ h = (char *) PR_MALLOC(hlen);
+ if (!h)
+ {
+ status = MIME_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+
+ /* If there's a URL parameter, remove all whitespace from it.
+ (The URL parameter to one of these headers is stored with
+ lines broken every 40 characters or less; it's assumed that
+ all significant whitespace was URL-hex-encoded, and all the
+ rest of it was inserted just to keep the lines short.)
+ */
+ if (url)
+ {
+ char *in, *out;
+ for (in = url, out = url; *in; in++)
+ if (!IS_SPACE(*in))
+ *out++ = *in;
+ *out = 0;
+ }
+
+ hdrs = MimeHeaders_new();
+ if (!hdrs)
+ {
+ status = MIME_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+
+# define FROB(STR,VAR) \
+ if (VAR) \
+ { \
+ PL_strncpyz(h, STR ": ", hlen); \
+ PL_strcatn(h, hlen, VAR); \
+ PL_strcatn(h, hlen, MSG_LINEBREAK); \
+ status = MimeHeaders_parse_line(h, strlen(h), hdrs); \
+ if (status < 0) goto FAIL; \
+ }
+ FROB("Access-Type", at);
+ FROB("URL", url);
+ FROB("Site", site);
+ FROB("Server", svr);
+ FROB("Directory", dir);
+ FROB("Name", name);
+ FROB("Type", ct);
+ FROB("Size", size);
+ FROB("Mode", mode);
+ FROB("Permission", perm);
+ FROB("Expiration", lexp);
+ FROB("Subject", subj);
+# undef FROB
+ PL_strncpyz(h, MSG_LINEBREAK, hlen);
+ status = MimeHeaders_parse_line(h, strlen(h), hdrs);
+ if (status < 0) goto FAIL;
+
+ lurl = MimeExternalBody_make_url(ct, at, lexp, size, perm, dir, mode,
+ name, url, site, svr, subj, bod->body);
+ if (lurl)
+ {
+ lname = MimeGetStringByID(MIME_MSG_LINK_TO_DOCUMENT);
+ }
+ else
+ {
+ lname = MimeGetStringByID(MIME_MSG_DOCUMENT_INFO);
+ all_headers_p = true;
+ }
+
+ all_headers_p = true; /* #### just do this all the time? */
+
+ if (bod->body && all_headers_p)
+ {
+ char *s = bod->body;
+ while (IS_SPACE(*s)) s++;
+ if (*s)
+ {
+ char *s2;
+ const char *pre = "<P><PRE>";
+ const char *suf = "</PRE>";
+ int32_t i;
+ for(i = strlen(s)-1; i >= 0 && IS_SPACE(s[i]); i--)
+ s[i] = 0;
+ s2 = MsgEscapeHTML(s);
+ if (!s2) goto FAIL;
+ body = (char *) PR_MALLOC(strlen(pre) + strlen(s2) +
+ strlen(suf) + 1);
+ if (!body)
+ {
+ NS_Free(s2);
+ goto FAIL;
+ }
+ PL_strcpy(body, pre);
+ PL_strcat(body, s2);
+ PL_strcat(body, suf);
+ }
+ }
+
+ newopt->fancy_headers_p = true;
+ newopt->headers = (all_headers_p ? MimeHeadersAll : MimeHeadersSome);
+
+FAIL:
+ if (hdrs)
+ MimeHeaders_free(hdrs);
+ PR_FREEIF(h);
+ PR_FREEIF(lname);
+ PR_FREEIF(lurl);
+ PR_FREEIF(body);
+ PR_FREEIF(ct);
+ PR_FREEIF(at);
+ PR_FREEIF(lexp);
+ PR_FREEIF(size);
+ PR_FREEIF(perm);
+ PR_FREEIF(dir);
+ PR_FREEIF(mode);
+ PR_FREEIF(name);
+ PR_FREEIF(url);
+ PR_FREEIF(site);
+ PR_FREEIF(svr);
+ PR_FREEIF(subj);
+ }
+
+#ifdef XP_MACOSX
+done:
+#endif
+
+ return status;
+}
+
+#if 0
+#if defined(DEBUG) && defined(XP_UNIX)
+static int
+MimeExternalBody_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth)
+{
+ MimeExternalBody *bod = (MimeExternalBody *) obj;
+ int i;
+ char *ct, *ct2;
+ char *addr = mime_part_address(obj);
+
+ if (obj->headers)
+ ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false);
+ if (bod->hdrs)
+ ct2 = MimeHeaders_get (bod->hdrs, HEADER_CONTENT_TYPE, false, false);
+
+ for (i=0; i < depth; i++)
+ PR_Write(stream, " ", 2);
+/***
+ fprintf(stream,
+ "<%s %s\n"
+ "\tcontent-type: %s\n"
+ "\tcontent-type: %s\n"
+ "\tBody:%s\n\t0x%08X>\n\n",
+ obj->clazz->class_name,
+ addr ? addr : "???",
+ ct ? ct : "<none>",
+ ct2 ? ct2 : "<none>",
+ bod->body ? bod->body : "<none>",
+ (uint32_t) obj);
+***/
+ PR_FREEIF(addr);
+ PR_FREEIF(ct);
+ PR_FREEIF(ct2);
+ return 0;
+}
+#endif
+#endif /* 0 */
+
+static bool
+MimeExternalBody_displayable_inline_p (MimeObjectClass *clazz,
+ MimeHeaders *hdrs)
+{
+ char *ct = MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, false, false);
+ char *at = MimeHeaders_get_parameter(ct, "access-type", NULL, NULL);
+ bool inline_p = false;
+
+ if (!at)
+ ;
+ else if (!PL_strcasecmp(at, "ftp") ||
+ !PL_strcasecmp(at, "anon-ftp") ||
+ !PL_strcasecmp(at, "local-file") ||
+ !PL_strcasecmp(at, "mail-server") ||
+ !PL_strcasecmp(at, "url"))
+ inline_p = true;
+#ifdef XP_UNIX
+ else if (!PL_strcasecmp(at, "afs")) /* only if there is a /afs/ directory */
+ {
+ nsCOMPtr <nsIFile> fs = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ bool exists = false;
+ if (fs)
+ {
+ fs->InitWithNativePath(NS_LITERAL_CSTRING("/afs/."));
+ fs->Exists(&exists);
+ }
+ if (!exists)
+ return 0;
+
+ inline_p = true;
+ }
+#endif /* XP_UNIX */
+
+ PR_FREEIF(ct);
+ PR_FREEIF(at);
+ return inline_p;
+}
diff --git a/mailnews/mime/src/mimeebod.h b/mailnews/mime/src/mimeebod.h
new file mode 100644
index 000000000..560bef77c
--- /dev/null
+++ b/mailnews/mime/src/mimeebod.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEEBOD_H_
+#define _MIMEEBOD_H_
+
+#include "mimeobj.h"
+
+/* The MimeExternalBody class implements the message/external-body MIME type.
+ (This is not to be confused with MimeExternalObject, which implements the
+ handler for application/octet-stream and other types with no more specific
+ handlers.)
+ */
+
+typedef struct MimeExternalBodyClass MimeExternalBodyClass;
+typedef struct MimeExternalBody MimeExternalBody;
+
+struct MimeExternalBodyClass {
+ MimeObjectClass object;
+};
+
+extern MimeExternalBodyClass mimeExternalBodyClass;
+
+struct MimeExternalBody {
+ MimeObject object; /* superclass variables */
+ MimeHeaders *hdrs; /* headers within this external-body, which
+ describe the network data which this body
+ is a pointer to. */
+ char *body; /* The "phantom body" of this link. */
+};
+
+#define MimeExternalBodyClassInitializer(ITYPE,CSUPER) \
+ { MimeObjectClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEEBOD_H_ */
diff --git a/mailnews/mime/src/mimeenc.cpp b/mailnews/mime/src/mimeenc.cpp
new file mode 100644
index 000000000..d565a6067
--- /dev/null
+++ b/mailnews/mime/src/mimeenc.cpp
@@ -0,0 +1,1107 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <stdio.h>
+#include "mimei.h"
+#include "prmem.h"
+#include "mimeobj.h"
+#include "mozilla/RangedPtr.h"
+#include "mozilla/mailnews/MimeEncoder.h"
+
+typedef enum mime_encoding {
+ mime_Base64, mime_QuotedPrintable, mime_uuencode, mime_yencode
+} mime_encoding;
+
+typedef enum mime_decoder_state {
+ DS_BEGIN, DS_BODY, DS_END
+} mime_decoder_state;
+
+struct MimeDecoderData {
+ mime_encoding encoding; /* Which encoding to use */
+
+ /* A read-buffer used for QP and B64. */
+ char token[4];
+ int token_size;
+
+ /* State and read-buffer used for uudecode and yencode. */
+ mime_decoder_state ds_state;
+ char *line_buffer;
+ int line_buffer_size;
+
+ MimeObject *objectToDecode; // might be null, only used for QP currently
+ /* Where to write the decoded data */
+ MimeConverterOutputCallback write_buffer;
+ void *closure;
+};
+
+
+static int
+mime_decode_qp_buffer (MimeDecoderData *data, const char *buffer,
+ int32_t length, int32_t *outSize)
+{
+ /* Warning, we are overwriting the buffer which was passed in.
+ This is ok, because decoding these formats will never result
+ in larger data than the input, only smaller. */
+ const char *in = buffer;
+ char *out = (char *) buffer;
+ char token [3];
+ int i;
+
+ NS_ASSERTION(data->encoding == mime_QuotedPrintable, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (data->encoding != mime_QuotedPrintable) return -1;
+
+ /* For the first pass, initialize the token from the unread-buffer. */
+ i = 0;
+ while (i < 3 && data->token_size > 0)
+ {
+ token [i] = data->token[i];
+ data->token_size--;
+ i++;
+ }
+
+ /* #### BUG: when decoding quoted-printable, we are required to
+ strip trailing whitespace from lines -- since when encoding in
+ qp, one is required to quote such trailing whitespace, any
+ trailing whitespace which remains must have been introduced
+ by a stupid gateway. */
+
+ /* Treat null bytes as spaces when format_out is
+ nsMimeOutput::nsMimeMessageBodyDisplay (see bug 243199 comment 7) */
+ bool treatNullAsSpace = data->objectToDecode &&
+ data->objectToDecode->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay;
+
+ while (length > 0 || i != 0)
+ {
+ while (i < 3 && length > 0)
+ {
+ token [i++] = *in;
+ in++;
+ length--;
+ }
+
+ if (i < 3)
+ {
+ /* Didn't get enough for a complete token.
+ If it might be a token, unread it.
+ Otherwise, just dump it.
+ */
+ memcpy (data->token, token, i);
+ data->token_size = i;
+ i = 0;
+ length = 0;
+ break;
+ }
+ i = 0;
+
+ if (token [0] == '=')
+ {
+ unsigned char c = 0;
+ if (token[1] >= '0' && token[1] <= '9')
+ c = token[1] - '0';
+ else if (token[1] >= 'A' && token[1] <= 'F')
+ c = token[1] - ('A' - 10);
+ else if (token[1] >= 'a' && token[1] <= 'f')
+ c = token[1] - ('a' - 10);
+ else if (token[1] == '\r' || token[1] == '\n')
+ {
+ /* =\n means ignore the newline. */
+ if (token[1] == '\r' && token[2] == '\n')
+ ; /* swallow all three chars */
+ else
+ {
+ in--; /* put the third char back */
+ length++;
+ }
+ continue;
+ }
+ else
+ {
+ /* = followed by something other than hex or newline -
+ pass it through unaltered, I guess. (But, if
+ this bogus token happened to occur over a buffer
+ boundary, we can't do this, since we don't have
+ space for it. Oh well. Screw it.) */
+ if (in > out) *out++ = token[0];
+ if (in > out) *out++ = token[1];
+ if (in > out) *out++ = token[2];
+ continue;
+ }
+
+ /* Second hex digit */
+ c = (c << 4);
+ if (token[2] >= '0' && token[2] <= '9')
+ c += token[2] - '0';
+ else if (token[2] >= 'A' && token[2] <= 'F')
+ c += token[2] - ('A' - 10);
+ else if (token[2] >= 'a' && token[2] <= 'f')
+ c += token[2] - ('a' - 10);
+ else
+ {
+ /* We got =xy where "x" was hex and "y" was not, so
+ treat that as a literal "=", x, and y. (But, if
+ this bogus token happened to occur over a buffer
+ boundary, we can't do this, since we don't have
+ space for it. Oh well. Screw it.) */
+ if (in > out) *out++ = token[0];
+ if (in > out) *out++ = token[1];
+ if (in > out) *out++ = token[2];
+ continue;
+ }
+
+ *out++ = c ? (char) c : ((treatNullAsSpace) ? ' ' : (char) c);
+ }
+ else
+ {
+ *out++ = token[0];
+
+ token[0] = token[1];
+ token[1] = token[2];
+ i = 2;
+ }
+ }
+
+ // Fill the size
+ if (outSize)
+ *outSize = out - buffer;
+
+ /* Now that we've altered the data in place, write it. */
+ if (out > buffer)
+ return data->write_buffer (buffer, (out - buffer), data->closure);
+ else
+ return 1;
+}
+
+
+static int
+mime_decode_base64_token (const char *in, char *out)
+{
+ /* reads 4, writes 0-3. Returns bytes written.
+ (Writes less than 3 only at EOF.) */
+ int j;
+ int eq_count = 0;
+ unsigned long num = 0;
+
+ for (j = 0; j < 4; j++)
+ {
+ unsigned char c = 0;
+ if (in[j] >= 'A' && in[j] <= 'Z') c = in[j] - 'A';
+ else if (in[j] >= 'a' && in[j] <= 'z') c = in[j] - ('a' - 26);
+ else if (in[j] >= '0' && in[j] <= '9') c = in[j] - ('0' - 52);
+ else if (in[j] == '+') c = 62;
+ else if (in[j] == '/') c = 63;
+ else if (in[j] == '=') c = 0, eq_count++;
+ else
+ NS_ERROR("Invalid character");
+ num = (num << 6) | c;
+ }
+
+ *out++ = (char) (num >> 16);
+ *out++ = (char) ((num >> 8) & 0xFF);
+ *out++ = (char) (num & 0xFF);
+
+ if (eq_count == 0)
+ return 3; /* No "=" padding means 4 bytes mapped to 3. */
+ else if (eq_count == 1)
+ return 2; /* "xxx=" means 3 bytes mapped to 2. */
+ else if (eq_count == 2)
+ return 1; /* "xx==" means 2 bytes mapped to 1. */
+ else
+ {
+ // "x===" can't happen, because "x" would then be encoding only
+ // 6 bits, not the min of 8.
+ NS_ERROR("Count is 6 bits, should be at least 8");
+ return 1;
+ }
+}
+
+
+static int
+mime_decode_base64_buffer (MimeDecoderData *data,
+ const char *buffer, int32_t length, int32_t *outSize)
+{
+ /* Warning, we are overwriting the buffer which was passed in.
+ This is ok, because decoding these formats will never result
+ in larger data than the input, only smaller. */
+ const char *in = buffer;
+ char *out = (char *) buffer;
+ char token [4];
+ int i;
+ bool leftover = (data->token_size > 0);
+
+ NS_ASSERTION(data->encoding == mime_Base64, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ /* For the first pass, initialize the token from the unread-buffer. */
+ i = 0;
+ while (i < 4 && data->token_size > 0)
+ {
+ token [i] = data->token[i];
+ data->token_size--;
+ i++;
+ }
+
+ while (length > 0)
+ {
+ while (i < 4 && length > 0)
+ {
+ if ((*in >= 'A' && *in <= 'Z') ||
+ (*in >= 'a' && *in <= 'z') ||
+ (*in >= '0' && *in <= '9') ||
+ *in == '+' || *in == '/' || *in == '=')
+ token [i++] = *in;
+ in++;
+ length--;
+ }
+
+ if (i < 4)
+ {
+ /* Didn't get enough for a complete token. */
+ memcpy (data->token, token, i);
+ data->token_size = i;
+ length = 0;
+ break;
+ }
+ i = 0;
+
+ if (leftover)
+ {
+ /* If there are characters left over from the last time around,
+ we might not have space in the buffer to do our dirty work
+ (if there were 2 or 3 left over, then there is only room for
+ 1 or 2 in the buffer right now, and we need 3.) This is only
+ a problem for the first chunk in each buffer, so in that
+ case, just write prematurely. */
+ int n;
+ n = mime_decode_base64_token (token, token);
+ n = data->write_buffer (token, n, data->closure);
+ if (n < 0) /* abort */
+ return n;
+
+ /* increment buffer so that we don't write the 1 or 2 unused
+ characters now at the front. */
+ buffer = in;
+ out = (char *) buffer;
+
+ leftover = false;
+ }
+ else
+ {
+ int n = mime_decode_base64_token (token, out);
+ /* Advance "out" by the number of bytes just written to it. */
+ out += n;
+ }
+ }
+
+ if (outSize)
+ *outSize = out - buffer;
+ /* Now that we've altered the data in place, write it. */
+ if (out > buffer)
+ return data->write_buffer (buffer, (out - buffer), data->closure);
+ else
+ return 1;
+}
+
+
+static int
+mime_decode_uue_buffer (MimeDecoderData *data,
+ const char *input_buffer, int32_t input_length, int32_t *outSize)
+{
+ /* First, copy input_buffer into state->line_buffer until we have
+ a complete line.
+
+ Then decode that line in place (in the line_buffer) and write
+ it out.
+
+ Then pull the next line into line_buffer and continue.
+ */
+ if (!data->line_buffer)
+ {
+ data->line_buffer_size = 128;
+ data->line_buffer = (char *)PR_MALLOC(data->line_buffer_size);
+ if (!data->line_buffer)
+ return -1;
+ data->line_buffer[0] = 0;
+ }
+
+ int status = 0;
+ char *line = data->line_buffer;
+ char *line_end = data->line_buffer + data->line_buffer_size - 1;
+
+ NS_ASSERTION(data->encoding == mime_uuencode, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (data->encoding != mime_uuencode) return -1;
+
+ if (data->ds_state == DS_END)
+ {
+ status = 0;
+ goto DONE;
+ }
+
+ while (input_length > 0)
+ {
+ /* Copy data from input_buffer to `line' until we have a complete line,
+ or until we've run out of input.
+
+ (line may have data in it already if the last time we were called,
+ we weren't called with a buffer that ended on a line boundary.)
+ */
+ {
+ char *out = line + strlen(line);
+ while (input_length > 0 &&
+ out < line_end)
+ {
+ *out++ = *input_buffer++;
+ input_length--;
+
+ if (out[-1] == '\r' || out[-1] == '\n')
+ {
+ /* If we just copied a CR, and an LF is waiting, grab it too.
+ */
+ if (out[-1] == '\r' &&
+ input_length > 0 &&
+ *input_buffer == '\n')
+ input_buffer++, input_length--;
+
+ /* We have a line. */
+ break;
+ }
+ }
+ *out = 0;
+
+ /* Ignore blank lines.
+ */
+ if (*line == '\r' || *line == '\n')
+ {
+ *line = 0;
+ continue;
+ }
+
+ /* If this line was bigger than our buffer, truncate it.
+ (This means the data was way corrupted, and there's basically
+ no chance of decoding it properly, but give it a shot anyway.)
+ */
+ if (out == line_end)
+ {
+ out--;
+ out[-1] = '\r';
+ out[0] = 0;
+ }
+
+ /* If we didn't get a complete line, simply return; we'll be called
+ with the rest of this line next time.
+ */
+ if (out[-1] != '\r' && out[-1] != '\n')
+ {
+ NS_ASSERTION (input_length == 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ break;
+ }
+ }
+
+
+ /* Now we have a complete line. Deal with it.
+ */
+
+
+ if (data->ds_state == DS_BODY &&
+ line[0] == 'e' &&
+ line[1] == 'n' &&
+ line[2] == 'd' &&
+ (line[3] == '\r' ||
+ line[3] == '\n'))
+ {
+ /* done! */
+ data->ds_state = DS_END;
+ *line = 0;
+ break;
+ }
+ else if (data->ds_state == DS_BEGIN)
+ {
+ if (!strncmp (line, "begin ", 6))
+ data->ds_state = DS_BODY;
+ *line = 0;
+ continue;
+ }
+ else
+ {
+ /* We're in DS_BODY. Decode the line. */
+ char *in, *out;
+ int32_t i;
+ long lost;
+
+ NS_ASSERTION (data->ds_state == DS_BODY, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ /* We map down `line', reading four bytes and writing three.
+ That means that `out' always stays safely behind `in'.
+ */
+ in = line;
+ out = line;
+
+# undef DEC
+# define DEC(c) (((c) - ' ') & 077)
+ i = DEC (*in); /* get length */
+
+ /* all the parens and casts are because gcc was doing something evil.
+ */
+ lost = ((long) i) - (((((long) strlen (in)) - 2L) * 3L) / 4L);
+
+ if (lost > 0) /* Short line!! */
+ {
+ /* If we get here, then the line is shorter than the length byte
+ at the beginning says it should be. However, the case where
+ the line is short because it was at the end of the buffer and
+ we didn't get the whole line was handled earlier (up by the
+ "didn't get a complete line" comment.) So if we've gotten
+ here, then this is a complete line which is internally
+ inconsistent. We will parse from it what we can...
+
+ This probably happened because some gateway stripped trailing
+ whitespace from the end of the line -- so pretend the line
+ was padded with spaces (which map to \000.)
+ */
+ i -= lost;
+ }
+
+ for (++in; i > 0; in += 4, i -= 3)
+ {
+ char ch;
+ NS_ASSERTION(out <= in, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ if (i >= 3)
+ {
+ /* We read four; write three. */
+ ch = DEC (in[0]) << 2 | DEC (in[1]) >> 4;
+ *out++ = ch;
+
+ NS_ASSERTION(out <= in+1, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ ch = DEC (in[1]) << 4 | DEC (in[2]) >> 2;
+ *out++ = ch;
+
+ NS_ASSERTION(out <= in+2, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ ch = DEC (in[2]) << 6 | DEC (in[3]);
+ *out++ = ch;
+
+ NS_ASSERTION(out <= in+3, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ }
+ else
+ {
+ /* Handle a line that isn't a multiple of 4 long.
+ (We read 1, 2, or 3, and will write 1 or 2.)
+ */
+ NS_ASSERTION (i > 0 && i < 3, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ ch = DEC (in[0]) << 2 | DEC (in[1]) >> 4;
+ *out++ = ch;
+
+ NS_ASSERTION(out <= in+1, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ if (i == 2)
+ {
+ ch = DEC (in[1]) << 4 | DEC (in[2]) >> 2;
+ *out++ = ch;
+
+ NS_ASSERTION(out <= in+2, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ }
+ }
+ }
+
+ /* If the line was truncated, pad the missing bytes with 0 (SPC). */
+ while (lost > 0)
+ {
+ *out++ = 0;
+ lost--;
+ in = out+1; /* just to prevent the assert, below. */
+ }
+# undef DEC
+
+ /* Now write out what we decoded for this line.
+ */
+ NS_ASSERTION(out >= line && out < in, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (out > line)
+ status = data->write_buffer (line, (out - line), data->closure);
+
+ // The assertion above tells us this is >= 0
+ if (outSize)
+ *outSize = out - line;
+
+ /* Reset the line so that we don't think it's partial next time. */
+ *line = 0;
+
+ if (status < 0) /* abort */
+ goto DONE;
+ }
+ }
+
+ status = 1;
+
+ DONE:
+
+ return status;
+}
+
+static int
+mime_decode_yenc_buffer (MimeDecoderData *data,
+ const char *input_buffer, int32_t input_length, int32_t *outSize)
+{
+ /* First, copy input_buffer into state->line_buffer until we have
+ a complete line.
+
+ Then decode that line in place (in the line_buffer) and write
+ it out.
+
+ Then pull the next line into line_buffer and continue.
+ */
+ if (!data->line_buffer)
+ {
+ data->line_buffer_size = 1000; // let make sure we have plenty of space for the header line
+ data->line_buffer = (char *)PR_MALLOC(data->line_buffer_size);
+ if (!data->line_buffer)
+ return -1;
+ data->line_buffer[0] = 0;
+ }
+
+ int status = 0;
+ char *line = data->line_buffer;
+ char *line_end = data->line_buffer + data->line_buffer_size - 1;
+
+ NS_ASSERTION(data->encoding == mime_yencode, "wrong decoder!");
+ if (data->encoding != mime_yencode) return -1;
+
+ if (data->ds_state == DS_END)
+ return 0;
+
+ while (input_length > 0)
+ {
+ /* Copy data from input_buffer to `line' until we have a complete line,
+ or until we've run out of input.
+
+ (line may have data in it already if the last time we were called,
+ we weren't called with a buffer that ended on a line boundary.)
+ */
+ {
+ char *out = line + strlen(line);
+ while (input_length > 0 && out < line_end)
+ {
+ *out++ = *input_buffer++;
+ input_length--;
+
+ if (out[-1] == '\r' || out[-1] == '\n')
+ {
+ /* If we just copied a CR, and an LF is waiting, grab it too. */
+ if (out[-1] == '\r' &&
+ input_length > 0 &&
+ *input_buffer == '\n')
+ input_buffer++, input_length--;
+
+ /* We have a line. */
+ break;
+ }
+ }
+ *out = 0;
+
+ /* Ignore blank lines. */
+ if (*line == '\r' || *line == '\n')
+ {
+ *line = 0;
+ continue;
+ }
+
+ /* If this line was bigger than our buffer, truncate it.
+ (This means the data was way corrupted, and there's basically
+ no chance of decoding it properly, but give it a shot anyway.)
+ */
+ if (out == line_end)
+ {
+ out--;
+ out[-1] = '\r';
+ out[0] = 0;
+ }
+
+ /* If we didn't get a complete line, simply return; we'll be called
+ with the rest of this line next time.
+ */
+ if (out[-1] != '\r' && out[-1] != '\n')
+ {
+ NS_ASSERTION (input_length == 0, "empty buffer!");
+ break;
+ }
+ }
+
+
+ /* Now we have a complete line. Deal with it.
+ */
+ const char * endOfLine = line + strlen(line);
+
+ if (data->ds_state == DS_BEGIN)
+ {
+ int new_line_size = 0;
+ /* this yenc decoder does not support yenc v2 or multipart yenc.
+ Therefore, we are looking first for "=ybegin line="
+ */
+ if ((endOfLine - line) >= 13 && !strncmp (line, "=ybegin line=", 13))
+ {
+ /* ...then couple digits. */
+ for (line += 13; line < endOfLine; line ++)
+ {
+ if (*line < '0' || *line > '9')
+ break;
+ new_line_size = (new_line_size * 10) + *line - '0';
+ }
+
+ /* ...next, look for <space>size= */
+ if ((endOfLine - line) >= 6 && !strncmp (line, " size=", 6))
+ {
+ /* ...then couple digits. */
+ for (line += 6; line < endOfLine; line ++)
+ if (*line < '0' || *line > '9')
+ break;
+
+ /* ...next, look for <space>name= */
+ if ((endOfLine - line) >= 6 && !strncmp (line, " name=", 6))
+ {
+ /* we have found the yenc header line.
+ Now check if we need to grow our buffer line
+ */
+ data->ds_state = DS_BODY;
+ if (new_line_size > data->line_buffer_size && new_line_size <= 997) /* don't let bad value hurt us! */
+ {
+ PR_Free(data->line_buffer);
+ data->line_buffer_size = new_line_size + 4; //extra chars for line ending and potential escape char
+ data->line_buffer = (char *)PR_MALLOC(data->line_buffer_size);
+ if (!data->line_buffer)
+ return -1;
+ }
+ }
+ }
+
+ }
+ *data->line_buffer = 0;
+ continue;
+ }
+
+ if (data->ds_state == DS_BODY && line[0] == '=')
+ {
+ /* look if this this the final line */
+ if (!strncmp (line, "=yend size=", 11))
+ {
+ /* done! */
+ data->ds_state = DS_END;
+ *line = 0;
+ break;
+ }
+ }
+
+ /* We're in DS_BODY. Decode the line in place. */
+ {
+ char *src = line;
+ char *dest = src;
+ char c;
+ for (; src < line_end; src ++)
+ {
+ c = *src;
+ if (!c || c == '\r' || c == '\n')
+ break;
+
+ if (c == '=')
+ {
+ src++;
+ c = *src;
+ if (c == 0)
+ return -1; /* last character cannot be escape char */
+ c -= 64;
+ }
+ c -= 42;
+ *dest = c;
+ dest ++;
+ }
+
+ // The assertion below is helpful, too
+ if (outSize)
+ *outSize = dest - line;
+
+ /* Now write out what we decoded for this line. */
+ NS_ASSERTION(dest >= line && dest <= src, "nothing to write!");
+ if (dest > line)
+ {
+ status = data->write_buffer (line, dest - line, data->closure);
+ if (status < 0) /* abort */
+ return status;
+ }
+
+ /* Reset the line so that we don't think it's partial next time. */
+ *line = 0;
+ }
+ }
+
+ return 1;
+}
+
+int
+MimeDecoderDestroy (MimeDecoderData *data, bool abort_p)
+{
+ int status = 0;
+ /* Flush out the last few buffered characters. */
+ if (!abort_p &&
+ data->token_size > 0 &&
+ data->token[0] != '=')
+ {
+ if (data->encoding == mime_Base64)
+ while ((unsigned int)data->token_size < sizeof (data->token))
+ data->token [data->token_size++] = '=';
+
+ status = data->write_buffer (data->token, data->token_size,
+ data->closure);
+ }
+
+ if (data->line_buffer)
+ PR_Free(data->line_buffer);
+ PR_Free (data);
+ return status;
+}
+
+
+static MimeDecoderData *
+mime_decoder_init (mime_encoding which,
+ MimeConverterOutputCallback output_fn,
+ void *closure)
+{
+ MimeDecoderData *data = PR_NEW(MimeDecoderData);
+ if (!data) return 0;
+ memset(data, 0, sizeof(*data));
+ data->encoding = which;
+ data->write_buffer = output_fn;
+ data->closure = closure;
+ data->line_buffer_size = 0;
+ data->line_buffer = nullptr;
+
+ return data;
+}
+
+MimeDecoderData *
+MimeB64DecoderInit (MimeConverterOutputCallback output_fn, void *closure)
+{
+ return mime_decoder_init (mime_Base64, output_fn, closure);
+}
+
+MimeDecoderData *
+MimeQPDecoderInit (MimeConverterOutputCallback output_fn,
+ void *closure, MimeObject *object)
+{
+ MimeDecoderData *retData = mime_decoder_init (mime_QuotedPrintable, output_fn, closure);
+ if (retData)
+ retData->objectToDecode = object;
+ return retData;
+}
+
+MimeDecoderData *
+MimeUUDecoderInit (MimeConverterOutputCallback output_fn,
+ void *closure)
+{
+ return mime_decoder_init (mime_uuencode, output_fn, closure);
+}
+
+MimeDecoderData *
+MimeYDecoderInit (MimeConverterOutputCallback output_fn,
+ void *closure)
+{
+ return mime_decoder_init (mime_yencode, output_fn, closure);
+}
+
+int
+MimeDecoderWrite (MimeDecoderData *data, const char *buffer, int32_t size,
+ int32_t *outSize)
+{
+ NS_ASSERTION(data, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!data) return -1;
+ switch(data->encoding)
+ {
+ case mime_Base64:
+ return mime_decode_base64_buffer (data, buffer, size, outSize);
+ case mime_QuotedPrintable:
+ return mime_decode_qp_buffer (data, buffer, size, outSize);
+ case mime_uuencode:
+ return mime_decode_uue_buffer (data, buffer, size, outSize);
+ case mime_yencode:
+ return mime_decode_yenc_buffer (data, buffer, size, outSize);
+ default:
+ NS_ERROR("Invalid decoding");
+ return -1;
+ }
+}
+
+
+namespace mozilla {
+namespace mailnews {
+
+MimeEncoder::MimeEncoder(OutputCallback callback, void *closure)
+: mCallback(callback),
+ mClosure(closure),
+ mCurrentColumn(0)
+{}
+
+class Base64Encoder : public MimeEncoder {
+ unsigned char in_buffer[3];
+ int32_t in_buffer_count;
+
+public:
+ Base64Encoder(OutputCallback callback, void *closure)
+ : MimeEncoder(callback, closure),
+ in_buffer_count(0) {}
+ virtual ~Base64Encoder() {}
+
+ virtual nsresult Write(const char *buffer, int32_t size) override;
+ virtual nsresult Flush() override;
+
+private:
+ static void Base64EncodeBits(RangedPtr<char> &out, uint32_t bits);
+};
+
+nsresult Base64Encoder::Write(const char *buffer, int32_t size)
+{
+ if (size == 0)
+ return NS_OK;
+ else if (size < 0)
+ {
+ NS_ERROR("Size is less than 0");
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this input buffer is too small, wait until next time.
+ if (size < (3 - in_buffer_count))
+ {
+ NS_ASSERTION(size == 1 || size == 2, "Unexpected size");
+ in_buffer[in_buffer_count++] = buffer[0];
+ if (size == 2)
+ in_buffer[in_buffer_count++] = buffer[1];
+ NS_ASSERTION(in_buffer_count < 3, "Unexpected out buffer size");
+ return NS_OK;
+ }
+
+
+ // If there are bytes that were put back last time, take them now.
+ uint32_t i = in_buffer_count, bits = 0;
+ if (in_buffer_count > 0) bits = in_buffer[0];
+ if (in_buffer_count > 1) bits = (bits << 8) + in_buffer[1];
+ in_buffer_count = 0;
+
+ // If this buffer is not a multiple of three, put one or two bytes back.
+ uint32_t excess = ((size + i) % 3);
+ if (excess)
+ {
+ in_buffer[0] = buffer[size - excess];
+ if (excess > 1)
+ in_buffer [1] = buffer[size - excess + 1];
+ in_buffer_count = excess;
+ size -= excess;
+ NS_ASSERTION (! ((size + i) % 3), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ }
+
+ const uint8_t *in = (const uint8_t *)buffer;
+ const uint8_t *end = (const uint8_t *)(buffer + size);
+ MOZ_ASSERT((end - in + i) % 3 == 0, "Need a multiple of 3 bytes to decode");
+
+ // Populate the out_buffer with base64 data, one line at a time.
+ char out_buffer[80]; // Max line length will be 80, so this is safe.
+ RangedPtr<char> out(out_buffer);
+ while (in < end)
+ {
+ // Accumulate the input bits.
+ while (i < 3)
+ {
+ bits = (bits << 8) | *in++;
+ i++;
+ }
+ i = 0;
+
+ Base64EncodeBits(out, bits);
+
+ mCurrentColumn += 4;
+ if (mCurrentColumn >= 72)
+ {
+ // Do a linebreak before column 76. Flush out the line buffer.
+ mCurrentColumn = 0;
+ *out++ = '\x0D';
+ *out++ = '\x0A';
+ nsresult rv = mCallback(out_buffer, (out.get() - out_buffer), mClosure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out = out_buffer;
+ }
+ }
+
+ // Write out the unwritten portion of the last line buffer.
+ if (out.get() > out_buffer)
+ {
+ nsresult rv = mCallback(out_buffer, out.get() - out_buffer, mClosure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult Base64Encoder::Flush()
+{
+ if (in_buffer_count == 0)
+ return NS_OK;
+
+ // Since we need to some buffering to get a multiple of three bytes on each
+ // block, there may be a few bytes left in the buffer after the last block has
+ // been written. We need to flush those out now.
+ char buf[4];
+ RangedPtr<char> out(buf);
+ uint32_t bits = ((uint32_t)in_buffer[0]) << 16;
+ if (in_buffer_count > 1)
+ bits |= (((uint32_t)in_buffer[1]) << 8);
+
+ Base64EncodeBits(out, bits);
+
+ // Pad with equal-signs.
+ if (in_buffer_count == 1)
+ buf[2] = '=';
+ buf[3] = '=';
+
+ return mCallback(buf, 4, mClosure);
+}
+
+void Base64Encoder::Base64EncodeBits(RangedPtr<char> &out, uint32_t bits)
+{
+ // Convert 3 bytes to 4 base64 bytes
+ for (int32_t j = 18; j >= 0; j -= 6)
+ {
+ unsigned int k = (bits >> j) & 0x3F;
+ if (k < 26) *out++ = k + 'A';
+ else if (k < 52) *out++ = k - 26 + 'a';
+ else if (k < 62) *out++ = k - 52 + '0';
+ else if (k == 62) *out++ = '+';
+ else if (k == 63) *out++ = '/';
+ else MOZ_CRASH("6 bits should only be between 0 and 64");
+ }
+}
+
+class QPEncoder : public MimeEncoder {
+public:
+ QPEncoder(OutputCallback callback, void *closure)
+ : MimeEncoder(callback, closure) {}
+ virtual ~QPEncoder() {}
+
+ virtual nsresult Write(const char *buffer, int32_t size) override;
+};
+
+nsresult QPEncoder::Write(const char *buffer, int32_t size)
+{
+ nsresult rv = NS_OK;
+ static const char *hexdigits = "0123456789ABCDEF";
+ char out_buffer[80];
+ RangedPtr<char> out(out_buffer);
+ bool white = false;
+
+ // Populate the out_buffer with quoted-printable data, one line at a time.
+ const uint8_t *in = (uint8_t *)buffer;
+ const uint8_t *end = in + size;
+ for (; in < end; in++)
+ {
+ if (*in == '\r' || *in == '\n')
+ {
+ // If it's CRLF, swallow two chars instead of one.
+ if (in + 1 < end && in[0] == '\r' && in[1] == '\n')
+ in++;
+
+ // Whitespace cannot be allowed to occur at the end of the line, so we
+ // back up and replace the whitespace with its code.
+ if (white)
+ {
+ out--;
+ char whitespace_char = *out;
+ *out++ = '=';
+ *out++ = hexdigits[whitespace_char >> 4];
+ *out++ = hexdigits[whitespace_char & 0xF];
+ }
+
+ // Now write out the newline.
+ *out++ = '\r';
+ *out++ = '\n';
+ white = false;
+
+ rv = mCallback(out_buffer, out.get() - out_buffer, mClosure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out = out_buffer;
+ mCurrentColumn = 0;
+ }
+ else if (mCurrentColumn == 0 && *in == '.')
+ {
+ // Just to be SMTP-safe, if "." appears in column 0, encode it.
+ goto HEX;
+ }
+ else if (mCurrentColumn == 0 && *in == 'F'
+ && (in >= end-1 || in[1] == 'r')
+ && (in >= end-2 || in[2] == 'o')
+ && (in >= end-3 || in[3] == 'm')
+ && (in >= end-4 || in[4] == ' '))
+ {
+ // If this line begins with "From " (or it could but we don't have enough
+ // data in the buffer to be certain), encode the 'F' in hex to avoid
+ // potential problems with BSD mailbox formats.
+ goto HEX;
+ }
+ else if ((*in >= 33 && *in <= 60) |
+ (*in >= 62 && *in <= 126)) // Printable characters except for '='
+ {
+ white = false;
+ *out++ = *in;
+ mCurrentColumn++;
+ }
+ else if (*in == ' ' || *in == '\t') // Whitespace
+ {
+ white = true;
+ *out++ = *in;
+ mCurrentColumn++;
+ }
+ else
+ {
+ // Encode the characters here
+HEX:
+ white = false;
+ *out++ = '=';
+ *out++ = hexdigits[*in >> 4];
+ *out++ = hexdigits[*in & 0xF];
+ mCurrentColumn += 3;
+ }
+
+ MOZ_ASSERT(mCurrentColumn <= 76, "Why haven't we added a line break yet?");
+
+ if (mCurrentColumn >= 73) // Soft line break for readability
+ {
+ *out++ = '=';
+ *out++ = '\r';
+ *out++ = '\n';
+
+ rv = mCallback(out_buffer, out.get() - out_buffer, mClosure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out = out_buffer;
+ white = false;
+ mCurrentColumn = 0;
+ }
+ }
+
+ // Write out the unwritten portion of the last line buffer.
+ if (out.get() != out_buffer)
+ {
+ rv = mCallback(out_buffer, out.get() - out_buffer, mClosure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+MimeEncoder *MimeEncoder::GetBase64Encoder(OutputCallback callback,
+ void *closure)
+{
+ return new Base64Encoder(callback, closure);
+}
+
+MimeEncoder *MimeEncoder::GetQPEncoder(OutputCallback callback, void *closure)
+{
+ return new QPEncoder(callback, closure);
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/mailnews/mime/src/mimeeobj.cpp b/mailnews/mime/src/mimeeobj.cpp
new file mode 100644
index 000000000..7d8b2f8a6
--- /dev/null
+++ b/mailnews/mime/src/mimeeobj.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "mimeeobj.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "nsMimeStringResources.h"
+#include "mimemoz2.h"
+#include "mimemapl.h"
+#include "nsMimeTypes.h"
+
+
+#define MIME_SUPERCLASS mimeLeafClass
+MimeDefClass(MimeExternalObject, MimeExternalObjectClass,
+ mimeExternalObjectClass, &MIME_SUPERCLASS);
+
+static int MimeExternalObject_initialize (MimeObject *);
+static void MimeExternalObject_finalize (MimeObject *);
+static int MimeExternalObject_parse_begin (MimeObject *);
+static int MimeExternalObject_parse_buffer (const char *, int32_t, MimeObject *);
+static int MimeExternalObject_parse_line (const char *, int32_t, MimeObject *);
+static int MimeExternalObject_parse_decoded_buffer (const char*, int32_t, MimeObject*);
+static bool MimeExternalObject_displayable_inline_p (MimeObjectClass *clazz,
+ MimeHeaders *hdrs);
+
+static int
+MimeExternalObjectClassInitialize(MimeExternalObjectClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeLeafClass *lclass = (MimeLeafClass *) clazz;
+
+ NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ oclass->initialize = MimeExternalObject_initialize;
+ oclass->finalize = MimeExternalObject_finalize;
+ oclass->parse_begin = MimeExternalObject_parse_begin;
+ oclass->parse_buffer = MimeExternalObject_parse_buffer;
+ oclass->parse_line = MimeExternalObject_parse_line;
+ oclass->displayable_inline_p = MimeExternalObject_displayable_inline_p;
+ lclass->parse_decoded_buffer = MimeExternalObject_parse_decoded_buffer;
+ return 0;
+}
+
+
+static int
+MimeExternalObject_initialize (MimeObject *object)
+{
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object);
+}
+
+static void
+MimeExternalObject_finalize (MimeObject *object)
+{
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object);
+}
+
+
+static int
+MimeExternalObject_parse_begin (MimeObject *obj)
+{
+ int status;
+
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+ if (status < 0) return status;
+
+ // If we're writing this object, and we're doing it in raw form, then
+ // now is the time to inform the backend what the type of this data is.
+ //
+ if (obj->output_p &&
+ obj->options &&
+ !obj->options->write_html_p &&
+ !obj->options->state->first_data_written_p)
+ {
+ status = MimeObject_output_init(obj, 0);
+ if (status < 0) return status;
+ NS_ASSERTION(obj->options->state->first_data_written_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ }
+
+ //
+ // If we're writing this object as HTML, do all the work now -- just write
+ // out a table with a link in it. (Later calls to the `parse_buffer' method
+ // will simply discard the data of the object itself.)
+ //
+ if (obj->options &&
+ obj->output_p &&
+ obj->options->write_html_p &&
+ obj->options->output_fn)
+ {
+ MimeDisplayOptions newopt = *obj->options; // copy it
+ char *id = 0;
+ char *id_url = 0;
+ char *id_name = 0;
+ nsCString id_imap;
+ bool all_headers_p = obj->options->headers == MimeHeadersAll;
+
+ id = mime_part_address (obj);
+ if (obj->options->missing_parts)
+ id_imap.Adopt(mime_imap_part_address (obj));
+ if (! id) return MIME_OUT_OF_MEMORY;
+
+ if (obj->options && obj->options->url)
+ {
+ const char *url = obj->options->url;
+ if (!id_imap.IsEmpty() && id)
+ {
+ // if this is an IMAP part.
+ id_url = mime_set_url_imap_part(url, id_imap.get(), id);
+ }
+ else
+ {
+ // This is just a normal MIME part as usual.
+ id_url = mime_set_url_part(url, id, true);
+ }
+ if (!id_url)
+ {
+ PR_Free(id);
+ return MIME_OUT_OF_MEMORY;
+ }
+ }
+ if (!strcmp (id, "0"))
+ {
+ PR_Free(id);
+ id = MimeGetStringByID(MIME_MSG_ATTACHMENT);
+ }
+ else
+ {
+ const char *p = "Part ";
+ uint32_t slen = strlen(p) + strlen(id) + 1;
+ char *s = (char *)PR_MALLOC(slen);
+ if (!s)
+ {
+ PR_Free(id);
+ PR_Free(id_url);
+ return MIME_OUT_OF_MEMORY;
+ }
+ // we have a valid id
+ if (id)
+ id_name = mime_find_suggested_name_of_part(id, obj);
+ PL_strncpyz(s, p, slen);
+ PL_strcatn(s, slen, id);
+ PR_Free(id);
+ id = s;
+ }
+
+ if (all_headers_p &&
+ // Don't bother showing all headers on this part if it's the only
+ // part in the message: in that case, we've already shown these
+ // headers.
+ obj->options->state &&
+ obj->options->state->root == obj->parent)
+ all_headers_p = false;
+
+ newopt.fancy_headers_p = true;
+ newopt.headers = (all_headers_p ? MimeHeadersAll : MimeHeadersSome);
+
+/******
+RICHIE SHERRY
+GOTTA STILL DO THIS FOR QUOTING!
+ status = MimeHeaders_write_attachment_box (obj->headers, &newopt,
+ obj->content_type,
+ obj->encoding,
+ id_name? id_name : id, id_url, 0)
+*****/
+
+ // obj->options really owns the storage for this.
+ newopt.part_to_load = nullptr;
+ newopt.default_charset = nullptr;
+ PR_FREEIF(id);
+ PR_FREEIF(id_url);
+ PR_FREEIF(id_name);
+ if (status < 0) return status;
+ }
+
+ return 0;
+}
+
+static int
+MimeExternalObject_parse_buffer (const char *buffer, int32_t size, MimeObject *obj)
+{
+ NS_ASSERTION(!obj->closed_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (obj->closed_p) return -1;
+
+ // Currently, we always want to stream, in order to determine the size of the
+ // MIME object.
+
+ /* The data will be base64-decoded and passed to
+ MimeExternalObject_parse_decoded_buffer. */
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_buffer(buffer, size, obj);
+}
+
+
+static int
+MimeExternalObject_parse_decoded_buffer (const char *buf, int32_t size,
+ MimeObject *obj)
+{
+ /* This is called (by MimeLeafClass->parse_buffer) with blocks of data
+ that have already been base64-decoded. This will only be called in
+ the case where we're not emitting HTML, and want access to the raw
+ data itself.
+
+ We override the `parse_decoded_buffer' method provided by MimeLeaf
+ because, unlike most children of MimeLeaf, we do not want to line-
+ buffer the decoded data -- we want to simply pass it along to the
+ backend, without going through our `parse_line' method.
+ */
+
+ /* Don't do a roundtrip through XPConnect when we're only interested in
+ * metadata and size. This includes when we are writing HTML (otherwise, the
+ * contents of binary attachments will just get dumped into messages when
+ * reading them) and the JS emitter (which doesn't care about attachment data
+ * at all). 0 means ok, the caller just checks for negative return value.
+ */
+ if (obj->options && (obj->options->metadata_only ||
+ obj->options->write_html_p))
+ return 0;
+ else
+ return MimeObject_write(obj, buf, size, true);
+}
+
+
+static int
+MimeExternalObject_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ NS_ERROR("This method should never be called (externals do no line buffering).");
+ return -1;
+}
+
+static bool
+MimeExternalObject_displayable_inline_p (MimeObjectClass *clazz,
+ MimeHeaders *hdrs)
+{
+ return false;
+}
+
+#undef MIME_SUPERCLASS
+#define MIME_SUPERCLASS mimeExternalObjectClass
+MimeDefClass(MimeSuppressedCrypto, MimeSuppressedCryptoClass,
+ mimeSuppressedCryptoClass, &MIME_SUPERCLASS);
+
+static int MimeSuppressedCryptoClassInitialize(MimeSuppressedCryptoClass *clazz) {
+ MimeExternalObjectClass *lclass = (MimeExternalObjectClass *)clazz;
+ return MimeExternalObjectClassInitialize(lclass);
+}
diff --git a/mailnews/mime/src/mimeeobj.h b/mailnews/mime/src/mimeeobj.h
new file mode 100644
index 000000000..076f8f9bf
--- /dev/null
+++ b/mailnews/mime/src/mimeeobj.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEEOBJ_H_
+#define _MIMEEOBJ_H_
+
+#include "mimeleaf.h"
+
+/* The MimeExternalObject class represents MIME parts which contain data
+ which cannot be displayed inline -- application/octet-stream and any
+ other type that is not otherwise specially handled. (This is not to
+ be confused with MimeExternalBody, which is the handler for the
+ message/external-object MIME type only.)
+ */
+
+typedef struct MimeExternalObjectClass MimeExternalObjectClass;
+typedef struct MimeExternalObject MimeExternalObject;
+
+struct MimeExternalObjectClass {
+ MimeLeafClass leaf;
+};
+
+extern "C" MimeExternalObjectClass mimeExternalObjectClass;
+
+struct MimeExternalObject {
+ MimeLeaf leaf;
+};
+
+#define MimeExternalObjectClassInitializer(ITYPE,CSUPER) \
+ { MimeLeafClassInitializer(ITYPE,CSUPER) }
+
+typedef struct MimeSuppressedCryptoClass MimeSuppressedCryptoClass;
+typedef struct MimeSuppressedCrypto MimeSuppressedCrypto;
+
+struct MimeSuppressedCryptoClass {
+ MimeExternalObjectClass eobj;
+};
+
+extern "C" MimeSuppressedCryptoClass mimeSuppressedCryptoClass;
+
+struct MimeSuppressedCrypto {
+ MimeExternalObject eobj;
+};
+
+#define MimeSuppressedCryptoClassInitializer(ITYPE, CSUPER) \
+ { MimeExternalObjectClassInitializer(ITYPE, CSUPER) }
+
+#endif /* _MIMEEOBJ_H_ */
diff --git a/mailnews/mime/src/mimefilt.cpp b/mailnews/mime/src/mimefilt.cpp
new file mode 100644
index 000000000..9ea4996b5
--- /dev/null
+++ b/mailnews/mime/src/mimefilt.cpp
@@ -0,0 +1,399 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* mimefilt.c --- test harness for libmime.a
+
+ This program reads a message from stdin and writes the output of the MIME
+ parser on stdout.
+
+ Parameters can be passed to the parser through the usual URL mechanism:
+
+ mimefilt BASE-URL?headers=all&rot13 < in > out
+
+ Some parameters can't be affected that way, so some additional switches
+ may be passed on the command line after the URL:
+
+ -fancy whether fancy headers should be generated (default)
+
+ -no-fancy opposite; this uses the headers used in the cases of
+ FO_SAVE_AS_TEXT or FO_QUOTE_MESSAGE
+
+ -html whether we should convert to HTML (like FO_PRESENT);
+ this is the default if no ?part= is specified.
+
+ -raw don't convert to HTML (FO_SAVE_AS);
+ this is the default if a ?part= is specified.
+
+ -outline at the end, print a debugging overview of the MIME structure
+
+ Before any output comes a blurb listing the content-type, charset, and
+ various other info that would have been put in the generated URL struct.
+ It's printed to the beginning of the output because otherwise this out-
+ of-band data would have been lost. (So the output of this program is,
+ in fact, a raw HTTP response.)
+ */
+
+#include "mimemsg.h"
+#include "prglobal.h"
+
+#include "key.h"
+#include "cert.h"
+#include "secrng.h"
+#include "secmod.h"
+#include "pk11func.h"
+#include "nsMimeStringResources.h"
+
+#ifndef XP_UNIX
+ERROR! This is a unix-only file for the "mimefilt" standalone program.
+ This does not go into libmime.a.
+#endif
+
+
+static char *
+test_file_type (const char *filename, void *stream_closure)
+{
+ const char *suf = PL_strrchr(filename, '.');
+ if (!suf)
+ return 0;
+ suf++;
+
+ if (!PL_strcasecmp(suf, "txt") ||
+ !PL_strcasecmp(suf, "text"))
+ return strdup("text/plain");
+ else if (!PL_strcasecmp(suf, "htm") ||
+ !PL_strcasecmp(suf, "html"))
+ return strdup("text/html");
+ else if (!PL_strcasecmp(suf, "gif"))
+ return strdup("image/gif");
+ else if (!PL_strcasecmp(suf, "svg"))
+ return strdup("image/svg+xml");
+ else if (!PL_strcasecmp(suf, "jpg") ||
+ !PL_strcasecmp(suf, "jpeg"))
+ return strdup("image/jpeg");
+ else if (!PL_strcasecmp(suf, "pjpg") ||
+ !PL_strcasecmp(suf, "pjpeg"))
+ return strdup("image/pjpeg");
+ else if (!PL_strcasecmp(suf, "xbm"))
+ return strdup("image/x-xbitmap");
+ else if (!PL_strcasecmp(suf, "xpm"))
+ return strdup("image/x-xpixmap");
+ else if (!PL_strcasecmp(suf, "xwd"))
+ return strdup("image/x-xwindowdump");
+ else if (!PL_strcasecmp(suf, "bmp"))
+ return strdup("image/x-MS-bmp");
+ else if (!PL_strcasecmp(suf, "au"))
+ return strdup("audio/basic");
+ else if (!PL_strcasecmp(suf, "aif") ||
+ !PL_strcasecmp(suf, "aiff") ||
+ !PL_strcasecmp(suf, "aifc"))
+ return strdup("audio/x-aiff");
+ else if (!PL_strcasecmp(suf, "ps"))
+ return strdup("application/postscript");
+ else
+ return 0;
+}
+
+static int
+test_output_fn(char *buf, int32_t size, void *closure)
+{
+ FILE *out = (FILE *) closure;
+ if (out)
+ return fwrite(buf, sizeof(*buf), size, out);
+ else
+ return 0;
+}
+
+static int
+test_output_init_fn (const char *type,
+ const char *charset,
+ const char *name,
+ const char *x_mac_type,
+ const char *x_mac_creator,
+ void *stream_closure)
+{
+ FILE *out = (FILE *) stream_closure;
+ fprintf(out, "CONTENT-TYPE: %s", type);
+ if (charset)
+ fprintf(out, "; charset=\"%s\"", charset);
+ if (name)
+ fprintf(out, "; name=\"%s\"", name);
+ if (x_mac_type || x_mac_creator)
+ fprintf(out, "; x-mac-type=\"%s\"; x-mac-creator=\"%s\"",
+ x_mac_type ? x_mac_type : "",
+ x_mac_creator ? x_mac_type : "");
+ fprintf(out, CRLF CRLF);
+ return 0;
+}
+
+static void *
+test_image_begin(const char *image_url, const char *content_type,
+ void *stream_closure)
+{
+ return ((void *) strdup(image_url));
+}
+
+static void
+test_image_end(void *image_closure, int status)
+{
+ char *url = (char *) image_closure;
+ if (url) PR_Free(url);
+}
+
+static char *
+test_image_make_image_html(void *image_data)
+{
+ char *url = (char *) image_data;
+#if 0
+ const char *prefix = "<P><CENTER><IMG SRC=\"";
+ const char *suffix = "\"></CENTER><P>";
+#else
+ const char *prefix = ("<P><CENTER><TABLE BORDER=2 CELLPADDING=20"
+ " BGCOLOR=WHITE>"
+ "<TR><TD ALIGN=CENTER>"
+ "an inlined image would have gone here for<BR>");
+ const char *suffix = "</TD></TR></TABLE></CENTER><P>";
+#endif
+ uint32_t buflen = strlen (prefix) + strlen (suffix) + strlen (url) + 20;
+ char *buf = (char *) PR_MALLOC (buflen);
+ if (!buf) return 0;
+ *buf = 0;
+ PL_strcatn (buf, buflen, prefix);
+ PL_strcatn (buf, buflen, url);
+ PL_strcatn (buf, buflen, suffix);
+ return buf;
+}
+
+static int test_image_write_buffer(const char *buf, int32_t size, void *image_closure)
+{
+ return 0;
+}
+
+static char *
+test_passwd_prompt (PK11SlotInfo *slot, void *wincx)
+{
+ char buf[2048], *s;
+ fprintf(stdout, "#### Password required: ");
+ s = fgets(buf, sizeof(buf)-1, stdin);
+ if (!s) return s;
+ if (s[strlen(s)-1] == '\r' ||
+ s[strlen(s)-1] == '\n')
+ s[strlen(s)-1] = '\0';
+ return s;
+}
+
+
+int
+test(FILE *in, FILE *out,
+ const char *url,
+ bool fancy_headers_p,
+ bool html_p,
+ bool outline_p,
+ bool dexlate_p,
+ bool variable_width_plaintext_p)
+{
+ int status = 0;
+ MimeObject *obj = 0;
+ MimeDisplayOptions *opt = new MimeDisplayOptions;
+// memset(opt, 0, sizeof(*opt));
+
+ if (dexlate_p) html_p = false;
+
+ opt->fancy_headers_p = fancy_headers_p;
+ opt->headers = MimeHeadersSome;
+ opt->rot13_p = false;
+
+ status = mime_parse_url_options(url, opt);
+ if (status < 0)
+ {
+ PR_Free(opt);
+ return MIME_OUT_OF_MEMORY;
+ }
+
+ opt->url = url;
+ opt->write_html_p = html_p;
+ opt->dexlate_p = dexlate_p;
+ opt->output_init_fn = test_output_init_fn;
+ opt->output_fn = test_output_fn;
+ opt->charset_conversion_fn= 0;
+ opt->rfc1522_conversion_p = false;
+ opt->file_type_fn = test_file_type;
+ opt->stream_closure = out;
+
+ opt->image_begin = test_image_begin;
+ opt->image_end = test_image_end;
+ opt->make_image_html = test_image_make_image_html;
+ opt->image_write_buffer = test_image_write_buffer;
+
+ opt->variable_width_plaintext_p = variable_width_plaintext_p;
+
+ obj = mime_new ((MimeObjectClass *)&mimeMessageClass,
+ (MimeHeaders *) NULL,
+ MESSAGE_RFC822);
+ if (!obj)
+ {
+ PR_Free(opt);
+ return MIME_OUT_OF_MEMORY;
+ }
+ obj->options = opt;
+
+ status = obj->class->initialize(obj);
+ if (status >= 0)
+ status = obj->class->parse_begin(obj);
+ if (status < 0)
+ {
+ PR_Free(opt);
+ PR_Free(obj);
+ return MIME_OUT_OF_MEMORY;
+ }
+
+ while (1)
+ {
+ char buf[255];
+ int size = fread(buf, sizeof(*buf), sizeof(buf), stdin);
+ if (size <= 0) break;
+ status = obj->class->parse_buffer(buf, size, obj);
+ if (status < 0)
+ {
+ mime_free(obj);
+ PR_Free(opt);
+ return status;
+ }
+ }
+
+ status = obj->class->parse_eof(obj, false);
+ if (status >= 0)
+ status = obj->class->parse_end(obj, false);
+ if (status < 0)
+ {
+ mime_free(obj);
+ PR_Free(opt);
+ return status;
+ }
+
+ if (outline_p)
+ {
+ fprintf(out, "\n\n"
+ "###############################################################\n");
+ obj->class->debug_print(obj, stderr, 0);
+ fprintf(out,
+ "###############################################################\n");
+ }
+
+ mime_free (obj);
+ PR_Free(opt);
+ return 0;
+}
+
+
+static char *
+test_cdb_name_cb (void *arg, int vers)
+{
+ static char f[1024];
+ if (vers <= 4)
+ sprintf(f, "%s/.netscape/cert.db", getenv("HOME"));
+ else
+ sprintf(f, "%s/.netscape/cert%d.db", getenv("HOME"), vers);
+ return f;
+}
+
+static char *
+test_kdb_name_cb (void *arg, int vers)
+{
+ static char f[1024];
+ if (vers <= 2)
+ sprintf(f, "%s/.netscape/key.db", getenv("HOME"));
+ else
+ sprintf(f, "%s/.netscape/key%d.db", getenv("HOME"), vers);
+ return f;
+}
+
+extern void SEC_Init(void);
+
+int
+main (int argc, char **argv)
+{
+ int32_t i = 1;
+ char *url = "";
+ bool fancy_p = true;
+ bool html_p = true;
+ bool outline_p = false;
+ bool dexlate_p = false;
+ char filename[1000];
+ CERTCertDBHandle *cdb_handle;
+ SECKEYKeyDBHandle *kdb_handle;
+
+ PR_Init("mimefilt", 24, 1, 0);
+
+ cdb_handle = (CERTCertDBHandle *) calloc(1, sizeof(*cdb_handle));
+
+ if (SECSuccess != CERT_OpenCertDB(cdb_handle, false, test_cdb_name_cb, NULL))
+ CERT_OpenVolatileCertDB(cdb_handle);
+ CERT_SetDefaultCertDB(cdb_handle);
+
+ RNG_RNGInit();
+
+ kdb_handle = SECKEY_OpenKeyDB(false, test_kdb_name_cb, NULL);
+ SECKEY_SetDefaultKeyDB(kdb_handle);
+
+ PK11_SetPasswordFunc(test_passwd_prompt);
+
+ sprintf(filename, "%s/.netscape/secmodule.db", getenv("HOME"));
+ SECMOD_init(filename);
+
+ SEC_Init();
+
+
+ if (i < argc)
+ {
+ if (argv[i][0] == '-')
+ url = strdup("");
+ else
+ url = argv[i++];
+ }
+
+ if (url &&
+ (PL_strstr(url, "?part=") ||
+ PL_strstr(url, "&part=")))
+ html_p = false;
+
+ while (i < argc)
+ {
+ if (!strcmp(argv[i], "-fancy"))
+ fancy_p = true;
+ else if (!strcmp(argv[i], "-no-fancy"))
+ fancy_p = false;
+ else if (!strcmp(argv[i], "-html"))
+ html_p = true;
+ else if (!strcmp(argv[i], "-raw"))
+ html_p = false;
+ else if (!strcmp(argv[i], "-outline"))
+ outline_p = true;
+ else if (!strcmp(argv[i], "-dexlate"))
+ dexlate_p = true;
+ else
+ {
+ fprintf(stderr,
+ "usage: %s [ URL [ -fancy | -no-fancy | -html | -raw | -outline | -dexlate ]]\n"
+ " < message/rfc822 > output\n",
+ (PL_strrchr(argv[0], '/') ?
+ PL_strrchr(argv[0], '/') + 1 :
+ argv[0]));
+ i = 1;
+ goto FAIL;
+ }
+ i++;
+ }
+
+ i = test(stdin, stdout, url, fancy_p, html_p, outline_p, dexlate_p, true);
+ fprintf(stdout, "\n");
+ fflush(stdout);
+
+ FAIL:
+
+ CERT_ClosePermCertDB(cdb_handle);
+ SECKEY_CloseKeyDB(kdb_handle);
+
+ exit(i);
+}
diff --git a/mailnews/mime/src/mimehdrs.cpp b/mailnews/mime/src/mimehdrs.cpp
new file mode 100644
index 000000000..6d187ed52
--- /dev/null
+++ b/mailnews/mime/src/mimehdrs.cpp
@@ -0,0 +1,888 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "msgCore.h"
+#include "mimei.h"
+#include "prmem.h"
+#include "prlog.h"
+#include "plstr.h"
+#include "mimebuf.h"
+#include "mimemoz2.h"
+#include "nsIMimeEmitter.h"
+#include "nsMsgMessageFlags.h"
+#include "comi18n.h"
+#include "nsMailHeaders.h"
+#include "msgCore.h"
+#include "nsMimeStringResources.h"
+#include "mimemoz2.h"
+#include "nsMsgI18N.h"
+#include "mimehdrs.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include <ctype.h>
+#include "nsMsgUtils.h"
+
+// Forward declares...
+int32_t MimeHeaders_build_heads_list(MimeHeaders *hdrs);
+
+void
+MimeHeaders_convert_header_value(MimeDisplayOptions *opt, nsCString &value,
+ bool convert_charset_only)
+{
+ if (value.IsEmpty())
+ return;
+
+ if (convert_charset_only)
+ {
+ nsAutoCString output;
+ ConvertRawBytesToUTF8(value, opt->default_charset, output);
+ value.Assign(output);
+ return;
+ }
+
+ if (opt && opt->rfc1522_conversion_p)
+ {
+ nsAutoCString temporary;
+ MIME_DecodeMimeHeader(value.get(), opt->default_charset,
+ opt->override_charset, true, temporary);
+
+ if (!temporary.IsEmpty())
+ {
+ value = temporary;
+ }
+ }
+ else
+ {
+ // This behavior, though highly unusual, was carefully preserved
+ // from the previous implementation. It may be that this is dead
+ // code, in which case opt->rfc1522_conversion_p is no longer
+ // needed.
+ value.Truncate();
+ }
+}
+
+MimeHeaders *
+MimeHeaders_new (void)
+{
+ MimeHeaders *hdrs = (MimeHeaders *) PR_MALLOC(sizeof(MimeHeaders));
+ if (!hdrs) return 0;
+
+ memset(hdrs, 0, sizeof(*hdrs));
+ hdrs->done_p = false;
+
+ return hdrs;
+}
+
+void
+MimeHeaders_free (MimeHeaders *hdrs)
+{
+ if (!hdrs) return;
+ PR_FREEIF(hdrs->all_headers);
+ PR_FREEIF(hdrs->heads);
+ PR_FREEIF(hdrs->obuffer);
+ PR_FREEIF(hdrs->munged_subject);
+ hdrs->obuffer_fp = 0;
+ hdrs->obuffer_size = 0;
+
+# ifdef DEBUG__
+ {
+ int i, size = sizeof(*hdrs);
+ uint32_t *array = (uint32_t*) hdrs;
+ for (i = 0; i < (size / sizeof(*array)); i++)
+ array[i] = (uint32_t) 0xDEADBEEF;
+ }
+# endif /* DEBUG */
+
+ PR_Free(hdrs);
+}
+
+int
+MimeHeaders_parse_line (const char *buffer, int32_t size, MimeHeaders *hdrs)
+{
+ int status = 0;
+ int desired_size;
+
+ NS_ASSERTION(hdrs, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48");
+ if (!hdrs) return -1;
+
+ /* Don't try and feed me more data after having fed me a blank line... */
+ NS_ASSERTION(!hdrs->done_p, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48");
+ if (hdrs->done_p) return -1;
+
+ if (!buffer || size == 0 || *buffer == '\r' || *buffer == '\n')
+ {
+ /* If this is a blank line, we're done.
+ */
+ hdrs->done_p = true;
+ return MimeHeaders_build_heads_list(hdrs);
+ }
+
+ /* Tack this data on to the end of our copy.
+ */
+ desired_size = hdrs->all_headers_fp + size + 1;
+ if (desired_size >= hdrs->all_headers_size)
+ {
+ status = mime_GrowBuffer (desired_size, sizeof(char), 255,
+ &hdrs->all_headers, &hdrs->all_headers_size);
+ if (status < 0) return status;
+ }
+ memcpy(hdrs->all_headers+hdrs->all_headers_fp, buffer, size);
+ hdrs->all_headers_fp += size;
+
+ return 0;
+}
+
+MimeHeaders *
+MimeHeaders_copy (MimeHeaders *hdrs)
+{
+ MimeHeaders *hdrs2;
+ if (!hdrs) return 0;
+
+ hdrs2 = (MimeHeaders *) PR_MALLOC(sizeof(*hdrs));
+ if (!hdrs2) return 0;
+ memset(hdrs2, 0, sizeof(*hdrs2));
+
+ if (hdrs->all_headers)
+ {
+ hdrs2->all_headers = (char *) PR_MALLOC(hdrs->all_headers_fp);
+ if (!hdrs2->all_headers)
+ {
+ PR_Free(hdrs2);
+ return 0;
+ }
+ memcpy(hdrs2->all_headers, hdrs->all_headers, hdrs->all_headers_fp);
+
+ hdrs2->all_headers_fp = hdrs->all_headers_fp;
+ hdrs2->all_headers_size = hdrs->all_headers_fp;
+ }
+
+ hdrs2->done_p = hdrs->done_p;
+
+ if (hdrs->heads)
+ {
+ int i;
+ hdrs2->heads = (char **) PR_MALLOC(hdrs->heads_size
+ * sizeof(*hdrs->heads));
+ if (!hdrs2->heads)
+ {
+ PR_FREEIF(hdrs2->all_headers);
+ PR_Free(hdrs2);
+ return 0;
+ }
+ hdrs2->heads_size = hdrs->heads_size;
+ for (i = 0; i < hdrs->heads_size; i++)
+ {
+ hdrs2->heads[i] = (hdrs2->all_headers +
+ (hdrs->heads[i] - hdrs->all_headers));
+ }
+ }
+ return hdrs2;
+}
+
+int
+MimeHeaders_build_heads_list(MimeHeaders *hdrs)
+{
+ char *s;
+ char *end;
+ int i;
+ NS_ASSERTION(hdrs, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!hdrs) return -1;
+
+ NS_ASSERTION(hdrs->done_p && !hdrs->heads, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!hdrs->done_p || hdrs->heads)
+ return -1;
+
+ if (hdrs->all_headers_fp == 0)
+ {
+ /* Must not have been any headers (we got the blank line right away.) */
+ PR_FREEIF (hdrs->all_headers);
+ hdrs->all_headers_size = 0;
+ return 0;
+ }
+
+ /* At this point, we might as well realloc all_headers back down to the
+ minimum size it must be (it could be up to 1k bigger.) But don't
+ bother if we're only off by a tiny bit. */
+ NS_ASSERTION(hdrs->all_headers_fp <= hdrs->all_headers_size, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (hdrs->all_headers_fp + 60 <= hdrs->all_headers_size)
+ {
+ char *ls = (char *)PR_Realloc(hdrs->all_headers, hdrs->all_headers_fp);
+ if (ls) /* can this ever fail? we're making it smaller... */
+ {
+ hdrs->all_headers = ls; /* in case it got relocated */
+ hdrs->all_headers_size = hdrs->all_headers_fp;
+ }
+ }
+
+ /* First go through and count up the number of headers in the block.
+ */
+ end = hdrs->all_headers + hdrs->all_headers_fp;
+ for (s = hdrs->all_headers; s < end; s++)
+ {
+ if (s < (end-1) && s[0] == '\r' && s[1] == '\n') /* CRLF -> LF */
+ s++;
+
+ if ((s[0] == '\r' || s[0] == '\n') && /* we're at a newline, and */
+ (s >= (end-1) || /* we're at EOF, or */
+ !(s[1] == ' ' || s[1] == '\t'))) /* next char is nonwhite */
+ hdrs->heads_size++;
+ }
+
+ /* Now allocate storage for the pointers to each of those headers.
+ */
+ hdrs->heads = (char **) PR_MALLOC((hdrs->heads_size + 1) * sizeof(char *));
+ if (!hdrs->heads)
+ return MIME_OUT_OF_MEMORY;
+ memset(hdrs->heads, 0, (hdrs->heads_size + 1) * sizeof(char *));
+
+ /* Now make another pass through the headers, and this time, record the
+ starting position of each header.
+ */
+
+ i = 0;
+ hdrs->heads[i++] = hdrs->all_headers;
+ s = hdrs->all_headers;
+
+ while (s < end)
+ {
+ SEARCH_NEWLINE:
+ while (s < end && *s != '\r' && *s != '\n')
+ s++;
+
+ if (s >= end)
+ break;
+
+ /* If "\r\n " or "\r\n\t" is next, that doesn't terminate the header. */
+ else if (s+2 < end &&
+ (s[0] == '\r' && s[1] == '\n') &&
+ (s[2] == ' ' || s[2] == '\t'))
+ {
+ s += 3;
+ goto SEARCH_NEWLINE;
+ }
+ /* If "\r " or "\r\t" or "\n " or "\n\t" is next, that doesn't terminate
+ the header either. */
+ else if (s+1 < end &&
+ (s[0] == '\r' || s[0] == '\n') &&
+ (s[1] == ' ' || s[1] == '\t'))
+ {
+ s += 2;
+ goto SEARCH_NEWLINE;
+ }
+
+ /* At this point, `s' points before a header-terminating newline.
+ Move past that newline, and store that new position in `heads'.
+ */
+ if (*s == '\r')
+ s++;
+
+ if (s >= end)
+ break;
+
+ if (*s == '\n')
+ s++;
+
+ if (s < end)
+ {
+ NS_ASSERTION(! (i > hdrs->heads_size), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (i > hdrs->heads_size)
+ return -1;
+ hdrs->heads[i++] = s;
+ }
+ }
+
+ return 0;
+}
+
+char *
+MimeHeaders_get (MimeHeaders *hdrs, const char *header_name,
+ bool strip_p, bool all_p)
+{
+ int i;
+ int name_length;
+ char *result = 0;
+
+ if (!hdrs) return 0;
+ NS_ASSERTION(header_name, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!header_name) return 0;
+
+ /* Specifying strip_p and all_p at the same time doesn't make sense... */
+ NS_ASSERTION(!(strip_p && all_p), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ /* One shouldn't be trying to read headers when one hasn't finished
+ parsing them yet... but this can happen if the message ended
+ prematurely, and has no body at all (as opposed to a null body,
+ which is more normal.) So, if we try to read from the headers,
+ let's assume that the headers are now finished. If they aren't
+ in fact finished, then a later attempt to write to them will assert.
+ */
+ if (!hdrs->done_p)
+ {
+ int status;
+ hdrs->done_p = true;
+ status = MimeHeaders_build_heads_list(hdrs);
+ if (status < 0) return 0;
+ }
+
+ if (!hdrs->heads) /* Must not have been any headers. */
+ {
+ NS_ASSERTION(hdrs->all_headers_fp == 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ return 0;
+ }
+
+ name_length = strlen(header_name);
+
+ for (i = 0; i < hdrs->heads_size; i++)
+ {
+ char *head = hdrs->heads[i];
+ char *end = (i == hdrs->heads_size-1
+ ? hdrs->all_headers + hdrs->all_headers_fp
+ : hdrs->heads[i+1]);
+ char *colon, *ocolon;
+
+ NS_ASSERTION(head, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!head) continue;
+
+ /* Quick hack to skip over BSD Mailbox delimiter. */
+ if (i == 0 && head[0] == 'F' && !strncmp(head, "From ", 5))
+ continue;
+
+ /* Find the colon. */
+ for (colon = head; colon < end; colon++)
+ if (*colon == ':') break;
+
+ if (colon >= end) continue;
+
+ /* Back up over whitespace before the colon. */
+ ocolon = colon;
+ for (; colon > head && IS_SPACE(colon[-1]); colon--)
+ ;
+
+ /* If the strings aren't the same length, it doesn't match. */
+ if (name_length != colon - head )
+ continue;
+
+ /* If the strings differ, it doesn't match. */
+ if (PL_strncasecmp(header_name, head, name_length))
+ continue;
+
+ /* Otherwise, we've got a match. */
+ {
+ char *contents = ocolon + 1;
+ char *s;
+
+ /* Skip over whitespace after colon. */
+ while (contents < end && IS_SPACE(contents[0])) {
+ /* Mac or Unix style line break, followed by space or tab. */
+ if (contents < (end - 1) &&
+ (contents[0] == '\r' || contents[0] == '\n') &&
+ (contents[1] == ' ' || contents[1] == '\t'))
+ contents += 2;
+ /* Windows style line break, followed by space or tab. */
+ else if (contents < (end - 2) &&
+ contents[0] == '\r' && contents[1] == '\n' &&
+ (contents[2] == ' ' || contents[2] == '\t'))
+ contents += 3;
+ /* Any space or tab. */
+ else if (contents[0] == ' ' || contents[0] == '\t')
+ contents++;
+ /* If we get here, it's because this character is a line break
+ followed by non-whitespace, or a line break followed by
+ another line break
+ */
+ else {
+ end = contents;
+ break;
+ }
+ }
+
+ /* If we're supposed to strip at the first token, pull `end' back to
+ the first whitespace or ';' after the first token.
+ */
+ if (strip_p)
+ {
+ for (s = contents;
+ s < end && *s != ';' && *s != ',' && !IS_SPACE(*s);
+ s++)
+ ;
+ end = s;
+ }
+
+ /* Now allocate some storage.
+ If `result' already has a value, enlarge it.
+ Otherwise, just allocate a block.
+ `s' gets set to the place where the new data goes.
+ */
+ if (!result)
+ {
+ result = (char *) PR_MALLOC(end - contents + 1);
+ if (!result)
+ return 0;
+ s = result;
+ }
+ else
+ {
+ int32_t L = strlen(result);
+ s = (char *) PR_Realloc(result, (L + (end - contents + 10)));
+ if (!s)
+ {
+ PR_Free(result);
+ return 0;
+ }
+ result = s;
+ s = result + L;
+
+ /* Since we are tacking more data onto the end of the header
+ field, we must make it be a well-formed continuation line,
+ by separating the old and new data with CR-LF-TAB.
+ */
+ *s++ = ','; /* #### only do this for addr headers? */
+ *s++ = MSG_LINEBREAK[0];
+# if (MSG_LINEBREAK_LEN == 2)
+ *s++ = MSG_LINEBREAK[1];
+# endif
+ *s++ = '\t';
+ }
+
+
+ /* Take off trailing whitespace... */
+ while (end > contents && IS_SPACE(end[-1]))
+ end--;
+
+ if (end > contents)
+ {
+ /* Now copy the header's contents in...
+ */
+ memcpy(s, contents, end - contents);
+ s[end - contents] = 0;
+ }
+ else
+ {
+ s[0] = 0;
+ }
+
+ /* If we only wanted the first occurence of this header, we're done. */
+ if (!all_p) break;
+ }
+ }
+
+ if (result && !*result) /* empty string */
+ {
+ PR_Free(result);
+ return 0;
+ }
+
+ return result;
+}
+
+char *
+MimeHeaders_get_parameter (const char *header_value, const char *parm_name,
+ char **charset, char **language)
+{
+ if (!header_value || !parm_name || !*header_value || !*parm_name)
+ return nullptr;
+
+ nsresult rv;
+ nsCOMPtr <nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ nsCString result;
+ rv = mimehdrpar->GetParameterInternal(header_value, parm_name, charset,
+ language, getter_Copies(result));
+ return NS_SUCCEEDED(rv) ? PL_strdup(result.get()) : nullptr;
+}
+
+#define MimeHeaders_write(HDRS,OPT,DATA,LENGTH) \
+ MimeOptions_write((HDRS), (OPT), (DATA), (LENGTH), true);
+
+
+#define MimeHeaders_grow_obuffer(hdrs, desired_size) \
+ ((((long) (desired_size)) >= ((long) (hdrs)->obuffer_size)) ? \
+ mime_GrowBuffer ((desired_size), sizeof(char), 255, \
+ &(hdrs)->obuffer, &(hdrs)->obuffer_size) \
+ : 0)
+
+int
+MimeHeaders_write_all_headers (MimeHeaders *hdrs, MimeDisplayOptions *opt, bool attachment)
+{
+ int status = 0;
+ int i;
+ bool wrote_any_p = false;
+
+ NS_ASSERTION(hdrs, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!hdrs)
+ return -1;
+
+ /* One shouldn't be trying to read headers when one hasn't finished
+ parsing them yet... but this can happen if the message ended
+ prematurely, and has no body at all (as opposed to a null body,
+ which is more normal.) So, if we try to read from the headers,
+ let's assume that the headers are now finished. If they aren't
+ in fact finished, then a later attempt to write to them will assert.
+ */
+ if (!hdrs->done_p)
+ {
+ hdrs->done_p = true;
+ status = MimeHeaders_build_heads_list(hdrs);
+ if (status < 0) return 0;
+ }
+
+ char *charset = nullptr;
+ if (opt->format_out == nsMimeOutput::nsMimeMessageSaveAs)
+ {
+ if (opt->override_charset)
+ charset = PL_strdup(opt->default_charset);
+ else
+ {
+ char *contentType = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false);
+ if (contentType)
+ charset = MimeHeaders_get_parameter(contentType, HEADER_PARM_CHARSET, nullptr, nullptr);
+ PR_FREEIF(contentType);
+ }
+ }
+
+ for (i = 0; i < hdrs->heads_size; i++)
+ {
+ char *head = hdrs->heads[i];
+ char *end = (i == hdrs->heads_size-1
+ ? hdrs->all_headers + hdrs->all_headers_fp
+ : hdrs->heads[i+1]);
+ char *colon, *ocolon;
+ char *contents = end;
+
+ /* Hack for BSD Mailbox delimiter. */
+ if (i == 0 && head[0] == 'F' && !strncmp(head, "From ", 5))
+ {
+ /* For now, we don't really want this header to be output so
+ we are going to just continue */
+ continue;
+ /* colon = head + 4; contents = colon + 1; */
+ }
+ else
+ {
+ /* Find the colon. */
+ for (colon = head; colon < end && *colon != ':'; colon++)
+ ;
+
+ /* Back up over whitespace before the colon. */
+ ocolon = colon;
+ for (; colon > head && IS_SPACE(colon[-1]); colon--)
+ ;
+
+ contents = ocolon + 1;
+ }
+
+ /* Skip over whitespace after colon. */
+ while (contents < end && IS_SPACE(*contents))
+ contents++;
+
+ /* Take off trailing whitespace... */
+ while (end > contents && IS_SPACE(end[-1]))
+ end--;
+
+ nsAutoCString name(Substring(head, colon));
+ nsAutoCString hdr_value;
+
+ if ( (end - contents) > 0 )
+ {
+ hdr_value = Substring(contents, end);
+ }
+
+ // MW Fixme: more?
+ bool convert_charset_only =
+ MsgLowerCaseEqualsLiteral(name, "to") || MsgLowerCaseEqualsLiteral(name, "from") ||
+ MsgLowerCaseEqualsLiteral(name, "cc") || MsgLowerCaseEqualsLiteral(name, "bcc") ||
+ MsgLowerCaseEqualsLiteral(name, "reply-to") || MsgLowerCaseEqualsLiteral(name, "sender");
+ MimeHeaders_convert_header_value(opt, hdr_value, convert_charset_only);
+ // if we're saving as html, we need to convert headers from utf8 to message charset, if any
+ if (opt->format_out == nsMimeOutput::nsMimeMessageSaveAs && charset)
+ {
+ nsAutoCString convertedStr;
+ if (NS_SUCCEEDED(ConvertFromUnicode(charset, NS_ConvertUTF8toUTF16(hdr_value),
+ convertedStr)))
+ {
+ hdr_value = convertedStr;
+ }
+ }
+
+ if (attachment) {
+ if (NS_FAILED(mimeEmitterAddAttachmentField(opt, name.get(), hdr_value.get())))
+ status = -1;
+ }
+ else {
+ if (NS_FAILED(mimeEmitterAddHeaderField(opt, name.get(), hdr_value.get())))
+ status = -1;
+ }
+
+ if (status < 0) return status;
+ if (!wrote_any_p)
+ wrote_any_p = (status > 0);
+ }
+ mimeEmitterAddAllHeaders(opt, hdrs->all_headers, hdrs->all_headers_fp);
+ PR_FREEIF(charset);
+
+ return 1;
+}
+
+/* Strip CR+LF runs within (original).
+ Since the string at (original) can only shrink,
+ this conversion is done in place. (original)
+ is returned. */
+extern "C" char *
+MIME_StripContinuations(char *original)
+{
+ char *p1, *p2;
+
+ /* If we were given a null string, return it as is */
+ if (!original) return NULL;
+
+ /* Start source and dest pointers at the beginning */
+ p1 = p2 = original;
+
+ while (*p2) {
+ /* p2 runs ahead at (CR and/or LF) */
+ if ((p2[0] == '\r') || (p2[0] == '\n'))
+ p2++;
+ else if (p2 > p1)
+ *p1++ = *p2++;
+ else {
+ p1++;
+ p2++;
+ }
+ }
+ *p1 = '\0';
+
+ return original;
+}
+
+extern int16_t INTL_DefaultMailToWinCharSetID(int16_t csid);
+
+/* Given text purporting to be a qtext header value, strip backslashes that
+ may be escaping other chars in the string. */
+char *
+mime_decode_filename(const char *name, const char *charset,
+ MimeDisplayOptions *opt)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv))
+ return nullptr;
+ nsAutoCString result;
+ rv = mimehdrpar->DecodeParameter(nsDependentCString(name), charset,
+ opt ? opt->default_charset : nullptr,
+ opt ? opt->override_charset : false,
+ result);
+ return NS_SUCCEEDED(rv) ? PL_strdup(result.get()) : nullptr;
+}
+
+/* Pull the name out of some header or another. Order is:
+ Content-Disposition: XXX; filename=NAME (RFC 1521/1806)
+ Content-Type: XXX; name=NAME (RFC 1341)
+ Content-Name: NAME (no RFC, but seen to occur)
+ X-Sun-Data-Name: NAME (no RFC, but used by MailTool)
+ */
+char *
+MimeHeaders_get_name(MimeHeaders *hdrs, MimeDisplayOptions *opt)
+{
+ char *s = 0, *name = 0, *cvt = 0;
+ char *charset = nullptr; // for RFC2231 support
+
+ s = MimeHeaders_get(hdrs, HEADER_CONTENT_DISPOSITION, false, false);
+ if (s)
+ {
+ name = MimeHeaders_get_parameter(s, HEADER_PARM_FILENAME, &charset, NULL);
+ PR_Free(s);
+ }
+
+ if (! name)
+ {
+ s = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false);
+ if (s)
+ {
+ free(charset);
+
+ name = MimeHeaders_get_parameter(s, HEADER_PARM_NAME, &charset, NULL);
+ PR_Free(s);
+ }
+ }
+
+ if (! name)
+ name = MimeHeaders_get (hdrs, HEADER_CONTENT_NAME, false, false);
+
+ if (! name)
+ name = MimeHeaders_get (hdrs, HEADER_X_SUN_DATA_NAME, false, false);
+
+ if (name)
+ {
+ /* First remove continuation delimiters (CR+LF+space), then
+ remove escape ('\\') characters, then attempt to decode
+ mime-2 encoded-words. The latter two are done in
+ mime_decode_filename.
+ */
+ MIME_StripContinuations(name);
+
+ /* Argh. What we should do if we want to be robust is to decode qtext
+ in all appropriate headers. Unfortunately, that would be too scary
+ at this juncture. So just decode qtext/mime2 here. */
+ cvt = mime_decode_filename(name, charset, opt);
+
+ free(charset);
+
+ if (cvt && cvt != name)
+ {
+ PR_Free(name);
+ name = cvt;
+ }
+ }
+
+ return name;
+}
+
+#ifdef XP_UNIX
+/* This piece of junk is so that I can use BBDB with Mozilla.
+ = Put bbdb-srv.perl on your path.
+ = Put bbdb-srv.el on your lisp path.
+ = Make sure gnudoit (comes with xemacs) is on your path.
+ = Put (gnuserv-start) in ~/.emacs
+ = setenv NS_MSG_DISPLAY_HOOK bbdb-srv.perl
+ */
+void
+MimeHeaders_do_unix_display_hook_hack(MimeHeaders *hdrs)
+{
+ static const char *cmd = 0;
+ if (!cmd)
+ {
+ /* The first time we're invoked, look up the command in the
+ environment. Use "" as the `no command' tag. */
+ cmd = getenv("NS_MSG_DISPLAY_HOOK");
+ if (!cmd)
+ cmd = "";
+ }
+
+ /* Invoke "cmd" at the end of a pipe, and give it the headers on stdin.
+ The command is expected to be safe from hostile input!!
+ */
+ if (cmd && *cmd)
+ {
+ FILE *fp = popen(cmd, "w");
+ if (fp)
+ {
+ fwrite(hdrs->all_headers, 1, hdrs->all_headers_fp, fp);
+ pclose(fp);
+ }
+ }
+}
+#endif /* XP_UNIX */
+
+static void
+MimeHeaders_compact (MimeHeaders *hdrs)
+{
+ NS_ASSERTION(hdrs, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48");
+ if (!hdrs) return;
+
+ PR_FREEIF(hdrs->obuffer);
+ hdrs->obuffer_fp = 0;
+ hdrs->obuffer_size = 0;
+
+ /* These really shouldn't have gotten out of whack again. */
+ NS_ASSERTION(hdrs->all_headers_fp <= hdrs->all_headers_size &&
+ hdrs->all_headers_fp + 100 > hdrs->all_headers_size, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48");
+}
+
+/* Writes the headers as text/plain.
+ This writes out a blank line after the headers, unless
+ dont_write_content_type is true, in which case the header-block
+ is not closed off, and none of the Content- headers are written.
+ */
+int
+MimeHeaders_write_raw_headers (MimeHeaders *hdrs, MimeDisplayOptions *opt,
+ bool dont_write_content_type)
+{
+ int status;
+
+ if (hdrs && !hdrs->done_p)
+ {
+ hdrs->done_p = true;
+ status = MimeHeaders_build_heads_list(hdrs);
+ if (status < 0) return 0;
+ }
+
+ if (!dont_write_content_type)
+ {
+ char nl[] = MSG_LINEBREAK;
+ if (hdrs)
+ {
+ status = MimeHeaders_write(hdrs, opt, hdrs->all_headers,
+ hdrs->all_headers_fp);
+ if (status < 0) return status;
+ }
+ status = MimeHeaders_write(hdrs, opt, nl, strlen(nl));
+ if (status < 0) return status;
+ }
+ else if (hdrs)
+ {
+ int32_t i;
+ for (i = 0; i < hdrs->heads_size; i++)
+ {
+ char *head = hdrs->heads[i];
+ char *end = (i == hdrs->heads_size-1
+ ? hdrs->all_headers + hdrs->all_headers_fp
+ : hdrs->heads[i+1]);
+
+ NS_ASSERTION(head, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48");
+ if (!head) continue;
+
+ /* Don't write out any Content- header. */
+ if (!PL_strncasecmp(head, "Content-", 8))
+ continue;
+
+ /* Write out this (possibly multi-line) header. */
+ status = MimeHeaders_write(hdrs, opt, head, end - head);
+ if (status < 0) return status;
+ }
+ }
+
+ if (hdrs)
+ MimeHeaders_compact(hdrs);
+
+ return 0;
+}
+
+// XXX Fix this XXX //
+char *
+MimeHeaders_open_crypto_stamp(void)
+{
+ return nullptr;
+}
+
+char *
+MimeHeaders_finish_open_crypto_stamp(void)
+{
+ return nullptr;
+}
+
+char *
+MimeHeaders_close_crypto_stamp(void)
+{
+ return nullptr;
+}
+
+char *
+MimeHeaders_make_crypto_stamp(bool encrypted_p,
+ bool signed_p,
+ bool good_p,
+ bool unverified_p,
+ bool close_parent_stamp_p,
+ const char *stamp_url)
+{
+ return nullptr;
+}
diff --git a/mailnews/mime/src/mimehdrs.h b/mailnews/mime/src/mimehdrs.h
new file mode 100644
index 000000000..e854633af
--- /dev/null
+++ b/mailnews/mime/src/mimehdrs.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEHDRS_H_
+#define _MIMEHDRS_H_
+
+#include "modlmime.h"
+
+/* This file defines the interface to message-header parsing and formatting
+ code, including conversion to HTML. */
+
+/* Other structs defined later in this file.
+ */
+
+/* Creation and destruction.
+ */
+extern MimeHeaders *MimeHeaders_new (void);
+//extern void MimeHeaders_free (MimeHeaders *);
+//extern MimeHeaders *MimeHeaders_copy (MimeHeaders *);
+
+
+/* Feed this method the raw data from which you would like a header
+ block to be parsed, one line at a time. Feed it a blank line when
+ you're done. Returns negative on allocation-related failure.
+ */
+extern int MimeHeaders_parse_line (const char *buffer, int32_t size,
+ MimeHeaders *hdrs);
+
+
+/* Converts a MimeHeaders object into HTML, by writing to the provided
+ output function.
+ */
+extern int MimeHeaders_write_headers_html (MimeHeaders *hdrs,
+ MimeDisplayOptions *opt,
+ bool attachment);
+
+/*
+ * Writes all headers to the mime emitter.
+ */
+extern int
+MimeHeaders_write_all_headers (MimeHeaders *, MimeDisplayOptions *, bool);
+
+/* Writes the headers as text/plain.
+ This writes out a blank line after the headers, unless
+ dont_write_content_type is true, in which case the header-block
+ is not closed off, and none of the Content- headers are written.
+ */
+extern int MimeHeaders_write_raw_headers (MimeHeaders *hdrs,
+ MimeDisplayOptions *opt,
+ bool dont_write_content_type);
+
+
+/* Some crypto-related HTML-generated utility routines.
+ * XXX This may not be needed. XXX
+ */
+extern char *MimeHeaders_open_crypto_stamp(void);
+extern char *MimeHeaders_finish_open_crypto_stamp(void);
+extern char *MimeHeaders_close_crypto_stamp(void);
+extern char *MimeHeaders_make_crypto_stamp(bool encrypted_p,
+
+ bool signed_p,
+
+ bool good_p,
+
+ bool unverified_p,
+
+ bool close_parent_stamp_p,
+
+ const char *stamp_url);
+
+/* Does all the heuristic silliness to find the filename in the given headers.
+ */
+extern char *MimeHeaders_get_name(MimeHeaders *hdrs, MimeDisplayOptions *opt);
+
+extern char *mime_decode_filename(const char *name, const char* charset,
+ MimeDisplayOptions *opt);
+
+extern "C" char * MIME_StripContinuations(char *original);
+
+/**
+ * Convert this value to a unicode string, based on the charset.
+ */
+extern void MimeHeaders_convert_header_value(MimeDisplayOptions *opt,
+ nsCString &value,
+ bool convert_charset_only);
+#endif /* _MIMEHDRS_H_ */
diff --git a/mailnews/mime/src/mimei.cpp b/mailnews/mime/src/mimei.cpp
new file mode 100644
index 000000000..015830a80
--- /dev/null
+++ b/mailnews/mime/src/mimei.cpp
@@ -0,0 +1,1910 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+#include "nsCOMPtr.h"
+#include "mimeobj.h" /* MimeObject (abstract) */
+#include "mimecont.h" /* |--- MimeContainer (abstract) */
+#include "mimemult.h" /* | |--- MimeMultipart (abstract) */
+#include "mimemmix.h" /* | | |--- MimeMultipartMixed */
+#include "mimemdig.h" /* | | |--- MimeMultipartDigest */
+#include "mimempar.h" /* | | |--- MimeMultipartParallel */
+#include "mimemalt.h" /* | | |--- MimeMultipartAlternative */
+#include "mimemrel.h" /* | | |--- MimeMultipartRelated */
+#include "mimemapl.h" /* | | |--- MimeMultipartAppleDouble */
+#include "mimesun.h" /* | | |--- MimeSunAttachment */
+#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/
+#ifdef ENABLE_SMIME
+#include "mimemcms.h" /* | | |---MimeMultipartSignedCMS */
+#endif
+#include "mimecryp.h" /* | |--- MimeEncrypted (abstract) */
+#ifdef ENABLE_SMIME
+#include "mimecms.h" /* | | |--- MimeEncryptedPKCS7 */
+#endif
+#include "mimemsg.h" /* | |--- MimeMessage */
+#include "mimeunty.h" /* | |--- MimeUntypedText */
+#include "mimeleaf.h" /* |--- MimeLeaf (abstract) */
+#include "mimetext.h" /* | |--- MimeInlineText (abstract) */
+#include "mimetpla.h" /* | | |--- MimeInlineTextPlain */
+#include "mimethpl.h" /* | | | |--- M.I.TextHTMLAsPlaintext */
+#include "mimetpfl.h" /* | | |--- MimeInlineTextPlainFlowed */
+#include "mimethtm.h" /* | | |--- MimeInlineTextHTML */
+#include "mimethsa.h" /* | | | |--- M.I.TextHTMLSanitized */
+#include "mimeTextHTMLParsed.h" /*| | |--- M.I.TextHTMLParsed */
+#include "mimetric.h" /* | | |--- MimeInlineTextRichtext */
+#include "mimetenr.h" /* | | | |--- MimeInlineTextEnriched */
+/* SUPPORTED VIA PLUGIN | | |--- MimeInlineTextVCard */
+#include "mimeiimg.h" /* | |--- MimeInlineImage */
+#include "mimeeobj.h" /* | |--- MimeExternalObject */
+#include "mimeebod.h" /* |--- MimeExternalBody */
+ /* If you add classes here,also add them to mimei.h */
+#include "prlog.h"
+#include "prmem.h"
+#include "prenv.h"
+#include "plstr.h"
+#include "prlink.h"
+#include "prprf.h"
+#include "mimecth.h"
+#include "mimebuf.h"
+#include "nsIServiceManager.h"
+#include "mimemoz2.h"
+#include "nsIMimeContentTypeHandler.h"
+#include "nsIComponentManager.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsXPCOMCID.h"
+#include "nsISimpleMimeConverter.h"
+#include "nsSimpleMimeConverterStub.h"
+#include "nsTArray.h"
+#include "nsMimeStringResources.h"
+#include "nsMimeTypes.h"
+#include "nsMsgUtils.h"
+#include "nsIPrefBranch.h"
+#include "mozilla/Preferences.h"
+#include "imgLoader.h"
+
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgHdr.h"
+
+using namespace mozilla;
+
+// forward declaration
+void getMsgHdrForCurrentURL(MimeDisplayOptions *opts, nsIMsgDBHdr ** aMsgHdr);
+
+#define IMAP_EXTERNAL_CONTENT_HEADER "X-Mozilla-IMAP-Part"
+#define EXTERNAL_ATTACHMENT_URL_HEADER "X-Mozilla-External-Attachment-URL"
+
+/* ==========================================================================
+ Allocation and destruction
+ ==========================================================================
+ */
+static int mime_classinit(MimeObjectClass *clazz);
+
+/*
+ * These are the necessary defines/variables for doing
+ * content type handlers in external plugins.
+ */
+typedef struct {
+ char content_type[128];
+ bool force_inline_display;
+} cthandler_struct;
+
+nsTArray<cthandler_struct*> *ctHandlerList = NULL;
+
+/*
+ * This will return TRUE if the content_type is found in the
+ * list, FALSE if it is not found.
+ */
+bool
+find_content_type_attribs(const char *content_type,
+ bool *force_inline_display)
+{
+ *force_inline_display = false;
+ if (!ctHandlerList)
+ return false;
+
+ for (size_t i = 0; i < ctHandlerList->Length(); i++)
+ {
+ cthandler_struct *ptr = ctHandlerList->ElementAt(i);
+ if (PL_strcasecmp(content_type, ptr->content_type) == 0)
+ {
+ *force_inline_display = ptr->force_inline_display;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+add_content_type_attribs(const char *content_type,
+ contentTypeHandlerInitStruct *ctHandlerInfo)
+{
+ cthandler_struct *ptr = NULL;
+ bool force_inline_display;
+
+ if (find_content_type_attribs(content_type, &force_inline_display))
+ return;
+
+ if ( (!content_type) || (!ctHandlerInfo) )
+ return;
+
+ if (!ctHandlerList)
+ ctHandlerList = new nsTArray<cthandler_struct*>();
+
+ if (!ctHandlerList)
+ return;
+
+ ptr = (cthandler_struct *) PR_MALLOC(sizeof(cthandler_struct));
+ if (!ptr)
+ return;
+
+ PL_strncpy(ptr->content_type, content_type, sizeof(ptr->content_type));
+ ptr->force_inline_display = ctHandlerInfo->force_inline_display;
+ ctHandlerList->AppendElement(ptr);
+}
+
+/*
+ * This routine will find all content type handler for a specifc content
+ * type (if it exists)
+ */
+bool
+force_inline_display(const char *content_type)
+{
+ bool force_inline_disp;
+
+ find_content_type_attribs(content_type, &force_inline_disp);
+ return (force_inline_disp);
+}
+
+/*
+ * This routine will find all content type handler for a specifc content
+ * type (if it exists) and is defined to the nsRegistry
+ */
+MimeObjectClass *
+mime_locate_external_content_handler(const char *content_type,
+ contentTypeHandlerInitStruct *ctHandlerInfo)
+{
+ if (!content_type || !*(content_type)) // null or empty content type
+ return nullptr;
+
+ MimeObjectClass *newObj = NULL;
+ nsresult rv;
+
+ nsAutoCString lookupID("@mozilla.org/mimecth;1?type=");
+ nsAutoCString contentType;
+ ToLowerCase(nsDependentCString(content_type), contentType);
+ lookupID += contentType;
+
+ nsCOMPtr<nsIMimeContentTypeHandler> ctHandler = do_CreateInstance(lookupID.get(), &rv);
+ if (NS_FAILED(rv) || !ctHandler) {
+ nsCOMPtr<nsICategoryManager> catman =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ nsCString value;
+ rv = catman->GetCategoryEntry(NS_SIMPLEMIMECONVERTERS_CATEGORY,
+ contentType.get(), getter_Copies(value));
+ if (NS_FAILED(rv) || value.IsEmpty())
+ return nullptr;
+ rv = MIME_NewSimpleMimeConverterStub(contentType.get(),
+ getter_AddRefs(ctHandler));
+ if (NS_FAILED(rv) || !ctHandler)
+ return nullptr;
+ }
+
+ rv = ctHandler->CreateContentTypeHandlerClass(contentType.get(), ctHandlerInfo, &newObj);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ add_content_type_attribs(contentType.get(), ctHandlerInfo);
+ return newObj;
+}
+
+/* This is necessary to expose the MimeObject method outside of this DLL */
+int
+MIME_MimeObject_write(MimeObject *obj, const char *output, int32_t length, bool user_visible_p)
+{
+ return MimeObject_write(obj, output, length, user_visible_p);
+}
+
+MimeObject *
+mime_new (MimeObjectClass *clazz, MimeHeaders *hdrs,
+ const char *override_content_type)
+{
+ int size = clazz->instance_size;
+ MimeObject *object;
+ int status;
+
+ /* Some assertions to verify that this isn't random junk memory... */
+ NS_ASSERTION(clazz->class_name && strlen(clazz->class_name) > 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ NS_ASSERTION(size > 0 && size < 1000, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ if (!clazz->class_initialized)
+ {
+ status = mime_classinit(clazz);
+ if (status < 0) return 0;
+ }
+
+ NS_ASSERTION(clazz->initialize && clazz->finalize, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ if (hdrs)
+ {
+ hdrs = MimeHeaders_copy (hdrs);
+ if (!hdrs) return 0;
+ }
+
+ object = (MimeObject *) PR_MALLOC(size);
+ if (!object) return 0;
+
+ memset(object, 0, size);
+ object->clazz = clazz;
+ object->headers = hdrs;
+ object->dontShowAsAttachment = false;
+
+ if (override_content_type && *override_content_type)
+ object->content_type = strdup(override_content_type);
+
+ status = clazz->initialize(object);
+ if (status < 0)
+ {
+ clazz->finalize(object);
+ PR_Free(object);
+ return 0;
+ }
+
+ return object;
+}
+
+void
+mime_free (MimeObject *object)
+{
+# ifdef DEBUG__
+ int i, size = object->clazz->instance_size;
+ uint32_t *array = (uint32_t*) object;
+# endif /* DEBUG */
+
+ object->clazz->finalize(object);
+
+# ifdef DEBUG__
+ for (i = 0; i < (size / sizeof(*array)); i++)
+ array[i] = (uint32_t) 0xDEADBEEF;
+# endif /* DEBUG */
+
+ PR_Free(object);
+}
+
+
+bool mime_is_allowed_class(const MimeObjectClass *clazz,
+ int32_t types_of_classes_to_disallow)
+{
+ if (types_of_classes_to_disallow == 0)
+ return true;
+ bool avoid_html = (types_of_classes_to_disallow >= 1);
+ bool avoid_images = (types_of_classes_to_disallow >= 2);
+ bool avoid_strange_content = (types_of_classes_to_disallow >= 3);
+ bool allow_only_vanilla_classes = (types_of_classes_to_disallow == 100);
+
+ if (allow_only_vanilla_classes)
+ /* A "safe" class is one that is unlikely to have security bugs or to
+ allow security exploits or one that is essential for the usefulness
+ of the application, even for paranoid users.
+ What's included here is more personal judgement than following
+ strict rules, though, unfortunately.
+ The function returns true only for known good classes, i.e. is a
+ "whitelist" in this case.
+ This idea comes from Georgi Guninski.
+ */
+ return
+ (
+ clazz == (MimeObjectClass *)&mimeInlineTextPlainClass ||
+ clazz == (MimeObjectClass *)&mimeInlineTextPlainFlowedClass ||
+ clazz == (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass ||
+ clazz == (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass ||
+ /* The latter 2 classes bear some risk, because they use the Gecko
+ HTML parser, but the user has the option to make an explicit
+ choice in this case, via html_as. */
+ clazz == (MimeObjectClass *)&mimeMultipartMixedClass ||
+ clazz == (MimeObjectClass *)&mimeMultipartAlternativeClass ||
+ clazz == (MimeObjectClass *)&mimeMultipartDigestClass ||
+ clazz == (MimeObjectClass *)&mimeMultipartAppleDoubleClass ||
+ clazz == (MimeObjectClass *)&mimeMessageClass ||
+ clazz == (MimeObjectClass *)&mimeExternalObjectClass ||
+ /* mimeUntypedTextClass? -- does uuencode */
+#ifdef ENABLE_SMIME
+ clazz == (MimeObjectClass *)&mimeMultipartSignedCMSClass ||
+ clazz == (MimeObjectClass *)&mimeEncryptedCMSClass ||
+#endif
+ clazz == 0
+ );
+
+ /* Contrairy to above, the below code is a "blacklist", i.e. it
+ *excludes* some "bad" classes. */
+ return
+ !(
+ (avoid_html
+ && (
+ clazz == (MimeObjectClass *)&mimeInlineTextHTMLParsedClass
+ /* Should not happen - we protect against that in
+ mime_find_class(). Still for safety... */
+ )) ||
+ (avoid_images
+ && (
+ clazz == (MimeObjectClass *)&mimeInlineImageClass
+ )) ||
+ (avoid_strange_content
+ && (
+ clazz == (MimeObjectClass *)&mimeInlineTextEnrichedClass ||
+ clazz == (MimeObjectClass *)&mimeInlineTextRichtextClass ||
+ clazz == (MimeObjectClass *)&mimeSunAttachmentClass ||
+ clazz == (MimeObjectClass *)&mimeExternalBodyClass
+ ))
+ );
+}
+
+void getMsgHdrForCurrentURL(MimeDisplayOptions *opts, nsIMsgDBHdr ** aMsgHdr)
+{
+ *aMsgHdr = nullptr;
+
+ if (!opts)
+ return;
+
+ mime_stream_data *msd = (mime_stream_data *) (opts->stream_closure);
+ if (!msd)
+ return;
+
+ nsCOMPtr<nsIChannel> channel = msd->channel; // note the lack of ref counting...
+ if (channel)
+ {
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIMsgMessageUrl> msgURI;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri)
+ {
+ msgURI = do_QueryInterface(uri);
+ if (msgURI)
+ {
+ msgURI->GetMessageHeader(aMsgHdr);
+ if (*aMsgHdr)
+ return;
+ nsCString rdfURI;
+ msgURI->GetUri(getter_Copies(rdfURI));
+ if (!rdfURI.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ GetMsgDBHdrFromURI(rdfURI.get(), getter_AddRefs(msgHdr));
+ NS_IF_ADDREF(*aMsgHdr = msgHdr);
+ }
+ }
+ }
+ }
+
+ return;
+}
+
+MimeObjectClass *
+mime_find_class (const char *content_type, MimeHeaders *hdrs,
+ MimeDisplayOptions *opts, bool exact_match_p)
+{
+ MimeObjectClass *clazz = 0;
+ MimeObjectClass *tempClass = 0;
+ contentTypeHandlerInitStruct ctHandlerInfo;
+
+ // Read some prefs
+ nsIPrefBranch *prefBranch = GetPrefBranch(opts);
+ int32_t html_as = 0; // def. see below
+ int32_t types_of_classes_to_disallow = 0; /* Let only a few libmime classes
+ process incoming data. This protects from bugs (e.g. buffer overflows)
+ and from security loopholes (e.g. allowing unchecked HTML in some
+ obscure classes, although the user has html_as > 0).
+ This option is mainly for the UI of html_as.
+ 0 = allow all available classes
+ 1 = Use hardcoded blacklist to avoid rendering (incoming) HTML
+ 2 = ... and images
+ 3 = ... and some other uncommon content types
+ 4 = show all body parts
+ 100 = Use hardcoded whitelist to avoid even more bugs(buffer overflows).
+ This mode will limit the features available (e.g. uncommon
+ attachment types and inline images) and is for paranoid users.
+ */
+ if (opts && opts->format_out != nsMimeOutput::nsMimeMessageFilterSniffer &&
+ opts->format_out != nsMimeOutput::nsMimeMessageDecrypt
+ && opts->format_out != nsMimeOutput::nsMimeMessageAttach)
+ if (prefBranch)
+ {
+ prefBranch->GetIntPref("mailnews.display.html_as", &html_as);
+ prefBranch->GetIntPref("mailnews.display.disallow_mime_handlers",
+ &types_of_classes_to_disallow);
+ if (types_of_classes_to_disallow > 0 && html_as == 0)
+ // We have non-sensical prefs. Do some fixup.
+ html_as = 1;
+ }
+
+ // First, check to see if the message has been marked as JUNK. If it has,
+ // then force the message to be rendered as simple, unless this has been
+ // called by a filtering routine.
+ bool sanitizeJunkMail = false;
+
+ // it is faster to read the pref first then figure out the msg hdr for the current url only if we have to
+ // XXX instead of reading this pref every time, part of mime should be an observer listening to this pref change
+ // and updating internal state accordingly. But none of the other prefs in this file seem to be doing that...=(
+ if (prefBranch)
+ prefBranch->GetBoolPref("mail.spam.display.sanitize", &sanitizeJunkMail);
+
+ if (sanitizeJunkMail &&
+ !(opts && opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer))
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ getMsgHdrForCurrentURL(opts, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ nsCString junkScoreStr;
+ (void) msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
+ if (html_as == 0 && junkScoreStr.get() && atoi(junkScoreStr.get()) > 50)
+ html_as = 3; // 3 == Simple HTML
+ } // if msgHdr
+ } // if we are supposed to sanitize junk mail
+
+ /*
+ * What we do first is check for an external content handler plugin.
+ * This will actually extend the mime handling by calling a routine
+ * which will allow us to load an external content type handler
+ * for specific content types. If one is not found, we will drop back
+ * to the default handler.
+ */
+ if ((tempClass = mime_locate_external_content_handler(content_type, &ctHandlerInfo)) != NULL)
+ {
+#ifdef MOZ_THUNDERBIRD
+ // This is a case where we only want to add this property if we are a thunderbird build AND
+ // we have found an external mime content handler for text/calendar
+ // This will enable iMIP support in Lightning
+ if ( hdrs && (!PL_strncasecmp(content_type, "text/calendar", 13)))
+ {
+ char *full_content_type = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false);
+ if (full_content_type)
+ {
+ char *imip_method = MimeHeaders_get_parameter(full_content_type, "method", NULL, NULL);
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ getMsgHdrForCurrentURL(opts, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ msgHdr->SetStringProperty("imip_method", (imip_method) ? imip_method : "nomethod");
+ // PR_Free checks for null
+ PR_Free(imip_method);
+ PR_Free(full_content_type);
+ }
+ }
+#endif
+
+ if (types_of_classes_to_disallow > 0
+ && (!PL_strncasecmp(content_type, "text/x-vcard", 12))
+ )
+ /* Use a little hack to prevent some dangerous plugins, which ship
+ with Mozilla, to run.
+ For the truely user-installed plugins, we rely on the judgement
+ of the user. */
+ {
+ if (!exact_match_p)
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass; // As attachment
+ }
+ else
+ clazz = (MimeObjectClass *)tempClass;
+ }
+ else
+ {
+ if (!content_type || !*content_type ||
+ !PL_strcasecmp(content_type, "text")) /* with no / in the type */
+ clazz = (MimeObjectClass *)&mimeUntypedTextClass;
+
+ /* Subtypes of text...
+ */
+ else if (!PL_strncasecmp(content_type, "text/", 5))
+ {
+ if (!PL_strcasecmp(content_type+5, "html"))
+ {
+ if (opts &&
+ (opts->format_out == nsMimeOutput::nsMimeMessageSaveAs ||
+ opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer ||
+ opts->format_out == nsMimeOutput::nsMimeMessageDecrypt ||
+ opts->format_out == nsMimeOutput::nsMimeMessageAttach))
+ // SaveAs in new modes doesn't work yet.
+ {
+ // Don't use the parsed HTML class if we're ...
+ // - saving the HTML of a message
+ // - getting message content for filtering
+ // - snarfing attachments (nsMimeMessageDecrypt used in SnarfMsgAttachment)
+ // - processing attachments (like deleting attachments).
+ clazz = (MimeObjectClass *)&mimeInlineTextHTMLClass;
+ types_of_classes_to_disallow = 0;
+ }
+ else if (html_as == 0 || html_as == 4) // Render sender's HTML
+ clazz = (MimeObjectClass *)&mimeInlineTextHTMLParsedClass;
+ else if (html_as == 1) // convert HTML to plaintext
+ // Do a HTML->TXT->HTML conversion, see mimethpl.h.
+ clazz = (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass;
+ else if (html_as == 2) // display HTML source
+ /* This is for the freaks. Treat HTML as plaintext,
+ which will cause the HTML source to be displayed.
+ Not very user-friendly, but some seem to want this. */
+ clazz = (MimeObjectClass *)&mimeInlineTextPlainClass;
+ else if (html_as == 3) // Sanitize
+ // Strip all but allowed HTML
+ clazz = (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass;
+ else // Goofy pref
+ /* User has an unknown pref value. Maybe he used a newer Mozilla
+ with a new alternative to avoid HTML. Defaulting to option 1,
+ which is less dangerous than defaulting to the raw HTML. */
+ clazz = (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass;
+ }
+ else if (!PL_strcasecmp(content_type+5, "enriched"))
+ clazz = (MimeObjectClass *)&mimeInlineTextEnrichedClass;
+ else if (!PL_strcasecmp(content_type+5, "richtext"))
+ clazz = (MimeObjectClass *)&mimeInlineTextRichtextClass;
+ else if (!PL_strcasecmp(content_type+5, "rtf"))
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ else if (!PL_strcasecmp(content_type+5, "plain"))
+ {
+ // Preliminary use the normal plain text
+ clazz = (MimeObjectClass *)&mimeInlineTextPlainClass;
+
+ if (opts && opts->format_out != nsMimeOutput::nsMimeMessageFilterSniffer
+ && opts->format_out != nsMimeOutput::nsMimeMessageAttach
+ && opts->format_out != nsMimeOutput::nsMimeMessageRaw)
+ {
+ bool disable_format_flowed = false;
+ if (prefBranch)
+ prefBranch->GetBoolPref("mailnews.display.disable_format_flowed_support",
+ &disable_format_flowed);
+
+ if(!disable_format_flowed)
+ {
+ // Check for format=flowed, damn, it is already stripped away from
+ // the contenttype!
+ // Look in headers instead even though it's expensive and clumsy
+ // First find Content-Type:
+ char *content_type_row =
+ (hdrs
+ ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE,
+ false, false)
+ : 0);
+ // Then the format parameter if there is one.
+ // I would rather use a PARAM_FORMAT but I can't find the right
+ // place to put the define. The others seems to be in net.h
+ // but is that really really the right place? There is also
+ // a nsMimeTypes.h but that one isn't included. Bug?
+ char *content_type_format =
+ (content_type_row
+ ? MimeHeaders_get_parameter(content_type_row, "format", NULL,NULL)
+ : 0);
+
+ if (content_type_format && !PL_strcasecmp(content_type_format,
+ "flowed"))
+ clazz = (MimeObjectClass *)&mimeInlineTextPlainFlowedClass;
+ PR_FREEIF(content_type_format);
+ PR_FREEIF(content_type_row);
+ }
+ }
+ }
+ else if (!exact_match_p)
+ clazz = (MimeObjectClass *)&mimeInlineTextPlainClass;
+ }
+
+ /* Subtypes of multipart...
+ */
+ else if (!PL_strncasecmp(content_type, "multipart/", 10))
+ {
+ // When html_as is 4, we want all MIME parts of the message to
+ // show up in the displayed message body, if they are MIME types
+ // that we know how to display, and also in the attachment pane
+ // if it's appropriate to put them there. Both
+ // multipart/alternative and multipart/related play games with
+ // hiding various MIME parts, and we don't want that to happen,
+ // so we prevent that by parsing those MIME types as
+ // multipart/mixed, which won't mess with anything.
+ //
+ // When our output format is nsMimeOutput::nsMimeMessageAttach,
+ // i.e., we are reformatting the message to remove attachments,
+ // we are in a similar boat. The code for deleting
+ // attachments properly in that mode is in mimemult.cpp
+ // functions which are inherited by mimeMultipartMixedClass but
+ // not by mimeMultipartAlternativeClass or
+ // mimeMultipartRelatedClass. Therefore, to ensure that
+ // everything is handled properly, in this context too we parse
+ // those MIME types as multipart/mixed.
+ bool basic_formatting = (html_as == 4) ||
+ (opts && opts->format_out == nsMimeOutput::nsMimeMessageAttach);
+ if (!PL_strcasecmp(content_type+10, "alternative"))
+ clazz = basic_formatting ? (MimeObjectClass *)&mimeMultipartMixedClass :
+ (MimeObjectClass *)&mimeMultipartAlternativeClass;
+ else if (!PL_strcasecmp(content_type+10, "related"))
+ clazz = basic_formatting ? (MimeObjectClass *)&mimeMultipartMixedClass :
+ (MimeObjectClass *)&mimeMultipartRelatedClass;
+ else if (!PL_strcasecmp(content_type+10, "digest"))
+ clazz = (MimeObjectClass *)&mimeMultipartDigestClass;
+ else if (!PL_strcasecmp(content_type+10, "appledouble") ||
+ !PL_strcasecmp(content_type+10, "header-set"))
+ clazz = (MimeObjectClass *)&mimeMultipartAppleDoubleClass;
+ else if (!PL_strcasecmp(content_type+10, "parallel"))
+ clazz = (MimeObjectClass *)&mimeMultipartParallelClass;
+ else if (!PL_strcasecmp(content_type+10, "mixed"))
+ clazz = (MimeObjectClass *)&mimeMultipartMixedClass;
+#ifdef ENABLE_SMIME
+ else if (!PL_strcasecmp(content_type+10, "signed"))
+ {
+ /* Check that the "protocol" and "micalg" parameters are ones we
+ know about. */
+ char *ct = (hdrs
+ ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE,
+ false, false)
+ : 0);
+ char *proto = (ct
+ ? MimeHeaders_get_parameter(ct, PARAM_PROTOCOL, NULL, NULL)
+ : 0);
+ char *micalg = (ct
+ ? MimeHeaders_get_parameter(ct, PARAM_MICALG, NULL, NULL)
+ : 0);
+
+ if (proto
+ && (
+ (/* is a signature */
+ !PL_strcasecmp(proto, APPLICATION_XPKCS7_SIGNATURE)
+ ||
+ !PL_strcasecmp(proto, APPLICATION_PKCS7_SIGNATURE))
+ && micalg
+ && (!PL_strcasecmp(micalg, PARAM_MICALG_MD5) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_MD5_2) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA1) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_2) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_3) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_4) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_5) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA256) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_2) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_3) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA384) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_2) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_3) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA512) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_2) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_3) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_MD2))))
+ clazz = (MimeObjectClass *)&mimeMultipartSignedCMSClass;
+ else
+ clazz = 0;
+ PR_FREEIF(proto);
+ PR_FREEIF(micalg);
+ PR_FREEIF(ct);
+ }
+#endif
+
+ if (!clazz && !exact_match_p)
+ /* Treat all unknown multipart subtypes as "multipart/mixed" */
+ clazz = (MimeObjectClass *)&mimeMultipartMixedClass;
+
+ /* If we are sniffing a message, let's treat alternative parts as mixed */
+ if (opts && opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer)
+ if (clazz == (MimeObjectClass *)&mimeMultipartAlternativeClass)
+ clazz = (MimeObjectClass *)&mimeMultipartMixedClass;
+ }
+
+ /* Subtypes of message...
+ */
+ else if (!PL_strncasecmp(content_type, "message/", 8))
+ {
+ if (!PL_strcasecmp(content_type+8, "rfc822") ||
+ !PL_strcasecmp(content_type+8, "news"))
+ clazz = (MimeObjectClass *)&mimeMessageClass;
+ else if (!PL_strcasecmp(content_type+8, "external-body"))
+ clazz = (MimeObjectClass *)&mimeExternalBodyClass;
+ else if (!PL_strcasecmp(content_type+8, "partial"))
+ /* I guess these are most useful as externals, for now... */
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ else if (!exact_match_p)
+ /* Treat all unknown message subtypes as "text/plain" */
+ clazz = (MimeObjectClass *)&mimeInlineTextPlainClass;
+ }
+
+ /* The magic image types which we are able to display internally...
+ */
+ else if (!PL_strncasecmp(content_type, "image/", 6)) {
+ if (imgLoader::SupportImageWithMimeType(content_type, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS))
+ clazz = (MimeObjectClass *)&mimeInlineImageClass;
+ else
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ }
+
+#ifdef ENABLE_SMIME
+ else if (!PL_strcasecmp(content_type, APPLICATION_XPKCS7_MIME)
+ || !PL_strcasecmp(content_type, APPLICATION_PKCS7_MIME)) {
+
+ if (Preferences::GetBool("mailnews.p7m_subparts_external", false) &&
+ opts->is_child) {
+ // We do not allow encrypted parts except as top level.
+ // Allowing them would leak the plain text in case the part is
+ // cleverly hidden and the decrypted content gets included in
+ // replies and forwards.
+ clazz = (MimeObjectClass *)&mimeSuppressedCryptoClass;
+ return clazz;
+ }
+
+ char *ct = (hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE,
+ false, false)
+ : nullptr);
+ char *st = (ct ? MimeHeaders_get_parameter(ct, "smime-type", NULL, NULL)
+ : nullptr);
+
+ /* by default, assume that it is an encrypted message */
+ clazz = (MimeObjectClass *)&mimeEncryptedCMSClass;
+
+ /* if the smime-type parameter says that it's a certs-only or
+ compressed file, then show it as an attachment, however
+ (MimeEncryptedCMS doesn't handle these correctly) */
+ if (st &&
+ (!PL_strcasecmp(st, "certs-only") ||
+ !PL_strcasecmp(st, "compressed-data")))
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ else {
+ /* look at the file extension... less reliable, but still covered
+ by the S/MIME specification (RFC 3851, section 3.2.1) */
+ char *name = (hdrs ? MimeHeaders_get_name(hdrs, opts) : nullptr);
+ if (name) {
+ char *suf = PL_strrchr(name, '.');
+ bool p7mExternal = false;
+
+ if (prefBranch)
+ prefBranch->GetBoolPref("mailnews.p7m_external", &p7mExternal);
+ if (suf &&
+ ((!PL_strcasecmp(suf, ".p7m") && p7mExternal) ||
+ !PL_strcasecmp(suf, ".p7c") ||
+ !PL_strcasecmp(suf, ".p7z")))
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ }
+ PR_Free(name);
+ }
+ PR_Free(st);
+ PR_Free(ct);
+ }
+#endif
+ /* A few types which occur in the real world and which we would otherwise
+ treat as non-text types (which would be bad) without this special-case...
+ */
+ else if (!PL_strcasecmp(content_type, APPLICATION_PGP) ||
+ !PL_strcasecmp(content_type, APPLICATION_PGP2))
+ clazz = (MimeObjectClass *)&mimeInlineTextPlainClass;
+
+ else if (!PL_strcasecmp(content_type, SUN_ATTACHMENT))
+ clazz = (MimeObjectClass *)&mimeSunAttachmentClass;
+
+ /* Everything else gets represented as a clickable link.
+ */
+ else if (!exact_match_p)
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+
+ if (!mime_is_allowed_class(clazz, types_of_classes_to_disallow))
+ {
+ /* Do that check here (not after the if block), because we want to allow
+ user-installed plugins. */
+ if(!exact_match_p)
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ else
+ clazz = 0;
+ }
+ }
+
+#ifdef ENABLE_SMIME
+ // see bug #189988
+ if (opts && opts->format_out == nsMimeOutput::nsMimeMessageDecrypt &&
+ (clazz != (MimeObjectClass *)&mimeEncryptedCMSClass)) {
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ }
+#endif
+
+ if (!exact_match_p)
+ NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!clazz) return 0;
+
+ NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ if (clazz && !clazz->class_initialized)
+ {
+ int status = mime_classinit(clazz);
+ if (status < 0) return 0;
+ }
+
+ return clazz;
+}
+
+
+MimeObject *
+mime_create (const char *content_type, MimeHeaders *hdrs,
+ MimeDisplayOptions *opts, bool forceInline /* = false */)
+{
+ /* If there is no Content-Disposition header, or if the Content-Disposition
+ is ``inline'', then we display the part inline (and let mime_find_class()
+ decide how.)
+
+ If there is any other Content-Disposition (either ``attachment'' or some
+ disposition that we don't recognise) then we always display the part as
+ an external link, by using MimeExternalObject to display it.
+
+ But Content-Disposition is ignored for all containers except `message'.
+ (including multipart/mixed, and multipart/digest.) It's not clear if
+ this is to spec, but from a usability standpoint, I think it's necessary.
+ */
+
+ MimeObjectClass *clazz = 0;
+ char *content_disposition = 0;
+ MimeObject *obj = 0;
+ char *override_content_type = 0;
+
+ /* We've had issues where the incoming content_type is invalid, of a format:
+ content_type="=?windows-1252?q?application/pdf" (bug 659355)
+ We decided to fix that by simply trimming the stuff before the ?
+ */
+ if (content_type)
+ {
+ const char *lastQuestion = strrchr(content_type, '?');
+ if (lastQuestion)
+ content_type = lastQuestion + 1; // the substring after the last '?'
+ }
+
+ /* There are some clients send out all attachments with a content-type
+ of application/octet-stream. So, if we have an octet-stream attachment,
+ try to guess what type it really is based on the file extension. I HATE
+ that we have to do this...
+ */
+ if (hdrs && opts && opts->file_type_fn &&
+
+ /* ### mwelch - don't override AppleSingle */
+ (content_type ? PL_strcasecmp(content_type, APPLICATION_APPLEFILE) : true) &&
+ /* ## davidm Apple double shouldn't use this #$%& either. */
+ (content_type ? PL_strcasecmp(content_type, MULTIPART_APPLEDOUBLE) : true) &&
+ (!content_type ||
+ !PL_strcasecmp(content_type, APPLICATION_OCTET_STREAM) ||
+ !PL_strcasecmp(content_type, UNKNOWN_CONTENT_TYPE)))
+ {
+ char *name = MimeHeaders_get_name(hdrs, opts);
+ if (name)
+ {
+ override_content_type = opts->file_type_fn (name, opts->stream_closure);
+ // appledouble isn't a valid override content type, and makes
+ // attachments invisible.
+ if (!PL_strcasecmp(override_content_type, MULTIPART_APPLEDOUBLE))
+ override_content_type = nullptr;
+ PR_FREEIF(name);
+
+ // Workaroung for saving '.eml" file encoded with base64.
+ // Do not override with message/rfc822 whenever Transfer-Encoding is
+ // base64 since base64 encoding of message/rfc822 is invalid.
+ // Our MimeMessageClass has no capability to decode it.
+ if (!PL_strcasecmp(override_content_type, MESSAGE_RFC822)) {
+ nsCString encoding;
+ encoding.Adopt(MimeHeaders_get(hdrs,
+ HEADER_CONTENT_TRANSFER_ENCODING,
+ true, false));
+ if (encoding.LowerCaseEqualsLiteral(ENCODING_BASE64))
+ override_content_type = nullptr;
+ }
+
+ // If we get here and it is not the unknown content type from the
+ // file name, let's do some better checking not to inline something bad
+ if (override_content_type &&
+ *override_content_type &&
+ (PL_strcasecmp(override_content_type, UNKNOWN_CONTENT_TYPE)))
+ content_type = override_content_type;
+ }
+ }
+
+ clazz = mime_find_class(content_type, hdrs, opts, false);
+
+ NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!clazz) goto FAIL;
+
+ if (opts && opts->part_to_load)
+ /* Always ignore Content-Disposition when we're loading some specific
+ sub-part (which may be within some container that we wouldn't otherwise
+ descend into, if the container itself had a Content-Disposition of
+ `attachment'. */
+ content_disposition = 0;
+
+ else if (mime_subclass_p(clazz,(MimeObjectClass *)&mimeContainerClass) &&
+ !mime_subclass_p(clazz,(MimeObjectClass *)&mimeMessageClass))
+ /* Ignore Content-Disposition on all containers except `message'.
+ That is, Content-Disposition is ignored for multipart/mixed objects,
+ but is obeyed for message/rfc822 objects. */
+ content_disposition = 0;
+
+ else
+ {
+ /* Check to see if the plugin should override the content disposition
+ to make it appear inline. One example is a vcard which has a content
+ disposition of an "attachment;" */
+ if (force_inline_display(content_type))
+ NS_MsgSACopy(&content_disposition, "inline");
+ else
+ content_disposition = (hdrs
+ ? MimeHeaders_get(hdrs, HEADER_CONTENT_DISPOSITION, true, false)
+ : 0);
+ }
+
+ if (!content_disposition || !PL_strcasecmp(content_disposition, "inline"))
+ ; /* Use the class we've got. */
+ else
+ {
+ // override messages that have content disposition set to "attachment"
+ // even though we probably should show them inline.
+ if ( (clazz != (MimeObjectClass *)&mimeInlineTextClass) &&
+ (clazz != (MimeObjectClass *)&mimeInlineTextPlainClass) &&
+ (clazz != (MimeObjectClass *)&mimeInlineTextPlainFlowedClass) &&
+ (clazz != (MimeObjectClass *)&mimeInlineTextHTMLClass) &&
+ (clazz != (MimeObjectClass *)&mimeInlineTextHTMLParsedClass) &&
+ (clazz != (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass) &&
+ (clazz != (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass) &&
+ (clazz != (MimeObjectClass *)&mimeInlineTextRichtextClass) &&
+ (clazz != (MimeObjectClass *)&mimeInlineTextEnrichedClass) &&
+ (clazz != (MimeObjectClass *)&mimeMessageClass) &&
+ (clazz != (MimeObjectClass *)&mimeInlineImageClass) ) {
+ // not a special inline type, so show as attachment
+ // However, mimeSuppressedCryptoClass is treated identically as
+ // mimeExternalObjectClass, let's not lose that type information.
+ if (clazz != (MimeObjectClass *)&mimeSuppressedCryptoClass) {
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ }
+ }
+ }
+
+ /* If the option `Show Attachments Inline' is off, now would be the time to change our mind... */
+ /* Also, if we're doing a reply (i.e. quoting the body), then treat that according to preference. */
+ if (opts && ((!opts->show_attachment_inline_p && !forceInline) ||
+ (!opts->quote_attachment_inline_p &&
+ (opts->format_out == nsMimeOutput::nsMimeMessageQuoting ||
+ opts->format_out == nsMimeOutput::nsMimeMessageBodyQuoting))))
+ {
+ if (mime_subclass_p(clazz, (MimeObjectClass *)&mimeInlineTextClass))
+ {
+ /* It's a text type. Write it only if it's the *first* part
+ that we're writing, and then only if it has no "filename"
+ specified (the assumption here being, if it has a filename,
+ it wasn't simply typed into the text field -- it was actually
+ an attached document.) */
+ if (opts->state && opts->state->first_part_written_p)
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ else
+ {
+ /* If there's a name, then write this as an attachment. */
+ char *name = (hdrs ? MimeHeaders_get_name(hdrs, opts) : nullptr);
+ if (name)
+ {
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ PR_Free(name);
+ }
+ }
+ }
+ else
+ if (mime_subclass_p(clazz,(MimeObjectClass *)&mimeContainerClass) &&
+ !mime_subclass_p(clazz,(MimeObjectClass *)&mimeMessageClass))
+ /* Multipart subtypes are ok, except for messages; descend into
+ multiparts, and defer judgement.
+
+ Encrypted blobs are just like other containers (make the crypto
+ layer invisible, and treat them as simple containers. So there's
+ no easy way to save encrypted data directly to disk; it will tend
+ to always be wrapped inside a message/rfc822. That's ok.) */
+ ;
+ else if (opts && opts->part_to_load &&
+ mime_subclass_p(clazz,(MimeObjectClass *)&mimeMessageClass))
+ /* Descend into messages only if we're looking for a specific sub-part. */
+ ;
+ else
+ {
+ /* Anything else, and display it as a link (and cause subsequent
+ text parts to also be displayed as links.) */
+ clazz = (MimeObjectClass *)&mimeExternalObjectClass;
+ }
+ }
+
+ PR_FREEIF(content_disposition);
+ obj = mime_new (clazz, hdrs, content_type);
+
+ FAIL:
+
+ /* If we decided to ignore the content-type in the headers of this object
+ (see above) then make sure that our new content-type is stored in the
+ object itself. (Or free it, if we're in an out-of-memory situation.)
+ */
+ if (override_content_type)
+ {
+ if (obj)
+ {
+ PR_FREEIF(obj->content_type);
+ obj->content_type = override_content_type;
+ }
+ else
+ {
+ PR_Free(override_content_type);
+ }
+ }
+
+ return obj;
+}
+
+
+
+static int mime_classinit_1(MimeObjectClass *clazz, MimeObjectClass *target);
+
+static int
+mime_classinit(MimeObjectClass *clazz)
+{
+ int status;
+ if (clazz->class_initialized)
+ return 0;
+
+ NS_ASSERTION(clazz->class_initialize, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!clazz->class_initialize)
+ return -1;
+
+ /* First initialize the superclass.
+ */
+ if (clazz->superclass && !clazz->superclass->class_initialized)
+ {
+ status = mime_classinit(clazz->superclass);
+ if (status < 0) return status;
+ }
+
+ /* Now run each of the superclass-init procedures in turn,
+ parentmost-first. */
+ status = mime_classinit_1(clazz, clazz);
+ if (status < 0) return status;
+
+ /* Now we're done. */
+ clazz->class_initialized = true;
+ return 0;
+}
+
+static int
+mime_classinit_1(MimeObjectClass *clazz, MimeObjectClass *target)
+{
+ int status;
+ if (clazz->superclass)
+ {
+ status = mime_classinit_1(clazz->superclass, target);
+ if (status < 0) return status;
+ }
+ return clazz->class_initialize(target);
+}
+
+
+bool
+mime_subclass_p(MimeObjectClass *child, MimeObjectClass *parent)
+{
+ if (child == parent)
+ return true;
+ else if (!child->superclass)
+ return false;
+ else
+ return mime_subclass_p(child->superclass, parent);
+}
+
+bool
+mime_typep(MimeObject *obj, MimeObjectClass *clazz)
+{
+ return mime_subclass_p(obj->clazz, clazz);
+}
+
+
+
+/* URL munging
+ */
+
+
+/* Returns a string describing the location of the part (like "2.5.3").
+ This is not a full URL, just a part-number.
+ */
+char *
+mime_part_address(MimeObject *obj)
+{
+ if (!obj->parent)
+ return strdup("0");
+ else
+ {
+ /* Find this object in its parent. */
+ int32_t i, j = -1;
+ char buf [20];
+ char *higher = 0;
+ MimeContainer *cont = (MimeContainer *) obj->parent;
+ NS_ASSERTION(mime_typep(obj->parent,
+ (MimeObjectClass *)&mimeContainerClass), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ for (i = 0; i < cont->nchildren; i++)
+ if (cont->children[i] == obj)
+ {
+ j = i+1;
+ break;
+ }
+ if (j == -1)
+ {
+ NS_ERROR("No children under MeimContainer");
+ return 0;
+ }
+
+ PR_snprintf(buf, sizeof(buf), "%ld", j);
+ if (obj->parent->parent)
+ {
+ higher = mime_part_address(obj->parent);
+ if (!higher) return 0; /* MIME_OUT_OF_MEMORY */
+ }
+
+ if (!higher)
+ return strdup(buf);
+ else
+ {
+ uint32_t slen = strlen(higher) + strlen(buf) + 3;
+ char *s = (char *)PR_MALLOC(slen);
+ if (!s)
+ {
+ PR_Free(higher);
+ return 0; /* MIME_OUT_OF_MEMORY */
+ }
+ PL_strncpyz(s, higher, slen);
+ PL_strcatn(s, slen, ".");
+ PL_strcatn(s, slen, buf);
+ PR_Free(higher);
+ return s;
+ }
+ }
+}
+
+
+/* Returns a string describing the location of the *IMAP* part (like "2.5.3").
+ This is not a full URL, just a part-number.
+ This part is explicitly passed in the X-Mozilla-IMAP-Part header.
+ Return value must be freed by the caller.
+ */
+char *
+mime_imap_part_address(MimeObject *obj)
+{
+ if (!obj || !obj->headers)
+ return 0;
+ else
+ return MimeHeaders_get(obj->headers, IMAP_EXTERNAL_CONTENT_HEADER, false, false);
+}
+
+/* Returns a full URL if the current mime object has a EXTERNAL_ATTACHMENT_URL_HEADER
+ header.
+ Return value must be freed by the caller.
+*/
+char *
+mime_external_attachment_url(MimeObject *obj)
+{
+ if (!obj || !obj->headers)
+ return 0;
+ else
+ return MimeHeaders_get(obj->headers, EXTERNAL_ATTACHMENT_URL_HEADER, false, false);
+}
+
+#ifdef ENABLE_SMIME
+/* Asks whether the given object is one of the cryptographically signed
+ or encrypted objects that we know about. (MimeMessageClass uses this
+ to decide if the headers need to be presented differently.)
+ */
+bool
+mime_crypto_object_p(MimeHeaders *hdrs, bool clearsigned_counts, MimeDisplayOptions *opts)
+{
+ char *ct;
+ MimeObjectClass *clazz;
+
+ if (!hdrs) return false;
+
+ ct = MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, true, false);
+ if (!ct) return false;
+
+ /* Rough cut -- look at the string before doing a more complex comparison. */
+ if (PL_strcasecmp(ct, MULTIPART_SIGNED) &&
+ PL_strncasecmp(ct, "application/", 12))
+ {
+ PR_Free(ct);
+ return false;
+ }
+
+ /* It's a candidate for being a crypto object. Let's find out for sure... */
+ clazz = mime_find_class(ct, hdrs, opts, true);
+ PR_Free(ct);
+
+ if (clazz == ((MimeObjectClass *)&mimeEncryptedCMSClass))
+ return true;
+ else if (clearsigned_counts &&
+ clazz == ((MimeObjectClass *)&mimeMultipartSignedCMSClass))
+ return true;
+ else
+ return false;
+}
+
+#endif // ENABLE_SMIME
+
+/* Puts a part-number into a URL. If append_p is true, then the part number
+ is appended to any existing part-number already in that URL; otherwise,
+ it replaces it.
+ */
+char *
+mime_set_url_part(const char *url, const char *part, bool append_p)
+{
+ const char *part_begin = 0;
+ const char *part_end = 0;
+ bool got_q = false;
+ const char *s;
+ char *result;
+
+ if (!url || !part) return 0;
+
+ nsAutoCString urlString(url);
+ int32_t typeIndex = urlString.Find("?type=application/x-message-display");
+ if (typeIndex != -1)
+ {
+ urlString.Cut(typeIndex, sizeof("?type=application/x-message-display") - 1);
+ if (urlString.CharAt(typeIndex) == '&')
+ urlString.Replace(typeIndex, 1, '?');
+ url = urlString.get();
+ }
+
+ for (s = url; *s; s++)
+ {
+ if (*s == '?')
+ {
+ got_q = true;
+ if (!PL_strncasecmp(s, "?part=", 6))
+ part_begin = (s += 6);
+ }
+ else if (got_q && *s == '&' && !PL_strncasecmp(s, "&part=", 6))
+ part_begin = (s += 6);
+
+ if (part_begin)
+ {
+ for (; (*s && *s != '?' && *s != '&'); s++)
+ ;
+ part_end = s;
+ break;
+ }
+ }
+
+ uint32_t resultlen = strlen(url) + strlen(part) + 10;
+ result = (char *) PR_MALLOC(resultlen);
+ if (!result) return 0;
+
+ if (part_begin)
+ {
+ if (append_p)
+ {
+ memcpy(result, url, part_end - url);
+ result [part_end - url] = '.';
+ result [part_end - url + 1] = 0;
+ }
+ else
+ {
+ memcpy(result, url, part_begin - url);
+ result [part_begin - url] = 0;
+ }
+ }
+ else
+ {
+ PL_strncpyz(result, url, resultlen);
+ if (got_q)
+ PL_strcatn(result, resultlen, "&part=");
+ else
+ PL_strcatn(result, resultlen, "?part=");
+ }
+
+ PL_strcatn(result, resultlen, part);
+
+ if (part_end && *part_end)
+ PL_strcatn(result, resultlen, part_end);
+
+ /* Semi-broken kludge to omit a trailing "?part=0". */
+ {
+ int L = strlen(result);
+ if (L > 6 &&
+ (result[L-7] == '?' || result[L-7] == '&') &&
+ !strcmp("part=0", result + L - 6))
+ result[L-7] = 0;
+ }
+
+ return result;
+}
+
+
+
+/* Puts an *IMAP* part-number into a URL.
+ Strips off any previous *IMAP* part numbers, since they are absolute, not relative.
+ */
+char *
+mime_set_url_imap_part(const char *url, const char *imappart, const char *libmimepart)
+{
+ char *result = 0;
+ char *whereCurrent = PL_strstr(url, "/;section=");
+ if (whereCurrent)
+ {
+ *whereCurrent = 0;
+ }
+
+ uint32_t resultLen = strlen(url) + strlen(imappart) + strlen(libmimepart) + 17;
+ result = (char *) PR_MALLOC(resultLen);
+ if (!result) return 0;
+
+ PL_strncpyz(result, url, resultLen);
+ PL_strcatn(result, resultLen, "/;section=");
+ PL_strcatn(result, resultLen, imappart);
+ PL_strcatn(result, resultLen, "?part=");
+ PL_strcatn(result, resultLen, libmimepart);
+
+ if (whereCurrent)
+ *whereCurrent = '/';
+
+ return result;
+}
+
+
+/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
+ number matches, and returns the MimeObject (else NULL.)
+ (part is not a URL -- it's of the form "1.3.5".)
+ */
+MimeObject *
+mime_address_to_part(const char *part, MimeObject *obj)
+{
+ /* Note: this is an N^2 operation, but the number of parts in a message
+ shouldn't ever be large enough that this really matters... */
+
+ bool match;
+
+ if (!part || !*part)
+ {
+ match = !obj->parent;
+ }
+ else
+ {
+ char *part2 = mime_part_address(obj);
+ if (!part2) return 0; /* MIME_OUT_OF_MEMORY */
+ match = !strcmp(part, part2);
+ PR_Free(part2);
+ }
+
+ if (match)
+ {
+ /* These are the droids we're looking for. */
+ return obj;
+ }
+ else if (!mime_typep(obj, (MimeObjectClass *) &mimeContainerClass))
+ {
+ /* Not a container, pull up, pull up! */
+ return 0;
+ }
+ else
+ {
+ int32_t i;
+ MimeContainer *cont = (MimeContainer *) obj;
+ for (i = 0; i < cont->nchildren; i++)
+ {
+ MimeObject *o2 = mime_address_to_part(part, cont->children[i]);
+ if (o2) return o2;
+ }
+ return 0;
+ }
+}
+
+/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
+ number matches; if one is found, returns the Content-Name of that part.
+ Else returns NULL. (part is not a URL -- it's of the form "1.3.5".)
+ */
+char *
+mime_find_content_type_of_part(const char *part, MimeObject *obj)
+{
+ char *result = 0;
+
+ obj = mime_address_to_part(part, obj);
+ if (!obj) return 0;
+
+ result = (obj->headers ? MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, true, false) : 0);
+
+ return result;
+}
+
+/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
+ number matches; if one is found, returns the Content-Name of that part.
+ Else returns NULL. (part is not a URL -- it's of the form "1.3.5".)
+ */
+char *
+mime_find_suggested_name_of_part(const char *part, MimeObject *obj)
+{
+ char *result = 0;
+
+ obj = mime_address_to_part(part, obj);
+ if (!obj) return 0;
+
+ result = (obj->headers ? MimeHeaders_get_name(obj->headers, obj->options) : 0);
+
+ /* If this part doesn't have a name, but this part is one fork of an
+ AppleDouble, and the AppleDouble itself has a name, then use that. */
+ if (!result &&
+ obj->parent &&
+ obj->parent->headers &&
+ mime_typep(obj->parent,
+ (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
+ result = MimeHeaders_get_name(obj->parent->headers, obj->options);
+
+ /* Else, if this part is itself an AppleDouble, and one of its children
+ has a name, then use that (check data fork first, then resource.) */
+ if (!result &&
+ mime_typep(obj, (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
+ {
+ MimeContainer *cont = (MimeContainer *) obj;
+ if (cont->nchildren > 1 &&
+ cont->children[1] &&
+ cont->children[1]->headers)
+ result = MimeHeaders_get_name(cont->children[1]->headers, obj->options);
+
+ if (!result &&
+ cont->nchildren > 0 &&
+ cont->children[0] &&
+ cont->children[0]->headers)
+ result = MimeHeaders_get_name(cont->children[0]->headers, obj->options);
+ }
+
+ /* Ok, now we have the suggested name, if any.
+ Now we remove any extensions that correspond to the
+ Content-Transfer-Encoding. For example, if we see the headers
+
+ Content-Type: text/plain
+ Content-Disposition: inline; filename=foo.text.uue
+ Content-Transfer-Encoding: x-uuencode
+
+ then we would look up (in mime.types) the file extensions which are
+ associated with the x-uuencode encoding, find that "uue" is one of
+ them, and remove that from the end of the file name, thus returning
+ "foo.text" as the name. This is because, by the time this file ends
+ up on disk, its content-transfer-encoding will have been removed;
+ therefore, we should suggest a file name that indicates that.
+ */
+ if (result && obj->encoding && *obj->encoding)
+ {
+ int32_t L = strlen(result);
+ const char **exts = 0;
+
+ /*
+ I'd like to ask the mime.types file, "what extensions correspond
+ to obj->encoding (which happens to be "x-uuencode") but doing that
+ in a non-sphagetti way would require brain surgery. So, since
+ currently uuencode is the only content-transfer-encoding which we
+ understand which traditionally has an extension, we just special-
+ case it here! Icepicks in my forehead!
+
+ Note that it's special-cased in a similar way in libmsg/compose.c.
+ */
+ if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE))
+ {
+ static const char *uue_exts[] = { "uu", "uue", 0 };
+ exts = uue_exts;
+ }
+
+ while (exts && *exts)
+ {
+ const char *ext = *exts;
+ int32_t L2 = strlen(ext);
+ if (L > L2 + 1 && /* long enough */
+ result[L - L2 - 1] == '.' && /* '.' in right place*/
+ !PL_strcasecmp(ext, result + (L - L2))) /* ext matches */
+ {
+ result[L - L2 - 1] = 0; /* truncate at '.' and stop. */
+ break;
+ }
+ exts++;
+ }
+ }
+
+ return result;
+}
+
+/* Parse the various "?" options off the URL and into the options struct.
+ */
+int
+mime_parse_url_options(const char *url, MimeDisplayOptions *options)
+{
+ const char *q;
+
+ if (!url || !*url) return 0;
+ if (!options) return 0;
+
+ MimeHeadersState default_headers = options->headers;
+
+ q = PL_strrchr (url, '?');
+ if (! q) return 0;
+ q++;
+ while (*q)
+ {
+ const char *end, *value, *name_end;
+ for (end = q; *end && *end != '&'; end++)
+ ;
+ for (value = q; *value != '=' && value < end; value++)
+ ;
+ name_end = value;
+ if (value < end) value++;
+ if (name_end <= q)
+ ;
+ else if (!PL_strncasecmp ("headers", q, name_end - q))
+ {
+ if (end > value && !PL_strncasecmp ("only", value, end-value))
+ options->headers = MimeHeadersOnly;
+ else if (end > value && !PL_strncasecmp ("none", value, end-value))
+ options->headers = MimeHeadersNone;
+ else if (end > value && !PL_strncasecmp ("all", value, end - value))
+ options->headers = MimeHeadersAll;
+ else if (end > value && !PL_strncasecmp ("some", value, end - value))
+ options->headers = MimeHeadersSome;
+ else if (end > value && !PL_strncasecmp ("micro", value, end - value))
+ options->headers = MimeHeadersMicro;
+ else if (end > value && !PL_strncasecmp ("cite", value, end - value))
+ options->headers = MimeHeadersCitation;
+ else if (end > value && !PL_strncasecmp ("citation", value, end-value))
+ options->headers = MimeHeadersCitation;
+ else
+ options->headers = default_headers;
+ }
+ else if (!PL_strncasecmp ("part", q, name_end - q) &&
+ options->format_out != nsMimeOutput::nsMimeMessageBodyQuoting)
+ {
+ PR_FREEIF (options->part_to_load);
+ if (end > value)
+ {
+ options->part_to_load = (char *) PR_MALLOC(end - value + 1);
+ if (!options->part_to_load)
+ return MIME_OUT_OF_MEMORY;
+ memcpy(options->part_to_load, value, end-value);
+ options->part_to_load[end-value] = 0;
+ }
+ }
+ else if (!PL_strncasecmp ("rot13", q, name_end - q))
+ {
+ options->rot13_p = end <= value || !PL_strncasecmp ("true", value, end - value);
+ }
+ else if (!PL_strncasecmp ("emitter", q, name_end - q))
+ {
+ if ((end > value) && !PL_strncasecmp ("js", value, end - value))
+ {
+ // the js emitter needs to hear about nested message bodies
+ // in order to build a proper representation.
+ options->notify_nested_bodies = true;
+ // show_attachment_inline_p has the side-effect of letting the
+ // emitter see all parts of a multipart/alternative, which it
+ // really appreciates.
+ options->show_attachment_inline_p = true;
+ // however, show_attachment_inline_p also results in a few
+ // subclasses writing junk into the body for display purposes.
+ // put a stop to these shenanigans by enabling write_pure_bodies.
+ // current offenders are:
+ // - MimeInlineImage
+ options->write_pure_bodies = true;
+ // we don't actually care about the data in the attachments, just the
+ // metadata (i.e. size)
+ options->metadata_only = true;
+ }
+ }
+
+ q = end;
+ if (*q)
+ q++;
+ }
+
+
+/* Compatibility with the "?part=" syntax used in the old (Mozilla 2.0)
+ MIME parser.
+
+ Basically, the problem is that the old part-numbering code was totally
+ busted: here's a comparison of the old and new numberings with a pair
+ of hypothetical messages (one with a single part, and one with nested
+ containers.)
+ NEW: OLD: OR:
+ message/rfc822
+ image/jpeg 1 0 0
+
+ message/rfc822
+ multipart/mixed 1 0 0
+ text/plain 1.1 1 1
+ image/jpeg 1.2 2 2
+ message/rfc822 1.3 - 3
+ text/plain 1.3.1 3 -
+ message/rfc822 1.4 - 4
+ multipart/mixed 1.4.1 4 -
+ text/plain 1.4.1.1 4.1 -
+ image/jpeg 1.4.1.2 4.2 -
+ text/plain 1.5 5 5
+
+ The "NEW" column is how the current code counts. The "OLD" column is
+ what "?part=" references would do in 3.0b4 and earlier; you'll see that
+ you couldn't directly refer to the child message/rfc822 objects at all!
+ But that's when it got really weird, because if you turned on
+ "Attachments As Links" (or used a URL like "?inline=false&part=...")
+ then you got a totally different numbering system (seen in the "OR"
+ column.) Gag!
+
+ So, the problem is, ClariNet had been using these part numbers in their
+ HTML news feeds, as a sleazy way of transmitting both complex HTML layouts
+ and images using NNTP as transport, without invoking HTTP.
+
+ The following clause is to provide some small amount of backward
+ compatibility. By looking at that table, one can see that in the new
+ model, "part=0" has no meaning, and neither does "part=2" or "part=3"
+ and so on.
+
+ "part=1" is ambiguous between the old and new way, as is any part
+ specification that has a "." in it.
+
+ So, the compatibility hack we do here is: if the part is "0", then map
+ that to "1". And if the part is >= "2", then prepend "1." to it (so that
+ we map "2" to "1.2", and "3" to "1.3".)
+
+ This leaves the URLs compatible in the cases of:
+
+ = single part messages
+ = references to elements of a top-level multipart except the first
+
+ and leaves them incompatible for:
+
+ = the first part of a top-level multipart
+ = all elements deeper than the outermost part
+
+ Life s#$%s when you don't properly think out things that end up turning
+ into de-facto standards...
+ */
+
+ if (options->part_to_load &&
+ !PL_strchr(options->part_to_load, '.')) /* doesn't contain a dot */
+ {
+ if (!strcmp(options->part_to_load, "0")) /* 0 */
+ {
+ PR_Free(options->part_to_load);
+ options->part_to_load = strdup("1");
+ if (!options->part_to_load)
+ return MIME_OUT_OF_MEMORY;
+ }
+ else if (strcmp(options->part_to_load, "1")) /* not 1 */
+ {
+ const char *prefix = "1.";
+ uint32_t slen = strlen(options->part_to_load) + strlen(prefix) + 1;
+ char *s = (char *) PR_MALLOC(slen);
+ if (!s) return MIME_OUT_OF_MEMORY;
+ PL_strncpyz(s, prefix, slen);
+ PL_strcatn(s, slen, options->part_to_load);
+ PR_Free(options->part_to_load);
+ options->part_to_load = s;
+ }
+ }
+
+ return 0;
+}
+
+
+/* Some output-generation utility functions...
+ */
+
+int
+MimeOptions_write(MimeHeaders *hdrs, MimeDisplayOptions *opt, const char *data,
+ int32_t length, bool user_visible_p)
+{
+ int status = 0;
+ void* closure = 0;
+ if (!opt || !opt->output_fn || !opt->state)
+ return 0;
+
+ closure = opt->output_closure;
+ if (!closure) closure = opt->stream_closure;
+
+// PR_ASSERT(opt->state->first_data_written_p);
+
+ if (opt->state->separator_queued_p && user_visible_p)
+ {
+ opt->state->separator_queued_p = false;
+ if (opt->state->separator_suppressed_p)
+ opt->state->separator_suppressed_p = false;
+ else {
+ const char *sep = "<BR><FIELDSET CLASS=\"mimeAttachmentHeader\">";
+ int lstatus = opt->output_fn(sep, strlen(sep), closure);
+ opt->state->separator_suppressed_p = false;
+ if (lstatus < 0) return lstatus;
+
+ nsCString name;
+ name.Adopt(MimeHeaders_get_name(hdrs, opt));
+ MimeHeaders_convert_header_value(opt, name, false);
+
+ if (!name.IsEmpty()) {
+ sep = "<LEGEND CLASS=\"mimeAttachmentHeaderName\">";
+ lstatus = opt->output_fn(sep, strlen(sep), closure);
+ opt->state->separator_suppressed_p = false;
+ if (lstatus < 0) return lstatus;
+
+ nsCString escapedName;
+ escapedName.Adopt(MsgEscapeHTML(name.get()));
+
+ lstatus = opt->output_fn(escapedName.get(),
+ escapedName.Length(), closure);
+ opt->state->separator_suppressed_p = false;
+ if (lstatus < 0) return lstatus;
+
+ sep = "</LEGEND>";
+ lstatus = opt->output_fn(sep, strlen(sep), closure);
+ opt->state->separator_suppressed_p = false;
+ if (lstatus < 0) return lstatus;
+ }
+
+ sep = "</FIELDSET><BR/>";
+ lstatus = opt->output_fn(sep, strlen(sep), closure);
+ opt->state->separator_suppressed_p = false;
+ if (lstatus < 0) return lstatus;
+ }
+ }
+ if (user_visible_p)
+ opt->state->separator_suppressed_p = false;
+
+ if (length > 0)
+ {
+ status = opt->output_fn(data, length, closure);
+ if (status < 0) return status;
+ }
+
+ return 0;
+}
+
+int
+MimeObject_write(MimeObject *obj, const char *output, int32_t length,
+ bool user_visible_p)
+{
+ if (!obj->output_p) return 0;
+
+ // if we're stripping attachments, check if any parent is not being ouput
+ if (obj->options->format_out == nsMimeOutput::nsMimeMessageAttach)
+ {
+ // if true, mime generates a separator in html - we don't want that.
+ user_visible_p = false;
+
+ for (MimeObject *parent = obj->parent; parent; parent = parent->parent)
+ {
+ if (!parent->output_p)
+ return 0;
+ }
+ }
+ if (!obj->options->state->first_data_written_p)
+ {
+ int status = MimeObject_output_init(obj, 0);
+ if (status < 0) return status;
+ NS_ASSERTION(obj->options->state->first_data_written_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ }
+
+ return MimeOptions_write(obj->headers, obj->options, output, length, user_visible_p);
+}
+
+int
+MimeObject_write_separator(MimeObject *obj)
+{
+ if (obj->options && obj->options->state &&
+ // we never want separators if we are asking for pure bodies
+ !obj->options->write_pure_bodies)
+ obj->options->state->separator_queued_p = true;
+ return 0;
+}
+
+int
+MimeObject_output_init(MimeObject *obj, const char *content_type)
+{
+ if (obj &&
+ obj->options &&
+ obj->options->state &&
+ !obj->options->state->first_data_written_p)
+ {
+ int status;
+ const char *charset = 0;
+ char *name = 0, *x_mac_type = 0, *x_mac_creator = 0;
+
+ if (!obj->options->output_init_fn)
+ {
+ obj->options->state->first_data_written_p = true;
+ return 0;
+ }
+
+ if (obj->headers)
+ {
+ char *ct;
+ name = MimeHeaders_get_name(obj->headers, obj->options);
+
+ ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE,
+ false, false);
+ if (ct)
+ {
+ x_mac_type = MimeHeaders_get_parameter(ct, PARAM_X_MAC_TYPE, NULL, NULL);
+ x_mac_creator= MimeHeaders_get_parameter(ct, PARAM_X_MAC_CREATOR, NULL, NULL);
+ /* if don't have a x_mac_type and x_mac_creator, we need to try to get it from its parent */
+ if (!x_mac_type && !x_mac_creator && obj->parent && obj->parent->headers)
+ {
+ char * ctp = MimeHeaders_get(obj->parent->headers, HEADER_CONTENT_TYPE, false, false);
+ if (ctp)
+ {
+ x_mac_type = MimeHeaders_get_parameter(ctp, PARAM_X_MAC_TYPE, NULL, NULL);
+ x_mac_creator= MimeHeaders_get_parameter(ctp, PARAM_X_MAC_CREATOR, NULL, NULL);
+ PR_Free(ctp);
+ }
+ }
+
+ if (!(obj->options->override_charset)) {
+ char *charset = MimeHeaders_get_parameter(ct, "charset", nullptr, nullptr);
+ if (charset)
+ {
+ PR_FREEIF(obj->options->default_charset);
+ obj->options->default_charset = charset;
+ }
+ }
+ PR_Free(ct);
+ }
+ }
+
+ if (mime_typep(obj, (MimeObjectClass *) &mimeInlineTextClass))
+ charset = ((MimeInlineText *)obj)->charset;
+
+ if (!content_type)
+ content_type = obj->content_type;
+ if (!content_type)
+ content_type = TEXT_PLAIN;
+
+ //
+ // Set the charset on the channel we are dealing with so people know
+ // what the charset is set to. Do this for quoting/Printing ONLY!
+ //
+ extern void ResetChannelCharset(MimeObject *obj);
+ if ( (obj->options) &&
+ ( (obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting) ||
+ (obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting) ||
+ (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs) ||
+ (obj->options->format_out == nsMimeOutput::nsMimeMessagePrintOutput) ) )
+ ResetChannelCharset(obj);
+
+ status = obj->options->output_init_fn (content_type, charset, name,
+ x_mac_type, x_mac_creator,
+ obj->options->stream_closure);
+ PR_FREEIF(name);
+ PR_FREEIF(x_mac_type);
+ PR_FREEIF(x_mac_creator);
+ obj->options->state->first_data_written_p = true;
+ return status;
+ }
+ return 0;
+}
+
+char *
+mime_get_base_url(const char *url)
+{
+ if (!url)
+ return nullptr;
+
+ const char *s = strrchr(url, '?');
+ if (s && !strncmp(s, "?type=application/x-message-display", sizeof("?type=application/x-message-display") - 1))
+ {
+ const char *nextTerm = strchr(s, '&');
+ s = (nextTerm) ? nextTerm : s + strlen(s) - 1;
+ }
+ // we need to keep the ?number part of the url, or we won't know
+ // which local message the part belongs to.
+ if (s && *s && *(s+1) && !strncmp(s + 1, "number=", sizeof("number=") - 1))
+ {
+ const char *nextTerm = strchr(++s, '&');
+ s = (nextTerm) ? nextTerm : s + strlen(s) - 1;
+ }
+ char *result = (char *) PR_MALLOC(strlen(url) + 1);
+ NS_ASSERTION(result, "out of memory");
+ if (!result)
+ return nullptr;
+
+ memcpy(result, url, s - url);
+ result[s - url] = 0;
+ return result;
+}
diff --git a/mailnews/mime/src/mimei.h b/mailnews/mime/src/mimei.h
new file mode 100644
index 000000000..a024c4523
--- /dev/null
+++ b/mailnews/mime/src/mimei.h
@@ -0,0 +1,410 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEI_H_
+#define _MIMEI_H_
+
+/*
+ This module, libmime, implements a general-purpose MIME parser.
+ One of the methods provided by this parser is the ability to emit
+ an HTML representation of it.
+
+ All Mozilla-specific code is (and should remain) isolated in the
+ file mimemoz.c. Generally, if the code involves images, netlib
+ streams it should be in mimemoz.c instead of in the main body of
+ the MIME parser.
+
+ The parser is object-oriented and fully buzzword-compliant.
+ There is a class for each MIME type, and each class is responsible
+ for parsing itself, and/or handing the input data off to one of its
+ child objects.
+
+ The class hierarchy is:
+
+ MimeObject (abstract)
+ |
+ +--- MimeContainer (abstract)
+ | |
+ | +--- MimeMultipart (abstract)
+ | | |
+ | | +--- MimeMultipartMixed
+ | | |
+ | | +--- MimeMultipartDigest
+ | | |
+ | | +--- MimeMultipartParallel
+ | | |
+ | | +--- MimeMultipartAlternative
+ | | |
+ | | +--- MimeMultipartRelated
+ | | |
+ | | +--- MimeMultipartAppleDouble
+ | | |
+ | | +--- MimeSunAttachment
+ | | |
+ | | \--- MimeMultipartSigned (abstract)
+ | | |
+ | | \--- MimeMultipartSignedCMS
+ | |
+ | +--- MimeEncrypted (abstract)
+ | | |
+ | | \--- MimeEncryptedPKCS7
+ | |
+ | +--- MimeXlateed (abstract)
+ | | |
+ | | \--- MimeXlateed
+ | |
+ | +--- MimeMessage
+ | |
+ | \--- MimeUntypedText
+ |
+ +--- MimeLeaf (abstract)
+ | |
+ | +--- MimeInlineText (abstract)
+ | | |
+ | | +--- MimeInlineTextPlain
+ | | | |
+ | | | \--- MimeInlineTextHTMLAsPlaintext
+ | | |
+ | | +--- MimeInlineTextPlainFlowed
+ | | |
+ | | +--- MimeInlineTextHTML
+ | | | |
+ | | | +--- MimeInlineTextHTMLParsed
+ | | | |
+ | | | \--- MimeInlineTextHTMLSanitized
+ | | |
+ | | +--- MimeInlineTextRichtext
+ | | | |
+ | | | \--- MimeInlineTextEnriched
+ | | |
+ | | +--- MimeInlineTextVCard
+ | |
+ | +--- MimeInlineImage
+ | |
+ | \--- MimeExternalObject
+ |
+ \--- MimeExternalBody
+
+
+ =========================================================================
+ The definition of these classes is somewhat idiosyncratic, since I defined
+ my own small object system, instead of giving the C++ virus another foothold.
+ (I would have liked to have written this in Java, but our runtime isn't
+ quite ready for prime time yet.)
+
+ There is one header file and one source file for each class (for example,
+ the MimeInlineText class is defined in "mimetext.h" and "mimetext.c".)
+ Each header file follows the following boiler-plate form:
+
+ TYPEDEFS: these come first to avoid circular dependencies.
+
+ typedef struct FoobarClass FoobarClass;
+ typedef struct Foobar Foobar;
+
+ CLASS DECLARATION:
+ Theis structure defines the callback routines and other per-class data
+ of the class defined in this module.
+
+ struct FoobarClass {
+ ParentClass superclass;
+ ...any callbacks or class-variables...
+ };
+
+ CLASS DEFINITION:
+ This variable holds an instance of the one-and-only class record; the
+ various instances of this class point to this object. (One interrogates
+ the type of an instance by comparing the value of its class pointer with
+ the address of this variable.)
+
+ extern FoobarClass foobarClass;
+
+ INSTANCE DECLARATION:
+ Theis structure defines the per-instance data of an object, and a pointer
+ to the corresponding class record.
+
+ struct Foobar {
+ Parent parent;
+ ...any instance variables...
+ };
+
+ Then, in the corresponding .c file, the following structure is used:
+
+ CLASS DEFINITION:
+ First we pull in the appropriate include file (which includes all necessary
+ include files for the parent classes) and then we define the class object
+ using the MimeDefClass macro:
+
+ #include "foobar.h"
+ #define MIME_SUPERCLASS parentlClass
+ MimeDefClass(Foobar, FoobarClass, foobarClass, &MIME_SUPERCLASS);
+
+ The definition of MIME_SUPERCLASS is just to move most of the knowlege of the
+ exact class hierarchy up to the file's header, instead of it being scattered
+ through the various methods; see below.
+
+ METHOD DECLARATIONS:
+ We will be putting function pointers into the class object, so we declare
+ them here. They can generally all be static, since nobody outside of this
+ file needs to reference them by name; all references to these routines should
+ be through the class object.
+
+ extern int FoobarMethod(Foobar *);
+ ...etc...
+
+ CLASS INITIALIZATION FUNCTION:
+ The MimeDefClass macro expects us to define a function which will finish up
+ any initialization of the class object that needs to happen before the first
+ time it is instantiated. Its name must be of the form "<class>Initialize",
+ and it should initialize the various method slots in the class as
+ appropriate. Any methods or class variables which this class does not wish
+ to override will be automatically inherited from the parent class (by virtue
+ of its class-initialization function having been run first.) Each class
+ object will only be initialized once.
+
+ static int
+ FoobarClassInitialize(FoobarClass *class)
+ {
+ clazz->method = FoobarMethod.
+ ...etc...
+ }
+
+ METHOD DEFINITIONS:
+ Next come the definitions of the methods we referred to in the class-init
+ function. The way to access earlier methods (methods defined on the
+ superclass) is to simply extract them from the superclass's object.
+ But note that you CANNOT get at methods by indirecting through
+ object->clazz->superclass: that will only work to one level, and will
+ go into a loop if some subclass tries to continue on this method.
+
+ The easiest way to do this is to make use of the MIME_SUPERCLASS macro that
+ was defined at the top of the file, as shown below. The alternative to that
+ involves typing the literal name of the direct superclass of the class
+ defined in this file, which will be a maintenance headache if the class
+ hierarchy changes. If you use the MIME_SUPERCLASS idiom, then a textual
+ change is required in only one place if this class's superclass changes.
+
+ static void
+ Foobar_finalize (MimeObject *object)
+ {
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); // RIGHT
+ parentClass.whatnot.object.finalize(object); // (works...)
+ object->clazz->superclass->finalize(object); // WRONG!!
+ }
+
+ If you write a libmime content type handler, libmime might create several
+ instances of your class at once and call e.g. the same finalize code for
+ 3 different objects in a row.
+ */
+
+#include "mimehdrs.h"
+#include "nsTArray.h"
+
+typedef struct MimeObject MimeObject;
+typedef struct MimeObjectClass MimeObjectClass;
+
+#ifdef ENABLE_SMIME
+class nsICMSMessage;
+#endif // ENABLE_SMIME
+
+/* (I don't pretend to understand this.) */
+#define cpp_stringify_noop_helper(x)#x
+#define cpp_stringify(x) cpp_stringify_noop_helper(x)
+
+#define MimeObjectClassInitializer(ITYPE,CSUPER) \
+ cpp_stringify(ITYPE), \
+ sizeof(ITYPE), \
+ (MimeObjectClass *) CSUPER, \
+ (int (*) (MimeObjectClass *)) ITYPE##ClassInitialize, \
+ 0
+
+/* Macro used for setting up class definitions.
+ */
+#define MimeDefClass(ITYPE,CTYPE,CVAR,CSUPER) \
+ static int ITYPE##ClassInitialize(ITYPE##Class *); \
+ ITYPE##Class CVAR = { ITYPE##ClassInitializer(ITYPE,CSUPER) }
+
+
+/* Creates a new (subclass of) MimeObject of the given class, with the
+ given headers (which are copied.)
+ */
+extern MimeObject *mime_new (MimeObjectClass *clazz, MimeHeaders *hdrs,
+ const char *override_content_type);
+
+
+/* Destroys a MimeObject (or subclass) and all data associated with it.
+ */
+extern "C" void mime_free (MimeObject *object);
+
+/* Given a content-type string, finds and returns an appropriate subclass
+ of MimeObject. A class object is returned. If `exact_match_p' is true,
+ then only fully-known types will be returned; that is, if it is true,
+ then "text/x-unknown" will return MimeInlineTextPlainType, but if it is
+ false, it will return NULL.
+ */
+extern MimeObjectClass *mime_find_class (const char *content_type,
+ MimeHeaders *hdrs,
+ MimeDisplayOptions *opts,
+ bool exact_match_p);
+
+/** Given a content-type string, creates and returns an appropriate subclass
+ * of MimeObject. The headers (from which the content-type was presumably
+ * extracted) are copied. forceInline is set to true when the caller wants
+ * the function to ignore opts->show_attachment_inline_p and force inline
+ * display, e.g., mimemalt wants the body part to be shown inline.
+ */
+extern MimeObject *mime_create (const char *content_type, MimeHeaders *hdrs,
+ MimeDisplayOptions *opts, bool forceInline = false);
+
+
+/* Querying the type hierarchy */
+extern bool mime_subclass_p(MimeObjectClass *child,
+ MimeObjectClass *parent);
+extern bool mime_typep(MimeObject *obj, MimeObjectClass *clazz);
+
+/* Returns a string describing the location of the part (like "2.5.3").
+ This is not a full URL, just a part-number.
+ */
+extern char *mime_part_address(MimeObject *obj);
+
+/* Returns a string describing the location of the *IMAP* part (like "2.5.3").
+ This is not a full URL, just a part-number.
+ This part is explicitly passed in the X-Mozilla-IMAP-Part header.
+ Return value must be freed by the caller.
+ */
+extern char *mime_imap_part_address(MimeObject *obj);
+
+extern char *mime_external_attachment_url(MimeObject *obj);
+
+/* Puts a part-number into a URL. If append_p is true, then the part number
+ is appended to any existing part-number already in that URL; otherwise,
+ it replaces it.
+ */
+extern char *mime_set_url_part(const char *url, const char *part, bool append_p);
+
+/*
+ cut the part of url for display a attachment as a email.
+*/
+extern char *mime_get_base_url(const char *url);
+
+/* Puts an *IMAP* part-number into a URL.
+ */
+extern char *mime_set_url_imap_part(const char *url, const char *part, const char *libmimepart);
+
+
+/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
+ number matches, and returns the MimeObject (else NULL.)
+ (part is not a URL -- it's of the form "1.3.5".)
+ */
+extern MimeObject *mime_address_to_part(const char *part, MimeObject *obj);
+
+
+/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
+ number matches; if one is found, returns the Content-Name of that part.
+ Else returns NULL. (part is not a URL -- it's of the form "1.3.5".)
+ */
+extern char *mime_find_suggested_name_of_part(const char *part,
+ MimeObject *obj);
+
+/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
+ number matches; if one is found, returns the Content-Name of that part.
+ Else returns NULL. (part is not a URL -- it's of the form "1.3.5".)
+ */
+extern char *mime_find_content_type_of_part(const char *part, MimeObject *obj);
+
+/* Parse the various "?" options off the URL and into the options struct.
+ */
+extern int mime_parse_url_options(const char *url, MimeDisplayOptions *);
+
+#ifdef ENABLE_SMIME
+
+/* Asks whether the given object is one of the cryptographically signed
+ or encrypted objects that we know about. (MimeMessageClass uses this
+ to decide if the headers need to be presented differently.)
+ */
+extern bool mime_crypto_object_p(MimeHeaders *, bool clearsigned_counts, MimeDisplayOptions *);
+
+/* Tells whether the given MimeObject is a message which has been encrypted
+ or signed. (Helper for MIME_GetMessageCryptoState()).
+ */
+extern void mime_get_crypto_state (MimeObject *obj,
+ bool *signed_p, bool *encrypted_p,
+ bool *signed_ok, bool *encrypted_ok);
+
+/* How the crypto code tells the MimeMessage object what the crypto stamp
+ on it says. */
+extern void mime_set_crypto_stamp(MimeObject *obj,
+ bool signed_p, bool encrypted_p);
+#endif // ENABLE_SMIME
+
+class MimeParseStateObject {
+public:
+
+ MimeParseStateObject()
+ {root = 0; separator_queued_p = false; separator_suppressed_p = false;
+ first_part_written_p = false; post_header_html_run_p = false; first_data_written_p = false;
+ strippingPart = false;
+ }
+ MimeObject *root; /* The outermost parser object. */
+
+ bool separator_queued_p; /* Whether a separator should be written out
+ before the next text is written (this lets
+ us write separators lazily, so that one
+ doesn't appear at the end, and so that more
+ than one don't appear in a row.) */
+
+ bool separator_suppressed_p; /* Whether the currently-queued separator
+ should not be printed; this is a kludge to
+ prevent seps from being printed just after
+ a header block... */
+
+ bool first_part_written_p; /* State used for the `Show Attachments As
+ Links' kludge. */
+
+ bool post_header_html_run_p; /* Whether we've run the
+ options->generate_post_header_html_fn */
+
+ bool first_data_written_p; /* State used for Mozilla lazy-stream-
+ creation evilness. */
+
+ nsTArray<nsCString> partsToStrip; /* if we're stripping parts, what parts to strip */
+ nsTArray<nsCString> detachToFiles; /* if we're detaching parts, where each part was detached to */
+ bool strippingPart;
+ nsCString detachedFilePath; /* if we've detached this part, filepath of detached part */
+};
+
+
+/* Some output-generation utility functions...
+ */
+extern int MimeObject_output_init(MimeObject *obj, const char *content_type);
+
+/* The `user_visible_p' argument says whether the output that has just been
+ written will cause characters or images to show up on the screen, that
+ is, it should be false if the stuff being written is merely structural
+ HTML or whitespace ("<P>", "</TABLE>", etc.) This information is used
+ when making the decision of whether a separating <HR> is needed.
+ */
+extern int MimeObject_write(MimeObject *, const char *data, int32_t length,
+ bool user_visible_p);
+extern int MimeOptions_write(MimeHeaders *,
+ MimeDisplayOptions *,
+ const char *data, int32_t length,
+ bool user_visible_p);
+
+/* Writes out the right kind of HR (or rather, queues it for writing.) */
+extern int MimeObject_write_separator(MimeObject *);
+
+extern bool MimeObjectIsMessageBody(MimeObject *obj);
+
+struct MimeDisplayData { /* This struct is what we hang off of
+ (context)->mime_data, to remember info
+ about the last MIME object we've
+ parsed and displayed. See
+ MimeGuessURLContentName() below.
+ */
+ MimeObject *last_parsed_object;
+ char *last_parsed_url;
+};
+
+#endif /* _MIMEI_H_ */
diff --git a/mailnews/mime/src/mimeiimg.cpp b/mailnews/mime/src/mimeiimg.cpp
new file mode 100644
index 000000000..1d47db2ab
--- /dev/null
+++ b/mailnews/mime/src/mimeiimg.cpp
@@ -0,0 +1,249 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "mimeiimg.h"
+#include "mimemoz2.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "nsMimeTypes.h"
+#include "nsMimeStringResources.h"
+#include "nsINetUtil.h"
+#include "nsMsgUtils.h"
+
+#define MIME_SUPERCLASS mimeLeafClass
+MimeDefClass(MimeInlineImage, MimeInlineImageClass,
+ mimeInlineImageClass, &MIME_SUPERCLASS);
+
+static int MimeInlineImage_initialize (MimeObject *);
+static void MimeInlineImage_finalize (MimeObject *);
+static int MimeInlineImage_parse_begin (MimeObject *);
+static int MimeInlineImage_parse_line (const char *, int32_t, MimeObject *);
+static int MimeInlineImage_parse_eof (MimeObject *, bool);
+static int MimeInlineImage_parse_decoded_buffer (const char *, int32_t, MimeObject *);
+
+static int
+MimeInlineImageClassInitialize(MimeInlineImageClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeLeafClass *lclass = (MimeLeafClass *) clazz;
+
+ NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ oclass->initialize = MimeInlineImage_initialize;
+ oclass->finalize = MimeInlineImage_finalize;
+ oclass->parse_begin = MimeInlineImage_parse_begin;
+ oclass->parse_line = MimeInlineImage_parse_line;
+ oclass->parse_eof = MimeInlineImage_parse_eof;
+ lclass->parse_decoded_buffer = MimeInlineImage_parse_decoded_buffer;
+
+ return 0;
+}
+
+
+static int
+MimeInlineImage_initialize (MimeObject *object)
+{
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object);
+}
+
+static void
+MimeInlineImage_finalize (MimeObject *object)
+{
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object);
+}
+
+static int
+MimeInlineImage_parse_begin (MimeObject *obj)
+{
+ MimeInlineImage *img = (MimeInlineImage *) obj;
+
+ int status;
+
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+ if (status < 0) return status;
+
+ if (!obj->output_p) return 0;
+
+ if (!obj->options || !obj->options->output_fn ||
+ // don't bother processing if the consumer doesn't want us
+ // gunking the body up.
+ obj->options->write_pure_bodies)
+ return 0;
+
+ if (obj->options &&
+ obj->options->image_begin &&
+ obj->options->write_html_p &&
+ obj->options->image_write_buffer)
+ {
+ char *html, *part, *image_url;
+ const char *ct;
+
+ part = mime_part_address(obj);
+ if (!part) return MIME_OUT_OF_MEMORY;
+
+ char *no_part_url = nullptr;
+ if (obj->options->part_to_load && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
+ no_part_url = mime_get_base_url(obj->options->url);
+
+ if (no_part_url)
+ {
+ image_url = mime_set_url_part(no_part_url, part, true);
+ PR_Free(no_part_url);
+ }
+ else
+ image_url = mime_set_url_part(obj->options->url, part, true);
+
+ if (!image_url)
+ {
+ PR_Free(part);
+ return MIME_OUT_OF_MEMORY;
+ }
+ PR_Free(part);
+
+ ct = obj->content_type;
+ if (!ct) ct = IMAGE_GIF; /* Can't happen? Close enough. */
+
+ // Fill in content type and attachment name here.
+ nsAutoCString url_with_filename(image_url);
+ url_with_filename += "&type=";
+ url_with_filename += ct;
+ char * filename = MimeHeaders_get_name ( obj->headers, obj->options );
+ if (filename)
+ {
+ nsCString escapedName;
+ MsgEscapeString(nsDependentCString(filename), nsINetUtil::ESCAPE_URL_PATH,
+ escapedName);
+ url_with_filename += "&filename=";
+ url_with_filename += escapedName;
+ PR_Free(filename);
+ }
+
+ // We need to separate images with HR's...
+ MimeObject_write_separator(obj);
+
+ img->image_data =
+ obj->options->image_begin(url_with_filename.get(), ct, obj->options->stream_closure);
+ PR_Free(image_url);
+
+ if (!img->image_data) return MIME_OUT_OF_MEMORY;
+
+ html = obj->options->make_image_html(img->image_data);
+ if (!html) return MIME_OUT_OF_MEMORY;
+
+ status = MimeObject_write(obj, html, strlen(html), true);
+ PR_Free(html);
+ if (status < 0) return status;
+ }
+
+ //
+ // Now we are going to see if we should set the content type in the
+ // URI for the url being run...
+ //
+ if (obj->options && obj->options->stream_closure && obj->content_type)
+ {
+ mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure);
+ if ( (msd) && (msd->channel) )
+ {
+ msd->channel->SetContentType(nsDependentCString(obj->content_type));
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+MimeInlineImage_parse_eof (MimeObject *obj, bool abort_p)
+{
+ MimeInlineImage *img = (MimeInlineImage *) obj;
+ int status;
+ if (obj->closed_p) return 0;
+
+ /* Force out any buffered data from the superclass (the base64 decoder.) */
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0) abort_p = true;
+
+ if (img->image_data)
+ {
+ obj->options->image_end(img->image_data,
+ (status < 0 ? status : (abort_p ? -1 : 0)));
+ img->image_data = 0;
+ }
+
+ return status;
+}
+
+
+static int
+MimeInlineImage_parse_decoded_buffer (const char *buf, int32_t size, MimeObject *obj)
+{
+ /* This is called (by MimeLeafClass->parse_buffer) with blocks of data
+ that have already been base64-decoded. Pass this raw image data
+ along to the backend-specific image display code.
+ */
+ MimeInlineImage *img = (MimeInlineImage *) obj;
+ int status;
+
+ /* Don't do a roundtrip through XPConnect when we're only interested in
+ * metadata and size. 0 means ok, the caller just checks for negative return
+ * value
+ */
+ if (obj->options && obj->options->metadata_only)
+ return 0;
+
+ if (obj->output_p &&
+ obj->options &&
+ !obj->options->write_html_p)
+ {
+ /* in this case, we just want the raw data...
+ Make the stream, if it's not made, and dump the data out.
+ */
+
+ if (!obj->options->state->first_data_written_p)
+ {
+ status = MimeObject_output_init(obj, 0);
+ if (status < 0) return status;
+ NS_ASSERTION(obj->options->state->first_data_written_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ }
+
+ return MimeObject_write(obj, buf, size, true);
+ }
+
+
+ if (!obj->options ||
+ !obj->options->image_write_buffer)
+ return 0;
+
+ /* If we don't have any image data, the image_end method must have already
+ been called, so don't call image_write_buffer again. */
+ if (!img->image_data) return 0;
+
+ /* Hand this data off to the backend-specific image display stream.
+ */
+ status = obj->options->image_write_buffer (buf, size, img->image_data);
+
+ /* If the image display stream fails, then close the stream - but do not
+ return the failure status, and do not give up on parsing this object.
+ Just because the image data was corrupt doesn't mean we need to give up
+ on the whole document; we can continue by just skipping over the rest of
+ this part, and letting our parent continue.
+ */
+ if (status < 0)
+ {
+ obj->options->image_end (img->image_data, status);
+ img->image_data = 0;
+ status = 0;
+ }
+
+ return status;
+}
+
+
+static int
+MimeInlineImage_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ NS_ERROR("This method should never be called (inline images do no line buffering).");
+ return -1;
+}
diff --git a/mailnews/mime/src/mimeiimg.h b/mailnews/mime/src/mimeiimg.h
new file mode 100644
index 000000000..ea8a9a3d1
--- /dev/null
+++ b/mailnews/mime/src/mimeiimg.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEIIMG_H_
+#define _MIMEIIMG_H_
+
+#include "mimeleaf.h"
+
+/* The MimeInlineImage class implements those MIME image types which can be
+ displayed inline.
+ */
+
+typedef struct MimeInlineImageClass MimeInlineImageClass;
+typedef struct MimeInlineImage MimeInlineImage;
+
+struct MimeInlineImageClass {
+ MimeLeafClass leaf;
+};
+
+extern MimeInlineImageClass mimeInlineImageClass;
+
+struct MimeInlineImage {
+ MimeLeaf leaf;
+
+ /* Opaque data object for the backend-specific inline-image-display code
+ (internal-external-reconnect nastiness.) */
+ void *image_data;
+};
+
+#define MimeInlineImageClassInitializer(ITYPE,CSUPER) \
+ { MimeLeafClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEIIMG_H_ */
diff --git a/mailnews/mime/src/mimeleaf.cpp b/mailnews/mime/src/mimeleaf.cpp
new file mode 100644
index 000000000..b76d0ce3b
--- /dev/null
+++ b/mailnews/mime/src/mimeleaf.cpp
@@ -0,0 +1,220 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "modmimee.h"
+#include "mimeleaf.h"
+#include "nsMimeTypes.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "nsMimeStringResources.h"
+
+#define MIME_SUPERCLASS mimeObjectClass
+MimeDefClass(MimeLeaf, MimeLeafClass, mimeLeafClass, &MIME_SUPERCLASS);
+
+static int MimeLeaf_initialize (MimeObject *);
+static void MimeLeaf_finalize (MimeObject *);
+static int MimeLeaf_parse_begin (MimeObject *);
+static int MimeLeaf_parse_buffer (const char *, int32_t, MimeObject *);
+static int MimeLeaf_parse_line (const char *, int32_t, MimeObject *);
+static int MimeLeaf_close_decoder (MimeObject *);
+static int MimeLeaf_parse_eof (MimeObject *, bool);
+static bool MimeLeaf_displayable_inline_p (MimeObjectClass *clazz,
+ MimeHeaders *hdrs);
+
+static int
+MimeLeafClassInitialize(MimeLeafClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ oclass->initialize = MimeLeaf_initialize;
+ oclass->finalize = MimeLeaf_finalize;
+ oclass->parse_begin = MimeLeaf_parse_begin;
+ oclass->parse_buffer = MimeLeaf_parse_buffer;
+ oclass->parse_line = MimeLeaf_parse_line;
+ oclass->parse_eof = MimeLeaf_parse_eof;
+ oclass->displayable_inline_p = MimeLeaf_displayable_inline_p;
+ clazz->close_decoder = MimeLeaf_close_decoder;
+
+ /* Default `parse_buffer' method is one which line-buffers the now-decoded
+ data and passes it on to `parse_line'. (We snarf the implementation of
+ this method from our superclass's implementation of `parse_buffer', which
+ inherited it from MimeObject.)
+ */
+ clazz->parse_decoded_buffer =
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_buffer;
+
+ return 0;
+}
+
+
+static int
+MimeLeaf_initialize (MimeObject *obj)
+{
+ /* This is an abstract class; it shouldn't be directly instantiated. */
+ NS_ASSERTION(obj->clazz != (MimeObjectClass *) &mimeLeafClass, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ // Initial size is -1 (meaning "unknown size") - we'll correct it in
+ // parse_buffer.
+ MimeLeaf *leaf = (MimeLeaf *) obj;
+ leaf->sizeSoFar = -1;
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj);
+}
+
+
+static void
+MimeLeaf_finalize (MimeObject *object)
+{
+ MimeLeaf *leaf = (MimeLeaf *)object;
+ object->clazz->parse_eof (object, false);
+
+ /* Free the decoder data, if it's still around. It was probably freed
+ in MimeLeaf_parse_eof(), but just in case... */
+ if (leaf->decoder_data)
+ {
+ MimeDecoderDestroy(leaf->decoder_data, true);
+ leaf->decoder_data = 0;
+ }
+
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (object);
+}
+
+
+static int
+MimeLeaf_parse_begin (MimeObject *obj)
+{
+ MimeLeaf *leaf = (MimeLeaf *) obj;
+ MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0;
+
+ /* Initialize a decoder if necessary.
+ */
+ if (!obj->encoding ||
+ // If we need the object as "raw" for saving or forwarding,
+ // don't decode text parts of message types. Other output formats,
+ // like "display" (nsMimeMessageBodyDisplay), need decoding.
+ (obj->options->format_out == nsMimeOutput::nsMimeMessageRaw &&
+ obj->parent && obj->parent->output_p &&
+ (!PL_strcasecmp(obj->parent->content_type, MESSAGE_NEWS) ||
+ !PL_strcasecmp(obj->parent->content_type, MESSAGE_RFC822))))
+ /* no-op */ ;
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_BASE64))
+ fn = &MimeB64DecoderInit;
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_QUOTED_PRINTABLE))
+ leaf->decoder_data =
+ MimeQPDecoderInit(((MimeConverterOutputCallback)
+ ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer),
+ obj, obj);
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE) ||
+ !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE2) ||
+ !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE3) ||
+ !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE4))
+ fn = &MimeUUDecoderInit;
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_YENCODE))
+ fn = &MimeYDecoderInit;
+
+ if (fn)
+ {
+ leaf->decoder_data =
+ fn (/* The MimeConverterOutputCallback cast is to turn the `void' argument
+ into `MimeObject'. */
+ ((MimeConverterOutputCallback)
+ ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer),
+ obj);
+
+ if (!leaf->decoder_data)
+ return MIME_OUT_OF_MEMORY;
+ }
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+}
+
+
+static int
+MimeLeaf_parse_buffer (const char *buffer, int32_t size, MimeObject *obj)
+{
+ MimeLeaf *leaf = (MimeLeaf *) obj;
+
+ NS_ASSERTION(!obj->closed_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (obj->closed_p) return -1;
+
+ /* If we're not supposed to write this object, bug out now.
+ */
+ if (!obj->output_p ||
+ !obj->options ||
+ !obj->options->output_fn)
+ return 0;
+
+ int rv;
+ if (leaf->sizeSoFar == -1)
+ leaf->sizeSoFar = 0;
+
+ if (leaf->decoder_data &&
+ obj->options &&
+ obj->options->format_out != nsMimeOutput::nsMimeMessageDecrypt
+ && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach) {
+ int outSize = 0;
+ rv = MimeDecoderWrite (leaf->decoder_data, buffer, size, &outSize);
+ leaf->sizeSoFar += outSize;
+ }
+ else {
+ rv = ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer (buffer, size,
+ obj);
+ leaf->sizeSoFar += size;
+ }
+ return rv;
+}
+
+static int
+MimeLeaf_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ NS_ERROR("MimeLeaf_parse_line shouldn't ever be called.");
+ return -1;
+}
+
+
+static int
+MimeLeaf_close_decoder (MimeObject *obj)
+{
+ MimeLeaf *leaf = (MimeLeaf *) obj;
+
+ if (leaf->decoder_data)
+ {
+ int status = MimeDecoderDestroy(leaf->decoder_data, false);
+ leaf->decoder_data = 0;
+ return status;
+ }
+
+ return 0;
+}
+
+
+static int
+MimeLeaf_parse_eof (MimeObject *obj, bool abort_p)
+{
+ MimeLeaf *leaf = (MimeLeaf *) obj;
+ if (obj->closed_p) return 0;
+
+ /* Close off the decoder, to cause it to give up any buffered data that
+ it is still holding.
+ */
+ if (leaf->decoder_data)
+ {
+ int status = MimeLeaf_close_decoder(obj);
+ if (status < 0) return status;
+ }
+
+ /* Now run the superclass's parse_eof, which will force out the line
+ buffer (which we may have just repopulated, above.)
+ */
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof (obj, abort_p);
+}
+
+
+static bool
+MimeLeaf_displayable_inline_p (MimeObjectClass *clazz, MimeHeaders *hdrs)
+{
+ return true;
+}
diff --git a/mailnews/mime/src/mimeleaf.h b/mailnews/mime/src/mimeleaf.h
new file mode 100644
index 000000000..10cfdc59a
--- /dev/null
+++ b/mailnews/mime/src/mimeleaf.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMELEAF_H_
+#define _MIMELEAF_H_
+
+#include "mimeobj.h"
+#include "modmimee.h"
+
+/* MimeLeaf is the class for the objects representing all MIME types which
+ are not containers for other MIME objects. The implication of this is
+ that they are MIME types which can have Content-Transfer-Encodings
+ applied to their data. This class provides that service in its
+ parse_buffer() method:
+
+ int (*parse_decoded_buffer) (const char *buf, int32_t size, MimeObject *obj)
+
+ The `parse_buffer' method of MimeLeaf passes each block of data through
+ the appropriate decoder (if any) and then calls `parse_decoded_buffer'
+ on each block (not line) of output.
+
+ The default `parse_decoded_buffer' method of MimeLeaf line-buffers the
+ now-decoded data, handing each line to the `parse_line' method in turn.
+ If different behavior is desired (for example, if a class wants access
+ to the decoded data before it is line-buffered) the `parse_decoded_buffer'
+ method should be overridden. (MimeExternalObject does this.)
+ */
+
+typedef struct MimeLeafClass MimeLeafClass;
+typedef struct MimeLeaf MimeLeaf;
+
+struct MimeLeafClass {
+ MimeObjectClass object;
+ /* This is the callback that is handed to the decoder. */
+ int (*parse_decoded_buffer) (const char *buf, int32_t size, MimeObject *obj);
+ int (*close_decoder) (MimeObject *obj);
+};
+
+extern MimeLeafClass mimeLeafClass;
+
+struct MimeLeaf {
+ MimeObject object; /* superclass variables */
+
+ /* If we're doing Base64, Quoted-Printable, or UU decoding, this is the
+ state object for the decoder. */
+ MimeDecoderData *decoder_data;
+
+ /* We want to count the size of the MimeObject to offer consumers the
+ * opportunity to display the sizes of attachments.
+ */
+ int sizeSoFar;
+};
+
+#define MimeLeafClassInitializer(ITYPE,CSUPER) \
+ { MimeObjectClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMELEAF_H_ */
diff --git a/mailnews/mime/src/mimemalt.cpp b/mailnews/mime/src/mimemalt.cpp
new file mode 100644
index 000000000..3354b1f9b
--- /dev/null
+++ b/mailnews/mime/src/mimemalt.cpp
@@ -0,0 +1,580 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ BACKGROUND
+ ----------
+
+ At the simplest level, multipart/alternative means "pick one of these and
+ display it." However, it's actually a lot more complicated than that.
+
+ The alternatives are in preference order, and counterintuitively, they go
+ from *least* to *most* preferred rather than the reverse. Therefore, when
+ we're parsing, we can't just take the first one we like and throw the rest
+ away -- we have to parse through the whole thing, discarding the n'th part if
+ we are capable of displaying the n+1'th.
+
+ Adding a wrinkle to that is the fact that we give the user the option of
+ demanding the plain-text alternative even though we are perfectly capable of
+ displaying the HTML, and it is almost always the preferred format, i.e., it
+ almost always comes after the plain-text alternative.
+
+ Speaking of which, you can't assume that each of the alternatives is just a
+ basic text/[whatever]. There may be, for example, a text/plain followed by a
+ multipart/related which contains text/html and associated embedded
+ images. Yikes!
+
+ You also can't assume that there will be just two parts. There can be an
+ arbitrary number, and the ones we are capable of displaying and the ones we
+ aren't could be interspersed in any order by the producer of the MIME.
+
+ We can't just throw away the parts we're not displaying when we're processing
+ the MIME for display. If we were to do that, then the MIME parts that
+ remained wouldn't get numbered properly, and that would mean, for example,
+ that deleting attachments wouldn't work in some messages. Indeed, that very
+ problem is what prompted a rewrite of this file into its current
+ architecture.
+
+ ARCHITECTURE
+ ------------
+
+ Parts are read and queued until we know whether we're going to display
+ them. If the first pending part is one we don't know how to display, then we
+ can add it to the MIME structure immediatelly, with output_p disabled. If the
+ first pending part is one we know how to display, then we can't add it to the
+ in-memory MIME structure until either (a) we encounter a later, more
+ preferred part we know how to display, or (b) we reach the end of the
+ parts. A display-capable part of the queue may be followed by one or more
+ display-incapable parts. We can't add them to the in-memory structure until
+ we figure out what to do with the first, display-capable pending part,
+ because otherwise the order and numbering will be wrong. All of the logic in
+ this paragraph is implemented in the flush_children function.
+
+ The display_cached_part function is what actually adds a MIME part to the
+ in-memory MIME structure. There is one complication there which forces us to
+ violate abstrations... Even if we set output_p on a child before adding it to
+ the parent, the parse_begin function resets it. The kluge I came up with to
+ prevent that was to give the child a separate options object and set
+ output_fn to nullptr in it, because that causes parse_begin to set output_p to
+ false. This seemed like the least onerous way to accomplish this, although I
+ can't say it's a solution I'm particularly fond of.
+
+ Another complication in display_cached_part is that if we were just a normal
+ multipart type, we could rely on MimeMultipart_parse_line to notify emitters
+ about content types, character sets, part numbers, etc. as our new children
+ get created. However, since we defer creation of some children, the
+ notification doesn't happen there, so we have to handle it
+ ourselves. Unfortunately, this requires a small abstraction violation in
+ MimeMultipart_parse_line -- we have to check there if the entity is
+ multipart/alternative and if so not notify emitters there because
+ MimeMultipartAlternative_create_child handles it.
+
+ - Jonathan Kamens, 2010-07-23
+
+ When the option prefer_plaintext is on, the last text/plain part
+ should be preferred over any other part that can be displayed. But
+ if no text/plain part is found, then the algorithm should go as
+ normal and convert any html part found to text. To achive this I
+ found that the simplest way was to change the function display_part_p
+ into returning priority as an integer instead of boolean can/can't
+ display. Then I also changed the function flush_children so it selects
+ the last part with the highest priority. (Priority 0 means it cannot
+ be displayed and the part is never choosen.)
+
+ - Terje Bråten, 2013-02-16
+*/
+
+#include "mimemalt.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "nsMimeTypes.h"
+#include "nsMimeStringResources.h"
+#include "nsIPrefBranch.h"
+#include "mimemoz2.h" // for prefs
+
+extern "C" MimeObjectClass mimeMultipartRelatedClass;
+
+#define MIME_SUPERCLASS mimeMultipartClass
+MimeDefClass(MimeMultipartAlternative, MimeMultipartAlternativeClass,
+ mimeMultipartAlternativeClass, &MIME_SUPERCLASS);
+
+static int MimeMultipartAlternative_initialize (MimeObject *);
+static void MimeMultipartAlternative_finalize (MimeObject *);
+static int MimeMultipartAlternative_parse_eof (MimeObject *, bool);
+static int MimeMultipartAlternative_create_child(MimeObject *);
+static int MimeMultipartAlternative_parse_child_line (MimeObject *, const char *,
+ int32_t, bool);
+static int MimeMultipartAlternative_close_child(MimeObject *);
+
+static int MimeMultipartAlternative_flush_children(MimeObject *, bool, priority_t);
+static priority_t MimeMultipartAlternative_display_part_p(MimeObject *self,
+ MimeHeaders *sub_hdrs);
+static priority_t MimeMultipartAlternative_prioritize_part(char *content_type,
+ bool prefer_plaintext);
+
+static int MimeMultipartAlternative_display_cached_part(MimeObject *,
+ MimeHeaders *,
+ MimePartBufferData *,
+ bool);
+
+static int
+MimeMultipartAlternativeClassInitialize(MimeMultipartAlternativeClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeMultipartClass *mclass = (MimeMultipartClass *) clazz;
+ PR_ASSERT(!oclass->class_initialized);
+ oclass->initialize = MimeMultipartAlternative_initialize;
+ oclass->finalize = MimeMultipartAlternative_finalize;
+ oclass->parse_eof = MimeMultipartAlternative_parse_eof;
+ mclass->create_child = MimeMultipartAlternative_create_child;
+ mclass->parse_child_line = MimeMultipartAlternative_parse_child_line;
+ mclass->close_child = MimeMultipartAlternative_close_child;
+ return 0;
+}
+
+
+static int
+MimeMultipartAlternative_initialize (MimeObject *obj)
+{
+ MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj;
+
+ NS_ASSERTION(!malt->part_buffers, "object initialized multiple times");
+ NS_ASSERTION(!malt->buffered_hdrs, "object initialized multiple times");
+ malt->pending_parts = 0;
+ malt->max_parts = 0;
+ malt->buffered_priority = PRIORITY_UNDISPLAYABLE;
+ malt->buffered_hdrs = nullptr;
+ malt->part_buffers = nullptr;
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj);
+}
+
+static void
+MimeMultipartAlternative_cleanup(MimeObject *obj)
+{
+ MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj;
+ int32_t i;
+
+ for (i = 0; i < malt->pending_parts; i++) {
+ MimeHeaders_free(malt->buffered_hdrs[i]);
+ MimePartBufferDestroy(malt->part_buffers[i]);
+ }
+ PR_FREEIF(malt->buffered_hdrs);
+ PR_FREEIF(malt->part_buffers);
+ malt->pending_parts = 0;
+ malt->max_parts = 0;
+}
+
+
+static void
+MimeMultipartAlternative_finalize (MimeObject *obj)
+{
+ MimeMultipartAlternative_cleanup(obj);
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj);
+}
+
+
+static int
+MimeMultipartAlternative_flush_children(MimeObject *obj,
+ bool finished,
+ priority_t next_priority)
+{
+ /*
+ The cache should always have at the head the part with highest priority.
+
+ Possible states:
+
+ 1. Cache contains nothing: do nothing.
+
+ 2. Finished, and the cache contains one displayable body followed
+ by zero or more bodies with lower priority:
+
+ 3. Finished, and the cache contains one non-displayable body:
+ create it with output off.
+
+ 4. Not finished, and the cache contains one displayable body
+ followed by zero or more bodies with lower priority, and the new
+ body we're about to create is higher or equal priority:
+ create all cached bodies with output off.
+
+ 5. Not finished, and the cache contains one displayable body
+ followed by zero or more bodies with lower priority, and the new
+ body we're about to create has lower priority: do nothing.
+
+ 6. Not finished, and the cache contains one non-displayable body:
+ create it with output off.
+ */
+ MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj;
+ bool have_displayable, do_flush, do_display;
+
+ /* Case 1 */
+ if (! malt->pending_parts)
+ return 0;
+
+ have_displayable = (malt->buffered_priority > next_priority);
+
+ if (finished && have_displayable) {
+ /* Case 2 */
+ do_flush = true;
+ do_display = true;
+ }
+ else if (finished && ! have_displayable) {
+ /* Case 3 */
+ do_flush = true;
+ do_display = false;
+ }
+ else if (! finished && have_displayable) {
+ /* Case 5 */
+ do_flush = false;
+ do_display = false;
+ }
+ else if (! finished && ! have_displayable) {
+ /* Case 4 */
+ /* Case 6 */
+ do_flush = true;
+ do_display = false;
+ }
+ else {
+ NS_ERROR("mimemalt.cpp: logic error in flush_children");
+ return -1;
+ }
+
+ if (do_flush) {
+ int32_t i;
+ for (i = 0; i < malt->pending_parts; i++) {
+ MimeMultipartAlternative_display_cached_part(obj,
+ malt->buffered_hdrs[i],
+ malt->part_buffers[i],
+ do_display && (i == 0));
+ MimeHeaders_free(malt->buffered_hdrs[i]);
+ MimePartBufferDestroy(malt->part_buffers[i]);
+ }
+ malt->pending_parts = 0;
+ }
+ return 0;
+}
+
+static int
+MimeMultipartAlternative_parse_eof (MimeObject *obj, bool abort_p)
+{
+ int status = 0;
+
+ if (obj->closed_p) return 0;
+
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0) return status;
+
+
+ status = MimeMultipartAlternative_flush_children(obj, true,
+ PRIORITY_UNDISPLAYABLE);
+ if (status < 0)
+ return status;
+
+ MimeMultipartAlternative_cleanup(obj);
+
+ return status;
+}
+
+
+static int
+MimeMultipartAlternative_create_child(MimeObject *obj)
+{
+ MimeMultipart *mult = (MimeMultipart *) obj;
+ MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj;
+
+ priority_t priority =
+ MimeMultipartAlternative_display_part_p (obj, mult->hdrs);
+
+ MimeMultipartAlternative_flush_children(obj, false, priority);
+
+ mult->state = MimeMultipartPartFirstLine;
+ int32_t i = malt->pending_parts++;
+
+ if (i==0) {
+ malt->buffered_priority = priority;
+ }
+
+ if (malt->pending_parts > malt->max_parts) {
+ malt->max_parts = malt->pending_parts;
+ MimeHeaders **newBuf = (MimeHeaders **)
+ PR_REALLOC(malt->buffered_hdrs,
+ malt->max_parts * sizeof(*malt->buffered_hdrs));
+ NS_ENSURE_TRUE(newBuf, MIME_OUT_OF_MEMORY);
+ malt->buffered_hdrs = newBuf;
+
+ MimePartBufferData **newBuf2 = (MimePartBufferData **)
+ PR_REALLOC(malt->part_buffers,
+ malt->max_parts * sizeof(*malt->part_buffers));
+ NS_ENSURE_TRUE(newBuf2, MIME_OUT_OF_MEMORY);
+ malt->part_buffers = newBuf2;
+ }
+
+ malt->buffered_hdrs[i] = MimeHeaders_copy(mult->hdrs);
+ NS_ENSURE_TRUE(malt->buffered_hdrs[i], MIME_OUT_OF_MEMORY);
+
+ malt->part_buffers[i] = MimePartBufferCreate();
+ NS_ENSURE_TRUE(malt->part_buffers[i], MIME_OUT_OF_MEMORY);
+
+ return 0;
+}
+
+
+static int
+MimeMultipartAlternative_parse_child_line (MimeObject *obj,
+ const char *line, int32_t length,
+ bool first_line_p)
+{
+ MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj;
+
+ NS_ASSERTION(malt->pending_parts, "should be pending parts, but there aren't");
+ if (!malt->pending_parts)
+ return -1;
+ int32_t i = malt->pending_parts - 1;
+
+ /* Push this line into the buffer for later retrieval. */
+ return MimePartBufferWrite (malt->part_buffers[i], line, length);
+}
+
+
+static int
+MimeMultipartAlternative_close_child(MimeObject *obj)
+{
+ MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj;
+ MimeMultipart *mult = (MimeMultipart *) obj;
+
+ /* PR_ASSERT(malt->part_buffer); Some Mac brokenness trips this...
+ if (!malt->part_buffer) return -1; */
+
+ if (malt->pending_parts)
+ MimePartBufferClose(malt->part_buffers[malt->pending_parts-1]);
+
+ /* PR_ASSERT(mult->hdrs); I expect the Mac trips this too */
+
+ if (mult->hdrs) {
+ MimeHeaders_free(mult->hdrs);
+ mult->hdrs = 0;
+ }
+
+ return 0;
+}
+
+
+static priority_t
+MimeMultipartAlternative_display_part_p(MimeObject *self,
+ MimeHeaders *sub_hdrs)
+{
+ priority_t priority = PRIORITY_UNDISPLAYABLE;
+ char *ct = MimeHeaders_get (sub_hdrs, HEADER_CONTENT_TYPE, true, false);
+ if (!ct)
+ return priority;
+
+ /* RFC 1521 says:
+ Receiving user agents should pick and display the last format
+ they are capable of displaying. In the case where one of the
+ alternatives is itself of type "multipart" and contains unrecognized
+ sub-parts, the user agent may choose either to show that alternative,
+ an earlier alternative, or both.
+ */
+
+ // We must pass 'true' as last parameter so that text/calendar is
+ // only displayable when Lightning is installed.
+ MimeObjectClass *clazz = mime_find_class(ct, sub_hdrs, self->options, true);
+ if (clazz && clazz->displayable_inline_p(clazz, sub_hdrs)) {
+ // prefer_plaintext pref
+ bool prefer_plaintext = false;
+ nsIPrefBranch *prefBranch = GetPrefBranch(self->options);
+ if (prefBranch) {
+ prefBranch->GetBoolPref("mailnews.display.prefer_plaintext",
+ &prefer_plaintext);
+ }
+ prefer_plaintext = prefer_plaintext &&
+ (self->options->format_out != nsMimeOutput::nsMimeMessageSaveAs) &&
+ (self->options->format_out != nsMimeOutput::nsMimeMessageRaw);
+
+ priority = MimeMultipartAlternative_prioritize_part(ct, prefer_plaintext);
+ }
+
+ PR_FREEIF(ct);
+ return priority;
+}
+
+/**
+* RFC 1521 says we should display the last format we are capable of displaying.
+* But for various reasons (mainly to improve the user experience) we choose
+* to ignore that in some cases, and rather pick one that we prioritize.
+*/
+static priority_t
+MimeMultipartAlternative_prioritize_part(char *content_type,
+ bool prefer_plaintext)
+{
+ /*
+ * PRIORITY_NORMAL is the priority of text/html, multipart/..., etc. that
+ * we normally display. We should try to have as few exceptions from
+ * PRIORITY_NORMAL as possible
+ */
+
+ /* (with no / in the type) */
+ if (!PL_strcasecmp(content_type, "text")) {
+ if (prefer_plaintext) {
+ /* When in plain text view, a plain text part is what we want. */
+ return PRIORITY_HIGH;
+ }
+ /* We normally prefer other parts over the unspecified text type. */
+ return PRIORITY_TEXT_UNKNOWN;
+ }
+
+ if (!PL_strncasecmp(content_type, "text/", 5)) {
+ char *text_type = content_type + 5;
+
+ if (!PL_strncasecmp(text_type, "plain", 5)) {
+ if (prefer_plaintext) {
+ /* When in plain text view,
+ the text/plain part is exactly what we want */
+ return PRIORITY_HIGHEST;
+ }
+ /*
+ * Because the html and the text part may be switched,
+ * or we have an extra text/plain added by f.ex. a buggy virus checker,
+ * we prioritize text/plain lower than normal.
+ */
+ return PRIORITY_TEXT_PLAIN;
+ }
+
+ if (!PL_strncasecmp(text_type, "calendar", 8) && prefer_plaintext) {
+ /*
+ * text/calendar receives an equally high priority so an invitation
+ * shows even in plaintext mode.
+ */
+ return PRIORITY_HIGHEST;
+ }
+
+ /* Need to white-list all text/... types that are or could be implemented. */
+ if (!PL_strncasecmp(text_type, "html", 4) ||
+ !PL_strncasecmp(text_type, "enriched", 8) ||
+ !PL_strncasecmp(text_type, "richtext", 8) ||
+ !PL_strncasecmp(text_type, "calendar", 8) ||
+ !PL_strncasecmp(text_type, "rtf", 3)) {
+ return PRIORITY_NORMAL;
+ }
+
+ /* We prefer other parts over unknown text types. */
+ return PRIORITY_TEXT_UNKNOWN;
+ }
+
+ // Guard against rogue messages with incorrect MIME structure and
+ // don't show images when plain text is requested.
+ if (!PL_strncasecmp(content_type, "image", 5)) {
+ if (prefer_plaintext)
+ return PRIORITY_UNDISPLAYABLE;
+ else
+ return PRIORITY_LOW;
+ }
+
+ return PRIORITY_NORMAL;
+}
+
+static int
+MimeMultipartAlternative_display_cached_part(MimeObject *obj,
+ MimeHeaders *hdrs,
+ MimePartBufferData *buffer,
+ bool do_display)
+{
+ int status;
+ bool old_options_no_output_p;
+
+ char *ct = (hdrs
+ ? MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, true, false)
+ : 0);
+ const char *dct = (((MimeMultipartClass *) obj->clazz)->default_part_type);
+ MimeObject *body;
+ /** Don't pass in NULL as the content-type (this means that the
+ * auto-uudecode-hack won't ever be done for subparts of a
+ * multipart, but only for untyped children of message/rfc822.
+ */
+ const char *uct = (ct && *ct) ? ct : (dct ? dct: TEXT_PLAIN);
+
+ // We always want to display the cached part inline.
+ body = mime_create(uct, hdrs, obj->options, true);
+ PR_FREEIF(ct);
+ if (!body) return MIME_OUT_OF_MEMORY;
+ body->output_p = do_display;
+
+ status = ((MimeContainerClass *) obj->clazz)->add_child(obj, body);
+ if (status < 0)
+ {
+ mime_free(body);
+ return status;
+ }
+ /* add_child assigns body->options from obj->options, but that's
+ just a pointer so if we muck with it in the child it'll modify
+ the parent as well, which we definitely don't want. Therefore we
+ need to make a copy of the old value and restore it later. */
+ old_options_no_output_p = obj->options->no_output_p;
+ if (! do_display)
+ body->options->no_output_p = true;
+
+#ifdef MIME_DRAFTS
+ /* if this object is a child of a multipart/related object, the parent is
+ taking care of decomposing the whole part, don't need to do it at this level.
+ However, we still have to call decompose_file_init_fn and decompose_file_close_fn
+ in order to set the correct content-type. But don't call MimePartBufferRead
+ */
+ bool multipartRelatedChild = mime_typep(obj->parent,(MimeObjectClass*)&mimeMultipartRelatedClass);
+ bool decomposeFile = do_display && obj->options &&
+ obj->options->decompose_file_p &&
+ obj->options->decompose_file_init_fn &&
+ !mime_typep(body, (MimeObjectClass *) &mimeMultipartClass);
+
+ if (decomposeFile)
+ {
+ status = obj->options->decompose_file_init_fn (
+ obj->options->stream_closure, hdrs);
+ if (status < 0) return status;
+ }
+#endif /* MIME_DRAFTS */
+
+ /* Now that we've added this new object to our list of children,
+ notify emitters and start its parser going. */
+ MimeMultipart_notify_emitter(body);
+
+ status = body->clazz->parse_begin(body);
+ if (status < 0) return status;
+
+#ifdef MIME_DRAFTS
+ if (decomposeFile && !multipartRelatedChild)
+ status = MimePartBufferRead (buffer,
+ obj->options->decompose_file_output_fn,
+ obj->options->stream_closure);
+ else
+#endif /* MIME_DRAFTS */
+
+ status = MimePartBufferRead (buffer,
+ /* The MimeConverterOutputCallback cast is to turn the
+ `void' argument into `MimeObject'. */
+ ((MimeConverterOutputCallback) body->clazz->parse_buffer),
+ body);
+
+ if (status < 0) return status;
+
+ /* Done parsing. */
+ status = body->clazz->parse_eof(body, false);
+ if (status < 0) return status;
+ status = body->clazz->parse_end(body, false);
+ if (status < 0) return status;
+
+#ifdef MIME_DRAFTS
+ if (decomposeFile)
+ {
+ status = obj->options->decompose_file_close_fn ( obj->options->stream_closure );
+ if (status < 0) return status;
+ }
+#endif /* MIME_DRAFTS */
+
+ /* Restore options to what parent classes expects. */
+ obj->options->no_output_p = old_options_no_output_p;
+
+ return 0;
+}
diff --git a/mailnews/mime/src/mimemalt.h b/mailnews/mime/src/mimemalt.h
new file mode 100644
index 000000000..6cd792f54
--- /dev/null
+++ b/mailnews/mime/src/mimemalt.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMALT_H_
+#define _MIMEMALT_H_
+
+#include "mimemult.h"
+#include "mimepbuf.h"
+
+/* The MimeMultipartAlternative class implements the multipart/alternative
+ MIME container, which displays only one (the `best') of a set of enclosed
+ documents.
+ */
+
+typedef struct MimeMultipartAlternativeClass MimeMultipartAlternativeClass;
+typedef struct MimeMultipartAlternative MimeMultipartAlternative;
+
+struct MimeMultipartAlternativeClass {
+ MimeMultipartClass multipart;
+};
+
+extern "C" MimeMultipartAlternativeClass mimeMultipartAlternativeClass;
+
+enum priority_t {PRIORITY_UNDISPLAYABLE,
+ PRIORITY_LOW,
+ PRIORITY_TEXT_UNKNOWN,
+ PRIORITY_TEXT_PLAIN,
+ PRIORITY_NORMAL,
+ PRIORITY_HIGH,
+ PRIORITY_HIGHEST};
+
+struct MimeMultipartAlternative {
+ MimeMultipart multipart; /* superclass variables */
+
+ MimeHeaders **buffered_hdrs; /* The headers of pending parts */
+ MimePartBufferData **part_buffers; /* The data of pending parts
+ (see mimepbuf.h) */
+ int32_t pending_parts;
+ int32_t max_parts;
+ priority_t buffered_priority; /* Priority of head of pending parts */
+};
+
+#define MimeMultipartAlternativeClassInitializer(ITYPE,CSUPER) \
+ { MimeMultipartClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEMALT_H_ */
diff --git a/mailnews/mime/src/mimemapl.cpp b/mailnews/mime/src/mimemapl.cpp
new file mode 100644
index 000000000..449d80ac0
--- /dev/null
+++ b/mailnews/mime/src/mimemapl.cpp
@@ -0,0 +1,189 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "mimemapl.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "nsMimeStringResources.h"
+#include "mimemoz2.h"
+#include "nsMimeTypes.h"
+
+#define MIME_SUPERCLASS mimeMultipartClass
+MimeDefClass(MimeMultipartAppleDouble, MimeMultipartAppleDoubleClass,
+ mimeMultipartAppleDoubleClass, &MIME_SUPERCLASS);
+
+static int MimeMultipartAppleDouble_parse_begin (MimeObject *);
+static bool MimeMultipartAppleDouble_output_child_p(MimeObject *,
+ MimeObject *);
+
+static int
+MimeMultipartAppleDoubleClassInitialize(MimeMultipartAppleDoubleClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeMultipartClass *mclass = (MimeMultipartClass *) clazz;
+
+ NS_ASSERTION(!oclass->class_initialized, "mime class not initialized");
+ oclass->parse_begin = MimeMultipartAppleDouble_parse_begin;
+ mclass->output_child_p = MimeMultipartAppleDouble_output_child_p;
+ return 0;
+}
+
+static int
+MimeMultipartAppleDouble_parse_begin (MimeObject *obj)
+{
+ /* #### This method is identical to MimeExternalObject_parse_begin
+ which kinda s#$%s...
+ */
+ int status;
+
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+ if (status < 0) return status;
+
+ /* If we're writing this object, and we're doing it in raw form, then
+ now is the time to inform the backend what the type of this data is.
+ */
+ if (obj->output_p &&
+ obj->options &&
+ !obj->options->write_html_p &&
+ !obj->options->state->first_data_written_p)
+ {
+ status = MimeObject_output_init(obj, 0);
+ if (status < 0) return status;
+ NS_ASSERTION(obj->options->state->first_data_written_p, "first data not written");
+ }
+
+#ifdef XP_MACOSX
+ if (obj->options && obj->options->state)
+ {
+// obj->options->state->separator_suppressed_p = true;
+ goto done;
+ }
+ /*
+ * It would be nice to not showing the resource fork links
+ * if we are displaying inline. But, there is no way we could
+ * know ahead of time that we could display the data fork and
+ * the data fork is always hidden on MAC platform.
+ */
+#endif
+ /* If we're writing this object as HTML, then emit a link for the
+ multipart/appledouble part (both links) that looks just like the
+ links that MimeExternalObject emits for external leaf parts.
+ */
+ if (obj->options &&
+ obj->output_p &&
+ obj->options->write_html_p &&
+ obj->options->output_fn)
+ {
+ char *id = 0;
+ char *id_url = 0;
+ char *id_imap = 0;
+
+ id = mime_part_address (obj);
+ if (! id) return MIME_OUT_OF_MEMORY;
+ if (obj->options->missing_parts)
+ id_imap = mime_imap_part_address (obj);
+
+ if (obj->options && obj->options->url)
+ {
+ const char *url = obj->options->url;
+ if (id_imap && id)
+ {
+ /* if this is an IMAP part. */
+ id_url = mime_set_url_imap_part(url, id_imap, id);
+ }
+ else
+ {
+ /* This is just a normal MIME part as usual. */
+ id_url = mime_set_url_part(url, id, true);
+ }
+ if (!id_url)
+ {
+ PR_Free(id);
+ return MIME_OUT_OF_MEMORY;
+ }
+ }
+
+/**********************
+ if (!strcmp (id, "0"))
+ {
+ PR_Free(id);
+ id = MimeGetStringByID(MIME_MSG_ATTACHMENT);
+ }
+ else
+ {
+ const char *p = "Part ";
+ char *s = (char *)PR_MALLOC(strlen(p) + strlen(id) + 1);
+ if (!s)
+ {
+ PR_Free(id);
+ PR_Free(id_url);
+ return MIME_OUT_OF_MEMORY;
+ }
+ PL_strcpy(s, p);
+ PL_strcat(s, id);
+ PR_Free(id);
+ id = s;
+ }
+
+ if (all_headers_p &&
+ // Don't bother showing all headers on this part if it's the only
+ // part in the message: in that case, we've already shown these
+ // headers.
+ obj->options->state &&
+ obj->options->state->root == obj->parent)
+ all_headers_p = false;
+
+ newopt.fancy_headers_p = true;
+ newopt.headers = (all_headers_p ? MimeHeadersAll : MimeHeadersSome);
+
+//
+RICHIE SHERRY
+GOTTA STILL DO THIS FOR QUOTING!
+ status = MimeHeaders_write_attachment_box (obj->headers, &newopt,
+ obj->content_type,
+ obj->encoding,
+ id_name? id_name : id, id_url, 0
+//
+*********************************************************************************/
+
+// FAIL:
+ PR_FREEIF(id);
+ PR_FREEIF(id_url);
+ PR_FREEIF(id_imap);
+ if (status < 0) return status;
+ }
+
+#ifdef XP_MACOSX
+done:
+#endif
+
+ return 0;
+}
+
+static bool
+MimeMultipartAppleDouble_output_child_p(MimeObject *obj, MimeObject *child)
+{
+ MimeContainer *cont = (MimeContainer *) obj;
+
+ /* If this is the first child, and it's an application/applefile, then
+ don't emit a link for it. (There *should* be only two children, and
+ the first one should always be an application/applefile.)
+ */
+
+ if (cont->nchildren >= 1 && cont->children[0] == child && child->content_type &&
+ !PL_strcasecmp(child->content_type, APPLICATION_APPLEFILE))
+ {
+#ifdef XP_MACOSX
+ if (obj->output_p && obj->options && obj->options->write_html_p) //output HTML
+ return false;
+#else
+ /* if we are not on a Macintosh, don't emitte the resources fork at all. */
+ return false;
+#endif
+ }
+
+ return true;
+}
diff --git a/mailnews/mime/src/mimemapl.h b/mailnews/mime/src/mimemapl.h
new file mode 100644
index 000000000..2ba48afdc
--- /dev/null
+++ b/mailnews/mime/src/mimemapl.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMAPL_H_
+#define _MIMEMAPL_H_
+
+#include "mimemult.h"
+
+/* The MimeMultipartAppleDouble class implements the multipart/appledouble
+ MIME container, which provides a method of encapsulating and reconstructing
+ a two-forked Macintosh file.
+ */
+
+typedef struct MimeMultipartAppleDoubleClass MimeMultipartAppleDoubleClass;
+typedef struct MimeMultipartAppleDouble MimeMultipartAppleDouble;
+
+struct MimeMultipartAppleDoubleClass {
+ MimeMultipartClass multipart;
+};
+
+extern MimeMultipartAppleDoubleClass mimeMultipartAppleDoubleClass;
+
+struct MimeMultipartAppleDouble {
+ MimeMultipart multipart;
+};
+
+#define MimeMultipartAppleDoubleClassInitializer(ITYPE,CSUPER) \
+ { MimeMultipartClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEMAPL_H_ */
diff --git a/mailnews/mime/src/mimemcms.cpp b/mailnews/mime/src/mimemcms.cpp
new file mode 100644
index 000000000..96aa5481f
--- /dev/null
+++ b/mailnews/mime/src/mimemcms.cpp
@@ -0,0 +1,494 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICMSMessage.h"
+#include "nsICMSMessageErrors.h"
+#include "nsICMSDecoder.h"
+#include "nsICryptoHash.h"
+#include "mimemcms.h"
+#include "mimecryp.h"
+#include "nsMimeTypes.h"
+#include "nspr.h"
+#include "nsMimeStringResources.h"
+#include "mimemsg.h"
+#include "mimemoz2.h"
+#include "nsIURI.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsIMsgSMIMEHeaderSink.h"
+#include "nsCOMPtr.h"
+#include "nsIX509Cert.h"
+#include "plstr.h"
+#include "nsComponentManagerUtils.h"
+
+#define MIME_SUPERCLASS mimeMultipartSignedClass
+MimeDefClass(MimeMultipartSignedCMS, MimeMultipartSignedCMSClass,
+ mimeMultipartSignedCMSClass, &MIME_SUPERCLASS);
+
+static int MimeMultipartSignedCMS_initialize (MimeObject *);
+
+static void *MimeMultCMS_init (MimeObject *);
+static int MimeMultCMS_data_hash (const char *, int32_t, void *);
+static int MimeMultCMS_sig_hash (const char *, int32_t, void *);
+static int MimeMultCMS_data_eof (void *, bool);
+static int MimeMultCMS_sig_eof (void *, bool);
+static int MimeMultCMS_sig_init (void *, MimeObject *, MimeHeaders *);
+static char * MimeMultCMS_generate (void *);
+static void MimeMultCMS_free (void *);
+static void MimeMultCMS_suppressed_child(void *crypto_closure);
+
+extern int SEC_ERROR_CERT_ADDR_MISMATCH;
+
+static int
+MimeMultipartSignedCMSClassInitialize(MimeMultipartSignedCMSClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeMultipartSignedClass *sclass = (MimeMultipartSignedClass *) clazz;
+
+ oclass->initialize = MimeMultipartSignedCMS_initialize;
+
+ sclass->crypto_init = MimeMultCMS_init;
+ sclass->crypto_data_hash = MimeMultCMS_data_hash;
+ sclass->crypto_data_eof = MimeMultCMS_data_eof;
+ sclass->crypto_signature_init = MimeMultCMS_sig_init;
+ sclass->crypto_signature_hash = MimeMultCMS_sig_hash;
+ sclass->crypto_signature_eof = MimeMultCMS_sig_eof;
+ sclass->crypto_generate_html = MimeMultCMS_generate;
+ sclass->crypto_notify_suppressed_child = MimeMultCMS_suppressed_child;
+ sclass->crypto_free = MimeMultCMS_free;
+
+ PR_ASSERT(!oclass->class_initialized);
+ return 0;
+}
+
+static int
+MimeMultipartSignedCMS_initialize (MimeObject *object)
+{
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object);
+}
+
+
+typedef struct MimeMultCMSdata
+{
+ int16_t hash_type;
+ nsCOMPtr<nsICryptoHash> data_hash_context;
+ nsCOMPtr<nsICMSDecoder> sig_decoder_context;
+ nsCOMPtr<nsICMSMessage> content_info;
+ char *sender_addr;
+ bool decoding_failed;
+ unsigned char* item_data;
+ uint32_t item_len;
+ MimeObject *self;
+ nsCOMPtr<nsIMsgSMIMEHeaderSink> smimeHeaderSink;
+ nsCString url;
+
+ MimeMultCMSdata()
+ :hash_type(0),
+ sender_addr(nullptr),
+ decoding_failed(false),
+ item_data(nullptr),
+ self(nullptr) {}
+
+ ~MimeMultCMSdata()
+ {
+ PR_FREEIF(sender_addr);
+
+ // Do a graceful shutdown of the nsICMSDecoder and release the nsICMSMessage //
+ if (sig_decoder_context)
+ {
+ nsCOMPtr<nsICMSMessage> cinfo;
+ sig_decoder_context->Finish(getter_AddRefs(cinfo));
+ }
+
+ delete [] item_data;
+ }
+} MimeMultCMSdata;
+
+/* #### MimeEncryptedCMS and MimeMultipartSignedCMS have a sleazy,
+ incestuous, dysfunctional relationship. */
+extern bool MimeAnyParentCMSSigned(MimeObject *obj);
+extern void MimeCMSGetFromSender(MimeObject *obj,
+ nsCString &from_addr,
+ nsCString &from_name,
+ nsCString &sender_addr,
+ nsCString &sender_name);
+extern bool MimeCMSHeadersAndCertsMatch(MimeObject *obj,
+ nsICMSMessage *,
+ bool *signing_cert_without_email_address);
+extern void MimeCMSRequestAsyncSignatureVerification(nsICMSMessage *aCMSMsg,
+ const char *aFromAddr, const char *aFromName,
+ const char *aSenderAddr, const char *aSenderName,
+ nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel,
+ const nsCString &aMsgNeckoURL,
+ unsigned char* item_data, uint32_t item_len,
+ int16_t digest_type);
+extern char *MimeCMS_MakeSAURL(MimeObject *obj);
+extern char *IMAP_CreateReloadAllPartsUrl(const char *url);
+extern int MIMEGetRelativeCryptoNestLevel(MimeObject *obj);
+
+static void *
+MimeMultCMS_init (MimeObject *obj)
+{
+ MimeHeaders *hdrs = obj->headers;
+ MimeMultCMSdata *data = 0;
+ char *ct, *micalg;
+ int16_t hash_type;
+ nsresult rv;
+
+ data = new MimeMultCMSdata;
+ if (!data)
+ return 0;
+
+ data->self = obj;
+
+ mime_stream_data *msd = (mime_stream_data *) (data->self->options->stream_closure);
+ if (msd)
+ {
+ nsIChannel *channel = msd->channel; // note the lack of ref counting...
+ if (channel)
+ {
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIMsgHeaderSink> headerSink;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl;
+ nsCOMPtr<nsISupports> securityInfo;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri)
+ {
+ rv = uri->GetSpec(data->url);
+
+ // We only want to update the UI if the current mime transaction
+ // is intended for display.
+ // If the current transaction is intended for background processing,
+ // we can learn that by looking at the additional header=filter
+ // string contained in the URI.
+ //
+ // If we find something, we do not set smimeHeaderSink,
+ // which will prevent us from giving UI feedback.
+ //
+ // If we do not find header=filter, we assume the result of the
+ // processing will be shown in the UI.
+
+ if (!strstr(data->url.get(), "?header=filter") &&
+ !strstr(data->url.get(), "&header=filter") &&
+ !strstr(data->url.get(), "?header=attach") &&
+ !strstr(data->url.get(), "&header=attach"))
+ {
+ msgurl = do_QueryInterface(uri);
+ if (msgurl)
+ msgurl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ msgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink));
+ if (headerSink)
+ headerSink->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (securityInfo)
+ data->smimeHeaderSink = do_QueryInterface(securityInfo);
+ }
+ }
+ } // if channel
+ } // if msd
+
+ if (obj->parent && MimeAnyParentCMSSigned(obj)) {
+ // Parent is signed. We know this part is a signature, too, because
+ // multipart doesn't allow encryption.
+ // We don't support "inner sign" with outer sign, because the
+ // inner encrypted part could have been produced by an attacker who
+ // stripped away a part containing the signature (S/MIME doesn't
+ // have integrity protection).
+ // Also we don't want to support sign-then-sign, that's misleading,
+ // which part would be shown as having a signature?
+ // TODO: should we show all contents, without any signature info?
+
+ if (data->smimeHeaderSink) {
+ data->smimeHeaderSink->SignedStatus(
+ MIMEGetRelativeCryptoNestLevel(data->self),
+ nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url);
+ }
+ delete data;
+ PR_SetError(-1, 0);
+ return 0;
+ }
+
+ ct = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false);
+ if (!ct) {
+ delete data;
+ return 0; /* #### bogus message? out of memory? */
+ }
+ micalg = MimeHeaders_get_parameter(ct, PARAM_MICALG, NULL, NULL);
+ PR_Free(ct);
+ ct = 0;
+ if (!micalg) {
+ delete data;
+ return 0; /* #### bogus message? out of memory? */
+ }
+
+ if (!PL_strcasecmp(micalg, PARAM_MICALG_MD5) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_MD5_2))
+ hash_type = nsICryptoHash::MD5;
+ else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA1) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_2) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_3) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_4) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_5))
+ hash_type = nsICryptoHash::SHA1;
+ else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA256) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_2) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_3))
+ hash_type = nsICryptoHash::SHA256;
+ else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA384) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_2) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_3))
+ hash_type = nsICryptoHash::SHA384;
+ else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA512) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_2) ||
+ !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_3))
+ hash_type = nsICryptoHash::SHA512;
+ else
+ hash_type = -1;
+
+ PR_Free(micalg);
+ micalg = 0;
+
+ if (hash_type == -1) {
+ delete data;
+ return 0; /* #### bogus message? */
+ }
+
+ data->hash_type = hash_type;
+
+ data->data_hash_context =
+ do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ if (NS_FAILED(rv)) {
+ delete data;
+ return 0;
+ }
+
+ rv = data->data_hash_context->Init(data->hash_type);
+ if (NS_FAILED(rv)) {
+ delete data;
+ return 0;
+ }
+
+ PR_SetError(0, 0);
+
+ return data;
+}
+
+static int
+MimeMultCMS_data_hash (const char *buf, int32_t size, void *crypto_closure)
+{
+ MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure;
+ if (!data || !data->data_hash_context) {
+ return -1;
+ }
+
+ PR_SetError(0, 0);
+ nsresult rv = data->data_hash_context->Update((unsigned char *) buf, size);
+ data->decoding_failed = NS_FAILED(rv);
+
+ return 0;
+}
+
+static int
+MimeMultCMS_data_eof (void *crypto_closure, bool abort_p)
+{
+ MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure;
+ if (!data || !data->data_hash_context) {
+ return -1;
+ }
+
+ nsAutoCString hashString;
+ data->data_hash_context->Finish(false, hashString);
+ PR_SetError(0, 0);
+
+ data->item_len = hashString.Length();
+ data->item_data = new unsigned char[data->item_len];
+ if (!data->item_data) return MIME_OUT_OF_MEMORY;
+
+ memcpy(data->item_data, hashString.get(), data->item_len);
+
+ // Release our reference to nsICryptoHash //
+ data->data_hash_context = nullptr;
+
+ /* At this point, data->item.data contains a digest for the first part.
+ When we process the signature, the security library will compare this
+ digest to what's in the signature object. */
+
+ return 0;
+}
+
+
+static int
+MimeMultCMS_sig_init (void *crypto_closure,
+ MimeObject *multipart_object,
+ MimeHeaders *signature_hdrs)
+{
+ MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure;
+ char *ct;
+ int status = 0;
+ nsresult rv;
+
+ if (!signature_hdrs) {
+ return -1;
+ }
+
+ ct = MimeHeaders_get (signature_hdrs, HEADER_CONTENT_TYPE, true, false);
+
+ /* Verify that the signature object is of the right type. */
+ if (!ct || /* is not a signature type */
+ (PL_strcasecmp(ct, APPLICATION_XPKCS7_SIGNATURE) != 0
+ && PL_strcasecmp(ct, APPLICATION_PKCS7_SIGNATURE) != 0)) {
+ status = -1; /* #### error msg about bogus message */
+ }
+ PR_FREEIF(ct);
+ if (status < 0) return status;
+
+ data->sig_decoder_context = do_CreateInstance(NS_CMSDECODER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return 0;
+
+ rv = data->sig_decoder_context->Start(nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ status = PR_GetError();
+ if (status >= 0) status = -1;
+ }
+ return status;
+}
+
+
+static int
+MimeMultCMS_sig_hash (const char *buf, int32_t size, void *crypto_closure)
+{
+ MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure;
+ nsresult rv;
+
+ if (!data || !data->sig_decoder_context) {
+ return -1;
+ }
+
+ rv = data->sig_decoder_context->Update(buf, size);
+ data->decoding_failed = NS_FAILED(rv);
+
+ return 0;
+}
+
+static int
+MimeMultCMS_sig_eof (void *crypto_closure, bool abort_p)
+{
+ MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure;
+
+ if (!data) {
+ return -1;
+ }
+
+ /* Hand an EOF to the crypto library.
+
+ We save away the value returned and will use it later to emit a
+ blurb about whether the signature validation was cool.
+ */
+
+ if (data->sig_decoder_context) {
+ data->sig_decoder_context->Finish(getter_AddRefs(data->content_info));
+
+ // Release our reference to nsICMSDecoder //
+ data->sig_decoder_context = nullptr;
+ }
+
+ return 0;
+}
+
+static void
+MimeMultCMS_free (void *crypto_closure)
+{
+ MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure;
+ if (!data) return;
+
+ delete data;
+}
+
+static void MimeMultCMS_suppressed_child(void *crypto_closure) {
+ // I'm a multipart/signed. If one of my cryptographic child elements
+ // was suppressed, then I want my signature to be shown as invalid.
+ MimeMultCMSdata *data = (MimeMultCMSdata *)crypto_closure;
+ if (data && data->smimeHeaderSink) {
+ data->smimeHeaderSink->SignedStatus(
+ MIMEGetRelativeCryptoNestLevel(data->self),
+ nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url);
+ }
+}
+
+static char *
+MimeMultCMS_generate (void *crypto_closure)
+{
+ MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure;
+ if (!data) return 0;
+ nsCOMPtr<nsIX509Cert> signerCert;
+
+ int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self);
+
+ if (aRelativeNestLevel < 0)
+ return nullptr;
+
+ int32_t maxNestLevel = 0;
+ if (data->smimeHeaderSink && aRelativeNestLevel >= 0)
+ {
+ data->smimeHeaderSink->MaxWantedNesting(&maxNestLevel);
+
+ if (aRelativeNestLevel > maxNestLevel)
+ return nullptr;
+ }
+
+ if (data->self->options->missing_parts)
+ {
+ // We were not given all parts of the message.
+ // We are therefore unable to verify correctness of the signature.
+
+ if (data->smimeHeaderSink) {
+ data->smimeHeaderSink->SignedStatus(aRelativeNestLevel,
+ nsICMSMessageErrors::VERIFY_NOT_YET_ATTEMPTED,
+ nullptr, data->url);
+ }
+ return nullptr;
+ }
+
+ if (!data->content_info)
+ {
+ /* No content_info at all -- since we're inside a multipart/signed,
+ that means that we've either gotten a message that was truncated
+ before the signature part, or we ran out of memory, or something
+ awful has happened.
+ */
+ return nullptr;
+ }
+
+ nsCString from_addr;
+ nsCString from_name;
+ nsCString sender_addr;
+ nsCString sender_name;
+
+ MimeCMSGetFromSender(data->self,
+ from_addr, from_name,
+ sender_addr, sender_name);
+
+ MimeCMSRequestAsyncSignatureVerification(data->content_info,
+ from_addr.get(), from_name.get(),
+ sender_addr.get(), sender_name.get(),
+ data->smimeHeaderSink, aRelativeNestLevel,
+ data->url,
+ data->item_data, data->item_len,
+ data->hash_type);
+
+ if (data->content_info)
+ {
+#if 0 // XXX Fix this. What do we do here? //
+ if (SEC_CMSContainsCertsOrCrls(data->content_info))
+ {
+ /* #### call libsec telling it to import the certs */
+ }
+#endif
+ }
+
+ return nullptr;
+}
diff --git a/mailnews/mime/src/mimemcms.h b/mailnews/mime/src/mimemcms.h
new file mode 100644
index 000000000..54f41da1b
--- /dev/null
+++ b/mailnews/mime/src/mimemcms.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMPKC_H_
+#define _MIMEMPKC_H_
+
+#include "mimemsig.h"
+
+class nsICMSMessage;
+
+/* The MimeMultipartSignedCMS class implements a multipart/signed MIME
+ container with protocol=application/x-CMS-signature, which passes the
+ signed object through CMS code to verify the signature. See mimemsig.h
+ for details of the general mechanism on which this is built.
+ */
+
+typedef struct MimeMultipartSignedCMSClass MimeMultipartSignedCMSClass;
+typedef struct MimeMultipartSignedCMS MimeMultipartSignedCMS;
+
+struct MimeMultipartSignedCMSClass {
+ MimeMultipartSignedClass msigned;
+};
+
+extern MimeMultipartSignedCMSClass mimeMultipartSignedCMSClass;
+
+struct MimeMultipartSignedCMS {
+ MimeMultipartSigned msigned;
+};
+
+#define MimeMultipartSignedCMSClassInitializer(ITYPE,CSUPER) \
+ { MimeMultipartSignedClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEMPKC_H_ */
diff --git a/mailnews/mime/src/mimemdig.cpp b/mailnews/mime/src/mimemdig.cpp
new file mode 100644
index 000000000..6b318fb2b
--- /dev/null
+++ b/mailnews/mime/src/mimemdig.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimemdig.h"
+#include "prlog.h"
+#include "nsMimeTypes.h"
+
+#define MIME_SUPERCLASS mimeMultipartClass
+MimeDefClass(MimeMultipartDigest, MimeMultipartDigestClass,
+ mimeMultipartDigestClass, &MIME_SUPERCLASS);
+
+static int
+MimeMultipartDigestClassInitialize(MimeMultipartDigestClass *clazz)
+{
+#ifdef DEBUG
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ PR_ASSERT(!oclass->class_initialized);
+#endif
+ MimeMultipartClass *mclass = (MimeMultipartClass *) clazz;
+ mclass->default_part_type = MESSAGE_RFC822;
+ return 0;
+}
diff --git a/mailnews/mime/src/mimemdig.h b/mailnews/mime/src/mimemdig.h
new file mode 100644
index 000000000..6844c286f
--- /dev/null
+++ b/mailnews/mime/src/mimemdig.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMDIG_H_
+#define _MIMEMDIG_H_
+
+#include "mimemult.h"
+
+/* The MimeMultipartDigest class implements the multipart/digest MIME
+ container, which is just like multipart/mixed, except that the default
+ type (for parts with no type explicitly specified) is message/rfc822
+ instead of text/plain.
+ */
+
+typedef struct MimeMultipartDigestClass MimeMultipartDigestClass;
+typedef struct MimeMultipartDigest MimeMultipartDigest;
+
+struct MimeMultipartDigestClass {
+ MimeMultipartClass multipart;
+};
+
+extern MimeMultipartDigestClass mimeMultipartDigestClass;
+
+struct MimeMultipartDigest {
+ MimeMultipart multipart;
+};
+
+#define MimeMultipartDigestClassInitializer(ITYPE,CSUPER) \
+ { MimeMultipartClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEMDIG_H_ */
diff --git a/mailnews/mime/src/mimemmix.cpp b/mailnews/mime/src/mimemmix.cpp
new file mode 100644
index 000000000..f2d7e31cd
--- /dev/null
+++ b/mailnews/mime/src/mimemmix.cpp
@@ -0,0 +1,21 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimemmix.h"
+#include "prlog.h"
+
+#define MIME_SUPERCLASS mimeMultipartClass
+MimeDefClass(MimeMultipartMixed, MimeMultipartMixedClass,
+ mimeMultipartMixedClass, &MIME_SUPERCLASS);
+
+static int
+MimeMultipartMixedClassInitialize(MimeMultipartMixedClass *clazz)
+{
+#ifdef DEBUG
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ PR_ASSERT(!oclass->class_initialized);
+#endif
+ return 0;
+}
diff --git a/mailnews/mime/src/mimemmix.h b/mailnews/mime/src/mimemmix.h
new file mode 100644
index 000000000..9f401fa31
--- /dev/null
+++ b/mailnews/mime/src/mimemmix.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMMIX_H_
+#define _MIMEMMIX_H_
+
+#include "mimemult.h"
+
+/* The MimeMultipartMixed class implements the multipart/mixed MIME container,
+ and is also used for any and all otherwise-unrecognised subparts of
+ multipart/.
+ */
+
+typedef struct MimeMultipartMixedClass MimeMultipartMixedClass;
+typedef struct MimeMultipartMixed MimeMultipartMixed;
+
+struct MimeMultipartMixedClass {
+ MimeMultipartClass multipart;
+};
+
+extern MimeMultipartMixedClass mimeMultipartMixedClass;
+
+struct MimeMultipartMixed {
+ MimeMultipart multipart;
+};
+
+#define MimeMultipartMixedClassInitializer(ITYPE,CSUPER) \
+ { MimeMultipartClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEMMIX_H_ */
diff --git a/mailnews/mime/src/mimemoz2.cpp b/mailnews/mime/src/mimemoz2.cpp
new file mode 100644
index 000000000..c47c5c8a5
--- /dev/null
+++ b/mailnews/mime/src/mimemoz2.cpp
@@ -0,0 +1,2212 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "prlog.h"
+#include "nsCOMPtr.h"
+#include "modlmime.h"
+#include "mimeobj.h"
+#include "mimemsg.h"
+#include "mimetric.h" /* for MIME_RichtextConverter */
+#include "mimethtm.h"
+#include "mimemsig.h"
+#include "mimemrel.h"
+#include "mimemalt.h"
+#include "mimebuf.h"
+#include "mimemapl.h"
+#include "prprf.h"
+#include "mimei.h" /* for moved MimeDisplayData struct */
+#include "mimebuf.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "mimemoz2.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsStringGlue.h"
+#include "nsMimeStringResources.h"
+#include "nsStreamConverter.h"
+#include "nsIMsgSend.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIMIMEService.h"
+#include "nsIImapUrl.h"
+#include "nsMsgI18N.h"
+#include "nsICharsetConverterManager.h"
+#include "nsMimeTypes.h"
+#include "nsIIOService.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsIMsgWindow.h"
+#include "nsIMimeMiscStatus.h"
+#include "nsMsgUtils.h"
+#include "nsIChannel.h"
+#include "nsITransport.h"
+#include "mimeebod.h"
+#include "mimeeobj.h"
+// <for functions="HTML2Plaintext,HTMLSantinize">
+#include "nsXPCOM.h"
+#include "nsLayoutCID.h"
+#include "nsIComponentManager.h"
+#include "nsIParserUtils.h"
+// </for>
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+
+void ValidateRealName(nsMsgAttachmentData *aAttach, MimeHeaders *aHdrs);
+
+static MimeHeadersState MIME_HeaderType;
+static bool MIME_WrapLongLines;
+static bool MIME_VariableWidthPlaintext;
+
+mime_stream_data::mime_stream_data() : url_name(nullptr), orig_url_name(nullptr),
+ pluginObj2(nullptr), istream(nullptr), obj(nullptr), options(nullptr),
+ headers(nullptr), output_emitter(nullptr), firstCheck(false)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Attachment handling routines
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+MimeObject *mime_get_main_object(MimeObject* obj);
+
+nsresult MimeGetSize(MimeObject *child, int32_t *size) {
+ bool isLeaf = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeLeafClass);
+ bool isContainer = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeContainerClass);
+ bool isMsg = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeMessageClass);
+
+ if (isLeaf) {
+ *size += ((MimeLeaf *)child)->sizeSoFar;
+ } else if (isMsg) {
+ *size += ((MimeMessage *)child)->sizeSoFar;
+ } else if (isContainer) {
+ int i;
+ MimeContainer *cont = (MimeContainer *)child;
+ for (i = 0; i < cont->nchildren; ++i) {
+ MimeGetSize(cont->children[i], size);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+ProcessBodyAsAttachment(MimeObject *obj, nsMsgAttachmentData **data)
+{
+ nsMsgAttachmentData *tmp;
+ char *disp = nullptr;
+ char *charset = nullptr;
+
+ // Ok, this is the special case when somebody sends an "attachment" as the
+ // body of an RFC822 message...I really don't think this is the way this
+ // should be done. I belive this should really be a multipart/mixed message
+ // with an empty body part, but what can ya do...our friends to the North seem
+ // to do this.
+ MimeObject *child = obj;
+
+ *data = new nsMsgAttachmentData[2];
+ if (!*data)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ tmp = *data;
+ tmp->m_realType = child->content_type;
+ tmp->m_realEncoding = child->encoding;
+ disp = MimeHeaders_get(child->headers, HEADER_CONTENT_DISPOSITION, false, false);
+ tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "name", &charset, NULL));
+ if (!tmp->m_realName.IsEmpty())
+ {
+ char *fname = NULL;
+ fname = mime_decode_filename(tmp->m_realName.get(), charset, obj->options);
+ free(charset);
+ if (fname)
+ tmp->m_realName.Adopt(fname);
+ }
+ else
+ {
+ tmp->m_realName.Adopt(MimeHeaders_get_name(child->headers, obj->options));
+
+ if (tmp->m_realName.IsEmpty() &&
+ tmp->m_realType.LowerCaseEqualsLiteral(MESSAGE_RFC822))
+ {
+ // We haven't actually parsed the message "attachment", so just give it a
+ // generic name.
+ tmp->m_realName = "AttachedMessage.eml";
+ }
+ }
+
+ tmp->m_hasFilename = !tmp->m_realName.IsEmpty();
+
+ if (tmp->m_realName.IsEmpty() &&
+ StringBeginsWith(tmp->m_realType, NS_LITERAL_CSTRING("text"),
+ nsCaseInsensitiveCStringComparator()))
+ ValidateRealName(tmp, child->headers);
+
+ tmp->m_displayableInline = obj->clazz->displayable_inline_p(obj->clazz,
+ obj->headers);
+
+ char *tmpURL = nullptr;
+ char *id = nullptr;
+ char *id_imap = nullptr;
+
+ id = mime_part_address (obj);
+ if (obj->options->missing_parts)
+ id_imap = mime_imap_part_address (obj);
+
+ tmp->m_isDownloaded = !id_imap;
+
+ if (! id)
+ {
+ delete [] *data;
+ *data = nullptr;
+ PR_FREEIF(id_imap);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (obj->options && obj->options->url)
+ {
+ const char *url = obj->options->url;
+ nsresult rv;
+ if (id_imap && id)
+ {
+ // if this is an IMAP part.
+ tmpURL = mime_set_url_imap_part(url, id_imap, id);
+ rv = nsMimeNewURI(getter_AddRefs(tmp->m_url), tmpURL, nullptr);
+ }
+ else
+ {
+ // This is just a normal MIME part as usual.
+ tmpURL = mime_set_url_part(url, id, true);
+ rv = nsMimeNewURI(getter_AddRefs(tmp->m_url), tmpURL, nullptr);
+ }
+
+ if (!tmp->m_url || NS_FAILED(rv))
+ {
+ delete [] *data;
+ *data = nullptr;
+ PR_FREEIF(id);
+ PR_FREEIF(id_imap);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ PR_FREEIF(id);
+ PR_FREEIF(id_imap);
+ PR_FREEIF(tmpURL);
+ tmp->m_description.Adopt(MimeHeaders_get(child->headers, HEADER_CONTENT_DESCRIPTION, false, false));
+
+ tmp->m_size = 0;
+ MimeGetSize(child, &tmp->m_size);
+
+ return NS_OK;
+}
+
+int32_t
+CountTotalMimeAttachments(MimeContainer *aObj)
+{
+ int32_t i;
+ int32_t rc = 0;
+
+ if ( (!aObj) || (!aObj->children) || (aObj->nchildren <= 0) )
+ return 0;
+
+ if (!mime_typep(((MimeObject *) aObj), (MimeObjectClass*) &mimeContainerClass))
+ return 0;
+
+ for (i=0; i<aObj->nchildren; i++)
+ rc += CountTotalMimeAttachments((MimeContainer *)aObj->children[i]) + 1;
+
+ return rc;
+}
+
+void
+ValidateRealName(nsMsgAttachmentData *aAttach, MimeHeaders *aHdrs)
+{
+ // Sanity.
+ if (!aAttach)
+ return;
+
+ // Do we need to validate?
+ if (!aAttach->m_realName.IsEmpty())
+ return;
+
+ // Internal MIME structures need not be named!
+ if (aAttach->m_realType.IsEmpty() ||
+ StringBeginsWith(aAttach->m_realType, NS_LITERAL_CSTRING("multipart"),
+ nsCaseInsensitiveCStringComparator()))
+ return;
+
+ //
+ // Now validate any other name we have for the attachment!
+ //
+ if (aAttach->m_realName.IsEmpty())
+ {
+ aAttach->m_realName = "attachment";
+ nsresult rv = NS_OK;
+ nsAutoCString contentType (aAttach->m_realType);
+ int32_t pos = contentType.FindChar(';');
+ if (pos > 0)
+ contentType.SetLength(pos);
+
+ nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString fileExtension;
+ rv = mimeFinder->GetPrimaryExtension(contentType, EmptyCString(), fileExtension);
+
+ if (NS_SUCCEEDED(rv) && !fileExtension.IsEmpty())
+ {
+ aAttach->m_realName.Append('.');
+ aAttach->m_realName.Append(fileExtension);
+ }
+ }
+ }
+}
+
+static int32_t attIndex = 0;
+
+nsresult
+GenerateAttachmentData(MimeObject *object, const char *aMessageURL, MimeDisplayOptions *options,
+ bool isAnAppleDoublePart, int32_t attSize, nsMsgAttachmentData *aAttachData)
+{
+ nsCString imappart;
+ nsCString part;
+ bool isExternalAttachment = false;
+
+ /* be sure the object has not be marked as Not to be an attachment */
+ if (object->dontShowAsAttachment)
+ return NS_OK;
+
+ part.Adopt(mime_part_address(object));
+ if (part.IsEmpty())
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (options->missing_parts)
+ imappart.Adopt(mime_imap_part_address(object));
+
+ char *urlSpec = nullptr;
+ if (!imappart.IsEmpty())
+ {
+ urlSpec = mime_set_url_imap_part(aMessageURL, imappart.get(), part.get());
+ }
+ else
+ {
+ char *no_part_url = nullptr;
+ if (options->part_to_load && options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
+ no_part_url = mime_get_base_url(aMessageURL);
+ if (no_part_url) {
+ urlSpec = mime_set_url_part(no_part_url, part.get(), true);
+ PR_Free(no_part_url);
+ }
+ else
+ {
+ // if the mime object contains an external attachment URL, then use it, otherwise
+ // fall back to creating an attachment url based on the message URI and the
+ // part number.
+ urlSpec = mime_external_attachment_url(object);
+ isExternalAttachment = urlSpec ? true : false;
+ if (!urlSpec)
+ urlSpec = mime_set_url_part(aMessageURL, part.get(), true);
+ }
+ }
+
+ if (!urlSpec)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if ((options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) && (PL_strncasecmp(aMessageURL, urlSpec, strlen(urlSpec)) == 0))
+ return NS_OK;
+
+ nsCString urlString(urlSpec);
+
+ nsMsgAttachmentData *tmp = &(aAttachData[attIndex++]);
+
+ tmp->m_realType = object->content_type;
+ tmp->m_realEncoding = object->encoding;
+ tmp->m_isExternalAttachment = isExternalAttachment;
+ tmp->m_isExternalLinkAttachment =
+ (isExternalAttachment &&
+ StringBeginsWith(urlString, NS_LITERAL_CSTRING("http"),
+ nsCaseInsensitiveCStringComparator()));
+ tmp->m_size = attSize;
+ tmp->m_sizeExternalStr = "-1";
+ tmp->m_disposition.Adopt(MimeHeaders_get(object->headers, HEADER_CONTENT_DISPOSITION, true, false));
+ tmp->m_displayableInline = object->clazz->displayable_inline_p(object->clazz, object->headers);
+
+ char *part_addr = mime_imap_part_address(object);
+ tmp->m_isDownloaded = !part_addr;
+ PR_FREEIF(part_addr);
+
+ int32_t i;
+ char *charset = nullptr;
+ char *disp = MimeHeaders_get(object->headers, HEADER_CONTENT_DISPOSITION, false, false);
+ if (disp)
+ {
+ tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "filename", &charset, nullptr));
+ if (isAnAppleDoublePart)
+ for (i = 0; i < 2 && tmp->m_realName.IsEmpty(); i ++)
+ {
+ PR_FREEIF(disp);
+ free(charset);
+ disp = MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, HEADER_CONTENT_DISPOSITION, false, false);
+ tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "filename", &charset, nullptr));
+ }
+
+ if (!tmp->m_realName.IsEmpty())
+ {
+ // check encoded type
+ //
+ // The parameter of Content-Disposition must use RFC 2231.
+ // But old Netscape 4.x and Outlook Express etc. use RFC2047.
+ // So we should parse both types.
+
+ char *fname = nullptr;
+ fname = mime_decode_filename(tmp->m_realName.get(), charset, options);
+ free(charset);
+
+ if (fname)
+ tmp->m_realName.Adopt(fname);
+ }
+
+ PR_FREEIF(disp);
+ }
+
+ disp = MimeHeaders_get(object->headers, HEADER_CONTENT_TYPE, false, false);
+ if (disp)
+ {
+ tmp->m_xMacType.Adopt(MimeHeaders_get_parameter(disp, PARAM_X_MAC_TYPE, nullptr, nullptr));
+ tmp->m_xMacCreator.Adopt(MimeHeaders_get_parameter(disp, PARAM_X_MAC_CREATOR, nullptr, nullptr));
+
+ if (tmp->m_realName.IsEmpty())
+ {
+ tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "name", &charset, nullptr));
+ if (isAnAppleDoublePart)
+ // the data fork is the 2nd part, and we should ALWAYS look there first for the file name
+ for (i = 1; i >= 0 && tmp->m_realName.IsEmpty(); i --)
+ {
+ PR_FREEIF(disp);
+ free(charset);
+ disp = MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, HEADER_CONTENT_TYPE, false, false);
+ tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "name", &charset, nullptr));
+ tmp->m_realType.Adopt(
+ MimeHeaders_get(((MimeContainer *)object)->children[i]->headers,
+ HEADER_CONTENT_TYPE, true, false));
+ }
+
+ if (!tmp->m_realName.IsEmpty())
+ {
+ // check encoded type
+ //
+ // The parameter of Content-Disposition must use RFC 2231.
+ // But old Netscape 4.x and Outlook Express etc. use RFC2047.
+ // So we should parse both types.
+
+ char *fname = nullptr;
+ fname = mime_decode_filename(tmp->m_realName.get(), charset, options);
+ free(charset);
+
+ if (fname)
+ tmp->m_realName.Adopt(fname);
+ }
+ }
+
+ if (tmp->m_isExternalLinkAttachment)
+ {
+ // If an external link attachment part's Content-Type contains a
+ // |size| parm, store it in m_sizeExternalStr. Let the msgHeaderSink
+ // addAttachmentField() figure out if it's sane, and don't bother
+ // strtol'ing it to an int only to emit it as a string.
+ char* sizeStr = MimeHeaders_get_parameter(disp, "size", nullptr, nullptr);
+ if (sizeStr)
+ tmp->m_sizeExternalStr = sizeStr;
+ }
+
+ PR_FREEIF(disp);
+ }
+
+ tmp->m_description.Adopt(MimeHeaders_get(object->headers, HEADER_CONTENT_DESCRIPTION,
+ false, false));
+
+ // Now, do the right thing with the name!
+ if (tmp->m_realName.IsEmpty() && !(tmp->m_realType.LowerCaseEqualsLiteral(MESSAGE_RFC822)))
+ {
+ // Keep in mind that the name was provided by us and this is probably not a
+ // real attachment.
+ tmp->m_hasFilename = false;
+ /* If this attachment doesn't have a name, just give it one... */
+ tmp->m_realName.Adopt(MimeGetStringByID(MIME_MSG_DEFAULT_ATTACHMENT_NAME));
+ if (!tmp->m_realName.IsEmpty())
+ {
+ char *newName = PR_smprintf(tmp->m_realName.get(), part.get());
+ if (newName)
+ tmp->m_realName.Adopt(newName);
+ }
+ else
+ tmp->m_realName.Adopt(mime_part_address(object));
+ } else {
+ tmp->m_hasFilename = true;
+ }
+
+ if (!tmp->m_realName.IsEmpty() && !tmp->m_isExternalAttachment)
+ {
+ urlString.Append("&filename=");
+ nsAutoCString aResult;
+ if (NS_SUCCEEDED(MsgEscapeString(tmp->m_realName,
+ nsINetUtil::ESCAPE_XALPHAS, aResult)))
+ urlString.Append(aResult);
+ else
+ urlString.Append(tmp->m_realName);
+ if (tmp->m_realType.EqualsLiteral("message/rfc822") &&
+ !StringEndsWith(urlString, NS_LITERAL_CSTRING(".eml"), nsCaseInsensitiveCStringComparator()))
+ urlString.Append(".eml");
+ } else if (tmp->m_isExternalAttachment) {
+ // Allows the JS mime emitter to figure out the part information.
+ urlString.Append("?part=");
+ urlString.Append(part);
+ } else if (tmp->m_realType.LowerCaseEqualsLiteral(MESSAGE_RFC822)) {
+ // Special case...if this is a enclosed RFC822 message, give it a nice
+ // name.
+ if (object->headers->munged_subject)
+ {
+ nsCString subject;
+ subject.Assign(object->headers->munged_subject);
+ MimeHeaders_convert_header_value(options, subject, false);
+ tmp->m_realName.Assign(subject);
+ tmp->m_realName.Append(".eml");
+ }
+ else
+ tmp->m_realName = "ForwardedMessage.eml";
+ }
+
+ nsresult rv = nsMimeNewURI(getter_AddRefs(tmp->m_url), urlString.get(), nullptr);
+
+ PR_FREEIF(urlSpec);
+
+ if (NS_FAILED(rv) || !tmp->m_url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ ValidateRealName(tmp, object->headers);
+
+ return NS_OK;
+}
+
+nsresult
+BuildAttachmentList(MimeObject *anObject, nsMsgAttachmentData *aAttachData, const char *aMessageURL)
+{
+ nsresult rv;
+ int32_t i;
+ MimeContainer *cobj = (MimeContainer *) anObject;
+ bool found_output = false;
+
+ if ( (!anObject) || (!cobj->children) || (!cobj->nchildren) ||
+ (mime_typep(anObject, (MimeObjectClass *)&mimeExternalBodyClass)))
+ return NS_OK;
+
+ for (i = 0; i < cobj->nchildren ; i++)
+ {
+ MimeObject *child = cobj->children[i];
+ char *ct = child->content_type;
+
+ // We're going to ignore the output_p attribute because we want to output
+ // any part with a name to work around bug 674473
+
+ // Skip the first child that's being output if it's in fact a message body.
+ // Start by assuming that it is, until proven otherwise in the code below.
+ bool skip = true;
+ if (found_output)
+ // not first child being output
+ skip = false;
+ else if (! ct)
+ // no content type so can't be message body
+ skip = false;
+ else if (PL_strcasecmp (ct, TEXT_PLAIN) &&
+ PL_strcasecmp (ct, TEXT_HTML) &&
+ PL_strcasecmp (ct, TEXT_MDL))
+ // not a type we recognize as a message body
+ skip = false;
+ // we're displaying all body parts
+ if (child->options->html_as_p == 4)
+ skip = false;
+ if (skip && child->headers)
+ {
+ // If it has a filename, we don't skip it regardless of the
+ // content disposition which can be "inline" or "attachment".
+ // Inline parts are not shown when attachments aren't displayed
+ // inline, so the only chance to see the part is as attachment.
+ char * name = MimeHeaders_get_name(child->headers, nullptr);
+ if (name)
+ skip = false;
+ PR_FREEIF(name);
+ }
+
+ found_output = true;
+ if (skip)
+ continue;
+
+ // We should generate an attachment for leaf object only but...
+ bool isALeafObject = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeLeafClass);
+
+ // ...we will generate an attachment for inline message too.
+ bool isAnInlineMessage = mime_typep(child, (MimeObjectClass *) &mimeMessageClass);
+
+ // AppleDouble part need special care: we need to fetch the part as well its two
+ // children for the needed info as they could be anywhere, eventually, they won't contain
+ // a name or file name. In any case we need to build only one attachment data
+ bool isAnAppleDoublePart = mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass) &&
+ ((MimeContainer *)child)->nchildren == 2;
+
+ // The function below does not necessarily set the size to something (I
+ // don't think it will work for external objects, for instance, since they
+ // are neither containers nor leafs).
+ int32_t attSize = 0;
+ MimeGetSize(child, &attSize);
+
+ if (isALeafObject || isAnInlineMessage || isAnAppleDoublePart)
+ {
+ rv = GenerateAttachmentData(child, aMessageURL, anObject->options, isAnAppleDoublePart, attSize, aAttachData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Now build the attachment list for the children of our object...
+ if (!isALeafObject && !isAnAppleDoublePart)
+ {
+ rv = BuildAttachmentList((MimeObject *)child, aAttachData, aMessageURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+
+}
+
+extern "C" nsresult
+MimeGetAttachmentList(MimeObject *tobj, const char *aMessageURL, nsMsgAttachmentData **data)
+{
+ MimeObject *obj;
+ MimeContainer *cobj;
+ int32_t n;
+ bool isAnInlineMessage;
+
+ if (!data)
+ return NS_ERROR_INVALID_ARG;
+ *data = nullptr;
+
+ obj = mime_get_main_object(tobj);
+ if (!obj)
+ return NS_OK;
+
+ if (!mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeContainerClass))
+ return ProcessBodyAsAttachment(obj, data);
+
+ isAnInlineMessage = mime_typep(obj, (MimeObjectClass *) &mimeMessageClass);
+
+ cobj = (MimeContainer*) obj;
+ n = CountTotalMimeAttachments(cobj);
+ if (n <= 0)
+ // XXX n is a regular number here, not meaningful as an nsresult
+ return static_cast<nsresult>(n);
+
+ // in case of an inline message (as body), we need an extra slot for the
+ // message itself that we will fill later...
+ if (isAnInlineMessage)
+ n ++;
+
+ *data = new nsMsgAttachmentData[n + 1];
+ if (!*data)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ attIndex = 0;
+
+ // Now, build the list!
+
+ nsresult rv;
+
+ if (isAnInlineMessage)
+ {
+ int32_t size = 0;
+ MimeGetSize(obj, &size);
+ rv = GenerateAttachmentData(obj, aMessageURL, obj->options, false, size,
+ *data);
+ if (NS_FAILED(rv))
+ {
+ delete [] *data; // release data in case of error return.
+ return rv;
+ }
+
+ }
+ rv = BuildAttachmentList((MimeObject *) cobj, *data, aMessageURL);
+ if (NS_FAILED(rv))
+ {
+ delete [] *data; // release data in case of error return.
+ }
+ return rv;
+}
+
+extern "C" void
+MimeFreeAttachmentList(nsMsgAttachmentData *data)
+{
+ delete [] data;
+}
+
+extern "C" void
+NotifyEmittersOfAttachmentList(MimeDisplayOptions *opt,
+ nsMsgAttachmentData *data)
+{
+ int32_t i = 0;
+ nsMsgAttachmentData *tmp = data;
+
+ if (!tmp)
+ return;
+
+ while (tmp->m_url)
+ {
+ // The code below implements the following logic:
+ // - Always display the attachment if the Content-Disposition is
+ // "attachment" or if it can't be displayed inline.
+ // - If there's no name at all, just skip it (we don't know what to do with
+ // it then).
+ // - If the attachment has a "provided name" (i.e. not something like "Part
+ // 1.2"), display it.
+ // - If we're asking for all body parts and NOT asking for metadata only,
+ // display it.
+ // - Otherwise, skip it.
+ if (!tmp->m_disposition.Equals("attachment") && tmp->m_displayableInline &&
+ (tmp->m_realName.IsEmpty() || (!tmp->m_hasFilename &&
+ (opt->html_as_p != 4 || opt->metadata_only))))
+ {
+ ++i;
+ ++tmp;
+ continue;
+ }
+
+ nsAutoCString spec;
+ if (tmp->m_url) {
+ if (tmp->m_isExternalLinkAttachment)
+ mozilla::Unused << tmp->m_url->GetAsciiSpec(spec);
+ else
+ mozilla::Unused << tmp->m_url->GetSpec(spec);
+ }
+
+ nsAutoCString sizeStr;
+ if (tmp->m_isExternalLinkAttachment)
+ sizeStr.Append(tmp->m_sizeExternalStr);
+ else
+ sizeStr.AppendInt(tmp->m_size);
+
+ nsAutoCString downloadedStr;
+ downloadedStr.AppendInt(tmp->m_isDownloaded);
+
+ mimeEmitterStartAttachment(opt, tmp->m_realName.get(), tmp->m_realType.get(),
+ spec.get(), tmp->m_isExternalAttachment);
+ mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_URL, spec.get());
+ mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_SIZE, sizeStr.get());
+ mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_DOWNLOADED, downloadedStr.get());
+
+ if ( (opt->format_out == nsMimeOutput::nsMimeMessageQuoting) ||
+ (opt->format_out == nsMimeOutput::nsMimeMessageBodyQuoting) ||
+ (opt->format_out == nsMimeOutput::nsMimeMessageSaveAs) ||
+ (opt->format_out == nsMimeOutput::nsMimeMessagePrintOutput))
+ {
+ mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_DESCRIPTION, tmp->m_description.get());
+ mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_TYPE, tmp->m_realType.get());
+ mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_ENCODING, tmp->m_realEncoding.get());
+ }
+
+ mimeEmitterEndAttachment(opt);
+ ++i;
+ ++tmp;
+ }
+ mimeEmitterEndAllAttachments(opt);
+}
+
+// Utility to create a nsIURI object...
+extern "C" nsresult
+nsMimeNewURI(nsIURI** aInstancePtrResult, const char *aSpec, nsIURI *aBase)
+{
+ if (nullptr == aInstancePtrResult)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsIIOService> pService =
+ mozilla::services::GetIOService();
+ NS_ENSURE_TRUE(pService, NS_ERROR_FACTORY_NOT_REGISTERED);
+
+ return pService->NewURI(nsDependentCString(aSpec), nullptr, aBase, aInstancePtrResult);
+}
+
+extern "C" nsresult
+SetMailCharacterSetToMsgWindow(MimeObject *obj, const char *aCharacterSet)
+{
+ nsresult rv = NS_OK;
+
+ if (obj && obj->options)
+ {
+ mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure);
+ if (msd)
+ {
+ nsIChannel *channel = msd->channel;
+ if (channel)
+ {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(uri));
+ if (msgurl)
+ {
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ msgurl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ rv = msgWindow->SetMailCharacterSet(!PL_strcasecmp(aCharacterSet, "us-ascii") ?
+ static_cast<const nsCString&>(NS_LITERAL_CSTRING("ISO-8859-1")) :
+ static_cast<const nsCString&>(nsDependentCString(aCharacterSet)));
+ }
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+static void ResetMsgHeaderSinkProps(nsIURI *uri)
+{
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl(do_QueryInterface(uri));
+ if (!msgurl)
+ return;
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ msgurl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (!msgWindow)
+ return;
+
+ nsCOMPtr<nsIMsgHeaderSink> msgHeaderSink;
+ msgWindow->GetMsgHeaderSink(getter_AddRefs(msgHeaderSink));
+ if (!msgHeaderSink)
+ return;
+
+ msgHeaderSink->ResetProperties();
+}
+
+static char *
+mime_file_type (const char *filename, void *stream_closure)
+{
+ char *retType = nullptr;
+ char *ext = nullptr;
+ nsresult rv;
+
+ ext = PL_strrchr(filename, '.');
+ if (ext)
+ {
+ ext++;
+ nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
+ if (mimeFinder) {
+ nsAutoCString type;
+ mimeFinder->GetTypeFromExtension(nsDependentCString(ext), type);
+ retType = ToNewCString(type);
+ }
+ }
+
+ return retType;
+}
+
+int ConvertUsingEncoderAndDecoder(const char *stringToUse, int32_t inLength,
+ nsIUnicodeEncoder *encoder, nsIUnicodeDecoder *decoder,
+ char **pConvertedString, int32_t *outLength)
+{
+ // buffer size 144 =
+ // 72 (default line len for compose)
+ // times 2 (converted byte len might be larger)
+ const int klocalbufsize = 144;
+ // do the conversion
+ char16_t *unichars;
+ int32_t unicharLength;
+ int32_t srcLen = inLength;
+ int32_t dstLength = 0;
+ char *dstPtr;
+ nsresult rv;
+
+ // use this local buffer if possible
+ char16_t localbuf[klocalbufsize+1];
+ if (inLength > klocalbufsize) {
+ rv = decoder->GetMaxLength(stringToUse, srcLen, &unicharLength);
+ // allocate temporary buffer to hold unicode string
+ unichars = new char16_t[unicharLength];
+ }
+ else {
+ unichars = localbuf;
+ unicharLength = klocalbufsize+1;
+ }
+ if (unichars == nullptr) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ else {
+ // convert to unicode, replacing failed chars with 0xFFFD as in
+ // the methode used in nsXMLHttpRequest::ConvertBodyToText and nsScanner::Append
+ //
+ // We will need several pass to convert the whole string if it has invalid characters
+ // 'totalChars' is where the sum of the number of converted characters will be done
+ // 'dataLen' is the number of character left to convert
+ // 'outLen' is the number of characters still available in the output buffer as input of decoder->Convert
+ // and the number of characters written in it as output.
+ int32_t totalChars = 0,
+ inBufferIndex = 0,
+ outBufferIndex = 0;
+ int32_t dataLen = srcLen,
+ outLen = unicharLength;
+
+ do {
+ int32_t inBufferLength = dataLen;
+ rv = decoder->Convert(&stringToUse[inBufferIndex],
+ &inBufferLength,
+ &unichars[outBufferIndex],
+ &outLen);
+ totalChars += outLen;
+ // Done if conversion successful
+ if (NS_SUCCEEDED(rv))
+ break;
+
+ // We consume one byte, replace it with U+FFFD
+ // and try the conversion again.
+ outBufferIndex += outLen;
+ unichars[outBufferIndex++] = char16_t(0xFFFD);
+ // totalChars is updated here
+ outLen = unicharLength - (++totalChars);
+
+ inBufferIndex += inBufferLength + 1;
+ dataLen -= inBufferLength + 1;
+
+ decoder->Reset();
+
+ // If there is not at least one byte available after the one we
+ // consumed, we're done
+ } while ( dataLen > 0 );
+
+ rv = encoder->GetMaxLength(unichars, totalChars, &dstLength);
+ // allocale an output buffer
+ dstPtr = (char *) PR_Malloc(dstLength + 1);
+ if (dstPtr == nullptr) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ else {
+ int32_t buffLength = dstLength;
+ // convert from unicode
+ rv = encoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace, nullptr, '?');
+ if (NS_SUCCEEDED(rv)) {
+ rv = encoder->Convert(unichars, &totalChars, dstPtr, &dstLength);
+ if (NS_SUCCEEDED(rv)) {
+ int32_t finLen = buffLength - dstLength;
+ rv = encoder->Finish((char *)(dstPtr+dstLength), &finLen);
+ if (NS_SUCCEEDED(rv)) {
+ dstLength += finLen;
+ }
+ dstPtr[dstLength] = '\0';
+ *pConvertedString = dstPtr; // set the result string
+ *outLength = dstLength;
+ }
+ }
+ }
+ if (inLength > klocalbufsize)
+ delete [] unichars;
+ }
+
+ return NS_SUCCEEDED(rv) ? 0 : -1;
+}
+
+
+static int
+mime_convert_charset (const char *input_line, int32_t input_length,
+ const char *input_charset, const char *output_charset,
+ char **output_ret, int32_t *output_size_ret,
+ void *stream_closure, nsIUnicodeDecoder *decoder, nsIUnicodeEncoder *encoder)
+{
+ int32_t res = -1;
+ char *convertedString = NULL;
+ int32_t convertedStringLen = 0;
+ if (encoder && decoder)
+ {
+ res = ConvertUsingEncoderAndDecoder(input_line, input_length, encoder, decoder, &convertedString, &convertedStringLen);
+ }
+ if (res != 0)
+ {
+ *output_ret = 0;
+ *output_size_ret = 0;
+ }
+ else
+ {
+ *output_ret = (char *) convertedString;
+ *output_size_ret = convertedStringLen;
+ }
+
+ return 0;
+}
+
+static int
+mime_output_fn(const char *buf, int32_t size, void *stream_closure)
+{
+ uint32_t written = 0;
+ mime_stream_data *msd = (mime_stream_data *) stream_closure;
+ if ( (!msd->pluginObj2) && (!msd->output_emitter) )
+ return -1;
+
+ // Fire pending start request
+ ((nsStreamConverter*)msd->pluginObj2)->FirePendingStartRequest();
+
+
+ // Now, write to the WriteBody method if this is a message body and not
+ // a part retrevial
+ if (!msd->options->part_to_load || msd->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
+ {
+ if (msd->output_emitter)
+ {
+ msd->output_emitter->WriteBody(Substring(buf, buf+size),
+ &written);
+ }
+ }
+ else
+ {
+ if (msd->output_emitter)
+ {
+ msd->output_emitter->Write(Substring(buf, buf+size), &written);
+ }
+ }
+ return written;
+}
+
+extern "C" int
+mime_display_stream_write (nsMIMESession *stream,
+ const char* buf,
+ int32_t size)
+{
+ mime_stream_data *msd = (mime_stream_data *) ((nsMIMESession *)stream)->data_object;
+
+ MimeObject *obj = (msd ? msd->obj : 0);
+ if (!obj) return -1;
+
+ //
+ // Ok, now check to see if this is a display operation for a MIME Parts on Demand
+ // enabled call.
+ //
+ if (msd->firstCheck)
+ {
+ if (msd->channel)
+ {
+ nsCOMPtr<nsIURI> aUri;
+ if (NS_SUCCEEDED(msd->channel->GetURI(getter_AddRefs(aUri))))
+ {
+ nsCOMPtr<nsIImapUrl> imapURL = do_QueryInterface(aUri);
+ if (imapURL)
+ {
+ nsImapContentModifiedType cModified;
+ if (NS_SUCCEEDED(imapURL->GetContentModified(&cModified)))
+ {
+ if ( cModified != nsImapContentModifiedTypes::IMAP_CONTENT_NOT_MODIFIED )
+ msd->options->missing_parts = true;
+ }
+ }
+ }
+ }
+
+ msd->firstCheck = false;
+ }
+
+ return obj->clazz->parse_buffer((char *) buf, size, obj);
+}
+
+extern "C" void
+mime_display_stream_complete (nsMIMESession *stream)
+{
+ mime_stream_data *msd = (mime_stream_data *) ((nsMIMESession *)stream)->data_object;
+ MimeObject *obj = (msd ? msd->obj : 0);
+ if (obj)
+ {
+ int status;
+ bool abortNow = false;
+
+ if ((obj->options) && (obj->options->headers == MimeHeadersOnly))
+ abortNow = true;
+
+ status = obj->clazz->parse_eof(obj, abortNow);
+ obj->clazz->parse_end(obj, (status < 0 ? true : false));
+
+ //
+ // Ok, now we are going to process the attachment data by getting all
+ // of the attachment info and then driving the emitter with this data.
+ //
+ if (!msd->options->part_to_load || msd->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
+ {
+ nsMsgAttachmentData *attachments;
+ nsresult rv = MimeGetAttachmentList(obj, msd->url_name, &attachments);
+ if (NS_SUCCEEDED(rv))
+ {
+ NotifyEmittersOfAttachmentList(msd->options, attachments);
+ MimeFreeAttachmentList(attachments);
+ }
+ }
+
+ // Release the conversion object - this has to be done after
+ // we finish processing data.
+ if ( obj->options)
+ {
+ NS_IF_RELEASE(obj->options->conv);
+ }
+
+ // Destroy the object now.
+ PR_ASSERT(msd->options == obj->options);
+ mime_free(obj);
+ obj = NULL;
+ if (msd->options)
+ {
+ delete msd->options;
+ msd->options = 0;
+ }
+ }
+
+ if (msd->headers)
+ MimeHeaders_free (msd->headers);
+
+ if (msd->url_name)
+ NS_Free(msd->url_name);
+
+ if (msd->orig_url_name)
+ NS_Free(msd->orig_url_name);
+
+ delete msd;
+}
+
+extern "C" void
+mime_display_stream_abort (nsMIMESession *stream, int status)
+{
+ mime_stream_data *msd = (mime_stream_data *) ((nsMIMESession *)stream)->data_object;
+
+ MimeObject *obj = (msd ? msd->obj : 0);
+ if (obj)
+ {
+ if (!obj->closed_p)
+ obj->clazz->parse_eof(obj, true);
+ if (!obj->parsed_p)
+ obj->clazz->parse_end(obj, true);
+
+ // Destroy code....
+ PR_ASSERT(msd->options == obj->options);
+ mime_free(obj);
+ if (msd->options)
+ {
+ delete msd->options;
+ msd->options = 0;
+ }
+ }
+
+ if (msd->headers)
+ MimeHeaders_free (msd->headers);
+
+ if (msd->url_name)
+ NS_Free(msd->url_name);
+
+ if (msd->orig_url_name)
+ NS_Free(msd->orig_url_name);
+
+ delete msd;
+}
+
+static int
+mime_output_init_fn (const char *type,
+ const char *charset,
+ const char *name,
+ const char *x_mac_type,
+ const char *x_mac_creator,
+ void *stream_closure)
+{
+ mime_stream_data *msd = (mime_stream_data *) stream_closure;
+
+ // Now, all of this stream creation is done outside of libmime, so this
+ // is just a check of the pluginObj member and returning accordingly.
+ if (!msd->pluginObj2)
+ return -1;
+ else
+ return 0;
+}
+
+static void *mime_image_begin(const char *image_url, const char *content_type,
+ void *stream_closure);
+static void mime_image_end(void *image_closure, int status);
+static char *mime_image_make_image_html(void *image_data);
+static int mime_image_write_buffer(const char *buf, int32_t size, void *image_closure);
+
+/* Interface between libmime and inline display of images: the abomination
+ that is known as "internal-external-reconnect".
+ */
+class mime_image_stream_data {
+public:
+ mime_image_stream_data();
+
+ mime_stream_data *msd;
+ char *url;
+ nsMIMESession *istream;
+};
+
+mime_image_stream_data::mime_image_stream_data()
+{
+ url = nullptr;
+ istream = nullptr;
+ msd = nullptr;
+}
+
+static void *
+mime_image_begin(const char *image_url, const char *content_type,
+ void *stream_closure)
+{
+ mime_stream_data *msd = (mime_stream_data *) stream_closure;
+ class mime_image_stream_data *mid;
+
+ mid = new mime_image_stream_data;
+ if (!mid) return nullptr;
+
+
+ mid->msd = msd;
+
+ mid->url = (char *) strdup(image_url);
+ if (!mid->url)
+ {
+ PR_Free(mid);
+ return nullptr;
+ }
+
+ mid->istream = (nsMIMESession *) msd->pluginObj2;
+ return mid;
+}
+
+static void
+mime_image_end(void *image_closure, int status)
+{
+ mime_image_stream_data *mid =
+ (mime_image_stream_data *) image_closure;
+
+ PR_ASSERT(mid);
+ if (!mid)
+ return;
+
+ PR_FREEIF(mid->url);
+ delete mid;
+}
+
+
+static char *
+mime_image_make_image_html(void *image_closure)
+{
+ mime_image_stream_data *mid =
+ (mime_image_stream_data *) image_closure;
+
+ const char *prefix;
+ /* Wouldn't it be nice if attributes were case-sensitive? */
+ const char *scaledPrefix = "<P><CENTER><IMG CLASS=\"moz-attached-image\" shrinktofit=\"yes\" SRC=\"";
+ const char *unscaledPrefix = "<P><CENTER><IMG CLASS=\"moz-attached-image\" SRC=\"";
+ const char *suffix = "\"></CENTER><P>";
+ const char *url;
+ char *buf;
+
+ PR_ASSERT(mid);
+ if (!mid) return 0;
+
+ /* Internal-external-reconnect only works when going to the screen. */
+ if (!mid->istream)
+ return strdup("<P><CENTER><IMG SRC=\"resource://gre-resources/loading-image.png\" ALT=\"[Image]\"></CENTER><P>");
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ bool resize = true;
+
+ if (prefSvc)
+ prefSvc->GetBranch("", getter_AddRefs(prefBranch));
+ if (prefBranch)
+ prefBranch->GetBoolPref("mail.enable_automatic_image_resizing", &resize); // ignore return value
+ prefix = resize ? scaledPrefix : unscaledPrefix;
+
+ if ( (!mid->url) || (!(*mid->url)) )
+ url = "";
+ else
+ url = mid->url;
+
+ uint32_t buflen = strlen(prefix) + strlen(suffix) + strlen(url) + 20;
+ buf = (char *) PR_MALLOC (buflen);
+ if (!buf)
+ return 0;
+ *buf = 0;
+
+ PL_strcatn (buf, buflen, prefix);
+ PL_strcatn (buf, buflen, url);
+ PL_strcatn (buf, buflen, suffix);
+ return buf;
+}
+
+static int
+mime_image_write_buffer(const char *buf, int32_t size, void *image_closure)
+{
+ mime_image_stream_data *mid =
+ (mime_image_stream_data *) image_closure;
+ mime_stream_data *msd = mid->msd;
+
+ if ( ( (!msd->output_emitter) ) &&
+ ( (!msd->pluginObj2) ) )
+ return -1;
+
+ return size;
+}
+
+MimeObject*
+mime_get_main_object(MimeObject* obj)
+{
+ MimeContainer *cobj;
+ if (!(mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeMessageClass)))
+ {
+ return obj;
+ }
+ cobj = (MimeContainer*) obj;
+ if (cobj->nchildren != 1) return obj;
+ obj = cobj->children[0];
+ while (obj)
+ {
+ if ( (!mime_subclass_p(obj->clazz,
+ (MimeObjectClass*) &mimeMultipartSignedClass)) &&
+ (PL_strcasecmp(obj->content_type, MULTIPART_SIGNED) != 0)
+ )
+ {
+ return obj;
+ }
+ else
+ {
+ if (mime_subclass_p(obj->clazz, (MimeObjectClass*)&mimeContainerClass))
+ {
+ // We don't care about a signed/smime object; Go inside to the
+ // thing that we signed or smime'ed
+ //
+ cobj = (MimeContainer*) obj;
+ if (cobj->nchildren > 0)
+ obj = cobj->children[0];
+ else
+ obj = nullptr;
+ }
+ else
+ {
+ // we received a message with a child object that looks like a signed
+ // object, but it is not a subclass of mimeContainer, so let's
+ // return the given child object.
+ return obj;
+ }
+ }
+ }
+ return nullptr;
+}
+
+static
+bool MimeObjectIsMessageBodyNoClimb(MimeObject *parent,
+ MimeObject *looking_for,
+ bool *stop)
+{
+ MimeContainer *container = (MimeContainer *)parent;
+ int32_t i;
+ char *disp;
+
+ NS_ASSERTION(stop, "NULL stop to MimeObjectIsMessageBodyNoClimb");
+
+ for (i = 0; i < container->nchildren; i++) {
+ MimeObject *child = container->children[i];
+ bool is_body = true;
+
+ // The body can't be something we're not displaying.
+ if (! child->output_p)
+ is_body = false;
+ else if ((disp = MimeHeaders_get (child->headers, HEADER_CONTENT_DISPOSITION,
+ true, false))) {
+ PR_Free(disp);
+ is_body = false;
+ }
+ else if (PL_strcasecmp (child->content_type, TEXT_PLAIN) &&
+ PL_strcasecmp (child->content_type, TEXT_HTML) &&
+ PL_strcasecmp (child->content_type, TEXT_MDL) &&
+ PL_strcasecmp (child->content_type, MESSAGE_NEWS) &&
+ PL_strcasecmp (child->content_type, MESSAGE_RFC822))
+ is_body = false;
+
+ if (is_body || child == looking_for) {
+ *stop = true;
+ return child == looking_for;
+ }
+
+ // The body could be down inside a multipart child, so search recursively.
+ if (mime_subclass_p(child->clazz, (MimeObjectClass*) &mimeContainerClass)) {
+ is_body = MimeObjectIsMessageBodyNoClimb(child, looking_for, stop);
+ if (is_body || *stop)
+ return is_body;
+ }
+ }
+ return false;
+}
+
+/* Should this be static in mimemult.cpp? */
+bool MimeObjectIsMessageBody(MimeObject *looking_for)
+{
+ bool stop = false;
+ MimeObject *root = looking_for;
+ while (root->parent)
+ root = root->parent;
+ return MimeObjectIsMessageBodyNoClimb(root, looking_for, &stop);
+}
+
+//
+// New Stream Converter Interface
+//
+
+// Get the connnection to prefs service manager
+nsIPrefBranch *
+GetPrefBranch(MimeDisplayOptions *opt)
+{
+ if (!opt)
+ return nullptr;
+
+ return opt->m_prefBranch;
+}
+
+// Get the text converter...
+mozITXTToHTMLConv *
+GetTextConverter(MimeDisplayOptions *opt)
+{
+ if (!opt)
+ return nullptr;
+
+ return opt->conv;
+}
+
+MimeDisplayOptions::MimeDisplayOptions()
+{
+ conv = nullptr; // For text conversion...
+ format_out = 0; // The format out type
+ url = nullptr;
+
+ memset(&headers,0, sizeof(headers));
+ fancy_headers_p = false;
+
+ output_vcard_buttons_p = false;
+
+ variable_width_plaintext_p = false;
+ wrap_long_lines_p = false;
+ rot13_p = false;
+ part_to_load = nullptr;
+
+ no_output_p = false;
+ write_html_p = false;
+
+ decrypt_p = false;
+
+ whattodo = 0 ;
+ default_charset = nullptr;
+ override_charset = false;
+ force_user_charset = false;
+ stream_closure = nullptr;
+
+ /* For setting up the display stream, so that the MIME parser can inform
+ the caller of the type of the data it will be getting. */
+ output_init_fn = nullptr;
+ output_fn = nullptr;
+
+ output_closure = nullptr;
+
+ charset_conversion_fn = nullptr;
+ rfc1522_conversion_p = false;
+
+ file_type_fn = nullptr;
+
+ passwd_prompt_fn = nullptr;
+
+ html_closure = nullptr;
+
+ generate_header_html_fn = nullptr;
+ generate_post_header_html_fn = nullptr;
+ generate_footer_html_fn = nullptr;
+ generate_reference_url_fn = nullptr;
+ generate_mailto_url_fn = nullptr;
+ generate_news_url_fn = nullptr;
+
+ image_begin = nullptr;
+ image_end = nullptr;
+ image_write_buffer = nullptr;
+ make_image_html = nullptr;
+ state = nullptr;
+
+#ifdef MIME_DRAFTS
+ decompose_file_p = false;
+ done_parsing_outer_headers = false;
+ is_multipart_msg = false;
+ decompose_init_count = 0;
+
+ signed_p = false;
+ caller_need_root_headers = false;
+ decompose_headers_info_fn = nullptr;
+ decompose_file_init_fn = nullptr;
+ decompose_file_output_fn = nullptr;
+ decompose_file_close_fn = nullptr;
+#endif /* MIME_DRAFTS */
+
+ attachment_icon_layer_id = 0;
+
+ missing_parts = false;
+ show_attachment_inline_p = false;
+ quote_attachment_inline_p = false;
+ notify_nested_bodies = false;
+ write_pure_bodies = false;
+ metadata_only = false;
+}
+
+MimeDisplayOptions::~MimeDisplayOptions()
+{
+ PR_FREEIF(part_to_load);
+ PR_FREEIF(default_charset);
+}
+////////////////////////////////////////////////////////////////
+// Bridge routines for new stream converter XP-COM interface
+////////////////////////////////////////////////////////////////
+extern "C" void *
+mime_bridge_create_display_stream(
+ nsIMimeEmitter *newEmitter,
+ nsStreamConverter *newPluginObj2,
+ nsIURI *uri,
+ nsMimeOutputType format_out,
+ uint32_t whattodo,
+ nsIChannel *aChannel)
+{
+ int status = 0;
+ MimeObject *obj;
+ mime_stream_data *msd;
+ nsMIMESession *stream = 0;
+
+ if (!uri)
+ return nullptr;
+
+ msd = new mime_stream_data;
+ if (!msd)
+ return NULL;
+
+ // Assign the new mime emitter - will handle output operations
+ msd->output_emitter = newEmitter;
+ msd->firstCheck = true;
+
+ // Store the URL string for this decode operation
+ nsAutoCString urlString;
+ nsresult rv;
+
+ // Keep a hold of the channel...
+ msd->channel = aChannel;
+ rv = uri->GetSpec(urlString);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (!urlString.IsEmpty())
+ {
+ msd->url_name = ToNewCString(urlString);
+ if (!(msd->url_name))
+ {
+ delete msd;
+ return NULL;
+ }
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(uri);
+ if (msgUrl)
+ msgUrl->GetOriginalSpec(&msd->orig_url_name);
+ }
+ }
+
+ msd->format_out = format_out; // output format
+ msd->pluginObj2 = newPluginObj2; // the plugin object pointer
+
+ msd->options = new MimeDisplayOptions;
+ if (!msd->options)
+ {
+ delete msd;
+ return 0;
+ }
+// memset(msd->options, 0, sizeof(*msd->options));
+ msd->options->format_out = format_out; // output format
+
+ msd->options->m_prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ {
+ delete msd;
+ return nullptr;
+ }
+
+ // Need the text converter...
+ rv = CallCreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &(msd->options->conv));
+ if (NS_FAILED(rv))
+ {
+ msd->options->m_prefBranch = nullptr;
+ delete msd;
+ return nullptr;
+ }
+
+ //
+ // Set the defaults, based on the context, and the output-type.
+ //
+ MIME_HeaderType = MimeHeadersAll;
+ msd->options->write_html_p = true;
+ switch (format_out)
+ {
+ case nsMimeOutput::nsMimeMessageSplitDisplay: // the wrapper HTML output to produce the split header/body display
+ case nsMimeOutput::nsMimeMessageHeaderDisplay: // the split header/body display
+ case nsMimeOutput::nsMimeMessageBodyDisplay: // the split header/body display
+ msd->options->fancy_headers_p = true;
+ msd->options->output_vcard_buttons_p = true;
+ break;
+
+ case nsMimeOutput::nsMimeMessageSaveAs: // Save As operations
+ case nsMimeOutput::nsMimeMessageQuoting: // all HTML quoted/printed output
+ case nsMimeOutput::nsMimeMessagePrintOutput:
+ msd->options->fancy_headers_p = true;
+ break;
+
+ case nsMimeOutput::nsMimeMessageBodyQuoting: // only HTML body quoted output
+ MIME_HeaderType = MimeHeadersNone;
+ break;
+
+ case nsMimeOutput::nsMimeMessageAttach: // handling attachment storage
+ msd->options->write_html_p = false;
+ break;
+ case nsMimeOutput::nsMimeMessageRaw: // the raw RFC822 data (view source) and attachments
+ case nsMimeOutput::nsMimeMessageDraftOrTemplate: // Loading drafts & templates
+ case nsMimeOutput::nsMimeMessageEditorTemplate: // Loading templates into editor
+ case nsMimeOutput::nsMimeMessageFilterSniffer: // generating an output that can be scan by a message filter
+ break;
+
+ case nsMimeOutput::nsMimeMessageDecrypt:
+ msd->options->decrypt_p = true;
+ msd->options->write_html_p = false;
+ break;
+ }
+
+ ////////////////////////////////////////////////////////////
+ // Now, get the libmime prefs...
+ ////////////////////////////////////////////////////////////
+
+ MIME_WrapLongLines = true;
+ MIME_VariableWidthPlaintext = true;
+ msd->options->force_user_charset = false;
+
+ if (msd->options->m_prefBranch)
+ {
+ msd->options->m_prefBranch->GetBoolPref("mail.wrap_long_lines", &MIME_WrapLongLines);
+ msd->options->m_prefBranch->GetBoolPref("mail.fixed_width_messages", &MIME_VariableWidthPlaintext);
+ //
+ // Charset overrides takes place here
+ //
+ // We have a bool pref (mail.force_user_charset) to deal with attachments.
+ // 1) If true - libmime does NO conversion and just passes it through to raptor
+ // 2) If false, then we try to use the charset of the part and if not available,
+ // the charset of the root message
+ //
+ msd->options->m_prefBranch->GetBoolPref("mail.force_user_charset", &(msd->options->force_user_charset));
+ msd->options->m_prefBranch->GetBoolPref("mail.inline_attachments", &(msd->options->show_attachment_inline_p));
+ msd->options->m_prefBranch->GetBoolPref("mail.reply_quote_inline", &(msd->options->quote_attachment_inline_p));
+ msd->options->m_prefBranch->GetIntPref("mailnews.display.html_as", &(msd->options->html_as_p));
+ }
+ /* This pref is written down in with the
+ opposite sense of what we like to use... */
+ MIME_VariableWidthPlaintext = !MIME_VariableWidthPlaintext;
+
+ msd->options->wrap_long_lines_p = MIME_WrapLongLines;
+ msd->options->headers = MIME_HeaderType;
+
+ // We need to have the URL to be able to support the various
+ // arguments
+ status = mime_parse_url_options(msd->url_name, msd->options);
+ if (status < 0)
+ {
+ PR_FREEIF(msd->options->part_to_load);
+ PR_Free(msd->options);
+ delete msd;
+ return 0;
+ }
+
+ if (msd->options->headers == MimeHeadersMicro &&
+ (msd->url_name == NULL || (strncmp(msd->url_name, "news:", 5) != 0 &&
+ strncmp(msd->url_name, "snews:", 6) != 0)) )
+ msd->options->headers = MimeHeadersMicroPlus;
+
+ msd->options->url = msd->url_name;
+ msd->options->output_init_fn = mime_output_init_fn;
+
+ msd->options->output_fn = mime_output_fn;
+
+ msd->options->whattodo = whattodo;
+ msd->options->charset_conversion_fn = mime_convert_charset;
+ msd->options->rfc1522_conversion_p = true;
+ msd->options->file_type_fn = mime_file_type;
+ msd->options->stream_closure = msd;
+ msd->options->passwd_prompt_fn = 0;
+
+ msd->options->image_begin = mime_image_begin;
+ msd->options->image_end = mime_image_end;
+ msd->options->make_image_html = mime_image_make_image_html;
+ msd->options->image_write_buffer = mime_image_write_buffer;
+
+ msd->options->variable_width_plaintext_p = MIME_VariableWidthPlaintext;
+
+ // If this is a part, then we should emit the HTML to render the data
+ // (i.e. embedded images)
+ if (msd->options->part_to_load && msd->options->format_out != nsMimeOutput::nsMimeMessageBodyDisplay)
+ msd->options->write_html_p = false;
+
+ obj = mime_new ((MimeObjectClass *)&mimeMessageClass, (MimeHeaders *) NULL, MESSAGE_RFC822);
+ if (!obj)
+ {
+ delete msd->options;
+ delete msd;
+ return 0;
+ }
+
+ obj->options = msd->options;
+ msd->obj = obj;
+
+ /* Both of these better not be true at the same time. */
+ PR_ASSERT(! (obj->options->decrypt_p && obj->options->write_html_p));
+
+ stream = PR_NEW(nsMIMESession);
+ if (!stream)
+ {
+ delete msd->options;
+ delete msd;
+ PR_Free(obj);
+ return 0;
+ }
+
+ ResetMsgHeaderSinkProps(uri);
+
+ memset (stream, 0, sizeof (*stream));
+ stream->name = "MIME Conversion Stream";
+ stream->complete = mime_display_stream_complete;
+ stream->abort = mime_display_stream_abort;
+ stream->put_block = mime_display_stream_write;
+ stream->data_object = msd;
+
+ status = obj->clazz->initialize(obj);
+ if (status >= 0)
+ status = obj->clazz->parse_begin(obj);
+ if (status < 0)
+ {
+ PR_Free(stream);
+ delete msd->options;
+ delete msd;
+ PR_Free(obj);
+ return 0;
+ }
+
+ return stream;
+}
+
+//
+// Emitter Wrapper Routines!
+//
+nsIMimeEmitter *
+GetMimeEmitter(MimeDisplayOptions *opt)
+{
+ mime_stream_data *msd = (mime_stream_data *)opt->stream_closure;
+ if (!msd)
+ return NULL;
+
+ nsIMimeEmitter *ptr = (nsIMimeEmitter *)(msd->output_emitter);
+ return ptr;
+}
+
+mime_stream_data *
+GetMSD(MimeDisplayOptions *opt)
+{
+ if (!opt)
+ return nullptr;
+ mime_stream_data *msd = (mime_stream_data *)opt->stream_closure;
+ return msd;
+}
+
+bool
+NoEmitterProcessing(nsMimeOutputType format_out)
+{
+ if ( (format_out == nsMimeOutput::nsMimeMessageDraftOrTemplate) ||
+ (format_out == nsMimeOutput::nsMimeMessageEditorTemplate))
+ return true;
+ else
+ return false;
+}
+
+extern "C" nsresult
+mimeEmitterAddAttachmentField(MimeDisplayOptions *opt, const char *field, const char *value)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+ return emitter->AddAttachmentField(field, value);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+extern "C" nsresult
+mimeEmitterAddHeaderField(MimeDisplayOptions *opt, const char *field, const char *value)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+ return emitter->AddHeaderField(field, value);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+extern "C" nsresult
+mimeEmitterAddAllHeaders(MimeDisplayOptions *opt, const char *allheaders, const int32_t allheadersize)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+ return emitter->AddAllHeaders(Substring(allheaders,
+ allheaders + allheadersize));
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+extern "C" nsresult
+mimeEmitterStartAttachment(MimeDisplayOptions *opt, const char *name, const char *contentType, const char *url,
+ bool aIsExternalAttachment)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+ return emitter->StartAttachment(nsDependentCString(name), contentType, url,
+ aIsExternalAttachment);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+extern "C" nsresult
+mimeEmitterEndAttachment(MimeDisplayOptions *opt)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+ if (emitter)
+ return emitter->EndAttachment();
+ else
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+extern "C" nsresult
+mimeEmitterEndAllAttachments(MimeDisplayOptions *opt)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+ if (emitter)
+ return emitter->EndAllAttachments();
+ else
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+extern "C" nsresult
+mimeEmitterStartBody(MimeDisplayOptions *opt, bool bodyOnly, const char *msgID, const char *outCharset)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+ return emitter->StartBody(bodyOnly, msgID, outCharset);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+extern "C" nsresult
+mimeEmitterEndBody(MimeDisplayOptions *opt)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+ return emitter->EndBody();
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+extern "C" nsresult
+mimeEmitterEndHeader(MimeDisplayOptions *opt, MimeObject *obj)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+
+ nsCString name;
+ if (msd->format_out == nsMimeOutput::nsMimeMessageSplitDisplay ||
+ msd->format_out == nsMimeOutput::nsMimeMessageHeaderDisplay ||
+ msd->format_out == nsMimeOutput::nsMimeMessageBodyDisplay ||
+ msd->format_out == nsMimeOutput::nsMimeMessageSaveAs ||
+ msd->format_out == nsMimeOutput::nsMimeMessagePrintOutput) {
+ if (obj->headers) {
+ nsMsgAttachmentData attachment;
+ attIndex = 0;
+ nsresult rv = GenerateAttachmentData(obj, msd->url_name, opt, false,
+ 0, &attachment);
+
+ if (NS_SUCCEEDED(rv))
+ name.Assign(attachment.m_realName);
+ }
+ }
+
+ MimeHeaders_convert_header_value(opt, name, false);
+ return emitter->EndHeader(name);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+extern "C" nsresult
+mimeEmitterUpdateCharacterSet(MimeDisplayOptions *opt, const char *aCharset)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+ return emitter->UpdateCharacterSet(aCharset);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+extern "C" nsresult
+mimeEmitterStartHeader(MimeDisplayOptions *opt, bool rootMailHeader, bool headerOnly, const char *msgID,
+ const char *outCharset)
+{
+ // Check for draft processing...
+ if (NoEmitterProcessing(opt->format_out))
+ return NS_OK;
+
+ mime_stream_data *msd = GetMSD(opt);
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ if (msd->output_emitter)
+ {
+ nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
+ return emitter->StartHeader(rootMailHeader, headerOnly, msgID, outCharset);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+
+extern "C" nsresult
+mimeSetNewURL(nsMIMESession *stream, char *url)
+{
+ if ( (!stream) || (!url) || (!*url) )
+ return NS_ERROR_FAILURE;
+
+ mime_stream_data *msd = (mime_stream_data *)stream->data_object;
+ if (!msd)
+ return NS_ERROR_FAILURE;
+
+ char *tmpPtr = strdup(url);
+ if (!tmpPtr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ PR_FREEIF(msd->url_name);
+ msd->url_name = tmpPtr;
+ return NS_OK;
+}
+
+#define MIME_URL "chrome://messenger/locale/mime.properties"
+
+extern "C"
+char *
+MimeGetStringByID(int32_t stringID)
+{
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ stringBundleService->CreateBundle(MIME_URL, getter_AddRefs(stringBundle));
+ if (stringBundle)
+ {
+ nsString v;
+ if (NS_SUCCEEDED(stringBundle->GetStringFromID(stringID, getter_Copies(v))))
+ return ToNewUTF8String(v);
+ }
+
+ return strdup("???");
+}
+
+extern "C"
+char *
+MimeGetStringByName(const char16_t *stringName)
+{
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ stringBundleService->CreateBundle(MIME_URL, getter_AddRefs(stringBundle));
+ if (stringBundle)
+ {
+ nsString v;
+ if (NS_SUCCEEDED(stringBundle->GetStringFromName(stringName, getter_Copies(v))))
+ return ToNewUTF8String(v);
+ }
+
+ return strdup("???");
+}
+
+void
+ResetChannelCharset(MimeObject *obj)
+{
+ if (obj->options && obj->options->stream_closure &&
+ obj->options->default_charset && obj->headers )
+ {
+ mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure);
+ char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false);
+ if ( (ct) && (msd) && (msd->channel) )
+ {
+ char *ptr = strstr(ct, "charset=");
+ if (ptr)
+ {
+ // First, setup the channel!
+ msd->channel->SetContentType(nsDependentCString(ct));
+
+ // Second, if this is a Save As operation, then we need to convert
+ // to override the output charset!
+ mime_stream_data *msd = GetMSD(obj->options);
+ if ( (msd) && (msd->format_out == nsMimeOutput::nsMimeMessageSaveAs) )
+ {
+ // Extract the charset alone
+ char *cSet = nullptr;
+ if (*(ptr+8) == '"')
+ cSet = strdup(ptr+9);
+ else
+ cSet = strdup(ptr+8);
+ if (cSet)
+ {
+ char *ptr2 = cSet;
+ while ( (*cSet) && (*cSet != ' ') && (*cSet != ';') &&
+ (*cSet != '\r') && (*cSet != '\n') && (*cSet != '"') )
+ ptr2++;
+
+ if (*cSet) {
+ PR_FREEIF(obj->options->default_charset);
+ obj->options->default_charset = strdup(cSet);
+ obj->options->override_charset = true;
+ }
+
+ PR_FREEIF(cSet);
+ }
+ }
+ }
+ PR_FREEIF(ct);
+ }
+ }
+}
+
+ ////////////////////////////////////////////////////////////
+ // Function to get up mail/news fontlang
+ ////////////////////////////////////////////////////////////
+
+
+nsresult GetMailNewsFont(MimeObject *obj, bool styleFixed, int32_t *fontPixelSize,
+ int32_t *fontSizePercentage, nsCString& fontLang)
+{
+ nsresult rv = NS_OK;
+
+ nsIPrefBranch *prefBranch = GetPrefBranch(obj->options);
+ if (prefBranch) {
+ MimeInlineText *text = (MimeInlineText *) obj;
+ nsAutoCString charset;
+
+ // get a charset
+ if (!text->initializeCharset)
+ ((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj);
+
+ if (!text->charset || !(*text->charset))
+ charset.Assign("us-ascii");
+ else
+ charset.Assign(text->charset);
+
+ nsCOMPtr<nsICharsetConverterManager> charSetConverterManager2;
+ nsCOMPtr<nsIAtom> langGroupAtom;
+ nsAutoCString prefStr;
+
+ ToLowerCase(charset);
+
+ charSetConverterManager2 = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ if ( NS_FAILED(rv))
+ return rv;
+
+ // get a language, e.g. x-western, ja
+ rv = charSetConverterManager2->GetCharsetLangGroup(charset.get(), getter_AddRefs(langGroupAtom));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = langGroupAtom->ToUTF8String(fontLang);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // get a font size from pref
+ prefStr.Assign(!styleFixed ? "font.size.variable." : "font.size.fixed.");
+ prefStr.Append(fontLang);
+ rv = prefBranch->GetIntPref(prefStr.get(), fontPixelSize);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIPrefBranch> prefDefBranch;
+ nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if(prefSvc)
+ rv = prefSvc->GetDefaultBranch("", getter_AddRefs(prefDefBranch));
+
+ if(!prefDefBranch)
+ return rv;
+
+ // get original font size
+ int32_t originalSize;
+ rv = prefDefBranch->GetIntPref(prefStr.get(), &originalSize);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // calculate percentage
+ *fontSizePercentage = originalSize ?
+ (int32_t)((float)*fontPixelSize / (float)originalSize * 100) : 0;
+
+ }
+
+ return NS_OK;
+}
+
+
+
+/**
+ * This function synchronously converts an HTML document (as string)
+ * to plaintext (as string) using the Gecko converter.
+ *
+ * @param flags see nsIDocumentEncoder.h
+ */
+nsresult
+HTML2Plaintext(const nsString& inString, nsString& outString,
+ uint32_t flags, uint32_t wrapCol)
+{
+ nsCOMPtr<nsIParserUtils> utils =
+ do_GetService(NS_PARSERUTILS_CONTRACTID);
+ return utils->ConvertToPlainText(inString, flags, wrapCol, outString);
+}
+
+
+/**
+ * This function synchronously sanitizes an HTML document (string->string)
+ * using the Gecko nsTreeSanitizer.
+ */
+nsresult
+HTMLSanitize(const nsString& inString, nsString& outString)
+{
+ // If you want to add alternative sanitization, you can insert a conditional
+ // call to another sanitizer and an early return here.
+
+ uint32_t flags = nsIParserUtils::SanitizerCidEmbedsOnly |
+ nsIParserUtils::SanitizerDropForms;
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ bool dropPresentational = true;
+ bool dropMedia = false;
+ prefs->GetBoolPref(
+ "mailnews.display.html_sanitizer.drop_non_css_presentation",
+ &dropPresentational);
+ prefs->GetBoolPref(
+ "mailnews.display.html_sanitizer.drop_media",
+ &dropMedia);
+ if (dropPresentational)
+ flags |= nsIParserUtils::SanitizerDropNonCSSPresentation;
+ if (dropMedia)
+ flags |= nsIParserUtils::SanitizerDropMedia;
+
+ nsCOMPtr<nsIParserUtils> utils = do_GetService(NS_PARSERUTILS_CONTRACTID);
+ return utils->Sanitize(inString, flags, outString);
+}
diff --git a/mailnews/mime/src/mimemoz2.h b/mailnews/mime/src/mimemoz2.h
new file mode 100644
index 000000000..962a42ae0
--- /dev/null
+++ b/mailnews/mime/src/mimemoz2.h
@@ -0,0 +1,196 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMOZ_H_
+#define _MIMEMOZ_H_
+
+#include "nsStreamConverter.h"
+#include "nsIMimeEmitter.h"
+#include "nsIURI.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsIMsgSend.h"
+#include "modmimee.h"
+#include "nsMsgAttachmentData.h"
+
+// SHERRY - Need to get these out of here eventually
+
+#ifdef XP_UNIX
+#undef Bool
+#endif
+
+
+
+#include "mimei.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "nsIPrefBranch.h"
+
+typedef struct _nsMIMESession nsMIMESession;
+
+/* stream functions */
+typedef unsigned int
+(*MKSessionWriteReadyFunc) (nsMIMESession *stream);
+
+#define MAX_WRITE_READY (((unsigned) (~0) << 1) >> 1) /* must be <= than MAXINT!!!!! */
+
+typedef int
+(*MKSessionWriteFunc) (nsMIMESession *stream, const char *str, int32_t len);
+
+typedef void
+(*MKSessionCompleteFunc) (nsMIMESession *stream);
+
+typedef void
+(*MKSessionAbortFunc) (nsMIMESession *stream, int status);
+
+/* streamclass function */
+struct _nsMIMESession {
+
+ const char * name; /* Just for diagnostics */
+
+ void * window_id; /* used for progress messages, etc. */
+
+ void * data_object; /* a pointer to whatever
+ * structure you wish to have
+ * passed to the routines below
+ * during writes, etc...
+ *
+ * this data object should hold
+ * the document, document
+ * structure or a pointer to the
+ * document.
+ */
+
+ MKSessionWriteReadyFunc is_write_ready; /* checks to see if the stream is ready
+ * for writing. Returns 0 if not ready
+ * or the number of bytes that it can
+ * accept for write
+ */
+ MKSessionWriteFunc put_block; /* writes a block of data to the stream */
+ MKSessionCompleteFunc complete; /* normal end */
+ MKSessionAbortFunc abort; /* abnormal end */
+
+ bool is_multipart; /* is the stream part of a multipart sequence */
+};
+
+/*
+ * This is for the reworked mime parser.
+ */
+class mime_stream_data { /* This object is the state we pass around
+ amongst the various stream functions
+ used by MIME_MessageConverter(). */
+public:
+ mime_stream_data();
+
+ char *url_name;
+ char *orig_url_name; /* original url name */
+ nsCOMPtr<nsIChannel> channel;
+ nsMimeOutputType format_out;
+ void *pluginObj2; /* The new XP-COM stream converter object */
+ nsMIMESession *istream; /* Holdover - new stream we're writing out image data-if any. */
+ MimeObject *obj; /* The root parser object */
+ MimeDisplayOptions *options; /* Data for communicating with libmime.a */
+ MimeHeaders *headers; /* Copy of outer most mime header */
+
+ nsIMimeEmitter *output_emitter; /* Output emitter engine for libmime */
+ bool firstCheck; /* Is this the first look at the stream data */
+};
+
+//
+// This object is the state we use for loading drafts and templates...
+//
+class mime_draft_data
+{
+public:
+ mime_draft_data();
+ char *url_name; // original url name */
+ nsMimeOutputType format_out; // intended output format; should be FO_OPEN_DRAFT */
+ nsMIMESession *stream; // not used for now
+ MimeObject *obj; // The root
+ MimeDisplayOptions *options; // data for communicating with libmime
+ MimeHeaders *headers; // Copy of outer most mime header
+ nsTArray<nsMsgAttachedFile*> attachments;// attachments
+ nsMsgAttachedFile *messageBody; // message body
+ nsMsgAttachedFile *curAttachment; // temp
+
+ nsCOMPtr <nsIFile> tmpFile;
+ nsCOMPtr <nsIOutputStream> tmpFileStream; // output file handle
+
+ MimeDecoderData *decoder_data;
+ char *mailcharset; // get it from CHARSET of Content-Type
+ bool forwardInline;
+ bool forwardInlineFilter;
+ bool overrideComposeFormat; // Override compose format (for forward inline).
+ nsString forwardToAddress;
+ nsCOMPtr<nsIMsgIdentity> identity;
+ char *originalMsgURI; // the original URI of the message we are currently processing
+ nsCOMPtr<nsIMsgDBHdr> origMsgHdr;
+};
+
+////////////////////////////////////////////////////////////////
+// Bridge routines for legacy mime code
+////////////////////////////////////////////////////////////////
+
+// Create bridge stream for libmime
+extern "C"
+void *mime_bridge_create_display_stream(nsIMimeEmitter *newEmitter,
+ nsStreamConverter *newPluginObj2,
+ nsIURI *uri,
+ nsMimeOutputType format_out,
+ uint32_t whattodo,
+ nsIChannel *aChannel);
+
+// To get the mime emitter...
+extern "C" nsIMimeEmitter *GetMimeEmitter(MimeDisplayOptions *opt);
+
+// To support 2 types of emitters...we need these routines :-(
+extern "C" nsresult mimeSetNewURL(nsMIMESession *stream, char *url);
+extern "C" nsresult mimeEmitterAddAttachmentField(MimeDisplayOptions *opt, const char *field, const char *value);
+extern "C" nsresult mimeEmitterAddHeaderField(MimeDisplayOptions *opt, const char *field, const char *value);
+extern "C" nsresult mimeEmitterAddAllHeaders(MimeDisplayOptions *opt, const char *allheaders, const int32_t allheadersize);
+extern "C" nsresult mimeEmitterStartAttachment(MimeDisplayOptions *opt, const char *name, const char *contentType, const char *url,
+ bool aIsExternalAttachment);
+extern "C" nsresult mimeEmitterEndAttachment(MimeDisplayOptions *opt);
+extern "C" nsresult mimeEmitterEndAllAttachments(MimeDisplayOptions *opt);
+extern "C" nsresult mimeEmitterStartBody(MimeDisplayOptions *opt, bool bodyOnly, const char *msgID, const char *outCharset);
+extern "C" nsresult mimeEmitterEndBody(MimeDisplayOptions *opt);
+extern "C" nsresult mimeEmitterEndHeader(MimeDisplayOptions *opt, MimeObject *obj);
+extern "C" nsresult mimeEmitterStartHeader(MimeDisplayOptions *opt, bool rootMailHeader, bool headerOnly, const char *msgID,
+ const char *outCharset);
+extern "C" nsresult mimeEmitterUpdateCharacterSet(MimeDisplayOptions *opt, const char *aCharset);
+
+extern "C" nsresult MimeGetAttachmentList(MimeObject *tobj, const char *aMessageURL, nsMsgAttachmentData **data);
+
+/* To Get the connnection to prefs service manager */
+extern "C" nsIPrefBranch *GetPrefBranch(MimeDisplayOptions *opt);
+
+// Get the text converter...
+mozITXTToHTMLConv *GetTextConverter(MimeDisplayOptions *opt);
+
+nsresult
+HTML2Plaintext(const nsString& inString, nsString& outString,
+ uint32_t flags, uint32_t wrapCol);
+nsresult
+HTMLSanitize(const nsString& inString, nsString& outString);
+
+extern "C" char *MimeGetStringByID(int32_t stringID);
+extern "C" char *MimeGetStringByName(const char16_t *stringName);
+
+// Utility to create a nsIURI object...
+extern "C" nsresult nsMimeNewURI(nsIURI** aInstancePtrResult, const char *aSpec, nsIURI *aBase);
+
+extern "C" nsresult SetMailCharacterSetToMsgWindow(MimeObject *obj, const char *aCharacterSet);
+
+extern "C" nsresult GetMailNewsFont(MimeObject *obj, bool styleFixed, int32_t *fontPixelSize, int32_t *fontSizePercentage, nsCString& fontLang);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _MIMEMOZ_H_ */
+
diff --git a/mailnews/mime/src/mimempar.cpp b/mailnews/mime/src/mimempar.cpp
new file mode 100644
index 000000000..efcd06445
--- /dev/null
+++ b/mailnews/mime/src/mimempar.cpp
@@ -0,0 +1,21 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimempar.h"
+#include "prlog.h"
+
+#define MIME_SUPERCLASS mimeMultipartClass
+MimeDefClass(MimeMultipartParallel, MimeMultipartParallelClass,
+ mimeMultipartParallelClass, &MIME_SUPERCLASS);
+
+static int
+MimeMultipartParallelClassInitialize(MimeMultipartParallelClass *clazz)
+{
+#ifdef DEBUG
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ PR_ASSERT(!oclass->class_initialized);
+#endif
+ return 0;
+}
diff --git a/mailnews/mime/src/mimempar.h b/mailnews/mime/src/mimempar.h
new file mode 100644
index 000000000..1ac39f1fc
--- /dev/null
+++ b/mailnews/mime/src/mimempar.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMPAR_H_
+#define _MIMEMPAR_H_
+
+#include "mimemult.h"
+
+/* The MimeMultipartParallel class implements the multipart/parallel MIME
+ container, which is currently no different from multipart/mixed, since
+ it's not clear that there's anything useful it could do differently.
+ */
+
+typedef struct MimeMultipartParallelClass MimeMultipartParallelClass;
+typedef struct MimeMultipartParallel MimeMultipartParallel;
+
+struct MimeMultipartParallelClass {
+ MimeMultipartClass multipart;
+};
+
+extern MimeMultipartParallelClass mimeMultipartParallelClass;
+
+struct MimeMultipartParallel {
+ MimeMultipart multipart;
+};
+
+#define MimeMultipartParallelClassInitializer(ITYPE,CSUPER) \
+ { MimeMultipartClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEMPAR_H_ */
diff --git a/mailnews/mime/src/mimemrel.cpp b/mailnews/mime/src/mimemrel.cpp
new file mode 100644
index 000000000..bbcb990b5
--- /dev/null
+++ b/mailnews/mime/src/mimemrel.cpp
@@ -0,0 +1,1199 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+/* Thoughts on how to implement this:
+
+ = if the type of this multipart/related is not text/html, then treat
+ it the same as multipart/mixed.
+
+ = For each part in this multipart/related
+ = if this part is not the "top" part
+ = then save this part to a tmp file or a memory object,
+ kind-of like what we do for multipart/alternative sub-parts.
+ If this is an object we're blocked on (see below) send its data along.
+ = else
+ = emit this part (remember, it's of type text/html)
+ = at some point, layout may load a URL for <IMG SRC="cid:xxxx">.
+ we intercept that.
+ = if one of our cached parts has that cid, return the data for it.
+ = else, "block", the same way the image library blocks layout when it
+ doesn't yet have the size of the image.
+ = at some point, layout may load a URL for <IMG SRC="relative/yyy">.
+ we need to intercept that too.
+ = expand the URL, and compare it to our cached objects.
+ if it matches, return it.
+ = else block on it.
+
+ = once we get to the end, if we have any sub-part references that we're
+ still blocked on, map over them:
+ = if they're cid: references, close them ("broken image" results.)
+ = if they're URLs, then load them in the normal way.
+
+ --------------------------------------------------
+
+ Ok, that's fairly complicated. How about an approach where we go through
+ all the parts first, and don't emit until the end?
+
+ = if the type of this multipart/related is not text/html, then treat
+ it the same as multipart/mixed.
+
+ = For each part in this multipart/related
+ = save this part to a tmp file or a memory object,
+ like what we do for multipart/alternative sub-parts.
+
+ = Emit the "top" part (the text/html one)
+ = intercept all calls to NET_GetURL, to allow us to rewrite the URL.
+ (hook into netlib, or only into imglib's calls to GetURL?)
+ (make sure we're behaving in a context-local way.)
+
+ = when a URL is loaded, look through our cached parts for a match.
+ = if we find one, map that URL to a "cid:" URL
+ = else, let it load normally
+
+ = at some point, layout may load a URL for <IMG SRC="cid:xxxx">.
+ it will do this either because that's what was in the HTML, or because
+ that's how we "rewrote" the URLs when we intercepted NET_GetURL.
+
+ = if one of our cached parts has the requested cid, return the data
+ for it.
+ = else, generate a "broken image"
+
+ = free all the cached data
+
+ --------------------------------------------------
+
+ How hard would be an approach where we rewrite the HTML?
+ (Looks like it's not much easier, and might be more error-prone.)
+
+ = if the type of this multipart/related is not text/html, then treat
+ it the same as multipart/mixed.
+
+ = For each part in this multipart/related
+ = save this part to a tmp file or a memory object,
+ like what we do for multipart/alternative sub-parts.
+
+ = Parse the "top" part, and emit slightly different HTML:
+ = for each <IMG SRC>, <IMG LOWSRC>, <A HREF>? Any others?
+ = look through our cached parts for a matching URL
+ = if we find one, map that URL to a "cid:" URL
+ = else, let it load normally
+
+ = at some point, layout may load a URL for <IMG SRC="cid:xxxx">.
+ = if one of our cached parts has the requested cid, return the data
+ for it.
+ = else, generate a "broken image"
+
+ = free all the cached data
+ */
+#include "nsCOMPtr.h"
+#include "mimemrel.h"
+#include "mimemapl.h"
+#include "prmem.h"
+#include "prprf.h"
+#include "prlog.h"
+#include "plstr.h"
+#include "mimemoz2.h"
+#include "nsStringGlue.h"
+#include "nsIURL.h"
+#include "msgCore.h"
+#include "nsMimeStringResources.h"
+#include "nsMimeTypes.h"
+#include "mimebuf.h"
+#include "nsMsgUtils.h"
+#include <ctype.h>
+
+//
+// External Defines...
+//
+
+extern nsresult
+nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile);
+
+#define MIME_SUPERCLASS mimeMultipartClass
+MimeDefClass(MimeMultipartRelated, MimeMultipartRelatedClass,
+ mimeMultipartRelatedClass, &MIME_SUPERCLASS);
+
+
+class MimeHashValue
+{
+public:
+ MimeHashValue(MimeObject *obj, char *url) {
+ m_obj = obj;
+ m_url = strdup(url);
+ }
+ virtual ~MimeHashValue() {
+ if (m_url)
+ PR_Free((void *)m_url);
+ }
+
+ MimeObject *m_obj;
+ char *m_url;
+};
+
+static int
+MimeMultipartRelated_initialize(MimeObject* obj)
+{
+ MimeMultipartRelated* relobj = (MimeMultipartRelated*) obj;
+ relobj->base_url = MimeHeaders_get(obj->headers, HEADER_CONTENT_BASE,
+ false, false);
+ /* rhp: need this for supporting Content-Location */
+ if (!relobj->base_url)
+ {
+ relobj->base_url = MimeHeaders_get(obj->headers, HEADER_CONTENT_LOCATION,
+ false, false);
+ }
+ /* rhp: need this for supporting Content-Location */
+
+ /* I used to have code here to test if the type was text/html. Then I
+ added multipart/alternative as being OK, too. Then I found that the
+ VCard spec seems to talk about having the first part of a
+ multipart/related be an application/directory. At that point, I decided
+ to punt. We handle anything as the first part, and stomp on the HTML it
+ generates to adjust tags to point into the other parts. This probably
+ works out to something reasonable in most cases. */
+
+ relobj->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues,
+ (PLHashAllocOps *)NULL, NULL);
+
+ if (!relobj->hash) return MIME_OUT_OF_MEMORY;
+
+ relobj->input_file_stream = nullptr;
+ relobj->output_file_stream = nullptr;
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj);
+}
+
+static int
+mime_multipart_related_nukehash(PLHashEntry *table,
+ int indx, void *arg)
+{
+ if (table->key)
+ PR_Free((char*) table->key);
+
+ if (table->value)
+ delete (MimeHashValue *)table->value;
+
+ return HT_ENUMERATE_NEXT; /* XP_Maphash will continue traversing the hash */
+}
+
+static void
+MimeMultipartRelated_finalize (MimeObject *obj)
+{
+ MimeMultipartRelated* relobj = (MimeMultipartRelated*) obj;
+ PR_FREEIF(relobj->base_url);
+ PR_FREEIF(relobj->curtag);
+ if (relobj->buffered_hdrs) {
+ PR_FREEIF(relobj->buffered_hdrs->all_headers);
+ PR_FREEIF(relobj->buffered_hdrs->heads);
+ PR_FREEIF(relobj->buffered_hdrs);
+ }
+ PR_FREEIF(relobj->head_buffer);
+ relobj->head_buffer_fp = 0;
+ relobj->head_buffer_size = 0;
+ if (relobj->hash) {
+ PL_HashTableEnumerateEntries(relobj->hash, mime_multipart_related_nukehash, NULL);
+ PL_HashTableDestroy(relobj->hash);
+ relobj->hash = NULL;
+ }
+
+ if (relobj->input_file_stream)
+ {
+ relobj->input_file_stream->Close();
+ relobj->input_file_stream = nullptr;
+ }
+
+ if (relobj->output_file_stream)
+ {
+ relobj->output_file_stream->Close();
+ relobj->output_file_stream = nullptr;
+ }
+
+ if (relobj->file_buffer)
+ {
+ relobj->file_buffer->Remove(false);
+ relobj->file_buffer = nullptr;
+ }
+
+ if (relobj->headobj) {
+ // In some error conditions when MimeMultipartRelated_parse_eof() isn't run
+ // (for example, no temp disk space available to extract message parts),
+ // the head object is also referenced as a child.
+ // If we free it, we remove the child reference first ... or crash later :-(
+ MimeContainer *cont = (MimeContainer *)relobj;
+ for (int i = 0; i < cont->nchildren; i++) {
+ if (cont->children[i] == relobj->headobj) {
+ // Shift remaining children down.
+ for (int j = i+1; j < cont->nchildren; j++) {
+ cont->children[j-1] = cont->children[j];
+ }
+ cont->children[--cont->nchildren] = nullptr;
+ break;
+ }
+ }
+
+ mime_free(relobj->headobj);
+ relobj->headobj = nullptr;
+ }
+
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj);
+}
+
+#define ISHEX(c) ( ((c) >= '0' && (c) <= '9') || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F') )
+#define NONHEX(c) (!ISHEX(c))
+
+extern "C" char *
+escape_unescaped_percents(const char *incomingURL)
+{
+ const char *inC;
+ char *outC;
+ char *result = (char *) PR_Malloc(strlen(incomingURL)*3+1);
+
+ if (result)
+ {
+ for(inC = incomingURL, outC = result; *inC != '\0'; inC++)
+ {
+ if (*inC == '%')
+ {
+ /* Check if either of the next two characters are non-hex. */
+ if ( !*(inC+1) || NONHEX(*(inC+1)) || !*(inC+2) || NONHEX(*(inC+2)) )
+ {
+ /* Hex characters don't follow, escape the
+ percent char */
+ *outC++ = '%'; *outC++ = '2'; *outC++ = '5';
+ }
+ else
+ {
+ /* Hex characters follow, so assume the percent
+ is escaping something else */
+ *outC++ = *inC;
+ }
+ }
+ else
+ *outC++ = *inC;
+ }
+ *outC = '\0';
+ }
+
+ return result;
+}
+
+/* This routine is only necessary because the mailbox URL fed to us
+ by the winfe can contain spaces and '>'s in it. It's a hack. */
+static char *
+escape_for_mrel_subst(char *inURL)
+{
+ char *output, *inC, *outC, *temp;
+
+ int size = strlen(inURL) + 1;
+
+ for(inC = inURL; *inC; inC++)
+ if ((*inC == ' ') || (*inC == '>'))
+ size += 2; /* space -> '%20', '>' -> '%3E', etc. */
+
+ output = (char *)PR_MALLOC(size);
+ if (output)
+ {
+ /* Walk through the source string, copying all chars
+ except for spaces, which get escaped. */
+ inC = inURL;
+ outC = output;
+ while(*inC)
+ {
+ if (*inC == ' ')
+ {
+ *outC++ = '%'; *outC++ = '2'; *outC++ = '0';
+ }
+ else if (*inC == '>')
+ {
+ *outC++ = '%'; *outC++ = '3'; *outC++ = 'E';
+ }
+ else
+ *outC++ = *inC;
+
+ inC++;
+ }
+ *outC = '\0';
+
+ temp = escape_unescaped_percents(output);
+ if (temp)
+ {
+ PR_FREEIF(output);
+ output = temp;
+ }
+ }
+ return output;
+}
+
+static bool
+MimeStartParamExists(MimeObject *obj, MimeObject* child)
+{
+ char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false);
+ char *st = (ct
+ ? MimeHeaders_get_parameter(ct, HEADER_PARM_START, NULL, NULL)
+ : 0);
+
+ PR_FREEIF(ct);
+ if (!st)
+ return false;
+
+ PR_FREEIF(st);
+ return true;
+}
+
+static bool
+MimeThisIsStartPart(MimeObject *obj, MimeObject* child)
+{
+ bool rval = false;
+ char *ct, *st, *cst;
+
+ ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false);
+ st = (ct
+ ? MimeHeaders_get_parameter(ct, HEADER_PARM_START, NULL, NULL)
+ : 0);
+
+ PR_FREEIF(ct);
+ if (!st)
+ return false;
+
+ cst = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, false, false);
+ if (!cst)
+ rval = false;
+ else
+ {
+ char *tmp = cst;
+ if (*tmp == '<')
+ {
+ int length;
+ tmp++;
+ length = strlen(tmp);
+ if (length > 0 && tmp[length - 1] == '>')
+ {
+ tmp[length - 1] = '\0';
+ }
+ }
+
+ rval = (!strcmp(st, tmp));
+ }
+
+ PR_FREEIF(st);
+ PR_FREEIF(cst);
+ return rval;
+}
+/* rhp - gotta support the "start" parameter */
+
+char *
+MakeAbsoluteURL(char *base_url, char *relative_url)
+{
+ char *retString = nullptr;
+ nsIURI *base = nullptr;
+
+ // if either is NULL, just return the relative if safe...
+ if (!base_url || !relative_url)
+ {
+ if (!relative_url)
+ return nullptr;
+
+ NS_MsgSACopy(&retString, relative_url);
+ return retString;
+ }
+
+ nsresult err = nsMimeNewURI(&base, base_url, nullptr);
+ if (NS_FAILED(err))
+ return nullptr;
+
+ nsAutoCString spec;
+
+ nsIURI *url = nullptr;
+ err = nsMimeNewURI(&url, relative_url, base);
+ if (NS_FAILED(err))
+ goto done;
+
+ err = url->GetSpec(spec);
+ if (NS_FAILED(err))
+ {
+ retString = nullptr;
+ goto done;
+ }
+ retString = ToNewCString(spec);
+
+done:
+ NS_IF_RELEASE(url);
+ NS_IF_RELEASE(base);
+ return retString;
+}
+
+static bool
+MimeMultipartRelated_output_child_p(MimeObject *obj, MimeObject* child)
+{
+ MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj;
+
+ /* rhp - Changed from "if (relobj->head_loaded)" alone to support the
+ start parameter
+ */
+ if (
+ (relobj->head_loaded) ||
+ (MimeStartParamExists(obj, child) && !MimeThisIsStartPart(obj, child))
+ )
+ {
+ /* This is a child part. Just remember the mapping between the URL
+ it represents and the part-URL to get it back. */
+
+ char* location = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION,
+ false, false);
+ if (!location) {
+ char* tmp = MimeHeaders_get(child->headers, HEADER_CONTENT_ID,
+ false, false);
+ if (tmp) {
+ char* tmp2 = tmp;
+ if (*tmp2 == '<') {
+ int length;
+ tmp2++;
+ length = strlen(tmp2);
+ if (length > 0 && tmp2[length - 1] == '>') {
+ tmp2[length - 1] = '\0';
+ }
+ }
+ location = PR_smprintf("cid:%s", tmp2);
+ PR_Free(tmp);
+ }
+ }
+
+ if (location) {
+ char *absolute;
+ char *base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_BASE,
+ false, false);
+ absolute = MakeAbsoluteURL(base_url ? base_url : relobj->base_url, location);
+
+ PR_FREEIF(base_url);
+ PR_Free(location);
+ if (absolute) {
+ nsAutoCString partnum;
+ nsAutoCString imappartnum;
+ partnum.Adopt(mime_part_address(child));
+ if (!partnum.IsEmpty()) {
+ if (obj->options->missing_parts)
+ {
+ char * imappart = mime_imap_part_address(child);
+ if (imappart)
+ imappartnum.Adopt(imappart);
+ }
+
+ /*
+ AppleDouble part need special care: we need to output only the data fork part of it.
+ The problem at this point is that we haven't yet decoded the children of the AppleDouble
+ part therfore we will have to hope the datafork is the second one!
+ */
+ if (mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
+ partnum.Append(".2");
+
+ char* part;
+ if (!imappartnum.IsEmpty())
+ part = mime_set_url_imap_part(obj->options->url, imappartnum.get(), partnum.get());
+ else
+ {
+ char *no_part_url = nullptr;
+ if (obj->options->part_to_load && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
+ no_part_url = mime_get_base_url(obj->options->url);
+ if (no_part_url)
+ {
+ part = mime_set_url_part(no_part_url, partnum.get(), false);
+ PR_Free(no_part_url);
+ }
+ else
+ part = mime_set_url_part(obj->options->url, partnum.get(), false);
+ }
+ if (part)
+ {
+ char *name = MimeHeaders_get_name(child->headers, child->options);
+ // let's stick the filename in the part so save as will work.
+ if (name)
+ {
+ char *savePart = part;
+ part = PR_smprintf("%s&filename=%s", savePart, name);
+ PR_Free(savePart);
+ PR_Free(name);
+ }
+ char *temp = part;
+ /* If there's a space in the url, escape the url.
+ (This happens primarily on Windows and Unix.) */
+ if (PL_strchr(part, ' ') || PL_strchr(part, '>') || PL_strchr(part, '%'))
+ temp = escape_for_mrel_subst(part);
+ MimeHashValue * value = new MimeHashValue(child, temp);
+ PL_HashTableAdd(relobj->hash, absolute, value);
+
+ /* rhp - If this part ALSO has a Content-ID we need to put that into
+ the hash table and this is what this code does
+ */
+ {
+ char *tloc;
+ char *tmp = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, false, false);
+ if (tmp)
+ {
+ char* tmp2 = tmp;
+ if (*tmp2 == '<')
+ {
+ int length;
+ tmp2++;
+ length = strlen(tmp2);
+ if (length > 0 && tmp2[length - 1] == '>')
+ {
+ tmp2[length - 1] = '\0';
+ }
+ }
+
+ tloc = PR_smprintf("cid:%s", tmp2);
+ PR_Free(tmp);
+ if (tloc)
+ {
+ MimeHashValue *value;
+ value = (MimeHashValue*)PL_HashTableLookup(relobj->hash, tloc);
+
+ if (!value)
+ {
+ value = new MimeHashValue(child, temp);
+ PL_HashTableAdd(relobj->hash, tloc, value);
+ }
+ else
+ PR_smprintf_free(tloc);
+ }
+ }
+ }
+ /* rhp - End of putting more stuff into the hash table */
+
+ /* it's possible that temp pointer is the same than the part pointer,
+ therefore be carefull to not freeing twice the same pointer */
+ if (temp && temp != part)
+ PR_Free(temp);
+ PR_Free(part);
+ }
+ }
+ }
+ }
+ } else {
+ /* Ah-hah! We're the head object. */
+ char* base_url;
+ relobj->head_loaded = true;
+ relobj->headobj = child;
+ relobj->buffered_hdrs = MimeHeaders_copy(child->headers);
+ base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_BASE,
+ false, false);
+ /* rhp: need this for supporting Content-Location */
+ if (!base_url)
+ {
+ base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION, false, false);
+ }
+ /* rhp: need this for supporting Content-Location */
+
+ if (base_url) {
+ /* If the head object has a base_url associated with it, use
+ that instead of any base_url that may have been associated
+ with the multipart/related. */
+ PR_FREEIF(relobj->base_url);
+ relobj->base_url = base_url;
+ }
+ }
+ if (obj->options && !obj->options->write_html_p
+#ifdef MIME_DRAFTS
+ && !obj->options->decompose_file_p
+#endif /* MIME_DRAFTS */
+ )
+ {
+ return true;
+ }
+
+ return false; /* Don't actually parse this child; we'll handle
+ all that at eof time. */
+}
+
+static int
+MimeMultipartRelated_parse_child_line (MimeObject *obj,
+ const char *line, int32_t length,
+ bool first_line_p)
+{
+ MimeContainer *cont = (MimeContainer *) obj;
+ MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj;
+ MimeObject *kid;
+
+ if (obj->options && !obj->options->write_html_p
+#ifdef MIME_DRAFTS
+ && !obj->options->decompose_file_p
+#endif /* MIME_DRAFTS */
+ )
+ {
+ /* Oh, just go do the normal thing... */
+ return ((MimeMultipartClass*)&MIME_SUPERCLASS)->
+ parse_child_line(obj, line, length, first_line_p);
+ }
+
+ /* Throw it away if this isn't the head object. (Someday, maybe we'll
+ cache it instead.) */
+ PR_ASSERT(cont->nchildren > 0);
+ if (cont->nchildren <= 0)
+ return -1;
+ kid = cont->children[cont->nchildren-1];
+ PR_ASSERT(kid);
+ if (!kid) return -1;
+ if (kid != relobj->headobj) return 0;
+
+ /* Buffer this up (###tw much code duplication from mimemalt.c) */
+ /* If we don't yet have a buffer (either memory or file) try and make a
+ memory buffer. */
+ if (!relobj->head_buffer && !relobj->file_buffer) {
+ int target_size = 1024 * 50; /* try for 50k */
+ while (target_size > 0) {
+ relobj->head_buffer = (char *) PR_MALLOC(target_size);
+ if (relobj->head_buffer) break; /* got it! */
+ target_size -= (1024 * 5); /* decrease it and try again */
+ }
+
+ if (relobj->head_buffer) {
+ relobj->head_buffer_size = target_size;
+ } else {
+ relobj->head_buffer_size = 0;
+ }
+
+ relobj->head_buffer_fp = 0;
+ }
+
+ nsresult rv;
+ /* Ok, if at this point we still don't have either kind of buffer, try and
+ make a file buffer. */
+ if (!relobj->head_buffer && !relobj->file_buffer)
+ {
+ nsCOMPtr <nsIFile> file;
+ rv = nsMsgCreateTempFile("nsma", getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, -1);
+ relobj->file_buffer = do_QueryInterface(file);
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(relobj->output_file_stream), relobj->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600);
+ NS_ENSURE_SUCCESS(rv, -1);
+ }
+
+ PR_ASSERT(relobj->head_buffer || relobj->output_file_stream);
+
+
+ /* If this line will fit in the memory buffer, put it there.
+ */
+ if (relobj->head_buffer &&
+ relobj->head_buffer_fp + length < relobj->head_buffer_size) {
+ memcpy(relobj->head_buffer + relobj->head_buffer_fp, line, length);
+ relobj->head_buffer_fp += length;
+ } else {
+ /* Otherwise it won't fit; write it to the file instead. */
+
+ /* If the file isn't open yet, open it, and dump the memory buffer
+ to it. */
+ if (!relobj->output_file_stream)
+ {
+ if (!relobj->file_buffer)
+ {
+ nsCOMPtr <nsIFile> file;
+ rv = nsMsgCreateTempFile("nsma", getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, -1);
+ relobj->file_buffer = do_QueryInterface(file);
+ }
+
+ nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(relobj->output_file_stream), relobj->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600);
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ if (relobj->head_buffer && relobj->head_buffer_fp)
+ {
+ uint32_t bytesWritten;
+ rv = relobj->output_file_stream->Write(relobj->head_buffer, relobj->head_buffer_fp, &bytesWritten);
+ if (NS_FAILED(rv) || (bytesWritten < relobj->head_buffer_fp))
+ return MIME_UNABLE_TO_OPEN_TMP_FILE;
+ }
+
+ PR_FREEIF(relobj->head_buffer);
+ relobj->head_buffer_fp = 0;
+ relobj->head_buffer_size = 0;
+ }
+
+ /* Dump this line to the file. */
+ uint32_t bytesWritten;
+ rv = relobj->output_file_stream->Write(line, length, &bytesWritten);
+ if ((int32_t) bytesWritten < length || NS_FAILED(rv))
+ return MIME_UNABLE_TO_OPEN_TMP_FILE;
+ }
+
+ return 0;
+}
+
+
+
+
+static int
+real_write(MimeMultipartRelated* relobj, const char* buf, int32_t size)
+{
+ MimeObject* obj = (MimeObject*) relobj;
+ void* closure = relobj->real_output_closure;
+
+#ifdef MIME_DRAFTS
+ if ( obj->options &&
+ obj->options->decompose_file_p &&
+ obj->options->decompose_file_output_fn )
+ {
+
+ // the buf here has already been decoded, but we want to use general output
+ // functions here that permit decoded or encoded input, using the closure
+ // to tell the difference. We'll temporarily disable the closure's decoder,
+ // then restore it when we are done. Not sure if we shouldn't just turn it off
+ // permanently though.
+
+ mime_draft_data *mdd = (mime_draft_data *) obj->options->stream_closure;
+ MimeDecoderData* old_decoder_data = mdd->decoder_data;
+ mdd->decoder_data = nullptr;
+ int status = obj->options->decompose_file_output_fn
+ (buf, size, (void *)mdd);
+ mdd->decoder_data = old_decoder_data;
+ return status;
+ }
+ else
+#endif /* MIME_DRAFTS */
+ {
+ if (!closure) {
+ MimeObject* lobj = (MimeObject*) relobj;
+ closure = lobj->options->stream_closure;
+ }
+ return relobj->real_output_fn(buf, size, closure);
+ }
+}
+
+
+static int
+push_tag(MimeMultipartRelated* relobj, const char* buf, int32_t size)
+{
+ if (size + relobj->curtag_length > relobj->curtag_max) {
+ relobj->curtag_max += 2 * size;
+ if (relobj->curtag_max < 1024)
+ relobj->curtag_max = 1024;
+
+ char* newBuf = (char*) PR_Realloc(relobj->curtag, relobj->curtag_max);
+ NS_ENSURE_TRUE(newBuf, MIME_OUT_OF_MEMORY);
+ relobj->curtag = newBuf;
+ }
+ memcpy(relobj->curtag + relobj->curtag_length, buf, size);
+ relobj->curtag_length += size;
+ return 0;
+}
+
+static bool accept_related_part(MimeMultipartRelated* relobj, MimeObject* part_obj)
+{
+ if (!relobj || !part_obj)
+ return false;
+
+ /* before accepting it as a valid related part, make sure we
+ are able to display it inline as an embedded object. Else just ignore
+ it, that will prevent any bad surprise... */
+ MimeObjectClass *clazz = mime_find_class (part_obj->content_type, part_obj->headers, part_obj->options, false);
+ if (clazz ? clazz->displayable_inline_p(clazz, part_obj->headers) : false)
+ return true;
+
+ /* ...but we always accept it if it's referenced by an anchor */
+ return (relobj->curtag && relobj->curtag_length >= 3 &&
+ (relobj->curtag[1] == 'A' || relobj->curtag[1] == 'a') && IS_SPACE(relobj->curtag[2]));
+}
+
+static int
+flush_tag(MimeMultipartRelated* relobj)
+{
+ int length = relobj->curtag_length;
+ char* buf;
+ int status;
+
+ if (relobj->curtag == NULL || length == 0) return 0;
+
+ status = push_tag(relobj, "", 1); /* Push on a trailing NULL. */
+ if (status < 0) return status;
+ buf = relobj->curtag;
+ PR_ASSERT(*buf == '<' && buf[length - 1] == '>');
+ while (*buf) {
+ char c;
+ char* absolute;
+ char* part_url;
+ char* ptr = buf;
+ char *ptr2;
+ char quoteDelimiter = '\0';
+ while (*ptr && *ptr != '=') ptr++;
+ if (*ptr == '=') {
+ /* Ignore = and leading space. */
+ /* Safe, because there's a '>' at the end! */
+ do {ptr++;} while (IS_SPACE(*ptr));
+ if (*ptr == '"' || *ptr == '\'') {
+ quoteDelimiter = *ptr;
+ /* Take up the quote and leading space here as well. */
+ /* Safe because there's a '>' at the end */
+ do {ptr++;} while (IS_SPACE(*ptr));
+ }
+ }
+ status = real_write(relobj, buf, ptr - buf);
+ if (status < 0) return status;
+ buf = ptr;
+ if (!*buf) break;
+ if (quoteDelimiter)
+ {
+ ptr = PL_strnchr(buf, quoteDelimiter, length - (buf - relobj->curtag));
+ } else {
+ for (ptr = buf; *ptr ; ptr++) {
+ if (*ptr == '>' || IS_SPACE(*ptr)) break;
+ }
+ PR_ASSERT(*ptr);
+ }
+ if (!ptr || !*ptr) break;
+
+ while(buf < ptr)
+ {
+ /* ### mwelch For each word in the value string, see if
+ the word is a cid: URL. If so, attempt to
+ substitute the appropriate mailbox part URL in
+ its place. */
+ ptr2=buf; /* walk from the left end rightward */
+ while((ptr2<ptr) && (!IS_SPACE(*ptr2)))
+ ptr2++;
+ /* Compare the beginning of the word with "cid:". Yuck. */
+ if (((ptr2 - buf) > 4) &&
+ ((buf[0]=='c' || buf[0]=='C') &&
+ (buf[1]=='i' || buf[1]=='I') &&
+ (buf[2]=='d' || buf[2]=='D') &&
+ buf[3]==':'))
+ {
+ // Make sure it's lowercase, otherwise it won't be found in the hash table
+ buf[0] = 'c'; buf[1] = 'i'; buf[2] = 'd';
+
+ /* Null terminate the word so we can... */
+ c = *ptr2;
+ *ptr2 = '\0';
+
+ /* Construct a URL out of the word. */
+ absolute = MakeAbsoluteURL(relobj->base_url, buf);
+
+ /* See if we have a mailbox part URL
+ corresponding to this cid. */
+ part_url = nullptr;
+ MimeHashValue * value = nullptr;
+ if (absolute)
+ {
+ value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, buf);
+ part_url = value ? value->m_url : nullptr;
+ PR_FREEIF(absolute);
+ }
+
+ /*If we found a mailbox part URL, write that out instead.*/
+ if (part_url && accept_related_part(relobj, value->m_obj))
+ {
+ status = real_write(relobj, part_url, strlen(part_url));
+ if (status < 0) return status;
+ buf = ptr2; /* skip over the cid: URL we substituted */
+
+ /* don't show that object as attachment */
+ if (value->m_obj)
+ value->m_obj->dontShowAsAttachment = true;
+ }
+
+ /* Restore the character that we nulled. */
+ *ptr2 = c;
+ }
+ /* rhp - if we get here, we should still check against the hash table! */
+ else
+ {
+ char holder = *ptr2;
+ char *realout;
+
+ *ptr2 = '\0';
+
+ /* Construct a URL out of the word. */
+ absolute = MakeAbsoluteURL(relobj->base_url, buf);
+
+ /* See if we have a mailbox part URL
+ corresponding to this cid. */
+ MimeHashValue * value;
+ if (absolute)
+ value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, absolute);
+ else
+ value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, buf);
+ realout = value ? value->m_url : nullptr;
+
+ *ptr2 = holder;
+ PR_FREEIF(absolute);
+
+ if (realout && accept_related_part(relobj, value->m_obj))
+ {
+ status = real_write(relobj, realout, strlen(realout));
+ if (status < 0) return status;
+ buf = ptr2; /* skip over the cid: URL we substituted */
+
+ /* don't show that object as attachment */
+ if (value->m_obj)
+ value->m_obj->dontShowAsAttachment = true;
+ }
+ }
+ /* rhp - if we get here, we should still check against the hash table! */
+
+ /* Advance to the beginning of the next word, or to
+ the end of the value string. */
+ while((ptr2<ptr) && (IS_SPACE(*ptr2)))
+ ptr2++;
+
+ /* Write whatever original text remains after
+ cid: URL substitution. */
+ status = real_write(relobj, buf, ptr2-buf);
+ if (status < 0) return status;
+ buf = ptr2;
+ }
+ }
+ if (buf && *buf) {
+ status = real_write(relobj, buf, strlen(buf));
+ if (status < 0) return status;
+ }
+ relobj->curtag_length = 0;
+ return 0;
+}
+
+
+
+static int
+mime_multipart_related_output_fn(const char* buf, int32_t size, void *stream_closure)
+{
+ MimeMultipartRelated *relobj = (MimeMultipartRelated *) stream_closure;
+ char* ptr;
+ int32_t delta;
+ int status;
+ while (size > 0) {
+ if (relobj->curtag_length > 0) {
+ ptr = PL_strnchr(buf, '>', size);
+ if (!ptr) {
+ return push_tag(relobj, buf, size);
+ }
+ delta = ptr - buf + 1;
+ status = push_tag(relobj, buf, delta);
+ if (status < 0) return status;
+ status = flush_tag(relobj);
+ if (status < 0) return status;
+ buf += delta;
+ size -= delta;
+ }
+ ptr = PL_strnchr(buf, '<', size);
+ if (ptr && ptr - buf >= size) ptr = 0;
+ if (!ptr) {
+ return real_write(relobj, buf, size);
+ }
+ delta = ptr - buf;
+ status = real_write(relobj, buf, delta);
+ if (status < 0) return status;
+ buf += delta;
+ size -= delta;
+ PR_ASSERT(relobj->curtag_length == 0);
+ status = push_tag(relobj, buf, 1);
+ if (status < 0) return status;
+ PR_ASSERT(relobj->curtag_length == 1);
+ buf++;
+ size--;
+ }
+ return 0;
+}
+
+
+static int
+MimeMultipartRelated_parse_eof (MimeObject *obj, bool abort_p)
+{
+ /* OK, all the necessary data has been collected. We now have to spew out
+ the HTML. We let it go through all the normal mechanisms (which
+ includes content-encoding handling), and intercept the output data to do
+ translation of the tags. Whee. */
+ MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj;
+ MimeContainer *cont = (MimeContainer *)obj;
+ int status = 0;
+ MimeObject *body;
+ char* ct;
+ const char* dct;
+
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0) goto FAIL;
+
+ if (!relobj->headobj) return 0;
+
+ ct = (relobj->buffered_hdrs
+ ? MimeHeaders_get (relobj->buffered_hdrs, HEADER_CONTENT_TYPE,
+ true, false)
+ : 0);
+ dct = (((MimeMultipartClass *) obj->clazz)->default_part_type);
+
+ relobj->real_output_fn = obj->options->output_fn;
+ relobj->real_output_closure = obj->options->output_closure;
+
+ obj->options->output_fn = mime_multipart_related_output_fn;
+ obj->options->output_closure = obj;
+
+ body = mime_create(((ct && *ct) ? ct : (dct ? dct : TEXT_HTML)),
+ relobj->buffered_hdrs, obj->options);
+
+ PR_FREEIF(ct);
+ if (!body) {
+ status = MIME_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+ // replace the existing head object with the new object
+ for (int iChild = 0; iChild < cont->nchildren; iChild++) {
+ if (cont->children[iChild] == relobj->headobj) {
+ // cleanup of the headobj is performed explicitly in our finalizer now
+ // that it does not get cleaned up as a child.
+ cont->children[iChild] = body;
+ body->parent = obj;
+ body->options = obj->options;
+ }
+ }
+
+ if (!body->parent) {
+ NS_WARNING("unexpected mime multipart related structure");
+ goto FAIL;
+ }
+
+ body->dontShowAsAttachment = body->clazz->displayable_inline_p(body->clazz, body->headers);
+
+#ifdef MIME_DRAFTS
+ if ( obj->options &&
+ obj->options->decompose_file_p &&
+ obj->options->decompose_file_init_fn &&
+ (relobj->file_buffer || relobj->head_buffer))
+ {
+ status = obj->options->decompose_file_init_fn ( obj->options->stream_closure,
+ relobj->buffered_hdrs );
+ if (status < 0) return status;
+ }
+#endif /* MIME_DRAFTS */
+
+ /* if the emitter wants to know about nested bodies, then it needs
+ to know that we jumped back to this body part. */
+ if (obj->options->notify_nested_bodies)
+ {
+ char *part_path = mime_part_address(body);
+ if (part_path)
+ {
+ mimeEmitterAddHeaderField(obj->options,
+ "x-jsemitter-part-path",
+ part_path);
+ PR_Free(part_path);
+ }
+ }
+
+ /* Now that we've added this new object to our list of children,
+ start its parser going. */
+ status = body->clazz->parse_begin(body);
+ if (status < 0) goto FAIL;
+
+ if (relobj->head_buffer)
+ {
+ /* Read it out of memory. */
+ PR_ASSERT(!relobj->file_buffer && !relobj->input_file_stream);
+
+ status = body->clazz->parse_buffer(relobj->head_buffer,
+ relobj->head_buffer_fp,
+ body);
+ }
+ else if (relobj->file_buffer)
+ {
+ /* Read it off disk. */
+ char *buf;
+
+ PR_ASSERT(relobj->head_buffer_size == 0 &&
+ relobj->head_buffer_fp == 0);
+ PR_ASSERT(relobj->file_buffer);
+ if (!relobj->file_buffer)
+ {
+ status = -1;
+ goto FAIL;
+ }
+
+ buf = (char *) PR_MALLOC(FILE_IO_BUFFER_SIZE);
+ if (!buf)
+ {
+ status = MIME_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+
+ // First, close the output file to open the input file!
+ if (relobj->output_file_stream)
+ relobj->output_file_stream->Close();
+
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(relobj->input_file_stream), relobj->file_buffer);
+ if (NS_FAILED(rv))
+ {
+ PR_Free(buf);
+ status = MIME_UNABLE_TO_OPEN_TMP_FILE;
+ goto FAIL;
+ }
+
+ while(1)
+ {
+ uint32_t bytesRead = 0;
+ rv = relobj->input_file_stream->Read(buf, FILE_IO_BUFFER_SIZE - 1, &bytesRead);
+ if (NS_FAILED(rv) || !bytesRead)
+ {
+ status = NS_FAILED(rv) ? -1 : 0;
+ break;
+ }
+ else
+ {
+ /* It would be really nice to be able to yield here, and let
+ some user events and other input sources get processed.
+ Oh well. */
+
+ status = body->clazz->parse_buffer(buf, bytesRead, body);
+ if (status < 0) break;
+ }
+ }
+ PR_Free(buf);
+ }
+
+ if (status < 0) goto FAIL;
+
+ /* Done parsing. */
+ status = body->clazz->parse_eof(body, false);
+ if (status < 0) goto FAIL;
+ status = body->clazz->parse_end(body, false);
+ if (status < 0) goto FAIL;
+
+FAIL:
+
+#ifdef MIME_DRAFTS
+ if ( obj->options &&
+ obj->options->decompose_file_p &&
+ obj->options->decompose_file_close_fn &&
+ (relobj->file_buffer || relobj->head_buffer)) {
+ status = obj->options->decompose_file_close_fn ( obj->options->stream_closure );
+ if (status < 0) return status;
+ }
+#endif /* MIME_DRAFTS */
+
+ obj->options->output_fn = relobj->real_output_fn;
+ obj->options->output_closure = relobj->real_output_closure;
+
+ return status;
+}
+
+
+
+
+static int
+MimeMultipartRelatedClassInitialize(MimeMultipartRelatedClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeMultipartClass *mclass = (MimeMultipartClass *) clazz;
+ PR_ASSERT(!oclass->class_initialized);
+ oclass->initialize = MimeMultipartRelated_initialize;
+ oclass->finalize = MimeMultipartRelated_finalize;
+ oclass->parse_eof = MimeMultipartRelated_parse_eof;
+ mclass->output_child_p = MimeMultipartRelated_output_child_p;
+ mclass->parse_child_line = MimeMultipartRelated_parse_child_line;
+ return 0;
+}
diff --git a/mailnews/mime/src/mimemrel.h b/mailnews/mime/src/mimemrel.h
new file mode 100644
index 000000000..0c56873cf
--- /dev/null
+++ b/mailnews/mime/src/mimemrel.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMREL_H_
+#define _MIMEMREL_H_
+
+#include "mimemult.h"
+#include "plhash.h"
+#include "prio.h"
+#include "nsNetUtil.h"
+#include "nsIMimeConverter.h" // for MimeConverterOutputCallback
+
+/* The MimeMultipartRelated class implements the multipart/related MIME
+ container, which allows `sibling' sub-parts to refer to each other.
+ */
+
+typedef struct MimeMultipartRelatedClass MimeMultipartRelatedClass;
+typedef struct MimeMultipartRelated MimeMultipartRelated;
+
+struct MimeMultipartRelatedClass {
+ MimeMultipartClass multipart;
+};
+
+extern "C" MimeMultipartRelatedClass mimeMultipartRelatedClass;
+
+struct MimeMultipartRelated {
+ MimeMultipart multipart; /* superclass variables */
+
+ char* base_url; /* Base URL (if any) for the whole
+ multipart/related. */
+
+ char* head_buffer; /* Buffer used to remember the text/html 'head'
+ part. */
+ uint32_t head_buffer_fp; /* Active length. */
+ uint32_t head_buffer_size; /* How big it is. */
+
+ nsCOMPtr <nsIFile> file_buffer; /* The nsIFile of a temp file used when we
+ run out of room in the head_buffer. */
+ nsCOMPtr <nsIInputStream> input_file_stream; /* A stream to it. */
+ nsCOMPtr <nsIOutputStream> output_file_stream; /* A stream to it. */
+
+ MimeHeaders* buffered_hdrs; /* The headers of the 'head' part. */
+
+ bool head_loaded; /* Whether we've already passed the 'head'
+ part. */
+ MimeObject* headobj; /* The actual text/html head object. */
+
+ PLHashTable *hash;
+
+ MimeConverterOutputCallback real_output_fn;
+ void* real_output_closure;
+
+ char* curtag;
+ int32_t curtag_max;
+ int32_t curtag_length;
+
+
+
+};
+
+#define MimeMultipartRelatedClassInitializer(ITYPE,CSUPER) \
+ { MimeMultipartClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEMREL_H_ */
diff --git a/mailnews/mime/src/mimemsg.cpp b/mailnews/mime/src/mimemsg.cpp
new file mode 100644
index 000000000..d024a7725
--- /dev/null
+++ b/mailnews/mime/src/mimemsg.cpp
@@ -0,0 +1,965 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsIMimeEmitter.h"
+#include "mimemsg.h"
+#include "mimemoz2.h"
+#include "prmem.h"
+#include "prio.h"
+#include "plstr.h"
+#include "msgCore.h"
+#include "prlog.h"
+#include "prprf.h"
+#include "nsMimeStringResources.h"
+#include "nsMimeTypes.h"
+#include "nsMsgMessageFlags.h"
+#include "nsStringGlue.h"
+#include "mimetext.h"
+#include "mimecryp.h"
+#include "mimetpfl.h"
+#include "nsINetUtil.h"
+#include "nsMsgUtils.h"
+#include "nsMsgI18N.h"
+
+#define MIME_SUPERCLASS mimeContainerClass
+MimeDefClass(MimeMessage, MimeMessageClass, mimeMessageClass,
+ &MIME_SUPERCLASS);
+
+static int MimeMessage_initialize (MimeObject *);
+static void MimeMessage_finalize (MimeObject *);
+static int MimeMessage_add_child (MimeObject *, MimeObject *);
+static int MimeMessage_parse_begin (MimeObject *);
+static int MimeMessage_parse_line (const char *, int32_t, MimeObject *);
+static int MimeMessage_parse_eof (MimeObject *, bool);
+static int MimeMessage_close_headers (MimeObject *obj);
+static int MimeMessage_write_headers_html (MimeObject *);
+static char *MimeMessage_partial_message_html(const char *data,
+ void *closure,
+ MimeHeaders *headers);
+
+#ifdef XP_UNIX
+extern void MimeHeaders_do_unix_display_hook_hack(MimeHeaders *);
+#endif /* XP_UNIX */
+
+#if defined(DEBUG) && defined(XP_UNIX)
+static int MimeMessage_debug_print (MimeObject *, PRFileDesc *, int32_t depth);
+#endif
+
+extern MimeObjectClass mimeMultipartClass;
+
+static int
+MimeMessageClassInitialize(MimeMessageClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeContainerClass *cclass = (MimeContainerClass *) clazz;
+
+ PR_ASSERT(!oclass->class_initialized);
+ oclass->initialize = MimeMessage_initialize;
+ oclass->finalize = MimeMessage_finalize;
+ oclass->parse_begin = MimeMessage_parse_begin;
+ oclass->parse_line = MimeMessage_parse_line;
+ oclass->parse_eof = MimeMessage_parse_eof;
+ cclass->add_child = MimeMessage_add_child;
+
+#if defined(DEBUG) && defined(XP_UNIX)
+ oclass->debug_print = MimeMessage_debug_print;
+#endif
+ return 0;
+}
+
+
+static int
+MimeMessage_initialize (MimeObject *object)
+{
+ MimeMessage *msg = (MimeMessage *)object;
+ msg->grabSubject = false;
+ msg->bodyLength = 0;
+ msg->sizeSoFar = 0;
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object);
+}
+
+static void
+MimeMessage_finalize (MimeObject *object)
+{
+ MimeMessage *msg = (MimeMessage *)object;
+ if (msg->hdrs)
+ MimeHeaders_free(msg->hdrs);
+ msg->hdrs = 0;
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object);
+}
+
+static int
+MimeMessage_parse_begin (MimeObject *obj)
+{
+ MimeMessage *msg = (MimeMessage *)obj;
+
+ int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+ if (status < 0) return status;
+
+ if (obj->parent)
+ {
+ msg->grabSubject = true;
+ }
+
+ /* Messages have separators before the headers, except for the outermost
+ message. */
+ return MimeObject_write_separator(obj);
+}
+
+
+static int
+MimeMessage_parse_line (const char *aLine, int32_t aLength, MimeObject *obj)
+{
+ const char * line = aLine;
+ int32_t length = aLength;
+
+ MimeMessage *msg = (MimeMessage *) obj;
+ int status = 0;
+
+ NS_ASSERTION(line && *line, "empty line in mime msg parse_line");
+ if (!line || !*line) return -1;
+
+ msg->sizeSoFar += length;
+
+ if (msg->grabSubject)
+ {
+ if ( (!PL_strncasecmp(line, "Subject: ", 9)) && (obj->parent) )
+ {
+ if ( (obj->headers) && (!obj->headers->munged_subject) )
+ {
+ obj->headers->munged_subject = (char *) PL_strndup(line + 9, length - 9);
+ char *tPtr = obj->headers->munged_subject;
+ while (*tPtr)
+ {
+ if ( (*tPtr == '\r') || (*tPtr == '\n') )
+ {
+ *tPtr = '\0';
+ break;
+ }
+ tPtr++;
+ }
+ }
+ }
+ }
+
+ /* If we already have a child object, then we're done parsing headers,
+ and all subsequent lines get passed to the inferior object without
+ further processing by us. (Our parent will stop feeding us lines
+ when this MimeMessage part is out of data.)
+ */
+ if (msg->container.nchildren)
+ {
+ MimeObject *kid = msg->container.children[0];
+ bool nl;
+ PR_ASSERT(kid);
+ if (!kid) return -1;
+
+ msg->bodyLength += length;
+
+ /* Don't allow MimeMessage objects to not end in a newline, since it
+ would be inappropriate for any following part to appear on the same
+ line as the last line of the message.
+
+ #### This assumes that the only time the `parse_line' method is
+ called with a line that doesn't end in a newline is when that line
+ is the last line.
+ */
+ nl = (length > 0 && (line[length-1] == '\r' || line[length-1] == '\n'));
+
+#ifdef MIME_DRAFTS
+ if (!mime_typep (kid, (MimeObjectClass*) &mimeMessageClass) &&
+ obj->options &&
+ obj->options->decompose_file_p &&
+ !obj->options->is_multipart_msg &&
+ obj->options->decompose_file_output_fn &&
+ !obj->options->decrypt_p)
+ {
+ // If we are processing a flowed plain text line, we need to parse the
+ // line in mimeInlineTextPlainFlowedClass.
+ if (mime_typep(kid, (MimeObjectClass *)&mimeInlineTextPlainFlowedClass))
+ {
+ // Remove any stuffed space.
+ if (length > 0 && ' ' == *line)
+ {
+ line++;
+ length--;
+ }
+ return kid->clazz->parse_line (line, length, kid);
+ }
+ else
+ {
+ status = obj->options->decompose_file_output_fn (line,
+ length,
+ obj->options->stream_closure);
+ if (status < 0) return status;
+ if (!nl) {
+ status = obj->options->decompose_file_output_fn (MSG_LINEBREAK,
+ MSG_LINEBREAK_LEN,
+ obj->options->stream_closure);
+ if (status < 0) return status;
+ }
+ return status;
+ }
+ }
+#endif /* MIME_DRAFTS */
+
+
+ if (nl)
+ return kid->clazz->parse_buffer (line, length, kid);
+ else
+ {
+ /* Hack a newline onto the end. */
+ char *s = (char *)PR_MALLOC(length + MSG_LINEBREAK_LEN + 1);
+ if (!s) return MIME_OUT_OF_MEMORY;
+ memcpy(s, line, length);
+ PL_strncpyz(s + length, MSG_LINEBREAK, MSG_LINEBREAK_LEN + 1);
+ status = kid->clazz->parse_buffer (s, length + MSG_LINEBREAK_LEN, kid);
+ PR_Free(s);
+ return status;
+ }
+ }
+
+ /* Otherwise we don't yet have a child object, which means we're not
+ done parsing our headers yet.
+ */
+ if (!msg->hdrs)
+ {
+ msg->hdrs = MimeHeaders_new();
+ if (!msg->hdrs) return MIME_OUT_OF_MEMORY;
+ }
+
+#ifdef MIME_DRAFTS
+ if ( obj->options &&
+ obj->options->decompose_file_p &&
+ ! obj->options->is_multipart_msg &&
+ obj->options->done_parsing_outer_headers &&
+ obj->options->decompose_file_output_fn )
+ {
+ status = obj->options->decompose_file_output_fn( line, length,
+ obj->options->stream_closure );
+ if (status < 0)
+ return status;
+ }
+#endif /* MIME_DRAFTS */
+
+ status = MimeHeaders_parse_line(line, length, msg->hdrs);
+ if (status < 0) return status;
+
+ /* If this line is blank, we're now done parsing headers, and should
+ examine our content-type to create our "body" part.
+ */
+ if (*line == '\r' || *line == '\n')
+ {
+ status = MimeMessage_close_headers(obj);
+ if (status < 0) return status;
+ }
+
+ return 0;
+}
+
+static int
+MimeMessage_close_headers (MimeObject *obj)
+{
+ MimeMessage *msg = (MimeMessage *) obj;
+ int status = 0;
+ char *ct = 0; /* Content-Type header */
+ MimeObject *body;
+
+ // Do a proper decoding of the munged subject.
+ if (obj->headers && msg->hdrs && msg->grabSubject && obj->headers->munged_subject) {
+ // nsMsgI18NConvertToUnicode wants nsAStrings...
+ nsDependentCString orig(obj->headers->munged_subject);
+ nsAutoString dest;
+ // First, get the Content-Type, then extract the charset="whatever" part of
+ // it.
+ nsCString charset;
+ nsCString contentType;
+ contentType.Adopt(MimeHeaders_get(msg->hdrs, HEADER_CONTENT_TYPE, false, false));
+ if (!contentType.IsEmpty())
+ charset.Adopt(MimeHeaders_get_parameter(contentType.get(), "charset", nullptr, nullptr));
+
+ // If we've got a charset, use nsMsgI18NConvertToUnicode to magically decode
+ // the munged subject.
+ if (!charset.IsEmpty()) {
+ nsresult rv = nsMsgI18NConvertToUnicode(charset.get(), orig, dest);
+ // If we managed to convert the string, replace munged_subject with the
+ // UTF8 version of it, otherwise, just forget about it (maybe there was an
+ // improperly encoded string in there).
+ PR_Free(obj->headers->munged_subject);
+ if (NS_SUCCEEDED(rv))
+ obj->headers->munged_subject = ToNewUTF8String(dest);
+ else
+ obj->headers->munged_subject = nullptr;
+ } else {
+ PR_Free(obj->headers->munged_subject);
+ obj->headers->munged_subject = nullptr;
+ }
+ }
+
+ if (msg->hdrs)
+ {
+ bool outer_p = !obj->headers; /* is this the outermost message? */
+
+
+#ifdef MIME_DRAFTS
+ if (outer_p &&
+ obj->options &&
+ (obj->options->decompose_file_p || obj->options->caller_need_root_headers) &&
+ obj->options->decompose_headers_info_fn)
+ {
+#ifdef ENABLE_SMIME
+ if (obj->options->decrypt_p && !mime_crypto_object_p(msg->hdrs, false, obj->options))
+ obj->options->decrypt_p = false;
+#endif /* ENABLE_SMIME */
+ if (!obj->options->caller_need_root_headers || (obj == obj->options->state->root))
+ status = obj->options->decompose_headers_info_fn (
+ obj->options->stream_closure,
+ msg->hdrs );
+ }
+#endif /* MIME_DRAFTS */
+
+
+ /* If this is the outermost message, we need to run the
+ `generate_header' callback. This happens here instead of
+ in `parse_begin', because it's only now that we've parsed
+ our headers. However, since this is the outermost message,
+ we have yet to write any HTML, so that's fine.
+ */
+ if (outer_p &&
+ obj->output_p &&
+ obj->options &&
+ obj->options->write_html_p &&
+ obj->options->generate_header_html_fn)
+ {
+ int lstatus = 0;
+ char *html = 0;
+
+ /* The generate_header_html_fn might return HTML, so it's important
+ that the output stream be set up with the proper type before we
+ make the MimeObject_write() call below. */
+ if (!obj->options->state->first_data_written_p)
+ {
+ lstatus = MimeObject_output_init (obj, TEXT_HTML);
+ if (lstatus < 0) return lstatus;
+ PR_ASSERT(obj->options->state->first_data_written_p);
+ }
+
+ html = obj->options->generate_header_html_fn(NULL,
+ obj->options->html_closure,
+ msg->hdrs);
+ if (html)
+ {
+ lstatus = MimeObject_write(obj, html, strlen(html), false);
+ PR_Free(html);
+ if (lstatus < 0) return lstatus;
+ }
+ }
+
+
+ /* Find the content-type of the body of this message.
+ */
+ {
+ bool ok = true;
+ char *mv = MimeHeaders_get (msg->hdrs, HEADER_MIME_VERSION,
+ true, false);
+
+#ifdef REQUIRE_MIME_VERSION_HEADER
+ /* If this is the outermost message, it must have a MIME-Version
+ header with the value 1.0 for us to believe what might be in
+ the Content-Type header. If the MIME-Version header is not
+ present, we must treat this message as untyped.
+ */
+ ok = (mv && !strcmp(mv, "1.0"));
+#else
+ /* #### actually, we didn't check this in Mozilla 2.0, and checking
+ it now could cause some compatibility nonsense, so for now, let's
+ just believe any Content-Type header we see.
+ */
+ ok = true;
+#endif
+
+ if (ok)
+ {
+ ct = MimeHeaders_get (msg->hdrs, HEADER_CONTENT_TYPE, true, false);
+
+ /* If there is no Content-Type header, but there is a MIME-Version
+ header, then assume that this *is* in fact a MIME message.
+ (I've seen messages with
+
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: quoted-printable
+
+ and no Content-Type, and we should treat those as being of type
+ MimeInlineTextPlain rather than MimeUntypedText.)
+ */
+ if (mv && !ct)
+ ct = strdup(TEXT_PLAIN);
+ }
+
+ PR_FREEIF(mv); /* done with this now. */
+ }
+
+ /* If this message has a body which is encrypted and we're going to
+ decrypt it (whithout converting it to HTML, since decrypt_p and
+ write_html_p are never true at the same time)
+ */
+ if (obj->output_p &&
+ obj->options &&
+ obj->options->decrypt_p
+#ifdef ENABLE_SMIME
+ && !mime_crypto_object_p(msg->hdrs, false, obj->options)
+#endif /* ENABLE_SMIME */
+ )
+ {
+ /* The body of this message is not an encrypted object, so we need
+ to turn off the decrypt_p flag (to prevent us from s#$%ing the
+ body of the internal object up into one.) In this case,
+ our output will end up being identical to our input.
+ */
+ obj->options->decrypt_p = false;
+ }
+
+ /* Emit the HTML for this message's headers. Do this before
+ creating the object representing the body.
+ */
+ if (obj->output_p &&
+ obj->options &&
+ obj->options->write_html_p)
+ {
+ /* If citation headers are on, and this is not the outermost message,
+ turn them off. */
+ if (obj->options->headers == MimeHeadersCitation && !outer_p)
+ obj->options->headers = MimeHeadersSome;
+
+ /* Emit a normal header block. */
+ status = MimeMessage_write_headers_html(obj);
+ if (status < 0)
+ {
+ PR_FREEIF(ct);
+ return status;
+ }
+ }
+ else if (obj->output_p)
+ {
+ /* Dump the headers, raw. */
+ status = MimeObject_write(obj, "", 0, false); /* initialize */
+ if (status < 0)
+ {
+ PR_FREEIF(ct);
+ return status;
+ }
+ status = MimeHeaders_write_raw_headers(msg->hdrs, obj->options,
+ obj->options->decrypt_p);
+ if (status < 0)
+ {
+ PR_FREEIF(ct);
+ return status;
+ }
+ }
+
+#ifdef XP_UNIX
+ if (outer_p && obj->output_p)
+ /* Kludge from mimehdrs.c */
+ MimeHeaders_do_unix_display_hook_hack(msg->hdrs);
+#endif /* XP_UNIX */
+ }
+
+ /* Never put out a separator after a message header block. */
+ if (obj->options && obj->options->state)
+ obj->options->state->separator_suppressed_p = true;
+
+#ifdef MIME_DRAFTS
+ if ( !obj->headers && /* outer most message header */
+ obj->options &&
+ obj->options->decompose_file_p &&
+ ct )
+ obj->options->is_multipart_msg = PL_strcasestr(ct, "multipart/") != NULL;
+#endif /* MIME_DRAFTS */
+
+
+ body = mime_create(ct, msg->hdrs, obj->options);
+
+ PR_FREEIF(ct);
+ if (!body) return MIME_OUT_OF_MEMORY;
+ status = ((MimeContainerClass *) obj->clazz)->add_child (obj, body);
+ if (status < 0)
+ {
+ mime_free(body);
+ return status;
+ }
+
+ // Only do this if this is a Text Object!
+ if ( mime_typep(body, (MimeObjectClass *) &mimeInlineTextClass) )
+ {
+ ((MimeInlineText *) body)->needUpdateMsgWinCharset = true;
+ }
+
+ /* Now that we've added this new object to our list of children,
+ start its parser going. */
+ status = body->clazz->parse_begin(body);
+ if (status < 0) return status;
+
+ // Now notify the emitter if this is the outer most message, unless
+ // it is a part that is not the head of the message. If it's a part,
+ // we need to figure out the content type/charset of the part
+ //
+ bool outer_p = !obj->headers; /* is this the outermost message? */
+
+ if ( (outer_p || obj->options->notify_nested_bodies) &&
+ (!obj->options->part_to_load || obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay))
+ {
+ // call SetMailCharacterSetToMsgWindow() to set a menu charset
+ if (mime_typep(body, (MimeObjectClass *) &mimeInlineTextClass))
+ {
+ MimeInlineText *text = (MimeInlineText *) body;
+ if (text && text->charset && *text->charset)
+ SetMailCharacterSetToMsgWindow(body, text->charset);
+ }
+
+ char *msgID = MimeHeaders_get (msg->hdrs, HEADER_MESSAGE_ID,
+ false, false);
+
+ const char *outCharset = NULL;
+ if (!obj->options->force_user_charset) /* Only convert if the user prefs is false */
+ outCharset = "UTF-8";
+
+ mimeEmitterStartBody(obj->options, (obj->options->headers == MimeHeadersNone), msgID, outCharset);
+ PR_FREEIF(msgID);
+
+ // setting up truncated message html fotter function
+ char *xmoz = MimeHeaders_get(msg->hdrs, HEADER_X_MOZILLA_STATUS, false,
+ false);
+ if (xmoz)
+ {
+ uint32_t flags = 0;
+ char dummy = 0;
+ if (sscanf(xmoz, " %x %c", &flags, &dummy) == 1 &&
+ flags & nsMsgMessageFlags::Partial)
+ {
+ obj->options->html_closure = obj;
+ obj->options->generate_footer_html_fn =
+ MimeMessage_partial_message_html;
+ }
+ PR_FREEIF(xmoz);
+ }
+ }
+
+ return 0;
+}
+
+
+
+static int
+MimeMessage_parse_eof (MimeObject *obj, bool abort_p)
+{
+ int status;
+ bool outer_p;
+ MimeMessage *msg = (MimeMessage *)obj;
+ if (obj->closed_p) return 0;
+
+ /* Run parent method first, to flush out any buffered data. */
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0) return status;
+
+ outer_p = !obj->headers; /* is this the outermost message? */
+
+ // Hack for messages with truncated headers (bug 244722)
+ // If there is no empty line in a message, the parser can't figure out where
+ // the headers end, causing parsing to hang. So we insert an extra newline
+ // to keep it happy. This is OK, since a message without any empty lines is
+ // broken anyway...
+ if(outer_p && msg->hdrs && ! msg->hdrs->done_p) {
+ MimeMessage_parse_line("\n", 1, obj);
+ }
+
+ // Once we get to the end of parsing the message, we will notify
+ // the emitter that we are done the the body.
+
+ // Mark the end of the mail body if we are actually emitting the
+ // body of the message (i.e. not Header ONLY)
+ if ((outer_p || obj->options->notify_nested_bodies) && obj->options &&
+ obj->options->write_html_p)
+ {
+ if (obj->options->generate_footer_html_fn)
+ {
+ mime_stream_data *msd =
+ (mime_stream_data *) obj->options->stream_closure;
+ if (msd)
+ {
+ char *html = obj->options->generate_footer_html_fn
+ (msd->orig_url_name, obj->options->html_closure, msg->hdrs);
+ if (html)
+ {
+ int lstatus = MimeObject_write(obj, html,
+ strlen(html),
+ false);
+ PR_Free(html);
+ if (lstatus < 0) return lstatus;
+ }
+ }
+ }
+ if ((!obj->options->part_to_load || obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) &&
+ obj->options->headers != MimeHeadersOnly)
+ mimeEmitterEndBody(obj->options);
+ }
+
+#ifdef MIME_DRAFTS
+ if ( obj->options &&
+ obj->options->decompose_file_p &&
+ obj->options->done_parsing_outer_headers &&
+ ! obj->options->is_multipart_msg &&
+ ! mime_typep(obj, (MimeObjectClass*) &mimeEncryptedClass) &&
+ obj->options->decompose_file_close_fn ) {
+ status = obj->options->decompose_file_close_fn (
+ obj->options->stream_closure );
+
+ if ( status < 0 ) return status;
+ }
+#endif /* MIME_DRAFTS */
+
+
+ /* Put out a separator after every message/rfc822 object. */
+ if (!abort_p && !outer_p)
+ {
+ status = MimeObject_write_separator(obj);
+ if (status < 0) return status;
+ }
+
+ return 0;
+}
+
+
+static int
+MimeMessage_add_child (MimeObject *parent, MimeObject *child)
+{
+ MimeContainer *cont = (MimeContainer *) parent;
+ PR_ASSERT(parent && child);
+ if (!parent || !child) return -1;
+
+ /* message/rfc822 containers can only have one child. */
+ PR_ASSERT(cont->nchildren == 0);
+ if (cont->nchildren != 0) return -1;
+
+#ifdef MIME_DRAFTS
+ if ( parent->options &&
+ parent->options->decompose_file_p &&
+ ! parent->options->is_multipart_msg &&
+ ! mime_typep(child, (MimeObjectClass*) &mimeEncryptedClass) &&
+ parent->options->decompose_file_init_fn ) {
+ int status = 0;
+ status = parent->options->decompose_file_init_fn (
+ parent->options->stream_closure,
+ ((MimeMessage*)parent)->hdrs );
+ if ( status < 0 ) return status;
+ }
+#endif /* MIME_DRAFTS */
+
+ return ((MimeContainerClass*)&MIME_SUPERCLASS)->add_child (parent, child);
+}
+
+// This is necessary to determine which charset to use for a reply/forward
+char *
+DetermineMailCharset(MimeMessage *msg)
+{
+ char *retCharset = nullptr;
+
+ if ( (msg) && (msg->hdrs) )
+ {
+ char *ct = MimeHeaders_get (msg->hdrs, HEADER_CONTENT_TYPE,
+ false, false);
+ if (ct)
+ {
+ retCharset = MimeHeaders_get_parameter (ct, "charset", NULL, NULL);
+ PR_Free(ct);
+ }
+
+ if (!retCharset)
+ {
+ // If we didn't find "Content-Type: ...; charset=XX" then look
+ // for "X-Sun-Charset: XX" instead. (Maybe this should be done
+ // in MimeSunAttachmentClass, but it's harder there than here.)
+ retCharset = MimeHeaders_get (msg->hdrs, HEADER_X_SUN_CHARSET,
+ false, false);
+ }
+ }
+
+ if (!retCharset)
+ return strdup("ISO-8859-1");
+ else
+ return retCharset;
+}
+
+static int
+MimeMessage_write_headers_html (MimeObject *obj)
+{
+ MimeMessage *msg = (MimeMessage *) obj;
+ int status;
+
+ if (!obj->options || !obj->options->output_fn)
+ return 0;
+
+ PR_ASSERT(obj->output_p && obj->options->write_html_p);
+
+ // To support the no header option! Make sure we are not
+ // suppressing headers on included email messages...
+ if ( (obj->options->headers == MimeHeadersNone) &&
+ (obj == obj->options->state->root) )
+ {
+ // Ok, we are going to kick the Emitter for a StartHeader
+ // operation ONLY WHEN THE CHARSET OF THE ORIGINAL MESSAGE IS
+ // NOT US-ASCII ("ISO-8859-1")
+ //
+ // This is only to notify the emitter of the charset of the
+ // original message
+ char *mailCharset = DetermineMailCharset(msg);
+
+ if ( (mailCharset) && (PL_strcasecmp(mailCharset, "US-ASCII")) &&
+ (PL_strcasecmp(mailCharset, "ISO-8859-1")) )
+ mimeEmitterUpdateCharacterSet(obj->options, mailCharset);
+ PR_FREEIF(mailCharset);
+ return 0;
+ }
+
+ if (!obj->options->state->first_data_written_p)
+ {
+ status = MimeObject_output_init (obj, TEXT_HTML);
+ if (status < 0)
+ {
+ mimeEmitterEndHeader(obj->options, obj);
+ return status;
+ }
+ PR_ASSERT(obj->options->state->first_data_written_p);
+ }
+
+ // Start the header parsing by the emitter
+ char *msgID = MimeHeaders_get (msg->hdrs, HEADER_MESSAGE_ID,
+ false, false);
+ bool outer_p = !obj->headers; /* is this the outermost message? */
+ if (!outer_p && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay &&
+ obj->options->part_to_load)
+ {
+ //Maybe we are displaying a embedded message as outer part!
+ char *id = mime_part_address(obj);
+ if (id)
+ {
+ outer_p = !strcmp(id, obj->options->part_to_load);
+ PR_Free(id);
+ }
+ }
+
+ // Ok, we should really find out the charset of this part. We always
+ // output UTF-8 for display, but the original charset is necessary for
+ // reply and forward operations.
+ //
+ char *mailCharset = DetermineMailCharset(msg);
+ mimeEmitterStartHeader(obj->options,
+ outer_p,
+ (obj->options->headers == MimeHeadersOnly),
+ msgID,
+ mailCharset);
+
+ // Change the default_charset by the charset of the original message
+ // ONLY WHEN THE CHARSET OF THE ORIGINAL MESSAGE IS NOT US-ASCII
+ // ("ISO-8859-1") and default_charset and mailCharset are different,
+ // or when there is no default_charset (this can happen with saved messages).
+ if ( (!obj->options->default_charset ||
+ ((mailCharset) && (PL_strcasecmp(mailCharset, "US-ASCII")) &&
+ (PL_strcasecmp(mailCharset, "ISO-8859-1")) &&
+ (PL_strcasecmp(obj->options->default_charset, mailCharset)))) &&
+ !obj->options->override_charset )
+ {
+ PR_FREEIF(obj->options->default_charset);
+ obj->options->default_charset = strdup(mailCharset);
+ }
+
+ PR_FREEIF(msgID);
+ PR_FREEIF(mailCharset);
+
+ status = MimeHeaders_write_all_headers (msg->hdrs, obj->options, false);
+ if (status < 0)
+ {
+ mimeEmitterEndHeader(obj->options, obj);
+ return status;
+ }
+
+ // If this is the outermost message, then now is the time to run the
+ // post_header_html_fn.
+ if (obj->options && obj->options->state &&
+ obj->options->generate_post_header_html_fn &&
+ !obj->options->state->post_header_html_run_p) {
+ char *html = 0;
+ PR_ASSERT(obj->options->state->first_data_written_p);
+ html = obj->options->generate_post_header_html_fn(
+ NULL, obj->options->html_closure, msg->hdrs);
+ obj->options->state->post_header_html_run_p = true;
+ if (html) {
+ status = MimeObject_write(obj, html, strlen(html), false);
+ PR_Free(html);
+ if (status < 0) {
+ mimeEmitterEndHeader(obj->options, obj);
+ return status;
+ }
+ }
+ }
+
+ mimeEmitterEndHeader(obj->options, obj);
+
+ // rhp:
+ // For now, we are going to parse the entire message, even if we are
+ // only interested in headers...why? Well, because this is the only
+ // way to build the attachment list. Now we will have the attachment
+ // list in the output being created by the XML emitter. If we ever
+ // want to go back to where we were before, just uncomment the conditional
+ // and it will stop at header parsing.
+ //
+ // if (obj->options->headers == MimeHeadersOnly)
+ // return -1;
+ // else
+
+ return 0;
+}
+
+static char *
+MimeMessage_partial_message_html(const char *data, void *closure,
+ MimeHeaders *headers)
+{
+ MimeMessage *msg = (MimeMessage *)closure;
+ nsAutoCString orig_url(data);
+ char *uidl = MimeHeaders_get(headers, HEADER_X_UIDL, false, false);
+ char *msgId = MimeHeaders_get(headers, HEADER_MESSAGE_ID, false,
+ false);
+ char *msgIdPtr = PL_strchr(msgId, '<');
+
+ int32_t pos = orig_url.Find("mailbox-message");
+ if (pos != -1)
+ orig_url.Cut(pos + 7, 8);
+
+ pos = orig_url.FindChar('#');
+ if (pos != -1)
+ orig_url.Replace(pos, 1, "?number=", 8);
+
+ if (msgIdPtr)
+ msgIdPtr++;
+ else
+ msgIdPtr = msgId;
+ char *gtPtr = PL_strchr(msgIdPtr, '>');
+ if (gtPtr)
+ *gtPtr = 0;
+
+ bool msgBaseTruncated = (msg->bodyLength > MSG_LINEBREAK_LEN);
+
+ nsCString partialMsgHtml;
+ nsCString item;
+
+ partialMsgHtml.AppendLiteral("<div style=\"margin: 1em auto; border: 1px solid black; width: 80%\">");
+ partialMsgHtml.AppendLiteral("<div style=\"margin: 5px; padding: 10px; border: 1px solid gray; font-weight: bold; text-align: center;\">");
+
+ partialMsgHtml.AppendLiteral("<span style=\"font-size: 120%;\">");
+ if (msgBaseTruncated)
+ item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_TRUNCATED"));
+ else
+ item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_NOT_DOWNLOADED"));
+ partialMsgHtml += item;
+ partialMsgHtml.AppendLiteral("</span><hr>");
+
+ if (msgBaseTruncated)
+ item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_TRUNCATED_EXPLANATION"));
+ else
+ item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_NOT_DOWNLOADED_EXPLANATION"));
+ partialMsgHtml += item;
+ partialMsgHtml.AppendLiteral("<br><br>");
+
+ partialMsgHtml.AppendLiteral("<a href=\"");
+ partialMsgHtml.Append(orig_url);
+
+ if (msgIdPtr) {
+ partialMsgHtml.AppendLiteral("&messageid=");
+
+ MsgEscapeString(nsDependentCString(msgIdPtr), nsINetUtil::ESCAPE_URL_PATH,
+ item);
+
+ partialMsgHtml.Append(item);
+ }
+
+ if (uidl) {
+ partialMsgHtml.AppendLiteral("&uidl=");
+
+ MsgEscapeString(nsDependentCString(uidl), nsINetUtil::ESCAPE_XALPHAS,
+ item);
+
+ partialMsgHtml.Append(item);
+ }
+
+ partialMsgHtml.AppendLiteral("\">");
+ item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_CLICK_FOR_REST"));
+ partialMsgHtml += item;
+ partialMsgHtml.AppendLiteral("</a>");
+
+ partialMsgHtml.AppendLiteral("</div></div>");
+
+ return ToNewCString(partialMsgHtml);
+}
+
+#if defined(DEBUG) && defined(XP_UNIX)
+static int
+MimeMessage_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth)
+{
+ MimeMessage *msg = (MimeMessage *) obj;
+ char *addr = mime_part_address(obj);
+ int i;
+ for (i=0; i < depth; i++)
+ PR_Write(stream, " ", 2);
+/*
+ fprintf(stream, "<%s %s%s 0x%08X>\n",
+ obj->clazz->class_name,
+ addr ? addr : "???",
+ (msg->container.nchildren == 0 ? " (no body)" : ""),
+ (uint32_t) msg);
+*/
+ PR_FREEIF(addr);
+
+#if 0
+ if (msg->hdrs)
+ {
+ char *s;
+
+ depth++;
+
+# define DUMP(HEADER) \
+ for (i=0; i < depth; i++) \
+ PR_Write(stream, " ", 2); \
+ s = MimeHeaders_get (msg->hdrs, HEADER, false, true);
+/**
+ \
+ PR_Write(stream, HEADER ": %s\n", s ? s : ""); \
+**/
+
+ PR_FREEIF(s)
+
+ DUMP(HEADER_SUBJECT);
+ DUMP(HEADER_DATE);
+ DUMP(HEADER_FROM);
+ DUMP(HEADER_TO);
+ /* DUMP(HEADER_CC); */
+ DUMP(HEADER_NEWSGROUPS);
+ DUMP(HEADER_MESSAGE_ID);
+# undef DUMP
+
+ PR_Write(stream, "\n", 1);
+ }
+#endif
+
+ PR_ASSERT(msg->container.nchildren <= 1);
+ if (msg->container.nchildren == 1)
+ {
+ MimeObject *kid = msg->container.children[0];
+ int status = kid->clazz->debug_print (kid, stream, depth+1);
+ if (status < 0) return status;
+ }
+ return 0;
+}
+#endif
diff --git a/mailnews/mime/src/mimemsg.h b/mailnews/mime/src/mimemsg.h
new file mode 100644
index 000000000..9a38a6e71
--- /dev/null
+++ b/mailnews/mime/src/mimemsg.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMSG_H_
+#define _MIMEMSG_H_
+
+#include "mimecont.h"
+
+/* The MimeMessage class implements the message/rfc822 and message/news
+ MIME containers, which is to say, mail and news messages.
+ */
+
+typedef struct MimeMessageClass MimeMessageClass;
+typedef struct MimeMessage MimeMessage;
+
+struct MimeMessageClass {
+ MimeContainerClass container;
+};
+
+extern MimeMessageClass mimeMessageClass;
+
+struct MimeMessage {
+ MimeContainer container; /* superclass variables */
+ MimeHeaders *hdrs; /* headers of this message */
+ bool newline_p; /* whether the last line ended in a newline */
+ bool grabSubject; /* Should we try to grab the subject of this message */
+ int32_t bodyLength; /* Used for determining if the body has been truncated */
+ int32_t sizeSoFar; /* The total size of the MIME message, once parsing is
+ finished. */
+};
+
+#define MimeMessageClassInitializer(ITYPE,CSUPER) \
+ { MimeContainerClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEMSG_H_ */
diff --git a/mailnews/mime/src/mimemsig.cpp b/mailnews/mime/src/mimemsig.cpp
new file mode 100644
index 000000000..5b4f8e556
--- /dev/null
+++ b/mailnews/mime/src/mimemsig.cpp
@@ -0,0 +1,783 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "modmimee.h"
+#include "mimemsig.h"
+#include "nspr.h"
+
+#include "prmem.h"
+#include "plstr.h"
+#include "prerror.h"
+#include "nsMimeTypes.h"
+#include "msgCore.h"
+#include "nsMimeStringResources.h"
+#include "mimemoz2.h"
+#include "mimeeobj.h"
+#include "nsIMimeConverter.h" // for MimeConverterOutputCallback
+#include "mozilla/Attributes.h"
+
+#define MIME_SUPERCLASS mimeMultipartClass
+MimeDefClass(MimeMultipartSigned, MimeMultipartSignedClass,
+ mimeMultipartSignedClass, &MIME_SUPERCLASS);
+
+static int MimeMultipartSigned_initialize (MimeObject *);
+static int MimeMultipartSigned_create_child (MimeObject *);
+static int MimeMultipartSigned_close_child(MimeObject *);
+static int MimeMultipartSigned_parse_line (const char *, int32_t, MimeObject *);
+static int MimeMultipartSigned_parse_child_line (MimeObject *, const char *, int32_t,
+ bool);
+static int MimeMultipartSigned_parse_eof (MimeObject *, bool);
+static void MimeMultipartSigned_finalize (MimeObject *);
+
+static int MimeMultipartSigned_emit_child (MimeObject *obj);
+
+extern "C" MimeSuppressedCryptoClass mimeSuppressedCryptoClass;
+
+static int
+MimeMultipartSignedClassInitialize(MimeMultipartSignedClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeMultipartClass *mclass = (MimeMultipartClass *) clazz;
+
+ oclass->initialize = MimeMultipartSigned_initialize;
+ oclass->parse_line = MimeMultipartSigned_parse_line;
+ oclass->parse_eof = MimeMultipartSigned_parse_eof;
+ oclass->finalize = MimeMultipartSigned_finalize;
+ mclass->create_child = MimeMultipartSigned_create_child;
+ mclass->parse_child_line = MimeMultipartSigned_parse_child_line;
+ mclass->close_child = MimeMultipartSigned_close_child;
+
+ PR_ASSERT(!oclass->class_initialized);
+ return 0;
+}
+
+static int
+MimeMultipartSigned_initialize (MimeObject *object)
+{
+ MimeMultipartSigned *sig = (MimeMultipartSigned *) object;
+
+ /* This is an abstract class; it shouldn't be directly instantiated. */
+ PR_ASSERT(object->clazz != (MimeObjectClass *) &mimeMultipartSignedClass);
+
+ sig->state = MimeMultipartSignedPreamble;
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object);
+}
+
+static void
+MimeMultipartSigned_cleanup (MimeObject *obj, bool finalizing_p)
+{
+ MimeMultipart *mult = (MimeMultipart *) obj; /* #58075. Fix suggested by jwz */
+ MimeMultipartSigned *sig = (MimeMultipartSigned *) obj;
+ if (sig->part_buffer)
+ {
+ MimePartBufferDestroy(sig->part_buffer);
+ sig->part_buffer = 0;
+ }
+ if (sig->body_hdrs)
+ {
+ MimeHeaders_free (sig->body_hdrs);
+ sig->body_hdrs = 0;
+ }
+ if (sig->sig_hdrs)
+ {
+ MimeHeaders_free (sig->sig_hdrs);
+ sig->sig_hdrs = 0;
+ }
+
+ mult->state = MimeMultipartEpilogue; /* #58075. Fix suggested by jwz */
+ sig->state = MimeMultipartSignedEpilogue;
+
+ if (finalizing_p && sig->crypto_closure) {
+ /* Don't free these until this object is really going away -- keep them
+ around for the lifetime of the MIME object, so that we can get at the
+ security info of sub-parts of the currently-displayed message. */
+ ((MimeMultipartSignedClass *) obj->clazz)->crypto_free (sig->crypto_closure);
+ sig->crypto_closure = 0;
+ }
+
+ if (sig->sig_decoder_data)
+ {
+ MimeDecoderDestroy(sig->sig_decoder_data, true);
+ sig->sig_decoder_data = 0;
+ }
+}
+
+static int
+MimeMultipartSigned_parse_eof (MimeObject *obj, bool abort_p)
+{
+ MimeMultipartSigned *sig = (MimeMultipartSigned *) obj;
+ int status = 0;
+
+ if (obj->closed_p) return 0;
+
+ /* Close off the signature, if we've gotten that far.
+ */
+ if (sig->state == MimeMultipartSignedSignatureHeaders ||
+ sig->state == MimeMultipartSignedSignatureFirstLine ||
+ sig->state == MimeMultipartSignedSignatureLine ||
+ sig->state == MimeMultipartSignedEpilogue)
+ {
+ status = (((MimeMultipartSignedClass *) obj->clazz)->crypto_signature_eof) (sig->crypto_closure, abort_p);
+ if (status < 0) return status;
+ }
+
+ if (!abort_p)
+ {
+ /* Now that we've read both the signed object and the signature (and
+ have presumably verified the signature) write out a blurb, and then
+ the signed object.
+ */
+ status = MimeMultipartSigned_emit_child(obj);
+ if (status < 0) return status;
+ }
+
+ MimeMultipartSigned_cleanup(obj, false);
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+}
+
+
+static void
+MimeMultipartSigned_finalize (MimeObject *obj)
+{
+ MimeMultipartSigned_cleanup(obj, true);
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj);
+}
+
+
+static int
+MimeMultipartSigned_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ MimeMultipart *mult = (MimeMultipart *) obj;
+ MimeMultipartSigned *sig = (MimeMultipartSigned *) obj;
+ MimeMultipartParseState old_state = mult->state;
+ bool hash_line_p = true;
+ bool no_headers_p = false;
+ int status = 0;
+
+ /* First do the parsing for normal multipart/ objects by handing it off to
+ the superclass method. This includes calling the create_child and
+ close_child methods.
+ */
+ status = (((MimeObjectClass *)(&MIME_SUPERCLASS))
+ ->parse_line (line, length, obj));
+ if (status < 0) return status;
+
+ /* The instance variable MimeMultipartClass->state tracks motion through
+ the various stages of multipart/ parsing. The instance variable
+ MimeMultipartSigned->state tracks the difference between the first
+ part (the body) and the second part (the signature.) This second,
+ more specific state variable is updated by noticing the transitions
+ of the first, more general state variable.
+ */
+ if (old_state != mult->state) /* there has been a state change */
+ {
+ switch (mult->state)
+ {
+ case MimeMultipartPreamble:
+ PR_ASSERT(0); /* can't move *in* to preamble state. */
+ sig->state = MimeMultipartSignedPreamble;
+ break;
+
+ case MimeMultipartHeaders:
+ /* If we're moving in to the Headers state, then that means
+ that this line is the preceeding boundary string (and we
+ should ignore it.)
+ */
+ hash_line_p = false;
+
+ if (sig->state == MimeMultipartSignedPreamble)
+ sig->state = MimeMultipartSignedBodyFirstHeader;
+ else if (sig->state == MimeMultipartSignedBodyFirstLine ||
+ sig->state == MimeMultipartSignedBodyLine)
+ sig->state = MimeMultipartSignedSignatureHeaders;
+ else if (sig->state == MimeMultipartSignedSignatureFirstLine ||
+ sig->state == MimeMultipartSignedSignatureLine)
+ sig->state = MimeMultipartSignedEpilogue;
+ break;
+
+ case MimeMultipartPartFirstLine:
+ if (sig->state == MimeMultipartSignedBodyFirstHeader)
+ {
+ sig->state = MimeMultipartSignedBodyFirstLine;
+ no_headers_p = true;
+ }
+ else if (sig->state == MimeMultipartSignedBodyHeaders)
+ sig->state = MimeMultipartSignedBodyFirstLine;
+ else if (sig->state == MimeMultipartSignedSignatureHeaders)
+ sig->state = MimeMultipartSignedSignatureFirstLine;
+ else
+ sig->state = MimeMultipartSignedEpilogue;
+ break;
+
+ case MimeMultipartPartLine:
+
+ PR_ASSERT(sig->state == MimeMultipartSignedBodyFirstLine ||
+ sig->state == MimeMultipartSignedBodyLine ||
+ sig->state == MimeMultipartSignedSignatureFirstLine ||
+ sig->state == MimeMultipartSignedSignatureLine);
+
+ if (sig->state == MimeMultipartSignedBodyFirstLine)
+ sig->state = MimeMultipartSignedBodyLine;
+ else if (sig->state == MimeMultipartSignedSignatureFirstLine)
+ sig->state = MimeMultipartSignedSignatureLine;
+ break;
+
+ case MimeMultipartEpilogue:
+ sig->state = MimeMultipartSignedEpilogue;
+ break;
+
+ default: /* bad state */
+ NS_ERROR("bad state in MultipartSigned parse line");
+ return -1;
+ break;
+ }
+ }
+
+
+ /* Perform multipart/signed-related actions on this line based on the state
+ of the parser.
+ */
+ switch (sig->state)
+ {
+ case MimeMultipartSignedPreamble:
+ /* Do nothing. */
+ break;
+
+ case MimeMultipartSignedBodyFirstLine:
+ /* We have just moved out of the MimeMultipartSignedBodyHeaders
+ state, so cache away the headers that apply only to the body part.
+ */
+ NS_ASSERTION(mult->hdrs, "null multipart hdrs");
+ NS_ASSERTION(!sig->body_hdrs, "signed part shouldn't have already have body_hdrs");
+ sig->body_hdrs = mult->hdrs;
+ mult->hdrs = 0;
+
+ /* fall through. */
+ MOZ_FALLTHROUGH;
+ case MimeMultipartSignedBodyFirstHeader:
+ case MimeMultipartSignedBodyHeaders:
+ case MimeMultipartSignedBodyLine:
+
+ if (!sig->crypto_closure)
+ {
+ /* Set error change */
+ PR_SetError(0, 0);
+ /* Initialize the signature verification library. */
+ sig->crypto_closure = (((MimeMultipartSignedClass *) obj->clazz)
+ ->crypto_init) (obj);
+ if (!sig->crypto_closure)
+ {
+ status = PR_GetError();
+ NS_ASSERTION(status < 0, "got non-negative status");
+ if (status >= 0)
+ status = -1;
+ return status;
+ }
+ }
+
+ if (hash_line_p)
+ {
+ /* this is the first hashed line if this is the first header
+ (that is, if it's the first line in the header state after
+ a state change.)
+ */
+ bool first_line_p
+ = (no_headers_p ||
+ sig->state == MimeMultipartSignedBodyFirstHeader);
+
+ if (sig->state == MimeMultipartSignedBodyFirstHeader)
+ sig->state = MimeMultipartSignedBodyHeaders;
+
+ /* The newline issues here are tricky, since both the newlines
+ before and after the boundary string are to be considered part
+ of the boundary: this is so that a part can be specified such
+ that it does not end in a trailing newline.
+
+ To implement this, we send a newline *before* each line instead
+ of after, except for the first line, which is not preceeded by a
+ newline.
+
+ For purposes of cryptographic hashing, we always hash line
+ breaks as CRLF -- the canonical, on-the-wire linebreaks, since
+ we have no idea of knowing what line breaks were used on the
+ originating system (SMTP rightly destroys that information.)
+ */
+
+ /* Remove the trailing newline... */
+ if (length > 0 && line[length-1] == '\n') length--;
+ if (length > 0 && line[length-1] == '\r') length--;
+
+ PR_ASSERT(sig->crypto_closure);
+
+ if (!first_line_p)
+ {
+ /* Push out a preceeding newline... */
+ char nl[] = CRLF;
+ status = (((MimeMultipartSignedClass *) obj->clazz)
+ ->crypto_data_hash (nl, 2, sig->crypto_closure));
+ if (status < 0) return status;
+ }
+
+ /* Now push out the line sans trailing newline. */
+ if (length > 0)
+ status = (((MimeMultipartSignedClass *) obj->clazz)
+ ->crypto_data_hash (line,length, sig->crypto_closure));
+ if (status < 0) return status;
+ }
+ break;
+
+ case MimeMultipartSignedSignatureHeaders:
+
+ if (sig->crypto_closure &&
+ old_state != mult->state)
+ {
+ /* We have just moved out of the MimeMultipartSignedBodyLine
+ state, so tell the signature verification library that we've
+ reached the end of the signed data.
+ */
+ status = (((MimeMultipartSignedClass *) obj->clazz)
+ ->crypto_data_eof) (sig->crypto_closure, false);
+ if (status < 0) return status;
+ }
+ break;
+
+ case MimeMultipartSignedSignatureFirstLine:
+ /* We have just moved out of the MimeMultipartSignedSignatureHeaders
+ state, so cache away the headers that apply only to the sig part.
+ */
+ PR_ASSERT(mult->hdrs);
+ PR_ASSERT(!sig->sig_hdrs);
+ sig->sig_hdrs = mult->hdrs;
+ mult->hdrs = 0;
+
+
+ /* If the signature block has an encoding, set up a decoder for it.
+ (Similar logic is in MimeLeafClass->parse_begin.)
+ */
+ {
+ MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0;
+ nsCString encoding;
+ encoding.Adopt(MimeHeaders_get (sig->sig_hdrs,
+ HEADER_CONTENT_TRANSFER_ENCODING,
+ true, false));
+ if (encoding.IsEmpty())
+ ;
+ else if (!PL_strcasecmp(encoding.get(), ENCODING_BASE64))
+ fn = &MimeB64DecoderInit;
+ else if (!PL_strcasecmp(encoding.get(), ENCODING_QUOTED_PRINTABLE))
+ {
+ sig->sig_decoder_data =
+ MimeQPDecoderInit (((MimeConverterOutputCallback)
+ (((MimeMultipartSignedClass *) obj->clazz)
+ ->crypto_signature_hash)),
+ sig->crypto_closure);
+ if (!sig->sig_decoder_data)
+ return MIME_OUT_OF_MEMORY;
+ }
+ else if (!PL_strcasecmp(encoding.get(), ENCODING_UUENCODE) ||
+ !PL_strcasecmp(encoding.get(), ENCODING_UUENCODE2) ||
+ !PL_strcasecmp(encoding.get(), ENCODING_UUENCODE3) ||
+ !PL_strcasecmp(encoding.get(), ENCODING_UUENCODE4))
+ fn = &MimeUUDecoderInit;
+ else if (!PL_strcasecmp(encoding.get(), ENCODING_YENCODE))
+ fn = &MimeYDecoderInit;
+ if (fn)
+ {
+ sig->sig_decoder_data =
+ fn (((MimeConverterOutputCallback)
+ (((MimeMultipartSignedClass *) obj->clazz)
+ ->crypto_signature_hash)),
+ sig->crypto_closure);
+ if (!sig->sig_decoder_data)
+ return MIME_OUT_OF_MEMORY;
+ }
+ }
+
+ /* Show these headers to the crypto module. */
+ if (hash_line_p)
+ {
+ status = (((MimeMultipartSignedClass *) obj->clazz)
+ ->crypto_signature_init) (sig->crypto_closure,
+ obj, sig->sig_hdrs);
+ if (status < 0) return status;
+ }
+
+ /* fall through. */
+ MOZ_FALLTHROUGH;
+ case MimeMultipartSignedSignatureLine:
+ if (hash_line_p)
+ {
+ /* Feed this line into the signature verification routines. */
+
+ if (sig->sig_decoder_data)
+ status = MimeDecoderWrite (sig->sig_decoder_data, line, length, nullptr);
+ else
+ status = (((MimeMultipartSignedClass *) obj->clazz)
+ ->crypto_signature_hash (line, length,
+ sig->crypto_closure));
+ if (status < 0) return status;
+ }
+ break;
+
+ case MimeMultipartSignedEpilogue:
+ /* Nothing special to do here. */
+ break;
+
+ default: /* bad state */
+ PR_ASSERT(0);
+ return -1;
+ }
+
+ return status;
+}
+
+
+static int
+MimeMultipartSigned_create_child (MimeObject *parent)
+{
+ /* Don't actually create a child -- we call the superclass create_child
+ method later, after we've fully parsed everything. (And we only call
+ it once, for part #1, and never for part #2 (the signature.))
+ */
+ MimeMultipart *mult = (MimeMultipart *) parent;
+ mult->state = MimeMultipartPartFirstLine;
+ return 0;
+}
+
+
+static int
+MimeMultipartSigned_close_child (MimeObject *obj)
+{
+ /* The close_child method on MimeMultipartSigned doesn't actually do
+ anything to the children list, since the create_child method also
+ doesn't do anything.
+ */
+ MimeMultipart *mult = (MimeMultipart *) obj;
+ MimeContainer *cont = (MimeContainer *) obj;
+ MimeMultipartSigned *msig = (MimeMultipartSigned *) obj;
+
+ if (msig->part_buffer)
+ /* Closes the tmp file, if there is one: doesn't free the part_buffer. */
+ MimePartBufferClose(msig->part_buffer);
+
+ if (mult->hdrs) /* duplicated from MimeMultipart_close_child, ugh. */
+ {
+ MimeHeaders_free(mult->hdrs);
+ mult->hdrs = 0;
+ }
+
+ /* Should be no kids yet. */
+ PR_ASSERT(cont->nchildren == 0);
+ if (cont->nchildren != 0) return -1;
+
+ return 0;
+}
+
+
+static int
+MimeMultipartSigned_parse_child_line (MimeObject *obj,
+ const char *line, int32_t length,
+ bool first_line_p)
+{
+ MimeMultipartSigned *sig = (MimeMultipartSigned *) obj;
+ MimeContainer *cont = (MimeContainer *) obj;
+ int status = 0;
+
+ /* Shouldn't have made any sub-parts yet. */
+ PR_ASSERT(cont->nchildren == 0);
+ if (cont->nchildren != 0) return -1;
+
+ switch (sig->state)
+ {
+ case MimeMultipartSignedPreamble:
+ case MimeMultipartSignedBodyFirstHeader:
+ case MimeMultipartSignedBodyHeaders:
+ // How'd we get here? Oh well, fall through.
+ NS_ERROR("wrong state in parse child line");
+ MOZ_FALLTHROUGH;
+ case MimeMultipartSignedBodyFirstLine:
+ PR_ASSERT(first_line_p);
+ if (!sig->part_buffer)
+ {
+ sig->part_buffer = MimePartBufferCreate();
+ if (!sig->part_buffer)
+ return MIME_OUT_OF_MEMORY;
+ }
+ /* fall through */
+ MOZ_FALLTHROUGH;
+ case MimeMultipartSignedBodyLine:
+ {
+ /* This is the first part; we are buffering it, and will emit it all
+ at the end (so that we know whether the signature matches before
+ showing anything to the user.)
+ */
+
+ /* The newline issues here are tricky, since both the newlines
+ before and after the boundary string are to be considered part
+ of the boundary: this is so that a part can be specified such
+ that it does not end in a trailing newline.
+
+ To implement this, we send a newline *before* each line instead
+ of after, except for the first line, which is not preceeded by a
+ newline.
+ */
+
+ /* Remove the trailing newline... */
+ if (length > 0 && line[length-1] == '\n') length--;
+ if (length > 0 && line[length-1] == '\r') length--;
+
+ PR_ASSERT(sig->part_buffer);
+ PR_ASSERT(first_line_p ==
+ (sig->state == MimeMultipartSignedBodyFirstLine));
+
+ if (!first_line_p)
+ {
+ /* Push out a preceeding newline... */
+ char nl[] = MSG_LINEBREAK;
+ status = MimePartBufferWrite (sig->part_buffer, nl, MSG_LINEBREAK_LEN);
+ if (status < 0) return status;
+ }
+
+ /* Now push out the line sans trailing newline. */
+ if (length > 0)
+ status = MimePartBufferWrite (sig->part_buffer, line, length);
+ if (status < 0) return status;
+ }
+ break;
+
+ case MimeMultipartSignedSignatureHeaders:
+ // How'd we get here? Oh well, fall through.
+ NS_ERROR("should have already parse sig hdrs");
+ MOZ_FALLTHROUGH;
+ case MimeMultipartSignedSignatureFirstLine:
+ case MimeMultipartSignedSignatureLine:
+ /* Nothing to do here -- hashing of the signature part is handled up
+ in MimeMultipartSigned_parse_line().
+ */
+ break;
+
+ case MimeMultipartSignedEpilogue:
+ /* Too many kids? MimeMultipartSigned_create_child() should have
+ prevented us from getting here. */
+ NS_ERROR("too many kids?");
+ return -1;
+ break;
+
+ default: /* bad state */
+ NS_ERROR("bad state in multipart signed parse line");
+ return -1;
+ break;
+ }
+
+ return status;
+}
+
+
+static int
+MimeMultipartSigned_emit_child (MimeObject *obj)
+{
+ MimeMultipartSigned *sig = (MimeMultipartSigned *) obj;
+ MimeMultipart *mult = (MimeMultipart *) obj;
+ MimeContainer *cont = (MimeContainer *) obj;
+ int status = 0;
+ MimeObject *body;
+
+ if (!sig->crypto_closure) {
+ // We might have decided to skip processing this part.
+ return 0;
+ }
+
+ NS_ASSERTION(sig->crypto_closure, "no crypto closure");
+
+ /* Emit some HTML saying whether the signature was cool.
+ But don't emit anything if in FO_QUOTE_MESSAGE mode.
+ */
+ if (obj->options &&
+ obj->options->headers != MimeHeadersCitation &&
+ obj->options->write_html_p &&
+ obj->options->output_fn &&
+ sig->crypto_closure)
+ {
+ // Calling crypto_generate_html may trigger wanted side effects,
+ // but we're no longer using its results.
+ char *html = (((MimeMultipartSignedClass *) obj->clazz)
+ ->crypto_generate_html (sig->crypto_closure));
+ PR_FREEIF(html);
+
+ /* Now that we have written out the crypto stamp, the outermost header
+ block is well and truly closed. If this is in fact the outermost
+ message, then run the post_header_html_fn now.
+ */
+ if (obj->options &&
+ obj->options->state &&
+ obj->options->generate_post_header_html_fn &&
+ !obj->options->state->post_header_html_run_p)
+ {
+ MimeHeaders *outer_headers=nullptr;
+ MimeObject *p;
+ for (p = obj; p->parent; p = p->parent)
+ outer_headers = p->headers;
+ NS_ASSERTION(obj->options->state->first_data_written_p,
+ "should have already written some data");
+ html = obj->options->generate_post_header_html_fn(NULL,
+ obj->options->html_closure,
+ outer_headers);
+ obj->options->state->post_header_html_run_p = true;
+ if (html)
+ {
+ status = MimeObject_write(obj, html, strlen(html), false);
+ PR_Free(html);
+ if (status < 0) return status;
+ }
+ }
+ }
+
+
+ /* Oh, this is fairly nasty. We're skipping over our "create child" method
+ and using the one our superclass defines. Perhaps instead we should add
+ a new method on this class, and initialize that method to be the
+ create_child method of the superclass. Whatever.
+ */
+
+
+ /* The superclass method expects to find the headers for the part that it's
+ to create in mult->hdrs, so ensure that they're there. */
+ NS_ASSERTION(!mult->hdrs, "shouldn't already have hdrs for multipart");
+ if (mult->hdrs) MimeHeaders_free(mult->hdrs);
+ mult->hdrs = sig->body_hdrs;
+ sig->body_hdrs = 0;
+
+ /* Run the superclass create_child method.
+ */
+ status = (((MimeMultipartClass *)(&MIME_SUPERCLASS))->create_child(obj));
+ if (status < 0) return status;
+
+ // Notify the charset of the first part.
+ if (obj->options && !(obj->options->override_charset)) {
+ MimeObject *firstChild = ((MimeContainer*) obj)->children[0];
+ char *disposition = MimeHeaders_get (firstChild->headers,
+ HEADER_CONTENT_DISPOSITION,
+ true,
+ false);
+ // check if need to show as inline
+ if (!disposition)
+ {
+ const char *content_type = firstChild->content_type;
+ if (!PL_strcasecmp (content_type, TEXT_PLAIN) ||
+ !PL_strcasecmp (content_type, TEXT_HTML) ||
+ !PL_strcasecmp (content_type, TEXT_MDL) ||
+ !PL_strcasecmp (content_type, MULTIPART_ALTERNATIVE) ||
+ !PL_strcasecmp (content_type, MULTIPART_RELATED) ||
+ !PL_strcasecmp (content_type, MESSAGE_NEWS) ||
+ !PL_strcasecmp (content_type, MESSAGE_RFC822)) {
+ char *ct = MimeHeaders_get(mult->hdrs, HEADER_CONTENT_TYPE, false, false);
+ if (ct) {
+ char *cset = MimeHeaders_get_parameter (ct, "charset", NULL, NULL);
+ if (cset) {
+ mimeEmitterUpdateCharacterSet(obj->options, cset);
+ SetMailCharacterSetToMsgWindow(obj, cset);
+ PR_Free(cset);
+ }
+ PR_Free(ct);
+ }
+ }
+ }
+ }
+
+ // The js emitter wants to know about the newly created child. Because
+ // MimeMultipartSigned dummies out its create_child operation, the logic
+ // in MimeMultipart_parse_line that would normally provide this notification
+ // does not get to fire.
+ if (obj->options && obj->options->notify_nested_bodies) {
+ MimeObject *kid = ((MimeContainer*) obj)->children[0];
+ // The emitter is expecting the content type with parameters; not the fully
+ // parsed thing, so get it from raw. (We do not do it in the charset
+ // notification block that just happened because it already has complex
+ // if-checks that do not jive with us.
+ char *ct = MimeHeaders_get(mult->hdrs, HEADER_CONTENT_TYPE, false,
+ false);
+ mimeEmitterAddHeaderField(obj->options, HEADER_CONTENT_TYPE,
+ ct ? ct : "text/plain");
+ PR_Free(ct);
+
+ char *part_path = mime_part_address(kid);
+ if (part_path) {
+ mimeEmitterAddHeaderField(obj->options,
+ "x-jsemitter-part-path",
+ part_path);
+ PR_Free(part_path);
+ }
+ }
+
+ /* Retrieve the child that it created.
+ */
+ NS_ASSERTION(cont->nchildren == 1, "should only have one child");
+ if (cont->nchildren != 1)
+ return -1;
+ body = cont->children[0];
+ NS_ASSERTION(body, "missing body");
+ if (!body)
+ return -1;
+
+ if (mime_typep(body, (MimeObjectClass *)&mimeSuppressedCryptoClass)) {
+ ((MimeMultipartSignedClass *)obj->clazz)
+ ->crypto_notify_suppressed_child(sig->crypto_closure);
+ }
+
+#ifdef MIME_DRAFTS
+ if (body->options->decompose_file_p) {
+ body->options->signed_p = true;
+ if (!mime_typep(body, (MimeObjectClass*)&mimeMultipartClass) &&
+ body->options->decompose_file_init_fn)
+ body->options->decompose_file_init_fn ( body->options->stream_closure, body->headers );
+ }
+#endif /* MIME_DRAFTS */
+
+ /* If there's no part_buffer, this is a zero-length signed message? */
+ if (sig->part_buffer)
+ {
+#ifdef MIME_DRAFTS
+ if (body->options->decompose_file_p &&
+ !mime_typep(body, (MimeObjectClass*)&mimeMultipartClass) &&
+ body->options->decompose_file_output_fn)
+ status = MimePartBufferRead (sig->part_buffer,
+ /* The (MimeConverterOutputCallback) cast is to turn the
+ `void' argument into `MimeObject'. */
+ ((MimeConverterOutputCallback)
+ body->options->decompose_file_output_fn),
+ body->options->stream_closure);
+ else
+#endif /* MIME_DRAFTS */
+
+ status = MimePartBufferRead (sig->part_buffer,
+ /* The (MimeConverterOutputCallback) cast is to turn the
+ `void' argument into `MimeObject'. */
+ ((MimeConverterOutputCallback) body->clazz->parse_buffer),
+ body);
+ if (status < 0) return status;
+ }
+
+ MimeMultipartSigned_cleanup(obj, false);
+
+ /* Done parsing. */
+ status = body->clazz->parse_eof(body, false);
+ if (status < 0) return status;
+ status = body->clazz->parse_end(body, false);
+ if (status < 0) return status;
+
+#ifdef MIME_DRAFTS
+ if (body->options->decompose_file_p &&
+ !mime_typep(body, (MimeObjectClass*)&mimeMultipartClass) &&
+ body->options->decompose_file_close_fn)
+ body->options->decompose_file_close_fn(body->options->stream_closure);
+#endif /* MIME_DRAFTS */
+
+ /* Put out a separator after every multipart/signed object. */
+ status = MimeObject_write_separator(obj);
+ if (status < 0) return status;
+
+ return 0;
+}
diff --git a/mailnews/mime/src/mimemsig.h b/mailnews/mime/src/mimemsig.h
new file mode 100644
index 000000000..2ec9d314d
--- /dev/null
+++ b/mailnews/mime/src/mimemsig.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMSIG_H_
+#define _MIMEMSIG_H_
+
+#include "mimemult.h"
+#include "mimepbuf.h"
+#include "modmimee.h"
+
+/* The MimeMultipartSigned class implements the multipart/signed MIME
+ container, which provides a general method of associating a cryptographic
+ signature to an arbitrary MIME object.
+
+ The MimeMultipartSigned class provides the following methods:
+
+ void *crypto_init (MimeObject *multipart_object)
+
+ This is called with the object, the object->headers of which should be
+ used to initialize the dexlateion engine. NULL indicates failure;
+ otherwise, an opaque closure object should be returned.
+
+ int crypto_data_hash (const char *data, int32_t data_size,
+ void *crypto_closure)
+
+ This is called with the raw data, for which a signature has been computed.
+ The crypto module should examine this, and compute a signature for it.
+
+ int crypto_data_eof (void *crypto_closure, bool abort_p)
+
+ This is called when no more data remains. If `abort_p' is true, then the
+ crypto module may choose to discard any data rather than processing it,
+ as we're terminating abnormally.
+
+ int crypto_signature_init (void *crypto_closure,
+ MimeObject *multipart_object,
+ MimeHeaders *signature_hdrs)
+
+ This is called after crypto_data_eof() and just before the first call to
+ crypto_signature_hash(). The crypto module may wish to do some
+ initialization here, or may wish to examine the actual headers of the
+ signature object itself.
+
+ int crypto_signature_hash (const char *data, int32_t data_size,
+ void *crypto_closure)
+
+ This is called with the raw data of the detached signature block. It will
+ be called after crypto_data_eof() has been called to signify the end of
+ the data which is signed. This data is the data of the signature itself.
+
+ int crypto_signature_eof (void *crypto_closure, bool abort_p)
+
+ This is called when no more signature data remains. If `abort_p' is true,
+ then the crypto module may choose to discard any data rather than
+ processing it, as we're terminating abnormally.
+
+ char * crypto_generate_html (void *crypto_closure)
+
+ This is called after `crypto_signature_eof' but before `crypto_free'.
+ The crypto module should return a newly-allocated string of HTML code
+ which explains the status of the dexlateion to the user (whether the
+ signature checks out, etc.)
+
+ void crypto_free (void *crypto_closure)
+
+ This will be called when we're all done, after `crypto_signature_eof' and
+ `crypto_emit_html'. It is intended to free any data represented by the
+ crypto_closure.
+ */
+
+typedef struct MimeMultipartSignedClass MimeMultipartSignedClass;
+typedef struct MimeMultipartSigned MimeMultipartSigned;
+
+typedef enum {
+ MimeMultipartSignedPreamble,
+ MimeMultipartSignedBodyFirstHeader,
+ MimeMultipartSignedBodyHeaders,
+ MimeMultipartSignedBodyFirstLine,
+ MimeMultipartSignedBodyLine,
+ MimeMultipartSignedSignatureHeaders,
+ MimeMultipartSignedSignatureFirstLine,
+ MimeMultipartSignedSignatureLine,
+ MimeMultipartSignedEpilogue
+} MimeMultipartSignedParseState;
+
+struct MimeMultipartSignedClass {
+ MimeMultipartClass multipart;
+
+ /* Callbacks used by dexlateion (really, signature verification) module. */
+ void * (*crypto_init) (MimeObject *multipart_object);
+
+ int (*crypto_data_hash) (const char *data, int32_t data_size,
+ void *crypto_closure);
+ int (*crypto_signature_hash) (const char *data, int32_t data_size,
+ void *crypto_closure);
+
+ int (*crypto_data_eof) (void *crypto_closure, bool abort_p);
+ int (*crypto_signature_eof) (void *crypto_closure, bool abort_p);
+
+ int (*crypto_signature_init) (void *crypto_closure,
+ MimeObject *multipart_object,
+ MimeHeaders *signature_hdrs);
+
+ char * (*crypto_generate_html) (void *crypto_closure);
+
+ void (*crypto_notify_suppressed_child)(void *crypto_closure);
+
+ void (*crypto_free) (void *crypto_closure);
+};
+
+extern "C" MimeMultipartSignedClass mimeMultipartSignedClass;
+
+struct MimeMultipartSigned {
+ MimeMultipart multipart;
+ MimeMultipartSignedParseState state; /* State of parser */
+
+ void *crypto_closure; /* Opaque data used by signature
+ verification module. */
+
+ MimeHeaders *body_hdrs; /* The headers of the signed object. */
+ MimeHeaders *sig_hdrs; /* The headers of the signature. */
+
+ MimePartBufferData *part_buffer; /* The buffered body of the signed
+ object (see mimepbuf.h) */
+
+ MimeDecoderData *sig_decoder_data; /* The signature is probably base64
+ encoded; this is the decoder used
+ to get raw bits out of it. */
+};
+
+#define MimeMultipartSignedClassInitializer(ITYPE,CSUPER) \
+ { MimeMultipartClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEMSIG_H_ */
diff --git a/mailnews/mime/src/mimemult.cpp b/mailnews/mime/src/mimemult.cpp
new file mode 100644
index 000000000..4695ba991
--- /dev/null
+++ b/mailnews/mime/src/mimemult.cpp
@@ -0,0 +1,753 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "mimemult.h"
+#include "mimemoz2.h"
+#include "mimeeobj.h"
+
+#include "prlog.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prio.h"
+#include "nsMimeStringResources.h"
+#include "nsMimeTypes.h"
+#include <ctype.h>
+
+#ifdef XP_MACOSX
+ extern MimeObjectClass mimeMultipartAppleDoubleClass;
+#endif
+
+#define MIME_SUPERCLASS mimeContainerClass
+MimeDefClass(MimeMultipart, MimeMultipartClass,
+ mimeMultipartClass, &MIME_SUPERCLASS);
+
+static int MimeMultipart_initialize (MimeObject *);
+static void MimeMultipart_finalize (MimeObject *);
+static int MimeMultipart_parse_line (const char *line, int32_t length, MimeObject *);
+static int MimeMultipart_parse_eof (MimeObject *object, bool abort_p);
+
+static MimeMultipartBoundaryType MimeMultipart_check_boundary(MimeObject *,
+ const char *,
+ int32_t);
+static int MimeMultipart_create_child(MimeObject *);
+static bool MimeMultipart_output_child_p(MimeObject *, MimeObject *);
+static int MimeMultipart_parse_child_line (MimeObject *, const char *, int32_t,
+ bool);
+static int MimeMultipart_close_child(MimeObject *);
+
+extern "C" MimeObjectClass mimeMultipartAlternativeClass;
+extern "C" MimeObjectClass mimeMultipartRelatedClass;
+extern "C" MimeObjectClass mimeMultipartSignedClass;
+extern "C" MimeObjectClass mimeInlineTextVCardClass;
+extern "C" MimeExternalObjectClass mimeExternalObjectClass;
+extern "C" MimeSuppressedCryptoClass mimeSuppressedCryptoClass;
+
+#if defined(DEBUG) && defined(XP_UNIX)
+static int MimeMultipart_debug_print (MimeObject *, PRFileDesc *, int32_t);
+#endif
+
+static int
+MimeMultipartClassInitialize(MimeMultipartClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeMultipartClass *mclass = (MimeMultipartClass *) clazz;
+
+ PR_ASSERT(!oclass->class_initialized);
+ oclass->initialize = MimeMultipart_initialize;
+ oclass->finalize = MimeMultipart_finalize;
+ oclass->parse_line = MimeMultipart_parse_line;
+ oclass->parse_eof = MimeMultipart_parse_eof;
+
+ mclass->check_boundary = MimeMultipart_check_boundary;
+ mclass->create_child = MimeMultipart_create_child;
+ mclass->output_child_p = MimeMultipart_output_child_p;
+ mclass->parse_child_line = MimeMultipart_parse_child_line;
+ mclass->close_child = MimeMultipart_close_child;
+
+#if defined(DEBUG) && defined(XP_UNIX)
+ oclass->debug_print = MimeMultipart_debug_print;
+#endif
+
+ return 0;
+}
+
+
+static int
+MimeMultipart_initialize (MimeObject *object)
+{
+ MimeMultipart *mult = (MimeMultipart *) object;
+ char *ct;
+
+ /* This is an abstract class; it shouldn't be directly instantiated. */
+ PR_ASSERT(object->clazz != (MimeObjectClass *) &mimeMultipartClass);
+
+ ct = MimeHeaders_get (object->headers, HEADER_CONTENT_TYPE, false, false);
+ mult->boundary = (ct
+ ? MimeHeaders_get_parameter (ct, HEADER_PARM_BOUNDARY, NULL, NULL)
+ : 0);
+ PR_FREEIF(ct);
+ mult->state = MimeMultipartPreamble;
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object);
+}
+
+
+static void
+MimeMultipart_finalize (MimeObject *object)
+{
+ MimeMultipart *mult = (MimeMultipart *) object;
+
+ object->clazz->parse_eof(object, false);
+
+ PR_FREEIF(mult->boundary);
+ if (mult->hdrs)
+ MimeHeaders_free(mult->hdrs);
+ mult->hdrs = 0;
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object);
+}
+
+int MimeWriteAString(MimeObject *obj, const nsACString &string)
+{
+ const nsCString &flatString = PromiseFlatCString(string);
+ return MimeObject_write(obj, flatString.get(), flatString.Length(), true);
+}
+
+static int
+MimeMultipart_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ MimeMultipart *mult = (MimeMultipart *) obj;
+ MimeContainer *container = (MimeContainer*) obj;
+ int status = 0;
+ MimeMultipartBoundaryType boundary;
+
+ NS_ASSERTION(line && *line, "empty line in multipart parse_line");
+ if (!line || !*line) return -1;
+
+ NS_ASSERTION(!obj->closed_p, "obj shouldn't already be closed");
+ if (obj->closed_p) return -1;
+
+ /* If we're supposed to write this object, but aren't supposed to convert
+ it to HTML, simply pass it through unaltered. */
+ if (obj->output_p &&
+ obj->options &&
+ !obj->options->write_html_p &&
+ obj->options->output_fn
+ && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach)
+ return MimeObject_write(obj, line, length, true);
+
+ if (mult->state == MimeMultipartEpilogue) /* already done */
+ boundary = MimeMultipartBoundaryTypeNone;
+ else
+ boundary = ((MimeMultipartClass *)obj->clazz)->check_boundary(obj, line,
+ length);
+
+ if (boundary == MimeMultipartBoundaryTypeTerminator ||
+ boundary == MimeMultipartBoundaryTypeSeparator)
+ {
+ /* Match! Close the currently-open part, move on to the next
+ state, and discard this line.
+ */
+ bool endOfPart = (mult->state != MimeMultipartPreamble);
+ if (endOfPart)
+ status = ((MimeMultipartClass *)obj->clazz)->close_child(obj);
+ if (status < 0) return status;
+
+ if (boundary == MimeMultipartBoundaryTypeTerminator)
+ mult->state = MimeMultipartEpilogue;
+ else
+ {
+ mult->state = MimeMultipartHeaders;
+
+ /* Reset the header parser for this upcoming part. */
+ NS_ASSERTION(!mult->hdrs, "mult->hdrs should be null here");
+ if (mult->hdrs)
+ MimeHeaders_free(mult->hdrs);
+ mult->hdrs = MimeHeaders_new();
+ if (!mult->hdrs)
+ return MIME_OUT_OF_MEMORY;
+ if (obj->options && obj->options->state &&
+ obj->options->state->partsToStrip.Length() > 0)
+ {
+ nsAutoCString newPart(mime_part_address(obj));
+ newPart.Append('.');
+ newPart.AppendInt(container->nchildren + 1);
+ obj->options->state->strippingPart = false;
+ // check if this is a sub-part of a part we're stripping.
+ for (uint32_t partIndex = 0; partIndex < obj->options->state->partsToStrip.Length(); partIndex++)
+ {
+ nsCString &curPartToStrip = obj->options->state->partsToStrip[partIndex];
+ if (newPart.Find(curPartToStrip) == 0 && (newPart.Length() == curPartToStrip.Length() || newPart.CharAt(curPartToStrip.Length()) == '.'))
+ {
+ obj->options->state->strippingPart = true;
+ if (partIndex < obj->options->state->detachToFiles.Length())
+ obj->options->state->detachedFilePath = obj->options->state->detachToFiles[partIndex];
+ break;
+ }
+ }
+ }
+ }
+
+ // if stripping out attachments, write the boundary line. Otherwise, return
+ // to ignore it.
+ if (obj->options && obj->options->format_out == nsMimeOutput::nsMimeMessageAttach)
+ {
+ // Because MimeMultipart_parse_child_line strips out the
+ // the CRLF of the last line before the end of a part, we need to add that
+ // back in here.
+ if (endOfPart)
+ MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK));
+
+ status = MimeObject_write(obj, line, length, true);
+ }
+ return 0;
+ }
+
+ /* Otherwise, this isn't a boundary string. So do whatever it is we
+ should do with this line (parse it as a header, feed it to the
+ child part, ignore it, etc.) */
+
+ switch (mult->state)
+ {
+ case MimeMultipartPreamble:
+ case MimeMultipartEpilogue:
+ /* Ignore this line. */
+ break;
+
+ case MimeMultipartHeaders:
+ /* Parse this line as a header for the sub-part. */
+ {
+ status = MimeHeaders_parse_line(line, length, mult->hdrs);
+ bool stripping = false;
+
+ if (status < 0) return status;
+
+ // If this line is blank, we're now done parsing headers, and should
+ // now examine the content-type to create this "body" part.
+ //
+ if (*line == '\r' || *line == '\n')
+ {
+ if (obj->options && obj->options->state &&
+ obj->options->state->strippingPart)
+ {
+ stripping = true;
+ bool detachingPart = obj->options->state->detachedFilePath.Length() > 0;
+
+ nsAutoCString fileName;
+ fileName.Adopt(MimeHeaders_get_name(mult->hdrs, obj->options));
+ if (detachingPart)
+ {
+ char *contentType = MimeHeaders_get(mult->hdrs, "Content-Type", false, false);
+ if (contentType)
+ {
+ MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Type: "));
+ MimeWriteAString(obj, nsDependentCString(contentType));
+ PR_Free(contentType);
+ }
+ MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK));
+ MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Disposition: attachment; filename=\""));
+ MimeWriteAString(obj, fileName);
+ MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK));
+ MimeWriteAString(obj, NS_LITERAL_CSTRING("X-Mozilla-External-Attachment-URL: "));
+ MimeWriteAString(obj, obj->options->state->detachedFilePath);
+ MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK));
+ MimeWriteAString(obj, NS_LITERAL_CSTRING("X-Mozilla-Altered: AttachmentDetached; date=\""));
+ }
+ else
+ {
+ nsAutoCString header("Content-Type: text/x-moz-deleted; name=\"Deleted: ");
+ header.Append(fileName);
+ status = MimeWriteAString(obj, header);
+ if (status < 0)
+ return status;
+ status = MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK "Content-Transfer-Encoding: 8bit" MSG_LINEBREAK));
+ MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Disposition: inline; filename=\"Deleted: "));
+ MimeWriteAString(obj, fileName);
+ MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK "X-Mozilla-Altered: AttachmentDeleted; date=\""));
+ }
+ nsCString result;
+ char timeBuffer[128];
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
+ PR_FormatTimeUSEnglish(timeBuffer, sizeof(timeBuffer),
+ "%a %b %d %H:%M:%S %Y",
+ &now);
+ MimeWriteAString(obj, nsDependentCString(timeBuffer));
+ MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK));
+ MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK "You deleted an attachment from this message. The original MIME headers for the attachment were:" MSG_LINEBREAK));
+ MimeHeaders_write_raw_headers(mult->hdrs, obj->options, false);
+ }
+ int32_t old_nchildren = container->nchildren;
+ status = ((MimeMultipartClass *) obj->clazz)->create_child(obj);
+ if (status < 0) return status;
+ NS_ASSERTION(mult->state != MimeMultipartHeaders,
+ "mult->state shouldn't be MimeMultipartHeaders");
+
+ if (!stripping && container->nchildren > old_nchildren && obj->options &&
+ !mime_typep(obj, (MimeObjectClass*)&mimeMultipartAlternativeClass)) {
+ // Notify emitter about content type and part path.
+ MimeObject *kid = container->children[container->nchildren-1];
+ MimeMultipart_notify_emitter(kid);
+ }
+ }
+ break;
+ }
+
+ case MimeMultipartPartFirstLine:
+ /* Hand this line off to the sub-part. */
+ status = (((MimeMultipartClass *) obj->clazz)->parse_child_line(obj,
+ line, length, true));
+ if (status < 0) return status;
+ mult->state = MimeMultipartPartLine;
+ break;
+
+ case MimeMultipartPartLine:
+ /* Hand this line off to the sub-part. */
+ status = (((MimeMultipartClass *) obj->clazz)->parse_child_line(obj,
+ line, length, false));
+ if (status < 0) return status;
+ break;
+
+ default:
+ NS_ERROR("unexpected state in parse line");
+ return -1;
+ }
+
+ if (obj->options &&
+ obj->options->format_out == nsMimeOutput::nsMimeMessageAttach &&
+ (!(obj->options->state && obj->options->state->strippingPart) &&
+ mult->state != MimeMultipartPartLine))
+ return MimeObject_write(obj, line, length, false);
+ return 0;
+}
+
+void MimeMultipart_notify_emitter(MimeObject *obj)
+{
+ char *ct = nullptr;
+
+ NS_ASSERTION(obj->options, "MimeMultipart_notify_emitter called with null options");
+ if (! obj->options)
+ return;
+
+ ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE,
+ false, false);
+ if (obj->options->notify_nested_bodies) {
+ mimeEmitterAddHeaderField(obj->options, HEADER_CONTENT_TYPE,
+ ct ? ct : TEXT_PLAIN);
+ char *part_path = mime_part_address(obj);
+ if (part_path) {
+ mimeEmitterAddHeaderField(obj->options, "x-jsemitter-part-path",
+ part_path);
+ PR_Free(part_path);
+ }
+ }
+
+ // Examine the headers and see if there is a special charset
+ // (i.e. non US-ASCII) for this message. If so, we need to
+ // tell the emitter that this is the case for use in any
+ // possible reply or forward operation.
+ if (ct && (obj->options->notify_nested_bodies ||
+ MimeObjectIsMessageBody(obj))) {
+ char *cset = MimeHeaders_get_parameter(ct, "charset", NULL, NULL);
+ if (cset) {
+ mimeEmitterUpdateCharacterSet(obj->options, cset);
+ if (!obj->options->override_charset)
+ // Also set this charset to msgWindow
+ SetMailCharacterSetToMsgWindow(obj, cset);
+ PR_Free(cset);
+ }
+ }
+
+ PR_FREEIF(ct);
+}
+
+static MimeMultipartBoundaryType
+MimeMultipart_check_boundary(MimeObject *obj, const char *line, int32_t length)
+{
+ MimeMultipart *mult = (MimeMultipart *) obj;
+ int32_t blen;
+ bool term_p;
+
+ if (!mult->boundary ||
+ line[0] != '-' ||
+ line[1] != '-')
+ return MimeMultipartBoundaryTypeNone;
+
+ /* This is a candidate line to be a boundary. Check it out... */
+ blen = strlen(mult->boundary);
+ term_p = false;
+
+ /* strip trailing whitespace (including the newline.) */
+ while(length > 2 && IS_SPACE(line[length-1]))
+ length--;
+
+ /* Could this be a terminating boundary? */
+ if (length == blen + 4 &&
+ line[length-1] == '-' &&
+ line[length-2] == '-')
+ {
+ term_p = true;
+ }
+
+ //looks like we have a separator but first, we need to check it's not for one of the part's children.
+ MimeContainer *cont = (MimeContainer *) obj;
+ if (cont->nchildren > 0)
+ {
+ MimeObject *kid = cont->children[cont->nchildren-1];
+ if (kid)
+ if (mime_typep(kid, (MimeObjectClass*) &mimeMultipartClass))
+ {
+ //Don't ask the kid to check the boundary if it has already detected a Teminator
+ MimeMultipart *mult = (MimeMultipart *) kid;
+ if (mult->state != MimeMultipartEpilogue)
+ if (MimeMultipart_check_boundary(kid, line, length) != MimeMultipartBoundaryTypeNone)
+ return MimeMultipartBoundaryTypeNone;
+ }
+ }
+
+ if (term_p)
+ length -= 2;
+
+ if (blen == length-2 && !strncmp(line+2, mult->boundary, length-2))
+ return (term_p
+ ? MimeMultipartBoundaryTypeTerminator
+ : MimeMultipartBoundaryTypeSeparator);
+ else
+ return MimeMultipartBoundaryTypeNone;
+}
+
+
+static int
+MimeMultipart_create_child(MimeObject *obj)
+{
+ MimeMultipart *mult = (MimeMultipart *) obj;
+ int status;
+ char *ct = (mult->hdrs
+ ? MimeHeaders_get (mult->hdrs, HEADER_CONTENT_TYPE,
+ true, false)
+ : 0);
+ const char *dct = (((MimeMultipartClass *) obj->clazz)->default_part_type);
+ MimeObject *body = NULL;
+
+ mult->state = MimeMultipartPartFirstLine;
+ if (obj->options)
+ obj->options->is_child = true;
+
+ /* Don't pass in NULL as the content-type (this means that the
+ auto-uudecode-hack won't ever be done for subparts of a
+ multipart, but only for untyped children of message/rfc822.
+ */
+ body = mime_create(((ct && *ct) ? ct : (dct ? dct: TEXT_PLAIN)),
+ mult->hdrs, obj->options);
+ PR_FREEIF(ct);
+ if (!body) return MIME_OUT_OF_MEMORY;
+ status = ((MimeContainerClass *) obj->clazz)->add_child(obj, body);
+ if (status < 0)
+ {
+ mime_free(body);
+ return status;
+ }
+
+#ifdef MIME_DRAFTS
+ if ( obj->options &&
+ obj->options->decompose_file_p &&
+ obj->options->is_multipart_msg &&
+ obj->options->decompose_file_init_fn )
+ {
+ if ( !mime_typep(obj,(MimeObjectClass*)&mimeMultipartRelatedClass) &&
+ !mime_typep(obj,(MimeObjectClass*)&mimeMultipartAlternativeClass) &&
+ !mime_typep(obj,(MimeObjectClass*)&mimeMultipartSignedClass) &&
+#ifdef MIME_DETAIL_CHECK
+ !mime_typep(body, (MimeObjectClass*)&mimeMultipartRelatedClass) &&
+ !mime_typep(body, (MimeObjectClass*)&mimeMultipartAlternativeClass) &&
+ !mime_typep(body,(MimeObjectClass*)&mimeMultipartSignedClass)
+#else
+ /* bug 21869 -- due to the fact that we are not generating the
+ correct mime class object for content-typ multipart/signed part
+ the above check failed. to solve the problem in general and not
+ to cause early temination when parsing message for opening as
+ draft we can simply make sure that the child is not a multipart
+ mime object. this way we could have a proper decomposing message
+ part functions set correctly */
+ !mime_typep(body, (MimeObjectClass*) &mimeMultipartClass)
+#endif
+ && !((mime_typep(body, (MimeObjectClass *)&mimeExternalObjectClass) ||
+ mime_typep(body, (MimeObjectClass *)&mimeSuppressedCryptoClass)) &&
+ !strcmp(body->content_type, "text/x-vcard"))
+ )
+ {
+ status = obj->options->decompose_file_init_fn ( obj->options->stream_closure, mult->hdrs );
+ if (status < 0) return status;
+ }
+ }
+#endif /* MIME_DRAFTS */
+
+
+ /* Now that we've added this new object to our list of children,
+ start its parser going (if we want to display it.)
+ */
+ body->output_p = (((MimeMultipartClass *) obj->clazz)->output_child_p(obj, body));
+ if (body->output_p)
+ {
+ status = body->clazz->parse_begin(body);
+
+#ifdef XP_MACOSX
+ /* if we are saving an apple double attachment, we need to set correctly the conten type of the channel */
+ if (mime_typep(obj, (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
+ {
+ mime_stream_data *msd = (mime_stream_data *)body->options->stream_closure;
+ if (!body->options->write_html_p && body->content_type && !PL_strcasecmp(body->content_type, APPLICATION_APPLEFILE))
+ {
+ if (msd && msd->channel)
+ msd->channel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_APPLEFILE));
+ }
+ }
+#endif
+
+ if (status < 0) return status;
+ }
+
+ return 0;
+}
+
+
+static bool
+MimeMultipart_output_child_p(MimeObject *obj, MimeObject *child)
+{
+ /* We don't output a child if we're stripping it. */
+ if (obj->options && obj->options->state && obj->options->state->strippingPart)
+ return false;
+ /* if we are saving an apple double attachment, ignore the appledouble wrapper part */
+ return (obj->options && obj->options->write_html_p) ||
+ PL_strcasecmp(child->content_type, MULTIPART_APPLEDOUBLE);
+}
+
+
+
+static int
+MimeMultipart_close_child(MimeObject *object)
+{
+ MimeMultipart *mult = (MimeMultipart *) object;
+ MimeContainer *cont = (MimeContainer *) object;
+
+ if (!mult->hdrs)
+ return 0;
+
+ MimeHeaders_free(mult->hdrs);
+ mult->hdrs = 0;
+
+ NS_ASSERTION(cont->nchildren > 0, "badly formed mime message");
+ if (cont->nchildren > 0)
+ {
+ MimeObject *kid = cont->children[cont->nchildren-1];
+ // If we have a child and it has not already been closed, process it.
+ // The kid would be already be closed if we encounter a multipart section
+ // that did not have a fully delineated header block. No header block means
+ // no creation of a new child, but the termination case still happens and
+ // we still end up here. Obviously, we don't want to close the child a
+ // second time and the best thing we can do is nothing.
+ if (kid && !kid->closed_p)
+ {
+ int status;
+ status = kid->clazz->parse_eof(kid, false);
+ if (status < 0) return status;
+ status = kid->clazz->parse_end(kid, false);
+ if (status < 0) return status;
+
+#ifdef MIME_DRAFTS
+ if ( object->options &&
+ object->options->decompose_file_p &&
+ object->options->is_multipart_msg &&
+ object->options->decompose_file_close_fn )
+ {
+ if ( !mime_typep(object,(MimeObjectClass*)&mimeMultipartRelatedClass) &&
+ !mime_typep(object,(MimeObjectClass*)&mimeMultipartAlternativeClass) &&
+ !mime_typep(object,(MimeObjectClass*)&mimeMultipartSignedClass) &&
+#ifdef MIME_DETAIL_CHECK
+ !mime_typep(kid,(MimeObjectClass*)&mimeMultipartRelatedClass) &&
+ !mime_typep(kid,(MimeObjectClass*)&mimeMultipartAlternativeClass) &&
+ !mime_typep(kid,(MimeObjectClass*)&mimeMultipartSignedClass)
+#else
+ /* bug 21869 -- due to the fact that we are not generating the
+ correct mime class object for content-typ multipart/signed part
+ the above check failed. to solve the problem in general and not
+ to cause early temination when parsing message for opening as
+ draft we can simply make sure that the child is not a multipart
+ mime object. this way we could have a proper decomposing message
+ part functions set correctly */
+ !mime_typep(kid,(MimeObjectClass*) &mimeMultipartClass)
+#endif
+ && !((mime_typep(kid, (MimeObjectClass *)&mimeExternalObjectClass) ||
+ mime_typep(kid, (MimeObjectClass *)&mimeSuppressedCryptoClass)) &&
+ !strcmp(kid->content_type, "text/x-vcard"))
+ )
+ {
+ status = object->options->decompose_file_close_fn ( object->options->stream_closure );
+ if (status < 0) return status;
+ }
+ }
+#endif /* MIME_DRAFTS */
+
+ }
+ }
+ return 0;
+}
+
+
+static int
+MimeMultipart_parse_child_line (MimeObject *obj, const char *line, int32_t length,
+ bool first_line_p)
+{
+ MimeContainer *cont = (MimeContainer *) obj;
+ int status;
+ MimeObject *kid;
+
+ PR_ASSERT(cont->nchildren > 0);
+ if (cont->nchildren <= 0)
+ return -1;
+
+ kid = cont->children[cont->nchildren-1];
+ PR_ASSERT(kid);
+ if (!kid) return -1;
+
+#ifdef MIME_DRAFTS
+ if ( obj->options &&
+ obj->options->decompose_file_p &&
+ obj->options->is_multipart_msg &&
+ obj->options->decompose_file_output_fn )
+ {
+ if (!mime_typep(obj,(MimeObjectClass*)&mimeMultipartAlternativeClass) &&
+ !mime_typep(obj,(MimeObjectClass*)&mimeMultipartRelatedClass) &&
+ !mime_typep(obj,(MimeObjectClass*)&mimeMultipartSignedClass) &&
+#ifdef MIME_DETAIL_CHECK
+ !mime_typep(kid,(MimeObjectClass*)&mimeMultipartAlternativeClass) &&
+ !mime_typep(kid,(MimeObjectClass*)&mimeMultipartRelatedClass) &&
+ !mime_typep(kid,(MimeObjectClass*)&mimeMultipartSignedClass)
+#else
+ /* bug 21869 -- due to the fact that we are not generating the
+ correct mime class object for content-typ multipart/signed part
+ the above check failed. to solve the problem in general and not
+ to cause early temination when parsing message for opening as
+ draft we can simply make sure that the child is not a multipart
+ mime object. this way we could have a proper decomposing message
+ part functions set correctly */
+ !mime_typep(kid, (MimeObjectClass*) &mimeMultipartClass)
+#endif
+ && !(mime_typep(kid, (MimeObjectClass*)&mimeExternalObjectClass) && !strcmp(kid->content_type, "text/x-vcard"))
+ )
+ return obj->options->decompose_file_output_fn (line, length, obj->options->stream_closure);
+ }
+#endif /* MIME_DRAFTS */
+
+ /* The newline issues here are tricky, since both the newlines before
+ and after the boundary string are to be considered part of the
+ boundary: this is so that a part can be specified such that it
+ does not end in a trailing newline.
+
+ To implement this, we send a newline *before* each line instead
+ of after, except for the first line, which is not preceeded by a
+ newline.
+ */
+
+ /* Remove the trailing newline... */
+ if (length > 0 && line[length-1] == '\n') length--;
+ if (length > 0 && line[length-1] == '\r') length--;
+
+ if (!first_line_p)
+ {
+ /* Push out a preceeding newline... */
+ char nl[] = MSG_LINEBREAK;
+ status = kid->clazz->parse_buffer (nl, MSG_LINEBREAK_LEN, kid);
+ if (status < 0) return status;
+ }
+
+ /* Now push out the line sans trailing newline. */
+ return kid->clazz->parse_buffer (line, length, kid);
+}
+
+
+static int
+MimeMultipart_parse_eof (MimeObject *obj, bool abort_p)
+{
+ MimeMultipart *mult = (MimeMultipart *) obj;
+ MimeContainer *cont = (MimeContainer *) obj;
+
+ if (obj->closed_p) return 0;
+
+ /* Push out the last trailing line if there's one in the buffer. If
+ this happens, this object does not end in a trailing newline (and
+ the parse_line method will be called with a string with no trailing
+ newline, which isn't the usual case.)
+ */
+ if (!abort_p && obj->ibuffer_fp > 0)
+ {
+ /* There is leftover data without a terminating newline. */
+ int status = obj->clazz->parse_line(obj->ibuffer, obj->ibuffer_fp,obj);
+ obj->ibuffer_fp = 0;
+ if (status < 0)
+ {
+ obj->closed_p = true;
+ return status;
+ }
+ }
+
+ /* Now call parse_eof for our active child, if there is one.
+ */
+ if (cont->nchildren > 0 &&
+ (mult->state == MimeMultipartPartLine ||
+ mult->state == MimeMultipartPartFirstLine))
+ {
+ MimeObject *kid = cont->children[cont->nchildren-1];
+ NS_ASSERTION(kid, "not expecting null kid");
+ if (kid)
+ {
+ int status = kid->clazz->parse_eof(kid, abort_p);
+ if (status < 0) return status;
+ }
+ }
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+}
+
+
+#if defined(DEBUG) && defined(XP_UNIX)
+static int
+MimeMultipart_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth)
+{
+ /* MimeMultipart *mult = (MimeMultipart *) obj; */
+ MimeContainer *cont = (MimeContainer *) obj;
+ char *addr = mime_part_address(obj);
+ int i;
+ for (i=0; i < depth; i++)
+ PR_Write(stream, " ", 2);
+/**
+ fprintf(stream, "<%s %s (%d kid%s) boundary=%s 0x%08X>\n",
+ obj->clazz->class_name,
+ addr ? addr : "???",
+ cont->nchildren, (cont->nchildren == 1 ? "" : "s"),
+ (mult->boundary ? mult->boundary : "(none)"),
+ (uint32_t) mult);
+**/
+ PR_FREEIF(addr);
+
+/*
+ if (cont->nchildren > 0)
+ fprintf(stream, "\n");
+ */
+
+ for (i = 0; i < cont->nchildren; i++)
+ {
+ MimeObject *kid = cont->children[i];
+ int status = kid->clazz->debug_print (kid, stream, depth+1);
+ if (status < 0) return status;
+ }
+
+/*
+ if (cont->nchildren > 0)
+ fprintf(stream, "\n");
+ */
+
+ return 0;
+}
+#endif
diff --git a/mailnews/mime/src/mimemult.h b/mailnews/mime/src/mimemult.h
new file mode 100644
index 000000000..877afb4d9
--- /dev/null
+++ b/mailnews/mime/src/mimemult.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEMULT_H_
+#define _MIMEMULT_H_
+
+#include "mimecont.h"
+
+/* The MimeMultipart class class implements the objects representing all of
+ the "multipart/" MIME types. In addition to the methods inherited from
+ MimeContainer, it provides the following methods and class variables:
+
+ int create_child (MimeObject *obj)
+
+ When it has been determined that a new sub-part should be created,
+ this method is called to do that. The default value for this method
+ does it in the usual multipart/mixed way. The headers of the object-
+ to-be-created may be found in the `hdrs' slot of the `MimeMultipart'
+ object.
+
+ bool output_child_p (MimeObject *parent, MimeObject *child)
+
+ Whether this child should be output. Default method always says `yes'.
+
+ int parse_child_line (MimeObject *obj, const char *line, int32_t length,
+ bool first_line_p)
+
+ When we have a line which should be handed off to the currently-active
+ child object, this method is called to do that. The `first_line_p'
+ variable will be true only for the very first line handed off to this
+ sub-part. The default method simply passes the line to the most-
+ recently-added child object.
+
+ int close_child (MimeObject *self)
+
+ When we reach the end of a sub-part (a separator line) this method is
+ called to shut down the currently-active child. The default method
+ simply calls `parse_eof' on the most-recently-added child object.
+
+ MimeMultipartBoundaryType check_boundary (MimeObject *obj,
+ const char *line, int32_t length)
+
+ This method is used to examine a line and determine whether it is a
+ part boundary, and if so, what kind. It should return a member of
+ the MimeMultipartBoundaryType describing the line.
+
+ const char *default_part_type
+
+ This is the type which should be assumed for sub-parts which have
+ no explicit type specified. The default is "text/plain", but the
+ "multipart/digest" subclass overrides this to "message/rfc822".
+ */
+
+typedef struct MimeMultipartClass MimeMultipartClass;
+typedef struct MimeMultipart MimeMultipart;
+
+typedef enum {
+ MimeMultipartPreamble,
+ MimeMultipartHeaders,
+ MimeMultipartPartFirstLine,
+ MimeMultipartPartLine,
+ MimeMultipartEpilogue
+} MimeMultipartParseState;
+
+typedef enum {
+ MimeMultipartBoundaryTypeNone,
+ MimeMultipartBoundaryTypeSeparator,
+ MimeMultipartBoundaryTypeTerminator
+} MimeMultipartBoundaryType;
+
+
+struct MimeMultipartClass {
+ MimeContainerClass container;
+ const char *default_part_type;
+
+ int (*create_child) (MimeObject *);
+ bool (*output_child_p) (MimeObject *self, MimeObject *child);
+ int (*close_child) (MimeObject *);
+ int (*parse_child_line) (MimeObject *, const char *line, int32_t length,
+ bool first_line_p);
+ MimeMultipartBoundaryType (*check_boundary) (MimeObject *, const char *line,
+ int32_t length);
+};
+
+extern MimeMultipartClass mimeMultipartClass;
+
+struct MimeMultipart {
+ MimeContainer container; /* superclass variables */
+ char *boundary; /* Inter-part delimiter string */
+ MimeHeaders *hdrs; /* headers of the part currently
+ being parsed, if any */
+ MimeMultipartParseState state; /* State of parser */
+};
+
+extern void MimeMultipart_notify_emitter(MimeObject *);
+
+#define MimeMultipartClassInitializer(ITYPE,CSUPER) \
+ { MimeContainerClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEMULT_H_ */
diff --git a/mailnews/mime/src/mimeobj.cpp b/mailnews/mime/src/mimeobj.cpp
new file mode 100644
index 000000000..26eb618ce
--- /dev/null
+++ b/mailnews/mime/src/mimeobj.cpp
@@ -0,0 +1,327 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+#include "mimeobj.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prio.h"
+#include "mimebuf.h"
+#include "prlog.h"
+#include "nsMimeTypes.h"
+#include "nsMimeStringResources.h"
+#include "nsMsgUtils.h"
+#include "mimemsg.h"
+#include "mimemapl.h"
+
+/* Way to destroy any notions of modularity or class hierarchy, Terry! */
+# include "mimetpla.h"
+# include "mimethtm.h"
+# include "mimecont.h"
+
+MimeDefClass (MimeObject, MimeObjectClass, mimeObjectClass, NULL);
+
+static int MimeObject_initialize (MimeObject *);
+static void MimeObject_finalize (MimeObject *);
+static int MimeObject_parse_begin (MimeObject *);
+static int MimeObject_parse_buffer (const char *, int32_t, MimeObject *);
+static int MimeObject_parse_line (const char *, int32_t, MimeObject *);
+static int MimeObject_parse_eof (MimeObject *, bool);
+static int MimeObject_parse_end (MimeObject *, bool);
+static bool MimeObject_displayable_inline_p (MimeObjectClass *clazz,
+ MimeHeaders *hdrs);
+
+#if defined(DEBUG) && defined(XP_UNIX)
+static int MimeObject_debug_print (MimeObject *, PRFileDesc *, int32_t depth);
+#endif
+
+static int
+MimeObjectClassInitialize(MimeObjectClass *clazz)
+{
+ NS_ASSERTION(!clazz->class_initialized, "class shouldn't already be initialized");
+ clazz->initialize = MimeObject_initialize;
+ clazz->finalize = MimeObject_finalize;
+ clazz->parse_begin = MimeObject_parse_begin;
+ clazz->parse_buffer = MimeObject_parse_buffer;
+ clazz->parse_line = MimeObject_parse_line;
+ clazz->parse_eof = MimeObject_parse_eof;
+ clazz->parse_end = MimeObject_parse_end;
+ clazz->displayable_inline_p = MimeObject_displayable_inline_p;
+
+#if defined(DEBUG) && defined(XP_UNIX)
+ clazz->debug_print = MimeObject_debug_print;
+#endif
+ return 0;
+}
+
+static int
+MimeObject_initialize (MimeObject *obj)
+{
+ /* This is an abstract class; it shouldn't be directly instantiated. */
+ NS_ASSERTION(obj->clazz != &mimeObjectClass, "should directly instantiate abstract class");
+
+ /* Set up the content-type and encoding. */
+ if (!obj->content_type && obj->headers)
+ obj->content_type = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE,
+ true, false);
+ if (!obj->encoding && obj->headers)
+ obj->encoding = MimeHeaders_get (obj->headers,
+ HEADER_CONTENT_TRANSFER_ENCODING,
+ true, false);
+
+ /* Special case to normalize some types and encodings to a canonical form.
+ (These are nonstandard types/encodings which have been seen to appear in
+ multiple forms; we normalize them so that things like looking up icons
+ and extensions has consistent behavior for the receiver, regardless of
+ the "alias" type that the sender used.)
+ */
+ if (!obj->content_type || !*(obj->content_type))
+ ;
+ else if (!PL_strcasecmp(obj->content_type, APPLICATION_UUENCODE2) ||
+ !PL_strcasecmp(obj->content_type, APPLICATION_UUENCODE3) ||
+ !PL_strcasecmp(obj->content_type, APPLICATION_UUENCODE4))
+ {
+ PR_Free(obj->content_type);
+ obj->content_type = strdup(APPLICATION_UUENCODE);
+ }
+ else if (!PL_strcasecmp(obj->content_type, IMAGE_XBM2) ||
+ !PL_strcasecmp(obj->content_type, IMAGE_XBM3))
+ {
+ PR_Free(obj->content_type);
+ obj->content_type = strdup(IMAGE_XBM);
+ }
+ else {
+ // MIME-types are case-insenitive, but let's make it lower case internally
+ // to avoid some hassle later down the road.
+ nsAutoCString lowerCaseContentType;
+ ToLowerCase(nsDependentCString(obj->content_type), lowerCaseContentType);
+ PR_Free(obj->content_type);
+ obj->content_type = ToNewCString(lowerCaseContentType);
+ }
+
+ if (!obj->encoding)
+ ;
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE2) ||
+ !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE3) ||
+ !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE4))
+ {
+ PR_Free(obj->encoding);
+ obj->encoding = strdup(ENCODING_UUENCODE);
+ }
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_COMPRESS2))
+ {
+ PR_Free(obj->encoding);
+ obj->encoding = strdup(ENCODING_COMPRESS);
+ }
+ else if (!PL_strcasecmp(obj->encoding, ENCODING_GZIP2))
+ {
+ PR_Free(obj->encoding);
+ obj->encoding = strdup(ENCODING_GZIP);
+ }
+
+ return 0;
+}
+
+static void
+MimeObject_finalize (MimeObject *obj)
+{
+ obj->clazz->parse_eof (obj, false);
+ obj->clazz->parse_end (obj, false);
+
+ if (obj->headers)
+ {
+ MimeHeaders_free(obj->headers);
+ obj->headers = 0;
+ }
+
+ /* Should have been freed by parse_eof, but just in case... */
+ NS_ASSERTION(!obj->ibuffer, "buffer not freed");
+ NS_ASSERTION(!obj->obuffer, "buffer not freed");
+ PR_FREEIF (obj->ibuffer);
+ PR_FREEIF (obj->obuffer);
+
+ PR_FREEIF(obj->content_type);
+ PR_FREEIF(obj->encoding);
+
+ if (obj->options && obj->options->state)
+ {
+ delete obj->options->state;
+ obj->options->state = nullptr;
+ }
+}
+
+static int
+MimeObject_parse_begin (MimeObject *obj)
+{
+ NS_ASSERTION (!obj->closed_p, "object shouldn't be already closed");
+
+ /* If we haven't set up the state object yet, then this should be
+ the outermost object... */
+ if (obj->options && !obj->options->state)
+ {
+ NS_ASSERTION(!obj->headers, "headers should be null"); /* should be the outermost object. */
+
+ obj->options->state = new MimeParseStateObject;
+ if (!obj->options->state) return MIME_OUT_OF_MEMORY;
+ obj->options->state->root = obj;
+ obj->options->state->separator_suppressed_p = true; /* no first sep */
+ const char *delParts = PL_strcasestr(obj->options->url, "&del=");
+ const char *detachLocations = PL_strcasestr(obj->options->url, "&detachTo=");
+ if (delParts)
+ {
+ const char *delEnd = PL_strcasestr(delParts + 1, "&");
+ if (!delEnd)
+ delEnd = delParts + strlen(delParts);
+ ParseString(Substring(delParts + 5, delEnd), ',', obj->options->state->partsToStrip);
+ }
+ if (detachLocations)
+ {
+ detachLocations += 10; // advance past "&detachTo="
+ ParseString(nsDependentCString(detachLocations), ',', obj->options->state->detachToFiles);
+ }
+ }
+
+ /* Decide whether this object should be output or not... */
+ if (!obj->options || obj->options->no_output_p || !obj->options->output_fn
+ /* if we are decomposing the message in files and processing a multipart object,
+ we must not output it without parsing it first */
+ || (obj->options->decompose_file_p && obj->options->decompose_file_output_fn &&
+ mime_typep(obj, (MimeObjectClass*) &mimeMultipartClass))
+ )
+ obj->output_p = false;
+ else if (!obj->options->part_to_load)
+ obj->output_p = true;
+ else
+ {
+ char *id = mime_part_address(obj);
+ if (!id) return MIME_OUT_OF_MEMORY;
+
+ // We need to check if a part is the subpart of the part to load.
+ // If so and this is a raw or body display output operation, then
+ // we should mark the part for subsequent output.
+
+ // First, check for an exact match
+ obj->output_p = !strcmp(id, obj->options->part_to_load);
+ if (!obj->output_p && (obj->options->format_out == nsMimeOutput::nsMimeMessageRaw ||
+ obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay ||
+ obj->options->format_out == nsMimeOutput::nsMimeMessageAttach))
+ {
+ // Then, check for subpart
+ unsigned int partlen = strlen(obj->options->part_to_load);
+ obj->output_p = (strlen(id) >= partlen + 2) && (id[partlen] == '.') &&
+ !strncmp(id, obj->options->part_to_load, partlen);
+ }
+
+ PR_Free(id);
+ }
+
+ // If we've decided not to output this part, we also shouldn't be showing it
+ // as an attachment.
+ obj->dontShowAsAttachment = !obj->output_p;
+
+ return 0;
+}
+
+static int
+MimeObject_parse_buffer (const char *buffer, int32_t size, MimeObject *obj)
+{
+ NS_ASSERTION(!obj->closed_p, "object shouldn't be closed");
+ if (obj->closed_p) return -1;
+
+ return mime_LineBuffer (buffer, size,
+ &obj->ibuffer, &obj->ibuffer_size, &obj->ibuffer_fp,
+ true,
+ ((int (*) (char *, int32_t, void *))
+ /* This cast is to turn void into MimeObject */
+ obj->clazz->parse_line),
+ obj);
+}
+
+static int
+MimeObject_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ NS_ERROR("shouldn't call this method");
+ return -1;
+}
+
+static int
+MimeObject_parse_eof (MimeObject *obj, bool abort_p)
+{
+ if (obj->closed_p) return 0;
+ NS_ASSERTION(!obj->parsed_p, "obj already parsed");
+
+ /* If there is still data in the ibuffer, that means that the last line of
+ this part didn't end in a newline; so push it out anyway (this means that
+ the parse_line method will be called with a string with no trailing
+ newline, which isn't the usual case.)
+ */
+ if (!abort_p &&
+ obj->ibuffer_fp > 0)
+ {
+ int status = obj->clazz->parse_line (obj->ibuffer, obj->ibuffer_fp, obj);
+ obj->ibuffer_fp = 0;
+ if (status < 0)
+ {
+ obj->closed_p = true;
+ return status;
+ }
+ }
+
+ obj->closed_p = true;
+ return 0;
+}
+
+static int
+MimeObject_parse_end (MimeObject *obj, bool abort_p)
+{
+ if (obj->parsed_p)
+ {
+ NS_ASSERTION(obj->closed_p, "object should be closed");
+ return 0;
+ }
+
+ /* We won't be needing these buffers any more; nuke 'em. */
+ PR_FREEIF(obj->ibuffer);
+ obj->ibuffer_fp = 0;
+ obj->ibuffer_size = 0;
+ PR_FREEIF(obj->obuffer);
+ obj->obuffer_fp = 0;
+ obj->obuffer_size = 0;
+
+ obj->parsed_p = true;
+ return 0;
+}
+
+static bool
+MimeObject_displayable_inline_p (MimeObjectClass *clazz, MimeHeaders *hdrs)
+{
+ NS_ERROR("shouldn't call this method");
+ return false;
+}
+
+#if defined(DEBUG) && defined(XP_UNIX)
+static int
+MimeObject_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth)
+{
+ int i;
+ char *addr = mime_part_address(obj);
+ for (i=0; i < depth; i++)
+ PR_Write(stream, " ", 2);
+/*
+ fprintf(stream, "<%s %s 0x%08X>\n", obj->clazz->class_name,
+ addr ? addr : "???",
+ (uint32_t) obj);
+*/
+ PR_FREEIF(addr);
+ return 0;
+}
+#endif
diff --git a/mailnews/mime/src/mimeobj.h b/mailnews/mime/src/mimeobj.h
new file mode 100644
index 000000000..7ae3eda68
--- /dev/null
+++ b/mailnews/mime/src/mimeobj.h
@@ -0,0 +1,186 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEOBJ_H_
+#define _MIMEOBJ_H_
+
+#include "mimei.h"
+#include "prio.h"
+/* MimeObject is the base-class for the objects representing all other
+ MIME types. It provides several methods:
+
+ int initialize (MimeObject *obj)
+
+ This is called from mime_new() when a new instance is allocated.
+ Subclasses should do whatever setup is necessary from this method,
+ and should call the superclass's initialize method, unless there's
+ a specific reason not to.
+
+ void finalize (MimeObject *obj)
+
+ This is called from mime_free() and should free all data associated
+ with the object. If the object points to other MIME objects, they
+ should be finalized as well (by calling mime_free(), not by calling
+ their finalize() methods directly.)
+
+ int parse_buffer (const char *buf, int32_t size, MimeObject *obj)
+
+ This is the method by which you feed arbitrary data into the parser
+ for this object. Most subclasses will probably inherit this method
+ from the MimeObject base-class, which line-buffers the data and then
+ hands it off to the parse_line() method.
+
+ If this object uses a Content-Transfer-Encoding (base64, qp, uue)
+ then the data may be decoded by parse_buffer() before parse_line()
+ is called. (The MimeLeaf class provides this functionality.)
+
+ int parse_begin (MimeObject *obj)
+ Called after `init' but before `parse_line' or `parse_buffer'.
+ Can be used to initialize various parsing machinery.
+
+ int parse_line (const char *line, int32_t length, MimeObject *obj)
+
+ This method is called (by parse_buffer()) for each complete line of
+ data handed to the parser, and is the method which most subclasses
+ will override to implement their parsers.
+
+ When handing data off to a MIME object for parsing, one should always
+ call the parse_buffer() method, and not call the parse_line() method
+ directly, since the parse_buffer() method may do other transformations
+ on the data (like base64 decoding.)
+
+ One should generally not call parse_line() directly, since that could
+ bypass decoding. One should call parse_buffer() instead.
+
+ int parse_eof (MimeObject *obj, bool abort_p)
+
+ This is called when there is no more data to be handed to the object:
+ when the parent object is done feeding data to an object being parsed.
+ Implementors of this method should be sure to also call the parse_eof()
+ methods of any sub-objects to which they have pointers.
+
+ This is also called by the finalize() method, just before object
+ destruction, if it has not already been called.
+
+ The `closed_p' instance variable is used to prevent multiple calls to
+ `parse_eof'.
+
+ int parse_end (MimeObject *obj)
+ Called after `parse_eof' but before `finalize'.
+ This can be used to free up any memory no longer needed now that parsing
+ is done (to avoid surprises due to unexpected method combination, it's
+ best to free things in this method in preference to `parse_eof'.)
+ Implementors of this method should be sure to also call the parse_end()
+ methods of any sub-objects to which they have pointers.
+
+ This is also called by the finalize() method, just before object
+ destruction, if it has not already been called.
+
+ The `parsed_p' instance variable is used to prevent multiple calls to
+ `parse_end'.
+
+
+ bool displayable_inline_p (MimeObjectClass *class, MimeHeaders *hdrs)
+
+ This method should return true if this class of object will be displayed
+ directly, as opposed to being displayed as a link. This information is
+ used by the "multipart/alternative" parser to decide which of its children
+ is the ``best'' one to display. Note that this is a class method, not
+ an object method -- there is not yet an instance of this class at the time
+ that it is called. The `hdrs' provided are the headers of the object that
+ might be instantiated -- from this, the method may extract additional
+ infomation that it might need to make its decision.
+ */
+
+
+/* this one is typdedef'ed in mimei.h, since it is the base-class. */
+struct MimeObjectClass {
+
+ /* Note: the order of these first five slots is known by MimeDefClass().
+ Technically, these are part of the object system, not the MIME code.
+ */
+ const char *class_name;
+ int instance_size;
+ struct MimeObjectClass *superclass;
+ int (*class_initialize) (MimeObjectClass *clazz);
+ bool class_initialized;
+
+ /* These are the methods shared by all MIME objects. See comment above.
+ */
+ int (*initialize) (MimeObject *obj);
+ void (*finalize) (MimeObject *obj);
+ int (*parse_begin) (MimeObject *obj);
+ int (*parse_buffer) (const char *buf, int32_t size, MimeObject *obj);
+ int (*parse_line) (const char *line, int32_t length, MimeObject *obj);
+ int (*parse_eof) (MimeObject *obj, bool abort_p);
+ int (*parse_end) (MimeObject *obj, bool abort_p);
+
+ bool (*displayable_inline_p) (MimeObjectClass *clazz, MimeHeaders *hdrs);
+
+#if defined(DEBUG) && defined(XP_UNIX)
+ int (*debug_print) (MimeObject *obj, PRFileDesc *stream, int32_t depth);
+#endif
+};
+
+extern "C" MimeObjectClass mimeObjectClass;
+
+/* this one is typdedef'ed in mimei.h, since it is the base-class. */
+struct MimeObject {
+ MimeObjectClass *clazz; /* Pointer to class object, for `type-of' */
+
+ MimeHeaders *headers; /* The header data associated with this object;
+ this is where the content-type, disposition,
+ description, and other meta-data live.
+
+ For example, the outermost message/rfc822 object
+ would have NULL here (since it has no parent,
+ thus no headers to describe it.) However, a
+ multipart/mixed object, which was the sole
+ child of that message/rfc822 object, would have
+ here a copy of the headers which began the
+ parent object (the headers which describe the
+ child.)
+ */
+
+ char *content_type; /* The MIME content-type and encoding. */
+ char *encoding; /* In most cases, these will be the same as the
+ values to be found in the `headers' object,
+ but in some cases, the values in these slots
+ will be more correct than the headers.
+ */
+
+
+ MimeObject *parent; /* Backpointer to a MimeContainer object. */
+
+ MimeDisplayOptions *options; /* Display preferences set by caller. */
+
+ bool closed_p; /* Whether it's done being written to. */
+ bool parsed_p; /* Whether the parser has been shut down. */
+ bool output_p; /* Whether it should be written. */
+ bool dontShowAsAttachment; /* Force an object to not be shown as attachment,
+ but when is false, it doesn't mean it will be
+ shown as attachment; specifically, body parts
+ are never shown as attachments. */
+
+ /* Read-buffer and write-buffer (on input, `parse_buffer' uses ibuffer to
+ compose calls to `parse_line'; on output, `obuffer' is used in various
+ ways by various routines.) These buffers are created and grow as needed.
+ `ibuffer' should be generally be considered hands-off, and `obuffer'
+ should generally be considered fair game.
+ */
+ char *ibuffer, *obuffer;
+ int32_t ibuffer_size, obuffer_size;
+ int32_t ibuffer_fp, obuffer_fp;
+};
+
+
+#define MimeObject_grow_obuffer(obj, desired_size) \
+ (((desired_size) >= (obj)->obuffer_size) ? \
+ mime_GrowBuffer ((uint32_t)(desired_size), (uint32_t)sizeof(char), 1024, \
+ &(obj)->obuffer, (int32_t*)&(obj)->obuffer_size) \
+ : 0)
+
+
+#endif /* _MIMEOBJ_H_ */
diff --git a/mailnews/mime/src/mimepbuf.cpp b/mailnews/mime/src/mimepbuf.cpp
new file mode 100644
index 000000000..8e352ed22
--- /dev/null
+++ b/mailnews/mime/src/mimepbuf.cpp
@@ -0,0 +1,296 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "mimepbuf.h"
+#include "mimemoz2.h"
+#include "prmem.h"
+#include "prio.h"
+#include "plstr.h"
+#include "nsMimeStringResources.h"
+#include "nsNetUtil.h"
+#include "nsMsgUtils.h"
+//
+// External Defines...
+//
+extern nsresult
+nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile);
+
+/* See mimepbuf.h for a description of the mission of this file.
+
+ Implementation:
+
+ When asked to buffer an object, we first try to malloc() a buffer to
+ hold the upcoming part. First we try to allocate a 50k buffer, and
+ then back off by 5k until we are able to complete the allocation,
+ or are unable to allocate anything.
+
+ As data is handed to us, we store it in the memory buffer, until the
+ size of the memory buffer is exceeded (including the case where no
+ memory buffer was able to be allocated at all.)
+
+ Once we've filled the memory buffer, we open a temp file on disk.
+ Anything that is currently in the memory buffer is then flushed out
+ to the disk file (and the memory buffer is discarded.) Subsequent
+ data that is passed in is appended to the file.
+
+ Thus only one of the memory buffer or the disk buffer ever exist at
+ the same time; and small parts tend to live completely in memory
+ while large parts tend to live on disk.
+
+ When we are asked to read the data back out of the buffer, we call
+ the provided read-function with either: the contents of the memory
+ buffer; or blocks read from the disk file.
+ */
+
+#define TARGET_MEMORY_BUFFER_SIZE (1024 * 50) /* try for 50k mem buffer */
+#define TARGET_MEMORY_BUFFER_QUANTUM (1024 * 5) /* decrease in steps of 5k */
+#define DISK_BUFFER_SIZE (1024 * 10) /* read disk in 10k chunks */
+
+
+struct MimePartBufferData
+{
+ char *part_buffer; /* Buffer used for part-lookahead. */
+ int32_t part_buffer_fp; /* Active length. */
+ int32_t part_buffer_size; /* How big it is. */
+
+ nsCOMPtr <nsIFile> file_buffer; /* The nsIFile of a temp file used when we
+ run out of room in the head_buffer. */
+ nsCOMPtr <nsIInputStream> input_file_stream; /* A stream to it. */
+ nsCOMPtr <nsIOutputStream> output_file_stream; /* A stream to it. */
+};
+
+MimePartBufferData *
+MimePartBufferCreate (void)
+{
+ MimePartBufferData *data = PR_NEW(MimePartBufferData);
+ if (!data) return 0;
+ memset(data, 0, sizeof(*data));
+ return data;
+}
+
+
+void
+MimePartBufferClose (MimePartBufferData *data)
+{
+ NS_ASSERTION(data, "MimePartBufferClose: no data");
+ if (!data) return;
+
+ if (data->input_file_stream)
+ {
+ data->input_file_stream->Close();
+ data->input_file_stream = nullptr;
+ }
+
+ if (data->output_file_stream)
+ {
+ data->output_file_stream->Close();
+ data->output_file_stream = nullptr;
+ }
+}
+
+
+void
+MimePartBufferReset (MimePartBufferData *data)
+{
+ NS_ASSERTION(data, "MimePartBufferReset: no data");
+ if (!data) return;
+
+ PR_FREEIF(data->part_buffer);
+ data->part_buffer_fp = 0;
+
+ if (data->input_file_stream)
+ {
+ data->input_file_stream->Close();
+ data->input_file_stream = nullptr;
+ }
+
+ if (data->output_file_stream)
+ {
+ data->output_file_stream->Close();
+ data->output_file_stream = nullptr;
+ }
+
+ if (data->file_buffer)
+ {
+ data->file_buffer->Remove(false);
+ data->file_buffer = nullptr;
+ }
+}
+
+
+void
+MimePartBufferDestroy (MimePartBufferData *data)
+{
+ NS_ASSERTION(data, "MimePartBufferDestroy: no data");
+ if (!data) return;
+ MimePartBufferReset (data);
+ PR_Free(data);
+}
+
+
+int
+MimePartBufferWrite (MimePartBufferData *data,
+ const char *buf, int32_t size)
+{
+ NS_ASSERTION(data && buf && size > 0, "MimePartBufferWrite: Bad param");
+ if (!data || !buf || size <= 0)
+ return -1;
+
+ /* If we don't yet have a buffer (either memory or file) try and make a
+ memory buffer.
+ */
+ if (!data->part_buffer &&
+ !data->file_buffer)
+ {
+ int target_size = TARGET_MEMORY_BUFFER_SIZE;
+ while (target_size > 0)
+ {
+ data->part_buffer = (char *) PR_MALLOC(target_size);
+ if (data->part_buffer) break; /* got it! */
+ target_size -= TARGET_MEMORY_BUFFER_QUANTUM; /* decrease it and try
+ again */
+ }
+
+ if (data->part_buffer)
+ data->part_buffer_size = target_size;
+ else
+ data->part_buffer_size = 0;
+
+ data->part_buffer_fp = 0;
+ }
+
+ /* Ok, if at this point we still don't have either kind of buffer, try and
+ make a file buffer. */
+ if (!data->part_buffer && !data->file_buffer)
+ {
+ nsCOMPtr <nsIFile> tmpFile;
+ nsresult rv = nsMsgCreateTempFile("nsma", getter_AddRefs(tmpFile));
+ NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE);
+ data->file_buffer = do_QueryInterface(tmpFile);
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(data->output_file_stream), data->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600);
+ NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE);
+ }
+
+ NS_ASSERTION(data->part_buffer || data->output_file_stream, "no part_buffer or file_stream");
+
+ /* If this buf will fit in the memory buffer, put it there.
+ */
+ if (data->part_buffer &&
+ data->part_buffer_fp + size < data->part_buffer_size)
+ {
+ memcpy(data->part_buffer + data->part_buffer_fp,
+ buf, size);
+ data->part_buffer_fp += size;
+ }
+
+ /* Otherwise it won't fit; write it to the file instead. */
+ else
+ {
+ /* If the file isn't open yet, open it, and dump the memory buffer
+ to it. */
+ if (!data->output_file_stream)
+ {
+ nsresult rv;
+ if (!data->file_buffer)
+ {
+ nsCOMPtr <nsIFile> tmpFile;
+ rv = nsMsgCreateTempFile("nsma", getter_AddRefs(tmpFile));
+ NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE);
+ data->file_buffer = do_QueryInterface(tmpFile);
+
+ }
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(data->output_file_stream), data->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600);
+ NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE);
+
+ if (data->part_buffer && data->part_buffer_fp)
+ {
+ uint32_t bytesWritten;
+ nsresult rv = data->output_file_stream->Write(data->part_buffer,
+ data->part_buffer_fp, &bytesWritten);
+ NS_ENSURE_SUCCESS(rv, MIME_ERROR_WRITING_FILE);
+ }
+
+ PR_FREEIF(data->part_buffer);
+ data->part_buffer_fp = 0;
+ data->part_buffer_size = 0;
+ }
+
+ /* Dump this buf to the file. */
+ uint32_t bytesWritten;
+ nsresult rv = data->output_file_stream->Write (buf, size, &bytesWritten);
+ if (NS_FAILED(rv) || (int32_t) bytesWritten < size)
+ return MIME_OUT_OF_MEMORY;
+ }
+
+ return 0;
+}
+
+
+int
+MimePartBufferRead (MimePartBufferData *data,
+ MimeConverterOutputCallback read_fn,
+ void *closure)
+{
+ int status = 0;
+ NS_ASSERTION(data, "no data");
+ if (!data) return -1;
+
+ if (data->part_buffer)
+ {
+ // Read it out of memory.
+ status = read_fn(data->part_buffer, data->part_buffer_fp, closure);
+ }
+ else if (data->file_buffer)
+ {
+ /* Read it off disk.
+ */
+ char *buf;
+ int32_t buf_size = DISK_BUFFER_SIZE;
+
+ NS_ASSERTION(data->part_buffer_size == 0 && data->part_buffer_fp == 0, "buffer size is not null");
+ NS_ASSERTION(data->file_buffer, "no file buffer name");
+ if (!data->file_buffer)
+ return -1;
+
+ buf = (char *) PR_MALLOC(buf_size);
+ if (!buf)
+ return MIME_OUT_OF_MEMORY;
+
+ // First, close the output file to open the input file!
+ if (data->output_file_stream)
+ data->output_file_stream->Close();
+
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(data->input_file_stream), data->file_buffer);
+ if (NS_FAILED(rv))
+ {
+ PR_Free(buf);
+ return MIME_UNABLE_TO_OPEN_TMP_FILE;
+ }
+ while(1)
+ {
+ uint32_t bytesRead = 0;
+ rv = data->input_file_stream->Read(buf, buf_size - 1, &bytesRead);
+ if (NS_FAILED(rv) || !bytesRead)
+ {
+ break;
+ }
+ else
+ {
+ /* It would be really nice to be able to yield here, and let
+ some user events and other input sources get processed.
+ Oh well. */
+
+ status = read_fn (buf, bytesRead, closure);
+ if (status < 0) break;
+ }
+ }
+ PR_Free(buf);
+ }
+
+ return 0;
+}
+
diff --git a/mailnews/mime/src/mimepbuf.h b/mailnews/mime/src/mimepbuf.h
new file mode 100644
index 000000000..200b64a89
--- /dev/null
+++ b/mailnews/mime/src/mimepbuf.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEPBUF_H_
+#define _MIMEPBUF_H_
+
+#include "mimei.h"
+#include "modmimee.h" // for MimeConverterOutputCallback
+
+/* This file provides the ability to save up the entire contents of a MIME
+ object (of arbitrary size), and then emit it all at once later. The
+ buffering is done in an efficient way that works well for both very large
+ and very small objects.
+
+ This is used in two places:
+
+ = The implementation of multipart/alternative uses this code to do a
+ one-part-lookahead. As it traverses its children, it moves forward
+ until it finds a part which cannot be displayed; and then it displays
+ the *previous* part (the last which *could* be displayed.) This code
+ is used to hold the previous part until it is needed.
+*/
+
+/* An opaque object used to represent the buffered data.
+ */
+typedef struct MimePartBufferData MimePartBufferData;
+
+/* Create an empty part buffer object.
+ */
+extern MimePartBufferData *MimePartBufferCreate (void);
+
+/* Assert that the buffer is now full (EOF has been reached on the current
+ part.) This will free some resources, but leaves the part in the buffer.
+ After calling MimePartBufferReset, the buffer may be used to store a
+ different object.
+ */
+void MimePartBufferClose (MimePartBufferData *data);
+
+/* Reset a part buffer object to the default state, discarding any currently-
+ buffered data.
+ */
+extern void MimePartBufferReset (MimePartBufferData *data);
+
+/* Free the part buffer itself, and discard any buffered data.
+ */
+extern void MimePartBufferDestroy (MimePartBufferData *data);
+
+/* Push a chunk of a MIME object into the buffer.
+ */
+extern int MimePartBufferWrite (MimePartBufferData *data,
+ const char *buf, int32_t size);
+
+/* Read the contents of the buffer back out. This will invoke the provided
+ read_fn with successive chunks of data until the buffer has been drained.
+ The provided function may be called once, or multiple times.
+ */
+extern int
+MimePartBufferRead (MimePartBufferData *data,
+ MimeConverterOutputCallback read_fn,
+ void *closure);
+
+#endif /* _MIMEPBUF_H_ */
diff --git a/mailnews/mime/src/mimesun.cpp b/mailnews/mime/src/mimesun.cpp
new file mode 100644
index 000000000..84f06a885
--- /dev/null
+++ b/mailnews/mime/src/mimesun.cpp
@@ -0,0 +1,342 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimesun.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "nsMimeTypes.h"
+#include "msgCore.h"
+#include "nsMimeStringResources.h"
+#include <ctype.h>
+
+#define MIME_SUPERCLASS mimeMultipartClass
+MimeDefClass(MimeSunAttachment, MimeSunAttachmentClass,
+ mimeSunAttachmentClass, &MIME_SUPERCLASS);
+
+static MimeMultipartBoundaryType MimeSunAttachment_check_boundary(MimeObject *,
+ const char *,
+ int32_t);
+static int MimeSunAttachment_create_child(MimeObject *);
+static int MimeSunAttachment_parse_child_line (MimeObject *, const char *, int32_t,
+ bool);
+static int MimeSunAttachment_parse_begin (MimeObject *);
+static int MimeSunAttachment_parse_eof (MimeObject *, bool);
+
+static int
+MimeSunAttachmentClassInitialize(MimeSunAttachmentClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeMultipartClass *mclass = (MimeMultipartClass *) clazz;
+
+ PR_ASSERT(!oclass->class_initialized);
+ oclass->parse_begin = MimeSunAttachment_parse_begin;
+ oclass->parse_eof = MimeSunAttachment_parse_eof;
+ mclass->check_boundary = MimeSunAttachment_check_boundary;
+ mclass->create_child = MimeSunAttachment_create_child;
+ mclass->parse_child_line = MimeSunAttachment_parse_child_line;
+ return 0;
+}
+
+
+static int
+MimeSunAttachment_parse_begin (MimeObject *obj)
+{
+ int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+ if (status < 0) return status;
+
+ /* Sun messages always have separators at the beginning. */
+ return MimeObject_write_separator(obj);
+}
+
+static int
+MimeSunAttachment_parse_eof (MimeObject *obj, bool abort_p)
+{
+ int status = 0;
+
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0) return status;
+
+ /* Sun messages always have separators at the end. */
+ if (!abort_p)
+ {
+ status = MimeObject_write_separator(obj);
+ if (status < 0) return status;
+ }
+
+ return 0;
+}
+
+
+static MimeMultipartBoundaryType
+MimeSunAttachment_check_boundary(MimeObject *obj, const char *line,
+ int32_t length)
+{
+ /* ten dashes */
+
+ if (line &&
+ line[0] == '-' && line[1] == '-' && line[2] == '-' && line[3] == '-' &&
+ line[4] == '-' && line[5] == '-' && line[6] == '-' && line[7] == '-' &&
+ line[8] == '-' && line[9] == '-' &&
+ (line[10] == '\r' || line[10] == '\n'))
+ return MimeMultipartBoundaryTypeSeparator;
+ else
+ return MimeMultipartBoundaryTypeNone;
+}
+
+
+static int
+MimeSunAttachment_create_child(MimeObject *obj)
+{
+ MimeMultipart *mult = (MimeMultipart *) obj;
+ int status = 0;
+
+ char *sun_data_type = 0;
+ const char *mime_ct = 0, *sun_enc_info = 0, *mime_cte = 0;
+ char *mime_ct2 = 0; /* sometimes we need to copy; this is for freeing. */
+ MimeObject *child = 0;
+
+ mult->state = MimeMultipartPartLine;
+
+ sun_data_type = (mult->hdrs
+ ? MimeHeaders_get (mult->hdrs, HEADER_X_SUN_DATA_TYPE,
+ true, false)
+ : 0);
+ if (sun_data_type)
+ {
+ int i;
+ static const struct { const char *in, *out; } sun_types[] = {
+
+ /* Convert recognised Sun types to the corresponding MIME types,
+ and convert unrecognized ones based on the file extension and
+ the mime.types file.
+
+ These are the magic types used by MailTool that I can determine.
+ The only actual written spec I've found only listed the first few.
+ The rest were found by inspection (both of real-world messages,
+ and by running `strings' on the MailTool binary, and on the file
+ /usr/openwin/lib/cetables/cetables (the "Class Engine", Sun's
+ equivalent to .mailcap and mime.types.)
+ */
+ { "default", TEXT_PLAIN },
+ { "default-doc", TEXT_PLAIN },
+ { "text", TEXT_PLAIN },
+ { "scribe", TEXT_PLAIN },
+ { "sgml", TEXT_PLAIN },
+ { "tex", TEXT_PLAIN },
+ { "troff", TEXT_PLAIN },
+ { "c-file", TEXT_PLAIN },
+ { "h-file", TEXT_PLAIN },
+ { "readme-file", TEXT_PLAIN },
+ { "shell-script", TEXT_PLAIN },
+ { "cshell-script", TEXT_PLAIN },
+ { "makefile", TEXT_PLAIN },
+ { "hidden-docs", TEXT_PLAIN },
+ { "message", MESSAGE_RFC822 },
+ { "mail-message", MESSAGE_RFC822 },
+ { "mail-file", TEXT_PLAIN },
+ { "gif-file", IMAGE_GIF },
+ { "jpeg-file", IMAGE_JPG },
+ { "ppm-file", IMAGE_PPM },
+ { "pgm-file", "image/x-portable-graymap" },
+ { "pbm-file", "image/x-portable-bitmap" },
+ { "xpm-file", "image/x-xpixmap" },
+ { "ilbm-file", "image/ilbm" },
+ { "tiff-file", "image/tiff" },
+ { "photocd-file", "image/x-photo-cd" },
+ { "sun-raster", "image/x-sun-raster" },
+ { "audio-file", AUDIO_BASIC },
+ { "postscript", APPLICATION_POSTSCRIPT },
+ { "postscript-file", APPLICATION_POSTSCRIPT },
+ { "framemaker-document", "application/x-framemaker" },
+ { "sundraw-document", "application/x-sun-draw" },
+ { "sunpaint-document", "application/x-sun-paint" },
+ { "sunwrite-document", "application/x-sun-write" },
+ { "islanddraw-document", "application/x-island-draw" },
+ { "islandpaint-document", "application/x-island-paint" },
+ { "islandwrite-document", "application/x-island-write" },
+ { "sun-executable", APPLICATION_OCTET_STREAM },
+ { "default-app", APPLICATION_OCTET_STREAM },
+ { 0, 0 }};
+ for (i = 0; sun_types[i].in; i++)
+ if (!PL_strcasecmp(sun_data_type, sun_types[i].in))
+ {
+ mime_ct = sun_types[i].out;
+ break;
+ }
+ }
+
+ /* If we didn't find a type, look at the extension on the file name.
+ */
+ if (!mime_ct &&
+ obj->options &&
+ obj->options->file_type_fn)
+ {
+ char *name = MimeHeaders_get_name(mult->hdrs, obj->options);
+ if (name)
+ {
+ mime_ct2 = obj->options->file_type_fn(name,
+ obj->options->stream_closure);
+ mime_ct = mime_ct2;
+ PR_Free(name);
+ if (!mime_ct2 || !PL_strcasecmp (mime_ct2, UNKNOWN_CONTENT_TYPE))
+ {
+ PR_FREEIF(mime_ct2);
+ mime_ct = APPLICATION_OCTET_STREAM;
+ }
+ }
+ }
+ if (!mime_ct)
+ mime_ct = APPLICATION_OCTET_STREAM;
+
+ PR_FREEIF(sun_data_type);
+
+
+ /* Convert recognised Sun encodings to the corresponding MIME encodings.
+ However, if the X-Sun-Encoding-Info field contains more than one
+ encoding (that is, contains a comma) then assign it the encoding of
+ the *rightmost* element in the list; and change its Content-Type to
+ application/octet-stream. Examples:
+
+ Sun Type: Translates To:
+ ================== ====================
+ type: TEXT type: text/plain
+ encoding: COMPRESS encoding: x-compress
+
+ type: POSTSCRIPT type: application/x-compress
+ encoding: COMPRESS,UUENCODE encoding: x-uuencode
+
+ type: TEXT type: application/octet-stream
+ encoding: UNKNOWN,UUENCODE encoding: x-uuencode
+ */
+
+ sun_data_type = (mult->hdrs
+ ? MimeHeaders_get (mult->hdrs, HEADER_X_SUN_ENCODING_INFO,
+ false,false)
+ : 0);
+ sun_enc_info = sun_data_type;
+
+
+ /* this "adpcm-compress" pseudo-encoding is some random junk that
+ MailTool adds to the encoding description of .AU files: we can
+ ignore it if it is the leftmost element of the encoding field.
+ (It looks like it's created via `audioconvert -f g721'. Why?
+ Who knows.)
+ */
+ if (sun_enc_info && !PL_strncasecmp (sun_enc_info, "adpcm-compress", 14))
+ {
+ sun_enc_info += 14;
+ while (IS_SPACE(*sun_enc_info) || *sun_enc_info == ',')
+ sun_enc_info++;
+ }
+
+ /* Extract the last element of the encoding field, changing the content
+ type if necessary (as described above.)
+ */
+ if (sun_enc_info && *sun_enc_info)
+ {
+ const char *prev;
+ const char *end = PL_strrchr(sun_enc_info, ',');
+ if (end)
+ {
+ const char *start = sun_enc_info;
+ sun_enc_info = end + 1;
+ while (IS_SPACE(*sun_enc_info))
+ sun_enc_info++;
+ for (prev = end-1; prev > start && *prev != ','; prev--)
+ ;
+ if (*prev == ',') prev++;
+
+ if (!PL_strncasecmp (prev, "uuencode", end-prev))
+ mime_ct = APPLICATION_UUENCODE;
+ else if (!PL_strncasecmp (prev, "gzip", end-prev))
+ mime_ct = APPLICATION_GZIP;
+ else if (!PL_strncasecmp (prev, "compress", end-prev))
+ mime_ct = APPLICATION_COMPRESS;
+ else if (!PL_strncasecmp (prev, "default-compress", end-prev))
+ mime_ct = APPLICATION_COMPRESS;
+ else
+ mime_ct = APPLICATION_OCTET_STREAM;
+ }
+ }
+
+ /* Convert the remaining Sun encoding to a MIME encoding.
+ If it isn't known, change the content-type instead.
+ */
+ if (!sun_enc_info || !*sun_enc_info)
+ ;
+ else if (!PL_strcasecmp(sun_enc_info,"compress")) mime_cte = ENCODING_COMPRESS;
+ else if (!PL_strcasecmp(sun_enc_info,"uuencode")) mime_cte = ENCODING_UUENCODE;
+ else if (!PL_strcasecmp(sun_enc_info,"gzip")) mime_cte = ENCODING_GZIP;
+ else mime_ct = APPLICATION_OCTET_STREAM;
+
+ PR_FREEIF(sun_data_type);
+
+
+ /* Now that we know its type and encoding, create a MimeObject to represent
+ this part.
+ */
+ child = mime_create(mime_ct, mult->hdrs, obj->options);
+ if (!child)
+ {
+ status = MIME_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+
+ /* Fake out the child's content-type and encoding (it probably doesn't have
+ one right now, because the X-Sun- headers aren't generally recognised by
+ the rest of this library.)
+ */
+ PR_FREEIF(child->content_type);
+ PR_FREEIF(child->encoding);
+ PR_ASSERT(mime_ct);
+ child->content_type = (mime_ct ? strdup(mime_ct) : 0);
+ child->encoding = (mime_cte ? strdup(mime_cte) : 0);
+
+ status = ((MimeContainerClass *) obj->clazz)->add_child(obj, child);
+ if (status < 0)
+ {
+ mime_free(child);
+ child = 0;
+ goto FAIL;
+ }
+
+ /* Sun attachments always have separators between parts. */
+ status = MimeObject_write_separator(obj);
+ if (status < 0) goto FAIL;
+
+ /* And now that we've added this new object to our list of
+ children, start its parser going. */
+ status = child->clazz->parse_begin(child);
+ if (status < 0) goto FAIL;
+
+ FAIL:
+ PR_FREEIF(mime_ct2);
+ PR_FREEIF(sun_data_type);
+ return status;
+}
+
+
+static int
+MimeSunAttachment_parse_child_line (MimeObject *obj, const char *line, int32_t length,
+ bool first_line_p)
+{
+ MimeContainer *cont = (MimeContainer *) obj;
+ MimeObject *kid;
+
+ /* This is simpler than MimeMultipart->parse_child_line in that it doesn't
+ play games about body parts without trailing newlines.
+ */
+
+ PR_ASSERT(cont->nchildren > 0);
+ if (cont->nchildren <= 0)
+ return -1;
+
+ kid = cont->children[cont->nchildren-1];
+ PR_ASSERT(kid);
+ if (!kid) return -1;
+
+ return kid->clazz->parse_buffer (line, length, kid);
+}
diff --git a/mailnews/mime/src/mimesun.h b/mailnews/mime/src/mimesun.h
new file mode 100644
index 000000000..5ffd7dade
--- /dev/null
+++ b/mailnews/mime/src/mimesun.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMESUN_H_
+#define _MIMESUN_H_
+
+#include "mimemult.h"
+
+/* MimeSunAttachment is the class for X-Sun-Attachment message contents, which
+ is the Content-Type assigned by that pile of garbage called MailTool. This
+ is not a MIME type per se, but it's very similar to multipart/mixed, so it's
+ easy to parse. Lots of people use MailTool, so what the hell.
+
+ The format is this:
+
+ = Content-Type is X-Sun-Attachment
+ = parts are separated by lines of exactly ten dashes
+ = just after the dashes comes a block of headers, including:
+
+ X-Sun-Data-Type: (manditory)
+ Values are Text, Postscript, Scribe, SGML, TeX, Troff, DVI,
+ and Message.
+
+ X-Sun-Encoding-Info: (optional)
+ Ordered, comma-separated values, including Compress and Uuencode.
+
+ X-Sun-Data-Name: (optional)
+ File name, maybe.
+
+ X-Sun-Data-Description: (optional)
+ Longer text.
+
+ X-Sun-Content-Lines: (manditory, unless Length is present)
+ Number of lines in the body, not counting headers and the blank
+ line that follows them.
+
+ X-Sun-Content-Length: (manditory, unless Lines is present)
+ Bytes, presumably using Unix line terminators.
+ */
+
+typedef struct MimeSunAttachmentClass MimeSunAttachmentClass;
+typedef struct MimeSunAttachment MimeSunAttachment;
+
+struct MimeSunAttachmentClass {
+ MimeMultipartClass multipart;
+};
+
+extern MimeSunAttachmentClass mimeSunAttachmentClass;
+
+struct MimeSunAttachment {
+ MimeMultipart multipart;
+};
+
+#define MimeSunAttachmentClassInitializer(ITYPE,CSUPER) \
+ { MimeMultipartClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMESUN_H_ */
diff --git a/mailnews/mime/src/mimetenr.cpp b/mailnews/mime/src/mimetenr.cpp
new file mode 100644
index 000000000..4fecb6bcf
--- /dev/null
+++ b/mailnews/mime/src/mimetenr.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimetenr.h"
+#include "prlog.h"
+
+/* All the magic for this class is in mimetric.c; since text/enriched and
+ text/richtext are so similar, it was easiest to implement them in the
+ same method (but this is a subclass anyway just for general goodness.)
+ */
+
+#define MIME_SUPERCLASS mimeInlineTextRichtextClass
+MimeDefClass(MimeInlineTextEnriched, MimeInlineTextEnrichedClass,
+ mimeInlineTextEnrichedClass, &MIME_SUPERCLASS);
+
+static int
+MimeInlineTextEnrichedClassInitialize(MimeInlineTextEnrichedClass *clazz)
+{
+#ifdef DEBUG
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ PR_ASSERT(!oclass->class_initialized);
+#endif
+ MimeInlineTextRichtextClass *rclass = (MimeInlineTextRichtextClass *) clazz;
+ rclass->enriched_p = true;
+ return 0;
+}
diff --git a/mailnews/mime/src/mimetenr.h b/mailnews/mime/src/mimetenr.h
new file mode 100644
index 000000000..34608f547
--- /dev/null
+++ b/mailnews/mime/src/mimetenr.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMETENR_H_
+#define _MIMETENR_H_
+
+#include "mimetric.h"
+
+/* The MimeInlineTextEnriched class implements the text/enriched MIME content
+ type, as defined in RFC 1563. It does this largely by virtue of being a
+ subclass of the MimeInlineTextRichtext class.
+ */
+
+typedef struct MimeInlineTextEnrichedClass MimeInlineTextEnrichedClass;
+typedef struct MimeInlineTextEnriched MimeInlineTextEnriched;
+
+struct MimeInlineTextEnrichedClass {
+ MimeInlineTextRichtextClass text;
+};
+
+extern MimeInlineTextEnrichedClass mimeInlineTextEnrichedClass;
+
+struct MimeInlineTextEnriched {
+ MimeInlineTextRichtext richtext;
+};
+
+#define MimeInlineTextEnrichedClassInitializer(ITYPE,CSUPER) \
+ { MimeInlineTextRichtextClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMETENR_H_ */
diff --git a/mailnews/mime/src/mimetext.cpp b/mailnews/mime/src/mimetext.cpp
new file mode 100644
index 000000000..a854348c5
--- /dev/null
+++ b/mailnews/mime/src/mimetext.cpp
@@ -0,0 +1,544 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+#include "mimetext.h"
+#include "mimebuf.h"
+#include "mimethtm.h"
+#include "comi18n.h"
+#include "mimemoz2.h"
+
+#include "prlog.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIServiceManager.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsMsgUtils.h"
+#include "nsMimeTypes.h"
+#include "nsServiceManagerUtils.h"
+
+#define MIME_SUPERCLASS mimeLeafClass
+MimeDefClass(MimeInlineText, MimeInlineTextClass, mimeInlineTextClass,
+ &MIME_SUPERCLASS);
+
+static int MimeInlineText_initialize (MimeObject *);
+static void MimeInlineText_finalize (MimeObject *);
+static int MimeInlineText_rot13_line (MimeObject *, char *line, int32_t length);
+static int MimeInlineText_parse_eof (MimeObject *obj, bool abort_p);
+static int MimeInlineText_parse_end (MimeObject *, bool);
+static int MimeInlineText_parse_decoded_buffer (const char *, int32_t, MimeObject *);
+static int MimeInlineText_rotate_convert_and_parse_line(char *, int32_t,
+ MimeObject *);
+static int MimeInlineText_open_dam(char *line, int32_t length, MimeObject *obj);
+static int MimeInlineText_initializeCharset(MimeObject *obj);
+
+static int
+MimeInlineTextClassInitialize(MimeInlineTextClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ MimeLeafClass *lclass = (MimeLeafClass *) clazz;
+ PR_ASSERT(!oclass->class_initialized);
+ oclass->initialize = MimeInlineText_initialize;
+ oclass->finalize = MimeInlineText_finalize;
+ oclass->parse_eof = MimeInlineText_parse_eof;
+ oclass->parse_end = MimeInlineText_parse_end;
+ clazz->rot13_line = MimeInlineText_rot13_line;
+ clazz->initialize_charset = MimeInlineText_initializeCharset;
+ lclass->parse_decoded_buffer = MimeInlineText_parse_decoded_buffer;
+ return 0;
+}
+
+static int
+MimeInlineText_initialize (MimeObject *obj)
+{
+ /* This is an abstract class; it shouldn't be directly instantiated. */
+ PR_ASSERT(obj->clazz != (MimeObjectClass *) &mimeInlineTextClass);
+
+ ((MimeInlineText *) obj)->initializeCharset = false;
+ ((MimeInlineText *) obj)->needUpdateMsgWinCharset = false;
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj);
+}
+
+static int MimeInlineText_initializeCharset(MimeObject *obj)
+{
+ MimeInlineText *text = (MimeInlineText *) obj;
+
+ text->inputAutodetect = false;
+ text->charsetOverridable = false;
+
+ /* Figure out an appropriate charset for this object.
+ */
+ if (!text->charset && obj->headers)
+ {
+ if (obj->options && obj->options->override_charset)
+ {
+ text->charset = strdup(obj->options->default_charset);
+ }
+ else
+ {
+ char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE,
+ false, false);
+ if (ct)
+ {
+ text->charset = MimeHeaders_get_parameter (ct, "charset", NULL, NULL);
+ PR_Free(ct);
+ }
+
+ if (!text->charset)
+ {
+ /* If we didn't find "Content-Type: ...; charset=XX" then look
+ for "X-Sun-Charset: XX" instead. (Maybe this should be done
+ in MimeSunAttachmentClass, but it's harder there than here.)
+ */
+ text->charset = MimeHeaders_get (obj->headers,
+ HEADER_X_SUN_CHARSET,
+ false, false);
+ }
+
+ /* iMIP entities without an explicit charset parameter default to
+ US-ASCII (RFC 2447, section 2.4). However, Microsoft Outlook generates
+ UTF-8 but omits the charset parameter.
+ When no charset is defined by the container (e.g. iMIP), iCalendar
+ files default to UTF-8 (RFC 2445, section 4.1.4).
+ */
+ if (!text->charset &&
+ obj->content_type &&
+ !PL_strcasecmp(obj->content_type, TEXT_CALENDAR))
+ text->charset = strdup("UTF-8");
+
+ if (!text->charset)
+ {
+ nsresult res;
+
+ text->charsetOverridable = true;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &res));
+ if (NS_SUCCEEDED(res))
+ {
+ nsCOMPtr<nsIPrefLocalizedString> str;
+ if (NS_SUCCEEDED(prefBranch->GetComplexValue("intl.charset.detector", NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str)))) {
+ //only if we can get autodetector name correctly, do we set this to true
+ text->inputAutodetect = true;
+ }
+ }
+
+ if (obj->options && obj->options->default_charset)
+ text->charset = strdup(obj->options->default_charset);
+ else
+ {
+ if (NS_SUCCEEDED(res))
+ {
+ nsString value;
+ NS_GetLocalizedUnicharPreferenceWithDefault(prefBranch, "mailnews.view_default_charset", EmptyString(), value);
+ text->charset = ToNewUTF8String(value);
+ }
+ else
+ text->charset = strdup("");
+ }
+ }
+ }
+ }
+
+ if (text->inputAutodetect)
+ {
+ //we need to prepare lineDam for charset detection
+ text->lineDamBuffer = (char*)PR_Malloc(DAM_MAX_BUFFER_SIZE);
+ text->lineDamPtrs = (char**)PR_Malloc(DAM_MAX_LINES*sizeof(char*));
+ text->curDamOffset = 0;
+ text->lastLineInDam = 0;
+ if (!text->lineDamBuffer || !text->lineDamPtrs)
+ {
+ text->inputAutodetect = false;
+ PR_FREEIF(text->lineDamBuffer);
+ PR_FREEIF(text->lineDamPtrs);
+ }
+ }
+
+ text->initializeCharset = true;
+
+ return 0;
+}
+
+static void
+MimeInlineText_finalize (MimeObject *obj)
+{
+ MimeInlineText *text = (MimeInlineText *) obj;
+
+ obj->clazz->parse_eof (obj, false);
+ obj->clazz->parse_end (obj, false);
+
+ text->inputDecoder = nullptr;
+ text->utf8Encoder = nullptr;
+ PR_FREEIF(text->charset);
+
+ /* Should have been freed by parse_eof, but just in case... */
+ PR_ASSERT(!text->cbuffer);
+ PR_FREEIF (text->cbuffer);
+
+ if (text->inputAutodetect) {
+ PR_FREEIF(text->lineDamBuffer);
+ PR_FREEIF(text->lineDamPtrs);
+ text->inputAutodetect = false;
+ }
+
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (obj);
+}
+
+
+static int
+MimeInlineText_parse_eof (MimeObject *obj, bool abort_p)
+{
+ int status;
+
+ if (obj->closed_p) return 0;
+ NS_ASSERTION(!obj->parsed_p, "obj already parsed");
+
+ MimeInlineText *text = (MimeInlineText *) obj;
+
+ /* Flush any buffered data from the MimeLeaf's decoder */
+ status = ((MimeLeafClass*)&MIME_SUPERCLASS)->close_decoder(obj);
+ if (status < 0) return status;
+
+ /* If there is still data in the ibuffer, that means that the last
+ line of this part didn't end in a newline; so push it out anyway
+ (this means that the parse_line method will be called with a string
+ with no trailing newline, which isn't the usual case). We do this
+ here, rather than in MimeObject_parse_eof, because MimeObject isn't
+ aware of the rotating-and-converting / charset detection we need to
+ do first.
+ */
+ if (!abort_p && obj->ibuffer_fp > 0)
+ {
+ status = MimeInlineText_rotate_convert_and_parse_line (obj->ibuffer,
+ obj->ibuffer_fp,
+ obj);
+ obj->ibuffer_fp = 0;
+ if (status < 0)
+ {
+ //we haven't find charset yet? Do it before return
+ if (text->inputAutodetect)
+ status = MimeInlineText_open_dam(nullptr, 0, obj);
+
+ obj->closed_p = true;
+ return status;
+ }
+ }
+
+ //we haven't find charset yet? now its the time
+ if (text->inputAutodetect)
+ status = MimeInlineText_open_dam(nullptr, 0, obj);
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof (obj, abort_p);
+}
+
+static int
+MimeInlineText_parse_end (MimeObject *obj, bool abort_p)
+{
+ MimeInlineText *text = (MimeInlineText *) obj;
+
+ if (obj->parsed_p)
+ {
+ PR_ASSERT(obj->closed_p);
+ return 0;
+ }
+
+ /* We won't be needing this buffer any more; nuke it. */
+ PR_FREEIF(text->cbuffer);
+ text->cbuffer_size = 0;
+
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_end (obj, abort_p);
+}
+
+
+/* This maps A-M to N-Z and N-Z to A-M. All other characters are left alone.
+ (Comments in GNUS imply that for Japanese, one should rotate by 47?)
+ */
+static const unsigned char MimeInlineText_rot13_table[256] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58,
+ 59, 60, 61, 62, 63, 64, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 91, 92, 93, 94, 95, 96,
+ 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 97, 98,
+ 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 123, 124, 125, 126,
+ 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
+ 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156,
+ 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171,
+ 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186,
+ 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201,
+ 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216,
+ 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231,
+ 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246,
+ 247, 248, 249, 250, 251, 252, 253, 254, 255 };
+
+static int
+MimeInlineText_rot13_line (MimeObject *obj, char *line, int32_t length)
+{
+ unsigned char *s, *end;
+ PR_ASSERT(line);
+ if (!line) return -1;
+ s = (unsigned char *) line;
+ end = s + length;
+ while (s < end)
+ {
+ *s = MimeInlineText_rot13_table[*s];
+ s++;
+ }
+ return 0;
+}
+
+
+static int
+MimeInlineText_parse_decoded_buffer (const char *buf, int32_t size, MimeObject *obj)
+{
+ PR_ASSERT(!obj->closed_p);
+ if (obj->closed_p) return -1;
+
+ /* MimeLeaf takes care of this. */
+ PR_ASSERT(obj->output_p && obj->options && obj->options->output_fn);
+ if (!obj->options) return -1;
+
+ /* If we're supposed to write this object, but aren't supposed to convert
+ it to HTML, simply pass it through unaltered. */
+ if (!obj->options->write_html_p && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach)
+ return MimeObject_write(obj, buf, size, true);
+
+ /* This is just like the parse_decoded_buffer method we inherit from the
+ MimeLeaf class, except that we line-buffer to our own wrapper on the
+ `parse_line' method instead of calling the `parse_line' method directly.
+ */
+ return mime_LineBuffer (buf, size,
+ &obj->ibuffer, &obj->ibuffer_size, &obj->ibuffer_fp,
+ true,
+ ((int (*) (char *, int32_t, void *))
+ /* This cast is to turn void into MimeObject */
+ MimeInlineText_rotate_convert_and_parse_line),
+ obj);
+}
+
+
+#define MimeInlineText_grow_cbuffer(text, desired_size) \
+ (((desired_size) >= (text)->cbuffer_size) ? \
+ mime_GrowBuffer ((desired_size), sizeof(char), 100, \
+ &(text)->cbuffer, &(text)->cbuffer_size) \
+ : 0)
+
+static int
+MimeInlineText_convert_and_parse_line(char *line, int32_t length, MimeObject *obj)
+{
+ int status;
+ char *converted = 0;
+ int32_t converted_len = 0;
+
+ MimeInlineText *text = (MimeInlineText *) obj;
+
+ //in case of charset autodetection, charset can be override by meta charset
+ if (text->charsetOverridable) {
+ if (mime_typep(obj, (MimeObjectClass *) &mimeInlineTextHTMLClass))
+ {
+ MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj;
+ if (textHTML->charset &&
+ *textHTML->charset &&
+ strcmp(textHTML->charset, text->charset))
+ {
+ //if meta tag specified charset is different from our detected result, use meta charset.
+ //but we don't want to redo previous lines
+ MIME_get_unicode_decoder(textHTML->charset, getter_AddRefs(text->inputDecoder));
+ PR_FREEIF(text->charset);
+ text->charset = strdup(textHTML->charset);
+
+ //update MsgWindow charset if we are instructed to do so
+ if (text->needUpdateMsgWinCharset && *text->charset)
+ SetMailCharacterSetToMsgWindow(obj, text->charset);
+ }
+ }
+ }
+
+ //initiate decoder if not yet
+ if (text->inputDecoder == nullptr)
+ MIME_get_unicode_decoder(text->charset, getter_AddRefs(text->inputDecoder));
+ // If no decoder found, use ""UTF-8"", that will map most non-US-ASCII chars as invalid
+ // A pure-ASCII only decoder would be better, but there is none
+ if (text->inputDecoder == nullptr)
+ MIME_get_unicode_decoder("UTF-8", getter_AddRefs(text->inputDecoder));
+ if (text->utf8Encoder == nullptr)
+ MIME_get_unicode_encoder("UTF-8", getter_AddRefs(text->utf8Encoder));
+
+ bool useInputCharsetConverter = obj->options->m_inputCharsetToUnicodeDecoder && !PL_strcasecmp(text->charset, obj->options->charsetForCachedInputDecoder.get());
+
+ if (useInputCharsetConverter)
+ status = obj->options->charset_conversion_fn(line, length,
+ text->charset,
+ "UTF-8",
+ &converted,
+ &converted_len,
+ obj->options->stream_closure, obj->options->m_inputCharsetToUnicodeDecoder,
+ obj->options->m_unicodeToUTF8Encoder);
+ else
+ status = obj->options->charset_conversion_fn(line, length,
+ text->charset,
+ "UTF-8",
+ &converted,
+ &converted_len,
+ obj->options->stream_closure, (nsIUnicodeDecoder*)text->inputDecoder,
+ (nsIUnicodeEncoder*)text->utf8Encoder);
+
+ if (status < 0)
+ {
+ PR_FREEIF(converted);
+ return status;
+ }
+
+ if (converted)
+ {
+ line = converted;
+ length = converted_len;
+ }
+
+ /* Now that the line has been converted, call the subclass's parse_line
+ method with the decoded data. */
+ status = obj->clazz->parse_line(line, length, obj);
+ PR_FREEIF(converted);
+
+ return status;
+}
+
+//In this function call, all buffered lines in lineDam will be sent to charset detector
+// and a charset will be used to parse all those line and following lines in this mime obj.
+static int
+MimeInlineText_open_dam(char *line, int32_t length, MimeObject *obj)
+{
+ MimeInlineText *text = (MimeInlineText *) obj;
+ const char* detectedCharset = nullptr;
+ nsresult res = NS_OK;
+ int status = 0;
+ int32_t i;
+
+ if (text->curDamOffset <= 0) {
+ //there is nothing in dam, use current line for detection
+ if (length > 0) {
+ res = MIME_detect_charset(line, length, &detectedCharset);
+ }
+ } else {
+ //we have stuff in dam, use the one
+ res = MIME_detect_charset(text->lineDamBuffer, text->curDamOffset, &detectedCharset);
+ }
+
+ //set the charset for this obj
+ if (NS_SUCCEEDED(res) && detectedCharset && *detectedCharset) {
+ PR_FREEIF(text->charset);
+ text->charset = strdup(detectedCharset);
+
+ //update MsgWindow charset if we are instructed to do so
+ if (text->needUpdateMsgWinCharset && *text->charset)
+ SetMailCharacterSetToMsgWindow(obj, text->charset);
+ }
+
+ //process dam and line using the charset
+ if (text->curDamOffset) {
+ for (i = 0; i < text->lastLineInDam-1; i++)
+ {
+ status = MimeInlineText_convert_and_parse_line(
+ text->lineDamPtrs[i],
+ text->lineDamPtrs[i+1] - text->lineDamPtrs[i],
+ obj );
+ }
+ status = MimeInlineText_convert_and_parse_line(
+ text->lineDamPtrs[i],
+ text->lineDamBuffer + text->curDamOffset - text->lineDamPtrs[i],
+ obj );
+ }
+
+ if (length)
+ status = MimeInlineText_convert_and_parse_line(line, length, obj);
+
+ PR_Free(text->lineDamPtrs);
+ PR_Free(text->lineDamBuffer);
+ text->lineDamPtrs = nullptr;
+ text->lineDamBuffer = nullptr;
+ text->inputAutodetect = false;
+
+ return status;
+}
+
+
+static int
+MimeInlineText_rotate_convert_and_parse_line(char *line, int32_t length,
+ MimeObject *obj)
+{
+ int status = 0;
+ MimeInlineTextClass *textc = (MimeInlineTextClass *) obj->clazz;
+
+ PR_ASSERT(!obj->closed_p);
+ if (obj->closed_p) return -1;
+
+ /* Rotate the line, if desired (this happens on the raw data, before any
+ charset conversion.) */
+ if (obj->options && obj->options->rot13_p)
+ {
+ status = textc->rot13_line(obj, line, length);
+ if (status < 0) return status;
+ }
+
+ // Now convert to the canonical charset, if desired.
+ //
+ bool doConvert = true;
+ // Don't convert vCard data
+ if ( ( (obj->content_type) && (!PL_strcasecmp(obj->content_type, TEXT_VCARD)) ) ||
+ (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs)
+ || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach)
+ doConvert = false;
+
+ // Only convert if the user prefs is false
+ if ( (obj->options && obj->options->charset_conversion_fn) &&
+ (!obj->options->force_user_charset) &&
+ (doConvert)
+ )
+ {
+ MimeInlineText *text = (MimeInlineText *) obj;
+
+ if (!text->initializeCharset)
+ {
+ MimeInlineText_initializeCharset(obj);
+ //update MsgWindow charset if we are instructed to do so
+ if (text->needUpdateMsgWinCharset && *text->charset)
+ SetMailCharacterSetToMsgWindow(obj, text->charset);
+ }
+
+ //if autodetect is on, push line to dam
+ if (text->inputAutodetect)
+ {
+ //see if we reach the lineDam buffer limit, if so, there is no need to keep buffering
+ if (text->lastLineInDam >= DAM_MAX_LINES ||
+ DAM_MAX_BUFFER_SIZE - text->curDamOffset <= length) {
+ //we let open dam process this line as well as thing that already in Dam
+ //In case there is nothing in dam because this line is too big, we need to
+ //perform autodetect on this line
+ status = MimeInlineText_open_dam(line, length, obj);
+ }
+ else {
+ //buffering current line
+ text->lineDamPtrs[text->lastLineInDam] = text->lineDamBuffer + text->curDamOffset;
+ memcpy(text->lineDamPtrs[text->lastLineInDam], line, length);
+ text->lastLineInDam++;
+ text->curDamOffset += length;
+ }
+ }
+ else
+ status = MimeInlineText_convert_and_parse_line(line, length, obj);
+ }
+ else
+ status = obj->clazz->parse_line(line, length, obj);
+
+ return status;
+}
diff --git a/mailnews/mime/src/mimetext.h b/mailnews/mime/src/mimetext.h
new file mode 100644
index 000000000..78cb6bf3a
--- /dev/null
+++ b/mailnews/mime/src/mimetext.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMETEXT_H_
+#define _MIMETEXT_H_
+
+#include "mimeleaf.h"
+
+/* The MimeInlineText class is the superclass of all handlers for the
+ MIME text/ content types (which convert various text formats to HTML,
+ in one form or another.)
+
+ It provides two services:
+
+ = if ROT13 decoding is desired, the text will be rotated before
+ the `parse_line' method it called;
+
+ = text will be converted from the message's charset to the "target"
+ charset before the `parse_line' method is called.
+
+ The contract with charset-conversion is that the converted data will
+ be such that one may interpret any octets (8-bit bytes) in the data
+ which are in the range of the ASCII characters (0-127) as ASCII
+ characters. It is explicitly legal, for example, to scan through
+ the string for "<" and replace it with "&lt;", and to search for things
+ that look like URLs and to wrap them with interesting HTML tags.
+
+ The charset to which we convert will probably be UTF-8 (an encoding of
+ the Unicode character set, with the feature that all octets with the
+ high bit off have the same interpretations as ASCII.)
+
+ #### NOTE: if it turns out that we use JIS (ISO-2022-JP) as the target
+ encoding, then this is not quite true; it is safe to search for the
+ low ASCII values (under hex 0x40, octal 0100, which is '@') but it
+ is NOT safe to search for values higher than that -- they may be
+ being used as the subsequent bytes in a multi-byte escape sequence.
+ It's a nice coincidence that HTML's critical characters ("<", ">",
+ and "&") have values under 0x40...
+ */
+
+typedef struct MimeInlineTextClass MimeInlineTextClass;
+typedef struct MimeInlineText MimeInlineText;
+
+struct MimeInlineTextClass {
+ MimeLeafClass leaf;
+ int (*rot13_line) (MimeObject *obj, char *line, int32_t length);
+ int (*convert_line_charset) (MimeObject *obj, char *line, int32_t length);
+ int (*initialize_charset) (MimeObject *obj);
+};
+
+extern MimeInlineTextClass mimeInlineTextClass;
+
+#define DAM_MAX_BUFFER_SIZE 8*1024
+#define DAM_MAX_LINES 1024
+
+struct MimeInlineText {
+ MimeLeaf leaf; /* superclass variables */
+ char *charset; /* The charset from the content-type of this
+ object, or the caller-specified overrides
+ or defaults. */
+ bool charsetOverridable;
+ bool needUpdateMsgWinCharset;
+ char *cbuffer; /* Buffer used for charset conversion. */
+ int32_t cbuffer_size;
+
+ nsCOMPtr<nsIUnicodeDecoder> inputDecoder;
+ nsCOMPtr<nsIUnicodeEncoder> utf8Encoder;
+
+ bool inputAutodetect;
+ bool initializeCharset;
+ int32_t lastLineInDam;
+ int32_t curDamOffset;
+ char *lineDamBuffer;
+ char **lineDamPtrs;
+};
+
+#define MimeInlineTextClassInitializer(ITYPE,CSUPER) \
+ { MimeLeafClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMETEXT_H_ */
diff --git a/mailnews/mime/src/mimethpl.cpp b/mailnews/mime/src/mimethpl.cpp
new file mode 100644
index 000000000..9d01229f9
--- /dev/null
+++ b/mailnews/mime/src/mimethpl.cpp
@@ -0,0 +1,165 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* TODO:
+ - If you Save As File .html with this mode, you get a total mess.
+ - Print is untested (crashes in all modes).
+*/
+/* If you fix a bug here, check, if the same is also in mimethsa, because that
+ class is based on this class. */
+
+#include "mimethpl.h"
+#include "prlog.h"
+#include "msgCore.h"
+#include "mimemoz2.h"
+#include "nsStringGlue.h"
+#include "nsIDocumentEncoder.h" // for output flags
+
+#define MIME_SUPERCLASS mimeInlineTextPlainClass
+/* I should use the Flowed class as base (because our HTML->TXT converter
+ can generate flowed, and we tell it to) - this would get a bit nicer
+ rendering. However, that class is more picky about line endings
+ and I currently don't feel like splitting up the generated plaintext
+ into separate lines again. So, I just throw the whole message at once
+ at the TextPlain_parse_line function - it happens to work *g*. */
+MimeDefClass(MimeInlineTextHTMLAsPlaintext, MimeInlineTextHTMLAsPlaintextClass,
+ mimeInlineTextHTMLAsPlaintextClass, &MIME_SUPERCLASS);
+
+static int MimeInlineTextHTMLAsPlaintext_parse_line (const char *, int32_t,
+ MimeObject *);
+static int MimeInlineTextHTMLAsPlaintext_parse_begin (MimeObject *obj);
+static int MimeInlineTextHTMLAsPlaintext_parse_eof (MimeObject *, bool);
+static void MimeInlineTextHTMLAsPlaintext_finalize (MimeObject *obj);
+
+static int
+MimeInlineTextHTMLAsPlaintextClassInitialize(MimeInlineTextHTMLAsPlaintextClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ NS_ASSERTION(!oclass->class_initialized, "problem with superclass");
+ oclass->parse_line = MimeInlineTextHTMLAsPlaintext_parse_line;
+ oclass->parse_begin = MimeInlineTextHTMLAsPlaintext_parse_begin;
+ oclass->parse_eof = MimeInlineTextHTMLAsPlaintext_parse_eof;
+ oclass->finalize = MimeInlineTextHTMLAsPlaintext_finalize;
+
+ return 0;
+}
+
+static int
+MimeInlineTextHTMLAsPlaintext_parse_begin (MimeObject *obj)
+{
+ MimeInlineTextHTMLAsPlaintext *textHTMLPlain =
+ (MimeInlineTextHTMLAsPlaintext *) obj;
+ textHTMLPlain->complete_buffer = new nsString();
+ // Let's just hope that libmime won't have the idea to call begin twice...
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+}
+
+static int
+MimeInlineTextHTMLAsPlaintext_parse_eof (MimeObject *obj, bool abort_p)
+{
+ if (obj->closed_p)
+ return 0;
+
+ // This is a hack. We need to call parse_eof() of the super class to flush out any buffered data.
+ // We can't call it yet for our direct super class, because it would "close" the output
+ // (write tags such as </pre> and </div>). We'll do that after parsing the buffer.
+ int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->superclass->parse_eof(obj, abort_p);
+ if (status < 0)
+ return status;
+
+ MimeInlineTextHTMLAsPlaintext *textHTMLPlain =
+ (MimeInlineTextHTMLAsPlaintext *) obj;
+
+ if (!textHTMLPlain || !textHTMLPlain->complete_buffer)
+ return 0;
+
+ nsString& cb = *(textHTMLPlain->complete_buffer);
+
+ // could be empty, e.g., if part isn't actually being displayed
+ if (cb.Length())
+ {
+ nsString asPlaintext;
+ uint32_t flags = nsIDocumentEncoder::OutputFormatted
+ | nsIDocumentEncoder::OutputWrap
+ | nsIDocumentEncoder::OutputFormatFlowed
+ | nsIDocumentEncoder::OutputLFLineBreak
+ | nsIDocumentEncoder::OutputNoScriptContent
+ | nsIDocumentEncoder::OutputNoFramesContent
+ | nsIDocumentEncoder::OutputBodyOnly;
+ HTML2Plaintext(cb, asPlaintext, flags, 80);
+
+ NS_ConvertUTF16toUTF8 resultCStr(asPlaintext);
+ // TODO parse each line independently
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_line(
+ resultCStr.BeginWriting(),
+ resultCStr.Length(),
+ obj);
+ cb.Truncate();
+ }
+
+ if (status < 0)
+ return status;
+
+ // Second part of the flush hack. Pretend obj wasn't closed yet, so that our super class
+ // gets a chance to write the closing.
+ bool save_closed_p = obj->closed_p;
+ obj->closed_p = false;
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ // Restore closed_p.
+ obj->closed_p = save_closed_p;
+ return status;
+}
+
+void
+MimeInlineTextHTMLAsPlaintext_finalize (MimeObject *obj)
+{
+ MimeInlineTextHTMLAsPlaintext *textHTMLPlain =
+ (MimeInlineTextHTMLAsPlaintext *) obj;
+ if (textHTMLPlain && textHTMLPlain->complete_buffer)
+ {
+ // If there's content in the buffer, make sure that we output it.
+ // don't care about return codes
+ obj->clazz->parse_eof(obj, false);
+
+ delete textHTMLPlain->complete_buffer;
+ textHTMLPlain->complete_buffer = NULL;
+ /* It is important to zero the pointer, so we can reliably check for
+ the validity of it in the other functions. See above. */
+ }
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (obj);
+}
+
+static int
+MimeInlineTextHTMLAsPlaintext_parse_line (const char *line, int32_t length,
+ MimeObject *obj)
+{
+ MimeInlineTextHTMLAsPlaintext *textHTMLPlain =
+ (MimeInlineTextHTMLAsPlaintext *) obj;
+
+ if (!textHTMLPlain || !(textHTMLPlain->complete_buffer))
+ {
+#if DEBUG
+printf("Can't output: %s\n", line);
+#endif
+ return -1;
+ }
+
+ /*
+ To convert HTML->TXT syncronously, I need the full source at once,
+ not line by line (how do you convert "<li>foo\n" to plaintext?).
+ parse_decoded_buffer claims to give me that, but in fact also gives
+ me single lines.
+ It might be theoretically possible to drive this asyncronously, but
+ I don't know, which odd circumstances might arise and how libmime
+ will behave then. It's not worth the trouble for me to figure this all out.
+ */
+ nsCString linestr(line, length);
+ NS_ConvertUTF8toUTF16 line_ucs2(linestr.get());
+ if (length && line_ucs2.IsEmpty())
+ CopyASCIItoUTF16 (linestr, line_ucs2);
+ (textHTMLPlain->complete_buffer)->Append(line_ucs2);
+
+ return 0;
+}
diff --git a/mailnews/mime/src/mimethpl.h b/mailnews/mime/src/mimethpl.h
new file mode 100644
index 000000000..fc302f4f1
--- /dev/null
+++ b/mailnews/mime/src/mimethpl.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The MimeInlineTextHTMLAsPlaintext class converts HTML->TXT->HTML, i.e.
+ HTML to Plaintext and the result to HTML again.
+ This might sound crazy, maybe it is, but it is for the "View as Plaintext"
+ option, if the sender didn't supply a plaintext alternative (bah!).
+ */
+
+#ifndef _MIMETHPL_H_
+#define _MIMETHPL_H_
+
+#include "mimetpla.h"
+#include "nsStringGlue.h"
+
+typedef struct MimeInlineTextHTMLAsPlaintextClass MimeInlineTextHTMLAsPlaintextClass;
+typedef struct MimeInlineTextHTMLAsPlaintext MimeInlineTextHTMLAsPlaintext;
+
+struct MimeInlineTextHTMLAsPlaintextClass {
+ MimeInlineTextPlainClass plaintext;
+};
+
+extern MimeInlineTextHTMLAsPlaintextClass mimeInlineTextHTMLAsPlaintextClass;
+
+struct MimeInlineTextHTMLAsPlaintext {
+ MimeInlineTextPlain plaintext;
+ nsString *complete_buffer; // Gecko parser expects wide strings
+};
+
+#define MimeInlineTextHTMLAsPlaintextClassInitializer(ITYPE,CSUPER) \
+ { MimeInlineTextPlainClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMETHPL_H_ */
diff --git a/mailnews/mime/src/mimethsa.cpp b/mailnews/mime/src/mimethsa.cpp
new file mode 100644
index 000000000..58441dee0
--- /dev/null
+++ b/mailnews/mime/src/mimethsa.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Most of this code is copied from mimethpl; see there for source comments.
+ If you find a bug here, check that class, too.
+*/
+
+/* The MimeInlineTextHTMLSanitized class cleans up HTML
+
+ This removes offending HTML features that have no business in mail.
+ It is a low-level stop gap for many classes of attacks,
+ and intended for security conscious users.
+ Paranoia is a feature here, and has served very well in practice.
+
+ It has already prevented countless serious exploits.
+
+ It pushes the HTML that we get from the sender of the message
+ through a sanitizer (nsTreeSanitizer), which lets only allowed tags through.
+ With the appropriate configuration, this protects from most of the
+ security and visual-formatting problems that otherwise usually come with HTML
+ (and which partly gave HTML in email the bad reputation that it has).
+
+ However, due to the parsing and serializing (and later parsing again)
+ required, there is an inherent, significant performance hit, when doing the
+ santinizing here at the MIME / HTML source level. But users of this class
+ will most likely find it worth the cost.
+ */
+
+#include "mimethsa.h"
+#include "prmem.h"
+#include "prlog.h"
+#include "msgCore.h"
+#include "mimemoz2.h"
+#include "nsIPrefBranch.h"
+#include "mimethtm.h"
+
+#define MIME_SUPERCLASS mimeInlineTextHTMLClass
+MimeDefClass(MimeInlineTextHTMLSanitized, MimeInlineTextHTMLSanitizedClass,
+ mimeInlineTextHTMLSanitizedClass, &MIME_SUPERCLASS);
+
+static int MimeInlineTextHTMLSanitized_parse_line(const char *, int32_t,
+ MimeObject *);
+static int MimeInlineTextHTMLSanitized_parse_begin(MimeObject *obj);
+static int MimeInlineTextHTMLSanitized_parse_eof(MimeObject *, bool);
+static void MimeInlineTextHTMLSanitized_finalize(MimeObject *obj);
+
+static int
+MimeInlineTextHTMLSanitizedClassInitialize(MimeInlineTextHTMLSanitizedClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *)clazz;
+ NS_ASSERTION(!oclass->class_initialized, "problem with superclass");
+ oclass->parse_line = MimeInlineTextHTMLSanitized_parse_line;
+ oclass->parse_begin = MimeInlineTextHTMLSanitized_parse_begin;
+ oclass->parse_eof = MimeInlineTextHTMLSanitized_parse_eof;
+ oclass->finalize = MimeInlineTextHTMLSanitized_finalize;
+
+ return 0;
+}
+
+static int
+MimeInlineTextHTMLSanitized_parse_begin(MimeObject *obj)
+{
+ MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj;
+ me->complete_buffer = new nsString();
+ int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+ if (status < 0)
+ return status;
+
+ return 0;
+}
+
+static int
+MimeInlineTextHTMLSanitized_parse_eof(MimeObject *obj, bool abort_p)
+{
+ if (obj->closed_p)
+ return 0;
+ int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0)
+ return status;
+ MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj;
+
+ // We have to cache all lines and parse the whole document at once.
+ // There's a useful sounding function parseFromStream(), but it only allows XML
+ // mimetypes, not HTML. Methinks that's because the HTML soup parser
+ // needs the entire doc to make sense of the gibberish that people write.
+ if (!me || !me->complete_buffer)
+ return 0;
+
+ nsString& cb = *(me->complete_buffer);
+ if (cb.IsEmpty())
+ return 0;
+ nsString sanitized;
+
+ // Sanitize.
+ HTMLSanitize(cb, sanitized);
+
+ // Write it out.
+ NS_ConvertUTF16toUTF8 resultCStr(sanitized);
+ MimeInlineTextHTML_insert_lang_div(obj, resultCStr);
+ // Call to MimeInlineTextHTML_remove_plaintext_tag() not needed since
+ // sanitization already removes that tag.
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_line(
+ resultCStr.BeginWriting(),
+ resultCStr.Length(),
+ obj);
+ cb.Truncate();
+ return status;
+}
+
+void
+MimeInlineTextHTMLSanitized_finalize(MimeObject *obj)
+{
+ MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj;
+
+ if (me && me->complete_buffer)
+ {
+ obj->clazz->parse_eof(obj, false);
+ delete me->complete_buffer;
+ me->complete_buffer = NULL;
+ }
+
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj);
+}
+
+static int
+MimeInlineTextHTMLSanitized_parse_line(const char *line, int32_t length,
+ MimeObject *obj)
+{
+ MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj;
+
+ if (!me || !(me->complete_buffer))
+ return -1;
+
+ nsCString linestr(line, length);
+ NS_ConvertUTF8toUTF16 line_ucs2(linestr.get());
+ if (length && line_ucs2.IsEmpty())
+ CopyASCIItoUTF16(linestr, line_ucs2);
+ (me->complete_buffer)->Append(line_ucs2);
+
+ return 0;
+}
diff --git a/mailnews/mime/src/mimethsa.h b/mailnews/mime/src/mimethsa.h
new file mode 100644
index 000000000..42a7f81b4
--- /dev/null
+++ b/mailnews/mime/src/mimethsa.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMETHSA_H_
+#define _MIMETHSA_H_
+
+#include "mimethtm.h"
+
+typedef struct MimeInlineTextHTMLSanitizedClass MimeInlineTextHTMLSanitizedClass;
+typedef struct MimeInlineTextHTMLSanitized MimeInlineTextHTMLSanitized;
+
+struct MimeInlineTextHTMLSanitizedClass {
+ MimeInlineTextHTMLClass html;
+};
+
+extern MimeInlineTextHTMLSanitizedClass mimeInlineTextHTMLSanitizedClass;
+
+struct MimeInlineTextHTMLSanitized {
+ MimeInlineTextHTML html;
+ nsString *complete_buffer; // Gecko parser expects wide strings
+};
+
+#define MimeInlineTextHTMLSanitizedClassInitializer(ITYPE,CSUPER) \
+ { MimeInlineTextHTMLClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMETHPL_H_ */
diff --git a/mailnews/mime/src/mimethtm.cpp b/mailnews/mime/src/mimethtm.cpp
new file mode 100644
index 000000000..edf39b35e
--- /dev/null
+++ b/mailnews/mime/src/mimethtm.cpp
@@ -0,0 +1,254 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mimethtm.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "prprf.h"
+#include "msgCore.h"
+#include "nsMimeStringResources.h"
+#include "mimemoz2.h"
+#include <ctype.h>
+
+#define MIME_SUPERCLASS mimeInlineTextClass
+MimeDefClass(MimeInlineTextHTML, MimeInlineTextHTMLClass,
+ mimeInlineTextHTMLClass, &MIME_SUPERCLASS);
+
+static int MimeInlineTextHTML_parse_line (const char *, int32_t, MimeObject *);
+static int MimeInlineTextHTML_parse_eof (MimeObject *, bool);
+static int MimeInlineTextHTML_parse_begin (MimeObject *obj);
+
+static int
+MimeInlineTextHTMLClassInitialize(MimeInlineTextHTMLClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ PR_ASSERT(!oclass->class_initialized);
+ oclass->parse_begin = MimeInlineTextHTML_parse_begin;
+ oclass->parse_line = MimeInlineTextHTML_parse_line;
+ oclass->parse_eof = MimeInlineTextHTML_parse_eof;
+
+ return 0;
+}
+
+static int
+MimeInlineTextHTML_parse_begin (MimeObject *obj)
+{
+ int status = ((MimeObjectClass*)&mimeLeafClass)->parse_begin(obj);
+ if (status < 0) return status;
+
+ if (!obj->output_p) return 0;
+
+ status = MimeObject_write_separator(obj);
+ if (status < 0) return status;
+
+ MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj;
+
+ textHTML->charset = nullptr;
+
+ /* If this HTML part has a Content-Base header, and if we're displaying
+ to the screen (that is, not writing this part "raw") then translate
+ that Content-Base header into a <BASE> tag in the HTML.
+ */
+ if (obj->options &&
+ obj->options->write_html_p &&
+ obj->options->output_fn)
+ {
+ char *base_hdr = MimeHeaders_get (obj->headers, HEADER_CONTENT_BASE,
+ false, false);
+
+ /* rhp - for MHTML Spec changes!!! */
+ if (!base_hdr)
+ {
+ base_hdr = MimeHeaders_get (obj->headers, HEADER_CONTENT_LOCATION, false, false);
+ }
+ /* rhp - for MHTML Spec changes!!! */
+
+ if (base_hdr)
+ {
+ uint32_t buflen = strlen(base_hdr) + 20;
+ char *buf = (char *) PR_MALLOC(buflen);
+ const char *in;
+ char *out;
+ if (!buf)
+ return MIME_OUT_OF_MEMORY;
+
+ /* The value of the Content-Base header is a number of "words".
+ Whitespace in this header is not significant -- it is assumed
+ that any real whitespace in the URL has already been encoded,
+ and whitespace has been inserted to allow the lines in the
+ mail header to be wrapped reasonably. Creators are supposed
+ to insert whitespace every 40 characters or less.
+ */
+ PL_strncpyz(buf, "<BASE HREF=\"", buflen);
+ out = buf + strlen(buf);
+
+ for (in = base_hdr; *in; in++)
+ /* ignore whitespace and quotes */
+ if (!IS_SPACE(*in) && *in != '"')
+ *out++ = *in;
+
+ /* Close the tag and argument. */
+ *out++ = '"';
+ *out++ = '>';
+ *out++ = 0;
+
+ PR_Free(base_hdr);
+
+ status = MimeObject_write(obj, buf, strlen(buf), false);
+ PR_Free(buf);
+ if (status < 0) return status;
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+MimeInlineTextHTML_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj;
+
+ if (!obj->output_p)
+ return 0;
+
+ if (!obj->options || !obj->options->output_fn)
+ return 0;
+
+ if (!textHTML->charset)
+ {
+ char * cp;
+ // First, try to detect a charset via a META tag!
+ if ((cp = PL_strncasestr(line, "META", length)) &&
+ (cp = PL_strncasestr(cp, "HTTP-EQUIV=", length - (int)(cp - line))) &&
+ (cp = PL_strncasestr(cp, "CONTENT=", length - (int)(cp - line))) &&
+ (cp = PL_strncasestr(cp, "CHARSET=", length - (int)(cp - line)))
+ )
+ {
+ char* cp1 = cp + 8; //8 for the length of "CHARSET="
+ char* cp2 = PL_strnpbrk(cp1, " \"\'", length - (int)(cp1 - line));
+ if (cp2)
+ {
+ char* charset = PL_strndup(cp1, (int)(cp2 - cp1));
+
+ // Fix bug 101434, in this case since this parsing is a char*
+ // operation, a real UTF-16 or UTF-32 document won't be parse
+ // correctly, if it got parse, it cannot be UTF-16 nor UTF-32
+ // there fore, we ignore them if somehow we got that value
+ // 6 == strlen("UTF-16") or strlen("UTF-32"), this will cover
+ // UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE
+ if ((charset != nullptr) &&
+ PL_strncasecmp(charset, "UTF-16", 6) &&
+ PL_strncasecmp(charset, "UTF-32", 6))
+ {
+ textHTML->charset = charset;
+
+ // write out the data without the charset part...
+ if (textHTML->charset)
+ {
+ int err = MimeObject_write(obj, line, cp - line, true);
+ if (err == 0)
+ err = MimeObject_write(obj, cp2, length - (int)(cp2 - line), true);
+
+ return err;
+ }
+ }
+ PR_FREEIF(charset);
+ }
+ }
+ }
+
+ // Now, just write out the data...
+ return MimeObject_write(obj, line, length, true);
+}
+
+static int
+MimeInlineTextHTML_parse_eof (MimeObject *obj, bool abort_p)
+{
+ int status;
+ MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj;
+ if (obj->closed_p) return 0;
+
+ PR_FREEIF(textHTML->charset);
+
+ /* Run parent method first, to flush out any buffered data. */
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0) return status;
+
+ return 0;
+}
+
+/*
+ * The following function adds <div class="moz-text-html" lang="..."> or
+ * <div class="moz-text-html"> as the first tag following the <body> tag in the
+ * serialised HTML of a message. This becomes a no-op if no <body> tag is found.
+ */
+void
+MimeInlineTextHTML_insert_lang_div(MimeObject *obj, nsCString &message)
+{
+ if (obj->options->format_out != nsMimeOutput::nsMimeMessageBodyDisplay &&
+ obj->options->format_out != nsMimeOutput::nsMimeMessagePrintOutput)
+ return;
+
+ // Make sure we have a <body> before we start.
+ int32_t index = message.Find("<body", /* ignoreCase = */ true);
+ if (index == kNotFound)
+ return;
+ index = message.FindChar('>', index) + 1;
+
+ // Insert <div class="moz-text-html" lang="..."> for the following two purposes:
+ // 1) Users can configure their HTML display via CSS for .moz-text-html.
+ // 2) The language group in the 'lang' attribure is used by Gecko to determine
+ // which font to use.
+ int32_t fontSize; // default font size
+ int32_t fontSizePercentage; // size percentage
+ nsAutoCString fontLang; // langgroup of the font.
+ if (NS_SUCCEEDED(GetMailNewsFont(obj, false, &fontSize, &fontSizePercentage, fontLang)))
+ {
+ message.Insert(NS_LITERAL_CSTRING("<div class=\"moz-text-html\" lang=\"") +
+ fontLang +
+ NS_LITERAL_CSTRING("\">"),
+ index);
+ }
+ else
+ {
+ message.Insert(NS_LITERAL_CSTRING("<div class=\"moz-text-html\">"),
+ index);
+ }
+
+ index = message.RFind("</body>", /* ignoreCase = */ true);
+ if (index != kNotFound)
+ message.Insert(NS_LITERAL_CSTRING("</div>"), index);
+}
+
+/*
+ * The following function replaces <plaintext> tags with <x-plaintext>.
+ * <plaintext> is a funny beast: It leads to everything following it
+ * being displayed verbatim, even a </plaintext> tag is ignored.
+ */
+void
+MimeInlineTextHTML_remove_plaintext_tag(MimeObject *obj, nsCString &message)
+{
+ if (obj->options->format_out != nsMimeOutput::nsMimeMessageBodyDisplay &&
+ obj->options->format_out != nsMimeOutput::nsMimeMessagePrintOutput)
+ return;
+
+ // Replace all <plaintext> and </plaintext> tags.
+ int32_t index = 0;
+ bool replaced = false;
+ while ((index = message.Find("<plaintext", /* ignoreCase = */ true, index)) != kNotFound) {
+ message.Insert("x-", index+1);
+ index += 12;
+ replaced = true;
+ }
+ if (replaced) {
+ index = 0;
+ while ((index = message.Find("</plaintext", /* ignoreCase = */ true, index)) != kNotFound) {
+ message.Insert("x-", index+2);
+ index += 13;
+ }
+ }
+}
+
diff --git a/mailnews/mime/src/mimethtm.h b/mailnews/mime/src/mimethtm.h
new file mode 100644
index 000000000..fbd730cd2
--- /dev/null
+++ b/mailnews/mime/src/mimethtm.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMETHTM_H_
+#define _MIMETHTM_H_
+
+#include "mimetext.h"
+
+/* The MimeInlineTextHTML class implements the text/html MIME content type.
+ */
+
+typedef struct MimeInlineTextHTMLClass MimeInlineTextHTMLClass;
+typedef struct MimeInlineTextHTML MimeInlineTextHTML;
+
+struct MimeInlineTextHTMLClass {
+ MimeInlineTextClass text;
+};
+
+extern MimeInlineTextHTMLClass mimeInlineTextHTMLClass;
+
+struct MimeInlineTextHTML {
+ MimeInlineText text;
+ char *charset; /* If we sniffed a charset, do some converting! */
+};
+
+#define MimeInlineTextHTMLClassInitializer(ITYPE,CSUPER) \
+ { MimeInlineTextClassInitializer(ITYPE,CSUPER) }
+
+void
+MimeInlineTextHTML_insert_lang_div(MimeObject *obj, nsCString &message);
+void
+MimeInlineTextHTML_remove_plaintext_tag(MimeObject *obj, nsCString &message);
+#endif /* _MIMETHTM_H_ */
diff --git a/mailnews/mime/src/mimetpfl.cpp b/mailnews/mime/src/mimetpfl.cpp
new file mode 100644
index 000000000..da7eff413
--- /dev/null
+++ b/mailnews/mime/src/mimetpfl.cpp
@@ -0,0 +1,630 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimetpfl.h"
+#include "mimebuf.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsStringGlue.h"
+#include "nsMimeStringResources.h"
+#include "nsIPrefBranch.h"
+#include "nsIServiceManager.h"
+#include "mimemoz2.h"
+#include "prprf.h"
+#include "nsMsgI18N.h"
+
+static const uint32_t kSpacesForATab = 4; // Must be at least 1.
+
+#define MIME_SUPERCLASS mimeInlineTextClass
+MimeDefClass(MimeInlineTextPlainFlowed, MimeInlineTextPlainFlowedClass,
+ mimeInlineTextPlainFlowedClass, &MIME_SUPERCLASS);
+
+static int MimeInlineTextPlainFlowed_parse_begin (MimeObject *);
+static int MimeInlineTextPlainFlowed_parse_line (const char *, int32_t, MimeObject *);
+static int MimeInlineTextPlainFlowed_parse_eof (MimeObject *, bool);
+
+static MimeInlineTextPlainFlowedExData *MimeInlineTextPlainFlowedExDataList = nullptr;
+
+// From mimetpla.cpp
+extern "C" void MimeTextBuildPrefixCSS(
+ int32_t quotedSizeSetting, // mail.quoted_size
+ int32_t quotedStyleSetting, // mail.quoted_style
+ char *citationColor, // mail.citation_color
+ nsACString &style);
+// Definition below
+static
+nsresult Line_convert_whitespace(const nsString& a_line,
+ const bool a_convert_all_whitespace,
+ nsString& a_out_line);
+
+static int
+MimeInlineTextPlainFlowedClassInitialize(MimeInlineTextPlainFlowedClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ NS_ASSERTION(!oclass->class_initialized, "class not initialized");
+ oclass->parse_begin = MimeInlineTextPlainFlowed_parse_begin;
+ oclass->parse_line = MimeInlineTextPlainFlowed_parse_line;
+ oclass->parse_eof = MimeInlineTextPlainFlowed_parse_eof;
+
+ return 0;
+}
+
+static int
+MimeInlineTextPlainFlowed_parse_begin (MimeObject *obj)
+{
+ int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+ if (status < 0) return status;
+
+ status = MimeObject_write(obj, "", 0, true); /* force out any separators... */
+ if(status<0) return status;
+
+ bool quoting = ( obj->options
+ && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting ||
+ obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting
+ ) ); // The output will be inserted in the composer as quotation
+ bool plainHTML = quoting || (obj->options &&
+ obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs);
+ // Just good(tm) HTML. No reliance on CSS.
+
+ // Setup the data structure that is connected to the actual document
+ // Saved in a linked list in case this is called with several documents
+ // at the same time.
+ /* This memory is freed when parse_eof is called. So it better be! */
+ struct MimeInlineTextPlainFlowedExData *exdata =
+ (MimeInlineTextPlainFlowedExData *)PR_MALLOC(sizeof(struct MimeInlineTextPlainFlowedExData));
+ if(!exdata) return MIME_OUT_OF_MEMORY;
+
+ MimeInlineTextPlainFlowed *text = (MimeInlineTextPlainFlowed *) obj;
+
+ // Link it up.
+ exdata->next = MimeInlineTextPlainFlowedExDataList;
+ MimeInlineTextPlainFlowedExDataList = exdata;
+
+ // Initialize data
+
+ exdata->ownerobj = obj;
+ exdata->inflow = false;
+ exdata->quotelevel = 0;
+ exdata->isSig = false;
+
+ // check for DelSp=yes (RFC 3676)
+
+ char *content_type_row =
+ (obj->headers
+ ? MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, false, false)
+ : 0);
+ char *content_type_delsp =
+ (content_type_row
+ ? MimeHeaders_get_parameter(content_type_row, "delsp", NULL,NULL)
+ : 0);
+ ((MimeInlineTextPlainFlowed *)obj)->delSp = content_type_delsp && !PL_strcasecmp(content_type_delsp, "yes");
+ PR_Free(content_type_delsp);
+ PR_Free(content_type_row);
+
+ // Get Prefs for viewing
+
+ exdata->fixedwidthfont = false;
+ // Quotes
+ text->mQuotedSizeSetting = 0; // mail.quoted_size
+ text->mQuotedStyleSetting = 0; // mail.quoted_style
+ text->mCitationColor = nullptr; // mail.citation_color
+ text->mStripSig = true; // mail.strip_sig_on_reply
+
+ nsIPrefBranch *prefBranch = GetPrefBranch(obj->options);
+ if (prefBranch)
+ {
+ prefBranch->GetIntPref("mail.quoted_size", &(text->mQuotedSizeSetting));
+ prefBranch->GetIntPref("mail.quoted_style", &(text->mQuotedStyleSetting));
+ prefBranch->GetCharPref("mail.citation_color", &(text->mCitationColor));
+ prefBranch->GetBoolPref("mail.strip_sig_on_reply", &(text->mStripSig));
+ mozilla::DebugOnly<nsresult> rv =
+ prefBranch->GetBoolPref("mail.fixed_width_messages", &(exdata->fixedwidthfont));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get pref");
+ // Check at least the success of one
+ }
+
+ // Get font
+ // only used for viewing (!plainHTML)
+ nsAutoCString fontstyle;
+ nsAutoCString fontLang; // langgroup of the font
+
+
+ // generic font-family name ( -moz-fixed for fixed font and NULL for
+ // variable font ) is sufficient now that bug 105199 has been fixed.
+
+ if (exdata->fixedwidthfont)
+ fontstyle = "font-family: -moz-fixed";
+
+ if (nsMimeOutput::nsMimeMessageBodyDisplay == obj->options->format_out ||
+ nsMimeOutput::nsMimeMessagePrintOutput == obj->options->format_out)
+ {
+ int32_t fontSize; // default font size
+ int32_t fontSizePercentage; // size percentage
+ nsresult rv = GetMailNewsFont(obj, exdata->fixedwidthfont,
+ &fontSize, &fontSizePercentage, fontLang);
+ if (NS_SUCCEEDED(rv))
+ {
+ if ( ! fontstyle.IsEmpty() ) {
+ fontstyle += "; ";
+ }
+ fontstyle += "font-size: ";
+ fontstyle.AppendInt(fontSize);
+ fontstyle += "px;";
+ }
+ }
+
+ // Opening <div>.
+ if (!quoting)
+ /* 4.x' editor can't break <div>s (e.g. to interleave comments).
+ We'll add the class to the <blockquote type=cite> later. */
+ {
+ nsAutoCString openingDiv("<div class=\"moz-text-flowed\"");
+ // We currently have to add formatting here. :-(
+ if (!plainHTML && !fontstyle.IsEmpty())
+ {
+ openingDiv += " style=\"";
+ openingDiv += fontstyle;
+ openingDiv += '"';
+ }
+ if (!plainHTML && !fontLang.IsEmpty())
+ {
+ openingDiv += " lang=\"";
+ openingDiv += fontLang;
+ openingDiv += '\"';
+ }
+ openingDiv += ">";
+ status = MimeObject_write(obj, openingDiv.get(), openingDiv.Length(), false);
+ if (status < 0) return status;
+ }
+
+ return 0;
+}
+
+static int
+MimeInlineTextPlainFlowed_parse_eof (MimeObject *obj, bool abort_p)
+{
+ int status = 0;
+ struct MimeInlineTextPlainFlowedExData *exdata = nullptr;
+
+ bool quoting = ( obj->options
+ && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting ||
+ obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting
+ ) ); // see above
+
+ // Has this method already been called for this object?
+ // In that case return.
+ if (obj->closed_p) return 0;
+
+ /* Run parent method first, to flush out any buffered data. */
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0) goto EarlyOut;
+
+ // Look up and unlink "our" extended data structure
+ // We do it in the beginning so that if an error occur, we can
+ // just free |exdata|.
+ struct MimeInlineTextPlainFlowedExData **prevexdata;
+ prevexdata = &MimeInlineTextPlainFlowedExDataList;
+
+ while ((exdata = *prevexdata) != nullptr) {
+ if (exdata->ownerobj == obj) {
+ // Fill hole
+ *prevexdata = exdata->next;
+ break;
+ }
+ prevexdata = &exdata->next;
+ }
+ NS_ASSERTION (exdata, "The extra data has disappeared!");
+
+ if (!obj->output_p) {
+ status = 0;
+ goto EarlyOut;
+ }
+
+ for(; exdata->quotelevel > 0; exdata->quotelevel--) {
+ status = MimeObject_write(obj, "</blockquote>", 13, false);
+ if(status<0) goto EarlyOut;
+ }
+
+ if (exdata->isSig && !quoting) {
+ status = MimeObject_write(obj, "</div>", 6, false); // .moz-txt-sig
+ if (status<0) goto EarlyOut;
+ }
+ if (!quoting) // HACK (see above)
+ {
+ status = MimeObject_write(obj, "</div>", 6, false); // .moz-text-flowed
+ if (status<0) goto EarlyOut;
+ }
+
+ status = 0;
+
+EarlyOut:
+ PR_Free(exdata);
+
+ // Free mCitationColor
+ MimeInlineTextPlainFlowed *text = (MimeInlineTextPlainFlowed *) obj;
+ PR_FREEIF(text->mCitationColor);
+ text->mCitationColor = nullptr;
+
+ return status;
+}
+
+
+static int
+MimeInlineTextPlainFlowed_parse_line (const char *aLine, int32_t length, MimeObject *obj)
+{
+ int status;
+ bool quoting = ( obj->options
+ && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting ||
+ obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting
+ ) ); // see above
+ bool plainHTML = quoting || (obj->options &&
+ obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs);
+ // see above
+
+ struct MimeInlineTextPlainFlowedExData *exdata;
+ exdata = MimeInlineTextPlainFlowedExDataList;
+ while(exdata && (exdata->ownerobj != obj)) {
+ exdata = exdata->next;
+ }
+
+ NS_ASSERTION(exdata, "The extra data has disappeared!");
+
+ NS_ASSERTION(length > 0, "zero length");
+ if (length <= 0) return 0;
+
+ uint32_t linequotelevel = 0;
+ nsAutoCString real_line(aLine, length);
+ char *line = real_line.BeginWriting();
+ const char *linep = real_line.BeginReading();
+ // Space stuffed?
+ if(' ' == *linep) {
+ linep++;
+ } else {
+ // count '>':s before the first non-'>'
+ while('>' == *linep) {
+ linep++;
+ linequotelevel++;
+ }
+ // Space stuffed?
+ if(' ' == *linep) {
+ linep++;
+ }
+ }
+
+ // Look if the last character (after stripping ending end
+ // of lines and quoting stuff) is a SPACE. If it is, we are looking at a
+ // flowed line. Normally we assume that the last two chars
+ // are CR and LF as said in RFC822, but that doesn't seem to
+ // be the case always.
+ bool flowed = false;
+ bool sigSeparator = false;
+ int32_t index = length-1;
+ while(index >= 0 && ('\r' == line[index] || '\n' == line[index])) {
+ index--;
+ }
+ if (index > linep - line && ' ' == line[index])
+ /* Ignore space stuffing, i.e. lines with just
+ (quote marks and) a space count as empty */
+ {
+ flowed = true;
+ sigSeparator = (index - (linep - line) + 1 == 3) && !strncmp(linep, "-- ", 3);
+ if (((MimeInlineTextPlainFlowed *) obj)->delSp && !sigSeparator)
+ /* If line is flowed and DelSp=yes, logically
+ delete trailing space. Line consisting of
+ dash dash space ("-- "), commonly used as
+ signature separator, gets special handling
+ (RFC 3676) */
+ {
+ length = index;
+ line[index] = '\0';
+ }
+ }
+
+ if (obj->options &&
+ obj->options->decompose_file_p &&
+ obj->options->decompose_file_output_fn)
+ {
+ return obj->options->decompose_file_output_fn(line,
+ length,
+ obj->options->stream_closure);
+ }
+
+ mozITXTToHTMLConv *conv = GetTextConverter(obj->options);
+
+ bool skipConversion = !conv ||
+ (obj->options && obj->options->force_user_charset);
+
+ nsAutoString lineSource;
+ nsString lineResult;
+
+ char *mailCharset = NULL;
+ nsresult rv;
+
+ if (!skipConversion)
+ {
+ // Convert only if the source string is not empty
+ if (length - (linep - line) > 0)
+ {
+ uint32_t whattodo = obj->options->whattodo;
+ if (plainHTML)
+ {
+ if (quoting)
+ whattodo = 0;
+ else
+ whattodo = whattodo & ~mozITXTToHTMLConv::kGlyphSubstitution;
+ /* Do recognition for the case, the result is viewed in
+ Mozilla, but not GlyphSubstitution, because other UAs
+ might not be able to display the glyphs. */
+ }
+
+ const nsDependentCSubstring& inputStr = Substring(linep, linep + (length - (linep - line)));
+
+ // For 'SaveAs', |line| is in |mailCharset|.
+ // convert |line| to UTF-16 before 'html'izing (calling ScanTXT())
+ if (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs)
+ {
+ // Get the mail charset of this message.
+ MimeInlineText *inlinetext = (MimeInlineText *) obj;
+ if (!inlinetext->initializeCharset)
+ ((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj);
+ mailCharset = inlinetext->charset;
+ if (mailCharset && *mailCharset) {
+ rv = nsMsgI18NConvertToUnicode(mailCharset, PromiseFlatCString(inputStr), lineSource);
+ NS_ENSURE_SUCCESS(rv, -1);
+ }
+ else // this probably never happens...
+ CopyUTF8toUTF16(inputStr, lineSource);
+ }
+ else // line is in UTF-8
+ CopyUTF8toUTF16(inputStr, lineSource);
+
+ // This is the main TXT to HTML conversion:
+ // escaping (very important), eventually recognizing etc.
+ rv = conv->ScanTXT(lineSource.get(), whattodo, getter_Copies(lineResult));
+ NS_ENSURE_SUCCESS(rv, -1);
+ }
+ }
+ else
+ {
+ CopyUTF8toUTF16(nsDependentCString(line, length), lineResult);
+ status = 0;
+ }
+
+ nsAutoCString preface;
+
+ /* Correct number of blockquotes */
+ int32_t quoteleveldiff=linequotelevel - exdata->quotelevel;
+ if((quoteleveldiff != 0) && flowed && exdata->inflow) {
+ // From RFC 2646 4.5
+ // The receiver SHOULD handle this error by using the 'quote-depth-wins' rule,
+ // which is to ignore the flowed indicator and treat the line as fixed. That
+ // is, the change in quote depth ends the paragraph.
+
+ // We get that behaviour by just going on.
+ }
+
+ // Cast so we have access to the prefs we need.
+ MimeInlineTextPlainFlowed *tObj = (MimeInlineTextPlainFlowed *) obj;
+ while(quoteleveldiff>0) {
+ quoteleveldiff--;
+ preface += "<blockquote type=cite";
+
+ nsAutoCString style;
+ MimeTextBuildPrefixCSS(tObj->mQuotedSizeSetting, tObj->mQuotedStyleSetting,
+ tObj->mCitationColor, style);
+ if (!plainHTML && !style.IsEmpty())
+ {
+ preface += " style=\"";
+ preface += style;
+ preface += '"';
+ }
+ preface += '>';
+ }
+ while(quoteleveldiff<0) {
+ quoteleveldiff++;
+ preface += "</blockquote>";
+ }
+ exdata->quotelevel = linequotelevel;
+
+ nsAutoString lineResult2;
+
+ if(flowed) {
+ // Check RFC 2646 "4.3. Usenet Signature Convention": "-- "+CRLF is
+ // not a flowed line
+ if (sigSeparator)
+ {
+ if (linequotelevel > 0 || exdata->isSig)
+ {
+ preface += "--&nbsp;<br>";
+ } else {
+ exdata->isSig = true;
+ preface += "<div class=\"moz-txt-sig\"><span class=\"moz-txt-tag\">"
+ "--&nbsp;<br></span>";
+ }
+ } else {
+ Line_convert_whitespace(lineResult, false /* Allow wraps */,
+ lineResult2);
+ }
+
+ exdata->inflow=true;
+ } else {
+ // Fixed paragraph.
+ Line_convert_whitespace(lineResult,
+ !plainHTML && !obj->options->wrap_long_lines_p
+ /* If wrap, convert all spaces but the last in
+ a row into nbsp, otherwise all. */,
+ lineResult2);
+ lineResult2.AppendLiteral("<br>");
+ exdata->inflow = false;
+ } // End Fixed line
+
+ if (!(exdata->isSig && quoting && tObj->mStripSig))
+ {
+ status = MimeObject_write(obj, preface.get(), preface.Length(), true);
+ if (status < 0) return status;
+ nsAutoCString outString;
+ if (obj->options->format_out != nsMimeOutput::nsMimeMessageSaveAs ||
+ !mailCharset || !*mailCharset)
+ CopyUTF16toUTF8(lineResult2, outString);
+ else
+ { // convert back to mailCharset before writing.
+ rv = nsMsgI18NConvertFromUnicode(mailCharset, lineResult2, outString);
+ NS_ENSURE_SUCCESS(rv, -1);
+ }
+ status = MimeObject_write(obj, outString.get(), outString.Length(), true);
+ return status;
+ }
+ return 0;
+}
+
+
+/**
+ * Maintains a small state machine with three states. "Not in tag",
+ * "In tag, but not in quote" and "In quote inside a tag". It also
+ * remembers what character started the quote (" or '). The state
+ * variables are kept outside this function and are included as
+ * parameters.
+ *
+ * @param in/out a_in_tag, if we are in a tag right now.
+ * @param in/out a_in_quote_in_tag, if we are in a quote inside a tag.
+ * @param in/out a_quote_char, the kind of quote (" or ').
+ * @param in a_current_char, the next char. It decides which state
+ * will be next.
+ */
+static void Update_in_tag_info(bool *a_in_tag, /* IN/OUT */
+ bool *a_in_quote_in_tag, /* IN/OUT */
+ char16_t *a_quote_char, /* IN/OUT (pointer to single char) */
+ char16_t a_current_char) /* IN */
+{
+ if(*a_in_tag) {
+ // Keep us informed of what's quoted so that we
+ // don't end the tag too soon. For instance in
+ // <font face="weird>font<name">
+ if(*a_in_quote_in_tag) {
+ // We are in a quote. A quote is ended by the same
+ // character that started it ('...' or "...")
+ if(*a_quote_char == a_current_char) {
+ *a_in_quote_in_tag = false;
+ }
+ } else {
+ // We are not currently in a quote, but we may enter
+ // one right this minute.
+ switch(a_current_char) {
+ case '"':
+ case '\'':
+ *a_in_quote_in_tag = true;
+ *a_quote_char = a_current_char;
+ break;
+ case '>':
+ // Tag is ended
+ *a_in_tag = false;
+ break;
+ default:
+ // Do nothing
+ ;
+ }
+ }
+ return;
+ }
+
+ // Not in a tag.
+ // Check if we are entering a tag by looking for '<'.
+ // All normal occurrences of '<' should have been replaced
+ // by &lt;
+ if ('<' == a_current_char) {
+ *a_in_tag = true;
+ *a_in_quote_in_tag = false;
+ }
+}
+
+
+/**
+ * Converts whitespace to |&nbsp;|, if appropriate.
+ *
+ * @param in a_current_char, the char to convert.
+ * @param in a_next_char, the char after the char to convert.
+ * @param in a_convert_all_whitespace, if also the last whitespace
+ * in a sequence should be
+ * converted.
+ * @param out a_out_string, result will be appended.
+*/
+static void Convert_whitespace(const char16_t a_current_char,
+ const char16_t a_next_char,
+ const bool a_convert_all_whitespace,
+ nsString& a_out_string)
+{
+ NS_ASSERTION('\t' == a_current_char || ' ' == a_current_char,
+ "Convert_whitespace got something else than a whitespace!");
+
+ uint32_t number_of_nbsp = 0;
+ uint32_t number_of_space = 1; // Assume we're going to output one space.
+
+ /* Output the spaces for a tab. All but the last are made into &nbsp;.
+ The last is treated like a normal space.
+ */
+ if('\t' == a_current_char) {
+ number_of_nbsp = kSpacesForATab - 1;
+ }
+
+ if(' ' == a_next_char || '\t' == a_next_char || a_convert_all_whitespace) {
+ number_of_nbsp += number_of_space;
+ number_of_space = 0;
+ }
+
+ while(number_of_nbsp--) {
+ a_out_string.AppendLiteral("&nbsp;");
+ }
+
+ while(number_of_space--) {
+ // a_out_string += ' '; gives error
+ a_out_string.AppendLiteral(" ");
+ }
+
+ return;
+}
+
+/**
+ * Passes over the line and converts whitespace to |&nbsp;|, if appropriate
+ *
+ * @param in a_convert_all_whitespace, if also the last whitespace
+ * in a sequence should be
+ * converted.
+ * @param out a_out_string, result will be appended.
+*/
+static
+nsresult Line_convert_whitespace(const nsString& a_line,
+ const bool a_convert_all_whitespace,
+ nsString& a_out_line)
+{
+ bool in_tag = false;
+ bool in_quote_in_tag = false;
+ char16_t quote_char;
+
+ for (uint32_t i = 0; a_line.Length() > i; i++)
+ {
+ const char16_t ic = a_line[i]; // Cache
+
+ Update_in_tag_info(&in_tag, &in_quote_in_tag, &quote_char, ic);
+ // We don't touch anything inside a tag.
+ if (!in_tag) {
+ if (ic == ' ' || ic == '\t') {
+ // Convert the whitespace to something appropriate
+ Convert_whitespace(ic, a_line.Length() > i + 1 ? a_line[i + 1] : '\0',
+ a_convert_all_whitespace ||
+ !i, // First char on line
+ a_out_line);
+ } else if (ic == '\r') {
+ // strip CRs
+ } else {
+ a_out_line += ic;
+ }
+ } else {
+ // In tag. Don't change anything
+ a_out_line += ic;
+ }
+ }
+ return NS_OK;
+}
diff --git a/mailnews/mime/src/mimetpfl.h b/mailnews/mime/src/mimetpfl.h
new file mode 100644
index 000000000..c3b20c445
--- /dev/null
+++ b/mailnews/mime/src/mimetpfl.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMETPFL_H_
+#define _MIMETPFL_H_
+
+#include "mimetext.h"
+
+/* The MimeInlineTextPlainFlowed class implements the
+ text/plain MIME content type for the special case of a supplied
+ format=flowed. See
+ ftp://ftp.ietf.org/internet-drafts/draft-gellens-format-06.txt for
+ more information.
+ */
+
+typedef struct MimeInlineTextPlainFlowedClass MimeInlineTextPlainFlowedClass;
+typedef struct MimeInlineTextPlainFlowed MimeInlineTextPlainFlowed;
+
+struct MimeInlineTextPlainFlowedClass {
+ MimeInlineTextClass text;
+};
+
+extern MimeInlineTextPlainFlowedClass mimeInlineTextPlainFlowedClass;
+
+struct MimeInlineTextPlainFlowed {
+ MimeInlineText text;
+ bool delSp; // DelSp=yes (RFC 3676)
+ int32_t mQuotedSizeSetting; // mail.quoted_size
+ int32_t mQuotedStyleSetting; // mail.quoted_style
+ char *mCitationColor; // mail.citation_color
+ bool mStripSig; // mail.strip_sig_on_reply
+};
+
+
+/*
+ * Made to contain information to be kept during the whole message parsing.
+ */
+struct MimeInlineTextPlainFlowedExData {
+ struct MimeObject *ownerobj; /* The owner of this struct */
+ bool inflow; /* If we currently are in flow */
+ bool fixedwidthfont; /* If we output text for fixed width font */
+ uint32_t quotelevel; /* How deep is your love, uhr, quotelevel I meen. */
+ bool isSig; // we're currently in a signature
+ struct MimeInlineTextPlainFlowedExData *next;
+};
+
+#define MimeInlineTextPlainFlowedClassInitializer(ITYPE,CSUPER) \
+ { MimeInlineTextClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMETPFL_H_ */
diff --git a/mailnews/mime/src/mimetpla.cpp b/mailnews/mime/src/mimetpla.cpp
new file mode 100644
index 000000000..72a974a73
--- /dev/null
+++ b/mailnews/mime/src/mimetpla.cpp
@@ -0,0 +1,451 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimetpla.h"
+#include "mimebuf.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsCOMPtr.h"
+#include "nsIComponentManager.h"
+#include "nsStringGlue.h"
+#include "nsMimeStringResources.h"
+#include "mimemoz2.h"
+#include "nsIServiceManager.h"
+#include "nsIPrefBranch.h"
+#include "prprf.h"
+#include "nsMsgI18N.h"
+
+#define MIME_SUPERCLASS mimeInlineTextClass
+MimeDefClass(MimeInlineTextPlain, MimeInlineTextPlainClass,
+ mimeInlineTextPlainClass, &MIME_SUPERCLASS);
+
+static int MimeInlineTextPlain_parse_begin (MimeObject *);
+static int MimeInlineTextPlain_parse_line (const char *, int32_t, MimeObject *);
+static int MimeInlineTextPlain_parse_eof (MimeObject *, bool);
+
+static int
+MimeInlineTextPlainClassInitialize(MimeInlineTextPlainClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ NS_ASSERTION(!oclass->class_initialized, "class not initialized");
+ oclass->parse_begin = MimeInlineTextPlain_parse_begin;
+ oclass->parse_line = MimeInlineTextPlain_parse_line;
+ oclass->parse_eof = MimeInlineTextPlain_parse_eof;
+ return 0;
+}
+
+extern "C"
+void
+MimeTextBuildPrefixCSS(int32_t quotedSizeSetting, // mail.quoted_size
+ int32_t quotedStyleSetting, // mail.quoted_style
+ char *citationColor, // mail.citation_color
+ nsACString &style)
+{
+ switch (quotedStyleSetting)
+ {
+ case 0: // regular
+ break;
+ case 1: // bold
+ style.Append("font-weight: bold; ");
+ break;
+ case 2: // italic
+ style.Append("font-style: italic; ");
+ break;
+ case 3: // bold-italic
+ style.Append("font-weight: bold; font-style: italic; ");
+ break;
+ }
+
+ switch (quotedSizeSetting)
+ {
+ case 0: // regular
+ break;
+ case 1: // large
+ style.Append("font-size: large; ");
+ break;
+ case 2: // small
+ style.Append("font-size: small; ");
+ break;
+ }
+
+ if (citationColor && *citationColor)
+ {
+ style += "color: ";
+ style += citationColor;
+ style += ';';
+ }
+}
+
+static int
+MimeInlineTextPlain_parse_begin (MimeObject *obj)
+{
+ int status = 0;
+ bool quoting = ( obj->options
+ && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting ||
+ obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting
+ ) ); // The output will be inserted in the composer as quotation
+ bool plainHTML = quoting || (obj->options &&
+ (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs));
+ // Just good(tm) HTML. No reliance on CSS.
+ bool rawPlainText = obj->options &&
+ (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer
+ || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach);
+
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+ if (status < 0) return status;
+
+ if (!obj->output_p) return 0;
+
+ if (obj->options &&
+ obj->options->write_html_p &&
+ obj->options->output_fn)
+ {
+ MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj;
+ text->mCiteLevel = 0;
+
+ // Get the prefs
+
+ // Quoting
+ text->mBlockquoting = true; // mail.quoteasblock
+
+ // Viewing
+ text->mQuotedSizeSetting = 0; // mail.quoted_size
+ text->mQuotedStyleSetting = 0; // mail.quoted_style
+ text->mCitationColor = nullptr; // mail.citation_color
+ text->mStripSig = true; // mail.strip_sig_on_reply
+ bool graphicalQuote = true; // mail.quoted_graphical
+
+ nsIPrefBranch *prefBranch = GetPrefBranch(obj->options);
+ if (prefBranch)
+ {
+ prefBranch->GetIntPref("mail.quoted_size", &(text->mQuotedSizeSetting));
+ prefBranch->GetIntPref("mail.quoted_style", &(text->mQuotedStyleSetting));
+ prefBranch->GetCharPref("mail.citation_color", &(text->mCitationColor));
+ prefBranch->GetBoolPref("mail.strip_sig_on_reply", &(text->mStripSig));
+ prefBranch->GetBoolPref("mail.quoted_graphical", &graphicalQuote);
+ prefBranch->GetBoolPref("mail.quoteasblock", &(text->mBlockquoting));
+ }
+
+ if (!rawPlainText)
+ {
+ // Get font
+ // only used for viewing (!plainHTML)
+ nsAutoCString fontstyle;
+ nsAutoCString fontLang; // langgroup of the font
+
+ // generic font-family name ( -moz-fixed for fixed font and NULL for
+ // variable font ) is sufficient now that bug 105199 has been fixed.
+
+ if (!obj->options->variable_width_plaintext_p)
+ fontstyle = "font-family: -moz-fixed";
+
+ if (nsMimeOutput::nsMimeMessageBodyDisplay == obj->options->format_out ||
+ nsMimeOutput::nsMimeMessagePrintOutput == obj->options->format_out)
+ {
+ int32_t fontSize; // default font size
+ int32_t fontSizePercentage; // size percentage
+ nsresult rv = GetMailNewsFont(obj,
+ !obj->options->variable_width_plaintext_p,
+ &fontSize, &fontSizePercentage, fontLang);
+ if (NS_SUCCEEDED(rv))
+ {
+ if ( ! fontstyle.IsEmpty() ) {
+ fontstyle += "; ";
+ }
+ fontstyle += "font-size: ";
+ fontstyle.AppendInt(fontSize);
+ fontstyle += "px;";
+ }
+ }
+
+ // Opening <div>. We currently have to add formatting here. :-(
+ nsAutoCString openingDiv;
+ if (!quoting)
+ /* 4.x' editor can't break <div>s (e.g. to interleave comments).
+ We'll add the class to the <blockquote type=cite> later. */
+ {
+ openingDiv = "<div class=\"moz-text-plain\"";
+ if (!plainHTML)
+ {
+ if (obj->options->wrap_long_lines_p)
+ openingDiv += " wrap=true";
+ else
+ openingDiv += " wrap=false";
+
+ if (graphicalQuote)
+ openingDiv += " graphical-quote=true";
+ else
+ openingDiv += " graphical-quote=false";
+
+ if (!fontstyle.IsEmpty())
+ {
+ openingDiv += " style=\"";
+ openingDiv += fontstyle;
+ openingDiv += '\"';
+ }
+ if (!fontLang.IsEmpty())
+ {
+ openingDiv += " lang=\"";
+ openingDiv += fontLang;
+ openingDiv += '\"';
+ }
+ }
+ openingDiv += "><pre wrap>\n";
+ }
+ else
+ openingDiv = "<pre wrap>\n";
+
+ /* text/plain objects always have separators before and after them.
+ Note that this is not the case for text/enriched objects. */
+ status = MimeObject_write_separator(obj);
+ if (status < 0) return status;
+
+ status = MimeObject_write(obj, openingDiv.get(), openingDiv.Length(), true);
+ if (status < 0) return status;
+ }
+ }
+
+ return 0;
+}
+
+static int
+MimeInlineTextPlain_parse_eof (MimeObject *obj, bool abort_p)
+{
+ int status;
+
+ // Has this method already been called for this object?
+ // In that case return.
+ if (obj->closed_p) return 0;
+
+ nsCString citationColor;
+ MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj;
+ if (text && text->mCitationColor)
+ citationColor.Adopt(text->mCitationColor);
+
+ bool quoting = ( obj->options
+ && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting ||
+ obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting
+ ) ); // see above
+
+ bool rawPlainText = obj->options &&
+ (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer
+ || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach);
+
+ /* Run parent method first, to flush out any buffered data. */
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0) return status;
+
+ if (!obj->output_p) return 0;
+
+ if (obj->options &&
+ obj->options->write_html_p &&
+ obj->options->output_fn &&
+ !abort_p && !rawPlainText)
+ {
+ MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj;
+ if (text->mIsSig && !quoting)
+ {
+ status = MimeObject_write(obj, "</div>", 6, false); // .moz-txt-sig
+ if (status < 0) return status;
+ }
+ status = MimeObject_write(obj, "</pre>", 6, false);
+ if (status < 0) return status;
+ if (!quoting)
+ {
+ status = MimeObject_write(obj, "</div>", 6, false);
+ // .moz-text-plain
+ if (status < 0) return status;
+ }
+
+ /* text/plain objects always have separators before and after them.
+ Note that this is not the case for text/enriched objects.
+ */
+ status = MimeObject_write_separator(obj);
+ if (status < 0) return status;
+ }
+
+ return 0;
+}
+
+
+static int
+MimeInlineTextPlain_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ int status;
+ bool quoting = ( obj->options
+ && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting ||
+ obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting
+ ) ); // see above
+ bool plainHTML = quoting || (obj->options &&
+ obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs);
+ // see above
+
+ bool rawPlainText = obj->options &&
+ (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer
+ || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach);
+
+ // this routine gets called for every line of data that comes through the
+ // mime converter. It's important to make sure we are efficient with
+ // how we allocate memory in this routine. be careful if you go to add
+ // more to this routine.
+
+ NS_ASSERTION(length > 0, "zero length");
+ if (length <= 0) return 0;
+
+ mozITXTToHTMLConv *conv = GetTextConverter(obj->options);
+ MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj;
+
+ bool skipConversion = !conv || rawPlainText ||
+ (obj->options && obj->options->force_user_charset);
+
+ char *mailCharset = NULL;
+ nsresult rv;
+
+ if (!skipConversion)
+ {
+ nsDependentCString inputStr(line, length);
+ nsAutoString lineSourceStr;
+
+ // For 'SaveAs', |line| is in |mailCharset|.
+ // convert |line| to UTF-16 before 'html'izing (calling ScanTXT())
+ if (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs)
+ { // Get the mail charset of this message.
+ MimeInlineText *inlinetext = (MimeInlineText *) obj;
+ if (!inlinetext->initializeCharset)
+ ((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj);
+ mailCharset = inlinetext->charset;
+ if (mailCharset && *mailCharset) {
+ rv = nsMsgI18NConvertToUnicode(mailCharset, inputStr, lineSourceStr);
+ NS_ENSURE_SUCCESS(rv, -1);
+ }
+ else // this probably never happens ...
+ CopyUTF8toUTF16(inputStr, lineSourceStr);
+ }
+ else // line is in UTF-8
+ CopyUTF8toUTF16(inputStr, lineSourceStr);
+
+ nsAutoCString prefaceResultStr; // Quoting stuff before the real text
+
+ // Recognize quotes
+ uint32_t oldCiteLevel = text->mCiteLevel;
+ uint32_t logicalLineStart = 0;
+ rv = conv->CiteLevelTXT(lineSourceStr.get(),
+ &logicalLineStart, &(text->mCiteLevel));
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ // Find out, which recognitions to do
+ uint32_t whattodo = obj->options->whattodo;
+ if (plainHTML)
+ {
+ if (quoting)
+ whattodo = 0; // This is done on Send. Don't do it twice.
+ else
+ whattodo = whattodo & ~mozITXTToHTMLConv::kGlyphSubstitution;
+ /* Do recognition for the case, the result is viewed in
+ Mozilla, but not GlyphSubstitution, because other UAs
+ might not be able to display the glyphs. */
+ if (!text->mBlockquoting)
+ text->mCiteLevel = 0;
+ }
+
+ // Write blockquote
+ if (text->mCiteLevel > oldCiteLevel)
+ {
+ prefaceResultStr += "</pre>";
+ for (uint32_t i = 0; i < text->mCiteLevel - oldCiteLevel; i++)
+ {
+ nsAutoCString style;
+ MimeTextBuildPrefixCSS(text->mQuotedSizeSetting, text->mQuotedStyleSetting,
+ text->mCitationColor, style);
+ if (!plainHTML && !style.IsEmpty())
+ {
+ prefaceResultStr += "<blockquote type=cite style=\"";
+ prefaceResultStr += style;
+ prefaceResultStr += "\">";
+ }
+ else
+ prefaceResultStr += "<blockquote type=cite>";
+ }
+ prefaceResultStr += "<pre wrap>\n";
+ }
+ else if (text->mCiteLevel < oldCiteLevel)
+ {
+ prefaceResultStr += "</pre>";
+ for (uint32_t i = 0; i < oldCiteLevel - text->mCiteLevel; i++)
+ prefaceResultStr += "</blockquote>";
+ prefaceResultStr += "<pre wrap>\n";
+ }
+
+ // Write plain text quoting tags
+ if (logicalLineStart != 0 && !(plainHTML && text->mBlockquoting))
+ {
+ if (!plainHTML)
+ prefaceResultStr += "<span class=\"moz-txt-citetags\">";
+
+ nsString citeTagsSource(StringHead(lineSourceStr, logicalLineStart));
+
+ // Convert to HTML
+ nsString citeTagsResultUnichar;
+ rv = conv->ScanTXT(citeTagsSource.get(), 0 /* no recognition */,
+ getter_Copies(citeTagsResultUnichar));
+ if (NS_FAILED(rv)) return -1;
+
+ prefaceResultStr.Append(NS_ConvertUTF16toUTF8(citeTagsResultUnichar));
+ if (!plainHTML)
+ prefaceResultStr += "</span>";
+ }
+
+
+ // recognize signature
+ if ((lineSourceStr.Length() >= 4)
+ && lineSourceStr.First() == '-'
+ && Substring(lineSourceStr, 0, 3).EqualsLiteral("-- ")
+ && (lineSourceStr[3] == '\r' || lineSourceStr[3] == '\n') )
+ {
+ text->mIsSig = true;
+ if (!quoting)
+ prefaceResultStr += "<div class=\"moz-txt-sig\">";
+ }
+
+
+ /* This is the main TXT to HTML conversion:
+ escaping (very important), eventually recognizing etc. */
+ nsString lineResultUnichar;
+
+ rv = conv->ScanTXT(lineSourceStr.get() + logicalLineStart,
+ whattodo, getter_Copies(lineResultUnichar));
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ if (!(text->mIsSig && quoting && text->mStripSig))
+ {
+ status = MimeObject_write(obj, prefaceResultStr.get(), prefaceResultStr.Length(), true);
+ if (status < 0) return status;
+ nsAutoCString outString;
+ if (obj->options->format_out != nsMimeOutput::nsMimeMessageSaveAs ||
+ !mailCharset || !*mailCharset)
+ CopyUTF16toUTF8(lineResultUnichar, outString);
+ else
+ { // convert back to mailCharset before writing.
+ rv = nsMsgI18NConvertFromUnicode(mailCharset,
+ lineResultUnichar, outString);
+ NS_ENSURE_SUCCESS(rv, -1);
+ }
+
+ status = MimeObject_write(obj, outString.get(), outString.Length(), true);
+ }
+ else
+ {
+ status = 0;
+ }
+ }
+ else
+ {
+ status = MimeObject_write(obj, line, length, true);
+ }
+
+ return status;
+}
+
diff --git a/mailnews/mime/src/mimetpla.h b/mailnews/mime/src/mimetpla.h
new file mode 100644
index 000000000..b846403bc
--- /dev/null
+++ b/mailnews/mime/src/mimetpla.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The MimeInlineTextPlain class implements the text/plain MIME content type,
+ and is also used for all otherwise-unknown text/ subtypes.
+ */
+
+#ifndef _MIMETPLA_H_
+#define _MIMETPLA_H_
+
+#include "mimetext.h"
+
+typedef struct MimeInlineTextPlainClass MimeInlineTextPlainClass;
+typedef struct MimeInlineTextPlain MimeInlineTextPlain;
+
+struct MimeInlineTextPlainClass {
+ MimeInlineTextClass text;
+};
+
+extern MimeInlineTextPlainClass mimeInlineTextPlainClass;
+
+struct MimeInlineTextPlain {
+ MimeInlineText text;
+ uint32_t mCiteLevel;
+ bool mBlockquoting;
+ //bool mInsideQuote;
+ int32_t mQuotedSizeSetting; // mail.quoted_size
+ int32_t mQuotedStyleSetting; // mail.quoted_style
+ char *mCitationColor; // mail.citation_color
+ bool mStripSig; // mail.strip_sig_on_reply
+ bool mIsSig;
+};
+
+#define MimeInlineTextPlainClassInitializer(ITYPE,CSUPER) \
+ { MimeInlineTextClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMETPLA_H_ */
diff --git a/mailnews/mime/src/mimetric.cpp b/mailnews/mime/src/mimetric.cpp
new file mode 100644
index 000000000..95e08ac9b
--- /dev/null
+++ b/mailnews/mime/src/mimetric.cpp
@@ -0,0 +1,353 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mimetric.h"
+#include "mimebuf.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "msgCore.h"
+#include <ctype.h>
+
+#define MIME_SUPERCLASS mimeInlineTextClass
+MimeDefClass(MimeInlineTextRichtext, MimeInlineTextRichtextClass,
+ mimeInlineTextRichtextClass, &MIME_SUPERCLASS);
+
+static int MimeInlineTextRichtext_parse_line (const char *, int32_t, MimeObject *);
+static int MimeInlineTextRichtext_parse_begin (MimeObject *);
+static int MimeInlineTextRichtext_parse_eof (MimeObject *, bool);
+
+static int
+MimeInlineTextRichtextClassInitialize(MimeInlineTextRichtextClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ PR_ASSERT(!oclass->class_initialized);
+ oclass->parse_begin = MimeInlineTextRichtext_parse_begin;
+ oclass->parse_line = MimeInlineTextRichtext_parse_line;
+ oclass->parse_eof = MimeInlineTextRichtext_parse_eof;
+ return 0;
+}
+
+/* This function has this clunky interface because it needs to be called
+ from outside this module (no MimeObject, etc.)
+ */
+int
+MimeRichtextConvert (const char *line, int32_t length,
+ MimeObject *obj,
+ char **obufferP,
+ int32_t *obuffer_sizeP,
+ bool enriched_p)
+{
+ /* RFC 1341 (the original MIME spec) defined text/richtext.
+ RFC 1563 superceded text/richtext with text/enriched.
+ The changes from text/richtext to text/enriched are:
+ - CRLF semantics are different
+ - << maps to <
+ - These tags were added:
+ <VERBATIM>, <NOFILL>, <PARAM>, <FLUSHBOTH>
+ - These tags were removed:
+ <COMMENT>, <OUTDENT>, <OUTDENTRIGHT>, <SAMEPAGE>, <SUBSCRIPT>,
+ <SUPERSCRIPT>, <HEADING>, <FOOTING>, <PARAGRAPH>, <SIGNATURE>,
+ <LT>, <NL>, <NP>
+ This method implements them both.
+
+ draft-resnick-text-enriched-03.txt is a proposed update to 1563.
+ - These tags were added:
+ <FONTFAMILY>, <COLOR>, <PARAINDENT>, <LANG>.
+ However, all of these rely on the magic <PARAM> tag, which we
+ don't implement, so we're ignoring all of these.
+ Interesting fact: it's by Peter W. Resnick from Qualcomm (Eudora).
+ And it also says "It is fully expected that other text formatting
+ standards like HTML and SGML will supplant text/enriched in
+ Internet mail."
+ */
+ int status = 0;
+ char *out;
+ const char *data_end;
+ const char *last_end;
+ const char *this_start;
+ const char *this_end;
+ unsigned int desired_size;
+
+ // The code below must never expand the input by more than 5x;
+ // if it does, the desired_size multiplier (5) below must be changed too
+#define BGROWTH 5
+ if ( (uint32_t)length >= ( (uint32_t) 0xfffffffe)/BGROWTH )
+ return -1;
+ desired_size = (length * BGROWTH) + 1;
+#undef BGROWTH
+ if (desired_size >= (uint32_t) *obuffer_sizeP)
+ status = mime_GrowBuffer (desired_size, sizeof(char), 1024,
+ obufferP, obuffer_sizeP);
+ if (status < 0) return status;
+
+ if (enriched_p)
+ {
+ for (this_start = line; this_start < line + length; this_start++)
+ if (!IS_SPACE (*this_start)) break;
+ if (this_start >= line + length) /* blank line */
+ {
+ PL_strncpyz (*obufferP, "<BR>", *obuffer_sizeP);
+ return MimeObject_write(obj, *obufferP, strlen(*obufferP), true);
+ }
+ }
+
+ uint32_t outlen = (uint32_t) *obuffer_sizeP;
+ out = *obufferP;
+ *out = 0;
+
+ data_end = line + length;
+ last_end = line;
+ this_start = last_end;
+ this_end = this_start;
+ uint32_t addedlen = 0;
+ while (this_end < data_end)
+ {
+ /* Skip forward to next special character. */
+ while (this_start < data_end &&
+ *this_start != '<' && *this_start != '>' &&
+ *this_start != '&')
+ this_start++;
+
+ this_end = this_start;
+
+ /* Skip to the end of the tag. */
+ if (this_start < data_end && *this_start == '<')
+ {
+ this_end++;
+ while (this_end < data_end &&
+ !IS_SPACE(*this_end) &&
+ *this_end != '<' && *this_end != '>' &&
+ *this_end != '&')
+ this_end++;
+ }
+
+ this_end++;
+
+ /* Push out the text preceeding the tag. */
+ if (last_end && last_end != this_start)
+ {
+ memcpy (out, last_end, this_start - last_end);
+ out += this_start - last_end;
+ *out = 0;
+ outlen -= (this_start - last_end);
+ }
+
+ if (this_start >= data_end)
+ break;
+ else if (*this_start == '&')
+ {
+ PL_strncpyz (out, "&amp;", outlen);
+ addedlen = strlen(out);
+ outlen -= addedlen;
+ out += addedlen;
+ }
+ else if (*this_start == '>')
+ {
+ PL_strncpyz (out, "&gt;", outlen);
+ addedlen = strlen(out);
+ outlen -= addedlen;
+ out += addedlen;
+ }
+ else if (enriched_p &&
+ this_start < data_end + 1 &&
+ this_start[0] == '<' &&
+ this_start[1] == '<')
+ {
+ PL_strncpyz (out, "&lt;", outlen);
+ addedlen = strlen(out);
+ outlen -= addedlen;
+ out += addedlen;
+ }
+ else if (this_start != this_end)
+ {
+ /* Push out this ID. */
+ const char *old = this_start + 1;
+ const char *tag_open = 0;
+ const char *tag_close = 0;
+ if (*old == '/')
+ {
+ /* This is </tag> */
+ old++;
+ }
+
+ switch (*old)
+ {
+ case 'b': case 'B':
+ if (!PL_strncasecmp ("BIGGER>", old, 7))
+ tag_open = "<FONT SIZE=\"+1\">", tag_close = "</FONT>";
+ else if (!PL_strncasecmp ("BLINK>", old, 5))
+ /* Of course, both text/richtext and text/enriched must be
+ enhanced *somehow*... Or else what would people think. */
+ tag_open = "<BLINK>", tag_close = "</BLINK>";
+ else if (!PL_strncasecmp ("BOLD>", old, 5))
+ tag_open = "<B>", tag_close = "</B>";
+ break;
+ case 'c': case 'C':
+ if (!PL_strncasecmp ("CENTER>", old, 7))
+ tag_open = "<CENTER>", tag_close = "</CENTER>";
+ else if (!enriched_p &&
+ !PL_strncasecmp ("COMMENT>", old, 8))
+ tag_open = "<!-- ", tag_close = " -->";
+ break;
+ case 'e': case 'E':
+ if (!PL_strncasecmp ("EXCERPT>", old, 8))
+ tag_open = "<BLOCKQUOTE>", tag_close = "</BLOCKQUOTE>";
+ break;
+ case 'f': case 'F':
+ if (!PL_strncasecmp ("FIXED>", old, 6))
+ tag_open = "<TT>", tag_close = "</TT>";
+ else if (enriched_p &&
+ !PL_strncasecmp ("FLUSHBOTH>", old, 10))
+ tag_open = "<P ALIGN=LEFT>", tag_close = "</P>";
+ else if (!PL_strncasecmp ("FLUSHLEFT>", old, 10))
+ tag_open = "<P ALIGN=LEFT>", tag_close = "</P>";
+ else if (!PL_strncasecmp ("FLUSHRIGHT>", old, 11))
+ tag_open = "<P ALIGN=RIGHT>", tag_close = "</P>";
+ else if (!enriched_p &&
+ !PL_strncasecmp ("FOOTING>", old, 8))
+ tag_open = "<H6>", tag_close = "</H6>";
+ break;
+ case 'h': case 'H':
+ if (!enriched_p &&
+ !PL_strncasecmp ("HEADING>", old, 8))
+ tag_open = "<H6>", tag_close = "</H6>";
+ break;
+ case 'i': case 'I':
+ if (!PL_strncasecmp ("INDENT>", old, 7))
+ tag_open = "<UL>", tag_close = "</UL>";
+ else if (!PL_strncasecmp ("INDENTRIGHT>", old, 12))
+ tag_open = 0, tag_close = 0;
+/* else if (!enriched_p &&
+ !PL_strncasecmp ("ISO-8859-", old, 9))
+ tag_open = 0, tag_close = 0; */
+ else if (!PL_strncasecmp ("ITALIC>", old, 7))
+ tag_open = "<I>", tag_close = "</I>";
+ break;
+ case 'l': case 'L':
+ if (!enriched_p &&
+ !PL_strncasecmp ("LT>", old, 3))
+ tag_open = "&lt;", tag_close = 0;
+ break;
+ case 'n': case 'N':
+ if (!enriched_p &&
+ !PL_strncasecmp ("NL>", old, 3))
+ tag_open = "<BR>", tag_close = 0;
+ if (enriched_p &&
+ !PL_strncasecmp ("NOFILL>", old, 7))
+ tag_open = "<NOBR>", tag_close = "</NOBR>";
+/* else if (!enriched_p &&
+ !PL_strncasecmp ("NO-OP>", old, 6))
+ tag_open = 0, tag_close = 0; */
+/* else if (!enriched_p &&
+ !PL_strncasecmp ("NP>", old, 3))
+ tag_open = 0, tag_close = 0; */
+ break;
+ case 'o': case 'O':
+ if (!enriched_p &&
+ !PL_strncasecmp ("OUTDENT>", old, 8))
+ tag_open = 0, tag_close = 0;
+ else if (!enriched_p &&
+ !PL_strncasecmp ("OUTDENTRIGHT>", old, 13))
+ tag_open = 0, tag_close = 0;
+ break;
+ case 'p': case 'P':
+ if (enriched_p &&
+ !PL_strncasecmp ("PARAM>", old, 6))
+ tag_open = "<!-- ", tag_close = " -->";
+ else if (!enriched_p &&
+ !PL_strncasecmp ("PARAGRAPH>", old, 10))
+ tag_open = "<P>", tag_close = 0;
+ break;
+ case 's': case 'S':
+ if (!enriched_p &&
+ !PL_strncasecmp ("SAMEPAGE>", old, 9))
+ tag_open = 0, tag_close = 0;
+ else if (!enriched_p &&
+ !PL_strncasecmp ("SIGNATURE>", old, 10))
+ tag_open = "<I><FONT SIZE=\"-1\">", tag_close = "</FONT></I>";
+ else if (!PL_strncasecmp ("SMALLER>", old, 8))
+ tag_open = "<FONT SIZE=\"-1\">", tag_close = "</FONT>";
+ else if (!enriched_p &&
+ !PL_strncasecmp ("SUBSCRIPT>", old, 10))
+ tag_open = "<SUB>", tag_close = "</SUB>";
+ else if (!enriched_p &&
+ !PL_strncasecmp ("SUPERSCRIPT>", old, 12))
+ tag_open = "<SUP>", tag_close = "</SUP>";
+ break;
+ case 'u': case 'U':
+ if (!PL_strncasecmp ("UNDERLINE>", old, 10))
+ tag_open = "<U>", tag_close = "</U>";
+/* else if (!enriched_p &&
+ !PL_strncasecmp ("US-ASCII>", old, 10))
+ tag_open = 0, tag_close = 0; */
+ break;
+ case 'v': case 'V':
+ if (enriched_p &&
+ !PL_strncasecmp ("VERBATIM>", old, 9))
+ tag_open = "<PRE>", tag_close = "</PRE>";
+ break;
+ }
+
+ if (this_start[1] == '/')
+ {
+ if (tag_close) PL_strncpyz (out, tag_close, outlen);
+ addedlen = strlen (out);
+ outlen -= addedlen;
+ out += addedlen;
+ }
+ else
+ {
+ if (tag_open) PL_strncpyz (out, tag_open, outlen);
+ addedlen = strlen (out);
+ outlen -= addedlen;
+ out += addedlen;
+ }
+ }
+
+ /* now go around again */
+ last_end = this_end;
+ this_start = last_end;
+ }
+ *out = 0;
+
+ return MimeObject_write(obj, *obufferP, out - *obufferP, true);
+}
+
+
+static int
+MimeInlineTextRichtext_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ bool enriched_p = (((MimeInlineTextRichtextClass *) obj->clazz)
+ ->enriched_p);
+
+ return MimeRichtextConvert (line, length,
+ obj,
+ &obj->obuffer, &obj->obuffer_size,
+ enriched_p);
+}
+
+
+static int
+MimeInlineTextRichtext_parse_begin (MimeObject *obj)
+{
+ int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+ char s[] = "";
+ if (status < 0) return status;
+ return MimeObject_write(obj, s, 0, true); /* force out any separators... */
+}
+
+
+static int
+MimeInlineTextRichtext_parse_eof (MimeObject *obj, bool abort_p)
+{
+ int status;
+ if (obj->closed_p) return 0;
+
+ /* Run parent method first, to flush out any buffered data. */
+ status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
+ if (status < 0) return status;
+
+ return 0;
+}
diff --git a/mailnews/mime/src/mimetric.h b/mailnews/mime/src/mimetric.h
new file mode 100644
index 000000000..fe6c66974
--- /dev/null
+++ b/mailnews/mime/src/mimetric.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMETRIC_H_
+#define _MIMETRIC_H_
+
+#include "mimetext.h"
+
+/* The MimeInlineTextRichtext class implements the (obsolete and deprecated)
+ text/richtext MIME content type, as defined in RFC 1341, and also the
+ text/enriched MIME content type, as defined in RFC 1563.
+ */
+
+typedef struct MimeInlineTextRichtextClass MimeInlineTextRichtextClass;
+typedef struct MimeInlineTextRichtext MimeInlineTextRichtext;
+
+struct MimeInlineTextRichtextClass {
+ MimeInlineTextClass text;
+ bool enriched_p; /* Whether we should act like text/enriched instead. */
+};
+
+extern MimeInlineTextRichtextClass mimeInlineTextRichtextClass;
+
+struct MimeInlineTextRichtext {
+ MimeInlineText text;
+};
+
+#define MimeInlineTextRichtextClassInitializer(ITYPE,CSUPER) \
+ { MimeInlineTextClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMETRIC_H_ */
diff --git a/mailnews/mime/src/mimeunty.cpp b/mailnews/mime/src/mimeunty.cpp
new file mode 100644
index 000000000..212b08ba8
--- /dev/null
+++ b/mailnews/mime/src/mimeunty.cpp
@@ -0,0 +1,588 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimeunty.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "nsMimeTypes.h"
+#include "msgCore.h"
+#include "nsMimeStringResources.h"
+#include <ctype.h>
+
+#define MIME_SUPERCLASS mimeContainerClass
+MimeDefClass(MimeUntypedText, MimeUntypedTextClass,
+ mimeUntypedTextClass, &MIME_SUPERCLASS);
+
+static int MimeUntypedText_initialize (MimeObject *);
+static void MimeUntypedText_finalize (MimeObject *);
+static int MimeUntypedText_parse_begin (MimeObject *);
+static int MimeUntypedText_parse_line (const char *, int32_t, MimeObject *);
+
+static int MimeUntypedText_open_subpart (MimeObject *obj,
+ MimeUntypedTextSubpartType ttype,
+ const char *type,
+ const char *enc,
+ const char *name,
+ const char *desc);
+static int MimeUntypedText_close_subpart (MimeObject *obj);
+
+static bool MimeUntypedText_uu_begin_line_p(const char *line, int32_t length,
+ MimeDisplayOptions *opt,
+ char **type_ret,
+ char **name_ret);
+static bool MimeUntypedText_uu_end_line_p(const char *line, int32_t length);
+
+static bool MimeUntypedText_yenc_begin_line_p(const char *line, int32_t length,
+ MimeDisplayOptions *opt,
+ char **type_ret,
+ char **name_ret);
+static bool MimeUntypedText_yenc_end_line_p(const char *line, int32_t length);
+
+static bool MimeUntypedText_binhex_begin_line_p(const char *line,
+ int32_t length,
+ MimeDisplayOptions *opt);
+static bool MimeUntypedText_binhex_end_line_p(const char *line,
+ int32_t length);
+
+static int
+MimeUntypedTextClassInitialize(MimeUntypedTextClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *) clazz;
+ PR_ASSERT(!oclass->class_initialized);
+ oclass->initialize = MimeUntypedText_initialize;
+ oclass->finalize = MimeUntypedText_finalize;
+ oclass->parse_begin = MimeUntypedText_parse_begin;
+ oclass->parse_line = MimeUntypedText_parse_line;
+ return 0;
+}
+
+
+static int
+MimeUntypedText_initialize (MimeObject *object)
+{
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object);
+}
+
+static void
+MimeUntypedText_finalize (MimeObject *object)
+{
+ MimeUntypedText *uty = (MimeUntypedText *) object;
+
+ if (uty->open_hdrs)
+ {
+ /* Oops, those shouldn't still be here... */
+ MimeHeaders_free(uty->open_hdrs);
+ uty->open_hdrs = 0;
+ }
+
+ /* What about the open_subpart? We're gonna have to assume that it
+ is also on the MimeContainer->children list, and will get cleaned
+ up by that class. */
+
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object);
+}
+
+static int
+MimeUntypedText_parse_begin (MimeObject *obj)
+{
+ return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
+}
+
+static int
+MimeUntypedText_parse_line (const char *line, int32_t length, MimeObject *obj)
+{
+ MimeUntypedText *uty = (MimeUntypedText *) obj;
+ int status = 0;
+ char *name = 0, *type = 0;
+ bool begin_line_p = false;
+
+ NS_ASSERTION(line && *line, "empty line in mime untyped parse_line");
+ if (!line || !*line) return -1;
+
+ /* If we're supposed to write this object, but aren't supposed to convert
+ it to HTML, simply pass it through unaltered. */
+ if (obj->output_p &&
+ obj->options &&
+ !obj->options->write_html_p &&
+ obj->options->output_fn)
+ return MimeObject_write(obj, line, length, true);
+
+
+ /* Open a new sub-part if this line demands it.
+ */
+ if (line[0] == 'b' &&
+ MimeUntypedText_uu_begin_line_p(line, length, obj->options,
+ &type, &name))
+ {
+ /* Close the old part and open a new one. */
+ status = MimeUntypedText_open_subpart (obj,
+ MimeUntypedTextSubpartTypeUUE,
+ type, ENCODING_UUENCODE,
+ name, NULL);
+ PR_FREEIF(name);
+ PR_FREEIF(type);
+ if (status < 0) return status;
+ begin_line_p = true;
+ }
+
+ else if (line[0] == '=' &&
+ MimeUntypedText_yenc_begin_line_p(line, length, obj->options,
+ &type, &name))
+ {
+ /* Close the old part and open a new one. */
+ status = MimeUntypedText_open_subpart (obj,
+ MimeUntypedTextSubpartTypeYEnc,
+ type, ENCODING_YENCODE,
+ name, NULL);
+ PR_FREEIF(name);
+ PR_FREEIF(type);
+ if (status < 0) return status;
+ begin_line_p = true;
+ }
+
+ else if (line[0] == '(' && line[1] == 'T' &&
+ MimeUntypedText_binhex_begin_line_p(line, length, obj->options))
+ {
+ /* Close the old part and open a new one. */
+ status = MimeUntypedText_open_subpart (obj,
+ MimeUntypedTextSubpartTypeBinhex,
+ APPLICATION_BINHEX, NULL,
+ NULL, NULL);
+ if (status < 0) return status;
+ begin_line_p = true;
+ }
+
+ /* Open a text/plain sub-part if there is no sub-part open.
+ */
+ if (!uty->open_subpart)
+ {
+ // rhp: If we get here and we are being fed a line ending, we should
+ // just eat it and continue and if we really get more data, we'll open
+ // up the subpart then.
+ //
+ if (line[0] == '\r') return 0;
+ if (line[0] == '\n') return 0;
+
+ PR_ASSERT(!begin_line_p);
+ status = MimeUntypedText_open_subpart (obj,
+ MimeUntypedTextSubpartTypeText,
+ TEXT_PLAIN, NULL, NULL, NULL);
+ PR_ASSERT(uty->open_subpart);
+ if (!uty->open_subpart) return -1;
+ if (status < 0) return status;
+ }
+
+ /* Hand this line to the currently-open sub-part.
+ */
+ status = uty->open_subpart->clazz->parse_buffer(line, length,
+ uty->open_subpart);
+ if (status < 0) return status;
+
+ /* Close this sub-part if this line demands it.
+ */
+ if (begin_line_p)
+ ;
+ else if (line[0] == 'e' &&
+ uty->type == MimeUntypedTextSubpartTypeUUE &&
+ MimeUntypedText_uu_end_line_p(line, length))
+ {
+ status = MimeUntypedText_close_subpart (obj);
+ if (status < 0) return status;
+ NS_ASSERTION(!uty->open_subpart, "no open subpart");
+ }
+ else if (line[0] == '=' &&
+ uty->type == MimeUntypedTextSubpartTypeYEnc &&
+ MimeUntypedText_yenc_end_line_p(line, length))
+ {
+ status = MimeUntypedText_close_subpart (obj);
+ if (status < 0) return status;
+ NS_ASSERTION(!uty->open_subpart, "no open subpart");
+ }
+ else if (uty->type == MimeUntypedTextSubpartTypeBinhex &&
+ MimeUntypedText_binhex_end_line_p(line, length))
+ {
+ status = MimeUntypedText_close_subpart (obj);
+ if (status < 0) return status;
+ NS_ASSERTION(!uty->open_subpart, "no open subpart");
+ }
+
+ return 0;
+}
+
+
+static int
+MimeUntypedText_close_subpart (MimeObject *obj)
+{
+ MimeUntypedText *uty = (MimeUntypedText *) obj;
+ int status;
+
+ if (uty->open_subpart)
+ {
+ status = uty->open_subpart->clazz->parse_eof(uty->open_subpart, false);
+ uty->open_subpart = 0;
+
+ PR_ASSERT(uty->open_hdrs);
+ if (uty->open_hdrs)
+ {
+ MimeHeaders_free(uty->open_hdrs);
+ uty->open_hdrs = 0;
+ }
+ uty->type = MimeUntypedTextSubpartTypeText;
+ if (status < 0) return status;
+
+ /* Never put out a separator between sub-parts of UntypedText.
+ (This bypasses the rule that text/plain subparts always
+ have separators before and after them.)
+ */
+ if (obj->options && obj->options->state)
+ obj->options->state->separator_suppressed_p = true;
+ }
+
+ PR_ASSERT(!uty->open_hdrs);
+ return 0;
+}
+
+static int
+MimeUntypedText_open_subpart (MimeObject *obj,
+ MimeUntypedTextSubpartType ttype,
+ const char *type,
+ const char *enc,
+ const char *name,
+ const char *desc)
+{
+ MimeUntypedText *uty = (MimeUntypedText *) obj;
+ int status = 0;
+ char *h = 0;
+
+ if (!type || !*type || !PL_strcasecmp(type, UNKNOWN_CONTENT_TYPE))
+ type = APPLICATION_OCTET_STREAM;
+ if (enc && !*enc)
+ enc = 0;
+ if (desc && !*desc)
+ desc = 0;
+ if (name && !*name)
+ name = 0;
+
+ if (uty->open_subpart)
+ {
+ status = MimeUntypedText_close_subpart (obj);
+ if (status < 0) return status;
+ }
+ NS_ASSERTION(!uty->open_subpart, "no open subpart");
+ NS_ASSERTION(!uty->open_hdrs, "no open headers");
+
+ /* To make one of these implicitly-typed sub-objects, we make up a fake
+ header block, containing only the minimum number of MIME headers needed.
+ We could do most of this (Type and Encoding) by making a null header
+ block, and simply setting obj->content_type and obj->encoding; but making
+ a fake header block is better for two reasons: first, it means that
+ something will actually be displayed when in `Show All Headers' mode;
+ and second, it's the only way to communicate the filename parameter,
+ aside from adding a new slot to MimeObject (which is something to be
+ avoided when possible.)
+ */
+
+ uty->open_hdrs = MimeHeaders_new();
+ if (!uty->open_hdrs) return MIME_OUT_OF_MEMORY;
+
+ uint32_t hlen = strlen(type) +
+ (enc ? strlen(enc) : 0) +
+ (desc ? strlen(desc) : 0) +
+ (name ? strlen(name) : 0) +
+ 100;
+ h = (char *) PR_MALLOC(hlen);
+ if (!h) return MIME_OUT_OF_MEMORY;
+
+ PL_strncpyz(h, HEADER_CONTENT_TYPE ": ", hlen);
+ PL_strcatn(h, hlen, type);
+ PL_strcatn(h, hlen, MSG_LINEBREAK);
+ status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs);
+ if (status < 0) goto FAIL;
+
+ if (enc)
+ {
+ PL_strncpyz(h, HEADER_CONTENT_TRANSFER_ENCODING ": ", hlen);
+ PL_strcatn(h, hlen, enc);
+ PL_strcatn(h, hlen, MSG_LINEBREAK);
+ status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs);
+ if (status < 0) goto FAIL;
+ }
+
+ if (desc)
+ {
+ PL_strncpyz(h, HEADER_CONTENT_DESCRIPTION ": ", hlen);
+ PL_strcatn(h, hlen, desc);
+ PL_strcatn(h, hlen, MSG_LINEBREAK);
+ status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs);
+ if (status < 0) goto FAIL;
+ }
+ if (name)
+ {
+ PL_strncpyz(h, HEADER_CONTENT_DISPOSITION ": inline; filename=\"", hlen);
+ PL_strcatn(h, hlen, name);
+ PL_strcatn(h, hlen, "\"" MSG_LINEBREAK);
+ status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs);
+ if (status < 0) goto FAIL;
+ }
+
+ /* push out a blank line. */
+ PL_strncpyz(h, MSG_LINEBREAK, hlen);
+ status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs);
+ if (status < 0) goto FAIL;
+
+
+ /* Create a child... */
+ {
+ bool horrid_kludge = (obj->options && obj->options->state &&
+ obj->options->state->first_part_written_p);
+ if (horrid_kludge)
+ obj->options->state->first_part_written_p = false;
+
+ uty->open_subpart = mime_create(type, uty->open_hdrs, obj->options);
+
+ if (horrid_kludge)
+ obj->options->state->first_part_written_p = true;
+
+ if (!uty->open_subpart)
+ {
+ status = MIME_OUT_OF_MEMORY;
+ goto FAIL;
+ }
+ }
+
+ /* Add it to the list... */
+ status = ((MimeContainerClass *) obj->clazz)->add_child(obj,
+ uty->open_subpart);
+ if (status < 0)
+ {
+ mime_free(uty->open_subpart);
+ uty->open_subpart = 0;
+ goto FAIL;
+ }
+
+ /* And start its parser going. */
+ status = uty->open_subpart->clazz->parse_begin(uty->open_subpart);
+ if (status < 0)
+ {
+ /* MimeContainer->finalize will take care of shutting it down now. */
+ uty->open_subpart = 0;
+ goto FAIL;
+ }
+
+ uty->type = ttype;
+
+ FAIL:
+ PR_FREEIF(h);
+
+ if (status < 0 && uty->open_hdrs)
+ {
+ MimeHeaders_free(uty->open_hdrs);
+ uty->open_hdrs = 0;
+ }
+
+ return status;
+}
+
+static bool
+MimeUntypedText_uu_begin_line_p(const char *line, int32_t length,
+ MimeDisplayOptions *opt,
+ char **type_ret, char **name_ret)
+{
+ const char *s;
+ char *name = 0;
+ char *type = 0;
+
+ if (type_ret) *type_ret = 0;
+ if (name_ret) *name_ret = 0;
+
+ if (strncmp (line, "begin ", 6)) return false;
+ /* ...then three or four octal digits. */
+ s = line + 6;
+ if (*s < '0' || *s > '7') return false;
+ s++;
+ if (*s < '0' || *s > '7') return false;
+ s++;
+ if (*s < '0' || *s > '7') return false;
+ s++;
+ if (*s == ' ')
+ s++;
+ else
+ {
+ if (*s < '0' || *s > '7') return false;
+ s++;
+ if (*s != ' ') return false;
+ }
+
+ while (IS_SPACE(*s))
+ s++;
+
+ name = (char *) PR_MALLOC(((line+length)-s) + 1);
+ if (!name) return false; /* grr... */
+ memcpy(name, s, (line+length)-s);
+ name[(line+length)-s] = 0;
+
+ /* take off newline. */
+ if (name[strlen(name)-1] == '\n') name[strlen(name)-1] = 0;
+ if (name[strlen(name)-1] == '\r') name[strlen(name)-1] = 0;
+
+ /* Now try and figure out a type.
+ */
+ if (opt && opt->file_type_fn)
+ type = opt->file_type_fn(name, opt->stream_closure);
+ else
+ type = 0;
+
+ if (name_ret)
+ *name_ret = name;
+ else
+ PR_FREEIF(name);
+
+ if (type_ret)
+ *type_ret = type;
+ else
+ PR_FREEIF(type);
+
+ return true;
+}
+
+static bool
+MimeUntypedText_uu_end_line_p(const char *line, int32_t length)
+{
+#if 0
+ /* A strictly conforming uuencode end line. */
+ return (line[0] == 'e' &&
+ line[1] == 'n' &&
+ line[2] == 'd' &&
+ (line[3] == 0 || IS_SPACE(line[3])));
+#else
+ /* ...but, why don't we accept any line that begins with the three
+ letters "END" in any case: I've seen lots of partial messages
+ that look like
+
+ BEGIN----- Cut Here-----
+ begin 644 foo.gif
+ Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ END------- Cut Here-----
+
+ so let's be lenient here. (This is only for the untyped-text-plain
+ case -- the uudecode parser itself is strict.)
+ */
+ return (line[0] == ' ' ||
+ line[0] == '\t' ||
+ ((line[0] == 'e' || line[0] == 'E') &&
+ (line[1] == 'n' || line[1] == 'N') &&
+ (line[2] == 'd' || line[2] == 'D')));
+#endif
+}
+
+static bool
+MimeUntypedText_yenc_begin_line_p(const char *line, int32_t length,
+ MimeDisplayOptions *opt,
+ char **type_ret, char **name_ret)
+{
+ const char *s;
+ const char *endofline = line + length;
+ char *name = 0;
+ char *type = 0;
+
+ if (type_ret) *type_ret = 0;
+ if (name_ret) *name_ret = 0;
+
+ /* we don't support yenc V2 neither multipart yencode,
+ therefore the second parameter should always be "line="*/
+ if (length < 13 || strncmp (line, "=ybegin line=", 13)) return false;
+
+ /* ...then couple digits. */
+ for (s = line + 13; s < endofline; s ++)
+ if (*s < '0' || *s > '9')
+ break;
+
+ /* ...next, look for <space>size= */
+ if ((endofline - s) < 6 || strncmp (s, " size=", 6)) return false;
+
+ /* ...then couple digits. */
+ for (s += 6; s < endofline; s ++)
+ if (*s < '0' || *s > '9')
+ break;
+
+ /* ...next, look for <space>name= */
+ if ((endofline - s) < 6 || strncmp (s, " name=", 6)) return false;
+
+ /* anything left is the file name */
+ s += 6;
+ name = (char *) PR_MALLOC((endofline-s) + 1);
+ if (!name) return false; /* grr... */
+ memcpy(name, s, endofline-s);
+ name[endofline-s] = 0;
+
+ /* take off newline. */
+ if (name[strlen(name)-1] == '\n') name[strlen(name)-1] = 0;
+ if (name[strlen(name)-1] == '\r') name[strlen(name)-1] = 0;
+
+ /* Now try and figure out a type.
+ */
+ if (opt && opt->file_type_fn)
+ type = opt->file_type_fn(name, opt->stream_closure);
+ else
+ type = 0;
+
+ if (name_ret)
+ *name_ret = name;
+ else
+ PR_FREEIF(name);
+
+ if (type_ret)
+ *type_ret = type;
+ else
+ PR_FREEIF(type);
+
+ return true;
+}
+
+static bool
+MimeUntypedText_yenc_end_line_p(const char *line, int32_t length)
+{
+ if (length < 11 || strncmp (line, "=yend size=", 11)) return false;
+
+ return true;
+}
+
+
+#define BINHEX_MAGIC "(This file must be converted with BinHex 4.0)"
+#define BINHEX_MAGIC_LEN 45
+
+static bool
+MimeUntypedText_binhex_begin_line_p(const char *line, int32_t length,
+ MimeDisplayOptions *opt)
+{
+ if (length <= BINHEX_MAGIC_LEN)
+ return false;
+
+ while(length > 0 && IS_SPACE(line[length-1]))
+ length--;
+
+ if (length != BINHEX_MAGIC_LEN)
+ return false;
+
+ if (!strncmp(line, BINHEX_MAGIC, BINHEX_MAGIC_LEN))
+ return true;
+ else
+ return false;
+}
+
+static bool
+MimeUntypedText_binhex_end_line_p(const char *line, int32_t length)
+{
+ if (length > 0 && line[length-1] == '\n') length--;
+ if (length > 0 && line[length-1] == '\r') length--;
+
+ if (length != 0 && length != 64)
+ return true;
+ else
+ return false;
+}
diff --git a/mailnews/mime/src/mimeunty.h b/mailnews/mime/src/mimeunty.h
new file mode 100644
index 000000000..9661f68bc
--- /dev/null
+++ b/mailnews/mime/src/mimeunty.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MIMEUNTY_H_
+#define _MIMEUNTY_H_
+
+#include "mimecont.h"
+
+/* The MimeUntypedText class is used for untyped message contents, that is,
+ it is the class used for the body of a message/rfc822 object which had
+ *no* Content-Type header, as opposed to an unrecognized content-type.
+ Such a message, technically, does not contain MIME data (it follows only
+ RFC 822, not RFC 1521.)
+
+ This is a container class, and the reason for that is that it loosely
+ parses the body of the message looking for ``sub-parts'' and then
+ creates appropriate containers for them.
+
+ More specifically, it looks for uuencoded data. It may do more than that
+ some day.
+
+ Basically, the algorithm followed is:
+
+ if line is "begin 644 foo.gif"
+ if there is an open sub-part, close it
+ add a sub-part with type: image/gif; encoding: x-uue
+ hand this line to it
+ and hand subsequent lines to that subpart
+ else if there is an open uuencoded sub-part, and line is "end"
+ hand this line to it
+ close off the uuencoded sub-part
+ else if there is an open sub-part
+ hand this line to it
+ else
+ open a text/plain subpart
+ hand this line to it
+
+ Adding other types than uuencode to this (for example, PGP) would be
+ pretty straightforward.
+ */
+
+typedef struct MimeUntypedTextClass MimeUntypedTextClass;
+typedef struct MimeUntypedText MimeUntypedText;
+
+struct MimeUntypedTextClass {
+ MimeContainerClass container;
+};
+
+extern MimeUntypedTextClass mimeUntypedTextClass;
+
+typedef enum {
+ MimeUntypedTextSubpartTypeText, /* text/plain */
+ MimeUntypedTextSubpartTypeUUE, /* uuencoded data */
+ MimeUntypedTextSubpartTypeYEnc, /* yencoded data */
+ MimeUntypedTextSubpartTypeBinhex /* Mac BinHex data */
+} MimeUntypedTextSubpartType;
+
+struct MimeUntypedText {
+ MimeContainer container; /* superclass variables */
+ MimeObject *open_subpart; /* The part still-being-parsed */
+ MimeUntypedTextSubpartType type; /* What kind of type it is */
+ MimeHeaders *open_hdrs; /* The faked-up headers describing it */
+};
+
+#define MimeUntypedTextClassInitializer(ITYPE,CSUPER) \
+ { MimeContainerClassInitializer(ITYPE,CSUPER) }
+
+#endif /* _MIMEUNTY_H_ */
diff --git a/mailnews/mime/src/modlmime.h b/mailnews/mime/src/modlmime.h
new file mode 100644
index 000000000..547739885
--- /dev/null
+++ b/mailnews/mime/src/modlmime.h
@@ -0,0 +1,398 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _LIBMIME_H_
+#define _LIBMIME_H_
+
+#ifdef XP_UNIX
+#undef Bool
+#endif
+
+#include "nsStringGlue.h"
+#include "nsMailHeaders.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsIUnicodeEncoder.h"
+#include "nsIPrefBranch.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsCOMPtr.h"
+#include "modmimee.h" // for MimeConverterOutputCallback
+
+#define MIME_DRAFTS
+
+/* Opaque object describing a block of message headers, and a couple of
+ routines for extracting data from one.
+ */
+
+typedef struct MimeHeaders
+{
+ char *all_headers; /* A char* of the entire header section. */
+ int32_t all_headers_fp; /* The length (it is not NULL-terminated.) */
+ int32_t all_headers_size; /* The size of the allocated block. */
+
+ bool done_p; /* Whether we've read the end-of-headers marker
+ (the terminating blank line.) */
+
+ char **heads; /* An array of length n_headers which points
+ to the beginning of each distinct header:
+ just after the newline which terminated
+ the previous one. This is to speed search.
+
+ This is not initialized until all the
+ headers have been read.
+ */
+ int32_t heads_size; /* The length (and consequently, how many
+ distinct headers are in here.) */
+
+
+ char *obuffer; /* This buffer is used for output. */
+ int32_t obuffer_size;
+ int32_t obuffer_fp;
+
+ char *munged_subject; /* What a hack. This is a place to write down
+ the subject header, after it's been
+ charset-ified and stuff. Remembered so that
+ we can later use it to generate the
+ <TITLE> tag. (Also works for giving names to RFC822 attachments) */
+} MimeHeaders;
+
+class MimeDisplayOptions;
+class MimeParseStateObject;
+typedef struct MSG_AttachmentData MSG_AttachmentData;
+
+/* Given the name of a header, returns the contents of that header as
+ a newly-allocated string (which the caller must free.) If the header
+ is not present, or has no contents, NULL is returned.
+
+ If `strip_p' is true, then the data returned will be the first token
+ of the header; else it will be the full text of the header. (This is
+ useful for getting just "text/plain" from "text/plain; name=foo".)
+
+ If `all_p' is false, then the first header encountered is used, and
+ any subsequent headers of the same name are ignored. If true, then
+ all headers of the same name are appended together (this is useful
+ for gathering up all CC headers into one, for example.)
+ */
+extern char *MimeHeaders_get(MimeHeaders *hdrs,
+ const char *header_name,
+ bool strip_p,
+ bool all_p);
+
+/* Given a header of the form of the MIME "Content-" headers, extracts a
+ named parameter from it, if it exists. For example,
+ MimeHeaders_get_parameter("text/plain; charset=us-ascii", "charset")
+ would return "us-ascii".
+
+ Returns NULL if there is no match, or if there is an allocation failure.
+
+ RFC2231 - MIME Parameter Value and Encoded Word Extensions: Character Sets,
+ Languages, and Continuations
+
+ RFC2231 has added the character sets, languages, and continuations mechanism.
+ charset, and language information may also be returned to the caller.
+ Note that charset and language should be free()'d while
+ the return value (parameter) has to be PR_FREE'd.
+
+ For example,
+ MimeHeaders_get_parameter("text/plain; name*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A", "name")
+ MimeHeaders_get_parameter("text/plain; name*0*=us-ascii'en-us'This%20is%20; CRLFLWSPname*1*=%2A%2A%2Afun%2A%2A%2A", "name")
+ would return "This is ***fun***" and *charset = "us-ascii", *language = "en-us"
+ */
+extern char *MimeHeaders_get_parameter (const char *header_value,
+ const char *parm_name,
+ char **charset,
+ char **language);
+
+extern MimeHeaders *MimeHeaders_copy (MimeHeaders *srcHeaders);
+
+extern void MimeHeaders_free (MimeHeaders *hdrs);
+
+typedef enum {
+ MimeHeadersAll, /* Show all headers */
+ MimeHeadersSome, /* Show all "interesting" headers */
+ MimeHeadersSomeNoRef, /* Same, but suppress the `References' header
+ (for when we're printing this message.) */
+ MimeHeadersMicro, /* Show a one-line header summary */
+ MimeHeadersMicroPlus, /* Same, but show the full recipient list as
+ well (To, CC, etc.) */
+ MimeHeadersCitation, /* A one-line summary geared toward use in a
+ reply citation ("So-and-so wrote:") */
+ MimeHeadersOnly, /* Just parse and output headers...nothing else! */
+ MimeHeadersNone /* Skip showing any headers */
+} MimeHeadersState;
+
+
+/* The signature for various callbacks in the MimeDisplayOptions structure.
+ */
+typedef char *(*MimeHTMLGeneratorFunction) (const char *data, void *closure,
+ MimeHeaders *headers);
+
+class MimeDisplayOptions
+{
+public:
+ MimeDisplayOptions();
+ virtual ~MimeDisplayOptions();
+ mozITXTToHTMLConv *conv; // For text conversion...
+ nsCOMPtr<nsIPrefBranch> m_prefBranch; /* prefBranch-service */
+ nsMimeOutputType format_out; // The format out type
+ nsCString charsetForCachedInputDecoder;
+ nsCOMPtr<nsIUnicodeDecoder> m_inputCharsetToUnicodeDecoder;
+ nsCOMPtr<nsIUnicodeEncoder> m_unicodeToUTF8Encoder;
+
+ const char *url; /* Base URL for the document. This string should
+ be freed by the caller, after the parser
+ completes (possibly at the same time as the
+ MimeDisplayOptions itself.) */
+
+ MimeHeadersState headers; /* How headers should be displayed. */
+ bool fancy_headers_p; /* Whether to do clever formatting of headers
+ using tables, instead of spaces. */
+
+ bool output_vcard_buttons_p; /* Whether to output the buttons */
+ /* on vcards. */
+
+ bool variable_width_plaintext_p; /* Whether text/plain messages should
+ be in variable width, or fixed. */
+ bool wrap_long_lines_p; /* Whether to wrap long lines in text/plain
+ messages. */
+
+ bool rot13_p; /* Whether text/plain parts should be rotated
+ Set by "?rot13=true" */
+ char *part_to_load; /* The particular part of the multipart which
+ we are extracting. Set by "?part=3.2.4" */
+
+ bool no_output_p; /* Will never write output when this is true.
+ (When false, output or not may depend on other things.)
+ This needs to be in the options, because the method
+ MimeObject_parse_begin is controlling the property "output_p"
+ (on the MimeObject) so we need a way for other functions to
+ override it and tell that we do not want output. */
+
+ bool write_html_p; /* Whether the output should be HTML, or raw. */
+
+ bool decrypt_p; /* Whether all traces of xlateion should be
+ eradicated -- this is only meaningful when
+ write_html_p is false; we set this when
+ attaching a message for forwarding, since
+ forwarding someone else a message that wasn't
+ xlated for them doesn't work. We have to
+ dexlate it before sending it.
+ */
+
+ /* Whether this MIME part is a child of another part (and not top level). */
+ bool is_child = false;
+
+ uint32_t whattodo ; /* from the prefs, we'll get if the user wants to do glyph or structure substitutions and set this member variable. */
+
+ char *default_charset; /* If this is non-NULL, then it is the charset to
+ assume when no other one is specified via a
+ `charset' parameter.
+ */
+ bool override_charset; /* If this is true, then we will assume that
+ all data is in the default_charset, regardless
+ of what the `charset' parameter of that part
+ says. (This is to cope with the fact that, in
+ the real world, many messages are mislabelled
+ with the wrong charset.)
+ */
+ bool force_user_charset; /* this is the new strategy to deal with incorrectly
+ labeled attachments */
+
+ /* =======================================================================
+ Stream-related callbacks; for these functions, the `closure' argument
+ is what is found in `options->stream_closure'. (One possible exception
+ is for output_fn; see "output_closure" below.)
+ */
+ void *stream_closure;
+
+ /* For setting up the display stream, so that the MIME parser can inform
+ the caller of the type of the data it will be getting. */
+ int (*output_init_fn) (const char *type,
+ const char *charset,
+ const char *name,
+ const char *x_mac_type,
+ const char *x_mac_creator,
+ void *stream_closure);
+
+ /* How the MIME parser feeds its output (HTML or raw) back to the caller. */
+ MimeConverterOutputCallback output_fn;
+
+ /* Closure to pass to the above output_fn. If NULL, then the
+ stream_closure is used. */
+ void *output_closure;
+
+ /* A hook for the caller to perform charset-conversion before HTML is
+ returned. Each set of characters which originated in a mail message
+ (body or headers) will be run through this filter before being converted
+ into HTML. (This should return bytes which may appear in an HTML file,
+ ie, we must be able to scan through the string to search for "<" and
+ turn it in to "&lt;", and so on.)
+
+ `input' is a non-NULL-terminated string of a single line from the message.
+ `input_length' is how long it is.
+ `input_charset' is a string representing the charset of this string (as
+ specified by MIME headers.)
+ `output_charset' is the charset to which conversion is desired.
+ `output_ret' is where a newly-malloced string is returned. It may be
+ NULL if no translation is needed.
+ `output_size_ret' is how long the returned string is (it need not be
+ NULL-terminated.).
+ */
+ int (*charset_conversion_fn) (const char *input_line,
+ int32_t input_length, const char *input_charset,
+ const char *output_charset,
+ char **output_ret, int32_t *output_size_ret,
+ void *stream_closure, nsIUnicodeDecoder *decoder, nsIUnicodeEncoder *encoder);
+
+ /* If true, perform both charset-conversion and decoding of
+ MIME-2 header fields (using RFC-1522 encoding.)
+ */
+ bool rfc1522_conversion_p;
+
+ /* A hook for the caller to turn a file name into a content-type. */
+ char *(*file_type_fn) (const char *filename, void *stream_closure);
+
+ /* A hook by which the user may be prompted for a password by the security
+ library. (This is really of type `SECKEYGetPasswordKey'; see sec.h.) */
+ void *(*passwd_prompt_fn)(void *arg1, void *arg2);
+
+ /* =======================================================================
+ Various callbacks; for all of these functions, the `closure' argument
+ is what is found in `html_closure'.
+ */
+ void *html_closure;
+
+ /* For emitting some HTML before the start of the outermost message
+ (this is called before any HTML is written to layout.) */
+ MimeHTMLGeneratorFunction generate_header_html_fn;
+
+ /* For emitting some HTML after the outermost header block, but before
+ the body of the first message. */
+ MimeHTMLGeneratorFunction generate_post_header_html_fn;
+
+ /* For emitting some HTML at the very end (this is called after libmime
+ has written everything it's going to write.) */
+ MimeHTMLGeneratorFunction generate_footer_html_fn;
+
+ /* For turning a message ID into a loadable URL. */
+ MimeHTMLGeneratorFunction generate_reference_url_fn;
+
+ /* For turning a mail address into a mailto URL. */
+ MimeHTMLGeneratorFunction generate_mailto_url_fn;
+
+ /* For turning a newsgroup name into a news URL. */
+ MimeHTMLGeneratorFunction generate_news_url_fn;
+
+ /* =======================================================================
+ Callbacks to handle the backend-specific inlined image display
+ (internal-external-reconnect junk.) For `image_begin', the `closure'
+ argument is what is found in `stream_closure'; but for all of the
+ others, the `closure' argument is the data that `image_begin' returned.
+ */
+
+ /* Begins processing an embedded image; the URL and content_type are of the
+ image itself. */
+ void *(*image_begin) (const char *image_url, const char *content_type,
+ void *stream_closure);
+
+ /* Stop processing an image. */
+ void (*image_end) (void *image_closure, int status);
+
+ /* Dump some raw image data down the stream. */
+ int (*image_write_buffer) (const char *buf, int32_t size, void *image_closure);
+
+ /* What HTML should be dumped out for this image. */
+ char *(*make_image_html) (void *image_closure);
+
+
+ /* =======================================================================
+ Other random opaque state.
+ */
+ MimeParseStateObject *state; /* Some state used by libmime internals;
+ initialize this to 0 and leave it alone.
+ */
+
+
+#ifdef MIME_DRAFTS
+ /* =======================================================================
+ Mail Draft hooks -- 09-19-1996
+ */
+ bool decompose_file_p; /* are we decomposing a mime msg
+ into separate files */
+ bool done_parsing_outer_headers; /* are we done parsing the outer message
+ headers; this is really useful when
+ we have multiple Message/RFC822
+ headers */
+ bool is_multipart_msg; /* are we decomposing a multipart
+ message */
+
+ int decompose_init_count; /* used for non multipart message only
+ */
+
+ bool signed_p; /* to tell draft this is a signed
+ message */
+
+ bool caller_need_root_headers; /* set it to true to receive the message main
+ headers through the callback
+ decompose_headers_info_fn */
+
+ /* Callback to gather the outer most headers so we could use the
+ information to initialize the addressing/subject/newsgroups fields
+ for the composition window. */
+ int (*decompose_headers_info_fn) (void *closure,
+ MimeHeaders *headers);
+
+ /* Callbacks to create temporary files for drafts attachments. */
+ int (*decompose_file_init_fn) (void *stream_closure,
+ MimeHeaders *headers );
+
+ MimeConverterOutputCallback decompose_file_output_fn;
+
+ int (*decompose_file_close_fn) (void *stream_closure);
+#endif /* MIME_DRAFTS */
+
+ int32_t attachment_icon_layer_id; /* Hackhackhack. This is zero if we have
+ not yet emitted the attachment layer
+ stuff. If we have, then this is the
+ id number for that layer, which is a
+ unique random number every time, to keep
+ evil people from writing javascript code
+ to hack it. */
+
+ bool missing_parts; /* Whether or not this message is going to contain
+ missing parts (from IMAP Mime Parts On Demand) */
+
+ bool show_attachment_inline_p; /* Whether or not we should display attachment inline (whatever say
+ the content-disposition) */
+
+ bool quote_attachment_inline_p; /* Whether or not we should include inlined attachments in
+ quotes of replies) */
+
+ int32_t html_as_p; /* How we should display HTML, which allows us to know if we should display all body parts */
+
+ /**
+ * Should StartBody/EndBody events be generated for nested MimeMessages. If
+ * false (the default value), the events are only generated for the outermost
+ * MimeMessage.
+ */
+ bool notify_nested_bodies;
+
+ /**
+ * When true, compels mime parts to only write the actual body
+ * payload and not display-gunk like links to attachments. This was
+ * primarily introduced for the benefit of the javascript emitter.
+ */
+ bool write_pure_bodies;
+
+ /**
+ * When true, only processes metadata (i.e. size) for streamed attachments.
+ * Mime emitters that expect any attachment data (including inline text and
+ * image attachments) should leave this as false (the default value). At
+ * the moment, only the JS mime emitter uses this.
+ */
+ bool metadata_only;
+};
+
+#endif /* _MODLMIME_H_ */
diff --git a/mailnews/mime/src/modmimee.h b/mailnews/mime/src/modmimee.h
new file mode 100644
index 000000000..123fbcdb1
--- /dev/null
+++ b/mailnews/mime/src/modmimee.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ /* -*- Mode: C; tab-width: 4 -*-
+ mimeenc.c --- MIME encoders and decoders, version 2 (see mimei.h)
+ Copyright (c) 1996 Netscape Communications Corporation, all rights reserved.
+ Created: Jamie Zawinski <jwz@netscape.com>, 15-May-96.
+ */
+
+#ifndef _MIMEENC_H_
+#define _MIMEENC_H_
+
+#include "nsError.h"
+#include "nscore.h" // for nullptr
+
+typedef int (*MimeConverterOutputCallback)
+ (const char *buf, int32_t size, void *closure);
+
+/* This file defines interfaces to generic implementations of Base64,
+ Quoted-Printable, and UU decoders; and of Base64 and Quoted-Printable
+ encoders.
+ */
+
+
+/* Opaque objects used by the encoder/decoder to store state. */
+typedef struct MimeDecoderData MimeDecoderData;
+
+struct MimeObject;
+
+
+/* functions for creating that opaque data.
+ */
+MimeDecoderData *MimeB64DecoderInit(MimeConverterOutputCallback output_fn,
+ void *closure);
+
+MimeDecoderData *MimeQPDecoderInit (MimeConverterOutputCallback output_fn,
+ void *closure, MimeObject *object = nullptr);
+
+MimeDecoderData *MimeUUDecoderInit (MimeConverterOutputCallback output_fn,
+ void *closure);
+MimeDecoderData *MimeYDecoderInit (MimeConverterOutputCallback output_fn,
+ void *closure);
+
+/* Push data through the encoder/decoder, causing the above-provided write_fn
+ to be called with encoded/decoded data. */
+int MimeDecoderWrite (MimeDecoderData *data, const char *buffer, int32_t size,
+ int32_t *outSize);
+
+/* When you're done encoding/decoding, call this to free the data. If
+ abort_p is false, then calling this may cause the write_fn to be called
+ one last time (as the last buffered data is flushed out.)
+ */
+int MimeDecoderDestroy(MimeDecoderData *data, bool abort_p);
+
+#endif /* _MODMIMEE_H_ */
diff --git a/mailnews/mime/src/moz.build b/mailnews/mime/src/moz.build
new file mode 100644
index 000000000..93bdbad98
--- /dev/null
+++ b/mailnews/mime/src/moz.build
@@ -0,0 +1,92 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'mimecont.h',
+ 'mimecryp.h',
+ 'mimecth.h',
+ 'mimehdrs.h',
+ 'mimei.h',
+ 'mimeleaf.h',
+ 'mimemoz2.h',
+ 'mimemsig.h',
+ 'mimemult.h',
+ 'mimeobj.h',
+ 'mimepbuf.h',
+ 'mimetext.h',
+ 'modlmime.h',
+ 'modmimee.h',
+ 'nsMimeStringResources.h',
+ 'nsStreamConverter.h',
+]
+
+SOURCES += [
+ 'comi18n.cpp',
+ 'mimebuf.cpp',
+ 'mimecms.cpp',
+ 'mimecom.cpp',
+ 'mimecont.cpp',
+ 'mimecryp.cpp',
+ 'mimecth.cpp',
+ 'mimedrft.cpp',
+ 'mimeebod.cpp',
+ 'mimeenc.cpp',
+ 'mimeeobj.cpp',
+ 'mimehdrs.cpp',
+ 'MimeHeaderParser.cpp',
+ 'mimei.cpp',
+ 'mimeiimg.cpp',
+ 'mimeleaf.cpp',
+ 'mimemalt.cpp',
+ 'mimemapl.cpp',
+ 'mimemcms.cpp',
+ 'mimemdig.cpp',
+ 'mimemmix.cpp',
+ 'mimemoz2.cpp',
+ 'mimempar.cpp',
+ 'mimemrel.cpp',
+ 'mimemsg.cpp',
+ 'mimemsig.cpp',
+ 'mimemult.cpp',
+ 'mimeobj.cpp',
+ 'mimepbuf.cpp',
+ 'mimesun.cpp',
+ 'mimetenr.cpp',
+ 'mimetext.cpp',
+ 'mimeTextHTMLParsed.cpp',
+ 'mimethpl.cpp',
+ 'mimethsa.cpp',
+ 'mimethtm.cpp',
+ 'mimetpfl.cpp',
+ 'mimetpla.cpp',
+ 'mimetric.cpp',
+ 'mimeunty.cpp',
+ 'nsCMS.cpp',
+ 'nsCMSSecureMessage.cpp',
+ 'nsMimeObjectClassAccess.cpp',
+ 'nsSimpleMimeConverterStub.cpp',
+ 'nsStreamConverter.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/security/certverifier',
+ '/security/manager/ssl',
+ '/security/pkix/include',
+]
+
+EXTRA_COMPONENTS += [
+ 'mimeJSComponents.js',
+ 'msgMime.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'extraMimeParsers.jsm',
+ 'jsmime.jsm',
+ 'mimeParser.jsm'
+]
+
+FINAL_LIBRARY = 'mail'
+
+DEFINES['ENABLE_SMIME'] = True
diff --git a/mailnews/mime/src/msgMime.manifest b/mailnews/mime/src/msgMime.manifest
new file mode 100644
index 000000000..9ce79bf74
--- /dev/null
+++ b/mailnews/mime/src/msgMime.manifest
@@ -0,0 +1,9 @@
+component {d1258011-f391-44fd-992e-c6f4b461a42f} mimeJSComponents.js
+component {96bd8769-2d0e-4440-963d-22b97fb3ba77} mimeJSComponents.js
+component {93f8c049-80ed-4dda-9000-94ad8daba44c} mimeJSComponents.js
+component {c560806a-425f-4f0f-bf69-397c58c599a7} mimeJSComponents.js
+contract @mozilla.org/messenger/mimeheaders;1 {d1258011-f391-44fd-992e-c6f4b461a42f}
+contract @mozilla.org/messenger/mimeconverter;1 {93f8c049-80ed-4dda-9000-94ad8daba44c}
+contract @mozilla.org/messenger/headerparser;1 {96bd8769-2d0e-4440-963d-22b97fb3ba77}
+contract @mozilla.org/messenger/structuredheaders;1 {c560806a-425f-4f0f-bf69-397c58c599a7}
+category custom-mime-encoder A-extra resource:///modules/extraMimeParsers.jsm
diff --git a/mailnews/mime/src/nsCMS.cpp b/mailnews/mime/src/nsCMS.cpp
new file mode 100644
index 000000000..dd9d31c28
--- /dev/null
+++ b/mailnews/mime/src/nsCMS.cpp
@@ -0,0 +1,1048 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCMS.h"
+
+#include "CertVerifier.h"
+#include "CryptoTask.h"
+#include "ScopedNSSTypes.h"
+#include "cms.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "nsArrayUtils.h"
+#include "nsIArray.h"
+#include "nsICMSMessageErrors.h"
+#include "nsICryptoHash.h"
+#include "nsISupports.h"
+#include "nsIX509CertDB.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSComponent.h"
+#include "nsNSSHelper.h"
+#include "nsServiceManagerUtils.h"
+#include "pkix/Result.h"
+#include "pkix/pkixtypes.h"
+#include "smime.h"
+#include "mozilla/StaticMutex.h"
+
+using namespace mozilla;
+using namespace mozilla::psm;
+using namespace mozilla::pkix;
+
+#ifdef PR_LOGGING
+extern mozilla::LazyLogModule gPIPNSSLog;
+#endif
+
+NS_IMPL_ISUPPORTS(nsCMSMessage, nsICMSMessage, nsICMSMessage2)
+
+nsCMSMessage::nsCMSMessage()
+{
+ m_cmsMsg = nullptr;
+}
+nsCMSMessage::nsCMSMessage(NSSCMSMessage *aCMSMsg)
+{
+ m_cmsMsg = aCMSMsg;
+}
+
+nsCMSMessage::~nsCMSMessage()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ destructorSafeDestroyNSSReference();
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+nsresult nsCMSMessage::Init()
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+void nsCMSMessage::virtualDestroyNSSReference()
+{
+ destructorSafeDestroyNSSReference();
+}
+
+void nsCMSMessage::destructorSafeDestroyNSSReference()
+{
+ if (m_cmsMsg) {
+ NSS_CMSMessage_Destroy(m_cmsMsg);
+ }
+}
+
+NS_IMETHODIMP nsCMSMessage::VerifySignature()
+{
+ return CommonVerifySignature(nullptr, 0, 0);
+}
+
+NSSCMSSignerInfo* nsCMSMessage::GetTopLevelSignerInfo()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return nullptr;
+
+ if (!m_cmsMsg)
+ return nullptr;
+
+ if (!NSS_CMSMessage_IsSigned(m_cmsMsg))
+ return nullptr;
+
+ NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(m_cmsMsg, 0);
+ if (!cinfo)
+ return nullptr;
+
+ NSSCMSSignedData *sigd = (NSSCMSSignedData*)NSS_CMSContentInfo_GetContent(cinfo);
+ if (!sigd)
+ return nullptr;
+
+ PR_ASSERT(NSS_CMSSignedData_SignerInfoCount(sigd) > 0);
+ return NSS_CMSSignedData_GetSignerInfo(sigd, 0);
+}
+
+NS_IMETHODIMP nsCMSMessage::GetSignerEmailAddress(char * * aEmail)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerEmailAddress\n"));
+ NS_ENSURE_ARG(aEmail);
+
+ NSSCMSSignerInfo *si = GetTopLevelSignerInfo();
+ if (!si)
+ return NS_ERROR_FAILURE;
+
+ *aEmail = NSS_CMSSignerInfo_GetSignerEmailAddress(si);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSMessage::GetSignerCommonName(char ** aName)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCommonName\n"));
+ NS_ENSURE_ARG(aName);
+
+ NSSCMSSignerInfo *si = GetTopLevelSignerInfo();
+ if (!si)
+ return NS_ERROR_FAILURE;
+
+ *aName = NSS_CMSSignerInfo_GetSignerCommonName(si);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSMessage::ContentIsEncrypted(bool *isEncrypted)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::ContentIsEncrypted\n"));
+ NS_ENSURE_ARG(isEncrypted);
+
+ if (!m_cmsMsg)
+ return NS_ERROR_FAILURE;
+
+ *isEncrypted = NSS_CMSMessage_IsEncrypted(m_cmsMsg);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSMessage::ContentIsSigned(bool *isSigned)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::ContentIsSigned\n"));
+ NS_ENSURE_ARG(isSigned);
+
+ if (!m_cmsMsg)
+ return NS_ERROR_FAILURE;
+
+ *isSigned = NSS_CMSMessage_IsSigned(m_cmsMsg);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSMessage::GetSignerCert(nsIX509Cert **scert)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ NSSCMSSignerInfo *si = GetTopLevelSignerInfo();
+ if (!si)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIX509Cert> cert;
+ if (si->cert) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCert got signer cert\n"));
+
+ nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID);
+ certdb->ConstructX509(reinterpret_cast<const char *>(si->cert->derCert.data),
+ si->cert->derCert.len,
+ getter_AddRefs(cert));
+ }
+ else {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCert no signer cert, do we have a cert list? %s\n",
+ (si->certList ? "yes" : "no") ));
+
+ *scert = nullptr;
+ }
+
+ cert.forget(scert);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSMessage::GetEncryptionCert(nsIX509Cert **ecert)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCMSMessage::VerifyDetachedSignature(unsigned char* aDigestData,
+ uint32_t aDigestDataLen,
+ int16_t aDigestType)
+{
+ if (!aDigestData || !aDigestDataLen)
+ return NS_ERROR_FAILURE;
+
+ return CommonVerifySignature(aDigestData, aDigestDataLen, aDigestType);
+}
+
+nsresult
+nsCMSMessage::CommonVerifySignature(unsigned char* aDigestData,
+ uint32_t aDigestDataLen,
+ int16_t aDigestType)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature, content level count %d\n", NSS_CMSMessage_ContentLevelCount(m_cmsMsg)));
+ NSSCMSContentInfo *cinfo = nullptr;
+ NSSCMSSignedData *sigd = nullptr;
+ NSSCMSSignerInfo *si;
+ int32_t nsigners;
+ RefPtr<SharedCertVerifier> certVerifier;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (!NSS_CMSMessage_IsSigned(m_cmsMsg)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - not signed\n"));
+ return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
+ }
+
+ cinfo = NSS_CMSMessage_ContentLevel(m_cmsMsg, 0);
+ if (cinfo) {
+ switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) {
+ case SEC_OID_PKCS7_SIGNED_DATA:
+ sigd = reinterpret_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
+ break;
+
+ case SEC_OID_PKCS7_ENVELOPED_DATA:
+ case SEC_OID_PKCS7_ENCRYPTED_DATA:
+ case SEC_OID_PKCS7_DIGESTED_DATA:
+ default: {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - unexpected ContentTypeTag\n"));
+ rv = NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
+ goto loser;
+ }
+ }
+ }
+
+ if (!sigd) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - no content info\n"));
+ rv = NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
+ goto loser;
+ }
+
+ if (aDigestData && aDigestDataLen)
+ {
+ SECOidTag oidTag;
+ SECItem digest;
+ digest.data = aDigestData;
+ digest.len = aDigestDataLen;
+
+ if (NSS_CMSSignedData_HasDigests(sigd)) {
+ SECAlgorithmID **existingAlgs = NSS_CMSSignedData_GetDigestAlgs(sigd);
+ if (existingAlgs) {
+ while (*existingAlgs) {
+ SECAlgorithmID *alg = *existingAlgs;
+ SECOidTag algOIDTag = SECOID_FindOIDTag(&alg->algorithm);
+ NSS_CMSSignedData_SetDigestValue(sigd, algOIDTag, NULL);
+ ++existingAlgs;
+ }
+ }
+ }
+
+ if (!GetIntHashToOidHash(aDigestType, oidTag)) {
+ rv = NS_ERROR_CMS_VERIFY_BAD_DIGEST;
+ goto loser;
+ }
+
+ if (NSS_CMSSignedData_SetDigestValue(sigd, oidTag, &digest)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - bad digest\n"));
+ rv = NS_ERROR_CMS_VERIFY_BAD_DIGEST;
+ goto loser;
+ }
+ }
+
+ // Import certs. Note that import failure is not a signature verification failure. //
+ if (NSS_CMSSignedData_ImportCerts(sigd, CERT_GetDefaultCertDB(), certUsageEmailRecipient, true) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - can not import certs\n"));
+ }
+
+ nsigners = NSS_CMSSignedData_SignerInfoCount(sigd);
+ PR_ASSERT(nsigners > 0);
+ NS_ENSURE_TRUE(nsigners > 0, NS_ERROR_UNEXPECTED);
+ si = NSS_CMSSignedData_GetSignerInfo(sigd, 0);
+
+ // See bug 324474. We want to make sure the signing cert is
+ // still valid at the current time.
+
+ certVerifier = GetDefaultCertVerifier();
+ NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
+
+ {
+ UniqueCERTCertList builtChain;
+ mozilla::pkix::Result result =
+ certVerifier->VerifyCert(si->cert,
+ certificateUsageEmailSigner,
+ Now(),
+ nullptr /*XXX pinarg*/,
+ nullptr /*hostname*/,
+ builtChain);
+ if (result != mozilla::pkix::Success) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - signing cert not trusted now\n"));
+ rv = NS_ERROR_CMS_VERIFY_UNTRUSTED;
+ goto loser;
+ }
+ }
+
+ // We verify the first signer info, only //
+ // XXX: NSS_CMSSignedData_VerifySignerInfo calls CERT_VerifyCert, which
+ // requires NSS's certificate verification configuration to be done in
+ // order to work well (e.g. honoring OCSP preferences and proxy settings
+ // for OCSP requests), but Gecko stopped doing that configuration. Something
+ // similar to what was done for Gecko bug 1028643 needs to be done here too.
+ if (NSS_CMSSignedData_VerifySignerInfo(sigd, 0, CERT_GetDefaultCertDB(), certUsageEmailSigner) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - unable to verify signature\n"));
+
+ if (NSSCMSVS_SigningCertNotFound == si->verificationStatus) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - signing cert not found\n"));
+ rv = NS_ERROR_CMS_VERIFY_NOCERT;
+ }
+ else if(NSSCMSVS_SigningCertNotTrusted == si->verificationStatus) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - signing cert not trusted at signing time\n"));
+ rv = NS_ERROR_CMS_VERIFY_UNTRUSTED;
+ }
+ else if(NSSCMSVS_Unverified == si->verificationStatus) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - can not verify\n"));
+ rv = NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED;
+ }
+ else if(NSSCMSVS_ProcessingError == si->verificationStatus) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - processing error\n"));
+ rv = NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
+ }
+ else if(NSSCMSVS_BadSignature == si->verificationStatus) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - bad signature\n"));
+ rv = NS_ERROR_CMS_VERIFY_BAD_SIGNATURE;
+ }
+ else if(NSSCMSVS_DigestMismatch == si->verificationStatus) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - digest mismatch\n"));
+ rv = NS_ERROR_CMS_VERIFY_DIGEST_MISMATCH;
+ }
+ else if(NSSCMSVS_SignatureAlgorithmUnknown == si->verificationStatus) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - algo unknown\n"));
+ rv = NS_ERROR_CMS_VERIFY_UNKNOWN_ALGO;
+ }
+ else if(NSSCMSVS_SignatureAlgorithmUnsupported == si->verificationStatus) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - algo not supported\n"));
+ rv = NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO;
+ }
+ else if(NSSCMSVS_MalformedSignature == si->verificationStatus) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - malformed signature\n"));
+ rv = NS_ERROR_CMS_VERIFY_MALFORMED_SIGNATURE;
+ }
+
+ goto loser;
+ }
+
+ // Save the profile. Note that save import failure is not a signature verification failure. //
+ if (NSS_SMIMESignerInfo_SaveSMIMEProfile(si) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - unable to save smime profile\n"));
+ }
+
+ rv = NS_OK;
+loser:
+ return rv;
+}
+
+NS_IMETHODIMP nsCMSMessage::AsyncVerifySignature(
+ nsISMimeVerificationListener *aListener)
+{
+ return CommonAsyncVerifySignature(aListener, nullptr, 0, 0);
+}
+
+NS_IMETHODIMP nsCMSMessage::AsyncVerifyDetachedSignature(
+ nsISMimeVerificationListener *aListener,
+ unsigned char* aDigestData,
+ uint32_t aDigestDataLen,
+ int16_t aDigestType)
+{
+ if (!aDigestData || !aDigestDataLen)
+ return NS_ERROR_FAILURE;
+
+ return CommonAsyncVerifySignature(aListener, aDigestData, aDigestDataLen, aDigestType);
+}
+
+class SMimeVerificationTask final : public CryptoTask
+{
+public:
+ SMimeVerificationTask(nsICMSMessage *aMessage,
+ nsISMimeVerificationListener *aListener,
+ unsigned char *aDigestData,
+ uint32_t aDigestDataLen,
+ int16_t aDigestType)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mMessage = aMessage;
+ mListener = aListener;
+ mDigestData.Assign(reinterpret_cast<char *>(aDigestData), aDigestDataLen);
+ mDigestType = aDigestType;
+ }
+
+private:
+ virtual void ReleaseNSSResources() override {}
+ virtual nsresult CalculateResult() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mozilla::StaticMutexAutoLock lock(sMutex);
+ nsresult rv;
+ if (!mDigestData.IsEmpty()) {
+ rv = mMessage->VerifyDetachedSignature(
+ reinterpret_cast<uint8_t*>(const_cast<char *>(mDigestData.get())),
+ mDigestData.Length(), mDigestType);
+ } else {
+ rv = mMessage->VerifySignature();
+ }
+
+ return rv;
+ }
+ virtual void CallCallback(nsresult rv) override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsICMSMessage2> m2 = do_QueryInterface(mMessage);
+ mListener->Notify(m2, rv);
+ }
+
+ nsCOMPtr<nsICMSMessage> mMessage;
+ nsCOMPtr<nsISMimeVerificationListener> mListener;
+ nsCString mDigestData;
+ int16_t mDigestType;
+
+ static mozilla::StaticMutex sMutex;
+};
+
+mozilla::StaticMutex SMimeVerificationTask::sMutex;
+
+nsresult nsCMSMessage::CommonAsyncVerifySignature(nsISMimeVerificationListener *aListener,
+ unsigned char* aDigestData, uint32_t aDigestDataLen,
+ int16_t aDigestType)
+{
+ RefPtr<CryptoTask> task = new SMimeVerificationTask(this, aListener, aDigestData, aDigestDataLen, aDigestType);
+ return task->Dispatch("SMimeVerify");
+}
+
+class nsZeroTerminatedCertArray : public nsNSSShutDownObject
+{
+public:
+ nsZeroTerminatedCertArray()
+ :mCerts(nullptr), mPoolp(nullptr), mSize(0)
+ {
+ }
+
+ ~nsZeroTerminatedCertArray()
+ {
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ destructorSafeDestroyNSSReference();
+ shutdown(ShutdownCalledFrom::Object);
+ }
+
+ void virtualDestroyNSSReference()
+ {
+ destructorSafeDestroyNSSReference();
+ }
+
+ void destructorSafeDestroyNSSReference()
+ {
+ if (mCerts)
+ {
+ for (uint32_t i=0; i < mSize; i++) {
+ if (mCerts[i]) {
+ CERT_DestroyCertificate(mCerts[i]);
+ }
+ }
+ }
+
+ if (mPoolp)
+ PORT_FreeArena(mPoolp, false);
+ }
+
+ bool allocate(uint32_t count)
+ {
+ // only allow allocation once
+ if (mPoolp)
+ return false;
+
+ mSize = count;
+
+ if (!mSize)
+ return false;
+
+ mPoolp = PORT_NewArena(1024);
+ if (!mPoolp)
+ return false;
+
+ mCerts = (CERTCertificate**)PORT_ArenaZAlloc(
+ mPoolp, (count+1)*sizeof(CERTCertificate*));
+
+ if (!mCerts)
+ return false;
+
+ // null array, including zero termination
+ for (uint32_t i = 0; i < count+1; i++) {
+ mCerts[i] = nullptr;
+ }
+
+ return true;
+ }
+
+ void set(uint32_t i, CERTCertificate *c)
+ {
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return;
+
+ if (i >= mSize)
+ return;
+
+ if (mCerts[i]) {
+ CERT_DestroyCertificate(mCerts[i]);
+ }
+
+ mCerts[i] = CERT_DupCertificate(c);
+ }
+
+ CERTCertificate *get(uint32_t i)
+ {
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return nullptr;
+
+ if (i >= mSize)
+ return nullptr;
+
+ return CERT_DupCertificate(mCerts[i]);
+ }
+
+ CERTCertificate **getRawArray()
+ {
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return nullptr;
+
+ return mCerts;
+ }
+
+private:
+ CERTCertificate **mCerts;
+ PLArenaPool *mPoolp;
+ uint32_t mSize;
+};
+
+NS_IMETHODIMP nsCMSMessage::CreateEncrypted(nsIArray * aRecipientCerts)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted\n"));
+ NSSCMSContentInfo *cinfo;
+ NSSCMSEnvelopedData *envd;
+ NSSCMSRecipientInfo *recipientInfo;
+ nsZeroTerminatedCertArray recipientCerts;
+ SECOidTag bulkAlgTag;
+ int keySize;
+ uint32_t i;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ // Check the recipient certificates //
+ uint32_t recipientCertCount;
+ aRecipientCerts->GetLength(&recipientCertCount);
+ PR_ASSERT(recipientCertCount > 0);
+
+ if (!recipientCerts.allocate(recipientCertCount)) {
+ goto loser;
+ }
+
+ for (i=0; i<recipientCertCount; i++) {
+ nsCOMPtr<nsIX509Cert> x509cert = do_QueryElementAt(aRecipientCerts, i);
+
+ if (!x509cert)
+ return NS_ERROR_FAILURE;
+
+ UniqueCERTCertificate c(x509cert->GetCert());
+ recipientCerts.set(i, c.get());
+ }
+
+ // Find a bulk key algorithm //
+ if (NSS_SMIMEUtil_FindBulkAlgForRecipients(recipientCerts.getRawArray(), &bulkAlgTag,
+ &keySize) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't find bulk alg for recipients\n"));
+ rv = NS_ERROR_CMS_ENCRYPT_NO_BULK_ALG;
+ goto loser;
+ }
+
+ m_cmsMsg = NSS_CMSMessage_Create(nullptr);
+ if (!m_cmsMsg) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create new cms message\n"));
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto loser;
+ }
+
+ if ((envd = NSS_CMSEnvelopedData_Create(m_cmsMsg, bulkAlgTag, keySize)) == nullptr) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create enveloped data\n"));
+ goto loser;
+ }
+
+ cinfo = NSS_CMSMessage_GetContentInfo(m_cmsMsg);
+ if (NSS_CMSContentInfo_SetContent_EnvelopedData(m_cmsMsg, cinfo, envd) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create content enveloped data\n"));
+ goto loser;
+ }
+
+ cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd);
+ if (NSS_CMSContentInfo_SetContent_Data(m_cmsMsg, cinfo, nullptr, false) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't set content data\n"));
+ goto loser;
+ }
+
+ // Create and attach recipient information //
+ for (i=0; i < recipientCertCount; i++) {
+ UniqueCERTCertificate rc(recipientCerts.get(i));
+ if ((recipientInfo = NSS_CMSRecipientInfo_Create(m_cmsMsg, rc.get())) == nullptr) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create recipient info\n"));
+ goto loser;
+ }
+ if (NSS_CMSEnvelopedData_AddRecipient(envd, recipientInfo) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't add recipient info\n"));
+ goto loser;
+ }
+ }
+
+ return NS_OK;
+loser:
+ if (m_cmsMsg) {
+ NSS_CMSMessage_Destroy(m_cmsMsg);
+ m_cmsMsg = nullptr;
+ }
+
+ return rv;
+}
+
+bool nsCMSMessage::IsAllowedHash(const int16_t aCryptoHashInt)
+{
+ switch (aCryptoHashInt) {
+ case nsICryptoHash::SHA1:
+ case nsICryptoHash::SHA256:
+ case nsICryptoHash::SHA384:
+ case nsICryptoHash::SHA512:
+ return true;
+ case nsICryptoHash::MD2:
+ case nsICryptoHash::MD5:
+ default:
+ return false;
+ }
+}
+
+bool nsCMSMessage::GetIntHashToOidHash(const int16_t aCryptoHashInt, SECOidTag &aOidTag)
+{
+ bool rv = true;
+
+ switch (aCryptoHashInt) {
+ case nsICryptoHash::MD2:
+ aOidTag = SEC_OID_MD2;
+ break;
+ case nsICryptoHash::MD5:
+ aOidTag = SEC_OID_MD5;
+ break;
+ case nsICryptoHash::SHA1:
+ aOidTag = SEC_OID_SHA1;
+ break;
+ case nsICryptoHash::SHA256:
+ aOidTag = SEC_OID_SHA256;
+ break;
+ case nsICryptoHash::SHA384:
+ aOidTag = SEC_OID_SHA384;
+ break;
+ case nsICryptoHash::SHA512:
+ aOidTag = SEC_OID_SHA512;
+ break;
+ default:
+ rv = false;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCMSMessage::CreateSigned(nsIX509Cert* aSigningCert, nsIX509Cert* aEncryptCert,
+ unsigned char* aDigestData, uint32_t aDigestDataLen,
+ int16_t aDigestType)
+{
+ NS_ENSURE_ARG(aSigningCert);
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned\n"));
+ NSSCMSContentInfo *cinfo;
+ NSSCMSSignedData *sigd;
+ NSSCMSSignerInfo *signerinfo;
+ UniqueCERTCertificate scert(aSigningCert->GetCert());
+ UniqueCERTCertificate ecert;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (!scert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aEncryptCert) {
+ ecert = UniqueCERTCertificate(aEncryptCert->GetCert());
+ }
+
+ SECOidTag digestType;
+ if (!IsAllowedHash(aDigestType) ||
+ !GetIntHashToOidHash(aDigestType, digestType)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ /*
+ * create the message object
+ */
+ m_cmsMsg = NSS_CMSMessage_Create(nullptr); /* create a message on its own pool */
+ if (!m_cmsMsg) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't create new message\n"));
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto loser;
+ }
+
+ /*
+ * build chain of objects: message->signedData->data
+ */
+ if ((sigd = NSS_CMSSignedData_Create(m_cmsMsg)) == nullptr) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't create signed data\n"));
+ goto loser;
+ }
+ cinfo = NSS_CMSMessage_GetContentInfo(m_cmsMsg);
+ if (NSS_CMSContentInfo_SetContent_SignedData(m_cmsMsg, cinfo, sigd)
+ != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't set content signed data\n"));
+ goto loser;
+ }
+
+ cinfo = NSS_CMSSignedData_GetContentInfo(sigd);
+
+ /* we're always passing data in and detaching optionally */
+ if (NSS_CMSContentInfo_SetContent_Data(m_cmsMsg, cinfo, nullptr, true)
+ != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't set content data\n"));
+ goto loser;
+ }
+
+ /*
+ * create & attach signer information
+ */
+ signerinfo = NSS_CMSSignerInfo_Create(m_cmsMsg, scert.get(), digestType);
+ if (!signerinfo) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't create signer info\n"));
+ goto loser;
+ }
+
+ /* we want the cert chain included for this one */
+ if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain,
+ certUsageEmailSigner)
+ != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't include signer cert chain\n"));
+ goto loser;
+ }
+
+ if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now())
+ != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add signing time\n"));
+ goto loser;
+ }
+
+ if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add smime caps\n"));
+ goto loser;
+ }
+
+ if (ecert) {
+ if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, ecert.get(),
+ CERT_GetDefaultCertDB())
+ != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add smime enc key prefs\n"));
+ goto loser;
+ }
+
+ if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, ecert.get(),
+ CERT_GetDefaultCertDB())
+ != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add MS smime enc key prefs\n"));
+ goto loser;
+ }
+
+ // If signing and encryption cert are identical, don't add it twice.
+ bool addEncryptionCert =
+ (ecert && (!scert || !CERT_CompareCerts(ecert.get(), scert.get())));
+
+ if (addEncryptionCert &&
+ NSS_CMSSignedData_AddCertificate(sigd, ecert.get()) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add own encryption certificate\n"));
+ goto loser;
+ }
+ }
+
+ if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add signer info\n"));
+ goto loser;
+ }
+
+ // Finally, add the pre-computed digest if passed in
+ if (aDigestData) {
+ SECItem digest;
+
+ digest.data = aDigestData;
+ digest.len = aDigestDataLen;
+
+ if (NSS_CMSSignedData_SetDigestValue(sigd, digestType, &digest) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't set digest value\n"));
+ goto loser;
+ }
+ }
+
+ return NS_OK;
+loser:
+ if (m_cmsMsg) {
+ NSS_CMSMessage_Destroy(m_cmsMsg);
+ m_cmsMsg = nullptr;
+ }
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsCMSDecoder, nsICMSDecoder)
+
+nsCMSDecoder::nsCMSDecoder()
+: m_dcx(nullptr)
+{
+}
+
+nsCMSDecoder::~nsCMSDecoder()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ destructorSafeDestroyNSSReference();
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+nsresult nsCMSDecoder::Init()
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+void nsCMSDecoder::virtualDestroyNSSReference()
+{
+ destructorSafeDestroyNSSReference();
+}
+
+void nsCMSDecoder::destructorSafeDestroyNSSReference()
+{
+ if (m_dcx) {
+ NSS_CMSDecoder_Cancel(m_dcx);
+ m_dcx = nullptr;
+ }
+}
+
+/* void start (in NSSCMSContentCallback cb, in voidPtr arg); */
+NS_IMETHODIMP nsCMSDecoder::Start(NSSCMSContentCallback cb, void * arg)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Start\n"));
+ m_ctx = new PipUIContext();
+
+ m_dcx = NSS_CMSDecoder_Start(0, cb, arg, 0, m_ctx, 0, 0);
+ if (!m_dcx) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Start - can't start decoder\n"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+/* void update (in string bug, in long len); */
+NS_IMETHODIMP nsCMSDecoder::Update(const char *buf, int32_t len)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Update\n"));
+ NSS_CMSDecoder_Update(m_dcx, (char *)buf, len);
+ return NS_OK;
+}
+
+/* void finish (); */
+NS_IMETHODIMP nsCMSDecoder::Finish(nsICMSMessage ** aCMSMsg)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Finish\n"));
+ NSSCMSMessage *cmsMsg;
+ cmsMsg = NSS_CMSDecoder_Finish(m_dcx);
+ m_dcx = nullptr;
+ if (cmsMsg) {
+ nsCMSMessage *obj = new nsCMSMessage(cmsMsg);
+ // The NSS object cmsMsg still carries a reference to the context
+ // we gave it on construction.
+ // Make sure the context will live long enough.
+ obj->referenceContext(m_ctx);
+ *aCMSMsg = obj;
+ NS_ADDREF(*aCMSMsg);
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsCMSEncoder, nsICMSEncoder)
+
+nsCMSEncoder::nsCMSEncoder()
+: m_ecx(nullptr)
+{
+}
+
+nsCMSEncoder::~nsCMSEncoder()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ destructorSafeDestroyNSSReference();
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+nsresult nsCMSEncoder::Init()
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+void nsCMSEncoder::virtualDestroyNSSReference()
+{
+ destructorSafeDestroyNSSReference();
+}
+
+void nsCMSEncoder::destructorSafeDestroyNSSReference()
+{
+ if (m_ecx)
+ NSS_CMSEncoder_Cancel(m_ecx);
+}
+
+/* void start (); */
+NS_IMETHODIMP nsCMSEncoder::Start(nsICMSMessage *aMsg, NSSCMSContentCallback cb, void * arg)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Start\n"));
+ nsCMSMessage *cmsMsg = static_cast<nsCMSMessage*>(aMsg);
+ m_ctx = new PipUIContext();
+
+ m_ecx = NSS_CMSEncoder_Start(cmsMsg->getCMS(), cb, arg, 0, 0, 0, m_ctx, 0, 0, 0, 0);
+ if (!m_ecx) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Start - can't start encoder\n"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+/* void update (in string aBuf, in long aLen); */
+NS_IMETHODIMP nsCMSEncoder::Update(const char *aBuf, int32_t aLen)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Update\n"));
+ if (!m_ecx || NSS_CMSEncoder_Update(m_ecx, aBuf, aLen) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Update - can't update encoder\n"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+/* void finish (); */
+NS_IMETHODIMP nsCMSEncoder::Finish()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = NS_OK;
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Finish\n"));
+ if (!m_ecx || NSS_CMSEncoder_Finish(m_ecx) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Finish - can't finish encoder\n"));
+ rv = NS_ERROR_FAILURE;
+ }
+ m_ecx = nullptr;
+ return rv;
+}
+
+/* void encode (in nsICMSMessage aMsg); */
+NS_IMETHODIMP nsCMSEncoder::Encode(nsICMSMessage *aMsg)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Encode\n"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/mailnews/mime/src/nsCMS.h b/mailnews/mime/src/nsCMS.h
new file mode 100644
index 000000000..393344277
--- /dev/null
+++ b/mailnews/mime/src/nsCMS.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_CMS_H__
+#define __NS_CMS_H__
+
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "nsXPIDLString.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsICMSMessage.h"
+#include "nsICMSMessage2.h"
+#include "nsIX509Cert.h"
+#include "nsICMSEncoder.h"
+#include "nsICMSDecoder.h"
+#include "sechash.h"
+#include "cms.h"
+#include "nsNSSShutDown.h"
+
+#define NS_CMSMESSAGE_CID \
+ { 0xa4557478, 0xae16, 0x11d5, { 0xba,0x4b,0x00,0x10,0x83,0x03,0xb1,0x17 } }
+
+class nsCMSMessage : public nsICMSMessage,
+ public nsICMSMessage2,
+ public nsNSSShutDownObject
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICMSMESSAGE
+ NS_DECL_NSICMSMESSAGE2
+
+ nsCMSMessage();
+ explicit nsCMSMessage(NSSCMSMessage* aCMSMsg);
+ nsresult Init();
+
+ void referenceContext(nsIInterfaceRequestor* aContext) {m_ctx = aContext;}
+ NSSCMSMessage* getCMS() {return m_cmsMsg;}
+private:
+ virtual ~nsCMSMessage();
+ nsCOMPtr<nsIInterfaceRequestor> m_ctx;
+ NSSCMSMessage * m_cmsMsg;
+ NSSCMSSignerInfo* GetTopLevelSignerInfo();
+ nsresult CommonVerifySignature(unsigned char* aDigestData, uint32_t aDigestDataLen,
+ int16_t aDigestType);
+
+ nsresult CommonAsyncVerifySignature(nsISMimeVerificationListener *aListener,
+ unsigned char* aDigestData, uint32_t aDigestDataLen,
+ int16_t aDigestType);
+ bool GetIntHashToOidHash(const int16_t aCryptoHashInt, SECOidTag &aOidTag);
+ bool IsAllowedHash(const int16_t aCryptoHashInt);
+
+ virtual void virtualDestroyNSSReference() override;
+ void destructorSafeDestroyNSSReference();
+
+};
+
+// ===============================================
+// nsCMSDecoder - implementation of nsICMSDecoder
+// ===============================================
+
+#define NS_CMSDECODER_CID \
+ { 0x9dcef3a4, 0xa3bc, 0x11d5, { 0xba, 0x47, 0x00, 0x10, 0x83, 0x03, 0xb1, 0x17 } }
+
+class nsCMSDecoder : public nsICMSDecoder,
+ public nsNSSShutDownObject
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICMSDECODER
+
+ nsCMSDecoder();
+ nsresult Init();
+
+private:
+ virtual ~nsCMSDecoder();
+ nsCOMPtr<nsIInterfaceRequestor> m_ctx;
+ NSSCMSDecoderContext *m_dcx;
+ virtual void virtualDestroyNSSReference() override;
+ void destructorSafeDestroyNSSReference();
+};
+
+// ===============================================
+// nsCMSEncoder - implementation of nsICMSEncoder
+// ===============================================
+
+#define NS_CMSENCODER_CID \
+ { 0xa15789aa, 0x8903, 0x462b, { 0x81, 0xe9, 0x4a, 0xa2, 0xcf, 0xf4, 0xd5, 0xcb } }
+class nsCMSEncoder : public nsICMSEncoder,
+ public nsNSSShutDownObject
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICMSENCODER
+
+ nsCMSEncoder();
+ nsresult Init();
+
+private:
+ virtual ~nsCMSEncoder();
+ nsCOMPtr<nsIInterfaceRequestor> m_ctx;
+ NSSCMSEncoderContext *m_ecx;
+ virtual void virtualDestroyNSSReference() override;
+ void destructorSafeDestroyNSSReference();
+};
+
+#endif
diff --git a/mailnews/mime/src/nsCMSSecureMessage.cpp b/mailnews/mime/src/nsCMSSecureMessage.cpp
new file mode 100644
index 000000000..0b7a99d7d
--- /dev/null
+++ b/mailnews/mime/src/nsCMSSecureMessage.cpp
@@ -0,0 +1,363 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsXPIDLString.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsCRT.h"
+#include "nsIX509CertDB.h"
+
+#include "nsICMSSecureMessage.h"
+
+#include "nsCMSSecureMessage.h"
+#include "nsIX509Cert.h"
+#include "nsNSSHelper.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSShutDown.h"
+
+#include <string.h>
+#include "plbase64.h"
+#include "cert.h"
+#include "cms.h"
+
+#include "nsIServiceManager.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+
+#include "mozilla/Logging.h"
+#ifdef PR_LOGGING
+extern mozilla::LazyLogModule gPIPNSSLog;
+#endif
+
+using namespace mozilla;
+
+// Standard ISupports implementation
+// NOTE: Should these be the thread-safe versions?
+
+/*****
+ * nsCMSSecureMessage
+ *****/
+
+// Standard ISupports implementation
+NS_IMPL_ISUPPORTS(nsCMSSecureMessage, nsICMSSecureMessage)
+
+// nsCMSSecureMessage constructor
+nsCMSSecureMessage::nsCMSSecureMessage()
+{
+ // initialize superclass
+}
+
+// nsCMSMessage destructor
+nsCMSSecureMessage::~nsCMSSecureMessage()
+{
+}
+
+nsresult nsCMSSecureMessage::Init()
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+/* string getCertByPrefID (in string certID); */
+NS_IMETHODIMP nsCMSSecureMessage::
+GetCertByPrefID(const char *certID, char **_retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::GetCertByPrefID\n"));
+ nsresult rv = NS_OK;
+ CERTCertificate *cert = 0;
+ nsXPIDLCString nickname;
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+
+ *_retval = 0;
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ goto done;
+ }
+
+ rv = prefs->GetCharPref(certID,
+ getter_Copies(nickname));
+ if (NS_FAILED(rv)) goto done;
+
+ /* Find a good cert in the user's database */
+ cert = CERT_FindUserCertByUsage(CERT_GetDefaultCertDB(), const_cast<char*>(nickname.get()),
+ certUsageEmailRecipient, true, ctx);
+
+ if (!cert) {
+ /* Success, but no value */
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::GetCertByPrefID - can't find user cert\n"));
+ goto done;
+ }
+
+ /* Convert the DER to a BASE64 String */
+ encode(cert->derCert.data, cert->derCert.len, _retval);
+
+done:
+ if (cert) CERT_DestroyCertificate(cert);
+ return rv;
+}
+
+
+// nsCMSSecureMessage::DecodeCert
+nsresult nsCMSSecureMessage::
+DecodeCert(const char *value, nsIX509Cert ** _retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::DecodeCert\n"));
+ nsresult rv = NS_OK;
+ int32_t length;
+ unsigned char *data = 0;
+
+ *_retval = 0;
+
+ if (!value) { return NS_ERROR_FAILURE; }
+
+ rv = decode(value, &data, &length);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::DecodeCert - can't decode cert\n"));
+ return rv;
+ }
+
+ nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID);
+ if (!certdb) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIX509Cert> cert;
+ certdb->ConstructX509(reinterpret_cast<char *>(data), length, getter_AddRefs(cert));
+
+ if (cert) {
+ *_retval = cert;
+ NS_ADDREF(*_retval);
+ }
+ else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ free((char*)data);
+ return rv;
+}
+
+// nsCMSSecureMessage::SendMessage
+nsresult nsCMSSecureMessage::
+SendMessage(const char *msg, const char *base64Cert, char ** _retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage\n"));
+ nsresult rv = NS_OK;
+ CERTCertificate *cert = 0;
+ NSSCMSMessage *cmsMsg = 0;
+ unsigned char *certDER = 0;
+ int32_t derLen;
+ NSSCMSEnvelopedData *env;
+ NSSCMSContentInfo *cinfo;
+ NSSCMSRecipientInfo *rcpt;
+ SECItem output;
+ PLArenaPool *arena = PORT_NewArena(1024);
+ SECStatus s;
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+
+ /* Step 0. Create a CMS Message */
+ cmsMsg = NSS_CMSMessage_Create(nullptr);
+ if (!cmsMsg) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't create NSSCMSMessage\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ /* Step 1. Import the certificate into NSS */
+ rv = decode(base64Cert, &certDER, &derLen);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't decode / import cert into NSS\n"));
+ goto done;
+ }
+
+ cert = CERT_DecodeCertFromPackage((char *)certDER, derLen);
+ if (!cert) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't decode cert from package\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ /* Step 2. Get a signature cert */
+
+ /* Step 3. Build inner (signature) content */
+
+ /* Step 4. Build outer (enveloped) content */
+ env = NSS_CMSEnvelopedData_Create(cmsMsg, SEC_OID_DES_EDE3_CBC, 0);
+ if (!env) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't create envelope data\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ cinfo = NSS_CMSEnvelopedData_GetContentInfo(env);
+ s = NSS_CMSContentInfo_SetContent_Data(cmsMsg, cinfo, 0, false);
+ if (s != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't set content data\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ rcpt = NSS_CMSRecipientInfo_Create(cmsMsg, cert);
+ if (!rcpt) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't create recipient info\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ s = NSS_CMSEnvelopedData_AddRecipient(env, rcpt);
+ if (s != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't add recipient\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ /* Step 5. Add content to message */
+ cinfo = NSS_CMSMessage_GetContentInfo(cmsMsg);
+ s = NSS_CMSContentInfo_SetContent_EnvelopedData(cmsMsg, cinfo, env);
+ if (s != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't set content enveloped data\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ /* Step 6. Encode */
+ NSSCMSEncoderContext *ecx;
+
+ output.data = 0; output.len = 0;
+ ecx = NSS_CMSEncoder_Start(cmsMsg, 0, 0, &output, arena,
+ 0, ctx, 0, 0, 0, 0);
+ if (!ecx) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't start cms encoder\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ s = NSS_CMSEncoder_Update(ecx, msg, strlen(msg));
+ if (s != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't update encoder\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ s = NSS_CMSEncoder_Finish(ecx);
+ if (s != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't finish encoder\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ /* Step 7. Base64 encode and return the result */
+ rv = encode(output.data, output.len, _retval);
+
+done:
+ if (certDER) free((char *)certDER);
+ if (cert) CERT_DestroyCertificate(cert);
+ if (cmsMsg) NSS_CMSMessage_Destroy(cmsMsg);
+ if (arena) PORT_FreeArena(arena, false); /* false? */
+
+ return rv;
+}
+
+/*
+ * nsCMSSecureMessage::ReceiveMessage
+ */
+nsresult nsCMSSecureMessage::
+ReceiveMessage(const char *msg, char **_retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage\n"));
+ nsresult rv = NS_OK;
+ NSSCMSDecoderContext *dcx;
+ unsigned char *der = 0;
+ int32_t derLen;
+ NSSCMSMessage *cmsMsg = 0;
+ SECItem *content;
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+
+ /* Step 1. Decode the base64 wrapper */
+ rv = decode(msg, &der, &derLen);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't base64 decode\n"));
+ goto done;
+ }
+
+ dcx = NSS_CMSDecoder_Start(0, 0, 0, /* pw */ 0, ctx, /* key */ 0, 0);
+ if (!dcx) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't start decoder\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ (void)NSS_CMSDecoder_Update(dcx, (char *)der, derLen);
+ cmsMsg = NSS_CMSDecoder_Finish(dcx);
+ if (!cmsMsg) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't finish decoder\n"));
+ rv = NS_ERROR_FAILURE;
+ /* Memory leak on dcx?? */
+ goto done;
+ }
+
+ content = NSS_CMSMessage_GetContent(cmsMsg);
+ if (!content) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't get content\n"));
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ /* Copy the data */
+ *_retval = (char*)malloc(content->len+1);
+ memcpy(*_retval, content->data, content->len);
+ (*_retval)[content->len] = 0;
+
+done:
+ if (der) free(der);
+ if (cmsMsg) NSS_CMSMessage_Destroy(cmsMsg);
+
+ return rv;
+}
+
+nsresult nsCMSSecureMessage::
+encode(const unsigned char *data, int32_t dataLen, char **_retval)
+{
+ nsresult rv = NS_OK;
+
+ *_retval = PL_Base64Encode((const char *)data, dataLen, nullptr);
+ if (!*_retval) { rv = NS_ERROR_OUT_OF_MEMORY; goto loser; }
+
+loser:
+ return rv;
+}
+
+nsresult nsCMSSecureMessage::
+decode(const char *data, unsigned char **result, int32_t * _retval)
+{
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::decode\n"));
+ nsresult rv = NS_OK;
+ uint32_t len = strlen(data);
+ int adjust = 0;
+
+ /* Compute length adjustment */
+ if (data[len-1] == '=') {
+ adjust++;
+ if (data[len-2] == '=') adjust++;
+ }
+
+ *result = (unsigned char *)PL_Base64Decode(data, len, nullptr);
+ if (!*result) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::decode - error decoding base64\n"));
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ goto loser;
+ }
+
+ *_retval = (len*3)/4 - adjust;
+
+loser:
+ return rv;
+}
diff --git a/mailnews/mime/src/nsCMSSecureMessage.h b/mailnews/mime/src/nsCMSSecureMessage.h
new file mode 100644
index 000000000..a36124ab0
--- /dev/null
+++ b/mailnews/mime/src/nsCMSSecureMessage.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _NSCMSSECUREMESSAGE_H_
+#define _NSCMSSECUREMESSAGE_H_
+
+#include "nsICMSSecureMessage.h"
+
+#include "cms.h"
+
+// ===============================================
+// nsCMSManager - implementation of nsICMSManager
+// ===============================================
+
+#define NS_CMSSECUREMESSAGE_CID \
+ { 0x5fb907e0, 0x1dd2, 0x11b2, { 0xa7, 0xc0, 0xf1, 0x4c, 0x41, 0x6a, 0x62, 0xa1 } }
+
+class nsCMSSecureMessage
+: public nsICMSSecureMessage
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICMSSECUREMESSAGE
+
+ nsCMSSecureMessage();
+ nsresult Init();
+
+private:
+ virtual ~nsCMSSecureMessage();
+ NS_METHOD encode(const unsigned char *data, int32_t dataLen, char **_retval);
+ NS_METHOD decode(const char *data, unsigned char **result, int32_t * _retval);
+};
+
+
+#endif /* _NSCMSMESSAGE_H_ */
diff --git a/mailnews/mime/src/nsMimeObjectClassAccess.cpp b/mailnews/mime/src/nsMimeObjectClassAccess.cpp
new file mode 100644
index 000000000..f1286dcc1
--- /dev/null
+++ b/mailnews/mime/src/nsMimeObjectClassAccess.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <stdio.h>
+#include "mimecom.h"
+#include "nscore.h"
+#include "nsMimeObjectClassAccess.h"
+
+/*
+ * The following macros actually implement addref, release and
+ * query interface for our component.
+ */
+NS_IMPL_ISUPPORTS(nsMimeObjectClassAccess, nsIMimeObjectClassAccess)
+
+/*
+ * nsMimeObjectClassAccess definitions....
+ */
+
+/*
+ * Inherited methods for nsMimeObjectClassAccess
+ */
+nsMimeObjectClassAccess::nsMimeObjectClassAccess()
+{
+}
+
+nsMimeObjectClassAccess::~nsMimeObjectClassAccess()
+{
+}
+
+nsresult
+nsMimeObjectClassAccess::MimeObjectWrite(void *mimeObject,
+ char *data,
+ int32_t length,
+ bool user_visible_p)
+{
+ int rc = XPCOM_MimeObject_write(mimeObject, data, length, user_visible_p);
+ NS_ENSURE_FALSE(rc < 0, NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeObjectClassAccess::GetmimeInlineTextClass(void **ptr)
+{
+ *ptr = XPCOM_GetmimeInlineTextClass();
+ return NS_OK;
+}
+
+nsresult
+nsMimeObjectClassAccess::GetmimeLeafClass(void **ptr)
+{
+ *ptr = XPCOM_GetmimeLeafClass();
+ return NS_OK;
+}
+
+nsresult
+nsMimeObjectClassAccess::GetmimeObjectClass(void **ptr)
+{
+ *ptr = XPCOM_GetmimeObjectClass();
+ return NS_OK;
+}
+
+nsresult
+nsMimeObjectClassAccess::GetmimeContainerClass(void **ptr)
+{
+ *ptr = XPCOM_GetmimeContainerClass();
+ return NS_OK;
+}
+
+nsresult
+nsMimeObjectClassAccess::GetmimeMultipartClass(void **ptr)
+{
+ *ptr = XPCOM_GetmimeMultipartClass();
+ return NS_OK;
+}
+
+nsresult
+nsMimeObjectClassAccess::GetmimeMultipartSignedClass(void **ptr)
+{
+ *ptr = XPCOM_GetmimeMultipartSignedClass();
+ return NS_OK;
+}
+
+nsresult
+nsMimeObjectClassAccess::GetmimeEncryptedClass(void **ptr)
+{
+ *ptr = XPCOM_GetmimeEncryptedClass();
+ return NS_OK;
+}
+
+nsresult
+nsMimeObjectClassAccess::MimeCreate(char * content_type, void * hdrs, void * opts, void **ptr)
+{
+ *ptr = XPCOM_Mime_create(content_type, hdrs, opts);
+ return NS_OK;
+}
diff --git a/mailnews/mime/src/nsMimeObjectClassAccess.h b/mailnews/mime/src/nsMimeObjectClassAccess.h
new file mode 100644
index 000000000..d4a189aac
--- /dev/null
+++ b/mailnews/mime/src/nsMimeObjectClassAccess.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This interface is implemented by libmime. This interface is used by
+ * a Content-Type handler "Plug In" (i.e. vCard) for accessing various
+ * internal information about the object class system of libmime. When
+ * libmime progresses to a C++ object class, this would probably change.
+ */
+#ifndef nsMimeObjectClassAccess_h_
+#define nsMimeObjectClassAccess_h_
+
+#include "mozilla/Attributes.h"
+#include "nsISupports.h"
+#include "nsIMimeObjectClassAccess.h"
+
+class nsMimeObjectClassAccess : public nsIMimeObjectClassAccess {
+public:
+ nsMimeObjectClassAccess();
+
+ /* this macro defines QueryInterface, AddRef and Release for this class */
+ NS_DECL_ISUPPORTS
+
+ // These methods are all implemented by libmime to be used by
+ // content type handler plugins for processing stream data.
+
+ // This is the write call for outputting processed stream data.
+ NS_IMETHOD MimeObjectWrite(void *mimeObject,
+ char *data,
+ int32_t length,
+ bool user_visible_p) override;
+
+ // The following group of calls expose the pointers for the object
+ // system within libmime.
+ NS_IMETHOD GetmimeInlineTextClass(void **ptr) override;
+ NS_IMETHOD GetmimeLeafClass(void **ptr) override;
+ NS_IMETHOD GetmimeObjectClass(void **ptr) override;
+ NS_IMETHOD GetmimeContainerClass(void **ptr) override;
+ NS_IMETHOD GetmimeMultipartClass(void **ptr) override;
+ NS_IMETHOD GetmimeMultipartSignedClass(void **ptr) override;
+ NS_IMETHOD GetmimeEncryptedClass(void **ptr) override;
+
+ NS_IMETHOD MimeCreate(char *content_type, void * hdrs,
+ void * opts, void**ptr) override;
+
+private:
+ virtual ~nsMimeObjectClassAccess();
+};
+
+#endif /* nsMimeObjectClassAccess_h_ */
diff --git a/mailnews/mime/src/nsMimeStringResources.h b/mailnews/mime/src/nsMimeStringResources.h
new file mode 100644
index 000000000..4177c7863
--- /dev/null
+++ b/mailnews/mime/src/nsMimeStringResources.h
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _NAME_OF_THIS_HEADER_FILE__
+#define _NAME_OF_THIS_HEADER_FILE__
+
+/* Note that the negative values are not actually strings: they are error
+ * codes masquerading as strings. Do not pass them to MimeGetStringByID()
+ * expecting to get anything back for your trouble.
+ */
+#define MIME_OUT_OF_MEMORY -1000
+#define MIME_UNABLE_TO_OPEN_TMP_FILE -1001
+#define MIME_ERROR_WRITING_FILE -1002
+#define MIME_MHTML_SUBJECT 1000
+#define MIME_MHTML_RESENT_COMMENTS 1001
+#define MIME_MHTML_RESENT_DATE 1002
+#define MIME_MHTML_RESENT_SENDER 1003
+#define MIME_MHTML_RESENT_FROM 1004
+#define MIME_MHTML_RESENT_TO 1005
+#define MIME_MHTML_RESENT_CC 1006
+#define MIME_MHTML_DATE 1007
+#define MIME_MHTML_SENDER 1008
+#define MIME_MHTML_FROM 1009
+#define MIME_MHTML_REPLY_TO 1010
+#define MIME_MHTML_ORGANIZATION 1011
+#define MIME_MHTML_TO 1012
+#define MIME_MHTML_CC 1013
+#define MIME_MHTML_NEWSGROUPS 1014
+#define MIME_MHTML_FOLLOWUP_TO 1015
+#define MIME_MHTML_REFERENCES 1016
+#define MIME_MHTML_MESSAGE_ID 1021
+#define MIME_MHTML_BCC 1023
+#define MIME_MSG_LINK_TO_DOCUMENT 1026
+#define MIME_MSG_DOCUMENT_INFO 1027
+#define MIME_MSG_ATTACHMENT 1028
+#define MIME_MSG_DEFAULT_ATTACHMENT_NAME 1040
+#define MIME_FORWARDED_MESSAGE_HTML_USER_WROTE 1041
+
+#endif /* _NAME_OF_THIS_HEADER_FILE__ */
diff --git a/mailnews/mime/src/nsSimpleMimeConverterStub.cpp b/mailnews/mime/src/nsSimpleMimeConverterStub.cpp
new file mode 100644
index 000000000..ca608b54c
--- /dev/null
+++ b/mailnews/mime/src/nsSimpleMimeConverterStub.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mimecth.h"
+#include "mimeobj.h"
+#include "mimetext.h"
+#include "mimemoz2.h"
+#include "mimecom.h"
+#include "nsStringGlue.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICategoryManager.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsISimpleMimeConverter.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSimpleMimeConverterStub.h"
+
+typedef struct MimeSimpleStub MimeSimpleStub;
+typedef struct MimeSimpleStubClass MimeSimpleStubClass;
+
+struct MimeSimpleStubClass {
+ MimeInlineTextClass text;
+};
+
+struct MimeSimpleStub {
+ MimeInlineText text;
+ nsCString *buffer;
+ nsCOMPtr<nsISimpleMimeConverter> innerScriptable;
+};
+
+#define MimeSimpleStubClassInitializer(ITYPE,CSUPER) \
+ { MimeInlineTextClassInitializer(ITYPE,CSUPER) }
+
+MimeDefClass(MimeSimpleStub, MimeSimpleStubClass, mimeSimpleStubClass, NULL);
+
+static int
+BeginGather(MimeObject *obj)
+{
+ MimeSimpleStub *ssobj = (MimeSimpleStub *)obj;
+ int status = ((MimeObjectClass *)XPCOM_GetmimeLeafClass())->parse_begin(obj);
+
+ if (status < 0)
+ return status;
+
+ if (!obj->output_p ||
+ !obj->options ||
+ !obj->options->write_html_p) {
+ return 0;
+ }
+
+ ssobj->buffer->Truncate();
+ return 0;
+}
+
+static int
+GatherLine(const char *line, int32_t length, MimeObject *obj)
+{
+ MimeSimpleStub *ssobj = (MimeSimpleStub *)obj;
+
+ if (!obj->output_p ||
+ !obj->options ||
+ !obj->options->output_fn) {
+ return 0;
+ }
+
+ if (!obj->options->write_html_p)
+ return MimeObject_write(obj, line, length, true);
+
+ ssobj->buffer->Append(line);
+ return 0;
+}
+
+static int
+EndGather(MimeObject *obj, bool abort_p)
+{
+ MimeSimpleStub *ssobj = (MimeSimpleStub *)obj;
+
+ if (obj->closed_p)
+ return 0;
+
+ int status = ((MimeObjectClass *)MIME_GetmimeInlineTextClass())->parse_eof(obj, abort_p);
+ if (status < 0)
+ return status;
+
+ if (ssobj->buffer->IsEmpty())
+ return 0;
+
+ mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure);
+ nsIChannel *channel = msd->channel; // note the lack of ref counting...
+ if (channel)
+ {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ ssobj->innerScriptable->SetUri(uri);
+ }
+ nsCString asHTML;
+ nsresult rv = ssobj->innerScriptable->ConvertToHTML(nsDependentCString(obj->content_type),
+ *ssobj->buffer,
+ asHTML);
+ if (NS_FAILED(rv)) {
+ NS_ASSERTION(NS_SUCCEEDED(rv), "converter failure");
+ return -1;
+ }
+
+ // MimeObject_write wants a non-const string for some reason, but it doesn't mutate it
+ status = MimeObject_write(obj, asHTML.get(),
+ asHTML.Length(), true);
+ if (status < 0)
+ return status;
+ return 0;
+}
+
+static int
+Initialize(MimeObject *obj)
+{
+ MimeSimpleStub *ssobj = (MimeSimpleStub *)obj;
+
+ nsresult rv;
+ nsCOMPtr<nsICategoryManager> catman =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return -1;
+
+ nsAutoCString contentType; // lowercase
+ ToLowerCase(nsDependentCString(obj->content_type), contentType);
+
+ nsCString value;
+ rv = catman->GetCategoryEntry(NS_SIMPLEMIMECONVERTERS_CATEGORY,
+ contentType.get(), getter_Copies(value));
+ if (NS_FAILED(rv) || value.IsEmpty())
+ return -1;
+
+ ssobj->innerScriptable = do_CreateInstance(value.get(), &rv);
+ if (NS_FAILED(rv) || !ssobj->innerScriptable)
+ return -1;
+ ssobj->buffer = new nsCString();
+ ((MimeObjectClass *)XPCOM_GetmimeLeafClass())->initialize(obj);
+
+ return 0;
+}
+
+static void
+Finalize(MimeObject *obj)
+{
+ MimeSimpleStub *ssobj = (MimeSimpleStub *)obj;
+ ssobj->innerScriptable = nullptr;
+ delete ssobj->buffer;
+}
+
+static int
+MimeSimpleStubClassInitialize(MimeSimpleStubClass *clazz)
+{
+ MimeObjectClass *oclass = (MimeObjectClass *)clazz;
+ oclass->parse_begin = BeginGather;
+ oclass->parse_line = GatherLine;
+ oclass->parse_eof = EndGather;
+ oclass->initialize = Initialize;
+ oclass->finalize = Finalize;
+ return 0;
+}
+
+class nsSimpleMimeConverterStub : public nsIMimeContentTypeHandler
+{
+public:
+ explicit nsSimpleMimeConverterStub(const char *aContentType) : mContentType(aContentType) { }
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD GetContentType(char **contentType) override
+ {
+ *contentType = ToNewCString(mContentType);
+ return *contentType ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_IMETHOD CreateContentTypeHandlerClass(const char *contentType,
+ contentTypeHandlerInitStruct *initString,
+ MimeObjectClass **objClass) override;
+private:
+ virtual ~nsSimpleMimeConverterStub() { }
+ nsCString mContentType;
+};
+
+NS_IMPL_ISUPPORTS(nsSimpleMimeConverterStub, nsIMimeContentTypeHandler)
+
+NS_IMETHODIMP
+nsSimpleMimeConverterStub::CreateContentTypeHandlerClass(const char *contentType,
+ contentTypeHandlerInitStruct *initStruct,
+ MimeObjectClass **objClass)
+{
+ NS_ENSURE_ARG_POINTER(objClass);
+
+ *objClass = (MimeObjectClass *)&mimeSimpleStubClass;
+ (*objClass)->superclass = (MimeObjectClass *)XPCOM_GetmimeInlineTextClass();
+ NS_ENSURE_TRUE((*objClass)->superclass, NS_ERROR_UNEXPECTED);
+
+ initStruct->force_inline_display = true;
+ return NS_OK;;
+}
+
+nsresult
+MIME_NewSimpleMimeConverterStub(const char *aContentType,
+ nsIMimeContentTypeHandler **aResult)
+{
+ RefPtr<nsSimpleMimeConverterStub> inst = new nsSimpleMimeConverterStub(aContentType);
+ NS_ENSURE_TRUE(inst, NS_ERROR_OUT_OF_MEMORY);
+
+ return CallQueryInterface(inst.get(), aResult);
+}
diff --git a/mailnews/mime/src/nsSimpleMimeConverterStub.h b/mailnews/mime/src/nsSimpleMimeConverterStub.h
new file mode 100644
index 000000000..bdc12e5e3
--- /dev/null
+++ b/mailnews/mime/src/nsSimpleMimeConverterStub.h
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_SIMPLE_MIME_CONVERTER_STUB_H_
+#define NS_SIMPLE_MIME_CONVERTER_STUB_H_
+
+nsresult
+MIME_NewSimpleMimeConverterStub(const char *aContentType,
+ nsIMimeContentTypeHandler **aResult);
+
+#endif /* NS_SIMPLE_MIME_CONVERTER_STUB_H_ */
diff --git a/mailnews/mime/src/nsStreamConverter.cpp b/mailnews/mime/src/nsStreamConverter.cpp
new file mode 100644
index 000000000..0d1781498
--- /dev/null
+++ b/mailnews/mime/src/nsStreamConverter.cpp
@@ -0,0 +1,1157 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include <stdio.h>
+#include "mimecom.h"
+#include "modmimee.h"
+#include "nscore.h"
+#include "nsStreamConverter.h"
+#include "prmem.h"
+#include "prprf.h"
+#include "prlog.h"
+#include "plstr.h"
+#include "mimemoz2.h"
+#include "nsMimeTypes.h"
+#include "nsIComponentManager.h"
+#include "nsIURL.h"
+#include "nsStringGlue.h"
+#include "nsUnicharUtils.h"
+#include "nsIServiceManager.h"
+#include "nsMemory.h"
+#include "nsIPipe.h"
+#include "nsMimeStringResources.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsNetUtil.h"
+#include "nsIMsgQuote.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNetUtil.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgWindow.h"
+#include "nsICategoryManager.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsMsgUtils.h"
+#include "mozilla/ArrayUtils.h"
+
+#define PREF_MAIL_DISPLAY_GLYPH "mail.display_glyph"
+#define PREF_MAIL_DISPLAY_STRUCT "mail.display_struct"
+
+////////////////////////////////////////////////////////////////
+// Bridge routines for new stream converter XP-COM interface
+////////////////////////////////////////////////////////////////
+
+extern "C" void *
+mime_bridge_create_draft_stream(nsIMimeEmitter *newEmitter,
+ nsStreamConverter *newPluginObj2,
+ nsIURI *uri,
+ nsMimeOutputType format_out);
+
+extern "C" void *
+bridge_create_stream(nsIMimeEmitter *newEmitter,
+ nsStreamConverter *newPluginObj2,
+ nsIURI *uri,
+ nsMimeOutputType format_out,
+ uint32_t whattodo,
+ nsIChannel *aChannel)
+{
+ if ( (format_out == nsMimeOutput::nsMimeMessageDraftOrTemplate) ||
+ (format_out == nsMimeOutput::nsMimeMessageEditorTemplate) )
+ return mime_bridge_create_draft_stream(newEmitter, newPluginObj2, uri, format_out);
+ else
+ return mime_bridge_create_display_stream(newEmitter, newPluginObj2, uri, format_out, whattodo,
+ aChannel);
+}
+
+void
+bridge_destroy_stream(void *newStream)
+{
+ nsMIMESession *stream = (nsMIMESession *)newStream;
+ if (!stream)
+ return;
+
+ PR_FREEIF(stream);
+}
+
+void
+bridge_set_output_type(void *bridgeStream, nsMimeOutputType aType)
+{
+ nsMIMESession *session = (nsMIMESession *)bridgeStream;
+
+ if (session)
+ {
+ // BAD ASSUMPTION!!!! NEED TO CHECK aType
+ mime_stream_data *msd = (mime_stream_data *)session->data_object;
+ if (msd)
+ msd->format_out = aType; // output format type
+ }
+}
+
+nsresult
+bridge_new_new_uri(void *bridgeStream, nsIURI *aURI, int32_t aOutputType)
+{
+ nsMIMESession *session = (nsMIMESession *)bridgeStream;
+ const char **fixup_pointer = nullptr;
+
+ if (session)
+ {
+ if (session->data_object)
+ {
+ bool *override_charset = nullptr;
+ char **default_charset = nullptr;
+ char **url_name = nullptr;
+
+ if ( (aOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) ||
+ (aOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) )
+ {
+ mime_draft_data *mdd = (mime_draft_data *)session->data_object;
+ if (mdd->options)
+ {
+ default_charset = &(mdd->options->default_charset);
+ override_charset = &(mdd->options->override_charset);
+ url_name = &(mdd->url_name);
+ }
+ }
+ else
+ {
+ mime_stream_data *msd = (mime_stream_data *)session->data_object;
+
+ if (msd->options)
+ {
+ default_charset = &(msd->options->default_charset);
+ override_charset = &(msd->options->override_charset);
+ url_name = &(msd->url_name);
+ fixup_pointer = &(msd->options->url);
+ }
+ }
+
+ if ( (default_charset) && (override_charset) && (url_name) )
+ {
+ //
+ // set the default charset to be the folder charset if we have one associated with
+ // this url...
+ nsCOMPtr<nsIMsgI18NUrl> i18nUrl (do_QueryInterface(aURI));
+ if (i18nUrl)
+ {
+ nsCString charset;
+
+ // check to see if we have a charset override...and if we do, set that field appropriately too...
+ nsresult rv = i18nUrl->GetCharsetOverRide(getter_Copies(charset));
+ if (NS_SUCCEEDED(rv) && !charset.IsEmpty() ) {
+ *override_charset = true;
+ *default_charset = ToNewCString(charset);
+ }
+ else
+ {
+ i18nUrl->GetFolderCharset(getter_Copies(charset));
+ if (!charset.IsEmpty())
+ *default_charset = ToNewCString(charset);
+ }
+
+ // if there is no manual override and a folder charset exists
+ // then check if we have a folder level override
+ if (!(*override_charset) && *default_charset && **default_charset)
+ {
+ bool folderCharsetOverride;
+ rv = i18nUrl->GetFolderCharsetOverride(&folderCharsetOverride);
+ if (NS_SUCCEEDED(rv) && folderCharsetOverride)
+ *override_charset = true;
+
+ // notify the default to msgWindow (for the menu check mark)
+ // do not set the default in case of nsMimeMessageDraftOrTemplate
+ // or nsMimeMessageEditorTemplate because it is already set
+ // when the message is displayed and doing it again may overwrite
+ // the correct MIME charset parsed from the message header
+ if (aOutputType != nsMimeOutput::nsMimeMessageDraftOrTemplate &&
+ aOutputType != nsMimeOutput::nsMimeMessageEditorTemplate)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(aURI));
+ if (msgurl)
+ {
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ msgurl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ msgWindow->SetMailCharacterSet(nsDependentCString(*default_charset));
+ msgWindow->SetCharsetOverride(*override_charset);
+ }
+ }
+ }
+
+ // if the pref says always override and no manual override then set the folder charset to override
+ if (!*override_charset) {
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (pPrefBranch)
+ {
+ bool force_override;
+ rv = pPrefBranch->GetBoolPref("mailnews.force_charset_override", &force_override);
+ if (NS_SUCCEEDED(rv) && force_override)
+ {
+ *override_charset = true;
+ }
+ }
+ }
+ }
+ }
+ nsAutoCString urlString;
+ if (NS_SUCCEEDED(aURI->GetSpec(urlString)))
+ {
+ if (!urlString.IsEmpty())
+ {
+ NS_Free(*url_name);
+ *url_name = ToNewCString(urlString);
+ if (!(*url_name))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // rhp: Ugh, this is ugly...but it works.
+ if (fixup_pointer)
+ *fixup_pointer = (const char *)*url_name;
+ }
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+static int
+mime_headers_callback ( void *closure, MimeHeaders *headers )
+{
+ // We get away with this because this doesn't get called on draft operations.
+ mime_stream_data *msd = (mime_stream_data *)closure;
+
+ NS_ASSERTION(msd && headers, "null mime stream data or headers");
+ if ( !msd || ! headers )
+ return 0;
+
+ NS_ASSERTION(!msd->headers, "non-null mime stream data headers");
+ msd->headers = MimeHeaders_copy ( headers );
+ return 0;
+}
+
+nsresult
+bridge_set_mime_stream_converter_listener(void *bridgeStream, nsIMimeStreamConverterListener* listener,
+ nsMimeOutputType aOutputType)
+{
+ nsMIMESession *session = (nsMIMESession *)bridgeStream;
+
+ if ( (session) && (session->data_object) )
+ {
+ if ( (aOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) ||
+ (aOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) )
+ {
+ mime_draft_data *mdd = (mime_draft_data *)session->data_object;
+ if (mdd->options)
+ {
+ if (listener)
+ {
+ mdd->options->caller_need_root_headers = true;
+ mdd->options->decompose_headers_info_fn = mime_headers_callback;
+ }
+ else
+ {
+ mdd->options->caller_need_root_headers = false;
+ mdd->options->decompose_headers_info_fn = nullptr;
+ }
+ }
+ }
+ else
+ {
+ mime_stream_data *msd = (mime_stream_data *)session->data_object;
+
+ if (msd->options)
+ {
+ if (listener)
+ {
+ msd->options->caller_need_root_headers = true;
+ msd->options->decompose_headers_info_fn = mime_headers_callback;
+ }
+ else
+ {
+ msd->options->caller_need_root_headers = false;
+ msd->options->decompose_headers_info_fn = nullptr;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// find a query element in a url and return a pointer to its data
+// (query must be in the form "query=")
+static const char *
+FindQueryElementData(const char * aUrl, const char * aQuery)
+{
+ if (aUrl && aQuery)
+ {
+ size_t queryLen = 0; // we don't call strlen until we need to
+ aUrl = PL_strcasestr(aUrl, aQuery);
+ while (aUrl)
+ {
+ if (!queryLen)
+ queryLen = strlen(aQuery);
+ if (*(aUrl-1) == '&' || *(aUrl-1) == '?')
+ return aUrl + queryLen;
+ aUrl = PL_strcasestr(aUrl + queryLen, aQuery);
+ }
+ }
+ return nullptr;
+}
+
+// case-sensitive test for string prefixing. If |string| is prefixed
+// by |prefix| then a pointer to the next character in |string| following
+// the prefix is returned. If it is not a prefix then |nullptr| is returned.
+static const char *
+SkipPrefix(const char *aString, const char *aPrefix)
+{
+ while (*aPrefix)
+ if (*aPrefix++ != *aString++)
+ return nullptr;
+ return aString;
+}
+
+//
+// Utility routines needed by this interface
+//
+nsresult
+nsStreamConverter::DetermineOutputFormat(const char *aUrl, nsMimeOutputType *aNewType)
+{
+ // sanity checking
+ NS_ENSURE_ARG_POINTER(aNewType);
+ if (!aUrl || !*aUrl)
+ {
+ // default to html for the entire document
+ *aNewType = nsMimeOutput::nsMimeMessageQuoting;
+ mOutputFormat = "text/html";
+ return NS_OK;
+ }
+
+ // shorten the url that we test for the query strings by skipping directly
+ // to the part where the query strings begin.
+ const char *queryPart = PL_strchr(aUrl, '?');
+
+ // First, did someone pass in a desired output format. They will be able to
+ // pass in any content type (i.e. image/gif, text/html, etc...but the "/" will
+ // have to be represented via the "%2F" value
+ const char *format = FindQueryElementData(queryPart, "outformat=");
+ if (format)
+ {
+ //NOTE: I've done a file contents search of every file (*.*) in the mozilla
+ // directory tree and there is not a single location where the string "outformat"
+ // is added to any URL. It appears that this code has been orphaned off by a change
+ // elsewhere and is no longer required. It will be removed in the future unless
+ // someone complains.
+ MOZ_ASSERT(false, "Is this code actually being used?");
+
+ while (*format == ' ')
+ ++format;
+
+ if (*format)
+ {
+ mOverrideFormat = "raw";
+
+ // set mOutputFormat to the supplied format, ensure that we replace any
+ // %2F strings with the slash character
+ const char *nextField = PL_strpbrk(format, "&; ");
+ mOutputFormat.Assign(format, nextField ? nextField - format : -1);
+ MsgReplaceSubstring(mOutputFormat, "%2F", "/");
+ MsgReplaceSubstring(mOutputFormat, "%2f", "/");
+
+ // Don't muck with this data!
+ *aNewType = nsMimeOutput::nsMimeMessageRaw;
+ return NS_OK;
+ }
+ }
+
+ // is this is a part that should just come out raw
+ const char *part = FindQueryElementData(queryPart, "part=");
+ if (part && !mToType.Equals("application/vnd.mozilla.xul+xml"))
+ {
+ // default for parts
+ mOutputFormat = "raw";
+ *aNewType = nsMimeOutput::nsMimeMessageRaw;
+
+ // if we are being asked to fetch a part....it should have a
+ // content type appended to it...if it does, we want to remember
+ // that as mOutputFormat
+ const char * typeField = FindQueryElementData(queryPart, "type=");
+ if (typeField && !strncmp(typeField, "application/x-message-display", sizeof("application/x-message-display") - 1))
+ {
+ const char *secondTypeField = FindQueryElementData(typeField, "type=");
+ if (secondTypeField)
+ typeField = secondTypeField;
+ }
+ if (typeField)
+ {
+ // store the real content type...mOutputFormat gets deleted later on...
+ // and make sure we only get our own value.
+ char *nextField = PL_strchr(typeField, '&');
+ mRealContentType.Assign(typeField, nextField ? nextField - typeField : -1);
+ if (mRealContentType.Equals("message/rfc822"))
+ {
+ mRealContentType = "application/x-message-display";
+ mOutputFormat = "text/html";
+ *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay;
+ }
+ else if (mRealContentType.Equals("application/x-message-display"))
+ {
+ mRealContentType = "";
+ mOutputFormat = "text/html";
+ *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ const char *emitter = FindQueryElementData(queryPart, "emitter=");
+ if (emitter)
+ {
+ const char *remainder = SkipPrefix(emitter, "js");
+ if (remainder && (!*remainder || *remainder == '&'))
+ mOverrideFormat = "application/x-js-mime-message";
+ }
+
+ // if using the header query
+ const char *header = FindQueryElementData(queryPart, "header=");
+ if (header)
+ {
+ struct HeaderType {
+ const char * headerType;
+ const char * outputFormat;
+ nsMimeOutputType mimeOutputType;
+ };
+
+ // place most commonly used options at the top
+ static const struct HeaderType rgTypes[] =
+ {
+ { "filter", "text/html", nsMimeOutput::nsMimeMessageFilterSniffer },
+ { "quotebody", "text/html", nsMimeOutput::nsMimeMessageBodyQuoting },
+ { "print", "text/html", nsMimeOutput::nsMimeMessagePrintOutput },
+ { "only", "text/xml", nsMimeOutput::nsMimeMessageHeaderDisplay },
+ { "none", "text/html", nsMimeOutput::nsMimeMessageBodyDisplay },
+ { "quote", "text/html", nsMimeOutput::nsMimeMessageQuoting },
+ { "saveas", "text/html", nsMimeOutput::nsMimeMessageSaveAs },
+ { "src", "text/plain", nsMimeOutput::nsMimeMessageSource },
+ { "attach", "raw", nsMimeOutput::nsMimeMessageAttach }
+ };
+
+ // find the requested header in table, ensure that we don't match on a prefix
+ // by checking that the following character is either null or the next query element
+ const char * remainder;
+ for (uint32_t n = 0; n < MOZ_ARRAY_LENGTH(rgTypes); ++n)
+ {
+ remainder = SkipPrefix(header, rgTypes[n].headerType);
+ if (remainder && (*remainder == '\0' || *remainder == '&'))
+ {
+ mOutputFormat = rgTypes[n].outputFormat;
+ *aNewType = rgTypes[n].mimeOutputType;
+ return NS_OK;
+ }
+ }
+ }
+
+ // default to html for just the body
+ mOutputFormat = "text/html";
+ *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay;
+
+ return NS_OK;
+}
+
+nsresult
+nsStreamConverter::InternalCleanup(void)
+{
+ if (mBridgeStream)
+ {
+ bridge_destroy_stream(mBridgeStream);
+ mBridgeStream = nullptr;
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Inherited methods for nsMimeConverter
+ */
+nsStreamConverter::nsStreamConverter()
+{
+ // Init member variables...
+ mWrapperOutput = false;
+ mBridgeStream = nullptr;
+ mOutputFormat = "text/html";
+ mAlreadyKnowOutputType = false;
+ mForwardInline = false;
+ mForwardInlineFilter = false;
+ mOverrideComposeFormat = false;
+
+ mPendingRequest = nullptr;
+ mPendingContext = nullptr;
+}
+
+nsStreamConverter::~nsStreamConverter()
+{
+ InternalCleanup();
+}
+
+NS_IMPL_ISUPPORTS(nsStreamConverter, nsIStreamListener, nsIRequestObserver,
+ nsIStreamConverter, nsIMimeStreamConverter)
+
+///////////////////////////////////////////////////////////////
+// nsStreamConverter definitions....
+///////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsStreamConverter::Init(nsIURI *aURI, nsIStreamListener * aOutListener, nsIChannel *aChannel)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsresult rv = NS_OK;
+ mOutListener = aOutListener;
+
+ // mscott --> we need to look at the url and figure out what the correct output type is...
+ nsMimeOutputType newType = mOutputType;
+ if (!mAlreadyKnowOutputType)
+ {
+ nsAutoCString urlSpec;
+ rv = aURI->GetSpec(urlSpec);
+ DetermineOutputFormat(urlSpec.get(), &newType);
+ mAlreadyKnowOutputType = true;
+ mOutputType = newType;
+ }
+
+ switch (newType)
+ {
+ case nsMimeOutput::nsMimeMessageSplitDisplay: // the wrapper HTML output to produce the split header/body display
+ mWrapperOutput = true;
+ mOutputFormat = "text/html";
+ break;
+ case nsMimeOutput::nsMimeMessageHeaderDisplay: // the split header/body display
+ mOutputFormat = "text/xml";
+ break;
+ case nsMimeOutput::nsMimeMessageBodyDisplay: // the split header/body display
+ mOutputFormat = "text/html";
+ break;
+
+ case nsMimeOutput::nsMimeMessageQuoting: // all HTML quoted output
+ case nsMimeOutput::nsMimeMessageSaveAs: // Save as operation
+ case nsMimeOutput::nsMimeMessageBodyQuoting: // only HTML body quoted output
+ case nsMimeOutput::nsMimeMessagePrintOutput: // all Printing output
+ mOutputFormat = "text/html";
+ break;
+
+ case nsMimeOutput::nsMimeMessageAttach:
+ case nsMimeOutput::nsMimeMessageDecrypt:
+ case nsMimeOutput::nsMimeMessageRaw: // the raw RFC822 data and attachments
+ mOutputFormat = "raw";
+ break;
+
+ case nsMimeOutput::nsMimeMessageSource: // the raw RFC822 data (view source) and attachments
+ mOutputFormat = "text/plain";
+ mOverrideFormat = "raw";
+ break;
+
+ case nsMimeOutput::nsMimeMessageDraftOrTemplate: // Loading drafts & templates
+ mOutputFormat = "message/draft";
+ break;
+
+ case nsMimeOutput::nsMimeMessageEditorTemplate: // Loading templates into editor
+ mOutputFormat = "text/html";
+ break;
+
+ case nsMimeOutput::nsMimeMessageFilterSniffer: // output all displayable part as raw
+ mOutputFormat = "text/html";
+ break;
+
+ default:
+ NS_ERROR("this means I made a mistake in my assumptions");
+ }
+
+
+ // the following output channel stream is used to fake the content type for people who later
+ // call into us..
+ nsCString contentTypeToUse;
+ GetContentType(getter_Copies(contentTypeToUse));
+ // mscott --> my theory is that we don't need this fake outgoing channel. Let's use the
+ // original channel and just set our content type ontop of the original channel...
+
+ aChannel->SetContentType(contentTypeToUse);
+
+ //rv = NS_NewInputStreamChannel(getter_AddRefs(mOutgoingChannel), aURI, nullptr, contentTypeToUse, -1);
+ //if (NS_FAILED(rv))
+ // return rv;
+
+ // Set system principal for this document, which will be dynamically generated
+
+ // We will first find an appropriate emitter in the repository that supports
+ // the requested output format...note, the special exceptions are nsMimeMessageDraftOrTemplate
+ // or nsMimeMessageEditorTemplate where we don't need any emitters
+ //
+
+ if ( (newType != nsMimeOutput::nsMimeMessageDraftOrTemplate) &&
+ (newType != nsMimeOutput::nsMimeMessageEditorTemplate) )
+ {
+ nsAutoCString categoryName ("@mozilla.org/messenger/mimeemitter;1?type=");
+ if (!mOverrideFormat.IsEmpty())
+ categoryName += mOverrideFormat;
+ else
+ categoryName += mOutputFormat;
+
+ nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString contractID;
+ catman->GetCategoryEntry("mime-emitter", categoryName.get(), getter_Copies(contractID));
+ if (!contractID.IsEmpty())
+ categoryName = contractID;
+ }
+
+ mEmitter = do_CreateInstance(categoryName.get(), &rv);
+
+ if ((NS_FAILED(rv)) || (!mEmitter))
+ {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // initialize our emitter
+ if (mEmitter)
+ {
+ // Now we want to create a pipe which we'll use for converting the data.
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+ rv = pipe->Init(true, true, 4096, 8);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // These always succeed because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(mInputStream)));
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(mOutputStream)));
+
+ mEmitter->Initialize(aURI, aChannel, newType);
+ mEmitter->SetPipe(mInputStream, mOutputStream);
+ mEmitter->SetOutputListener(aOutListener);
+ }
+
+ uint32_t whattodo = mozITXTToHTMLConv::kURLs;
+ bool enable_emoticons = true;
+ bool enable_structs = true;
+
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (pPrefBranch)
+ {
+ rv = pPrefBranch->GetBoolPref(PREF_MAIL_DISPLAY_GLYPH,&enable_emoticons);
+ if (NS_FAILED(rv) || enable_emoticons)
+ {
+ whattodo = whattodo | mozITXTToHTMLConv::kGlyphSubstitution;
+ }
+ rv = pPrefBranch->GetBoolPref(PREF_MAIL_DISPLAY_STRUCT,&enable_structs);
+ if (NS_FAILED(rv) || enable_structs)
+ {
+ whattodo = whattodo | mozITXTToHTMLConv::kStructPhrase;
+ }
+ }
+
+ if (mOutputType == nsMimeOutput::nsMimeMessageSource)
+ return NS_OK;
+ else
+ {
+ mBridgeStream = bridge_create_stream(mEmitter, this, aURI, newType, whattodo, aChannel);
+ if (!mBridgeStream)
+ return NS_ERROR_OUT_OF_MEMORY;
+ else
+ {
+ SetStreamURI(aURI);
+
+ //Do we need to setup an Mime Stream Converter Listener?
+ if (mMimeStreamConverterListener)
+ bridge_set_mime_stream_converter_listener((nsMIMESession *)mBridgeStream, mMimeStreamConverterListener, mOutputType);
+
+ return NS_OK;
+ }
+ }
+}
+
+NS_IMETHODIMP nsStreamConverter::GetContentType(char **aOutputContentType)
+{
+ if (!aOutputContentType)
+ return NS_ERROR_NULL_POINTER;
+
+ // since this method passes a string through an IDL file we need to use nsMemory to allocate it
+ // and not strdup!
+ // (1) check to see if we have a real content type...use it first...
+ if (!mRealContentType.IsEmpty())
+ *aOutputContentType = ToNewCString(mRealContentType);
+ else if (mOutputFormat.Equals("raw"))
+ *aOutputContentType = (char *) nsMemory::Clone(UNKNOWN_CONTENT_TYPE, sizeof(UNKNOWN_CONTENT_TYPE));
+ else
+ *aOutputContentType = ToNewCString(mOutputFormat);
+ return NS_OK;
+}
+
+//
+// This is the type of output operation that is being requested by libmime. The types
+// of output are specified by nsIMimeOutputType enum
+//
+nsresult
+nsStreamConverter::SetMimeOutputType(nsMimeOutputType aType)
+{
+ mAlreadyKnowOutputType = true;
+ mOutputType = aType;
+ if (mBridgeStream)
+ bridge_set_output_type(mBridgeStream, aType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsStreamConverter::GetMimeOutputType(nsMimeOutputType *aOutFormat)
+{
+ nsresult rv = NS_OK;
+ if (aOutFormat)
+ *aOutFormat = mOutputType;
+ else
+ rv = NS_ERROR_NULL_POINTER;
+
+ return rv;
+}
+
+//
+// This is needed by libmime for MHTML link processing...this is the URI associated
+// with this input stream
+//
+nsresult
+nsStreamConverter::SetStreamURI(nsIURI *aURI)
+{
+ mURI = aURI;
+ if (mBridgeStream)
+ return bridge_new_new_uri((nsMIMESession *)mBridgeStream, aURI, mOutputType);
+ else
+ return NS_OK;
+}
+
+nsresult
+nsStreamConverter::SetMimeHeadersListener(nsIMimeStreamConverterListener *listener, nsMimeOutputType aType)
+{
+ mMimeStreamConverterListener = listener;
+ bridge_set_mime_stream_converter_listener((nsMIMESession *)mBridgeStream, listener, aType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::SetForwardInline(bool aForwardInline)
+{
+ mForwardInline = aForwardInline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::GetForwardToAddress(nsAString &aAddress)
+{
+ aAddress = mForwardToAddress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::SetForwardToAddress(const nsAString &aAddress)
+{
+ mForwardToAddress = aAddress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::GetOverrideComposeFormat(bool *aResult)
+{
+ if (!aResult)
+ return NS_ERROR_NULL_POINTER;
+ *aResult = mOverrideComposeFormat;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::SetOverrideComposeFormat(bool aOverrideComposeFormat)
+{
+ mOverrideComposeFormat = aOverrideComposeFormat;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::GetForwardInline(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mForwardInline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::GetForwardInlineFilter(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mForwardInlineFilter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::SetForwardInlineFilter(bool aForwardInlineFilter)
+{
+ mForwardInlineFilter = aForwardInlineFilter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::GetIdentity(nsIMsgIdentity * *aIdentity)
+{
+ if (!aIdentity) return NS_ERROR_NULL_POINTER;
+ /*
+ We don't have an identity for the local folders account,
+ we will return null but it is not an error!
+ */
+ *aIdentity = mIdentity;
+ NS_IF_ADDREF(*aIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::SetIdentity(nsIMsgIdentity * aIdentity)
+{
+ mIdentity = aIdentity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::SetOriginalMsgURI(const char * originalMsgURI)
+{
+ mOriginalMsgURI = originalMsgURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::GetOriginalMsgURI(char ** result)
+{
+ if (!result) return NS_ERROR_NULL_POINTER;
+ *result = ToNewCString(mOriginalMsgURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::SetOrigMsgHdr(nsIMsgDBHdr *aMsgHdr)
+{
+ mOrigMsgHdr = aMsgHdr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::GetOrigMsgHdr(nsIMsgDBHdr * *aMsgHdr)
+{
+ if (!aMsgHdr) return NS_ERROR_NULL_POINTER;
+ NS_IF_ADDREF(*aMsgHdr = mOrigMsgHdr);
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Methods for nsIStreamListener...
+/////////////////////////////////////////////////////////////////////////////
+//
+// Notify the client that data is available in the input stream. This
+// method is called whenver data is written into the input stream by the
+// networking library...
+//
+nsresult
+nsStreamConverter::OnDataAvailable(nsIRequest *request,
+ nsISupports *ctxt,
+ nsIInputStream *aIStream,
+ uint64_t sourceOffset,
+ uint32_t aLength)
+{
+ nsresult rc=NS_OK; // should this be an error instead?
+ uint32_t readLen = aLength;
+ uint32_t written;
+
+ // If this is the first time through and we are supposed to be
+ // outputting the wrapper two pane URL, then do it now.
+ if (mWrapperOutput)
+ {
+ char outBuf[1024];
+const char output[] = "\
+<HTML>\
+<FRAMESET ROWS=\"30%%,70%%\">\
+<FRAME NAME=messageHeader SRC=\"%s?header=only\">\
+<FRAME NAME=messageBody SRC=\"%s?header=none\">\
+</FRAMESET>\
+</HTML>";
+
+ nsAutoCString url;
+ if (NS_FAILED(mURI->GetSpec(url)))
+ return NS_ERROR_FAILURE;
+
+ PR_snprintf(outBuf, sizeof(outBuf), output, url.get(), url.get());
+
+ if (mEmitter)
+ mEmitter->Write(nsDependentCString(outBuf), &written);
+
+ // rhp: will this stop the stream???? Not sure.
+ return NS_ERROR_FAILURE;
+ }
+
+ char *buf = (char *)PR_Malloc(aLength);
+ if (!buf)
+ return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */
+
+ readLen = aLength;
+ aIStream->Read(buf, aLength, &readLen);
+
+ // We need to filter out any null characters else we will have a lot of trouble
+ // as we use c string everywhere in mime
+ char * readPtr;
+ char * endPtr = buf + readLen;
+
+ // First let see if the stream contains null characters
+ for (readPtr = buf; readPtr < endPtr && *readPtr; readPtr ++)
+ ;
+
+ // Did we find a null character? Then, we need to cleanup the stream
+ if (readPtr < endPtr)
+ {
+ char * writePtr = readPtr;
+ for (readPtr ++; readPtr < endPtr; readPtr ++)
+ {
+ if (!*readPtr)
+ continue;
+
+ *writePtr = *readPtr;
+ writePtr ++;
+ }
+ readLen = writePtr - buf;
+ }
+
+ if (mOutputType == nsMimeOutput::nsMimeMessageSource)
+ {
+ rc = NS_OK;
+ if (mEmitter)
+ {
+ rc = mEmitter->Write(Substring(buf, buf+readLen), &written);
+ }
+ }
+ else if (mBridgeStream)
+ {
+ nsMIMESession *tSession = (nsMIMESession *) mBridgeStream;
+ // XXX Casting int to nsresult
+ rc = static_cast<nsresult>(
+ tSession->put_block((nsMIMESession *)mBridgeStream, buf, readLen));
+ }
+
+ PR_FREEIF(buf);
+ return rc;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Methods for nsIRequestObserver
+/////////////////////////////////////////////////////////////////////////////
+//
+// Notify the observer that the URL has started to load. This method is
+// called only once, at the beginning of a URL load.
+//
+nsresult
+nsStreamConverter::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+#ifdef DEBUG_rhp
+ printf("nsStreamConverter::OnStartRequest()\n");
+#endif
+
+#ifdef DEBUG_mscott
+ mConvertContentTime = PR_IntervalNow();
+#endif
+
+ // here's a little bit of hackery....
+ // since the mime converter is now between the channel
+ // and the
+ if (request)
+ {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel)
+ {
+ nsCString contentType;
+ GetContentType(getter_Copies(contentType));
+
+ channel->SetContentType(contentType);
+ }
+ }
+
+ // forward the start request to any listeners
+ if (mOutListener)
+ {
+ if (mOutputType == nsMimeOutput::nsMimeMessageRaw)
+ {
+ //we need to delay the on start request until we have figure out the real content type
+ mPendingRequest = request;
+ mPendingContext = ctxt;
+ }
+ else
+ mOutListener->OnStartRequest(request, ctxt);
+ }
+
+ return NS_OK;
+}
+
+//
+// Notify the observer that the URL has finished loading. This method is
+// called once when the networking library has finished processing the
+//
+nsresult
+nsStreamConverter::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
+{
+ // Make sure we fire any pending OnStartRequest before we do OnStop.
+ FirePendingStartRequest();
+#ifdef DEBUG_rhp
+ printf("nsStreamConverter::OnStopRequest()\n");
+#endif
+
+ //
+ // Now complete the stream!
+ //
+ if (mBridgeStream)
+ {
+ nsMIMESession *tSession = (nsMIMESession *) mBridgeStream;
+
+ if (mMimeStreamConverterListener)
+ {
+
+ MimeHeaders **workHeaders = nullptr;
+
+ if ( (mOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) ||
+ (mOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) )
+ {
+ mime_draft_data *mdd = (mime_draft_data *)tSession->data_object;
+ if (mdd)
+ workHeaders = &(mdd->headers);
+ }
+ else
+ {
+ mime_stream_data *msd = (mime_stream_data *)tSession->data_object;
+ if (msd)
+ workHeaders = &(msd->headers);
+ }
+
+ if (workHeaders)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMimeHeaders> mimeHeaders = do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ if (*workHeaders)
+ mimeHeaders->Initialize(Substring((*workHeaders)->all_headers,
+ (*workHeaders)->all_headers_fp));
+ mMimeStreamConverterListener->OnHeadersReady(mimeHeaders);
+ }
+ else
+ mMimeStreamConverterListener->OnHeadersReady(nullptr);
+ }
+
+ mMimeStreamConverterListener = nullptr; // release our reference
+ }
+
+ tSession->complete((nsMIMESession *)mBridgeStream);
+ }
+
+ //
+ // Now complete the emitter and do necessary cleanup!
+ //
+ if (mEmitter)
+ {
+ mEmitter->Complete();
+ }
+
+ // First close the output stream...
+ if (mOutputStream)
+ mOutputStream->Close();
+
+ // Make sure to do necessary cleanup!
+ InternalCleanup();
+
+#if 0
+ // print out the mime timing information BEFORE we flush to layout
+ // otherwise we'll be including layout information.
+ printf("Time Spent in mime: %d ms\n", PR_IntervalToMilliseconds(PR_IntervalNow() - mConvertContentTime));
+#endif
+
+ // forward on top request to any listeners
+ if (mOutListener)
+ mOutListener->OnStopRequest(request, ctxt, status);
+
+
+ mAlreadyKnowOutputType = false;
+
+ // since we are done converting data, lets close all the objects we own...
+ // this helps us fix some circular ref counting problems we are running into...
+ Close();
+
+ // Time to return...
+ return NS_OK;
+}
+
+nsresult nsStreamConverter::Close()
+{
+ mOutgoingChannel = nullptr;
+ mEmitter = nullptr;
+ mOutListener = nullptr;
+ return NS_OK;
+}
+
+// nsIStreamConverter implementation
+
+// No syncronous conversion at this time.
+NS_IMETHODIMP nsStreamConverter::Convert(nsIInputStream *aFromStream,
+ const char *aFromType,
+ const char *aToType,
+ nsISupports *aCtxt,
+ nsIInputStream **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// Stream converter service calls this to initialize the actual stream converter (us).
+NS_IMETHODIMP nsStreamConverter::AsyncConvertData(const char *aFromType,
+ const char *aToType,
+ nsIStreamListener *aListener,
+ nsISupports *aCtxt)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgQuote> aMsgQuote = do_QueryInterface(aCtxt, &rv);
+ nsCOMPtr<nsIChannel> aChannel;
+
+ if (aMsgQuote)
+ {
+ nsCOMPtr<nsIMimeStreamConverterListener> quoteListener;
+ rv = aMsgQuote->GetQuoteListener(getter_AddRefs(quoteListener));
+ if (quoteListener)
+ SetMimeHeadersListener(quoteListener, nsMimeOutput::nsMimeMessageQuoting);
+ rv = aMsgQuote->GetQuoteChannel(getter_AddRefs(aChannel));
+ }
+ else
+ {
+ aChannel = do_QueryInterface(aCtxt, &rv);
+ }
+
+ mFromType = aFromType;
+ mToType = aToType;
+
+ NS_ASSERTION(aChannel && NS_SUCCEEDED(rv), "mailnews mime converter has to have the channel passed in...");
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> aUri;
+ aChannel->GetURI(getter_AddRefs(aUri));
+ return Init(aUri, aListener, aChannel);
+}
+
+NS_IMETHODIMP nsStreamConverter::FirePendingStartRequest()
+{
+ if (mPendingRequest && mOutListener)
+ {
+ mOutListener->OnStartRequest(mPendingRequest, mPendingContext);
+ mPendingRequest = nullptr;
+ mPendingContext = nullptr;
+ }
+ return NS_OK;
+}
diff --git a/mailnews/mime/src/nsStreamConverter.h b/mailnews/mime/src/nsStreamConverter.h
new file mode 100644
index 000000000..0bd11d1d9
--- /dev/null
+++ b/mailnews/mime/src/nsStreamConverter.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsStreamConverter_h_
+#define nsStreamConverter_h_
+
+#include "nsIStreamConverter.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsIMimeEmitter.h"
+#include "nsIURI.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIChannel.h"
+#include "nsStringGlue.h"
+#include "nsCOMPtr.h"
+
+#define MIME_FORWARD_HTML_PREFIX "<HTML><BODY><BR><BR>"
+
+class nsStreamConverter : public nsIStreamConverter, public nsIMimeStreamConverter {
+public:
+ nsStreamConverter();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIMimeStreamConverter support
+ NS_DECL_NSIMIMESTREAMCONVERTER
+ // nsIStreamConverter methods
+ NS_DECL_NSISTREAMCONVERTER
+ // nsIStreamListener methods
+ NS_DECL_NSISTREAMLISTENER
+
+ // nsIRequestObserver methods
+ NS_DECL_NSIREQUESTOBSERVER
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsStreamConverter specific methods:
+ ////////////////////////////////////////////////////////////////////////////
+ NS_IMETHOD Init(nsIURI *aURI, nsIStreamListener * aOutListener, nsIChannel *aChannel);
+ NS_IMETHOD GetContentType(char **aOutputContentType);
+ NS_IMETHOD InternalCleanup(void);
+ NS_IMETHOD DetermineOutputFormat(const char *url, nsMimeOutputType *newType);
+ NS_IMETHOD FirePendingStartRequest(void);
+
+private:
+ virtual ~nsStreamConverter();
+ nsresult Close();
+
+ // the input and output streams form a pipe...they need to be passed around together..
+ nsCOMPtr<nsIAsyncOutputStream> mOutputStream; // output stream
+ nsCOMPtr<nsIAsyncInputStream> mInputStream;
+
+ nsCOMPtr<nsIStreamListener> mOutListener; // output stream listener
+ nsCOMPtr<nsIChannel> mOutgoingChannel;
+
+ nsCOMPtr<nsIMimeEmitter> mEmitter; // emitter being used...
+ nsCOMPtr<nsIURI> mURI; // URI being processed
+ nsMimeOutputType mOutputType; // the output type we should use for the operation
+ bool mAlreadyKnowOutputType;
+
+ void *mBridgeStream; // internal libmime data stream
+
+ // Type of output, entire message, header only, body only
+ nsCString mOutputFormat;
+ nsCString mRealContentType; // if we know the content type for real, this will be set (used by attachments)
+
+ nsCString mOverrideFormat; // this is a possible override for emitter creation
+ bool mWrapperOutput; // Should we output the frame split message display
+
+ nsCOMPtr<nsIMimeStreamConverterListener> mMimeStreamConverterListener;
+ bool mForwardInline;
+ bool mForwardInlineFilter;
+ bool mOverrideComposeFormat;
+ nsString mForwardToAddress;
+ nsCOMPtr<nsIMsgIdentity> mIdentity;
+ nsCString mOriginalMsgURI;
+ nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr;
+
+ nsCString mFromType;
+ nsCString mToType;
+#ifdef DEBUG_mscott
+ PRTime mConvertContentTime;
+#endif
+ nsIRequest * mPendingRequest; // used when we need to delay to fire onStartRequest
+ nsISupports * mPendingContext; // used when we need to delay to fire onStartRequest
+};
+
+#endif /* nsStreamConverter_h_ */
diff --git a/mailnews/moz.build b/mailnews/moz.build
new file mode 100644
index 000000000..1d12894c5
--- /dev/null
+++ b/mailnews/moz.build
@@ -0,0 +1,61 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'addrbook',
+ 'base',
+ 'compose',
+ 'db/gloda',
+ 'db/msgdb',
+ 'extensions',
+ 'imap/public',
+ 'imap/src',
+ 'import/public',
+ 'import/src',
+ 'import/text/src',
+ 'import/vcard/src',
+ 'intl',
+ 'jsaccount',
+ 'local/public',
+ 'local/src',
+ 'mime',
+ 'news',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DIRS += [
+ 'import/applemail/src',
+ ]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ DIRS += [ 'import/becky/src' ]
+
+ if CONFIG['MOZ_MAPI_SUPPORT']:
+ DIRS += ['import/outlook/src']
+
+ if not CONFIG['GNU_CC']:
+ DIRS += [
+ 'import/winlivemail',
+ ]
+
+if CONFIG['MOZ_MAPI_SUPPORT']:
+ DIRS += [
+ 'mapi/mapiDLL',
+ 'mapi/mapihook',
+ ]
+
+DIRS += [
+ 'build',
+ 'import/build',
+]
+
+DEFINES['OS_ARCH'] = CONFIG['OS_ARCH']
+DEFINES['MOZ_WIDGET_TOOLKIT'] = CONFIG['MOZ_WIDGET_TOOLKIT']
+
+JAR_MANIFESTS += ['jar.mn']
+
+JS_PREFERENCE_PP_FILES += [
+ 'mailnews.js',
+]
diff --git a/mailnews/news/content/downloadheaders.js b/mailnews/news/content/downloadheaders.js
new file mode 100644
index 000000000..bd856ae0d
--- /dev/null
+++ b/mailnews/news/content/downloadheaders.js
@@ -0,0 +1,85 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+var markreadElement = null;
+var numberElement = null;
+
+var nntpServer = null;
+var args = null;
+
+function OnLoad()
+{
+ let newsBundle = document.getElementById("bundle_news");
+
+ if ("arguments" in window && window.arguments[0]) {
+ args = window.arguments[0]
+ .QueryInterface(Components.interfaces.nsINewsDownloadDialogArgs);
+ /* by default, act like the user hit cancel */
+ args.hitOK = false;
+ /* by default, act like the user did not select download all */
+ args.downloadAll = false;
+
+
+ nntpServer = MailServices.accounts.getIncomingServer(args.serverKey)
+ .QueryInterface(Components.interfaces.nsINntpIncomingServer);
+
+ document.title = newsBundle.getString("downloadHeadersTitlePrefix");
+
+ let infotext = newsBundle.getFormattedString("downloadHeadersInfoText",
+ [args.articleCount]);
+ setText('info', infotext);
+ let okButtonText = newsBundle.getString("okButtonText");
+ let okbutton = document.documentElement.getButton("accept");
+ okbutton.setAttribute("label", okButtonText);
+ okbutton.focus();
+ setText("newsgroupLabel", args.groupName);
+ }
+
+ numberElement = document.getElementById("number");
+ numberElement.value = nntpServer.maxArticles;
+
+ markreadElement = document.getElementById("markread");
+ markreadElement.checked = nntpServer.markOldRead;
+
+ return true;
+}
+
+function setText(id, value) {
+ let element = document.getElementById(id);
+ if (!element)
+ return;
+
+ if (element.hasChildNodes())
+ element.firstChild.remove();
+ let textNode = document.createTextNode(value);
+ element.appendChild(textNode);
+}
+
+function OkButtonCallback() {
+ nntpServer.maxArticles = numberElement.value;
+ nntpServer.markOldRead = markreadElement.checked;
+
+ let radio = document.getElementById("all");
+ if (radio)
+ args.downloadAll = radio.selected;
+
+ args.hitOK = true;
+ return true;
+}
+
+function CancelButtonCallback() {
+ args.hitOK = false;
+ return true;
+}
+
+function setupDownloadUI(enable) {
+ let checkbox = document.getElementById("markread");
+ let numberFld = document.getElementById("number");
+
+ checkbox.disabled = !enable;
+ numberFld.disabled = !enable;
+}
diff --git a/mailnews/news/content/downloadheaders.xul b/mailnews/news/content/downloadheaders.xul
new file mode 100644
index 000000000..1adf7c819
--- /dev/null
+++ b/mailnews/news/content/downloadheaders.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/downloadheaders.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="max-width: 27em;"
+ buttonpack="center"
+ ondialogaccept="return OkButtonCallback();"
+ ondialogcancel="return CancelButtonCallback();"
+ onload="OnLoad();">
+
+ <stringbundle id="bundle_news" src="chrome://messenger/locale/news.properties"/>
+ <script type="application/javascript" src="chrome://messenger/content/downloadheaders.js"/>
+
+ <label class="header" style="width: 25em; max-width: 25em;" id="newsgroupLabel" control="downloadGroup"/>
+ <description style="width: 25em; max-width: 25em;" id="info" control="downloadGroup"/>
+ <separator class="thin"/>
+ <vbox class="indent">
+ <radiogroup id="downloadGroup">
+ <radio id="all" label="&all.label;" accesskey="&all.accesskey;"
+ oncommand="setupDownloadUI(false);"/>
+ <separator class="thin"/>
+ <hbox align="center" valign="middle">
+ <radio id="some" selected="true" label="&download.label;"
+ accesskey="&download.accesskey;"
+ oncommand="setupDownloadUI(true);"
+ aria-labelledby="some number headers"/>
+ <textbox id="number"
+ size="7"
+ type="number"
+ min="1"
+ increment="10"
+ aria-labelledby="some number headers"/>
+ <label value="&headers.label;" accesskey="&headers.accesskey;"
+ id="headers" control="number"/>
+ </hbox>
+ </radiogroup>
+
+ <hbox class="indent" align="start">
+ <checkbox id="markread" label="&mark.label;" accesskey="&mark.accesskey;"/>
+ </hbox>
+ </vbox>
+
+</dialog>
diff --git a/mailnews/news/moz.build b/mailnews/news/moz.build
new file mode 100644
index 000000000..27dcb8746
--- /dev/null
+++ b/mailnews/news/moz.build
@@ -0,0 +1,9 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'public',
+ 'src',
+]
diff --git a/mailnews/news/public/moz.build b/mailnews/news/public/moz.build
new file mode 100644
index 000000000..b1e02bd5b
--- /dev/null
+++ b/mailnews/news/public/moz.build
@@ -0,0 +1,24 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIMsgNewsFolder.idl',
+ 'nsIMsgOfflineNewsState.idl',
+ 'nsINewsDownloadDialogArgs.idl',
+ 'nsINNTPArticleList.idl',
+ 'nsINntpIncomingServer.idl',
+ 'nsINNTPNewsgroupList.idl',
+ 'nsINNTPNewsgroupPost.idl',
+ 'nsINNTPProtocol.idl',
+ 'nsINntpService.idl',
+ 'nsINntpUrl.idl',
+]
+
+XPIDL_MODULE = 'msgnews'
+
+EXPORTS += [
+ 'nsMsgNewsCID.h',
+]
+
diff --git a/mailnews/news/public/nsIMsgNewsFolder.idl b/mailnews/news/public/nsIMsgNewsFolder.idl
new file mode 100644
index 000000000..291fe3bfd
--- /dev/null
+++ b/mailnews/news/public/nsIMsgNewsFolder.idl
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgFolder.idl"
+
+%{C++
+#include "nsTArray.h"
+%}
+
+interface nsIMsgWindow;
+interface nsINntpIncomingServer;
+
+[ref] native nsMsgKeyArrayRef(nsTArray<nsMsgKey>);
+
+[scriptable, uuid(9a12c3a5-9de5-4c57-ace3-d51802b525a9)]
+interface nsIMsgNewsFolder : nsISupports {
+ readonly attribute AString unicodeName;
+ /**|rawName| is an 8-bit string to represent the name of a newsgroup used by
+ * a news server. It's offered for the convenience of callers so that they
+ * don't have to convert |unicodeName| to the server-side name when
+ * communicating with a news server. It's US-ASCII except for some
+ * 'stand-alone' Chinese news servers that use GB2312 for newsgroup names
+ * violating RFC 1036. For those servers, it's GB2312. However, it can be any
+ * other single and multibyte encoding in principle. The encoding of this
+ * string is stored in |nsINntpIncomingServer| because that's a server-wide
+ * property.
+ **/
+ [noscript] readonly attribute ACString rawName;
+ readonly attribute nsINntpIncomingServer nntpServer;
+ attribute boolean saveArticleOffline;
+
+ /**
+ * @name Authentication methods
+ * NNTP authentication is slightly wonky, due to edge cases that are not seen
+ * in other protocols. Authentication is not necessary; if authentication is
+ * used, it could be configured on a per-group basis or even require only a
+ * username and not a password.
+ *
+ * Since passwords could be per-group, it is necessary to refer to passwords
+ * using the methods on this interface and not nsIMsgIncomingServer. Passwords
+ * for the server as a whole are found via the root folder. If the server is
+ * configured to use single sign-on (the default), asking any group for its
+ * password will result in the server's password, otherwise, each group stores
+ * its password individually.
+ *
+ * Due to this setup, most of the password management functions on
+ * nsIMsgIncomingServer do not correctly work. The only one that would affect
+ * the passwords stored on folders correctly is forgetPassword; using any
+ * other on a news server would result in inconsistent state.
+ *
+ * Before requesting either the username or password for authentication, it is
+ * first necessary to call getAuthenticationCredentials. If the method returns
+ * true, then groupUsername and groupPassword are appropriately set up for
+ * necessary authentication; if not, then authentication must be stopped.
+ */
+ /// @{
+
+ /**
+ * Gets the authentication credentials, returning if the results are valid.
+ *
+ * If mustPrompt is true, then the user will always be asked for the
+ * credentials. Otherwise, if mayPrompt is true, then the user will be asked
+ * for credentials if there are no saved credentials. If mayPrompt is false,
+ * then no prompt will be shown, even if there are no saved credentials.
+ *
+ * If this method returns true, then groupUsername and groupPassword will
+ * contain non-empty results that could be used for authentication. If this
+ * method returns false, then the values of groupUsername and groupPassword
+ * will be cleared if they had previously been set. This could happen if
+ * mustPrompt were true and the user decided to cancel the authentication
+ * prompt.
+ *
+ * Note that this method will be executed synchronously; if an async prompt
+ * is wanted, it is the responsibility of the caller to manage it explicitly
+ * with nsIMsgAsyncPrompter.
+ */
+ bool getAuthenticationCredentials(in nsIMsgWindow aMsgWindow,
+ in bool mayPrompt, in bool mustPrompt);
+
+ /// The username that should be used for this group
+ attribute ACString groupUsername;
+
+ /// The password that should be used for this group
+ attribute ACString groupPassword;
+
+ /// Forgets saved authentication credentials permanently.
+ void forgetAuthenticationCredentials();
+ /// @}
+
+ void moveFolder(in nsIMsgFolder aNewsgroupToMove, in nsIMsgFolder aRefNewsgroup, in int32_t aOrientation);
+
+ nsIMsgFolder addNewsgroup(in AUTF8String newsgroupName, in ACString setStr);
+
+ void setReadSetFromStr(in ACString setStr);
+
+ readonly attribute ACString newsrcLine;
+ readonly attribute ACString optionLines;
+ readonly attribute ACString unsubscribedNewsgroupLines;
+ void SetNewsrcHasChanged(in boolean newsrcHasChanged);
+ void updateSummaryFromNNTPInfo(in long oldest, in long youngest, in long total);
+ void removeMessage(in nsMsgKey key);
+ [noscript] void removeMessages(in nsMsgKeyArrayRef aMsgKeys);
+ void cancelComplete();
+ void cancelFailed();
+
+ ACString getMessageIdForKey(in nsMsgKey key);
+
+ void getNextNMessages(in nsIMsgWindow aMsgWindow);
+ void notifyDownloadedLine(in string line, in nsMsgKey key);
+ void notifyFinishedDownloadinghdrs();
+
+ /**
+ * Retrieves the database, but does not cache it in mDatabase.
+ *
+ * This is useful for operations that shouldn't hold open the database.
+ */
+ nsIMsgDatabase getDatabaseWithoutCache();
+
+ /**
+ * Requests that a message be canceled.
+ *
+ * Note that, before sending the news cancel, this method will check to make
+ * sure that the user has proper permission to cancel the message.
+ *
+ * @param aMsgHdr The header of the message to be canceled.
+ * @param aMsgWindow The standard message window object, for error dialogs.
+ */
+ void cancelMessage(in nsIMsgDBHdr aMsgHdr, in nsIMsgWindow aMsgWindow);
+};
diff --git a/mailnews/news/public/nsIMsgOfflineNewsState.idl b/mailnews/news/public/nsIMsgOfflineNewsState.idl
new file mode 100644
index 000000000..7268ad6fd
--- /dev/null
+++ b/mailnews/news/public/nsIMsgOfflineNewsState.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * offline news message state. Interface for old MSG_OfflineNewsArtState
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(921AC210-96B5-11d2-B7EB-00805F05FFA5)]
+interface nsIMsgOfflineNewsState : nsISupports {
+
+ /* outputBuffer is actually
+ * a buffer to dump data into, but we normally pass it NET_Socket_Buffer,
+ * which is constant. The implementation should only allocate a new
+ * buffer if *outputBuffer is NULL.
+ */
+ long Process(out string outputBuffer, in long bufferSize);
+ long Interrupt();
+};
+
diff --git a/mailnews/news/public/nsINNTPArticleList.idl b/mailnews/news/public/nsINNTPArticleList.idl
new file mode 100644
index 000000000..a4ba9a967
--- /dev/null
+++ b/mailnews/news/public/nsINNTPArticleList.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgNewsFolder;
+
+[scriptable, uuid(043d9dd4-b133-4eb4-a1a8-71abff69b613)]
+interface nsINNTPArticleList : nsISupports {
+ void initialize(in nsIMsgNewsFolder newsFolder);
+ void addArticleKey(in nsMsgKey key);
+ void finishAddingArticleKeys();
+};
+
diff --git a/mailnews/news/public/nsINNTPNewsgroupList.idl b/mailnews/news/public/nsINNTPNewsgroupList.idl
new file mode 100644
index 000000000..537293c44
--- /dev/null
+++ b/mailnews/news/public/nsINNTPNewsgroupList.idl
@@ -0,0 +1,93 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgNewsFolder;
+interface nsINntpUrl;
+interface nsIMsgWindow;
+
+/**
+ * A utility class for nsINNTPProtocol that handles the list of new headers.
+ */
+[scriptable, uuid(579aa17b-4c77-465d-8eb6-feaa927cb19c)]
+interface nsINNTPNewsgroupList : nsISupports {
+
+ void initialize(in nsINntpUrl runningURL, in nsIMsgNewsFolder newsFolder);
+
+ long getRangeOfArtsToDownload(in nsIMsgWindow aMsgWindow, in long first_message,
+ in long last_message,
+ in long maxextra,
+ out long real_first_message,
+ out long real_last_message);
+
+ void addToKnownArticles(in long first_message, in long last_message);
+
+ /**
+ * Initializes the internal state to get the messages.
+ *
+ * This method should be called before sending the line
+ * <tt>XOVER @arg first_message-@arg last_message</tt> to the server.
+ *
+ * @param first_message The first message of the download range.
+ * @param last_message The last message of the download range.
+ */
+ void initXOVER(in long first_message, in long last_message);
+ void processXOVERLINE(in string line, out unsigned long status);
+ void resetXOVER();
+ void finishXOVERLINE(in long status, out long newstatus);
+
+ /**
+ * Initalizes the state in preparation for a call to XHDR.
+ *
+ * @return The next header to get, or an empty string if done.
+ */
+ ACString initXHDR();
+ /**
+ * Processes a line of the server's response to XHDR.
+ *
+ * It will calculate the message number and other information itself, so the
+ * unadulterated line itself should be sent.
+ *
+ * @param aLine The line as sent from the server.
+ */
+ void processXHDRLine(in ACString aLine);
+
+ /**
+ * Initalizes the internal state to process a HEAD command.
+ *
+ * This method should be called before sending the line
+ * <tt>HEAD @arg aMessage</tt> to the server.
+ *
+ * @param aMessage The message number that will be sent.
+ */
+ void initHEAD(in long aMessage);
+ /**
+ * Processes a line of the server's response to HEAD.
+ *
+ * This will not check for a quoted '.' at the beginning.
+ *
+ * @param aLine The line the server sent.
+ */
+ void processHEADLine(in ACString aLine);
+ /**
+ * Manages the internal state if the call to HEAD failed.
+ *
+ * @param aMessage The message key that caused the HEAD failure.
+ */
+ void HEADFailed(in long aMessage);
+
+ /**
+ * Calls the filters after all messages have been processed.
+ *
+ * This method also cleans out some internal state relating to the messages
+ * that have been processed, so it should always be called at the end of
+ * XOVER/XHDR/HEAD processing.
+ */
+ void callFilters();
+
+ attribute boolean getOldMessages;
+};
+
diff --git a/mailnews/news/public/nsINNTPNewsgroupPost.idl b/mailnews/news/public/nsINNTPNewsgroupPost.idl
new file mode 100644
index 000000000..382ad5d65
--- /dev/null
+++ b/mailnews/news/public/nsINNTPNewsgroupPost.idl
@@ -0,0 +1,54 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This object represents the stream of data which will be sent to an
+ NNTP server. You basically set up all the RFC850 required headers, etc,
+ then pass it to something that reads off the nsIInputStream interface.
+*/
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(9979a2cb-a4e6-45e6-bfeb-b08e704c5a2b)]
+interface nsINNTPNewsgroupPost : nsISupports {
+
+ /* from RFC850 */
+ /* section 2.1 - required headers */
+ attribute string relayVersion;
+ attribute string postingVersion;
+ attribute string from;
+ attribute string date;
+
+ void AddNewsgroup(in string newsgroupName);
+ readonly attribute string newsgroups;
+
+ attribute string subject;
+ attribute string path;
+
+ /* Secion 2.2 - optional headers */
+ attribute string replyTo;
+ attribute string sender;
+ attribute string followupTo;
+ attribute string dateReceived;
+ attribute string expires;
+
+ readonly attribute string references;
+
+ attribute string control;
+ attribute string distribution;
+ attribute string organization;
+
+ /* the message itself */
+ attribute string body;
+
+ /* is this a control message? */
+ readonly attribute boolean isControl;
+
+ attribute nsIFile postMessageFile;
+};
+
+
+
diff --git a/mailnews/news/public/nsINNTPProtocol.idl b/mailnews/news/public/nsINNTPProtocol.idl
new file mode 100644
index 000000000..f7c9105bb
--- /dev/null
+++ b/mailnews/news/public/nsINNTPProtocol.idl
@@ -0,0 +1,31 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+
+[scriptable, uuid(30106238-0991-11d4-a565-0060b0fc04b7)]
+interface nsINNTPProtocol : nsISupports {
+
+ /////////////////////////////////////////////////////////////////////////
+ // isBusy is true if the connection is currently processing a url
+ // and false otherwise.
+ /////////////////////////////////////////////////////////////////////////
+ attribute boolean isBusy;
+
+ void LoadNewsUrl(in nsIURI aUri, in nsISupports aConsumer);
+ void Initialize(in nsIURI aURL, in nsIMsgWindow aMsgWindow);
+
+ // Get last active time stamp
+ void GetLastActiveTimeStamp(out PRTime aTimeStamp);
+
+ attribute boolean isCachedConnection;
+ readonly attribute nsIMsgFolder currentFolder;
+
+ void CloseConnection();
+};
diff --git a/mailnews/news/public/nsINewsDownloadDialogArgs.idl b/mailnews/news/public/nsINewsDownloadDialogArgs.idl
new file mode 100644
index 000000000..9d728a9bd
--- /dev/null
+++ b/mailnews/news/public/nsINewsDownloadDialogArgs.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(3634327c-392b-4686-adf5-576e6cef9196)]
+interface nsINewsDownloadDialogArgs: nsISupports {
+ attribute AString groupName;
+ attribute long articleCount;
+ attribute string serverKey;
+ attribute boolean hitOK;
+ attribute boolean downloadAll;
+};
+
+%{ C++
+#define DOWNLOAD_HEADERS_URL "chrome://messenger/content/downloadheaders.xul"
+%}
diff --git a/mailnews/news/public/nsINntpIncomingServer.idl b/mailnews/news/public/nsINntpIncomingServer.idl
new file mode 100644
index 000000000..c2ac6e9d7
--- /dev/null
+++ b/mailnews/news/public/nsINntpIncomingServer.idl
@@ -0,0 +1,152 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIMsgNewsFolder;
+interface nsINNTPProtocol;
+interface nsNNTPProtocol;
+interface nsIChannel;
+interface nsIURI;
+interface nsIMsgWindow;
+
+[scriptable, uuid(077620ed-c6c4-4d4d-bed5-4d041f924002)]
+interface nsINntpIncomingServer : nsISupports {
+ /* the on-disk path to the newsrc file for this server */
+ attribute nsIFile newsrcFilePath;
+
+ /* the newsrc root path (the directories all the newsrc files live) */
+ attribute nsIFile newsrcRootPath;
+
+ /* ask the user before downloading more than maxArticles? */
+ attribute boolean notifyOn;
+
+ /* the max articles to download */
+ attribute long maxArticles;
+
+ /* when we don't download all, do we mark the rest read? */
+ attribute boolean markOldRead;
+
+ /* abbreviate the newsgroup names in the folder pane? */
+ attribute boolean abbreviate;
+
+ /* do we use a single login per server or do we login per group */
+ attribute boolean singleSignon;
+
+ /** the server charset and it may be needed to display newsgroup folder
+ * names correctly
+ **/
+ attribute ACString charset;
+
+ /* the server keeps track of all the newsgroups we are subscribed to */
+ void addNewsgroup(in AString name);
+ void removeNewsgroup(in AString name);
+
+ void writeNewsrcFile();
+
+ attribute boolean newsrcHasChanged;
+
+ /**
+ * The maximum number of connections to make to the server.
+ *
+ * This preference (internally max_cached_connections) controls how many
+ * connections we can make. A negative connection count is treated as only
+ * one connection, while 0 (the default) loads the default number of
+ * connections, presently 2.
+ */
+ attribute long maximumConnectionsNumber;
+
+ void displaySubscribedGroup(in nsIMsgNewsFolder msgFolder,
+ in long firstMessage, in long lastMessage,
+ in long totalMessages);
+
+
+ /**
+ * Get a new NNTP channel to run the URI.
+ *
+ * If the server has used up all of its connections, this will place the URI
+ * in the queue to be run when one is freed.
+ *
+ * @param uri The URI to run.
+ * @param window The standard message window object.
+ */
+ nsIChannel getNntpChannel(in nsIURI uri, in nsIMsgWindow window);
+ /**
+ * Enqueues a URI to be run when we have a free connection.
+ *
+ * If there is one already free, it will be immediately started.
+ *
+ * @param uri The URI to run.
+ * @param window The standard message window object.
+ * @param consumer An argument to be passed to nsINNTPProtocol:LoadNewUrl.
+ */
+ void loadNewsUrl(in nsIURI uri, in nsIMsgWindow window,
+ in nsISupports consumer);
+
+ /**
+ * Remove a connection from our connection cache.
+ *
+ * @param aNntpConnection The connection to be removed.
+ */
+ void removeConnection(in nsINNTPProtocol aNntpConnection);
+
+ /**
+ * Load the next URI in the queue to the given connection.
+ *
+ * @param aNntpConnection The newly-freed connection.
+ */
+ [noscript] void prepareForNextUrl(in nsNNTPProtocol aNntpConnection);
+
+ /**
+ * Returns whether or not the server has subscribed to the given newsgroup.
+ *
+ * Note that the name here is intended to be escaped; however, since `%' is
+ * not a legal newsgroup name, it is possibly safe to pass in an unescaped
+ * newsgroup name.
+ */
+ boolean containsNewsgroup(in AUTF8String escapedName);
+
+ void subscribeToNewsgroup(in AUTF8String name);
+
+ /* used for the subscribe dialog.
+ name is encoded in |charset| (attribute declared above) */
+ [noscript] void addNewsgroupToList(in string name);
+
+ attribute boolean supportsExtensions;
+ void addExtension(in string extension);
+ boolean queryExtension(in string extension);
+
+ attribute boolean postingAllowed;
+ attribute boolean pushAuth;
+ attribute unsigned long lastUpdatedTime;
+
+ void addPropertyForGet(in string name, in string value);
+ string queryPropertyForGet(in string name);
+
+ void addSearchableGroup(in AString name);
+ boolean querySearchableGroup(in AString name);
+
+ void addSearchableHeader(in string headerName);
+ boolean querySearchableHeader(in string headerName);
+
+ /**
+ * Returns the folder corresponding to the given group.
+ *
+ * Note that this name is expected to be unescaped.
+ * @note If the group does not exist, a bogus news folder will be returned.
+ * DO NOT call this method unless you are sure that the newsgroup
+ * is subscribed to (e.g., by containsNewsgroup)
+ */
+ nsIMsgNewsFolder findGroup(in AUTF8String name);
+
+ readonly attribute AUTF8String firstGroupNeedingExtraInfo;
+ void setGroupNeedsExtraInfo(in AUTF8String name, in boolean needsExtraInfo);
+
+ void groupNotFound(in nsIMsgWindow window, in AString group,
+ in boolean opening);
+
+ void setPrettyNameForGroup(in AString name, in AString prettyName);
+};
diff --git a/mailnews/news/public/nsINntpService.idl b/mailnews/news/public/nsINntpService.idl
new file mode 100644
index 000000000..0383bf864
--- /dev/null
+++ b/mailnews/news/public/nsINntpService.idl
@@ -0,0 +1,50 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIUrlListener.idl"
+#include "nsINntpIncomingServer.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIURI;
+interface nsIFile;
+interface nsIMsgWindow;
+interface nsIMsgFolder;
+interface nsICacheStorage;
+
+[scriptable, uuid(dc5cadb0-966c-4ef1-a4c8-cc1e48d1ac61)]
+interface nsINntpService : nsISupports {
+
+ /* newsgroupsList is a comma separated list of newsgroups, which may be
+ * in news://host/group or group form
+ * "news://host/group1,news://host/group2" or "group1,group2"
+ *
+ * newsgroupsHeaderVal is a comma separated list of groups in the group form
+ * "group1,group2"
+ *
+ * newshostHeaderVal is a single hostname.
+ * "host"
+ */
+ void generateNewsHeaderValsForPosting(in ACString newsgroupsList, out string newsgroupsHeaderVal, out string newshostHeaderVal);
+
+ nsIURI postMessage(in nsIFile aFileToPost, in string newsgroupNames, in string aAccountKey, in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow);
+
+ nsIURI getNewNews(in nsINntpIncomingServer nntpServer, in string uri, in boolean getOld, in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow);
+
+ nsIURI cancelMessage(in string cancelURL, in string messageURI, in nsISupports aConsumer, in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow);
+
+ void getListOfGroupsOnServer(in nsINntpIncomingServer nntpServer, in nsIMsgWindow aMsgWindow, in boolean getOnlyNew);
+
+ nsIURI fetchMessage(in nsIMsgFolder newsFolder, in nsMsgKey key, in nsIMsgWindow aMsgWindow, in nsISupports aConsumer, in nsIUrlListener aUrlListener);
+
+ void downloadNewsgroupsForOffline(in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
+ /**
+ * can handle news-message:// and news://
+ */
+ void decomposeNewsURI(in string uri, out nsIMsgFolder folder, out nsMsgKey key);
+
+ // handle to the cache session used by news....
+ readonly attribute nsICacheStorage cacheStorage;
+};
diff --git a/mailnews/news/public/nsINntpUrl.idl b/mailnews/news/public/nsINntpUrl.idl
new file mode 100644
index 000000000..d85b37e63
--- /dev/null
+++ b/mailnews/news/public/nsINntpUrl.idl
@@ -0,0 +1,99 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsINNTPNewsgroupPost;
+
+typedef long nsNewsAction;
+
+/**
+ * Represents specific attributes to a URL for news usage.
+ *
+ * Note that the urls represented by this interface can be one of five schemes:
+ * [s]news, nntp[s], or news-message. Any URI that is valid under RFC 5538 will
+ * be accepted. However, it is possible for some queries to be invalid. There
+ * are also a few important things to note:
+ *
+ * - Missing authorities in [s]news: URIs cause nsIMsgMailNewsUrl::server and
+ * nsIMsgMessageUrl::folder to be null.
+ * - nsIMsgMailNewsUrl::server and nsIMsgMessageUrl::folder will be null if the
+ * specified server does not actually exist. In addition, the folder is null
+ * if the group is not currently subscribed on that server.
+ * - Although news-message URIs are parsable, there is no protocol handler
+ * associated with this url. To run these, you should convert these to the
+ * corresponding [s]news or nntp URL, and set the original one in
+ * nsIMsgMessageUrl::uri and ::originalSpec.
+ * - A URI that results in an ActionUnknown will not be run.
+ * - Cancel URIs require the original spec to also be set, so it can find both
+ * the message ID and the group/key combination.
+ * * Some actions require either a group or a message id. Since actions can be
+ * set after the fact, these conditions are not verified.
+ */
+[scriptable, uuid(ef920ca3-9c46-48b8-9fa3-cb430d3681ea)]
+interface nsINntpUrl : nsISupports {
+ /// For ActionPostArticle URIs, the message to be posted.
+ attribute nsINNTPNewsgroupPost messageToPost;
+
+ /**
+ * The action that this URL will take when run.
+ *
+ * Most actions can be automatically determined from the URL spec as follows:
+ *
+ * 1. The query string is searched for the appropriate action.
+ *
+ * 2. A non-empty message ID or key is found (sets ActionFetchArticle).
+ *
+ * 3. A non-empty group is found (ActionGetNewNews or ActionListGroups).
+ */
+ attribute nsNewsAction newsAction;
+
+ /// For ActionGetNewNews URIs, whether or not to get older messages.
+ attribute boolean getOldMessages;
+
+ /**
+ * The group portion of the URI, if one is present.
+ *
+ * This group name is fully unescaped; if you need to construct news URLs with
+ * this value, be sure to escape it first.
+ */
+ readonly attribute ACString group;
+
+ /// The message ID portion of the URI, if one is present
+ readonly attribute ACString messageID;
+
+ /// The message key portion of the URI or nsMsgKey_None if not present
+ readonly attribute nsMsgKey key;
+
+ /// The action of this news URI could not be determined
+ const nsNewsAction ActionUnknown = 0;
+ /// Fetch the contents of an article
+ const nsNewsAction ActionFetchArticle = 1;
+ /// Fetch the part of an article (requires ?part=)
+ const nsNewsAction ActionFetchPart = 2;
+ /// Save the contents of an article to disk
+ const nsNewsAction ActionSaveMessageToDisk = 3;
+ /// Cancel the article (requires ?cancel)
+ const nsNewsAction ActionCancelArticle = 4;
+ /// Post an article
+ const nsNewsAction ActionPostArticle = 5;
+ /// List the non-expired ids in the newsgroup (requires ?list-ids)
+ const nsNewsAction ActionListIds = 6;
+ /// Do an online newsgroup search (requires ?search)
+ const nsNewsAction ActionSearch = 7;
+ /// Retrieve new messages from the server
+ const nsNewsAction ActionGetNewNews = 8;
+ /// List groups for subscribe
+ const nsNewsAction ActionListGroups = 9;
+ /// List new groups for subscribe (requires ?new-groups)
+ const nsNewsAction ActionListNewGroups = 10;
+
+ /// Constant for the default NNTP over ssl port number
+ const int32_t DEFAULT_NNTP_PORT = 119;
+
+ /// Constant for the default NNTP over ssl port number
+ const int32_t DEFAULT_NNTPS_PORT = 563;
+};
diff --git a/mailnews/news/public/nsMsgNewsCID.h b/mailnews/news/public/nsMsgNewsCID.h
new file mode 100644
index 000000000..e56a93bb7
--- /dev/null
+++ b/mailnews/news/public/nsMsgNewsCID.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgNewsCID_h__
+#define nsMsgNewsCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+#include "nsMsgBaseCID.h"
+
+//
+// nsMsgNewsFolder
+#define NS_NEWSFOLDERRESOURCE_CONTRACTID \
+ NS_RDF_RESOURCE_FACTORY_CONTRACTID_PREFIX "news"
+#define NS_NEWSFOLDERRESOURCE_CID \
+{ /* 4ace448a-f6d4-11d2-880d-004005263078 */ \
+ 0x4ace448a, 0xf6d4, 0x11d2, \
+ {0x88, 0x0d, 0x00, 0x40, 0x05, 0x26, 0x30, 0x78} \
+}
+
+//
+// nsNntpIncomingServer
+//
+#define NS_NNTPINCOMINGSERVER_CONTRACTID \
+ NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX "nntp"
+
+#define NS_NNTPINCOMINGSERVER_CID \
+{ /* 6ff28d0a-f776-11d2-87ca-004005263078 */ \
+ 0x6ff28d0a, 0xf776, 0x11d2, \
+ {0x87, 0xca, 0x00, 0x40, 0x05, 0x26, 0x30, 0x78} \
+}
+
+//
+// nsNntpService
+//
+
+#define NS_NNTPPROTOCOLINFO_CONTRACTID \
+ NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX "nntp"
+
+#define NS_NEWSPROTOCOLHANDLER_CONTRACTID \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "news"
+#define NS_SNEWSPROTOCOLHANDLER_CONTRACTID \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "snews"
+#define NS_NNTPPROTOCOLHANDLER_CONTRACTID \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "nntp"
+#define NS_NEWSMESSAGESERVICE_CONTRACTID \
+ "@mozilla.org/messenger/messageservice;1?type=news-message"
+#define NS_NNTPMESSAGESERVICE_CONTRACTID \
+ "@mozilla.org/messenger/messageservice;1?type=news"
+#define NS_NNTPSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/nntpservice;1"
+#define NS_NEWSSTARTUPHANDLER_CONTRACTID \
+ "@mozilla.org/commandlinehandler/general-startup;1?type=news"
+
+#define NS_NNTPSERVICE_CID \
+{ /* 4C9F90E1-E19B-11d2-806E-006008128C4E */ \
+ 0x4c9f90e1, 0xe19b, 0x11d2, \
+ {0x80, 0x6e, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e} \
+}
+
+//
+// nsNNTPNewsgroupPost
+//
+#define NS_NNTPNEWSGROUPPOST_CONTRACTID \
+ "@mozilla.org/messenger/nntpnewsgrouppost;1"
+#define NS_NNTPNEWSGROUPPOST_CID \
+{ /* 30c60228-187e-11d3-842f-004005263078 */ \
+ 0x30c60228, 0x187e, 0x11d3, \
+ {0x84, 0x2f, 0x00, 0x40, 0x05, 0x26, 0x30, 0x78} \
+}
+
+//
+// nsNNTPNewsgroupList
+//
+#define NS_NNTPNEWSGROUPLIST_CONTRACTID \
+ "@mozilla.org/messenger/nntpnewsgrouplist;1"
+#define NS_NNTPNEWSGROUPLIST_CID \
+{ /* 631e9054-1893-11d3-9916-004005263078 */ \
+ 0x631e9054, 0x1893, 0x11d3, \
+ {0x99, 0x16, 0x00, 0x40, 0x05, 0x26, 0x30, 0x78} \
+}
+
+//
+// nsNNTPArticleList
+//
+#define NS_NNTPARTICLELIST_CONTRACTID \
+ "@mozilla.org/messenger/nntparticlelist;1"
+#define NS_NNTPARTICLELIST_CID \
+{ /* 9f12bdf0-189f-11d3-973e-00805f916fd3 */ \
+ 0x9f12bdf0, 0x189f, 0x11d3, \
+ {0x97, 0x3e, 0x00, 0x80, 0x5f, 0x91, 0x6f, 0xd3} \
+}
+
+//
+// nsNntpUrl
+//
+#define NS_NNTPURL_CONTRACTID \
+ "@mozilla.org/messenger/nntpurl;1"
+#define NS_NNTPURL_CID \
+{ /* 196B4B30-E18C-11d2-806E-006008128C4E */ \
+ 0x196b4b30, 0xe18c, 0x11d2, \
+ { 0x80, 0x6e, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e } }
+
+//
+// nsNewsDownloadDialogArgs
+//
+#define NS_NEWSDOWNLOADDIALOGARGS_CONTRACTID \
+ "@mozilla.org/messenger/newsdownloaddialogargs;1"
+#define NS_NEWSDOWNLOADDIALOGARGS_CID \
+{ /* 1540689e-1dd2-11b2-933d-f0d1e460ef4a */ \
+ 0x1540689e, 0x1dd2, 0x11b2, \
+ { 0x93, 0x3d, 0xf0, 0xd1, 0xe4, 0x60, 0xef, 0x4a} }
+
+#endif // nsMsgNewsCID_h__
diff --git a/mailnews/news/src/moz.build b/mailnews/news/src/moz.build
new file mode 100644
index 000000000..c2f18c389
--- /dev/null
+++ b/mailnews/news/src/moz.build
@@ -0,0 +1,27 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsNewsDownloadDialogArgs.cpp',
+ 'nsNewsDownloader.cpp',
+ 'nsNewsFolder.cpp',
+ 'nsNewsUtils.cpp',
+ 'nsNNTPArticleList.cpp',
+ 'nsNntpIncomingServer.cpp',
+ 'nsNntpMockChannel.cpp',
+ 'nsNNTPNewsgroupList.cpp',
+ 'nsNNTPNewsgroupPost.cpp',
+ 'nsNNTPProtocol.cpp',
+ 'nsNntpService.cpp',
+ 'nsNntpUrl.cpp',
+]
+
+EXTRA_COMPONENTS += [
+ 'nsNewsAutoCompleteSearch.js',
+ 'nsNewsAutoCompleteSearch.manifest',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/news/src/nntpCore.h b/mailnews/news/src/nntpCore.h
new file mode 100644
index 000000000..e09f72f2c
--- /dev/null
+++ b/mailnews/news/src/nntpCore.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _NNTPCore_H__
+#define _NNTPCore_H__
+
+#define NEWS_MSGS_URL "chrome://messenger/locale/news.properties"
+
+// The following string constants are protocol strings. I'm defining them as macros here
+// so I don't have to sprinkle all of the strings throughout the protocol.
+#define NNTP_CMD_LIST_EXTENSIONS "LIST EXTENSIONS" CRLF
+#define NNTP_CMD_MODE_READER "MODE READER" CRLF
+#define NNTP_CMD_LIST_SEARCHES "LIST SEARCHES" CRLF
+#define NNTP_CMD_LIST_SEARCH_FIELDS "LIST SRCHFIELDS" CRLF
+#define NNTP_CMD_GET_PROPERTIES "GET" CRLF
+#define NNTP_CMD_LIST_SUBSCRIPTIONS "LIST SUBSCRIPTIONS" CRLF
+#define NNTP_CMD_POST "POST" CRLF
+#define NNTP_CMD_QUIT "QUIT" CRLF
+
+// end of protocol strings
+
+#define MK_NNTP_RESPONSE_HELP 100
+
+#define MK_NNTP_RESPONSE_POSTING_ALLOWED 200
+#define MK_NNTP_RESPONSE_POSTING_DENIED 201
+
+#define MK_NNTP_RESPONSE_DISCONTINUED 400
+
+#define MK_NNTP_RESPONSE_COMMAND_UNKNOWN 500
+#define MK_NNTP_RESPONSE_SYNTAX_ERROR 501
+#define MK_NNTP_RESPONSE_PERMISSION_DENIED 502
+#define MK_NNTP_RESPONSE_SERVER_ERROR 503
+
+#define MK_NNTP_RESPONSE_ARTICLE_BOTH 220
+#define MK_NNTP_RESPONSE_ARTICLE_HEAD 221
+#define MK_NNTP_RESPONSE_ARTICLE_BODY 222
+#define MK_NNTP_RESPONSE_ARTICLE_NONE 223
+#define MK_NNTP_RESPONSE_ARTICLE_NO_GROUP 412
+#define MK_NNTP_RESPONSE_ARTICLE_NO_CURRENT 420
+#define MK_NNTP_RESPONSE_ARTICLE_NONEXIST 423
+#define MK_NNTP_RESPONSE_ARTICLE_NOTFOUND 430
+
+#define MK_NNTP_RESPONSE_GROUP_SELECTED 211
+#define MK_NNTP_RESPONSE_GROUP_NO_GROUP 411
+
+#define MK_NNTP_RESPONSE_IHAVE_OK 235
+#define MK_NNTP_RESPONSE_IHAVE_ARTICLE 335
+#define MK_NNTP_RESPONSE_IHAVE_NOT_WANTED 435
+#define MK_NNTP_RESPONSE_IHAVE_FAILED 436
+#define MK_NNTP_RESPONSE_IHAVE_REJECTED 437
+
+#define MK_NNTP_RESPONSE_LAST_OK 223
+#define MK_NNTP_RESPONSE_LAST_NO_GROUP 412
+#define MK_NNTP_RESPONSE_LAST_NO_CURRENT 420
+#define MK_NNTP_RESPONSE_LAST_NO_ARTICLE 422
+
+#define MK_NNTP_RESPONSE_LIST_OK 215
+
+#define MK_NNTP_RESPONSE_NEWGROUPS_OK 231
+
+#define MK_NNTP_RESPONSE_NEWNEWS_OK 230
+
+#define MK_NNTP_RESPONSE_NEXT_OK 223
+#define MK_NNTP_RESPONSE_NEXT_NO_GROUP 412
+#define MK_NNTP_RESPONSE_NEXT_NO_CURRENT 420
+#define MK_NNTP_RESPONSE_NEXT_NO_ARTICLE 421
+
+#define MK_NNTP_RESPONSE_POST_OK 240
+#define MK_NNTP_RESPONSE_POST_SEND_NOW 340
+#define MK_NNTP_RESPONSE_POST_DENIED 440
+#define MK_NNTP_RESPONSE_POST_FAILED 441
+
+#define MK_NNTP_RESPONSE_QUIT_OK 205
+
+#define MK_NNTP_RESPONSE_SLAVE_OK 202
+
+#define MK_NNTP_RESPONSE_CHECK_NO_ARTICLE 238
+#define MK_NNTP_RESPONSE_CHECK_NO_ACCEPT 400
+#define MK_NNTP_RESPONSE_CHECK_LATER 431
+#define MK_NNTP_RESPONSE_CHECK_DONT_SEND 438
+#define MK_NNTP_RESPONSE_CHECK_DENIED 480
+#define MK_NNTP_RESPONSE_CHECK_ERROR 500
+
+#define MK_NNTP_RESPONSE_XHDR_OK 221
+#define MK_NNTP_RESPONSE_XHDR_NO_GROUP 412
+#define MK_NNTP_RESPONSE_XHDR_NO_CURRENT 420
+#define MK_NNTP_RESPONSE_XHDR_NO_ARTICLE 430
+#define MK_NNTP_RESPONSE_XHDR_DENIED 502
+
+#define MK_NNTP_RESPONSE_XOVER_OK 224
+#define MK_NNTP_RESPONSE_XOVER_NO_GROUP 412
+#define MK_NNTP_RESPONSE_XOVER_NO_CURRENT 420
+#define MK_NNTP_RESPONSE_XOVER_DENIED 502
+
+#define MK_NNTP_RESPONSE_XPAT_OK 221
+#define MK_NNTP_RESPONSE_XPAT_NO_ARTICLE 430
+#define MK_NNTP_RESPONSE_XPAT_DENIED 502
+
+#define MK_NNTP_RESPONSE_AUTHINFO_OK 281
+#define MK_NNTP_RESPONSE_AUTHINFO_CONT 381
+#define MK_NNTP_RESPONSE_AUTHINFO_REQUIRE 480
+#define MK_NNTP_RESPONSE_AUTHINFO_REJECT 482
+#define MK_NNTP_RESPONSE_AUTHINFO_DENIED 502
+
+#define MK_NNTP_RESPONSE_
+
+#define MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_OK 250
+#define MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_CONT 350
+#define MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_REQUIRE 450
+#define MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_REJECT 452
+
+#define MK_NNTP_RESPONSE_TYPE_INFO 1
+#define MK_NNTP_RESPONSE_TYPE_OK 2
+#define MK_NNTP_RESPONSE_TYPE_CONT 3
+#define MK_NNTP_RESPONSE_TYPE_CANNOT 4
+#define MK_NNTP_RESPONSE_TYPE_ERROR 5
+
+#define MK_NNTP_RESPONSE_TYPE(x) (x/100)
+
+// the following used to be defined in allxpstr.h. Until we find a new values for these,
+// I'm defining them here because I don't want to link against xplib.lib...(mscott)
+
+#define MK_DATA_LOADED 1
+#define MK_EMPTY_NEWS_LIST -227
+#define MK_INTERRUPTED -201
+#define MK_MALFORMED_URL_ERROR -209
+#define MK_NEWS_ERROR_FMT -430
+#define MK_NNTP_CANCEL_CONFIRM -426
+#define MK_NNTP_CANCEL_DISALLOWED -427
+#define MK_NNTP_NOT_CANCELLED -429
+#define MK_OUT_OF_MEMORY -207
+#define XP_CONFIRM_SAVE_NEWSGROUPS -1
+#define XP_HTML_ARTICLE_EXPIRED -1
+#define XP_HTML_NEWS_ERROR -1
+#define XP_PROGRESS_READ_NEWSGROUPINFO 1
+#define XP_PROGRESS_RECEIVE_ARTICLE 1
+#define XP_PROGRESS_RECEIVE_LISTARTICLES 1
+#define XP_PROGRESS_RECEIVE_NEWSGROUP 1
+#define XP_PROGRESS_SORT_ARTICLES 1
+#define XP_PROGRESS_READ_NEWSGROUP_COUNTS 1
+#define XP_THERMO_PERCENT_FORM 1
+#define XP_PROMPT_ENTER_USERNAME 1
+#define MK_NNTP_AUTH_FAILED -260
+#define MK_NNTP_ERROR_MESSAGE -304
+#define MK_NNTP_NEWSGROUP_SCAN_ERROR -305
+#define MK_NNTP_SERVER_ERROR -217
+#define MK_NNTP_SERVER_NOT_CONFIGURED -307
+#define MK_TCP_READ_ERROR -252
+#define MK_TCP_WRITE_ERROR -236
+#define MK_NNTP_CANCEL_ERROR -428
+#define XP_CONNECT_NEWS_HOST_CONTACTED_WAITING_FOR_REPLY 1
+#define XP_PLEASE_ENTER_A_PASSWORD_FOR_NEWS_SERVER_ACCESS 1
+#define XP_GARBAGE_COLLECTING 1
+#define XP_MESSAGE_SENT_WAITING_NEWS_REPLY 1
+#define MK_MSG_DELIV_NEWS 1
+#define MK_MSG_COLLABRA_DISABLED 1
+#define MK_MSG_EXPIRE_NEWS_ARTICLES 1
+#define MK_MSG_HTML_IMAP_NO_CACHED_BODY 1
+#define MK_MSG_CANT_MOVE_FOLDER 1
+
+#endif /* NNTPCore_H__ */
diff --git a/mailnews/news/src/nsNNTPArticleList.cpp b/mailnews/news/src/nsNNTPArticleList.cpp
new file mode 100644
index 000000000..106f5439f
--- /dev/null
+++ b/mailnews/news/src/nsNNTPArticleList.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+
+#include "nsCOMPtr.h"
+#include "nsNNTPArticleList.h"
+#include "nsIMsgFolder.h"
+#include "nsAutoPtr.h"
+#include "nsMsgKeyArray.h"
+
+NS_IMPL_ISUPPORTS(nsNNTPArticleList, nsINNTPArticleList)
+
+nsNNTPArticleList::nsNNTPArticleList()
+{
+}
+
+nsNNTPArticleList::~nsNNTPArticleList()
+{
+ if (m_newsDB) {
+ m_newsDB->Commit(nsMsgDBCommitType::kSessionCommit);
+ m_newsDB->Close(true);
+ m_newsDB = nullptr;
+ }
+
+ m_newsFolder = nullptr;
+}
+
+NS_IMETHODIMP
+nsNNTPArticleList::Initialize(nsIMsgNewsFolder *newsFolder)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(newsFolder);
+
+ m_dbIndex = 0;
+
+ m_newsFolder = newsFolder;
+
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = folder->GetMsgDatabase(getter_AddRefs(m_newsDB));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!m_newsDB) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ rv = m_newsDB->ListAllKeys(keys);
+ NS_ENSURE_SUCCESS(rv,rv);
+ keys->Sort();
+ m_idsInDB.AppendElements(keys->m_keys);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPArticleList::AddArticleKey(nsMsgKey key)
+{
+#ifdef DEBUG
+ m_idsOnServer.AppendElement(key);
+#endif
+
+ if (m_dbIndex < m_idsInDB.Length())
+ {
+ nsMsgKey idInDBToCheck = m_idsInDB[m_dbIndex];
+ // if there are keys in the database that aren't in the newsgroup
+ // on the server, remove them. We probably shouldn't do this if
+ // we have a copy of the article offline.
+ // We'll add to m_idsDeleted for now and remove the id later
+ while (idInDBToCheck < key)
+ {
+ m_idsDeleted.AppendElement(idInDBToCheck);
+ if (m_dbIndex >= m_idsInDB.Length())
+ break;
+ idInDBToCheck = m_idsInDB[++m_dbIndex];
+ }
+ if (idInDBToCheck == key)
+ m_dbIndex++;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPArticleList::FinishAddingArticleKeys()
+{
+ // if the last n messages in the group are cancelled, they won't have gotten removed
+ // so we have to go and remove them now.
+ if (m_dbIndex < m_idsInDB.Length())
+ m_idsDeleted.AppendElements(&m_idsInDB[m_dbIndex],
+ m_idsInDB.Length() - m_dbIndex);
+
+ if (m_idsDeleted.Length())
+ m_newsFolder->RemoveMessages(m_idsDeleted);
+
+#ifdef DEBUG
+ // make sure none of the deleted turned up on the idsOnServer list
+ for (uint32_t i = 0; i < m_idsDeleted.Length(); i++) {
+ NS_ASSERTION(!m_idsOnServer.Contains((nsMsgKey)m_idsDeleted[i]),
+ "a deleted turned up on the idsOnServer list");
+ }
+#endif
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNNTPArticleList.h b/mailnews/news/src/nsNNTPArticleList.h
new file mode 100644
index 000000000..5cbfc47df
--- /dev/null
+++ b/mailnews/news/src/nsNNTPArticleList.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNNTPArticleList_h___
+#define nsNNTPArticleList_h___
+
+#include "nsCOMPtr.h"
+#include "nsINNTPArticleList.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIMsgDatabase.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+
+class nsNNTPArticleList : public nsINNTPArticleList
+{
+public:
+ nsNNTPArticleList();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINNTPARTICLELIST
+
+protected:
+ virtual ~nsNNTPArticleList();
+
+ nsTArray<nsMsgKey> m_idsInDB;
+
+#ifdef DEBUG
+ nsTArray<nsMsgKey> m_idsOnServer;
+#endif
+ nsTArray<nsMsgKey> m_idsDeleted;
+
+ nsCOMPtr <nsIMsgNewsFolder> m_newsFolder;
+ nsCOMPtr <nsIMsgDatabase> m_newsDB;
+
+ uint32_t m_dbIndex;
+};
+
+#endif /* nsNNTPArticleList_h___ */
diff --git a/mailnews/news/src/nsNNTPNewsgroupList.cpp b/mailnews/news/src/nsNNTPNewsgroupList.cpp
new file mode 100644
index 000000000..3833390c7
--- /dev/null
+++ b/mailnews/news/src/nsNNTPNewsgroupList.cpp
@@ -0,0 +1,1332 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * formerly listngst.cpp
+ * This class should ultimately be part of a news group listing
+ * state machine - either by inheritance or delegation.
+ * Currently, a folder pane owns one and libnet news group listing
+ * related messages get passed to this object.
+ */
+
+#include "msgCore.h" // precompiled header...
+#include "MailNewsTypes.h"
+#include "nsCOMPtr.h"
+#include "nsIDBFolderInfo.h"
+#include "nsINewsDatabase.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsINntpIncomingServer.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgFilter.h"
+#include "nsNNTPNewsgroupList.h"
+
+#include "nsINNTPArticleList.h"
+#include "nsMsgKeySet.h"
+
+#include "nntpCore.h"
+#include "nsIStringBundle.h"
+
+#include "plstr.h"
+#include "prmem.h"
+#include "prprf.h"
+
+#include "nsMsgUtils.h"
+
+#include "nsMsgDatabase.h"
+
+#include "nsIDBFolderInfo.h"
+
+#include "nsNewsUtils.h"
+
+#include "nsMsgDBCID.h"
+
+#include "nsINewsDownloadDialogArgs.h"
+
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIMsgWindow.h"
+#include "nsIDocShell.h"
+#include "nsIMutableArray.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMsgFilterCustomAction.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+
+// update status on header download once per second
+#define MIN_STATUS_UPDATE_INTERVAL PRTime(PR_USEC_PER_SEC)
+
+
+nsNNTPNewsgroupList::nsNNTPNewsgroupList()
+ : m_finishingXover(false),
+ m_getOldMessages(false),
+ m_promptedAlready(false),
+ m_downloadAll(false),
+ m_maxArticles(0),
+ m_lastPercent(-1),
+ m_lastStatusUpdate(0),
+ m_lastProcessedNumber(0),
+ m_firstMsgNumber(0),
+ m_lastMsgNumber(0),
+ m_firstMsgToDownload(0),
+ m_lastMsgToDownload(0),
+ m_set(nullptr)
+{
+ memset(&m_knownArts, 0, sizeof(m_knownArts));
+}
+
+nsNNTPNewsgroupList::~nsNNTPNewsgroupList()
+{
+ CleanUp();
+}
+
+NS_IMPL_ISUPPORTS(nsNNTPNewsgroupList, nsINNTPNewsgroupList, nsIMsgFilterHitNotify)
+
+nsresult
+nsNNTPNewsgroupList::Initialize(nsINntpUrl *runningURL, nsIMsgNewsFolder *newsFolder)
+{
+ m_newsFolder = newsFolder;
+ m_runningURL = runningURL;
+ m_knownArts.set = nsMsgKeySet::Create();
+
+ nsresult rv = m_newsFolder->GetDatabaseWithoutCache(getter_AddRefs(m_newsDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = folder->GetFilterList(m_msgWindow, getter_AddRefs(m_filterList));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCString ngHeaders;
+ m_filterList->GetArbitraryHeaders(ngHeaders);
+ ParseString(ngHeaders, ' ', m_filterHeaders);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = server->GetFilterList(m_msgWindow, getter_AddRefs(m_serverFilterList));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsAutoCString servHeaders;
+ m_serverFilterList->GetArbitraryHeaders(servHeaders);
+
+ nsTArray<nsCString> servArray;
+ ParseString(servHeaders, ' ', servArray);
+
+ // servArray may have duplicates already in m_filterHeaders.
+ for (uint32_t i = 0; i < servArray.Length(); i++)
+ {
+ if (!m_filterHeaders.Contains(servArray[i]))
+ m_filterHeaders.AppendElement(servArray[i]);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::CleanUp()
+{
+ // here we make sure that there aren't missing articles in the unread set
+ // So if an article is the unread set, and the known arts set, but isn't in the
+ // db, then we should mark it read in the unread set.
+ if (m_newsDB)
+ {
+ if (m_knownArts.set && m_knownArts.set->getLength() && m_set->getLength())
+ {
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ m_newsDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ int32_t firstKnown = m_knownArts.set->GetFirstMember();
+ int32_t lastKnown = m_knownArts.set->GetLastMember();
+ if (folderInfo)
+ {
+ uint32_t lastMissingCheck;
+ folderInfo->GetUint32Property("lastMissingCheck", 0, &lastMissingCheck);
+ if (lastMissingCheck)
+ firstKnown = lastMissingCheck + 1;
+ }
+ bool foundMissingArticle = false;
+ while (firstKnown <= lastKnown)
+ {
+ int32_t firstUnreadStart, firstUnreadEnd;
+ if (firstKnown == 0)
+ firstKnown = 1;
+ m_set->FirstMissingRange(firstKnown, lastKnown, &firstUnreadStart, &firstUnreadEnd);
+ if (firstUnreadStart)
+ {
+ while (firstUnreadStart <= firstUnreadEnd)
+ {
+ bool containsKey;
+ m_newsDB->ContainsKey(firstUnreadStart, &containsKey);
+ if (!containsKey)
+ {
+ m_set->Add(firstUnreadStart);
+ foundMissingArticle = true;
+ }
+ firstUnreadStart++;
+ }
+ firstKnown = firstUnreadStart;
+ }
+ else
+ break;
+
+ }
+ if (folderInfo)
+ folderInfo->SetUint32Property("lastMissingCheck", lastKnown);
+
+ if (foundMissingArticle)
+ {
+ nsresult rv;
+ nsCOMPtr<nsINewsDatabase> db(do_QueryInterface(m_newsDB, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+ db->SetReadSet(m_set);
+ }
+ }
+ m_newsDB->Commit(nsMsgDBCommitType::kSessionCommit);
+ m_newsDB->Close(true);
+ m_newsDB = nullptr;
+ }
+
+ if (m_knownArts.set)
+ {
+ delete m_knownArts.set;
+ m_knownArts.set = nullptr;
+ }
+ if (m_newsFolder)
+ m_newsFolder->NotifyFinishedDownloadinghdrs();
+
+ m_newsFolder = nullptr;
+ m_runningURL = nullptr;
+
+ return NS_OK;
+}
+
+#ifdef HAVE_CHANGELISTENER
+void nsNNTPNewsgroupList::OnAnnouncerGoingAway (ChangeAnnouncer *instigator)
+{
+}
+#endif
+
+static nsresult
+openWindow(nsIMsgWindow *aMsgWindow, const char *chromeURL,
+ nsINewsDownloadDialogArgs *param)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+ nsCOMPtr<nsIDocShell> docShell;
+ rv = aMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow(do_GetInterface(docShell));
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow = nsPIDOMWindowOuter::From(domWindow);
+ parentWindow = parentWindow->GetOuterWindow();
+ NS_ENSURE_ARG_POINTER(parentWindow);
+
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ifptr->SetData(param);
+ ifptr->SetDataIID(&NS_GET_IID(nsINewsDownloadDialogArgs));
+
+ nsCOMPtr<nsPIDOMWindowOuter> dialogWindow;
+ rv = parentWindow->OpenDialog(NS_ConvertASCIItoUTF16(chromeURL),
+ NS_LITERAL_STRING("_blank"),
+ NS_LITERAL_STRING("centerscreen,chrome,modal,titlebar"),
+ ifptr, getter_AddRefs(dialogWindow));
+
+ return rv;
+}
+
+nsresult
+nsNNTPNewsgroupList::GetRangeOfArtsToDownload(nsIMsgWindow *aMsgWindow,
+ int32_t first_possible,
+ int32_t last_possible,
+ int32_t maxextra,
+ int32_t *first,
+ int32_t *last,
+ int32_t *status)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG_POINTER(first);
+ NS_ENSURE_ARG_POINTER(last);
+ NS_ENSURE_ARG_POINTER(status);
+ *first = 0;
+ *last = 0;
+
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_msgWindow = aMsgWindow;
+
+ nsCOMPtr<nsINewsDatabase> db(do_QueryInterface(m_newsDB, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = db->GetReadSet(&m_set);
+ if (NS_FAILED(rv) || !m_set)
+ return rv;
+
+ m_set->SetLastMember(last_possible); // make sure highwater mark is valid.
+
+ nsCOMPtr <nsIDBFolderInfo> newsGroupInfo;
+ rv = m_newsDB->GetDBFolderInfo(getter_AddRefs(newsGroupInfo));
+ if (NS_SUCCEEDED(rv) && newsGroupInfo) {
+ nsCString knownArtsString;
+ nsMsgKey mark;
+ newsGroupInfo->GetKnownArtsSet(getter_Copies(knownArtsString));
+
+ rv = newsGroupInfo->GetHighWater(&mark);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (last_possible < ((int32_t)mark))
+ newsGroupInfo->SetHighWater(last_possible);
+ if (m_knownArts.set)
+ delete m_knownArts.set;
+ m_knownArts.set = nsMsgKeySet::Create(knownArtsString.get());
+ }
+ else
+ {
+ if (m_knownArts.set)
+ delete m_knownArts.set;
+ m_knownArts.set = nsMsgKeySet::Create();
+ nsMsgKey low, high;
+ rv = m_newsDB->GetLowWaterArticleNum(&low);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = m_newsDB->GetHighWaterArticleNum(&high);
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_knownArts.set->AddRange(low,high);
+ }
+
+ if (m_knownArts.set->IsMember(last_possible)) {
+ nsString statusString;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = bundle->GetStringFromName(u"noNewMessages", getter_Copies(statusString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetProgressStatus(statusString.get());
+ }
+
+ if (maxextra <= 0 || last_possible < first_possible || last_possible < 1)
+ {
+ *status=0;
+ return NS_OK;
+ }
+
+ m_knownArts.first_possible = first_possible;
+ m_knownArts.last_possible = last_possible;
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ /* Determine if we only want to get just new articles or more messages.
+ If there are new articles at the end we haven't seen, we always want to get those first.
+ Otherwise, we get the newest articles we haven't gotten, if we're getting more.
+ My thought for now is that opening a newsgroup should only try to get new articles.
+ Selecting "More Messages" will first try to get unseen messages, then old messages. */
+
+ if (m_getOldMessages || !m_knownArts.set->IsMember(last_possible))
+ {
+ bool notifyMaxExceededOn = true;
+ rv = nntpServer->GetNotifyOn(&notifyMaxExceededOn);
+ if (NS_FAILED(rv)) notifyMaxExceededOn = true;
+
+ // if the preference to notify when downloading more than x headers is not on,
+ // and we're downloading new headers, set maxextra to a very large number.
+ if (!m_getOldMessages && !notifyMaxExceededOn)
+ maxextra = 0x7FFFFFFFL;
+ int result =
+ m_knownArts.set->LastMissingRange(first_possible, last_possible,
+ first, last);
+ if (result < 0) {
+ *status=result;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ if (*first > 0 && *last - *first >= maxextra)
+ {
+ if (!m_getOldMessages && !m_promptedAlready && notifyMaxExceededOn)
+ {
+ m_downloadAll = false;
+ nsCOMPtr<nsINewsDownloadDialogArgs> args = do_CreateInstance("@mozilla.org/messenger/newsdownloaddialogargs;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = args->SetArticleCount(*last - *first + 1);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString groupName;
+ rv = m_newsFolder->GetUnicodeName(groupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = args->SetGroupName(groupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // get the server key
+ nsCString serverKey;
+ rv = server->GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = args->SetServerKey(serverKey.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // we many not have a msgWindow if we are running an autosubscribe url from the browser
+ // and there isn't a 3 pane open.
+ //
+ // if we don't have one, bad things will happen when we fail to open up the "download headers dialog"
+ // (we will subscribe to the newsgroup, but it will appear like there are no messages!)
+ //
+ // for now, act like the "download headers dialog" came up, and the user hit cancel. (very safe)
+ //
+ // TODO, figure out why we aren't opening and using a 3 pane when the autosubscribe url is run.
+ // perhaps we can find an available 3 pane, and use it.
+
+ bool download = false;
+
+ if (aMsgWindow) {
+ rv = openWindow(aMsgWindow, DOWNLOAD_HEADERS_URL, args);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = args->GetHitOK(&download);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (download) {
+ rv = args->GetDownloadAll(&m_downloadAll);
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_maxArticles = 0;
+ rv = nntpServer->GetMaxArticles(&m_maxArticles);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ maxextra = m_maxArticles;
+ if (!m_downloadAll)
+ {
+ bool markOldRead = false;
+
+ rv = nntpServer->GetMarkOldRead(&markOldRead);
+ if (NS_FAILED(rv)) markOldRead = false;
+
+ if (markOldRead && m_set)
+ m_set->AddRange(*first, *last - maxextra);
+ *first = *last - maxextra + 1;
+ }
+ }
+ else
+ *first = *last = 0;
+ m_promptedAlready = true;
+ }
+ else if (m_promptedAlready && !m_downloadAll)
+ *first = *last - m_maxArticles + 1;
+ else if (!m_downloadAll)
+ *first = *last - maxextra + 1;
+ }
+ }
+
+ m_firstMsgToDownload = *first;
+ m_lastMsgToDownload = *last;
+ *status=0;
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::AddToKnownArticles(int32_t first, int32_t last)
+{
+ nsresult status;
+
+ if (!m_knownArts.set)
+ {
+ m_knownArts.set = nsMsgKeySet::Create();
+ if (!m_knownArts.set)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // XXX Casting int to nsresult
+ status = static_cast<nsresult>(m_knownArts.set->AddRange(first, last));
+
+ if (m_newsDB) {
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIDBFolderInfo> newsGroupInfo;
+ rv = m_newsDB->GetDBFolderInfo(getter_AddRefs(newsGroupInfo));
+ if (NS_SUCCEEDED(rv) && newsGroupInfo) {
+ nsCString output;
+ status = m_knownArts.set->Output(getter_Copies(output));
+ if (!output.IsEmpty())
+ newsGroupInfo->SetKnownArtsSet(output.get());
+ }
+ }
+ return status;
+}
+
+nsresult
+nsNNTPNewsgroupList::InitXOVER(int32_t first_msg, int32_t last_msg)
+{
+ /* Consistency checks, not that I know what to do if it fails (it will
+ probably handle it OK...) */
+ NS_ASSERTION(first_msg <= last_msg, "first > last");
+
+ /* If any XOVER lines from the last time failed to come in, mark those
+ messages as read. */
+ if (m_lastProcessedNumber < m_lastMsgNumber)
+ {
+ m_set->AddRange(m_lastProcessedNumber + 1, m_lastMsgNumber);
+ }
+ m_firstMsgNumber = first_msg;
+ m_lastMsgNumber = last_msg;
+ m_lastProcessedNumber = first_msg > 1 ? first_msg - 1 : 1;
+ m_currentXHDRIndex = -1;
+ return NS_OK;
+}
+
+// from RFC 822, don't translate
+#define FROM_HEADER "From: "
+#define SUBJECT_HEADER "Subject: "
+#define DATE_HEADER "Date: "
+
+nsresult
+nsNNTPNewsgroupList::ParseLine(char *line, uint32_t * message_number)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgDBHdr> newMsgHdr;
+
+ if (!line || !message_number) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ char *next = line;
+
+#define GET_TOKEN() \
+ line = next; \
+ next = (line ? PL_strchr (line, '\t') : 0); \
+ if (next) *next++ = 0
+
+ GET_TOKEN (); /* message number */
+ *message_number = atol(line);
+
+ if (atol(line) == 0) /* bogus xover data */
+ return NS_ERROR_UNEXPECTED;
+
+ m_newsDB->CreateNewHdr(*message_number, getter_AddRefs(newMsgHdr));
+
+ NS_ASSERTION(newMsgHdr, "CreateNewHdr didn't fail, but it returned a null newMsgHdr");
+ if (!newMsgHdr)
+ return NS_ERROR_NULL_POINTER;
+
+ GET_TOKEN (); /* subject */
+ if (line) {
+ const char *subject = line; /* #### const evilness */
+
+ uint32_t flags = 0;
+ // ### should call IsHeaderRead here...
+ /* strip "Re: " */
+ nsCString modifiedSubject;
+ if (NS_MsgStripRE(nsDependentCString(subject), modifiedSubject))
+ (void) newMsgHdr->OrFlags(nsMsgMessageFlags::HasRe, &flags);
+
+ // this will make sure read flags agree with newsrc
+ if (! (flags & nsMsgMessageFlags::Read))
+ rv = newMsgHdr->OrFlags(nsMsgMessageFlags::New, &flags);
+
+ rv = newMsgHdr->SetSubject(modifiedSubject.IsEmpty() ? subject : modifiedSubject.get());
+
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ GET_TOKEN (); /* author */
+ if (line) {
+ rv = newMsgHdr->SetAuthor(line);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ GET_TOKEN ();
+ if (line) {
+ PRTime date;
+ PRStatus status = PR_ParseTimeString (line, false, &date);
+ if (PR_SUCCESS == status) {
+ rv = newMsgHdr->SetDate(date); /* date */
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ GET_TOKEN (); /* message id */
+ if (line) {
+ char *strippedId = line;
+ if (strippedId[0] == '<')
+ strippedId++;
+ char * lastChar = strippedId + PL_strlen(strippedId) -1;
+
+ if (*lastChar == '>')
+ *lastChar = '\0';
+
+ rv = newMsgHdr->SetMessageId(strippedId);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ GET_TOKEN (); /* references */
+ if (line) {
+ rv = newMsgHdr->SetReferences(line);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ GET_TOKEN (); /* bytes */
+ if (line) {
+ uint32_t msgSize = 0;
+ msgSize = (line) ? atol (line) : 0;
+
+ rv = newMsgHdr->SetMessageSize(msgSize);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ GET_TOKEN (); /* lines */
+ if (line) {
+ uint32_t numLines = 0;
+ numLines = line ? atol (line) : 0;
+ rv = newMsgHdr->SetLineCount(numLines);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ GET_TOKEN (); /* xref */
+
+ m_newHeaders.AppendObject(newMsgHdr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPNewsgroupList::ApplyFilterHit(nsIMsgFilter *aFilter, nsIMsgWindow *aMsgWindow, bool *aApplyMore)
+{
+ NS_ENSURE_ARG_POINTER(aFilter);
+ NS_ENSURE_ARG_POINTER(aApplyMore);
+ NS_ENSURE_TRUE(m_newMsgHdr, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(m_newsDB, NS_ERROR_UNEXPECTED);
+
+ // you can't move news messages, so applyMore is always true
+ *aApplyMore = true;
+
+ nsCOMPtr<nsIArray> filterActionList;
+
+ nsresult rv = aFilter->GetSortedActionList(getter_AddRefs(filterActionList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numActions;
+ rv = filterActionList->GetLength(&numActions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool loggingEnabled = false;
+ nsCOMPtr<nsIMsgFilterList> currentFilterList;
+ rv = aFilter->GetFilterList(getter_AddRefs(currentFilterList));
+ if (NS_SUCCEEDED(rv) && currentFilterList && numActions)
+ currentFilterList->GetLoggingEnabled(&loggingEnabled);
+
+ for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++)
+ {
+ nsCOMPtr<nsIMsgRuleAction> filterAction;
+ rv = filterActionList->QueryElementAt(actionIndex, NS_GET_IID(nsIMsgRuleAction),
+ getter_AddRefs(filterAction));
+ if (NS_FAILED(rv) || !filterAction)
+ continue;
+
+ nsMsgRuleActionType actionType;
+ if (NS_SUCCEEDED(filterAction->GetType(&actionType)))
+ {
+ if (loggingEnabled)
+ (void) aFilter->LogRuleHit(filterAction, m_newMsgHdr);
+
+ switch (actionType)
+ {
+ case nsMsgFilterAction::Delete:
+ m_addHdrToDB = false;
+ break;
+ case nsMsgFilterAction::MarkRead:
+ m_newsDB->MarkHdrRead(m_newMsgHdr, true, nullptr);
+ break;
+ case nsMsgFilterAction::MarkUnread:
+ m_newsDB->MarkHdrRead(m_newMsgHdr, false, nullptr);
+ break;
+ case nsMsgFilterAction::KillThread:
+ m_newMsgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored);
+ break;
+ case nsMsgFilterAction::KillSubthread:
+ {
+ uint32_t newFlags;
+ m_newMsgHdr->OrFlags(nsMsgMessageFlags::Ignored, &newFlags);
+ }
+ break;
+ case nsMsgFilterAction::WatchThread:
+ {
+ uint32_t newFlags;
+ m_newMsgHdr->OrFlags(nsMsgMessageFlags::Watched, &newFlags);
+ }
+ break;
+ case nsMsgFilterAction::MarkFlagged:
+ m_newMsgHdr->MarkFlagged(true);
+ break;
+ case nsMsgFilterAction::ChangePriority:
+ {
+ nsMsgPriorityValue filterPriority;
+ filterAction->GetPriority(&filterPriority);
+ m_newMsgHdr->SetPriority(filterPriority);
+ }
+ break;
+ case nsMsgFilterAction::AddTag:
+ {
+ nsCString keyword;
+ filterAction->GetStrValue(keyword);
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ messageArray->AppendElement(m_newMsgHdr, false);
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ if (folder)
+ folder->AddKeywordsToMessages(messageArray, keyword);
+ break;
+ }
+ case nsMsgFilterAction::Label:
+ {
+ nsMsgLabelValue filterLabel;
+ filterAction->GetLabel(&filterLabel);
+ nsMsgKey msgKey;
+ m_newMsgHdr->GetMessageKey(&msgKey);
+ m_newsDB->SetLabel(msgKey, filterLabel);
+ }
+ break;
+
+ case nsMsgFilterAction::StopExecution:
+ {
+ // don't apply any more filters
+ *aApplyMore = false;
+ }
+ break;
+
+ case nsMsgFilterAction::Custom:
+ {
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString value;
+ filterAction->GetStrValue(value);
+
+ nsCOMPtr<nsIMutableArray> messageArray(
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(messageArray, rv);
+ messageArray->AppendElement(m_newMsgHdr, false);
+
+ customAction->Apply(messageArray, value, nullptr,
+ nsMsgFilterType::NewsRule, aMsgWindow);
+ }
+ break;
+
+ default:
+ NS_ERROR("unexpected action");
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::ProcessXOVERLINE(const char *line, uint32_t *status)
+{
+ uint32_t message_number=0;
+ // int32_t lines;
+ nsresult rv = NS_OK;
+
+ NS_ASSERTION(line, "null ptr");
+ if (!line)
+ return NS_ERROR_NULL_POINTER;
+
+ if (m_newsDB)
+ {
+ char *xoverline = PL_strdup(line);
+ if (!xoverline)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = ParseLine(xoverline, &message_number);
+ PL_strfree(xoverline);
+ xoverline = nullptr;
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NS_ASSERTION(message_number > m_lastProcessedNumber ||
+ message_number == 1, "bad message_number");
+ if (m_set && message_number > m_lastProcessedNumber + 1)
+ {
+ /* There are some articles that XOVER skipped; they must no longer
+ exist. Mark them as read in the newsrc, so we don't include them
+ next time in our estimated number of unread messages. */
+ if (m_set->AddRange(m_lastProcessedNumber + 1, message_number - 1))
+ {
+ /* This isn't really an important enough change to warrant causing
+ the newsrc file to be saved; we haven't gathered any information
+ that won't also be gathered for free next time. */
+ }
+ }
+
+ m_lastProcessedNumber = message_number;
+ if (m_knownArts.set)
+ {
+ int result = m_knownArts.set->Add(message_number);
+ if (result < 0) {
+ if (status)
+ *status = result;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ }
+
+ if (message_number > m_lastMsgNumber)
+ m_lastMsgNumber = message_number;
+ else if (message_number < m_firstMsgNumber)
+ m_firstMsgNumber = message_number;
+
+ if (m_set) {
+ (void) m_set->IsMember(message_number);
+ }
+
+ /* Update the progress meter with a percentage of articles retrieved */
+ if (m_lastMsgNumber > m_firstMsgNumber)
+ {
+ int32_t totalToDownload = m_lastMsgToDownload - m_firstMsgToDownload + 1;
+ int32_t lastIndex = m_lastProcessedNumber - m_firstMsgNumber + 1;
+ int32_t numDownloaded = lastIndex;
+ int32_t totIndex = m_lastMsgNumber - m_firstMsgNumber + 1;
+
+ PRTime elapsedTime = PR_Now() - m_lastStatusUpdate;
+
+ if (elapsedTime > MIN_STATUS_UPDATE_INTERVAL || lastIndex == totIndex)
+ UpdateStatus(false, numDownloaded, totalToDownload);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::ResetXOVER()
+{
+ m_lastMsgNumber = m_firstMsgNumber;
+ m_lastProcessedNumber = m_lastMsgNumber;
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::FinishXOVERLINE(int status, int *newstatus)
+{
+ nsresult rv;
+ struct MSG_NewsKnown* k;
+
+ /* If any XOVER lines from the last time failed to come in, mark those
+ messages as read. */
+
+ if (status >= 0 && m_lastProcessedNumber < m_lastMsgNumber) {
+ m_set->AddRange(m_lastProcessedNumber + 1, m_lastMsgNumber);
+ }
+
+ if (m_lastProcessedNumber)
+ AddToKnownArticles(m_firstMsgNumber, m_lastProcessedNumber);
+
+ k = &m_knownArts;
+
+ if (k && k->set)
+ {
+ int32_t n = k->set->FirstNonMember();
+ if (n < k->first_possible || n > k->last_possible)
+ {
+ /* We know we've gotten all there is to know.
+ Take advantage of that to update our counts... */
+ // ### dmb
+ }
+ }
+
+ if (!m_finishingXover)
+ {
+ // turn on m_finishingXover - this is a horrible hack to avoid recursive
+ // calls which happen when the fe selects a message as a result of getting EndingUpdate,
+ // which interrupts this url right before it was going to finish and causes FinishXOver
+ // to get called again.
+ m_finishingXover = true;
+
+ // XXX is this correct?
+ m_runningURL = nullptr;
+
+ if (m_lastMsgNumber > 0) {
+ nsAutoString firstStr;
+ firstStr.AppendInt(m_lastProcessedNumber - m_firstMsgNumber + 1);
+
+ nsAutoString lastStr;
+ lastStr.AppendInt(m_lastMsgNumber - m_firstMsgNumber + 1);
+
+ nsString statusString;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char16_t *formatStrings[2] = { firstStr.get(), lastStr.get() };
+ rv = bundle->FormatStringFromName(u"downloadingArticles", formatStrings, 2, getter_Copies(statusString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetProgressStatus(statusString.get());
+ }
+ }
+
+ if (newstatus)
+ *newstatus=0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPNewsgroupList::InitXHDR(nsACString &header)
+{
+ if (++m_currentXHDRIndex >= m_filterHeaders.Length())
+ header.Truncate();
+ else
+ header.Assign(m_filterHeaders[m_currentXHDRIndex]);
+ // Don't include these in our XHDR bouts, as they are already provided through
+ // XOVER.
+ if (header.EqualsLiteral("message-id") ||
+ header.EqualsLiteral("references"))
+ return InitXHDR(header);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPNewsgroupList::ProcessXHDRLine(const nsACString &line)
+{
+ int32_t middle = line.FindChar(' ');
+ nsCString value, key = PromiseFlatCString(line);
+ if (middle == -1)
+ return NS_OK;
+ value = Substring(line, middle+1);
+ key.SetLength((uint32_t)middle);
+
+ // According to RFC 2980, some will send (none) instead.
+ // So we don't treat this is an error.
+ if (key.CharAt(0) < '0' || key.CharAt(0) > '9')
+ return NS_OK;
+
+ nsresult rv;
+ int32_t number = key.ToInteger(&rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ // RFC 2980 specifies one or more spaces.
+ value.Trim(" ");
+
+ nsCOMPtr<nsIMsgDBHdr> header;
+ rv = m_newsDB->GetMsgHdrForKey(number, getter_AddRefs(header));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = header->SetStringProperty(m_filterHeaders[m_currentXHDRIndex].get(), value.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t totalToDownload = m_lastMsgToDownload - m_firstMsgToDownload + 1;
+ int32_t numDownloaded = number - m_firstMsgNumber + 1;
+
+ PRTime elapsedTime = PR_Now() - m_lastStatusUpdate;
+
+ if (elapsedTime > MIN_STATUS_UPDATE_INTERVAL)
+ UpdateStatus(true, numDownloaded, totalToDownload);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNNTPNewsgroupList::InitHEAD(int32_t number)
+{
+ if (m_newMsgHdr)
+ {
+ // Finish processing for this header
+ // If HEAD didn't properly return, then the header won't be set
+ m_newHeaders.AppendObject(m_newMsgHdr);
+
+ int32_t totalToDownload = m_lastMsgToDownload - m_firstMsgToDownload + 1;
+ int32_t lastIndex = m_lastProcessedNumber - m_firstMsgNumber + 1;
+ int32_t numDownloaded = lastIndex;
+ int32_t totIndex = m_lastMsgNumber - m_firstMsgNumber + 1;
+
+ PRTime elapsedTime = PR_Now() - m_lastStatusUpdate;
+
+ if (elapsedTime > MIN_STATUS_UPDATE_INTERVAL || lastIndex == totIndex)
+ UpdateStatus(false, numDownloaded, totalToDownload);
+ }
+
+ if (number >= 0)
+ {
+ if (m_newHeaders.Count() > 0 && m_lastMsgNumber == m_lastProcessedNumber)
+ {
+ // We have done some processing of messages. This means that we have
+ // relics of headers from XOVER. Since we will get everything from HEAD
+ // anyways, just clear the array.
+ m_newHeaders.Clear();
+ }
+
+ nsresult rv = m_newsDB->CreateNewHdr(number, getter_AddRefs(m_newMsgHdr));
+ m_lastProcessedNumber = number;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ AddToKnownArticles(m_firstMsgNumber, m_lastProcessedNumber);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPNewsgroupList::HEADFailed(int32_t number)
+{
+ m_set->Add(number);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPNewsgroupList::ProcessHEADLine(const nsACString &line)
+{
+ int32_t colon = line.FindChar(':');
+ nsCString header = PromiseFlatCString(line), value;
+ if (colon != -1)
+ {
+ value = Substring(line, colon+1);
+ header.SetLength((uint32_t)colon);
+ }
+ else if (line.CharAt(0) == ' ' || line.CharAt(0) == '\t') // We are continuing the header
+ {
+ m_thisLine += header; // Preserve whitespace (should we?)
+ return NS_OK;
+ }
+ else
+ {
+ return NS_OK; // We are malformed. Just ignore and hope for the best...
+ }
+
+ nsresult rv;
+ if (!m_lastHeader.IsEmpty())
+ {
+ rv = AddHeader(m_lastHeader.get(), m_thisLine.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ value.Trim(" ");
+
+ ToLowerCase(header, m_lastHeader);
+ m_thisLine.Assign(value);
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::AddHeader(const char *header, const char *value)
+{
+ nsresult rv = NS_OK;
+ // The From, Date, and Subject headers have special requirements.
+ if (PL_strcmp(header, "from") == 0)
+ {
+ rv = m_newMsgHdr->SetAuthor(value);
+ }
+ else if (PL_strcmp(header, "date") == 0)
+ {
+ PRTime date;
+ PRStatus status = PR_ParseTimeString (value, false, &date);
+ if (PR_SUCCESS == status)
+ rv = m_newMsgHdr->SetDate(date);
+ }
+ else if (PL_strcmp(header, "subject") == 0)
+ {
+ const char *subject = value;
+
+ uint32_t flags = 0;
+ // ### should call IsHeaderRead here...
+ /* strip "Re: " */
+ nsCString modifiedSubject;
+ if (NS_MsgStripRE(nsDependentCString(subject), modifiedSubject))
+ // this will make sure read flags agree with newsrc
+ (void) m_newMsgHdr->OrFlags(nsMsgMessageFlags::HasRe, &flags);
+
+ if (! (flags & nsMsgMessageFlags::Read))
+ rv = m_newMsgHdr->OrFlags(nsMsgMessageFlags::New, &flags);
+
+ rv = m_newMsgHdr->SetSubject(modifiedSubject.IsEmpty() ? subject :
+ modifiedSubject.get());
+ }
+ else if (PL_strcmp(header, "message-id") == 0)
+ {
+ rv = m_newMsgHdr->SetMessageId(value);
+ }
+ else if (PL_strcmp(header, "references") == 0)
+ {
+ rv = m_newMsgHdr->SetReferences(value);
+ }
+ else if (PL_strcmp(header, "bytes") == 0)
+ {
+ rv = m_newMsgHdr->SetMessageSize(atol(value));
+ }
+ else if (PL_strcmp(header, "lines") == 0)
+ {
+ rv = m_newMsgHdr->SetLineCount(atol(value));
+ }
+ else if (m_filterHeaders.Contains(nsDependentCString(header)))
+ {
+ rv = m_newMsgHdr->SetStringProperty(header, value);
+ }
+ return rv;
+}
+
+nsresult
+nsNNTPNewsgroupList::CallFilters()
+{
+ nsresult rv;
+ nsCString filterString;
+
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t filterCount = 0;
+ if (m_filterList)
+ {
+ rv = m_filterList->GetFilterCount(&filterCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ uint32_t serverFilterCount = 0;
+ if (m_serverFilterList)
+ {
+ rv = m_serverFilterList->GetFilterCount(&serverFilterCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ uint32_t count = m_newHeaders.Count();
+
+ // Notify MsgFolderListeners of message adds
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ m_newMsgHdr = m_newHeaders[i];
+ if (!filterCount && !serverFilterCount)
+ {
+ m_newsDB->AddNewHdrToDB(m_newMsgHdr, true);
+
+ if (notifier)
+ notifier->NotifyMsgAdded(m_newMsgHdr);
+ // mark the header as not yet reported classified
+ nsMsgKey msgKey;
+ m_newMsgHdr->GetMessageKey(&msgKey);
+ folder->OrProcessingFlags(msgKey,
+ nsMsgProcessingFlags::NotReportedClassified);
+
+ continue;
+ }
+ m_addHdrToDB = true;
+
+ // build up a "headers" for filter code
+ nsCString subject, author, date;
+ rv = m_newMsgHdr->GetSubject(getter_Copies(subject));
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = m_newMsgHdr->GetAuthor(getter_Copies(author));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString fullHeaders;
+ if (!(author.IsEmpty()))
+ {
+ fullHeaders.AppendLiteral(FROM_HEADER);
+ fullHeaders += author;
+ fullHeaders += '\0';
+ }
+
+ if (!(subject.IsEmpty()))
+ {
+ fullHeaders.AppendLiteral(SUBJECT_HEADER);
+ fullHeaders += subject;
+ fullHeaders += '\0';
+ }
+
+ for (uint32_t header = 0; header < m_filterHeaders.Length(); header++)
+ {
+ nsCString retValue;
+ m_newMsgHdr->GetStringProperty(m_filterHeaders[header].get(),
+ getter_Copies(retValue));
+ if (!retValue.IsEmpty())
+ {
+ fullHeaders += m_filterHeaders[header];
+ fullHeaders.AppendLiteral(": ");
+ fullHeaders += retValue;
+ fullHeaders += '\0';
+ }
+ }
+
+ // The per-newsgroup filters should go first. If something stops filter
+ // execution, then users should be able to override the global filters in
+ // the per-newsgroup filters.
+ if (filterCount)
+ {
+ rv = m_filterList->ApplyFiltersToHdr(nsMsgFilterType::NewsRule,
+ m_newMsgHdr, folder, m_newsDB, fullHeaders.get(),
+ fullHeaders.Length(), this, m_msgWindow);
+ }
+ if (serverFilterCount)
+ {
+ rv = m_serverFilterList->ApplyFiltersToHdr(nsMsgFilterType::NewsRule,
+ m_newMsgHdr, folder, m_newsDB, fullHeaders.get(),
+ fullHeaders.Length(), this, m_msgWindow);
+ }
+
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (m_addHdrToDB)
+ {
+ m_newsDB->AddNewHdrToDB(m_newMsgHdr, true);
+ if (notifier)
+ notifier->NotifyMsgAdded(m_newMsgHdr);
+ // mark the header as not yet reported classified
+ nsMsgKey msgKey;
+ m_newMsgHdr->GetMessageKey(&msgKey);
+ folder->OrProcessingFlags(msgKey,
+ nsMsgProcessingFlags::NotReportedClassified);
+ }
+ }
+ m_newHeaders.Clear();
+ return NS_OK;
+}
+
+void
+nsNNTPNewsgroupList::SetProgressBarPercent(int32_t percent)
+{
+ if (!m_runningURL)
+ return;
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
+ if (mailnewsUrl) {
+ nsCOMPtr <nsIMsgStatusFeedback> feedback;
+ mailnewsUrl->GetStatusFeedback(getter_AddRefs(feedback));
+
+ if (feedback) {
+ feedback->ShowProgress(percent);
+ }
+ }
+}
+
+void
+nsNNTPNewsgroupList::SetProgressStatus(const char16_t *aMessage)
+{
+ if (!m_runningURL)
+ return;
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
+ if (mailnewsUrl) {
+ nsCOMPtr <nsIMsgStatusFeedback> feedback;
+ mailnewsUrl->GetStatusFeedback(getter_AddRefs(feedback));
+
+ if (feedback) {
+ // prepending the account name to the status message.
+ nsresult rv;
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = mailnewsUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ nsString accountName;
+ server->GetPrettyName(accountName);
+ nsString statusMessage;
+ nsCOMPtr<nsIStringBundleService> sbs =
+ mozilla::services::GetStringBundleService();
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = sbs->CreateBundle(MSGS_URL,
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ const char16_t *params[] = { accountName.get(), aMessage };
+ bundle->FormatStringFromName(u"statusMessage",
+ params, 2, getter_Copies(statusMessage));
+
+ feedback->ShowStatusString(statusMessage);
+ }
+ }
+}
+
+void
+nsNNTPNewsgroupList::UpdateStatus(bool filtering, int32_t numDLed, int32_t totToDL)
+{
+ int32_t numerator = (filtering ? m_currentXHDRIndex + 1 : 1) * numDLed;
+ int32_t denominator = (m_filterHeaders.Length() + 1) * totToDL;
+ int32_t percent = numerator * 100 / denominator;
+
+ nsAutoString numDownloadedStr;
+ numDownloadedStr.AppendInt(numDLed);
+
+ nsAutoString totalToDownloadStr;
+ totalToDownloadStr.AppendInt(totToDL);
+
+ nsAutoString newsgroupName;
+ nsresult rv = m_newsFolder->GetUnicodeName(newsgroupName);
+ if (!NS_SUCCEEDED(rv))
+ return;
+
+ nsString statusString;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService)
+ return;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ if (!NS_SUCCEEDED(rv))
+ return;
+
+ if (filtering)
+ {
+ NS_ConvertUTF8toUTF16 header(m_filterHeaders[m_currentXHDRIndex]);
+ const char16_t *formatStrings[4] = { header.get(),
+ numDownloadedStr.get(), totalToDownloadStr.get(), newsgroupName.get() };
+ rv = bundle->FormatStringFromName(u"newNewsgroupFilteringHeaders",
+ formatStrings, 4, getter_Copies(statusString));
+ }
+ else
+ {
+ const char16_t *formatStrings[3] = { numDownloadedStr.get(),
+ totalToDownloadStr.get(), newsgroupName.get() };
+ rv = bundle->FormatStringFromName(u"newNewsgroupHeaders",
+ formatStrings, 3, getter_Copies(statusString));
+ }
+ if (!NS_SUCCEEDED(rv))
+ return;
+
+ SetProgressStatus(statusString.get());
+ m_lastStatusUpdate = PR_Now();
+
+ // only update the progress meter if it has changed
+ if (percent != m_lastPercent)
+ {
+ SetProgressBarPercent(percent);
+ m_lastPercent = percent;
+ }
+}
+
+NS_IMETHODIMP nsNNTPNewsgroupList::SetGetOldMessages(bool aGetOldMessages)
+{
+ m_getOldMessages = aGetOldMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPNewsgroupList::GetGetOldMessages(bool *aGetOldMessages)
+{
+ NS_ENSURE_ARG(aGetOldMessages);
+
+ *aGetOldMessages = m_getOldMessages;
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNNTPNewsgroupList.h b/mailnews/news/src/nsNNTPNewsgroupList.h
new file mode 100644
index 000000000..6b21be32b
--- /dev/null
+++ b/mailnews/news/src/nsNNTPNewsgroupList.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+ * formerly listngst.h
+ * This class should ultimately be part of a news group listing
+ * state machine - either by inheritance or delegation.
+ * Currently, a folder pane owns one and libnet news group listing
+ * related messages get passed to this object.
+ */
+#ifndef nsNNTPNewsgroupListState_h___
+#define nsNNTPNewsgroupListState_h___
+
+#include "nsINNTPNewsgroupList.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgKeySet.h"
+#include "nsINntpUrl.h"
+#include "nsIMsgFilterList.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgWindow.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+
+/* The below is all stuff that we remember for netlib about which
+ articles we've already seen in the current newsgroup. */
+
+typedef struct MSG_NewsKnown {
+ nsMsgKeySet* set; /* Set of articles we've already gotten
+ from the newsserver (if it's marked
+ "read", then we've already gotten it).
+ If an article is marked "read", it
+ doesn't mean we're actually displaying
+ it; it may be an article that no longer
+ exists, or it may be one that we've
+ marked read and we're only viewing
+ unread messages. */
+
+ int32_t first_possible; /* The oldest article in this group. */
+ int32_t last_possible; /* The newest article in this group. */
+
+ bool shouldGetOldest;
+} MSG_NewsKnown;
+
+// This class should ultimately be part of a news group listing
+// state machine - either by inheritance or delegation.
+// Currently, a folder pane owns one and libnet news group listing
+// related messages get passed to this object.
+class nsNNTPNewsgroupList : public nsINNTPNewsgroupList, public nsIMsgFilterHitNotify
+#ifdef HAVE_CHANGELISTENER
+/* ,public ChangeListener */
+#endif
+{
+public:
+ nsNNTPNewsgroupList();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINNTPNEWSGROUPLIST
+ NS_DECL_NSIMSGFILTERHITNOTIFY
+
+private:
+ virtual ~nsNNTPNewsgroupList();
+
+ NS_METHOD CleanUp();
+
+ bool m_finishingXover;
+
+#ifdef HAVE_CHANGELISTENER
+ virtual void OnAnnouncerGoingAway (ChangeAnnouncer *instigator);
+#endif
+ nsresult ParseLine(char *line, uint32_t *message_number);
+ nsresult GetDatabase(const char *uri, nsIMsgDatabase **db);
+ void SetProgressBarPercent(int32_t percent);
+ void SetProgressStatus(const char16_t *aMessage);
+
+ void UpdateStatus(bool filtering, int32_t numDled, int32_t totToDL);
+
+ nsresult AddHeader(const char * header, const char * value);
+protected:
+ bool m_getOldMessages;
+ bool m_promptedAlready;
+ bool m_downloadAll;
+ int32_t m_maxArticles;
+ int32_t m_lastPercent;
+ PRTime m_lastStatusUpdate;
+
+ nsCOMPtr <nsIMsgNewsFolder> m_newsFolder;
+ nsCOMPtr <nsIMsgDatabase> m_newsDB;
+ nsCOMPtr <nsINntpUrl> m_runningURL;
+
+ /**
+ * The last message that we have processed (XOVER or HEAD).
+ */
+ nsMsgKey m_lastProcessedNumber;
+ /**
+ * The endpoints of the message chunk we are actually downloading.
+ */
+ nsMsgKey m_firstMsgNumber, m_lastMsgNumber;
+ /**
+ * The endpoints of the message chunk we are capable of downloading.
+ */
+ int32_t m_firstMsgToDownload, m_lastMsgToDownload;
+
+ struct MSG_NewsKnown m_knownArts;
+ nsMsgKeySet *m_set;
+
+ nsTArray<nsCString> m_filterHeaders;
+ uint32_t m_currentXHDRIndex;
+ nsCString m_lastHeader;
+ nsCString m_thisLine;
+
+private:
+ nsCOMPtr <nsIMsgWindow> m_msgWindow;
+ nsCOMPtr <nsIMsgFilterList> m_filterList;
+ nsCOMPtr <nsIMsgFilterList> m_serverFilterList;
+ nsCOMPtr <nsIMsgDBHdr> m_newMsgHdr; // current message header we're building
+ nsCOMArray<nsIMsgDBHdr> m_newHeaders;
+
+ bool m_addHdrToDB;
+
+};
+
+#endif /* nsNNTPNewsgroupListState_h___ */
+
diff --git a/mailnews/news/src/nsNNTPNewsgroupPost.cpp b/mailnews/news/src/nsNNTPNewsgroupPost.cpp
new file mode 100644
index 000000000..03c67c455
--- /dev/null
+++ b/mailnews/news/src/nsNNTPNewsgroupPost.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nsNNTPNewsgroupPost.h"
+
+NS_IMPL_ISUPPORTS(nsNNTPNewsgroupPost, nsINNTPNewsgroupPost)
+
+nsNNTPNewsgroupPost::nsNNTPNewsgroupPost()
+{
+ m_isControl=false;
+}
+
+nsNNTPNewsgroupPost::~nsNNTPNewsgroupPost()
+{
+}
+
+#define IMPL_GETSET(attribute, member) \
+ NS_IMETHODIMP nsNNTPNewsgroupPost::Get##attribute(char **result) \
+ { \
+ NS_ENSURE_ARG_POINTER(result); \
+ *result = ToNewCString(member); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP nsNNTPNewsgroupPost::Set##attribute(const char *aValue) \
+ { \
+ member.Assign(aValue); \
+ return NS_OK; \
+ }
+
+IMPL_GETSET(RelayVersion, m_header[IDX_HEADER_RELAYVERSION])
+IMPL_GETSET(PostingVersion, m_header[IDX_HEADER_POSTINGVERSION])
+IMPL_GETSET(From, m_header[IDX_HEADER_FROM])
+IMPL_GETSET(Date, m_header[IDX_HEADER_DATE])
+IMPL_GETSET(Subject, m_header[IDX_HEADER_SUBJECT])
+IMPL_GETSET(Path, m_header[IDX_HEADER_PATH])
+IMPL_GETSET(ReplyTo, m_header[IDX_HEADER_REPLYTO])
+IMPL_GETSET(Sender, m_header[IDX_HEADER_SENDER])
+IMPL_GETSET(FollowupTo, m_header[IDX_HEADER_FOLLOWUPTO])
+IMPL_GETSET(DateReceived, m_header[IDX_HEADER_DATERECEIVED])
+IMPL_GETSET(Expires, m_header[IDX_HEADER_EXPIRES])
+IMPL_GETSET(Control, m_header[IDX_HEADER_CONTROL])
+IMPL_GETSET(Distribution, m_header[IDX_HEADER_DISTRIBUTION])
+IMPL_GETSET(Organization, m_header[IDX_HEADER_ORGANIZATION])
+IMPL_GETSET(Body, m_body)
+
+NS_IMETHODIMP nsNNTPNewsgroupPost::GetNewsgroups(char **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = ToNewCString(m_header[IDX_HEADER_NEWSGROUPS]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPNewsgroupPost::GetReferences(char **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = ToNewCString(m_header[IDX_HEADER_REFERENCES]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPNewsgroupPost::GetIsControl(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_isControl;
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupPost::AddNewsgroup(const char *newsgroup)
+{
+ m_header[IDX_HEADER_NEWSGROUPS].AppendLiteral(", ");
+ m_header[IDX_HEADER_NEWSGROUPS].Append(newsgroup);
+ return NS_OK;
+}
+
+
+// the message can be stored in a file....allow accessors for getting and setting
+// the file name to post...
+nsresult
+nsNNTPNewsgroupPost::SetPostMessageFile(nsIFile * aPostMessageFile)
+{
+ m_postMessageFile = aPostMessageFile;
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupPost::GetPostMessageFile(nsIFile ** aPostMessageFile)
+{
+ if (aPostMessageFile)
+ NS_IF_ADDREF(*aPostMessageFile = m_postMessageFile);
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNNTPNewsgroupPost.h b/mailnews/news/src/nsNNTPNewsgroupPost.h
new file mode 100644
index 000000000..721b4f1f5
--- /dev/null
+++ b/mailnews/news/src/nsNNTPNewsgroupPost.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsNNTPNewsgroupPost_h
+#define __nsNNTPNewsgroupPost_h
+
+#include "msgCore.h"
+#include "nsINNTPNewsgroupPost.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIFile.h"
+
+#define IDX_HEADER_FROM 0
+#define IDX_HEADER_NEWSGROUPS 1
+#define IDX_HEADER_SUBJECT 2
+
+// set this to the last required header
+#define IDX_HEADER_LAST_REQUIRED IDX_HEADER_SUBJECT
+
+#define IDX_HEADER_PATH 3
+#define IDX_HEADER_DATE 4
+
+#define IDX_HEADER_REPLYTO 5
+#define IDX_HEADER_SENDER 6
+#define IDX_HEADER_FOLLOWUPTO 7
+#define IDX_HEADER_DATERECEIVED 8
+#define IDX_HEADER_EXPIRES 9
+#define IDX_HEADER_CONTROL 10
+#define IDX_HEADER_DISTRIBUTION 11
+#define IDX_HEADER_ORGANIZATION 12
+#define IDX_HEADER_REFERENCES 13
+
+// stuff that's required to be in the message,
+// but probably generated on the server
+#define IDX_HEADER_RELAYVERSION 14
+#define IDX_HEADER_POSTINGVERSION 15
+#define IDX_HEADER_MESSAGEID 16
+
+// keep this in sync with the above
+#define HEADER_LAST IDX_HEADER_MESSAGEID
+
+class nsNNTPNewsgroupPost : public nsINNTPNewsgroupPost {
+
+public:
+ nsNNTPNewsgroupPost();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINNTPNEWSGROUPPOST
+
+private:
+ virtual ~nsNNTPNewsgroupPost();
+
+ nsCOMPtr<nsIFile> m_postMessageFile;
+ nsCString m_header[HEADER_LAST+1];
+ nsCString m_body;
+ bool m_isControl;
+};
+
+#endif /* __nsNNTPNewsgroupPost_h */
diff --git a/mailnews/news/src/nsNNTPProtocol.cpp b/mailnews/news/src/nsNNTPProtocol.cpp
new file mode 100644
index 000000000..8ce367faa
--- /dev/null
+++ b/mailnews/news/src/nsNNTPProtocol.cpp
@@ -0,0 +1,4747 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "MailNewsTypes.h"
+#include "nntpCore.h"
+#include "nsNetUtil.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgHdr.h"
+#include "nsNNTPProtocol.h"
+#include "nsINNTPArticleList.h"
+#include "nsIOutputStream.h"
+#include "nsIMemory.h"
+#include "nsIPipe.h"
+#include "nsCOMPtr.h"
+#include "nsMsgI18N.h"
+#include "nsINNTPNewsgroupPost.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgNewsCID.h"
+
+#include "nsINntpUrl.h"
+#include "prmem.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "prerror.h"
+#include "nsStringGlue.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Services.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+#include "prprf.h"
+
+/* include event sink interfaces for news */
+
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIMsgStatusFeedback.h"
+
+#include "nsMsgKeySet.h"
+
+#include "nsNewsUtils.h"
+#include "nsMsgUtils.h"
+
+#include "nsIMsgIdentity.h"
+#include "nsIMsgAccountManager.h"
+
+#include "nsIPrompt.h"
+#include "nsIMsgStatusFeedback.h"
+
+#include "nsIMsgFolder.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIDocShell.h"
+
+#include "nsIMsgFilterList.h"
+
+// for the memory cache...
+#include "nsICacheEntry.h"
+#include "nsICacheStorage.h"
+#include "nsIApplicationCache.h"
+#include "nsIStreamListener.h"
+#include "nsNetCID.h"
+
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+
+#include "nsIMsgWindow.h"
+#include "nsIWindowWatcher.h"
+
+#include "nsINntpService.h"
+#include "nntpCore.h"
+#include "nsIStreamConverterService.h"
+#include "nsIStreamListenerTee.h"
+#include "nsISocketTransport.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+
+#include "nsIInputStreamPump.h"
+#include "nsIProxyInfo.h"
+#include "nsContentSecurityManager.h"
+
+#include <time.h>
+
+#undef GetPort // XXX Windows!
+#undef SetPort // XXX Windows!
+#undef PostMessage // avoid to collision with WinUser.h
+
+#define PREF_NEWS_CANCEL_CONFIRM "news.cancel.confirm"
+#define PREF_NEWS_CANCEL_ALERT_ON_SUCCESS "news.cancel.alert_on_success"
+#define READ_NEWS_LIST_COUNT_MAX 500 /* number of groups to process at a time when reading the list from the server */
+#define READ_NEWS_LIST_TIMEOUT 50 /* uSec to wait until doing more */
+#define RATE_STR_BUF_LEN 32
+#define UPDATE_THRESHHOLD 25600 /* only update every 25 KB */
+
+using namespace mozilla::mailnews;
+using namespace mozilla;
+
+// NNTP extensions are supported yet
+// until the extension code is ported,
+// we'll skip right to the first nntp command
+// after doing "mode reader"
+// and "pushed" authentication (if necessary),
+//#define HAVE_NNTP_EXTENSIONS
+
+// quiet compiler warnings by defining these function prototypes
+char *MSG_UnEscapeSearchUrl (const char *commandSpecificData);
+
+/* Logging stuff */
+
+PRLogModuleInfo* NNTP = NULL;
+#define out LogLevel::Info
+
+#define NNTP_LOG_READ(buf) \
+if (NNTP==NULL) \
+ NNTP = PR_NewLogModule("NNTP"); \
+MOZ_LOG(NNTP, out, ("(%p) Receiving: %s", this, buf)) ;
+
+#define NNTP_LOG_WRITE(buf) \
+if (NNTP==NULL) \
+ NNTP = PR_NewLogModule("NNTP"); \
+MOZ_LOG(NNTP, out, ("(%p) Sending: %s", this, buf)) ;
+
+#define NNTP_LOG_NOTE(buf) \
+if (NNTP==NULL) \
+ NNTP = PR_NewLogModule("NNTP"); \
+MOZ_LOG(NNTP, out, ("(%p) %s",this, buf)) ;
+
+const char *const stateLabels[] = {
+"NNTP_RESPONSE",
+#ifdef BLOCK_UNTIL_AVAILABLE_CONNECTION
+"NNTP_BLOCK_UNTIL_CONNECTIONS_ARE_AVAILABLE",
+"NNTP_CONNECTIONS_ARE_AVAILABLE",
+#endif
+"NNTP_CONNECT",
+"NNTP_CONNECT_WAIT",
+"NNTP_LOGIN_RESPONSE",
+"NNTP_SEND_MODE_READER",
+"NNTP_SEND_MODE_READER_RESPONSE",
+"SEND_LIST_EXTENSIONS",
+"SEND_LIST_EXTENSIONS_RESPONSE",
+"SEND_LIST_SEARCHES",
+"SEND_LIST_SEARCHES_RESPONSE",
+"NNTP_LIST_SEARCH_HEADERS",
+"NNTP_LIST_SEARCH_HEADERS_RESPONSE",
+"NNTP_GET_PROPERTIES",
+"NNTP_GET_PROPERTIES_RESPONSE",
+"SEND_LIST_SUBSCRIPTIONS",
+"SEND_LIST_SUBSCRIPTIONS_RESPONSE",
+"SEND_FIRST_NNTP_COMMAND",
+"SEND_FIRST_NNTP_COMMAND_RESPONSE",
+"SETUP_NEWS_STREAM",
+"NNTP_BEGIN_AUTHORIZE",
+"NNTP_AUTHORIZE_RESPONSE",
+"NNTP_PASSWORD_RESPONSE",
+"NNTP_READ_LIST_BEGIN",
+"NNTP_READ_LIST",
+"DISPLAY_NEWSGROUPS",
+"NNTP_NEWGROUPS_BEGIN",
+"NNTP_NEWGROUPS",
+"NNTP_BEGIN_ARTICLE",
+"NNTP_READ_ARTICLE",
+"NNTP_XOVER_BEGIN",
+"NNTP_FIGURE_NEXT_CHUNK",
+"NNTP_XOVER_SEND",
+"NNTP_XOVER_RESPONSE",
+"NNTP_XOVER",
+"NEWS_PROCESS_XOVER",
+"NNTP_XHDR_SEND",
+"NNTP_XHDR_RESPONSE",
+"NNTP_READ_GROUP",
+"NNTP_READ_GROUP_RESPONSE",
+"NNTP_READ_GROUP_BODY",
+"NNTP_SEND_GROUP_FOR_ARTICLE",
+"NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE",
+"NNTP_SEND_ARTICLE_NUMBER",
+"NEWS_PROCESS_BODIES",
+"NNTP_PRINT_ARTICLE_HEADERS",
+"NNTP_SEND_POST_DATA",
+"NNTP_SEND_POST_DATA_RESPONSE",
+"NNTP_CHECK_FOR_MESSAGE",
+"NEWS_START_CANCEL",
+"NEWS_DO_CANCEL",
+"NNTP_XPAT_SEND",
+"NNTP_XPAT_RESPONSE",
+"NNTP_SEARCH",
+"NNTP_SEARCH_RESPONSE",
+"NNTP_SEARCH_RESULTS",
+"NNTP_LIST_PRETTY_NAMES",
+"NNTP_LIST_PRETTY_NAMES_RESPONSE",
+"NNTP_LIST_XACTIVE_RESPONSE",
+"NNTP_LIST_XACTIVE",
+"NNTP_LIST_GROUP",
+"NNTP_LIST_GROUP_RESPONSE",
+"NEWS_DONE",
+"NEWS_POST_DONE",
+"NEWS_ERROR",
+"NNTP_ERROR",
+"NEWS_FREE",
+"NNTP_SUSPENDED"
+};
+
+
+/* end logging */
+
+/* Forward declarations */
+
+#define LIST_WANTED 0
+#define ARTICLE_WANTED 1
+#define CANCEL_WANTED 2
+#define GROUP_WANTED 3
+#define NEWS_POST 4
+#define NEW_GROUPS 5
+#define SEARCH_WANTED 6
+#define IDS_WANTED 7
+
+/* the output_buffer_size must be larger than the largest possible line
+ * 2000 seems good for news
+ *
+ * jwz: I increased this to 4k since it must be big enough to hold the
+ * entire button-bar HTML, and with the new "mailto" format, that can
+ * contain arbitrarily long header fields like "references".
+ *
+ * fortezza: proxy auth is huge, buffer increased to 8k (sigh).
+ */
+#define OUTPUT_BUFFER_SIZE (4096*2)
+
+/* the amount of time to subtract from the machine time
+ * for the newgroup command sent to the nntp server
+ */
+#define NEWGROUPS_TIME_OFFSET 60L*60L*12L /* 12 hours */
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// TEMPORARY HARD CODED FUNCTIONS
+///////////////////////////////////////////////////////////////////////////////////////////
+#define NET_IS_SPACE(x) ((x)==' ' || (x)=='\t')
+
+// turn "\xx" (with xx being hex numbers) in string into chars
+char *MSG_UnEscapeSearchUrl (const char *commandSpecificData)
+{
+ nsAutoCString result(commandSpecificData);
+ int32_t slashpos = 0;
+ while (slashpos = result.FindChar('\\', slashpos),
+ slashpos != kNotFound)
+ {
+ nsAutoCString hex;
+ hex.Assign(Substring(result, slashpos + 1, 2));
+ int32_t ch;
+ nsresult rv;
+ ch = hex.ToInteger(&rv, 16);
+ result.Replace(slashpos, 3, NS_SUCCEEDED(rv) && ch != 0 ? (char) ch : 'X');
+ slashpos++;
+ }
+ return ToNewCString(result);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// END OF TEMPORARY HARD CODED FUNCTIONS
+///////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNNTPProtocol, nsMsgProtocol, nsINNTPProtocol,
+ nsITimerCallback, nsICacheEntryOpenCallback, nsIMsgAsyncPromptListener)
+
+nsNNTPProtocol::nsNNTPProtocol(nsINntpIncomingServer *aServer, nsIURI *aURL,
+ nsIMsgWindow *aMsgWindow)
+: nsMsgProtocol(aURL),
+ m_connectionBusy(false),
+ m_nntpServer(aServer)
+{
+ if (!NNTP)
+ NNTP = PR_NewLogModule("NNTP");
+
+ m_ProxyServer = nullptr;
+ m_responseText = nullptr;
+ m_dataBuf = nullptr;
+
+ m_key = nsMsgKey_None;
+
+ mBytesReceived = 0;
+ mBytesReceivedSinceLastStatusUpdate = 0;
+ m_startTime = PR_Now();
+
+ if (aMsgWindow) {
+ m_msgWindow = aMsgWindow;
+ }
+
+ m_runningURL = nullptr;
+ m_fromCache = false;
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) creating",this));
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) initializing, so unset m_currentGroup",this));
+ m_currentGroup.Truncate();
+ m_lastActiveTimeStamp = 0;
+}
+
+nsNNTPProtocol::~nsNNTPProtocol()
+{
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) destroying",this));
+ if (m_nntpServer) {
+ m_nntpServer->WriteNewsrcFile();
+ m_nntpServer->RemoveConnection(this);
+ }
+ if (mUpdateTimer) {
+ mUpdateTimer->Cancel();
+ mUpdateTimer = nullptr;
+ }
+ Cleanup();
+}
+
+void nsNNTPProtocol::Cleanup() //free char* member variables
+{
+ PR_FREEIF(m_responseText);
+ PR_FREEIF(m_dataBuf);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::Initialize(nsIURI *aURL, nsIMsgWindow *aMsgWindow)
+{
+ if (aMsgWindow) {
+ m_msgWindow = aMsgWindow;
+ }
+ nsMsgProtocol::InitFromURI(aURL);
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer);
+ NS_ASSERTION(m_nntpServer, "nsNNTPProtocol need an m_nntpServer.");
+ NS_ENSURE_TRUE(m_nntpServer, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = m_nntpServer->GetMaxArticles(&m_maxArticles);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port = 0;
+ rv = m_url->GetPort(&port);
+ if (NS_FAILED(rv) || (port<=0)) {
+ rv = server->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (port<=0) {
+ port = (socketType == nsMsgSocketType::SSL) ?
+ nsINntpUrl::DEFAULT_NNTPS_PORT : nsINntpUrl::DEFAULT_NNTP_PORT;
+ }
+
+ rv = m_url->SetPort(port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_PRECONDITION(m_url, "invalid URL passed into NNTP Protocol");
+
+ m_runningURL = do_QueryInterface(m_url, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetIsBusy(true);
+
+ nsCString group;
+
+ // Initialize m_newsAction before possible use in ParseURL method
+ m_runningURL->GetNewsAction(&m_newsAction);
+
+ // parse url to get the msg folder and check if the message is in the folder's
+ // local cache before opening a new socket and trying to download the message
+ rv = ParseURL(m_url, group, m_messageID);
+
+ if (NS_SUCCEEDED(rv) && m_runningURL)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
+ if (mailnewsUrl)
+ {
+ if (aMsgWindow)
+ mailnewsUrl->SetMsgWindow(aMsgWindow);
+
+ if (m_newsAction == nsINntpUrl::ActionFetchArticle || m_newsAction == nsINntpUrl::ActionFetchPart
+ || m_newsAction == nsINntpUrl::ActionSaveMessageToDisk)
+ {
+ // Look for the content length
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_runningURL));
+ if (msgUrl)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ // Note that for attachments, the messageSize is going to be the
+ // size of the entire message
+ uint32_t messageSize;
+ msgHdr->GetMessageSize(&messageSize);
+ SetContentLength(messageSize);
+ }
+ }
+
+ bool msgIsInLocalCache = false;
+ mailnewsUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
+ if (msgIsInLocalCache || WeAreOffline())
+ return NS_OK; // probably don't need to do anything else - definitely don't want
+ // to open the socket.
+ }
+ }
+ }
+ else {
+ return rv;
+ }
+
+ if (!m_socketIsOpen)
+ {
+ // When we are making a secure connection, we need to make sure that we
+ // pass an interface requestor down to the socket transport so that PSM can
+ // retrieve a nsIPrompt instance if needed.
+ nsCOMPtr<nsIInterfaceRequestor> ir;
+ if (socketType != nsMsgSocketType::plain && aMsgWindow)
+ {
+ nsCOMPtr<nsIDocShell> docShell;
+ aMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ ir = do_QueryInterface(docShell);
+ }
+
+ // call base class to set up the transport
+
+ int32_t port = 0;
+ nsCString hostName;
+ m_url->GetPort(&port);
+
+ rv = server->GetRealHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_LOG(NNTP, LogLevel::Info, ("(%p) opening connection to %s on port %d",
+ this, hostName.get(), port));
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ rv = MsgExamineForProxy(this, getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) proxyInfo = nullptr;
+
+ rv = OpenNetworkSocketWithInfo(hostName.get(), port,
+ (socketType == nsMsgSocketType::SSL) ? "ssl" : nullptr,
+ proxyInfo, ir);
+
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_nextState = NNTP_LOGIN_RESPONSE;
+ }
+ else {
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+ }
+ m_dataBuf = (char *) PR_Malloc(sizeof(char) * OUTPUT_BUFFER_SIZE);
+ m_dataBufSize = OUTPUT_BUFFER_SIZE;
+
+ if (!m_lineStreamBuffer)
+ m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true /* create new lines */);
+
+ m_typeWanted = 0;
+ m_responseCode = 0;
+ m_previousResponseCode = 0;
+ m_responseText = nullptr;
+
+ m_firstArticle = 0;
+ m_lastArticle = 0;
+ m_firstPossibleArticle = 0;
+ m_lastPossibleArticle = 0;
+ m_numArticlesLoaded = 0;
+ m_numArticlesWanted = 0;
+
+ m_key = nsMsgKey_None;
+
+ m_articleNumber = 0;
+ m_originalContentLength = 0;
+ m_cancelID.Truncate();
+ m_cancelFromHdr.Truncate();
+ m_cancelNewsgroups.Truncate();
+ m_cancelDistribution.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::GetIsBusy(bool *aIsBusy)
+{
+ NS_ENSURE_ARG_POINTER(aIsBusy);
+ *aIsBusy = m_connectionBusy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::SetIsBusy(bool aIsBusy)
+{
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) setting busy to %d",this, aIsBusy));
+ m_connectionBusy = aIsBusy;
+
+ // Maybe we could load another URI.
+ if (!aIsBusy && m_nntpServer)
+ m_nntpServer->PrepareForNextUrl(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::GetIsCachedConnection(bool *aIsCachedConnection)
+{
+ NS_ENSURE_ARG_POINTER(aIsCachedConnection);
+ *aIsCachedConnection = m_fromCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::SetIsCachedConnection(bool aIsCachedConnection)
+{
+ m_fromCache = aIsCachedConnection;
+ return NS_OK;
+}
+
+/* void GetLastActiveTimeStamp (out PRTime aTimeStamp); */
+NS_IMETHODIMP nsNNTPProtocol::GetLastActiveTimeStamp(PRTime *aTimeStamp)
+{
+ NS_ENSURE_ARG_POINTER(aTimeStamp);
+ *aTimeStamp = m_lastActiveTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::LoadNewsUrl(nsIURI * aURL, nsISupports * aConsumer)
+{
+ // clear the previous channel listener and use the new one....
+ // don't reuse an existing channel listener
+ m_channelListener = nullptr;
+ m_channelListener = do_QueryInterface(aConsumer);
+ nsCOMPtr<nsINntpUrl> newsUrl (do_QueryInterface(aURL));
+ newsUrl->GetNewsAction(&m_newsAction);
+
+ SetupPartExtractorListener(m_channelListener);
+ return LoadUrl(aURL, aConsumer);
+}
+
+
+// WARNING: the cache stream listener is intended to be accessed from the UI thread!
+// it will NOT create another proxy for the stream listener that gets passed in...
+class nsNntpCacheStreamListener : public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsNntpCacheStreamListener ();
+
+ nsresult Init(nsIStreamListener * aStreamListener, nsIChannel* channel, nsIMsgMailNewsUrl *aRunningUrl);
+protected:
+ virtual ~nsNntpCacheStreamListener();
+ nsCOMPtr<nsIChannel> mChannelToUse;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIMsgMailNewsUrl> mRunningUrl;
+};
+
+NS_IMPL_ADDREF(nsNntpCacheStreamListener)
+NS_IMPL_RELEASE(nsNntpCacheStreamListener)
+
+NS_INTERFACE_MAP_BEGIN(nsNntpCacheStreamListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+NS_INTERFACE_MAP_END
+
+nsNntpCacheStreamListener::nsNntpCacheStreamListener()
+{
+}
+
+nsNntpCacheStreamListener::~nsNntpCacheStreamListener()
+{}
+
+nsresult nsNntpCacheStreamListener::Init(nsIStreamListener * aStreamListener, nsIChannel* channel,
+ nsIMsgMailNewsUrl *aRunningUrl)
+{
+ NS_ENSURE_ARG(aStreamListener);
+ NS_ENSURE_ARG(channel);
+
+ mChannelToUse = channel;
+
+ mListener = aStreamListener;
+ mRunningUrl = aRunningUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpCacheStreamListener::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
+{
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ nsCOMPtr <nsIRequest> ourRequest = do_QueryInterface(mChannelToUse);
+
+ NS_ASSERTION(mChannelToUse, "null channel in OnStartRequest");
+ if (mChannelToUse)
+ mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->AddRequest(ourRequest, nullptr /* context isupports */);
+ return (mListener) ? mListener->OnStartRequest(ourRequest, aCtxt) : NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpCacheStreamListener::OnStopRequest(nsIRequest *request, nsISupports * aCtxt, nsresult aStatus)
+{
+ nsCOMPtr <nsIRequest> ourRequest = do_QueryInterface(mChannelToUse);
+ nsresult rv = NS_OK;
+ NS_ASSERTION(mListener, "this assertion is for Bug 531794 comment 7");
+ if (mListener)
+ mListener->OnStopRequest(ourRequest, aCtxt, aStatus);
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ NS_ASSERTION(mChannelToUse, "null channel in OnStopRequest");
+ if (mChannelToUse)
+ mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->RemoveRequest(ourRequest, nullptr, aStatus);
+
+ // clear out mem cache entry so we're not holding onto it.
+ if (mRunningUrl)
+ mRunningUrl->SetMemCacheEntry(nullptr);
+
+ mListener = nullptr;
+ nsCOMPtr <nsINNTPProtocol> nntpProtocol = do_QueryInterface(mChannelToUse);
+ if (nntpProtocol) {
+ rv = nntpProtocol->SetIsBusy(false);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ mChannelToUse = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpCacheStreamListener::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, nsIInputStream * aInStream, uint64_t aSourceOffset, uint32_t aCount)
+{
+ NS_ENSURE_STATE(mListener);
+ nsCOMPtr <nsIRequest> ourRequest = do_QueryInterface(mChannelToUse);
+ return mListener->OnDataAvailable(ourRequest, aCtxt, aInStream, aSourceOffset, aCount);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::GetOriginalURI(nsIURI* *aURI)
+{
+ // News does not seem to have the notion of an original URI (See Bug #193317)
+ *aURI = m_url;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::SetOriginalURI(nsIURI* aURI)
+{
+ // News does not seem to have the notion of an original URI (See Bug #193317)
+ return NS_OK; // ignore
+}
+
+nsresult nsNNTPProtocol::SetupPartExtractorListener(nsIStreamListener * aConsumer)
+{
+ bool convertData = false;
+ nsresult rv = NS_OK;
+
+ if (m_newsAction == nsINntpUrl::ActionFetchArticle)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(m_runningURL, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString queryStr;
+ rv = msgUrl->GetQuery(queryStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // check if this is a filter plugin requesting the message.
+ // in that case, set up a text converter
+ convertData = (queryStr.Find("header=filter") != kNotFound
+ || queryStr.Find("header=attach") != kNotFound);
+ }
+ else
+ {
+ convertData = (m_newsAction == nsINntpUrl::ActionFetchPart);
+ }
+ if (convertData)
+ {
+ nsCOMPtr<nsIStreamConverterService> converter = do_GetService("@mozilla.org/streamConverters;1");
+ if (converter && aConsumer)
+ {
+ nsCOMPtr<nsIStreamListener> newConsumer;
+ nsCOMPtr<nsIChannel> channel;
+ QueryInterface(NS_GET_IID(nsIChannel), getter_AddRefs(channel));
+ converter->AsyncConvertData("message/rfc822", "*/*",
+ aConsumer, channel, getter_AddRefs(newConsumer));
+ if (newConsumer)
+ m_channelListener = newConsumer;
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::ReadFromMemCache(nsICacheEntry *entry)
+{
+ NS_ENSURE_ARG(entry);
+
+ nsCOMPtr<nsIInputStream> cacheStream;
+ nsresult rv = entry->OpenInputStream(0, getter_AddRefs(cacheStream));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), cacheStream);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString group;
+ // do this to get m_key set, so that marking the message read will work.
+ rv = ParseURL(m_url, group, m_messageID);
+
+ RefPtr<nsNntpCacheStreamListener> cacheListener =
+ new nsNntpCacheStreamListener();
+
+ SetLoadGroup(m_loadGroup);
+ m_typeWanted = ARTICLE_WANTED;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
+ cacheListener->Init(m_channelListener, static_cast<nsIChannel *>(this), mailnewsUrl);
+
+ mContentType = ""; // reset the content type for the upcoming read....
+
+ rv = pump->AsyncRead(cacheListener, m_channelContext);
+
+ if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read should we return
+ {
+ // we're not calling nsMsgProtocol::AsyncRead(), which calls nsNNTPProtocol::LoadUrl, so we need to take care of some stuff it does.
+ m_channelListener = nullptr;
+ return rv;
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::ReadFromNewsConnection()
+{
+ // we might end up here if we thought we had a news message offline
+ // but it turned out not to be so. In which case, we need to
+ // recall Initialize().
+ if (!m_socketIsOpen || !m_dataBuf)
+ {
+ nsresult rv = Initialize(m_url, m_msgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return nsMsgProtocol::AsyncOpen(m_channelListener, m_channelContext);
+}
+
+// for messages stored in our offline cache, we have special code to handle that...
+// If it's in the local cache, we return true and we can abort the download because
+// this method does the rest of the work.
+bool nsNNTPProtocol::ReadFromLocalCache()
+{
+ bool msgIsInLocalCache = false;
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
+ mailnewsUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
+
+ if (msgIsInLocalCache)
+ {
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder);
+ if (folder && NS_SUCCEEDED(rv))
+ {
+ // we want to create a file channel and read the msg from there.
+ nsCOMPtr<nsIInputStream> fileStream;
+ int64_t offset=0;
+ uint32_t size=0;
+ rv = folder->GetOfflineFileStream(m_key, &offset, &size, getter_AddRefs(fileStream));
+
+ // get the file stream from the folder, somehow (through the message or
+ // folder sink?) We also need to set the transfer offset to the message offset
+ if (fileStream && NS_SUCCEEDED(rv))
+ {
+ // dougt - This may break the ablity to "cancel" a read from offline mail reading.
+ // fileChannel->SetLoadGroup(m_loadGroup);
+
+ m_typeWanted = ARTICLE_WANTED;
+
+ RefPtr<nsNntpCacheStreamListener> cacheListener =
+ new nsNntpCacheStreamListener();
+
+ cacheListener->Init(m_channelListener, static_cast<nsIChannel *>(this), mailnewsUrl);
+
+ // create a stream pump that will async read the specified amount of data.
+ // XXX make size 64-bit int
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump),
+ fileStream, offset, (int64_t) size);
+ if (NS_SUCCEEDED(rv))
+ rv = pump->AsyncRead(cacheListener, m_channelContext);
+
+ if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read should we return
+ {
+ mContentType.Truncate();
+ m_channelListener = nullptr;
+ NNTP_LOG_NOTE("Loading message from offline storage");
+ return true;
+ }
+ }
+ else
+ mailnewsUrl->SetMsgIsInLocalCache(false);
+ }
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsNNTPProtocol::OnCacheEntryAvailable(nsICacheEntry *entry, bool aNew, nsIApplicationCache* aAppCache, nsresult status)
+{
+ nsresult rv = NS_OK;
+
+ if (NS_SUCCEEDED(status))
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL, &rv);
+ mailnewsUrl->SetMemCacheEntry(entry);
+
+ // Insert a "stream T" into the flow so data gets written to both.
+ if (aNew)
+ {
+ // use a stream listener Tee to force data into the cache and to our current channel listener...
+ nsCOMPtr<nsIStreamListener> newListener;
+ nsCOMPtr<nsIStreamListenerTee> tee = do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> outStream;
+ rv = entry->OpenOutputStream(0, getter_AddRefs(outStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = tee->Init(m_channelListener, outStream, nullptr);
+ m_channelListener = do_QueryInterface(tee);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ rv = ReadFromMemCache(entry);
+ if (NS_SUCCEEDED(rv)) {
+ entry->MarkValid();
+ return NS_OK; // kick out if reading from the cache succeeded...
+ }
+ }
+ } // if we got a valid entry back from the cache...
+
+ // if reading from the cache failed or if we are writing into the cache, default to ReadFromNewsConnection.
+ return ReadFromNewsConnection();
+}
+
+NS_IMETHODIMP
+nsNNTPProtocol::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache,
+ uint32_t* aResult)
+{
+ *aResult = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::OpenCacheEntry()
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL, &rv);
+ // get the cache session from our nntp service...
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ rv = nntpService->GetCacheStorage(getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Open a cache entry with key = url, no extension.
+ nsCOMPtr<nsIURI> uri;
+ rv = mailnewsUrl->GetBaseURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Truncate of the query part so we don't duplicate urls in the cache for
+ // various message parts.
+ nsCOMPtr<nsIURI> newUri;
+ uri->Clone(getter_AddRefs(newUri));
+ nsAutoCString path;
+ newUri->GetPath(path);
+ int32_t pos = path.FindChar('?');
+ if (pos != kNotFound) {
+ path.SetLength(pos);
+ newUri->SetPath(path);
+ }
+ return cacheStorage->AsyncOpenURI(newUri, EmptyCString(), nsICacheStorage::OPEN_NORMALLY, this);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t port;
+ rv = mailnewsUrl->GetPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = NS_CheckPortSafety(port, "news");
+ if (NS_FAILED(rv))
+ return rv;
+
+ m_channelContext = ctxt;
+ m_channelListener = listener;
+ m_runningURL->GetNewsAction(&m_newsAction);
+
+ // Before running through the connection, try to see if we can grab the data
+ // from the offline storage or the memory cache. Only actions retrieving
+ // messages can be cached.
+ if (mailnewsUrl && (m_newsAction == nsINntpUrl::ActionFetchArticle ||
+ m_newsAction == nsINntpUrl::ActionFetchPart ||
+ m_newsAction == nsINntpUrl::ActionSaveMessageToDisk))
+ {
+ SetupPartExtractorListener(m_channelListener);
+
+ // Attempt to get the message from the offline storage cache. If this
+ // succeeds, we don't need to use our connection, so tell the server that we
+ // are ready for the next URL.
+ if (ReadFromLocalCache())
+ {
+ if (m_nntpServer)
+ m_nntpServer->PrepareForNextUrl(this);
+ return NS_OK;
+ }
+
+ // If it wasn't offline, try to get the cache from memory. If this call
+ // succeeds, we probably won't need the connection, but the cache might fail
+ // later on. The code there will determine if we need to fallback and will
+ // handle informing the server of our readiness.
+ if (NS_SUCCEEDED(OpenCacheEntry()))
+ return NS_OK;
+ }
+ return nsMsgProtocol::AsyncOpen(listener, ctxt);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+nsresult nsNNTPProtocol::LoadUrl(nsIURI * aURL, nsISupports * aConsumer)
+{
+ NS_ENSURE_ARG_POINTER(aURL);
+
+ nsCString group;
+ mContentType.Truncate();
+ nsresult rv = NS_OK;
+
+ m_runningURL = do_QueryInterface(aURL, &rv);
+ if (NS_FAILED(rv)) return rv;
+ m_runningURL->GetNewsAction(&m_newsAction);
+
+ SetIsBusy(true);
+
+ rv = ParseURL(aURL, group, m_messageID);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to parse news url");
+ //if (NS_FAILED(rv)) return rv;
+ // XXX group returned from ParseURL is assumed to be in UTF-8
+ NS_ASSERTION(MsgIsUTF8(group), "newsgroup name is not in UTF-8");
+ NS_ASSERTION(m_nntpServer, "Parsing must result in an m_nntpServer");
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) m_messageID = %s", this, m_messageID.get()));
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) group = %s", this, group.get()));
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) m_key = %d",this,m_key));
+
+ if (m_newsAction == nsINntpUrl::ActionFetchArticle ||
+ m_newsAction == nsINntpUrl::ActionFetchPart ||
+ m_newsAction == nsINntpUrl::ActionSaveMessageToDisk)
+ m_typeWanted = ARTICLE_WANTED;
+ else if (m_newsAction == nsINntpUrl::ActionCancelArticle)
+ m_typeWanted = CANCEL_WANTED;
+ else if (m_newsAction == nsINntpUrl::ActionPostArticle)
+ {
+ m_typeWanted = NEWS_POST;
+ m_messageID = "";
+ }
+ else if (m_newsAction == nsINntpUrl::ActionListIds)
+ {
+ m_typeWanted = IDS_WANTED;
+ rv = m_nntpServer->FindGroup(group, getter_AddRefs(m_newsFolder));
+ }
+ else if (m_newsAction == nsINntpUrl::ActionSearch)
+ {
+ m_typeWanted = SEARCH_WANTED;
+
+ // Get the search data
+ nsCString commandSpecificData;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(m_runningURL);
+ rv = url->GetQuery(commandSpecificData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MsgUnescapeString(commandSpecificData, 0, m_searchData);
+
+ rv = m_nntpServer->FindGroup(group, getter_AddRefs(m_newsFolder));
+ if (!m_newsFolder)
+ goto FAIL;
+ }
+ else if (m_newsAction == nsINntpUrl::ActionGetNewNews)
+ {
+ bool containsGroup = true;
+ rv = m_nntpServer->ContainsNewsgroup(group, &containsGroup);
+ if (NS_FAILED(rv))
+ goto FAIL;
+
+ if (!containsGroup)
+ {
+ // We have the name of a newsgroup which we're not subscribed to,
+ // the next step is to ask the user whether we should subscribe to it.
+ nsCOMPtr<nsIPrompt> dialog;
+
+ if (m_msgWindow)
+ m_msgWindow->GetPromptDialog(getter_AddRefs(dialog));
+
+ if (!dialog)
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ wwatch->GetNewPrompter(nullptr, getter_AddRefs(dialog));
+ }
+
+ nsString statusString, confirmText;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+
+ // to handle non-ASCII newsgroup names, we store them internally
+ // as escaped. decode and unescape the newsgroup name so we'll
+ // display a proper name.
+
+ nsAutoString unescapedName;
+ rv = NS_MsgDecodeUnescapeURLPath(group, unescapedName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ const char16_t *formatStrings[1] = { unescapedName.get() };
+
+ rv = bundle->FormatStringFromName(
+ u"autoSubscribeText", formatStrings, 1,
+ getter_Copies(confirmText));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool confirmResult = false;
+ rv = dialog->Confirm(nullptr, confirmText.get(), &confirmResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (confirmResult)
+ {
+ rv = m_nntpServer->SubscribeToNewsgroup(group);
+ containsGroup = true;
+ }
+ else
+ {
+ // XXX FIX ME
+ // the way news is current written, we've already opened the socket
+ // and initialized the connection.
+ //
+ // until that is fixed, when the user cancels an autosubscribe, we've got to close it and clean up after ourselves
+ //
+ // see bug http://bugzilla.mozilla.org/show_bug.cgi?id=108293
+ // another problem, autosubscribe urls are ending up as cache entries
+ // because the default action on nntp urls is ActionFetchArticle
+ //
+ // see bug http://bugzilla.mozilla.org/show_bug.cgi?id=108294
+ if (m_runningURL)
+ FinishMemCacheEntry(false); // cleanup mem cache entry
+
+ return CloseConnection();
+ }
+ }
+
+ // If we have a group (since before, or just subscribed), set the m_newsFolder.
+ if (containsGroup)
+ {
+ rv = m_nntpServer->FindGroup(group, getter_AddRefs(m_newsFolder));
+ if (!m_newsFolder)
+ goto FAIL;
+ }
+ m_typeWanted = GROUP_WANTED;
+ }
+ else if (m_newsAction == nsINntpUrl::ActionListGroups)
+ m_typeWanted = LIST_WANTED;
+ else if (m_newsAction == nsINntpUrl::ActionListNewGroups)
+ m_typeWanted = NEW_GROUPS;
+ else if (!m_messageID.IsEmpty() || m_key != nsMsgKey_None)
+ m_typeWanted = ARTICLE_WANTED;
+ else
+ {
+ NS_NOTREACHED("Unknown news action");
+ rv = NS_ERROR_FAILURE;
+ }
+
+ // if this connection comes from the cache, we need to initialize the
+ // load group here, by generating the start request notification. nsMsgProtocol::OnStartRequest
+ // ignores the first parameter (which is supposed to be the channel) so we'll pass in null.
+ if (m_fromCache)
+ nsMsgProtocol::OnStartRequest(nullptr, aURL);
+
+ /* At this point, we're all done parsing the URL, and know exactly
+ what we want to do with it.
+ */
+
+FAIL:
+ if (NS_FAILED(rv))
+ {
+ AlertError(0, nullptr);
+ return rv;
+ }
+ else
+ {
+ if (!m_socketIsOpen)
+ {
+ m_nextStateAfterResponse = m_nextState;
+ m_nextState = NNTP_RESPONSE;
+ }
+ rv = nsMsgProtocol::LoadUrl(aURL, aConsumer);
+ }
+
+ // Make sure that we have the information we need to be able to run the
+ // URLs
+ NS_ASSERTION(m_nntpServer, "Parsing must result in an m_nntpServer");
+ if (m_typeWanted == ARTICLE_WANTED)
+ {
+ if (m_key != nsMsgKey_None)
+ NS_ASSERTION(m_newsFolder, "ARTICLE_WANTED needs m_newsFolder w/ key");
+ else
+ NS_ASSERTION(!m_messageID.IsEmpty(), "ARTICLE_WANTED needs m_messageID w/o key");
+ }
+ else if (m_typeWanted == CANCEL_WANTED)
+ {
+ NS_ASSERTION(!m_messageID.IsEmpty(), "CANCEL_WANTED needs m_messageID");
+ NS_ASSERTION(m_newsFolder, "CANCEL_WANTED needs m_newsFolder");
+ NS_ASSERTION(m_key != nsMsgKey_None, "CANCEL_WANTED needs m_key");
+ }
+ else if (m_typeWanted == GROUP_WANTED)
+ NS_ASSERTION(m_newsFolder, "GROUP_WANTED needs m_newsFolder");
+ else if (m_typeWanted == SEARCH_WANTED)
+ NS_ASSERTION(!m_searchData.IsEmpty(), "SEARCH_WANTED needs m_searchData");
+ else if (m_typeWanted == IDS_WANTED)
+ NS_ASSERTION(m_newsFolder, "IDS_WANTED needs m_newsFolder");
+
+ return rv;
+}
+
+void nsNNTPProtocol::FinishMemCacheEntry(bool valid)
+{
+ nsCOMPtr <nsICacheEntry> memCacheEntry;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (mailnewsurl)
+ mailnewsurl->GetMemCacheEntry(getter_AddRefs(memCacheEntry));
+ if (memCacheEntry)
+ {
+ if (valid)
+ memCacheEntry->MarkValid();
+ else
+ memCacheEntry->AsyncDoom(nullptr);
+ }
+}
+
+// stop binding is a "notification" informing us that the stream associated with aURL is going away.
+NS_IMETHODIMP nsNNTPProtocol::OnStopRequest(nsIRequest *request, nsISupports * aContext, nsresult aStatus)
+{
+ // either remove mem cache entry, or mark it valid if url successful and
+ // command succeeded
+ FinishMemCacheEntry(NS_SUCCEEDED(aStatus)
+ && MK_NNTP_RESPONSE_TYPE(m_responseCode) == MK_NNTP_RESPONSE_TYPE_OK);
+
+ nsMsgProtocol::OnStopRequest(request, aContext, aStatus);
+
+ // nsMsgProtocol::OnStopRequest() has called m_channelListener. There is
+ // no need to be called again in CloseSocket(). Let's clear it here.
+ if (m_channelListener) {
+ m_channelListener = nullptr;
+ }
+
+ // okay, we've been told that the send is done and the connection is going away. So
+ // we need to release all of our state
+ return CloseSocket();
+}
+
+NS_IMETHODIMP nsNNTPProtocol::Cancel(nsresult status) // handle stop button
+{
+ m_nextState = NNTP_ERROR;
+ return nsMsgProtocol::Cancel(NS_BINDING_ABORTED);
+}
+
+nsresult
+nsNNTPProtocol::ParseURL(nsIURI *aURL, nsCString &aGroup, nsCString &aMessageID)
+{
+ NS_ENSURE_ARG_POINTER(aURL);
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) ParseURL",this));
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningURL, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(msgUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString spec;
+ rv = msgUrl->GetOriginalSpec(getter_Copies(spec));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if the original spec is non empty, use it to determine m_newsFolder and m_key
+ if (!spec.IsEmpty()) {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) original message spec = %s",this,spec.get()));
+
+ rv = nntpService->DecomposeNewsURI(spec.get(), getter_AddRefs(folder), &m_key);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // since we are reading a message in this folder, we can set m_newsFolder
+ m_newsFolder = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if we are cancelling, we aren't done. we still need to parse out the messageID from the url
+ // later, we'll use m_newsFolder and m_key to delete the message from the DB, if the cancel is successful.
+ if (m_newsAction != nsINntpUrl::ActionCancelArticle) {
+ return NS_OK;
+ }
+ }
+ else {
+ // clear this, we'll set it later.
+ m_newsFolder = nullptr;
+ m_currentGroup.Truncate();
+ }
+
+ // Load the values from the URL for parsing.
+ rv = m_runningURL->GetGroup(aGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = m_runningURL->GetMessageID(aMessageID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(aMessageID.IsEmpty() || aMessageID != aGroup, "something not null");
+
+ // If we are cancelling, we've got our message id, m_key, and m_newsFolder.
+ // Bail out now to prevent messing those up.
+ if (m_newsAction == nsINntpUrl::ActionCancelArticle)
+ return NS_OK;
+
+ rv = m_runningURL->GetKey(&m_key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if the key is in the local cache.
+ // It's possible that we're have a server/group/key combo that doesn't exist
+ // (think nntp://server/group/key), so not having the folder isn't a bad
+ // thing.
+ if (m_key != nsMsgKey_None)
+ {
+ rv = mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ m_newsFolder = do_QueryInterface(folder);
+
+ if (NS_SUCCEEDED(rv) && m_newsFolder)
+ {
+ bool useLocalCache = false;
+ rv = folder->HasMsgOffline(m_key, &useLocalCache);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // set message is in local cache
+ rv = mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+
+ return NS_OK;
+}
+/*
+ * Writes the data contained in dataBuffer into the current output stream. It also informs
+ * the transport layer that this data is now available for transmission.
+ * Returns a positive number for success, 0 for failure (not all the bytes were written to the
+ * stream, etc). We need to make another pass through this file to install an error system (mscott)
+ */
+
+nsresult nsNNTPProtocol::SendData(const char * dataBuffer, bool aSuppressLogging)
+{
+ if (!aSuppressLogging) {
+ NNTP_LOG_WRITE(dataBuffer);
+ }
+ else {
+ MOZ_LOG(NNTP, out, ("(%p) Logging suppressed for this command (it probably contained authentication information)", this));
+ }
+
+ return nsMsgProtocol::SendData(dataBuffer); // base class actually transmits the data
+}
+
+/* gets the response code from the nntp server and the
+ * response line
+ *
+ * returns the TCP return code from the read
+ */
+nsresult nsNNTPProtocol::NewsResponse(nsIInputStream *inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+
+ NS_PRECONDITION(nullptr != inputStream, "invalid input stream");
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
+
+ NNTP_LOG_READ(line);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if(!line)
+ return NS_ERROR_FAILURE;
+
+ ClearFlag(NNTP_PAUSE_FOR_READ); /* don't pause if we got a line */
+
+ /* almost correct */
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ m_previousResponseCode = m_responseCode;
+
+ PR_sscanf(line, "%d", &m_responseCode);
+
+ if (m_responseCode && PL_strlen(line) > 3)
+ NS_MsgSACopy(&m_responseText, line + 4);
+ else
+ NS_MsgSACopy(&m_responseText, line);
+
+ /* authentication required can come at any time
+ */
+ if (MK_NNTP_RESPONSE_AUTHINFO_REQUIRE == m_responseCode ||
+ MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_REQUIRE == m_responseCode)
+ {
+ m_nextState = NNTP_BEGIN_AUTHORIZE;
+ }
+ else {
+ m_nextState = m_nextStateAfterResponse;
+ }
+
+ PR_FREEIF(line);
+ return NS_OK;
+}
+
+/* interpret the server response after the connect
+ *
+ * returns negative if the server responds unexpectedly
+ */
+
+nsresult nsNNTPProtocol::LoginResponse()
+{
+ bool postingAllowed = m_responseCode == MK_NNTP_RESPONSE_POSTING_ALLOWED;
+
+ if(MK_NNTP_RESPONSE_TYPE(m_responseCode)!=MK_NNTP_RESPONSE_TYPE_OK)
+ {
+ AlertError(MK_NNTP_ERROR_MESSAGE, m_responseText);
+
+ m_nextState = NNTP_ERROR;
+ return NS_ERROR_FAILURE;
+ }
+
+ m_nntpServer->SetPostingAllowed(postingAllowed);
+ m_nextState = NNTP_SEND_MODE_READER;
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SendModeReader()
+{
+ nsresult rv = NS_OK;
+
+ rv = SendData(NNTP_CMD_MODE_READER);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_SEND_MODE_READER_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendModeReaderResponse()
+{
+ SetFlag(NNTP_READER_PERFORMED);
+
+ /* ignore the response code and continue
+ */
+ bool pushAuth = false;
+ nsresult rv = NS_OK;
+
+ NS_ASSERTION(m_nntpServer, "no server, see bug #107797");
+ if (m_nntpServer) {
+ rv = m_nntpServer->GetPushAuth(&pushAuth);
+ }
+ if (NS_SUCCEEDED(rv) && pushAuth) {
+ /* if the news host is set up to require volunteered (pushed) authentication,
+ * do that before we do anything else
+ */
+ m_nextState = NNTP_BEGIN_AUTHORIZE;
+ }
+ else {
+#ifdef HAVE_NNTP_EXTENSIONS
+ m_nextState = SEND_LIST_EXTENSIONS;
+#else
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+#endif /* HAVE_NNTP_EXTENSIONS */
+ }
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SendListExtensions()
+{
+ nsresult rv = SendData(NNTP_CMD_LIST_EXTENSIONS);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = SEND_LIST_EXTENSIONS_RESPONSE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListExtensionsResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ nsresult rv = NS_OK;
+
+ if (MK_NNTP_RESPONSE_TYPE(m_responseCode) == MK_NNTP_RESPONSE_TYPE_OK)
+ {
+ uint32_t status = 0;
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' != line[0]) {
+ m_nntpServer->AddExtension(line);
+ }
+ else
+ {
+ /* tell libmsg that it's ok to ask this news host for extensions */
+ m_nntpServer->SetSupportsExtensions(true);
+ /* all extensions received */
+ m_nextState = SEND_LIST_SEARCHES;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+ }
+ else
+ {
+ /* LIST EXTENSIONS not recognized
+ * tell libmsg not to ask for any more extensions and move on to
+ * the real NNTP command we were trying to do. */
+
+ m_nntpServer->SetSupportsExtensions(false);
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SendListSearches()
+{
+ nsresult rv;
+ bool searchable=false;
+
+ rv = m_nntpServer->QueryExtension("SEARCH",&searchable);
+ if (NS_SUCCEEDED(rv) && searchable)
+ {
+ rv = SendData(NNTP_CMD_LIST_SEARCHES);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = SEND_LIST_SEARCHES_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ }
+ else
+ {
+ /* since SEARCH isn't supported, move on to GET */
+ m_nextState = NNTP_GET_PROPERTIES;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListSearchesResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv = NS_OK;
+
+ NS_PRECONDITION(inputStream, "invalid input stream");
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ NNTP_LOG_READ(line);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' != line[0])
+ {
+ nsAutoCString charset;
+ nsAutoString lineUtf16;
+ if (NS_FAILED(m_nntpServer->GetCharset(charset)) ||
+ NS_FAILED(nsMsgI18NConvertToUnicode(charset.get(),
+ nsDependentCString(line),
+ lineUtf16, true)))
+ CopyUTF8toUTF16(nsDependentCString(line), lineUtf16);
+
+ m_nntpServer->AddSearchableGroup(lineUtf16);
+ }
+ else
+ {
+ /* all searchable groups received */
+ /* LIST SRCHFIELDS is legal if the server supports the SEARCH extension, which */
+ /* we already know it does */
+ m_nextState = NNTP_LIST_SEARCH_HEADERS;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ PR_FREEIF(line);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListSearchHeaders()
+{
+ nsresult rv = SendData(NNTP_CMD_LIST_SEARCH_FIELDS);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_LIST_SEARCH_HEADERS_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListSearchHeadersResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv;
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' != line[0])
+ m_nntpServer->AddSearchableHeader(line);
+ else
+ {
+ m_nextState = NNTP_GET_PROPERTIES;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ PR_FREEIF(line);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::GetProperties()
+{
+ nsresult rv;
+ bool setget=false;
+
+ rv = m_nntpServer->QueryExtension("SETGET",&setget);
+ if (NS_SUCCEEDED(rv) && setget)
+ {
+ rv = SendData(NNTP_CMD_GET_PROPERTIES);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_GET_PROPERTIES_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ }
+ else
+ {
+ /* since GET isn't supported, move on LIST SUBSCRIPTIONS */
+ m_nextState = SEND_LIST_SUBSCRIPTIONS;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+ return rv;
+}
+
+nsresult nsNNTPProtocol::GetPropertiesResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv;
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' != line[0])
+ {
+ char *propertyName = NS_strdup(line);
+ if (propertyName)
+ {
+ char *space = PL_strchr(propertyName, ' ');
+ if (space)
+ {
+ char *propertyValue = space + 1;
+ *space = '\0';
+ m_nntpServer->AddPropertyForGet(propertyName, propertyValue);
+ }
+ PR_Free(propertyName);
+ }
+ }
+ else
+ {
+ /* all GET properties received, move on to LIST SUBSCRIPTIONS */
+ m_nextState = SEND_LIST_SUBSCRIPTIONS;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ PR_FREEIF(line);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListSubscriptions()
+{
+ nsresult rv = NS_OK;
+/* TODO: is this needed for anything?
+#if 0
+ bool searchable=false;
+ rv = m_nntpServer->QueryExtension("LISTSUBSCR",&listsubscr);
+ if (NS_SUCCEEDED(rv) && listsubscr)
+#else
+ if (0)
+#endif
+ {
+ rv = SendData(NNTP_CMD_LIST_SUBSCRIPTIONS);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = SEND_LIST_SUBSCRIPTIONS_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ }
+ else
+*/
+ {
+ /* since LIST SUBSCRIPTIONS isn't supported, move on to real work */
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListSubscriptionsResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv;
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' != line[0])
+ {
+ NS_ERROR("fix me");
+#if 0
+ char *url = PR_smprintf ("%s//%s/%s", NEWS_SCHEME, m_hostName, line);
+ if (url)
+ MSG_AddSubscribedNewsgroup (cd->pane, url);
+#endif
+ }
+ else
+ {
+ /* all default subscriptions received */
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ PR_FREEIF(line);
+ return rv;
+}
+
+/* figure out what the first command is and send it
+ *
+ * returns the status from the NETWrite */
+
+nsresult nsNNTPProtocol::SendFirstNNTPCommand(nsIURI * url)
+{
+ char *command=0;
+
+ if (m_typeWanted == ARTICLE_WANTED) {
+ if (m_key != nsMsgKey_None) {
+ nsresult rv;
+ nsCString newsgroupName;
+ if (m_newsFolder) {
+ rv = m_newsFolder->GetRawName(newsgroupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ MOZ_LOG(NNTP, LogLevel::Info,
+ ("(%p) current group = %s, desired group = %s", this,
+ m_currentGroup.get(), newsgroupName.get()));
+ // if the current group is the desired group, we can just issue the ARTICLE command
+ // if not, we have to do a GROUP first
+ if (newsgroupName.Equals(m_currentGroup))
+ m_nextState = NNTP_SEND_ARTICLE_NUMBER;
+ else
+ m_nextState = NNTP_SEND_GROUP_FOR_ARTICLE;
+
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ }
+
+ if(m_typeWanted == NEWS_POST)
+ { /* posting to the news group */
+ NS_MsgSACopy(&command, "POST");
+ }
+ else if (m_typeWanted == NEW_GROUPS)
+ {
+ uint32_t last_update;
+ nsresult rv;
+
+ if (!m_nntpServer)
+ {
+ NNTP_LOG_NOTE("m_nntpServer is null, panic!");
+ return NS_ERROR_FAILURE;
+ }
+ rv = m_nntpServer->GetLastUpdatedTime(&last_update);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!last_update)
+ {
+ NS_MsgSACopy(&command, "LIST");
+ }
+ else
+ {
+ char small_buf[64];
+ PRExplodedTime expandedTime;
+ PRTime t_usec = (PRTime)last_update * PR_USEC_PER_SEC;
+ PR_ExplodeTime(t_usec, PR_LocalTimeParameters, &expandedTime);
+ PR_FormatTimeUSEnglish(small_buf, sizeof(small_buf),
+ "NEWGROUPS %y%m%d %H%M%S", &expandedTime);
+ NS_MsgSACopy(&command, small_buf);
+ }
+ }
+ else if(m_typeWanted == LIST_WANTED)
+ {
+ nsresult rv;
+
+ ClearFlag(NNTP_USE_FANCY_NEWSGROUP);
+
+ NS_ASSERTION(m_nntpServer, "no m_nntpServer");
+ if (!m_nntpServer) {
+ NNTP_LOG_NOTE("m_nntpServer is null, panic!");
+ return NS_ERROR_FAILURE;
+ }
+
+ bool xactive=false;
+ rv = m_nntpServer->QueryExtension("XACTIVE",&xactive);
+ if (NS_SUCCEEDED(rv) && xactive)
+ {
+ NS_MsgSACopy(&command, "LIST XACTIVE");
+ SetFlag(NNTP_USE_FANCY_NEWSGROUP);
+ }
+ else
+ {
+ NS_MsgSACopy(&command, "LIST");
+ }
+ }
+ else if(m_typeWanted == GROUP_WANTED)
+ {
+ nsresult rv = NS_ERROR_NULL_POINTER;
+
+ NS_ASSERTION(m_newsFolder, "m_newsFolder is null, panic!");
+ if (!m_newsFolder) return NS_ERROR_FAILURE;
+
+ nsCString group_name;
+ rv = m_newsFolder->GetRawName(group_name);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get newsgroup name");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_firstArticle = 0;
+ m_lastArticle = 0;
+
+ NS_MsgSACopy(&command, "GROUP ");
+ NS_MsgSACat(&command, group_name.get());
+ }
+ else if (m_typeWanted == SEARCH_WANTED)
+ {
+ nsresult rv;
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) doing GROUP for XPAT", this));
+ nsCString group_name;
+
+ /* for XPAT, we have to GROUP into the group before searching */
+ if (!m_newsFolder) {
+ NNTP_LOG_NOTE("m_newsFolder is null, panic!");
+ return NS_ERROR_FAILURE;
+ }
+ rv = m_newsFolder->GetRawName(group_name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_MsgSACopy(&command, "GROUP ");
+ NS_MsgSACat (&command, group_name.get());
+
+ // force a GROUP next time
+ m_currentGroup.Truncate();
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_XPAT_SEND;
+ }
+ else if (m_typeWanted == IDS_WANTED)
+ {
+ m_nextState = NNTP_LIST_GROUP;
+ return NS_OK;
+ }
+ else /* article or cancel */
+ {
+ NS_ASSERTION(!m_messageID.IsEmpty(), "No message ID, bailing!");
+ if (m_messageID.IsEmpty()) return NS_ERROR_FAILURE;
+
+ if (m_typeWanted == CANCEL_WANTED)
+ NS_MsgSACopy(&command, "HEAD ");
+ else {
+ NS_ASSERTION(m_typeWanted == ARTICLE_WANTED, "not cancel, and not article");
+ NS_MsgSACopy(&command, "ARTICLE ");
+ }
+
+ if (m_messageID[0] != '<')
+ NS_MsgSACat(&command,"<");
+
+ NS_MsgSACat(&command, m_messageID.get());
+
+ if (PL_strchr(command+8, '>')==0)
+ NS_MsgSACat(&command,">");
+ }
+
+ NS_MsgSACat(&command, CRLF);
+ nsresult rv = SendData(command);
+ PR_Free(command);
+
+ m_nextState = NNTP_RESPONSE;
+ if (m_typeWanted != SEARCH_WANTED)
+ m_nextStateAfterResponse = SEND_FIRST_NNTP_COMMAND_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return rv;
+} /* sent first command */
+
+
+/* interprets the server response from the first command sent
+ *
+ * returns negative if the server responds unexpectedly
+ */
+
+nsresult nsNNTPProtocol::SendFirstNNTPCommandResponse()
+{
+ int32_t major_opcode = MK_NNTP_RESPONSE_TYPE(m_responseCode);
+
+ if((major_opcode == MK_NNTP_RESPONSE_TYPE_CONT &&
+ m_typeWanted == NEWS_POST)
+ || (major_opcode == MK_NNTP_RESPONSE_TYPE_OK &&
+ m_typeWanted != NEWS_POST) )
+ {
+
+ m_nextState = SETUP_NEWS_STREAM;
+ SetFlag(NNTP_SOME_PROTOCOL_SUCCEEDED);
+ return NS_OK;
+ }
+ else
+ {
+ nsresult rv = NS_OK;
+
+ nsString group_name;
+ NS_ASSERTION(m_newsFolder, "no newsFolder");
+ if (m_newsFolder)
+ rv = m_newsFolder->GetUnicodeName(group_name);
+
+ if (m_responseCode == MK_NNTP_RESPONSE_GROUP_NO_GROUP &&
+ m_typeWanted == GROUP_WANTED) {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) group (%s) not found, so unset"
+ " m_currentGroup", this,
+ NS_ConvertUTF16toUTF8(group_name).get()));
+ m_currentGroup.Truncate();
+
+ m_nntpServer->GroupNotFound(m_msgWindow, group_name, true /* opening */);
+ }
+
+ /* if the server returned a 400 error then it is an expected
+ * error. the NEWS_ERROR state will not sever the connection
+ */
+ if(major_opcode == MK_NNTP_RESPONSE_TYPE_CANNOT)
+ m_nextState = NEWS_ERROR;
+ else
+ m_nextState = NNTP_ERROR;
+ // if we have no channel listener, then we're likely downloading
+ // the message for offline use (or at least not displaying it)
+ bool savingArticleOffline = (m_channelListener == nullptr);
+
+ if (m_runningURL)
+ FinishMemCacheEntry(false); // cleanup mem cache entry
+
+ if (NS_SUCCEEDED(rv) && !group_name.IsEmpty() && !savingArticleOffline) {
+ nsString titleStr;
+ rv = GetNewsStringByName("htmlNewsErrorTitle", getter_Copies(titleStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString newsErrorStr;
+ rv = GetNewsStringByName("htmlNewsError", getter_Copies(newsErrorStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsAutoString errorHtml;
+ errorHtml.Append(newsErrorStr);
+
+ errorHtml.AppendLiteral("<b>");
+ errorHtml.Append(NS_ConvertASCIItoUTF16(m_responseText));
+ errorHtml.AppendLiteral("</b><p>");
+
+ rv = GetNewsStringByName("articleExpired", getter_Copies(newsErrorStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ errorHtml.Append(newsErrorStr);
+
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ if ((m_key != nsMsgKey_None) && m_newsFolder) {
+ nsCString messageID;
+ rv = m_newsFolder->GetMessageIdForKey(m_key, messageID);
+ if (NS_SUCCEEDED(rv)) {
+ PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE,"<P>&lt;%.512s&gt; (%lu)", messageID.get(), m_key);
+ errorHtml.Append(NS_ConvertASCIItoUTF16(outputBuffer));
+ }
+ }
+
+ if (m_newsFolder) {
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ if (NS_SUCCEEDED(rv) && folder) {
+ nsCString folderURI;
+ rv = folder->GetURI(folderURI);
+ if (NS_SUCCEEDED(rv)) {
+ PR_snprintf(outputBuffer,OUTPUT_BUFFER_SIZE,"<P> <A HREF=\"%s?list-ids\">", folderURI.get());
+ }
+ }
+ }
+
+ errorHtml.Append(NS_ConvertASCIItoUTF16(outputBuffer));
+
+ rv = GetNewsStringByName("removeExpiredArtLinkText", getter_Copies(newsErrorStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ errorHtml.Append(newsErrorStr);
+ errorHtml.AppendLiteral("</A> </P>");
+
+ if (!m_msgWindow) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (mailnewsurl) {
+ rv = mailnewsurl->GetMsgWindow(getter_AddRefs(m_msgWindow));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ if (!m_msgWindow) return NS_ERROR_FAILURE;
+
+ // note, this will cause us to close the connection.
+ // this will call nsDocShell::LoadURI(), which will
+ // call nsDocShell::Stop(STOP_NETWORK), which will eventually
+ // call nsNNTPProtocol::Cancel(), which will close the socket.
+ // we need to fix this, since the connection is still valid.
+ rv = m_msgWindow->DisplayHTMLInMessagePane(titleStr, errorHtml, true);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ // let's take the opportunity of removing the hdr from the db so we don't try to download
+ // it again.
+ else if (savingArticleOffline)
+ {
+ if ((m_key != nsMsgKey_None) && (m_newsFolder)) {
+ rv = m_newsFolder->RemoveMessage(m_key);
+ }
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+}
+
+nsresult nsNNTPProtocol::SendGroupForArticle()
+{
+ nsresult rv;
+
+ nsCString groupname;
+ rv = m_newsFolder->GetRawName(groupname);
+ NS_ASSERTION(NS_SUCCEEDED(rv) && !groupname.IsEmpty(), "no group name");
+
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "GROUP %.512s" CRLF,
+ groupname.get());
+
+ rv = SendData(outputBuffer);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return rv;
+}
+
+nsresult
+nsNNTPProtocol::SetCurrentGroup()
+{
+ nsCString groupname;
+ NS_ASSERTION(m_newsFolder, "no news folder");
+ if (!m_newsFolder) {
+ m_currentGroup.Truncate();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mozilla::DebugOnly<nsresult> rv = m_newsFolder->GetRawName(groupname);
+ NS_ASSERTION(NS_SUCCEEDED(rv) && !groupname.IsEmpty(), "no group name");
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) SetCurrentGroup to %s",this, groupname.get()));
+ m_currentGroup = groupname;
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SendGroupForArticleResponse()
+{
+ /* ignore the response code and continue
+ */
+ m_nextState = NNTP_SEND_ARTICLE_NUMBER;
+
+ return SetCurrentGroup();
+}
+
+
+nsresult nsNNTPProtocol::SendArticleNumber()
+{
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+ PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "ARTICLE %lu" CRLF, m_key);
+
+ nsresult rv = SendData(outputBuffer);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = SEND_FIRST_NNTP_COMMAND_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::BeginArticle()
+{
+ if (m_typeWanted != ARTICLE_WANTED && m_typeWanted != CANCEL_WANTED)
+ return NS_OK;
+
+ /* Set up the HTML stream
+ */
+
+#ifdef NO_ARTICLE_CACHEING
+ ce->format_out = CLEAR_CACHE_BIT (ce->format_out);
+#endif
+
+ // if we have a channel listener,
+ // create a pipe to pump the message into...the output will go to whoever
+ // is consuming the message display
+ //
+ // the pipe must have an unlimited length since we are going to be filling
+ // it on the main thread while reading it from the main thread. iow, the
+ // write must not block!! (see bug 190988)
+ //
+ if (m_channelListener) {
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+ nsresult rv = pipe->Init(false, false, 4096, PR_UINT32_MAX);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // These always succeed because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(mDisplayInputStream)));
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(mDisplayOutputStream)));
+ }
+
+ m_nextState = NNTP_READ_ARTICLE;
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::DisplayArticle(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t line_length = 0;
+
+ bool pauseForMoreData = false;
+ if (m_channelListener)
+ {
+ nsresult rv = NS_OK;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, line_length, pauseForMoreData, &rv, true);
+ if (pauseForMoreData)
+ {
+ uint64_t inlength = 0;
+ mDisplayInputStream->Available(&inlength);
+ if (inlength > 0) // broadcast our batched up ODA changes
+ m_channelListener->OnDataAvailable(this, m_channelContext, mDisplayInputStream, 0, std::min(inlength, uint64_t(PR_UINT32_MAX)));
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ PR_Free(line);
+ return rv;
+ }
+
+ if (m_newsFolder)
+ m_newsFolder->NotifyDownloadedLine(line, m_key);
+
+ // line only contains a single dot -> message end
+ if (line_length == 1 + MSG_LINEBREAK_LEN && line[0] == '.')
+ {
+ m_nextState = NEWS_DONE;
+
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+
+ uint64_t inlength = 0;
+ mDisplayInputStream->Available(&inlength);
+ if (inlength > 0) // broadcast our batched up ODA changes
+ m_channelListener->OnDataAvailable(this, m_channelContext, mDisplayInputStream, 0, std::min(inlength, uint64_t(PR_UINT32_MAX)));
+ PR_Free(line);
+ return rv;
+ }
+ else // we aren't finished with the message yet
+ {
+ uint32_t count = 0;
+
+ // skip over the quoted '.'
+ if (line_length > 1 && line[0] == '.' && line[1] == '.')
+ mDisplayOutputStream->Write(line+1, line_length-1, &count);
+ else
+ mDisplayOutputStream->Write(line, line_length, &count);
+ }
+
+ PR_Free(line);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::ReadArticle(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv;
+ char *outputBuffer;
+
+ bool pauseForMoreData = false;
+
+ // if we have a channel listener, spool directly to it....
+ // otherwise we must be doing something like save to disk or cancel
+ // in which case we are doing the work.
+ if (m_channelListener)
+ return DisplayArticle(inputStream, length);
+
+
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv, true);
+ if (m_newsFolder && line)
+ {
+ const char *unescapedLine = line;
+ // lines beginning with '.' are escaped by nntp server
+ // or is it just '.' on a line by itself?
+ if (line[0] == '.' && line[1] == '.')
+ unescapedLine++;
+ m_newsFolder->NotifyDownloadedLine(unescapedLine, m_key);
+
+ }
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ if(!line)
+ return rv; /* no line yet or error */
+
+ nsCOMPtr<nsISupports> ctxt = do_QueryInterface(m_runningURL);
+
+ if (m_typeWanted == CANCEL_WANTED && m_responseCode != MK_NNTP_RESPONSE_ARTICLE_HEAD)
+ {
+ /* HEAD command failed. */
+ PR_FREEIF(line);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (line[0] == '.' && line[MSG_LINEBREAK_LEN + 1] == 0)
+ {
+ if (m_typeWanted == CANCEL_WANTED)
+ m_nextState = NEWS_START_CANCEL;
+ else
+ m_nextState = NEWS_DONE;
+
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+ else
+ {
+ if (line[0] == '.')
+ outputBuffer = line + 1;
+ else
+ outputBuffer = line;
+
+ /* Don't send content-type to mime parser if we're doing a cancel
+ because it confuses mime parser into not parsing.
+ */
+ if (m_typeWanted != CANCEL_WANTED || strncmp(outputBuffer, "Content-Type:", 13))
+ {
+ // if we are attempting to cancel, we want to snarf the headers and save the aside, which is what
+ // ParseHeaderForCancel() does.
+ if (m_typeWanted == CANCEL_WANTED) {
+ ParseHeaderForCancel(outputBuffer);
+ }
+
+ }
+ }
+
+ PR_Free(line);
+
+ return NS_OK;
+}
+
+void nsNNTPProtocol::ParseHeaderForCancel(char *buf)
+{
+ static int lastHeader = 0;
+ nsAutoCString header(buf);
+ if (header.First() == ' ' || header.First() == '\t') {
+ header.StripWhitespace();
+ // Add folded line to header if needed.
+ switch (lastHeader) {
+ case 1:
+ m_cancelFromHdr += header;
+ break;
+ case 2:
+ m_cancelID += header;
+ break;
+ case 3:
+ m_cancelNewsgroups += header;
+ break;
+ case 4:
+ m_cancelDistribution += header;
+ break;
+ }
+ // Other folded lines are of no interest.
+ return;
+ }
+
+ lastHeader = 0;
+ int32_t colon = header.FindChar(':');
+ if (!colon)
+ return;
+
+ nsCString value(Substring(header, colon + 1));
+ value.StripWhitespace();
+
+ switch (header.First()) {
+ case 'F': case 'f':
+ if (header.Find("From", CaseInsensitiveCompare) == 0) {
+ m_cancelFromHdr = value;
+ lastHeader = 1;
+ }
+ break;
+ case 'M': case 'm':
+ if (header.Find("Message-ID", CaseInsensitiveCompare) == 0) {
+ m_cancelID = value;
+ lastHeader = 2;
+ }
+ break;
+ case 'N': case 'n':
+ if (header.Find("Newsgroups", CaseInsensitiveCompare) == 0) {
+ m_cancelNewsgroups = value;
+ lastHeader = 3;
+ }
+ break;
+ case 'D': case 'd':
+ if (header.Find("Distributions", CaseInsensitiveCompare) == 0) {
+ m_cancelDistribution = value;
+ lastHeader = 4;
+ }
+ break;
+ }
+
+ return;
+}
+
+nsresult nsNNTPProtocol::BeginAuthorization()
+{
+ char * command = 0;
+ nsresult rv = NS_OK;
+
+ if (!m_newsFolder && m_nntpServer) {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer);
+ if (m_nntpServer) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ m_newsFolder = do_QueryInterface(rootFolder);
+ }
+ }
+ }
+
+ NS_ASSERTION(m_newsFolder, "no m_newsFolder");
+ if (!m_newsFolder)
+ return NS_ERROR_FAILURE;
+
+ // We want to get authentication credentials, but it is possible that the
+ // master password prompt will end up being synchronous. In that case, check
+ // to see if we already have the credentials stored.
+ nsCString username, password;
+ rv = m_newsFolder->GetGroupUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = m_newsFolder->GetGroupPassword(password);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we don't have either a username or a password, queue an asynchronous
+ // prompt.
+ if (username.IsEmpty() || password.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgAsyncPrompter> asyncPrompter =
+ do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the key to coalesce auth prompts.
+ bool singleSignon = false;
+ m_nntpServer->GetSingleSignon(&singleSignon);
+
+ nsCString queueKey;
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer);
+ server->GetKey(queueKey);
+ if (!singleSignon)
+ {
+ nsCString groupName;
+ m_newsFolder->GetRawName(groupName);
+ queueKey += groupName;
+ }
+
+ // If we were called back from HandleAuthenticationFailure, we must have
+ // been handling the response of an authorization state. In that case,
+ // let's hurry up on the auth request
+ bool didAuthFail = m_nextStateAfterResponse == NNTP_AUTHORIZE_RESPONSE ||
+ m_nextStateAfterResponse == NNTP_PASSWORD_RESPONSE;
+ rv = asyncPrompter->QueueAsyncAuthPrompt(queueKey, didAuthFail, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_nextState = NNTP_SUSPENDED;
+ if (m_request)
+ m_request->Suspend();
+ return NS_OK;
+ }
+
+ NS_MsgSACopy(&command, "AUTHINFO user ");
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) use %s as the username", this, username.get()));
+ NS_MsgSACat(&command, username.get());
+ NS_MsgSACat(&command, CRLF);
+
+ rv = SendData(command);
+
+ PR_Free(command);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_AUTHORIZE_RESPONSE;
+
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::AuthorizationResponse()
+{
+ nsresult rv = NS_OK;
+
+ if (MK_NNTP_RESPONSE_AUTHINFO_OK == m_responseCode ||
+ MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_OK == m_responseCode)
+ {
+ /* successful login */
+#ifdef HAVE_NNTP_EXTENSIONS
+ bool pushAuth;
+ /* If we're here because the host demanded authentication before we
+ * even sent a single command, then jump back to the beginning of everything
+ */
+ rv = m_nntpServer->GetPushAuth(&pushAuth);
+
+ if (!TestFlag(NNTP_READER_PERFORMED))
+ m_nextState = NNTP_SEND_MODE_READER;
+ /* If we're here because the host needs pushed authentication, then we
+ * should jump back to SEND_LIST_EXTENSIONS
+ */
+ else if (NS_SUCCEEDED(rv) && pushAuth)
+ m_nextState = SEND_LIST_EXTENSIONS;
+ else
+ /* Normal authentication */
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+#else
+ if (!TestFlag(NNTP_READER_PERFORMED))
+ m_nextState = NNTP_SEND_MODE_READER;
+ else
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+#endif /* HAVE_NNTP_EXTENSIONS */
+
+ return NS_OK;
+ }
+ else if (MK_NNTP_RESPONSE_AUTHINFO_CONT == m_responseCode)
+ {
+ char * command = 0;
+
+ // Since we had to have called BeginAuthorization to get here, we've already
+ // prompted for the authorization credentials. Just grab them without a
+ // further prompt.
+ nsCString password;
+ rv = m_newsFolder->GetGroupPassword(password);
+ if (NS_FAILED(rv) || password.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ NS_MsgSACopy(&command, "AUTHINFO pass ");
+ NS_MsgSACat(&command, password.get());
+ NS_MsgSACat(&command, CRLF);
+
+ rv = SendData(command, true);
+
+ PR_FREEIF(command);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_PASSWORD_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return rv;
+ }
+ else
+ {
+ /* login failed */
+ HandleAuthenticationFailure();
+ return NS_OK;
+ }
+
+ NS_ERROR("should never get here");
+ return NS_ERROR_FAILURE;
+
+}
+
+nsresult nsNNTPProtocol::PasswordResponse()
+{
+ if (MK_NNTP_RESPONSE_AUTHINFO_OK == m_responseCode ||
+ MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_OK == m_responseCode)
+ {
+ /* successful login */
+#ifdef HAVE_NNTP_EXTENSIONS
+ bool pushAuth;
+ /* If we're here because the host demanded authentication before we
+ * even sent a single command, then jump back to the beginning of everything
+ */
+ nsresult rv = m_nntpServer->GetPushAuth(&pushAuth);
+
+ if (!TestFlag(NNTP_READER_PERFORMED))
+ m_nextState = NNTP_SEND_MODE_READER;
+ /* If we're here because the host needs pushed authentication, then we
+ * should jump back to SEND_LIST_EXTENSIONS
+ */
+ else if (NS_SUCCEEDED(rv) && pushAuth)
+ m_nextState = SEND_LIST_EXTENSIONS;
+ else
+ /* Normal authentication */
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+#else
+ if (!TestFlag(NNTP_READER_PERFORMED))
+ m_nextState = NNTP_SEND_MODE_READER;
+ else
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+#endif /* HAVE_NNTP_EXTENSIONS */
+ return NS_OK;
+ }
+ else
+ {
+ HandleAuthenticationFailure();
+ return NS_OK;
+ }
+
+ NS_ERROR("should never get here");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::OnPromptStart(bool *authAvailable)
+{
+ NS_ENSURE_ARG_POINTER(authAvailable);
+ NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
+
+ if (!m_newsFolder)
+ {
+ // If we don't have a news folder, we may have been closed already.
+ NNTP_LOG_NOTE("Canceling queued authentication prompt");
+ *authAvailable = false;
+ return NS_OK;
+ }
+
+ bool didAuthFail = m_nextState == NNTP_AUTHORIZE_RESPONSE ||
+ m_nextState == NNTP_PASSWORD_RESPONSE;
+ nsresult rv = m_newsFolder->GetAuthenticationCredentials(m_msgWindow,
+ true, didAuthFail, authAvailable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // What we do depends on whether or not we have valid credentials
+ return *authAvailable ? OnPromptAuthAvailable() : OnPromptCanceled();
+}
+
+NS_IMETHODIMP nsNNTPProtocol::OnPromptAuthAvailable()
+{
+ NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
+
+ // We previously suspended the request; now resume it to read input
+ if (m_request)
+ m_request->Resume();
+
+ // Now we have our password details accessible from the group, so just call
+ // into the state machine to start the process going again.
+ m_nextState = NNTP_BEGIN_AUTHORIZE;
+ return ProcessProtocolState(nullptr, nullptr, 0, 0);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::OnPromptCanceled()
+{
+ NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
+
+ // We previously suspended the request; now resume it to read input
+ if (m_request)
+ m_request->Resume();
+
+ // Since the prompt was canceled, we can no longer continue the connection.
+ // Thus, we need to go to the NNTP_ERROR state.
+ m_nextState = NNTP_ERROR;
+ return ProcessProtocolState(nullptr, nullptr, 0, 0);
+}
+
+nsresult nsNNTPProtocol::DisplayNewsgroups()
+{
+ m_nextState = NEWS_DONE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) DisplayNewsgroups()",this));
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::BeginNewsgroups()
+{
+ m_nextState = NNTP_NEWGROUPS;
+ mBytesReceived = 0;
+ mBytesReceivedSinceLastStatusUpdate = 0;
+ m_startTime = PR_Now();
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::ProcessNewsgroups(nsIInputStream * inputStream, uint32_t length)
+{
+ char *line, *lineToFree, *s, *s1=NULL, *s2=NULL;
+ uint32_t status = 0;
+ nsresult rv = NS_OK;
+
+ bool pauseForMoreData = false;
+ line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if(!line)
+ return rv; /* no line yet */
+
+ /* End of list?
+ */
+ if (line[0]=='.' && line[1]=='\0')
+ {
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ bool xactive=false;
+ rv = m_nntpServer->QueryExtension("XACTIVE",&xactive);
+ if (NS_SUCCEEDED(rv) && xactive)
+ {
+ nsAutoCString groupName;
+ rv = m_nntpServer->GetFirstGroupNeedingExtraInfo(groupName);
+ if (NS_SUCCEEDED(rv)) {
+ rv = m_nntpServer->FindGroup(groupName, getter_AddRefs(m_newsFolder));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "FindGroup failed");
+ m_nextState = NNTP_LIST_XACTIVE;
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) listing xactive for %s", this,
+ groupName.get()));
+ PR_Free(lineToFree);
+ return NS_OK;
+ }
+ }
+ m_nextState = NEWS_DONE;
+
+ PR_Free(lineToFree);
+ if(status > 0)
+ return NS_OK;
+ else
+ return rv;
+ }
+ else if (line [0] == '.' && line [1] == '.')
+ /* The NNTP server quotes all lines beginning with "." by doubling it. */
+ line++;
+
+ /* almost correct
+ */
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ /* format is "rec.arts.movies.past-films 7302 7119 y"
+ */
+ s = PL_strchr (line, ' ');
+ if (s)
+ {
+ *s = 0;
+ s1 = s+1;
+ s = PL_strchr (s1, ' ');
+ if (s)
+ {
+ *s = 0;
+ s2 = s+1;
+ s = PL_strchr (s2, ' ');
+ if (s)
+ {
+ *s = 0;
+ }
+ }
+ }
+
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+
+ NS_ASSERTION(m_nntpServer, "no nntp incoming server");
+ if (m_nntpServer) {
+ rv = m_nntpServer->AddNewsgroupToList(line);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to add to subscribe ds");
+ }
+
+ bool xactive=false;
+ rv = m_nntpServer->QueryExtension("XACTIVE",&xactive);
+ if (NS_SUCCEEDED(rv) && xactive)
+ {
+ nsAutoCString charset;
+ nsAutoString lineUtf16;
+ if (NS_SUCCEEDED(m_nntpServer->GetCharset(charset)) &&
+ NS_SUCCEEDED(nsMsgI18NConvertToUnicode(charset.get(),
+ nsDependentCString(line),
+ lineUtf16, true)))
+ m_nntpServer->SetGroupNeedsExtraInfo(NS_ConvertUTF16toUTF8(lineUtf16),
+ true);
+ else
+ m_nntpServer->SetGroupNeedsExtraInfo(nsDependentCString(line), true);
+ }
+
+ PR_Free(lineToFree);
+ return rv;
+}
+
+/* Ahhh, this like print's out the headers and stuff
+ *
+ * always returns 0
+ */
+
+nsresult nsNNTPProtocol::BeginReadNewsList()
+{
+ m_readNewsListCount = 0;
+ mNumGroupsListed = 0;
+ m_nextState = NNTP_READ_LIST;
+
+ mBytesReceived = 0;
+ mBytesReceivedSinceLastStatusUpdate = 0;
+ m_startTime = PR_Now();
+
+ return NS_OK;
+}
+
+#define RATE_CONSTANT 976.5625 /* PR_USEC_PER_SEC / 1024 bytes */
+
+static void ComputeRate(int32_t bytes, PRTime startTime, float *rate)
+{
+ // rate = (bytes / USECS since start) * RATE_CONSTANT
+
+ // compute usecs since we started.
+ int32_t delta = (int32_t)(PR_Now() - startTime);
+
+ // compute rate
+ if (delta > 0) {
+ *rate = (float) ((bytes * RATE_CONSTANT) / delta);
+ }
+ else {
+ *rate = 0.0;
+ }
+}
+
+/* display a list of all or part of the newsgroups list
+ * from the news server
+ */
+nsresult nsNNTPProtocol::ReadNewsList(nsIInputStream * inputStream, uint32_t length)
+{
+ nsresult rv = NS_OK;
+ int32_t i=0;
+ uint32_t status = 1;
+
+ bool pauseForMoreData = false;
+ char *line, *lineToFree;
+ line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if (pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ PR_Free(lineToFree);
+ return NS_OK;
+ }
+
+ if (!line)
+ return rv; /* no line yet */
+
+ /* End of list? */
+ if (line[0]=='.' && line[1]=='\0')
+ {
+ bool listpnames=false;
+ NS_ASSERTION(m_nntpServer, "no nntp incoming server");
+ if (m_nntpServer) {
+ rv = m_nntpServer->QueryExtension("LISTPNAMES",&listpnames);
+ }
+ if (NS_SUCCEEDED(rv) && listpnames)
+ m_nextState = NNTP_LIST_PRETTY_NAMES;
+ else
+ m_nextState = DISPLAY_NEWSGROUPS;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_Free(lineToFree);
+ return NS_OK;
+ }
+ else if (line[0] == '.')
+ {
+ if ((line[1] == ' ') || (line[1] == '.' && line [2] == '.' && line[3] == ' '))
+ {
+ // some servers send "... 0000000001 0000000002 y"
+ // and some servers send ". 0000000001 0000000002 y"
+ // just skip that those lines
+ // see bug #69231 and #123560
+ PR_Free(lineToFree);
+ return rv;
+ }
+ // The NNTP server quotes all lines beginning with "." by doubling it, so unquote
+ line++;
+ }
+
+ /* almost correct
+ */
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+
+ if ((mBytesReceivedSinceLastStatusUpdate > UPDATE_THRESHHOLD) && m_msgWindow) {
+ mBytesReceivedSinceLastStatusUpdate = 0;
+
+ nsCOMPtr <nsIMsgStatusFeedback> msgStatusFeedback;
+
+ rv = m_msgWindow->GetStatusFeedback(getter_AddRefs(msgStatusFeedback));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString statusString;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString bytesStr;
+ bytesStr.AppendInt(mBytesReceived / 1024);
+
+ // compute the rate, and then convert it have one
+ // decimal precision.
+ float rate = 0.0;
+ ComputeRate(mBytesReceived, m_startTime, &rate);
+ char rate_buf[RATE_STR_BUF_LEN];
+ PR_snprintf(rate_buf,RATE_STR_BUF_LEN,"%.1f", rate);
+
+ nsAutoString numGroupsStr;
+ numGroupsStr.AppendInt(mNumGroupsListed);
+ NS_ConvertASCIItoUTF16 rateStr(rate_buf);
+
+ const char16_t *formatStrings[3] = { numGroupsStr.get(), bytesStr.get(), rateStr.get()};
+ rv = bundle->FormatStringFromName(u"bytesReceived",
+ formatStrings, 3,
+ getter_Copies(statusString));
+
+ rv = msgStatusFeedback->ShowStatusString(statusString);
+ if (NS_FAILED(rv)) {
+ PR_Free(lineToFree);
+ return rv;
+ }
+ }
+ }
+
+ /* find whitespace separator if it exits */
+ for(i=0; line[i] != '\0' && !NET_IS_SPACE(line[i]); i++)
+ ; /* null body */
+
+ line[i] = 0; /* terminate group name */
+
+ /* store all the group names */
+ NS_ASSERTION(m_nntpServer, "no nntp incoming server");
+ if (m_nntpServer) {
+ m_readNewsListCount++;
+ mNumGroupsListed++;
+ rv = m_nntpServer->AddNewsgroupToList(line);
+// NS_ASSERTION(NS_SUCCEEDED(rv),"failed to add to subscribe ds");
+ // since it's not fatal, don't let this error stop the LIST command.
+ rv = NS_OK;
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+
+ if (m_readNewsListCount == READ_NEWS_LIST_COUNT_MAX) {
+ m_readNewsListCount = 0;
+ if (mUpdateTimer) {
+ mUpdateTimer->Cancel();
+ mUpdateTimer = nullptr;
+ }
+ mUpdateTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create timer");
+ if (NS_FAILED(rv)) {
+ PR_Free(lineToFree);
+ return rv;
+ }
+
+ mInputStream = inputStream;
+
+ const uint32_t kUpdateTimerDelay = READ_NEWS_LIST_TIMEOUT;
+ rv = mUpdateTimer->InitWithCallback(static_cast<nsITimerCallback*>(this), kUpdateTimerDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to init timer");
+ if (NS_FAILED(rv)) {
+ PR_Free(lineToFree);
+ return rv;
+ }
+
+ m_nextState = NNTP_SUSPENDED;
+
+ // suspend necko request until timeout
+ // might not have a request if someone called CloseSocket()
+ // see bug #195440
+ if (m_request)
+ m_request->Suspend();
+ }
+
+ PR_Free(lineToFree);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNNTPProtocol::Notify(nsITimer *timer)
+{
+ NS_ASSERTION(timer == mUpdateTimer.get(), "Hey, this ain't my timer!");
+ mUpdateTimer = nullptr; // release my hold
+ TimerCallback();
+ return NS_OK;
+}
+
+void nsNNTPProtocol::TimerCallback()
+{
+ MOZ_LOG(NNTP, LogLevel::Info,("nsNNTPProtocol::TimerCallback\n"));
+ m_nextState = NNTP_READ_LIST;
+
+ // process whatever is already in the buffer at least once.
+ //
+ // NOTE: while downloading, it would almost be enough to just
+ // resume necko since it will call us again with data. however,
+ // if we are at the end of the data stream then we must call
+ // ProcessProtocolState since necko will not call us again.
+ //
+ // NOTE: this function may Suspend necko. Suspend is a reference
+ // counted (i.e., two suspends requires two resumes before the
+ // request will actually be resumed).
+ //
+ ProcessProtocolState(nullptr, mInputStream, 0,0);
+
+ // resume necko request
+ // might not have a request if someone called CloseSocket()
+ // see bug #195440
+ if (m_request)
+ m_request->Resume();
+
+ return;
+}
+
+void nsNNTPProtocol::HandleAuthenticationFailure()
+{
+ nsCOMPtr<nsIMsgIncomingServer> server(do_QueryInterface(m_nntpServer));
+ nsCString hostname;
+ server->GetRealHostName(hostname);
+ int32_t choice = 1;
+ MsgPromptLoginFailed(m_msgWindow, hostname, &choice);
+
+ if (choice == 1) // Cancel
+ {
+ // When the user requests to cancel the connection, we can't do anything
+ // anymore.
+ NNTP_LOG_NOTE("Password failed, user opted to cancel connection");
+ m_nextState = NNTP_ERROR;
+ return;
+ }
+
+ if (choice == 2) // New password
+ {
+ NNTP_LOG_NOTE("Password failed, user opted to enter new password");
+ NS_ASSERTION(m_newsFolder, "no newsFolder");
+ m_newsFolder->ForgetAuthenticationCredentials();
+ }
+ else if (choice == 0) // Retry
+ {
+ NNTP_LOG_NOTE("Password failed, user opted to retry");
+ }
+
+ // At this point, we've either forgotten the password or opted to retry. In
+ // both cases, we need to try to auth with the password again, so return to
+ // the authentication state.
+ m_nextState = NNTP_BEGIN_AUTHORIZE;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// XOVER, XHDR, and HEAD processing code
+// Used for filters
+// State machine explanation located in doxygen comments for nsNNTPProtocol
+///////////////////////////////////////////////////////////////////////////////
+
+nsresult nsNNTPProtocol::BeginReadXover()
+{
+ int32_t count; /* Response fields */
+ nsresult rv = NS_OK;
+
+ rv = SetCurrentGroup();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* Make sure we never close and automatically reopen the connection at this
+ point; we'll confuse libmsg too much... */
+
+ SetFlag(NNTP_SOME_PROTOCOL_SUCCEEDED);
+
+ /* We have just issued a GROUP command and read the response.
+ Now parse that response to help decide which articles to request
+ xover data for.
+ */
+ PR_sscanf(m_responseText,
+ "%d %d %d",
+ &count,
+ &m_firstPossibleArticle,
+ &m_lastPossibleArticle);
+
+ m_newsgroupList = do_CreateInstance(NS_NNTPNEWSGROUPLIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_newsgroupList->Initialize(m_runningURL, m_newsFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_newsFolder->UpdateSummaryFromNNTPInfo(m_firstPossibleArticle, m_lastPossibleArticle, count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_numArticlesLoaded = 0;
+
+ // if the user sets max_articles to a bogus value, get them everything
+ m_numArticlesWanted = m_maxArticles > 0 ? m_maxArticles : 1L << 30;
+
+ m_nextState = NNTP_FIGURE_NEXT_CHUNK;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::FigureNextChunk()
+{
+ nsresult rv = NS_OK;
+ int32_t status = 0;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (m_firstArticle > 0)
+ {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) add to known articles: %d - %d", this, m_firstArticle, m_lastArticle));
+
+ if (NS_SUCCEEDED(rv) && m_newsgroupList) {
+ rv = m_newsgroupList->AddToKnownArticles(m_firstArticle,
+ m_lastArticle);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (m_numArticlesLoaded >= m_numArticlesWanted)
+ {
+ m_nextState = NEWS_PROCESS_XOVER;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ NS_ASSERTION(m_newsgroupList, "no newsgroupList");
+ if (!m_newsgroupList) return NS_ERROR_FAILURE;
+
+ bool getOldMessages = false;
+ if (m_runningURL) {
+ rv = m_runningURL->GetGetOldMessages(&getOldMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = m_newsgroupList->SetGetOldMessages(getOldMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_newsgroupList->GetRangeOfArtsToDownload(m_msgWindow,
+ m_firstPossibleArticle,
+ m_lastPossibleArticle,
+ m_numArticlesWanted - m_numArticlesLoaded,
+ &(m_firstArticle),
+ &(m_lastArticle),
+ &status);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_firstArticle <= 0 || m_firstArticle > m_lastArticle)
+ {
+ /* Nothing more to get. */
+ m_nextState = NEWS_PROCESS_XOVER;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) Chunk will be (%d-%d)", this, m_firstArticle, m_lastArticle));
+
+ m_articleNumber = m_firstArticle;
+
+ /* was MSG_InitXOVER() */
+ if (m_newsgroupList) {
+ rv = m_newsgroupList->InitXOVER(m_firstArticle, m_lastArticle);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ if (TestFlag(NNTP_NO_XOVER_SUPPORT))
+ m_nextState = NNTP_READ_GROUP;
+ else
+ m_nextState = NNTP_XOVER_SEND;
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::XoverSend()
+{
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "XOVER %d-%d" CRLF,
+ m_firstArticle,
+ m_lastArticle);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_XOVER_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return SendData(outputBuffer);
+}
+
+/* see if the xover response is going to return us data
+ * if the proper code isn't returned then assume xover
+ * isn't supported and use
+ * normal read_group
+ */
+
+nsresult nsNNTPProtocol::ReadXoverResponse()
+{
+#ifdef TEST_NO_XOVER_SUPPORT
+ m_responseCode = MK_NNTP_RESPONSE_CHECK_ERROR; /* pretend XOVER generated an error */
+#endif
+
+ if(m_responseCode != MK_NNTP_RESPONSE_XOVER_OK)
+ {
+ /* If we didn't get back "224 data follows" from the XOVER request,
+ then that must mean that this server doesn't support XOVER. Or
+ maybe the server's XOVER support is busted or something. So,
+ in that case, fall back to the very slow HEAD method.
+
+ But, while debugging here at HQ, getting into this state means
+ something went very wrong, since our servers do XOVER. Thus
+ the assert.
+ */
+ /*NS_ASSERTION (0,"something went very wrong");*/
+ m_nextState = NNTP_READ_GROUP;
+ SetFlag(NNTP_NO_XOVER_SUPPORT);
+ }
+ else
+ {
+ m_nextState = NNTP_XOVER;
+ }
+
+ return NS_OK; /* continue */
+}
+
+/* process the xover list as it comes from the server
+ * and load it into the sort list.
+ */
+
+nsresult nsNNTPProtocol::ReadXover(nsIInputStream * inputStream, uint32_t length)
+{
+ char *line, *lineToFree;
+ nsresult rv;
+ uint32_t status = 1;
+
+ bool pauseForMoreData = false;
+ line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if(!line)
+ return rv; /* no line yet or TCP error */
+
+ if(line[0] == '.' && line[1] == '\0')
+ {
+ m_nextState = NNTP_XHDR_SEND;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_Free(lineToFree);
+ return NS_OK;
+ }
+ else if (line [0] == '.' && line [1] == '.')
+ /* The NNTP server quotes all lines beginning with "." by doubling it. */
+ line++;
+
+ /* almost correct
+ */
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ rv = m_newsgroupList->ProcessXOVERLINE(line, &status);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to process the XOVERLINE");
+
+ m_numArticlesLoaded++;
+ PR_Free(lineToFree);
+ return rv;
+}
+
+/* Finished processing all the XOVER data.
+*/
+
+nsresult nsNNTPProtocol::ProcessXover()
+{
+ nsresult rv;
+
+ /* xover_parse_state stored in MSG_Pane cd->pane */
+ NS_ASSERTION(m_newsgroupList, "no newsgroupList");
+ if (!m_newsgroupList) return NS_ERROR_FAILURE;
+
+ // Some people may use the notifications in CallFilters to close the cached
+ // connections, which will clear m_newsgroupList. So we keep a copy for
+ // ourselves to ward off this threat.
+ nsCOMPtr<nsINNTPNewsgroupList> list(m_newsgroupList);
+ list->CallFilters();
+ int32_t status = 0;
+ rv = list->FinishXOVERLINE(0, &status);
+ m_newsgroupList = nullptr;
+ if (NS_SUCCEEDED(rv) && status < 0) return NS_ERROR_FAILURE;
+
+ m_nextState = NEWS_DONE;
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::XhdrSend()
+{
+ nsCString header;
+ m_newsgroupList->InitXHDR(header);
+ if (header.IsEmpty())
+ {
+ m_nextState = NNTP_FIGURE_NEXT_CHUNK;
+ return NS_OK;
+ }
+
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+ PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "XHDR %s %d-%d" CRLF,
+ header.get(), m_firstArticle, m_lastArticle);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_XHDR_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return SendData(outputBuffer);
+}
+
+nsresult nsNNTPProtocol::XhdrResponse(nsIInputStream *inputStream)
+{
+ if (m_responseCode != MK_NNTP_RESPONSE_XHDR_OK)
+ {
+ m_nextState = NNTP_READ_GROUP;
+ // The reasoning behind setting this flag and not an XHDR flag is that we
+ // are going to have to use HEAD instead. At that point, using XOVER as
+ // well is just wasting bandwidth.
+ SetFlag(NNTP_NO_XOVER_SUPPORT);
+ return NS_OK;
+ }
+
+ char *line, *lineToFree;
+ nsresult rv;
+ uint32_t status = 1;
+
+ bool pauseForMoreData = false;
+ line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if (pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if (!line)
+ return rv; /* no line yet or TCP error */
+
+ if (line[0] == '.' && line[1] == '\0')
+ {
+ m_nextState = NNTP_XHDR_SEND;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_Free(lineToFree);
+ return NS_OK;
+ }
+
+ if (status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ rv = m_newsgroupList->ProcessXHDRLine(nsDependentCString(line));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to process the XHDRLINE");
+
+ m_numArticlesLoaded++;
+ PR_Free(lineToFree);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::ReadHeaders()
+{
+ if(m_articleNumber > m_lastArticle)
+ { /* end of groups */
+
+ m_newsgroupList->InitHEAD(-1);
+ m_nextState = NNTP_FIGURE_NEXT_CHUNK;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ else
+ {
+ m_newsgroupList->InitHEAD(m_articleNumber);
+
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "HEAD %ld" CRLF,
+ m_articleNumber++);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_READ_GROUP_RESPONSE;
+
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return SendData(outputBuffer);
+ }
+}
+
+/* See if the "HEAD" command was successful
+*/
+
+nsresult nsNNTPProtocol::ReadNewsgroupResponse()
+{
+ if (m_responseCode == MK_NNTP_RESPONSE_ARTICLE_HEAD)
+ { /* Head follows - parse it:*/
+ m_nextState = NNTP_READ_GROUP_BODY;
+
+ return NS_OK;
+ }
+ else
+ {
+ m_newsgroupList->HEADFailed(m_articleNumber);
+ m_nextState = NNTP_READ_GROUP;
+ return NS_OK;
+ }
+}
+
+/* read the body of the "HEAD" command
+*/
+nsresult nsNNTPProtocol::ReadNewsgroupBody(nsIInputStream * inputStream, uint32_t length)
+{
+ char *line, *lineToFree;
+ nsresult rv;
+ uint32_t status = 1;
+
+ bool pauseForMoreData = false;
+ line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ /* if TCP error of if there is not a full line yet return
+ */
+ if(!line)
+ return rv;
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) read_group_body: got line: %s|",this,line));
+
+ /* End of body? */
+ if (line[0]=='.' && line[1]=='\0')
+ {
+ m_nextState = NNTP_READ_GROUP;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ else if (line [0] == '.' && line [1] == '.')
+ /* The NNTP server quotes all lines beginning with "." by doubling it. */
+ line++;
+
+ nsCString safe_line(line);
+ rv = m_newsgroupList->ProcessHEADLine(safe_line);
+ PR_Free(lineToFree);
+ return rv;
+}
+
+
+nsresult nsNNTPProtocol::GetNewsStringByID(int32_t stringID, char16_t **aString)
+{
+ nsresult rv;
+ nsAutoString resultString(NS_LITERAL_STRING("???"));
+
+ if (!m_stringBundle)
+ {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(m_stringBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (m_stringBundle) {
+ char16_t *ptrv = nullptr;
+ rv = m_stringBundle->GetStringFromID(stringID, &ptrv);
+
+ if (NS_FAILED(rv)) {
+ resultString.AssignLiteral("[StringID");
+ resultString.AppendInt(stringID);
+ resultString.AppendLiteral("?]");
+ *aString = ToNewUnicode(resultString);
+ }
+ else {
+ *aString = ptrv;
+ }
+ }
+ else {
+ rv = NS_OK;
+ *aString = ToNewUnicode(resultString);
+ }
+ return rv;
+}
+
+nsresult nsNNTPProtocol::GetNewsStringByName(const char *aName, char16_t **aString)
+{
+ nsresult rv;
+ nsAutoString resultString(NS_LITERAL_STRING("???"));
+ if (!m_stringBundle)
+ {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(m_stringBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (m_stringBundle)
+ {
+ nsAutoString unicodeName;
+ CopyASCIItoUTF16(nsDependentCString(aName), unicodeName);
+
+ char16_t *ptrv = nullptr;
+ rv = m_stringBundle->GetStringFromName(unicodeName.get(), &ptrv);
+
+ if (NS_FAILED(rv))
+ {
+ resultString.AssignLiteral("[StringName");
+ resultString.Append(NS_ConvertASCIItoUTF16(aName));
+ resultString.AppendLiteral("?]");
+ *aString = ToNewUnicode(resultString);
+ }
+ else
+ {
+ *aString = ptrv;
+ }
+ }
+ else
+ {
+ rv = NS_OK;
+ *aString = ToNewUnicode(resultString);
+ }
+ return rv;
+}
+
+// sspitzer: PostMessageInFile is derived from nsSmtpProtocol::SendMessageInFile()
+nsresult nsNNTPProtocol::PostMessageInFile(nsIFile *postMessageFile)
+{
+ nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningURL);
+ if (url && postMessageFile)
+ nsMsgProtocol::PostMessage(url, postMessageFile);
+
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ // for now, we are always done at this point..we aren't making multiple
+ // calls to post data...
+
+ // always issue a '.' and CRLF when we are done...
+ PL_strcpy(m_dataBuf, "." CRLF);
+ SendData(m_dataBuf);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_SEND_POST_DATA_RESPONSE;
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::PostData()
+{
+ /* returns 0 on done and negative on error
+ * positive if it needs to continue.
+ */
+ NNTP_LOG_NOTE("nsNNTPProtocol::PostData()");
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsINNTPNewsgroupPost> message;
+ rv = m_runningURL->GetMessageToPost(getter_AddRefs(message));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIFile> filePath;
+ rv = message->GetPostMessageFile(getter_AddRefs(filePath));
+ if (NS_SUCCEEDED(rv))
+ PostMessageInFile(filePath);
+ }
+
+ return NS_OK;
+}
+
+
+/* interpret the response code from the server
+ * after the post is done
+ */
+nsresult nsNNTPProtocol::PostDataResponse()
+{
+ if (m_responseCode != MK_NNTP_RESPONSE_POST_OK)
+ {
+ AlertError(MK_NNTP_ERROR_MESSAGE,m_responseText);
+ m_nextState = NEWS_ERROR;
+ return NS_ERROR_FAILURE;
+ }
+ m_nextState = NEWS_POST_DONE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::CheckForArticle()
+{
+ m_nextState = NEWS_ERROR;
+ if (m_responseCode >= 220 && m_responseCode <= 223) {
+ /* Yes, this article is already there, we're all done. */
+ return NS_OK;
+ }
+ else
+ {
+ /* The article isn't there, so the failure we had earlier wasn't due to
+ a duplicate message-id. Return the error from that previous
+ posting attempt (which is already in ce->URL_s->error_msg). */
+ return NS_ERROR_FAILURE;
+ }
+}
+
+nsresult nsNNTPProtocol::StartCancel()
+{
+ nsresult rv = SendData(NNTP_CMD_POST);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NEWS_DO_CANCEL;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return rv;
+}
+
+void nsNNTPProtocol::CheckIfAuthor(nsIMsgIdentity *aIdentity, const nsCString &aOldFrom, nsCString &aFrom)
+{
+ nsAutoCString from;
+ nsresult rv = aIdentity->GetEmail(from);
+ if (NS_FAILED(rv))
+ return;
+ MOZ_LOG(NNTP, LogLevel::Info,("from = %s", from.get()));
+
+ nsCString us;
+ nsCString them;
+ ExtractEmail(EncodedHeader(from), us);
+ ExtractEmail(EncodedHeader(aOldFrom), them);
+
+ MOZ_LOG(NNTP, LogLevel::Info,("us = %s, them = %s", us.get(), them.get()));
+
+ if (us.Equals(them, nsCaseInsensitiveCStringComparator()))
+ aFrom = from;
+}
+
+nsresult nsNNTPProtocol::DoCancel()
+{
+ int32_t status = 0;
+ bool failure = false;
+ nsresult rv = NS_OK;
+ bool requireConfirmationForCancel = true;
+ bool showAlertAfterCancel = true;
+
+ /* #### Should we do a more real check than this? If the POST command
+ didn't respond with "MK_NNTP_RESPONSE_POST_SEND_NOW Ok", then it's not ready for us to throw a
+ message at it... But the normal posting code doesn't do this check.
+ Why?
+ */
+ NS_ASSERTION (m_responseCode == MK_NNTP_RESPONSE_POST_SEND_NOW, "code != POST_SEND_NOW");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(brandBundle));
+ NS_ENSURE_TRUE(brandBundle, NS_ERROR_FAILURE);
+
+ nsString brandFullName;
+ rv = brandBundle->GetStringFromName(u"brandFullName",
+ getter_Copies(brandFullName));
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ConvertUTF16toUTF8 appName(brandFullName);
+
+ nsCString newsgroups(m_cancelNewsgroups);
+ nsCString distribution (m_cancelDistribution);
+ nsCString id (m_cancelID);
+ nsCString oldFrom(m_cancelFromHdr);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrompt> dialog;
+ if (m_runningURL)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(m_runningURL));
+ rv = GetPromptDialogFromUrl(msgUrl, getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (id.IsEmpty() || newsgroups.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ m_cancelNewsgroups.Truncate();
+ m_cancelDistribution.Truncate();
+ m_cancelFromHdr.Truncate();
+ m_cancelID.Truncate();
+
+ nsString alertText;
+ nsString confirmText;
+ int32_t confirmCancelResult = 0;
+
+ // A little early to declare, but the goto causes problems
+ nsAutoCString otherHeaders;
+
+ /* Make sure that this loser isn't cancelling someone else's posting.
+ Yes, there are occasionally good reasons to do so. Those people
+ capable of making that decision (news admins) have other tools with
+ which to cancel postings (like telnet.)
+
+ Don't do this if server tells us it will validate user. DMB 3/19/97
+ */
+ bool cancelchk=false;
+ rv = m_nntpServer->QueryExtension("CANCELCHK",&cancelchk);
+ nsCString from;
+ if (NS_SUCCEEDED(rv) && !cancelchk)
+ {
+ NNTP_LOG_NOTE("CANCELCHK not supported");
+
+ // get the current identity from the news session....
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && accountManager) {
+ nsCOMPtr<nsIArray> identities;
+ rv = accountManager->GetAllIdentities(getter_AddRefs(identities));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t length;
+ rv = identities->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < length && from.IsEmpty(); ++i)
+ {
+ nsCOMPtr<nsIMsgIdentity> identity(do_QueryElementAt(identities, i, &rv));
+ if (NS_SUCCEEDED(rv))
+ CheckIfAuthor(identity, oldFrom, from);
+ }
+ }
+
+ if (from.IsEmpty())
+ {
+ GetNewsStringByName("cancelDisallowed", getter_Copies(alertText));
+ rv = dialog->Alert(nullptr, alertText.get());
+ // XXX: todo, check rv?
+
+ /* After the cancel is disallowed, Make the status update to be the same as though the
+ cancel was allowed, otherwise, the newsgroup is not able to take further requests as
+ reported here */
+ status = MK_NNTP_CANCEL_DISALLOWED;
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_SEND_POST_DATA_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ failure = true;
+ goto FAIL;
+ }
+ else
+ {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) CANCELCHK not supported, so post the cancel message as %s", this, from.get()));
+ }
+ }
+ else
+ NNTP_LOG_NOTE("CANCELCHK supported, don't do the us vs. them test");
+
+ // QA needs to be able to disable this confirm dialog, for the automated tests. see bug #31057
+ rv = prefBranch->GetBoolPref(PREF_NEWS_CANCEL_CONFIRM, &requireConfirmationForCancel);
+ if (NS_FAILED(rv) || requireConfirmationForCancel) {
+ /* Last chance to cancel the cancel.*/
+ GetNewsStringByName("cancelConfirm", getter_Copies(confirmText));
+ bool dummyValue = false;
+ rv = dialog->ConfirmEx(nullptr, confirmText.get(), nsIPrompt::STD_YES_NO_BUTTONS,
+ nullptr, nullptr, nullptr, nullptr, &dummyValue, &confirmCancelResult);
+ if (NS_FAILED(rv))
+ confirmCancelResult = 1; // Default to No.
+ }
+ else
+ confirmCancelResult = 0; // Default to Yes.
+
+ if (confirmCancelResult != 0) {
+ // they cancelled the cancel
+ status = MK_NNTP_NOT_CANCELLED;
+ failure = true;
+ goto FAIL;
+ }
+
+ otherHeaders.AppendLiteral("Control: cancel ");
+ otherHeaders += id;
+ otherHeaders.AppendLiteral(CRLF);
+ if (!distribution.IsEmpty()) {
+ otherHeaders.AppendLiteral("Distribution: ");
+ otherHeaders += distribution;
+ otherHeaders.AppendLiteral(CRLF);
+ }
+
+ m_cancelStatus = 0;
+
+ {
+ /* NET_BlockingWrite() should go away soon? I think. */
+ /* The following are what we really need to cancel a posted message */
+ char *data;
+ data = PR_smprintf("From: %s" CRLF
+ "Newsgroups: %s" CRLF
+ "Subject: cancel %s" CRLF
+ "References: %s" CRLF
+ "%s" /* otherHeaders, already with CRLF */
+ CRLF /* body separator */
+ "This message was cancelled from within %s." CRLF /* body */
+ "." CRLF, /* trailing message terminator "." */
+ from.get(), newsgroups.get(), id.get(), id.get(),
+ otherHeaders.get(), appName.get());
+
+ rv = SendData(data);
+ PR_Free (data);
+ if (NS_FAILED(rv)) {
+ nsAutoCString errorText;
+ errorText.AppendInt(status);
+ AlertError(MK_TCP_WRITE_ERROR, errorText.get());
+ failure = true;
+ goto FAIL;
+ }
+
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_SEND_POST_DATA_RESPONSE;
+
+ // QA needs to be able to turn this alert off, for the automate tests. see bug #31057
+ rv = prefBranch->GetBoolPref(PREF_NEWS_CANCEL_ALERT_ON_SUCCESS, &showAlertAfterCancel);
+ if (NS_FAILED(rv) || showAlertAfterCancel) {
+ GetNewsStringByName("messageCancelled", getter_Copies(alertText));
+ rv = dialog->Alert(nullptr, alertText.get());
+ // XXX: todo, check rv?
+ }
+
+ if (!m_runningURL) return NS_ERROR_FAILURE;
+
+ // delete the message from the db here.
+ NS_ASSERTION(NS_SUCCEEDED(rv) && m_newsFolder && (m_key != nsMsgKey_None), "need more to remove this message from the db");
+ if ((m_key != nsMsgKey_None) && (m_newsFolder))
+ rv = m_newsFolder->RemoveMessage(m_key);
+
+ }
+
+FAIL:
+ NS_ASSERTION(m_newsFolder,"no news folder");
+ if (m_newsFolder)
+ rv = ( failure ) ? m_newsFolder->CancelFailed()
+ : m_newsFolder->CancelComplete();
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::XPATSend()
+{
+ nsresult rv = NS_OK;
+ int32_t slash = m_searchData.FindChar('/');
+
+ if (slash >= 0)
+ {
+ /* extract the XPAT encoding for one query term */
+ /* char *next_search = NULL; */
+ char *command = NULL;
+ char *unescapedCommand = NULL;
+ char *endOfTerm = NULL;
+ NS_MsgSACopy (&command, m_searchData.get() + slash + 1);
+ endOfTerm = PL_strchr(command, '/');
+ if (endOfTerm)
+ *endOfTerm = '\0';
+ NS_MsgSACat(&command, CRLF);
+
+ unescapedCommand = MSG_UnEscapeSearchUrl(command);
+
+ /* send one term off to the server */
+ rv = SendData(unescapedCommand);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_XPAT_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ PR_Free(command);
+ PR_Free(unescapedCommand);
+ }
+ else
+ {
+ m_nextState = NEWS_DONE;
+ }
+ return rv;
+}
+
+nsresult nsNNTPProtocol::XPATResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 1;
+ nsresult rv;
+
+ if (m_responseCode != MK_NNTP_RESPONSE_XPAT_OK)
+ {
+ AlertError(MK_NNTP_ERROR_MESSAGE,m_responseText);
+ m_nextState = NNTP_ERROR;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_ERROR_FAILURE;
+ }
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ NNTP_LOG_READ(line);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if (line)
+ {
+ if (line[0] != '.')
+ {
+ long articleNumber;
+ PR_sscanf(line, "%ld", &articleNumber);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (mailnewsurl)
+ {
+ nsCOMPtr <nsIMsgSearchSession> searchSession;
+ nsCOMPtr <nsIMsgSearchAdapter> searchAdapter;
+ mailnewsurl->GetSearchSession(getter_AddRefs(searchSession));
+ if (searchSession)
+ {
+ searchSession->GetRunningAdapter(getter_AddRefs(searchAdapter));
+ if (searchAdapter)
+ searchAdapter->AddHit((uint32_t) articleNumber);
+ }
+ }
+ }
+ else
+ {
+ /* set up the next term for next time around */
+ int32_t slash = m_searchData.FindChar('/');
+
+ if (slash >= 0)
+ m_searchData.Cut(0, slash + 1);
+ else
+ m_searchData.Truncate();
+
+ m_nextState = NNTP_XPAT_SEND;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_FREEIF(line);
+ return NS_OK;
+ }
+ }
+ PR_FREEIF(line);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::ListPrettyNames()
+{
+
+ nsCString group_name;
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ m_newsFolder->GetRawName(group_name);
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "LIST PRETTYNAMES %.512s" CRLF,
+ group_name.get());
+
+ nsresult rv = SendData(outputBuffer);
+ NNTP_LOG_NOTE(outputBuffer);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_LIST_PRETTY_NAMES_RESPONSE;
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::ListPrettyNamesResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+
+ if (m_responseCode != MK_NNTP_RESPONSE_LIST_OK)
+ {
+ m_nextState = DISPLAY_NEWSGROUPS;
+ /* m_nextState = NEWS_DONE; */
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
+
+ NNTP_LOG_READ(line);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if (line)
+ {
+ if (line[0] != '.')
+ {
+#if 0 // SetPrettyName is not yet implemented. No reason to bother
+ int i;
+ /* find whitespace separator if it exits */
+ for (i=0; line[i] != '\0' && !NET_IS_SPACE(line[i]); i++)
+ ; /* null body */
+
+ char *prettyName;
+ if(line[i] == '\0')
+ prettyName = &line[i];
+ else
+ prettyName = &line[i+1];
+
+ line[i] = 0; /* terminate group name */
+ if (i > 0) {
+ nsAutoCString charset;
+ nsAutoString lineUtf16, prettyNameUtf16;
+ if (NS_FAILED(m_nntpServer->GetCharset(charset) ||
+ NS_FAILED(ConvertToUnicode(charset, line, lineUtf16)) ||
+ NS_FAILED(ConvertToUnicode(charset, prettyName, prettyNameUtf16)))) {
+ CopyUTF8toUTF16(line, lineUtf16);
+ CopyUTF8toUTF16(prettyName, prettyNameUtf16);
+ }
+ m_nntpServer->SetPrettyNameForGroup(lineUtf16, prettyNameUtf16);
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) adding pretty name %s", this,
+ NS_ConvertUTF16toUTF8(prettyNameUtf16).get()));
+ }
+#endif
+ }
+ else
+ {
+ m_nextState = DISPLAY_NEWSGROUPS; /* this assumes we were doing a list */
+ /* m_nextState = NEWS_DONE; */ /* ### dmb - don't really know */
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_FREEIF(line);
+ return NS_OK;
+ }
+ }
+ PR_FREEIF(line);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::ListXActive()
+{
+ nsCString group_name;
+ nsresult rv;
+ rv = m_newsFolder->GetRawName(group_name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "LIST XACTIVE %.512s" CRLF,
+ group_name.get());
+
+ rv = SendData(outputBuffer);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_LIST_XACTIVE_RESPONSE;
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::ListXActiveResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv;
+
+ NS_ASSERTION(m_responseCode == MK_NNTP_RESPONSE_LIST_OK, "code != LIST_OK");
+ if (m_responseCode != MK_NNTP_RESPONSE_LIST_OK)
+ {
+ m_nextState = DISPLAY_NEWSGROUPS;
+ /* m_nextState = NEWS_DONE; */
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
+
+ NNTP_LOG_READ(line);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ /* almost correct */
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ if (line)
+ {
+ if (line[0] != '.')
+ {
+ char *s = line;
+ /* format is "rec.arts.movies.past-films 7302 7119 csp"
+ */
+ while (*s && !NET_IS_SPACE(*s))
+ s++;
+ if (*s)
+ {
+ char flags[32]; /* ought to be big enough */
+ *s = 0;
+ PR_sscanf(s + 1,
+ "%d %d %31s",
+ &m_firstPossibleArticle,
+ &m_lastPossibleArticle,
+ flags);
+
+
+ NS_ASSERTION(m_nntpServer, "no nntp incoming server");
+ if (m_nntpServer) {
+ rv = m_nntpServer->AddNewsgroupToList(line);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to add to subscribe ds");
+ }
+
+ /* we're either going to list prettynames first, or list
+ all prettynames every time, so we won't care so much
+ if it gets interrupted. */
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) got xactive for %s of %s", this, line, flags));
+ /* This isn't required, because the extra info is
+ initialized to false for new groups. And it's
+ an expensive call.
+ */
+ /* MSG_SetGroupNeedsExtraInfo(cd->host, line, false); */
+ }
+ }
+ else
+ {
+ bool xactive=false;
+ rv = m_nntpServer->QueryExtension("XACTIVE",&xactive);
+ if (m_typeWanted == NEW_GROUPS &&
+ NS_SUCCEEDED(rv) && xactive)
+ {
+ nsCOMPtr <nsIMsgNewsFolder> old_newsFolder;
+ old_newsFolder = m_newsFolder;
+ nsCString groupName;
+
+ rv = m_nntpServer->GetFirstGroupNeedingExtraInfo(groupName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = m_nntpServer->FindGroup(groupName,
+ getter_AddRefs(m_newsFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // see if we got a different group
+ if (old_newsFolder && m_newsFolder &&
+ (old_newsFolder.get() != m_newsFolder.get()))
+ /* make sure we're not stuck on the same group */
+ {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) listing xactive for %s", this, groupName.get()));
+ m_nextState = NNTP_LIST_XACTIVE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_FREEIF(line);
+ return NS_OK;
+ }
+ else
+ {
+ m_newsFolder = nullptr;
+ }
+ }
+ bool listpname;
+ rv = m_nntpServer->QueryExtension("LISTPNAME",&listpname);
+ if (NS_SUCCEEDED(rv) && listpname)
+ m_nextState = NNTP_LIST_PRETTY_NAMES;
+ else
+ m_nextState = DISPLAY_NEWSGROUPS; /* this assumes we were doing a list - who knows? */
+ /* m_nextState = NEWS_DONE; */ /* ### dmb - don't really know */
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_FREEIF(line);
+ return NS_OK;
+ }
+ }
+ PR_FREEIF(line);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SendListGroup()
+{
+ nsresult rv;
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ NS_ASSERTION(m_newsFolder,"no newsFolder");
+ if (!m_newsFolder) return NS_ERROR_FAILURE;
+ nsCString newsgroupName;
+
+ rv = m_newsFolder->GetRawName(newsgroupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "listgroup %.512s" CRLF,
+ newsgroupName.get());
+
+ m_articleList = do_CreateInstance(NS_NNTPARTICLELIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = m_articleList->Initialize(m_newsFolder);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = SendData(outputBuffer);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_LIST_GROUP_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListGroupResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+
+ NS_ASSERTION(m_responseCode == MK_NNTP_RESPONSE_GROUP_SELECTED, "code != GROUP_SELECTED");
+ if (m_responseCode != MK_NNTP_RESPONSE_GROUP_SELECTED)
+ {
+ m_nextState = NEWS_DONE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if (line)
+ {
+ mozilla::DebugOnly<nsresult> rv;
+ if (line[0] != '.')
+ {
+ nsMsgKey found_id = nsMsgKey_None;
+ PR_sscanf(line, "%ld", &found_id);
+ rv = m_articleList->AddArticleKey(found_id);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "add article key failed");
+ }
+ else
+ {
+ rv = m_articleList->FinishAddingArticleKeys();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "finish adding article key failed");
+ m_articleList = nullptr;
+ m_nextState = NEWS_DONE; /* ### dmb - don't really know */
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_FREEIF(line);
+ return NS_OK;
+ }
+ }
+ PR_FREEIF(line);
+ return NS_OK;
+}
+
+
+nsresult nsNNTPProtocol::Search()
+{
+ NS_ERROR("Search not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsNNTPProtocol::SearchResponse()
+{
+ if (MK_NNTP_RESPONSE_TYPE(m_responseCode) == MK_NNTP_RESPONSE_TYPE_OK)
+ m_nextState = NNTP_SEARCH_RESULTS;
+ else
+ m_nextState = NEWS_DONE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SearchResults(nsIInputStream *inputStream, uint32_t length)
+{
+ uint32_t status = 1;
+ nsresult rv;
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' == line[0])
+ {
+ /* all overview lines received */
+ m_nextState = NEWS_DONE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+ PR_FREEIF(line);
+ return rv;
+}
+
+/* Sets state for the transfer. This used to be known as net_setup_news_stream */
+nsresult nsNNTPProtocol::SetupForTransfer()
+{
+ if (m_typeWanted == NEWS_POST)
+ {
+ m_nextState = NNTP_SEND_POST_DATA;
+ }
+ else if(m_typeWanted == LIST_WANTED)
+ {
+ if (TestFlag(NNTP_USE_FANCY_NEWSGROUP))
+ m_nextState = NNTP_LIST_XACTIVE_RESPONSE;
+ else
+ m_nextState = NNTP_READ_LIST_BEGIN;
+ }
+ else if(m_typeWanted == GROUP_WANTED)
+ m_nextState = NNTP_XOVER_BEGIN;
+ else if(m_typeWanted == NEW_GROUPS)
+ m_nextState = NNTP_NEWGROUPS_BEGIN;
+ else if(m_typeWanted == ARTICLE_WANTED ||
+ m_typeWanted== CANCEL_WANTED)
+ m_nextState = NNTP_BEGIN_ARTICLE;
+ else if (m_typeWanted== SEARCH_WANTED)
+ m_nextState = NNTP_XPAT_SEND;
+ else
+ {
+ NS_ERROR("unexpected");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+// The following method is used for processing the news state machine.
+// It returns a negative number (mscott: we'll change this to be an enumerated type which we'll coordinate
+// with the netlib folks?) when we are done processing.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+nsresult nsNNTPProtocol::ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length)
+{
+ nsresult status = NS_OK;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (inputStream && (!mailnewsurl || !m_nntpServer))
+ {
+ // In these cases, we are going to return since our data is effectively
+ // invalid. However, nsInputStream would really rather that we at least read
+ // some of our input data (even if not all of it). Therefore, we'll read a
+ // little bit.
+ char buffer[128];
+ uint32_t readData = 0;
+ inputStream->Read(buffer, 127, &readData);
+ buffer[readData] = '\0';
+ MOZ_LOG(NNTP, LogLevel::Debug, ("(%p) Ignoring data: %s", this, buffer));
+ }
+
+ if (!mailnewsurl)
+ return NS_OK; // probably no data available - it's OK.
+
+ if (!m_nntpServer)
+ {
+ // Parsing must result in our m_nntpServer being set, so we should never
+ // have a case where m_nntpServer being false is safe. Most likely, we have
+ // already closed our socket and we are merely flushing out the socket
+ // receive queue. Since the user told us to stop, don't process any more
+ // input.
+ return inputStream ? inputStream->Close() : NS_OK;
+ }
+
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+
+ while(!TestFlag(NNTP_PAUSE_FOR_READ))
+ {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) Next state: %s",this, stateLabels[m_nextState]));
+ // examine our current state and call an appropriate handler for that state.....
+ switch(m_nextState)
+ {
+ case NNTP_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = NewsResponse(inputStream, length);
+ break;
+
+ // mscott: I've removed the states involving connections on the assumption
+ // that core netlib will now be managing that information.
+
+ case NNTP_LOGIN_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = LoginResponse();
+ break;
+
+ case NNTP_SEND_MODE_READER:
+ status = SendModeReader();
+ break;
+
+ case NNTP_SEND_MODE_READER_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendModeReaderResponse();
+ break;
+
+ case SEND_LIST_EXTENSIONS:
+ status = SendListExtensions();
+ break;
+ case SEND_LIST_EXTENSIONS_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendListExtensionsResponse(inputStream, length);
+ break;
+ case SEND_LIST_SEARCHES:
+ status = SendListSearches();
+ break;
+ case SEND_LIST_SEARCHES_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendListSearchesResponse(inputStream, length);
+ break;
+ case NNTP_LIST_SEARCH_HEADERS:
+ status = SendListSearchHeaders();
+ break;
+ case NNTP_LIST_SEARCH_HEADERS_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendListSearchHeadersResponse(inputStream, length);
+ break;
+ case NNTP_GET_PROPERTIES:
+ status = GetProperties();
+ break;
+ case NNTP_GET_PROPERTIES_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = GetPropertiesResponse(inputStream, length);
+ break;
+ case SEND_LIST_SUBSCRIPTIONS:
+ status = SendListSubscriptions();
+ break;
+ case SEND_LIST_SUBSCRIPTIONS_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendListSubscriptionsResponse(inputStream, length);
+ break;
+
+ case SEND_FIRST_NNTP_COMMAND:
+ status = SendFirstNNTPCommand(url);
+ break;
+ case SEND_FIRST_NNTP_COMMAND_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendFirstNNTPCommandResponse();
+ break;
+
+ case NNTP_SEND_GROUP_FOR_ARTICLE:
+ status = SendGroupForArticle();
+ break;
+ case NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendGroupForArticleResponse();
+ break;
+ case NNTP_SEND_ARTICLE_NUMBER:
+ status = SendArticleNumber();
+ break;
+
+ case SETUP_NEWS_STREAM:
+ status = SetupForTransfer();
+ break;
+
+ case NNTP_BEGIN_AUTHORIZE:
+ status = BeginAuthorization();
+ break;
+
+ case NNTP_AUTHORIZE_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = AuthorizationResponse();
+ break;
+
+ case NNTP_PASSWORD_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = PasswordResponse();
+ break;
+
+ // read list
+ case NNTP_READ_LIST_BEGIN:
+ status = BeginReadNewsList();
+ break;
+ case NNTP_READ_LIST:
+ status = ReadNewsList(inputStream, length);
+ break;
+
+ // news group
+ case DISPLAY_NEWSGROUPS:
+ status = DisplayNewsgroups();
+ break;
+ case NNTP_NEWGROUPS_BEGIN:
+ status = BeginNewsgroups();
+ break;
+ case NNTP_NEWGROUPS:
+ status = ProcessNewsgroups(inputStream, length);
+ break;
+
+ // article specific
+ case NNTP_BEGIN_ARTICLE:
+ status = BeginArticle();
+ break;
+
+ case NNTP_READ_ARTICLE:
+ status = ReadArticle(inputStream, length);
+ break;
+
+ case NNTP_XOVER_BEGIN:
+ status = BeginReadXover();
+ break;
+
+ case NNTP_FIGURE_NEXT_CHUNK:
+ status = FigureNextChunk();
+ break;
+
+ case NNTP_XOVER_SEND:
+ status = XoverSend();
+ break;
+
+ case NNTP_XOVER:
+ status = ReadXover(inputStream, length);
+ break;
+
+ case NNTP_XOVER_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = ReadXoverResponse();
+ break;
+
+ case NEWS_PROCESS_XOVER:
+ case NEWS_PROCESS_BODIES:
+ status = ProcessXover();
+ break;
+
+ case NNTP_XHDR_SEND:
+ status = XhdrSend();
+ break;
+
+ case NNTP_XHDR_RESPONSE:
+ status = XhdrResponse(inputStream);
+ break;
+
+ case NNTP_READ_GROUP:
+ status = ReadHeaders();
+ break;
+
+ case NNTP_READ_GROUP_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = ReadNewsgroupResponse();
+ break;
+
+ case NNTP_READ_GROUP_BODY:
+ status = ReadNewsgroupBody(inputStream, length);
+ break;
+
+ case NNTP_SEND_POST_DATA:
+ status = PostData();
+ break;
+ case NNTP_SEND_POST_DATA_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = PostDataResponse();
+ break;
+
+ case NNTP_CHECK_FOR_MESSAGE:
+ status = CheckForArticle();
+ break;
+
+ // cancel
+ case NEWS_START_CANCEL:
+ status = StartCancel();
+ break;
+
+ case NEWS_DO_CANCEL:
+ status = DoCancel();
+ break;
+
+ // XPAT
+ case NNTP_XPAT_SEND:
+ status = XPATSend();
+ break;
+ case NNTP_XPAT_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = XPATResponse(inputStream, length);
+ break;
+
+ // search
+ case NNTP_SEARCH:
+ status = Search();
+ break;
+ case NNTP_SEARCH_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SearchResponse();
+ break;
+ case NNTP_SEARCH_RESULTS:
+ status = SearchResults(inputStream, length);
+ break;
+
+
+ case NNTP_LIST_PRETTY_NAMES:
+ status = ListPrettyNames();
+ break;
+ case NNTP_LIST_PRETTY_NAMES_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = ListPrettyNamesResponse(inputStream, length);
+ break;
+ case NNTP_LIST_XACTIVE:
+ status = ListXActive();
+ break;
+ case NNTP_LIST_XACTIVE_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = ListXActiveResponse(inputStream, length);
+ break;
+ case NNTP_LIST_GROUP:
+ status = SendListGroup();
+ break;
+ case NNTP_LIST_GROUP_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendListGroupResponse(inputStream, length);
+ break;
+ case NEWS_DONE:
+ m_nextState = NEWS_FREE;
+ break;
+ case NEWS_POST_DONE:
+ NNTP_LOG_NOTE("NEWS_POST_DONE");
+ mailnewsurl->SetUrlState(false, NS_OK);
+ m_nextState = NEWS_FREE;
+ break;
+ case NEWS_ERROR:
+ NNTP_LOG_NOTE("NEWS_ERROR");
+ if (m_responseCode == MK_NNTP_RESPONSE_ARTICLE_NOTFOUND || m_responseCode == MK_NNTP_RESPONSE_ARTICLE_NONEXIST)
+ mailnewsurl->SetUrlState(false, NS_MSG_NEWS_ARTICLE_NOT_FOUND);
+ else
+ mailnewsurl->SetUrlState(false, NS_ERROR_FAILURE);
+ m_nextState = NEWS_FREE;
+ break;
+ case NNTP_ERROR:
+ // XXX do we really want to remove the connection from
+ // the cache on error?
+ /* check if this connection came from the cache or if it was
+ * a new connection. If it was not new lets start it over
+ * again. But only if we didn't have any successful protocol
+ * dialog at all.
+ */
+ FinishMemCacheEntry(false); // cleanup mem cache entry
+ if (m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NOTFOUND && m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NONEXIST)
+ return CloseConnection();
+ MOZ_FALLTHROUGH;
+ case NEWS_FREE:
+ // Remember when we last used this connection
+ m_lastActiveTimeStamp = PR_Now();
+ CleanupAfterRunningUrl();
+ MOZ_FALLTHROUGH;
+ case NNTP_SUSPENDED:
+ return NS_OK;
+ break;
+ default:
+ /* big error */
+ return NS_ERROR_FAILURE;
+
+ } // end switch
+
+ if (NS_FAILED(status) && m_nextState != NEWS_ERROR &&
+ m_nextState != NNTP_ERROR && m_nextState != NEWS_FREE)
+ {
+ m_nextState = NNTP_ERROR;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ } /* end big while */
+
+ return NS_OK; /* keep going */
+}
+
+NS_IMETHODIMP nsNNTPProtocol::CloseConnection()
+{
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) ClosingConnection",this));
+ SendData(NNTP_CMD_QUIT); // this will cause OnStopRequest get called, which will call CloseSocket()
+ // break some cycles
+ CleanupNewsgroupList();
+
+ if (m_nntpServer) {
+ m_nntpServer->RemoveConnection(this);
+ m_nntpServer = nullptr;
+ }
+ CloseSocket();
+ m_newsFolder = nullptr;
+
+ if (m_articleList) {
+ m_articleList->FinishAddingArticleKeys();
+ m_articleList = nullptr;
+ }
+
+ m_key = nsMsgKey_None;
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::CleanupNewsgroupList()
+{
+ nsresult rv;
+ if (!m_newsgroupList) return NS_OK;
+ int32_t status = 0;
+ rv = m_newsgroupList->FinishXOVERLINE(0,&status);
+ m_newsgroupList = nullptr;
+ NS_ASSERTION(NS_SUCCEEDED(rv), "FinishXOVERLINE failed");
+ return rv;
+}
+
+nsresult nsNNTPProtocol::CleanupAfterRunningUrl()
+{
+ /* do we need to know if we're parsing xover to call finish xover? */
+ /* yes, I think we do! Why did I think we should??? */
+ /* If we've gotten to NEWS_FREE and there is still XOVER
+ data, there was an error or we were interrupted or
+ something. So, tell libmsg there was an abnormal
+ exit so that it can free its data. */
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) CleanupAfterRunningUrl()", this));
+
+ // send StopRequest notification after we've cleaned up the protocol
+ // because it can synchronously causes a new url to get run in the
+ // protocol - truly evil, but we're stuck at the moment.
+ if (m_channelListener)
+ (void) m_channelListener->OnStopRequest(this, m_channelContext, NS_OK);
+
+ if (m_loadGroup)
+ (void) m_loadGroup->RemoveRequest(static_cast<nsIRequest *>(this), nullptr, NS_OK);
+ CleanupNewsgroupList();
+
+ // clear out mem cache entry so we're not holding onto it.
+ if (m_runningURL)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (mailnewsurl)
+ {
+ mailnewsurl->SetUrlState(false, NS_OK);
+ mailnewsurl->SetMemCacheEntry(nullptr);
+ }
+ }
+
+ Cleanup();
+
+ mDisplayInputStream = nullptr;
+ mDisplayOutputStream = nullptr;
+ mProgressEventSink = nullptr;
+ SetOwner(nullptr);
+
+ m_channelContext = nullptr;
+ m_channelListener = nullptr;
+ m_loadGroup = nullptr;
+ mCallbacks = nullptr;
+
+ // disable timeout before caching.
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
+ if (strans)
+ strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, PR_UINT32_MAX);
+
+ // don't mark ourselves as not busy until we are done cleaning up the connection. it should be the
+ // last thing we do.
+ SetIsBusy(false);
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::CloseSocket()
+{
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) ClosingSocket()",this));
+
+ if (m_nntpServer) {
+ m_nntpServer->RemoveConnection(this);
+ m_nntpServer = nullptr;
+ }
+
+ CleanupAfterRunningUrl(); // is this needed?
+ return nsMsgProtocol::CloseSocket();
+}
+
+void nsNNTPProtocol::SetProgressBarPercent(uint32_t aProgress, uint32_t aProgressMax)
+{
+ // XXX 64-bit
+ if (mProgressEventSink)
+ mProgressEventSink->OnProgress(this, m_channelContext, uint64_t(aProgress),
+ uint64_t(aProgressMax));
+}
+
+nsresult
+nsNNTPProtocol::SetProgressStatus(const char16_t *aMessage)
+{
+ nsresult rv = NS_OK;
+ if (mProgressEventSink)
+ rv = mProgressEventSink->OnStatus(this, m_channelContext, NS_OK, aMessage);
+ return rv;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::GetContentType(nsACString &aContentType)
+{
+
+ // if we've been set with a content type, then return it....
+ // this happens when we go through libmime now as it sets our new content type
+ if (!mContentType.IsEmpty())
+ {
+ aContentType = mContentType;
+ return NS_OK;
+ }
+
+ // otherwise do what we did before...
+
+ if (m_typeWanted == GROUP_WANTED)
+ aContentType.AssignLiteral("x-application-newsgroup");
+ else if (m_typeWanted == IDS_WANTED)
+ aContentType.AssignLiteral("x-application-newsgroup-listids");
+ else
+ aContentType.AssignLiteral("message/rfc822");
+ return NS_OK;
+}
+
+nsresult
+nsNNTPProtocol::AlertError(int32_t errorCode, const char *text)
+{
+ nsresult rv = NS_OK;
+
+ // get the prompt from the running url....
+ if (m_runningURL) {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(m_runningURL));
+ nsCOMPtr<nsIPrompt> dialog;
+ rv = GetPromptDialogFromUrl(msgUrl, getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString alertText;
+ rv = GetNewsStringByID(MK_NNTP_ERROR_MESSAGE, getter_Copies(alertText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (text) {
+ alertText.Append(' ');
+ alertText.Append(NS_ConvertASCIItoUTF16(text));
+ }
+ rv = dialog->Alert(nullptr, alertText.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::GetCurrentFolder(nsIMsgFolder **aFolder)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ NS_ENSURE_ARG_POINTER(aFolder);
+ if (m_newsFolder)
+ rv = m_newsFolder->QueryInterface(NS_GET_IID(nsIMsgFolder), (void **) aFolder);
+ return rv;
+}
+
diff --git a/mailnews/news/src/nsNNTPProtocol.h b/mailnews/news/src/nsNNTPProtocol.h
new file mode 100644
index 000000000..198bfb96a
--- /dev/null
+++ b/mailnews/news/src/nsNNTPProtocol.h
@@ -0,0 +1,509 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNNTPProtocol_h___
+#define nsNNTPProtocol_h___
+
+#include "nsMsgProtocol.h"
+
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsINntpUrl.h"
+#include "nsINntpIncomingServer.h"
+#include "nsINNTPProtocol.h"
+
+#include "nsINNTPNewsgroupList.h"
+#include "nsINNTPArticleList.h"
+#include "nsIMsgAsyncPrompter.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIMsgWindow.h"
+
+#include "nsMsgLineBuffer.h"
+#include "nsIStringBundle.h"
+#include "nsITimer.h"
+#include "nsICacheEntryOpenCallback.h"
+
+// this is only needed as long as our libmime hack is in place
+#include "prio.h"
+
+// State Flags (Note, I use the word state in terms of storing
+// state information about the connection (authentication, have we sent
+// commands, etc. I do not intend it to refer to protocol state)
+
+#define NNTP_PAUSE_FOR_READ 0x00000001 /* should we pause for the next read */
+#define NNTP_PROXY_AUTH_REQUIRED 0x00000002 /* is auth required */
+#define NNTP_SENT_PROXY_AUTH 0x00000004 /* have we sent a proxy auth? */
+#define NNTP_READER_PERFORMED 0x00000010 /* have we sent any cmds to the server yet? */
+#define NNTP_USE_FANCY_NEWSGROUP 0x00000020 /* use LIST XACTIVE or LIST */
+#define NNTP_DESTROY_PROGRESS_GRAPH 0x00000040 /* do we need to destroy graph progress */
+#define NNTP_SOME_PROTOCOL_SUCCEEDED 0x0000080 /* some protocol has suceeded so don't kill the connection */
+#define NNTP_NO_XOVER_SUPPORT 0x00000100 /* xover command is not supported here */
+
+/* states of the machine
+ */
+typedef enum _StatesEnum {
+NNTP_RESPONSE,
+#ifdef BLOCK_UNTIL_AVAILABLE_CONNECTION
+NNTP_BLOCK_UNTIL_CONNECTIONS_ARE_AVAILABLE,
+NNTP_CONNECTIONS_ARE_AVAILABLE,
+#endif
+NNTP_CONNECT,
+NNTP_CONNECT_WAIT,
+NNTP_LOGIN_RESPONSE,
+NNTP_SEND_MODE_READER,
+NNTP_SEND_MODE_READER_RESPONSE,
+SEND_LIST_EXTENSIONS,
+SEND_LIST_EXTENSIONS_RESPONSE,
+SEND_LIST_SEARCHES,
+SEND_LIST_SEARCHES_RESPONSE,
+NNTP_LIST_SEARCH_HEADERS,
+NNTP_LIST_SEARCH_HEADERS_RESPONSE,
+NNTP_GET_PROPERTIES,
+NNTP_GET_PROPERTIES_RESPONSE,
+SEND_LIST_SUBSCRIPTIONS,
+SEND_LIST_SUBSCRIPTIONS_RESPONSE,
+SEND_FIRST_NNTP_COMMAND,
+SEND_FIRST_NNTP_COMMAND_RESPONSE,
+SETUP_NEWS_STREAM,
+NNTP_BEGIN_AUTHORIZE,
+NNTP_AUTHORIZE_RESPONSE,
+NNTP_PASSWORD_RESPONSE,
+NNTP_READ_LIST_BEGIN,
+NNTP_READ_LIST,
+DISPLAY_NEWSGROUPS,
+NNTP_NEWGROUPS_BEGIN,
+NNTP_NEWGROUPS,
+NNTP_BEGIN_ARTICLE,
+NNTP_READ_ARTICLE,
+NNTP_XOVER_BEGIN,
+NNTP_FIGURE_NEXT_CHUNK,
+NNTP_XOVER_SEND,
+NNTP_XOVER_RESPONSE,
+NNTP_XOVER,
+NEWS_PROCESS_XOVER,
+NNTP_XHDR_SEND,
+NNTP_XHDR_RESPONSE,
+NNTP_READ_GROUP,
+NNTP_READ_GROUP_RESPONSE,
+NNTP_READ_GROUP_BODY,
+NNTP_SEND_GROUP_FOR_ARTICLE,
+NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE,
+NNTP_SEND_ARTICLE_NUMBER,
+NEWS_PROCESS_BODIES,
+NNTP_PRINT_ARTICLE_HEADERS,
+NNTP_SEND_POST_DATA,
+NNTP_SEND_POST_DATA_RESPONSE,
+NNTP_CHECK_FOR_MESSAGE,
+NEWS_START_CANCEL,
+NEWS_DO_CANCEL,
+NNTP_XPAT_SEND,
+NNTP_XPAT_RESPONSE,
+NNTP_SEARCH,
+NNTP_SEARCH_RESPONSE,
+NNTP_SEARCH_RESULTS,
+NNTP_LIST_PRETTY_NAMES,
+NNTP_LIST_PRETTY_NAMES_RESPONSE,
+NNTP_LIST_XACTIVE,
+NNTP_LIST_XACTIVE_RESPONSE,
+NNTP_LIST_GROUP,
+NNTP_LIST_GROUP_RESPONSE,
+NEWS_DONE,
+NEWS_POST_DONE,
+NEWS_ERROR,
+NNTP_ERROR,
+NEWS_FREE,
+NNTP_SUSPENDED
+} StatesEnum;
+
+class nsICacheEntry;
+
+class nsNNTPProtocol : public nsMsgProtocol,
+ public nsINNTPProtocol,
+ public nsITimerCallback,
+ public nsICacheEntryOpenCallback,
+ public nsIMsgAsyncPromptListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINNTPPROTOCOL
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIMSGASYNCPROMPTLISTENER
+
+ // Creating a protocol instance requires the URL
+ // need to call Initialize after we do a new of nsNNTPProtocol
+ nsNNTPProtocol(nsINntpIncomingServer *aServer, nsIURI *aURL,
+ nsIMsgWindow *aMsgWindow);
+
+ // stop binding is a "notification" informing us that the stream associated with aURL is going away.
+ NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports * aCtxt, nsresult aStatus) override;
+
+ char * m_ProxyServer; /* proxy server hostname */
+
+ NS_IMETHOD Cancel(nsresult status) override; // handle stop button
+ NS_IMETHOD GetContentType(nsACString &aContentType) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) override;
+ NS_IMETHOD AsyncOpen2(nsIStreamListener *listener) override;
+ NS_IMETHOD GetOriginalURI(nsIURI* *aURI) override;
+ NS_IMETHOD SetOriginalURI(nsIURI* aURI) override;
+
+ nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer) override;
+
+private:
+ virtual ~nsNNTPProtocol();
+ /**
+ * Triggers the protocol state machine.
+ * Most of the time, this machine will read as much input as it can before
+ * closing.
+ *
+ * This method additionally handles some states not covered by other methods:
+ * NEWS_DONE: Alias for NEWS_FREE
+ * NEWS_POST_DONE: Alias for NEWS_FREE that cleans up the URL state
+ * NEWS_ERROR: An error which permits further use of the connection
+ * NNTP_ERROR: An error which does not permit further use of the connection
+ * NEWS_FREE: Cleans up from the current URL and prepares for the next one
+ * NNTP_SUSPENDED: A state where the state machine does not read input until
+ * reenabled by a non-network related callback
+ *
+ * @note Use of NNTP_SUSPENDED is dangerous: if input comes along the socket,
+ * the code will not read the input stream at all. Therefore, it is strongly
+ * advised to suspend the request before using this state.
+ */
+ virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length) override;
+ virtual nsresult CloseSocket() override;
+
+ // we have our own implementation of SendData which writes to the nntp log
+ // and then calls the base class to transmit the data
+ nsresult SendData(const char * dataBuffer, bool aSuppressLogging = false) override;
+
+ nsresult CleanupAfterRunningUrl();
+ void Cleanup(); //free char* member variables
+
+ void ParseHeaderForCancel(char *buf);
+
+ virtual const char* GetType() override { return "nntp"; }
+
+ static void CheckIfAuthor(nsIMsgIdentity *aIdentity, const nsCString &aOldFrom, nsCString &aFrom);
+
+ nsCOMPtr <nsINNTPNewsgroupList> m_newsgroupList;
+ nsCOMPtr <nsINNTPArticleList> m_articleList;
+
+ nsCOMPtr <nsIMsgNewsFolder> m_newsFolder;
+ nsCOMPtr <nsIMsgWindow> m_msgWindow;
+
+ nsCOMPtr<nsIAsyncInputStream> mDisplayInputStream;
+ nsCOMPtr<nsIAsyncOutputStream> mDisplayOutputStream;
+ RefPtr<nsMsgLineStreamBuffer> m_lineStreamBuffer; // used to efficiently extract lines from the incoming data stream
+ // the nsINntpURL that is currently running
+ nsCOMPtr<nsINntpUrl> m_runningURL;
+ bool m_connectionBusy;
+ bool m_fromCache; // is this connection from the cache?
+ PRTime m_lastActiveTimeStamp;
+ nsNewsAction m_newsAction;
+
+ // Generic state information -- What state are we in? What state do we want to go to
+ // after the next response? What was the last response code? etc.
+ StatesEnum m_nextState;
+ StatesEnum m_nextStateAfterResponse;
+ int32_t m_typeWanted; /* Article, List, or Group */
+ int32_t m_responseCode; /* code returned from NNTP server */
+ int32_t m_previousResponseCode;
+ char *m_responseText; /* text returned from NNTP server */
+
+ char *m_dataBuf;
+ uint32_t m_dataBufSize;
+
+ /* for group command */
+ nsCString m_currentGroup; /* current group */
+
+ int32_t m_firstArticle;
+ int32_t m_lastArticle;
+ int32_t m_firstPossibleArticle;
+ int32_t m_lastPossibleArticle;
+
+ int32_t m_numArticlesLoaded; /* How many articles we got XOVER lines for. */
+ int32_t m_numArticlesWanted; /* How many articles we wanted to get XOVER lines for. */
+ int32_t m_maxArticles; /* max articles to get during an XOVER */
+
+ // Cancelation specific state. In particular, the headers that should be
+ // used for the cancelation message.
+ nsCString m_cancelFromHdr;
+ nsCString m_cancelNewsgroups;
+ nsCString m_cancelDistribution;
+ nsCString m_cancelID;
+ int32_t m_cancelStatus;
+
+ // variable for ReadNewsList
+ int32_t m_readNewsListCount;
+
+ // Per news article state information. (article number, author, subject, id, etc
+ nsCString m_messageID;
+ int32_t m_articleNumber; /* current article number */
+ nsCString m_searchData;
+
+ int32_t m_originalContentLength; /* the content length at the time of calling graph progress */
+
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+
+ nsCOMPtr<nsINntpIncomingServer> m_nntpServer;
+
+ nsresult GetNewsStringByName(const char *aName, char16_t **aString);
+ nsresult GetNewsStringByID(int32_t stringID, char16_t **aString);
+
+ nsresult PostMessageInFile(nsIFile * filePath);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Communication methods --> Reading and writing protocol
+ //////////////////////////////////////////////////////////////////////////////
+
+ int32_t ReadLine(nsIInputStream * inputStream, uint32_t length, char ** line);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Protocol Methods --> This protocol is state driven so each protocol method
+ // is designed to re-act to the current "state". I've attempted to
+ // group them together based on functionality.
+ //////////////////////////////////////////////////////////////////////////////
+
+ // gets the response code from the nntp server and the response line. Returns the TCP return code
+ // from the read.
+ nsresult NewsResponse(nsIInputStream *inputStream, uint32_t length);
+
+ // Interpret the server response after the connect.
+ // Returns negative if the server responds unexpectedly
+ nsresult LoginResponse();
+ nsresult SendModeReader();
+ nsresult SendModeReaderResponse();
+
+ nsresult SendListExtensions();
+ nsresult SendListExtensionsResponse(nsIInputStream *inputStream, uint32_t length);
+
+ nsresult SendListSearches();
+ nsresult SendListSearchesResponse(nsIInputStream *inputStream, uint32_t length);
+
+ nsresult SendListSearchHeaders();
+ nsresult SendListSearchHeadersResponse(nsIInputStream *inputStream, uint32_t length);
+
+ nsresult GetProperties();
+ nsresult GetPropertiesResponse(nsIInputStream *inputStream, uint32_t length);
+
+ nsresult SendListSubscriptions();
+ nsresult SendListSubscriptionsResponse(nsIInputStream *inputStream, uint32_t length);
+
+ // Figure out what the first command is and send it.
+ // Returns the status from the NETWrite.
+ nsresult SendFirstNNTPCommand(nsIURI *url);
+
+ // Interprets the server response from the first command sent.
+ // returns negative if the server responds unexpectedly.
+ nsresult SendFirstNNTPCommandResponse();
+
+ nsresult SetupForTransfer();
+
+ nsresult SendGroupForArticle();
+ nsresult SendGroupForArticleResponse();
+
+ nsresult SendArticleNumber();
+ nsresult BeginArticle();
+ nsresult ReadArticle(nsIInputStream *inputStream, uint32_t length);
+ nsresult DisplayArticle(nsIInputStream *inputStream, uint32_t length);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // News authentication code
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Sends the username via AUTHINFO USER, NNTP_BEGIN_AUTHORIZE.
+ * This also handles the step of getting authentication credentials; if this
+ * requires a prompt to run, the connection will be suspended and we will
+ * come back to this point.
+ * Followed by: NNTP_AUTHORIZE_RESPONSE if the username was sent
+ * NNTP_SUSPENDED if we need to wait for the password
+ */
+ nsresult BeginAuthorization();
+ /**
+ * Sends the password if necessary, the state NNTP_AUTHORIZE_RESPONSE.
+ * This also reads the result of the username.
+ * Followed by: NNTP_PASSWORD_RESPONSE if a password is sent
+ * NNTP_SEND_MODE_READER if MODE READER needed auth
+ * SEND_FIRST_NNTP_COMMAND if any other command needed auth
+ * NNTP_ERROR if the username was rejected
+ */
+ nsresult AuthorizationResponse();
+ /**
+ * This state, NNTP_PASSWORD_RESPONSE, reads the password.
+ * Followed by: NNTP_SEND_MODE_READER if MODE READER needed auth
+ * SEND_FIRST_NNTP_COMMAND if any other command needed auth
+ * NNTP_ERROR if the password was rejected
+ */
+ nsresult PasswordResponse();
+
+ nsresult BeginReadNewsList();
+ nsresult ReadNewsList(nsIInputStream *inputStream, uint32_t length);
+
+ // Newsgroup specific protocol handlers
+ nsresult DisplayNewsgroups();
+ nsresult BeginNewsgroups();
+ nsresult ProcessNewsgroups(nsIInputStream *inputStream, uint32_t length);
+
+ // Protocol handlers used for posting data
+ nsresult PostData();
+ nsresult PostDataResponse();
+
+ nsresult CheckForArticle();
+
+ /////////////////////////////////////////////////////////////////////////////
+ // XHDR, XOVER, HEAD filtering process handlers
+ // These are ordered by the rough order of usage
+ /////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * The first step in the filtering process, the state NNTP_XOVER_BEGIN.
+ * This method sets up m_newsgroupList.
+ * Followed by: NNTP_FIGURE_NEXT_CHUNK
+ */
+ nsresult BeginReadXover();
+ /**
+ * The loop control for filtering, the state NNTP_FIGURE_NEXT_CHUNK.
+ * This method contacts the newsgroupList to figure out which articles to
+ * download and then prepares it for XOVER support.
+ * Followed by: NEWS_PROCESS_XOVER if everything is finished
+ * NNTP_READ_GROUP if XOVER doesn't work
+ * NNTP_XOVER_SEND if XOVER does work
+ */
+ nsresult FigureNextChunk();
+
+ // The XOVER process core
+ /**
+ * The state NNTP_XOVER_SEND, which actually sends the message.
+ * Followed by: NNTP_XOVER_RESPONSE
+ */
+ nsresult XoverSend();
+ /**
+ * This state, NNTP_XOVER_RESPONSE, actually checks the XOVER capabiliity.
+ * Followed by: NNTP_XOVER if XOVER is supported
+ * NNTP_READ_GROUP if it isn't
+ */
+ nsresult ReadXoverResponse();
+ /**
+ * This state, NNTP_XOVER, processes the results from the XOVER command.
+ * It asks nsNNTPNewsgroupList to process the line using ProcessXOVERLINE.
+ * Followed by: NNTP_XHDR_SEND
+ */
+ nsresult ReadXover(nsIInputStream *inputStream, uint32_t length);
+
+ // The XHDR process core
+ /**
+ * This state, NNTP_XHDR_SEND, sends the XHDR command.
+ * The headers are all managed by nsNNTPNewsgroupList, and this picks them up
+ * one by one as they are needed.
+ * Followed by: NNTP_XHDR_RESPONSE if there is a header to be sent
+ * NNTP_FIGURE_NEXT_CHUNK if all headers have been sent
+ */
+ nsresult XhdrSend();
+ /**
+ * This state, NNTP_XHDR_RESPONSE, processes the XHDR response.
+ * It mostly passes the information off to nsNNTPNewsgroupList, and only does
+ * response code checking and a bit of preprocessing. Note that if XHDR
+ * doesn't work properly, HEAD fallback is switched on and all subsequent
+ * chunks will NOT use XOVER.
+ * Followed by: NNTP_READ_GROUP if XHDR doesn't work properly
+ * NNTP_XHDR_SEND when finished processing XHR.
+ */
+ nsresult XhdrResponse(nsIInputStream *inputStream);
+
+ // HEAD processing core
+ /**
+ * This state, NNTP_READ_GROUP, is the control for the HEAD processor.
+ * It sends the HEAD command and increments the article number until it is
+ * finished. WARNING: HEAD is REALLY SLOW.
+ * Followed by: NNTP_FIGURE_NEXT_CHUNK when it is finished
+ * NNTP_READ_GROUP_RESPONSE when it is not
+ */
+ nsresult ReadHeaders();
+ /**
+ * This state, NNTP_READ_GROUP_RESPONSE, checks if the article exists.
+ * Because it is required by NNTP, if it doesn't work, the only problem would
+ * be that the article doesn't exist. Passes off article number data to
+ * nsNNTPNewsgroupList.
+ * Followed by: NNTP_READ_GROUP_BODY if the article exists
+ * NNTP_READ_GROUP if it doesn't.
+ */
+ nsresult ReadNewsgroupResponse();
+ /**
+ * This state, NNTP_READ_GROUP_BODY, reads the body of the HEAD command.
+ * Once again, it passes information off to nsNNTPNewsgroupList.
+ * Followed by: NNTP_READ_GROUP
+ */
+ nsresult ReadNewsgroupBody(nsIInputStream *inputStream, uint32_t length);
+
+ /**
+ * This state, NNTP_PROCESS_XOVER, is the final step of the filter-processing
+ * code. Currently, all it does is cleans up the unread count and calls the
+ * filters, both via nsNNTPNewsgroupList.
+ * Followed by: NEWS_DONE
+ */
+ nsresult ProcessXover();
+
+
+
+ // Canceling
+ nsresult StartCancel();
+ nsresult DoCancel();
+
+ // XPAT
+ nsresult XPATSend();
+ nsresult XPATResponse(nsIInputStream *inputStream, uint32_t length);
+ nsresult ListPrettyNames();
+ nsresult ListPrettyNamesResponse(nsIInputStream *inputStream, uint32_t length);
+
+ nsresult ListXActive();
+ nsresult ListXActiveResponse(nsIInputStream *inputStream, uint32_t length);
+
+ // for "?list-ids"
+ nsresult SendListGroup();
+ nsresult SendListGroupResponse(nsIInputStream *inputStream, uint32_t length);
+
+ // Searching Protocol....
+ nsresult Search();
+ nsresult SearchResponse();
+ nsresult SearchResults(nsIInputStream *inputStream, uint32_t length);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // End of Protocol Methods
+ //////////////////////////////////////////////////////////////////////////////
+
+ nsresult ParseURL(nsIURI *aURL, nsCString &aGroup, nsCString &aMessageID);
+
+ void SetProgressBarPercent(uint32_t aProgress, uint32_t aProgressMax);
+ nsresult SetProgressStatus(const char16_t *aMessage);
+ nsresult InitializeNewsFolderFromUri(const char *uri);
+ void TimerCallback();
+
+ void HandleAuthenticationFailure();
+ nsCOMPtr <nsIInputStream> mInputStream;
+ nsCOMPtr <nsITimer> mUpdateTimer;
+ nsresult AlertError(int32_t errorCode, const char *text);
+ int32_t mBytesReceived;
+ int32_t mBytesReceivedSinceLastStatusUpdate;
+ PRTime m_startTime;
+ int32_t mNumGroupsListed;
+ nsMsgKey m_key;
+
+ nsresult SetCurrentGroup(); /* sets m_currentGroup. should be called after doing a successful GROUP command */
+ nsresult CleanupNewsgroupList(); /* cleans up m_newsgroupList, and set it to null */
+
+ // cache related helper methods
+ void FinishMemCacheEntry(bool valid); // either mark it valid, or doom it
+ nsresult OpenCacheEntry(); // makes a request to the cache service for a cache entry for a url
+ bool ReadFromLocalCache(); // attempts to read the url out of our local (offline) cache....
+ nsresult ReadFromNewsConnection(); // creates a new news connection to read the url
+ nsresult ReadFromMemCache(nsICacheEntry *entry); // attempts to read the url out of our memory cache
+ nsresult SetupPartExtractorListener(nsIStreamListener * aConsumer);
+};
+
+
+#endif // nsNNTPProtocol_h___
diff --git a/mailnews/news/src/nsNewsAutoCompleteSearch.js b/mailnews/news/src/nsNewsAutoCompleteSearch.js
new file mode 100644
index 000000000..335c32cba
--- /dev/null
+++ b/mailnews/news/src/nsNewsAutoCompleteSearch.js
@@ -0,0 +1,141 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var kACR = Ci.nsIAutoCompleteResult;
+var kSupportedTypes = new Set(["addr_newsgroups", "addr_followup"]);
+
+function nsNewsAutoCompleteResult(aSearchString) {
+ // Can't create this in the prototype as we'd get the same array for
+ // all instances
+ this._searchResults = [];
+ this.searchString = aSearchString;
+}
+
+nsNewsAutoCompleteResult.prototype = {
+ _searchResults: null,
+
+ // nsIAutoCompleteResult
+
+ searchString: null,
+ searchResult: kACR.RESULT_NOMATCH,
+ defaultIndex: -1,
+ errorDescription: null,
+
+ get matchCount() {
+ return this._searchResults.length;
+ },
+
+ getValueAt: function getValueAt(aIndex) {
+ return this._searchResults[aIndex].value;
+ },
+
+ getLabelAt: function getLabelAt(aIndex) {
+ return this._searchResults[aIndex].value;
+ },
+
+ getCommentAt: function getCommentAt(aIndex) {
+ return this._searchResults[aIndex].comment;
+ },
+
+ getStyleAt: function getStyleAt(aIndex) {
+ return "subscribed-news";
+ },
+
+ getImageAt: function getImageAt(aIndex) {
+ return "";
+ },
+
+ getFinalCompleteValueAt: function(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ removeValueAt: function removeValueAt(aRowIndex, aRemoveFromDB) {
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([kACR])
+}
+
+function nsNewsAutoCompleteSearch() {}
+
+nsNewsAutoCompleteSearch.prototype = {
+ // For component registration
+ classDescription: "Newsgroup Autocomplete",
+ classID: Components.ID("e9bb3330-ac7e-11de-8a39-0800200c9a66"),
+
+ cachedAccountKey: "",
+ cachedServer: null,
+
+ /**
+ * Find the newsgroup server associated with the given accountKey.
+ *
+ * @param accountKey The key of the account.
+ * @return The incoming news server (or null if one does not exist).
+ */
+ _findServer: function _findServer(accountKey) {
+ let account = MailServices.accounts.getAccount(accountKey);
+
+ if (account.incomingServer.type == 'nntp')
+ return account.incomingServer;
+ else
+ return null;
+ },
+
+ // nsIAutoCompleteSearch
+ startSearch: function startSearch(aSearchString, aSearchParam,
+ aPreviousResult, aListener) {
+ let params = aSearchParam ? JSON.parse(aSearchParam) : {};
+ let result = new nsNewsAutoCompleteResult(aSearchString);
+ if (!("type" in params) || !("accountKey" in params) ||
+ !kSupportedTypes.has(params.type)) {
+ result.searchResult = kACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, result);
+ return;
+ }
+
+ if (("accountKey" in params) && (params.accountKey != this.cachedAccountKey)) {
+ this.cachedAccountKey = params.accountKey;
+ this.cachedServer = this._findServer(params.accountKey);
+ }
+
+ if (this.cachedServer) {
+ let groups = this.cachedServer.rootFolder.subFolders;
+ while (groups.hasMoreElements()) {
+ let curr = groups.getNext().QueryInterface(Ci.nsIMsgFolder);
+ if (curr.prettiestName.includes(aSearchString)) {
+ result._searchResults.push({
+ value: curr.prettiestName,
+ comment: this.cachedServer.prettyName
+ });
+ }
+ }
+ }
+
+ if (result.matchCount) {
+ result.searchResult = kACR.RESULT_SUCCESS;
+ // If the user does not select anything, use the first entry:
+ result.defaultIndex = 0;
+ }
+ aListener.onSearchResult(this, result);
+ },
+
+ stopSearch: function stopSearch() {
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch])
+};
+
+// Module
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsNewsAutoCompleteSearch]);
diff --git a/mailnews/news/src/nsNewsAutoCompleteSearch.manifest b/mailnews/news/src/nsNewsAutoCompleteSearch.manifest
new file mode 100644
index 000000000..7a90b8074
--- /dev/null
+++ b/mailnews/news/src/nsNewsAutoCompleteSearch.manifest
@@ -0,0 +1,2 @@
+component {e9bb3330-ac7e-11de-8a39-0800200c9a66} nsNewsAutoCompleteSearch.js
+contract @mozilla.org/autocomplete/search;1?name=news {e9bb3330-ac7e-11de-8a39-0800200c9a66}
diff --git a/mailnews/news/src/nsNewsDownloadDialogArgs.cpp b/mailnews/news/src/nsNewsDownloadDialogArgs.cpp
new file mode 100644
index 000000000..277f635b0
--- /dev/null
+++ b/mailnews/news/src/nsNewsDownloadDialogArgs.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNewsDownloadDialogArgs.h"
+
+nsNewsDownloadDialogArgs::nsNewsDownloadDialogArgs()
+{
+ mArticleCount = 0;
+ mServerKey = "";
+ mHitOK = false;
+ mDownloadAll = false;
+}
+
+nsNewsDownloadDialogArgs::~nsNewsDownloadDialogArgs()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsNewsDownloadDialogArgs, nsINewsDownloadDialogArgs)
+
+NS_IMETHODIMP nsNewsDownloadDialogArgs::GetGroupName(nsAString & aGroupName)
+ {
+ aGroupName = mGroupName;
+
+ return NS_OK;
+ }
+NS_IMETHODIMP nsNewsDownloadDialogArgs::SetGroupName(const nsAString & aGroupName)
+ {
+
+ mGroupName = aGroupName;
+
+ return NS_OK;
+ }
+NS_IMETHODIMP nsNewsDownloadDialogArgs::GetArticleCount(int32_t *aArticleCount)
+{
+ NS_ENSURE_ARG_POINTER(aArticleCount);
+
+ *aArticleCount = mArticleCount;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::SetArticleCount(int32_t aArticleCount)
+{
+ mArticleCount = aArticleCount;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::GetServerKey(char * *aServerKey)
+{
+ NS_ENSURE_ARG_POINTER(aServerKey);
+
+ *aServerKey = ToNewCString(mServerKey);
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::SetServerKey(const char * aServerKey)
+{
+ NS_ENSURE_ARG_POINTER(aServerKey);
+
+ mServerKey = aServerKey;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::GetHitOK(bool *aHitOK)
+{
+ NS_ENSURE_ARG_POINTER(aHitOK);
+
+ *aHitOK = mHitOK;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::SetHitOK(bool aHitOK)
+{
+ mHitOK = aHitOK;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::GetDownloadAll(bool *aDownloadAll)
+{
+ NS_ENSURE_ARG_POINTER(aDownloadAll);
+
+ *aDownloadAll = mDownloadAll;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::SetDownloadAll(bool aDownloadAll)
+{
+ mDownloadAll = aDownloadAll;
+
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNewsDownloadDialogArgs.h b/mailnews/news/src/nsNewsDownloadDialogArgs.h
new file mode 100644
index 000000000..5ec8e0df6
--- /dev/null
+++ b/mailnews/news/src/nsNewsDownloadDialogArgs.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNewsDownloadDialogArgs_h__
+#define nsNewsDownloadDialogArgs_h__
+
+#include "nsINewsDownloadDialogArgs.h"
+#include "nsStringGlue.h"
+
+class nsNewsDownloadDialogArgs : public nsINewsDownloadDialogArgs
+{
+public:
+ nsNewsDownloadDialogArgs();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINEWSDOWNLOADDIALOGARGS
+
+private:
+ virtual ~nsNewsDownloadDialogArgs();
+
+ nsString mGroupName;
+ int32_t mArticleCount;
+ nsCString mServerKey;
+ bool mHitOK;
+ bool mDownloadAll;
+};
+
+#endif // nsNewsDownloadDialogArgs_h__
diff --git a/mailnews/news/src/nsNewsDownloader.cpp b/mailnews/news/src/nsNewsDownloader.cpp
new file mode 100644
index 000000000..0794e30e7
--- /dev/null
+++ b/mailnews/news/src/nsNewsDownloader.cpp
@@ -0,0 +1,586 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nntpCore.h"
+#include "netCore.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIStringBundle.h"
+#include "nsNewsDownloader.h"
+#include "nsINntpService.h"
+#include "nsMsgNewsCID.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgSearchValidityManager.h"
+#include "nsRDFCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIRequestObserver.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+
+// This file contains the news article download state machine.
+
+// if pIds is not null, download the articles whose id's are passed in. Otherwise,
+// which articles to download is determined by nsNewsDownloader object,
+// or subclasses thereof. News can download marked objects, for example.
+nsresult nsNewsDownloader::DownloadArticles(nsIMsgWindow *window, nsIMsgFolder *folder, nsTArray<nsMsgKey> *pIds)
+{
+ if (pIds != nullptr)
+ m_keysToDownload.InsertElementsAt(0, pIds->Elements(), pIds->Length());
+
+ if (!m_keysToDownload.IsEmpty())
+ m_downloadFromKeys = true;
+
+ m_folder = folder;
+ m_window = window;
+ m_numwrote = 0;
+
+ bool headersToDownload = GetNextHdrToRetrieve();
+ // should we have a special error code for failure here?
+ return (headersToDownload) ? DownloadNext(true) : NS_ERROR_FAILURE;
+}
+
+/* Saving news messages
+ */
+
+NS_IMPL_ISUPPORTS(nsNewsDownloader, nsIUrlListener, nsIMsgSearchNotify)
+
+nsNewsDownloader::nsNewsDownloader(nsIMsgWindow *window, nsIMsgDatabase *msgDB, nsIUrlListener *listener)
+{
+ m_numwrote = 0;
+ m_downloadFromKeys = false;
+ m_newsDB = msgDB;
+ m_abort = false;
+ m_listener = listener;
+ m_window = window;
+ m_lastPercent = -1;
+ m_lastProgressTime = 0;
+ // not the perfect place for this, but I think it will work.
+ if (m_window)
+ m_window->SetStopped(false);
+}
+
+nsNewsDownloader::~nsNewsDownloader()
+{
+ if (m_listener)
+ m_listener->OnStopRunningUrl(/* don't have a url */nullptr, m_status);
+ if (m_newsDB)
+ {
+ m_newsDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ m_newsDB = nullptr;
+ }
+}
+
+NS_IMETHODIMP nsNewsDownloader::OnStartRunningUrl(nsIURI* url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNewsDownloader::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
+{
+ bool stopped = false;
+ if (m_window)
+ m_window->GetStopped(&stopped);
+ if (stopped)
+ exitCode = NS_BINDING_ABORTED;
+
+ nsresult rv = exitCode;
+ if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_NEWS_ARTICLE_NOT_FOUND)
+ rv = DownloadNext(false);
+
+ return rv;
+}
+
+nsresult nsNewsDownloader::DownloadNext(bool firstTimeP)
+{
+ nsresult rv;
+ if (!firstTimeP)
+ {
+ bool moreHeaders = GetNextHdrToRetrieve();
+ if (!moreHeaders)
+ {
+ if (m_listener)
+ m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ return NS_OK;
+ }
+ }
+ StartDownload();
+ m_wroteAnyP = false;
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nntpService->FetchMessage(m_folder, m_keyToDownload, m_window, nullptr, this, nullptr);
+}
+
+bool DownloadNewsArticlesToOfflineStore::GetNextHdrToRetrieve()
+{
+ nsresult rv;
+
+ if (m_downloadFromKeys)
+ return nsNewsDownloader::GetNextHdrToRetrieve();
+
+ if (m_headerEnumerator == nullptr)
+ rv = m_newsDB->EnumerateMessages(getter_AddRefs(m_headerEnumerator));
+
+ bool hasMore = false;
+
+ while (NS_SUCCEEDED(rv = m_headerEnumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = m_headerEnumerator->GetNext(getter_AddRefs(supports));
+ m_newsHeader = do_QueryInterface(supports);
+ NS_ENSURE_SUCCESS(rv, false);
+ uint32_t hdrFlags;
+ m_newsHeader->GetFlags(&hdrFlags);
+ if (hdrFlags & nsMsgMessageFlags::Marked)
+ {
+ m_newsHeader->GetMessageKey(&m_keyToDownload);
+ break;
+ }
+ else
+ {
+ m_newsHeader = nullptr;
+ }
+ }
+ return hasMore;
+}
+
+void nsNewsDownloader::Abort() {}
+void nsNewsDownloader::Complete() {}
+
+bool nsNewsDownloader::GetNextHdrToRetrieve()
+{
+ nsresult rv;
+ if (m_downloadFromKeys)
+ {
+ if (m_numwrote >= (int32_t) m_keysToDownload.Length())
+ return false;
+
+ m_keyToDownload = m_keysToDownload[m_numwrote++];
+ int32_t percent;
+ percent = (100 * m_numwrote) / (int32_t) m_keysToDownload.Length();
+
+ int64_t nowMS = 0;
+ if (percent < 100) // always need to do 100%
+ {
+ nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
+ if (nowMS - m_lastProgressTime < 750)
+ return true;
+ }
+
+ m_lastProgressTime = nowMS;
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_folder);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, false);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoString firstStr;
+ firstStr.AppendInt(m_numwrote);
+ nsAutoString totalStr;
+ totalStr.AppendInt(int(m_keysToDownload.Length()));
+ nsString prettiestName;
+ nsString statusString;
+
+ m_folder->GetPrettiestName(prettiestName);
+
+ const char16_t *formatStrings[3] = { firstStr.get(), totalStr.get(), prettiestName.get() };
+ rv = bundle->FormatStringFromName(u"downloadingArticlesForOffline",
+ formatStrings, 3, getter_Copies(statusString));
+ NS_ENSURE_SUCCESS(rv, false);
+ ShowProgress(statusString.get(), percent);
+ return true;
+ }
+ NS_ASSERTION(false, "shouldn't get here if we're not downloading from keys.");
+ return false; // shouldn't get here if we're not downloading from keys.
+}
+
+nsresult nsNewsDownloader::ShowProgress(const char16_t *progressString, int32_t percent)
+{
+ if (!m_statusFeedback)
+ {
+ if (m_window)
+ m_window->GetStatusFeedback(getter_AddRefs(m_statusFeedback));
+ }
+ if (m_statusFeedback)
+ {
+ m_statusFeedback->ShowStatusString(nsDependentString(progressString));
+ if (percent != m_lastPercent)
+ {
+ m_statusFeedback->ShowProgress(percent);
+ m_lastPercent = percent;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP DownloadNewsArticlesToOfflineStore::OnStartRunningUrl(nsIURI* url)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP DownloadNewsArticlesToOfflineStore::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
+{
+ m_status = exitCode;
+ if (m_newsHeader != nullptr)
+ {
+#ifdef DEBUG_bienvenu
+ // XP_Trace("finished retrieving %ld\n", m_newsHeader->GetMessageKey());
+#endif
+ if (m_newsDB)
+ {
+ nsMsgKey msgKey;
+ m_newsHeader->GetMessageKey(&msgKey);
+ m_newsDB->MarkMarked(msgKey, false, nullptr);
+ }
+ }
+ m_newsHeader = nullptr;
+ return nsNewsDownloader::OnStopRunningUrl(url, exitCode);
+}
+
+int DownloadNewsArticlesToOfflineStore::FinishDownload()
+{
+ return 0;
+}
+
+
+NS_IMETHODIMP nsNewsDownloader::OnSearchHit(nsIMsgDBHdr *header, nsIMsgFolder *folder)
+{
+ NS_ENSURE_ARG(header);
+
+
+ uint32_t msgFlags;
+ header->GetFlags(&msgFlags);
+ // only need to download articles we don't already have...
+ if (! (msgFlags & nsMsgMessageFlags::Offline))
+ {
+ nsMsgKey key;
+ header->GetMessageKey(&key);
+ m_keysToDownload.AppendElement(key);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNewsDownloader::OnSearchDone(nsresult status)
+{
+ if (m_keysToDownload.IsEmpty())
+ {
+ if (m_listener)
+ return m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ nsresult rv = DownloadArticles(m_window, m_folder,
+ /* we've already set m_keysToDownload, so don't pass it in */ nullptr);
+ if (NS_FAILED(rv))
+ if (m_listener)
+ m_listener->OnStopRunningUrl(nullptr, rv);
+
+ return rv;
+}
+NS_IMETHODIMP nsNewsDownloader::OnNewSearch()
+{
+ return NS_OK;
+}
+
+int DownloadNewsArticlesToOfflineStore::StartDownload()
+{
+ m_newsDB->GetMsgHdrForKey(m_keyToDownload, getter_AddRefs(m_newsHeader));
+ return 0;
+}
+
+DownloadNewsArticlesToOfflineStore::DownloadNewsArticlesToOfflineStore(nsIMsgWindow *window, nsIMsgDatabase *db, nsIUrlListener *listener)
+ : nsNewsDownloader(window, db, listener)
+{
+ m_newsDB = db;
+}
+
+DownloadNewsArticlesToOfflineStore::~DownloadNewsArticlesToOfflineStore()
+{
+}
+
+DownloadMatchingNewsArticlesToNewsDB::DownloadMatchingNewsArticlesToNewsDB
+ (nsIMsgWindow *window, nsIMsgFolder *folder, nsIMsgDatabase *newsDB,
+ nsIUrlListener *listener) :
+ DownloadNewsArticlesToOfflineStore(window, newsDB, listener)
+{
+ m_window = window;
+ m_folder = folder;
+ m_newsDB = newsDB;
+ m_downloadFromKeys = true; // search term matching means downloadFromKeys.
+}
+
+DownloadMatchingNewsArticlesToNewsDB::~DownloadMatchingNewsArticlesToNewsDB()
+{
+}
+
+
+NS_IMPL_ISUPPORTS(nsMsgDownloadAllNewsgroups, nsIUrlListener)
+
+
+nsMsgDownloadAllNewsgroups::nsMsgDownloadAllNewsgroups(nsIMsgWindow *window, nsIUrlListener *listener)
+{
+ m_window = window;
+ m_listener = listener;
+ m_downloaderForGroup = new DownloadMatchingNewsArticlesToNewsDB(window, nullptr, nullptr, this);
+ NS_IF_ADDREF(m_downloaderForGroup);
+ m_downloadedHdrsForCurGroup = false;
+}
+
+nsMsgDownloadAllNewsgroups::~nsMsgDownloadAllNewsgroups()
+{
+ NS_IF_RELEASE(m_downloaderForGroup);
+}
+
+NS_IMETHODIMP nsMsgDownloadAllNewsgroups::OnStartRunningUrl(nsIURI* url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDownloadAllNewsgroups::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
+{
+ nsresult rv = exitCode;
+ if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_NEWS_ARTICLE_NOT_FOUND)
+ {
+ if (m_downloadedHdrsForCurGroup)
+ {
+ bool savingArticlesOffline = false;
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
+ if (newsFolder)
+ newsFolder->GetSaveArticleOffline(&savingArticlesOffline);
+
+ m_downloadedHdrsForCurGroup = false;
+ if (savingArticlesOffline) // skip this group - we're saving to it already
+ rv = ProcessNextGroup();
+ else
+ rv = DownloadMsgsForCurrentGroup();
+ }
+ else
+ {
+ rv = ProcessNextGroup();
+ }
+ }
+ else if (m_listener) // notify main observer.
+ m_listener->OnStopRunningUrl(url, exitCode);
+
+ return rv;
+}
+
+/**
+ * Leaves m_currentServer at the next nntp "server" that
+ * might have folders to download for offline use. If no more servers,
+ * m_currentServer will be left at nullptr and the function returns false.
+ * Also, sets up m_serverEnumerator to enumerate over the server.
+ * If no servers found, m_serverEnumerator will be left at null.
+ */
+bool nsMsgDownloadAllNewsgroups::AdvanceToNextServer()
+{
+ nsresult rv;
+
+ if (!m_allServers)
+ {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ASSERTION(accountManager && NS_SUCCEEDED(rv), "couldn't get account mgr");
+ if (!accountManager || NS_FAILED(rv))
+ return false;
+
+ rv = accountManager->GetAllServers(getter_AddRefs(m_allServers));
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+ uint32_t serverIndex = 0;
+ if (m_currentServer)
+ {
+ rv = m_allServers->IndexOf(0, m_currentServer, &serverIndex);
+ if (NS_FAILED(rv))
+ serverIndex = -1;
+
+ ++serverIndex;
+ }
+ m_currentServer = nullptr;
+ uint32_t numServers;
+ m_allServers->GetLength(&numServers);
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+
+ while (serverIndex < numServers)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(m_allServers, serverIndex);
+ serverIndex++;
+
+ nsCOMPtr <nsINntpIncomingServer> newsServer = do_QueryInterface(server);
+ if (!newsServer) // we're only looking for news servers
+ continue;
+
+ if (server)
+ {
+ m_currentServer = server;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ {
+ rv = rootFolder->GetDescendants(getter_AddRefs(m_allFolders));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = m_allFolders->Enumerate(getter_AddRefs(m_serverEnumerator));
+ if (NS_SUCCEEDED(rv) && m_serverEnumerator)
+ {
+ bool hasMore = false;
+ rv = m_serverEnumerator->HasMoreElements(&hasMore);
+ if (NS_SUCCEEDED(rv) && hasMore)
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Sets m_currentFolder to the next usable folder.
+ *
+ * @return False if no more folders found, otherwise true.
+ */
+bool nsMsgDownloadAllNewsgroups::AdvanceToNextGroup()
+{
+ nsresult rv = NS_OK;
+
+ if (m_currentFolder)
+ {
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
+ if (newsFolder)
+ newsFolder->SetSaveArticleOffline(false);
+
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && session)
+ {
+ bool folderOpen;
+ uint32_t folderFlags;
+ m_currentFolder->GetFlags(&folderFlags);
+ session->IsFolderOpenInWindow(m_currentFolder, &folderOpen);
+ if (!folderOpen && ! (folderFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
+ m_currentFolder->SetMsgDatabase(nullptr);
+ }
+ m_currentFolder = nullptr;
+ }
+
+ bool hasMore = false;
+ if (m_currentServer)
+ m_serverEnumerator->HasMoreElements(&hasMore);
+ if (!hasMore)
+ hasMore = AdvanceToNextServer();
+
+ if (hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ rv = m_serverEnumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv))
+ m_currentFolder = do_QueryInterface(supports);
+ }
+ return m_currentFolder;
+}
+
+nsresult DownloadMatchingNewsArticlesToNewsDB::RunSearch(nsIMsgFolder *folder, nsIMsgDatabase *newsDB, nsIMsgSearchSession *searchSession)
+{
+ m_folder = folder;
+ m_newsDB = newsDB;
+ m_searchSession = searchSession;
+
+ m_keysToDownload.Clear();
+
+ NS_ENSURE_ARG(searchSession);
+ NS_ENSURE_ARG(folder);
+
+ searchSession->RegisterListener(this,
+ nsIMsgSearchSession::allNotifications);
+ nsresult rv = searchSession->AddScopeTerm(nsMsgSearchScope::localNews, folder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return searchSession->Search(m_window);
+}
+
+nsresult nsMsgDownloadAllNewsgroups::ProcessNextGroup()
+{
+ bool done = false;
+
+ while (!done)
+ {
+ done = !AdvanceToNextGroup();
+ if (!done && m_currentFolder)
+ {
+ uint32_t folderFlags;
+ m_currentFolder->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Offline)
+ break;
+ }
+ }
+ if (done)
+ {
+ if (m_listener)
+ return m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ m_downloadedHdrsForCurGroup = true;
+ return m_currentFolder ? m_currentFolder->GetNewMessages(m_window, this) : NS_ERROR_NOT_INITIALIZED;
+}
+
+nsresult nsMsgDownloadAllNewsgroups::DownloadMsgsForCurrentGroup()
+{
+ NS_ENSURE_TRUE(m_downloaderForGroup, NS_ERROR_OUT_OF_MEMORY);
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsCOMPtr <nsIMsgDownloadSettings> downloadSettings;
+ m_currentFolder->GetMsgDatabase(getter_AddRefs(db));
+ nsresult rv = m_currentFolder->GetDownloadSettings(getter_AddRefs(downloadSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
+ if (newsFolder)
+ newsFolder->SetSaveArticleOffline(true);
+
+ nsCOMPtr <nsIMsgSearchSession> searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool downloadByDate, downloadUnreadOnly;
+ uint32_t ageLimitOfMsgsToDownload;
+
+ downloadSettings->GetDownloadByDate(&downloadByDate);
+ downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly);
+ downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload);
+
+ nsCOMPtr <nsIMsgSearchTerm> term;
+ nsCOMPtr <nsIMsgSearchValue> value;
+
+ rv = searchSession->CreateTerm(getter_AddRefs(term));
+ NS_ENSURE_SUCCESS(rv, rv);
+ term->GetValue(getter_AddRefs(value));
+
+ if (downloadUnreadOnly)
+ {
+ value->SetAttrib(nsMsgSearchAttrib::MsgStatus);
+ value->SetStatus(nsMsgMessageFlags::Read);
+ searchSession->AddSearchTerm(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, value, true, nullptr);
+ }
+ if (downloadByDate)
+ {
+ value->SetAttrib(nsMsgSearchAttrib::AgeInDays);
+ value->SetAge(ageLimitOfMsgsToDownload);
+ searchSession->AddSearchTerm(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, value, nsMsgSearchBooleanOp::BooleanAND, nullptr);
+ }
+ value->SetAttrib(nsMsgSearchAttrib::MsgStatus);
+ value->SetStatus(nsMsgMessageFlags::Offline);
+ searchSession->AddSearchTerm(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, value, nsMsgSearchBooleanOp::BooleanAND, nullptr);
+
+ m_downloaderForGroup->RunSearch(m_currentFolder, db, searchSession);
+ return rv;
+}
diff --git a/mailnews/news/src/nsNewsDownloader.h b/mailnews/news/src/nsNewsDownloader.h
new file mode 100644
index 000000000..b9a018032
--- /dev/null
+++ b/mailnews/news/src/nsNewsDownloader.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsNewsDownloader_H_
+#define _nsNewsDownloader_H_
+
+
+#include "nsIMsgDatabase.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsIMsgSearchSession.h"
+
+// base class for downloading articles in a single newsgroup. Keys to download are passed in
+// to DownloadArticles method.
+class nsNewsDownloader : public nsIUrlListener, public nsIMsgSearchNotify
+{
+public:
+ nsNewsDownloader(nsIMsgWindow *window, nsIMsgDatabase *db, nsIUrlListener *listener);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGSEARCHNOTIFY
+
+ virtual nsresult DownloadArticles(nsIMsgWindow *window, nsIMsgFolder *folder, nsTArray<nsMsgKey> *pKeyArray);
+
+ bool ShouldAbort() const { return m_abort; }
+
+protected:
+ virtual ~nsNewsDownloader();
+
+ virtual int32_t Write(const char * /*block*/, int32_t length) {return length;}
+ virtual void Abort();
+ virtual void Complete();
+ virtual bool GetNextHdrToRetrieve();
+ virtual nsresult DownloadNext(bool firstTimeP);
+ virtual int32_t FinishDownload() {return 0;}
+ virtual int32_t StartDownload() {return 0;}
+ virtual nsresult ShowProgress(const char16_t *progressString, int32_t percent);
+
+ nsTArray<nsMsgKey> m_keysToDownload;
+ nsCOMPtr <nsIMsgFolder> m_folder;
+ nsCOMPtr <nsIMsgDatabase> m_newsDB;
+ nsCOMPtr <nsIUrlListener> m_listener;
+ bool m_downloadFromKeys;
+ bool m_existedP;
+ bool m_wroteAnyP;
+ bool m_summaryValidP;
+ bool m_abort;
+ int32_t m_numwrote;
+ nsMsgKey m_keyToDownload;
+ nsCOMPtr <nsIMsgWindow> m_window;
+ nsCOMPtr <nsIMsgStatusFeedback> m_statusFeedback;
+ nsCOMPtr <nsIMsgSearchSession> m_searchSession;
+ int32_t m_lastPercent;
+ int64_t m_lastProgressTime;
+ nsresult m_status;
+};
+
+
+// class for downloading articles in a single newsgroup to the offline store.
+class DownloadNewsArticlesToOfflineStore : public nsNewsDownloader
+{
+public:
+ DownloadNewsArticlesToOfflineStore(nsIMsgWindow *window, nsIMsgDatabase *db, nsIUrlListener *listener);
+ virtual ~DownloadNewsArticlesToOfflineStore();
+
+ NS_IMETHOD OnStartRunningUrl(nsIURI* url);
+ NS_IMETHOD OnStopRunningUrl(nsIURI* url, nsresult exitCode);
+protected:
+ virtual int32_t StartDownload();
+ virtual int32_t FinishDownload();
+ virtual bool GetNextHdrToRetrieve();
+
+ nsCOMPtr <nsISimpleEnumerator> m_headerEnumerator;
+ nsCOMPtr <nsIMsgDBHdr> m_newsHeader;
+};
+
+// class for downloading all the articles that match the passed in search criteria
+// for a single newsgroup.
+class DownloadMatchingNewsArticlesToNewsDB : public DownloadNewsArticlesToOfflineStore
+{
+public:
+ DownloadMatchingNewsArticlesToNewsDB(nsIMsgWindow *window, nsIMsgFolder *folder, nsIMsgDatabase *newsDB, nsIUrlListener *listener);
+ virtual ~DownloadMatchingNewsArticlesToNewsDB();
+ nsresult RunSearch(nsIMsgFolder *folder, nsIMsgDatabase *newsDB, nsIMsgSearchSession *searchSession);
+protected:
+};
+
+// this class iterates all the news servers for each group on the server that's configured for
+// offline use, downloads the messages that meet the download criteria for that newsgroup/server
+class nsMsgDownloadAllNewsgroups : public nsIUrlListener
+{
+public:
+ nsMsgDownloadAllNewsgroups(nsIMsgWindow *window, nsIUrlListener *listener);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+
+ nsresult ProcessNextGroup();
+
+protected:
+ virtual ~nsMsgDownloadAllNewsgroups();
+
+ bool AdvanceToNextServer();
+ bool AdvanceToNextGroup();
+ nsresult DownloadMsgsForCurrentGroup();
+
+ DownloadMatchingNewsArticlesToNewsDB *m_downloaderForGroup;
+
+ nsCOMPtr <nsIMsgFolder> m_currentFolder;
+ nsCOMPtr <nsIMsgWindow> m_window;
+ nsCOMPtr <nsIArray> m_allServers;
+ nsCOMPtr <nsIArray> m_allFolders;
+ nsCOMPtr <nsIMsgIncomingServer> m_currentServer;
+ nsCOMPtr <nsISimpleEnumerator> m_serverEnumerator;
+ nsCOMPtr <nsIUrlListener> m_listener;
+
+ bool m_downloadedHdrsForCurGroup;
+};
+
+#endif
diff --git a/mailnews/news/src/nsNewsFolder.cpp b/mailnews/news/src/nsNewsFolder.cpp
new file mode 100644
index 000000000..33faa4269
--- /dev/null
+++ b/mailnews/news/src/nsNewsFolder.cpp
@@ -0,0 +1,1864 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "prlog.h"
+
+#include "msgCore.h" // precompiled header...
+#include "nntpCore.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsNewsFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "MailNewsTypes.h"
+#include "prprf.h"
+#include "prsystem.h"
+#include "nsIArray.h"
+#include "nsIServiceManager.h"
+#include "nsINntpService.h"
+#include "nsIFolderListener.h"
+#include "nsCOMPtr.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgNewsCID.h"
+#include "nsMsgUtils.h"
+#include "nsNewsUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsINntpIncomingServer.h"
+#include "nsINewsDatabase.h"
+#include "nsMsgBaseCID.h"
+#include "nsILineInputStream.h"
+
+#include "nsIMsgWindow.h"
+#include "nsIDocShell.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+
+#include "nsNetUtil.h"
+#include "nsIAuthPrompt.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsINntpUrl.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsArrayEnumerator.h"
+#include "nsNewsDownloader.h"
+#include "nsIStringBundle.h"
+#include "nsMsgI18N.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIMsgAccountManager.h"
+#include "nsArrayUtils.h"
+#include "nsIMsgAsyncPrompter.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMutableArray.h"
+#include "nsILoginInfo.h"
+#include "nsILoginManager.h"
+#include "nsIPromptService.h"
+#include "nsEmbedCID.h"
+#include "nsIDOMWindow.h"
+#include "mozilla/Services.h"
+#include "nsAutoPtr.h"
+#include "nsIInputStream.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+// ###tw This really ought to be the most
+// efficient file reading size for the current
+// operating system.
+#define NEWSRC_FILE_BUFFER_SIZE 1024
+
+#define kNewsSortOffset 9000
+
+#define NEWS_SCHEME "news:"
+#define SNEWS_SCHEME "snews:"
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsMsgNewsFolder::nsMsgNewsFolder(void) :
+ mExpungedBytes(0), mGettingNews(false),
+ mInitialized(false),
+ m_downloadMessageForOfflineUse(false), m_downloadingMultipleMessages(false),
+ mReadSet(nullptr), mSortOrder(kNewsSortOffset)
+{
+ MOZ_COUNT_CTOR(nsMsgNewsFolder); // double count these for now.
+ mFolderSize = kSizeUnknown;
+}
+
+nsMsgNewsFolder::~nsMsgNewsFolder(void)
+{
+ MOZ_COUNT_DTOR(nsMsgNewsFolder);
+ delete mReadSet;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsMsgNewsFolder, nsMsgDBFolder)
+NS_IMPL_RELEASE_INHERITED(nsMsgNewsFolder, nsMsgDBFolder)
+
+NS_IMETHODIMP nsMsgNewsFolder::QueryInterface(REFNSIID aIID, void** aInstancePtr)
+{
+ if (!aInstancePtr)
+ return NS_ERROR_NULL_POINTER;
+ *aInstancePtr = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsIMsgNewsFolder)))
+ *aInstancePtr = static_cast<nsIMsgNewsFolder*>(this);
+ if(*aInstancePtr)
+ {
+ AddRef();
+ return NS_OK;
+ }
+
+ return nsMsgDBFolder::QueryInterface(aIID, aInstancePtr);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsMsgNewsFolder::CreateSubFolders(nsIFile *path)
+{
+ nsresult rv;
+ bool isNewsServer = false;
+ rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isNewsServer)
+ {
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nntpServer->GetNewsrcFilePath(getter_AddRefs(mNewsrcFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = LoadNewsrcFileAndCreateNewsgroups();
+ }
+ else // is not a host, so it has no newsgroups. (what about categories??)
+ rv = NS_OK;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::AddNewsgroup(const nsACString &name, const nsACString& setStr,
+ nsIMsgFolder **child)
+{
+ NS_ENSURE_ARG_POINTER(child);
+ nsresult rv;
+ nsCOMPtr <nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+ // URI should use UTF-8
+ // (see RFC2396 Uniform Resource Identifiers (URI): Generic Syntax)
+
+ // we are handling newsgroup names in UTF-8
+ NS_ConvertUTF8toUTF16 nameUtf16(name);
+
+ nsAutoCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(nameUtf16, escapedName);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = nntpServer->AddNewsgroup(nameUtf16);
+ if (NS_FAILED(rv)) return rv;
+
+ uri.Append(escapedName);
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgNewsFolder> newsFolder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ // cache this for when we open the db
+ rv = newsFolder->SetReadSetFromStr(setStr);
+
+ rv = folder->SetParent(this);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // this what shows up in the UI
+ rv = folder->SetName(nameUtf16);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = folder->SetFlag(nsMsgFolderFlags::Newsgroup);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t numExistingGroups = mSubFolders.Count();
+
+ // add kNewsSortOffset (9000) to prevent this problem: 1,10,11,2,3,4,5
+ // We use 9000 instead of 1000 so newsgroups will sort to bottom of flat folder views
+ rv = folder->SetSortOrder(numExistingGroups + kNewsSortOffset);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ mSubFolders.AppendObject(folder);
+ folder->SetParent(this);
+ folder.swap(*child);
+ return rv;
+}
+
+nsresult nsMsgNewsFolder::ParseFolder(nsIFile *path)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsMsgNewsFolder::AddDirectorySeparator(nsIFile *path)
+{
+ // don't concat the full separator with .sbd
+ return (mURI.Equals(kNewsRootURI)) ?
+ NS_OK :
+ nsMsgDBFolder::AddDirectorySeparator(path);
+}
+
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetSubFolders(nsISimpleEnumerator **aResult)
+{
+ if (!mInitialized)
+ {
+ // do this first, so we make sure to do it, even on failure.
+ // see bug #70494
+ mInitialized = true;
+
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CreateSubFolders(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // force ourselves to get initialized from cache
+ // Don't care if it fails. this will fail the first time after
+ // migration, but we continue on. see #66018
+ (void)UpdateSummaryTotals(false);
+ }
+
+ return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER;
+}
+
+//Makes sure the database is open and exists. If the database is valid then
+//returns NS_OK. Otherwise returns a failure error value.
+nsresult nsMsgNewsFolder::GetDatabase()
+{
+ nsresult rv;
+ if (!mDatabase)
+ {
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Get the database, blowing it away if it's out of date.
+ rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase));
+ if (NS_FAILED(rv))
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if(mAddListener)
+ rv = mDatabase->AddListener(this);
+
+ nsCOMPtr<nsINewsDatabase> db = do_QueryInterface(mDatabase, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = db->SetReadSet(mReadSet);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = UpdateSummaryTotals(true);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetDatabaseWithoutCache(nsIMsgDatabase **db)
+{
+ NS_ENSURE_ARG_POINTER(db);
+
+ // The simplest way to perform this operation is to get the database normally
+ // and then clear our information about it if we didn't already hold it open.
+ bool wasCached = !!mDatabase;
+ nsresult rv = GetDatabase();
+ NS_IF_ADDREF(*db = mDatabase);
+
+ // If the DB was not open before, close our reference to it now.
+ if (!wasCached && mDatabase)
+ {
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::UpdateFolder(nsIMsgWindow *aWindow)
+{
+ // Get news.get_messages_on_select pref
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool getMessagesOnSelect = true;
+ prefBranch->GetBoolPref("news.get_messages_on_select", &getMessagesOnSelect);
+
+ // Only if news.get_messages_on_select is true do we get new messages automatically
+ if (getMessagesOnSelect)
+ {
+ rv = GetDatabase(); // want this cached...
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mDatabase)
+ {
+ nsCOMPtr<nsIMsgRetentionSettings> retentionSettings;
+ nsresult rv = GetRetentionSettings(getter_AddRefs(retentionSettings));
+ if (NS_SUCCEEDED(rv))
+ rv = mDatabase->ApplyRetentionSettings(retentionSettings, false);
+ }
+ rv = AutoCompact(aWindow);
+ NS_ENSURE_SUCCESS(rv,rv);
+ // GetNewMessages has to be the last rv set before we get to the next check, so
+ // that we'll have rv set to NS_MSG_ERROR_OFFLINE when offline and send
+ // a folder loaded notification to the front end.
+ rv = GetNewMessages(aWindow, nullptr);
+ }
+ if (rv != NS_MSG_ERROR_OFFLINE)
+ return rv;
+ }
+ // We're not getting messages because either get_messages_on_select is
+ // false or we're offline. Send an immediate folder loaded notification.
+ NotifyFolderEvent(mFolderLoadedAtom);
+ (void) RefreshSizeOnDisk();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanSubscribe(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ bool isNewsServer = false;
+ nsresult rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ // you can only subscribe to news servers, not news groups
+ *aResult = isNewsServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanFileMessages(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // you can't file messages into a news server or news group
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanCreateSubfolders(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // you can't create subfolders on a news server or a news group
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanRename(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // you can't rename a news server or a news group
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanCompact(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // you can't compact a news server or a news group
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetMessages(nsISimpleEnumerator **result)
+{
+ nsresult rv = GetDatabase();
+ *result = nullptr;
+
+ if(NS_SUCCEEDED(rv))
+ rv = mDatabase->EnumerateMessages(result);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetFolderURL(nsACString& aUrl)
+{
+ nsCString hostName;
+ nsresult rv = GetHostname(hostName);
+ nsString groupName;
+ rv = GetName(groupName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port;
+ rv = server->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ const char *newsScheme = (socketType == nsMsgSocketType::SSL) ?
+ SNEWS_SCHEME : NEWS_SCHEME;
+ nsCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(groupName, escapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString tmpStr;
+ tmpStr.Adopt(PR_smprintf("%s//%s:%ld/%s", newsScheme, hostName.get(), port,
+ escapedName.get()));
+ aUrl.Assign(tmpStr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetNewsrcHasChanged(bool newsrcHasChanged)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+ return nntpServer->SetNewsrcHasChanged(newsrcHasChanged);
+}
+
+nsresult nsMsgNewsFolder::CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder)
+{
+ nsMsgNewsFolder *newFolder = new nsMsgNewsFolder;
+ if (!newFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*folder = newFolder);
+ newFolder->Init(uri.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CreateSubfolder(const nsAString& newsgroupName,
+ nsIMsgWindow *msgWindow)
+{
+ nsresult rv = NS_OK;
+ if (newsgroupName.IsEmpty())
+ return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ nsCOMPtr<nsIMsgFolder> child;
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDatabase> newsDBFactory;
+ nsCOMPtr <nsIMsgDatabase> newsDB;
+
+ //Now let's create the actual new folder
+ rv = AddNewsgroup(NS_ConvertUTF16toUTF8(newsgroupName), EmptyCString(), getter_AddRefs(child));
+
+ if (NS_SUCCEEDED(rv))
+ SetNewsrcHasChanged(true); // subscribe UI does this - but maybe we got here through auto-subscribe
+
+ if(NS_SUCCEEDED(rv) && child){
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString dataCharset;
+ rv = nntpServer->GetCharset(dataCharset);
+ if (NS_FAILED(rv)) return rv;
+
+ child->SetCharset(dataCharset);
+ NotifyItemAdded(child);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderAdded(child);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Delete()
+{
+ nsresult rv = nsMsgDBFolder::Delete();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString name;
+ rv = GetUnicodeName(name);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nntpServer->RemoveNewsgroup(name);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ (void) RefreshSizeOnDisk();
+
+ return SetNewsrcHasChanged(true);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Rename(const nsAString& newName, nsIMsgWindow *msgWindow)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetAbbreviatedName(nsAString& aAbbreviatedName)
+{
+ nsresult rv;
+
+ rv = nsMsgDBFolder::GetPrettyName(aAbbreviatedName);
+ if(NS_FAILED(rv)) return rv;
+
+ // only do this for newsgroup names, not for newsgroup hosts.
+ bool isNewsServer = false;
+ rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!isNewsServer) {
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ bool abbreviate = true;
+ rv = nntpServer->GetAbbreviate(&abbreviate);
+ if (NS_FAILED(rv)) return rv;
+
+ if (abbreviate)
+ rv = AbbreviatePrettyName(aAbbreviatedName, 1 /* hardcoded for now */);
+ }
+ return rv;
+}
+
+// original code from Oleg Rekutin
+// rekusha@asan.com
+// Public domain, created by Oleg Rekutin
+//
+// takes a newsgroup name, number of words from the end to leave unabberviated
+// the newsgroup name, will get reset to the following format:
+// x.x.x, where x is the first letter of each word and with the
+// exception of last 'fullwords' words, which are left intact.
+// If a word has a dash in it, it is abbreviated as a-b, where
+// 'a' is the first letter of the part of the word before the
+// dash and 'b' is the first letter of the part of the word after
+// the dash
+nsresult nsMsgNewsFolder::AbbreviatePrettyName(nsAString& prettyName, int32_t fullwords)
+{
+ nsAutoString name(prettyName);
+ int32_t totalwords = 0; // total no. of words
+
+ // get the total no. of words
+ int32_t pos = 0;
+ while(1)
+ {
+ pos = name.FindChar('.', pos);
+ if(pos == -1)
+ {
+ totalwords++;
+ break;
+ }
+ else
+ {
+ totalwords++;
+ pos++;
+ }
+ }
+
+ // get the no. of words to abbreviate
+ int32_t abbrevnum = totalwords - fullwords;
+ if (abbrevnum < 1)
+ return NS_OK; // nothing to abbreviate
+
+ // build the ellipsis
+ nsAutoString out;
+ out += name[0];
+
+ int32_t length = name.Length();
+ int32_t newword = 0; // == 2 if done with all abbreviated words
+
+ fullwords = 0;
+ char16_t currentChar;
+ for (int32_t i = 1; i < length; i++)
+ {
+ // this temporary assignment is needed to fix an intel mac compiler bug.
+ // See Bug #327037 for details.
+ currentChar = name[i];
+ if (newword < 2) {
+ switch (currentChar) {
+ case '.':
+ fullwords++;
+ // check if done with all abbreviated words...
+ if (fullwords == abbrevnum)
+ newword = 2;
+ else
+ newword = 1;
+ break;
+ case '-':
+ newword = 1;
+ break;
+ default:
+ if (newword)
+ newword = 0;
+ else
+ continue;
+ }
+ }
+ out.Append(currentChar);
+ }
+ prettyName = out;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db)
+{
+ NS_ENSURE_ARG_POINTER(folderInfo);
+ NS_ENSURE_ARG_POINTER(db);
+ nsresult openErr;
+ openErr = GetDatabase();
+ *db = mDatabase;
+ if (mDatabase) {
+ NS_ADDREF(*db);
+ if (NS_SUCCEEDED(openErr))
+ openErr = (*db)->GetDBFolderInfo(folderInfo);
+ }
+ return openErr;
+}
+
+/* this used to be MSG_FolderInfoNews::UpdateSummaryFromNNTPInfo() */
+NS_IMETHODIMP
+nsMsgNewsFolder::UpdateSummaryFromNNTPInfo(int32_t oldest, int32_t youngest, int32_t total)
+{
+ /* First, mark all of the articles now known to be expired as read. */
+ if (oldest > 1)
+ {
+ nsCString oldSet;
+ nsCString newSet;
+ mReadSet->Output(getter_Copies(oldSet));
+ mReadSet->AddRange(1, oldest - 1);
+ mReadSet->Output(getter_Copies(newSet));
+ }
+
+ /* Now search the newsrc line and figure out how many of these messages are marked as unread. */
+
+ /* make sure youngest is a least 1. MSNews seems to return a youngest of 0. */
+ if (youngest == 0)
+ youngest = 1;
+
+ int32_t unread = mReadSet->CountMissingInRange(oldest, youngest);
+ NS_ASSERTION(unread >= 0,"CountMissingInRange reported unread < 0");
+ if (unread < 0)
+ // servers can send us stuff like "211 0 41 40 nz.netstatus"
+ // we should handle it gracefully.
+ unread = 0;
+
+ if (unread > total)
+ {
+ /* This can happen when the newsrc file shows more unread than exist in the group (total is not necessarily `end - start'.) */
+ unread = total;
+ int32_t deltaInDB = mNumTotalMessages - mNumUnreadMessages;
+ //int32_t deltaInDB = m_totalInDB - m_unreadInDB;
+ /* if we know there are read messages in the db, subtract that from the unread total */
+ if (deltaInDB > 0)
+ unread -= deltaInDB;
+ }
+
+ bool dbWasOpen = mDatabase != nullptr;
+ int32_t pendingUnreadDelta = unread - mNumUnreadMessages - mNumPendingUnreadMessages;
+ int32_t pendingTotalDelta = total - mNumTotalMessages - mNumPendingTotalMessages;
+ ChangeNumPendingUnread(pendingUnreadDelta);
+ ChangeNumPendingTotalMessages(pendingTotalDelta);
+ if (!dbWasOpen && mDatabase)
+ {
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetExpungedBytesCount(int64_t *count)
+{
+ NS_ENSURE_ARG_POINTER(count);
+ *count = mExpungedBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetDeletable(bool *deletable)
+{
+ NS_ENSURE_ARG_POINTER(deletable);
+
+ *deletable = false;
+ // For legacy reasons, there can be Saved search folders under news accounts.
+ // Allow deleting those.
+ GetFlag(nsMsgFolderFlags::Virtual, deletable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::RefreshSizeOnDisk()
+{
+ uint64_t oldFolderSize = mFolderSize;
+ // We set size to unknown to force it to get recalculated from disk.
+ mFolderSize = kSizeUnknown;
+ if (NS_SUCCEEDED(GetSizeOnDisk(&mFolderSize)))
+ NotifyIntPropertyChanged(kFolderSizeAtom, oldFolderSize, mFolderSize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetSizeOnDisk(int64_t *size)
+{
+ NS_ENSURE_ARG_POINTER(size);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ // If this is the rootFolder, return 0 as a safe value.
+ if (NS_FAILED(rv) || isServer)
+ mFolderSize = 0;
+
+ // 0 is a valid folder size (meaning empty file with no offline messages),
+ // but 1 is not. So use -1 as a special value meaning no file size was fetched
+ // from disk yet.
+ if (mFolderSize == kSizeUnknown)
+ {
+ nsCOMPtr<nsIFile> diskFile;
+ nsresult rv = GetFilePath(getter_AddRefs(diskFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there were no news messages downloaded for offline use, the folder file
+ // may not exist yet. In that case size is 0.
+ bool exists = false;
+ rv = diskFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ {
+ mFolderSize = 0;
+ }
+ else
+ {
+ int64_t fileSize;
+ rv = diskFile->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mFolderSize = fileSize;
+ }
+ }
+
+ *size = mFolderSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::DeleteMessages(nsIArray *messages, nsIMsgWindow *aMsgWindow,
+ bool deleteStorage, bool isMove,
+ nsIMsgCopyServiceListener* listener,
+ bool allowUndo)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG_POINTER(messages);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ if (!isMove)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsDeleted(messages);
+ }
+
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = EnableNotifications(allMessageCountNotifications, false, true);
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t count = 0;
+ rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < count && NS_SUCCEEDED(rv); i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i, &rv);
+ if (msgHdr)
+ rv = mDatabase->DeleteHeader(msgHdr, nullptr, true, true);
+ }
+ EnableNotifications(allMessageCountNotifications, true, true);
+ }
+
+ if (!isMove)
+ NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom :
+ mDeleteOrMoveMsgFailedAtom);
+
+ (void) RefreshSizeOnDisk();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CancelMessage(nsIMsgDBHdr *msgHdr,
+ nsIMsgWindow *aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ nsresult rv;
+
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // for cancel, we need to
+ // turn "newsmessage://sspitzer@news.mozilla.org/netscape.test#5428"
+ // into "news://sspitzer@news.mozilla.org/23423@netscape.com"
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString serverURI;
+ rv = server->GetServerURI(serverURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageID;
+ rv = msgHdr->GetMessageId(getter_Copies(messageID));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // we need to escape the message ID,
+ // it might contain characters which will mess us up later, like #
+ // see bug #120502
+ nsCString escapedMessageID;
+ MsgEscapeString(messageID, nsINetUtil::ESCAPE_URL_PATH, escapedMessageID);
+
+ nsAutoCString cancelURL(serverURI.get());
+ cancelURL += '/';
+ cancelURL += escapedMessageID;
+ cancelURL += "?cancel";
+
+ nsCString messageURI;
+ rv = GetUriForMsg(msgHdr, messageURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return nntpService->CancelMessage(cancelURL.get(), messageURI.get(), nullptr /* consumer */, nullptr,
+ aMsgWindow, nullptr);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetNewMessages(nsIMsgWindow *aMsgWindow, nsIUrlListener *aListener)
+{
+ return GetNewsMessages(aMsgWindow, false, aListener);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetNextNMessages(nsIMsgWindow *aMsgWindow)
+{
+ return GetNewsMessages(aMsgWindow, true, nullptr);
+}
+
+nsresult nsMsgNewsFolder::GetNewsMessages(nsIMsgWindow *aMsgWindow, bool aGetOld, nsIUrlListener *aUrlListener)
+{
+ nsresult rv = NS_OK;
+
+ bool isNewsServer = false;
+ rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isNewsServer)
+ // get new messages only works on a newsgroup, not a news server
+ return NS_OK;
+
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIURI> resultUri;
+ rv = nntpService->GetNewNews(nntpServer, mURI.get(), aGetOld, this,
+ aMsgWindow, getter_AddRefs(resultUri));
+ if (aUrlListener && NS_SUCCEEDED(rv) && resultUri)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(resultUri));
+ if (msgUrl)
+ msgUrl->RegisterListener(aUrlListener);
+ }
+ return rv;
+}
+
+nsresult
+nsMsgNewsFolder::LoadNewsrcFileAndCreateNewsgroups()
+{
+ nsresult rv = NS_OK;
+ if (!mNewsrcFilePath) return NS_ERROR_FAILURE;
+
+ bool exists;
+ rv = mNewsrcFilePath->Exists(&exists);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!exists)
+ // it is ok for the newsrc file to not exist yet
+ return NS_OK;
+
+ nsCOMPtr<nsIInputStream> fileStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), mNewsrcFilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsCString line;
+
+ while (more && NS_SUCCEEDED(rv))
+ {
+ rv = lineInputStream->ReadLine(line, &more);
+ if (line.IsEmpty())
+ continue;
+ HandleNewsrcLine(line.get(), line.Length());
+ }
+
+ fileStream->Close();
+ return rv;
+}
+
+int32_t
+nsMsgNewsFolder::HandleNewsrcLine(const char * line, uint32_t line_size)
+{
+ nsresult rv;
+
+ /* guard against blank line lossage */
+ if (line[0] == '#' || line[0] == '\r' || line[0] == '\n') return 0;
+
+ if ((line[0] == 'o' || line[0] == 'O') &&
+ !PL_strncasecmp (line, "options", 7))
+ return RememberLine(nsDependentCString(line));
+
+ const char *s = nullptr;
+ const char *setStr = nullptr;
+ const char *end = line + line_size;
+
+ for (s = line; s < end; s++)
+ if ((*s == ':') || (*s == '!'))
+ break;
+
+ if (*s == 0)
+ /* What is this?? Well, don't just throw it away... */
+ return RememberLine(nsDependentCString(line));
+
+ bool subscribed = (*s == ':');
+ setStr = s+1;
+
+ if (*line == '\0')
+ return 0;
+
+ // previous versions of Communicator poluted the
+ // newsrc files with articles
+ // (this would happen when you clicked on a link like
+ // news://news.mozilla.org/3746EF3F.6080309@netscape.com)
+ //
+ // legal newsgroup names can't contain @ or %
+ //
+ // News group names are structured into parts separated by dots,
+ // for example "netscape.public.mozilla.mail-news".
+ // Each part may be up to 14 characters long, and should consist
+ // only of letters, digits, "+" and "-", with at least one letter
+ //
+ // @ indicates an article and %40 is @ escaped.
+ // previous versions of Communicator also dumped
+ // the escaped version into the newsrc file
+ //
+ // So lines like this in a newsrc file should be ignored:
+ // 3746EF3F.6080309@netscape.com:
+ // 3746EF3F.6080309%40netscape.com:
+ if (PL_strchr(line, '@') || PL_strstr(line, "%40"))
+ // skipping, it contains @ or %40
+ subscribed = false;
+
+ if (subscribed)
+ {
+ // we're subscribed, so add it
+ nsCOMPtr <nsIMsgFolder> child;
+
+ rv = AddNewsgroup(Substring(line, s), nsDependentCString(setStr), getter_AddRefs(child));
+ if (NS_FAILED(rv)) return -1;
+ }
+ else {
+ rv = RememberUnsubscribedGroup(nsDependentCString(line), nsDependentCString(setStr));
+ if (NS_FAILED(rv)) return -1;
+ }
+
+ return 0;
+}
+
+
+nsresult
+nsMsgNewsFolder::RememberUnsubscribedGroup(const nsACString& newsgroup, const nsACString& setStr)
+{
+ mUnsubscribedNewsgroupLines.Append(newsgroup);
+ mUnsubscribedNewsgroupLines.AppendLiteral("! ");
+ if (!setStr.IsEmpty())
+ mUnsubscribedNewsgroupLines.Append(setStr);
+ else
+ mUnsubscribedNewsgroupLines.Append(MSG_LINEBREAK);
+ return NS_OK;
+}
+
+int32_t
+nsMsgNewsFolder::RememberLine(const nsACString& line)
+{
+ mOptionLines = line;
+ mOptionLines.Append(MSG_LINEBREAK);
+ return 0;
+}
+
+nsresult nsMsgNewsFolder::ForgetLine()
+{
+ mOptionLines.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetGroupUsername(nsACString& aGroupUsername)
+{
+ aGroupUsername = mGroupUsername;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetGroupUsername(const nsACString& aGroupUsername)
+{
+ mGroupUsername = aGroupUsername;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetGroupPassword(nsACString& aGroupPassword)
+{
+ aGroupPassword = mGroupPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetGroupPassword(const nsACString& aGroupPassword)
+{
+ mGroupPassword = aGroupPassword;
+ return NS_OK;
+}
+
+nsresult nsMsgNewsFolder::CreateNewsgroupUrlForSignon(const char *ref,
+ nsAString &result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ bool singleSignon = true;
+ rv = nntpServer->GetSingleSignon(&singleSignon);
+
+ if (singleSignon)
+ {
+ nsCString serverURI;
+ rv = server->GetServerURI(serverURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->SetSpec(serverURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ rv = url->SetSpec(mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ int32_t port = 0;
+ rv = url->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (port <= 0)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ nsresult rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only set this for ssl newsgroups as for non-ssl connections, we don't
+ // need to specify the port as it is the default for the protocol and
+ // password manager "blanks" those out.
+ if (socketType == nsMsgSocketType::SSL)
+ {
+ rv = url->SetPort(nsINntpUrl::DEFAULT_NNTPS_PORT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsCString rawResult;
+ if (ref)
+ {
+ rv = url->SetRef(nsDependentCString(ref));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->GetSpec(rawResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ // If the url doesn't have a path, make sure we don't get a '/' on the end
+ // as that will confuse searching in password manager.
+ nsCString spec;
+ rv = url->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!spec.IsEmpty() && spec[spec.Length() - 1] == '/')
+ rawResult = StringHead(spec, spec.Length() - 1);
+ else
+ rawResult = spec;
+ }
+ result = NS_ConvertASCIItoUTF16(rawResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetAuthenticationCredentials(nsIMsgWindow *aMsgWindow,
+ bool mayPrompt, bool mustPrompt, bool *validCredentials)
+{
+ // Not strictly necessary, but it would help consumers to realize that this is
+ // a rather nonsensical combination.
+ NS_ENSURE_FALSE(mustPrompt && !mayPrompt, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_ARG_POINTER(validCredentials);
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString signonUrl;
+ rv = CreateNewsgroupUrlForSignon(nullptr, signonUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we don't have a username or password, try to load it via the login mgr.
+ // Do this even if mustPrompt is true, to prefill the dialog.
+ if (mGroupUsername.IsEmpty() || mGroupPassword.IsEmpty())
+ {
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numLogins = 0;
+ nsILoginInfo **logins = nullptr;
+ rv = loginMgr->FindLogins(&numLogins, signonUrl, EmptyString(), signonUrl,
+ &logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (numLogins > 0)
+ {
+ nsString uniUsername, uniPassword;
+ logins[0]->GetUsername(uniUsername);
+ logins[0]->GetPassword(uniPassword);
+ mGroupUsername = NS_LossyConvertUTF16toASCII(uniUsername);
+ mGroupPassword = NS_LossyConvertUTF16toASCII(uniPassword);
+
+ *validCredentials = true;
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numLogins, logins);
+ }
+
+ // Show the prompt if we need to
+ if (mustPrompt ||
+ (mayPrompt && (mGroupUsername.IsEmpty() || mGroupPassword.IsEmpty())))
+ {
+ nsCOMPtr<nsIAuthPrompt> dialog;
+ if (aMsgWindow)
+ {
+ rv = aMsgWindow->GetAuthPrompt(getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch)
+ wwatch->GetNewAuthPrompter(0, getter_AddRefs(dialog));
+ if (!dialog) return NS_ERROR_FAILURE;
+ }
+
+ NS_ASSERTION(dialog, "We didn't get a net prompt");
+ if (dialog)
+ {
+ // Format the prompt text strings
+ nsString promptTitle, promptText;
+ bundle->GetStringFromName(u"enterUserPassTitle",
+ getter_Copies(promptTitle));
+
+ nsString serverName;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ server->GetPrettyName(serverName);
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool singleSignon = true;
+ nntpServer->GetSingleSignon(&singleSignon);
+
+ const char16_t *params[2];
+ params[0] = mName.get();
+ params[1] = serverName.get();
+ if (singleSignon)
+ bundle->FormatStringFromName(
+ u"enterUserPassServer",
+ &params[1], 1, getter_Copies(promptText));
+ else
+ bundle->FormatStringFromName(
+ u"enterUserPassGroup",
+ params, 2, getter_Copies(promptText));
+
+ // Fill the signon url for the dialog
+ nsString signonURL;
+ rv = CreateNewsgroupUrlForSignon(nullptr, signonURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Prefill saved username/password
+ char16_t *uniGroupUsername = ToNewUnicode(
+ NS_ConvertASCIItoUTF16(mGroupUsername));
+ char16_t *uniGroupPassword = ToNewUnicode(
+ NS_ConvertASCIItoUTF16(mGroupPassword));
+
+ // Prompt for the dialog
+ rv = dialog->PromptUsernameAndPassword(promptTitle.get(),
+ promptText.get(), signonURL.get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ &uniGroupUsername, &uniGroupPassword, validCredentials);
+
+ nsAutoString uniPasswordAdopted, uniUsernameAdopted;
+ uniPasswordAdopted.Adopt(uniGroupPassword);
+ uniUsernameAdopted.Adopt(uniGroupUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only use the username/password if the user didn't cancel.
+ if (*validCredentials)
+ {
+ SetGroupUsername(NS_LossyConvertUTF16toASCII(uniUsernameAdopted));
+ SetGroupPassword(NS_LossyConvertUTF16toASCII(uniPasswordAdopted));
+ }
+ else
+ {
+ mGroupUsername.Truncate();
+ mGroupPassword.Truncate();
+ }
+ }
+ }
+
+ *validCredentials = !(mGroupUsername.IsEmpty() || mGroupPassword.IsEmpty());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::ForgetAuthenticationCredentials()
+{
+ nsString signonUrl;
+ nsresult rv = CreateNewsgroupUrlForSignon(nullptr, signonUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ nsILoginInfo** logins;
+
+ rv = loginMgr->FindLogins(&count, signonUrl, EmptyString(), signonUrl,
+ &logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // There should only be one-login stored for this url, however just in case
+ // there isn't.
+ for (uint32_t i = 0; i < count; ++i)
+ loginMgr->RemoveLogin(logins[i]);
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins);
+
+ // Clear out the saved passwords for anyone else who tries to call.
+ mGroupUsername.Truncate();
+ mGroupPassword.Truncate();
+
+ return NS_OK;
+}
+
+// change order of subfolders (newsgroups)
+// aOrientation = -1 ... aNewsgroupToMove aRefNewsgroup ...
+// aOrientation = 1 ... aRefNewsgroup aNewsgroupToMove ...
+NS_IMETHODIMP nsMsgNewsFolder::MoveFolder(nsIMsgFolder *aNewsgroupToMove, nsIMsgFolder *aRefNewsgroup, int32_t aOrientation)
+{
+ // if folders are identical do nothing
+ if (aNewsgroupToMove == aRefNewsgroup)
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+
+ // get index for aNewsgroupToMove
+ int32_t indexNewsgroupToMove = mSubFolders.IndexOf(aNewsgroupToMove);
+ if (indexNewsgroupToMove == -1)
+ // aNewsgroupToMove is no subfolder of this folder
+ return NS_ERROR_INVALID_ARG;
+
+ // get index for aRefNewsgroup
+ int32_t indexRefNewsgroup = mSubFolders.IndexOf(aRefNewsgroup);
+ if (indexRefNewsgroup == -1)
+ // aRefNewsgroup is no subfolder of this folder
+ return NS_ERROR_INVALID_ARG;
+
+ // set new index for NewsgroupToMove
+ uint32_t indexMin, indexMax;
+ if (indexNewsgroupToMove < indexRefNewsgroup)
+ {
+ if (aOrientation < 0)
+ indexRefNewsgroup--;
+ indexMin = indexNewsgroupToMove;
+ indexMax = indexRefNewsgroup;
+ }
+ else
+ {
+ if (aOrientation > 0)
+ indexRefNewsgroup++;
+ indexMin = indexRefNewsgroup;
+ indexMax = indexNewsgroupToMove;
+ }
+
+ // move NewsgroupToMove to new index and set new sort order
+ NotifyItemRemoved(aNewsgroupToMove);
+
+ if (indexNewsgroupToMove != indexRefNewsgroup)
+ {
+ nsCOMPtr<nsIMsgFolder> newsgroup = mSubFolders[indexNewsgroupToMove];
+
+ mSubFolders.RemoveObjectAt(indexNewsgroupToMove);
+
+ // indexRefNewsgroup is already set up correctly.
+ mSubFolders.InsertObjectAt(newsgroup, indexRefNewsgroup);
+ }
+
+ for (uint32_t i = indexMin; i <= indexMax; i++)
+ mSubFolders[i]->SetSortOrder(kNewsSortOffset + i);
+
+ NotifyItemAdded(aNewsgroupToMove);
+
+ // write changes back to file
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nntpServer->SetNewsrcHasChanged(true);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nntpServer->WriteNewsrcFile();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return rv;
+}
+
+nsresult nsMsgNewsFolder::CreateBaseMessageURI(const nsACString& aURI)
+{
+ return nsCreateNewsBaseMessageURI(nsCString(aURI).get(), mBaseMessageURI);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetNewsrcLine(nsACString& newsrcLine)
+{
+ nsresult rv;
+ nsString newsgroupNameUtf16;
+ rv = GetName(newsgroupNameUtf16);
+ if (NS_FAILED(rv)) return rv;
+ NS_ConvertUTF16toUTF8 newsgroupName(newsgroupNameUtf16);
+
+ newsrcLine = newsgroupName;
+ newsrcLine.Append(':');
+
+ if (mReadSet) {
+ nsCString setStr;
+ mReadSet->Output(getter_Copies(setStr));
+ if (NS_SUCCEEDED(rv))
+ {
+ newsrcLine.Append(' ');
+ newsrcLine.Append(setStr);
+ newsrcLine.AppendLiteral(MSG_LINEBREAK);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetReadSetFromStr(const nsACString& newsrcLine)
+{
+ delete mReadSet;
+ mReadSet = nsMsgKeySet::Create(nsCString(newsrcLine).get());
+ NS_ENSURE_TRUE(mReadSet, NS_ERROR_OUT_OF_MEMORY);
+
+ // Now that mReadSet is recreated, make sure it's stored in the db as well.
+ nsCOMPtr<nsINewsDatabase> db = do_QueryInterface(mDatabase);
+ if (db) // it's ok not to have a db here.
+ db->SetReadSet(mReadSet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetUnsubscribedNewsgroupLines(nsACString& aUnsubscribedNewsgroupLines)
+{
+ aUnsubscribedNewsgroupLines = mUnsubscribedNewsgroupLines;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetOptionLines(nsACString& optionLines)
+{
+ optionLines = mOptionLines;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::OnReadChanged(nsIDBChangeListener * aInstigator)
+{
+ return SetNewsrcHasChanged(true);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetUnicodeName(nsAString& aName)
+{
+ return GetName(aName);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetRawName(nsACString & aRawName)
+{
+ nsresult rv;
+ if (mRawName.IsEmpty())
+ {
+ nsString name;
+ rv = GetName(name);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // convert to the server-side encoding
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString dataCharset;
+ rv = nntpServer->GetCharset(dataCharset);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = nsMsgI18NConvertFromUnicode(dataCharset.get(), name, mRawName);
+
+ if (NS_FAILED(rv))
+ LossyCopyUTF16toASCII(name, mRawName);
+ }
+ aRawName = mRawName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetNntpServer(nsINntpIncomingServer **result)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = server->QueryInterface(NS_GET_IID(nsINntpIncomingServer),
+ getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv))
+ return rv;
+ nntpServer.swap(*result);
+ return NS_OK;
+}
+
+// this gets called after the message actually gets cancelled
+// it removes the cancelled message from the db
+NS_IMETHODIMP nsMsgNewsFolder::RemoveMessage(nsMsgKey key)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv); // if GetDatabase succeeds, mDatabase will be non-null
+
+ // Notify listeners of a delete for a single message
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> msgHdrs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ msgHdrs->AppendElement(msgHdr, false);
+
+ notifier->NotifyMsgsDeleted(msgHdrs);
+ }
+ return mDatabase->DeleteMessage(key, nullptr, false);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::RemoveMessages(nsTArray<nsMsgKey> &aMsgKeys)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv); // if GetDatabase succeeds, mDatabase will be non-null
+
+ // Notify listeners of a multiple message delete
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+
+ if (notifier)
+ {
+ nsCOMPtr<nsIMutableArray> msgHdrs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ rv = MsgGetHeadersFromKeys(mDatabase, aMsgKeys, msgHdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ notifier->NotifyMsgsDeleted(msgHdrs);
+ }
+
+ return mDatabase->DeleteMessages(aMsgKeys.Length(), aMsgKeys.Elements(), nullptr);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CancelComplete()
+{
+ NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CancelFailed()
+{
+ NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetSaveArticleOffline(bool *aBool)
+{
+ NS_ENSURE_ARG(aBool);
+ *aBool = m_downloadMessageForOfflineUse;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetSaveArticleOffline(bool aBool)
+{
+ m_downloadMessageForOfflineUse = aBool;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow)
+{
+ nsTArray<nsMsgKey> srcKeyArray;
+ SetSaveArticleOffline(true);
+ nsresult rv = NS_OK;
+
+ // build up message keys.
+ if (mDatabase)
+ {
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ bool hasMore;
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports);
+ if (pHeader && NS_SUCCEEDED(rv))
+ {
+ bool shouldStoreMsgOffline = false;
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline);
+ if (shouldStoreMsgOffline)
+ srcKeyArray.AppendElement(msgKey);
+ }
+ }
+ }
+ }
+ RefPtr<DownloadNewsArticlesToOfflineStore> downloadState =
+ new DownloadNewsArticlesToOfflineStore(msgWindow, mDatabase, this);
+ m_downloadingMultipleMessages = true;
+ rv = downloadState->DownloadArticles(msgWindow, this, &srcKeyArray);
+ (void) RefreshSizeOnDisk();
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::DownloadMessagesForOffline(nsIArray *messages, nsIMsgWindow *window)
+{
+ nsTArray<nsMsgKey> srcKeyArray;
+ SetSaveArticleOffline(true); // ### TODO need to clear this when we've finished
+ uint32_t count = 0;
+ uint32_t i;
+ nsresult rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // build up message keys.
+ for (i = 0; i < count; i++)
+ {
+ nsMsgKey key;
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr = do_QueryElementAt(messages, i, &rv);
+ if (msgDBHdr)
+ rv = msgDBHdr->GetMessageKey(&key);
+ if (NS_SUCCEEDED(rv))
+ srcKeyArray.AppendElement(key);
+ }
+ RefPtr<DownloadNewsArticlesToOfflineStore> downloadState =
+ new DownloadNewsArticlesToOfflineStore(window, mDatabase, this);
+ m_downloadingMultipleMessages = true;
+
+ rv = downloadState->DownloadArticles(window, this, &srcKeyArray);
+ (void) RefreshSizeOnDisk();
+ return rv;
+}
+
+// line does not have a line terminator (e.g., CR or CRLF)
+NS_IMETHODIMP nsMsgNewsFolder::NotifyDownloadedLine(const char *line, nsMsgKey keyOfArticle)
+{
+ nsresult rv = NS_OK;
+ if (m_downloadMessageForOfflineUse)
+ {
+ if (!m_offlineHeader)
+ {
+ GetMessageHeader(keyOfArticle, getter_AddRefs(m_offlineHeader));
+ rv = StartNewOfflineMessage();
+ }
+ m_numOfflineMsgLines++;
+ }
+
+ if (m_tempMessageStream)
+ {
+ // line now contains the linebreak.
+ if (line[0] == '.' && line[MSG_LINEBREAK_LEN + 1] == 0)
+ {
+ // end of article.
+ if (m_offlineHeader)
+ EndNewOfflineMessage();
+
+ if (m_tempMessageStream && !m_downloadingMultipleMessages)
+ {
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+ }
+ }
+ else
+ {
+ uint32_t count = 0;
+ rv = m_tempMessageStream->Write(line, strlen(line), &count);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::NotifyFinishedDownloadinghdrs()
+{
+ bool wasCached = !!mDatabase;
+ ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
+ ChangeNumPendingUnread(-mNumPendingUnreadMessages);
+ bool filtersRun;
+ // run the bayesian spam filters, if enabled.
+ CallFilterPlugins(nullptr, &filtersRun);
+
+ // If the DB was not open before, close our reference to it now.
+ if (!wasCached && mDatabase)
+ {
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ mDatabase->RemoveListener(this);
+ // This also clears all of the cached headers that may have been added while
+ // we were downloading messages (and those clearing refcount cycles in the
+ // database).
+ mDatabase->ClearCachedHdrs();
+ mDatabase = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ rv = GetDatabase();
+ if (mDatabase)
+ ApplyRetentionSettings();
+ (void) RefreshSizeOnDisk();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::ApplyRetentionSettings()
+{
+ return nsMsgDBFolder::ApplyRetentionSettings(false);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetMessageIdForKey(nsMsgKey key, nsACString& result)
+{
+ nsresult rv = GetDatabase();
+ if (!mDatabase)
+ return rv;
+ nsCOMPtr <nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCString id;
+ rv = hdr->GetMessageId(getter_Copies(id));
+ result.Assign(id);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetSortOrder(int32_t order)
+{
+ int32_t oldOrder = mSortOrder;
+
+ mSortOrder = order;
+ nsCOMPtr<nsIAtom> sortOrderAtom = MsgGetAtom("SortOrder");
+ // What to do if the atom can't be allocated?
+ NotifyIntPropertyChanged(sortOrderAtom, oldOrder, order);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetSortOrder(int32_t *order)
+{
+ NS_ENSURE_ARG_POINTER(order);
+ *order = mSortOrder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Shutdown(bool shutdownChildren)
+{
+ if (mFilterList)
+ {
+ // close the filter log stream
+ nsresult rv = mFilterList->SetLogStream(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mFilterList = nullptr;
+ }
+
+ mInitialized = false;
+ if (mReadSet) {
+ // the nsINewsDatabase holds a weak ref to the readset,
+ // and we outlive the db, so it's safe to delete it here.
+ nsCOMPtr<nsINewsDatabase> db = do_QueryInterface(mDatabase);
+ if (db)
+ db->SetReadSet(nullptr);
+ delete mReadSet;
+ mReadSet = nullptr;
+ }
+ return nsMsgDBFolder::Shutdown(shutdownChildren);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::SetFilterList(nsIMsgFilterList *aFilterList)
+{
+ if (mIsServer)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return server->SetFilterList(aFilterList);
+ }
+
+ mFilterList = aFilterList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ if (mIsServer)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return server->GetFilterList(aMsgWindow, aResult);
+ }
+
+ if (!mFilterList)
+ {
+ nsCOMPtr<nsIFile> thisFolder;
+ nsresult rv = GetFilePath(getter_AddRefs(thisFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIFile> filterFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);;
+ rv = filterFile->InitWithFile(thisFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // in 4.x, the news filter file was
+ // C:\Program Files\Netscape\Users\meer\News\host-news.mcom.com\mcom.test.dat
+ // where the summary file was
+ // C:\Program Files\Netscape\Users\meer\News\host-news.mcom.com\mcom.test.snm
+ // we make the rules file ".dat" in mozilla, so that migration works.
+
+ // NOTE:
+ // we don't we need to call NS_MsgHashIfNecessary()
+ // it's already been hashed, if necessary
+ nsCString filterFileName;
+ rv = filterFile->GetNativeLeafName(filterFileName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ filterFileName.AppendLiteral(".dat");
+
+ rv = filterFile->SetNativeLeafName(filterFileName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filterService->OpenFilterList(filterFile, this, aMsgWindow, getter_AddRefs(mFilterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*aResult = mFilterList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetEditableFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ // We don't support pluggable filter list types for news.
+ return GetFilterList(aMsgWindow, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::SetEditableFilterList(nsIMsgFilterList *aFilterList)
+{
+ return SetFilterList(aFilterList);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ if (m_tempMessageStream)
+ {
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+ }
+ m_downloadingMultipleMessages = false;
+ return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetIncomingServerType(nsACString& serverType)
+{
+ serverType.AssignLiteral("nntp");
+ return NS_OK;
+}
+
diff --git a/mailnews/news/src/nsNewsFolder.h b/mailnews/news/src/nsNewsFolder.h
new file mode 100644
index 000000000..b5b085b0b
--- /dev/null
+++ b/mailnews/news/src/nsNewsFolder.h
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ Interface for representing News folders.
+*/
+
+#ifndef nsMsgNewsFolder_h__
+#define nsMsgNewsFolder_h__
+
+#include "mozilla/Attributes.h"
+#include "nsMsgDBFolder.h"
+#include "nsIFile.h"
+#include "nsINntpIncomingServer.h" // need this for the IID
+#include "nsNewsUtils.h"
+#include "nsMsgKeySet.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFilterService.h"
+#include "nsIArray.h"
+
+class nsMsgNewsFolder : public nsMsgDBFolder, public nsIMsgNewsFolder
+{
+public:
+ nsMsgNewsFolder(void);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMSGNEWSFOLDER
+
+ // nsIUrlListener method
+ NS_IMETHOD OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode) override;
+ // nsIMsgFolder methods:
+ NS_IMETHOD GetSubFolders(nsISimpleEnumerator **aResult) override;
+
+ NS_IMETHOD GetMessages(nsISimpleEnumerator **result) override;
+ NS_IMETHOD UpdateFolder(nsIMsgWindow *aWindow) override;
+
+ NS_IMETHOD CreateSubfolder(const nsAString& folderName,
+ nsIMsgWindow *msgWindow) override;
+
+ NS_IMETHOD Delete() override;
+ NS_IMETHOD Rename(const nsAString& newName,
+ nsIMsgWindow *msgWindow) override;
+
+ NS_IMETHOD GetAbbreviatedName(nsAString& aAbbreviatedName) override;
+
+ NS_IMETHOD GetFolderURL(nsACString& url) override;
+
+ NS_IMETHOD GetExpungedBytesCount(int64_t *count);
+ NS_IMETHOD GetDeletable(bool *deletable) override;
+
+ NS_IMETHOD RefreshSizeOnDisk();
+
+ NS_IMETHOD GetSizeOnDisk(int64_t *size) override;
+
+ NS_IMETHOD GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo,
+ nsIMsgDatabase **db) override;
+
+ NS_IMETHOD DeleteMessages(nsIArray *messages,
+ nsIMsgWindow *msgWindow, bool deleteStorage,
+ bool isMove, nsIMsgCopyServiceListener* listener,
+ bool allowUndo) override;
+ NS_IMETHOD GetNewMessages(nsIMsgWindow *aWindow,
+ nsIUrlListener *aListener) override;
+
+ NS_IMETHOD GetCanSubscribe(bool *aResult) override;
+ NS_IMETHOD GetCanFileMessages(bool *aResult) override;
+ NS_IMETHOD GetCanCreateSubfolders(bool *aResult) override;
+ NS_IMETHOD GetCanRename(bool *aResult) override;
+ NS_IMETHOD GetCanCompact(bool *aResult) override;
+ NS_IMETHOD OnReadChanged(nsIDBChangeListener * aInstigator) override;
+
+ NS_IMETHOD DownloadMessagesForOffline(nsIArray *messages,
+ nsIMsgWindow *window) override;
+ NS_IMETHOD Compact(nsIUrlListener *aListener,
+ nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD DownloadAllForOffline(nsIUrlListener *listener,
+ nsIMsgWindow *msgWindow) override;
+ NS_IMETHOD GetSortOrder(int32_t *order) override;
+ NS_IMETHOD SetSortOrder(int32_t order) override;
+
+ NS_IMETHOD Shutdown(bool shutdownChildren) override;
+
+ NS_IMETHOD GetFilterList(nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList **aFilterList) override;
+ NS_IMETHOD GetEditableFilterList(nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList **aFilterList) override;
+ NS_IMETHOD SetFilterList(nsIMsgFilterList *aFilterList) override;
+ NS_IMETHOD SetEditableFilterList(nsIMsgFilterList *aFilterList) override;
+ NS_IMETHOD ApplyRetentionSettings() override;
+ NS_IMETHOD GetIncomingServerType(nsACString& serverType) override;
+
+protected:
+ virtual ~nsMsgNewsFolder();
+ // helper routine to parse the URI and update member variables
+ nsresult AbbreviatePrettyName(nsAString& prettyName, int32_t fullwords);
+ nsresult ParseFolder(nsIFile *path);
+ nsresult CreateSubFolders(nsIFile *path);
+ nsresult AddDirectorySeparator(nsIFile *path);
+ nsresult GetDatabase() override;
+ virtual nsresult CreateChildFromURI(const nsCString &uri,
+ nsIMsgFolder **folder) override;
+
+ nsresult LoadNewsrcFileAndCreateNewsgroups();
+ int32_t RememberLine(const nsACString& line);
+ nsresult RememberUnsubscribedGroup(const nsACString& newsgroup, const nsACString& setStr);
+ nsresult ForgetLine(void);
+ nsresult GetNewsMessages(nsIMsgWindow *aMsgWindow, bool getOld, nsIUrlListener *aListener);
+
+ int32_t HandleNewsrcLine(const char * line, uint32_t line_size);
+ virtual nsresult CreateBaseMessageURI(const nsACString& aURI) override;
+
+protected:
+ int64_t mExpungedBytes;
+ bool mGettingNews;
+ bool mInitialized;
+ bool m_downloadMessageForOfflineUse;
+ bool m_downloadingMultipleMessages;
+
+ nsCString mOptionLines;
+ nsCString mUnsubscribedNewsgroupLines;
+ nsMsgKeySet *mReadSet;
+
+ nsCOMPtr<nsIFile> mNewsrcFilePath;
+
+ // used for auth news
+ nsCString mGroupUsername;
+ nsCString mGroupPassword;
+
+ // the name of the newsgroup.
+ nsCString mRawName;
+ int32_t mSortOrder;
+
+private:
+ /**
+ * Constructs a signon url for use in login manager.
+ *
+ * @param ref The URI ref (should be null unless working with legacy).
+ * @param result The result of the string
+ */
+ nsresult CreateNewsgroupUrlForSignon(const char *ref, nsAString &result);
+ nsCOMPtr <nsIMsgFilterList> mFilterList;
+};
+
+#endif // nsMsgNewsFolder_h__
diff --git a/mailnews/news/src/nsNewsUtils.cpp b/mailnews/news/src/nsNewsUtils.cpp
new file mode 100644
index 000000000..b7fc5e70c
--- /dev/null
+++ b/mailnews/news/src/nsNewsUtils.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nntpCore.h"
+#include "nsNewsUtils.h"
+#include "nsMsgUtils.h"
+
+
+/* parses NewsMessageURI */
+nsresult
+nsParseNewsMessageURI(const char* uri, nsCString& group, nsMsgKey *key)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(key);
+
+ nsAutoCString uriStr(uri);
+ int32_t keySeparator = uriStr.FindChar('#');
+ if(keySeparator != -1)
+ {
+ int32_t keyEndSeparator = MsgFindCharInSet(uriStr, "?&", keySeparator);
+
+ // Grab between the last '/' and the '#' for the key
+ group = StringHead(uriStr, keySeparator);
+ int32_t groupSeparator = group.RFind("/");
+ if (groupSeparator == -1)
+ return NS_ERROR_FAILURE;
+
+ // Our string APIs don't let us unescape into the same buffer from earlier,
+ // so escape into a temporary
+ nsAutoCString unescapedGroup;
+ MsgUnescapeString(Substring(group, groupSeparator + 1), 0, unescapedGroup);
+ group = unescapedGroup;
+
+ nsAutoCString keyStr;
+ if (keyEndSeparator != -1)
+ keyStr = Substring(uriStr, keySeparator + 1, keyEndSeparator - (keySeparator + 1));
+ else
+ keyStr = Substring(uriStr, keySeparator + 1);
+ nsresult errorCode;
+ *key = keyStr.ToInteger(&errorCode);
+
+ return errorCode;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsCreateNewsBaseMessageURI(const char *baseURI, nsCString &baseMessageURI)
+{
+ nsAutoCString tailURI(baseURI);
+
+ // chop off news:/
+ if (tailURI.Find(kNewsRootURI) == 0)
+ tailURI.Cut(0, PL_strlen(kNewsRootURI));
+
+ baseMessageURI = kNewsMessageRootURI;
+ baseMessageURI += tailURI;
+
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNewsUtils.h b/mailnews/news/src/nsNewsUtils.h
new file mode 100644
index 000000000..17170d6c2
--- /dev/null
+++ b/mailnews/news/src/nsNewsUtils.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_NEWSUTILS_H
+#define NS_NEWSUTILS_H
+
+#include "nsStringGlue.h"
+#include "MailNewsTypes2.h"
+
+class nsIMsgNewsFolder;
+
+#define kNewsRootURI "news:/"
+#define kNntpRootURI "nntp:/"
+#define kNewsMessageRootURI "news-message:/"
+#define kNewsURIGroupQuery "?group="
+#define kNewsURIKeyQuery "&key="
+
+#define kNewsRootURILen 6
+#define kNntpRootURILen 6
+#define kNewsMessageRootURILen 14
+#define kNewsURIGroupQueryLen 7
+#define kNewsURIKeyQueryLen 5
+
+extern nsresult
+nsParseNewsMessageURI(const char* uri, nsCString& group, nsMsgKey *key);
+
+extern nsresult
+nsCreateNewsBaseMessageURI(const char *baseURI, nsCString &baseMessageURI);
+
+#endif //NS_NEWSUTILS_H
+
diff --git a/mailnews/news/src/nsNntpIncomingServer.cpp b/mailnews/news/src/nsNntpIncomingServer.cpp
new file mode 100644
index 000000000..7c3fcad4b
--- /dev/null
+++ b/mailnews/news/src/nsNntpIncomingServer.cpp
@@ -0,0 +1,2162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNntpIncomingServer.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsNewsFolder.h"
+#include "nsIMsgFolder.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsINntpService.h"
+#include "nsINNTPProtocol.h"
+#include "nsMsgNewsCID.h"
+#include "nsNNTPProtocol.h"
+#include "nsIDirectoryService.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsMsgUtils.h"
+#include "nsIPrompt.h"
+#include "nsIStringBundle.h"
+#include "nntpCore.h"
+#include "nsIWindowWatcher.h"
+#include "nsITreeColumns.h"
+#include "nsIDOMElement.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgI18N.h"
+#include "nsUnicharUtils.h"
+#include "nsILineInputStream.h"
+#include "nsNetUtil.h"
+#include "nsISimpleEnumerator.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+#include "nsITreeBoxObject.h"
+
+#define INVALID_VERSION 0
+#define VALID_VERSION 2
+#define NEW_NEWS_DIR_NAME "News"
+#define PREF_MAIL_NEWSRC_ROOT "mail.newsrc_root"
+#define PREF_MAIL_NEWSRC_ROOT_REL "mail.newsrc_root-rel"
+#define PREF_MAILNEWS_VIEW_DEFAULT_CHARSET "mailnews.view_default_charset"
+#define HOSTINFO_FILE_NAME "hostinfo.dat"
+
+#define NEWS_DELIMITER '.'
+
+// this platform specific junk is so the newsrc filenames we create
+// will resemble the migrated newsrc filenames.
+#if defined(XP_UNIX)
+#define NEWSRC_FILE_PREFIX "newsrc-"
+#define NEWSRC_FILE_SUFFIX ""
+#else
+#define NEWSRC_FILE_PREFIX ""
+#define NEWSRC_FILE_SUFFIX ".rc"
+#endif /* XP_UNIX */
+
+// ###tw This really ought to be the most
+// efficient file reading size for the current
+// operating system.
+#define HOSTINFO_FILE_BUFFER_SIZE 1024
+
+#include "nsMsgUtils.h"
+
+/**
+ * A comparator class to do cases insensitive comparisons for nsTArray.Sort()
+ */
+class nsCStringLowerCaseComparator
+{
+public:
+ bool Equals(const nsCString &a, const nsCString &b) const
+ {
+ return a.Equals(b, nsCaseInsensitiveCStringComparator());
+ }
+
+ bool LessThan(const nsCString &a, const nsCString &b) const
+ {
+ return Compare(a, b, nsCaseInsensitiveCStringComparator()) < 0;
+ }
+};
+
+static NS_DEFINE_CID(kSubscribableServerCID, NS_SUBSCRIBABLESERVER_CID);
+
+NS_IMPL_ADDREF_INHERITED(nsNntpIncomingServer, nsMsgIncomingServer)
+NS_IMPL_RELEASE_INHERITED(nsNntpIncomingServer, nsMsgIncomingServer)
+
+NS_INTERFACE_MAP_BEGIN(nsNntpIncomingServer)
+ NS_INTERFACE_MAP_ENTRY(nsINntpIncomingServer)
+ NS_INTERFACE_MAP_ENTRY(nsIUrlListener)
+ NS_INTERFACE_MAP_ENTRY(nsISubscribableServer)
+ NS_INTERFACE_MAP_ENTRY(nsITreeView)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgIncomingServer)
+
+nsNntpIncomingServer::nsNntpIncomingServer()
+{
+ mNewsrcHasChanged = false;
+
+ mGetOnlyNew = true;
+
+ mHostInfoLoaded = false;
+ mHostInfoHasChanged = false;
+ mVersion = INVALID_VERSION;
+
+ mLastGroupDate = 0;
+ mUniqueId = 0;
+ mHasSeenBeginGroups = false;
+ mPostingAllowed = false;
+ mLastUpdatedTime = 0;
+
+ // these atoms are used for subscribe search
+ mSubscribedAtom = MsgGetAtom("subscribed");
+ mNntpAtom = MsgGetAtom("nntp");
+
+ // we have server wide and per group filters
+ m_canHaveFilters = true;
+
+ SetupNewsrcSaveTimer();
+}
+
+nsNntpIncomingServer::~nsNntpIncomingServer()
+{
+ mozilla::DebugOnly<nsresult> rv;
+
+ if (mNewsrcSaveTimer) {
+ mNewsrcSaveTimer->Cancel();
+ mNewsrcSaveTimer = nullptr;
+ }
+ rv = ClearInner();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "ClearInner failed");
+
+ rv = CloseCachedConnections();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CloseCachedConnections failed");
+}
+
+NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, NotifyOn, "notify.on")
+NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, MarkOldRead, "mark_old_read")
+NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, Abbreviate, "abbreviate")
+NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, PushAuth, "always_authenticate")
+NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, SingleSignon, "singleSignon")
+NS_IMPL_SERVERPREF_INT(nsNntpIncomingServer, MaxArticles, "max_articles")
+
+nsresult
+nsNntpIncomingServer::CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder)
+{
+ nsMsgNewsFolder *newRootFolder = new nsMsgNewsFolder;
+ if (!newRootFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*rootFolder = newRootFolder);
+ newRootFolder->Init(serverUri.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetNewsrcFilePath(nsIFile **aNewsrcFilePath)
+{
+ nsresult rv;
+ if (mNewsrcFilePath)
+ {
+ *aNewsrcFilePath = mNewsrcFilePath;
+ NS_IF_ADDREF(*aNewsrcFilePath);
+ return NS_OK;
+ }
+
+ rv = GetFileValue("newsrc.file-rel", "newsrc.file", aNewsrcFilePath);
+ if (NS_SUCCEEDED(rv) && *aNewsrcFilePath)
+ {
+ mNewsrcFilePath = *aNewsrcFilePath;
+ return rv;
+ }
+
+ rv = GetNewsrcRootPath(getter_AddRefs(mNewsrcFilePath));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString hostname;
+ rv = GetHostName(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newsrcFileName(NEWSRC_FILE_PREFIX);
+ newsrcFileName.Append(hostname);
+ newsrcFileName.Append(NEWSRC_FILE_SUFFIX);
+ rv = mNewsrcFilePath->AppendNative(newsrcFileName);
+ rv = mNewsrcFilePath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetNewsrcFilePath(mNewsrcFilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aNewsrcFilePath = mNewsrcFilePath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetNewsrcFilePath(nsIFile *aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ bool exists;
+ nsresult rv = aFile->Exists(&exists);
+ if (!exists)
+ {
+ rv = aFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0664);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return SetFileValue("newsrc.file-rel", "newsrc.file", aFile);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetLocalStoreType(nsACString& type)
+{
+ type.AssignLiteral("news");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetLocalDatabaseType(nsACString& type)
+{
+ type.AssignLiteral("news");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetNewsrcRootPath(nsIFile *aNewsrcRootPath)
+{
+ NS_ENSURE_ARG(aNewsrcRootPath);
+ return NS_SetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT, aNewsrcRootPath);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetNewsrcRootPath(nsIFile **aNewsrcRootPath)
+{
+ NS_ENSURE_ARG_POINTER(aNewsrcRootPath);
+ *aNewsrcRootPath = nullptr;
+
+ bool havePref;
+ nsresult rv = NS_GetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL,
+ PREF_MAIL_NEWSRC_ROOT,
+ NS_APP_NEWS_50_DIR,
+ havePref,
+ aNewsrcRootPath);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = (*aNewsrcRootPath)->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = (*aNewsrcRootPath)->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!havePref || !exists)
+ {
+ rv = NS_SetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT, *aNewsrcRootPath);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
+ }
+ return rv;
+}
+
+/* static */ void nsNntpIncomingServer::OnNewsrcSaveTimer(nsITimer *timer, void *voidIncomingServer)
+{
+ nsNntpIncomingServer *incomingServer = (nsNntpIncomingServer*)voidIncomingServer;
+ incomingServer->WriteNewsrcFile();
+}
+
+nsresult nsNntpIncomingServer::SetupNewsrcSaveTimer()
+{
+ int64_t ms(300000); // hard code, 5 minutes.
+ //Convert biffDelay into milliseconds
+ uint32_t timeInMSUint32 = (uint32_t)ms;
+ //Can't currently reset a timer when it's in the process of
+ //calling Notify. So, just release the timer here and create a new one.
+ if(mNewsrcSaveTimer)
+ mNewsrcSaveTimer->Cancel();
+ mNewsrcSaveTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mNewsrcSaveTimer->InitWithFuncCallback(OnNewsrcSaveTimer, (void*)this, timeInMSUint32,
+ nsITimer::TYPE_REPEATING_SLACK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetCharset(const nsACString & aCharset)
+{
+ return SetCharValue("charset", aCharset);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCharset(nsACString & aCharset)
+{
+ //first we get the per-server settings mail.server.<serverkey>.charset
+ nsresult rv = GetCharValue("charset", aCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ //if the per-server setting is empty,we get the default charset from
+ //mailnews.view_default_charset setting and set it as per-server preference.
+ if (aCharset.IsEmpty()) {
+ nsString defaultCharset;
+ rv = NS_GetLocalizedUnicharPreferenceWithDefault(nullptr,
+ PREF_MAILNEWS_VIEW_DEFAULT_CHARSET,
+ NS_LITERAL_STRING("ISO-8859-1"), defaultCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LossyCopyUTF16toASCII(defaultCharset, aCharset);
+ SetCharset(aCharset);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::WriteNewsrcFile()
+{
+ nsresult rv;
+
+ bool newsrcHasChanged;
+ rv = GetNewsrcHasChanged(&newsrcHasChanged);
+ if (NS_FAILED(rv)) return rv;
+
+#ifdef DEBUG_NEWS
+ nsCString hostname;
+ rv = GetHostName(hostname);
+ if (NS_FAILED(rv)) return rv;
+#endif /* DEBUG_NEWS */
+
+ if (newsrcHasChanged) {
+#ifdef DEBUG_NEWS
+ printf("write newsrc file for %s\n", hostname.get());
+#endif
+ nsCOMPtr <nsIFile> newsrcFile;
+ rv = GetNewsrcFilePath(getter_AddRefs(newsrcFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> newsrcStream;
+ nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(newsrcStream), newsrcFile, -1, 00600);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(rootFolder, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t bytesWritten;
+ nsCString optionLines;
+ rv = newsFolder->GetOptionLines(optionLines);
+ if (NS_SUCCEEDED(rv) && !optionLines.IsEmpty()) {
+ newsrcStream->Write(optionLines.get(), optionLines.Length(), &bytesWritten);
+#ifdef DEBUG_NEWS
+ printf("option lines:\n%s", optionLines.get());
+#endif /* DEBUG_NEWS */
+ }
+#ifdef DEBUG_NEWS
+ else {
+ printf("no option lines to write out\n");
+ }
+#endif /* DEBUG_NEWS */
+
+ nsCString unsubscribedLines;
+ rv = newsFolder->GetUnsubscribedNewsgroupLines(unsubscribedLines);
+ if (NS_SUCCEEDED(rv) && !unsubscribedLines.IsEmpty()) {
+ newsrcStream->Write(unsubscribedLines.get(), unsubscribedLines.Length(), &bytesWritten);
+#ifdef DEBUG_NEWS
+ printf("unsubscribedLines:\n%s", unsubscribedLines.get());
+#endif /* DEBUG_NEWS */
+ }
+#ifdef DEBUG_NEWS
+ else {
+ printf("no unsubscribed lines to write out\n");
+ }
+#endif /* DEBUG_NEWS */
+
+ rv = rootFolder->GetSubFolders(getter_AddRefs(subFolders));
+ if (NS_FAILED(rv)) return rv;
+
+ bool moreFolders;
+
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) &&
+ moreFolders) {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ newsFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && newsFolder) {
+ nsCString newsrcLine;
+ rv = newsFolder->GetNewsrcLine(newsrcLine);
+ if (NS_SUCCEEDED(rv) && !newsrcLine.IsEmpty()) {
+ // write the line to the newsrc file
+ newsrcStream->Write(newsrcLine.get(), newsrcLine.Length(), &bytesWritten);
+ }
+ }
+ }
+ }
+
+ newsrcStream->Close();
+
+ rv = SetNewsrcHasChanged(false);
+ if (NS_FAILED(rv)) return rv;
+ }
+#ifdef DEBUG_NEWS
+ else {
+ printf("no need to write newsrc file for %s, it was not dirty\n", (hostname.get()));
+ }
+#endif /* DEBUG_NEWS */
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetNewsrcHasChanged(bool aNewsrcHasChanged)
+{
+ mNewsrcHasChanged = aNewsrcHasChanged;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetNewsrcHasChanged(bool *aNewsrcHasChanged)
+{
+ if (!aNewsrcHasChanged) return NS_ERROR_NULL_POINTER;
+
+ *aNewsrcHasChanged = mNewsrcHasChanged;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::CloseCachedConnections()
+{
+ nsresult rv;
+ nsCOMPtr<nsINNTPProtocol> connection;
+
+ // iterate through the connection cache and close the connections.
+ int32_t cnt = mConnectionCache.Count();
+
+ for (int32_t i = 0; i < cnt; ++i)
+ {
+ connection = mConnectionCache[0];
+ if (connection)
+ {
+ rv = connection->CloseConnection();
+ // We need to do this instead of RemoveObjectAt(0) because the
+ // above call will likely cause the object to be removed from the
+ // array anyway
+ mConnectionCache.RemoveObject(connection);
+ }
+ }
+
+ rv = WriteNewsrcFile();
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mGetOnlyNew && !mHostInfoLoaded)
+ {
+ rv = WriteHostInfoFile();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetMaximumConnectionsNumber(int32_t *aMaxConnections)
+{
+ NS_ENSURE_ARG_POINTER(aMaxConnections);
+
+ nsresult rv = GetIntValue("max_cached_connections", aMaxConnections);
+ // Get our maximum connection count. We need at least 1. If the value is 0,
+ // we use the default. If it's negative, we treat that as 1.
+ if (NS_SUCCEEDED(rv) && *aMaxConnections > 0)
+ return NS_OK;
+
+ *aMaxConnections = (NS_FAILED(rv) || (*aMaxConnections == 0)) ? 2 : 1;
+ (void)SetMaximumConnectionsNumber(*aMaxConnections);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetMaximumConnectionsNumber(int32_t aMaxConnections)
+{
+ return SetIntValue("max_cached_connections", aMaxConnections);
+}
+
+bool
+nsNntpIncomingServer::ConnectionTimeOut(nsINNTPProtocol* aConnection)
+{
+ bool retVal = false;
+ if (!aConnection)
+ return retVal;
+
+ PRTime lastActiveTimeStamp;
+ if (NS_FAILED(aConnection->GetLastActiveTimeStamp(&lastActiveTimeStamp)))
+ return retVal;
+
+ if (PR_Now() - lastActiveTimeStamp >= PRTime(170) * PR_USEC_PER_SEC)
+ {
+#ifdef DEBUG_seth
+ printf("XXX connection timed out, close it, and remove it from the connection cache\n");
+#endif
+ aConnection->CloseConnection();
+ mConnectionCache.RemoveObject(aConnection);
+ retVal = true;
+ }
+ return retVal;
+}
+
+
+nsresult
+nsNntpIncomingServer::CreateProtocolInstance(nsINNTPProtocol ** aNntpConnection, nsIURI *url,
+ nsIMsgWindow *aMsgWindow)
+{
+ // create a new connection and add it to the connection cache
+ // we may need to flag the protocol connection as busy so we don't get
+ // a race
+ // condition where someone else goes through this code
+ nsNNTPProtocol *protocolInstance = new nsNNTPProtocol(this, url, aMsgWindow);
+ if (!protocolInstance)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = protocolInstance->QueryInterface(NS_GET_IID(nsINNTPProtocol), (void **) aNntpConnection);
+ // take the protocol instance and add it to the connectionCache
+ if (NS_SUCCEEDED(rv) && *aNntpConnection)
+ mConnectionCache.AppendObject(*aNntpConnection);
+ return rv;
+}
+
+
+nsresult
+nsNntpIncomingServer::GetNntpConnection(nsIURI * aUri, nsIMsgWindow *aMsgWindow,
+ nsINNTPProtocol ** aNntpConnection)
+{
+ int32_t maxConnections;
+ (void)GetMaximumConnectionsNumber(&maxConnections);
+
+ // Find a non-busy connection
+ nsCOMPtr<nsINNTPProtocol> connection;
+ int32_t cnt = mConnectionCache.Count();
+ for (int32_t i = 0; i < cnt; i++)
+ {
+ connection = mConnectionCache[i];
+ if (connection)
+ {
+ bool isBusy;
+ connection->GetIsBusy(&isBusy);
+ if (!isBusy)
+ break;
+ connection = nullptr;
+ }
+ }
+
+ if (ConnectionTimeOut(connection))
+ {
+ connection = nullptr;
+ // We have one less connection, since we closed this one.
+ --cnt;
+ }
+
+ if (connection)
+ {
+ NS_IF_ADDREF(*aNntpConnection = connection);
+ connection->SetIsCachedConnection(true);
+ }
+ else if (cnt < maxConnections)
+ {
+ // We have room for another connection. Create this connection and return
+ // it to the caller.
+ nsresult rv = CreateProtocolInstance(aNntpConnection, aUri, aMsgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ // We maxed out our connection count. The caller must therefore enqueue the
+ // call.
+ *aNntpConnection = nullptr;
+ return NS_OK;
+ }
+
+ // Initialize the URI here and now.
+ return (*aNntpConnection)->Initialize(aUri, aMsgWindow);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetNntpChannel(nsIURI *aURI, nsIMsgWindow *aMsgWindow,
+ nsIChannel **aChannel)
+{
+ NS_ENSURE_ARG_POINTER(aChannel);
+
+ nsCOMPtr<nsINNTPProtocol> protocol;
+ nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (protocol)
+ return CallQueryInterface(protocol, aChannel);
+
+ // No protocol? We need our mock channel.
+ nsNntpMockChannel *channel = new nsNntpMockChannel(aURI, aMsgWindow);
+ if (!channel)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aChannel = channel);
+
+ m_queuedChannels.AppendElement(channel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::LoadNewsUrl(nsIURI *aURI, nsIMsgWindow *aMsgWindow,
+ nsISupports *aConsumer)
+{
+ nsCOMPtr<nsINNTPProtocol> protocol;
+ nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (protocol)
+ return protocol->LoadNewsUrl(aURI, aConsumer);
+
+ // No protocol? We need our mock channel.
+ nsNntpMockChannel *channel = new nsNntpMockChannel(aURI, aMsgWindow,
+ aConsumer);
+ if (!channel)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ m_queuedChannels.AppendElement(channel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PrepareForNextUrl(nsNNTPProtocol *aConnection)
+{
+ NS_ENSURE_ARG(aConnection);
+
+ // Start the connection on the next URL in the queue. If it can't get a URL to
+ // work, drop that URL (the channel will handle failure notification) and move
+ // on.
+ while (m_queuedChannels.Length() > 0)
+ {
+ RefPtr<nsNntpMockChannel> channel = m_queuedChannels[0];
+ m_queuedChannels.RemoveElementAt(0);
+ nsresult rv = channel->AttachNNTPConnection(*aConnection);
+ // If this succeeded, the connection is now running the URL.
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+ }
+
+ // No queued uris.
+ return NS_OK;
+}
+
+/* void RemoveConnection (in nsINNTPProtocol aNntpConnection); */
+NS_IMETHODIMP nsNntpIncomingServer::RemoveConnection(nsINNTPProtocol *aNntpConnection)
+{
+ if (aNntpConnection)
+ mConnectionCache.RemoveObject(aNntpConnection);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PerformExpand(nsIMsgWindow *aMsgWindow)
+{
+ // Get news.update_unread_on_expand pref
+ nsresult rv;
+ bool updateUnreadOnExpand = true;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ prefBranch->GetBoolPref("news.update_unread_on_expand", &updateUnreadOnExpand);
+
+ // Only if news.update_unread_on_expand is true do we update the unread counts
+ if (updateUnreadOnExpand)
+ return DownloadMail(aMsgWindow);
+ return NS_OK;
+}
+
+nsresult
+nsNntpIncomingServer::DownloadMail(nsIMsgWindow *aMsgWindow)
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> groups;
+ rv = rootFolder->GetSubFolders(getter_AddRefs(groups));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasNext;
+ while (NS_SUCCEEDED(rv = groups->HasMoreElements(&hasNext)) && hasNext)
+ {
+ nsCOMPtr<nsISupports> nextGroup;
+ rv = groups->GetNext(getter_AddRefs(nextGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> group(do_QueryInterface(nextGroup));
+ rv = group->GetNewMessages(aMsgWindow, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::DisplaySubscribedGroup(nsIMsgNewsFolder *aMsgFolder, int32_t aFirstMessage, int32_t aLastMessage, int32_t aTotalMessages)
+{
+ nsresult rv;
+
+ if (!aMsgFolder) return NS_ERROR_NULL_POINTER;
+#ifdef DEBUG_NEWS
+ printf("DisplaySubscribedGroup(...,%ld,%ld,%ld)\n",aFirstMessage,aLastMessage,aTotalMessages);
+#endif
+ rv = aMsgFolder->UpdateSummaryFromNNTPInfo(aFirstMessage,aLastMessage,aTotalMessages);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PerformBiff(nsIMsgWindow *aMsgWindow)
+{
+ // Biff will force a download of the messages. If the user doesn't want this
+ // (e.g., there is a lot of high-traffic newsgroups), the better option is to
+ // just ignore biff.
+ return PerformExpand(aMsgWindow);
+}
+
+NS_IMETHODIMP nsNntpIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff)
+{
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ *aServerRequiresPasswordForBiff = false; // for news, biff is getting the unread counts
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::OnStartRunningUrl(nsIURI *url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::OnStopRunningUrl(nsIURI *url, nsresult exitCode)
+{
+ nsresult rv;
+ rv = UpdateSubscribed();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = StopPopulating(mMsgWindow);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::ContainsNewsgroup(const nsACString &aName,
+ bool *containsGroup)
+{
+ NS_ENSURE_ARG_POINTER(containsGroup);
+ NS_ENSURE_FALSE(aName.IsEmpty(), NS_ERROR_FAILURE);
+
+ if (mSubscribedNewsgroups.Length() == 0)
+ {
+ // If this is empty, we may need to discover folders
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ {
+ nsCOMPtr<nsISimpleEnumerator> subfolders;
+ rootFolder->GetSubFolders(getter_AddRefs(subfolders));
+ }
+ }
+ nsAutoCString unescapedName;
+ MsgUnescapeString(aName, 0, unescapedName);
+ *containsGroup = mSubscribedNewsgroups.Contains(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SubscribeToNewsgroup(const nsACString &aName)
+{
+ NS_ASSERTION(!aName.IsEmpty(), "no name");
+ NS_ENSURE_FALSE(aName.IsEmpty(), NS_ERROR_FAILURE);
+
+ // If we already have this newsgroup, do nothing and report success.
+ bool containsGroup = false;
+ nsresult rv = ContainsNewsgroup(aName, &containsGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (containsGroup)
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> msgfolder;
+ rv = GetRootMsgFolder(getter_AddRefs(msgfolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(msgfolder, NS_ERROR_FAILURE);
+
+ return msgfolder->CreateSubfolder(NS_ConvertUTF8toUTF16(aName), nullptr);
+}
+
+bool
+writeGroupToHostInfoFile(nsCString &aElement, void *aData)
+{
+ nsIOutputStream *stream;
+ stream = (nsIOutputStream *)aData;
+ NS_ASSERTION(stream, "no stream");
+ if (!stream) {
+ // stop, something is bad.
+ return false;
+ }
+ return true;
+}
+
+void nsNntpIncomingServer::WriteLine(nsIOutputStream *stream, nsCString &str)
+{
+ uint32_t bytesWritten;
+ str.Append(MSG_LINEBREAK);
+ stream->Write(str.get(), str.Length(), &bytesWritten);
+}
+nsresult
+nsNntpIncomingServer::WriteHostInfoFile()
+{
+ if (!mHostInfoHasChanged)
+ return NS_OK;
+
+ mLastUpdatedTime = uint32_t(PR_Now() / PR_USEC_PER_SEC);
+
+ nsCString hostname;
+ nsresult rv = GetHostName(hostname);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!mHostInfoFile)
+ return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIOutputStream> hostInfoStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(hostInfoStream), mHostInfoFile, -1, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX TODO: missing some formatting, see the 4.x code
+ nsAutoCString header("# News host information file.");
+ WriteLine(hostInfoStream, header);
+ header.Assign("# This is a generated file! Do not edit.");
+ WriteLine(hostInfoStream, header);
+ header.Truncate();
+ WriteLine(hostInfoStream, header);
+ nsAutoCString version("version=");
+ version.AppendInt(VALID_VERSION);
+ WriteLine(hostInfoStream, version);
+ nsAutoCString newsrcname("newsrcname=");
+ newsrcname.Append(hostname);
+ WriteLine(hostInfoStream, hostname);
+ nsAutoCString dateStr("lastgroupdate=");
+ dateStr.AppendInt(mLastUpdatedTime);
+ WriteLine(hostInfoStream, dateStr);
+ dateStr = "uniqueid=";
+ dateStr.AppendInt(mUniqueId);
+ WriteLine(hostInfoStream, dateStr);
+ header.Assign(MSG_LINEBREAK"begingroups");
+ WriteLine(hostInfoStream, header);
+
+ // XXX TODO: sort groups first?
+ uint32_t length = mGroupsOnServer.Length();
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ uint32_t bytesWritten;
+ hostInfoStream->Write(mGroupsOnServer[i].get(), mGroupsOnServer[i].Length(),
+ &bytesWritten);
+ hostInfoStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten);
+ }
+
+ hostInfoStream->Close();
+ mHostInfoHasChanged = false;
+ return NS_OK;
+}
+
+nsresult
+nsNntpIncomingServer::LoadHostInfoFile()
+{
+ nsresult rv;
+ // we haven't loaded it yet
+ mHostInfoLoaded = false;
+
+ rv = GetLocalPath(getter_AddRefs(mHostInfoFile));
+ if (NS_FAILED(rv)) return rv;
+ if (!mHostInfoFile) return NS_ERROR_FAILURE;
+
+ rv = mHostInfoFile->AppendNative(NS_LITERAL_CSTRING(HOSTINFO_FILE_NAME));
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ rv = mHostInfoFile->Exists(&exists);
+ if (NS_FAILED(rv)) return rv;
+
+ // it is ok if the hostinfo.dat file does not exist.
+ if (!exists) return NS_OK;
+
+ nsCOMPtr<nsIInputStream> fileStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), mHostInfoFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsCString line;
+
+ while (more && NS_SUCCEEDED(rv))
+ {
+ rv = lineInputStream->ReadLine(line, &more);
+ if (line.IsEmpty())
+ continue;
+ HandleLine(line.get(), line.Length());
+ }
+ mHasSeenBeginGroups = false;
+ fileStream->Close();
+
+ return UpdateSubscribed();
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::StartPopulatingWithUri(nsIMsgWindow *aMsgWindow, bool aForceToServer, const char *uri)
+{
+#ifdef DEBUG_seth
+ printf("StartPopulatingWithUri(%s)\n",uri);
+#endif
+
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mInner->StartPopulatingWithUri(aMsgWindow, aForceToServer, uri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = StopPopulating(mMsgWindow);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SubscribeCleanup()
+{
+ nsresult rv = NS_OK;
+ rv = ClearInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::StartPopulating(nsIMsgWindow *aMsgWindow, bool aForceToServer, bool aGetOnlyNew)
+{
+ mMsgWindow = aMsgWindow;
+
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mInner->StartPopulating(aMsgWindow, aForceToServer, aGetOnlyNew);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = SetDelimiter(NEWS_DELIMITER);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = SetShowFullName(true);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ mHostInfoLoaded = false;
+ mVersion = INVALID_VERSION;
+ mGroupsOnServer.Clear();
+ mGetOnlyNew = aGetOnlyNew;
+
+ if (!aForceToServer) {
+ rv = LoadHostInfoFile();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // mHostInfoLoaded can be false if we failed to load anything
+ if (aForceToServer || !mHostInfoLoaded || (mVersion != VALID_VERSION)) {
+ // set these to true, so when we are done and we call WriteHostInfoFile()
+ // we'll write out to hostinfo.dat
+ mHostInfoHasChanged = true;
+ mVersion = VALID_VERSION;
+
+ mGroupsOnServer.Clear();
+ rv = nntpService->GetListOfGroupsOnServer(this, aMsgWindow, aGetOnlyNew);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ rv = StopPopulating(aMsgWindow);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * This method is the entry point for |nsNNTPProtocol| class. |aName| is now
+ * encoded in the serverside character encoding, but we need to handle
+ * newsgroup names in UTF-8 internally, So we convert |aName| to
+ * UTF-8 here for later use.
+ **/
+NS_IMETHODIMP
+nsNntpIncomingServer::AddNewsgroupToList(const char *aName)
+{
+ nsresult rv;
+
+ nsAutoString newsgroupName;
+ nsAutoCString dataCharset;
+ rv = GetCharset(dataCharset);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nsMsgI18NConvertToUnicode(dataCharset.get(),
+ nsDependentCString(aName),
+ newsgroupName);
+#ifdef DEBUG_jungshik
+ NS_ASSERTION(NS_SUCCEEDED(rv), "newsgroup name conversion failed");
+#endif
+ if (NS_FAILED(rv)) {
+ CopyASCIItoUTF16(nsDependentCString(aName), newsgroupName);
+ }
+
+ rv = AddTo(NS_ConvertUTF16toUTF8(newsgroupName),
+ false, true, true);
+ if (NS_FAILED(rv)) return rv;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetIncomingServer(nsIMsgIncomingServer *aServer)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetIncomingServer(aServer);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetShowFullName(bool showFullName)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetShowFullName(showFullName);
+}
+
+nsresult
+nsNntpIncomingServer::ClearInner()
+{
+ nsresult rv = NS_OK;
+
+ if (mInner) {
+ rv = mInner->SetSubscribeListener(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mInner->SetIncomingServer(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ mInner = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsNntpIncomingServer::EnsureInner()
+{
+ nsresult rv = NS_OK;
+
+ if (mInner)
+ return NS_OK;
+
+ mInner = do_CreateInstance(kSubscribableServerCID,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!mInner)
+ return NS_ERROR_FAILURE;
+
+ rv = SetIncomingServer(this);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetDelimiter(char *aDelimiter)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetDelimiter(aDelimiter);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetDelimiter(char aDelimiter)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetDelimiter(aDelimiter);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetAsSubscribed(const nsACString &path)
+{
+ mTempSubscribed.AppendElement(path);
+ if (mGetOnlyNew && (!mGroupsOnServer.Contains(path)))
+ return NS_OK;
+
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetAsSubscribed(path);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::UpdateSubscribed()
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ mTempSubscribed.Clear();
+ uint32_t length = mSubscribedNewsgroups.Length();
+ for (uint32_t i = 0; i < length; ++i)
+ SetAsSubscribed(mSubscribedNewsgroups[i]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddTo(const nsACString &aName, bool addAsSubscribed,
+ bool aSubscribable, bool changeIfExists)
+{
+ NS_ASSERTION(MsgIsUTF8(aName), "Non-UTF-8 newsgroup name");
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AddGroupOnServer(aName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mInner->AddTo(aName, addAsSubscribed, aSubscribable, changeIfExists);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::StopPopulating(nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsISubscribeListener> listener;
+ rv = GetSubscribeListener(getter_AddRefs(listener));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!listener)
+ return NS_ERROR_FAILURE;
+
+ rv = listener->OnDonePopulating();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mInner->StopPopulating(aMsgWindow);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!mGetOnlyNew && !mHostInfoLoaded)
+ {
+ rv = WriteHostInfoFile();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // XXX TODO: when do I set this to null?
+ // rv = ClearInner();
+ // NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetSubscribeListener(nsISubscribeListener *aListener)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetSubscribeListener(aListener);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSubscribeListener(nsISubscribeListener **aListener)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetSubscribeListener(aListener);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::Subscribe(const char16_t *aUnicharName)
+{
+ return SubscribeToNewsgroup(NS_ConvertUTF16toUTF8(aUnicharName));
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::Unsubscribe(const char16_t *aUnicharName)
+{
+ NS_ENSURE_ARG_POINTER(aUnicharName);
+
+ nsresult rv;
+
+ nsCOMPtr <nsIMsgFolder> serverFolder;
+ rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!serverFolder)
+ return NS_ERROR_FAILURE;
+
+ // to handle non-ASCII newsgroup names, we store them internally as escaped.
+ // so we need to escape and encode the name, in order to find it.
+ nsAutoCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(nsDependentString(aUnicharName), escapedName);
+
+ nsCOMPtr <nsIMsgFolder> newsgroupFolder;
+ rv = serverFolder->FindSubFolder(escapedName,
+ getter_AddRefs(newsgroupFolder));
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!newsgroupFolder)
+ return NS_ERROR_FAILURE;
+
+ rv = serverFolder->PropagateDelete(newsgroupFolder, true /* delete storage */, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // since we've unsubscribed to a newsgroup, the newsrc needs to be written out
+ rv = SetNewsrcHasChanged(true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return NS_OK;
+}
+
+nsresult
+nsNntpIncomingServer::HandleLine(const char* line, uint32_t line_size)
+{
+ NS_ASSERTION(line, "line is null");
+ if (!line)
+ return NS_OK;
+
+ // skip blank lines and comments
+ if (line[0] == '#' || line[0] == '\0')
+ return NS_OK;
+ // XXX TODO: make this truly const, maybe pass in an nsCString &
+
+ if (mHasSeenBeginGroups) {
+ // v1 hostinfo files had additional data fields delimited by commas.
+ // with v2 hostinfo files, the additional data fields are removed.
+ char *commaPos = (char *) PL_strchr(line,',');
+ if (commaPos) *commaPos = 0;
+
+ // newsrc entries are all in UTF-8
+#ifdef DEBUG_jungshik
+ NS_ASSERTION(MsgIsUTF8(nsDependentCString(line)), "newsrc line is not utf-8");
+#endif
+ nsresult rv = AddTo(nsDependentCString(line), false, true, true);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to add line");
+ if (NS_SUCCEEDED(rv)) {
+ // since we've seen one group, we can claim we've loaded the
+ // hostinfo file
+ mHostInfoLoaded = true;
+ }
+ }
+ else {
+ if (PL_strncmp(line,"begingroups", 11) == 0) {
+ mHasSeenBeginGroups = true;
+ }
+ char*equalPos = (char *) PL_strchr(line, '=');
+ if (equalPos) {
+ *equalPos++ = '\0';
+ if (PL_strcmp(line, "lastgroupdate") == 0) {
+ mLastUpdatedTime = strtoul(equalPos, nullptr, 10);
+ } else if (PL_strcmp(line, "uniqueid") == 0) {
+ mUniqueId = strtol(equalPos, nullptr, 16);
+ } else if (PL_strcmp(line, "version") == 0) {
+ mVersion = strtol(equalPos, nullptr, 16);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNntpIncomingServer::AddGroupOnServer(const nsACString &aName)
+{
+ mGroupsOnServer.AppendElement(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddNewsgroup(const nsAString &aName)
+{
+ // handle duplicates?
+ mSubscribedNewsgroups.AppendElement(NS_ConvertUTF16toUTF8(aName));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::RemoveNewsgroup(const nsAString &aName)
+{
+ // handle duplicates?
+ mSubscribedNewsgroups.RemoveElement(NS_ConvertUTF16toUTF8(aName));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetState(const nsACString &path, bool state,
+ bool *stateChanged)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mInner->SetState(path, state, stateChanged);
+ if (*stateChanged) {
+ if (state)
+ mTempSubscribed.AppendElement(path);
+ else
+ mTempSubscribed.RemoveElement(path);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::HasChildren(const nsACString &path, bool *aHasChildren)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->HasChildren(path, aHasChildren);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsSubscribed(const nsACString &path,
+ bool *aIsSubscribed)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->IsSubscribed(path, aIsSubscribed);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsSubscribable(const nsACString &path,
+ bool *aIsSubscribable)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->IsSubscribable(path, aIsSubscribable);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetLeafName(const nsACString &path, nsAString &aLeafName)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetLeafName(path, aLeafName);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetFirstChildURI(const nsACString &path, nsACString &aResult)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetFirstChildURI(path, aResult);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetChildren(const nsACString &aPath,
+ nsISimpleEnumerator **aResult)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetChildren(aPath, aResult);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::CommitSubscribeChanges()
+{
+ // we force the newrc to be dirty, so it will get written out when
+ // we call WriteNewsrcFile()
+ nsresult rv = SetNewsrcHasChanged(true);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return WriteNewsrcFile();
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::ForgetPassword()
+{
+ // clear password of root folder (for the news account)
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!rootFolder) return NS_ERROR_FAILURE;
+
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(rootFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!newsFolder) return NS_ERROR_FAILURE;
+
+ rv = newsFolder->ForgetAuthenticationCredentials();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // clear password of all child folders
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+
+ rv = rootFolder->GetSubFolders(getter_AddRefs(subFolders));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool moreFolders = false;
+
+ nsresult return_rv = NS_OK;
+
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) &&
+ moreFolders) {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ newsFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && newsFolder) {
+ rv = newsFolder->ForgetAuthenticationCredentials();
+ if (NS_FAILED(rv))
+ return_rv = rv;
+ }
+ else {
+ return_rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ return return_rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSupportsExtensions(bool *aSupportsExtensions)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetSupportsExtensions(bool aSupportsExtensions)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddExtension(const char *extension)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::QueryExtension(const char *extension, bool *result)
+{
+#ifdef DEBUG_seth
+ printf("no extension support yet\n");
+#endif
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetPostingAllowed(bool *aPostingAllowed)
+{
+ *aPostingAllowed = mPostingAllowed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetPostingAllowed(bool aPostingAllowed)
+{
+ mPostingAllowed = aPostingAllowed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetLastUpdatedTime(uint32_t *aLastUpdatedTime)
+{
+ *aLastUpdatedTime = mLastUpdatedTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetLastUpdatedTime(uint32_t aLastUpdatedTime)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddPropertyForGet(const char *name, const char *value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::QueryPropertyForGet(const char *name, char **value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddSearchableGroup(const nsAString &name)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::QuerySearchableGroup(const nsAString &name, bool *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddSearchableHeader(const char *name)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::QuerySearchableHeader(const char *name, bool *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::FindGroup(const nsACString &name, nsIMsgNewsFolder **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> serverFolder;
+ rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!serverFolder) return NS_ERROR_FAILURE;
+
+ // Escape the name for using FindSubFolder
+ nsAutoCString escapedName;
+ rv = MsgEscapeString(name, nsINetUtil::ESCAPE_URL_PATH, escapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgFolder> subFolder;
+ rv = serverFolder->FindSubFolder(escapedName, getter_AddRefs(subFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!subFolder) return NS_ERROR_FAILURE;
+
+ rv = subFolder->QueryInterface(NS_GET_IID(nsIMsgNewsFolder), (void**)result);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!*result) return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetFirstGroupNeedingExtraInfo(nsACString &result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetGroupNeedsExtraInfo(const nsACString &name,
+ bool needsExtraInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GroupNotFound(nsIMsgWindow *aMsgWindow,
+ const nsAString &aName, bool aOpening)
+{
+ nsresult rv;
+ nsCOMPtr <nsIPrompt> prompt;
+
+ if (aMsgWindow) {
+ rv = aMsgWindow->GetPromptDialog(getter_AddRefs(prompt));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "no prompt from the msg window");
+ }
+
+ if (!prompt) {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ rv = wwatch->GetNewPrompter(nullptr, getter_AddRefs(prompt));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ nsCOMPtr <nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr <nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString hostname;
+ rv = GetRealHostName(hostname);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ConvertUTF8toUTF16 hostStr(hostname);
+
+ nsString groupName(aName);
+ const char16_t *formatStrings[2] = { groupName.get(), hostStr.get() };
+ nsString confirmText;
+ rv = bundle->FormatStringFromName(
+ u"autoUnsubscribeText",
+ formatStrings, 2,
+ getter_Copies(confirmText));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool confirmResult = false;
+ rv = prompt->Confirm(nullptr, confirmText.get(), &confirmResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (confirmResult) {
+ rv = Unsubscribe(groupName.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetPrettyNameForGroup(const nsAString &name,
+ const nsAString &prettyName)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCanSearchMessages(bool *canSearchMessages)
+{
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ *canSearchMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetOfflineSupportLevel(int32_t *aSupportLevel)
+{
+ NS_ENSURE_ARG_POINTER(aSupportLevel);
+ nsresult rv;
+
+ rv = GetIntValue("offline_support_level", aSupportLevel);
+ if (*aSupportLevel != OFFLINE_SUPPORT_LEVEL_UNDEFINED) return rv;
+
+ // set default value
+ *aSupportLevel = OFFLINE_SUPPORT_LEVEL_EXTENDED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetDefaultCopiesAndFoldersPrefsToServer(bool *aCopiesAndFoldersOnServer)
+{
+ NS_ENSURE_ARG_POINTER(aCopiesAndFoldersOnServer);
+
+ /**
+ * When a news account is created, the copies and folder prefs for the
+ * associated identity don't point to folders on the server.
+ * This makes sense, since there is no "Drafts" folder on a news server.
+ * They'll point to the ones on "Local Folders"
+ */
+
+ *aCopiesAndFoldersOnServer = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer)
+{
+ NS_ENSURE_ARG_POINTER(aCanCreateFoldersOnServer);
+
+ // No folder creation on news servers. Return false.
+ *aCanCreateFoldersOnServer = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetSearchValue(const nsAString &aSearchValue)
+{
+ nsCString searchValue = NS_ConvertUTF16toUTF8(aSearchValue);
+ MsgCompressWhitespace(searchValue);
+
+ if (mTree) {
+ mTree->BeginUpdateBatch();
+ mTree->RowCountChanged(0, -static_cast<int32_t>(mSubscribeSearchResult.Length()));
+ }
+
+ nsTArray<nsCString> searchStringParts;
+ if (!searchValue.IsEmpty())
+ ParseString(searchValue, ' ', searchStringParts);
+
+ mSubscribeSearchResult.Clear();
+ uint32_t length = mGroupsOnServer.Length();
+ for (uint32_t i = 0; i < length; i++)
+ {
+ // check that all parts of the search string occur
+ bool found = true;
+ for (uint32_t j = 0; j < searchStringParts.Length(); ++j) {
+ if (MsgFind(mGroupsOnServer[i], searchStringParts[j], true, 0) == kNotFound) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found)
+ mSubscribeSearchResult.AppendElement(mGroupsOnServer[i]);
+ }
+
+ nsCStringLowerCaseComparator comparator;
+ mSubscribeSearchResult.Sort(comparator);
+
+ if (mTree)
+ {
+ mTree->RowCountChanged(0, mSubscribeSearchResult.Length());
+ mTree->EndUpdateBatch();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSupportsSubscribeSearch(bool *retVal)
+{
+ *retVal = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetRowCount(int32_t *aRowCount)
+{
+ *aRowCount = mSubscribeSearchResult.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSelection(nsITreeSelection * *aSelection)
+{
+ *aSelection = mTreeSelection;
+ NS_IF_ADDREF(*aSelection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetSelection(nsITreeSelection * aSelection)
+{
+ mTreeSelection = aSelection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetRowProperties(int32_t index, nsAString& properties)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCellProperties(int32_t row, nsITreeColumn* col, nsAString& properties)
+{
+ if (!IsValidRow(row))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ENSURE_ARG_POINTER(col);
+
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+ if (colID[0] == 's') {
+ // if <name> is in our temporary list of subscribed groups
+ // add the "subscribed" property so the check mark shows up
+ // in the "subscribedCol"
+ if (mSearchResultSortDescending)
+ row = mSubscribeSearchResult.Length() - 1 - row;
+ if (mTempSubscribed.Contains(mSubscribeSearchResult.ElementAt(row))) {
+ properties.AssignLiteral("subscribed");
+ }
+ }
+ else if (colID[0] == 'n') {
+ // add the "nntp" property to the "nameCol"
+ // so we get the news folder icon in the search view
+ properties.AssignLiteral("nntp");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetColumnProperties(nsITreeColumn* col, nsAString& properties)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsContainer(int32_t index, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsContainerOpen(int32_t index, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsContainerEmpty(int32_t index, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsSeparator(int32_t index, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsSorted(bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::CanDrop(int32_t index,
+ int32_t orientation,
+ nsIDOMDataTransfer *dataTransfer,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::Drop(int32_t row,
+ int32_t orientation,
+ nsIDOMDataTransfer *dataTransfer)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetParentIndex(int32_t rowIndex, int32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetLevel(int32_t index, int32_t *_retval)
+{
+ *_retval = 0;
+ return NS_OK;
+}
+
+bool
+nsNntpIncomingServer::IsValidRow(int32_t row)
+{
+ return ((row >= 0) && (row < (int32_t)mSubscribeSearchResult.Length()));
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ if (!IsValidRow(row))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ENSURE_ARG_POINTER(col);
+
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+
+ nsresult rv = NS_OK;
+ if (colID[0] == 'n') {
+ nsAutoCString str;
+ if (mSearchResultSortDescending)
+ row = mSubscribeSearchResult.Length() - 1 - row;
+ // some servers have newsgroup names that are non ASCII. we store
+ // those as escaped. unescape here so the UI is consistent
+ rv = NS_MsgDecodeUnescapeURLPath(mSubscribeSearchResult.ElementAt(row), _retval);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetTree(nsITreeBoxObject *tree)
+{
+ mTree = tree;
+ if (!tree)
+ return NS_OK;
+
+ nsCOMPtr<nsITreeColumns> cols;
+ tree->GetColumns(getter_AddRefs(cols));
+ if (!cols)
+ return NS_OK;
+
+ nsCOMPtr<nsITreeColumn> col;
+ cols->GetKeyColumn(getter_AddRefs(col));
+ if (!col)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMElement> element;
+ col->GetElement(getter_AddRefs(element));
+ if (!element)
+ return NS_OK;
+
+ nsAutoString dir;
+ element->GetAttribute(NS_LITERAL_STRING("sortDirection"), dir);
+ mSearchResultSortDescending = dir.EqualsLiteral("descending");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::ToggleOpenState(int32_t index)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::CycleHeader(nsITreeColumn* col)
+{
+ NS_ENSURE_ARG_POINTER(col);
+
+ bool cycler;
+ col->GetCycler(&cycler);
+ if (!cycler) {
+ NS_NAMED_LITERAL_STRING(dir, "sortDirection");
+ nsCOMPtr<nsIDOMElement> element;
+ col->GetElement(getter_AddRefs(element));
+ mSearchResultSortDescending = !mSearchResultSortDescending;
+ element->SetAttribute(dir, mSearchResultSortDescending ?
+ NS_LITERAL_STRING("descending") : NS_LITERAL_STRING("ascending"));
+ mTree->Invalidate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SelectionChanged()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::CycleCell(int32_t row, nsITreeColumn* col)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsEditable(int32_t row, nsITreeColumn* col, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsSelectable(int32_t row, nsITreeColumn* col, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PerformAction(const char16_t *action)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PerformActionOnRow(const char16_t *action, int32_t row)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PerformActionOnCell(const char16_t *action, int32_t row, nsITreeColumn* col)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer)
+{
+ NS_ENSURE_ARG_POINTER(aCanFileMessagesOnServer);
+
+ // No folder creation on news servers. Return false.
+ *aCanFileMessagesOnServer = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetFilterScope(nsMsgSearchScopeValue *filterScope)
+{
+ NS_ENSURE_ARG_POINTER(filterScope);
+
+ *filterScope = nsMsgSearchScope::newsFilter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSearchScope(nsMsgSearchScopeValue *searchScope)
+{
+ NS_ENSURE_ARG_POINTER(searchScope);
+
+ if (WeAreOffline()) {
+ // This value is set to the localNewsBody scope to be compatible with
+ // the legacy default value.
+ *searchScope = nsMsgSearchScope::localNewsBody;
+ }
+ else {
+ *searchScope = nsMsgSearchScope::news;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSocketType(int32_t *aSocketType)
+{
+ NS_ENSURE_ARG_POINTER(aSocketType);
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mPrefBranch->GetIntPref("socketType", aSocketType);
+ if (NS_FAILED(rv))
+ {
+ if (!mDefPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+ rv = mDefPrefBranch->GetIntPref("socketType", aSocketType);
+ if (NS_FAILED(rv))
+ *aSocketType = nsMsgSocketType::plain;
+ }
+
+ // nsMsgIncomingServer::GetSocketType migrates old isSecure to socketType
+ // style for mail. Unfortunately, a bug caused news socketType 0 to be stored
+ // in the prefs even for isSecure true, so the migration wouldn't happen :(
+
+ // Now that we know the socket, make sure isSecure true + socketType 0
+ // doesn't mix. Migrate if that's the case here.
+ if (*aSocketType == nsMsgSocketType::plain)
+ {
+ bool isSecure = false;
+ nsresult rv2 = mPrefBranch->GetBoolPref("isSecure", &isSecure);
+ if (NS_SUCCEEDED(rv2) && isSecure)
+ {
+ *aSocketType = nsMsgSocketType::SSL;
+ // Don't call virtual method in case overrides call GetSocketType.
+ nsMsgIncomingServer::SetSocketType(*aSocketType);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetSocketType(int32_t aSocketType)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+ nsresult rv = nsMsgIncomingServer::SetSocketType(aSocketType);
+ if (NS_SUCCEEDED(rv))
+ {
+ bool isSecure = false;
+ if (NS_SUCCEEDED(mPrefBranch->GetBoolPref("isSecure", &isSecure)))
+ {
+ // Must keep isSecure in sync since we migrate based on it... if it's set.
+ rv = mPrefBranch->SetBoolPref("isSecure",
+ aSocketType == nsMsgSocketType::SSL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged)
+{
+ nsresult rv;
+ // 1. Do common things in the base class.
+ rv = nsMsgIncomingServer::OnUserOrHostNameChanged(oldName, newName, hostnameChanged);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // 2. Remove file hostinfo.dat so that the new subscribe
+ // list will be reloaded from the new server.
+ nsCOMPtr <nsIFile> hostInfoFile;
+ rv = GetLocalPath(getter_AddRefs(hostInfoFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = hostInfoFile->AppendNative(NS_LITERAL_CSTRING(HOSTINFO_FILE_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+ hostInfoFile->Remove(false);
+
+ // 3.Unsubscribe and then subscribe the existing groups to clean up the article numbers
+ // in the rc file (this is because the old and new servers may maintain different
+ // numbers for the same articles if both servers handle the same groups).
+ nsCOMPtr <nsIMsgFolder> serverFolder;
+ rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ rv = serverFolder->GetSubFolders(getter_AddRefs(subFolders));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsTArray<nsString> groupList;
+ nsString folderName;
+
+ // Prepare the group list
+ bool hasMore;
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ subFolders->GetNext(getter_AddRefs(item));
+ nsCOMPtr<nsIMsgFolder> newsgroupFolder(do_QueryInterface(item));
+ if (!newsgroupFolder)
+ continue;
+
+ rv = newsgroupFolder->GetName(folderName);
+ NS_ENSURE_SUCCESS(rv,rv);
+ groupList.AppendElement(folderName);
+ }
+
+ // If nothing subscribed then we're done.
+ if (groupList.Length() == 0)
+ return NS_OK;
+
+ // Now unsubscribe & subscribe.
+ uint32_t i;
+ uint32_t cnt = groupList.Length();
+ nsAutoCString cname;
+ for (i = 0; i < cnt; i++)
+ {
+ // unsubscribe.
+ rv = Unsubscribe(groupList[i].get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ for (i = 0; i < cnt; i++)
+ {
+ // subscribe.
+ rv = SubscribeToNewsgroup(NS_ConvertUTF16toUTF8(groupList[i]));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // Force updating the rc file.
+ return CommitSubscribeChanges();
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSortOrder(int32_t* aSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = 500000000;
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNntpIncomingServer.h b/mailnews/news/src/nsNntpIncomingServer.h
new file mode 100644
index 000000000..b2196f05d
--- /dev/null
+++ b/mailnews/news/src/nsNntpIncomingServer.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsNntpIncomingServer_h
+#define __nsNntpIncomingServer_h
+
+#include "nsINntpIncomingServer.h"
+#include "nsIUrlListener.h"
+#include "nscore.h"
+
+#include "nsMsgIncomingServer.h"
+
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+
+#include "nsIMsgWindow.h"
+#include "nsISubscribableServer.h"
+#include "nsITimer.h"
+#include "nsIFile.h"
+#include "nsITreeView.h"
+#include "nsITreeSelection.h"
+#include "nsIAtom.h"
+#include "nsCOMArray.h"
+
+#include "nsNntpMockChannel.h"
+#include "nsAutoPtr.h"
+
+class nsINntpUrl;
+class nsIMsgMailNewsUrl;
+
+/* get some implementation from nsMsgIncomingServer */
+class nsNntpIncomingServer : public nsMsgIncomingServer,
+ public nsINntpIncomingServer,
+ public nsIUrlListener,
+ public nsISubscribableServer,
+ public nsITreeView
+
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINNTPINCOMINGSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSISUBSCRIBABLESERVER
+ NS_DECL_NSITREEVIEW
+
+ nsNntpIncomingServer();
+
+ NS_IMETHOD GetLocalStoreType(nsACString& type) override;
+ NS_IMETHOD GetLocalDatabaseType(nsACString& type) override;
+ NS_IMETHOD CloseCachedConnections() override;
+ NS_IMETHOD PerformBiff(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD PerformExpand(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged) override;
+
+ // for nsMsgLineBuffer
+ virtual nsresult HandleLine(const char *line, uint32_t line_size);
+
+ // override to clear all passwords associated with server
+ NS_IMETHODIMP ForgetPassword() override;
+ NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override;
+ NS_IMETHOD GetOfflineSupportLevel(int32_t *aSupportLevel) override;
+ NS_IMETHOD GetDefaultCopiesAndFoldersPrefsToServer(bool *aCopiesAndFoldersOnServer) override;
+ NS_IMETHOD GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer) override;
+ NS_IMETHOD GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer) override;
+ NS_IMETHOD GetFilterScope(nsMsgSearchScopeValue *filterScope) override;
+ NS_IMETHOD GetSearchScope(nsMsgSearchScopeValue *searchScope) override;
+
+ NS_IMETHOD GetSocketType(int32_t *aSocketType) override; // override nsMsgIncomingServer impl
+ NS_IMETHOD SetSocketType(int32_t aSocketType) override; // override nsMsgIncomingServer impl
+ NS_IMETHOD GetSortOrder(int32_t* aSortOrder) override;
+
+protected:
+ virtual ~nsNntpIncomingServer();
+ virtual nsresult CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder) override;
+ nsresult GetNntpConnection(nsIURI *url, nsIMsgWindow *window,
+ nsINNTPProtocol **aNntpConnection);
+ nsresult CreateProtocolInstance(nsINNTPProtocol **aNntpConnection,
+ nsIURI *url, nsIMsgWindow *window);
+ bool ConnectionTimeOut(nsINNTPProtocol* aNntpConnection);
+ nsCOMArray<nsINNTPProtocol> mConnectionCache;
+ nsTArray<RefPtr<nsNntpMockChannel> > m_queuedChannels;
+
+ /**
+ * Downloads the newsgroup headers.
+ */
+ nsresult DownloadMail(nsIMsgWindow *aMsgWindow);
+
+ NS_IMETHOD GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) override;
+ nsresult SetupNewsrcSaveTimer();
+ static void OnNewsrcSaveTimer(nsITimer *timer, void *voidIncomingServer);
+ void WriteLine(nsIOutputStream *stream, nsCString &str);
+
+private:
+ nsTArray<nsCString> mSubscribedNewsgroups;
+ nsTArray<nsCString> mGroupsOnServer;
+ nsTArray<nsCString> mSubscribeSearchResult;
+ bool mSearchResultSortDescending;
+ // the list of of subscribed newsgroups within a given
+ // subscribed dialog session.
+ // we need to keep track of them so we know what to show as "checked"
+ // in the search view
+ nsTArray<nsCString> mTempSubscribed;
+ nsCOMPtr<nsIAtom> mSubscribedAtom;
+ nsCOMPtr<nsIAtom> mNntpAtom;
+
+ nsCOMPtr<nsITreeBoxObject> mTree;
+ nsCOMPtr<nsITreeSelection> mTreeSelection;
+
+ bool mHasSeenBeginGroups;
+ bool mGetOnlyNew;
+ nsresult WriteHostInfoFile();
+ nsresult LoadHostInfoFile();
+ nsresult AddGroupOnServer(const nsACString &name);
+
+ bool mNewsrcHasChanged;
+ bool mHostInfoLoaded;
+ bool mHostInfoHasChanged;
+ nsCOMPtr <nsIFile> mHostInfoFile;
+
+ uint32_t mLastGroupDate;
+ int32_t mUniqueId;
+ uint32_t mLastUpdatedTime;
+ int32_t mVersion;
+ bool mPostingAllowed;
+
+ nsCOMPtr<nsITimer> mNewsrcSaveTimer;
+ nsCOMPtr <nsIMsgWindow> mMsgWindow;
+
+ nsCOMPtr <nsISubscribableServer> mInner;
+ nsresult EnsureInner();
+ nsresult ClearInner();
+ bool IsValidRow(int32_t row);
+ nsCOMPtr<nsIFile> mNewsrcFilePath;
+};
+
+#endif
diff --git a/mailnews/news/src/nsNntpMockChannel.cpp b/mailnews/news/src/nsNntpMockChannel.cpp
new file mode 100644
index 000000000..1dfd462ff
--- /dev/null
+++ b/mailnews/news/src/nsNntpMockChannel.cpp
@@ -0,0 +1,353 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNntpMockChannel.h"
+
+#include "msgCore.h"
+#include "nsILoadInfo.h"
+#include "nsNNTPProtocol.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsContentSecurityManager.h"
+
+NS_IMPL_ISUPPORTS(nsNntpMockChannel, nsIChannel, nsIRequest)
+
+nsNntpMockChannel::nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow)
+: m_url(aUri),
+ m_msgWindow(aMsgWindow),
+ m_channelState(CHANNEL_UNOPENED),
+ m_protocol(nullptr),
+ m_cancelStatus(NS_OK),
+ m_loadFlags(0),
+ m_contentLength(-1)
+{
+}
+
+nsNntpMockChannel::nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow,
+ nsISupports *aConsumer)
+: m_url(aUri),
+ m_context(aConsumer),
+ m_msgWindow(aMsgWindow),
+ m_channelState(CHANNEL_OPEN_WITH_LOAD),
+ m_protocol(nullptr),
+ m_cancelStatus(NS_OK),
+ m_loadFlags(0),
+ m_contentLength(-1)
+{
+}
+
+nsNntpMockChannel::~nsNntpMockChannel()
+{
+}
+
+#define FORWARD_CALL(function, argument) \
+ if (m_protocol) \
+ return m_protocol->function(argument);
+
+////////////////////////
+// nsIRequest methods //
+////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::GetName(nsACString &result)
+{
+ FORWARD_CALL(GetName, result)
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::IsPending(bool *result)
+{
+ FORWARD_CALL(IsPending, result)
+ // We haven't been loaded yet, so we're still pending.
+ *result = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetStatus(nsresult *status)
+{
+ FORWARD_CALL(GetStatus, status)
+ *status = m_cancelStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Cancel(nsresult status)
+{
+ m_cancelStatus = status;
+ m_channelState = CHANNEL_CLOSED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Suspend()
+{
+ NS_NOTREACHED("nsNntpMockChannel::Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Resume()
+{
+ NS_NOTREACHED("nsNntpMockChannel::Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ FORWARD_CALL(SetLoadGroup, aLoadGroup)
+ m_loadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ FORWARD_CALL(GetLoadGroup, aLoadGroup)
+ NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ FORWARD_CALL(GetLoadFlags, aLoadFlags)
+ *aLoadFlags = m_loadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ FORWARD_CALL(SetLoadFlags, aLoadFlags)
+ m_loadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ FORWARD_CALL(GetLoadInfo, aLoadInfo)
+ NS_IF_ADDREF(*aLoadInfo = m_loadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetLoadInfo(nsILoadInfo *aLoadInfo)
+{
+ FORWARD_CALL(SetLoadInfo, aLoadInfo)
+ m_loadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+////////////////////////
+// nsIChannel methods //
+////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::GetOriginalURI(nsIURI **aURI)
+{
+ FORWARD_CALL(GetOriginalURI, aURI)
+ NS_IF_ADDREF(*aURI = m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetOriginalURI(nsIURI *aURI)
+{
+ FORWARD_CALL(SetOriginalURI, aURI)
+ // News does not seem to have the notion of an original URI.
+ // (See bug 193317 and bug 1312314.)
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetURI(nsIURI **aURI)
+{
+ NS_IF_ADDREF(*aURI = m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetOwner(nsISupports **owner)
+{
+ FORWARD_CALL(GetOwner, owner)
+ NS_IF_ADDREF(*owner = m_owner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetOwner(nsISupports *aOwner)
+{
+ FORWARD_CALL(SetOwner, aOwner)
+ m_owner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::GetNotificationCallbacks(nsIInterfaceRequestor **callbacks)
+{
+ FORWARD_CALL(GetNotificationCallbacks, callbacks)
+ NS_IF_ADDREF(*callbacks = m_notificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ FORWARD_CALL(SetNotificationCallbacks, aCallbacks)
+ m_notificationCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetSecurityInfo(nsISupports **securityInfo)
+{
+ FORWARD_CALL(GetSecurityInfo, securityInfo)
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentType(nsACString &aContentType)
+{
+ FORWARD_CALL(GetContentType, aContentType)
+ aContentType = m_contentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetContentType(const nsACString &aContentType)
+{
+ FORWARD_CALL(SetContentType, aContentType)
+ return NS_ParseResponseContentType(aContentType, m_contentType, m_contentCharset);
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentCharset(nsACString &aCharset)
+{
+ FORWARD_CALL(GetContentCharset, aCharset)
+ aCharset = m_contentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetContentCharset(const nsACString &aCharset)
+{
+ FORWARD_CALL(SetContentCharset, aCharset)
+ m_contentCharset = aCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentLength(int64_t *length)
+{
+ FORWARD_CALL(GetContentLength, length)
+ *length = m_contentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetContentLength(int64_t aLength)
+{
+ FORWARD_CALL(SetContentLength, aLength)
+ m_contentLength = aLength;
+ return NS_OK;
+}
+
+////////////////////////////////////////
+// nsIChannel and nsNNTPProtocol glue //
+////////////////////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::Open(nsIInputStream **_retval)
+{
+ return NS_ImplementChannelOpen(this, _retval);
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Open2(nsIInputStream **_retval)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(_retval);
+}
+
+NS_IMETHODIMP nsNntpMockChannel::AsyncOpen(nsIStreamListener *listener,
+ nsISupports *ctxt)
+{
+ m_channelState = CHANNEL_OPEN_WITH_ASYNC;
+ m_channelListener = listener;
+ m_context = ctxt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+nsresult
+nsNntpMockChannel::AttachNNTPConnection(nsNNTPProtocol &protocol)
+{
+ // First things first. Were we canceled? If so, tell the protocol.
+ if (m_channelState == CHANNEL_CLOSED || m_channelState == CHANNEL_UNOPENED)
+ return NS_ERROR_FAILURE;
+
+
+ // We're going to active the protocol now. Note that if the user has
+ // interacted with us through the nsIChannel API, we need to pass it to the
+ // protocol instance. We also need to initialize it. For best results, we're
+ // going to initialize the code and then set the values.
+ nsresult rv = protocol.Initialize(m_url, m_msgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Variable fun
+ protocol.SetLoadGroup(m_loadGroup);
+ protocol.SetLoadFlags(m_loadFlags);
+ protocol.SetOwner(m_owner);
+ protocol.SetNotificationCallbacks(m_notificationCallbacks);
+ protocol.SetContentType(m_contentType);
+
+ // Now that we've set up the protocol, attach it to ourselves so that we can
+ // forward all future calls to the protocol instance. We do not refcount this
+ // instance, since the server will be owning all of them: once the server
+ // releases its reference, the protocol instance is no longer usable anyways.
+ m_protocol = &protocol;
+
+ switch (m_channelState)
+ {
+ case CHANNEL_OPEN_WITH_LOAD:
+ rv = protocol.LoadNewsUrl(m_url, m_context);
+ break;
+ case CHANNEL_OPEN_WITH_ASYNC:
+ rv = protocol.AsyncOpen(m_channelListener, m_context);
+ break;
+ default:
+ NS_NOTREACHED("Unknown channel state got us here.");
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we fail, that means that loading the NNTP protocol failed. Since we
+ // essentially promised that we would load (by virtue of returning NS_OK to
+ // AsyncOpen), we must now tell our listener the bad news.
+ if (NS_FAILED(rv) && m_channelListener)
+ m_channelListener->OnStopRequest(this, m_context, rv);
+
+ // Returning a failure code is our way of telling the server that this URL
+ // isn't going to run, so it should give the connection the next URL in the
+ // queue.
+ return rv;
+}
diff --git a/mailnews/news/src/nsNntpMockChannel.h b/mailnews/news/src/nsNntpMockChannel.h
new file mode 100644
index 000000000..dc20185ec
--- /dev/null
+++ b/mailnews/news/src/nsNntpMockChannel.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNntpMockChannel_h___
+#define nsNntpMockChannel_h___
+
+#include "nsIChannel.h"
+#include "nsIMsgWindow.h"
+
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+
+class nsNNTPProtocol;
+
+class nsNntpMockChannel : public nsIChannel
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUEST
+
+ nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow);
+ nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow,
+ nsISupports *aConsumer);
+
+ nsresult AttachNNTPConnection(nsNNTPProtocol &protocol);
+protected:
+ virtual ~nsNntpMockChannel();
+
+ // The URL we will be running
+ nsCOMPtr<nsIURI> m_url;
+
+ // Variables for arguments to pass into the opening phase.
+ nsCOMPtr<nsIStreamListener> m_channelListener;
+ nsCOMPtr<nsISupports> m_context;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+
+ // The state we're in
+ enum
+ {
+ CHANNEL_UNOPENED, //!< No one bothered to open this yet
+ CHANNEL_OPEN_WITH_LOAD, //!< We should open with LoadNewsUrl
+ CHANNEL_OPEN_WITH_ASYNC, //!< We should open with AsyncOpen
+ CHANNEL_CLOSED //!< We were closed and should not open
+ } m_channelState;
+
+ // The protocol instance
+ nsNNTPProtocol *m_protocol;
+
+ // Temporary variables for accessors before we get to the actual instance.
+ nsresult m_cancelStatus;
+ nsCOMPtr<nsILoadGroup> m_loadGroup;
+ nsCOMPtr<nsILoadInfo> m_loadInfo;
+ nsLoadFlags m_loadFlags;
+
+ nsCOMPtr<nsISupports> m_owner;
+ nsCOMPtr<nsIInterfaceRequestor> m_notificationCallbacks;
+ nsCString m_contentType;
+ nsCString m_contentCharset;
+ int64_t m_contentLength;
+};
+
+#endif // nsNntpMockChannel_h___
diff --git a/mailnews/news/src/nsNntpService.cpp b/mailnews/news/src/nsNntpService.cpp
new file mode 100644
index 000000000..8cb3cb2ec
--- /dev/null
+++ b/mailnews/news/src/nsNntpService.cpp
@@ -0,0 +1,1751 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nntpCore.h"
+#include "nsMsgNewsCID.h"
+#include "nsINntpUrl.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsNNTPNewsgroupPost.h"
+#include "nsIMsgIdentity.h"
+#include "nsStringGlue.h"
+#include "nsNewsUtils.h"
+#include "nsNewsDatabase.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgBaseCID.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsNntpService.h"
+#include "nsIChannel.h"
+#include "nsILoadGroup.h"
+#include "nsCOMPtr.h"
+#include "nsIDirectoryService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMessengerMigrator.h"
+#include "nsINntpIncomingServer.h"
+#include "nsICategoryManager.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIMessengerWindowService.h"
+#include "nsIWindowMediator.h"
+#include "mozIDOMWindow.h"
+#include "nsIMsgSearchSession.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsIWebNavigation.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsIPrompt.h"
+#include "nsNewsDownloader.h"
+#include "prprf.h"
+#include "nsICacheStorage.h"
+#include "nsICacheStorageService.h"
+#include "nsILoadContextInfo.h"
+#include "nsICacheEntry.h"
+#include "nsMsgUtils.h"
+#include "nsNetUtil.h"
+#include "nsIWindowWatcher.h"
+#include "nsICommandLine.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgMailSession.h"
+#include "nsISupportsPrimitives.h"
+#include "nsArrayUtils.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "../../base/src/MailnewsLoadContextInfo.h"
+
+#undef GetPort // XXX Windows!
+#undef SetPort // XXX Windows!
+
+#define PREF_MAIL_ROOT_NNTP "mail.root.nntp" // old - for backward compatibility only
+#define PREF_MAIL_ROOT_NNTP_REL "mail.root.nntp-rel"
+
+nsNntpService::nsNntpService()
+{
+ mPrintingOperation = false;
+ mOpenAttachmentOperation = false;
+}
+
+nsNntpService::~nsNntpService()
+{
+ // do nothing
+}
+
+NS_IMPL_ISUPPORTS(nsNntpService, nsINntpService, nsIMsgMessageService,
+ nsIProtocolHandler, nsIMsgProtocolInfo, nsICommandLineHandler,
+ nsIMsgMessageFetchPartService, nsIContentHandler)
+
+////////////////////////////////////////////////////////////////////////////////////////
+// nsIMsgMessageService support
+////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsNntpService::SaveMessageToDisk(const char *aMessageURI,
+ nsIFile *aFile,
+ bool aAddDummyEnvelope,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ bool canonicalLineEnding,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+
+ // double check it is a news-message:/ uri
+ if (PL_strncmp(aMessageURI, kNewsMessageRootURI, kNewsMessageRootURILen))
+ {
+ rv = NS_ERROR_UNEXPECTED;
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsMsgKey key = nsMsgKey_None;
+ rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageIdURL;
+ rv = CreateMessageIDURL(folder, key, getter_Copies(messageIdURL));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(messageIdURL.get(), aUrlListener, aMsgWindow, aMessageURI, nsINntpUrl::ActionSaveMessageToDisk, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(url);
+ if (msgUrl) {
+// msgUrl->SetMessageFile(aFile);
+ msgUrl->SetAddDummyEnvelope(aAddDummyEnvelope);
+ msgUrl->SetCanonicalLineEnding(canonicalLineEnding);
+ }
+
+ bool hasMsgOffline = false;
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(url);
+ if (folder)
+ {
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(folder);
+ if (newsFolder)
+ {
+ if (mailNewsUrl)
+ {
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ mailNewsUrl->SetMsgIsInLocalCache(hasMsgOffline);
+ }
+ }
+ }
+
+ if (mailNewsUrl)
+ {
+ nsCOMPtr <nsIStreamListener> saveAsListener;
+ mailNewsUrl->GetSaveAsListener(aAddDummyEnvelope, aFile, getter_AddRefs(saveAsListener));
+
+ rv = DisplayMessage(aMessageURI, saveAsListener, /* nsIMsgWindow *aMsgWindow */nullptr, aUrlListener, nullptr /*aCharsetOverride */, aURL);
+ }
+ return rv;
+}
+
+
+nsresult
+nsNntpService::CreateMessageIDURL(nsIMsgFolder *folder, nsMsgKey key, char **url)
+{
+ NS_ENSURE_ARG_POINTER(folder);
+ NS_ENSURE_ARG_POINTER(url);
+ if (key == nsMsgKey_None) return NS_ERROR_INVALID_ARG;
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageID;
+ rv = newsFolder->GetMessageIdForKey(key, messageID);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // we need to escape the message ID,
+ // it might contain characters which will mess us up later, like #
+ // see bug #120502
+ nsCString escapedMessageID;
+ MsgEscapeString(messageID, nsINetUtil::ESCAPE_URL_PATH, escapedMessageID);
+
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ rv = folder->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString rootFolderURI;
+ rv = rootFolder->GetURI(rootFolderURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString groupName;
+ rv = folder->GetName(groupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString uri;
+ uri = rootFolderURI.get();
+ uri += '/';
+ uri += escapedMessageID;
+ uri += kNewsURIGroupQuery; // ?group=
+ AppendUTF16toUTF8(groupName, uri);
+ uri += kNewsURIKeyQuery; // &key=
+ uri.AppendInt(key);
+ *url = ToNewCString(uri);
+
+ if (!*url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::DisplayMessage(const char* aMessageURI, nsISupports * aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow, nsIUrlListener * aUrlListener, const char * aCharsetOverride, nsIURI ** aURL)
+{
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsMsgKey key = nsMsgKey_None;
+ rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString urlStr;
+ // if we are displaying (or printing), we want the news://host/message-id url
+ // we keep the original uri around, for cancelling and so we can get to the
+ // articles by doing GROUP and then ARTICLE <n>.
+ //
+ // using news://host/message-id has an extra benefit.
+ // we'll use that to look up in the cache, so if
+ // you are reading a message that you've already read, you
+ // (from a cross post) it would be in your cache.
+ rv = CreateMessageIDURL(folder, key, getter_Copies(urlStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // rhp: If we are displaying this message for the purposes of printing, append
+ // the magic operand.
+ if (mPrintingOperation)
+ urlStr.Append("?header=print");
+
+ nsNewsAction action = nsINntpUrl::ActionFetchArticle;
+ if (mOpenAttachmentOperation)
+ action = nsINntpUrl::ActionFetchPart;
+
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(urlStr.get(), aUrlListener, aMsgWindow, aMessageURI, action, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(url,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgI18NUrl> i18nurl = do_QueryInterface(msgUrl,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ i18nurl->SetCharsetOverRide(aCharsetOverride);
+
+ bool shouldStoreMsgOffline = false;
+
+ if (folder)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // We need to set the port on the url, just like
+ // nsNNTPProtocol::Initialize does, so the specs will be the same.
+ // we can ignore errors here - worst case, we'll display the
+ // "message not available" message.
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port = 0;
+ rv = url->GetPort(&port);
+ if (NS_FAILED(rv) || (port <= 0))
+ {
+ rv = server->GetPort(&port);
+ if (NS_FAILED(rv) || (port <= 0))
+ {
+ int32_t socketType;
+ rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ port = (socketType == nsMsgSocketType::SSL) ?
+ nsINntpUrl::DEFAULT_NNTPS_PORT : nsINntpUrl::DEFAULT_NNTP_PORT;
+ }
+
+ rv = url->SetPort(port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ folder->ShouldStoreMsgOffline(key, &shouldStoreMsgOffline);
+
+ // Look for the message in the offline cache
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+
+ // Now look in the memory cache
+ if (!hasMsgOffline)
+ {
+ rv = IsMsgInMemCache(url, folder, &hasMsgOffline);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If the message is not found in either, then we might need to return
+ if (!hasMsgOffline && WeAreOffline())
+ return server->DisplayOfflineMsg(aMsgWindow);
+
+ msgUrl->SetMsgIsInLocalCache(hasMsgOffline);
+
+ nsCOMPtr<nsIMsgNewsFolder> newsFolder(do_QueryInterface(folder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ newsFolder->SetSaveArticleOffline(shouldStoreMsgOffline);
+ }
+
+ if (aURL)
+ NS_IF_ADDREF(*aURL = url);
+
+ return GetMessageFromUrl(url, aMsgWindow, aDisplayConsumer);
+}
+
+nsresult nsNntpService::GetMessageFromUrl(nsIURI *aUrl,
+ nsIMsgWindow *aMsgWindow,
+ nsISupports *aDisplayConsumer)
+{
+ nsresult rv;
+ // if the consumer is the docshell then we want to run the url in the webshell
+ // in order to display it. If it isn't a docshell then just run the news url
+ // like we would any other news url.
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ // DIRTY LITTLE HACK --> if we are opening an attachment we want the docshell to
+ // treat this load as if it were a user click event. Then the dispatching stuff will be much
+ // happier.
+ if (mOpenAttachmentOperation)
+ {
+ docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadLink);
+ }
+
+ rv = docShell->LoadURI(aUrl, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false);
+ }
+ else
+ {
+ nsCOMPtr<nsIStreamListener> aStreamListener(do_QueryInterface(aDisplayConsumer, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIChannel> aChannel;
+ nsCOMPtr<nsILoadGroup> aLoadGroup;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aUrl, &rv);
+ if (NS_SUCCEEDED(rv) && mailnewsUrl)
+ {
+ if (aMsgWindow)
+ mailnewsUrl->SetMsgWindow(aMsgWindow);
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(aLoadGroup));
+ }
+ rv = NewChannel(aUrl, getter_AddRefs(aChannel));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aChannel->SetLoadGroup(aLoadGroup);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISupports> aCtxt = do_QueryInterface(aUrl);
+ // now try to open the channel passing in our display consumer as the listener
+ rv = aChannel->AsyncOpen(aStreamListener, aCtxt);
+ }
+ else
+ rv = RunNewsUrl(aUrl, aMsgWindow, aDisplayConsumer);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::FetchMessage(nsIMsgFolder *folder, nsMsgKey key, nsIMsgWindow *aMsgWindow, nsISupports * aConsumer, nsIUrlListener * aUrlListener, nsIURI ** aURL)
+{
+ NS_ENSURE_ARG_POINTER(folder);
+ nsresult rv;
+ nsCOMPtr<nsIMsgNewsFolder> msgNewsFolder = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgDBHdr> hdr;
+ rv = folder->GetMessageHeader(key, getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString originalMessageUri;
+ rv = folder->GetUriForMsg(hdr, originalMessageUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageIdURL;
+ rv = CreateMessageIDURL(folder, key, getter_Copies(messageIdURL));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(messageIdURL.get(), aUrlListener, aMsgWindow, originalMessageUri.get(),
+ nsINntpUrl::ActionFetchArticle, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = RunNewsUrl(url, aMsgWindow, aConsumer);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aURL)
+ url.swap(*aURL);
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpService::FetchMimePart(nsIURI *aURI, const char *aMessageURI, nsISupports *aDisplayConsumer, nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener, nsIURI **aURL)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(aURI, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgUrl->SetMsgWindow(aMsgWindow);
+
+ // set up the url listener
+ if (aUrlListener)
+ msgUrl->RegisterListener(aUrlListener);
+
+// this code isn't ready yet, but it helps getting opening attachments
+// while offline working
+// nsCOMPtr<nsIMsgMessageUrl> msgMessageUrl = do_QueryInterface(aURI);
+// if (msgMessageUrl)
+// {
+// nsAutoCString spec;
+// rv = aURI->GetSpec(spec);
+// NS_ENSURE_SUCCESS(rv, rv);
+// msgMessageUrl->SetOriginalSpec(spec.get());
+// }
+ return RunNewsUrl(msgUrl, aMsgWindow, aDisplayConsumer);
+}
+
+NS_IMETHODIMP nsNntpService::OpenAttachment(const char *aContentType,
+ const char *aFileName,
+ const char *aUrl,
+ const char *aMessageUri,
+ nsISupports *aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ NS_ENSURE_ARG_POINTER(aFileName);
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = NS_OK;
+ nsAutoCString newsUrl;
+ newsUrl = aUrl;
+ newsUrl += "&type=";
+ newsUrl += aContentType;
+ newsUrl += "&filename=";
+ newsUrl += aFileName;
+
+ NewURI(newsUrl, nullptr, nullptr, getter_AddRefs(url));
+
+ if (NS_SUCCEEDED(rv) && url)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(url, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgUrl->SetMsgWindow(aMsgWindow);
+ msgUrl->SetFileName(nsDependentCString(aFileName));
+// this code isn't ready yet, but it helps getting opening attachments
+// while offline working
+// nsCOMPtr<nsIMsgMessageUrl> msgMessageUrl = do_QueryInterface(url);
+// if (msgMessageUrl)
+// msgMessageUrl->SetOriginalSpec(newsUrl.get());
+ // set up the url listener
+ if (aUrlListener)
+ msgUrl->RegisterListener(aUrlListener);
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ if (NS_SUCCEEDED(rv) && docShell)
+ {
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadLink);
+ return docShell->LoadURI(url, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false);
+ }
+ else
+ return RunNewsUrl(url, aMsgWindow, aDisplayConsumer);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::GetUrlForUri(const char *aMessageURI, nsIURI **aURL, nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+
+ // double check that it is a news-message:/ uri
+ if (PL_strncmp(aMessageURI, kNewsMessageRootURI, kNewsMessageRootURILen))
+ {
+ rv = NS_ERROR_UNEXPECTED;
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsMsgKey key = nsMsgKey_None;
+ rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageIdURL;
+ rv = CreateMessageIDURL(folder, key, getter_Copies(messageIdURL));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // this is only called by view message source
+ rv = ConstructNntpUrl(messageIdURL.get(), nullptr, aMsgWindow, aMessageURI, nsINntpUrl::ActionFetchArticle, aURL);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (folder && *aURL)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(*aURL);
+ if (mailnewsUrl)
+ {
+ bool useLocalCache = false;
+ folder->HasMsgOffline(key, &useLocalCache);
+ mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+ }
+ }
+ return rv;
+
+}
+
+NS_IMETHODIMP
+nsNntpService::DecomposeNewsURI(const char *uri, nsIMsgFolder **folder, nsMsgKey *aMsgKey)
+{
+ nsresult rv;
+
+ rv = DecomposeNewsMessageURI(uri, folder, aMsgKey);
+
+ return rv;
+}
+
+nsresult
+nsNntpService::DecomposeNewsMessageURI(const char * aMessageURI, nsIMsgFolder ** aFolder, nsMsgKey *aMsgKey)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aMsgKey);
+
+ nsresult rv = NS_OK;
+
+ // Construct the news URL
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_CreateInstance(NS_NNTPURL_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsINntpUrl> nntpUrl = do_QueryInterface(mailnewsurl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mailnewsurl->SetSpec(nsDependentCString(aMessageURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the group name and key from the url
+ nsAutoCString groupName;
+ rv = nntpUrl->GetGroup(groupName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nntpUrl->GetKey(aMsgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there is no group, try the harder way.
+ if (groupName.IsEmpty())
+ {
+ *aMsgKey = nsMsgKey_None;
+ return GetFolderFromUri(aMessageURI, aFolder);
+ }
+
+ return mailnewsurl->GetFolder(aFolder);
+}
+
+nsresult
+nsNntpService::GetFolderFromUri(const char *aUri, nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aUri);
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(aUri));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString path;
+ rv = uri->GetPath(path);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = accountManager->FindServerByURI(uri, false, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // check if path is "/"
+ // if so, use the root folder
+ if (path.Length() == 1)
+ {
+ NS_ADDREF(*aFolder = rootFolder);
+ return NS_OK;
+ }
+
+ // the URI is news://host/(escaped group)
+ // but the *name* of the newsgroup (we are calling ::GetChildNamed())
+ // is unescaped. see http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c17
+ // for more about this
+ nsCString unescapedPath;
+ MsgUnescapeString(Substring(path, 1), 0, unescapedPath); /* skip the leading slash */
+
+ nsCOMPtr<nsIMsgFolder> subFolder;
+ rv = rootFolder->GetChildNamed(NS_ConvertUTF8toUTF16(unescapedPath),
+ getter_AddRefs(subFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ subFolder.swap(*aFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::CopyMessage(const char * aSrcMessageURI, nsIStreamListener * aMailboxCopyHandler, bool moveMessage,
+ nsIUrlListener * aUrlListener, nsIMsgWindow *aMsgWindow, nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aSrcMessageURI);
+ NS_ENSURE_ARG_POINTER(aMailboxCopyHandler);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> streamSupport = do_QueryInterface(aMailboxCopyHandler, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = DisplayMessage(aSrcMessageURI, streamSupport, aMsgWindow, aUrlListener, nullptr, aURL);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::CopyMessages(uint32_t aNumKeys, nsMsgKey *akeys,
+ nsIMsgFolder *srcFolder,
+ nsIStreamListener * aMailboxCopyHandler,
+ bool moveMessage,
+ nsIUrlListener * aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsNntpService::FindServerWithNewsgroup(nsCString &host, nsCString &groupName)
+{
+ nsresult rv;
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIArray> servers;
+ rv = accountManager->GetAllServers(getter_AddRefs(servers));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(MsgIsUTF8(groupName),
+ "newsgroup is not in UTF-8");
+
+ // XXX TODO
+ // this only looks at the list of subscribed newsgroups.
+ // fix to use the hostinfo.dat information
+
+ uint32_t length;
+ rv = servers->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ nsCOMPtr<nsINntpIncomingServer> newsserver(do_QueryElementAt(servers, i, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ bool containsGroup = false;
+ rv = newsserver->ContainsNewsgroup(groupName,
+ &containsGroup);
+ if (containsGroup)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server(do_QueryInterface(newsserver, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return server->GetHostName(host);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsNntpService::FindHostFromGroup(nsCString &host, nsCString &groupName)
+{
+ nsresult rv = NS_OK;
+ // host always comes in as ""
+ NS_ASSERTION(host.IsEmpty(), "host is not empty");
+ if (!host.IsEmpty()) return NS_ERROR_FAILURE;
+
+ rv = FindServerWithNewsgroup(host, groupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // host can be empty
+ return NS_OK;
+}
+
+nsresult
+nsNntpService::SetUpNntpUrlForPosting(const char *aAccountKey, char **newsUrlSpec)
+{
+ nsresult rv = NS_OK;
+
+ nsCString host;
+ int32_t port = -1;
+
+ nsCOMPtr<nsIMsgIncomingServer> nntpServer;
+ rv = GetNntpServerByAccount(aAccountKey, getter_AddRefs(nntpServer));
+ if (NS_SUCCEEDED(rv) && nntpServer)
+ {
+ nntpServer->GetHostName(host);
+ nntpServer->GetPort(&port);
+ }
+ else
+ {
+ NS_WARNING("Failure to obtain host and port");
+ }
+
+ *newsUrlSpec = PR_smprintf("%s/%s:%d",kNewsRootURI, host.IsEmpty() ? "news" : host.get(), port);
+ if (!*newsUrlSpec) return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+////////////////////////////////////////////////////////////////////////////////
+// nsINntpService support
+////////////////////////////////////////////////////////////////////////////////
+// XXX : may not work with non-ASCII newsgroup names and IDN hostnames
+NS_IMETHODIMP
+nsNntpService::GenerateNewsHeaderValsForPosting(const nsACString& newsgroupsList, char **newsgroupsHeaderVal, char **newshostHeaderVal)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG_POINTER(newsgroupsHeaderVal);
+ NS_ENSURE_ARG_POINTER(newshostHeaderVal);
+
+ // newsgroupsList can be a comma separated list of these:
+ // news://host/group
+ // news://group
+ // host/group
+ // group
+ //
+ // we are not going to allow the user to cross post to multiple hosts.
+ // if we detect that, we stop and return error.
+
+ nsAutoCString host;
+ nsAutoCString newsgroups;
+
+ nsTArray<nsCString> list;
+ ParseString(newsgroupsList, ',', list);
+ for (uint32_t index = 0; index < list.Length(); index++)
+ {
+ list[index].StripWhitespace();
+ if (!list[index].IsEmpty())
+ {
+ nsAutoCString currentHost;
+ nsAutoCString theRest;
+ // does list[index] start with "news:/"?
+ if (StringBeginsWith(list[index], NS_LITERAL_CSTRING(kNewsRootURI)))
+ {
+ // we have news://group or news://host/group
+ // set theRest to what's after news://
+ theRest = Substring(list[index], kNewsRootURILen /* for news:/ */ + 1 /* for the slash */);
+ }
+ else if (list[index].Find(":/") != -1)
+ {
+ // we have x:/y where x != news. this is bad, return failure
+ return NS_ERROR_FAILURE;
+ }
+ else
+ theRest = list[index];
+
+ // theRest is "group" or "host/group"
+ int32_t slashpos = theRest.FindChar('/');
+ if (slashpos > 0 )
+ {
+ nsAutoCString currentGroup;
+
+ // theRest is "host/group"
+ currentHost = StringHead(theRest, slashpos);
+
+ // from "host/group", put "group" into currentGroup;
+ currentGroup = Substring(theRest, slashpos + 1);
+
+ NS_ASSERTION(!currentGroup.IsEmpty(), "currentGroup is empty");
+ if (currentGroup.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ // build up the newsgroups
+ if (!newsgroups.IsEmpty())
+ newsgroups += ",";
+ newsgroups += currentGroup;
+ }
+ else
+ {
+ // theRest is "group"
+ rv = FindHostFromGroup(currentHost, theRest);
+ if (NS_FAILED(rv))
+ return rv;
+ // build up the newsgroups
+ if (!newsgroups.IsEmpty())
+ newsgroups += ",";
+ newsgroups += theRest;
+ }
+
+ if (!currentHost.IsEmpty())
+ {
+ if (host.IsEmpty())
+ host = currentHost;
+ else
+ {
+ if (!host.Equals(currentHost))
+ return NS_ERROR_NNTP_NO_CROSS_POSTING;
+ }
+ }
+ currentHost = "";
+ }
+ }
+
+ *newshostHeaderVal = ToNewCString(host);
+ if (!*newshostHeaderVal) return NS_ERROR_OUT_OF_MEMORY;
+
+ *newsgroupsHeaderVal = ToNewCString(newsgroups);
+ if (!*newsgroupsHeaderVal) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+nsresult
+nsNntpService::GetNntpServerByAccount(const char *aAccountKey, nsIMsgIncomingServer **aNntpServer)
+{
+ NS_ENSURE_ARG_POINTER(aNntpServer);
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (aAccountKey)
+ {
+ nsCOMPtr <nsIMsgAccount> account;
+ rv = accountManager->GetAccount(nsDependentCString(aAccountKey), getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account)
+ rv = account->GetIncomingServer(aNntpServer);
+ }
+
+ // if we don't have a news host, find the first news server and use it
+ if (NS_FAILED(rv) || !*aNntpServer)
+ rv = accountManager->FindServer(EmptyCString(), EmptyCString(), NS_LITERAL_CSTRING("nntp"), aNntpServer);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::PostMessage(nsIFile *aFileToPost, const char *newsgroupsNames, const char *aAccountKey, nsIUrlListener * aUrlListener, nsIMsgWindow *aMsgWindow, nsIURI **_retval)
+{
+ // aMsgWindow might be null
+ NS_ENSURE_ARG_POINTER(newsgroupsNames);
+
+ NS_ENSURE_ARG(*newsgroupsNames);
+
+ nsresult rv;
+
+ nsCOMPtr <nsINntpUrl> nntpUrl = do_CreateInstance(NS_NNTPURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nntpUrl->SetNewsAction(nsINntpUrl::ActionPostArticle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString newsUrlSpec;
+ rv = SetUpNntpUrlForPosting(aAccountKey, getter_Copies(newsUrlSpec));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(nntpUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailnewsurl->SetSpec(newsUrlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aUrlListener) // register listener if there is one...
+ mailnewsurl->RegisterListener(aUrlListener);
+
+ nsCOMPtr<nsINNTPNewsgroupPost> post = do_CreateInstance(NS_NNTPNEWSGROUPPOST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = post->SetPostMessageFile(aFileToPost);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nntpUrl->SetMessageToPost(post);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> url = do_QueryInterface(nntpUrl);
+ rv = RunNewsUrl(url, aMsgWindow, nullptr /* consumer */);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (_retval)
+ rv = CallQueryInterface(nntpUrl, _retval);
+
+ return rv;
+}
+
+nsresult
+nsNntpService::ConstructNntpUrl(const char *urlString, nsIUrlListener *aUrlListener, nsIMsgWindow *aMsgWindow, const char *originalMessageUri, int32_t action, nsIURI ** aUrl)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsINntpUrl> nntpUrl = do_CreateInstance(NS_NNTPURL_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(nntpUrl);
+ mailnewsurl->SetMsgWindow(aMsgWindow);
+ nsCOMPtr <nsIMsgMessageUrl> msgUrl = do_QueryInterface(nntpUrl);
+ msgUrl->SetUri(originalMessageUri);
+ rv = mailnewsurl->SetSpec(nsDependentCString(urlString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nntpUrl->SetNewsAction(action);
+
+ if (originalMessageUri)
+ {
+ // we'll use this later in nsNNTPProtocol::ParseURL()
+ rv = msgUrl->SetOriginalSpec(originalMessageUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (aUrlListener) // register listener if there is one...
+ mailnewsurl->RegisterListener(aUrlListener);
+
+ (*aUrl) = mailnewsurl;
+ NS_IF_ADDREF(*aUrl);
+ return rv;
+}
+
+nsresult
+nsNntpService::CreateNewsAccount(const char *aHostname, bool aUseSSL,
+ int32_t aPort, nsIMsgIncomingServer **aServer)
+{
+ NS_ENSURE_ARG_POINTER(aHostname);
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgAccount> account;
+ rv = accountManager->CreateAccount(getter_AddRefs(account));
+ if (NS_FAILED(rv)) return rv;
+
+ // for news, username is always null
+ rv = accountManager->CreateIncomingServer(EmptyCString(), nsDependentCString(aHostname), NS_LITERAL_CSTRING("nntp"), aServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (aUseSSL)
+ {
+ rv = (*aServer)->SetSocketType(nsMsgSocketType::SSL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = (*aServer)->SetPort(aPort);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIMsgIdentity> identity;
+ rv = accountManager->CreateIdentity(getter_AddRefs(identity));
+ if (NS_FAILED(rv)) return rv;
+ if (!identity) return NS_ERROR_FAILURE;
+
+ // by default, news accounts should be composing in plain text
+ rv = identity->SetComposeHtml(false);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // the identity isn't filled in, so it is not valid.
+ rv = (*aServer)->SetValid(false);
+ if (NS_FAILED(rv)) return rv;
+
+ // hook them together
+ rv = account->SetIncomingServer(*aServer);
+ if (NS_FAILED(rv)) return rv;
+ rv = account->AddIdentity(identity);
+ if (NS_FAILED(rv)) return rv;
+
+ // Now save the new acct info to pref file.
+ rv = accountManager->SaveAccountInfo();
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult
+nsNntpService::GetServerForUri(nsIURI *aUri, nsINntpIncomingServer **aServer)
+{
+ nsAutoCString hostName;
+ nsAutoCString scheme;
+ nsAutoCString path;
+ int32_t port = 0;
+ nsresult rv;
+
+ rv = aUri->GetAsciiHost(hostName);
+ rv = aUri->GetScheme(scheme);
+ rv = aUri->GetPort(&port);
+ rv = aUri->GetPath(path);
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // find the incoming server, it if exists.
+ // migrate if necessary, before searching for it.
+ // if it doesn't exist, create it.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ // Grab all servers for if this is a no-authority URL. This also loads
+ // accounts if they haven't been loaded, i.e., we're running this straight
+ // from the command line
+ nsCOMPtr <nsIArray> servers;
+ rv = accountManager->GetAllServers(getter_AddRefs(servers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!server && !hostName.IsEmpty())
+ {
+ // If we don't have this server but it isn't no-auth, add it.
+ // Ideally, we should remove this account quickly (see bug 41133)
+ bool useSSL = false;
+ if (scheme.EqualsLiteral("snews") || scheme.EqualsLiteral("nntps"))
+ {
+ useSSL = true;
+ if ((port == 0) || (port == -1))
+ port = nsINntpUrl::DEFAULT_NNTPS_PORT;
+ }
+ rv = CreateNewsAccount(hostName.get(), useSSL, port, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!server && hostName.IsEmpty())
+ // XXX: Until we support no-auth uris, bail
+ return NS_ERROR_FAILURE;
+
+ if (!server) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ nntpServer = do_QueryInterface(server, &rv);
+
+ if (!nntpServer || NS_FAILED(rv))
+ return rv;
+
+ NS_IF_ADDREF(*aServer = nntpServer);
+
+ nsAutoCString spec;
+ rv = aUri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+#if 0 // this not ready yet.
+ nsNewsAction action = nsINntpUrl::ActionUnknown;
+ nsCOMPtr <nsINntpUrl> nntpUrl = do_QueryInterface(aUri);
+ if (nntpUrl) {
+ rv = nntpUrl->GetNewsAction(&action);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // if this is a news-message:/ uri, decompose it and set hasMsgOffline on the uri
+ // Or, if it's of this form, we need to do the same.
+ // "news://news.mozilla.org:119/3D612B96.1050301%40netscape.com?part=1.2&type=image/gif&filename=hp_icon_logo.gif"
+
+ // XXX todo, or do we want to check if it is a news-message:// uri or
+ // a news:// uri (but action is not a fetch related action?)
+ if (!PL_strncmp(spec.get(), kNewsMessageRootURI, kNewsMessageRootURILen) ||
+ (action == nsINntpUrl::ActionFetchPart || action == nsINntpUrl::ActionFetchArticle))
+ {
+#else
+ // if this is a news-message:/ uri, decompose it and set hasMsgOffline on the uri
+ if (!PL_strncmp(spec.get(), kNewsMessageRootURI, kNewsMessageRootURILen))
+ {
+#endif
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsMsgKey key = nsMsgKey_None;
+ rv = DecomposeNewsMessageURI(spec.get(), getter_AddRefs(folder), &key);
+ if (NS_SUCCEEDED(rv) && folder)
+ {
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(aUri));
+ if (msgUrl)
+ msgUrl->SetMsgIsInLocalCache(hasMsgOffline);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNntpService::RunNewsUrl(nsIURI * aUri, nsIMsgWindow *aMsgWindow, nsISupports * aConsumer)
+{
+ nsresult rv;
+
+ if (WeAreOffline())
+ return NS_MSG_ERROR_OFFLINE;
+
+ // almost there...now create a nntp protocol instance to run the url in...
+ nsCOMPtr<nsINntpIncomingServer> server;
+ rv = GetServerForUri(aUri, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return server->LoadNewsUrl(aUri, aMsgWindow, aConsumer);
+}
+
+NS_IMETHODIMP nsNntpService::GetNewNews(nsINntpIncomingServer *nntpServer, const char *uri, bool aGetOld, nsIUrlListener * aUrlListener, nsIMsgWindow *aMsgWindow, nsIURI **_retval)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ server = do_QueryInterface(nntpServer);
+
+ /* double check that it is a "news:/" url */
+ if (strncmp(uri, kNewsRootURI, kNewsRootURILen) == 0)
+ {
+ nsCOMPtr<nsIURI> aUrl;
+ rv = ConstructNntpUrl(uri, aUrlListener, aMsgWindow, nullptr, nsINntpUrl::ActionGetNewNews, getter_AddRefs(aUrl));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsINntpUrl> nntpUrl = do_QueryInterface(aUrl);
+ if (nntpUrl)
+ {
+ rv = nntpUrl->SetGetOldMessages(aGetOld);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(aUrl);
+ if (mailNewsUrl)
+ mailNewsUrl->SetUpdatingFolder(true);
+
+ rv = RunNewsUrl(aUrl, aMsgWindow, nullptr);
+
+ if (_retval)
+ NS_IF_ADDREF(*_retval = aUrl);
+ }
+ else
+ {
+ NS_ERROR("not a news:/ url");
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::CancelMessage(const char *cancelURL, const char *messageURI, nsISupports * aConsumer, nsIUrlListener * aUrlListener, nsIMsgWindow *aMsgWindow, nsIURI ** aURL)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(cancelURL);
+ NS_ENSURE_ARG_POINTER(messageURI);
+
+ nsCOMPtr<nsIURI> url;
+ // the url should be "news://host/message-id?cancel"
+ rv = ConstructNntpUrl(cancelURL, aUrlListener, aMsgWindow, messageURI, nsINntpUrl::ActionCancelArticle, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = RunNewsUrl(url, aMsgWindow, aConsumer);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aURL)
+ {
+ *aURL = url;
+ NS_IF_ADDREF(*aURL);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpService::GetScheme(nsACString &aScheme)
+{
+ aScheme = "news";
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::GetDefaultDoBiff(bool *aDoBiff)
+{
+ NS_ENSURE_ARG_POINTER(aDoBiff);
+ // by default, don't do biff for NNTP servers
+ *aDoBiff = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::GetDefaultPort(int32_t *aDefaultPort)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultPort);
+ *aDefaultPort = nsINntpUrl::DEFAULT_NNTP_PORT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ *_retval = true; // allow news on any port
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetDefaultServerPort(bool aUseSSL, int32_t *aDefaultPort)
+{
+ nsresult rv = NS_OK;
+
+ // Return Secure NNTP Port if secure option chosen i.e., if useSSL is TRUE.
+ if (aUseSSL)
+ *aDefaultPort = nsINntpUrl::DEFAULT_NNTPS_PORT;
+ else
+ rv = GetDefaultPort(aDefaultPort);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpService::GetProtocolFlags(uint32_t *aUritype)
+{
+ NS_ENSURE_ARG_POINTER(aUritype);
+ *aUritype = URI_NORELATIVE | URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ URI_LOADABLE_BY_ANYONE | ALLOWS_PROXY | URI_FORBIDS_COOKIE_ACCESS
+#ifdef IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ | ORIGIN_IS_FULL_SPEC
+#endif
+ ;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignored
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> nntpUri = do_CreateInstance(NS_NNTPURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aBaseURI)
+ {
+ nsAutoCString newSpec;
+ aBaseURI->Resolve(aSpec, newSpec);
+ rv = nntpUri->SetSpec(newSpec);
+ }
+ else
+ {
+ rv = nntpUri->SetSpec(aSpec);
+ }
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ADDREF(*_retval = nntpUri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+NS_IMETHODIMP nsNntpService::NewChannel2(nsIURI *aURI,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel **_retval)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINntpIncomingServer> server;
+ rv = GetServerForUri(aURI, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = server->GetNntpChannel(aURI, nullptr, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = channel->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::SetDefaultLocalPath(nsIFile *aPath)
+{
+ NS_ENSURE_ARG(aPath);
+ return NS_SetPersistentFile(PREF_MAIL_ROOT_NNTP_REL, PREF_MAIL_ROOT_NNTP, aPath);
+}
+
+NS_IMETHODIMP
+nsNntpService::GetDefaultLocalPath(nsIFile ** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ bool havePref;
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_GetPersistentFile(PREF_MAIL_ROOT_NNTP_REL,
+ PREF_MAIL_ROOT_NNTP,
+ NS_APP_NEWS_50_DIR,
+ havePref,
+ getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ rv = localFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!havePref || !exists)
+ {
+ rv = NS_SetPersistentFile(PREF_MAIL_ROOT_NNTP_REL, PREF_MAIL_ROOT_NNTP, localFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
+ }
+
+ NS_IF_ADDREF(*aResult = localFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetServerIID(nsIID* *aServerIID)
+{
+ *aServerIID = new nsIID(NS_GET_IID(nsINntpIncomingServer));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetRequiresUsername(bool *aRequiresUsername)
+{
+ NS_ENSURE_ARG_POINTER(aRequiresUsername);
+ *aRequiresUsername = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress)
+{
+ NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress);
+ *aPreflightPrettyNameWithEmailAddress = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp)
+{
+ NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp);
+ *aCanLoginAtStartUp = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetCanDelete(bool *aCanDelete)
+{
+ NS_ENSURE_ARG_POINTER(aCanDelete);
+ *aCanDelete = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetCanDuplicate(bool *aCanDuplicate)
+{
+ NS_ENSURE_ARG_POINTER(aCanDuplicate);
+ *aCanDuplicate = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetCanGetMessages(bool *aCanGetMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetMessages);
+ *aCanGetMessages = false; // poorly named, this just means we don't have an inbox.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages);
+ // temporarily returns false because we don't yet support spam
+ // filtering in news. this will change.
+ *aCanGetIncomingMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetShowComposeMsgLink(bool *showComposeMsgLink)
+{
+ NS_ENSURE_ARG_POINTER(showComposeMsgLink);
+ *showComposeMsgLink = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetFoldersCreatedAsync(bool *aAsyncCreation)
+{
+ NS_ENSURE_ARG_POINTER(aAsyncCreation);
+ *aAsyncCreation = false;
+ return NS_OK;
+}
+
+//
+// rhp: Right now, this is the same as simple DisplayMessage, but it will change
+// to support print rendering.
+//
+NS_IMETHODIMP nsNntpService::DisplayMessageForPrinting(const char* aMessageURI, nsISupports * aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow, nsIUrlListener * aUrlListener, nsIURI ** aURL)
+{
+ mPrintingOperation = true;
+ nsresult rv = DisplayMessage(aMessageURI, aDisplayConsumer, aMsgWindow, aUrlListener, nullptr, aURL);
+ mPrintingOperation = false;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::StreamMessage(const char *aMessageURI, nsISupports *aConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ bool /* convertData */,
+ const nsACString &aAdditionalHeader,
+ bool aLocalOnly,
+ nsIURI **aURL)
+{
+ // The nntp protocol object will look for "header=filter" to decide if it wants to convert
+ // the data instead of using aConvertData. It turns out to be way too hard to pass aConvertData
+ // all the way over to the nntp protocol object.
+ nsAutoCString aURIString(aMessageURI);
+
+ if (!aAdditionalHeader.IsEmpty())
+ {
+ aURIString.FindChar('?') == kNotFound ? aURIString += "?" : aURIString += "&";
+ aURIString += "header=";
+ aURIString += aAdditionalHeader;
+ }
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey key;
+ nsresult rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString urlStr;
+ rv = CreateMessageIDURL(folder, key, getter_Copies(urlStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsNewsAction action = nsINntpUrl::ActionFetchArticle;
+ if (mOpenAttachmentOperation)
+ action = nsINntpUrl::ActionFetchPart;
+
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(urlStr.get(), aUrlListener, aMsgWindow, aURIString.get(),
+ action, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLocalOnly || WeAreOffline())
+ {
+ // Check in the offline cache, then in the mem cache
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl(do_QueryInterface(url, &rv));
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ if (!hasMsgOffline)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ url->SetPort((socketType == nsMsgSocketType::SSL) ?
+ nsINntpUrl::DEFAULT_NNTPS_PORT : nsINntpUrl::DEFAULT_NNTP_PORT);
+
+ rv = IsMsgInMemCache(url, folder, &hasMsgOffline);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Return with an error if we didn't find it in the memory cache either
+ if (!hasMsgOffline)
+ return NS_ERROR_FAILURE;
+
+ msgUrl->SetMsgIsInLocalCache(true);
+ }
+
+ if (aURL)
+ NS_IF_ADDREF(*aURL = url);
+
+ return GetMessageFromUrl(url, aMsgWindow, aConsumer);
+}
+
+NS_IMETHODIMP nsNntpService::StreamHeaders(const char *aMessageURI,
+ nsIStreamListener *aConsumer,
+ nsIUrlListener *aUrlListener,
+ bool aLocalOnly,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+ NS_ENSURE_ARG_POINTER(aConsumer);
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (key == nsMsgKey_None)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ if (hasMsgOffline)
+ {
+ int64_t messageOffset;
+ uint32_t messageSize;
+ folder->GetOfflineFileStream(key, &messageOffset, &messageSize, getter_AddRefs(inputStream));
+ if (inputStream)
+ return MsgStreamMsgHeaders(inputStream, aConsumer);
+ }
+ nsAutoCString urlStr;
+ rv = CreateMessageIDURL(folder, key, getter_Copies(urlStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLocalOnly)
+ return NS_ERROR_FAILURE;
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpService::IsMsgInMemCache(nsIURI *aUrl,
+ nsIMsgFolder *aFolder,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ *aResult = false;
+ nsresult rv;
+
+ if (mCacheStorage)
+ {
+ // NNTP urls are truncated at the query part when used as cache keys.
+ nsCOMPtr <nsIURI> newUri;
+ aUrl->Clone(getter_AddRefs(newUri));
+ nsAutoCString path;
+ newUri->GetPath(path);
+ int32_t pos = path.FindChar('?');
+ if (pos != kNotFound) {
+ path.SetLength(pos);
+ newUri->SetPath(path);
+ }
+ bool exists;
+ rv = mCacheStorage->Exists(newUri, EmptyCString(), &exists);
+ if (NS_SUCCEEDED(rv) && exists) {
+ *aResult = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::Search(nsIMsgSearchSession *aSearchSession, nsIMsgWindow *aMsgWindow, nsIMsgFolder *aMsgFolder, const char *aSearchUri)
+{
+ NS_ENSURE_ARG(aMsgFolder);
+ NS_ENSURE_ARG(aSearchUri);
+
+ nsresult rv;
+
+ nsCString searchUrl;
+ rv = aMsgFolder->GetURI(searchUrl);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ searchUrl.Append(aSearchUri);
+
+ nsCOMPtr <nsIUrlListener> urlListener = do_QueryInterface(aSearchSession);
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(searchUrl.get(), urlListener, aMsgWindow, nullptr, nsINntpUrl::ActionSearch, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(url));
+ if (msgurl)
+ msgurl->SetSearchSession(aSearchSession);
+
+ // run the url to update the counts
+ return RunNewsUrl(url, nullptr, nullptr);
+}
+
+NS_IMETHODIMP
+nsNntpService::GetListOfGroupsOnServer(nsINntpIncomingServer *aNntpServer, nsIMsgWindow *aMsgWindow, bool aGetOnlyNew)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aNntpServer);
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(aNntpServer, &rv);
+ if (NS_FAILED(rv)) return rv;
+ if (!server) return NS_ERROR_FAILURE;
+
+ nsCString serverUri;
+ rv = server->GetServerURI(serverUri);
+ nsNewsAction newsAction;
+ if (aGetOnlyNew)
+ {
+ serverUri.AppendLiteral("/?newgroups");
+ newsAction = nsINntpUrl::ActionListNewGroups;
+ }
+ else
+ {
+ serverUri.AppendLiteral("/*");
+ newsAction = nsINntpUrl::ActionListGroups;
+ }
+
+ nsCOMPtr <nsIUrlListener> listener = do_QueryInterface(aNntpServer, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(serverUri.get(), listener, aMsgWindow, nullptr, newsAction, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now run the url to add the rest of the groups
+ return RunNewsUrl(url, aMsgWindow, nullptr);
+}
+
+
+NS_IMETHODIMP
+nsNntpService::Handle(nsICommandLine* aCmdLine)
+{
+ NS_ENSURE_ARG_POINTER(aCmdLine);
+
+ nsresult rv;
+ bool found;
+
+ rv = aCmdLine->HandleFlag(NS_LITERAL_STRING("news"), false, &found);
+ if (NS_SUCCEEDED(rv) && found) {
+ nsCOMPtr<nsIWindowWatcher> wwatch (do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE);
+
+ nsCOMPtr<mozIDOMWindowProxy> opened;
+ wwatch->OpenWindow(nullptr, "chrome://messenger/content/", "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar",
+ nullptr, getter_AddRefs(opened));
+ aCmdLine->SetPreventDefault(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetHelpInfo(nsACString& aResult)
+{
+ aResult.Assign(NS_LITERAL_CSTRING(" -news Open the news client.\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::HandleContent(const char * aContentType, nsIInterfaceRequestor* aWindowContext, nsIRequest *request)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(request);
+
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check for x-application-newsgroup or x-application-newsgroup-listids
+ if (PL_strncasecmp(aContentType, "x-application-newsgroup", 23) == 0)
+ {
+ nsCOMPtr<nsIURI> uri;
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(uri);
+ if (mailUrl)
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = mailUrl->GetFolder(getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // No folder means we can't handle this
+ if (!msgFolder)
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+
+ nsCString folderURL;
+ rv = msgFolder->GetURI(folderURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // this is all we need for listing newsgroup ids.
+ if (!PL_strcasecmp(aContentType, "x-application-newsgroup-listids"))
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ mailUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (!msgWindow)
+ {
+ // This came from a docshell that didn't set msgWindow, so find one
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+
+ if (!msgWindow)
+ {
+ // We need to create a 3-pane window, then
+ nsCOMPtr<nsIWindowWatcher> wwatcher =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsCString> arg =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
+ arg->SetData(folderURL);
+
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ rv = wwatcher->OpenWindow(nullptr, "chrome://messenger/content/",
+ "_blank", "chome,all,dialog=no", arg, getter_AddRefs(newWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ msgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectFolder(folderURL);
+ }
+ request->Cancel(NS_BINDING_ABORTED);
+ }
+ } else // The content-type was not x-application-newsgroup.
+ rv = NS_ERROR_WONT_HANDLE_CONTENT;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::MessageURIToMsgHdr(const char *uri, nsIMsgDBHdr **_retval)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsMsgKey msgKey;
+
+ rv = DecomposeNewsMessageURI(uri, getter_AddRefs(folder), &msgKey);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!folder)
+ return NS_ERROR_NULL_POINTER;
+
+ rv = folder->GetMessageHeader(msgKey, _retval);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::DownloadNewsgroupsForOffline(nsIMsgWindow *aMsgWindow, nsIUrlListener *aListener)
+{
+ RefPtr<nsMsgDownloadAllNewsgroups> newsgroupDownloader =
+ new nsMsgDownloadAllNewsgroups(aMsgWindow, aListener);
+ return newsgroupDownloader->ProcessNextGroup();
+}
+
+NS_IMETHODIMP nsNntpService::GetCacheStorage(nsICacheStorage **result)
+{
+ nsresult rv = NS_OK;
+ if (!mCacheStorage)
+ {
+ nsCOMPtr<nsICacheStorageService> cacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<MailnewsLoadContextInfo> lci =
+ new MailnewsLoadContextInfo(false, false, mozilla::NeckoOriginAttributes());
+
+ rv = cacheStorageService->MemoryCacheStorage(lci, getter_AddRefs(mCacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*result = mCacheStorage);
+ return rv;
+}
diff --git a/mailnews/news/src/nsNntpService.h b/mailnews/news/src/nsNntpService.h
new file mode 100644
index 000000000..58c2699f2
--- /dev/null
+++ b/mailnews/news/src/nsNntpService.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNntpService_h___
+#define nsNntpService_h___
+
+#include "nsINntpService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIMsgMessageService.h"
+#include "nsINntpIncomingServer.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIFile.h"
+#include "MailNewsTypes.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIMsgWindow.h"
+#include "nsINntpUrl.h"
+#include "nsCOMPtr.h"
+#include "nsIContentHandler.h"
+#include "nsICacheStorage.h"
+
+#include "nsICommandLineHandler.h"
+
+class nsIURI;
+class nsIUrlListener;
+
+class nsNntpService : public nsINntpService,
+ public nsIMsgMessageService,
+ public nsIMsgMessageFetchPartService,
+ public nsIProtocolHandler,
+ public nsIMsgProtocolInfo,
+ public nsICommandLineHandler,
+ public nsIContentHandler
+{
+public:
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINNTPSERVICE
+ NS_DECL_NSIMSGMESSAGESERVICE
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIMSGPROTOCOLINFO
+ NS_DECL_NSICONTENTHANDLER
+ NS_DECL_NSIMSGMESSAGEFETCHPARTSERVICE
+ NS_DECL_NSICOMMANDLINEHANDLER
+
+ // nsNntpService
+ nsNntpService();
+
+protected:
+ virtual ~nsNntpService();
+
+ nsresult GetNntpServerByAccount(const char *aAccountKey, nsIMsgIncomingServer **aNntpServer);
+ nsresult SetUpNntpUrlForPosting(const char *aAccountKey, char **newsUrlSpec);
+ nsresult FindHostFromGroup(nsCString &host, nsCString &groupName);
+ nsresult FindServerWithNewsgroup(nsCString &host, nsCString &groupName);
+
+ nsresult CreateMessageIDURL(nsIMsgFolder *folder, nsMsgKey key, char **url);
+ nsresult GetMessageFromUrl(nsIURI *aUrl, nsIMsgWindow *aMsgWindow, nsISupports *aDisplayConsumer);
+ // a convience routine used to put together news urls
+ nsresult ConstructNntpUrl(const char * urlString, nsIUrlListener *aUrlListener, nsIMsgWindow * aMsgWindow, const char *originalMessageUri, int32_t action, nsIURI ** aUrl);
+ nsresult CreateNewsAccount(const char *aHostname, bool aIsSecure, int32_t aPort, nsIMsgIncomingServer **aServer);
+ nsresult GetServerForUri(nsIURI *aUri, nsINntpIncomingServer **aProtocol);
+ // a convience routine to run news urls
+ nsresult RunNewsUrl (nsIURI * aUrl, nsIMsgWindow *aMsgWindow, nsISupports * aConsumer);
+ // a convience routine to go from folder uri to msg folder
+ nsresult GetFolderFromUri(const char *uri, nsIMsgFolder **folder);
+ nsresult DecomposeNewsMessageURI(const char * aMessageURI, nsIMsgFolder ** aFolder, nsMsgKey *aMsgKey);
+
+ bool mPrintingOperation; // Flag for printing operations
+ bool mOpenAttachmentOperation; // Flag for opening attachments
+
+ nsCOMPtr<nsICacheStorage> mCacheStorage; // the cache storage used by news
+};
+
+#endif /* nsNntpService_h___ */
diff --git a/mailnews/news/src/nsNntpUrl.cpp b/mailnews/news/src/nsNntpUrl.cpp
new file mode 100644
index 000000000..5cdc3ba8d
--- /dev/null
+++ b/mailnews/news/src/nsNntpUrl.cpp
@@ -0,0 +1,578 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+
+#include "nsIURL.h"
+#include "nsNntpUrl.h"
+
+#include "nsStringGlue.h"
+#include "nsNewsUtils.h"
+#include "nsMsgUtils.h"
+
+#include "nntpCore.h"
+
+#include "nsCOMPtr.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgNewsCID.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsINntpService.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsServiceManagerUtils.h"
+
+
+nsNntpUrl::nsNntpUrl()
+{
+ m_newsgroupPost = nullptr;
+ m_newsAction = nsINntpUrl::ActionUnknown;
+ m_addDummyEnvelope = false;
+ m_canonicalLineEnding = false;
+ m_filePath = nullptr;
+ m_getOldMessages = false;
+ m_key = nsMsgKey_None;
+}
+
+nsNntpUrl::~nsNntpUrl()
+{
+}
+
+NS_IMPL_ADDREF_INHERITED(nsNntpUrl, nsMsgMailNewsUrl)
+NS_IMPL_RELEASE_INHERITED(nsNntpUrl, nsMsgMailNewsUrl)
+
+NS_INTERFACE_MAP_BEGIN(nsNntpUrl)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINntpUrl)
+ NS_INTERFACE_MAP_ENTRY(nsINntpUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgMessageUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgI18NUrl)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgMailNewsUrl)
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin nsINntpUrl specific support
+////////////////////////////////////////////////////////////////////////////////
+
+/* News URI parsing explanation:
+ * We support 3 different news URI schemes, essentially boiling down to 8
+ * different formats:
+ * news://host/group
+ * news://host/message
+ * news://host/
+ * news:group
+ * news:message
+ * nntp://host/group
+ * nntp://host/group/key
+ * news-message://host/group#key
+ *
+ * In addition, we use queries on the news URIs with authorities for internal
+ * NNTP processing. The most important one is ?group=group&key=key, for cache
+ * canonicalization.
+ */
+
+NS_IMETHODIMP nsNntpUrl::SetSpec(const nsACString &aSpec)
+{
+ // For [s]news: URIs, we need to munge the spec if it is no authority, because
+ // the URI parser guesses the wrong thing otherwise
+ nsCString parseSpec(aSpec);
+ int32_t colon = parseSpec.Find(":");
+
+ // Our smallest scheme is 4 characters long, so colon must be at least 4
+ if (colon < 4 || colon + 1 == (int32_t) parseSpec.Length())
+ return NS_ERROR_MALFORMED_URI;
+
+ if (Substring(parseSpec, colon - 4, 4).EqualsLiteral("news") &&
+ parseSpec[colon + 1] != '/')
+ {
+ // To make this parse properly, we add in three slashes, which convinces the
+ // parser that the authority component is empty.
+ parseSpec = Substring(aSpec, 0, colon + 1);
+ parseSpec.AppendLiteral("///");
+ parseSpec += Substring(aSpec, colon + 1);
+ }
+
+ nsresult rv = nsMsgMailNewsUrl::SetSpec(parseSpec);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString scheme;
+ rv = GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (scheme.EqualsLiteral("news") || scheme.EqualsLiteral("snews"))
+ rv = ParseNewsURL();
+ else if (scheme.EqualsLiteral("nntp") || scheme.EqualsLiteral("nntps"))
+ rv = ParseNntpURL();
+ else if (scheme.EqualsLiteral("news-message"))
+ {
+ nsAutoCString spec;
+ rv = GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsParseNewsMessageURI(spec.get(), m_group, &m_key);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+ }
+ else
+ return NS_ERROR_MALFORMED_URI;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = DetermineNewsAction();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsNntpUrl::ParseNewsURL()
+{
+ // The path here is the group/msgid portion
+ nsAutoCString path;
+ nsresult rv = GetFilePath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Drop the potential beginning from the path
+ if (path.Length() && path[0] == '/')
+ path = Substring(path, 1);
+
+ // The presence of an `@' is a sign we have a msgid
+ if (path.Find("@") != -1 || path.Find("%40") != -1)
+ {
+ MsgUnescapeString(path, 0, m_messageID);
+
+ // Set group, key for ?group=foo&key=123 uris
+ nsAutoCString spec;
+ rv = GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t groupPos = spec.Find(kNewsURIGroupQuery); // find ?group=
+ int32_t keyPos = spec.Find(kNewsURIKeyQuery); // find &key=
+ if (groupPos != kNotFound && keyPos != kNotFound)
+ {
+ // get group name and message key
+ m_group = Substring(spec, groupPos + kNewsURIGroupQueryLen,
+ keyPos - groupPos - kNewsURIGroupQueryLen);
+ nsCString keyStr(Substring(spec, keyPos + kNewsURIKeyQueryLen));
+ m_key = keyStr.ToInteger(&rv, 10);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+ }
+ }
+ else
+ MsgUnescapeString(path, 0, m_group);
+
+ return NS_OK;
+}
+
+nsresult nsNntpUrl::ParseNntpURL()
+{
+ nsAutoCString path;
+ nsresult rv = GetFilePath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (path.Length() > 0 && path[0] == '/')
+ path = Substring(path, 1);
+
+ if (path.IsEmpty())
+ return NS_ERROR_MALFORMED_URI;
+
+ int32_t slash = path.FindChar('/');
+ if (slash == -1)
+ {
+ m_group = path;
+ m_key = nsMsgKey_None;
+ }
+ else
+ {
+ m_group = Substring(path, 0, slash);
+ nsAutoCString keyStr;
+ keyStr = Substring(path, slash + 1);
+ m_key = keyStr.ToInteger(&rv, 10);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+
+ // Keys must be at least one
+ if (m_key == 0)
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsNntpUrl::DetermineNewsAction()
+{
+ nsAutoCString path;
+ nsresult rv = nsMsgMailNewsUrl::GetPath(path);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString query;
+ rv = GetQuery(query);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (query.EqualsLiteral("cancel"))
+ {
+ m_newsAction = nsINntpUrl::ActionCancelArticle;
+ return NS_OK;
+ }
+ if (query.EqualsLiteral("list-ids"))
+ {
+ m_newsAction = nsINntpUrl::ActionListIds;
+ return NS_OK;
+ }
+ if (query.EqualsLiteral("newgroups"))
+ {
+ m_newsAction = nsINntpUrl::ActionListNewGroups;
+ return NS_OK;
+ }
+ if (StringBeginsWith(query, NS_LITERAL_CSTRING("search")))
+ {
+ m_newsAction = nsINntpUrl::ActionSearch;
+ return NS_OK;
+ }
+ if (StringBeginsWith(query, NS_LITERAL_CSTRING("part=")) ||
+ query.Find("&part=") > 0)
+ {
+ // news://news.mozilla.org:119/3B98D201.3020100%40cs.com?part=1
+ // news://news.mozilla.org:119/b58dme%24aia2%40ripley.netscape.com?header=print&part=1.2&type=image/jpeg&filename=Pole.jpg
+ m_newsAction = nsINntpUrl::ActionFetchPart;
+ return NS_OK;
+ }
+
+ if (!m_messageID.IsEmpty() || m_key != nsMsgKey_None)
+ {
+ m_newsAction = nsINntpUrl::ActionFetchArticle;
+ return NS_OK;
+ }
+
+ if (m_group.Find("*") >= 0)
+ {
+ // If the group is a wildmat, list groups instead of grabbing a group.
+ m_newsAction = nsINntpUrl::ActionListGroups;
+ return NS_OK;
+ }
+ if (!m_group.IsEmpty())
+ {
+ m_newsAction = nsINntpUrl::ActionGetNewNews;
+ return NS_OK;
+ }
+
+ // At this point, we have a URI that contains neither a query, a group, nor a
+ // message ID. Ergo, we don't know what it is.
+ m_newsAction = nsINntpUrl::ActionUnknown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::SetGetOldMessages(bool aGetOldMessages)
+{
+ m_getOldMessages = aGetOldMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetGetOldMessages(bool * aGetOldMessages)
+{
+ NS_ENSURE_ARG(aGetOldMessages);
+ *aGetOldMessages = m_getOldMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetNewsAction(nsNewsAction *aNewsAction)
+{
+ if (aNewsAction)
+ *aNewsAction = m_newsAction;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsNntpUrl::SetNewsAction(nsNewsAction aNewsAction)
+{
+ m_newsAction = aNewsAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetGroup(nsACString &group)
+{
+ group = m_group;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetMessageID(nsACString &messageID)
+{
+ messageID = m_messageID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetKey(nsMsgKey *key)
+{
+ NS_ENSURE_ARG_POINTER(key);
+ *key = m_key;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetPrincipalSpec(nsACString& aPrincipalSpec)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpUrl::SetUri(const char * aURI)
+{
+ mURI = aURI;
+ return NS_OK;
+}
+
+// from nsIMsgMessageUrl
+NS_IMETHODIMP nsNntpUrl::GetUri(char ** aURI)
+{
+ nsresult rv = NS_OK;
+
+ // if we have been given a uri to associate with this url, then use it
+ // otherwise try to reconstruct a URI on the fly....
+ if (mURI.IsEmpty()) {
+ nsAutoCString spec;
+ rv = GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mURI = spec;
+ }
+
+ *aURI = ToNewCString(mURI);
+ if (!*aURI) return NS_ERROR_OUT_OF_MEMORY;
+ return rv;
+}
+
+
+NS_IMPL_GETSET(nsNntpUrl, AddDummyEnvelope, bool, m_addDummyEnvelope)
+NS_IMPL_GETSET(nsNntpUrl, CanonicalLineEnding, bool, m_canonicalLineEnding)
+
+NS_IMETHODIMP nsNntpUrl::SetMessageFile(nsIFile * aFile)
+{
+ m_messageFile = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetMessageFile(nsIFile ** aFile)
+{
+ if (aFile)
+ NS_IF_ADDREF(*aFile = m_messageFile);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// End nsINntpUrl specific support
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsNntpUrl::SetMessageToPost(nsINNTPNewsgroupPost *post)
+{
+ m_newsgroupPost = post;
+ if (post)
+ SetNewsAction(nsINntpUrl::ActionPostArticle);
+ return NS_OK;
+}
+
+nsresult nsNntpUrl::GetMessageToPost(nsINNTPNewsgroupPost **aPost)
+{
+ NS_ENSURE_ARG_POINTER(aPost);
+ NS_IF_ADDREF(*aPost = m_newsgroupPost);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::SetMessageHeader(nsIMsgDBHdr *aMsgHdr)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetMessageHeader(nsIMsgDBHdr ** aMsgHdr)
+{
+ nsresult rv;
+
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgMessageService> msgService = do_QueryInterface(nntpService, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString spec(mOriginalSpec);
+ if (spec.IsEmpty()) {
+ // Handle the case where necko directly runs an internal news:// URL,
+ // one that looks like news://host/message-id?group=mozilla.announce&key=15
+ // Other sorts of URLs -- e.g. news://host/message-id -- will not succeed.
+ rv = GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return msgService->MessageURIToMsgHdr(spec.get(), aMsgHdr);
+}
+
+NS_IMETHODIMP nsNntpUrl::IsUrlType(uint32_t type, bool *isType)
+{
+ NS_ENSURE_ARG(isType);
+
+ switch(type)
+ {
+ case nsIMsgMailNewsUrl::eDisplay:
+ *isType = (m_newsAction == nsINntpUrl::ActionFetchArticle);
+ break;
+ default:
+ *isType = false;
+ };
+
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP
+nsNntpUrl::GetOriginalSpec(char **aSpec)
+{
+ NS_ENSURE_ARG_POINTER(aSpec);
+ *aSpec = ToNewCString(mOriginalSpec);
+ if (!*aSpec) return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpUrl::SetOriginalSpec(const char *aSpec)
+{
+ mOriginalSpec = aSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpUrl::GetServer(nsIMsgIncomingServer **aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ nsresult rv;
+ nsAutoCString scheme, user, host;
+
+ GetScheme(scheme);
+ GetUsername(user);
+ GetHost(host);
+
+ // No authority -> no server
+ if (host.IsEmpty())
+ {
+ *aServer = nullptr;
+ return NS_OK;
+ }
+
+ // Looking up the server...
+ // news-message is used purely internally, so it can never refer to the real
+ // attribute. nntp is never used internally, so it probably refers to the real
+ // one. news is used both internally and externally, so it could refer to
+ // either one. We'll assume it's an internal one first, though.
+ bool isNews = scheme.EqualsLiteral("news") || scheme.EqualsLiteral("snews");
+ bool isNntp = scheme.EqualsLiteral("nntp") || scheme.EqualsLiteral("nntps");
+
+ bool tryReal = isNntp;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ignoring return results: it is perfectly acceptable for the server to not
+ // exist, but FindServer (and not FindRealServer) throws NS_ERROR_UNEXPECTED
+ // in this case.
+ *aServer = nullptr;
+ if (tryReal)
+ accountManager->FindRealServer(user, host, NS_LITERAL_CSTRING("nntp"), 0,
+ aServer);
+ else
+ accountManager->FindServer(user, host, NS_LITERAL_CSTRING("nntp"), aServer);
+ if (!*aServer && (isNews || isNntp))
+ {
+ // Didn't find it, try the other option
+ if (tryReal)
+ accountManager->FindServer(user, host, NS_LITERAL_CSTRING("nntp"),
+ aServer);
+ else
+ accountManager->FindRealServer(user, host, NS_LITERAL_CSTRING("nntp"), 0,
+ aServer);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetFolder(nsIMsgFolder **msgFolder)
+{
+ NS_ENSURE_ARG_POINTER(msgFolder);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Need a server and a group to get the folder
+ if (!server || m_group.IsEmpty())
+ {
+ *msgFolder = nullptr;
+ return NS_OK;
+ }
+
+ // Find the group on the server
+ nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasGroup = false;
+ rv = nntpServer->ContainsNewsgroup(m_group, &hasGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasGroup)
+ {
+ *msgFolder = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgNewsFolder> newsFolder;
+ rv = nntpServer->FindGroup(m_group, getter_AddRefs(newsFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return newsFolder->QueryInterface(NS_GET_IID(nsIMsgFolder), (void**)msgFolder);
+}
+
+NS_IMETHODIMP
+nsNntpUrl::GetFolderCharset(char **aCharacterSet)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetFolder(getter_AddRefs(folder));
+ // don't assert here. this can happen if there is no message folder
+ // like when we display a news://host/message-id url
+ if (NS_FAILED(rv) || !folder)
+ return rv;
+ nsCString tmpStr;
+ rv = folder->GetCharset(tmpStr);
+ *aCharacterSet = ToNewCString(tmpStr);
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetFolderCharsetOverride(bool * aCharacterSetOverride)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ENSURE_TRUE(folder, NS_ERROR_FAILURE);
+ rv = folder->GetCharsetOverride(aCharacterSetOverride);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetCharsetOverRide(char ** aCharacterSet)
+{
+ if (!mCharsetOverride.IsEmpty())
+ *aCharacterSet = ToNewCString(mCharsetOverride);
+ else
+ *aCharacterSet = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::SetCharsetOverRide(const char * aCharacterSet)
+{
+ mCharsetOverride = aCharacterSet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef,
+ nsIURI **_retval)
+{
+ nsresult rv;
+ rv = nsMsgMailNewsUrl::CloneInternal(aRefHandlingMode, newRef, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMessageUrl> newsurl = do_QueryInterface(*_retval, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return newsurl->SetUri(mURI.get());
+}
+
diff --git a/mailnews/news/src/nsNntpUrl.h b/mailnews/news/src/nsNntpUrl.h
new file mode 100644
index 000000000..db040a395
--- /dev/null
+++ b/mailnews/news/src/nsNntpUrl.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNntpUrl_h__
+#define nsNntpUrl_h__
+
+#include "nsINntpUrl.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsINNTPNewsgroupPost.h"
+#include "nsIFile.h"
+
+class nsNntpUrl : public nsINntpUrl, public nsMsgMailNewsUrl, public nsIMsgMessageUrl, public nsIMsgI18NUrl
+{
+public:
+ NS_DECL_NSINNTPURL
+ NS_DECL_NSIMSGMESSAGEURL
+ NS_DECL_NSIMSGI18NURL
+
+ // nsIURI over-ride...
+ NS_IMETHOD SetSpec(const nsACString &aSpec) override;
+
+ NS_IMETHOD IsUrlType(uint32_t type, bool *isType) override;
+
+ // nsIMsgMailNewsUrl overrides
+ NS_IMETHOD GetServer(nsIMsgIncomingServer **server) override;
+ NS_IMETHOD GetFolder(nsIMsgFolder **msgFolder) override;
+ NS_IMETHOD CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef,nsIURI **_retval) override;
+
+ // nsNntpUrl
+ nsNntpUrl();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ virtual ~nsNntpUrl();
+ nsresult DetermineNewsAction();
+ nsresult ParseNewsURL();
+ nsresult ParseNntpURL();
+
+ nsCOMPtr<nsINNTPNewsgroupPost> m_newsgroupPost;
+ nsNewsAction m_newsAction; // the action this url represents...parse mailbox, display messages, etc.
+
+ nsCString mURI; // the RDF URI associated with this url.
+ nsCString mCharsetOverride; // used by nsIMsgI18NUrl...
+
+ nsCString mOriginalSpec;
+ nsCOMPtr <nsIFile> m_filePath;
+
+ // used by save message to disk
+ nsCOMPtr<nsIFile> m_messageFile;
+
+ bool m_addDummyEnvelope;
+ bool m_canonicalLineEnding;
+ bool m_getOldMessages;
+
+ nsCString m_group;
+ nsCString m_messageID;
+ nsMsgKey m_key;
+};
+
+#endif // nsNntpUrl_h__